commit 7a77188758721931313ae6814af1d7e7dc8421c9 Author: Intege-rs Date: Sat Jan 24 07:55:36 2026 -0500 v0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1fac4d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ +.kotlin + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..4b3d693 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Hytale-Server \ No newline at end of file diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..14746e7 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..32cf4db --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/_decomp/decomp.ps1 b/_decomp/decomp.ps1 new file mode 100644 index 0000000..e69de29 diff --git a/_decomp/vineflower-1.11.2-slim.jar b/_decomp/vineflower-1.11.2-slim.jar new file mode 100644 index 0000000..855a773 Binary files /dev/null and b/_decomp/vineflower-1.11.2-slim.jar differ diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..94004fc --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + id("java") +} + +group = "hytale-server.kotlin" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} + +sourceSets { + getByName("main") { + java.setSrcDirs(listOf("src")) + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..887a104 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Jan 24 07:48:30 EST 2026 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for 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 «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..64b935c --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "Hytale-Server" \ No newline at end of file diff --git a/src/com/hypixel/fastutil/FastCollection.java b/src/com/hypixel/fastutil/FastCollection.java new file mode 100644 index 0000000..8600eee --- /dev/null +++ b/src/com/hypixel/fastutil/FastCollection.java @@ -0,0 +1,82 @@ +package com.hypixel.fastutil; + +import java.util.Collection; + +public interface FastCollection extends Collection { + void forEachWithFloat(FastCollection.FastConsumerF var1, float var2); + + void forEachWithInt(FastCollection.FastConsumerI var1, int var2); + + void forEachWithLong(FastCollection.FastConsumerL var1, long var2); + + void forEach( + FastCollection.FastConsumerD9 var1, + A var2, + double var3, + double var5, + double var7, + double var9, + double var11, + double var13, + double var15, + double var17, + double var19, + B var21, + C var22, + D var23 + ); + + void forEach( + FastCollection.FastConsumerD6 var1, + A var2, + double var3, + double var5, + double var7, + double var9, + double var11, + double var13, + B var15, + C var16, + D var17 + ); + + @FunctionalInterface + public interface FastConsumerD6 { + void accept(A var1, B var2, double var3, double var5, double var7, double var9, double var11, double var13, C var15, D var16, E var17); + } + + @FunctionalInterface + public interface FastConsumerD9 { + void accept( + A var1, + B var2, + double var3, + double var5, + double var7, + double var9, + double var11, + double var13, + double var15, + double var17, + double var19, + C var21, + D var22, + E var23 + ); + } + + @FunctionalInterface + public interface FastConsumerF { + void accept(A var1, float var2); + } + + @FunctionalInterface + public interface FastConsumerI { + void accept(A var1, int var2); + } + + @FunctionalInterface + public interface FastConsumerL { + void accept(A var1, long var2); + } +} diff --git a/src/com/hypixel/fastutil/bytes/Byte2ByteOperator.java b/src/com/hypixel/fastutil/bytes/Byte2ByteOperator.java new file mode 100644 index 0000000..526f08e --- /dev/null +++ b/src/com/hypixel/fastutil/bytes/Byte2ByteOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.bytes; + +@FunctionalInterface +public interface Byte2ByteOperator { + byte apply(byte var1, byte var2); +} diff --git a/src/com/hypixel/fastutil/bytes/Byte2CharOperator.java b/src/com/hypixel/fastutil/bytes/Byte2CharOperator.java new file mode 100644 index 0000000..cc1bef4 --- /dev/null +++ b/src/com/hypixel/fastutil/bytes/Byte2CharOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.bytes; + +@FunctionalInterface +public interface Byte2CharOperator { + char apply(byte var1, char var2); +} diff --git a/src/com/hypixel/fastutil/bytes/Byte2DoubleOperator.java b/src/com/hypixel/fastutil/bytes/Byte2DoubleOperator.java new file mode 100644 index 0000000..9dd7858 --- /dev/null +++ b/src/com/hypixel/fastutil/bytes/Byte2DoubleOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.bytes; + +@FunctionalInterface +public interface Byte2DoubleOperator { + double apply(byte var1, double var2); +} diff --git a/src/com/hypixel/fastutil/bytes/Byte2FloatOperator.java b/src/com/hypixel/fastutil/bytes/Byte2FloatOperator.java new file mode 100644 index 0000000..94560a7 --- /dev/null +++ b/src/com/hypixel/fastutil/bytes/Byte2FloatOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.bytes; + +@FunctionalInterface +public interface Byte2FloatOperator { + float apply(byte var1, float var2); +} diff --git a/src/com/hypixel/fastutil/bytes/Byte2IntOperator.java b/src/com/hypixel/fastutil/bytes/Byte2IntOperator.java new file mode 100644 index 0000000..1948db0 --- /dev/null +++ b/src/com/hypixel/fastutil/bytes/Byte2IntOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.bytes; + +@FunctionalInterface +public interface Byte2IntOperator { + int apply(byte var1, int var2); +} diff --git a/src/com/hypixel/fastutil/bytes/Byte2LongOperator.java b/src/com/hypixel/fastutil/bytes/Byte2LongOperator.java new file mode 100644 index 0000000..bda657e --- /dev/null +++ b/src/com/hypixel/fastutil/bytes/Byte2LongOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.bytes; + +@FunctionalInterface +public interface Byte2LongOperator { + long apply(byte var1, long var2); +} diff --git a/src/com/hypixel/fastutil/bytes/Byte2ObjectConcurrentHashMap.java b/src/com/hypixel/fastutil/bytes/Byte2ObjectConcurrentHashMap.java new file mode 100644 index 0000000..bba1450 --- /dev/null +++ b/src/com/hypixel/fastutil/bytes/Byte2ObjectConcurrentHashMap.java @@ -0,0 +1,10256 @@ +package com.hypixel.fastutil.bytes; + +import com.hypixel.fastutil.FastCollection; +import com.hypixel.fastutil.util.SneakyThrow; +import com.hypixel.fastutil.util.TLRUtil; +import it.unimi.dsi.fastutil.bytes.Byte2ObjectMap; +import it.unimi.dsi.fastutil.bytes.ByteCollection; +import it.unimi.dsi.fastutil.bytes.ByteConsumer; +import it.unimi.dsi.fastutil.bytes.ByteIterator; +import it.unimi.dsi.fastutil.bytes.ByteSet; +import it.unimi.dsi.fastutil.bytes.ByteSpliterator; +import it.unimi.dsi.fastutil.objects.ObjectCollection; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import it.unimi.dsi.fastutil.objects.ObjectSpliterator; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.CountedCompleter; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.DoubleBinaryOperator; +import java.util.function.Function; +import java.util.function.IntBinaryOperator; +import java.util.function.LongBinaryOperator; +import java.util.function.Predicate; +import java.util.function.ToDoubleFunction; +import java.util.function.ToIntFunction; +import java.util.function.ToLongFunction; +import sun.misc.Unsafe; + +public class Byte2ObjectConcurrentHashMap { + protected static final long serialVersionUID = 7249069246763182397L; + protected static final int MAXIMUM_CAPACITY = 1073741824; + protected static final int DEFAULT_CAPACITY = 16; + protected static final int MAX_ARRAY_SIZE = 2147483639; + protected static final int DEFAULT_CONCURRENCY_LEVEL = 16; + protected static final float LOAD_FACTOR = 0.75F; + protected static final int TREEIFY_THRESHOLD = 8; + protected static final int UNTREEIFY_THRESHOLD = 6; + protected static final int MIN_TREEIFY_CAPACITY = 64; + protected static final int MIN_TRANSFER_STRIDE = 16; + protected static int RESIZE_STAMP_BITS = 16; + protected static final int MAX_RESIZERS = (1 << 32 - RESIZE_STAMP_BITS) - 1; + protected static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS; + protected static final int MOVED = -1; + protected static final int TREEBIN = -2; + protected static final int RESERVED = -3; + protected static final int HASH_BITS = Integer.MAX_VALUE; + protected static final int NCPU = Runtime.getRuntime().availableProcessors(); + protected transient volatile Byte2ObjectConcurrentHashMap.Node[] table; + protected transient volatile Byte2ObjectConcurrentHashMap.Node[] nextTable; + protected transient volatile long baseCount; + protected transient volatile int sizeCtl; + protected transient volatile int transferIndex; + protected transient volatile int cellsBusy; + protected transient volatile Byte2ObjectConcurrentHashMap.CounterCell[] counterCells; + protected transient Byte2ObjectConcurrentHashMap.KeySetView keySet; + protected transient Byte2ObjectConcurrentHashMap.ValuesView values; + protected transient Byte2ObjectConcurrentHashMap.EntrySetView entrySet; + protected final byte EMPTY; + protected static final Unsafe U; + protected static final long SIZECTL; + protected static final long TRANSFERINDEX; + protected static final long BASECOUNT; + protected static final long CELLSBUSY; + protected static final long CELLVALUE; + protected static final long ABASE; + protected static final int ASHIFT; + + protected static final int spread(int h) { + return (h ^ h >>> 16) & 2147483647; + } + + protected static final int tableSizeFor(int c) { + int n = c - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return n < 0 ? 1 : (n >= 1073741824 ? 1073741824 : n + 1); + } + + protected static final Byte2ObjectConcurrentHashMap.Node tabAt(Byte2ObjectConcurrentHashMap.Node[] tab, int i) { + return (Byte2ObjectConcurrentHashMap.Node)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); + } + + protected static final boolean casTabAt( + Byte2ObjectConcurrentHashMap.Node[] tab, int i, Byte2ObjectConcurrentHashMap.Node c, Byte2ObjectConcurrentHashMap.Node v + ) { + return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v); + } + + protected static final void setTabAt(Byte2ObjectConcurrentHashMap.Node[] tab, int i, Byte2ObjectConcurrentHashMap.Node v) { + U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v); + } + + public Byte2ObjectConcurrentHashMap() { + this.EMPTY = -1; + } + + public Byte2ObjectConcurrentHashMap(boolean nonce, byte emptyValue) { + this.EMPTY = emptyValue; + } + + public Byte2ObjectConcurrentHashMap(int initialCapacity) { + this(initialCapacity, true, (byte)-1); + } + + public Byte2ObjectConcurrentHashMap(int initialCapacity, boolean nonce, byte emptyValue) { + if (initialCapacity < 0) { + throw new IllegalArgumentException(); + } else { + int cap = initialCapacity >= 536870912 ? 1073741824 : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1); + this.sizeCtl = cap; + this.EMPTY = emptyValue; + } + } + + public Byte2ObjectConcurrentHashMap(Map m, byte emptyValue) { + this.sizeCtl = 16; + this.EMPTY = emptyValue; + this.putAll(m); + } + + public Byte2ObjectConcurrentHashMap(Byte2ObjectConcurrentHashMap m) { + this.sizeCtl = 16; + this.EMPTY = m.EMPTY; + this.putAll(m); + } + + public Byte2ObjectConcurrentHashMap(Byte2ObjectMap m) { + this.sizeCtl = 16; + this.EMPTY = -1; + this.putAll(m); + } + + public Byte2ObjectConcurrentHashMap(Byte2ObjectMap m, byte emptyValue) { + this.sizeCtl = 16; + this.EMPTY = emptyValue; + this.putAll(m); + } + + public Byte2ObjectConcurrentHashMap(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, 1, (byte)-1); + } + + public Byte2ObjectConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel, byte emptyValue) { + if (loadFactor > 0.0F && initialCapacity >= 0 && concurrencyLevel > 0) { + if (initialCapacity < concurrencyLevel) { + initialCapacity = concurrencyLevel; + } + + long size = (long)(1.0 + (float)initialCapacity / loadFactor); + int cap = size >= 1073741824L ? 1073741824 : tableSizeFor((int)size); + this.sizeCtl = cap; + this.EMPTY = emptyValue; + } else { + throw new IllegalArgumentException(); + } + } + + public int size() { + long n = this.sumCount(); + return n < 0L ? 0 : (n > 2147483647L ? Integer.MAX_VALUE : (int)n); + } + + public boolean isEmpty() { + return this.sumCount() <= 0L; + } + + public V get(byte key) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + int h = spread(Byte.hashCode(key)); + Byte2ObjectConcurrentHashMap.Node[] tab = this.table; + Byte2ObjectConcurrentHashMap.Node e; + int n; + if (this.table != null && (n = tab.length) > 0 && (e = tabAt(tab, n - 1 & h)) != null) { + int eh = e.hash; + if (e.hash == h) { + byte ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + return e.val; + } + } else if (eh < 0) { + Byte2ObjectConcurrentHashMap.Node p; + return (p = e.find(h, key)) != null ? p.val : null; + } + + while ((e = e.next) != null) { + if (e.hash == h) { + byte ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + return e.val; + } + } + } + } + + return null; + } + } + + public boolean containsKey(byte key) { + return this.get(key) != null; + } + + public boolean containsValue(Object value) { + if (value == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label85: + while (true) { + if (p != null) { + next = p; + break; + } + + if (baseIndex < baseLimit) { + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab != null && (n = tab.length) > index && index >= 0) { + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + continue; + } + + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + continue label85; + } + } + } + + next = null; + break; + } + + if (p == null) { + break; + } + + V v = p.val; + if (p.val == value || v != null && value.equals(v)) { + return true; + } + } + } + + return false; + } + } + + public V put(byte key, V value) { + return this.putVal(key, value, false); + } + + protected final V putVal(byte key, V value, boolean onlyIfAbsent) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value == null) { + throw new NullPointerException(); + } else { + int hash = spread(Byte.hashCode(key)); + int binCount = 0; + Byte2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Byte2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & hash)) == null) { + if (casTabAt(tab, i, null, new Byte2ObjectConcurrentHashMap.Node<>(this.EMPTY, hash, key, value, null))) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + V oldVal = null; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Byte2ObjectConcurrentHashMap.Node p; + if ((p = ((Byte2ObjectConcurrentHashMap.TreeBin)f).putTreeVal(hash, key, value)) != null) { + oldVal = p.val; + if (!onlyIfAbsent) { + p.val = value; + } + } + } + } else { + binCount = 1; + Byte2ObjectConcurrentHashMap.Node e = f; + + while (true) { + if (e.hash == hash) { + byte ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + oldVal = e.val; + if (!onlyIfAbsent) { + e.val = value; + } + break; + } + } + + Byte2ObjectConcurrentHashMap.Node pred = e; + if ((e = e.next) == null) { + pred.next = new Byte2ObjectConcurrentHashMap.Node<>(this.EMPTY, hash, key, value, null); + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + + if (oldVal != null) { + return oldVal; + } + break; + } + } + } + } + + this.addCount(1L, binCount); + return null; + } + } + + public void putAll(Map m) { + this.tryPresize(m.size()); + + for (Map.Entry e : m.entrySet()) { + this.putVal(e.getKey(), (V)e.getValue(), false); + } + } + + public void putAll(Byte2ObjectConcurrentHashMap m) { + this.tryPresize(m.size()); + + for (Byte2ObjectMap.Entry e : m.byte2ObjectEntrySet()) { + this.putVal(e.getByteKey(), (V)e.getValue(), false); + } + } + + public void putAll(Byte2ObjectMap m) { + this.tryPresize(m.size()); + + for (Byte2ObjectMap.Entry next : m.byte2ObjectEntrySet()) { + this.putVal(next.getByteKey(), (V)next.getValue(), false); + } + } + + public V remove(byte key) { + return this.replaceNode(key, null, null); + } + + @Deprecated + public V remove(Byte key) { + return this.replaceNode(key, null, null); + } + + @Deprecated + public V remove(Object key) { + return this.remove((Byte)key); + } + + protected final V replaceNode(byte key, V value, Object cv) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + int hash = spread(Byte.hashCode(key)); + Byte2ObjectConcurrentHashMap.Node[] tab = this.table; + + Byte2ObjectConcurrentHashMap.Node f; + int n; + int i; + while (tab != null && (n = tab.length) != 0 && (f = tabAt(tab, i = n - 1 & hash)) != null) { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + V oldVal = null; + boolean validated = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + validated = true; + Byte2ObjectConcurrentHashMap.TreeBin t = (Byte2ObjectConcurrentHashMap.TreeBin)f; + Byte2ObjectConcurrentHashMap.TreeNode r = t.root; + Byte2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(hash, key, null)) != null) { + V pv = p.val; + if (cv == null || cv == pv || pv != null && cv.equals(pv)) { + oldVal = pv; + if (value != null) { + p.val = value; + } else if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } + } else { + validated = true; + Byte2ObjectConcurrentHashMap.Node e = f; + Byte2ObjectConcurrentHashMap.Node pred = null; + + do { + if (e.hash == hash) { + byte ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + V ev = e.val; + if (cv == null || cv == ev || ev != null && cv.equals(ev)) { + oldVal = ev; + if (value != null) { + e.val = value; + } else if (pred != null) { + pred.next = e.next; + } else { + setTabAt(tab, i, e.next); + } + } + break; + } + } + + pred = e; + } while ((e = e.next) != null); + } + } + } + + if (validated) { + if (oldVal != null) { + if (value == null) { + this.addCount(-1L, -1); + } + + return oldVal; + } + break; + } + } + } + + return null; + } + } + + public void clear() { + long delta = 0L; + int i = 0; + Byte2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (tab != null && i < tab.length) { + Byte2ObjectConcurrentHashMap.Node f = tabAt(tab, i); + if (f == null) { + i++; + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + i = 0; + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + for (Byte2ObjectConcurrentHashMap.Node p = (Byte2ObjectConcurrentHashMap.Node)(fh >= 0 + ? f + : (f instanceof Byte2ObjectConcurrentHashMap.TreeBin ? ((Byte2ObjectConcurrentHashMap.TreeBin)f).first : null)); + p != null; + p = p.next + ) { + delta--; + } + + setTabAt(tab, i++, null); + } + } + } + } + } + + if (delta != 0L) { + this.addCount(delta, -1); + } + } + + public Byte2ObjectConcurrentHashMap.KeySetView keySet() { + Byte2ObjectConcurrentHashMap.KeySetView ks = this.keySet; + return this.keySet != null ? ks : (this.keySet = this.buildKeySetView()); + } + + protected Byte2ObjectConcurrentHashMap.KeySetView buildKeySetView() { + return new Byte2ObjectConcurrentHashMap.KeySetView<>(this, null); + } + + public FastCollection values() { + Byte2ObjectConcurrentHashMap.ValuesView vs = this.values; + return this.values != null ? vs : (this.values = this.buildValuesView()); + } + + protected Byte2ObjectConcurrentHashMap.ValuesView buildValuesView() { + return new Byte2ObjectConcurrentHashMap.ValuesView<>(this); + } + + public ObjectSet> byte2ObjectEntrySet() { + Byte2ObjectConcurrentHashMap.EntrySetView es = this.entrySet; + return this.entrySet != null ? es : (this.entrySet = this.buildEntrySetView()); + } + + @Deprecated + public ObjectSet> entrySet() { + return this.byte2ObjectEntrySet(); + } + + protected Byte2ObjectConcurrentHashMap.EntrySetView buildEntrySetView() { + return new Byte2ObjectConcurrentHashMap.EntrySetView<>(this); + } + + @Override + public int hashCode() { + int h = 0; + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label75: { + while (true) { + if (p != null) { + next = p; + break label75; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + h += Byte.hashCode(p.key) ^ p.val.hashCode(); + } + } + + return h; + } + + @Override + public String toString() { + Byte2ObjectConcurrentHashMap.Node[] t = this.table; + int f = this.table == null ? 0 : t.length; + Byte2ObjectConcurrentHashMap.Traverser it = new Byte2ObjectConcurrentHashMap.Traverser<>(t, f, 0, f); + StringBuilder sb = new StringBuilder(); + sb.append('{'); + Byte2ObjectConcurrentHashMap.Node p; + if ((p = it.advance()) != null) { + while (true) { + byte k = p.key; + V v = p.val; + sb.append((int)k); + sb.append('='); + sb.append(v == this ? "(this Map)" : v); + if ((p = it.advance()) == null) { + break; + } + + sb.append(',').append(' '); + } + } + + return sb.append('}').toString(); + } + + @Override + public boolean equals(Object o) { + if (o != this) { + if (!(o instanceof Byte2ObjectConcurrentHashMap m)) { + return false; + } + + Byte2ObjectConcurrentHashMap.Node[] t = this.table; + int f = this.table == null ? 0 : t.length; + Byte2ObjectConcurrentHashMap.Traverser it = new Byte2ObjectConcurrentHashMap.Traverser<>(t, f, 0, f); + + Byte2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + V val = p.val; + Object v = m.get(p.key); + if (v == null || v != val && !v.equals(val)) { + return false; + } + } + + for (Byte2ObjectMap.Entry e : m.byte2ObjectEntrySet()) { + Object mv; + Object v; + byte mk; + if ((mk = e.getByteKey()) == m.EMPTY || (mv = e.getValue()) == null || (v = this.get(mk)) == null || mv != v && !mv.equals(v)) { + return false; + } + } + } + + return true; + } + + public V putIfAbsent(byte key, V value) { + return this.putVal(key, value, true); + } + + public boolean remove(byte key, Object value) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + return value != null && this.replaceNode(key, null, value) != null; + } + } + + public boolean replace(byte key, V oldValue, V newValue) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (oldValue != null && newValue != null) { + return this.replaceNode(key, newValue, oldValue) != null; + } else { + throw new NullPointerException(); + } + } + + public V replace(byte key, V value) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value == null) { + throw new NullPointerException(); + } else { + return this.replaceNode(key, value, null); + } + } + + public V getOrDefault(byte key, V defaultValue) { + V v; + return (v = this.get(key)) == null ? defaultValue : v; + } + + public int forEach(Byte2ObjectConcurrentHashMap.ByteObjConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val); + count++; + } + } + + return count; + } + } + + public int forEach(Byte2ObjectConcurrentHashMap.ByteBiObjConsumer action, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, x); + count++; + } + } + + return count; + } + } + + public int forEach(Byte2ObjectConcurrentHashMap.ByteTriObjConsumer action, X x, Y y) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, x, y); + count++; + } + } + + return count; + } + } + + public int forEachWithByte(Byte2ObjectConcurrentHashMap.ByteObjByteConsumer action, byte ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithShort(Byte2ObjectConcurrentHashMap.ByteObjShortConsumer action, short ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithInt(Byte2ObjectConcurrentHashMap.ByteObjIntConsumer action, int ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithLong(Byte2ObjectConcurrentHashMap.ByteObjLongConsumer action, long ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithFloat(Byte2ObjectConcurrentHashMap.ByteObjFloatConsumer action, float ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithDouble(Byte2ObjectConcurrentHashMap.ByteObjDoubleConsumer action, double ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithByte(Byte2ObjectConcurrentHashMap.ByteBiObjByteConsumer action, byte ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithShort(Byte2ObjectConcurrentHashMap.ByteBiObjShortConsumer action, short ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithInt(Byte2ObjectConcurrentHashMap.ByteBiObjIntConsumer action, int ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithLong(Byte2ObjectConcurrentHashMap.ByteBiObjLongConsumer action, long ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithFloat(Byte2ObjectConcurrentHashMap.ByteBiObjFloatConsumer action, float ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithDouble(Byte2ObjectConcurrentHashMap.ByteBiObjDoubleConsumer action, double ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public void replaceAll(Byte2ObjectOperator function) { + if (function == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label86: { + while (true) { + if (p != null) { + next = p; + break label86; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + V oldValue = p.val; + byte key = p.key; + + V newValue; + do { + newValue = function.apply(key, oldValue); + if (newValue == null) { + throw new NullPointerException(); + } + } while (this.replaceNode(key, newValue, oldValue) == null && (oldValue = this.get(key)) != null); + } + } + } + } + + public V computeIfAbsent(byte key, Byte2ObjectConcurrentHashMap.ByteFunction mappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (mappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Byte.hashCode(key)); + V val = null; + int binCount = 0; + Byte2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Byte2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + Byte2ObjectConcurrentHashMap.Node r = new Byte2ObjectConcurrentHashMap.ReservationNode<>(this.EMPTY); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Byte2ObjectConcurrentHashMap.Node node = null; + + try { + if ((val = (V)mappingFunction.apply(key)) != null) { + node = new Byte2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + } finally { + setTabAt(tab, i, node); + } + } + } + + if (binCount != 0) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + boolean added = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Byte2ObjectConcurrentHashMap.TreeBin t = (Byte2ObjectConcurrentHashMap.TreeBin)f; + Byte2ObjectConcurrentHashMap.TreeNode r = t.root; + Byte2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(h, key, null)) != null) { + val = p.val; + } else if ((val = (V)mappingFunction.apply(key)) != null) { + added = true; + t.putTreeVal(h, key, val); + } + } + } else { + binCount = 1; + Byte2ObjectConcurrentHashMap.Node e = f; + + while (true) { + if (e.hash == h) { + byte ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = e.val; + break; + } + } + + Byte2ObjectConcurrentHashMap.Node pred = e; + if ((e = e.next) == null) { + if ((val = (V)mappingFunction.apply(key)) != null) { + added = true; + pred.next = new Byte2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + + if (!added) { + return val; + } + break; + } + } + } + } + + if (val != null) { + this.addCount(1L, binCount); + } + + return val; + } + } + + public V computeIfPresent(byte key, Byte2ObjectConcurrentHashMap.ByteObjFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (remappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Byte.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Byte2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Byte2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + break; + } + + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Byte2ObjectConcurrentHashMap.TreeBin t = (Byte2ObjectConcurrentHashMap.TreeBin)f; + Byte2ObjectConcurrentHashMap.TreeNode r = t.root; + Byte2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(h, key, null)) != null) { + val = (V)remappingFunction.apply(key, p.val); + if (val != null) { + p.val = val; + } else { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } + } else { + binCount = 1; + Byte2ObjectConcurrentHashMap.Node e = f; + Byte2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + byte ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(key, e.val); + if (val != null) { + e.val = val; + } else { + delta = -1; + Byte2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + break; + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } + } + + public V compute(byte key, Byte2ObjectConcurrentHashMap.ByteObjFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (remappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Byte.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Byte2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Byte2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + Byte2ObjectConcurrentHashMap.Node r = new Byte2ObjectConcurrentHashMap.ReservationNode<>(this.EMPTY); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Byte2ObjectConcurrentHashMap.Node node = null; + + try { + if ((val = (V)remappingFunction.apply(key, null)) != null) { + delta = 1; + node = new Byte2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + } finally { + setTabAt(tab, i, node); + } + } + } + + if (binCount != 0) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + binCount = 1; + Byte2ObjectConcurrentHashMap.TreeBin t = (Byte2ObjectConcurrentHashMap.TreeBin)f; + Byte2ObjectConcurrentHashMap.TreeNode r = t.root; + Byte2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null) { + p = r.findTreeNode(h, key, null); + } else { + p = null; + } + + V pv = p == null ? null : p.val; + val = (V)remappingFunction.apply(key, pv); + if (val != null) { + if (p != null) { + p.val = val; + } else { + delta = 1; + t.putTreeVal(h, key, val); + } + } else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } else { + binCount = 1; + Byte2ObjectConcurrentHashMap.Node e = f; + Byte2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + byte ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(key, e.val); + if (val != null) { + e.val = val; + } else { + delta = -1; + Byte2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + val = (V)remappingFunction.apply(key, null); + if (val != null) { + delta = 1; + pred.next = new Byte2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + break; + } + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } + } + + public V merge(byte key, V value, BiFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value != null && remappingFunction != null) { + int h = spread(Byte.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Byte2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Byte2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + if (casTabAt(tab, i, null, new Byte2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, value, null))) { + delta = 1; + val = value; + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Byte2ObjectConcurrentHashMap.TreeBin t = (Byte2ObjectConcurrentHashMap.TreeBin)f; + Byte2ObjectConcurrentHashMap.TreeNode r = t.root; + Byte2ObjectConcurrentHashMap.TreeNode p = r == null ? null : r.findTreeNode(h, key, null); + val = p == null ? value : remappingFunction.apply(p.val, value); + if (val != null) { + if (p != null) { + p.val = val; + } else { + delta = 1; + t.putTreeVal(h, key, val); + } + } else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } else { + binCount = 1; + Byte2ObjectConcurrentHashMap.Node e = f; + Byte2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + byte ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(e.val, value); + if (val != null) { + e.val = val; + } else { + delta = -1; + Byte2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + delta = 1; + val = value; + pred.next = new Byte2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, value, null); + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + break; + } + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } else { + throw new NullPointerException(); + } + } + + public long mappingCount() { + long n = this.sumCount(); + return n < 0L ? 0L : n; + } + + public static ByteSet newKeySet() { + return new Byte2ObjectConcurrentHashMap.KeySetView<>(new Byte2ObjectConcurrentHashMap<>(), Boolean.TRUE); + } + + public static Byte2ObjectConcurrentHashMap.KeySetView newKeySet(int initialCapacity) { + return new Byte2ObjectConcurrentHashMap.KeySetView<>(new Byte2ObjectConcurrentHashMap<>(initialCapacity), Boolean.TRUE); + } + + public Byte2ObjectConcurrentHashMap.KeySetView keySet(V mappedValue) { + if (mappedValue == null) { + throw new NullPointerException(); + } else { + return new Byte2ObjectConcurrentHashMap.KeySetView<>(this, mappedValue); + } + } + + protected static final int resizeStamp(int n) { + return Integer.numberOfLeadingZeros(n) | 1 << RESIZE_STAMP_BITS - 1; + } + + protected final Byte2ObjectConcurrentHashMap.Node[] initTable() { + Byte2ObjectConcurrentHashMap.Node[] tab; + while (true) { + tab = this.table; + if (this.table != null && tab.length != 0) { + break; + } + + int sc = this.sizeCtl; + if (this.sizeCtl < 0) { + Thread.yield(); + } else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + tab = this.table; + if (this.table == null || tab.length == 0) { + int n = sc > 0 ? sc : 16; + Byte2ObjectConcurrentHashMap.Node[] nt = new Byte2ObjectConcurrentHashMap.Node[n]; + tab = nt; + this.table = nt; + sc = n - (n >>> 2); + } + break; + } finally { + this.sizeCtl = sc; + } + } + } + + return tab; + } + + protected final void addCount(long x, int check) { + boolean uncontended; + label77: { + long s; + label74: { + Byte2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + if (this.counterCells == null) { + long b = this.baseCount; + if (U.compareAndSwapLong(this, BASECOUNT, this.baseCount, s = b + x)) { + break label74; + } + } + + uncontended = (boolean)1; + Byte2ObjectConcurrentHashMap.CounterCell a; + int m; + if (as == null || (m = as.length - 1) < 0 || (a = as[TLRUtil.getProbe() & m]) == null) { + break label77; + } + + long v = a.value; + if (!(uncontended = U.compareAndSwapLong(a, CELLVALUE, a.value, v + x))) { + break label77; + } + + if (check <= 1) { + return; + } + + s = this.sumCount(); + } + + if (check >= 0) { + while (true) { + int sc = this.sizeCtl; + if (s < this.sizeCtl) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] tab = this.table; + int n; + if (this.table == null || (n = tab.length) >= 1073741824) { + break; + } + + uncontended = (boolean)resizeStamp(n); + if (sc < 0) { + if (sc >>> RESIZE_STAMP_SHIFT != uncontended || sc == uncontended + 1 || sc == uncontended + MAX_RESIZERS) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] nt = this.nextTable; + if (this.nextTable == null || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nt); + } + } else if (U.compareAndSwapInt(this, SIZECTL, sc, (uncontended << RESIZE_STAMP_SHIFT) + 2)) { + this.transfer(tab, null); + } + + s = this.sumCount(); + } + } + + return; + } + + this.fullAddCount(x, uncontended); + } + + protected final Byte2ObjectConcurrentHashMap.Node[] helpTransfer(Byte2ObjectConcurrentHashMap.Node[] tab, Byte2ObjectConcurrentHashMap.Node f) { + if (tab != null && f instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + Byte2ObjectConcurrentHashMap.Node[] nextTab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)f).nextTable; + if (((Byte2ObjectConcurrentHashMap.ForwardingNode)f).nextTable != null) { + int rs = resizeStamp(tab.length); + + while (nextTab == this.nextTable && this.table == tab) { + int sc = this.sizeCtl; + if (this.sizeCtl >= 0 || sc >>> RESIZE_STAMP_SHIFT != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nextTab); + break; + } + } + + return nextTab; + } + } + + return this.table; + } + + protected final void tryPresize(int size) { + int c = size >= 536870912 ? 1073741824 : tableSizeFor(size + (size >>> 1) + 1); + + while (true) { + int sc = this.sizeCtl; + if (this.sizeCtl < 0) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] tab = this.table; + int n; + if (tab != null && (n = tab.length) != 0) { + if (c <= sc || n >= 1073741824) { + break; + } + + if (tab == this.table) { + int rs = resizeStamp(n); + if (sc < 0) { + if (sc >>> RESIZE_STAMP_SHIFT != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] nt = this.nextTable; + if (this.nextTable == null || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nt); + } + } else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) { + this.transfer(tab, null); + } + } + } else { + n = sc > c ? sc : c; + if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + if (this.table == tab) { + Byte2ObjectConcurrentHashMap.Node[] ntx = new Byte2ObjectConcurrentHashMap.Node[n]; + this.table = ntx; + sc = n - (n >>> 2); + } + } finally { + this.sizeCtl = sc; + } + } + } + } + } + + protected final void transfer(Byte2ObjectConcurrentHashMap.Node[] tab, Byte2ObjectConcurrentHashMap.Node[] nextTab) { + int n = tab.length; + int stride; + if ((stride = NCPU > 1 ? (n >>> 3) / NCPU : n) < 16) { + stride = 16; + } + + if (nextTab == null) { + try { + Byte2ObjectConcurrentHashMap.Node[] nt = new Byte2ObjectConcurrentHashMap.Node[n << 1]; + nextTab = nt; + } catch (Throwable var27) { + this.sizeCtl = Integer.MAX_VALUE; + return; + } + + this.nextTable = nextTab; + this.transferIndex = n; + } + + int nextn = nextTab.length; + Byte2ObjectConcurrentHashMap.ForwardingNode fwd = new Byte2ObjectConcurrentHashMap.ForwardingNode<>(this.EMPTY, nextTab); + boolean advance = true; + boolean finishing = false; + int i = 0; + int bound = 0; + + while (true) { + while (!advance) { + if (i >= 0 && i < n && i + n < nextn) { + Byte2ObjectConcurrentHashMap.Node f; + if ((f = tabAt(tab, i)) == null) { + advance = casTabAt(tab, i, null, fwd); + } else { + int fh = f.hash; + if (f.hash == -1) { + advance = true; + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + int runBit = fh & n; + Byte2ObjectConcurrentHashMap.Node lastRun = f; + + for (Byte2ObjectConcurrentHashMap.Node p = f.next; p != null; p = p.next) { + int b = p.hash & n; + if (b != runBit) { + runBit = b; + lastRun = p; + } + } + + Byte2ObjectConcurrentHashMap.Node ln; + Byte2ObjectConcurrentHashMap.Node hn; + if (runBit == 0) { + ln = lastRun; + hn = null; + } else { + hn = lastRun; + ln = null; + } + + for (Byte2ObjectConcurrentHashMap.Node px = f; px != lastRun; px = px.next) { + int ph = px.hash; + byte pk = px.key; + V pv = px.val; + if ((ph & n) == 0) { + ln = new Byte2ObjectConcurrentHashMap.Node<>(this.EMPTY, ph, pk, pv, ln); + } else { + hn = new Byte2ObjectConcurrentHashMap.Node<>(this.EMPTY, ph, pk, pv, hn); + } + } + + setTabAt(nextTab, i, ln); + setTabAt(nextTab, i + n, hn); + setTabAt(tab, i, fwd); + advance = true; + } else if (f instanceof Byte2ObjectConcurrentHashMap.TreeBin t) { + Byte2ObjectConcurrentHashMap.TreeNode lo = null; + Byte2ObjectConcurrentHashMap.TreeNode loTail = null; + Byte2ObjectConcurrentHashMap.TreeNode hi = null; + Byte2ObjectConcurrentHashMap.TreeNode hiTail = null; + int lc = 0; + int hc = 0; + + for (Byte2ObjectConcurrentHashMap.Node e = t.first; e != null; e = e.next) { + int h = e.hash; + Byte2ObjectConcurrentHashMap.TreeNode pxx = new Byte2ObjectConcurrentHashMap.TreeNode<>( + this.EMPTY, h, e.key, e.val, null, null + ); + if ((h & n) == 0) { + if ((pxx.prev = loTail) == null) { + lo = pxx; + } else { + loTail.next = pxx; + } + + loTail = pxx; + lc++; + } else { + if ((pxx.prev = hiTail) == null) { + hi = pxx; + } else { + hiTail.next = pxx; + } + + hiTail = pxx; + hc++; + } + } + + Byte2ObjectConcurrentHashMap.Node ln = (Byte2ObjectConcurrentHashMap.Node)(lc <= 6 + ? this.untreeify(lo) + : (hc != 0 ? new Byte2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, lo) : t)); + Byte2ObjectConcurrentHashMap.Node hn = (Byte2ObjectConcurrentHashMap.Node)(hc <= 6 + ? this.untreeify(hi) + : (lc != 0 ? new Byte2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, hi) : t)); + setTabAt(nextTab, i, ln); + setTabAt(nextTab, i + n, hn); + setTabAt(tab, i, fwd); + advance = true; + } + } + } + } + } + } else { + if (finishing) { + this.nextTable = null; + this.table = nextTab; + this.sizeCtl = (n << 1) - (n >>> 1); + return; + } + + int sc = this.sizeCtl; + if (U.compareAndSwapInt(this, SIZECTL, this.sizeCtl, sc - 1)) { + if (sc - 2 != resizeStamp(n) << RESIZE_STAMP_SHIFT) { + return; + } + + advance = true; + finishing = true; + i = n; + } + } + } + + if (--i < bound && !finishing) { + int nextIndex = this.transferIndex; + if (this.transferIndex <= 0) { + i = -1; + advance = false; + } else { + int nextBound; + if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex, nextBound = nextIndex > stride ? nextIndex - stride : 0)) { + bound = nextBound; + i = nextIndex - 1; + advance = false; + } + } + } else { + advance = false; + } + } + } + + protected final long sumCount() { + Byte2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + long sum = this.baseCount; + if (as != null) { + for (int i = 0; i < as.length; i++) { + Byte2ObjectConcurrentHashMap.CounterCell a; + if ((a = as[i]) != null) { + sum += a.value; + } + } + } + + return sum; + } + + protected final void fullAddCount(long x, boolean wasUncontended) { + int h; + if ((h = TLRUtil.getProbe()) == 0) { + TLRUtil.localInit(); + h = TLRUtil.getProbe(); + wasUncontended = true; + } + + boolean collide = false; + + while (true) { + Byte2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + int n; + if (this.counterCells != null && (n = as.length) > 0) { + Byte2ObjectConcurrentHashMap.CounterCell a; + if ((a = as[n - 1 & h]) == null) { + if (this.cellsBusy == 0) { + Byte2ObjectConcurrentHashMap.CounterCell r = new Byte2ObjectConcurrentHashMap.CounterCell(x); + if (this.cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean created = false; + + try { + Byte2ObjectConcurrentHashMap.CounterCell[] rs = this.counterCells; + int m; + int j; + if (this.counterCells != null && (m = rs.length) > 0 && rs[j = m - 1 & h] == null) { + rs[j] = r; + created = true; + } + } finally { + this.cellsBusy = 0; + } + + if (created) { + break; + } + continue; + } + } + + collide = false; + } else if (!wasUncontended) { + wasUncontended = true; + } else { + long v = a.value; + if (U.compareAndSwapLong(a, CELLVALUE, a.value, v + x)) { + break; + } + + if (this.counterCells != as || n >= NCPU) { + collide = false; + } else if (!collide) { + collide = true; + } else if (this.cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + try { + if (this.counterCells == as) { + Byte2ObjectConcurrentHashMap.CounterCell[] rs = new Byte2ObjectConcurrentHashMap.CounterCell[n << 1]; + + for (int i = 0; i < n; i++) { + rs[i] = as[i]; + } + + this.counterCells = rs; + } + } finally { + this.cellsBusy = 0; + } + + collide = false; + continue; + } + } + + h = TLRUtil.advanceProbe(h); + } else if (this.cellsBusy == 0 && this.counterCells == as && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean init = false; + + try { + if (this.counterCells == as) { + Byte2ObjectConcurrentHashMap.CounterCell[] rs = new Byte2ObjectConcurrentHashMap.CounterCell[2]; + rs[h & 1] = new Byte2ObjectConcurrentHashMap.CounterCell(x); + this.counterCells = rs; + init = true; + } + } finally { + this.cellsBusy = 0; + } + + if (init) { + break; + } + } else { + long vx = this.baseCount; + if (U.compareAndSwapLong(this, BASECOUNT, this.baseCount, vx + x)) { + break; + } + } + } + } + + protected final void treeifyBin(Byte2ObjectConcurrentHashMap.Node[] tab, int index) { + if (tab != null) { + int n; + if ((n = tab.length) < 64) { + this.tryPresize(n << 1); + } else { + Byte2ObjectConcurrentHashMap.Node b; + if ((b = tabAt(tab, index)) != null && b.hash >= 0) { + synchronized (b) { + if (tabAt(tab, index) == b) { + Byte2ObjectConcurrentHashMap.TreeNode hd = null; + Byte2ObjectConcurrentHashMap.TreeNode tl = null; + + for (Byte2ObjectConcurrentHashMap.Node e = b; e != null; e = e.next) { + Byte2ObjectConcurrentHashMap.TreeNode p = new Byte2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, e.hash, e.key, e.val, null, null); + if ((p.prev = tl) == null) { + hd = p; + } else { + tl.next = p; + } + + tl = p; + } + + setTabAt(tab, index, new Byte2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, hd)); + } + } + } + } + } + } + + protected Byte2ObjectConcurrentHashMap.Node untreeify(Byte2ObjectConcurrentHashMap.Node b) { + Byte2ObjectConcurrentHashMap.Node hd = null; + Byte2ObjectConcurrentHashMap.Node tl = null; + + for (Byte2ObjectConcurrentHashMap.Node q = b; q != null; q = q.next) { + Byte2ObjectConcurrentHashMap.Node p = new Byte2ObjectConcurrentHashMap.Node<>(this.EMPTY, q.hash, q.key, q.val, null); + if (tl == null) { + hd = p; + } else { + tl.next = p; + } + + tl = p; + } + + return hd; + } + + protected final int batchFor(long b) { + long n; + if (b != Long.MAX_VALUE && (n = this.sumCount()) > 1L && n >= b) { + int sp = ForkJoinPool.getCommonPoolParallelism() << 2; + long var6; + return b > 0L && (var6 = n / b) < sp ? (int)var6 : sp; + } else { + return 0; + } + } + + public void forEach(long parallelismThreshold, Byte2ObjectConcurrentHashMap.ByteObjConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Byte2ObjectConcurrentHashMap.ForEachMappingTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEach( + long parallelismThreshold, Byte2ObjectConcurrentHashMap.ByteObjFunction transformer, Consumer action + ) { + if (transformer != null && action != null) { + new Byte2ObjectConcurrentHashMap.ForEachTransformedMappingTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U search(long parallelismThreshold, Byte2ObjectConcurrentHashMap.ByteObjFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Byte2ObjectConcurrentHashMap.SearchMappingsTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public U search(Byte2ObjectConcurrentHashMap.ByteObjFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U search(Byte2ObjectConcurrentHashMap.ByteBiObjFunction searchFunction, X x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithByte(Byte2ObjectConcurrentHashMap.ByteObjByteFunction searchFunction, byte x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithShort(Byte2ObjectConcurrentHashMap.ByteObjShortFunction searchFunction, short x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithInt(Byte2ObjectConcurrentHashMap.ByteObjIntFunction searchFunction, int x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithLong(Byte2ObjectConcurrentHashMap.ByteObjLongFunction searchFunction, long x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithFloat(Byte2ObjectConcurrentHashMap.ByteObjFloatFunction searchFunction, float x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithDouble(Byte2ObjectConcurrentHashMap.ByteObjDoubleFunction searchFunction, double x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U reduce( + long parallelismThreshold, + Byte2ObjectConcurrentHashMap.ByteObjFunction transformer, + BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Byte2ObjectConcurrentHashMap.MapReduceMappingsTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer + ) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U reduce(Byte2ObjectConcurrentHashMap.ByteObjFunction transformer, BiFunction reducer) { + if (transformer != null && reducer != null) { + Byte2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table == null) { + return null; + } else { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + U r = null; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label88: { + while (true) { + if (p != null) { + next = p; + break label88; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + return r; + } + + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + } + } else { + throw new NullPointerException(); + } + } + + public double reduceToDouble( + long parallelismThreshold, Byte2ObjectConcurrentHashMap.ToDoubleByteObjFunction transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Byte2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceToLong( + long parallelismThreshold, Byte2ObjectConcurrentHashMap.ToLongByteObjFunction transformer, long basis, LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Byte2ObjectConcurrentHashMap.MapReduceMappingsToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceToInt( + long parallelismThreshold, Byte2ObjectConcurrentHashMap.ToIntByteObjFunction transformer, int basis, IntBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Byte2ObjectConcurrentHashMap.MapReduceMappingsToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachKey(long parallelismThreshold, ByteConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Byte2ObjectConcurrentHashMap.ForEachKeyTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachKey(long parallelismThreshold, Byte2ObjectConcurrentHashMap.ByteFunction transformer, Consumer action) { + if (transformer != null && action != null) { + new Byte2ObjectConcurrentHashMap.ForEachTransformedKeyTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchKeys(long parallelismThreshold, Byte2ObjectConcurrentHashMap.ByteFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Byte2ObjectConcurrentHashMap.SearchKeysTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public byte reduceKeys(long parallelismThreshold, Byte2ObjectConcurrentHashMap.ByteReduceTaskOperator reducer) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Byte2ObjectConcurrentHashMap.ReduceKeysTask<>(this.EMPTY, null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer) + .invoke0(); + } + } + + public U reduceKeys( + long parallelismThreshold, Byte2ObjectConcurrentHashMap.ByteFunction transformer, BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Byte2ObjectConcurrentHashMap.MapReduceKeysTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceKeysToDouble( + long parallelismThreshold, Byte2ObjectConcurrentHashMap.ByteToDoubleFunction transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Byte2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceKeysToLong(long parallelismThreshold, Byte2ObjectConcurrentHashMap.ByteToLongFunction transformer, long basis, LongBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Byte2ObjectConcurrentHashMap.MapReduceKeysToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceKeysToInt(long parallelismThreshold, Byte2ObjectConcurrentHashMap.ByteToIntFunction transformer, int basis, IntBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Byte2ObjectConcurrentHashMap.MapReduceKeysToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachValue(long parallelismThreshold, Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Byte2ObjectConcurrentHashMap.ForEachValueTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachValue(long parallelismThreshold, Function transformer, Consumer action) { + if (transformer != null && action != null) { + new Byte2ObjectConcurrentHashMap.ForEachTransformedValueTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchValues(long parallelismThreshold, Function searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Byte2ObjectConcurrentHashMap.SearchValuesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public V reduceValues(long parallelismThreshold, BiFunction reducer) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Byte2ObjectConcurrentHashMap.ReduceValuesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer).invoke(); + } + } + + public U reduceValues(long parallelismThreshold, Function transformer, BiFunction reducer) { + if (transformer != null && reducer != null) { + return new Byte2ObjectConcurrentHashMap.MapReduceValuesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceValuesToDouble(long parallelismThreshold, ToDoubleFunction transformer, double basis, DoubleBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Byte2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceValuesToLong(long parallelismThreshold, ToLongFunction transformer, long basis, LongBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Byte2ObjectConcurrentHashMap.MapReduceValuesToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceValuesToInt(long parallelismThreshold, ToIntFunction transformer, int basis, IntBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Byte2ObjectConcurrentHashMap.MapReduceValuesToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachEntry(long parallelismThreshold, Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Byte2ObjectConcurrentHashMap.ForEachEntryTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachEntry(long parallelismThreshold, Function, ? extends U> transformer, Consumer action) { + if (transformer != null && action != null) { + new Byte2ObjectConcurrentHashMap.ForEachTransformedEntryTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchEntries(long parallelismThreshold, Function, ? extends U> searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Byte2ObjectConcurrentHashMap.SearchEntriesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public Byte2ObjectConcurrentHashMap.Entry reduceEntries( + long parallelismThreshold, + BiFunction, Byte2ObjectConcurrentHashMap.Entry, ? extends Byte2ObjectConcurrentHashMap.Entry> reducer + ) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Byte2ObjectConcurrentHashMap.ReduceEntriesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer).invoke(); + } + } + + public U reduceEntries( + long parallelismThreshold, + Function, ? extends U> transformer, + BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Byte2ObjectConcurrentHashMap.MapReduceEntriesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceEntriesToDouble( + long parallelismThreshold, ToDoubleFunction> transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Byte2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceEntriesToLong( + long parallelismThreshold, ToLongFunction> transformer, long basis, LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Byte2ObjectConcurrentHashMap.MapReduceEntriesToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceEntriesToInt( + long parallelismThreshold, ToIntFunction> transformer, int basis, IntBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Byte2ObjectConcurrentHashMap.MapReduceEntriesToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public V valueMatching(Predicate predicate) { + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + Byte2ObjectConcurrentHashMap.Node[] tab = this.table; + int f = this.table == null ? 0 : tab.length; + int baseLimit = f; + int baseSize = f; + boolean b = false; + + label80: + while (next != null || !b) { + b |= true; + Byte2ObjectConcurrentHashMap.Node e = next; + if (next != null) { + e = next.next; + } + + label76: + while (e == null) { + if (baseIndex < baseLimit) { + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab != null && (n = tab.length) > index && index >= 0) { + if ((e = tabAt(tab, index)) != null && e.hash < 0) { + if (e instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (e instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + e = ((Byte2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + continue; + } + + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack next1 = stack.next; + stack.next = spare; + stack = next1; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + continue label76; + } + } + } + + next = null; + continue label80; + } + + next = e; + if (predicate.test(e.val)) { + return e.val; + } + } + + return null; + } + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + U = (Unsafe)f.get(null); + Class k = Byte2ObjectConcurrentHashMap.class; + SIZECTL = U.objectFieldOffset(k.getDeclaredField("sizeCtl")); + TRANSFERINDEX = U.objectFieldOffset(k.getDeclaredField("transferIndex")); + BASECOUNT = U.objectFieldOffset(k.getDeclaredField("baseCount")); + CELLSBUSY = U.objectFieldOffset(k.getDeclaredField("cellsBusy")); + Class ck = Byte2ObjectConcurrentHashMap.CounterCell.class; + CELLVALUE = U.objectFieldOffset(ck.getDeclaredField("value")); + Class ak = Byte2ObjectConcurrentHashMap.Node[].class; + ABASE = U.arrayBaseOffset(ak); + int scale = U.arrayIndexScale(ak); + if ((scale & scale - 1) != 0) { + throw new Error("data type scale not a power of two"); + } else { + ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); + } + } catch (Exception var5) { + throw new Error(var5); + } + } + + protected static class BaseIterator extends Byte2ObjectConcurrentHashMap.Traverser { + public final Byte2ObjectConcurrentHashMap map; + public Byte2ObjectConcurrentHashMap.Node lastReturned; + + public BaseIterator(Byte2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, Byte2ObjectConcurrentHashMap map) { + super(tab, size, index, limit); + this.map = map; + this.advance(); + } + + public final boolean hasNext() { + return this.next != null; + } + + public final boolean hasMoreElements() { + return this.next != null; + } + + public final void remove() { + Byte2ObjectConcurrentHashMap.Node p = this.lastReturned; + if (this.lastReturned == null) { + throw new IllegalStateException(); + } else { + this.lastReturned = null; + this.map.replaceNode(p.key, null, null); + } + } + } + + protected abstract static class BulkTask extends CountedCompleter { + public Byte2ObjectConcurrentHashMap.Node[] tab; + public Byte2ObjectConcurrentHashMap.Node next; + public Byte2ObjectConcurrentHashMap.TableStack stack; + public Byte2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + public int batch; + + protected BulkTask(Byte2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Byte2ObjectConcurrentHashMap.Node[] t) { + super(par); + this.batch = b; + this.index = this.baseIndex = i; + if ((this.tab = t) == null) { + this.baseSize = this.baseLimit = 0; + } else if (par == null) { + this.baseSize = this.baseLimit = t.length; + } else { + this.baseLimit = f; + this.baseSize = par.baseSize; + } + } + + protected final Byte2ObjectConcurrentHashMap.Node advance() { + Byte2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Byte2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + e = ((Byte2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Byte2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Byte2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Byte2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + } + + @FunctionalInterface + public interface ByteBiObjByteConsumer { + void accept(byte var1, V var2, byte var3, X var4); + } + + @FunctionalInterface + public interface ByteBiObjConsumer { + void accept(byte var1, V var2, X var3); + } + + @FunctionalInterface + public interface ByteBiObjDoubleConsumer { + void accept(byte var1, V var2, double var3, X var5); + } + + @FunctionalInterface + public interface ByteBiObjFloatConsumer { + void accept(byte var1, V var2, float var3, X var4); + } + + @FunctionalInterface + public interface ByteBiObjFunction { + J apply(byte var1, V var2, X var3); + } + + @FunctionalInterface + public interface ByteBiObjIntConsumer { + void accept(byte var1, V var2, int var3, X var4); + } + + @FunctionalInterface + public interface ByteBiObjLongConsumer { + void accept(byte var1, V var2, long var3, X var5); + } + + @FunctionalInterface + public interface ByteBiObjShortConsumer { + void accept(byte var1, V var2, short var3, X var4); + } + + @FunctionalInterface + public interface ByteFunction { + R apply(byte var1); + } + + @FunctionalInterface + public interface ByteObjByteConsumer { + void accept(byte var1, V var2, byte var3); + } + + @FunctionalInterface + public interface ByteObjByteFunction { + J apply(byte var1, V var2, byte var3); + } + + @FunctionalInterface + public interface ByteObjConsumer { + void accept(byte var1, V var2); + } + + @FunctionalInterface + public interface ByteObjDoubleConsumer { + void accept(byte var1, V var2, double var3); + } + + @FunctionalInterface + public interface ByteObjDoubleFunction { + J apply(byte var1, V var2, double var3); + } + + @FunctionalInterface + public interface ByteObjFloatConsumer { + void accept(byte var1, V var2, float var3); + } + + @FunctionalInterface + public interface ByteObjFloatFunction { + J apply(byte var1, V var2, float var3); + } + + @FunctionalInterface + public interface ByteObjFunction { + J apply(byte var1, V var2); + } + + @FunctionalInterface + public interface ByteObjIntConsumer { + void accept(byte var1, V var2, int var3); + } + + @FunctionalInterface + public interface ByteObjIntFunction { + J apply(byte var1, V var2, int var3); + } + + @FunctionalInterface + public interface ByteObjLongConsumer { + void accept(byte var1, V var2, long var3); + } + + @FunctionalInterface + public interface ByteObjLongFunction { + J apply(byte var1, V var2, long var3); + } + + @FunctionalInterface + public interface ByteObjShortConsumer { + void accept(byte var1, V var2, short var3); + } + + @FunctionalInterface + public interface ByteObjShortFunction { + J apply(byte var1, V var2, short var3); + } + + @FunctionalInterface + public interface ByteReduceTaskOperator { + byte reduce(byte var1, byte var2, byte var3); + } + + protected abstract static class ByteReturningBulkTask2 extends Byte2ObjectConcurrentHashMap.BulkTask { + public byte result; + + public ByteReturningBulkTask2(Byte2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Byte2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected byte invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + @FunctionalInterface + public interface ByteToDoubleFunction { + double applyAsDouble(byte var1); + } + + @FunctionalInterface + public interface ByteToIntFunction { + int applyAsInt(byte var1); + } + + @FunctionalInterface + public interface ByteToLongFunction { + long applyAsLong(byte var1); + } + + @FunctionalInterface + public interface ByteTriObjConsumer { + void accept(byte var1, V var2, X var3, Y var4); + } + + protected abstract static class CollectionView implements ObjectCollection, Serializable { + public static final long serialVersionUID = 7249069246763182397L; + public final Byte2ObjectConcurrentHashMap map; + protected static final String oomeMsg = "Required array size too large"; + + public CollectionView(Byte2ObjectConcurrentHashMap map) { + this.map = map; + } + + public Byte2ObjectConcurrentHashMap getMap() { + return this.map; + } + + @Override + public final void clear() { + this.map.clear(); + } + + @Override + public final int size() { + return this.map.size(); + } + + @Override + public final boolean isEmpty() { + return this.map.isEmpty(); + } + + @Override + public abstract ObjectIterator iterator(); + + @Override + public abstract boolean contains(Object var1); + + @Override + public abstract boolean remove(Object var1); + + @Override + public final Object[] toArray() { + long sz = this.map.mappingCount(); + if (sz > 2147483639L) { + throw new OutOfMemoryError("Required array size too large"); + } else { + int n = (int)sz; + Object[] r = new Object[n]; + int i = 0; + + for (E e : this) { + if (i == n) { + if (n >= 2147483639) { + throw new OutOfMemoryError("Required array size too large"); + } + + if (n >= 1073741819) { + n = 2147483639; + } else { + n += (n >>> 1) + 1; + } + + r = Arrays.copyOf(r, n); + } + + r[i++] = e; + } + + return i == n ? r : Arrays.copyOf(r, i); + } + } + + @Override + public final T[] toArray(T[] a) { + long sz = this.map.mappingCount(); + if (sz > 2147483639L) { + throw new OutOfMemoryError("Required array size too large"); + } else { + int m = (int)sz; + T[] r = (T[])(a.length >= m ? a : (Object[])Array.newInstance(a.getClass().getComponentType(), m)); + int n = r.length; + int i = 0; + + for (E e : this) { + if (i == n) { + if (n >= 2147483639) { + throw new OutOfMemoryError("Required array size too large"); + } + + if (n >= 1073741819) { + n = 2147483639; + } else { + n += (n >>> 1) + 1; + } + + r = (T[])Arrays.copyOf(r, n); + } + + r[i++] = (T)e; + } + + if (a == r && i < n) { + r[i] = null; + return r; + } else { + return (T[])(i == n ? r : Arrays.copyOf(r, i)); + } + } + } + + @Override + public final String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + Iterator it = this.iterator(); + if (it.hasNext()) { + while (true) { + Object e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (!it.hasNext()) { + break; + } + + sb.append(',').append(' '); + } + } + + return sb.append(']').toString(); + } + + @Override + public final boolean containsAll(Collection c) { + if (c != this) { + for (Object e : c) { + if (e == null || !this.contains(e)) { + return false; + } + } + } + + return true; + } + + @Override + public final boolean removeAll(Collection c) { + if (c == null) { + throw new NullPointerException(); + } else { + boolean modified = false; + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (c.contains(it.next())) { + it.remove(); + modified = true; + } + } + + return modified; + } + } + + @Override + public final boolean retainAll(Collection c) { + if (c == null) { + throw new NullPointerException(); + } else { + boolean modified = false; + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + + return modified; + } + } + } + + protected static final class CounterCell { + public volatile long value; + + public CounterCell(long x) { + this.value = x; + } + } + + protected abstract static class DoubleReturningBulkTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public double result; + + public DoubleReturningBulkTask(Byte2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Byte2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected double invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + public interface Entry extends Byte2ObjectMap.Entry { + boolean isEmpty(); + + @Deprecated + @Override + Byte getKey(); + + @Override + byte getByteKey(); + + @Override + V getValue(); + + @Override + int hashCode(); + + @Override + String toString(); + + @Override + boolean equals(Object var1); + + @Override + V setValue(V var1); + } + + protected static final class EntryIterator extends Byte2ObjectConcurrentHashMap.BaseIterator implements ObjectIterator> { + public EntryIterator(Byte2ObjectConcurrentHashMap.Node[] tab, int index, int size, int limit, Byte2ObjectConcurrentHashMap map) { + super(tab, index, size, limit, map); + } + + public final Byte2ObjectConcurrentHashMap.Entry next() { + Byte2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + byte k = p.key; + V v = p.val; + this.lastReturned = p; + this.advance(); + return new Byte2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), k, v, this.map); + } + } + } + + protected static final class EntrySetView + extends Byte2ObjectConcurrentHashMap.CollectionView> + implements ObjectSet>, + Serializable { + public static final long serialVersionUID = 2249069246763182397L; + + public EntrySetView(Byte2ObjectConcurrentHashMap map) { + super(map); + } + + @Override + public boolean contains(Object o) { + if (o instanceof Byte2ObjectMap.Entry) { + Byte2ObjectMap.Entry e; + byte k = (e = (Byte2ObjectMap.Entry)o).getByteKey(); + if (!((Byte2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + Object v; + Object r; + return (r = this.map.get(k)) != null && (v = e.getValue()) != null && (v == r || v.equals(r)); + } + } + + return false; + } + + @Override + public boolean remove(Object o) { + if (o instanceof Byte2ObjectMap.Entry) { + Byte2ObjectMap.Entry e; + byte k = (e = (Byte2ObjectMap.Entry)o).getByteKey(); + if (!((Byte2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + Object v; + return (v = e.getValue()) != null && this.map.remove(k, v); + } + } + + return false; + } + + @Override + public ObjectIterator> iterator() { + Byte2ObjectConcurrentHashMap m = this.map; + Byte2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Byte2ObjectConcurrentHashMap.EntryIterator<>(t, f, 0, f, m); + } + + public boolean add(Byte2ObjectMap.Entry e) { + return this.map.putVal(e.getByteKey(), e.getValue(), false) == null; + } + + @Override + public boolean addAll(Collection> c) { + boolean added = false; + + for (Byte2ObjectMap.Entry e : c) { + if (this.add(e)) { + added = true; + } + } + + return added; + } + + @Override + public final int hashCode() { + int h = 0; + Byte2ObjectConcurrentHashMap.Node[] t = this.map.table; + if (this.map.table != null) { + Byte2ObjectConcurrentHashMap.Traverser it = new Byte2ObjectConcurrentHashMap.Traverser<>(t, t.length, 0, t.length); + + Byte2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + h += p.hashCode(); + } + } + + return h; + } + + @Override + public final boolean equals(Object o) { + Set c; + return o instanceof Set && ((c = (Set)o) == this || this.containsAll(c) && c.containsAll(this)); + } + + @Override + public ObjectSpliterator> spliterator() { + Byte2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Byte2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Byte2ObjectConcurrentHashMap.EntrySpliterator<>(t, f, 0, f, n < 0L ? 0L : n, m); + } + + @Override + public void forEach(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node[] t = this.map.table; + if (this.map.table != null) { + Byte2ObjectConcurrentHashMap.Traverser it = new Byte2ObjectConcurrentHashMap.Traverser<>(t, t.length, 0, t.length); + + Byte2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + action.accept(new Byte2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + } + } + } + } + } + + protected static final class EntrySpliterator extends Byte2ObjectConcurrentHashMap.Traverser implements ObjectSpliterator> { + public final Byte2ObjectConcurrentHashMap map; + public long est; + + public EntrySpliterator(Byte2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est, Byte2ObjectConcurrentHashMap map) { + super(tab, size, index, limit); + this.map = map; + this.est = est; + } + + @Override + public ObjectSpliterator> trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Byte2ObjectConcurrentHashMap.EntrySpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1, this.map); + } + + @Override + public void forEachRemaining(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(new Byte2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + } + } + } + + @Override + public boolean tryAdvance(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(new Byte2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4353; + } + } + + protected static final class ForEachEntryTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public final Consumer> action; + + public ForEachEntryTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Consumer> action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Consumer> action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Byte2ObjectConcurrentHashMap.ForEachEntryTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachKeyTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public final ByteConsumer action; + + public ForEachKeyTask(Byte2ObjectConcurrentHashMap.BulkTask p, int b, int i, int f, Byte2ObjectConcurrentHashMap.Node[] t, ByteConsumer action) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + ByteConsumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Byte2ObjectConcurrentHashMap.ForEachKeyTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.key); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachMappingTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public final Byte2ObjectConcurrentHashMap.ByteObjConsumer action; + + public ForEachMappingTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.ByteObjConsumer action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Byte2ObjectConcurrentHashMap.ByteObjConsumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Byte2ObjectConcurrentHashMap.ForEachMappingTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.key, p.val); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachTransformedEntryTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> transformer; + public final Consumer action; + + public ForEachTransformedEntryTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Function, ? extends U> transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Function, ? extends U> transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Byte2ObjectConcurrentHashMap.ForEachTransformedEntryTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedKeyTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public final Byte2ObjectConcurrentHashMap.ByteFunction transformer; + public final Consumer action; + + public ForEachTransformedKeyTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.ByteFunction transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Byte2ObjectConcurrentHashMap.ByteFunction transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Byte2ObjectConcurrentHashMap.ForEachTransformedKeyTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedMappingTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public final Byte2ObjectConcurrentHashMap.ByteObjFunction transformer; + public final Consumer action; + + public ForEachTransformedMappingTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.ByteObjFunction transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Byte2ObjectConcurrentHashMap.ByteObjFunction transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Byte2ObjectConcurrentHashMap.ForEachTransformedMappingTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action + ) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedValueTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public final Function transformer; + public final Consumer action; + + public ForEachTransformedValueTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Function transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Function transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Byte2ObjectConcurrentHashMap.ForEachTransformedValueTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.val)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachValueTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public final Consumer action; + + public ForEachValueTask( + Byte2ObjectConcurrentHashMap.BulkTask p, int b, int i, int f, Byte2ObjectConcurrentHashMap.Node[] t, Consumer action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Byte2ObjectConcurrentHashMap.ForEachValueTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.val); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForwardingNode extends Byte2ObjectConcurrentHashMap.Node { + public final Byte2ObjectConcurrentHashMap.Node[] nextTable; + + public ForwardingNode(byte empty, Byte2ObjectConcurrentHashMap.Node[] tab) { + super(empty, -1, empty, null, null); + this.nextTable = tab; + } + + @Override + protected Byte2ObjectConcurrentHashMap.Node find(int h, byte k) { + Byte2ObjectConcurrentHashMap.Node[] tab = this.nextTable; + + Byte2ObjectConcurrentHashMap.Node e; + int n; + label41: + while (k != this.EMPTY && tab != null && (n = tab.length) != 0 && (e = Byte2ObjectConcurrentHashMap.tabAt(tab, n - 1 & h)) != null) { + do { + int eh = e.hash; + if (e.hash == h) { + byte ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + + if (eh < 0) { + if (!(e instanceof Byte2ObjectConcurrentHashMap.ForwardingNode)) { + return e.find(h, k); + } + + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + continue label41; + } + } while ((e = e.next) != null); + + return null; + } + + return null; + } + } + + protected abstract static class IntReturningBulkTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public int result; + + public IntReturningBulkTask(Byte2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Byte2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected int invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + protected static final class KeyIterator implements ByteIterator { + public Byte2ObjectConcurrentHashMap.Node[] tab; + public Byte2ObjectConcurrentHashMap.Node next; + public Byte2ObjectConcurrentHashMap.TableStack stack; + public Byte2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + public final Byte2ObjectConcurrentHashMap map; + public Byte2ObjectConcurrentHashMap.Node lastReturned; + + public KeyIterator(Byte2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, Byte2ObjectConcurrentHashMap map) { + this.tab = tab; + this.baseSize = size; + this.baseIndex = this.index = index; + this.baseLimit = limit; + this.next = null; + this.map = map; + this.advance(); + } + + protected final Byte2ObjectConcurrentHashMap.Node advance() { + Byte2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Byte2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + e = ((Byte2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Byte2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Byte2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Byte2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + + @Override + public final boolean hasNext() { + return this.next != null; + } + + public final boolean hasMoreElements() { + return this.next != null; + } + + @Override + public final void remove() { + Byte2ObjectConcurrentHashMap.Node p = this.lastReturned; + if (this.lastReturned == null) { + throw new IllegalStateException(); + } else { + this.lastReturned = null; + this.map.replaceNode(p.key, null, null); + } + } + + @Override + public final byte nextByte() { + Byte2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + byte k = p.key; + this.lastReturned = p; + this.advance(); + return k; + } + } + } + + public static class KeySetView implements ByteSet { + public static final long serialVersionUID = 7249069246763182397L; + public final Byte2ObjectConcurrentHashMap map; + public final V value; + + public KeySetView(Byte2ObjectConcurrentHashMap map, V value) { + this.map = map; + this.value = value; + } + + public V getMappedValue() { + return this.value; + } + + @Override + public boolean contains(byte o) { + return this.map.containsKey(o); + } + + @Override + public boolean remove(byte o) { + return this.map.remove(o) != null; + } + + @Override + public ByteIterator iterator() { + Byte2ObjectConcurrentHashMap m = this.map; + Byte2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Byte2ObjectConcurrentHashMap.KeyIterator<>(t, f, 0, f, m); + } + + @Override + public boolean add(byte e) { + V v = this.value; + if (this.value == null) { + throw new UnsupportedOperationException(); + } else { + return this.map.putVal(e, v, true) == null; + } + } + + @Override + public boolean addAll(ByteCollection c) { + boolean added = false; + V v = this.value; + if (this.value == null) { + throw new UnsupportedOperationException(); + } else { + ByteIterator iter = c.iterator(); + + while (iter.hasNext()) { + byte e = iter.nextByte(); + if (this.map.putVal(e, v, true) == null) { + added = true; + } + } + + return added; + } + } + + @Override + public int hashCode() { + int h = 0; + ByteIterator iter = this.iterator(); + + while (iter.hasNext()) { + h += Byte.hashCode(iter.nextByte()); + } + + return h; + } + + @Override + public boolean equals(Object o) { + ByteSet c; + return o instanceof ByteSet && ((c = (ByteSet)o) == this || this.containsAll(c) && c.containsAll(this)); + } + + public byte getNoEntryValue() { + return this.map.EMPTY; + } + + @Override + public int size() { + return this.map.size(); + } + + @Override + public boolean isEmpty() { + return this.map.isEmpty(); + } + + @Override + public Object[] toArray() { + Object[] out = new Byte[this.size()]; + ByteIterator iter = this.iterator(); + + int i; + for (i = 0; i < out.length && iter.hasNext(); i++) { + out[i] = iter.nextByte(); + } + + if (out.length > i + 1) { + out[i] = this.map.EMPTY; + } + + return out; + } + + @Override + public Object[] toArray(Object[] dest) { + ByteIterator iter = this.iterator(); + + int i; + for (i = 0; i < dest.length && iter.hasNext() && i <= dest.length; i++) { + dest[i] = iter.next(); + } + + if (dest.length > i + 1) { + dest[i] = this.map.EMPTY; + } + + return dest; + } + + @Override + public byte[] toByteArray() { + byte[] out = new byte[this.size()]; + ByteIterator iter = this.iterator(); + + int i; + for (i = 0; i < out.length && iter.hasNext(); i++) { + out[i] = iter.next(); + } + + if (out.length > i + 1) { + out[i] = this.map.EMPTY; + } + + return out; + } + + @Override + public byte[] toArray(byte[] dest) { + ByteIterator iter = this.iterator(); + + int i; + for (i = 0; i < dest.length && iter.hasNext() && i <= dest.length; i++) { + dest[i] = iter.next(); + } + + if (dest.length > i + 1) { + dest[i] = this.map.EMPTY; + } + + return dest; + } + + @Override + public byte[] toByteArray(byte[] dest) { + return this.toArray(dest); + } + + @Override + public boolean containsAll(Collection collection) { + for (Object element : collection) { + if (!(element instanceof Long)) { + return false; + } + + byte c = (Byte)element; + if (!this.contains(c)) { + return false; + } + } + + return true; + } + + @Override + public boolean containsAll(ByteCollection collection) { + ByteIterator iter = collection.iterator(); + + while (iter.hasNext()) { + byte element = iter.next(); + if (!this.contains(element)) { + return false; + } + } + + return true; + } + + public boolean containsAll(byte[] array) { + int i = array.length; + + while (i-- > 0) { + if (!this.contains(array[i])) { + return false; + } + } + + return true; + } + + @Override + public boolean addAll(Collection collection) { + boolean changed = false; + + for (Byte element : collection) { + byte e = element; + if (this.add(e)) { + changed = true; + } + } + + return changed; + } + + public boolean addAll(byte[] array) { + boolean changed = false; + int i = array.length; + + while (i-- > 0) { + if (this.add(array[i])) { + changed = true; + } + } + + return changed; + } + + @Override + public boolean retainAll(Collection collection) { + boolean modified = false; + ByteIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (!collection.contains(iter.next())) { + iter.remove(); + modified = true; + } + } + + return modified; + } + + @Override + public boolean retainAll(ByteCollection collection) { + if (this == collection) { + return false; + } else { + boolean modified = false; + ByteIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (!collection.contains(iter.next())) { + iter.remove(); + modified = true; + } + } + + return modified; + } + } + + public boolean retainAll(byte[] array) { + boolean modified = false; + ByteIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (Arrays.binarySearch(array, iter.next().byteValue()) < 0) { + iter.remove(); + modified = true; + } + } + + return modified; + } + + @Override + public boolean removeAll(Collection collection) { + boolean changed = false; + + for (Object element : collection) { + if (element instanceof Byte) { + byte c = (Byte)element; + if (this.remove(c)) { + changed = true; + } + } + } + + return changed; + } + + @Override + public boolean removeAll(ByteCollection collection) { + boolean changed = false; + ByteIterator iter = collection.iterator(); + + while (iter.hasNext()) { + byte element = iter.next(); + if (this.remove(element)) { + changed = true; + } + } + + return changed; + } + + public boolean removeAll(byte[] array) { + boolean changed = false; + int i = array.length; + + while (i-- > 0) { + if (this.remove(array[i])) { + changed = true; + } + } + + return changed; + } + + @Override + public void clear() { + this.map.clear(); + } + + @Override + public ByteSpliterator spliterator() { + Byte2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Byte2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Byte2ObjectConcurrentHashMap.KeySpliterator<>(t, f, 0, f, n < 0L ? 0L : n); + } + } + + protected static final class KeySpliterator extends Byte2ObjectConcurrentHashMap.Traverser implements ByteSpliterator { + public long est; + + public KeySpliterator(Byte2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est) { + super(tab, size, index, limit); + this.est = est; + } + + @Override + public ByteSpliterator trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Byte2ObjectConcurrentHashMap.KeySpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1); + } + + @Override + public boolean tryAdvance(Consumer action) { + return action instanceof ByteConsumer ? this.tryAdvance((ByteConsumer)action) : this.tryAdvance(value -> action.accept(value)); + } + + public void forEachRemaining(ByteConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(p.key); + } + } + } + + public boolean tryAdvance(ByteConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(p.key); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4353; + } + } + + protected abstract static class LongReturningBulkTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public long result; + + public LongReturningBulkTask(Byte2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Byte2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected long invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + protected static final class MapEntry implements Byte2ObjectConcurrentHashMap.Entry { + public final boolean empty; + public final byte key; + public V val; + public final Byte2ObjectConcurrentHashMap map; + + public MapEntry(boolean empty, byte key, V val, Byte2ObjectConcurrentHashMap map) { + this.empty = empty; + this.key = key; + this.val = val; + this.map = map; + } + + @Override + public boolean isEmpty() { + return this.empty; + } + + @Override + public Byte getKey() { + return this.key; + } + + @Override + public byte getByteKey() { + return this.key; + } + + @Override + public V getValue() { + return this.val; + } + + @Override + public String toString() { + return this.empty ? "EMPTY=" + this.val : this.key + "=" + this.val; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o instanceof Byte2ObjectConcurrentHashMap.Entry) { + if (this.empty != ((Byte2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + return false; + } else { + return !this.empty && this.key != ((Byte2ObjectConcurrentHashMap.Entry)o).getByteKey() + ? false + : this.val.equals(((Byte2ObjectConcurrentHashMap.Entry)o).getValue()); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.empty ? 1 : 0; + result = 31 * result + Byte.hashCode(this.key); + return 31 * result + this.val.hashCode(); + } + + @Override + public V setValue(V value) { + if (value == null) { + throw new NullPointerException(); + } else { + V v = this.val; + this.val = value; + this.map.put(this.key, value); + return v; + } + } + } + + protected static final class MapReduceEntriesTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> transformer; + public final BiFunction reducer; + public U result; + public Byte2ObjectConcurrentHashMap.MapReduceEntriesTask rights; + public Byte2ObjectConcurrentHashMap.MapReduceEntriesTask nextRight; + + public MapReduceEntriesTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.MapReduceEntriesTask nextRight, + Function, ? extends U> transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Function, ? extends U> transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.MapReduceEntriesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Byte2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.MapReduceEntriesTask t = (Byte2ObjectConcurrentHashMap.MapReduceEntriesTask)c; + + for (Byte2ObjectConcurrentHashMap.MapReduceEntriesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceEntriesToDoubleTask extends Byte2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final ToDoubleFunction> transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Byte2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask rights; + public Byte2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask nextRight; + + public MapReduceEntriesToDoubleTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask nextRight, + ToDoubleFunction> transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToDoubleFunction> transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask t = (Byte2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask)c; + + for (Byte2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceEntriesToIntTask extends Byte2ObjectConcurrentHashMap.IntReturningBulkTask { + public final ToIntFunction> transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Byte2ObjectConcurrentHashMap.MapReduceEntriesToIntTask rights; + public Byte2ObjectConcurrentHashMap.MapReduceEntriesToIntTask nextRight; + + public MapReduceEntriesToIntTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.MapReduceEntriesToIntTask nextRight, + ToIntFunction> transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToIntFunction> transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.MapReduceEntriesToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.MapReduceEntriesToIntTask t = (Byte2ObjectConcurrentHashMap.MapReduceEntriesToIntTask)c; + + for (Byte2ObjectConcurrentHashMap.MapReduceEntriesToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceEntriesToLongTask extends Byte2ObjectConcurrentHashMap.LongReturningBulkTask { + public final ToLongFunction> transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Byte2ObjectConcurrentHashMap.MapReduceEntriesToLongTask rights; + public Byte2ObjectConcurrentHashMap.MapReduceEntriesToLongTask nextRight; + + public MapReduceEntriesToLongTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.MapReduceEntriesToLongTask nextRight, + ToLongFunction> transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToLongFunction> transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.MapReduceEntriesToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.MapReduceEntriesToLongTask t = (Byte2ObjectConcurrentHashMap.MapReduceEntriesToLongTask)c; + + for (Byte2ObjectConcurrentHashMap.MapReduceEntriesToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public final Byte2ObjectConcurrentHashMap.ByteFunction transformer; + public final BiFunction reducer; + public U result; + public Byte2ObjectConcurrentHashMap.MapReduceKeysTask rights; + public Byte2ObjectConcurrentHashMap.MapReduceKeysTask nextRight; + + public MapReduceKeysTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.MapReduceKeysTask nextRight, + Byte2ObjectConcurrentHashMap.ByteFunction transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Byte2ObjectConcurrentHashMap.ByteFunction transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.MapReduceKeysTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Byte2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.MapReduceKeysTask t = (Byte2ObjectConcurrentHashMap.MapReduceKeysTask)c; + + for (Byte2ObjectConcurrentHashMap.MapReduceKeysTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceKeysToDoubleTask extends Byte2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final Byte2ObjectConcurrentHashMap.ByteToDoubleFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Byte2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask rights; + public Byte2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask nextRight; + + public MapReduceKeysToDoubleTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask nextRight, + Byte2ObjectConcurrentHashMap.ByteToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Byte2ObjectConcurrentHashMap.ByteToDoubleFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask t = (Byte2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask)c; + + for (Byte2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysToIntTask extends Byte2ObjectConcurrentHashMap.IntReturningBulkTask { + public final Byte2ObjectConcurrentHashMap.ByteToIntFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Byte2ObjectConcurrentHashMap.MapReduceKeysToIntTask rights; + public Byte2ObjectConcurrentHashMap.MapReduceKeysToIntTask nextRight; + + public MapReduceKeysToIntTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.MapReduceKeysToIntTask nextRight, + Byte2ObjectConcurrentHashMap.ByteToIntFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Byte2ObjectConcurrentHashMap.ByteToIntFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.MapReduceKeysToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.MapReduceKeysToIntTask t = (Byte2ObjectConcurrentHashMap.MapReduceKeysToIntTask)c; + + for (Byte2ObjectConcurrentHashMap.MapReduceKeysToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysToLongTask extends Byte2ObjectConcurrentHashMap.LongReturningBulkTask { + public final Byte2ObjectConcurrentHashMap.ByteToLongFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Byte2ObjectConcurrentHashMap.MapReduceKeysToLongTask rights; + public Byte2ObjectConcurrentHashMap.MapReduceKeysToLongTask nextRight; + + public MapReduceKeysToLongTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.MapReduceKeysToLongTask nextRight, + Byte2ObjectConcurrentHashMap.ByteToLongFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Byte2ObjectConcurrentHashMap.ByteToLongFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.MapReduceKeysToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.MapReduceKeysToLongTask t = (Byte2ObjectConcurrentHashMap.MapReduceKeysToLongTask)c; + + for (Byte2ObjectConcurrentHashMap.MapReduceKeysToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public final Byte2ObjectConcurrentHashMap.ByteObjFunction transformer; + public final BiFunction reducer; + public U result; + public Byte2ObjectConcurrentHashMap.MapReduceMappingsTask rights; + public Byte2ObjectConcurrentHashMap.MapReduceMappingsTask nextRight; + + public MapReduceMappingsTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.MapReduceMappingsTask nextRight, + Byte2ObjectConcurrentHashMap.ByteObjFunction transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Byte2ObjectConcurrentHashMap.ByteObjFunction transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.MapReduceMappingsTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Byte2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.MapReduceMappingsTask t = (Byte2ObjectConcurrentHashMap.MapReduceMappingsTask)c; + + for (Byte2ObjectConcurrentHashMap.MapReduceMappingsTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceMappingsToDoubleTask extends Byte2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final Byte2ObjectConcurrentHashMap.ToDoubleByteObjFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Byte2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask rights; + public Byte2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask nextRight; + + public MapReduceMappingsToDoubleTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask nextRight, + Byte2ObjectConcurrentHashMap.ToDoubleByteObjFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Byte2ObjectConcurrentHashMap.ToDoubleByteObjFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask t = (Byte2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask)c; + + for (Byte2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsToIntTask extends Byte2ObjectConcurrentHashMap.IntReturningBulkTask { + public final Byte2ObjectConcurrentHashMap.ToIntByteObjFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Byte2ObjectConcurrentHashMap.MapReduceMappingsToIntTask rights; + public Byte2ObjectConcurrentHashMap.MapReduceMappingsToIntTask nextRight; + + public MapReduceMappingsToIntTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.MapReduceMappingsToIntTask nextRight, + Byte2ObjectConcurrentHashMap.ToIntByteObjFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Byte2ObjectConcurrentHashMap.ToIntByteObjFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.MapReduceMappingsToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.MapReduceMappingsToIntTask t = (Byte2ObjectConcurrentHashMap.MapReduceMappingsToIntTask)c; + + for (Byte2ObjectConcurrentHashMap.MapReduceMappingsToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsToLongTask extends Byte2ObjectConcurrentHashMap.LongReturningBulkTask { + public final Byte2ObjectConcurrentHashMap.ToLongByteObjFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Byte2ObjectConcurrentHashMap.MapReduceMappingsToLongTask rights; + public Byte2ObjectConcurrentHashMap.MapReduceMappingsToLongTask nextRight; + + public MapReduceMappingsToLongTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.MapReduceMappingsToLongTask nextRight, + Byte2ObjectConcurrentHashMap.ToLongByteObjFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Byte2ObjectConcurrentHashMap.ToLongByteObjFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.MapReduceMappingsToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.MapReduceMappingsToLongTask t = (Byte2ObjectConcurrentHashMap.MapReduceMappingsToLongTask)c; + + for (Byte2ObjectConcurrentHashMap.MapReduceMappingsToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public final Function transformer; + public final BiFunction reducer; + public U result; + public Byte2ObjectConcurrentHashMap.MapReduceValuesTask rights; + public Byte2ObjectConcurrentHashMap.MapReduceValuesTask nextRight; + + public MapReduceValuesTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.MapReduceValuesTask nextRight, + Function transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Function transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.MapReduceValuesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Byte2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.MapReduceValuesTask t = (Byte2ObjectConcurrentHashMap.MapReduceValuesTask)c; + + for (Byte2ObjectConcurrentHashMap.MapReduceValuesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceValuesToDoubleTask extends Byte2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final ToDoubleFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Byte2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask rights; + public Byte2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask nextRight; + + public MapReduceValuesToDoubleTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask nextRight, + ToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToDoubleFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask t = (Byte2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask)c; + + for (Byte2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesToIntTask extends Byte2ObjectConcurrentHashMap.IntReturningBulkTask { + public final ToIntFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Byte2ObjectConcurrentHashMap.MapReduceValuesToIntTask rights; + public Byte2ObjectConcurrentHashMap.MapReduceValuesToIntTask nextRight; + + public MapReduceValuesToIntTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.MapReduceValuesToIntTask nextRight, + ToIntFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToIntFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.MapReduceValuesToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.MapReduceValuesToIntTask t = (Byte2ObjectConcurrentHashMap.MapReduceValuesToIntTask)c; + + for (Byte2ObjectConcurrentHashMap.MapReduceValuesToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesToLongTask extends Byte2ObjectConcurrentHashMap.LongReturningBulkTask { + public final ToLongFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Byte2ObjectConcurrentHashMap.MapReduceValuesToLongTask rights; + public Byte2ObjectConcurrentHashMap.MapReduceValuesToLongTask nextRight; + + public MapReduceValuesToLongTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.MapReduceValuesToLongTask nextRight, + ToLongFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToLongFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.MapReduceValuesToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.MapReduceValuesToLongTask t = (Byte2ObjectConcurrentHashMap.MapReduceValuesToLongTask)c; + + for (Byte2ObjectConcurrentHashMap.MapReduceValuesToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static class Node implements Byte2ObjectConcurrentHashMap.Entry { + public final byte EMPTY; + public final int hash; + public final byte key; + public volatile V val; + public volatile Byte2ObjectConcurrentHashMap.Node next; + + public Node(byte empty, int hash, byte key, V val, Byte2ObjectConcurrentHashMap.Node next) { + this.EMPTY = empty; + this.hash = hash; + this.key = key; + this.val = val; + this.next = next; + } + + @Override + public final boolean isEmpty() { + return this.key == this.EMPTY; + } + + @Override + public final Byte getKey() { + return this.key; + } + + @Override + public final byte getByteKey() { + return this.key; + } + + @Override + public final V getValue() { + return this.val; + } + + @Override + public final int hashCode() { + return Byte.hashCode(this.key) ^ this.val.hashCode(); + } + + @Override + public final String toString() { + return this.isEmpty() ? "EMPTY=" + this.val : this.key + "=" + this.val; + } + + @Override + public final V setValue(V value) { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean equals(Object o) { + boolean empty = this.isEmpty(); + if (o instanceof Byte2ObjectConcurrentHashMap.Entry) { + if (empty != ((Byte2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + return false; + } else { + return !empty && this.key != ((Byte2ObjectConcurrentHashMap.Entry)o).getByteKey() + ? false + : this.val.equals(((Byte2ObjectConcurrentHashMap.Entry)o).getValue()); + } + } else { + return false; + } + } + + protected Byte2ObjectConcurrentHashMap.Node find(int h, byte k) { + Byte2ObjectConcurrentHashMap.Node e = this; + if (k != this.EMPTY) { + do { + if (e.hash == h) { + byte ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + } while ((e = e.next) != null); + } + + return null; + } + } + + protected static final class ReduceEntriesTask extends Byte2ObjectConcurrentHashMap.BulkTask> { + public final BiFunction, Byte2ObjectConcurrentHashMap.Entry, ? extends Byte2ObjectConcurrentHashMap.Entry> reducer; + public Byte2ObjectConcurrentHashMap.Entry result; + public Byte2ObjectConcurrentHashMap.ReduceEntriesTask rights; + public Byte2ObjectConcurrentHashMap.ReduceEntriesTask nextRight; + + public ReduceEntriesTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.ReduceEntriesTask nextRight, + BiFunction, Byte2ObjectConcurrentHashMap.Entry, ? extends Byte2ObjectConcurrentHashMap.Entry> reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.reducer = reducer; + } + + public final Byte2ObjectConcurrentHashMap.Entry getRawResult() { + return this.result; + } + + @Override + public final void compute() { + BiFunction, Byte2ObjectConcurrentHashMap.Entry, ? extends Byte2ObjectConcurrentHashMap.Entry> reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.ReduceEntriesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + Byte2ObjectConcurrentHashMap.Entry r = null; + + Byte2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + r = (Byte2ObjectConcurrentHashMap.Entry)(r == null ? p : reducer.apply(r, p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.ReduceEntriesTask t = (Byte2ObjectConcurrentHashMap.ReduceEntriesTask)c; + + for (Byte2ObjectConcurrentHashMap.ReduceEntriesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + Byte2ObjectConcurrentHashMap.Entry sr = s.result; + if (s.result != null) { + Byte2ObjectConcurrentHashMap.Entry tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + + protected static final class ReduceKeysTask extends Byte2ObjectConcurrentHashMap.ByteReturningBulkTask2 { + public final byte EMPTY; + public final Byte2ObjectConcurrentHashMap.ByteReduceTaskOperator reducer; + public Byte2ObjectConcurrentHashMap.ReduceKeysTask rights; + public Byte2ObjectConcurrentHashMap.ReduceKeysTask nextRight; + + public ReduceKeysTask( + byte EMPTY, + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.ReduceKeysTask nextRight, + Byte2ObjectConcurrentHashMap.ByteReduceTaskOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.EMPTY = EMPTY; + this.reducer = reducer; + } + + public final Byte getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Byte2ObjectConcurrentHashMap.ByteReduceTaskOperator reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.ReduceKeysTask<>( + this.EMPTY, this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + boolean found = false; + byte r = this.EMPTY; + + Byte2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + byte u = p.key; + if (!found) { + found = true; + r = u; + } else if (!p.isEmpty()) { + found = true; + r = reducer.reduce(this.EMPTY, r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.ReduceKeysTask t = (Byte2ObjectConcurrentHashMap.ReduceKeysTask)c; + + for (Byte2ObjectConcurrentHashMap.ReduceKeysTask s = t.rights; s != null; s = t.rights = s.nextRight) { + byte sr = s.result; + if (s.result != this.EMPTY) { + byte tr = t.result; + t.result = t.result == this.EMPTY ? sr : reducer.reduce(this.EMPTY, tr, sr); + } + } + } + } + } + } + + protected static final class ReduceValuesTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public final BiFunction reducer; + public V result; + public Byte2ObjectConcurrentHashMap.ReduceValuesTask rights; + public Byte2ObjectConcurrentHashMap.ReduceValuesTask nextRight; + + public ReduceValuesTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.ReduceValuesTask nextRight, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.reducer = reducer; + } + + @Override + public final V getRawResult() { + return this.result; + } + + @Override + public final void compute() { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Byte2ObjectConcurrentHashMap.ReduceValuesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + V r = null; + + Byte2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + V v = p.val; + r = r == null ? v : reducer.apply(r, v); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Byte2ObjectConcurrentHashMap.ReduceValuesTask t = (Byte2ObjectConcurrentHashMap.ReduceValuesTask)c; + + for (Byte2ObjectConcurrentHashMap.ReduceValuesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + V sr = s.result; + if (s.result != null) { + V tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + + protected static final class ReservationNode extends Byte2ObjectConcurrentHashMap.Node { + public ReservationNode(byte empty) { + super(empty, -3, empty, null, null); + } + + @Override + protected Byte2ObjectConcurrentHashMap.Node find(int h, byte k) { + return null; + } + } + + protected static final class SearchEntriesTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> searchFunction; + public final AtomicReference result; + + public SearchEntriesTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Function, ? extends U> searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Function, ? extends U> searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Byte2ObjectConcurrentHashMap.SearchEntriesTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Byte2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + + return; + } + } + } + } + } + } + + protected static final class SearchKeysTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public final Byte2ObjectConcurrentHashMap.ByteFunction searchFunction; + public final AtomicReference result; + + public SearchKeysTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.ByteFunction searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Byte2ObjectConcurrentHashMap.ByteFunction searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Byte2ObjectConcurrentHashMap.SearchKeysTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Byte2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.key)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static final class SearchMappingsTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public final Byte2ObjectConcurrentHashMap.ByteObjFunction searchFunction; + public final AtomicReference result; + + public SearchMappingsTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Byte2ObjectConcurrentHashMap.ByteObjFunction searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Byte2ObjectConcurrentHashMap.ByteObjFunction searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Byte2ObjectConcurrentHashMap.SearchMappingsTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result) + .fork(); + } + + while (result.get() == null) { + Byte2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.key, p.val)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static final class SearchValuesTask extends Byte2ObjectConcurrentHashMap.BulkTask { + public final Function searchFunction; + public final AtomicReference result; + + public SearchValuesTask( + Byte2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Byte2ObjectConcurrentHashMap.Node[] t, + Function searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Function searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Byte2ObjectConcurrentHashMap.SearchValuesTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Byte2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.val)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static class Segment extends ReentrantLock implements Serializable { + public static final long serialVersionUID = 2249069246763182397L; + public final float loadFactor; + + public Segment(float lf) { + this.loadFactor = lf; + } + } + + protected static final class TableStack { + public int length; + public int index; + public Byte2ObjectConcurrentHashMap.Node[] tab; + public Byte2ObjectConcurrentHashMap.TableStack next; + + public TableStack() { + } + } + + @FunctionalInterface + public interface ToByteFunction { + byte applyAsByte(T var1); + } + + @FunctionalInterface + public interface ToDoubleByteObjFunction { + double applyAsDouble(byte var1, V var2); + } + + @FunctionalInterface + public interface ToIntByteObjFunction { + int applyAsInt(byte var1, V var2); + } + + @FunctionalInterface + public interface ToLongByteObjFunction { + long applyAsLong(byte var1, V var2); + } + + protected static class Traverser { + public Byte2ObjectConcurrentHashMap.Node[] tab; + public Byte2ObjectConcurrentHashMap.Node next; + public Byte2ObjectConcurrentHashMap.TableStack stack; + public Byte2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + + public Traverser(Byte2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit) { + this.tab = tab; + this.baseSize = size; + this.baseIndex = this.index = index; + this.baseLimit = limit; + this.next = null; + } + + protected final Byte2ObjectConcurrentHashMap.Node advance() { + Byte2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Byte2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + e = ((Byte2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Byte2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Byte2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Byte2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + } + + protected static final class TreeBin extends Byte2ObjectConcurrentHashMap.Node { + public Byte2ObjectConcurrentHashMap.TreeNode root; + public volatile Byte2ObjectConcurrentHashMap.TreeNode first; + public volatile Thread waiter; + public volatile int lockState; + public static final int WRITER = 1; + public static final int WAITER = 2; + public static final int READER = 4; + protected static final Unsafe U; + protected static final long LOCKSTATE; + + protected int tieBreakOrder(byte a, byte b) { + int comp = Byte.compare(a, b); + return comp > 0 ? 1 : -1; + } + + public TreeBin(byte empty, Byte2ObjectConcurrentHashMap.TreeNode b) { + super(empty, -2, empty, null, null); + this.first = b; + Byte2ObjectConcurrentHashMap.TreeNode r = null; + Byte2ObjectConcurrentHashMap.TreeNode x = b; + + while (x != null) { + Byte2ObjectConcurrentHashMap.TreeNode next = (Byte2ObjectConcurrentHashMap.TreeNode)x.next; + x.left = x.right = null; + if (r == null) { + x.parent = null; + x.red = false; + r = x; + } else { + byte k = x.key; + int h = x.hash; + Class kc = null; + Byte2ObjectConcurrentHashMap.TreeNode p = r; + + int dir; + Byte2ObjectConcurrentHashMap.TreeNode xp; + do { + byte pk = p.key; + int ph = p.hash; + if (p.hash > h) { + dir = -1; + } else if (ph < h) { + dir = 1; + } else if ((dir = Byte.compare(k, pk)) == 0) { + dir = this.tieBreakOrder(k, pk); + } + + xp = p; + } while ((p = dir <= 0 ? p.left : p.right) != null); + + x.parent = xp; + if (dir <= 0) { + xp.left = x; + } else { + xp.right = x; + } + + r = this.balanceInsertion(r, x); + } + + x = next; + } + + this.root = r; + + assert this.checkInvariants(this.root); + } + + protected final void lockRoot() { + if (!U.compareAndSwapInt(this, LOCKSTATE, 0, 1)) { + this.contendedLock(); + } + } + + protected final void unlockRoot() { + this.lockState = 0; + } + + protected final void contendedLock() { + boolean waiting = false; + + while (true) { + int s = this.lockState; + if ((this.lockState & -3) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, 1)) { + if (waiting) { + this.waiter = null; + } + + return; + } + } else if ((s & 2) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, s | 2)) { + waiting = true; + this.waiter = Thread.currentThread(); + } + } else if (waiting) { + LockSupport.park(this); + } + } + } + + @Override + protected final Byte2ObjectConcurrentHashMap.Node find(int h, byte k) { + if (k != this.EMPTY) { + Byte2ObjectConcurrentHashMap.Node e = this.first; + + while (e != null) { + int s = this.lockState; + if ((this.lockState & 3) != 0) { + if (e.hash == h) { + byte ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + + e = e.next; + } else if (U.compareAndSwapInt(this, LOCKSTATE, s, s + 4)) { + Byte2ObjectConcurrentHashMap.TreeNode p; + try { + Byte2ObjectConcurrentHashMap.TreeNode r = this.root; + p = this.root == null ? null : r.findTreeNode(h, k, null); + } finally { + if (U.getAndAddInt(this, LOCKSTATE, -4) == 6) { + Thread w = this.waiter; + if (this.waiter != null) { + LockSupport.unpark(w); + } + } + } + + return p; + } + } + } + + return null; + } + + protected final Byte2ObjectConcurrentHashMap.TreeNode putTreeVal(int h, byte k, V v) { + Class kc = null; + boolean searched = false; + Byte2ObjectConcurrentHashMap.TreeNode p = this.root; + + while (true) { + if (p == null) { + this.first = this.root = new Byte2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, h, k, v, null, null); + } else { + int ph = p.hash; + int dir; + if (p.hash > h) { + dir = -1; + } else if (ph < h) { + dir = 1; + } else { + byte pk = p.key; + if (p.key == k || pk != this.EMPTY && k == pk) { + return p; + } + + if ((dir = Byte.compare(k, pk)) == 0) { + if (!searched) { + searched = true; + Byte2ObjectConcurrentHashMap.TreeNode ch = p.left; + Byte2ObjectConcurrentHashMap.TreeNode q; + if (p.left != null && (q = ch.findTreeNode(h, k, kc)) != null) { + return q; + } + + ch = p.right; + if (p.right != null && (q = ch.findTreeNode(h, k, kc)) != null) { + return q; + } + } + + dir = this.tieBreakOrder(k, pk); + } + } + + Byte2ObjectConcurrentHashMap.TreeNode xp = p; + if ((p = dir <= 0 ? p.left : p.right) != null) { + continue; + } + + Byte2ObjectConcurrentHashMap.TreeNode f = this.first; + Byte2ObjectConcurrentHashMap.TreeNode x; + this.first = x = new Byte2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, h, k, v, f, xp); + if (f != null) { + f.prev = x; + } + + if (dir <= 0) { + xp.left = x; + } else { + xp.right = x; + } + + if (!xp.red) { + x.red = true; + } else { + this.lockRoot(); + + try { + this.root = this.balanceInsertion(this.root, x); + } finally { + this.unlockRoot(); + } + } + } + + assert this.checkInvariants(this.root); + + return null; + } + } + + protected final boolean removeTreeNode(Byte2ObjectConcurrentHashMap.TreeNode p) { + Byte2ObjectConcurrentHashMap.TreeNode next = (Byte2ObjectConcurrentHashMap.TreeNode)p.next; + Byte2ObjectConcurrentHashMap.TreeNode pred = p.prev; + if (pred == null) { + this.first = next; + } else { + pred.next = next; + } + + if (next != null) { + next.prev = pred; + } + + if (this.first == null) { + this.root = null; + return true; + } else { + Byte2ObjectConcurrentHashMap.TreeNode r = this.root; + if (this.root != null && r.right != null) { + Byte2ObjectConcurrentHashMap.TreeNode rl = r.left; + if (r.left != null && rl.left != null) { + this.lockRoot(); + + try { + Byte2ObjectConcurrentHashMap.TreeNode pl = p.left; + Byte2ObjectConcurrentHashMap.TreeNode pr = p.right; + Byte2ObjectConcurrentHashMap.TreeNode replacement; + if (pl != null && pr != null) { + Byte2ObjectConcurrentHashMap.TreeNode s = pr; + + while (true) { + Byte2ObjectConcurrentHashMap.TreeNode sl = s.left; + if (s.left == null) { + boolean c = s.red; + s.red = p.red; + p.red = c; + Byte2ObjectConcurrentHashMap.TreeNode sr = s.right; + Byte2ObjectConcurrentHashMap.TreeNode pp = p.parent; + if (s == pr) { + p.parent = s; + s.right = p; + } else { + Byte2ObjectConcurrentHashMap.TreeNode sp = s.parent; + if ((p.parent = sp) != null) { + if (s == sp.left) { + sp.left = p; + } else { + sp.right = p; + } + } + + if ((s.right = pr) != null) { + pr.parent = s; + } + } + + p.left = null; + if ((p.right = sr) != null) { + sr.parent = p; + } + + if ((s.left = pl) != null) { + pl.parent = s; + } + + if ((s.parent = pp) == null) { + r = s; + } else if (p == pp.left) { + pp.left = s; + } else { + pp.right = s; + } + + if (sr != null) { + replacement = sr; + } else { + replacement = p; + } + break; + } + + s = sl; + } + } else if (pl != null) { + replacement = pl; + } else if (pr != null) { + replacement = pr; + } else { + replacement = p; + } + + if (replacement != p) { + Byte2ObjectConcurrentHashMap.TreeNode ppx = replacement.parent = p.parent; + if (ppx == null) { + r = replacement; + } else if (p == ppx.left) { + ppx.left = replacement; + } else { + ppx.right = replacement; + } + + p.left = p.right = p.parent = null; + } + + this.root = p.red ? r : this.balanceDeletion(r, replacement); + if (p == replacement) { + Byte2ObjectConcurrentHashMap.TreeNode ppx = p.parent; + if (p.parent != null) { + if (p == ppx.left) { + ppx.left = null; + } else if (p == ppx.right) { + ppx.right = null; + } + + p.parent = null; + } + } + } finally { + this.unlockRoot(); + } + + assert this.checkInvariants(this.root); + + return false; + } + } + + return true; + } + } + + protected Byte2ObjectConcurrentHashMap.TreeNode rotateLeft( + Byte2ObjectConcurrentHashMap.TreeNode root, Byte2ObjectConcurrentHashMap.TreeNode p + ) { + if (p != null) { + Byte2ObjectConcurrentHashMap.TreeNode r = p.right; + if (p.right != null) { + Byte2ObjectConcurrentHashMap.TreeNode rl; + if ((rl = p.right = r.left) != null) { + rl.parent = p; + } + + Byte2ObjectConcurrentHashMap.TreeNode pp; + if ((pp = r.parent = p.parent) == null) { + root = r; + r.red = false; + } else if (pp.left == p) { + pp.left = r; + } else { + pp.right = r; + } + + r.left = p; + p.parent = r; + } + } + + return root; + } + + protected Byte2ObjectConcurrentHashMap.TreeNode rotateRight( + Byte2ObjectConcurrentHashMap.TreeNode root, Byte2ObjectConcurrentHashMap.TreeNode p + ) { + if (p != null) { + Byte2ObjectConcurrentHashMap.TreeNode l = p.left; + if (p.left != null) { + Byte2ObjectConcurrentHashMap.TreeNode lr; + if ((lr = p.left = l.right) != null) { + lr.parent = p; + } + + Byte2ObjectConcurrentHashMap.TreeNode pp; + if ((pp = l.parent = p.parent) == null) { + root = l; + l.red = false; + } else if (pp.right == p) { + pp.right = l; + } else { + pp.left = l; + } + + l.right = p; + p.parent = l; + } + } + + return root; + } + + protected Byte2ObjectConcurrentHashMap.TreeNode balanceInsertion( + Byte2ObjectConcurrentHashMap.TreeNode root, Byte2ObjectConcurrentHashMap.TreeNode x + ) { + x.red = true; + + while (true) { + Byte2ObjectConcurrentHashMap.TreeNode xp = x.parent; + if (x.parent == null) { + x.red = false; + return x; + } + + if (!xp.red) { + break; + } + + Byte2ObjectConcurrentHashMap.TreeNode xpp = xp.parent; + if (xp.parent == null) { + break; + } + + Byte2ObjectConcurrentHashMap.TreeNode xppl = xpp.left; + if (xp == xpp.left) { + Byte2ObjectConcurrentHashMap.TreeNode xppr = xpp.right; + if (xpp.right != null && xppr.red) { + xppr.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } else { + if (x == xp.right) { + x = xp; + root = this.rotateLeft(root, xp); + xpp = (xp = xp.parent) == null ? null : xp.parent; + } + + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = this.rotateRight(root, xpp); + } + } + } + } else if (xppl != null && xppl.red) { + xppl.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } else { + if (x == xp.left) { + x = xp; + root = this.rotateRight(root, xp); + xpp = (xp = xp.parent) == null ? null : xp.parent; + } + + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = this.rotateLeft(root, xpp); + } + } + } + } + + return root; + } + + protected Byte2ObjectConcurrentHashMap.TreeNode balanceDeletion( + Byte2ObjectConcurrentHashMap.TreeNode root, Byte2ObjectConcurrentHashMap.TreeNode x + ) { + while (x != null && x != root) { + Byte2ObjectConcurrentHashMap.TreeNode xp = x.parent; + if (x.parent == null) { + x.red = false; + return x; + } + + if (x.red) { + x.red = false; + return root; + } + + Byte2ObjectConcurrentHashMap.TreeNode xpl = xp.left; + if (xp.left == x) { + Byte2ObjectConcurrentHashMap.TreeNode xpr = xp.right; + if (xp.right != null && xpr.red) { + xpr.red = false; + xp.red = true; + root = this.rotateLeft(root, xp); + xp = x.parent; + xpr = x.parent == null ? null : xp.right; + } + + if (xpr == null) { + x = xp; + } else { + Byte2ObjectConcurrentHashMap.TreeNode sl = xpr.left; + Byte2ObjectConcurrentHashMap.TreeNode sr = xpr.right; + if (sr != null && sr.red || sl != null && sl.red) { + if (sr == null || !sr.red) { + if (sl != null) { + sl.red = false; + } + + xpr.red = true; + root = this.rotateRight(root, xpr); + xp = x.parent; + xpr = x.parent == null ? null : xp.right; + } + + if (xpr != null) { + xpr.red = xp == null ? false : xp.red; + sr = xpr.right; + if (xpr.right != null) { + sr.red = false; + } + } + + if (xp != null) { + xp.red = false; + root = this.rotateLeft(root, xp); + } + + x = root; + } else { + xpr.red = true; + x = xp; + } + } + } else { + if (xpl != null && xpl.red) { + xpl.red = false; + xp.red = true; + root = this.rotateRight(root, xp); + xp = x.parent; + xpl = x.parent == null ? null : xp.left; + } + + if (xpl == null) { + x = xp; + } else { + Byte2ObjectConcurrentHashMap.TreeNode sl = xpl.left; + Byte2ObjectConcurrentHashMap.TreeNode sr = xpl.right; + if (sl != null && sl.red || sr != null && sr.red) { + if (sl == null || !sl.red) { + if (sr != null) { + sr.red = false; + } + + xpl.red = true; + root = this.rotateLeft(root, xpl); + xp = x.parent; + xpl = x.parent == null ? null : xp.left; + } + + if (xpl != null) { + xpl.red = xp == null ? false : xp.red; + sl = xpl.left; + if (xpl.left != null) { + sl.red = false; + } + } + + if (xp != null) { + xp.red = false; + root = this.rotateRight(root, xp); + } + + x = root; + } else { + xpl.red = true; + x = xp; + } + } + } + } + + return root; + } + + protected boolean checkInvariants(Byte2ObjectConcurrentHashMap.TreeNode t) { + Byte2ObjectConcurrentHashMap.TreeNode tp = t.parent; + Byte2ObjectConcurrentHashMap.TreeNode tl = t.left; + Byte2ObjectConcurrentHashMap.TreeNode tr = t.right; + Byte2ObjectConcurrentHashMap.TreeNode tb = t.prev; + Byte2ObjectConcurrentHashMap.TreeNode tn = (Byte2ObjectConcurrentHashMap.TreeNode)t.next; + if (tb != null && tb.next != t) { + return false; + } else if (tn != null && tn.prev != t) { + return false; + } else if (tp != null && t != tp.left && t != tp.right) { + return false; + } else if (tl == null || tl.parent == t && tl.hash <= t.hash) { + if (tr == null || tr.parent == t && tr.hash >= t.hash) { + if (t.red && tl != null && tl.red && tr != null && tr.red) { + return false; + } else { + return tl != null && !this.checkInvariants(tl) ? false : tr == null || this.checkInvariants(tr); + } + } else { + return false; + } + } else { + return false; + } + } + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + U = (Unsafe)f.get(null); + Class k = Byte2ObjectConcurrentHashMap.TreeBin.class; + LOCKSTATE = U.objectFieldOffset(k.getDeclaredField("lockState")); + } catch (Exception var2) { + throw new Error(var2); + } + } + } + + protected static final class TreeNode extends Byte2ObjectConcurrentHashMap.Node { + public Byte2ObjectConcurrentHashMap.TreeNode parent; + public Byte2ObjectConcurrentHashMap.TreeNode left; + public Byte2ObjectConcurrentHashMap.TreeNode right; + public Byte2ObjectConcurrentHashMap.TreeNode prev; + public boolean red; + + public TreeNode(byte empty, int hash, byte key, V val, Byte2ObjectConcurrentHashMap.Node next, Byte2ObjectConcurrentHashMap.TreeNode parent) { + super(empty, hash, key, val, next); + this.parent = parent; + } + + @Override + protected Byte2ObjectConcurrentHashMap.Node find(int h, byte k) { + return this.findTreeNode(h, k, null); + } + + protected final Byte2ObjectConcurrentHashMap.TreeNode findTreeNode(int h, byte k, Class kc) { + if (k != this.EMPTY) { + Byte2ObjectConcurrentHashMap.TreeNode p = this; + + do { + Byte2ObjectConcurrentHashMap.TreeNode pl = p.left; + Byte2ObjectConcurrentHashMap.TreeNode pr = p.right; + int ph = p.hash; + if (p.hash > h) { + p = pl; + } else if (ph < h) { + p = pr; + } else { + byte pk = p.key; + if (p.key == k || pk != this.EMPTY && k == pk) { + return p; + } + + if (pl == null) { + p = pr; + } else if (pr == null) { + p = pl; + } else { + int dir; + if ((dir = Byte.compare(k, pk)) != 0) { + p = dir < 0 ? pl : pr; + } else { + Byte2ObjectConcurrentHashMap.TreeNode q; + if ((q = pr.findTreeNode(h, k, kc)) != null) { + return q; + } + + p = pl; + } + } + } + } while (p != null); + } + + return null; + } + } + + protected static final class ValueIterator extends Byte2ObjectConcurrentHashMap.BaseIterator implements ObjectIterator, Enumeration { + public ValueIterator(Byte2ObjectConcurrentHashMap.Node[] tab, int index, int size, int limit, Byte2ObjectConcurrentHashMap map) { + super(tab, index, size, limit, map); + } + + @Override + public final V next() { + Byte2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + V v = p.val; + this.lastReturned = p; + this.advance(); + return v; + } + } + + @Override + public final V nextElement() { + return this.next(); + } + } + + protected static final class ValueSpliterator extends Byte2ObjectConcurrentHashMap.Traverser implements ObjectSpliterator { + public long est; + + public ValueSpliterator(Byte2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est) { + super(tab, size, index, limit); + this.est = est; + } + + @Override + public ObjectSpliterator trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Byte2ObjectConcurrentHashMap.ValueSpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1); + } + + @Override + public void forEachRemaining(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(p.val); + } + } + } + + @Override + public boolean tryAdvance(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(p.val); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4352; + } + } + + protected static final class ValuesView extends Byte2ObjectConcurrentHashMap.CollectionView implements FastCollection, Serializable { + public static final long serialVersionUID = 2249069246763182397L; + + public ValuesView(Byte2ObjectConcurrentHashMap map) { + super(map); + } + + @Override + public final boolean contains(Object o) { + return this.map.containsValue(o); + } + + @Override + public final boolean remove(Object o) { + if (o != null) { + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (o.equals(it.next())) { + it.remove(); + return true; + } + } + } + + return false; + } + + @Override + public final ObjectIterator iterator() { + Byte2ObjectConcurrentHashMap m = this.map; + Byte2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Byte2ObjectConcurrentHashMap.ValueIterator<>(t, f, 0, f, m); + } + + @Override + public final boolean add(V e) { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public ObjectSpliterator spliterator() { + Byte2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Byte2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Byte2ObjectConcurrentHashMap.ValueSpliterator<>(t, f, 0, f, n < 0L ? 0L : n); + } + + @Override + public void forEach(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Byte2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.val); + } + } + } + } + + @Override + public void forEach( + FastCollection.FastConsumerD9 consumer, + A a, + double d1, + double d2, + double d3, + double d4, + double d5, + double d6, + double d7, + double d8, + double d9, + B b, + C c, + D d + ) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Byte2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, a, d1, d2, d3, d4, d5, d6, d7, d8, d9, b, c, d); + } + } + } + } + + @Override + public void forEach( + FastCollection.FastConsumerD6 consumer, A a, double d1, double d2, double d3, double d4, double d5, double d6, B b, C c, D d + ) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Byte2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, a, d1, d2, d3, d4, d5, d6, b, c, d); + } + } + } + } + + @Override + public void forEachWithFloat(FastCollection.FastConsumerF consumer, float ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Byte2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + + @Override + public void forEachWithInt(FastCollection.FastConsumerI consumer, int ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Byte2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + + @Override + public void forEachWithLong(FastCollection.FastConsumerL consumer, long ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Byte2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Byte2ObjectConcurrentHashMap.Node[] tab = tt; + Byte2ObjectConcurrentHashMap.Node next = null; + Byte2ObjectConcurrentHashMap.TableStack stack = null; + Byte2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Byte2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Byte2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Byte2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Byte2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Byte2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Byte2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Byte2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Byte2ObjectConcurrentHashMap.TreeBin) { + p = ((Byte2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Byte2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Byte2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + } +} diff --git a/src/com/hypixel/fastutil/bytes/Byte2ObjectOperator.java b/src/com/hypixel/fastutil/bytes/Byte2ObjectOperator.java new file mode 100644 index 0000000..8590f29 --- /dev/null +++ b/src/com/hypixel/fastutil/bytes/Byte2ObjectOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.bytes; + +@FunctionalInterface +public interface Byte2ObjectOperator { + V apply(byte var1, V var2); +} diff --git a/src/com/hypixel/fastutil/bytes/Byte2ShortOperator.java b/src/com/hypixel/fastutil/bytes/Byte2ShortOperator.java new file mode 100644 index 0000000..24131cf --- /dev/null +++ b/src/com/hypixel/fastutil/bytes/Byte2ShortOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.bytes; + +@FunctionalInterface +public interface Byte2ShortOperator { + short apply(byte var1, short var2); +} diff --git a/src/com/hypixel/fastutil/chars/Char2ByteOperator.java b/src/com/hypixel/fastutil/chars/Char2ByteOperator.java new file mode 100644 index 0000000..8de60f6 --- /dev/null +++ b/src/com/hypixel/fastutil/chars/Char2ByteOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.chars; + +@FunctionalInterface +public interface Char2ByteOperator { + byte apply(char var1, byte var2); +} diff --git a/src/com/hypixel/fastutil/chars/Char2CharOperator.java b/src/com/hypixel/fastutil/chars/Char2CharOperator.java new file mode 100644 index 0000000..9a7a628 --- /dev/null +++ b/src/com/hypixel/fastutil/chars/Char2CharOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.chars; + +@FunctionalInterface +public interface Char2CharOperator { + char apply(char var1, char var2); +} diff --git a/src/com/hypixel/fastutil/chars/Char2DoubleOperator.java b/src/com/hypixel/fastutil/chars/Char2DoubleOperator.java new file mode 100644 index 0000000..78cc019 --- /dev/null +++ b/src/com/hypixel/fastutil/chars/Char2DoubleOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.chars; + +@FunctionalInterface +public interface Char2DoubleOperator { + double apply(char var1, double var2); +} diff --git a/src/com/hypixel/fastutil/chars/Char2FloatOperator.java b/src/com/hypixel/fastutil/chars/Char2FloatOperator.java new file mode 100644 index 0000000..5ff2390 --- /dev/null +++ b/src/com/hypixel/fastutil/chars/Char2FloatOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.chars; + +@FunctionalInterface +public interface Char2FloatOperator { + float apply(char var1, float var2); +} diff --git a/src/com/hypixel/fastutil/chars/Char2IntOperator.java b/src/com/hypixel/fastutil/chars/Char2IntOperator.java new file mode 100644 index 0000000..3e365d2 --- /dev/null +++ b/src/com/hypixel/fastutil/chars/Char2IntOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.chars; + +@FunctionalInterface +public interface Char2IntOperator { + int apply(char var1, int var2); +} diff --git a/src/com/hypixel/fastutil/chars/Char2LongOperator.java b/src/com/hypixel/fastutil/chars/Char2LongOperator.java new file mode 100644 index 0000000..463b04a --- /dev/null +++ b/src/com/hypixel/fastutil/chars/Char2LongOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.chars; + +@FunctionalInterface +public interface Char2LongOperator { + long apply(char var1, long var2); +} diff --git a/src/com/hypixel/fastutil/chars/Char2ObjectConcurrentHashMap.java b/src/com/hypixel/fastutil/chars/Char2ObjectConcurrentHashMap.java new file mode 100644 index 0000000..897ea6e --- /dev/null +++ b/src/com/hypixel/fastutil/chars/Char2ObjectConcurrentHashMap.java @@ -0,0 +1,10256 @@ +package com.hypixel.fastutil.chars; + +import com.hypixel.fastutil.FastCollection; +import com.hypixel.fastutil.util.SneakyThrow; +import com.hypixel.fastutil.util.TLRUtil; +import it.unimi.dsi.fastutil.chars.Char2ObjectMap; +import it.unimi.dsi.fastutil.chars.CharCollection; +import it.unimi.dsi.fastutil.chars.CharConsumer; +import it.unimi.dsi.fastutil.chars.CharIterator; +import it.unimi.dsi.fastutil.chars.CharSet; +import it.unimi.dsi.fastutil.chars.CharSpliterator; +import it.unimi.dsi.fastutil.objects.ObjectCollection; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import it.unimi.dsi.fastutil.objects.ObjectSpliterator; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.CountedCompleter; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.DoubleBinaryOperator; +import java.util.function.Function; +import java.util.function.IntBinaryOperator; +import java.util.function.LongBinaryOperator; +import java.util.function.Predicate; +import java.util.function.ToDoubleFunction; +import java.util.function.ToIntFunction; +import java.util.function.ToLongFunction; +import sun.misc.Unsafe; + +public class Char2ObjectConcurrentHashMap { + protected static final long serialVersionUID = 7249069246763182397L; + protected static final int MAXIMUM_CAPACITY = 1073741824; + protected static final int DEFAULT_CAPACITY = 16; + protected static final int MAX_ARRAY_SIZE = 2147483639; + protected static final int DEFAULT_CONCURRENCY_LEVEL = 16; + protected static final float LOAD_FACTOR = 0.75F; + protected static final int TREEIFY_THRESHOLD = 8; + protected static final int UNTREEIFY_THRESHOLD = 6; + protected static final int MIN_TREEIFY_CAPACITY = 64; + protected static final int MIN_TRANSFER_STRIDE = 16; + protected static int RESIZE_STAMP_BITS = 16; + protected static final int MAX_RESIZERS = (1 << 32 - RESIZE_STAMP_BITS) - 1; + protected static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS; + protected static final int MOVED = -1; + protected static final int TREEBIN = -2; + protected static final int RESERVED = -3; + protected static final int HASH_BITS = Integer.MAX_VALUE; + protected static final int NCPU = Runtime.getRuntime().availableProcessors(); + protected transient volatile Char2ObjectConcurrentHashMap.Node[] table; + protected transient volatile Char2ObjectConcurrentHashMap.Node[] nextTable; + protected transient volatile long baseCount; + protected transient volatile int sizeCtl; + protected transient volatile int transferIndex; + protected transient volatile int cellsBusy; + protected transient volatile Char2ObjectConcurrentHashMap.CounterCell[] counterCells; + protected transient Char2ObjectConcurrentHashMap.KeySetView keySet; + protected transient Char2ObjectConcurrentHashMap.ValuesView values; + protected transient Char2ObjectConcurrentHashMap.EntrySetView entrySet; + protected final char EMPTY; + protected static final Unsafe U; + protected static final long SIZECTL; + protected static final long TRANSFERINDEX; + protected static final long BASECOUNT; + protected static final long CELLSBUSY; + protected static final long CELLVALUE; + protected static final long ABASE; + protected static final int ASHIFT; + + protected static final int spread(int h) { + return (h ^ h >>> 16) & 2147483647; + } + + protected static final int tableSizeFor(int c) { + int n = c - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return n < 0 ? 1 : (n >= 1073741824 ? 1073741824 : n + 1); + } + + protected static final Char2ObjectConcurrentHashMap.Node tabAt(Char2ObjectConcurrentHashMap.Node[] tab, int i) { + return (Char2ObjectConcurrentHashMap.Node)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); + } + + protected static final boolean casTabAt( + Char2ObjectConcurrentHashMap.Node[] tab, int i, Char2ObjectConcurrentHashMap.Node c, Char2ObjectConcurrentHashMap.Node v + ) { + return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v); + } + + protected static final void setTabAt(Char2ObjectConcurrentHashMap.Node[] tab, int i, Char2ObjectConcurrentHashMap.Node v) { + U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v); + } + + public Char2ObjectConcurrentHashMap() { + this.EMPTY = '\uffff'; + } + + public Char2ObjectConcurrentHashMap(boolean nonce, char emptyValue) { + this.EMPTY = emptyValue; + } + + public Char2ObjectConcurrentHashMap(int initialCapacity) { + this(initialCapacity, true, '\uffff'); + } + + public Char2ObjectConcurrentHashMap(int initialCapacity, boolean nonce, char emptyValue) { + if (initialCapacity < 0) { + throw new IllegalArgumentException(); + } else { + int cap = initialCapacity >= 536870912 ? 1073741824 : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1); + this.sizeCtl = cap; + this.EMPTY = emptyValue; + } + } + + public Char2ObjectConcurrentHashMap(Map m, char emptyValue) { + this.sizeCtl = 16; + this.EMPTY = emptyValue; + this.putAll(m); + } + + public Char2ObjectConcurrentHashMap(Char2ObjectConcurrentHashMap m) { + this.sizeCtl = 16; + this.EMPTY = m.EMPTY; + this.putAll(m); + } + + public Char2ObjectConcurrentHashMap(Char2ObjectMap m) { + this.sizeCtl = 16; + this.EMPTY = '\uffff'; + this.putAll(m); + } + + public Char2ObjectConcurrentHashMap(Char2ObjectMap m, char emptyValue) { + this.sizeCtl = 16; + this.EMPTY = emptyValue; + this.putAll(m); + } + + public Char2ObjectConcurrentHashMap(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, 1, '\uffff'); + } + + public Char2ObjectConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel, char emptyValue) { + if (loadFactor > 0.0F && initialCapacity >= 0 && concurrencyLevel > 0) { + if (initialCapacity < concurrencyLevel) { + initialCapacity = concurrencyLevel; + } + + long size = (long)(1.0 + (float)initialCapacity / loadFactor); + int cap = size >= 1073741824L ? 1073741824 : tableSizeFor((int)size); + this.sizeCtl = cap; + this.EMPTY = emptyValue; + } else { + throw new IllegalArgumentException(); + } + } + + public int size() { + long n = this.sumCount(); + return n < 0L ? 0 : (n > 2147483647L ? Integer.MAX_VALUE : (int)n); + } + + public boolean isEmpty() { + return this.sumCount() <= 0L; + } + + public V get(char key) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + int h = spread(Character.hashCode(key)); + Char2ObjectConcurrentHashMap.Node[] tab = this.table; + Char2ObjectConcurrentHashMap.Node e; + int n; + if (this.table != null && (n = tab.length) > 0 && (e = tabAt(tab, n - 1 & h)) != null) { + int eh = e.hash; + if (e.hash == h) { + char ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + return e.val; + } + } else if (eh < 0) { + Char2ObjectConcurrentHashMap.Node p; + return (p = e.find(h, key)) != null ? p.val : null; + } + + while ((e = e.next) != null) { + if (e.hash == h) { + char ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + return e.val; + } + } + } + } + + return null; + } + } + + public boolean containsKey(char key) { + return this.get(key) != null; + } + + public boolean containsValue(Object value) { + if (value == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label85: + while (true) { + if (p != null) { + next = p; + break; + } + + if (baseIndex < baseLimit) { + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab != null && (n = tab.length) > index && index >= 0) { + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + continue; + } + + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + continue label85; + } + } + } + + next = null; + break; + } + + if (p == null) { + break; + } + + V v = p.val; + if (p.val == value || v != null && value.equals(v)) { + return true; + } + } + } + + return false; + } + } + + public V put(char key, V value) { + return this.putVal(key, value, false); + } + + protected final V putVal(char key, V value, boolean onlyIfAbsent) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value == null) { + throw new NullPointerException(); + } else { + int hash = spread(Character.hashCode(key)); + int binCount = 0; + Char2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Char2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & hash)) == null) { + if (casTabAt(tab, i, null, new Char2ObjectConcurrentHashMap.Node<>(this.EMPTY, hash, key, value, null))) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + V oldVal = null; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Char2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Char2ObjectConcurrentHashMap.Node p; + if ((p = ((Char2ObjectConcurrentHashMap.TreeBin)f).putTreeVal(hash, key, value)) != null) { + oldVal = p.val; + if (!onlyIfAbsent) { + p.val = value; + } + } + } + } else { + binCount = 1; + Char2ObjectConcurrentHashMap.Node e = f; + + while (true) { + if (e.hash == hash) { + char ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + oldVal = e.val; + if (!onlyIfAbsent) { + e.val = value; + } + break; + } + } + + Char2ObjectConcurrentHashMap.Node pred = e; + if ((e = e.next) == null) { + pred.next = new Char2ObjectConcurrentHashMap.Node<>(this.EMPTY, hash, key, value, null); + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + + if (oldVal != null) { + return oldVal; + } + break; + } + } + } + } + + this.addCount(1L, binCount); + return null; + } + } + + public void putAll(Map m) { + this.tryPresize(m.size()); + + for (Map.Entry e : m.entrySet()) { + this.putVal(e.getKey(), (V)e.getValue(), false); + } + } + + public void putAll(Char2ObjectConcurrentHashMap m) { + this.tryPresize(m.size()); + + for (Char2ObjectMap.Entry e : m.char2ObjectEntrySet()) { + this.putVal(e.getCharKey(), (V)e.getValue(), false); + } + } + + public void putAll(Char2ObjectMap m) { + this.tryPresize(m.size()); + + for (Char2ObjectMap.Entry next : m.char2ObjectEntrySet()) { + this.putVal(next.getCharKey(), (V)next.getValue(), false); + } + } + + public V remove(char key) { + return this.replaceNode(key, null, null); + } + + @Deprecated + public V remove(Character key) { + return this.replaceNode(key, null, null); + } + + @Deprecated + public V remove(Object key) { + return this.remove((Character)key); + } + + protected final V replaceNode(char key, V value, Object cv) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + int hash = spread(Character.hashCode(key)); + Char2ObjectConcurrentHashMap.Node[] tab = this.table; + + Char2ObjectConcurrentHashMap.Node f; + int n; + int i; + while (tab != null && (n = tab.length) != 0 && (f = tabAt(tab, i = n - 1 & hash)) != null) { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + V oldVal = null; + boolean validated = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Char2ObjectConcurrentHashMap.TreeBin) { + validated = true; + Char2ObjectConcurrentHashMap.TreeBin t = (Char2ObjectConcurrentHashMap.TreeBin)f; + Char2ObjectConcurrentHashMap.TreeNode r = t.root; + Char2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(hash, key, null)) != null) { + V pv = p.val; + if (cv == null || cv == pv || pv != null && cv.equals(pv)) { + oldVal = pv; + if (value != null) { + p.val = value; + } else if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } + } else { + validated = true; + Char2ObjectConcurrentHashMap.Node e = f; + Char2ObjectConcurrentHashMap.Node pred = null; + + do { + if (e.hash == hash) { + char ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + V ev = e.val; + if (cv == null || cv == ev || ev != null && cv.equals(ev)) { + oldVal = ev; + if (value != null) { + e.val = value; + } else if (pred != null) { + pred.next = e.next; + } else { + setTabAt(tab, i, e.next); + } + } + break; + } + } + + pred = e; + } while ((e = e.next) != null); + } + } + } + + if (validated) { + if (oldVal != null) { + if (value == null) { + this.addCount(-1L, -1); + } + + return oldVal; + } + break; + } + } + } + + return null; + } + } + + public void clear() { + long delta = 0L; + int i = 0; + Char2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (tab != null && i < tab.length) { + Char2ObjectConcurrentHashMap.Node f = tabAt(tab, i); + if (f == null) { + i++; + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + i = 0; + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + for (Char2ObjectConcurrentHashMap.Node p = (Char2ObjectConcurrentHashMap.Node)(fh >= 0 + ? f + : (f instanceof Char2ObjectConcurrentHashMap.TreeBin ? ((Char2ObjectConcurrentHashMap.TreeBin)f).first : null)); + p != null; + p = p.next + ) { + delta--; + } + + setTabAt(tab, i++, null); + } + } + } + } + } + + if (delta != 0L) { + this.addCount(delta, -1); + } + } + + public Char2ObjectConcurrentHashMap.KeySetView keySet() { + Char2ObjectConcurrentHashMap.KeySetView ks = this.keySet; + return this.keySet != null ? ks : (this.keySet = this.buildKeySetView()); + } + + protected Char2ObjectConcurrentHashMap.KeySetView buildKeySetView() { + return new Char2ObjectConcurrentHashMap.KeySetView<>(this, null); + } + + public FastCollection values() { + Char2ObjectConcurrentHashMap.ValuesView vs = this.values; + return this.values != null ? vs : (this.values = this.buildValuesView()); + } + + protected Char2ObjectConcurrentHashMap.ValuesView buildValuesView() { + return new Char2ObjectConcurrentHashMap.ValuesView<>(this); + } + + public ObjectSet> char2ObjectEntrySet() { + Char2ObjectConcurrentHashMap.EntrySetView es = this.entrySet; + return this.entrySet != null ? es : (this.entrySet = this.buildEntrySetView()); + } + + @Deprecated + public ObjectSet> entrySet() { + return this.char2ObjectEntrySet(); + } + + protected Char2ObjectConcurrentHashMap.EntrySetView buildEntrySetView() { + return new Char2ObjectConcurrentHashMap.EntrySetView<>(this); + } + + @Override + public int hashCode() { + int h = 0; + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label75: { + while (true) { + if (p != null) { + next = p; + break label75; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + h += Character.hashCode(p.key) ^ p.val.hashCode(); + } + } + + return h; + } + + @Override + public String toString() { + Char2ObjectConcurrentHashMap.Node[] t = this.table; + int f = this.table == null ? 0 : t.length; + Char2ObjectConcurrentHashMap.Traverser it = new Char2ObjectConcurrentHashMap.Traverser<>(t, f, 0, f); + StringBuilder sb = new StringBuilder(); + sb.append('{'); + Char2ObjectConcurrentHashMap.Node p; + if ((p = it.advance()) != null) { + while (true) { + char k = p.key; + V v = p.val; + sb.append(k); + sb.append('='); + sb.append(v == this ? "(this Map)" : v); + if ((p = it.advance()) == null) { + break; + } + + sb.append(',').append(' '); + } + } + + return sb.append('}').toString(); + } + + @Override + public boolean equals(Object o) { + if (o != this) { + if (!(o instanceof Char2ObjectConcurrentHashMap m)) { + return false; + } + + Char2ObjectConcurrentHashMap.Node[] t = this.table; + int f = this.table == null ? 0 : t.length; + Char2ObjectConcurrentHashMap.Traverser it = new Char2ObjectConcurrentHashMap.Traverser<>(t, f, 0, f); + + Char2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + V val = p.val; + Object v = m.get(p.key); + if (v == null || v != val && !v.equals(val)) { + return false; + } + } + + for (Char2ObjectMap.Entry e : m.char2ObjectEntrySet()) { + Object mv; + Object v; + char mk; + if ((mk = e.getCharKey()) == m.EMPTY || (mv = e.getValue()) == null || (v = this.get(mk)) == null || mv != v && !mv.equals(v)) { + return false; + } + } + } + + return true; + } + + public V putIfAbsent(char key, V value) { + return this.putVal(key, value, true); + } + + public boolean remove(char key, Object value) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + return value != null && this.replaceNode(key, null, value) != null; + } + } + + public boolean replace(char key, V oldValue, V newValue) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (oldValue != null && newValue != null) { + return this.replaceNode(key, newValue, oldValue) != null; + } else { + throw new NullPointerException(); + } + } + + public V replace(char key, V value) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value == null) { + throw new NullPointerException(); + } else { + return this.replaceNode(key, value, null); + } + } + + public V getOrDefault(char key, V defaultValue) { + V v; + return (v = this.get(key)) == null ? defaultValue : v; + } + + public int forEach(Char2ObjectConcurrentHashMap.CharObjConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val); + count++; + } + } + + return count; + } + } + + public int forEach(Char2ObjectConcurrentHashMap.CharBiObjConsumer action, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, x); + count++; + } + } + + return count; + } + } + + public int forEach(Char2ObjectConcurrentHashMap.CharTriObjConsumer action, X x, Y y) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, x, y); + count++; + } + } + + return count; + } + } + + public int forEachWithByte(Char2ObjectConcurrentHashMap.CharObjByteConsumer action, byte ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithShort(Char2ObjectConcurrentHashMap.CharObjShortConsumer action, short ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithInt(Char2ObjectConcurrentHashMap.CharObjIntConsumer action, int ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithLong(Char2ObjectConcurrentHashMap.CharObjLongConsumer action, long ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithFloat(Char2ObjectConcurrentHashMap.CharObjFloatConsumer action, float ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithDouble(Char2ObjectConcurrentHashMap.CharObjDoubleConsumer action, double ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithByte(Char2ObjectConcurrentHashMap.CharBiObjByteConsumer action, byte ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithShort(Char2ObjectConcurrentHashMap.CharBiObjShortConsumer action, short ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithInt(Char2ObjectConcurrentHashMap.CharBiObjIntConsumer action, int ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithLong(Char2ObjectConcurrentHashMap.CharBiObjLongConsumer action, long ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithFloat(Char2ObjectConcurrentHashMap.CharBiObjFloatConsumer action, float ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithDouble(Char2ObjectConcurrentHashMap.CharBiObjDoubleConsumer action, double ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public void replaceAll(Char2ObjectOperator function) { + if (function == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label86: { + while (true) { + if (p != null) { + next = p; + break label86; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + V oldValue = p.val; + char key = p.key; + + V newValue; + do { + newValue = function.apply(key, oldValue); + if (newValue == null) { + throw new NullPointerException(); + } + } while (this.replaceNode(key, newValue, oldValue) == null && (oldValue = this.get(key)) != null); + } + } + } + } + + public V computeIfAbsent(char key, Char2ObjectConcurrentHashMap.CharFunction mappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (mappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Character.hashCode(key)); + V val = null; + int binCount = 0; + Char2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Char2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + Char2ObjectConcurrentHashMap.Node r = new Char2ObjectConcurrentHashMap.ReservationNode<>(this.EMPTY); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Char2ObjectConcurrentHashMap.Node node = null; + + try { + if ((val = (V)mappingFunction.apply(key)) != null) { + node = new Char2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + } finally { + setTabAt(tab, i, node); + } + } + } + + if (binCount != 0) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + boolean added = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Char2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Char2ObjectConcurrentHashMap.TreeBin t = (Char2ObjectConcurrentHashMap.TreeBin)f; + Char2ObjectConcurrentHashMap.TreeNode r = t.root; + Char2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(h, key, null)) != null) { + val = p.val; + } else if ((val = (V)mappingFunction.apply(key)) != null) { + added = true; + t.putTreeVal(h, key, val); + } + } + } else { + binCount = 1; + Char2ObjectConcurrentHashMap.Node e = f; + + while (true) { + if (e.hash == h) { + char ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = e.val; + break; + } + } + + Char2ObjectConcurrentHashMap.Node pred = e; + if ((e = e.next) == null) { + if ((val = (V)mappingFunction.apply(key)) != null) { + added = true; + pred.next = new Char2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + + if (!added) { + return val; + } + break; + } + } + } + } + + if (val != null) { + this.addCount(1L, binCount); + } + + return val; + } + } + + public V computeIfPresent(char key, Char2ObjectConcurrentHashMap.CharObjFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (remappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Character.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Char2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Char2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + break; + } + + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Char2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Char2ObjectConcurrentHashMap.TreeBin t = (Char2ObjectConcurrentHashMap.TreeBin)f; + Char2ObjectConcurrentHashMap.TreeNode r = t.root; + Char2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(h, key, null)) != null) { + val = (V)remappingFunction.apply(key, p.val); + if (val != null) { + p.val = val; + } else { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } + } else { + binCount = 1; + Char2ObjectConcurrentHashMap.Node e = f; + Char2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + char ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(key, e.val); + if (val != null) { + e.val = val; + } else { + delta = -1; + Char2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + break; + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } + } + + public V compute(char key, Char2ObjectConcurrentHashMap.CharObjFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (remappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Character.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Char2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Char2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + Char2ObjectConcurrentHashMap.Node r = new Char2ObjectConcurrentHashMap.ReservationNode<>(this.EMPTY); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Char2ObjectConcurrentHashMap.Node node = null; + + try { + if ((val = (V)remappingFunction.apply(key, null)) != null) { + delta = 1; + node = new Char2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + } finally { + setTabAt(tab, i, node); + } + } + } + + if (binCount != 0) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Char2ObjectConcurrentHashMap.TreeBin) { + binCount = 1; + Char2ObjectConcurrentHashMap.TreeBin t = (Char2ObjectConcurrentHashMap.TreeBin)f; + Char2ObjectConcurrentHashMap.TreeNode r = t.root; + Char2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null) { + p = r.findTreeNode(h, key, null); + } else { + p = null; + } + + V pv = p == null ? null : p.val; + val = (V)remappingFunction.apply(key, pv); + if (val != null) { + if (p != null) { + p.val = val; + } else { + delta = 1; + t.putTreeVal(h, key, val); + } + } else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } else { + binCount = 1; + Char2ObjectConcurrentHashMap.Node e = f; + Char2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + char ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(key, e.val); + if (val != null) { + e.val = val; + } else { + delta = -1; + Char2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + val = (V)remappingFunction.apply(key, null); + if (val != null) { + delta = 1; + pred.next = new Char2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + break; + } + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } + } + + public V merge(char key, V value, BiFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value != null && remappingFunction != null) { + int h = spread(Character.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Char2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Char2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + if (casTabAt(tab, i, null, new Char2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, value, null))) { + delta = 1; + val = value; + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Char2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Char2ObjectConcurrentHashMap.TreeBin t = (Char2ObjectConcurrentHashMap.TreeBin)f; + Char2ObjectConcurrentHashMap.TreeNode r = t.root; + Char2ObjectConcurrentHashMap.TreeNode p = r == null ? null : r.findTreeNode(h, key, null); + val = p == null ? value : remappingFunction.apply(p.val, value); + if (val != null) { + if (p != null) { + p.val = val; + } else { + delta = 1; + t.putTreeVal(h, key, val); + } + } else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } else { + binCount = 1; + Char2ObjectConcurrentHashMap.Node e = f; + Char2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + char ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(e.val, value); + if (val != null) { + e.val = val; + } else { + delta = -1; + Char2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + delta = 1; + val = value; + pred.next = new Char2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, value, null); + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + break; + } + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } else { + throw new NullPointerException(); + } + } + + public long mappingCount() { + long n = this.sumCount(); + return n < 0L ? 0L : n; + } + + public static CharSet newKeySet() { + return new Char2ObjectConcurrentHashMap.KeySetView<>(new Char2ObjectConcurrentHashMap<>(), Boolean.TRUE); + } + + public static Char2ObjectConcurrentHashMap.KeySetView newKeySet(int initialCapacity) { + return new Char2ObjectConcurrentHashMap.KeySetView<>(new Char2ObjectConcurrentHashMap<>(initialCapacity), Boolean.TRUE); + } + + public Char2ObjectConcurrentHashMap.KeySetView keySet(V mappedValue) { + if (mappedValue == null) { + throw new NullPointerException(); + } else { + return new Char2ObjectConcurrentHashMap.KeySetView<>(this, mappedValue); + } + } + + protected static final int resizeStamp(int n) { + return Integer.numberOfLeadingZeros(n) | 1 << RESIZE_STAMP_BITS - 1; + } + + protected final Char2ObjectConcurrentHashMap.Node[] initTable() { + Char2ObjectConcurrentHashMap.Node[] tab; + while (true) { + tab = this.table; + if (this.table != null && tab.length != 0) { + break; + } + + int sc = this.sizeCtl; + if (this.sizeCtl < 0) { + Thread.yield(); + } else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + tab = this.table; + if (this.table == null || tab.length == 0) { + int n = sc > 0 ? sc : 16; + Char2ObjectConcurrentHashMap.Node[] nt = new Char2ObjectConcurrentHashMap.Node[n]; + tab = nt; + this.table = nt; + sc = n - (n >>> 2); + } + break; + } finally { + this.sizeCtl = sc; + } + } + } + + return tab; + } + + protected final void addCount(long x, int check) { + boolean uncontended; + label77: { + long s; + label74: { + Char2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + if (this.counterCells == null) { + long b = this.baseCount; + if (U.compareAndSwapLong(this, BASECOUNT, this.baseCount, s = b + x)) { + break label74; + } + } + + uncontended = (boolean)1; + Char2ObjectConcurrentHashMap.CounterCell a; + int m; + if (as == null || (m = as.length - 1) < 0 || (a = as[TLRUtil.getProbe() & m]) == null) { + break label77; + } + + long v = a.value; + if (!(uncontended = U.compareAndSwapLong(a, CELLVALUE, a.value, v + x))) { + break label77; + } + + if (check <= 1) { + return; + } + + s = this.sumCount(); + } + + if (check >= 0) { + while (true) { + int sc = this.sizeCtl; + if (s < this.sizeCtl) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] tab = this.table; + int n; + if (this.table == null || (n = tab.length) >= 1073741824) { + break; + } + + uncontended = (boolean)resizeStamp(n); + if (sc < 0) { + if (sc >>> RESIZE_STAMP_SHIFT != uncontended || sc == uncontended + 1 || sc == uncontended + MAX_RESIZERS) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] nt = this.nextTable; + if (this.nextTable == null || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nt); + } + } else if (U.compareAndSwapInt(this, SIZECTL, sc, (uncontended << RESIZE_STAMP_SHIFT) + 2)) { + this.transfer(tab, null); + } + + s = this.sumCount(); + } + } + + return; + } + + this.fullAddCount(x, uncontended); + } + + protected final Char2ObjectConcurrentHashMap.Node[] helpTransfer(Char2ObjectConcurrentHashMap.Node[] tab, Char2ObjectConcurrentHashMap.Node f) { + if (tab != null && f instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + Char2ObjectConcurrentHashMap.Node[] nextTab = ((Char2ObjectConcurrentHashMap.ForwardingNode)f).nextTable; + if (((Char2ObjectConcurrentHashMap.ForwardingNode)f).nextTable != null) { + int rs = resizeStamp(tab.length); + + while (nextTab == this.nextTable && this.table == tab) { + int sc = this.sizeCtl; + if (this.sizeCtl >= 0 || sc >>> RESIZE_STAMP_SHIFT != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nextTab); + break; + } + } + + return nextTab; + } + } + + return this.table; + } + + protected final void tryPresize(int size) { + int c = size >= 536870912 ? 1073741824 : tableSizeFor(size + (size >>> 1) + 1); + + while (true) { + int sc = this.sizeCtl; + if (this.sizeCtl < 0) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] tab = this.table; + int n; + if (tab != null && (n = tab.length) != 0) { + if (c <= sc || n >= 1073741824) { + break; + } + + if (tab == this.table) { + int rs = resizeStamp(n); + if (sc < 0) { + if (sc >>> RESIZE_STAMP_SHIFT != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] nt = this.nextTable; + if (this.nextTable == null || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nt); + } + } else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) { + this.transfer(tab, null); + } + } + } else { + n = sc > c ? sc : c; + if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + if (this.table == tab) { + Char2ObjectConcurrentHashMap.Node[] ntx = new Char2ObjectConcurrentHashMap.Node[n]; + this.table = ntx; + sc = n - (n >>> 2); + } + } finally { + this.sizeCtl = sc; + } + } + } + } + } + + protected final void transfer(Char2ObjectConcurrentHashMap.Node[] tab, Char2ObjectConcurrentHashMap.Node[] nextTab) { + int n = tab.length; + int stride; + if ((stride = NCPU > 1 ? (n >>> 3) / NCPU : n) < 16) { + stride = 16; + } + + if (nextTab == null) { + try { + Char2ObjectConcurrentHashMap.Node[] nt = new Char2ObjectConcurrentHashMap.Node[n << 1]; + nextTab = nt; + } catch (Throwable var27) { + this.sizeCtl = Integer.MAX_VALUE; + return; + } + + this.nextTable = nextTab; + this.transferIndex = n; + } + + int nextn = nextTab.length; + Char2ObjectConcurrentHashMap.ForwardingNode fwd = new Char2ObjectConcurrentHashMap.ForwardingNode<>(this.EMPTY, nextTab); + boolean advance = true; + boolean finishing = false; + int i = 0; + int bound = 0; + + while (true) { + while (!advance) { + if (i >= 0 && i < n && i + n < nextn) { + Char2ObjectConcurrentHashMap.Node f; + if ((f = tabAt(tab, i)) == null) { + advance = casTabAt(tab, i, null, fwd); + } else { + int fh = f.hash; + if (f.hash == -1) { + advance = true; + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + int runBit = fh & n; + Char2ObjectConcurrentHashMap.Node lastRun = f; + + for (Char2ObjectConcurrentHashMap.Node p = f.next; p != null; p = p.next) { + int b = p.hash & n; + if (b != runBit) { + runBit = b; + lastRun = p; + } + } + + Char2ObjectConcurrentHashMap.Node ln; + Char2ObjectConcurrentHashMap.Node hn; + if (runBit == 0) { + ln = lastRun; + hn = null; + } else { + hn = lastRun; + ln = null; + } + + for (Char2ObjectConcurrentHashMap.Node px = f; px != lastRun; px = px.next) { + int ph = px.hash; + char pk = px.key; + V pv = px.val; + if ((ph & n) == 0) { + ln = new Char2ObjectConcurrentHashMap.Node<>(this.EMPTY, ph, pk, pv, ln); + } else { + hn = new Char2ObjectConcurrentHashMap.Node<>(this.EMPTY, ph, pk, pv, hn); + } + } + + setTabAt(nextTab, i, ln); + setTabAt(nextTab, i + n, hn); + setTabAt(tab, i, fwd); + advance = true; + } else if (f instanceof Char2ObjectConcurrentHashMap.TreeBin t) { + Char2ObjectConcurrentHashMap.TreeNode lo = null; + Char2ObjectConcurrentHashMap.TreeNode loTail = null; + Char2ObjectConcurrentHashMap.TreeNode hi = null; + Char2ObjectConcurrentHashMap.TreeNode hiTail = null; + int lc = 0; + int hc = 0; + + for (Char2ObjectConcurrentHashMap.Node e = t.first; e != null; e = e.next) { + int h = e.hash; + Char2ObjectConcurrentHashMap.TreeNode pxx = new Char2ObjectConcurrentHashMap.TreeNode<>( + this.EMPTY, h, e.key, e.val, null, null + ); + if ((h & n) == 0) { + if ((pxx.prev = loTail) == null) { + lo = pxx; + } else { + loTail.next = pxx; + } + + loTail = pxx; + lc++; + } else { + if ((pxx.prev = hiTail) == null) { + hi = pxx; + } else { + hiTail.next = pxx; + } + + hiTail = pxx; + hc++; + } + } + + Char2ObjectConcurrentHashMap.Node ln = (Char2ObjectConcurrentHashMap.Node)(lc <= 6 + ? this.untreeify(lo) + : (hc != 0 ? new Char2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, lo) : t)); + Char2ObjectConcurrentHashMap.Node hn = (Char2ObjectConcurrentHashMap.Node)(hc <= 6 + ? this.untreeify(hi) + : (lc != 0 ? new Char2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, hi) : t)); + setTabAt(nextTab, i, ln); + setTabAt(nextTab, i + n, hn); + setTabAt(tab, i, fwd); + advance = true; + } + } + } + } + } + } else { + if (finishing) { + this.nextTable = null; + this.table = nextTab; + this.sizeCtl = (n << 1) - (n >>> 1); + return; + } + + int sc = this.sizeCtl; + if (U.compareAndSwapInt(this, SIZECTL, this.sizeCtl, sc - 1)) { + if (sc - 2 != resizeStamp(n) << RESIZE_STAMP_SHIFT) { + return; + } + + advance = true; + finishing = true; + i = n; + } + } + } + + if (--i < bound && !finishing) { + int nextIndex = this.transferIndex; + if (this.transferIndex <= 0) { + i = -1; + advance = false; + } else { + int nextBound; + if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex, nextBound = nextIndex > stride ? nextIndex - stride : 0)) { + bound = nextBound; + i = nextIndex - 1; + advance = false; + } + } + } else { + advance = false; + } + } + } + + protected final long sumCount() { + Char2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + long sum = this.baseCount; + if (as != null) { + for (int i = 0; i < as.length; i++) { + Char2ObjectConcurrentHashMap.CounterCell a; + if ((a = as[i]) != null) { + sum += a.value; + } + } + } + + return sum; + } + + protected final void fullAddCount(long x, boolean wasUncontended) { + int h; + if ((h = TLRUtil.getProbe()) == 0) { + TLRUtil.localInit(); + h = TLRUtil.getProbe(); + wasUncontended = true; + } + + boolean collide = false; + + while (true) { + Char2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + int n; + if (this.counterCells != null && (n = as.length) > 0) { + Char2ObjectConcurrentHashMap.CounterCell a; + if ((a = as[n - 1 & h]) == null) { + if (this.cellsBusy == 0) { + Char2ObjectConcurrentHashMap.CounterCell r = new Char2ObjectConcurrentHashMap.CounterCell(x); + if (this.cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean created = false; + + try { + Char2ObjectConcurrentHashMap.CounterCell[] rs = this.counterCells; + int m; + int j; + if (this.counterCells != null && (m = rs.length) > 0 && rs[j = m - 1 & h] == null) { + rs[j] = r; + created = true; + } + } finally { + this.cellsBusy = 0; + } + + if (created) { + break; + } + continue; + } + } + + collide = false; + } else if (!wasUncontended) { + wasUncontended = true; + } else { + long v = a.value; + if (U.compareAndSwapLong(a, CELLVALUE, a.value, v + x)) { + break; + } + + if (this.counterCells != as || n >= NCPU) { + collide = false; + } else if (!collide) { + collide = true; + } else if (this.cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + try { + if (this.counterCells == as) { + Char2ObjectConcurrentHashMap.CounterCell[] rs = new Char2ObjectConcurrentHashMap.CounterCell[n << 1]; + + for (int i = 0; i < n; i++) { + rs[i] = as[i]; + } + + this.counterCells = rs; + } + } finally { + this.cellsBusy = 0; + } + + collide = false; + continue; + } + } + + h = TLRUtil.advanceProbe(h); + } else if (this.cellsBusy == 0 && this.counterCells == as && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean init = false; + + try { + if (this.counterCells == as) { + Char2ObjectConcurrentHashMap.CounterCell[] rs = new Char2ObjectConcurrentHashMap.CounterCell[2]; + rs[h & 1] = new Char2ObjectConcurrentHashMap.CounterCell(x); + this.counterCells = rs; + init = true; + } + } finally { + this.cellsBusy = 0; + } + + if (init) { + break; + } + } else { + long vx = this.baseCount; + if (U.compareAndSwapLong(this, BASECOUNT, this.baseCount, vx + x)) { + break; + } + } + } + } + + protected final void treeifyBin(Char2ObjectConcurrentHashMap.Node[] tab, int index) { + if (tab != null) { + int n; + if ((n = tab.length) < 64) { + this.tryPresize(n << 1); + } else { + Char2ObjectConcurrentHashMap.Node b; + if ((b = tabAt(tab, index)) != null && b.hash >= 0) { + synchronized (b) { + if (tabAt(tab, index) == b) { + Char2ObjectConcurrentHashMap.TreeNode hd = null; + Char2ObjectConcurrentHashMap.TreeNode tl = null; + + for (Char2ObjectConcurrentHashMap.Node e = b; e != null; e = e.next) { + Char2ObjectConcurrentHashMap.TreeNode p = new Char2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, e.hash, e.key, e.val, null, null); + if ((p.prev = tl) == null) { + hd = p; + } else { + tl.next = p; + } + + tl = p; + } + + setTabAt(tab, index, new Char2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, hd)); + } + } + } + } + } + } + + protected Char2ObjectConcurrentHashMap.Node untreeify(Char2ObjectConcurrentHashMap.Node b) { + Char2ObjectConcurrentHashMap.Node hd = null; + Char2ObjectConcurrentHashMap.Node tl = null; + + for (Char2ObjectConcurrentHashMap.Node q = b; q != null; q = q.next) { + Char2ObjectConcurrentHashMap.Node p = new Char2ObjectConcurrentHashMap.Node<>(this.EMPTY, q.hash, q.key, q.val, null); + if (tl == null) { + hd = p; + } else { + tl.next = p; + } + + tl = p; + } + + return hd; + } + + protected final int batchFor(long b) { + long n; + if (b != Long.MAX_VALUE && (n = this.sumCount()) > 1L && n >= b) { + int sp = ForkJoinPool.getCommonPoolParallelism() << 2; + long var6; + return b > 0L && (var6 = n / b) < sp ? (int)var6 : sp; + } else { + return 0; + } + } + + public void forEach(long parallelismThreshold, Char2ObjectConcurrentHashMap.CharObjConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Char2ObjectConcurrentHashMap.ForEachMappingTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEach( + long parallelismThreshold, Char2ObjectConcurrentHashMap.CharObjFunction transformer, Consumer action + ) { + if (transformer != null && action != null) { + new Char2ObjectConcurrentHashMap.ForEachTransformedMappingTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U search(long parallelismThreshold, Char2ObjectConcurrentHashMap.CharObjFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Char2ObjectConcurrentHashMap.SearchMappingsTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public U search(Char2ObjectConcurrentHashMap.CharObjFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U search(Char2ObjectConcurrentHashMap.CharBiObjFunction searchFunction, X x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithByte(Char2ObjectConcurrentHashMap.CharObjByteFunction searchFunction, byte x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithShort(Char2ObjectConcurrentHashMap.CharObjShortFunction searchFunction, short x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithInt(Char2ObjectConcurrentHashMap.CharObjIntFunction searchFunction, int x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithLong(Char2ObjectConcurrentHashMap.CharObjLongFunction searchFunction, long x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithFloat(Char2ObjectConcurrentHashMap.CharObjFloatFunction searchFunction, float x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithDouble(Char2ObjectConcurrentHashMap.CharObjDoubleFunction searchFunction, double x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U reduce( + long parallelismThreshold, + Char2ObjectConcurrentHashMap.CharObjFunction transformer, + BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Char2ObjectConcurrentHashMap.MapReduceMappingsTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer + ) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U reduce(Char2ObjectConcurrentHashMap.CharObjFunction transformer, BiFunction reducer) { + if (transformer != null && reducer != null) { + Char2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table == null) { + return null; + } else { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + U r = null; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label88: { + while (true) { + if (p != null) { + next = p; + break label88; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + return r; + } + + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + } + } else { + throw new NullPointerException(); + } + } + + public double reduceToDouble( + long parallelismThreshold, Char2ObjectConcurrentHashMap.ToDoubleCharObjFunction transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Char2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceToLong( + long parallelismThreshold, Char2ObjectConcurrentHashMap.ToLongCharObjFunction transformer, long basis, LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Char2ObjectConcurrentHashMap.MapReduceMappingsToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceToInt( + long parallelismThreshold, Char2ObjectConcurrentHashMap.ToIntCharObjFunction transformer, int basis, IntBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Char2ObjectConcurrentHashMap.MapReduceMappingsToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachKey(long parallelismThreshold, CharConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Char2ObjectConcurrentHashMap.ForEachKeyTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachKey(long parallelismThreshold, Char2ObjectConcurrentHashMap.CharFunction transformer, Consumer action) { + if (transformer != null && action != null) { + new Char2ObjectConcurrentHashMap.ForEachTransformedKeyTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchKeys(long parallelismThreshold, Char2ObjectConcurrentHashMap.CharFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Char2ObjectConcurrentHashMap.SearchKeysTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public char reduceKeys(long parallelismThreshold, Char2ObjectConcurrentHashMap.CharacterReduceTaskOperator reducer) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Char2ObjectConcurrentHashMap.ReduceKeysTask<>(this.EMPTY, null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer) + .invoke0(); + } + } + + public U reduceKeys( + long parallelismThreshold, Char2ObjectConcurrentHashMap.CharFunction transformer, BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Char2ObjectConcurrentHashMap.MapReduceKeysTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceKeysToDouble( + long parallelismThreshold, Char2ObjectConcurrentHashMap.CharToDoubleFunction transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Char2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceKeysToLong(long parallelismThreshold, Char2ObjectConcurrentHashMap.CharToLongFunction transformer, long basis, LongBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Char2ObjectConcurrentHashMap.MapReduceKeysToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceKeysToInt(long parallelismThreshold, Char2ObjectConcurrentHashMap.CharToIntFunction transformer, int basis, IntBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Char2ObjectConcurrentHashMap.MapReduceKeysToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachValue(long parallelismThreshold, Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Char2ObjectConcurrentHashMap.ForEachValueTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachValue(long parallelismThreshold, Function transformer, Consumer action) { + if (transformer != null && action != null) { + new Char2ObjectConcurrentHashMap.ForEachTransformedValueTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchValues(long parallelismThreshold, Function searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Char2ObjectConcurrentHashMap.SearchValuesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public V reduceValues(long parallelismThreshold, BiFunction reducer) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Char2ObjectConcurrentHashMap.ReduceValuesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer).invoke(); + } + } + + public U reduceValues(long parallelismThreshold, Function transformer, BiFunction reducer) { + if (transformer != null && reducer != null) { + return new Char2ObjectConcurrentHashMap.MapReduceValuesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceValuesToDouble(long parallelismThreshold, ToDoubleFunction transformer, double basis, DoubleBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Char2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceValuesToLong(long parallelismThreshold, ToLongFunction transformer, long basis, LongBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Char2ObjectConcurrentHashMap.MapReduceValuesToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceValuesToInt(long parallelismThreshold, ToIntFunction transformer, int basis, IntBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Char2ObjectConcurrentHashMap.MapReduceValuesToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachEntry(long parallelismThreshold, Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Char2ObjectConcurrentHashMap.ForEachEntryTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachEntry(long parallelismThreshold, Function, ? extends U> transformer, Consumer action) { + if (transformer != null && action != null) { + new Char2ObjectConcurrentHashMap.ForEachTransformedEntryTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchEntries(long parallelismThreshold, Function, ? extends U> searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Char2ObjectConcurrentHashMap.SearchEntriesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public Char2ObjectConcurrentHashMap.Entry reduceEntries( + long parallelismThreshold, + BiFunction, Char2ObjectConcurrentHashMap.Entry, ? extends Char2ObjectConcurrentHashMap.Entry> reducer + ) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Char2ObjectConcurrentHashMap.ReduceEntriesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer).invoke(); + } + } + + public U reduceEntries( + long parallelismThreshold, + Function, ? extends U> transformer, + BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Char2ObjectConcurrentHashMap.MapReduceEntriesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceEntriesToDouble( + long parallelismThreshold, ToDoubleFunction> transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Char2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceEntriesToLong( + long parallelismThreshold, ToLongFunction> transformer, long basis, LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Char2ObjectConcurrentHashMap.MapReduceEntriesToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceEntriesToInt( + long parallelismThreshold, ToIntFunction> transformer, int basis, IntBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Char2ObjectConcurrentHashMap.MapReduceEntriesToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public V valueMatching(Predicate predicate) { + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + Char2ObjectConcurrentHashMap.Node[] tab = this.table; + int f = this.table == null ? 0 : tab.length; + int baseLimit = f; + int baseSize = f; + boolean b = false; + + label80: + while (next != null || !b) { + b |= true; + Char2ObjectConcurrentHashMap.Node e = next; + if (next != null) { + e = next.next; + } + + label76: + while (e == null) { + if (baseIndex < baseLimit) { + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab != null && (n = tab.length) > index && index >= 0) { + if ((e = tabAt(tab, index)) != null && e.hash < 0) { + if (e instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (e instanceof Char2ObjectConcurrentHashMap.TreeBin) { + e = ((Char2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + continue; + } + + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack next1 = stack.next; + stack.next = spare; + stack = next1; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + continue label76; + } + } + } + + next = null; + continue label80; + } + + next = e; + if (predicate.test(e.val)) { + return e.val; + } + } + + return null; + } + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + U = (Unsafe)f.get(null); + Class k = Char2ObjectConcurrentHashMap.class; + SIZECTL = U.objectFieldOffset(k.getDeclaredField("sizeCtl")); + TRANSFERINDEX = U.objectFieldOffset(k.getDeclaredField("transferIndex")); + BASECOUNT = U.objectFieldOffset(k.getDeclaredField("baseCount")); + CELLSBUSY = U.objectFieldOffset(k.getDeclaredField("cellsBusy")); + Class ck = Char2ObjectConcurrentHashMap.CounterCell.class; + CELLVALUE = U.objectFieldOffset(ck.getDeclaredField("value")); + Class ak = Char2ObjectConcurrentHashMap.Node[].class; + ABASE = U.arrayBaseOffset(ak); + int scale = U.arrayIndexScale(ak); + if ((scale & scale - 1) != 0) { + throw new Error("data type scale not a power of two"); + } else { + ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); + } + } catch (Exception var5) { + throw new Error(var5); + } + } + + protected static class BaseIterator extends Char2ObjectConcurrentHashMap.Traverser { + public final Char2ObjectConcurrentHashMap map; + public Char2ObjectConcurrentHashMap.Node lastReturned; + + public BaseIterator(Char2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, Char2ObjectConcurrentHashMap map) { + super(tab, size, index, limit); + this.map = map; + this.advance(); + } + + public final boolean hasNext() { + return this.next != null; + } + + public final boolean hasMoreElements() { + return this.next != null; + } + + public final void remove() { + Char2ObjectConcurrentHashMap.Node p = this.lastReturned; + if (this.lastReturned == null) { + throw new IllegalStateException(); + } else { + this.lastReturned = null; + this.map.replaceNode(p.key, null, null); + } + } + } + + protected abstract static class BulkTask extends CountedCompleter { + public Char2ObjectConcurrentHashMap.Node[] tab; + public Char2ObjectConcurrentHashMap.Node next; + public Char2ObjectConcurrentHashMap.TableStack stack; + public Char2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + public int batch; + + protected BulkTask(Char2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Char2ObjectConcurrentHashMap.Node[] t) { + super(par); + this.batch = b; + this.index = this.baseIndex = i; + if ((this.tab = t) == null) { + this.baseSize = this.baseLimit = 0; + } else if (par == null) { + this.baseSize = this.baseLimit = t.length; + } else { + this.baseLimit = f; + this.baseSize = par.baseSize; + } + } + + protected final Char2ObjectConcurrentHashMap.Node advance() { + Char2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Char2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Char2ObjectConcurrentHashMap.TreeBin) { + e = ((Char2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Char2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Char2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Char2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + } + + @FunctionalInterface + public interface CharBiObjByteConsumer { + void accept(char var1, V var2, byte var3, X var4); + } + + @FunctionalInterface + public interface CharBiObjConsumer { + void accept(char var1, V var2, X var3); + } + + @FunctionalInterface + public interface CharBiObjDoubleConsumer { + void accept(char var1, V var2, double var3, X var5); + } + + @FunctionalInterface + public interface CharBiObjFloatConsumer { + void accept(char var1, V var2, float var3, X var4); + } + + @FunctionalInterface + public interface CharBiObjFunction { + J apply(char var1, V var2, X var3); + } + + @FunctionalInterface + public interface CharBiObjIntConsumer { + void accept(char var1, V var2, int var3, X var4); + } + + @FunctionalInterface + public interface CharBiObjLongConsumer { + void accept(char var1, V var2, long var3, X var5); + } + + @FunctionalInterface + public interface CharBiObjShortConsumer { + void accept(char var1, V var2, short var3, X var4); + } + + @FunctionalInterface + public interface CharFunction { + R apply(char var1); + } + + @FunctionalInterface + public interface CharObjByteConsumer { + void accept(char var1, V var2, byte var3); + } + + @FunctionalInterface + public interface CharObjByteFunction { + J apply(char var1, V var2, byte var3); + } + + @FunctionalInterface + public interface CharObjConsumer { + void accept(char var1, V var2); + } + + @FunctionalInterface + public interface CharObjDoubleConsumer { + void accept(char var1, V var2, double var3); + } + + @FunctionalInterface + public interface CharObjDoubleFunction { + J apply(char var1, V var2, double var3); + } + + @FunctionalInterface + public interface CharObjFloatConsumer { + void accept(char var1, V var2, float var3); + } + + @FunctionalInterface + public interface CharObjFloatFunction { + J apply(char var1, V var2, float var3); + } + + @FunctionalInterface + public interface CharObjFunction { + J apply(char var1, V var2); + } + + @FunctionalInterface + public interface CharObjIntConsumer { + void accept(char var1, V var2, int var3); + } + + @FunctionalInterface + public interface CharObjIntFunction { + J apply(char var1, V var2, int var3); + } + + @FunctionalInterface + public interface CharObjLongConsumer { + void accept(char var1, V var2, long var3); + } + + @FunctionalInterface + public interface CharObjLongFunction { + J apply(char var1, V var2, long var3); + } + + @FunctionalInterface + public interface CharObjShortConsumer { + void accept(char var1, V var2, short var3); + } + + @FunctionalInterface + public interface CharObjShortFunction { + J apply(char var1, V var2, short var3); + } + + @FunctionalInterface + public interface CharToDoubleFunction { + double applyAsDouble(char var1); + } + + @FunctionalInterface + public interface CharToIntFunction { + int applyAsInt(char var1); + } + + @FunctionalInterface + public interface CharToLongFunction { + long applyAsLong(char var1); + } + + @FunctionalInterface + public interface CharTriObjConsumer { + void accept(char var1, V var2, X var3, Y var4); + } + + @FunctionalInterface + public interface CharacterReduceTaskOperator { + char reduce(char var1, char var2, char var3); + } + + protected abstract static class CharacterReturningBulkTask2 extends Char2ObjectConcurrentHashMap.BulkTask { + public char result; + + public CharacterReturningBulkTask2(Char2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Char2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected char invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + protected abstract static class CollectionView implements ObjectCollection, Serializable { + public static final long serialVersionUID = 7249069246763182397L; + public final Char2ObjectConcurrentHashMap map; + protected static final String oomeMsg = "Required array size too large"; + + public CollectionView(Char2ObjectConcurrentHashMap map) { + this.map = map; + } + + public Char2ObjectConcurrentHashMap getMap() { + return this.map; + } + + @Override + public final void clear() { + this.map.clear(); + } + + @Override + public final int size() { + return this.map.size(); + } + + @Override + public final boolean isEmpty() { + return this.map.isEmpty(); + } + + @Override + public abstract ObjectIterator iterator(); + + @Override + public abstract boolean contains(Object var1); + + @Override + public abstract boolean remove(Object var1); + + @Override + public final Object[] toArray() { + long sz = this.map.mappingCount(); + if (sz > 2147483639L) { + throw new OutOfMemoryError("Required array size too large"); + } else { + int n = (int)sz; + Object[] r = new Object[n]; + int i = 0; + + for (E e : this) { + if (i == n) { + if (n >= 2147483639) { + throw new OutOfMemoryError("Required array size too large"); + } + + if (n >= 1073741819) { + n = 2147483639; + } else { + n += (n >>> 1) + 1; + } + + r = Arrays.copyOf(r, n); + } + + r[i++] = e; + } + + return i == n ? r : Arrays.copyOf(r, i); + } + } + + @Override + public final T[] toArray(T[] a) { + long sz = this.map.mappingCount(); + if (sz > 2147483639L) { + throw new OutOfMemoryError("Required array size too large"); + } else { + int m = (int)sz; + T[] r = (T[])(a.length >= m ? a : (Object[])Array.newInstance(a.getClass().getComponentType(), m)); + int n = r.length; + int i = 0; + + for (E e : this) { + if (i == n) { + if (n >= 2147483639) { + throw new OutOfMemoryError("Required array size too large"); + } + + if (n >= 1073741819) { + n = 2147483639; + } else { + n += (n >>> 1) + 1; + } + + r = (T[])Arrays.copyOf(r, n); + } + + r[i++] = (T)e; + } + + if (a == r && i < n) { + r[i] = null; + return r; + } else { + return (T[])(i == n ? r : Arrays.copyOf(r, i)); + } + } + } + + @Override + public final String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + Iterator it = this.iterator(); + if (it.hasNext()) { + while (true) { + Object e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (!it.hasNext()) { + break; + } + + sb.append(',').append(' '); + } + } + + return sb.append(']').toString(); + } + + @Override + public final boolean containsAll(Collection c) { + if (c != this) { + for (Object e : c) { + if (e == null || !this.contains(e)) { + return false; + } + } + } + + return true; + } + + @Override + public final boolean removeAll(Collection c) { + if (c == null) { + throw new NullPointerException(); + } else { + boolean modified = false; + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (c.contains(it.next())) { + it.remove(); + modified = true; + } + } + + return modified; + } + } + + @Override + public final boolean retainAll(Collection c) { + if (c == null) { + throw new NullPointerException(); + } else { + boolean modified = false; + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + + return modified; + } + } + } + + protected static final class CounterCell { + public volatile long value; + + public CounterCell(long x) { + this.value = x; + } + } + + protected abstract static class DoubleReturningBulkTask extends Char2ObjectConcurrentHashMap.BulkTask { + public double result; + + public DoubleReturningBulkTask(Char2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Char2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected double invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + public interface Entry extends Char2ObjectMap.Entry { + boolean isEmpty(); + + @Deprecated + @Override + Character getKey(); + + @Override + char getCharKey(); + + @Override + V getValue(); + + @Override + int hashCode(); + + @Override + String toString(); + + @Override + boolean equals(Object var1); + + @Override + V setValue(V var1); + } + + protected static final class EntryIterator extends Char2ObjectConcurrentHashMap.BaseIterator implements ObjectIterator> { + public EntryIterator(Char2ObjectConcurrentHashMap.Node[] tab, int index, int size, int limit, Char2ObjectConcurrentHashMap map) { + super(tab, index, size, limit, map); + } + + public final Char2ObjectConcurrentHashMap.Entry next() { + Char2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + char k = p.key; + V v = p.val; + this.lastReturned = p; + this.advance(); + return new Char2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), k, v, this.map); + } + } + } + + protected static final class EntrySetView + extends Char2ObjectConcurrentHashMap.CollectionView> + implements ObjectSet>, + Serializable { + public static final long serialVersionUID = 2249069246763182397L; + + public EntrySetView(Char2ObjectConcurrentHashMap map) { + super(map); + } + + @Override + public boolean contains(Object o) { + if (o instanceof Char2ObjectMap.Entry) { + Char2ObjectMap.Entry e; + char k = (e = (Char2ObjectMap.Entry)o).getCharKey(); + if (!((Char2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + Object v; + Object r; + return (r = this.map.get(k)) != null && (v = e.getValue()) != null && (v == r || v.equals(r)); + } + } + + return false; + } + + @Override + public boolean remove(Object o) { + if (o instanceof Char2ObjectMap.Entry) { + Char2ObjectMap.Entry e; + char k = (e = (Char2ObjectMap.Entry)o).getCharKey(); + if (!((Char2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + Object v; + return (v = e.getValue()) != null && this.map.remove(k, v); + } + } + + return false; + } + + @Override + public ObjectIterator> iterator() { + Char2ObjectConcurrentHashMap m = this.map; + Char2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Char2ObjectConcurrentHashMap.EntryIterator<>(t, f, 0, f, m); + } + + public boolean add(Char2ObjectMap.Entry e) { + return this.map.putVal(e.getCharKey(), e.getValue(), false) == null; + } + + @Override + public boolean addAll(Collection> c) { + boolean added = false; + + for (Char2ObjectMap.Entry e : c) { + if (this.add(e)) { + added = true; + } + } + + return added; + } + + @Override + public final int hashCode() { + int h = 0; + Char2ObjectConcurrentHashMap.Node[] t = this.map.table; + if (this.map.table != null) { + Char2ObjectConcurrentHashMap.Traverser it = new Char2ObjectConcurrentHashMap.Traverser<>(t, t.length, 0, t.length); + + Char2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + h += p.hashCode(); + } + } + + return h; + } + + @Override + public final boolean equals(Object o) { + Set c; + return o instanceof Set && ((c = (Set)o) == this || this.containsAll(c) && c.containsAll(this)); + } + + @Override + public ObjectSpliterator> spliterator() { + Char2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Char2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Char2ObjectConcurrentHashMap.EntrySpliterator<>(t, f, 0, f, n < 0L ? 0L : n, m); + } + + @Override + public void forEach(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node[] t = this.map.table; + if (this.map.table != null) { + Char2ObjectConcurrentHashMap.Traverser it = new Char2ObjectConcurrentHashMap.Traverser<>(t, t.length, 0, t.length); + + Char2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + action.accept(new Char2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + } + } + } + } + } + + protected static final class EntrySpliterator extends Char2ObjectConcurrentHashMap.Traverser implements ObjectSpliterator> { + public final Char2ObjectConcurrentHashMap map; + public long est; + + public EntrySpliterator(Char2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est, Char2ObjectConcurrentHashMap map) { + super(tab, size, index, limit); + this.map = map; + this.est = est; + } + + @Override + public ObjectSpliterator> trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Char2ObjectConcurrentHashMap.EntrySpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1, this.map); + } + + @Override + public void forEachRemaining(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(new Char2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + } + } + } + + @Override + public boolean tryAdvance(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(new Char2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4353; + } + } + + protected static final class ForEachEntryTask extends Char2ObjectConcurrentHashMap.BulkTask { + public final Consumer> action; + + public ForEachEntryTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Consumer> action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Consumer> action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Char2ObjectConcurrentHashMap.ForEachEntryTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachKeyTask extends Char2ObjectConcurrentHashMap.BulkTask { + public final CharConsumer action; + + public ForEachKeyTask(Char2ObjectConcurrentHashMap.BulkTask p, int b, int i, int f, Char2ObjectConcurrentHashMap.Node[] t, CharConsumer action) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + CharConsumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Char2ObjectConcurrentHashMap.ForEachKeyTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.key); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachMappingTask extends Char2ObjectConcurrentHashMap.BulkTask { + public final Char2ObjectConcurrentHashMap.CharObjConsumer action; + + public ForEachMappingTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.CharObjConsumer action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Char2ObjectConcurrentHashMap.CharObjConsumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Char2ObjectConcurrentHashMap.ForEachMappingTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.key, p.val); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachTransformedEntryTask extends Char2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> transformer; + public final Consumer action; + + public ForEachTransformedEntryTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Function, ? extends U> transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Function, ? extends U> transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Char2ObjectConcurrentHashMap.ForEachTransformedEntryTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedKeyTask extends Char2ObjectConcurrentHashMap.BulkTask { + public final Char2ObjectConcurrentHashMap.CharFunction transformer; + public final Consumer action; + + public ForEachTransformedKeyTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.CharFunction transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Char2ObjectConcurrentHashMap.CharFunction transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Char2ObjectConcurrentHashMap.ForEachTransformedKeyTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedMappingTask extends Char2ObjectConcurrentHashMap.BulkTask { + public final Char2ObjectConcurrentHashMap.CharObjFunction transformer; + public final Consumer action; + + public ForEachTransformedMappingTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.CharObjFunction transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Char2ObjectConcurrentHashMap.CharObjFunction transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Char2ObjectConcurrentHashMap.ForEachTransformedMappingTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action + ) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedValueTask extends Char2ObjectConcurrentHashMap.BulkTask { + public final Function transformer; + public final Consumer action; + + public ForEachTransformedValueTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Function transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Function transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Char2ObjectConcurrentHashMap.ForEachTransformedValueTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.val)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachValueTask extends Char2ObjectConcurrentHashMap.BulkTask { + public final Consumer action; + + public ForEachValueTask( + Char2ObjectConcurrentHashMap.BulkTask p, int b, int i, int f, Char2ObjectConcurrentHashMap.Node[] t, Consumer action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Char2ObjectConcurrentHashMap.ForEachValueTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.val); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForwardingNode extends Char2ObjectConcurrentHashMap.Node { + public final Char2ObjectConcurrentHashMap.Node[] nextTable; + + public ForwardingNode(char empty, Char2ObjectConcurrentHashMap.Node[] tab) { + super(empty, -1, empty, null, null); + this.nextTable = tab; + } + + @Override + protected Char2ObjectConcurrentHashMap.Node find(int h, char k) { + Char2ObjectConcurrentHashMap.Node[] tab = this.nextTable; + + Char2ObjectConcurrentHashMap.Node e; + int n; + label41: + while (k != this.EMPTY && tab != null && (n = tab.length) != 0 && (e = Char2ObjectConcurrentHashMap.tabAt(tab, n - 1 & h)) != null) { + do { + int eh = e.hash; + if (e.hash == h) { + char ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + + if (eh < 0) { + if (!(e instanceof Char2ObjectConcurrentHashMap.ForwardingNode)) { + return e.find(h, k); + } + + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + continue label41; + } + } while ((e = e.next) != null); + + return null; + } + + return null; + } + } + + protected abstract static class IntReturningBulkTask extends Char2ObjectConcurrentHashMap.BulkTask { + public int result; + + public IntReturningBulkTask(Char2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Char2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected int invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + protected static final class KeyIterator implements CharIterator { + public Char2ObjectConcurrentHashMap.Node[] tab; + public Char2ObjectConcurrentHashMap.Node next; + public Char2ObjectConcurrentHashMap.TableStack stack; + public Char2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + public final Char2ObjectConcurrentHashMap map; + public Char2ObjectConcurrentHashMap.Node lastReturned; + + public KeyIterator(Char2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, Char2ObjectConcurrentHashMap map) { + this.tab = tab; + this.baseSize = size; + this.baseIndex = this.index = index; + this.baseLimit = limit; + this.next = null; + this.map = map; + this.advance(); + } + + protected final Char2ObjectConcurrentHashMap.Node advance() { + Char2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Char2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Char2ObjectConcurrentHashMap.TreeBin) { + e = ((Char2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Char2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Char2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Char2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + + @Override + public final boolean hasNext() { + return this.next != null; + } + + public final boolean hasMoreElements() { + return this.next != null; + } + + @Override + public final void remove() { + Char2ObjectConcurrentHashMap.Node p = this.lastReturned; + if (this.lastReturned == null) { + throw new IllegalStateException(); + } else { + this.lastReturned = null; + this.map.replaceNode(p.key, null, null); + } + } + + @Override + public final char nextChar() { + Char2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + char k = p.key; + this.lastReturned = p; + this.advance(); + return k; + } + } + } + + public static class KeySetView implements CharSet { + public static final long serialVersionUID = 7249069246763182397L; + public final Char2ObjectConcurrentHashMap map; + public final V value; + + public KeySetView(Char2ObjectConcurrentHashMap map, V value) { + this.map = map; + this.value = value; + } + + public V getMappedValue() { + return this.value; + } + + @Override + public boolean contains(char o) { + return this.map.containsKey(o); + } + + @Override + public boolean remove(char o) { + return this.map.remove(o) != null; + } + + @Override + public CharIterator iterator() { + Char2ObjectConcurrentHashMap m = this.map; + Char2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Char2ObjectConcurrentHashMap.KeyIterator<>(t, f, 0, f, m); + } + + @Override + public boolean add(char e) { + V v = this.value; + if (this.value == null) { + throw new UnsupportedOperationException(); + } else { + return this.map.putVal(e, v, true) == null; + } + } + + @Override + public boolean addAll(CharCollection c) { + boolean added = false; + V v = this.value; + if (this.value == null) { + throw new UnsupportedOperationException(); + } else { + CharIterator iter = c.iterator(); + + while (iter.hasNext()) { + char e = iter.nextChar(); + if (this.map.putVal(e, v, true) == null) { + added = true; + } + } + + return added; + } + } + + @Override + public int hashCode() { + int h = 0; + CharIterator iter = this.iterator(); + + while (iter.hasNext()) { + h += Character.hashCode(iter.nextChar()); + } + + return h; + } + + @Override + public boolean equals(Object o) { + CharSet c; + return o instanceof CharSet && ((c = (CharSet)o) == this || this.containsAll(c) && c.containsAll(this)); + } + + public char getNoEntryValue() { + return this.map.EMPTY; + } + + @Override + public int size() { + return this.map.size(); + } + + @Override + public boolean isEmpty() { + return this.map.isEmpty(); + } + + @Override + public Object[] toArray() { + Object[] out = new Character[this.size()]; + CharIterator iter = this.iterator(); + + int i; + for (i = 0; i < out.length && iter.hasNext(); i++) { + out[i] = iter.nextChar(); + } + + if (out.length > i + 1) { + out[i] = this.map.EMPTY; + } + + return out; + } + + @Override + public Object[] toArray(Object[] dest) { + CharIterator iter = this.iterator(); + + int i; + for (i = 0; i < dest.length && iter.hasNext() && i <= dest.length; i++) { + dest[i] = iter.next(); + } + + if (dest.length > i + 1) { + dest[i] = this.map.EMPTY; + } + + return dest; + } + + @Override + public char[] toCharArray() { + char[] out = new char[this.size()]; + CharIterator iter = this.iterator(); + + int i; + for (i = 0; i < out.length && iter.hasNext(); i++) { + out[i] = iter.next(); + } + + if (out.length > i + 1) { + out[i] = this.map.EMPTY; + } + + return out; + } + + @Override + public char[] toArray(char[] dest) { + CharIterator iter = this.iterator(); + + int i; + for (i = 0; i < dest.length && iter.hasNext() && i <= dest.length; i++) { + dest[i] = iter.next(); + } + + if (dest.length > i + 1) { + dest[i] = this.map.EMPTY; + } + + return dest; + } + + @Override + public char[] toCharArray(char[] dest) { + return this.toArray(dest); + } + + @Override + public boolean containsAll(Collection collection) { + for (Object element : collection) { + if (!(element instanceof Long)) { + return false; + } + + char c = (Character)element; + if (!this.contains(c)) { + return false; + } + } + + return true; + } + + @Override + public boolean containsAll(CharCollection collection) { + CharIterator iter = collection.iterator(); + + while (iter.hasNext()) { + char element = iter.next(); + if (!this.contains(element)) { + return false; + } + } + + return true; + } + + public boolean containsAll(char[] array) { + int i = array.length; + + while (i-- > 0) { + if (!this.contains(array[i])) { + return false; + } + } + + return true; + } + + @Override + public boolean addAll(Collection collection) { + boolean changed = false; + + for (Character element : collection) { + char e = element; + if (this.add(e)) { + changed = true; + } + } + + return changed; + } + + public boolean addAll(char[] array) { + boolean changed = false; + int i = array.length; + + while (i-- > 0) { + if (this.add(array[i])) { + changed = true; + } + } + + return changed; + } + + @Override + public boolean retainAll(Collection collection) { + boolean modified = false; + CharIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (!collection.contains(iter.next())) { + iter.remove(); + modified = true; + } + } + + return modified; + } + + @Override + public boolean retainAll(CharCollection collection) { + if (this == collection) { + return false; + } else { + boolean modified = false; + CharIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (!collection.contains(iter.next())) { + iter.remove(); + modified = true; + } + } + + return modified; + } + } + + public boolean retainAll(char[] array) { + boolean modified = false; + CharIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (Arrays.binarySearch(array, iter.next().charValue()) < 0) { + iter.remove(); + modified = true; + } + } + + return modified; + } + + @Override + public boolean removeAll(Collection collection) { + boolean changed = false; + + for (Object element : collection) { + if (element instanceof Character) { + char c = (Character)element; + if (this.remove(c)) { + changed = true; + } + } + } + + return changed; + } + + @Override + public boolean removeAll(CharCollection collection) { + boolean changed = false; + CharIterator iter = collection.iterator(); + + while (iter.hasNext()) { + char element = iter.next(); + if (this.remove(element)) { + changed = true; + } + } + + return changed; + } + + public boolean removeAll(char[] array) { + boolean changed = false; + int i = array.length; + + while (i-- > 0) { + if (this.remove(array[i])) { + changed = true; + } + } + + return changed; + } + + @Override + public void clear() { + this.map.clear(); + } + + @Override + public CharSpliterator spliterator() { + Char2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Char2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Char2ObjectConcurrentHashMap.KeySpliterator<>(t, f, 0, f, n < 0L ? 0L : n); + } + } + + protected static final class KeySpliterator extends Char2ObjectConcurrentHashMap.Traverser implements CharSpliterator { + public long est; + + public KeySpliterator(Char2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est) { + super(tab, size, index, limit); + this.est = est; + } + + @Override + public CharSpliterator trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Char2ObjectConcurrentHashMap.KeySpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1); + } + + @Override + public boolean tryAdvance(Consumer action) { + return action instanceof CharConsumer ? this.tryAdvance((CharConsumer)action) : this.tryAdvance(value -> action.accept(value)); + } + + public void forEachRemaining(CharConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(p.key); + } + } + } + + public boolean tryAdvance(CharConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(p.key); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4353; + } + } + + protected abstract static class LongReturningBulkTask extends Char2ObjectConcurrentHashMap.BulkTask { + public long result; + + public LongReturningBulkTask(Char2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Char2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected long invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + protected static final class MapEntry implements Char2ObjectConcurrentHashMap.Entry { + public final boolean empty; + public final char key; + public V val; + public final Char2ObjectConcurrentHashMap map; + + public MapEntry(boolean empty, char key, V val, Char2ObjectConcurrentHashMap map) { + this.empty = empty; + this.key = key; + this.val = val; + this.map = map; + } + + @Override + public boolean isEmpty() { + return this.empty; + } + + @Override + public Character getKey() { + return this.key; + } + + @Override + public char getCharKey() { + return this.key; + } + + @Override + public V getValue() { + return this.val; + } + + @Override + public String toString() { + return this.empty ? "EMPTY=" + this.val : this.key + "=" + this.val; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o instanceof Char2ObjectConcurrentHashMap.Entry) { + if (this.empty != ((Char2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + return false; + } else { + return !this.empty && this.key != ((Char2ObjectConcurrentHashMap.Entry)o).getCharKey() + ? false + : this.val.equals(((Char2ObjectConcurrentHashMap.Entry)o).getValue()); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.empty ? 1 : 0; + result = 31 * result + Character.hashCode(this.key); + return 31 * result + this.val.hashCode(); + } + + @Override + public V setValue(V value) { + if (value == null) { + throw new NullPointerException(); + } else { + V v = this.val; + this.val = value; + this.map.put(this.key, value); + return v; + } + } + } + + protected static final class MapReduceEntriesTask extends Char2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> transformer; + public final BiFunction reducer; + public U result; + public Char2ObjectConcurrentHashMap.MapReduceEntriesTask rights; + public Char2ObjectConcurrentHashMap.MapReduceEntriesTask nextRight; + + public MapReduceEntriesTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.MapReduceEntriesTask nextRight, + Function, ? extends U> transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Function, ? extends U> transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.MapReduceEntriesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Char2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.MapReduceEntriesTask t = (Char2ObjectConcurrentHashMap.MapReduceEntriesTask)c; + + for (Char2ObjectConcurrentHashMap.MapReduceEntriesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceEntriesToDoubleTask extends Char2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final ToDoubleFunction> transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Char2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask rights; + public Char2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask nextRight; + + public MapReduceEntriesToDoubleTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask nextRight, + ToDoubleFunction> transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToDoubleFunction> transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask t = (Char2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask)c; + + for (Char2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceEntriesToIntTask extends Char2ObjectConcurrentHashMap.IntReturningBulkTask { + public final ToIntFunction> transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Char2ObjectConcurrentHashMap.MapReduceEntriesToIntTask rights; + public Char2ObjectConcurrentHashMap.MapReduceEntriesToIntTask nextRight; + + public MapReduceEntriesToIntTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.MapReduceEntriesToIntTask nextRight, + ToIntFunction> transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToIntFunction> transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.MapReduceEntriesToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.MapReduceEntriesToIntTask t = (Char2ObjectConcurrentHashMap.MapReduceEntriesToIntTask)c; + + for (Char2ObjectConcurrentHashMap.MapReduceEntriesToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceEntriesToLongTask extends Char2ObjectConcurrentHashMap.LongReturningBulkTask { + public final ToLongFunction> transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Char2ObjectConcurrentHashMap.MapReduceEntriesToLongTask rights; + public Char2ObjectConcurrentHashMap.MapReduceEntriesToLongTask nextRight; + + public MapReduceEntriesToLongTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.MapReduceEntriesToLongTask nextRight, + ToLongFunction> transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToLongFunction> transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.MapReduceEntriesToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.MapReduceEntriesToLongTask t = (Char2ObjectConcurrentHashMap.MapReduceEntriesToLongTask)c; + + for (Char2ObjectConcurrentHashMap.MapReduceEntriesToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysTask extends Char2ObjectConcurrentHashMap.BulkTask { + public final Char2ObjectConcurrentHashMap.CharFunction transformer; + public final BiFunction reducer; + public U result; + public Char2ObjectConcurrentHashMap.MapReduceKeysTask rights; + public Char2ObjectConcurrentHashMap.MapReduceKeysTask nextRight; + + public MapReduceKeysTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.MapReduceKeysTask nextRight, + Char2ObjectConcurrentHashMap.CharFunction transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Char2ObjectConcurrentHashMap.CharFunction transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.MapReduceKeysTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Char2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.MapReduceKeysTask t = (Char2ObjectConcurrentHashMap.MapReduceKeysTask)c; + + for (Char2ObjectConcurrentHashMap.MapReduceKeysTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceKeysToDoubleTask extends Char2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final Char2ObjectConcurrentHashMap.CharToDoubleFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Char2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask rights; + public Char2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask nextRight; + + public MapReduceKeysToDoubleTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask nextRight, + Char2ObjectConcurrentHashMap.CharToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Char2ObjectConcurrentHashMap.CharToDoubleFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask t = (Char2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask)c; + + for (Char2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysToIntTask extends Char2ObjectConcurrentHashMap.IntReturningBulkTask { + public final Char2ObjectConcurrentHashMap.CharToIntFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Char2ObjectConcurrentHashMap.MapReduceKeysToIntTask rights; + public Char2ObjectConcurrentHashMap.MapReduceKeysToIntTask nextRight; + + public MapReduceKeysToIntTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.MapReduceKeysToIntTask nextRight, + Char2ObjectConcurrentHashMap.CharToIntFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Char2ObjectConcurrentHashMap.CharToIntFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.MapReduceKeysToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.MapReduceKeysToIntTask t = (Char2ObjectConcurrentHashMap.MapReduceKeysToIntTask)c; + + for (Char2ObjectConcurrentHashMap.MapReduceKeysToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysToLongTask extends Char2ObjectConcurrentHashMap.LongReturningBulkTask { + public final Char2ObjectConcurrentHashMap.CharToLongFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Char2ObjectConcurrentHashMap.MapReduceKeysToLongTask rights; + public Char2ObjectConcurrentHashMap.MapReduceKeysToLongTask nextRight; + + public MapReduceKeysToLongTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.MapReduceKeysToLongTask nextRight, + Char2ObjectConcurrentHashMap.CharToLongFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Char2ObjectConcurrentHashMap.CharToLongFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.MapReduceKeysToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.MapReduceKeysToLongTask t = (Char2ObjectConcurrentHashMap.MapReduceKeysToLongTask)c; + + for (Char2ObjectConcurrentHashMap.MapReduceKeysToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsTask extends Char2ObjectConcurrentHashMap.BulkTask { + public final Char2ObjectConcurrentHashMap.CharObjFunction transformer; + public final BiFunction reducer; + public U result; + public Char2ObjectConcurrentHashMap.MapReduceMappingsTask rights; + public Char2ObjectConcurrentHashMap.MapReduceMappingsTask nextRight; + + public MapReduceMappingsTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.MapReduceMappingsTask nextRight, + Char2ObjectConcurrentHashMap.CharObjFunction transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Char2ObjectConcurrentHashMap.CharObjFunction transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.MapReduceMappingsTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Char2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.MapReduceMappingsTask t = (Char2ObjectConcurrentHashMap.MapReduceMappingsTask)c; + + for (Char2ObjectConcurrentHashMap.MapReduceMappingsTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceMappingsToDoubleTask extends Char2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final Char2ObjectConcurrentHashMap.ToDoubleCharObjFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Char2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask rights; + public Char2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask nextRight; + + public MapReduceMappingsToDoubleTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask nextRight, + Char2ObjectConcurrentHashMap.ToDoubleCharObjFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Char2ObjectConcurrentHashMap.ToDoubleCharObjFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask t = (Char2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask)c; + + for (Char2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsToIntTask extends Char2ObjectConcurrentHashMap.IntReturningBulkTask { + public final Char2ObjectConcurrentHashMap.ToIntCharObjFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Char2ObjectConcurrentHashMap.MapReduceMappingsToIntTask rights; + public Char2ObjectConcurrentHashMap.MapReduceMappingsToIntTask nextRight; + + public MapReduceMappingsToIntTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.MapReduceMappingsToIntTask nextRight, + Char2ObjectConcurrentHashMap.ToIntCharObjFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Char2ObjectConcurrentHashMap.ToIntCharObjFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.MapReduceMappingsToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.MapReduceMappingsToIntTask t = (Char2ObjectConcurrentHashMap.MapReduceMappingsToIntTask)c; + + for (Char2ObjectConcurrentHashMap.MapReduceMappingsToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsToLongTask extends Char2ObjectConcurrentHashMap.LongReturningBulkTask { + public final Char2ObjectConcurrentHashMap.ToLongCharObjFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Char2ObjectConcurrentHashMap.MapReduceMappingsToLongTask rights; + public Char2ObjectConcurrentHashMap.MapReduceMappingsToLongTask nextRight; + + public MapReduceMappingsToLongTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.MapReduceMappingsToLongTask nextRight, + Char2ObjectConcurrentHashMap.ToLongCharObjFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Char2ObjectConcurrentHashMap.ToLongCharObjFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.MapReduceMappingsToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.MapReduceMappingsToLongTask t = (Char2ObjectConcurrentHashMap.MapReduceMappingsToLongTask)c; + + for (Char2ObjectConcurrentHashMap.MapReduceMappingsToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesTask extends Char2ObjectConcurrentHashMap.BulkTask { + public final Function transformer; + public final BiFunction reducer; + public U result; + public Char2ObjectConcurrentHashMap.MapReduceValuesTask rights; + public Char2ObjectConcurrentHashMap.MapReduceValuesTask nextRight; + + public MapReduceValuesTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.MapReduceValuesTask nextRight, + Function transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Function transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.MapReduceValuesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Char2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.MapReduceValuesTask t = (Char2ObjectConcurrentHashMap.MapReduceValuesTask)c; + + for (Char2ObjectConcurrentHashMap.MapReduceValuesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceValuesToDoubleTask extends Char2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final ToDoubleFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Char2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask rights; + public Char2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask nextRight; + + public MapReduceValuesToDoubleTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask nextRight, + ToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToDoubleFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask t = (Char2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask)c; + + for (Char2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesToIntTask extends Char2ObjectConcurrentHashMap.IntReturningBulkTask { + public final ToIntFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Char2ObjectConcurrentHashMap.MapReduceValuesToIntTask rights; + public Char2ObjectConcurrentHashMap.MapReduceValuesToIntTask nextRight; + + public MapReduceValuesToIntTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.MapReduceValuesToIntTask nextRight, + ToIntFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToIntFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.MapReduceValuesToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.MapReduceValuesToIntTask t = (Char2ObjectConcurrentHashMap.MapReduceValuesToIntTask)c; + + for (Char2ObjectConcurrentHashMap.MapReduceValuesToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesToLongTask extends Char2ObjectConcurrentHashMap.LongReturningBulkTask { + public final ToLongFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Char2ObjectConcurrentHashMap.MapReduceValuesToLongTask rights; + public Char2ObjectConcurrentHashMap.MapReduceValuesToLongTask nextRight; + + public MapReduceValuesToLongTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.MapReduceValuesToLongTask nextRight, + ToLongFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToLongFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.MapReduceValuesToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.MapReduceValuesToLongTask t = (Char2ObjectConcurrentHashMap.MapReduceValuesToLongTask)c; + + for (Char2ObjectConcurrentHashMap.MapReduceValuesToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static class Node implements Char2ObjectConcurrentHashMap.Entry { + public final char EMPTY; + public final int hash; + public final char key; + public volatile V val; + public volatile Char2ObjectConcurrentHashMap.Node next; + + public Node(char empty, int hash, char key, V val, Char2ObjectConcurrentHashMap.Node next) { + this.EMPTY = empty; + this.hash = hash; + this.key = key; + this.val = val; + this.next = next; + } + + @Override + public final boolean isEmpty() { + return this.key == this.EMPTY; + } + + @Override + public final Character getKey() { + return this.key; + } + + @Override + public final char getCharKey() { + return this.key; + } + + @Override + public final V getValue() { + return this.val; + } + + @Override + public final int hashCode() { + return Character.hashCode(this.key) ^ this.val.hashCode(); + } + + @Override + public final String toString() { + return this.isEmpty() ? "EMPTY=" + this.val : this.key + "=" + this.val; + } + + @Override + public final V setValue(V value) { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean equals(Object o) { + boolean empty = this.isEmpty(); + if (o instanceof Char2ObjectConcurrentHashMap.Entry) { + if (empty != ((Char2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + return false; + } else { + return !empty && this.key != ((Char2ObjectConcurrentHashMap.Entry)o).getCharKey() + ? false + : this.val.equals(((Char2ObjectConcurrentHashMap.Entry)o).getValue()); + } + } else { + return false; + } + } + + protected Char2ObjectConcurrentHashMap.Node find(int h, char k) { + Char2ObjectConcurrentHashMap.Node e = this; + if (k != this.EMPTY) { + do { + if (e.hash == h) { + char ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + } while ((e = e.next) != null); + } + + return null; + } + } + + protected static final class ReduceEntriesTask extends Char2ObjectConcurrentHashMap.BulkTask> { + public final BiFunction, Char2ObjectConcurrentHashMap.Entry, ? extends Char2ObjectConcurrentHashMap.Entry> reducer; + public Char2ObjectConcurrentHashMap.Entry result; + public Char2ObjectConcurrentHashMap.ReduceEntriesTask rights; + public Char2ObjectConcurrentHashMap.ReduceEntriesTask nextRight; + + public ReduceEntriesTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.ReduceEntriesTask nextRight, + BiFunction, Char2ObjectConcurrentHashMap.Entry, ? extends Char2ObjectConcurrentHashMap.Entry> reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.reducer = reducer; + } + + public final Char2ObjectConcurrentHashMap.Entry getRawResult() { + return this.result; + } + + @Override + public final void compute() { + BiFunction, Char2ObjectConcurrentHashMap.Entry, ? extends Char2ObjectConcurrentHashMap.Entry> reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.ReduceEntriesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + Char2ObjectConcurrentHashMap.Entry r = null; + + Char2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + r = (Char2ObjectConcurrentHashMap.Entry)(r == null ? p : reducer.apply(r, p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.ReduceEntriesTask t = (Char2ObjectConcurrentHashMap.ReduceEntriesTask)c; + + for (Char2ObjectConcurrentHashMap.ReduceEntriesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + Char2ObjectConcurrentHashMap.Entry sr = s.result; + if (s.result != null) { + Char2ObjectConcurrentHashMap.Entry tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + + protected static final class ReduceKeysTask extends Char2ObjectConcurrentHashMap.CharacterReturningBulkTask2 { + public final char EMPTY; + public final Char2ObjectConcurrentHashMap.CharacterReduceTaskOperator reducer; + public Char2ObjectConcurrentHashMap.ReduceKeysTask rights; + public Char2ObjectConcurrentHashMap.ReduceKeysTask nextRight; + + public ReduceKeysTask( + char EMPTY, + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.ReduceKeysTask nextRight, + Char2ObjectConcurrentHashMap.CharacterReduceTaskOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.EMPTY = EMPTY; + this.reducer = reducer; + } + + public final Character getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Char2ObjectConcurrentHashMap.CharacterReduceTaskOperator reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.ReduceKeysTask<>( + this.EMPTY, this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + boolean found = false; + char r = this.EMPTY; + + Char2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + char u = p.key; + if (!found) { + found = true; + r = u; + } else if (!p.isEmpty()) { + found = true; + r = reducer.reduce(this.EMPTY, r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.ReduceKeysTask t = (Char2ObjectConcurrentHashMap.ReduceKeysTask)c; + + for (Char2ObjectConcurrentHashMap.ReduceKeysTask s = t.rights; s != null; s = t.rights = s.nextRight) { + char sr = s.result; + if (s.result != this.EMPTY) { + char tr = t.result; + t.result = t.result == this.EMPTY ? sr : reducer.reduce(this.EMPTY, tr, sr); + } + } + } + } + } + } + + protected static final class ReduceValuesTask extends Char2ObjectConcurrentHashMap.BulkTask { + public final BiFunction reducer; + public V result; + public Char2ObjectConcurrentHashMap.ReduceValuesTask rights; + public Char2ObjectConcurrentHashMap.ReduceValuesTask nextRight; + + public ReduceValuesTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.ReduceValuesTask nextRight, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.reducer = reducer; + } + + @Override + public final V getRawResult() { + return this.result; + } + + @Override + public final void compute() { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Char2ObjectConcurrentHashMap.ReduceValuesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + V r = null; + + Char2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + V v = p.val; + r = r == null ? v : reducer.apply(r, v); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Char2ObjectConcurrentHashMap.ReduceValuesTask t = (Char2ObjectConcurrentHashMap.ReduceValuesTask)c; + + for (Char2ObjectConcurrentHashMap.ReduceValuesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + V sr = s.result; + if (s.result != null) { + V tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + + protected static final class ReservationNode extends Char2ObjectConcurrentHashMap.Node { + public ReservationNode(char empty) { + super(empty, -3, empty, null, null); + } + + @Override + protected Char2ObjectConcurrentHashMap.Node find(int h, char k) { + return null; + } + } + + protected static final class SearchEntriesTask extends Char2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> searchFunction; + public final AtomicReference result; + + public SearchEntriesTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Function, ? extends U> searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Function, ? extends U> searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Char2ObjectConcurrentHashMap.SearchEntriesTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Char2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + + return; + } + } + } + } + } + } + + protected static final class SearchKeysTask extends Char2ObjectConcurrentHashMap.BulkTask { + public final Char2ObjectConcurrentHashMap.CharFunction searchFunction; + public final AtomicReference result; + + public SearchKeysTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.CharFunction searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Char2ObjectConcurrentHashMap.CharFunction searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Char2ObjectConcurrentHashMap.SearchKeysTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Char2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.key)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static final class SearchMappingsTask extends Char2ObjectConcurrentHashMap.BulkTask { + public final Char2ObjectConcurrentHashMap.CharObjFunction searchFunction; + public final AtomicReference result; + + public SearchMappingsTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Char2ObjectConcurrentHashMap.CharObjFunction searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Char2ObjectConcurrentHashMap.CharObjFunction searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Char2ObjectConcurrentHashMap.SearchMappingsTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result) + .fork(); + } + + while (result.get() == null) { + Char2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.key, p.val)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static final class SearchValuesTask extends Char2ObjectConcurrentHashMap.BulkTask { + public final Function searchFunction; + public final AtomicReference result; + + public SearchValuesTask( + Char2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Char2ObjectConcurrentHashMap.Node[] t, + Function searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Function searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Char2ObjectConcurrentHashMap.SearchValuesTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Char2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.val)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static class Segment extends ReentrantLock implements Serializable { + public static final long serialVersionUID = 2249069246763182397L; + public final float loadFactor; + + public Segment(float lf) { + this.loadFactor = lf; + } + } + + protected static final class TableStack { + public int length; + public int index; + public Char2ObjectConcurrentHashMap.Node[] tab; + public Char2ObjectConcurrentHashMap.TableStack next; + + public TableStack() { + } + } + + @FunctionalInterface + public interface ToCharFunction { + char applyAsChar(T var1); + } + + @FunctionalInterface + public interface ToDoubleCharObjFunction { + double applyAsDouble(char var1, V var2); + } + + @FunctionalInterface + public interface ToIntCharObjFunction { + int applyAsInt(char var1, V var2); + } + + @FunctionalInterface + public interface ToLongCharObjFunction { + long applyAsLong(char var1, V var2); + } + + protected static class Traverser { + public Char2ObjectConcurrentHashMap.Node[] tab; + public Char2ObjectConcurrentHashMap.Node next; + public Char2ObjectConcurrentHashMap.TableStack stack; + public Char2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + + public Traverser(Char2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit) { + this.tab = tab; + this.baseSize = size; + this.baseIndex = this.index = index; + this.baseLimit = limit; + this.next = null; + } + + protected final Char2ObjectConcurrentHashMap.Node advance() { + Char2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Char2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Char2ObjectConcurrentHashMap.TreeBin) { + e = ((Char2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Char2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Char2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Char2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + } + + protected static final class TreeBin extends Char2ObjectConcurrentHashMap.Node { + public Char2ObjectConcurrentHashMap.TreeNode root; + public volatile Char2ObjectConcurrentHashMap.TreeNode first; + public volatile Thread waiter; + public volatile int lockState; + public static final int WRITER = 1; + public static final int WAITER = 2; + public static final int READER = 4; + protected static final Unsafe U; + protected static final long LOCKSTATE; + + protected int tieBreakOrder(char a, char b) { + int comp = Character.compare(a, b); + return comp > 0 ? 1 : -1; + } + + public TreeBin(char empty, Char2ObjectConcurrentHashMap.TreeNode b) { + super(empty, -2, empty, null, null); + this.first = b; + Char2ObjectConcurrentHashMap.TreeNode r = null; + Char2ObjectConcurrentHashMap.TreeNode x = b; + + while (x != null) { + Char2ObjectConcurrentHashMap.TreeNode next = (Char2ObjectConcurrentHashMap.TreeNode)x.next; + x.left = x.right = null; + if (r == null) { + x.parent = null; + x.red = false; + r = x; + } else { + char k = x.key; + int h = x.hash; + Class kc = null; + Char2ObjectConcurrentHashMap.TreeNode p = r; + + int dir; + Char2ObjectConcurrentHashMap.TreeNode xp; + do { + char pk = p.key; + int ph = p.hash; + if (p.hash > h) { + dir = -1; + } else if (ph < h) { + dir = 1; + } else if ((dir = Character.compare(k, pk)) == 0) { + dir = this.tieBreakOrder(k, pk); + } + + xp = p; + } while ((p = dir <= 0 ? p.left : p.right) != null); + + x.parent = xp; + if (dir <= 0) { + xp.left = x; + } else { + xp.right = x; + } + + r = this.balanceInsertion(r, x); + } + + x = next; + } + + this.root = r; + + assert this.checkInvariants(this.root); + } + + protected final void lockRoot() { + if (!U.compareAndSwapInt(this, LOCKSTATE, 0, 1)) { + this.contendedLock(); + } + } + + protected final void unlockRoot() { + this.lockState = 0; + } + + protected final void contendedLock() { + boolean waiting = false; + + while (true) { + int s = this.lockState; + if ((this.lockState & -3) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, 1)) { + if (waiting) { + this.waiter = null; + } + + return; + } + } else if ((s & 2) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, s | 2)) { + waiting = true; + this.waiter = Thread.currentThread(); + } + } else if (waiting) { + LockSupport.park(this); + } + } + } + + @Override + protected final Char2ObjectConcurrentHashMap.Node find(int h, char k) { + if (k != this.EMPTY) { + Char2ObjectConcurrentHashMap.Node e = this.first; + + while (e != null) { + int s = this.lockState; + if ((this.lockState & 3) != 0) { + if (e.hash == h) { + char ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + + e = e.next; + } else if (U.compareAndSwapInt(this, LOCKSTATE, s, s + 4)) { + Char2ObjectConcurrentHashMap.TreeNode p; + try { + Char2ObjectConcurrentHashMap.TreeNode r = this.root; + p = this.root == null ? null : r.findTreeNode(h, k, null); + } finally { + if (U.getAndAddInt(this, LOCKSTATE, -4) == 6) { + Thread w = this.waiter; + if (this.waiter != null) { + LockSupport.unpark(w); + } + } + } + + return p; + } + } + } + + return null; + } + + protected final Char2ObjectConcurrentHashMap.TreeNode putTreeVal(int h, char k, V v) { + Class kc = null; + boolean searched = false; + Char2ObjectConcurrentHashMap.TreeNode p = this.root; + + while (true) { + if (p == null) { + this.first = this.root = new Char2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, h, k, v, null, null); + } else { + int ph = p.hash; + int dir; + if (p.hash > h) { + dir = -1; + } else if (ph < h) { + dir = 1; + } else { + char pk = p.key; + if (p.key == k || pk != this.EMPTY && k == pk) { + return p; + } + + if ((dir = Character.compare(k, pk)) == 0) { + if (!searched) { + searched = true; + Char2ObjectConcurrentHashMap.TreeNode ch = p.left; + Char2ObjectConcurrentHashMap.TreeNode q; + if (p.left != null && (q = ch.findTreeNode(h, k, kc)) != null) { + return q; + } + + ch = p.right; + if (p.right != null && (q = ch.findTreeNode(h, k, kc)) != null) { + return q; + } + } + + dir = this.tieBreakOrder(k, pk); + } + } + + Char2ObjectConcurrentHashMap.TreeNode xp = p; + if ((p = dir <= 0 ? p.left : p.right) != null) { + continue; + } + + Char2ObjectConcurrentHashMap.TreeNode f = this.first; + Char2ObjectConcurrentHashMap.TreeNode x; + this.first = x = new Char2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, h, k, v, f, xp); + if (f != null) { + f.prev = x; + } + + if (dir <= 0) { + xp.left = x; + } else { + xp.right = x; + } + + if (!xp.red) { + x.red = true; + } else { + this.lockRoot(); + + try { + this.root = this.balanceInsertion(this.root, x); + } finally { + this.unlockRoot(); + } + } + } + + assert this.checkInvariants(this.root); + + return null; + } + } + + protected final boolean removeTreeNode(Char2ObjectConcurrentHashMap.TreeNode p) { + Char2ObjectConcurrentHashMap.TreeNode next = (Char2ObjectConcurrentHashMap.TreeNode)p.next; + Char2ObjectConcurrentHashMap.TreeNode pred = p.prev; + if (pred == null) { + this.first = next; + } else { + pred.next = next; + } + + if (next != null) { + next.prev = pred; + } + + if (this.first == null) { + this.root = null; + return true; + } else { + Char2ObjectConcurrentHashMap.TreeNode r = this.root; + if (this.root != null && r.right != null) { + Char2ObjectConcurrentHashMap.TreeNode rl = r.left; + if (r.left != null && rl.left != null) { + this.lockRoot(); + + try { + Char2ObjectConcurrentHashMap.TreeNode pl = p.left; + Char2ObjectConcurrentHashMap.TreeNode pr = p.right; + Char2ObjectConcurrentHashMap.TreeNode replacement; + if (pl != null && pr != null) { + Char2ObjectConcurrentHashMap.TreeNode s = pr; + + while (true) { + Char2ObjectConcurrentHashMap.TreeNode sl = s.left; + if (s.left == null) { + boolean c = s.red; + s.red = p.red; + p.red = c; + Char2ObjectConcurrentHashMap.TreeNode sr = s.right; + Char2ObjectConcurrentHashMap.TreeNode pp = p.parent; + if (s == pr) { + p.parent = s; + s.right = p; + } else { + Char2ObjectConcurrentHashMap.TreeNode sp = s.parent; + if ((p.parent = sp) != null) { + if (s == sp.left) { + sp.left = p; + } else { + sp.right = p; + } + } + + if ((s.right = pr) != null) { + pr.parent = s; + } + } + + p.left = null; + if ((p.right = sr) != null) { + sr.parent = p; + } + + if ((s.left = pl) != null) { + pl.parent = s; + } + + if ((s.parent = pp) == null) { + r = s; + } else if (p == pp.left) { + pp.left = s; + } else { + pp.right = s; + } + + if (sr != null) { + replacement = sr; + } else { + replacement = p; + } + break; + } + + s = sl; + } + } else if (pl != null) { + replacement = pl; + } else if (pr != null) { + replacement = pr; + } else { + replacement = p; + } + + if (replacement != p) { + Char2ObjectConcurrentHashMap.TreeNode ppx = replacement.parent = p.parent; + if (ppx == null) { + r = replacement; + } else if (p == ppx.left) { + ppx.left = replacement; + } else { + ppx.right = replacement; + } + + p.left = p.right = p.parent = null; + } + + this.root = p.red ? r : this.balanceDeletion(r, replacement); + if (p == replacement) { + Char2ObjectConcurrentHashMap.TreeNode ppx = p.parent; + if (p.parent != null) { + if (p == ppx.left) { + ppx.left = null; + } else if (p == ppx.right) { + ppx.right = null; + } + + p.parent = null; + } + } + } finally { + this.unlockRoot(); + } + + assert this.checkInvariants(this.root); + + return false; + } + } + + return true; + } + } + + protected Char2ObjectConcurrentHashMap.TreeNode rotateLeft( + Char2ObjectConcurrentHashMap.TreeNode root, Char2ObjectConcurrentHashMap.TreeNode p + ) { + if (p != null) { + Char2ObjectConcurrentHashMap.TreeNode r = p.right; + if (p.right != null) { + Char2ObjectConcurrentHashMap.TreeNode rl; + if ((rl = p.right = r.left) != null) { + rl.parent = p; + } + + Char2ObjectConcurrentHashMap.TreeNode pp; + if ((pp = r.parent = p.parent) == null) { + root = r; + r.red = false; + } else if (pp.left == p) { + pp.left = r; + } else { + pp.right = r; + } + + r.left = p; + p.parent = r; + } + } + + return root; + } + + protected Char2ObjectConcurrentHashMap.TreeNode rotateRight( + Char2ObjectConcurrentHashMap.TreeNode root, Char2ObjectConcurrentHashMap.TreeNode p + ) { + if (p != null) { + Char2ObjectConcurrentHashMap.TreeNode l = p.left; + if (p.left != null) { + Char2ObjectConcurrentHashMap.TreeNode lr; + if ((lr = p.left = l.right) != null) { + lr.parent = p; + } + + Char2ObjectConcurrentHashMap.TreeNode pp; + if ((pp = l.parent = p.parent) == null) { + root = l; + l.red = false; + } else if (pp.right == p) { + pp.right = l; + } else { + pp.left = l; + } + + l.right = p; + p.parent = l; + } + } + + return root; + } + + protected Char2ObjectConcurrentHashMap.TreeNode balanceInsertion( + Char2ObjectConcurrentHashMap.TreeNode root, Char2ObjectConcurrentHashMap.TreeNode x + ) { + x.red = true; + + while (true) { + Char2ObjectConcurrentHashMap.TreeNode xp = x.parent; + if (x.parent == null) { + x.red = false; + return x; + } + + if (!xp.red) { + break; + } + + Char2ObjectConcurrentHashMap.TreeNode xpp = xp.parent; + if (xp.parent == null) { + break; + } + + Char2ObjectConcurrentHashMap.TreeNode xppl = xpp.left; + if (xp == xpp.left) { + Char2ObjectConcurrentHashMap.TreeNode xppr = xpp.right; + if (xpp.right != null && xppr.red) { + xppr.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } else { + if (x == xp.right) { + x = xp; + root = this.rotateLeft(root, xp); + xpp = (xp = xp.parent) == null ? null : xp.parent; + } + + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = this.rotateRight(root, xpp); + } + } + } + } else if (xppl != null && xppl.red) { + xppl.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } else { + if (x == xp.left) { + x = xp; + root = this.rotateRight(root, xp); + xpp = (xp = xp.parent) == null ? null : xp.parent; + } + + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = this.rotateLeft(root, xpp); + } + } + } + } + + return root; + } + + protected Char2ObjectConcurrentHashMap.TreeNode balanceDeletion( + Char2ObjectConcurrentHashMap.TreeNode root, Char2ObjectConcurrentHashMap.TreeNode x + ) { + while (x != null && x != root) { + Char2ObjectConcurrentHashMap.TreeNode xp = x.parent; + if (x.parent == null) { + x.red = false; + return x; + } + + if (x.red) { + x.red = false; + return root; + } + + Char2ObjectConcurrentHashMap.TreeNode xpl = xp.left; + if (xp.left == x) { + Char2ObjectConcurrentHashMap.TreeNode xpr = xp.right; + if (xp.right != null && xpr.red) { + xpr.red = false; + xp.red = true; + root = this.rotateLeft(root, xp); + xp = x.parent; + xpr = x.parent == null ? null : xp.right; + } + + if (xpr == null) { + x = xp; + } else { + Char2ObjectConcurrentHashMap.TreeNode sl = xpr.left; + Char2ObjectConcurrentHashMap.TreeNode sr = xpr.right; + if (sr != null && sr.red || sl != null && sl.red) { + if (sr == null || !sr.red) { + if (sl != null) { + sl.red = false; + } + + xpr.red = true; + root = this.rotateRight(root, xpr); + xp = x.parent; + xpr = x.parent == null ? null : xp.right; + } + + if (xpr != null) { + xpr.red = xp == null ? false : xp.red; + sr = xpr.right; + if (xpr.right != null) { + sr.red = false; + } + } + + if (xp != null) { + xp.red = false; + root = this.rotateLeft(root, xp); + } + + x = root; + } else { + xpr.red = true; + x = xp; + } + } + } else { + if (xpl != null && xpl.red) { + xpl.red = false; + xp.red = true; + root = this.rotateRight(root, xp); + xp = x.parent; + xpl = x.parent == null ? null : xp.left; + } + + if (xpl == null) { + x = xp; + } else { + Char2ObjectConcurrentHashMap.TreeNode sl = xpl.left; + Char2ObjectConcurrentHashMap.TreeNode sr = xpl.right; + if (sl != null && sl.red || sr != null && sr.red) { + if (sl == null || !sl.red) { + if (sr != null) { + sr.red = false; + } + + xpl.red = true; + root = this.rotateLeft(root, xpl); + xp = x.parent; + xpl = x.parent == null ? null : xp.left; + } + + if (xpl != null) { + xpl.red = xp == null ? false : xp.red; + sl = xpl.left; + if (xpl.left != null) { + sl.red = false; + } + } + + if (xp != null) { + xp.red = false; + root = this.rotateRight(root, xp); + } + + x = root; + } else { + xpl.red = true; + x = xp; + } + } + } + } + + return root; + } + + protected boolean checkInvariants(Char2ObjectConcurrentHashMap.TreeNode t) { + Char2ObjectConcurrentHashMap.TreeNode tp = t.parent; + Char2ObjectConcurrentHashMap.TreeNode tl = t.left; + Char2ObjectConcurrentHashMap.TreeNode tr = t.right; + Char2ObjectConcurrentHashMap.TreeNode tb = t.prev; + Char2ObjectConcurrentHashMap.TreeNode tn = (Char2ObjectConcurrentHashMap.TreeNode)t.next; + if (tb != null && tb.next != t) { + return false; + } else if (tn != null && tn.prev != t) { + return false; + } else if (tp != null && t != tp.left && t != tp.right) { + return false; + } else if (tl == null || tl.parent == t && tl.hash <= t.hash) { + if (tr == null || tr.parent == t && tr.hash >= t.hash) { + if (t.red && tl != null && tl.red && tr != null && tr.red) { + return false; + } else { + return tl != null && !this.checkInvariants(tl) ? false : tr == null || this.checkInvariants(tr); + } + } else { + return false; + } + } else { + return false; + } + } + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + U = (Unsafe)f.get(null); + Class k = Char2ObjectConcurrentHashMap.TreeBin.class; + LOCKSTATE = U.objectFieldOffset(k.getDeclaredField("lockState")); + } catch (Exception var2) { + throw new Error(var2); + } + } + } + + protected static final class TreeNode extends Char2ObjectConcurrentHashMap.Node { + public Char2ObjectConcurrentHashMap.TreeNode parent; + public Char2ObjectConcurrentHashMap.TreeNode left; + public Char2ObjectConcurrentHashMap.TreeNode right; + public Char2ObjectConcurrentHashMap.TreeNode prev; + public boolean red; + + public TreeNode(char empty, int hash, char key, V val, Char2ObjectConcurrentHashMap.Node next, Char2ObjectConcurrentHashMap.TreeNode parent) { + super(empty, hash, key, val, next); + this.parent = parent; + } + + @Override + protected Char2ObjectConcurrentHashMap.Node find(int h, char k) { + return this.findTreeNode(h, k, null); + } + + protected final Char2ObjectConcurrentHashMap.TreeNode findTreeNode(int h, char k, Class kc) { + if (k != this.EMPTY) { + Char2ObjectConcurrentHashMap.TreeNode p = this; + + do { + Char2ObjectConcurrentHashMap.TreeNode pl = p.left; + Char2ObjectConcurrentHashMap.TreeNode pr = p.right; + int ph = p.hash; + if (p.hash > h) { + p = pl; + } else if (ph < h) { + p = pr; + } else { + char pk = p.key; + if (p.key == k || pk != this.EMPTY && k == pk) { + return p; + } + + if (pl == null) { + p = pr; + } else if (pr == null) { + p = pl; + } else { + int dir; + if ((dir = Character.compare(k, pk)) != 0) { + p = dir < 0 ? pl : pr; + } else { + Char2ObjectConcurrentHashMap.TreeNode q; + if ((q = pr.findTreeNode(h, k, kc)) != null) { + return q; + } + + p = pl; + } + } + } + } while (p != null); + } + + return null; + } + } + + protected static final class ValueIterator extends Char2ObjectConcurrentHashMap.BaseIterator implements ObjectIterator, Enumeration { + public ValueIterator(Char2ObjectConcurrentHashMap.Node[] tab, int index, int size, int limit, Char2ObjectConcurrentHashMap map) { + super(tab, index, size, limit, map); + } + + @Override + public final V next() { + Char2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + V v = p.val; + this.lastReturned = p; + this.advance(); + return v; + } + } + + @Override + public final V nextElement() { + return this.next(); + } + } + + protected static final class ValueSpliterator extends Char2ObjectConcurrentHashMap.Traverser implements ObjectSpliterator { + public long est; + + public ValueSpliterator(Char2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est) { + super(tab, size, index, limit); + this.est = est; + } + + @Override + public ObjectSpliterator trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Char2ObjectConcurrentHashMap.ValueSpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1); + } + + @Override + public void forEachRemaining(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(p.val); + } + } + } + + @Override + public boolean tryAdvance(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(p.val); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4352; + } + } + + protected static final class ValuesView extends Char2ObjectConcurrentHashMap.CollectionView implements FastCollection, Serializable { + public static final long serialVersionUID = 2249069246763182397L; + + public ValuesView(Char2ObjectConcurrentHashMap map) { + super(map); + } + + @Override + public final boolean contains(Object o) { + return this.map.containsValue(o); + } + + @Override + public final boolean remove(Object o) { + if (o != null) { + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (o.equals(it.next())) { + it.remove(); + return true; + } + } + } + + return false; + } + + @Override + public final ObjectIterator iterator() { + Char2ObjectConcurrentHashMap m = this.map; + Char2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Char2ObjectConcurrentHashMap.ValueIterator<>(t, f, 0, f, m); + } + + @Override + public final boolean add(V e) { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public ObjectSpliterator spliterator() { + Char2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Char2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Char2ObjectConcurrentHashMap.ValueSpliterator<>(t, f, 0, f, n < 0L ? 0L : n); + } + + @Override + public void forEach(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Char2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.val); + } + } + } + } + + @Override + public void forEach( + FastCollection.FastConsumerD9 consumer, + A a, + double d1, + double d2, + double d3, + double d4, + double d5, + double d6, + double d7, + double d8, + double d9, + B b, + C c, + D d + ) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Char2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, a, d1, d2, d3, d4, d5, d6, d7, d8, d9, b, c, d); + } + } + } + } + + @Override + public void forEach( + FastCollection.FastConsumerD6 consumer, A a, double d1, double d2, double d3, double d4, double d5, double d6, B b, C c, D d + ) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Char2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, a, d1, d2, d3, d4, d5, d6, b, c, d); + } + } + } + } + + @Override + public void forEachWithFloat(FastCollection.FastConsumerF consumer, float ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Char2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + + @Override + public void forEachWithInt(FastCollection.FastConsumerI consumer, int ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Char2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + + @Override + public void forEachWithLong(FastCollection.FastConsumerL consumer, long ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Char2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Char2ObjectConcurrentHashMap.Node[] tab = tt; + Char2ObjectConcurrentHashMap.Node next = null; + Char2ObjectConcurrentHashMap.TableStack stack = null; + Char2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Char2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Char2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Char2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Char2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Char2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Char2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Char2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Char2ObjectConcurrentHashMap.TreeBin) { + p = ((Char2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Char2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Char2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + } +} diff --git a/src/com/hypixel/fastutil/chars/Char2ObjectOperator.java b/src/com/hypixel/fastutil/chars/Char2ObjectOperator.java new file mode 100644 index 0000000..ac7e8e6 --- /dev/null +++ b/src/com/hypixel/fastutil/chars/Char2ObjectOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.chars; + +@FunctionalInterface +public interface Char2ObjectOperator { + V apply(char var1, V var2); +} diff --git a/src/com/hypixel/fastutil/chars/Char2ShortOperator.java b/src/com/hypixel/fastutil/chars/Char2ShortOperator.java new file mode 100644 index 0000000..4a5fc75 --- /dev/null +++ b/src/com/hypixel/fastutil/chars/Char2ShortOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.chars; + +@FunctionalInterface +public interface Char2ShortOperator { + short apply(char var1, short var2); +} diff --git a/src/com/hypixel/fastutil/doubles/Double2ByteOperator.java b/src/com/hypixel/fastutil/doubles/Double2ByteOperator.java new file mode 100644 index 0000000..13d6014 --- /dev/null +++ b/src/com/hypixel/fastutil/doubles/Double2ByteOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.doubles; + +@FunctionalInterface +public interface Double2ByteOperator { + byte apply(double var1, byte var3); +} diff --git a/src/com/hypixel/fastutil/doubles/Double2CharOperator.java b/src/com/hypixel/fastutil/doubles/Double2CharOperator.java new file mode 100644 index 0000000..4cacf42 --- /dev/null +++ b/src/com/hypixel/fastutil/doubles/Double2CharOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.doubles; + +@FunctionalInterface +public interface Double2CharOperator { + char apply(double var1, char var3); +} diff --git a/src/com/hypixel/fastutil/doubles/Double2DoubleOperator.java b/src/com/hypixel/fastutil/doubles/Double2DoubleOperator.java new file mode 100644 index 0000000..6123e1b --- /dev/null +++ b/src/com/hypixel/fastutil/doubles/Double2DoubleOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.doubles; + +@FunctionalInterface +public interface Double2DoubleOperator { + double apply(double var1, double var3); +} diff --git a/src/com/hypixel/fastutil/doubles/Double2FloatOperator.java b/src/com/hypixel/fastutil/doubles/Double2FloatOperator.java new file mode 100644 index 0000000..7da9b3e --- /dev/null +++ b/src/com/hypixel/fastutil/doubles/Double2FloatOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.doubles; + +@FunctionalInterface +public interface Double2FloatOperator { + float apply(double var1, float var3); +} diff --git a/src/com/hypixel/fastutil/doubles/Double2IntOperator.java b/src/com/hypixel/fastutil/doubles/Double2IntOperator.java new file mode 100644 index 0000000..28aff98 --- /dev/null +++ b/src/com/hypixel/fastutil/doubles/Double2IntOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.doubles; + +@FunctionalInterface +public interface Double2IntOperator { + int apply(double var1, int var3); +} diff --git a/src/com/hypixel/fastutil/doubles/Double2LongOperator.java b/src/com/hypixel/fastutil/doubles/Double2LongOperator.java new file mode 100644 index 0000000..cb7b4dc --- /dev/null +++ b/src/com/hypixel/fastutil/doubles/Double2LongOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.doubles; + +@FunctionalInterface +public interface Double2LongOperator { + long apply(double var1, long var3); +} diff --git a/src/com/hypixel/fastutil/doubles/Double2ObjectConcurrentHashMap.java b/src/com/hypixel/fastutil/doubles/Double2ObjectConcurrentHashMap.java new file mode 100644 index 0000000..885823c --- /dev/null +++ b/src/com/hypixel/fastutil/doubles/Double2ObjectConcurrentHashMap.java @@ -0,0 +1,10282 @@ +package com.hypixel.fastutil.doubles; + +import com.hypixel.fastutil.FastCollection; +import com.hypixel.fastutil.util.SneakyThrow; +import com.hypixel.fastutil.util.TLRUtil; +import it.unimi.dsi.fastutil.doubles.Double2ObjectMap; +import it.unimi.dsi.fastutil.doubles.DoubleCollection; +import it.unimi.dsi.fastutil.doubles.DoubleIterator; +import it.unimi.dsi.fastutil.doubles.DoubleSet; +import it.unimi.dsi.fastutil.doubles.DoubleSpliterator; +import it.unimi.dsi.fastutil.objects.ObjectCollection; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import it.unimi.dsi.fastutil.objects.ObjectSpliterator; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.CountedCompleter; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.DoubleBinaryOperator; +import java.util.function.DoubleConsumer; +import java.util.function.Function; +import java.util.function.IntBinaryOperator; +import java.util.function.LongBinaryOperator; +import java.util.function.Predicate; +import java.util.function.ToIntFunction; +import java.util.function.ToLongFunction; +import sun.misc.Unsafe; + +public class Double2ObjectConcurrentHashMap { + protected static final long serialVersionUID = 7249069246763182397L; + protected static final int MAXIMUM_CAPACITY = 1073741824; + protected static final int DEFAULT_CAPACITY = 16; + protected static final int MAX_ARRAY_SIZE = 2147483639; + protected static final int DEFAULT_CONCURRENCY_LEVEL = 16; + protected static final float LOAD_FACTOR = 0.75F; + protected static final int TREEIFY_THRESHOLD = 8; + protected static final int UNTREEIFY_THRESHOLD = 6; + protected static final int MIN_TREEIFY_CAPACITY = 64; + protected static final int MIN_TRANSFER_STRIDE = 16; + protected static int RESIZE_STAMP_BITS = 16; + protected static final int MAX_RESIZERS = (1 << 32 - RESIZE_STAMP_BITS) - 1; + protected static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS; + protected static final int MOVED = -1; + protected static final int TREEBIN = -2; + protected static final int RESERVED = -3; + protected static final int HASH_BITS = Integer.MAX_VALUE; + protected static final int NCPU = Runtime.getRuntime().availableProcessors(); + protected transient volatile Double2ObjectConcurrentHashMap.Node[] table; + protected transient volatile Double2ObjectConcurrentHashMap.Node[] nextTable; + protected transient volatile long baseCount; + protected transient volatile int sizeCtl; + protected transient volatile int transferIndex; + protected transient volatile int cellsBusy; + protected transient volatile Double2ObjectConcurrentHashMap.CounterCell[] counterCells; + protected transient Double2ObjectConcurrentHashMap.KeySetView keySet; + protected transient Double2ObjectConcurrentHashMap.ValuesView values; + protected transient Double2ObjectConcurrentHashMap.EntrySetView entrySet; + protected final double EMPTY; + protected static final Unsafe U; + protected static final long SIZECTL; + protected static final long TRANSFERINDEX; + protected static final long BASECOUNT; + protected static final long CELLSBUSY; + protected static final long CELLVALUE; + protected static final long ABASE; + protected static final int ASHIFT; + + protected static final int spread(int h) { + return (h ^ h >>> 16) & 2147483647; + } + + protected static final int tableSizeFor(int c) { + int n = c - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return n < 0 ? 1 : (n >= 1073741824 ? 1073741824 : n + 1); + } + + protected static final Double2ObjectConcurrentHashMap.Node tabAt(Double2ObjectConcurrentHashMap.Node[] tab, int i) { + return (Double2ObjectConcurrentHashMap.Node)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); + } + + protected static final boolean casTabAt( + Double2ObjectConcurrentHashMap.Node[] tab, int i, Double2ObjectConcurrentHashMap.Node c, Double2ObjectConcurrentHashMap.Node v + ) { + return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v); + } + + protected static final void setTabAt(Double2ObjectConcurrentHashMap.Node[] tab, int i, Double2ObjectConcurrentHashMap.Node v) { + U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v); + } + + public Double2ObjectConcurrentHashMap() { + this.EMPTY = -1.0; + } + + public Double2ObjectConcurrentHashMap(boolean nonce, double emptyValue) { + this.EMPTY = emptyValue; + } + + public Double2ObjectConcurrentHashMap(int initialCapacity) { + this(initialCapacity, true, -1.0); + } + + public Double2ObjectConcurrentHashMap(int initialCapacity, boolean nonce, double emptyValue) { + if (initialCapacity < 0) { + throw new IllegalArgumentException(); + } else { + int cap = initialCapacity >= 536870912 ? 1073741824 : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1); + this.sizeCtl = cap; + this.EMPTY = emptyValue; + } + } + + public Double2ObjectConcurrentHashMap(Map m, double emptyValue) { + this.sizeCtl = 16; + this.EMPTY = emptyValue; + this.putAll(m); + } + + public Double2ObjectConcurrentHashMap(Double2ObjectConcurrentHashMap m) { + this.sizeCtl = 16; + this.EMPTY = m.EMPTY; + this.putAll(m); + } + + public Double2ObjectConcurrentHashMap(Double2ObjectMap m) { + this.sizeCtl = 16; + this.EMPTY = -1.0; + this.putAll(m); + } + + public Double2ObjectConcurrentHashMap(Double2ObjectMap m, double emptyValue) { + this.sizeCtl = 16; + this.EMPTY = emptyValue; + this.putAll(m); + } + + public Double2ObjectConcurrentHashMap(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, 1, -1.0); + } + + public Double2ObjectConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel, double emptyValue) { + if (loadFactor > 0.0F && initialCapacity >= 0 && concurrencyLevel > 0) { + if (initialCapacity < concurrencyLevel) { + initialCapacity = concurrencyLevel; + } + + long size = (long)(1.0 + (float)initialCapacity / loadFactor); + int cap = size >= 1073741824L ? 1073741824 : tableSizeFor((int)size); + this.sizeCtl = cap; + this.EMPTY = emptyValue; + } else { + throw new IllegalArgumentException(); + } + } + + public int size() { + long n = this.sumCount(); + return n < 0L ? 0 : (n > 2147483647L ? Integer.MAX_VALUE : (int)n); + } + + public boolean isEmpty() { + return this.sumCount() <= 0L; + } + + public V get(double key) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + int h = spread(Double.hashCode(key)); + Double2ObjectConcurrentHashMap.Node[] tab = this.table; + Double2ObjectConcurrentHashMap.Node e; + int n; + if (this.table != null && (n = tab.length) > 0 && (e = tabAt(tab, n - 1 & h)) != null) { + int eh = e.hash; + if (e.hash == h) { + double ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + return e.val; + } + } else if (eh < 0) { + Double2ObjectConcurrentHashMap.Node p; + return (p = e.find(h, key)) != null ? p.val : null; + } + + while ((e = e.next) != null) { + if (e.hash == h) { + double ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + return e.val; + } + } + } + } + + return null; + } + } + + public boolean containsKey(double key) { + return this.get(key) != null; + } + + public boolean containsValue(Object value) { + if (value == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label85: + while (true) { + if (p != null) { + next = p; + break; + } + + if (baseIndex < baseLimit) { + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab != null && (n = tab.length) > index && index >= 0) { + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + continue; + } + + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + continue label85; + } + } + } + + next = null; + break; + } + + if (p == null) { + break; + } + + V v = p.val; + if (p.val == value || v != null && value.equals(v)) { + return true; + } + } + } + + return false; + } + } + + public V put(double key, V value) { + return this.putVal(key, value, false); + } + + protected final V putVal(double key, V value, boolean onlyIfAbsent) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value == null) { + throw new NullPointerException(); + } else { + int hash = spread(Double.hashCode(key)); + int binCount = 0; + Double2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Double2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & hash)) == null) { + if (casTabAt(tab, i, null, new Double2ObjectConcurrentHashMap.Node<>(this.EMPTY, hash, key, value, null))) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + V oldVal = null; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Double2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Double2ObjectConcurrentHashMap.Node p; + if ((p = ((Double2ObjectConcurrentHashMap.TreeBin)f).putTreeVal(hash, key, value)) != null) { + oldVal = p.val; + if (!onlyIfAbsent) { + p.val = value; + } + } + } + } else { + binCount = 1; + Double2ObjectConcurrentHashMap.Node e = f; + + while (true) { + if (e.hash == hash) { + double ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + oldVal = e.val; + if (!onlyIfAbsent) { + e.val = value; + } + break; + } + } + + Double2ObjectConcurrentHashMap.Node pred = e; + if ((e = e.next) == null) { + pred.next = new Double2ObjectConcurrentHashMap.Node<>(this.EMPTY, hash, key, value, null); + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + + if (oldVal != null) { + return oldVal; + } + break; + } + } + } + } + + this.addCount(1L, binCount); + return null; + } + } + + public void putAll(Map m) { + this.tryPresize(m.size()); + + for (Map.Entry e : m.entrySet()) { + this.putVal(e.getKey(), (V)e.getValue(), false); + } + } + + public void putAll(Double2ObjectConcurrentHashMap m) { + this.tryPresize(m.size()); + + for (Double2ObjectMap.Entry e : m.double2ObjectEntrySet()) { + this.putVal(e.getDoubleKey(), (V)e.getValue(), false); + } + } + + public void putAll(Double2ObjectMap m) { + this.tryPresize(m.size()); + + for (Double2ObjectMap.Entry next : m.double2ObjectEntrySet()) { + this.putVal(next.getDoubleKey(), (V)next.getValue(), false); + } + } + + public V remove(double key) { + return this.replaceNode(key, null, null); + } + + @Deprecated + public V remove(Double key) { + return this.replaceNode(key, null, null); + } + + @Deprecated + public V remove(Object key) { + return this.remove((Double)key); + } + + protected final V replaceNode(double key, V value, Object cv) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + int hash = spread(Double.hashCode(key)); + Double2ObjectConcurrentHashMap.Node[] tab = this.table; + + Double2ObjectConcurrentHashMap.Node f; + int n; + int i; + while (tab != null && (n = tab.length) != 0 && (f = tabAt(tab, i = n - 1 & hash)) != null) { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + V oldVal = null; + boolean validated = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Double2ObjectConcurrentHashMap.TreeBin) { + validated = true; + Double2ObjectConcurrentHashMap.TreeBin t = (Double2ObjectConcurrentHashMap.TreeBin)f; + Double2ObjectConcurrentHashMap.TreeNode r = t.root; + Double2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(hash, key, null)) != null) { + V pv = p.val; + if (cv == null || cv == pv || pv != null && cv.equals(pv)) { + oldVal = pv; + if (value != null) { + p.val = value; + } else if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } + } else { + validated = true; + Double2ObjectConcurrentHashMap.Node e = f; + Double2ObjectConcurrentHashMap.Node pred = null; + + do { + if (e.hash == hash) { + double ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + V ev = e.val; + if (cv == null || cv == ev || ev != null && cv.equals(ev)) { + oldVal = ev; + if (value != null) { + e.val = value; + } else if (pred != null) { + pred.next = e.next; + } else { + setTabAt(tab, i, e.next); + } + } + break; + } + } + + pred = e; + } while ((e = e.next) != null); + } + } + } + + if (validated) { + if (oldVal != null) { + if (value == null) { + this.addCount(-1L, -1); + } + + return oldVal; + } + break; + } + } + } + + return null; + } + } + + public void clear() { + long delta = 0L; + int i = 0; + Double2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (tab != null && i < tab.length) { + Double2ObjectConcurrentHashMap.Node f = tabAt(tab, i); + if (f == null) { + i++; + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + i = 0; + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + for (Double2ObjectConcurrentHashMap.Node p = (Double2ObjectConcurrentHashMap.Node)(fh >= 0 + ? f + : (f instanceof Double2ObjectConcurrentHashMap.TreeBin ? ((Double2ObjectConcurrentHashMap.TreeBin)f).first : null)); + p != null; + p = p.next + ) { + delta--; + } + + setTabAt(tab, i++, null); + } + } + } + } + } + + if (delta != 0L) { + this.addCount(delta, -1); + } + } + + public Double2ObjectConcurrentHashMap.KeySetView keySet() { + Double2ObjectConcurrentHashMap.KeySetView ks = this.keySet; + return this.keySet != null ? ks : (this.keySet = this.buildKeySetView()); + } + + protected Double2ObjectConcurrentHashMap.KeySetView buildKeySetView() { + return new Double2ObjectConcurrentHashMap.KeySetView<>(this, null); + } + + public FastCollection values() { + Double2ObjectConcurrentHashMap.ValuesView vs = this.values; + return this.values != null ? vs : (this.values = this.buildValuesView()); + } + + protected Double2ObjectConcurrentHashMap.ValuesView buildValuesView() { + return new Double2ObjectConcurrentHashMap.ValuesView<>(this); + } + + public ObjectSet> double2ObjectEntrySet() { + Double2ObjectConcurrentHashMap.EntrySetView es = this.entrySet; + return this.entrySet != null ? es : (this.entrySet = this.buildEntrySetView()); + } + + @Deprecated + public ObjectSet> entrySet() { + return this.double2ObjectEntrySet(); + } + + protected Double2ObjectConcurrentHashMap.EntrySetView buildEntrySetView() { + return new Double2ObjectConcurrentHashMap.EntrySetView<>(this); + } + + @Override + public int hashCode() { + int h = 0; + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label75: { + while (true) { + if (p != null) { + next = p; + break label75; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + h += Double.hashCode(p.key) ^ p.val.hashCode(); + } + } + + return h; + } + + @Override + public String toString() { + Double2ObjectConcurrentHashMap.Node[] t = this.table; + int f = this.table == null ? 0 : t.length; + Double2ObjectConcurrentHashMap.Traverser it = new Double2ObjectConcurrentHashMap.Traverser<>(t, f, 0, f); + StringBuilder sb = new StringBuilder(); + sb.append('{'); + Double2ObjectConcurrentHashMap.Node p; + if ((p = it.advance()) != null) { + while (true) { + double k = p.key; + V v = p.val; + sb.append(k); + sb.append('='); + sb.append(v == this ? "(this Map)" : v); + if ((p = it.advance()) == null) { + break; + } + + sb.append(',').append(' '); + } + } + + return sb.append('}').toString(); + } + + @Override + public boolean equals(Object o) { + if (o != this) { + if (!(o instanceof Double2ObjectConcurrentHashMap m)) { + return false; + } + + Double2ObjectConcurrentHashMap.Node[] t = this.table; + int f = this.table == null ? 0 : t.length; + Double2ObjectConcurrentHashMap.Traverser it = new Double2ObjectConcurrentHashMap.Traverser<>(t, f, 0, f); + + Double2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + V val = p.val; + Object v = m.get(p.key); + if (v == null || v != val && !v.equals(val)) { + return false; + } + } + + for (Double2ObjectMap.Entry e : m.double2ObjectEntrySet()) { + Object mv; + Object v; + double mk; + if ((mk = e.getDoubleKey()) == m.EMPTY || (mv = e.getValue()) == null || (v = this.get(mk)) == null || mv != v && !mv.equals(v)) { + return false; + } + } + } + + return true; + } + + public V putIfAbsent(double key, V value) { + return this.putVal(key, value, true); + } + + public boolean remove(double key, Object value) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + return value != null && this.replaceNode(key, null, value) != null; + } + } + + public boolean replace(double key, V oldValue, V newValue) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (oldValue != null && newValue != null) { + return this.replaceNode(key, newValue, oldValue) != null; + } else { + throw new NullPointerException(); + } + } + + public V replace(double key, V value) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value == null) { + throw new NullPointerException(); + } else { + return this.replaceNode(key, value, null); + } + } + + public V getOrDefault(double key, V defaultValue) { + V v; + return (v = this.get(key)) == null ? defaultValue : v; + } + + public int forEach(Double2ObjectConcurrentHashMap.DoubleObjConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val); + count++; + } + } + + return count; + } + } + + public int forEach(Double2ObjectConcurrentHashMap.DoubleBiObjConsumer action, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, x); + count++; + } + } + + return count; + } + } + + public int forEach(Double2ObjectConcurrentHashMap.DoubleTriObjConsumer action, X x, Y y) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, x, y); + count++; + } + } + + return count; + } + } + + public int forEachWithByte(Double2ObjectConcurrentHashMap.DoubleObjByteConsumer action, byte ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithShort(Double2ObjectConcurrentHashMap.DoubleObjShortConsumer action, short ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithInt(Double2ObjectConcurrentHashMap.DoubleObjIntConsumer action, int ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithLong(Double2ObjectConcurrentHashMap.DoubleObjLongConsumer action, long ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithFloat(Double2ObjectConcurrentHashMap.DoubleObjFloatConsumer action, float ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithDouble(Double2ObjectConcurrentHashMap.DoubleObjDoubleConsumer action, double ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithByte(Double2ObjectConcurrentHashMap.DoubleBiObjByteConsumer action, byte ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithShort(Double2ObjectConcurrentHashMap.DoubleBiObjShortConsumer action, short ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithInt(Double2ObjectConcurrentHashMap.DoubleBiObjIntConsumer action, int ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithLong(Double2ObjectConcurrentHashMap.DoubleBiObjLongConsumer action, long ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithFloat(Double2ObjectConcurrentHashMap.DoubleBiObjFloatConsumer action, float ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithDouble(Double2ObjectConcurrentHashMap.DoubleBiObjDoubleConsumer action, double ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public void replaceAll(Double2ObjectOperator function) { + if (function == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label86: { + while (true) { + if (p != null) { + next = p; + break label86; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + V oldValue = p.val; + double key = p.key; + + V newValue; + do { + newValue = function.apply(key, oldValue); + if (newValue == null) { + throw new NullPointerException(); + } + } while (this.replaceNode(key, newValue, oldValue) == null && (oldValue = this.get(key)) != null); + } + } + } + } + + public V computeIfAbsent(double key, Double2ObjectConcurrentHashMap.DoubleFunction mappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (mappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Double.hashCode(key)); + V val = null; + int binCount = 0; + Double2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Double2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + Double2ObjectConcurrentHashMap.Node r = new Double2ObjectConcurrentHashMap.ReservationNode<>(this.EMPTY); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Double2ObjectConcurrentHashMap.Node node = null; + + try { + if ((val = (V)mappingFunction.apply(key)) != null) { + node = new Double2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + } finally { + setTabAt(tab, i, node); + } + } + } + + if (binCount != 0) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + boolean added = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Double2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Double2ObjectConcurrentHashMap.TreeBin t = (Double2ObjectConcurrentHashMap.TreeBin)f; + Double2ObjectConcurrentHashMap.TreeNode r = t.root; + Double2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(h, key, null)) != null) { + val = p.val; + } else if ((val = (V)mappingFunction.apply(key)) != null) { + added = true; + t.putTreeVal(h, key, val); + } + } + } else { + binCount = 1; + Double2ObjectConcurrentHashMap.Node e = f; + + while (true) { + if (e.hash == h) { + double ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = e.val; + break; + } + } + + Double2ObjectConcurrentHashMap.Node pred = e; + if ((e = e.next) == null) { + if ((val = (V)mappingFunction.apply(key)) != null) { + added = true; + pred.next = new Double2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + + if (!added) { + return val; + } + break; + } + } + } + } + + if (val != null) { + this.addCount(1L, binCount); + } + + return val; + } + } + + public V computeIfPresent(double key, Double2ObjectConcurrentHashMap.DoubleObjFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (remappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Double.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Double2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Double2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + break; + } + + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Double2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Double2ObjectConcurrentHashMap.TreeBin t = (Double2ObjectConcurrentHashMap.TreeBin)f; + Double2ObjectConcurrentHashMap.TreeNode r = t.root; + Double2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(h, key, null)) != null) { + val = (V)remappingFunction.apply(key, p.val); + if (val != null) { + p.val = val; + } else { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } + } else { + binCount = 1; + Double2ObjectConcurrentHashMap.Node e = f; + Double2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + double ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(key, e.val); + if (val != null) { + e.val = val; + } else { + delta = -1; + Double2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + break; + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } + } + + public V compute(double key, Double2ObjectConcurrentHashMap.DoubleObjFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (remappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Double.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Double2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Double2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + Double2ObjectConcurrentHashMap.Node r = new Double2ObjectConcurrentHashMap.ReservationNode<>(this.EMPTY); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Double2ObjectConcurrentHashMap.Node node = null; + + try { + if ((val = (V)remappingFunction.apply(key, null)) != null) { + delta = 1; + node = new Double2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + } finally { + setTabAt(tab, i, node); + } + } + } + + if (binCount != 0) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Double2ObjectConcurrentHashMap.TreeBin) { + binCount = 1; + Double2ObjectConcurrentHashMap.TreeBin t = (Double2ObjectConcurrentHashMap.TreeBin)f; + Double2ObjectConcurrentHashMap.TreeNode r = t.root; + Double2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null) { + p = r.findTreeNode(h, key, null); + } else { + p = null; + } + + V pv = p == null ? null : p.val; + val = (V)remappingFunction.apply(key, pv); + if (val != null) { + if (p != null) { + p.val = val; + } else { + delta = 1; + t.putTreeVal(h, key, val); + } + } else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } else { + binCount = 1; + Double2ObjectConcurrentHashMap.Node e = f; + Double2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + double ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(key, e.val); + if (val != null) { + e.val = val; + } else { + delta = -1; + Double2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + val = (V)remappingFunction.apply(key, null); + if (val != null) { + delta = 1; + pred.next = new Double2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + break; + } + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } + } + + public V merge(double key, V value, BiFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value != null && remappingFunction != null) { + int h = spread(Double.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Double2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Double2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + if (casTabAt(tab, i, null, new Double2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, value, null))) { + delta = 1; + val = value; + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Double2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Double2ObjectConcurrentHashMap.TreeBin t = (Double2ObjectConcurrentHashMap.TreeBin)f; + Double2ObjectConcurrentHashMap.TreeNode r = t.root; + Double2ObjectConcurrentHashMap.TreeNode p = r == null ? null : r.findTreeNode(h, key, null); + val = p == null ? value : remappingFunction.apply(p.val, value); + if (val != null) { + if (p != null) { + p.val = val; + } else { + delta = 1; + t.putTreeVal(h, key, val); + } + } else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } else { + binCount = 1; + Double2ObjectConcurrentHashMap.Node e = f; + Double2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + double ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(e.val, value); + if (val != null) { + e.val = val; + } else { + delta = -1; + Double2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + delta = 1; + val = value; + pred.next = new Double2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, value, null); + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + break; + } + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } else { + throw new NullPointerException(); + } + } + + public long mappingCount() { + long n = this.sumCount(); + return n < 0L ? 0L : n; + } + + public static DoubleSet newKeySet() { + return new Double2ObjectConcurrentHashMap.KeySetView<>(new Double2ObjectConcurrentHashMap<>(), Boolean.TRUE); + } + + public static Double2ObjectConcurrentHashMap.KeySetView newKeySet(int initialCapacity) { + return new Double2ObjectConcurrentHashMap.KeySetView<>(new Double2ObjectConcurrentHashMap<>(initialCapacity), Boolean.TRUE); + } + + public Double2ObjectConcurrentHashMap.KeySetView keySet(V mappedValue) { + if (mappedValue == null) { + throw new NullPointerException(); + } else { + return new Double2ObjectConcurrentHashMap.KeySetView<>(this, mappedValue); + } + } + + protected static final int resizeStamp(int n) { + return Integer.numberOfLeadingZeros(n) | 1 << RESIZE_STAMP_BITS - 1; + } + + protected final Double2ObjectConcurrentHashMap.Node[] initTable() { + Double2ObjectConcurrentHashMap.Node[] tab; + while (true) { + tab = this.table; + if (this.table != null && tab.length != 0) { + break; + } + + int sc = this.sizeCtl; + if (this.sizeCtl < 0) { + Thread.yield(); + } else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + tab = this.table; + if (this.table == null || tab.length == 0) { + int n = sc > 0 ? sc : 16; + Double2ObjectConcurrentHashMap.Node[] nt = new Double2ObjectConcurrentHashMap.Node[n]; + tab = nt; + this.table = nt; + sc = n - (n >>> 2); + } + break; + } finally { + this.sizeCtl = sc; + } + } + } + + return tab; + } + + protected final void addCount(long x, int check) { + boolean uncontended; + label77: { + long s; + label74: { + Double2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + if (this.counterCells == null) { + long b = this.baseCount; + if (U.compareAndSwapLong(this, BASECOUNT, this.baseCount, s = b + x)) { + break label74; + } + } + + uncontended = (boolean)1; + Double2ObjectConcurrentHashMap.CounterCell a; + int m; + if (as == null || (m = as.length - 1) < 0 || (a = as[TLRUtil.getProbe() & m]) == null) { + break label77; + } + + long v = a.value; + if (!(uncontended = U.compareAndSwapLong(a, CELLVALUE, a.value, v + x))) { + break label77; + } + + if (check <= 1) { + return; + } + + s = this.sumCount(); + } + + if (check >= 0) { + while (true) { + int sc = this.sizeCtl; + if (s < this.sizeCtl) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] tab = this.table; + int n; + if (this.table == null || (n = tab.length) >= 1073741824) { + break; + } + + uncontended = (boolean)resizeStamp(n); + if (sc < 0) { + if (sc >>> RESIZE_STAMP_SHIFT != uncontended || sc == uncontended + 1 || sc == uncontended + MAX_RESIZERS) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] nt = this.nextTable; + if (this.nextTable == null || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nt); + } + } else if (U.compareAndSwapInt(this, SIZECTL, sc, (uncontended << RESIZE_STAMP_SHIFT) + 2)) { + this.transfer(tab, null); + } + + s = this.sumCount(); + } + } + + return; + } + + this.fullAddCount(x, uncontended); + } + + protected final Double2ObjectConcurrentHashMap.Node[] helpTransfer(Double2ObjectConcurrentHashMap.Node[] tab, Double2ObjectConcurrentHashMap.Node f) { + if (tab != null && f instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + Double2ObjectConcurrentHashMap.Node[] nextTab = ((Double2ObjectConcurrentHashMap.ForwardingNode)f).nextTable; + if (((Double2ObjectConcurrentHashMap.ForwardingNode)f).nextTable != null) { + int rs = resizeStamp(tab.length); + + while (nextTab == this.nextTable && this.table == tab) { + int sc = this.sizeCtl; + if (this.sizeCtl >= 0 || sc >>> RESIZE_STAMP_SHIFT != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nextTab); + break; + } + } + + return nextTab; + } + } + + return this.table; + } + + protected final void tryPresize(int size) { + int c = size >= 536870912 ? 1073741824 : tableSizeFor(size + (size >>> 1) + 1); + + while (true) { + int sc = this.sizeCtl; + if (this.sizeCtl < 0) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] tab = this.table; + int n; + if (tab != null && (n = tab.length) != 0) { + if (c <= sc || n >= 1073741824) { + break; + } + + if (tab == this.table) { + int rs = resizeStamp(n); + if (sc < 0) { + if (sc >>> RESIZE_STAMP_SHIFT != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] nt = this.nextTable; + if (this.nextTable == null || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nt); + } + } else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) { + this.transfer(tab, null); + } + } + } else { + n = sc > c ? sc : c; + if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + if (this.table == tab) { + Double2ObjectConcurrentHashMap.Node[] ntx = new Double2ObjectConcurrentHashMap.Node[n]; + this.table = ntx; + sc = n - (n >>> 2); + } + } finally { + this.sizeCtl = sc; + } + } + } + } + } + + protected final void transfer(Double2ObjectConcurrentHashMap.Node[] tab, Double2ObjectConcurrentHashMap.Node[] nextTab) { + int n = tab.length; + int stride; + if ((stride = NCPU > 1 ? (n >>> 3) / NCPU : n) < 16) { + stride = 16; + } + + if (nextTab == null) { + try { + Double2ObjectConcurrentHashMap.Node[] nt = new Double2ObjectConcurrentHashMap.Node[n << 1]; + nextTab = nt; + } catch (Throwable var27) { + this.sizeCtl = Integer.MAX_VALUE; + return; + } + + this.nextTable = nextTab; + this.transferIndex = n; + } + + int nextn = nextTab.length; + Double2ObjectConcurrentHashMap.ForwardingNode fwd = new Double2ObjectConcurrentHashMap.ForwardingNode<>(this.EMPTY, nextTab); + boolean advance = true; + boolean finishing = false; + int i = 0; + int bound = 0; + + while (true) { + while (!advance) { + if (i >= 0 && i < n && i + n < nextn) { + Double2ObjectConcurrentHashMap.Node f; + if ((f = tabAt(tab, i)) == null) { + advance = casTabAt(tab, i, null, fwd); + } else { + int fh = f.hash; + if (f.hash == -1) { + advance = true; + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + int runBit = fh & n; + Double2ObjectConcurrentHashMap.Node lastRun = f; + + for (Double2ObjectConcurrentHashMap.Node p = f.next; p != null; p = p.next) { + int b = p.hash & n; + if (b != runBit) { + runBit = b; + lastRun = p; + } + } + + Double2ObjectConcurrentHashMap.Node ln; + Double2ObjectConcurrentHashMap.Node hn; + if (runBit == 0) { + ln = lastRun; + hn = null; + } else { + hn = lastRun; + ln = null; + } + + for (Double2ObjectConcurrentHashMap.Node px = f; px != lastRun; px = px.next) { + int ph = px.hash; + double pk = px.key; + V pv = px.val; + if ((ph & n) == 0) { + ln = new Double2ObjectConcurrentHashMap.Node<>(this.EMPTY, ph, pk, pv, ln); + } else { + hn = new Double2ObjectConcurrentHashMap.Node<>(this.EMPTY, ph, pk, pv, hn); + } + } + + setTabAt(nextTab, i, ln); + setTabAt(nextTab, i + n, hn); + setTabAt(tab, i, fwd); + advance = true; + } else if (f instanceof Double2ObjectConcurrentHashMap.TreeBin t) { + Double2ObjectConcurrentHashMap.TreeNode lo = null; + Double2ObjectConcurrentHashMap.TreeNode loTail = null; + Double2ObjectConcurrentHashMap.TreeNode hi = null; + Double2ObjectConcurrentHashMap.TreeNode hiTail = null; + int lc = 0; + int hc = 0; + + for (Double2ObjectConcurrentHashMap.Node e = t.first; e != null; e = e.next) { + int h = e.hash; + Double2ObjectConcurrentHashMap.TreeNode pxx = new Double2ObjectConcurrentHashMap.TreeNode<>( + this.EMPTY, h, e.key, e.val, null, null + ); + if ((h & n) == 0) { + if ((pxx.prev = loTail) == null) { + lo = pxx; + } else { + loTail.next = pxx; + } + + loTail = pxx; + lc++; + } else { + if ((pxx.prev = hiTail) == null) { + hi = pxx; + } else { + hiTail.next = pxx; + } + + hiTail = pxx; + hc++; + } + } + + Double2ObjectConcurrentHashMap.Node ln = (Double2ObjectConcurrentHashMap.Node)(lc <= 6 + ? this.untreeify(lo) + : (hc != 0 ? new Double2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, lo) : t)); + Double2ObjectConcurrentHashMap.Node hn = (Double2ObjectConcurrentHashMap.Node)(hc <= 6 + ? this.untreeify(hi) + : (lc != 0 ? new Double2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, hi) : t)); + setTabAt(nextTab, i, ln); + setTabAt(nextTab, i + n, hn); + setTabAt(tab, i, fwd); + advance = true; + } + } + } + } + } + } else { + if (finishing) { + this.nextTable = null; + this.table = nextTab; + this.sizeCtl = (n << 1) - (n >>> 1); + return; + } + + int sc = this.sizeCtl; + if (U.compareAndSwapInt(this, SIZECTL, this.sizeCtl, sc - 1)) { + if (sc - 2 != resizeStamp(n) << RESIZE_STAMP_SHIFT) { + return; + } + + advance = true; + finishing = true; + i = n; + } + } + } + + if (--i < bound && !finishing) { + int nextIndex = this.transferIndex; + if (this.transferIndex <= 0) { + i = -1; + advance = false; + } else { + int nextBound; + if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex, nextBound = nextIndex > stride ? nextIndex - stride : 0)) { + bound = nextBound; + i = nextIndex - 1; + advance = false; + } + } + } else { + advance = false; + } + } + } + + protected final long sumCount() { + Double2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + long sum = this.baseCount; + if (as != null) { + for (int i = 0; i < as.length; i++) { + Double2ObjectConcurrentHashMap.CounterCell a; + if ((a = as[i]) != null) { + sum += a.value; + } + } + } + + return sum; + } + + protected final void fullAddCount(long x, boolean wasUncontended) { + int h; + if ((h = TLRUtil.getProbe()) == 0) { + TLRUtil.localInit(); + h = TLRUtil.getProbe(); + wasUncontended = true; + } + + boolean collide = false; + + while (true) { + Double2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + int n; + if (this.counterCells != null && (n = as.length) > 0) { + Double2ObjectConcurrentHashMap.CounterCell a; + if ((a = as[n - 1 & h]) == null) { + if (this.cellsBusy == 0) { + Double2ObjectConcurrentHashMap.CounterCell r = new Double2ObjectConcurrentHashMap.CounterCell(x); + if (this.cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean created = false; + + try { + Double2ObjectConcurrentHashMap.CounterCell[] rs = this.counterCells; + int m; + int j; + if (this.counterCells != null && (m = rs.length) > 0 && rs[j = m - 1 & h] == null) { + rs[j] = r; + created = true; + } + } finally { + this.cellsBusy = 0; + } + + if (created) { + break; + } + continue; + } + } + + collide = false; + } else if (!wasUncontended) { + wasUncontended = true; + } else { + long v = a.value; + if (U.compareAndSwapLong(a, CELLVALUE, a.value, v + x)) { + break; + } + + if (this.counterCells != as || n >= NCPU) { + collide = false; + } else if (!collide) { + collide = true; + } else if (this.cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + try { + if (this.counterCells == as) { + Double2ObjectConcurrentHashMap.CounterCell[] rs = new Double2ObjectConcurrentHashMap.CounterCell[n << 1]; + + for (int i = 0; i < n; i++) { + rs[i] = as[i]; + } + + this.counterCells = rs; + } + } finally { + this.cellsBusy = 0; + } + + collide = false; + continue; + } + } + + h = TLRUtil.advanceProbe(h); + } else if (this.cellsBusy == 0 && this.counterCells == as && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean init = false; + + try { + if (this.counterCells == as) { + Double2ObjectConcurrentHashMap.CounterCell[] rs = new Double2ObjectConcurrentHashMap.CounterCell[2]; + rs[h & 1] = new Double2ObjectConcurrentHashMap.CounterCell(x); + this.counterCells = rs; + init = true; + } + } finally { + this.cellsBusy = 0; + } + + if (init) { + break; + } + } else { + long vx = this.baseCount; + if (U.compareAndSwapLong(this, BASECOUNT, this.baseCount, vx + x)) { + break; + } + } + } + } + + protected final void treeifyBin(Double2ObjectConcurrentHashMap.Node[] tab, int index) { + if (tab != null) { + int n; + if ((n = tab.length) < 64) { + this.tryPresize(n << 1); + } else { + Double2ObjectConcurrentHashMap.Node b; + if ((b = tabAt(tab, index)) != null && b.hash >= 0) { + synchronized (b) { + if (tabAt(tab, index) == b) { + Double2ObjectConcurrentHashMap.TreeNode hd = null; + Double2ObjectConcurrentHashMap.TreeNode tl = null; + + for (Double2ObjectConcurrentHashMap.Node e = b; e != null; e = e.next) { + Double2ObjectConcurrentHashMap.TreeNode p = new Double2ObjectConcurrentHashMap.TreeNode<>( + this.EMPTY, e.hash, e.key, e.val, null, null + ); + if ((p.prev = tl) == null) { + hd = p; + } else { + tl.next = p; + } + + tl = p; + } + + setTabAt(tab, index, new Double2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, hd)); + } + } + } + } + } + } + + protected Double2ObjectConcurrentHashMap.Node untreeify(Double2ObjectConcurrentHashMap.Node b) { + Double2ObjectConcurrentHashMap.Node hd = null; + Double2ObjectConcurrentHashMap.Node tl = null; + + for (Double2ObjectConcurrentHashMap.Node q = b; q != null; q = q.next) { + Double2ObjectConcurrentHashMap.Node p = new Double2ObjectConcurrentHashMap.Node<>(this.EMPTY, q.hash, q.key, q.val, null); + if (tl == null) { + hd = p; + } else { + tl.next = p; + } + + tl = p; + } + + return hd; + } + + protected final int batchFor(long b) { + long n; + if (b != Long.MAX_VALUE && (n = this.sumCount()) > 1L && n >= b) { + int sp = ForkJoinPool.getCommonPoolParallelism() << 2; + long var6; + return b > 0L && (var6 = n / b) < sp ? (int)var6 : sp; + } else { + return 0; + } + } + + public void forEach(long parallelismThreshold, Double2ObjectConcurrentHashMap.DoubleObjConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Double2ObjectConcurrentHashMap.ForEachMappingTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEach( + long parallelismThreshold, Double2ObjectConcurrentHashMap.DoubleObjFunction transformer, Consumer action + ) { + if (transformer != null && action != null) { + new Double2ObjectConcurrentHashMap.ForEachTransformedMappingTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U search(long parallelismThreshold, Double2ObjectConcurrentHashMap.DoubleObjFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Double2ObjectConcurrentHashMap.SearchMappingsTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public U search(Double2ObjectConcurrentHashMap.DoubleObjFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U search(Double2ObjectConcurrentHashMap.DoubleBiObjFunction searchFunction, X x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithByte(Double2ObjectConcurrentHashMap.DoubleObjByteFunction searchFunction, byte x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithShort(Double2ObjectConcurrentHashMap.DoubleObjShortFunction searchFunction, short x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithInt(Double2ObjectConcurrentHashMap.DoubleObjIntFunction searchFunction, int x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithLong(Double2ObjectConcurrentHashMap.DoubleObjLongFunction searchFunction, long x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithFloat(Double2ObjectConcurrentHashMap.DoubleObjFloatFunction searchFunction, float x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithDouble(Double2ObjectConcurrentHashMap.DoubleObjDoubleFunction searchFunction, double x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U reduce( + long parallelismThreshold, + Double2ObjectConcurrentHashMap.DoubleObjFunction transformer, + BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Double2ObjectConcurrentHashMap.MapReduceMappingsTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer + ) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U reduce( + Double2ObjectConcurrentHashMap.DoubleObjFunction transformer, BiFunction reducer + ) { + if (transformer != null && reducer != null) { + Double2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table == null) { + return null; + } else { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + U r = null; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label88: { + while (true) { + if (p != null) { + next = p; + break label88; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + return r; + } + + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + } + } else { + throw new NullPointerException(); + } + } + + public double reduceToDouble( + long parallelismThreshold, Double2ObjectConcurrentHashMap.ToDoubleDoubleObjFunction transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Double2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceToLong( + long parallelismThreshold, Double2ObjectConcurrentHashMap.ToLongDoubleObjFunction transformer, long basis, LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Double2ObjectConcurrentHashMap.MapReduceMappingsToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceToInt( + long parallelismThreshold, Double2ObjectConcurrentHashMap.ToIntDoubleObjFunction transformer, int basis, IntBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Double2ObjectConcurrentHashMap.MapReduceMappingsToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachKey(long parallelismThreshold, DoubleConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Double2ObjectConcurrentHashMap.ForEachKeyTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachKey(long parallelismThreshold, Double2ObjectConcurrentHashMap.DoubleFunction transformer, Consumer action) { + if (transformer != null && action != null) { + new Double2ObjectConcurrentHashMap.ForEachTransformedKeyTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchKeys(long parallelismThreshold, Double2ObjectConcurrentHashMap.DoubleFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Double2ObjectConcurrentHashMap.SearchKeysTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public double reduceKeys(long parallelismThreshold, Double2ObjectConcurrentHashMap.DoubleReduceTaskOperator reducer) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Double2ObjectConcurrentHashMap.ReduceKeysTask<>(this.EMPTY, null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer) + .invoke0(); + } + } + + public U reduceKeys( + long parallelismThreshold, Double2ObjectConcurrentHashMap.DoubleFunction transformer, BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Double2ObjectConcurrentHashMap.MapReduceKeysTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceKeysToDouble( + long parallelismThreshold, Double2ObjectConcurrentHashMap.DoubleToDoubleFunction transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Double2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceKeysToLong( + long parallelismThreshold, Double2ObjectConcurrentHashMap.DoubleToLongFunction transformer, long basis, LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Double2ObjectConcurrentHashMap.MapReduceKeysToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceKeysToInt(long parallelismThreshold, Double2ObjectConcurrentHashMap.DoubleToIntFunction transformer, int basis, IntBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Double2ObjectConcurrentHashMap.MapReduceKeysToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachValue(long parallelismThreshold, Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Double2ObjectConcurrentHashMap.ForEachValueTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachValue(long parallelismThreshold, Function transformer, Consumer action) { + if (transformer != null && action != null) { + new Double2ObjectConcurrentHashMap.ForEachTransformedValueTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchValues(long parallelismThreshold, Function searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Double2ObjectConcurrentHashMap.SearchValuesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public V reduceValues(long parallelismThreshold, BiFunction reducer) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Double2ObjectConcurrentHashMap.ReduceValuesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer).invoke(); + } + } + + public U reduceValues(long parallelismThreshold, Function transformer, BiFunction reducer) { + if (transformer != null && reducer != null) { + return new Double2ObjectConcurrentHashMap.MapReduceValuesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer + ) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceValuesToDouble( + long parallelismThreshold, Double2ObjectConcurrentHashMap.ToDoubleFunction transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Double2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceValuesToLong(long parallelismThreshold, ToLongFunction transformer, long basis, LongBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Double2ObjectConcurrentHashMap.MapReduceValuesToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceValuesToInt(long parallelismThreshold, ToIntFunction transformer, int basis, IntBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Double2ObjectConcurrentHashMap.MapReduceValuesToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachEntry(long parallelismThreshold, Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Double2ObjectConcurrentHashMap.ForEachEntryTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachEntry( + long parallelismThreshold, Function, ? extends U> transformer, Consumer action + ) { + if (transformer != null && action != null) { + new Double2ObjectConcurrentHashMap.ForEachTransformedEntryTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchEntries(long parallelismThreshold, Function, ? extends U> searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Double2ObjectConcurrentHashMap.SearchEntriesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public Double2ObjectConcurrentHashMap.Entry reduceEntries( + long parallelismThreshold, + BiFunction, Double2ObjectConcurrentHashMap.Entry, ? extends Double2ObjectConcurrentHashMap.Entry> reducer + ) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Double2ObjectConcurrentHashMap.ReduceEntriesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer).invoke(); + } + } + + public U reduceEntries( + long parallelismThreshold, + Function, ? extends U> transformer, + BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Double2ObjectConcurrentHashMap.MapReduceEntriesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer + ) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceEntriesToDouble( + long parallelismThreshold, + Double2ObjectConcurrentHashMap.ToDoubleFunction> transformer, + double basis, + DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Double2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceEntriesToLong( + long parallelismThreshold, ToLongFunction> transformer, long basis, LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Double2ObjectConcurrentHashMap.MapReduceEntriesToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceEntriesToInt( + long parallelismThreshold, ToIntFunction> transformer, int basis, IntBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Double2ObjectConcurrentHashMap.MapReduceEntriesToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public V valueMatching(Predicate predicate) { + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + Double2ObjectConcurrentHashMap.Node[] tab = this.table; + int f = this.table == null ? 0 : tab.length; + int baseLimit = f; + int baseSize = f; + boolean b = false; + + label80: + while (next != null || !b) { + b |= true; + Double2ObjectConcurrentHashMap.Node e = next; + if (next != null) { + e = next.next; + } + + label76: + while (e == null) { + if (baseIndex < baseLimit) { + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab != null && (n = tab.length) > index && index >= 0) { + if ((e = tabAt(tab, index)) != null && e.hash < 0) { + if (e instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (e instanceof Double2ObjectConcurrentHashMap.TreeBin) { + e = ((Double2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + continue; + } + + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack next1 = stack.next; + stack.next = spare; + stack = next1; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + continue label76; + } + } + } + + next = null; + continue label80; + } + + next = e; + if (predicate.test(e.val)) { + return e.val; + } + } + + return null; + } + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + U = (Unsafe)f.get(null); + Class k = Double2ObjectConcurrentHashMap.class; + SIZECTL = U.objectFieldOffset(k.getDeclaredField("sizeCtl")); + TRANSFERINDEX = U.objectFieldOffset(k.getDeclaredField("transferIndex")); + BASECOUNT = U.objectFieldOffset(k.getDeclaredField("baseCount")); + CELLSBUSY = U.objectFieldOffset(k.getDeclaredField("cellsBusy")); + Class ck = Double2ObjectConcurrentHashMap.CounterCell.class; + CELLVALUE = U.objectFieldOffset(ck.getDeclaredField("value")); + Class ak = Double2ObjectConcurrentHashMap.Node[].class; + ABASE = U.arrayBaseOffset(ak); + int scale = U.arrayIndexScale(ak); + if ((scale & scale - 1) != 0) { + throw new Error("data type scale not a power of two"); + } else { + ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); + } + } catch (Exception var5) { + throw new Error(var5); + } + } + + protected static class BaseIterator extends Double2ObjectConcurrentHashMap.Traverser { + public final Double2ObjectConcurrentHashMap map; + public Double2ObjectConcurrentHashMap.Node lastReturned; + + public BaseIterator(Double2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, Double2ObjectConcurrentHashMap map) { + super(tab, size, index, limit); + this.map = map; + this.advance(); + } + + public final boolean hasNext() { + return this.next != null; + } + + public final boolean hasMoreElements() { + return this.next != null; + } + + public final void remove() { + Double2ObjectConcurrentHashMap.Node p = this.lastReturned; + if (this.lastReturned == null) { + throw new IllegalStateException(); + } else { + this.lastReturned = null; + this.map.replaceNode(p.key, null, null); + } + } + } + + protected abstract static class BulkTask extends CountedCompleter { + public Double2ObjectConcurrentHashMap.Node[] tab; + public Double2ObjectConcurrentHashMap.Node next; + public Double2ObjectConcurrentHashMap.TableStack stack; + public Double2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + public int batch; + + protected BulkTask(Double2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Double2ObjectConcurrentHashMap.Node[] t) { + super(par); + this.batch = b; + this.index = this.baseIndex = i; + if ((this.tab = t) == null) { + this.baseSize = this.baseLimit = 0; + } else if (par == null) { + this.baseSize = this.baseLimit = t.length; + } else { + this.baseLimit = f; + this.baseSize = par.baseSize; + } + } + + protected final Double2ObjectConcurrentHashMap.Node advance() { + Double2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Double2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Double2ObjectConcurrentHashMap.TreeBin) { + e = ((Double2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Double2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Double2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Double2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + } + + protected abstract static class CollectionView implements ObjectCollection, Serializable { + public static final long serialVersionUID = 7249069246763182397L; + public final Double2ObjectConcurrentHashMap map; + protected static final String oomeMsg = "Required array size too large"; + + public CollectionView(Double2ObjectConcurrentHashMap map) { + this.map = map; + } + + public Double2ObjectConcurrentHashMap getMap() { + return this.map; + } + + @Override + public final void clear() { + this.map.clear(); + } + + @Override + public final int size() { + return this.map.size(); + } + + @Override + public final boolean isEmpty() { + return this.map.isEmpty(); + } + + @Override + public abstract ObjectIterator iterator(); + + @Override + public abstract boolean contains(Object var1); + + @Override + public abstract boolean remove(Object var1); + + @Override + public final Object[] toArray() { + long sz = this.map.mappingCount(); + if (sz > 2147483639L) { + throw new OutOfMemoryError("Required array size too large"); + } else { + int n = (int)sz; + Object[] r = new Object[n]; + int i = 0; + + for (E e : this) { + if (i == n) { + if (n >= 2147483639) { + throw new OutOfMemoryError("Required array size too large"); + } + + if (n >= 1073741819) { + n = 2147483639; + } else { + n += (n >>> 1) + 1; + } + + r = Arrays.copyOf(r, n); + } + + r[i++] = e; + } + + return i == n ? r : Arrays.copyOf(r, i); + } + } + + @Override + public final T[] toArray(T[] a) { + long sz = this.map.mappingCount(); + if (sz > 2147483639L) { + throw new OutOfMemoryError("Required array size too large"); + } else { + int m = (int)sz; + T[] r = (T[])(a.length >= m ? a : (Object[])Array.newInstance(a.getClass().getComponentType(), m)); + int n = r.length; + int i = 0; + + for (E e : this) { + if (i == n) { + if (n >= 2147483639) { + throw new OutOfMemoryError("Required array size too large"); + } + + if (n >= 1073741819) { + n = 2147483639; + } else { + n += (n >>> 1) + 1; + } + + r = (T[])Arrays.copyOf(r, n); + } + + r[i++] = (T)e; + } + + if (a == r && i < n) { + r[i] = null; + return r; + } else { + return (T[])(i == n ? r : Arrays.copyOf(r, i)); + } + } + } + + @Override + public final String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + Iterator it = this.iterator(); + if (it.hasNext()) { + while (true) { + Object e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (!it.hasNext()) { + break; + } + + sb.append(',').append(' '); + } + } + + return sb.append(']').toString(); + } + + @Override + public final boolean containsAll(Collection c) { + if (c != this) { + for (Object e : c) { + if (e == null || !this.contains(e)) { + return false; + } + } + } + + return true; + } + + @Override + public final boolean removeAll(Collection c) { + if (c == null) { + throw new NullPointerException(); + } else { + boolean modified = false; + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (c.contains(it.next())) { + it.remove(); + modified = true; + } + } + + return modified; + } + } + + @Override + public final boolean retainAll(Collection c) { + if (c == null) { + throw new NullPointerException(); + } else { + boolean modified = false; + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + + return modified; + } + } + } + + protected static final class CounterCell { + public volatile long value; + + public CounterCell(long x) { + this.value = x; + } + } + + @FunctionalInterface + public interface DoubleBiObjByteConsumer { + void accept(double var1, V var3, byte var4, X var5); + } + + @FunctionalInterface + public interface DoubleBiObjConsumer { + void accept(double var1, V var3, X var4); + } + + @FunctionalInterface + public interface DoubleBiObjDoubleConsumer { + void accept(double var1, V var3, double var4, X var6); + } + + @FunctionalInterface + public interface DoubleBiObjFloatConsumer { + void accept(double var1, V var3, float var4, X var5); + } + + @FunctionalInterface + public interface DoubleBiObjFunction { + J apply(double var1, V var3, X var4); + } + + @FunctionalInterface + public interface DoubleBiObjIntConsumer { + void accept(double var1, V var3, int var4, X var5); + } + + @FunctionalInterface + public interface DoubleBiObjLongConsumer { + void accept(double var1, V var3, long var4, X var6); + } + + @FunctionalInterface + public interface DoubleBiObjShortConsumer { + void accept(double var1, V var3, short var4, X var5); + } + + @FunctionalInterface + public interface DoubleFunction { + R apply(double var1); + } + + @FunctionalInterface + public interface DoubleObjByteConsumer { + void accept(double var1, V var3, byte var4); + } + + @FunctionalInterface + public interface DoubleObjByteFunction { + J apply(double var1, V var3, byte var4); + } + + @FunctionalInterface + public interface DoubleObjConsumer { + void accept(double var1, V var3); + } + + @FunctionalInterface + public interface DoubleObjDoubleConsumer { + void accept(double var1, V var3, double var4); + } + + @FunctionalInterface + public interface DoubleObjDoubleFunction { + J apply(double var1, V var3, double var4); + } + + @FunctionalInterface + public interface DoubleObjFloatConsumer { + void accept(double var1, V var3, float var4); + } + + @FunctionalInterface + public interface DoubleObjFloatFunction { + J apply(double var1, V var3, float var4); + } + + @FunctionalInterface + public interface DoubleObjFunction { + J apply(double var1, V var3); + } + + @FunctionalInterface + public interface DoubleObjIntConsumer { + void accept(double var1, V var3, int var4); + } + + @FunctionalInterface + public interface DoubleObjIntFunction { + J apply(double var1, V var3, int var4); + } + + @FunctionalInterface + public interface DoubleObjLongConsumer { + void accept(double var1, V var3, long var4); + } + + @FunctionalInterface + public interface DoubleObjLongFunction { + J apply(double var1, V var3, long var4); + } + + @FunctionalInterface + public interface DoubleObjShortConsumer { + void accept(double var1, V var3, short var4); + } + + @FunctionalInterface + public interface DoubleObjShortFunction { + J apply(double var1, V var3, short var4); + } + + @FunctionalInterface + public interface DoubleReduceTaskOperator { + double reduce(double var1, double var3, double var5); + } + + protected abstract static class DoubleReturningBulkTask extends Double2ObjectConcurrentHashMap.BulkTask { + public double result; + + public DoubleReturningBulkTask(Double2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Double2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected double invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + protected abstract static class DoubleReturningBulkTask2 extends Double2ObjectConcurrentHashMap.BulkTask { + public double result; + + public DoubleReturningBulkTask2(Double2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Double2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected double invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + @FunctionalInterface + public interface DoubleToDoubleFunction { + double applyAsDouble(double var1); + } + + @FunctionalInterface + public interface DoubleToIntFunction { + int applyAsInt(double var1); + } + + @FunctionalInterface + public interface DoubleToLongFunction { + long applyAsLong(double var1); + } + + @FunctionalInterface + public interface DoubleTriObjConsumer { + void accept(double var1, V var3, X var4, Y var5); + } + + public interface Entry extends Double2ObjectMap.Entry { + boolean isEmpty(); + + @Deprecated + @Override + Double getKey(); + + @Override + double getDoubleKey(); + + @Override + V getValue(); + + @Override + int hashCode(); + + @Override + String toString(); + + @Override + boolean equals(Object var1); + + @Override + V setValue(V var1); + } + + protected static final class EntryIterator extends Double2ObjectConcurrentHashMap.BaseIterator implements ObjectIterator> { + public EntryIterator(Double2ObjectConcurrentHashMap.Node[] tab, int index, int size, int limit, Double2ObjectConcurrentHashMap map) { + super(tab, index, size, limit, map); + } + + public final Double2ObjectConcurrentHashMap.Entry next() { + Double2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + double k = p.key; + V v = p.val; + this.lastReturned = p; + this.advance(); + return new Double2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), k, v, this.map); + } + } + } + + protected static final class EntrySetView + extends Double2ObjectConcurrentHashMap.CollectionView> + implements ObjectSet>, + Serializable { + public static final long serialVersionUID = 2249069246763182397L; + + public EntrySetView(Double2ObjectConcurrentHashMap map) { + super(map); + } + + @Override + public boolean contains(Object o) { + if (o instanceof Double2ObjectMap.Entry) { + Double2ObjectMap.Entry e; + double k = (e = (Double2ObjectMap.Entry)o).getDoubleKey(); + if (!((Double2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + Object v; + Object r; + return (r = this.map.get(k)) != null && (v = e.getValue()) != null && (v == r || v.equals(r)); + } + } + + return false; + } + + @Override + public boolean remove(Object o) { + if (o instanceof Double2ObjectMap.Entry) { + Double2ObjectMap.Entry e; + double k = (e = (Double2ObjectMap.Entry)o).getDoubleKey(); + if (!((Double2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + Object v; + return (v = e.getValue()) != null && this.map.remove(k, v); + } + } + + return false; + } + + @Override + public ObjectIterator> iterator() { + Double2ObjectConcurrentHashMap m = this.map; + Double2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Double2ObjectConcurrentHashMap.EntryIterator<>(t, f, 0, f, m); + } + + public boolean add(Double2ObjectMap.Entry e) { + return this.map.putVal(e.getDoubleKey(), e.getValue(), false) == null; + } + + @Override + public boolean addAll(Collection> c) { + boolean added = false; + + for (Double2ObjectMap.Entry e : c) { + if (this.add(e)) { + added = true; + } + } + + return added; + } + + @Override + public final int hashCode() { + int h = 0; + Double2ObjectConcurrentHashMap.Node[] t = this.map.table; + if (this.map.table != null) { + Double2ObjectConcurrentHashMap.Traverser it = new Double2ObjectConcurrentHashMap.Traverser<>(t, t.length, 0, t.length); + + Double2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + h += p.hashCode(); + } + } + + return h; + } + + @Override + public final boolean equals(Object o) { + Set c; + return o instanceof Set && ((c = (Set)o) == this || this.containsAll(c) && c.containsAll(this)); + } + + @Override + public ObjectSpliterator> spliterator() { + Double2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Double2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Double2ObjectConcurrentHashMap.EntrySpliterator<>(t, f, 0, f, n < 0L ? 0L : n, m); + } + + @Override + public void forEach(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node[] t = this.map.table; + if (this.map.table != null) { + Double2ObjectConcurrentHashMap.Traverser it = new Double2ObjectConcurrentHashMap.Traverser<>(t, t.length, 0, t.length); + + Double2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + action.accept(new Double2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + } + } + } + } + } + + protected static final class EntrySpliterator extends Double2ObjectConcurrentHashMap.Traverser implements ObjectSpliterator> { + public final Double2ObjectConcurrentHashMap map; + public long est; + + public EntrySpliterator(Double2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est, Double2ObjectConcurrentHashMap map) { + super(tab, size, index, limit); + this.map = map; + this.est = est; + } + + @Override + public ObjectSpliterator> trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Double2ObjectConcurrentHashMap.EntrySpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1, this.map); + } + + @Override + public void forEachRemaining(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(new Double2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + } + } + } + + @Override + public boolean tryAdvance(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(new Double2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4353; + } + } + + protected static final class ForEachEntryTask extends Double2ObjectConcurrentHashMap.BulkTask { + public final Consumer> action; + + public ForEachEntryTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Consumer> action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Consumer> action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Double2ObjectConcurrentHashMap.ForEachEntryTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachKeyTask extends Double2ObjectConcurrentHashMap.BulkTask { + public final DoubleConsumer action; + + public ForEachKeyTask( + Double2ObjectConcurrentHashMap.BulkTask p, int b, int i, int f, Double2ObjectConcurrentHashMap.Node[] t, DoubleConsumer action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + DoubleConsumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Double2ObjectConcurrentHashMap.ForEachKeyTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.key); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachMappingTask extends Double2ObjectConcurrentHashMap.BulkTask { + public final Double2ObjectConcurrentHashMap.DoubleObjConsumer action; + + public ForEachMappingTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.DoubleObjConsumer action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Double2ObjectConcurrentHashMap.DoubleObjConsumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Double2ObjectConcurrentHashMap.ForEachMappingTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.key, p.val); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachTransformedEntryTask extends Double2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> transformer; + public final Consumer action; + + public ForEachTransformedEntryTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Function, ? extends U> transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Function, ? extends U> transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Double2ObjectConcurrentHashMap.ForEachTransformedEntryTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action + ) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedKeyTask extends Double2ObjectConcurrentHashMap.BulkTask { + public final Double2ObjectConcurrentHashMap.DoubleFunction transformer; + public final Consumer action; + + public ForEachTransformedKeyTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.DoubleFunction transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Double2ObjectConcurrentHashMap.DoubleFunction transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Double2ObjectConcurrentHashMap.ForEachTransformedKeyTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedMappingTask extends Double2ObjectConcurrentHashMap.BulkTask { + public final Double2ObjectConcurrentHashMap.DoubleObjFunction transformer; + public final Consumer action; + + public ForEachTransformedMappingTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.DoubleObjFunction transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Double2ObjectConcurrentHashMap.DoubleObjFunction transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Double2ObjectConcurrentHashMap.ForEachTransformedMappingTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action + ) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedValueTask extends Double2ObjectConcurrentHashMap.BulkTask { + public final Function transformer; + public final Consumer action; + + public ForEachTransformedValueTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Function transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Function transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Double2ObjectConcurrentHashMap.ForEachTransformedValueTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action + ) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.val)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachValueTask extends Double2ObjectConcurrentHashMap.BulkTask { + public final Consumer action; + + public ForEachValueTask( + Double2ObjectConcurrentHashMap.BulkTask p, int b, int i, int f, Double2ObjectConcurrentHashMap.Node[] t, Consumer action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Double2ObjectConcurrentHashMap.ForEachValueTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.val); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForwardingNode extends Double2ObjectConcurrentHashMap.Node { + public final Double2ObjectConcurrentHashMap.Node[] nextTable; + + public ForwardingNode(double empty, Double2ObjectConcurrentHashMap.Node[] tab) { + super(empty, -1, empty, null, null); + this.nextTable = tab; + } + + @Override + protected Double2ObjectConcurrentHashMap.Node find(int h, double k) { + Double2ObjectConcurrentHashMap.Node[] tab = this.nextTable; + + Double2ObjectConcurrentHashMap.Node e; + int n; + label41: + while (k != this.EMPTY && tab != null && (n = tab.length) != 0 && (e = Double2ObjectConcurrentHashMap.tabAt(tab, n - 1 & h)) != null) { + do { + int eh = e.hash; + if (e.hash == h) { + double ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + + if (eh < 0) { + if (!(e instanceof Double2ObjectConcurrentHashMap.ForwardingNode)) { + return e.find(h, k); + } + + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + continue label41; + } + } while ((e = e.next) != null); + + return null; + } + + return null; + } + } + + protected abstract static class IntReturningBulkTask extends Double2ObjectConcurrentHashMap.BulkTask { + public int result; + + public IntReturningBulkTask(Double2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Double2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected int invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + protected static final class KeyIterator implements DoubleIterator { + public Double2ObjectConcurrentHashMap.Node[] tab; + public Double2ObjectConcurrentHashMap.Node next; + public Double2ObjectConcurrentHashMap.TableStack stack; + public Double2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + public final Double2ObjectConcurrentHashMap map; + public Double2ObjectConcurrentHashMap.Node lastReturned; + + public KeyIterator(Double2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, Double2ObjectConcurrentHashMap map) { + this.tab = tab; + this.baseSize = size; + this.baseIndex = this.index = index; + this.baseLimit = limit; + this.next = null; + this.map = map; + this.advance(); + } + + protected final Double2ObjectConcurrentHashMap.Node advance() { + Double2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Double2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Double2ObjectConcurrentHashMap.TreeBin) { + e = ((Double2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Double2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Double2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Double2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + + @Override + public final boolean hasNext() { + return this.next != null; + } + + public final boolean hasMoreElements() { + return this.next != null; + } + + @Override + public final void remove() { + Double2ObjectConcurrentHashMap.Node p = this.lastReturned; + if (this.lastReturned == null) { + throw new IllegalStateException(); + } else { + this.lastReturned = null; + this.map.replaceNode(p.key, null, null); + } + } + + @Override + public final double nextDouble() { + Double2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + double k = p.key; + this.lastReturned = p; + this.advance(); + return k; + } + } + } + + public static class KeySetView implements DoubleSet { + public static final long serialVersionUID = 7249069246763182397L; + public final Double2ObjectConcurrentHashMap map; + public final V value; + + public KeySetView(Double2ObjectConcurrentHashMap map, V value) { + this.map = map; + this.value = value; + } + + public V getMappedValue() { + return this.value; + } + + @Override + public boolean contains(double o) { + return this.map.containsKey(o); + } + + @Override + public boolean remove(double o) { + return this.map.remove(o) != null; + } + + @Override + public DoubleIterator iterator() { + Double2ObjectConcurrentHashMap m = this.map; + Double2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Double2ObjectConcurrentHashMap.KeyIterator<>(t, f, 0, f, m); + } + + @Override + public boolean add(double e) { + V v = this.value; + if (this.value == null) { + throw new UnsupportedOperationException(); + } else { + return this.map.putVal(e, v, true) == null; + } + } + + @Override + public boolean addAll(DoubleCollection c) { + boolean added = false; + V v = this.value; + if (this.value == null) { + throw new UnsupportedOperationException(); + } else { + DoubleIterator iter = c.iterator(); + + while (iter.hasNext()) { + double e = iter.nextDouble(); + if (this.map.putVal(e, v, true) == null) { + added = true; + } + } + + return added; + } + } + + @Override + public int hashCode() { + int h = 0; + DoubleIterator iter = this.iterator(); + + while (iter.hasNext()) { + h += Double.hashCode(iter.nextDouble()); + } + + return h; + } + + @Override + public boolean equals(Object o) { + DoubleSet c; + return o instanceof DoubleSet && ((c = (DoubleSet)o) == this || this.containsAll(c) && c.containsAll(this)); + } + + public double getNoEntryValue() { + return this.map.EMPTY; + } + + @Override + public int size() { + return this.map.size(); + } + + @Override + public boolean isEmpty() { + return this.map.isEmpty(); + } + + @Override + public Object[] toArray() { + Object[] out = new Double[this.size()]; + DoubleIterator iter = this.iterator(); + + int i; + for (i = 0; i < out.length && iter.hasNext(); i++) { + out[i] = iter.nextDouble(); + } + + if (out.length > i + 1) { + out[i] = this.map.EMPTY; + } + + return out; + } + + @Override + public Object[] toArray(Object[] dest) { + DoubleIterator iter = this.iterator(); + + int i; + for (i = 0; i < dest.length && iter.hasNext() && i <= dest.length; i++) { + dest[i] = iter.next(); + } + + if (dest.length > i + 1) { + dest[i] = this.map.EMPTY; + } + + return dest; + } + + @Override + public double[] toDoubleArray() { + double[] out = new double[this.size()]; + DoubleIterator iter = this.iterator(); + + int i; + for (i = 0; i < out.length && iter.hasNext(); i++) { + out[i] = iter.next(); + } + + if (out.length > i + 1) { + out[i] = this.map.EMPTY; + } + + return out; + } + + @Override + public double[] toArray(double[] dest) { + DoubleIterator iter = this.iterator(); + + int i; + for (i = 0; i < dest.length && iter.hasNext() && i <= dest.length; i++) { + dest[i] = iter.next(); + } + + if (dest.length > i + 1) { + dest[i] = this.map.EMPTY; + } + + return dest; + } + + @Override + public double[] toDoubleArray(double[] dest) { + return this.toArray(dest); + } + + @Override + public boolean containsAll(Collection collection) { + for (Object element : collection) { + if (!(element instanceof Long)) { + return false; + } + + double c = (Double)element; + if (!this.contains(c)) { + return false; + } + } + + return true; + } + + @Override + public boolean containsAll(DoubleCollection collection) { + DoubleIterator iter = collection.iterator(); + + while (iter.hasNext()) { + double element = iter.next(); + if (!this.contains(element)) { + return false; + } + } + + return true; + } + + public boolean containsAll(double[] array) { + int i = array.length; + + while (i-- > 0) { + if (!this.contains(array[i])) { + return false; + } + } + + return true; + } + + @Override + public boolean addAll(Collection collection) { + boolean changed = false; + + for (Double element : collection) { + double e = element; + if (this.add(e)) { + changed = true; + } + } + + return changed; + } + + public boolean addAll(double[] array) { + boolean changed = false; + int i = array.length; + + while (i-- > 0) { + if (this.add(array[i])) { + changed = true; + } + } + + return changed; + } + + @Override + public boolean retainAll(Collection collection) { + boolean modified = false; + DoubleIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (!collection.contains(iter.next())) { + iter.remove(); + modified = true; + } + } + + return modified; + } + + @Override + public boolean retainAll(DoubleCollection collection) { + if (this == collection) { + return false; + } else { + boolean modified = false; + DoubleIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (!collection.contains(iter.next())) { + iter.remove(); + modified = true; + } + } + + return modified; + } + } + + public boolean retainAll(double[] array) { + boolean modified = false; + DoubleIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (Arrays.binarySearch(array, iter.next().doubleValue()) < 0) { + iter.remove(); + modified = true; + } + } + + return modified; + } + + @Override + public boolean removeAll(Collection collection) { + boolean changed = false; + + for (Object element : collection) { + if (element instanceof Double) { + double c = (Double)element; + if (this.remove(c)) { + changed = true; + } + } + } + + return changed; + } + + @Override + public boolean removeAll(DoubleCollection collection) { + boolean changed = false; + DoubleIterator iter = collection.iterator(); + + while (iter.hasNext()) { + double element = iter.next(); + if (this.remove(element)) { + changed = true; + } + } + + return changed; + } + + public boolean removeAll(double[] array) { + boolean changed = false; + int i = array.length; + + while (i-- > 0) { + if (this.remove(array[i])) { + changed = true; + } + } + + return changed; + } + + @Override + public void clear() { + this.map.clear(); + } + + @Override + public DoubleSpliterator spliterator() { + Double2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Double2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Double2ObjectConcurrentHashMap.KeySpliterator<>(t, f, 0, f, n < 0L ? 0L : n); + } + } + + protected static final class KeySpliterator extends Double2ObjectConcurrentHashMap.Traverser implements DoubleSpliterator { + public long est; + + public KeySpliterator(Double2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est) { + super(tab, size, index, limit); + this.est = est; + } + + @Override + public DoubleSpliterator trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Double2ObjectConcurrentHashMap.KeySpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1); + } + + @Override + public boolean tryAdvance(Consumer action) { + return action instanceof DoubleConsumer ? this.tryAdvance((DoubleConsumer)action) : this.tryAdvance(value -> action.accept(value)); + } + + @Override + public void forEachRemaining(DoubleConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(p.key); + } + } + } + + @Override + public boolean tryAdvance(DoubleConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(p.key); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4353; + } + } + + protected abstract static class LongReturningBulkTask extends Double2ObjectConcurrentHashMap.BulkTask { + public long result; + + public LongReturningBulkTask(Double2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Double2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected long invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + protected static final class MapEntry implements Double2ObjectConcurrentHashMap.Entry { + public final boolean empty; + public final double key; + public V val; + public final Double2ObjectConcurrentHashMap map; + + public MapEntry(boolean empty, double key, V val, Double2ObjectConcurrentHashMap map) { + this.empty = empty; + this.key = key; + this.val = val; + this.map = map; + } + + @Override + public boolean isEmpty() { + return this.empty; + } + + @Override + public Double getKey() { + return this.key; + } + + @Override + public double getDoubleKey() { + return this.key; + } + + @Override + public V getValue() { + return this.val; + } + + @Override + public String toString() { + return this.empty ? "EMPTY=" + this.val : this.key + "=" + this.val; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o instanceof Double2ObjectConcurrentHashMap.Entry) { + if (this.empty != ((Double2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + return false; + } else { + return !this.empty && this.key != ((Double2ObjectConcurrentHashMap.Entry)o).getDoubleKey() + ? false + : this.val.equals(((Double2ObjectConcurrentHashMap.Entry)o).getValue()); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.empty ? 1 : 0; + result = 31 * result + Double.hashCode(this.key); + return 31 * result + this.val.hashCode(); + } + + @Override + public V setValue(V value) { + if (value == null) { + throw new NullPointerException(); + } else { + V v = this.val; + this.val = value; + this.map.put(this.key, value); + return v; + } + } + } + + protected static final class MapReduceEntriesTask extends Double2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> transformer; + public final BiFunction reducer; + public U result; + public Double2ObjectConcurrentHashMap.MapReduceEntriesTask rights; + public Double2ObjectConcurrentHashMap.MapReduceEntriesTask nextRight; + + public MapReduceEntriesTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.MapReduceEntriesTask nextRight, + Function, ? extends U> transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Function, ? extends U> transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.MapReduceEntriesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Double2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.MapReduceEntriesTask t = (Double2ObjectConcurrentHashMap.MapReduceEntriesTask)c; + + for (Double2ObjectConcurrentHashMap.MapReduceEntriesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceEntriesToDoubleTask extends Double2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final Double2ObjectConcurrentHashMap.ToDoubleFunction> transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Double2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask rights; + public Double2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask nextRight; + + public MapReduceEntriesToDoubleTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask nextRight, + Double2ObjectConcurrentHashMap.ToDoubleFunction> transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Double2ObjectConcurrentHashMap.ToDoubleFunction> transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask t = (Double2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask)c; + + for (Double2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceEntriesToIntTask extends Double2ObjectConcurrentHashMap.IntReturningBulkTask { + public final ToIntFunction> transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Double2ObjectConcurrentHashMap.MapReduceEntriesToIntTask rights; + public Double2ObjectConcurrentHashMap.MapReduceEntriesToIntTask nextRight; + + public MapReduceEntriesToIntTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.MapReduceEntriesToIntTask nextRight, + ToIntFunction> transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToIntFunction> transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.MapReduceEntriesToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.MapReduceEntriesToIntTask t = (Double2ObjectConcurrentHashMap.MapReduceEntriesToIntTask)c; + + for (Double2ObjectConcurrentHashMap.MapReduceEntriesToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceEntriesToLongTask extends Double2ObjectConcurrentHashMap.LongReturningBulkTask { + public final ToLongFunction> transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Double2ObjectConcurrentHashMap.MapReduceEntriesToLongTask rights; + public Double2ObjectConcurrentHashMap.MapReduceEntriesToLongTask nextRight; + + public MapReduceEntriesToLongTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.MapReduceEntriesToLongTask nextRight, + ToLongFunction> transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToLongFunction> transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.MapReduceEntriesToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.MapReduceEntriesToLongTask t = (Double2ObjectConcurrentHashMap.MapReduceEntriesToLongTask)c; + + for (Double2ObjectConcurrentHashMap.MapReduceEntriesToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysTask extends Double2ObjectConcurrentHashMap.BulkTask { + public final Double2ObjectConcurrentHashMap.DoubleFunction transformer; + public final BiFunction reducer; + public U result; + public Double2ObjectConcurrentHashMap.MapReduceKeysTask rights; + public Double2ObjectConcurrentHashMap.MapReduceKeysTask nextRight; + + public MapReduceKeysTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.MapReduceKeysTask nextRight, + Double2ObjectConcurrentHashMap.DoubleFunction transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Double2ObjectConcurrentHashMap.DoubleFunction transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.MapReduceKeysTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Double2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.MapReduceKeysTask t = (Double2ObjectConcurrentHashMap.MapReduceKeysTask)c; + + for (Double2ObjectConcurrentHashMap.MapReduceKeysTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceKeysToDoubleTask extends Double2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final Double2ObjectConcurrentHashMap.DoubleToDoubleFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Double2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask rights; + public Double2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask nextRight; + + public MapReduceKeysToDoubleTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask nextRight, + Double2ObjectConcurrentHashMap.DoubleToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Double2ObjectConcurrentHashMap.DoubleToDoubleFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask t = (Double2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask)c; + + for (Double2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysToIntTask extends Double2ObjectConcurrentHashMap.IntReturningBulkTask { + public final Double2ObjectConcurrentHashMap.DoubleToIntFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Double2ObjectConcurrentHashMap.MapReduceKeysToIntTask rights; + public Double2ObjectConcurrentHashMap.MapReduceKeysToIntTask nextRight; + + public MapReduceKeysToIntTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.MapReduceKeysToIntTask nextRight, + Double2ObjectConcurrentHashMap.DoubleToIntFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Double2ObjectConcurrentHashMap.DoubleToIntFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.MapReduceKeysToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.MapReduceKeysToIntTask t = (Double2ObjectConcurrentHashMap.MapReduceKeysToIntTask)c; + + for (Double2ObjectConcurrentHashMap.MapReduceKeysToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysToLongTask extends Double2ObjectConcurrentHashMap.LongReturningBulkTask { + public final Double2ObjectConcurrentHashMap.DoubleToLongFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Double2ObjectConcurrentHashMap.MapReduceKeysToLongTask rights; + public Double2ObjectConcurrentHashMap.MapReduceKeysToLongTask nextRight; + + public MapReduceKeysToLongTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.MapReduceKeysToLongTask nextRight, + Double2ObjectConcurrentHashMap.DoubleToLongFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Double2ObjectConcurrentHashMap.DoubleToLongFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.MapReduceKeysToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.MapReduceKeysToLongTask t = (Double2ObjectConcurrentHashMap.MapReduceKeysToLongTask)c; + + for (Double2ObjectConcurrentHashMap.MapReduceKeysToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsTask extends Double2ObjectConcurrentHashMap.BulkTask { + public final Double2ObjectConcurrentHashMap.DoubleObjFunction transformer; + public final BiFunction reducer; + public U result; + public Double2ObjectConcurrentHashMap.MapReduceMappingsTask rights; + public Double2ObjectConcurrentHashMap.MapReduceMappingsTask nextRight; + + public MapReduceMappingsTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.MapReduceMappingsTask nextRight, + Double2ObjectConcurrentHashMap.DoubleObjFunction transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Double2ObjectConcurrentHashMap.DoubleObjFunction transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.MapReduceMappingsTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Double2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.MapReduceMappingsTask t = (Double2ObjectConcurrentHashMap.MapReduceMappingsTask)c; + + for (Double2ObjectConcurrentHashMap.MapReduceMappingsTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceMappingsToDoubleTask extends Double2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final Double2ObjectConcurrentHashMap.ToDoubleDoubleObjFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Double2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask rights; + public Double2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask nextRight; + + public MapReduceMappingsToDoubleTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask nextRight, + Double2ObjectConcurrentHashMap.ToDoubleDoubleObjFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Double2ObjectConcurrentHashMap.ToDoubleDoubleObjFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask t = (Double2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask)c; + + for (Double2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsToIntTask extends Double2ObjectConcurrentHashMap.IntReturningBulkTask { + public final Double2ObjectConcurrentHashMap.ToIntDoubleObjFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Double2ObjectConcurrentHashMap.MapReduceMappingsToIntTask rights; + public Double2ObjectConcurrentHashMap.MapReduceMappingsToIntTask nextRight; + + public MapReduceMappingsToIntTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.MapReduceMappingsToIntTask nextRight, + Double2ObjectConcurrentHashMap.ToIntDoubleObjFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Double2ObjectConcurrentHashMap.ToIntDoubleObjFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.MapReduceMappingsToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.MapReduceMappingsToIntTask t = (Double2ObjectConcurrentHashMap.MapReduceMappingsToIntTask)c; + + for (Double2ObjectConcurrentHashMap.MapReduceMappingsToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsToLongTask extends Double2ObjectConcurrentHashMap.LongReturningBulkTask { + public final Double2ObjectConcurrentHashMap.ToLongDoubleObjFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Double2ObjectConcurrentHashMap.MapReduceMappingsToLongTask rights; + public Double2ObjectConcurrentHashMap.MapReduceMappingsToLongTask nextRight; + + public MapReduceMappingsToLongTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.MapReduceMappingsToLongTask nextRight, + Double2ObjectConcurrentHashMap.ToLongDoubleObjFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Double2ObjectConcurrentHashMap.ToLongDoubleObjFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.MapReduceMappingsToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.MapReduceMappingsToLongTask t = (Double2ObjectConcurrentHashMap.MapReduceMappingsToLongTask)c; + + for (Double2ObjectConcurrentHashMap.MapReduceMappingsToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesTask extends Double2ObjectConcurrentHashMap.BulkTask { + public final Function transformer; + public final BiFunction reducer; + public U result; + public Double2ObjectConcurrentHashMap.MapReduceValuesTask rights; + public Double2ObjectConcurrentHashMap.MapReduceValuesTask nextRight; + + public MapReduceValuesTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.MapReduceValuesTask nextRight, + Function transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Function transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.MapReduceValuesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Double2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.MapReduceValuesTask t = (Double2ObjectConcurrentHashMap.MapReduceValuesTask)c; + + for (Double2ObjectConcurrentHashMap.MapReduceValuesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceValuesToDoubleTask extends Double2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final Double2ObjectConcurrentHashMap.ToDoubleFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Double2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask rights; + public Double2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask nextRight; + + public MapReduceValuesToDoubleTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask nextRight, + Double2ObjectConcurrentHashMap.ToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Double2ObjectConcurrentHashMap.ToDoubleFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask t = (Double2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask)c; + + for (Double2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesToIntTask extends Double2ObjectConcurrentHashMap.IntReturningBulkTask { + public final ToIntFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Double2ObjectConcurrentHashMap.MapReduceValuesToIntTask rights; + public Double2ObjectConcurrentHashMap.MapReduceValuesToIntTask nextRight; + + public MapReduceValuesToIntTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.MapReduceValuesToIntTask nextRight, + ToIntFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToIntFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.MapReduceValuesToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.MapReduceValuesToIntTask t = (Double2ObjectConcurrentHashMap.MapReduceValuesToIntTask)c; + + for (Double2ObjectConcurrentHashMap.MapReduceValuesToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesToLongTask extends Double2ObjectConcurrentHashMap.LongReturningBulkTask { + public final ToLongFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Double2ObjectConcurrentHashMap.MapReduceValuesToLongTask rights; + public Double2ObjectConcurrentHashMap.MapReduceValuesToLongTask nextRight; + + public MapReduceValuesToLongTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.MapReduceValuesToLongTask nextRight, + ToLongFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToLongFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.MapReduceValuesToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.MapReduceValuesToLongTask t = (Double2ObjectConcurrentHashMap.MapReduceValuesToLongTask)c; + + for (Double2ObjectConcurrentHashMap.MapReduceValuesToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static class Node implements Double2ObjectConcurrentHashMap.Entry { + public final double EMPTY; + public final int hash; + public final double key; + public volatile V val; + public volatile Double2ObjectConcurrentHashMap.Node next; + + public Node(double empty, int hash, double key, V val, Double2ObjectConcurrentHashMap.Node next) { + this.EMPTY = empty; + this.hash = hash; + this.key = key; + this.val = val; + this.next = next; + } + + @Override + public final boolean isEmpty() { + return this.key == this.EMPTY; + } + + @Override + public final Double getKey() { + return this.key; + } + + @Override + public final double getDoubleKey() { + return this.key; + } + + @Override + public final V getValue() { + return this.val; + } + + @Override + public final int hashCode() { + return Double.hashCode(this.key) ^ this.val.hashCode(); + } + + @Override + public final String toString() { + return this.isEmpty() ? "EMPTY=" + this.val : this.key + "=" + this.val; + } + + @Override + public final V setValue(V value) { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean equals(Object o) { + boolean empty = this.isEmpty(); + if (o instanceof Double2ObjectConcurrentHashMap.Entry) { + if (empty != ((Double2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + return false; + } else { + return !empty && this.key != ((Double2ObjectConcurrentHashMap.Entry)o).getDoubleKey() + ? false + : this.val.equals(((Double2ObjectConcurrentHashMap.Entry)o).getValue()); + } + } else { + return false; + } + } + + protected Double2ObjectConcurrentHashMap.Node find(int h, double k) { + Double2ObjectConcurrentHashMap.Node e = this; + if (k != this.EMPTY) { + do { + if (e.hash == h) { + double ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + } while ((e = e.next) != null); + } + + return null; + } + } + + protected static final class ReduceEntriesTask extends Double2ObjectConcurrentHashMap.BulkTask> { + public final BiFunction, Double2ObjectConcurrentHashMap.Entry, ? extends Double2ObjectConcurrentHashMap.Entry> reducer; + public Double2ObjectConcurrentHashMap.Entry result; + public Double2ObjectConcurrentHashMap.ReduceEntriesTask rights; + public Double2ObjectConcurrentHashMap.ReduceEntriesTask nextRight; + + public ReduceEntriesTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.ReduceEntriesTask nextRight, + BiFunction, Double2ObjectConcurrentHashMap.Entry, ? extends Double2ObjectConcurrentHashMap.Entry> reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.reducer = reducer; + } + + public final Double2ObjectConcurrentHashMap.Entry getRawResult() { + return this.result; + } + + @Override + public final void compute() { + BiFunction, Double2ObjectConcurrentHashMap.Entry, ? extends Double2ObjectConcurrentHashMap.Entry> reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.ReduceEntriesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + Double2ObjectConcurrentHashMap.Entry r = null; + + Double2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + r = (Double2ObjectConcurrentHashMap.Entry)(r == null ? p : reducer.apply(r, p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.ReduceEntriesTask t = (Double2ObjectConcurrentHashMap.ReduceEntriesTask)c; + + for (Double2ObjectConcurrentHashMap.ReduceEntriesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + Double2ObjectConcurrentHashMap.Entry sr = s.result; + if (s.result != null) { + Double2ObjectConcurrentHashMap.Entry tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + + protected static final class ReduceKeysTask extends Double2ObjectConcurrentHashMap.DoubleReturningBulkTask2 { + public final double EMPTY; + public final Double2ObjectConcurrentHashMap.DoubleReduceTaskOperator reducer; + public Double2ObjectConcurrentHashMap.ReduceKeysTask rights; + public Double2ObjectConcurrentHashMap.ReduceKeysTask nextRight; + + public ReduceKeysTask( + double EMPTY, + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.ReduceKeysTask nextRight, + Double2ObjectConcurrentHashMap.DoubleReduceTaskOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.EMPTY = EMPTY; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Double2ObjectConcurrentHashMap.DoubleReduceTaskOperator reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.ReduceKeysTask<>( + this.EMPTY, this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + boolean found = false; + double r = this.EMPTY; + + Double2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + double u = p.key; + if (!found) { + found = true; + r = u; + } else if (!p.isEmpty()) { + found = true; + r = reducer.reduce(this.EMPTY, r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.ReduceKeysTask t = (Double2ObjectConcurrentHashMap.ReduceKeysTask)c; + + for (Double2ObjectConcurrentHashMap.ReduceKeysTask s = t.rights; s != null; s = t.rights = s.nextRight) { + double sr = s.result; + if (s.result != this.EMPTY) { + double tr = t.result; + t.result = t.result == this.EMPTY ? sr : reducer.reduce(this.EMPTY, tr, sr); + } + } + } + } + } + } + + protected static final class ReduceValuesTask extends Double2ObjectConcurrentHashMap.BulkTask { + public final BiFunction reducer; + public V result; + public Double2ObjectConcurrentHashMap.ReduceValuesTask rights; + public Double2ObjectConcurrentHashMap.ReduceValuesTask nextRight; + + public ReduceValuesTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.ReduceValuesTask nextRight, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.reducer = reducer; + } + + @Override + public final V getRawResult() { + return this.result; + } + + @Override + public final void compute() { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Double2ObjectConcurrentHashMap.ReduceValuesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + V r = null; + + Double2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + V v = p.val; + r = r == null ? v : reducer.apply(r, v); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Double2ObjectConcurrentHashMap.ReduceValuesTask t = (Double2ObjectConcurrentHashMap.ReduceValuesTask)c; + + for (Double2ObjectConcurrentHashMap.ReduceValuesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + V sr = s.result; + if (s.result != null) { + V tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + + protected static final class ReservationNode extends Double2ObjectConcurrentHashMap.Node { + public ReservationNode(double empty) { + super(empty, -3, empty, null, null); + } + + @Override + protected Double2ObjectConcurrentHashMap.Node find(int h, double k) { + return null; + } + } + + protected static final class SearchEntriesTask extends Double2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> searchFunction; + public final AtomicReference result; + + public SearchEntriesTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Function, ? extends U> searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Function, ? extends U> searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Double2ObjectConcurrentHashMap.SearchEntriesTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result) + .fork(); + } + + while (result.get() == null) { + Double2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + + return; + } + } + } + } + } + } + + protected static final class SearchKeysTask extends Double2ObjectConcurrentHashMap.BulkTask { + public final Double2ObjectConcurrentHashMap.DoubleFunction searchFunction; + public final AtomicReference result; + + public SearchKeysTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.DoubleFunction searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Double2ObjectConcurrentHashMap.DoubleFunction searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Double2ObjectConcurrentHashMap.SearchKeysTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Double2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.key)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static final class SearchMappingsTask extends Double2ObjectConcurrentHashMap.BulkTask { + public final Double2ObjectConcurrentHashMap.DoubleObjFunction searchFunction; + public final AtomicReference result; + + public SearchMappingsTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Double2ObjectConcurrentHashMap.DoubleObjFunction searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Double2ObjectConcurrentHashMap.DoubleObjFunction searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Double2ObjectConcurrentHashMap.SearchMappingsTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result) + .fork(); + } + + while (result.get() == null) { + Double2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.key, p.val)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static final class SearchValuesTask extends Double2ObjectConcurrentHashMap.BulkTask { + public final Function searchFunction; + public final AtomicReference result; + + public SearchValuesTask( + Double2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Double2ObjectConcurrentHashMap.Node[] t, + Function searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Function searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Double2ObjectConcurrentHashMap.SearchValuesTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result) + .fork(); + } + + while (result.get() == null) { + Double2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.val)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static class Segment extends ReentrantLock implements Serializable { + public static final long serialVersionUID = 2249069246763182397L; + public final float loadFactor; + + public Segment(float lf) { + this.loadFactor = lf; + } + } + + protected static final class TableStack { + public int length; + public int index; + public Double2ObjectConcurrentHashMap.Node[] tab; + public Double2ObjectConcurrentHashMap.TableStack next; + + public TableStack() { + } + } + + @FunctionalInterface + public interface ToDoubleDoubleObjFunction { + double applyAsDouble(double var1, V var3); + } + + @FunctionalInterface + public interface ToDoubleFunction { + double applyAsDouble(T var1); + } + + @FunctionalInterface + public interface ToIntDoubleObjFunction { + int applyAsInt(double var1, V var3); + } + + @FunctionalInterface + public interface ToLongDoubleObjFunction { + long applyAsLong(double var1, V var3); + } + + protected static class Traverser { + public Double2ObjectConcurrentHashMap.Node[] tab; + public Double2ObjectConcurrentHashMap.Node next; + public Double2ObjectConcurrentHashMap.TableStack stack; + public Double2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + + public Traverser(Double2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit) { + this.tab = tab; + this.baseSize = size; + this.baseIndex = this.index = index; + this.baseLimit = limit; + this.next = null; + } + + protected final Double2ObjectConcurrentHashMap.Node advance() { + Double2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Double2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Double2ObjectConcurrentHashMap.TreeBin) { + e = ((Double2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Double2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Double2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Double2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + } + + protected static final class TreeBin extends Double2ObjectConcurrentHashMap.Node { + public Double2ObjectConcurrentHashMap.TreeNode root; + public volatile Double2ObjectConcurrentHashMap.TreeNode first; + public volatile Thread waiter; + public volatile int lockState; + public static final int WRITER = 1; + public static final int WAITER = 2; + public static final int READER = 4; + protected static final Unsafe U; + protected static final long LOCKSTATE; + + protected int tieBreakOrder(double a, double b) { + int comp = Double.compare(a, b); + return comp > 0 ? 1 : -1; + } + + public TreeBin(double empty, Double2ObjectConcurrentHashMap.TreeNode b) { + super(empty, -2, empty, null, null); + this.first = b; + Double2ObjectConcurrentHashMap.TreeNode r = null; + Double2ObjectConcurrentHashMap.TreeNode x = b; + + while (x != null) { + Double2ObjectConcurrentHashMap.TreeNode next = (Double2ObjectConcurrentHashMap.TreeNode)x.next; + x.left = x.right = null; + if (r == null) { + x.parent = null; + x.red = false; + r = x; + } else { + double k = x.key; + int h = x.hash; + Class kc = null; + Double2ObjectConcurrentHashMap.TreeNode p = r; + + int dir; + Double2ObjectConcurrentHashMap.TreeNode xp; + do { + double pk = p.key; + int ph = p.hash; + if (p.hash > h) { + dir = -1; + } else if (ph < h) { + dir = 1; + } else if ((dir = Double.compare(k, pk)) == 0) { + dir = this.tieBreakOrder(k, pk); + } + + xp = p; + } while ((p = dir <= 0 ? p.left : p.right) != null); + + x.parent = xp; + if (dir <= 0) { + xp.left = x; + } else { + xp.right = x; + } + + r = this.balanceInsertion(r, x); + } + + x = next; + } + + this.root = r; + + assert this.checkInvariants(this.root); + } + + protected final void lockRoot() { + if (!U.compareAndSwapInt(this, LOCKSTATE, 0, 1)) { + this.contendedLock(); + } + } + + protected final void unlockRoot() { + this.lockState = 0; + } + + protected final void contendedLock() { + boolean waiting = false; + + while (true) { + int s = this.lockState; + if ((this.lockState & -3) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, 1)) { + if (waiting) { + this.waiter = null; + } + + return; + } + } else if ((s & 2) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, s | 2)) { + waiting = true; + this.waiter = Thread.currentThread(); + } + } else if (waiting) { + LockSupport.park(this); + } + } + } + + @Override + protected final Double2ObjectConcurrentHashMap.Node find(int h, double k) { + if (k != this.EMPTY) { + Double2ObjectConcurrentHashMap.Node e = this.first; + + while (e != null) { + int s = this.lockState; + if ((this.lockState & 3) != 0) { + if (e.hash == h) { + double ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + + e = e.next; + } else if (U.compareAndSwapInt(this, LOCKSTATE, s, s + 4)) { + Double2ObjectConcurrentHashMap.TreeNode p; + try { + Double2ObjectConcurrentHashMap.TreeNode r = this.root; + p = this.root == null ? null : r.findTreeNode(h, k, null); + } finally { + if (U.getAndAddInt(this, LOCKSTATE, -4) == 6) { + Thread w = this.waiter; + if (this.waiter != null) { + LockSupport.unpark(w); + } + } + } + + return p; + } + } + } + + return null; + } + + protected final Double2ObjectConcurrentHashMap.TreeNode putTreeVal(int h, double k, V v) { + Class kc = null; + boolean searched = false; + Double2ObjectConcurrentHashMap.TreeNode p = this.root; + + while (true) { + if (p == null) { + this.first = this.root = new Double2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, h, k, v, null, null); + } else { + int ph = p.hash; + int dir; + if (p.hash > h) { + dir = -1; + } else if (ph < h) { + dir = 1; + } else { + double pk = p.key; + if (p.key == k || pk != this.EMPTY && k == pk) { + return p; + } + + if ((dir = Double.compare(k, pk)) == 0) { + if (!searched) { + searched = true; + Double2ObjectConcurrentHashMap.TreeNode ch = p.left; + Double2ObjectConcurrentHashMap.TreeNode q; + if (p.left != null && (q = ch.findTreeNode(h, k, kc)) != null) { + return q; + } + + ch = p.right; + if (p.right != null && (q = ch.findTreeNode(h, k, kc)) != null) { + return q; + } + } + + dir = this.tieBreakOrder(k, pk); + } + } + + Double2ObjectConcurrentHashMap.TreeNode xp = p; + if ((p = dir <= 0 ? p.left : p.right) != null) { + continue; + } + + Double2ObjectConcurrentHashMap.TreeNode f = this.first; + Double2ObjectConcurrentHashMap.TreeNode x; + this.first = x = new Double2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, h, k, v, f, xp); + if (f != null) { + f.prev = x; + } + + if (dir <= 0) { + xp.left = x; + } else { + xp.right = x; + } + + if (!xp.red) { + x.red = true; + } else { + this.lockRoot(); + + try { + this.root = this.balanceInsertion(this.root, x); + } finally { + this.unlockRoot(); + } + } + } + + assert this.checkInvariants(this.root); + + return null; + } + } + + protected final boolean removeTreeNode(Double2ObjectConcurrentHashMap.TreeNode p) { + Double2ObjectConcurrentHashMap.TreeNode next = (Double2ObjectConcurrentHashMap.TreeNode)p.next; + Double2ObjectConcurrentHashMap.TreeNode pred = p.prev; + if (pred == null) { + this.first = next; + } else { + pred.next = next; + } + + if (next != null) { + next.prev = pred; + } + + if (this.first == null) { + this.root = null; + return true; + } else { + Double2ObjectConcurrentHashMap.TreeNode r = this.root; + if (this.root != null && r.right != null) { + Double2ObjectConcurrentHashMap.TreeNode rl = r.left; + if (r.left != null && rl.left != null) { + this.lockRoot(); + + try { + Double2ObjectConcurrentHashMap.TreeNode pl = p.left; + Double2ObjectConcurrentHashMap.TreeNode pr = p.right; + Double2ObjectConcurrentHashMap.TreeNode replacement; + if (pl != null && pr != null) { + Double2ObjectConcurrentHashMap.TreeNode s = pr; + + while (true) { + Double2ObjectConcurrentHashMap.TreeNode sl = s.left; + if (s.left == null) { + boolean c = s.red; + s.red = p.red; + p.red = c; + Double2ObjectConcurrentHashMap.TreeNode sr = s.right; + Double2ObjectConcurrentHashMap.TreeNode pp = p.parent; + if (s == pr) { + p.parent = s; + s.right = p; + } else { + Double2ObjectConcurrentHashMap.TreeNode sp = s.parent; + if ((p.parent = sp) != null) { + if (s == sp.left) { + sp.left = p; + } else { + sp.right = p; + } + } + + if ((s.right = pr) != null) { + pr.parent = s; + } + } + + p.left = null; + if ((p.right = sr) != null) { + sr.parent = p; + } + + if ((s.left = pl) != null) { + pl.parent = s; + } + + if ((s.parent = pp) == null) { + r = s; + } else if (p == pp.left) { + pp.left = s; + } else { + pp.right = s; + } + + if (sr != null) { + replacement = sr; + } else { + replacement = p; + } + break; + } + + s = sl; + } + } else if (pl != null) { + replacement = pl; + } else if (pr != null) { + replacement = pr; + } else { + replacement = p; + } + + if (replacement != p) { + Double2ObjectConcurrentHashMap.TreeNode ppx = replacement.parent = p.parent; + if (ppx == null) { + r = replacement; + } else if (p == ppx.left) { + ppx.left = replacement; + } else { + ppx.right = replacement; + } + + p.left = p.right = p.parent = null; + } + + this.root = p.red ? r : this.balanceDeletion(r, replacement); + if (p == replacement) { + Double2ObjectConcurrentHashMap.TreeNode ppx = p.parent; + if (p.parent != null) { + if (p == ppx.left) { + ppx.left = null; + } else if (p == ppx.right) { + ppx.right = null; + } + + p.parent = null; + } + } + } finally { + this.unlockRoot(); + } + + assert this.checkInvariants(this.root); + + return false; + } + } + + return true; + } + } + + protected Double2ObjectConcurrentHashMap.TreeNode rotateLeft( + Double2ObjectConcurrentHashMap.TreeNode root, Double2ObjectConcurrentHashMap.TreeNode p + ) { + if (p != null) { + Double2ObjectConcurrentHashMap.TreeNode r = p.right; + if (p.right != null) { + Double2ObjectConcurrentHashMap.TreeNode rl; + if ((rl = p.right = r.left) != null) { + rl.parent = p; + } + + Double2ObjectConcurrentHashMap.TreeNode pp; + if ((pp = r.parent = p.parent) == null) { + root = r; + r.red = false; + } else if (pp.left == p) { + pp.left = r; + } else { + pp.right = r; + } + + r.left = p; + p.parent = r; + } + } + + return root; + } + + protected Double2ObjectConcurrentHashMap.TreeNode rotateRight( + Double2ObjectConcurrentHashMap.TreeNode root, Double2ObjectConcurrentHashMap.TreeNode p + ) { + if (p != null) { + Double2ObjectConcurrentHashMap.TreeNode l = p.left; + if (p.left != null) { + Double2ObjectConcurrentHashMap.TreeNode lr; + if ((lr = p.left = l.right) != null) { + lr.parent = p; + } + + Double2ObjectConcurrentHashMap.TreeNode pp; + if ((pp = l.parent = p.parent) == null) { + root = l; + l.red = false; + } else if (pp.right == p) { + pp.right = l; + } else { + pp.left = l; + } + + l.right = p; + p.parent = l; + } + } + + return root; + } + + protected Double2ObjectConcurrentHashMap.TreeNode balanceInsertion( + Double2ObjectConcurrentHashMap.TreeNode root, Double2ObjectConcurrentHashMap.TreeNode x + ) { + x.red = true; + + while (true) { + Double2ObjectConcurrentHashMap.TreeNode xp = x.parent; + if (x.parent == null) { + x.red = false; + return x; + } + + if (!xp.red) { + break; + } + + Double2ObjectConcurrentHashMap.TreeNode xpp = xp.parent; + if (xp.parent == null) { + break; + } + + Double2ObjectConcurrentHashMap.TreeNode xppl = xpp.left; + if (xp == xpp.left) { + Double2ObjectConcurrentHashMap.TreeNode xppr = xpp.right; + if (xpp.right != null && xppr.red) { + xppr.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } else { + if (x == xp.right) { + x = xp; + root = this.rotateLeft(root, xp); + xpp = (xp = xp.parent) == null ? null : xp.parent; + } + + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = this.rotateRight(root, xpp); + } + } + } + } else if (xppl != null && xppl.red) { + xppl.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } else { + if (x == xp.left) { + x = xp; + root = this.rotateRight(root, xp); + xpp = (xp = xp.parent) == null ? null : xp.parent; + } + + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = this.rotateLeft(root, xpp); + } + } + } + } + + return root; + } + + protected Double2ObjectConcurrentHashMap.TreeNode balanceDeletion( + Double2ObjectConcurrentHashMap.TreeNode root, Double2ObjectConcurrentHashMap.TreeNode x + ) { + while (x != null && x != root) { + Double2ObjectConcurrentHashMap.TreeNode xp = x.parent; + if (x.parent == null) { + x.red = false; + return x; + } + + if (x.red) { + x.red = false; + return root; + } + + Double2ObjectConcurrentHashMap.TreeNode xpl = xp.left; + if (xp.left == x) { + Double2ObjectConcurrentHashMap.TreeNode xpr = xp.right; + if (xp.right != null && xpr.red) { + xpr.red = false; + xp.red = true; + root = this.rotateLeft(root, xp); + xp = x.parent; + xpr = x.parent == null ? null : xp.right; + } + + if (xpr == null) { + x = xp; + } else { + Double2ObjectConcurrentHashMap.TreeNode sl = xpr.left; + Double2ObjectConcurrentHashMap.TreeNode sr = xpr.right; + if (sr != null && sr.red || sl != null && sl.red) { + if (sr == null || !sr.red) { + if (sl != null) { + sl.red = false; + } + + xpr.red = true; + root = this.rotateRight(root, xpr); + xp = x.parent; + xpr = x.parent == null ? null : xp.right; + } + + if (xpr != null) { + xpr.red = xp == null ? false : xp.red; + sr = xpr.right; + if (xpr.right != null) { + sr.red = false; + } + } + + if (xp != null) { + xp.red = false; + root = this.rotateLeft(root, xp); + } + + x = root; + } else { + xpr.red = true; + x = xp; + } + } + } else { + if (xpl != null && xpl.red) { + xpl.red = false; + xp.red = true; + root = this.rotateRight(root, xp); + xp = x.parent; + xpl = x.parent == null ? null : xp.left; + } + + if (xpl == null) { + x = xp; + } else { + Double2ObjectConcurrentHashMap.TreeNode sl = xpl.left; + Double2ObjectConcurrentHashMap.TreeNode sr = xpl.right; + if (sl != null && sl.red || sr != null && sr.red) { + if (sl == null || !sl.red) { + if (sr != null) { + sr.red = false; + } + + xpl.red = true; + root = this.rotateLeft(root, xpl); + xp = x.parent; + xpl = x.parent == null ? null : xp.left; + } + + if (xpl != null) { + xpl.red = xp == null ? false : xp.red; + sl = xpl.left; + if (xpl.left != null) { + sl.red = false; + } + } + + if (xp != null) { + xp.red = false; + root = this.rotateRight(root, xp); + } + + x = root; + } else { + xpl.red = true; + x = xp; + } + } + } + } + + return root; + } + + protected boolean checkInvariants(Double2ObjectConcurrentHashMap.TreeNode t) { + Double2ObjectConcurrentHashMap.TreeNode tp = t.parent; + Double2ObjectConcurrentHashMap.TreeNode tl = t.left; + Double2ObjectConcurrentHashMap.TreeNode tr = t.right; + Double2ObjectConcurrentHashMap.TreeNode tb = t.prev; + Double2ObjectConcurrentHashMap.TreeNode tn = (Double2ObjectConcurrentHashMap.TreeNode)t.next; + if (tb != null && tb.next != t) { + return false; + } else if (tn != null && tn.prev != t) { + return false; + } else if (tp != null && t != tp.left && t != tp.right) { + return false; + } else if (tl == null || tl.parent == t && tl.hash <= t.hash) { + if (tr == null || tr.parent == t && tr.hash >= t.hash) { + if (t.red && tl != null && tl.red && tr != null && tr.red) { + return false; + } else { + return tl != null && !this.checkInvariants(tl) ? false : tr == null || this.checkInvariants(tr); + } + } else { + return false; + } + } else { + return false; + } + } + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + U = (Unsafe)f.get(null); + Class k = Double2ObjectConcurrentHashMap.TreeBin.class; + LOCKSTATE = U.objectFieldOffset(k.getDeclaredField("lockState")); + } catch (Exception var2) { + throw new Error(var2); + } + } + } + + protected static final class TreeNode extends Double2ObjectConcurrentHashMap.Node { + public Double2ObjectConcurrentHashMap.TreeNode parent; + public Double2ObjectConcurrentHashMap.TreeNode left; + public Double2ObjectConcurrentHashMap.TreeNode right; + public Double2ObjectConcurrentHashMap.TreeNode prev; + public boolean red; + + public TreeNode(double empty, int hash, double key, V val, Double2ObjectConcurrentHashMap.Node next, Double2ObjectConcurrentHashMap.TreeNode parent) { + super(empty, hash, key, val, next); + this.parent = parent; + } + + @Override + protected Double2ObjectConcurrentHashMap.Node find(int h, double k) { + return this.findTreeNode(h, k, null); + } + + protected final Double2ObjectConcurrentHashMap.TreeNode findTreeNode(int h, double k, Class kc) { + if (k != this.EMPTY) { + Double2ObjectConcurrentHashMap.TreeNode p = this; + + do { + Double2ObjectConcurrentHashMap.TreeNode pl = p.left; + Double2ObjectConcurrentHashMap.TreeNode pr = p.right; + int ph = p.hash; + if (p.hash > h) { + p = pl; + } else if (ph < h) { + p = pr; + } else { + double pk = p.key; + if (p.key == k || pk != this.EMPTY && k == pk) { + return p; + } + + if (pl == null) { + p = pr; + } else if (pr == null) { + p = pl; + } else { + int dir; + if ((dir = Double.compare(k, pk)) != 0) { + p = dir < 0 ? pl : pr; + } else { + Double2ObjectConcurrentHashMap.TreeNode q; + if ((q = pr.findTreeNode(h, k, kc)) != null) { + return q; + } + + p = pl; + } + } + } + } while (p != null); + } + + return null; + } + } + + protected static final class ValueIterator extends Double2ObjectConcurrentHashMap.BaseIterator implements ObjectIterator, Enumeration { + public ValueIterator(Double2ObjectConcurrentHashMap.Node[] tab, int index, int size, int limit, Double2ObjectConcurrentHashMap map) { + super(tab, index, size, limit, map); + } + + @Override + public final V next() { + Double2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + V v = p.val; + this.lastReturned = p; + this.advance(); + return v; + } + } + + @Override + public final V nextElement() { + return this.next(); + } + } + + protected static final class ValueSpliterator extends Double2ObjectConcurrentHashMap.Traverser implements ObjectSpliterator { + public long est; + + public ValueSpliterator(Double2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est) { + super(tab, size, index, limit); + this.est = est; + } + + @Override + public ObjectSpliterator trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Double2ObjectConcurrentHashMap.ValueSpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1); + } + + @Override + public void forEachRemaining(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(p.val); + } + } + } + + @Override + public boolean tryAdvance(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(p.val); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4352; + } + } + + protected static final class ValuesView extends Double2ObjectConcurrentHashMap.CollectionView implements FastCollection, Serializable { + public static final long serialVersionUID = 2249069246763182397L; + + public ValuesView(Double2ObjectConcurrentHashMap map) { + super(map); + } + + @Override + public final boolean contains(Object o) { + return this.map.containsValue(o); + } + + @Override + public final boolean remove(Object o) { + if (o != null) { + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (o.equals(it.next())) { + it.remove(); + return true; + } + } + } + + return false; + } + + @Override + public final ObjectIterator iterator() { + Double2ObjectConcurrentHashMap m = this.map; + Double2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Double2ObjectConcurrentHashMap.ValueIterator<>(t, f, 0, f, m); + } + + @Override + public final boolean add(V e) { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public ObjectSpliterator spliterator() { + Double2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Double2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Double2ObjectConcurrentHashMap.ValueSpliterator<>(t, f, 0, f, n < 0L ? 0L : n); + } + + @Override + public void forEach(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Double2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.val); + } + } + } + } + + @Override + public void forEach( + FastCollection.FastConsumerD9 consumer, + A a, + double d1, + double d2, + double d3, + double d4, + double d5, + double d6, + double d7, + double d8, + double d9, + B b, + C c, + D d + ) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Double2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, a, d1, d2, d3, d4, d5, d6, d7, d8, d9, b, c, d); + } + } + } + } + + @Override + public void forEach( + FastCollection.FastConsumerD6 consumer, A a, double d1, double d2, double d3, double d4, double d5, double d6, B b, C c, D d + ) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Double2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, a, d1, d2, d3, d4, d5, d6, b, c, d); + } + } + } + } + + @Override + public void forEachWithFloat(FastCollection.FastConsumerF consumer, float ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Double2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + + @Override + public void forEachWithInt(FastCollection.FastConsumerI consumer, int ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Double2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + + @Override + public void forEachWithLong(FastCollection.FastConsumerL consumer, long ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Double2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Double2ObjectConcurrentHashMap.Node[] tab = tt; + Double2ObjectConcurrentHashMap.Node next = null; + Double2ObjectConcurrentHashMap.TableStack stack = null; + Double2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Double2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Double2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Double2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Double2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Double2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Double2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Double2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Double2ObjectConcurrentHashMap.TreeBin) { + p = ((Double2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Double2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Double2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + } +} diff --git a/src/com/hypixel/fastutil/doubles/Double2ObjectOperator.java b/src/com/hypixel/fastutil/doubles/Double2ObjectOperator.java new file mode 100644 index 0000000..671b88c --- /dev/null +++ b/src/com/hypixel/fastutil/doubles/Double2ObjectOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.doubles; + +@FunctionalInterface +public interface Double2ObjectOperator { + V apply(double var1, V var3); +} diff --git a/src/com/hypixel/fastutil/doubles/Double2ShortOperator.java b/src/com/hypixel/fastutil/doubles/Double2ShortOperator.java new file mode 100644 index 0000000..0f080a2 --- /dev/null +++ b/src/com/hypixel/fastutil/doubles/Double2ShortOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.doubles; + +@FunctionalInterface +public interface Double2ShortOperator { + short apply(double var1, short var3); +} diff --git a/src/com/hypixel/fastutil/floats/Float2ByteOperator.java b/src/com/hypixel/fastutil/floats/Float2ByteOperator.java new file mode 100644 index 0000000..247d758 --- /dev/null +++ b/src/com/hypixel/fastutil/floats/Float2ByteOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.floats; + +@FunctionalInterface +public interface Float2ByteOperator { + byte apply(float var1, byte var2); +} diff --git a/src/com/hypixel/fastutil/floats/Float2CharOperator.java b/src/com/hypixel/fastutil/floats/Float2CharOperator.java new file mode 100644 index 0000000..2c1cbc6 --- /dev/null +++ b/src/com/hypixel/fastutil/floats/Float2CharOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.floats; + +@FunctionalInterface +public interface Float2CharOperator { + char apply(float var1, char var2); +} diff --git a/src/com/hypixel/fastutil/floats/Float2DoubleOperator.java b/src/com/hypixel/fastutil/floats/Float2DoubleOperator.java new file mode 100644 index 0000000..4c8a102 --- /dev/null +++ b/src/com/hypixel/fastutil/floats/Float2DoubleOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.floats; + +@FunctionalInterface +public interface Float2DoubleOperator { + double apply(float var1, double var2); +} diff --git a/src/com/hypixel/fastutil/floats/Float2FloatOperator.java b/src/com/hypixel/fastutil/floats/Float2FloatOperator.java new file mode 100644 index 0000000..05feafd --- /dev/null +++ b/src/com/hypixel/fastutil/floats/Float2FloatOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.floats; + +@FunctionalInterface +public interface Float2FloatOperator { + float apply(float var1, float var2); +} diff --git a/src/com/hypixel/fastutil/floats/Float2IntOperator.java b/src/com/hypixel/fastutil/floats/Float2IntOperator.java new file mode 100644 index 0000000..84caf10 --- /dev/null +++ b/src/com/hypixel/fastutil/floats/Float2IntOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.floats; + +@FunctionalInterface +public interface Float2IntOperator { + int apply(float var1, int var2); +} diff --git a/src/com/hypixel/fastutil/floats/Float2LongOperator.java b/src/com/hypixel/fastutil/floats/Float2LongOperator.java new file mode 100644 index 0000000..82fa4d2 --- /dev/null +++ b/src/com/hypixel/fastutil/floats/Float2LongOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.floats; + +@FunctionalInterface +public interface Float2LongOperator { + long apply(float var1, long var2); +} diff --git a/src/com/hypixel/fastutil/floats/Float2ObjectConcurrentHashMap.java b/src/com/hypixel/fastutil/floats/Float2ObjectConcurrentHashMap.java new file mode 100644 index 0000000..30b8a39 --- /dev/null +++ b/src/com/hypixel/fastutil/floats/Float2ObjectConcurrentHashMap.java @@ -0,0 +1,10267 @@ +package com.hypixel.fastutil.floats; + +import com.hypixel.fastutil.FastCollection; +import com.hypixel.fastutil.util.SneakyThrow; +import com.hypixel.fastutil.util.TLRUtil; +import it.unimi.dsi.fastutil.floats.Float2ObjectMap; +import it.unimi.dsi.fastutil.floats.FloatCollection; +import it.unimi.dsi.fastutil.floats.FloatConsumer; +import it.unimi.dsi.fastutil.floats.FloatIterator; +import it.unimi.dsi.fastutil.floats.FloatSet; +import it.unimi.dsi.fastutil.floats.FloatSpliterator; +import it.unimi.dsi.fastutil.objects.ObjectCollection; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import it.unimi.dsi.fastutil.objects.ObjectSpliterator; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.CountedCompleter; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.DoubleBinaryOperator; +import java.util.function.Function; +import java.util.function.IntBinaryOperator; +import java.util.function.LongBinaryOperator; +import java.util.function.Predicate; +import java.util.function.ToDoubleFunction; +import java.util.function.ToIntFunction; +import java.util.function.ToLongFunction; +import sun.misc.Unsafe; + +public class Float2ObjectConcurrentHashMap { + protected static final long serialVersionUID = 7249069246763182397L; + protected static final int MAXIMUM_CAPACITY = 1073741824; + protected static final int DEFAULT_CAPACITY = 16; + protected static final int MAX_ARRAY_SIZE = 2147483639; + protected static final int DEFAULT_CONCURRENCY_LEVEL = 16; + protected static final float LOAD_FACTOR = 0.75F; + protected static final int TREEIFY_THRESHOLD = 8; + protected static final int UNTREEIFY_THRESHOLD = 6; + protected static final int MIN_TREEIFY_CAPACITY = 64; + protected static final int MIN_TRANSFER_STRIDE = 16; + protected static int RESIZE_STAMP_BITS = 16; + protected static final int MAX_RESIZERS = (1 << 32 - RESIZE_STAMP_BITS) - 1; + protected static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS; + protected static final int MOVED = -1; + protected static final int TREEBIN = -2; + protected static final int RESERVED = -3; + protected static final int HASH_BITS = Integer.MAX_VALUE; + protected static final int NCPU = Runtime.getRuntime().availableProcessors(); + protected transient volatile Float2ObjectConcurrentHashMap.Node[] table; + protected transient volatile Float2ObjectConcurrentHashMap.Node[] nextTable; + protected transient volatile long baseCount; + protected transient volatile int sizeCtl; + protected transient volatile int transferIndex; + protected transient volatile int cellsBusy; + protected transient volatile Float2ObjectConcurrentHashMap.CounterCell[] counterCells; + protected transient Float2ObjectConcurrentHashMap.KeySetView keySet; + protected transient Float2ObjectConcurrentHashMap.ValuesView values; + protected transient Float2ObjectConcurrentHashMap.EntrySetView entrySet; + protected final float EMPTY; + protected static final Unsafe U; + protected static final long SIZECTL; + protected static final long TRANSFERINDEX; + protected static final long BASECOUNT; + protected static final long CELLSBUSY; + protected static final long CELLVALUE; + protected static final long ABASE; + protected static final int ASHIFT; + + protected static final int spread(int h) { + return (h ^ h >>> 16) & 2147483647; + } + + protected static final int tableSizeFor(int c) { + int n = c - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return n < 0 ? 1 : (n >= 1073741824 ? 1073741824 : n + 1); + } + + protected static final Float2ObjectConcurrentHashMap.Node tabAt(Float2ObjectConcurrentHashMap.Node[] tab, int i) { + return (Float2ObjectConcurrentHashMap.Node)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); + } + + protected static final boolean casTabAt( + Float2ObjectConcurrentHashMap.Node[] tab, int i, Float2ObjectConcurrentHashMap.Node c, Float2ObjectConcurrentHashMap.Node v + ) { + return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v); + } + + protected static final void setTabAt(Float2ObjectConcurrentHashMap.Node[] tab, int i, Float2ObjectConcurrentHashMap.Node v) { + U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v); + } + + public Float2ObjectConcurrentHashMap() { + this.EMPTY = -1.0F; + } + + public Float2ObjectConcurrentHashMap(boolean nonce, float emptyValue) { + this.EMPTY = emptyValue; + } + + public Float2ObjectConcurrentHashMap(int initialCapacity) { + this(initialCapacity, true, -1.0F); + } + + public Float2ObjectConcurrentHashMap(int initialCapacity, boolean nonce, float emptyValue) { + if (initialCapacity < 0) { + throw new IllegalArgumentException(); + } else { + int cap = initialCapacity >= 536870912 ? 1073741824 : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1); + this.sizeCtl = cap; + this.EMPTY = emptyValue; + } + } + + public Float2ObjectConcurrentHashMap(Map m, float emptyValue) { + this.sizeCtl = 16; + this.EMPTY = emptyValue; + this.putAll(m); + } + + public Float2ObjectConcurrentHashMap(Float2ObjectConcurrentHashMap m) { + this.sizeCtl = 16; + this.EMPTY = m.EMPTY; + this.putAll(m); + } + + public Float2ObjectConcurrentHashMap(Float2ObjectMap m) { + this.sizeCtl = 16; + this.EMPTY = -1.0F; + this.putAll(m); + } + + public Float2ObjectConcurrentHashMap(Float2ObjectMap m, float emptyValue) { + this.sizeCtl = 16; + this.EMPTY = emptyValue; + this.putAll(m); + } + + public Float2ObjectConcurrentHashMap(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, 1, -1.0F); + } + + public Float2ObjectConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel, float emptyValue) { + if (loadFactor > 0.0F && initialCapacity >= 0 && concurrencyLevel > 0) { + if (initialCapacity < concurrencyLevel) { + initialCapacity = concurrencyLevel; + } + + long size = (long)(1.0 + (float)initialCapacity / loadFactor); + int cap = size >= 1073741824L ? 1073741824 : tableSizeFor((int)size); + this.sizeCtl = cap; + this.EMPTY = emptyValue; + } else { + throw new IllegalArgumentException(); + } + } + + public int size() { + long n = this.sumCount(); + return n < 0L ? 0 : (n > 2147483647L ? Integer.MAX_VALUE : (int)n); + } + + public boolean isEmpty() { + return this.sumCount() <= 0L; + } + + public V get(float key) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + int h = spread(Float.hashCode(key)); + Float2ObjectConcurrentHashMap.Node[] tab = this.table; + Float2ObjectConcurrentHashMap.Node e; + int n; + if (this.table != null && (n = tab.length) > 0 && (e = tabAt(tab, n - 1 & h)) != null) { + int eh = e.hash; + if (e.hash == h) { + float ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + return e.val; + } + } else if (eh < 0) { + Float2ObjectConcurrentHashMap.Node p; + return (p = e.find(h, key)) != null ? p.val : null; + } + + while ((e = e.next) != null) { + if (e.hash == h) { + float ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + return e.val; + } + } + } + } + + return null; + } + } + + public boolean containsKey(float key) { + return this.get(key) != null; + } + + public boolean containsValue(Object value) { + if (value == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label85: + while (true) { + if (p != null) { + next = p; + break; + } + + if (baseIndex < baseLimit) { + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab != null && (n = tab.length) > index && index >= 0) { + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + continue; + } + + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + continue label85; + } + } + } + + next = null; + break; + } + + if (p == null) { + break; + } + + V v = p.val; + if (p.val == value || v != null && value.equals(v)) { + return true; + } + } + } + + return false; + } + } + + public V put(float key, V value) { + return this.putVal(key, value, false); + } + + protected final V putVal(float key, V value, boolean onlyIfAbsent) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value == null) { + throw new NullPointerException(); + } else { + int hash = spread(Float.hashCode(key)); + int binCount = 0; + Float2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Float2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & hash)) == null) { + if (casTabAt(tab, i, null, new Float2ObjectConcurrentHashMap.Node<>(this.EMPTY, hash, key, value, null))) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + V oldVal = null; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Float2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Float2ObjectConcurrentHashMap.Node p; + if ((p = ((Float2ObjectConcurrentHashMap.TreeBin)f).putTreeVal(hash, key, value)) != null) { + oldVal = p.val; + if (!onlyIfAbsent) { + p.val = value; + } + } + } + } else { + binCount = 1; + Float2ObjectConcurrentHashMap.Node e = f; + + while (true) { + if (e.hash == hash) { + float ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + oldVal = e.val; + if (!onlyIfAbsent) { + e.val = value; + } + break; + } + } + + Float2ObjectConcurrentHashMap.Node pred = e; + if ((e = e.next) == null) { + pred.next = new Float2ObjectConcurrentHashMap.Node<>(this.EMPTY, hash, key, value, null); + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + + if (oldVal != null) { + return oldVal; + } + break; + } + } + } + } + + this.addCount(1L, binCount); + return null; + } + } + + public void putAll(Map m) { + this.tryPresize(m.size()); + + for (Map.Entry e : m.entrySet()) { + this.putVal(e.getKey(), (V)e.getValue(), false); + } + } + + public void putAll(Float2ObjectConcurrentHashMap m) { + this.tryPresize(m.size()); + + for (Float2ObjectMap.Entry e : m.float2ObjectEntrySet()) { + this.putVal(e.getFloatKey(), (V)e.getValue(), false); + } + } + + public void putAll(Float2ObjectMap m) { + this.tryPresize(m.size()); + + for (Float2ObjectMap.Entry next : m.float2ObjectEntrySet()) { + this.putVal(next.getFloatKey(), (V)next.getValue(), false); + } + } + + public V remove(float key) { + return this.replaceNode(key, null, null); + } + + @Deprecated + public V remove(Float key) { + return this.replaceNode(key, null, null); + } + + @Deprecated + public V remove(Object key) { + return this.remove((Float)key); + } + + protected final V replaceNode(float key, V value, Object cv) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + int hash = spread(Float.hashCode(key)); + Float2ObjectConcurrentHashMap.Node[] tab = this.table; + + Float2ObjectConcurrentHashMap.Node f; + int n; + int i; + while (tab != null && (n = tab.length) != 0 && (f = tabAt(tab, i = n - 1 & hash)) != null) { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + V oldVal = null; + boolean validated = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Float2ObjectConcurrentHashMap.TreeBin) { + validated = true; + Float2ObjectConcurrentHashMap.TreeBin t = (Float2ObjectConcurrentHashMap.TreeBin)f; + Float2ObjectConcurrentHashMap.TreeNode r = t.root; + Float2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(hash, key, null)) != null) { + V pv = p.val; + if (cv == null || cv == pv || pv != null && cv.equals(pv)) { + oldVal = pv; + if (value != null) { + p.val = value; + } else if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } + } else { + validated = true; + Float2ObjectConcurrentHashMap.Node e = f; + Float2ObjectConcurrentHashMap.Node pred = null; + + do { + if (e.hash == hash) { + float ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + V ev = e.val; + if (cv == null || cv == ev || ev != null && cv.equals(ev)) { + oldVal = ev; + if (value != null) { + e.val = value; + } else if (pred != null) { + pred.next = e.next; + } else { + setTabAt(tab, i, e.next); + } + } + break; + } + } + + pred = e; + } while ((e = e.next) != null); + } + } + } + + if (validated) { + if (oldVal != null) { + if (value == null) { + this.addCount(-1L, -1); + } + + return oldVal; + } + break; + } + } + } + + return null; + } + } + + public void clear() { + long delta = 0L; + int i = 0; + Float2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (tab != null && i < tab.length) { + Float2ObjectConcurrentHashMap.Node f = tabAt(tab, i); + if (f == null) { + i++; + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + i = 0; + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + for (Float2ObjectConcurrentHashMap.Node p = (Float2ObjectConcurrentHashMap.Node)(fh >= 0 + ? f + : (f instanceof Float2ObjectConcurrentHashMap.TreeBin ? ((Float2ObjectConcurrentHashMap.TreeBin)f).first : null)); + p != null; + p = p.next + ) { + delta--; + } + + setTabAt(tab, i++, null); + } + } + } + } + } + + if (delta != 0L) { + this.addCount(delta, -1); + } + } + + public Float2ObjectConcurrentHashMap.KeySetView keySet() { + Float2ObjectConcurrentHashMap.KeySetView ks = this.keySet; + return this.keySet != null ? ks : (this.keySet = this.buildKeySetView()); + } + + protected Float2ObjectConcurrentHashMap.KeySetView buildKeySetView() { + return new Float2ObjectConcurrentHashMap.KeySetView<>(this, null); + } + + public FastCollection values() { + Float2ObjectConcurrentHashMap.ValuesView vs = this.values; + return this.values != null ? vs : (this.values = this.buildValuesView()); + } + + protected Float2ObjectConcurrentHashMap.ValuesView buildValuesView() { + return new Float2ObjectConcurrentHashMap.ValuesView<>(this); + } + + public ObjectSet> float2ObjectEntrySet() { + Float2ObjectConcurrentHashMap.EntrySetView es = this.entrySet; + return this.entrySet != null ? es : (this.entrySet = this.buildEntrySetView()); + } + + @Deprecated + public ObjectSet> entrySet() { + return this.float2ObjectEntrySet(); + } + + protected Float2ObjectConcurrentHashMap.EntrySetView buildEntrySetView() { + return new Float2ObjectConcurrentHashMap.EntrySetView<>(this); + } + + @Override + public int hashCode() { + int h = 0; + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label75: { + while (true) { + if (p != null) { + next = p; + break label75; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + h += Float.hashCode(p.key) ^ p.val.hashCode(); + } + } + + return h; + } + + @Override + public String toString() { + Float2ObjectConcurrentHashMap.Node[] t = this.table; + int f = this.table == null ? 0 : t.length; + Float2ObjectConcurrentHashMap.Traverser it = new Float2ObjectConcurrentHashMap.Traverser<>(t, f, 0, f); + StringBuilder sb = new StringBuilder(); + sb.append('{'); + Float2ObjectConcurrentHashMap.Node p; + if ((p = it.advance()) != null) { + while (true) { + float k = p.key; + V v = p.val; + sb.append(k); + sb.append('='); + sb.append(v == this ? "(this Map)" : v); + if ((p = it.advance()) == null) { + break; + } + + sb.append(',').append(' '); + } + } + + return sb.append('}').toString(); + } + + @Override + public boolean equals(Object o) { + if (o != this) { + if (!(o instanceof Float2ObjectConcurrentHashMap m)) { + return false; + } + + Float2ObjectConcurrentHashMap.Node[] t = this.table; + int f = this.table == null ? 0 : t.length; + Float2ObjectConcurrentHashMap.Traverser it = new Float2ObjectConcurrentHashMap.Traverser<>(t, f, 0, f); + + Float2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + V val = p.val; + Object v = m.get(p.key); + if (v == null || v != val && !v.equals(val)) { + return false; + } + } + + for (Float2ObjectMap.Entry e : m.float2ObjectEntrySet()) { + Object mv; + Object v; + float mk; + if ((mk = e.getFloatKey()) == m.EMPTY || (mv = e.getValue()) == null || (v = this.get(mk)) == null || mv != v && !mv.equals(v)) { + return false; + } + } + } + + return true; + } + + public V putIfAbsent(float key, V value) { + return this.putVal(key, value, true); + } + + public boolean remove(float key, Object value) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + return value != null && this.replaceNode(key, null, value) != null; + } + } + + public boolean replace(float key, V oldValue, V newValue) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (oldValue != null && newValue != null) { + return this.replaceNode(key, newValue, oldValue) != null; + } else { + throw new NullPointerException(); + } + } + + public V replace(float key, V value) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value == null) { + throw new NullPointerException(); + } else { + return this.replaceNode(key, value, null); + } + } + + public V getOrDefault(float key, V defaultValue) { + V v; + return (v = this.get(key)) == null ? defaultValue : v; + } + + public int forEach(Float2ObjectConcurrentHashMap.FloatObjConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val); + count++; + } + } + + return count; + } + } + + public int forEach(Float2ObjectConcurrentHashMap.FloatBiObjConsumer action, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, x); + count++; + } + } + + return count; + } + } + + public int forEach(Float2ObjectConcurrentHashMap.FloatTriObjConsumer action, X x, Y y) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, x, y); + count++; + } + } + + return count; + } + } + + public int forEachWithByte(Float2ObjectConcurrentHashMap.FloatObjByteConsumer action, byte ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithShort(Float2ObjectConcurrentHashMap.FloatObjShortConsumer action, short ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithInt(Float2ObjectConcurrentHashMap.FloatObjIntConsumer action, int ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithLong(Float2ObjectConcurrentHashMap.FloatObjLongConsumer action, long ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithFloat(Float2ObjectConcurrentHashMap.FloatObjFloatConsumer action, float ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithDouble(Float2ObjectConcurrentHashMap.FloatObjDoubleConsumer action, double ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithByte(Float2ObjectConcurrentHashMap.FloatBiObjByteConsumer action, byte ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithShort(Float2ObjectConcurrentHashMap.FloatBiObjShortConsumer action, short ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithInt(Float2ObjectConcurrentHashMap.FloatBiObjIntConsumer action, int ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithLong(Float2ObjectConcurrentHashMap.FloatBiObjLongConsumer action, long ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithFloat(Float2ObjectConcurrentHashMap.FloatBiObjFloatConsumer action, float ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithDouble(Float2ObjectConcurrentHashMap.FloatBiObjDoubleConsumer action, double ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public void replaceAll(Float2ObjectOperator function) { + if (function == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label86: { + while (true) { + if (p != null) { + next = p; + break label86; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + V oldValue = p.val; + float key = p.key; + + V newValue; + do { + newValue = function.apply(key, oldValue); + if (newValue == null) { + throw new NullPointerException(); + } + } while (this.replaceNode(key, newValue, oldValue) == null && (oldValue = this.get(key)) != null); + } + } + } + } + + public V computeIfAbsent(float key, Float2ObjectConcurrentHashMap.FloatFunction mappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (mappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Float.hashCode(key)); + V val = null; + int binCount = 0; + Float2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Float2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + Float2ObjectConcurrentHashMap.Node r = new Float2ObjectConcurrentHashMap.ReservationNode<>(this.EMPTY); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Float2ObjectConcurrentHashMap.Node node = null; + + try { + if ((val = (V)mappingFunction.apply(key)) != null) { + node = new Float2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + } finally { + setTabAt(tab, i, node); + } + } + } + + if (binCount != 0) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + boolean added = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Float2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Float2ObjectConcurrentHashMap.TreeBin t = (Float2ObjectConcurrentHashMap.TreeBin)f; + Float2ObjectConcurrentHashMap.TreeNode r = t.root; + Float2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(h, key, null)) != null) { + val = p.val; + } else if ((val = (V)mappingFunction.apply(key)) != null) { + added = true; + t.putTreeVal(h, key, val); + } + } + } else { + binCount = 1; + Float2ObjectConcurrentHashMap.Node e = f; + + while (true) { + if (e.hash == h) { + float ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = e.val; + break; + } + } + + Float2ObjectConcurrentHashMap.Node pred = e; + if ((e = e.next) == null) { + if ((val = (V)mappingFunction.apply(key)) != null) { + added = true; + pred.next = new Float2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + + if (!added) { + return val; + } + break; + } + } + } + } + + if (val != null) { + this.addCount(1L, binCount); + } + + return val; + } + } + + public V computeIfPresent(float key, Float2ObjectConcurrentHashMap.FloatObjFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (remappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Float.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Float2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Float2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + break; + } + + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Float2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Float2ObjectConcurrentHashMap.TreeBin t = (Float2ObjectConcurrentHashMap.TreeBin)f; + Float2ObjectConcurrentHashMap.TreeNode r = t.root; + Float2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(h, key, null)) != null) { + val = (V)remappingFunction.apply(key, p.val); + if (val != null) { + p.val = val; + } else { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } + } else { + binCount = 1; + Float2ObjectConcurrentHashMap.Node e = f; + Float2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + float ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(key, e.val); + if (val != null) { + e.val = val; + } else { + delta = -1; + Float2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + break; + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } + } + + public V compute(float key, Float2ObjectConcurrentHashMap.FloatObjFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (remappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Float.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Float2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Float2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + Float2ObjectConcurrentHashMap.Node r = new Float2ObjectConcurrentHashMap.ReservationNode<>(this.EMPTY); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Float2ObjectConcurrentHashMap.Node node = null; + + try { + if ((val = (V)remappingFunction.apply(key, null)) != null) { + delta = 1; + node = new Float2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + } finally { + setTabAt(tab, i, node); + } + } + } + + if (binCount != 0) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Float2ObjectConcurrentHashMap.TreeBin) { + binCount = 1; + Float2ObjectConcurrentHashMap.TreeBin t = (Float2ObjectConcurrentHashMap.TreeBin)f; + Float2ObjectConcurrentHashMap.TreeNode r = t.root; + Float2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null) { + p = r.findTreeNode(h, key, null); + } else { + p = null; + } + + V pv = p == null ? null : p.val; + val = (V)remappingFunction.apply(key, pv); + if (val != null) { + if (p != null) { + p.val = val; + } else { + delta = 1; + t.putTreeVal(h, key, val); + } + } else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } else { + binCount = 1; + Float2ObjectConcurrentHashMap.Node e = f; + Float2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + float ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(key, e.val); + if (val != null) { + e.val = val; + } else { + delta = -1; + Float2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + val = (V)remappingFunction.apply(key, null); + if (val != null) { + delta = 1; + pred.next = new Float2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + break; + } + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } + } + + public V merge(float key, V value, BiFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value != null && remappingFunction != null) { + int h = spread(Float.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Float2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Float2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + if (casTabAt(tab, i, null, new Float2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, value, null))) { + delta = 1; + val = value; + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Float2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Float2ObjectConcurrentHashMap.TreeBin t = (Float2ObjectConcurrentHashMap.TreeBin)f; + Float2ObjectConcurrentHashMap.TreeNode r = t.root; + Float2ObjectConcurrentHashMap.TreeNode p = r == null ? null : r.findTreeNode(h, key, null); + val = p == null ? value : remappingFunction.apply(p.val, value); + if (val != null) { + if (p != null) { + p.val = val; + } else { + delta = 1; + t.putTreeVal(h, key, val); + } + } else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } else { + binCount = 1; + Float2ObjectConcurrentHashMap.Node e = f; + Float2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + float ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(e.val, value); + if (val != null) { + e.val = val; + } else { + delta = -1; + Float2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + delta = 1; + val = value; + pred.next = new Float2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, value, null); + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + break; + } + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } else { + throw new NullPointerException(); + } + } + + public long mappingCount() { + long n = this.sumCount(); + return n < 0L ? 0L : n; + } + + public static FloatSet newKeySet() { + return new Float2ObjectConcurrentHashMap.KeySetView<>(new Float2ObjectConcurrentHashMap<>(), Boolean.TRUE); + } + + public static Float2ObjectConcurrentHashMap.KeySetView newKeySet(int initialCapacity) { + return new Float2ObjectConcurrentHashMap.KeySetView<>(new Float2ObjectConcurrentHashMap<>(initialCapacity), Boolean.TRUE); + } + + public Float2ObjectConcurrentHashMap.KeySetView keySet(V mappedValue) { + if (mappedValue == null) { + throw new NullPointerException(); + } else { + return new Float2ObjectConcurrentHashMap.KeySetView<>(this, mappedValue); + } + } + + protected static final int resizeStamp(int n) { + return Integer.numberOfLeadingZeros(n) | 1 << RESIZE_STAMP_BITS - 1; + } + + protected final Float2ObjectConcurrentHashMap.Node[] initTable() { + Float2ObjectConcurrentHashMap.Node[] tab; + while (true) { + tab = this.table; + if (this.table != null && tab.length != 0) { + break; + } + + int sc = this.sizeCtl; + if (this.sizeCtl < 0) { + Thread.yield(); + } else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + tab = this.table; + if (this.table == null || tab.length == 0) { + int n = sc > 0 ? sc : 16; + Float2ObjectConcurrentHashMap.Node[] nt = new Float2ObjectConcurrentHashMap.Node[n]; + tab = nt; + this.table = nt; + sc = n - (n >>> 2); + } + break; + } finally { + this.sizeCtl = sc; + } + } + } + + return tab; + } + + protected final void addCount(long x, int check) { + boolean uncontended; + label77: { + long s; + label74: { + Float2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + if (this.counterCells == null) { + long b = this.baseCount; + if (U.compareAndSwapLong(this, BASECOUNT, this.baseCount, s = b + x)) { + break label74; + } + } + + uncontended = (boolean)1; + Float2ObjectConcurrentHashMap.CounterCell a; + int m; + if (as == null || (m = as.length - 1) < 0 || (a = as[TLRUtil.getProbe() & m]) == null) { + break label77; + } + + long v = a.value; + if (!(uncontended = U.compareAndSwapLong(a, CELLVALUE, a.value, v + x))) { + break label77; + } + + if (check <= 1) { + return; + } + + s = this.sumCount(); + } + + if (check >= 0) { + while (true) { + int sc = this.sizeCtl; + if (s < this.sizeCtl) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] tab = this.table; + int n; + if (this.table == null || (n = tab.length) >= 1073741824) { + break; + } + + uncontended = (boolean)resizeStamp(n); + if (sc < 0) { + if (sc >>> RESIZE_STAMP_SHIFT != uncontended || sc == uncontended + 1 || sc == uncontended + MAX_RESIZERS) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] nt = this.nextTable; + if (this.nextTable == null || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nt); + } + } else if (U.compareAndSwapInt(this, SIZECTL, sc, (uncontended << RESIZE_STAMP_SHIFT) + 2)) { + this.transfer(tab, null); + } + + s = this.sumCount(); + } + } + + return; + } + + this.fullAddCount(x, uncontended); + } + + protected final Float2ObjectConcurrentHashMap.Node[] helpTransfer(Float2ObjectConcurrentHashMap.Node[] tab, Float2ObjectConcurrentHashMap.Node f) { + if (tab != null && f instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + Float2ObjectConcurrentHashMap.Node[] nextTab = ((Float2ObjectConcurrentHashMap.ForwardingNode)f).nextTable; + if (((Float2ObjectConcurrentHashMap.ForwardingNode)f).nextTable != null) { + int rs = resizeStamp(tab.length); + + while (nextTab == this.nextTable && this.table == tab) { + int sc = this.sizeCtl; + if (this.sizeCtl >= 0 || sc >>> RESIZE_STAMP_SHIFT != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nextTab); + break; + } + } + + return nextTab; + } + } + + return this.table; + } + + protected final void tryPresize(int size) { + int c = size >= 536870912 ? 1073741824 : tableSizeFor(size + (size >>> 1) + 1); + + while (true) { + int sc = this.sizeCtl; + if (this.sizeCtl < 0) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] tab = this.table; + int n; + if (tab != null && (n = tab.length) != 0) { + if (c <= sc || n >= 1073741824) { + break; + } + + if (tab == this.table) { + int rs = resizeStamp(n); + if (sc < 0) { + if (sc >>> RESIZE_STAMP_SHIFT != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] nt = this.nextTable; + if (this.nextTable == null || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nt); + } + } else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) { + this.transfer(tab, null); + } + } + } else { + n = sc > c ? sc : c; + if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + if (this.table == tab) { + Float2ObjectConcurrentHashMap.Node[] ntx = new Float2ObjectConcurrentHashMap.Node[n]; + this.table = ntx; + sc = n - (n >>> 2); + } + } finally { + this.sizeCtl = sc; + } + } + } + } + } + + protected final void transfer(Float2ObjectConcurrentHashMap.Node[] tab, Float2ObjectConcurrentHashMap.Node[] nextTab) { + int n = tab.length; + int stride; + if ((stride = NCPU > 1 ? (n >>> 3) / NCPU : n) < 16) { + stride = 16; + } + + if (nextTab == null) { + try { + Float2ObjectConcurrentHashMap.Node[] nt = new Float2ObjectConcurrentHashMap.Node[n << 1]; + nextTab = nt; + } catch (Throwable var27) { + this.sizeCtl = Integer.MAX_VALUE; + return; + } + + this.nextTable = nextTab; + this.transferIndex = n; + } + + int nextn = nextTab.length; + Float2ObjectConcurrentHashMap.ForwardingNode fwd = new Float2ObjectConcurrentHashMap.ForwardingNode<>(this.EMPTY, nextTab); + boolean advance = true; + boolean finishing = false; + int i = 0; + int bound = 0; + + while (true) { + while (!advance) { + if (i >= 0 && i < n && i + n < nextn) { + Float2ObjectConcurrentHashMap.Node f; + if ((f = tabAt(tab, i)) == null) { + advance = casTabAt(tab, i, null, fwd); + } else { + int fh = f.hash; + if (f.hash == -1) { + advance = true; + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + int runBit = fh & n; + Float2ObjectConcurrentHashMap.Node lastRun = f; + + for (Float2ObjectConcurrentHashMap.Node p = f.next; p != null; p = p.next) { + int b = p.hash & n; + if (b != runBit) { + runBit = b; + lastRun = p; + } + } + + Float2ObjectConcurrentHashMap.Node ln; + Float2ObjectConcurrentHashMap.Node hn; + if (runBit == 0) { + ln = lastRun; + hn = null; + } else { + hn = lastRun; + ln = null; + } + + for (Float2ObjectConcurrentHashMap.Node px = f; px != lastRun; px = px.next) { + int ph = px.hash; + float pk = px.key; + V pv = px.val; + if ((ph & n) == 0) { + ln = new Float2ObjectConcurrentHashMap.Node<>(this.EMPTY, ph, pk, pv, ln); + } else { + hn = new Float2ObjectConcurrentHashMap.Node<>(this.EMPTY, ph, pk, pv, hn); + } + } + + setTabAt(nextTab, i, ln); + setTabAt(nextTab, i + n, hn); + setTabAt(tab, i, fwd); + advance = true; + } else if (f instanceof Float2ObjectConcurrentHashMap.TreeBin t) { + Float2ObjectConcurrentHashMap.TreeNode lo = null; + Float2ObjectConcurrentHashMap.TreeNode loTail = null; + Float2ObjectConcurrentHashMap.TreeNode hi = null; + Float2ObjectConcurrentHashMap.TreeNode hiTail = null; + int lc = 0; + int hc = 0; + + for (Float2ObjectConcurrentHashMap.Node e = t.first; e != null; e = e.next) { + int h = e.hash; + Float2ObjectConcurrentHashMap.TreeNode pxx = new Float2ObjectConcurrentHashMap.TreeNode<>( + this.EMPTY, h, e.key, e.val, null, null + ); + if ((h & n) == 0) { + if ((pxx.prev = loTail) == null) { + lo = pxx; + } else { + loTail.next = pxx; + } + + loTail = pxx; + lc++; + } else { + if ((pxx.prev = hiTail) == null) { + hi = pxx; + } else { + hiTail.next = pxx; + } + + hiTail = pxx; + hc++; + } + } + + Float2ObjectConcurrentHashMap.Node ln = (Float2ObjectConcurrentHashMap.Node)(lc <= 6 + ? this.untreeify(lo) + : (hc != 0 ? new Float2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, lo) : t)); + Float2ObjectConcurrentHashMap.Node hn = (Float2ObjectConcurrentHashMap.Node)(hc <= 6 + ? this.untreeify(hi) + : (lc != 0 ? new Float2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, hi) : t)); + setTabAt(nextTab, i, ln); + setTabAt(nextTab, i + n, hn); + setTabAt(tab, i, fwd); + advance = true; + } + } + } + } + } + } else { + if (finishing) { + this.nextTable = null; + this.table = nextTab; + this.sizeCtl = (n << 1) - (n >>> 1); + return; + } + + int sc = this.sizeCtl; + if (U.compareAndSwapInt(this, SIZECTL, this.sizeCtl, sc - 1)) { + if (sc - 2 != resizeStamp(n) << RESIZE_STAMP_SHIFT) { + return; + } + + advance = true; + finishing = true; + i = n; + } + } + } + + if (--i < bound && !finishing) { + int nextIndex = this.transferIndex; + if (this.transferIndex <= 0) { + i = -1; + advance = false; + } else { + int nextBound; + if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex, nextBound = nextIndex > stride ? nextIndex - stride : 0)) { + bound = nextBound; + i = nextIndex - 1; + advance = false; + } + } + } else { + advance = false; + } + } + } + + protected final long sumCount() { + Float2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + long sum = this.baseCount; + if (as != null) { + for (int i = 0; i < as.length; i++) { + Float2ObjectConcurrentHashMap.CounterCell a; + if ((a = as[i]) != null) { + sum += a.value; + } + } + } + + return sum; + } + + protected final void fullAddCount(long x, boolean wasUncontended) { + int h; + if ((h = TLRUtil.getProbe()) == 0) { + TLRUtil.localInit(); + h = TLRUtil.getProbe(); + wasUncontended = true; + } + + boolean collide = false; + + while (true) { + Float2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + int n; + if (this.counterCells != null && (n = as.length) > 0) { + Float2ObjectConcurrentHashMap.CounterCell a; + if ((a = as[n - 1 & h]) == null) { + if (this.cellsBusy == 0) { + Float2ObjectConcurrentHashMap.CounterCell r = new Float2ObjectConcurrentHashMap.CounterCell(x); + if (this.cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean created = false; + + try { + Float2ObjectConcurrentHashMap.CounterCell[] rs = this.counterCells; + int m; + int j; + if (this.counterCells != null && (m = rs.length) > 0 && rs[j = m - 1 & h] == null) { + rs[j] = r; + created = true; + } + } finally { + this.cellsBusy = 0; + } + + if (created) { + break; + } + continue; + } + } + + collide = false; + } else if (!wasUncontended) { + wasUncontended = true; + } else { + long v = a.value; + if (U.compareAndSwapLong(a, CELLVALUE, a.value, v + x)) { + break; + } + + if (this.counterCells != as || n >= NCPU) { + collide = false; + } else if (!collide) { + collide = true; + } else if (this.cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + try { + if (this.counterCells == as) { + Float2ObjectConcurrentHashMap.CounterCell[] rs = new Float2ObjectConcurrentHashMap.CounterCell[n << 1]; + + for (int i = 0; i < n; i++) { + rs[i] = as[i]; + } + + this.counterCells = rs; + } + } finally { + this.cellsBusy = 0; + } + + collide = false; + continue; + } + } + + h = TLRUtil.advanceProbe(h); + } else if (this.cellsBusy == 0 && this.counterCells == as && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean init = false; + + try { + if (this.counterCells == as) { + Float2ObjectConcurrentHashMap.CounterCell[] rs = new Float2ObjectConcurrentHashMap.CounterCell[2]; + rs[h & 1] = new Float2ObjectConcurrentHashMap.CounterCell(x); + this.counterCells = rs; + init = true; + } + } finally { + this.cellsBusy = 0; + } + + if (init) { + break; + } + } else { + long vx = this.baseCount; + if (U.compareAndSwapLong(this, BASECOUNT, this.baseCount, vx + x)) { + break; + } + } + } + } + + protected final void treeifyBin(Float2ObjectConcurrentHashMap.Node[] tab, int index) { + if (tab != null) { + int n; + if ((n = tab.length) < 64) { + this.tryPresize(n << 1); + } else { + Float2ObjectConcurrentHashMap.Node b; + if ((b = tabAt(tab, index)) != null && b.hash >= 0) { + synchronized (b) { + if (tabAt(tab, index) == b) { + Float2ObjectConcurrentHashMap.TreeNode hd = null; + Float2ObjectConcurrentHashMap.TreeNode tl = null; + + for (Float2ObjectConcurrentHashMap.Node e = b; e != null; e = e.next) { + Float2ObjectConcurrentHashMap.TreeNode p = new Float2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, e.hash, e.key, e.val, null, null); + if ((p.prev = tl) == null) { + hd = p; + } else { + tl.next = p; + } + + tl = p; + } + + setTabAt(tab, index, new Float2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, hd)); + } + } + } + } + } + } + + protected Float2ObjectConcurrentHashMap.Node untreeify(Float2ObjectConcurrentHashMap.Node b) { + Float2ObjectConcurrentHashMap.Node hd = null; + Float2ObjectConcurrentHashMap.Node tl = null; + + for (Float2ObjectConcurrentHashMap.Node q = b; q != null; q = q.next) { + Float2ObjectConcurrentHashMap.Node p = new Float2ObjectConcurrentHashMap.Node<>(this.EMPTY, q.hash, q.key, q.val, null); + if (tl == null) { + hd = p; + } else { + tl.next = p; + } + + tl = p; + } + + return hd; + } + + protected final int batchFor(long b) { + long n; + if (b != Long.MAX_VALUE && (n = this.sumCount()) > 1L && n >= b) { + int sp = ForkJoinPool.getCommonPoolParallelism() << 2; + long var6; + return b > 0L && (var6 = n / b) < sp ? (int)var6 : sp; + } else { + return 0; + } + } + + public void forEach(long parallelismThreshold, Float2ObjectConcurrentHashMap.FloatObjConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Float2ObjectConcurrentHashMap.ForEachMappingTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEach( + long parallelismThreshold, Float2ObjectConcurrentHashMap.FloatObjFunction transformer, Consumer action + ) { + if (transformer != null && action != null) { + new Float2ObjectConcurrentHashMap.ForEachTransformedMappingTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U search(long parallelismThreshold, Float2ObjectConcurrentHashMap.FloatObjFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Float2ObjectConcurrentHashMap.SearchMappingsTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public U search(Float2ObjectConcurrentHashMap.FloatObjFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U search(Float2ObjectConcurrentHashMap.FloatBiObjFunction searchFunction, X x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithByte(Float2ObjectConcurrentHashMap.FloatObjByteFunction searchFunction, byte x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithShort(Float2ObjectConcurrentHashMap.FloatObjShortFunction searchFunction, short x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithInt(Float2ObjectConcurrentHashMap.FloatObjIntFunction searchFunction, int x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithLong(Float2ObjectConcurrentHashMap.FloatObjLongFunction searchFunction, long x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithFloat(Float2ObjectConcurrentHashMap.FloatObjFloatFunction searchFunction, float x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithDouble(Float2ObjectConcurrentHashMap.FloatObjDoubleFunction searchFunction, double x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U reduce( + long parallelismThreshold, + Float2ObjectConcurrentHashMap.FloatObjFunction transformer, + BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Float2ObjectConcurrentHashMap.MapReduceMappingsTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer + ) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U reduce( + Float2ObjectConcurrentHashMap.FloatObjFunction transformer, BiFunction reducer + ) { + if (transformer != null && reducer != null) { + Float2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table == null) { + return null; + } else { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + U r = null; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label88: { + while (true) { + if (p != null) { + next = p; + break label88; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + return r; + } + + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + } + } else { + throw new NullPointerException(); + } + } + + public double reduceToDouble( + long parallelismThreshold, Float2ObjectConcurrentHashMap.ToDoubleFloatObjFunction transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Float2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceToLong( + long parallelismThreshold, Float2ObjectConcurrentHashMap.ToLongFloatObjFunction transformer, long basis, LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Float2ObjectConcurrentHashMap.MapReduceMappingsToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceToInt( + long parallelismThreshold, Float2ObjectConcurrentHashMap.ToIntFloatObjFunction transformer, int basis, IntBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Float2ObjectConcurrentHashMap.MapReduceMappingsToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachKey(long parallelismThreshold, FloatConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Float2ObjectConcurrentHashMap.ForEachKeyTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachKey(long parallelismThreshold, Float2ObjectConcurrentHashMap.FloatFunction transformer, Consumer action) { + if (transformer != null && action != null) { + new Float2ObjectConcurrentHashMap.ForEachTransformedKeyTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchKeys(long parallelismThreshold, Float2ObjectConcurrentHashMap.FloatFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Float2ObjectConcurrentHashMap.SearchKeysTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public float reduceKeys(long parallelismThreshold, Float2ObjectConcurrentHashMap.FloatReduceTaskOperator reducer) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Float2ObjectConcurrentHashMap.ReduceKeysTask<>(this.EMPTY, null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer) + .invoke0(); + } + } + + public U reduceKeys( + long parallelismThreshold, Float2ObjectConcurrentHashMap.FloatFunction transformer, BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Float2ObjectConcurrentHashMap.MapReduceKeysTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceKeysToDouble( + long parallelismThreshold, Float2ObjectConcurrentHashMap.FloatToDoubleFunction transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Float2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceKeysToLong( + long parallelismThreshold, Float2ObjectConcurrentHashMap.FloatToLongFunction transformer, long basis, LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Float2ObjectConcurrentHashMap.MapReduceKeysToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceKeysToInt(long parallelismThreshold, Float2ObjectConcurrentHashMap.FloatToIntFunction transformer, int basis, IntBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Float2ObjectConcurrentHashMap.MapReduceKeysToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachValue(long parallelismThreshold, Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Float2ObjectConcurrentHashMap.ForEachValueTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachValue(long parallelismThreshold, Function transformer, Consumer action) { + if (transformer != null && action != null) { + new Float2ObjectConcurrentHashMap.ForEachTransformedValueTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchValues(long parallelismThreshold, Function searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Float2ObjectConcurrentHashMap.SearchValuesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public V reduceValues(long parallelismThreshold, BiFunction reducer) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Float2ObjectConcurrentHashMap.ReduceValuesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer).invoke(); + } + } + + public U reduceValues(long parallelismThreshold, Function transformer, BiFunction reducer) { + if (transformer != null && reducer != null) { + return new Float2ObjectConcurrentHashMap.MapReduceValuesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceValuesToDouble(long parallelismThreshold, ToDoubleFunction transformer, double basis, DoubleBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Float2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceValuesToLong(long parallelismThreshold, ToLongFunction transformer, long basis, LongBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Float2ObjectConcurrentHashMap.MapReduceValuesToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceValuesToInt(long parallelismThreshold, ToIntFunction transformer, int basis, IntBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Float2ObjectConcurrentHashMap.MapReduceValuesToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachEntry(long parallelismThreshold, Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Float2ObjectConcurrentHashMap.ForEachEntryTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachEntry( + long parallelismThreshold, Function, ? extends U> transformer, Consumer action + ) { + if (transformer != null && action != null) { + new Float2ObjectConcurrentHashMap.ForEachTransformedEntryTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchEntries(long parallelismThreshold, Function, ? extends U> searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Float2ObjectConcurrentHashMap.SearchEntriesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public Float2ObjectConcurrentHashMap.Entry reduceEntries( + long parallelismThreshold, + BiFunction, Float2ObjectConcurrentHashMap.Entry, ? extends Float2ObjectConcurrentHashMap.Entry> reducer + ) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Float2ObjectConcurrentHashMap.ReduceEntriesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer).invoke(); + } + } + + public U reduceEntries( + long parallelismThreshold, + Function, ? extends U> transformer, + BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Float2ObjectConcurrentHashMap.MapReduceEntriesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer + ) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceEntriesToDouble( + long parallelismThreshold, ToDoubleFunction> transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Float2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceEntriesToLong( + long parallelismThreshold, ToLongFunction> transformer, long basis, LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Float2ObjectConcurrentHashMap.MapReduceEntriesToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceEntriesToInt( + long parallelismThreshold, ToIntFunction> transformer, int basis, IntBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Float2ObjectConcurrentHashMap.MapReduceEntriesToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public V valueMatching(Predicate predicate) { + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + Float2ObjectConcurrentHashMap.Node[] tab = this.table; + int f = this.table == null ? 0 : tab.length; + int baseLimit = f; + int baseSize = f; + boolean b = false; + + label80: + while (next != null || !b) { + b |= true; + Float2ObjectConcurrentHashMap.Node e = next; + if (next != null) { + e = next.next; + } + + label76: + while (e == null) { + if (baseIndex < baseLimit) { + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab != null && (n = tab.length) > index && index >= 0) { + if ((e = tabAt(tab, index)) != null && e.hash < 0) { + if (e instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (e instanceof Float2ObjectConcurrentHashMap.TreeBin) { + e = ((Float2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + continue; + } + + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack next1 = stack.next; + stack.next = spare; + stack = next1; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + continue label76; + } + } + } + + next = null; + continue label80; + } + + next = e; + if (predicate.test(e.val)) { + return e.val; + } + } + + return null; + } + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + U = (Unsafe)f.get(null); + Class k = Float2ObjectConcurrentHashMap.class; + SIZECTL = U.objectFieldOffset(k.getDeclaredField("sizeCtl")); + TRANSFERINDEX = U.objectFieldOffset(k.getDeclaredField("transferIndex")); + BASECOUNT = U.objectFieldOffset(k.getDeclaredField("baseCount")); + CELLSBUSY = U.objectFieldOffset(k.getDeclaredField("cellsBusy")); + Class ck = Float2ObjectConcurrentHashMap.CounterCell.class; + CELLVALUE = U.objectFieldOffset(ck.getDeclaredField("value")); + Class ak = Float2ObjectConcurrentHashMap.Node[].class; + ABASE = U.arrayBaseOffset(ak); + int scale = U.arrayIndexScale(ak); + if ((scale & scale - 1) != 0) { + throw new Error("data type scale not a power of two"); + } else { + ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); + } + } catch (Exception var5) { + throw new Error(var5); + } + } + + protected static class BaseIterator extends Float2ObjectConcurrentHashMap.Traverser { + public final Float2ObjectConcurrentHashMap map; + public Float2ObjectConcurrentHashMap.Node lastReturned; + + public BaseIterator(Float2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, Float2ObjectConcurrentHashMap map) { + super(tab, size, index, limit); + this.map = map; + this.advance(); + } + + public final boolean hasNext() { + return this.next != null; + } + + public final boolean hasMoreElements() { + return this.next != null; + } + + public final void remove() { + Float2ObjectConcurrentHashMap.Node p = this.lastReturned; + if (this.lastReturned == null) { + throw new IllegalStateException(); + } else { + this.lastReturned = null; + this.map.replaceNode(p.key, null, null); + } + } + } + + protected abstract static class BulkTask extends CountedCompleter { + public Float2ObjectConcurrentHashMap.Node[] tab; + public Float2ObjectConcurrentHashMap.Node next; + public Float2ObjectConcurrentHashMap.TableStack stack; + public Float2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + public int batch; + + protected BulkTask(Float2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Float2ObjectConcurrentHashMap.Node[] t) { + super(par); + this.batch = b; + this.index = this.baseIndex = i; + if ((this.tab = t) == null) { + this.baseSize = this.baseLimit = 0; + } else if (par == null) { + this.baseSize = this.baseLimit = t.length; + } else { + this.baseLimit = f; + this.baseSize = par.baseSize; + } + } + + protected final Float2ObjectConcurrentHashMap.Node advance() { + Float2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Float2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Float2ObjectConcurrentHashMap.TreeBin) { + e = ((Float2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Float2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Float2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Float2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + } + + protected abstract static class CollectionView implements ObjectCollection, Serializable { + public static final long serialVersionUID = 7249069246763182397L; + public final Float2ObjectConcurrentHashMap map; + protected static final String oomeMsg = "Required array size too large"; + + public CollectionView(Float2ObjectConcurrentHashMap map) { + this.map = map; + } + + public Float2ObjectConcurrentHashMap getMap() { + return this.map; + } + + @Override + public final void clear() { + this.map.clear(); + } + + @Override + public final int size() { + return this.map.size(); + } + + @Override + public final boolean isEmpty() { + return this.map.isEmpty(); + } + + @Override + public abstract ObjectIterator iterator(); + + @Override + public abstract boolean contains(Object var1); + + @Override + public abstract boolean remove(Object var1); + + @Override + public final Object[] toArray() { + long sz = this.map.mappingCount(); + if (sz > 2147483639L) { + throw new OutOfMemoryError("Required array size too large"); + } else { + int n = (int)sz; + Object[] r = new Object[n]; + int i = 0; + + for (E e : this) { + if (i == n) { + if (n >= 2147483639) { + throw new OutOfMemoryError("Required array size too large"); + } + + if (n >= 1073741819) { + n = 2147483639; + } else { + n += (n >>> 1) + 1; + } + + r = Arrays.copyOf(r, n); + } + + r[i++] = e; + } + + return i == n ? r : Arrays.copyOf(r, i); + } + } + + @Override + public final T[] toArray(T[] a) { + long sz = this.map.mappingCount(); + if (sz > 2147483639L) { + throw new OutOfMemoryError("Required array size too large"); + } else { + int m = (int)sz; + T[] r = (T[])(a.length >= m ? a : (Object[])Array.newInstance(a.getClass().getComponentType(), m)); + int n = r.length; + int i = 0; + + for (E e : this) { + if (i == n) { + if (n >= 2147483639) { + throw new OutOfMemoryError("Required array size too large"); + } + + if (n >= 1073741819) { + n = 2147483639; + } else { + n += (n >>> 1) + 1; + } + + r = (T[])Arrays.copyOf(r, n); + } + + r[i++] = (T)e; + } + + if (a == r && i < n) { + r[i] = null; + return r; + } else { + return (T[])(i == n ? r : Arrays.copyOf(r, i)); + } + } + } + + @Override + public final String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + Iterator it = this.iterator(); + if (it.hasNext()) { + while (true) { + Object e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (!it.hasNext()) { + break; + } + + sb.append(',').append(' '); + } + } + + return sb.append(']').toString(); + } + + @Override + public final boolean containsAll(Collection c) { + if (c != this) { + for (Object e : c) { + if (e == null || !this.contains(e)) { + return false; + } + } + } + + return true; + } + + @Override + public final boolean removeAll(Collection c) { + if (c == null) { + throw new NullPointerException(); + } else { + boolean modified = false; + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (c.contains(it.next())) { + it.remove(); + modified = true; + } + } + + return modified; + } + } + + @Override + public final boolean retainAll(Collection c) { + if (c == null) { + throw new NullPointerException(); + } else { + boolean modified = false; + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + + return modified; + } + } + } + + protected static final class CounterCell { + public volatile long value; + + public CounterCell(long x) { + this.value = x; + } + } + + protected abstract static class DoubleReturningBulkTask extends Float2ObjectConcurrentHashMap.BulkTask { + public double result; + + public DoubleReturningBulkTask(Float2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Float2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected double invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + public interface Entry extends Float2ObjectMap.Entry { + boolean isEmpty(); + + @Deprecated + @Override + Float getKey(); + + @Override + float getFloatKey(); + + @Override + V getValue(); + + @Override + int hashCode(); + + @Override + String toString(); + + @Override + boolean equals(Object var1); + + @Override + V setValue(V var1); + } + + protected static final class EntryIterator extends Float2ObjectConcurrentHashMap.BaseIterator implements ObjectIterator> { + public EntryIterator(Float2ObjectConcurrentHashMap.Node[] tab, int index, int size, int limit, Float2ObjectConcurrentHashMap map) { + super(tab, index, size, limit, map); + } + + public final Float2ObjectConcurrentHashMap.Entry next() { + Float2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + float k = p.key; + V v = p.val; + this.lastReturned = p; + this.advance(); + return new Float2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), k, v, this.map); + } + } + } + + protected static final class EntrySetView + extends Float2ObjectConcurrentHashMap.CollectionView> + implements ObjectSet>, + Serializable { + public static final long serialVersionUID = 2249069246763182397L; + + public EntrySetView(Float2ObjectConcurrentHashMap map) { + super(map); + } + + @Override + public boolean contains(Object o) { + if (o instanceof Float2ObjectMap.Entry) { + Float2ObjectMap.Entry e; + float k = (e = (Float2ObjectMap.Entry)o).getFloatKey(); + if (!((Float2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + Object v; + Object r; + return (r = this.map.get(k)) != null && (v = e.getValue()) != null && (v == r || v.equals(r)); + } + } + + return false; + } + + @Override + public boolean remove(Object o) { + if (o instanceof Float2ObjectMap.Entry) { + Float2ObjectMap.Entry e; + float k = (e = (Float2ObjectMap.Entry)o).getFloatKey(); + if (!((Float2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + Object v; + return (v = e.getValue()) != null && this.map.remove(k, v); + } + } + + return false; + } + + @Override + public ObjectIterator> iterator() { + Float2ObjectConcurrentHashMap m = this.map; + Float2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Float2ObjectConcurrentHashMap.EntryIterator<>(t, f, 0, f, m); + } + + public boolean add(Float2ObjectMap.Entry e) { + return this.map.putVal(e.getFloatKey(), e.getValue(), false) == null; + } + + @Override + public boolean addAll(Collection> c) { + boolean added = false; + + for (Float2ObjectMap.Entry e : c) { + if (this.add(e)) { + added = true; + } + } + + return added; + } + + @Override + public final int hashCode() { + int h = 0; + Float2ObjectConcurrentHashMap.Node[] t = this.map.table; + if (this.map.table != null) { + Float2ObjectConcurrentHashMap.Traverser it = new Float2ObjectConcurrentHashMap.Traverser<>(t, t.length, 0, t.length); + + Float2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + h += p.hashCode(); + } + } + + return h; + } + + @Override + public final boolean equals(Object o) { + Set c; + return o instanceof Set && ((c = (Set)o) == this || this.containsAll(c) && c.containsAll(this)); + } + + @Override + public ObjectSpliterator> spliterator() { + Float2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Float2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Float2ObjectConcurrentHashMap.EntrySpliterator<>(t, f, 0, f, n < 0L ? 0L : n, m); + } + + @Override + public void forEach(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node[] t = this.map.table; + if (this.map.table != null) { + Float2ObjectConcurrentHashMap.Traverser it = new Float2ObjectConcurrentHashMap.Traverser<>(t, t.length, 0, t.length); + + Float2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + action.accept(new Float2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + } + } + } + } + } + + protected static final class EntrySpliterator extends Float2ObjectConcurrentHashMap.Traverser implements ObjectSpliterator> { + public final Float2ObjectConcurrentHashMap map; + public long est; + + public EntrySpliterator(Float2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est, Float2ObjectConcurrentHashMap map) { + super(tab, size, index, limit); + this.map = map; + this.est = est; + } + + @Override + public ObjectSpliterator> trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Float2ObjectConcurrentHashMap.EntrySpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1, this.map); + } + + @Override + public void forEachRemaining(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(new Float2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + } + } + } + + @Override + public boolean tryAdvance(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(new Float2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4353; + } + } + + @FunctionalInterface + public interface FloatBiObjByteConsumer { + void accept(float var1, V var2, byte var3, X var4); + } + + @FunctionalInterface + public interface FloatBiObjConsumer { + void accept(float var1, V var2, X var3); + } + + @FunctionalInterface + public interface FloatBiObjDoubleConsumer { + void accept(float var1, V var2, double var3, X var5); + } + + @FunctionalInterface + public interface FloatBiObjFloatConsumer { + void accept(float var1, V var2, float var3, X var4); + } + + @FunctionalInterface + public interface FloatBiObjFunction { + J apply(float var1, V var2, X var3); + } + + @FunctionalInterface + public interface FloatBiObjIntConsumer { + void accept(float var1, V var2, int var3, X var4); + } + + @FunctionalInterface + public interface FloatBiObjLongConsumer { + void accept(float var1, V var2, long var3, X var5); + } + + @FunctionalInterface + public interface FloatBiObjShortConsumer { + void accept(float var1, V var2, short var3, X var4); + } + + @FunctionalInterface + public interface FloatFunction { + R apply(float var1); + } + + @FunctionalInterface + public interface FloatObjByteConsumer { + void accept(float var1, V var2, byte var3); + } + + @FunctionalInterface + public interface FloatObjByteFunction { + J apply(float var1, V var2, byte var3); + } + + @FunctionalInterface + public interface FloatObjConsumer { + void accept(float var1, V var2); + } + + @FunctionalInterface + public interface FloatObjDoubleConsumer { + void accept(float var1, V var2, double var3); + } + + @FunctionalInterface + public interface FloatObjDoubleFunction { + J apply(float var1, V var2, double var3); + } + + @FunctionalInterface + public interface FloatObjFloatConsumer { + void accept(float var1, V var2, float var3); + } + + @FunctionalInterface + public interface FloatObjFloatFunction { + J apply(float var1, V var2, float var3); + } + + @FunctionalInterface + public interface FloatObjFunction { + J apply(float var1, V var2); + } + + @FunctionalInterface + public interface FloatObjIntConsumer { + void accept(float var1, V var2, int var3); + } + + @FunctionalInterface + public interface FloatObjIntFunction { + J apply(float var1, V var2, int var3); + } + + @FunctionalInterface + public interface FloatObjLongConsumer { + void accept(float var1, V var2, long var3); + } + + @FunctionalInterface + public interface FloatObjLongFunction { + J apply(float var1, V var2, long var3); + } + + @FunctionalInterface + public interface FloatObjShortConsumer { + void accept(float var1, V var2, short var3); + } + + @FunctionalInterface + public interface FloatObjShortFunction { + J apply(float var1, V var2, short var3); + } + + @FunctionalInterface + public interface FloatReduceTaskOperator { + float reduce(float var1, float var2, float var3); + } + + protected abstract static class FloatReturningBulkTask2 extends Float2ObjectConcurrentHashMap.BulkTask { + public float result; + + public FloatReturningBulkTask2(Float2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Float2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected float invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + @FunctionalInterface + public interface FloatToDoubleFunction { + double applyAsDouble(float var1); + } + + @FunctionalInterface + public interface FloatToIntFunction { + int applyAsInt(float var1); + } + + @FunctionalInterface + public interface FloatToLongFunction { + long applyAsLong(float var1); + } + + @FunctionalInterface + public interface FloatTriObjConsumer { + void accept(float var1, V var2, X var3, Y var4); + } + + protected static final class ForEachEntryTask extends Float2ObjectConcurrentHashMap.BulkTask { + public final Consumer> action; + + public ForEachEntryTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Consumer> action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Consumer> action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Float2ObjectConcurrentHashMap.ForEachEntryTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachKeyTask extends Float2ObjectConcurrentHashMap.BulkTask { + public final FloatConsumer action; + + public ForEachKeyTask( + Float2ObjectConcurrentHashMap.BulkTask p, int b, int i, int f, Float2ObjectConcurrentHashMap.Node[] t, FloatConsumer action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + FloatConsumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Float2ObjectConcurrentHashMap.ForEachKeyTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.key); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachMappingTask extends Float2ObjectConcurrentHashMap.BulkTask { + public final Float2ObjectConcurrentHashMap.FloatObjConsumer action; + + public ForEachMappingTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.FloatObjConsumer action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Float2ObjectConcurrentHashMap.FloatObjConsumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Float2ObjectConcurrentHashMap.ForEachMappingTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.key, p.val); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachTransformedEntryTask extends Float2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> transformer; + public final Consumer action; + + public ForEachTransformedEntryTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Function, ? extends U> transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Function, ? extends U> transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Float2ObjectConcurrentHashMap.ForEachTransformedEntryTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedKeyTask extends Float2ObjectConcurrentHashMap.BulkTask { + public final Float2ObjectConcurrentHashMap.FloatFunction transformer; + public final Consumer action; + + public ForEachTransformedKeyTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.FloatFunction transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Float2ObjectConcurrentHashMap.FloatFunction transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Float2ObjectConcurrentHashMap.ForEachTransformedKeyTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedMappingTask extends Float2ObjectConcurrentHashMap.BulkTask { + public final Float2ObjectConcurrentHashMap.FloatObjFunction transformer; + public final Consumer action; + + public ForEachTransformedMappingTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.FloatObjFunction transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Float2ObjectConcurrentHashMap.FloatObjFunction transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Float2ObjectConcurrentHashMap.ForEachTransformedMappingTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action + ) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedValueTask extends Float2ObjectConcurrentHashMap.BulkTask { + public final Function transformer; + public final Consumer action; + + public ForEachTransformedValueTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Function transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Function transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Float2ObjectConcurrentHashMap.ForEachTransformedValueTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.val)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachValueTask extends Float2ObjectConcurrentHashMap.BulkTask { + public final Consumer action; + + public ForEachValueTask( + Float2ObjectConcurrentHashMap.BulkTask p, int b, int i, int f, Float2ObjectConcurrentHashMap.Node[] t, Consumer action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Float2ObjectConcurrentHashMap.ForEachValueTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.val); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForwardingNode extends Float2ObjectConcurrentHashMap.Node { + public final Float2ObjectConcurrentHashMap.Node[] nextTable; + + public ForwardingNode(float empty, Float2ObjectConcurrentHashMap.Node[] tab) { + super(empty, -1, empty, null, null); + this.nextTable = tab; + } + + @Override + protected Float2ObjectConcurrentHashMap.Node find(int h, float k) { + Float2ObjectConcurrentHashMap.Node[] tab = this.nextTable; + + Float2ObjectConcurrentHashMap.Node e; + int n; + label41: + while (k != this.EMPTY && tab != null && (n = tab.length) != 0 && (e = Float2ObjectConcurrentHashMap.tabAt(tab, n - 1 & h)) != null) { + do { + int eh = e.hash; + if (e.hash == h) { + float ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + + if (eh < 0) { + if (!(e instanceof Float2ObjectConcurrentHashMap.ForwardingNode)) { + return e.find(h, k); + } + + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + continue label41; + } + } while ((e = e.next) != null); + + return null; + } + + return null; + } + } + + protected abstract static class IntReturningBulkTask extends Float2ObjectConcurrentHashMap.BulkTask { + public int result; + + public IntReturningBulkTask(Float2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Float2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected int invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + protected static final class KeyIterator implements FloatIterator { + public Float2ObjectConcurrentHashMap.Node[] tab; + public Float2ObjectConcurrentHashMap.Node next; + public Float2ObjectConcurrentHashMap.TableStack stack; + public Float2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + public final Float2ObjectConcurrentHashMap map; + public Float2ObjectConcurrentHashMap.Node lastReturned; + + public KeyIterator(Float2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, Float2ObjectConcurrentHashMap map) { + this.tab = tab; + this.baseSize = size; + this.baseIndex = this.index = index; + this.baseLimit = limit; + this.next = null; + this.map = map; + this.advance(); + } + + protected final Float2ObjectConcurrentHashMap.Node advance() { + Float2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Float2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Float2ObjectConcurrentHashMap.TreeBin) { + e = ((Float2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Float2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Float2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Float2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + + @Override + public final boolean hasNext() { + return this.next != null; + } + + public final boolean hasMoreElements() { + return this.next != null; + } + + @Override + public final void remove() { + Float2ObjectConcurrentHashMap.Node p = this.lastReturned; + if (this.lastReturned == null) { + throw new IllegalStateException(); + } else { + this.lastReturned = null; + this.map.replaceNode(p.key, null, null); + } + } + + @Override + public final float nextFloat() { + Float2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + float k = p.key; + this.lastReturned = p; + this.advance(); + return k; + } + } + } + + public static class KeySetView implements FloatSet { + public static final long serialVersionUID = 7249069246763182397L; + public final Float2ObjectConcurrentHashMap map; + public final V value; + + public KeySetView(Float2ObjectConcurrentHashMap map, V value) { + this.map = map; + this.value = value; + } + + public V getMappedValue() { + return this.value; + } + + @Override + public boolean contains(float o) { + return this.map.containsKey(o); + } + + @Override + public boolean remove(float o) { + return this.map.remove(o) != null; + } + + @Override + public FloatIterator iterator() { + Float2ObjectConcurrentHashMap m = this.map; + Float2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Float2ObjectConcurrentHashMap.KeyIterator<>(t, f, 0, f, m); + } + + @Override + public boolean add(float e) { + V v = this.value; + if (this.value == null) { + throw new UnsupportedOperationException(); + } else { + return this.map.putVal(e, v, true) == null; + } + } + + @Override + public boolean addAll(FloatCollection c) { + boolean added = false; + V v = this.value; + if (this.value == null) { + throw new UnsupportedOperationException(); + } else { + FloatIterator iter = c.iterator(); + + while (iter.hasNext()) { + float e = iter.nextFloat(); + if (this.map.putVal(e, v, true) == null) { + added = true; + } + } + + return added; + } + } + + @Override + public int hashCode() { + int h = 0; + FloatIterator iter = this.iterator(); + + while (iter.hasNext()) { + h += Float.hashCode(iter.nextFloat()); + } + + return h; + } + + @Override + public boolean equals(Object o) { + FloatSet c; + return o instanceof FloatSet && ((c = (FloatSet)o) == this || this.containsAll(c) && c.containsAll(this)); + } + + public float getNoEntryValue() { + return this.map.EMPTY; + } + + @Override + public int size() { + return this.map.size(); + } + + @Override + public boolean isEmpty() { + return this.map.isEmpty(); + } + + @Override + public Object[] toArray() { + Object[] out = new Float[this.size()]; + FloatIterator iter = this.iterator(); + + int i; + for (i = 0; i < out.length && iter.hasNext(); i++) { + out[i] = iter.nextFloat(); + } + + if (out.length > i + 1) { + out[i] = this.map.EMPTY; + } + + return out; + } + + @Override + public Object[] toArray(Object[] dest) { + FloatIterator iter = this.iterator(); + + int i; + for (i = 0; i < dest.length && iter.hasNext() && i <= dest.length; i++) { + dest[i] = iter.next(); + } + + if (dest.length > i + 1) { + dest[i] = this.map.EMPTY; + } + + return dest; + } + + @Override + public float[] toFloatArray() { + float[] out = new float[this.size()]; + FloatIterator iter = this.iterator(); + + int i; + for (i = 0; i < out.length && iter.hasNext(); i++) { + out[i] = iter.next(); + } + + if (out.length > i + 1) { + out[i] = this.map.EMPTY; + } + + return out; + } + + @Override + public float[] toArray(float[] dest) { + FloatIterator iter = this.iterator(); + + int i; + for (i = 0; i < dest.length && iter.hasNext() && i <= dest.length; i++) { + dest[i] = iter.next(); + } + + if (dest.length > i + 1) { + dest[i] = this.map.EMPTY; + } + + return dest; + } + + @Override + public float[] toFloatArray(float[] dest) { + return this.toArray(dest); + } + + @Override + public boolean containsAll(Collection collection) { + for (Object element : collection) { + if (!(element instanceof Long)) { + return false; + } + + float c = (Float)element; + if (!this.contains(c)) { + return false; + } + } + + return true; + } + + @Override + public boolean containsAll(FloatCollection collection) { + FloatIterator iter = collection.iterator(); + + while (iter.hasNext()) { + float element = iter.next(); + if (!this.contains(element)) { + return false; + } + } + + return true; + } + + public boolean containsAll(float[] array) { + int i = array.length; + + while (i-- > 0) { + if (!this.contains(array[i])) { + return false; + } + } + + return true; + } + + @Override + public boolean addAll(Collection collection) { + boolean changed = false; + + for (Float element : collection) { + float e = element; + if (this.add(e)) { + changed = true; + } + } + + return changed; + } + + public boolean addAll(float[] array) { + boolean changed = false; + int i = array.length; + + while (i-- > 0) { + if (this.add(array[i])) { + changed = true; + } + } + + return changed; + } + + @Override + public boolean retainAll(Collection collection) { + boolean modified = false; + FloatIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (!collection.contains(iter.next())) { + iter.remove(); + modified = true; + } + } + + return modified; + } + + @Override + public boolean retainAll(FloatCollection collection) { + if (this == collection) { + return false; + } else { + boolean modified = false; + FloatIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (!collection.contains(iter.next())) { + iter.remove(); + modified = true; + } + } + + return modified; + } + } + + public boolean retainAll(float[] array) { + boolean modified = false; + FloatIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (Arrays.binarySearch(array, iter.next().floatValue()) < 0) { + iter.remove(); + modified = true; + } + } + + return modified; + } + + @Override + public boolean removeAll(Collection collection) { + boolean changed = false; + + for (Object element : collection) { + if (element instanceof Float) { + float c = (Float)element; + if (this.remove(c)) { + changed = true; + } + } + } + + return changed; + } + + @Override + public boolean removeAll(FloatCollection collection) { + boolean changed = false; + FloatIterator iter = collection.iterator(); + + while (iter.hasNext()) { + float element = iter.next(); + if (this.remove(element)) { + changed = true; + } + } + + return changed; + } + + public boolean removeAll(float[] array) { + boolean changed = false; + int i = array.length; + + while (i-- > 0) { + if (this.remove(array[i])) { + changed = true; + } + } + + return changed; + } + + @Override + public void clear() { + this.map.clear(); + } + + @Override + public FloatSpliterator spliterator() { + Float2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Float2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Float2ObjectConcurrentHashMap.KeySpliterator<>(t, f, 0, f, n < 0L ? 0L : n); + } + } + + protected static final class KeySpliterator extends Float2ObjectConcurrentHashMap.Traverser implements FloatSpliterator { + public long est; + + public KeySpliterator(Float2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est) { + super(tab, size, index, limit); + this.est = est; + } + + @Override + public FloatSpliterator trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Float2ObjectConcurrentHashMap.KeySpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1); + } + + @Override + public boolean tryAdvance(Consumer action) { + return action instanceof FloatConsumer ? this.tryAdvance((FloatConsumer)action) : this.tryAdvance(value -> action.accept(value)); + } + + public void forEachRemaining(FloatConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(p.key); + } + } + } + + public boolean tryAdvance(FloatConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(p.key); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4353; + } + } + + protected abstract static class LongReturningBulkTask extends Float2ObjectConcurrentHashMap.BulkTask { + public long result; + + public LongReturningBulkTask(Float2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Float2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected long invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + protected static final class MapEntry implements Float2ObjectConcurrentHashMap.Entry { + public final boolean empty; + public final float key; + public V val; + public final Float2ObjectConcurrentHashMap map; + + public MapEntry(boolean empty, float key, V val, Float2ObjectConcurrentHashMap map) { + this.empty = empty; + this.key = key; + this.val = val; + this.map = map; + } + + @Override + public boolean isEmpty() { + return this.empty; + } + + @Override + public Float getKey() { + return this.key; + } + + @Override + public float getFloatKey() { + return this.key; + } + + @Override + public V getValue() { + return this.val; + } + + @Override + public String toString() { + return this.empty ? "EMPTY=" + this.val : this.key + "=" + this.val; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o instanceof Float2ObjectConcurrentHashMap.Entry) { + if (this.empty != ((Float2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + return false; + } else { + return !this.empty && this.key != ((Float2ObjectConcurrentHashMap.Entry)o).getFloatKey() + ? false + : this.val.equals(((Float2ObjectConcurrentHashMap.Entry)o).getValue()); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.empty ? 1 : 0; + result = 31 * result + Float.hashCode(this.key); + return 31 * result + this.val.hashCode(); + } + + @Override + public V setValue(V value) { + if (value == null) { + throw new NullPointerException(); + } else { + V v = this.val; + this.val = value; + this.map.put(this.key, value); + return v; + } + } + } + + protected static final class MapReduceEntriesTask extends Float2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> transformer; + public final BiFunction reducer; + public U result; + public Float2ObjectConcurrentHashMap.MapReduceEntriesTask rights; + public Float2ObjectConcurrentHashMap.MapReduceEntriesTask nextRight; + + public MapReduceEntriesTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.MapReduceEntriesTask nextRight, + Function, ? extends U> transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Function, ? extends U> transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.MapReduceEntriesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Float2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.MapReduceEntriesTask t = (Float2ObjectConcurrentHashMap.MapReduceEntriesTask)c; + + for (Float2ObjectConcurrentHashMap.MapReduceEntriesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceEntriesToDoubleTask extends Float2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final ToDoubleFunction> transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Float2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask rights; + public Float2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask nextRight; + + public MapReduceEntriesToDoubleTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask nextRight, + ToDoubleFunction> transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToDoubleFunction> transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask t = (Float2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask)c; + + for (Float2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceEntriesToIntTask extends Float2ObjectConcurrentHashMap.IntReturningBulkTask { + public final ToIntFunction> transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Float2ObjectConcurrentHashMap.MapReduceEntriesToIntTask rights; + public Float2ObjectConcurrentHashMap.MapReduceEntriesToIntTask nextRight; + + public MapReduceEntriesToIntTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.MapReduceEntriesToIntTask nextRight, + ToIntFunction> transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToIntFunction> transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.MapReduceEntriesToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.MapReduceEntriesToIntTask t = (Float2ObjectConcurrentHashMap.MapReduceEntriesToIntTask)c; + + for (Float2ObjectConcurrentHashMap.MapReduceEntriesToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceEntriesToLongTask extends Float2ObjectConcurrentHashMap.LongReturningBulkTask { + public final ToLongFunction> transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Float2ObjectConcurrentHashMap.MapReduceEntriesToLongTask rights; + public Float2ObjectConcurrentHashMap.MapReduceEntriesToLongTask nextRight; + + public MapReduceEntriesToLongTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.MapReduceEntriesToLongTask nextRight, + ToLongFunction> transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToLongFunction> transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.MapReduceEntriesToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.MapReduceEntriesToLongTask t = (Float2ObjectConcurrentHashMap.MapReduceEntriesToLongTask)c; + + for (Float2ObjectConcurrentHashMap.MapReduceEntriesToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysTask extends Float2ObjectConcurrentHashMap.BulkTask { + public final Float2ObjectConcurrentHashMap.FloatFunction transformer; + public final BiFunction reducer; + public U result; + public Float2ObjectConcurrentHashMap.MapReduceKeysTask rights; + public Float2ObjectConcurrentHashMap.MapReduceKeysTask nextRight; + + public MapReduceKeysTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.MapReduceKeysTask nextRight, + Float2ObjectConcurrentHashMap.FloatFunction transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Float2ObjectConcurrentHashMap.FloatFunction transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.MapReduceKeysTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Float2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.MapReduceKeysTask t = (Float2ObjectConcurrentHashMap.MapReduceKeysTask)c; + + for (Float2ObjectConcurrentHashMap.MapReduceKeysTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceKeysToDoubleTask extends Float2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final Float2ObjectConcurrentHashMap.FloatToDoubleFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Float2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask rights; + public Float2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask nextRight; + + public MapReduceKeysToDoubleTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask nextRight, + Float2ObjectConcurrentHashMap.FloatToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Float2ObjectConcurrentHashMap.FloatToDoubleFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask t = (Float2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask)c; + + for (Float2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysToIntTask extends Float2ObjectConcurrentHashMap.IntReturningBulkTask { + public final Float2ObjectConcurrentHashMap.FloatToIntFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Float2ObjectConcurrentHashMap.MapReduceKeysToIntTask rights; + public Float2ObjectConcurrentHashMap.MapReduceKeysToIntTask nextRight; + + public MapReduceKeysToIntTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.MapReduceKeysToIntTask nextRight, + Float2ObjectConcurrentHashMap.FloatToIntFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Float2ObjectConcurrentHashMap.FloatToIntFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.MapReduceKeysToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.MapReduceKeysToIntTask t = (Float2ObjectConcurrentHashMap.MapReduceKeysToIntTask)c; + + for (Float2ObjectConcurrentHashMap.MapReduceKeysToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysToLongTask extends Float2ObjectConcurrentHashMap.LongReturningBulkTask { + public final Float2ObjectConcurrentHashMap.FloatToLongFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Float2ObjectConcurrentHashMap.MapReduceKeysToLongTask rights; + public Float2ObjectConcurrentHashMap.MapReduceKeysToLongTask nextRight; + + public MapReduceKeysToLongTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.MapReduceKeysToLongTask nextRight, + Float2ObjectConcurrentHashMap.FloatToLongFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Float2ObjectConcurrentHashMap.FloatToLongFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.MapReduceKeysToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.MapReduceKeysToLongTask t = (Float2ObjectConcurrentHashMap.MapReduceKeysToLongTask)c; + + for (Float2ObjectConcurrentHashMap.MapReduceKeysToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsTask extends Float2ObjectConcurrentHashMap.BulkTask { + public final Float2ObjectConcurrentHashMap.FloatObjFunction transformer; + public final BiFunction reducer; + public U result; + public Float2ObjectConcurrentHashMap.MapReduceMappingsTask rights; + public Float2ObjectConcurrentHashMap.MapReduceMappingsTask nextRight; + + public MapReduceMappingsTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.MapReduceMappingsTask nextRight, + Float2ObjectConcurrentHashMap.FloatObjFunction transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Float2ObjectConcurrentHashMap.FloatObjFunction transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.MapReduceMappingsTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Float2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.MapReduceMappingsTask t = (Float2ObjectConcurrentHashMap.MapReduceMappingsTask)c; + + for (Float2ObjectConcurrentHashMap.MapReduceMappingsTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceMappingsToDoubleTask extends Float2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final Float2ObjectConcurrentHashMap.ToDoubleFloatObjFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Float2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask rights; + public Float2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask nextRight; + + public MapReduceMappingsToDoubleTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask nextRight, + Float2ObjectConcurrentHashMap.ToDoubleFloatObjFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Float2ObjectConcurrentHashMap.ToDoubleFloatObjFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask t = (Float2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask)c; + + for (Float2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsToIntTask extends Float2ObjectConcurrentHashMap.IntReturningBulkTask { + public final Float2ObjectConcurrentHashMap.ToIntFloatObjFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Float2ObjectConcurrentHashMap.MapReduceMappingsToIntTask rights; + public Float2ObjectConcurrentHashMap.MapReduceMappingsToIntTask nextRight; + + public MapReduceMappingsToIntTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.MapReduceMappingsToIntTask nextRight, + Float2ObjectConcurrentHashMap.ToIntFloatObjFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Float2ObjectConcurrentHashMap.ToIntFloatObjFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.MapReduceMappingsToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.MapReduceMappingsToIntTask t = (Float2ObjectConcurrentHashMap.MapReduceMappingsToIntTask)c; + + for (Float2ObjectConcurrentHashMap.MapReduceMappingsToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsToLongTask extends Float2ObjectConcurrentHashMap.LongReturningBulkTask { + public final Float2ObjectConcurrentHashMap.ToLongFloatObjFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Float2ObjectConcurrentHashMap.MapReduceMappingsToLongTask rights; + public Float2ObjectConcurrentHashMap.MapReduceMappingsToLongTask nextRight; + + public MapReduceMappingsToLongTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.MapReduceMappingsToLongTask nextRight, + Float2ObjectConcurrentHashMap.ToLongFloatObjFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Float2ObjectConcurrentHashMap.ToLongFloatObjFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.MapReduceMappingsToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.MapReduceMappingsToLongTask t = (Float2ObjectConcurrentHashMap.MapReduceMappingsToLongTask)c; + + for (Float2ObjectConcurrentHashMap.MapReduceMappingsToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesTask extends Float2ObjectConcurrentHashMap.BulkTask { + public final Function transformer; + public final BiFunction reducer; + public U result; + public Float2ObjectConcurrentHashMap.MapReduceValuesTask rights; + public Float2ObjectConcurrentHashMap.MapReduceValuesTask nextRight; + + public MapReduceValuesTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.MapReduceValuesTask nextRight, + Function transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Function transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.MapReduceValuesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Float2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.MapReduceValuesTask t = (Float2ObjectConcurrentHashMap.MapReduceValuesTask)c; + + for (Float2ObjectConcurrentHashMap.MapReduceValuesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceValuesToDoubleTask extends Float2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final ToDoubleFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Float2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask rights; + public Float2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask nextRight; + + public MapReduceValuesToDoubleTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask nextRight, + ToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToDoubleFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask t = (Float2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask)c; + + for (Float2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesToIntTask extends Float2ObjectConcurrentHashMap.IntReturningBulkTask { + public final ToIntFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Float2ObjectConcurrentHashMap.MapReduceValuesToIntTask rights; + public Float2ObjectConcurrentHashMap.MapReduceValuesToIntTask nextRight; + + public MapReduceValuesToIntTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.MapReduceValuesToIntTask nextRight, + ToIntFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToIntFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.MapReduceValuesToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.MapReduceValuesToIntTask t = (Float2ObjectConcurrentHashMap.MapReduceValuesToIntTask)c; + + for (Float2ObjectConcurrentHashMap.MapReduceValuesToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesToLongTask extends Float2ObjectConcurrentHashMap.LongReturningBulkTask { + public final ToLongFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Float2ObjectConcurrentHashMap.MapReduceValuesToLongTask rights; + public Float2ObjectConcurrentHashMap.MapReduceValuesToLongTask nextRight; + + public MapReduceValuesToLongTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.MapReduceValuesToLongTask nextRight, + ToLongFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToLongFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.MapReduceValuesToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.MapReduceValuesToLongTask t = (Float2ObjectConcurrentHashMap.MapReduceValuesToLongTask)c; + + for (Float2ObjectConcurrentHashMap.MapReduceValuesToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static class Node implements Float2ObjectConcurrentHashMap.Entry { + public final float EMPTY; + public final int hash; + public final float key; + public volatile V val; + public volatile Float2ObjectConcurrentHashMap.Node next; + + public Node(float empty, int hash, float key, V val, Float2ObjectConcurrentHashMap.Node next) { + this.EMPTY = empty; + this.hash = hash; + this.key = key; + this.val = val; + this.next = next; + } + + @Override + public final boolean isEmpty() { + return this.key == this.EMPTY; + } + + @Override + public final Float getKey() { + return this.key; + } + + @Override + public final float getFloatKey() { + return this.key; + } + + @Override + public final V getValue() { + return this.val; + } + + @Override + public final int hashCode() { + return Float.hashCode(this.key) ^ this.val.hashCode(); + } + + @Override + public final String toString() { + return this.isEmpty() ? "EMPTY=" + this.val : this.key + "=" + this.val; + } + + @Override + public final V setValue(V value) { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean equals(Object o) { + boolean empty = this.isEmpty(); + if (o instanceof Float2ObjectConcurrentHashMap.Entry) { + if (empty != ((Float2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + return false; + } else { + return !empty && this.key != ((Float2ObjectConcurrentHashMap.Entry)o).getFloatKey() + ? false + : this.val.equals(((Float2ObjectConcurrentHashMap.Entry)o).getValue()); + } + } else { + return false; + } + } + + protected Float2ObjectConcurrentHashMap.Node find(int h, float k) { + Float2ObjectConcurrentHashMap.Node e = this; + if (k != this.EMPTY) { + do { + if (e.hash == h) { + float ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + } while ((e = e.next) != null); + } + + return null; + } + } + + protected static final class ReduceEntriesTask extends Float2ObjectConcurrentHashMap.BulkTask> { + public final BiFunction, Float2ObjectConcurrentHashMap.Entry, ? extends Float2ObjectConcurrentHashMap.Entry> reducer; + public Float2ObjectConcurrentHashMap.Entry result; + public Float2ObjectConcurrentHashMap.ReduceEntriesTask rights; + public Float2ObjectConcurrentHashMap.ReduceEntriesTask nextRight; + + public ReduceEntriesTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.ReduceEntriesTask nextRight, + BiFunction, Float2ObjectConcurrentHashMap.Entry, ? extends Float2ObjectConcurrentHashMap.Entry> reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.reducer = reducer; + } + + public final Float2ObjectConcurrentHashMap.Entry getRawResult() { + return this.result; + } + + @Override + public final void compute() { + BiFunction, Float2ObjectConcurrentHashMap.Entry, ? extends Float2ObjectConcurrentHashMap.Entry> reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.ReduceEntriesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + Float2ObjectConcurrentHashMap.Entry r = null; + + Float2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + r = (Float2ObjectConcurrentHashMap.Entry)(r == null ? p : reducer.apply(r, p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.ReduceEntriesTask t = (Float2ObjectConcurrentHashMap.ReduceEntriesTask)c; + + for (Float2ObjectConcurrentHashMap.ReduceEntriesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + Float2ObjectConcurrentHashMap.Entry sr = s.result; + if (s.result != null) { + Float2ObjectConcurrentHashMap.Entry tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + + protected static final class ReduceKeysTask extends Float2ObjectConcurrentHashMap.FloatReturningBulkTask2 { + public final float EMPTY; + public final Float2ObjectConcurrentHashMap.FloatReduceTaskOperator reducer; + public Float2ObjectConcurrentHashMap.ReduceKeysTask rights; + public Float2ObjectConcurrentHashMap.ReduceKeysTask nextRight; + + public ReduceKeysTask( + float EMPTY, + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.ReduceKeysTask nextRight, + Float2ObjectConcurrentHashMap.FloatReduceTaskOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.EMPTY = EMPTY; + this.reducer = reducer; + } + + public final Float getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Float2ObjectConcurrentHashMap.FloatReduceTaskOperator reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.ReduceKeysTask<>( + this.EMPTY, this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + boolean found = false; + float r = this.EMPTY; + + Float2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + float u = p.key; + if (!found) { + found = true; + r = u; + } else if (!p.isEmpty()) { + found = true; + r = reducer.reduce(this.EMPTY, r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.ReduceKeysTask t = (Float2ObjectConcurrentHashMap.ReduceKeysTask)c; + + for (Float2ObjectConcurrentHashMap.ReduceKeysTask s = t.rights; s != null; s = t.rights = s.nextRight) { + float sr = s.result; + if (s.result != this.EMPTY) { + float tr = t.result; + t.result = t.result == this.EMPTY ? sr : reducer.reduce(this.EMPTY, tr, sr); + } + } + } + } + } + } + + protected static final class ReduceValuesTask extends Float2ObjectConcurrentHashMap.BulkTask { + public final BiFunction reducer; + public V result; + public Float2ObjectConcurrentHashMap.ReduceValuesTask rights; + public Float2ObjectConcurrentHashMap.ReduceValuesTask nextRight; + + public ReduceValuesTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.ReduceValuesTask nextRight, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.reducer = reducer; + } + + @Override + public final V getRawResult() { + return this.result; + } + + @Override + public final void compute() { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Float2ObjectConcurrentHashMap.ReduceValuesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + V r = null; + + Float2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + V v = p.val; + r = r == null ? v : reducer.apply(r, v); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Float2ObjectConcurrentHashMap.ReduceValuesTask t = (Float2ObjectConcurrentHashMap.ReduceValuesTask)c; + + for (Float2ObjectConcurrentHashMap.ReduceValuesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + V sr = s.result; + if (s.result != null) { + V tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + + protected static final class ReservationNode extends Float2ObjectConcurrentHashMap.Node { + public ReservationNode(float empty) { + super(empty, -3, empty, null, null); + } + + @Override + protected Float2ObjectConcurrentHashMap.Node find(int h, float k) { + return null; + } + } + + protected static final class SearchEntriesTask extends Float2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> searchFunction; + public final AtomicReference result; + + public SearchEntriesTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Function, ? extends U> searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Function, ? extends U> searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Float2ObjectConcurrentHashMap.SearchEntriesTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result) + .fork(); + } + + while (result.get() == null) { + Float2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + + return; + } + } + } + } + } + } + + protected static final class SearchKeysTask extends Float2ObjectConcurrentHashMap.BulkTask { + public final Float2ObjectConcurrentHashMap.FloatFunction searchFunction; + public final AtomicReference result; + + public SearchKeysTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.FloatFunction searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Float2ObjectConcurrentHashMap.FloatFunction searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Float2ObjectConcurrentHashMap.SearchKeysTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Float2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.key)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static final class SearchMappingsTask extends Float2ObjectConcurrentHashMap.BulkTask { + public final Float2ObjectConcurrentHashMap.FloatObjFunction searchFunction; + public final AtomicReference result; + + public SearchMappingsTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Float2ObjectConcurrentHashMap.FloatObjFunction searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Float2ObjectConcurrentHashMap.FloatObjFunction searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Float2ObjectConcurrentHashMap.SearchMappingsTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result) + .fork(); + } + + while (result.get() == null) { + Float2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.key, p.val)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static final class SearchValuesTask extends Float2ObjectConcurrentHashMap.BulkTask { + public final Function searchFunction; + public final AtomicReference result; + + public SearchValuesTask( + Float2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Float2ObjectConcurrentHashMap.Node[] t, + Function searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Function searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Float2ObjectConcurrentHashMap.SearchValuesTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Float2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.val)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static class Segment extends ReentrantLock implements Serializable { + public static final long serialVersionUID = 2249069246763182397L; + public final float loadFactor; + + public Segment(float lf) { + this.loadFactor = lf; + } + } + + protected static final class TableStack { + public int length; + public int index; + public Float2ObjectConcurrentHashMap.Node[] tab; + public Float2ObjectConcurrentHashMap.TableStack next; + + public TableStack() { + } + } + + @FunctionalInterface + public interface ToDoubleFloatObjFunction { + double applyAsDouble(float var1, V var2); + } + + @FunctionalInterface + public interface ToFloatFunction { + float applyAsFloat(T var1); + } + + @FunctionalInterface + public interface ToIntFloatObjFunction { + int applyAsInt(float var1, V var2); + } + + @FunctionalInterface + public interface ToLongFloatObjFunction { + long applyAsLong(float var1, V var2); + } + + protected static class Traverser { + public Float2ObjectConcurrentHashMap.Node[] tab; + public Float2ObjectConcurrentHashMap.Node next; + public Float2ObjectConcurrentHashMap.TableStack stack; + public Float2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + + public Traverser(Float2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit) { + this.tab = tab; + this.baseSize = size; + this.baseIndex = this.index = index; + this.baseLimit = limit; + this.next = null; + } + + protected final Float2ObjectConcurrentHashMap.Node advance() { + Float2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Float2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Float2ObjectConcurrentHashMap.TreeBin) { + e = ((Float2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Float2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Float2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Float2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + } + + protected static final class TreeBin extends Float2ObjectConcurrentHashMap.Node { + public Float2ObjectConcurrentHashMap.TreeNode root; + public volatile Float2ObjectConcurrentHashMap.TreeNode first; + public volatile Thread waiter; + public volatile int lockState; + public static final int WRITER = 1; + public static final int WAITER = 2; + public static final int READER = 4; + protected static final Unsafe U; + protected static final long LOCKSTATE; + + protected int tieBreakOrder(float a, float b) { + int comp = Float.compare(a, b); + return comp > 0 ? 1 : -1; + } + + public TreeBin(float empty, Float2ObjectConcurrentHashMap.TreeNode b) { + super(empty, -2, empty, null, null); + this.first = b; + Float2ObjectConcurrentHashMap.TreeNode r = null; + Float2ObjectConcurrentHashMap.TreeNode x = b; + + while (x != null) { + Float2ObjectConcurrentHashMap.TreeNode next = (Float2ObjectConcurrentHashMap.TreeNode)x.next; + x.left = x.right = null; + if (r == null) { + x.parent = null; + x.red = false; + r = x; + } else { + float k = x.key; + int h = x.hash; + Class kc = null; + Float2ObjectConcurrentHashMap.TreeNode p = r; + + int dir; + Float2ObjectConcurrentHashMap.TreeNode xp; + do { + float pk = p.key; + int ph = p.hash; + if (p.hash > h) { + dir = -1; + } else if (ph < h) { + dir = 1; + } else if ((dir = Float.compare(k, pk)) == 0) { + dir = this.tieBreakOrder(k, pk); + } + + xp = p; + } while ((p = dir <= 0 ? p.left : p.right) != null); + + x.parent = xp; + if (dir <= 0) { + xp.left = x; + } else { + xp.right = x; + } + + r = this.balanceInsertion(r, x); + } + + x = next; + } + + this.root = r; + + assert this.checkInvariants(this.root); + } + + protected final void lockRoot() { + if (!U.compareAndSwapInt(this, LOCKSTATE, 0, 1)) { + this.contendedLock(); + } + } + + protected final void unlockRoot() { + this.lockState = 0; + } + + protected final void contendedLock() { + boolean waiting = false; + + while (true) { + int s = this.lockState; + if ((this.lockState & -3) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, 1)) { + if (waiting) { + this.waiter = null; + } + + return; + } + } else if ((s & 2) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, s | 2)) { + waiting = true; + this.waiter = Thread.currentThread(); + } + } else if (waiting) { + LockSupport.park(this); + } + } + } + + @Override + protected final Float2ObjectConcurrentHashMap.Node find(int h, float k) { + if (k != this.EMPTY) { + Float2ObjectConcurrentHashMap.Node e = this.first; + + while (e != null) { + int s = this.lockState; + if ((this.lockState & 3) != 0) { + if (e.hash == h) { + float ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + + e = e.next; + } else if (U.compareAndSwapInt(this, LOCKSTATE, s, s + 4)) { + Float2ObjectConcurrentHashMap.TreeNode p; + try { + Float2ObjectConcurrentHashMap.TreeNode r = this.root; + p = this.root == null ? null : r.findTreeNode(h, k, null); + } finally { + if (U.getAndAddInt(this, LOCKSTATE, -4) == 6) { + Thread w = this.waiter; + if (this.waiter != null) { + LockSupport.unpark(w); + } + } + } + + return p; + } + } + } + + return null; + } + + protected final Float2ObjectConcurrentHashMap.TreeNode putTreeVal(int h, float k, V v) { + Class kc = null; + boolean searched = false; + Float2ObjectConcurrentHashMap.TreeNode p = this.root; + + while (true) { + if (p == null) { + this.first = this.root = new Float2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, h, k, v, null, null); + } else { + int ph = p.hash; + int dir; + if (p.hash > h) { + dir = -1; + } else if (ph < h) { + dir = 1; + } else { + float pk = p.key; + if (p.key == k || pk != this.EMPTY && k == pk) { + return p; + } + + if ((dir = Float.compare(k, pk)) == 0) { + if (!searched) { + searched = true; + Float2ObjectConcurrentHashMap.TreeNode ch = p.left; + Float2ObjectConcurrentHashMap.TreeNode q; + if (p.left != null && (q = ch.findTreeNode(h, k, kc)) != null) { + return q; + } + + ch = p.right; + if (p.right != null && (q = ch.findTreeNode(h, k, kc)) != null) { + return q; + } + } + + dir = this.tieBreakOrder(k, pk); + } + } + + Float2ObjectConcurrentHashMap.TreeNode xp = p; + if ((p = dir <= 0 ? p.left : p.right) != null) { + continue; + } + + Float2ObjectConcurrentHashMap.TreeNode f = this.first; + Float2ObjectConcurrentHashMap.TreeNode x; + this.first = x = new Float2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, h, k, v, f, xp); + if (f != null) { + f.prev = x; + } + + if (dir <= 0) { + xp.left = x; + } else { + xp.right = x; + } + + if (!xp.red) { + x.red = true; + } else { + this.lockRoot(); + + try { + this.root = this.balanceInsertion(this.root, x); + } finally { + this.unlockRoot(); + } + } + } + + assert this.checkInvariants(this.root); + + return null; + } + } + + protected final boolean removeTreeNode(Float2ObjectConcurrentHashMap.TreeNode p) { + Float2ObjectConcurrentHashMap.TreeNode next = (Float2ObjectConcurrentHashMap.TreeNode)p.next; + Float2ObjectConcurrentHashMap.TreeNode pred = p.prev; + if (pred == null) { + this.first = next; + } else { + pred.next = next; + } + + if (next != null) { + next.prev = pred; + } + + if (this.first == null) { + this.root = null; + return true; + } else { + Float2ObjectConcurrentHashMap.TreeNode r = this.root; + if (this.root != null && r.right != null) { + Float2ObjectConcurrentHashMap.TreeNode rl = r.left; + if (r.left != null && rl.left != null) { + this.lockRoot(); + + try { + Float2ObjectConcurrentHashMap.TreeNode pl = p.left; + Float2ObjectConcurrentHashMap.TreeNode pr = p.right; + Float2ObjectConcurrentHashMap.TreeNode replacement; + if (pl != null && pr != null) { + Float2ObjectConcurrentHashMap.TreeNode s = pr; + + while (true) { + Float2ObjectConcurrentHashMap.TreeNode sl = s.left; + if (s.left == null) { + boolean c = s.red; + s.red = p.red; + p.red = c; + Float2ObjectConcurrentHashMap.TreeNode sr = s.right; + Float2ObjectConcurrentHashMap.TreeNode pp = p.parent; + if (s == pr) { + p.parent = s; + s.right = p; + } else { + Float2ObjectConcurrentHashMap.TreeNode sp = s.parent; + if ((p.parent = sp) != null) { + if (s == sp.left) { + sp.left = p; + } else { + sp.right = p; + } + } + + if ((s.right = pr) != null) { + pr.parent = s; + } + } + + p.left = null; + if ((p.right = sr) != null) { + sr.parent = p; + } + + if ((s.left = pl) != null) { + pl.parent = s; + } + + if ((s.parent = pp) == null) { + r = s; + } else if (p == pp.left) { + pp.left = s; + } else { + pp.right = s; + } + + if (sr != null) { + replacement = sr; + } else { + replacement = p; + } + break; + } + + s = sl; + } + } else if (pl != null) { + replacement = pl; + } else if (pr != null) { + replacement = pr; + } else { + replacement = p; + } + + if (replacement != p) { + Float2ObjectConcurrentHashMap.TreeNode ppx = replacement.parent = p.parent; + if (ppx == null) { + r = replacement; + } else if (p == ppx.left) { + ppx.left = replacement; + } else { + ppx.right = replacement; + } + + p.left = p.right = p.parent = null; + } + + this.root = p.red ? r : this.balanceDeletion(r, replacement); + if (p == replacement) { + Float2ObjectConcurrentHashMap.TreeNode ppx = p.parent; + if (p.parent != null) { + if (p == ppx.left) { + ppx.left = null; + } else if (p == ppx.right) { + ppx.right = null; + } + + p.parent = null; + } + } + } finally { + this.unlockRoot(); + } + + assert this.checkInvariants(this.root); + + return false; + } + } + + return true; + } + } + + protected Float2ObjectConcurrentHashMap.TreeNode rotateLeft( + Float2ObjectConcurrentHashMap.TreeNode root, Float2ObjectConcurrentHashMap.TreeNode p + ) { + if (p != null) { + Float2ObjectConcurrentHashMap.TreeNode r = p.right; + if (p.right != null) { + Float2ObjectConcurrentHashMap.TreeNode rl; + if ((rl = p.right = r.left) != null) { + rl.parent = p; + } + + Float2ObjectConcurrentHashMap.TreeNode pp; + if ((pp = r.parent = p.parent) == null) { + root = r; + r.red = false; + } else if (pp.left == p) { + pp.left = r; + } else { + pp.right = r; + } + + r.left = p; + p.parent = r; + } + } + + return root; + } + + protected Float2ObjectConcurrentHashMap.TreeNode rotateRight( + Float2ObjectConcurrentHashMap.TreeNode root, Float2ObjectConcurrentHashMap.TreeNode p + ) { + if (p != null) { + Float2ObjectConcurrentHashMap.TreeNode l = p.left; + if (p.left != null) { + Float2ObjectConcurrentHashMap.TreeNode lr; + if ((lr = p.left = l.right) != null) { + lr.parent = p; + } + + Float2ObjectConcurrentHashMap.TreeNode pp; + if ((pp = l.parent = p.parent) == null) { + root = l; + l.red = false; + } else if (pp.right == p) { + pp.right = l; + } else { + pp.left = l; + } + + l.right = p; + p.parent = l; + } + } + + return root; + } + + protected Float2ObjectConcurrentHashMap.TreeNode balanceInsertion( + Float2ObjectConcurrentHashMap.TreeNode root, Float2ObjectConcurrentHashMap.TreeNode x + ) { + x.red = true; + + while (true) { + Float2ObjectConcurrentHashMap.TreeNode xp = x.parent; + if (x.parent == null) { + x.red = false; + return x; + } + + if (!xp.red) { + break; + } + + Float2ObjectConcurrentHashMap.TreeNode xpp = xp.parent; + if (xp.parent == null) { + break; + } + + Float2ObjectConcurrentHashMap.TreeNode xppl = xpp.left; + if (xp == xpp.left) { + Float2ObjectConcurrentHashMap.TreeNode xppr = xpp.right; + if (xpp.right != null && xppr.red) { + xppr.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } else { + if (x == xp.right) { + x = xp; + root = this.rotateLeft(root, xp); + xpp = (xp = xp.parent) == null ? null : xp.parent; + } + + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = this.rotateRight(root, xpp); + } + } + } + } else if (xppl != null && xppl.red) { + xppl.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } else { + if (x == xp.left) { + x = xp; + root = this.rotateRight(root, xp); + xpp = (xp = xp.parent) == null ? null : xp.parent; + } + + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = this.rotateLeft(root, xpp); + } + } + } + } + + return root; + } + + protected Float2ObjectConcurrentHashMap.TreeNode balanceDeletion( + Float2ObjectConcurrentHashMap.TreeNode root, Float2ObjectConcurrentHashMap.TreeNode x + ) { + while (x != null && x != root) { + Float2ObjectConcurrentHashMap.TreeNode xp = x.parent; + if (x.parent == null) { + x.red = false; + return x; + } + + if (x.red) { + x.red = false; + return root; + } + + Float2ObjectConcurrentHashMap.TreeNode xpl = xp.left; + if (xp.left == x) { + Float2ObjectConcurrentHashMap.TreeNode xpr = xp.right; + if (xp.right != null && xpr.red) { + xpr.red = false; + xp.red = true; + root = this.rotateLeft(root, xp); + xp = x.parent; + xpr = x.parent == null ? null : xp.right; + } + + if (xpr == null) { + x = xp; + } else { + Float2ObjectConcurrentHashMap.TreeNode sl = xpr.left; + Float2ObjectConcurrentHashMap.TreeNode sr = xpr.right; + if (sr != null && sr.red || sl != null && sl.red) { + if (sr == null || !sr.red) { + if (sl != null) { + sl.red = false; + } + + xpr.red = true; + root = this.rotateRight(root, xpr); + xp = x.parent; + xpr = x.parent == null ? null : xp.right; + } + + if (xpr != null) { + xpr.red = xp == null ? false : xp.red; + sr = xpr.right; + if (xpr.right != null) { + sr.red = false; + } + } + + if (xp != null) { + xp.red = false; + root = this.rotateLeft(root, xp); + } + + x = root; + } else { + xpr.red = true; + x = xp; + } + } + } else { + if (xpl != null && xpl.red) { + xpl.red = false; + xp.red = true; + root = this.rotateRight(root, xp); + xp = x.parent; + xpl = x.parent == null ? null : xp.left; + } + + if (xpl == null) { + x = xp; + } else { + Float2ObjectConcurrentHashMap.TreeNode sl = xpl.left; + Float2ObjectConcurrentHashMap.TreeNode sr = xpl.right; + if (sl != null && sl.red || sr != null && sr.red) { + if (sl == null || !sl.red) { + if (sr != null) { + sr.red = false; + } + + xpl.red = true; + root = this.rotateLeft(root, xpl); + xp = x.parent; + xpl = x.parent == null ? null : xp.left; + } + + if (xpl != null) { + xpl.red = xp == null ? false : xp.red; + sl = xpl.left; + if (xpl.left != null) { + sl.red = false; + } + } + + if (xp != null) { + xp.red = false; + root = this.rotateRight(root, xp); + } + + x = root; + } else { + xpl.red = true; + x = xp; + } + } + } + } + + return root; + } + + protected boolean checkInvariants(Float2ObjectConcurrentHashMap.TreeNode t) { + Float2ObjectConcurrentHashMap.TreeNode tp = t.parent; + Float2ObjectConcurrentHashMap.TreeNode tl = t.left; + Float2ObjectConcurrentHashMap.TreeNode tr = t.right; + Float2ObjectConcurrentHashMap.TreeNode tb = t.prev; + Float2ObjectConcurrentHashMap.TreeNode tn = (Float2ObjectConcurrentHashMap.TreeNode)t.next; + if (tb != null && tb.next != t) { + return false; + } else if (tn != null && tn.prev != t) { + return false; + } else if (tp != null && t != tp.left && t != tp.right) { + return false; + } else if (tl == null || tl.parent == t && tl.hash <= t.hash) { + if (tr == null || tr.parent == t && tr.hash >= t.hash) { + if (t.red && tl != null && tl.red && tr != null && tr.red) { + return false; + } else { + return tl != null && !this.checkInvariants(tl) ? false : tr == null || this.checkInvariants(tr); + } + } else { + return false; + } + } else { + return false; + } + } + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + U = (Unsafe)f.get(null); + Class k = Float2ObjectConcurrentHashMap.TreeBin.class; + LOCKSTATE = U.objectFieldOffset(k.getDeclaredField("lockState")); + } catch (Exception var2) { + throw new Error(var2); + } + } + } + + protected static final class TreeNode extends Float2ObjectConcurrentHashMap.Node { + public Float2ObjectConcurrentHashMap.TreeNode parent; + public Float2ObjectConcurrentHashMap.TreeNode left; + public Float2ObjectConcurrentHashMap.TreeNode right; + public Float2ObjectConcurrentHashMap.TreeNode prev; + public boolean red; + + public TreeNode(float empty, int hash, float key, V val, Float2ObjectConcurrentHashMap.Node next, Float2ObjectConcurrentHashMap.TreeNode parent) { + super(empty, hash, key, val, next); + this.parent = parent; + } + + @Override + protected Float2ObjectConcurrentHashMap.Node find(int h, float k) { + return this.findTreeNode(h, k, null); + } + + protected final Float2ObjectConcurrentHashMap.TreeNode findTreeNode(int h, float k, Class kc) { + if (k != this.EMPTY) { + Float2ObjectConcurrentHashMap.TreeNode p = this; + + do { + Float2ObjectConcurrentHashMap.TreeNode pl = p.left; + Float2ObjectConcurrentHashMap.TreeNode pr = p.right; + int ph = p.hash; + if (p.hash > h) { + p = pl; + } else if (ph < h) { + p = pr; + } else { + float pk = p.key; + if (p.key == k || pk != this.EMPTY && k == pk) { + return p; + } + + if (pl == null) { + p = pr; + } else if (pr == null) { + p = pl; + } else { + int dir; + if ((dir = Float.compare(k, pk)) != 0) { + p = dir < 0 ? pl : pr; + } else { + Float2ObjectConcurrentHashMap.TreeNode q; + if ((q = pr.findTreeNode(h, k, kc)) != null) { + return q; + } + + p = pl; + } + } + } + } while (p != null); + } + + return null; + } + } + + protected static final class ValueIterator extends Float2ObjectConcurrentHashMap.BaseIterator implements ObjectIterator, Enumeration { + public ValueIterator(Float2ObjectConcurrentHashMap.Node[] tab, int index, int size, int limit, Float2ObjectConcurrentHashMap map) { + super(tab, index, size, limit, map); + } + + @Override + public final V next() { + Float2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + V v = p.val; + this.lastReturned = p; + this.advance(); + return v; + } + } + + @Override + public final V nextElement() { + return this.next(); + } + } + + protected static final class ValueSpliterator extends Float2ObjectConcurrentHashMap.Traverser implements ObjectSpliterator { + public long est; + + public ValueSpliterator(Float2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est) { + super(tab, size, index, limit); + this.est = est; + } + + @Override + public ObjectSpliterator trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Float2ObjectConcurrentHashMap.ValueSpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1); + } + + @Override + public void forEachRemaining(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(p.val); + } + } + } + + @Override + public boolean tryAdvance(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(p.val); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4352; + } + } + + protected static final class ValuesView extends Float2ObjectConcurrentHashMap.CollectionView implements FastCollection, Serializable { + public static final long serialVersionUID = 2249069246763182397L; + + public ValuesView(Float2ObjectConcurrentHashMap map) { + super(map); + } + + @Override + public final boolean contains(Object o) { + return this.map.containsValue(o); + } + + @Override + public final boolean remove(Object o) { + if (o != null) { + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (o.equals(it.next())) { + it.remove(); + return true; + } + } + } + + return false; + } + + @Override + public final ObjectIterator iterator() { + Float2ObjectConcurrentHashMap m = this.map; + Float2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Float2ObjectConcurrentHashMap.ValueIterator<>(t, f, 0, f, m); + } + + @Override + public final boolean add(V e) { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public ObjectSpliterator spliterator() { + Float2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Float2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Float2ObjectConcurrentHashMap.ValueSpliterator<>(t, f, 0, f, n < 0L ? 0L : n); + } + + @Override + public void forEach(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Float2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.val); + } + } + } + } + + @Override + public void forEach( + FastCollection.FastConsumerD9 consumer, + A a, + double d1, + double d2, + double d3, + double d4, + double d5, + double d6, + double d7, + double d8, + double d9, + B b, + C c, + D d + ) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Float2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, a, d1, d2, d3, d4, d5, d6, d7, d8, d9, b, c, d); + } + } + } + } + + @Override + public void forEach( + FastCollection.FastConsumerD6 consumer, A a, double d1, double d2, double d3, double d4, double d5, double d6, B b, C c, D d + ) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Float2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, a, d1, d2, d3, d4, d5, d6, b, c, d); + } + } + } + } + + @Override + public void forEachWithFloat(FastCollection.FastConsumerF consumer, float ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Float2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + + @Override + public void forEachWithInt(FastCollection.FastConsumerI consumer, int ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Float2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + + @Override + public void forEachWithLong(FastCollection.FastConsumerL consumer, long ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Float2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Float2ObjectConcurrentHashMap.Node[] tab = tt; + Float2ObjectConcurrentHashMap.Node next = null; + Float2ObjectConcurrentHashMap.TableStack stack = null; + Float2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Float2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Float2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Float2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Float2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Float2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Float2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Float2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Float2ObjectConcurrentHashMap.TreeBin) { + p = ((Float2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Float2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Float2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + } +} diff --git a/src/com/hypixel/fastutil/floats/Float2ObjectOperator.java b/src/com/hypixel/fastutil/floats/Float2ObjectOperator.java new file mode 100644 index 0000000..495e986 --- /dev/null +++ b/src/com/hypixel/fastutil/floats/Float2ObjectOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.floats; + +@FunctionalInterface +public interface Float2ObjectOperator { + V apply(float var1, V var2); +} diff --git a/src/com/hypixel/fastutil/floats/Float2ShortOperator.java b/src/com/hypixel/fastutil/floats/Float2ShortOperator.java new file mode 100644 index 0000000..9d16e99 --- /dev/null +++ b/src/com/hypixel/fastutil/floats/Float2ShortOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.floats; + +@FunctionalInterface +public interface Float2ShortOperator { + short apply(float var1, short var2); +} diff --git a/src/com/hypixel/fastutil/ints/Int2ByteOperator.java b/src/com/hypixel/fastutil/ints/Int2ByteOperator.java new file mode 100644 index 0000000..036b893 --- /dev/null +++ b/src/com/hypixel/fastutil/ints/Int2ByteOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.ints; + +@FunctionalInterface +public interface Int2ByteOperator { + byte apply(int var1, byte var2); +} diff --git a/src/com/hypixel/fastutil/ints/Int2CharOperator.java b/src/com/hypixel/fastutil/ints/Int2CharOperator.java new file mode 100644 index 0000000..25cf718 --- /dev/null +++ b/src/com/hypixel/fastutil/ints/Int2CharOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.ints; + +@FunctionalInterface +public interface Int2CharOperator { + char apply(int var1, char var2); +} diff --git a/src/com/hypixel/fastutil/ints/Int2DoubleOperator.java b/src/com/hypixel/fastutil/ints/Int2DoubleOperator.java new file mode 100644 index 0000000..eb8a32b --- /dev/null +++ b/src/com/hypixel/fastutil/ints/Int2DoubleOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.ints; + +@FunctionalInterface +public interface Int2DoubleOperator { + double apply(int var1, double var2); +} diff --git a/src/com/hypixel/fastutil/ints/Int2FloatOperator.java b/src/com/hypixel/fastutil/ints/Int2FloatOperator.java new file mode 100644 index 0000000..d633725 --- /dev/null +++ b/src/com/hypixel/fastutil/ints/Int2FloatOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.ints; + +@FunctionalInterface +public interface Int2FloatOperator { + float apply(int var1, float var2); +} diff --git a/src/com/hypixel/fastutil/ints/Int2IntOperator.java b/src/com/hypixel/fastutil/ints/Int2IntOperator.java new file mode 100644 index 0000000..b26fd68 --- /dev/null +++ b/src/com/hypixel/fastutil/ints/Int2IntOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.ints; + +@FunctionalInterface +public interface Int2IntOperator { + int apply(int var1, int var2); +} diff --git a/src/com/hypixel/fastutil/ints/Int2LongOperator.java b/src/com/hypixel/fastutil/ints/Int2LongOperator.java new file mode 100644 index 0000000..a4c9baf --- /dev/null +++ b/src/com/hypixel/fastutil/ints/Int2LongOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.ints; + +@FunctionalInterface +public interface Int2LongOperator { + long apply(int var1, long var2); +} diff --git a/src/com/hypixel/fastutil/ints/Int2ObjectConcurrentHashMap.java b/src/com/hypixel/fastutil/ints/Int2ObjectConcurrentHashMap.java new file mode 100644 index 0000000..8414681 --- /dev/null +++ b/src/com/hypixel/fastutil/ints/Int2ObjectConcurrentHashMap.java @@ -0,0 +1,10248 @@ +package com.hypixel.fastutil.ints; + +import com.hypixel.fastutil.FastCollection; +import com.hypixel.fastutil.util.SneakyThrow; +import com.hypixel.fastutil.util.TLRUtil; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntCollection; +import it.unimi.dsi.fastutil.ints.IntIterator; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.IntSpliterator; +import it.unimi.dsi.fastutil.objects.ObjectCollection; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import it.unimi.dsi.fastutil.objects.ObjectSpliterator; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.CountedCompleter; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.DoubleBinaryOperator; +import java.util.function.Function; +import java.util.function.IntBinaryOperator; +import java.util.function.IntConsumer; +import java.util.function.LongBinaryOperator; +import java.util.function.Predicate; +import java.util.function.ToDoubleFunction; +import java.util.function.ToLongFunction; +import sun.misc.Unsafe; + +public class Int2ObjectConcurrentHashMap { + protected static final long serialVersionUID = 7249069246763182397L; + protected static final int MAXIMUM_CAPACITY = 1073741824; + protected static final int DEFAULT_CAPACITY = 16; + protected static final int MAX_ARRAY_SIZE = 2147483639; + protected static final int DEFAULT_CONCURRENCY_LEVEL = 16; + protected static final float LOAD_FACTOR = 0.75F; + protected static final int TREEIFY_THRESHOLD = 8; + protected static final int UNTREEIFY_THRESHOLD = 6; + protected static final int MIN_TREEIFY_CAPACITY = 64; + protected static final int MIN_TRANSFER_STRIDE = 16; + protected static int RESIZE_STAMP_BITS = 16; + protected static final int MAX_RESIZERS = (1 << 32 - RESIZE_STAMP_BITS) - 1; + protected static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS; + protected static final int MOVED = -1; + protected static final int TREEBIN = -2; + protected static final int RESERVED = -3; + protected static final int HASH_BITS = Integer.MAX_VALUE; + protected static final int NCPU = Runtime.getRuntime().availableProcessors(); + protected transient volatile Int2ObjectConcurrentHashMap.Node[] table; + protected transient volatile Int2ObjectConcurrentHashMap.Node[] nextTable; + protected transient volatile long baseCount; + protected transient volatile int sizeCtl; + protected transient volatile int transferIndex; + protected transient volatile int cellsBusy; + protected transient volatile Int2ObjectConcurrentHashMap.CounterCell[] counterCells; + protected transient Int2ObjectConcurrentHashMap.KeySetView keySet; + protected transient Int2ObjectConcurrentHashMap.ValuesView values; + protected transient Int2ObjectConcurrentHashMap.EntrySetView entrySet; + protected final int EMPTY; + protected static final Unsafe U; + protected static final long SIZECTL; + protected static final long TRANSFERINDEX; + protected static final long BASECOUNT; + protected static final long CELLSBUSY; + protected static final long CELLVALUE; + protected static final long ABASE; + protected static final int ASHIFT; + + protected static final int spread(int h) { + return (h ^ h >>> 16) & 2147483647; + } + + protected static final int tableSizeFor(int c) { + int n = c - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return n < 0 ? 1 : (n >= 1073741824 ? 1073741824 : n + 1); + } + + protected static final Int2ObjectConcurrentHashMap.Node tabAt(Int2ObjectConcurrentHashMap.Node[] tab, int i) { + return (Int2ObjectConcurrentHashMap.Node)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); + } + + protected static final boolean casTabAt( + Int2ObjectConcurrentHashMap.Node[] tab, int i, Int2ObjectConcurrentHashMap.Node c, Int2ObjectConcurrentHashMap.Node v + ) { + return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v); + } + + protected static final void setTabAt(Int2ObjectConcurrentHashMap.Node[] tab, int i, Int2ObjectConcurrentHashMap.Node v) { + U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v); + } + + public Int2ObjectConcurrentHashMap() { + this.EMPTY = -1; + } + + public Int2ObjectConcurrentHashMap(boolean nonce, int emptyValue) { + this.EMPTY = emptyValue; + } + + public Int2ObjectConcurrentHashMap(int initialCapacity) { + this(initialCapacity, true, -1); + } + + public Int2ObjectConcurrentHashMap(int initialCapacity, boolean nonce, int emptyValue) { + if (initialCapacity < 0) { + throw new IllegalArgumentException(); + } else { + int cap = initialCapacity >= 536870912 ? 1073741824 : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1); + this.sizeCtl = cap; + this.EMPTY = emptyValue; + } + } + + public Int2ObjectConcurrentHashMap(Map m, int emptyValue) { + this.sizeCtl = 16; + this.EMPTY = emptyValue; + this.putAll(m); + } + + public Int2ObjectConcurrentHashMap(Int2ObjectConcurrentHashMap m) { + this.sizeCtl = 16; + this.EMPTY = m.EMPTY; + this.putAll(m); + } + + public Int2ObjectConcurrentHashMap(Int2ObjectMap m) { + this.sizeCtl = 16; + this.EMPTY = -1; + this.putAll(m); + } + + public Int2ObjectConcurrentHashMap(Int2ObjectMap m, int emptyValue) { + this.sizeCtl = 16; + this.EMPTY = emptyValue; + this.putAll(m); + } + + public Int2ObjectConcurrentHashMap(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, 1, -1); + } + + public Int2ObjectConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel, int emptyValue) { + if (loadFactor > 0.0F && initialCapacity >= 0 && concurrencyLevel > 0) { + if (initialCapacity < concurrencyLevel) { + initialCapacity = concurrencyLevel; + } + + long size = (long)(1.0 + (float)initialCapacity / loadFactor); + int cap = size >= 1073741824L ? 1073741824 : tableSizeFor((int)size); + this.sizeCtl = cap; + this.EMPTY = emptyValue; + } else { + throw new IllegalArgumentException(); + } + } + + public int size() { + long n = this.sumCount(); + return n < 0L ? 0 : (n > 2147483647L ? Integer.MAX_VALUE : (int)n); + } + + public boolean isEmpty() { + return this.sumCount() <= 0L; + } + + public V get(int key) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + int h = spread(Integer.hashCode(key)); + Int2ObjectConcurrentHashMap.Node[] tab = this.table; + Int2ObjectConcurrentHashMap.Node e; + int n; + if (this.table != null && (n = tab.length) > 0 && (e = tabAt(tab, n - 1 & h)) != null) { + int eh = e.hash; + if (e.hash == h) { + int ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + return e.val; + } + } else if (eh < 0) { + Int2ObjectConcurrentHashMap.Node p; + return (p = e.find(h, key)) != null ? p.val : null; + } + + while ((e = e.next) != null) { + if (e.hash == h) { + int ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + return e.val; + } + } + } + } + + return null; + } + } + + public boolean containsKey(int key) { + return this.get(key) != null; + } + + public boolean containsValue(Object value) { + if (value == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label85: + while (true) { + if (p != null) { + next = p; + break; + } + + if (baseIndex < baseLimit) { + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab != null && (n = tab.length) > index && index >= 0) { + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + continue; + } + + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + continue label85; + } + } + } + + next = null; + break; + } + + if (p == null) { + break; + } + + V v = p.val; + if (p.val == value || v != null && value.equals(v)) { + return true; + } + } + } + + return false; + } + } + + public V put(int key, V value) { + return this.putVal(key, value, false); + } + + protected final V putVal(int key, V value, boolean onlyIfAbsent) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value == null) { + throw new NullPointerException(); + } else { + int hash = spread(Integer.hashCode(key)); + int binCount = 0; + Int2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Int2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & hash)) == null) { + if (casTabAt(tab, i, null, new Int2ObjectConcurrentHashMap.Node<>(this.EMPTY, hash, key, value, null))) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + V oldVal = null; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Int2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Int2ObjectConcurrentHashMap.Node p; + if ((p = ((Int2ObjectConcurrentHashMap.TreeBin)f).putTreeVal(hash, key, value)) != null) { + oldVal = p.val; + if (!onlyIfAbsent) { + p.val = value; + } + } + } + } else { + binCount = 1; + Int2ObjectConcurrentHashMap.Node e = f; + + while (true) { + if (e.hash == hash) { + int ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + oldVal = e.val; + if (!onlyIfAbsent) { + e.val = value; + } + break; + } + } + + Int2ObjectConcurrentHashMap.Node pred = e; + if ((e = e.next) == null) { + pred.next = new Int2ObjectConcurrentHashMap.Node<>(this.EMPTY, hash, key, value, null); + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + + if (oldVal != null) { + return oldVal; + } + break; + } + } + } + } + + this.addCount(1L, binCount); + return null; + } + } + + public void putAll(Map m) { + this.tryPresize(m.size()); + + for (Map.Entry e : m.entrySet()) { + this.putVal(e.getKey(), (V)e.getValue(), false); + } + } + + public void putAll(Int2ObjectConcurrentHashMap m) { + this.tryPresize(m.size()); + + for (Int2ObjectMap.Entry e : m.int2ObjectEntrySet()) { + this.putVal(e.getIntKey(), (V)e.getValue(), false); + } + } + + public void putAll(Int2ObjectMap m) { + this.tryPresize(m.size()); + + for (Int2ObjectMap.Entry next : m.int2ObjectEntrySet()) { + this.putVal(next.getIntKey(), (V)next.getValue(), false); + } + } + + public V remove(int key) { + return this.replaceNode(key, null, null); + } + + @Deprecated + public V remove(Integer key) { + return this.replaceNode(key, null, null); + } + + @Deprecated + public V remove(Object key) { + return this.remove((Integer)key); + } + + protected final V replaceNode(int key, V value, Object cv) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + int hash = spread(Integer.hashCode(key)); + Int2ObjectConcurrentHashMap.Node[] tab = this.table; + + Int2ObjectConcurrentHashMap.Node f; + int n; + int i; + while (tab != null && (n = tab.length) != 0 && (f = tabAt(tab, i = n - 1 & hash)) != null) { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + V oldVal = null; + boolean validated = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Int2ObjectConcurrentHashMap.TreeBin) { + validated = true; + Int2ObjectConcurrentHashMap.TreeBin t = (Int2ObjectConcurrentHashMap.TreeBin)f; + Int2ObjectConcurrentHashMap.TreeNode r = t.root; + Int2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(hash, key, null)) != null) { + V pv = p.val; + if (cv == null || cv == pv || pv != null && cv.equals(pv)) { + oldVal = pv; + if (value != null) { + p.val = value; + } else if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } + } else { + validated = true; + Int2ObjectConcurrentHashMap.Node e = f; + Int2ObjectConcurrentHashMap.Node pred = null; + + do { + if (e.hash == hash) { + int ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + V ev = e.val; + if (cv == null || cv == ev || ev != null && cv.equals(ev)) { + oldVal = ev; + if (value != null) { + e.val = value; + } else if (pred != null) { + pred.next = e.next; + } else { + setTabAt(tab, i, e.next); + } + } + break; + } + } + + pred = e; + } while ((e = e.next) != null); + } + } + } + + if (validated) { + if (oldVal != null) { + if (value == null) { + this.addCount(-1L, -1); + } + + return oldVal; + } + break; + } + } + } + + return null; + } + } + + public void clear() { + long delta = 0L; + int i = 0; + Int2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (tab != null && i < tab.length) { + Int2ObjectConcurrentHashMap.Node f = tabAt(tab, i); + if (f == null) { + i++; + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + i = 0; + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + for (Int2ObjectConcurrentHashMap.Node p = (Int2ObjectConcurrentHashMap.Node)(fh >= 0 + ? f + : (f instanceof Int2ObjectConcurrentHashMap.TreeBin ? ((Int2ObjectConcurrentHashMap.TreeBin)f).first : null)); + p != null; + p = p.next + ) { + delta--; + } + + setTabAt(tab, i++, null); + } + } + } + } + } + + if (delta != 0L) { + this.addCount(delta, -1); + } + } + + public Int2ObjectConcurrentHashMap.KeySetView keySet() { + Int2ObjectConcurrentHashMap.KeySetView ks = this.keySet; + return this.keySet != null ? ks : (this.keySet = this.buildKeySetView()); + } + + protected Int2ObjectConcurrentHashMap.KeySetView buildKeySetView() { + return new Int2ObjectConcurrentHashMap.KeySetView<>(this, null); + } + + public FastCollection values() { + Int2ObjectConcurrentHashMap.ValuesView vs = this.values; + return this.values != null ? vs : (this.values = this.buildValuesView()); + } + + protected Int2ObjectConcurrentHashMap.ValuesView buildValuesView() { + return new Int2ObjectConcurrentHashMap.ValuesView<>(this); + } + + public ObjectSet> int2ObjectEntrySet() { + Int2ObjectConcurrentHashMap.EntrySetView es = this.entrySet; + return this.entrySet != null ? es : (this.entrySet = this.buildEntrySetView()); + } + + @Deprecated + public ObjectSet> entrySet() { + return this.int2ObjectEntrySet(); + } + + protected Int2ObjectConcurrentHashMap.EntrySetView buildEntrySetView() { + return new Int2ObjectConcurrentHashMap.EntrySetView<>(this); + } + + @Override + public int hashCode() { + int h = 0; + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label75: { + while (true) { + if (p != null) { + next = p; + break label75; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + h += Integer.hashCode(p.key) ^ p.val.hashCode(); + } + } + + return h; + } + + @Override + public String toString() { + Int2ObjectConcurrentHashMap.Node[] t = this.table; + int f = this.table == null ? 0 : t.length; + Int2ObjectConcurrentHashMap.Traverser it = new Int2ObjectConcurrentHashMap.Traverser<>(t, f, 0, f); + StringBuilder sb = new StringBuilder(); + sb.append('{'); + Int2ObjectConcurrentHashMap.Node p; + if ((p = it.advance()) != null) { + while (true) { + int k = p.key; + V v = p.val; + sb.append(k); + sb.append('='); + sb.append(v == this ? "(this Map)" : v); + if ((p = it.advance()) == null) { + break; + } + + sb.append(',').append(' '); + } + } + + return sb.append('}').toString(); + } + + @Override + public boolean equals(Object o) { + if (o != this) { + if (!(o instanceof Int2ObjectConcurrentHashMap m)) { + return false; + } + + Int2ObjectConcurrentHashMap.Node[] t = this.table; + int f = this.table == null ? 0 : t.length; + Int2ObjectConcurrentHashMap.Traverser it = new Int2ObjectConcurrentHashMap.Traverser<>(t, f, 0, f); + + Int2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + V val = p.val; + Object v = m.get(p.key); + if (v == null || v != val && !v.equals(val)) { + return false; + } + } + + for (Int2ObjectMap.Entry e : m.int2ObjectEntrySet()) { + Object mv; + Object v; + int mk; + if ((mk = e.getIntKey()) == m.EMPTY || (mv = e.getValue()) == null || (v = this.get(mk)) == null || mv != v && !mv.equals(v)) { + return false; + } + } + } + + return true; + } + + public V putIfAbsent(int key, V value) { + return this.putVal(key, value, true); + } + + public boolean remove(int key, Object value) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + return value != null && this.replaceNode(key, null, value) != null; + } + } + + public boolean replace(int key, V oldValue, V newValue) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (oldValue != null && newValue != null) { + return this.replaceNode(key, newValue, oldValue) != null; + } else { + throw new NullPointerException(); + } + } + + public V replace(int key, V value) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value == null) { + throw new NullPointerException(); + } else { + return this.replaceNode(key, value, null); + } + } + + public V getOrDefault(int key, V defaultValue) { + V v; + return (v = this.get(key)) == null ? defaultValue : v; + } + + public int forEach(Int2ObjectConcurrentHashMap.IntObjConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val); + count++; + } + } + + return count; + } + } + + public int forEach(Int2ObjectConcurrentHashMap.IntBiObjConsumer action, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, x); + count++; + } + } + + return count; + } + } + + public int forEach(Int2ObjectConcurrentHashMap.IntTriObjConsumer action, X x, Y y) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, x, y); + count++; + } + } + + return count; + } + } + + public int forEachWithByte(Int2ObjectConcurrentHashMap.IntObjByteConsumer action, byte ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithShort(Int2ObjectConcurrentHashMap.IntObjShortConsumer action, short ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithInt(Int2ObjectConcurrentHashMap.IntObjIntConsumer action, int ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithLong(Int2ObjectConcurrentHashMap.IntObjLongConsumer action, long ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithFloat(Int2ObjectConcurrentHashMap.IntObjFloatConsumer action, float ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithDouble(Int2ObjectConcurrentHashMap.IntObjDoubleConsumer action, double ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithByte(Int2ObjectConcurrentHashMap.IntBiObjByteConsumer action, byte ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithShort(Int2ObjectConcurrentHashMap.IntBiObjShortConsumer action, short ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithInt(Int2ObjectConcurrentHashMap.IntBiObjIntConsumer action, int ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithLong(Int2ObjectConcurrentHashMap.IntBiObjLongConsumer action, long ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithFloat(Int2ObjectConcurrentHashMap.IntBiObjFloatConsumer action, float ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithDouble(Int2ObjectConcurrentHashMap.IntBiObjDoubleConsumer action, double ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public void replaceAll(Int2ObjectOperator function) { + if (function == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label86: { + while (true) { + if (p != null) { + next = p; + break label86; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + V oldValue = p.val; + int key = p.key; + + V newValue; + do { + newValue = function.apply(key, oldValue); + if (newValue == null) { + throw new NullPointerException(); + } + } while (this.replaceNode(key, newValue, oldValue) == null && (oldValue = this.get(key)) != null); + } + } + } + } + + public V computeIfAbsent(int key, Int2ObjectConcurrentHashMap.IntFunction mappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (mappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Integer.hashCode(key)); + V val = null; + int binCount = 0; + Int2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Int2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + Int2ObjectConcurrentHashMap.Node r = new Int2ObjectConcurrentHashMap.ReservationNode<>(this.EMPTY); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Int2ObjectConcurrentHashMap.Node node = null; + + try { + if ((val = (V)mappingFunction.apply(key)) != null) { + node = new Int2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + } finally { + setTabAt(tab, i, node); + } + } + } + + if (binCount != 0) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + boolean added = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Int2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Int2ObjectConcurrentHashMap.TreeBin t = (Int2ObjectConcurrentHashMap.TreeBin)f; + Int2ObjectConcurrentHashMap.TreeNode r = t.root; + Int2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(h, key, null)) != null) { + val = p.val; + } else if ((val = (V)mappingFunction.apply(key)) != null) { + added = true; + t.putTreeVal(h, key, val); + } + } + } else { + binCount = 1; + Int2ObjectConcurrentHashMap.Node e = f; + + while (true) { + if (e.hash == h) { + int ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = e.val; + break; + } + } + + Int2ObjectConcurrentHashMap.Node pred = e; + if ((e = e.next) == null) { + if ((val = (V)mappingFunction.apply(key)) != null) { + added = true; + pred.next = new Int2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + + if (!added) { + return val; + } + break; + } + } + } + } + + if (val != null) { + this.addCount(1L, binCount); + } + + return val; + } + } + + public V computeIfPresent(int key, Int2ObjectConcurrentHashMap.IntObjFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (remappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Integer.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Int2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Int2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + break; + } + + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Int2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Int2ObjectConcurrentHashMap.TreeBin t = (Int2ObjectConcurrentHashMap.TreeBin)f; + Int2ObjectConcurrentHashMap.TreeNode r = t.root; + Int2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(h, key, null)) != null) { + val = (V)remappingFunction.apply(key, p.val); + if (val != null) { + p.val = val; + } else { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } + } else { + binCount = 1; + Int2ObjectConcurrentHashMap.Node e = f; + Int2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + int ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(key, e.val); + if (val != null) { + e.val = val; + } else { + delta = -1; + Int2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + break; + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } + } + + public V compute(int key, Int2ObjectConcurrentHashMap.IntObjFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (remappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Integer.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Int2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Int2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + Int2ObjectConcurrentHashMap.Node r = new Int2ObjectConcurrentHashMap.ReservationNode<>(this.EMPTY); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Int2ObjectConcurrentHashMap.Node node = null; + + try { + if ((val = (V)remappingFunction.apply(key, null)) != null) { + delta = 1; + node = new Int2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + } finally { + setTabAt(tab, i, node); + } + } + } + + if (binCount != 0) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Int2ObjectConcurrentHashMap.TreeBin) { + binCount = 1; + Int2ObjectConcurrentHashMap.TreeBin t = (Int2ObjectConcurrentHashMap.TreeBin)f; + Int2ObjectConcurrentHashMap.TreeNode r = t.root; + Int2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null) { + p = r.findTreeNode(h, key, null); + } else { + p = null; + } + + V pv = p == null ? null : p.val; + val = (V)remappingFunction.apply(key, pv); + if (val != null) { + if (p != null) { + p.val = val; + } else { + delta = 1; + t.putTreeVal(h, key, val); + } + } else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } else { + binCount = 1; + Int2ObjectConcurrentHashMap.Node e = f; + Int2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + int ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(key, e.val); + if (val != null) { + e.val = val; + } else { + delta = -1; + Int2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + val = (V)remappingFunction.apply(key, null); + if (val != null) { + delta = 1; + pred.next = new Int2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + break; + } + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } + } + + public V merge(int key, V value, BiFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value != null && remappingFunction != null) { + int h = spread(Integer.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Int2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Int2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + if (casTabAt(tab, i, null, new Int2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, value, null))) { + delta = 1; + val = value; + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Int2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Int2ObjectConcurrentHashMap.TreeBin t = (Int2ObjectConcurrentHashMap.TreeBin)f; + Int2ObjectConcurrentHashMap.TreeNode r = t.root; + Int2ObjectConcurrentHashMap.TreeNode p = r == null ? null : r.findTreeNode(h, key, null); + val = p == null ? value : remappingFunction.apply(p.val, value); + if (val != null) { + if (p != null) { + p.val = val; + } else { + delta = 1; + t.putTreeVal(h, key, val); + } + } else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } else { + binCount = 1; + Int2ObjectConcurrentHashMap.Node e = f; + Int2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + int ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(e.val, value); + if (val != null) { + e.val = val; + } else { + delta = -1; + Int2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + delta = 1; + val = value; + pred.next = new Int2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, value, null); + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + break; + } + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } else { + throw new NullPointerException(); + } + } + + public long mappingCount() { + long n = this.sumCount(); + return n < 0L ? 0L : n; + } + + public static IntSet newKeySet() { + return new Int2ObjectConcurrentHashMap.KeySetView<>(new Int2ObjectConcurrentHashMap<>(), Boolean.TRUE); + } + + public static Int2ObjectConcurrentHashMap.KeySetView newKeySet(int initialCapacity) { + return new Int2ObjectConcurrentHashMap.KeySetView<>(new Int2ObjectConcurrentHashMap<>(initialCapacity), Boolean.TRUE); + } + + public Int2ObjectConcurrentHashMap.KeySetView keySet(V mappedValue) { + if (mappedValue == null) { + throw new NullPointerException(); + } else { + return new Int2ObjectConcurrentHashMap.KeySetView<>(this, mappedValue); + } + } + + protected static final int resizeStamp(int n) { + return Integer.numberOfLeadingZeros(n) | 1 << RESIZE_STAMP_BITS - 1; + } + + protected final Int2ObjectConcurrentHashMap.Node[] initTable() { + Int2ObjectConcurrentHashMap.Node[] tab; + while (true) { + tab = this.table; + if (this.table != null && tab.length != 0) { + break; + } + + int sc = this.sizeCtl; + if (this.sizeCtl < 0) { + Thread.yield(); + } else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + tab = this.table; + if (this.table == null || tab.length == 0) { + int n = sc > 0 ? sc : 16; + Int2ObjectConcurrentHashMap.Node[] nt = new Int2ObjectConcurrentHashMap.Node[n]; + tab = nt; + this.table = nt; + sc = n - (n >>> 2); + } + break; + } finally { + this.sizeCtl = sc; + } + } + } + + return tab; + } + + protected final void addCount(long x, int check) { + boolean uncontended; + label77: { + long s; + label74: { + Int2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + if (this.counterCells == null) { + long b = this.baseCount; + if (U.compareAndSwapLong(this, BASECOUNT, this.baseCount, s = b + x)) { + break label74; + } + } + + uncontended = (boolean)1; + Int2ObjectConcurrentHashMap.CounterCell a; + int m; + if (as == null || (m = as.length - 1) < 0 || (a = as[TLRUtil.getProbe() & m]) == null) { + break label77; + } + + long v = a.value; + if (!(uncontended = U.compareAndSwapLong(a, CELLVALUE, a.value, v + x))) { + break label77; + } + + if (check <= 1) { + return; + } + + s = this.sumCount(); + } + + if (check >= 0) { + while (true) { + int sc = this.sizeCtl; + if (s < this.sizeCtl) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] tab = this.table; + int n; + if (this.table == null || (n = tab.length) >= 1073741824) { + break; + } + + uncontended = (boolean)resizeStamp(n); + if (sc < 0) { + if (sc >>> RESIZE_STAMP_SHIFT != uncontended || sc == uncontended + 1 || sc == uncontended + MAX_RESIZERS) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] nt = this.nextTable; + if (this.nextTable == null || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nt); + } + } else if (U.compareAndSwapInt(this, SIZECTL, sc, (uncontended << RESIZE_STAMP_SHIFT) + 2)) { + this.transfer(tab, null); + } + + s = this.sumCount(); + } + } + + return; + } + + this.fullAddCount(x, uncontended); + } + + protected final Int2ObjectConcurrentHashMap.Node[] helpTransfer(Int2ObjectConcurrentHashMap.Node[] tab, Int2ObjectConcurrentHashMap.Node f) { + if (tab != null && f instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + Int2ObjectConcurrentHashMap.Node[] nextTab = ((Int2ObjectConcurrentHashMap.ForwardingNode)f).nextTable; + if (((Int2ObjectConcurrentHashMap.ForwardingNode)f).nextTable != null) { + int rs = resizeStamp(tab.length); + + while (nextTab == this.nextTable && this.table == tab) { + int sc = this.sizeCtl; + if (this.sizeCtl >= 0 || sc >>> RESIZE_STAMP_SHIFT != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nextTab); + break; + } + } + + return nextTab; + } + } + + return this.table; + } + + protected final void tryPresize(int size) { + int c = size >= 536870912 ? 1073741824 : tableSizeFor(size + (size >>> 1) + 1); + + while (true) { + int sc = this.sizeCtl; + if (this.sizeCtl < 0) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] tab = this.table; + int n; + if (tab != null && (n = tab.length) != 0) { + if (c <= sc || n >= 1073741824) { + break; + } + + if (tab == this.table) { + int rs = resizeStamp(n); + if (sc < 0) { + if (sc >>> RESIZE_STAMP_SHIFT != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] nt = this.nextTable; + if (this.nextTable == null || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nt); + } + } else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) { + this.transfer(tab, null); + } + } + } else { + n = sc > c ? sc : c; + if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + if (this.table == tab) { + Int2ObjectConcurrentHashMap.Node[] ntx = new Int2ObjectConcurrentHashMap.Node[n]; + this.table = ntx; + sc = n - (n >>> 2); + } + } finally { + this.sizeCtl = sc; + } + } + } + } + } + + protected final void transfer(Int2ObjectConcurrentHashMap.Node[] tab, Int2ObjectConcurrentHashMap.Node[] nextTab) { + int n = tab.length; + int stride; + if ((stride = NCPU > 1 ? (n >>> 3) / NCPU : n) < 16) { + stride = 16; + } + + if (nextTab == null) { + try { + Int2ObjectConcurrentHashMap.Node[] nt = new Int2ObjectConcurrentHashMap.Node[n << 1]; + nextTab = nt; + } catch (Throwable var27) { + this.sizeCtl = Integer.MAX_VALUE; + return; + } + + this.nextTable = nextTab; + this.transferIndex = n; + } + + int nextn = nextTab.length; + Int2ObjectConcurrentHashMap.ForwardingNode fwd = new Int2ObjectConcurrentHashMap.ForwardingNode<>(this.EMPTY, nextTab); + boolean advance = true; + boolean finishing = false; + int i = 0; + int bound = 0; + + while (true) { + while (!advance) { + if (i >= 0 && i < n && i + n < nextn) { + Int2ObjectConcurrentHashMap.Node f; + if ((f = tabAt(tab, i)) == null) { + advance = casTabAt(tab, i, null, fwd); + } else { + int fh = f.hash; + if (f.hash == -1) { + advance = true; + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + int runBit = fh & n; + Int2ObjectConcurrentHashMap.Node lastRun = f; + + for (Int2ObjectConcurrentHashMap.Node p = f.next; p != null; p = p.next) { + int b = p.hash & n; + if (b != runBit) { + runBit = b; + lastRun = p; + } + } + + Int2ObjectConcurrentHashMap.Node ln; + Int2ObjectConcurrentHashMap.Node hn; + if (runBit == 0) { + ln = lastRun; + hn = null; + } else { + hn = lastRun; + ln = null; + } + + for (Int2ObjectConcurrentHashMap.Node px = f; px != lastRun; px = px.next) { + int ph = px.hash; + int pk = px.key; + V pv = px.val; + if ((ph & n) == 0) { + ln = new Int2ObjectConcurrentHashMap.Node<>(this.EMPTY, ph, pk, pv, ln); + } else { + hn = new Int2ObjectConcurrentHashMap.Node<>(this.EMPTY, ph, pk, pv, hn); + } + } + + setTabAt(nextTab, i, ln); + setTabAt(nextTab, i + n, hn); + setTabAt(tab, i, fwd); + advance = true; + } else if (f instanceof Int2ObjectConcurrentHashMap.TreeBin t) { + Int2ObjectConcurrentHashMap.TreeNode lo = null; + Int2ObjectConcurrentHashMap.TreeNode loTail = null; + Int2ObjectConcurrentHashMap.TreeNode hi = null; + Int2ObjectConcurrentHashMap.TreeNode hiTail = null; + int lc = 0; + int hc = 0; + + for (Int2ObjectConcurrentHashMap.Node e = t.first; e != null; e = e.next) { + int h = e.hash; + Int2ObjectConcurrentHashMap.TreeNode pxx = new Int2ObjectConcurrentHashMap.TreeNode<>( + this.EMPTY, h, e.key, e.val, null, null + ); + if ((h & n) == 0) { + if ((pxx.prev = loTail) == null) { + lo = pxx; + } else { + loTail.next = pxx; + } + + loTail = pxx; + lc++; + } else { + if ((pxx.prev = hiTail) == null) { + hi = pxx; + } else { + hiTail.next = pxx; + } + + hiTail = pxx; + hc++; + } + } + + Int2ObjectConcurrentHashMap.Node ln = (Int2ObjectConcurrentHashMap.Node)(lc <= 6 + ? this.untreeify(lo) + : (hc != 0 ? new Int2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, lo) : t)); + Int2ObjectConcurrentHashMap.Node hn = (Int2ObjectConcurrentHashMap.Node)(hc <= 6 + ? this.untreeify(hi) + : (lc != 0 ? new Int2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, hi) : t)); + setTabAt(nextTab, i, ln); + setTabAt(nextTab, i + n, hn); + setTabAt(tab, i, fwd); + advance = true; + } + } + } + } + } + } else { + if (finishing) { + this.nextTable = null; + this.table = nextTab; + this.sizeCtl = (n << 1) - (n >>> 1); + return; + } + + int sc = this.sizeCtl; + if (U.compareAndSwapInt(this, SIZECTL, this.sizeCtl, sc - 1)) { + if (sc - 2 != resizeStamp(n) << RESIZE_STAMP_SHIFT) { + return; + } + + advance = true; + finishing = true; + i = n; + } + } + } + + if (--i < bound && !finishing) { + int nextIndex = this.transferIndex; + if (this.transferIndex <= 0) { + i = -1; + advance = false; + } else { + int nextBound; + if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex, nextBound = nextIndex > stride ? nextIndex - stride : 0)) { + bound = nextBound; + i = nextIndex - 1; + advance = false; + } + } + } else { + advance = false; + } + } + } + + protected final long sumCount() { + Int2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + long sum = this.baseCount; + if (as != null) { + for (int i = 0; i < as.length; i++) { + Int2ObjectConcurrentHashMap.CounterCell a; + if ((a = as[i]) != null) { + sum += a.value; + } + } + } + + return sum; + } + + protected final void fullAddCount(long x, boolean wasUncontended) { + int h; + if ((h = TLRUtil.getProbe()) == 0) { + TLRUtil.localInit(); + h = TLRUtil.getProbe(); + wasUncontended = true; + } + + boolean collide = false; + + while (true) { + Int2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + int n; + if (this.counterCells != null && (n = as.length) > 0) { + Int2ObjectConcurrentHashMap.CounterCell a; + if ((a = as[n - 1 & h]) == null) { + if (this.cellsBusy == 0) { + Int2ObjectConcurrentHashMap.CounterCell r = new Int2ObjectConcurrentHashMap.CounterCell(x); + if (this.cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean created = false; + + try { + Int2ObjectConcurrentHashMap.CounterCell[] rs = this.counterCells; + int m; + int j; + if (this.counterCells != null && (m = rs.length) > 0 && rs[j = m - 1 & h] == null) { + rs[j] = r; + created = true; + } + } finally { + this.cellsBusy = 0; + } + + if (created) { + break; + } + continue; + } + } + + collide = false; + } else if (!wasUncontended) { + wasUncontended = true; + } else { + long v = a.value; + if (U.compareAndSwapLong(a, CELLVALUE, a.value, v + x)) { + break; + } + + if (this.counterCells != as || n >= NCPU) { + collide = false; + } else if (!collide) { + collide = true; + } else if (this.cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + try { + if (this.counterCells == as) { + Int2ObjectConcurrentHashMap.CounterCell[] rs = new Int2ObjectConcurrentHashMap.CounterCell[n << 1]; + + for (int i = 0; i < n; i++) { + rs[i] = as[i]; + } + + this.counterCells = rs; + } + } finally { + this.cellsBusy = 0; + } + + collide = false; + continue; + } + } + + h = TLRUtil.advanceProbe(h); + } else if (this.cellsBusy == 0 && this.counterCells == as && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean init = false; + + try { + if (this.counterCells == as) { + Int2ObjectConcurrentHashMap.CounterCell[] rs = new Int2ObjectConcurrentHashMap.CounterCell[2]; + rs[h & 1] = new Int2ObjectConcurrentHashMap.CounterCell(x); + this.counterCells = rs; + init = true; + } + } finally { + this.cellsBusy = 0; + } + + if (init) { + break; + } + } else { + long vx = this.baseCount; + if (U.compareAndSwapLong(this, BASECOUNT, this.baseCount, vx + x)) { + break; + } + } + } + } + + protected final void treeifyBin(Int2ObjectConcurrentHashMap.Node[] tab, int index) { + if (tab != null) { + int n; + if ((n = tab.length) < 64) { + this.tryPresize(n << 1); + } else { + Int2ObjectConcurrentHashMap.Node b; + if ((b = tabAt(tab, index)) != null && b.hash >= 0) { + synchronized (b) { + if (tabAt(tab, index) == b) { + Int2ObjectConcurrentHashMap.TreeNode hd = null; + Int2ObjectConcurrentHashMap.TreeNode tl = null; + + for (Int2ObjectConcurrentHashMap.Node e = b; e != null; e = e.next) { + Int2ObjectConcurrentHashMap.TreeNode p = new Int2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, e.hash, e.key, e.val, null, null); + if ((p.prev = tl) == null) { + hd = p; + } else { + tl.next = p; + } + + tl = p; + } + + setTabAt(tab, index, new Int2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, hd)); + } + } + } + } + } + } + + protected Int2ObjectConcurrentHashMap.Node untreeify(Int2ObjectConcurrentHashMap.Node b) { + Int2ObjectConcurrentHashMap.Node hd = null; + Int2ObjectConcurrentHashMap.Node tl = null; + + for (Int2ObjectConcurrentHashMap.Node q = b; q != null; q = q.next) { + Int2ObjectConcurrentHashMap.Node p = new Int2ObjectConcurrentHashMap.Node<>(this.EMPTY, q.hash, q.key, q.val, null); + if (tl == null) { + hd = p; + } else { + tl.next = p; + } + + tl = p; + } + + return hd; + } + + protected final int batchFor(long b) { + long n; + if (b != Long.MAX_VALUE && (n = this.sumCount()) > 1L && n >= b) { + int sp = ForkJoinPool.getCommonPoolParallelism() << 2; + long var6; + return b > 0L && (var6 = n / b) < sp ? (int)var6 : sp; + } else { + return 0; + } + } + + public void forEach(long parallelismThreshold, Int2ObjectConcurrentHashMap.IntObjConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Int2ObjectConcurrentHashMap.ForEachMappingTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEach( + long parallelismThreshold, Int2ObjectConcurrentHashMap.IntObjFunction transformer, Consumer action + ) { + if (transformer != null && action != null) { + new Int2ObjectConcurrentHashMap.ForEachTransformedMappingTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U search(long parallelismThreshold, Int2ObjectConcurrentHashMap.IntObjFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Int2ObjectConcurrentHashMap.SearchMappingsTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public U search(Int2ObjectConcurrentHashMap.IntObjFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U search(Int2ObjectConcurrentHashMap.IntBiObjFunction searchFunction, X x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithByte(Int2ObjectConcurrentHashMap.IntObjByteFunction searchFunction, byte x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithShort(Int2ObjectConcurrentHashMap.IntObjShortFunction searchFunction, short x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithInt(Int2ObjectConcurrentHashMap.IntObjIntFunction searchFunction, int x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithLong(Int2ObjectConcurrentHashMap.IntObjLongFunction searchFunction, long x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithFloat(Int2ObjectConcurrentHashMap.IntObjFloatFunction searchFunction, float x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithDouble(Int2ObjectConcurrentHashMap.IntObjDoubleFunction searchFunction, double x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U reduce( + long parallelismThreshold, + Int2ObjectConcurrentHashMap.IntObjFunction transformer, + BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Int2ObjectConcurrentHashMap.MapReduceMappingsTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U reduce(Int2ObjectConcurrentHashMap.IntObjFunction transformer, BiFunction reducer) { + if (transformer != null && reducer != null) { + Int2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table == null) { + return null; + } else { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + U r = null; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label88: { + while (true) { + if (p != null) { + next = p; + break label88; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + return r; + } + + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + } + } else { + throw new NullPointerException(); + } + } + + public double reduceToDouble( + long parallelismThreshold, Int2ObjectConcurrentHashMap.ToDoubleIntObjFunction transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Int2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceToLong( + long parallelismThreshold, Int2ObjectConcurrentHashMap.ToLongIntObjFunction transformer, long basis, LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Int2ObjectConcurrentHashMap.MapReduceMappingsToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceToInt( + long parallelismThreshold, Int2ObjectConcurrentHashMap.ToIntIntObjFunction transformer, int basis, IntBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Int2ObjectConcurrentHashMap.MapReduceMappingsToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachKey(long parallelismThreshold, IntConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Int2ObjectConcurrentHashMap.ForEachKeyTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachKey(long parallelismThreshold, Int2ObjectConcurrentHashMap.IntFunction transformer, Consumer action) { + if (transformer != null && action != null) { + new Int2ObjectConcurrentHashMap.ForEachTransformedKeyTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action).invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchKeys(long parallelismThreshold, Int2ObjectConcurrentHashMap.IntFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Int2ObjectConcurrentHashMap.SearchKeysTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public int reduceKeys(long parallelismThreshold, Int2ObjectConcurrentHashMap.IntegerReduceTaskOperator reducer) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Int2ObjectConcurrentHashMap.ReduceKeysTask<>(this.EMPTY, null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer) + .invoke0(); + } + } + + public U reduceKeys( + long parallelismThreshold, Int2ObjectConcurrentHashMap.IntFunction transformer, BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Int2ObjectConcurrentHashMap.MapReduceKeysTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceKeysToDouble( + long parallelismThreshold, Int2ObjectConcurrentHashMap.IntToDoubleFunction transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Int2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceKeysToLong(long parallelismThreshold, Int2ObjectConcurrentHashMap.IntToLongFunction transformer, long basis, LongBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Int2ObjectConcurrentHashMap.MapReduceKeysToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceKeysToInt(long parallelismThreshold, Int2ObjectConcurrentHashMap.IntToIntFunction transformer, int basis, IntBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Int2ObjectConcurrentHashMap.MapReduceKeysToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachValue(long parallelismThreshold, Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Int2ObjectConcurrentHashMap.ForEachValueTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachValue(long parallelismThreshold, Function transformer, Consumer action) { + if (transformer != null && action != null) { + new Int2ObjectConcurrentHashMap.ForEachTransformedValueTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchValues(long parallelismThreshold, Function searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Int2ObjectConcurrentHashMap.SearchValuesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public V reduceValues(long parallelismThreshold, BiFunction reducer) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Int2ObjectConcurrentHashMap.ReduceValuesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer).invoke(); + } + } + + public U reduceValues(long parallelismThreshold, Function transformer, BiFunction reducer) { + if (transformer != null && reducer != null) { + return new Int2ObjectConcurrentHashMap.MapReduceValuesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceValuesToDouble(long parallelismThreshold, ToDoubleFunction transformer, double basis, DoubleBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Int2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceValuesToLong(long parallelismThreshold, ToLongFunction transformer, long basis, LongBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Int2ObjectConcurrentHashMap.MapReduceValuesToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceValuesToInt( + long parallelismThreshold, Int2ObjectConcurrentHashMap.ToIntFunction transformer, int basis, IntBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Int2ObjectConcurrentHashMap.MapReduceValuesToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachEntry(long parallelismThreshold, Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Int2ObjectConcurrentHashMap.ForEachEntryTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachEntry(long parallelismThreshold, Function, ? extends U> transformer, Consumer action) { + if (transformer != null && action != null) { + new Int2ObjectConcurrentHashMap.ForEachTransformedEntryTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchEntries(long parallelismThreshold, Function, ? extends U> searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Int2ObjectConcurrentHashMap.SearchEntriesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public Int2ObjectConcurrentHashMap.Entry reduceEntries( + long parallelismThreshold, + BiFunction, Int2ObjectConcurrentHashMap.Entry, ? extends Int2ObjectConcurrentHashMap.Entry> reducer + ) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Int2ObjectConcurrentHashMap.ReduceEntriesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer).invoke(); + } + } + + public U reduceEntries( + long parallelismThreshold, Function, ? extends U> transformer, BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Int2ObjectConcurrentHashMap.MapReduceEntriesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceEntriesToDouble( + long parallelismThreshold, ToDoubleFunction> transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Int2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceEntriesToLong( + long parallelismThreshold, ToLongFunction> transformer, long basis, LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Int2ObjectConcurrentHashMap.MapReduceEntriesToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceEntriesToInt( + long parallelismThreshold, + Int2ObjectConcurrentHashMap.ToIntFunction> transformer, + int basis, + IntBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Int2ObjectConcurrentHashMap.MapReduceEntriesToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public V valueMatching(Predicate predicate) { + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + Int2ObjectConcurrentHashMap.Node[] tab = this.table; + int f = this.table == null ? 0 : tab.length; + int baseLimit = f; + int baseSize = f; + boolean b = false; + + label80: + while (next != null || !b) { + b |= true; + Int2ObjectConcurrentHashMap.Node e = next; + if (next != null) { + e = next.next; + } + + label76: + while (e == null) { + if (baseIndex < baseLimit) { + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab != null && (n = tab.length) > index && index >= 0) { + if ((e = tabAt(tab, index)) != null && e.hash < 0) { + if (e instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (e instanceof Int2ObjectConcurrentHashMap.TreeBin) { + e = ((Int2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + continue; + } + + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack next1 = stack.next; + stack.next = spare; + stack = next1; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + continue label76; + } + } + } + + next = null; + continue label80; + } + + next = e; + if (predicate.test(e.val)) { + return e.val; + } + } + + return null; + } + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + U = (Unsafe)f.get(null); + Class k = Int2ObjectConcurrentHashMap.class; + SIZECTL = U.objectFieldOffset(k.getDeclaredField("sizeCtl")); + TRANSFERINDEX = U.objectFieldOffset(k.getDeclaredField("transferIndex")); + BASECOUNT = U.objectFieldOffset(k.getDeclaredField("baseCount")); + CELLSBUSY = U.objectFieldOffset(k.getDeclaredField("cellsBusy")); + Class ck = Int2ObjectConcurrentHashMap.CounterCell.class; + CELLVALUE = U.objectFieldOffset(ck.getDeclaredField("value")); + Class ak = Int2ObjectConcurrentHashMap.Node[].class; + ABASE = U.arrayBaseOffset(ak); + int scale = U.arrayIndexScale(ak); + if ((scale & scale - 1) != 0) { + throw new Error("data type scale not a power of two"); + } else { + ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); + } + } catch (Exception var5) { + throw new Error(var5); + } + } + + protected static class BaseIterator extends Int2ObjectConcurrentHashMap.Traverser { + public final Int2ObjectConcurrentHashMap map; + public Int2ObjectConcurrentHashMap.Node lastReturned; + + public BaseIterator(Int2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, Int2ObjectConcurrentHashMap map) { + super(tab, size, index, limit); + this.map = map; + this.advance(); + } + + public final boolean hasNext() { + return this.next != null; + } + + public final boolean hasMoreElements() { + return this.next != null; + } + + public final void remove() { + Int2ObjectConcurrentHashMap.Node p = this.lastReturned; + if (this.lastReturned == null) { + throw new IllegalStateException(); + } else { + this.lastReturned = null; + this.map.replaceNode(p.key, null, null); + } + } + } + + protected abstract static class BulkTask extends CountedCompleter { + public Int2ObjectConcurrentHashMap.Node[] tab; + public Int2ObjectConcurrentHashMap.Node next; + public Int2ObjectConcurrentHashMap.TableStack stack; + public Int2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + public int batch; + + protected BulkTask(Int2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Int2ObjectConcurrentHashMap.Node[] t) { + super(par); + this.batch = b; + this.index = this.baseIndex = i; + if ((this.tab = t) == null) { + this.baseSize = this.baseLimit = 0; + } else if (par == null) { + this.baseSize = this.baseLimit = t.length; + } else { + this.baseLimit = f; + this.baseSize = par.baseSize; + } + } + + protected final Int2ObjectConcurrentHashMap.Node advance() { + Int2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Int2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Int2ObjectConcurrentHashMap.TreeBin) { + e = ((Int2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Int2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Int2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Int2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + } + + protected abstract static class CollectionView implements ObjectCollection, Serializable { + public static final long serialVersionUID = 7249069246763182397L; + public final Int2ObjectConcurrentHashMap map; + protected static final String oomeMsg = "Required array size too large"; + + public CollectionView(Int2ObjectConcurrentHashMap map) { + this.map = map; + } + + public Int2ObjectConcurrentHashMap getMap() { + return this.map; + } + + @Override + public final void clear() { + this.map.clear(); + } + + @Override + public final int size() { + return this.map.size(); + } + + @Override + public final boolean isEmpty() { + return this.map.isEmpty(); + } + + @Override + public abstract ObjectIterator iterator(); + + @Override + public abstract boolean contains(Object var1); + + @Override + public abstract boolean remove(Object var1); + + @Override + public final Object[] toArray() { + long sz = this.map.mappingCount(); + if (sz > 2147483639L) { + throw new OutOfMemoryError("Required array size too large"); + } else { + int n = (int)sz; + Object[] r = new Object[n]; + int i = 0; + + for (E e : this) { + if (i == n) { + if (n >= 2147483639) { + throw new OutOfMemoryError("Required array size too large"); + } + + if (n >= 1073741819) { + n = 2147483639; + } else { + n += (n >>> 1) + 1; + } + + r = Arrays.copyOf(r, n); + } + + r[i++] = e; + } + + return i == n ? r : Arrays.copyOf(r, i); + } + } + + @Override + public final T[] toArray(T[] a) { + long sz = this.map.mappingCount(); + if (sz > 2147483639L) { + throw new OutOfMemoryError("Required array size too large"); + } else { + int m = (int)sz; + T[] r = (T[])(a.length >= m ? a : (Object[])Array.newInstance(a.getClass().getComponentType(), m)); + int n = r.length; + int i = 0; + + for (E e : this) { + if (i == n) { + if (n >= 2147483639) { + throw new OutOfMemoryError("Required array size too large"); + } + + if (n >= 1073741819) { + n = 2147483639; + } else { + n += (n >>> 1) + 1; + } + + r = (T[])Arrays.copyOf(r, n); + } + + r[i++] = (T)e; + } + + if (a == r && i < n) { + r[i] = null; + return r; + } else { + return (T[])(i == n ? r : Arrays.copyOf(r, i)); + } + } + } + + @Override + public final String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + Iterator it = this.iterator(); + if (it.hasNext()) { + while (true) { + Object e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (!it.hasNext()) { + break; + } + + sb.append(',').append(' '); + } + } + + return sb.append(']').toString(); + } + + @Override + public final boolean containsAll(Collection c) { + if (c != this) { + for (Object e : c) { + if (e == null || !this.contains(e)) { + return false; + } + } + } + + return true; + } + + @Override + public final boolean removeAll(Collection c) { + if (c == null) { + throw new NullPointerException(); + } else { + boolean modified = false; + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (c.contains(it.next())) { + it.remove(); + modified = true; + } + } + + return modified; + } + } + + @Override + public final boolean retainAll(Collection c) { + if (c == null) { + throw new NullPointerException(); + } else { + boolean modified = false; + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + + return modified; + } + } + } + + protected static final class CounterCell { + public volatile long value; + + public CounterCell(long x) { + this.value = x; + } + } + + protected abstract static class DoubleReturningBulkTask extends Int2ObjectConcurrentHashMap.BulkTask { + public double result; + + public DoubleReturningBulkTask(Int2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Int2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected double invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + public interface Entry extends Int2ObjectMap.Entry { + boolean isEmpty(); + + @Deprecated + @Override + Integer getKey(); + + @Override + int getIntKey(); + + @Override + V getValue(); + + @Override + int hashCode(); + + @Override + String toString(); + + @Override + boolean equals(Object var1); + + @Override + V setValue(V var1); + } + + protected static final class EntryIterator extends Int2ObjectConcurrentHashMap.BaseIterator implements ObjectIterator> { + public EntryIterator(Int2ObjectConcurrentHashMap.Node[] tab, int index, int size, int limit, Int2ObjectConcurrentHashMap map) { + super(tab, index, size, limit, map); + } + + public final Int2ObjectConcurrentHashMap.Entry next() { + Int2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + int k = p.key; + V v = p.val; + this.lastReturned = p; + this.advance(); + return new Int2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), k, v, this.map); + } + } + } + + protected static final class EntrySetView + extends Int2ObjectConcurrentHashMap.CollectionView> + implements ObjectSet>, + Serializable { + public static final long serialVersionUID = 2249069246763182397L; + + public EntrySetView(Int2ObjectConcurrentHashMap map) { + super(map); + } + + @Override + public boolean contains(Object o) { + if (o instanceof Int2ObjectMap.Entry) { + Int2ObjectMap.Entry e; + int k = (e = (Int2ObjectMap.Entry)o).getIntKey(); + if (!((Int2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + Object v; + Object r; + return (r = this.map.get(k)) != null && (v = e.getValue()) != null && (v == r || v.equals(r)); + } + } + + return false; + } + + @Override + public boolean remove(Object o) { + if (o instanceof Int2ObjectMap.Entry) { + Int2ObjectMap.Entry e; + int k = (e = (Int2ObjectMap.Entry)o).getIntKey(); + if (!((Int2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + Object v; + return (v = e.getValue()) != null && this.map.remove(k, v); + } + } + + return false; + } + + @Override + public ObjectIterator> iterator() { + Int2ObjectConcurrentHashMap m = this.map; + Int2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Int2ObjectConcurrentHashMap.EntryIterator<>(t, f, 0, f, m); + } + + public boolean add(Int2ObjectMap.Entry e) { + return this.map.putVal(e.getIntKey(), e.getValue(), false) == null; + } + + @Override + public boolean addAll(Collection> c) { + boolean added = false; + + for (Int2ObjectMap.Entry e : c) { + if (this.add(e)) { + added = true; + } + } + + return added; + } + + @Override + public final int hashCode() { + int h = 0; + Int2ObjectConcurrentHashMap.Node[] t = this.map.table; + if (this.map.table != null) { + Int2ObjectConcurrentHashMap.Traverser it = new Int2ObjectConcurrentHashMap.Traverser<>(t, t.length, 0, t.length); + + Int2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + h += p.hashCode(); + } + } + + return h; + } + + @Override + public final boolean equals(Object o) { + Set c; + return o instanceof Set && ((c = (Set)o) == this || this.containsAll(c) && c.containsAll(this)); + } + + @Override + public ObjectSpliterator> spliterator() { + Int2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Int2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Int2ObjectConcurrentHashMap.EntrySpliterator<>(t, f, 0, f, n < 0L ? 0L : n, m); + } + + @Override + public void forEach(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node[] t = this.map.table; + if (this.map.table != null) { + Int2ObjectConcurrentHashMap.Traverser it = new Int2ObjectConcurrentHashMap.Traverser<>(t, t.length, 0, t.length); + + Int2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + action.accept(new Int2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + } + } + } + } + } + + protected static final class EntrySpliterator extends Int2ObjectConcurrentHashMap.Traverser implements ObjectSpliterator> { + public final Int2ObjectConcurrentHashMap map; + public long est; + + public EntrySpliterator(Int2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est, Int2ObjectConcurrentHashMap map) { + super(tab, size, index, limit); + this.map = map; + this.est = est; + } + + @Override + public ObjectSpliterator> trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Int2ObjectConcurrentHashMap.EntrySpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1, this.map); + } + + @Override + public void forEachRemaining(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(new Int2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + } + } + } + + @Override + public boolean tryAdvance(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(new Int2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4353; + } + } + + protected static final class ForEachEntryTask extends Int2ObjectConcurrentHashMap.BulkTask { + public final Consumer> action; + + public ForEachEntryTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Consumer> action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Consumer> action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Int2ObjectConcurrentHashMap.ForEachEntryTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachKeyTask extends Int2ObjectConcurrentHashMap.BulkTask { + public final IntConsumer action; + + public ForEachKeyTask(Int2ObjectConcurrentHashMap.BulkTask p, int b, int i, int f, Int2ObjectConcurrentHashMap.Node[] t, IntConsumer action) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + IntConsumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Int2ObjectConcurrentHashMap.ForEachKeyTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.key); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachMappingTask extends Int2ObjectConcurrentHashMap.BulkTask { + public final Int2ObjectConcurrentHashMap.IntObjConsumer action; + + public ForEachMappingTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.IntObjConsumer action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Int2ObjectConcurrentHashMap.IntObjConsumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Int2ObjectConcurrentHashMap.ForEachMappingTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.key, p.val); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachTransformedEntryTask extends Int2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> transformer; + public final Consumer action; + + public ForEachTransformedEntryTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Function, ? extends U> transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Function, ? extends U> transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Int2ObjectConcurrentHashMap.ForEachTransformedEntryTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedKeyTask extends Int2ObjectConcurrentHashMap.BulkTask { + public final Int2ObjectConcurrentHashMap.IntFunction transformer; + public final Consumer action; + + public ForEachTransformedKeyTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.IntFunction transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Int2ObjectConcurrentHashMap.IntFunction transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Int2ObjectConcurrentHashMap.ForEachTransformedKeyTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedMappingTask extends Int2ObjectConcurrentHashMap.BulkTask { + public final Int2ObjectConcurrentHashMap.IntObjFunction transformer; + public final Consumer action; + + public ForEachTransformedMappingTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.IntObjFunction transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Int2ObjectConcurrentHashMap.IntObjFunction transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Int2ObjectConcurrentHashMap.ForEachTransformedMappingTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedValueTask extends Int2ObjectConcurrentHashMap.BulkTask { + public final Function transformer; + public final Consumer action; + + public ForEachTransformedValueTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Function transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Function transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Int2ObjectConcurrentHashMap.ForEachTransformedValueTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.val)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachValueTask extends Int2ObjectConcurrentHashMap.BulkTask { + public final Consumer action; + + public ForEachValueTask( + Int2ObjectConcurrentHashMap.BulkTask p, int b, int i, int f, Int2ObjectConcurrentHashMap.Node[] t, Consumer action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Int2ObjectConcurrentHashMap.ForEachValueTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.val); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForwardingNode extends Int2ObjectConcurrentHashMap.Node { + public final Int2ObjectConcurrentHashMap.Node[] nextTable; + + public ForwardingNode(int empty, Int2ObjectConcurrentHashMap.Node[] tab) { + super(empty, -1, empty, null, null); + this.nextTable = tab; + } + + @Override + protected Int2ObjectConcurrentHashMap.Node find(int h, int k) { + Int2ObjectConcurrentHashMap.Node[] tab = this.nextTable; + + Int2ObjectConcurrentHashMap.Node e; + int n; + label41: + while (k != this.EMPTY && tab != null && (n = tab.length) != 0 && (e = Int2ObjectConcurrentHashMap.tabAt(tab, n - 1 & h)) != null) { + do { + int eh = e.hash; + if (e.hash == h) { + int ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + + if (eh < 0) { + if (!(e instanceof Int2ObjectConcurrentHashMap.ForwardingNode)) { + return e.find(h, k); + } + + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + continue label41; + } + } while ((e = e.next) != null); + + return null; + } + + return null; + } + } + + @FunctionalInterface + public interface IntBiObjByteConsumer { + void accept(int var1, V var2, byte var3, X var4); + } + + @FunctionalInterface + public interface IntBiObjConsumer { + void accept(int var1, V var2, X var3); + } + + @FunctionalInterface + public interface IntBiObjDoubleConsumer { + void accept(int var1, V var2, double var3, X var5); + } + + @FunctionalInterface + public interface IntBiObjFloatConsumer { + void accept(int var1, V var2, float var3, X var4); + } + + @FunctionalInterface + public interface IntBiObjFunction { + J apply(int var1, V var2, X var3); + } + + @FunctionalInterface + public interface IntBiObjIntConsumer { + void accept(int var1, V var2, int var3, X var4); + } + + @FunctionalInterface + public interface IntBiObjLongConsumer { + void accept(int var1, V var2, long var3, X var5); + } + + @FunctionalInterface + public interface IntBiObjShortConsumer { + void accept(int var1, V var2, short var3, X var4); + } + + @FunctionalInterface + public interface IntFunction { + R apply(int var1); + } + + @FunctionalInterface + public interface IntObjByteConsumer { + void accept(int var1, V var2, byte var3); + } + + @FunctionalInterface + public interface IntObjByteFunction { + J apply(int var1, V var2, byte var3); + } + + @FunctionalInterface + public interface IntObjConsumer { + void accept(int var1, V var2); + } + + @FunctionalInterface + public interface IntObjDoubleConsumer { + void accept(int var1, V var2, double var3); + } + + @FunctionalInterface + public interface IntObjDoubleFunction { + J apply(int var1, V var2, double var3); + } + + @FunctionalInterface + public interface IntObjFloatConsumer { + void accept(int var1, V var2, float var3); + } + + @FunctionalInterface + public interface IntObjFloatFunction { + J apply(int var1, V var2, float var3); + } + + @FunctionalInterface + public interface IntObjFunction { + J apply(int var1, V var2); + } + + @FunctionalInterface + public interface IntObjIntConsumer { + void accept(int var1, V var2, int var3); + } + + @FunctionalInterface + public interface IntObjIntFunction { + J apply(int var1, V var2, int var3); + } + + @FunctionalInterface + public interface IntObjLongConsumer { + void accept(int var1, V var2, long var3); + } + + @FunctionalInterface + public interface IntObjLongFunction { + J apply(int var1, V var2, long var3); + } + + @FunctionalInterface + public interface IntObjShortConsumer { + void accept(int var1, V var2, short var3); + } + + @FunctionalInterface + public interface IntObjShortFunction { + J apply(int var1, V var2, short var3); + } + + protected abstract static class IntReturningBulkTask extends Int2ObjectConcurrentHashMap.BulkTask { + public int result; + + public IntReturningBulkTask(Int2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Int2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected int invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + @FunctionalInterface + public interface IntToDoubleFunction { + double applyAsDouble(int var1); + } + + @FunctionalInterface + public interface IntToIntFunction { + int applyAsInt(int var1); + } + + @FunctionalInterface + public interface IntToLongFunction { + long applyAsLong(int var1); + } + + @FunctionalInterface + public interface IntTriObjConsumer { + void accept(int var1, V var2, X var3, Y var4); + } + + @FunctionalInterface + public interface IntegerReduceTaskOperator { + int reduce(int var1, int var2, int var3); + } + + protected abstract static class IntegerReturningBulkTask2 extends Int2ObjectConcurrentHashMap.BulkTask { + public int result; + + public IntegerReturningBulkTask2(Int2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Int2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected int invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + protected static final class KeyIterator implements IntIterator { + public Int2ObjectConcurrentHashMap.Node[] tab; + public Int2ObjectConcurrentHashMap.Node next; + public Int2ObjectConcurrentHashMap.TableStack stack; + public Int2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + public final Int2ObjectConcurrentHashMap map; + public Int2ObjectConcurrentHashMap.Node lastReturned; + + public KeyIterator(Int2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, Int2ObjectConcurrentHashMap map) { + this.tab = tab; + this.baseSize = size; + this.baseIndex = this.index = index; + this.baseLimit = limit; + this.next = null; + this.map = map; + this.advance(); + } + + protected final Int2ObjectConcurrentHashMap.Node advance() { + Int2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Int2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Int2ObjectConcurrentHashMap.TreeBin) { + e = ((Int2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Int2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Int2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Int2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + + @Override + public final boolean hasNext() { + return this.next != null; + } + + public final boolean hasMoreElements() { + return this.next != null; + } + + @Override + public final void remove() { + Int2ObjectConcurrentHashMap.Node p = this.lastReturned; + if (this.lastReturned == null) { + throw new IllegalStateException(); + } else { + this.lastReturned = null; + this.map.replaceNode(p.key, null, null); + } + } + + @Override + public final int nextInt() { + Int2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + int k = p.key; + this.lastReturned = p; + this.advance(); + return k; + } + } + } + + public static class KeySetView implements IntSet { + public static final long serialVersionUID = 7249069246763182397L; + public final Int2ObjectConcurrentHashMap map; + public final V value; + + public KeySetView(Int2ObjectConcurrentHashMap map, V value) { + this.map = map; + this.value = value; + } + + public V getMappedValue() { + return this.value; + } + + @Override + public boolean contains(int o) { + return this.map.containsKey(o); + } + + @Override + public boolean remove(int o) { + return this.map.remove(o) != null; + } + + @Override + public IntIterator iterator() { + Int2ObjectConcurrentHashMap m = this.map; + Int2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Int2ObjectConcurrentHashMap.KeyIterator<>(t, f, 0, f, m); + } + + @Override + public boolean add(int e) { + V v = this.value; + if (this.value == null) { + throw new UnsupportedOperationException(); + } else { + return this.map.putVal(e, v, true) == null; + } + } + + @Override + public boolean addAll(IntCollection c) { + boolean added = false; + V v = this.value; + if (this.value == null) { + throw new UnsupportedOperationException(); + } else { + IntIterator iter = c.iterator(); + + while (iter.hasNext()) { + int e = iter.nextInt(); + if (this.map.putVal(e, v, true) == null) { + added = true; + } + } + + return added; + } + } + + @Override + public int hashCode() { + int h = 0; + IntIterator iter = this.iterator(); + + while (iter.hasNext()) { + h += Integer.hashCode(iter.nextInt()); + } + + return h; + } + + @Override + public boolean equals(Object o) { + IntSet c; + return o instanceof IntSet && ((c = (IntSet)o) == this || this.containsAll(c) && c.containsAll(this)); + } + + public int getNoEntryValue() { + return this.map.EMPTY; + } + + @Override + public int size() { + return this.map.size(); + } + + @Override + public boolean isEmpty() { + return this.map.isEmpty(); + } + + @Override + public Object[] toArray() { + Object[] out = new Integer[this.size()]; + IntIterator iter = this.iterator(); + + int i; + for (i = 0; i < out.length && iter.hasNext(); i++) { + out[i] = iter.nextInt(); + } + + if (out.length > i + 1) { + out[i] = this.map.EMPTY; + } + + return out; + } + + @Override + public Object[] toArray(Object[] dest) { + IntIterator iter = this.iterator(); + + int i; + for (i = 0; i < dest.length && iter.hasNext() && i <= dest.length; i++) { + dest[i] = iter.next(); + } + + if (dest.length > i + 1) { + dest[i] = this.map.EMPTY; + } + + return dest; + } + + @Override + public int[] toIntArray() { + int[] out = new int[this.size()]; + IntIterator iter = this.iterator(); + + int i; + for (i = 0; i < out.length && iter.hasNext(); i++) { + out[i] = iter.next(); + } + + if (out.length > i + 1) { + out[i] = this.map.EMPTY; + } + + return out; + } + + @Override + public int[] toArray(int[] dest) { + IntIterator iter = this.iterator(); + + int i; + for (i = 0; i < dest.length && iter.hasNext() && i <= dest.length; i++) { + dest[i] = iter.next(); + } + + if (dest.length > i + 1) { + dest[i] = this.map.EMPTY; + } + + return dest; + } + + @Override + public int[] toIntArray(int[] dest) { + return this.toArray(dest); + } + + @Override + public boolean containsAll(Collection collection) { + for (Object element : collection) { + if (!(element instanceof Long)) { + return false; + } + + int c = (Integer)element; + if (!this.contains(c)) { + return false; + } + } + + return true; + } + + @Override + public boolean containsAll(IntCollection collection) { + IntIterator iter = collection.iterator(); + + while (iter.hasNext()) { + int element = iter.next(); + if (!this.contains(element)) { + return false; + } + } + + return true; + } + + public boolean containsAll(int[] array) { + int i = array.length; + + while (i-- > 0) { + if (!this.contains(array[i])) { + return false; + } + } + + return true; + } + + @Override + public boolean addAll(Collection collection) { + boolean changed = false; + + for (Integer element : collection) { + int e = element; + if (this.add(e)) { + changed = true; + } + } + + return changed; + } + + public boolean addAll(int[] array) { + boolean changed = false; + int i = array.length; + + while (i-- > 0) { + if (this.add(array[i])) { + changed = true; + } + } + + return changed; + } + + @Override + public boolean retainAll(Collection collection) { + boolean modified = false; + IntIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (!collection.contains(iter.next())) { + iter.remove(); + modified = true; + } + } + + return modified; + } + + @Override + public boolean retainAll(IntCollection collection) { + if (this == collection) { + return false; + } else { + boolean modified = false; + IntIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (!collection.contains(iter.next())) { + iter.remove(); + modified = true; + } + } + + return modified; + } + } + + public boolean retainAll(int[] array) { + boolean modified = false; + IntIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (Arrays.binarySearch(array, iter.next().intValue()) < 0) { + iter.remove(); + modified = true; + } + } + + return modified; + } + + @Override + public boolean removeAll(Collection collection) { + boolean changed = false; + + for (Object element : collection) { + if (element instanceof Integer) { + int c = (Integer)element; + if (this.remove(c)) { + changed = true; + } + } + } + + return changed; + } + + @Override + public boolean removeAll(IntCollection collection) { + boolean changed = false; + IntIterator iter = collection.iterator(); + + while (iter.hasNext()) { + int element = iter.next(); + if (this.remove(element)) { + changed = true; + } + } + + return changed; + } + + public boolean removeAll(int[] array) { + boolean changed = false; + int i = array.length; + + while (i-- > 0) { + if (this.remove(array[i])) { + changed = true; + } + } + + return changed; + } + + @Override + public void clear() { + this.map.clear(); + } + + @Override + public IntSpliterator spliterator() { + Int2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Int2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Int2ObjectConcurrentHashMap.KeySpliterator<>(t, f, 0, f, n < 0L ? 0L : n); + } + } + + protected static final class KeySpliterator extends Int2ObjectConcurrentHashMap.Traverser implements IntSpliterator { + public long est; + + public KeySpliterator(Int2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est) { + super(tab, size, index, limit); + this.est = est; + } + + @Override + public IntSpliterator trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Int2ObjectConcurrentHashMap.KeySpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1); + } + + @Override + public boolean tryAdvance(Consumer action) { + return action instanceof IntConsumer ? this.tryAdvance((IntConsumer)action) : this.tryAdvance(value -> action.accept(value)); + } + + @Override + public void forEachRemaining(IntConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(p.key); + } + } + } + + @Override + public boolean tryAdvance(IntConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(p.key); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4353; + } + } + + protected abstract static class LongReturningBulkTask extends Int2ObjectConcurrentHashMap.BulkTask { + public long result; + + public LongReturningBulkTask(Int2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Int2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected long invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + protected static final class MapEntry implements Int2ObjectConcurrentHashMap.Entry { + public final boolean empty; + public final int key; + public V val; + public final Int2ObjectConcurrentHashMap map; + + public MapEntry(boolean empty, int key, V val, Int2ObjectConcurrentHashMap map) { + this.empty = empty; + this.key = key; + this.val = val; + this.map = map; + } + + @Override + public boolean isEmpty() { + return this.empty; + } + + @Override + public Integer getKey() { + return this.key; + } + + @Override + public int getIntKey() { + return this.key; + } + + @Override + public V getValue() { + return this.val; + } + + @Override + public String toString() { + return this.empty ? "EMPTY=" + this.val : this.key + "=" + this.val; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o instanceof Int2ObjectConcurrentHashMap.Entry) { + if (this.empty != ((Int2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + return false; + } else { + return !this.empty && this.key != ((Int2ObjectConcurrentHashMap.Entry)o).getIntKey() + ? false + : this.val.equals(((Int2ObjectConcurrentHashMap.Entry)o).getValue()); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.empty ? 1 : 0; + result = 31 * result + Integer.hashCode(this.key); + return 31 * result + this.val.hashCode(); + } + + @Override + public V setValue(V value) { + if (value == null) { + throw new NullPointerException(); + } else { + V v = this.val; + this.val = value; + this.map.put(this.key, value); + return v; + } + } + } + + protected static final class MapReduceEntriesTask extends Int2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> transformer; + public final BiFunction reducer; + public U result; + public Int2ObjectConcurrentHashMap.MapReduceEntriesTask rights; + public Int2ObjectConcurrentHashMap.MapReduceEntriesTask nextRight; + + public MapReduceEntriesTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.MapReduceEntriesTask nextRight, + Function, ? extends U> transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Function, ? extends U> transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.MapReduceEntriesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Int2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.MapReduceEntriesTask t = (Int2ObjectConcurrentHashMap.MapReduceEntriesTask)c; + + for (Int2ObjectConcurrentHashMap.MapReduceEntriesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceEntriesToDoubleTask extends Int2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final ToDoubleFunction> transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Int2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask rights; + public Int2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask nextRight; + + public MapReduceEntriesToDoubleTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask nextRight, + ToDoubleFunction> transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToDoubleFunction> transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask t = (Int2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask)c; + + for (Int2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceEntriesToIntTask extends Int2ObjectConcurrentHashMap.IntReturningBulkTask { + public final Int2ObjectConcurrentHashMap.ToIntFunction> transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Int2ObjectConcurrentHashMap.MapReduceEntriesToIntTask rights; + public Int2ObjectConcurrentHashMap.MapReduceEntriesToIntTask nextRight; + + public MapReduceEntriesToIntTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.MapReduceEntriesToIntTask nextRight, + Int2ObjectConcurrentHashMap.ToIntFunction> transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Int2ObjectConcurrentHashMap.ToIntFunction> transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.MapReduceEntriesToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.MapReduceEntriesToIntTask t = (Int2ObjectConcurrentHashMap.MapReduceEntriesToIntTask)c; + + for (Int2ObjectConcurrentHashMap.MapReduceEntriesToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceEntriesToLongTask extends Int2ObjectConcurrentHashMap.LongReturningBulkTask { + public final ToLongFunction> transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Int2ObjectConcurrentHashMap.MapReduceEntriesToLongTask rights; + public Int2ObjectConcurrentHashMap.MapReduceEntriesToLongTask nextRight; + + public MapReduceEntriesToLongTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.MapReduceEntriesToLongTask nextRight, + ToLongFunction> transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToLongFunction> transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.MapReduceEntriesToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.MapReduceEntriesToLongTask t = (Int2ObjectConcurrentHashMap.MapReduceEntriesToLongTask)c; + + for (Int2ObjectConcurrentHashMap.MapReduceEntriesToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysTask extends Int2ObjectConcurrentHashMap.BulkTask { + public final Int2ObjectConcurrentHashMap.IntFunction transformer; + public final BiFunction reducer; + public U result; + public Int2ObjectConcurrentHashMap.MapReduceKeysTask rights; + public Int2ObjectConcurrentHashMap.MapReduceKeysTask nextRight; + + public MapReduceKeysTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.MapReduceKeysTask nextRight, + Int2ObjectConcurrentHashMap.IntFunction transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Int2ObjectConcurrentHashMap.IntFunction transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.MapReduceKeysTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Int2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.MapReduceKeysTask t = (Int2ObjectConcurrentHashMap.MapReduceKeysTask)c; + + for (Int2ObjectConcurrentHashMap.MapReduceKeysTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceKeysToDoubleTask extends Int2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final Int2ObjectConcurrentHashMap.IntToDoubleFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Int2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask rights; + public Int2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask nextRight; + + public MapReduceKeysToDoubleTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask nextRight, + Int2ObjectConcurrentHashMap.IntToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Int2ObjectConcurrentHashMap.IntToDoubleFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask t = (Int2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask)c; + + for (Int2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysToIntTask extends Int2ObjectConcurrentHashMap.IntReturningBulkTask { + public final Int2ObjectConcurrentHashMap.IntToIntFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Int2ObjectConcurrentHashMap.MapReduceKeysToIntTask rights; + public Int2ObjectConcurrentHashMap.MapReduceKeysToIntTask nextRight; + + public MapReduceKeysToIntTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.MapReduceKeysToIntTask nextRight, + Int2ObjectConcurrentHashMap.IntToIntFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Int2ObjectConcurrentHashMap.IntToIntFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.MapReduceKeysToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.MapReduceKeysToIntTask t = (Int2ObjectConcurrentHashMap.MapReduceKeysToIntTask)c; + + for (Int2ObjectConcurrentHashMap.MapReduceKeysToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysToLongTask extends Int2ObjectConcurrentHashMap.LongReturningBulkTask { + public final Int2ObjectConcurrentHashMap.IntToLongFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Int2ObjectConcurrentHashMap.MapReduceKeysToLongTask rights; + public Int2ObjectConcurrentHashMap.MapReduceKeysToLongTask nextRight; + + public MapReduceKeysToLongTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.MapReduceKeysToLongTask nextRight, + Int2ObjectConcurrentHashMap.IntToLongFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Int2ObjectConcurrentHashMap.IntToLongFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.MapReduceKeysToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.MapReduceKeysToLongTask t = (Int2ObjectConcurrentHashMap.MapReduceKeysToLongTask)c; + + for (Int2ObjectConcurrentHashMap.MapReduceKeysToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsTask extends Int2ObjectConcurrentHashMap.BulkTask { + public final Int2ObjectConcurrentHashMap.IntObjFunction transformer; + public final BiFunction reducer; + public U result; + public Int2ObjectConcurrentHashMap.MapReduceMappingsTask rights; + public Int2ObjectConcurrentHashMap.MapReduceMappingsTask nextRight; + + public MapReduceMappingsTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.MapReduceMappingsTask nextRight, + Int2ObjectConcurrentHashMap.IntObjFunction transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Int2ObjectConcurrentHashMap.IntObjFunction transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.MapReduceMappingsTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Int2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.MapReduceMappingsTask t = (Int2ObjectConcurrentHashMap.MapReduceMappingsTask)c; + + for (Int2ObjectConcurrentHashMap.MapReduceMappingsTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceMappingsToDoubleTask extends Int2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final Int2ObjectConcurrentHashMap.ToDoubleIntObjFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Int2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask rights; + public Int2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask nextRight; + + public MapReduceMappingsToDoubleTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask nextRight, + Int2ObjectConcurrentHashMap.ToDoubleIntObjFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Int2ObjectConcurrentHashMap.ToDoubleIntObjFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask t = (Int2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask)c; + + for (Int2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsToIntTask extends Int2ObjectConcurrentHashMap.IntReturningBulkTask { + public final Int2ObjectConcurrentHashMap.ToIntIntObjFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Int2ObjectConcurrentHashMap.MapReduceMappingsToIntTask rights; + public Int2ObjectConcurrentHashMap.MapReduceMappingsToIntTask nextRight; + + public MapReduceMappingsToIntTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.MapReduceMappingsToIntTask nextRight, + Int2ObjectConcurrentHashMap.ToIntIntObjFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Int2ObjectConcurrentHashMap.ToIntIntObjFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.MapReduceMappingsToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.MapReduceMappingsToIntTask t = (Int2ObjectConcurrentHashMap.MapReduceMappingsToIntTask)c; + + for (Int2ObjectConcurrentHashMap.MapReduceMappingsToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsToLongTask extends Int2ObjectConcurrentHashMap.LongReturningBulkTask { + public final Int2ObjectConcurrentHashMap.ToLongIntObjFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Int2ObjectConcurrentHashMap.MapReduceMappingsToLongTask rights; + public Int2ObjectConcurrentHashMap.MapReduceMappingsToLongTask nextRight; + + public MapReduceMappingsToLongTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.MapReduceMappingsToLongTask nextRight, + Int2ObjectConcurrentHashMap.ToLongIntObjFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Int2ObjectConcurrentHashMap.ToLongIntObjFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.MapReduceMappingsToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.MapReduceMappingsToLongTask t = (Int2ObjectConcurrentHashMap.MapReduceMappingsToLongTask)c; + + for (Int2ObjectConcurrentHashMap.MapReduceMappingsToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesTask extends Int2ObjectConcurrentHashMap.BulkTask { + public final Function transformer; + public final BiFunction reducer; + public U result; + public Int2ObjectConcurrentHashMap.MapReduceValuesTask rights; + public Int2ObjectConcurrentHashMap.MapReduceValuesTask nextRight; + + public MapReduceValuesTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.MapReduceValuesTask nextRight, + Function transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Function transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.MapReduceValuesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Int2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.MapReduceValuesTask t = (Int2ObjectConcurrentHashMap.MapReduceValuesTask)c; + + for (Int2ObjectConcurrentHashMap.MapReduceValuesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceValuesToDoubleTask extends Int2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final ToDoubleFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Int2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask rights; + public Int2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask nextRight; + + public MapReduceValuesToDoubleTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask nextRight, + ToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToDoubleFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask t = (Int2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask)c; + + for (Int2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesToIntTask extends Int2ObjectConcurrentHashMap.IntReturningBulkTask { + public final Int2ObjectConcurrentHashMap.ToIntFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Int2ObjectConcurrentHashMap.MapReduceValuesToIntTask rights; + public Int2ObjectConcurrentHashMap.MapReduceValuesToIntTask nextRight; + + public MapReduceValuesToIntTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.MapReduceValuesToIntTask nextRight, + Int2ObjectConcurrentHashMap.ToIntFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Int2ObjectConcurrentHashMap.ToIntFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.MapReduceValuesToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.MapReduceValuesToIntTask t = (Int2ObjectConcurrentHashMap.MapReduceValuesToIntTask)c; + + for (Int2ObjectConcurrentHashMap.MapReduceValuesToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesToLongTask extends Int2ObjectConcurrentHashMap.LongReturningBulkTask { + public final ToLongFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Int2ObjectConcurrentHashMap.MapReduceValuesToLongTask rights; + public Int2ObjectConcurrentHashMap.MapReduceValuesToLongTask nextRight; + + public MapReduceValuesToLongTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.MapReduceValuesToLongTask nextRight, + ToLongFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToLongFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.MapReduceValuesToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.MapReduceValuesToLongTask t = (Int2ObjectConcurrentHashMap.MapReduceValuesToLongTask)c; + + for (Int2ObjectConcurrentHashMap.MapReduceValuesToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static class Node implements Int2ObjectConcurrentHashMap.Entry { + public final int EMPTY; + public final int hash; + public final int key; + public volatile V val; + public volatile Int2ObjectConcurrentHashMap.Node next; + + public Node(int empty, int hash, int key, V val, Int2ObjectConcurrentHashMap.Node next) { + this.EMPTY = empty; + this.hash = hash; + this.key = key; + this.val = val; + this.next = next; + } + + @Override + public final boolean isEmpty() { + return this.key == this.EMPTY; + } + + @Override + public final Integer getKey() { + return this.key; + } + + @Override + public final int getIntKey() { + return this.key; + } + + @Override + public final V getValue() { + return this.val; + } + + @Override + public final int hashCode() { + return Integer.hashCode(this.key) ^ this.val.hashCode(); + } + + @Override + public final String toString() { + return this.isEmpty() ? "EMPTY=" + this.val : this.key + "=" + this.val; + } + + @Override + public final V setValue(V value) { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean equals(Object o) { + boolean empty = this.isEmpty(); + if (o instanceof Int2ObjectConcurrentHashMap.Entry) { + if (empty != ((Int2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + return false; + } else { + return !empty && this.key != ((Int2ObjectConcurrentHashMap.Entry)o).getIntKey() + ? false + : this.val.equals(((Int2ObjectConcurrentHashMap.Entry)o).getValue()); + } + } else { + return false; + } + } + + protected Int2ObjectConcurrentHashMap.Node find(int h, int k) { + Int2ObjectConcurrentHashMap.Node e = this; + if (k != this.EMPTY) { + do { + if (e.hash == h) { + int ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + } while ((e = e.next) != null); + } + + return null; + } + } + + protected static final class ReduceEntriesTask extends Int2ObjectConcurrentHashMap.BulkTask> { + public final BiFunction, Int2ObjectConcurrentHashMap.Entry, ? extends Int2ObjectConcurrentHashMap.Entry> reducer; + public Int2ObjectConcurrentHashMap.Entry result; + public Int2ObjectConcurrentHashMap.ReduceEntriesTask rights; + public Int2ObjectConcurrentHashMap.ReduceEntriesTask nextRight; + + public ReduceEntriesTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.ReduceEntriesTask nextRight, + BiFunction, Int2ObjectConcurrentHashMap.Entry, ? extends Int2ObjectConcurrentHashMap.Entry> reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.reducer = reducer; + } + + public final Int2ObjectConcurrentHashMap.Entry getRawResult() { + return this.result; + } + + @Override + public final void compute() { + BiFunction, Int2ObjectConcurrentHashMap.Entry, ? extends Int2ObjectConcurrentHashMap.Entry> reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.ReduceEntriesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + Int2ObjectConcurrentHashMap.Entry r = null; + + Int2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + r = (Int2ObjectConcurrentHashMap.Entry)(r == null ? p : reducer.apply(r, p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.ReduceEntriesTask t = (Int2ObjectConcurrentHashMap.ReduceEntriesTask)c; + + for (Int2ObjectConcurrentHashMap.ReduceEntriesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + Int2ObjectConcurrentHashMap.Entry sr = s.result; + if (s.result != null) { + Int2ObjectConcurrentHashMap.Entry tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + + protected static final class ReduceKeysTask extends Int2ObjectConcurrentHashMap.IntegerReturningBulkTask2 { + public final int EMPTY; + public final Int2ObjectConcurrentHashMap.IntegerReduceTaskOperator reducer; + public Int2ObjectConcurrentHashMap.ReduceKeysTask rights; + public Int2ObjectConcurrentHashMap.ReduceKeysTask nextRight; + + public ReduceKeysTask( + int EMPTY, + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.ReduceKeysTask nextRight, + Int2ObjectConcurrentHashMap.IntegerReduceTaskOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.EMPTY = EMPTY; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Int2ObjectConcurrentHashMap.IntegerReduceTaskOperator reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.ReduceKeysTask<>( + this.EMPTY, this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + boolean found = false; + int r = this.EMPTY; + + Int2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + int u = p.key; + if (!found) { + found = true; + r = u; + } else if (!p.isEmpty()) { + found = true; + r = reducer.reduce(this.EMPTY, r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.ReduceKeysTask t = (Int2ObjectConcurrentHashMap.ReduceKeysTask)c; + + for (Int2ObjectConcurrentHashMap.ReduceKeysTask s = t.rights; s != null; s = t.rights = s.nextRight) { + int sr = s.result; + if (s.result != this.EMPTY) { + int tr = t.result; + t.result = t.result == this.EMPTY ? sr : reducer.reduce(this.EMPTY, tr, sr); + } + } + } + } + } + } + + protected static final class ReduceValuesTask extends Int2ObjectConcurrentHashMap.BulkTask { + public final BiFunction reducer; + public V result; + public Int2ObjectConcurrentHashMap.ReduceValuesTask rights; + public Int2ObjectConcurrentHashMap.ReduceValuesTask nextRight; + + public ReduceValuesTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.ReduceValuesTask nextRight, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.reducer = reducer; + } + + @Override + public final V getRawResult() { + return this.result; + } + + @Override + public final void compute() { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Int2ObjectConcurrentHashMap.ReduceValuesTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer)) + .fork(); + } + + V r = null; + + Int2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + V v = p.val; + r = r == null ? v : reducer.apply(r, v); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Int2ObjectConcurrentHashMap.ReduceValuesTask t = (Int2ObjectConcurrentHashMap.ReduceValuesTask)c; + + for (Int2ObjectConcurrentHashMap.ReduceValuesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + V sr = s.result; + if (s.result != null) { + V tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + + protected static final class ReservationNode extends Int2ObjectConcurrentHashMap.Node { + public ReservationNode(int empty) { + super(empty, -3, empty, null, null); + } + + @Override + protected Int2ObjectConcurrentHashMap.Node find(int h, int k) { + return null; + } + } + + protected static final class SearchEntriesTask extends Int2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> searchFunction; + public final AtomicReference result; + + public SearchEntriesTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Function, ? extends U> searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Function, ? extends U> searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Int2ObjectConcurrentHashMap.SearchEntriesTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Int2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + + return; + } + } + } + } + } + } + + protected static final class SearchKeysTask extends Int2ObjectConcurrentHashMap.BulkTask { + public final Int2ObjectConcurrentHashMap.IntFunction searchFunction; + public final AtomicReference result; + + public SearchKeysTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.IntFunction searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Int2ObjectConcurrentHashMap.IntFunction searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Int2ObjectConcurrentHashMap.SearchKeysTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Int2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.key)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static final class SearchMappingsTask extends Int2ObjectConcurrentHashMap.BulkTask { + public final Int2ObjectConcurrentHashMap.IntObjFunction searchFunction; + public final AtomicReference result; + + public SearchMappingsTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Int2ObjectConcurrentHashMap.IntObjFunction searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Int2ObjectConcurrentHashMap.IntObjFunction searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Int2ObjectConcurrentHashMap.SearchMappingsTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Int2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.key, p.val)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static final class SearchValuesTask extends Int2ObjectConcurrentHashMap.BulkTask { + public final Function searchFunction; + public final AtomicReference result; + + public SearchValuesTask( + Int2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Int2ObjectConcurrentHashMap.Node[] t, + Function searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Function searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Int2ObjectConcurrentHashMap.SearchValuesTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Int2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.val)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static class Segment extends ReentrantLock implements Serializable { + public static final long serialVersionUID = 2249069246763182397L; + public final float loadFactor; + + public Segment(float lf) { + this.loadFactor = lf; + } + } + + protected static final class TableStack { + public int length; + public int index; + public Int2ObjectConcurrentHashMap.Node[] tab; + public Int2ObjectConcurrentHashMap.TableStack next; + + public TableStack() { + } + } + + @FunctionalInterface + public interface ToDoubleIntObjFunction { + double applyAsDouble(int var1, V var2); + } + + @FunctionalInterface + public interface ToIntFunction { + int applyAsInt(T var1); + } + + @FunctionalInterface + public interface ToIntIntObjFunction { + int applyAsInt(int var1, V var2); + } + + @FunctionalInterface + public interface ToLongIntObjFunction { + long applyAsLong(int var1, V var2); + } + + protected static class Traverser { + public Int2ObjectConcurrentHashMap.Node[] tab; + public Int2ObjectConcurrentHashMap.Node next; + public Int2ObjectConcurrentHashMap.TableStack stack; + public Int2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + + public Traverser(Int2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit) { + this.tab = tab; + this.baseSize = size; + this.baseIndex = this.index = index; + this.baseLimit = limit; + this.next = null; + } + + protected final Int2ObjectConcurrentHashMap.Node advance() { + Int2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Int2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Int2ObjectConcurrentHashMap.TreeBin) { + e = ((Int2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Int2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Int2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Int2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + } + + protected static final class TreeBin extends Int2ObjectConcurrentHashMap.Node { + public Int2ObjectConcurrentHashMap.TreeNode root; + public volatile Int2ObjectConcurrentHashMap.TreeNode first; + public volatile Thread waiter; + public volatile int lockState; + public static final int WRITER = 1; + public static final int WAITER = 2; + public static final int READER = 4; + protected static final Unsafe U; + protected static final long LOCKSTATE; + + protected int tieBreakOrder(int a, int b) { + int comp = Integer.compare(a, b); + return comp > 0 ? 1 : -1; + } + + public TreeBin(int empty, Int2ObjectConcurrentHashMap.TreeNode b) { + super(empty, -2, empty, null, null); + this.first = b; + Int2ObjectConcurrentHashMap.TreeNode r = null; + Int2ObjectConcurrentHashMap.TreeNode x = b; + + while (x != null) { + Int2ObjectConcurrentHashMap.TreeNode next = (Int2ObjectConcurrentHashMap.TreeNode)x.next; + x.left = x.right = null; + if (r == null) { + x.parent = null; + x.red = false; + r = x; + } else { + int k = x.key; + int h = x.hash; + Class kc = null; + Int2ObjectConcurrentHashMap.TreeNode p = r; + + int dir; + Int2ObjectConcurrentHashMap.TreeNode xp; + do { + int pk = p.key; + int ph = p.hash; + if (p.hash > h) { + dir = -1; + } else if (ph < h) { + dir = 1; + } else if ((dir = Integer.compare(k, pk)) == 0) { + dir = this.tieBreakOrder(k, pk); + } + + xp = p; + } while ((p = dir <= 0 ? p.left : p.right) != null); + + x.parent = xp; + if (dir <= 0) { + xp.left = x; + } else { + xp.right = x; + } + + r = this.balanceInsertion(r, x); + } + + x = next; + } + + this.root = r; + + assert this.checkInvariants(this.root); + } + + protected final void lockRoot() { + if (!U.compareAndSwapInt(this, LOCKSTATE, 0, 1)) { + this.contendedLock(); + } + } + + protected final void unlockRoot() { + this.lockState = 0; + } + + protected final void contendedLock() { + boolean waiting = false; + + while (true) { + int s = this.lockState; + if ((this.lockState & -3) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, 1)) { + if (waiting) { + this.waiter = null; + } + + return; + } + } else if ((s & 2) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, s | 2)) { + waiting = true; + this.waiter = Thread.currentThread(); + } + } else if (waiting) { + LockSupport.park(this); + } + } + } + + @Override + protected final Int2ObjectConcurrentHashMap.Node find(int h, int k) { + if (k != this.EMPTY) { + Int2ObjectConcurrentHashMap.Node e = this.first; + + while (e != null) { + int s = this.lockState; + if ((this.lockState & 3) != 0) { + if (e.hash == h) { + int ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + + e = e.next; + } else if (U.compareAndSwapInt(this, LOCKSTATE, s, s + 4)) { + Int2ObjectConcurrentHashMap.TreeNode p; + try { + Int2ObjectConcurrentHashMap.TreeNode r = this.root; + p = this.root == null ? null : r.findTreeNode(h, k, null); + } finally { + if (U.getAndAddInt(this, LOCKSTATE, -4) == 6) { + Thread w = this.waiter; + if (this.waiter != null) { + LockSupport.unpark(w); + } + } + } + + return p; + } + } + } + + return null; + } + + protected final Int2ObjectConcurrentHashMap.TreeNode putTreeVal(int h, int k, V v) { + Class kc = null; + boolean searched = false; + Int2ObjectConcurrentHashMap.TreeNode p = this.root; + + while (true) { + if (p == null) { + this.first = this.root = new Int2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, h, k, v, null, null); + } else { + int ph = p.hash; + int dir; + if (p.hash > h) { + dir = -1; + } else if (ph < h) { + dir = 1; + } else { + int pk = p.key; + if (p.key == k || pk != this.EMPTY && k == pk) { + return p; + } + + if ((dir = Integer.compare(k, pk)) == 0) { + if (!searched) { + searched = true; + Int2ObjectConcurrentHashMap.TreeNode ch = p.left; + Int2ObjectConcurrentHashMap.TreeNode q; + if (p.left != null && (q = ch.findTreeNode(h, k, kc)) != null) { + return q; + } + + ch = p.right; + if (p.right != null && (q = ch.findTreeNode(h, k, kc)) != null) { + return q; + } + } + + dir = this.tieBreakOrder(k, pk); + } + } + + Int2ObjectConcurrentHashMap.TreeNode xp = p; + if ((p = dir <= 0 ? p.left : p.right) != null) { + continue; + } + + Int2ObjectConcurrentHashMap.TreeNode f = this.first; + Int2ObjectConcurrentHashMap.TreeNode x; + this.first = x = new Int2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, h, k, v, f, xp); + if (f != null) { + f.prev = x; + } + + if (dir <= 0) { + xp.left = x; + } else { + xp.right = x; + } + + if (!xp.red) { + x.red = true; + } else { + this.lockRoot(); + + try { + this.root = this.balanceInsertion(this.root, x); + } finally { + this.unlockRoot(); + } + } + } + + assert this.checkInvariants(this.root); + + return null; + } + } + + protected final boolean removeTreeNode(Int2ObjectConcurrentHashMap.TreeNode p) { + Int2ObjectConcurrentHashMap.TreeNode next = (Int2ObjectConcurrentHashMap.TreeNode)p.next; + Int2ObjectConcurrentHashMap.TreeNode pred = p.prev; + if (pred == null) { + this.first = next; + } else { + pred.next = next; + } + + if (next != null) { + next.prev = pred; + } + + if (this.first == null) { + this.root = null; + return true; + } else { + Int2ObjectConcurrentHashMap.TreeNode r = this.root; + if (this.root != null && r.right != null) { + Int2ObjectConcurrentHashMap.TreeNode rl = r.left; + if (r.left != null && rl.left != null) { + this.lockRoot(); + + try { + Int2ObjectConcurrentHashMap.TreeNode pl = p.left; + Int2ObjectConcurrentHashMap.TreeNode pr = p.right; + Int2ObjectConcurrentHashMap.TreeNode replacement; + if (pl != null && pr != null) { + Int2ObjectConcurrentHashMap.TreeNode s = pr; + + while (true) { + Int2ObjectConcurrentHashMap.TreeNode sl = s.left; + if (s.left == null) { + boolean c = s.red; + s.red = p.red; + p.red = c; + Int2ObjectConcurrentHashMap.TreeNode sr = s.right; + Int2ObjectConcurrentHashMap.TreeNode pp = p.parent; + if (s == pr) { + p.parent = s; + s.right = p; + } else { + Int2ObjectConcurrentHashMap.TreeNode sp = s.parent; + if ((p.parent = sp) != null) { + if (s == sp.left) { + sp.left = p; + } else { + sp.right = p; + } + } + + if ((s.right = pr) != null) { + pr.parent = s; + } + } + + p.left = null; + if ((p.right = sr) != null) { + sr.parent = p; + } + + if ((s.left = pl) != null) { + pl.parent = s; + } + + if ((s.parent = pp) == null) { + r = s; + } else if (p == pp.left) { + pp.left = s; + } else { + pp.right = s; + } + + if (sr != null) { + replacement = sr; + } else { + replacement = p; + } + break; + } + + s = sl; + } + } else if (pl != null) { + replacement = pl; + } else if (pr != null) { + replacement = pr; + } else { + replacement = p; + } + + if (replacement != p) { + Int2ObjectConcurrentHashMap.TreeNode ppx = replacement.parent = p.parent; + if (ppx == null) { + r = replacement; + } else if (p == ppx.left) { + ppx.left = replacement; + } else { + ppx.right = replacement; + } + + p.left = p.right = p.parent = null; + } + + this.root = p.red ? r : this.balanceDeletion(r, replacement); + if (p == replacement) { + Int2ObjectConcurrentHashMap.TreeNode ppx = p.parent; + if (p.parent != null) { + if (p == ppx.left) { + ppx.left = null; + } else if (p == ppx.right) { + ppx.right = null; + } + + p.parent = null; + } + } + } finally { + this.unlockRoot(); + } + + assert this.checkInvariants(this.root); + + return false; + } + } + + return true; + } + } + + protected Int2ObjectConcurrentHashMap.TreeNode rotateLeft(Int2ObjectConcurrentHashMap.TreeNode root, Int2ObjectConcurrentHashMap.TreeNode p) { + if (p != null) { + Int2ObjectConcurrentHashMap.TreeNode r = p.right; + if (p.right != null) { + Int2ObjectConcurrentHashMap.TreeNode rl; + if ((rl = p.right = r.left) != null) { + rl.parent = p; + } + + Int2ObjectConcurrentHashMap.TreeNode pp; + if ((pp = r.parent = p.parent) == null) { + root = r; + r.red = false; + } else if (pp.left == p) { + pp.left = r; + } else { + pp.right = r; + } + + r.left = p; + p.parent = r; + } + } + + return root; + } + + protected Int2ObjectConcurrentHashMap.TreeNode rotateRight(Int2ObjectConcurrentHashMap.TreeNode root, Int2ObjectConcurrentHashMap.TreeNode p) { + if (p != null) { + Int2ObjectConcurrentHashMap.TreeNode l = p.left; + if (p.left != null) { + Int2ObjectConcurrentHashMap.TreeNode lr; + if ((lr = p.left = l.right) != null) { + lr.parent = p; + } + + Int2ObjectConcurrentHashMap.TreeNode pp; + if ((pp = l.parent = p.parent) == null) { + root = l; + l.red = false; + } else if (pp.right == p) { + pp.right = l; + } else { + pp.left = l; + } + + l.right = p; + p.parent = l; + } + } + + return root; + } + + protected Int2ObjectConcurrentHashMap.TreeNode balanceInsertion( + Int2ObjectConcurrentHashMap.TreeNode root, Int2ObjectConcurrentHashMap.TreeNode x + ) { + x.red = true; + + while (true) { + Int2ObjectConcurrentHashMap.TreeNode xp = x.parent; + if (x.parent == null) { + x.red = false; + return x; + } + + if (!xp.red) { + break; + } + + Int2ObjectConcurrentHashMap.TreeNode xpp = xp.parent; + if (xp.parent == null) { + break; + } + + Int2ObjectConcurrentHashMap.TreeNode xppl = xpp.left; + if (xp == xpp.left) { + Int2ObjectConcurrentHashMap.TreeNode xppr = xpp.right; + if (xpp.right != null && xppr.red) { + xppr.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } else { + if (x == xp.right) { + x = xp; + root = this.rotateLeft(root, xp); + xpp = (xp = xp.parent) == null ? null : xp.parent; + } + + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = this.rotateRight(root, xpp); + } + } + } + } else if (xppl != null && xppl.red) { + xppl.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } else { + if (x == xp.left) { + x = xp; + root = this.rotateRight(root, xp); + xpp = (xp = xp.parent) == null ? null : xp.parent; + } + + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = this.rotateLeft(root, xpp); + } + } + } + } + + return root; + } + + protected Int2ObjectConcurrentHashMap.TreeNode balanceDeletion( + Int2ObjectConcurrentHashMap.TreeNode root, Int2ObjectConcurrentHashMap.TreeNode x + ) { + while (x != null && x != root) { + Int2ObjectConcurrentHashMap.TreeNode xp = x.parent; + if (x.parent == null) { + x.red = false; + return x; + } + + if (x.red) { + x.red = false; + return root; + } + + Int2ObjectConcurrentHashMap.TreeNode xpl = xp.left; + if (xp.left == x) { + Int2ObjectConcurrentHashMap.TreeNode xpr = xp.right; + if (xp.right != null && xpr.red) { + xpr.red = false; + xp.red = true; + root = this.rotateLeft(root, xp); + xp = x.parent; + xpr = x.parent == null ? null : xp.right; + } + + if (xpr == null) { + x = xp; + } else { + Int2ObjectConcurrentHashMap.TreeNode sl = xpr.left; + Int2ObjectConcurrentHashMap.TreeNode sr = xpr.right; + if (sr != null && sr.red || sl != null && sl.red) { + if (sr == null || !sr.red) { + if (sl != null) { + sl.red = false; + } + + xpr.red = true; + root = this.rotateRight(root, xpr); + xp = x.parent; + xpr = x.parent == null ? null : xp.right; + } + + if (xpr != null) { + xpr.red = xp == null ? false : xp.red; + sr = xpr.right; + if (xpr.right != null) { + sr.red = false; + } + } + + if (xp != null) { + xp.red = false; + root = this.rotateLeft(root, xp); + } + + x = root; + } else { + xpr.red = true; + x = xp; + } + } + } else { + if (xpl != null && xpl.red) { + xpl.red = false; + xp.red = true; + root = this.rotateRight(root, xp); + xp = x.parent; + xpl = x.parent == null ? null : xp.left; + } + + if (xpl == null) { + x = xp; + } else { + Int2ObjectConcurrentHashMap.TreeNode sl = xpl.left; + Int2ObjectConcurrentHashMap.TreeNode sr = xpl.right; + if (sl != null && sl.red || sr != null && sr.red) { + if (sl == null || !sl.red) { + if (sr != null) { + sr.red = false; + } + + xpl.red = true; + root = this.rotateLeft(root, xpl); + xp = x.parent; + xpl = x.parent == null ? null : xp.left; + } + + if (xpl != null) { + xpl.red = xp == null ? false : xp.red; + sl = xpl.left; + if (xpl.left != null) { + sl.red = false; + } + } + + if (xp != null) { + xp.red = false; + root = this.rotateRight(root, xp); + } + + x = root; + } else { + xpl.red = true; + x = xp; + } + } + } + } + + return root; + } + + protected boolean checkInvariants(Int2ObjectConcurrentHashMap.TreeNode t) { + Int2ObjectConcurrentHashMap.TreeNode tp = t.parent; + Int2ObjectConcurrentHashMap.TreeNode tl = t.left; + Int2ObjectConcurrentHashMap.TreeNode tr = t.right; + Int2ObjectConcurrentHashMap.TreeNode tb = t.prev; + Int2ObjectConcurrentHashMap.TreeNode tn = (Int2ObjectConcurrentHashMap.TreeNode)t.next; + if (tb != null && tb.next != t) { + return false; + } else if (tn != null && tn.prev != t) { + return false; + } else if (tp != null && t != tp.left && t != tp.right) { + return false; + } else if (tl == null || tl.parent == t && tl.hash <= t.hash) { + if (tr == null || tr.parent == t && tr.hash >= t.hash) { + if (t.red && tl != null && tl.red && tr != null && tr.red) { + return false; + } else { + return tl != null && !this.checkInvariants(tl) ? false : tr == null || this.checkInvariants(tr); + } + } else { + return false; + } + } else { + return false; + } + } + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + U = (Unsafe)f.get(null); + Class k = Int2ObjectConcurrentHashMap.TreeBin.class; + LOCKSTATE = U.objectFieldOffset(k.getDeclaredField("lockState")); + } catch (Exception var2) { + throw new Error(var2); + } + } + } + + protected static final class TreeNode extends Int2ObjectConcurrentHashMap.Node { + public Int2ObjectConcurrentHashMap.TreeNode parent; + public Int2ObjectConcurrentHashMap.TreeNode left; + public Int2ObjectConcurrentHashMap.TreeNode right; + public Int2ObjectConcurrentHashMap.TreeNode prev; + public boolean red; + + public TreeNode(int empty, int hash, int key, V val, Int2ObjectConcurrentHashMap.Node next, Int2ObjectConcurrentHashMap.TreeNode parent) { + super(empty, hash, key, val, next); + this.parent = parent; + } + + @Override + protected Int2ObjectConcurrentHashMap.Node find(int h, int k) { + return this.findTreeNode(h, k, null); + } + + protected final Int2ObjectConcurrentHashMap.TreeNode findTreeNode(int h, int k, Class kc) { + if (k != this.EMPTY) { + Int2ObjectConcurrentHashMap.TreeNode p = this; + + do { + Int2ObjectConcurrentHashMap.TreeNode pl = p.left; + Int2ObjectConcurrentHashMap.TreeNode pr = p.right; + int ph = p.hash; + if (p.hash > h) { + p = pl; + } else if (ph < h) { + p = pr; + } else { + int pk = p.key; + if (p.key == k || pk != this.EMPTY && k == pk) { + return p; + } + + if (pl == null) { + p = pr; + } else if (pr == null) { + p = pl; + } else { + int dir; + if ((dir = Integer.compare(k, pk)) != 0) { + p = dir < 0 ? pl : pr; + } else { + Int2ObjectConcurrentHashMap.TreeNode q; + if ((q = pr.findTreeNode(h, k, kc)) != null) { + return q; + } + + p = pl; + } + } + } + } while (p != null); + } + + return null; + } + } + + protected static final class ValueIterator extends Int2ObjectConcurrentHashMap.BaseIterator implements ObjectIterator, Enumeration { + public ValueIterator(Int2ObjectConcurrentHashMap.Node[] tab, int index, int size, int limit, Int2ObjectConcurrentHashMap map) { + super(tab, index, size, limit, map); + } + + @Override + public final V next() { + Int2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + V v = p.val; + this.lastReturned = p; + this.advance(); + return v; + } + } + + @Override + public final V nextElement() { + return this.next(); + } + } + + protected static final class ValueSpliterator extends Int2ObjectConcurrentHashMap.Traverser implements ObjectSpliterator { + public long est; + + public ValueSpliterator(Int2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est) { + super(tab, size, index, limit); + this.est = est; + } + + @Override + public ObjectSpliterator trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Int2ObjectConcurrentHashMap.ValueSpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1); + } + + @Override + public void forEachRemaining(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(p.val); + } + } + } + + @Override + public boolean tryAdvance(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(p.val); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4352; + } + } + + protected static final class ValuesView extends Int2ObjectConcurrentHashMap.CollectionView implements FastCollection, Serializable { + public static final long serialVersionUID = 2249069246763182397L; + + public ValuesView(Int2ObjectConcurrentHashMap map) { + super(map); + } + + @Override + public final boolean contains(Object o) { + return this.map.containsValue(o); + } + + @Override + public final boolean remove(Object o) { + if (o != null) { + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (o.equals(it.next())) { + it.remove(); + return true; + } + } + } + + return false; + } + + @Override + public final ObjectIterator iterator() { + Int2ObjectConcurrentHashMap m = this.map; + Int2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Int2ObjectConcurrentHashMap.ValueIterator<>(t, f, 0, f, m); + } + + @Override + public final boolean add(V e) { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public ObjectSpliterator spliterator() { + Int2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Int2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Int2ObjectConcurrentHashMap.ValueSpliterator<>(t, f, 0, f, n < 0L ? 0L : n); + } + + @Override + public void forEach(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Int2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.val); + } + } + } + } + + @Override + public void forEach( + FastCollection.FastConsumerD9 consumer, + A a, + double d1, + double d2, + double d3, + double d4, + double d5, + double d6, + double d7, + double d8, + double d9, + B b, + C c, + D d + ) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Int2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, a, d1, d2, d3, d4, d5, d6, d7, d8, d9, b, c, d); + } + } + } + } + + @Override + public void forEach( + FastCollection.FastConsumerD6 consumer, A a, double d1, double d2, double d3, double d4, double d5, double d6, B b, C c, D d + ) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Int2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, a, d1, d2, d3, d4, d5, d6, b, c, d); + } + } + } + } + + @Override + public void forEachWithFloat(FastCollection.FastConsumerF consumer, float ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Int2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + + @Override + public void forEachWithInt(FastCollection.FastConsumerI consumer, int ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Int2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + + @Override + public void forEachWithLong(FastCollection.FastConsumerL consumer, long ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Int2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Int2ObjectConcurrentHashMap.Node[] tab = tt; + Int2ObjectConcurrentHashMap.Node next = null; + Int2ObjectConcurrentHashMap.TableStack stack = null; + Int2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Int2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Int2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Int2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Int2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Int2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Int2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Int2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Int2ObjectConcurrentHashMap.TreeBin) { + p = ((Int2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Int2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Int2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + } +} diff --git a/src/com/hypixel/fastutil/ints/Int2ObjectOperator.java b/src/com/hypixel/fastutil/ints/Int2ObjectOperator.java new file mode 100644 index 0000000..84d7521 --- /dev/null +++ b/src/com/hypixel/fastutil/ints/Int2ObjectOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.ints; + +@FunctionalInterface +public interface Int2ObjectOperator { + V apply(int var1, V var2); +} diff --git a/src/com/hypixel/fastutil/ints/Int2ShortOperator.java b/src/com/hypixel/fastutil/ints/Int2ShortOperator.java new file mode 100644 index 0000000..f60c043 --- /dev/null +++ b/src/com/hypixel/fastutil/ints/Int2ShortOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.ints; + +@FunctionalInterface +public interface Int2ShortOperator { + short apply(int var1, short var2); +} diff --git a/src/com/hypixel/fastutil/longs/Long2ByteOperator.java b/src/com/hypixel/fastutil/longs/Long2ByteOperator.java new file mode 100644 index 0000000..d290f29 --- /dev/null +++ b/src/com/hypixel/fastutil/longs/Long2ByteOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.longs; + +@FunctionalInterface +public interface Long2ByteOperator { + byte apply(long var1, byte var3); +} diff --git a/src/com/hypixel/fastutil/longs/Long2CharOperator.java b/src/com/hypixel/fastutil/longs/Long2CharOperator.java new file mode 100644 index 0000000..6f4fab0 --- /dev/null +++ b/src/com/hypixel/fastutil/longs/Long2CharOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.longs; + +@FunctionalInterface +public interface Long2CharOperator { + char apply(long var1, char var3); +} diff --git a/src/com/hypixel/fastutil/longs/Long2DoubleOperator.java b/src/com/hypixel/fastutil/longs/Long2DoubleOperator.java new file mode 100644 index 0000000..53296b0 --- /dev/null +++ b/src/com/hypixel/fastutil/longs/Long2DoubleOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.longs; + +@FunctionalInterface +public interface Long2DoubleOperator { + double apply(long var1, double var3); +} diff --git a/src/com/hypixel/fastutil/longs/Long2FloatOperator.java b/src/com/hypixel/fastutil/longs/Long2FloatOperator.java new file mode 100644 index 0000000..e14d434 --- /dev/null +++ b/src/com/hypixel/fastutil/longs/Long2FloatOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.longs; + +@FunctionalInterface +public interface Long2FloatOperator { + float apply(long var1, float var3); +} diff --git a/src/com/hypixel/fastutil/longs/Long2IntOperator.java b/src/com/hypixel/fastutil/longs/Long2IntOperator.java new file mode 100644 index 0000000..b22d867 --- /dev/null +++ b/src/com/hypixel/fastutil/longs/Long2IntOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.longs; + +@FunctionalInterface +public interface Long2IntOperator { + int apply(long var1, int var3); +} diff --git a/src/com/hypixel/fastutil/longs/Long2LongOperator.java b/src/com/hypixel/fastutil/longs/Long2LongOperator.java new file mode 100644 index 0000000..f7e8fb2 --- /dev/null +++ b/src/com/hypixel/fastutil/longs/Long2LongOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.longs; + +@FunctionalInterface +public interface Long2LongOperator { + long apply(long var1, long var3); +} diff --git a/src/com/hypixel/fastutil/longs/Long2ObjectConcurrentHashMap.java b/src/com/hypixel/fastutil/longs/Long2ObjectConcurrentHashMap.java new file mode 100644 index 0000000..00f0d00 --- /dev/null +++ b/src/com/hypixel/fastutil/longs/Long2ObjectConcurrentHashMap.java @@ -0,0 +1,10262 @@ +package com.hypixel.fastutil.longs; + +import com.hypixel.fastutil.FastCollection; +import com.hypixel.fastutil.util.SneakyThrow; +import com.hypixel.fastutil.util.TLRUtil; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.LongCollection; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.longs.LongSpliterator; +import it.unimi.dsi.fastutil.objects.ObjectCollection; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import it.unimi.dsi.fastutil.objects.ObjectSpliterator; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.CountedCompleter; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.DoubleBinaryOperator; +import java.util.function.Function; +import java.util.function.IntBinaryOperator; +import java.util.function.LongBinaryOperator; +import java.util.function.LongConsumer; +import java.util.function.Predicate; +import java.util.function.ToDoubleFunction; +import java.util.function.ToIntFunction; +import sun.misc.Unsafe; + +public class Long2ObjectConcurrentHashMap { + protected static final long serialVersionUID = 7249069246763182397L; + protected static final int MAXIMUM_CAPACITY = 1073741824; + protected static final int DEFAULT_CAPACITY = 16; + protected static final int MAX_ARRAY_SIZE = 2147483639; + protected static final int DEFAULT_CONCURRENCY_LEVEL = 16; + protected static final float LOAD_FACTOR = 0.75F; + protected static final int TREEIFY_THRESHOLD = 8; + protected static final int UNTREEIFY_THRESHOLD = 6; + protected static final int MIN_TREEIFY_CAPACITY = 64; + protected static final int MIN_TRANSFER_STRIDE = 16; + protected static int RESIZE_STAMP_BITS = 16; + protected static final int MAX_RESIZERS = (1 << 32 - RESIZE_STAMP_BITS) - 1; + protected static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS; + protected static final int MOVED = -1; + protected static final int TREEBIN = -2; + protected static final int RESERVED = -3; + protected static final int HASH_BITS = Integer.MAX_VALUE; + protected static final int NCPU = Runtime.getRuntime().availableProcessors(); + protected transient volatile Long2ObjectConcurrentHashMap.Node[] table; + protected transient volatile Long2ObjectConcurrentHashMap.Node[] nextTable; + protected transient volatile long baseCount; + protected transient volatile int sizeCtl; + protected transient volatile int transferIndex; + protected transient volatile int cellsBusy; + protected transient volatile Long2ObjectConcurrentHashMap.CounterCell[] counterCells; + protected transient Long2ObjectConcurrentHashMap.KeySetView keySet; + protected transient Long2ObjectConcurrentHashMap.ValuesView values; + protected transient Long2ObjectConcurrentHashMap.EntrySetView entrySet; + protected final long EMPTY; + protected static final Unsafe U; + protected static final long SIZECTL; + protected static final long TRANSFERINDEX; + protected static final long BASECOUNT; + protected static final long CELLSBUSY; + protected static final long CELLVALUE; + protected static final long ABASE; + protected static final int ASHIFT; + + protected static final int spread(int h) { + return (h ^ h >>> 16) & 2147483647; + } + + protected static final int tableSizeFor(int c) { + int n = c - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return n < 0 ? 1 : (n >= 1073741824 ? 1073741824 : n + 1); + } + + protected static final Long2ObjectConcurrentHashMap.Node tabAt(Long2ObjectConcurrentHashMap.Node[] tab, int i) { + return (Long2ObjectConcurrentHashMap.Node)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); + } + + protected static final boolean casTabAt( + Long2ObjectConcurrentHashMap.Node[] tab, int i, Long2ObjectConcurrentHashMap.Node c, Long2ObjectConcurrentHashMap.Node v + ) { + return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v); + } + + protected static final void setTabAt(Long2ObjectConcurrentHashMap.Node[] tab, int i, Long2ObjectConcurrentHashMap.Node v) { + U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v); + } + + public Long2ObjectConcurrentHashMap() { + this.EMPTY = -1L; + } + + public Long2ObjectConcurrentHashMap(boolean nonce, long emptyValue) { + this.EMPTY = emptyValue; + } + + public Long2ObjectConcurrentHashMap(int initialCapacity) { + this(initialCapacity, true, -1L); + } + + public Long2ObjectConcurrentHashMap(int initialCapacity, boolean nonce, long emptyValue) { + if (initialCapacity < 0) { + throw new IllegalArgumentException(); + } else { + int cap = initialCapacity >= 536870912 ? 1073741824 : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1); + this.sizeCtl = cap; + this.EMPTY = emptyValue; + } + } + + public Long2ObjectConcurrentHashMap(Map m, long emptyValue) { + this.sizeCtl = 16; + this.EMPTY = emptyValue; + this.putAll(m); + } + + public Long2ObjectConcurrentHashMap(Long2ObjectConcurrentHashMap m) { + this.sizeCtl = 16; + this.EMPTY = m.EMPTY; + this.putAll(m); + } + + public Long2ObjectConcurrentHashMap(Long2ObjectMap m) { + this.sizeCtl = 16; + this.EMPTY = -1L; + this.putAll(m); + } + + public Long2ObjectConcurrentHashMap(Long2ObjectMap m, long emptyValue) { + this.sizeCtl = 16; + this.EMPTY = emptyValue; + this.putAll(m); + } + + public Long2ObjectConcurrentHashMap(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, 1, -1L); + } + + public Long2ObjectConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel, long emptyValue) { + if (loadFactor > 0.0F && initialCapacity >= 0 && concurrencyLevel > 0) { + if (initialCapacity < concurrencyLevel) { + initialCapacity = concurrencyLevel; + } + + long size = (long)(1.0 + (float)initialCapacity / loadFactor); + int cap = size >= 1073741824L ? 1073741824 : tableSizeFor((int)size); + this.sizeCtl = cap; + this.EMPTY = emptyValue; + } else { + throw new IllegalArgumentException(); + } + } + + public int size() { + long n = this.sumCount(); + return n < 0L ? 0 : (n > 2147483647L ? Integer.MAX_VALUE : (int)n); + } + + public boolean isEmpty() { + return this.sumCount() <= 0L; + } + + public V get(long key) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + int h = spread(Long.hashCode(key)); + Long2ObjectConcurrentHashMap.Node[] tab = this.table; + Long2ObjectConcurrentHashMap.Node e; + int n; + if (this.table != null && (n = tab.length) > 0 && (e = tabAt(tab, n - 1 & h)) != null) { + int eh = e.hash; + if (e.hash == h) { + long ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + return e.val; + } + } else if (eh < 0) { + Long2ObjectConcurrentHashMap.Node p; + return (p = e.find(h, key)) != null ? p.val : null; + } + + while ((e = e.next) != null) { + if (e.hash == h) { + long ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + return e.val; + } + } + } + } + + return null; + } + } + + public boolean containsKey(long key) { + return this.get(key) != null; + } + + public boolean containsValue(Object value) { + if (value == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label85: + while (true) { + if (p != null) { + next = p; + break; + } + + if (baseIndex < baseLimit) { + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab != null && (n = tab.length) > index && index >= 0) { + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + continue; + } + + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + continue label85; + } + } + } + + next = null; + break; + } + + if (p == null) { + break; + } + + V v = p.val; + if (p.val == value || v != null && value.equals(v)) { + return true; + } + } + } + + return false; + } + } + + public V put(long key, V value) { + return this.putVal(key, value, false); + } + + protected final V putVal(long key, V value, boolean onlyIfAbsent) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value == null) { + throw new NullPointerException(); + } else { + int hash = spread(Long.hashCode(key)); + int binCount = 0; + Long2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Long2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & hash)) == null) { + if (casTabAt(tab, i, null, new Long2ObjectConcurrentHashMap.Node<>(this.EMPTY, hash, key, value, null))) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + V oldVal = null; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Long2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Long2ObjectConcurrentHashMap.Node p; + if ((p = ((Long2ObjectConcurrentHashMap.TreeBin)f).putTreeVal(hash, key, value)) != null) { + oldVal = p.val; + if (!onlyIfAbsent) { + p.val = value; + } + } + } + } else { + binCount = 1; + Long2ObjectConcurrentHashMap.Node e = f; + + while (true) { + if (e.hash == hash) { + long ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + oldVal = e.val; + if (!onlyIfAbsent) { + e.val = value; + } + break; + } + } + + Long2ObjectConcurrentHashMap.Node pred = e; + if ((e = e.next) == null) { + pred.next = new Long2ObjectConcurrentHashMap.Node<>(this.EMPTY, hash, key, value, null); + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + + if (oldVal != null) { + return oldVal; + } + break; + } + } + } + } + + this.addCount(1L, binCount); + return null; + } + } + + public void putAll(Map m) { + this.tryPresize(m.size()); + + for (Map.Entry e : m.entrySet()) { + this.putVal(e.getKey(), (V)e.getValue(), false); + } + } + + public void putAll(Long2ObjectConcurrentHashMap m) { + this.tryPresize(m.size()); + + for (Long2ObjectMap.Entry e : m.long2ObjectEntrySet()) { + this.putVal(e.getLongKey(), (V)e.getValue(), false); + } + } + + public void putAll(Long2ObjectMap m) { + this.tryPresize(m.size()); + + for (Long2ObjectMap.Entry next : m.long2ObjectEntrySet()) { + this.putVal(next.getLongKey(), (V)next.getValue(), false); + } + } + + public V remove(long key) { + return this.replaceNode(key, null, null); + } + + @Deprecated + public V remove(Long key) { + return this.replaceNode(key, null, null); + } + + @Deprecated + public V remove(Object key) { + return this.remove((Long)key); + } + + protected final V replaceNode(long key, V value, Object cv) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + int hash = spread(Long.hashCode(key)); + Long2ObjectConcurrentHashMap.Node[] tab = this.table; + + Long2ObjectConcurrentHashMap.Node f; + int n; + int i; + while (tab != null && (n = tab.length) != 0 && (f = tabAt(tab, i = n - 1 & hash)) != null) { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + V oldVal = null; + boolean validated = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Long2ObjectConcurrentHashMap.TreeBin) { + validated = true; + Long2ObjectConcurrentHashMap.TreeBin t = (Long2ObjectConcurrentHashMap.TreeBin)f; + Long2ObjectConcurrentHashMap.TreeNode r = t.root; + Long2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(hash, key, null)) != null) { + V pv = p.val; + if (cv == null || cv == pv || pv != null && cv.equals(pv)) { + oldVal = pv; + if (value != null) { + p.val = value; + } else if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } + } else { + validated = true; + Long2ObjectConcurrentHashMap.Node e = f; + Long2ObjectConcurrentHashMap.Node pred = null; + + do { + if (e.hash == hash) { + long ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + V ev = e.val; + if (cv == null || cv == ev || ev != null && cv.equals(ev)) { + oldVal = ev; + if (value != null) { + e.val = value; + } else if (pred != null) { + pred.next = e.next; + } else { + setTabAt(tab, i, e.next); + } + } + break; + } + } + + pred = e; + } while ((e = e.next) != null); + } + } + } + + if (validated) { + if (oldVal != null) { + if (value == null) { + this.addCount(-1L, -1); + } + + return oldVal; + } + break; + } + } + } + + return null; + } + } + + public void clear() { + long delta = 0L; + int i = 0; + Long2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (tab != null && i < tab.length) { + Long2ObjectConcurrentHashMap.Node f = tabAt(tab, i); + if (f == null) { + i++; + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + i = 0; + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + for (Long2ObjectConcurrentHashMap.Node p = (Long2ObjectConcurrentHashMap.Node)(fh >= 0 + ? f + : (f instanceof Long2ObjectConcurrentHashMap.TreeBin ? ((Long2ObjectConcurrentHashMap.TreeBin)f).first : null)); + p != null; + p = p.next + ) { + delta--; + } + + setTabAt(tab, i++, null); + } + } + } + } + } + + if (delta != 0L) { + this.addCount(delta, -1); + } + } + + public Long2ObjectConcurrentHashMap.KeySetView keySet() { + Long2ObjectConcurrentHashMap.KeySetView ks = this.keySet; + return this.keySet != null ? ks : (this.keySet = this.buildKeySetView()); + } + + protected Long2ObjectConcurrentHashMap.KeySetView buildKeySetView() { + return new Long2ObjectConcurrentHashMap.KeySetView<>(this, null); + } + + public FastCollection values() { + Long2ObjectConcurrentHashMap.ValuesView vs = this.values; + return this.values != null ? vs : (this.values = this.buildValuesView()); + } + + protected Long2ObjectConcurrentHashMap.ValuesView buildValuesView() { + return new Long2ObjectConcurrentHashMap.ValuesView<>(this); + } + + public ObjectSet> long2ObjectEntrySet() { + Long2ObjectConcurrentHashMap.EntrySetView es = this.entrySet; + return this.entrySet != null ? es : (this.entrySet = this.buildEntrySetView()); + } + + @Deprecated + public ObjectSet> entrySet() { + return this.long2ObjectEntrySet(); + } + + protected Long2ObjectConcurrentHashMap.EntrySetView buildEntrySetView() { + return new Long2ObjectConcurrentHashMap.EntrySetView<>(this); + } + + @Override + public int hashCode() { + int h = 0; + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label75: { + while (true) { + if (p != null) { + next = p; + break label75; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + h += Long.hashCode(p.key) ^ p.val.hashCode(); + } + } + + return h; + } + + @Override + public String toString() { + Long2ObjectConcurrentHashMap.Node[] t = this.table; + int f = this.table == null ? 0 : t.length; + Long2ObjectConcurrentHashMap.Traverser it = new Long2ObjectConcurrentHashMap.Traverser<>(t, f, 0, f); + StringBuilder sb = new StringBuilder(); + sb.append('{'); + Long2ObjectConcurrentHashMap.Node p; + if ((p = it.advance()) != null) { + while (true) { + long k = p.key; + V v = p.val; + sb.append(k); + sb.append('='); + sb.append(v == this ? "(this Map)" : v); + if ((p = it.advance()) == null) { + break; + } + + sb.append(',').append(' '); + } + } + + return sb.append('}').toString(); + } + + @Override + public boolean equals(Object o) { + if (o != this) { + if (!(o instanceof Long2ObjectConcurrentHashMap m)) { + return false; + } + + Long2ObjectConcurrentHashMap.Node[] t = this.table; + int f = this.table == null ? 0 : t.length; + Long2ObjectConcurrentHashMap.Traverser it = new Long2ObjectConcurrentHashMap.Traverser<>(t, f, 0, f); + + Long2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + V val = p.val; + Object v = m.get(p.key); + if (v == null || v != val && !v.equals(val)) { + return false; + } + } + + for (Long2ObjectMap.Entry e : m.long2ObjectEntrySet()) { + Object mv; + Object v; + long mk; + if ((mk = e.getLongKey()) == m.EMPTY || (mv = e.getValue()) == null || (v = this.get(mk)) == null || mv != v && !mv.equals(v)) { + return false; + } + } + } + + return true; + } + + public V putIfAbsent(long key, V value) { + return this.putVal(key, value, true); + } + + public boolean remove(long key, Object value) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + return value != null && this.replaceNode(key, null, value) != null; + } + } + + public boolean replace(long key, V oldValue, V newValue) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (oldValue != null && newValue != null) { + return this.replaceNode(key, newValue, oldValue) != null; + } else { + throw new NullPointerException(); + } + } + + public V replace(long key, V value) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value == null) { + throw new NullPointerException(); + } else { + return this.replaceNode(key, value, null); + } + } + + public V getOrDefault(long key, V defaultValue) { + V v; + return (v = this.get(key)) == null ? defaultValue : v; + } + + public int forEach(Long2ObjectConcurrentHashMap.LongObjConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val); + count++; + } + } + + return count; + } + } + + public int forEach(Long2ObjectConcurrentHashMap.LongBiObjConsumer action, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, x); + count++; + } + } + + return count; + } + } + + public int forEach(Long2ObjectConcurrentHashMap.LongTriObjConsumer action, X x, Y y) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, x, y); + count++; + } + } + + return count; + } + } + + public int forEachWithByte(Long2ObjectConcurrentHashMap.LongObjByteConsumer action, byte ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithShort(Long2ObjectConcurrentHashMap.LongObjShortConsumer action, short ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithInt(Long2ObjectConcurrentHashMap.LongObjIntConsumer action, int ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithLong(Long2ObjectConcurrentHashMap.LongObjLongConsumer action, long ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithFloat(Long2ObjectConcurrentHashMap.LongObjFloatConsumer action, float ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithDouble(Long2ObjectConcurrentHashMap.LongObjDoubleConsumer action, double ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithByte(Long2ObjectConcurrentHashMap.LongBiObjByteConsumer action, byte ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithShort(Long2ObjectConcurrentHashMap.LongBiObjShortConsumer action, short ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithInt(Long2ObjectConcurrentHashMap.LongBiObjIntConsumer action, int ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithLong(Long2ObjectConcurrentHashMap.LongBiObjLongConsumer action, long ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithFloat(Long2ObjectConcurrentHashMap.LongBiObjFloatConsumer action, float ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithDouble(Long2ObjectConcurrentHashMap.LongBiObjDoubleConsumer action, double ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public void replaceAll(Long2ObjectOperator function) { + if (function == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label86: { + while (true) { + if (p != null) { + next = p; + break label86; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + V oldValue = p.val; + long key = p.key; + + V newValue; + do { + newValue = function.apply(key, oldValue); + if (newValue == null) { + throw new NullPointerException(); + } + } while (this.replaceNode(key, newValue, oldValue) == null && (oldValue = this.get(key)) != null); + } + } + } + } + + public V computeIfAbsent(long key, Long2ObjectConcurrentHashMap.LongFunction mappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (mappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Long.hashCode(key)); + V val = null; + int binCount = 0; + Long2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Long2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + Long2ObjectConcurrentHashMap.Node r = new Long2ObjectConcurrentHashMap.ReservationNode<>(this.EMPTY); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Long2ObjectConcurrentHashMap.Node node = null; + + try { + if ((val = (V)mappingFunction.apply(key)) != null) { + node = new Long2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + } finally { + setTabAt(tab, i, node); + } + } + } + + if (binCount != 0) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + boolean added = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Long2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Long2ObjectConcurrentHashMap.TreeBin t = (Long2ObjectConcurrentHashMap.TreeBin)f; + Long2ObjectConcurrentHashMap.TreeNode r = t.root; + Long2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(h, key, null)) != null) { + val = p.val; + } else if ((val = (V)mappingFunction.apply(key)) != null) { + added = true; + t.putTreeVal(h, key, val); + } + } + } else { + binCount = 1; + Long2ObjectConcurrentHashMap.Node e = f; + + while (true) { + if (e.hash == h) { + long ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = e.val; + break; + } + } + + Long2ObjectConcurrentHashMap.Node pred = e; + if ((e = e.next) == null) { + if ((val = (V)mappingFunction.apply(key)) != null) { + added = true; + pred.next = new Long2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + + if (!added) { + return val; + } + break; + } + } + } + } + + if (val != null) { + this.addCount(1L, binCount); + } + + return val; + } + } + + public V computeIfPresent(long key, Long2ObjectConcurrentHashMap.LongObjFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (remappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Long.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Long2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Long2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + break; + } + + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Long2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Long2ObjectConcurrentHashMap.TreeBin t = (Long2ObjectConcurrentHashMap.TreeBin)f; + Long2ObjectConcurrentHashMap.TreeNode r = t.root; + Long2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(h, key, null)) != null) { + val = (V)remappingFunction.apply(key, p.val); + if (val != null) { + p.val = val; + } else { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } + } else { + binCount = 1; + Long2ObjectConcurrentHashMap.Node e = f; + Long2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + long ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(key, e.val); + if (val != null) { + e.val = val; + } else { + delta = -1; + Long2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + break; + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } + } + + public V compute(long key, Long2ObjectConcurrentHashMap.LongObjFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (remappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Long.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Long2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Long2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + Long2ObjectConcurrentHashMap.Node r = new Long2ObjectConcurrentHashMap.ReservationNode<>(this.EMPTY); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Long2ObjectConcurrentHashMap.Node node = null; + + try { + if ((val = (V)remappingFunction.apply(key, null)) != null) { + delta = 1; + node = new Long2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + } finally { + setTabAt(tab, i, node); + } + } + } + + if (binCount != 0) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Long2ObjectConcurrentHashMap.TreeBin) { + binCount = 1; + Long2ObjectConcurrentHashMap.TreeBin t = (Long2ObjectConcurrentHashMap.TreeBin)f; + Long2ObjectConcurrentHashMap.TreeNode r = t.root; + Long2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null) { + p = r.findTreeNode(h, key, null); + } else { + p = null; + } + + V pv = p == null ? null : p.val; + val = (V)remappingFunction.apply(key, pv); + if (val != null) { + if (p != null) { + p.val = val; + } else { + delta = 1; + t.putTreeVal(h, key, val); + } + } else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } else { + binCount = 1; + Long2ObjectConcurrentHashMap.Node e = f; + Long2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + long ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(key, e.val); + if (val != null) { + e.val = val; + } else { + delta = -1; + Long2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + val = (V)remappingFunction.apply(key, null); + if (val != null) { + delta = 1; + pred.next = new Long2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + break; + } + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } + } + + public V merge(long key, V value, BiFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value != null && remappingFunction != null) { + int h = spread(Long.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Long2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Long2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + if (casTabAt(tab, i, null, new Long2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, value, null))) { + delta = 1; + val = value; + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Long2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Long2ObjectConcurrentHashMap.TreeBin t = (Long2ObjectConcurrentHashMap.TreeBin)f; + Long2ObjectConcurrentHashMap.TreeNode r = t.root; + Long2ObjectConcurrentHashMap.TreeNode p = r == null ? null : r.findTreeNode(h, key, null); + val = p == null ? value : remappingFunction.apply(p.val, value); + if (val != null) { + if (p != null) { + p.val = val; + } else { + delta = 1; + t.putTreeVal(h, key, val); + } + } else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } else { + binCount = 1; + Long2ObjectConcurrentHashMap.Node e = f; + Long2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + long ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(e.val, value); + if (val != null) { + e.val = val; + } else { + delta = -1; + Long2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + delta = 1; + val = value; + pred.next = new Long2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, value, null); + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + break; + } + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } else { + throw new NullPointerException(); + } + } + + public long mappingCount() { + long n = this.sumCount(); + return n < 0L ? 0L : n; + } + + public static LongSet newKeySet() { + return new Long2ObjectConcurrentHashMap.KeySetView<>(new Long2ObjectConcurrentHashMap<>(), Boolean.TRUE); + } + + public static Long2ObjectConcurrentHashMap.KeySetView newKeySet(int initialCapacity) { + return new Long2ObjectConcurrentHashMap.KeySetView<>(new Long2ObjectConcurrentHashMap<>(initialCapacity), Boolean.TRUE); + } + + public Long2ObjectConcurrentHashMap.KeySetView keySet(V mappedValue) { + if (mappedValue == null) { + throw new NullPointerException(); + } else { + return new Long2ObjectConcurrentHashMap.KeySetView<>(this, mappedValue); + } + } + + protected static final int resizeStamp(int n) { + return Integer.numberOfLeadingZeros(n) | 1 << RESIZE_STAMP_BITS - 1; + } + + protected final Long2ObjectConcurrentHashMap.Node[] initTable() { + Long2ObjectConcurrentHashMap.Node[] tab; + while (true) { + tab = this.table; + if (this.table != null && tab.length != 0) { + break; + } + + int sc = this.sizeCtl; + if (this.sizeCtl < 0) { + Thread.yield(); + } else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + tab = this.table; + if (this.table == null || tab.length == 0) { + int n = sc > 0 ? sc : 16; + Long2ObjectConcurrentHashMap.Node[] nt = new Long2ObjectConcurrentHashMap.Node[n]; + tab = nt; + this.table = nt; + sc = n - (n >>> 2); + } + break; + } finally { + this.sizeCtl = sc; + } + } + } + + return tab; + } + + protected final void addCount(long x, int check) { + boolean uncontended; + label77: { + long s; + label74: { + Long2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + if (this.counterCells == null) { + long b = this.baseCount; + if (U.compareAndSwapLong(this, BASECOUNT, this.baseCount, s = b + x)) { + break label74; + } + } + + uncontended = (boolean)1; + Long2ObjectConcurrentHashMap.CounterCell a; + int m; + if (as == null || (m = as.length - 1) < 0 || (a = as[TLRUtil.getProbe() & m]) == null) { + break label77; + } + + long v = a.value; + if (!(uncontended = U.compareAndSwapLong(a, CELLVALUE, a.value, v + x))) { + break label77; + } + + if (check <= 1) { + return; + } + + s = this.sumCount(); + } + + if (check >= 0) { + while (true) { + int sc = this.sizeCtl; + if (s < this.sizeCtl) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] tab = this.table; + int n; + if (this.table == null || (n = tab.length) >= 1073741824) { + break; + } + + uncontended = (boolean)resizeStamp(n); + if (sc < 0) { + if (sc >>> RESIZE_STAMP_SHIFT != uncontended || sc == uncontended + 1 || sc == uncontended + MAX_RESIZERS) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] nt = this.nextTable; + if (this.nextTable == null || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nt); + } + } else if (U.compareAndSwapInt(this, SIZECTL, sc, (uncontended << RESIZE_STAMP_SHIFT) + 2)) { + this.transfer(tab, null); + } + + s = this.sumCount(); + } + } + + return; + } + + this.fullAddCount(x, uncontended); + } + + protected final Long2ObjectConcurrentHashMap.Node[] helpTransfer(Long2ObjectConcurrentHashMap.Node[] tab, Long2ObjectConcurrentHashMap.Node f) { + if (tab != null && f instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + Long2ObjectConcurrentHashMap.Node[] nextTab = ((Long2ObjectConcurrentHashMap.ForwardingNode)f).nextTable; + if (((Long2ObjectConcurrentHashMap.ForwardingNode)f).nextTable != null) { + int rs = resizeStamp(tab.length); + + while (nextTab == this.nextTable && this.table == tab) { + int sc = this.sizeCtl; + if (this.sizeCtl >= 0 || sc >>> RESIZE_STAMP_SHIFT != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nextTab); + break; + } + } + + return nextTab; + } + } + + return this.table; + } + + protected final void tryPresize(int size) { + int c = size >= 536870912 ? 1073741824 : tableSizeFor(size + (size >>> 1) + 1); + + while (true) { + int sc = this.sizeCtl; + if (this.sizeCtl < 0) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] tab = this.table; + int n; + if (tab != null && (n = tab.length) != 0) { + if (c <= sc || n >= 1073741824) { + break; + } + + if (tab == this.table) { + int rs = resizeStamp(n); + if (sc < 0) { + if (sc >>> RESIZE_STAMP_SHIFT != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] nt = this.nextTable; + if (this.nextTable == null || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nt); + } + } else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) { + this.transfer(tab, null); + } + } + } else { + n = sc > c ? sc : c; + if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + if (this.table == tab) { + Long2ObjectConcurrentHashMap.Node[] ntx = new Long2ObjectConcurrentHashMap.Node[n]; + this.table = ntx; + sc = n - (n >>> 2); + } + } finally { + this.sizeCtl = sc; + } + } + } + } + } + + protected final void transfer(Long2ObjectConcurrentHashMap.Node[] tab, Long2ObjectConcurrentHashMap.Node[] nextTab) { + int n = tab.length; + int stride; + if ((stride = NCPU > 1 ? (n >>> 3) / NCPU : n) < 16) { + stride = 16; + } + + if (nextTab == null) { + try { + Long2ObjectConcurrentHashMap.Node[] nt = new Long2ObjectConcurrentHashMap.Node[n << 1]; + nextTab = nt; + } catch (Throwable var27) { + this.sizeCtl = Integer.MAX_VALUE; + return; + } + + this.nextTable = nextTab; + this.transferIndex = n; + } + + int nextn = nextTab.length; + Long2ObjectConcurrentHashMap.ForwardingNode fwd = new Long2ObjectConcurrentHashMap.ForwardingNode<>(this.EMPTY, nextTab); + boolean advance = true; + boolean finishing = false; + int i = 0; + int bound = 0; + + while (true) { + while (!advance) { + if (i >= 0 && i < n && i + n < nextn) { + Long2ObjectConcurrentHashMap.Node f; + if ((f = tabAt(tab, i)) == null) { + advance = casTabAt(tab, i, null, fwd); + } else { + int fh = f.hash; + if (f.hash == -1) { + advance = true; + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + int runBit = fh & n; + Long2ObjectConcurrentHashMap.Node lastRun = f; + + for (Long2ObjectConcurrentHashMap.Node p = f.next; p != null; p = p.next) { + int b = p.hash & n; + if (b != runBit) { + runBit = b; + lastRun = p; + } + } + + Long2ObjectConcurrentHashMap.Node ln; + Long2ObjectConcurrentHashMap.Node hn; + if (runBit == 0) { + ln = lastRun; + hn = null; + } else { + hn = lastRun; + ln = null; + } + + for (Long2ObjectConcurrentHashMap.Node px = f; px != lastRun; px = px.next) { + int ph = px.hash; + long pk = px.key; + V pv = px.val; + if ((ph & n) == 0) { + ln = new Long2ObjectConcurrentHashMap.Node<>(this.EMPTY, ph, pk, pv, ln); + } else { + hn = new Long2ObjectConcurrentHashMap.Node<>(this.EMPTY, ph, pk, pv, hn); + } + } + + setTabAt(nextTab, i, ln); + setTabAt(nextTab, i + n, hn); + setTabAt(tab, i, fwd); + advance = true; + } else if (f instanceof Long2ObjectConcurrentHashMap.TreeBin t) { + Long2ObjectConcurrentHashMap.TreeNode lo = null; + Long2ObjectConcurrentHashMap.TreeNode loTail = null; + Long2ObjectConcurrentHashMap.TreeNode hi = null; + Long2ObjectConcurrentHashMap.TreeNode hiTail = null; + int lc = 0; + int hc = 0; + + for (Long2ObjectConcurrentHashMap.Node e = t.first; e != null; e = e.next) { + int h = e.hash; + Long2ObjectConcurrentHashMap.TreeNode pxx = new Long2ObjectConcurrentHashMap.TreeNode<>( + this.EMPTY, h, e.key, e.val, null, null + ); + if ((h & n) == 0) { + if ((pxx.prev = loTail) == null) { + lo = pxx; + } else { + loTail.next = pxx; + } + + loTail = pxx; + lc++; + } else { + if ((pxx.prev = hiTail) == null) { + hi = pxx; + } else { + hiTail.next = pxx; + } + + hiTail = pxx; + hc++; + } + } + + Long2ObjectConcurrentHashMap.Node ln = (Long2ObjectConcurrentHashMap.Node)(lc <= 6 + ? this.untreeify(lo) + : (hc != 0 ? new Long2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, lo) : t)); + Long2ObjectConcurrentHashMap.Node hn = (Long2ObjectConcurrentHashMap.Node)(hc <= 6 + ? this.untreeify(hi) + : (lc != 0 ? new Long2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, hi) : t)); + setTabAt(nextTab, i, ln); + setTabAt(nextTab, i + n, hn); + setTabAt(tab, i, fwd); + advance = true; + } + } + } + } + } + } else { + if (finishing) { + this.nextTable = null; + this.table = nextTab; + this.sizeCtl = (n << 1) - (n >>> 1); + return; + } + + int sc = this.sizeCtl; + if (U.compareAndSwapInt(this, SIZECTL, this.sizeCtl, sc - 1)) { + if (sc - 2 != resizeStamp(n) << RESIZE_STAMP_SHIFT) { + return; + } + + advance = true; + finishing = true; + i = n; + } + } + } + + if (--i < bound && !finishing) { + int nextIndex = this.transferIndex; + if (this.transferIndex <= 0) { + i = -1; + advance = false; + } else { + int nextBound; + if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex, nextBound = nextIndex > stride ? nextIndex - stride : 0)) { + bound = nextBound; + i = nextIndex - 1; + advance = false; + } + } + } else { + advance = false; + } + } + } + + protected final long sumCount() { + Long2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + long sum = this.baseCount; + if (as != null) { + for (int i = 0; i < as.length; i++) { + Long2ObjectConcurrentHashMap.CounterCell a; + if ((a = as[i]) != null) { + sum += a.value; + } + } + } + + return sum; + } + + protected final void fullAddCount(long x, boolean wasUncontended) { + int h; + if ((h = TLRUtil.getProbe()) == 0) { + TLRUtil.localInit(); + h = TLRUtil.getProbe(); + wasUncontended = true; + } + + boolean collide = false; + + while (true) { + Long2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + int n; + if (this.counterCells != null && (n = as.length) > 0) { + Long2ObjectConcurrentHashMap.CounterCell a; + if ((a = as[n - 1 & h]) == null) { + if (this.cellsBusy == 0) { + Long2ObjectConcurrentHashMap.CounterCell r = new Long2ObjectConcurrentHashMap.CounterCell(x); + if (this.cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean created = false; + + try { + Long2ObjectConcurrentHashMap.CounterCell[] rs = this.counterCells; + int m; + int j; + if (this.counterCells != null && (m = rs.length) > 0 && rs[j = m - 1 & h] == null) { + rs[j] = r; + created = true; + } + } finally { + this.cellsBusy = 0; + } + + if (created) { + break; + } + continue; + } + } + + collide = false; + } else if (!wasUncontended) { + wasUncontended = true; + } else { + long v = a.value; + if (U.compareAndSwapLong(a, CELLVALUE, a.value, v + x)) { + break; + } + + if (this.counterCells != as || n >= NCPU) { + collide = false; + } else if (!collide) { + collide = true; + } else if (this.cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + try { + if (this.counterCells == as) { + Long2ObjectConcurrentHashMap.CounterCell[] rs = new Long2ObjectConcurrentHashMap.CounterCell[n << 1]; + + for (int i = 0; i < n; i++) { + rs[i] = as[i]; + } + + this.counterCells = rs; + } + } finally { + this.cellsBusy = 0; + } + + collide = false; + continue; + } + } + + h = TLRUtil.advanceProbe(h); + } else if (this.cellsBusy == 0 && this.counterCells == as && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean init = false; + + try { + if (this.counterCells == as) { + Long2ObjectConcurrentHashMap.CounterCell[] rs = new Long2ObjectConcurrentHashMap.CounterCell[2]; + rs[h & 1] = new Long2ObjectConcurrentHashMap.CounterCell(x); + this.counterCells = rs; + init = true; + } + } finally { + this.cellsBusy = 0; + } + + if (init) { + break; + } + } else { + long vx = this.baseCount; + if (U.compareAndSwapLong(this, BASECOUNT, this.baseCount, vx + x)) { + break; + } + } + } + } + + protected final void treeifyBin(Long2ObjectConcurrentHashMap.Node[] tab, int index) { + if (tab != null) { + int n; + if ((n = tab.length) < 64) { + this.tryPresize(n << 1); + } else { + Long2ObjectConcurrentHashMap.Node b; + if ((b = tabAt(tab, index)) != null && b.hash >= 0) { + synchronized (b) { + if (tabAt(tab, index) == b) { + Long2ObjectConcurrentHashMap.TreeNode hd = null; + Long2ObjectConcurrentHashMap.TreeNode tl = null; + + for (Long2ObjectConcurrentHashMap.Node e = b; e != null; e = e.next) { + Long2ObjectConcurrentHashMap.TreeNode p = new Long2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, e.hash, e.key, e.val, null, null); + if ((p.prev = tl) == null) { + hd = p; + } else { + tl.next = p; + } + + tl = p; + } + + setTabAt(tab, index, new Long2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, hd)); + } + } + } + } + } + } + + protected Long2ObjectConcurrentHashMap.Node untreeify(Long2ObjectConcurrentHashMap.Node b) { + Long2ObjectConcurrentHashMap.Node hd = null; + Long2ObjectConcurrentHashMap.Node tl = null; + + for (Long2ObjectConcurrentHashMap.Node q = b; q != null; q = q.next) { + Long2ObjectConcurrentHashMap.Node p = new Long2ObjectConcurrentHashMap.Node<>(this.EMPTY, q.hash, q.key, q.val, null); + if (tl == null) { + hd = p; + } else { + tl.next = p; + } + + tl = p; + } + + return hd; + } + + protected final int batchFor(long b) { + long n; + if (b != Long.MAX_VALUE && (n = this.sumCount()) > 1L && n >= b) { + int sp = ForkJoinPool.getCommonPoolParallelism() << 2; + long var6; + return b > 0L && (var6 = n / b) < sp ? (int)var6 : sp; + } else { + return 0; + } + } + + public void forEach(long parallelismThreshold, Long2ObjectConcurrentHashMap.LongObjConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Long2ObjectConcurrentHashMap.ForEachMappingTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEach( + long parallelismThreshold, Long2ObjectConcurrentHashMap.LongObjFunction transformer, Consumer action + ) { + if (transformer != null && action != null) { + new Long2ObjectConcurrentHashMap.ForEachTransformedMappingTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U search(long parallelismThreshold, Long2ObjectConcurrentHashMap.LongObjFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Long2ObjectConcurrentHashMap.SearchMappingsTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public U search(Long2ObjectConcurrentHashMap.LongObjFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U search(Long2ObjectConcurrentHashMap.LongBiObjFunction searchFunction, X x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithByte(Long2ObjectConcurrentHashMap.LongObjByteFunction searchFunction, byte x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithShort(Long2ObjectConcurrentHashMap.LongObjShortFunction searchFunction, short x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithInt(Long2ObjectConcurrentHashMap.LongObjIntFunction searchFunction, int x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithLong(Long2ObjectConcurrentHashMap.LongObjLongFunction searchFunction, long x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithFloat(Long2ObjectConcurrentHashMap.LongObjFloatFunction searchFunction, float x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithDouble(Long2ObjectConcurrentHashMap.LongObjDoubleFunction searchFunction, double x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U reduce( + long parallelismThreshold, + Long2ObjectConcurrentHashMap.LongObjFunction transformer, + BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Long2ObjectConcurrentHashMap.MapReduceMappingsTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer + ) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U reduce(Long2ObjectConcurrentHashMap.LongObjFunction transformer, BiFunction reducer) { + if (transformer != null && reducer != null) { + Long2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table == null) { + return null; + } else { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + U r = null; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label88: { + while (true) { + if (p != null) { + next = p; + break label88; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + return r; + } + + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + } + } else { + throw new NullPointerException(); + } + } + + public double reduceToDouble( + long parallelismThreshold, Long2ObjectConcurrentHashMap.ToDoubleLongObjFunction transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Long2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceToLong( + long parallelismThreshold, Long2ObjectConcurrentHashMap.ToLongLongObjFunction transformer, long basis, LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Long2ObjectConcurrentHashMap.MapReduceMappingsToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceToInt( + long parallelismThreshold, Long2ObjectConcurrentHashMap.ToIntLongObjFunction transformer, int basis, IntBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Long2ObjectConcurrentHashMap.MapReduceMappingsToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachKey(long parallelismThreshold, LongConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Long2ObjectConcurrentHashMap.ForEachKeyTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachKey(long parallelismThreshold, Long2ObjectConcurrentHashMap.LongFunction transformer, Consumer action) { + if (transformer != null && action != null) { + new Long2ObjectConcurrentHashMap.ForEachTransformedKeyTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchKeys(long parallelismThreshold, Long2ObjectConcurrentHashMap.LongFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Long2ObjectConcurrentHashMap.SearchKeysTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public long reduceKeys(long parallelismThreshold, Long2ObjectConcurrentHashMap.LongReduceTaskOperator reducer) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Long2ObjectConcurrentHashMap.ReduceKeysTask<>(this.EMPTY, null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer) + .invoke0(); + } + } + + public U reduceKeys( + long parallelismThreshold, Long2ObjectConcurrentHashMap.LongFunction transformer, BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Long2ObjectConcurrentHashMap.MapReduceKeysTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceKeysToDouble( + long parallelismThreshold, Long2ObjectConcurrentHashMap.LongToDoubleFunction transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Long2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceKeysToLong(long parallelismThreshold, Long2ObjectConcurrentHashMap.LongToLongFunction transformer, long basis, LongBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Long2ObjectConcurrentHashMap.MapReduceKeysToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceKeysToInt(long parallelismThreshold, Long2ObjectConcurrentHashMap.LongToIntFunction transformer, int basis, IntBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Long2ObjectConcurrentHashMap.MapReduceKeysToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachValue(long parallelismThreshold, Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Long2ObjectConcurrentHashMap.ForEachValueTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachValue(long parallelismThreshold, Function transformer, Consumer action) { + if (transformer != null && action != null) { + new Long2ObjectConcurrentHashMap.ForEachTransformedValueTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchValues(long parallelismThreshold, Function searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Long2ObjectConcurrentHashMap.SearchValuesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public V reduceValues(long parallelismThreshold, BiFunction reducer) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Long2ObjectConcurrentHashMap.ReduceValuesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer).invoke(); + } + } + + public U reduceValues(long parallelismThreshold, Function transformer, BiFunction reducer) { + if (transformer != null && reducer != null) { + return new Long2ObjectConcurrentHashMap.MapReduceValuesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceValuesToDouble(long parallelismThreshold, ToDoubleFunction transformer, double basis, DoubleBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Long2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceValuesToLong( + long parallelismThreshold, Long2ObjectConcurrentHashMap.ToLongFunction transformer, long basis, LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Long2ObjectConcurrentHashMap.MapReduceValuesToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceValuesToInt(long parallelismThreshold, ToIntFunction transformer, int basis, IntBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Long2ObjectConcurrentHashMap.MapReduceValuesToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachEntry(long parallelismThreshold, Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Long2ObjectConcurrentHashMap.ForEachEntryTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachEntry(long parallelismThreshold, Function, ? extends U> transformer, Consumer action) { + if (transformer != null && action != null) { + new Long2ObjectConcurrentHashMap.ForEachTransformedEntryTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchEntries(long parallelismThreshold, Function, ? extends U> searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Long2ObjectConcurrentHashMap.SearchEntriesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public Long2ObjectConcurrentHashMap.Entry reduceEntries( + long parallelismThreshold, + BiFunction, Long2ObjectConcurrentHashMap.Entry, ? extends Long2ObjectConcurrentHashMap.Entry> reducer + ) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Long2ObjectConcurrentHashMap.ReduceEntriesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer).invoke(); + } + } + + public U reduceEntries( + long parallelismThreshold, + Function, ? extends U> transformer, + BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Long2ObjectConcurrentHashMap.MapReduceEntriesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceEntriesToDouble( + long parallelismThreshold, ToDoubleFunction> transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Long2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceEntriesToLong( + long parallelismThreshold, + Long2ObjectConcurrentHashMap.ToLongFunction> transformer, + long basis, + LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Long2ObjectConcurrentHashMap.MapReduceEntriesToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceEntriesToInt( + long parallelismThreshold, ToIntFunction> transformer, int basis, IntBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Long2ObjectConcurrentHashMap.MapReduceEntriesToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public V valueMatching(Predicate predicate) { + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + Long2ObjectConcurrentHashMap.Node[] tab = this.table; + int f = this.table == null ? 0 : tab.length; + int baseLimit = f; + int baseSize = f; + boolean b = false; + + label80: + while (next != null || !b) { + b |= true; + Long2ObjectConcurrentHashMap.Node e = next; + if (next != null) { + e = next.next; + } + + label76: + while (e == null) { + if (baseIndex < baseLimit) { + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab != null && (n = tab.length) > index && index >= 0) { + if ((e = tabAt(tab, index)) != null && e.hash < 0) { + if (e instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (e instanceof Long2ObjectConcurrentHashMap.TreeBin) { + e = ((Long2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + continue; + } + + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack next1 = stack.next; + stack.next = spare; + stack = next1; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + continue label76; + } + } + } + + next = null; + continue label80; + } + + next = e; + if (predicate.test(e.val)) { + return e.val; + } + } + + return null; + } + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + U = (Unsafe)f.get(null); + Class k = Long2ObjectConcurrentHashMap.class; + SIZECTL = U.objectFieldOffset(k.getDeclaredField("sizeCtl")); + TRANSFERINDEX = U.objectFieldOffset(k.getDeclaredField("transferIndex")); + BASECOUNT = U.objectFieldOffset(k.getDeclaredField("baseCount")); + CELLSBUSY = U.objectFieldOffset(k.getDeclaredField("cellsBusy")); + Class ck = Long2ObjectConcurrentHashMap.CounterCell.class; + CELLVALUE = U.objectFieldOffset(ck.getDeclaredField("value")); + Class ak = Long2ObjectConcurrentHashMap.Node[].class; + ABASE = U.arrayBaseOffset(ak); + int scale = U.arrayIndexScale(ak); + if ((scale & scale - 1) != 0) { + throw new Error("data type scale not a power of two"); + } else { + ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); + } + } catch (Exception var5) { + throw new Error(var5); + } + } + + protected static class BaseIterator extends Long2ObjectConcurrentHashMap.Traverser { + public final Long2ObjectConcurrentHashMap map; + public Long2ObjectConcurrentHashMap.Node lastReturned; + + public BaseIterator(Long2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, Long2ObjectConcurrentHashMap map) { + super(tab, size, index, limit); + this.map = map; + this.advance(); + } + + public final boolean hasNext() { + return this.next != null; + } + + public final boolean hasMoreElements() { + return this.next != null; + } + + public final void remove() { + Long2ObjectConcurrentHashMap.Node p = this.lastReturned; + if (this.lastReturned == null) { + throw new IllegalStateException(); + } else { + this.lastReturned = null; + this.map.replaceNode(p.key, null, null); + } + } + } + + protected abstract static class BulkTask extends CountedCompleter { + public Long2ObjectConcurrentHashMap.Node[] tab; + public Long2ObjectConcurrentHashMap.Node next; + public Long2ObjectConcurrentHashMap.TableStack stack; + public Long2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + public int batch; + + protected BulkTask(Long2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Long2ObjectConcurrentHashMap.Node[] t) { + super(par); + this.batch = b; + this.index = this.baseIndex = i; + if ((this.tab = t) == null) { + this.baseSize = this.baseLimit = 0; + } else if (par == null) { + this.baseSize = this.baseLimit = t.length; + } else { + this.baseLimit = f; + this.baseSize = par.baseSize; + } + } + + protected final Long2ObjectConcurrentHashMap.Node advance() { + Long2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Long2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Long2ObjectConcurrentHashMap.TreeBin) { + e = ((Long2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Long2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Long2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Long2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + } + + protected abstract static class CollectionView implements ObjectCollection, Serializable { + public static final long serialVersionUID = 7249069246763182397L; + public final Long2ObjectConcurrentHashMap map; + protected static final String oomeMsg = "Required array size too large"; + + public CollectionView(Long2ObjectConcurrentHashMap map) { + this.map = map; + } + + public Long2ObjectConcurrentHashMap getMap() { + return this.map; + } + + @Override + public final void clear() { + this.map.clear(); + } + + @Override + public final int size() { + return this.map.size(); + } + + @Override + public final boolean isEmpty() { + return this.map.isEmpty(); + } + + @Override + public abstract ObjectIterator iterator(); + + @Override + public abstract boolean contains(Object var1); + + @Override + public abstract boolean remove(Object var1); + + @Override + public final Object[] toArray() { + long sz = this.map.mappingCount(); + if (sz > 2147483639L) { + throw new OutOfMemoryError("Required array size too large"); + } else { + int n = (int)sz; + Object[] r = new Object[n]; + int i = 0; + + for (E e : this) { + if (i == n) { + if (n >= 2147483639) { + throw new OutOfMemoryError("Required array size too large"); + } + + if (n >= 1073741819) { + n = 2147483639; + } else { + n += (n >>> 1) + 1; + } + + r = Arrays.copyOf(r, n); + } + + r[i++] = e; + } + + return i == n ? r : Arrays.copyOf(r, i); + } + } + + @Override + public final T[] toArray(T[] a) { + long sz = this.map.mappingCount(); + if (sz > 2147483639L) { + throw new OutOfMemoryError("Required array size too large"); + } else { + int m = (int)sz; + T[] r = (T[])(a.length >= m ? a : (Object[])Array.newInstance(a.getClass().getComponentType(), m)); + int n = r.length; + int i = 0; + + for (E e : this) { + if (i == n) { + if (n >= 2147483639) { + throw new OutOfMemoryError("Required array size too large"); + } + + if (n >= 1073741819) { + n = 2147483639; + } else { + n += (n >>> 1) + 1; + } + + r = (T[])Arrays.copyOf(r, n); + } + + r[i++] = (T)e; + } + + if (a == r && i < n) { + r[i] = null; + return r; + } else { + return (T[])(i == n ? r : Arrays.copyOf(r, i)); + } + } + } + + @Override + public final String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + Iterator it = this.iterator(); + if (it.hasNext()) { + while (true) { + Object e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (!it.hasNext()) { + break; + } + + sb.append(',').append(' '); + } + } + + return sb.append(']').toString(); + } + + @Override + public final boolean containsAll(Collection c) { + if (c != this) { + for (Object e : c) { + if (e == null || !this.contains(e)) { + return false; + } + } + } + + return true; + } + + @Override + public final boolean removeAll(Collection c) { + if (c == null) { + throw new NullPointerException(); + } else { + boolean modified = false; + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (c.contains(it.next())) { + it.remove(); + modified = true; + } + } + + return modified; + } + } + + @Override + public final boolean retainAll(Collection c) { + if (c == null) { + throw new NullPointerException(); + } else { + boolean modified = false; + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + + return modified; + } + } + } + + protected static final class CounterCell { + public volatile long value; + + public CounterCell(long x) { + this.value = x; + } + } + + protected abstract static class DoubleReturningBulkTask extends Long2ObjectConcurrentHashMap.BulkTask { + public double result; + + public DoubleReturningBulkTask(Long2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Long2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected double invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + public interface Entry extends Long2ObjectMap.Entry { + boolean isEmpty(); + + @Deprecated + @Override + Long getKey(); + + @Override + long getLongKey(); + + @Override + V getValue(); + + @Override + int hashCode(); + + @Override + String toString(); + + @Override + boolean equals(Object var1); + + @Override + V setValue(V var1); + } + + protected static final class EntryIterator extends Long2ObjectConcurrentHashMap.BaseIterator implements ObjectIterator> { + public EntryIterator(Long2ObjectConcurrentHashMap.Node[] tab, int index, int size, int limit, Long2ObjectConcurrentHashMap map) { + super(tab, index, size, limit, map); + } + + public final Long2ObjectConcurrentHashMap.Entry next() { + Long2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + long k = p.key; + V v = p.val; + this.lastReturned = p; + this.advance(); + return new Long2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), k, v, this.map); + } + } + } + + protected static final class EntrySetView + extends Long2ObjectConcurrentHashMap.CollectionView> + implements ObjectSet>, + Serializable { + public static final long serialVersionUID = 2249069246763182397L; + + public EntrySetView(Long2ObjectConcurrentHashMap map) { + super(map); + } + + @Override + public boolean contains(Object o) { + if (o instanceof Long2ObjectMap.Entry) { + Long2ObjectMap.Entry e; + long k = (e = (Long2ObjectMap.Entry)o).getLongKey(); + if (!((Long2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + Object v; + Object r; + return (r = this.map.get(k)) != null && (v = e.getValue()) != null && (v == r || v.equals(r)); + } + } + + return false; + } + + @Override + public boolean remove(Object o) { + if (o instanceof Long2ObjectMap.Entry) { + Long2ObjectMap.Entry e; + long k = (e = (Long2ObjectMap.Entry)o).getLongKey(); + if (!((Long2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + Object v; + return (v = e.getValue()) != null && this.map.remove(k, v); + } + } + + return false; + } + + @Override + public ObjectIterator> iterator() { + Long2ObjectConcurrentHashMap m = this.map; + Long2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Long2ObjectConcurrentHashMap.EntryIterator<>(t, f, 0, f, m); + } + + public boolean add(Long2ObjectMap.Entry e) { + return this.map.putVal(e.getLongKey(), e.getValue(), false) == null; + } + + @Override + public boolean addAll(Collection> c) { + boolean added = false; + + for (Long2ObjectMap.Entry e : c) { + if (this.add(e)) { + added = true; + } + } + + return added; + } + + @Override + public final int hashCode() { + int h = 0; + Long2ObjectConcurrentHashMap.Node[] t = this.map.table; + if (this.map.table != null) { + Long2ObjectConcurrentHashMap.Traverser it = new Long2ObjectConcurrentHashMap.Traverser<>(t, t.length, 0, t.length); + + Long2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + h += p.hashCode(); + } + } + + return h; + } + + @Override + public final boolean equals(Object o) { + Set c; + return o instanceof Set && ((c = (Set)o) == this || this.containsAll(c) && c.containsAll(this)); + } + + @Override + public ObjectSpliterator> spliterator() { + Long2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Long2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Long2ObjectConcurrentHashMap.EntrySpliterator<>(t, f, 0, f, n < 0L ? 0L : n, m); + } + + @Override + public void forEach(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node[] t = this.map.table; + if (this.map.table != null) { + Long2ObjectConcurrentHashMap.Traverser it = new Long2ObjectConcurrentHashMap.Traverser<>(t, t.length, 0, t.length); + + Long2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + action.accept(new Long2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + } + } + } + } + } + + protected static final class EntrySpliterator extends Long2ObjectConcurrentHashMap.Traverser implements ObjectSpliterator> { + public final Long2ObjectConcurrentHashMap map; + public long est; + + public EntrySpliterator(Long2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est, Long2ObjectConcurrentHashMap map) { + super(tab, size, index, limit); + this.map = map; + this.est = est; + } + + @Override + public ObjectSpliterator> trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Long2ObjectConcurrentHashMap.EntrySpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1, this.map); + } + + @Override + public void forEachRemaining(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(new Long2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + } + } + } + + @Override + public boolean tryAdvance(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(new Long2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4353; + } + } + + protected static final class ForEachEntryTask extends Long2ObjectConcurrentHashMap.BulkTask { + public final Consumer> action; + + public ForEachEntryTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Consumer> action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Consumer> action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Long2ObjectConcurrentHashMap.ForEachEntryTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachKeyTask extends Long2ObjectConcurrentHashMap.BulkTask { + public final LongConsumer action; + + public ForEachKeyTask(Long2ObjectConcurrentHashMap.BulkTask p, int b, int i, int f, Long2ObjectConcurrentHashMap.Node[] t, LongConsumer action) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + LongConsumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Long2ObjectConcurrentHashMap.ForEachKeyTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.key); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachMappingTask extends Long2ObjectConcurrentHashMap.BulkTask { + public final Long2ObjectConcurrentHashMap.LongObjConsumer action; + + public ForEachMappingTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.LongObjConsumer action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Long2ObjectConcurrentHashMap.LongObjConsumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Long2ObjectConcurrentHashMap.ForEachMappingTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.key, p.val); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachTransformedEntryTask extends Long2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> transformer; + public final Consumer action; + + public ForEachTransformedEntryTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Function, ? extends U> transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Function, ? extends U> transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Long2ObjectConcurrentHashMap.ForEachTransformedEntryTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedKeyTask extends Long2ObjectConcurrentHashMap.BulkTask { + public final Long2ObjectConcurrentHashMap.LongFunction transformer; + public final Consumer action; + + public ForEachTransformedKeyTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.LongFunction transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Long2ObjectConcurrentHashMap.LongFunction transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Long2ObjectConcurrentHashMap.ForEachTransformedKeyTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedMappingTask extends Long2ObjectConcurrentHashMap.BulkTask { + public final Long2ObjectConcurrentHashMap.LongObjFunction transformer; + public final Consumer action; + + public ForEachTransformedMappingTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.LongObjFunction transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Long2ObjectConcurrentHashMap.LongObjFunction transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Long2ObjectConcurrentHashMap.ForEachTransformedMappingTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action + ) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedValueTask extends Long2ObjectConcurrentHashMap.BulkTask { + public final Function transformer; + public final Consumer action; + + public ForEachTransformedValueTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Function transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Function transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Long2ObjectConcurrentHashMap.ForEachTransformedValueTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.val)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachValueTask extends Long2ObjectConcurrentHashMap.BulkTask { + public final Consumer action; + + public ForEachValueTask( + Long2ObjectConcurrentHashMap.BulkTask p, int b, int i, int f, Long2ObjectConcurrentHashMap.Node[] t, Consumer action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Long2ObjectConcurrentHashMap.ForEachValueTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.val); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForwardingNode extends Long2ObjectConcurrentHashMap.Node { + public final Long2ObjectConcurrentHashMap.Node[] nextTable; + + public ForwardingNode(long empty, Long2ObjectConcurrentHashMap.Node[] tab) { + super(empty, -1, empty, null, null); + this.nextTable = tab; + } + + @Override + protected Long2ObjectConcurrentHashMap.Node find(int h, long k) { + Long2ObjectConcurrentHashMap.Node[] tab = this.nextTable; + + Long2ObjectConcurrentHashMap.Node e; + int n; + label41: + while (k != this.EMPTY && tab != null && (n = tab.length) != 0 && (e = Long2ObjectConcurrentHashMap.tabAt(tab, n - 1 & h)) != null) { + do { + int eh = e.hash; + if (e.hash == h) { + long ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + + if (eh < 0) { + if (!(e instanceof Long2ObjectConcurrentHashMap.ForwardingNode)) { + return e.find(h, k); + } + + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + continue label41; + } + } while ((e = e.next) != null); + + return null; + } + + return null; + } + } + + protected abstract static class IntReturningBulkTask extends Long2ObjectConcurrentHashMap.BulkTask { + public int result; + + public IntReturningBulkTask(Long2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Long2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected int invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + protected static final class KeyIterator implements LongIterator { + public Long2ObjectConcurrentHashMap.Node[] tab; + public Long2ObjectConcurrentHashMap.Node next; + public Long2ObjectConcurrentHashMap.TableStack stack; + public Long2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + public final Long2ObjectConcurrentHashMap map; + public Long2ObjectConcurrentHashMap.Node lastReturned; + + public KeyIterator(Long2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, Long2ObjectConcurrentHashMap map) { + this.tab = tab; + this.baseSize = size; + this.baseIndex = this.index = index; + this.baseLimit = limit; + this.next = null; + this.map = map; + this.advance(); + } + + protected final Long2ObjectConcurrentHashMap.Node advance() { + Long2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Long2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Long2ObjectConcurrentHashMap.TreeBin) { + e = ((Long2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Long2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Long2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Long2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + + @Override + public final boolean hasNext() { + return this.next != null; + } + + public final boolean hasMoreElements() { + return this.next != null; + } + + @Override + public final void remove() { + Long2ObjectConcurrentHashMap.Node p = this.lastReturned; + if (this.lastReturned == null) { + throw new IllegalStateException(); + } else { + this.lastReturned = null; + this.map.replaceNode(p.key, null, null); + } + } + + @Override + public final long nextLong() { + Long2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + long k = p.key; + this.lastReturned = p; + this.advance(); + return k; + } + } + } + + public static class KeySetView implements LongSet { + public static final long serialVersionUID = 7249069246763182397L; + public final Long2ObjectConcurrentHashMap map; + public final V value; + + public KeySetView(Long2ObjectConcurrentHashMap map, V value) { + this.map = map; + this.value = value; + } + + public V getMappedValue() { + return this.value; + } + + @Override + public boolean contains(long o) { + return this.map.containsKey(o); + } + + @Override + public boolean remove(long o) { + return this.map.remove(o) != null; + } + + @Override + public LongIterator iterator() { + Long2ObjectConcurrentHashMap m = this.map; + Long2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Long2ObjectConcurrentHashMap.KeyIterator<>(t, f, 0, f, m); + } + + @Override + public boolean add(long e) { + V v = this.value; + if (this.value == null) { + throw new UnsupportedOperationException(); + } else { + return this.map.putVal(e, v, true) == null; + } + } + + @Override + public boolean addAll(LongCollection c) { + boolean added = false; + V v = this.value; + if (this.value == null) { + throw new UnsupportedOperationException(); + } else { + LongIterator iter = c.iterator(); + + while (iter.hasNext()) { + long e = iter.nextLong(); + if (this.map.putVal(e, v, true) == null) { + added = true; + } + } + + return added; + } + } + + @Override + public int hashCode() { + int h = 0; + LongIterator iter = this.iterator(); + + while (iter.hasNext()) { + h += Long.hashCode(iter.nextLong()); + } + + return h; + } + + @Override + public boolean equals(Object o) { + LongSet c; + return o instanceof LongSet && ((c = (LongSet)o) == this || this.containsAll(c) && c.containsAll(this)); + } + + public long getNoEntryValue() { + return this.map.EMPTY; + } + + @Override + public int size() { + return this.map.size(); + } + + @Override + public boolean isEmpty() { + return this.map.isEmpty(); + } + + @Override + public Object[] toArray() { + Object[] out = new Long[this.size()]; + LongIterator iter = this.iterator(); + + int i; + for (i = 0; i < out.length && iter.hasNext(); i++) { + out[i] = iter.nextLong(); + } + + if (out.length > i + 1) { + out[i] = this.map.EMPTY; + } + + return out; + } + + @Override + public Object[] toArray(Object[] dest) { + LongIterator iter = this.iterator(); + + int i; + for (i = 0; i < dest.length && iter.hasNext() && i <= dest.length; i++) { + dest[i] = iter.next(); + } + + if (dest.length > i + 1) { + dest[i] = this.map.EMPTY; + } + + return dest; + } + + @Override + public long[] toLongArray() { + long[] out = new long[this.size()]; + LongIterator iter = this.iterator(); + + int i; + for (i = 0; i < out.length && iter.hasNext(); i++) { + out[i] = iter.next(); + } + + if (out.length > i + 1) { + out[i] = this.map.EMPTY; + } + + return out; + } + + @Override + public long[] toArray(long[] dest) { + LongIterator iter = this.iterator(); + + int i; + for (i = 0; i < dest.length && iter.hasNext() && i <= dest.length; i++) { + dest[i] = iter.next(); + } + + if (dest.length > i + 1) { + dest[i] = this.map.EMPTY; + } + + return dest; + } + + @Override + public long[] toLongArray(long[] dest) { + return this.toArray(dest); + } + + @Override + public boolean containsAll(Collection collection) { + for (Object element : collection) { + if (!(element instanceof Long)) { + return false; + } + + long c = (Long)element; + if (!this.contains(c)) { + return false; + } + } + + return true; + } + + @Override + public boolean containsAll(LongCollection collection) { + LongIterator iter = collection.iterator(); + + while (iter.hasNext()) { + long element = iter.next(); + if (!this.contains(element)) { + return false; + } + } + + return true; + } + + public boolean containsAll(long[] array) { + int i = array.length; + + while (i-- > 0) { + if (!this.contains(array[i])) { + return false; + } + } + + return true; + } + + @Override + public boolean addAll(Collection collection) { + boolean changed = false; + + for (Long element : collection) { + long e = element; + if (this.add(e)) { + changed = true; + } + } + + return changed; + } + + public boolean addAll(long[] array) { + boolean changed = false; + int i = array.length; + + while (i-- > 0) { + if (this.add(array[i])) { + changed = true; + } + } + + return changed; + } + + @Override + public boolean retainAll(Collection collection) { + boolean modified = false; + LongIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (!collection.contains(iter.next())) { + iter.remove(); + modified = true; + } + } + + return modified; + } + + @Override + public boolean retainAll(LongCollection collection) { + if (this == collection) { + return false; + } else { + boolean modified = false; + LongIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (!collection.contains(iter.next())) { + iter.remove(); + modified = true; + } + } + + return modified; + } + } + + public boolean retainAll(long[] array) { + boolean modified = false; + LongIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (Arrays.binarySearch(array, iter.next().longValue()) < 0) { + iter.remove(); + modified = true; + } + } + + return modified; + } + + @Override + public boolean removeAll(Collection collection) { + boolean changed = false; + + for (Object element : collection) { + if (element instanceof Long) { + long c = (Long)element; + if (this.remove(c)) { + changed = true; + } + } + } + + return changed; + } + + @Override + public boolean removeAll(LongCollection collection) { + boolean changed = false; + LongIterator iter = collection.iterator(); + + while (iter.hasNext()) { + long element = iter.next(); + if (this.remove(element)) { + changed = true; + } + } + + return changed; + } + + public boolean removeAll(long[] array) { + boolean changed = false; + int i = array.length; + + while (i-- > 0) { + if (this.remove(array[i])) { + changed = true; + } + } + + return changed; + } + + @Override + public void clear() { + this.map.clear(); + } + + @Override + public LongSpliterator spliterator() { + Long2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Long2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Long2ObjectConcurrentHashMap.KeySpliterator<>(t, f, 0, f, n < 0L ? 0L : n); + } + } + + protected static final class KeySpliterator extends Long2ObjectConcurrentHashMap.Traverser implements LongSpliterator { + public long est; + + public KeySpliterator(Long2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est) { + super(tab, size, index, limit); + this.est = est; + } + + @Override + public LongSpliterator trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Long2ObjectConcurrentHashMap.KeySpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1); + } + + @Override + public boolean tryAdvance(Consumer action) { + return action instanceof LongConsumer ? this.tryAdvance((LongConsumer)action) : this.tryAdvance(value -> action.accept(value)); + } + + @Override + public void forEachRemaining(LongConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(p.key); + } + } + } + + @Override + public boolean tryAdvance(LongConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(p.key); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4353; + } + } + + @FunctionalInterface + public interface LongBiObjByteConsumer { + void accept(long var1, V var3, byte var4, X var5); + } + + @FunctionalInterface + public interface LongBiObjConsumer { + void accept(long var1, V var3, X var4); + } + + @FunctionalInterface + public interface LongBiObjDoubleConsumer { + void accept(long var1, V var3, double var4, X var6); + } + + @FunctionalInterface + public interface LongBiObjFloatConsumer { + void accept(long var1, V var3, float var4, X var5); + } + + @FunctionalInterface + public interface LongBiObjFunction { + J apply(long var1, V var3, X var4); + } + + @FunctionalInterface + public interface LongBiObjIntConsumer { + void accept(long var1, V var3, int var4, X var5); + } + + @FunctionalInterface + public interface LongBiObjLongConsumer { + void accept(long var1, V var3, long var4, X var6); + } + + @FunctionalInterface + public interface LongBiObjShortConsumer { + void accept(long var1, V var3, short var4, X var5); + } + + @FunctionalInterface + public interface LongFunction { + R apply(long var1); + } + + @FunctionalInterface + public interface LongObjByteConsumer { + void accept(long var1, V var3, byte var4); + } + + @FunctionalInterface + public interface LongObjByteFunction { + J apply(long var1, V var3, byte var4); + } + + @FunctionalInterface + public interface LongObjConsumer { + void accept(long var1, V var3); + } + + @FunctionalInterface + public interface LongObjDoubleConsumer { + void accept(long var1, V var3, double var4); + } + + @FunctionalInterface + public interface LongObjDoubleFunction { + J apply(long var1, V var3, double var4); + } + + @FunctionalInterface + public interface LongObjFloatConsumer { + void accept(long var1, V var3, float var4); + } + + @FunctionalInterface + public interface LongObjFloatFunction { + J apply(long var1, V var3, float var4); + } + + @FunctionalInterface + public interface LongObjFunction { + J apply(long var1, V var3); + } + + @FunctionalInterface + public interface LongObjIntConsumer { + void accept(long var1, V var3, int var4); + } + + @FunctionalInterface + public interface LongObjIntFunction { + J apply(long var1, V var3, int var4); + } + + @FunctionalInterface + public interface LongObjLongConsumer { + void accept(long var1, V var3, long var4); + } + + @FunctionalInterface + public interface LongObjLongFunction { + J apply(long var1, V var3, long var4); + } + + @FunctionalInterface + public interface LongObjShortConsumer { + void accept(long var1, V var3, short var4); + } + + @FunctionalInterface + public interface LongObjShortFunction { + J apply(long var1, V var3, short var4); + } + + @FunctionalInterface + public interface LongReduceTaskOperator { + long reduce(long var1, long var3, long var5); + } + + protected abstract static class LongReturningBulkTask extends Long2ObjectConcurrentHashMap.BulkTask { + public long result; + + public LongReturningBulkTask(Long2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Long2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected long invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + protected abstract static class LongReturningBulkTask2 extends Long2ObjectConcurrentHashMap.BulkTask { + public long result; + + public LongReturningBulkTask2(Long2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Long2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected long invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + @FunctionalInterface + public interface LongToDoubleFunction { + double applyAsDouble(long var1); + } + + @FunctionalInterface + public interface LongToIntFunction { + int applyAsInt(long var1); + } + + @FunctionalInterface + public interface LongToLongFunction { + long applyAsLong(long var1); + } + + @FunctionalInterface + public interface LongTriObjConsumer { + void accept(long var1, V var3, X var4, Y var5); + } + + protected static final class MapEntry implements Long2ObjectConcurrentHashMap.Entry { + public final boolean empty; + public final long key; + public V val; + public final Long2ObjectConcurrentHashMap map; + + public MapEntry(boolean empty, long key, V val, Long2ObjectConcurrentHashMap map) { + this.empty = empty; + this.key = key; + this.val = val; + this.map = map; + } + + @Override + public boolean isEmpty() { + return this.empty; + } + + @Override + public Long getKey() { + return this.key; + } + + @Override + public long getLongKey() { + return this.key; + } + + @Override + public V getValue() { + return this.val; + } + + @Override + public String toString() { + return this.empty ? "EMPTY=" + this.val : this.key + "=" + this.val; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o instanceof Long2ObjectConcurrentHashMap.Entry) { + if (this.empty != ((Long2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + return false; + } else { + return !this.empty && this.key != ((Long2ObjectConcurrentHashMap.Entry)o).getLongKey() + ? false + : this.val.equals(((Long2ObjectConcurrentHashMap.Entry)o).getValue()); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.empty ? 1 : 0; + result = 31 * result + Long.hashCode(this.key); + return 31 * result + this.val.hashCode(); + } + + @Override + public V setValue(V value) { + if (value == null) { + throw new NullPointerException(); + } else { + V v = this.val; + this.val = value; + this.map.put(this.key, value); + return v; + } + } + } + + protected static final class MapReduceEntriesTask extends Long2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> transformer; + public final BiFunction reducer; + public U result; + public Long2ObjectConcurrentHashMap.MapReduceEntriesTask rights; + public Long2ObjectConcurrentHashMap.MapReduceEntriesTask nextRight; + + public MapReduceEntriesTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.MapReduceEntriesTask nextRight, + Function, ? extends U> transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Function, ? extends U> transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.MapReduceEntriesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Long2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.MapReduceEntriesTask t = (Long2ObjectConcurrentHashMap.MapReduceEntriesTask)c; + + for (Long2ObjectConcurrentHashMap.MapReduceEntriesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceEntriesToDoubleTask extends Long2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final ToDoubleFunction> transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Long2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask rights; + public Long2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask nextRight; + + public MapReduceEntriesToDoubleTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask nextRight, + ToDoubleFunction> transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToDoubleFunction> transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask t = (Long2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask)c; + + for (Long2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceEntriesToIntTask extends Long2ObjectConcurrentHashMap.IntReturningBulkTask { + public final ToIntFunction> transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Long2ObjectConcurrentHashMap.MapReduceEntriesToIntTask rights; + public Long2ObjectConcurrentHashMap.MapReduceEntriesToIntTask nextRight; + + public MapReduceEntriesToIntTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.MapReduceEntriesToIntTask nextRight, + ToIntFunction> transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToIntFunction> transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.MapReduceEntriesToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.MapReduceEntriesToIntTask t = (Long2ObjectConcurrentHashMap.MapReduceEntriesToIntTask)c; + + for (Long2ObjectConcurrentHashMap.MapReduceEntriesToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceEntriesToLongTask extends Long2ObjectConcurrentHashMap.LongReturningBulkTask { + public final Long2ObjectConcurrentHashMap.ToLongFunction> transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Long2ObjectConcurrentHashMap.MapReduceEntriesToLongTask rights; + public Long2ObjectConcurrentHashMap.MapReduceEntriesToLongTask nextRight; + + public MapReduceEntriesToLongTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.MapReduceEntriesToLongTask nextRight, + Long2ObjectConcurrentHashMap.ToLongFunction> transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Long2ObjectConcurrentHashMap.ToLongFunction> transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.MapReduceEntriesToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.MapReduceEntriesToLongTask t = (Long2ObjectConcurrentHashMap.MapReduceEntriesToLongTask)c; + + for (Long2ObjectConcurrentHashMap.MapReduceEntriesToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysTask extends Long2ObjectConcurrentHashMap.BulkTask { + public final Long2ObjectConcurrentHashMap.LongFunction transformer; + public final BiFunction reducer; + public U result; + public Long2ObjectConcurrentHashMap.MapReduceKeysTask rights; + public Long2ObjectConcurrentHashMap.MapReduceKeysTask nextRight; + + public MapReduceKeysTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.MapReduceKeysTask nextRight, + Long2ObjectConcurrentHashMap.LongFunction transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Long2ObjectConcurrentHashMap.LongFunction transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.MapReduceKeysTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Long2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.MapReduceKeysTask t = (Long2ObjectConcurrentHashMap.MapReduceKeysTask)c; + + for (Long2ObjectConcurrentHashMap.MapReduceKeysTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceKeysToDoubleTask extends Long2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final Long2ObjectConcurrentHashMap.LongToDoubleFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Long2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask rights; + public Long2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask nextRight; + + public MapReduceKeysToDoubleTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask nextRight, + Long2ObjectConcurrentHashMap.LongToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Long2ObjectConcurrentHashMap.LongToDoubleFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask t = (Long2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask)c; + + for (Long2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysToIntTask extends Long2ObjectConcurrentHashMap.IntReturningBulkTask { + public final Long2ObjectConcurrentHashMap.LongToIntFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Long2ObjectConcurrentHashMap.MapReduceKeysToIntTask rights; + public Long2ObjectConcurrentHashMap.MapReduceKeysToIntTask nextRight; + + public MapReduceKeysToIntTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.MapReduceKeysToIntTask nextRight, + Long2ObjectConcurrentHashMap.LongToIntFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Long2ObjectConcurrentHashMap.LongToIntFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.MapReduceKeysToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.MapReduceKeysToIntTask t = (Long2ObjectConcurrentHashMap.MapReduceKeysToIntTask)c; + + for (Long2ObjectConcurrentHashMap.MapReduceKeysToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysToLongTask extends Long2ObjectConcurrentHashMap.LongReturningBulkTask { + public final Long2ObjectConcurrentHashMap.LongToLongFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Long2ObjectConcurrentHashMap.MapReduceKeysToLongTask rights; + public Long2ObjectConcurrentHashMap.MapReduceKeysToLongTask nextRight; + + public MapReduceKeysToLongTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.MapReduceKeysToLongTask nextRight, + Long2ObjectConcurrentHashMap.LongToLongFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Long2ObjectConcurrentHashMap.LongToLongFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.MapReduceKeysToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.MapReduceKeysToLongTask t = (Long2ObjectConcurrentHashMap.MapReduceKeysToLongTask)c; + + for (Long2ObjectConcurrentHashMap.MapReduceKeysToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsTask extends Long2ObjectConcurrentHashMap.BulkTask { + public final Long2ObjectConcurrentHashMap.LongObjFunction transformer; + public final BiFunction reducer; + public U result; + public Long2ObjectConcurrentHashMap.MapReduceMappingsTask rights; + public Long2ObjectConcurrentHashMap.MapReduceMappingsTask nextRight; + + public MapReduceMappingsTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.MapReduceMappingsTask nextRight, + Long2ObjectConcurrentHashMap.LongObjFunction transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Long2ObjectConcurrentHashMap.LongObjFunction transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.MapReduceMappingsTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Long2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.MapReduceMappingsTask t = (Long2ObjectConcurrentHashMap.MapReduceMappingsTask)c; + + for (Long2ObjectConcurrentHashMap.MapReduceMappingsTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceMappingsToDoubleTask extends Long2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final Long2ObjectConcurrentHashMap.ToDoubleLongObjFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Long2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask rights; + public Long2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask nextRight; + + public MapReduceMappingsToDoubleTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask nextRight, + Long2ObjectConcurrentHashMap.ToDoubleLongObjFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Long2ObjectConcurrentHashMap.ToDoubleLongObjFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask t = (Long2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask)c; + + for (Long2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsToIntTask extends Long2ObjectConcurrentHashMap.IntReturningBulkTask { + public final Long2ObjectConcurrentHashMap.ToIntLongObjFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Long2ObjectConcurrentHashMap.MapReduceMappingsToIntTask rights; + public Long2ObjectConcurrentHashMap.MapReduceMappingsToIntTask nextRight; + + public MapReduceMappingsToIntTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.MapReduceMappingsToIntTask nextRight, + Long2ObjectConcurrentHashMap.ToIntLongObjFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Long2ObjectConcurrentHashMap.ToIntLongObjFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.MapReduceMappingsToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.MapReduceMappingsToIntTask t = (Long2ObjectConcurrentHashMap.MapReduceMappingsToIntTask)c; + + for (Long2ObjectConcurrentHashMap.MapReduceMappingsToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsToLongTask extends Long2ObjectConcurrentHashMap.LongReturningBulkTask { + public final Long2ObjectConcurrentHashMap.ToLongLongObjFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Long2ObjectConcurrentHashMap.MapReduceMappingsToLongTask rights; + public Long2ObjectConcurrentHashMap.MapReduceMappingsToLongTask nextRight; + + public MapReduceMappingsToLongTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.MapReduceMappingsToLongTask nextRight, + Long2ObjectConcurrentHashMap.ToLongLongObjFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Long2ObjectConcurrentHashMap.ToLongLongObjFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.MapReduceMappingsToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.MapReduceMappingsToLongTask t = (Long2ObjectConcurrentHashMap.MapReduceMappingsToLongTask)c; + + for (Long2ObjectConcurrentHashMap.MapReduceMappingsToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesTask extends Long2ObjectConcurrentHashMap.BulkTask { + public final Function transformer; + public final BiFunction reducer; + public U result; + public Long2ObjectConcurrentHashMap.MapReduceValuesTask rights; + public Long2ObjectConcurrentHashMap.MapReduceValuesTask nextRight; + + public MapReduceValuesTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.MapReduceValuesTask nextRight, + Function transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Function transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.MapReduceValuesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Long2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.MapReduceValuesTask t = (Long2ObjectConcurrentHashMap.MapReduceValuesTask)c; + + for (Long2ObjectConcurrentHashMap.MapReduceValuesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceValuesToDoubleTask extends Long2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final ToDoubleFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Long2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask rights; + public Long2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask nextRight; + + public MapReduceValuesToDoubleTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask nextRight, + ToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToDoubleFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask t = (Long2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask)c; + + for (Long2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesToIntTask extends Long2ObjectConcurrentHashMap.IntReturningBulkTask { + public final ToIntFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Long2ObjectConcurrentHashMap.MapReduceValuesToIntTask rights; + public Long2ObjectConcurrentHashMap.MapReduceValuesToIntTask nextRight; + + public MapReduceValuesToIntTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.MapReduceValuesToIntTask nextRight, + ToIntFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToIntFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.MapReduceValuesToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.MapReduceValuesToIntTask t = (Long2ObjectConcurrentHashMap.MapReduceValuesToIntTask)c; + + for (Long2ObjectConcurrentHashMap.MapReduceValuesToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesToLongTask extends Long2ObjectConcurrentHashMap.LongReturningBulkTask { + public final Long2ObjectConcurrentHashMap.ToLongFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Long2ObjectConcurrentHashMap.MapReduceValuesToLongTask rights; + public Long2ObjectConcurrentHashMap.MapReduceValuesToLongTask nextRight; + + public MapReduceValuesToLongTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.MapReduceValuesToLongTask nextRight, + Long2ObjectConcurrentHashMap.ToLongFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Long2ObjectConcurrentHashMap.ToLongFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.MapReduceValuesToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.MapReduceValuesToLongTask t = (Long2ObjectConcurrentHashMap.MapReduceValuesToLongTask)c; + + for (Long2ObjectConcurrentHashMap.MapReduceValuesToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static class Node implements Long2ObjectConcurrentHashMap.Entry { + public final long EMPTY; + public final int hash; + public final long key; + public volatile V val; + public volatile Long2ObjectConcurrentHashMap.Node next; + + public Node(long empty, int hash, long key, V val, Long2ObjectConcurrentHashMap.Node next) { + this.EMPTY = empty; + this.hash = hash; + this.key = key; + this.val = val; + this.next = next; + } + + @Override + public final boolean isEmpty() { + return this.key == this.EMPTY; + } + + @Override + public final Long getKey() { + return this.key; + } + + @Override + public final long getLongKey() { + return this.key; + } + + @Override + public final V getValue() { + return this.val; + } + + @Override + public final int hashCode() { + return Long.hashCode(this.key) ^ this.val.hashCode(); + } + + @Override + public final String toString() { + return this.isEmpty() ? "EMPTY=" + this.val : this.key + "=" + this.val; + } + + @Override + public final V setValue(V value) { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean equals(Object o) { + boolean empty = this.isEmpty(); + if (o instanceof Long2ObjectConcurrentHashMap.Entry) { + if (empty != ((Long2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + return false; + } else { + return !empty && this.key != ((Long2ObjectConcurrentHashMap.Entry)o).getLongKey() + ? false + : this.val.equals(((Long2ObjectConcurrentHashMap.Entry)o).getValue()); + } + } else { + return false; + } + } + + protected Long2ObjectConcurrentHashMap.Node find(int h, long k) { + Long2ObjectConcurrentHashMap.Node e = this; + if (k != this.EMPTY) { + do { + if (e.hash == h) { + long ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + } while ((e = e.next) != null); + } + + return null; + } + } + + protected static final class ReduceEntriesTask extends Long2ObjectConcurrentHashMap.BulkTask> { + public final BiFunction, Long2ObjectConcurrentHashMap.Entry, ? extends Long2ObjectConcurrentHashMap.Entry> reducer; + public Long2ObjectConcurrentHashMap.Entry result; + public Long2ObjectConcurrentHashMap.ReduceEntriesTask rights; + public Long2ObjectConcurrentHashMap.ReduceEntriesTask nextRight; + + public ReduceEntriesTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.ReduceEntriesTask nextRight, + BiFunction, Long2ObjectConcurrentHashMap.Entry, ? extends Long2ObjectConcurrentHashMap.Entry> reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.reducer = reducer; + } + + public final Long2ObjectConcurrentHashMap.Entry getRawResult() { + return this.result; + } + + @Override + public final void compute() { + BiFunction, Long2ObjectConcurrentHashMap.Entry, ? extends Long2ObjectConcurrentHashMap.Entry> reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.ReduceEntriesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + Long2ObjectConcurrentHashMap.Entry r = null; + + Long2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + r = (Long2ObjectConcurrentHashMap.Entry)(r == null ? p : reducer.apply(r, p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.ReduceEntriesTask t = (Long2ObjectConcurrentHashMap.ReduceEntriesTask)c; + + for (Long2ObjectConcurrentHashMap.ReduceEntriesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + Long2ObjectConcurrentHashMap.Entry sr = s.result; + if (s.result != null) { + Long2ObjectConcurrentHashMap.Entry tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + + protected static final class ReduceKeysTask extends Long2ObjectConcurrentHashMap.LongReturningBulkTask2 { + public final long EMPTY; + public final Long2ObjectConcurrentHashMap.LongReduceTaskOperator reducer; + public Long2ObjectConcurrentHashMap.ReduceKeysTask rights; + public Long2ObjectConcurrentHashMap.ReduceKeysTask nextRight; + + public ReduceKeysTask( + long EMPTY, + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.ReduceKeysTask nextRight, + Long2ObjectConcurrentHashMap.LongReduceTaskOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.EMPTY = EMPTY; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Long2ObjectConcurrentHashMap.LongReduceTaskOperator reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.ReduceKeysTask<>( + this.EMPTY, this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + boolean found = false; + long r = this.EMPTY; + + Long2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + long u = p.key; + if (!found) { + found = true; + r = u; + } else if (!p.isEmpty()) { + found = true; + r = reducer.reduce(this.EMPTY, r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.ReduceKeysTask t = (Long2ObjectConcurrentHashMap.ReduceKeysTask)c; + + for (Long2ObjectConcurrentHashMap.ReduceKeysTask s = t.rights; s != null; s = t.rights = s.nextRight) { + long sr = s.result; + if (s.result != this.EMPTY) { + long tr = t.result; + t.result = t.result == this.EMPTY ? sr : reducer.reduce(this.EMPTY, tr, sr); + } + } + } + } + } + } + + protected static final class ReduceValuesTask extends Long2ObjectConcurrentHashMap.BulkTask { + public final BiFunction reducer; + public V result; + public Long2ObjectConcurrentHashMap.ReduceValuesTask rights; + public Long2ObjectConcurrentHashMap.ReduceValuesTask nextRight; + + public ReduceValuesTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.ReduceValuesTask nextRight, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.reducer = reducer; + } + + @Override + public final V getRawResult() { + return this.result; + } + + @Override + public final void compute() { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Long2ObjectConcurrentHashMap.ReduceValuesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + V r = null; + + Long2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + V v = p.val; + r = r == null ? v : reducer.apply(r, v); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Long2ObjectConcurrentHashMap.ReduceValuesTask t = (Long2ObjectConcurrentHashMap.ReduceValuesTask)c; + + for (Long2ObjectConcurrentHashMap.ReduceValuesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + V sr = s.result; + if (s.result != null) { + V tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + + protected static final class ReservationNode extends Long2ObjectConcurrentHashMap.Node { + public ReservationNode(long empty) { + super(empty, -3, empty, null, null); + } + + @Override + protected Long2ObjectConcurrentHashMap.Node find(int h, long k) { + return null; + } + } + + protected static final class SearchEntriesTask extends Long2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> searchFunction; + public final AtomicReference result; + + public SearchEntriesTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Function, ? extends U> searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Function, ? extends U> searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Long2ObjectConcurrentHashMap.SearchEntriesTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Long2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + + return; + } + } + } + } + } + } + + protected static final class SearchKeysTask extends Long2ObjectConcurrentHashMap.BulkTask { + public final Long2ObjectConcurrentHashMap.LongFunction searchFunction; + public final AtomicReference result; + + public SearchKeysTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.LongFunction searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Long2ObjectConcurrentHashMap.LongFunction searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Long2ObjectConcurrentHashMap.SearchKeysTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Long2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.key)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static final class SearchMappingsTask extends Long2ObjectConcurrentHashMap.BulkTask { + public final Long2ObjectConcurrentHashMap.LongObjFunction searchFunction; + public final AtomicReference result; + + public SearchMappingsTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Long2ObjectConcurrentHashMap.LongObjFunction searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Long2ObjectConcurrentHashMap.LongObjFunction searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Long2ObjectConcurrentHashMap.SearchMappingsTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result) + .fork(); + } + + while (result.get() == null) { + Long2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.key, p.val)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static final class SearchValuesTask extends Long2ObjectConcurrentHashMap.BulkTask { + public final Function searchFunction; + public final AtomicReference result; + + public SearchValuesTask( + Long2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Long2ObjectConcurrentHashMap.Node[] t, + Function searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Function searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Long2ObjectConcurrentHashMap.SearchValuesTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Long2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.val)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static class Segment extends ReentrantLock implements Serializable { + public static final long serialVersionUID = 2249069246763182397L; + public final float loadFactor; + + public Segment(float lf) { + this.loadFactor = lf; + } + } + + protected static final class TableStack { + public int length; + public int index; + public Long2ObjectConcurrentHashMap.Node[] tab; + public Long2ObjectConcurrentHashMap.TableStack next; + + public TableStack() { + } + } + + @FunctionalInterface + public interface ToDoubleLongObjFunction { + double applyAsDouble(long var1, V var3); + } + + @FunctionalInterface + public interface ToIntLongObjFunction { + int applyAsInt(long var1, V var3); + } + + @FunctionalInterface + public interface ToLongFunction { + long applyAsLong(T var1); + } + + @FunctionalInterface + public interface ToLongLongObjFunction { + long applyAsLong(long var1, V var3); + } + + protected static class Traverser { + public Long2ObjectConcurrentHashMap.Node[] tab; + public Long2ObjectConcurrentHashMap.Node next; + public Long2ObjectConcurrentHashMap.TableStack stack; + public Long2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + + public Traverser(Long2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit) { + this.tab = tab; + this.baseSize = size; + this.baseIndex = this.index = index; + this.baseLimit = limit; + this.next = null; + } + + protected final Long2ObjectConcurrentHashMap.Node advance() { + Long2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Long2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Long2ObjectConcurrentHashMap.TreeBin) { + e = ((Long2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Long2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Long2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Long2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + } + + protected static final class TreeBin extends Long2ObjectConcurrentHashMap.Node { + public Long2ObjectConcurrentHashMap.TreeNode root; + public volatile Long2ObjectConcurrentHashMap.TreeNode first; + public volatile Thread waiter; + public volatile int lockState; + public static final int WRITER = 1; + public static final int WAITER = 2; + public static final int READER = 4; + protected static final Unsafe U; + protected static final long LOCKSTATE; + + protected int tieBreakOrder(long a, long b) { + int comp = Long.compare(a, b); + return comp > 0 ? 1 : -1; + } + + public TreeBin(long empty, Long2ObjectConcurrentHashMap.TreeNode b) { + super(empty, -2, empty, null, null); + this.first = b; + Long2ObjectConcurrentHashMap.TreeNode r = null; + Long2ObjectConcurrentHashMap.TreeNode x = b; + + while (x != null) { + Long2ObjectConcurrentHashMap.TreeNode next = (Long2ObjectConcurrentHashMap.TreeNode)x.next; + x.left = x.right = null; + if (r == null) { + x.parent = null; + x.red = false; + r = x; + } else { + long k = x.key; + int h = x.hash; + Class kc = null; + Long2ObjectConcurrentHashMap.TreeNode p = r; + + int dir; + Long2ObjectConcurrentHashMap.TreeNode xp; + do { + long pk = p.key; + int ph = p.hash; + if (p.hash > h) { + dir = -1; + } else if (ph < h) { + dir = 1; + } else if ((dir = Long.compare(k, pk)) == 0) { + dir = this.tieBreakOrder(k, pk); + } + + xp = p; + } while ((p = dir <= 0 ? p.left : p.right) != null); + + x.parent = xp; + if (dir <= 0) { + xp.left = x; + } else { + xp.right = x; + } + + r = this.balanceInsertion(r, x); + } + + x = next; + } + + this.root = r; + + assert this.checkInvariants(this.root); + } + + protected final void lockRoot() { + if (!U.compareAndSwapInt(this, LOCKSTATE, 0, 1)) { + this.contendedLock(); + } + } + + protected final void unlockRoot() { + this.lockState = 0; + } + + protected final void contendedLock() { + boolean waiting = false; + + while (true) { + int s = this.lockState; + if ((this.lockState & -3) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, 1)) { + if (waiting) { + this.waiter = null; + } + + return; + } + } else if ((s & 2) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, s | 2)) { + waiting = true; + this.waiter = Thread.currentThread(); + } + } else if (waiting) { + LockSupport.park(this); + } + } + } + + @Override + protected final Long2ObjectConcurrentHashMap.Node find(int h, long k) { + if (k != this.EMPTY) { + Long2ObjectConcurrentHashMap.Node e = this.first; + + while (e != null) { + int s = this.lockState; + if ((this.lockState & 3) != 0) { + if (e.hash == h) { + long ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + + e = e.next; + } else if (U.compareAndSwapInt(this, LOCKSTATE, s, s + 4)) { + Long2ObjectConcurrentHashMap.TreeNode p; + try { + Long2ObjectConcurrentHashMap.TreeNode r = this.root; + p = this.root == null ? null : r.findTreeNode(h, k, null); + } finally { + if (U.getAndAddInt(this, LOCKSTATE, -4) == 6) { + Thread w = this.waiter; + if (this.waiter != null) { + LockSupport.unpark(w); + } + } + } + + return p; + } + } + } + + return null; + } + + protected final Long2ObjectConcurrentHashMap.TreeNode putTreeVal(int h, long k, V v) { + Class kc = null; + boolean searched = false; + Long2ObjectConcurrentHashMap.TreeNode p = this.root; + + while (true) { + if (p == null) { + this.first = this.root = new Long2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, h, k, v, null, null); + } else { + int ph = p.hash; + int dir; + if (p.hash > h) { + dir = -1; + } else if (ph < h) { + dir = 1; + } else { + long pk = p.key; + if (p.key == k || pk != this.EMPTY && k == pk) { + return p; + } + + if ((dir = Long.compare(k, pk)) == 0) { + if (!searched) { + searched = true; + Long2ObjectConcurrentHashMap.TreeNode ch = p.left; + Long2ObjectConcurrentHashMap.TreeNode q; + if (p.left != null && (q = ch.findTreeNode(h, k, kc)) != null) { + return q; + } + + ch = p.right; + if (p.right != null && (q = ch.findTreeNode(h, k, kc)) != null) { + return q; + } + } + + dir = this.tieBreakOrder(k, pk); + } + } + + Long2ObjectConcurrentHashMap.TreeNode xp = p; + if ((p = dir <= 0 ? p.left : p.right) != null) { + continue; + } + + Long2ObjectConcurrentHashMap.TreeNode f = this.first; + Long2ObjectConcurrentHashMap.TreeNode x; + this.first = x = new Long2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, h, k, v, f, xp); + if (f != null) { + f.prev = x; + } + + if (dir <= 0) { + xp.left = x; + } else { + xp.right = x; + } + + if (!xp.red) { + x.red = true; + } else { + this.lockRoot(); + + try { + this.root = this.balanceInsertion(this.root, x); + } finally { + this.unlockRoot(); + } + } + } + + assert this.checkInvariants(this.root); + + return null; + } + } + + protected final boolean removeTreeNode(Long2ObjectConcurrentHashMap.TreeNode p) { + Long2ObjectConcurrentHashMap.TreeNode next = (Long2ObjectConcurrentHashMap.TreeNode)p.next; + Long2ObjectConcurrentHashMap.TreeNode pred = p.prev; + if (pred == null) { + this.first = next; + } else { + pred.next = next; + } + + if (next != null) { + next.prev = pred; + } + + if (this.first == null) { + this.root = null; + return true; + } else { + Long2ObjectConcurrentHashMap.TreeNode r = this.root; + if (this.root != null && r.right != null) { + Long2ObjectConcurrentHashMap.TreeNode rl = r.left; + if (r.left != null && rl.left != null) { + this.lockRoot(); + + try { + Long2ObjectConcurrentHashMap.TreeNode pl = p.left; + Long2ObjectConcurrentHashMap.TreeNode pr = p.right; + Long2ObjectConcurrentHashMap.TreeNode replacement; + if (pl != null && pr != null) { + Long2ObjectConcurrentHashMap.TreeNode s = pr; + + while (true) { + Long2ObjectConcurrentHashMap.TreeNode sl = s.left; + if (s.left == null) { + boolean c = s.red; + s.red = p.red; + p.red = c; + Long2ObjectConcurrentHashMap.TreeNode sr = s.right; + Long2ObjectConcurrentHashMap.TreeNode pp = p.parent; + if (s == pr) { + p.parent = s; + s.right = p; + } else { + Long2ObjectConcurrentHashMap.TreeNode sp = s.parent; + if ((p.parent = sp) != null) { + if (s == sp.left) { + sp.left = p; + } else { + sp.right = p; + } + } + + if ((s.right = pr) != null) { + pr.parent = s; + } + } + + p.left = null; + if ((p.right = sr) != null) { + sr.parent = p; + } + + if ((s.left = pl) != null) { + pl.parent = s; + } + + if ((s.parent = pp) == null) { + r = s; + } else if (p == pp.left) { + pp.left = s; + } else { + pp.right = s; + } + + if (sr != null) { + replacement = sr; + } else { + replacement = p; + } + break; + } + + s = sl; + } + } else if (pl != null) { + replacement = pl; + } else if (pr != null) { + replacement = pr; + } else { + replacement = p; + } + + if (replacement != p) { + Long2ObjectConcurrentHashMap.TreeNode ppx = replacement.parent = p.parent; + if (ppx == null) { + r = replacement; + } else if (p == ppx.left) { + ppx.left = replacement; + } else { + ppx.right = replacement; + } + + p.left = p.right = p.parent = null; + } + + this.root = p.red ? r : this.balanceDeletion(r, replacement); + if (p == replacement) { + Long2ObjectConcurrentHashMap.TreeNode ppx = p.parent; + if (p.parent != null) { + if (p == ppx.left) { + ppx.left = null; + } else if (p == ppx.right) { + ppx.right = null; + } + + p.parent = null; + } + } + } finally { + this.unlockRoot(); + } + + assert this.checkInvariants(this.root); + + return false; + } + } + + return true; + } + } + + protected Long2ObjectConcurrentHashMap.TreeNode rotateLeft( + Long2ObjectConcurrentHashMap.TreeNode root, Long2ObjectConcurrentHashMap.TreeNode p + ) { + if (p != null) { + Long2ObjectConcurrentHashMap.TreeNode r = p.right; + if (p.right != null) { + Long2ObjectConcurrentHashMap.TreeNode rl; + if ((rl = p.right = r.left) != null) { + rl.parent = p; + } + + Long2ObjectConcurrentHashMap.TreeNode pp; + if ((pp = r.parent = p.parent) == null) { + root = r; + r.red = false; + } else if (pp.left == p) { + pp.left = r; + } else { + pp.right = r; + } + + r.left = p; + p.parent = r; + } + } + + return root; + } + + protected Long2ObjectConcurrentHashMap.TreeNode rotateRight( + Long2ObjectConcurrentHashMap.TreeNode root, Long2ObjectConcurrentHashMap.TreeNode p + ) { + if (p != null) { + Long2ObjectConcurrentHashMap.TreeNode l = p.left; + if (p.left != null) { + Long2ObjectConcurrentHashMap.TreeNode lr; + if ((lr = p.left = l.right) != null) { + lr.parent = p; + } + + Long2ObjectConcurrentHashMap.TreeNode pp; + if ((pp = l.parent = p.parent) == null) { + root = l; + l.red = false; + } else if (pp.right == p) { + pp.right = l; + } else { + pp.left = l; + } + + l.right = p; + p.parent = l; + } + } + + return root; + } + + protected Long2ObjectConcurrentHashMap.TreeNode balanceInsertion( + Long2ObjectConcurrentHashMap.TreeNode root, Long2ObjectConcurrentHashMap.TreeNode x + ) { + x.red = true; + + while (true) { + Long2ObjectConcurrentHashMap.TreeNode xp = x.parent; + if (x.parent == null) { + x.red = false; + return x; + } + + if (!xp.red) { + break; + } + + Long2ObjectConcurrentHashMap.TreeNode xpp = xp.parent; + if (xp.parent == null) { + break; + } + + Long2ObjectConcurrentHashMap.TreeNode xppl = xpp.left; + if (xp == xpp.left) { + Long2ObjectConcurrentHashMap.TreeNode xppr = xpp.right; + if (xpp.right != null && xppr.red) { + xppr.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } else { + if (x == xp.right) { + x = xp; + root = this.rotateLeft(root, xp); + xpp = (xp = xp.parent) == null ? null : xp.parent; + } + + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = this.rotateRight(root, xpp); + } + } + } + } else if (xppl != null && xppl.red) { + xppl.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } else { + if (x == xp.left) { + x = xp; + root = this.rotateRight(root, xp); + xpp = (xp = xp.parent) == null ? null : xp.parent; + } + + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = this.rotateLeft(root, xpp); + } + } + } + } + + return root; + } + + protected Long2ObjectConcurrentHashMap.TreeNode balanceDeletion( + Long2ObjectConcurrentHashMap.TreeNode root, Long2ObjectConcurrentHashMap.TreeNode x + ) { + while (x != null && x != root) { + Long2ObjectConcurrentHashMap.TreeNode xp = x.parent; + if (x.parent == null) { + x.red = false; + return x; + } + + if (x.red) { + x.red = false; + return root; + } + + Long2ObjectConcurrentHashMap.TreeNode xpl = xp.left; + if (xp.left == x) { + Long2ObjectConcurrentHashMap.TreeNode xpr = xp.right; + if (xp.right != null && xpr.red) { + xpr.red = false; + xp.red = true; + root = this.rotateLeft(root, xp); + xp = x.parent; + xpr = x.parent == null ? null : xp.right; + } + + if (xpr == null) { + x = xp; + } else { + Long2ObjectConcurrentHashMap.TreeNode sl = xpr.left; + Long2ObjectConcurrentHashMap.TreeNode sr = xpr.right; + if (sr != null && sr.red || sl != null && sl.red) { + if (sr == null || !sr.red) { + if (sl != null) { + sl.red = false; + } + + xpr.red = true; + root = this.rotateRight(root, xpr); + xp = x.parent; + xpr = x.parent == null ? null : xp.right; + } + + if (xpr != null) { + xpr.red = xp == null ? false : xp.red; + sr = xpr.right; + if (xpr.right != null) { + sr.red = false; + } + } + + if (xp != null) { + xp.red = false; + root = this.rotateLeft(root, xp); + } + + x = root; + } else { + xpr.red = true; + x = xp; + } + } + } else { + if (xpl != null && xpl.red) { + xpl.red = false; + xp.red = true; + root = this.rotateRight(root, xp); + xp = x.parent; + xpl = x.parent == null ? null : xp.left; + } + + if (xpl == null) { + x = xp; + } else { + Long2ObjectConcurrentHashMap.TreeNode sl = xpl.left; + Long2ObjectConcurrentHashMap.TreeNode sr = xpl.right; + if (sl != null && sl.red || sr != null && sr.red) { + if (sl == null || !sl.red) { + if (sr != null) { + sr.red = false; + } + + xpl.red = true; + root = this.rotateLeft(root, xpl); + xp = x.parent; + xpl = x.parent == null ? null : xp.left; + } + + if (xpl != null) { + xpl.red = xp == null ? false : xp.red; + sl = xpl.left; + if (xpl.left != null) { + sl.red = false; + } + } + + if (xp != null) { + xp.red = false; + root = this.rotateRight(root, xp); + } + + x = root; + } else { + xpl.red = true; + x = xp; + } + } + } + } + + return root; + } + + protected boolean checkInvariants(Long2ObjectConcurrentHashMap.TreeNode t) { + Long2ObjectConcurrentHashMap.TreeNode tp = t.parent; + Long2ObjectConcurrentHashMap.TreeNode tl = t.left; + Long2ObjectConcurrentHashMap.TreeNode tr = t.right; + Long2ObjectConcurrentHashMap.TreeNode tb = t.prev; + Long2ObjectConcurrentHashMap.TreeNode tn = (Long2ObjectConcurrentHashMap.TreeNode)t.next; + if (tb != null && tb.next != t) { + return false; + } else if (tn != null && tn.prev != t) { + return false; + } else if (tp != null && t != tp.left && t != tp.right) { + return false; + } else if (tl == null || tl.parent == t && tl.hash <= t.hash) { + if (tr == null || tr.parent == t && tr.hash >= t.hash) { + if (t.red && tl != null && tl.red && tr != null && tr.red) { + return false; + } else { + return tl != null && !this.checkInvariants(tl) ? false : tr == null || this.checkInvariants(tr); + } + } else { + return false; + } + } else { + return false; + } + } + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + U = (Unsafe)f.get(null); + Class k = Long2ObjectConcurrentHashMap.TreeBin.class; + LOCKSTATE = U.objectFieldOffset(k.getDeclaredField("lockState")); + } catch (Exception var2) { + throw new Error(var2); + } + } + } + + protected static final class TreeNode extends Long2ObjectConcurrentHashMap.Node { + public Long2ObjectConcurrentHashMap.TreeNode parent; + public Long2ObjectConcurrentHashMap.TreeNode left; + public Long2ObjectConcurrentHashMap.TreeNode right; + public Long2ObjectConcurrentHashMap.TreeNode prev; + public boolean red; + + public TreeNode(long empty, int hash, long key, V val, Long2ObjectConcurrentHashMap.Node next, Long2ObjectConcurrentHashMap.TreeNode parent) { + super(empty, hash, key, val, next); + this.parent = parent; + } + + @Override + protected Long2ObjectConcurrentHashMap.Node find(int h, long k) { + return this.findTreeNode(h, k, null); + } + + protected final Long2ObjectConcurrentHashMap.TreeNode findTreeNode(int h, long k, Class kc) { + if (k != this.EMPTY) { + Long2ObjectConcurrentHashMap.TreeNode p = this; + + do { + Long2ObjectConcurrentHashMap.TreeNode pl = p.left; + Long2ObjectConcurrentHashMap.TreeNode pr = p.right; + int ph = p.hash; + if (p.hash > h) { + p = pl; + } else if (ph < h) { + p = pr; + } else { + long pk = p.key; + if (p.key == k || pk != this.EMPTY && k == pk) { + return p; + } + + if (pl == null) { + p = pr; + } else if (pr == null) { + p = pl; + } else { + int dir; + if ((dir = Long.compare(k, pk)) != 0) { + p = dir < 0 ? pl : pr; + } else { + Long2ObjectConcurrentHashMap.TreeNode q; + if ((q = pr.findTreeNode(h, k, kc)) != null) { + return q; + } + + p = pl; + } + } + } + } while (p != null); + } + + return null; + } + } + + protected static final class ValueIterator extends Long2ObjectConcurrentHashMap.BaseIterator implements ObjectIterator, Enumeration { + public ValueIterator(Long2ObjectConcurrentHashMap.Node[] tab, int index, int size, int limit, Long2ObjectConcurrentHashMap map) { + super(tab, index, size, limit, map); + } + + @Override + public final V next() { + Long2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + V v = p.val; + this.lastReturned = p; + this.advance(); + return v; + } + } + + @Override + public final V nextElement() { + return this.next(); + } + } + + protected static final class ValueSpliterator extends Long2ObjectConcurrentHashMap.Traverser implements ObjectSpliterator { + public long est; + + public ValueSpliterator(Long2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est) { + super(tab, size, index, limit); + this.est = est; + } + + @Override + public ObjectSpliterator trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Long2ObjectConcurrentHashMap.ValueSpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1); + } + + @Override + public void forEachRemaining(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(p.val); + } + } + } + + @Override + public boolean tryAdvance(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(p.val); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4352; + } + } + + protected static final class ValuesView extends Long2ObjectConcurrentHashMap.CollectionView implements FastCollection, Serializable { + public static final long serialVersionUID = 2249069246763182397L; + + public ValuesView(Long2ObjectConcurrentHashMap map) { + super(map); + } + + @Override + public final boolean contains(Object o) { + return this.map.containsValue(o); + } + + @Override + public final boolean remove(Object o) { + if (o != null) { + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (o.equals(it.next())) { + it.remove(); + return true; + } + } + } + + return false; + } + + @Override + public final ObjectIterator iterator() { + Long2ObjectConcurrentHashMap m = this.map; + Long2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Long2ObjectConcurrentHashMap.ValueIterator<>(t, f, 0, f, m); + } + + @Override + public final boolean add(V e) { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public ObjectSpliterator spliterator() { + Long2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Long2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Long2ObjectConcurrentHashMap.ValueSpliterator<>(t, f, 0, f, n < 0L ? 0L : n); + } + + @Override + public void forEach(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Long2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.val); + } + } + } + } + + @Override + public void forEach( + FastCollection.FastConsumerD9 consumer, + A a, + double d1, + double d2, + double d3, + double d4, + double d5, + double d6, + double d7, + double d8, + double d9, + B b, + C c, + D d + ) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Long2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, a, d1, d2, d3, d4, d5, d6, d7, d8, d9, b, c, d); + } + } + } + } + + @Override + public void forEach( + FastCollection.FastConsumerD6 consumer, A a, double d1, double d2, double d3, double d4, double d5, double d6, B b, C c, D d + ) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Long2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, a, d1, d2, d3, d4, d5, d6, b, c, d); + } + } + } + } + + @Override + public void forEachWithFloat(FastCollection.FastConsumerF consumer, float ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Long2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + + @Override + public void forEachWithInt(FastCollection.FastConsumerI consumer, int ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Long2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + + @Override + public void forEachWithLong(FastCollection.FastConsumerL consumer, long ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Long2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Long2ObjectConcurrentHashMap.Node[] tab = tt; + Long2ObjectConcurrentHashMap.Node next = null; + Long2ObjectConcurrentHashMap.TableStack stack = null; + Long2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Long2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Long2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Long2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Long2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Long2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Long2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Long2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Long2ObjectConcurrentHashMap.TreeBin) { + p = ((Long2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Long2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Long2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + } +} diff --git a/src/com/hypixel/fastutil/longs/Long2ObjectOperator.java b/src/com/hypixel/fastutil/longs/Long2ObjectOperator.java new file mode 100644 index 0000000..7ed581d --- /dev/null +++ b/src/com/hypixel/fastutil/longs/Long2ObjectOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.longs; + +@FunctionalInterface +public interface Long2ObjectOperator { + V apply(long var1, V var3); +} diff --git a/src/com/hypixel/fastutil/longs/Long2ShortOperator.java b/src/com/hypixel/fastutil/longs/Long2ShortOperator.java new file mode 100644 index 0000000..6f4490e --- /dev/null +++ b/src/com/hypixel/fastutil/longs/Long2ShortOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.longs; + +@FunctionalInterface +public interface Long2ShortOperator { + short apply(long var1, short var3); +} diff --git a/src/com/hypixel/fastutil/shorts/Short2ByteOperator.java b/src/com/hypixel/fastutil/shorts/Short2ByteOperator.java new file mode 100644 index 0000000..afea77e --- /dev/null +++ b/src/com/hypixel/fastutil/shorts/Short2ByteOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.shorts; + +@FunctionalInterface +public interface Short2ByteOperator { + byte apply(short var1, byte var2); +} diff --git a/src/com/hypixel/fastutil/shorts/Short2CharOperator.java b/src/com/hypixel/fastutil/shorts/Short2CharOperator.java new file mode 100644 index 0000000..9d2608d --- /dev/null +++ b/src/com/hypixel/fastutil/shorts/Short2CharOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.shorts; + +@FunctionalInterface +public interface Short2CharOperator { + char apply(short var1, char var2); +} diff --git a/src/com/hypixel/fastutil/shorts/Short2DoubleOperator.java b/src/com/hypixel/fastutil/shorts/Short2DoubleOperator.java new file mode 100644 index 0000000..238e797 --- /dev/null +++ b/src/com/hypixel/fastutil/shorts/Short2DoubleOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.shorts; + +@FunctionalInterface +public interface Short2DoubleOperator { + double apply(short var1, double var2); +} diff --git a/src/com/hypixel/fastutil/shorts/Short2FloatOperator.java b/src/com/hypixel/fastutil/shorts/Short2FloatOperator.java new file mode 100644 index 0000000..98cd39f --- /dev/null +++ b/src/com/hypixel/fastutil/shorts/Short2FloatOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.shorts; + +@FunctionalInterface +public interface Short2FloatOperator { + float apply(short var1, float var2); +} diff --git a/src/com/hypixel/fastutil/shorts/Short2IntOperator.java b/src/com/hypixel/fastutil/shorts/Short2IntOperator.java new file mode 100644 index 0000000..f4c304a --- /dev/null +++ b/src/com/hypixel/fastutil/shorts/Short2IntOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.shorts; + +@FunctionalInterface +public interface Short2IntOperator { + int apply(short var1, int var2); +} diff --git a/src/com/hypixel/fastutil/shorts/Short2LongOperator.java b/src/com/hypixel/fastutil/shorts/Short2LongOperator.java new file mode 100644 index 0000000..f5e7479 --- /dev/null +++ b/src/com/hypixel/fastutil/shorts/Short2LongOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.shorts; + +@FunctionalInterface +public interface Short2LongOperator { + long apply(short var1, long var2); +} diff --git a/src/com/hypixel/fastutil/shorts/Short2ObjectConcurrentHashMap.java b/src/com/hypixel/fastutil/shorts/Short2ObjectConcurrentHashMap.java new file mode 100644 index 0000000..14b0fcd --- /dev/null +++ b/src/com/hypixel/fastutil/shorts/Short2ObjectConcurrentHashMap.java @@ -0,0 +1,10267 @@ +package com.hypixel.fastutil.shorts; + +import com.hypixel.fastutil.FastCollection; +import com.hypixel.fastutil.util.SneakyThrow; +import com.hypixel.fastutil.util.TLRUtil; +import it.unimi.dsi.fastutil.objects.ObjectCollection; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import it.unimi.dsi.fastutil.objects.ObjectSpliterator; +import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import it.unimi.dsi.fastutil.shorts.ShortCollection; +import it.unimi.dsi.fastutil.shorts.ShortConsumer; +import it.unimi.dsi.fastutil.shorts.ShortIterator; +import it.unimi.dsi.fastutil.shorts.ShortSet; +import it.unimi.dsi.fastutil.shorts.ShortSpliterator; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.CountedCompleter; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.DoubleBinaryOperator; +import java.util.function.Function; +import java.util.function.IntBinaryOperator; +import java.util.function.LongBinaryOperator; +import java.util.function.Predicate; +import java.util.function.ToDoubleFunction; +import java.util.function.ToIntFunction; +import java.util.function.ToLongFunction; +import sun.misc.Unsafe; + +public class Short2ObjectConcurrentHashMap { + protected static final long serialVersionUID = 7249069246763182397L; + protected static final int MAXIMUM_CAPACITY = 1073741824; + protected static final int DEFAULT_CAPACITY = 16; + protected static final int MAX_ARRAY_SIZE = 2147483639; + protected static final int DEFAULT_CONCURRENCY_LEVEL = 16; + protected static final float LOAD_FACTOR = 0.75F; + protected static final int TREEIFY_THRESHOLD = 8; + protected static final int UNTREEIFY_THRESHOLD = 6; + protected static final int MIN_TREEIFY_CAPACITY = 64; + protected static final int MIN_TRANSFER_STRIDE = 16; + protected static int RESIZE_STAMP_BITS = 16; + protected static final int MAX_RESIZERS = (1 << 32 - RESIZE_STAMP_BITS) - 1; + protected static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS; + protected static final int MOVED = -1; + protected static final int TREEBIN = -2; + protected static final int RESERVED = -3; + protected static final int HASH_BITS = Integer.MAX_VALUE; + protected static final int NCPU = Runtime.getRuntime().availableProcessors(); + protected transient volatile Short2ObjectConcurrentHashMap.Node[] table; + protected transient volatile Short2ObjectConcurrentHashMap.Node[] nextTable; + protected transient volatile long baseCount; + protected transient volatile int sizeCtl; + protected transient volatile int transferIndex; + protected transient volatile int cellsBusy; + protected transient volatile Short2ObjectConcurrentHashMap.CounterCell[] counterCells; + protected transient Short2ObjectConcurrentHashMap.KeySetView keySet; + protected transient Short2ObjectConcurrentHashMap.ValuesView values; + protected transient Short2ObjectConcurrentHashMap.EntrySetView entrySet; + protected final short EMPTY; + protected static final Unsafe U; + protected static final long SIZECTL; + protected static final long TRANSFERINDEX; + protected static final long BASECOUNT; + protected static final long CELLSBUSY; + protected static final long CELLVALUE; + protected static final long ABASE; + protected static final int ASHIFT; + + protected static final int spread(int h) { + return (h ^ h >>> 16) & 2147483647; + } + + protected static final int tableSizeFor(int c) { + int n = c - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return n < 0 ? 1 : (n >= 1073741824 ? 1073741824 : n + 1); + } + + protected static final Short2ObjectConcurrentHashMap.Node tabAt(Short2ObjectConcurrentHashMap.Node[] tab, int i) { + return (Short2ObjectConcurrentHashMap.Node)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); + } + + protected static final boolean casTabAt( + Short2ObjectConcurrentHashMap.Node[] tab, int i, Short2ObjectConcurrentHashMap.Node c, Short2ObjectConcurrentHashMap.Node v + ) { + return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v); + } + + protected static final void setTabAt(Short2ObjectConcurrentHashMap.Node[] tab, int i, Short2ObjectConcurrentHashMap.Node v) { + U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v); + } + + public Short2ObjectConcurrentHashMap() { + this.EMPTY = -1; + } + + public Short2ObjectConcurrentHashMap(boolean nonce, short emptyValue) { + this.EMPTY = emptyValue; + } + + public Short2ObjectConcurrentHashMap(int initialCapacity) { + this(initialCapacity, true, (short)-1); + } + + public Short2ObjectConcurrentHashMap(int initialCapacity, boolean nonce, short emptyValue) { + if (initialCapacity < 0) { + throw new IllegalArgumentException(); + } else { + int cap = initialCapacity >= 536870912 ? 1073741824 : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1); + this.sizeCtl = cap; + this.EMPTY = emptyValue; + } + } + + public Short2ObjectConcurrentHashMap(Map m, short emptyValue) { + this.sizeCtl = 16; + this.EMPTY = emptyValue; + this.putAll(m); + } + + public Short2ObjectConcurrentHashMap(Short2ObjectConcurrentHashMap m) { + this.sizeCtl = 16; + this.EMPTY = m.EMPTY; + this.putAll(m); + } + + public Short2ObjectConcurrentHashMap(Short2ObjectMap m) { + this.sizeCtl = 16; + this.EMPTY = -1; + this.putAll(m); + } + + public Short2ObjectConcurrentHashMap(Short2ObjectMap m, short emptyValue) { + this.sizeCtl = 16; + this.EMPTY = emptyValue; + this.putAll(m); + } + + public Short2ObjectConcurrentHashMap(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, 1, (short)-1); + } + + public Short2ObjectConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel, short emptyValue) { + if (loadFactor > 0.0F && initialCapacity >= 0 && concurrencyLevel > 0) { + if (initialCapacity < concurrencyLevel) { + initialCapacity = concurrencyLevel; + } + + long size = (long)(1.0 + (float)initialCapacity / loadFactor); + int cap = size >= 1073741824L ? 1073741824 : tableSizeFor((int)size); + this.sizeCtl = cap; + this.EMPTY = emptyValue; + } else { + throw new IllegalArgumentException(); + } + } + + public int size() { + long n = this.sumCount(); + return n < 0L ? 0 : (n > 2147483647L ? Integer.MAX_VALUE : (int)n); + } + + public boolean isEmpty() { + return this.sumCount() <= 0L; + } + + public V get(short key) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + int h = spread(Short.hashCode(key)); + Short2ObjectConcurrentHashMap.Node[] tab = this.table; + Short2ObjectConcurrentHashMap.Node e; + int n; + if (this.table != null && (n = tab.length) > 0 && (e = tabAt(tab, n - 1 & h)) != null) { + int eh = e.hash; + if (e.hash == h) { + short ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + return e.val; + } + } else if (eh < 0) { + Short2ObjectConcurrentHashMap.Node p; + return (p = e.find(h, key)) != null ? p.val : null; + } + + while ((e = e.next) != null) { + if (e.hash == h) { + short ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + return e.val; + } + } + } + } + + return null; + } + } + + public boolean containsKey(short key) { + return this.get(key) != null; + } + + public boolean containsValue(Object value) { + if (value == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label85: + while (true) { + if (p != null) { + next = p; + break; + } + + if (baseIndex < baseLimit) { + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab != null && (n = tab.length) > index && index >= 0) { + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + continue; + } + + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + continue label85; + } + } + } + + next = null; + break; + } + + if (p == null) { + break; + } + + V v = p.val; + if (p.val == value || v != null && value.equals(v)) { + return true; + } + } + } + + return false; + } + } + + public V put(short key, V value) { + return this.putVal(key, value, false); + } + + protected final V putVal(short key, V value, boolean onlyIfAbsent) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value == null) { + throw new NullPointerException(); + } else { + int hash = spread(Short.hashCode(key)); + int binCount = 0; + Short2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Short2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & hash)) == null) { + if (casTabAt(tab, i, null, new Short2ObjectConcurrentHashMap.Node<>(this.EMPTY, hash, key, value, null))) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + V oldVal = null; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Short2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Short2ObjectConcurrentHashMap.Node p; + if ((p = ((Short2ObjectConcurrentHashMap.TreeBin)f).putTreeVal(hash, key, value)) != null) { + oldVal = p.val; + if (!onlyIfAbsent) { + p.val = value; + } + } + } + } else { + binCount = 1; + Short2ObjectConcurrentHashMap.Node e = f; + + while (true) { + if (e.hash == hash) { + short ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + oldVal = e.val; + if (!onlyIfAbsent) { + e.val = value; + } + break; + } + } + + Short2ObjectConcurrentHashMap.Node pred = e; + if ((e = e.next) == null) { + pred.next = new Short2ObjectConcurrentHashMap.Node<>(this.EMPTY, hash, key, value, null); + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + + if (oldVal != null) { + return oldVal; + } + break; + } + } + } + } + + this.addCount(1L, binCount); + return null; + } + } + + public void putAll(Map m) { + this.tryPresize(m.size()); + + for (Map.Entry e : m.entrySet()) { + this.putVal(e.getKey(), (V)e.getValue(), false); + } + } + + public void putAll(Short2ObjectConcurrentHashMap m) { + this.tryPresize(m.size()); + + for (Short2ObjectMap.Entry e : m.short2ObjectEntrySet()) { + this.putVal(e.getShortKey(), (V)e.getValue(), false); + } + } + + public void putAll(Short2ObjectMap m) { + this.tryPresize(m.size()); + + for (Short2ObjectMap.Entry next : m.short2ObjectEntrySet()) { + this.putVal(next.getShortKey(), (V)next.getValue(), false); + } + } + + public V remove(short key) { + return this.replaceNode(key, null, null); + } + + @Deprecated + public V remove(Short key) { + return this.replaceNode(key, null, null); + } + + @Deprecated + public V remove(Object key) { + return this.remove((Short)key); + } + + protected final V replaceNode(short key, V value, Object cv) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + int hash = spread(Short.hashCode(key)); + Short2ObjectConcurrentHashMap.Node[] tab = this.table; + + Short2ObjectConcurrentHashMap.Node f; + int n; + int i; + while (tab != null && (n = tab.length) != 0 && (f = tabAt(tab, i = n - 1 & hash)) != null) { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + V oldVal = null; + boolean validated = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Short2ObjectConcurrentHashMap.TreeBin) { + validated = true; + Short2ObjectConcurrentHashMap.TreeBin t = (Short2ObjectConcurrentHashMap.TreeBin)f; + Short2ObjectConcurrentHashMap.TreeNode r = t.root; + Short2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(hash, key, null)) != null) { + V pv = p.val; + if (cv == null || cv == pv || pv != null && cv.equals(pv)) { + oldVal = pv; + if (value != null) { + p.val = value; + } else if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } + } else { + validated = true; + Short2ObjectConcurrentHashMap.Node e = f; + Short2ObjectConcurrentHashMap.Node pred = null; + + do { + if (e.hash == hash) { + short ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + V ev = e.val; + if (cv == null || cv == ev || ev != null && cv.equals(ev)) { + oldVal = ev; + if (value != null) { + e.val = value; + } else if (pred != null) { + pred.next = e.next; + } else { + setTabAt(tab, i, e.next); + } + } + break; + } + } + + pred = e; + } while ((e = e.next) != null); + } + } + } + + if (validated) { + if (oldVal != null) { + if (value == null) { + this.addCount(-1L, -1); + } + + return oldVal; + } + break; + } + } + } + + return null; + } + } + + public void clear() { + long delta = 0L; + int i = 0; + Short2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (tab != null && i < tab.length) { + Short2ObjectConcurrentHashMap.Node f = tabAt(tab, i); + if (f == null) { + i++; + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + i = 0; + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + for (Short2ObjectConcurrentHashMap.Node p = (Short2ObjectConcurrentHashMap.Node)(fh >= 0 + ? f + : (f instanceof Short2ObjectConcurrentHashMap.TreeBin ? ((Short2ObjectConcurrentHashMap.TreeBin)f).first : null)); + p != null; + p = p.next + ) { + delta--; + } + + setTabAt(tab, i++, null); + } + } + } + } + } + + if (delta != 0L) { + this.addCount(delta, -1); + } + } + + public Short2ObjectConcurrentHashMap.KeySetView keySet() { + Short2ObjectConcurrentHashMap.KeySetView ks = this.keySet; + return this.keySet != null ? ks : (this.keySet = this.buildKeySetView()); + } + + protected Short2ObjectConcurrentHashMap.KeySetView buildKeySetView() { + return new Short2ObjectConcurrentHashMap.KeySetView<>(this, null); + } + + public FastCollection values() { + Short2ObjectConcurrentHashMap.ValuesView vs = this.values; + return this.values != null ? vs : (this.values = this.buildValuesView()); + } + + protected Short2ObjectConcurrentHashMap.ValuesView buildValuesView() { + return new Short2ObjectConcurrentHashMap.ValuesView<>(this); + } + + public ObjectSet> short2ObjectEntrySet() { + Short2ObjectConcurrentHashMap.EntrySetView es = this.entrySet; + return this.entrySet != null ? es : (this.entrySet = this.buildEntrySetView()); + } + + @Deprecated + public ObjectSet> entrySet() { + return this.short2ObjectEntrySet(); + } + + protected Short2ObjectConcurrentHashMap.EntrySetView buildEntrySetView() { + return new Short2ObjectConcurrentHashMap.EntrySetView<>(this); + } + + @Override + public int hashCode() { + int h = 0; + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label75: { + while (true) { + if (p != null) { + next = p; + break label75; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + h += Short.hashCode(p.key) ^ p.val.hashCode(); + } + } + + return h; + } + + @Override + public String toString() { + Short2ObjectConcurrentHashMap.Node[] t = this.table; + int f = this.table == null ? 0 : t.length; + Short2ObjectConcurrentHashMap.Traverser it = new Short2ObjectConcurrentHashMap.Traverser<>(t, f, 0, f); + StringBuilder sb = new StringBuilder(); + sb.append('{'); + Short2ObjectConcurrentHashMap.Node p; + if ((p = it.advance()) != null) { + while (true) { + short k = p.key; + V v = p.val; + sb.append((int)k); + sb.append('='); + sb.append(v == this ? "(this Map)" : v); + if ((p = it.advance()) == null) { + break; + } + + sb.append(',').append(' '); + } + } + + return sb.append('}').toString(); + } + + @Override + public boolean equals(Object o) { + if (o != this) { + if (!(o instanceof Short2ObjectConcurrentHashMap m)) { + return false; + } + + Short2ObjectConcurrentHashMap.Node[] t = this.table; + int f = this.table == null ? 0 : t.length; + Short2ObjectConcurrentHashMap.Traverser it = new Short2ObjectConcurrentHashMap.Traverser<>(t, f, 0, f); + + Short2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + V val = p.val; + Object v = m.get(p.key); + if (v == null || v != val && !v.equals(val)) { + return false; + } + } + + for (Short2ObjectMap.Entry e : m.short2ObjectEntrySet()) { + Object mv; + Object v; + short mk; + if ((mk = e.getShortKey()) == m.EMPTY || (mv = e.getValue()) == null || (v = this.get(mk)) == null || mv != v && !mv.equals(v)) { + return false; + } + } + } + + return true; + } + + public V putIfAbsent(short key, V value) { + return this.putVal(key, value, true); + } + + public boolean remove(short key, Object value) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else { + return value != null && this.replaceNode(key, null, value) != null; + } + } + + public boolean replace(short key, V oldValue, V newValue) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (oldValue != null && newValue != null) { + return this.replaceNode(key, newValue, oldValue) != null; + } else { + throw new NullPointerException(); + } + } + + public V replace(short key, V value) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value == null) { + throw new NullPointerException(); + } else { + return this.replaceNode(key, value, null); + } + } + + public V getOrDefault(short key, V defaultValue) { + V v; + return (v = this.get(key)) == null ? defaultValue : v; + } + + public int forEach(Short2ObjectConcurrentHashMap.ShortObjConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val); + count++; + } + } + + return count; + } + } + + public int forEach(Short2ObjectConcurrentHashMap.ShortBiObjConsumer action, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, x); + count++; + } + } + + return count; + } + } + + public int forEach(Short2ObjectConcurrentHashMap.ShortTriObjConsumer action, X x, Y y) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, x, y); + count++; + } + } + + return count; + } + } + + public int forEachWithByte(Short2ObjectConcurrentHashMap.ShortObjByteConsumer action, byte ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithShort(Short2ObjectConcurrentHashMap.ShortObjShortConsumer action, short ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithInt(Short2ObjectConcurrentHashMap.ShortObjIntConsumer action, int ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithLong(Short2ObjectConcurrentHashMap.ShortObjLongConsumer action, long ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithFloat(Short2ObjectConcurrentHashMap.ShortObjFloatConsumer action, float ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithDouble(Short2ObjectConcurrentHashMap.ShortObjDoubleConsumer action, double ii) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii); + count++; + } + } + + return count; + } + } + + public int forEachWithByte(Short2ObjectConcurrentHashMap.ShortBiObjByteConsumer action, byte ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithShort(Short2ObjectConcurrentHashMap.ShortBiObjShortConsumer action, short ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithInt(Short2ObjectConcurrentHashMap.ShortBiObjIntConsumer action, int ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithLong(Short2ObjectConcurrentHashMap.ShortBiObjLongConsumer action, long ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithFloat(Short2ObjectConcurrentHashMap.ShortBiObjFloatConsumer action, float ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public int forEachWithDouble(Short2ObjectConcurrentHashMap.ShortBiObjDoubleConsumer action, double ii, X x) { + if (action == null) { + throw new NullPointerException(); + } else { + int count = 0; + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.key, p.val, ii, x); + count++; + } + } + + return count; + } + } + + public void replaceAll(Short2ObjectOperator function) { + if (function == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label86: { + while (true) { + if (p != null) { + next = p; + break label86; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + V oldValue = p.val; + short key = p.key; + + V newValue; + do { + newValue = function.apply(key, oldValue); + if (newValue == null) { + throw new NullPointerException(); + } + } while (this.replaceNode(key, newValue, oldValue) == null && (oldValue = this.get(key)) != null); + } + } + } + } + + public V computeIfAbsent(short key, Short2ObjectConcurrentHashMap.ShortFunction mappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (mappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Short.hashCode(key)); + V val = null; + int binCount = 0; + Short2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Short2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + Short2ObjectConcurrentHashMap.Node r = new Short2ObjectConcurrentHashMap.ReservationNode<>(this.EMPTY); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Short2ObjectConcurrentHashMap.Node node = null; + + try { + if ((val = (V)mappingFunction.apply(key)) != null) { + node = new Short2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + } finally { + setTabAt(tab, i, node); + } + } + } + + if (binCount != 0) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + boolean added = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Short2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Short2ObjectConcurrentHashMap.TreeBin t = (Short2ObjectConcurrentHashMap.TreeBin)f; + Short2ObjectConcurrentHashMap.TreeNode r = t.root; + Short2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(h, key, null)) != null) { + val = p.val; + } else if ((val = (V)mappingFunction.apply(key)) != null) { + added = true; + t.putTreeVal(h, key, val); + } + } + } else { + binCount = 1; + Short2ObjectConcurrentHashMap.Node e = f; + + while (true) { + if (e.hash == h) { + short ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = e.val; + break; + } + } + + Short2ObjectConcurrentHashMap.Node pred = e; + if ((e = e.next) == null) { + if ((val = (V)mappingFunction.apply(key)) != null) { + added = true; + pred.next = new Short2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + + if (!added) { + return val; + } + break; + } + } + } + } + + if (val != null) { + this.addCount(1L, binCount); + } + + return val; + } + } + + public V computeIfPresent(short key, Short2ObjectConcurrentHashMap.ShortObjFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (remappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Short.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Short2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Short2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + break; + } + + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Short2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Short2ObjectConcurrentHashMap.TreeBin t = (Short2ObjectConcurrentHashMap.TreeBin)f; + Short2ObjectConcurrentHashMap.TreeNode r = t.root; + Short2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null && (p = r.findTreeNode(h, key, null)) != null) { + val = (V)remappingFunction.apply(key, p.val); + if (val != null) { + p.val = val; + } else { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } + } else { + binCount = 1; + Short2ObjectConcurrentHashMap.Node e = f; + Short2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + short ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(key, e.val); + if (val != null) { + e.val = val; + } else { + delta = -1; + Short2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + break; + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } + } + + public V compute(short key, Short2ObjectConcurrentHashMap.ShortObjFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (remappingFunction == null) { + throw new NullPointerException(); + } else { + int h = spread(Short.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Short2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Short2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + Short2ObjectConcurrentHashMap.Node r = new Short2ObjectConcurrentHashMap.ReservationNode<>(this.EMPTY); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Short2ObjectConcurrentHashMap.Node node = null; + + try { + if ((val = (V)remappingFunction.apply(key, null)) != null) { + delta = 1; + node = new Short2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + } finally { + setTabAt(tab, i, node); + } + } + } + + if (binCount != 0) { + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Short2ObjectConcurrentHashMap.TreeBin) { + binCount = 1; + Short2ObjectConcurrentHashMap.TreeBin t = (Short2ObjectConcurrentHashMap.TreeBin)f; + Short2ObjectConcurrentHashMap.TreeNode r = t.root; + Short2ObjectConcurrentHashMap.TreeNode p; + if (t.root != null) { + p = r.findTreeNode(h, key, null); + } else { + p = null; + } + + V pv = p == null ? null : p.val; + val = (V)remappingFunction.apply(key, pv); + if (val != null) { + if (p != null) { + p.val = val; + } else { + delta = 1; + t.putTreeVal(h, key, val); + } + } else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } else { + binCount = 1; + Short2ObjectConcurrentHashMap.Node e = f; + Short2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + short ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(key, e.val); + if (val != null) { + e.val = val; + } else { + delta = -1; + Short2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + val = (V)remappingFunction.apply(key, null); + if (val != null) { + delta = 1; + pred.next = new Short2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, val, null); + } + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + break; + } + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } + } + + public V merge(short key, V value, BiFunction remappingFunction) { + if (key == this.EMPTY) { + throw new IllegalArgumentException("Key is EMPTY: " + this.EMPTY); + } else if (value != null && remappingFunction != null) { + int h = spread(Short.hashCode(key)); + V val = null; + int delta = 0; + int binCount = 0; + Short2ObjectConcurrentHashMap.Node[] tab = this.table; + + while (true) { + int n; + while (tab == null || (n = tab.length) == 0) { + tab = this.initTable(); + } + + Short2ObjectConcurrentHashMap.Node f; + int i; + if ((f = tabAt(tab, i = n - 1 & h)) == null) { + if (casTabAt(tab, i, null, new Short2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, value, null))) { + delta = 1; + val = value; + break; + } + } else { + int fh = f.hash; + if (f.hash == -1) { + tab = this.helpTransfer(tab, f); + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh < 0) { + if (f instanceof Short2ObjectConcurrentHashMap.TreeBin) { + binCount = 2; + Short2ObjectConcurrentHashMap.TreeBin t = (Short2ObjectConcurrentHashMap.TreeBin)f; + Short2ObjectConcurrentHashMap.TreeNode r = t.root; + Short2ObjectConcurrentHashMap.TreeNode p = r == null ? null : r.findTreeNode(h, key, null); + val = p == null ? value : remappingFunction.apply(p.val, value); + if (val != null) { + if (p != null) { + p.val = val; + } else { + delta = 1; + t.putTreeVal(h, key, val); + } + } else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, this.untreeify(t.first)); + } + } + } + } else { + binCount = 1; + Short2ObjectConcurrentHashMap.Node e = f; + Short2ObjectConcurrentHashMap.Node pred = null; + + while (true) { + if (e.hash == h) { + short ek = e.key; + if (e.key == key || ek != this.EMPTY && key == ek) { + val = (V)remappingFunction.apply(e.val, value); + if (val != null) { + e.val = val; + } else { + delta = -1; + Short2ObjectConcurrentHashMap.Node en = e.next; + if (pred != null) { + pred.next = en; + } else { + setTabAt(tab, i, en); + } + } + break; + } + } + + pred = e; + if ((e = e.next) == null) { + delta = 1; + val = value; + pred.next = new Short2ObjectConcurrentHashMap.Node<>(this.EMPTY, h, key, value, null); + break; + } + + binCount++; + } + } + } + } + + if (binCount != 0) { + if (binCount >= 8) { + this.treeifyBin(tab, i); + } + break; + } + } + } + } + + if (delta != 0) { + this.addCount(delta, binCount); + } + + return val; + } else { + throw new NullPointerException(); + } + } + + public long mappingCount() { + long n = this.sumCount(); + return n < 0L ? 0L : n; + } + + public static ShortSet newKeySet() { + return new Short2ObjectConcurrentHashMap.KeySetView<>(new Short2ObjectConcurrentHashMap<>(), Boolean.TRUE); + } + + public static Short2ObjectConcurrentHashMap.KeySetView newKeySet(int initialCapacity) { + return new Short2ObjectConcurrentHashMap.KeySetView<>(new Short2ObjectConcurrentHashMap<>(initialCapacity), Boolean.TRUE); + } + + public Short2ObjectConcurrentHashMap.KeySetView keySet(V mappedValue) { + if (mappedValue == null) { + throw new NullPointerException(); + } else { + return new Short2ObjectConcurrentHashMap.KeySetView<>(this, mappedValue); + } + } + + protected static final int resizeStamp(int n) { + return Integer.numberOfLeadingZeros(n) | 1 << RESIZE_STAMP_BITS - 1; + } + + protected final Short2ObjectConcurrentHashMap.Node[] initTable() { + Short2ObjectConcurrentHashMap.Node[] tab; + while (true) { + tab = this.table; + if (this.table != null && tab.length != 0) { + break; + } + + int sc = this.sizeCtl; + if (this.sizeCtl < 0) { + Thread.yield(); + } else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + tab = this.table; + if (this.table == null || tab.length == 0) { + int n = sc > 0 ? sc : 16; + Short2ObjectConcurrentHashMap.Node[] nt = new Short2ObjectConcurrentHashMap.Node[n]; + tab = nt; + this.table = nt; + sc = n - (n >>> 2); + } + break; + } finally { + this.sizeCtl = sc; + } + } + } + + return tab; + } + + protected final void addCount(long x, int check) { + boolean uncontended; + label77: { + long s; + label74: { + Short2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + if (this.counterCells == null) { + long b = this.baseCount; + if (U.compareAndSwapLong(this, BASECOUNT, this.baseCount, s = b + x)) { + break label74; + } + } + + uncontended = (boolean)1; + Short2ObjectConcurrentHashMap.CounterCell a; + int m; + if (as == null || (m = as.length - 1) < 0 || (a = as[TLRUtil.getProbe() & m]) == null) { + break label77; + } + + long v = a.value; + if (!(uncontended = U.compareAndSwapLong(a, CELLVALUE, a.value, v + x))) { + break label77; + } + + if (check <= 1) { + return; + } + + s = this.sumCount(); + } + + if (check >= 0) { + while (true) { + int sc = this.sizeCtl; + if (s < this.sizeCtl) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] tab = this.table; + int n; + if (this.table == null || (n = tab.length) >= 1073741824) { + break; + } + + uncontended = (boolean)resizeStamp(n); + if (sc < 0) { + if (sc >>> RESIZE_STAMP_SHIFT != uncontended || sc == uncontended + 1 || sc == uncontended + MAX_RESIZERS) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] nt = this.nextTable; + if (this.nextTable == null || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nt); + } + } else if (U.compareAndSwapInt(this, SIZECTL, sc, (uncontended << RESIZE_STAMP_SHIFT) + 2)) { + this.transfer(tab, null); + } + + s = this.sumCount(); + } + } + + return; + } + + this.fullAddCount(x, uncontended); + } + + protected final Short2ObjectConcurrentHashMap.Node[] helpTransfer(Short2ObjectConcurrentHashMap.Node[] tab, Short2ObjectConcurrentHashMap.Node f) { + if (tab != null && f instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + Short2ObjectConcurrentHashMap.Node[] nextTab = ((Short2ObjectConcurrentHashMap.ForwardingNode)f).nextTable; + if (((Short2ObjectConcurrentHashMap.ForwardingNode)f).nextTable != null) { + int rs = resizeStamp(tab.length); + + while (nextTab == this.nextTable && this.table == tab) { + int sc = this.sizeCtl; + if (this.sizeCtl >= 0 || sc >>> RESIZE_STAMP_SHIFT != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nextTab); + break; + } + } + + return nextTab; + } + } + + return this.table; + } + + protected final void tryPresize(int size) { + int c = size >= 536870912 ? 1073741824 : tableSizeFor(size + (size >>> 1) + 1); + + while (true) { + int sc = this.sizeCtl; + if (this.sizeCtl < 0) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] tab = this.table; + int n; + if (tab != null && (n = tab.length) != 0) { + if (c <= sc || n >= 1073741824) { + break; + } + + if (tab == this.table) { + int rs = resizeStamp(n); + if (sc < 0) { + if (sc >>> RESIZE_STAMP_SHIFT != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] nt = this.nextTable; + if (this.nextTable == null || this.transferIndex <= 0) { + break; + } + + if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { + this.transfer(tab, nt); + } + } else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) { + this.transfer(tab, null); + } + } + } else { + n = sc > c ? sc : c; + if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + if (this.table == tab) { + Short2ObjectConcurrentHashMap.Node[] ntx = new Short2ObjectConcurrentHashMap.Node[n]; + this.table = ntx; + sc = n - (n >>> 2); + } + } finally { + this.sizeCtl = sc; + } + } + } + } + } + + protected final void transfer(Short2ObjectConcurrentHashMap.Node[] tab, Short2ObjectConcurrentHashMap.Node[] nextTab) { + int n = tab.length; + int stride; + if ((stride = NCPU > 1 ? (n >>> 3) / NCPU : n) < 16) { + stride = 16; + } + + if (nextTab == null) { + try { + Short2ObjectConcurrentHashMap.Node[] nt = new Short2ObjectConcurrentHashMap.Node[n << 1]; + nextTab = nt; + } catch (Throwable var27) { + this.sizeCtl = Integer.MAX_VALUE; + return; + } + + this.nextTable = nextTab; + this.transferIndex = n; + } + + int nextn = nextTab.length; + Short2ObjectConcurrentHashMap.ForwardingNode fwd = new Short2ObjectConcurrentHashMap.ForwardingNode<>(this.EMPTY, nextTab); + boolean advance = true; + boolean finishing = false; + int i = 0; + int bound = 0; + + while (true) { + while (!advance) { + if (i >= 0 && i < n && i + n < nextn) { + Short2ObjectConcurrentHashMap.Node f; + if ((f = tabAt(tab, i)) == null) { + advance = casTabAt(tab, i, null, fwd); + } else { + int fh = f.hash; + if (f.hash == -1) { + advance = true; + } else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + int runBit = fh & n; + Short2ObjectConcurrentHashMap.Node lastRun = f; + + for (Short2ObjectConcurrentHashMap.Node p = f.next; p != null; p = p.next) { + int b = p.hash & n; + if (b != runBit) { + runBit = b; + lastRun = p; + } + } + + Short2ObjectConcurrentHashMap.Node ln; + Short2ObjectConcurrentHashMap.Node hn; + if (runBit == 0) { + ln = lastRun; + hn = null; + } else { + hn = lastRun; + ln = null; + } + + for (Short2ObjectConcurrentHashMap.Node px = f; px != lastRun; px = px.next) { + int ph = px.hash; + short pk = px.key; + V pv = px.val; + if ((ph & n) == 0) { + ln = new Short2ObjectConcurrentHashMap.Node<>(this.EMPTY, ph, pk, pv, ln); + } else { + hn = new Short2ObjectConcurrentHashMap.Node<>(this.EMPTY, ph, pk, pv, hn); + } + } + + setTabAt(nextTab, i, ln); + setTabAt(nextTab, i + n, hn); + setTabAt(tab, i, fwd); + advance = true; + } else if (f instanceof Short2ObjectConcurrentHashMap.TreeBin t) { + Short2ObjectConcurrentHashMap.TreeNode lo = null; + Short2ObjectConcurrentHashMap.TreeNode loTail = null; + Short2ObjectConcurrentHashMap.TreeNode hi = null; + Short2ObjectConcurrentHashMap.TreeNode hiTail = null; + int lc = 0; + int hc = 0; + + for (Short2ObjectConcurrentHashMap.Node e = t.first; e != null; e = e.next) { + int h = e.hash; + Short2ObjectConcurrentHashMap.TreeNode pxx = new Short2ObjectConcurrentHashMap.TreeNode<>( + this.EMPTY, h, e.key, e.val, null, null + ); + if ((h & n) == 0) { + if ((pxx.prev = loTail) == null) { + lo = pxx; + } else { + loTail.next = pxx; + } + + loTail = pxx; + lc++; + } else { + if ((pxx.prev = hiTail) == null) { + hi = pxx; + } else { + hiTail.next = pxx; + } + + hiTail = pxx; + hc++; + } + } + + Short2ObjectConcurrentHashMap.Node ln = (Short2ObjectConcurrentHashMap.Node)(lc <= 6 + ? this.untreeify(lo) + : (hc != 0 ? new Short2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, lo) : t)); + Short2ObjectConcurrentHashMap.Node hn = (Short2ObjectConcurrentHashMap.Node)(hc <= 6 + ? this.untreeify(hi) + : (lc != 0 ? new Short2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, hi) : t)); + setTabAt(nextTab, i, ln); + setTabAt(nextTab, i + n, hn); + setTabAt(tab, i, fwd); + advance = true; + } + } + } + } + } + } else { + if (finishing) { + this.nextTable = null; + this.table = nextTab; + this.sizeCtl = (n << 1) - (n >>> 1); + return; + } + + int sc = this.sizeCtl; + if (U.compareAndSwapInt(this, SIZECTL, this.sizeCtl, sc - 1)) { + if (sc - 2 != resizeStamp(n) << RESIZE_STAMP_SHIFT) { + return; + } + + advance = true; + finishing = true; + i = n; + } + } + } + + if (--i < bound && !finishing) { + int nextIndex = this.transferIndex; + if (this.transferIndex <= 0) { + i = -1; + advance = false; + } else { + int nextBound; + if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex, nextBound = nextIndex > stride ? nextIndex - stride : 0)) { + bound = nextBound; + i = nextIndex - 1; + advance = false; + } + } + } else { + advance = false; + } + } + } + + protected final long sumCount() { + Short2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + long sum = this.baseCount; + if (as != null) { + for (int i = 0; i < as.length; i++) { + Short2ObjectConcurrentHashMap.CounterCell a; + if ((a = as[i]) != null) { + sum += a.value; + } + } + } + + return sum; + } + + protected final void fullAddCount(long x, boolean wasUncontended) { + int h; + if ((h = TLRUtil.getProbe()) == 0) { + TLRUtil.localInit(); + h = TLRUtil.getProbe(); + wasUncontended = true; + } + + boolean collide = false; + + while (true) { + Short2ObjectConcurrentHashMap.CounterCell[] as = this.counterCells; + int n; + if (this.counterCells != null && (n = as.length) > 0) { + Short2ObjectConcurrentHashMap.CounterCell a; + if ((a = as[n - 1 & h]) == null) { + if (this.cellsBusy == 0) { + Short2ObjectConcurrentHashMap.CounterCell r = new Short2ObjectConcurrentHashMap.CounterCell(x); + if (this.cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean created = false; + + try { + Short2ObjectConcurrentHashMap.CounterCell[] rs = this.counterCells; + int m; + int j; + if (this.counterCells != null && (m = rs.length) > 0 && rs[j = m - 1 & h] == null) { + rs[j] = r; + created = true; + } + } finally { + this.cellsBusy = 0; + } + + if (created) { + break; + } + continue; + } + } + + collide = false; + } else if (!wasUncontended) { + wasUncontended = true; + } else { + long v = a.value; + if (U.compareAndSwapLong(a, CELLVALUE, a.value, v + x)) { + break; + } + + if (this.counterCells != as || n >= NCPU) { + collide = false; + } else if (!collide) { + collide = true; + } else if (this.cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + try { + if (this.counterCells == as) { + Short2ObjectConcurrentHashMap.CounterCell[] rs = new Short2ObjectConcurrentHashMap.CounterCell[n << 1]; + + for (int i = 0; i < n; i++) { + rs[i] = as[i]; + } + + this.counterCells = rs; + } + } finally { + this.cellsBusy = 0; + } + + collide = false; + continue; + } + } + + h = TLRUtil.advanceProbe(h); + } else if (this.cellsBusy == 0 && this.counterCells == as && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean init = false; + + try { + if (this.counterCells == as) { + Short2ObjectConcurrentHashMap.CounterCell[] rs = new Short2ObjectConcurrentHashMap.CounterCell[2]; + rs[h & 1] = new Short2ObjectConcurrentHashMap.CounterCell(x); + this.counterCells = rs; + init = true; + } + } finally { + this.cellsBusy = 0; + } + + if (init) { + break; + } + } else { + long vx = this.baseCount; + if (U.compareAndSwapLong(this, BASECOUNT, this.baseCount, vx + x)) { + break; + } + } + } + } + + protected final void treeifyBin(Short2ObjectConcurrentHashMap.Node[] tab, int index) { + if (tab != null) { + int n; + if ((n = tab.length) < 64) { + this.tryPresize(n << 1); + } else { + Short2ObjectConcurrentHashMap.Node b; + if ((b = tabAt(tab, index)) != null && b.hash >= 0) { + synchronized (b) { + if (tabAt(tab, index) == b) { + Short2ObjectConcurrentHashMap.TreeNode hd = null; + Short2ObjectConcurrentHashMap.TreeNode tl = null; + + for (Short2ObjectConcurrentHashMap.Node e = b; e != null; e = e.next) { + Short2ObjectConcurrentHashMap.TreeNode p = new Short2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, e.hash, e.key, e.val, null, null); + if ((p.prev = tl) == null) { + hd = p; + } else { + tl.next = p; + } + + tl = p; + } + + setTabAt(tab, index, new Short2ObjectConcurrentHashMap.TreeBin<>(this.EMPTY, hd)); + } + } + } + } + } + } + + protected Short2ObjectConcurrentHashMap.Node untreeify(Short2ObjectConcurrentHashMap.Node b) { + Short2ObjectConcurrentHashMap.Node hd = null; + Short2ObjectConcurrentHashMap.Node tl = null; + + for (Short2ObjectConcurrentHashMap.Node q = b; q != null; q = q.next) { + Short2ObjectConcurrentHashMap.Node p = new Short2ObjectConcurrentHashMap.Node<>(this.EMPTY, q.hash, q.key, q.val, null); + if (tl == null) { + hd = p; + } else { + tl.next = p; + } + + tl = p; + } + + return hd; + } + + protected final int batchFor(long b) { + long n; + if (b != Long.MAX_VALUE && (n = this.sumCount()) > 1L && n >= b) { + int sp = ForkJoinPool.getCommonPoolParallelism() << 2; + long var6; + return b > 0L && (var6 = n / b) < sp ? (int)var6 : sp; + } else { + return 0; + } + } + + public void forEach(long parallelismThreshold, Short2ObjectConcurrentHashMap.ShortObjConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Short2ObjectConcurrentHashMap.ForEachMappingTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEach( + long parallelismThreshold, Short2ObjectConcurrentHashMap.ShortObjFunction transformer, Consumer action + ) { + if (transformer != null && action != null) { + new Short2ObjectConcurrentHashMap.ForEachTransformedMappingTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U search(long parallelismThreshold, Short2ObjectConcurrentHashMap.ShortObjFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Short2ObjectConcurrentHashMap.SearchMappingsTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public U search(Short2ObjectConcurrentHashMap.ShortObjFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U search(Short2ObjectConcurrentHashMap.ShortBiObjFunction searchFunction, X x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithByte(Short2ObjectConcurrentHashMap.ShortObjByteFunction searchFunction, byte x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithShort(Short2ObjectConcurrentHashMap.ShortObjShortFunction searchFunction, short x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithInt(Short2ObjectConcurrentHashMap.ShortObjIntFunction searchFunction, int x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithLong(Short2ObjectConcurrentHashMap.ShortObjLongFunction searchFunction, long x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithFloat(Short2ObjectConcurrentHashMap.ShortObjFloatFunction searchFunction, float x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U searchWithDouble(Short2ObjectConcurrentHashMap.ShortObjDoubleFunction searchFunction, double x) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label81: { + while (true) { + if (p != null) { + next = p; + break label81; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + U u = (U)searchFunction.apply(p.key, p.val, x); + if (u != null) { + return u; + } + } + } + + return null; + } + } + + public U reduce( + long parallelismThreshold, + Short2ObjectConcurrentHashMap.ShortObjFunction transformer, + BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Short2ObjectConcurrentHashMap.MapReduceMappingsTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer + ) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U reduce( + Short2ObjectConcurrentHashMap.ShortObjFunction transformer, BiFunction reducer + ) { + if (transformer != null && reducer != null) { + Short2ObjectConcurrentHashMap.Node[] tt = this.table; + if (this.table == null) { + return null; + } else { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + U r = null; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label88: { + while (true) { + if (p != null) { + next = p; + break label88; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + return r; + } + + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + } + } else { + throw new NullPointerException(); + } + } + + public double reduceToDouble( + long parallelismThreshold, Short2ObjectConcurrentHashMap.ToDoubleShortObjFunction transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Short2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceToLong( + long parallelismThreshold, Short2ObjectConcurrentHashMap.ToLongShortObjFunction transformer, long basis, LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Short2ObjectConcurrentHashMap.MapReduceMappingsToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceToInt( + long parallelismThreshold, Short2ObjectConcurrentHashMap.ToIntShortObjFunction transformer, int basis, IntBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Short2ObjectConcurrentHashMap.MapReduceMappingsToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachKey(long parallelismThreshold, ShortConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Short2ObjectConcurrentHashMap.ForEachKeyTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachKey(long parallelismThreshold, Short2ObjectConcurrentHashMap.ShortFunction transformer, Consumer action) { + if (transformer != null && action != null) { + new Short2ObjectConcurrentHashMap.ForEachTransformedKeyTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchKeys(long parallelismThreshold, Short2ObjectConcurrentHashMap.ShortFunction searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Short2ObjectConcurrentHashMap.SearchKeysTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public short reduceKeys(long parallelismThreshold, Short2ObjectConcurrentHashMap.ShortReduceTaskOperator reducer) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Short2ObjectConcurrentHashMap.ReduceKeysTask<>(this.EMPTY, null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer) + .invoke0(); + } + } + + public U reduceKeys( + long parallelismThreshold, Short2ObjectConcurrentHashMap.ShortFunction transformer, BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Short2ObjectConcurrentHashMap.MapReduceKeysTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceKeysToDouble( + long parallelismThreshold, Short2ObjectConcurrentHashMap.ShortToDoubleFunction transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Short2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceKeysToLong( + long parallelismThreshold, Short2ObjectConcurrentHashMap.ShortToLongFunction transformer, long basis, LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Short2ObjectConcurrentHashMap.MapReduceKeysToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceKeysToInt(long parallelismThreshold, Short2ObjectConcurrentHashMap.ShortToIntFunction transformer, int basis, IntBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Short2ObjectConcurrentHashMap.MapReduceKeysToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachValue(long parallelismThreshold, Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Short2ObjectConcurrentHashMap.ForEachValueTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachValue(long parallelismThreshold, Function transformer, Consumer action) { + if (transformer != null && action != null) { + new Short2ObjectConcurrentHashMap.ForEachTransformedValueTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchValues(long parallelismThreshold, Function searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Short2ObjectConcurrentHashMap.SearchValuesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public V reduceValues(long parallelismThreshold, BiFunction reducer) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Short2ObjectConcurrentHashMap.ReduceValuesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer).invoke(); + } + } + + public U reduceValues(long parallelismThreshold, Function transformer, BiFunction reducer) { + if (transformer != null && reducer != null) { + return new Short2ObjectConcurrentHashMap.MapReduceValuesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceValuesToDouble(long parallelismThreshold, ToDoubleFunction transformer, double basis, DoubleBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Short2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceValuesToLong(long parallelismThreshold, ToLongFunction transformer, long basis, LongBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Short2ObjectConcurrentHashMap.MapReduceValuesToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceValuesToInt(long parallelismThreshold, ToIntFunction transformer, int basis, IntBinaryOperator reducer) { + if (transformer != null && reducer != null) { + return new Short2ObjectConcurrentHashMap.MapReduceValuesToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public void forEachEntry(long parallelismThreshold, Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + new Short2ObjectConcurrentHashMap.ForEachEntryTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, action).invoke(); + } + } + + public void forEachEntry( + long parallelismThreshold, Function, ? extends U> transformer, Consumer action + ) { + if (transformer != null && action != null) { + new Short2ObjectConcurrentHashMap.ForEachTransformedEntryTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, transformer, action) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public U searchEntries(long parallelismThreshold, Function, ? extends U> searchFunction) { + if (searchFunction == null) { + throw new NullPointerException(); + } else { + return new Short2ObjectConcurrentHashMap.SearchEntriesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, searchFunction, new AtomicReference() + ) + .invoke(); + } + } + + public Short2ObjectConcurrentHashMap.Entry reduceEntries( + long parallelismThreshold, + BiFunction, Short2ObjectConcurrentHashMap.Entry, ? extends Short2ObjectConcurrentHashMap.Entry> reducer + ) { + if (reducer == null) { + throw new NullPointerException(); + } else { + return new Short2ObjectConcurrentHashMap.ReduceEntriesTask<>(null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, reducer).invoke(); + } + } + + public U reduceEntries( + long parallelismThreshold, + Function, ? extends U> transformer, + BiFunction reducer + ) { + if (transformer != null && reducer != null) { + return new Short2ObjectConcurrentHashMap.MapReduceEntriesTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, reducer + ) + .invoke(); + } else { + throw new NullPointerException(); + } + } + + public double reduceEntriesToDouble( + long parallelismThreshold, ToDoubleFunction> transformer, double basis, DoubleBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Short2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public long reduceEntriesToLong( + long parallelismThreshold, ToLongFunction> transformer, long basis, LongBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Short2ObjectConcurrentHashMap.MapReduceEntriesToLongTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public int reduceEntriesToInt( + long parallelismThreshold, ToIntFunction> transformer, int basis, IntBinaryOperator reducer + ) { + if (transformer != null && reducer != null) { + return new Short2ObjectConcurrentHashMap.MapReduceEntriesToIntTask<>( + null, this.batchFor(parallelismThreshold), 0, 0, this.table, null, transformer, basis, reducer + ) + .invoke0(); + } else { + throw new NullPointerException(); + } + } + + public V valueMatching(Predicate predicate) { + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + Short2ObjectConcurrentHashMap.Node[] tab = this.table; + int f = this.table == null ? 0 : tab.length; + int baseLimit = f; + int baseSize = f; + boolean b = false; + + label80: + while (next != null || !b) { + b |= true; + Short2ObjectConcurrentHashMap.Node e = next; + if (next != null) { + e = next.next; + } + + label76: + while (e == null) { + if (baseIndex < baseLimit) { + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab != null && (n = tab.length) > index && index >= 0) { + if ((e = tabAt(tab, index)) != null && e.hash < 0) { + if (e instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (e instanceof Short2ObjectConcurrentHashMap.TreeBin) { + e = ((Short2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + continue; + } + + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack next1 = stack.next; + stack.next = spare; + stack = next1; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + continue label76; + } + } + } + + next = null; + continue label80; + } + + next = e; + if (predicate.test(e.val)) { + return e.val; + } + } + + return null; + } + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + U = (Unsafe)f.get(null); + Class k = Short2ObjectConcurrentHashMap.class; + SIZECTL = U.objectFieldOffset(k.getDeclaredField("sizeCtl")); + TRANSFERINDEX = U.objectFieldOffset(k.getDeclaredField("transferIndex")); + BASECOUNT = U.objectFieldOffset(k.getDeclaredField("baseCount")); + CELLSBUSY = U.objectFieldOffset(k.getDeclaredField("cellsBusy")); + Class ck = Short2ObjectConcurrentHashMap.CounterCell.class; + CELLVALUE = U.objectFieldOffset(ck.getDeclaredField("value")); + Class ak = Short2ObjectConcurrentHashMap.Node[].class; + ABASE = U.arrayBaseOffset(ak); + int scale = U.arrayIndexScale(ak); + if ((scale & scale - 1) != 0) { + throw new Error("data type scale not a power of two"); + } else { + ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); + } + } catch (Exception var5) { + throw new Error(var5); + } + } + + protected static class BaseIterator extends Short2ObjectConcurrentHashMap.Traverser { + public final Short2ObjectConcurrentHashMap map; + public Short2ObjectConcurrentHashMap.Node lastReturned; + + public BaseIterator(Short2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, Short2ObjectConcurrentHashMap map) { + super(tab, size, index, limit); + this.map = map; + this.advance(); + } + + public final boolean hasNext() { + return this.next != null; + } + + public final boolean hasMoreElements() { + return this.next != null; + } + + public final void remove() { + Short2ObjectConcurrentHashMap.Node p = this.lastReturned; + if (this.lastReturned == null) { + throw new IllegalStateException(); + } else { + this.lastReturned = null; + this.map.replaceNode(p.key, null, null); + } + } + } + + protected abstract static class BulkTask extends CountedCompleter { + public Short2ObjectConcurrentHashMap.Node[] tab; + public Short2ObjectConcurrentHashMap.Node next; + public Short2ObjectConcurrentHashMap.TableStack stack; + public Short2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + public int batch; + + protected BulkTask(Short2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Short2ObjectConcurrentHashMap.Node[] t) { + super(par); + this.batch = b; + this.index = this.baseIndex = i; + if ((this.tab = t) == null) { + this.baseSize = this.baseLimit = 0; + } else if (par == null) { + this.baseSize = this.baseLimit = t.length; + } else { + this.baseLimit = f; + this.baseSize = par.baseSize; + } + } + + protected final Short2ObjectConcurrentHashMap.Node advance() { + Short2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Short2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Short2ObjectConcurrentHashMap.TreeBin) { + e = ((Short2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Short2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Short2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Short2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + } + + protected abstract static class CollectionView implements ObjectCollection, Serializable { + public static final long serialVersionUID = 7249069246763182397L; + public final Short2ObjectConcurrentHashMap map; + protected static final String oomeMsg = "Required array size too large"; + + public CollectionView(Short2ObjectConcurrentHashMap map) { + this.map = map; + } + + public Short2ObjectConcurrentHashMap getMap() { + return this.map; + } + + @Override + public final void clear() { + this.map.clear(); + } + + @Override + public final int size() { + return this.map.size(); + } + + @Override + public final boolean isEmpty() { + return this.map.isEmpty(); + } + + @Override + public abstract ObjectIterator iterator(); + + @Override + public abstract boolean contains(Object var1); + + @Override + public abstract boolean remove(Object var1); + + @Override + public final Object[] toArray() { + long sz = this.map.mappingCount(); + if (sz > 2147483639L) { + throw new OutOfMemoryError("Required array size too large"); + } else { + int n = (int)sz; + Object[] r = new Object[n]; + int i = 0; + + for (E e : this) { + if (i == n) { + if (n >= 2147483639) { + throw new OutOfMemoryError("Required array size too large"); + } + + if (n >= 1073741819) { + n = 2147483639; + } else { + n += (n >>> 1) + 1; + } + + r = Arrays.copyOf(r, n); + } + + r[i++] = e; + } + + return i == n ? r : Arrays.copyOf(r, i); + } + } + + @Override + public final T[] toArray(T[] a) { + long sz = this.map.mappingCount(); + if (sz > 2147483639L) { + throw new OutOfMemoryError("Required array size too large"); + } else { + int m = (int)sz; + T[] r = (T[])(a.length >= m ? a : (Object[])Array.newInstance(a.getClass().getComponentType(), m)); + int n = r.length; + int i = 0; + + for (E e : this) { + if (i == n) { + if (n >= 2147483639) { + throw new OutOfMemoryError("Required array size too large"); + } + + if (n >= 1073741819) { + n = 2147483639; + } else { + n += (n >>> 1) + 1; + } + + r = (T[])Arrays.copyOf(r, n); + } + + r[i++] = (T)e; + } + + if (a == r && i < n) { + r[i] = null; + return r; + } else { + return (T[])(i == n ? r : Arrays.copyOf(r, i)); + } + } + } + + @Override + public final String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + Iterator it = this.iterator(); + if (it.hasNext()) { + while (true) { + Object e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (!it.hasNext()) { + break; + } + + sb.append(',').append(' '); + } + } + + return sb.append(']').toString(); + } + + @Override + public final boolean containsAll(Collection c) { + if (c != this) { + for (Object e : c) { + if (e == null || !this.contains(e)) { + return false; + } + } + } + + return true; + } + + @Override + public final boolean removeAll(Collection c) { + if (c == null) { + throw new NullPointerException(); + } else { + boolean modified = false; + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (c.contains(it.next())) { + it.remove(); + modified = true; + } + } + + return modified; + } + } + + @Override + public final boolean retainAll(Collection c) { + if (c == null) { + throw new NullPointerException(); + } else { + boolean modified = false; + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + + return modified; + } + } + } + + protected static final class CounterCell { + public volatile long value; + + public CounterCell(long x) { + this.value = x; + } + } + + protected abstract static class DoubleReturningBulkTask extends Short2ObjectConcurrentHashMap.BulkTask { + public double result; + + public DoubleReturningBulkTask(Short2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Short2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected double invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + public interface Entry extends Short2ObjectMap.Entry { + boolean isEmpty(); + + @Deprecated + @Override + Short getKey(); + + @Override + short getShortKey(); + + @Override + V getValue(); + + @Override + int hashCode(); + + @Override + String toString(); + + @Override + boolean equals(Object var1); + + @Override + V setValue(V var1); + } + + protected static final class EntryIterator extends Short2ObjectConcurrentHashMap.BaseIterator implements ObjectIterator> { + public EntryIterator(Short2ObjectConcurrentHashMap.Node[] tab, int index, int size, int limit, Short2ObjectConcurrentHashMap map) { + super(tab, index, size, limit, map); + } + + public final Short2ObjectConcurrentHashMap.Entry next() { + Short2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + short k = p.key; + V v = p.val; + this.lastReturned = p; + this.advance(); + return new Short2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), k, v, this.map); + } + } + } + + protected static final class EntrySetView + extends Short2ObjectConcurrentHashMap.CollectionView> + implements ObjectSet>, + Serializable { + public static final long serialVersionUID = 2249069246763182397L; + + public EntrySetView(Short2ObjectConcurrentHashMap map) { + super(map); + } + + @Override + public boolean contains(Object o) { + if (o instanceof Short2ObjectMap.Entry) { + Short2ObjectMap.Entry e; + short k = (e = (Short2ObjectMap.Entry)o).getShortKey(); + if (!((Short2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + Object v; + Object r; + return (r = this.map.get(k)) != null && (v = e.getValue()) != null && (v == r || v.equals(r)); + } + } + + return false; + } + + @Override + public boolean remove(Object o) { + if (o instanceof Short2ObjectMap.Entry) { + Short2ObjectMap.Entry e; + short k = (e = (Short2ObjectMap.Entry)o).getShortKey(); + if (!((Short2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + Object v; + return (v = e.getValue()) != null && this.map.remove(k, v); + } + } + + return false; + } + + @Override + public ObjectIterator> iterator() { + Short2ObjectConcurrentHashMap m = this.map; + Short2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Short2ObjectConcurrentHashMap.EntryIterator<>(t, f, 0, f, m); + } + + public boolean add(Short2ObjectMap.Entry e) { + return this.map.putVal(e.getShortKey(), e.getValue(), false) == null; + } + + @Override + public boolean addAll(Collection> c) { + boolean added = false; + + for (Short2ObjectMap.Entry e : c) { + if (this.add(e)) { + added = true; + } + } + + return added; + } + + @Override + public final int hashCode() { + int h = 0; + Short2ObjectConcurrentHashMap.Node[] t = this.map.table; + if (this.map.table != null) { + Short2ObjectConcurrentHashMap.Traverser it = new Short2ObjectConcurrentHashMap.Traverser<>(t, t.length, 0, t.length); + + Short2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + h += p.hashCode(); + } + } + + return h; + } + + @Override + public final boolean equals(Object o) { + Set c; + return o instanceof Set && ((c = (Set)o) == this || this.containsAll(c) && c.containsAll(this)); + } + + @Override + public ObjectSpliterator> spliterator() { + Short2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Short2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Short2ObjectConcurrentHashMap.EntrySpliterator<>(t, f, 0, f, n < 0L ? 0L : n, m); + } + + @Override + public void forEach(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node[] t = this.map.table; + if (this.map.table != null) { + Short2ObjectConcurrentHashMap.Traverser it = new Short2ObjectConcurrentHashMap.Traverser<>(t, t.length, 0, t.length); + + Short2ObjectConcurrentHashMap.Node p; + while ((p = it.advance()) != null) { + action.accept(new Short2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + } + } + } + } + } + + protected static final class EntrySpliterator extends Short2ObjectConcurrentHashMap.Traverser implements ObjectSpliterator> { + public final Short2ObjectConcurrentHashMap map; + public long est; + + public EntrySpliterator(Short2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est, Short2ObjectConcurrentHashMap map) { + super(tab, size, index, limit); + this.map = map; + this.est = est; + } + + @Override + public ObjectSpliterator> trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Short2ObjectConcurrentHashMap.EntrySpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1, this.map); + } + + @Override + public void forEachRemaining(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(new Short2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + } + } + } + + @Override + public boolean tryAdvance(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(new Short2ObjectConcurrentHashMap.MapEntry<>(p.isEmpty(), p.key, p.val, this.map)); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4353; + } + } + + protected static final class ForEachEntryTask extends Short2ObjectConcurrentHashMap.BulkTask { + public final Consumer> action; + + public ForEachEntryTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Consumer> action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Consumer> action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Short2ObjectConcurrentHashMap.ForEachEntryTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachKeyTask extends Short2ObjectConcurrentHashMap.BulkTask { + public final ShortConsumer action; + + public ForEachKeyTask( + Short2ObjectConcurrentHashMap.BulkTask p, int b, int i, int f, Short2ObjectConcurrentHashMap.Node[] t, ShortConsumer action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + ShortConsumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Short2ObjectConcurrentHashMap.ForEachKeyTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.key); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachMappingTask extends Short2ObjectConcurrentHashMap.BulkTask { + public final Short2ObjectConcurrentHashMap.ShortObjConsumer action; + + public ForEachMappingTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.ShortObjConsumer action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Short2ObjectConcurrentHashMap.ShortObjConsumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Short2ObjectConcurrentHashMap.ForEachMappingTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.key, p.val); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForEachTransformedEntryTask extends Short2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> transformer; + public final Consumer action; + + public ForEachTransformedEntryTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Function, ? extends U> transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Function, ? extends U> transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Short2ObjectConcurrentHashMap.ForEachTransformedEntryTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedKeyTask extends Short2ObjectConcurrentHashMap.BulkTask { + public final Short2ObjectConcurrentHashMap.ShortFunction transformer; + public final Consumer action; + + public ForEachTransformedKeyTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.ShortFunction transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Short2ObjectConcurrentHashMap.ShortFunction transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Short2ObjectConcurrentHashMap.ForEachTransformedKeyTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedMappingTask extends Short2ObjectConcurrentHashMap.BulkTask { + public final Short2ObjectConcurrentHashMap.ShortObjFunction transformer; + public final Consumer action; + + public ForEachTransformedMappingTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.ShortObjFunction transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Short2ObjectConcurrentHashMap.ShortObjFunction transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Short2ObjectConcurrentHashMap.ForEachTransformedMappingTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action + ) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachTransformedValueTask extends Short2ObjectConcurrentHashMap.BulkTask { + public final Function transformer; + public final Consumer action; + + public ForEachTransformedValueTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Function transformer, + Consumer action + ) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + Function transformer = this.transformer; + if (this.transformer != null) { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Short2ObjectConcurrentHashMap.ForEachTransformedValueTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, transformer, action) + .fork(); + } + + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.val)) != null) { + action.accept(u); + } + } + + this.propagateCompletion(); + } + } + } + } + + protected static final class ForEachValueTask extends Short2ObjectConcurrentHashMap.BulkTask { + public final Consumer action; + + public ForEachValueTask( + Short2ObjectConcurrentHashMap.BulkTask p, int b, int i, int f, Short2ObjectConcurrentHashMap.Node[] t, Consumer action + ) { + super(p, b, i, f, t); + this.action = action; + } + + @Override + public final void compute() { + Consumer action = this.action; + if (this.action != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + new Short2ObjectConcurrentHashMap.ForEachValueTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, action).fork(); + } + + while ((p = this.advance()) != null) { + action.accept(p.val); + } + + this.propagateCompletion(); + } + } + } + + protected static final class ForwardingNode extends Short2ObjectConcurrentHashMap.Node { + public final Short2ObjectConcurrentHashMap.Node[] nextTable; + + public ForwardingNode(short empty, Short2ObjectConcurrentHashMap.Node[] tab) { + super(empty, -1, empty, null, null); + this.nextTable = tab; + } + + @Override + protected Short2ObjectConcurrentHashMap.Node find(int h, short k) { + Short2ObjectConcurrentHashMap.Node[] tab = this.nextTable; + + Short2ObjectConcurrentHashMap.Node e; + int n; + label41: + while (k != this.EMPTY && tab != null && (n = tab.length) != 0 && (e = Short2ObjectConcurrentHashMap.tabAt(tab, n - 1 & h)) != null) { + do { + int eh = e.hash; + if (e.hash == h) { + short ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + + if (eh < 0) { + if (!(e instanceof Short2ObjectConcurrentHashMap.ForwardingNode)) { + return e.find(h, k); + } + + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + continue label41; + } + } while ((e = e.next) != null); + + return null; + } + + return null; + } + } + + protected abstract static class IntReturningBulkTask extends Short2ObjectConcurrentHashMap.BulkTask { + public int result; + + public IntReturningBulkTask(Short2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Short2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected int invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + protected static final class KeyIterator implements ShortIterator { + public Short2ObjectConcurrentHashMap.Node[] tab; + public Short2ObjectConcurrentHashMap.Node next; + public Short2ObjectConcurrentHashMap.TableStack stack; + public Short2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + public final Short2ObjectConcurrentHashMap map; + public Short2ObjectConcurrentHashMap.Node lastReturned; + + public KeyIterator(Short2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, Short2ObjectConcurrentHashMap map) { + this.tab = tab; + this.baseSize = size; + this.baseIndex = this.index = index; + this.baseLimit = limit; + this.next = null; + this.map = map; + this.advance(); + } + + protected final Short2ObjectConcurrentHashMap.Node advance() { + Short2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Short2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Short2ObjectConcurrentHashMap.TreeBin) { + e = ((Short2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Short2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Short2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Short2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + + @Override + public final boolean hasNext() { + return this.next != null; + } + + public final boolean hasMoreElements() { + return this.next != null; + } + + @Override + public final void remove() { + Short2ObjectConcurrentHashMap.Node p = this.lastReturned; + if (this.lastReturned == null) { + throw new IllegalStateException(); + } else { + this.lastReturned = null; + this.map.replaceNode(p.key, null, null); + } + } + + @Override + public final short nextShort() { + Short2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + short k = p.key; + this.lastReturned = p; + this.advance(); + return k; + } + } + } + + public static class KeySetView implements ShortSet { + public static final long serialVersionUID = 7249069246763182397L; + public final Short2ObjectConcurrentHashMap map; + public final V value; + + public KeySetView(Short2ObjectConcurrentHashMap map, V value) { + this.map = map; + this.value = value; + } + + public V getMappedValue() { + return this.value; + } + + @Override + public boolean contains(short o) { + return this.map.containsKey(o); + } + + @Override + public boolean remove(short o) { + return this.map.remove(o) != null; + } + + @Override + public ShortIterator iterator() { + Short2ObjectConcurrentHashMap m = this.map; + Short2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Short2ObjectConcurrentHashMap.KeyIterator<>(t, f, 0, f, m); + } + + @Override + public boolean add(short e) { + V v = this.value; + if (this.value == null) { + throw new UnsupportedOperationException(); + } else { + return this.map.putVal(e, v, true) == null; + } + } + + @Override + public boolean addAll(ShortCollection c) { + boolean added = false; + V v = this.value; + if (this.value == null) { + throw new UnsupportedOperationException(); + } else { + ShortIterator iter = c.iterator(); + + while (iter.hasNext()) { + short e = iter.nextShort(); + if (this.map.putVal(e, v, true) == null) { + added = true; + } + } + + return added; + } + } + + @Override + public int hashCode() { + int h = 0; + ShortIterator iter = this.iterator(); + + while (iter.hasNext()) { + h += Short.hashCode(iter.nextShort()); + } + + return h; + } + + @Override + public boolean equals(Object o) { + ShortSet c; + return o instanceof ShortSet && ((c = (ShortSet)o) == this || this.containsAll(c) && c.containsAll(this)); + } + + public short getNoEntryValue() { + return this.map.EMPTY; + } + + @Override + public int size() { + return this.map.size(); + } + + @Override + public boolean isEmpty() { + return this.map.isEmpty(); + } + + @Override + public Object[] toArray() { + Object[] out = new Short[this.size()]; + ShortIterator iter = this.iterator(); + + int i; + for (i = 0; i < out.length && iter.hasNext(); i++) { + out[i] = iter.nextShort(); + } + + if (out.length > i + 1) { + out[i] = this.map.EMPTY; + } + + return out; + } + + @Override + public Object[] toArray(Object[] dest) { + ShortIterator iter = this.iterator(); + + int i; + for (i = 0; i < dest.length && iter.hasNext() && i <= dest.length; i++) { + dest[i] = iter.next(); + } + + if (dest.length > i + 1) { + dest[i] = this.map.EMPTY; + } + + return dest; + } + + @Override + public short[] toShortArray() { + short[] out = new short[this.size()]; + ShortIterator iter = this.iterator(); + + int i; + for (i = 0; i < out.length && iter.hasNext(); i++) { + out[i] = iter.next(); + } + + if (out.length > i + 1) { + out[i] = this.map.EMPTY; + } + + return out; + } + + @Override + public short[] toArray(short[] dest) { + ShortIterator iter = this.iterator(); + + int i; + for (i = 0; i < dest.length && iter.hasNext() && i <= dest.length; i++) { + dest[i] = iter.next(); + } + + if (dest.length > i + 1) { + dest[i] = this.map.EMPTY; + } + + return dest; + } + + @Override + public short[] toShortArray(short[] dest) { + return this.toArray(dest); + } + + @Override + public boolean containsAll(Collection collection) { + for (Object element : collection) { + if (!(element instanceof Long)) { + return false; + } + + short c = (Short)element; + if (!this.contains(c)) { + return false; + } + } + + return true; + } + + @Override + public boolean containsAll(ShortCollection collection) { + ShortIterator iter = collection.iterator(); + + while (iter.hasNext()) { + short element = iter.next(); + if (!this.contains(element)) { + return false; + } + } + + return true; + } + + public boolean containsAll(short[] array) { + int i = array.length; + + while (i-- > 0) { + if (!this.contains(array[i])) { + return false; + } + } + + return true; + } + + @Override + public boolean addAll(Collection collection) { + boolean changed = false; + + for (Short element : collection) { + short e = element; + if (this.add(e)) { + changed = true; + } + } + + return changed; + } + + public boolean addAll(short[] array) { + boolean changed = false; + int i = array.length; + + while (i-- > 0) { + if (this.add(array[i])) { + changed = true; + } + } + + return changed; + } + + @Override + public boolean retainAll(Collection collection) { + boolean modified = false; + ShortIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (!collection.contains(iter.next())) { + iter.remove(); + modified = true; + } + } + + return modified; + } + + @Override + public boolean retainAll(ShortCollection collection) { + if (this == collection) { + return false; + } else { + boolean modified = false; + ShortIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (!collection.contains(iter.next())) { + iter.remove(); + modified = true; + } + } + + return modified; + } + } + + public boolean retainAll(short[] array) { + boolean modified = false; + ShortIterator iter = this.iterator(); + + while (iter.hasNext()) { + if (Arrays.binarySearch(array, iter.next().shortValue()) < 0) { + iter.remove(); + modified = true; + } + } + + return modified; + } + + @Override + public boolean removeAll(Collection collection) { + boolean changed = false; + + for (Object element : collection) { + if (element instanceof Short) { + short c = (Short)element; + if (this.remove(c)) { + changed = true; + } + } + } + + return changed; + } + + @Override + public boolean removeAll(ShortCollection collection) { + boolean changed = false; + ShortIterator iter = collection.iterator(); + + while (iter.hasNext()) { + short element = iter.next(); + if (this.remove(element)) { + changed = true; + } + } + + return changed; + } + + public boolean removeAll(short[] array) { + boolean changed = false; + int i = array.length; + + while (i-- > 0) { + if (this.remove(array[i])) { + changed = true; + } + } + + return changed; + } + + @Override + public void clear() { + this.map.clear(); + } + + @Override + public ShortSpliterator spliterator() { + Short2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Short2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Short2ObjectConcurrentHashMap.KeySpliterator<>(t, f, 0, f, n < 0L ? 0L : n); + } + } + + protected static final class KeySpliterator extends Short2ObjectConcurrentHashMap.Traverser implements ShortSpliterator { + public long est; + + public KeySpliterator(Short2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est) { + super(tab, size, index, limit); + this.est = est; + } + + @Override + public ShortSpliterator trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Short2ObjectConcurrentHashMap.KeySpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1); + } + + @Override + public boolean tryAdvance(Consumer action) { + return action instanceof ShortConsumer ? this.tryAdvance((ShortConsumer)action) : this.tryAdvance(value -> action.accept(value)); + } + + public void forEachRemaining(ShortConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(p.key); + } + } + } + + public boolean tryAdvance(ShortConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(p.key); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4353; + } + } + + protected abstract static class LongReturningBulkTask extends Short2ObjectConcurrentHashMap.BulkTask { + public long result; + + public LongReturningBulkTask(Short2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Short2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected long invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + protected static final class MapEntry implements Short2ObjectConcurrentHashMap.Entry { + public final boolean empty; + public final short key; + public V val; + public final Short2ObjectConcurrentHashMap map; + + public MapEntry(boolean empty, short key, V val, Short2ObjectConcurrentHashMap map) { + this.empty = empty; + this.key = key; + this.val = val; + this.map = map; + } + + @Override + public boolean isEmpty() { + return this.empty; + } + + @Override + public Short getKey() { + return this.key; + } + + @Override + public short getShortKey() { + return this.key; + } + + @Override + public V getValue() { + return this.val; + } + + @Override + public String toString() { + return this.empty ? "EMPTY=" + this.val : this.key + "=" + this.val; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o instanceof Short2ObjectConcurrentHashMap.Entry) { + if (this.empty != ((Short2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + return false; + } else { + return !this.empty && this.key != ((Short2ObjectConcurrentHashMap.Entry)o).getShortKey() + ? false + : this.val.equals(((Short2ObjectConcurrentHashMap.Entry)o).getValue()); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.empty ? 1 : 0; + result = 31 * result + Short.hashCode(this.key); + return 31 * result + this.val.hashCode(); + } + + @Override + public V setValue(V value) { + if (value == null) { + throw new NullPointerException(); + } else { + V v = this.val; + this.val = value; + this.map.put(this.key, value); + return v; + } + } + } + + protected static final class MapReduceEntriesTask extends Short2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> transformer; + public final BiFunction reducer; + public U result; + public Short2ObjectConcurrentHashMap.MapReduceEntriesTask rights; + public Short2ObjectConcurrentHashMap.MapReduceEntriesTask nextRight; + + public MapReduceEntriesTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.MapReduceEntriesTask nextRight, + Function, ? extends U> transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Function, ? extends U> transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.MapReduceEntriesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Short2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.MapReduceEntriesTask t = (Short2ObjectConcurrentHashMap.MapReduceEntriesTask)c; + + for (Short2ObjectConcurrentHashMap.MapReduceEntriesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceEntriesToDoubleTask extends Short2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final ToDoubleFunction> transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Short2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask rights; + public Short2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask nextRight; + + public MapReduceEntriesToDoubleTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask nextRight, + ToDoubleFunction> transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToDoubleFunction> transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask t = (Short2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask)c; + + for (Short2ObjectConcurrentHashMap.MapReduceEntriesToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceEntriesToIntTask extends Short2ObjectConcurrentHashMap.IntReturningBulkTask { + public final ToIntFunction> transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Short2ObjectConcurrentHashMap.MapReduceEntriesToIntTask rights; + public Short2ObjectConcurrentHashMap.MapReduceEntriesToIntTask nextRight; + + public MapReduceEntriesToIntTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.MapReduceEntriesToIntTask nextRight, + ToIntFunction> transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToIntFunction> transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.MapReduceEntriesToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.MapReduceEntriesToIntTask t = (Short2ObjectConcurrentHashMap.MapReduceEntriesToIntTask)c; + + for (Short2ObjectConcurrentHashMap.MapReduceEntriesToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceEntriesToLongTask extends Short2ObjectConcurrentHashMap.LongReturningBulkTask { + public final ToLongFunction> transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Short2ObjectConcurrentHashMap.MapReduceEntriesToLongTask rights; + public Short2ObjectConcurrentHashMap.MapReduceEntriesToLongTask nextRight; + + public MapReduceEntriesToLongTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.MapReduceEntriesToLongTask nextRight, + ToLongFunction> transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToLongFunction> transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.MapReduceEntriesToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.MapReduceEntriesToLongTask t = (Short2ObjectConcurrentHashMap.MapReduceEntriesToLongTask)c; + + for (Short2ObjectConcurrentHashMap.MapReduceEntriesToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysTask extends Short2ObjectConcurrentHashMap.BulkTask { + public final Short2ObjectConcurrentHashMap.ShortFunction transformer; + public final BiFunction reducer; + public U result; + public Short2ObjectConcurrentHashMap.MapReduceKeysTask rights; + public Short2ObjectConcurrentHashMap.MapReduceKeysTask nextRight; + + public MapReduceKeysTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.MapReduceKeysTask nextRight, + Short2ObjectConcurrentHashMap.ShortFunction transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Short2ObjectConcurrentHashMap.ShortFunction transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.MapReduceKeysTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Short2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.MapReduceKeysTask t = (Short2ObjectConcurrentHashMap.MapReduceKeysTask)c; + + for (Short2ObjectConcurrentHashMap.MapReduceKeysTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceKeysToDoubleTask extends Short2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final Short2ObjectConcurrentHashMap.ShortToDoubleFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Short2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask rights; + public Short2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask nextRight; + + public MapReduceKeysToDoubleTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask nextRight, + Short2ObjectConcurrentHashMap.ShortToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Short2ObjectConcurrentHashMap.ShortToDoubleFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask t = (Short2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask)c; + + for (Short2ObjectConcurrentHashMap.MapReduceKeysToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysToIntTask extends Short2ObjectConcurrentHashMap.IntReturningBulkTask { + public final Short2ObjectConcurrentHashMap.ShortToIntFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Short2ObjectConcurrentHashMap.MapReduceKeysToIntTask rights; + public Short2ObjectConcurrentHashMap.MapReduceKeysToIntTask nextRight; + + public MapReduceKeysToIntTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.MapReduceKeysToIntTask nextRight, + Short2ObjectConcurrentHashMap.ShortToIntFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Short2ObjectConcurrentHashMap.ShortToIntFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.MapReduceKeysToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.MapReduceKeysToIntTask t = (Short2ObjectConcurrentHashMap.MapReduceKeysToIntTask)c; + + for (Short2ObjectConcurrentHashMap.MapReduceKeysToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceKeysToLongTask extends Short2ObjectConcurrentHashMap.LongReturningBulkTask { + public final Short2ObjectConcurrentHashMap.ShortToLongFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Short2ObjectConcurrentHashMap.MapReduceKeysToLongTask rights; + public Short2ObjectConcurrentHashMap.MapReduceKeysToLongTask nextRight; + + public MapReduceKeysToLongTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.MapReduceKeysToLongTask nextRight, + Short2ObjectConcurrentHashMap.ShortToLongFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Short2ObjectConcurrentHashMap.ShortToLongFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.MapReduceKeysToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.key)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.MapReduceKeysToLongTask t = (Short2ObjectConcurrentHashMap.MapReduceKeysToLongTask)c; + + for (Short2ObjectConcurrentHashMap.MapReduceKeysToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsTask extends Short2ObjectConcurrentHashMap.BulkTask { + public final Short2ObjectConcurrentHashMap.ShortObjFunction transformer; + public final BiFunction reducer; + public U result; + public Short2ObjectConcurrentHashMap.MapReduceMappingsTask rights; + public Short2ObjectConcurrentHashMap.MapReduceMappingsTask nextRight; + + public MapReduceMappingsTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.MapReduceMappingsTask nextRight, + Short2ObjectConcurrentHashMap.ShortObjFunction transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Short2ObjectConcurrentHashMap.ShortObjFunction transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.MapReduceMappingsTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Short2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.key, p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.MapReduceMappingsTask t = (Short2ObjectConcurrentHashMap.MapReduceMappingsTask)c; + + for (Short2ObjectConcurrentHashMap.MapReduceMappingsTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceMappingsToDoubleTask extends Short2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final Short2ObjectConcurrentHashMap.ToDoubleShortObjFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Short2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask rights; + public Short2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask nextRight; + + public MapReduceMappingsToDoubleTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask nextRight, + Short2ObjectConcurrentHashMap.ToDoubleShortObjFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Short2ObjectConcurrentHashMap.ToDoubleShortObjFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask t = (Short2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask)c; + + for (Short2ObjectConcurrentHashMap.MapReduceMappingsToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsToIntTask extends Short2ObjectConcurrentHashMap.IntReturningBulkTask { + public final Short2ObjectConcurrentHashMap.ToIntShortObjFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Short2ObjectConcurrentHashMap.MapReduceMappingsToIntTask rights; + public Short2ObjectConcurrentHashMap.MapReduceMappingsToIntTask nextRight; + + public MapReduceMappingsToIntTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.MapReduceMappingsToIntTask nextRight, + Short2ObjectConcurrentHashMap.ToIntShortObjFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Short2ObjectConcurrentHashMap.ToIntShortObjFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.MapReduceMappingsToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.MapReduceMappingsToIntTask t = (Short2ObjectConcurrentHashMap.MapReduceMappingsToIntTask)c; + + for (Short2ObjectConcurrentHashMap.MapReduceMappingsToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceMappingsToLongTask extends Short2ObjectConcurrentHashMap.LongReturningBulkTask { + public final Short2ObjectConcurrentHashMap.ToLongShortObjFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Short2ObjectConcurrentHashMap.MapReduceMappingsToLongTask rights; + public Short2ObjectConcurrentHashMap.MapReduceMappingsToLongTask nextRight; + + public MapReduceMappingsToLongTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.MapReduceMappingsToLongTask nextRight, + Short2ObjectConcurrentHashMap.ToLongShortObjFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Short2ObjectConcurrentHashMap.ToLongShortObjFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.MapReduceMappingsToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.key, p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.MapReduceMappingsToLongTask t = (Short2ObjectConcurrentHashMap.MapReduceMappingsToLongTask)c; + + for (Short2ObjectConcurrentHashMap.MapReduceMappingsToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesTask extends Short2ObjectConcurrentHashMap.BulkTask { + public final Function transformer; + public final BiFunction reducer; + public U result; + public Short2ObjectConcurrentHashMap.MapReduceValuesTask rights; + public Short2ObjectConcurrentHashMap.MapReduceValuesTask nextRight; + + public MapReduceValuesTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.MapReduceValuesTask nextRight, + Function transformer, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + + @Override + public final U getRawResult() { + return this.result; + } + + @Override + public final void compute() { + Function transformer = this.transformer; + if (this.transformer != null) { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.MapReduceValuesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, reducer + )) + .fork(); + } + + U r = null; + + Short2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + U u; + if ((u = (U)transformer.apply(p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.MapReduceValuesTask t = (Short2ObjectConcurrentHashMap.MapReduceValuesTask)c; + + for (Short2ObjectConcurrentHashMap.MapReduceValuesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + U sr = s.result; + if (s.result != null) { + U tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + } + + protected static final class MapReduceValuesToDoubleTask extends Short2ObjectConcurrentHashMap.DoubleReturningBulkTask { + public final ToDoubleFunction transformer; + public final DoubleBinaryOperator reducer; + public final double basis; + public Short2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask rights; + public Short2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask nextRight; + + public MapReduceValuesToDoubleTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask nextRight, + ToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Double getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToDoubleFunction transformer = this.transformer; + if (this.transformer != null) { + DoubleBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + double r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask t = (Short2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask)c; + + for (Short2ObjectConcurrentHashMap.MapReduceValuesToDoubleTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsDouble(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesToIntTask extends Short2ObjectConcurrentHashMap.IntReturningBulkTask { + public final ToIntFunction transformer; + public final IntBinaryOperator reducer; + public final int basis; + public Short2ObjectConcurrentHashMap.MapReduceValuesToIntTask rights; + public Short2ObjectConcurrentHashMap.MapReduceValuesToIntTask nextRight; + + public MapReduceValuesToIntTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.MapReduceValuesToIntTask nextRight, + ToIntFunction transformer, + int basis, + IntBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Integer getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToIntFunction transformer = this.transformer; + if (this.transformer != null) { + IntBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + int r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.MapReduceValuesToIntTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsInt(r, transformer.applyAsInt(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.MapReduceValuesToIntTask t = (Short2ObjectConcurrentHashMap.MapReduceValuesToIntTask)c; + + for (Short2ObjectConcurrentHashMap.MapReduceValuesToIntTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsInt(t.result, s.result); + } + } + } + } + } + } + + protected static final class MapReduceValuesToLongTask extends Short2ObjectConcurrentHashMap.LongReturningBulkTask { + public final ToLongFunction transformer; + public final LongBinaryOperator reducer; + public final long basis; + public Short2ObjectConcurrentHashMap.MapReduceValuesToLongTask rights; + public Short2ObjectConcurrentHashMap.MapReduceValuesToLongTask nextRight; + + public MapReduceValuesToLongTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.MapReduceValuesToLongTask nextRight, + ToLongFunction transformer, + long basis, + LongBinaryOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + public final Long getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + ToLongFunction transformer = this.transformer; + if (this.transformer != null) { + LongBinaryOperator reducer = this.reducer; + if (this.reducer != null) { + long r = this.basis; + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.MapReduceValuesToLongTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, transformer, r, reducer + )) + .fork(); + } + + while ((p = this.advance()) != null) { + r = reducer.applyAsLong(r, transformer.applyAsLong(p.val)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.MapReduceValuesToLongTask t = (Short2ObjectConcurrentHashMap.MapReduceValuesToLongTask)c; + + for (Short2ObjectConcurrentHashMap.MapReduceValuesToLongTask s = t.rights; s != null; s = t.rights = s.nextRight) { + t.result = reducer.applyAsLong(t.result, s.result); + } + } + } + } + } + } + + protected static class Node implements Short2ObjectConcurrentHashMap.Entry { + public final short EMPTY; + public final int hash; + public final short key; + public volatile V val; + public volatile Short2ObjectConcurrentHashMap.Node next; + + public Node(short empty, int hash, short key, V val, Short2ObjectConcurrentHashMap.Node next) { + this.EMPTY = empty; + this.hash = hash; + this.key = key; + this.val = val; + this.next = next; + } + + @Override + public final boolean isEmpty() { + return this.key == this.EMPTY; + } + + @Override + public final Short getKey() { + return this.key; + } + + @Override + public final short getShortKey() { + return this.key; + } + + @Override + public final V getValue() { + return this.val; + } + + @Override + public final int hashCode() { + return Short.hashCode(this.key) ^ this.val.hashCode(); + } + + @Override + public final String toString() { + return this.isEmpty() ? "EMPTY=" + this.val : this.key + "=" + this.val; + } + + @Override + public final V setValue(V value) { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean equals(Object o) { + boolean empty = this.isEmpty(); + if (o instanceof Short2ObjectConcurrentHashMap.Entry) { + if (empty != ((Short2ObjectConcurrentHashMap.Entry)o).isEmpty()) { + return false; + } else { + return !empty && this.key != ((Short2ObjectConcurrentHashMap.Entry)o).getShortKey() + ? false + : this.val.equals(((Short2ObjectConcurrentHashMap.Entry)o).getValue()); + } + } else { + return false; + } + } + + protected Short2ObjectConcurrentHashMap.Node find(int h, short k) { + Short2ObjectConcurrentHashMap.Node e = this; + if (k != this.EMPTY) { + do { + if (e.hash == h) { + short ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + } while ((e = e.next) != null); + } + + return null; + } + } + + protected static final class ReduceEntriesTask extends Short2ObjectConcurrentHashMap.BulkTask> { + public final BiFunction, Short2ObjectConcurrentHashMap.Entry, ? extends Short2ObjectConcurrentHashMap.Entry> reducer; + public Short2ObjectConcurrentHashMap.Entry result; + public Short2ObjectConcurrentHashMap.ReduceEntriesTask rights; + public Short2ObjectConcurrentHashMap.ReduceEntriesTask nextRight; + + public ReduceEntriesTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.ReduceEntriesTask nextRight, + BiFunction, Short2ObjectConcurrentHashMap.Entry, ? extends Short2ObjectConcurrentHashMap.Entry> reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.reducer = reducer; + } + + public final Short2ObjectConcurrentHashMap.Entry getRawResult() { + return this.result; + } + + @Override + public final void compute() { + BiFunction, Short2ObjectConcurrentHashMap.Entry, ? extends Short2ObjectConcurrentHashMap.Entry> reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.ReduceEntriesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + Short2ObjectConcurrentHashMap.Entry r = null; + + Short2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + r = (Short2ObjectConcurrentHashMap.Entry)(r == null ? p : reducer.apply(r, p)); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.ReduceEntriesTask t = (Short2ObjectConcurrentHashMap.ReduceEntriesTask)c; + + for (Short2ObjectConcurrentHashMap.ReduceEntriesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + Short2ObjectConcurrentHashMap.Entry sr = s.result; + if (s.result != null) { + Short2ObjectConcurrentHashMap.Entry tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + + protected static final class ReduceKeysTask extends Short2ObjectConcurrentHashMap.ShortReturningBulkTask2 { + public final short EMPTY; + public final Short2ObjectConcurrentHashMap.ShortReduceTaskOperator reducer; + public Short2ObjectConcurrentHashMap.ReduceKeysTask rights; + public Short2ObjectConcurrentHashMap.ReduceKeysTask nextRight; + + public ReduceKeysTask( + short EMPTY, + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.ReduceKeysTask nextRight, + Short2ObjectConcurrentHashMap.ShortReduceTaskOperator reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.EMPTY = EMPTY; + this.reducer = reducer; + } + + public final Short getRawResult() { + throw new UnsupportedOperationException(); + } + + @Override + public final void compute() { + Short2ObjectConcurrentHashMap.ShortReduceTaskOperator reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.ReduceKeysTask<>( + this.EMPTY, this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + boolean found = false; + short r = this.EMPTY; + + Short2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + short u = p.key; + if (!found) { + found = true; + r = u; + } else if (!p.isEmpty()) { + found = true; + r = reducer.reduce(this.EMPTY, r, u); + } + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.ReduceKeysTask t = (Short2ObjectConcurrentHashMap.ReduceKeysTask)c; + + for (Short2ObjectConcurrentHashMap.ReduceKeysTask s = t.rights; s != null; s = t.rights = s.nextRight) { + short sr = s.result; + if (s.result != this.EMPTY) { + short tr = t.result; + t.result = t.result == this.EMPTY ? sr : reducer.reduce(this.EMPTY, tr, sr); + } + } + } + } + } + } + + protected static final class ReduceValuesTask extends Short2ObjectConcurrentHashMap.BulkTask { + public final BiFunction reducer; + public V result; + public Short2ObjectConcurrentHashMap.ReduceValuesTask rights; + public Short2ObjectConcurrentHashMap.ReduceValuesTask nextRight; + + public ReduceValuesTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.ReduceValuesTask nextRight, + BiFunction reducer + ) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.reducer = reducer; + } + + @Override + public final V getRawResult() { + return this.result; + } + + @Override + public final void compute() { + BiFunction reducer = this.reducer; + if (this.reducer != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + this.addToPendingCount(1); + (this.rights = new Short2ObjectConcurrentHashMap.ReduceValuesTask<>( + this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, this.rights, reducer + )) + .fork(); + } + + V r = null; + + Short2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + V v = p.val; + r = r == null ? v : reducer.apply(r, v); + } + + this.result = r; + + for (CountedCompleter c = this.firstComplete(); c != null; c = c.nextComplete()) { + Short2ObjectConcurrentHashMap.ReduceValuesTask t = (Short2ObjectConcurrentHashMap.ReduceValuesTask)c; + + for (Short2ObjectConcurrentHashMap.ReduceValuesTask s = t.rights; s != null; s = t.rights = s.nextRight) { + V sr = s.result; + if (s.result != null) { + V tr = t.result; + t.result = t.result == null ? sr : reducer.apply(tr, sr); + } + } + } + } + } + } + + protected static final class ReservationNode extends Short2ObjectConcurrentHashMap.Node { + public ReservationNode(short empty) { + super(empty, -3, empty, null, null); + } + + @Override + protected Short2ObjectConcurrentHashMap.Node find(int h, short k) { + return null; + } + } + + protected static final class SearchEntriesTask extends Short2ObjectConcurrentHashMap.BulkTask { + public final Function, ? extends U> searchFunction; + public final AtomicReference result; + + public SearchEntriesTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Function, ? extends U> searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Function, ? extends U> searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Short2ObjectConcurrentHashMap.SearchEntriesTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result) + .fork(); + } + + while (result.get() == null) { + Short2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + + return; + } + } + } + } + } + } + + protected static final class SearchKeysTask extends Short2ObjectConcurrentHashMap.BulkTask { + public final Short2ObjectConcurrentHashMap.ShortFunction searchFunction; + public final AtomicReference result; + + public SearchKeysTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.ShortFunction searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Short2ObjectConcurrentHashMap.ShortFunction searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Short2ObjectConcurrentHashMap.SearchKeysTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Short2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.key)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static final class SearchMappingsTask extends Short2ObjectConcurrentHashMap.BulkTask { + public final Short2ObjectConcurrentHashMap.ShortObjFunction searchFunction; + public final AtomicReference result; + + public SearchMappingsTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Short2ObjectConcurrentHashMap.ShortObjFunction searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Short2ObjectConcurrentHashMap.ShortObjFunction searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Short2ObjectConcurrentHashMap.SearchMappingsTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result) + .fork(); + } + + while (result.get() == null) { + Short2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.key, p.val)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static final class SearchValuesTask extends Short2ObjectConcurrentHashMap.BulkTask { + public final Function searchFunction; + public final AtomicReference result; + + public SearchValuesTask( + Short2ObjectConcurrentHashMap.BulkTask p, + int b, + int i, + int f, + Short2ObjectConcurrentHashMap.Node[] t, + Function searchFunction, + AtomicReference result + ) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return this.result.get(); + } + + @Override + public final void compute() { + Function searchFunction = this.searchFunction; + if (this.searchFunction != null) { + AtomicReference result = this.result; + if (this.result != null) { + int i = this.baseIndex; + + while (this.batch > 0) { + int f = this.baseLimit; + int h; + if ((h = this.baseLimit + i >>> 1) <= i) { + break; + } + + if (result.get() != null) { + return; + } + + this.addToPendingCount(1); + new Short2ObjectConcurrentHashMap.SearchValuesTask<>(this, this.batch >>>= 1, this.baseLimit = h, f, this.tab, searchFunction, result).fork(); + } + + while (result.get() == null) { + Short2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + this.propagateCompletion(); + break; + } + + U u; + if ((u = (U)searchFunction.apply(p.val)) != null) { + if (result.compareAndSet(null, u)) { + this.quietlyCompleteRoot(); + } + break; + } + } + } + } + } + } + + protected static class Segment extends ReentrantLock implements Serializable { + public static final long serialVersionUID = 2249069246763182397L; + public final float loadFactor; + + public Segment(float lf) { + this.loadFactor = lf; + } + } + + @FunctionalInterface + public interface ShortBiObjByteConsumer { + void accept(short var1, V var2, byte var3, X var4); + } + + @FunctionalInterface + public interface ShortBiObjConsumer { + void accept(short var1, V var2, X var3); + } + + @FunctionalInterface + public interface ShortBiObjDoubleConsumer { + void accept(short var1, V var2, double var3, X var5); + } + + @FunctionalInterface + public interface ShortBiObjFloatConsumer { + void accept(short var1, V var2, float var3, X var4); + } + + @FunctionalInterface + public interface ShortBiObjFunction { + J apply(short var1, V var2, X var3); + } + + @FunctionalInterface + public interface ShortBiObjIntConsumer { + void accept(short var1, V var2, int var3, X var4); + } + + @FunctionalInterface + public interface ShortBiObjLongConsumer { + void accept(short var1, V var2, long var3, X var5); + } + + @FunctionalInterface + public interface ShortBiObjShortConsumer { + void accept(short var1, V var2, short var3, X var4); + } + + @FunctionalInterface + public interface ShortFunction { + R apply(short var1); + } + + @FunctionalInterface + public interface ShortObjByteConsumer { + void accept(short var1, V var2, byte var3); + } + + @FunctionalInterface + public interface ShortObjByteFunction { + J apply(short var1, V var2, byte var3); + } + + @FunctionalInterface + public interface ShortObjConsumer { + void accept(short var1, V var2); + } + + @FunctionalInterface + public interface ShortObjDoubleConsumer { + void accept(short var1, V var2, double var3); + } + + @FunctionalInterface + public interface ShortObjDoubleFunction { + J apply(short var1, V var2, double var3); + } + + @FunctionalInterface + public interface ShortObjFloatConsumer { + void accept(short var1, V var2, float var3); + } + + @FunctionalInterface + public interface ShortObjFloatFunction { + J apply(short var1, V var2, float var3); + } + + @FunctionalInterface + public interface ShortObjFunction { + J apply(short var1, V var2); + } + + @FunctionalInterface + public interface ShortObjIntConsumer { + void accept(short var1, V var2, int var3); + } + + @FunctionalInterface + public interface ShortObjIntFunction { + J apply(short var1, V var2, int var3); + } + + @FunctionalInterface + public interface ShortObjLongConsumer { + void accept(short var1, V var2, long var3); + } + + @FunctionalInterface + public interface ShortObjLongFunction { + J apply(short var1, V var2, long var3); + } + + @FunctionalInterface + public interface ShortObjShortConsumer { + void accept(short var1, V var2, short var3); + } + + @FunctionalInterface + public interface ShortObjShortFunction { + J apply(short var1, V var2, short var3); + } + + @FunctionalInterface + public interface ShortReduceTaskOperator { + short reduce(short var1, short var2, short var3); + } + + protected abstract static class ShortReturningBulkTask2 extends Short2ObjectConcurrentHashMap.BulkTask { + public short result; + + public ShortReturningBulkTask2(Short2ObjectConcurrentHashMap.BulkTask par, int b, int i, int f, Short2ObjectConcurrentHashMap.Node[] t) { + super(par, b, i, f, t); + } + + protected short invoke0() { + this.quietlyInvoke(); + Throwable exc = this.getException(); + if (exc != null) { + throw SneakyThrow.sneakyThrow(exc); + } else { + return this.result; + } + } + } + + @FunctionalInterface + public interface ShortToDoubleFunction { + double applyAsDouble(short var1); + } + + @FunctionalInterface + public interface ShortToIntFunction { + int applyAsInt(short var1); + } + + @FunctionalInterface + public interface ShortToLongFunction { + long applyAsLong(short var1); + } + + @FunctionalInterface + public interface ShortTriObjConsumer { + void accept(short var1, V var2, X var3, Y var4); + } + + protected static final class TableStack { + public int length; + public int index; + public Short2ObjectConcurrentHashMap.Node[] tab; + public Short2ObjectConcurrentHashMap.TableStack next; + + public TableStack() { + } + } + + @FunctionalInterface + public interface ToDoubleShortObjFunction { + double applyAsDouble(short var1, V var2); + } + + @FunctionalInterface + public interface ToIntShortObjFunction { + int applyAsInt(short var1, V var2); + } + + @FunctionalInterface + public interface ToLongShortObjFunction { + long applyAsLong(short var1, V var2); + } + + @FunctionalInterface + public interface ToShortFunction { + short applyAsShort(T var1); + } + + protected static class Traverser { + public Short2ObjectConcurrentHashMap.Node[] tab; + public Short2ObjectConcurrentHashMap.Node next; + public Short2ObjectConcurrentHashMap.TableStack stack; + public Short2ObjectConcurrentHashMap.TableStack spare; + public int index; + public int baseIndex; + public int baseLimit; + public final int baseSize; + + public Traverser(Short2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit) { + this.tab = tab; + this.baseSize = size; + this.baseIndex = this.index = index; + this.baseLimit = limit; + this.next = null; + } + + protected final Short2ObjectConcurrentHashMap.Node advance() { + Short2ObjectConcurrentHashMap.Node e = this.next; + if (this.next != null) { + e = e.next; + } + + while (true) { + if (e != null) { + return this.next = e; + } + + if (this.baseIndex >= this.baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = this.tab; + if (this.tab == null) { + break; + } + + int n; + int var10000 = n = t.length; + int i = this.index; + if (var10000 <= this.index || i < 0) { + break; + } + + if ((e = Short2ObjectConcurrentHashMap.tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + this.tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)e).nextTable; + e = null; + this.pushState(t, i, n); + continue; + } + + if (e instanceof Short2ObjectConcurrentHashMap.TreeBin) { + e = ((Short2ObjectConcurrentHashMap.TreeBin)e).first; + } else { + e = null; + } + } + + if (this.stack != null) { + this.recoverState(n); + } else if ((this.index = i + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + } + + return this.next = null; + } + + protected void pushState(Short2ObjectConcurrentHashMap.Node[] t, int i, int n) { + Short2ObjectConcurrentHashMap.TableStack s = this.spare; + if (s != null) { + this.spare = s.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = i; + s.next = this.stack; + this.stack = s; + } + + protected void recoverState(int n) { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = this.stack; + if (this.stack != null) { + int len = s.length; + if ((this.index = this.index + s.length) >= n) { + n = len; + this.index = s.index; + this.tab = s.tab; + s.tab = null; + Short2ObjectConcurrentHashMap.TableStack next = s.next; + s.next = this.spare; + this.stack = next; + this.spare = s; + continue; + } + } + + if (s == null && (this.index = this.index + this.baseSize) >= n) { + this.index = ++this.baseIndex; + } + + return; + } + } + } + + protected static final class TreeBin extends Short2ObjectConcurrentHashMap.Node { + public Short2ObjectConcurrentHashMap.TreeNode root; + public volatile Short2ObjectConcurrentHashMap.TreeNode first; + public volatile Thread waiter; + public volatile int lockState; + public static final int WRITER = 1; + public static final int WAITER = 2; + public static final int READER = 4; + protected static final Unsafe U; + protected static final long LOCKSTATE; + + protected int tieBreakOrder(short a, short b) { + int comp = Short.compare(a, b); + return comp > 0 ? 1 : -1; + } + + public TreeBin(short empty, Short2ObjectConcurrentHashMap.TreeNode b) { + super(empty, -2, empty, null, null); + this.first = b; + Short2ObjectConcurrentHashMap.TreeNode r = null; + Short2ObjectConcurrentHashMap.TreeNode x = b; + + while (x != null) { + Short2ObjectConcurrentHashMap.TreeNode next = (Short2ObjectConcurrentHashMap.TreeNode)x.next; + x.left = x.right = null; + if (r == null) { + x.parent = null; + x.red = false; + r = x; + } else { + short k = x.key; + int h = x.hash; + Class kc = null; + Short2ObjectConcurrentHashMap.TreeNode p = r; + + int dir; + Short2ObjectConcurrentHashMap.TreeNode xp; + do { + short pk = p.key; + int ph = p.hash; + if (p.hash > h) { + dir = -1; + } else if (ph < h) { + dir = 1; + } else if ((dir = Short.compare(k, pk)) == 0) { + dir = this.tieBreakOrder(k, pk); + } + + xp = p; + } while ((p = dir <= 0 ? p.left : p.right) != null); + + x.parent = xp; + if (dir <= 0) { + xp.left = x; + } else { + xp.right = x; + } + + r = this.balanceInsertion(r, x); + } + + x = next; + } + + this.root = r; + + assert this.checkInvariants(this.root); + } + + protected final void lockRoot() { + if (!U.compareAndSwapInt(this, LOCKSTATE, 0, 1)) { + this.contendedLock(); + } + } + + protected final void unlockRoot() { + this.lockState = 0; + } + + protected final void contendedLock() { + boolean waiting = false; + + while (true) { + int s = this.lockState; + if ((this.lockState & -3) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, 1)) { + if (waiting) { + this.waiter = null; + } + + return; + } + } else if ((s & 2) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, s | 2)) { + waiting = true; + this.waiter = Thread.currentThread(); + } + } else if (waiting) { + LockSupport.park(this); + } + } + } + + @Override + protected final Short2ObjectConcurrentHashMap.Node find(int h, short k) { + if (k != this.EMPTY) { + Short2ObjectConcurrentHashMap.Node e = this.first; + + while (e != null) { + int s = this.lockState; + if ((this.lockState & 3) != 0) { + if (e.hash == h) { + short ek = e.key; + if (e.key == k || ek != this.EMPTY && k == ek) { + return e; + } + } + + e = e.next; + } else if (U.compareAndSwapInt(this, LOCKSTATE, s, s + 4)) { + Short2ObjectConcurrentHashMap.TreeNode p; + try { + Short2ObjectConcurrentHashMap.TreeNode r = this.root; + p = this.root == null ? null : r.findTreeNode(h, k, null); + } finally { + if (U.getAndAddInt(this, LOCKSTATE, -4) == 6) { + Thread w = this.waiter; + if (this.waiter != null) { + LockSupport.unpark(w); + } + } + } + + return p; + } + } + } + + return null; + } + + protected final Short2ObjectConcurrentHashMap.TreeNode putTreeVal(int h, short k, V v) { + Class kc = null; + boolean searched = false; + Short2ObjectConcurrentHashMap.TreeNode p = this.root; + + while (true) { + if (p == null) { + this.first = this.root = new Short2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, h, k, v, null, null); + } else { + int ph = p.hash; + int dir; + if (p.hash > h) { + dir = -1; + } else if (ph < h) { + dir = 1; + } else { + short pk = p.key; + if (p.key == k || pk != this.EMPTY && k == pk) { + return p; + } + + if ((dir = Short.compare(k, pk)) == 0) { + if (!searched) { + searched = true; + Short2ObjectConcurrentHashMap.TreeNode ch = p.left; + Short2ObjectConcurrentHashMap.TreeNode q; + if (p.left != null && (q = ch.findTreeNode(h, k, kc)) != null) { + return q; + } + + ch = p.right; + if (p.right != null && (q = ch.findTreeNode(h, k, kc)) != null) { + return q; + } + } + + dir = this.tieBreakOrder(k, pk); + } + } + + Short2ObjectConcurrentHashMap.TreeNode xp = p; + if ((p = dir <= 0 ? p.left : p.right) != null) { + continue; + } + + Short2ObjectConcurrentHashMap.TreeNode f = this.first; + Short2ObjectConcurrentHashMap.TreeNode x; + this.first = x = new Short2ObjectConcurrentHashMap.TreeNode<>(this.EMPTY, h, k, v, f, xp); + if (f != null) { + f.prev = x; + } + + if (dir <= 0) { + xp.left = x; + } else { + xp.right = x; + } + + if (!xp.red) { + x.red = true; + } else { + this.lockRoot(); + + try { + this.root = this.balanceInsertion(this.root, x); + } finally { + this.unlockRoot(); + } + } + } + + assert this.checkInvariants(this.root); + + return null; + } + } + + protected final boolean removeTreeNode(Short2ObjectConcurrentHashMap.TreeNode p) { + Short2ObjectConcurrentHashMap.TreeNode next = (Short2ObjectConcurrentHashMap.TreeNode)p.next; + Short2ObjectConcurrentHashMap.TreeNode pred = p.prev; + if (pred == null) { + this.first = next; + } else { + pred.next = next; + } + + if (next != null) { + next.prev = pred; + } + + if (this.first == null) { + this.root = null; + return true; + } else { + Short2ObjectConcurrentHashMap.TreeNode r = this.root; + if (this.root != null && r.right != null) { + Short2ObjectConcurrentHashMap.TreeNode rl = r.left; + if (r.left != null && rl.left != null) { + this.lockRoot(); + + try { + Short2ObjectConcurrentHashMap.TreeNode pl = p.left; + Short2ObjectConcurrentHashMap.TreeNode pr = p.right; + Short2ObjectConcurrentHashMap.TreeNode replacement; + if (pl != null && pr != null) { + Short2ObjectConcurrentHashMap.TreeNode s = pr; + + while (true) { + Short2ObjectConcurrentHashMap.TreeNode sl = s.left; + if (s.left == null) { + boolean c = s.red; + s.red = p.red; + p.red = c; + Short2ObjectConcurrentHashMap.TreeNode sr = s.right; + Short2ObjectConcurrentHashMap.TreeNode pp = p.parent; + if (s == pr) { + p.parent = s; + s.right = p; + } else { + Short2ObjectConcurrentHashMap.TreeNode sp = s.parent; + if ((p.parent = sp) != null) { + if (s == sp.left) { + sp.left = p; + } else { + sp.right = p; + } + } + + if ((s.right = pr) != null) { + pr.parent = s; + } + } + + p.left = null; + if ((p.right = sr) != null) { + sr.parent = p; + } + + if ((s.left = pl) != null) { + pl.parent = s; + } + + if ((s.parent = pp) == null) { + r = s; + } else if (p == pp.left) { + pp.left = s; + } else { + pp.right = s; + } + + if (sr != null) { + replacement = sr; + } else { + replacement = p; + } + break; + } + + s = sl; + } + } else if (pl != null) { + replacement = pl; + } else if (pr != null) { + replacement = pr; + } else { + replacement = p; + } + + if (replacement != p) { + Short2ObjectConcurrentHashMap.TreeNode ppx = replacement.parent = p.parent; + if (ppx == null) { + r = replacement; + } else if (p == ppx.left) { + ppx.left = replacement; + } else { + ppx.right = replacement; + } + + p.left = p.right = p.parent = null; + } + + this.root = p.red ? r : this.balanceDeletion(r, replacement); + if (p == replacement) { + Short2ObjectConcurrentHashMap.TreeNode ppx = p.parent; + if (p.parent != null) { + if (p == ppx.left) { + ppx.left = null; + } else if (p == ppx.right) { + ppx.right = null; + } + + p.parent = null; + } + } + } finally { + this.unlockRoot(); + } + + assert this.checkInvariants(this.root); + + return false; + } + } + + return true; + } + } + + protected Short2ObjectConcurrentHashMap.TreeNode rotateLeft( + Short2ObjectConcurrentHashMap.TreeNode root, Short2ObjectConcurrentHashMap.TreeNode p + ) { + if (p != null) { + Short2ObjectConcurrentHashMap.TreeNode r = p.right; + if (p.right != null) { + Short2ObjectConcurrentHashMap.TreeNode rl; + if ((rl = p.right = r.left) != null) { + rl.parent = p; + } + + Short2ObjectConcurrentHashMap.TreeNode pp; + if ((pp = r.parent = p.parent) == null) { + root = r; + r.red = false; + } else if (pp.left == p) { + pp.left = r; + } else { + pp.right = r; + } + + r.left = p; + p.parent = r; + } + } + + return root; + } + + protected Short2ObjectConcurrentHashMap.TreeNode rotateRight( + Short2ObjectConcurrentHashMap.TreeNode root, Short2ObjectConcurrentHashMap.TreeNode p + ) { + if (p != null) { + Short2ObjectConcurrentHashMap.TreeNode l = p.left; + if (p.left != null) { + Short2ObjectConcurrentHashMap.TreeNode lr; + if ((lr = p.left = l.right) != null) { + lr.parent = p; + } + + Short2ObjectConcurrentHashMap.TreeNode pp; + if ((pp = l.parent = p.parent) == null) { + root = l; + l.red = false; + } else if (pp.right == p) { + pp.right = l; + } else { + pp.left = l; + } + + l.right = p; + p.parent = l; + } + } + + return root; + } + + protected Short2ObjectConcurrentHashMap.TreeNode balanceInsertion( + Short2ObjectConcurrentHashMap.TreeNode root, Short2ObjectConcurrentHashMap.TreeNode x + ) { + x.red = true; + + while (true) { + Short2ObjectConcurrentHashMap.TreeNode xp = x.parent; + if (x.parent == null) { + x.red = false; + return x; + } + + if (!xp.red) { + break; + } + + Short2ObjectConcurrentHashMap.TreeNode xpp = xp.parent; + if (xp.parent == null) { + break; + } + + Short2ObjectConcurrentHashMap.TreeNode xppl = xpp.left; + if (xp == xpp.left) { + Short2ObjectConcurrentHashMap.TreeNode xppr = xpp.right; + if (xpp.right != null && xppr.red) { + xppr.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } else { + if (x == xp.right) { + x = xp; + root = this.rotateLeft(root, xp); + xpp = (xp = xp.parent) == null ? null : xp.parent; + } + + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = this.rotateRight(root, xpp); + } + } + } + } else if (xppl != null && xppl.red) { + xppl.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } else { + if (x == xp.left) { + x = xp; + root = this.rotateRight(root, xp); + xpp = (xp = xp.parent) == null ? null : xp.parent; + } + + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = this.rotateLeft(root, xpp); + } + } + } + } + + return root; + } + + protected Short2ObjectConcurrentHashMap.TreeNode balanceDeletion( + Short2ObjectConcurrentHashMap.TreeNode root, Short2ObjectConcurrentHashMap.TreeNode x + ) { + while (x != null && x != root) { + Short2ObjectConcurrentHashMap.TreeNode xp = x.parent; + if (x.parent == null) { + x.red = false; + return x; + } + + if (x.red) { + x.red = false; + return root; + } + + Short2ObjectConcurrentHashMap.TreeNode xpl = xp.left; + if (xp.left == x) { + Short2ObjectConcurrentHashMap.TreeNode xpr = xp.right; + if (xp.right != null && xpr.red) { + xpr.red = false; + xp.red = true; + root = this.rotateLeft(root, xp); + xp = x.parent; + xpr = x.parent == null ? null : xp.right; + } + + if (xpr == null) { + x = xp; + } else { + Short2ObjectConcurrentHashMap.TreeNode sl = xpr.left; + Short2ObjectConcurrentHashMap.TreeNode sr = xpr.right; + if (sr != null && sr.red || sl != null && sl.red) { + if (sr == null || !sr.red) { + if (sl != null) { + sl.red = false; + } + + xpr.red = true; + root = this.rotateRight(root, xpr); + xp = x.parent; + xpr = x.parent == null ? null : xp.right; + } + + if (xpr != null) { + xpr.red = xp == null ? false : xp.red; + sr = xpr.right; + if (xpr.right != null) { + sr.red = false; + } + } + + if (xp != null) { + xp.red = false; + root = this.rotateLeft(root, xp); + } + + x = root; + } else { + xpr.red = true; + x = xp; + } + } + } else { + if (xpl != null && xpl.red) { + xpl.red = false; + xp.red = true; + root = this.rotateRight(root, xp); + xp = x.parent; + xpl = x.parent == null ? null : xp.left; + } + + if (xpl == null) { + x = xp; + } else { + Short2ObjectConcurrentHashMap.TreeNode sl = xpl.left; + Short2ObjectConcurrentHashMap.TreeNode sr = xpl.right; + if (sl != null && sl.red || sr != null && sr.red) { + if (sl == null || !sl.red) { + if (sr != null) { + sr.red = false; + } + + xpl.red = true; + root = this.rotateLeft(root, xpl); + xp = x.parent; + xpl = x.parent == null ? null : xp.left; + } + + if (xpl != null) { + xpl.red = xp == null ? false : xp.red; + sl = xpl.left; + if (xpl.left != null) { + sl.red = false; + } + } + + if (xp != null) { + xp.red = false; + root = this.rotateRight(root, xp); + } + + x = root; + } else { + xpl.red = true; + x = xp; + } + } + } + } + + return root; + } + + protected boolean checkInvariants(Short2ObjectConcurrentHashMap.TreeNode t) { + Short2ObjectConcurrentHashMap.TreeNode tp = t.parent; + Short2ObjectConcurrentHashMap.TreeNode tl = t.left; + Short2ObjectConcurrentHashMap.TreeNode tr = t.right; + Short2ObjectConcurrentHashMap.TreeNode tb = t.prev; + Short2ObjectConcurrentHashMap.TreeNode tn = (Short2ObjectConcurrentHashMap.TreeNode)t.next; + if (tb != null && tb.next != t) { + return false; + } else if (tn != null && tn.prev != t) { + return false; + } else if (tp != null && t != tp.left && t != tp.right) { + return false; + } else if (tl == null || tl.parent == t && tl.hash <= t.hash) { + if (tr == null || tr.parent == t && tr.hash >= t.hash) { + if (t.red && tl != null && tl.red && tr != null && tr.red) { + return false; + } else { + return tl != null && !this.checkInvariants(tl) ? false : tr == null || this.checkInvariants(tr); + } + } else { + return false; + } + } else { + return false; + } + } + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + U = (Unsafe)f.get(null); + Class k = Short2ObjectConcurrentHashMap.TreeBin.class; + LOCKSTATE = U.objectFieldOffset(k.getDeclaredField("lockState")); + } catch (Exception var2) { + throw new Error(var2); + } + } + } + + protected static final class TreeNode extends Short2ObjectConcurrentHashMap.Node { + public Short2ObjectConcurrentHashMap.TreeNode parent; + public Short2ObjectConcurrentHashMap.TreeNode left; + public Short2ObjectConcurrentHashMap.TreeNode right; + public Short2ObjectConcurrentHashMap.TreeNode prev; + public boolean red; + + public TreeNode(short empty, int hash, short key, V val, Short2ObjectConcurrentHashMap.Node next, Short2ObjectConcurrentHashMap.TreeNode parent) { + super(empty, hash, key, val, next); + this.parent = parent; + } + + @Override + protected Short2ObjectConcurrentHashMap.Node find(int h, short k) { + return this.findTreeNode(h, k, null); + } + + protected final Short2ObjectConcurrentHashMap.TreeNode findTreeNode(int h, short k, Class kc) { + if (k != this.EMPTY) { + Short2ObjectConcurrentHashMap.TreeNode p = this; + + do { + Short2ObjectConcurrentHashMap.TreeNode pl = p.left; + Short2ObjectConcurrentHashMap.TreeNode pr = p.right; + int ph = p.hash; + if (p.hash > h) { + p = pl; + } else if (ph < h) { + p = pr; + } else { + short pk = p.key; + if (p.key == k || pk != this.EMPTY && k == pk) { + return p; + } + + if (pl == null) { + p = pr; + } else if (pr == null) { + p = pl; + } else { + int dir; + if ((dir = Short.compare(k, pk)) != 0) { + p = dir < 0 ? pl : pr; + } else { + Short2ObjectConcurrentHashMap.TreeNode q; + if ((q = pr.findTreeNode(h, k, kc)) != null) { + return q; + } + + p = pl; + } + } + } + } while (p != null); + } + + return null; + } + } + + protected static final class ValueIterator extends Short2ObjectConcurrentHashMap.BaseIterator implements ObjectIterator, Enumeration { + public ValueIterator(Short2ObjectConcurrentHashMap.Node[] tab, int index, int size, int limit, Short2ObjectConcurrentHashMap map) { + super(tab, index, size, limit, map); + } + + @Override + public final V next() { + Short2ObjectConcurrentHashMap.Node p = this.next; + if (this.next == null) { + throw new NoSuchElementException(); + } else { + V v = p.val; + this.lastReturned = p; + this.advance(); + return v; + } + } + + @Override + public final V nextElement() { + return this.next(); + } + } + + protected static final class ValueSpliterator extends Short2ObjectConcurrentHashMap.Traverser implements ObjectSpliterator { + public long est; + + public ValueSpliterator(Short2ObjectConcurrentHashMap.Node[] tab, int size, int index, int limit, long est) { + super(tab, size, index, limit); + this.est = est; + } + + @Override + public ObjectSpliterator trySplit() { + int i = this.baseIndex; + int f = this.baseLimit; + int h; + return (h = this.baseIndex + this.baseLimit >>> 1) <= i + ? null + : new Short2ObjectConcurrentHashMap.ValueSpliterator<>(this.tab, this.baseSize, this.baseLimit = h, f, this.est >>>= 1); + } + + @Override + public void forEachRemaining(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node p; + while ((p = this.advance()) != null) { + action.accept(p.val); + } + } + } + + @Override + public boolean tryAdvance(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node p; + if ((p = this.advance()) == null) { + return false; + } else { + action.accept(p.val); + return true; + } + } + } + + @Override + public long estimateSize() { + return this.est; + } + + @Override + public int characteristics() { + return 4352; + } + } + + protected static final class ValuesView extends Short2ObjectConcurrentHashMap.CollectionView implements FastCollection, Serializable { + public static final long serialVersionUID = 2249069246763182397L; + + public ValuesView(Short2ObjectConcurrentHashMap map) { + super(map); + } + + @Override + public final boolean contains(Object o) { + return this.map.containsValue(o); + } + + @Override + public final boolean remove(Object o) { + if (o != null) { + Iterator it = this.iterator(); + + while (it.hasNext()) { + if (o.equals(it.next())) { + it.remove(); + return true; + } + } + } + + return false; + } + + @Override + public final ObjectIterator iterator() { + Short2ObjectConcurrentHashMap m = this.map; + Short2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Short2ObjectConcurrentHashMap.ValueIterator<>(t, f, 0, f, m); + } + + @Override + public final boolean add(V e) { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public ObjectSpliterator spliterator() { + Short2ObjectConcurrentHashMap m = this.map; + long n = m.sumCount(); + Short2ObjectConcurrentHashMap.Node[] t = m.table; + int f = m.table == null ? 0 : t.length; + return new Short2ObjectConcurrentHashMap.ValueSpliterator<>(t, f, 0, f, n < 0L ? 0L : n); + } + + @Override + public void forEach(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Short2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + action.accept(p.val); + } + } + } + } + + @Override + public void forEach( + FastCollection.FastConsumerD9 consumer, + A a, + double d1, + double d2, + double d3, + double d4, + double d5, + double d6, + double d7, + double d8, + double d9, + B b, + C c, + D d + ) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Short2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, a, d1, d2, d3, d4, d5, d6, d7, d8, d9, b, c, d); + } + } + } + } + + @Override + public void forEach( + FastCollection.FastConsumerD6 consumer, A a, double d1, double d2, double d3, double d4, double d5, double d6, B b, C c, D d + ) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Short2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, a, d1, d2, d3, d4, d5, d6, b, c, d); + } + } + } + } + + @Override + public void forEachWithFloat(FastCollection.FastConsumerF consumer, float ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Short2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + + @Override + public void forEachWithInt(FastCollection.FastConsumerI consumer, int ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Short2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + + @Override + public void forEachWithLong(FastCollection.FastConsumerL consumer, long ii) { + if (consumer == null) { + throw new NullPointerException(); + } else { + Short2ObjectConcurrentHashMap.Node[] tt = this.map.table; + if (this.map.table != null) { + Short2ObjectConcurrentHashMap.Node[] tab = tt; + Short2ObjectConcurrentHashMap.Node next = null; + Short2ObjectConcurrentHashMap.TableStack stack = null; + Short2ObjectConcurrentHashMap.TableStack spare = null; + int index = 0; + int baseIndex = 0; + int baseLimit = tt.length; + int baseSize = tt.length; + + while (true) { + Short2ObjectConcurrentHashMap.Node p = null; + p = next; + if (next != null) { + p = next.next; + } + + label78: { + while (true) { + if (p != null) { + next = p; + break label78; + } + + if (baseIndex >= baseLimit) { + break; + } + + Short2ObjectConcurrentHashMap.Node[] t = tab; + int n; + if (tab == null || (n = tab.length) <= index || index < 0) { + break; + } + + if ((p = Short2ObjectConcurrentHashMap.tabAt(tab, index)) != null && p.hash < 0) { + if (p instanceof Short2ObjectConcurrentHashMap.ForwardingNode) { + tab = ((Short2ObjectConcurrentHashMap.ForwardingNode)p).nextTable; + p = null; + Short2ObjectConcurrentHashMap.TableStack s = spare; + if (spare != null) { + spare = spare.next; + } else { + s = new Short2ObjectConcurrentHashMap.TableStack<>(); + } + + s.tab = t; + s.length = n; + s.index = index; + s.next = stack; + stack = s; + continue; + } + + if (p instanceof Short2ObjectConcurrentHashMap.TreeBin) { + p = ((Short2ObjectConcurrentHashMap.TreeBin)p).first; + } else { + p = null; + } + } + + if (stack == null) { + if ((index += baseSize) >= n) { + index = ++baseIndex; + } + } else { + while (true) { + Short2ObjectConcurrentHashMap.TableStack s = stack; + if (stack != null) { + int len = stack.length; + if ((index += stack.length) >= n) { + n = len; + index = stack.index; + tab = stack.tab; + stack.tab = null; + Short2ObjectConcurrentHashMap.TableStack anext = stack.next; + stack.next = spare; + stack = anext; + spare = s; + continue; + } + } + + if (stack == null && (index += baseSize) >= n) { + index = ++baseIndex; + } + break; + } + } + } + + next = null; + } + + if (p == null) { + break; + } + + consumer.accept(p.val, ii); + } + } + } + } + } +} diff --git a/src/com/hypixel/fastutil/shorts/Short2ObjectOperator.java b/src/com/hypixel/fastutil/shorts/Short2ObjectOperator.java new file mode 100644 index 0000000..62768f7 --- /dev/null +++ b/src/com/hypixel/fastutil/shorts/Short2ObjectOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.shorts; + +@FunctionalInterface +public interface Short2ObjectOperator { + V apply(short var1, V var2); +} diff --git a/src/com/hypixel/fastutil/shorts/Short2ShortOperator.java b/src/com/hypixel/fastutil/shorts/Short2ShortOperator.java new file mode 100644 index 0000000..a803012 --- /dev/null +++ b/src/com/hypixel/fastutil/shorts/Short2ShortOperator.java @@ -0,0 +1,6 @@ +package com.hypixel.fastutil.shorts; + +@FunctionalInterface +public interface Short2ShortOperator { + short apply(short var1, short var2); +} diff --git a/src/com/hypixel/fastutil/util/SneakyThrow.java b/src/com/hypixel/fastutil/util/SneakyThrow.java new file mode 100644 index 0000000..8b6743e --- /dev/null +++ b/src/com/hypixel/fastutil/util/SneakyThrow.java @@ -0,0 +1,18 @@ +package com.hypixel.fastutil.util; + +public class SneakyThrow { + public SneakyThrow() { + } + + public static RuntimeException sneakyThrow(Throwable t) { + if (t == null) { + throw new NullPointerException("t"); + } else { + return sneakyThrow0(t); + } + } + + private static T sneakyThrow0(Throwable t) throws T { + throw t; + } +} diff --git a/src/com/hypixel/fastutil/util/TLRUtil.java b/src/com/hypixel/fastutil/util/TLRUtil.java new file mode 100644 index 0000000..40f5537 --- /dev/null +++ b/src/com/hypixel/fastutil/util/TLRUtil.java @@ -0,0 +1,55 @@ +package com.hypixel.fastutil.util; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.concurrent.ThreadLocalRandom; +import sun.misc.Unsafe; + +public class TLRUtil { + private static final Unsafe UNSAFE; + private static final long PROBE; + + public TLRUtil() { + } + + public static void localInit() { + ThreadLocalRandom.current(); + } + + public static int getProbe() { + return UNSAFE.getInt(Thread.currentThread(), PROBE); + } + + public static int advanceProbe(int probe) { + probe ^= probe << 13; + probe ^= probe >>> 17; + probe ^= probe << 5; + UNSAFE.putInt(Thread.currentThread(), PROBE, probe); + return probe; + } + + static { + Unsafe instance; + try { + Field field = Unsafe.class.getDeclaredField("theUnsafe"); + field.setAccessible(true); + instance = (Unsafe)field.get(null); + } catch (Exception var5) { + try { + Constructor c = Unsafe.class.getDeclaredConstructor(); + c.setAccessible(true); + instance = c.newInstance(); + } catch (Exception var4) { + throw SneakyThrow.sneakyThrow(var4); + } + } + + UNSAFE = instance; + + try { + PROBE = UNSAFE.objectFieldOffset(Thread.class.getDeclaredField("threadLocalRandomProbe")); + } catch (NoSuchFieldException var3) { + throw SneakyThrow.sneakyThrow(var3); + } + } +} diff --git a/src/com/hypixel/hytale/LateMain.java b/src/com/hypixel/hytale/LateMain.java new file mode 100644 index 0000000..6303c79 --- /dev/null +++ b/src/com/hypixel/hytale/LateMain.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.backend.HytaleFileHandler; +import com.hypixel.hytale.logger.backend.HytaleLoggerBackend; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.HytaleServerConfig; +import com.hypixel.hytale.server.core.Options; +import io.sentry.Sentry; +import java.util.Map.Entry; +import java.util.logging.Level; + +public class LateMain { + public LateMain() { + } + + public static void lateMain(String[] args) { + try { + if (!Options.parse(args)) { + HytaleLogger.init(); + HytaleFileHandler.INSTANCE.enable(); + HytaleLogger.replaceStd(); + HytaleLoggerBackend.LOG_LEVEL_LOADER = name -> { + for (Entry e : Options.getOptionSet().valuesOf(Options.LOG_LEVELS)) { + if (name.equals(e.getKey())) { + return e.getValue(); + } + } + + HytaleServer hytaleServer = HytaleServer.get(); + if (hytaleServer != null) { + HytaleServerConfig config = hytaleServer.getConfig(); + if (config != null) { + Level configLevel = config.getLogLevels().get(name); + if (configLevel != null) { + return configLevel; + } + } + } + + return Options.getOptionSet().has(Options.SHUTDOWN_AFTER_VALIDATE) ? Level.WARNING : null; + }; + if (Options.getOptionSet().has(Options.SHUTDOWN_AFTER_VALIDATE)) { + HytaleLoggerBackend.reloadLogLevels(); + } + + new HytaleServer(); + } + } catch (Throwable var2) { + if (!SkipSentryException.hasSkipSentry(var2)) { + Sentry.captureException(var2); + } + + var2.printStackTrace(); + throw new RuntimeException("Failed to create HytaleServer", var2); + } + } +} diff --git a/src/com/hypixel/hytale/Main.java b/src/com/hypixel/hytale/Main.java new file mode 100644 index 0000000..c8ef1a2 --- /dev/null +++ b/src/com/hypixel/hytale/Main.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale; + +import com.hypixel.hytale.plugin.early.EarlyPluginLoader; +import com.hypixel.hytale.plugin.early.TransformingClassLoader; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Locale; +import javax.annotation.Nonnull; + +public final class Main { + public Main() { + } + + public static void main(String[] args) { + Locale.setDefault(Locale.ENGLISH); + System.setProperty("java.awt.headless", "true"); + System.setProperty("file.encoding", "UTF-8"); + EarlyPluginLoader.loadEarlyPlugins(args); + if (EarlyPluginLoader.hasTransformers()) { + launchWithTransformingClassLoader(args); + } else { + LateMain.lateMain(args); + } + } + + private static void launchWithTransformingClassLoader(@Nonnull String[] args) { + try { + URL[] urls = getClasspathUrls(); + ClassLoader appClassLoader = Main.class.getClassLoader(); + TransformingClassLoader transformingClassLoader = new TransformingClassLoader( + urls, EarlyPluginLoader.getTransformers(), appClassLoader.getParent(), appClassLoader + ); + Thread.currentThread().setContextClassLoader(transformingClassLoader); + Class lateMainClass = transformingClassLoader.loadClass("com.hypixel.hytale.LateMain"); + Method mainMethod = lateMainClass.getMethod("lateMain", String[].class); + mainMethod.invoke(null, args); + } catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException var6) { + throw new RuntimeException("Failed to launch with transforming classloader", var6); + } catch (InvocationTargetException var7) { + Throwable cause = var7.getCause(); + if (cause instanceof RuntimeException re) { + throw re; + } else if (cause instanceof Error err) { + throw err; + } else { + throw new RuntimeException("LateMain.lateMain() threw an exception", cause); + } + } + } + + private static URL[] getClasspathUrls() { + if (Main.class.getClassLoader() instanceof URLClassLoader urlClassLoader) { + return urlClassLoader.getURLs(); + } else { + ObjectArrayList urls = new ObjectArrayList<>(); + String classpath = System.getProperty("java.class.path"); + if (classpath != null && !classpath.isEmpty()) { + for (String pathStr : classpath.split(System.getProperty("path.separator"))) { + try { + Path path = Path.of(pathStr); + if (Files.exists(path)) { + urls.add(path.toUri().toURL()); + } + } catch (Exception var8) { + System.err.println("[EarlyPlugin] Failed to parse classpath entry: " + pathStr); + } + } + } + + return urls.toArray(URL[]::new); + } + } +} diff --git a/src/com/hypixel/hytale/assetstore/AssetConstants.java b/src/com/hypixel/hytale/assetstore/AssetConstants.java new file mode 100644 index 0000000..ccd1cac --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/AssetConstants.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.assetstore; + +public class AssetConstants { + public static final int EXPECTED_CHILDREN_PER_ASSET = 3; + public static final int EXPECTED_ASSETS_PER_PATH = 1; + public static final int EXPECTED_VALUES_PER_TAG = 3; + public static final int EXPECTED_ASSETS_PER_TAG = 3; + + public AssetConstants() { + } +} diff --git a/src/com/hypixel/hytale/assetstore/AssetExtraInfo.java b/src/com/hypixel/hytale/assetstore/AssetExtraInfo.java new file mode 100644 index 0000000..b72f410 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/AssetExtraInfo.java @@ -0,0 +1,279 @@ +package com.hypixel.hytale.assetstore; + +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.common.util.ArrayUtil; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.IntSets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetExtraInfo extends ExtraInfo { + @Nullable + private final Path assetPath; + private final AssetExtraInfo.Data data; + + public AssetExtraInfo(AssetExtraInfo.Data data) { + super(Integer.MAX_VALUE, AssetValidationResults::new); + this.assetPath = null; + this.data = data; + } + + public AssetExtraInfo(Path assetPath, AssetExtraInfo.Data data) { + super(Integer.MAX_VALUE, AssetValidationResults::new); + this.assetPath = assetPath; + this.data = data; + } + + @Nonnull + public String generateKey() { + return "*" + this.getKey() + "_" + this.peekKey('_'); + } + + public K getKey() { + return (K)this.getData().getKey(); + } + + @Nullable + public Path getAssetPath() { + return this.assetPath; + } + + public AssetExtraInfo.Data getData() { + return this.data; + } + + @Override + public void appendDetailsTo(@Nonnull StringBuilder sb) { + sb.append("Id: ").append(this.getKey()).append("\n"); + if (this.assetPath != null) { + sb.append("Path: ").append(this.assetPath).append("\n"); + } + } + + public AssetValidationResults getValidationResults() { + return (AssetValidationResults)super.getValidationResults(); + } + + @Nonnull + @Override + public String toString() { + return "AssetExtraInfo{assetPath=" + this.assetPath + ", data=" + this.data + "} " + super.toString(); + } + + public static class Data { + public static final char TAG_VALUE_SEPARATOR = '='; + private Map, List> containedAssets; + private Map, List>> containedRawAssets; + @Nullable + private AssetExtraInfo.Data containerData; + private Class> assetClass; + private Object key; + private Object parentKey; + private final Map rawTags = new HashMap<>(0); + private final Int2ObjectMap tagStorage = new Int2ObjectOpenHashMap<>(0); + private final Int2ObjectMap unmodifiableTagStorage = new Int2ObjectOpenHashMap<>(0); + private final IntSet expandedTagStorage = new IntOpenHashSet(0); + private final IntSet unmodifiableExpandedTagStorage = IntSets.unmodifiable(this.expandedTagStorage); + + public Data(Class> assetClass, K key, K parentKey) { + this.assetClass = assetClass; + this.key = key; + this.parentKey = parentKey; + } + + public Data(@Nullable AssetExtraInfo.Data containerData, Class> aClass, K key, K parentKey, boolean inheritContainerTags) { + this(aClass, key, parentKey); + this.containerData = containerData; + if (containerData != null && inheritContainerTags) { + this.putTags(containerData.rawTags); + } + } + + public Class> getAssetClass() { + return this.assetClass; + } + + public Object getKey() { + return this.key; + } + + public Object getParentKey() { + return this.parentKey; + } + + @Nonnull + public AssetExtraInfo.Data getRootContainerData() { + AssetExtraInfo.Data temp = this; + + while (temp.containerData != null) { + temp = temp.containerData; + } + + return temp; + } + + @Nullable + public AssetExtraInfo.Data getContainerData() { + return this.containerData; + } + + @Nullable + public K getContainerKey(Class> aClass) { + if (this.containerData == null) { + return null; + } else { + return (K)(this.containerData.assetClass.equals(aClass) ? this.containerData.key : this.containerData.getContainerKey(aClass)); + } + } + + public void putTags(@Nonnull Map tags) { + for (Entry entry : tags.entrySet()) { + String tag = entry.getKey().intern(); + this.rawTags.merge(tag, entry.getValue(), ArrayUtil::combine); + IntSet tagIndexes = this.ensureTag(tag); + + for (String value : entry.getValue()) { + tagIndexes.add(AssetRegistry.getOrCreateTagIndex(value)); + this.ensureTag(value); + this.rawTags.putIfAbsent(value, ArrayUtil.EMPTY_STRING_ARRAY); + String valueTag = (tag + "=" + value).intern(); + this.rawTags.putIfAbsent(valueTag, ArrayUtil.EMPTY_STRING_ARRAY); + this.ensureTag(valueTag); + } + } + } + + @Nonnull + public Map getRawTags() { + return Collections.unmodifiableMap(this.rawTags); + } + + @Nonnull + public Int2ObjectMap getTags() { + return Int2ObjectMaps.unmodifiable(this.unmodifiableTagStorage); + } + + @Nonnull + public IntSet getExpandedTagIndexes() { + return this.unmodifiableExpandedTagStorage; + } + + public IntSet getTag(int tagIndex) { + return this.unmodifiableTagStorage.getOrDefault(tagIndex, IntSets.EMPTY_SET); + } + + public , M extends AssetMap> void addContainedAsset(Class assetClass, T asset) { + if (this.containedAssets == null) { + this.containedAssets = new HashMap<>(); + } + + this.containedAssets.computeIfAbsent(assetClass, k -> new ArrayList<>()).add(asset); + } + + public , M extends AssetMap> void addContainedAsset(Class assetClass, RawAsset rawAsset) { + if (this.containedRawAssets == null) { + this.containedRawAssets = new HashMap<>(); + } + + this.containedRawAssets.computeIfAbsent(assetClass, k -> new ArrayList<>()).add(rawAsset); + } + + public void fetchContainedAssets(K key, @Nonnull Map, Map>> containedAssets) { + if (this.containedAssets != null) { + for (Entry, List> entry : this.containedAssets.entrySet()) { + containedAssets.computeIfAbsent(entry.getKey(), k -> new HashMap<>()).computeIfAbsent(key, k -> new ArrayList<>(3)).addAll(entry.getValue()); + } + } + } + + public void fetchContainedRawAssets(K key, @Nonnull Map, Map>>> containedAssets) { + if (this.containedRawAssets != null) { + for (Entry, List>> entry : this.containedRawAssets.entrySet()) { + containedAssets.computeIfAbsent(entry.getKey(), k -> new HashMap<>()).computeIfAbsent(key, k -> new ArrayList<>(3)).addAll(entry.getValue()); + } + } + } + + public , M extends AssetMap> boolean containsAsset(Class tClass, K key) { + if (this.containedAssets != null) { + List assets = (List)this.containedAssets.get(tClass); + if (assets != null) { + Function keyFunction = AssetRegistry.getAssetStore(tClass).getKeyFunction(); + + for (T asset : assets) { + if (key.equals(keyFunction.apply(asset))) { + return true; + } + } + } + } + + if (this.containedRawAssets != null) { + List> rawAssets = (List>)this.containedRawAssets.get(tClass); + if (rawAssets != null) { + for (RawAsset rawAsset : rawAssets) { + if (key.equals(rawAsset.getKey())) { + return true; + } + } + } + } + + return false; + } + + public void loadContainedAssets(boolean reloading) { + if (this.containedAssets != null) { + for (Entry, List> entry : this.containedAssets.entrySet()) { + AssetRegistry.getAssetStore(entry.getKey()).loadAssets("Hytale:Hytale", entry.getValue(), AssetUpdateQuery.DEFAULT, reloading); + } + } + + if (this.containedRawAssets != null) { + for (Entry, List>> entry : this.containedRawAssets.entrySet()) { + AssetRegistry.getAssetStore(entry.getKey()).loadBuffersWithKeys("Hytale:Hytale", entry.getValue(), AssetUpdateQuery.DEFAULT, reloading); + } + } + } + + @Nonnull + private IntSet ensureTag(@Nonnull String tag) { + int idx = AssetRegistry.getOrCreateTagIndex(tag); + this.expandedTagStorage.add(idx); + return this.tagStorage.computeIfAbsent(idx, k -> { + IntSet set = new IntOpenHashSet(3); + this.unmodifiableTagStorage.put(k, IntSets.unmodifiable(set)); + return set; + }); + } + + @Nonnull + @Override + public String toString() { + return "Data{containedAssets=" + + this.containedRawAssets + + ", rawTags=" + + this.rawTags + + ", parent=" + + this.containerData + + ", assetClass=" + + this.assetClass + + ", key=" + + this.key + + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/assetstore/AssetHolder.java b/src/com/hypixel/hytale/assetstore/AssetHolder.java new file mode 100644 index 0000000..56735a2 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/AssetHolder.java @@ -0,0 +1,4 @@ +package com.hypixel.hytale.assetstore; + +public interface AssetHolder { +} diff --git a/src/com/hypixel/hytale/assetstore/AssetKeyValidator.java b/src/com/hypixel/hytale/assetstore/AssetKeyValidator.java new file mode 100644 index 0000000..037a84e --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/AssetKeyValidator.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.assetstore; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class AssetKeyValidator implements Validator { + private final Supplier> store; + + public AssetKeyValidator(Supplier> store) { + this.store = store; + } + + public AssetStore getStore() { + return this.store.get(); + } + + @Override + public void accept(K k, @Nonnull ValidationResults results) { + this.store.get().validate(k, results, results.getExtraInfo()); + } + + @Override + public void updateSchema(SchemaContext context, @Nonnull Schema target) { + target.setHytaleAssetRef(this.store.get().getAssetClass().getSimpleName()); + } +} diff --git a/src/com/hypixel/hytale/assetstore/AssetLoadResult.java b/src/com/hypixel/hytale/assetstore/AssetLoadResult.java new file mode 100644 index 0000000..a183d44 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/AssetLoadResult.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.assetstore; + +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import java.nio.file.Path; +import java.util.Map; +import java.util.Set; + +public class AssetLoadResult { + private final Map loadedAssets; + private final Map loadedKeyToPathMap; + private final Set failedToLoadKeys; + private final Set failedToLoadPaths; + private final Map, AssetLoadResult> childAssetResults; + + public AssetLoadResult( + Map loadedAssets, + Map loadedKeyToPathMap, + Set failedToLoadKeys, + Set failedToLoadPaths, + Map, AssetLoadResult> childAssetResults + ) { + this.loadedAssets = loadedAssets; + this.loadedKeyToPathMap = loadedKeyToPathMap; + this.failedToLoadKeys = failedToLoadKeys; + this.failedToLoadPaths = failedToLoadPaths; + this.childAssetResults = childAssetResults; + } + + public Map getLoadedAssets() { + return this.loadedAssets; + } + + public Map getLoadedKeyToPathMap() { + return this.loadedKeyToPathMap; + } + + public Set getFailedToLoadKeys() { + return this.failedToLoadKeys; + } + + public Set getFailedToLoadPaths() { + return this.failedToLoadPaths; + } + + public boolean hasFailed() { + if (this.failedToLoadKeys.isEmpty() && this.failedToLoadPaths.isEmpty()) { + for (AssetLoadResult result : this.childAssetResults.values()) { + if (result.hasFailed()) { + return true; + } + } + + return false; + } else { + return true; + } + } +} diff --git a/src/com/hypixel/hytale/assetstore/AssetMap.java b/src/com/hypixel/hytale/assetstore/AssetMap.java new file mode 100644 index 0000000..226b1ca --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/AssetMap.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.assetstore; + +import com.hypixel.hytale.assetstore.codec.AssetCodec; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class AssetMap> { + public AssetMap() { + } + + @Nullable + public abstract T getAsset(K var1); + + @Nullable + public abstract T getAsset(@Nonnull String var1, K var2); + + @Nullable + public abstract Path getPath(K var1); + + @Nullable + public abstract String getAssetPack(K var1); + + public abstract Set getKeys(Path var1); + + public abstract Set getChildren(K var1); + + public abstract int getAssetCount(); + + public abstract Map getAssetMap(); + + public abstract Map getPathMap(@Nonnull String var1); + + public abstract Set getKeysForTag(int var1); + + public abstract IntSet getTagIndexes(); + + public abstract int getTagCount(); + + protected abstract void clear(); + + protected abstract void putAll(@Nonnull String var1, AssetCodec var2, Map var3, Map var4, Map> var5); + + protected abstract Set remove(Set var1); + + protected abstract Set remove(@Nonnull String var1, Set var2, List> var3); + + public boolean requireReplaceOnRemove() { + return false; + } + + public abstract Set getKeysForPack(@Nonnull String var1); +} diff --git a/src/com/hypixel/hytale/assetstore/AssetPack.java b/src/com/hypixel/hytale/assetstore/AssetPack.java new file mode 100644 index 0000000..54a883d --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/AssetPack.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.assetstore; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetPack { + @Nonnull + private final String name; + @Nonnull + private final Path root; + @Nullable + private final FileSystem fileSystem; + private final boolean isImmutable; + private final PluginManifest manifest; + private final Path packLocation; + + public AssetPack(Path packLocation, @Nonnull String name, @Nonnull Path root, @Nullable FileSystem fileSystem, boolean isImmutable, PluginManifest manifest) { + this.name = name; + this.root = root; + this.fileSystem = fileSystem; + this.isImmutable = isImmutable; + this.manifest = manifest; + this.packLocation = packLocation; + } + + @Nonnull + public String getName() { + return this.name; + } + + @Nonnull + public Path getRoot() { + return this.root; + } + + @Nullable + public FileSystem getFileSystem() { + return this.fileSystem; + } + + public PluginManifest getManifest() { + return this.manifest; + } + + public boolean isImmutable() { + return this.isImmutable; + } + + public Path getPackLocation() { + return this.packLocation; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + AssetPack assetPack = (AssetPack)o; + return !this.name.equals(assetPack.name) ? false : this.root.equals(assetPack.root); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.name.hashCode(); + return 31 * result + this.root.hashCode(); + } + + @Nonnull + @Override + public String toString() { + return "AssetPack{name='" + this.name + "', root=" + this.root + ", fileSystem=" + this.fileSystem + "}"; + } +} diff --git a/src/com/hypixel/hytale/assetstore/AssetReferences.java b/src/com/hypixel/hytale/assetstore/AssetReferences.java new file mode 100644 index 0000000..0331fb8 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/AssetReferences.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.assetstore; + +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import java.util.Collections; +import java.util.Set; +import javax.annotation.Nonnull; + +public class AssetReferences> { + private final Class parentAssetClass; + private final Set parentKeys; + + public AssetReferences(Class parentAssetClass, Set parentKeys) { + this.parentAssetClass = parentAssetClass; + this.parentKeys = parentKeys; + } + + public Class getParentAssetClass() { + return this.parentAssetClass; + } + + public Set getParentKeys() { + return this.parentKeys; + } + + public , K> void addChildAssetReferences(Class tClass, K childKey) { + Class parentAssetClass = this.parentAssetClass; + AssetStore assetStore = AssetRegistry.getAssetStore(parentAssetClass); + + for (CK parentKey : this.parentKeys) { + assetStore.addChildAssetReferences(parentKey, tClass, Collections.singleton(childKey)); + } + } + + @Nonnull + @Override + public String toString() { + return "AssetReferences{parentAssetClass=" + this.parentAssetClass + ", parentKeys=" + this.parentKeys + "}"; + } +} diff --git a/src/com/hypixel/hytale/assetstore/AssetRegistry.java b/src/com/hypixel/hytale/assetstore/AssetRegistry.java new file mode 100644 index 0000000..9563a5b --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/AssetRegistry.java @@ -0,0 +1,145 @@ +package com.hypixel.hytale.assetstore; + +import com.hypixel.hytale.assetstore.event.RegisterAssetStoreEvent; +import com.hypixel.hytale.assetstore.event.RemoveAssetStoreEvent; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.event.IEventDispatcher; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.StampedLock; +import javax.annotation.Nonnull; + +public class AssetRegistry { + public static final ReadWriteLock ASSET_LOCK = new ReentrantReadWriteLock(); + public static boolean HAS_INIT = false; + public static final int TAG_NOT_FOUND = Integer.MIN_VALUE; + private static final Map, AssetStore> storeMap = new HashMap<>(); + private static final Map, AssetStore> storeMapUnmodifiable = Collections.unmodifiableMap(storeMap); + private static final AtomicInteger NEXT_TAG_INDEX = new AtomicInteger(); + private static final StampedLock TAG_LOCK = new StampedLock(); + private static final Object2IntMap TAG_MAP = new Object2IntOpenHashMap<>(); + private static final Object2IntMap CLIENT_TAG_MAP = new Object2IntOpenHashMap<>(); + + public AssetRegistry() { + } + + @Nonnull + public static Map, AssetStore> getStoreMap() { + return storeMapUnmodifiable; + } + + public static , M extends AssetMap> AssetStore getAssetStore(Class tClass) { + return (AssetStore)storeMap.get(tClass); + } + + @Nonnull + public static , M extends AssetMap, S extends AssetStore> S register(@Nonnull S assetStore) { + ASSET_LOCK.writeLock().lock(); + + try { + if (storeMap.putIfAbsent(assetStore.getAssetClass(), assetStore) != null) { + throw new IllegalArgumentException("Asset Store already exists for " + assetStore.getAssetClass()); + } + } finally { + ASSET_LOCK.writeLock().unlock(); + } + + IEventDispatcher dispatch = assetStore.getEventBus().dispatchFor(RegisterAssetStoreEvent.class); + if (dispatch.hasListener()) { + dispatch.dispatch(new RegisterAssetStoreEvent(assetStore)); + } + + return assetStore; + } + + public static , M extends AssetMap, S extends AssetStore> void unregister(@Nonnull S assetStore) { + ASSET_LOCK.writeLock().lock(); + + try { + storeMap.remove(assetStore.getAssetClass()); + } finally { + ASSET_LOCK.writeLock().unlock(); + } + + IEventDispatcher dispatch = assetStore.getEventBus().dispatchFor(RemoveAssetStoreEvent.class); + if (dispatch.hasListener()) { + dispatch.dispatch(new RemoveAssetStoreEvent(assetStore)); + } + } + + public static int getTagIndex(@Nonnull String tag) { + if (tag == null) { + throw new IllegalArgumentException("tag can't be null!"); + } else { + long stamp = TAG_LOCK.readLock(); + + int var3; + try { + var3 = TAG_MAP.getInt(tag); + } finally { + TAG_LOCK.unlockRead(stamp); + } + + return var3; + } + } + + public static int getOrCreateTagIndex(@Nonnull String tag) { + if (tag == null) { + throw new IllegalArgumentException("tag can't be null!"); + } else { + long stamp = TAG_LOCK.writeLock(); + + int var3; + try { + var3 = TAG_MAP.computeIfAbsent(tag.intern(), k -> NEXT_TAG_INDEX.getAndIncrement()); + } finally { + TAG_LOCK.unlockWrite(stamp); + } + + return var3; + } + } + + public static boolean registerClientTag(@Nonnull String tag) { + if (tag == null) { + throw new IllegalArgumentException("tag can't be null!"); + } else { + long stamp = TAG_LOCK.writeLock(); + + boolean var3; + try { + var3 = CLIENT_TAG_MAP.put(tag, TAG_MAP.computeIfAbsent(tag, k -> NEXT_TAG_INDEX.getAndIncrement())) == Integer.MIN_VALUE; + } finally { + TAG_LOCK.unlockWrite(stamp); + } + + return var3; + } + } + + @Nonnull + public static Object2IntMap getClientTags() { + long stamp = TAG_LOCK.readLock(); + + Object2IntOpenHashMap var2; + try { + var2 = new Object2IntOpenHashMap<>(CLIENT_TAG_MAP); + } finally { + TAG_LOCK.unlockRead(stamp); + } + + return var2; + } + + static { + TAG_MAP.defaultReturnValue(Integer.MIN_VALUE); + CLIENT_TAG_MAP.defaultReturnValue(Integer.MIN_VALUE); + } +} diff --git a/src/com/hypixel/hytale/assetstore/AssetStore.java b/src/com/hypixel/hytale/assetstore/AssetStore.java new file mode 100644 index 0000000..c2fb8e4 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/AssetStore.java @@ -0,0 +1,1761 @@ +package com.hypixel.hytale.assetstore; + +import com.hypixel.hytale.assetstore.codec.AssetCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.event.GenerateAssetsEvent; +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.assetstore.event.RemovedAssetsEvent; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.builder.BuilderField; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.exception.CodecValidationException; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import com.hypixel.hytale.codec.validation.validator.ArrayValidator; +import com.hypixel.hytale.codec.validation.validator.MapKeyValidator; +import com.hypixel.hytale.codec.validation.validator.MapValueValidator; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.event.IEventBus; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.backend.HytaleLoggerBackend; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.logger.util.GithubMessageUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import java.io.IOException; +import java.nio.file.FileVisitOption; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.BsonValue; + +public abstract class AssetStore, M extends AssetMap> { + public static boolean DISABLE_ASSET_COMPARE = true; + @Nonnull + protected final HytaleLogger logger; + @Nonnull + protected final Class kClass; + @Nonnull + protected final Class tClass; + protected final String path; + @Nonnull + protected final String extension; + protected final AssetCodec codec; + protected final Function keyFunction; + @Nonnull + protected final Set>> loadsAfter; + @Nonnull + protected final Set>> unmodifiableLoadsAfter; + @Nonnull + protected final Set>> loadsBefore; + protected final M assetMap; + protected final Function replaceOnRemove; + @Nonnull + protected final Predicate isUnknown; + protected final boolean unmodifiable; + protected final List preAddedAssets; + protected final Class> idProvider; + protected final Map>, Map>> childAssetsMap = new ConcurrentHashMap<>(); + @Nonnull + protected Set> loadedContainedAssetsFor = new HashSet<>(); + public static boolean DISABLE_DYNAMIC_DEPENDENCIES = false; + + public AssetStore(@Nonnull AssetStore.Builder builder) { + this.kClass = builder.kClass; + this.tClass = builder.tClass; + this.logger = HytaleLogger.get("AssetStore|" + this.tClass.getSimpleName()); + this.path = builder.path; + this.extension = builder.extension; + this.codec = builder.codec; + this.keyFunction = builder.keyFunction; + this.isUnknown = builder.isUnknown == null ? v -> false : builder.isUnknown; + this.loadsAfter = builder.loadsAfter; + this.unmodifiableLoadsAfter = Collections.unmodifiableSet(builder.loadsAfter); + this.loadsBefore = Collections.unmodifiableSet(builder.loadsBefore); + this.assetMap = builder.assetMap; + this.replaceOnRemove = builder.replaceOnRemove; + this.unmodifiable = builder.unmodifiable; + this.preAddedAssets = builder.preAddedAssets; + this.idProvider = builder.idProvider; + if (builder.replaceOnRemove == null && this.assetMap.requireReplaceOnRemove()) { + throw new IllegalArgumentException( + "AssetStore for " + + this.tClass.getSimpleName() + + " using an AssetMap of " + + this.assetMap.getClass().getSimpleName() + + " must use #setReplaceOnRemove" + ); + } + } + + protected abstract IEventBus getEventBus(); + + public abstract void addFileMonitor(@Nonnull String var1, Path var2); + + public abstract void removeFileMonitor(Path var1); + + protected abstract void handleRemoveOrUpdate(Set var1, Map var2, @Nonnull AssetUpdateQuery var3); + + @Nonnull + public Class getKeyClass() { + return this.kClass; + } + + @Nonnull + public Class getAssetClass() { + return this.tClass; + } + + public String getPath() { + return this.path; + } + + @Nonnull + public String getExtension() { + return this.extension; + } + + public AssetCodec getCodec() { + return this.codec; + } + + public Function getKeyFunction() { + return this.keyFunction; + } + + @Nonnull + public Set>> getLoadsAfter() { + return this.unmodifiableLoadsAfter; + } + + public M getAssetMap() { + return this.assetMap; + } + + public Function getReplaceOnRemove() { + return this.replaceOnRemove; + } + + public boolean isUnmodifiable() { + return this.unmodifiable; + } + + public List getPreAddedAssets() { + return this.preAddedAssets; + } + + public boolean hasLoadedContainedAssetsFor(Class x) { + return this.loadedContainedAssetsFor.contains(x); + } + + public Class> getIdProvider() { + return this.idProvider; + } + + @Nonnull + public HytaleLogger getLogger() { + return this.logger; + } + + public void simplifyLoadBeforeDependencies() { + for (Class> aClass : this.loadsBefore) { + AssetRegistry.getAssetStore((Class)aClass).loadsAfter.add(this.tClass); + } + } + + @Deprecated + public > void injectLoadsAfter(Class aClass) { + if (DISABLE_DYNAMIC_DEPENDENCIES) { + throw new IllegalArgumentException("Asset stores have already loaded! Injecting a dependency is now pointless."); + } else { + this.loadsAfter.add(aClass); + } + } + + @Nullable + public K decodeFilePathKey(@Nonnull Path path) { + String fileName = path.getFileName().toString(); + return this.decodeStringKey(fileName.substring(0, fileName.length() - this.extension.length())); + } + + @Nullable + public K decodeStringKey(String key) { + return (K)(this.codec.getKeyCodec().getChildCodec() == Codec.STRING ? key : this.codec.getKeyCodec().getChildCodec().decode(new BsonString(key))); + } + + @Nullable + public K transformKey(@Nullable Object o) { + if (o == null) { + return null; + } else { + return (K)(o.getClass().equals(this.kClass) ? o : this.decodeStringKey(o.toString())); + } + } + + public void validate(@Nullable K key, @Nonnull ValidationResults results, ExtraInfo extraInfo) { + if (key != null) { + if (this.assetMap.getAsset(key) == null) { + if (extraInfo instanceof AssetExtraInfo) { + for (AssetExtraInfo.Data data = ((AssetExtraInfo)extraInfo).getData(); data != null; data = data.getContainerData()) { + if (data.containsAsset(this.tClass, key)) { + return; + } + } + } + + results.fail("Asset '" + key + "' of type " + this.tClass.getName() + " doesn't exist!"); + } + } + } + + public void validateCodecDefaults() { + ExtraInfo extraInfo = new ExtraInfo(Integer.MAX_VALUE, AssetValidationResults::new); + this.codec.validateDefaults(extraInfo, new HashSet<>()); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(this.logger, "Default Asset Validation Failed!\n"); + } + + public void logDependencies() { + ExtraInfo extraInfo = new ExtraInfo(Integer.MAX_VALUE, AssetValidationResults::new); + HashSet> tested = new HashSet<>(); + this.codec.validateDefaults(extraInfo, tested); + Set>> assetClasses = new HashSet<>(); + Set>> maybeLateAssetClasses = new HashSet<>(); + + for (Codec other : tested) { + if (other instanceof BuilderCodec) { + for (BuilderCodec builderCodec = (BuilderCodec)other; builderCodec != null; builderCodec = builderCodec.getParent()) { + for (List> value : builderCodec.getEntries().values()) { + for (BuilderField field : value) { + if (field.supportsVersion(extraInfo.getVersion())) { + List> validators = field.getValidators(); + if (validators != null) { + for (Validator validator : validators) { + if (validator instanceof ArrayValidator arrayValidator) { + validator = arrayValidator.getValidator(); + } else if (validator instanceof MapKeyValidator arrayValidator) { + validator = arrayValidator.getKeyValidator(); + } else if (validator instanceof MapValueValidator arrayValidator) { + validator = arrayValidator.getValueValidator(); + } + + if (validator instanceof AssetKeyValidator assetKeyValidator) { + assetClasses.add(assetKeyValidator.getStore().getAssetClass()); + } + } + } + } + } + } + } + } else if (other instanceof ContainedAssetCodec containedAssetCodec) { + maybeLateAssetClasses.add((Class>)containedAssetCodec.getAssetClass()); + } + } + + HashSet missing = new HashSet<>(); + HashSet unused = new HashSet<>(); + + for (Class> assetClass : assetClasses) { + if (!this.loadsAfter.contains(assetClass)) { + missing.add(assetClass); + } + } + + for (Class> aClass : this.loadsAfter) { + if (!assetClasses.contains(aClass) && !maybeLateAssetClasses.contains(aClass)) { + unused.add(aClass); + } + } + + if (!missing.isEmpty()) { + this.logger.at(Level.WARNING).log("\nMissing Dependencies:" + missing.stream().map(Object::toString).collect(Collectors.joining("\n- ", "\n- ", ""))); + } + + if (!unused.isEmpty()) { + this.logger.at(Level.WARNING).log("\nUnused Dependencies:" + unused.stream().map(Object::toString).collect(Collectors.joining("\n- ", "\n- ", ""))); + } + } + + @Nonnull + public AssetLoadResult loadAssetsFromDirectory(@Nonnull String packKey, @Nonnull Path assetsPath) throws IOException { + if (this.unmodifiable) { + throw new UnsupportedOperationException("AssetStore is unmodifiable!"); + } else { + Objects.requireNonNull(assetsPath, "assetsPath can't be null"); + final ArrayList files = new ArrayList<>(); + Set optionsSet = Set.of(); + Files.walkFileTree(assetsPath, optionsSet, Integer.MAX_VALUE, new SimpleFileVisitor() { + @Nonnull + public FileVisitResult visitFile(@Nonnull Path file, @Nonnull BasicFileAttributes attrs) throws IOException { + if (attrs.isRegularFile() && file.toString().endsWith(AssetStore.this.extension)) { + files.add(file); + } + + return FileVisitResult.CONTINUE; + } + }); + return this.loadAssetsFromPaths(packKey, files); + } + } + + @Nonnull + public AssetLoadResult loadAssetsFromPaths(@Nonnull String packKey, @Nonnull List paths) { + return this.loadAssetsFromPaths(packKey, paths, AssetUpdateQuery.DEFAULT); + } + + @Nonnull + public AssetLoadResult loadAssetsFromPaths(@Nonnull String packKey, @Nonnull Collection paths, @Nonnull AssetUpdateQuery query) { + return this.loadAssetsFromPaths(packKey, paths, query, false); + } + + @Nonnull + public AssetLoadResult loadAssetsFromPaths( + @Nonnull String packKey, @Nonnull Collection paths, @Nonnull AssetUpdateQuery query, boolean forceLoadAll + ) { + if (this.unmodifiable) { + throw new UnsupportedOperationException("AssetStore is unmodifiable!"); + } else { + Objects.requireNonNull(paths, "paths can't be null"); + long start = System.nanoTime(); + Set documents = new HashSet<>(); + + for (Path path : paths) { + Path normalize = path.toAbsolutePath().normalize(); + Set keys = this.assetMap.getKeys(normalize); + if (keys != null) { + for (K key : keys) { + this.loadAllChildren(documents, key); + } + } + + documents.add(normalize); + this.loadAllChildren(documents, this.decodeFilePathKey(path)); + } + + List> rawAssets = new ArrayList<>(documents.size()); + + for (Path p : documents) { + rawAssets.add(new RawAsset<>(this.decodeFilePathKey(p), p)); + } + + Map loadedAssets = Collections.synchronizedMap(new Object2ObjectLinkedOpenHashMap<>()); + Map loadedKeyToPathMap = new ConcurrentHashMap<>(); + Set failedToLoadKeys = ConcurrentHashMap.newKeySet(); + Set failedToLoadPaths = ConcurrentHashMap.newKeySet(); + Map, AssetLoadResult> childAssetResults = new ConcurrentHashMap<>(); + this.loadAssets0(packKey, loadedAssets, rawAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, query, forceLoadAll, childAssetResults); + long end = System.nanoTime(); + long diff = end - start; + this.logger + .at(Level.FINE) + .log( + "Loaded %d and removed %s (%s total) of %s from %s files in %s", + loadedAssets.size(), + failedToLoadKeys.size(), + this.assetMap.getAssetCount(), + this.tClass.getSimpleName(), + paths.size(), + FormatUtil.nanosToString(diff) + ); + return new AssetLoadResult<>(loadedAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, childAssetResults); + } + } + + @Nonnull + public AssetLoadResult loadBuffersWithKeys( + @Nonnull String packKey, @Nonnull List> preLoaded, @Nonnull AssetUpdateQuery query, boolean forceLoadAll + ) { + long start = System.nanoTime(); + Set documents = new HashSet<>(); + + for (RawAsset document : preLoaded) { + this.loadAllChildren(documents, document.getKey()); + } + + List> rawAssets = new ArrayList<>(preLoaded.size() + documents.size()); + rawAssets.addAll(preLoaded); + + for (Path p : documents) { + rawAssets.add(new RawAsset<>(this.decodeFilePathKey(p), p)); + } + + Map loadedAssets = Collections.synchronizedMap(new Object2ObjectLinkedOpenHashMap<>()); + Map loadedKeyToPathMap = new ConcurrentHashMap<>(); + Set failedToLoadKeys = ConcurrentHashMap.newKeySet(); + Set failedToLoadPaths = ConcurrentHashMap.newKeySet(); + Map, AssetLoadResult> childAssetResults = new ConcurrentHashMap<>(); + this.loadAssets0(packKey, loadedAssets, rawAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, query, forceLoadAll, childAssetResults); + long end = System.nanoTime(); + long diff = end - start; + this.logger + .at(Level.FINE) + .log( + "Loaded %d and removed %s (%s total) of %s via loadBuffersWithKeys in %s", + loadedAssets.size(), + failedToLoadKeys.size(), + this.assetMap.getAssetCount(), + this.tClass.getSimpleName(), + FormatUtil.nanosToString(diff) + ); + return new AssetLoadResult<>(loadedAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, childAssetResults); + } + + @Nonnull + public AssetLoadResult loadAssets(@Nonnull String packKey, @Nonnull List assets) { + return this.loadAssets(packKey, assets, AssetUpdateQuery.DEFAULT); + } + + @Nonnull + public AssetLoadResult loadAssets(@Nonnull String packKey, @Nonnull List assets, @Nonnull AssetUpdateQuery query) { + return this.loadAssets(packKey, assets, query, false); + } + + @Nonnull + public AssetLoadResult loadAssets(@Nonnull String packKey, @Nonnull List assets, @Nonnull AssetUpdateQuery query, boolean forceLoadAll) { + if (this.unmodifiable) { + throw new UnsupportedOperationException("AssetStore is unmodifiable!"); + } else { + Objects.requireNonNull(assets, "assets can't be null"); + long start = System.nanoTime(); + Map loadedAssets = Collections.synchronizedMap(new Object2ObjectLinkedOpenHashMap<>()); + Set documents = new HashSet<>(); + this.loadAllChildren(loadedAssets, assets, documents); + List> rawAssets = new ArrayList<>(documents.size()); + + for (Path p : documents) { + rawAssets.add(new RawAsset<>(this.decodeFilePathKey(p), p)); + } + + Map loadedKeyToPathMap = new ConcurrentHashMap<>(); + Set failedToLoadKeys = ConcurrentHashMap.newKeySet(); + Set failedToLoadPaths = ConcurrentHashMap.newKeySet(); + Map, AssetLoadResult> childAssetResults = new ConcurrentHashMap<>(); + this.loadAssets0(packKey, loadedAssets, rawAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, query, forceLoadAll, childAssetResults); + long end = System.nanoTime(); + long diff = end - start; + this.logger + .at(Level.FINE) + .log( + "Loaded %d and removed %s (%s total) of %s via loadAssets in %s", + loadedAssets.size(), + failedToLoadKeys.size(), + this.assetMap.getAssetCount(), + this.tClass.getSimpleName(), + FormatUtil.nanosToString(diff) + ); + return new AssetLoadResult<>(loadedAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, childAssetResults); + } + } + + @Nonnull + public AssetLoadResult loadAssetsWithReferences(@Nonnull String packKey, @Nonnull Map>> assets) { + return this.loadAssetsWithReferences(packKey, assets, AssetUpdateQuery.DEFAULT); + } + + @Nonnull + public AssetLoadResult loadAssetsWithReferences( + @Nonnull String packKey, @Nonnull Map>> assets, @Nonnull AssetUpdateQuery query + ) { + return this.loadAssetsWithReferences(packKey, assets, query, false); + } + + @Nonnull + public AssetLoadResult loadAssetsWithReferences( + @Nonnull String packKey, @Nonnull Map>> assets, @Nonnull AssetUpdateQuery query, boolean forceLoadAll + ) { + if (this.unmodifiable) { + throw new UnsupportedOperationException("AssetStore is unmodifiable!"); + } else { + Objects.requireNonNull(assets, "assets can't be null"); + long start = System.nanoTime(); + Map loadedAssets = Collections.synchronizedMap(new Object2ObjectLinkedOpenHashMap<>()); + Set assetKeys = assets.keySet(); + Set documents = new HashSet<>(); + this.loadAllChildren(loadedAssets, assetKeys, documents); + List> rawAssets = new ArrayList<>(documents.size()); + + for (Path p : documents) { + rawAssets.add(new RawAsset<>(this.decodeFilePathKey(p), p)); + } + + Map loadedKeyToPathMap = new ConcurrentHashMap<>(); + Set failedToLoadKeys = ConcurrentHashMap.newKeySet(); + Set failedToLoadPaths = ConcurrentHashMap.newKeySet(); + Map, AssetLoadResult> childAssetResults = new ConcurrentHashMap<>(); + this.loadAssets0(packKey, loadedAssets, rawAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, query, forceLoadAll, childAssetResults); + + for (Entry>> entry : assets.entrySet()) { + T asset = entry.getKey(); + Objects.requireNonNull(asset, "asset can't be null"); + K key = this.keyFunction.apply(asset); + if (key == null) { + throw new NullPointerException(String.format("key can't be null: %s", asset)); + } + + for (AssetReferences references : entry.getValue()) { + references.addChildAssetReferences(this.tClass, key); + } + } + + long end = System.nanoTime(); + long diff = end - start; + this.logger + .at(Level.FINE) + .log( + "Loaded %d and removed %s (%s total) of %s via loadAssetsWithReferences in %s", + loadedAssets.size(), + failedToLoadKeys.size(), + this.assetMap.getAssetCount(), + this.tClass.getSimpleName(), + FormatUtil.nanosToString(diff) + ); + return new AssetLoadResult<>(loadedAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, childAssetResults); + } + } + + @Nonnull + public Set removeAssetWithPaths(@Nonnull String packKey, @Nonnull List paths) { + return this.removeAssetWithPaths(packKey, paths, AssetUpdateQuery.DEFAULT); + } + + @Nonnull + public Set removeAssetWithPaths(@Nonnull String packKey, @Nonnull List paths, @Nonnull AssetUpdateQuery assetUpdateQuery) { + if (this.unmodifiable) { + throw new UnsupportedOperationException("AssetStore is unmodifiable!"); + } else { + Set allKeys = new HashSet<>(); + + for (Path path : paths) { + Path normalize = path.toAbsolutePath().normalize(); + Set keys = this.assetMap.getKeys(normalize); + if (keys != null) { + allKeys.addAll(keys); + } + } + + return this.removeAssets(packKey, false, allKeys, assetUpdateQuery); + } + } + + @Nonnull + public Set removeAssetWithPath(Path path) { + return this.removeAssetWithPath(path, AssetUpdateQuery.DEFAULT); + } + + @Nonnull + public Set removeAssetWithPath(Path path, @Nonnull AssetUpdateQuery assetUpdateQuery) { + if (this.unmodifiable) { + throw new UnsupportedOperationException("AssetStore is unmodifiable!"); + } else { + Path normalize = path.toAbsolutePath().normalize(); + Set keys = this.assetMap.getKeys(normalize); + return keys != null ? this.removeAssets("Hytale:Hytale", true, keys, assetUpdateQuery) : Collections.emptySet(); + } + } + + @Nonnull + public Set removeAssets(@Nonnull Collection keys) { + return this.removeAssets("Hytale:Hytale", true, keys, AssetUpdateQuery.DEFAULT); + } + + @Nonnull + public Set removeAssets(@Nonnull String packKey, boolean all, @Nonnull Collection keys, @Nonnull AssetUpdateQuery assetUpdateQuery) { + if (this.unmodifiable) { + throw new UnsupportedOperationException("AssetStore is unmodifiable!"); + } else { + long start = System.nanoTime(); + AssetRegistry.ASSET_LOCK.writeLock().lock(); + + List> pathsToReload; + try { + Set toBeRemoved = new HashSet<>(); + Set temp = new HashSet<>(); + + for (K key : keys) { + toBeRemoved.add(key); + Path path = this.assetMap.getPath(key); + if (path != null) { + this.logRemoveAsset(key, path); + } else { + this.logRemoveAsset(key, null); + } + + temp.clear(); + this.collectAllChildren(key, temp); + this.logRemoveChildren(key, temp); + toBeRemoved.addAll(temp); + } + + if (!toBeRemoved.isEmpty()) { + this.removeChildrenAssets(packKey, toBeRemoved); + pathsToReload = null; + if (all) { + this.assetMap.remove(toBeRemoved); + } else { + pathsToReload = new ArrayList<>(); + this.assetMap.remove(packKey, toBeRemoved, pathsToReload); + } + + if (this.replaceOnRemove != null) { + Map replacements = toBeRemoved.stream().collect(Collectors.toMap(Function.identity(), key -> { + T replacement = this.replaceOnRemove.apply((K)key); + Objects.requireNonNull(replacement, "Replacement can't be null!"); + K replacementKey = this.keyFunction.apply(replacement); + if (replacementKey == null) { + throw new NullPointerException(key.toString()); + } else { + if (!key.equals(replacementKey)) { + this.logger.at(Level.WARNING).log("Replacement key '%s' doesn't match key '%s'", replacementKey, key); + } + + return replacement; + } + })); + this.assetMap.putAll("Hytale:Hytale", this.codec, replacements, Collections.emptyMap(), Collections.emptyMap()); + this.handleRemoveOrUpdate(null, replacements, AssetUpdateQuery.DEFAULT); + this.loadContainedAssets("Hytale:Hytale", replacements.values(), new HashMap<>(), AssetUpdateQuery.DEFAULT, false); + } else { + this.handleRemoveOrUpdate(toBeRemoved, null, assetUpdateQuery); + } + + if (pathsToReload != null) { + for (Entry e : pathsToReload) { + if (e.getValue() instanceof Path) { + this.loadAssetsFromPaths(e.getKey(), List.of((Path)e.getValue())); + } else { + this.loadAssets(e.getKey(), List.of((T)e.getValue())); + } + } + } + + long end = System.nanoTime(); + long diff = end - start; + this.logger + .at(Level.INFO) + .log( + "Removed %d (%s total) of %s via removeAssets in %s", + toBeRemoved.size(), + this.assetMap.getAssetCount(), + this.tClass.getSimpleName(), + FormatUtil.nanosToString(diff) + ); + if (!toBeRemoved.isEmpty()) { + IEventDispatcher dispatcher = this.getEventBus().dispatchFor(RemovedAssetsEvent.class, this.tClass); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new RemovedAssetsEvent<>(this.tClass, this.assetMap, toBeRemoved, this.replaceOnRemove != null)); + } + } + + return toBeRemoved; + } + + pathsToReload = toBeRemoved; + } finally { + AssetRegistry.ASSET_LOCK.writeLock().unlock(); + } + + return pathsToReload; + } + } + + public void removeAssetPack(@Nonnull String name) { + AssetRegistry.ASSET_LOCK.writeLock().lock(); + + try { + Set assets = this.assetMap.getKeysForPack(name); + if (assets != null) { + this.removeAssets(name, false, assets, AssetUpdateQuery.DEFAULT); + return; + } + } finally { + AssetRegistry.ASSET_LOCK.writeLock().unlock(); + } + } + + public AssetLoadResult writeAssetToDisk(@Nonnull AssetPack pack, @Nonnull Map assetsByPath) throws IOException { + return this.writeAssetToDisk(pack, assetsByPath, AssetUpdateQuery.DEFAULT); + } + + public AssetLoadResult writeAssetToDisk(@Nonnull AssetPack pack, @Nonnull Map assetsByPath, @Nonnull AssetUpdateQuery query) throws IOException { + if (pack.isImmutable()) { + throw new IOException("Pack is immutable"); + } else { + for (Entry entry : assetsByPath.entrySet()) { + T asset = entry.getValue(); + K id = asset.getId(); + Path assetPath = pack.getRoot().resolve("Server").resolve(this.path).resolve(entry.getKey()); + AssetExtraInfo.Data data = this.codec.getData(asset); + Object parentId = data == null ? null : data.getParentKey(); + BsonValue bsonValue = this.codec + .encode(asset, new AssetExtraInfo(assetPath, new AssetExtraInfo.Data(this.tClass, id, this.transformKey(parentId)))); + Files.writeString(assetPath, bsonValue.toString(), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); + } + + return this.loadAssets(pack.getName(), new ArrayList<>(assetsByPath.values()), query); + } + } + + @Nonnull + public T decode(@Nonnull String packKey, @Nonnull K key, @Nonnull BsonDocument document) { + KeyedCodec parentCodec = this.codec.getParentCodec(); + K parentKey = parentCodec != null ? parentCodec.getOrNull(document) : null; + RawJsonReader reader = RawJsonReader.fromBuffer(document.toString().toCharArray()); + + try { + AssetExtraInfo extraInfo = new AssetExtraInfo<>(new AssetExtraInfo.Data(this.getAssetClass(), key, parentKey)); + if (parentKey == null) { + reader.consumeWhiteSpace(); + T asset = this.codec.decodeJsonAsset(reader, extraInfo); + if (asset == null) { + throw new NullPointerException(document.toString()); + } else { + extraInfo.getValidationResults().logOrThrowValidatorExceptions(this.logger); + this.logUnusedKeys(key, null, extraInfo); + return asset; + } + } else { + T parent = parentKey.equals("super") ? this.assetMap.getAsset(packKey, key) : this.assetMap.getAsset(parentKey); + if (parent == null) { + throw new NullPointerException(parentKey.toString()); + } else { + reader.consumeWhiteSpace(); + T asset = this.codec.decodeAndInheritJsonAsset(reader, parent, extraInfo); + if (asset == null) { + throw new NullPointerException(document.toString()); + } else { + extraInfo.getValidationResults().logOrThrowValidatorExceptions(this.logger); + this.logUnusedKeys(key, null, extraInfo); + return asset; + } + } + } + } catch (IOException var10) { + throw SneakyThrow.sneakyThrow(var10); + } + } + + public void addChildAssetReferences(K parentKey, Class> childAssetClass, @Nonnull Set childKeys) { + this.childAssetsMap + .computeIfAbsent(childAssetClass, k -> new ConcurrentHashMap<>()) + .computeIfAbsent(parentKey, k -> ConcurrentHashMap.newKeySet()) + .addAll(childKeys); + } + + protected void loadAssets0( + @Nonnull String packKey, + @Nonnull Map loadedAssets, + @Nonnull List> preLoaded, + @Nonnull Map loadedKeyToPathMap, + @Nonnull Set failedToLoadKeys, + @Nonnull Set failedToLoadPaths, + @Nonnull AssetUpdateQuery query, + boolean forceLoadAll, + @Nonnull Map, AssetLoadResult> childAssetResults + ) { + Map> loadedAssetChildren = new ConcurrentHashMap<>(); + this.decodeAssets( + packKey, preLoaded, loadedAssets, loadedKeyToPathMap, loadedAssetChildren, failedToLoadKeys, failedToLoadPaths, this.assetMap, query, forceLoadAll + ); + AssetRegistry.ASSET_LOCK.writeLock().lock(); + + try { + IEventDispatcher generateDispatcher = this.getEventBus().dispatchFor(GenerateAssetsEvent.class, this.tClass); + if (generateDispatcher.hasListener()) { + generateDispatcher.dispatch(new GenerateAssetsEvent<>(this.tClass, this.assetMap, loadedAssets, loadedAssetChildren)); + } + + Map toBeRemovedMap = new HashMap<>(); + Set temp = new HashSet<>(); + + for (K key : failedToLoadKeys) { + if (toBeRemovedMap.putIfAbsent(key, key) == null) { + this.logRemoveAsset(key, null); + temp.clear(); + this.collectAllChildren(key, temp); + + for (K k : temp) { + toBeRemovedMap.putIfAbsent(k, key); + } + } + } + + for (Path path : failedToLoadPaths) { + Set keys = this.assetMap.getKeys(path); + if (keys != null) { + for (K keyx : keys) { + if (toBeRemovedMap.putIfAbsent(keyx, keyx) == null) { + this.logRemoveAsset(keyx, path); + temp.clear(); + this.collectAllChildren(keyx, temp); + + for (K k : temp) { + toBeRemovedMap.putIfAbsent(k, keyx); + } + } + } + } + } + + this.assetMap.putAll(packKey, this.codec, loadedAssets, loadedKeyToPathMap, loadedAssetChildren); + Set toBeRemoved = toBeRemovedMap.keySet(); + if (!toBeRemoved.isEmpty()) { + this.logRemoveChildren(toBeRemovedMap); + this.removeChildrenAssets(packKey, toBeRemoved); + } + + if (this.replaceOnRemove != null && !toBeRemoved.isEmpty()) { + Map replacements = toBeRemoved.stream() + .filter(kx -> this.assetMap.getAsset((K)kx) != null) + .collect(Collectors.toMap(Function.identity(), keyxx -> { + T replacement = this.replaceOnRemove.apply((K)keyxx); + Objects.requireNonNull(replacement, "Replacement can't be null!"); + K replacementKey = this.keyFunction.apply(replacement); + if (replacementKey == null) { + throw new NullPointerException(keyxx.toString()); + } else { + if (!keyxx.equals(replacementKey)) { + this.logger.at(Level.WARNING).log("Replacement key '%s' doesn't match key '%s'", replacementKey, keyxx); + } + + return replacement; + } + })); + this.assetMap.putAll("Hytale:Hytale", this.codec, replacements, Collections.emptyMap(), Collections.emptyMap()); + replacements.putAll(loadedAssets); + this.handleRemoveOrUpdate(null, replacements, query); + } else { + this.assetMap.remove(toBeRemoved); + this.handleRemoveOrUpdate(toBeRemoved, loadedAssets, query); + } + + this.loadContainedAssets(packKey, loadedAssets.values(), childAssetResults, query, forceLoadAll); + this.reloadChildrenContainerAssets(packKey, loadedAssets); + if (!loadedAssets.isEmpty()) { + IEventDispatcher dispatcher = this.getEventBus().dispatchFor(LoadedAssetsEvent.class, this.tClass); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new LoadedAssetsEvent<>(this.tClass, this.assetMap, loadedAssets, false, query)); + } + } + + if (!toBeRemoved.isEmpty()) { + IEventDispatcher dispatcher = this.getEventBus().dispatchFor(RemovedAssetsEvent.class, this.tClass); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new RemovedAssetsEvent<>(this.tClass, this.assetMap, toBeRemoved, this.replaceOnRemove != null)); + } + } + } finally { + AssetRegistry.ASSET_LOCK.writeLock().unlock(); + } + } + + private void reloadChildrenContainerAssets(@Nonnull String packKey, @Nonnull Map loadedAssets) { + HashSet toReload = new HashSet<>(); + HashMap>, Set> toReloadTypes = new HashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + K key = entry.getKey(); + Path path = this.assetMap.getPath(key); + if (path != null) { + this.collectChildrenInDifferentFile(key, path, toReload, toReloadTypes, loadedAssets.keySet()); + } + } + + AssetUpdateQuery query = null; + if (!toReload.isEmpty()) { + query = new AssetUpdateQuery(true, AssetUpdateQuery.RebuildCache.DEFAULT); + this.loadAssetsFromPaths(packKey, toReload, query, true); + } + + if (!toReloadTypes.isEmpty()) { + if (query == null) { + query = new AssetUpdateQuery(true, AssetUpdateQuery.RebuildCache.DEFAULT); + } + + for (Entry>, Set> entryx : toReloadTypes.entrySet()) { + AssetStore assetStore = AssetRegistry.getAssetStore((Class)entryx.getKey()); + assetStore.loadAssetsFromPaths(packKey, entryx.getValue(), query, true); + } + } + } + + private void collectChildrenInDifferentFile( + K key, @Nonnull Path path, @Nonnull Set paths, @Nonnull Map>, Set> typedPaths, @Nonnull Set ignore + ) { + for (K child : this.assetMap.getChildren(key)) { + if (!ignore.contains(child)) { + Path childPath = this.assetMap.getPath(child); + if (childPath != null && !path.equals(childPath)) { + paths.add(childPath); + } else { + AssetExtraInfo.Data data = this.codec.getData(this.assetMap.getAsset(child)); + AssetExtraInfo.Data root = data != null ? data.getRootContainerData() : null; + if (root != null) { + if (root.getAssetClass() == this.tClass) { + K rootKey = (K)root.getKey(); + if (ignore.contains(rootKey)) { + continue; + } + + Path rootPath = this.assetMap.getPath(rootKey); + if (!path.equals(rootPath)) { + paths.add(rootPath); + continue; + } + } else { + Class assetClass = root.getAssetClass(); + if (assetClass == null) { + continue; + } + + AssetStore assetStore = AssetRegistry.getAssetStore(assetClass); + Path rootPath = assetStore.getAssetMap().getPath(root.getKey()); + if (rootPath != null) { + typedPaths.computeIfAbsent(assetClass, k -> new HashSet<>()).add(rootPath); + continue; + } + } + } + + this.collectChildrenInDifferentFile(child, path, paths, typedPaths, ignore); + } + } + } + } + + protected void removeChildrenAssets(@Nonnull String packKey, @Nonnull Set toBeRemoved) { + for (Entry>, Map>> entry : this.childAssetsMap.entrySet()) { + Class k = entry.getKey(); + Map> value = entry.getValue(); + Set allChildKeys = null; + + for (K key : toBeRemoved) { + Set childKeys = value.remove(key); + if (childKeys != null) { + if (allChildKeys == null) { + allChildKeys = new HashSet<>(); + } + + allChildKeys.addAll(childKeys); + } + } + + if (allChildKeys != null && !allChildKeys.isEmpty()) { + AssetRegistry.getAssetStore(k).removeAssets(packKey, false, allChildKeys, AssetUpdateQuery.DEFAULT); + } + } + } + + protected void loadContainedAssets( + @Nonnull String packKey, + @Nonnull Collection assets, + @Nonnull Map, AssetLoadResult> childAssetsResults, + @Nonnull AssetUpdateQuery query, + boolean forceLoadAll + ) { + Map, Map>> containedAssetsByClass = new HashMap<>(); + + for (T t : assets) { + AssetExtraInfo.Data data = this.codec.getData(t); + if (data != null) { + data.fetchContainedAssets(this.keyFunction.apply(t), containedAssetsByClass); + } + } + + for (Entry, Map>> entry : containedAssetsByClass.entrySet()) { + Class assetClass = entry.getKey(); + Map> containedAssets = entry.getValue(); + AssetStore assetStore = AssetRegistry.getAssetStore((Class)assetClass); + this.loadedContainedAssetsFor.add(assetClass); + List childList = new ArrayList<>(); + + for (Entry> containedEntry : containedAssets.entrySet()) { + K key = containedEntry.getKey(); + + for (Object contained : containedEntry.getValue()) { + Object containedKey = assetStore.getKeyFunction().apply(contained); + this.childAssetsMap + .computeIfAbsent(assetStore.getAssetClass(), k -> new ConcurrentHashMap<>()) + .computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet()) + .add(containedKey); + childList.add(contained); + } + } + + AssetLoadResult result = assetStore.loadAssets(packKey, (List)childList, query, forceLoadAll); + childAssetsResults.put(assetClass, result); + } + + Map, Map>>> containedRawAssetsByClass = new HashMap<>(); + + for (T tx : assets) { + AssetExtraInfo.Data data = this.codec.getData(tx); + if (data != null) { + data.fetchContainedRawAssets(this.keyFunction.apply(tx), containedRawAssetsByClass); + } + } + + for (Entry, Map>>> entry : containedRawAssetsByClass.entrySet()) { + Class assetClass = entry.getKey(); + Map>> containedAssets = entry.getValue(); + AssetStore assetStore = AssetRegistry.getAssetStore((Class)assetClass); + this.loadedContainedAssetsFor.add(assetClass); + List> childList = new ArrayList<>(); + + for (Entry>> containedEntry : containedAssets.entrySet()) { + K key = containedEntry.getKey(); + + for (RawAsset contained : containedEntry.getValue()) { + Object containedKey = contained.getKey(); + this.childAssetsMap + .computeIfAbsent(assetStore.getAssetClass(), k -> new ConcurrentHashMap<>()) + .computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet()) + .add(containedKey); + + RawAsset resolvedContained = switch (contained.getContainedAssetMode()) { + case NONE, GENERATE_ID, INJECT_PARENT, INHERIT_ID -> contained; + case INHERIT_ID_AND_PARENT -> { + Object parentKey = contained.getParentKey(); + if (parentKey == null) { + yield contained; + } else if (assetStore.getAssetMap().getAsset(parentKey) == null && !containedAssets.containsKey(parentKey)) { + this.logger + .at(Level.WARNING) + .log("Failed to find inherited parent asset %s (%s) for %s", parentKey, assetStore.getAssetClass().getSimpleName(), containedKey); + yield contained.withResolveKeys(containedKey, null); + } else { + yield contained; + } + } + }; + childList.add(resolvedContained); + } + } + + AssetLoadResult result = assetStore.loadBuffersWithKeys(packKey, childList, query, forceLoadAll); + childAssetsResults.put(assetClass, result); + } + } + + protected void decodeAssets( + @Nonnull String packKey, + @Nonnull List> rawAssets, + @Nonnull Map loadedAssets, + @Nonnull Map loadedKeyToPathMap, + @Nonnull Map> loadedAssetChildren, + @Nonnull Set failedToLoadKeys, + @Nonnull Set failedToLoadPaths, + @Nullable M assetMap, + @Nonnull AssetUpdateQuery query, + boolean forceLoadAll + ) { + if (!rawAssets.isEmpty()) { + Map> waitingForParent = new ConcurrentHashMap<>(); + CompletableFuture>[] futuresArr = new CompletableFuture[rawAssets.size()]; + + for (int i = 0; i < rawAssets.size(); i++) { + futuresArr[i] = this.executeAssetDecode( + loadedAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, assetMap, query, forceLoadAll, waitingForParent, rawAssets.get(i) + ); + } + + CompletableFuture.allOf(futuresArr).join(); + + for (CompletableFuture> future : futuresArr) { + DecodedAsset decodedAsset = future.getNow(null); + if (decodedAsset != null) { + loadedAssets.put(decodedAsset.getKey(), decodedAsset.getAsset()); + } + } + + List>> futures = new ArrayList<>(); + + while (!waitingForParent.isEmpty()) { + int processedAssets = 0; + + for (Entry> entry : waitingForParent.entrySet()) { + K key = entry.getKey(); + RawAsset rawAsset = entry.getValue(); + Path path = rawAsset.getPath(); + K parentKey = rawAsset.getParentKey(); + T parent = loadedAssets.get(parentKey); + if (parent == null) { + if (waitingForParent.containsKey(parentKey)) { + continue; + } + + if (assetMap == null) { + this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, path); + this.logger.at(Level.SEVERE).log("Failed to find parent '%s' for asset: %s, %s (assetMap was null)", parentKey, key, path); + continue; + } + + parent = parentKey.equals("super") ? assetMap.getAsset(packKey, key) : assetMap.getAsset(parentKey); + if (parent == null) { + this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, path); + this.logger.at(Level.SEVERE).log("Failed to find parent '%s' for asset: %s, %s", parentKey, key, path); + continue; + } + } + + if (this.isUnknown.test(parent)) { + this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, path); + this.logger.at(Level.SEVERE).log("Parent '%s' for asset: %s, %s is an unknown type", parentKey, key, path); + } else { + processedAssets++; + T finalParent = parent; + futures.add( + CompletableFuture.supplyAsync( + () -> { + char[] buffer = RawJsonReader.READ_BUFFER.get(); + RawJsonReader reader; + if (rawAsset.getBuffer() != null) { + reader = RawJsonReader.fromBuffer(rawAsset.getBuffer()); + } else { + try { + reader = RawJsonReader.fromPath(path, buffer); + } catch (IOException var26) { + this.logger.at(Level.SEVERE).withCause(var26).log("Failed to load asset: %s", path); + return null; + } + } + + DecodedAsset decodedAsset = null; + + try { + decodedAsset = this.decodeAssetWithParent0( + loadedAssets, + loadedKeyToPathMap, + loadedAssetChildren, + failedToLoadKeys, + failedToLoadPaths, + assetMap, + query, + forceLoadAll, + rawAsset, + reader, + finalParent + ); + } finally { + try { + if (rawAsset.getBuffer() != null) { + reader.close(); + } else { + char[] value = reader.closeAndTakeBuffer(); + if (value.length > buffer.length) { + RawJsonReader.READ_BUFFER.set(value); + } + } + } catch (IOException var24x) { + this.logger.at(Level.SEVERE).withCause(var24x).log("Failed to close asset reader: %s", path); + } + + if (decodedAsset == null) { + waitingForParent.remove(key); + } + } + + return decodedAsset; + } + ) + ); + } + } + + CompletableFuture>[] futuresArray = futures.toArray(CompletableFuture[]::new); + CompletableFuture.allOf(futuresArray).join(); + futures.clear(); + + for (CompletableFuture> futurex : futuresArray) { + DecodedAsset decodedAsset = futurex.getNow(null); + if (decodedAsset != null) { + loadedAssets.put(decodedAsset.getKey(), decodedAsset.getAsset()); + waitingForParent.remove(decodedAsset.getKey()); + } + } + + if (processedAssets == 0) { + for (Entry> entry : waitingForParent.entrySet()) { + K keyx = entry.getKey(); + Path assetPath = entry.getValue().getPath(); + K parentKeyx = entry.getValue().getParentKey(); + this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, keyx, assetPath); + this.logger.at(Level.SEVERE).log("Failed to find parent with key '%s' for asset: %s, %s", parentKeyx, keyx, assetPath); + } + break; + } + } + } + } + + @Nonnull + private CompletableFuture> executeAssetDecode( + @Nonnull Map loadedAssets, + @Nonnull Map loadedKeyToPathMap, + @Nonnull Set failedToLoadKeys, + @Nonnull Set failedToLoadPaths, + M assetMap, + @Nonnull AssetUpdateQuery query, + boolean forceLoadAll, + @Nonnull Map> waitingForParent, + @Nonnull RawAsset rawAsset + ) { + return CompletableFuture.supplyAsync(() -> { + RawJsonReader reader; + try { + reader = rawAsset.toRawJsonReader(RawJsonReader.READ_BUFFER::get); + } catch (IOException var20) { + this.logger.at(Level.SEVERE).withCause(var20).log("Failed to load asset: %s", rawAsset); + return null; + } + + AssetHolder holder; + try { + holder = this.decodeAsset0(loadedAssets, loadedKeyToPathMap, failedToLoadKeys, failedToLoadPaths, assetMap, query, forceLoadAll, rawAsset, reader); + if (holder instanceof RawAsset waiting) { + waitingForParent.put(waiting.getKey(), waiting); + } + } finally { + try { + if (rawAsset.getBuffer() != null) { + reader.close(); + } else { + char[] value = reader.closeAndTakeBuffer(); + if (value.length > RawJsonReader.READ_BUFFER.get().length) { + RawJsonReader.READ_BUFFER.set(value); + } + } + } catch (IOException var19) { + this.logger.at(Level.SEVERE).withCause(var19).log("Failed to close asset reader: %s", this.path); + } + } + + return holder instanceof DecodedAsset ? (DecodedAsset)holder : null; + }); + } + + @Nullable + private AssetHolder decodeAsset0( + @Nonnull Map loadedAssets, + @Nonnull Map loadedKeyToPathMap, + @Nonnull Set failedToLoadKeys, + @Nonnull Set failedToLoadPaths, + @Nullable M assetMap, + @Nonnull AssetUpdateQuery query, + boolean forceLoadAll, + @Nonnull RawAsset rawAsset, + @Nonnull RawJsonReader reader + ) { + Path assetPath = rawAsset.getPath(); + long start = System.nanoTime(); + K key = rawAsset.getKey(); + K parentKey = rawAsset.getParentKey(); + + try { + KeyedCodec keyCodec = this.codec.getKeyCodec(); + KeyedCodec parentCodec = this.codec.getParentCodec(); + if (key == null) { + if (rawAsset.getPath() != null) { + throw new IllegalArgumentException("Asset with path should infer its 'Id'!"); + } + + reader.mark(); + if (parentCodec != null && !rawAsset.isParentKeyResolved()) { + String s = RawJsonReader.seekToKeyFromObjectStart(reader, keyCodec.getKey(), parentCodec.getKey()); + if (s != null) { + if (keyCodec.getKey().equals(s)) { + key = keyCodec.getChildCodec().decodeJson(reader); + } else if (parentCodec.getKey().equals(s)) { + parentKey = parentCodec.getChildCodec().decodeJson(reader); + } + + s = RawJsonReader.seekToKeyFromObjectContinued(reader, keyCodec.getKey(), parentCodec.getKey()); + if (s != null) { + if (keyCodec.getKey().equals(s)) { + key = keyCodec.getChildCodec().decodeJson(reader); + } else if (parentCodec.getKey().equals(s)) { + parentKey = parentCodec.getChildCodec().decodeJson(reader); + } + } + } + } else if (RawJsonReader.seekToKey(reader, keyCodec.getKey())) { + key = keyCodec.getChildCodec().decodeJson(reader); + } + + if (key == null) { + throw new CodecException("Unable to find 'Id' in document!"); + } + + reader.reset(); + } else if (parentCodec != null && !rawAsset.isParentKeyResolved()) { + reader.mark(); + if (RawJsonReader.seekToKey(reader, parentCodec.getKey())) { + parentKey = parentCodec.getChildCodec().decodeJson(reader); + } + + reader.reset(); + } + + if (assetPath == null) { + assetPath = loadedKeyToPathMap.get(key); + } + + if (parentKey != null) { + return rawAsset.withResolveKeys(key, parentKey); + } + + AssetExtraInfo extraInfo = new AssetExtraInfo<>(assetPath, rawAsset.makeData(this.getAssetClass(), key, null)); + reader.consumeWhiteSpace(); + T asset = this.codec.decodeJsonAsset(reader, extraInfo); + if (asset == null) { + throw new NullPointerException(rawAsset.toString()); + } + + extraInfo.getValidationResults() + .logOrThrowValidatorExceptions( + this.logger, "Failed to validate asset!\n", assetPath == null ? rawAsset.getParentPath() : assetPath, rawAsset.getLineOffset() + ); + if (!DISABLE_ASSET_COMPARE && (query == null || !query.isDisableAssetCompare()) && assetMap != null && asset.equals(assetMap.getAsset(key))) { + this.logger.at(Level.INFO).log("Skipping asset that hasn't changed: %s", key); + return null; + } + + this.testKeyFormat(key, assetPath); + if (!forceLoadAll) { + } + + if (assetPath != null) { + loadedKeyToPathMap.put(key, assetPath); + } + + this.logUnusedKeys(key, assetPath, extraInfo); + this.logLoadedAsset(key, null, assetPath); + return new DecodedAsset<>(key, asset); + } catch (CodecValidationException var19) { + this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath); + this.logger.at(Level.SEVERE).log("Failed to validate asset: %s, %s, %s", key, assetPath, var19.getMessage()); + } catch (CodecException | IOException var20) { + this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath); + if (GithubMessageUtil.isGithub()) { + String pathStr = assetPath == null ? (key == null ? "unknown" : key.toString()) : assetPath.toString(); + String message; + if (var20 instanceof CodecException codecException) { + message = codecException.getMessage(); + if (codecException.getCause() != null) { + message = message + "\nCause: " + codecException.getCause().getMessage(); + } + } else { + message = var20.getMessage(); + } + + if (reader.getLine() == -1) { + HytaleLoggerBackend.rawLog(GithubMessageUtil.messageError(pathStr, message)); + } else { + HytaleLoggerBackend.rawLog(GithubMessageUtil.messageError(pathStr, reader.getLine(), reader.getColumn(), message)); + } + } + + this.logger.at(Level.SEVERE).withCause(new SkipSentryException(var20)).log("Failed to decode asset: %s, %s:\n%s", key, assetPath, reader); + } catch (Throwable var21) { + this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath); + if (GithubMessageUtil.isGithub()) { + String pathStrx = assetPath == null ? (key == null ? "unknown" : key.toString()) : assetPath.toString(); + String messagex = var21.getMessage(); + HytaleLoggerBackend.rawLog(GithubMessageUtil.messageError(pathStrx, messagex)); + } + + this.logger.at(Level.SEVERE).withCause(var21).log("Failed to decode asset: %s, %s", key, assetPath); + } + + return null; + } + + @Nullable + private DecodedAsset decodeAssetWithParent0( + @Nonnull Map loadedAssets, + @Nonnull Map loadedKeyToPathMap, + @Nonnull Map> loadedAssetChildren, + @Nonnull Set failedToLoadKeys, + @Nonnull Set failedToLoadPaths, + @Nullable M assetMap, + @Nonnull AssetUpdateQuery query, + boolean forceLoadAll, + @Nonnull RawAsset rawAsset, + @Nonnull RawJsonReader reader, + T parent + ) { + K key = rawAsset.getKey(); + if (!rawAsset.isParentKeyResolved()) { + throw new IllegalArgumentException("Parent key is required when decoding an asset with a parent!"); + } else { + K parentKey = rawAsset.getParentKey(); + Path assetPath = rawAsset.getPath(); + + try { + if (assetPath == null) { + assetPath = loadedKeyToPathMap.get(key); + } + + AssetExtraInfo extraInfo = new AssetExtraInfo<>(assetPath, rawAsset.makeData(this.getAssetClass(), key, parentKey)); + reader.consumeWhiteSpace(); + T asset = this.codec.decodeAndInheritJsonAsset(reader, parent, extraInfo); + if (asset == null) { + throw new NullPointerException(assetPath.toString()); + } + + extraInfo.getValidationResults().logOrThrowValidatorExceptions(this.logger); + if (key.equals(parentKey)) { + this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath); + this.logger.at(Level.SEVERE).log("Failed to load asset '%s' because it is its own parent!", key); + return null; + } + + if (!DISABLE_ASSET_COMPARE && (query == null || !query.isDisableAssetCompare()) && assetMap != null && asset.equals(assetMap.getAsset(key))) { + this.logger.at(Level.INFO).log("Skipping asset that hasn't changed: %s", key); + return null; + } + + this.testKeyFormat(key, assetPath); + if (!forceLoadAll) { + } + + loadedAssetChildren.computeIfAbsent(parentKey, k -> ConcurrentHashMap.newKeySet()).add(key); + if (assetPath != null) { + loadedKeyToPathMap.put(key, assetPath); + } + + this.logUnusedKeys(key, assetPath, extraInfo); + this.logLoadedAsset(key, parentKey, assetPath); + return new DecodedAsset<>(key, asset); + } catch (CodecValidationException var17) { + this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath); + this.logger.at(Level.SEVERE).log("Failed to decode asset: %s, %s, %s", key, assetPath, var17.getMessage()); + } catch (CodecException | IOException var18) { + this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath); + this.logger.at(Level.SEVERE).withCause(new SkipSentryException(var18)).log("Failed to decode asset: %s, %s:\n%s", key, assetPath, reader); + } catch (Exception var19) { + this.recordFailedToLoad(failedToLoadKeys, failedToLoadPaths, key, assetPath); + this.logger.at(Level.SEVERE).withCause(var19).log("Failed to decode asset: %s, %s", key, assetPath); + } + + return null; + } + } + + private void loadAllChildren(@Nonnull Map loadedAssets, @Nonnull Collection assetKeys, @Nonnull Set documents) { + for (T asset : assetKeys) { + Objects.requireNonNull(asset, "asset can't be null"); + K key = this.keyFunction.apply(asset); + if (key == null) { + throw new NullPointerException(String.format("key can't be null: %s", asset)); + } + + loadedAssets.put(key, asset); + if (this.loadAllChildren(documents, key)) { + StringBuilder sb = new StringBuilder(); + sb.append(key).append(":\n"); + this.logChildTree(sb, " ", key, new HashSet<>()); + this.logger.at(Level.SEVERE).log("Found a circular dependency when trying to collect all children!\n%s", sb); + } + } + } + + protected boolean loadAllChildren(@Nonnull Set documents, K key) { + Set set = this.assetMap.getChildren(key); + if (set == null) { + return false; + } else { + boolean circular = false; + + for (K child : set) { + Path childPath = this.assetMap.getPath(child); + if (childPath != null) { + if (documents.add(childPath)) { + circular |= this.loadAllChildren(documents, child); + } else { + circular = true; + } + } + } + + return circular; + } + } + + protected void collectAllChildren(K key, @Nonnull Set children) { + if (this.collectAllChildren0(key, children)) { + StringBuilder sb = new StringBuilder(); + sb.append(key).append(":\n"); + this.logChildTree(sb, " ", key, new HashSet<>()); + this.logger.at(Level.SEVERE).log("Found a circular dependency when trying to collect all children!\n%s", sb); + } + } + + private boolean collectAllChildren0(K key, @Nonnull Set children) { + Set set = this.assetMap.getChildren(key); + if (set == null) { + return false; + } else { + boolean circular = false; + + for (K child : set) { + if (children.add(child)) { + circular |= this.collectAllChildren0(child, children); + } else { + circular = true; + } + } + + return circular; + } + } + + protected void logChildTree(@Nonnull StringBuilder sb, String indent, K key, @Nonnull Set children) { + Set set = this.assetMap.getChildren(key); + if (set != null) { + for (K child : set) { + if (children.add(child)) { + sb.append(indent).append("- ").append(child).append('\n'); + this.logChildTree(sb, indent + " ", child, children); + } else { + sb.append(indent).append("- ").append(child).append('\n').append(indent).append(" ").append("** Circular **\n"); + } + } + } + } + + protected void logRemoveChildren(K parentKey, @Nonnull Set toBeRemoved) { + Path path = this.assetMap.getPath(parentKey); + + for (K child : toBeRemoved) { + Path childPath = this.assetMap.getPath(child); + if (childPath != null) { + if (path != null) { + this.logger.at(Level.WARNING).log("Removing child asset '%s' of removed asset '%s'", childPath, path); + } else { + this.logger.at(Level.WARNING).log("Removing child asset '%s' of removed asset '%s'", childPath, parentKey); + } + } else { + this.logger.at(Level.WARNING).log("Removing child asset '%s' of removed asset '%s'", child, parentKey); + } + } + } + + protected void logRemoveChildren(@Nonnull Map toBeRemoved) { + for (Entry entry : toBeRemoved.entrySet()) { + K child = entry.getKey(); + K parentKey = entry.getValue(); + Path childPath = this.assetMap.getPath(child); + if (childPath != null) { + Path path = this.assetMap.getPath(parentKey); + if (path != null) { + this.logger.at(Level.WARNING).log("Removing child asset '%s' of removed asset '%s'", childPath, path); + } else { + this.logger.at(Level.WARNING).log("Removing child asset '%s' of removed asset '%s'", childPath, parentKey); + } + } else { + this.logger.at(Level.WARNING).log("Removing child asset '%s' of removed asset '%s'", child, parentKey); + } + } + } + + protected void testKeyFormat(@Nonnull K key, @Nullable Path assetPath) { + String keyStr = key.toString(); + if (!StringUtil.isCapitalized(keyStr, '_')) { + String expected = StringUtil.capitalize(keyStr, '_'); + if (assetPath == null) { + this.logger.at(Level.WARNING).log("Asset key '%s' has incorrect format! Expected: '%s'", key, expected); + } else { + this.logger.at(Level.WARNING).log("Asset key '%s' for file '%s' has incorrect format! Expected: '%s'", key, assetPath, expected); + } + } + } + + public void logUnusedKeys(@Nonnull K key, @Nullable Path assetPath, @Nonnull AssetExtraInfo extraInfo) { + List unknownKeys = extraInfo.getUnknownKeys(); + if (!unknownKeys.isEmpty()) { + if (GithubMessageUtil.isGithub()) { + String pathStr = assetPath == null ? key.toString() : assetPath.toString(); + + for (int i = 0; i < unknownKeys.size(); i++) { + String unknownKey = unknownKeys.get(i); + HytaleLoggerBackend.rawLog(GithubMessageUtil.messageWarning(pathStr, "Unused key: " + unknownKey)); + } + } else if (assetPath != null) { + this.logger.at(Level.WARNING).log("Unused key(s) in '%s' file %s: %s", key, assetPath, String.join(", ", unknownKeys)); + } else { + this.logger.at(Level.WARNING).log("Unused key(s) in '%s': %s", key, String.join(", ", unknownKeys)); + } + } + } + + protected void logLoadedAsset(K key, @Nullable K parentKey, @Nullable Path path) { + if (path == null && parentKey == null) { + this.logger.at(Level.FINE).log("Loaded asset: %s", key); + } else if (path == null) { + this.logger.at(Level.FINE).log("Loaded asset: '%s' with parent '%s'", key, parentKey); + } else if (parentKey == null) { + this.logger.at(Level.FINE).log("Loaded asset: '%s' from '%s'", key, path); + } else { + this.logger.at(Level.FINE).log("Loaded asset: '%s' from '%s' with parent '%s'", key, path, parentKey); + } + } + + protected void logRemoveAsset(K key, @Nullable Path path) { + if (path == null) { + this.logger.at(Level.FINE).log("Removed asset: '%s'", key); + } else { + this.logger.at(Level.FINE).log("Removed asset: '%s' from '%s'", key, path); + } + } + + private void recordFailedToLoad(@Nonnull Set failedToLoadKeys, @Nonnull Set failedToLoadPaths, @Nullable K key, @Nullable Path path) { + if (key != null) { + failedToLoadKeys.add(key); + } + + if (path != null) { + failedToLoadPaths.add(path); + } + } + + @Nonnull + @Override + public String toString() { + return "AssetStore{tClass=" + this.tClass + "}"; + } + + protected abstract static class Builder, M extends AssetMap, B extends AssetStore.Builder> { + @Nonnull + protected final Class kClass; + @Nonnull + protected final Class tClass; + protected final M assetMap; + protected final Set>> loadsAfter = new HashSet<>(); + protected final Set>> loadsBefore = new HashSet<>(); + protected String path; + @Nonnull + protected String extension = ".json"; + protected AssetCodec codec; + protected Function keyFunction; + protected Function replaceOnRemove; + protected Predicate isUnknown; + protected boolean unmodifiable; + protected List preAddedAssets; + protected Class> idProvider; + + public Builder(Class kClass, Class tClass, M assetMap) { + this.kClass = Objects.requireNonNull(kClass, "key class can't be null!"); + this.tClass = Objects.requireNonNull(tClass, "asset class can't be null!"); + this.assetMap = assetMap; + } + + @Nonnull + public B setPath(String path) { + this.path = Objects.requireNonNull(path, "path can't be null!"); + return (B)this; + } + + @Nonnull + public B setExtension(@Nonnull String extension) { + Objects.requireNonNull(extension, "extension can't be null!"); + if (extension.length() >= 2 && extension.charAt(0) == '.') { + this.extension = extension; + return (B)this; + } else { + throw new IllegalArgumentException("Extension must start with '.' and have at least one character after"); + } + } + + @Nonnull + public B setCodec(AssetCodec codec) { + this.codec = Objects.requireNonNull(codec, "codec can't be null!"); + return (B)this; + } + + @Nonnull + public B setKeyFunction(Function keyFunction) { + this.keyFunction = Objects.requireNonNull(keyFunction, "keyFunction can't be null!"); + return (B)this; + } + + @Nonnull + public B setIsUnknown(Predicate isUnknown) { + this.isUnknown = Objects.requireNonNull(isUnknown, "isUnknown can't be null!"); + return (B)this; + } + + @Nonnull + @SafeVarargs + public final B loadsAfter(Class>... clazz) { + Collections.addAll(this.loadsAfter, clazz); + return (B)this; + } + + @Nonnull + @SafeVarargs + public final B loadsBefore(Class>... clazz) { + Collections.addAll(this.loadsBefore, clazz); + return (B)this; + } + + @Nonnull + public B setReplaceOnRemove(Function replaceOnRemove) { + this.replaceOnRemove = Objects.requireNonNull(replaceOnRemove, "replaceOnRemove can't be null!"); + return (B)this; + } + + @Nonnull + public B unmodifiable() { + this.unmodifiable = true; + return (B)this; + } + + @Nonnull + public B preLoadAssets(@Nonnull List list) { + if (this.preAddedAssets == null) { + this.preAddedAssets = new ArrayList<>(); + } + + this.preAddedAssets.addAll(list); + return (B)this; + } + + @Nonnull + public B setIdProvider(Class> provider) { + this.idProvider = provider; + return (B)this; + } + + public abstract AssetStore build(); + } +} diff --git a/src/com/hypixel/hytale/assetstore/AssetUpdateQuery.java b/src/com/hypixel/hytale/assetstore/AssetUpdateQuery.java new file mode 100644 index 0000000..372611a --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/AssetUpdateQuery.java @@ -0,0 +1,178 @@ +package com.hypixel.hytale.assetstore; + +import javax.annotation.Nonnull; + +public class AssetUpdateQuery { + public static final AssetUpdateQuery DEFAULT = new AssetUpdateQuery(AssetUpdateQuery.RebuildCache.DEFAULT); + public static final AssetUpdateQuery DEFAULT_NO_REBUILD = new AssetUpdateQuery(AssetUpdateQuery.RebuildCache.NO_REBUILD); + private final boolean disableAssetCompare; + private final AssetUpdateQuery.RebuildCache rebuildCache; + + public AssetUpdateQuery(boolean disableAssetCompare, AssetUpdateQuery.RebuildCache rebuildCache) { + this.disableAssetCompare = disableAssetCompare; + this.rebuildCache = rebuildCache; + } + + public AssetUpdateQuery(AssetUpdateQuery.RebuildCache rebuildCache) { + this(AssetStore.DISABLE_ASSET_COMPARE, rebuildCache); + } + + public boolean isDisableAssetCompare() { + return this.disableAssetCompare; + } + + @Nonnull + public AssetUpdateQuery.RebuildCache getRebuildCache() { + return this.rebuildCache; + } + + @Nonnull + @Override + public String toString() { + return "AssetUpdateQuery{rebuildCache=" + this.rebuildCache + "}"; + } + + public static class RebuildCache { + public static final AssetUpdateQuery.RebuildCache DEFAULT = new AssetUpdateQuery.RebuildCache(true, true, true, true, true, true); + public static final AssetUpdateQuery.RebuildCache NO_REBUILD = new AssetUpdateQuery.RebuildCache(false, false, false, false, false, false); + private final boolean blockTextures; + private final boolean models; + private final boolean modelTextures; + private final boolean mapGeometry; + private final boolean itemIcons; + private final boolean commonAssetsRebuild; + + public RebuildCache(boolean blockTextures, boolean models, boolean modelTextures, boolean mapGeometry, boolean itemIcons, boolean commonAssetsRebuild) { + this.blockTextures = blockTextures; + this.models = models; + this.modelTextures = modelTextures; + this.mapGeometry = mapGeometry; + this.itemIcons = itemIcons; + this.commonAssetsRebuild = commonAssetsRebuild; + } + + public boolean isBlockTextures() { + return this.blockTextures; + } + + public boolean isModels() { + return this.models; + } + + public boolean isModelTextures() { + return this.modelTextures; + } + + public boolean isMapGeometry() { + return this.mapGeometry; + } + + public boolean isItemIcons() { + return this.itemIcons; + } + + public boolean isCommonAssetsRebuild() { + return this.commonAssetsRebuild; + } + + @Nonnull + public AssetUpdateQuery.RebuildCacheBuilder toBuilder() { + return new AssetUpdateQuery.RebuildCacheBuilder( + this.blockTextures, this.models, this.modelTextures, this.mapGeometry, this.itemIcons, this.commonAssetsRebuild + ); + } + + @Nonnull + public static AssetUpdateQuery.RebuildCacheBuilder builder() { + return new AssetUpdateQuery.RebuildCacheBuilder(); + } + + @Nonnull + @Override + public String toString() { + return "RebuildCache{blockTextures=" + + this.blockTextures + + ", models=" + + this.models + + ", modelTextures=" + + this.modelTextures + + ", mapGeometry=" + + this.mapGeometry + + ", icons=" + + this.itemIcons + + ", commonAssetsRebuild=" + + this.commonAssetsRebuild + + "}"; + } + } + + public static class RebuildCacheBuilder { + private boolean blockTextures; + private boolean models; + private boolean modelTextures; + private boolean mapGeometry; + private boolean itemIcons; + private boolean commonAssetsRebuild; + + RebuildCacheBuilder() { + } + + RebuildCacheBuilder(boolean blockTextures, boolean models, boolean modelTextures, boolean mapGeometry, boolean itemIcons, boolean commonAssetsRebuild) { + this.blockTextures = blockTextures; + this.models = models; + this.modelTextures = modelTextures; + this.mapGeometry = mapGeometry; + this.itemIcons = itemIcons; + this.commonAssetsRebuild = commonAssetsRebuild; + } + + public void setBlockTextures(boolean blockTextures) { + this.blockTextures = blockTextures; + } + + public void setModels(boolean models) { + this.models = models; + } + + public void setModelTextures(boolean modelTextures) { + this.modelTextures = modelTextures; + } + + public void setMapGeometry(boolean mapGeometry) { + this.mapGeometry = mapGeometry; + } + + public void setItemIcons(boolean itemIcons) { + this.itemIcons = itemIcons; + } + + public void setCommonAssetsRebuild(boolean commonAssetsRebuild) { + this.commonAssetsRebuild = commonAssetsRebuild; + } + + @Nonnull + public AssetUpdateQuery.RebuildCache build() { + return new AssetUpdateQuery.RebuildCache( + this.blockTextures, this.models, this.modelTextures, this.mapGeometry, this.itemIcons, this.commonAssetsRebuild + ); + } + + @Nonnull + @Override + public String toString() { + return "RebuildCache{blockTextures=" + + this.blockTextures + + ", models=" + + this.models + + ", modelTextures=" + + this.modelTextures + + ", mapGeometry=" + + this.mapGeometry + + ", icons=" + + this.itemIcons + + ", commonAssetsRebuild=" + + this.commonAssetsRebuild + + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/assetstore/AssetValidationResults.java b/src/com/hypixel/hytale/assetstore/AssetValidationResults.java new file mode 100644 index 0000000..36501d1 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/AssetValidationResults.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.assetstore; + +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.backend.HytaleLoggerBackend; +import com.hypixel.hytale.logger.util.GithubMessageUtil; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class AssetValidationResults extends ValidationResults { + private Set> disabledMissingAssetClasses; + + public AssetValidationResults(ExtraInfo extraInfo) { + super(extraInfo); + } + + public void handleMissingAsset(String field, @Nonnull Class assetType, Object assetId) { + if (this.disabledMissingAssetClasses == null || !this.disabledMissingAssetClasses.contains(assetType)) { + throw new MissingAssetException(field, assetType, assetId); + } + } + + public void handleMissingAsset(String field, @Nonnull Class assetType, Object assetId, String extra) { + if (this.disabledMissingAssetClasses == null || !this.disabledMissingAssetClasses.contains(assetType)) { + throw new MissingAssetException(field, assetType, assetId, extra); + } + } + + public void disableMissingAssetFor(Class assetType) { + if (this.disabledMissingAssetClasses == null) { + this.disabledMissingAssetClasses = new HashSet<>(); + } + + this.disabledMissingAssetClasses.add(assetType); + } + + @Override + public void logOrThrowValidatorExceptions(@NonNullDecl HytaleLogger logger, @NonNullDecl String msg) { + this.logOrThrowValidatorExceptions(logger, msg, null, 0); + } + + public void logOrThrowValidatorExceptions(@NonNullDecl HytaleLogger logger, @NonNullDecl String msg, @Nullable Path path, int lineOffset) { + if (GithubMessageUtil.isGithub() && this.validatorExceptions != null && !this.validatorExceptions.isEmpty()) { + for (ValidationResults.ValidatorResultsHolder holder : this.validatorExceptions) { + String file = "unknown"; + if (path == null && this.extraInfo instanceof AssetExtraInfo assetExtraInfo) { + path = assetExtraInfo.getAssetPath(); + } + + if (path != null) { + file = path.toString(); + } + + for (ValidationResults.ValidationResult result : holder.results()) { + HytaleLoggerBackend.rawLog( + switch (result.result()) { + case SUCCESS -> ""; + case WARNING -> holder.line() == -1 + ? GithubMessageUtil.messageWarning(file, result.reason()) + : GithubMessageUtil.messageWarning(file, holder.line() + lineOffset, holder.column(), result.reason()); + case FAIL -> holder.line() == -1 + ? GithubMessageUtil.messageError(file, result.reason()) + : GithubMessageUtil.messageError(file, holder.line() + lineOffset, holder.column(), result.reason()); + } + ); + } + } + } + + super.logOrThrowValidatorExceptions(logger, msg); + } +} diff --git a/src/com/hypixel/hytale/assetstore/DecodedAsset.java b/src/com/hypixel/hytale/assetstore/DecodedAsset.java new file mode 100644 index 0000000..47371ad --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/DecodedAsset.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.assetstore; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DecodedAsset> implements AssetHolder { + private final K key; + private final T asset; + + public DecodedAsset(K key, T asset) { + this.key = key; + this.asset = asset; + } + + public K getKey() { + return this.key; + } + + public T getAsset() { + return this.asset; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DecodedAsset that = (DecodedAsset)o; + if (this.key != null ? this.key.equals(that.key) : that.key == null) { + return this.asset != null ? this.asset.equals(that.asset) : that.asset == null; + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.key != null ? this.key.hashCode() : 0; + return 31 * result + (this.asset != null ? this.asset.hashCode() : 0); + } + + @Nonnull + @Override + public String toString() { + return "DecodedAsset{key=" + this.key + ", asset=" + this.asset + "}"; + } +} diff --git a/src/com/hypixel/hytale/assetstore/JsonAsset.java b/src/com/hypixel/hytale/assetstore/JsonAsset.java new file mode 100644 index 0000000..dc9c758 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/JsonAsset.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.assetstore; + +public interface JsonAsset { + K getId(); +} diff --git a/src/com/hypixel/hytale/assetstore/MissingAssetException.java b/src/com/hypixel/hytale/assetstore/MissingAssetException.java new file mode 100644 index 0000000..856be22 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/MissingAssetException.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.assetstore; + +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.validation.ValidationResults; +import javax.annotation.Nonnull; + +public class MissingAssetException extends RuntimeException { + private String field; + private Class assetType; + private Object assetId; + + public MissingAssetException(String field, @Nonnull Class assetType, Object assetId) { + super("Missing asset '" + assetId + "' of type " + assetType.getSimpleName() + " for field '" + field + "'!"); + this.field = field; + this.assetType = assetType; + this.assetId = assetId; + } + + public MissingAssetException(String field, @Nonnull Class assetType, Object assetId, String extra) { + super("Missing asset '" + assetId + "' of type " + assetType.getSimpleName() + " for field '" + field + "'! " + extra); + this.field = field; + this.assetType = assetType; + this.assetId = assetId; + } + + public String getField() { + return this.field; + } + + public Class getAssetType() { + return this.assetType; + } + + public Object getAssetId() { + return this.assetId; + } + + public static void handle(@Nonnull ExtraInfo extraInfo, String field, @Nonnull Class assetType, Object assetId) { + ValidationResults validationResults = extraInfo.getValidationResults(); + if (validationResults instanceof AssetValidationResults) { + ((AssetValidationResults)validationResults).handleMissingAsset(field, assetType, assetId); + } else { + throw new MissingAssetException(field, assetType, assetId); + } + } + + public static void handle(@Nonnull ExtraInfo extraInfo, String field, @Nonnull Class assetType, Object assetId, String extra) { + ValidationResults validationResults = extraInfo.getValidationResults(); + if (validationResults instanceof AssetValidationResults) { + ((AssetValidationResults)validationResults).handleMissingAsset(field, assetType, assetId, extra); + } else { + throw new MissingAssetException(field, assetType, assetId, extra); + } + } +} diff --git a/src/com/hypixel/hytale/assetstore/RawAsset.java b/src/com/hypixel/hytale/assetstore/RawAsset.java new file mode 100644 index 0000000..7f30c12 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/RawAsset.java @@ -0,0 +1,153 @@ +package com.hypixel.hytale.assetstore; + +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import java.nio.file.Path; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNullableByDefault; + +@ParametersAreNullableByDefault +public class RawAsset implements AssetHolder { + private final Path parentPath; + @Nullable + private final K key; + private final int lineOffset; + private final boolean parentKeyResolved; + @Nullable + private final K parentKey; + @Nullable + private final Path path; + @Nullable + private final char[] buffer; + @Nullable + private final AssetExtraInfo.Data containerData; + @Nonnull + private final ContainedAssetCodec.Mode containedAssetMode; + + public RawAsset(K key, Path path) { + this.key = key; + this.lineOffset = 0; + this.parentKeyResolved = false; + this.parentKey = null; + this.path = path; + this.parentPath = null; + this.buffer = null; + this.containerData = null; + this.containedAssetMode = ContainedAssetCodec.Mode.NONE; + } + + public RawAsset( + Path parentPath, + K key, + K parentKey, + int lineOffset, + char[] buffer, + AssetExtraInfo.Data containerData, + @Nonnull ContainedAssetCodec.Mode containedAssetMode + ) { + this.key = key; + this.lineOffset = lineOffset; + this.parentKeyResolved = true; + this.parentKey = parentKey; + this.path = null; + this.parentPath = parentPath; + this.buffer = buffer; + this.containerData = containerData; + this.containedAssetMode = containedAssetMode; + } + + private RawAsset( + K key, + boolean parentKeyResolved, + K parentKey, + Path path, + char[] buffer, + AssetExtraInfo.Data containerData, + @Nonnull ContainedAssetCodec.Mode containedAssetMode + ) { + this.key = key; + this.lineOffset = 0; + this.parentKeyResolved = parentKeyResolved; + this.parentKey = parentKey; + this.path = path; + this.parentPath = null; + this.buffer = buffer; + this.containerData = containerData; + this.containedAssetMode = containedAssetMode; + } + + @Nullable + public K getKey() { + return this.key; + } + + public boolean isParentKeyResolved() { + return this.parentKeyResolved; + } + + @Nullable + public K getParentKey() { + return this.parentKey; + } + + @Nullable + public Path getPath() { + return this.path; + } + + public Path getParentPath() { + return this.parentPath; + } + + public int getLineOffset() { + return this.lineOffset; + } + + public char[] getBuffer() { + return this.buffer; + } + + @Nonnull + public ContainedAssetCodec.Mode getContainedAssetMode() { + return this.containedAssetMode; + } + + @Nonnull + public RawJsonReader toRawJsonReader(@Nonnull Supplier bufferSupplier) throws IOException { + return this.path != null ? RawJsonReader.fromPath(this.path, bufferSupplier.get()) : RawJsonReader.fromBuffer(this.buffer); + } + + @Nonnull + public AssetExtraInfo.Data makeData(Class> aClass, K key, K parentKey) { + boolean inheritTags = switch (this.containedAssetMode) { + case INHERIT_ID, INHERIT_ID_AND_PARENT, INJECT_PARENT -> true; + case NONE, GENERATE_ID -> false; + }; + return new AssetExtraInfo.Data(this.containerData, aClass, key, parentKey, inheritTags); + } + + @Nonnull + public RawAsset withResolveKeys(K key, K parentKey) { + return new RawAsset<>(key, true, parentKey, this.path, this.buffer, this.containerData, this.containedAssetMode); + } + + @Nonnull + @Override + public String toString() { + return "RawAsset{key=" + + this.key + + ", parentKeyResolved=" + + this.parentKeyResolved + + ", parentKey=" + + this.parentKey + + ", path=" + + this.path + + ", buffer.length=" + + (this.buffer != null ? this.buffer.length : -1) + + "}"; + } +} diff --git a/src/com/hypixel/hytale/assetstore/codec/AssetBuilderCodec.java b/src/com/hypixel/hytale/assetstore/codec/AssetBuilderCodec.java new file mode 100644 index 0000000..bc1c882 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/codec/AssetBuilderCodec.java @@ -0,0 +1,225 @@ +package com.hypixel.hytale.assetstore.codec; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.JsonAsset; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetBuilderCodec> extends BuilderCodec implements AssetCodec { + public static final KeyedCodec> TAGS_CODEC = new KeyedCodec<>("Tags", new MapCodec<>(Codec.STRING_ARRAY, HashMap::new)); + private static final String TAG_DOCUMENTATION = "Tags are a general way to describe an asset that can be interpreted by other systems in a way they see fit.\n\nFor example you could tag something with a **Material** tag with the values **Solid** and **Stone**, And another single tag **Ore**.\n\nTags will be expanded into a single list of tags automatically. Using the above example with **Material** and **Ore** the end result would be the following list of tags: **Ore**, **Material**, **Solid**, **Stone**, **Material=Solid** and **Material=Stone**."; + @Nonnull + protected final KeyedCodec idCodec; + @Nonnull + protected final KeyedCodec parentCodec; + protected final BiConsumer idSetter; + protected final BiConsumer dataSetter; + @Nonnull + protected final Function dataGetter; + + protected AssetBuilderCodec(@Nonnull AssetBuilderCodec.Builder builder) { + super(builder); + this.idCodec = builder.idCodec; + this.parentCodec = new KeyedCodec<>("Parent", this.idCodec.getChildCodec()); + this.idSetter = builder.idSetter; + this.dataSetter = builder.dataSetter; + this.dataGetter = builder.dataGetter; + } + + @Nonnull + @Override + public KeyedCodec getKeyCodec() { + return this.idCodec; + } + + @Nonnull + @Override + public KeyedCodec getParentCodec() { + return this.parentCodec; + } + + @Override + public AssetExtraInfo.Data getData(T t) { + return this.dataGetter.apply(t); + } + + @Override + public T decodeJsonAsset(@Nonnull RawJsonReader reader, @Nonnull AssetExtraInfo extraInfo) throws IOException { + return this.decodeAndInheritJsonAsset(reader, null, extraInfo); + } + + @Override + public T decodeAndInheritJsonAsset(@Nonnull RawJsonReader reader, @Nullable T parent, @Nonnull AssetExtraInfo extraInfo) throws IOException { + T t = this.supplier.get(); + this.dataSetter.accept(t, extraInfo.getData()); + if (parent != null) { + this.inherit(t, parent, extraInfo); + } + + this.decodeAndInheritJson0(reader, t, parent, extraInfo); + this.idSetter.accept(t, extraInfo.getKey()); + this.afterDecodeAndValidate(t, extraInfo); + return t; + } + + @Nonnull + @Override + public ObjectSchema toSchema(@Nonnull SchemaContext context) { + return this.toSchema(context, this.supplier.get()); + } + + @Nonnull + public ObjectSchema toSchema(@Nonnull SchemaContext context, @Nullable T def) { + ObjectSchema schema = super.toSchema(context, def); + KeyedCodec parent = this.getParentCodec(); + Schema parentSchema = parent.getChildCodec().toSchema(context); + parentSchema.setMarkdownDescription( + "When set this asset will inherit properties from the named asset.\n\nWhen inheriting from another **" + + this.tClass.getSimpleName() + + "** most properties will simply be copied from the parent asset to this asset. In the case where both child and parent provide a field the child field will simply replace the value provided by the parent, in the case of nested structures this will apply to the fields within the structure. In some cases the field may decide to act differently, for example: by merging the parent and child fields together." + ); + Class rootClass = this.tClass; + + for (BuilderCodec rootCodec = this; rootCodec.getParent() != null; rootClass = rootCodec.getInnerClass()) { + rootCodec = rootCodec.getParent(); + } + + parentSchema.setHytaleParent(new Schema.InheritSettings(rootClass.getSimpleName())); + LinkedHashMap props = new LinkedHashMap<>(); + props.put(parent.getKey(), parentSchema); + props.putAll(schema.getProperties()); + schema.setProperties(props); + return schema; + } + + @Nonnull + public static > AssetBuilderCodec.Builder builder( + Class tClass, + Supplier supplier, + Codec idCodec, + BiConsumer idSetter, + Function idGetter, + BiConsumer dataSetter, + @Nonnull Function dataGetter + ) { + return new AssetBuilderCodec.Builder<>(tClass, supplier, idCodec, idSetter, idGetter, dataSetter, dataGetter); + } + + @Nonnull + public static > AssetBuilderCodec.Builder builder( + Class tClass, + Supplier supplier, + BuilderCodec parentCodec, + Codec idCodec, + BiConsumer idSetter, + Function idGetter, + BiConsumer dataSetter, + @Nonnull Function dataGetter + ) { + return new AssetBuilderCodec.Builder<>(tClass, supplier, parentCodec, idCodec, idSetter, idGetter, dataSetter, dataGetter); + } + + @Nonnull + public static > AssetBuilderCodec wrap( + @Nonnull BuilderCodec codec, + Codec idCodec, + BiConsumer idSetter, + Function idGetter, + BiConsumer dataSetter, + @Nonnull Function dataGetter + ) { + return builder(codec.getInnerClass(), codec.getSupplier(), codec, idCodec, idSetter, idGetter, dataSetter, dataGetter) + .documentation(codec.getDocumentation()) + .build(); + } + + public static class Builder> extends BuilderCodec.BuilderBase> { + @Nonnull + protected final KeyedCodec idCodec; + protected final BiConsumer idSetter; + protected final BiConsumer dataSetter; + @Nonnull + protected final Function dataGetter; + + public Builder( + Class tClass, + Supplier supplier, + Codec idCodec, + BiConsumer idSetter, + Function idGetter, + BiConsumer dataSetter, + @Nonnull Function dataGetter + ) { + super(tClass, supplier); + this.idCodec = new KeyedCodec<>("Id", idCodec); + this.idSetter = idSetter; + this.dataSetter = dataSetter; + this.dataGetter = dataGetter; + this.>appendInherited(AssetBuilderCodec.TAGS_CODEC, (t, tags) -> dataGetter.apply(t).putTags(tags), t -> { + AssetExtraInfo.Data data = dataGetter.apply(t); + return data != null ? data.getRawTags() : null; + }, (t, parent) -> { + AssetExtraInfo.Data data = dataGetter.apply(t); + AssetExtraInfo.Data parentData = dataGetter.apply(parent); + if (data != null && parentData != null) { + data.putTags(parentData.getRawTags()); + } + }) + .documentation( + "Tags are a general way to describe an asset that can be interpreted by other systems in a way they see fit.\n\nFor example you could tag something with a **Material** tag with the values **Solid** and **Stone**, And another single tag **Ore**.\n\nTags will be expanded into a single list of tags automatically. Using the above example with **Material** and **Ore** the end result would be the following list of tags: **Ore**, **Material**, **Solid**, **Stone**, **Material=Solid** and **Material=Stone**." + ) + .add(); + } + + public Builder( + Class tClass, + Supplier supplier, + BuilderCodec parentCodec, + Codec idCodec, + BiConsumer idSetter, + Function idGetter, + BiConsumer dataSetter, + @Nonnull Function dataGetter + ) { + super(tClass, supplier, parentCodec); + this.idCodec = new KeyedCodec<>("Id", idCodec); + this.idSetter = idSetter; + this.dataSetter = dataSetter; + this.dataGetter = dataGetter; + this.>appendInherited(AssetBuilderCodec.TAGS_CODEC, (t, tags) -> dataGetter.apply(t).putTags(tags), t -> { + AssetExtraInfo.Data data = dataGetter.apply(t); + return data != null ? data.getRawTags() : null; + }, (t, parent) -> { + AssetExtraInfo.Data data = dataGetter.apply(t); + AssetExtraInfo.Data parentData = dataGetter.apply(parent); + if (data != null && parentData != null) { + data.putTags(parentData.getRawTags()); + } + }) + .documentation( + "Tags are a general way to describe an asset that can be interpreted by other systems in a way they see fit.\n\nFor example you could tag something with a **Material** tag with the values **Solid** and **Stone**, And another single tag **Ore**.\n\nTags will be expanded into a single list of tags automatically. Using the above example with **Material** and **Ore** the end result would be the following list of tags: **Ore**, **Material**, **Solid**, **Stone**, **Material=Solid** and **Material=Stone**." + ) + .add(); + } + + @Nonnull + public AssetBuilderCodec build() { + return new AssetBuilderCodec<>(this); + } + } +} diff --git a/src/com/hypixel/hytale/assetstore/codec/AssetCodec.java b/src/com/hypixel/hytale/assetstore/codec/AssetCodec.java new file mode 100644 index 0000000..652fa2e --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/codec/AssetCodec.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.assetstore.codec; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.JsonAsset; +import com.hypixel.hytale.codec.InheritCodec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.codec.validation.ValidatableCodec; +import java.io.IOException; +import javax.annotation.Nullable; + +public interface AssetCodec> extends InheritCodec, ValidatableCodec { + KeyedCodec getKeyCodec(); + + KeyedCodec getParentCodec(); + + @Nullable + AssetExtraInfo.Data getData(T var1); + + T decodeJsonAsset(RawJsonReader var1, AssetExtraInfo var2) throws IOException; + + T decodeAndInheritJsonAsset(RawJsonReader var1, T var2, AssetExtraInfo var3) throws IOException; +} diff --git a/src/com/hypixel/hytale/assetstore/codec/AssetCodecMapCodec.java b/src/com/hypixel/hytale/assetstore/codec/AssetCodecMapCodec.java new file mode 100644 index 0000000..3c4b8fc --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/codec/AssetCodecMapCodec.java @@ -0,0 +1,308 @@ +package com.hypixel.hytale.assetstore.codec; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.JsonAsset; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.lookup.ACodecMapCodec; +import com.hypixel.hytale.codec.lookup.Priority; +import com.hypixel.hytale.codec.lookup.StringCodecMapCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class AssetCodecMapCodec> extends StringCodecMapCodec> implements AssetCodec { + @Nonnull + protected final KeyedCodec idCodec; + @Nonnull + protected final KeyedCodec parentCodec; + protected final BiConsumer idSetter; + protected final Function idGetter; + protected final BiConsumer dataSetter; + protected final Function dataGetter; + + public AssetCodecMapCodec( + Codec idCodec, + BiConsumer idSetter, + Function idGetter, + BiConsumer dataSetter, + Function dataGetter + ) { + super("Type"); + this.idCodec = new KeyedCodec<>("Id", idCodec); + this.parentCodec = new KeyedCodec<>("Parent", idCodec); + this.idSetter = idSetter; + this.idGetter = idGetter; + this.dataSetter = dataSetter; + this.dataGetter = dataGetter; + } + + public AssetCodecMapCodec( + String key, + Codec idCodec, + BiConsumer idSetter, + Function idGetter, + BiConsumer dataSetter, + Function dataGetter + ) { + super(key); + this.idCodec = new KeyedCodec<>("Id", idCodec); + this.parentCodec = new KeyedCodec<>("Parent", idCodec); + this.idSetter = idSetter; + this.idGetter = idGetter; + this.dataSetter = dataSetter; + this.dataGetter = dataGetter; + } + + public AssetCodecMapCodec( + Codec idCodec, + BiConsumer idSetter, + Function idGetter, + BiConsumer dataSetter, + Function dataGetter, + boolean allowDefault + ) { + super("Type", allowDefault); + this.idCodec = new KeyedCodec<>("Id", idCodec); + this.parentCodec = new KeyedCodec<>("Parent", idCodec); + this.idSetter = idSetter; + this.idGetter = idGetter; + this.dataSetter = dataSetter; + this.dataGetter = dataGetter; + } + + public AssetCodecMapCodec( + String key, + Codec idCodec, + BiConsumer idSetter, + Function idGetter, + BiConsumer dataSetter, + Function dataGetter, + boolean allowDefault + ) { + super(key, allowDefault); + this.idCodec = new KeyedCodec<>("Id", idCodec); + this.parentCodec = new KeyedCodec<>("Parent", idCodec); + this.idSetter = idSetter; + this.idGetter = idGetter; + this.dataSetter = dataSetter; + this.dataGetter = dataGetter; + } + + @Nonnull + @Override + public KeyedCodec getKeyCodec() { + return this.idCodec; + } + + @Nonnull + @Override + public KeyedCodec getParentCodec() { + return this.parentCodec; + } + + @Override + public AssetExtraInfo.Data getData(T t) { + return this.dataGetter.apply(t); + } + + @Nonnull + public AssetCodecMapCodec register(@Nonnull String id, Class aClass, BuilderCodec codec) { + return this.register(Priority.NORMAL, id, aClass, codec); + } + + @Nonnull + public AssetCodecMapCodec register(@Nonnull Priority priority, @Nonnull String id, Class aClass, BuilderCodec codec) { + AssetBuilderCodec assetCodec = AssetBuilderCodec.wrap( + (BuilderCodec)codec, this.idCodec.getChildCodec(), this.idSetter, this.idGetter, this.dataSetter, this.dataGetter + ); + super.register(priority, id, aClass, assetCodec); + return this; + } + + public T decodeAndInherit(@Nonnull BsonDocument document, T parent, ExtraInfo extraInfo) { + BsonValue id = document.get(this.key); + AssetBuilderCodec codec = this.idToCodec.get(id == null ? null : id.asString().getValue()); + if (codec == null) { + AssetBuilderCodec defaultCodec = this.getDefaultCodec(); + if (defaultCodec == null) { + throw new ACodecMapCodec.UnknownIdException("No codec registered with for '" + this.key + "': " + id); + } else { + return defaultCodec.decodeAndInherit(document, parent, extraInfo); + } + } else { + return codec.decodeAndInherit(document, parent, extraInfo); + } + } + + public void decodeAndInherit(@Nonnull BsonDocument document, T t, T parent, ExtraInfo extraInfo) { + BsonValue id = document.get(this.key); + AssetBuilderCodec codec = this.idToCodec.get(id == null ? null : id.asString().getValue()); + if (codec == null) { + AssetBuilderCodec defaultCodec = this.getDefaultCodec(); + if (defaultCodec == null) { + throw new ACodecMapCodec.UnknownIdException("No codec registered with for '" + this.key + "': " + id); + } else { + defaultCodec.decodeAndInherit(document, t, parent, extraInfo); + } + } else { + codec.decodeAndInherit(document, t, parent, extraInfo); + } + } + + public T decodeAndInheritJson(@Nonnull RawJsonReader reader, @Nullable T parent, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.mark(); + String id = null; + if (RawJsonReader.seekToKey(reader, this.key)) { + id = reader.readString(); + } else if (parent != null) { + id = this.getIdFor((Class)parent.getClass()); + } + + reader.reset(); + extraInfo.ignoreUnusedKey(this.key); + + JsonAsset var7; + try { + AssetBuilderCodec codec = id == null ? null : this.idToCodec.get(id); + if (codec != null) { + return codec.decodeAndInheritJson(reader, parent, extraInfo); + } + + AssetBuilderCodec defaultCodec = this.getDefaultCodec(); + if (defaultCodec == null) { + throw new ACodecMapCodec.UnknownIdException("No codec registered with for '" + this.key + "': " + id); + } + + var7 = defaultCodec.decodeAndInheritJson(reader, parent, extraInfo); + } finally { + extraInfo.popIgnoredUnusedKey(); + } + + return (T)var7; + } + + public void decodeAndInheritJson(@Nonnull RawJsonReader reader, T t, @Nullable T parent, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.mark(); + String id = null; + if (RawJsonReader.seekToKey(reader, this.key)) { + id = reader.readString(); + } else if (parent != null) { + id = this.getIdFor((Class)parent.getClass()); + } + + reader.reset(); + extraInfo.ignoreUnusedKey(this.key); + + try { + AssetBuilderCodec codec = id == null ? null : this.idToCodec.get(id); + if (codec != null) { + codec.decodeAndInheritJson(reader, t, parent, extraInfo); + return; + } + + AssetBuilderCodec defaultCodec = this.getDefaultCodec(); + if (defaultCodec == null) { + throw new ACodecMapCodec.UnknownIdException("No codec registered with for '" + this.key + "': " + id); + } + + defaultCodec.decodeAndInheritJson(reader, t, parent, extraInfo); + } finally { + extraInfo.popIgnoredUnusedKey(); + } + } + + @Override + public T decodeJsonAsset(@Nonnull RawJsonReader reader, @Nonnull AssetExtraInfo extraInfo) throws IOException { + return this.decodeAndInheritJsonAsset(reader, null, extraInfo); + } + + @Override + public T decodeAndInheritJsonAsset(@Nonnull RawJsonReader reader, @Nullable T parent, @Nonnull AssetExtraInfo extraInfo) throws IOException { + reader.mark(); + String id = null; + if (RawJsonReader.seekToKey(reader, this.key)) { + id = reader.readString(); + } else if (parent != null) { + id = this.getIdFor((Class)parent.getClass()); + } + + reader.reset(); + extraInfo.ignoreUnusedKey(this.key); + + JsonAsset var8; + try { + AssetBuilderCodec codec = id == null ? null : this.idToCodec.get(id); + if (codec == null) { + AssetBuilderCodec defaultCodec = this.getDefaultCodec(); + if (defaultCodec == null) { + throw new ACodecMapCodec.UnknownIdException("No codec registered with for '" + this.key + "': " + id); + } + + codec = defaultCodec; + } + + Supplier supplier = codec.getSupplier(); + if (supplier == null) { + throw new CodecException( + "This BuilderCodec is for an abstract or direct codec. To use this codec you must specify an existing object to decode into." + ); + } + + T t = supplier.get(); + this.dataSetter.accept(t, extraInfo.getData()); + if (parent != null) { + codec.inherit(t, parent, extraInfo); + } + + codec.decodeAndInheritJson0(reader, t, parent, extraInfo); + this.idSetter.accept(t, extraInfo.getKey()); + codec.afterDecodeAndValidate(t, extraInfo); + var8 = t; + } finally { + extraInfo.popIgnoredUnusedKey(); + } + + return (T)var8; + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + Schema schema = super.toSchema(context); + schema.getHytaleSchemaTypeField().setParentPropertyKey(this.parentCodec.getKey()); + return schema; + } + + @Override + protected void mutateChildSchema(String key, @Nonnull SchemaContext context, BuilderCodec c, @Nonnull ObjectSchema objectSchema) { + super.mutateChildSchema(key, context, c, objectSchema); + AssetBuilderCodec def = this.getDefaultCodec(); + if (!this.allowDefault || def != c) { + Schema idField = new Schema(); + idField.setRequired(this.key); + Schema parentField = new Schema(); + parentField.setRequired(this.parentCodec.getKey()); + AssetBuilderCodec bc = (AssetBuilderCodec)c; + Schema parentSchema = objectSchema.getProperties().get(bc.getParentCodec().getKey()); + if (parentSchema != null) { + Schema.InheritSettings settings = parentSchema.getHytaleParent(); + settings.setMapKey(this.key); + settings.setMapKeyValue(key); + objectSchema.setOneOf(idField, parentField); + } + } + } +} diff --git a/src/com/hypixel/hytale/assetstore/codec/ContainedAssetCodec.java b/src/com/hypixel/hytale/assetstore/codec/ContainedAssetCodec.java new file mode 100644 index 0000000..4408378 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/codec/ContainedAssetCodec.java @@ -0,0 +1,234 @@ +package com.hypixel.hytale.assetstore.codec; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.RawAsset; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.codec.validation.ValidatableCodec; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Set; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonValue; + +public class ContainedAssetCodec, M extends AssetMap> implements Codec, ValidatableCodec { + private static final boolean DISABLE_DIRECT_LOADING = true; + private final Class assetClass; + private final AssetCodec codec; + @Nonnull + private final ContainedAssetCodec.Mode mode; + private final Function, K> keyGenerator; + + public ContainedAssetCodec(Class assetClass, AssetCodec codec) { + this(assetClass, codec, ContainedAssetCodec.Mode.GENERATE_ID); + } + + public ContainedAssetCodec(Class assetClass, AssetCodec codec, @Nonnull ContainedAssetCodec.Mode mode) { + this(assetClass, codec, mode, assetExtraInfo -> AssetRegistry.getAssetStore(assetClass).transformKey(assetExtraInfo.generateKey())); + } + + public ContainedAssetCodec(Class assetClass, AssetCodec codec, @Nonnull ContainedAssetCodec.Mode mode, Function, K> keyGenerator) { + if (mode == ContainedAssetCodec.Mode.NONE) { + throw new UnsupportedOperationException("Contained asset mode can't be NONE!"); + } else { + this.assetClass = assetClass; + this.codec = codec; + this.mode = mode; + this.keyGenerator = keyGenerator; + } + } + + public Class getAssetClass() { + return this.assetClass; + } + + @Nullable + @Override + public K decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + if (!(extraInfo instanceof AssetExtraInfo assetExtraInfo)) { + throw new UnsupportedOperationException("Unable to decode asset from codec used outside of an AssetStore"); + } else if (bsonValue.isString()) { + return this.codec.getKeyCodec().getChildCodec().decode(bsonValue, extraInfo); + } else { + KeyedCodec parentCodec = this.codec.getParentCodec(); + K parentId = parentCodec != null ? parentCodec.getOrNull(bsonValue.asDocument(), extraInfo) : null; + AssetStore assetStore = AssetRegistry.getAssetStore(this.assetClass); + K id; + switch (this.mode) { + case GENERATE_ID: { + id = this.keyGenerator.apply(assetExtraInfo); + boolean inheritContainerTags = false; + break; + } + case INHERIT_ID: { + id = assetStore.transformKey(assetExtraInfo.getKey()); + boolean inheritContainerTags = true; + break; + } + case INHERIT_ID_AND_PARENT: { + id = assetStore.transformKey(assetExtraInfo.getKey()); + if (parentId == null) { + Object thisAssetParentId = assetExtraInfo.getData().getParentKey(); + if (thisAssetParentId != null) { + parentId = assetStore.transformKey(thisAssetParentId); + } + } + + boolean inheritContainerTags = true; + break; + } + case INJECT_PARENT: { + id = this.keyGenerator.apply(assetExtraInfo); + if (parentId == null && !assetExtraInfo.getKey().equals(id)) { + parentId = assetExtraInfo.getKey(); + } + + boolean inheritContainerTags = true; + break; + } + default: + throw new UnsupportedOperationException("Contained asset mode can't be NONE!"); + } + + T parent = parentId != null ? assetStore.getAssetMap().getAsset(parentId) : null; + if (parentId != null && parent != null) { + } + + char[] clone = bsonValue.asDocument().toJson().toCharArray(); + Path path = assetExtraInfo.getAssetPath(); + assetExtraInfo.getData().addContainedAsset(this.assetClass, new RawAsset<>(path, id, parentId, 0, clone, assetExtraInfo.getData(), this.mode)); + return id; + } + } + + @Override + public BsonValue encode(@Nonnull K key, ExtraInfo extraInfo) { + if (key.toString().startsWith("*")) { + T asset = (T)AssetRegistry.getAssetStore(this.assetClass).getAssetMap().getAsset(key); + if (asset != null) { + return this.codec.encode(asset, extraInfo); + } + } + + return this.codec.getKeyCodec().getChildCodec().encode(key, extraInfo); + } + + @Nullable + @Override + public K decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + if (!(extraInfo instanceof AssetExtraInfo assetExtraInfo)) { + throw new UnsupportedOperationException("Unable to decode asset from codec used outside of an AssetStore"); + } else { + int lineStart = reader.getLine() - 1; + if (reader.peekFor('"')) { + return this.codec.getKeyCodec().getChildCodec().decodeJson(reader, extraInfo); + } else { + reader.mark(); + K parentId = null; + boolean needsSkip = false; + KeyedCodec parentCodec = this.codec.getParentCodec(); + if (parentCodec != null && RawJsonReader.seekToKey(reader, parentCodec.getKey())) { + parentId = parentCodec.getChildCodec().decodeJson(reader, extraInfo); + needsSkip = true; + } + + AssetStore assetStore = AssetRegistry.getAssetStore(this.assetClass); + K id; + switch (this.mode) { + case GENERATE_ID: { + id = this.keyGenerator.apply(assetExtraInfo); + boolean inheritContainerTags = false; + break; + } + case INHERIT_ID: { + id = assetStore.transformKey(assetExtraInfo.getKey()); + boolean inheritContainerTags = true; + break; + } + case INHERIT_ID_AND_PARENT: { + id = assetStore.transformKey(assetExtraInfo.getKey()); + if (parentId == null) { + Object thisAssetParentId = assetExtraInfo.getData().getParentKey(); + if (thisAssetParentId != null) { + parentId = assetStore.transformKey(thisAssetParentId); + } + } + + boolean inheritContainerTags = true; + break; + } + case INJECT_PARENT: { + id = this.keyGenerator.apply(assetExtraInfo); + if (parentId == null && !assetExtraInfo.getKey().equals(id)) { + parentId = assetExtraInfo.getKey(); + } + + boolean inheritContainerTags = true; + break; + } + default: + throw new UnsupportedOperationException("Contained asset mode can't be NONE!"); + } + + T parent = parentId != null ? assetStore.getAssetMap().getAsset(parentId) : null; + if (parentId != null && parent != null) { + } + + if (needsSkip) { + reader.skipObjectContinued(); + } + + char[] clone = reader.cloneMark(); + reader.unmark(); + Path path = assetExtraInfo.getAssetPath(); + assetExtraInfo.getData() + .addContainedAsset(this.assetClass, new RawAsset<>(path, id, parentId, lineStart, clone, assetExtraInfo.getData(), this.mode)); + return id; + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + Schema keySchema = context.refDefinition(this.codec.getKeyCodec().getChildCodec()); + keySchema.setTitle("Reference to " + this.assetClass.getSimpleName()); + Schema nestedSchema = context.refDefinition(this.codec); + Schema s = Schema.anyOf(keySchema, nestedSchema); + s.setHytaleAssetRef(this.assetClass.getSimpleName()); + return s; + } + + @Override + public void validate(K k, @Nonnull ExtraInfo extraInfo) { + AssetRegistry.getAssetStore(this.assetClass).validate(k, extraInfo.getValidationResults(), extraInfo); + } + + @Override + public void validateDefaults(ExtraInfo extraInfo, @Nonnull Set> tested) { + if (tested.add(this)) { + ; + } + } + + public static enum Mode { + NONE, + GENERATE_ID, + INHERIT_ID, + INHERIT_ID_AND_PARENT, + INJECT_PARENT; + + private Mode() { + } + } +} diff --git a/src/com/hypixel/hytale/assetstore/event/AssetMonitorEvent.java b/src/com/hypixel/hytale/assetstore/event/AssetMonitorEvent.java new file mode 100644 index 0000000..7c3bdf3 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/event/AssetMonitorEvent.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.assetstore.event; + +import com.hypixel.hytale.event.IEvent; +import java.nio.file.Path; +import java.util.List; +import javax.annotation.Nonnull; + +public abstract class AssetMonitorEvent implements IEvent { + @Nonnull + private final List createdOrModifiedFilesToLoad; + @Nonnull + private final List removedFilesToUnload; + @Nonnull + private final List createdOrModifiedDirectories; + @Nonnull + private final List removedFilesAndDirectories; + @Nonnull + private final String assetPack; + + public AssetMonitorEvent( + @Nonnull String assetPack, + @Nonnull List createdOrModified, + @Nonnull List removed, + @Nonnull List createdDirectories, + @Nonnull List removedDirectories + ) { + this.assetPack = assetPack; + this.createdOrModifiedFilesToLoad = createdOrModified; + this.removedFilesToUnload = removed; + this.createdOrModifiedDirectories = createdDirectories; + this.removedFilesAndDirectories = removedDirectories; + } + + @Nonnull + public String getAssetPack() { + return this.assetPack; + } + + @Nonnull + public List getCreatedOrModifiedFilesToLoad() { + return this.createdOrModifiedFilesToLoad; + } + + @Nonnull + public List getRemovedFilesToUnload() { + return this.removedFilesToUnload; + } + + @Nonnull + public List getRemovedFilesAndDirectories() { + return this.removedFilesAndDirectories; + } + + @Nonnull + public List getCreatedOrModifiedDirectories() { + return this.createdOrModifiedDirectories; + } +} diff --git a/src/com/hypixel/hytale/assetstore/event/AssetStoreEvent.java b/src/com/hypixel/hytale/assetstore/event/AssetStoreEvent.java new file mode 100644 index 0000000..058bc75 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/event/AssetStoreEvent.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.assetstore.event; + +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.event.IEvent; +import javax.annotation.Nonnull; + +public abstract class AssetStoreEvent implements IEvent { + @Nonnull + private final AssetStore assetStore; + + public AssetStoreEvent(@Nonnull AssetStore assetStore) { + this.assetStore = assetStore; + } + + @Nonnull + public AssetStore getAssetStore() { + return this.assetStore; + } + + @Nonnull + @Override + public String toString() { + return "AssetStoreEvent{assetStore=" + this.assetStore + "}"; + } +} diff --git a/src/com/hypixel/hytale/assetstore/event/AssetStoreMonitorEvent.java b/src/com/hypixel/hytale/assetstore/event/AssetStoreMonitorEvent.java new file mode 100644 index 0000000..c6bd3e2 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/event/AssetStoreMonitorEvent.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.assetstore.event; + +import com.hypixel.hytale.assetstore.AssetStore; +import java.nio.file.Path; +import java.util.List; +import javax.annotation.Nonnull; + +public class AssetStoreMonitorEvent extends AssetMonitorEvent { + @Nonnull + private final AssetStore assetStore; + + public AssetStoreMonitorEvent( + @Nonnull String assetPack, + @Nonnull AssetStore assetStore, + @Nonnull List createdOrModified, + @Nonnull List removed, + @Nonnull List createdOrModifiedDirectories, + @Nonnull List removedDirectories + ) { + super(assetPack, createdOrModified, removed, createdOrModifiedDirectories, removedDirectories); + this.assetStore = assetStore; + } + + @Nonnull + public AssetStore getAssetStore() { + return this.assetStore; + } + + @Nonnull + @Override + public String toString() { + return "AssetMonitorEvent{assetStore=" + this.assetStore + "}"; + } +} diff --git a/src/com/hypixel/hytale/assetstore/event/AssetsEvent.java b/src/com/hypixel/hytale/assetstore/event/AssetsEvent.java new file mode 100644 index 0000000..e16ad85 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/event/AssetsEvent.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.assetstore.event; + +import com.hypixel.hytale.assetstore.JsonAsset; +import com.hypixel.hytale.event.IEvent; + +public abstract class AssetsEvent> implements IEvent> { + public AssetsEvent() { + } +} diff --git a/src/com/hypixel/hytale/assetstore/event/GenerateAssetsEvent.java b/src/com/hypixel/hytale/assetstore/event/GenerateAssetsEvent.java new file mode 100644 index 0000000..bf31a0d --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/event/GenerateAssetsEvent.java @@ -0,0 +1,201 @@ +package com.hypixel.hytale.assetstore.event; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.event.IProcessedEvent; +import com.hypixel.hytale.logger.HytaleLogger; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class GenerateAssetsEvent, M extends AssetMap> extends AssetsEvent implements IProcessedEvent { + private final Class tClass; + private final M assetMap; + @Nonnull + private final Map loadedAssets; + private final Map> assetChildren; + @Nonnull + private final Map unmodifiableLoadedAssets; + private final Map addedAssets = new ConcurrentHashMap<>(); + private final Map> addedAssetChildren = new ConcurrentHashMap<>(); + private final Map>, Map>> addedChildAssetsMap = new ConcurrentHashMap<>(); + private long before; + + public GenerateAssetsEvent(Class tClass, M assetMap, @Nonnull Map loadedAssets, Map> assetChildren) { + this.tClass = tClass; + this.assetMap = assetMap; + this.loadedAssets = loadedAssets; + this.assetChildren = assetChildren; + this.unmodifiableLoadedAssets = Collections.unmodifiableMap(loadedAssets); + this.before = System.nanoTime(); + } + + public Class getAssetClass() { + return this.tClass; + } + + @Nonnull + public Map getLoadedAssets() { + return this.unmodifiableLoadedAssets; + } + + public M getAssetMap() { + return this.assetMap; + } + + public void addChildAsset(K childKey, T asset, @Nonnull K parent) { + if (!this.loadedAssets.containsKey(parent) && this.assetMap.getAsset(parent) == null) { + throw new IllegalArgumentException("Parent '" + parent + "' doesn't exist!"); + } else if (parent.equals(childKey)) { + throw new IllegalArgumentException("Unable to to add asset '" + parent + "' because it is its own parent!"); + } else { + AssetStore assetStore = AssetRegistry.getAssetStore(this.tClass); + AssetExtraInfo extraInfo = new AssetExtraInfo<>(assetStore.getCodec().getData(asset)); + assetStore.getCodec().validate(asset, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(assetStore.getLogger()); + this.addedAssets.put(childKey, asset); + this.addedAssetChildren.computeIfAbsent(parent, k -> new HashSet<>()).add(childKey); + } + } + + @SafeVarargs + public final void addChildAsset(K childKey, T asset, @Nonnull K... parents) { + for (int i = 0; i < parents.length; i++) { + K parent = parents[i]; + if (!this.loadedAssets.containsKey(parent) && this.assetMap.getAsset(parent) == null) { + throw new IllegalArgumentException("Parent at " + i + " '" + parent + "' doesn't exist!"); + } + + if (parent.equals(childKey)) { + throw new IllegalArgumentException("Unable to to add asset '" + parent + "' because it is its own parent!"); + } + } + + AssetStore assetStore = AssetRegistry.getAssetStore(this.tClass); + AssetExtraInfo extraInfo = new AssetExtraInfo<>(assetStore.getCodec().getData(asset)); + assetStore.getCodec().validate(asset, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(assetStore.getLogger()); + this.addedAssets.put(childKey, asset); + + for (K parentx : parents) { + this.addedAssetChildren.computeIfAbsent(parentx, k -> new HashSet<>()).add(childKey); + } + } + + public

, PK> void addChildAssetWithReference(K childKey, T asset, Class

parentAssetClass, @Nonnull PK parentKey) { + if (AssetRegistry.>getAssetStore(parentAssetClass).getAssetMap().getAsset(parentKey) == null) { + throw new IllegalArgumentException("Parent '" + parentKey + "' from " + parentAssetClass + " doesn't exist!"); + } else if (parentKey.equals(childKey)) { + throw new IllegalArgumentException("Unable to to add asset '" + parentKey + "' because it is its own parent!"); + } else { + AssetStore assetStore = AssetRegistry.getAssetStore(this.tClass); + AssetExtraInfo extraInfo = new AssetExtraInfo<>(assetStore.getCodec().getData(asset)); + assetStore.getCodec().validate(asset, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(assetStore.getLogger()); + this.addedAssets.put(childKey, asset); + ((Map>)this.addedChildAssetsMap + .computeIfAbsent(parentAssetClass, k -> new ConcurrentHashMap<>())) + .computeIfAbsent(parentKey, k -> new HashSet()) + .add(childKey); + } + } + + public void addChildAssetWithReferences(K childKey, T asset, @Nonnull GenerateAssetsEvent.ParentReference... parents) { + for (int i = 0; i < parents.length; i++) { + GenerateAssetsEvent.ParentReference parent = parents[i]; + if (AssetRegistry.getAssetStore(parent.getParentAssetClass()).getAssetMap().getAsset((K)parent.getParentKey()) == null) { + throw new IllegalArgumentException("Parent at " + i + " '" + parent + "' doesn't exist!"); + } + + if (parent.parentKey.equals(childKey)) { + throw new IllegalArgumentException("Unable to to add asset '" + parent.parentKey + "' because it is its own parent!"); + } + } + + AssetStore assetStore = AssetRegistry.getAssetStore(this.tClass); + AssetExtraInfo extraInfo = new AssetExtraInfo<>(assetStore.getCodec().getData(asset)); + assetStore.getCodec().validate(asset, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(assetStore.getLogger()); + this.addedAssets.put(childKey, asset); + + for (GenerateAssetsEvent.ParentReference parentx : parents) { + ((Map>)this.addedChildAssetsMap + .computeIfAbsent((Class>)parentx.parentAssetClass, k -> new ConcurrentHashMap<>())) + .computeIfAbsent(parentx.parentKey, k -> new HashSet()) + .add(childKey); + } + } + + @Override + public void processEvent(@Nonnull String hookName) { + HytaleLogger.getLogger() + .at(Level.INFO) + .log( + "Generated %d of %s from %s in %s", + this.addedAssets.size(), + this.tClass.getSimpleName(), + hookName, + FormatUtil.nanosToString(System.nanoTime() - this.before) + ); + this.loadedAssets.putAll(this.addedAssets); + this.addedAssets.clear(); + + for (Entry> entry : this.addedAssetChildren.entrySet()) { + K parent = entry.getKey(); + this.assetChildren.computeIfAbsent(parent, kx -> ConcurrentHashMap.newKeySet()).addAll(entry.getValue()); + } + + this.addedAssetChildren.clear(); + + for (Entry>, Map>> entry : this.addedChildAssetsMap.entrySet()) { + Class k = entry.getKey(); + AssetStore assetStore = AssetRegistry.getAssetStore(k); + + for (Entry> childEntry : entry.getValue().entrySet()) { + assetStore.addChildAssetReferences(childEntry.getKey(), this.tClass, childEntry.getValue()); + } + } + + this.addedChildAssetsMap.clear(); + this.before = System.nanoTime(); + } + + @Nonnull + @Override + public String toString() { + return "GenerateAssetsEvent{tClass=" + this.tClass + ", loadedAssets.size()=" + this.loadedAssets.size() + ", " + super.toString() + "}"; + } + + public static class ParentReference

, PK> { + private final Class

parentAssetClass; + private final PK parentKey; + + public ParentReference(Class

parentAssetClass, PK parentKey) { + this.parentAssetClass = parentAssetClass; + this.parentKey = parentKey; + } + + public Class

getParentAssetClass() { + return this.parentAssetClass; + } + + public PK getParentKey() { + return this.parentKey; + } + + @Nonnull + @Override + public String toString() { + return "ParentReference{parentAssetClass=" + this.parentAssetClass + ", parentKey=" + this.parentKey + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/assetstore/event/LoadedAssetsEvent.java b/src/com/hypixel/hytale/assetstore/event/LoadedAssetsEvent.java new file mode 100644 index 0000000..4ac8d09 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/event/LoadedAssetsEvent.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.assetstore.event; + +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.JsonAsset; +import java.util.Collections; +import java.util.Map; +import javax.annotation.Nonnull; + +public class LoadedAssetsEvent, M extends AssetMap> extends AssetsEvent { + @Nonnull + private final Class tClass; + @Nonnull + private final M assetMap; + @Nonnull + private final Map loadedAssets; + private final boolean initial; + @Nonnull + private final AssetUpdateQuery query; + + public LoadedAssetsEvent(@Nonnull Class tClass, @Nonnull M assetMap, @Nonnull Map loadedAssets, boolean initial, @Nonnull AssetUpdateQuery query) { + this.tClass = tClass; + this.assetMap = assetMap; + this.loadedAssets = Collections.unmodifiableMap(loadedAssets); + this.initial = initial; + this.query = query; + } + + public Class getAssetClass() { + return this.tClass; + } + + public M getAssetMap() { + return this.assetMap; + } + + @Nonnull + public Map getLoadedAssets() { + return this.loadedAssets; + } + + public boolean isInitial() { + return this.initial; + } + + @Nonnull + public AssetUpdateQuery getQuery() { + return this.query; + } + + @Nonnull + @Override + public String toString() { + return "LoadedAssetsEvent{loadedAssets=" + this.loadedAssets + ", initial=" + this.initial + ", query=" + this.query + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/assetstore/event/RegisterAssetStoreEvent.java b/src/com/hypixel/hytale/assetstore/event/RegisterAssetStoreEvent.java new file mode 100644 index 0000000..7bb992a --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/event/RegisterAssetStoreEvent.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.assetstore.event; + +import com.hypixel.hytale.assetstore.AssetStore; +import javax.annotation.Nonnull; + +public class RegisterAssetStoreEvent extends AssetStoreEvent { + public RegisterAssetStoreEvent(@Nonnull AssetStore assetStore) { + super(assetStore); + } +} diff --git a/src/com/hypixel/hytale/assetstore/event/RemoveAssetStoreEvent.java b/src/com/hypixel/hytale/assetstore/event/RemoveAssetStoreEvent.java new file mode 100644 index 0000000..e5e11ba --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/event/RemoveAssetStoreEvent.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.assetstore.event; + +import com.hypixel.hytale.assetstore.AssetStore; +import javax.annotation.Nonnull; + +public class RemoveAssetStoreEvent extends AssetStoreEvent { + public RemoveAssetStoreEvent(@Nonnull AssetStore assetStore) { + super(assetStore); + } +} diff --git a/src/com/hypixel/hytale/assetstore/event/RemovedAssetsEvent.java b/src/com/hypixel/hytale/assetstore/event/RemovedAssetsEvent.java new file mode 100644 index 0000000..258322f --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/event/RemovedAssetsEvent.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.assetstore.event; + +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.JsonAsset; +import java.util.Collections; +import java.util.Set; +import javax.annotation.Nonnull; + +public class RemovedAssetsEvent, M extends AssetMap> extends AssetsEvent { + private final Class tClass; + private final M assetMap; + @Nonnull + private final Set removedAssets; + private final boolean replaced; + + public RemovedAssetsEvent(Class tClass, M assetMap, @Nonnull Set removedAssets, boolean replaced) { + this.tClass = tClass; + this.assetMap = assetMap; + this.removedAssets = Collections.unmodifiableSet(removedAssets); + this.replaced = replaced; + } + + public Class getAssetClass() { + return this.tClass; + } + + public M getAssetMap() { + return this.assetMap; + } + + @Nonnull + public Set getRemovedAssets() { + return this.removedAssets; + } + + public boolean isReplaced() { + return this.replaced; + } + + @Nonnull + @Override + public String toString() { + return "RemovedAssetsEvent{removedAssets=" + this.removedAssets + ", replaced=" + this.replaced + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/assetstore/iterator/AssetStoreIterator.java b/src/com/hypixel/hytale/assetstore/iterator/AssetStoreIterator.java new file mode 100644 index 0000000..61c06d2 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/iterator/AssetStoreIterator.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.assetstore.iterator; + +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.JsonAsset; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetStoreIterator implements Iterator>, Closeable { + @Nonnull + private final List> list; + + public AssetStoreIterator(@Nonnull Collection> values) { + this.list = new ArrayList<>(values); + } + + @Override + public boolean hasNext() { + return !this.list.isEmpty(); + } + + @Nullable + public AssetStore next() { + Iterator> iterator = this.list.iterator(); + + while (iterator.hasNext()) { + AssetStore>, ? extends AssetMap>> assetStore = (AssetStore>, ? extends AssetMap>>)iterator.next(); + if (!this.isWaitingForDependencies(assetStore)) { + iterator.remove(); + return assetStore; + } + } + + return null; + } + + public int size() { + return this.list.size(); + } + + public boolean isWaitingForDependencies(@Nonnull AssetStore assetStore) { + for (Class> aClass : assetStore.getLoadsAfter()) { + AssetStore otherStore = AssetRegistry.getAssetStore(aClass); + if (otherStore == null) { + throw new IllegalArgumentException("Unable to find asset store: " + aClass); + } + + if (this.list.contains(otherStore)) { + return true; + } + } + + return false; + } + + public boolean isBeingWaitedFor(@Nonnull AssetStore assetStore) { + Class>> assetClass = (Class>>)assetStore.getAssetClass(); + + for (AssetStore store : this.list) { + if (store.getLoadsAfter().contains(assetClass)) { + return true; + } + } + + return false; + } + + @Override + public void close() { + } +} diff --git a/src/com/hypixel/hytale/assetstore/iterator/CircularDependencyException.java b/src/com/hypixel/hytale/assetstore/iterator/CircularDependencyException.java new file mode 100644 index 0000000..a8d9737 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/iterator/CircularDependencyException.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.assetstore.iterator; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.JsonAsset; +import java.util.Collection; +import javax.annotation.Nonnull; + +public class CircularDependencyException extends RuntimeException { + public CircularDependencyException(@Nonnull Collection> values, @Nonnull AssetStoreIterator iterator) { + super(makeMessage(values, iterator)); + } + + @Nonnull + protected static String makeMessage(@Nonnull Collection> values, @Nonnull AssetStoreIterator iterator) { + StringBuilder sb = new StringBuilder( + "Failed to process any stores there must be a circular dependency! " + values + ", " + iterator.size() + "\nWaiting for Asset Stores:\n" + ); + + for (AssetStore store : values) { + if (iterator.isWaitingForDependencies(store)) { + sb.append(store.getAssetClass()).append("\n"); + + for (Class> aClass : store.getLoadsAfter()) { + AssetStore otherStore = AssetRegistry.getAssetStore(aClass); + if (otherStore == null) { + throw new IllegalArgumentException("Unable to find asset store: " + aClass); + } + + if (iterator.isWaitingForDependencies(otherStore)) { + sb.append("\t- ").append(otherStore.getAssetClass()).append("\n"); + } + } + } + } + + return sb.toString(); + } +} diff --git a/src/com/hypixel/hytale/assetstore/map/AssetMapWithIndexes.java b/src/com/hypixel/hytale/assetstore/map/AssetMapWithIndexes.java new file mode 100644 index 0000000..853e43f --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/map/AssetMapWithIndexes.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.assetstore.map; + +import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap; +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.JsonAsset; +import com.hypixel.hytale.assetstore.codec.AssetCodec; +import it.unimi.dsi.fastutil.ints.IntIterator; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.IntSets; +import java.util.Map; +import javax.annotation.Nonnull; + +public abstract class AssetMapWithIndexes> extends DefaultAssetMap { + public static final int NOT_FOUND = Integer.MIN_VALUE; + protected final Int2ObjectConcurrentHashMap indexedTagStorage = new Int2ObjectConcurrentHashMap<>(); + protected final Int2ObjectConcurrentHashMap unmodifiableIndexedTagStorage = new Int2ObjectConcurrentHashMap<>(); + + public AssetMapWithIndexes() { + } + + @Override + protected void clear() { + super.clear(); + this.indexedTagStorage.clear(); + this.unmodifiableIndexedTagStorage.clear(); + } + + public IntSet getIndexesForTag(int index) { + return this.unmodifiableIndexedTagStorage.getOrDefault(index, IntSets.EMPTY_SET); + } + + @Override + protected void putAssetTags(AssetCodec codec, Map loadedAssets) { + } + + protected void putAssetTag(@Nonnull AssetCodec codec, K key, int index, T value) { + AssetExtraInfo.Data data = codec.getData(value); + if (data != null) { + IntIterator iterator = data.getExpandedTagIndexes().iterator(); + + while (iterator.hasNext()) { + int tag = iterator.nextInt(); + this.putAssetTag(key, index, tag); + } + } + } + + protected void putAssetTag(K key, int index, int tag) { + this.putAssetTag(key, tag); + this.indexedTagStorage.computeIfAbsent(tag, k -> { + IntSet set = Int2ObjectConcurrentHashMap.newKeySet(3); + this.unmodifiableIndexedTagStorage.put(k, IntSets.unmodifiable(set)); + return set; + }).add(index); + } + + @Override + public boolean requireReplaceOnRemove() { + return true; + } +} diff --git a/src/com/hypixel/hytale/assetstore/map/BlockTypeAssetMap.java b/src/com/hypixel/hytale/assetstore/map/BlockTypeAssetMap.java new file mode 100644 index 0000000..4ff3fa8 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/map/BlockTypeAssetMap.java @@ -0,0 +1,256 @@ +package com.hypixel.hytale.assetstore.map; + +import com.hypixel.hytale.assetstore.codec.AssetCodec; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import it.unimi.dsi.fastutil.objects.ObjectSets; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.StampedLock; +import java.util.function.Function; +import java.util.function.IntFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockTypeAssetMap>> extends AssetMapWithIndexes { + private final AtomicInteger nextIndex = new AtomicInteger(); + private final StampedLock keyToIndexLock = new StampedLock(); + private final Object2IntMap keyToIndex = new Object2IntOpenCustomHashMap<>(CaseInsensitiveHashStrategy.getInstance()); + @Nonnull + private final IntFunction arrayProvider; + private final ReentrantLock arrayLock = new ReentrantLock(); + private T[] array; + private final Map> subKeyMap = new Object2ObjectOpenCustomHashMap<>(CaseInsensitiveHashStrategy.getInstance()); + @Deprecated + private final Function groupGetter; + @Deprecated + private final Object2IntMap groupMap = new Object2IntOpenHashMap<>(); + + public BlockTypeAssetMap(@Nonnull IntFunction arrayProvider, Function groupGetter) { + this.arrayProvider = arrayProvider; + this.groupGetter = groupGetter; + this.array = (T[])((JsonAssetWithMap[])arrayProvider.apply(0)); + this.keyToIndex.defaultReturnValue(Integer.MIN_VALUE); + } + + public int getIndex(K key) { + long stamp = this.keyToIndexLock.tryOptimisticRead(); + int value = this.keyToIndex.getInt(key); + if (this.keyToIndexLock.validate(stamp)) { + return value; + } else { + stamp = this.keyToIndexLock.readLock(); + + int var5; + try { + var5 = this.keyToIndex.getInt(key); + } finally { + this.keyToIndexLock.unlockRead(stamp); + } + + return var5; + } + } + + public int getIndexOrDefault(K key, int def) { + long stamp = this.keyToIndexLock.tryOptimisticRead(); + int value = this.keyToIndex.getOrDefault(key, def); + if (this.keyToIndexLock.validate(stamp)) { + return value; + } else { + stamp = this.keyToIndexLock.readLock(); + + int var6; + try { + var6 = this.keyToIndex.getOrDefault(key, def); + } finally { + this.keyToIndexLock.unlockRead(stamp); + } + + return var6; + } + } + + public int getNextIndex() { + this.arrayLock.lock(); + + int var1; + try { + var1 = this.array.length; + } finally { + this.arrayLock.unlock(); + } + + return var1; + } + + @Nullable + public T getAsset(int index) { + return index >= 0 && index < this.array.length ? this.array[index] : null; + } + + public T getAssetOrDefault(int index, T def) { + return index >= 0 && index < this.array.length ? this.array[index] : def; + } + + @Nonnull + public ObjectSet getSubKeys(K key) { + ObjectSet subKeySet = this.subKeyMap.get(key); + return subKeySet != null ? ObjectSets.unmodifiable(subKeySet) : ObjectSets.singleton(key); + } + + public int getGroupId(String group) { + return this.groupMap.getInt(group); + } + + @Nonnull + public String[] getGroups() { + return this.groupMap.keySet().toArray(String[]::new); + } + + @Override + protected void clear() { + super.clear(); + long stamp = this.keyToIndexLock.writeLock(); + this.arrayLock.lock(); + + try { + this.keyToIndex.clear(); + this.array = (T[])((JsonAssetWithMap[])this.arrayProvider.apply(0)); + } finally { + this.arrayLock.unlock(); + this.keyToIndexLock.unlockWrite(stamp); + } + } + + @Override + protected void putAll( + @Nonnull String packKey, + @Nonnull AssetCodec codec, + @Nonnull Map loadedAssets, + @Nonnull Map loadedKeyToPathMap, + @Nonnull Map> loadedAssetChildren + ) { + super.putAll(packKey, codec, loadedAssets, loadedKeyToPathMap, loadedAssetChildren); + this.putAll0(codec, loadedAssets); + } + + private void putAll0(@Nonnull AssetCodec codec, @Nonnull Map loadedAssets) { + long stamp = this.keyToIndexLock.writeLock(); + this.arrayLock.lock(); + + try { + int highestIndex = 0; + + for (K key : loadedAssets.keySet()) { + int index = this.keyToIndex.getInt(key); + if (index == Integer.MIN_VALUE) { + this.keyToIndex.put(key, index = this.nextIndex.getAndIncrement()); + } + + if (index < 0) { + throw new IllegalArgumentException("Index can't be less than zero!"); + } + + if (index > highestIndex) { + highestIndex = index; + } + } + + int length = highestIndex + 1; + if (length < 0) { + throw new IllegalArgumentException("Highest index can't be less than zero!"); + } + + if (length > this.array.length) { + T[] newArray = (T[])((JsonAssetWithMap[])this.arrayProvider.apply(length)); + System.arraycopy(this.array, 0, newArray, 0, this.array.length); + this.array = newArray; + } + + for (Entry entry : loadedAssets.entrySet()) { + K key = entry.getKey(); + int indexx = this.keyToIndex.getInt(key); + if (indexx < 0) { + throw new IllegalArgumentException("Index can't be less than zero!"); + } + + T value = entry.getValue(); + this.array[indexx] = value; + ObjectSet subKeySet = this.subKeyMap.get(key); + if (subKeySet != null) { + subKeySet.add(key); + } + + String group = this.groupGetter.apply(value); + if (!this.groupMap.containsKey(group)) { + int groupIndex = this.groupMap.size(); + this.groupMap.put(group, groupIndex); + } + + this.putAssetTag(codec, key, indexx, value); + } + } finally { + this.arrayLock.unlock(); + this.keyToIndexLock.unlockWrite(stamp); + } + } + + @Override + protected Set remove(@Nonnull Set keys) { + Set remove = super.remove(keys); + this.remove0(keys); + return remove; + } + + @Override + protected Set remove(@Nonnull String packKey, @Nonnull Set keys, @Nonnull List> pathsToReload) { + Set remove = super.remove(packKey, keys, pathsToReload); + this.remove0(keys); + return remove; + } + + private void remove0(@Nonnull Set keys) { + long stamp = this.keyToIndexLock.writeLock(); + this.arrayLock.lock(); + + try { + for (K key : keys) { + int blockId = this.keyToIndex.getInt(key); + if (blockId != Integer.MIN_VALUE) { + this.array[blockId] = null; + this.indexedTagStorage.forEachWithInt((_k, value, id) -> value.remove(id), blockId); + } + + ObjectSet subKeySet = this.subKeyMap.get(key); + if (subKeySet != null) { + subKeySet.remove(key); + } + } + + int i = this.array.length - 1; + + while (i > 0 && this.array[i] == null) { + i--; + } + + int length = i + 1; + if (length != this.array.length) { + T[] newArray = (T[])((JsonAssetWithMap[])this.arrayProvider.apply(length)); + System.arraycopy(this.array, 0, newArray, 0, newArray.length); + this.array = newArray; + } + } finally { + this.arrayLock.unlock(); + this.keyToIndexLock.unlockWrite(stamp); + } + } +} diff --git a/src/com/hypixel/hytale/assetstore/map/CaseInsensitiveHashStrategy.java b/src/com/hypixel/hytale/assetstore/map/CaseInsensitiveHashStrategy.java new file mode 100644 index 0000000..9b9f6aa --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/map/CaseInsensitiveHashStrategy.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.assetstore.map; + +import it.unimi.dsi.fastutil.Hash.Strategy; + +public class CaseInsensitiveHashStrategy implements Strategy { + private static final CaseInsensitiveHashStrategy INSTANCE = new CaseInsensitiveHashStrategy(); + + public CaseInsensitiveHashStrategy() { + } + + public static CaseInsensitiveHashStrategy getInstance() { + return INSTANCE; + } + + @Override + public int hashCode(K key) { + if (key == null) { + return 0; + } else if (!(key instanceof String s)) { + return key.hashCode(); + } else { + int hash = 0; + + for (int i = 0; i < s.length(); i++) { + hash = 31 * hash + Character.toLowerCase(s.charAt(i)); + } + + return hash; + } + } + + @Override + public boolean equals(K a, K b) { + if (a == b) { + return true; + } else if (a != null && b != null) { + return a instanceof String sa && b instanceof String sb ? sa.equalsIgnoreCase(sb) : a.equals(b); + } else { + return false; + } + } +} diff --git a/src/com/hypixel/hytale/assetstore/map/DefaultAssetMap.java b/src/com/hypixel/hytale/assetstore/map/DefaultAssetMap.java new file mode 100644 index 0000000..4b66db3 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/map/DefaultAssetMap.java @@ -0,0 +1,503 @@ +package com.hypixel.hytale.assetstore.map; + +import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap; +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.JsonAsset; +import com.hypixel.hytale.assetstore.codec.AssetCodec; +import it.unimi.dsi.fastutil.ints.IntIterator; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.IntSets; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import it.unimi.dsi.fastutil.objects.ObjectSets; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.StampedLock; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DefaultAssetMap> extends AssetMap { + public static final DefaultAssetMap.AssetRef[] EMPTY_PAIR_ARRAY = new DefaultAssetMap.AssetRef[0]; + public static final String DEFAULT_PACK_KEY = "Hytale:Hytale"; + protected final StampedLock assetMapLock = new StampedLock(); + @Nonnull + protected final Map assetMap; + protected final Map[]> assetChainMap; + protected final Map> packAssetKeys = new ConcurrentHashMap<>(); + protected final Map> pathToKeyMap = new ConcurrentHashMap<>(); + protected final Map> assetChildren; + protected final Int2ObjectConcurrentHashMap> tagStorage = new Int2ObjectConcurrentHashMap<>(); + protected final Int2ObjectConcurrentHashMap> unmodifiableTagStorage = new Int2ObjectConcurrentHashMap<>(); + protected final IntSet unmodifiableTagKeys = IntSets.unmodifiable(this.tagStorage.keySet()); + + public DefaultAssetMap() { + this.assetMap = new Object2ObjectOpenCustomHashMap<>(CaseInsensitiveHashStrategy.getInstance()); + this.assetChainMap = new Object2ObjectOpenCustomHashMap<>(CaseInsensitiveHashStrategy.getInstance()); + this.assetChildren = new Object2ObjectOpenCustomHashMap<>(CaseInsensitiveHashStrategy.getInstance()); + } + + public DefaultAssetMap(@Nonnull Map assetMap) { + this.assetMap = assetMap; + this.assetChainMap = new Object2ObjectOpenCustomHashMap<>(CaseInsensitiveHashStrategy.getInstance()); + this.assetChildren = new Object2ObjectOpenCustomHashMap<>(CaseInsensitiveHashStrategy.getInstance()); + } + + @Nullable + @Override + public T getAsset(K key) { + long stamp = this.assetMapLock.tryOptimisticRead(); + T value = this.assetMap.get(key); + if (this.assetMapLock.validate(stamp)) { + return value; + } else { + stamp = this.assetMapLock.readLock(); + + JsonAsset var5; + try { + var5 = this.assetMap.get(key); + } finally { + this.assetMapLock.unlockRead(stamp); + } + + return (T)var5; + } + } + + @Nullable + @Override + public T getAsset(@Nonnull String packKey, K key) { + long stamp = this.assetMapLock.tryOptimisticRead(); + T result = this.getAssetForPack0(packKey, key); + if (this.assetMapLock.validate(stamp)) { + return result; + } else { + stamp = this.assetMapLock.readLock(); + + JsonAsset var6; + try { + var6 = this.getAssetForPack0(packKey, key); + } finally { + this.assetMapLock.unlockRead(stamp); + } + + return (T)var6; + } + } + + private T getAssetForPack0(@Nonnull String packKey, K key) { + DefaultAssetMap.AssetRef[] chain = this.assetChainMap.get(key); + if (chain == null) { + return null; + } else { + for (int i = 0; i < chain.length; i++) { + DefaultAssetMap.AssetRef pair = chain[i]; + if (Objects.equals(pair.pack, packKey)) { + if (i == 0) { + return null; + } + + return chain[i - 1].value; + } + } + + return this.assetMap.get(key); + } + } + + @Nullable + @Override + public Path getPath(K key) { + long stamp = this.assetMapLock.tryOptimisticRead(); + Path result = this.getPath0(key); + if (this.assetMapLock.validate(stamp)) { + return result; + } else { + stamp = this.assetMapLock.readLock(); + + Path var5; + try { + var5 = this.getPath0(key); + } finally { + this.assetMapLock.unlockRead(stamp); + } + + return var5; + } + } + + @Nullable + @Override + public String getAssetPack(K key) { + long stamp = this.assetMapLock.tryOptimisticRead(); + String result = this.getAssetPack0(key); + if (this.assetMapLock.validate(stamp)) { + return result; + } else { + stamp = this.assetMapLock.readLock(); + + String var5; + try { + var5 = this.getAssetPack0(key); + } finally { + this.assetMapLock.unlockRead(stamp); + } + + return var5; + } + } + + @Nullable + private Path getPath0(K key) { + DefaultAssetMap.AssetRef result = this.getAssetRef(key); + return result != null ? result.path : null; + } + + @Nullable + private String getAssetPack0(K key) { + DefaultAssetMap.AssetRef result = this.getAssetRef(key); + return result != null ? result.pack : null; + } + + @Nullable + private DefaultAssetMap.AssetRef getAssetRef(K key) { + DefaultAssetMap.AssetRef[] chain = this.assetChainMap.get(key); + return chain == null ? null : chain[chain.length - 1]; + } + + @Override + public Set getKeys(@Nonnull Path path) { + ObjectSet set = this.pathToKeyMap.get(path); + return set == null ? ObjectSets.emptySet() : ObjectSets.unmodifiable(set); + } + + @Override + public Set getChildren(K key) { + long stamp = this.assetMapLock.tryOptimisticRead(); + ObjectSet children = this.assetChildren.get(key); + Set result = children == null ? ObjectSets.emptySet() : ObjectSets.unmodifiable(children); + if (this.assetMapLock.validate(stamp)) { + return result; + } else { + stamp = this.assetMapLock.readLock(); + + ObjectSet var6; + try { + children = this.assetChildren.get(key); + var6 = children == null ? ObjectSets.emptySet() : ObjectSets.unmodifiable(children); + } finally { + this.assetMapLock.unlockRead(stamp); + } + + return var6; + } + } + + @Override + public int getAssetCount() { + return this.assetMap.size(); + } + + @Nonnull + @Override + public Map getAssetMap() { + return Collections.unmodifiableMap(this.assetMap); + } + + @Nonnull + @Override + public Map getPathMap(@Nonnull String packKey) { + long stamp = this.assetMapLock.readLock(); + + Map var4; + try { + var4 = this.assetChainMap + .entrySet() + .stream() + .map(e -> Map.entry(e.getKey(), Arrays.stream(e.getValue()).filter(v -> Objects.equals(v.pack, packKey)).findFirst())) + .filter(e -> e.getValue().isPresent()) + .filter(e -> e.getValue().get().path != null) + .collect(Collectors.toMap(Entry::getKey, e -> e.getValue().get().path)); + } finally { + this.assetMapLock.unlockRead(stamp); + } + + return var4; + } + + @Override + public Set getKeysForTag(int tagIndex) { + return this.unmodifiableTagStorage.getOrDefault(tagIndex, ObjectSets.emptySet()); + } + + @Nonnull + @Override + public IntSet getTagIndexes() { + return this.unmodifiableTagKeys; + } + + @Override + public int getTagCount() { + return this.tagStorage.size(); + } + + @Override + protected void clear() { + long stamp = this.assetMapLock.writeLock(); + + try { + this.assetChildren.clear(); + this.assetChainMap.clear(); + this.pathToKeyMap.clear(); + this.assetMap.clear(); + this.tagStorage.clear(); + this.unmodifiableTagStorage.clear(); + } finally { + this.assetMapLock.unlockWrite(stamp); + } + } + + @Override + protected void putAll( + @Nonnull String packKey, + @Nonnull AssetCodec codec, + @Nonnull Map loadedAssets, + @Nonnull Map loadedKeyToPathMap, + @Nonnull Map> loadedAssetChildren + ) { + long stamp = this.assetMapLock.writeLock(); + + try { + for (Entry> entry : loadedAssetChildren.entrySet()) { + this.assetChildren.computeIfAbsent(entry.getKey(), k -> new ObjectOpenHashSet<>(3)).addAll(entry.getValue()); + } + + for (Entry entry : loadedKeyToPathMap.entrySet()) { + this.pathToKeyMap.computeIfAbsent(entry.getValue(), k -> new ObjectOpenHashSet<>(1)).add(entry.getKey()); + } + + for (Entry e : loadedAssets.entrySet()) { + K key = e.getKey(); + this.packAssetKeys.computeIfAbsent(packKey, v -> new ObjectOpenHashSet<>()).add(key); + DefaultAssetMap.AssetRef[] chain = this.assetChainMap.get(key); + if (chain == null) { + chain = EMPTY_PAIR_ARRAY; + } + + boolean found = false; + DefaultAssetMap.AssetRef[] finalVal = chain; + int var14 = chain.length; + int var15 = 0; + + while (true) { + if (var15 < var14) { + DefaultAssetMap.AssetRef pair = finalVal[var15]; + if (!Objects.equals(pair.pack, packKey)) { + var15++; + continue; + } + + pair.value = e.getValue(); + found = true; + } + + if (!found) { + chain = Arrays.copyOf(chain, chain.length + 1); + chain[chain.length - 1] = new DefaultAssetMap.AssetRef<>(packKey, loadedKeyToPathMap.get(e.getKey()), e.getValue()); + this.assetChainMap.put(key, chain); + } + + T finalValx = chain[chain.length - 1].value; + this.assetMap.put(key, finalValx); + break; + } + } + } finally { + this.assetMapLock.unlockWrite(stamp); + } + + this.putAssetTags(codec, loadedAssets); + } + + protected void putAssetTags(@Nonnull AssetCodec codec, @Nonnull Map loadedAssets) { + for (Entry entry : loadedAssets.entrySet()) { + AssetExtraInfo.Data data = codec.getData(entry.getValue()); + if (data != null) { + K key = entry.getKey(); + IntIterator iterator = data.getExpandedTagIndexes().iterator(); + + while (iterator.hasNext()) { + int tag = iterator.nextInt(); + this.putAssetTag(key, tag); + } + } + } + } + + protected void putAssetTag(K key, int tag) { + this.tagStorage.computeIfAbsent(tag, k -> { + ObjectOpenHashSet set = new ObjectOpenHashSet<>(3); + this.unmodifiableTagStorage.put(k, ObjectSets.unmodifiable(set)); + return set; + }).add(key); + } + + @Override + public Set getKeysForPack(@Nonnull String name) { + return this.packAssetKeys.get(name); + } + + @Override + protected Set remove(@Nonnull Set keys) { + long stamp = this.assetMapLock.writeLock(); + + Object var16; + try { + Set children = new HashSet<>(); + + for (K key : keys) { + DefaultAssetMap.AssetRef[] chain = this.assetChainMap.remove(key); + if (chain != null) { + DefaultAssetMap.AssetRef info = chain[chain.length - 1]; + if (info.path != null) { + this.pathToKeyMap.computeIfPresent(info.path, (p, list) -> { + list.remove(key); + return list.isEmpty() ? null : list; + }); + } + + this.assetMap.remove(key); + + for (DefaultAssetMap.AssetRef c : chain) { + this.packAssetKeys.get(Objects.requireNonNullElse(c.pack, "Hytale:Hytale")).remove(key); + } + + for (ObjectSet child : this.assetChildren.values()) { + child.remove(key); + } + + ObjectSet child = this.assetChildren.remove(key); + if (child != null) { + children.addAll(child); + } + } + } + + this.tagStorage.forEach((_k, value, removedKeys) -> value.removeAll(removedKeys), keys); + children.removeAll(keys); + var16 = children; + } finally { + this.assetMapLock.unlockWrite(stamp); + } + + return (Set)var16; + } + + @Override + protected Set remove(@Nonnull String packKey, @Nonnull Set keys, @Nonnull List> pathsToReload) { + long stamp = this.assetMapLock.writeLock(); + + Set iterator; + try { + Set children = new HashSet<>(); + ObjectSet packKeys = this.packAssetKeys.get(Objects.requireNonNullElse(packKey, "Hytale:Hytale")); + if (packKeys != null) { + Iterator iteratorx = keys.iterator(); + + while (iteratorx.hasNext()) { + K key = iteratorx.next(); + packKeys.remove(key); + DefaultAssetMap.AssetRef[] chain = this.assetChainMap.remove(key); + if (chain.length == 1) { + DefaultAssetMap.AssetRef info = chain[0]; + if (!Objects.equals(info.pack, packKey)) { + iteratorx.remove(); + this.assetChainMap.put(key, chain); + } else { + if (info.path != null) { + this.pathToKeyMap.computeIfPresent(info.path, (p, list) -> { + list.remove(key); + return list.isEmpty() ? null : list; + }); + } + + this.assetMap.remove(key); + + for (ObjectSet child : this.assetChildren.values()) { + child.remove(key); + } + + ObjectSet child = this.assetChildren.remove(key); + if (child != null) { + children.addAll(child); + } + } + } else { + iteratorx.remove(); + DefaultAssetMap.AssetRef[] newChain = new DefaultAssetMap.AssetRef[chain.length - 1]; + int offset = 0; + + for (int i = 0; i < chain.length; i++) { + DefaultAssetMap.AssetRef pair = chain[i]; + if (Objects.equals(pair.pack, packKey)) { + if (pair.path != null) { + this.pathToKeyMap.computeIfPresent(pair.path, (p, list) -> { + list.remove(key); + return list.isEmpty() ? null : list; + }); + } + } else { + newChain[offset++] = pair; + if (pair.path != null) { + pathsToReload.add(Map.entry(pair.pack, pair.path)); + } else { + pathsToReload.add(Map.entry(pair.pack, pair.value)); + } + } + } + + this.assetChainMap.put(key, newChain); + DefaultAssetMap.AssetRef newAsset = newChain[newChain.length - 1]; + this.assetMap.put(key, newAsset.value); + if (newAsset.path != null) { + this.pathToKeyMap.computeIfAbsent(newAsset.path, k -> new ObjectOpenHashSet<>(1)).add(key); + } + } + } + + this.tagStorage.forEach((_k, value, removedKeys) -> value.removeAll(removedKeys), keys); + children.removeAll(keys); + return children; + } + + iterator = Collections.emptySet(); + } finally { + this.assetMapLock.unlockWrite(stamp); + } + + return iterator; + } + + protected static class AssetRef { + protected final String pack; + protected final Path path; + protected T value; + + protected AssetRef(String pack, Path path, T value) { + this.pack = pack; + this.path = path; + this.value = value; + } + } +} diff --git a/src/com/hypixel/hytale/assetstore/map/IndexedAssetMap.java b/src/com/hypixel/hytale/assetstore/map/IndexedAssetMap.java new file mode 100644 index 0000000..cdff379 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/map/IndexedAssetMap.java @@ -0,0 +1,131 @@ +package com.hypixel.hytale.assetstore.map; + +import com.hypixel.hytale.assetstore.codec.AssetCodec; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.StampedLock; +import javax.annotation.Nonnull; + +public class IndexedAssetMap>> extends AssetMapWithIndexes { + private final AtomicInteger nextIndex = new AtomicInteger(); + private final StampedLock keyToIndexLock = new StampedLock(); + private final Object2IntMap keyToIndex = new Object2IntOpenCustomHashMap<>(CaseInsensitiveHashStrategy.getInstance()); + + public IndexedAssetMap() { + this.keyToIndex.defaultReturnValue(Integer.MIN_VALUE); + } + + public int getIndex(K key) { + long stamp = this.keyToIndexLock.tryOptimisticRead(); + int value = this.keyToIndex.getInt(key); + if (this.keyToIndexLock.validate(stamp)) { + return value; + } else { + stamp = this.keyToIndexLock.readLock(); + + int var5; + try { + var5 = this.keyToIndex.getInt(key); + } finally { + this.keyToIndexLock.unlockRead(stamp); + } + + return var5; + } + } + + public int getIndexOrDefault(K key, int def) { + long stamp = this.keyToIndexLock.tryOptimisticRead(); + int value = this.keyToIndex.getOrDefault(key, def); + if (this.keyToIndexLock.validate(stamp)) { + return value; + } else { + stamp = this.keyToIndexLock.readLock(); + + int var6; + try { + var6 = this.keyToIndex.getOrDefault(key, def); + } finally { + this.keyToIndexLock.unlockRead(stamp); + } + + return var6; + } + } + + public int getNextIndex() { + return this.nextIndex.get(); + } + + @Override + protected void clear() { + super.clear(); + long stamp = this.keyToIndexLock.writeLock(); + + try { + this.keyToIndex.clear(); + } finally { + this.keyToIndexLock.unlockWrite(stamp); + } + } + + @Override + protected void putAll( + @Nonnull String packKey, + @Nonnull AssetCodec codec, + @Nonnull Map loadedAssets, + @Nonnull Map loadedKeyToPathMap, + @Nonnull Map> loadedAssetChildren + ) { + super.putAll(packKey, codec, loadedAssets, loadedKeyToPathMap, loadedAssetChildren); + long stamp = this.keyToIndexLock.writeLock(); + + try { + for (Entry entry : loadedAssets.entrySet()) { + K key = entry.getKey(); + int index; + if ((index = this.keyToIndex.getInt(key)) == Integer.MIN_VALUE) { + this.keyToIndex.put(key, index = this.nextIndex.getAndIncrement()); + } + + T value = entry.getValue(); + this.putAssetTag(codec, key, index, value); + } + } finally { + this.keyToIndexLock.unlockWrite(stamp); + } + } + + @Override + protected Set remove(@Nonnull Set keys) { + Set remove = super.remove(keys); + this.remove0(keys); + return remove; + } + + @Override + protected Set remove(@Nonnull String packKey, @Nonnull Set keys, @Nonnull List> pathsToReload) { + Set remove = super.remove(packKey, keys, pathsToReload); + this.remove0(keys); + return remove; + } + + private void remove0(@Nonnull Set keys) { + long stamp = this.keyToIndexLock.writeLock(); + + try { + for (K key : keys) { + int index = this.keyToIndex.removeInt(key); + this.indexedTagStorage.forEachWithInt((_k, value, idx) -> value.remove(idx), index); + } + } finally { + this.keyToIndexLock.unlockWrite(stamp); + } + } +} diff --git a/src/com/hypixel/hytale/assetstore/map/IndexedLookupTableAssetMap.java b/src/com/hypixel/hytale/assetstore/map/IndexedLookupTableAssetMap.java new file mode 100644 index 0000000..ff54f8d --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/map/IndexedLookupTableAssetMap.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.assetstore.map; + +import com.hypixel.hytale.assetstore.codec.AssetCodec; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.StampedLock; +import java.util.function.IntFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class IndexedLookupTableAssetMap>> extends AssetMapWithIndexes { + private final AtomicInteger nextIndex = new AtomicInteger(); + private final StampedLock keyToIndexLock = new StampedLock(); + private final Object2IntMap keyToIndex = new Object2IntOpenCustomHashMap<>(CaseInsensitiveHashStrategy.getInstance()); + @Nonnull + private final IntFunction arrayProvider; + private final ReentrantLock arrayLock = new ReentrantLock(); + private T[] array; + + public IndexedLookupTableAssetMap(@Nonnull IntFunction arrayProvider) { + this.arrayProvider = arrayProvider; + this.array = (T[])((JsonAssetWithMap[])arrayProvider.apply(0)); + this.keyToIndex.defaultReturnValue(Integer.MIN_VALUE); + } + + public int getIndex(K key) { + long stamp = this.keyToIndexLock.tryOptimisticRead(); + int value = this.keyToIndex.getInt(key); + if (this.keyToIndexLock.validate(stamp)) { + return value; + } else { + stamp = this.keyToIndexLock.readLock(); + + int var5; + try { + var5 = this.keyToIndex.getInt(key); + } finally { + this.keyToIndexLock.unlockRead(stamp); + } + + return var5; + } + } + + public int getIndexOrDefault(K key, int def) { + long stamp = this.keyToIndexLock.tryOptimisticRead(); + int value = this.keyToIndex.getOrDefault(key, def); + if (this.keyToIndexLock.validate(stamp)) { + return value; + } else { + stamp = this.keyToIndexLock.readLock(); + + int var6; + try { + var6 = this.keyToIndex.getOrDefault(key, def); + } finally { + this.keyToIndexLock.unlockRead(stamp); + } + + return var6; + } + } + + public int getNextIndex() { + return this.nextIndex.get(); + } + + @Nullable + public T getAsset(int index) { + return index >= 0 && index < this.array.length ? this.array[index] : null; + } + + public T getAssetOrDefault(int index, T def) { + return index >= 0 && index < this.array.length ? this.array[index] : def; + } + + @Override + protected void clear() { + super.clear(); + long stamp = this.keyToIndexLock.writeLock(); + this.arrayLock.lock(); + + try { + this.keyToIndex.clear(); + this.array = (T[])((JsonAssetWithMap[])this.arrayProvider.apply(0)); + } finally { + this.arrayLock.unlock(); + this.keyToIndexLock.unlockWrite(stamp); + } + } + + @Override + protected void putAll( + @Nonnull String packKey, + @Nonnull AssetCodec codec, + @Nonnull Map loadedAssets, + @Nonnull Map loadedKeyToPathMap, + @Nonnull Map> loadedAssetChildren + ) { + super.putAll(packKey, codec, loadedAssets, loadedKeyToPathMap, loadedAssetChildren); + this.putAll0(codec, loadedAssets); + } + + private void putAll0(@Nonnull AssetCodec codec, @Nonnull Map loadedAssets) { + long stamp = this.keyToIndexLock.writeLock(); + this.arrayLock.lock(); + + try { + int highestIndex = 0; + + for (K key : loadedAssets.keySet()) { + int index = this.keyToIndex.getInt(key); + if (index == Integer.MIN_VALUE) { + this.keyToIndex.put(key, index = this.nextIndex.getAndIncrement()); + } + + if (index < 0) { + throw new IllegalArgumentException("Index can't be less than zero!"); + } + + if (index > highestIndex) { + highestIndex = index; + } + } + + int length = highestIndex + 1; + if (length < 0) { + throw new IllegalArgumentException("Highest index can't be less than zero!"); + } + + if (length > this.array.length) { + T[] newArray = (T[])((JsonAssetWithMap[])this.arrayProvider.apply(length)); + System.arraycopy(this.array, 0, newArray, 0, this.array.length); + this.array = newArray; + } + + for (Entry entry : loadedAssets.entrySet()) { + K key = entry.getKey(); + int indexx = this.keyToIndex.getInt(key); + if (indexx < 0) { + throw new IllegalArgumentException("Index can't be less than zero!"); + } + + T value = entry.getValue(); + this.array[indexx] = value; + this.putAssetTag(codec, key, indexx, value); + } + } finally { + this.arrayLock.unlock(); + this.keyToIndexLock.unlockWrite(stamp); + } + } + + @Override + protected Set remove(@Nonnull Set keys) { + Set remove = super.remove(keys); + this.remove0(keys); + return remove; + } + + @Override + protected Set remove(@Nonnull String packKey, @Nonnull Set keys, @Nonnull List> pathsToReload) { + Set remove = super.remove(packKey, keys, pathsToReload); + this.remove0(keys); + return remove; + } + + private void remove0(@Nonnull Set keys) { + long stamp = this.keyToIndexLock.writeLock(); + this.arrayLock.lock(); + + try { + for (K key : keys) { + int blockId = this.keyToIndex.getInt(key); + if (blockId != Integer.MIN_VALUE) { + this.array[blockId] = null; + this.indexedTagStorage.forEachWithInt((_k, value, id) -> value.remove(id), blockId); + } + } + + int i = this.array.length - 1; + + while (i > 0 && this.array[i] == null) { + i--; + } + + int length = i + 1; + if (length != this.array.length) { + T[] newArray = (T[])((JsonAssetWithMap[])this.arrayProvider.apply(length)); + System.arraycopy(this.array, 0, newArray, 0, newArray.length); + this.array = newArray; + } + } finally { + this.arrayLock.unlock(); + this.keyToIndexLock.unlockWrite(stamp); + } + } +} diff --git a/src/com/hypixel/hytale/assetstore/map/JsonAssetWithMap.java b/src/com/hypixel/hytale/assetstore/map/JsonAssetWithMap.java new file mode 100644 index 0000000..9f7df95 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/map/JsonAssetWithMap.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.assetstore.map; + +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.JsonAsset; + +public interface JsonAssetWithMap> extends JsonAsset { +} diff --git a/src/com/hypixel/hytale/assetstore/map/LookupTableAssetMap.java b/src/com/hypixel/hytale/assetstore/map/LookupTableAssetMap.java new file mode 100644 index 0000000..6b7e153 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/map/LookupTableAssetMap.java @@ -0,0 +1,145 @@ +package com.hypixel.hytale.assetstore.map; + +import com.hypixel.hytale.assetstore.codec.AssetCodec; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.IntFunction; +import java.util.function.IntSupplier; +import java.util.function.ToIntFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class LookupTableAssetMap>> extends AssetMapWithIndexes { + @Nonnull + private final IntFunction arrayProvider; + private final ToIntFunction indexGetter; + private final IntSupplier maxIndexGetter; + private final ReentrantLock arrayLock = new ReentrantLock(); + private T[] array; + + public LookupTableAssetMap(@Nonnull IntFunction arrayProvider, ToIntFunction indexGetter, IntSupplier maxIndexGetter) { + this.arrayProvider = arrayProvider; + this.indexGetter = indexGetter; + this.maxIndexGetter = maxIndexGetter; + this.array = (T[])((JsonAssetWithMap[])arrayProvider.apply(0)); + } + + @Nullable + public T getAsset(int index) { + return index >= 0 && index < this.array.length ? this.array[index] : null; + } + + public T getAssetOrDefault(int index, T def) { + return index >= 0 && index < this.array.length ? this.array[index] : def; + } + + @Override + protected void clear() { + super.clear(); + this.arrayLock.lock(); + + try { + this.array = (T[])((JsonAssetWithMap[])this.arrayProvider.apply(0)); + } finally { + this.arrayLock.unlock(); + } + } + + @Override + protected void putAll( + @Nonnull String packKey, + @Nonnull AssetCodec codec, + @Nonnull Map loadedAssets, + @Nonnull Map loadedKeyToPathMap, + @Nonnull Map> loadedAssetChildren + ) { + super.putAll(packKey, codec, loadedAssets, loadedKeyToPathMap, loadedAssetChildren); + this.arrayLock.lock(); + + try { + this.resize(); + + for (Entry entry : loadedAssets.entrySet()) { + K key = entry.getKey(); + int index = this.indexGetter.applyAsInt(key); + if (index < 0) { + throw new IllegalArgumentException("Index can't be less than zero!"); + } + + if (index >= this.array.length) { + throw new IllegalArgumentException("Index can't be higher than the max index!"); + } + + T value = entry.getValue(); + this.array[index] = value; + this.putAssetTag(codec, key, index, value); + } + } finally { + this.arrayLock.unlock(); + } + } + + @Override + protected Set remove(@Nonnull Set keys) { + Set remove = super.remove(keys); + this.remove0(keys); + return remove; + } + + @Override + protected Set remove(@Nonnull String packKey, @Nonnull Set keys, @Nonnull List> pathsToReload) { + Set remove = super.remove(packKey, keys, pathsToReload); + this.remove0(keys); + return remove; + } + + private void remove0(@Nonnull Set keys) { + this.arrayLock.lock(); + + try { + for (K key : keys) { + int blockId = this.indexGetter.applyAsInt(key); + if (blockId != Integer.MIN_VALUE) { + this.array[blockId] = null; + this.indexedTagStorage.forEachWithInt((_k, value, id) -> value.remove(id), blockId); + } + } + + this.resize(); + } finally { + this.arrayLock.unlock(); + } + } + + private void resize() { + int length = this.maxIndexGetter.getAsInt(); + if (length < 0) { + throw new IllegalArgumentException("max index can't be less than zero!"); + } else { + if (length > this.array.length) { + T[] newArray = (T[])((JsonAssetWithMap[])this.arrayProvider.apply(length)); + System.arraycopy(this.array, 0, newArray, 0, this.array.length); + this.array = newArray; + } else if (length < this.array.length) { + for (int i = length; i < this.array.length; i++) { + if (this.array[i] != null) { + throw new IllegalArgumentException("Assets exist in the array outside of the max index!"); + } + } + + T[] newArray = (T[])((JsonAssetWithMap[])this.arrayProvider.apply(length)); + System.arraycopy(this.array, 0, newArray, 0, newArray.length); + this.array = newArray; + } + } + } + + @Override + public boolean requireReplaceOnRemove() { + return false; + } +} diff --git a/src/com/hypixel/hytale/assetstore/map/ProvidedIndexAssetMap.java b/src/com/hypixel/hytale/assetstore/map/ProvidedIndexAssetMap.java new file mode 100644 index 0000000..a1510d5 --- /dev/null +++ b/src/com/hypixel/hytale/assetstore/map/ProvidedIndexAssetMap.java @@ -0,0 +1,122 @@ +package com.hypixel.hytale.assetstore.map; + +import com.hypixel.hytale.assetstore.codec.AssetCodec; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap; +import java.nio.file.Path; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.locks.StampedLock; +import java.util.function.ToIntBiFunction; +import javax.annotation.Nonnull; + +public class ProvidedIndexAssetMap>> extends AssetMapWithIndexes { + private final StampedLock keyToIndexLock = new StampedLock(); + private final Object2IntMap keyToIndex = new Object2IntOpenCustomHashMap<>(CaseInsensitiveHashStrategy.getInstance()); + private final ToIntBiFunction indexGetter; + + public ProvidedIndexAssetMap(ToIntBiFunction indexGetter) { + this.indexGetter = indexGetter; + this.keyToIndex.defaultReturnValue(Integer.MIN_VALUE); + } + + public int getIndex(K key) { + long stamp = this.keyToIndexLock.tryOptimisticRead(); + int value = this.keyToIndex.getInt(key); + if (this.keyToIndexLock.validate(stamp)) { + return value; + } else { + stamp = this.keyToIndexLock.readLock(); + + int var5; + try { + var5 = this.keyToIndex.getInt(key); + } finally { + this.keyToIndexLock.unlockRead(stamp); + } + + return var5; + } + } + + public int getIndexOrDefault(K key, int def) { + long stamp = this.keyToIndexLock.tryOptimisticRead(); + int value = this.keyToIndex.getOrDefault(key, def); + if (this.keyToIndexLock.validate(stamp)) { + return value; + } else { + stamp = this.keyToIndexLock.readLock(); + + int var6; + try { + var6 = this.keyToIndex.getOrDefault(key, def); + } finally { + this.keyToIndexLock.unlockRead(stamp); + } + + return var6; + } + } + + @Override + protected void clear() { + super.clear(); + long stamp = this.keyToIndexLock.writeLock(); + + try { + this.keyToIndex.clear(); + } finally { + this.keyToIndexLock.unlockWrite(stamp); + } + } + + @Override + protected void putAll( + @Nonnull String packKey, + @Nonnull AssetCodec codec, + @Nonnull Map loadedAssets, + @Nonnull Map loadedKeyToPathMap, + @Nonnull Map> loadedAssetChildren + ) { + super.putAll(packKey, codec, loadedAssets, loadedKeyToPathMap, loadedAssetChildren); + long stamp = this.keyToIndexLock.writeLock(); + + try { + for (Entry entry : loadedAssets.entrySet()) { + K key = entry.getKey(); + T value = entry.getValue(); + int index; + if ((index = this.keyToIndex.getInt(key)) == Integer.MIN_VALUE) { + this.keyToIndex.put(key, index = this.indexGetter.applyAsInt(key, value)); + } + + this.putAssetTag(codec, key, index, value); + } + } finally { + this.keyToIndexLock.unlockWrite(stamp); + } + } + + @Override + protected Set remove(@Nonnull Set keys) { + Set remove = super.remove(keys); + long stamp = this.keyToIndexLock.writeLock(); + + try { + for (K key : keys) { + int index = this.keyToIndex.removeInt(key); + this.indexedTagStorage.forEachWithInt((_k, value, idx) -> value.remove(idx), index); + } + } finally { + this.keyToIndexLock.unlockWrite(stamp); + } + + return remove; + } + + @Override + public boolean requireReplaceOnRemove() { + return false; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/camera/CameraPlugin.java b/src/com/hypixel/hytale/builtin/adventure/camera/CameraPlugin.java new file mode 100644 index 0000000..a922b75 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/camera/CameraPlugin.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.adventure.camera; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedAssetMap; +import com.hypixel.hytale.builtin.adventure.camera.asset.cameraeffect.CameraShakeEffect; +import com.hypixel.hytale.builtin.adventure.camera.asset.camerashake.CameraShake; +import com.hypixel.hytale.builtin.adventure.camera.asset.camerashake.CameraShakePacketGenerator; +import com.hypixel.hytale.builtin.adventure.camera.asset.viewbobbing.ViewBobbing; +import com.hypixel.hytale.builtin.adventure.camera.asset.viewbobbing.ViewBobbingPacketGenerator; +import com.hypixel.hytale.builtin.adventure.camera.command.CameraEffectCommand; +import com.hypixel.hytale.builtin.adventure.camera.interaction.CameraShakeInteraction; +import com.hypixel.hytale.builtin.adventure.camera.system.CameraEffectSystem; +import com.hypixel.hytale.protocol.MovementType; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.asset.type.camera.CameraEffect; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.plugin.registry.AssetRegistry; +import javax.annotation.Nonnull; + +public class CameraPlugin extends JavaPlugin { + private static final String CODEC_CAMERA_SHAKE = "CameraShake"; + + public CameraPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + AssetRegistry assetRegistry = this.getAssetRegistry(); + this.getCodecRegistry(CameraEffect.CODEC).register("CameraShake", CameraShakeEffect.class, CameraShakeEffect.CODEC); + this.getCodecRegistry(Interaction.CODEC).register("CameraShake", CameraShakeInteraction.class, CameraShakeInteraction.CODEC); + assetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + String.class, CameraShake.class, new IndexedAssetMap() + ) + .loadsBefore(CameraEffect.class)) + .setCodec(CameraShake.CODEC)) + .setPath("Camera/CameraShake")) + .setKeyFunction(CameraShake::getId)) + .setReplaceOnRemove(CameraShake::new)) + .setPacketGenerator(new CameraShakePacketGenerator()) + .build() + ); + assetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + MovementType.class, ViewBobbing.class, new DefaultAssetMap() + ) + .setCodec(ViewBobbing.CODEC)) + .setPath("Camera/ViewBobbing")) + .setKeyFunction(ViewBobbing::getId)) + .setPacketGenerator(new ViewBobbingPacketGenerator()) + .build() + ); + this.getCommandRegistry().registerCommand(new CameraEffectCommand()); + this.getEntityStoreRegistry().registerSystem(new CameraEffectSystem()); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/camera/asset/CameraShakeConfig.java b/src/com/hypixel/hytale/builtin/adventure/camera/asset/CameraShakeConfig.java new file mode 100644 index 0000000..011d9da --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/camera/asset/CameraShakeConfig.java @@ -0,0 +1,163 @@ +package com.hypixel.hytale.builtin.adventure.camera.asset; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class CameraShakeConfig implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(CameraShakeConfig.class, CameraShakeConfig::new) + .appendInherited(new KeyedCodec<>("Duration", Codec.FLOAT), (o, v) -> o.duration = v, o -> o.duration, (o, p) -> o.duration = p.duration) + .documentation("The time period that the camera will shake at full intensity for") + .addValidator(Validators.min(0.0F)) + .add() + .appendInherited(new KeyedCodec<>("StartTime", Codec.FLOAT), (o, v) -> o.startTime = v, o -> o.startTime, (o, p) -> o.startTime = p.startTime) + .documentation( + "The initial time value that the Offset and Rotation noises are sampled from when the camera-shake starts. If absent, the camera-shake uses a continuously incremented time value." + ) + .add() + .appendInherited(new KeyedCodec<>("EaseIn", EasingConfig.CODEC), (o, v) -> o.easeIn = v, o -> o.easeIn, (o, p) -> o.easeIn = p.easeIn) + .documentation("The fade-in time and intensity curve for the camera shake") + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("EaseOut", EasingConfig.CODEC), (o, v) -> o.easeOut = v, o -> o.easeOut, (o, p) -> o.easeOut = p.easeOut) + .documentation("The fade-out time and intensity curve for the camera shake") + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Offset", CameraShakeConfig.OffsetNoise.CODEC), (o, v) -> o.offset = v, o -> o.offset, (o, p) -> o.offset = p.offset + ) + .documentation("The translational offset motion") + .add() + .appendInherited( + new KeyedCodec<>("Rotation", CameraShakeConfig.RotationNoise.CODEC), (o, v) -> o.rotation = v, o -> o.rotation, (o, p) -> o.rotation = p.rotation + ) + .documentation("The rotational motion") + .add() + .build(); + protected float duration; + protected Float startTime; + protected EasingConfig easeIn = EasingConfig.NONE; + protected EasingConfig easeOut = EasingConfig.NONE; + protected CameraShakeConfig.OffsetNoise offset = CameraShakeConfig.OffsetNoise.NONE; + protected CameraShakeConfig.RotationNoise rotation = CameraShakeConfig.RotationNoise.NONE; + + public CameraShakeConfig() { + } + + @Nonnull + public com.hypixel.hytale.protocol.CameraShakeConfig toPacket() { + boolean continuous = this.startTime == null; + float startTime = continuous ? 0.0F : this.startTime; + return new com.hypixel.hytale.protocol.CameraShakeConfig( + this.duration, startTime, continuous, this.easeIn.toPacket(), this.easeOut.toPacket(), this.offset.toPacket(), this.rotation.toPacket() + ); + } + + @Nonnull + @Override + public String toString() { + return "CameraShakeConfig{duration=" + + this.duration + + ", startTime=" + + this.startTime + + ", easeIn=" + + this.easeIn + + ", easeOut=" + + this.easeOut + + ", offset=" + + this.offset + + ", rotation=" + + this.rotation + + "}"; + } + + public static class OffsetNoise implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CameraShakeConfig.OffsetNoise.class, CameraShakeConfig.OffsetNoise::new + ) + .documentation( + "The translational offset noise sources. Each component's list of noise configurations are summed together to calculate the output value for that component" + ) + .appendInherited(new KeyedCodec<>("X", NoiseConfig.ARRAY_CODEC), (o, v) -> o.x = v, o -> o.x, (o, p) -> o.x = p.x) + .documentation("The noise used to vary the camera x-offset") + .add() + .appendInherited(new KeyedCodec<>("Y", NoiseConfig.ARRAY_CODEC), (o, v) -> o.y = v, o -> o.y, (o, p) -> o.y = p.y) + .documentation("The noise used to vary the camera y-offset") + .add() + .appendInherited(new KeyedCodec<>("Z", NoiseConfig.ARRAY_CODEC), (o, v) -> o.z = v, o -> o.z, (o, p) -> o.z = p.z) + .documentation("The noise used to vary the camera z-offset") + .add() + .build(); + public static final CameraShakeConfig.OffsetNoise NONE = new CameraShakeConfig.OffsetNoise(); + protected NoiseConfig[] x; + protected NoiseConfig[] y; + protected NoiseConfig[] z; + + public OffsetNoise() { + } + + @Nonnull + public com.hypixel.hytale.protocol.OffsetNoise toPacket() { + return new com.hypixel.hytale.protocol.OffsetNoise(NoiseConfig.toPacket(this.x), NoiseConfig.toPacket(this.y), NoiseConfig.toPacket(this.z)); + } + + @Nonnull + @Override + public String toString() { + return "OffsetNoise{x=" + + Arrays.toString((Object[])this.x) + + ", y=" + + Arrays.toString((Object[])this.y) + + ", z=" + + Arrays.toString((Object[])this.z) + + "}"; + } + } + + public static class RotationNoise implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CameraShakeConfig.RotationNoise.class, CameraShakeConfig.RotationNoise::new + ) + .documentation( + "The rotational noise sources. Each component's list of noise configurations are summed together to calculate the output value for that component" + ) + .appendInherited(new KeyedCodec<>("Pitch", NoiseConfig.ARRAY_CODEC), (o, v) -> o.pitch = v, o -> o.pitch, (o, p) -> o.pitch = p.pitch) + .documentation("The noise used to vary the camera pitch") + .add() + .appendInherited(new KeyedCodec<>("Yaw", NoiseConfig.ARRAY_CODEC), (o, v) -> o.yaw = v, o -> o.yaw, (o, p) -> o.yaw = p.yaw) + .documentation("The noise used to vary the camera yaw") + .add() + .appendInherited(new KeyedCodec<>("Roll", NoiseConfig.ARRAY_CODEC), (o, v) -> o.roll = v, o -> o.roll, (o, p) -> o.roll = p.roll) + .documentation("The noise used to vary the camera roll") + .add() + .build(); + public static final CameraShakeConfig.RotationNoise NONE = new CameraShakeConfig.RotationNoise(); + protected NoiseConfig[] pitch; + protected NoiseConfig[] yaw; + protected NoiseConfig[] roll; + + public RotationNoise() { + } + + @Nonnull + public com.hypixel.hytale.protocol.RotationNoise toPacket() { + return new com.hypixel.hytale.protocol.RotationNoise(NoiseConfig.toPacket(this.pitch), NoiseConfig.toPacket(this.yaw), NoiseConfig.toPacket(this.roll)); + } + + @Nonnull + @Override + public String toString() { + return "RotationNoise{pitch=" + + Arrays.toString((Object[])this.pitch) + + ", yaw=" + + Arrays.toString((Object[])this.yaw) + + ", roll=" + + Arrays.toString((Object[])this.roll) + + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/camera/asset/EasingConfig.java b/src/com/hypixel/hytale/builtin/adventure/camera/asset/EasingConfig.java new file mode 100644 index 0000000..6ea7e51 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/camera/asset/EasingConfig.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.builtin.adventure.camera.asset; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.EasingType; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class EasingConfig implements NetworkSerializable { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(EasingConfig.class, EasingConfig::new) + .appendInherited(new KeyedCodec<>("Time", Codec.FLOAT), (o, v) -> o.time = v, o -> o.time, (o, p) -> o.time = p.time) + .documentation("The duration time of the easing") + .addValidator(Validators.min(0.0F)) + .add() + .appendInherited(new KeyedCodec<>("Type", ProtocolCodecs.EASING_TYPE_CODEC), (o, v) -> o.type = v, o -> o.type, (o, p) -> o.type = p.type) + .documentation("The curve type of the easing") + .addValidator(Validators.nonNull()) + .add() + .build(); + @Nonnull + public static final EasingConfig NONE = new EasingConfig(); + protected float time; + @Nonnull + protected EasingType type = EasingType.Linear; + + public EasingConfig() { + } + + @Nonnull + public com.hypixel.hytale.protocol.EasingConfig toPacket() { + return new com.hypixel.hytale.protocol.EasingConfig(this.time, this.type); + } + + @Nonnull + @Override + public String toString() { + return "EasingConfig{time=" + this.time + ", type=" + this.type + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/camera/asset/NoiseConfig.java b/src/com/hypixel/hytale/builtin/adventure/camera/asset/NoiseConfig.java new file mode 100644 index 0000000..e781cbe --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/camera/asset/NoiseConfig.java @@ -0,0 +1,135 @@ +package com.hypixel.hytale.builtin.adventure.camera.asset; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDefaultCollapsedState; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.NoiseType; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NoiseConfig implements NetworkSerializable { + @Nonnull + public static final Codec NOISE_TYPE_CODEC = new EnumCodec<>(NoiseType.class); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(NoiseConfig.class, NoiseConfig::new) + .appendInherited(new KeyedCodec<>("Seed", Codec.INTEGER), (o, v) -> o.seed = v, o -> o.seed, (o, p) -> o.seed = p.seed) + .documentation("The value used to seed the noise source") + .add() + .appendInherited(new KeyedCodec<>("Type", NOISE_TYPE_CODEC), (o, v) -> o.type = v, o -> o.type, (o, p) -> o.type = p.type) + .documentation("The type of noise used to move the camera") + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Frequency", Codec.FLOAT), (o, v) -> o.frequency = v, o -> o.frequency, (o, p) -> o.frequency = p.frequency) + .documentation("The frequency at which the noise source is sampled") + .add() + .appendInherited(new KeyedCodec<>("Amplitude", Codec.FLOAT), (o, v) -> o.amplitude = v, o -> o.amplitude, (o, p) -> o.amplitude = p.amplitude) + .documentation("The maximum extent of the noise source output") + .add() + .appendInherited( + new KeyedCodec<>("Clamp", NoiseConfig.ClampConfig.CODEC), (o, v) -> o.clamp = v, o -> o.clamp, (o, p) -> o.clamp = p.clamp + ) + .documentation("Restricts the range of values that the noise source can output") + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .build(); + @Nonnull + public static final ArrayCodec ARRAY_CODEC = ArrayCodec.ofBuilderCodec(CODEC, NoiseConfig[]::new); + @Nonnull + public static final com.hypixel.hytale.protocol.NoiseConfig[] NOISE_CONFIGS = new com.hypixel.hytale.protocol.NoiseConfig[0]; + protected int seed; + @Nonnull + protected NoiseType type = NoiseType.Sin; + @Nonnull + protected NoiseConfig.ClampConfig clamp = NoiseConfig.ClampConfig.NONE; + protected float frequency; + protected float amplitude; + + public NoiseConfig() { + } + + @Nonnull + public com.hypixel.hytale.protocol.NoiseConfig toPacket() { + return new com.hypixel.hytale.protocol.NoiseConfig(this.seed, this.type, this.frequency, this.amplitude, this.clamp.toPacket()); + } + + @Nonnull + @Override + public String toString() { + return "NoiseConfig{seed=" + + this.seed + + ", type=" + + this.type + + ", clamp=" + + this.clamp + + ", frequency=" + + this.frequency + + ", amplitude=" + + this.amplitude + + "}"; + } + + @Nonnull + public static com.hypixel.hytale.protocol.NoiseConfig[] toPacket(@Nullable NoiseConfig[] configs) { + if (configs != null && configs.length != 0) { + com.hypixel.hytale.protocol.NoiseConfig[] result = new com.hypixel.hytale.protocol.NoiseConfig[configs.length]; + + for (int i = 0; i < configs.length; i++) { + NoiseConfig config = configs[i]; + if (config != null) { + result[i] = config.toPacket(); + } + } + + return result; + } else { + return NOISE_CONFIGS; + } + } + + public static class ClampConfig implements NetworkSerializable { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(NoiseConfig.ClampConfig.class, NoiseConfig.ClampConfig::new) + .appendInherited(new KeyedCodec<>("Min", Codec.FLOAT), (o, v) -> o.min = v, o -> o.min, (o, p) -> o.min = p.min) + .documentation("The inclusive minimum value of the clamp range") + .addValidator(Validators.range(-1.0F, 1.0F)) + .add() + .appendInherited(new KeyedCodec<>("Max", Codec.FLOAT), (o, v) -> o.max = v, o -> o.max, (o, p) -> o.max = p.max) + .documentation("The inclusive maximum value of the clamp range") + .addValidator(Validators.range(-1.0F, 1.0F)) + .add() + .appendInherited( + new KeyedCodec<>("Normalize", Codec.BOOLEAN), (o, v) -> o.normalize = v, o -> o.normalize, (o, p) -> o.normalize = p.normalize + ) + .documentation("Rescales the clamped output value back to the range -1 to 1") + .add() + .afterDecode(range -> { + range.min = Math.min(range.min, range.max); + range.max = Math.max(range.min, range.max); + }) + .build(); + @Nonnull + public static final NoiseConfig.ClampConfig NONE = new NoiseConfig.ClampConfig(); + protected float min = -1.0F; + protected float max = 1.0F; + protected boolean normalize = true; + + public ClampConfig() { + } + + @Nonnull + public com.hypixel.hytale.protocol.ClampConfig toPacket() { + return new com.hypixel.hytale.protocol.ClampConfig(this.min, this.max, this.normalize); + } + + @Nonnull + @Override + public String toString() { + return "ClampConfig{min=" + this.min + ", max=" + this.max + ", normalize=" + this.normalize + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/camera/asset/cameraeffect/CameraShakeEffect.java b/src/com/hypixel/hytale/builtin/adventure/camera/asset/cameraeffect/CameraShakeEffect.java new file mode 100644 index 0000000..bb58976 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/camera/asset/cameraeffect/CameraShakeEffect.java @@ -0,0 +1,94 @@ +package com.hypixel.hytale.builtin.adventure.camera.asset.cameraeffect; + +import com.hypixel.hytale.builtin.adventure.camera.asset.camerashake.CameraShake; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.AccumulationMode; +import com.hypixel.hytale.server.core.asset.type.camera.CameraEffect; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CameraShakeEffect extends CameraEffect { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(CameraShakeEffect.class, CameraShakeEffect::new) + .appendInherited( + new KeyedCodec<>("CameraShake", CameraShake.CHILD_ASSET_CODEC), + (cameraShakeEffect, s) -> cameraShakeEffect.cameraShakeId = s, + cameraShakeEffect -> cameraShakeEffect.cameraShakeId, + (cameraShakeEffect, parent) -> cameraShakeEffect.cameraShakeId = parent.cameraShakeId + ) + .documentation("The type of camera shake to apply for this effect.") + .addValidator(CameraShake.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("Intensity", ShakeIntensity.CODEC), + (cameraShakeEffect, s) -> cameraShakeEffect.intensity = s, + cameraShakeEffect -> cameraShakeEffect.intensity, + (cameraShakeEffect, parent) -> cameraShakeEffect.intensity = parent.intensity + ) + .documentation("Controls how intensity-context (such as damage) is interpreted as shake intensity.") + .add() + .afterDecode(cameraShakeEffect -> { + if (cameraShakeEffect.cameraShakeId != null) { + cameraShakeEffect.cameraShakeIndex = CameraShake.getAssetMap().getIndex(cameraShakeEffect.cameraShakeId); + } + }) + .build(); + @Nullable + protected String cameraShakeId; + protected int cameraShakeIndex = Integer.MIN_VALUE; + @Nullable + protected ShakeIntensity intensity; + + public CameraShakeEffect() { + } + + @Nonnull + public AccumulationMode getAccumulationMode() { + return this.intensity == null ? ShakeIntensity.DEFAULT_ACCUMULATION_MODE : this.intensity.getAccumulationMode(); + } + + public float getDefaultIntensityContext() { + return this.intensity == null ? 0.0F : this.intensity.getValue(); + } + + public float calculateIntensity(float intensityContext) { + if (this.intensity == null) { + return intensityContext; + } else { + ShakeIntensity.Modifier modifier = this.intensity.getModifier(); + return modifier == null ? intensityContext : modifier.apply(intensityContext); + } + } + + @Nonnull + @Override + public com.hypixel.hytale.protocol.packets.camera.CameraShakeEffect createCameraShakePacket() { + float intensity = this.getDefaultIntensityContext(); + return this.createCameraShakePacket(intensity); + } + + @Nonnull + @Override + public com.hypixel.hytale.protocol.packets.camera.CameraShakeEffect createCameraShakePacket(float intensityContext) { + float intensity = this.calculateIntensity(intensityContext); + AccumulationMode accumulationMode = this.getAccumulationMode(); + return new com.hypixel.hytale.protocol.packets.camera.CameraShakeEffect(this.cameraShakeIndex, intensity, accumulationMode); + } + + @Nonnull + @Override + public String toString() { + return "CameraShakeEffect{id='" + + this.id + + "', data=" + + this.data + + ", cameraShakeId='" + + this.cameraShakeId + + "', cameraShakeIndex=" + + this.cameraShakeIndex + + ", intensity=" + + this.intensity + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/camera/asset/cameraeffect/ShakeIntensity.java b/src/com/hypixel/hytale/builtin/adventure/camera/asset/cameraeffect/ShakeIntensity.java new file mode 100644 index 0000000..612fada --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/camera/asset/cameraeffect/ShakeIntensity.java @@ -0,0 +1,135 @@ +package com.hypixel.hytale.builtin.adventure.camera.asset.cameraeffect; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.protocol.AccumulationMode; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import it.unimi.dsi.fastutil.floats.FloatUnaryOperator; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ShakeIntensity { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(ShakeIntensity.class, ShakeIntensity::new) + .appendInherited( + new KeyedCodec<>("Value", Codec.FLOAT), + (cameraShakeEffect, s) -> cameraShakeEffect.value = s, + cameraShakeEffect -> cameraShakeEffect.value, + (cameraShakeEffect, parent) -> cameraShakeEffect.value = parent.value + ) + .documentation("The intensity used when no contextual value (such as damage) is present.") + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .appendInherited( + new KeyedCodec<>("AccumulationMode", ProtocolCodecs.ACCUMULATION_MODE_CODEC), + (intensity, mode) -> intensity.accumulationMode = mode, + intensity -> intensity.accumulationMode, + (intensity, parent) -> intensity.accumulationMode = parent.accumulationMode + ) + .documentation("The method by which intensity is combined when multiple instances of the same camera effect overlap.") + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Modifier", ShakeIntensity.Modifier.CODEC), + (intensity, modifier) -> intensity.modifier = modifier, + intensity -> intensity.modifier, + (intensity, parent) -> intensity.modifier = parent.modifier + ) + .documentation("Converts a contextual-intensity value (such as damage) to a camera shake intensity value.") + .add() + .build(); + @Nonnull + static final AccumulationMode DEFAULT_ACCUMULATION_MODE = AccumulationMode.Set; + public static final float DEFAULT_CONTEXT_VALUE = 0.0F; + protected float value = 0.0F; + @Nonnull + protected AccumulationMode accumulationMode = DEFAULT_ACCUMULATION_MODE; + @Nullable + protected ShakeIntensity.Modifier modifier; + + public ShakeIntensity() { + } + + public float getValue() { + return this.value; + } + + @Nonnull + public AccumulationMode getAccumulationMode() { + return this.accumulationMode; + } + + @Nullable + public ShakeIntensity.Modifier getModifier() { + return this.modifier; + } + + @Nonnull + @Override + public String toString() { + return "ShakeIntensity{value=" + this.value + ", accumulationMode=" + this.accumulationMode + ", modifier=" + this.modifier + "}"; + } + + public static class Modifier implements FloatUnaryOperator { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(ShakeIntensity.Modifier.class, ShakeIntensity.Modifier::new) + .appendInherited( + new KeyedCodec<>("Input", Codec.FLOAT_ARRAY), + (modifier, v) -> modifier.input = v, + modifier -> modifier.input, + (modifier, parent) -> modifier.input = parent.input + ) + .addValidator(Validators.nonEmptyFloatArray()) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Output", Codec.FLOAT_ARRAY), + (modifier, v) -> modifier.output = v, + modifier -> modifier.output, + (modifier, parent) -> modifier.output = parent.output + ) + .addValidator(Validators.nonEmptyFloatArray()) + .addValidator(Validators.nonNull()) + .add() + .build(); + private float[] input; + private float[] output; + + public Modifier() { + } + + @Override + public float apply(float intensityContext) { + float inputMin = this.input[0]; + float outputMin = this.output[0]; + if (intensityContext < inputMin) { + return outputMin; + } else { + int length = Math.min(this.input.length, this.output.length); + + for (int i = 1; i < length; i++) { + float inputMax = this.input[i]; + float outputMax = this.output[i]; + if (!(intensityContext > inputMax)) { + return MathUtil.mapToRange(intensityContext, inputMin, inputMax, outputMin, outputMax); + } + + inputMin = inputMax; + outputMin = outputMax; + } + + return outputMin; + } + } + + @Nonnull + @Override + public String toString() { + return "Modifier{input=" + Arrays.toString(this.input) + ", output=" + Arrays.toString(this.output) + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/camera/asset/camerashake/CameraShake.java b/src/com/hypixel/hytale/builtin/adventure/camera/asset/camerashake/CameraShake.java new file mode 100644 index 0000000..0295ba6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/camera/asset/camerashake/CameraShake.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.builtin.adventure.camera.asset.camerashake; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.IndexedAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.adventure.camera.asset.CameraShakeConfig; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class CameraShake implements NetworkSerializable, JsonAssetWithMap> { + @Nonnull + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + CameraShake.class, CameraShake::new, Codec.STRING, (o, v) -> o.id = v, CameraShake::getId, (o, data) -> o.data = data, o -> o.data + ) + .appendInherited( + new KeyedCodec<>("FirstPerson", CameraShakeConfig.CODEC), (o, v) -> o.firstPerson = v, o -> o.firstPerson, (o, p) -> o.firstPerson = p.firstPerson + ) + .documentation("The camera shake to apply to the first-person camera") + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("ThirdPerson", CameraShakeConfig.CODEC), (o, v) -> o.thirdPerson = v, o -> o.thirdPerson, (o, p) -> o.thirdPerson = p.thirdPerson + ) + .documentation("The camera shake to apply to the third-person camera") + .addValidator(Validators.nonNull()) + .add() + .build(); + @Nonnull + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(CameraShake.class, CODEC); + @Nonnull + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(CameraShake::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected String id; + protected AssetExtraInfo.Data data; + @Nonnull + protected CameraShakeConfig firstPerson; + @Nonnull + protected CameraShakeConfig thirdPerson; + + @Nonnull + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(CameraShake.class); + } + + return ASSET_STORE; + } + + @Nonnull + public static IndexedAssetMap getAssetMap() { + return (IndexedAssetMap)getAssetStore().getAssetMap(); + } + + public CameraShake() { + } + + public CameraShake(@Nonnull String id) { + this.id = id; + } + + @Nonnull + public com.hypixel.hytale.protocol.CameraShake toPacket() { + return new com.hypixel.hytale.protocol.CameraShake(this.firstPerson.toPacket(), this.thirdPerson.toPacket()); + } + + public String getId() { + return this.id; + } + + @Nonnull + @Override + public String toString() { + return "CameraShake{id='" + this.id + "', data=" + this.data + ", firstPerson=" + this.firstPerson + ", thirdPerson=" + this.thirdPerson + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/camera/asset/camerashake/CameraShakePacketGenerator.java b/src/com/hypixel/hytale/builtin/adventure/camera/asset/camerashake/CameraShakePacketGenerator.java new file mode 100644 index 0000000..fa1975b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/camera/asset/camerashake/CameraShakePacketGenerator.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.builtin.adventure.camera.asset.camerashake; + +import com.hypixel.hytale.assetstore.map.IndexedAssetMap; +import com.hypixel.hytale.protocol.CachedPacket; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateCameraShake; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class CameraShakePacketGenerator extends SimpleAssetPacketGenerator> { + public CameraShakePacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedAssetMap assetMap, @Nonnull Map assets) { + return toCachedPacket(UpdateType.Init, assetMap, assets); + } + + @Nonnull + protected Packet generateUpdatePacket(@Nonnull IndexedAssetMap assetMap, @Nonnull Map loadedAssets) { + return toCachedPacket(UpdateType.AddOrUpdate, assetMap, loadedAssets); + } + + @Nonnull + protected Packet generateRemovePacket(@Nonnull IndexedAssetMap assetMap, @Nonnull Set removed) { + Int2ObjectOpenHashMap profiles = new Int2ObjectOpenHashMap<>(); + + for (String key : removed) { + int index = assetMap.getIndex(key); + profiles.put(index, null); + } + + UpdateCameraShake packet = new UpdateCameraShake(); + packet.type = UpdateType.Remove; + packet.profiles = profiles; + return CachedPacket.cache(packet); + } + + @Nonnull + protected static Packet toCachedPacket(UpdateType type, @Nonnull IndexedAssetMap assetMap, @Nonnull Map assets) { + Int2ObjectOpenHashMap profiles = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + int index = assetMap.getIndex(entry.getKey()); + profiles.put(index, entry.getValue().toPacket()); + } + + UpdateCameraShake packet = new UpdateCameraShake(); + packet.type = type; + packet.profiles = profiles; + return CachedPacket.cache(packet); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/camera/asset/viewbobbing/ViewBobbing.java b/src/com/hypixel/hytale/builtin/adventure/camera/asset/viewbobbing/ViewBobbing.java new file mode 100644 index 0000000..b2649cf --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/camera/asset/viewbobbing/ViewBobbing.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.adventure.camera.asset.viewbobbing; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.adventure.camera.asset.CameraShakeConfig; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.MovementType; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class ViewBobbing + implements NetworkSerializable, + JsonAssetWithMap> { + @Nonnull + public static final Codec MOVEMENT_TYPE_CODEC = new EnumCodec<>(MovementType.class); + @Nonnull + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ViewBobbing.class, ViewBobbing::new, MOVEMENT_TYPE_CODEC, (o, v) -> o.id = v, ViewBobbing::getId, (o, data) -> o.data = data, o -> o.data + ) + .appendInherited( + new KeyedCodec<>("FirstPerson", CameraShakeConfig.CODEC), (o, v) -> o.firstPerson = v, o -> o.firstPerson, (o, p) -> o.firstPerson = p.firstPerson + ) + .documentation("The camera shake profile to be applied") + .addValidator(Validators.nonNull()) + .add() + .build(); + protected MovementType id; + protected AssetExtraInfo.Data data; + @Nonnull + protected CameraShakeConfig firstPerson; + + public ViewBobbing() { + } + + public MovementType getId() { + return this.id; + } + + @Nonnull + public com.hypixel.hytale.protocol.ViewBobbing toPacket() { + return new com.hypixel.hytale.protocol.ViewBobbing(this.firstPerson.toPacket()); + } + + @Nonnull + @Override + public String toString() { + return "ViewBobbing{id=" + this.id + ", data=" + this.data + ", firstPerson=" + this.firstPerson + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/camera/asset/viewbobbing/ViewBobbingPacketGenerator.java b/src/com/hypixel/hytale/builtin/adventure/camera/asset/viewbobbing/ViewBobbingPacketGenerator.java new file mode 100644 index 0000000..20e2343 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/camera/asset/viewbobbing/ViewBobbingPacketGenerator.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.builtin.adventure.camera.asset.viewbobbing; + +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.protocol.CachedPacket; +import com.hypixel.hytale.protocol.MovementType; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateViewBobbing; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import java.util.EnumMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class ViewBobbingPacketGenerator extends SimpleAssetPacketGenerator> { + public ViewBobbingPacketGenerator() { + } + + @Nonnull + @Override + public Packet generateInitPacket(AssetMap assetMap, @Nonnull Map assets) { + return toCachedPacket(UpdateType.Init, assets); + } + + @Nonnull + @Override + protected Packet generateUpdatePacket(AssetMap assetMap, @Nonnull Map loadedAssets) { + return toCachedPacket(UpdateType.AddOrUpdate, loadedAssets); + } + + @Nonnull + @Override + protected Packet generateRemovePacket(AssetMap assetMap, @Nonnull Set removed) { + UpdateViewBobbing packet = new UpdateViewBobbing(); + packet.type = UpdateType.Remove; + packet.profiles = new EnumMap<>(MovementType.class); + + for (MovementType type : removed) { + packet.profiles.put(type, null); + } + + return CachedPacket.cache(packet); + } + + @Nonnull + protected static Packet toCachedPacket(UpdateType type, @Nonnull Map assets) { + UpdateViewBobbing packet = new UpdateViewBobbing(); + packet.type = type; + packet.profiles = new EnumMap<>(MovementType.class); + + for (Entry entry : assets.entrySet()) { + packet.profiles.put(entry.getKey(), entry.getValue().toPacket()); + } + + return CachedPacket.cache(packet); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/camera/command/CameraEffectCommand.java b/src/com/hypixel/hytale/builtin/adventure/camera/command/CameraEffectCommand.java new file mode 100644 index 0000000..8fd1bfa --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/camera/command/CameraEffectCommand.java @@ -0,0 +1,115 @@ +package com.hypixel.hytale.builtin.adventure.camera.command; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.camera.CameraEffect; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgumentType; +import com.hypixel.hytale.server.core.command.system.arguments.types.AssetArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class CameraEffectCommand extends AbstractCommandCollection { + @Nonnull + protected static final ArgumentType CAMERA_EFFECT_ARGUMENT_TYPE = new AssetArgumentType("CameraEffect", CameraEffect.class, ""); + + public CameraEffectCommand() { + super("camshake", "server.commands.camshake.desc"); + this.addSubCommand(new CameraEffectCommand.DamageCommand()); + this.addSubCommand(new CameraEffectCommand.DebugCommand()); + } + + protected static class DamageCommand extends AbstractTargetPlayerCommand { + @Nonnull + protected static final ArgumentType DAMAGE_CAUSE_ARGUMENT_TYPE = new AssetArgumentType("DamageCause", DamageCause.class, ""); + @Nonnull + protected final OptionalArg effectArg = this.withOptionalArg( + "effect", "server.commands.camshake.effect.desc", CameraEffectCommand.CAMERA_EFFECT_ARGUMENT_TYPE + ); + @Nonnull + protected final RequiredArg causeArg = this.withRequiredArg( + "cause", "server.commands.camshake.damage.cause.desc", DAMAGE_CAUSE_ARGUMENT_TYPE + ); + @Nonnull + protected final RequiredArg damageArg = this.withRequiredArg("amount", "server.commands.camshake.damage.amount.desc", ArgTypes.FLOAT); + + public DamageCommand() { + super("damage", "server.commands.camshake.damage.desc"); + } + + @Override + protected void execute( + @NonNullDecl CommandContext context, + @NullableDecl Ref sourceRef, + @NonNullDecl Ref ref, + @NonNullDecl PlayerRef playerRef, + @NonNullDecl World world, + @NonNullDecl Store store + ) { + DamageCause damageCause = context.get(this.causeArg); + float damageAmount = context.get(this.damageArg); + Damage.CommandSource damageSource = new Damage.CommandSource(context.sender(), this.getName()); + Damage damageEvent = new Damage(damageSource, damageCause, damageAmount); + String cameraEffectId = "Default"; + if (this.effectArg.provided(context)) { + cameraEffectId = context.get(this.effectArg).getId(); + Damage.CameraEffect damageEffect = new Damage.CameraEffect(CameraEffect.getAssetMap().getIndex(cameraEffectId)); + damageEvent.getMetaStore().putMetaObject(Damage.CAMERA_EFFECT, damageEffect); + } + + DamageSystems.executeDamage(ref, store, damageEvent); + context.sendMessage( + Message.translation("server.commands.camshake.damage.success") + .param("effect", cameraEffectId) + .param("cause", damageCause.getId()) + .param("amount", damageAmount) + ); + } + } + + protected static class DebugCommand extends AbstractTargetPlayerCommand { + private static final String MESSAGE_SUCCESS = "server.commands.camshake.debug.success"; + @Nonnull + protected final RequiredArg effectArg = this.withRequiredArg( + "effect", "server.commands.camshake.effect.desc", CameraEffectCommand.CAMERA_EFFECT_ARGUMENT_TYPE + ); + @Nonnull + protected final RequiredArg intensityArg = this.withRequiredArg("intensity", "server.commands.camshake.debug.intensity.desc", ArgTypes.FLOAT); + + public DebugCommand() { + super("debug", "server.commands.camshake.debug.desc"); + } + + @Override + protected void execute( + @NonNullDecl CommandContext context, + @NullableDecl Ref sourceRef, + @NonNullDecl Ref ref, + @NonNullDecl PlayerRef playerRef, + @NonNullDecl World world, + @NonNullDecl Store store + ) { + CameraEffect cameraEffect = context.get(this.effectArg); + float intensity = context.get(this.intensityArg); + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerRefComponent.getPacketHandler().writeNoCache(cameraEffect.createCameraShakePacket(intensity)); + context.sendMessage(Message.translation("server.commands.camshake.debug.success").param("effect", cameraEffect.getId()).param("intensity", intensity)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/camera/interaction/CameraShakeInteraction.java b/src/com/hypixel/hytale/builtin/adventure/camera/interaction/CameraShakeInteraction.java new file mode 100644 index 0000000..8fb3e36 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/camera/interaction/CameraShakeInteraction.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.builtin.adventure.camera.interaction; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.camera.CameraEffect; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CameraShakeInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + CameraShakeInteraction.class, CameraShakeInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Triggers a camera shake effect on use.") + .appendInherited( + new KeyedCodec<>("CameraEffect", CameraEffect.CHILD_ASSET_CODEC), + (interaction, effect) -> interaction.effectId = effect, + interaction -> interaction.effectId, + (interaction, parent) -> interaction.effectId = parent.effectId + ) + .addValidator(Validators.nonNull()) + .addValidator(CameraEffect.VALIDATOR_CACHE.getValidator()) + .add() + .afterDecode(cameraShakeInteraction -> { + if (cameraShakeInteraction.effectId != null) { + cameraShakeInteraction.effectIndex = CameraEffect.getAssetMap().getIndex(cameraShakeInteraction.effectId); + } + }) + .build(); + @Nullable + protected String effectId; + protected int effectIndex = Integer.MIN_VALUE; + + public CameraShakeInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + if (this.effectIndex != Integer.MIN_VALUE) { + Ref ref = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + PlayerRef playerRefComponent = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); + if (playerRefComponent != null) { + CameraEffect cameraShakeEffect = CameraEffect.getAssetMap().getAsset(this.effectIndex); + if (cameraShakeEffect != null) { + playerRefComponent.getPacketHandler().writeNoCache(cameraShakeEffect.createCameraShakePacket()); + } + } + } + } + + @Nonnull + @Override + public String toString() { + return "CameraShakeInteraction{effectId='" + this.effectId + "', effectIndex=" + this.effectIndex + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/camera/system/CameraEffectSystem.java b/src/com/hypixel/hytale/builtin/adventure/camera/system/CameraEffectSystem.java new file mode 100644 index 0000000..d779012 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/camera/system/CameraEffectSystem.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.builtin.adventure.camera.system; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.asset.type.camera.CameraEffect; +import com.hypixel.hytale.server.core.asset.type.gameplay.CameraEffectsConfig; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageEventSystem; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageModule; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue; +import com.hypixel.hytale.server.core.modules.entitystats.asset.DefaultEntityStatTypes; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CameraEffectSystem extends DamageEventSystem { + @Nonnull + private static final ComponentType PLAYER_REF_COMPONENT_TYPE = PlayerRef.getComponentType(); + private static final ComponentType ENTITY_STAT_MAP_COMPONENT_TYPE = EntityStatMap.getComponentType(); + @Nonnull + private static final Query QUERY = Query.and(PLAYER_REF_COMPONENT_TYPE, ENTITY_STAT_MAP_COMPONENT_TYPE); + + public CameraEffectSystem() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getInspectDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + EntityStatMap entityStatMapComponent = archetypeChunk.getComponent(index, ENTITY_STAT_MAP_COMPONENT_TYPE); + + assert entityStatMapComponent != null; + + EntityStatValue healthStat = entityStatMapComponent.get(DefaultEntityStatTypes.getHealth()); + if (healthStat != null) { + float health = healthStat.getMax() - healthStat.getMin(); + if (!(health <= 0.0F)) { + PlayerRef playerRefComponent = archetypeChunk.getComponent(index, PLAYER_REF_COMPONENT_TYPE); + + assert playerRefComponent != null; + + World world = commandBuffer.getExternalData().getWorld(); + CameraEffectsConfig cameraEffectsConfig = world.getGameplayConfig().getCameraEffectsConfig(); + Damage.CameraEffect effect = damage.getIfPresentMetaObject(Damage.CAMERA_EFFECT); + int effectIndex = effect != null ? effect.getEffectIndex() : cameraEffectsConfig.getCameraEffectIndex(damage.getDamageCauseIndex()); + if (effectIndex != Integer.MIN_VALUE) { + CameraEffect cameraEffect = CameraEffect.getAssetMap().getAsset(effectIndex); + if (cameraEffect != null) { + float intensity = MathUtil.clamp(damage.getAmount() / health, 0.0F, 1.0F); + playerRefComponent.getPacketHandler().writeNoCache(cameraEffect.createCameraShakePacket(intensity)); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/FarmingPlugin.java b/src/com/hypixel/hytale/builtin/adventure/farming/FarmingPlugin.java new file mode 100644 index 0000000..6adcf92 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/FarmingPlugin.java @@ -0,0 +1,150 @@ +package com.hypixel.hytale.builtin.adventure.farming; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.builtin.adventure.farming.component.CoopResidentComponent; +import com.hypixel.hytale.builtin.adventure.farming.config.FarmingCoopAsset; +import com.hypixel.hytale.builtin.adventure.farming.config.modifiers.FertilizerGrowthModifierAsset; +import com.hypixel.hytale.builtin.adventure.farming.config.modifiers.LightLevelGrowthModifierAsset; +import com.hypixel.hytale.builtin.adventure.farming.config.modifiers.WaterGrowthModifierAsset; +import com.hypixel.hytale.builtin.adventure.farming.config.stages.BlockStateFarmingStageData; +import com.hypixel.hytale.builtin.adventure.farming.config.stages.BlockTypeFarmingStageData; +import com.hypixel.hytale.builtin.adventure.farming.config.stages.PrefabFarmingStageData; +import com.hypixel.hytale.builtin.adventure.farming.config.stages.spread.DirectionalGrowthBehaviour; +import com.hypixel.hytale.builtin.adventure.farming.config.stages.spread.SpreadFarmingStageData; +import com.hypixel.hytale.builtin.adventure.farming.config.stages.spread.SpreadGrowthBehaviour; +import com.hypixel.hytale.builtin.adventure.farming.interactions.ChangeFarmingStageInteraction; +import com.hypixel.hytale.builtin.adventure.farming.interactions.FertilizeSoilInteraction; +import com.hypixel.hytale.builtin.adventure.farming.interactions.HarvestCropInteraction; +import com.hypixel.hytale.builtin.adventure.farming.interactions.UseCaptureCrateInteraction; +import com.hypixel.hytale.builtin.adventure.farming.interactions.UseCoopInteraction; +import com.hypixel.hytale.builtin.adventure.farming.interactions.UseWateringCanInteraction; +import com.hypixel.hytale.builtin.adventure.farming.states.CoopBlock; +import com.hypixel.hytale.builtin.adventure.farming.states.FarmingBlock; +import com.hypixel.hytale.builtin.adventure.farming.states.FarmingBlockState; +import com.hypixel.hytale.builtin.adventure.farming.states.TilledSoilBlock; +import com.hypixel.hytale.builtin.tagset.config.NPCGroup; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.event.EventPriority; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.FarmingStageData; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.GrowthModifierAsset; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList; +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.events.ChunkPreLoadProcessEvent; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import javax.annotation.Nonnull; + +public class FarmingPlugin extends JavaPlugin { + protected static FarmingPlugin instance; + private ComponentType tiledSoilBlockComponentType; + private ComponentType farmingBlockComponentType; + private ComponentType farmingBlockStateComponentType; + private ComponentType coopBlockStateComponentType; + private ComponentType coopResidentComponentType; + + public static FarmingPlugin get() { + return instance; + } + + public FarmingPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + instance = this; + this.getAssetRegistry() + .register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + GrowthModifierAsset.class, new DefaultAssetMap() + ) + .setPath("Farming/Modifiers")) + .setCodec(GrowthModifierAsset.CODEC)) + .loadsAfter(Weather.class)) + .setKeyFunction(GrowthModifierAsset::getId)) + .build() + ); + this.getAssetRegistry() + .register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + FarmingCoopAsset.class, new DefaultAssetMap() + ) + .setPath("Farming/Coops")) + .setCodec(FarmingCoopAsset.CODEC)) + .loadsAfter(ItemDropList.class, NPCGroup.class)) + .setKeyFunction(FarmingCoopAsset::getId)) + .build() + ); + this.getCodecRegistry(Interaction.CODEC) + .register("HarvestCrop", HarvestCropInteraction.class, HarvestCropInteraction.CODEC) + .register("FertilizeSoil", FertilizeSoilInteraction.class, FertilizeSoilInteraction.CODEC) + .register("ChangeFarmingStage", ChangeFarmingStageInteraction.class, ChangeFarmingStageInteraction.CODEC) + .register("UseWateringCan", UseWateringCanInteraction.class, UseWateringCanInteraction.CODEC) + .register("UseCoop", UseCoopInteraction.class, UseCoopInteraction.CODEC) + .register("UseCaptureCrate", UseCaptureCrateInteraction.class, UseCaptureCrateInteraction.CODEC); + this.getCodecRegistry(GrowthModifierAsset.CODEC).register("Fertilizer", FertilizerGrowthModifierAsset.class, FertilizerGrowthModifierAsset.CODEC); + this.getCodecRegistry(GrowthModifierAsset.CODEC).register("LightLevel", LightLevelGrowthModifierAsset.class, LightLevelGrowthModifierAsset.CODEC); + this.getCodecRegistry(GrowthModifierAsset.CODEC).register("Water", WaterGrowthModifierAsset.class, WaterGrowthModifierAsset.CODEC); + this.getCodecRegistry(FarmingStageData.CODEC).register("BlockType", BlockTypeFarmingStageData.class, BlockTypeFarmingStageData.CODEC); + this.getCodecRegistry(FarmingStageData.CODEC).register("BlockState", BlockStateFarmingStageData.class, BlockStateFarmingStageData.CODEC); + this.getCodecRegistry(FarmingStageData.CODEC).register("Prefab", PrefabFarmingStageData.class, PrefabFarmingStageData.CODEC); + this.getCodecRegistry(FarmingStageData.CODEC).register("Spread", SpreadFarmingStageData.class, SpreadFarmingStageData.CODEC); + this.getCodecRegistry(SpreadGrowthBehaviour.CODEC).register("Directional", DirectionalGrowthBehaviour.class, DirectionalGrowthBehaviour.CODEC); + this.tiledSoilBlockComponentType = this.getChunkStoreRegistry().registerComponent(TilledSoilBlock.class, "TilledSoil", TilledSoilBlock.CODEC); + this.farmingBlockComponentType = this.getChunkStoreRegistry().registerComponent(FarmingBlock.class, "FarmingBlock", FarmingBlock.CODEC); + this.farmingBlockStateComponentType = this.getChunkStoreRegistry().registerComponent(FarmingBlockState.class, "Farming", FarmingBlockState.CODEC); + this.coopBlockStateComponentType = this.getChunkStoreRegistry().registerComponent(CoopBlock.class, "Coop", CoopBlock.CODEC); + this.coopResidentComponentType = this.getEntityStoreRegistry() + .registerComponent(CoopResidentComponent.class, "CoopResident", CoopResidentComponent.CODEC); + this.getChunkStoreRegistry().registerSystem(new FarmingSystems.OnSoilAdded()); + this.getChunkStoreRegistry().registerSystem(new FarmingSystems.OnFarmBlockAdded()); + this.getChunkStoreRegistry().registerSystem(new FarmingSystems.Ticking()); + this.getChunkStoreRegistry().registerSystem(new FarmingSystems.MigrateFarming()); + this.getChunkStoreRegistry().registerSystem(new FarmingSystems.OnCoopAdded()); + this.getEntityStoreRegistry().registerSystem(new FarmingSystems.CoopResidentEntitySystem()); + this.getEntityStoreRegistry().registerSystem(new FarmingSystems.CoopResidentTicking()); + this.getEventRegistry().registerGlobal(EventPriority.LAST, ChunkPreLoadProcessEvent.class, FarmingPlugin::preventSpreadOnNew); + } + + private static void preventSpreadOnNew(ChunkPreLoadProcessEvent event) { + if (event.isNewlyGenerated()) { + BlockComponentChunk components = event.getHolder().getComponent(BlockComponentChunk.getComponentType()); + if (components != null) { + Int2ObjectMap> holders = components.getEntityHolders(); + holders.values().forEach(v -> { + FarmingBlock farming = v.getComponent(FarmingBlock.getComponentType()); + if (farming != null) { + farming.setSpreadRate(0.0F); + } + }); + } + } + } + + public ComponentType getTiledSoilBlockComponentType() { + return this.tiledSoilBlockComponentType; + } + + public ComponentType getFarmingBlockComponentType() { + return this.farmingBlockComponentType; + } + + public ComponentType getFarmingBlockStateComponentType() { + return this.farmingBlockStateComponentType; + } + + public ComponentType getCoopBlockStateComponentType() { + return this.coopBlockStateComponentType; + } + + public ComponentType getCoopResidentComponentType() { + return this.coopResidentComponentType; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/FarmingSystems.java b/src/com/hypixel/hytale/builtin/adventure/farming/FarmingSystems.java new file mode 100644 index 0000000..1162b5c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/FarmingSystems.java @@ -0,0 +1,684 @@ +package com.hypixel.hytale.builtin.adventure.farming; + +import com.hypixel.hytale.builtin.adventure.farming.component.CoopResidentComponent; +import com.hypixel.hytale.builtin.adventure.farming.config.FarmingCoopAsset; +import com.hypixel.hytale.builtin.adventure.farming.config.stages.BlockStateFarmingStageData; +import com.hypixel.hytale.builtin.adventure.farming.config.stages.BlockTypeFarmingStageData; +import com.hypixel.hytale.builtin.adventure.farming.states.CoopBlock; +import com.hypixel.hytale.builtin.adventure.farming.states.FarmingBlock; +import com.hypixel.hytale.builtin.adventure.farming.states.FarmingBlockState; +import com.hypixel.hytale.builtin.adventure.farming.states.TilledSoilBlock; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.Rangef; +import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.FarmingData; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.FarmingStageData; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class FarmingSystems { + public FarmingSystems() { + } + + private static boolean updateSoilDecayTime(CommandBuffer commandBuffer, TilledSoilBlock soilBlock, BlockType blockType) { + if (blockType != null && blockType.getFarming() != null && blockType.getFarming().getSoilConfig() != null) { + FarmingData.SoilConfig soilConfig = blockType.getFarming().getSoilConfig(); + Rangef range = soilConfig.getLifetime(); + if (range == null) { + return false; + } else { + double baseDuration = range.min + (range.max - range.min) * ThreadLocalRandom.current().nextDouble(); + Instant currentTime = commandBuffer.getExternalData() + .getWorld() + .getEntityStore() + .getStore() + .getResource(WorldTimeResource.getResourceType()) + .getGameTime(); + Instant endTime = currentTime.plus(Math.round(baseDuration), ChronoUnit.SECONDS); + soilBlock.setDecayTime(endTime); + return true; + } + } else { + return false; + } + } + + public static class CoopResidentEntitySystem extends RefSystem { + private static final ComponentType componentType = CoopResidentComponent.getComponentType(); + + public CoopResidentEntitySystem() { + } + + @Override + public Query getQuery() { + return componentType; + } + + @Override + public void onEntityAdded( + @NonNullDecl Ref ref, + @NonNullDecl AddReason reason, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + } + + @Override + public void onEntityRemove( + @NonNullDecl Ref ref, + @NonNullDecl RemoveReason reason, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + if (reason != RemoveReason.UNLOAD) { + UUIDComponent uuidComponent = commandBuffer.getComponent(ref, UUIDComponent.getComponentType()); + if (uuidComponent != null) { + UUID uuid = uuidComponent.getUuid(); + CoopResidentComponent coopResidentComponent = commandBuffer.getComponent(ref, componentType); + if (coopResidentComponent != null) { + Vector3i coopPosition = coopResidentComponent.getCoopLocation(); + World world = commandBuffer.getExternalData().getWorld(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(coopPosition.x, coopPosition.z); + WorldChunk chunk = world.getChunkIfLoaded(chunkIndex); + if (chunk != null) { + Ref chunkReference = world.getChunkStore().getChunkReference(chunkIndex); + if (chunkReference != null && chunkReference.isValid()) { + Store chunkStore = world.getChunkStore().getStore(); + ChunkColumn chunkColumnComponent = chunkStore.getComponent(chunkReference, ChunkColumn.getComponentType()); + if (chunkColumnComponent != null) { + BlockChunk blockChunkComponent = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType()); + if (blockChunkComponent != null) { + Ref sectionRef = chunkColumnComponent.getSection(ChunkUtil.chunkCoordinate(coopPosition.y)); + if (sectionRef != null && sectionRef.isValid()) { + BlockComponentChunk blockComponentChunk = chunkStore.getComponent(chunkReference, BlockComponentChunk.getComponentType()); + if (blockComponentChunk != null) { + int blockIndexColumn = ChunkUtil.indexBlockInColumn(coopPosition.x, coopPosition.y, coopPosition.z); + Ref coopEntityReference = blockComponentChunk.getEntityReference(blockIndexColumn); + if (coopEntityReference != null) { + CoopBlock coop = chunkStore.getComponent(coopEntityReference, CoopBlock.getComponentType()); + if (coop != null) { + coop.handleResidentDespawn(uuid); + } + } + } + } + } + } + } + } + } + } + } + } + } + + public static class CoopResidentTicking extends EntityTickingSystem { + private static final ComponentType componentType = CoopResidentComponent.getComponentType(); + + public CoopResidentTicking() { + } + + @Override + public Query getQuery() { + return componentType; + } + + @Override + public void tick( + float dt, + int index, + @NonNullDecl ArchetypeChunk archetypeChunk, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + CoopResidentComponent coopResidentComponent = archetypeChunk.getComponent(index, CoopResidentComponent.getComponentType()); + if (coopResidentComponent != null) { + if (coopResidentComponent.getMarkedForDespawn()) { + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + } + } + } + } + + @Deprecated(forRemoval = true) + public static class MigrateFarming extends BlockModule.MigrationSystem { + public MigrateFarming() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + FarmingBlockState oldState = holder.getComponent(FarmingPlugin.get().getFarmingBlockStateComponentType()); + FarmingBlock farming = new FarmingBlock(); + farming.setGrowthProgress(oldState.getCurrentFarmingStageIndex()); + farming.setCurrentStageSet(oldState.getCurrentFarmingStageSetName()); + farming.setSpreadRate(oldState.getSpreadRate()); + holder.putComponent(FarmingBlock.getComponentType(), farming); + holder.removeComponent(FarmingPlugin.get().getFarmingBlockStateComponentType()); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nullable + @Override + public Query getQuery() { + return FarmingPlugin.get().getFarmingBlockStateComponentType(); + } + } + + public static class OnCoopAdded extends RefSystem { + private static final Query QUERY = Query.and(BlockModule.BlockStateInfo.getComponentType(), CoopBlock.getComponentType()); + + public OnCoopAdded() { + } + + @Override + public void onEntityAdded( + @NonNullDecl Ref ref, + @NonNullDecl AddReason reason, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + CoopBlock coopBlock = commandBuffer.getComponent(ref, CoopBlock.getComponentType()); + if (coopBlock != null) { + WorldTimeResource worldTimeResource = commandBuffer.getExternalData() + .getWorld() + .getEntityStore() + .getStore() + .getResource(WorldTimeResource.getResourceType()); + BlockModule.BlockStateInfo info = commandBuffer.getComponent(ref, BlockModule.BlockStateInfo.getComponentType()); + + assert info != null; + + int x = ChunkUtil.xFromBlockInColumn(info.getIndex()); + int y = ChunkUtil.yFromBlockInColumn(info.getIndex()); + int z = ChunkUtil.zFromBlockInColumn(info.getIndex()); + BlockChunk blockChunk = commandBuffer.getComponent(info.getChunkRef(), BlockChunk.getComponentType()); + + assert blockChunk != null; + + BlockSection blockSection = blockChunk.getSectionAtBlockY(y); + blockSection.scheduleTick(ChunkUtil.indexBlock(x, y, z), coopBlock.getNextScheduledTick(worldTimeResource)); + } + } + + @Override + public void onEntityRemove( + @NonNullDecl Ref ref, + @NonNullDecl RemoveReason reason, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + if (reason != RemoveReason.UNLOAD) { + CoopBlock coop = commandBuffer.getComponent(ref, CoopBlock.getComponentType()); + if (coop != null) { + BlockModule.BlockStateInfo info = commandBuffer.getComponent(ref, BlockModule.BlockStateInfo.getComponentType()); + + assert info != null; + + Store entityStore = commandBuffer.getExternalData().getWorld().getEntityStore().getStore(); + int x = ChunkUtil.xFromBlockInColumn(info.getIndex()); + int y = ChunkUtil.yFromBlockInColumn(info.getIndex()); + int z = ChunkUtil.zFromBlockInColumn(info.getIndex()); + BlockChunk blockChunk = commandBuffer.getComponent(info.getChunkRef(), BlockChunk.getComponentType()); + + assert blockChunk != null; + + ChunkColumn column = commandBuffer.getComponent(info.getChunkRef(), ChunkColumn.getComponentType()); + + assert column != null; + + Ref sectionRef = column.getSection(ChunkUtil.chunkCoordinate(y)); + + assert sectionRef != null; + + BlockSection blockSection = commandBuffer.getComponent(sectionRef, BlockSection.getComponentType()); + + assert blockSection != null; + + ChunkSection chunkSection = commandBuffer.getComponent(sectionRef, ChunkSection.getComponentType()); + + assert chunkSection != null; + + int worldX = ChunkUtil.worldCoordFromLocalCoord(chunkSection.getX(), x); + int worldY = ChunkUtil.worldCoordFromLocalCoord(chunkSection.getY(), y); + int worldZ = ChunkUtil.worldCoordFromLocalCoord(chunkSection.getZ(), z); + World world = commandBuffer.getExternalData().getWorld(); + WorldTimeResource worldTimeResource = world.getEntityStore().getStore().getResource(WorldTimeResource.getResourceType()); + coop.handleBlockBroken(world, worldTimeResource, entityStore, worldX, worldY, worldZ); + } + } + } + + @NullableDecl + @Override + public Query getQuery() { + return QUERY; + } + } + + public static class OnFarmBlockAdded extends RefSystem { + private static final Query QUERY = Query.and(BlockModule.BlockStateInfo.getComponentType(), FarmingBlock.getComponentType()); + + public OnFarmBlockAdded() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + FarmingBlock farmingBlock = commandBuffer.getComponent(ref, FarmingBlock.getComponentType()); + + assert farmingBlock != null; + + BlockModule.BlockStateInfo info = commandBuffer.getComponent(ref, BlockModule.BlockStateInfo.getComponentType()); + + assert info != null; + + if (farmingBlock.getLastTickGameTime() == null) { + BlockChunk blockChunk = commandBuffer.getComponent(info.getChunkRef(), BlockChunk.getComponentType()); + int blockId = blockChunk.getBlock( + ChunkUtil.xFromBlockInColumn(info.getIndex()), ChunkUtil.yFromBlockInColumn(info.getIndex()), ChunkUtil.zFromBlockInColumn(info.getIndex()) + ); + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + if (blockType.getFarming() == null) { + return; + } + + farmingBlock.setCurrentStageSet(blockType.getFarming().getStartingStageSet()); + farmingBlock.setLastTickGameTime( + store.getExternalData().getWorld().getEntityStore().getStore().getResource(WorldTimeResource.getResourceType()).getGameTime() + ); + if (blockType.getFarming().getStages() != null) { + FarmingStageData[] stages = blockType.getFarming().getStages().get(blockType.getFarming().getStartingStageSet()); + if (stages != null && stages.length > 0) { + boolean found = false; + + for (int i = 0; i < stages.length; i++) { + FarmingStageData stage = stages[i]; + switch (stage) { + case BlockTypeFarmingStageData data: + if (data.getBlock().equals(blockType.getId())) { + farmingBlock.setGrowthProgress(i); + found = true; + } + break; + case BlockStateFarmingStageData datax: + BlockType stateBlockType = blockType.getBlockForState(datax.getState()); + if (stateBlockType != null && stateBlockType.getId().equals(blockType.getId())) { + farmingBlock.setGrowthProgress(i); + found = true; + } + break; + default: + } + } + + if (!found) { + Ref sectionRef = commandBuffer.getComponent(info.getChunkRef(), ChunkColumn.getComponentType()) + .getSection(ChunkUtil.chunkCoordinate(ChunkUtil.yFromBlockInColumn(info.getIndex()))); + stages[0] + .apply( + commandBuffer, + sectionRef, + ref, + ChunkUtil.xFromBlockInColumn(info.getIndex()), + ChunkUtil.yFromBlockInColumn(info.getIndex()), + ChunkUtil.zFromBlockInColumn(info.getIndex()), + null + ); + } + } + } + } + + if (farmingBlock.getLastTickGameTime() == null) { + farmingBlock.setLastTickGameTime( + store.getExternalData().getWorld().getEntityStore().getStore().getResource(WorldTimeResource.getResourceType()).getGameTime() + ); + } + + int x = ChunkUtil.xFromBlockInColumn(info.getIndex()); + int y = ChunkUtil.yFromBlockInColumn(info.getIndex()); + int z = ChunkUtil.zFromBlockInColumn(info.getIndex()); + BlockComponentChunk blockComponentChunk = commandBuffer.getComponent(info.getChunkRef(), BlockComponentChunk.getComponentType()); + + assert blockComponentChunk != null; + + ChunkColumn column = commandBuffer.getComponent(info.getChunkRef(), ChunkColumn.getComponentType()); + + assert column != null; + + Ref section = column.getSection(ChunkUtil.chunkCoordinate(y)); + BlockSection blockSection = commandBuffer.getComponent(section, BlockSection.getComponentType()); + FarmingUtil.tickFarming(commandBuffer, blockSection, section, ref, farmingBlock, x, y, z, true); + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Nullable + @Override + public Query getQuery() { + return QUERY; + } + } + + public static class OnSoilAdded extends RefSystem { + private static final Query QUERY = Query.and(BlockModule.BlockStateInfo.getComponentType(), TilledSoilBlock.getComponentType()); + + public OnSoilAdded() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + TilledSoilBlock soil = commandBuffer.getComponent(ref, TilledSoilBlock.getComponentType()); + + assert soil != null; + + BlockModule.BlockStateInfo info = commandBuffer.getComponent(ref, BlockModule.BlockStateInfo.getComponentType()); + + assert info != null; + + if (!soil.isPlanted()) { + int x = ChunkUtil.xFromBlockInColumn(info.getIndex()); + int y = ChunkUtil.yFromBlockInColumn(info.getIndex()); + int z = ChunkUtil.zFromBlockInColumn(info.getIndex()); + + assert info.getChunkRef() != null; + + BlockChunk blockChunk = commandBuffer.getComponent(info.getChunkRef(), BlockChunk.getComponentType()); + + assert blockChunk != null; + + BlockSection blockSection = blockChunk.getSectionAtBlockY(y); + Instant decayTime = soil.getDecayTime(); + if (decayTime == null) { + BlockType blockType = BlockType.getAssetMap().getAsset(blockSection.get(x, y, z)); + FarmingSystems.updateSoilDecayTime(commandBuffer, soil, blockType); + } + + if (decayTime == null) { + return; + } + + blockSection.scheduleTick(ChunkUtil.indexBlock(x, y, z), decayTime); + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Nullable + @Override + public Query getQuery() { + return QUERY; + } + } + + public static class Ticking extends EntityTickingSystem { + private static final Query QUERY = Query.and(BlockSection.getComponentType(), ChunkSection.getComponentType()); + + public Ticking() { + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + BlockSection blocks = archetypeChunk.getComponent(index, BlockSection.getComponentType()); + + assert blocks != null; + + if (blocks.getTickingBlocksCountCopy() != 0) { + ChunkSection section = archetypeChunk.getComponent(index, ChunkSection.getComponentType()); + + assert section != null; + + BlockComponentChunk blockComponentChunk = commandBuffer.getComponent(section.getChunkColumnReference(), BlockComponentChunk.getComponentType()); + + assert blockComponentChunk != null; + + Ref ref = archetypeChunk.getReferenceTo(index); + blocks.forEachTicking( + blockComponentChunk, commandBuffer, section.getY(), (blockComponentChunk1, commandBuffer1, localX, localY, localZ, blockId) -> { + Ref blockRef = blockComponentChunk1.getEntityReference(ChunkUtil.indexBlockInColumn(localX, localY, localZ)); + if (blockRef == null) { + return BlockTickStrategy.IGNORED; + } else { + FarmingBlock farming = commandBuffer1.getComponent(blockRef, FarmingBlock.getComponentType()); + if (farming != null) { + FarmingUtil.tickFarming(commandBuffer1, blocks, ref, blockRef, farming, localX, localY, localZ, false); + return BlockTickStrategy.SLEEP; + } else { + TilledSoilBlock soil = commandBuffer1.getComponent(blockRef, TilledSoilBlock.getComponentType()); + if (soil != null) { + tickSoil(commandBuffer1, blockComponentChunk1, blockRef, soil); + return BlockTickStrategy.SLEEP; + } else { + CoopBlock coop = commandBuffer1.getComponent(blockRef, CoopBlock.getComponentType()); + if (coop != null) { + tickCoop(commandBuffer1, blockComponentChunk1, blockRef, coop); + return BlockTickStrategy.SLEEP; + } else { + return BlockTickStrategy.IGNORED; + } + } + } + } + } + ); + } + } + + private static void tickSoil( + CommandBuffer commandBuffer, BlockComponentChunk blockComponentChunk, Ref blockRef, TilledSoilBlock soilBlock + ) { + BlockModule.BlockStateInfo info = commandBuffer.getComponent(blockRef, BlockModule.BlockStateInfo.getComponentType()); + + assert info != null; + + int x = ChunkUtil.xFromBlockInColumn(info.getIndex()); + int y = ChunkUtil.yFromBlockInColumn(info.getIndex()); + int z = ChunkUtil.zFromBlockInColumn(info.getIndex()); + if (y < 320) { + int checkIndex = ChunkUtil.indexBlockInColumn(x, y + 1, z); + Ref aboveBlockRef = blockComponentChunk.getEntityReference(checkIndex); + boolean hasCrop = false; + if (aboveBlockRef != null) { + FarmingBlock farmingBlock = commandBuffer.getComponent(aboveBlockRef, FarmingBlock.getComponentType()); + hasCrop = farmingBlock != null; + } + + assert info.getChunkRef() != null; + + BlockChunk blockChunk = commandBuffer.getComponent(info.getChunkRef(), BlockChunk.getComponentType()); + + assert blockChunk != null; + + BlockSection blockSection = blockChunk.getSectionAtBlockY(y); + BlockType blockType = BlockType.getAssetMap().getAsset(blockSection.get(x, y, z)); + Instant currentTime = commandBuffer.getExternalData() + .getWorld() + .getEntityStore() + .getStore() + .getResource(WorldTimeResource.getResourceType()) + .getGameTime(); + Instant decayTime = soilBlock.getDecayTime(); + if (soilBlock.isPlanted() && !hasCrop) { + if (!FarmingSystems.updateSoilDecayTime(commandBuffer, soilBlock, blockType)) { + return; + } + + if (decayTime != null) { + blockSection.scheduleTick(ChunkUtil.indexBlock(x, y, z), decayTime); + } + } else if (!soilBlock.isPlanted() && !hasCrop) { + if (decayTime == null || !decayTime.isAfter(currentTime)) { + assert info.getChunkRef() != null; + + if (blockType != null && blockType.getFarming() != null && blockType.getFarming().getSoilConfig() != null) { + FarmingData.SoilConfig soilConfig = blockType.getFarming().getSoilConfig(); + String targetBlock = soilConfig.getTargetBlock(); + if (targetBlock == null) { + return; + } else { + int targetBlockId = BlockType.getAssetMap().getIndex(targetBlock); + if (targetBlockId == Integer.MIN_VALUE) { + return; + } else { + BlockType targetBlockType = BlockType.getAssetMap().getAsset(targetBlockId); + int rotation = blockSection.getRotationIndex(x, y, z); + WorldChunk worldChunk = commandBuffer.getComponent(info.getChunkRef(), WorldChunk.getComponentType()); + commandBuffer.run(_store -> worldChunk.setBlock(x, y, z, targetBlockId, targetBlockType, rotation, 0, 0)); + return; + } + } + } else { + return; + } + } + } else if (hasCrop) { + soilBlock.setDecayTime(null); + } + + String targetBlock = soilBlock.computeBlockType(currentTime, blockType); + if (targetBlock != null && !targetBlock.equals(blockType.getId())) { + WorldChunk worldChunk = commandBuffer.getComponent(info.getChunkRef(), WorldChunk.getComponentType()); + int rotation = blockSection.getRotationIndex(x, y, z); + int targetBlockId = BlockType.getAssetMap().getIndex(targetBlock); + BlockType targetBlockType = BlockType.getAssetMap().getAsset(targetBlockId); + commandBuffer.run(_store -> worldChunk.setBlock(x, y, z, targetBlockId, targetBlockType, rotation, 0, 2)); + } + + soilBlock.setPlanted(hasCrop); + } + } + + private static void tickCoop( + CommandBuffer commandBuffer, BlockComponentChunk blockComponentChunk, Ref blockRef, CoopBlock coopBlock + ) { + BlockModule.BlockStateInfo info = commandBuffer.getComponent(blockRef, BlockModule.BlockStateInfo.getComponentType()); + + assert info != null; + + Store store = commandBuffer.getExternalData().getWorld().getEntityStore().getStore(); + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + FarmingCoopAsset coopAsset = coopBlock.getCoopAsset(); + if (coopAsset != null) { + int x = ChunkUtil.xFromBlockInColumn(info.getIndex()); + int y = ChunkUtil.yFromBlockInColumn(info.getIndex()); + int z = ChunkUtil.zFromBlockInColumn(info.getIndex()); + BlockChunk blockChunk = commandBuffer.getComponent(info.getChunkRef(), BlockChunk.getComponentType()); + + assert blockChunk != null; + + ChunkColumn column = commandBuffer.getComponent(info.getChunkRef(), ChunkColumn.getComponentType()); + + assert column != null; + + Ref sectionRef = column.getSection(ChunkUtil.chunkCoordinate(y)); + + assert sectionRef != null; + + BlockSection blockSection = commandBuffer.getComponent(sectionRef, BlockSection.getComponentType()); + + assert blockSection != null; + + ChunkSection chunkSection = commandBuffer.getComponent(sectionRef, ChunkSection.getComponentType()); + + assert chunkSection != null; + + int worldX = ChunkUtil.worldCoordFromLocalCoord(chunkSection.getX(), x); + int worldY = ChunkUtil.worldCoordFromLocalCoord(chunkSection.getY(), y); + int worldZ = ChunkUtil.worldCoordFromLocalCoord(chunkSection.getZ(), z); + World world = commandBuffer.getExternalData().getWorld(); + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(worldX, worldZ)); + double blockRotation = chunk.getRotation(worldX, worldY, worldZ).yaw().getRadians(); + Vector3d spawnOffset = new Vector3d().assign(coopAsset.getResidentSpawnOffset()).rotateY((float)blockRotation); + Vector3i coopLocation = new Vector3i(worldX, worldY, worldZ); + boolean tryCapture = coopAsset.getCaptureWildNPCsInRange(); + float captureRange = coopAsset.getWildCaptureRadius(); + if (tryCapture && captureRange >= 0.0F) { + world.execute(() -> { + for (Ref entity : TargetUtil.getAllEntitiesInSphere(coopLocation.toVector3d(), captureRange, store)) { + coopBlock.tryPutWildResidentFromWild(store, entity, worldTimeResource, coopLocation); + } + }); + } + + if (coopBlock.shouldResidentsBeInCoop(worldTimeResource)) { + world.execute(() -> coopBlock.ensureNoResidentsInWorld(store)); + } else { + world.execute(() -> { + coopBlock.ensureSpawnResidentsInWorld(world, store, coopLocation.toVector3d(), spawnOffset); + coopBlock.generateProduceToInventory(worldTimeResource); + Vector3i blockPos = new Vector3i(worldX, worldY, worldZ); + BlockType currentBlockType = world.getBlockType(blockPos); + + assert currentBlockType != null; + + chunk.setBlockInteractionState(blockPos, currentBlockType, coopBlock.hasProduce() ? "Produce_Ready" : "default"); + }); + } + + Instant nextTickInstant = coopBlock.getNextScheduledTick(worldTimeResource); + if (nextTickInstant != null) { + blockSection.scheduleTick(ChunkUtil.indexBlock(x, y, z), nextTickInstant); + } + } + } + + @Nullable + @Override + public Query getQuery() { + return QUERY; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/FarmingUtil.java b/src/com/hypixel/hytale/builtin/adventure/farming/FarmingUtil.java new file mode 100644 index 0000000..1a17501 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/FarmingUtil.java @@ -0,0 +1,302 @@ +package com.hypixel.hytale.builtin.adventure.farming; + +import com.hypixel.hytale.builtin.adventure.farming.states.FarmingBlock; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.Rangef; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.HarvestingDropType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.FarmingData; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.FarmingStageData; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.GrowthModifierAsset; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.entity.ItemUtils; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.entity.component.PersistentModel; +import com.hypixel.hytale.server.core.modules.interaction.BlockHarvestUtils; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.metadata.CapturedNPCMetadata; +import java.time.Instant; +import java.util.Map; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class FarmingUtil { + private static final int MAX_SECONDS_BETWEEN_TICKS = 15; + private static final int BETWEEN_RANDOM = 10; + + public FarmingUtil() { + } + + public static void tickFarming( + CommandBuffer commandBuffer, + BlockSection blockSection, + Ref sectionRef, + Ref blockRef, + FarmingBlock farmingBlock, + int x, + int y, + int z, + boolean initialTick + ) { + World world = commandBuffer.getExternalData().getWorld(); + WorldTimeResource worldTimeResource = world.getEntityStore().getStore().getResource(WorldTimeResource.getResourceType()); + Instant currentTime = worldTimeResource.getGameTime(); + BlockType blockType = farmingBlock.getPreviousBlockType() != null + ? BlockType.getAssetMap().getAsset(farmingBlock.getPreviousBlockType()) + : BlockType.getAssetMap().getAsset(blockSection.get(x, y, z)); + if (blockType != null) { + if (blockType.getFarming() != null) { + FarmingData farmingConfig = blockType.getFarming(); + if (farmingConfig.getStages() != null) { + float currentProgress = farmingBlock.getGrowthProgress(); + int currentStage = (int)currentProgress; + String currentStageSet = farmingBlock.getCurrentStageSet(); + FarmingStageData[] stages = farmingConfig.getStages().get(currentStageSet); + if (stages != null) { + if (currentStage < 0) { + currentStage = 0; + currentProgress = 0.0F; + farmingBlock.setGrowthProgress(0.0F); + } + + if (currentStage >= stages.length) { + commandBuffer.removeEntity(blockRef, RemoveReason.REMOVE); + } else { + long remainingTimeSeconds = currentTime.getEpochSecond() - farmingBlock.getLastTickGameTime().getEpochSecond(); + ChunkSection section = commandBuffer.getComponent(sectionRef, ChunkSection.getComponentType()); + int worldX = ChunkUtil.worldCoordFromLocalCoord(section.getX(), x); + int worldY = ChunkUtil.worldCoordFromLocalCoord(section.getY(), y); + int worldZ = ChunkUtil.worldCoordFromLocalCoord(section.getZ(), z); + + while (currentStage < stages.length) { + FarmingStageData stage = stages[currentStage]; + if (stage.shouldStop(commandBuffer, sectionRef, blockRef, x, y, z)) { + farmingBlock.setGrowthProgress(stages.length); + commandBuffer.removeEntity(blockRef, RemoveReason.REMOVE); + break; + } + + Rangef range = stage.getDuration(); + if (range == null) { + commandBuffer.removeEntity(blockRef, RemoveReason.REMOVE); + break; + } + + double rand = HashUtil.random(farmingBlock.getGeneration(), worldX, worldY, worldZ); + double baseDuration = range.min + (range.max - range.min) * rand; + long remainingDurationSeconds = Math.round(baseDuration * (1.0 - currentProgress % 1.0)); + double growthMultiplier = 1.0; + if (farmingConfig.getGrowthModifiers() != null) { + for (String modifierName : farmingConfig.getGrowthModifiers()) { + GrowthModifierAsset modifier = GrowthModifierAsset.getAssetMap().getAsset(modifierName); + if (modifier != null) { + growthMultiplier *= modifier.getCurrentGrowthMultiplier(commandBuffer, sectionRef, blockRef, x, y, z, initialTick); + } + } + } + + remainingDurationSeconds = Math.round(remainingDurationSeconds / growthMultiplier); + if (remainingTimeSeconds < remainingDurationSeconds) { + currentProgress += (float)(remainingTimeSeconds / (baseDuration / growthMultiplier)); + farmingBlock.setGrowthProgress(currentProgress); + long nextGrowthInNanos = (remainingDurationSeconds - remainingTimeSeconds) * 1000000000L; + long randCap = (long)( + (15.0 + 10.0 * HashUtil.random(farmingBlock.getGeneration() ^ 3405692655L, worldX, worldY, worldZ)) + * world.getTps() + * WorldTimeResource.getSecondsPerTick(world) + * 1.0E9 + ); + long cappedNextGrowthInNanos = Math.min(nextGrowthInNanos, randCap); + blockSection.scheduleTick(ChunkUtil.indexBlock(x, y, z), currentTime.plusNanos(cappedNextGrowthInNanos)); + break; + } + + remainingTimeSeconds -= remainingDurationSeconds; + currentProgress = ++currentStage; + farmingBlock.setGrowthProgress(currentProgress); + farmingBlock.setGeneration(farmingBlock.getGeneration() + 1); + if (currentStage >= stages.length) { + if (stages[currentStage - 1].implementsShouldStop()) { + currentStage = stages.length - 1; + farmingBlock.setGrowthProgress(currentStage); + stages[currentStage].apply(commandBuffer, sectionRef, blockRef, x, y, z, stages[currentStage]); + } else { + farmingBlock.setGrowthProgress(stages.length); + commandBuffer.removeEntity(blockRef, RemoveReason.REMOVE); + } + } else { + farmingBlock.setExecutions(0); + stages[currentStage].apply(commandBuffer, sectionRef, blockRef, x, y, z, stages[currentStage - 1]); + } + } + + farmingBlock.setLastTickGameTime(currentTime); + } + } + } + } + } + } + + public static void harvest( + @Nonnull World world, + @Nonnull ComponentAccessor store, + @Nonnull Ref ref, + @Nonnull BlockType blockType, + int rotationIndex, + @Nonnull Vector3i blockPosition + ) { + if (world.getGameplayConfig().getWorldConfig().isBlockGatheringAllowed()) { + harvest0(store, ref, blockType, rotationIndex, blockPosition); + } + } + + @NullableDecl + public static CapturedNPCMetadata generateCapturedNPCMetadata(ComponentAccessor componentAccessor, Ref entityRef, int roleIndex) { + PersistentModel persistentModel = componentAccessor.getComponent(entityRef, PersistentModel.getComponentType()); + if (persistentModel == null) { + return null; + } else { + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(persistentModel.getModelReference().getModelAssetId()); + CapturedNPCMetadata meta = new CapturedNPCMetadata(); + if (modelAsset != null) { + meta.setIconPath(modelAsset.getIcon()); + } + + meta.setRoleIndex(roleIndex); + return meta; + } + } + + protected static boolean harvest0( + @Nonnull ComponentAccessor store, + @Nonnull Ref ref, + @Nonnull BlockType blockType, + int rotationIndex, + @Nonnull Vector3i blockPosition + ) { + FarmingData farmingConfig = blockType.getFarming(); + if (farmingConfig == null || farmingConfig.getStages() == null) { + return false; + } else if (blockType.getGathering().getHarvest() == null) { + return false; + } else { + World world = store.getExternalData().getWorld(); + Vector3d centerPosition = new Vector3d(); + blockType.getBlockCenter(rotationIndex, centerPosition); + centerPosition.add(blockPosition); + if (farmingConfig.getStageSetAfterHarvest() == null) { + giveDrops(store, ref, centerPosition, blockType); + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(blockPosition.x, blockPosition.z)); + if (chunk != null) { + chunk.breakBlock(blockPosition.x, blockPosition.y, blockPosition.z); + } + + return true; + } else { + giveDrops(store, ref, centerPosition, blockType); + Map stageSets = farmingConfig.getStages(); + FarmingStageData[] stages = stageSets.get(farmingConfig.getStartingStageSet()); + if (stages == null) { + return false; + } else { + int currentStageIndex = stages.length - 1; + FarmingStageData previousStage = stages[currentStageIndex]; + String newStageSet = farmingConfig.getStageSetAfterHarvest(); + FarmingStageData[] newStages = stageSets.get(newStageSet); + if (newStages != null && newStages.length != 0) { + Store chunkStore = world.getChunkStore().getStore(); + Ref chunkRef = world.getChunkStore().getChunkReference(ChunkUtil.indexChunkFromBlock(blockPosition.x, blockPosition.z)); + if (chunkRef == null) { + return false; + } else { + BlockComponentChunk blockComponentChunk = chunkStore.getComponent(chunkRef, BlockComponentChunk.getComponentType()); + if (blockComponentChunk == null) { + return false; + } else { + Instant now = store.getExternalData() + .getWorld() + .getEntityStore() + .getStore() + .getResource(WorldTimeResource.getResourceType()) + .getGameTime(); + int blockIndexColumn = ChunkUtil.indexBlockInColumn(blockPosition.x, blockPosition.y, blockPosition.z); + Ref blockRef = blockComponentChunk.getEntityReference(blockIndexColumn); + FarmingBlock farmingBlock; + if (blockRef == null) { + Holder blockEntity = ChunkStore.REGISTRY.newHolder(); + blockEntity.putComponent(BlockModule.BlockStateInfo.getComponentType(), new BlockModule.BlockStateInfo(blockIndexColumn, chunkRef)); + farmingBlock = new FarmingBlock(); + farmingBlock.setLastTickGameTime(now); + farmingBlock.setCurrentStageSet(newStageSet); + blockEntity.addComponent(FarmingBlock.getComponentType(), farmingBlock); + blockRef = chunkStore.addEntity(blockEntity, AddReason.SPAWN); + } else { + farmingBlock = chunkStore.ensureAndGetComponent(blockRef, FarmingBlock.getComponentType()); + } + + farmingBlock.setCurrentStageSet(newStageSet); + farmingBlock.setGrowthProgress(0.0F); + farmingBlock.setExecutions(0); + farmingBlock.setGeneration(farmingBlock.getGeneration() + 1); + farmingBlock.setLastTickGameTime(now); + Ref sectionRef = world.getChunkStore() + .getChunkSectionReference( + ChunkUtil.chunkCoordinate(blockPosition.x), + ChunkUtil.chunkCoordinate(blockPosition.y), + ChunkUtil.chunkCoordinate(blockPosition.z) + ); + if (sectionRef == null) { + return false; + } else if (blockRef == null) { + return false; + } else { + BlockSection section = chunkStore.getComponent(sectionRef, BlockSection.getComponentType()); + if (section != null) { + section.scheduleTick(ChunkUtil.indexBlock(blockPosition.x, blockPosition.y, blockPosition.z), now); + } + + newStages[0].apply(chunkStore, sectionRef, blockRef, blockPosition.x, blockPosition.y, blockPosition.z, previousStage); + return true; + } + } + } + } else { + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(blockPosition.x, blockPosition.z)); + if (chunk != null) { + chunk.breakBlock(blockPosition.x, blockPosition.y, blockPosition.z); + } + + return false; + } + } + } + } + } + + protected static void giveDrops( + @Nonnull ComponentAccessor store, @Nonnull Ref ref, @Nonnull Vector3d origin, @Nonnull BlockType blockType + ) { + HarvestingDropType harvest = blockType.getGathering().getHarvest(); + String itemId = harvest.getItemId(); + String dropListId = harvest.getDropListId(); + BlockHarvestUtils.getDrops(blockType, 1, itemId, dropListId).forEach(itemStack -> ItemUtils.interactivelyPickupItem(ref, itemStack, origin, store)); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/component/CoopResidentComponent.java b/src/com/hypixel/hytale/builtin/adventure/farming/component/CoopResidentComponent.java new file mode 100644 index 0000000..772c55b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/component/CoopResidentComponent.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.builtin.adventure.farming.component; + +import com.hypixel.hytale.builtin.adventure.farming.FarmingPlugin; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class CoopResidentComponent implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(CoopResidentComponent.class, CoopResidentComponent::new) + .append(new KeyedCodec<>("CoopLocation", Vector3i.CODEC), (comp, ref) -> comp.coopLocation = ref, comp -> comp.coopLocation) + .add() + .append( + new KeyedCodec<>("MarkedForDespawn", BuilderCodec.BOOLEAN), + (comp, markedForDespawn) -> comp.markedForDespawn = markedForDespawn, + comp -> comp.markedForDespawn + ) + .add() + .build(); + private Vector3i coopLocation = new Vector3i(); + private boolean markedForDespawn; + + public CoopResidentComponent() { + } + + public static ComponentType getComponentType() { + return FarmingPlugin.get().getCoopResidentComponentType(); + } + + public void setCoopLocation(Vector3i coopLocation) { + this.coopLocation = coopLocation; + } + + public Vector3i getCoopLocation() { + return this.coopLocation; + } + + public void setMarkedForDespawn(boolean markedForDespawn) { + this.markedForDespawn = markedForDespawn; + } + + public boolean getMarkedForDespawn() { + return this.markedForDespawn; + } + + @NullableDecl + @Override + public Component clone() { + CoopResidentComponent component = new CoopResidentComponent(); + component.coopLocation.assign(this.coopLocation); + component.markedForDespawn = this.markedForDespawn; + return component; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/config/FarmingCoopAsset.java b/src/com/hypixel/hytale/builtin/adventure/farming/config/FarmingCoopAsset.java new file mode 100644 index 0000000..83dc6ca --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/config/FarmingCoopAsset.java @@ -0,0 +1,152 @@ +package com.hypixel.hytale.builtin.adventure.farming.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.tagset.config.NPCGroup; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.range.IntRange; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class FarmingCoopAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + FarmingCoopAsset.class, FarmingCoopAsset::new, Codec.STRING, (o, v) -> o.id = v, FarmingCoopAsset::getId, (o, data) -> o.data = data, o -> o.data + ) + .appendInherited( + new KeyedCodec<>("MaxResidents", Codec.INTEGER), + (asset, maxResidents) -> asset.maxResidents = maxResidents, + asset -> asset.maxResidents, + (asset, parent) -> asset.maxResidents = parent.maxResidents + ) + .add() + .append( + new KeyedCodec<>("ProduceDrops", new MapCodec<>(ItemDropList.CHILD_ASSET_CODEC, HashMap::new)), + (asset, drops) -> asset.produceDrops = drops, + asset -> asset.produceDrops + ) + .addValidator(ItemDropList.VALIDATOR_CACHE.getMapValueValidator()) + .add() + .append( + new KeyedCodec<>("ResidentSpawnOffset", Vector3d.CODEC), + (asset, residentSpawnOffset) -> asset.residentSpawnOffset.assign(residentSpawnOffset), + asset -> asset.residentSpawnOffset + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("ResidentRoamTime", IntRange.CODEC), + (asset, residentRoamTime) -> asset.residentRoamTime = residentRoamTime, + asset -> asset.residentRoamTime + ) + .add() + .append( + new KeyedCodec<>("CaptureWildNPCsInRange", Codec.BOOLEAN), + (asset, captureWildNPCsInRange) -> asset.captureWildNPCsInRange = captureWildNPCsInRange, + asset -> asset.captureWildNPCsInRange + ) + .add() + .append( + new KeyedCodec<>("WildCaptureRadius", Codec.FLOAT), + (asset, wildCaptureRadius) -> asset.wildCaptureRadius = wildCaptureRadius, + asset -> asset.wildCaptureRadius + ) + .add() + .appendInherited( + new KeyedCodec<>("AcceptedNpcGroups", NPCGroup.CHILD_ASSET_CODEC_ARRAY), + (o, v) -> o.acceptedNpcGroupIds = v, + o -> o.acceptedNpcGroupIds, + (o, p) -> o.acceptedNpcGroupIds = p.acceptedNpcGroupIds + ) + .addValidator(NPCGroup.VALIDATOR_CACHE.getArrayValidator()) + .add() + .afterDecode(captureData -> { + if (captureData.acceptedNpcGroupIds != null) { + captureData.acceptedNpcGroupIndexes = new int[captureData.acceptedNpcGroupIds.length]; + + for (int i = 0; i < captureData.acceptedNpcGroupIds.length; i++) { + int assetIdx = NPCGroup.getAssetMap().getIndex(captureData.acceptedNpcGroupIds[i]); + captureData.acceptedNpcGroupIndexes[i] = assetIdx; + } + } + }) + .build(); + private static AssetStore> ASSET_STORE; + private AssetExtraInfo.Data data; + protected String id; + protected int maxResidents; + protected Map produceDrops = Collections.emptyMap(); + protected IntRange residentRoamTime; + protected Vector3d residentSpawnOffset = new Vector3d(); + protected String[] acceptedNpcGroupIds; + protected int[] acceptedNpcGroupIndexes; + protected boolean captureWildNPCsInRange; + protected float wildCaptureRadius; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(FarmingCoopAsset.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public FarmingCoopAsset() { + } + + public FarmingCoopAsset(String id) { + this.id = id; + } + + public String getId() { + return this.id; + } + + public Map getProduceDrops() { + return this.produceDrops; + } + + public int getMaxResidents() { + return this.maxResidents; + } + + public IntRange getResidentRoamTime() { + return this.residentRoamTime; + } + + public Vector3d getResidentSpawnOffset() { + return this.residentSpawnOffset; + } + + public int[] getAcceptedNpcGroupIndexes() { + return this.acceptedNpcGroupIndexes; + } + + public float getWildCaptureRadius() { + return this.wildCaptureRadius; + } + + public boolean getCaptureWildNPCsInRange() { + return this.captureWildNPCsInRange; + } + + @Nonnull + @Override + public String toString() { + return "FarmingCoopAsset{id='" + this.id + "', maxResidents=" + this.maxResidents + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/config/modifiers/FertilizerGrowthModifierAsset.java b/src/com/hypixel/hytale/builtin/adventure/farming/config/modifiers/FertilizerGrowthModifierAsset.java new file mode 100644 index 0000000..2b266b5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/config/modifiers/FertilizerGrowthModifierAsset.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.adventure.farming.config.modifiers; + +import com.hypixel.hytale.builtin.adventure.farming.states.TilledSoilBlock; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.GrowthModifierAsset; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; + +public class FertilizerGrowthModifierAsset extends GrowthModifierAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + FertilizerGrowthModifierAsset.class, FertilizerGrowthModifierAsset::new, ABSTRACT_CODEC + ) + .build(); + + public FertilizerGrowthModifierAsset() { + } + + @Override + public double getCurrentGrowthMultiplier( + CommandBuffer commandBuffer, Ref sectionRef, Ref blockRef, int x, int y, int z, boolean initialTick + ) { + ChunkSection chunkSection = commandBuffer.getComponent(sectionRef, ChunkSection.getComponentType()); + Ref chunk = chunkSection.getChunkColumnReference(); + BlockComponentChunk blockComponentChunk = commandBuffer.getComponent(chunk, BlockComponentChunk.getComponentType()); + Ref blockRefBelow = blockComponentChunk.getEntityReference(ChunkUtil.indexBlockInColumn(x, y - 1, z)); + if (blockRefBelow == null) { + return 1.0; + } else { + TilledSoilBlock soil = commandBuffer.getComponent(blockRefBelow, TilledSoilBlock.getComponentType()); + return soil != null && soil.isFertilized() ? super.getCurrentGrowthMultiplier(commandBuffer, sectionRef, blockRef, x, y, z, initialTick) : 1.0; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/config/modifiers/LightLevelGrowthModifierAsset.java b/src/com/hypixel/hytale/builtin/adventure/farming/config/modifiers/LightLevelGrowthModifierAsset.java new file mode 100644 index 0000000..c29be91 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/config/modifiers/LightLevelGrowthModifierAsset.java @@ -0,0 +1,155 @@ +package com.hypixel.hytale.builtin.adventure.farming.config.modifiers; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.Range; +import com.hypixel.hytale.protocol.Rangef; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.GrowthModifierAsset; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkLightData; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; + +public class LightLevelGrowthModifierAsset extends GrowthModifierAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + LightLevelGrowthModifierAsset.class, LightLevelGrowthModifierAsset::new, ABSTRACT_CODEC + ) + .addField( + new KeyedCodec<>("ArtificialLight", LightLevelGrowthModifierAsset.ArtificialLight.CODEC), + (lightLevel, artificialLight) -> lightLevel.artificialLight = artificialLight, + lightLevel -> lightLevel.artificialLight + ) + .addField( + new KeyedCodec<>("Sunlight", ProtocolCodecs.RANGEF), (lightLevel, sunLight) -> lightLevel.sunlight = sunLight, lightLevel -> lightLevel.sunlight + ) + .addField( + new KeyedCodec<>("RequireBoth", Codec.BOOLEAN), + (lightLevel, requireBoth) -> lightLevel.requireBoth = requireBoth, + lightLevel -> lightLevel.requireBoth + ) + .build(); + protected LightLevelGrowthModifierAsset.ArtificialLight artificialLight; + protected Rangef sunlight; + protected boolean requireBoth; + + public LightLevelGrowthModifierAsset() { + } + + public LightLevelGrowthModifierAsset.ArtificialLight getArtificialLight() { + return this.artificialLight; + } + + public Rangef getSunlight() { + return this.sunlight; + } + + public boolean isRequireBoth() { + return this.requireBoth; + } + + protected boolean checkArtificialLight(byte red, byte green, byte blue) { + LightLevelGrowthModifierAsset.ArtificialLight artificialLight = this.artificialLight; + Range redRange = artificialLight.getRed(); + Range greenRange = artificialLight.getGreen(); + Range blueRange = artificialLight.getBlue(); + return isInRange(redRange, red) && isInRange(greenRange, green) && isInRange(blueRange, blue); + } + + protected boolean checkSunLight(WorldTimeResource worldTimeResource, byte sky) { + Rangef range = this.sunlight; + double sunlightFactor = worldTimeResource.getSunlightFactor(); + double daylight = sunlightFactor * sky; + return range.min <= daylight && daylight <= range.max; + } + + protected static boolean isInRange(@Nonnull Range range, int value) { + return range.min <= value && value <= range.max; + } + + @Override + public double getCurrentGrowthMultiplier( + CommandBuffer commandBuffer, Ref sectionRef, Ref blockRef, int x, int y, int z, boolean initialTick + ) { + BlockSection blockSection = commandBuffer.getComponent(sectionRef, BlockSection.getComponentType()); + short lightRaw = blockSection.getGlobalLight().getLightRaw(x, y, z); + byte redLight = ChunkLightData.getLightValue(lightRaw, 0); + byte greenLight = ChunkLightData.getLightValue(lightRaw, 1); + byte blueLight = ChunkLightData.getLightValue(lightRaw, 2); + byte skyLight = ChunkLightData.getLightValue(lightRaw, 3); + WorldTimeResource worldTimeResource = commandBuffer.getExternalData() + .getWorld() + .getEntityStore() + .getStore() + .getResource(WorldTimeResource.getResourceType()); + boolean active = false; + boolean onlySunlight = false; + if (this.requireBoth) { + active = this.checkArtificialLight(redLight, greenLight, blueLight) && this.checkSunLight(worldTimeResource, skyLight); + } else if (this.checkArtificialLight(redLight, greenLight, blueLight)) { + active = true; + } else if (this.checkSunLight(worldTimeResource, skyLight)) { + active = true; + onlySunlight = true; + } + + if (active) { + return onlySunlight && initialTick + ? super.getCurrentGrowthMultiplier(commandBuffer, sectionRef, blockRef, x, y, z, initialTick) * 0.6F + : super.getCurrentGrowthMultiplier(commandBuffer, sectionRef, blockRef, x, y, z, initialTick); + } else { + return 1.0; + } + } + + @Nonnull + @Override + public String toString() { + return "LightLevelGrowthModifierAsset{artificialLight=" + + this.artificialLight + + ", sunLight=" + + this.sunlight + + ", requireBoth=" + + this.requireBoth + + "} " + + super.toString(); + } + + public static class ArtificialLight { + public static final BuilderCodec CODEC = BuilderCodec.builder( + LightLevelGrowthModifierAsset.ArtificialLight.class, LightLevelGrowthModifierAsset.ArtificialLight::new + ) + .addField(new KeyedCodec<>("Red", ProtocolCodecs.RANGE), (light, red) -> light.red = red, light -> light.red) + .addField(new KeyedCodec<>("Green", ProtocolCodecs.RANGE), (light, green) -> light.green = green, light -> light.green) + .addField(new KeyedCodec<>("Blue", ProtocolCodecs.RANGE), (light, blue) -> light.blue = blue, light -> light.blue) + .build(); + protected Range red; + protected Range green; + protected Range blue; + + public ArtificialLight() { + } + + public Range getRed() { + return this.red; + } + + public Range getGreen() { + return this.green; + } + + public Range getBlue() { + return this.blue; + } + + @Nonnull + @Override + public String toString() { + return "ArtificialLightLevel{red=" + this.red + ", green=" + this.green + ", blue=" + this.blue + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/config/modifiers/WaterGrowthModifierAsset.java b/src/com/hypixel/hytale/builtin/adventure/farming/config/modifiers/WaterGrowthModifierAsset.java new file mode 100644 index 0000000..f2134d8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/config/modifiers/WaterGrowthModifierAsset.java @@ -0,0 +1,235 @@ +package com.hypixel.hytale.builtin.adventure.farming.config.modifiers; + +import com.hypixel.hytale.builtin.adventure.farming.states.TilledSoilBlock; +import com.hypixel.hytale.builtin.weather.resources.WeatherResource; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.GrowthModifierAsset; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import java.time.Instant; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WaterGrowthModifierAsset extends GrowthModifierAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + WaterGrowthModifierAsset.class, WaterGrowthModifierAsset::new, ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Fluids", new ArrayCodec<>(Codec.STRING, String[]::new)), (asset, blocks) -> asset.fluids = blocks, asset -> asset.fluids) + .addValidator(Fluid.VALIDATOR_CACHE.getArrayValidator().late()) + .add() + .append(new KeyedCodec<>("Weathers", Codec.STRING_ARRAY), (asset, weathers) -> asset.weathers = weathers, asset -> asset.weathers) + .addValidator(Weather.VALIDATOR_CACHE.getArrayValidator()) + .add() + .addField(new KeyedCodec<>("RainDuration", Codec.INTEGER), (asset, duration) -> asset.rainDuration = duration, asset -> asset.rainDuration) + .afterDecode(asset -> { + if (asset.fluids != null) { + asset.fluidIds = new IntOpenHashSet(); + + for (int i = 0; i < asset.fluids.length; i++) { + asset.fluidIds.add(Fluid.getAssetMap().getIndex(asset.fluids[i])); + } + } + + if (asset.weathers != null) { + asset.weatherIds = new IntOpenHashSet(); + + for (int i = 0; i < asset.weathers.length; i++) { + asset.weatherIds.add(Weather.getAssetMap().getIndex(asset.weathers[i])); + } + } + }) + .build(); + protected String[] fluids; + protected IntOpenHashSet fluidIds; + protected String[] weathers; + protected IntOpenHashSet weatherIds; + protected int rainDuration; + + public WaterGrowthModifierAsset() { + } + + public String[] getFluids() { + return this.fluids; + } + + public IntOpenHashSet getFluidIds() { + return this.fluidIds; + } + + public String[] getWeathers() { + return this.weathers; + } + + public IntOpenHashSet getWeatherIds() { + return this.weatherIds; + } + + public int getRainDuration() { + return this.rainDuration; + } + + @Override + public double getCurrentGrowthMultiplier( + CommandBuffer commandBuffer, Ref sectionRef, Ref blockRef, int x, int y, int z, boolean initialTick + ) { + boolean hasWaterBlock = this.checkIfWaterSource(commandBuffer, sectionRef, blockRef, x, y, z); + boolean isRaining = this.checkIfRaining(commandBuffer, sectionRef, x, y, z); + boolean active = hasWaterBlock || isRaining; + TilledSoilBlock soil = getSoil(commandBuffer, sectionRef, x, y, z); + if (soil != null) { + if (soil.hasExternalWater() != active) { + soil.setExternalWater(active); + commandBuffer.getComponent(sectionRef, BlockSection.getComponentType()).setTicking(x, y, z, true); + } + + active |= this.isSoilWaterExpiring( + commandBuffer.getExternalData().getWorld().getEntityStore().getStore().getResource(WorldTimeResource.getResourceType()), soil + ); + } + + return !active ? 1.0 : super.getCurrentGrowthMultiplier(commandBuffer, sectionRef, blockRef, x, y, z, initialTick); + } + + @Nullable + private static TilledSoilBlock getSoil(CommandBuffer commandBuffer, Ref sectionRef, int x, int y, int z) { + ChunkSection chunkSection = commandBuffer.getComponent(sectionRef, ChunkSection.getComponentType()); + Ref chunk = chunkSection.getChunkColumnReference(); + BlockComponentChunk blockComponentChunk = commandBuffer.getComponent(chunk, BlockComponentChunk.getComponentType()); + Ref blockRefBelow = blockComponentChunk.getEntityReference(ChunkUtil.indexBlockInColumn(x, y - 1, z)); + return blockRefBelow == null ? null : commandBuffer.getComponent(blockRefBelow, TilledSoilBlock.getComponentType()); + } + + protected boolean checkIfWaterSource(CommandBuffer commandBuffer, Ref sectionRef, Ref blockRef, int x, int y, int z) { + IntOpenHashSet waterBlocks = this.fluidIds; + if (waterBlocks == null) { + return false; + } else { + TilledSoilBlock soil = getSoil(commandBuffer, sectionRef, x, y, z); + if (soil == null) { + return false; + } else { + int[] fluids = this.getNeighbourFluids(commandBuffer, sectionRef, x, y - 1, z); + + for (int block : fluids) { + if (waterBlocks.contains(block)) { + return true; + } + } + + return false; + } + } + } + + private int[] getNeighbourFluids(CommandBuffer commandBuffer, Ref sectionRef, int x, int y, int z) { + ChunkSection section = commandBuffer.getComponent(sectionRef, ChunkSection.getComponentType()); + return new int[]{ + this.getFluidAtPos(x - 1, y, z, sectionRef, section, commandBuffer), + this.getFluidAtPos(x + 1, y, z, sectionRef, section, commandBuffer), + this.getFluidAtPos(x, y, z - 1, sectionRef, section, commandBuffer), + this.getFluidAtPos(x, y, z + 1, sectionRef, section, commandBuffer) + }; + } + + private int getFluidAtPos( + int posX, int posY, int posZ, Ref sectionRef, ChunkSection currentChunkSection, CommandBuffer commandBuffer + ) { + Ref chunkToUse = sectionRef; + int chunkX = ChunkUtil.worldCoordFromLocalCoord(currentChunkSection.getX(), posX); + int chunkY = ChunkUtil.worldCoordFromLocalCoord(currentChunkSection.getY(), posY); + int chunkZ = ChunkUtil.worldCoordFromLocalCoord(currentChunkSection.getZ(), posZ); + if (ChunkUtil.isSameChunkSection(chunkX, chunkY, chunkZ, currentChunkSection.getX(), currentChunkSection.getY(), currentChunkSection.getZ())) { + chunkToUse = commandBuffer.getExternalData().getChunkSectionReference(chunkX, chunkY, chunkZ); + } + + return chunkToUse == null ? Integer.MIN_VALUE : commandBuffer.getComponent(chunkToUse, FluidSection.getComponentType()).getFluidId(posX, posY, posZ); + } + + protected boolean checkIfRaining(CommandBuffer commandBuffer, Ref sectionRef, int x, int y, int z) { + if (this.weatherIds == null) { + return false; + } else { + ChunkSection section = commandBuffer.getComponent(sectionRef, ChunkSection.getComponentType()); + Ref chunk = section.getChunkColumnReference(); + BlockChunk blockChunk = commandBuffer.getComponent(chunk, BlockChunk.getComponentType()); + int cropId = blockChunk.getBlock(x, y, z); + Store store = commandBuffer.getExternalData().getWorld().getEntityStore().getStore(); + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + WeatherResource weatherResource = store.getResource(WeatherResource.getResourceType()); + int environment = blockChunk.getEnvironment(x, y, z); + int weatherId; + if (weatherResource.getForcedWeatherIndex() != 0) { + weatherId = weatherResource.getForcedWeatherIndex(); + } else { + weatherId = weatherResource.getWeatherIndexForEnvironment(environment); + } + + if (this.weatherIds.contains(weatherId)) { + boolean unobstructed = true; + + for (int searchY = y + 1; searchY < 320; searchY++) { + int block = blockChunk.getBlock(x, searchY, z); + if (block != 0 && block != cropId) { + unobstructed = false; + break; + } + } + + if (unobstructed) { + return true; + } + } + + return false; + } + } + + private boolean isSoilWaterExpiring(WorldTimeResource worldTimeResource, TilledSoilBlock soilBlock) { + Instant until = soilBlock.getWateredUntil(); + if (until == null) { + return false; + } else { + Instant now = worldTimeResource.getGameTime(); + if (now.isAfter(until)) { + soilBlock.setWateredUntil(null); + return false; + } else { + return true; + } + } + } + + @Nonnull + @Override + public String toString() { + return "WaterGrowthModifierAsset{blocks=" + + Arrays.toString((Object[])this.fluids) + + ", blockIds=" + + this.fluidIds + + ", weathers=" + + Arrays.toString((Object[])this.weathers) + + ", weatherIds=" + + this.weatherIds + + ", rainDuration=" + + this.rainDuration + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/BlockStateFarmingStageData.java b/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/BlockStateFarmingStageData.java new file mode 100644 index 0000000..fb84145 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/BlockStateFarmingStageData.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.builtin.adventure.farming.config.stages; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.FarmingStageData; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockStateFarmingStageData extends FarmingStageData { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder( + BlockStateFarmingStageData.class, BlockStateFarmingStageData::new, FarmingStageData.BASE_CODEC + ) + .append(new KeyedCodec<>("State", Codec.STRING), (stage, block) -> stage.state = block, stage -> stage.state) + .add() + .build(); + protected String state; + + public BlockStateFarmingStageData() { + } + + public String getState() { + return this.state; + } + + @Override + public void apply( + ComponentAccessor commandBuffer, + Ref sectionRef, + Ref blockRef, + int x, + int y, + int z, + @Nullable FarmingStageData previousStage + ) { + super.apply(commandBuffer, sectionRef, blockRef, x, y, z, previousStage); + ChunkSection section = commandBuffer.getComponent(sectionRef, ChunkSection.getComponentType()); + WorldChunk worldChunk = commandBuffer.getComponent(section.getChunkColumnReference(), WorldChunk.getComponentType()); + int origBlockId = worldChunk.getBlock(x, y, z); + BlockType origBlockType = BlockType.getAssetMap().getAsset(origBlockId); + BlockType blockType = origBlockType.getBlockForState(this.state); + if (blockType != null) { + int newType = BlockType.getAssetMap().getIndex(blockType.getId()); + if (origBlockId != newType) { + int rotation = worldChunk.getRotationIndex(x, y, z); + commandBuffer.getExternalData().getWorld().execute(() -> worldChunk.setBlock(x, y, z, newType, blockType, rotation, 0, 2)); + } + } + } + + @Nonnull + @Override + public String toString() { + return "BlockStateFarmingStageData{state='" + this.state + "'} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/BlockTypeFarmingStageData.java b/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/BlockTypeFarmingStageData.java new file mode 100644 index 0000000..dcc0aa3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/BlockTypeFarmingStageData.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.adventure.farming.config.stages; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.FarmingStageData; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockTypeFarmingStageData extends FarmingStageData { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder( + BlockTypeFarmingStageData.class, BlockTypeFarmingStageData::new, FarmingStageData.BASE_CODEC + ) + .append(new KeyedCodec<>("Block", Codec.STRING), (stage, block) -> stage.block = block, stage -> stage.block) + .addValidatorLate(() -> BlockType.VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + protected String block; + + public BlockTypeFarmingStageData() { + } + + public String getBlock() { + return this.block; + } + + @Override + public void apply( + ComponentAccessor commandBuffer, + Ref sectionRef, + Ref blockRef, + int x, + int y, + int z, + @Nullable FarmingStageData previousStage + ) { + super.apply(commandBuffer, sectionRef, blockRef, x, y, z, previousStage); + ChunkSection section = commandBuffer.getComponent(sectionRef, ChunkSection.getComponentType()); + WorldChunk worldChunk = commandBuffer.getComponent(section.getChunkColumnReference(), WorldChunk.getComponentType()); + int blockId = BlockType.getAssetMap().getIndex(this.block); + if (blockId != worldChunk.getBlock(x, y, z)) { + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + commandBuffer.getExternalData().getWorld().execute(() -> worldChunk.setBlock(x, y, z, blockId, blockType, worldChunk.getRotationIndex(x, y, z), 0, 2)); + } + } + + @Nonnull + @Override + public String toString() { + return "BlockTypeFarmingStageData{block=" + this.block + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/PrefabFarmingStageData.java b/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/PrefabFarmingStageData.java new file mode 100644 index 0000000..ca12622 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/PrefabFarmingStageData.java @@ -0,0 +1,379 @@ +package com.hypixel.hytale.builtin.adventure.farming.config.stages; + +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.builtin.adventure.farming.states.FarmingBlock; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.map.IWeightedElement; +import com.hypixel.hytale.common.map.IWeightedMap; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.FastRandom; +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.FarmingStageData; +import com.hypixel.hytale.server.core.codec.WeightedMapCodec; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import com.hypixel.hytale.server.core.prefab.PrefabStore; +import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabBufferCall; +import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabBufferUtil; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.IPrefabBuffer; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.accessor.LocalCachedChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.util.PrefabUtil; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabFarmingStageData extends FarmingStageData { + public static final float MIN_VOLUME_PREFAB = 125.0F; + public static final float MAX_VOLUME_PREFAB = 1000.0F; + public static final float MIN_BROKEN_PARTICLE_RATE = 0.25F; + public static final float MAX_BROKEN_PARTICLE_RATE = 0.75F; + private static final String[] EMPTY_REPLACE_MASK = new String[0]; + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder( + PrefabFarmingStageData.class, PrefabFarmingStageData::new, FarmingStageData.BASE_CODEC + ) + .append( + new KeyedCodec<>("Prefabs", new WeightedMapCodec<>(PrefabFarmingStageData.PrefabStage.CODEC, PrefabFarmingStageData.PrefabStage.EMPTY_ARRAY)), + (stage, prefabStages) -> stage.prefabStages = prefabStages, + stage -> stage.prefabStages + ) + .add() + .append( + new KeyedCodec<>("ReplaceMaskTags", new ArrayCodec<>(Codec.STRING, String[]::new)), + (stage, replaceMask) -> stage.replaceMaskTags = replaceMask, + stage -> stage.replaceMaskTags + ) + .add() + .afterDecode(PrefabFarmingStageData::processConfig) + .build(); + protected IWeightedMap prefabStages; + private String[] replaceMaskTags = EMPTY_REPLACE_MASK; + private int[] replaceMaskTagIndices; + + public PrefabFarmingStageData() { + } + + private static double computeParticlesRate(@Nonnull IPrefabBuffer prefab) { + double xLength = prefab.getMaxX() - prefab.getMinX(); + double yLength = prefab.getMaxY() - prefab.getMinY(); + double zLength = prefab.getMaxZ() - prefab.getMinZ(); + double volume = xLength * yLength * zLength; + double ratio = -5.7142857E-4F; + double rate = (volume - 125.0) * ratio; + return MathUtil.clamp(rate + 0.75, 0.25, 0.75); + } + + private static boolean isPrefabBlockIntact( + LocalCachedChunkAccessor chunkAccessor, + int worldX, + int worldY, + int worldZ, + int blockX, + int blockY, + int blockZ, + int blockId, + int rotation, + PrefabRotation prefabRotation + ) { + int globalX = prefabRotation.getX(blockX, blockZ) + worldX; + int globalY = blockY + worldY; + int globalZ = prefabRotation.getZ(blockX, blockZ) + worldZ; + BlockType block = BlockType.getAssetMap().getAsset(blockId); + if (block.getMaterial() == BlockMaterial.Empty) { + return true; + } else { + WorldChunk chunk = chunkAccessor.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(globalX, globalZ)); + if (chunk == null) { + return false; + } else { + int worldBlockId = chunk.getBlock(globalX, globalY, globalZ); + if (worldBlockId != blockId) { + return false; + } else { + int expectedRotation = prefabRotation.getRotation(rotation); + int worldRotation = chunk.getRotationIndex(globalX, globalY, globalZ); + return worldRotation == expectedRotation; + } + } + } + } + + private static boolean isPrefabIntact( + IPrefabBuffer prefabBuffer, LocalCachedChunkAccessor chunkAccessor, int worldX, int worldY, int worldZ, PrefabRotation prefabRotation, FastRandom random + ) { + return prefabBuffer.forEachRaw( + IPrefabBuffer.iterateAllColumns(), + (blockX, blockY, blockZ, blockId, chance, holder, supportValue, rotation, filler, t) -> isPrefabBlockIntact( + chunkAccessor, worldX, worldY, worldZ, blockX, blockY, blockZ, blockId, rotation, prefabRotation + ), + (fluidX, fluidY, fluidZ, fluidId, level, o) -> true, + null, + new PrefabBufferCall(random, prefabRotation) + ); + } + + public IWeightedMap getPrefabStages() { + return this.prefabStages; + } + + @Override + public void apply( + ComponentAccessor commandBuffer, + Ref sectionRef, + Ref blockRef, + int x, + int y, + int z, + @Nullable FarmingStageData previousStage + ) { + FarmingBlock farming = commandBuffer.getComponent(blockRef, FarmingBlock.getComponentType()); + IPrefabBuffer prefabBuffer = this.getCachedPrefab(x, y, z, farming.getGeneration()); + BlockSection blockSection = commandBuffer.getComponent(sectionRef, BlockSection.getComponentType()); + int randomRotation = HashUtil.randomInt(x, y, z, Rotation.VALUES.length); + RotationTuple yaw = RotationTuple.of(Rotation.VALUES[randomRotation], Rotation.None); + World world = commandBuffer.getExternalData().getWorld(); + ChunkSection section = commandBuffer.getComponent(sectionRef, ChunkSection.getComponentType()); + int worldX = ChunkUtil.worldCoordFromLocalCoord(section.getX(), x); + int worldY = ChunkUtil.worldCoordFromLocalCoord(section.getY(), y); + int worldZ = ChunkUtil.worldCoordFromLocalCoord(section.getZ(), z); + if (farming.getPreviousBlockType() == null) { + farming.setPreviousBlockType(BlockType.getAssetMap().getAsset(blockSection.get(x, y, z)).getId()); + } + + double xLength = prefabBuffer.getMaxX() - prefabBuffer.getMinX(); + double zLength = prefabBuffer.getMaxZ() - prefabBuffer.getMinZ(); + int prefabRadius = (int)MathUtil.fastFloor(0.5 * Math.sqrt(xLength * xLength + zLength * zLength)); + LocalCachedChunkAccessor chunkAccessor = LocalCachedChunkAccessor.atWorldCoords(world, x, z, prefabRadius); + FastRandom random = new FastRandom(); + PrefabRotation prefabRotation = PrefabRotation.fromRotation(yaw.yaw()); + BlockTypeAssetMap blockTypeMap = BlockType.getAssetMap(); + if (previousStage instanceof PrefabFarmingStageData oldPrefab) { + IPrefabBuffer oldPrefabBuffer = oldPrefab.getCachedPrefab(worldX, worldY, worldZ, farming.getGeneration() - 1); + double brokenParticlesRate = computeParticlesRate(prefabBuffer); + world.execute( + () -> { + boolean isIntact = isPrefabIntact(oldPrefabBuffer, chunkAccessor, worldX, worldY, worldZ, prefabRotation, random); + if (isIntact) { + boolean isUnobstructed = prefabBuffer.compare( + (px, py, pz, blockId, stateWrapper, chance, rotation, filler, secondBlockId, secondStateWrapper, secondChance, secondRotation, secondFiller, prefabBufferCall) -> { + int bx = worldX + px; + int by = worldY + py; + int bz = worldZ + pz; + if ((secondBlockId == 0 || secondBlockId == Integer.MIN_VALUE) && blockId != 0 && blockId != Integer.MIN_VALUE) { + WorldChunk nonTickingChunk = chunkAccessor.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(bx, bz)); + int worldBlock = nonTickingChunk.getBlock(bx, by, bz); + return !this.doesBlockObstruct(blockId, worldBlock); + } else { + return true; + } + }, + new PrefabBufferCall(random, prefabRotation), + oldPrefabBuffer + ); + if (isUnobstructed) { + prefabBuffer.compare( + (px, py, pz, blockId, stateWrapper, chance, rotation, filler, secondBlockId, secondStateWrapper, secondChance, secondRotation, secondFiller, prefabBufferCall) -> { + int bx = worldX + px; + int by = worldY + py; + int bz = worldZ + pz; + WorldChunk nonTickingChunk = chunkAccessor.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(bx, bz)); + int updatedSetBlockSettings = 2; + if (random.nextDouble() > brokenParticlesRate) { + updatedSetBlockSettings |= 4; + } + + if (blockId != 0 && blockId != Integer.MIN_VALUE) { + BlockType block = blockTypeMap.getAsset(blockId); + if (filler != 0) { + return true; + } + + int worldBlock = nonTickingChunk.getBlock(bx, by, bz); + if ((secondBlockId == 0 || secondBlockId == Integer.MIN_VALUE) && !this.canReplace(worldBlock, blockTypeMap)) { + return true; + } + + nonTickingChunk.setBlock(bx, by, bz, blockId, block, rotation, filler, updatedSetBlockSettings); + if (stateWrapper != null) { + nonTickingChunk.setState(bx, by, bz, stateWrapper.clone()); + } + } else if (secondBlockId != 0 && secondBlockId != Integer.MIN_VALUE) { + nonTickingChunk.breakBlock(bx, by, bz, updatedSetBlockSettings); + } + + return true; + }, + new PrefabBufferCall(random, prefabRotation), + oldPrefabBuffer + ); + } + } + } + ); + } else { + super.apply(commandBuffer, sectionRef, blockRef, x, y, z, previousStage); + world.execute( + () -> { + boolean isUnObstructed = prefabBuffer.forEachRaw( + IPrefabBuffer.iterateAllColumns(), (blockX, blockY, blockZ, blockId, chance, holder, supportValue, rotation, filler, t) -> { + int bx = worldX + prefabRotation.getX(blockX, blockZ); + int by = worldY + blockY; + int bz = worldZ + prefabRotation.getX(blockZ, blockX); + if (blockId != 0 && blockId != Integer.MIN_VALUE) { + int worldBlock = chunkAccessor.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(bx, bz)).getBlock(bx, by, bz); + return !this.doesBlockObstruct(blockId, worldBlock); + } else { + return true; + } + }, (fluidX, fluidY, fluidZ, fluidId, level, o) -> true, null, new PrefabBufferCall(random, prefabRotation) + ); + if (isUnObstructed) { + prefabBuffer.forEach( + IPrefabBuffer.iterateAllColumns(), (blockX, blockY, blockZ, blockId, holder, supportValue, rotation, filler, t, fluidId, fluidLevel) -> { + int bx = worldX + blockX; + int by = worldY + blockY; + int bz = worldZ + blockZ; + WorldChunk nonTickingChunk = chunkAccessor.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(bx, bz)); + int updatedSetBlockSettings = 2; + if (blockId != 0 && blockId != Integer.MIN_VALUE) { + BlockType block = blockTypeMap.getAsset(blockId); + if (filler != 0) { + return; + } + + int worldBlock = nonTickingChunk.getBlock(bx, by, bz); + if (!this.canReplace(worldBlock, blockTypeMap)) { + return; + } + + nonTickingChunk.setBlock(bx, by, bz, blockId, block, rotation, filler, updatedSetBlockSettings); + if (holder != null) { + nonTickingChunk.setState(bx, by, bz, holder.clone()); + } + } + }, null, null, new PrefabBufferCall(random, prefabRotation) + ); + } + } + ); + } + } + + private boolean doesBlockObstruct(int blockId, int worldBlockId) { + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + BlockType blockType = assetMap.getAsset(blockId); + return blockType != null && blockType.getMaterial() != BlockMaterial.Empty ? !this.canReplace(worldBlockId, assetMap) : false; + } + + private boolean canReplace(int worldBlockId, BlockTypeAssetMap assetMap) { + BlockType worldBlockType = assetMap.getAsset(worldBlockId); + if (worldBlockType != null && worldBlockType.getMaterial() != BlockMaterial.Empty) { + for (int tagIndex : this.replaceMaskTagIndices) { + if (assetMap.getIndexesForTag(tagIndex).contains(worldBlockId)) { + return true; + } + } + + return false; + } else { + return true; + } + } + + @Override + public void remove(ComponentAccessor commandBuffer, Ref sectionRef, Ref blockRef, int x, int y, int z) { + super.remove(commandBuffer, sectionRef, blockRef, x, y, z); + ChunkSection section = commandBuffer.getComponent(sectionRef, ChunkSection.getComponentType()); + int worldX = ChunkUtil.worldCoordFromLocalCoord(section.getX(), x); + int worldY = ChunkUtil.worldCoordFromLocalCoord(section.getY(), y); + int worldZ = ChunkUtil.worldCoordFromLocalCoord(section.getZ(), z); + FarmingBlock farming = commandBuffer.getComponent(blockRef, FarmingBlock.getComponentType()); + IPrefabBuffer prefab = this.getCachedPrefab(worldX, worldY, worldZ, farming.getGeneration() - 1); + RotationTuple rotation = commandBuffer.getComponent(sectionRef, BlockSection.getComponentType()).getRotation(x, y, z); + double rate = computeParticlesRate(prefab); + World world = commandBuffer.getExternalData().getWorld(); + world.execute(() -> PrefabUtil.remove(prefab, world, new Vector3i(worldX, worldY, worldZ), rotation.yaw(), true, new FastRandom(), 2, rate)); + } + + @Nonnull + private IPrefabBuffer getCachedPrefab(int x, int y, int z, int generation) { + return PrefabBufferUtil.getCached(this.prefabStages.get(HashUtil.random(x, y, z, generation)).getResolvedPath()); + } + + private void processConfig() { + this.replaceMaskTagIndices = new int[this.replaceMaskTags.length]; + + for (int i = 0; i < this.replaceMaskTags.length; i++) { + this.replaceMaskTagIndices[i] = AssetRegistry.getOrCreateTagIndex(this.replaceMaskTags[i]); + } + } + + @Override + public String toString() { + return "PrefabFarmingStageData{replaceMaskTags=" + Arrays.toString((Object[])this.replaceMaskTags) + ", prefabStages=" + this.prefabStages + "}"; + } + + public static class PrefabStage implements IWeightedElement { + public static final PrefabFarmingStageData.PrefabStage[] EMPTY_ARRAY = new PrefabFarmingStageData.PrefabStage[0]; + @Nonnull + public static Codec CODEC = BuilderCodec.builder( + PrefabFarmingStageData.PrefabStage.class, PrefabFarmingStageData.PrefabStage::new + ) + .append(new KeyedCodec<>("Weight", Codec.INTEGER), (prefabStage, integer) -> prefabStage.weight = integer, prefabStage -> prefabStage.weight) + .addValidator(Validators.greaterThanOrEqual(1)) + .add() + .append(new KeyedCodec<>("Path", Codec.STRING), (prefabStage, s) -> prefabStage.path = s, prefabStage -> prefabStage.path) + .addValidator(Validators.nonNull()) + .add() + .build(); + protected int weight = 1; + protected String path; + + public PrefabStage() { + } + + @Override + public double getWeight() { + return this.weight; + } + + @Nonnull + public Path getResolvedPath() { + for (AssetPack pack : AssetModule.get().getAssetPacks()) { + Path assetPath = pack.getRoot().resolve("Server").resolve("Prefabs").resolve(this.path); + if (Files.exists(assetPath)) { + return assetPath; + } + } + + return PrefabStore.get().getAssetPrefabsPath().resolve(this.path); + } + + @Nonnull + @Override + public String toString() { + return "PrefabStage{weight=" + this.weight + ", path='" + this.path + "'}"; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/spread/DirectionalGrowthBehaviour.java b/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/spread/DirectionalGrowthBehaviour.java new file mode 100644 index 0000000..06623d2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/spread/DirectionalGrowthBehaviour.java @@ -0,0 +1,273 @@ +package com.hypixel.hytale.builtin.adventure.farming.config.stages.spread; + +import com.hypixel.hytale.builtin.adventure.farming.states.FarmingBlock; +import com.hypixel.hytale.builtin.blockphysics.BlockPhysicsSystems; +import com.hypixel.hytale.builtin.blockphysics.BlockPhysicsUtil; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.map.IWeightedElement; +import com.hypixel.hytale.common.map.IWeightedMap; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.range.IntRange; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.FastRandom; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; +import com.hypixel.hytale.server.core.codec.WeightedMapCodec; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.accessor.LocalCachedChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; + +public class DirectionalGrowthBehaviour extends SpreadGrowthBehaviour { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DirectionalGrowthBehaviour.class, DirectionalGrowthBehaviour::new, BASE_CODEC + ) + .append( + new KeyedCodec<>( + "GrowthBlockTypes", new WeightedMapCodec<>(DirectionalGrowthBehaviour.BlockTypeWeight.CODEC, new DirectionalGrowthBehaviour.BlockTypeWeight[0]) + ), + (directionalGrowthBehaviour, blockTypeWeightIWeightedMap) -> directionalGrowthBehaviour.blockTypes = blockTypeWeightIWeightedMap, + directionalGrowthBehaviour -> directionalGrowthBehaviour.blockTypes + ) + .documentation("Defines a map of the possible BlockType to spread.") + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("Horizontal", IntRange.CODEC), + (directionalGrowthBehaviour, intRange) -> directionalGrowthBehaviour.horizontalRange = intRange, + directionalGrowthBehaviour -> directionalGrowthBehaviour.horizontalRange + ) + .documentation("Defines if the spread can happen horizontally. The range must be set with positive integers.") + .add() + .append( + new KeyedCodec<>("Vertical", IntRange.CODEC), + (directionalGrowthBehaviour, intRange) -> directionalGrowthBehaviour.verticalRange = intRange, + directionalGrowthBehaviour -> directionalGrowthBehaviour.verticalRange + ) + .documentation("Defines if the spread can happen vertically. The range must be set with positive integers.") + .add() + .append( + new KeyedCodec<>("VerticalDirection", new EnumCodec<>(DirectionalGrowthBehaviour.VerticalDirection.class)), + (directionalGrowthBehaviour, verticalDirection) -> directionalGrowthBehaviour.verticalDirection = verticalDirection, + directionalGrowthBehaviour -> directionalGrowthBehaviour.verticalDirection + ) + .documentation("Defines in which direction the vertical spread should happen. Possible values are: 'Upwards' and 'Downwards', default value: 'Upwards'.") + .addValidator(Validators.nonNull()) + .add() + .build(); + private static final int PLACE_BLOCK_TRIES = 100; + protected IWeightedMap blockTypes; + protected IntRange horizontalRange; + protected IntRange verticalRange; + protected DirectionalGrowthBehaviour.VerticalDirection verticalDirection = DirectionalGrowthBehaviour.VerticalDirection.BOTH; + + public DirectionalGrowthBehaviour() { + } + + public IWeightedMap getBlockTypes() { + return this.blockTypes; + } + + public IntRange getHorizontalRange() { + return this.horizontalRange; + } + + public IntRange getVerticalRange() { + return this.verticalRange; + } + + public DirectionalGrowthBehaviour.VerticalDirection getVerticalDirection() { + return this.verticalDirection; + } + + @Override + public void execute( + ComponentAccessor commandBuffer, + Ref sectionRef, + Ref blockRef, + int worldX, + int worldY, + int worldZ, + float newSpreadRate + ) { + int x = 0; + int z = 0; + FastRandom random = new FastRandom(); + String blockTypeKey = this.blockTypes.get(random).getBlockTypeKey(); + World world = commandBuffer.getExternalData().getWorld(); + LocalCachedChunkAccessor chunkAccessor = LocalCachedChunkAccessor.atWorldCoords(world, worldX, worldZ, 1); + + for (int i = 0; i < 100; i++) { + if (this.horizontalRange != null) { + double angle = (float) (Math.PI * 2) * random.nextFloat(); + int radius = this.horizontalRange.getInt(random.nextFloat()); + x = MathUtil.fastRound(radius * TrigMathUtil.cos(angle)); + z = MathUtil.fastRound(radius * TrigMathUtil.sin(angle)); + } + + int targetX = worldX + x; + int targetZ = worldZ + z; + int chunkX = ChunkUtil.chunkCoordinate(targetX); + int chunkZ = ChunkUtil.chunkCoordinate(targetZ); + WorldChunk chunk = chunkAccessor.getChunkIfInMemory(ChunkUtil.indexChunk(chunkX, chunkZ)); + if (chunk != null) { + int targetY; + if (this.verticalRange != null) { + int directionValue = switch (this.verticalDirection) { + case DOWNWARDS, UPWARDS -> this.verticalDirection.getValue(); + case BOTH -> random.nextBoolean() ? 1 : -1; + }; + targetY = worldY + this.verticalRange.getInt(random.nextFloat()) * directionValue; + } else { + targetY = chunk.getHeight(targetX, targetZ) + 1; + } + + if (this.tryPlaceBlock(world, chunk, targetX, targetY, targetZ, blockTypeKey, 0)) { + int finalTargetY = targetY; + world.execute(() -> { + WorldChunk loadedChunk = chunkAccessor.getChunk(ChunkUtil.indexChunk(chunkX, chunkZ)); + if (loadedChunk != null) { + loadedChunk.placeBlock(targetX, finalTargetY, targetZ, blockTypeKey, Rotation.None, Rotation.None, Rotation.None); + decaySpread(commandBuffer, loadedChunk.getBlockComponentChunk(), targetX, finalTargetY, targetZ, newSpreadRate); + } + }); + return; + } + } + } + } + + private static void decaySpread( + ComponentAccessor commandBuffer, BlockComponentChunk blockComponentChunk, int worldX, int worldY, int worldZ, float newSpreadRate + ) { + Ref blockRefPlaced = blockComponentChunk.getEntityReference(ChunkUtil.indexBlockInColumn(worldX, worldY, worldZ)); + if (blockRefPlaced != null) { + FarmingBlock farmingPlaced = commandBuffer.getComponent(blockRefPlaced, FarmingBlock.getComponentType()); + if (farmingPlaced != null) { + farmingPlaced.setSpreadRate(newSpreadRate); + } + } + } + + private boolean tryPlaceBlock(@Nonnull World world, @Nonnull WorldChunk chunk, int worldX, int worldY, int worldZ, String blockTypeKey, int rotation) { + if (chunk.getBlock(worldX, worldY, worldZ) != 0) { + return false; + } else if (!this.validatePosition(world, worldX, worldY, worldZ)) { + return false; + } else { + BlockType blockType = BlockType.getAssetMap().getAsset(blockTypeKey); + if (blockType == null) { + return false; + } else if (!chunk.testPlaceBlock(worldX, worldY, worldZ, blockType, rotation)) { + return false; + } else { + int cx = chunk.getX(); + int cz = chunk.getZ(); + int cy = ChunkUtil.indexSection(worldY); + Ref sectionRef = world.getChunkStore().getChunkSectionReference(cx, cy, cz); + if (sectionRef == null) { + return false; + } else { + Store store = world.getChunkStore().getStore(); + BlockPhysics blockPhysics = store.getComponent(sectionRef, BlockPhysics.getComponentType()); + FluidSection fluidSection = store.getComponent(sectionRef, FluidSection.getComponentType()); + BlockSection blockSection = store.getComponent(sectionRef, BlockSection.getComponentType()); + int filler = blockSection.getFiller(worldX, worldY, worldZ); + BlockPhysicsSystems.CachedAccessor cachedAccessor = BlockPhysicsSystems.CachedAccessor.of( + store, blockSection, blockPhysics, fluidSection, cx, cy, cz, 14 + ); + return BlockPhysicsUtil.testBlockPhysics( + cachedAccessor, blockSection, blockPhysics, fluidSection, worldX, worldY, worldZ, blockType, rotation, filler + ) + != 0; + } + } + } + } + + @Nonnull + @Override + public String toString() { + return "DirectionalGrowthBehaviour{blockTypes=" + + this.blockTypes + + ", horizontalRange=" + + this.horizontalRange + + ", verticalRange=" + + this.verticalRange + + ", verticalDirection=" + + this.verticalDirection + + "} " + + super.toString(); + } + + public static class BlockTypeWeight implements IWeightedElement { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder( + DirectionalGrowthBehaviour.BlockTypeWeight.class, DirectionalGrowthBehaviour.BlockTypeWeight::new + ) + .append( + new KeyedCodec<>("Weight", Codec.DOUBLE), (blockTypeWeight, integer) -> blockTypeWeight.weight = integer, blockTypeWeight -> blockTypeWeight.weight + ) + .documentation("Defines the probability to have this entry.") + .addValidator(Validators.greaterThan(0.0)) + .add() + .append( + new KeyedCodec<>("BlockType", Codec.STRING), + (blockTypeWeight, blockTypeKey) -> blockTypeWeight.blockTypeKey = blockTypeKey, + blockTypeWeight -> blockTypeWeight.blockTypeKey + ) + .documentation("Defines the BlockType that'll be spread") + .addValidator(Validators.nonNull()) + .add() + .build(); + protected double weight = 1.0; + protected String blockTypeKey; + + public BlockTypeWeight() { + } + + @Override + public double getWeight() { + return this.weight; + } + + public String getBlockTypeKey() { + return this.blockTypeKey; + } + + @Nonnull + @Override + public String toString() { + return "BlockTypeWeight{weight=" + this.weight + ", blockTypeKey=" + this.blockTypeKey + "}"; + } + } + + private static enum VerticalDirection { + DOWNWARDS(-1), + BOTH(0), + UPWARDS(1); + + private final int value; + + private VerticalDirection(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/spread/SpreadFarmingStageData.java b/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/spread/SpreadFarmingStageData.java new file mode 100644 index 0000000..0a1e153 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/spread/SpreadFarmingStageData.java @@ -0,0 +1,149 @@ +package com.hypixel.hytale.builtin.adventure.farming.config.stages.spread; + +import com.hypixel.hytale.builtin.adventure.farming.states.FarmingBlock; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.range.IntRange; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.FarmingStageData; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SpreadFarmingStageData extends FarmingStageData { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder( + SpreadFarmingStageData.class, SpreadFarmingStageData::new, FarmingStageData.BASE_CODEC + ) + .append( + new KeyedCodec<>("Executions", IntRange.CODEC), + (spreadFarmingStageData, intRange) -> spreadFarmingStageData.executions = intRange, + spreadFarmingStageData -> spreadFarmingStageData.executions + ) + .documentation("Defines the number of times the stage will be repeated. Range must be positive, min value must be >= 1.") + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("SpreadDecayPercent", IntRange.CODEC), + (spreadFarmingStageData, intRange) -> spreadFarmingStageData.spreadDecayPercent = intRange, + spreadFarmingStageData -> spreadFarmingStageData.spreadDecayPercent + ) + .documentation( + "The amount to reduce (linear decay) the spread rate (chance to spread) for any spawned blocks that also have a spread stage. Range must be positive." + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("GrowthBehaviours", new ArrayCodec<>(SpreadGrowthBehaviour.CODEC, SpreadGrowthBehaviour[]::new)), + (spreadFarmingStageData, spreadGrowthBehaviour) -> spreadFarmingStageData.spreadGrowthBehaviours = spreadGrowthBehaviour, + spreadFarmingStageData -> spreadFarmingStageData.spreadGrowthBehaviours + ) + .documentation("Defines an array of the different growth behaviours that'll be run for each execution.") + .addValidator(Validators.nonEmptyArray()) + .add() + .afterDecode( + stageData -> { + if (stageData.executions != null && stageData.executions.getInclusiveMin() < 1) { + throw new IllegalArgumentException( + "The min value for Executions range must be >= 1! Current min value is: " + stageData.executions.getInclusiveMin() + ); + } else if (stageData.spreadDecayPercent != null && stageData.spreadDecayPercent.getInclusiveMin() < 0) { + throw new IllegalArgumentException( + "The min value for SpreadDecayPercent range must be >= 0! Current min value is: " + stageData.spreadDecayPercent.getInclusiveMin() + ); + } + } + ) + .build(); + protected IntRange executions; + protected IntRange spreadDecayPercent; + protected SpreadGrowthBehaviour[] spreadGrowthBehaviours; + + public SpreadFarmingStageData() { + } + + public IntRange getExecutions() { + return this.executions; + } + + public IntRange getSpreadDecayPercent() { + return this.spreadDecayPercent; + } + + public SpreadGrowthBehaviour[] getSpreadGrowthBehaviours() { + return this.spreadGrowthBehaviours; + } + + @Override + public boolean implementsShouldStop() { + return true; + } + + @Override + public boolean shouldStop(ComponentAccessor commandBuffer, Ref sectionRef, Ref blockRef, int x, int y, int z) { + FarmingBlock farming = commandBuffer.getComponent(blockRef, FarmingBlock.getComponentType()); + float spreadRate = farming.getSpreadRate(); + ChunkSection section = commandBuffer.getComponent(sectionRef, ChunkSection.getComponentType()); + int worldX = ChunkUtil.worldCoordFromLocalCoord(section.getX(), x); + int worldY = ChunkUtil.worldCoordFromLocalCoord(section.getY(), y); + int worldZ = ChunkUtil.worldCoordFromLocalCoord(section.getZ(), z); + float executions = this.executions.getInt(HashUtil.random(worldX, worldY, worldZ, farming.getGeneration())) * spreadRate; + int executed = farming.getExecutions(); + return spreadRate <= 0.0F || executed >= executions; + } + + @Override + public void apply( + ComponentAccessor commandBuffer, + Ref sectionRef, + Ref blockRef, + int x, + int y, + int z, + @Nullable FarmingStageData previousStage + ) { + super.apply(commandBuffer, sectionRef, blockRef, x, y, z, previousStage); + FarmingBlock farming = commandBuffer.getComponent(blockRef, FarmingBlock.getComponentType()); + ChunkSection section = commandBuffer.getComponent(sectionRef, ChunkSection.getComponentType()); + int worldX = ChunkUtil.worldCoordFromLocalCoord(section.getX(), x); + int worldY = ChunkUtil.worldCoordFromLocalCoord(section.getY(), y); + int worldZ = ChunkUtil.worldCoordFromLocalCoord(section.getZ(), z); + float spreadRate = farming.getSpreadRate(); + double executions = Math.floor(this.executions.getInt(HashUtil.random(worldX, worldY, worldZ, farming.getGeneration())) * spreadRate); + int executed = farming.getExecutions(); + if (!(spreadRate <= 0.0F) && !(executed >= executions)) { + for (int i = 0; i < this.spreadGrowthBehaviours.length; i++) { + SpreadGrowthBehaviour spreadGrowthBehaviour = this.spreadGrowthBehaviours[i]; + float decayRate = this.spreadDecayPercent.getInt(HashUtil.random(i | (long)farming.getGeneration() << 32, worldX, worldY, worldZ)) / 100.0F; + spreadGrowthBehaviour.execute(commandBuffer, sectionRef, blockRef, worldX, worldY, worldZ, spreadRate - decayRate); + } + + farming.setExecutions(++executed); + } + } + + @Override + public void remove(ComponentAccessor commandBuffer, Ref sectionRef, Ref blockRef, int x, int y, int z) { + super.remove(commandBuffer, sectionRef, blockRef, x, y, z); + } + + @Nonnull + @Override + public String toString() { + return "SpreadFarmingStageData{executions=" + + this.executions + + ", spreadDecayPercent=" + + this.spreadDecayPercent + + ", spreadGrowthBehaviours=" + + Arrays.toString((Object[])this.spreadGrowthBehaviours) + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/spread/SpreadGrowthBehaviour.java b/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/spread/SpreadGrowthBehaviour.java new file mode 100644 index 0000000..6d917fc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/config/stages/spread/SpreadGrowthBehaviour.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.builtin.adventure.farming.config.stages.spread; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.worldlocationcondition.WorldLocationCondition; + +public abstract class SpreadGrowthBehaviour { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(SpreadGrowthBehaviour.class) + .append( + new KeyedCodec<>("LocationConditions", new ArrayCodec<>(WorldLocationCondition.CODEC, WorldLocationCondition[]::new)), + (spreadGrowthBehaviour, worldLocationConditions) -> spreadGrowthBehaviour.worldLocationConditions = worldLocationConditions, + spreadGrowthBehaviour -> spreadGrowthBehaviour.worldLocationConditions + ) + .documentation("Defines the possible location conditions a position has to fulfill to be considered as valid.") + .add() + .build(); + protected WorldLocationCondition[] worldLocationConditions; + + public SpreadGrowthBehaviour() { + } + + public abstract void execute(ComponentAccessor var1, Ref var2, Ref var3, int var4, int var5, int var6, float var7); + + protected boolean validatePosition(World world, int worldX, int worldY, int worldZ) { + if (this.worldLocationConditions == null) { + return true; + } else { + for (int i = 0; i < this.worldLocationConditions.length; i++) { + if (!this.worldLocationConditions[i].test(world, worldX, worldY, worldZ)) { + return false; + } + } + + return true; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/interactions/ChangeFarmingStageInteraction.java b/src/com/hypixel/hytale/builtin/adventure/farming/interactions/ChangeFarmingStageInteraction.java new file mode 100644 index 0000000..1d1152b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/interactions/ChangeFarmingStageInteraction.java @@ -0,0 +1,337 @@ +package com.hypixel.hytale.builtin.adventure.farming.interactions; + +import com.hypixel.hytale.builtin.adventure.farming.states.FarmingBlock; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.FarmingData; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.FarmingStageData; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChangeFarmingStageInteraction extends SimpleBlockInteraction { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final BuilderCodec CODEC = BuilderCodec.builder( + ChangeFarmingStageInteraction.class, ChangeFarmingStageInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Changes the farming stage of the target block.") + .appendInherited( + new KeyedCodec<>("Stage", Codec.INTEGER), + (interaction, stage) -> interaction.targetStage = stage, + interaction -> interaction.targetStage, + (o, p) -> o.targetStage = p.targetStage + ) + .documentation("The stage index to set (0, 1, 2, etc.). Use -1 for the final stage. Ignored if Increase is set.") + .add() + .appendInherited( + new KeyedCodec<>("Increase", Codec.INTEGER), + (interaction, increase) -> interaction.increaseBy = increase, + interaction -> interaction.increaseBy, + (o, p) -> o.increaseBy = p.increaseBy + ) + .documentation("Add this amount to the current stage (e.g., 1 = advance one stage, 2 = advance two stages). Takes priority over Decrease and Stage.") + .add() + .appendInherited( + new KeyedCodec<>("Decrease", Codec.INTEGER), + (interaction, decrease) -> interaction.decreaseBy = decrease, + interaction -> interaction.decreaseBy, + (o, p) -> o.decreaseBy = p.decreaseBy + ) + .documentation("Subtract this amount from the current stage (e.g., 1 = go back one stage). Takes priority over Stage.") + .add() + .appendInherited( + new KeyedCodec<>("StageSet", Codec.STRING), + (interaction, stageSet) -> interaction.targetStageSet = stageSet, + interaction -> interaction.targetStageSet, + (o, p) -> o.targetStageSet = p.targetStageSet + ) + .documentation("Optional. The stage set to switch to (e.g., 'Default', 'Harvested'). If not provided, uses current stage set.") + .add() + .build(); + protected int targetStage = -1; + @Nullable + protected Integer increaseBy = null; + @Nullable + protected Integer decreaseBy = null; + @Nullable + protected String targetStageSet = null; + + public ChangeFarmingStageInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + int x = targetBlock.getX(); + int y = targetBlock.getY(); + int z = targetBlock.getZ(); + LOGGER.atInfo() + .log( + "[ChangeFarmingStage] Starting interaction at pos=(%d, %d, %d), increaseBy=%s, decreaseBy=%s, targetStage=%d, targetStageSet=%s", + x, + y, + z, + this.increaseBy, + this.decreaseBy, + this.targetStage, + this.targetStageSet + ); + WorldChunk worldChunk = world.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + if (worldChunk == null) { + LOGGER.atWarning().log("[ChangeFarmingStage] FAILED: worldChunk is null at pos=(%d, %d, %d)", x, y, z); + context.getState().state = InteractionState.Failed; + } else { + BlockType blockType = worldChunk.getBlockType(targetBlock); + if (blockType == null) { + LOGGER.atWarning().log("[ChangeFarmingStage] FAILED: blockType is null at pos=(%d, %d, %d)", x, y, z); + context.getState().state = InteractionState.Failed; + } else { + LOGGER.atInfo().log("[ChangeFarmingStage] Block type: %s (id=%s)", blockType.getId(), blockType.getClass().getSimpleName()); + FarmingData farmingConfig = blockType.getFarming(); + if (farmingConfig != null && farmingConfig.getStages() != null) { + LOGGER.atInfo() + .log( + "[ChangeFarmingStage] Farming config found. StartingStageSet=%s, StageSetAfterHarvest=%s, AvailableStageSets=%s", + farmingConfig.getStartingStageSet(), + farmingConfig.getStageSetAfterHarvest(), + farmingConfig.getStages() != null ? farmingConfig.getStages().keySet() : "null" + ); + Store chunkStore = world.getChunkStore().getStore(); + WorldTimeResource worldTimeResource = world.getEntityStore().getStore().getResource(WorldTimeResource.getResourceType()); + Instant now = worldTimeResource.getGameTime(); + Ref chunkRef = world.getChunkStore().getChunkReference(ChunkUtil.indexChunkFromBlock(x, z)); + if (chunkRef == null) { + LOGGER.atWarning().log("[ChangeFarmingStage] FAILED: chunkRef is null at pos=(%d, %d, %d)", x, y, z); + context.getState().state = InteractionState.Failed; + } else { + BlockComponentChunk blockComponentChunk = chunkStore.getComponent(chunkRef, BlockComponentChunk.getComponentType()); + if (blockComponentChunk == null) { + LOGGER.atWarning().log("[ChangeFarmingStage] FAILED: blockComponentChunk is null at pos=(%d, %d, %d)", x, y, z); + context.getState().state = InteractionState.Failed; + } else { + int blockIndexColumn = ChunkUtil.indexBlockInColumn(x, y, z); + Ref blockRef = blockComponentChunk.getEntityReference(blockIndexColumn); + boolean hadExistingBlockRef = blockRef != null; + LOGGER.atInfo().log("[ChangeFarmingStage] Initial blockRef from getEntityReference: %s", hadExistingBlockRef ? "exists" : "null"); + String initialStageSetLookup = this.targetStageSet != null ? this.targetStageSet : farmingConfig.getStartingStageSet(); + FarmingStageData[] stages = farmingConfig.getStages().get(initialStageSetLookup); + if (stages != null && stages.length != 0) { + LOGGER.atInfo().log("[ChangeFarmingStage] Initial stages lookup: stageSet=%s, stageCount=%d", initialStageSetLookup, stages.length); + FarmingBlock farmingBlock; + if (blockRef == null) { + LOGGER.atInfo().log("[ChangeFarmingStage] Creating new block entity (harvest0 pattern)"); + Holder blockEntity = ChunkStore.REGISTRY.newHolder(); + blockEntity.putComponent(BlockModule.BlockStateInfo.getComponentType(), new BlockModule.BlockStateInfo(blockIndexColumn, chunkRef)); + farmingBlock = new FarmingBlock(); + farmingBlock.setLastTickGameTime(now); + farmingBlock.setCurrentStageSet(this.targetStageSet != null ? this.targetStageSet : farmingConfig.getStartingStageSet()); + int initStage = Math.max(0, stages.length - 2); + farmingBlock.setGrowthProgress(initStage); + blockEntity.addComponent(FarmingBlock.getComponentType(), farmingBlock); + blockRef = chunkStore.addEntity(blockEntity, AddReason.SPAWN); + LOGGER.atInfo() + .log( + "[ChangeFarmingStage] Created new block entity with FarmingBlock: stageSet=%s, initialProgress=%d (second-to-last to avoid removal)", + farmingBlock.getCurrentStageSet(), + initStage + ); + if (blockRef != null) { + farmingBlock.setGrowthProgress(stages.length - 1); + LOGGER.atInfo().log("[ChangeFarmingStage] Updated growthProgress to %d (actual final stage)", stages.length - 1); + } + } else { + farmingBlock = chunkStore.getComponent(blockRef, FarmingBlock.getComponentType()); + boolean hadExistingFarmingBlock = farmingBlock != null; + LOGGER.atInfo() + .log("[ChangeFarmingStage] Block entity exists, FarmingBlock component: %s", hadExistingFarmingBlock ? "exists" : "null"); + if (farmingBlock == null) { + farmingBlock = new FarmingBlock(); + farmingBlock.setLastTickGameTime(now); + farmingBlock.setCurrentStageSet(this.targetStageSet != null ? this.targetStageSet : farmingConfig.getStartingStageSet()); + farmingBlock.setGrowthProgress(stages.length - 1); + chunkStore.putComponent(blockRef, FarmingBlock.getComponentType(), farmingBlock); + LOGGER.atInfo() + .log( + "[ChangeFarmingStage] Added FarmingBlock to existing entity: stageSet=%s, initialProgress=%d", + farmingBlock.getCurrentStageSet(), + stages.length - 1 + ); + } else { + LOGGER.atInfo() + .log( + "[ChangeFarmingStage] Existing FarmingBlock: stageSet=%s, growthProgress=%.2f, lastTickGameTime=%d", + farmingBlock.getCurrentStageSet(), + farmingBlock.getGrowthProgress(), + farmingBlock.getLastTickGameTime() + ); + } + } + + if (blockRef == null) { + LOGGER.atWarning().log("[ChangeFarmingStage] FAILED: blockRef is still null after entity creation"); + context.getState().state = InteractionState.Failed; + } else { + String stageSetName = this.targetStageSet != null ? this.targetStageSet : farmingBlock.getCurrentStageSet(); + stages = farmingConfig.getStages().get(stageSetName); + if (stages != null && stages.length != 0) { + LOGGER.atInfo().log("[ChangeFarmingStage] Using stageSet=%s with %d stages", stageSetName, stages.length); + int currentStage = (int)farmingBlock.getGrowthProgress(); + int originalCurrentStage = currentStage; + if (currentStage >= stages.length) { + LOGGER.atInfo() + .log("[ChangeFarmingStage] Clamping currentStage from %d to %d (was >= stages.length)", currentStage, stages.length - 1); + currentStage = stages.length - 1; + } + + int stageIndex; + if (this.increaseBy != null) { + stageIndex = currentStage + this.increaseBy; + LOGGER.atInfo().log("[ChangeFarmingStage] Mode=INCREASE: %d + %d = %d", currentStage, this.increaseBy, stageIndex); + } else if (this.decreaseBy != null) { + stageIndex = currentStage - this.decreaseBy; + LOGGER.atInfo().log("[ChangeFarmingStage] Mode=DECREASE: %d - %d = %d", currentStage, this.decreaseBy, stageIndex); + } else { + stageIndex = this.targetStage; + if (stageIndex < 0) { + stageIndex = stages.length - 1; + } + + LOGGER.atInfo().log("[ChangeFarmingStage] Mode=ABSOLUTE: targetStage=%d, resolved=%d", this.targetStage, stageIndex); + } + + int preClampStageIndex = stageIndex; + if (stageIndex < 0) { + stageIndex = 0; + } + + if (stageIndex >= stages.length) { + stageIndex = stages.length - 1; + } + + if (preClampStageIndex != stageIndex) { + LOGGER.atInfo().log("[ChangeFarmingStage] Clamped stageIndex from %d to %d", preClampStageIndex, stageIndex); + } + + int previousStageIndex = (int)farmingBlock.getGrowthProgress(); + FarmingStageData previousStage = null; + FarmingStageData[] currentStages = farmingConfig.getStages().get(farmingBlock.getCurrentStageSet()); + if (currentStages != null && previousStageIndex >= 0 && previousStageIndex < currentStages.length) { + previousStage = currentStages[previousStageIndex]; + } + + LOGGER.atInfo() + .log("[ChangeFarmingStage] Previous stage data: index=%d, hasPreviousStage=%s", previousStageIndex, previousStage != null); + farmingBlock.setCurrentStageSet(stageSetName); + farmingBlock.setGrowthProgress(stageIndex); + farmingBlock.setExecutions(0); + farmingBlock.setGeneration(farmingBlock.getGeneration() + 1); + farmingBlock.setLastTickGameTime(now); + LOGGER.atInfo() + .log( + "[ChangeFarmingStage] Updated FarmingBlock: stageSet=%s, growthProgress=%d, generation=%d", + stageSetName, + stageIndex, + farmingBlock.getGeneration() + ); + Ref sectionRef = world.getChunkStore() + .getChunkSectionReference(ChunkUtil.chunkCoordinate(x), ChunkUtil.chunkCoordinate(y), ChunkUtil.chunkCoordinate(z)); + if (sectionRef != null) { + BlockSection section = chunkStore.getComponent(sectionRef, BlockSection.getComponentType()); + if (section != null) { + section.scheduleTick(ChunkUtil.indexBlock(x, y, z), now); + } + + stages[stageIndex].apply(chunkStore, sectionRef, blockRef, x, y, z, previousStage); + LOGGER.atInfo().log("[ChangeFarmingStage] Applied stage %d via stages[%d].apply()", stageIndex, stageIndex); + } else { + LOGGER.atWarning().log("[ChangeFarmingStage] sectionRef was null - could not apply stage!"); + } + + worldChunk.setTicking(x, y, z, true); + LOGGER.atInfo() + .log( + "[ChangeFarmingStage] SUCCESS: Changed stage from %d to %d at pos=(%d, %d, %d)", originalCurrentStage, stageIndex, x, y, z + ); + } else { + LOGGER.atWarning().log("[ChangeFarmingStage] FAILED: stages null/empty after re-fetch with stageSet=%s", stageSetName); + context.getState().state = InteractionState.Failed; + } + } + } else { + LOGGER.atWarning().log("[ChangeFarmingStage] FAILED: stages is null or empty for stageSet=%s", initialStageSetLookup); + context.getState().state = InteractionState.Failed; + } + } + } + } else { + LOGGER.atWarning() + .log( + "[ChangeFarmingStage] FAILED: farmingConfig is null or has no stages. blockType=%s, hasFarmingConfig=%s", + blockType.getId(), + farmingConfig != null + ); + context.getState().state = InteractionState.Failed; + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } + + @Nonnull + @Override + public String toString() { + return "ChangeFarmingStageInteraction{targetStage=" + + this.targetStage + + ", increaseBy=" + + this.increaseBy + + ", decreaseBy=" + + this.decreaseBy + + ", targetStageSet='" + + this.targetStageSet + + "'} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/interactions/FertilizeSoilInteraction.java b/src/com/hypixel/hytale/builtin/adventure/farming/interactions/FertilizeSoilInteraction.java new file mode 100644 index 0000000..3fc0d9b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/interactions/FertilizeSoilInteraction.java @@ -0,0 +1,105 @@ +package com.hypixel.hytale.builtin.adventure.farming.interactions; + +import com.hypixel.hytale.builtin.adventure.farming.states.FarmingBlock; +import com.hypixel.hytale.builtin.adventure.farming.states.TilledSoilBlock; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FertilizeSoilInteraction extends SimpleBlockInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + FertilizeSoilInteraction.class, FertilizeSoilInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("If the target block is farmable then set it to fertilized.") + .addField( + new KeyedCodec<>("RefreshModifiers", Codec.STRING_ARRAY), + (interaction, refreshModifiers) -> interaction.refreshModifiers = refreshModifiers, + interaction -> interaction.refreshModifiers + ) + .build(); + protected String[] refreshModifiers; + + public FertilizeSoilInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + int x = targetBlock.getX(); + int z = targetBlock.getZ(); + WorldChunk worldChunk = world.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + Ref blockRef = worldChunk.getBlockComponentEntity(x, targetBlock.getY(), z); + if (blockRef == null) { + blockRef = BlockModule.ensureBlockEntity(worldChunk, targetBlock.x, targetBlock.y, targetBlock.z); + } + + if (blockRef == null) { + context.getState().state = InteractionState.Failed; + } else { + Store chunkStore = world.getChunkStore().getStore(); + TilledSoilBlock soil = chunkStore.getComponent(blockRef, TilledSoilBlock.getComponentType()); + if (soil != null && !soil.isFertilized()) { + soil.setFertilized(true); + worldChunk.setTicking(x, targetBlock.getY(), z, true); + worldChunk.setTicking(x, targetBlock.getY() + 1, z, true); + } else { + FarmingBlock farmingState = chunkStore.getComponent(blockRef, FarmingBlock.getComponentType()); + if (farmingState == null) { + context.getState().state = InteractionState.Failed; + } else { + Ref soilRef = worldChunk.getBlockComponentEntity(x, targetBlock.getY() - 1, z); + if (soilRef == null) { + context.getState().state = InteractionState.Failed; + } else { + soil = chunkStore.getComponent(soilRef, TilledSoilBlock.getComponentType()); + if (soil != null && !soil.isFertilized()) { + soil.setFertilized(true); + worldChunk.setTicking(x, targetBlock.getY() - 1, z, true); + worldChunk.setTicking(x, targetBlock.getY(), z, true); + } else { + context.getState().state = InteractionState.Failed; + } + } + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/interactions/HarvestCropInteraction.java b/src/com/hypixel/hytale/builtin/adventure/farming/interactions/HarvestCropInteraction.java new file mode 100644 index 0000000..c404cb9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/interactions/HarvestCropInteraction.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.builtin.adventure.farming.interactions; + +import com.hypixel.hytale.builtin.adventure.farming.FarmingUtil; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HarvestCropInteraction extends SimpleBlockInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + HarvestCropInteraction.class, HarvestCropInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Harvests the resources from the target farmable block.") + .build(); + + public HarvestCropInteraction() { + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + Ref ref = context.getEntity(); + ChunkStore chunkStore = world.getChunkStore(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z); + Ref chunkRef = chunkStore.getChunkReference(chunkIndex); + if (chunkRef != null && chunkRef.isValid()) { + BlockChunk blockChunkComponent = chunkStore.getStore().getComponent(chunkRef, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + BlockSection section = blockChunkComponent.getSectionAtBlockY(targetBlock.y); + if (section != null) { + WorldChunk worldChunkComponent = chunkStore.getStore().getComponent(chunkRef, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + BlockType blockType = worldChunkComponent.getBlockType(targetBlock); + if (blockType != null) { + int rotationIndex = section.getRotationIndex(targetBlock.x, targetBlock.y, targetBlock.z); + FarmingUtil.harvest(world, commandBuffer, ref, blockType, rotationIndex, targetBlock); + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } + + @Nonnull + @Override + public String toString() { + return "HarvestCropInteraction{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/interactions/UseCaptureCrateInteraction.java b/src/com/hypixel/hytale/builtin/adventure/farming/interactions/UseCaptureCrateInteraction.java new file mode 100644 index 0000000..401bd8d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/interactions/UseCaptureCrateInteraction.java @@ -0,0 +1,229 @@ +package com.hypixel.hytale.builtin.adventure.farming.interactions; + +import com.hypixel.hytale.builtin.adventure.farming.states.CoopBlock; +import com.hypixel.hytale.builtin.tagset.TagSetPlugin; +import com.hypixel.hytale.builtin.tagset.config.NPCGroup; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFace; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.entity.component.PersistentModel; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.metadata.CapturedNPCMetadata; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class UseCaptureCrateInteraction extends SimpleBlockInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + UseCaptureCrateInteraction.class, UseCaptureCrateInteraction::new, SimpleInteraction.CODEC + ) + .appendInherited( + new KeyedCodec<>("AcceptedNpcGroups", NPCGroup.CHILD_ASSET_CODEC_ARRAY), + (o, v) -> o.acceptedNpcGroupIds = v, + o -> o.acceptedNpcGroupIds, + (o, p) -> o.acceptedNpcGroupIds = p.acceptedNpcGroupIds + ) + .addValidator(NPCGroup.VALIDATOR_CACHE.getArrayValidator()) + .add() + .appendInherited(new KeyedCodec<>("FullIcon", Codec.STRING), (o, v) -> o.fullIcon = v, o -> o.fullIcon, (o, p) -> o.fullIcon = p.fullIcon) + .add() + .afterDecode(captureData -> { + if (captureData.acceptedNpcGroupIds != null) { + captureData.acceptedNpcGroupIndexes = new int[captureData.acceptedNpcGroupIds.length]; + + for (int i = 0; i < captureData.acceptedNpcGroupIds.length; i++) { + int assetIdx = NPCGroup.getAssetMap().getIndex(captureData.acceptedNpcGroupIds[i]); + captureData.acceptedNpcGroupIndexes[i] = assetIdx; + } + } + }) + .build(); + protected String[] acceptedNpcGroupIds; + protected int[] acceptedNpcGroupIndexes; + protected String fullIcon; + + public UseCaptureCrateInteraction() { + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @NonNullDecl InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + if (commandBuffer == null) { + context.getState().state = InteractionState.Failed; + } else { + ItemStack item = context.getHeldItem(); + if (item == null) { + context.getState().state = InteractionState.Failed; + } else { + Ref playerRef = context.getEntity(); + LivingEntity playerEntity = (LivingEntity)EntityUtils.getEntity(playerRef, commandBuffer); + Inventory playerInventory = playerEntity.getInventory(); + byte activeHotbarSlot = playerInventory.getActiveHotbarSlot(); + ItemStack inHandItemStack = playerInventory.getActiveHotbarItem(); + CapturedNPCMetadata existingMeta = item.getFromMetadataOrNull("CapturedEntity", CapturedNPCMetadata.CODEC); + if (existingMeta != null) { + super.tick0(firstRun, time, type, context, cooldownHandler); + } else { + Ref targetEntity = context.getTargetEntity(); + if (targetEntity == null) { + context.getState().state = InteractionState.Failed; + } else { + NPCEntity npc = commandBuffer.getComponent(targetEntity, NPCEntity.getComponentType()); + if (npc == null) { + context.getState().state = InteractionState.Failed; + } else { + TagSetPlugin.TagSetLookup tagSetPlugin = TagSetPlugin.get(NPCGroup.class); + boolean tagFound = false; + + for (int group : this.acceptedNpcGroupIndexes) { + if (tagSetPlugin.tagInSet(group, npc.getRoleIndex())) { + tagFound = true; + break; + } + } + + if (!tagFound) { + context.getState().state = InteractionState.Failed; + } else { + PersistentModel persistentModel = commandBuffer.getComponent(targetEntity, PersistentModel.getComponentType()); + if (persistentModel == null) { + context.getState().state = InteractionState.Failed; + } else { + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(persistentModel.getModelReference().getModelAssetId()); + CapturedNPCMetadata meta = inHandItemStack.getFromMetadataOrDefault("CapturedEntity", CapturedNPCMetadata.CODEC); + if (modelAsset != null) { + meta.setIconPath(modelAsset.getIcon()); + } + + meta.setRoleIndex(npc.getRoleIndex()); + String npcName = NPCPlugin.get().getName(npc.getRoleIndex()); + if (npcName != null) { + meta.setNpcNameKey(npcName); + } + + if (this.fullIcon != null) { + meta.setFullItemIcon(this.fullIcon); + } + + ItemStack itemWithNPC = inHandItemStack.withMetadata(CapturedNPCMetadata.KEYED_CODEC, meta); + playerInventory.getHotbar().replaceItemStackInSlot(activeHotbarSlot, item, itemWithNPC); + commandBuffer.removeEntity(targetEntity, RemoveReason.REMOVE); + } + } + } + } + } + } + } + } + + @Override + protected void interactWithBlock( + @NonNullDecl World world, + @NonNullDecl CommandBuffer commandBuffer, + @NonNullDecl InteractionType type, + @NonNullDecl InteractionContext context, + @NullableDecl ItemStack itemInHand, + @NonNullDecl Vector3i targetBlock, + @NonNullDecl CooldownHandler cooldownHandler + ) { + ItemStack item = context.getHeldItem(); + if (item == null) { + context.getState().state = InteractionState.Failed; + } else { + Ref playerRef = context.getEntity(); + LivingEntity playerEntity = (LivingEntity)EntityUtils.getEntity(playerRef, commandBuffer); + Inventory playerInventory = playerEntity.getInventory(); + byte activeHotbarSlot = playerInventory.getActiveHotbarSlot(); + CapturedNPCMetadata existingMeta = item.getFromMetadataOrNull("CapturedEntity", CapturedNPCMetadata.CODEC); + if (existingMeta == null) { + context.getState().state = InteractionState.Failed; + } else { + BlockPosition pos = context.getTargetBlock(); + if (pos == null) { + context.getState().state = InteractionState.Failed; + } else { + WorldChunk worldChunk = world.getChunk(ChunkUtil.indexChunkFromBlock(pos.x, pos.z)); + Ref blockRef = worldChunk.getBlockComponentEntity(pos.x, pos.y, pos.z); + if (blockRef == null) { + blockRef = BlockModule.ensureBlockEntity(worldChunk, pos.x, pos.y, pos.z); + } + + ItemStack noMetaItemStack = item.withMetadata(null); + if (blockRef != null) { + Store chunkStore = world.getChunkStore().getStore(); + CoopBlock coopBlockState = chunkStore.getComponent(blockRef, CoopBlock.getComponentType()); + if (coopBlockState != null) { + WorldTimeResource worldTimeResource = commandBuffer.getResource(WorldTimeResource.getResourceType()); + if (coopBlockState.tryPutResident(existingMeta, worldTimeResource)) { + world.execute( + () -> coopBlockState.ensureSpawnResidentsInWorld( + world, world.getEntityStore().getStore(), new Vector3d(pos.x, pos.y, pos.z), new Vector3d().assign(Vector3d.FORWARD) + ) + ); + playerInventory.getHotbar().replaceItemStackInSlot(activeHotbarSlot, item, noMetaItemStack); + } else { + context.getState().state = InteractionState.Failed; + } + + return; + } + } + + Vector3d spawnPos = new Vector3d(pos.x + 0.5F, pos.y, pos.z + 0.5F); + if (context.getClientState() != null) { + BlockFace blockFace = BlockFace.fromProtocolFace(context.getClientState().blockFace); + if (blockFace != null) { + spawnPos.add(blockFace.getDirection()); + } + } + + NPCPlugin npcModule = NPCPlugin.get(); + Store store = context.getCommandBuffer().getStore(); + int roleIndex = existingMeta.getRoleIndex(); + commandBuffer.run(_store -> npcModule.spawnEntity(store, roleIndex, spawnPos, Vector3f.ZERO, null, null)); + playerInventory.getHotbar().replaceItemStackInSlot(activeHotbarSlot, item, noMetaItemStack); + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @NonNullDecl InteractionType type, + @NonNullDecl InteractionContext context, + @NullableDecl ItemStack itemInHand, + @NonNullDecl World world, + @NonNullDecl Vector3i targetBlock + ) { + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/interactions/UseCoopInteraction.java b/src/com/hypixel/hytale/builtin/adventure/farming/interactions/UseCoopInteraction.java new file mode 100644 index 0000000..5c543a6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/interactions/UseCoopInteraction.java @@ -0,0 +1,95 @@ +package com.hypixel.hytale.builtin.adventure.farming.interactions; + +import com.hypixel.hytale.builtin.adventure.farming.states.CoopBlock; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class UseCoopInteraction extends SimpleBlockInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + UseCoopInteraction.class, UseCoopInteraction::new, SimpleBlockInteraction.CODEC + ) + .build(); + + public UseCoopInteraction() { + } + + @Override + protected void interactWithBlock( + @NonNullDecl World world, + @NonNullDecl CommandBuffer commandBuffer, + @NonNullDecl InteractionType type, + @NonNullDecl InteractionContext context, + @NullableDecl ItemStack itemInHand, + @NonNullDecl Vector3i targetBlock, + @NonNullDecl CooldownHandler cooldownHandler + ) { + int x = targetBlock.getX(); + int z = targetBlock.getZ(); + WorldChunk worldChunk = world.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + if (worldChunk == null) { + context.getState().state = InteractionState.Failed; + } else { + Ref blockRef = worldChunk.getBlockComponentEntity(x, targetBlock.getY(), z); + if (blockRef == null) { + blockRef = BlockModule.ensureBlockEntity(worldChunk, targetBlock.x, targetBlock.y, targetBlock.z); + } + + if (blockRef == null) { + context.getState().state = InteractionState.Failed; + } else { + Store chunkStore = world.getChunkStore().getStore(); + CoopBlock coopBlockState = chunkStore.getComponent(blockRef, CoopBlock.getComponentType()); + if (coopBlockState == null) { + context.getState().state = InteractionState.Failed; + } else { + Ref playerRef = context.getEntity(); + LivingEntity playerEntity = (LivingEntity)EntityUtils.getEntity(playerRef, commandBuffer); + if (playerEntity == null) { + context.getState().state = InteractionState.Failed; + } else { + CombinedItemContainer playerInventoryContainer = playerEntity.getInventory().getCombinedHotbarFirst(); + if (playerInventoryContainer != null) { + coopBlockState.gatherProduceFromInventory(playerInventoryContainer); + BlockType currentBlockType = worldChunk.getBlockType(targetBlock); + + assert currentBlockType != null; + + worldChunk.setBlockInteractionState(targetBlock, currentBlockType, coopBlockState.hasProduce() ? "Produce_Ready" : "default"); + } + } + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @NonNullDecl InteractionType type, + @NonNullDecl InteractionContext context, + @NullableDecl ItemStack itemInHand, + @NonNullDecl World world, + @NonNullDecl Vector3i targetBlock + ) { + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/interactions/UseWateringCanInteraction.java b/src/com/hypixel/hytale/builtin/adventure/farming/interactions/UseWateringCanInteraction.java new file mode 100644 index 0000000..b4f37ed --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/interactions/UseWateringCanInteraction.java @@ -0,0 +1,115 @@ +package com.hypixel.hytale.builtin.adventure.farming.interactions; + +import com.hypixel.hytale.builtin.adventure.farming.states.FarmingBlock; +import com.hypixel.hytale.builtin.adventure.farming.states.TilledSoilBlock; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UseWateringCanInteraction extends SimpleBlockInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + UseWateringCanInteraction.class, UseWateringCanInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Waters the target farmable block.") + .addField(new KeyedCodec<>("Duration", Codec.LONG), (interaction, duration) -> interaction.duration = duration, interaction -> interaction.duration) + .addField( + new KeyedCodec<>("RefreshModifiers", Codec.STRING_ARRAY), + (interaction, refreshModifiers) -> interaction.refreshModifiers = refreshModifiers, + interaction -> interaction.refreshModifiers + ) + .build(); + protected long duration; + protected String[] refreshModifiers; + + public UseWateringCanInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + int x = targetBlock.getX(); + int z = targetBlock.getZ(); + WorldChunk worldChunk = world.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + Ref blockRef = worldChunk.getBlockComponentEntity(x, targetBlock.getY(), z); + if (blockRef == null) { + blockRef = BlockModule.ensureBlockEntity(worldChunk, targetBlock.x, targetBlock.y, targetBlock.z); + } + + if (blockRef == null) { + context.getState().state = InteractionState.Failed; + } else { + Store chunkStore = world.getChunkStore().getStore(); + WorldTimeResource worldTimeResource = commandBuffer.getResource(WorldTimeResource.getResourceType()); + TilledSoilBlock soil = chunkStore.getComponent(blockRef, TilledSoilBlock.getComponentType()); + if (soil != null) { + Instant wateredUntil = worldTimeResource.getGameTime().plus(this.duration, ChronoUnit.SECONDS); + soil.setWateredUntil(wateredUntil); + worldChunk.setTicking(x, targetBlock.getY(), z, true); + worldChunk.getBlockChunk().getSectionAtBlockY(targetBlock.y).scheduleTick(ChunkUtil.indexBlock(x, targetBlock.y, z), wateredUntil); + worldChunk.setTicking(x, targetBlock.getY() + 1, z, true); + } else { + FarmingBlock farmingState = chunkStore.getComponent(blockRef, FarmingBlock.getComponentType()); + if (farmingState == null) { + context.getState().state = InteractionState.Failed; + } else { + Ref soilRef = worldChunk.getBlockComponentEntity(x, targetBlock.getY() - 1, z); + if (soilRef == null) { + context.getState().state = InteractionState.Failed; + } else { + soil = chunkStore.getComponent(soilRef, TilledSoilBlock.getComponentType()); + if (soil == null) { + context.getState().state = InteractionState.Failed; + } else { + Instant wateredUntil = worldTimeResource.getGameTime().plus(this.duration, ChronoUnit.SECONDS); + soil.setWateredUntil(wateredUntil); + worldChunk.getBlockChunk().getSectionAtBlockY(targetBlock.y - 1).scheduleTick(ChunkUtil.indexBlock(x, targetBlock.y - 1, z), wateredUntil); + worldChunk.setTicking(x, targetBlock.getY() - 1, z, true); + worldChunk.setTicking(x, targetBlock.getY(), z, true); + } + } + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/states/CoopBlock.java b/src/com/hypixel/hytale/builtin/adventure/farming/states/CoopBlock.java new file mode 100644 index 0000000..b6ab6b8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/states/CoopBlock.java @@ -0,0 +1,477 @@ +package com.hypixel.hytale.builtin.adventure.farming.states; + +import com.hypixel.hytale.builtin.adventure.farming.FarmingPlugin; +import com.hypixel.hytale.builtin.adventure.farming.FarmingUtil; +import com.hypixel.hytale.builtin.adventure.farming.component.CoopResidentComponent; +import com.hypixel.hytale.builtin.adventure.farming.config.FarmingCoopAsset; +import com.hypixel.hytale.builtin.tagset.TagSetPlugin; +import com.hypixel.hytale.builtin.tagset.config.NPCGroup; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.range.IntRange; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDrop; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.reference.PersistentRef; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.EmptyItemContainer; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.metadata.CapturedNPCMetadata; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.spawning.ISpawnableWithModel; +import com.hypixel.hytale.server.spawning.SpawnTestResult; +import com.hypixel.hytale.server.spawning.SpawningContext; +import it.unimi.dsi.fastutil.Pair; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class CoopBlock implements Component { + public static final String STATE_PRODUCE = "Produce_Ready"; + public static final BuilderCodec CODEC = BuilderCodec.builder(CoopBlock.class, CoopBlock::new) + .append(new KeyedCodec<>("FarmingCoopId", Codec.STRING, true), (coop, s) -> coop.coopAssetId = s, coop -> coop.coopAssetId) + .add() + .append( + new KeyedCodec<>("Residents", new ArrayCodec<>(CoopBlock.CoopResident.CODEC, CoopBlock.CoopResident[]::new)), + (coop, residents) -> coop.residents = new ArrayList<>(Arrays.asList(residents)), + coop -> coop.residents.toArray(CoopBlock.CoopResident[]::new) + ) + .add() + .append(new KeyedCodec<>("Storage", ItemContainer.CODEC), (coop, storage) -> coop.itemContainer = storage, coop -> coop.itemContainer) + .add() + .build(); + HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + protected String coopAssetId; + protected List residents = new ArrayList<>(); + protected ItemContainer itemContainer = EmptyItemContainer.INSTANCE; + + public static ComponentType getComponentType() { + return FarmingPlugin.get().getCoopBlockStateComponentType(); + } + + public CoopBlock() { + ArrayList remainder = new ArrayList<>(); + this.itemContainer = ItemContainer.ensureContainerCapacity(this.itemContainer, (short)5, SimpleItemContainer::new, remainder); + } + + @NullableDecl + public FarmingCoopAsset getCoopAsset() { + return FarmingCoopAsset.getAssetMap().getAsset(this.coopAssetId); + } + + public CoopBlock(String farmingCoopId, List residents, ItemContainer itemContainer) { + this.coopAssetId = farmingCoopId; + this.residents.addAll(residents); + this.itemContainer = itemContainer.clone(); + ArrayList remainder = new ArrayList<>(); + this.itemContainer = ItemContainer.ensureContainerCapacity(this.itemContainer, (short)5, SimpleItemContainer::new, remainder); + } + + public boolean tryPutResident(CapturedNPCMetadata metadata, WorldTimeResource worldTimeResource) { + FarmingCoopAsset coopAsset = this.getCoopAsset(); + if (coopAsset == null) { + return false; + } else if (this.residents.size() >= coopAsset.getMaxResidents()) { + return false; + } else if (!this.getCoopAcceptsNPCGroup(metadata.getRoleIndex())) { + return false; + } else { + this.residents.add(new CoopBlock.CoopResident(metadata, null, worldTimeResource.getGameTime())); + return true; + } + } + + public boolean tryPutWildResidentFromWild(Store store, Ref entityRef, WorldTimeResource worldTimeResource, Vector3i coopLocation) { + FarmingCoopAsset coopAsset = this.getCoopAsset(); + if (coopAsset == null) { + return false; + } else { + NPCEntity npcComponent = store.getComponent(entityRef, NPCEntity.getComponentType()); + if (npcComponent == null) { + return false; + } else { + CoopResidentComponent coopResidentComponent = store.getComponent(entityRef, CoopResidentComponent.getComponentType()); + if (coopResidentComponent != null) { + return false; + } else if (!this.getCoopAcceptsNPCGroup(npcComponent.getRoleIndex())) { + return false; + } else if (this.residents.size() >= coopAsset.getMaxResidents()) { + return false; + } else { + coopResidentComponent = store.ensureAndGetComponent(entityRef, CoopResidentComponent.getComponentType()); + coopResidentComponent.setCoopLocation(coopLocation); + UUIDComponent uuidComponent = store.getComponent(entityRef, UUIDComponent.getComponentType()); + if (uuidComponent == null) { + return false; + } else { + PersistentRef persistentRef = new PersistentRef(); + persistentRef.setEntity(entityRef, uuidComponent.getUuid()); + CapturedNPCMetadata metadata = FarmingUtil.generateCapturedNPCMetadata(store, entityRef, npcComponent.getRoleIndex()); + CoopBlock.CoopResident residentRecord = new CoopBlock.CoopResident(metadata, persistentRef, worldTimeResource.getGameTime()); + residentRecord.deployedToWorld = true; + this.residents.add(residentRecord); + return true; + } + } + } + } + } + + public boolean getCoopAcceptsNPCGroup(int npcRoleIndex) { + TagSetPlugin.TagSetLookup tagSetPlugin = TagSetPlugin.get(NPCGroup.class); + FarmingCoopAsset coopAsset = this.getCoopAsset(); + if (coopAsset == null) { + return false; + } else { + int[] acceptedNpcGroupIndexes = coopAsset.getAcceptedNpcGroupIndexes(); + if (acceptedNpcGroupIndexes == null) { + return true; + } else { + for (int group : acceptedNpcGroupIndexes) { + if (tagSetPlugin.tagInSet(group, npcRoleIndex)) { + return true; + } + } + + return false; + } + } + } + + public void generateProduceToInventory(WorldTimeResource worldTimeResource) { + Instant currentTime = worldTimeResource.getGameTime(); + FarmingCoopAsset coopAsset = this.getCoopAsset(); + if (coopAsset != null) { + Map produceDropsMap = coopAsset.getProduceDrops(); + if (!produceDropsMap.isEmpty()) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + List generatedItemDrops = new ArrayList<>(); + + for (CoopBlock.CoopResident resident : this.residents) { + Instant lastProduced = resident.getLastProduced(); + if (lastProduced == null) { + resident.setLastProduced(currentTime); + } else { + CapturedNPCMetadata residentMeta = resident.getMetadata(); + int npcRoleIndex = residentMeta.getRoleIndex(); + String npcName = NPCPlugin.get().getName(npcRoleIndex); + String npcDropListName = produceDropsMap.get(npcName); + if (npcDropListName != null) { + ItemDropList dropListAsset = ItemDropList.getAssetMap().getAsset(npcDropListName); + if (dropListAsset != null) { + Duration harvestDiff = Duration.between(lastProduced, currentTime); + long hoursSinceLastHarvest = harvestDiff.toHours(); + int produceCount = MathUtil.ceil((float)hoursSinceLastHarvest / WorldTimeResource.HOURS_PER_DAY); + List configuredItemDrops = new ArrayList<>(); + + for (int i = 0; i < produceCount; i++) { + dropListAsset.getContainer().populateDrops(configuredItemDrops, random::nextDouble, npcDropListName); + + for (ItemDrop drop : configuredItemDrops) { + if (drop != null && drop.getItemId() != null) { + int amount = drop.getRandomQuantity(random); + if (amount > 0) { + generatedItemDrops.add(new ItemStack(drop.getItemId(), amount, drop.getMetadata())); + } + } else { + HytaleLogger.forEnclosingClass() + .atWarning() + .log("Tried to create ItemDrop for non-existent item in drop list id '%s'", npcDropListName); + } + } + + configuredItemDrops.clear(); + } + + resident.setLastProduced(currentTime); + } + } + } + } + + this.itemContainer.addItemStacks(generatedItemDrops); + } + } + } + + public void gatherProduceFromInventory(ItemContainer playerInventory) { + for (ItemStack item : this.itemContainer.removeAllItemStacks()) { + playerInventory.addItemStack(item); + } + } + + public void ensureSpawnResidentsInWorld(World world, Store store, Vector3d coopLocation, Vector3d spawnOffset) { + NPCPlugin npcModule = NPCPlugin.get(); + FarmingCoopAsset coopAsset = this.getCoopAsset(); + if (coopAsset != null) { + float radiansPerSpawn = (float) (Math.PI * 2) / coopAsset.getMaxResidents(); + Vector3d spawnOffsetIteration = spawnOffset; + SpawningContext spawningContext = new SpawningContext(); + + for (CoopBlock.CoopResident resident : this.residents) { + CapturedNPCMetadata residentMeta = resident.getMetadata(); + int npcRoleIndex = residentMeta.getRoleIndex(); + boolean residentDeployed = resident.getDeployedToWorld(); + PersistentRef residentEntityId = resident.getPersistentRef(); + if (!residentDeployed && residentEntityId == null) { + Vector3d residentSpawnLocation = new Vector3d().assign(coopLocation).add(spawnOffsetIteration); + Builder roleBuilder = NPCPlugin.get().tryGetCachedValidRole(npcRoleIndex); + if (roleBuilder != null) { + spawningContext.setSpawnable((ISpawnableWithModel)roleBuilder); + if (spawningContext.set(world, residentSpawnLocation.x, residentSpawnLocation.y, residentSpawnLocation.z) + && spawningContext.canSpawn() == SpawnTestResult.TEST_OK) { + Pair, NPCEntity> npcPair = npcModule.spawnEntity( + store, npcRoleIndex, spawningContext.newPosition(), Vector3f.ZERO, null, null + ); + if (npcPair == null) { + resident.setPersistentRef(null); + resident.setDeployedToWorld(false); + } else { + Ref npcRef = npcPair.first(); + NPCEntity npcComponent = npcPair.second(); + npcComponent.getLeashPoint().assign(coopLocation); + if (npcRef != null && npcRef.isValid()) { + UUIDComponent uuidComponent = store.getComponent(npcRef, UUIDComponent.getComponentType()); + if (uuidComponent == null) { + resident.setPersistentRef(null); + resident.setDeployedToWorld(false); + } else { + CoopResidentComponent coopResidentComponent = new CoopResidentComponent(); + coopResidentComponent.setCoopLocation(coopLocation.toVector3i()); + store.addComponent(npcRef, CoopResidentComponent.getComponentType(), coopResidentComponent); + PersistentRef persistentRef = new PersistentRef(); + persistentRef.setEntity(npcRef, uuidComponent.getUuid()); + resident.setPersistentRef(persistentRef); + resident.setDeployedToWorld(true); + spawnOffsetIteration = spawnOffsetIteration.rotateY(radiansPerSpawn); + } + } else { + resident.setPersistentRef(null); + resident.setDeployedToWorld(false); + } + } + } + } + } + } + } + } + + public void ensureNoResidentsInWorld(Store store) { + ArrayList residentsToRemove = new ArrayList<>(); + + for (CoopBlock.CoopResident resident : this.residents) { + boolean deployed = resident.getDeployedToWorld(); + PersistentRef entityUuid = resident.getPersistentRef(); + if (deployed || entityUuid != null) { + Ref entityRef = entityUuid.getEntity(store); + if (entityRef == null) { + residentsToRemove.add(resident); + } else { + CoopResidentComponent coopResidentComponent = store.getComponent(entityRef, CoopResidentComponent.getComponentType()); + if (coopResidentComponent == null) { + residentsToRemove.add(resident); + } else { + DeathComponent deathComponent = store.getComponent(entityRef, DeathComponent.getComponentType()); + if (deathComponent != null) { + residentsToRemove.add(resident); + } else { + coopResidentComponent.setMarkedForDespawn(true); + resident.setPersistentRef(null); + resident.setDeployedToWorld(false); + } + } + } + } + } + + for (CoopBlock.CoopResident residentx : residentsToRemove) { + this.residents.remove(residentx); + } + } + + public boolean shouldResidentsBeInCoop(WorldTimeResource worldTimeResource) { + FarmingCoopAsset coopAsset = this.getCoopAsset(); + if (coopAsset == null) { + return true; + } else { + IntRange roamTimeRange = coopAsset.getResidentRoamTime(); + if (roamTimeRange == null) { + return true; + } else { + int gameHour = worldTimeResource.getCurrentHour(); + return !roamTimeRange.includes(gameHour); + } + } + } + + @NullableDecl + public Instant getNextScheduledTick(WorldTimeResource worldTimeResource) { + Instant gameTime = worldTimeResource.getGameTime(); + LocalDateTime gameDateTime = worldTimeResource.getGameDateTime(); + int gameHour = worldTimeResource.getCurrentHour(); + int minutes = gameDateTime.getMinute(); + FarmingCoopAsset coopAsset = this.getCoopAsset(); + if (coopAsset == null) { + return null; + } else { + IntRange roamTimeRange = coopAsset.getResidentRoamTime(); + if (roamTimeRange == null) { + return null; + } else { + int nextScheduledHour = 0; + int minTime = roamTimeRange.getInclusiveMin(); + int maxTime = roamTimeRange.getInclusiveMax(); + if (coopAsset.getResidentRoamTime().includes(gameHour)) { + nextScheduledHour = coopAsset.getResidentRoamTime().getInclusiveMax() + 1 - gameHour; + } else if (gameHour > maxTime) { + nextScheduledHour = WorldTimeResource.HOURS_PER_DAY - gameHour + minTime; + } else { + nextScheduledHour = minTime - gameHour; + } + + return gameTime.plus(nextScheduledHour * 60L - minutes, ChronoUnit.MINUTES); + } + } + } + + public void handleResidentDespawn(UUID entityUuid) { + CoopBlock.CoopResident removedResident = null; + + for (CoopBlock.CoopResident resident : this.residents) { + if (resident.persistentRef != null && resident.persistentRef.getUuid() == entityUuid) { + removedResident = resident; + break; + } + } + + if (removedResident != null) { + this.residents.remove(removedResident); + } + } + + public void handleBlockBroken(World world, WorldTimeResource worldTimeResource, Store store, int blockX, int blockY, int blockZ) { + Vector3i location = new Vector3i(blockX, blockY, blockZ); + world.execute(() -> this.ensureSpawnResidentsInWorld(world, store, location.toVector3d(), new Vector3d().assign(Vector3d.FORWARD))); + this.generateProduceToInventory(worldTimeResource); + Vector3d dropPosition = new Vector3d(blockX + 0.5F, blockY, blockZ + 0.5F); + Holder[] itemEntityHolders = ItemComponent.generateItemDrops(store, this.itemContainer.removeAllItemStacks(), dropPosition, Vector3f.ZERO); + if (itemEntityHolders.length > 0) { + world.execute(() -> store.addEntities(itemEntityHolders, AddReason.SPAWN)); + } + + world.execute(() -> { + for (CoopBlock.CoopResident resident : this.residents) { + PersistentRef persistentRef = resident.getPersistentRef(); + if (persistentRef != null) { + Ref ref = persistentRef.getEntity(store); + if (ref == null) { + return; + } + + store.tryRemoveComponent(ref, CoopResidentComponent.getComponentType()); + } + } + }); + } + + public boolean hasProduce() { + return !this.itemContainer.isEmpty(); + } + + @Override + public Component clone() { + return new CoopBlock(this.coopAssetId, this.residents, this.itemContainer); + } + + public static class CoopResident { + public static final BuilderCodec CODEC = BuilderCodec.builder(CoopBlock.CoopResident.class, CoopBlock.CoopResident::new) + .append(new KeyedCodec<>("Metadata", CapturedNPCMetadata.CODEC), (coop, meta) -> coop.metadata = meta, coop -> coop.metadata) + .add() + .append( + new KeyedCodec<>("PersistentRef", PersistentRef.CODEC), (coop, persistentRef) -> coop.persistentRef = persistentRef, coop -> coop.persistentRef + ) + .add() + .append( + new KeyedCodec<>("DeployedToWorld", Codec.BOOLEAN), (coop, deployedToWorld) -> coop.deployedToWorld = deployedToWorld, coop -> coop.deployedToWorld + ) + .add() + .append(new KeyedCodec<>("LastHarvested", Codec.INSTANT), (coop, instant) -> coop.lastProduced = instant, coop -> coop.lastProduced) + .add() + .build(); + protected CapturedNPCMetadata metadata; + @NullableDecl + protected PersistentRef persistentRef; + protected boolean deployedToWorld; + protected Instant lastProduced; + + public CoopResident() { + } + + public CoopResident(CapturedNPCMetadata metadata, PersistentRef persistentRef, Instant lastProduced) { + this.metadata = metadata; + this.persistentRef = persistentRef; + this.lastProduced = lastProduced; + } + + public CapturedNPCMetadata getMetadata() { + return this.metadata; + } + + @NullableDecl + public PersistentRef getPersistentRef() { + return this.persistentRef; + } + + public void setPersistentRef(@NullableDecl PersistentRef persistentRef) { + this.persistentRef = persistentRef; + } + + public boolean getDeployedToWorld() { + return this.deployedToWorld; + } + + public void setDeployedToWorld(boolean deployedToWorld) { + this.deployedToWorld = deployedToWorld; + } + + public Instant getLastProduced() { + return this.lastProduced; + } + + public void setLastProduced(Instant lastProduced) { + this.lastProduced = lastProduced; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/states/FarmingBlock.java b/src/com/hypixel/hytale/builtin/adventure/farming/states/FarmingBlock.java new file mode 100644 index 0000000..dfd9636 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/states/FarmingBlock.java @@ -0,0 +1,168 @@ +package com.hypixel.hytale.builtin.adventure.farming.states; + +import com.hypixel.hytale.builtin.adventure.farming.FarmingPlugin; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.time.Instant; +import javax.annotation.Nullable; + +public class FarmingBlock implements Component { + public static final String DEFAULT_STAGE_SET = "Default"; + public static final BuilderCodec CODEC = BuilderCodec.builder(FarmingBlock.class, FarmingBlock::new) + .append( + new KeyedCodec<>("CurrentStageSet", Codec.STRING), + (farmingBlock, currentStageSet) -> farmingBlock.currentStageSet = currentStageSet, + farmingBlock -> "Default".equals(farmingBlock.currentStageSet) ? null : "Default" + ) + .add() + .append( + new KeyedCodec<>("GrowthProgress", Codec.FLOAT), + (farmingBlock, growthProgress) -> farmingBlock.growthProgress = growthProgress, + farmingBlock -> farmingBlock.growthProgress == 0.0F ? null : farmingBlock.growthProgress + ) + .add() + .append( + new KeyedCodec<>("LastTickGameTime", Codec.INSTANT), + (farmingBlock, lastTickGameTime) -> farmingBlock.lastTickGameTime = lastTickGameTime, + farmingBlock -> farmingBlock.lastTickGameTime + ) + .add() + .append( + new KeyedCodec<>("Generation", Codec.INTEGER), + (farmingBlock, generation) -> farmingBlock.generation = generation, + farmingBlock -> farmingBlock.generation == 0 ? null : farmingBlock.generation + ) + .add() + .append( + new KeyedCodec<>("PreviousBlockType", Codec.STRING), + (farmingBlock, previousBlockType) -> farmingBlock.previousBlockType = previousBlockType, + farmingBlock -> farmingBlock.previousBlockType + ) + .add() + .append( + new KeyedCodec<>("SpreadRate", Codec.FLOAT), + (farmingBlock, spreadRate) -> farmingBlock.spreadRate = spreadRate, + farmingBlock -> farmingBlock.spreadRate == 1.0F ? null : farmingBlock.spreadRate + ) + .add() + .append( + new KeyedCodec<>("Executions", Codec.INTEGER), + (farmingBlock, executions) -> farmingBlock.executions = executions, + farmingBlock -> farmingBlock.executions == 0 ? null : farmingBlock.executions + ) + .add() + .build(); + private String currentStageSet = "Default"; + private float growthProgress; + private Instant lastTickGameTime; + private int generation; + private String previousBlockType; + private float spreadRate = 1.0F; + private int executions = 0; + + public static ComponentType getComponentType() { + return FarmingPlugin.get().getFarmingBlockComponentType(); + } + + public FarmingBlock() { + } + + public FarmingBlock( + String currentStageSet, float growthProgress, Instant lastTickGameTime, int generation, String previousBlockType, float spreadRate, int executions + ) { + this.currentStageSet = currentStageSet; + this.growthProgress = growthProgress; + this.lastTickGameTime = lastTickGameTime; + this.generation = generation; + this.previousBlockType = previousBlockType; + this.spreadRate = spreadRate; + this.executions = executions; + } + + public String getCurrentStageSet() { + return this.currentStageSet; + } + + public void setCurrentStageSet(String currentStageSet) { + this.currentStageSet = currentStageSet != null ? currentStageSet : "Default"; + } + + public float getGrowthProgress() { + return this.growthProgress; + } + + public void setGrowthProgress(float growthProgress) { + this.growthProgress = growthProgress; + } + + public Instant getLastTickGameTime() { + return this.lastTickGameTime; + } + + public void setLastTickGameTime(Instant lastTickGameTime) { + this.lastTickGameTime = lastTickGameTime; + } + + public int getGeneration() { + return this.generation; + } + + public void setGeneration(int generation) { + this.generation = generation; + } + + public String getPreviousBlockType() { + return this.previousBlockType; + } + + public void setPreviousBlockType(String previousBlockType) { + this.previousBlockType = previousBlockType; + } + + public float getSpreadRate() { + return this.spreadRate; + } + + public void setSpreadRate(float spreadRate) { + this.spreadRate = spreadRate; + } + + public int getExecutions() { + return this.executions; + } + + public void setExecutions(int executions) { + this.executions = executions; + } + + @Nullable + @Override + public Component clone() { + return new FarmingBlock( + this.currentStageSet, this.growthProgress, this.lastTickGameTime, this.generation, this.previousBlockType, this.spreadRate, this.executions + ); + } + + @Override + public String toString() { + return "FarmingBlock{currentStageSet='" + + this.currentStageSet + + "', growthProgress=" + + this.growthProgress + + ", lastTickGameTime=" + + this.lastTickGameTime + + ", generation=" + + this.generation + + ", previousBlockType='" + + this.previousBlockType + + "', spreadRate=" + + this.spreadRate + + ", executions=" + + this.executions + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/states/FarmingBlockState.java b/src/com/hypixel/hytale/builtin/adventure/farming/states/FarmingBlockState.java new file mode 100644 index 0000000..b490d86 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/states/FarmingBlockState.java @@ -0,0 +1,122 @@ +package com.hypixel.hytale.builtin.adventure.farming.states; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.time.Instant; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@Deprecated(forRemoval = true) +public class FarmingBlockState implements Component { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder(FarmingBlockState.class, FarmingBlockState::new) + .append(new KeyedCodec<>("BaseCrop", Codec.STRING), (state, crop) -> state.baseCrop = crop, state -> state.baseCrop) + .add() + .append(new KeyedCodec<>("StageStart", Codec.INSTANT), (state, start) -> state.stageStart = start, state -> state.stageStart) + .add() + .append( + new KeyedCodec<>("CurrentFarmingStageIndex", Codec.INTEGER), + (baseFarmingBlockState, integer) -> baseFarmingBlockState.currentFarmingStageIndex = integer, + baseFarmingBlockState -> baseFarmingBlockState.currentFarmingStageIndex + ) + .add() + .append( + new KeyedCodec<>("CurrentFarmingStageSetName", Codec.STRING), + (farmingBlockState, s) -> farmingBlockState.currentFarmingStageSetName = s, + farmingBlockState -> farmingBlockState.currentFarmingStageSetName + ) + .add() + .append(new KeyedCodec<>("SpreadRate", Codec.FLOAT), (blockState, aFloat) -> blockState.spreadRate = aFloat, blockState -> blockState.spreadRate) + .add() + .build(); + public boolean loaded; + public String baseCrop; + public Instant stageStart; + public String currentFarmingStageSetName; + public int currentFarmingStageIndex; + public Instant[] stageCompletionTimes; + public String stageSetAfterHarvest; + public double lastGrowthMultiplier; + public float spreadRate = 1.0F; + + public FarmingBlockState() { + } + + public String getCurrentFarmingStageSetName() { + return this.currentFarmingStageSetName; + } + + public void setCurrentFarmingStageSetName(String currentFarmingStageSetName) { + this.currentFarmingStageSetName = currentFarmingStageSetName; + } + + public int getCurrentFarmingStageIndex() { + return this.currentFarmingStageIndex; + } + + public void setCurrentFarmingStageIndex(int currentFarmingStageIndex) { + this.currentFarmingStageIndex = currentFarmingStageIndex; + } + + public String getStageSetAfterHarvest() { + return this.stageSetAfterHarvest; + } + + public void setStageSetAfterHarvest(String stageSetAfterHarvest) { + this.stageSetAfterHarvest = stageSetAfterHarvest; + } + + public float getSpreadRate() { + return this.spreadRate; + } + + public void setSpreadRate(float spreadRate) { + this.spreadRate = spreadRate; + } + + @Nonnull + @Override + public String toString() { + return "FarmingBlockState{loaded=" + + this.loaded + + ", baseCrop=" + + this.baseCrop + + ", stageStart=" + + this.stageStart + + ", currentFarmingStageSetName='" + + this.currentFarmingStageSetName + + "', currentFarmingStageIndex=" + + this.currentFarmingStageIndex + + ", stageCompletionTimes=" + + Arrays.toString((Object[])this.stageCompletionTimes) + + ", stageSetAfterHarvest='" + + this.stageSetAfterHarvest + + "', lastGrowthMultiplier=" + + this.lastGrowthMultiplier + + "} " + + super.toString(); + } + + @Nullable + @Override + public Component clone() { + return this; + } + + protected static class RefreshFlags { + protected static final int REFRESH_ALL_FLAG = 1; + protected static final int UNLOADING_FLAG = 2; + protected static final int RETROACTIVE_FLAG = 4; + protected static final int DEFAULT = 1; + protected static final int ON_UNLOADING = 3; + protected static final int ON_LOADING = 5; + protected static final int NONE = 0; + + protected RefreshFlags() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/farming/states/TilledSoilBlock.java b/src/com/hypixel/hytale/builtin/adventure/farming/states/TilledSoilBlock.java new file mode 100644 index 0000000..1e501be --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/farming/states/TilledSoilBlock.java @@ -0,0 +1,150 @@ +package com.hypixel.hytale.builtin.adventure.farming.states; + +import com.hypixel.hytale.builtin.adventure.farming.FarmingPlugin; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.time.Instant; +import java.util.Map; +import javax.annotation.Nullable; + +public class TilledSoilBlock implements Component { + public static int VERSION = 1; + public static final BuilderCodec CODEC = BuilderCodec.builder(TilledSoilBlock.class, TilledSoilBlock::new) + .versioned() + .codecVersion(VERSION) + .append(new KeyedCodec<>("Planted", Codec.BOOLEAN), (state, planted) -> state.planted = planted, state -> state.planted ? Boolean.TRUE : null) + .add() + .append(new KeyedCodec<>("ModifierTimes", new MapCodec<>(Codec.INSTANT, Object2ObjectOpenHashMap::new, false)), (state, times) -> { + if (times != null) { + state.wateredUntil = (Instant)times.get("WateredUntil"); + } + }, state -> null) + .setVersionRange(0, 0) + .add() + .append(new KeyedCodec<>("Flags", Codec.STRING_ARRAY), (state, flags) -> { + if (flags != null) { + state.fertilized = ArrayUtil.contains(flags, "Fertilized"); + state.externalWater = ArrayUtil.contains(flags, "ExternalWater"); + } + }, state -> null) + .setVersionRange(0, 0) + .add() + .append(new KeyedCodec<>("Fertilized", Codec.BOOLEAN), (state, v) -> state.fertilized = v, state -> state.fertilized ? Boolean.TRUE : null) + .setVersionRange(1, VERSION) + .add() + .append( + new KeyedCodec<>("ExternalWater", Codec.BOOLEAN), (state, v) -> state.externalWater = v, state -> state.externalWater ? Boolean.TRUE : null + ) + .setVersionRange(1, VERSION) + .add() + .append(new KeyedCodec<>("WateredUntil", Codec.INSTANT), (state, v) -> state.wateredUntil = v, state -> state.wateredUntil) + .setVersionRange(1, VERSION) + .add() + .append(new KeyedCodec<>("DecayTime", Codec.INSTANT), (state, v) -> state.decayTime = v, state -> state.decayTime) + .add() + .build(); + protected boolean planted; + protected boolean fertilized; + protected boolean externalWater; + @Nullable + protected Instant wateredUntil; + @Nullable + protected Instant decayTime; + + public static ComponentType getComponentType() { + return FarmingPlugin.get().getTiledSoilBlockComponentType(); + } + + public TilledSoilBlock() { + } + + public TilledSoilBlock(boolean planted, boolean fertilized, boolean externalWater, Instant wateredUntil, Instant decayTime) { + this.planted = planted; + this.fertilized = fertilized; + this.externalWater = externalWater; + this.wateredUntil = wateredUntil; + this.decayTime = decayTime; + } + + public boolean isPlanted() { + return this.planted; + } + + public void setPlanted(boolean planted) { + this.planted = planted; + } + + public void setWateredUntil(@Nullable Instant wateredUntil) { + this.wateredUntil = wateredUntil; + } + + @Nullable + public Instant getWateredUntil() { + return this.wateredUntil; + } + + public boolean isFertilized() { + return this.fertilized; + } + + public void setFertilized(boolean fertilized) { + this.fertilized = fertilized; + } + + public boolean hasExternalWater() { + return this.externalWater; + } + + public void setExternalWater(boolean externalWater) { + this.externalWater = externalWater; + } + + @Nullable + public Instant getDecayTime() { + return this.decayTime; + } + + public void setDecayTime(@Nullable Instant decayTime) { + this.decayTime = decayTime; + } + + public String computeBlockType(Instant gameTime, BlockType type) { + boolean watered = this.hasExternalWater() || this.wateredUntil != null && this.wateredUntil.isAfter(gameTime); + if (this.fertilized && watered) { + return type.getBlockKeyForState("Fertilized_Watered"); + } else if (this.fertilized) { + return type.getBlockKeyForState("Fertilized"); + } else { + return watered ? type.getBlockKeyForState("Watered") : type.getBlockKeyForState("default"); + } + } + + @Override + public String toString() { + return "TilledSoilBlock{planted=" + + this.planted + + ", fertilized=" + + this.fertilized + + ", externalWater=" + + this.externalWater + + ", wateredUntil=" + + this.wateredUntil + + ", decayTime=" + + this.decayTime + + "}"; + } + + @Nullable + @Override + public Component clone() { + return new TilledSoilBlock(this.planted, this.fertilized, this.externalWater, this.wateredUntil, this.decayTime); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/MemoriesGameplayConfig.java b/src/com/hypixel/hytale/builtin/adventure/memories/MemoriesGameplayConfig.java new file mode 100644 index 0000000..0dade29 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/MemoriesGameplayConfig.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.builtin.adventure.memories; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MemoriesGameplayConfig { + public static final String ID = "Memories"; + public static final BuilderCodec CODEC = BuilderCodec.builder(MemoriesGameplayConfig.class, MemoriesGameplayConfig::new) + .appendInherited( + new KeyedCodec<>("MemoriesAmountPerLevel", Codec.INT_ARRAY), + (config, value) -> config.memoriesAmountPerLevel = value, + config -> config.memoriesAmountPerLevel, + (config, parent) -> config.memoriesAmountPerLevel = parent.memoriesAmountPerLevel + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("MemoriesRecordParticles", Codec.STRING), + (config, value) -> config.memoriesRecordParticles = value, + config -> config.memoriesRecordParticles, + (config, parent) -> config.memoriesRecordParticles = parent.memoriesRecordParticles + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("MemoriesCatchItemId", Codec.STRING), + (memoriesGameplayConfig, s) -> memoriesGameplayConfig.memoriesCatchItemId = s, + memoriesGameplayConfig -> memoriesGameplayConfig.memoriesCatchItemId, + (memoriesGameplayConfig, parent) -> memoriesGameplayConfig.memoriesCatchItemId = parent.memoriesCatchItemId + ) + .addValidator(Validators.nonNull()) + .addValidator(Item.VALIDATOR_CACHE.getValidator()) + .add() + .build(); + private int[] memoriesAmountPerLevel; + private String memoriesRecordParticles; + private String memoriesCatchItemId; + + public MemoriesGameplayConfig() { + } + + @Nullable + public static MemoriesGameplayConfig get(@Nonnull GameplayConfig config) { + return config.getPluginConfig().get(MemoriesGameplayConfig.class); + } + + public int[] getMemoriesAmountPerLevel() { + return this.memoriesAmountPerLevel; + } + + public String getMemoriesRecordParticles() { + return this.memoriesRecordParticles; + } + + public String getMemoriesCatchItemId() { + return this.memoriesCatchItemId; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/MemoriesPlugin.java b/src/com/hypixel/hytale/builtin/adventure/memories/MemoriesPlugin.java new file mode 100644 index 0000000..585e97f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/MemoriesPlugin.java @@ -0,0 +1,362 @@ +package com.hypixel.hytale.builtin.adventure.memories; + +import com.hypixel.hytale.builtin.adventure.memories.commands.MemoriesCommand; +import com.hypixel.hytale.builtin.adventure.memories.component.PlayerMemories; +import com.hypixel.hytale.builtin.adventure.memories.interactions.MemoriesConditionInteraction; +import com.hypixel.hytale.builtin.adventure.memories.interactions.SetMemoriesCapacityInteraction; +import com.hypixel.hytale.builtin.adventure.memories.memories.Memory; +import com.hypixel.hytale.builtin.adventure.memories.memories.MemoryProvider; +import com.hypixel.hytale.builtin.adventure.memories.memories.npc.NPCMemory; +import com.hypixel.hytale.builtin.adventure.memories.memories.npc.NPCMemoryProvider; +import com.hypixel.hytale.builtin.adventure.memories.page.MemoriesPage; +import com.hypixel.hytale.builtin.adventure.memories.page.MemoriesPageSupplier; +import com.hypixel.hytale.builtin.adventure.memories.temple.ForgottenTempleConfig; +import com.hypixel.hytale.builtin.adventure.memories.temple.TempleRespawnPlayersSystem; +import com.hypixel.hytale.builtin.adventure.memories.window.MemoriesWindow; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.Object2DoubleMapCodec; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.protocol.packets.player.UpdateMemoriesFeatureStatus; +import com.hypixel.hytale.protocol.packets.window.WindowType; +import com.hypixel.hytale.server.core.Constants; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.windows.Window; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSystems; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenCustomUIInteraction; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.BsonUtil; +import com.hypixel.hytale.server.core.util.Config; +import com.hypixel.hytale.server.npc.AllNPCsLoadedEvent; +import it.unimi.dsi.fastutil.objects.Object2DoubleMap; +import it.unimi.dsi.fastutil.objects.Object2DoubleMaps; +import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MemoriesPlugin extends JavaPlugin { + private static MemoriesPlugin instance; + private final Config config = this.withConfig(MemoriesPlugin.MemoriesPluginConfig.CODEC); + private final List> providers = new ObjectArrayList<>(); + private final Map> allMemories = new Object2ObjectOpenHashMap<>(); + private ComponentType playerMemoriesComponentType; + @Nullable + private MemoriesPlugin.RecordedMemories recordedMemories; + private boolean hasInitializedMemories; + + public static MemoriesPlugin get() { + return instance; + } + + public MemoriesPlugin(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + ComponentRegistryProxy entityStoreRegistry = this.getEntityStoreRegistry(); + this.getCommandRegistry().registerCommand(new MemoriesCommand()); + OpenCustomUIInteraction.registerCustomPageSupplier(this, MemoriesPage.class, "Memories", new MemoriesPageSupplier()); + Window.CLIENT_REQUESTABLE_WINDOW_TYPES.put(WindowType.Memories, MemoriesWindow::new); + this.playerMemoriesComponentType = entityStoreRegistry.registerComponent(PlayerMemories.class, "PlayerMemories", PlayerMemories.CODEC); + NPCMemoryProvider npcMemoryProvider = new NPCMemoryProvider(); + this.registerMemoryProvider(npcMemoryProvider); + entityStoreRegistry.registerSystem(new NPCMemory.GatherMemoriesSystem(npcMemoryProvider.getCollectionRadius())); + + for (MemoryProvider provider : this.providers) { + BuilderCodec codec = (BuilderCodec)provider.getCodec(); + this.getCodecRegistry(Memory.CODEC).register(provider.getId(), codec.getInnerClass(), codec); + } + + this.getEventRegistry().register(AllNPCsLoadedEvent.class, event -> this.onAssetsLoad()); + entityStoreRegistry.registerSystem(new MemoriesPlugin.PlayerAddedSystem()); + this.getCodecRegistry(Interaction.CODEC).register("SetMemoriesCapacity", SetMemoriesCapacityInteraction.class, SetMemoriesCapacityInteraction.CODEC); + this.getCodecRegistry(GameplayConfig.PLUGIN_CODEC).register(MemoriesGameplayConfig.class, "Memories", MemoriesGameplayConfig.CODEC); + this.getCodecRegistry(Interaction.CODEC).register("MemoriesCondition", MemoriesConditionInteraction.class, MemoriesConditionInteraction.CODEC); + entityStoreRegistry.registerSystem(new TempleRespawnPlayersSystem()); + this.getCodecRegistry(GameplayConfig.PLUGIN_CODEC).register(ForgottenTempleConfig.class, "ForgottenTemple", ForgottenTempleConfig.CODEC); + } + + @Override + protected void start() { + try { + Path path = Constants.UNIVERSE_PATH.resolve("memories.json"); + if (Files.exists(path)) { + this.recordedMemories = RawJsonReader.readSync(path, MemoriesPlugin.RecordedMemories.CODEC, this.getLogger()); + } else { + this.recordedMemories = new MemoriesPlugin.RecordedMemories(); + } + } catch (IOException var2) { + throw new RuntimeException(var2); + } + + this.hasInitializedMemories = true; + this.onAssetsLoad(); + } + + @Override + protected void shutdown() { + this.recordedMemories.lock.readLock().lock(); + + try { + BsonUtil.writeSync(Constants.UNIVERSE_PATH.resolve("memories.json"), MemoriesPlugin.RecordedMemories.CODEC, this.recordedMemories, this.getLogger()); + } catch (IOException var5) { + throw new RuntimeException(var5); + } finally { + this.recordedMemories.lock.readLock().unlock(); + } + } + + private void onAssetsLoad() { + if (this.hasInitializedMemories) { + this.allMemories.clear(); + + for (MemoryProvider provider : this.providers) { + for (Entry> entry : provider.getAllMemories().entrySet()) { + this.allMemories.computeIfAbsent(entry.getKey(), k -> new HashSet<>()).addAll(entry.getValue()); + } + } + } + } + + public MemoriesPlugin.MemoriesPluginConfig getConfig() { + return this.config.get(); + } + + public ComponentType getPlayerMemoriesComponentType() { + return this.playerMemoriesComponentType; + } + + public void registerMemoryProvider(MemoryProvider memoryProvider) { + this.providers.add(memoryProvider); + } + + public Map> getAllMemories() { + return this.allMemories; + } + + public int getMemoriesLevel(@Nonnull GameplayConfig gameplayConfig) { + MemoriesGameplayConfig config = MemoriesGameplayConfig.get(gameplayConfig); + int memoriesLevel = 1; + if (config == null) { + return memoriesLevel; + } else { + int recordedMemoriesCount = this.getRecordedMemories().size(); + int[] memoriesAmountPerLevel = config.getMemoriesAmountPerLevel(); + + for (int i = 0; i < memoriesAmountPerLevel.length && recordedMemoriesCount >= memoriesAmountPerLevel[i]; i++) { + memoriesLevel += i + 1; + } + + return memoriesLevel; + } + } + + public int getMemoriesForNextLevel(@Nonnull GameplayConfig gameplayConfig) { + MemoriesGameplayConfig memoriesConfig = MemoriesGameplayConfig.get(gameplayConfig); + if (memoriesConfig == null) { + return -1; + } else { + int memoriesLevel = this.getMemoriesLevel(gameplayConfig); + int[] memoriesAmountPerLevel = memoriesConfig.getMemoriesAmountPerLevel(); + if (memoriesLevel >= memoriesAmountPerLevel.length) { + return -1; + } else { + int recordedMemoriesCount = this.getRecordedMemories().size(); + return memoriesAmountPerLevel[memoriesLevel] - recordedMemoriesCount; + } + } + } + + public boolean hasRecordedMemory(Memory memory) { + this.recordedMemories.lock.readLock().lock(); + + boolean var2; + try { + var2 = this.recordedMemories.memories.contains(memory); + } finally { + this.recordedMemories.lock.readLock().unlock(); + } + + return var2; + } + + public boolean recordPlayerMemories(@Nonnull PlayerMemories playerMemories) { + this.recordedMemories.lock.writeLock().lock(); + + try { + if (playerMemories.takeMemories(this.recordedMemories.memories)) { + BsonUtil.writeSync(Constants.UNIVERSE_PATH.resolve("memories.json"), MemoriesPlugin.RecordedMemories.CODEC, this.recordedMemories, this.getLogger()); + return true; + } + } catch (IOException var6) { + throw new RuntimeException(var6); + } finally { + this.recordedMemories.lock.writeLock().unlock(); + } + + return false; + } + + @Nonnull + public Set getRecordedMemories() { + this.recordedMemories.lock.readLock().lock(); + + HashSet var1; + try { + var1 = new HashSet<>(this.recordedMemories.memories); + } finally { + this.recordedMemories.lock.readLock().unlock(); + } + + return var1; + } + + public void clearRecordedMemories() { + this.recordedMemories.lock.writeLock().lock(); + + try { + this.recordedMemories.memories.clear(); + BsonUtil.writeSync(Constants.UNIVERSE_PATH.resolve("memories.json"), MemoriesPlugin.RecordedMemories.CODEC, this.recordedMemories, this.getLogger()); + } catch (IOException var5) { + throw new RuntimeException(var5); + } finally { + this.recordedMemories.lock.writeLock().unlock(); + } + } + + public void recordAllMemories() { + this.recordedMemories.lock.writeLock().lock(); + + try { + for (Entry> entry : this.allMemories.entrySet()) { + this.recordedMemories.memories.addAll(entry.getValue()); + } + + BsonUtil.writeSync(Constants.UNIVERSE_PATH.resolve("memories.json"), MemoriesPlugin.RecordedMemories.CODEC, this.recordedMemories, this.getLogger()); + } catch (IOException var6) { + throw new RuntimeException(var6); + } finally { + this.recordedMemories.lock.writeLock().unlock(); + } + } + + public static class MemoriesPluginConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder( + MemoriesPlugin.MemoriesPluginConfig.class, MemoriesPlugin.MemoriesPluginConfig::new + ) + .append( + new KeyedCodec<>("CollectionRadius", new Object2DoubleMapCodec<>(Codec.STRING, Object2DoubleOpenHashMap::new)), + (config, map) -> config.collectionRadius = map, + config -> config.collectionRadius + ) + .add() + .build(); + private Object2DoubleMap collectionRadius; + + public MemoriesPluginConfig() { + } + + @Nonnull + public Object2DoubleMap getCollectionRadius() { + return (Object2DoubleMap)(this.collectionRadius != null ? this.collectionRadius : Object2DoubleMaps.EMPTY_MAP); + } + } + + public static class PlayerAddedSystem extends RefSystem { + @Nonnull + private final Set> dependencies = Set.of(new SystemDependency<>(Order.AFTER, PlayerSystems.PlayerSpawnedSystem.class)); + @Nonnull + private final Query query = Query.and(Player.getComponentType(), PlayerRef.getComponentType()); + + public PlayerAddedSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + PlayerMemories playerMemoriesComponent = store.getComponent(ref, PlayerMemories.getComponentType()); + boolean isFeatureUnlockedByPlayer = playerMemoriesComponent != null; + PacketHandler playerConnection = playerRefComponent.getPacketHandler(); + playerConnection.writeNoCache(new UpdateMemoriesFeatureStatus(isFeatureUnlockedByPlayer)); + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + } + + private static class RecordedMemories { + public static final BuilderCodec CODEC = BuilderCodec.builder( + MemoriesPlugin.RecordedMemories.class, MemoriesPlugin.RecordedMemories::new + ) + .append(new KeyedCodec<>("Memories", new ArrayCodec<>(Memory.CODEC, Memory[]::new)), (recordedMemories, memories) -> { + if (memories != null) { + Collections.addAll(recordedMemories.memories, memories); + } + }, recordedMemories -> recordedMemories.memories.toArray(Memory[]::new)) + .add() + .build(); + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final Set memories = new HashSet<>(); + + private RecordedMemories() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/commands/MemoriesCapacityCommand.java b/src/com/hypixel/hytale/builtin/adventure/memories/commands/MemoriesCapacityCommand.java new file mode 100644 index 0000000..8cce936 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/commands/MemoriesCapacityCommand.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.builtin.adventure.memories.commands; + +import com.hypixel.hytale.builtin.adventure.memories.component.PlayerMemories; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.player.UpdateMemoriesFeatureStatus; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class MemoriesCapacityCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg capacityArg = this.withRequiredArg("capacity", "server.commands.memories.capacity.capacity.desc", ArgTypes.INTEGER); + + public MemoriesCapacityCommand() { + super("capacity", "server.commands.memories.capacity.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Integer capacity = this.capacityArg.get(context); + PacketHandler playerConnection = playerRef.getPacketHandler(); + if (capacity <= 0) { + store.tryRemoveComponent(ref, PlayerMemories.getComponentType()); + context.sendMessage(Message.translation("server.commands.memories.capacity.success").param("capacity", 0)); + playerConnection.writeNoCache(new UpdateMemoriesFeatureStatus(false)); + } else { + PlayerMemories playerMemories = store.ensureAndGetComponent(ref, PlayerMemories.getComponentType()); + playerMemories.setMemoriesCapacity(capacity); + context.sendMessage(Message.translation("server.commands.memories.capacity.success").param("capacity", capacity)); + playerConnection.writeNoCache(new UpdateMemoriesFeatureStatus(true)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/commands/MemoriesClearCommand.java b/src/com/hypixel/hytale/builtin/adventure/memories/commands/MemoriesClearCommand.java new file mode 100644 index 0000000..d1b7cbb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/commands/MemoriesClearCommand.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.builtin.adventure.memories.commands; + +import com.hypixel.hytale.builtin.adventure.memories.MemoriesPlugin; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import javax.annotation.Nonnull; + +public class MemoriesClearCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_MEMORIES_CLEAR_SUCCESS = Message.translation("server.commands.memories.clear.success"); + + public MemoriesClearCommand() { + super("clear", "server.commands.memories.clear.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + MemoriesPlugin.get().clearRecordedMemories(); + context.sendMessage(MESSAGE_COMMANDS_MEMORIES_CLEAR_SUCCESS); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/commands/MemoriesCommand.java b/src/com/hypixel/hytale/builtin/adventure/memories/commands/MemoriesCommand.java new file mode 100644 index 0000000..dd661f7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/commands/MemoriesCommand.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.builtin.adventure.memories.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class MemoriesCommand extends AbstractCommandCollection { + public MemoriesCommand() { + super("memories", "server.commands.memories.desc"); + this.addSubCommand(new MemoriesClearCommand()); + this.addSubCommand(new MemoriesCapacityCommand()); + this.addSubCommand(new MemoriesLevelCommand()); + this.addSubCommand(new MemoriesUnlockCommand()); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/commands/MemoriesLevelCommand.java b/src/com/hypixel/hytale/builtin/adventure/memories/commands/MemoriesLevelCommand.java new file mode 100644 index 0000000..55db72d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/commands/MemoriesLevelCommand.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.builtin.adventure.memories.commands; + +import com.hypixel.hytale.builtin.adventure.memories.MemoriesPlugin; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class MemoriesLevelCommand extends AbstractWorldCommand { + public MemoriesLevelCommand() { + super("level", "server.commands.memories.level.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + int level = MemoriesPlugin.get().getMemoriesLevel(world.getGameplayConfig()); + context.sendMessage(Message.translation("server.commands.memories.level.success").param("level", level)); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/commands/MemoriesUnlockCommand.java b/src/com/hypixel/hytale/builtin/adventure/memories/commands/MemoriesUnlockCommand.java new file mode 100644 index 0000000..c425489 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/commands/MemoriesUnlockCommand.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.builtin.adventure.memories.commands; + +import com.hypixel.hytale.builtin.adventure.memories.MemoriesPlugin; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class MemoriesUnlockCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_MEMORIES_UNLOCK_ALL_SUCCESS = Message.translation("server.commands.memories.unlockAll.success"); + + public MemoriesUnlockCommand() { + super("unlockAll", "server.commands.memories.unlockAll.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + MemoriesPlugin.get().recordAllMemories(); + context.sendMessage(MESSAGE_COMMANDS_MEMORIES_UNLOCK_ALL_SUCCESS); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/component/PlayerMemories.java b/src/com/hypixel/hytale/builtin/adventure/memories/component/PlayerMemories.java new file mode 100644 index 0000000..04e06da --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/component/PlayerMemories.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.builtin.adventure.memories.component; + +import com.hypixel.hytale.builtin.adventure.memories.MemoriesPlugin; +import com.hypixel.hytale.builtin.adventure.memories.memories.Memory; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.annotation.Nonnull; + +public class PlayerMemories implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(PlayerMemories.class, PlayerMemories::new) + .append( + new KeyedCodec<>("Capacity", Codec.INTEGER), + (playerMemories, integer) -> playerMemories.memoriesCapacity = integer, + playerMemories -> playerMemories.memoriesCapacity + ) + .add() + .append(new KeyedCodec<>("Memories", new ArrayCodec<>(Memory.CODEC, Memory[]::new)), (playerMemories, memories) -> { + if (memories != null) { + Collections.addAll(playerMemories.memories, memories); + } + }, playerMemories -> playerMemories.memories.toArray(Memory[]::new)) + .add() + .build(); + private final Set memories = new LinkedHashSet<>(); + private int memoriesCapacity; + + public PlayerMemories() { + } + + public static ComponentType getComponentType() { + return MemoriesPlugin.get().getPlayerMemoriesComponentType(); + } + + @Nonnull + @Override + public Component clone() { + PlayerMemories playerMemories = new PlayerMemories(); + playerMemories.memories.addAll(this.memories); + playerMemories.memoriesCapacity = this.memoriesCapacity; + return playerMemories; + } + + public int getMemoriesCapacity() { + return this.memoriesCapacity; + } + + public void setMemoriesCapacity(int memoriesCapacity) { + this.memoriesCapacity = memoriesCapacity; + } + + public boolean recordMemory(Memory memory) { + return this.memories.size() >= this.memoriesCapacity ? false : this.memories.add(memory); + } + + public boolean hasMemories() { + return !this.memories.isEmpty(); + } + + public boolean takeMemories(@Nonnull Set outMemories) { + boolean result = outMemories.addAll(this.memories); + this.memories.clear(); + return result; + } + + @Nonnull + public Set getRecordedMemories() { + return Collections.unmodifiableSet(this.memories); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/interactions/MemoriesConditionInteraction.java b/src/com/hypixel/hytale/builtin/adventure/memories/interactions/MemoriesConditionInteraction.java new file mode 100644 index 0000000..602952c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/interactions/MemoriesConditionInteraction.java @@ -0,0 +1,186 @@ +package com.hypixel.hytale.builtin.adventure.memories.interactions; + +import com.hypixel.hytale.builtin.adventure.memories.MemoriesPlugin; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.Int2ObjectMapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.Collector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.CollectorTag; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.StringTag; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.Label; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.OperationsBuilder; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class MemoriesConditionInteraction extends Interaction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + MemoriesConditionInteraction.class, MemoriesConditionInteraction::new, ABSTRACT_CODEC + ) + .appendInherited( + new KeyedCodec<>("Next", new Int2ObjectMapCodec<>(Interaction.CHILD_ASSET_CODEC, Int2ObjectOpenHashMap::new)), + (o, v) -> o.next = v, + o -> o.next, + (o, p) -> o.next = p.next + ) + .documentation("The interaction to run if the player's memories level matches the key.") + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Failed", Interaction.CHILD_ASSET_CODEC), (o, v) -> o.failed = v, o -> o.failed, (o, p) -> o.failed = p.failed) + .documentation("The interaction to run if the player's memories level does not match any key.") + .add() + .afterDecode(o -> { + o.levelToLabel.defaultReturnValue(-1); + o.sortedKeys = o.next.keySet().toIntArray(); + Arrays.sort(o.sortedKeys); + o.levelToLabel.clear(); + + for (int i = 0; i < o.sortedKeys.length; i++) { + o.levelToLabel.put(o.sortedKeys[i], i); + } + }) + .build(); + @Nonnull + private static final StringTag TAG_FAILED = StringTag.of("Failed"); + @Nonnull + private Int2ObjectMap next = Int2ObjectMaps.emptyMap(); + private transient int[] sortedKeys; + @Nonnull + private final Int2IntOpenHashMap levelToLabel = new Int2IntOpenHashMap(); + @Nullable + private String failed; + + public MemoriesConditionInteraction() { + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @NonNullDecl InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + World world = commandBuffer.getExternalData().getWorld(); + int memoriesLevel = MemoriesPlugin.get().getMemoriesLevel(world.getGameplayConfig()); + context.getState().chainingIndex = memoriesLevel; + int labelIndex = this.levelToLabel.get(memoriesLevel); + if (labelIndex == -1) { + labelIndex = this.sortedKeys.length; + context.getState().state = InteractionState.Failed; + } else { + context.getState().state = InteractionState.Finished; + } + + context.jump(context.getLabel(labelIndex)); + } + + @Override + protected void simulateTick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @NonNullDecl InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + int memoriesLevel = context.getServerState().chainingIndex; + int labelIndex = this.levelToLabel.get(memoriesLevel); + if (labelIndex == -1) { + labelIndex = this.sortedKeys.length; + context.getState().state = InteractionState.Failed; + } else { + context.getState().state = InteractionState.Finished; + } + + context.jump(context.getLabel(labelIndex)); + } + + @Override + public void compile(@Nonnull OperationsBuilder builder) { + Label end = builder.createUnresolvedLabel(); + Label[] labels = new Label[this.next.size() + 1]; + + for (int i = 0; i < labels.length; i++) { + labels[i] = builder.createUnresolvedLabel(); + } + + builder.addOperation(this, labels); + builder.jump(end); + + for (int i = 0; i < this.sortedKeys.length; i++) { + int key = this.sortedKeys[i]; + builder.resolveLabel(labels[i]); + Interaction interaction = Interaction.getInteractionOrUnknown(this.next.get(key)); + interaction.compile(builder); + builder.jump(end); + } + + int failedIndex = this.sortedKeys.length; + builder.resolveLabel(labels[failedIndex]); + if (this.failed != null) { + Interaction interaction = Interaction.getInteractionOrUnknown(this.failed); + interaction.compile(builder); + } + + builder.resolveLabel(end); + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.Interaction generatePacket() { + return new com.hypixel.hytale.protocol.MemoriesConditionInteraction(); + } + + @Override + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.MemoriesConditionInteraction p = (com.hypixel.hytale.protocol.MemoriesConditionInteraction)packet; + p.memoriesNext = new Int2IntOpenHashMap(this.next.size()); + + for (Entry e : this.next.int2ObjectEntrySet()) { + p.memoriesNext.put(e.getIntKey(), Interaction.getInteractionIdOrUnknown(e.getValue())); + } + + p.failed = Interaction.getInteractionIdOrUnknown(this.failed); + } + + @Override + public boolean walk(@Nonnull Collector collector, @Nonnull InteractionContext context) { + if (this.next != null) { + for (Entry entry : this.next.int2ObjectEntrySet()) { + if (InteractionManager.walkInteraction(collector, context, new MemoriesConditionInteraction.MemoriesTag(entry.getIntKey()), entry.getValue())) { + return true; + } + } + } + + return this.failed != null ? InteractionManager.walkInteraction(collector, context, TAG_FAILED, this.failed) : false; + } + + @Override + public boolean needsRemoteSync() { + return false; + } + + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + private record MemoriesTag(int memoryLevel) implements CollectorTag { + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/interactions/SetMemoriesCapacityInteraction.java b/src/com/hypixel/hytale/builtin/adventure/memories/interactions/SetMemoriesCapacityInteraction.java new file mode 100644 index 0000000..c5a9fc3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/interactions/SetMemoriesCapacityInteraction.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.builtin.adventure.memories.interactions; + +import com.hypixel.hytale.builtin.adventure.memories.component.PlayerMemories; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.protocol.packets.player.UpdateMemoriesFeatureStatus; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.NotificationUtil; +import javax.annotation.Nonnull; + +public class SetMemoriesCapacityInteraction extends SimpleInstantInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SetMemoriesCapacityInteraction.class, SetMemoriesCapacityInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Sets how many memories a player can store.") + .appendInherited( + new KeyedCodec<>("Capacity", Codec.INTEGER), (i, s) -> i.capacity = s, i -> i.capacity, (i, parent) -> i.capacity = parent.capacity + ) + .documentation("Defines the amount of memories that a player can store.") + .add() + .build(); + private int capacity; + + public SetMemoriesCapacityInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + Ref ref = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + PlayerMemories memoriesComponent = commandBuffer.ensureAndGetComponent(ref, PlayerMemories.getComponentType()); + if (this.capacity <= memoriesComponent.getMemoriesCapacity()) { + context.getState().state = InteractionState.Failed; + } else { + int previousCapacity = memoriesComponent.getMemoriesCapacity(); + memoriesComponent.setMemoriesCapacity(this.capacity); + if (previousCapacity <= 0) { + PlayerRef playerRefComponent = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + PacketHandler playerConnection = playerRefComponent.getPacketHandler(); + playerConnection.writeNoCache(new UpdateMemoriesFeatureStatus(true)); + NotificationUtil.sendNotification( + playerConnection, Message.translation("server.memories.general.featureUnlockedNotification"), null, "NotificationIcons/MemoriesIcon.png" + ); + playerRefComponent.sendMessage(Message.translation("server.memories.general.featureUnlockedMessage")); + } + + context.getState().state = InteractionState.Finished; + } + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + public String toString() { + return super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/memories/Memory.java b/src/com/hypixel/hytale/builtin/adventure/memories/memories/Memory.java new file mode 100644 index 0000000..2b134c6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/memories/Memory.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.builtin.adventure.memories.memories; + +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.server.core.Message; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class Memory { + public static final CodecMapCodec CODEC = new CodecMapCodec<>(); + + public Memory() { + } + + public abstract String getId(); + + public abstract String getTitle(); + + public abstract Message getTooltipText(); + + @Nullable + public abstract String getIconPath(); + + public abstract Message getUndiscoveredTooltipText(); + + @Override + public boolean equals(@Nullable Object o) { + return this == o || o != null && this.getClass() == o.getClass(); + } + + @Override + public int hashCode() { + return this.getClass().hashCode(); + } + + @Nonnull + @Override + public String toString() { + return "Memory{}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/memories/MemoryProvider.java b/src/com/hypixel/hytale/builtin/adventure/memories/memories/MemoryProvider.java new file mode 100644 index 0000000..9058eeb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/memories/MemoryProvider.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.adventure.memories.memories; + +import com.hypixel.hytale.builtin.adventure.memories.MemoriesPlugin; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import java.util.Map; +import java.util.Set; + +public abstract class MemoryProvider { + private final String id; + private final BuilderCodec codec; + private final double defaultRadius; + + public MemoryProvider(String id, BuilderCodec codec, double defaultRadius) { + this.id = id; + this.codec = codec; + this.defaultRadius = defaultRadius; + } + + public String getId() { + return this.id; + } + + public BuilderCodec getCodec() { + return this.codec; + } + + public double getCollectionRadius() { + return MemoriesPlugin.get().getConfig().getCollectionRadius().getOrDefault(this.id, this.defaultRadius); + } + + public abstract Map> getAllMemories(); +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/memories/npc/NPCMemory.java b/src/com/hypixel/hytale/builtin/adventure/memories/memories/npc/NPCMemory.java new file mode 100644 index 0000000..3c044e3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/memories/npc/NPCMemory.java @@ -0,0 +1,317 @@ +package com.hypixel.hytale.builtin.adventure.memories.memories.npc; + +import com.hypixel.hytale.builtin.adventure.memories.MemoriesGameplayConfig; +import com.hypixel.hytale.builtin.adventure.memories.MemoriesPlugin; +import com.hypixel.hytale.builtin.adventure.memories.component.PlayerMemories; +import com.hypixel.hytale.builtin.adventure.memories.memories.Memory; +import com.hypixel.hytale.builtin.instances.config.InstanceDiscoveryConfig; +import com.hypixel.hytale.builtin.instances.config.InstanceWorldConfig; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.modules.entity.item.PickupItemComponent; +import com.hypixel.hytale.server.core.modules.i18n.I18nModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.NotificationUtil; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.worldgen.chunk.ChunkGenerator; +import com.hypixel.hytale.server.worldgen.chunk.ZoneBiomeResult; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NPCMemory extends Memory { + @Nonnull + public static final String ID = "NPC"; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(NPCMemory.class, NPCMemory::new) + .append(new KeyedCodec<>("NPCRole", Codec.STRING), (npcMemory, s) -> npcMemory.npcRole = s, npcMemory -> npcMemory.npcRole) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("TranslationKey", Codec.STRING), (npcMemory, s) -> npcMemory.memoryTitleKey = s, npcMemory -> npcMemory.memoryTitleKey) + .add() + .append( + new KeyedCodec<>("IsMemoriesNameOverridden", Codec.BOOLEAN), + (npcMemory, aBoolean) -> npcMemory.isMemoriesNameOverridden = aBoolean, + npcMemory -> npcMemory.isMemoriesNameOverridden + ) + .add() + .append( + new KeyedCodec<>("CapturedTimestamp", Codec.LONG), + (npcMemory, aDouble) -> npcMemory.capturedTimestamp = aDouble, + npcMemory -> npcMemory.capturedTimestamp + ) + .add() + .append( + new KeyedCodec<>("FoundLocationZoneNameKey", Codec.STRING), + (npcMemory, s) -> npcMemory.foundLocationZoneNameKey = s, + npcMemory -> npcMemory.foundLocationZoneNameKey + ) + .add() + .append( + new KeyedCodec<>("FoundLocationNameKey", Codec.STRING), + (npcMemory, s) -> npcMemory.foundLocationGeneralNameKey = s, + npcMemory -> npcMemory.foundLocationGeneralNameKey + ) + .add() + .afterDecode(NPCMemory::processConfig) + .build(); + private String npcRole; + private boolean isMemoriesNameOverridden; + private long capturedTimestamp; + private String foundLocationZoneNameKey; + private String foundLocationGeneralNameKey; + private String memoryTitleKey; + + private NPCMemory() { + } + + public NPCMemory(@Nonnull String npcRole, @Nonnull String nameTranslationKey, boolean isMemoriesNameOverridden) { + this.npcRole = npcRole; + this.memoryTitleKey = nameTranslationKey; + this.isMemoriesNameOverridden = isMemoriesNameOverridden; + this.processConfig(); + } + + @Override + public String getId() { + return this.npcRole; + } + + @Nonnull + @Override + public String getTitle() { + return this.memoryTitleKey; + } + + @Nonnull + @Override + public Message getTooltipText() { + return Message.translation("server.memories.general.discovered.tooltipText"); + } + + @Nullable + @Override + public String getIconPath() { + return "UI/Custom/Pages/Memories/npcs/" + this.npcRole + ".png"; + } + + public void processConfig() { + if (this.isMemoriesNameOverridden) { + this.memoryTitleKey = "server.npcRoles." + this.npcRole + ".name"; + if (I18nModule.get().getMessage("en-US", this.memoryTitleKey) == null) { + this.memoryTitleKey = "server.memories.names." + this.npcRole; + } + } + + if (this.memoryTitleKey == null || this.memoryTitleKey.isEmpty()) { + this.memoryTitleKey = "server.npcRoles." + this.npcRole + ".name"; + } + } + + @Nonnull + @Override + public Message getUndiscoveredTooltipText() { + return Message.translation("server.memories.general.undiscovered.tooltipText"); + } + + @Nonnull + public String getNpcRole() { + return this.npcRole; + } + + public long getCapturedTimestamp() { + return this.capturedTimestamp; + } + + public String getFoundLocationZoneNameKey() { + return this.foundLocationZoneNameKey; + } + + public Message getLocationMessage() { + if (this.foundLocationGeneralNameKey != null) { + return Message.translation(this.foundLocationGeneralNameKey); + } else { + return this.foundLocationZoneNameKey != null ? Message.translation("server.map.region." + this.foundLocationZoneNameKey) : Message.raw("???"); + } + } + + @Override + public boolean equals(Object o) { + if (o == null || this.getClass() != o.getClass()) { + return false; + } else if (!super.equals(o)) { + return false; + } else { + NPCMemory npcMemory = (NPCMemory)o; + return this.isMemoriesNameOverridden == npcMemory.isMemoriesNameOverridden && Objects.equals(this.npcRole, npcMemory.npcRole); + } + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + Objects.hashCode(this.npcRole); + return 31 * result + Boolean.hashCode(this.isMemoriesNameOverridden); + } + + @Override + public String toString() { + return "NPCMemory{npcRole='" + + this.npcRole + + "', isMemoriesNameOverride=" + + this.isMemoriesNameOverridden + + "', capturedTimestamp=" + + this.capturedTimestamp + + "', foundLocationZoneNameKey='" + + this.foundLocationZoneNameKey + + "}"; + } + + public static class GatherMemoriesSystem extends EntityTickingSystem { + @Nonnull + public static final Query QUERY = Query.and( + TransformComponent.getComponentType(), Player.getComponentType(), PlayerMemories.getComponentType() + ); + private final double radius; + + public GatherMemoriesSystem(double radius) { + this.radius = radius; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Player playerComponent = archetypeChunk.getComponent(index, Player.getComponentType()); + + assert playerComponent != null; + + if (playerComponent.getGameMode() == GameMode.Adventure) { + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + SpatialResource, EntityStore> npcSpatialResource = store.getResource(NPCPlugin.get().getNpcSpatialResource()); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + npcSpatialResource.getSpatialStructure().collect(position, this.radius, results); + if (!results.isEmpty()) { + PlayerRef playerRefComponent = archetypeChunk.getComponent(index, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + MemoriesPlugin memoriesPlugin = MemoriesPlugin.get(); + PlayerMemories playerMemoriesComponent = archetypeChunk.getComponent(index, PlayerMemories.getComponentType()); + + assert playerMemoriesComponent != null; + + NPCMemory temp = new NPCMemory(); + World world = commandBuffer.getExternalData().getWorld(); + String foundLocationZoneNameKey = findLocationZoneName(world, position); + + for (Ref npcRef : results) { + NPCEntity npcComponent = commandBuffer.getComponent(npcRef, NPCEntity.getComponentType()); + if (npcComponent != null) { + Role role = npcComponent.getRole(); + + assert role != null; + + if (role.isMemory()) { + temp.isMemoriesNameOverridden = role.isMemoriesNameOverriden(); + temp.npcRole = temp.isMemoriesNameOverridden ? role.getMemoriesNameOverride() : npcComponent.getRoleName(); + temp.memoryTitleKey = role.getNameTranslationKey(); + temp.capturedTimestamp = System.currentTimeMillis(); + temp.foundLocationGeneralNameKey = foundLocationZoneNameKey; + if (!memoriesPlugin.hasRecordedMemory(temp)) { + temp.processConfig(); + if (playerMemoriesComponent.recordMemory(temp)) { + NotificationUtil.sendNotification( + playerRefComponent.getPacketHandler(), + Message.translation("server.memories.general.collected").param("memoryTitle", Message.translation(temp.getTitle())), + null, + "NotificationIcons/MemoriesIcon.png" + ); + temp = new NPCMemory(); + TransformComponent npcTransformComponent = commandBuffer.getComponent(npcRef, TransformComponent.getComponentType()); + + assert npcTransformComponent != null; + + MemoriesGameplayConfig memoriesGameplayConfig = MemoriesGameplayConfig.get(store.getExternalData().getWorld().getGameplayConfig()); + if (memoriesGameplayConfig != null) { + ItemStack memoryItemStack = new ItemStack(memoriesGameplayConfig.getMemoriesCatchItemId()); + Vector3d memoryItemHolderPosition = npcTransformComponent.getPosition().clone(); + BoundingBox boundingBox = commandBuffer.getComponent(npcRef, BoundingBox.getComponentType()); + if (boundingBox != null) { + memoryItemHolderPosition.y = memoryItemHolderPosition.y + boundingBox.getBoundingBox().middleY(); + } + + Holder memoryItemHolder = ItemComponent.generatePickedUpItem( + memoryItemStack, memoryItemHolderPosition, commandBuffer, playerRefComponent.getReference() + ); + float memoryCatchItemLifetimeS = 0.62F; + memoryItemHolder.getComponent(PickupItemComponent.getComponentType()).setInitialLifeTime(memoryCatchItemLifetimeS); + commandBuffer.addEntity(memoryItemHolder, AddReason.SPAWN); + } + } + } + } + } + } + } + } + } + + private static String findLocationZoneName(World world, Vector3d position) { + if (world.getChunkStore().getGenerator() instanceof ChunkGenerator generator) { + int seed = (int)world.getWorldConfig().getSeed(); + ZoneBiomeResult result = generator.getZoneBiomeResultAt(seed, MathUtil.floor(position.x), MathUtil.floor(position.z)); + return "server.map.region." + result.getZoneResult().getZone().name(); + } else { + InstanceWorldConfig instanceConfig = world.getWorldConfig().getPluginConfig().get(InstanceWorldConfig.class); + if (instanceConfig != null) { + InstanceDiscoveryConfig discovery = instanceConfig.getDiscovery(); + if (discovery != null && discovery.getTitleKey() != null) { + return discovery.getTitleKey(); + } + } + + return "???"; + } + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/memories/npc/NPCMemoryProvider.java b/src/com/hypixel/hytale/builtin/adventure/memories/memories/npc/NPCMemoryProvider.java new file mode 100644 index 0000000..234ea7c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/memories/npc/NPCMemoryProvider.java @@ -0,0 +1,108 @@ +package com.hypixel.hytale.builtin.adventure.memories.memories.npc; + +import com.hypixel.hytale.builtin.adventure.memories.MemoriesPlugin; +import com.hypixel.hytale.builtin.adventure.memories.memories.Memory; +import com.hypixel.hytale.builtin.adventure.memories.memories.MemoryProvider; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderInfo; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.spawning.ISpawnableWithModel; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class NPCMemoryProvider extends MemoryProvider { + public static final double DEFAULT_RADIUS = 10.0; + + public NPCMemoryProvider() { + super("NPC", NPCMemory.CODEC, 10.0); + } + + @Nonnull + @Override + public Map> getAllMemories() { + Map> allMemories = new Object2ObjectOpenHashMap<>(); + Int2ObjectMap allBuilders = NPCPlugin.get().getBuilderManager().getAllBuilders(); + + for (BuilderInfo builderInfo : allBuilders.values()) { + try { + Builder builder = builderInfo.getBuilder(); + if (builder.isSpawnable() && !builder.isDeprecated() && builderInfo.isValid() && isMemory(builder)) { + String category = getCategory(builder); + if (category != null) { + String memoriesNameOverride = getMemoriesNameOverride(builder); + String translationKey = getNPCNameTranslationKey(builder); + NPCMemory memory; + if (memoriesNameOverride != null && !memoriesNameOverride.isEmpty()) { + memory = new NPCMemory(memoriesNameOverride, translationKey, true); + } else { + memory = new NPCMemory(builderInfo.getKeyName(), translationKey, false); + } + + allMemories.computeIfAbsent(category, s -> new HashSet<>()).add(memory); + } + } + } catch (SkipSentryException var10) { + MemoriesPlugin.get().getLogger().at(Level.SEVERE).log(var10.getMessage()); + } + } + + return allMemories; + } + + @Nullable + private static String getCategory(@Nonnull Builder builder) { + if (builder instanceof ISpawnableWithModel spawnableWithModel) { + ExecutionContext executionContext = new ExecutionContext(); + executionContext.setScope(spawnableWithModel.createExecutionScope()); + Scope modifierScope = spawnableWithModel.createModifierScope(executionContext); + return spawnableWithModel.getMemoriesCategory(executionContext, modifierScope); + } else { + return "Other"; + } + } + + private static boolean isMemory(@Nonnull Builder builder) { + if (builder instanceof ISpawnableWithModel spawnableWithModel) { + ExecutionContext executionContext = new ExecutionContext(); + executionContext.setScope(spawnableWithModel.createExecutionScope()); + Scope modifierScope = spawnableWithModel.createModifierScope(executionContext); + return spawnableWithModel.isMemory(executionContext, modifierScope); + } else { + return false; + } + } + + @NullableDecl + private static String getMemoriesNameOverride(@Nonnull Builder builder) { + if (builder instanceof ISpawnableWithModel spawnableWithModel) { + ExecutionContext executionContext = new ExecutionContext(); + executionContext.setScope(spawnableWithModel.createExecutionScope()); + Scope modifierScope = spawnableWithModel.createModifierScope(executionContext); + return spawnableWithModel.getMemoriesNameOverride(executionContext, modifierScope); + } else { + return null; + } + } + + @Nonnull + private static String getNPCNameTranslationKey(@Nonnull Builder builder) { + if (builder instanceof ISpawnableWithModel spawnableWithModel) { + ExecutionContext executionContext = new ExecutionContext(); + executionContext.setScope(spawnableWithModel.createExecutionScope()); + Scope modifierScope = spawnableWithModel.createModifierScope(executionContext); + return spawnableWithModel.getNameTranslationKey(executionContext, modifierScope); + } else { + throw new SkipSentryException(new IllegalStateException("Cannot get translation key for a non spawnable NPC role!")); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/page/MemoriesPage.java b/src/com/hypixel/hytale/builtin/adventure/memories/page/MemoriesPage.java new file mode 100644 index 0000000..b88675e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/page/MemoriesPage.java @@ -0,0 +1,402 @@ +package com.hypixel.hytale.builtin.adventure.memories.page; + +import com.hypixel.hytale.builtin.adventure.memories.MemoriesGameplayConfig; +import com.hypixel.hytale.builtin.adventure.memories.MemoriesPlugin; +import com.hypixel.hytale.builtin.adventure.memories.component.PlayerMemories; +import com.hypixel.hytale.builtin.adventure.memories.memories.Memory; +import com.hypixel.hytale.builtin.adventure.memories.memories.npc.NPCMemory; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.ui.Anchor; +import com.hypixel.hytale.server.core.ui.Value; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.ParticleUtil; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MemoriesPage extends InteractiveCustomUIPage { + @Nullable + private String currentCategory; + @Nullable + private Memory selectedMemory; + private Vector3d recordMemoriesParticlesPosition; + + public MemoriesPage(@Nonnull PlayerRef playerRef, BlockPosition blockPosition) { + super(playerRef, CustomPageLifetime.CanDismissOrCloseThroughInteraction, MemoriesPage.PageEventData.CODEC); + this.recordMemoriesParticlesPosition = new Vector3d(blockPosition.x, blockPosition.y, blockPosition.z); + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + MemoriesPlugin memoriesPlugin = MemoriesPlugin.get(); + if (this.currentCategory == null) { + commandBuilder.append("Pages/Memories/MemoriesCategoryPanel.ui"); + Map> allMemories = memoriesPlugin.getAllMemories(); + Set recordedMemories = memoriesPlugin.getRecordedMemories(); + int totalMemories = 0; + + for (Set value : allMemories.values()) { + totalMemories += value.size(); + } + + commandBuilder.set("#MemoriesProgressBar.Value", (float)recordedMemories.size() / totalMemories); + commandBuilder.set("#MemoriesProgressBarTexture.Value", (float)recordedMemories.size() / totalMemories); + commandBuilder.set("#TotalCollected.Text", String.valueOf(recordedMemories.size())); + commandBuilder.set("#MemoriesTotal.Text", String.valueOf(totalMemories)); + GameplayConfig gameplayConfig = store.getExternalData().getWorld().getGameplayConfig(); + PlayerMemories playerMemories = store.getComponent(ref, PlayerMemories.getComponentType()); + int i = 0; + + for (Entry> entry : allMemories.entrySet()) { + String category = entry.getKey(); + Set memoriesInCategory = entry.getValue(); + String selector = "#IconList[" + i++ + "] "; + int recordedMemoriesCount = 0; + + for (Memory memory : memoriesInCategory) { + if (recordedMemories.contains(memory)) { + recordedMemoriesCount++; + } + } + + commandBuilder.append("#IconList", "Pages/Memories/MemoriesCategory.ui"); + commandBuilder.set(selector + "#Button.Text", Message.translation("server.memories.categories." + category + ".title")); + commandBuilder.set(selector + "#CurrentMemoryCountNotComplete.Text", String.valueOf(recordedMemoriesCount)); + commandBuilder.set(selector + "#CurrentMemoryCountComplete.Text", String.valueOf(recordedMemoriesCount)); + commandBuilder.set(selector + "#TotalMemoryCountNotComplete.Text", String.valueOf(memoriesInCategory.size())); + commandBuilder.set(selector + "#TotalMemoryCountComplete.Text", String.valueOf(memoriesInCategory.size())); + boolean isCategoryComplete = recordedMemoriesCount == memoriesInCategory.size(); + if (isCategoryComplete) { + commandBuilder.set(selector + "#CategoryIcon.Background", "Pages/Memories/categories/" + category + "Complete.png"); + commandBuilder.set(selector + "#CompleteCategoryBackground.Visible", true); + commandBuilder.set(selector + "#CompleteCategoryCounter.Visible", true); + } else { + commandBuilder.set(selector + "#CategoryIcon.Background", "Pages/Memories/categories/" + category + ".png"); + commandBuilder.set(selector + "#NotCompleteCategoryCounter.Visible", true); + } + + if (playerMemories != null) { + Set newMemories = playerMemories.getRecordedMemories(); + + for (Memory memoryx : memoriesInCategory) { + if (newMemories.contains(memoryx)) { + commandBuilder.set(selector + "#NewMemoryIndicator.Visible", true); + break; + } + } + } + + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + selector + "#Button", + new EventData().append("Action", MemoriesPage.PageAction.ViewCategory).append("Category", category) + ); + } + + commandBuilder.set("#RecordButton.Visible", true); + commandBuilder.set("#RecordButton.Disabled", playerMemories == null || !playerMemories.hasMemories()); + buildChestMarkers(commandBuilder, gameplayConfig, totalMemories); + if (playerMemories != null && playerMemories.hasMemories()) { + commandBuilder.set( + "#RecordButton.Text", Message.translation("server.memories.general.recordNum").param("count", playerMemories.getRecordedMemories().size()) + ); + } + + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#RecordButton", new EventData().append("Action", MemoriesPage.PageAction.Record)); + } else { + commandBuilder.append("Pages/Memories/MemoriesPanel.ui"); + Set memoriesSet = memoriesPlugin.getAllMemories().get(this.currentCategory); + ObjectArrayList memories = new ObjectArrayList<>(memoriesSet); + memories.sort(Comparator.comparing(Memory::getTitle)); + Set recordedMemories = memoriesPlugin.getRecordedMemories(); + int recordedMemoriesCount = 0; + + for (Memory memoryxx : memories) { + if (recordedMemories.contains(memoryxx)) { + recordedMemoriesCount++; + } + } + + commandBuilder.set("#CategoryTitle.Text", Message.translation("server.memories.categories." + this.currentCategory + ".title")); + commandBuilder.set("#CategoryCount.Text", recordedMemoriesCount + "/" + memories.size()); + + for (int i = 0; i < memories.size(); i++) { + Memory memoryxxx = memories.get(i); + String selector = "#IconList[" + i + "] "; + commandBuilder.append("#IconList", "Pages/Memories/Memory.ui"); + boolean isDiscovered = recordedMemories.contains(memoryxxx); + boolean isSelected = this.selectedMemory != null && this.selectedMemory.equals(memoryxxx); + String buttonSelector = isSelected ? selector + "#ButtonSelected" : selector + "#ButtonNotSelected"; + if (isDiscovered) { + commandBuilder.set(buttonSelector + ".Visible", true); + commandBuilder.set(buttonSelector + ".TooltipText", memoryxxx.getTooltipText()); + commandBuilder.setNull(buttonSelector + ".Background"); + String iconPath = memoryxxx.getIconPath(); + if (iconPath != null && !iconPath.isEmpty()) { + commandBuilder.set(selector + "#Icon.AssetPath", iconPath); + } + + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + buttonSelector, + new EventData().append("Action", MemoriesPage.PageAction.SelectMemory).append("MemoryId", memoryxxx.getId()) + ); + } else { + commandBuilder.set(selector + "#EmptyBackground.Visible", true); + } + } + + if (this.selectedMemory != null && recordedMemories.contains(this.selectedMemory)) { + updateMemoryDetailsPanel(commandBuilder, this.selectedMemory); + } + + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#BackButton", new EventData().append("Action", MemoriesPage.PageAction.Back)); + } + } + + private static void buildChestMarkers(@Nonnull UICommandBuilder commandBuilder, @Nonnull GameplayConfig gameplayConfig, int totalMemories) { + MemoriesGameplayConfig memoriesConfig = MemoriesGameplayConfig.get(gameplayConfig); + if (memoriesConfig != null) { + int[] memoriesAmountPerLevel = memoriesConfig.getMemoriesAmountPerLevel(); + if (memoriesAmountPerLevel != null && memoriesAmountPerLevel.length > 1) { + MemoriesPlugin memoriesPlugin = MemoriesPlugin.get(); + int recordedMemoriesCount = memoriesPlugin.getRecordedMemories().size(); + int PROGRESS_BAR_PADDING = 18; + int PROGRESS_BAR_WIDTH = 1018; + int CHEST_POSITION_AREA = 1000; + + for (int i = 0; i < memoriesAmountPerLevel.length; i++) { + int memoryAmount = memoriesAmountPerLevel[i]; + boolean hasReachedLevel = recordedMemoriesCount >= memoryAmount; + String selector = "#ChestMarkers[" + i + "]"; + Anchor anchor = new Anchor(); + int left = memoryAmount * 1000 / totalMemories; + commandBuilder.append("#ChestMarkers", "Pages/Memories/ChestMarker.ui"); + anchor.setLeft(Value.of(left)); + commandBuilder.setObject(selector + ".Anchor", anchor); + Message rewardsMessage = Message.translation("server.memories.general.chestActive.level" + (i + 1) + ".rewards"); + if (hasReachedLevel) { + Message memoriesUnlockedMessage = Message.translation("server.memories.general.chestActive.tooltipText").param("count", memoryAmount); + Message activeTooltipMessage = memoriesUnlockedMessage.insert("\n").insert(rewardsMessage); + commandBuilder.set(selector + " #Arrow.Visible", true); + commandBuilder.set(selector + " #ChestActive.Visible", true); + commandBuilder.set(selector + " #ChestActive.TooltipTextSpans", activeTooltipMessage); + } else { + commandBuilder.set(selector + " #ChestDisabled.Visible", true); + Message memoriesToUnlockMessage = Message.translation("server.memories.general.chestLocked.tooltipText").param("count", memoryAmount); + Message disabledTooltipMessage = memoriesToUnlockMessage.insert("\n").insert(rewardsMessage); + commandBuilder.set(selector + " #ChestDisabled.TooltipTextSpans", disabledTooltipMessage); + } + } + } + } + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull MemoriesPage.PageEventData data) { + Player player = store.getComponent(ref, Player.getComponentType()); + + assert player != null; + + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + switch (data.action) { + case Record: + PlayerMemories playerMemories = store.getComponent(ref, PlayerMemories.getComponentType()); + if (playerMemories == null) { + this.sendUpdate(); + return; + } + + if (!MemoriesPlugin.get().recordPlayerMemories(playerMemories)) { + this.rebuild(); + return; + } + + MemoriesGameplayConfig memoriesGameplayConfig = MemoriesGameplayConfig.get(store.getExternalData().getWorld().getGameplayConfig()); + if (memoriesGameplayConfig != null) { + SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get().getPlayerSpatialResourceType()); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(this.recordMemoriesParticlesPosition, 75.0, results); + ParticleUtil.spawnParticleEffect(memoriesGameplayConfig.getMemoriesRecordParticles(), this.recordMemoriesParticlesPosition, results, store); + } + + this.close(); + break; + case ViewCategory: + this.currentCategory = data.category; + this.selectedMemory = null; + this.rebuild(); + break; + case Back: + this.currentCategory = null; + this.selectedMemory = null; + this.rebuild(); + break; + case SelectMemory: + if (data.memoryId == null || this.currentCategory == null) { + return; + } + + Set memoriesSet = MemoriesPlugin.get().getAllMemories().get(this.currentCategory); + if (memoriesSet == null) { + return; + } + + ObjectArrayList memories = new ObjectArrayList<>(memoriesSet); + memories.sort(Comparator.comparing(Memory::getTitle)); + Set recordedMemories = MemoriesPlugin.get().getRecordedMemories(); + if (recordedMemories == null) { + return; + } + + Memory newSelection = null; + Iterator commandBuilder = recordedMemories.iterator(); + + while (true) { + if (commandBuilder.hasNext()) { + Memory memory = (Memory)commandBuilder.next(); + if (!memory.getId().equals(data.memoryId)) { + continue; + } + + newSelection = memory; + } + + if (newSelection == null) { + return; + } + + if (!memories.contains(newSelection) || newSelection.equals(this.selectedMemory)) { + return; + } + + UICommandBuilder commandBuilderx = new UICommandBuilder(); + if (this.selectedMemory != null && recordedMemories.contains(this.selectedMemory)) { + int previousIndex = memories.indexOf(this.selectedMemory); + if (previousIndex >= 0) { + updateMemoryButtonSelection(commandBuilderx, previousIndex, this.selectedMemory, false); + } + } + + int newIndex = memories.indexOf(newSelection); + if (newIndex >= 0) { + updateMemoryButtonSelection(commandBuilderx, newIndex, newSelection, true); + } + + updateMemoryDetailsPanel(commandBuilderx, newSelection); + this.selectedMemory = newSelection; + this.sendUpdate(commandBuilderx); + break; + } + } + } + + private static void updateMemoryButtonSelection(@Nonnull UICommandBuilder commandBuilder, int index, @Nonnull Memory memory, boolean isSelected) { + String selector = "#IconList[" + index + "] "; + if (isSelected) { + commandBuilder.set(selector + "#ButtonNotSelected.Visible", false); + commandBuilder.set(selector + "#ButtonSelected.Visible", true); + commandBuilder.setNull(selector + "#ButtonSelected.Background"); + commandBuilder.set(selector + "#ButtonSelected.TooltipText", memory.getTooltipText()); + } else { + commandBuilder.set(selector + "#ButtonSelected.Visible", false); + commandBuilder.set(selector + "#ButtonNotSelected.Visible", true); + commandBuilder.setNull(selector + "#ButtonNotSelected.Background"); + commandBuilder.set(selector + "#ButtonNotSelected.TooltipText", memory.getTooltipText()); + } + } + + private static void updateMemoryDetailsPanel(@Nonnull UICommandBuilder commandBuilder, @Nonnull Memory memory) { + commandBuilder.set("#MemoryName.Text", Message.translation(memory.getTitle())); + commandBuilder.set("#MemoryTimeLocation.Text", ""); + if (memory instanceof NPCMemory npcMemory) { + Message locationNameKey = npcMemory.getLocationMessage(); + long capturedTimestamp = npcMemory.getCapturedTimestamp(); + String timeString = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT) + .withLocale(Locale.getDefault()) + .format(Instant.ofEpochMilli(capturedTimestamp).atZone(ZoneOffset.UTC)); + Message memoryLocationTimeText = Message.translation("server.memories.general.foundIn").param("location", locationNameKey).param("time", timeString); + commandBuilder.set("#MemoryTimeLocation.TextSpans", memoryLocationTimeText); + } + + String iconPath = memory.getIconPath(); + if (iconPath != null && !iconPath.isEmpty()) { + commandBuilder.set("#MemoryIcon.AssetPath", iconPath); + } else { + commandBuilder.setNull("#MemoryIcon.AssetPath"); + } + } + + public static enum PageAction { + Record, + ViewCategory, + Back, + SelectMemory; + + public static final Codec CODEC = new EnumCodec<>(MemoriesPage.PageAction.class); + + private PageAction() { + } + } + + public static class PageEventData { + public static final String KEY_ACTION = "Action"; + public static final String KEY_CATEGORY = "Category"; + public static final String KEY_MEMORY_ID = "MemoryId"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + MemoriesPage.PageEventData.class, MemoriesPage.PageEventData::new + ) + .append( + new KeyedCodec<>("Action", MemoriesPage.PageAction.CODEC), + (pageEventData, pageAction) -> pageEventData.action = pageAction, + pageEventData -> pageEventData.action + ) + .add() + .append(new KeyedCodec<>("Category", Codec.STRING), (pageEventData, s) -> pageEventData.category = s, pageEventData -> pageEventData.category) + .add() + .append(new KeyedCodec<>("MemoryId", Codec.STRING), (pageEventData, id) -> pageEventData.memoryId = id, pageEventData -> pageEventData.memoryId) + .add() + .build(); + public MemoriesPage.PageAction action; + public String category; + public String memoryId; + + public PageEventData() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/page/MemoriesPageSupplier.java b/src/com/hypixel/hytale/builtin/adventure/memories/page/MemoriesPageSupplier.java new file mode 100644 index 0000000..724d297 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/page/MemoriesPageSupplier.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.builtin.adventure.memories.page; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.player.pages.CustomUIPage; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenCustomUIInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class MemoriesPageSupplier implements OpenCustomUIInteraction.CustomPageSupplier { + public MemoriesPageSupplier() { + } + + @NullableDecl + @Override + public CustomUIPage tryCreate(Ref ref, ComponentAccessor componentAccessor, PlayerRef playerRef, InteractionContext context) { + return new MemoriesPage(playerRef, context.getTargetBlock()); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/temple/ForgottenTempleConfig.java b/src/com/hypixel/hytale/builtin/adventure/memories/temple/ForgottenTempleConfig.java new file mode 100644 index 0000000..8d19bc4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/temple/ForgottenTempleConfig.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.builtin.adventure.memories.temple; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; + +public class ForgottenTempleConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder(ForgottenTempleConfig.class, ForgottenTempleConfig::new) + .append(new KeyedCodec<>("MinYRespawn", Codec.DOUBLE), (config, o) -> config.minYRespawn = o, config -> config.minYRespawn) + .documentation("The Y at which players are teleported back to spawn.") + .add() + .append(new KeyedCodec<>("RespawnSound", Codec.STRING), (config, o) -> config.respawnSound = o, config -> config.respawnSound) + .documentation("The sound ID to play when players respawn in the temple.") + .add() + .build(); + private double minYRespawn = 5.0; + private String respawnSound; + + public ForgottenTempleConfig() { + } + + public double getMinYRespawn() { + return this.minYRespawn; + } + + public String getRespawnSound() { + return this.respawnSound; + } + + public int getRespawnSoundIndex() { + return this.respawnSound == null ? 0 : SoundEvent.getAssetMap().getIndex(this.respawnSound); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/temple/TempleRespawnPlayersSystem.java b/src/com/hypixel/hytale/builtin/adventure/memories/temple/TempleRespawnPlayersSystem.java new file mode 100644 index 0000000..7d98f16 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/temple/TempleRespawnPlayersSystem.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.adventure.memories.temple; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.DelayedEntitySystem; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class TempleRespawnPlayersSystem extends DelayedEntitySystem { + public static final Query QUERY = Query.and(PlayerRef.getComponentType(), TransformComponent.getComponentType()); + + public TempleRespawnPlayersSystem() { + super(1.0F); + } + + @Override + public void tick( + float dt, + int index, + @NonNullDecl ArchetypeChunk archetypeChunk, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + World world = store.getExternalData().getWorld(); + GameplayConfig gameplayConfig = world.getGameplayConfig(); + ForgottenTempleConfig config = gameplayConfig.getPluginConfig().get(ForgottenTempleConfig.class); + if (config != null) { + Vector3d position = archetypeChunk.getComponent(index, TransformComponent.getComponentType()).getPosition(); + if (!(position.getY() > config.getMinYRespawn())) { + Ref ref = archetypeChunk.getReferenceTo(index); + ISpawnProvider spawnProvider = world.getWorldConfig().getSpawnProvider(); + Transform spawnPoint = spawnProvider.getSpawnPoint(ref, commandBuffer); + commandBuffer.addComponent(ref, Teleport.getComponentType(), new Teleport(null, spawnPoint)); + PlayerRef playerRef = archetypeChunk.getComponent(index, PlayerRef.getComponentType()); + SoundUtil.playSoundEvent2dToPlayer(playerRef, config.getRespawnSoundIndex(), SoundCategory.SFX); + } + } + } + + @NullableDecl + @Override + public Query getQuery() { + return QUERY; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/memories/window/MemoriesWindow.java b/src/com/hypixel/hytale/builtin/adventure/memories/window/MemoriesWindow.java new file mode 100644 index 0000000..6b239ee --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/memories/window/MemoriesWindow.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.builtin.adventure.memories.window; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.hypixel.hytale.builtin.adventure.memories.MemoriesPlugin; +import com.hypixel.hytale.builtin.adventure.memories.component.PlayerMemories; +import com.hypixel.hytale.builtin.adventure.memories.memories.Memory; +import com.hypixel.hytale.codec.EmptyExtraInfo; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.packets.window.WindowType; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.player.windows.Window; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.BsonUtil; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MemoriesWindow extends Window { + private final JsonObject windowData = new JsonObject(); + + public MemoriesWindow() { + super(WindowType.Memories); + } + + @Nonnull + @Override + public JsonObject getData() { + return this.windowData; + } + + @Override + public boolean onOpen0() { + JsonArray array = new JsonArray(); + Ref ref = this.getPlayerRef().getReference(); + PlayerMemories playerMemories = ref.getStore().getComponent(ref, PlayerMemories.getComponentType()); + if (playerMemories != null) { + this.windowData.addProperty("capacity", playerMemories.getMemoriesCapacity()); + + for (Memory memory : playerMemories.getRecordedMemories()) { + JsonObject obj = new JsonObject(); + obj.addProperty("title", memory.getTitle()); + obj.add("tooltipText", BsonUtil.translateBsonToJson(Message.CODEC.encode(memory.getTooltipText(), EmptyExtraInfo.EMPTY).asDocument())); + String iconPath = memory.getIconPath(); + if (iconPath != null && !iconPath.isEmpty()) { + obj.addProperty("icon", iconPath); + } + + String category = GetCategoryIconPathForMemory(memory); + if (category != null) { + obj.addProperty("categoryIcon", category); + } + + array.add(obj); + } + } else { + this.windowData.addProperty("capacity", 0); + } + + this.windowData.add("memories", array); + this.invalidate(); + return true; + } + + @Nullable + private static String GetCategoryIconPathForMemory(@Nonnull Memory memory) { + Map> allMemories = MemoriesPlugin.get().getAllMemories(); + + for (Entry> entry : allMemories.entrySet()) { + if (entry.getValue().contains(memory)) { + String memoryCategoryIconBasePath = "UI/Custom/Pages/Memories/categories/%s.png"; + return String.format("UI/Custom/Pages/Memories/categories/%s.png", entry.getKey()); + } + } + + return null; + } + + @Override + public void onClose0() { + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/NPCObjectivesPlugin.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/NPCObjectivesPlugin.java new file mode 100644 index 0000000..f686d5a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/NPCObjectivesPlugin.java @@ -0,0 +1,164 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.builtin.adventure.npcobjectives.assets.BountyObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.npcobjectives.assets.KillObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.npcobjectives.assets.KillSpawnBeaconObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.npcobjectives.assets.KillSpawnMarkerObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.npcobjectives.npc.builders.BuilderActionCompleteTask; +import com.hypixel.hytale.builtin.adventure.npcobjectives.npc.builders.BuilderActionStartObjective; +import com.hypixel.hytale.builtin.adventure.npcobjectives.npc.builders.BuilderSensorHasTask; +import com.hypixel.hytale.builtin.adventure.npcobjectives.resources.KillTrackerResource; +import com.hypixel.hytale.builtin.adventure.npcobjectives.systems.KillTrackerSystem; +import com.hypixel.hytale.builtin.adventure.npcobjectives.systems.SpawnBeaconCheckRemovalSystem; +import com.hypixel.hytale.builtin.adventure.npcobjectives.task.BountyObjectiveTask; +import com.hypixel.hytale.builtin.adventure.npcobjectives.task.KillNPCObjectiveTask; +import com.hypixel.hytale.builtin.adventure.npcobjectives.task.KillSpawnBeaconObjectiveTask; +import com.hypixel.hytale.builtin.adventure.npcobjectives.task.KillSpawnMarkerObjectiveTask; +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectiveDataStore; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.UseEntityObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.task.ObjectiveTask; +import com.hypixel.hytale.builtin.adventure.objectives.task.UseEntityObjectiveTask; +import com.hypixel.hytale.builtin.tagset.config.NPCGroup; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.spawning.assets.spawnmarker.config.SpawnMarker; +import com.hypixel.hytale.server.spawning.assets.spawns.config.BeaconNPCSpawn; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NPCObjectivesPlugin extends JavaPlugin { + protected static NPCObjectivesPlugin instance; + private ResourceType killTrackerResourceType; + + public static NPCObjectivesPlugin get() { + return instance; + } + + public NPCObjectivesPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + instance = this; + ObjectivePlugin.get() + .registerTask( + "KillSpawnBeacon", + KillSpawnBeaconObjectiveTaskAsset.class, + KillSpawnBeaconObjectiveTaskAsset.CODEC, + KillSpawnBeaconObjectiveTask.class, + KillSpawnBeaconObjectiveTask.CODEC, + KillSpawnBeaconObjectiveTask::new + ); + ObjectivePlugin.get() + .registerTask( + "KillSpawnMarker", + KillSpawnMarkerObjectiveTaskAsset.class, + KillSpawnMarkerObjectiveTaskAsset.CODEC, + KillSpawnMarkerObjectiveTask.class, + KillSpawnMarkerObjectiveTask.CODEC, + KillSpawnMarkerObjectiveTask::new + ); + ObjectivePlugin.get() + .registerTask( + "Bounty", + BountyObjectiveTaskAsset.class, + BountyObjectiveTaskAsset.CODEC, + BountyObjectiveTask.class, + BountyObjectiveTask.CODEC, + BountyObjectiveTask::new + ); + ObjectivePlugin.get() + .registerTask( + "KillNPC", + KillObjectiveTaskAsset.class, + KillObjectiveTaskAsset.CODEC, + KillNPCObjectiveTask.class, + KillNPCObjectiveTask.CODEC, + KillNPCObjectiveTask::new + ); + this.getEntityStoreRegistry().registerSystem(new SpawnBeaconCheckRemovalSystem()); + this.killTrackerResourceType = this.getEntityStoreRegistry().registerResource(KillTrackerResource.class, KillTrackerResource::new); + this.getEntityStoreRegistry().registerSystem(new KillTrackerSystem()); + NPCPlugin.get() + .registerCoreComponentType("CompleteTask", BuilderActionCompleteTask::new) + .registerCoreComponentType("StartObjective", BuilderActionStartObjective::new) + .registerCoreComponentType("HasTask", BuilderSensorHasTask::new); + AssetRegistry.getAssetStore(ObjectiveAsset.class).injectLoadsAfter(SpawnMarker.class); + AssetRegistry.getAssetStore(ObjectiveAsset.class).injectLoadsAfter(BeaconNPCSpawn.class); + AssetRegistry.getAssetStore(ObjectiveAsset.class).injectLoadsAfter(NPCGroup.class); + } + + public static boolean hasTask(@Nonnull UUID playerUUID, @Nonnull UUID npcId, @Nonnull String taskId) { + Map> entityObjectives = ObjectivePlugin.get().getObjectiveDataStore().getEntityTasksForPlayer(playerUUID); + return entityObjectives == null ? false : entityObjectives.get(taskId) != null; + } + + @Nullable + public static String updateTaskCompletion( + @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull UUID npcId, @Nonnull String taskId + ) { + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + ObjectiveDataStore objectiveDataStore = ObjectivePlugin.get().getObjectiveDataStore(); + Map> entityObjectiveUUIDs = objectiveDataStore.getEntityTasksForPlayer(uuidComponent.getUuid()); + if (entityObjectiveUUIDs == null) { + return null; + } else { + Set objectiveUUIDsForTaskId = entityObjectiveUUIDs.get(taskId); + if (objectiveUUIDsForTaskId == null) { + return null; + } else { + for (UUID objectiveUUID : objectiveUUIDsForTaskId) { + Objective objective = objectiveDataStore.getObjective(objectiveUUID); + if (objective != null) { + for (ObjectiveTask task : objective.getCurrentTasks()) { + if (task instanceof UseEntityObjectiveTask useEntityTask) { + UseEntityObjectiveTaskAsset taskAsset = useEntityTask.getAsset(); + if (taskAsset.getTaskId().equals(taskId)) { + if (!useEntityTask.increaseTaskCompletion(store, ref, 1, objective, playerRef, npcId)) { + return null; + } + + return taskAsset.getAnimationIdToPlay(); + } + } + } + } + } + + return null; + } + } + } + + public static void startObjective(@Nonnull Ref playerReference, @Nonnull String taskId, @Nonnull Store store) { + UUIDComponent uuidComponent = store.getComponent(playerReference, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + World world = store.getExternalData().getWorld(); + ObjectivePlugin.get().startObjective(taskId, Set.of(uuidComponent.getUuid()), world.getWorldConfig().getUuid(), null, store); + } + + public ResourceType getKillTrackerResourceType() { + return this.killTrackerResourceType; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/assets/BountyObjectiveTaskAsset.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/assets/BountyObjectiveTaskAsset.java new file mode 100644 index 0000000..9e843d3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/assets/BountyObjectiveTaskAsset.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.assets; + +import com.hypixel.hytale.builtin.adventure.objectives.config.task.ObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.taskcondition.TaskConditionAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.worldlocationproviders.WorldLocationProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BountyObjectiveTaskAsset extends ObjectiveTaskAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + BountyObjectiveTaskAsset.class, BountyObjectiveTaskAsset::new, ObjectiveTaskAsset.BASE_CODEC + ) + .append( + new KeyedCodec<>("NpcId", Codec.STRING), + (bountyObjectiveTaskAsset, s) -> bountyObjectiveTaskAsset.npcId = s, + bountyObjectiveTaskAsset -> bountyObjectiveTaskAsset.npcId + ) + .add() + .append( + new KeyedCodec<>("WorldLocationCondition", WorldLocationProvider.CODEC), + (bountyObjectiveTaskAsset, worldLocationCondition) -> bountyObjectiveTaskAsset.worldLocationProvider = worldLocationCondition, + bountyObjectiveTaskAsset -> bountyObjectiveTaskAsset.worldLocationProvider + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + protected String npcId; + protected WorldLocationProvider worldLocationProvider; + + public BountyObjectiveTaskAsset( + String descriptionId, TaskConditionAsset[] taskConditions, Vector3i[] mapMarkers, String npcId, WorldLocationProvider worldLocationProvider + ) { + super(descriptionId, taskConditions, mapMarkers); + this.npcId = npcId; + this.worldLocationProvider = worldLocationProvider; + } + + protected BountyObjectiveTaskAsset() { + } + + @Nonnull + @Override + public ObjectiveTaskAsset.TaskScope getTaskScope() { + return ObjectiveTaskAsset.TaskScope.PLAYER; + } + + public String getNpcId() { + return this.npcId; + } + + public WorldLocationProvider getWorldLocationProvider() { + return this.worldLocationProvider; + } + + @Override + protected boolean matchesAsset0(ObjectiveTaskAsset task) { + if (task instanceof BountyObjectiveTaskAsset asset) { + return !Objects.equals(asset.npcId, this.npcId) ? false : Objects.equals(asset.worldLocationProvider, this.worldLocationProvider); + } else { + return false; + } + } + + @Nonnull + @Override + public String toString() { + return "BountyObjectiveTaskAsset{npcId='" + this.npcId + "', worldLocationCondition=" + this.worldLocationProvider + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/assets/KillObjectiveTaskAsset.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/assets/KillObjectiveTaskAsset.java new file mode 100644 index 0000000..b8f7c1b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/assets/KillObjectiveTaskAsset.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.assets; + +import com.hypixel.hytale.builtin.adventure.objectives.config.task.CountObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.ObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.taskcondition.TaskConditionAsset; +import com.hypixel.hytale.builtin.tagset.config.NPCGroup; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class KillObjectiveTaskAsset extends CountObjectiveTaskAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + KillObjectiveTaskAsset.class, KillObjectiveTaskAsset::new, CountObjectiveTaskAsset.CODEC + ) + .append(new KeyedCodec<>("NPCGroupId", Codec.STRING), (objective, entityType) -> objective.npcGroupId = entityType, objective -> objective.npcGroupId) + .addValidator(Validators.nonNull()) + .addValidator(NPCGroup.VALIDATOR_CACHE.getValidator()) + .add() + .build(); + protected String npcGroupId; + + public KillObjectiveTaskAsset(String descriptionId, TaskConditionAsset[] taskConditions, Vector3i[] mapMarkers, int count, String npcGroupId) { + super(descriptionId, taskConditions, mapMarkers, count); + this.npcGroupId = npcGroupId; + } + + protected KillObjectiveTaskAsset() { + } + + @Nonnull + @Override + public ObjectiveTaskAsset.TaskScope getTaskScope() { + return ObjectiveTaskAsset.TaskScope.PLAYER_AND_MARKER; + } + + public String getNpcGroupId() { + return this.npcGroupId; + } + + @Override + protected boolean matchesAsset0(ObjectiveTaskAsset task) { + if (!super.matchesAsset0(task)) { + return false; + } else { + return !(task instanceof KillObjectiveTaskAsset) ? false : ((KillObjectiveTaskAsset)task).npcGroupId.equals(this.npcGroupId); + } + } + + @Nonnull + @Override + public String toString() { + return "KillObjectiveTaskAsset{npcGroupId='" + this.npcGroupId + "'} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/assets/KillSpawnBeaconObjectiveTaskAsset.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/assets/KillSpawnBeaconObjectiveTaskAsset.java new file mode 100644 index 0000000..e805fbb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/assets/KillSpawnBeaconObjectiveTaskAsset.java @@ -0,0 +1,146 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.assets; + +import com.hypixel.hytale.builtin.adventure.objectives.config.task.ObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.taskcondition.TaskConditionAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.worldlocationproviders.WorldLocationProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.spawning.assets.spawns.config.BeaconNPCSpawn; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class KillSpawnBeaconObjectiveTaskAsset extends KillObjectiveTaskAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + KillSpawnBeaconObjectiveTaskAsset.class, KillSpawnBeaconObjectiveTaskAsset::new, KillObjectiveTaskAsset.CODEC + ) + .append( + new KeyedCodec<>( + "SpawnBeacons", + new ArrayCodec<>(KillSpawnBeaconObjectiveTaskAsset.ObjectiveSpawnBeacon.CODEC, KillSpawnBeaconObjectiveTaskAsset.ObjectiveSpawnBeacon[]::new) + ), + (killSpawnBeaconObjectiveTaskAsset, objectiveSpawnBeacons) -> killSpawnBeaconObjectiveTaskAsset.spawnBeacons = objectiveSpawnBeacons, + killSpawnBeaconObjectiveTaskAsset -> killSpawnBeaconObjectiveTaskAsset.spawnBeacons + ) + .addValidator(Validators.nonEmptyArray()) + .add() + .build(); + protected KillSpawnBeaconObjectiveTaskAsset.ObjectiveSpawnBeacon[] spawnBeacons; + + public KillSpawnBeaconObjectiveTaskAsset( + String descriptionId, + TaskConditionAsset[] taskConditions, + Vector3i[] mapMarkers, + int count, + String npcGroupId, + KillSpawnBeaconObjectiveTaskAsset.ObjectiveSpawnBeacon[] spawnBeacons + ) { + super(descriptionId, taskConditions, mapMarkers, count, npcGroupId); + this.spawnBeacons = spawnBeacons; + } + + protected KillSpawnBeaconObjectiveTaskAsset() { + } + + public KillSpawnBeaconObjectiveTaskAsset.ObjectiveSpawnBeacon[] getSpawnBeacons() { + return this.spawnBeacons; + } + + @Override + protected boolean matchesAsset0(ObjectiveTaskAsset task) { + if (!super.matchesAsset0(task)) { + return false; + } else { + return !(task instanceof KillSpawnBeaconObjectiveTaskAsset) + ? false + : Arrays.equals((Object[])((KillSpawnBeaconObjectiveTaskAsset)task).spawnBeacons, (Object[])this.spawnBeacons); + } + } + + @Nonnull + @Override + public String toString() { + return "KillSpawnBeaconObjectiveTaskAsset{spawnBeacons=" + Arrays.toString((Object[])this.spawnBeacons) + "} " + super.toString(); + } + + public static class ObjectiveSpawnBeacon { + public static final BuilderCodec CODEC = BuilderCodec.builder( + KillSpawnBeaconObjectiveTaskAsset.ObjectiveSpawnBeacon.class, KillSpawnBeaconObjectiveTaskAsset.ObjectiveSpawnBeacon::new + ) + .append( + new KeyedCodec<>("SpawnBeaconId", Codec.STRING), + (objectiveSpawnBeacon, s) -> objectiveSpawnBeacon.spawnBeaconId = s, + objectiveSpawnBeacon -> objectiveSpawnBeacon.spawnBeaconId + ) + .addValidator(Validators.nonNull()) + .addValidator(BeaconNPCSpawn.VALIDATOR_CACHE.getValidator()) + .add() + .append( + new KeyedCodec<>("Offset", Vector3d.CODEC), + (objectiveSpawnBeacon, vector3d) -> objectiveSpawnBeacon.offset = vector3d, + objectiveSpawnBeacon -> objectiveSpawnBeacon.offset + ) + .add() + .append( + new KeyedCodec<>("WorldLocationCondition", WorldLocationProvider.CODEC), + (objectiveSpawnBeacon, worldLocationCondition) -> objectiveSpawnBeacon.worldLocationProvider = worldLocationCondition, + objectiveSpawnBeacon -> objectiveSpawnBeacon.worldLocationProvider + ) + .add() + .build(); + protected String spawnBeaconId; + protected Vector3d offset; + protected WorldLocationProvider worldLocationProvider; + + public ObjectiveSpawnBeacon() { + } + + public String getSpawnBeaconId() { + return this.spawnBeaconId; + } + + public Vector3d getOffset() { + return this.offset; + } + + public WorldLocationProvider getWorldLocationProvider() { + return this.worldLocationProvider; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + KillSpawnBeaconObjectiveTaskAsset.ObjectiveSpawnBeacon that = (KillSpawnBeaconObjectiveTaskAsset.ObjectiveSpawnBeacon)o; + if (!this.spawnBeaconId.equals(that.spawnBeaconId)) { + return false; + } else if (this.offset != null ? this.offset.equals(that.offset) : that.offset == null) { + return this.worldLocationProvider != null ? this.worldLocationProvider.equals(that.worldLocationProvider) : that.worldLocationProvider == null; + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.spawnBeaconId.hashCode(); + result = 31 * result + (this.offset != null ? this.offset.hashCode() : 0); + return 31 * result + (this.worldLocationProvider != null ? this.worldLocationProvider.hashCode() : 0); + } + + @Nonnull + @Override + public String toString() { + return "ObjectiveSpawnBeacon{spawnBeaconId='" + this.spawnBeaconId + "', offset=" + this.offset + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/assets/KillSpawnMarkerObjectiveTaskAsset.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/assets/KillSpawnMarkerObjectiveTaskAsset.java new file mode 100644 index 0000000..1ba2733 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/assets/KillSpawnMarkerObjectiveTaskAsset.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.assets; + +import com.hypixel.hytale.builtin.adventure.objectives.config.task.ObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.taskcondition.TaskConditionAsset; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.codec.validation.validator.ArrayValidator; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.spawning.assets.spawnmarker.config.SpawnMarker; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class KillSpawnMarkerObjectiveTaskAsset extends KillObjectiveTaskAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + KillSpawnMarkerObjectiveTaskAsset.class, KillSpawnMarkerObjectiveTaskAsset::new, KillObjectiveTaskAsset.CODEC + ) + .append( + new KeyedCodec<>("Radius", Codec.FLOAT), + (killSpawnMarkerObjectiveTaskAsset, aFloat) -> killSpawnMarkerObjectiveTaskAsset.radius = aFloat, + killSpawnMarkerObjectiveTaskAsset -> killSpawnMarkerObjectiveTaskAsset.radius + ) + .addValidator(Validators.greaterThan(0.0F)) + .add() + .append( + new KeyedCodec<>("SpawnMarkerIds", Codec.STRING_ARRAY), + (killSpawnMarkerObjectiveTaskAsset, s) -> killSpawnMarkerObjectiveTaskAsset.spawnMarkerIds = s, + killSpawnMarkerObjectiveTaskAsset -> killSpawnMarkerObjectiveTaskAsset.spawnMarkerIds + ) + .addValidator(Validators.nonEmptyArray()) + .addValidator(SpawnMarker.VALIDATOR_CACHE.getArrayValidator()) + .addValidator(new ArrayValidator<>((LegacyValidator)((o, results) -> { + SpawnMarker spawnMarker = SpawnMarker.getAssetMap().getAsset(o); + if (spawnMarker != null && !spawnMarker.isManualTrigger()) { + results.fail("SpawnMarker '" + o + "' can't be triggered manually!"); + } + }))) + .add() + .build(); + protected String[] spawnMarkerIds; + protected float radius = 1.0F; + + public KillSpawnMarkerObjectiveTaskAsset( + String descriptionId, TaskConditionAsset[] taskConditions, Vector3i[] mapMarkers, int count, String npcGroupId, String[] spawnMarkerIds, float radius + ) { + super(descriptionId, taskConditions, mapMarkers, count, npcGroupId); + this.spawnMarkerIds = spawnMarkerIds; + this.radius = radius; + } + + protected KillSpawnMarkerObjectiveTaskAsset() { + } + + @Nonnull + public String[] getSpawnMarkerIds() { + return this.spawnMarkerIds; + } + + public float getRadius() { + return this.radius; + } + + @Override + protected boolean matchesAsset0(ObjectiveTaskAsset task) { + if (!super.matchesAsset0(task)) { + return false; + } else if (task instanceof KillSpawnMarkerObjectiveTaskAsset killSpawnMarkerObjectiveTaskAsset) { + return !Arrays.equals((Object[])killSpawnMarkerObjectiveTaskAsset.spawnMarkerIds, (Object[])this.spawnMarkerIds) + ? false + : killSpawnMarkerObjectiveTaskAsset.radius == this.radius; + } else { + return false; + } + } + + @Nonnull + @Override + public String toString() { + return "KillSpawnMarkerObjectiveTaskAsset{spawnMarkerIds=" + + Arrays.toString((Object[])this.spawnMarkerIds) + + ", radius=" + + this.radius + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/ActionCompleteTask.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/ActionCompleteTask.java new file mode 100644 index 0000000..c8f1e19 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/ActionCompleteTask.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.npc; + +import com.hypixel.hytale.builtin.adventure.npcobjectives.NPCObjectivesPlugin; +import com.hypixel.hytale.builtin.adventure.npcobjectives.npc.builders.BuilderActionCompleteTask; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.ActionPlayAnimation; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import java.util.List; +import javax.annotation.Nonnull; + +public class ActionCompleteTask extends ActionPlayAnimation { + protected final boolean playAnimation; + + public ActionCompleteTask(@Nonnull BuilderActionCompleteTask builder, @Nonnull BuilderSupport support) { + super(builder, support); + this.playAnimation = builder.isPlayAnimation(support); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + Ref target = role.getStateSupport().getInteractionIterationTarget(); + boolean targetExists = target != null && !store.getArchetype(target).contains(DeathComponent.getComponentType()); + return super.canExecute(ref, role, sensorInfo, dt, store) && targetExists; + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + UUIDComponent parentUuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert parentUuidComponent != null; + + Ref targetPlayerReference = role.getStateSupport().getInteractionIterationTarget(); + if (targetPlayerReference == null) { + return false; + } else { + PlayerRef targetPlayerRefComponent = store.getComponent(targetPlayerReference, PlayerRef.getComponentType()); + if (targetPlayerRefComponent == null) { + return false; + } else { + List activeTasks = role.getEntitySupport().getTargetPlayerActiveTasks(); + String animation = null; + if (activeTasks != null) { + for (int i = 0; i < activeTasks.size(); i++) { + animation = NPCObjectivesPlugin.updateTaskCompletion( + store, targetPlayerReference, targetPlayerRefComponent, parentUuidComponent.getUuid(), activeTasks.get(i) + ); + } + } + + if (this.playAnimation && animation != null) { + this.setAnimationId(animation); + super.execute(ref, role, sensorInfo, dt, store); + } + + return true; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/ActionStartObjective.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/ActionStartObjective.java new file mode 100644 index 0000000..c503202 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/ActionStartObjective.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.npc; + +import com.hypixel.hytale.builtin.adventure.npcobjectives.NPCObjectivesPlugin; +import com.hypixel.hytale.builtin.adventure.npcobjectives.npc.builders.BuilderActionStartObjective; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionStartObjective extends ActionBase { + protected final String objectiveId; + + public ActionStartObjective(@Nonnull BuilderActionStartObjective builder, @Nonnull BuilderSupport support) { + super(builder); + this.objectiveId = builder.getObjectiveId(support); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && role.getStateSupport().getInteractionIterationTarget() != null; + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + NPCObjectivesPlugin.startObjective(role.getStateSupport().getInteractionIterationTarget(), this.objectiveId, store); + return true; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/SensorHasTask.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/SensorHasTask.java new file mode 100644 index 0000000..d6e1ebb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/SensorHasTask.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.npc; + +import com.hypixel.hytale.builtin.adventure.npcobjectives.NPCObjectivesPlugin; +import com.hypixel.hytale.builtin.adventure.npcobjectives.npc.builders.BuilderSensorHasTask; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.EntitySupport; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SensorHasTask extends SensorBase { + @Nullable + protected final String[] tasksById; + + public SensorHasTask(@Nonnull BuilderSensorHasTask builder, @Nonnull BuilderSupport support) { + super(builder); + this.tasksById = builder.getTasksById(support); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + return false; + } else { + Ref target = role.getStateSupport().getInteractionIterationTarget(); + if (target == null) { + return false; + } else { + Archetype targetArchetype = store.getArchetype(target); + if (targetArchetype.contains(DeathComponent.getComponentType())) { + return false; + } else { + UUIDComponent targetUuidComponent = store.getComponent(target, UUIDComponent.getComponentType()); + + assert targetUuidComponent != null; + + UUID targetUuid = targetUuidComponent.getUuid(); + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID uuid = uuidComponent.getUuid(); + NPCObjectivesPlugin objectiveSystem = NPCObjectivesPlugin.get(); + EntitySupport entitySupport = role.getEntitySupport(); + boolean match = false; + + for (String taskById : this.tasksById) { + if (NPCObjectivesPlugin.hasTask(targetUuid, uuid, taskById)) { + match = true; + entitySupport.addTargetPlayerActiveTask(taskById); + } + } + + return match; + } + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/builders/BuilderActionCompleteTask.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/builders/BuilderActionCompleteTask.java new file mode 100644 index 0000000..31c67c7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/builders/BuilderActionCompleteTask.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.npc.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.builtin.adventure.npcobjectives.npc.ActionCompleteTask; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionPlayAnimation; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class BuilderActionCompleteTask extends BuilderActionPlayAnimation { + protected final BooleanHolder playAnimation = new BooleanHolder(); + + public BuilderActionCompleteTask() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Complete a task"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Complete a task. Tasks are picked based on those provided to SensorCanInteract."; + } + + @Nonnull + public ActionCompleteTask build(@Nonnull BuilderSupport builderSupport) { + return new ActionCompleteTask(this, builderSupport); + } + + @Nonnull + public BuilderActionCompleteTask readConfig(@Nonnull JsonElement data) { + super.readConfig(data); + this.getBoolean( + data, + "PlayAnimation", + this.playAnimation, + true, + BuilderDescriptorState.Stable, + "Whether or not to play the animation associated with completing this task", + null + ); + this.requireInstructionType(EnumSet.of(InstructionType.Interaction)); + return this; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + public boolean isPlayAnimation(@Nonnull BuilderSupport support) { + return this.playAnimation.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/builders/BuilderActionStartObjective.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/builders/BuilderActionStartObjective.java new file mode 100644 index 0000000..93ccc22 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/builders/BuilderActionStartObjective.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.npc.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.builtin.adventure.npcobjectives.npc.ActionStartObjective; +import com.hypixel.hytale.builtin.adventure.npcobjectives.npc.validators.ObjectiveExistsValidator; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class BuilderActionStartObjective extends BuilderActionBase { + protected final AssetHolder objectiveId = new AssetHolder(); + + public BuilderActionStartObjective() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Start the given objective for the currently iterated player in the interaction instruction"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public ActionStartObjective build(@Nonnull BuilderSupport builderSupport) { + return new ActionStartObjective(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionStartObjective readConfig(@Nonnull JsonElement data) { + this.requireAsset(data, "Objective", this.objectiveId, ObjectiveExistsValidator.required(), BuilderDescriptorState.Stable, "The task to start", null); + this.requireInstructionType(EnumSet.of(InstructionType.Interaction)); + return this; + } + + public String getObjectiveId(@Nonnull BuilderSupport support) { + return this.objectiveId.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/builders/BuilderSensorHasTask.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/builders/BuilderSensorHasTask.java new file mode 100644 index 0000000..1af9bb2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/builders/BuilderSensorHasTask.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.npc.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.builtin.adventure.npcobjectives.npc.SensorHasTask; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringArrayNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import java.util.EnumSet; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderSensorHasTask extends BuilderSensorBase { + protected final StringArrayHolder tasksById = new StringArrayHolder(); + + public BuilderSensorHasTask() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Checks whether or not the player being iterated by the interaction instruction has any of the given tasks"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorHasTask(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireStringArray( + data, + "TasksById", + this.tasksById, + 0, + Integer.MAX_VALUE, + StringArrayNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "Completable tasks to match by name", + null + ); + this.requireInstructionType(EnumSet.of(InstructionType.Interaction)); + return this; + } + + @Nullable + public String[] getTasksById(@Nonnull BuilderSupport support) { + return this.tasksById.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/validators/ObjectiveExistsValidator.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/validators/ObjectiveExistsValidator.java new file mode 100644 index 0000000..e6a6bc7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/npc/validators/ObjectiveExistsValidator.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.npc.validators; + +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveAsset; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class ObjectiveExistsValidator extends AssetValidator { + private static final ObjectiveExistsValidator DEFAULT_INSTANCE = new ObjectiveExistsValidator(); + + private ObjectiveExistsValidator() { + } + + private ObjectiveExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "Objective"; + } + + @Override + public boolean test(String objective) { + return ObjectiveAsset.getAssetMap().getAsset(objective) != null; + } + + @Nonnull + @Override + public String errorMessage(String objective, String attributeName) { + return "The objective with the name \"" + objective + "\" does not exist for attribute \"" + attributeName + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return ObjectiveAsset.class.getSimpleName(); + } + + public static ObjectiveExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static ObjectiveExistsValidator withConfig(EnumSet config) { + return new ObjectiveExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/resources/KillTrackerResource.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/resources/KillTrackerResource.java new file mode 100644 index 0000000..64dd1f8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/resources/KillTrackerResource.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.resources; + +import com.hypixel.hytale.builtin.adventure.npcobjectives.NPCObjectivesPlugin; +import com.hypixel.hytale.builtin.adventure.npcobjectives.transaction.KillTaskTransaction; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class KillTrackerResource implements Resource { + private final List killTasks = new ObjectArrayList<>(); + + public KillTrackerResource() { + } + + public static ResourceType getResourceType() { + return NPCObjectivesPlugin.get().getKillTrackerResourceType(); + } + + public void watch(KillTaskTransaction task) { + this.killTasks.add(task); + } + + public void unwatch(KillTaskTransaction task) { + this.killTasks.remove(task); + } + + @Nonnull + public List getKillTasks() { + return this.killTasks; + } + + @Nonnull + @Override + public Resource clone() { + return new KillTrackerResource(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/systems/KillTrackerSystem.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/systems/KillTrackerSystem.java new file mode 100644 index 0000000..fb7a166 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/systems/KillTrackerSystem.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.systems; + +import com.hypixel.hytale.builtin.adventure.npcobjectives.resources.KillTrackerResource; +import com.hypixel.hytale.builtin.adventure.npcobjectives.transaction.KillTaskTransaction; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathSystems; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class KillTrackerSystem extends DeathSystems.OnDeathSystem { + public KillTrackerSystem() { + } + + @Nullable + @Override + public Query getQuery() { + return NPCEntity.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + NPCEntity entity = store.getComponent(ref, NPCEntity.getComponentType()); + KillTrackerResource tracker = store.getResource(KillTrackerResource.getResourceType()); + List killTasks = tracker.getKillTasks(); + int size = killTasks.size(); + + for (int i = size - 1; i >= 0; i--) { + KillTaskTransaction entry = killTasks.get(i); + entry.getTask().checkKilledEntity(store, ref, entry.getObjective(), entity, component.getDeathInfo()); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/systems/SpawnBeaconCheckRemovalSystem.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/systems/SpawnBeaconCheckRemovalSystem.java new file mode 100644 index 0000000..1505f22 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/systems/SpawnBeaconCheckRemovalSystem.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.systems; + +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.spawning.beacons.LegacySpawnBeaconEntity; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SpawnBeaconCheckRemovalSystem extends HolderSystem { + public SpawnBeaconCheckRemovalSystem() { + } + + @Nullable + @Override + public Query getQuery() { + return LegacySpawnBeaconEntity.getComponentType(); + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + LegacySpawnBeaconEntity spawnBeaconComponent = holder.getComponent(LegacySpawnBeaconEntity.getComponentType()); + + assert spawnBeaconComponent != null; + + UUID objectiveUUID = spawnBeaconComponent.getObjectiveUUID(); + if (objectiveUUID != null && ObjectivePlugin.get().getObjectiveDataStore().getObjective(objectiveUUID) == null) { + spawnBeaconComponent.remove(); + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/BountyObjectiveTask.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/BountyObjectiveTask.java new file mode 100644 index 0000000..8929cf8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/BountyObjectiveTask.java @@ -0,0 +1,143 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.task; + +import com.hypixel.hytale.builtin.adventure.npcobjectives.assets.BountyObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.npcobjectives.resources.KillTrackerResource; +import com.hypixel.hytale.builtin.adventure.npcobjectives.transaction.KillTaskTransaction; +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.ObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.task.ObjectiveTask; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.RegistrationTransactionRecord; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.SpawnEntityTransactionRecord; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionRecord; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.npc.INonPlayerCharacter; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.PositionUtil; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import it.unimi.dsi.fastutil.Pair; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class BountyObjectiveTask extends ObjectiveTask implements KillTask { + public static final BuilderCodec CODEC = BuilderCodec.builder( + BountyObjectiveTask.class, BountyObjectiveTask::new, ObjectiveTask.BASE_CODEC + ) + .append( + new KeyedCodec<>("Completed", Codec.BOOLEAN), + (bountyObjectiveTask, aBoolean) -> bountyObjectiveTask.completed = aBoolean, + bountyObjectiveTask -> bountyObjectiveTask.completed + ) + .add() + .append( + new KeyedCodec<>("EntityUUID", Codec.UUID_BINARY), + (bountyObjectiveTask, uuid) -> bountyObjectiveTask.entityUuid = uuid, + bountyObjectiveTask -> bountyObjectiveTask.entityUuid + ) + .add() + .build(); + boolean completed; + UUID entityUuid; + + public BountyObjectiveTask(@Nonnull ObjectiveTaskAsset asset, int taskSetIndex, int taskIndex) { + super(asset, taskSetIndex, taskIndex); + } + + protected BountyObjectiveTask() { + } + + @Nonnull + public BountyObjectiveTaskAsset getAsset() { + return (BountyObjectiveTaskAsset)super.getAsset(); + } + + @Nonnull + @Override + protected TransactionRecord[] setup0(@Nonnull Objective objective, @Nonnull World world, @Nonnull Store store) { + if (this.serializedTransactionRecords != null) { + return RegistrationTransactionRecord.append(this.serializedTransactionRecords, this.eventRegistry); + } else { + Vector3d objectivePosition = objective.getPosition(store); + + assert objectivePosition != null; + + Vector3i spawnPosition = this.getAsset().getWorldLocationProvider().runCondition(world, objectivePosition.clone().floor().toVector3i()); + TransactionRecord[] transactionRecords = new TransactionRecord[2]; + Pair, INonPlayerCharacter> npcPair = NPCPlugin.get() + .spawnNPC(store, this.getAsset().getNpcId(), null, spawnPosition.toVector3d(), Vector3f.ZERO); + Ref npcReference = npcPair.first(); + UUIDComponent npcUuidComponent = store.getComponent(npcReference, UUIDComponent.getComponentType()); + + assert npcUuidComponent != null; + + UUID npcUuid = npcUuidComponent.getUuid(); + ObjectivePlugin.get().getLogger().at(Level.INFO).log("Spawned Entity '" + this.getAsset().getNpcId() + "' at position: " + spawnPosition); + transactionRecords[0] = new SpawnEntityTransactionRecord(world.getWorldConfig().getUuid(), npcUuid); + this.entityUuid = npcUuid; + this.addMarker( + new MapMarker(getBountyMarkerIDFromUUID(npcUuid), "Bounty Target", "Home.png", PositionUtil.toTransformPacket(new Transform(spawnPosition)), null) + ); + KillTaskTransaction transaction = new KillTaskTransaction(this, objective, store); + store.getResource(KillTrackerResource.getResourceType()).watch(transaction); + transactionRecords[1] = transaction; + return transactionRecords; + } + } + + @Override + public boolean checkCompletion() { + return this.completed; + } + + @Nonnull + public static String getBountyMarkerIDFromUUID(@Nonnull UUID uuid) { + return "Bounty_" + uuid; + } + + @Override + public void checkKilledEntity( + @Nonnull Store store, @Nonnull Ref npcRef, @Nonnull Objective objective, NPCEntity npc, Damage damageInfo + ) { + UUIDComponent uuidComponent = store.getComponent(npcRef, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID uuid = uuidComponent.getUuid(); + if (this.entityUuid.equals(uuid)) { + this.completed = true; + this.consumeTaskConditions(store, npcRef, objective.getPlayerUUIDs()); + this.complete(objective, store); + objective.checkTaskSetCompletion(store); + this.removeMarker(getBountyMarkerIDFromUUID(uuid)); + } + } + + @Nonnull + public com.hypixel.hytale.protocol.ObjectiveTask toPacket(@Nonnull Objective objective) { + com.hypixel.hytale.protocol.ObjectiveTask packet = new com.hypixel.hytale.protocol.ObjectiveTask(); + packet.taskDescriptionKey = this.asset.getDescriptionKey(objective.getObjectiveId(), this.taskSetIndex, this.taskIndex); + packet.currentCompletion = this.completed ? 1 : 0; + packet.completionNeeded = 1; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "BountyObjectiveTask{completed=" + this.completed + ", entityUuid=" + this.entityUuid + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/KillNPCObjectiveTask.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/KillNPCObjectiveTask.java new file mode 100644 index 0000000..b4a5573 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/KillNPCObjectiveTask.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.task; + +import com.hypixel.hytale.builtin.adventure.npcobjectives.assets.KillObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.npcobjectives.resources.KillTrackerResource; +import com.hypixel.hytale.builtin.adventure.npcobjectives.transaction.KillTaskTransaction; +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionRecord; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class KillNPCObjectiveTask extends KillObjectiveTask { + public static final BuilderCodec CODEC = BuilderCodec.builder( + KillNPCObjectiveTask.class, KillNPCObjectiveTask::new, KillObjectiveTask.CODEC + ) + .build(); + + public KillNPCObjectiveTask(@Nonnull KillObjectiveTaskAsset asset, int taskSetIndex, int taskIndex) { + super(asset, taskSetIndex, taskIndex); + } + + protected KillNPCObjectiveTask() { + } + + @Nonnull + @Override + protected TransactionRecord[] setup0(@Nonnull Objective objective, @Nonnull World world, @Nonnull Store store) { + KillTaskTransaction transaction = new KillTaskTransaction(this, objective, store); + store.getResource(KillTrackerResource.getResourceType()).watch(transaction); + return new TransactionRecord[]{transaction}; + } + + @Nonnull + @Override + public String toString() { + return "KillNPCObjectiveTask{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/KillObjectiveTask.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/KillObjectiveTask.java new file mode 100644 index 0000000..2d8be2e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/KillObjectiveTask.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.task; + +import com.hypixel.hytale.builtin.adventure.npcobjectives.assets.KillObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.task.CountObjectiveTask; +import com.hypixel.hytale.builtin.tagset.TagSetPlugin; +import com.hypixel.hytale.builtin.tagset.config.NPCGroup; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import javax.annotation.Nonnull; + +public abstract class KillObjectiveTask extends CountObjectiveTask implements KillTask { + public static final BuilderCodec CODEC = BuilderCodec.abstractBuilder(KillObjectiveTask.class, CountObjectiveTask.CODEC).build(); + + public KillObjectiveTask(@Nonnull KillObjectiveTaskAsset asset, int taskSetIndex, int taskIndex) { + super(asset, taskSetIndex, taskIndex); + } + + protected KillObjectiveTask() { + } + + @Nonnull + public KillObjectiveTaskAsset getAsset() { + return (KillObjectiveTaskAsset)super.getAsset(); + } + + @Override + public void checkKilledEntity( + @Nonnull Store store, @Nonnull Ref npcRef, @Nonnull Objective objective, @Nonnull NPCEntity npc, @Nonnull Damage info + ) { + String key = this.getAsset().getNpcGroupId(); + int index = NPCGroup.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown npc group! " + key); + } else if (TagSetPlugin.get(NPCGroup.class).tagInSet(index, npc.getNPCTypeIndex())) { + if (info.getSource() instanceof Damage.EntitySource) { + Ref attackerEntityRef = ((Damage.EntitySource)info.getSource()).getRef(); + Entity attackerEntity = EntityUtils.getEntity(attackerEntityRef, attackerEntityRef.getStore()); + if (attackerEntity instanceof Player) { + UUIDComponent attackerUuidComponent = store.getComponent(attackerEntityRef, UUIDComponent.getComponentType()); + + assert attackerUuidComponent != null; + + if (objective.getActivePlayerUUIDs().contains(attackerUuidComponent.getUuid())) { + this.increaseTaskCompletion(store, npcRef, 1, objective); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/KillSpawnBeaconObjectiveTask.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/KillSpawnBeaconObjectiveTask.java new file mode 100644 index 0000000..517c3aa --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/KillSpawnBeaconObjectiveTask.java @@ -0,0 +1,123 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.task; + +import com.hypixel.hytale.builtin.adventure.npcobjectives.assets.KillSpawnBeaconObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.npcobjectives.resources.KillTrackerResource; +import com.hypixel.hytale.builtin.adventure.npcobjectives.transaction.KillTaskTransaction; +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.config.worldlocationproviders.WorldLocationProvider; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.SpawnEntityTransactionRecord; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionRecord; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionUtil; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.WorldTransactionRecord; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.spawning.SpawningPlugin; +import com.hypixel.hytale.server.spawning.assets.spawns.config.BeaconNPCSpawn; +import com.hypixel.hytale.server.spawning.beacons.LegacySpawnBeaconEntity; +import com.hypixel.hytale.server.spawning.wrappers.BeaconSpawnWrapper; +import it.unimi.dsi.fastutil.Pair; +import java.util.Arrays; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class KillSpawnBeaconObjectiveTask extends KillObjectiveTask { + public static final BuilderCodec CODEC = BuilderCodec.builder( + KillSpawnBeaconObjectiveTask.class, KillSpawnBeaconObjectiveTask::new, KillObjectiveTask.CODEC + ) + .build(); + + public KillSpawnBeaconObjectiveTask(@Nonnull KillSpawnBeaconObjectiveTaskAsset asset, int taskSetIndex, int taskIndex) { + super(asset, taskSetIndex, taskIndex); + } + + protected KillSpawnBeaconObjectiveTask() { + } + + @Nonnull + public KillSpawnBeaconObjectiveTaskAsset getAsset() { + return (KillSpawnBeaconObjectiveTaskAsset)super.getAsset(); + } + + @Nonnull + @Override + protected TransactionRecord[] setup0(@Nonnull Objective objective, @Nonnull World world, @Nonnull Store store) { + TransactionRecord[] transactionRecords = this.serializedTransactionRecords; + if (transactionRecords == null) { + transactionRecords = this.setupSpawnBeacons(objective, world, store); + if (TransactionUtil.anyFailed(transactionRecords)) { + return transactionRecords; + } + } + + KillTaskTransaction transaction = new KillTaskTransaction(this, objective, store); + store.getResource(KillTrackerResource.getResourceType()).watch(transaction); + return ArrayUtil.append(transactionRecords, transaction); + } + + @Nonnull + private TransactionRecord[] setupSpawnBeacons(@Nonnull Objective objective, @Nonnull World world, @Nonnull ComponentAccessor componentAccessor) { + Vector3d position = objective.getPosition(componentAccessor); + if (position == null) { + return TransactionRecord.appendFailedTransaction(null, new WorldTransactionRecord(), "No valid position found for the objective."); + } else { + KillSpawnBeaconObjectiveTaskAsset.ObjectiveSpawnBeacon[] spawnBeaconConfigs = this.getAsset().getSpawnBeacons(); + TransactionRecord[] transactionRecords = new TransactionRecord[spawnBeaconConfigs.length]; + HytaleLogger logger = ObjectivePlugin.get().getLogger(); + + for (int i = 0; i < spawnBeaconConfigs.length; i++) { + Vector3d spawnPosition = position.clone(); + KillSpawnBeaconObjectiveTaskAsset.ObjectiveSpawnBeacon spawnBeaconConfig = spawnBeaconConfigs[i]; + String spawnBeaconId = spawnBeaconConfig.getSpawnBeaconId(); + int index = BeaconNPCSpawn.getAssetMap().getIndex(spawnBeaconId); + if (index == Integer.MIN_VALUE) { + transactionRecords[i] = new WorldTransactionRecord().fail("Failed to find spawn beacon " + spawnBeaconId); + return Arrays.copyOf(transactionRecords, i + 1); + } + + Vector3d offset = spawnBeaconConfig.getOffset(); + if (offset != null) { + spawnPosition.add(offset); + } + + WorldLocationProvider worldLocationCondition = spawnBeaconConfig.getWorldLocationProvider(); + if (worldLocationCondition != null) { + spawnPosition = worldLocationCondition.runCondition(world, spawnPosition.toVector3i()).toVector3d(); + } + + if (spawnPosition == null) { + transactionRecords[i] = new WorldTransactionRecord().fail("Failed to find a valid position to spawn beacon " + spawnBeaconId); + } else { + BeaconSpawnWrapper wrapper = SpawningPlugin.get().getBeaconSpawnWrapper(index); + Pair, LegacySpawnBeaconEntity> spawnBeaconPair = LegacySpawnBeaconEntity.create( + wrapper, spawnPosition, Vector3f.FORWARD, componentAccessor + ); + spawnBeaconPair.second().setObjectiveUUID(objective.getObjectiveUUID()); + UUIDComponent spawnBeaconUuidComponent = componentAccessor.getComponent(spawnBeaconPair.first(), UUIDComponent.getComponentType()); + + assert spawnBeaconUuidComponent != null; + + logger.at(Level.INFO).log("Spawned SpawnBeacon '" + spawnBeaconId + "' at position: " + position); + transactionRecords[i] = new SpawnEntityTransactionRecord(world.getWorldConfig().getUuid(), spawnBeaconUuidComponent.getUuid()); + } + } + + return transactionRecords; + } + } + + @Nonnull + @Override + public String toString() { + return "KillSpawnBeaconObjectiveTask{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/KillSpawnMarkerObjectiveTask.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/KillSpawnMarkerObjectiveTask.java new file mode 100644 index 0000000..e1adc3f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/KillSpawnMarkerObjectiveTask.java @@ -0,0 +1,87 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.task; + +import com.hypixel.hytale.builtin.adventure.npcobjectives.assets.KillSpawnMarkerObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.npcobjectives.resources.KillTrackerResource; +import com.hypixel.hytale.builtin.adventure.npcobjectives.transaction.KillTaskTransaction; +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionRecord; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.spawning.SpawningPlugin; +import com.hypixel.hytale.server.spawning.spawnmarkers.SpawnMarkerEntity; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class KillSpawnMarkerObjectiveTask extends KillObjectiveTask { + public static final BuilderCodec CODEC = BuilderCodec.builder( + KillSpawnMarkerObjectiveTask.class, KillSpawnMarkerObjectiveTask::new, KillObjectiveTask.CODEC + ) + .build(); + private static final ComponentType SPAWN_MARKER_COMPONENT_TYPE = SpawnMarkerEntity.getComponentType(); + private static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + + public KillSpawnMarkerObjectiveTask(@Nonnull KillSpawnMarkerObjectiveTaskAsset asset, int taskSetIndex, int taskIndex) { + super(asset, taskSetIndex, taskIndex); + } + + protected KillSpawnMarkerObjectiveTask() { + } + + @Nonnull + public KillSpawnMarkerObjectiveTaskAsset getAsset() { + return (KillSpawnMarkerObjectiveTaskAsset)super.getAsset(); + } + + @Nonnull + @Override + protected TransactionRecord[] setup0(@Nonnull Objective objective, @Nonnull World world, @Nonnull Store store) { + Vector3d objectivePosition = objective.getPosition(store); + if (objectivePosition != null) { + KillSpawnMarkerObjectiveTaskAsset asset = this.getAsset(); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + SpatialResource, EntityStore> spatialResource = store.getResource(SpawningPlugin.get().getSpawnMarkerSpatialResource()); + spatialResource.getSpatialStructure().collect(objectivePosition, asset.getRadius(), results); + String[] spawnMarkerIds = asset.getSpawnMarkerIds(); + HytaleLogger logger = ObjectivePlugin.get().getLogger(); + + for (Ref entityReference : results) { + SpawnMarkerEntity entitySpawnMarkerComponent = store.getComponent(entityReference, SPAWN_MARKER_COMPONENT_TYPE); + + assert entitySpawnMarkerComponent != null; + + String spawnMarkerId = entitySpawnMarkerComponent.getSpawnMarkerId(); + if (ArrayUtil.contains(spawnMarkerIds, spawnMarkerId)) { + world.execute(() -> entitySpawnMarkerComponent.trigger(entityReference, store)); + logger.at(Level.INFO) + .log( + "Triggered SpawnMarker '" + + spawnMarkerId + + "' at position: " + + store.getComponent(entityReference, TRANSFORM_COMPONENT_TYPE).getPosition() + ); + } + } + } + + KillTaskTransaction transaction = new KillTaskTransaction(this, objective, store); + store.getResource(KillTrackerResource.getResourceType()).watch(transaction); + return new TransactionRecord[]{transaction}; + } + + @Nonnull + @Override + public String toString() { + return "KillSpawnMarkerObjectiveTask{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/KillTask.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/KillTask.java new file mode 100644 index 0000000..354a714 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/task/KillTask.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.task; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; + +public interface KillTask { + void checkKilledEntity(Store var1, Ref var2, Objective var3, NPCEntity var4, Damage var5); +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcobjectives/transaction/KillTaskTransaction.java b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/transaction/KillTaskTransaction.java new file mode 100644 index 0000000..d8a0df2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcobjectives/transaction/KillTaskTransaction.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.adventure.npcobjectives.transaction; + +import com.hypixel.hytale.builtin.adventure.npcobjectives.resources.KillTrackerResource; +import com.hypixel.hytale.builtin.adventure.npcobjectives.task.KillTask; +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionRecord; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class KillTaskTransaction extends TransactionRecord { + @Nonnull + private final KillTask task; + @Nonnull + private final Objective objective; + @Nonnull + private final ComponentAccessor componentAccessor; + + public KillTaskTransaction(@Nonnull KillTask task, @Nonnull Objective objective, @Nonnull ComponentAccessor componentAccessor) { + this.task = task; + this.objective = objective; + this.componentAccessor = componentAccessor; + } + + @Override + public void revert() { + this.componentAccessor.getResource(KillTrackerResource.getResourceType()).unwatch(this); + } + + @Override + public void complete() { + this.componentAccessor.getResource(KillTrackerResource.getResourceType()).unwatch(this); + } + + @Override + public void unload() { + this.componentAccessor.getResource(KillTrackerResource.getResourceType()).unwatch(this); + } + + @Nonnull + public KillTask getTask() { + return this.task; + } + + @Nonnull + public Objective getObjective() { + return this.objective; + } + + @Override + public boolean shouldBeSerialized() { + return false; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcreputation/NPCReputationHolderSystem.java b/src/com/hypixel/hytale/builtin/adventure/npcreputation/NPCReputationHolderSystem.java new file mode 100644 index 0000000..5f94b48 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcreputation/NPCReputationHolderSystem.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.builtin.adventure.npcreputation; + +import com.hypixel.hytale.builtin.adventure.reputation.ReputationGroupComponent; +import com.hypixel.hytale.builtin.adventure.reputation.assets.ReputationGroup; +import com.hypixel.hytale.builtin.tagset.TagSetPlugin; +import com.hypixel.hytale.builtin.tagset.config.NPCGroup; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class NPCReputationHolderSystem extends HolderSystem { + private final ComponentType reputationGroupComponentType; + private final ComponentType npcEntityComponentType; + @Nonnull + private final Query query; + + public NPCReputationHolderSystem( + ComponentType reputationGroupComponentType, ComponentType npcEntityComponentType + ) { + this.reputationGroupComponentType = reputationGroupComponentType; + this.npcEntityComponentType = npcEntityComponentType; + this.query = Query.and(npcEntityComponentType, Query.not(reputationGroupComponentType)); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + NPCEntity npcEntity = holder.getComponent(this.npcEntityComponentType); + int npcTypeIndex = npcEntity.getNPCTypeIndex(); + + for (Entry reputationEntry : ReputationGroup.getAssetMap().getAssetMap().entrySet()) { + for (String npcGroup : reputationEntry.getValue().getNpcGroups()) { + int index = NPCGroup.getAssetMap().getIndex(npcGroup); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown npc group! " + npcGroup); + } + + if (TagSetPlugin.get(NPCGroup.class).tagInSet(index, npcTypeIndex)) { + holder.addComponent(this.reputationGroupComponentType, new ReputationGroupComponent(reputationEntry.getKey())); + return; + } + } + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcreputation/NPCReputationPlugin.java b/src/com/hypixel/hytale/builtin/adventure/npcreputation/NPCReputationPlugin.java new file mode 100644 index 0000000..6bf2e93 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcreputation/NPCReputationPlugin.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.builtin.adventure.npcreputation; + +import com.hypixel.hytale.builtin.adventure.reputation.ReputationGroupComponent; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import javax.annotation.Nonnull; + +public class NPCReputationPlugin extends JavaPlugin { + public NPCReputationPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + this.getEntityStoreRegistry().registerSystem(new ReputationAttitudeSystem()); + this.getEntityStoreRegistry().registerSystem(new NPCReputationHolderSystem(ReputationGroupComponent.getComponentType(), NPCEntity.getComponentType())); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcreputation/ReputationAttitudeSystem.java b/src/com/hypixel/hytale/builtin/adventure/npcreputation/ReputationAttitudeSystem.java new file mode 100644 index 0000000..a3c6736 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcreputation/ReputationAttitudeSystem.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.adventure.npcreputation; + +import com.hypixel.hytale.builtin.adventure.reputation.ReputationPlugin; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.StoreSystem; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.blackboard.Blackboard; +import com.hypixel.hytale.server.npc.blackboard.view.attitude.AttitudeView; +import javax.annotation.Nonnull; + +public class ReputationAttitudeSystem extends StoreSystem { + private final ResourceType resourceType = Blackboard.getResourceType(); + + public ReputationAttitudeSystem() { + } + + @Override + public void onSystemAddedToStore(@Nonnull Store store) { + Blackboard blackboard = store.getResource(this.resourceType); + AttitudeView view = blackboard.getView(AttitudeView.class, 0L); + view.registerProvider(100, (ref, role, targetRef, accessor) -> { + Player playerComponent = store.getComponent(targetRef, Player.getComponentType()); + return playerComponent == null ? null : ReputationPlugin.get().getAttitude(store, targetRef, ref); + }); + } + + @Override + public void onSystemRemovedFromStore(@Nonnull Store store) { + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcshop/NPCShopPlugin.java b/src/com/hypixel/hytale/builtin/adventure/npcshop/NPCShopPlugin.java new file mode 100644 index 0000000..9287d53 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcshop/NPCShopPlugin.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.builtin.adventure.npcshop; + +import com.hypixel.hytale.builtin.adventure.npcshop.npc.builders.BuilderActionOpenBarterShop; +import com.hypixel.hytale.builtin.adventure.npcshop.npc.builders.BuilderActionOpenShop; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.npc.NPCPlugin; +import javax.annotation.Nonnull; + +public class NPCShopPlugin extends JavaPlugin { + public NPCShopPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + NPCPlugin.get().registerCoreComponentType("OpenShop", BuilderActionOpenShop::new); + NPCPlugin.get().registerCoreComponentType("OpenBarterShop", BuilderActionOpenBarterShop::new); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/ActionOpenBarterShop.java b/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/ActionOpenBarterShop.java new file mode 100644 index 0000000..68b5f02 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/ActionOpenBarterShop.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.builtin.adventure.npcshop.npc; + +import com.hypixel.hytale.builtin.adventure.npcshop.npc.builders.BuilderActionOpenBarterShop; +import com.hypixel.hytale.builtin.adventure.shop.barter.BarterPage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionOpenBarterShop extends ActionBase { + protected final String shopId; + + public ActionOpenBarterShop(@Nonnull BuilderActionOpenBarterShop builder, @Nonnull BuilderSupport support) { + super(builder); + this.shopId = builder.getShopId(support); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && role.getStateSupport().getInteractionIterationTarget() != null; + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + Ref playerReference = role.getStateSupport().getInteractionIterationTarget(); + if (playerReference == null) { + return false; + } else { + PlayerRef playerRefComponent = store.getComponent(playerReference, PlayerRef.getComponentType()); + if (playerRefComponent == null) { + return false; + } else { + Player playerComponent = store.getComponent(playerReference, Player.getComponentType()); + if (playerComponent == null) { + return false; + } else { + playerComponent.getPageManager().openCustomPage(ref, store, new BarterPage(playerRefComponent, this.shopId)); + return true; + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/ActionOpenShop.java b/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/ActionOpenShop.java new file mode 100644 index 0000000..d5d976b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/ActionOpenShop.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.builtin.adventure.npcshop.npc; + +import com.hypixel.hytale.builtin.adventure.npcshop.npc.builders.BuilderActionOpenShop; +import com.hypixel.hytale.builtin.adventure.shop.ShopPage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionOpenShop extends ActionBase { + protected final String shopId; + + public ActionOpenShop(@Nonnull BuilderActionOpenShop builder, @Nonnull BuilderSupport support) { + super(builder); + this.shopId = builder.getShopId(support); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && role.getStateSupport().getInteractionIterationTarget() != null; + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + Ref playerReference = role.getStateSupport().getInteractionIterationTarget(); + if (playerReference == null) { + return false; + } else { + PlayerRef playerRefComponent = store.getComponent(playerReference, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + Player playerComponent = store.getComponent(playerReference, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getPageManager().openCustomPage(ref, store, new ShopPage(playerRefComponent, this.shopId)); + return true; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/BarterShopExistsValidator.java b/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/BarterShopExistsValidator.java new file mode 100644 index 0000000..627e2f9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/BarterShopExistsValidator.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.builtin.adventure.npcshop.npc; + +import com.hypixel.hytale.builtin.adventure.shop.barter.BarterShopAsset; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class BarterShopExistsValidator extends AssetValidator { + private static final BarterShopExistsValidator DEFAULT_INSTANCE = new BarterShopExistsValidator(); + + private BarterShopExistsValidator() { + } + + private BarterShopExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "BarterShop"; + } + + @Override + public boolean test(String marker) { + return BarterShopAsset.getAssetMap().getAsset(marker) != null; + } + + @Nonnull + @Override + public String errorMessage(String marker, String attributeName) { + return "The barter shop asset with the name \"" + marker + "\" does not exist for attribute \"" + attributeName + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return BarterShopAsset.class.getSimpleName(); + } + + public static BarterShopExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static BarterShopExistsValidator withConfig(EnumSet config) { + return new BarterShopExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/ShopExistsValidator.java b/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/ShopExistsValidator.java new file mode 100644 index 0000000..6bd0b3a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/ShopExistsValidator.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.builtin.adventure.npcshop.npc; + +import com.hypixel.hytale.builtin.adventure.shop.ShopAsset; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class ShopExistsValidator extends AssetValidator { + private static final ShopExistsValidator DEFAULT_INSTANCE = new ShopExistsValidator(); + + private ShopExistsValidator() { + } + + private ShopExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "Shop"; + } + + @Override + public boolean test(String marker) { + return ShopAsset.getAssetMap().getAsset(marker) != null; + } + + @Nonnull + @Override + public String errorMessage(String marker, String attributeName) { + return "The shop asset with the name \"" + marker + "\" does not exist for attribute \"" + attributeName + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return ShopAsset.class.getSimpleName(); + } + + public static ShopExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static ShopExistsValidator withConfig(EnumSet config) { + return new ShopExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/builders/BuilderActionOpenBarterShop.java b/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/builders/BuilderActionOpenBarterShop.java new file mode 100644 index 0000000..c95945f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/builders/BuilderActionOpenBarterShop.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.adventure.npcshop.npc.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.builtin.adventure.npcshop.npc.ActionOpenBarterShop; +import com.hypixel.hytale.builtin.adventure.npcshop.npc.BarterShopExistsValidator; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.instructions.Action; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class BuilderActionOpenBarterShop extends BuilderActionBase { + protected final AssetHolder shopId = new AssetHolder(); + + public BuilderActionOpenBarterShop() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Open the barter shop UI for the current player"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionOpenBarterShop(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionOpenBarterShop readConfig(@Nonnull JsonElement data) { + this.requireAsset(data, "Shop", this.shopId, BarterShopExistsValidator.required(), BuilderDescriptorState.Stable, "The barter shop to open", null); + this.requireInstructionType(EnumSet.of(InstructionType.Interaction)); + return this; + } + + public String getShopId(@Nonnull BuilderSupport support) { + return this.shopId.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/builders/BuilderActionOpenShop.java b/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/builders/BuilderActionOpenShop.java new file mode 100644 index 0000000..c534425 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/npcshop/npc/builders/BuilderActionOpenShop.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.adventure.npcshop.npc.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.builtin.adventure.npcshop.npc.ActionOpenShop; +import com.hypixel.hytale.builtin.adventure.npcshop.npc.ShopExistsValidator; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.instructions.Action; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class BuilderActionOpenShop extends BuilderActionBase { + protected final AssetHolder shopId = new AssetHolder(); + + public BuilderActionOpenShop() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Open the shop UI for the current player"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionOpenShop(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionOpenShop readConfig(@Nonnull JsonElement data) { + this.requireAsset(data, "Shop", this.shopId, ShopExistsValidator.required(), BuilderDescriptorState.Stable, "The shop to open", null); + this.requireInstructionType(EnumSet.of(InstructionType.Interaction)); + return this; + } + + public String getShopId(@Nonnull BuilderSupport support) { + return this.shopId.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectivereputation/ObjectiveReputationPlugin.java b/src/com/hypixel/hytale/builtin/adventure/objectivereputation/ObjectiveReputationPlugin.java new file mode 100644 index 0000000..cb47782 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectivereputation/ObjectiveReputationPlugin.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.adventure.objectivereputation; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.builtin.adventure.objectivereputation.assets.ReputationCompletionAsset; +import com.hypixel.hytale.builtin.adventure.objectivereputation.historydata.ReputationObjectiveRewardHistoryData; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveAsset; +import com.hypixel.hytale.builtin.adventure.objectives.historydata.ObjectiveRewardHistoryData; +import com.hypixel.hytale.builtin.adventure.reputation.assets.ReputationGroup; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import javax.annotation.Nonnull; + +public class ObjectiveReputationPlugin extends JavaPlugin { + protected static ObjectiveReputationPlugin instance; + + public static ObjectiveReputationPlugin get() { + return instance; + } + + public ObjectiveReputationPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + instance = this; + ObjectiveRewardHistoryData.CODEC.register("Reputation", ReputationObjectiveRewardHistoryData.class, ReputationObjectiveRewardHistoryData.CODEC); + ObjectivePlugin.get().registerCompletion("Reputation", ReputationCompletionAsset.class, ReputationCompletionAsset.CODEC, ReputationCompletion::new); + AssetRegistry.getAssetStore(ObjectiveAsset.class).injectLoadsAfter(ReputationGroup.class); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectivereputation/ReputationCompletion.java b/src/com/hypixel/hytale/builtin/adventure/objectivereputation/ReputationCompletion.java new file mode 100644 index 0000000..5fa22a1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectivereputation/ReputationCompletion.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.adventure.objectivereputation; + +import com.hypixel.hytale.builtin.adventure.objectivereputation.assets.ReputationCompletionAsset; +import com.hypixel.hytale.builtin.adventure.objectivereputation.historydata.ReputationObjectiveRewardHistoryData; +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.completion.ObjectiveCompletion; +import com.hypixel.hytale.builtin.adventure.reputation.ReputationPlugin; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ReputationCompletion extends ObjectiveCompletion { + public ReputationCompletion(@Nonnull ReputationCompletionAsset asset) { + super(asset); + } + + @Nonnull + public ReputationCompletionAsset getAsset() { + return (ReputationCompletionAsset)super.getAsset(); + } + + @Override + public void handle(@Nonnull Objective objective, @Nonnull ComponentAccessor componentAccessor) { + ReputationPlugin reputationModule = ReputationPlugin.get(); + objective.forEachParticipant((participantReference, asset, objectiveHistoryData) -> { + Player playerComponent = componentAccessor.getComponent(participantReference, Player.getComponentType()); + if (playerComponent != null) { + UUIDComponent uuidComponent = componentAccessor.getComponent(participantReference, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + String reputationGroupId = asset.getReputationGroupId(); + int amount = asset.getAmount(); + reputationModule.changeReputation(playerComponent, reputationGroupId, amount, componentAccessor); + objectiveHistoryData.addRewardForPlayerUUID(uuidComponent.getUuid(), new ReputationObjectiveRewardHistoryData(reputationGroupId, amount)); + } + }, this.getAsset(), objective.getObjectiveHistoryData()); + } + + @Nonnull + @Override + public String toString() { + return "ReputationCompletion{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectivereputation/assets/ReputationCompletionAsset.java b/src/com/hypixel/hytale/builtin/adventure/objectivereputation/assets/ReputationCompletionAsset.java new file mode 100644 index 0000000..ed8a800 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectivereputation/assets/ReputationCompletionAsset.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.builtin.adventure.objectivereputation.assets; + +import com.hypixel.hytale.builtin.adventure.objectives.config.completion.ObjectiveCompletionAsset; +import com.hypixel.hytale.builtin.adventure.reputation.assets.ReputationGroup; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class ReputationCompletionAsset extends ObjectiveCompletionAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ReputationCompletionAsset.class, ReputationCompletionAsset::new, ObjectiveCompletionAsset.BASE_CODEC + ) + .append( + new KeyedCodec<>("ReputationGroupId", Codec.STRING), + (reputationCompletionAsset, s) -> reputationCompletionAsset.reputationGroupId = s, + reputationCompletionAsset -> reputationCompletionAsset.reputationGroupId + ) + .addValidator(Validators.nonNull()) + .addValidator(ReputationGroup.VALIDATOR_CACHE.getValidator()) + .add() + .append( + new KeyedCodec<>("Amount", Codec.INTEGER), + (reputationCompletionAsset, integer) -> reputationCompletionAsset.amount = integer, + reputationCompletionAsset -> reputationCompletionAsset.amount + ) + .addValidator(Validators.notEqual(0)) + .add() + .build(); + protected String reputationGroupId; + protected int amount = 1; + + public ReputationCompletionAsset(String reputationGroupId, int amount) { + this.reputationGroupId = reputationGroupId; + this.amount = amount; + } + + protected ReputationCompletionAsset() { + } + + public String getReputationGroupId() { + return this.reputationGroupId; + } + + public int getAmount() { + return this.amount; + } + + @Nonnull + @Override + public String toString() { + return "ReputationCompletionAsset{reputationGroupId='" + this.reputationGroupId + "', amount=" + this.amount + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectivereputation/historydata/ReputationObjectiveRewardHistoryData.java b/src/com/hypixel/hytale/builtin/adventure/objectivereputation/historydata/ReputationObjectiveRewardHistoryData.java new file mode 100644 index 0000000..a3c3062 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectivereputation/historydata/ReputationObjectiveRewardHistoryData.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.adventure.objectivereputation.historydata; + +import com.hypixel.hytale.builtin.adventure.objectives.historydata.ObjectiveRewardHistoryData; +import com.hypixel.hytale.builtin.adventure.reputation.assets.ReputationGroup; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public final class ReputationObjectiveRewardHistoryData extends ObjectiveRewardHistoryData { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ReputationObjectiveRewardHistoryData.class, ReputationObjectiveRewardHistoryData::new, ObjectiveRewardHistoryData.BASE_CODEC + ) + .append( + new KeyedCodec<>("ReputationGroupId", Codec.STRING), + (reputationObjectiveRewardDetails, s) -> reputationObjectiveRewardDetails.reputationGroupId = s, + reputationObjectiveRewardDetails -> reputationObjectiveRewardDetails.reputationGroupId + ) + .addValidator(Validators.nonNull()) + .addValidator(ReputationGroup.VALIDATOR_CACHE.getValidator()) + .add() + .append( + new KeyedCodec<>("Amount", Codec.INTEGER), + (reputationObjectiveRewardHistoryData, integer) -> reputationObjectiveRewardHistoryData.amount = integer, + reputationObjectiveRewardHistoryData -> reputationObjectiveRewardHistoryData.amount + ) + .add() + .build(); + protected String reputationGroupId; + protected int amount; + + public ReputationObjectiveRewardHistoryData(String reputationGroupId, int amount) { + this.reputationGroupId = reputationGroupId; + this.amount = amount; + } + + protected ReputationObjectiveRewardHistoryData() { + } + + public String getReputationGroupId() { + return this.reputationGroupId; + } + + public int getAmount() { + return this.amount; + } + + @Nonnull + @Override + public String toString() { + return "ReputationObjectiveRewardHistoryData{reputationGroupId='" + this.reputationGroupId + "', amount=" + this.amount + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/DialogPage.java b/src/com/hypixel/hytale/builtin/adventure/objectives/DialogPage.java new file mode 100644 index 0000000..d06ad12 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/DialogPage.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.builtin.adventure.objectives; + +import com.hypixel.hytale.builtin.adventure.objectives.config.task.UseEntityObjectiveTaskAsset; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class DialogPage extends InteractiveCustomUIPage { + public static final String LAYOUT = "Pages/DialogPage.ui"; + private final UseEntityObjectiveTaskAsset.DialogOptions dialogOptions; + + public DialogPage(@Nonnull PlayerRef playerRef, UseEntityObjectiveTaskAsset.DialogOptions dialogOptions) { + super(playerRef, CustomPageLifetime.CanDismissOrCloseThroughInteraction, DialogPage.DialogPageEventData.CODEC); + this.dialogOptions = dialogOptions; + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/DialogPage.ui"); + commandBuilder.set("#EntityName.Text", Message.translation(this.dialogOptions.getEntityNameKey())); + commandBuilder.set("#Dialog.Text", Message.translation(this.dialogOptions.getDialogKey())); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#CloseButton"); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull DialogPage.DialogPageEventData data) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + playerComponent.getPageManager().setPage(ref, store, Page.None); + } + + public static class DialogPageEventData { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DialogPage.DialogPageEventData.class, DialogPage.DialogPageEventData::new + ) + .build(); + + public DialogPageEventData() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/Objective.java b/src/com/hypixel/hytale/builtin/adventure/objectives/Objective.java new file mode 100644 index 0000000..d17e514 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/Objective.java @@ -0,0 +1,561 @@ +package com.hypixel.hytale.builtin.adventure.objectives; + +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveLineAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.completion.ObjectiveCompletionAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.ObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.TaskSet; +import com.hypixel.hytale.builtin.adventure.objectives.historydata.ObjectiveHistoryData; +import com.hypixel.hytale.builtin.adventure.objectives.historydata.ObjectiveLineHistoryData; +import com.hypixel.hytale.builtin.adventure.objectives.task.ObjectiveTask; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionRecord; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionUtil; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.function.consumer.TriConsumer; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.packets.assets.TrackOrUpdateObjective; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Objective implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(Objective.class, Objective::new) + .append(new KeyedCodec<>("ObjectiveUUID", Codec.UUID_BINARY), (objective, uuid) -> objective.objectiveUUID = uuid, objective -> objective.objectiveUUID) + .add() + .append(new KeyedCodec<>("ObjectiveId", Codec.STRING), (objective, s) -> objective.objectiveId = s, objective -> objective.objectiveId) + .add() + .append( + new KeyedCodec<>("ObjectiveLineData", ObjectiveLineHistoryData.CODEC), + (objective, objectiveLineData) -> objective.objectiveLineHistoryData = objectiveLineData, + objective -> objective.objectiveLineHistoryData + ) + .add() + .append( + new KeyedCodec<>("ObjectiveData", ObjectiveHistoryData.CODEC), + (objective, objectiveHistoryData) -> objective.objectiveHistoryData = objectiveHistoryData, + objective -> objective.objectiveHistoryData + ) + .add() + .append(new KeyedCodec<>("Players", new ArrayCodec<>(Codec.UUID_STRING, UUID[]::new)), (objective, o) -> { + objective.playerUUIDs = new HashSet<>(); + Collections.addAll(objective.playerUUIDs, o); + }, objective -> objective.playerUUIDs.toArray(UUID[]::new)) + .add() + .append( + new KeyedCodec<>("CurrentTasks", new ArrayCodec<>(ObjectiveTask.CODEC, ObjectiveTask[]::new)), + (objective, aObjectiveTasks) -> objective.currentTasks = aObjectiveTasks, + objective -> objective.currentTasks + ) + .add() + .append( + new KeyedCodec<>("CurrentTaskSetIndex", Codec.INTEGER), + (objective, integer) -> objective.currentTaskSetIndex = integer, + objective -> objective.currentTaskSetIndex + ) + .add() + .append(new KeyedCodec<>("WorldUUID", Codec.UUID_BINARY), (objective, s) -> objective.worldUUID = s, objective -> objective.worldUUID) + .add() + .append( + new KeyedCodec<>("ObjectiveItemStarter", ItemStack.CODEC), + (objective, itemStack) -> objective.objectiveItemStarter = itemStack, + objective -> objective.objectiveItemStarter + ) + .add() + .build(); + protected UUID objectiveUUID; + protected String objectiveId; + @Nullable + protected ObjectiveLineHistoryData objectiveLineHistoryData; + protected ObjectiveHistoryData objectiveHistoryData; + protected Set playerUUIDs; + @Nonnull + protected Set activePlayerUUIDs = ConcurrentHashMap.newKeySet(); + @Nullable + protected ObjectiveTask[] currentTasks; + protected int currentTaskSetIndex; + protected boolean completed; + protected UUID worldUUID; + @Nullable + protected UUID markerUUID; + protected boolean dirty; + protected ItemStack objectiveItemStarter; + + public Objective( + @Nonnull ObjectiveAsset asset, @Nullable UUID objectiveUUID, @Nonnull Set playerUUIDs, @Nonnull UUID worldUUID, @Nullable UUID markerUUID + ) { + this.objectiveId = asset.getId(); + this.currentTaskSetIndex = 0; + this.playerUUIDs = playerUUIDs; + this.worldUUID = worldUUID; + this.objectiveUUID = objectiveUUID == null ? UUID.randomUUID() : objectiveUUID; + this.markerUUID = markerUUID; + this.objectiveHistoryData = new ObjectiveHistoryData(asset.getId(), asset.getCategory()); + } + + protected Objective() { + } + + @Nonnull + public UUID getObjectiveUUID() { + return this.objectiveUUID; + } + + @Nonnull + public String getObjectiveId() { + return this.objectiveId; + } + + @Nullable + public ObjectiveAsset getObjectiveAsset() { + return ObjectiveAsset.getAssetMap().getAsset(this.objectiveId); + } + + @Nullable + public ObjectiveLineHistoryData getObjectiveLineHistoryData() { + return this.objectiveLineHistoryData; + } + + public void setObjectiveLineHistoryData(@Nullable ObjectiveLineHistoryData objectiveLineHistoryData) { + this.objectiveLineHistoryData = objectiveLineHistoryData; + } + + @Nonnull + public ObjectiveHistoryData getObjectiveHistoryData() { + return this.objectiveHistoryData; + } + + @Nullable + public ObjectiveLineAsset getObjectiveLineAsset() { + return this.objectiveLineHistoryData == null ? null : ObjectiveLineAsset.getAssetMap().getAsset(this.objectiveLineHistoryData.getId()); + } + + public Set getPlayerUUIDs() { + return this.playerUUIDs; + } + + @Nonnull + public Set getActivePlayerUUIDs() { + return this.activePlayerUUIDs; + } + + @Nullable + public ObjectiveTask[] getCurrentTasks() { + return this.currentTasks; + } + + public int getCurrentTaskSetIndex() { + return this.currentTaskSetIndex; + } + + public String getCurrentDescription() { + ObjectiveAsset objectiveAsset = Objects.requireNonNull(this.getObjectiveAsset()); + TaskSet currentTaskSet = objectiveAsset.getTaskSets()[this.currentTaskSetIndex]; + return currentTaskSet.getDescriptionId() != null + ? currentTaskSet.getDescriptionKey(this.objectiveId, this.currentTaskSetIndex) + : objectiveAsset.getDescriptionKey(); + } + + public boolean isCompleted() { + return this.completed; + } + + public UUID getWorldUUID() { + return this.worldUUID; + } + + @Nullable + public UUID getMarkerUUID() { + return this.markerUUID; + } + + public boolean isDirty() { + return this.dirty; + } + + public ItemStack getObjectiveItemStarter() { + return this.objectiveItemStarter; + } + + public void setObjectiveItemStarter(@Nonnull ItemStack objectiveItemStarter) { + this.objectiveItemStarter = objectiveItemStarter; + } + + public boolean setup(@Nonnull Store componentAccessor) { + ObjectiveAsset objectiveAsset = Objects.requireNonNull(this.getObjectiveAsset()); + ObjectivePlugin objectiveModule = ObjectivePlugin.get(); + TaskSet[] taskSets = objectiveAsset.getTaskSets(); + if (this.currentTaskSetIndex >= taskSets.length) { + objectiveModule.getLogger().at(Level.WARNING).log("Current taskSet index is higher than total number of taskSets for objective %s", this.objectiveId); + return false; + } else { + ObjectiveTaskAsset[] tasks = taskSets[this.currentTaskSetIndex].getTasks(); + ObjectiveTask[] newTasks = new ObjectiveTask[tasks.length]; + + for (int i = 0; i < tasks.length; i++) { + newTasks[i] = objectiveModule.createTask(tasks[i], this.currentTaskSetIndex, i); + } + + this.currentTasks = newTasks; + return this.setupCurrentTasks(componentAccessor); + } + } + + public boolean setupCurrentTasks(@Nonnull Store store) { + for (ObjectiveTask task : this.currentTasks) { + if (!task.isComplete()) { + TransactionRecord[] taskTransactions = task.setup(this, store); + if (taskTransactions != null && TransactionUtil.anyFailed(taskTransactions)) { + ObjectivePlugin.get() + .getLogger() + .at(Level.WARNING) + .log("Failed to setup objective tasks, transaction records:%s", Arrays.toString((Object[])taskTransactions)); + + for (ObjectiveTask taskSetup : this.currentTasks) { + taskSetup.revertTransactionRecords(); + if (taskSetup == task) { + break; + } + } + + return false; + } + } + } + + return true; + } + + public boolean checkTaskSetCompletion(@Nonnull Store store) { + for (ObjectiveTask task : this.currentTasks) { + if (!task.isComplete()) { + return false; + } + } + + this.taskSetComplete(store); + return true; + } + + protected void taskSetComplete(@Nonnull Store store) { + ObjectiveAsset objectiveAsset = Objects.requireNonNull(this.getObjectiveAsset()); + this.currentTaskSetIndex++; + TaskSet[] taskSets = objectiveAsset.getTaskSets(); + if (this.currentTaskSetIndex < taskSets.length) { + if (!this.setup(store)) { + this.taskSetComplete(store); + } else { + TrackOrUpdateObjective trackObjectivePacket = new TrackOrUpdateObjective(this.toPacket()); + this.forEachParticipant((participantReference, message, trackOrUpdateObjective) -> { + PlayerRef playerRefComponent = store.getComponent(participantReference, PlayerRef.getComponentType()); + if (playerRefComponent != null) { + playerRefComponent.sendMessage(message); + playerRefComponent.getPacketHandler().writeNoCache(trackOrUpdateObjective); + } + }, this.getTaskInfoMessage(), trackObjectivePacket); + this.checkTaskSetCompletion(store); + } + } else { + this.complete(store); + } + } + + public void complete(@Nonnull Store store) { + ObjectiveAsset objectiveAsset = Objects.requireNonNull(this.getObjectiveAsset()); + this.forEachParticipant((participantReference, message) -> { + PlayerRef playerRefComponent = store.getComponent(participantReference, PlayerRef.getComponentType()); + if (playerRefComponent != null) { + playerRefComponent.sendMessage(message); + } + }, Message.translation("server.modules.objective.completed").param("title", Message.translation(objectiveAsset.getTitleKey()))); + ObjectivePlugin objectiveModule = ObjectivePlugin.get(); + ObjectiveCompletionAsset[] completionHandlerAssets = objectiveAsset.getCompletionHandlers(); + if (completionHandlerAssets != null) { + for (ObjectiveCompletionAsset objectiveCompletionAsset : completionHandlerAssets) { + objectiveModule.createCompletion(objectiveCompletionAsset).handle(this, store); + } + } + + this.completed = true; + objectiveModule.objectiveCompleted(this, store); + } + + public void cancel() { + for (ObjectiveTask currentTask : this.currentTasks) { + currentTask.revertTransactionRecords(); + } + } + + public void unload() { + for (ObjectiveTask currentTask : this.currentTasks) { + currentTask.unloadTransactionRecords(); + } + } + + @Nonnull + public Message getTaskInfoMessage() { + Message info = Message.translation(this.getCurrentDescription()); + + for (ObjectiveTask task : this.currentTasks) { + info.insert("\n").insert(task.getInfoMessage(this)); + } + + return info; + } + + public void reloadObjectiveAsset(@Nonnull Map reloadedAssets) { + ObjectiveTaskAsset[] taskAssets = this.checkPossibleAssetReload(reloadedAssets); + if (taskAssets != null) { + World world = Universe.get().getWorld(this.worldUUID); + if (world != null) { + world.execute(() -> { + Store store = world.getEntityStore().getStore(); + ObjectiveTask[] newTasks = this.setupAndUpdateTasks(taskAssets, store); + if (newTasks != null) { + this.revertRemovedTasks(newTasks); + this.currentTasks = newTasks; + + for (ObjectiveTask currentTask : this.currentTasks) { + currentTask.assetChanged(this); + } + + if (!this.checkTaskSetCompletion(store)) { + TrackOrUpdateObjective updatePacket = new TrackOrUpdateObjective(this.toPacket()); + this.forEachParticipant((participantReference, packet) -> { + PlayerRef playerRefComponent = store.getComponent(participantReference, PlayerRef.getComponentType()); + if (playerRefComponent != null) { + playerRefComponent.getPacketHandler().writeNoCache(packet); + } + }, updatePacket); + } + } + }); + } + } + } + + @Nullable + private ObjectiveTaskAsset[] checkPossibleAssetReload(@Nonnull Map reloadedAssets) { + ObjectiveLineAsset objectiveLineAsset = this.getObjectiveLineAsset(); + if (this.objectiveLineHistoryData != null && objectiveLineAsset == null) { + this.cancel(); + return null; + } else { + ObjectiveAsset objectiveAsset = reloadedAssets.get(this.objectiveId); + if (objectiveAsset == null) { + return null; + } else { + TaskSet[] taskSets = objectiveAsset.getTaskSets(); + if (this.currentTaskSetIndex > taskSets.length) { + this.cancel(); + return null; + } else { + return taskSets[this.currentTaskSetIndex].getTasks(); + } + } + } + } + + @Nullable + private ObjectiveTask[] setupAndUpdateTasks(@Nonnull ObjectiveTaskAsset[] taskAssets, @Nonnull Store store) { + ObjectiveTask[] newTasks = new ObjectiveTask[taskAssets.length]; + + for (int i = 0; i < taskAssets.length; i++) { + ObjectiveTaskAsset taskAsset = taskAssets[i]; + ObjectiveTask objectiveTask = this.findMatchingObjectiveTask(taskAsset); + if (objectiveTask != null) { + objectiveTask.setAsset(taskAsset); + newTasks[i] = objectiveTask; + } else { + ObjectiveTask newTask = newTasks[i] = ObjectivePlugin.get().createTask(taskAsset, this.currentTaskSetIndex, i); + TransactionRecord[] transactionRecords = newTask.setup(this, store); + if (TransactionUtil.anyFailed(transactionRecords)) { + this.cancelReload(newTasks); + return null; + } + } + } + + return newTasks; + } + + @Nullable + private ObjectiveTask findMatchingObjectiveTask(@Nonnull ObjectiveTaskAsset taskAsset) { + for (ObjectiveTask objectiveTask : this.currentTasks) { + if (objectiveTask.getAsset().matchesAsset(taskAsset)) { + return objectiveTask; + } + } + + return null; + } + + private void cancelReload(@Nonnull ObjectiveTask[] newTasks) { + for (ObjectiveTask taskToRevert : newTasks) { + if (taskToRevert != null) { + taskToRevert.revertTransactionRecords(); + } + } + + this.cancel(); + this.currentTasks = null; + } + + private void revertRemovedTasks(@Nonnull ObjectiveTask[] newTasks) { + for (ObjectiveTask objectiveTask : this.currentTasks) { + boolean foundMatchingTask = false; + + for (ObjectiveTask newTask : newTasks) { + if (newTask.equals(objectiveTask)) { + foundMatchingTask = true; + break; + } + } + + if (!foundMatchingTask) { + objectiveTask.revertTransactionRecords(); + } + } + } + + public void forEachParticipant(@Nonnull Consumer> consumer) { + for (UUID playerUUID : this.playerUUIDs) { + PlayerRef playerRef = Universe.get().getPlayer(playerUUID); + if (playerRef != null) { + consumer.accept(playerRef.getReference()); + } + } + } + + public void forEachParticipant(@Nonnull BiConsumer, T> consumer, T meta) { + for (UUID playerUUID : this.playerUUIDs) { + PlayerRef playerRef = Universe.get().getPlayer(playerUUID); + if (playerRef != null) { + consumer.accept(playerRef.getReference(), meta); + } + } + } + + public void forEachParticipant(@Nonnull TriConsumer, T, U> consumer, @Nonnull T t, @Nonnull U u) { + for (UUID playerUUID : this.playerUUIDs) { + PlayerRef playerRef = Universe.get().getPlayer(playerUUID); + if (playerRef != null) { + consumer.accept(playerRef.getReference(), t, u); + } + } + } + + @Nullable + public Vector3d getPosition(@Nonnull ComponentAccessor componentAccessor) { + UUID entityUUIDToFind = null; + if (this.markerUUID != null) { + entityUUIDToFind = this.markerUUID; + } else if (!this.playerUUIDs.isEmpty()) { + entityUUIDToFind = this.playerUUIDs.iterator().next(); + } + + if (entityUUIDToFind == null) { + return null; + } else { + World world = componentAccessor.getExternalData().getWorld(); + Ref markerEntityReference = world.getEntityRef(entityUUIDToFind); + if (markerEntityReference != null && markerEntityReference.isValid()) { + TransformComponent transformComponent = componentAccessor.getComponent(markerEntityReference, TransformComponent.getComponentType()); + return transformComponent != null ? transformComponent.getPosition() : null; + } else { + return null; + } + } + } + + public void addActivePlayerUUID(UUID playerUUID) { + this.activePlayerUUIDs.add(playerUUID); + } + + public void removeActivePlayerUUID(UUID playerUUID) { + this.activePlayerUUIDs.remove(playerUUID); + } + + public void markDirty() { + this.dirty = true; + } + + public boolean consumeDirty() { + boolean previous = this.dirty; + this.dirty = false; + return previous; + } + + @Nonnull + public com.hypixel.hytale.protocol.Objective toPacket() { + ObjectiveAsset objectiveAsset = Objects.requireNonNull(this.getObjectiveAsset()); + com.hypixel.hytale.protocol.Objective packet = new com.hypixel.hytale.protocol.Objective(); + packet.objectiveUuid = this.objectiveUUID; + packet.objectiveTitleKey = objectiveAsset.getTitleKey(); + packet.objectiveDescriptionKey = this.getCurrentDescription(); + if (this.objectiveLineHistoryData != null) { + packet.objectiveLineId = this.objectiveLineHistoryData.getId(); + } + + packet.tasks = new com.hypixel.hytale.protocol.ObjectiveTask[this.currentTasks.length]; + + for (int i = 0; i < this.currentTasks.length; i++) { + packet.tasks[i] = this.currentTasks[i].toPacket(this); + } + + return packet; + } + + @Nonnull + @Override + public String toString() { + return "Objective{objectiveUUID=" + + this.objectiveUUID + + ", objectiveId='" + + this.objectiveId + + "', objectiveLineHistoryData=" + + this.objectiveLineHistoryData + + ", objectiveHistoryData=" + + this.objectiveHistoryData + + ", playerUUIDs=" + + this.playerUUIDs + + ", activePlayerUUIDs=" + + this.activePlayerUUIDs + + ", currentTasks=" + + Arrays.toString((Object[])this.currentTasks) + + ", currentTaskSetIndex=" + + this.currentTaskSetIndex + + ", completed=" + + this.completed + + ", worldUUID=" + + this.worldUUID + + ", markerUUID=" + + this.markerUUID + + ", dirty=" + + this.dirty + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/ObjectiveDataStore.java b/src/com/hypixel/hytale/builtin/adventure/objectives/ObjectiveDataStore.java new file mode 100644 index 0000000..8c9fd68 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/ObjectiveDataStore.java @@ -0,0 +1,180 @@ +package com.hypixel.hytale.builtin.adventure.objectives; + +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveAsset; +import com.hypixel.hytale.builtin.adventure.objectives.task.ObjectiveTask; +import com.hypixel.hytale.builtin.adventure.objectives.task.ObjectiveTaskRef; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.universe.datastore.DataStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ObjectiveDataStore { + private final Map objectives = new ConcurrentHashMap<>(); + private final Map>> entityObjectiveUUIDsPerPlayer = new ConcurrentHashMap<>(); + @Nonnull + private final DataStore dataStore; + private final Map, Set>> taskRefByType = new ConcurrentHashMap<>(); + @Nonnull + private final HytaleLogger logger; + + public ObjectiveDataStore(@Nonnull DataStore dataStore) { + this.dataStore = dataStore; + this.logger = ObjectivePlugin.get().getLogger(); + } + + public Objective getObjective(UUID objectiveUUID) { + return this.objectives.get(objectiveUUID); + } + + public Map> getEntityTasksForPlayer(UUID playerUUID) { + return this.entityObjectiveUUIDsPerPlayer.get(playerUUID); + } + + @Nonnull + public Collection getObjectiveCollection() { + return this.objectives.values(); + } + + public Set> getTaskRefsForType(Class taskClass) { + return (Set>)this.taskRefByType.get(taskClass); + } + + public void addTaskRef(@Nonnull ObjectiveTaskRef taskRef) { + this.taskRefByType.get(taskRef.getObjectiveTask().getClass()).add(taskRef); + } + + public void removeTaskRef(@Nullable ObjectiveTaskRef taskRef) { + if (taskRef != null) { + this.taskRefByType.get(taskRef.getObjectiveTask().getClass()).remove(taskRef); + } + } + + public void registerTaskRef(Class taskClass) { + this.taskRefByType.put(taskClass, ConcurrentHashMap.newKeySet()); + } + + public void saveToDisk(String objectiveId, @Nonnull Objective objective) { + if (objective.consumeDirty()) { + this.dataStore.save(objectiveId, objective); + } + } + + public void saveToDiskAllObjectives() { + for (Entry entry : this.objectives.entrySet()) { + this.saveToDisk(entry.getKey().toString(), entry.getValue()); + } + } + + public boolean removeFromDisk(String objectiveId) { + try { + this.dataStore.remove(objectiveId); + return true; + } catch (IOException var3) { + this.logger.at(Level.WARNING).withCause(var3).log("Failed removal of objective with UUID: %s", objectiveId); + return false; + } + } + + public boolean addObjective(UUID objectiveUUID, Objective objective) { + return this.objectives.putIfAbsent(objectiveUUID, objective) == null; + } + + public void removeObjective(UUID objectiveUUID) { + this.objectives.remove(objectiveUUID); + } + + public void addEntityTaskForPlayer(UUID playerUUID, String taskId, UUID objectiveUUID) { + this.entityObjectiveUUIDsPerPlayer + .computeIfAbsent(playerUUID, s -> new ConcurrentHashMap<>()) + .computeIfAbsent(taskId, s -> ConcurrentHashMap.newKeySet()) + .add(objectiveUUID); + } + + public void removeEntityTask(UUID objectiveUUID, String taskId) { + Iterator>>> entityObjectiveUUIDsPerPlayerIterator = this.entityObjectiveUUIDsPerPlayer.entrySet().iterator(); + + while (entityObjectiveUUIDsPerPlayerIterator.hasNext()) { + Entry>> entityObjectiveUUIDsEntry = entityObjectiveUUIDsPerPlayerIterator.next(); + Map> entityObjectiveUUIDs = entityObjectiveUUIDsEntry.getValue(); + Set objectiveUUIDs = entityObjectiveUUIDs.get(taskId); + if (objectiveUUIDs != null && objectiveUUIDs.remove(objectiveUUID)) { + if (objectiveUUIDs.isEmpty()) { + entityObjectiveUUIDs.remove(taskId); + } + + if (entityObjectiveUUIDs.isEmpty()) { + entityObjectiveUUIDsPerPlayerIterator.remove(); + } + } + } + } + + public void removeEntityTaskForPlayer(UUID objectiveUUID, String taskId, UUID playerUUID) { + Map> entityObjectiveUUIDs = this.entityObjectiveUUIDsPerPlayer.get(playerUUID); + if (entityObjectiveUUIDs != null) { + Set objectiveUUIDs = entityObjectiveUUIDs.get(taskId); + if (objectiveUUIDs != null) { + if (objectiveUUIDs.remove(objectiveUUID)) { + if (objectiveUUIDs.isEmpty()) { + entityObjectiveUUIDs.remove(taskId); + } + + if (entityObjectiveUUIDs.isEmpty()) { + this.entityObjectiveUUIDsPerPlayer.remove(playerUUID); + } + } + } + } + } + + @Nullable + public Objective loadObjective(@Nonnull UUID objectiveUUID, @Nonnull Store store) { + Objective objective = this.objectives.get(objectiveUUID); + if (objective != null) { + return objective; + } else { + try { + objective = this.dataStore.load(objectiveUUID.toString()); + } catch (IOException var5) { + this.logger.at(Level.WARNING).withCause(var5).log("Unable to load objective with UUID '%s'", objectiveUUID); + return null; + } + + if (objective == null) { + this.logger.at(Level.WARNING).log("No objective saved with UUID '%s'", objectiveUUID); + return null; + } else { + String objectiveId = objective.getObjectiveId(); + if (ObjectiveAsset.getAssetMap().getAsset(objectiveId) == null) { + this.logger.at(Level.WARNING).log("Couldn't find objective '%s'. Skipping objective.", objectiveId); + return null; + } else if (!objective.setupCurrentTasks(store)) { + this.logger.at(Level.WARNING).log("A problem occurred while setting up the objective '%s'. Skipping objective.", objectiveId); + return null; + } else { + this.addObjective(objectiveUUID, objective); + return objective; + } + } + } + } + + public void unloadObjective(UUID objectiveUUID) { + Objective objective = this.objectives.get(objectiveUUID); + if (objective != null) { + objective.unload(); + this.removeObjective(objective.getObjectiveUUID()); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/ObjectivePlugin.java b/src/com/hypixel/hytale/builtin/adventure/objectives/ObjectivePlugin.java new file mode 100644 index 0000000..b9f5287 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/ObjectivePlugin.java @@ -0,0 +1,909 @@ +package com.hypixel.hytale.builtin.adventure.objectives; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.builtin.adventure.objectives.blockstates.TreasureChestState; +import com.hypixel.hytale.builtin.adventure.objectives.commands.ObjectiveCommand; +import com.hypixel.hytale.builtin.adventure.objectives.completion.ClearObjectiveItemsCompletion; +import com.hypixel.hytale.builtin.adventure.objectives.completion.GiveItemsCompletion; +import com.hypixel.hytale.builtin.adventure.objectives.completion.ObjectiveCompletion; +import com.hypixel.hytale.builtin.adventure.objectives.components.ObjectiveHistoryComponent; +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveLineAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveLocationMarkerAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.completion.ClearObjectiveItemsCompletionAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.completion.GiveItemsCompletionAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.completion.ObjectiveCompletionAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.gameplayconfig.ObjectiveGameplayConfig; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.CraftObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.GatherObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.ObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.ReachLocationTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.TreasureMapObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.UseBlockObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.UseEntityObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.historydata.CommonObjectiveHistoryData; +import com.hypixel.hytale.builtin.adventure.objectives.historydata.ItemObjectiveRewardHistoryData; +import com.hypixel.hytale.builtin.adventure.objectives.historydata.ObjectiveHistoryData; +import com.hypixel.hytale.builtin.adventure.objectives.historydata.ObjectiveLineHistoryData; +import com.hypixel.hytale.builtin.adventure.objectives.historydata.ObjectiveRewardHistoryData; +import com.hypixel.hytale.builtin.adventure.objectives.interactions.CanBreakRespawnPointInteraction; +import com.hypixel.hytale.builtin.adventure.objectives.interactions.StartObjectiveInteraction; +import com.hypixel.hytale.builtin.adventure.objectives.markers.ObjectiveMarkerProvider; +import com.hypixel.hytale.builtin.adventure.objectives.markers.objectivelocation.ObjectiveLocationMarker; +import com.hypixel.hytale.builtin.adventure.objectives.markers.objectivelocation.ObjectiveLocationMarkerSystems; +import com.hypixel.hytale.builtin.adventure.objectives.markers.reachlocation.ReachLocationMarker; +import com.hypixel.hytale.builtin.adventure.objectives.markers.reachlocation.ReachLocationMarkerAsset; +import com.hypixel.hytale.builtin.adventure.objectives.markers.reachlocation.ReachLocationMarkerSystems; +import com.hypixel.hytale.builtin.adventure.objectives.systems.ObjectiveItemEntityRemovalSystem; +import com.hypixel.hytale.builtin.adventure.objectives.systems.ObjectivePlayerSetupSystem; +import com.hypixel.hytale.builtin.adventure.objectives.task.CraftObjectiveTask; +import com.hypixel.hytale.builtin.adventure.objectives.task.GatherObjectiveTask; +import com.hypixel.hytale.builtin.adventure.objectives.task.ObjectiveTask; +import com.hypixel.hytale.builtin.adventure.objectives.task.ReachLocationTask; +import com.hypixel.hytale.builtin.adventure.objectives.task.TreasureMapObjectiveTask; +import com.hypixel.hytale.builtin.adventure.objectives.task.UseBlockObjectiveTask; +import com.hypixel.hytale.builtin.adventure.objectives.task.UseEntityObjectiveTask; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.AndQuery; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.function.function.TriFunction; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.packets.assets.TrackOrUpdateObjective; +import com.hypixel.hytale.protocol.packets.assets.UntrackObjective; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData; +import com.hypixel.hytale.server.core.event.events.entity.LivingEntityInventoryChangeEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerDisconnectEvent; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.PersistentModel; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.datastore.DataStoreProvider; +import com.hypixel.hytale.server.core.universe.datastore.DiskDataStoreProvider; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.events.AddWorldEvent; +import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.Config; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ObjectivePlugin extends JavaPlugin { + protected static ObjectivePlugin instance; + public static final String OBJECTIVE_LOCATION_MARKER_MODEL_ID = "Objective_Location_Marker"; + public static final long SAVE_INTERVAL_MINUTES = 5L; + private final Map, TriFunction> taskGenerators = new ConcurrentHashMap<>(); + private final Map, Function> completionGenerators = new ConcurrentHashMap<>(); + private final Config config = this.withConfig(ObjectivePlugin.ObjectivePluginConfig.CODEC); + private Model objectiveLocationMarkerModel; + private ComponentType objectiveHistoryComponentType; + private ComponentType reachLocationMarkerComponentType; + private ComponentType objectiveLocationMarkerComponentType; + private ObjectiveDataStore objectiveDataStore; + + public static ObjectivePlugin get() { + return instance; + } + + public ObjectivePlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + public ComponentType getObjectiveHistoryComponentType() { + return this.objectiveHistoryComponentType; + } + + public Model getObjectiveLocationMarkerModel() { + return this.objectiveLocationMarkerModel; + } + + public ObjectiveDataStore getObjectiveDataStore() { + return this.objectiveDataStore; + } + + @Override + protected void setup() { + instance = this; + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ObjectiveAsset.class, new DefaultAssetMap() + ) + .setPath("Objective/Objectives")) + .setCodec(ObjectiveAsset.CODEC)) + .setKeyFunction(ObjectiveAsset::getId)) + .loadsAfter(ItemDropList.class, Item.class, BlockType.class, ReachLocationMarkerAsset.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ObjectiveLineAsset.class, new DefaultAssetMap() + ) + .setPath("Objective/ObjectiveLines")) + .setCodec(ObjectiveLineAsset.CODEC)) + .setKeyFunction(ObjectiveLineAsset::getId)) + .loadsAfter(ObjectiveAsset.class)) + .loadsBefore(GameplayConfig.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ObjectiveLocationMarkerAsset.class, new DefaultAssetMap() + ) + .setPath("Objective/ObjectiveLocationMarkers")) + .setCodec(ObjectiveLocationMarkerAsset.CODEC)) + .setKeyFunction(ObjectiveLocationMarkerAsset::getId)) + .loadsAfter(ObjectiveAsset.class, Environment.class, Weather.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ReachLocationMarkerAsset.class, new DefaultAssetMap() + ) + .setPath("Objective/ReachLocationMarkers")) + .setCodec(ReachLocationMarkerAsset.CODEC)) + .setKeyFunction(ReachLocationMarkerAsset::getId)) + .build() + ); + this.objectiveDataStore = new ObjectiveDataStore(this.config.get().getDataStoreProvider().create(Objective.CODEC)); + this.reachLocationMarkerComponentType = this.getEntityStoreRegistry() + .registerComponent(ReachLocationMarker.class, "ReachLocationMarker", ReachLocationMarker.CODEC); + this.objectiveLocationMarkerComponentType = this.getEntityStoreRegistry() + .registerComponent(ObjectiveLocationMarker.class, "ObjectiveLocation", ObjectiveLocationMarker.CODEC); + this.registerTask( + "Craft", CraftObjectiveTaskAsset.class, CraftObjectiveTaskAsset.CODEC, CraftObjectiveTask.class, CraftObjectiveTask.CODEC, CraftObjectiveTask::new + ); + this.registerTask( + "Gather", + GatherObjectiveTaskAsset.class, + GatherObjectiveTaskAsset.CODEC, + GatherObjectiveTask.class, + GatherObjectiveTask.CODEC, + GatherObjectiveTask::new + ); + this.registerTask( + "UseBlock", + UseBlockObjectiveTaskAsset.class, + UseBlockObjectiveTaskAsset.CODEC, + UseBlockObjectiveTask.class, + UseBlockObjectiveTask.CODEC, + UseBlockObjectiveTask::new + ); + this.registerTask( + "UseEntity", + UseEntityObjectiveTaskAsset.class, + UseEntityObjectiveTaskAsset.CODEC, + UseEntityObjectiveTask.class, + UseEntityObjectiveTask.CODEC, + UseEntityObjectiveTask::new + ); + this.registerTask( + "TreasureMap", + TreasureMapObjectiveTaskAsset.class, + TreasureMapObjectiveTaskAsset.CODEC, + TreasureMapObjectiveTask.class, + TreasureMapObjectiveTask.CODEC, + TreasureMapObjectiveTask::new + ); + this.registerTask( + "ReachLocation", ReachLocationTaskAsset.class, ReachLocationTaskAsset.CODEC, ReachLocationTask.class, ReachLocationTask.CODEC, ReachLocationTask::new + ); + this.registerCompletion("GiveItems", GiveItemsCompletionAsset.class, GiveItemsCompletionAsset.CODEC, GiveItemsCompletion::new); + this.registerCompletion( + "ClearObjectiveItems", ClearObjectiveItemsCompletionAsset.class, ClearObjectiveItemsCompletionAsset.CODEC, ClearObjectiveItemsCompletion::new + ); + this.getEventRegistry().register(LoadedAssetsEvent.class, ObjectiveLineAsset.class, this::onObjectiveLineAssetLoaded); + this.getEventRegistry().register(LoadedAssetsEvent.class, ObjectiveAsset.class, this::onObjectiveAssetLoaded); + this.getEventRegistry().register(PlayerDisconnectEvent.class, this::onPlayerDisconnect); + this.getEventRegistry().register(LoadedAssetsEvent.class, ObjectiveLocationMarkerAsset.class, ObjectivePlugin::onObjectiveLocationMarkerChange); + this.getEventRegistry().register(LoadedAssetsEvent.class, ModelAsset.class, this::onModelAssetChange); + this.getEventRegistry().registerGlobal(LivingEntityInventoryChangeEvent.class, this::onLivingEntityInventoryChange); + this.getEventRegistry().registerGlobal(AddWorldEvent.class, this::onWorldAdded); + this.getCommandRegistry().registerCommand(new ObjectiveCommand()); + EntityModule entityModule = EntityModule.get(); + ComponentType playerRefComponentType = PlayerRef.getComponentType(); + ResourceType, EntityStore>> playerSpatialComponent = entityModule.getPlayerSpatialResourceType(); + this.getEntityStoreRegistry().registerSystem(new ReachLocationMarkerSystems.EntityAdded(this.reachLocationMarkerComponentType)); + this.getEntityStoreRegistry().registerSystem(new ReachLocationMarkerSystems.EnsureNetworkSendable()); + this.getEntityStoreRegistry().registerSystem(new ReachLocationMarkerSystems.Ticking(this.reachLocationMarkerComponentType, playerSpatialComponent)); + this.getEntityStoreRegistry().registerSystem(new ObjectiveLocationMarkerSystems.EnsureNetworkSendableSystem()); + this.getEntityStoreRegistry().registerSystem(new ObjectiveLocationMarkerSystems.InitSystem(this.objectiveLocationMarkerComponentType)); + this.getEntityStoreRegistry() + .registerSystem( + new ObjectiveLocationMarkerSystems.TickingSystem(this.objectiveLocationMarkerComponentType, playerRefComponentType, playerSpatialComponent) + ); + CommonObjectiveHistoryData.CODEC.register("Objective", ObjectiveHistoryData.class, ObjectiveHistoryData.CODEC); + CommonObjectiveHistoryData.CODEC.register("ObjectiveLine", ObjectiveLineHistoryData.class, ObjectiveLineHistoryData.CODEC); + ObjectiveRewardHistoryData.CODEC.register("Item", ItemObjectiveRewardHistoryData.class, ItemObjectiveRewardHistoryData.CODEC); + this.objectiveHistoryComponentType = this.getEntityStoreRegistry() + .registerComponent(ObjectiveHistoryComponent.class, "ObjectiveHistory", ObjectiveHistoryComponent.CODEC); + this.getEntityStoreRegistry().registerSystem(new ObjectivePlayerSetupSystem(this.objectiveHistoryComponentType, Player.getComponentType())); + this.getEntityStoreRegistry().registerSystem(new ObjectiveItemEntityRemovalSystem()); + this.getCodecRegistry(Interaction.CODEC).register("StartObjective", StartObjectiveInteraction.class, StartObjectiveInteraction.CODEC); + this.getCodecRegistry(Interaction.CODEC).register("CanBreakRespawnPoint", CanBreakRespawnPointInteraction.class, CanBreakRespawnPointInteraction.CODEC); + BlockStateModule.get().registerBlockState(TreasureChestState.class, "TreasureChest", TreasureChestState.CODEC); + this.getCodecRegistry(GameplayConfig.PLUGIN_CODEC).register(ObjectiveGameplayConfig.class, "Objective", ObjectiveGameplayConfig.CODEC); + this.getEntityStoreRegistry() + .registerSystem( + new EntityModule.TangibleMigrationSystem(Query.or(ObjectiveLocationMarker.getComponentType(), ReachLocationMarker.getComponentType())), true + ); + this.getEntityStoreRegistry() + .registerSystem( + new EntityModule.HiddenFromPlayerMigrationSystem(Query.or(ObjectiveLocationMarker.getComponentType(), ReachLocationMarker.getComponentType())), + true + ); + } + + @Override + protected void start() { + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset("Objective_Location_Marker"); + if (modelAsset == null) { + throw new IllegalStateException(String.format("Default objective location marker model '%s' not found", "Objective_Location_Marker")); + } else { + this.objectiveLocationMarkerModel = Model.createUnitScaleModel(modelAsset); + HytaleServer.SCHEDULED_EXECUTOR.scheduleWithFixedDelay(() -> this.objectiveDataStore.saveToDiskAllObjectives(), 5L, 5L, TimeUnit.MINUTES); + } + } + + @Override + protected void shutdown() { + this.objectiveDataStore.saveToDiskAllObjectives(); + } + + public ComponentType getReachLocationMarkerComponentType() { + return this.reachLocationMarkerComponentType; + } + + public ComponentType getObjectiveLocationMarkerComponentType() { + return this.objectiveLocationMarkerComponentType; + } + + public void registerTask( + String id, + Class assetClass, + Codec assetCodec, + Class implementationClass, + Codec implementationCodec, + TriFunction generator + ) { + ObjectiveTaskAsset.CODEC.register(id, assetClass, assetCodec); + ObjectiveTask.CODEC.register(id, implementationClass, implementationCodec); + this.taskGenerators.put(assetClass, generator); + this.objectiveDataStore.registerTaskRef(implementationClass); + } + + public void registerCompletion( + String id, Class assetClass, Codec codec, Function generator + ) { + ObjectiveCompletionAsset.CODEC.register(id, assetClass, codec); + this.completionGenerators.put(assetClass, generator); + } + + public ObjectiveTask createTask(@Nonnull ObjectiveTaskAsset task, int taskSetIndex, int taskIndex) { + return this.taskGenerators.get(task.getClass()).apply(task, taskSetIndex, taskIndex); + } + + public ObjectiveCompletion createCompletion(@Nonnull ObjectiveCompletionAsset completionAsset) { + return this.completionGenerators.get(completionAsset.getClass()).apply(completionAsset); + } + + @Nullable + public Objective startObjective( + @Nonnull String objectiveId, @Nonnull Set playerUUIDs, @Nonnull UUID worldUUID, @Nullable UUID markerUUID, @Nonnull Store store + ) { + return this.startObjective(objectiveId, null, playerUUIDs, worldUUID, markerUUID, store); + } + + @Nullable + public Objective startObjective( + @Nonnull String objectiveId, + @Nullable UUID objectiveUUID, + @Nonnull Set playerUUIDs, + @Nonnull UUID worldUUID, + @Nullable UUID markerUUID, + @Nonnull Store store + ) { + ObjectiveAsset asset = ObjectiveAsset.getAssetMap().getAsset(objectiveId); + if (asset == null) { + this.getLogger().at(Level.WARNING).log("Failed to find objective asset '%s'", objectiveId); + return null; + } else if (markerUUID == null && !asset.isValidForPlayer()) { + this.getLogger().at(Level.WARNING).log("Objective %s can't be used for Player", asset.getId()); + return null; + } else { + Objective objective = new Objective(asset, objectiveUUID, playerUUIDs, worldUUID, markerUUID); + boolean setupResult = objective.setup(store); + Message assetTitleMessage = Message.translation(asset.getTitleKey()); + if (!setupResult || !this.objectiveDataStore.addObjective(objective.getObjectiveUUID(), objective)) { + this.getLogger().at(Level.WARNING).log("Failed to start objective %s", asset.getId()); + if (objective.getPlayerUUIDs() == null) { + return null; + } else { + objective.forEachParticipant(participantReference -> { + PlayerRef playerRefComponent = store.getComponent(participantReference, PlayerRef.getComponentType()); + if (playerRefComponent != null) { + playerRefComponent.sendMessage(Message.translation("server.modules.objective.start.failed").param("title", assetTitleMessage)); + } + }); + return null; + } + } else if (objective.getPlayerUUIDs() == null) { + return objective; + } else { + TrackOrUpdateObjective trackObjectivePacket = new TrackOrUpdateObjective(objective.toPacket()); + String objectiveAssetId = asset.getId(); + objective.forEachParticipant(participantReference -> { + Player playerComponent = store.getComponent(participantReference, Player.getComponentType()); + if (playerComponent != null) { + if (!this.canPlayerDoObjective(playerComponent, objectiveAssetId)) { + playerComponent.sendMessage(Message.translation("server.modules.objective.playerAlreadyDoingObjective").param("title", assetTitleMessage)); + } else { + PlayerRef playerRefComponent = store.getComponent(participantReference, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + UUIDComponent uuidComponent = store.getComponent(participantReference, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + objective.addActivePlayerUUID(uuidComponent.getUuid()); + PlayerConfigData playerConfigData = playerComponent.getPlayerConfigData(); + HashSet activeObjectiveUUIDs = new HashSet<>(playerConfigData.getActiveObjectiveUUIDs()); + activeObjectiveUUIDs.add(objective.getObjectiveUUID()); + playerConfigData.setActiveObjectiveUUIDs(activeObjectiveUUIDs); + playerRefComponent.sendMessage(Message.translation("server.modules.objective.start.success").param("title", assetTitleMessage)); + playerRefComponent.sendMessage(objective.getTaskInfoMessage()); + playerRefComponent.getPacketHandler().writeNoCache(trackObjectivePacket); + } + } + }); + objective.markDirty(); + return objective; + } + } + } + + public boolean canPlayerDoObjective(@Nonnull Player player, @Nonnull String objectiveAssetId) { + Set activeObjectiveUUIDs = player.getPlayerConfigData().getActiveObjectiveUUIDs(); + if (activeObjectiveUUIDs == null) { + return true; + } else { + for (UUID objectiveUUID : activeObjectiveUUIDs) { + Objective objective = this.objectiveDataStore.getObjective(objectiveUUID); + if (objective != null && objective.getObjectiveId().equals(objectiveAssetId)) { + return false; + } + } + + return true; + } + } + + @Nullable + public Objective startObjectiveLine( + @Nonnull Store store, @Nonnull String objectiveLineId, @Nonnull Set playerUUIDs, @Nonnull UUID worldUUID, @Nullable UUID markerUUID + ) { + ObjectiveLineAsset objectiveLineAsset = ObjectiveLineAsset.getAssetMap().getAsset(objectiveLineId); + if (objectiveLineAsset == null) { + return null; + } else { + String[] objectiveIds = objectiveLineAsset.getObjectiveIds(); + if (objectiveIds != null && objectiveIds.length != 0) { + Universe universe = Universe.get(); + HashSet playerList = new HashSet<>(); + + for (UUID playerUUID : playerUUIDs) { + PlayerRef playerRef = universe.getPlayer(playerUUID); + if (playerRef != null) { + Ref playerReference = playerRef.getReference(); + if (playerReference != null && playerReference.isValid()) { + Player playerComponent = store.getComponent(playerReference, Player.getComponentType()); + + assert playerComponent != null; + + if (this.canPlayerDoObjectiveLine(playerComponent, objectiveLineId)) { + playerList.add(playerUUID); + } else { + Message objectiveLineIdMessage = Message.translation(objectiveLineId); + playerRef.sendMessage( + Message.translation("server.modules.objective.playerAlreadyDoingObjectiveLine").param("id", objectiveLineIdMessage) + ); + } + } + } + } + + Objective objective = this.startObjective(objectiveLineAsset.getObjectiveIds()[0], playerList, worldUUID, markerUUID, store); + if (objective == null) { + return null; + } else { + objective.setObjectiveLineHistoryData( + new ObjectiveLineHistoryData(objectiveLineId, objectiveLineAsset.getCategory(), objectiveLineAsset.getNextObjectiveLineIds()) + ); + objective.checkTaskSetCompletion(store); + return objective; + } + } else { + return null; + } + } + } + + public boolean canPlayerDoObjectiveLine(@Nonnull Player player, @Nonnull String objectiveLineId) { + Set activeObjectiveUUIDs = player.getPlayerConfigData().getActiveObjectiveUUIDs(); + if (activeObjectiveUUIDs == null) { + return true; + } else { + for (UUID objectiveUUID : activeObjectiveUUIDs) { + Objective objective = this.objectiveDataStore.getObjective(objectiveUUID); + if (objective != null) { + ObjectiveLineHistoryData objectiveLineHistoryData = objective.getObjectiveLineHistoryData(); + if (objectiveLineHistoryData != null && objectiveLineId.equals(objectiveLineHistoryData.getId())) { + return false; + } + } + } + + return true; + } + } + + public void objectiveCompleted(@Nonnull Objective objective, @Nonnull Store store) { + for (UUID playerUUID : objective.getPlayerUUIDs()) { + this.untrackObjectiveForPlayer(objective, playerUUID); + } + + UUID objectiveUUID = objective.getObjectiveUUID(); + this.objectiveDataStore.removeObjective(objectiveUUID); + if (this.objectiveDataStore.removeFromDisk(objectiveUUID.toString())) { + ObjectiveLineAsset objectiveLineAsset = objective.getObjectiveLineAsset(); + if (objectiveLineAsset == null) { + this.storeObjectiveHistoryData(objective); + } else { + ObjectiveLineHistoryData objectiveLineHistoryData = objective.getObjectiveLineHistoryData(); + + assert objectiveLineHistoryData != null; + + objectiveLineHistoryData.addObjectiveHistoryData(objective.getObjectiveHistoryData()); + String nextObjectiveId = objectiveLineAsset.getNextObjectiveId(objective.getObjectiveId()); + if (nextObjectiveId != null) { + Objective newObjective = this.startObjective( + nextObjectiveId, objectiveUUID, objective.getPlayerUUIDs(), objective.getWorldUUID(), objective.getMarkerUUID(), store + ); + if (newObjective != null) { + newObjective.setObjectiveLineHistoryData(objectiveLineHistoryData); + newObjective.checkTaskSetCompletion(store); + } + } else { + this.storeObjectiveLineHistoryData(objectiveLineHistoryData, objective.getPlayerUUIDs()); + String[] nextObjectiveLineIds = objectiveLineHistoryData.getNextObjectiveLineIds(); + if (nextObjectiveLineIds != null) { + for (String nextObjectiveLineId : nextObjectiveLineIds) { + this.startObjectiveLine(store, nextObjectiveLineId, objective.getPlayerUUIDs(), objective.getWorldUUID(), objective.getMarkerUUID()); + } + } + } + } + } + } + + public void storeObjectiveHistoryData(@Nonnull Objective objective) { + String objectiveId = objective.getObjectiveId(); + Universe universe = Universe.get(); + + for (UUID playerUUID : objective.getPlayerUUIDs()) { + PlayerRef playerRef = universe.getPlayer(playerUUID); + if (playerRef != null && playerRef.isValid()) { + Ref playerReference = playerRef.getReference(); + if (playerReference != null && playerReference.isValid()) { + Store store = playerReference.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + ObjectiveHistoryComponent objectiveHistoryComponent = store.getComponent(playerReference, this.objectiveHistoryComponentType); + + assert objectiveHistoryComponent != null; + + Map completedObjectiveDataMap = objectiveHistoryComponent.getObjectiveHistoryMap(); + ObjectiveHistoryData completedObjectiveData = completedObjectiveDataMap.get(objectiveId); + if (completedObjectiveData != null) { + completedObjectiveData.completed(playerUUID, objective.getObjectiveHistoryData()); + } else { + completedObjectiveDataMap.put(objectiveId, objective.getObjectiveHistoryData().cloneForPlayer(playerUUID)); + } + }); + } + } + } + } + + public void storeObjectiveLineHistoryData(@Nonnull ObjectiveLineHistoryData objectiveLineHistoryData, @Nonnull Set playerUUIDs) { + Map objectiveLineHistoryPerPlayerMap = objectiveLineHistoryData.cloneForPlayers(playerUUIDs); + String objectiveLineId = objectiveLineHistoryData.getId(); + Universe universe = Universe.get(); + + for (Entry entry : objectiveLineHistoryPerPlayerMap.entrySet()) { + UUID playerUUID = entry.getKey(); + PlayerRef playerRef = universe.getPlayer(playerUUID); + if (playerRef != null && playerRef.isValid()) { + Ref playerReference = playerRef.getReference(); + if (playerReference != null && playerReference.isValid()) { + Store store = playerReference.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + ObjectiveHistoryComponent objectiveHistoryComponent = store.getComponent(playerReference, this.objectiveHistoryComponentType); + + assert objectiveHistoryComponent != null; + + Map completedObjectiveLineDataMap = objectiveHistoryComponent.getObjectiveLineHistoryMap(); + ObjectiveLineHistoryData completedObjectiveLineData = completedObjectiveLineDataMap.get(objectiveLineId); + if (completedObjectiveLineData != null) { + completedObjectiveLineData.completed(playerUUID, entry.getValue()); + } else { + completedObjectiveLineDataMap.put(objectiveLineId, entry.getValue()); + } + }); + } + } + } + } + + public void cancelObjective(@Nonnull UUID objectiveUUID, @Nonnull Store store) { + Objective objective = this.objectiveDataStore.loadObjective(objectiveUUID, store); + if (objective != null) { + objective.cancel(); + + for (UUID playerUUID : objective.getPlayerUUIDs()) { + this.untrackObjectiveForPlayer(objective, playerUUID); + } + + this.objectiveDataStore.removeObjective(objectiveUUID); + this.objectiveDataStore.removeFromDisk(objectiveUUID.toString()); + } + } + + public void untrackObjectiveForPlayer(@Nonnull Objective objective, @Nonnull UUID playerUUID) { + UUID objectiveUUID = objective.getObjectiveUUID(); + ObjectiveTask[] currentTasks = objective.getCurrentTasks(); + + for (ObjectiveTask task : currentTasks) { + if (task instanceof UseEntityObjectiveTask) { + this.objectiveDataStore.removeEntityTaskForPlayer(objectiveUUID, ((UseEntityObjectiveTask)task).getAsset().getTaskId(), playerUUID); + } + } + + PlayerRef playerRef = Universe.get().getPlayer(playerUUID); + if (playerRef != null) { + Player player = playerRef.getComponent(Player.getComponentType()); + HashSet activeObjectiveUUIDs = new HashSet<>(player.getPlayerConfigData().getActiveObjectiveUUIDs()); + activeObjectiveUUIDs.remove(objectiveUUID); + player.getPlayerConfigData().setActiveObjectiveUUIDs(activeObjectiveUUIDs); + playerRef.getPacketHandler().writeNoCache(new UntrackObjective(objectiveUUID)); + } + } + + public void addPlayerToExistingObjective(@Nonnull Store store, @Nonnull UUID playerUUID, @Nonnull UUID objectiveUUID) { + Objective objective = this.objectiveDataStore.loadObjective(objectiveUUID, store); + if (objective != null) { + objective.addActivePlayerUUID(playerUUID); + ObjectiveDataStore objectiveDataStore = get().getObjectiveDataStore(); + ObjectiveTask[] currentTasks = objective.getCurrentTasks(); + + for (ObjectiveTask task : currentTasks) { + if (task instanceof UseEntityObjectiveTask) { + objectiveDataStore.addEntityTaskForPlayer(playerUUID, ((UseEntityObjectiveTask)task).getAsset().getTaskId(), objectiveUUID); + } + } + + PlayerRef playerRef = Universe.get().getPlayer(playerUUID); + if (playerRef != null && playerRef.isValid()) { + Ref playerReference = playerRef.getReference(); + if (playerReference != null && playerReference.isValid()) { + Player playerComponent = store.getComponent(playerReference, Player.getComponentType()); + + assert playerComponent != null; + + HashSet activeObjectiveUUIDs = new HashSet<>(playerComponent.getPlayerConfigData().getActiveObjectiveUUIDs()); + activeObjectiveUUIDs.add(objectiveUUID); + playerComponent.getPlayerConfigData().setActiveObjectiveUUIDs(activeObjectiveUUIDs); + playerRef.getPacketHandler().writeNoCache(new TrackOrUpdateObjective(objective.toPacket())); + } + } + } + } + + public void removePlayerFromExistingObjective(@Nonnull Store store, @Nonnull UUID playerUUID, @Nonnull UUID objectiveUUID) { + Objective objective = this.objectiveDataStore.loadObjective(objectiveUUID, store); + if (objective != null) { + objective.removeActivePlayerUUID(playerUUID); + if (objective.getActivePlayerUUIDs().isEmpty()) { + this.objectiveDataStore.saveToDisk(objectiveUUID.toString(), objective); + this.objectiveDataStore.unloadObjective(objectiveUUID); + } + + this.untrackObjectiveForPlayer(objective, playerUUID); + } + } + + private void onPlayerDisconnect(@Nonnull PlayerDisconnectEvent event) { + PlayerRef playerRef = event.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + if (ref.isValid()) { + UUID playerUUID = playerRef.getUuid(); + this.getLogger().at(Level.INFO).log("Checking objectives for disconnecting player '" + playerRef.getUsername() + "' (" + playerUUID + ")"); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + Set activeObjectiveUUIDs = playerComponent.getPlayerConfigData().getActiveObjectiveUUIDs(); + if (activeObjectiveUUIDs == null) { + this.getLogger().at(Level.INFO).log("No active objectives found for player '" + playerRef.getUsername() + "' (" + playerUUID + ")"); + } else { + this.getLogger() + .at(Level.INFO) + .log("Processing " + activeObjectiveUUIDs.size() + " active objectives for '" + playerRef.getUsername() + "' (" + playerUUID + ")"); + + for (UUID objectiveUUID : activeObjectiveUUIDs) { + Objective objective = this.objectiveDataStore.getObjective(objectiveUUID); + if (objective != null) { + objective.removeActivePlayerUUID(playerUUID); + if (objective.getActivePlayerUUIDs().isEmpty()) { + this.objectiveDataStore.saveToDisk(objectiveUUID.toString(), objective); + this.objectiveDataStore.unloadObjective(objectiveUUID); + } + } + } + } + } + } + } + ); + } + } + + private void onObjectiveLineAssetLoaded(@Nonnull LoadedAssetsEvent> event) { + if (this.objectiveDataStore != null) { + for (Entry objectiveLineEntry : event.getLoadedAssets().entrySet()) { + String objectiveLineId = objectiveLineEntry.getKey(); + String[] objectiveIds = objectiveLineEntry.getValue().getObjectiveIds(); + + for (Objective activeObjective : this.objectiveDataStore.getObjectiveCollection()) { + ObjectiveLineHistoryData objectiveLineHistoryData = activeObjective.getObjectiveLineHistoryData(); + if (objectiveLineHistoryData != null + && objectiveLineId.equals(objectiveLineHistoryData.getId()) + && !ArrayUtil.contains(objectiveIds, activeObjective.getObjectiveId())) { + World objectiveWorld = Universe.get().getWorld(activeObjective.worldUUID); + if (objectiveWorld != null) { + objectiveWorld.execute(() -> { + Store store = objectiveWorld.getEntityStore().getStore(); + this.cancelObjective(activeObjective.getObjectiveUUID(), store); + }); + } + break; + } + } + } + } + } + + private void onObjectiveAssetLoaded(@Nonnull LoadedAssetsEvent> event) { + this.objectiveDataStore.getObjectiveCollection().forEach(objective -> objective.reloadObjectiveAsset(event.getLoadedAssets())); + } + + private static void onObjectiveLocationMarkerChange( + @Nonnull LoadedAssetsEvent> event + ) { + Map loadedAssets = event.getLoadedAssets(); + AndQuery query = Query.and( + ObjectiveLocationMarker.getComponentType(), ModelComponent.getComponentType(), TransformComponent.getComponentType() + ); + Universe.get() + .getWorlds() + .forEach( + (s, world) -> world.execute( + () -> { + Store store = world.getEntityStore().getStore(); + store.forEachChunk( + query, + (archetypeChunk, commandBuffer) -> { + for (int index = 0; index < archetypeChunk.size(); index++) { + ObjectiveLocationMarker objectiveLocationMarkerComponent = archetypeChunk.getComponent( + index, ObjectiveLocationMarker.getComponentType() + ); + + assert objectiveLocationMarkerComponent != null; + + ObjectiveLocationMarkerAsset objectiveLocationMarkerAsset = loadedAssets.get( + objectiveLocationMarkerComponent.getObjectiveLocationMarkerId() + ); + if (objectiveLocationMarkerAsset != null) { + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3f rotation = transformComponent.getRotation(); + objectiveLocationMarkerComponent.updateLocationMarkerValues(objectiveLocationMarkerAsset, rotation.getYaw(), store); + ModelComponent modelComponent = archetypeChunk.getComponent(index, ModelComponent.getComponentType()); + + assert modelComponent != null; + + Model oldModel = modelComponent.getModel(); + PersistentModel persistentModelComponent = archetypeChunk.getComponent(index, PersistentModel.getComponentType()); + + assert persistentModelComponent != null; + + Model newModel = new Model( + oldModel.getModelAssetId(), + oldModel.getScale(), + oldModel.getRandomAttachmentIds(), + oldModel.getAttachments(), + objectiveLocationMarkerComponent.getArea().getBoxForEntryArea(), + oldModel.getModel(), + oldModel.getTexture(), + oldModel.getGradientSet(), + oldModel.getGradientId(), + oldModel.getEyeHeight(), + oldModel.getCrouchOffset(), + oldModel.getAnimationSetMap(), + oldModel.getCamera(), + oldModel.getLight(), + oldModel.getParticles(), + oldModel.getTrails(), + oldModel.getPhysicsValues(), + oldModel.getDetailBoxes(), + oldModel.getPhobia(), + oldModel.getPhobiaModelAssetId() + ); + persistentModelComponent.setModelReference(newModel.toReference()); + commandBuffer.putComponent(archetypeChunk.getReferenceTo(index), ModelComponent.getComponentType(), new ModelComponent(newModel)); + } + } + } + ); + } + ) + ); + } + + private void onModelAssetChange(@Nonnull LoadedAssetsEvent> event) { + Map modelMap = event.getLoadedAssets(); + ModelAsset modelAsset = modelMap.get("Objective_Location_Marker"); + if (modelAsset != null) { + this.objectiveLocationMarkerModel = Model.createUnitScaleModel(modelAsset); + } + } + + private void onLivingEntityInventoryChange(@Nonnull LivingEntityInventoryChangeEvent event) { + LivingEntity entity = event.getEntity(); + if (entity instanceof Player player) { + Set activeObjectiveUUIDs = player.getPlayerConfigData().getActiveObjectiveUUIDs(); + if (!activeObjectiveUUIDs.isEmpty()) { + Set inventoryItemObjectiveUUIDs = null; + CombinedItemContainer inventory = entity.getInventory().getCombinedHotbarFirst(); + + for (short i = 0; i < inventory.getCapacity(); i++) { + ItemStack itemStack = inventory.getItemStack(i); + if (!ItemStack.isEmpty(itemStack)) { + UUID objectiveUUID = itemStack.getFromMetadataOrNull(StartObjectiveInteraction.OBJECTIVE_UUID); + if (objectiveUUID != null) { + if (inventoryItemObjectiveUUIDs == null) { + inventoryItemObjectiveUUIDs = new HashSet<>(activeObjectiveUUIDs); + } + + inventoryItemObjectiveUUIDs.add(objectiveUUID); + } + } + } + + for (UUID activeObjectiveUUID : activeObjectiveUUIDs) { + if (inventoryItemObjectiveUUIDs == null || !inventoryItemObjectiveUUIDs.contains(activeObjectiveUUID)) { + Objective objective = this.objectiveDataStore.getObjective(activeObjectiveUUID); + if (objective != null) { + ObjectiveAsset objectiveAsset = objective.getObjectiveAsset(); + if (objectiveAsset != null && objectiveAsset.isRemoveOnItemDrop()) { + Ref reference = entity.getReference(); + Store store = reference.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + UUIDComponent uuidComponent = store.getComponent(reference, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + get().removePlayerFromExistingObjective(store, uuidComponent.getUuid(), activeObjectiveUUID); + }); + } + } + } + } + } + } + } + + private void onWorldAdded(AddWorldEvent event) { + event.getWorld().getWorldMapManager().addMarkerProvider("objectives", ObjectiveMarkerProvider.INSTANCE); + } + + @Nonnull + public String getObjectiveDataDump() { + StringBuilder sb = new StringBuilder("Objective Data\n"); + + for (Objective objective : this.objectiveDataStore.getObjectiveCollection()) { + sb.append("Objective ID: ") + .append(objective.getObjectiveId()) + .append("\n\t") + .append("UUID: ") + .append(objective.getObjectiveUUID()) + .append("\n\t") + .append("Players: ") + .append(Arrays.toString(objective.getPlayerUUIDs().toArray())) + .append("\n\t") + .append("Active players: ") + .append(Arrays.toString(objective.getActivePlayerUUIDs().toArray())) + .append("\n\n"); + } + + return sb.toString(); + } + + public static class ObjectivePluginConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ObjectivePlugin.ObjectivePluginConfig.class, ObjectivePlugin.ObjectivePluginConfig::new + ) + .append( + new KeyedCodec<>("DataStore", DataStoreProvider.CODEC), + (objectivePluginConfig, s) -> objectivePluginConfig.dataStoreProvider = s, + objectivePluginConfig -> objectivePluginConfig.dataStoreProvider + ) + .add() + .build(); + private DataStoreProvider dataStoreProvider = new DiskDataStoreProvider("objectives"); + + public ObjectivePluginConfig() { + } + + public DataStoreProvider getDataStoreProvider() { + return this.dataStoreProvider; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/admin/ObjectiveAdminPanelPage.java b/src/com/hypixel/hytale/builtin/adventure/objectives/admin/ObjectiveAdminPanelPage.java new file mode 100644 index 0000000..5e976ad --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/admin/ObjectiveAdminPanelPage.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.builtin.adventure.objectives.admin; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.server.core.entity.entities.player.pages.BasicCustomUIPage; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import java.util.Arrays; +import java.util.Collection; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class ObjectiveAdminPanelPage extends BasicCustomUIPage { + public ObjectiveAdminPanelPage(@Nonnull PlayerRef playerRef) { + super(playerRef, CustomPageLifetime.CanDismiss); + } + + @Override + public void build(@Nonnull UICommandBuilder commandBuilder) { + commandBuilder.append("Pages/ObjectiveAdminPanelPage.ui"); + Collection objectives = ObjectivePlugin.get().getObjectiveDataStore().getObjectiveCollection(); + int index = 0; + + for (Objective objective : objectives) { + String selector = "#ObjectiveList[" + index + "]"; + commandBuilder.append("#ObjectiveList", "Pages/ObjectiveAdminPanelDataSlot.ui"); + commandBuilder.set(selector + " #Id.Text", objective.getObjectiveId()); + commandBuilder.set(selector + " #UUID.Text", "Objective UUID: " + objective.getObjectiveUUID().toString()); + StringBuilder stringBuilder = new StringBuilder(); + Universe universe = Universe.get(); + + for (UUID playerUUID : objective.getActivePlayerUUIDs()) { + PlayerRef player = universe.getPlayer(playerUUID); + if (player != null) { + if (!stringBuilder.isEmpty()) { + stringBuilder.append(", "); + } + + stringBuilder.append(player.getUsername()); + } + } + + commandBuilder.set(selector + " #CurrentPlayers.Text", "Current players: " + stringBuilder.toString()); + commandBuilder.set(selector + " #AllTimePlayers.Text", "All time players: " + Arrays.toString(objective.getPlayerUUIDs().toArray())); + index++; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/blockstates/TreasureChestState.java b/src/com/hypixel/hytale/builtin/adventure/objectives/blockstates/TreasureChestState.java new file mode 100644 index 0000000..1649d5f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/blockstates/TreasureChestState.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.builtin.adventure.objectives.blockstates; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.events.TreasureChestOpeningEvent; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.meta.state.BreakValidatedBlockState; +import com.hypixel.hytale.server.core.universe.world.meta.state.ItemContainerState; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class TreasureChestState extends ItemContainerState implements BreakValidatedBlockState { + public static final BuilderCodec CODEC = BuilderCodec.builder(TreasureChestState.class, TreasureChestState::new, BlockState.BASE_CODEC) + .append( + new KeyedCodec<>("ObjectiveUUID", Codec.UUID_BINARY), + (treasureChestState, uuid) -> treasureChestState.objectiveUUID = uuid, + treasureChestState -> treasureChestState.objectiveUUID + ) + .add() + .append( + new KeyedCodec<>("ChestUUID", Codec.UUID_BINARY), + (treasureChestState, uuid) -> treasureChestState.chestUUID = uuid, + treasureChestState -> treasureChestState.chestUUID + ) + .add() + .append( + new KeyedCodec<>("Opened", Codec.BOOLEAN), + (treasureChestState, aBoolean) -> treasureChestState.opened = aBoolean, + treasureChestState -> treasureChestState.opened + ) + .add() + .build(); + protected UUID objectiveUUID; + protected UUID chestUUID; + protected boolean opened; + + public TreasureChestState() { + } + + @Override + public boolean canOpen(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + if (!this.opened) { + UUIDComponent uuidComponent = componentAccessor.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + Objective objective = ObjectivePlugin.get().getObjectiveDataStore().getObjective(this.objectiveUUID); + return objective != null && objective.getActivePlayerUUIDs().contains(uuidComponent.getUuid()); + } else { + return true; + } + } + + @Override + public boolean canDestroy(@Nonnull Ref playerRef, @Nonnull ComponentAccessor componentAccessor) { + return this.opened; + } + + @Override + public void onOpen(@Nonnull Ref ref, @Nonnull World world, @Nonnull Store store) { + IEventDispatcher dispatcher = HytaleServer.get() + .getEventBus() + .dispatchFor(TreasureChestOpeningEvent.class, world.getName()); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new TreasureChestOpeningEvent(this.objectiveUUID, this.chestUUID, ref, store)); + } + + this.setOpened(true); + } + + public void setOpened(boolean opened) { + this.opened = opened; + } + + public void setObjectiveData(UUID objectiveUUID, UUID chestUUID, List itemStacks) { + this.objectiveUUID = objectiveUUID; + this.chestUUID = chestUUID; + this.itemContainer.addItemStacks(itemStacks); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveCommand.java b/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveCommand.java new file mode 100644 index 0000000..92e6a6a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveCommand.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.builtin.adventure.objectives.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class ObjectiveCommand extends AbstractCommandCollection { + public ObjectiveCommand() { + super("objective", "server.commands.objective"); + this.addAliases("obj"); + this.addSubCommand(new ObjectiveStartCommand()); + this.addSubCommand(new ObjectiveCompleteCommand()); + this.addSubCommand(new ObjectivePanelCommand()); + this.addSubCommand(new ObjectiveHistoryCommand()); + this.addSubCommand(new ObjectiveLocationMarkerCommand()); + this.addSubCommand(new ObjectiveReachLocationMarkerCommand()); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveCompleteCommand.java b/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveCompleteCommand.java new file mode 100644 index 0000000..e48b11f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveCompleteCommand.java @@ -0,0 +1,164 @@ +package com.hypixel.hytale.builtin.adventure.objectives.commands; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectiveDataStore; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.task.ObjectiveTask; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ObjectiveCompleteCommand extends AbstractCommandCollection { + @Nonnull + private static final Message MESSAGE_COMMANDS_OBJECTIVE_OBJECTIVE_NOT_FOUND = Message.translation("server.commands.objective.objectiveNotFound"); + @Nonnull + private static final Message MESSAGE_COMMANDS_OBJECTIVE_NO_TASK_FOR_INDEX = Message.translation("server.commands.objective.noTaskForIndex"); + @Nonnull + private static final Message MESSAGE_COMMANDS_OBJECTIVE_TASK_ALREADY_COMPLETED = Message.translation("server.commands.objective.taskAlreadyCompleted"); + + public ObjectiveCompleteCommand() { + super("complete", "server.commands.objective.complete"); + this.addSubCommand(new ObjectiveCompleteCommand.CompleteTaskCommand()); + this.addSubCommand(new ObjectiveCompleteCommand.CompleteTaskSetCommand()); + this.addSubCommand(new ObjectiveCompleteCommand.CompleteObjectiveCommand()); + } + + @Nullable + private static Objective getObjectiveFromId( + @Nonnull Ref participantRef, @Nonnull String objectiveId, @Nonnull ComponentAccessor componentAccessor + ) { + Player playerComponent = componentAccessor.getComponent(participantRef, Player.getComponentType()); + if (playerComponent == null) { + return null; + } else { + Set activeObjectiveUUIDs = playerComponent.getPlayerConfigData().getActiveObjectiveUUIDs(); + ObjectiveDataStore objectiveDataStore = ObjectivePlugin.get().getObjectiveDataStore(); + + for (UUID objectiveUUID : activeObjectiveUUIDs) { + Objective objective = objectiveDataStore.getObjective(objectiveUUID); + if (objective != null && objective.getObjectiveId().equals(objectiveId)) { + return objective; + } + } + + return null; + } + } + + public static class CompleteObjectiveCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg objectiveArg = this.withRequiredArg( + "objectiveId", "server.commands.objective.complete.objective.arg.objectiveId.desc", ArgTypes.STRING + ); + + public CompleteObjectiveCommand() { + super("objective", "server.commands.objective.complete.objective"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + String objectiveId = this.objectiveArg.get(context); + Objective objective = ObjectiveCompleteCommand.getObjectiveFromId(ref, objectiveId, store); + if (objective == null) { + context.sendMessage(ObjectiveCompleteCommand.MESSAGE_COMMANDS_OBJECTIVE_OBJECTIVE_NOT_FOUND); + } else { + ObjectiveTask[] tasks = objective.getCurrentTasks(); + if (tasks == null) { + context.sendMessage(ObjectiveCompleteCommand.MESSAGE_COMMANDS_OBJECTIVE_NO_TASK_FOR_INDEX); + } else { + for (ObjectiveTask task : tasks) { + task.completeTransactionRecords(); + } + + objective.complete(store); + } + } + } + } + + public static class CompleteTaskCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg objectiveArg = this.withRequiredArg( + "objectiveId", "server.commands.objective.complete.task.arg.objectiveId.desc", ArgTypes.STRING + ); + @Nonnull + private final RequiredArg taskIndexArg = this.withRequiredArg( + "taskIndex", "server.commands.objective.complete.task.arg.taskIndex.desc", ArgTypes.INTEGER + ); + + public CompleteTaskCommand() { + super("task", "server.commands.objective.complete.task"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + String objectiveId = this.objectiveArg.get(context); + int taskIndex = this.taskIndexArg.get(context); + Objective objective = ObjectiveCompleteCommand.getObjectiveFromId(ref, objectiveId, store); + if (objective == null) { + context.sendMessage(ObjectiveCompleteCommand.MESSAGE_COMMANDS_OBJECTIVE_OBJECTIVE_NOT_FOUND); + } else { + ObjectiveTask[] tasks = objective.getCurrentTasks(); + if (taskIndex >= tasks.length) { + context.sendMessage(ObjectiveCompleteCommand.MESSAGE_COMMANDS_OBJECTIVE_NO_TASK_FOR_INDEX); + } else if (tasks[taskIndex].isComplete()) { + context.sendMessage(ObjectiveCompleteCommand.MESSAGE_COMMANDS_OBJECTIVE_TASK_ALREADY_COMPLETED); + } else { + tasks[taskIndex].complete(objective, store); + objective.checkTaskSetCompletion(store); + } + } + } + } + + public static class CompleteTaskSetCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg objectiveArg = this.withRequiredArg( + "objectiveId", "server.commands.objective.complete.taskSet.arg.objectiveId.desc", ArgTypes.STRING + ); + + public CompleteTaskSetCommand() { + super("taskSet", "server.commands.objective.complete.taskSet"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + String objectiveId = this.objectiveArg.get(context); + Objective objective = ObjectiveCompleteCommand.getObjectiveFromId(ref, objectiveId, store); + if (objective == null) { + context.sendMessage(ObjectiveCompleteCommand.MESSAGE_COMMANDS_OBJECTIVE_OBJECTIVE_NOT_FOUND); + } else { + ObjectiveTask[] tasks = objective.getCurrentTasks(); + if (tasks != null && tasks.length != 0) { + for (ObjectiveTask task : tasks) { + task.complete(objective, store); + } + + objective.checkTaskSetCompletion(store); + } else { + context.sendMessage(ObjectiveCompleteCommand.MESSAGE_COMMANDS_OBJECTIVE_NO_TASK_FOR_INDEX); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveHistoryCommand.java b/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveHistoryCommand.java new file mode 100644 index 0000000..b921c77 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveHistoryCommand.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.adventure.objectives.commands; + +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.components.ObjectiveHistoryComponent; +import com.hypixel.hytale.builtin.adventure.objectives.historydata.ObjectiveHistoryData; +import com.hypixel.hytale.builtin.adventure.objectives.historydata.ObjectiveLineHistoryData; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import javax.annotation.Nonnull; + +public class ObjectiveHistoryCommand extends AbstractPlayerCommand { + public ObjectiveHistoryCommand() { + super("history", "server.commands.objective.history"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + StringBuilder sb = new StringBuilder("Completed objectives\n"); + ObjectiveHistoryComponent objectiveHistoryComponent = store.getComponent(ref, ObjectivePlugin.get().getObjectiveHistoryComponentType()); + + assert objectiveHistoryComponent != null; + + Map objectiveDataMap = objectiveHistoryComponent.getObjectiveHistoryMap(); + + for (ObjectiveHistoryData objectiveHistory : objectiveDataMap.values()) { + sb.append(objectiveHistory).append("\n"); + } + + sb.append("\nCompleted objective lines\n"); + Map objectiveLineDataMap = objectiveHistoryComponent.getObjectiveLineHistoryMap(); + + for (ObjectiveLineHistoryData objectiveLineHistory : objectiveLineDataMap.values()) { + sb.append(objectiveLineHistory).append("\n"); + } + + context.sendMessage(Message.raw(sb.toString())); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveLocationMarkerCommand.java b/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveLocationMarkerCommand.java new file mode 100644 index 0000000..a6f804a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveLocationMarkerCommand.java @@ -0,0 +1,130 @@ +package com.hypixel.hytale.builtin.adventure.objectives.commands; + +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveLocationMarkerAsset; +import com.hypixel.hytale.builtin.adventure.objectives.markers.objectivelocation.ObjectiveLocationMarker; +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.nameplate.Nameplate; +import com.hypixel.hytale.server.core.modules.entity.component.HiddenFromAdventurePlayers; +import com.hypixel.hytale.server.core.modules.entity.component.Intangible; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.PersistentModel; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ObjectiveLocationMarkerCommand extends AbstractCommandCollection { + @Nonnull + private static final Message MESSAGE_COMMANDS_OBJECTIVE_LOCATION_MARKER_NOT_FOUND = Message.translation("server.commands.objective.locationMarker.notFound"); + @Nonnull + private static final Message MESSAGE_GENERAL_FAILED_DID_YOU_MEAN = Message.translation("server.general.failed.didYouMean"); + @Nonnull + private static final Message MESSAGE_COMMANDS_OBJECTIVE_LOCATION_MARKER_ADDED = Message.translation("server.commands.objective.locationMarker.added"); + @Nonnull + private static final Message MESSAGE_COMMANDS_OBJECTIVE_LOCATION_MARKER_ENABLED = Message.translation("server.commands.objective.locationMarker.enabled"); + @Nonnull + private static final Message MESSAGE_COMMANDS_OBJECTIVE_LOCATION_MARKER_DISABLED = Message.translation("server.commands.objective.locationMarker.disabled"); + + public ObjectiveLocationMarkerCommand() { + super("locationmarker", "server.commands.objective.locationMarker"); + this.addAliases("marker"); + this.addSubCommand(new ObjectiveLocationMarkerCommand.AddLocationMarkerCommand()); + this.addSubCommand(new ObjectiveLocationMarkerCommand.EnableLocationMarkerCommand()); + this.addSubCommand(new ObjectiveLocationMarkerCommand.DisableLocationMarkerCommand()); + } + + public static class AddLocationMarkerCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg locationMarkerArg = this.withRequiredArg( + "locationMarkerId", "server.commands.objective.locationMarker.add.arg.locationMarkerId.desc", ArgTypes.STRING + ); + + public AddLocationMarkerCommand() { + super("add", "server.commands.objective.locationMarker.add"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Ref playerReference = playerRef.getReference(); + TransformComponent playerTransformComponent = store.getComponent(playerReference, TransformComponent.getComponentType()); + + assert playerTransformComponent != null; + + String objectiveLocationMarkerId = this.locationMarkerArg.get(context); + if (ObjectiveLocationMarkerAsset.getAssetMap().getAsset(objectiveLocationMarkerId) == null) { + context.sendMessage(ObjectiveLocationMarkerCommand.MESSAGE_COMMANDS_OBJECTIVE_LOCATION_MARKER_NOT_FOUND.param("id", objectiveLocationMarkerId)); + context.sendMessage( + ObjectiveLocationMarkerCommand.MESSAGE_GENERAL_FAILED_DID_YOU_MEAN + .param( + "choices", + StringUtil.sortByFuzzyDistance( + objectiveLocationMarkerId, ObjectiveLocationMarkerAsset.getAssetMap().getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT + ) + .toString() + ) + ); + } else { + Holder holder = EntityStore.REGISTRY.newHolder(); + holder.addComponent(ObjectiveLocationMarker.getComponentType(), new ObjectiveLocationMarker(objectiveLocationMarkerId)); + Model model = ObjectivePlugin.get().getObjectiveLocationMarkerModel(); + holder.addComponent(ModelComponent.getComponentType(), new ModelComponent(model)); + holder.addComponent(PersistentModel.getComponentType(), new PersistentModel(model.toReference())); + holder.addComponent(Nameplate.getComponentType(), new Nameplate(objectiveLocationMarkerId)); + TransformComponent transform = new TransformComponent(playerTransformComponent.getPosition(), playerTransformComponent.getRotation()); + holder.addComponent(TransformComponent.getComponentType(), transform); + holder.ensureComponent(UUIDComponent.getComponentType()); + holder.ensureComponent(Intangible.getComponentType()); + holder.ensureComponent(HiddenFromAdventurePlayers.getComponentType()); + store.addEntity(holder, AddReason.SPAWN); + context.sendMessage(ObjectiveLocationMarkerCommand.MESSAGE_COMMANDS_OBJECTIVE_LOCATION_MARKER_ADDED.param("id", objectiveLocationMarkerId)); + } + } + } + + public static class DisableLocationMarkerCommand extends AbstractWorldCommand { + public DisableLocationMarkerCommand() { + super("disable", "server.commands.objective.locationMarker.disable"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + WorldConfig worldConfig = world.getWorldConfig(); + worldConfig.setObjectiveMarkersEnabled(false); + worldConfig.markChanged(); + context.sendMessage(ObjectiveLocationMarkerCommand.MESSAGE_COMMANDS_OBJECTIVE_LOCATION_MARKER_DISABLED.param("worldName", world.getName())); + } + } + + public static class EnableLocationMarkerCommand extends AbstractWorldCommand { + public EnableLocationMarkerCommand() { + super("enable", "server.commands.objective.locationMarker.enable"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + WorldConfig worldConfig = world.getWorldConfig(); + worldConfig.setObjectiveMarkersEnabled(true); + worldConfig.markChanged(); + context.sendMessage(ObjectiveLocationMarkerCommand.MESSAGE_COMMANDS_OBJECTIVE_LOCATION_MARKER_ENABLED.param("worldName", world.getName())); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectivePanelCommand.java b/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectivePanelCommand.java new file mode 100644 index 0000000..2639ca3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectivePanelCommand.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.builtin.adventure.objectives.commands; + +import com.hypixel.hytale.builtin.adventure.objectives.admin.ObjectiveAdminPanelPage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ObjectivePanelCommand extends AbstractPlayerCommand { + public ObjectivePanelCommand() { + super("panel", "server.commands.objective.panel"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getPageManager().openCustomPage(ref, store, new ObjectiveAdminPanelPage(playerRef)); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveReachLocationMarkerCommand.java b/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveReachLocationMarkerCommand.java new file mode 100644 index 0000000..b2622d9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveReachLocationMarkerCommand.java @@ -0,0 +1,98 @@ +package com.hypixel.hytale.builtin.adventure.objectives.commands; + +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.markers.reachlocation.ReachLocationMarker; +import com.hypixel.hytale.builtin.adventure.objectives.markers.reachlocation.ReachLocationMarkerAsset; +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.nameplate.Nameplate; +import com.hypixel.hytale.server.core.modules.entity.component.HiddenFromAdventurePlayers; +import com.hypixel.hytale.server.core.modules.entity.component.Intangible; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.PersistentModel; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ObjectiveReachLocationMarkerCommand extends AbstractCommandCollection { + @Nonnull + private static final Message MESSAGE_COMMANDS_OBJECTIVE_REACH_LOCATION_MARKER_NOT_FOUND = Message.translation( + "server.commands.objective.reachLocationMarker.notFound" + ); + @Nonnull + private static final Message MESSAGE_GENERAL_FAILED_DID_YOU_MEAN = Message.translation("server.general.failed.didYouMean"); + @Nonnull + private static final Message MESSAGE_COMMANDS_OBJECTIVE_REACH_LOCATION_MARKER_ADDED = Message.translation( + "server.commands.objective.reachLocationMarker.added" + ); + + public ObjectiveReachLocationMarkerCommand() { + super("reachlocationmarker", "server.commands.objective.reachLocationMarker"); + this.addSubCommand(new ObjectiveReachLocationMarkerCommand.AddReachLocationMarkerCommand()); + } + + public static class AddReachLocationMarkerCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg reachLocationMarkerArg = this.withRequiredArg( + "reachLocationMarkerId", "server.commands.objective.reachLocationMarker.add.arg.reachLocationMarkerId.desc", ArgTypes.STRING + ); + + public AddReachLocationMarkerCommand() { + super("add", "server.commands.objective.reachLocationMarker.add"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + String reachLocationMarkerId = this.reachLocationMarkerArg.get(context); + if (ReachLocationMarkerAsset.getAssetMap().getAsset(reachLocationMarkerId) == null) { + context.sendMessage( + ObjectiveReachLocationMarkerCommand.MESSAGE_COMMANDS_OBJECTIVE_REACH_LOCATION_MARKER_NOT_FOUND.param("id", reachLocationMarkerId) + ); + context.sendMessage( + ObjectiveReachLocationMarkerCommand.MESSAGE_GENERAL_FAILED_DID_YOU_MEAN + .param( + "choices", + StringUtil.sortByFuzzyDistance( + reachLocationMarkerId, ReachLocationMarkerAsset.getAssetMap().getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT + ) + .toString() + ) + ); + } else { + Holder holder = EntityStore.REGISTRY.newHolder(); + holder.addComponent(ReachLocationMarker.getComponentType(), new ReachLocationMarker(reachLocationMarkerId)); + Model model = ObjectivePlugin.get().getObjectiveLocationMarkerModel(); + holder.addComponent(ModelComponent.getComponentType(), new ModelComponent(model)); + holder.addComponent(PersistentModel.getComponentType(), new PersistentModel(model.toReference())); + holder.addComponent(Nameplate.getComponentType(), new Nameplate(reachLocationMarkerId)); + TransformComponent playerTransformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert playerTransformComponent != null; + + TransformComponent transform = new TransformComponent(playerTransformComponent.getPosition(), playerTransformComponent.getRotation()); + holder.addComponent(TransformComponent.getComponentType(), transform); + holder.ensureComponent(UUIDComponent.getComponentType()); + holder.ensureComponent(Intangible.getComponentType()); + holder.ensureComponent(HiddenFromAdventurePlayers.getComponentType()); + store.addEntity(holder, AddReason.SPAWN); + context.sendMessage(ObjectiveReachLocationMarkerCommand.MESSAGE_COMMANDS_OBJECTIVE_REACH_LOCATION_MARKER_ADDED.param("id", reachLocationMarkerId)); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveStartCommand.java b/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveStartCommand.java new file mode 100644 index 0000000..372dc99 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/commands/ObjectiveStartCommand.java @@ -0,0 +1,105 @@ +package com.hypixel.hytale.builtin.adventure.objectives.commands; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveLineAsset; +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.HashSet; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class ObjectiveStartCommand extends AbstractCommandCollection { + public ObjectiveStartCommand() { + super("start", "server.commands.objective.start"); + this.addSubCommand(new ObjectiveStartCommand.StartObjectiveCommand()); + this.addSubCommand(new ObjectiveStartCommand.StartObjectiveLineCommand()); + } + + public static class StartObjectiveCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_OBJECTIVE_OBJECTIVE_NOT_FOUND = Message.translation("server.commands.objective.objectiveNotFound"); + @Nonnull + private static final Message MESSAGE_GENERAL_FAILED_DID_YOU_MEAN = Message.translation("server.general.failed.didYouMean"); + @Nonnull + private final RequiredArg objectiveArg = this.withRequiredArg( + "objectiveId", "server.commands.objective.start.objective.arg.objectiveId.desc", ArgTypes.STRING + ); + + public StartObjectiveCommand() { + super("objective", "server.commands.objective.start.objective"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + String objectiveId = this.objectiveArg.get(context); + ObjectiveAsset asset = ObjectiveAsset.getAssetMap().getAsset(objectiveId); + if (asset == null) { + context.sendMessage(MESSAGE_COMMANDS_OBJECTIVE_OBJECTIVE_NOT_FOUND.param("id", objectiveId)); + context.sendMessage( + MESSAGE_GENERAL_FAILED_DID_YOU_MEAN.param( + "choices", + StringUtil.sortByFuzzyDistance(objectiveId, ObjectiveAsset.getAssetMap().getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT).toString() + ) + ); + } else { + HashSet playerSet = new HashSet<>(); + playerSet.add(playerRef.getUuid()); + Objective objective = ObjectivePlugin.get().startObjective(objectiveId, playerSet, world.getWorldConfig().getUuid(), null, store); + if (objective != null) { + objective.checkTaskSetCompletion(store); + } + } + } + } + + public static class StartObjectiveLineCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_OBJECTIVE_OBJECTIVE_LINE_NOT_FOUND = Message.translation("server.commands.objective.objectiveLineNotFound"); + @Nonnull + private static final Message MESSAGE_GENERAL_FAILED_DID_YOU_MEAN = Message.translation("server.general.failed.didYouMean"); + @Nonnull + private final RequiredArg objectiveLineArg = this.withRequiredArg( + "objectiveLineId", "server.commands.objective.start.objectiveLine.arg.objectiveLineId.desc", ArgTypes.STRING + ); + + public StartObjectiveLineCommand() { + super("line", "server.commands.objective.start.objectiveLine"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + String objectiveLineId = this.objectiveLineArg.get(context); + if (ObjectiveLineAsset.getAssetMap().getAsset(objectiveLineId) == null) { + context.sendMessage(MESSAGE_COMMANDS_OBJECTIVE_OBJECTIVE_LINE_NOT_FOUND.param("id", objectiveLineId)); + context.sendMessage( + MESSAGE_GENERAL_FAILED_DID_YOU_MEAN.param( + "choices", + StringUtil.sortByFuzzyDistance(objectiveLineId, ObjectiveLineAsset.getAssetMap().getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT) + .toString() + ) + ); + } else { + HashSet playerSet = new HashSet<>(); + playerSet.add(playerRef.getUuid()); + ObjectivePlugin.get().startObjectiveLine(store, objectiveLineId, playerSet, world.getWorldConfig().getUuid(), null); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/completion/ClearObjectiveItemsCompletion.java b/src/com/hypixel/hytale/builtin/adventure/objectives/completion/ClearObjectiveItemsCompletion.java new file mode 100644 index 0000000..182f383 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/completion/ClearObjectiveItemsCompletion.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.builtin.adventure.objectives.completion; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.config.completion.ClearObjectiveItemsCompletionAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.completion.ObjectiveCompletionAsset; +import com.hypixel.hytale.builtin.adventure.objectives.interactions.StartObjectiveInteraction; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class ClearObjectiveItemsCompletion extends ObjectiveCompletion { + public ClearObjectiveItemsCompletion(@Nonnull ObjectiveCompletionAsset asset) { + super(asset); + } + + @Nonnull + public ClearObjectiveItemsCompletionAsset getAsset() { + return (ClearObjectiveItemsCompletionAsset)super.getAsset(); + } + + @Override + public void handle(@Nonnull Objective objective, @Nonnull ComponentAccessor componentAccessor) { + objective.forEachParticipant((participantReference, objectiveUuid) -> { + Entity entity = EntityUtils.getEntity(participantReference, componentAccessor); + if (entity instanceof LivingEntity) { + CombinedItemContainer inventory = ((LivingEntity)entity).getInventory().getCombinedHotbarFirst(); + + for (short i = 0; i < inventory.getCapacity(); i++) { + ItemStack itemStack = inventory.getItemStack(i); + if (itemStack != null) { + UUID savedObjectiveUuid = itemStack.getFromMetadataOrNull(StartObjectiveInteraction.OBJECTIVE_UUID); + if (objectiveUuid.equals(savedObjectiveUuid)) { + inventory.removeItemStackFromSlot(i); + } + } + } + } + }, objective.getObjectiveUUID()); + } + + @Nonnull + @Override + public String toString() { + return "ClearObjectiveItemsCompletion{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/completion/GiveItemsCompletion.java b/src/com/hypixel/hytale/builtin/adventure/objectives/completion/GiveItemsCompletion.java new file mode 100644 index 0000000..0796c4f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/completion/GiveItemsCompletion.java @@ -0,0 +1,86 @@ +package com.hypixel.hytale.builtin.adventure.objectives.completion; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.config.completion.GiveItemsCompletionAsset; +import com.hypixel.hytale.builtin.adventure.objectives.historydata.ItemObjectiveRewardHistoryData; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer; +import com.hypixel.hytale.server.core.modules.item.ItemModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.NotificationUtil; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class GiveItemsCompletion extends ObjectiveCompletion { + public GiveItemsCompletion(@Nonnull GiveItemsCompletionAsset asset) { + super(asset); + } + + @Nonnull + public GiveItemsCompletionAsset getAsset() { + return (GiveItemsCompletionAsset)super.getAsset(); + } + + @Override + public void handle(@Nonnull Objective objective, @Nonnull ComponentAccessor componentAccessor) { + World world = Universe.get().getWorld(objective.getWorldUUID()); + if (world != null) { + Store store = world.getEntityStore().getStore(); + boolean showItemNotification = world.getGameplayConfig().getShowItemPickupNotifications(); + objective.forEachParticipant( + (participantReference, asset, objectiveHistoryData) -> { + if (EntityUtils.getEntity(participantReference, componentAccessor) instanceof LivingEntity livingEntity) { + Inventory inventory = livingEntity.getInventory(); + List itemStacks = ItemModule.get().getRandomItemDrops(asset.getDropListId()); + SimpleItemContainer.addOrDropItemStacks(store, participantReference, inventory.getCombinedHotbarFirst(), itemStacks); + Player playerComponent = componentAccessor.getComponent(participantReference, Player.getComponentType()); + if (playerComponent != null) { + PlayerRef playerRefComponent = componentAccessor.getComponent(participantReference, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + UUIDComponent uuidComponent = store.getComponent(participantReference, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID uuid = uuidComponent.getUuid(); + + for (ItemStack itemStack : itemStacks) { + objectiveHistoryData.addRewardForPlayerUUID(uuid, new ItemObjectiveRewardHistoryData(itemStack.getItemId(), itemStack.getQuantity())); + if (showItemNotification) { + Message itemNameMessage = Message.translation(itemStack.getItem().getTranslationKey()); + NotificationUtil.sendNotification( + playerRefComponent.getPacketHandler(), + Message.translation("server.objectives.itemObjectiveCompletion").param("item", itemNameMessage), + null, + itemStack.toPacket() + ); + } + } + } + } + }, + this.getAsset(), + objective.getObjectiveHistoryData() + ); + } + } + + @Nonnull + @Override + public String toString() { + return "GiveItemsCompletion{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/completion/ObjectiveCompletion.java b/src/com/hypixel/hytale/builtin/adventure/objectives/completion/ObjectiveCompletion.java new file mode 100644 index 0000000..80fb600 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/completion/ObjectiveCompletion.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.builtin.adventure.objectives.completion; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.config.completion.ObjectiveCompletionAsset; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public abstract class ObjectiveCompletion { + @Nonnull + protected final ObjectiveCompletionAsset asset; + + public ObjectiveCompletion(@Nonnull ObjectiveCompletionAsset asset) { + this.asset = asset; + } + + @Nonnull + public ObjectiveCompletionAsset getAsset() { + return this.asset; + } + + public abstract void handle(@Nonnull Objective var1, @Nonnull ComponentAccessor var2); + + @Nonnull + @Override + public String toString() { + return "ObjectiveCompletion{asset=" + this.asset + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/components/ObjectiveHistoryComponent.java b/src/com/hypixel/hytale/builtin/adventure/objectives/components/ObjectiveHistoryComponent.java new file mode 100644 index 0000000..bd365cf --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/components/ObjectiveHistoryComponent.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.adventure.objectives.components; + +import com.hypixel.hytale.builtin.adventure.objectives.historydata.ObjectiveHistoryData; +import com.hypixel.hytale.builtin.adventure.objectives.historydata.ObjectiveLineHistoryData; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class ObjectiveHistoryComponent implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(ObjectiveHistoryComponent.class, ObjectiveHistoryComponent::new) + .append( + new KeyedCodec<>("ObjectiveHistory", new MapCodec<>(ObjectiveHistoryData.CODEC, Object2ObjectOpenHashMap::new, false)), + (objectiveHistoryComponent, stringObjectiveHistoryDataMap) -> objectiveHistoryComponent.objectiveHistoryMap = stringObjectiveHistoryDataMap, + objectiveHistoryComponent -> objectiveHistoryComponent.objectiveHistoryMap + ) + .add() + .append( + new KeyedCodec<>("ObjectiveLineHistory", new MapCodec<>(ObjectiveLineHistoryData.CODEC, Object2ObjectOpenHashMap::new, false)), + (objectiveHistoryComponent, stringObjectiveLineHistoryDataMap) -> objectiveHistoryComponent.objectiveLineHistoryMap = stringObjectiveLineHistoryDataMap, + objectiveHistoryComponent -> objectiveHistoryComponent.objectiveLineHistoryMap + ) + .add() + .build(); + private Map objectiveHistoryMap = new Object2ObjectOpenHashMap<>(); + private Map objectiveLineHistoryMap = new Object2ObjectOpenHashMap<>(); + + public ObjectiveHistoryComponent() { + } + + public Map getObjectiveHistoryMap() { + return this.objectiveHistoryMap; + } + + public Map getObjectiveLineHistoryMap() { + return this.objectiveLineHistoryMap; + } + + @Nonnull + @Override + public Component clone() { + ObjectiveHistoryComponent component = new ObjectiveHistoryComponent(); + component.objectiveHistoryMap.putAll(this.objectiveHistoryMap); + component.objectiveLineHistoryMap.putAll(this.objectiveLineHistoryMap); + return component; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/ObjectiveAsset.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/ObjectiveAsset.java new file mode 100644 index 0000000..7d35d88 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/ObjectiveAsset.java @@ -0,0 +1,215 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.config.completion.ObjectiveCompletionAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.ObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.TaskSet; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class ObjectiveAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ObjectiveAsset.class, + ObjectiveAsset::new, + Codec.STRING, + (objective, s) -> objective.id = s, + objective -> objective.id, + (objective, data) -> objective.extraData = data, + objective -> objective.extraData + ) + .appendInherited( + new KeyedCodec<>("Category", Codec.STRING), + (objectiveAsset, s) -> objectiveAsset.category = s, + objectiveAsset -> objectiveAsset.category, + (objectiveAsset, parent) -> objectiveAsset.category = parent.category + ) + .add() + .appendInherited( + new KeyedCodec<>("TaskSets", new ArrayCodec<>(TaskSet.CODEC, TaskSet[]::new)), + (objective, tasks) -> objective.taskSets = tasks, + objective -> objective.taskSets, + (objective, parent) -> objective.taskSets = parent.taskSets + ) + .addValidator(Validators.nonEmptyArray()) + .add() + .appendInherited( + new KeyedCodec<>("Completions", new ArrayCodec<>(ObjectiveCompletionAsset.CODEC, ObjectiveCompletionAsset[]::new)), + (objective, rewards) -> objective.completionHandlers = rewards, + objective -> objective.completionHandlers, + (objective, parent) -> objective.completionHandlers = parent.completionHandlers + ) + .add() + .appendInherited( + new KeyedCodec<>("TitleId", Codec.STRING), + (objectiveAsset, s) -> objectiveAsset.objectiveTitleKey = s, + objectiveAsset -> objectiveAsset.objectiveTitleKey, + (objectiveAsset, parent) -> objectiveAsset.objectiveTitleKey = parent.objectiveTitleKey + ) + .metadata(new UIEditor(new UIEditor.LocalizationKeyField("server.objectives.{assetId}.title", true))) + .add() + .appendInherited( + new KeyedCodec<>("DescriptionId", Codec.STRING), + (objectiveAsset, s) -> objectiveAsset.objectiveDescriptionKey = s, + objectiveAsset -> objectiveAsset.objectiveDescriptionKey, + (objectiveAsset, parent) -> objectiveAsset.objectiveDescriptionKey = parent.objectiveDescriptionKey + ) + .metadata(new UIEditor(new UIEditor.LocalizationKeyField("server.objectives.{assetId}.desc"))) + .add() + .appendInherited( + new KeyedCodec<>("RemoveOnItemDrop", Codec.BOOLEAN), + (objectiveAsset, aBoolean) -> objectiveAsset.removeOnItemDrop = aBoolean, + objectiveAsset -> objectiveAsset.removeOnItemDrop, + (objectiveAsset, parent) -> objectiveAsset.removeOnItemDrop = parent.removeOnItemDrop + ) + .add() + .afterDecode(objectiveAsset -> { + if (objectiveAsset.objectiveTitleKey == null) { + objectiveAsset.objectiveTitleKey = MessageFormat.format("server.objectives.{0}.title", objectiveAsset.id); + } + + if (objectiveAsset.objectiveDescriptionKey == null) { + objectiveAsset.objectiveDescriptionKey = MessageFormat.format("server.objectives.{0}.desc", objectiveAsset.id); + } + }) + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ObjectiveAsset::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data extraData; + protected String id; + protected String category; + protected TaskSet[] taskSets; + protected ObjectiveCompletionAsset[] completionHandlers; + protected String objectiveTitleKey; + protected String objectiveDescriptionKey; + protected boolean removeOnItemDrop; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ObjectiveAsset.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public ObjectiveAsset( + String id, + String category, + TaskSet[] taskSets, + ObjectiveCompletionAsset[] completionHandlers, + String objectiveTitleKey, + String objectiveDescriptionKey, + boolean removeOnItemDrop + ) { + this.id = id; + this.category = category; + this.taskSets = taskSets; + this.completionHandlers = completionHandlers; + this.objectiveTitleKey = objectiveTitleKey; + this.objectiveDescriptionKey = objectiveDescriptionKey; + this.removeOnItemDrop = removeOnItemDrop; + } + + protected ObjectiveAsset() { + } + + public String getId() { + return this.id; + } + + public String getCategory() { + return this.category; + } + + public String getTitleKey() { + return this.objectiveTitleKey; + } + + public String getDescriptionKey() { + return this.objectiveDescriptionKey; + } + + public TaskSet[] getTaskSets() { + return this.taskSets; + } + + public ObjectiveCompletionAsset[] getCompletionHandlers() { + return this.completionHandlers; + } + + public String getObjectiveTitleKey() { + return this.objectiveTitleKey; + } + + public String getObjectiveDescriptionKey() { + return this.objectiveDescriptionKey; + } + + public boolean isRemoveOnItemDrop() { + return this.removeOnItemDrop; + } + + public boolean isValidForPlayer() { + for (TaskSet taskSet : this.taskSets) { + for (ObjectiveTaskAsset task : taskSet.getTasks()) { + if (!task.getTaskScope().isTaskPossibleForPlayer()) { + ObjectivePlugin.get().getLogger().at(Level.WARNING).log("Task %s isn't valid for Player held objective", task.getClass().toString()); + return false; + } + } + } + + return true; + } + + public boolean isValidForMarker() { + for (TaskSet taskSet : this.taskSets) { + for (ObjectiveTaskAsset task : taskSet.getTasks()) { + if (!task.getTaskScope().isTaskPossibleForMarker()) { + ObjectivePlugin.get().getLogger().at(Level.WARNING).log("Task %s isn't valid for Marker held objective", task.getClass().toString()); + return false; + } + } + } + + return true; + } + + @Nonnull + @Override + public String toString() { + return "ObjectiveAsset{id='" + + this.id + + "', category='" + + this.category + + "', taskSets=" + + Arrays.toString((Object[])this.taskSets) + + ", completionHandlers=" + + Arrays.toString((Object[])this.completionHandlers) + + ", objectiveTitleKey='" + + this.objectiveTitleKey + + "', objectiveDescriptionKey='" + + this.objectiveDescriptionKey + + "', removeOnItemDrop=" + + this.removeOnItemDrop + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/ObjectiveLineAsset.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/ObjectiveLineAsset.java new file mode 100644 index 0000000..a10be80 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/ObjectiveLineAsset.java @@ -0,0 +1,174 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import java.text.MessageFormat; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ObjectiveLineAsset implements JsonAssetWithMap> { + @Nonnull + public static AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ObjectiveLineAsset.class, + ObjectiveLineAsset::new, + Codec.STRING, + (objectiveLine, s) -> objectiveLine.id = s, + objectiveLine -> objectiveLine.id, + (objectiveLine, data) -> objectiveLine.extraData = data, + objectiveLine -> objectiveLine.extraData + ) + .appendInherited( + new KeyedCodec<>("Category", Codec.STRING), + (objectiveAsset, s) -> objectiveAsset.category = s, + objectiveAsset -> objectiveAsset.category, + (objectiveAsset, parent) -> objectiveAsset.category = parent.category + ) + .add() + .appendInherited( + new KeyedCodec<>("ObjectiveIds", Codec.STRING_ARRAY), + (objectiveLineAsset, strings) -> objectiveLineAsset.objectiveIds = strings, + objectiveLineAsset -> objectiveLineAsset.objectiveIds, + (objectiveLineAsset, parent) -> objectiveLineAsset.objectiveIds = parent.objectiveIds + ) + .addValidator(Validators.nonEmptyArray()) + .addValidator(Validators.uniqueInArray()) + .addValidator(ObjectiveAsset.VALIDATOR_CACHE.getArrayValidator()) + .add() + .appendInherited( + new KeyedCodec<>("NextObjectiveLineIds", Codec.STRING_ARRAY), + (objectiveLineAsset, strings) -> objectiveLineAsset.nextObjectiveLineIds = strings, + objectiveLineAsset -> objectiveLineAsset.nextObjectiveLineIds, + (objectiveLineAsset, parent) -> objectiveLineAsset.nextObjectiveLineIds = parent.nextObjectiveLineIds + ) + .addValidator(Validators.uniqueInArray()) + .add() + .appendInherited( + new KeyedCodec<>("TitleId", Codec.STRING), + (objectiveLineAsset, s) -> objectiveLineAsset.objectiveTitleKey = s, + objectiveLineAsset -> objectiveLineAsset.objectiveTitleKey, + (objectiveLineAsset, parent) -> objectiveLineAsset.objectiveTitleKey = parent.objectiveTitleKey + ) + .metadata(new UIEditor(new UIEditor.LocalizationKeyField("objectivelines.{assetId}.title", true))) + .add() + .appendInherited( + new KeyedCodec<>("DescriptionId", Codec.STRING), + (objectiveLineAsset, s) -> objectiveLineAsset.objectiveDescriptionKey = s, + objectiveLineAsset -> objectiveLineAsset.objectiveDescriptionKey, + (objectiveLineAsset, parent) -> objectiveLineAsset.objectiveDescriptionKey = parent.objectiveDescriptionKey + ) + .metadata(new UIEditor(new UIEditor.LocalizationKeyField("objectivelines.{assetId}.desc", true))) + .add() + .afterDecode(objectiveAsset -> { + if (objectiveAsset.objectiveTitleKey != null) { + objectiveAsset.objectiveTitleKey = MessageFormat.format("objectivelines.{0}.title", objectiveAsset.id); + } + + if (objectiveAsset.objectiveDescriptionKey != null) { + objectiveAsset.objectiveDescriptionKey = MessageFormat.format("objectivelines.{0}.desc", objectiveAsset.id); + } + }) + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ObjectiveLineAsset::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data extraData; + protected String id; + protected String category; + protected String[] objectiveIds; + protected String objectiveTitleKey; + protected String objectiveDescriptionKey; + protected String[] nextObjectiveLineIds; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ObjectiveLineAsset.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public ObjectiveLineAsset( + String id, String category, String[] objectiveIds, String objectiveTitleKey, String objectiveDescriptionKey, String[] nextObjectiveLineIds + ) { + this.id = id; + this.category = category; + this.objectiveIds = objectiveIds; + this.objectiveTitleKey = objectiveTitleKey; + this.objectiveDescriptionKey = objectiveDescriptionKey; + this.nextObjectiveLineIds = nextObjectiveLineIds; + } + + protected ObjectiveLineAsset() { + } + + public String getId() { + return this.id; + } + + public String getCategory() { + return this.category; + } + + public String[] getObjectiveIds() { + return this.objectiveIds; + } + + @Nullable + public String getNextObjectiveId(String currentObjectiveId) { + if (this.objectiveIds != null && this.objectiveIds.length != 0) { + for (int i = 0; i < this.objectiveIds.length - 1; i++) { + if (this.objectiveIds[i].equals(currentObjectiveId)) { + return this.objectiveIds[i + 1]; + } + } + + return null; + } else { + return null; + } + } + + public String getObjectiveTitleKey() { + return this.objectiveTitleKey; + } + + public String getObjectiveDescriptionKey() { + return this.objectiveDescriptionKey; + } + + public String[] getNextObjectiveLineIds() { + return this.nextObjectiveLineIds; + } + + @Nonnull + @Override + public String toString() { + return "ObjectiveLineAsset{id='" + + this.id + + "', category='" + + this.category + + "', objectiveIds=" + + Arrays.toString((Object[])this.objectiveIds) + + ", objectiveTitleKey='" + + this.objectiveTitleKey + + "', objectiveDescriptionKey='" + + this.objectiveDescriptionKey + + "', nextObjectiveLineIds=" + + Arrays.toString((Object[])this.nextObjectiveLineIds) + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/ObjectiveLocationMarkerAsset.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/ObjectiveLocationMarkerAsset.java new file mode 100644 index 0000000..a5da578 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/ObjectiveLocationMarkerAsset.java @@ -0,0 +1,141 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.adventure.objectives.config.markerarea.ObjectiveLocationMarkerArea; +import com.hypixel.hytale.builtin.adventure.objectives.config.objectivesetup.ObjectiveTypeSetup; +import com.hypixel.hytale.builtin.adventure.objectives.config.triggercondition.ObjectiveLocationTriggerCondition; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class ObjectiveLocationMarkerAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ObjectiveLocationMarkerAsset.class, + ObjectiveLocationMarkerAsset::new, + Codec.STRING, + (t, k) -> t.id = k, + t -> t.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .append( + new KeyedCodec<>("Setup", ObjectiveTypeSetup.CODEC), + (objectiveLocationMarkerAsset, objectiveTypeSetup) -> objectiveLocationMarkerAsset.objectiveTypeSetup = objectiveTypeSetup, + objectiveLocationMarkerAsset -> objectiveLocationMarkerAsset.objectiveTypeSetup + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("Area", ObjectiveLocationMarkerArea.CODEC), + (objectiveLocationMarkerAsset, area) -> objectiveLocationMarkerAsset.area = area, + objectiveLocationMarkerAsset -> objectiveLocationMarkerAsset.area + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("EnvironmentIds", Codec.STRING_ARRAY), + (objectiveLocationMarkerAsset, strings) -> objectiveLocationMarkerAsset.environmentIds = strings, + objectiveLocationMarkerAsset -> objectiveLocationMarkerAsset.environmentIds + ) + .addValidator(Environment.VALIDATOR_CACHE.getArrayValidator()) + .add() + .append( + new KeyedCodec<>("TriggerConditions", new ArrayCodec<>(ObjectiveLocationTriggerCondition.CODEC, ObjectiveLocationTriggerCondition[]::new)), + (objectiveLocationMarkerAsset, objectiveLocationTriggerConditions) -> objectiveLocationMarkerAsset.triggerConditions = objectiveLocationTriggerConditions, + objectiveLocationMarkerAsset -> objectiveLocationMarkerAsset.triggerConditions + ) + .add() + .afterDecode(objectiveLocationMarkerAsset -> { + if (objectiveLocationMarkerAsset.environmentIds != null && objectiveLocationMarkerAsset.environmentIds.length > 0) { + objectiveLocationMarkerAsset.environmentIndexes = new int[objectiveLocationMarkerAsset.environmentIds.length]; + + for (int i = 0; i < objectiveLocationMarkerAsset.environmentIds.length; i++) { + String key = objectiveLocationMarkerAsset.environmentIds[i]; + int index = Environment.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + objectiveLocationMarkerAsset.environmentIndexes[i] = index; + } + + Arrays.sort(objectiveLocationMarkerAsset.environmentIndexes); + } + }) + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ObjectiveLocationMarkerAsset::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected ObjectiveTypeSetup objectiveTypeSetup; + protected ObjectiveLocationMarkerArea area; + protected String[] environmentIds; + protected int[] environmentIndexes; + protected ObjectiveLocationTriggerCondition[] triggerConditions; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ObjectiveLocationMarkerAsset.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public ObjectiveLocationMarkerAsset() { + } + + public String getId() { + return this.id; + } + + public ObjectiveTypeSetup getObjectiveTypeSetup() { + return this.objectiveTypeSetup; + } + + public ObjectiveLocationMarkerArea getArea() { + return this.area; + } + + public String[] getEnvironmentIds() { + return this.environmentIds; + } + + public int[] getEnvironmentIndexes() { + return this.environmentIndexes; + } + + public ObjectiveLocationTriggerCondition[] getTriggerConditions() { + return this.triggerConditions; + } + + @Nonnull + @Override + public String toString() { + return "ObjectiveLocationMarkerAsset{id='" + + this.id + + "', objectiveTypeSetup=" + + this.objectiveTypeSetup + + ", area=" + + this.area + + ", environmentIds=" + + Arrays.toString((Object[])this.environmentIds) + + ", triggerConditions=" + + Arrays.toString((Object[])this.triggerConditions) + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/completion/ClearObjectiveItemsCompletionAsset.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/completion/ClearObjectiveItemsCompletionAsset.java new file mode 100644 index 0000000..f29247a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/completion/ClearObjectiveItemsCompletionAsset.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.completion; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class ClearObjectiveItemsCompletionAsset extends ObjectiveCompletionAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ClearObjectiveItemsCompletionAsset.class, ClearObjectiveItemsCompletionAsset::new, BASE_CODEC + ) + .build(); + + protected ClearObjectiveItemsCompletionAsset() { + } + + @Nonnull + @Override + public String toString() { + return "ClearObjectiveItemsCompletionAsset{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/completion/GiveItemsCompletionAsset.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/completion/GiveItemsCompletionAsset.java new file mode 100644 index 0000000..a3522bb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/completion/GiveItemsCompletionAsset.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.completion; + +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList; +import javax.annotation.Nonnull; + +public class GiveItemsCompletionAsset extends ObjectiveCompletionAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + GiveItemsCompletionAsset.class, GiveItemsCompletionAsset::new, BASE_CODEC + ) + .append( + new KeyedCodec<>("DropList", new ContainedAssetCodec<>(ItemDropList.class, ItemDropList.CODEC)), + (objective, dropListId) -> objective.dropListId = dropListId, + objective -> objective.dropListId + ) + .addValidator(Validators.nonNull()) + .addValidator(ItemDropList.VALIDATOR_CACHE.getValidator()) + .add() + .build(); + protected String dropListId; + + public GiveItemsCompletionAsset(String dropListId) { + this.dropListId = dropListId; + } + + protected GiveItemsCompletionAsset() { + } + + public String getDropListId() { + return this.dropListId; + } + + @Nonnull + @Override + public String toString() { + return "GiveItemsCompletionAsset{dropListId='" + this.dropListId + "'} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/completion/ObjectiveCompletionAsset.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/completion/ObjectiveCompletionAsset.java new file mode 100644 index 0000000..fb766fd --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/completion/ObjectiveCompletionAsset.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.completion; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import javax.annotation.Nonnull; + +public abstract class ObjectiveCompletionAsset { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(ObjectiveCompletionAsset.class).build(); + + protected ObjectiveCompletionAsset() { + } + + @Nonnull + @Override + public String toString() { + return "ObjectiveCompletionAsset{}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/gameplayconfig/ObjectiveGameplayConfig.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/gameplayconfig/ObjectiveGameplayConfig.java new file mode 100644 index 0000000..0e06831 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/gameplayconfig/ObjectiveGameplayConfig.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.gameplayconfig; + +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveLineAsset; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ObjectiveGameplayConfig { + public static final String ID = "Objective"; + public static final BuilderCodec CODEC = BuilderCodec.builder(ObjectiveGameplayConfig.class, ObjectiveGameplayConfig::new) + .appendInherited( + new KeyedCodec<>("StarterObjectiveLinePerWorld", new MapCodec<>(Codec.STRING, Object2ObjectOpenHashMap::new, true)), + (o, s) -> o.starterObjectiveLinePerWorld = s, + o -> o.starterObjectiveLinePerWorld, + (o, parent) -> o.starterObjectiveLinePerWorld = parent.starterObjectiveLinePerWorld + ) + .addValidator(ObjectiveLineAsset.VALIDATOR_CACHE.getMapValueValidator()) + .add() + .build(); + protected Map starterObjectiveLinePerWorld; + + public ObjectiveGameplayConfig() { + } + + @Nullable + public static ObjectiveGameplayConfig get(@Nonnull GameplayConfig config) { + return config.getPluginConfig().get(ObjectiveGameplayConfig.class); + } + + public Map getStarterObjectiveLinePerWorld() { + return this.starterObjectiveLinePerWorld; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/markerarea/ObjectiveLocationAreaBox.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/markerarea/ObjectiveLocationAreaBox.java new file mode 100644 index 0000000..aa24713 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/markerarea/ObjectiveLocationAreaBox.java @@ -0,0 +1,130 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.markerarea; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.List; +import javax.annotation.Nonnull; + +public class ObjectiveLocationAreaBox extends ObjectiveLocationMarkerArea { + public static final BuilderCodec CODEC = BuilderCodec.builder(ObjectiveLocationAreaBox.class, ObjectiveLocationAreaBox::new) + .append( + new KeyedCodec<>("EntryBox", Box.CODEC), + (objectiveLocationAreaBox, box) -> objectiveLocationAreaBox.entryArea = box, + objectiveLocationAreaBox -> objectiveLocationAreaBox.entryArea + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("ExitBox", Box.CODEC), + (objectiveLocationAreaBox, box) -> objectiveLocationAreaBox.exitArea = box, + objectiveLocationAreaBox -> objectiveLocationAreaBox.exitArea + ) + .addValidator(Validators.nonNull()) + .add() + .afterDecode(ObjectiveLocationAreaBox::computeAreaBoxes) + .build(); + private static final Box DEFAULT_ENTRY_BOX = new Box(-5.0, -5.0, -5.0, 5.0, 5.0, 5.0); + private static final Box DEFAULT_EXIT_BOX = new Box(-10.0, -10.0, -10.0, 10.0, 10.0, 10.0); + private Box entryArea; + private Box exitArea; + + public ObjectiveLocationAreaBox(Box entryBox, Box exitBox) { + this.entryArea = entryBox; + this.exitArea = exitBox; + this.computeAreaBoxes(); + } + + protected ObjectiveLocationAreaBox() { + this(DEFAULT_ENTRY_BOX, DEFAULT_EXIT_BOX); + } + + public Box getEntryArea() { + return this.entryArea; + } + + public Box getExitArea() { + return this.exitArea; + } + + @Override + public void getPlayersInEntryArea( + @Nonnull SpatialResource, EntityStore> spatialComponent, @Nonnull List> results, @Nonnull Vector3d markerPosition + ) { + getPlayersInArea(spatialComponent, results, markerPosition, this.entryArea); + } + + @Override + public void getPlayersInExitArea( + @Nonnull SpatialResource, EntityStore> spatialComponent, @Nonnull List> results, @Nonnull Vector3d markerPosition + ) { + getPlayersInArea(spatialComponent, results, markerPosition, this.exitArea); + } + + @Override + public boolean hasPlayerInExitArea( + @Nonnull SpatialResource, EntityStore> spatialComponent, + @Nonnull ComponentType playerRefComponentType, + @Nonnull Vector3d markerPosition, + @Nonnull CommandBuffer commandBuffer + ) { + Ref reference = spatialComponent.getSpatialStructure().closest(markerPosition); + if (reference == null) { + return false; + } else { + TransformComponent transformComponent = commandBuffer.getComponent(reference, TransformComponent.getComponentType()); + + assert transformComponent != null; + + return this.exitArea.containsPosition(markerPosition, transformComponent.getPosition()); + } + } + + @Override + public boolean isPlayerInEntryArea(@Nonnull Vector3d playerPosition, @Nonnull Vector3d markerPosition) { + return this.entryArea.containsPosition(markerPosition, playerPosition); + } + + @Nonnull + @Override + public ObjectiveLocationMarkerArea getRotatedArea(float yaw, float pitch) { + float snappedYaw = Math.round(yaw / (float) (Math.PI / 2)) * (float) (Math.PI / 2); + if (Math.abs(snappedYaw % (float) (Math.PI * 2)) > (float) (Math.PI / 4)) { + Box entry = this.entryArea.clone().rotateY(snappedYaw).normalize(); + Box exit = this.exitArea.clone().rotateY(snappedYaw).normalize(); + return new ObjectiveLocationAreaBox(entry, exit); + } else { + return this; + } + } + + @Override + protected void computeAreaBoxes() { + this.entryAreaBox = this.entryArea; + this.exitAreaBox = this.exitArea; + } + + private static void getPlayersInArea( + @Nonnull SpatialResource, EntityStore> spatialComponent, + List> results, + @Nonnull Vector3d markerPosition, + @Nonnull Box box + ) { + spatialComponent.getSpatialStructure().collect(markerPosition, box.getMaximumExtent(), results); + } + + @Nonnull + @Override + public String toString() { + return "ObjectiveLocationAreaBox{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/markerarea/ObjectiveLocationAreaRadius.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/markerarea/ObjectiveLocationAreaRadius.java new file mode 100644 index 0000000..2303af7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/markerarea/ObjectiveLocationAreaRadius.java @@ -0,0 +1,128 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.markerarea; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.VectorSphereUtil; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.List; +import javax.annotation.Nonnull; + +public class ObjectiveLocationAreaRadius extends ObjectiveLocationMarkerArea { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ObjectiveLocationAreaRadius.class, ObjectiveLocationAreaRadius::new + ) + .append( + new KeyedCodec<>("EntryRadius", Codec.INTEGER), + (objectiveLocationAreaRadius, integer) -> objectiveLocationAreaRadius.entryArea = integer, + objectiveLocationAreaRadius -> objectiveLocationAreaRadius.entryArea + ) + .addValidator(Validators.greaterThan(0)) + .add() + .append( + new KeyedCodec<>("ExitRadius", Codec.INTEGER), + (objectiveLocationAreaRadius, integer) -> objectiveLocationAreaRadius.exitArea = integer, + objectiveLocationAreaRadius -> objectiveLocationAreaRadius.exitArea + ) + .addValidator(Validators.greaterThan(0)) + .add() + .validator((objectiveLocationAreaRadius, validationResults) -> { + if (objectiveLocationAreaRadius.exitArea < objectiveLocationAreaRadius.entryArea) { + validationResults.fail("ExitRadius needs to be greater than EntryRadius"); + } + }) + .afterDecode(ObjectiveLocationAreaRadius::computeAreaBoxes) + .build(); + public static final int DEFAULT_ENTRY_RADIUS = 5; + public static final int DEFAULT_EXIT_RADIUS = 10; + protected int entryArea; + protected int exitArea; + + public ObjectiveLocationAreaRadius(int entryRadius, int exitRadius) { + this.entryArea = entryRadius; + this.exitArea = exitRadius; + this.computeAreaBoxes(); + } + + protected ObjectiveLocationAreaRadius() { + this(5, 10); + } + + public int getEntryArea() { + return this.entryArea; + } + + public int getExitArea() { + return this.exitArea; + } + + @Override + public void getPlayersInEntryArea( + @Nonnull SpatialResource, EntityStore> spatialComponent, @Nonnull List> results, @Nonnull Vector3d markerPosition + ) { + getPlayersInArea(spatialComponent, results, markerPosition, this.entryArea); + } + + @Override + public void getPlayersInExitArea( + @Nonnull SpatialResource, EntityStore> spatialComponent, @Nonnull List> results, @Nonnull Vector3d markerPosition + ) { + getPlayersInArea(spatialComponent, results, markerPosition, this.exitArea); + } + + @Override + public boolean hasPlayerInExitArea( + @Nonnull SpatialResource, EntityStore> spatialComponent, + @Nonnull ComponentType playerRefComponentType, + @Nonnull Vector3d markerPosition, + @Nonnull CommandBuffer commandBuffer + ) { + Ref reference = spatialComponent.getSpatialStructure().closest(markerPosition); + if (reference == null) { + return false; + } else { + TransformComponent transformComponent = commandBuffer.getComponent(reference, TransformComponent.getComponentType()); + + assert transformComponent != null; + + return VectorSphereUtil.isInside( + markerPosition.x, markerPosition.y, markerPosition.z, this.exitArea, this.exitArea, this.exitArea, transformComponent.getPosition() + ); + } + } + + @Override + public boolean isPlayerInEntryArea(@Nonnull Vector3d playerPosition, @Nonnull Vector3d markerPosition) { + return VectorSphereUtil.isInside(markerPosition.x, markerPosition.y, markerPosition.z, this.entryArea, playerPosition); + } + + @Override + protected void computeAreaBoxes() { + this.entryAreaBox = new Box(-this.entryArea, -this.entryArea, -this.entryArea, this.entryArea, this.entryArea, this.entryArea); + this.exitAreaBox = new Box(-this.exitArea, -this.exitArea, -this.exitArea, this.exitArea, this.exitArea, this.exitArea); + } + + private static void getPlayersInArea( + @Nonnull SpatialResource, EntityStore> spatialComponent, + @Nonnull List> results, + @Nonnull Vector3d markerPosition, + int radius + ) { + spatialComponent.getSpatialStructure().collect(markerPosition, radius, results); + } + + @Nonnull + @Override + public String toString() { + return "ObjectiveLocationAreaRadius{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/markerarea/ObjectiveLocationMarkerArea.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/markerarea/ObjectiveLocationMarkerArea.java new file mode 100644 index 0000000..a7a21d0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/markerarea/ObjectiveLocationMarkerArea.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.markerarea; + +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.List; +import javax.annotation.Nonnull; + +public abstract class ObjectiveLocationMarkerArea { + @Nonnull + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + protected Box entryAreaBox; + protected Box exitAreaBox; + + public ObjectiveLocationMarkerArea() { + } + + public abstract void getPlayersInEntryArea( + @Nonnull SpatialResource, EntityStore> var1, @Nonnull List> var2, @Nonnull Vector3d var3 + ); + + public abstract void getPlayersInExitArea( + @Nonnull SpatialResource, EntityStore> var1, @Nonnull List> var2, @Nonnull Vector3d var3 + ); + + public abstract boolean hasPlayerInExitArea( + @Nonnull SpatialResource, EntityStore> var1, + @Nonnull ComponentType var2, + @Nonnull Vector3d var3, + @Nonnull CommandBuffer var4 + ); + + public abstract boolean isPlayerInEntryArea(@Nonnull Vector3d var1, @Nonnull Vector3d var2); + + public Box getBoxForEntryArea() { + return this.entryAreaBox; + } + + public Box getBoxForExitArea() { + return this.exitAreaBox; + } + + @Nonnull + public ObjectiveLocationMarkerArea getRotatedArea(float yaw, float pitch) { + return this; + } + + protected abstract void computeAreaBoxes(); + + @Nonnull + @Override + public String toString() { + return "ObjectiveLocationMarkerArea{, entryAreaBox=" + this.entryAreaBox + ", exitAreaBox=" + this.exitAreaBox + "}"; + } + + static { + CODEC.register("Box", ObjectiveLocationAreaBox.class, ObjectiveLocationAreaBox.CODEC); + CODEC.register("Radius", ObjectiveLocationAreaRadius.class, ObjectiveLocationAreaRadius.CODEC); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/objectivesetup/ObjectiveTypeSetup.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/objectivesetup/ObjectiveTypeSetup.java new file mode 100644 index 0000000..cb0089a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/objectivesetup/ObjectiveTypeSetup.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.objectivesetup; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class ObjectiveTypeSetup { + @Nonnull + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + + public ObjectiveTypeSetup() { + } + + @Nullable + public abstract String getObjectiveIdToStart(); + + @Nullable + public abstract Objective setup(@Nonnull Set var1, @Nonnull UUID var2, @Nullable UUID var3, @Nonnull Store var4); + + @Nonnull + @Override + public String toString() { + return "ObjectiveTypeSetup{}"; + } + + static { + CODEC.register("Objective", SetupObjective.class, SetupObjective.CODEC); + CODEC.register("ObjectiveLine", SetupObjectiveLine.class, SetupObjectiveLine.CODEC); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/objectivesetup/SetupObjective.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/objectivesetup/SetupObjective.java new file mode 100644 index 0000000..092c7c8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/objectivesetup/SetupObjective.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.objectivesetup; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveAsset; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SetupObjective extends ObjectiveTypeSetup { + public static final BuilderCodec CODEC = BuilderCodec.builder(SetupObjective.class, SetupObjective::new) + .append( + new KeyedCodec<>("ObjectiveId", Codec.STRING), (setupObjective, s) -> setupObjective.objectiveId = s, setupObjective -> setupObjective.objectiveId + ) + .addValidator(Validators.nonNull()) + .addValidatorLate(() -> ObjectiveAsset.VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + protected String objectiveId; + + public SetupObjective() { + } + + @Override + public String getObjectiveIdToStart() { + return this.objectiveId; + } + + @Nullable + @Override + public Objective setup(@Nonnull Set playerUUIDs, @Nonnull UUID worldUUID, @Nullable UUID markerUUID, @Nonnull Store store) { + return ObjectivePlugin.get().startObjective(this.objectiveId, playerUUIDs, worldUUID, markerUUID, store); + } + + @Nonnull + @Override + public String toString() { + return "SetupObjective{objectiveId='" + this.objectiveId + "'} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/objectivesetup/SetupObjectiveLine.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/objectivesetup/SetupObjectiveLine.java new file mode 100644 index 0000000..3559594 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/objectivesetup/SetupObjectiveLine.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.objectivesetup; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveLineAsset; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SetupObjectiveLine extends ObjectiveTypeSetup { + public static final BuilderCodec CODEC = BuilderCodec.builder(SetupObjectiveLine.class, SetupObjectiveLine::new) + .append( + new KeyedCodec<>("ObjectiveLineId", Codec.STRING), + (setupObjectiveLine, s) -> setupObjectiveLine.objectiveLineId = s, + setupObjectiveLine -> setupObjectiveLine.objectiveLineId + ) + .addValidator(Validators.nonNull()) + .addValidatorLate(() -> ObjectiveLineAsset.VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + protected String objectiveLineId; + + public SetupObjectiveLine() { + } + + @Nullable + @Override + public String getObjectiveIdToStart() { + ObjectiveLineAsset objectiveLineAsset = ObjectiveLineAsset.getAssetMap().getAsset(this.objectiveLineId); + return objectiveLineAsset != null ? objectiveLineAsset.getObjectiveIds()[0] : null; + } + + @Nullable + @Override + public Objective setup(@Nonnull Set playerUUIDs, @Nonnull UUID worldUUID, @Nullable UUID markerUUID, @Nonnull Store store) { + return ObjectivePlugin.get().startObjectiveLine(store, this.objectiveLineId, playerUUIDs, worldUUID, markerUUID); + } + + @Nonnull + @Override + public String toString() { + return "SetupObjectiveLine{objectiveLineId='" + this.objectiveLineId + "'} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/BlockTagOrItemIdField.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/BlockTagOrItemIdField.java new file mode 100644 index 0000000..7bb61a1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/BlockTagOrItemIdField.java @@ -0,0 +1,102 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.task; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockTagOrItemIdField { + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockTagOrItemIdField.class, BlockTagOrItemIdField::new) + .append( + new KeyedCodec<>("BlockTag", Codec.STRING), + (blockTagOrItemIdField, s) -> blockTagOrItemIdField.blockTag = s, + blockTagOrItemIdField -> blockTagOrItemIdField.blockTag + ) + .add() + .append( + new KeyedCodec<>("ItemId", Codec.STRING), + (blockTagOrItemIdField, blockTypeKey) -> blockTagOrItemIdField.itemId = blockTypeKey, + blockTagOrItemIdField -> blockTagOrItemIdField.itemId + ) + .addValidator(Item.VALIDATOR_CACHE.getValidator()) + .add() + .validator((task, validationResults) -> { + if (task.blockTag == null && task.itemId == null) { + validationResults.fail("One and only one of BlockTag or ItemId must be set!"); + } + }) + .afterDecode(blockTagOrItemIdField -> { + if (blockTagOrItemIdField.blockTag != null) { + blockTagOrItemIdField.blockTagIndex = AssetRegistry.getOrCreateTagIndex(blockTagOrItemIdField.blockTag); + } + }) + .build(); + protected String blockTag; + protected int blockTagIndex = Integer.MIN_VALUE; + protected String itemId; + + public BlockTagOrItemIdField(String blockTag, String itemId) { + this.blockTag = blockTag; + this.itemId = itemId; + } + + protected BlockTagOrItemIdField() { + } + + public int getBlockTagIndex() { + return this.blockTagIndex; + } + + public String getItemId() { + return this.itemId; + } + + public boolean isBlockTypeIncluded(String blockTypeToCheck) { + if (this.blockTagIndex != Integer.MIN_VALUE) { + return Item.getAssetMap().getKeysForTag(this.blockTagIndex).contains(blockTypeToCheck); + } else { + return this.itemId != null ? this.itemId.equals(blockTypeToCheck) : false; + } + } + + public void consumeItemStacks(@Nonnull ItemContainer container, int quantity) { + if (this.itemId != null) { + container.removeItemStack(new ItemStack(this.itemId, quantity)); + } else if (this.blockTagIndex != Integer.MIN_VALUE) { + container.removeTag(this.blockTagIndex, quantity); + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + BlockTagOrItemIdField that = (BlockTagOrItemIdField)o; + if (this.blockTag != null ? this.blockTag.equals(that.blockTag) : that.blockTag == null) { + return this.itemId != null ? this.itemId.equals(that.itemId) : that.itemId == null; + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.blockTag != null ? this.blockTag.hashCode() : 0; + return 31 * result + (this.itemId != null ? this.itemId.hashCode() : 0); + } + + @Nonnull + @Override + public String toString() { + return "BlockTagOrItemIdField{blockTag='" + this.blockTag + "', itemId=" + this.itemId + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/CountObjectiveTaskAsset.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/CountObjectiveTaskAsset.java new file mode 100644 index 0000000..7aea1d5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/CountObjectiveTaskAsset.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.task; + +import com.hypixel.hytale.builtin.adventure.objectives.config.taskcondition.TaskConditionAsset; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public abstract class CountObjectiveTaskAsset extends ObjectiveTaskAsset { + public static final BuilderCodec CODEC = BuilderCodec.abstractBuilder(CountObjectiveTaskAsset.class, BASE_CODEC) + .append(new KeyedCodec<>("Count", Codec.INTEGER), (taskAsset, count) -> taskAsset.count = count, taskAsset -> taskAsset.count) + .addValidator(Validators.greaterThan(0)) + .add() + .build(); + protected int count = 1; + + public CountObjectiveTaskAsset(String descriptionId, TaskConditionAsset[] taskConditions, Vector3i[] mapMarkers, int count) { + super(descriptionId, taskConditions, mapMarkers); + this.count = count; + } + + protected CountObjectiveTaskAsset() { + } + + public int getCount() { + return this.count; + } + + @Override + protected boolean matchesAsset0(ObjectiveTaskAsset task) { + return !(task instanceof CountObjectiveTaskAsset) ? false : ((CountObjectiveTaskAsset)task).count == this.count; + } + + @Nonnull + @Override + public String toString() { + return "CountObjectiveTaskAsset{count=" + this.count + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/CraftObjectiveTaskAsset.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/CraftObjectiveTaskAsset.java new file mode 100644 index 0000000..69e4ffe --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/CraftObjectiveTaskAsset.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.task; + +import com.hypixel.hytale.builtin.adventure.objectives.config.taskcondition.TaskConditionAsset; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import javax.annotation.Nonnull; + +public class CraftObjectiveTaskAsset extends CountObjectiveTaskAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CraftObjectiveTaskAsset.class, CraftObjectiveTaskAsset::new, CountObjectiveTaskAsset.CODEC + ) + .append(new KeyedCodec<>("ItemId", Codec.STRING), (objective, entityType) -> objective.itemId = entityType, objective -> objective.itemId) + .addValidator(Validators.nonNull()) + .addValidator(Item.VALIDATOR_CACHE.getValidator()) + .add() + .build(); + protected String itemId; + + public CraftObjectiveTaskAsset(String descriptionId, TaskConditionAsset[] taskConditions, Vector3i[] mapMarkers, int count, String itemId) { + super(descriptionId, taskConditions, mapMarkers, count); + this.itemId = itemId; + } + + protected CraftObjectiveTaskAsset() { + } + + @Nonnull + @Override + public ObjectiveTaskAsset.TaskScope getTaskScope() { + return ObjectiveTaskAsset.TaskScope.PLAYER_AND_MARKER; + } + + public String getItemId() { + return this.itemId; + } + + @Override + protected boolean matchesAsset0(ObjectiveTaskAsset task) { + if (!super.matchesAsset0(task)) { + return false; + } else { + return !(task instanceof CraftObjectiveTaskAsset) ? false : ((CraftObjectiveTaskAsset)task).itemId.equals(this.itemId); + } + } + + @Nonnull + @Override + public String toString() { + return "CraftObjectiveTaskAsset{itemId='" + this.itemId + "'} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/GatherObjectiveTaskAsset.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/GatherObjectiveTaskAsset.java new file mode 100644 index 0000000..9080ba7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/GatherObjectiveTaskAsset.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.task; + +import com.hypixel.hytale.builtin.adventure.objectives.config.taskcondition.TaskConditionAsset; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class GatherObjectiveTaskAsset extends CountObjectiveTaskAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + GatherObjectiveTaskAsset.class, GatherObjectiveTaskAsset::new, CountObjectiveTaskAsset.CODEC + ) + .append( + new KeyedCodec<>("BlockTagOrItemId", BlockTagOrItemIdField.CODEC), + (gatherObjectiveTaskAsset, blockTagOrItemIdField) -> gatherObjectiveTaskAsset.blockTagOrItemIdField = blockTagOrItemIdField, + gatherObjectiveTaskAsset -> gatherObjectiveTaskAsset.blockTagOrItemIdField + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + protected BlockTagOrItemIdField blockTagOrItemIdField; + + public GatherObjectiveTaskAsset( + String descriptionId, TaskConditionAsset[] taskConditions, Vector3i[] mapMarkers, int count, BlockTagOrItemIdField blockTagOrItemIdField + ) { + super(descriptionId, taskConditions, mapMarkers, count); + this.blockTagOrItemIdField = blockTagOrItemIdField; + } + + protected GatherObjectiveTaskAsset() { + } + + @Nonnull + @Override + public ObjectiveTaskAsset.TaskScope getTaskScope() { + return ObjectiveTaskAsset.TaskScope.PLAYER_AND_MARKER; + } + + public BlockTagOrItemIdField getBlockTagOrItemIdField() { + return this.blockTagOrItemIdField; + } + + @Override + protected boolean matchesAsset0(ObjectiveTaskAsset task) { + if (!super.matchesAsset0(task)) { + return false; + } else { + return !(task instanceof GatherObjectiveTaskAsset) ? false : ((GatherObjectiveTaskAsset)task).blockTagOrItemIdField.equals(this.blockTagOrItemIdField); + } + } + + @Nonnull + @Override + public String toString() { + return "GatherObjectiveTaskAsset{blockTagOrItemIdTask=" + this.blockTagOrItemIdField + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/ObjectiveTaskAsset.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/ObjectiveTaskAsset.java new file mode 100644 index 0000000..0e059f4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/ObjectiveTaskAsset.java @@ -0,0 +1,118 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.task; + +import com.hypixel.hytale.builtin.adventure.objectives.config.taskcondition.TaskConditionAsset; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import java.text.MessageFormat; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public abstract class ObjectiveTaskAsset { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(ObjectiveTaskAsset.class) + .append( + new KeyedCodec<>("DescriptionId", Codec.STRING), + (objectiveTaskAsset, s) -> objectiveTaskAsset.descriptionId = s, + objectiveTaskAsset -> objectiveTaskAsset.descriptionId + ) + .add() + .append( + new KeyedCodec<>("TaskConditions", new ArrayCodec<>(TaskConditionAsset.CODEC, TaskConditionAsset[]::new)), + (useBlockObjectiveTaskAsset, inventoryConditions) -> useBlockObjectiveTaskAsset.taskConditions = inventoryConditions, + useBlockObjectiveTaskAsset -> useBlockObjectiveTaskAsset.taskConditions + ) + .add() + .append( + new KeyedCodec<>("MapMarkers", new ArrayCodec<>(Vector3i.CODEC, Vector3i[]::new)), + (taskAsset, vector3is) -> taskAsset.mapMarkers = vector3is, + taskAsset -> taskAsset.mapMarkers + ) + .add() + .build(); + public static final String TASK_DESCRIPTION_KEY = "server.objectives.{0}.taskSet.{1}.task.{2}"; + protected String descriptionId; + protected TaskConditionAsset[] taskConditions; + protected Vector3i[] mapMarkers; + private String defaultDescriptionId; + + public ObjectiveTaskAsset(String descriptionId, TaskConditionAsset[] taskConditions, Vector3i[] mapMarkers) { + this.descriptionId = descriptionId; + this.taskConditions = taskConditions; + this.mapMarkers = mapMarkers; + } + + protected ObjectiveTaskAsset() { + } + + public String getDescriptionId() { + return this.descriptionId; + } + + @Nonnull + public String getDescriptionKey(String objectiveId, int taskSetIndex, int taskIndex) { + if (this.descriptionId != null) { + return this.descriptionId; + } else { + if (this.defaultDescriptionId == null) { + this.defaultDescriptionId = MessageFormat.format("server.objectives.{0}.taskSet.{1}.task.{2}", objectiveId, taskSetIndex, taskIndex); + } + + return this.defaultDescriptionId; + } + } + + public TaskConditionAsset[] getTaskConditions() { + return this.taskConditions; + } + + public Vector3i[] getMapMarkers() { + return this.mapMarkers; + } + + public abstract ObjectiveTaskAsset.TaskScope getTaskScope(); + + public boolean matchesAsset(@Nonnull ObjectiveTaskAsset task) { + if (!Arrays.equals((Object[])task.taskConditions, (Object[])this.taskConditions)) { + return false; + } else if (!Arrays.equals((Object[])task.mapMarkers, (Object[])this.mapMarkers)) { + return false; + } else { + return !task.getClass().equals(this.getClass()) ? false : this.matchesAsset0(task); + } + } + + protected abstract boolean matchesAsset0(ObjectiveTaskAsset var1); + + @Nonnull + @Override + public String toString() { + return "ObjectiveTaskAsset{descriptionId='" + + this.descriptionId + + "', taskConditions=" + + Arrays.toString((Object[])this.taskConditions) + + ", mapMarkers=" + + Arrays.toString((Object[])this.mapMarkers) + + "}"; + } + + public static enum TaskScope { + PLAYER, + MARKER, + PLAYER_AND_MARKER; + + private TaskScope() { + } + + public boolean isTaskPossibleForMarker() { + return this == MARKER || this == PLAYER_AND_MARKER; + } + + public boolean isTaskPossibleForPlayer() { + return this == PLAYER || this == PLAYER_AND_MARKER; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/ReachLocationTaskAsset.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/ReachLocationTaskAsset.java new file mode 100644 index 0000000..22b190f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/ReachLocationTaskAsset.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.task; + +import com.hypixel.hytale.builtin.adventure.objectives.markers.reachlocation.ReachLocationMarkerAsset; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class ReachLocationTaskAsset extends ObjectiveTaskAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(ReachLocationTaskAsset.class, ReachLocationTaskAsset::new, BASE_CODEC) + .append( + new KeyedCodec<>("TargetLocation", Codec.STRING), + (reachLocationTaskAsset, vector3i) -> reachLocationTaskAsset.targetLocationId = vector3i, + reachLocationTaskAsset -> reachLocationTaskAsset.targetLocationId + ) + .addValidator(Validators.nonNull()) + .addValidator(ReachLocationMarkerAsset.VALIDATOR_CACHE.getValidator()) + .add() + .build(); + protected String targetLocationId; + + public ReachLocationTaskAsset() { + } + + @Nonnull + @Override + public ObjectiveTaskAsset.TaskScope getTaskScope() { + return ObjectiveTaskAsset.TaskScope.PLAYER; + } + + public String getTargetLocationId() { + return this.targetLocationId; + } + + @Override + protected boolean matchesAsset0(ObjectiveTaskAsset task) { + return task instanceof ReachLocationTaskAsset asset ? Objects.equals(asset.targetLocationId, this.targetLocationId) : false; + } + + @Nonnull + @Override + public String toString() { + return "ReachLocationTaskAsset{targetLocationId=" + this.targetLocationId + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/TaskSet.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/TaskSet.java new file mode 100644 index 0000000..566227d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/TaskSet.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.task; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import java.text.MessageFormat; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class TaskSet { + public static final BuilderCodec CODEC = BuilderCodec.builder(TaskSet.class, TaskSet::new) + .append(new KeyedCodec<>("DescriptionId", Codec.STRING), (taskSet, s) -> taskSet.descriptionId = s, taskSet -> taskSet.descriptionId) + .add() + .append( + new KeyedCodec<>("Tasks", new ArrayCodec<>(ObjectiveTaskAsset.CODEC, ObjectiveTaskAsset[]::new)), + (taskSet, objectiveTaskAssets) -> taskSet.tasks = objectiveTaskAssets, + taskSet -> taskSet.tasks + ) + .addValidator(Validators.nonEmptyArray()) + .add() + .build(); + public static final String TASKSET_DESCRIPTION_KEY = "server.objectives.{0}.taskSet.{1}"; + protected String descriptionId; + protected ObjectiveTaskAsset[] tasks; + + public TaskSet(String descriptionId, ObjectiveTaskAsset[] tasks) { + this.descriptionId = descriptionId; + this.tasks = tasks; + } + + protected TaskSet() { + } + + public String getDescriptionId() { + return this.descriptionId; + } + + @Nonnull + public String getDescriptionKey(String objectiveId, int taskSetIndex) { + return this.descriptionId != null ? this.descriptionId : MessageFormat.format("server.objectives.{0}.taskSet.{1}", objectiveId, taskSetIndex); + } + + public ObjectiveTaskAsset[] getTasks() { + return this.tasks; + } + + @Nonnull + @Override + public String toString() { + return "TaskSet{descriptionId='" + this.descriptionId + "', tasks=" + Arrays.toString((Object[])this.tasks) + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/TreasureMapObjectiveTaskAsset.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/TreasureMapObjectiveTaskAsset.java new file mode 100644 index 0000000..e625099 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/TreasureMapObjectiveTaskAsset.java @@ -0,0 +1,187 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.task; + +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.builtin.adventure.objectives.config.taskcondition.TaskConditionAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.worldlocationproviders.WorldLocationProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TreasureMapObjectiveTaskAsset extends ObjectiveTaskAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + TreasureMapObjectiveTaskAsset.class, TreasureMapObjectiveTaskAsset::new, BASE_CODEC + ) + .append( + new KeyedCodec<>("Chests", new ArrayCodec<>(TreasureMapObjectiveTaskAsset.ChestConfig.CODEC, TreasureMapObjectiveTaskAsset.ChestConfig[]::new)), + (treasureMapObjectiveTaskAsset, chestConfigs) -> treasureMapObjectiveTaskAsset.chestConfigs = chestConfigs, + treasureMapObjectiveTaskAsset -> treasureMapObjectiveTaskAsset.chestConfigs + ) + .addValidator(Validators.nonEmptyArray()) + .add() + .build(); + protected TreasureMapObjectiveTaskAsset.ChestConfig[] chestConfigs; + + public TreasureMapObjectiveTaskAsset( + String descriptionId, TaskConditionAsset[] taskConditions, Vector3i[] mapMarkers, TreasureMapObjectiveTaskAsset.ChestConfig[] chestConfigs + ) { + super(descriptionId, taskConditions, mapMarkers); + this.chestConfigs = chestConfigs; + } + + protected TreasureMapObjectiveTaskAsset() { + } + + @Nonnull + @Override + public ObjectiveTaskAsset.TaskScope getTaskScope() { + return ObjectiveTaskAsset.TaskScope.PLAYER; + } + + public TreasureMapObjectiveTaskAsset.ChestConfig[] getChestConfigs() { + return this.chestConfigs; + } + + @Override + protected boolean matchesAsset0(ObjectiveTaskAsset task) { + return task instanceof TreasureMapObjectiveTaskAsset treasureMapObjectiveTaskAsset + ? Arrays.equals((Object[])treasureMapObjectiveTaskAsset.chestConfigs, (Object[])this.chestConfigs) + : false; + } + + @Nonnull + @Override + public String toString() { + return "TreasureMapObjectiveTaskAsset{chestConfigs=" + Arrays.toString((Object[])this.chestConfigs) + "} " + super.toString(); + } + + public static class ChestConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder( + TreasureMapObjectiveTaskAsset.ChestConfig.class, TreasureMapObjectiveTaskAsset.ChestConfig::new + ) + .append(new KeyedCodec<>("MinRadius", Codec.FLOAT), (chestConfig, aFloat) -> chestConfig.minRadius = aFloat, chestConfig -> chestConfig.minRadius) + .addValidator(Validators.greaterThan(0.0F)) + .add() + .append( + new KeyedCodec<>("MaxRadius", Codec.FLOAT), (chestConfig, aFloat) -> chestConfig.maxRadius = aFloat, chestConfig -> chestConfig.maxRadius + ) + .addValidator(Validators.greaterThan(1.0F)) + .add() + .append( + new KeyedCodec<>("DropList", new ContainedAssetCodec<>(ItemDropList.class, ItemDropList.CODEC)), + (chestConfig, s) -> chestConfig.droplistId = s, + chestConfig -> chestConfig.droplistId + ) + .addValidator(Validators.nonNull()) + .addValidator(ItemDropList.VALIDATOR_CACHE.getValidator()) + .add() + .append( + new KeyedCodec<>("WorldLocationCondition", WorldLocationProvider.CODEC), + (chestConfig, worldLocationCondition) -> chestConfig.worldLocationProvider = worldLocationCondition, + chestConfig -> chestConfig.worldLocationProvider + ) + .add() + .append( + new KeyedCodec<>("ChestBlockTypeKey", Codec.STRING), + (chestConfig, blockTypeKey) -> chestConfig.chestBlockTypeKey = blockTypeKey, + chestConfig -> chestConfig.chestBlockTypeKey + ) + .addValidator(Validators.nonNull()) + .addValidator(BlockType.VALIDATOR_CACHE.getValidator()) + .add() + .afterDecode( + chestConfig -> { + if (chestConfig.minRadius >= chestConfig.maxRadius) { + throw new IllegalArgumentException( + "ChestConfig.MinRadius (" + chestConfig.minRadius + ") needs to be greater than ChestConfig.MaxRadius (" + chestConfig.maxRadius + ")" + ); + } + } + ) + .build(); + protected float minRadius = 10.0F; + protected float maxRadius = 20.0F; + protected String droplistId; + protected WorldLocationProvider worldLocationProvider; + protected String chestBlockTypeKey; + + public ChestConfig() { + } + + public float getMinRadius() { + return this.minRadius; + } + + public float getMaxRadius() { + return this.maxRadius; + } + + public String getDroplistId() { + return this.droplistId; + } + + public WorldLocationProvider getWorldLocationProvider() { + return this.worldLocationProvider; + } + + public String getChestBlockTypeKey() { + return this.chestBlockTypeKey; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + TreasureMapObjectiveTaskAsset.ChestConfig that = (TreasureMapObjectiveTaskAsset.ChestConfig)o; + if (Float.compare(that.minRadius, this.minRadius) != 0) { + return false; + } else if (Float.compare(that.maxRadius, this.maxRadius) != 0) { + return false; + } else if (this.droplistId != null ? this.droplistId.equals(that.droplistId) : that.droplistId == null) { + if (this.worldLocationProvider != null ? this.worldLocationProvider.equals(that.worldLocationProvider) : that.worldLocationProvider == null) { + return this.chestBlockTypeKey != null ? this.chestBlockTypeKey.equals(that.chestBlockTypeKey) : that.chestBlockTypeKey == null; + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.minRadius != 0.0F ? Float.floatToIntBits(this.minRadius) : 0; + result = 31 * result + (this.maxRadius != 0.0F ? Float.floatToIntBits(this.maxRadius) : 0); + result = 31 * result + (this.droplistId != null ? this.droplistId.hashCode() : 0); + result = 31 * result + (this.worldLocationProvider != null ? this.worldLocationProvider.hashCode() : 0); + return 31 * result + (this.chestBlockTypeKey != null ? this.chestBlockTypeKey.hashCode() : 0); + } + + @Nonnull + @Override + public String toString() { + return "ChestConfig{minRadius=" + + this.minRadius + + ", maxRadius=" + + this.maxRadius + + ", droplistId='" + + this.droplistId + + "', worldLocationCondition=" + + this.worldLocationProvider + + ", chestBlockTypeKey=" + + this.chestBlockTypeKey + + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/UseBlockObjectiveTaskAsset.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/UseBlockObjectiveTaskAsset.java new file mode 100644 index 0000000..e14ff7c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/UseBlockObjectiveTaskAsset.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.task; + +import com.hypixel.hytale.builtin.adventure.objectives.config.taskcondition.TaskConditionAsset; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class UseBlockObjectiveTaskAsset extends CountObjectiveTaskAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + UseBlockObjectiveTaskAsset.class, UseBlockObjectiveTaskAsset::new, CountObjectiveTaskAsset.CODEC + ) + .append( + new KeyedCodec<>("BlockTagOrItemId", BlockTagOrItemIdField.CODEC), + (useBlockObjectiveTaskAsset, blockTypeOrSetTaskField) -> useBlockObjectiveTaskAsset.blockTagOrItemIdField = blockTypeOrSetTaskField, + useBlockObjectiveTaskAsset -> useBlockObjectiveTaskAsset.blockTagOrItemIdField + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + protected BlockTagOrItemIdField blockTagOrItemIdField; + + public UseBlockObjectiveTaskAsset( + String descriptionId, TaskConditionAsset[] taskConditions, Vector3i[] mapMarkers, int count, BlockTagOrItemIdField blockTagOrItemIdField + ) { + super(descriptionId, taskConditions, mapMarkers, count); + this.blockTagOrItemIdField = blockTagOrItemIdField; + } + + protected UseBlockObjectiveTaskAsset() { + } + + @Nonnull + @Override + public ObjectiveTaskAsset.TaskScope getTaskScope() { + return ObjectiveTaskAsset.TaskScope.PLAYER_AND_MARKER; + } + + public BlockTagOrItemIdField getBlockTagOrItemIdField() { + return this.blockTagOrItemIdField; + } + + @Override + protected boolean matchesAsset0(ObjectiveTaskAsset task) { + if (!super.matchesAsset0(task)) { + return false; + } else { + return !(task instanceof UseBlockObjectiveTaskAsset) + ? false + : ((UseBlockObjectiveTaskAsset)task).blockTagOrItemIdField.equals(this.blockTagOrItemIdField); + } + } + + @Nonnull + @Override + public String toString() { + return "UseBlockObjectiveTaskAsset{blockTagOrItemIdField=" + this.blockTagOrItemIdField + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/UseEntityObjectiveTaskAsset.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/UseEntityObjectiveTaskAsset.java new file mode 100644 index 0000000..9e94529 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/task/UseEntityObjectiveTaskAsset.java @@ -0,0 +1,166 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.task; + +import com.hypixel.hytale.builtin.adventure.objectives.config.taskcondition.TaskConditionAsset; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UseEntityObjectiveTaskAsset extends CountObjectiveTaskAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + UseEntityObjectiveTaskAsset.class, UseEntityObjectiveTaskAsset::new, CountObjectiveTaskAsset.CODEC + ) + .append( + new KeyedCodec<>("TaskId", Codec.STRING), + (useEntityObjectiveTaskAsset, s) -> useEntityObjectiveTaskAsset.taskId = s, + useEntityObjectiveTaskAsset -> useEntityObjectiveTaskAsset.taskId + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("AnimationIdToPlay", Codec.STRING), + (useEntityObjectiveTaskAsset, s) -> useEntityObjectiveTaskAsset.animationIdToPlay = s, + useEntityObjectiveTaskAsset -> useEntityObjectiveTaskAsset.animationIdToPlay + ) + .add() + .append( + new KeyedCodec<>("Dialog", UseEntityObjectiveTaskAsset.DialogOptions.CODEC), + (useEntityObjectiveTask, dialogOptions) -> useEntityObjectiveTask.dialogOptions = dialogOptions, + useEntityObjectiveTask -> useEntityObjectiveTask.dialogOptions + ) + .add() + .build(); + protected String taskId; + protected String animationIdToPlay; + protected UseEntityObjectiveTaskAsset.DialogOptions dialogOptions; + + public UseEntityObjectiveTaskAsset( + String descriptionId, + TaskConditionAsset[] taskConditions, + Vector3i[] mapMarkers, + int count, + String taskId, + String animationIdToPlay, + UseEntityObjectiveTaskAsset.DialogOptions dialogOptions + ) { + super(descriptionId, taskConditions, mapMarkers, count); + this.taskId = taskId; + this.animationIdToPlay = animationIdToPlay; + this.dialogOptions = dialogOptions; + } + + protected UseEntityObjectiveTaskAsset() { + } + + @Nonnull + @Override + public ObjectiveTaskAsset.TaskScope getTaskScope() { + return ObjectiveTaskAsset.TaskScope.PLAYER_AND_MARKER; + } + + public String getTaskId() { + return this.taskId; + } + + public String getAnimationIdToPlay() { + return this.animationIdToPlay; + } + + public UseEntityObjectiveTaskAsset.DialogOptions getDialogOptions() { + return this.dialogOptions; + } + + @Override + protected boolean matchesAsset0(ObjectiveTaskAsset task) { + if (!super.matchesAsset0(task)) { + return false; + } else if (task instanceof UseEntityObjectiveTaskAsset asset) { + if (!Objects.equals(asset.animationIdToPlay, this.animationIdToPlay)) { + return false; + } else { + return !Objects.equals(asset.dialogOptions, this.dialogOptions) ? false : asset.taskId.equals(this.taskId); + } + } else { + return false; + } + } + + @Nonnull + @Override + public String toString() { + return "UseEntityObjectiveTaskAsset{taskId='" + + this.taskId + + "', animationIdToPlay='" + + this.animationIdToPlay + + "', dialogOptions=" + + this.dialogOptions + + "} " + + super.toString(); + } + + public static class DialogOptions { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder( + UseEntityObjectiveTaskAsset.DialogOptions.class, UseEntityObjectiveTaskAsset.DialogOptions::new + ) + .append( + new KeyedCodec<>("EntityNameKey", Codec.STRING), + (dialogOptions, s) -> dialogOptions.entityNameKey = s, + dialogOptions -> dialogOptions.entityNameKey + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("DialogKey", Codec.STRING), (dialogOptions, s) -> dialogOptions.dialogKey = s, dialogOptions -> dialogOptions.dialogKey + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + protected String entityNameKey; + protected String dialogKey; + + public DialogOptions(String entityNameKey, String dialogKey) { + this.entityNameKey = entityNameKey; + this.dialogKey = dialogKey; + } + + protected DialogOptions() { + } + + public String getEntityNameKey() { + return this.entityNameKey; + } + + public String getDialogKey() { + return this.dialogKey; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + UseEntityObjectiveTaskAsset.DialogOptions that = (UseEntityObjectiveTaskAsset.DialogOptions)o; + return !this.entityNameKey.equals(that.entityNameKey) ? false : this.dialogKey.equals(that.dialogKey); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.entityNameKey.hashCode(); + return 31 * result + this.dialogKey.hashCode(); + } + + @Nonnull + @Override + public String toString() { + return "DialogOptions{entityNameKey='" + this.entityNameKey + "', dialogKey='" + this.dialogKey + "'}"; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/taskcondition/SoloInventoryCondition.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/taskcondition/SoloInventoryCondition.java new file mode 100644 index 0000000..b1af7ca --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/taskcondition/SoloInventoryCondition.java @@ -0,0 +1,141 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.taskcondition; + +import com.hypixel.hytale.builtin.adventure.objectives.config.task.BlockTagOrItemIdField; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SoloInventoryCondition extends TaskConditionAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(SoloInventoryCondition.class, SoloInventoryCondition::new) + .append( + new KeyedCodec<>("BlockTagOrItemId", BlockTagOrItemIdField.CODEC), + (soloInventoryCondition, blockTagOrItemIdField) -> soloInventoryCondition.blockTypeOrTagTask = blockTagOrItemIdField, + soloInventoryCondition -> soloInventoryCondition.blockTypeOrTagTask + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("Quantity", Codec.INTEGER), + (soloInventoryCondition, integer) -> soloInventoryCondition.quantity = integer, + soloInventoryCondition -> soloInventoryCondition.quantity + ) + .addValidator(Validators.greaterThan(0)) + .add() + .append( + new KeyedCodec<>("ConsumeOnCompletion", Codec.BOOLEAN), + (soloInventoryCondition, aBoolean) -> soloInventoryCondition.consumeOnCompletion = aBoolean, + soloInventoryCondition -> soloInventoryCondition.consumeOnCompletion + ) + .add() + .append( + new KeyedCodec<>("HoldInHand", Codec.BOOLEAN), + (soloInventoryCondition, aBoolean) -> soloInventoryCondition.holdInHand = aBoolean, + soloInventoryCondition -> soloInventoryCondition.holdInHand + ) + .add() + .build(); + protected BlockTagOrItemIdField blockTypeOrTagTask; + protected int quantity = 1; + protected boolean consumeOnCompletion; + protected boolean holdInHand; + + public SoloInventoryCondition() { + } + + public BlockTagOrItemIdField getBlockTypeOrTagTask() { + return this.blockTypeOrTagTask; + } + + public int getQuantity() { + return this.quantity; + } + + public boolean isConsumeOnCompletion() { + return this.consumeOnCompletion; + } + + public boolean isHoldInHand() { + return this.holdInHand; + } + + @Override + public boolean isConditionFulfilled(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, Set objectivePlayers) { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + return false; + } else { + Inventory inventory = playerComponent.getInventory(); + if (this.holdInHand) { + ItemStack itemInHand = inventory.getItemInHand(); + return !this.blockTypeOrTagTask.isBlockTypeIncluded(itemInHand.getItemId()) ? false : inventory.getItemInHand().getQuantity() >= this.quantity; + } else { + return inventory.getCombinedHotbarFirst().countItemStacks(itemStack -> this.blockTypeOrTagTask.isBlockTypeIncluded(itemStack.getItemId())) + >= this.quantity; + } + } + } + + @Override + public void consumeCondition(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, Set objectivePlayers) { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + if (this.consumeOnCompletion) { + this.blockTypeOrTagTask.consumeItemStacks(playerComponent.getInventory().getCombinedHotbarFirst(), this.quantity); + } + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + SoloInventoryCondition that = (SoloInventoryCondition)o; + if (this.quantity != that.quantity) { + return false; + } else if (this.consumeOnCompletion != that.consumeOnCompletion) { + return false; + } else if (this.holdInHand != that.holdInHand) { + return false; + } else { + return this.blockTypeOrTagTask != null ? this.blockTypeOrTagTask.equals(that.blockTypeOrTagTask) : that.blockTypeOrTagTask == null; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.blockTypeOrTagTask != null ? this.blockTypeOrTagTask.hashCode() : 0; + result = 31 * result + this.quantity; + result = 31 * result + (this.consumeOnCompletion ? 1 : 0); + return 31 * result + (this.holdInHand ? 1 : 0); + } + + @Nonnull + @Override + public String toString() { + return "SoloInventoryCondition{blockTypeOrTagTask=" + + this.blockTypeOrTagTask + + ", quantity=" + + this.quantity + + ", consumeOnCompletion=" + + this.consumeOnCompletion + + ", holdInHand=" + + this.holdInHand + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/taskcondition/TaskConditionAsset.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/taskcondition/TaskConditionAsset.java new file mode 100644 index 0000000..559e2ca --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/taskcondition/TaskConditionAsset.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.taskcondition; + +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import java.util.UUID; + +public abstract class TaskConditionAsset { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + + protected TaskConditionAsset() { + } + + public abstract boolean isConditionFulfilled(ComponentAccessor var1, Ref var2, Set var3); + + public abstract void consumeCondition(ComponentAccessor var1, Ref var2, Set var3); + + @Override + public abstract boolean equals(Object var1); + + @Override + public abstract int hashCode(); + + static { + CODEC.register("SoloInventory", SoloInventoryCondition.class, SoloInventoryCondition.CODEC); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/triggercondition/HourRangeTriggerCondition.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/triggercondition/HourRangeTriggerCondition.java new file mode 100644 index 0000000..0c3e649 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/triggercondition/HourRangeTriggerCondition.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.triggercondition; + +import com.hypixel.hytale.builtin.adventure.objectives.markers.objectivelocation.ObjectiveLocationMarker; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class HourRangeTriggerCondition extends ObjectiveLocationTriggerCondition { + public static final BuilderCodec CODEC = BuilderCodec.builder(HourRangeTriggerCondition.class, HourRangeTriggerCondition::new) + .append( + new KeyedCodec<>("MinHour", Codec.INTEGER), + (hourRangeTriggerCondition, integer) -> hourRangeTriggerCondition.minHour = integer, + hourRangeTriggerCondition -> hourRangeTriggerCondition.minHour + ) + .add() + .append( + new KeyedCodec<>("MaxHour", Codec.INTEGER), + (hourRangeTriggerCondition, integer) -> hourRangeTriggerCondition.maxHour = integer, + hourRangeTriggerCondition -> hourRangeTriggerCondition.maxHour + ) + .add() + .build(); + protected static final ResourceType WORLD_TIME_RESOURCE_RESOURCE_TYPE = WorldTimeResource.getResourceType(); + protected int minHour; + protected int maxHour; + + public HourRangeTriggerCondition() { + } + + @Override + public boolean isConditionMet( + @Nonnull ComponentAccessor componentAccessor, Ref ref, ObjectiveLocationMarker objectiveLocationMarker + ) { + int currentHour = componentAccessor.getResource(WORLD_TIME_RESOURCE_RESOURCE_TYPE).getCurrentHour(); + return this.minHour > this.maxHour + ? currentHour >= this.minHour || currentHour < this.maxHour + : currentHour >= this.minHour && currentHour < this.maxHour; + } + + @Nonnull + @Override + public String toString() { + return "HourRangeTriggerCondition{minHour=" + this.minHour + ", maxHour=" + this.maxHour + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/triggercondition/ObjectiveLocationTriggerCondition.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/triggercondition/ObjectiveLocationTriggerCondition.java new file mode 100644 index 0000000..5ad2faa --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/triggercondition/ObjectiveLocationTriggerCondition.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.triggercondition; + +import com.hypixel.hytale.builtin.adventure.objectives.markers.objectivelocation.ObjectiveLocationMarker; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public abstract class ObjectiveLocationTriggerCondition { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + + public ObjectiveLocationTriggerCondition() { + } + + @Nonnull + @Override + public String toString() { + return "ObjectiveLocationTriggerCondition{}"; + } + + public abstract boolean isConditionMet(ComponentAccessor var1, Ref var2, ObjectiveLocationMarker var3); + + static { + CODEC.register("HourRange", HourRangeTriggerCondition.class, HourRangeTriggerCondition.CODEC); + CODEC.register("Weather", WeatherTriggerCondition.class, WeatherTriggerCondition.CODEC); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/triggercondition/WeatherTriggerCondition.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/triggercondition/WeatherTriggerCondition.java new file mode 100644 index 0000000..f6813df --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/triggercondition/WeatherTriggerCondition.java @@ -0,0 +1,92 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.triggercondition; + +import com.hypixel.hytale.builtin.adventure.objectives.markers.objectivelocation.ObjectiveLocationMarker; +import com.hypixel.hytale.builtin.weather.resources.WeatherResource; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class WeatherTriggerCondition extends ObjectiveLocationTriggerCondition { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(WeatherTriggerCondition.class, WeatherTriggerCondition::new) + .append( + new KeyedCodec<>("WeatherIds", Codec.STRING_ARRAY), + (weatherTriggerCondition, strings) -> weatherTriggerCondition.weatherIds = strings, + weatherTriggerCondition -> weatherTriggerCondition.weatherIds + ) + .addValidator(Validators.nonEmptyArray()) + .addValidator(Weather.VALIDATOR_CACHE.getArrayValidator()) + .add() + .afterDecode(weatherTriggerCondition -> { + if (weatherTriggerCondition.weatherIds != null) { + weatherTriggerCondition.weatherIndexes = new int[weatherTriggerCondition.weatherIds.length]; + + for (int i = 0; i < weatherTriggerCondition.weatherIds.length; i++) { + String key = weatherTriggerCondition.weatherIds[i]; + int index = Weather.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + weatherTriggerCondition.weatherIndexes[i] = index; + } + + Arrays.sort(weatherTriggerCondition.weatherIndexes); + } + }) + .build(); + @Nonnull + protected static final ResourceType WEATHER_RESOURCE_RESOURCE_TYPE = WeatherResource.getResourceType(); + @Nonnull + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected String[] weatherIds; + protected int[] weatherIndexes; + + public WeatherTriggerCondition() { + } + + @Override + public boolean isConditionMet( + @Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, ObjectiveLocationMarker objectiveLocationMarker + ) { + WeatherResource weatherResource = componentAccessor.getResource(WEATHER_RESOURCE_RESOURCE_TYPE); + TransformComponent transformComponent = componentAccessor.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + Ref chunkRef = transformComponent.getChunkRef(); + if (chunkRef != null && chunkRef.isValid()) { + World world = componentAccessor.getExternalData().getWorld(); + Store chunkStore = world.getChunkStore().getStore(); + BlockChunk blockChunkComponent = chunkStore.getComponent(chunkRef, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + int environmentIndex = blockChunkComponent.getEnvironment(transformComponent.getPosition()); + int currentWeatherIndex = weatherResource.getWeatherIndexForEnvironment(environmentIndex); + return Arrays.binarySearch(this.weatherIndexes, currentWeatherIndex) >= 0; + } else { + return false; + } + } + + @Nonnull + @Override + public String toString() { + return "WeatherTriggerCondition{weatherIds=" + Arrays.toString((Object[])this.weatherIds) + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/worldlocationproviders/CheckTagWorldHeightRadiusProvider.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/worldlocationproviders/CheckTagWorldHeightRadiusProvider.java new file mode 100644 index 0000000..0bf3db5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/worldlocationproviders/CheckTagWorldHeightRadiusProvider.java @@ -0,0 +1,113 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.worldlocationproviders; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.iterator.SpiralIterator; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CheckTagWorldHeightRadiusProvider extends WorldLocationProvider { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CheckTagWorldHeightRadiusProvider.class, CheckTagWorldHeightRadiusProvider::new, BASE_CODEC + ) + .append( + new KeyedCodec<>("BlockTags", Codec.STRING_ARRAY), + (lookBlocksBelowCondition, strings) -> lookBlocksBelowCondition.blockTags = strings, + lookBlocksBelowCondition -> lookBlocksBelowCondition.blockTags + ) + .addValidator(Validators.nonEmptyArray()) + .addValidator(Validators.uniqueInArray()) + .add() + .append( + new KeyedCodec<>("Radius", Codec.INTEGER), + (checkTagWorldHeightRadiusCondition, integer) -> checkTagWorldHeightRadiusCondition.radius = integer, + checkTagWorldHeightRadiusCondition -> checkTagWorldHeightRadiusCondition.radius + ) + .addValidator(Validators.greaterThan(0)) + .add() + .afterDecode(checkTagWorldHeightRadiusCondition -> { + if (checkTagWorldHeightRadiusCondition.blockTags != null) { + checkTagWorldHeightRadiusCondition.blockTagsIndexes = new int[checkTagWorldHeightRadiusCondition.blockTags.length]; + + for (int i = 0; i < checkTagWorldHeightRadiusCondition.blockTags.length; i++) { + String blockTag = checkTagWorldHeightRadiusCondition.blockTags[i]; + checkTagWorldHeightRadiusCondition.blockTagsIndexes[i] = AssetRegistry.getOrCreateTagIndex(blockTag); + } + } + }) + .build(); + protected String[] blockTags; + protected int radius = 5; + private int[] blockTagsIndexes; + + public CheckTagWorldHeightRadiusProvider(@Nonnull String[] blockTags, int radius) { + this.blockTags = blockTags; + this.radius = radius; + this.blockTagsIndexes = new int[blockTags.length]; + + for (int i = 0; i < blockTags.length; i++) { + String blockTag = blockTags[i]; + this.blockTagsIndexes[i] = AssetRegistry.getOrCreateTagIndex(blockTag); + } + } + + protected CheckTagWorldHeightRadiusProvider() { + } + + @Nullable + @Override + public Vector3i runCondition(@Nonnull World world, @Nonnull Vector3i position) { + SpiralIterator iterator = new SpiralIterator(position.x, position.z, this.radius); + + while (iterator.hasNext()) { + long pos = iterator.next(); + int blockX = MathUtil.unpackLeft(pos); + int blockZ = MathUtil.unpackRight(pos); + WorldChunk chunk = world.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(blockX, blockZ)); + int blockY = chunk.getHeight(blockX, blockZ); + int blockId = chunk.getBlock(blockX, blockY, blockZ); + + for (int i = 0; i < this.blockTagsIndexes.length; i++) { + if (BlockType.getAssetMap().getIndexesForTag(this.blockTagsIndexes[i]).contains(blockId)) { + return new Vector3i(blockX, blockY + 1, blockZ); + } + } + } + + return null; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + CheckTagWorldHeightRadiusProvider that = (CheckTagWorldHeightRadiusProvider)o; + return this.radius != that.radius ? false : Arrays.equals((Object[])this.blockTags, (Object[])that.blockTags); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = Arrays.hashCode((Object[])this.blockTags); + return 31 * result + this.radius; + } + + @Nonnull + @Override + public String toString() { + return "CheckTagWorldHeightRadiusProvider{blockTags=" + Arrays.toString((Object[])this.blockTags) + ", radius=" + this.radius + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/worldlocationproviders/LocationRadiusProvider.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/worldlocationproviders/LocationRadiusProvider.java new file mode 100644 index 0000000..435696e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/worldlocationproviders/LocationRadiusProvider.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.worldlocationproviders; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.universe.world.World; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class LocationRadiusProvider extends WorldLocationProvider { + public static final BuilderCodec CODEC = BuilderCodec.builder(LocationRadiusProvider.class, LocationRadiusProvider::new, BASE_CODEC) + .append( + new KeyedCodec<>("MinRadius", Codec.INTEGER), + (locationRadiusCondition, integer) -> locationRadiusCondition.minRadius = integer, + locationRadiusCondition -> locationRadiusCondition.minRadius + ) + .addValidator(Validators.greaterThan(0)) + .add() + .append( + new KeyedCodec<>("MaxRadius", Codec.INTEGER), + (locationRadiusCondition, integer) -> locationRadiusCondition.maxRadius = integer, + locationRadiusCondition -> locationRadiusCondition.maxRadius + ) + .addValidator(Validators.greaterThanOrEqual(1)) + .add() + .afterDecode( + locationRadiusCondition -> { + if (locationRadiusCondition.minRadius > locationRadiusCondition.maxRadius) { + throw new IllegalArgumentException( + "LocationRadiusCondition.MinRadius (" + + locationRadiusCondition.minRadius + + ") needs to be greater than LocationRadiusCondition.MaxRadius (" + + locationRadiusCondition.maxRadius + + ")" + ); + } + } + ) + .build(); + protected int minRadius = 10; + protected int maxRadius = 50; + + public LocationRadiusProvider() { + } + + @Nonnull + @Override + public Vector3i runCondition(@Nonnull World world, @Nonnull Vector3i position) { + double angle = Math.random() * (float) (Math.PI * 2); + int radius = MathUtil.randomInt(this.minRadius, this.maxRadius); + Vector3i newPosition = position.clone(); + newPosition.add((int)(radius * TrigMathUtil.cos(angle)), 0, (int)(radius * TrigMathUtil.sin(angle))); + newPosition.y = world.getChunk(ChunkUtil.indexChunkFromBlock(newPosition.x, newPosition.z)).getHeight(newPosition.x, newPosition.z); + return newPosition; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + LocationRadiusProvider that = (LocationRadiusProvider)o; + return this.minRadius != that.minRadius ? false : this.maxRadius == that.maxRadius; + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.minRadius; + return 31 * result + this.maxRadius; + } + + @Nonnull + @Override + public String toString() { + return "LocationRadiusProvider{minRadius=" + this.minRadius + ", maxRadius=" + this.maxRadius + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/worldlocationproviders/LookBlocksBelowProvider.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/worldlocationproviders/LookBlocksBelowProvider.java new file mode 100644 index 0000000..86dbe1a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/worldlocationproviders/LookBlocksBelowProvider.java @@ -0,0 +1,172 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.worldlocationproviders; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class LookBlocksBelowProvider extends WorldLocationProvider { + public static final BuilderCodec CODEC = BuilderCodec.builder( + LookBlocksBelowProvider.class, LookBlocksBelowProvider::new, BASE_CODEC + ) + .append( + new KeyedCodec<>("BlockTags", Codec.STRING_ARRAY), + (lookBlocksBelowCondition, strings) -> lookBlocksBelowCondition.blockTags = strings, + lookBlocksBelowCondition -> lookBlocksBelowCondition.blockTags + ) + .addValidator(Validators.nonEmptyArray()) + .addValidator(Validators.uniqueInArray()) + .add() + .append( + new KeyedCodec<>("Count", Codec.INTEGER), + (lookBlocksBelowCondition, integer) -> lookBlocksBelowCondition.count = integer, + lookBlocksBelowCondition -> lookBlocksBelowCondition.count + ) + .addValidator(Validators.greaterThan(0)) + .add() + .append( + new KeyedCodec<>("MinRange", Codec.INTEGER), + (lookBlocksBelowCondition, integer) -> lookBlocksBelowCondition.minRange = integer, + lookBlocksBelowCondition -> lookBlocksBelowCondition.minRange + ) + .addValidator(Validators.greaterThanOrEqual(0)) + .add() + .append( + new KeyedCodec<>("MaxRange", Codec.INTEGER), + (lookBlocksBelowCondition, integer) -> lookBlocksBelowCondition.maxRange = integer, + lookBlocksBelowCondition -> lookBlocksBelowCondition.maxRange + ) + .addValidator(Validators.greaterThanOrEqual(1)) + .add() + .afterDecode( + lookBlocksBelowCondition -> { + if (lookBlocksBelowCondition.blockTags != null) { + lookBlocksBelowCondition.blockTagsIndexes = new int[lookBlocksBelowCondition.blockTags.length]; + + for (int i = 0; i < lookBlocksBelowCondition.blockTags.length; i++) { + String blockTag = lookBlocksBelowCondition.blockTags[i]; + lookBlocksBelowCondition.blockTagsIndexes[i] = AssetRegistry.getOrCreateTagIndex(blockTag); + } + + if (lookBlocksBelowCondition.minRange > lookBlocksBelowCondition.maxRange) { + throw new IllegalArgumentException( + "LookBlocksBelowCondition.MinRange (" + + lookBlocksBelowCondition.minRange + + ") needs to be greater than LookBlocksBelowCondition.MaxRange (" + + lookBlocksBelowCondition.maxRange + + ")" + ); + } + } + } + ) + .build(); + protected String[] blockTags; + protected int count = 1; + protected int minRange = 0; + protected int maxRange = 10; + private int[] blockTagsIndexes; + + public LookBlocksBelowProvider(@Nonnull String[] blockTags, int count, int minRange, int maxRange) { + this.blockTags = blockTags; + this.count = count; + this.minRange = minRange; + this.maxRange = maxRange; + this.blockTagsIndexes = new int[blockTags.length]; + + for (int i = 0; i < blockTags.length; i++) { + String blockTag = blockTags[i]; + this.blockTagsIndexes[i] = AssetRegistry.getOrCreateTagIndex(blockTag); + } + } + + protected LookBlocksBelowProvider() { + } + + @Nullable + @Override + public Vector3i runCondition(@Nonnull World world, @Nonnull Vector3i position) { + Vector3i newPosition = position.clone(); + WorldChunk worldChunk = world.getChunk(ChunkUtil.indexChunkFromBlock(newPosition.x, newPosition.z)); + int baseY = newPosition.y; + int x = newPosition.x; + int y = newPosition.y; + int z = newPosition.z; + int currentCount = 0; + + while (y >= this.minRange && baseY - y <= this.maxRange) { + String blockStateKey = worldChunk.getBlockType(x, y, z).getId(); + boolean found = false; + + for (int i = 0; i < this.blockTagsIndexes.length; i++) { + int blockTagId = this.blockTagsIndexes[i]; + if (BlockType.getAssetMap().getKeysForTag(blockTagId).contains(blockStateKey)) { + found = true; + currentCount++; + break; + } + } + + if (currentCount == this.count) { + break; + } + + y--; + if (!found) { + currentCount = 0; + } + } + + return currentCount == this.count ? new Vector3i(x, y, z) : null; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + LookBlocksBelowProvider that = (LookBlocksBelowProvider)o; + if (this.count != that.count) { + return false; + } else if (this.minRange != that.minRange) { + return false; + } else { + return this.maxRange != that.maxRange ? false : Arrays.equals((Object[])this.blockTags, (Object[])that.blockTags); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = Arrays.hashCode((Object[])this.blockTags); + result = 31 * result + this.count; + result = 31 * result + this.minRange; + return 31 * result + this.maxRange; + } + + @Nonnull + @Override + public String toString() { + return "LookBlocksBelowProvider{blockTags=" + + Arrays.toString((Object[])this.blockTags) + + ", count=" + + this.count + + ", minRange=" + + this.minRange + + ", maxRange=" + + this.maxRange + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/config/worldlocationproviders/WorldLocationProvider.java b/src/com/hypixel/hytale/builtin/adventure/objectives/config/worldlocationproviders/WorldLocationProvider.java new file mode 100644 index 0000000..11fc8d7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/config/worldlocationproviders/WorldLocationProvider.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.adventure.objectives.config.worldlocationproviders; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.universe.world.World; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class WorldLocationProvider { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(WorldLocationProvider.class).build(); + + public WorldLocationProvider() { + } + + @Nullable + public abstract Vector3i runCondition(World var1, Vector3i var2); + + @Override + public abstract boolean equals(Object var1); + + @Override + public abstract int hashCode(); + + @Nonnull + @Override + public String toString() { + return "WorldLocationProvider{}"; + } + + static { + CODEC.register("LookBlocksBelow", LookBlocksBelowProvider.class, LookBlocksBelowProvider.CODEC); + CODEC.register("LocationRadius", LocationRadiusProvider.class, LocationRadiusProvider.CODEC); + CODEC.register("TagBlockHeight", CheckTagWorldHeightRadiusProvider.class, CheckTagWorldHeightRadiusProvider.CODEC); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/events/TreasureChestOpeningEvent.java b/src/com/hypixel/hytale/builtin/adventure/objectives/events/TreasureChestOpeningEvent.java new file mode 100644 index 0000000..407ac8c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/events/TreasureChestOpeningEvent.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.adventure.objectives.events; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class TreasureChestOpeningEvent implements IEvent { + @Nonnull + private final UUID objectiveUUID; + @Nonnull + private final UUID chestUUID; + @Nonnull + private final Ref playerRef; + @Nonnull + private final Store store; + + public TreasureChestOpeningEvent( + @Nonnull UUID objectiveUUID, @Nonnull UUID chestUUID, @Nonnull Ref playerRef, @Nonnull Store store + ) { + this.objectiveUUID = objectiveUUID; + this.chestUUID = chestUUID; + this.playerRef = playerRef; + this.store = store; + } + + @Nonnull + public UUID getObjectiveUUID() { + return this.objectiveUUID; + } + + @Nonnull + public UUID getChestUUID() { + return this.chestUUID; + } + + @Nonnull + public Ref getPlayerRef() { + return this.playerRef; + } + + @Nonnull + public Store getStore() { + return this.store; + } + + @Nonnull + @Override + public String toString() { + return "TreasureChestOpeningEvent{objectiveUUID=" + this.objectiveUUID + ", chestUUID=" + this.chestUUID + ", player=" + this.playerRef + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/historydata/CommonObjectiveHistoryData.java b/src/com/hypixel/hytale/builtin/adventure/objectives/historydata/CommonObjectiveHistoryData.java new file mode 100644 index 0000000..82aacd0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/historydata/CommonObjectiveHistoryData.java @@ -0,0 +1,87 @@ +package com.hypixel.hytale.builtin.adventure.objectives.historydata; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import java.time.Instant; +import javax.annotation.Nonnull; + +public abstract class CommonObjectiveHistoryData { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(CommonObjectiveHistoryData.class) + .append( + new KeyedCodec<>("Id", Codec.STRING), + (commonObjectiveHistoryData, s) -> commonObjectiveHistoryData.id = s, + commonObjectiveHistoryData -> commonObjectiveHistoryData.id + ) + .add() + .append( + new KeyedCodec<>("TimesCompleted", Codec.INTEGER), + (commonObjectiveHistoryData, integer) -> commonObjectiveHistoryData.timesCompleted = integer, + commonObjectiveHistoryData -> commonObjectiveHistoryData.timesCompleted + ) + .add() + .append( + new KeyedCodec<>("LastCompletionTimestamp", Codec.LONG), + (o, i) -> o.lastCompletionTimestamp = Instant.ofEpochMilli(i), + o -> o.lastCompletionTimestamp == null ? null : o.lastCompletionTimestamp.toEpochMilli() + ) + .add() + .append( + new KeyedCodec<>("Category", Codec.STRING), + (commonObjectiveHistoryData, s) -> commonObjectiveHistoryData.category = s, + commonObjectiveHistoryData -> commonObjectiveHistoryData.category + ) + .add() + .build(); + protected String id; + protected int timesCompleted; + protected Instant lastCompletionTimestamp; + protected String category; + + public CommonObjectiveHistoryData(String id, String category) { + this.id = id; + this.timesCompleted = 1; + this.lastCompletionTimestamp = Instant.now(); + this.category = category; + } + + protected CommonObjectiveHistoryData() { + } + + public String getId() { + return this.id; + } + + public int getTimesCompleted() { + return this.timesCompleted; + } + + public Instant getLastCompletionTimestamp() { + return this.lastCompletionTimestamp; + } + + public String getCategory() { + return this.category; + } + + protected void completed() { + this.timesCompleted++; + this.lastCompletionTimestamp = Instant.now(); + } + + @Nonnull + @Override + public String toString() { + return "CommonObjectiveHistoryData{id='" + + this.id + + "', timesCompleted=" + + this.timesCompleted + + ", lastCompletionTimestamp=" + + this.lastCompletionTimestamp + + ", category='" + + this.category + + "'}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/historydata/ItemObjectiveRewardHistoryData.java b/src/com/hypixel/hytale/builtin/adventure/objectives/historydata/ItemObjectiveRewardHistoryData.java new file mode 100644 index 0000000..1aca9d1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/historydata/ItemObjectiveRewardHistoryData.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.adventure.objectives.historydata; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import javax.annotation.Nonnull; + +public final class ItemObjectiveRewardHistoryData extends ObjectiveRewardHistoryData { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ItemObjectiveRewardHistoryData.class, ItemObjectiveRewardHistoryData::new, BASE_CODEC + ) + .append( + new KeyedCodec<>("ItemId", Codec.STRING), + (itemObjectiveRewardDetails, blockTypeKey) -> itemObjectiveRewardDetails.itemId = blockTypeKey, + itemObjectiveRewardDetails -> itemObjectiveRewardDetails.itemId + ) + .addValidator(Item.VALIDATOR_CACHE.getValidator()) + .add() + .append( + new KeyedCodec<>("Quantity", Codec.INTEGER), + (itemObjectiveRewardDetails, integer) -> itemObjectiveRewardDetails.quantity = integer, + itemObjectiveRewardDetails -> itemObjectiveRewardDetails.quantity + ) + .add() + .build(); + protected String itemId; + protected int quantity; + + public ItemObjectiveRewardHistoryData(String itemId, int quantity) { + this.itemId = itemId; + this.quantity = quantity; + } + + protected ItemObjectiveRewardHistoryData() { + } + + public String getItemId() { + return this.itemId; + } + + public int getQuantity() { + return this.quantity; + } + + @Nonnull + @Override + public String toString() { + return "ItemObjectiveRewardHistoryData{itemId=" + this.itemId + ", quantity=" + this.quantity + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/historydata/ObjectiveHistoryData.java b/src/com/hypixel/hytale/builtin/adventure/objectives/historydata/ObjectiveHistoryData.java new file mode 100644 index 0000000..f59fb7d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/historydata/ObjectiveHistoryData.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.builtin.adventure.objectives.historydata; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; + +public final class ObjectiveHistoryData extends CommonObjectiveHistoryData { + public static final BuilderCodec CODEC = BuilderCodec.builder(ObjectiveHistoryData.class, ObjectiveHistoryData::new, BASE_CODEC) + .append( + new KeyedCodec<>("Rewards", new ArrayCodec<>(ObjectiveRewardHistoryData.CODEC, ObjectiveRewardHistoryData[]::new)), + (objectiveDetails, objectiveRewardHistoryData) -> objectiveDetails.rewards = objectiveRewardHistoryData, + objectiveDetails -> objectiveDetails.rewards + ) + .add() + .build(); + @Nonnull + protected Map> rewardsPerPlayer = new ConcurrentHashMap<>(); + protected ObjectiveRewardHistoryData[] rewards; + + public ObjectiveHistoryData(String id, String category) { + super(id, category); + } + + public ObjectiveHistoryData(String id, String category, ObjectiveRewardHistoryData[] rewards) { + super(id, category); + this.rewards = rewards; + } + + protected ObjectiveHistoryData() { + } + + public ObjectiveRewardHistoryData[] getRewards() { + return this.rewards; + } + + public void addRewardForPlayerUUID(UUID playerUUID, ObjectiveRewardHistoryData objectiveRewardHistoryData) { + this.rewardsPerPlayer.computeIfAbsent(playerUUID, k -> new ObjectArrayList<>()).add(objectiveRewardHistoryData); + } + + @Nonnull + public ObjectiveHistoryData cloneForPlayer(UUID playerUUID) { + List playerRewards = this.rewardsPerPlayer.get(playerUUID); + return playerRewards == null + ? new ObjectiveHistoryData(this.id, this.category) + : new ObjectiveHistoryData(this.id, this.category, playerRewards.toArray(ObjectiveRewardHistoryData[]::new)); + } + + public void completed(UUID playerUUID, @Nonnull ObjectiveHistoryData objectiveHistoryData) { + this.completed(); + List lastRewards = objectiveHistoryData.rewardsPerPlayer.get(playerUUID); + if (lastRewards != null) { + this.rewards = lastRewards.toArray(ObjectiveRewardHistoryData[]::new); + } + } + + @Nonnull + @Override + public String toString() { + return "ObjectiveHistoryData{rewardsPerPlayer=" + + this.rewardsPerPlayer + + ", rewards=" + + Arrays.toString((Object[])this.rewards) + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/historydata/ObjectiveLineHistoryData.java b/src/com/hypixel/hytale/builtin/adventure/objectives/historydata/ObjectiveLineHistoryData.java new file mode 100644 index 0000000..fb01a32 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/historydata/ObjectiveLineHistoryData.java @@ -0,0 +1,92 @@ +package com.hypixel.hytale.builtin.adventure.objectives.historydata; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; + +public final class ObjectiveLineHistoryData extends CommonObjectiveHistoryData { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ObjectiveLineHistoryData.class, ObjectiveLineHistoryData::new, BASE_CODEC + ) + .append( + new KeyedCodec<>("Objectives", new ArrayCodec<>(ObjectiveHistoryData.CODEC, ObjectiveHistoryData[]::new)), + (objectiveLineDetails, objectiveDetails) -> objectiveLineDetails.objectiveHistoryDataArray = objectiveDetails, + objectiveLineDetails -> objectiveLineDetails.objectiveHistoryDataArray + ) + .add() + .build(); + private ObjectiveHistoryData[] objectiveHistoryDataArray; + private String[] nextObjectiveLineIds; + + public ObjectiveLineHistoryData(String id, String category, String[] nextObjectiveLineIds) { + super(id, category); + this.nextObjectiveLineIds = nextObjectiveLineIds; + } + + private ObjectiveLineHistoryData() { + } + + public ObjectiveHistoryData[] getObjectiveHistoryDataArray() { + return this.objectiveHistoryDataArray; + } + + public String[] getNextObjectiveLineIds() { + return this.nextObjectiveLineIds; + } + + public void addObjectiveHistoryData(@Nonnull ObjectiveHistoryData objectiveHistoryData) { + this.objectiveHistoryDataArray = ArrayUtil.append(this.objectiveHistoryDataArray, objectiveHistoryData); + } + + @Nonnull + public Map cloneForPlayers(@Nonnull Set playerUUIDs) { + Map objectiveLineDataPerPlayer = new Object2ObjectOpenHashMap<>(); + + for (ObjectiveHistoryData objectiveHistoryData : this.objectiveHistoryDataArray) { + for (UUID playerUUID : playerUUIDs) { + objectiveLineDataPerPlayer.computeIfAbsent(playerUUID, k -> new ObjectiveLineHistoryData()) + .addObjectiveHistoryData(objectiveHistoryData.cloneForPlayer(playerUUID)); + } + } + + return objectiveLineDataPerPlayer; + } + + public void completed(UUID playerUUID, @Nonnull ObjectiveLineHistoryData objectiveLineHistoryData) { + this.completed(); + + for (ObjectiveHistoryData latestObjectiveHistoryData : objectiveLineHistoryData.objectiveHistoryDataArray) { + boolean updated = false; + + for (ObjectiveHistoryData savedObjectiveHistoryData : this.objectiveHistoryDataArray) { + if (savedObjectiveHistoryData.id.equals(latestObjectiveHistoryData.id)) { + savedObjectiveHistoryData.completed(playerUUID, latestObjectiveHistoryData); + updated = true; + break; + } + } + + if (!updated) { + this.addObjectiveHistoryData(latestObjectiveHistoryData); + } + } + } + + @Nonnull + @Override + public String toString() { + return "ObjectiveLineHistoryData{objectiveHistoryDataArray=" + + Arrays.toString((Object[])this.objectiveHistoryDataArray) + + ", nextObjectiveLineIds=" + + Arrays.toString((Object[])this.nextObjectiveLineIds) + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/historydata/ObjectiveRewardHistoryData.java b/src/com/hypixel/hytale/builtin/adventure/objectives/historydata/ObjectiveRewardHistoryData.java new file mode 100644 index 0000000..333ded0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/historydata/ObjectiveRewardHistoryData.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.builtin.adventure.objectives.historydata; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; + +public abstract class ObjectiveRewardHistoryData { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(ObjectiveRewardHistoryData.class).build(); + + public ObjectiveRewardHistoryData() { + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/interactions/CanBreakRespawnPointInteraction.java b/src/com/hypixel/hytale/builtin/adventure/objectives/interactions/CanBreakRespawnPointInteraction.java new file mode 100644 index 0000000..216df0a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/interactions/CanBreakRespawnPointInteraction.java @@ -0,0 +1,89 @@ +package com.hypixel.hytale.builtin.adventure.objectives.interactions; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.meta.state.RespawnBlock; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CanBreakRespawnPointInteraction extends SimpleBlockInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CanBreakRespawnPointInteraction.class, CanBreakRespawnPointInteraction::new, SimpleBlockInteraction.CODEC + ) + .build(); + + public CanBreakRespawnPointInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + ChunkStore chunkStore = world.getChunkStore(); + Ref chunk = chunkStore.getChunkReference(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)); + if (chunk == null) { + context.getState().state = InteractionState.Failed; + } else { + BlockComponentChunk blockComp = chunkStore.getStore().getComponent(chunk, BlockComponentChunk.getComponentType()); + if (blockComp == null) { + context.getState().state = InteractionState.Failed; + } else { + Ref blockEntity = blockComp.getEntityReference(ChunkUtil.indexBlockInColumn(targetBlock.x, targetBlock.y, targetBlock.z)); + if (blockEntity == null) { + context.getState().state = InteractionState.Finished; + } else { + RespawnBlock respawnState = chunkStore.getStore().getComponent(blockEntity, RespawnBlock.getComponentType()); + if (respawnState == null) { + context.getState().state = InteractionState.Finished; + } else { + UUIDComponent uuidComponent = commandBuffer.getComponent(context.getOwningEntity(), UUIDComponent.getComponentType()); + if (uuidComponent == null) { + context.getState().state = InteractionState.Failed; + } else { + UUID ownerUUID = respawnState.getOwnerUUID(); + if (ownerUUID != null && !uuidComponent.getUuid().equals(ownerUUID)) { + context.getState().state = InteractionState.Failed; + } else { + context.getState().state = InteractionState.Finished; + } + } + } + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/interactions/StartObjectiveInteraction.java b/src/com/hypixel/hytale/builtin/adventure/objectives/interactions/StartObjectiveInteraction.java new file mode 100644 index 0000000..d513d3d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/interactions/StartObjectiveInteraction.java @@ -0,0 +1,91 @@ +package com.hypixel.hytale.builtin.adventure.objectives.interactions; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.config.objectivesetup.ObjectiveTypeSetup; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; + +public class StartObjectiveInteraction extends SimpleInstantInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + StartObjectiveInteraction.class, StartObjectiveInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Starts the given objective or adds the player to an existing one.") + .appendInherited( + new KeyedCodec<>("Setup", ObjectiveTypeSetup.CODEC), + (startObjectiveInteraction, objectiveTypeSetup) -> startObjectiveInteraction.objectiveTypeSetup = objectiveTypeSetup, + startObjectiveInteraction -> startObjectiveInteraction.objectiveTypeSetup, + (startObjectiveInteraction, parent) -> startObjectiveInteraction.objectiveTypeSetup = parent.objectiveTypeSetup + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + public static final KeyedCodec OBJECTIVE_UUID = new KeyedCodec<>("ObjectiveUUID", Codec.UUID_BINARY); + protected ObjectiveTypeSetup objectiveTypeSetup; + + public StartObjectiveInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + Ref ref = context.getEntity(); + PlayerRef playerRefComponent = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); + if (playerRefComponent != null) { + ItemStack itemStack = context.getHeldItem(); + Store store = commandBuffer.getStore(); + UUID objectiveUUID = itemStack.getFromMetadataOrNull(OBJECTIVE_UUID); + if (objectiveUUID == null) { + this.startObjective(playerRefComponent, context, itemStack, store); + } else { + ObjectivePlugin.get().addPlayerToExistingObjective(store, playerRefComponent.getUuid(), objectiveUUID); + } + } + } + + private void startObjective(@Nonnull PlayerRef player, @Nonnull InteractionContext context, @Nonnull ItemStack itemStack, @Nonnull Store store) { + BsonDocument itemStackMetadata = itemStack.getMetadata(); + if (itemStackMetadata == null) { + itemStackMetadata = new BsonDocument(); + } + + World world = store.getExternalData().getWorld(); + Objective objective = this.objectiveTypeSetup.setup(Set.of(player.getUuid()), world.getWorldConfig().getUuid(), null, store); + if (objective == null) { + ObjectivePlugin.get() + .getLogger() + .at(Level.WARNING) + .log("Failed to start objective '%s' from item: %s", this.objectiveTypeSetup.getObjectiveIdToStart(), itemStack); + } else { + OBJECTIVE_UUID.put(itemStackMetadata, objective.getObjectiveUUID()); + ItemStack clonedItemStack = itemStack.withMetadata(itemStackMetadata); + objective.setObjectiveItemStarter(clonedItemStack); + context.setHeldItem(clonedItemStack); + context.getHeldItemContainer().replaceItemStackInSlot(context.getHeldItemSlot(), itemStack, clonedItemStack); + } + } + + @Nonnull + @Override + public String toString() { + return "StartObjectiveInteraction{objectiveTypeSetup=" + this.objectiveTypeSetup + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/markers/ObjectiveMarkerProvider.java b/src/com/hypixel/hytale/builtin/adventure/objectives/markers/ObjectiveMarkerProvider.java new file mode 100644 index 0000000..440fac2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/markers/ObjectiveMarkerProvider.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.builtin.adventure.objectives.markers; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectiveDataStore; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.task.ObjectiveTask; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class ObjectiveMarkerProvider implements WorldMapManager.MarkerProvider { + public static final ObjectiveMarkerProvider INSTANCE = new ObjectiveMarkerProvider(); + + private ObjectiveMarkerProvider() { + } + + @Override + public void update( + @Nonnull World world, @Nonnull GameplayConfig gameplayConfig, @Nonnull WorldMapTracker tracker, int chunkViewRadius, int playerChunkX, int playerChunkZ + ) { + Player player = tracker.getPlayer(); + Set activeObjectiveUUIDs = player.getPlayerConfigData().getActiveObjectiveUUIDs(); + if (!activeObjectiveUUIDs.isEmpty()) { + UUID playerUUID = player.getUuid(); + ObjectiveDataStore objectiveDataStore = ObjectivePlugin.get().getObjectiveDataStore(); + + for (UUID objectiveUUID : activeObjectiveUUIDs) { + Objective objective = objectiveDataStore.getObjective(objectiveUUID); + if (objective != null && objective.getActivePlayerUUIDs().contains(playerUUID)) { + ObjectiveTask[] tasks = objective.getCurrentTasks(); + if (tasks != null) { + for (ObjectiveTask task : tasks) { + for (MapMarker marker : task.getMarkers()) { + tracker.trySendMarker(chunkViewRadius, playerChunkX, playerChunkZ, marker); + } + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/markers/objectivelocation/ObjectiveLocationMarker.java b/src/com/hypixel/hytale/builtin/adventure/objectives/markers/objectivelocation/ObjectiveLocationMarker.java new file mode 100644 index 0000000..f040318 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/markers/objectivelocation/ObjectiveLocationMarker.java @@ -0,0 +1,115 @@ +package com.hypixel.hytale.builtin.adventure.objectives.markers.objectivelocation; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveLocationMarkerAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.markerarea.ObjectiveLocationMarkerArea; +import com.hypixel.hytale.builtin.adventure.objectives.config.triggercondition.ObjectiveLocationTriggerCondition; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.assets.UntrackObjective; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ObjectiveLocationMarker implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(ObjectiveLocationMarker.class, ObjectiveLocationMarker::new) + .append( + new KeyedCodec<>("ObjectiveLocationMarkerId", Codec.STRING), + (objectiveLocationMarkerEntity, s) -> objectiveLocationMarkerEntity.objectiveLocationMarkerId = s, + objectiveLocationMarkerEntity -> objectiveLocationMarkerEntity.objectiveLocationMarkerId + ) + .addValidator(Validators.nonNull()) + .addValidator(ObjectiveLocationMarkerAsset.VALIDATOR_CACHE.getValidator()) + .add() + .append( + new KeyedCodec<>("ActiveObjectiveUUID", Codec.UUID_BINARY), + (objectiveLocationMarkerEntity, uuid) -> objectiveLocationMarkerEntity.activeObjectiveUUID = uuid, + objectiveLocationMarkerEntity -> objectiveLocationMarkerEntity.activeObjectiveUUID + ) + .add() + .build(); + protected String objectiveLocationMarkerId; + protected UUID activeObjectiveUUID; + protected ObjectiveLocationMarkerArea area; + protected int[] environmentIndexes; + protected ObjectiveLocationTriggerCondition[] triggerConditions; + @Nullable + private Objective activeObjective; + private UntrackObjective untrackPacket; + + public static ComponentType getComponentType() { + return ObjectivePlugin.get().getObjectiveLocationMarkerComponentType(); + } + + public ObjectiveLocationMarker() { + } + + public ObjectiveLocationMarker(String objectiveLocationMarkerId) { + this.objectiveLocationMarkerId = objectiveLocationMarkerId; + } + + public void setObjectiveLocationMarkerId(String objectiveLocationMarkerId) { + this.objectiveLocationMarkerId = objectiveLocationMarkerId; + } + + public void setActiveObjectiveUUID(UUID activeObjectiveUUID) { + this.activeObjectiveUUID = activeObjectiveUUID; + } + + @Nullable + public Objective getActiveObjective() { + return this.activeObjective; + } + + public void setActiveObjective(Objective activeObjective) { + this.activeObjective = activeObjective; + } + + public String getObjectiveLocationMarkerId() { + return this.objectiveLocationMarkerId; + } + + public UntrackObjective getUntrackPacket() { + return this.untrackPacket; + } + + public void setUntrackPacket(@Nonnull UntrackObjective untrackPacket) { + this.untrackPacket = untrackPacket; + } + + public ObjectiveLocationMarkerArea getArea() { + return this.area; + } + + public void updateLocationMarkerValues(@Nonnull ObjectiveLocationMarkerAsset objectiveLocationMarkerAsset, float yaw, @Nonnull Store store) { + if (this.activeObjective != null + && !this.activeObjective.getObjectiveId().equals(objectiveLocationMarkerAsset.getObjectiveTypeSetup().getObjectiveIdToStart())) { + ObjectivePlugin.get().cancelObjective(this.activeObjectiveUUID, store); + this.activeObjective = null; + } + + this.environmentIndexes = objectiveLocationMarkerAsset.getEnvironmentIndexes(); + this.area = objectiveLocationMarkerAsset.getArea().getRotatedArea(yaw, 0.0F); + this.triggerConditions = objectiveLocationMarkerAsset.getTriggerConditions(); + } + + @Nonnull + @Override + public Component clone() { + ObjectiveLocationMarker marker = new ObjectiveLocationMarker(this.objectiveLocationMarkerId); + marker.activeObjectiveUUID = this.activeObjectiveUUID; + marker.area = this.area; + marker.environmentIndexes = this.environmentIndexes; + marker.triggerConditions = this.triggerConditions; + marker.activeObjective = this.activeObjective; + marker.untrackPacket = this.untrackPacket; + return marker; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/markers/objectivelocation/ObjectiveLocationMarkerSystems.java b/src/com/hypixel/hytale/builtin/adventure/objectives/markers/objectivelocation/ObjectiveLocationMarkerSystems.java new file mode 100644 index 0000000..227282d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/markers/objectivelocation/ObjectiveLocationMarkerSystems.java @@ -0,0 +1,472 @@ +package com.hypixel.hytale.builtin.adventure.objectives.markers.objectivelocation; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectiveDataStore; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveLocationMarkerAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.triggercondition.ObjectiveLocationTriggerCondition; +import com.hypixel.hytale.builtin.adventure.objectives.task.ObjectiveTask; +import com.hypixel.hytale.builtin.adventure.objectives.task.UseEntityObjectiveTask; +import com.hypixel.hytale.builtin.weather.components.WeatherTracker; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.OrderPriority; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.packets.assets.TrackOrUpdateObjective; +import com.hypixel.hytale.protocol.packets.assets.UntrackObjective; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.system.PlayerSpatialSystem; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.prefab.PrefabCopyableComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ObjectiveLocationMarkerSystems { + public ObjectiveLocationMarkerSystems() { + } + + public static class EnsureNetworkSendableSystem extends HolderSystem { + @Nonnull + private final Query query = Query.and(ObjectiveLocationMarker.getComponentType(), Query.not(NetworkId.getComponentType())); + + public EnsureNetworkSendableSystem() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.addComponent(NetworkId.getComponentType(), new NetworkId(store.getExternalData().takeNextNetworkId())); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + } + + public static class InitSystem extends RefSystem { + @Nonnull + private final ComponentType objectiveLocationMarkerComponent; + @Nonnull + private final ComponentType modelComponentType; + @Nonnull + private final ComponentType transformComponentType; + @Nonnull + private final Query query; + + public InitSystem(@Nonnull ComponentType objectiveLocationMarkerComponent) { + this.objectiveLocationMarkerComponent = objectiveLocationMarkerComponent; + this.modelComponentType = ModelComponent.getComponentType(); + this.transformComponentType = TransformComponent.getComponentType(); + this.query = Query.and(objectiveLocationMarkerComponent, this.modelComponentType, this.transformComponentType); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + ObjectiveLocationMarker objectiveLocationMarkerComponent = store.getComponent(ref, this.objectiveLocationMarkerComponent); + + assert objectiveLocationMarkerComponent != null; + + ObjectiveLocationMarkerAsset markerAsset = ObjectiveLocationMarkerAsset.getAssetMap() + .getAsset(objectiveLocationMarkerComponent.objectiveLocationMarkerId); + if (markerAsset == null) { + ObjectivePlugin.get() + .getLogger() + .at(Level.WARNING) + .log("Failed to find ObjectiveLocationMarker '%s'. Entity removed!", objectiveLocationMarkerComponent.objectiveLocationMarkerId); + commandBuffer.removeEntity(ref, RemoveReason.REMOVE); + } else { + if (objectiveLocationMarkerComponent.activeObjectiveUUID != null) { + Objective activeObjective = ObjectivePlugin.get() + .getObjectiveDataStore() + .loadObjective(objectiveLocationMarkerComponent.activeObjectiveUUID, store); + if (activeObjective == null) { + ObjectivePlugin.get() + .getLogger() + .at(Level.WARNING) + .log("Failed to load Objective with UUID '%s'. Entity removed!", objectiveLocationMarkerComponent.activeObjectiveUUID); + commandBuffer.removeEntity(ref, RemoveReason.REMOVE); + return; + } + + objectiveLocationMarkerComponent.setActiveObjective(activeObjective); + objectiveLocationMarkerComponent.setUntrackPacket(new UntrackObjective(objectiveLocationMarkerComponent.activeObjectiveUUID)); + } + + TransformComponent transformComponent = store.getComponent(ref, this.transformComponentType); + + assert transformComponent != null; + + Vector3f rotation = transformComponent.getRotation(); + objectiveLocationMarkerComponent.updateLocationMarkerValues(markerAsset, rotation.getYaw(), store); + ModelComponent modelComponent = store.getComponent(ref, this.modelComponentType); + + assert modelComponent != null; + + Model model = modelComponent.getModel(); + commandBuffer.putComponent( + ref, + this.modelComponentType, + new ModelComponent( + new Model( + model.getModelAssetId(), + model.getScale(), + model.getRandomAttachmentIds(), + model.getAttachments(), + objectiveLocationMarkerComponent.getArea().getBoxForEntryArea(), + model.getModel(), + model.getTexture(), + model.getGradientSet(), + model.getGradientId(), + model.getEyeHeight(), + model.getCrouchOffset(), + model.getAnimationSetMap(), + model.getCamera(), + model.getLight(), + model.getParticles(), + model.getTrails(), + model.getPhysicsValues(), + model.getDetailBoxes(), + model.getPhobia(), + model.getPhobiaModelAssetId() + ) + ) + ); + commandBuffer.ensureComponent(ref, PrefabCopyableComponent.getComponentType()); + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + ObjectiveLocationMarker objectLocationMarkerComponent = store.getComponent(ref, this.objectiveLocationMarkerComponent); + + assert objectLocationMarkerComponent != null; + + Objective activeObjective = objectLocationMarkerComponent.getActiveObjective(); + if (activeObjective != null) { + ObjectiveDataStore objectiveDataStore = ObjectivePlugin.get().getObjectiveDataStore(); + objectiveDataStore.saveToDisk(objectLocationMarkerComponent.activeObjectiveUUID.toString(), activeObjective); + objectiveDataStore.unloadObjective(objectLocationMarkerComponent.activeObjectiveUUID); + if (reason == RemoveReason.REMOVE) { + commandBuffer.run(theStore -> ObjectivePlugin.get().cancelObjective(objectLocationMarkerComponent.activeObjectiveUUID, theStore)); + } + } + } + } + + public static class TickingSystem extends EntityTickingSystem { + @Nonnull + private final ComponentType objectiveLocationMarkerComponentType; + @Nonnull + private final ComponentType playerRefComponentType; + @Nonnull + private final ComponentType transformComponentType = TransformComponent.getComponentType(); + @Nonnull + private final ComponentType weatherTrackerComponentType = WeatherTracker.getComponentType(); + @Nonnull + private final ComponentType uuidComponentType = UUIDComponent.getComponentType(); + @Nonnull + private final ResourceType, EntityStore>> playerSpatialComponent; + @Nonnull + private final Query query; + @Nonnull + private final Set> dependencies; + + public TickingSystem( + @Nonnull ComponentType objectiveLocationMarkerComponentType, + @Nonnull ComponentType playerRefComponentType, + @Nonnull ResourceType, EntityStore>> playerSpatialComponent + ) { + this.objectiveLocationMarkerComponentType = objectiveLocationMarkerComponentType; + this.playerRefComponentType = playerRefComponentType; + this.playerSpatialComponent = playerSpatialComponent; + this.query = Archetype.of(objectiveLocationMarkerComponentType, this.transformComponentType, this.uuidComponentType); + this.dependencies = Set.of(new SystemDependency<>(Order.AFTER, PlayerSpatialSystem.class, OrderPriority.CLOSEST)); + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return false; + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + World world = store.getExternalData().getWorld(); + if (world.getWorldConfig().isObjectiveMarkersEnabled()) { + store.tick(this, dt, systemIndex); + } + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + ObjectiveLocationMarker objectiveLocationMarkerComponent = archetypeChunk.getComponent(index, this.objectiveLocationMarkerComponentType); + + assert objectiveLocationMarkerComponent != null; + + TransformComponent transformComponent = archetypeChunk.getComponent(index, this.transformComponentType); + + assert transformComponent != null; + + Ref entityReference = archetypeChunk.getReferenceTo(index); + Vector3d position = transformComponent.getPosition(); + Objective activeObjective = objectiveLocationMarkerComponent.getActiveObjective(); + if (activeObjective == null) { + UUIDComponent uuidComponent = archetypeChunk.getComponent(index, this.uuidComponentType); + + assert uuidComponent != null; + + UUID uuid = uuidComponent.getUuid(); + this.setupMarker(store, objectiveLocationMarkerComponent, entityReference, position, uuid, commandBuffer); + } else if (!activeObjective.isCompleted()) { + SpatialResource, EntityStore> spatialResource = store.getResource(this.playerSpatialComponent); + ObjectList> playerReferences = SpatialResource.getThreadLocalReferenceList(); + objectiveLocationMarkerComponent.area.getPlayersInExitArea(spatialResource, playerReferences, position); + HashSet playersInExitArea = new HashSet<>(playerReferences.size()); + PlayerRef[] playersInEntryArea = new PlayerRef[playerReferences.size()]; + int playersInEntryAreaSize = 0; + + for (Ref playerReference : playerReferences) { + PlayerRef playerRefComponent = commandBuffer.getComponent(playerReference, this.playerRefComponentType); + + assert playerRefComponent != null; + + UUIDComponent playerUuidComponent = commandBuffer.getComponent(playerReference, this.uuidComponentType); + + assert playerUuidComponent != null; + + TransformComponent playerTransformComponent = commandBuffer.getComponent(playerReference, this.transformComponentType); + + assert playerTransformComponent != null; + + WeatherTracker playerWeatherTrackerComponent = commandBuffer.getComponent(playerReference, this.weatherTrackerComponentType); + + assert playerWeatherTrackerComponent != null; + + if (isPlayerInSpecificEnvironment(objectiveLocationMarkerComponent, playerWeatherTrackerComponent, playerTransformComponent, commandBuffer)) { + playersInExitArea.add(playerUuidComponent.getUuid()); + if (objectiveLocationMarkerComponent.area.isPlayerInEntryArea(playerTransformComponent.getPosition(), position)) { + playersInEntryArea[playersInEntryAreaSize++] = playerRefComponent; + } + } + } + + Set playerUUIDs = activeObjective.getPlayerUUIDs(); + Set activePlayerUUIDs = activeObjective.getActivePlayerUUIDs(); + String objectiveId = activeObjective.getObjectiveId(); + updateIncomingPlayers(playersInEntryArea, playersInEntryAreaSize, objectiveLocationMarkerComponent, playerUUIDs, activePlayerUUIDs, objectiveId); + updateOutgoingPlayers(playersInExitArea, objectiveLocationMarkerComponent, activePlayerUUIDs, objectiveId); + } else { + commandBuffer.removeEntity(entityReference, RemoveReason.REMOVE); + } + } + + private void setupMarker( + @Nonnull Store store, + @Nonnull ObjectiveLocationMarker entity, + @Nonnull Ref entityReference, + @Nonnull Vector3d position, + @Nonnull UUID uuid, + @Nonnull CommandBuffer commandBuffer + ) { + if (entity.triggerConditions != null && entity.triggerConditions.length > 0) { + SpatialResource, EntityStore> spatialResource = store.getResource(this.playerSpatialComponent); + if (!entity.area.hasPlayerInExitArea(spatialResource, this.playerRefComponentType, position, commandBuffer)) { + return; + } + + for (ObjectiveLocationTriggerCondition triggerCondition : entity.triggerConditions) { + if (!triggerCondition.isConditionMet(commandBuffer, entityReference, entity)) { + return; + } + } + } + + ObjectiveLocationMarkerAsset objectiveLocationMarkerAsset = ObjectiveLocationMarkerAsset.getAssetMap().getAsset(entity.objectiveLocationMarkerId); + if (objectiveLocationMarkerAsset == null) { + ObjectivePlugin.get() + .getLogger() + .at(Level.WARNING) + .log("Could not find ObjectiveLocationMarker '%s'. Entity removed!", entity.objectiveLocationMarkerId); + commandBuffer.removeEntity(entityReference, RemoveReason.REMOVE); + } else { + World world = store.getExternalData().getWorld(); + Objective objective = objectiveLocationMarkerAsset.getObjectiveTypeSetup().setup(new HashSet<>(), world.getWorldConfig().getUuid(), uuid, store); + if (objective == null) { + ObjectivePlugin.get() + .getLogger() + .at(Level.WARNING) + .log("Objective failed to setup for ObjectiveLocationMarker '%s'. Entity removed!", entity.objectiveLocationMarkerId); + commandBuffer.removeEntity(entityReference, RemoveReason.REMOVE); + } else { + entity.setActiveObjective(objective); + entity.activeObjectiveUUID = objective.getObjectiveUUID(); + entity.setUntrackPacket(new UntrackObjective(entity.activeObjectiveUUID)); + } + } + } + + private static void updateIncomingPlayers( + @Nonnull PlayerRef[] playersInArea, + int playersInAreaSize, + @Nonnull ObjectiveLocationMarker entity, + @Nonnull Set playerUUIDs, + @Nonnull Set activePlayerUUIDs, + @Nonnull String objectiveId + ) { + if (playersInArea.length != 0) { + TrackOrUpdateObjective trackPacket = null; + ObjectivePlugin objectiveModule = ObjectivePlugin.get(); + HytaleLogger logger = objectiveModule.getLogger(); + ObjectiveDataStore objectiveDataStore = objectiveModule.getObjectiveDataStore(); + + for (int i = 0; i < playersInAreaSize; i++) { + PlayerRef playerRef = playersInArea[i]; + UUID playerUUID = playerRef.getUuid(); + if (activePlayerUUIDs.add(playerUUID)) { + playerUUIDs.add(playerUUID); + logger.at(Level.FINE) + .log( + "Player '%s' joined the objective area for marker '%s', current objective '%s' with UUID '%s'", + playerRef.getUsername(), + entity.objectiveLocationMarkerId, + objectiveId, + entity.activeObjectiveUUID + ); + if (trackPacket == null) { + trackPacket = new TrackOrUpdateObjective(entity.getActiveObjective().toPacket()); + } + + playerRef.getPacketHandler().writeNoCache(trackPacket); + + for (ObjectiveTask task : entity.getActiveObjective().getCurrentTasks()) { + if (task instanceof UseEntityObjectiveTask) { + objectiveDataStore.addEntityTaskForPlayer(playerUUID, ((UseEntityObjectiveTask)task).getAsset().getTaskId(), entity.activeObjectiveUUID); + } + } + } + } + } + } + + private static void updateOutgoingPlayers( + @Nonnull Set playersInArea, @Nonnull ObjectiveLocationMarker entity, @Nullable Set activePlayerUUIDs, @Nonnull String objectiveId + ) { + if (activePlayerUUIDs != null && !activePlayerUUIDs.isEmpty()) { + HytaleLogger logger = ObjectivePlugin.get().getLogger(); + Iterator iterator = activePlayerUUIDs.iterator(); + Universe universe = Universe.get(); + + while (iterator.hasNext()) { + UUID uuid = iterator.next(); + if (!playersInArea.contains(uuid)) { + iterator.remove(); + untrackEntityObjectiveForPlayer(entity, uuid); + PlayerRef playerRef = universe.getPlayer(uuid); + logger.at(Level.FINE) + .log( + "Player '%s' left the objective area for marker '%s', current objective '%s' with UUID '%s'", + playerRef == null ? uuid : playerRef.getUsername(), + entity.objectiveLocationMarkerId, + objectiveId, + entity.activeObjectiveUUID + ); + if (playerRef != null) { + playerRef.getPacketHandler().write(entity.getUntrackPacket()); + } + } + } + } + } + + private static boolean isPlayerInSpecificEnvironment( + @Nonnull ObjectiveLocationMarker entity, + @Nonnull WeatherTracker weatherTracker, + @Nonnull TransformComponent transform, + @Nonnull ComponentAccessor componentAccessor + ) { + if (entity.environmentIndexes == null) { + return true; + } else { + weatherTracker.updateEnvironment(transform, componentAccessor); + int environmentIndex = weatherTracker.getEnvironmentId(); + return Arrays.binarySearch(entity.environmentIndexes, environmentIndex) >= 0; + } + } + + private static void untrackEntityObjectiveForPlayer(@Nonnull ObjectiveLocationMarker entity, @Nonnull UUID playerUUID) { + ObjectiveDataStore objectiveDataStore = ObjectivePlugin.get().getObjectiveDataStore(); + + for (ObjectiveTask task : entity.getActiveObjective().getCurrentTasks()) { + if (task instanceof UseEntityObjectiveTask) { + String taskId = ((UseEntityObjectiveTask)task).getAsset().getTaskId(); + objectiveDataStore.removeEntityTaskForPlayer(entity.activeObjectiveUUID, taskId, playerUUID); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/markers/reachlocation/ReachLocationMarker.java b/src/com/hypixel/hytale/builtin/adventure/objectives/markers/reachlocation/ReachLocationMarker.java new file mode 100644 index 0000000..519542c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/markers/reachlocation/ReachLocationMarker.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.builtin.adventure.objectives.markers.reachlocation; + +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ReachLocationMarker implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(ReachLocationMarker.class, ReachLocationMarker::new) + .append( + new KeyedCodec<>("MarkerId", Codec.STRING), + (reachLocationMarkerEntity, uuid) -> reachLocationMarkerEntity.markerId = uuid, + reachLocationMarkerEntity -> reachLocationMarkerEntity.markerId + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + private String markerId; + private final Set players = new HashSet<>(); + + public static ComponentType getComponentType() { + return ObjectivePlugin.get().getReachLocationMarkerComponentType(); + } + + public ReachLocationMarker() { + } + + public ReachLocationMarker(String markerId) { + this.markerId = markerId; + } + + public String getMarkerId() { + return this.markerId; + } + + @Nullable + public String getLocationName() { + ReachLocationMarkerAsset asset = ReachLocationMarkerAsset.getAssetMap().getAsset(this.markerId); + return asset != null ? asset.getName() : null; + } + + @Nonnull + public Set getPlayers() { + return this.players; + } + + @Nonnull + @Override + public Component clone() { + ReachLocationMarker marker = new ReachLocationMarker(this.markerId); + marker.players.addAll(this.players); + return marker; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/markers/reachlocation/ReachLocationMarkerAsset.java b/src/com/hypixel/hytale/builtin/adventure/objectives/markers/reachlocation/ReachLocationMarkerAsset.java new file mode 100644 index 0000000..3684d07 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/markers/reachlocation/ReachLocationMarkerAsset.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.builtin.adventure.objectives.markers.reachlocation; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class ReachLocationMarkerAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ReachLocationMarkerAsset.class, + ReachLocationMarkerAsset::new, + Codec.STRING, + (t, k) -> t.id = k, + t -> t.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .append( + new KeyedCodec<>("Radius", Codec.FLOAT), + (reachLocationMarkerAsset, aFloat) -> reachLocationMarkerAsset.radius = aFloat, + reachLocationMarkerAsset -> reachLocationMarkerAsset.radius + ) + .addValidator(Validators.greaterThan(0.0F)) + .add() + .append( + new KeyedCodec<>("Name", Codec.STRING), + (reachLocationMarkerAsset, s) -> reachLocationMarkerAsset.name = s, + reachLocationMarkerAsset -> reachLocationMarkerAsset.name + ) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyString()) + .add() + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ReachLocationMarkerAsset::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected String name; + protected float radius = 1.0F; + + public ReachLocationMarkerAsset() { + } + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ReachLocationMarkerAsset.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public String getId() { + return this.id; + } + + public float getRadius() { + return this.radius; + } + + public String getName() { + return this.name; + } + + @Nonnull + @Override + public String toString() { + return "ReachLocationMarkerAsset{id='" + this.id + "', name='" + this.name + "', radius=" + this.radius + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/markers/reachlocation/ReachLocationMarkerSystems.java b/src/com/hypixel/hytale/builtin/adventure/objectives/markers/reachlocation/ReachLocationMarkerSystems.java new file mode 100644 index 0000000..df251cc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/markers/reachlocation/ReachLocationMarkerSystems.java @@ -0,0 +1,210 @@ +package com.hypixel.hytale.builtin.adventure.objectives.markers.reachlocation; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectiveDataStore; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.task.ObjectiveTaskRef; +import com.hypixel.hytale.builtin.adventure.objectives.task.ReachLocationTask; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.OrderPriority; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.system.PlayerSpatialSystem; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class ReachLocationMarkerSystems { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final ThreadLocal> THREAD_LOCAL_TEMP_UUIDS = ThreadLocal.withInitial(HashSet::new); + + public ReachLocationMarkerSystems() { + } + + public static class EnsureNetworkSendable extends HolderSystem { + private final Query query = Query.and(ReachLocationMarker.getComponentType(), Query.not(NetworkId.getComponentType())); + + public EnsureNetworkSendable() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.addComponent(NetworkId.getComponentType(), new NetworkId(store.getExternalData().takeNextNetworkId())); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + } + + public static class EntityAdded extends RefSystem { + private final ComponentType reachLocationMarkerComponent; + private final ComponentType transformComponentType; + @Nonnull + private final Query query; + + public EntityAdded(ComponentType reachLocationMarkerComponent) { + this.reachLocationMarkerComponent = reachLocationMarkerComponent; + this.transformComponentType = TransformComponent.getComponentType(); + this.query = Query.and(reachLocationMarkerComponent, this.transformComponentType); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + ReachLocationMarker reachLocationMarkerComponent = commandBuffer.getComponent(ref, this.reachLocationMarkerComponent); + + assert reachLocationMarkerComponent != null; + + TransformComponent transformComponent = commandBuffer.getComponent(ref, this.transformComponentType); + + assert transformComponent != null; + + Vector3d pos = transformComponent.getPosition(); + ObjectiveDataStore objectiveDataStore = ObjectivePlugin.get().getObjectiveDataStore(); + + for (ObjectiveTaskRef taskRef : objectiveDataStore.getTaskRefsForType(ReachLocationTask.class)) { + Objective objective = objectiveDataStore.getObjective(taskRef.getObjectiveUUID()); + if (objective != null) { + taskRef.getObjectiveTask().setupMarker(objective, reachLocationMarkerComponent, pos, commandBuffer); + } + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + } + + public static class Ticking extends EntityTickingSystem { + private final ComponentType reachLocationMarkerComponent; + private final ComponentType transformComponentType; + private final ResourceType, EntityStore>> playerSpatialComponent; + private final ComponentType uuidComponentType = UUIDComponent.getComponentType(); + @Nonnull + private final Query query; + @Nonnull + private final Set> dependencies; + + public Ticking( + ComponentType reachLocationMarkerComponent, + ResourceType, EntityStore>> playerSpatialComponent + ) { + this.reachLocationMarkerComponent = reachLocationMarkerComponent; + this.transformComponentType = TransformComponent.getComponentType(); + this.playerSpatialComponent = playerSpatialComponent; + this.query = Query.and(reachLocationMarkerComponent, this.transformComponentType); + this.dependencies = Set.of(new SystemDependency<>(Order.AFTER, PlayerSpatialSystem.class, OrderPriority.CLOSEST)); + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return false; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + ReachLocationMarker reachLocationMarkerComponent = archetypeChunk.getComponent(index, this.reachLocationMarkerComponent); + + assert reachLocationMarkerComponent != null; + + String markerId = reachLocationMarkerComponent.getMarkerId(); + ReachLocationMarkerAsset asset = ReachLocationMarkerAsset.getAssetMap().getAsset(markerId); + if (asset == null) { + ReachLocationMarkerSystems.LOGGER + .at(Level.WARNING) + .log("No ReachLocationMarkerAsset found for ID '%s', entity removed! %s", markerId, reachLocationMarkerComponent); + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + } else { + Set previousPlayers = ReachLocationMarkerSystems.THREAD_LOCAL_TEMP_UUIDS.get(); + previousPlayers.clear(); + previousPlayers.addAll(reachLocationMarkerComponent.getPlayers()); + Set players = reachLocationMarkerComponent.getPlayers(); + players.clear(); + SpatialResource, EntityStore> spatialResource = store.getResource(this.playerSpatialComponent); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + TransformComponent transformComponent = archetypeChunk.getComponent(index, this.transformComponentType); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + spatialResource.getSpatialStructure().ordered(position, asset.getRadius(), results); + ObjectiveDataStore objectiveDataStore = ObjectivePlugin.get().getObjectiveDataStore(); + + for (int i = 0; i < results.size(); i++) { + Ref otherEntityReference = results.get(i); + UUIDComponent otherUuidComponent = commandBuffer.getComponent(otherEntityReference, this.uuidComponentType); + + assert otherUuidComponent != null; + + UUID otherUuid = otherUuidComponent.getUuid(); + players.add(otherUuid); + if (!previousPlayers.contains(otherUuid)) { + for (ObjectiveTaskRef taskRef : objectiveDataStore.getTaskRefsForType(ReachLocationTask.class)) { + Objective objective = objectiveDataStore.getObjective(taskRef.getObjectiveUUID()); + if (objective != null) { + taskRef.getObjectiveTask().onPlayerReachLocationMarker(store, otherEntityReference, markerId, objective); + } + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/systems/ObjectiveItemEntityRemovalSystem.java b/src/com/hypixel/hytale/builtin/adventure/objectives/systems/ObjectiveItemEntityRemovalSystem.java new file mode 100644 index 0000000..c35cf9b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/systems/ObjectiveItemEntityRemovalSystem.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.builtin.adventure.objectives.systems; + +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.interactions.StartObjectiveInteraction; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class ObjectiveItemEntityRemovalSystem extends HolderSystem { + private static final ComponentType COMPONENT_TYPE = ItemComponent.getComponentType(); + + public ObjectiveItemEntityRemovalSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return COMPONENT_TYPE; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + if (reason == RemoveReason.REMOVE) { + ItemComponent itemComponent = holder.getComponent(COMPONENT_TYPE); + + assert itemComponent != null; + + ItemStack itemStack = itemComponent.getItemStack(); + if (itemStack != null) { + UUID objectiveUUID = itemStack.getFromMetadataOrNull(StartObjectiveInteraction.OBJECTIVE_UUID); + if (objectiveUUID != null) { + if (!itemComponent.isRemovedByPlayerPickup()) { + ObjectivePlugin.get().cancelObjective(objectiveUUID, store); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/systems/ObjectivePlayerSetupSystem.java b/src/com/hypixel/hytale/builtin/adventure/objectives/systems/ObjectivePlayerSetupSystem.java new file mode 100644 index 0000000..083b21d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/systems/ObjectivePlayerSetupSystem.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.builtin.adventure.objectives.systems; + +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.components.ObjectiveHistoryComponent; +import com.hypixel.hytale.builtin.adventure.objectives.config.gameplayconfig.ObjectiveGameplayConfig; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerWorldData; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class ObjectivePlayerSetupSystem extends RefSystem { + @Nonnull + private final ComponentType objectiveHistoryComponentType; + @Nonnull + private final ComponentType playerComponentType; + @Nonnull + private final ComponentType uuidComponentType = UUIDComponent.getComponentType(); + @Nonnull + private final Query query; + + public ObjectivePlayerSetupSystem( + @Nonnull ComponentType objectiveHistoryComponentType, + @Nonnull ComponentType playerComponentType + ) { + this.objectiveHistoryComponentType = objectiveHistoryComponentType; + this.playerComponentType = playerComponentType; + this.query = Query.and(playerComponentType, this.uuidComponentType); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + commandBuffer.ensureComponent(ref, this.objectiveHistoryComponentType); + Player playerComponent = store.getComponent(ref, this.playerComponentType); + + assert playerComponent != null; + + UUIDComponent uuidComponent = store.getComponent(ref, this.uuidComponentType); + + assert uuidComponent != null; + + ObjectivePlugin objectiveModule = ObjectivePlugin.get(); + UUID playerUuid = uuidComponent.getUuid(); + PlayerConfigData playerConfigData = playerComponent.getPlayerConfigData(); + Set activeObjectiveUUIDs = playerConfigData.getActiveObjectiveUUIDs(); + if (activeObjectiveUUIDs != null) { + for (UUID objectiveUUID : activeObjectiveUUIDs) { + objectiveModule.addPlayerToExistingObjective(store, playerUuid, objectiveUUID); + } + } + + World world = store.getExternalData().getWorld(); + String worldName = world.getName(); + PlayerWorldData perWorldData = playerConfigData.getPerWorldData(worldName); + if (perWorldData.isFirstSpawn()) { + ObjectiveGameplayConfig config = ObjectiveGameplayConfig.get(world.getGameplayConfig()); + Map starterObjectiveLinePerWorld = config != null ? config.getStarterObjectiveLinePerWorld() : null; + if (starterObjectiveLinePerWorld != null) { + String objectiveLineId = starterObjectiveLinePerWorld.get(worldName); + if (objectiveLineId != null) { + objectiveModule.startObjectiveLine(store, objectiveLineId, Set.of(playerUuid), world.getWorldConfig().getUuid(), null); + } + } + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/task/CountObjectiveTask.java b/src/com/hypixel/hytale/builtin/adventure/objectives/task/CountObjectiveTask.java new file mode 100644 index 0000000..68e0376 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/task/CountObjectiveTask.java @@ -0,0 +1,86 @@ +package com.hypixel.hytale.builtin.adventure.objectives.task; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.CountObjectiveTaskAsset; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public abstract class CountObjectiveTask extends ObjectiveTask { + public static final BuilderCodec CODEC = BuilderCodec.abstractBuilder(CountObjectiveTask.class, ObjectiveTask.BASE_CODEC) + .append(new KeyedCodec<>("Count", Codec.INTEGER), (countTask, integer) -> countTask.count = integer, countTask -> countTask.count) + .add() + .build(); + protected int count; + + public CountObjectiveTask(@Nonnull CountObjectiveTaskAsset asset, int taskSetIndex, int taskIndex) { + super(asset, taskSetIndex, taskIndex); + } + + protected CountObjectiveTask() { + } + + @Nonnull + public CountObjectiveTaskAsset getAsset() { + return (CountObjectiveTaskAsset)super.getAsset(); + } + + @Nonnull + @Override + public Message getInfoMessage(@Nonnull Objective objective) { + return super.getInfoMessage(objective).insert(" " + this.count + "/" + this.getAsset().getCount()); + } + + @Override + public boolean checkCompletion() { + return this.count >= this.getAsset().getCount(); + } + + @Override + public void assetChanged(@Nonnull Objective objective) { + if (this.complete) { + this.count = this.getAsset().getCount(); + } + + super.assetChanged(objective); + } + + public void increaseTaskCompletion(@Nonnull Store store, @Nonnull Ref ref, int qty, @Nonnull Objective objective) { + if (this.areTaskConditionsFulfilled(store, ref, objective.getActivePlayerUUIDs())) { + this.count = MathUtil.clamp(this.count + qty, this.count, this.getAsset().getCount()); + this.updateTaskCompletion(store, ref, objective); + } + } + + public void setTaskCompletion(@Nonnull Store store, @Nonnull Ref ref, int qty, @Nonnull Objective objective) { + if (this.areTaskConditionsFulfilled(store, ref, objective.getActivePlayerUUIDs())) { + this.count = MathUtil.clamp(qty, 0, this.getAsset().getCount()); + this.updateTaskCompletion(store, ref, objective); + } + } + + private void updateTaskCompletion(@Nonnull Store store, @Nonnull Ref ref, @Nonnull Objective objective) { + objective.markDirty(); + this.sendUpdateObjectiveTaskPacket(objective); + if (this.checkCompletion()) { + this.consumeTaskConditions(store, ref, objective.getActivePlayerUUIDs()); + this.complete(objective, store); + objective.checkTaskSetCompletion(store); + } + } + + @Nonnull + public com.hypixel.hytale.protocol.ObjectiveTask toPacket(@Nonnull Objective objective) { + com.hypixel.hytale.protocol.ObjectiveTask packet = new com.hypixel.hytale.protocol.ObjectiveTask(); + packet.taskDescriptionKey = this.asset.getDescriptionKey(objective.getObjectiveId(), this.taskSetIndex, this.taskIndex); + packet.currentCompletion = this.count; + packet.completionNeeded = this.getAsset().getCount(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/task/CraftObjectiveTask.java b/src/com/hypixel/hytale/builtin/adventure/objectives/task/CraftObjectiveTask.java new file mode 100644 index 0000000..0e19ef5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/task/CraftObjectiveTask.java @@ -0,0 +1,71 @@ +package com.hypixel.hytale.builtin.adventure.objectives.task; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.CraftObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.RegistrationTransactionRecord; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionRecord; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.event.events.player.PlayerCraftEvent; +import com.hypixel.hytale.server.core.inventory.MaterialQuantity; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class CraftObjectiveTask extends CountObjectiveTask { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CraftObjectiveTask.class, CraftObjectiveTask::new, CountObjectiveTask.CODEC + ) + .build(); + + public CraftObjectiveTask(@Nonnull CraftObjectiveTaskAsset asset, int taskSetIndex, int taskIndex) { + super(asset, taskSetIndex, taskIndex); + } + + protected CraftObjectiveTask() { + } + + @Nonnull + public CraftObjectiveTaskAsset getAsset() { + return (CraftObjectiveTaskAsset)super.getAsset(); + } + + @Nonnull + @Override + protected TransactionRecord[] setup0(@Nonnull Objective objective, @Nonnull World world, @Nonnull Store store) { + this.eventRegistry.register(PlayerCraftEvent.class, world.getName(), event -> { + String desiredItemId = this.getAsset().getItemId(); + CraftingRecipe recipe = event.getCraftedRecipe(); + boolean isOutput = false; + + for (MaterialQuantity materialQuantity : recipe.getOutputs()) { + if (Objects.equals(materialQuantity.getItemId(), desiredItemId)) { + isOutput = true; + break; + } + } + + if (isOutput) { + Ref ref = event.getPlayerRef(); + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + if (objective.getActivePlayerUUIDs().contains(uuidComponent.getUuid())) { + this.increaseTaskCompletion(store, ref, event.getQuantity(), objective); + } + } + }); + return RegistrationTransactionRecord.wrap(this.eventRegistry); + } + + @Nonnull + @Override + public String toString() { + return "CraftObjectiveTask{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/task/GatherObjectiveTask.java b/src/com/hypixel/hytale/builtin/adventure/objectives/task/GatherObjectiveTask.java new file mode 100644 index 0000000..1c229bc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/task/GatherObjectiveTask.java @@ -0,0 +1,107 @@ +package com.hypixel.hytale.builtin.adventure.objectives.task; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.BlockTagOrItemIdField; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.GatherObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.RegistrationTransactionRecord; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionRecord; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.event.events.entity.LivingEntityInventoryChangeEvent; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class GatherObjectiveTask extends CountObjectiveTask { + public static final BuilderCodec CODEC = BuilderCodec.builder( + GatherObjectiveTask.class, GatherObjectiveTask::new, CountObjectiveTask.CODEC + ) + .build(); + + public GatherObjectiveTask(@Nonnull GatherObjectiveTaskAsset asset, int taskSetIndex, int taskIndex) { + super(asset, taskSetIndex, taskIndex); + } + + protected GatherObjectiveTask() { + } + + @Nonnull + public GatherObjectiveTaskAsset getAsset() { + return (GatherObjectiveTaskAsset)super.getAsset(); + } + + @Nullable + @Override + protected TransactionRecord[] setup0(@Nonnull Objective objective, @Nonnull World world, @Nonnull Store store) { + Set participatingPlayers = objective.getPlayerUUIDs(); + int countItem = this.countObjectiveItemInInventories(participatingPlayers, store); + if (this.areTaskConditionsFulfilled(null, null, participatingPlayers)) { + this.count = MathUtil.clamp(countItem, 0, this.getAsset().getCount()); + if (this.checkCompletion()) { + this.consumeTaskConditions(null, null, participatingPlayers); + this.complete = true; + return null; + } + } + + this.eventRegistry.register(LivingEntityInventoryChangeEvent.class, world.getName(), event -> { + LivingEntity livingEntity = event.getEntity(); + if (livingEntity instanceof Player) { + Ref ref = livingEntity.getReference(); + World refWorld = store.getExternalData().getWorld(); + refWorld.execute(() -> { + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + Set activePlayerUUIDs = objective.getActivePlayerUUIDs(); + if (activePlayerUUIDs.contains(uuidComponent.getUuid())) { + int count = this.countObjectiveItemInInventories(activePlayerUUIDs, store); + this.setTaskCompletion(store, ref, count, objective); + } + }); + } + }); + return RegistrationTransactionRecord.wrap(this.eventRegistry); + } + + private int countObjectiveItemInInventories(@Nonnull Set participatingPlayers, @Nonnull ComponentAccessor componentAccessor) { + int count = 0; + BlockTagOrItemIdField blockTypeOrSet = this.getAsset().getBlockTagOrItemIdField(); + + for (UUID playerUUID : participatingPlayers) { + PlayerRef playerRefComponent = Universe.get().getPlayer(playerUUID); + if (playerRefComponent != null) { + Ref playerRef = playerRefComponent.getReference(); + if (playerRef != null && playerRef.isValid()) { + Player playerComponent = componentAccessor.getComponent(playerRef, Player.getComponentType()); + + assert playerComponent != null; + + CombinedItemContainer inventory = playerComponent.getInventory().getCombinedHotbarFirst(); + count += inventory.countItemStacks(itemStack -> blockTypeOrSet.isBlockTypeIncluded(itemStack.getItemId())); + } + } + } + + return count; + } + + @Nonnull + @Override + public String toString() { + return "GatherObjectiveTask{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/task/ObjectiveTask.java b/src/com/hypixel/hytale/builtin/adventure/objectives/task/ObjectiveTask.java new file mode 100644 index 0000000..4e6b980 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/task/ObjectiveTask.java @@ -0,0 +1,342 @@ +package com.hypixel.hytale.builtin.adventure.objectives.task; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.ObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.taskcondition.TaskConditionAsset; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionRecord; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionUtil; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.WorldTransactionRecord; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.EventRegistry; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.packets.assets.UpdateObjectiveTask; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.io.NetworkSerializer; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.PositionUtil; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class ObjectiveTask implements NetworkSerializer { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(ObjectiveTask.class) + .append( + new KeyedCodec<>("Task", ObjectiveTaskAsset.CODEC), + (aObjectiveTask, objectiveTaskAsset) -> aObjectiveTask.asset = objectiveTaskAsset, + aObjectiveTask -> aObjectiveTask.asset + ) + .add() + .append( + new KeyedCodec<>("Complete", Codec.BOOLEAN), + (aObjectiveTask, aBoolean) -> aObjectiveTask.complete = aBoolean, + aObjectiveTask -> aObjectiveTask.complete + ) + .add() + .append( + new KeyedCodec<>("TransactionRecords", new ArrayCodec<>(TransactionRecord.CODEC, TransactionRecord[]::new)), + (objectiveTask, transactionRecords) -> objectiveTask.serializedTransactionRecords = transactionRecords, + objectiveTask -> objectiveTask.serializedTransactionRecords + ) + .add() + .append( + new KeyedCodec<>("TaskIndex", Codec.INTEGER), (objectiveTask, integer) -> objectiveTask.taskIndex = integer, objectiveTask -> objectiveTask.taskIndex + ) + .add() + .append(new KeyedCodec<>("Markers", ProtocolCodecs.MARKER_ARRAY), (objectiveTask, markers) -> { + objectiveTask.markers.clear(); + Collections.addAll(objectiveTask.markers, markers); + }, objectiveTask -> objectiveTask.markers.toArray(MapMarker[]::new)) + .add() + .append( + new KeyedCodec<>("TaskSetIndex", Codec.INTEGER), + (objectiveTask, integer) -> objectiveTask.taskSetIndex = integer, + objectiveTask -> objectiveTask.taskSetIndex + ) + .add() + .build(); + protected ObjectiveTaskAsset asset; + protected boolean complete = false; + @Nullable + protected EventRegistry eventRegistry; + @Nullable + protected TransactionRecord[] serializedTransactionRecords; + @Nullable + protected TransactionRecord[] nonSerializedTransactionRecords; + protected int taskIndex; + @Nonnull + protected List markers = new ObjectArrayList<>(); + protected int taskSetIndex; + protected ObjectiveTaskRef taskRef; + + public ObjectiveTask(@Nonnull ObjectiveTaskAsset asset, int taskSetIndex, int taskIndex) { + this.asset = asset; + this.taskIndex = taskIndex; + this.taskSetIndex = taskSetIndex; + } + + protected ObjectiveTask() { + } + + @Nonnull + public ObjectiveTaskAsset getAsset() { + return this.asset; + } + + public void setAsset(@Nonnull ObjectiveTaskAsset asset) { + this.asset = asset; + } + + public boolean isComplete() { + return this.complete; + } + + @Nullable + public TransactionRecord[] getSerializedTransactionRecords() { + return this.serializedTransactionRecords; + } + + @Nullable + public TransactionRecord[] getNonSerializedTransactionRecords() { + return this.nonSerializedTransactionRecords; + } + + @Nonnull + public Message getInfoMessage(@Nonnull Objective objective) { + return Message.translation(this.asset.getDescriptionKey(objective.getObjectiveId(), this.taskSetIndex, this.taskIndex)); + } + + @Nonnull + public List getMarkers() { + return this.markers; + } + + public void addMarker(@Nonnull MapMarker marker) { + this.markers.add(marker); + } + + public void removeMarker(String id) { + for (MapMarker marker : this.markers) { + if (marker.id.equals(id)) { + this.markers.remove(marker); + return; + } + } + } + + public abstract boolean checkCompletion(); + + @Nullable + protected abstract TransactionRecord[] setup0(@Nonnull Objective var1, @Nonnull World var2, @Nonnull Store var3); + + @Nullable + public final TransactionRecord[] setup(@Nonnull Objective objective, @Nonnull Store store) { + World world = Universe.get().getWorld(objective.getWorldUUID()); + if (world == null) { + String transactionMessage = "This World doesn't exist in this Universe: " + objective.getWorldUUID(); + return TransactionRecord.appendFailedTransaction(this.nonSerializedTransactionRecords, new WorldTransactionRecord(), transactionMessage); + } else if (this.eventRegistry != null) { + throw new IllegalStateException("ObjectiveTask.eventRegistry is not null, setup() shouldn't be run more than once!"); + } else { + this.eventRegistry = new EventRegistry(new CopyOnWriteArrayList<>(), () -> true, null, world.getEventRegistry()); + Vector3i[] mapMarkerPositions = this.asset.getMapMarkers(); + if (mapMarkerPositions != null) { + String objectiveUUIDString = objective.getObjectiveUUID().toString(); + + for (int i = 0; i < mapMarkerPositions.length; i++) { + Vector3i mapMarkerPosition = mapMarkerPositions[i]; + this.addMarker( + new MapMarker( + "ObjectiveMarker_" + objectiveUUIDString + "_" + i, + "Objective", + "Home.png", + PositionUtil.toTransformPacket(new Transform(mapMarkerPosition)), + null + ) + ); + } + } + + this.taskRef = new ObjectiveTaskRef<>(objective.getObjectiveUUID(), this); + this.registerTaskRef(); + TransactionRecord[] transactionRecords = this.setup0(objective, world, store); + if (transactionRecords == null) { + return null; + } else { + int serializedCount = 0; + + for (TransactionRecord transactionRecord : transactionRecords) { + if (transactionRecord.shouldBeSerialized()) { + serializedCount++; + } + } + + this.serializedTransactionRecords = new TransactionRecord[serializedCount]; + this.nonSerializedTransactionRecords = new TransactionRecord[transactionRecords.length - serializedCount]; + int serializedIndex = 0; + int nonSerializedIndex = 0; + + for (TransactionRecord transactionRecordx : transactionRecords) { + if (transactionRecordx.shouldBeSerialized()) { + this.serializedTransactionRecords[serializedIndex++] = transactionRecordx; + } else { + this.nonSerializedTransactionRecords[nonSerializedIndex++] = transactionRecordx; + } + } + + return transactionRecords; + } + } + } + + public void complete(@Nonnull Objective objective, @Nullable ComponentAccessor componentAccessor) { + if (!this.complete) { + if (componentAccessor != null) { + objective.forEachParticipant((participantReference, message) -> { + Player playerComponent = componentAccessor.getComponent(participantReference, Player.getComponentType()); + if (playerComponent != null) { + playerComponent.sendMessage(message); + } + }, Message.translation("server.modules.objective.task.completed").insert(this.getInfoMessage(objective))); + } + + this.markers.clear(); + this.complete = true; + this.completeTransactionRecords(); + } + } + + private void registerTaskRef() { + ObjectivePlugin.get().getObjectiveDataStore().addTaskRef(this.taskRef); + } + + private void unregisterTaskRef() { + ObjectivePlugin.get().getObjectiveDataStore().removeTaskRef(this.taskRef); + } + + public void completeTransactionRecords() { + TransactionUtil.completeAll(this.serializedTransactionRecords); + this.serializedTransactionRecords = null; + TransactionUtil.completeAll(this.nonSerializedTransactionRecords); + this.nonSerializedTransactionRecords = null; + this.shutdownEventRegistry(); + this.unregisterTaskRef(); + } + + public void revertTransactionRecords() { + TransactionUtil.revertAll(this.serializedTransactionRecords); + this.serializedTransactionRecords = null; + TransactionUtil.revertAll(this.nonSerializedTransactionRecords); + this.nonSerializedTransactionRecords = null; + this.shutdownEventRegistry(); + this.unregisterTaskRef(); + } + + public void unloadTransactionRecords() { + TransactionUtil.unloadAll(this.serializedTransactionRecords); + this.serializedTransactionRecords = null; + TransactionUtil.unloadAll(this.nonSerializedTransactionRecords); + this.nonSerializedTransactionRecords = null; + this.shutdownEventRegistry(); + this.unregisterTaskRef(); + } + + private void shutdownEventRegistry() { + if (this.eventRegistry != null) { + this.eventRegistry.shutdown(); + this.eventRegistry = null; + } + } + + public void assetChanged(@Nonnull Objective objective) { + if (!this.complete) { + if (this.checkCompletion()) { + this.consumeTaskConditions(null, null, objective.getActivePlayerUUIDs()); + this.complete(objective, null); + } + } + } + + public void sendUpdateObjectiveTaskPacket(@Nonnull Objective objective) { + UpdateObjectiveTask updateObjectiveTaskPacket = new UpdateObjectiveTask(objective.getObjectiveUUID(), this.taskIndex, this.toPacket(objective)); + Universe universe = Universe.get(); + + for (UUID playerUUID : objective.getActivePlayerUUIDs()) { + PlayerRef player = universe.getPlayer(playerUUID); + if (player != null) { + player.getPacketHandler().writeNoCache(updateObjectiveTaskPacket); + } + } + } + + public boolean areTaskConditionsFulfilled( + @Nullable ComponentAccessor componentAccessor, @Nullable Ref ref, @Nullable Set objectivePlayers + ) { + TaskConditionAsset[] taskConditions = this.asset.getTaskConditions(); + if (taskConditions == null) { + return true; + } else { + for (TaskConditionAsset taskCondition : taskConditions) { + if (!taskCondition.isConditionFulfilled(componentAccessor, ref, objectivePlayers)) { + return false; + } + } + + return true; + } + } + + public void consumeTaskConditions( + @Nullable ComponentAccessor componentAccessor, @Nullable Ref ref, @Nonnull Set objectivePlayers + ) { + TaskConditionAsset[] taskConditions = this.asset.getTaskConditions(); + if (taskConditions != null) { + for (TaskConditionAsset taskCondition : taskConditions) { + taskCondition.consumeCondition(componentAccessor, ref, objectivePlayers); + } + } + } + + @Nonnull + @Override + public String toString() { + return "ObjectiveTask{asset=" + + this.asset + + ", complete=" + + this.complete + + ", eventRegistry=" + + this.eventRegistry + + ", serializedTransactionRecords=" + + Arrays.toString((Object[])this.serializedTransactionRecords) + + ", nonSerializedTransactionRecords=" + + Arrays.toString((Object[])this.nonSerializedTransactionRecords) + + ", taskIndex=" + + this.taskIndex + + ", markers=" + + this.markers + + ", taskSetIndex=" + + this.taskSetIndex + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/task/ObjectiveTaskRef.java b/src/com/hypixel/hytale/builtin/adventure/objectives/task/ObjectiveTaskRef.java new file mode 100644 index 0000000..cdc1abd --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/task/ObjectiveTaskRef.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.builtin.adventure.objectives.task; + +import java.util.UUID; + +public class ObjectiveTaskRef { + private final UUID objectiveUUID; + private final T objectiveTask; + + public ObjectiveTaskRef(UUID objectiveUUID, T objectiveTask) { + this.objectiveUUID = objectiveUUID; + this.objectiveTask = objectiveTask; + } + + public UUID getObjectiveUUID() { + return this.objectiveUUID; + } + + public T getObjectiveTask() { + return this.objectiveTask; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/task/ReachLocationTask.java b/src/com/hypixel/hytale/builtin/adventure/objectives/task/ReachLocationTask.java new file mode 100644 index 0000000..4704877 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/task/ReachLocationTask.java @@ -0,0 +1,193 @@ +package com.hypixel.hytale.builtin.adventure.objectives.task; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.ObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.ReachLocationTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.markers.reachlocation.ReachLocationMarker; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionRecord; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.PositionUtil; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ReachLocationTask extends ObjectiveTask { + public static final BuilderCodec CODEC = BuilderCodec.builder(ReachLocationTask.class, ReachLocationTask::new, BASE_CODEC) + .append( + new KeyedCodec<>("Completed", Codec.BOOLEAN), + (reachLocationTask, aBoolean) -> reachLocationTask.completed = aBoolean, + reachLocationTask -> reachLocationTask.completed + ) + .add() + .append( + new KeyedCodec<>("MarkerLoaded", Codec.BOOLEAN), + (reachLocationTask, transactionRecord) -> reachLocationTask.markerLoaded = transactionRecord, + reachLocationTask -> reachLocationTask.markerLoaded + ) + .add() + .build(); + @Nonnull + public static String MARKER_ICON = "Home.png"; + private static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + private static final ComponentType REACH_LOCATION_MARKER_COMPONENT_TYPE = ReachLocationMarker.getComponentType(); + private boolean completed; + private boolean markerLoaded; + + public ReachLocationTask(@Nonnull ObjectiveTaskAsset asset, int taskSetIndex, int taskIndex) { + super(asset, taskSetIndex, taskIndex); + } + + protected ReachLocationTask() { + } + + @Override + public boolean checkCompletion() { + return this.completed; + } + + @Nonnull + public ReachLocationTaskAsset getAsset() { + return (ReachLocationTaskAsset)this.asset; + } + + @Nonnull + private String getMarkerId(@Nonnull Objective objective) { + return String.format("ReachLocation_%s_%d", objective.getObjectiveUUID(), this.taskIndex); + } + + @Nullable + @Override + protected TransactionRecord[] setup0(@Nonnull Objective objective, @Nonnull World world, @Nonnull Store store) { + if (!this.markers.isEmpty()) { + return null; + } else { + String targetLocationId = this.getAsset().getTargetLocationId(); + List> reachLocationMarkerEntities = new ObjectArrayList<>(); + store.forEachChunk(REACH_LOCATION_MARKER_COMPONENT_TYPE, (archetypeChunk, componentStoreCommandBuffer) -> { + for (int index = 0; index < archetypeChunk.size(); index++) { + ReachLocationMarker reachLocationMarkerComponent = archetypeChunk.getComponent(index, REACH_LOCATION_MARKER_COMPONENT_TYPE); + if (reachLocationMarkerComponent != null && reachLocationMarkerComponent.getMarkerId().equals(targetLocationId)) { + reachLocationMarkerEntities.add(archetypeChunk.getReferenceTo(index)); + } + } + }); + if (!reachLocationMarkerEntities.isEmpty()) { + Vector3d currentLocation = objective.getPosition(store); + Ref closestMarker = reachLocationMarkerEntities.getFirst(); + TransformComponent closestMarkerTransformComponent = store.getComponent(closestMarker, TRANSFORM_COMPONENT_TYPE); + + assert closestMarkerTransformComponent != null; + + ReachLocationMarker closestMarkerReachComponent = store.getComponent(closestMarker, REACH_LOCATION_MARKER_COMPONENT_TYPE); + + assert closestMarkerReachComponent != null; + + Vector3d closestPosition = closestMarkerTransformComponent.getPosition(); + double shortestDistance = closestPosition.distanceSquaredTo(currentLocation); + String closestLocationName = closestMarkerReachComponent.getLocationName(); + + for (int i = 1; i < reachLocationMarkerEntities.size(); i++) { + Ref markerEntityReference = reachLocationMarkerEntities.get(i); + TransformComponent markerTransformComponent = store.getComponent(markerEntityReference, TRANSFORM_COMPONENT_TYPE); + + assert markerTransformComponent != null; + + ReachLocationMarker markerReachLocationComponent = store.getComponent(markerEntityReference, REACH_LOCATION_MARKER_COMPONENT_TYPE); + + assert markerReachLocationComponent != null; + + Vector3d pos = markerTransformComponent.getPosition(); + double distance = pos.distanceSquaredTo(currentLocation); + String locationName = markerReachLocationComponent.getLocationName(); + if (distance < shortestDistance && locationName != null) { + shortestDistance = distance; + closestPosition = pos; + closestLocationName = locationName; + } + } + + if (closestLocationName != null) { + this.addMarker( + new MapMarker( + this.getMarkerId(objective), closestLocationName, MARKER_ICON, PositionUtil.toTransformPacket(new Transform(closestPosition)), null + ) + ); + this.markerLoaded = true; + return null; + } + } + + return null; + } + } + + public void setupMarker( + @Nonnull Objective objective, + @Nonnull ReachLocationMarker locationMarkerEntity, + @Nonnull Vector3d position, + @Nonnull CommandBuffer commandBuffer + ) { + if (!this.markerLoaded) { + String markerId = locationMarkerEntity.getMarkerId(); + if (markerId.equals(this.getAsset().getTargetLocationId())) { + String locationName = locationMarkerEntity.getLocationName(); + if (locationName != null) { + this.addMarker( + new MapMarker(this.getMarkerId(objective), locationName, MARKER_ICON, PositionUtil.toTransformPacket(new Transform(position)), null) + ); + this.markerLoaded = true; + } + } + } + } + + @Nonnull + public com.hypixel.hytale.protocol.ObjectiveTask toPacket(@Nonnull Objective objective) { + com.hypixel.hytale.protocol.ObjectiveTask packet = new com.hypixel.hytale.protocol.ObjectiveTask(); + packet.taskDescriptionKey = this.asset.getDescriptionKey(objective.getObjectiveId(), this.taskSetIndex, this.taskIndex); + packet.currentCompletion = this.completed ? 1 : 0; + packet.completionNeeded = 1; + return packet; + } + + public void onPlayerReachLocationMarker( + @Nonnull Store store, @Nonnull Ref ref, @Nonnull String locationMarkerId, @Nonnull Objective objective + ) { + if (locationMarkerId.equals(this.getAsset().getTargetLocationId())) { + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + if (objective.getActivePlayerUUIDs().contains(uuidComponent.getUuid())) { + if (this.areTaskConditionsFulfilled(store, ref, null)) { + this.completed = true; + objective.markDirty(); + this.sendUpdateObjectiveTaskPacket(objective); + this.consumeTaskConditions(store, ref, objective.getActivePlayerUUIDs()); + this.complete(objective, store); + objective.checkTaskSetCompletion(store); + } + } + } + } + + @Nonnull + @Override + public String toString() { + return "ReachLocationTask{completed=" + this.completed + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/task/TreasureMapObjectiveTask.java b/src/com/hypixel/hytale/builtin/adventure/objectives/task/TreasureMapObjectiveTask.java new file mode 100644 index 0000000..4ca7ecb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/task/TreasureMapObjectiveTask.java @@ -0,0 +1,227 @@ +package com.hypixel.hytale.builtin.adventure.objectives.task; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.blockstates.TreasureChestState; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.TreasureMapObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.events.TreasureChestOpeningEvent; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.RegistrationTransactionRecord; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.SpawnTreasureChestTransactionRecord; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionRecord; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.item.ItemModule; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule; +import com.hypixel.hytale.server.core.universe.world.meta.state.ItemContainerState; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.PositionUtil; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TreasureMapObjectiveTask extends ObjectiveTask { + public static final BuilderCodec CODEC = BuilderCodec.builder( + TreasureMapObjectiveTask.class, TreasureMapObjectiveTask::new, BASE_CODEC + ) + .append( + new KeyedCodec<>("CurrentCompletion", Codec.INTEGER), + (treasureMapObjectiveTask, integer) -> treasureMapObjectiveTask.currentCompletion = integer, + treasureMapObjectiveTask -> treasureMapObjectiveTask.currentCompletion + ) + .add() + .append( + new KeyedCodec<>("ChestCount", Codec.INTEGER), + (treasureMapObjectiveTask, integer) -> treasureMapObjectiveTask.chestCount = integer, + treasureMapObjectiveTask -> treasureMapObjectiveTask.chestCount + ) + .add() + .append(new KeyedCodec<>("ChestUUIDs", new ArrayCodec<>(Codec.UUID_BINARY, UUID[]::new)), (treasureMapObjectiveTask, uuids) -> { + treasureMapObjectiveTask.chestUUIDs.clear(); + Collections.addAll(treasureMapObjectiveTask.chestUUIDs, uuids); + }, treasureMapObjectiveTask -> treasureMapObjectiveTask.chestUUIDs.toArray(UUID[]::new)) + .add() + .build(); + public static final int CHEST_SPAWN_TRY = 500; + private int currentCompletion; + private int chestCount; + private final List chestUUIDs = new ObjectArrayList<>(); + + public TreasureMapObjectiveTask(@Nonnull TreasureMapObjectiveTaskAsset asset, int taskSetIndex, int taskIndex) { + super(asset, taskSetIndex, taskIndex); + } + + protected TreasureMapObjectiveTask() { + } + + @Nonnull + public TreasureMapObjectiveTaskAsset getAsset() { + return (TreasureMapObjectiveTaskAsset)super.getAsset(); + } + + @Nonnull + public String getChestMarkerIDFromUUID(@Nonnull UUID uuid) { + return "TreasureChest_" + uuid.toString(); + } + + @Nonnull + @Override + protected TransactionRecord[] setup0(@Nonnull Objective objective, @Nonnull World world, @Nonnull Store store) { + this.eventRegistry.register(TreasureChestOpeningEvent.class, world.getName(), event -> this.onTreasureChestOpeningEvent(objective, event)); + TransactionRecord[] transactionRecords = this.serializedTransactionRecords; + if (transactionRecords != null) { + return RegistrationTransactionRecord.append(transactionRecords, this.eventRegistry); + } else { + TreasureMapObjectiveTaskAsset.ChestConfig[] chestConfigs = this.getAsset().getChestConfigs(); + transactionRecords = new TransactionRecord[chestConfigs.length]; + this.chestCount = chestConfigs.length; + + for (int i = 0; i < chestConfigs.length; i++) { + transactionRecords[i] = this.spawnChest(objective, world, chestConfigs[i], store); + } + + return RegistrationTransactionRecord.append(transactionRecords, this.eventRegistry); + } + } + + @Override + public boolean checkCompletion() { + return this.currentCompletion >= this.chestCount; + } + + private void onTreasureChestOpeningEvent(@Nonnull Objective objective, @Nonnull TreasureChestOpeningEvent event) { + UUID chestUUID = event.getChestUUID(); + if (this.chestUUIDs.contains(chestUUID)) { + this.currentCompletion++; + objective.markDirty(); + this.sendUpdateObjectiveTaskPacket(objective); + String chestMarkerID = this.getChestMarkerIDFromUUID(chestUUID); + this.removeMarker(chestMarkerID); + Ref playerRef = event.getPlayerRef(); + Store store = event.getStore(); + if (this.checkCompletion()) { + this.consumeTaskConditions(store, playerRef, objective.getActivePlayerUUIDs()); + this.complete(objective, store); + objective.checkTaskSetCompletion(store); + } + } + } + + @Nonnull + private TransactionRecord spawnChest( + @Nonnull Objective objective, + @Nonnull World world, + @Nonnull TreasureMapObjectiveTaskAsset.ChestConfig chestConfig, + @Nonnull ComponentAccessor componentAccessor + ) { + Vector3i conditionPosition = this.calculateChestSpawnPosition(chestConfig, objective, world, componentAccessor); + SpawnTreasureChestTransactionRecord transactionRecord = new SpawnTreasureChestTransactionRecord(world.getWorldConfig().getUuid(), conditionPosition); + if (conditionPosition == null) { + return transactionRecord.fail("Position not safe to spawn chest"); + } else { + TreasureChestState treasureChestState = this.spawnChestBlock(world, conditionPosition, chestConfig.getChestBlockTypeKey(), transactionRecord); + if (treasureChestState == null) { + return transactionRecord; + } else { + UUID chestUUID = UUID.randomUUID(); + List stacks = ItemModule.get().getRandomItemDrops(chestConfig.getDroplistId()); + treasureChestState.setObjectiveData(objective.getObjectiveUUID(), chestUUID, stacks); + this.chestUUIDs.add(chestUUID); + treasureChestState.getChunk().setState(conditionPosition.getX(), conditionPosition.getY(), conditionPosition.getZ(), treasureChestState); + ObjectivePlugin.get().getLogger().at(Level.INFO).log("Spawned chest at: " + conditionPosition); + this.addMarker( + new MapMarker( + this.getChestMarkerIDFromUUID(chestUUID), "Chest", "Home.png", PositionUtil.toTransformPacket(new Transform(conditionPosition)), null + ) + ); + return transactionRecord; + } + } + } + + @Nullable + private TreasureChestState spawnChestBlock( + @Nonnull World world, @Nonnull Vector3i conditionPosition, String chestBlockTypeKey, @Nonnull SpawnTreasureChestTransactionRecord transactionRecord + ) { + WorldChunk worldChunk = world.getChunk(ChunkUtil.indexChunkFromBlock(conditionPosition.x, conditionPosition.z)); + worldChunk.setBlock(conditionPosition.x, conditionPosition.y, conditionPosition.z, chestBlockTypeKey); + BlockState blockState = worldChunk.getState(conditionPosition.x, conditionPosition.y, conditionPosition.z); + if (!(blockState instanceof ItemContainerState)) { + transactionRecord.fail("BlockState is not a container"); + return null; + } else { + TreasureChestState treasureChestState = BlockStateModule.get() + .createBlockState(TreasureChestState.class, worldChunk, conditionPosition.clone(), blockState.getBlockType()); + if (treasureChestState == null) { + transactionRecord.fail("Failed to create TreasureChestState!"); + return null; + } else { + return treasureChestState; + } + } + } + + @Nullable + private Vector3i calculateChestSpawnPosition( + @Nonnull TreasureMapObjectiveTaskAsset.ChestConfig chestConfig, + @Nonnull Objective objective, + @Nonnull World world, + @Nonnull ComponentAccessor componentAccessor + ) { + int currentTry = 0; + + Vector3i conditionPosition; + for (conditionPosition = null; currentTry < 500 && conditionPosition == null; currentTry++) { + double angle = Math.random() * (float) (Math.PI * 2); + float radius = MathUtil.randomFloat(chestConfig.getMinRadius(), chestConfig.getMaxRadius()); + Vector3d objectivePosition = objective.getPosition(componentAccessor); + Vector3d position = objectivePosition.clone().floor(); + position.add(radius * TrigMathUtil.cos(angle), 0.0, radius * TrigMathUtil.sin(angle)); + position.y = world.getChunk(ChunkUtil.indexChunkFromBlock(position.x, position.z)).getHeight(MathUtil.floor(position.x), MathUtil.floor(position.z)); + conditionPosition = chestConfig.getWorldLocationProvider().runCondition(world, position.toVector3i()); + } + + return conditionPosition; + } + + @Nonnull + public com.hypixel.hytale.protocol.ObjectiveTask toPacket(@Nonnull Objective objective) { + com.hypixel.hytale.protocol.ObjectiveTask packet = new com.hypixel.hytale.protocol.ObjectiveTask(); + packet.taskDescriptionKey = this.asset.getDescriptionKey(objective.getObjectiveId(), this.taskSetIndex, this.taskIndex); + packet.currentCompletion = this.currentCompletion; + packet.completionNeeded = this.chestCount; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "TreasureMapObjectiveTask{currentCompletion=" + + this.currentCompletion + + ", chestCount=" + + this.chestCount + + ", chestUUIDs=" + + this.chestUUIDs + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/task/UseBlockObjectiveTask.java b/src/com/hypixel/hytale/builtin/adventure/objectives/task/UseBlockObjectiveTask.java new file mode 100644 index 0000000..c4c338e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/task/UseBlockObjectiveTask.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.builtin.adventure.objectives.task; + +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.UseBlockObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.RegistrationTransactionRecord; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionRecord; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.event.events.entity.LivingEntityUseBlockEvent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class UseBlockObjectiveTask extends CountObjectiveTask { + public static final BuilderCodec CODEC = BuilderCodec.builder( + UseBlockObjectiveTask.class, UseBlockObjectiveTask::new, CountObjectiveTask.CODEC + ) + .build(); + + public UseBlockObjectiveTask(@Nonnull UseBlockObjectiveTaskAsset asset, int taskSetIndex, int taskIndex) { + super(asset, taskSetIndex, taskIndex); + } + + protected UseBlockObjectiveTask() { + } + + @Nonnull + public UseBlockObjectiveTaskAsset getAsset() { + return (UseBlockObjectiveTaskAsset)super.getAsset(); + } + + @Nonnull + @Override + protected TransactionRecord[] setup0(@Nonnull Objective objective, @Nonnull World world, @Nonnull Store store) { + this.eventRegistry.register(LivingEntityUseBlockEvent.class, world.getName(), event -> { + BlockType blockType = BlockType.getAssetMap().getAsset(event.getBlockType()); + if (blockType != null) { + String baseItem = blockType.getItem().getId(); + if (this.getAsset().getBlockTagOrItemIdField().isBlockTypeIncluded(baseItem)) { + Ref entityRef = event.getRef(); + Store entityStore = entityRef.getStore(); + Player playerComponent = entityStore.getComponent(entityRef, Player.getComponentType()); + if (playerComponent != null) { + UUIDComponent uuidComponent = store.getComponent(entityRef, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + if (objective.getActivePlayerUUIDs().contains(uuidComponent.getUuid())) { + this.increaseTaskCompletion(store, entityRef, 1, objective); + } + } + } + } + }); + return RegistrationTransactionRecord.wrap(this.eventRegistry); + } + + @Nonnull + @Override + public String toString() { + return "UseBlockObjectiveTask{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/task/UseEntityObjectiveTask.java b/src/com/hypixel/hytale/builtin/adventure/objectives/task/UseEntityObjectiveTask.java new file mode 100644 index 0000000..a46b494 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/task/UseEntityObjectiveTask.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.builtin.adventure.objectives.task; + +import com.hypixel.hytale.builtin.adventure.objectives.DialogPage; +import com.hypixel.hytale.builtin.adventure.objectives.Objective; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectiveDataStore; +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.config.task.UseEntityObjectiveTaskAsset; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionRecord; +import com.hypixel.hytale.builtin.adventure.objectives.transaction.UseEntityTransactionRecord; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class UseEntityObjectiveTask extends CountObjectiveTask { + public static final BuilderCodec CODEC = BuilderCodec.builder( + UseEntityObjectiveTask.class, UseEntityObjectiveTask::new, CountObjectiveTask.CODEC + ) + .append(new KeyedCodec<>("NpcUUIDs", new ArrayCodec<>(Codec.UUID_BINARY, UUID[]::new)), (useEntityObjectiveTask, uuids) -> { + useEntityObjectiveTask.npcUUIDs.clear(); + Collections.addAll(useEntityObjectiveTask.npcUUIDs, uuids); + }, useEntityObjectiveTask -> useEntityObjectiveTask.npcUUIDs.toArray(UUID[]::new)) + .add() + .build(); + @Nonnull + protected Set npcUUIDs = new HashSet<>(); + + public UseEntityObjectiveTask(@Nonnull UseEntityObjectiveTaskAsset asset, int taskSetIndex, int taskIndex) { + super(asset, taskSetIndex, taskIndex); + } + + protected UseEntityObjectiveTask() { + } + + @Nonnull + public UseEntityObjectiveTaskAsset getAsset() { + return (UseEntityObjectiveTaskAsset)super.getAsset(); + } + + @Nonnull + @Override + protected TransactionRecord[] setup0(@Nonnull Objective objective, @Nonnull World world, @Nonnull Store store) { + UUID objectiveUUID = objective.getObjectiveUUID(); + ObjectiveDataStore objectiveDataStore = ObjectivePlugin.get().getObjectiveDataStore(); + String taskId = this.getAsset().getTaskId(); + + for (UUID playerUUID : objective.getActivePlayerUUIDs()) { + objectiveDataStore.addEntityTaskForPlayer(playerUUID, taskId, objectiveUUID); + } + + return TransactionRecord.appendTransaction(null, new UseEntityTransactionRecord(objectiveUUID, taskId)); + } + + public boolean increaseTaskCompletion( + @Nonnull Store store, @Nonnull Ref ref, int qty, @Nonnull Objective objective, @Nonnull PlayerRef playerRef, UUID npcUUID + ) { + if (!this.npcUUIDs.add(npcUUID)) { + playerRef.sendMessage(Message.translation("server.modules.objective.task.alreadyInteractedWithNPC")); + return false; + } else { + super.increaseTaskCompletion(store, ref, qty, objective); + if (this.isComplete()) { + UseEntityObjectiveTaskAsset.DialogOptions dialogOptions = this.getAsset().getDialogOptions(); + if (dialogOptions != null) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerRef.sendMessage( + Message.join(Message.translation(dialogOptions.getEntityNameKey()), Message.raw(": "), Message.translation(dialogOptions.getDialogKey())) + ); + playerComponent.getPageManager().openCustomPage(ref, store, new DialogPage(playerRef, dialogOptions)); + } + } + + return true; + } + } + + @Nonnull + @Override + public String toString() { + return "UseEntityObjectiveTask{npcUUIDs=" + this.npcUUIDs + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/RegistrationTransactionRecord.java b/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/RegistrationTransactionRecord.java new file mode 100644 index 0000000..42751d0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/RegistrationTransactionRecord.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.builtin.adventure.objectives.transaction; + +import com.hypixel.hytale.event.EventRegistry; +import com.hypixel.hytale.function.consumer.BooleanConsumer; +import javax.annotation.Nonnull; + +public class RegistrationTransactionRecord extends TransactionRecord { + protected BooleanConsumer registration; + + public RegistrationTransactionRecord(BooleanConsumer registration) { + this.registration = registration; + } + + @Override + public void revert() { + this.registration.accept(false); + } + + @Override + public void complete() { + this.registration.accept(false); + } + + @Override + public void unload() { + this.registration.accept(false); + } + + @Override + public boolean shouldBeSerialized() { + return false; + } + + @Nonnull + @Override + public String toString() { + return "RegistrationTransactionRecord{registration=" + this.registration + "} " + super.toString(); + } + + @Nonnull + public static TransactionRecord[] wrap(@Nonnull EventRegistry registry) { + BooleanConsumer[] registrations = registry.getRegistrations().toArray(BooleanConsumer[]::new); + TransactionRecord[] records = new TransactionRecord[registrations.length]; + int i = 0; + + for (BooleanConsumer registration : registrations) { + records[i++] = new RegistrationTransactionRecord(registration); + } + + return records; + } + + @Nonnull + public static TransactionRecord[] append(@Nonnull TransactionRecord[] arr, @Nonnull EventRegistry registry) { + BooleanConsumer[] registrations = registry.getRegistrations().toArray(BooleanConsumer[]::new); + TransactionRecord[] records = new TransactionRecord[arr.length + registrations.length]; + System.arraycopy(arr, 0, records, 0, arr.length); + int i = registrations.length; + + for (BooleanConsumer registration : registrations) { + records[i++] = new RegistrationTransactionRecord(registration); + } + + return records; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/SpawnEntityTransactionRecord.java b/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/SpawnEntityTransactionRecord.java new file mode 100644 index 0000000..81ab8b1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/SpawnEntityTransactionRecord.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.builtin.adventure.objectives.transaction; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class SpawnEntityTransactionRecord extends TransactionRecord { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SpawnEntityTransactionRecord.class, SpawnEntityTransactionRecord::new, BASE_CODEC + ) + .append( + new KeyedCodec<>("WorldUUID", Codec.UUID_BINARY), + (spawnEntityTransactionRecord, uuid) -> spawnEntityTransactionRecord.worldUUID = uuid, + spawnEntityTransactionRecord -> spawnEntityTransactionRecord.worldUUID + ) + .add() + .append( + new KeyedCodec<>("EntityUUID", Codec.UUID_BINARY), + (spawnEntityTransactionRecord, uuid) -> spawnEntityTransactionRecord.entityUUID = uuid, + spawnEntityTransactionRecord -> spawnEntityTransactionRecord.entityUUID + ) + .add() + .build(); + protected UUID worldUUID; + protected UUID entityUUID; + + public SpawnEntityTransactionRecord(@Nonnull UUID worldUUID, @Nonnull UUID entityUUID) { + this.worldUUID = worldUUID; + this.entityUUID = entityUUID; + } + + protected SpawnEntityTransactionRecord() { + } + + @Override + public void revert() { + this.removeEntity(); + } + + @Override + public void complete() { + this.removeEntity(); + } + + @Override + public void unload() { + } + + @Override + public boolean shouldBeSerialized() { + return true; + } + + private void removeEntity() { + World world = Universe.get().getWorld(this.worldUUID); + Entity entity = world.getEntity(this.entityUUID); + if (entity != null) { + if (!entity.remove()) { + throw new RuntimeException("Failed to revert record!"); + } + } + } + + @Nonnull + @Override + public String toString() { + return "SpawnEntityTransactionRecord{worldUUID=" + this.worldUUID + ", entityUUID=" + this.entityUUID + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/SpawnTreasureChestTransactionRecord.java b/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/SpawnTreasureChestTransactionRecord.java new file mode 100644 index 0000000..09f98a3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/SpawnTreasureChestTransactionRecord.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.builtin.adventure.objectives.transaction; + +import com.hypixel.hytale.builtin.adventure.objectives.blockstates.TreasureChestState; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class SpawnTreasureChestTransactionRecord extends TransactionRecord { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SpawnTreasureChestTransactionRecord.class, SpawnTreasureChestTransactionRecord::new, BASE_CODEC + ) + .append( + new KeyedCodec<>("WorldUUID", Codec.UUID_BINARY), + (spawnTreasureChestTransactionRecord, uuid) -> spawnTreasureChestTransactionRecord.worldUUID = uuid, + spawnTreasureChestTransactionRecord -> spawnTreasureChestTransactionRecord.worldUUID + ) + .add() + .append( + new KeyedCodec<>("BlockPosition", Vector3i.CODEC), + (spawnTreasureChestTransactionRecord, vector3d) -> spawnTreasureChestTransactionRecord.blockPosition = vector3d, + spawnTreasureChestTransactionRecord -> spawnTreasureChestTransactionRecord.blockPosition + ) + .add() + .build(); + protected UUID worldUUID; + protected Vector3i blockPosition; + + public SpawnTreasureChestTransactionRecord(UUID worldUUID, Vector3i blockPosition) { + this.worldUUID = worldUUID; + this.blockPosition = blockPosition; + } + + protected SpawnTreasureChestTransactionRecord() { + } + + @Override + public void revert() { + World world = Universe.get().getWorld(this.worldUUID); + if (world != null) { + WorldChunk worldChunk = world.getChunk(ChunkUtil.indexChunkFromBlock(this.blockPosition.x, this.blockPosition.z)); + BlockState blockState = worldChunk.getState(this.blockPosition.x, this.blockPosition.y, this.blockPosition.z); + if (blockState instanceof TreasureChestState) { + ((TreasureChestState)blockState).setOpened(true); + } + } + } + + @Override + public void complete() { + } + + @Override + public void unload() { + } + + @Override + public boolean shouldBeSerialized() { + return true; + } + + @Nonnull + @Override + public String toString() { + return "SpawnTreasureChestTransactionRecord{worldUUID=" + this.worldUUID + ", blockPosition=" + this.blockPosition + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/TransactionRecord.java b/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/TransactionRecord.java new file mode 100644 index 0000000..714ae39 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/TransactionRecord.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.builtin.adventure.objectives.transaction; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class TransactionRecord { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(TransactionRecord.class) + .append( + new KeyedCodec<>("Status", new EnumCodec<>(TransactionStatus.class, EnumCodec.EnumStyle.LEGACY)), + (spawnEntityTransactionRecord, status) -> spawnEntityTransactionRecord.status = status, + spawnEntityTransactionRecord -> spawnEntityTransactionRecord.status + ) + .add() + .build(); + protected TransactionStatus status = TransactionStatus.SUCCESS; + private String reason; + + public TransactionRecord() { + } + + public TransactionStatus getStatus() { + return this.status; + } + + public abstract void revert(); + + public abstract void complete(); + + public abstract void unload(); + + public abstract boolean shouldBeSerialized(); + + @Nonnull + public TransactionRecord fail(String reason) { + this.status = TransactionStatus.FAIL; + this.reason = reason; + return this; + } + + @Nonnull + @Override + public String toString() { + return "TransactionRecord{status=" + this.status + ", reason='" + this.reason + "'}"; + } + + @Nonnull + public static TransactionRecord[] appendTransaction(@Nullable TransactionRecord[] transactions, @Nonnull T transaction) { + return transactions == null ? new TransactionRecord[]{transaction} : ArrayUtil.append(transactions, transaction); + } + + @Nonnull + public static TransactionRecord[] appendFailedTransaction( + TransactionRecord[] transactions, @Nonnull T transaction, String reason + ) { + return appendTransaction(transactions, transaction.fail(reason)); + } + + static { + CODEC.register("SpawnEntity", SpawnEntityTransactionRecord.class, SpawnEntityTransactionRecord.CODEC); + CODEC.register("SpawnBlock", SpawnTreasureChestTransactionRecord.class, SpawnTreasureChestTransactionRecord.CODEC); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/TransactionStatus.java b/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/TransactionStatus.java new file mode 100644 index 0000000..a2cac3b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/TransactionStatus.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.builtin.adventure.objectives.transaction; + +enum TransactionStatus { + SUCCESS, + FAIL; + + private TransactionStatus() { + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/TransactionUtil.java b/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/TransactionUtil.java new file mode 100644 index 0000000..b44cdad --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/TransactionUtil.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.adventure.objectives.transaction; + +import javax.annotation.Nullable; + +public class TransactionUtil { + public TransactionUtil() { + } + + public static boolean anyFailed(@Nullable TransactionRecord[] transactionRecords) { + if (transactionRecords == null) { + return false; + } else { + for (TransactionRecord transactionRecord : transactionRecords) { + if (transactionRecord.status == TransactionStatus.FAIL) { + return true; + } + } + + return false; + } + } + + public static void revertAll(@Nullable TransactionRecord[] transactionRecords) { + if (transactionRecords != null) { + for (TransactionRecord transactionRecord : transactionRecords) { + transactionRecord.revert(); + } + } + } + + public static void completeAll(@Nullable TransactionRecord[] transactionRecords) { + if (transactionRecords != null) { + for (TransactionRecord transactionRecord : transactionRecords) { + transactionRecord.complete(); + } + } + } + + public static void unloadAll(@Nullable TransactionRecord[] transactionRecords) { + if (transactionRecords != null) { + for (TransactionRecord transactionRecord : transactionRecords) { + transactionRecord.unload(); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/UseEntityTransactionRecord.java b/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/UseEntityTransactionRecord.java new file mode 100644 index 0000000..86159ab --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/UseEntityTransactionRecord.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.adventure.objectives.transaction; + +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class UseEntityTransactionRecord extends TransactionRecord { + protected UUID objectiveUUID; + protected String taskId; + + public UseEntityTransactionRecord(UUID objectiveUUID, String taskId) { + this.objectiveUUID = objectiveUUID; + this.taskId = taskId; + } + + @Override + public void revert() { + ObjectivePlugin.get().getObjectiveDataStore().removeEntityTask(this.objectiveUUID, this.taskId); + } + + @Override + public void complete() { + ObjectivePlugin.get().getObjectiveDataStore().removeEntityTask(this.objectiveUUID, this.taskId); + } + + @Override + public void unload() { + ObjectivePlugin.get().getObjectiveDataStore().removeEntityTask(this.objectiveUUID, this.taskId); + } + + @Override + public boolean shouldBeSerialized() { + return false; + } + + @Nonnull + @Override + public String toString() { + return "UseEntityTransactionRecord{objectiveUUID=" + this.objectiveUUID + ", taskId='" + this.taskId + "'} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/WorldTransactionRecord.java b/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/WorldTransactionRecord.java new file mode 100644 index 0000000..4b3c306 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectives/transaction/WorldTransactionRecord.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.builtin.adventure.objectives.transaction; + +import javax.annotation.Nonnull; + +public class WorldTransactionRecord extends TransactionRecord { + public WorldTransactionRecord() { + } + + @Override + public void revert() { + } + + @Override + public void complete() { + } + + @Override + public void unload() { + } + + @Override + public boolean shouldBeSerialized() { + return false; + } + + @Nonnull + @Override + public String toString() { + return "WorldTransactionRecord{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectiveshop/CanStartObjectiveRequirement.java b/src/com/hypixel/hytale/builtin/adventure/objectiveshop/CanStartObjectiveRequirement.java new file mode 100644 index 0000000..dc96f79 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectiveshop/CanStartObjectiveRequirement.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.adventure.objectiveshop; + +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceRequirement; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class CanStartObjectiveRequirement extends ChoiceRequirement { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CanStartObjectiveRequirement.class, CanStartObjectiveRequirement::new, ChoiceRequirement.BASE_CODEC + ) + .append( + new KeyedCodec<>("ObjectiveId", Codec.STRING), + (canStartObjectiveRequirement, s) -> canStartObjectiveRequirement.objectiveId = s, + canStartObjectiveRequirement -> canStartObjectiveRequirement.objectiveId + ) + .add() + .build(); + protected String objectiveId; + + public CanStartObjectiveRequirement(String objectiveId) { + this.objectiveId = objectiveId; + } + + protected CanStartObjectiveRequirement() { + } + + @Override + public boolean canFulfillRequirement(@Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + return playerComponent == null ? false : ObjectivePlugin.get().canPlayerDoObjective(playerComponent, this.objectiveId); + } + + @Nonnull + @Override + public String toString() { + return "CanStartObjectiveRequirement{objectiveId='" + this.objectiveId + "'} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectiveshop/ObjectiveShopPlugin.java b/src/com/hypixel/hytale/builtin/adventure/objectiveshop/ObjectiveShopPlugin.java new file mode 100644 index 0000000..ac0bd64 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectiveshop/ObjectiveShopPlugin.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.builtin.adventure.objectiveshop; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveAsset; +import com.hypixel.hytale.builtin.adventure.shop.ShopAsset; +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceInteraction; +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceRequirement; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import javax.annotation.Nonnull; + +public class ObjectiveShopPlugin extends JavaPlugin { + protected static ObjectiveShopPlugin instance; + + public static ObjectiveShopPlugin get() { + return instance; + } + + public ObjectiveShopPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + instance = this; + ChoiceInteraction.CODEC.register("StartObjective", StartObjectiveInteraction.class, StartObjectiveInteraction.CODEC); + ChoiceRequirement.CODEC.register("CanStartObjective", CanStartObjectiveRequirement.class, CanStartObjectiveRequirement.CODEC); + AssetRegistry.getAssetStore(ShopAsset.class).injectLoadsAfter(ObjectiveAsset.class); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/objectiveshop/StartObjectiveInteraction.java b/src/com/hypixel/hytale/builtin/adventure/objectiveshop/StartObjectiveInteraction.java new file mode 100644 index 0000000..4cc896a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/objectiveshop/StartObjectiveInteraction.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.builtin.adventure.objectiveshop; + +import com.hypixel.hytale.builtin.adventure.objectives.ObjectivePlugin; +import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveAsset; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.HashSet; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class StartObjectiveInteraction extends ChoiceInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + StartObjectiveInteraction.class, StartObjectiveInteraction::new, ChoiceInteraction.BASE_CODEC + ) + .append( + new KeyedCodec<>("ObjectiveId", Codec.STRING), + (startObjectiveInteraction, s) -> startObjectiveInteraction.objectiveId = s, + startObjectiveInteraction -> startObjectiveInteraction.objectiveId + ) + .addValidator(ObjectiveAsset.VALIDATOR_CACHE.getValidator()) + .add() + .build(); + protected String objectiveId; + + public StartObjectiveInteraction(String objectiveId) { + this.objectiveId = objectiveId; + } + + protected StartObjectiveInteraction() { + } + + public String getObjectiveId() { + return this.objectiveId; + } + + @Override + public void run(@Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef) { + HashSet playerSet = new HashSet<>(); + playerSet.add(playerRef.getUuid()); + World world = store.getExternalData().getWorld(); + ObjectivePlugin.get().startObjective(this.objectiveId, playerSet, world.getWorldConfig().getUuid(), null, store); + } + + @Nonnull + @Override + public String toString() { + return "StartObjectiveInteraction{objectiveId='" + this.objectiveId + "'} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/reputation/ReputationGameplayConfig.java b/src/com/hypixel/hytale/builtin/adventure/reputation/ReputationGameplayConfig.java new file mode 100644 index 0000000..687dd03 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/reputation/ReputationGameplayConfig.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.builtin.adventure.reputation; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ReputationGameplayConfig { + public static final String ID = "Reputation"; + public static final BuilderCodec CODEC = BuilderCodec.builder(ReputationGameplayConfig.class, ReputationGameplayConfig::new) + .appendInherited( + new KeyedCodec<>("ReputationStorage", new EnumCodec<>(ReputationGameplayConfig.ReputationStorageType.class)), + (gameplayConfig, o) -> gameplayConfig.reputationStorageType = o, + gameplayConfig -> gameplayConfig.reputationStorageType, + (gameplayConfig, parent) -> gameplayConfig.reputationStorageType = parent.reputationStorageType + ) + .add() + .build(); + private static final ReputationGameplayConfig DEFAULT_REPUTATION_GAMEPLAY_CONFIG = new ReputationGameplayConfig(); + @Nonnull + protected ReputationGameplayConfig.ReputationStorageType reputationStorageType = ReputationGameplayConfig.ReputationStorageType.PerPlayer; + + public ReputationGameplayConfig() { + } + + @Nullable + public static ReputationGameplayConfig get(@Nonnull GameplayConfig config) { + return config.getPluginConfig().get(ReputationGameplayConfig.class); + } + + public static ReputationGameplayConfig getOrDefault(@Nonnull GameplayConfig config) { + ReputationGameplayConfig reputationGameplayConfig = get(config); + return reputationGameplayConfig != null ? reputationGameplayConfig : DEFAULT_REPUTATION_GAMEPLAY_CONFIG; + } + + @Nonnull + public ReputationGameplayConfig.ReputationStorageType getReputationStorageType() { + return this.reputationStorageType; + } + + @Nonnull + @Override + public String toString() { + return "ReputationGameplayConfig{reputationStorageType=" + this.reputationStorageType + "}"; + } + + public static enum ReputationStorageType { + PerPlayer, + PerWorld; + + private ReputationStorageType() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/reputation/ReputationGroupComponent.java b/src/com/hypixel/hytale/builtin/adventure/reputation/ReputationGroupComponent.java new file mode 100644 index 0000000..6a61645 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/reputation/ReputationGroupComponent.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.builtin.adventure.reputation; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ReputationGroupComponent implements Component { + @Nonnull + private final String reputationGroupId; + + @Nonnull + public static ComponentType getComponentType() { + return ReputationPlugin.get().getReputationGroupComponentType(); + } + + public ReputationGroupComponent(@Nonnull String reputationGroupId) { + this.reputationGroupId = reputationGroupId; + } + + @Nonnull + public String getReputationGroupId() { + return this.reputationGroupId; + } + + @Nonnull + @Override + public Component clone() { + return new ReputationGroupComponent(this.reputationGroupId); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/reputation/ReputationPlugin.java b/src/com/hypixel/hytale/builtin/adventure/reputation/ReputationPlugin.java new file mode 100644 index 0000000..dc1402a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/reputation/ReputationPlugin.java @@ -0,0 +1,272 @@ +package com.hypixel.hytale.builtin.adventure.reputation; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.builtin.adventure.reputation.assets.ReputationGroup; +import com.hypixel.hytale.builtin.adventure.reputation.assets.ReputationRank; +import com.hypixel.hytale.builtin.adventure.reputation.choices.ReputationRequirement; +import com.hypixel.hytale.builtin.adventure.reputation.command.ReputationCommand; +import com.hypixel.hytale.builtin.adventure.reputation.store.ReputationDataResource; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData; +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceRequirement; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ReputationPlugin extends JavaPlugin { + private static ReputationPlugin instance; + private ComponentType reputationGroupComponentType; + private ResourceType reputationDataResourceType; + private List reputationRanks; + private int maxReputationValue = Integer.MIN_VALUE; + private int minReputationValue = Integer.MAX_VALUE; + public static final int NO_REPUTATION_GROUP = Integer.MIN_VALUE; + + public static ReputationPlugin get() { + return instance; + } + + public ReputationPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + public ComponentType getReputationGroupComponentType() { + return this.reputationGroupComponentType; + } + + @Override + protected void setup() { + instance = this; + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(ReputationRank.class, new DefaultAssetMap()) + .setPath("NPC/Reputation/Ranks")) + .setCodec(ReputationRank.CODEC)) + .setKeyFunction(ReputationRank::getId)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(ReputationGroup.class, new DefaultAssetMap()) + .setPath("NPC/Reputation/Groups")) + .setCodec(ReputationGroup.CODEC)) + .setKeyFunction(ReputationGroup::getId)) + .build() + ); + this.getCommandRegistry().registerCommand(new ReputationCommand()); + ChoiceRequirement.CODEC.register("Reputation", ReputationRequirement.class, ReputationRequirement.CODEC); + this.reputationDataResourceType = this.getEntityStoreRegistry() + .registerResource(ReputationDataResource.class, "ReputationData", ReputationDataResource.CODEC); + this.reputationGroupComponentType = this.getEntityStoreRegistry().registerComponent(ReputationGroupComponent.class, () -> { + throw new UnsupportedOperationException("Not implemented!"); + }); + GameplayConfig.PLUGIN_CODEC.register(ReputationGameplayConfig.class, "Reputation", ReputationGameplayConfig.CODEC); + } + + @Override + protected void start() { + this.reputationRanks = new ObjectArrayList<>(ReputationRank.getAssetMap().getAssetMap().values()); + if (this.reputationRanks.size() > 1) { + this.reputationRanks.sort(Comparator.comparingInt(ReputationRank::getMinValue)); + int previousMaxValue = this.reputationRanks.getFirst().getMaxValue(); + + for (int i = 1; i < this.reputationRanks.size(); i++) { + ReputationRank reputationRank = this.reputationRanks.get(i); + if (previousMaxValue < reputationRank.getMinValue()) { + this.getLogger() + .at(Level.WARNING) + .log( + "There is a gap between the values of the ReputationRank %s and %s, please review the assets.", + reputationRank.getId(), + this.reputationRanks.get(i - 1).getId() + ); + } + + if (previousMaxValue > reputationRank.getMinValue()) { + this.getLogger() + .at(Level.WARNING) + .log( + "Min value of rank %s is already contained in rank %s, please review the asset.", + reputationRank.getId(), + this.reputationRanks.get(i - 1).getId() + ); + } + + previousMaxValue = reputationRank.getMaxValue(); + } + + this.minReputationValue = this.reputationRanks.getFirst().getMinValue(); + this.maxReputationValue = this.reputationRanks.getLast().getMaxValue(); + } + } + + public int changeReputation(@Nonnull Player player, @Nonnull Ref npcRef, int value, @Nonnull ComponentAccessor componentAccessor) { + ReputationGroupComponent reputationGroupComponent = componentAccessor.getComponent(npcRef, this.reputationGroupComponentType); + return reputationGroupComponent == null + ? Integer.MIN_VALUE + : this.changeReputation(player, reputationGroupComponent.getReputationGroupId(), value, componentAccessor); + } + + public int changeReputation(@Nonnull Player player, @Nonnull String reputationGroupId, int value, @Nonnull ComponentAccessor componentAccessor) { + World world = componentAccessor.getExternalData().getWorld(); + ReputationGameplayConfig reputationGameplayConfig = ReputationGameplayConfig.getOrDefault(world.getGameplayConfig()); + if (reputationGameplayConfig.getReputationStorageType() == ReputationGameplayConfig.ReputationStorageType.PerPlayer) { + ReputationGroup reputationGroup = ReputationGroup.getAssetMap().getAsset(reputationGroupId); + if (reputationGroup == null) { + return Integer.MIN_VALUE; + } else { + PlayerConfigData playerConfigData = player.getPlayerConfigData(); + Object2IntOpenHashMap reputationData = new Object2IntOpenHashMap<>(playerConfigData.getReputationData()); + int newReputationValue = this.computeReputation(reputationData, reputationGroup, value); + playerConfigData.setReputationData(reputationData); + return newReputationValue; + } + } else { + return this.changeReputation(world, reputationGroupId, value); + } + } + + public int changeReputation(@Nonnull World world, @Nonnull String reputationGroupId, int value) { + ReputationGameplayConfig reputationGameplayConfig = ReputationGameplayConfig.getOrDefault(world.getGameplayConfig()); + if (reputationGameplayConfig.getReputationStorageType() != ReputationGameplayConfig.ReputationStorageType.PerWorld) { + return -1; + } else { + ReputationGroup reputationGroup = ReputationGroup.getAssetMap().getAsset(reputationGroupId); + if (reputationGroup == null) { + return Integer.MIN_VALUE; + } else { + ReputationDataResource reputationDataResource = world.getEntityStore().getStore().getResource(this.reputationDataResourceType); + return this.computeReputation(reputationDataResource.getReputationStats(), reputationGroup, value); + } + } + } + + private int computeReputation(@Nonnull Object2IntMap reputationData, @Nonnull ReputationGroup reputationGroup, int value) { + return reputationData.compute(reputationGroup.getId(), (k, oldValue) -> { + int newValue = oldValue == null ? reputationGroup.getInitialReputationValue() + value : oldValue + value; + return MathUtil.clamp(newValue, this.minReputationValue, this.maxReputationValue - 1); + }); + } + + public int getReputationValue(@Nonnull Store store, @Nonnull Ref playerEntityRef, @Nonnull Ref npcEntityRef) { + ReputationGroupComponent reputationGroupComponent = store.getComponent(npcEntityRef, this.reputationGroupComponentType); + return reputationGroupComponent == null + ? Integer.MIN_VALUE + : this.getReputationValue(store, playerEntityRef, reputationGroupComponent.getReputationGroupId()); + } + + public int getReputationValue(@Nonnull Store store, @Nonnull Ref playerEntityRef, @Nonnull String reputationGroupId) { + World world = store.getExternalData().getWorld(); + Player playerComponent = store.getComponent(playerEntityRef, Player.getComponentType()); + ReputationGameplayConfig reputationGameplayConfig = ReputationGameplayConfig.getOrDefault(world.getGameplayConfig()); + if (reputationGameplayConfig.getReputationStorageType() == ReputationGameplayConfig.ReputationStorageType.PerPlayer) { + ReputationGroup reputationGroup = ReputationGroup.getAssetMap().getAsset(reputationGroupId); + if (reputationGroup != null) { + Object2IntMap reputationData = playerComponent.getPlayerConfigData().getReputationData(); + return this.getReputationValueForGroup(reputationData, reputationGroup); + } else { + return Integer.MIN_VALUE; + } + } else { + return this.getReputationValue(store, reputationGroupId); + } + } + + public int getReputationValue(@Nonnull Store store, @Nonnull Ref npcRef) { + String reputationGroupId = store.getComponent(npcRef, this.reputationGroupComponentType).getReputationGroupId(); + return this.getReputationValue(store, reputationGroupId); + } + + public int getReputationValue(@Nonnull Store store, String reputationGroupId) { + World world = store.getExternalData().getWorld(); + ReputationGameplayConfig reputationGameplayConfig = ReputationGameplayConfig.getOrDefault(world.getGameplayConfig()); + if (reputationGameplayConfig.getReputationStorageType() != ReputationGameplayConfig.ReputationStorageType.PerWorld) { + return Integer.MIN_VALUE; + } else { + ReputationGroup reputationGroup = ReputationGroup.getAssetMap().getAsset(reputationGroupId); + if (reputationGroup == null) { + return Integer.MIN_VALUE; + } else { + Object2IntMap reputationData = world.getEntityStore().getStore().getResource(this.reputationDataResourceType).getReputationStats(); + return this.getReputationValueForGroup(reputationData, reputationGroup); + } + } + } + + private int getReputationValueForGroup(@Nonnull Object2IntMap reputationData, @Nonnull ReputationGroup reputationGroup) { + return reputationData.getOrDefault(reputationGroup.getId(), reputationGroup.getInitialReputationValue()); + } + + @Nullable + public ReputationRank getReputationRank(@Nonnull Store store, @Nonnull Ref ref, @Nonnull Ref npcRef) { + ReputationGroupComponent reputationGroupComponent = store.getComponent(npcRef, this.reputationGroupComponentType); + if (reputationGroupComponent == null) { + return null; + } else { + String reputationGroupId = reputationGroupComponent.getReputationGroupId(); + return this.getReputationRank(store, ref, reputationGroupId); + } + } + + @Nullable + public ReputationRank getReputationRank(@Nonnull Store store, @Nonnull Ref ref, @Nonnull String reputationGroupId) { + int value = this.getReputationValue(store, ref, reputationGroupId); + return this.getReputationRankFromValue(value); + } + + @Nullable + public ReputationRank getReputationRankFromValue(int value) { + if (value == Integer.MIN_VALUE) { + return null; + } else { + for (int i = 0; i < this.reputationRanks.size(); i++) { + if (this.reputationRanks.get(i).containsValue(value)) { + return this.reputationRanks.get(i); + } + } + + return null; + } + } + + @Nullable + public ReputationRank getReputationRank(@Nonnull Store store, @Nonnull Ref npcRef) { + World world = store.getExternalData().getWorld(); + ReputationGameplayConfig reputationGameplayConfig = ReputationGameplayConfig.getOrDefault(world.getGameplayConfig()); + if (reputationGameplayConfig.getReputationStorageType() != ReputationGameplayConfig.ReputationStorageType.PerWorld) { + return null; + } else { + int value = this.getReputationValue(store, npcRef); + return this.getReputationRankFromValue(value); + } + } + + @Nullable + public Attitude getAttitude(@Nonnull Store store, @Nonnull Ref ref, @Nonnull Ref npc) { + ReputationRank reputationRank = this.getReputationRank(store, ref, npc); + return reputationRank != null ? reputationRank.getAttitude() : null; + } + + @Nullable + public Attitude getAttitude(@Nonnull Store store, @Nonnull Ref npcRef) { + ReputationRank reputationRank = this.getReputationRank(store, npcRef); + return reputationRank != null ? reputationRank.getAttitude() : null; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/reputation/assets/ReputationGroup.java b/src/com/hypixel/hytale/builtin/adventure/reputation/assets/ReputationGroup.java new file mode 100644 index 0000000..90b8fab --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/reputation/assets/ReputationGroup.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.builtin.adventure.reputation.assets; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import javax.annotation.Nonnull; + +public class ReputationGroup implements JsonAssetWithMap> { + @Nonnull + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ReputationGroup.class, ReputationGroup::new, Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ) + .addField( + new KeyedCodec<>("NPCGroups", Codec.STRING_ARRAY), (reputationRank, s) -> reputationRank.npcGroups = s, reputationRank -> reputationRank.npcGroups + ) + .addField( + new KeyedCodec<>("InitialReputationValue", Codec.INTEGER), + (reputationRank, s) -> reputationRank.initialReputationValue = s, + reputationRank -> reputationRank.initialReputationValue + ) + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ReputationGroup::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected String[] npcGroups; + protected int initialReputationValue; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ReputationGroup.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public ReputationGroup(String id, String[] npcGroups, int initialReputationValue) { + this.id = id; + this.npcGroups = npcGroups; + this.initialReputationValue = initialReputationValue; + } + + protected ReputationGroup() { + } + + public String getId() { + return this.id; + } + + public String[] getNpcGroups() { + return this.npcGroups; + } + + public int getInitialReputationValue() { + return this.initialReputationValue; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/reputation/assets/ReputationRank.java b/src/com/hypixel/hytale/builtin/adventure/reputation/assets/ReputationRank.java new file mode 100644 index 0000000..c990e34 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/reputation/assets/ReputationRank.java @@ -0,0 +1,87 @@ +package com.hypixel.hytale.builtin.adventure.reputation.assets; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import javax.annotation.Nonnull; + +public class ReputationRank implements JsonAssetWithMap> { + @Nonnull + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ReputationRank.class, ReputationRank::new, Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ) + .addField(new KeyedCodec<>("MinValue", Codec.INTEGER), (reputationRank, s) -> reputationRank.minValue = s, reputationRank -> reputationRank.minValue) + .addField(new KeyedCodec<>("MaxValue", Codec.INTEGER), (reputationRank, s) -> reputationRank.maxValue = s, reputationRank -> reputationRank.maxValue) + .addField( + new KeyedCodec<>("Attitude", Attitude.CODEC, true), (reputationRank, s) -> reputationRank.attitude = s, reputationRank -> reputationRank.attitude + ) + .validator((asset, results) -> { + if (asset.getMinValue() >= asset.getMaxValue()) { + results.fail("Min value must be strictly inferior than the max value"); + } + }) + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ReputationRank::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected int minValue; + protected int maxValue; + protected Attitude attitude; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ReputationRank.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public ReputationRank(String id, int minValue, int maxValue, Attitude attitude) { + this.id = id; + this.minValue = minValue; + this.maxValue = maxValue; + this.attitude = attitude; + } + + protected ReputationRank() { + } + + public String getId() { + return this.id; + } + + public int getMinValue() { + return this.minValue; + } + + public int getMaxValue() { + return this.maxValue; + } + + public Attitude getAttitude() { + return this.attitude; + } + + public boolean containsValue(int value) { + return value >= this.minValue && value < this.maxValue; + } + + @Nonnull + @Override + public String toString() { + return "ReputationRank{id='" + this.id + "', minValue=" + this.minValue + ", maxValue=" + this.maxValue + ", attitude=" + this.attitude + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/reputation/choices/ReputationRequirement.java b/src/com/hypixel/hytale/builtin/adventure/reputation/choices/ReputationRequirement.java new file mode 100644 index 0000000..39957ec --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/reputation/choices/ReputationRequirement.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.builtin.adventure.reputation.choices; + +import com.hypixel.hytale.builtin.adventure.reputation.ReputationPlugin; +import com.hypixel.hytale.builtin.adventure.reputation.assets.ReputationGroup; +import com.hypixel.hytale.builtin.adventure.reputation.assets.ReputationRank; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceRequirement; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ReputationRequirement extends ChoiceRequirement { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ReputationRequirement.class, ReputationRequirement::new, ChoiceRequirement.BASE_CODEC + ) + .append( + new KeyedCodec<>("ReputationGroupId", Codec.STRING), + (reputationRequirement, s) -> reputationRequirement.reputationGroupId = s, + reputationRequirement -> reputationRequirement.reputationGroupId + ) + .addValidator(ReputationGroup.VALIDATOR_CACHE.getValidator()) + .add() + .append( + new KeyedCodec<>("MinRequiredRankId", Codec.STRING), + (reputationRequirement, s) -> reputationRequirement.minRequiredRankId = s, + reputationRequirement -> reputationRequirement.minRequiredRankId + ) + .addValidator(ReputationRank.VALIDATOR_CACHE.getValidator()) + .add() + .build(); + protected String reputationGroupId; + protected String minRequiredRankId; + + public ReputationRequirement(String reputationGroupId, String minRequiredRankId) { + this.reputationGroupId = reputationGroupId; + this.minRequiredRankId = minRequiredRankId; + } + + protected ReputationRequirement() { + } + + @Override + public boolean canFulfillRequirement(@Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef) { + ReputationPlugin reputationModule = ReputationPlugin.get(); + int playerReputationValue = reputationModule.getReputationValue(store, ref, this.reputationGroupId); + if (playerReputationValue == Integer.MIN_VALUE) { + return false; + } else { + ReputationRank minReputationRank = ReputationRank.getAssetMap().getAsset(this.minRequiredRankId); + return minReputationRank == null ? false : playerReputationValue >= minReputationRank.getMinValue(); + } + } + + @Nonnull + @Override + public String toString() { + return "ReputationRequirement{reputationGroupId='" + + this.reputationGroupId + + "', minRequiredRankId='" + + this.minRequiredRankId + + "'} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/reputation/command/ReputationAddCommand.java b/src/com/hypixel/hytale/builtin/adventure/reputation/command/ReputationAddCommand.java new file mode 100644 index 0000000..45d7018 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/reputation/command/ReputationAddCommand.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.builtin.adventure.reputation.command; + +import com.hypixel.hytale.builtin.adventure.reputation.ReputationPlugin; +import com.hypixel.hytale.builtin.adventure.reputation.assets.ReputationGroup; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.AssetArgumentType; +import com.hypixel.hytale.server.core.command.system.arguments.types.SingleArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ReputationAddCommand extends AbstractTargetPlayerCommand { + @Nonnull + private static final SingleArgumentType REPUTATION_GROUP_ARG_TYPE = new AssetArgumentType( + "server.commands.parsing.argtype.asset.reputationgroup.name", ReputationGroup.class, "server.commands.parsing.argtype.asset.reputationgroup.usage" + ); + @Nonnull + private final RequiredArg reputationGroupIdArg = this.withRequiredArg( + "reputationGroupId", "server.commands.reputation.add.reputationGroupId.desc", REPUTATION_GROUP_ARG_TYPE + ); + @Nonnull + private final RequiredArg valueArg = this.withRequiredArg("value", "server.commands.reputation.add.value.desc", ArgTypes.INTEGER); + + public ReputationAddCommand() { + super("add", "server.commands.reputation.add.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + ReputationGroup reputationGroup = this.reputationGroupIdArg.get(context); + int value = this.valueArg.get(context); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + int newReputationAmount = ReputationPlugin.get().changeReputation(playerComponent, reputationGroup.getId(), value, store); + context.sendMessage(Message.translation("server.modules.reputation.success").param("id", reputationGroup.getId()).param("value", newReputationAmount)); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/reputation/command/ReputationCommand.java b/src/com/hypixel/hytale/builtin/adventure/reputation/command/ReputationCommand.java new file mode 100644 index 0000000..6bb4771 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/reputation/command/ReputationCommand.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.builtin.adventure.reputation.command; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class ReputationCommand extends AbstractCommandCollection { + public ReputationCommand() { + super("reputation", "server.commands.reputation.desc"); + this.addSubCommand(new ReputationAddCommand()); + this.addSubCommand(new ReputationSetCommand()); + this.addSubCommand(new ReputationRankCommand()); + this.addSubCommand(new ReputationValueCommand()); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/reputation/command/ReputationRankCommand.java b/src/com/hypixel/hytale/builtin/adventure/reputation/command/ReputationRankCommand.java new file mode 100644 index 0000000..0692491 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/reputation/command/ReputationRankCommand.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.builtin.adventure.reputation.command; + +import com.hypixel.hytale.builtin.adventure.reputation.ReputationPlugin; +import com.hypixel.hytale.builtin.adventure.reputation.assets.ReputationGroup; +import com.hypixel.hytale.builtin.adventure.reputation.assets.ReputationRank; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.AssetArgumentType; +import com.hypixel.hytale.server.core.command.system.arguments.types.SingleArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ReputationRankCommand extends AbstractTargetPlayerCommand { + @Nonnull + private static final SingleArgumentType REPUTATION_GROUP_ARG_TYPE = new AssetArgumentType( + "server.commands.parsing.argtype.asset.reputationgroup.name", ReputationGroup.class, "server.commands.parsing.argtype.asset.reputationgroup.usage" + ); + @Nonnull + private final RequiredArg reputationGroupIdArg = this.withRequiredArg( + "reputationGroupId", "server.commands.reputation.check.rank.reputationGroupId.desc", REPUTATION_GROUP_ARG_TYPE + ); + + public ReputationRankCommand() { + super("rank", "server.commands.reputation.check.rank.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + ReputationGroup reputationGroup = this.reputationGroupIdArg.get(context); + ReputationRank rank = ReputationPlugin.get().getReputationRank(store, ref, reputationGroup.getId()); + if (rank != null) { + context.sendMessage( + Message.translation("server.modules.reputation.valueForGroup").param("id", reputationGroup.getId()).param("value", rank.toString()) + ); + } else { + context.sendMessage( + Message.translation("server.modules.reputation.noRankFoundForValue") + .param("value", ReputationPlugin.get().getReputationValue(store, ref, reputationGroup.getId())) + ); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/reputation/command/ReputationSetCommand.java b/src/com/hypixel/hytale/builtin/adventure/reputation/command/ReputationSetCommand.java new file mode 100644 index 0000000..bddfab1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/reputation/command/ReputationSetCommand.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.builtin.adventure.reputation.command; + +import com.hypixel.hytale.builtin.adventure.reputation.ReputationPlugin; +import com.hypixel.hytale.builtin.adventure.reputation.assets.ReputationGroup; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.AssetArgumentType; +import com.hypixel.hytale.server.core.command.system.arguments.types.SingleArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ReputationSetCommand extends AbstractTargetPlayerCommand { + @Nonnull + private static final SingleArgumentType REPUTATION_GROUP_ARG_TYPE = new AssetArgumentType( + "server.commands.parsing.argtype.asset.reputationgroup.name", ReputationGroup.class, "server.commands.parsing.argtype.asset.reputationgroup.usage" + ); + @Nonnull + private final RequiredArg reputationGroupIdArg = this.withRequiredArg( + "reputationGroupId", "server.commands.reputation.set.reputationGroupId.desc", REPUTATION_GROUP_ARG_TYPE + ); + @Nonnull + private final RequiredArg valueArg = this.withRequiredArg("value", "server.commands.reputation.set.value.desc", ArgTypes.INTEGER); + + public ReputationSetCommand() { + super("set", "server.commands.reputation.set.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + ReputationGroup reputationGroup = this.reputationGroupIdArg.get(context); + int value = this.valueArg.get(context); + Player player = store.getComponent(ref, Player.getComponentType()); + + assert player != null; + + int currentValue = ReputationPlugin.get().getReputationValue(store, ref, reputationGroup.getId()); + int newReputationAmount = ReputationPlugin.get().changeReputation(player, reputationGroup.getId(), value - currentValue, store); + context.sendMessage(Message.translation("server.modules.reputation.success").param("id", reputationGroup.getId()).param("value", newReputationAmount)); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/reputation/command/ReputationValueCommand.java b/src/com/hypixel/hytale/builtin/adventure/reputation/command/ReputationValueCommand.java new file mode 100644 index 0000000..e6e629f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/reputation/command/ReputationValueCommand.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.builtin.adventure.reputation.command; + +import com.hypixel.hytale.builtin.adventure.reputation.ReputationPlugin; +import com.hypixel.hytale.builtin.adventure.reputation.assets.ReputationGroup; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.AssetArgumentType; +import com.hypixel.hytale.server.core.command.system.arguments.types.SingleArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ReputationValueCommand extends AbstractTargetPlayerCommand { + @Nonnull + private static final SingleArgumentType REPUTATION_GROUP_ARG_TYPE = new AssetArgumentType( + "server.commands.parsing.argtype.asset.reputationgroup.name", ReputationGroup.class, "server.commands.parsing.argtype.asset.reputationgroup.usage" + ); + @Nonnull + private final RequiredArg reputationGroupIdArg = this.withRequiredArg( + "reputationGroupId", "server.commands.reputation.check.value.reputationGroupId.desc", REPUTATION_GROUP_ARG_TYPE + ); + + public ReputationValueCommand() { + super("value", "server.commands.reputation.check.value.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + ReputationGroup reputationGroup = this.reputationGroupIdArg.get(context); + context.sendMessage( + Message.translation("server.modules.reputation.valueForGroup") + .param("id", reputationGroup.getId()) + .param("value", ReputationPlugin.get().getReputationValue(store, ref, reputationGroup.getId())) + ); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/reputation/store/ReputationDataResource.java b/src/com/hypixel/hytale/builtin/adventure/reputation/store/ReputationDataResource.java new file mode 100644 index 0000000..dc6cfde --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/reputation/store/ReputationDataResource.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.adventure.reputation.store; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.Object2IntMapCodec; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import javax.annotation.Nonnull; + +public class ReputationDataResource implements Resource { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(ReputationDataResource.class, ReputationDataResource::new) + .append( + new KeyedCodec<>("Stats", new Object2IntMapCodec<>(Codec.STRING, Object2IntOpenHashMap::new, false)), + (reputationDataResource, stringObject2IntMap) -> reputationDataResource.reputationStats = stringObject2IntMap, + reputationDataResource -> reputationDataResource.reputationStats + ) + .add() + .build(); + @Nonnull + private Object2IntMap reputationStats = new Object2IntOpenHashMap<>(0); + + public ReputationDataResource() { + } + + @Nonnull + public Object2IntMap getReputationStats() { + return this.reputationStats; + } + + @Nonnull + @Override + public Resource clone() { + ReputationDataResource resource = new ReputationDataResource(); + resource.reputationStats = new Object2IntOpenHashMap<>(this.reputationStats); + return resource; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/shop/GiveItemInteraction.java b/src/com/hypixel/hytale/builtin/adventure/shop/GiveItemInteraction.java new file mode 100644 index 0000000..7a7ac5b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/shop/GiveItemInteraction.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.builtin.adventure.shop; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceInteraction; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class GiveItemInteraction extends ChoiceInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + GiveItemInteraction.class, GiveItemInteraction::new, ChoiceInteraction.BASE_CODEC + ) + .append( + new KeyedCodec<>("ItemId", Codec.STRING), + (giveItemInteraction, blockTypeKey) -> giveItemInteraction.itemId = blockTypeKey, + giveItemInteraction -> giveItemInteraction.itemId + ) + .addValidator(Validators.nonNull()) + .addValidator(Item.VALIDATOR_CACHE.getValidator()) + .add() + .append( + new KeyedCodec<>("Quantity", Codec.INTEGER), + (giveItemInteraction, integer) -> giveItemInteraction.quantity = integer, + giveItemInteraction -> giveItemInteraction.quantity + ) + .addValidator(Validators.greaterThanOrEqual(1)) + .add() + .build(); + protected String itemId; + protected int quantity = 1; + + public GiveItemInteraction(String itemId, int quantity) { + this.itemId = itemId; + this.quantity = quantity; + } + + protected GiveItemInteraction() { + } + + public String getItemId() { + return this.itemId; + } + + public int getQuantity() { + return this.quantity; + } + + @Override + public void run(@Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + playerComponent.getInventory().getCombinedHotbarFirst().addItemStack(new ItemStack(this.itemId, this.quantity)); + } + + @Nonnull + @Override + public String toString() { + return "GiveItemInteraction{itemId=" + this.itemId + ", quantity=" + this.quantity + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/shop/ShopAsset.java b/src/com/hypixel/hytale/builtin/adventure/shop/ShopAsset.java new file mode 100644 index 0000000..5e9bdfa --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/shop/ShopAsset.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.builtin.adventure.shop; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceElement; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class ShopAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ShopAsset.class, + ShopAsset::new, + Codec.STRING, + (shopAsset, s) -> shopAsset.id = s, + shopAsset -> shopAsset.id, + (shopAsset, data) -> shopAsset.extraData = data, + shopAsset -> shopAsset.extraData + ) + .addField( + new KeyedCodec<>("Content", new ArrayCodec<>(ChoiceElement.CODEC, ChoiceElement[]::new)), + (shopAsset, choiceElements) -> shopAsset.elements = choiceElements, + shopAsset -> shopAsset.elements + ) + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ShopAsset::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data extraData; + protected String id; + protected ChoiceElement[] elements; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ShopAsset.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public ShopAsset(String id, ChoiceElement[] elements) { + this.id = id; + this.elements = elements; + } + + protected ShopAsset() { + } + + public String getId() { + return this.id; + } + + public ChoiceElement[] getElements() { + return this.elements; + } + + @Nonnull + @Override + public String toString() { + return "ShopAsset{id='" + this.id + "', elements=" + Arrays.toString((Object[])this.elements) + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/shop/ShopElement.java b/src/com/hypixel/hytale/builtin/adventure/shop/ShopElement.java new file mode 100644 index 0000000..cb4cbf7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/shop/ShopElement.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.builtin.adventure.shop; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceElement; +import com.hypixel.hytale.server.core.ui.LocalizableString; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ShopElement extends ChoiceElement { + public static final BuilderCodec CODEC = BuilderCodec.builder(ShopElement.class, ShopElement::new, ChoiceElement.BASE_CODEC) + .append(new KeyedCodec<>("Cost", Codec.INTEGER), (shopElement, integer) -> shopElement.cost = integer, shopElement -> shopElement.cost) + .addValidator(Validators.greaterThanOrEqual(0)) + .add() + .append(new KeyedCodec<>("Icon", Codec.STRING), (shopElement, s) -> shopElement.iconPath = s, shopElement -> shopElement.iconPath) + .add() + .build(); + protected int cost; + protected String iconPath; + + public ShopElement() { + } + + @Override + public void addButton(@Nonnull UICommandBuilder commandBuilder, UIEventBuilder eventBuilder, String selector, PlayerRef playerRef) { + commandBuilder.append("#ElementList", "Pages/ShopElementButton.ui"); + commandBuilder.set(selector + " #Icon.Background", this.iconPath); + commandBuilder.setObject(selector + " #Name.Text", LocalizableString.fromMessageId(this.displayNameKey)); + commandBuilder.setObject(selector + " #Description.Text", LocalizableString.fromMessageId(this.descriptionKey)); + commandBuilder.set(selector + " #Cost.Text", this.cost + ""); + } + + @Override + public boolean canFulfillRequirements(@Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef) { + return super.canFulfillRequirements(store, ref, playerRef); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/shop/ShopPage.java b/src/com/hypixel/hytale/builtin/adventure/shop/ShopPage.java new file mode 100644 index 0000000..28435e5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/shop/ShopPage.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.builtin.adventure.shop; + +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceBasePage; +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceElement; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ShopPage extends ChoiceBasePage { + public ShopPage(@Nonnull PlayerRef playerRef, String shopId) { + super(playerRef, getShopElements(shopId), "Pages/ShopPage.ui"); + } + + @Nullable + protected static ChoiceElement[] getShopElements(String shopId) { + ShopAsset shopAsset = ShopAsset.getAssetMap().getAsset(shopId); + return shopAsset == null ? null : shopAsset.getElements(); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/shop/ShopPageSupplier.java b/src/com/hypixel/hytale/builtin/adventure/shop/ShopPageSupplier.java new file mode 100644 index 0000000..7a23f30 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/shop/ShopPageSupplier.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.builtin.adventure.shop; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.player.pages.CustomUIPage; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenCustomUIInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ShopPageSupplier implements OpenCustomUIInteraction.CustomPageSupplier { + public static final BuilderCodec CODEC = BuilderCodec.builder(ShopPageSupplier.class, ShopPageSupplier::new) + .appendInherited( + new KeyedCodec<>("ShopId", Codec.STRING), (data, o) -> data.shopId = o, data -> data.shopId, (data, parent) -> data.shopId = parent.shopId + ) + .add() + .build(); + protected String shopId; + + public ShopPageSupplier() { + } + + @Nonnull + @Override + public CustomUIPage tryCreate( + Ref ref, ComponentAccessor componentAccessor, @Nonnull PlayerRef playerRef, InteractionContext context + ) { + return new ShopPage(playerRef, this.shopId); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/shop/ShopPlugin.java b/src/com/hypixel/hytale/builtin/adventure/shop/ShopPlugin.java new file mode 100644 index 0000000..6a4da93 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/shop/ShopPlugin.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.builtin.adventure.shop; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.builtin.adventure.shop.barter.BarterShopAsset; +import com.hypixel.hytale.builtin.adventure.shop.barter.BarterShopState; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceElement; +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenCustomUIInteraction; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class ShopPlugin extends JavaPlugin { + protected static ShopPlugin instance; + + public static ShopPlugin get() { + return instance; + } + + public ShopPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + instance = this; + this.getAssetRegistry() + .register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ShopAsset.class, new DefaultAssetMap() + ) + .setPath("Shops")) + .setCodec(ShopAsset.CODEC)) + .setKeyFunction(ShopAsset::getId)) + .loadsAfter(Item.class)) + .build() + ); + this.getAssetRegistry() + .register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + BarterShopAsset.class, new DefaultAssetMap() + ) + .setPath("BarterShops")) + .setCodec(BarterShopAsset.CODEC)) + .setKeyFunction(BarterShopAsset::getId)) + .loadsAfter(Item.class)) + .build() + ); + this.getCodecRegistry(ChoiceElement.CODEC).register("ShopElement", ShopElement.class, ShopElement.CODEC); + this.getCodecRegistry(ChoiceInteraction.CODEC).register("GiveItem", GiveItemInteraction.class, GiveItemInteraction.CODEC); + this.getCodecRegistry(OpenCustomUIInteraction.PAGE_CODEC).register("Shop", ShopPageSupplier.class, ShopPageSupplier.CODEC); + } + + @Override + protected void start() { + BarterShopState.initialize(this.getDataDirectory()); + this.getLogger().at(Level.INFO).log("Barter shop state initialized"); + } + + @Override + protected void shutdown() { + BarterShopState.shutdown(); + this.getLogger().at(Level.INFO).log("Barter shop state saved"); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/shop/barter/BarterItemStack.java b/src/com/hypixel/hytale/builtin/adventure/shop/barter/BarterItemStack.java new file mode 100644 index 0000000..6a795d0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/shop/barter/BarterItemStack.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.builtin.adventure.shop.barter; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class BarterItemStack { + public static final BuilderCodec CODEC = BuilderCodec.builder(BarterItemStack.class, BarterItemStack::new) + .append(new KeyedCodec<>("ItemId", Codec.STRING), (stack, s) -> stack.itemId = s, stack -> stack.itemId) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Quantity", Codec.INTEGER), (stack, i) -> stack.quantity = i, stack -> stack.quantity) + .addValidator(Validators.greaterThanOrEqual(1)) + .add() + .build(); + protected String itemId; + protected int quantity = 1; + + public BarterItemStack(String itemId, int quantity) { + this.itemId = itemId; + this.quantity = quantity; + } + + protected BarterItemStack() { + } + + public String getItemId() { + return this.itemId; + } + + public int getQuantity() { + return this.quantity; + } + + @Nonnull + @Override + public String toString() { + return "BarterItemStack{itemId='" + this.itemId + "', quantity=" + this.quantity + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/shop/barter/BarterPage.java b/src/com/hypixel/hytale/builtin/adventure/shop/barter/BarterPage.java new file mode 100644 index 0000000..d59e711 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/shop/barter/BarterPage.java @@ -0,0 +1,363 @@ +package com.hypixel.hytale.builtin.adventure.shop.barter; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.ItemUtils; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; +import com.hypixel.hytale.server.core.modules.item.ItemModule; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Duration; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class BarterPage extends InteractiveCustomUIPage { + private final BarterShopAsset shopAsset; + + public BarterPage(@Nonnull PlayerRef playerRef, @Nonnull String shopId) { + super(playerRef, CustomPageLifetime.CanDismissOrCloseThroughInteraction, BarterPage.BarterEventData.CODEC); + this.shopAsset = BarterShopAsset.getAssetMap().getAsset(shopId); + } + + private boolean isTradeValid(BarterTrade trade) { + if (!ItemModule.exists(trade.getOutput().getItemId())) { + return false; + } else { + for (BarterItemStack input : trade.getInput()) { + if (!ItemModule.exists(input.getItemId())) { + return false; + } + } + + return true; + } + } + + private String getSafeItemId(String itemId) { + return ItemModule.exists(itemId) ? itemId : "Unknown"; + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + if (this.shopAsset != null) { + commandBuilder.append("Pages/BarterPage.ui"); + String titleKey = this.shopAsset.getDisplayNameKey() != null ? this.shopAsset.getDisplayNameKey() : this.shopAsset.getId(); + commandBuilder.set("#ShopTitle.Text", Message.translation(titleKey)); + WorldTimeResource timeResource = store.getResource(WorldTimeResource.getResourceType()); + Instant gameTime = timeResource != null ? timeResource.getGameTime() : Instant.now(); + BarterShopState barterState = BarterShopState.get(); + int[] stockArray = barterState.getStockArray(this.shopAsset, gameTime); + Message refreshText = this.getRefreshTimerText(barterState, gameTime); + if (refreshText != null) { + commandBuilder.set("#RefreshTimer.Text", refreshText); + } + + commandBuilder.clear("#TradeGrid"); + Ref playerEntityRef = this.playerRef.getReference(); + Player playerComponent = playerEntityRef != null ? store.getComponent(playerEntityRef, Player.getComponentType()) : null; + ItemContainer playerInventory = null; + if (playerComponent != null) { + playerInventory = playerComponent.getInventory().getCombinedHotbarFirst(); + } + + BarterTrade[] trades = barterState.getResolvedTrades(this.shopAsset, gameTime); + + for (int i = 0; i < trades.length; i++) { + BarterTrade trade = trades[i]; + String selector = "#TradeGrid[" + i + "]"; + int stock = i < stockArray.length ? stockArray[i] : 0; + boolean tradeValid = this.isTradeValid(trade); + commandBuilder.append("#TradeGrid", "Pages/BarterTradeRow.ui"); + commandBuilder.set(selector + " #OutputSlot.ItemId", this.getSafeItemId(trade.getOutput().getItemId())); + int outputQty = trade.getOutput().getQuantity(); + commandBuilder.set(selector + " #OutputQuantity.Text", outputQty > 1 ? String.valueOf(outputQty) : ""); + boolean canAfford = true; + int playerHas = 0; + if (trade.getInput().length > 0) { + BarterItemStack firstInput = trade.getInput()[0]; + String inputItemId = firstInput.getItemId(); + int inputQty = firstInput.getQuantity(); + commandBuilder.set(selector + " #InputSlot.ItemId", this.getSafeItemId(inputItemId)); + commandBuilder.set(selector + " #InputQuantity.Text", inputQty > 1 ? String.valueOf(inputQty) : ""); + if (ItemModule.exists(inputItemId)) { + playerHas = playerInventory != null ? this.countItemsInContainer(playerInventory, inputItemId) : 0; + canAfford = playerHas >= inputQty; + } else { + canAfford = false; + } + + commandBuilder.set(selector + " #InputSlotBorder.Background", canAfford ? "#2a5a3a" : "#5a2a2a"); + commandBuilder.set(selector + " #HaveNeedLabel.Text", "Have: " + playerHas); + commandBuilder.set(selector + " #HaveNeedLabel.Style.TextColor", canAfford ? "#3d913f" : "#962f2f"); + } + + if (!tradeValid) { + commandBuilder.set(selector + " #Stock.Visible", false); + commandBuilder.set(selector + " #OutOfStockOverlay.Visible", true); + commandBuilder.set(selector + " #OutOfStockLabel.Text", "INVALID ITEM"); + commandBuilder.set(selector + " #OutOfStockLabel.Style.TextColor", "#cc8844"); + commandBuilder.set(selector + " #TradeButton.Disabled", true); + commandBuilder.set(selector + " #TradeButton.Style.Disabled.Background", "#4a3020"); + } else if (stock <= 0) { + commandBuilder.set(selector + " #Stock.Visible", false); + commandBuilder.set(selector + " #OutOfStockOverlay.Visible", true); + commandBuilder.set(selector + " #OutOfStockLabel.Text", "OUT OF STOCK"); + commandBuilder.set(selector + " #OutOfStockLabel.Style.TextColor", "#cc4444"); + commandBuilder.set(selector + " #TradeButton.Disabled", true); + commandBuilder.set(selector + " #TradeButton.Style.Disabled.Background", "#4a2020"); + } else { + commandBuilder.set(selector + " #Stock.TextSpans", Message.translation("server.barter.customUI.barterPage.inStock").param("count", stock)); + } + + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, selector + " #TradeButton", EventData.of("TradeIndex", String.valueOf(i)).append("Quantity", "1"), false + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.RightClicking, selector + " #TradeButton", EventData.of("TradeIndex", String.valueOf(i)).append("Quantity", "1"), false + ); + } + + int cardsPerRow = 3; + int remainder = trades.length % cardsPerRow; + if (remainder > 0) { + int spacersNeeded = cardsPerRow - remainder; + + for (int s = 0; s < spacersNeeded; s++) { + commandBuilder.append("#TradeGrid", "Pages/BarterGridSpacer.ui"); + } + } + } + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull BarterPage.BarterEventData data) { + if (this.shopAsset != null) { + int tradeIndex = data.getTradeIndex(); + int requestedQuantity = data.getQuantity(); + if (requestedQuantity > 0) { + WorldTimeResource timeResource = store.getResource(WorldTimeResource.getResourceType()); + Instant gameTime = timeResource != null ? timeResource.getGameTime() : Instant.now(); + BarterShopState barterState = BarterShopState.get(); + BarterTrade[] trades = barterState.getResolvedTrades(this.shopAsset, gameTime); + if (tradeIndex >= 0 && tradeIndex < trades.length) { + BarterTrade trade = trades[tradeIndex]; + if (this.isTradeValid(trade)) { + BarterShopState.ShopInstanceState shopState = barterState.getOrCreateShopState(this.shopAsset, gameTime); + int currentStock = shopState.getCurrentStock()[tradeIndex]; + if (currentStock > 0) { + Ref playerEntityRef = this.playerRef.getReference(); + if (playerEntityRef != null) { + Player playerComponent = store.getComponent(playerEntityRef, Player.getComponentType()); + if (playerComponent != null) { + Inventory inventory = playerComponent.getInventory(); + CombinedItemContainer container = inventory.getCombinedHotbarFirst(); + int maxQuantity = Math.min(requestedQuantity, currentStock); + + for (BarterItemStack inputStack : trade.getInput()) { + int has = this.countItemsInContainer(container, inputStack.getItemId()); + int canAfford = has / inputStack.getQuantity(); + maxQuantity = Math.min(maxQuantity, canAfford); + } + + if (maxQuantity > 0) { + int quantity = maxQuantity; + + for (BarterItemStack inputStack : trade.getInput()) { + int toRemove = inputStack.getQuantity() * quantity; + this.removeItemsFromContainer(container, inputStack.getItemId(), toRemove); + } + + BarterItemStack output = trade.getOutput(); + ItemStack outputStack = new ItemStack(output.getItemId(), output.getQuantity() * quantity); + ItemStackTransaction transaction = container.addItemStack(outputStack); + ItemStack remainder = transaction.getRemainder(); + if (remainder != null && !remainder.isEmpty()) { + int addedQty = outputStack.getQuantity() - remainder.getQuantity(); + if (addedQty > 0) { + playerComponent.notifyPickupItem(playerEntityRef, outputStack.withQuantity(addedQty), null, store); + } + + ItemUtils.dropItem(playerEntityRef, remainder, store); + } else { + playerComponent.notifyPickupItem(playerEntityRef, outputStack, null, store); + } + + barterState.executeTrade(this.shopAsset, tradeIndex, quantity, gameTime); + this.updateAfterTrade(ref, store, tradeIndex); + } + } + } + } + } + } + } + } + } + + private void updateAfterTrade(@Nonnull Ref ref, @Nonnull Store store, int tradedIndex) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + WorldTimeResource timeResource = store.getResource(WorldTimeResource.getResourceType()); + Instant gameTime = timeResource != null ? timeResource.getGameTime() : Instant.now(); + BarterShopState barterState = BarterShopState.get(); + int[] stockArray = barterState.getStockArray(this.shopAsset, gameTime); + BarterTrade[] trades = barterState.getResolvedTrades(this.shopAsset, gameTime); + Ref playerEntityRef = this.playerRef.getReference(); + Player playerComponent = playerEntityRef != null ? store.getComponent(playerEntityRef, Player.getComponentType()) : null; + ItemContainer playerInventory = null; + if (playerComponent != null) { + playerInventory = playerComponent.getInventory().getCombinedHotbarFirst(); + } + + for (int i = 0; i < trades.length; i++) { + BarterTrade trade = trades[i]; + String selector = "#TradeGrid[" + i + "]"; + int stock = i < stockArray.length ? stockArray[i] : 0; + boolean tradeValid = this.isTradeValid(trade); + if (trade.getInput().length > 0) { + BarterItemStack firstInput = trade.getInput()[0]; + int playerHas = 0; + boolean canAfford = false; + if (ItemModule.exists(firstInput.getItemId())) { + playerHas = playerInventory != null ? this.countItemsInContainer(playerInventory, firstInput.getItemId()) : 0; + canAfford = playerHas >= firstInput.getQuantity(); + } + + commandBuilder.set(selector + " #InputSlotBorder.Background", canAfford ? "#2a5a3a" : "#5a2a2a"); + commandBuilder.set(selector + " #HaveNeedLabel.Text", "Have: " + playerHas); + commandBuilder.set(selector + " #HaveNeedLabel.Style.TextColor", canAfford ? "#3d913f" : "#962f2f"); + } + + if (!tradeValid) { + commandBuilder.set(selector + " #Stock.Visible", false); + commandBuilder.set(selector + " #OutOfStockOverlay.Visible", true); + commandBuilder.set(selector + " #TradeButton.Disabled", true); + } else if (stock <= 0) { + commandBuilder.set(selector + " #Stock.Visible", false); + commandBuilder.set(selector + " #OutOfStockOverlay.Visible", true); + commandBuilder.set(selector + " #OutOfStockLabel.Text", "OUT OF STOCK"); + commandBuilder.set(selector + " #OutOfStockLabel.Style.TextColor", "#cc4444"); + commandBuilder.set(selector + " #TradeButton.Disabled", true); + commandBuilder.set(selector + " #TradeButton.Style.Disabled.Background", "#4a2020"); + } else { + commandBuilder.set(selector + " #Stock.Visible", true); + commandBuilder.set(selector + " #Stock.TextSpans", Message.translation("server.barter.customUI.barterPage.inStock").param("count", stock)); + commandBuilder.set(selector + " #OutOfStockOverlay.Visible", false); + commandBuilder.set(selector + " #TradeButton.Disabled", false); + commandBuilder.set(selector + " #TradeButton.Style.Default.Background", "#1e2a3a"); + } + } + + this.sendUpdate(commandBuilder, new UIEventBuilder(), false); + } + + private int countItemsInContainer(ItemContainer container, String itemId) { + return container.countItemStacks(stack -> itemId.equals(stack.getItemId())); + } + + private void removeItemsFromContainer(ItemContainer container, String itemId, int amount) { + container.removeItemStack(new ItemStack(itemId, amount)); + } + + private void refreshUI(@Nonnull Ref ref, @Nonnull Store store) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.build(ref, commandBuilder, eventBuilder, store); + this.sendUpdate(commandBuilder, eventBuilder, true); + } + + private Message getRefreshTimerText(BarterShopState barterState, Instant gameTime) { + if (this.shopAsset == null) { + return null; + } else { + RefreshInterval interval = this.shopAsset.getRefreshInterval(); + if (interval == null) { + return null; + } else { + BarterShopState.ShopInstanceState shopState = barterState.getOrCreateShopState(this.shopAsset, gameTime); + Instant nextRefresh = shopState.getNextRefreshTime(); + if (nextRefresh == null) { + return null; + } else { + Duration remaining = Duration.between(gameTime, nextRefresh); + if (!remaining.isNegative() && !remaining.isZero()) { + long currentDayNumber = gameTime.getEpochSecond() / WorldTimeResource.SECONDS_PER_DAY; + long refreshDayNumber = nextRefresh.getEpochSecond() / WorldTimeResource.SECONDS_PER_DAY; + long daysUntilRefresh = refreshDayNumber - currentDayNumber; + int hour = this.shopAsset.getRestockHour(); + String amPm = hour >= 12 ? "PM" : "AM"; + int displayHour = hour % 12; + if (displayHour == 0) { + displayHour = 12; + } + + String timeString = String.format("%d:00 %s", displayHour, amPm); + if (daysUntilRefresh <= 0L) { + return Message.translation("server.barter.customUI.barterPage.restocksToday").param("restockTime", timeString); + } else { + return daysUntilRefresh == 1L + ? Message.translation("server.barter.customUI.barterPage.restocksTomorrow").param("restockTime", timeString) + : Message.translation("server.barter.customUI.barterPage.restocksInDays").param("days", (int)daysUntilRefresh); + } + } else { + return null; + } + } + } + } + } + + public static class BarterEventData { + static final String TRADE_INDEX = "TradeIndex"; + static final String QUANTITY = "Quantity"; + static final String SHIFT_HELD = "ShiftHeld"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + BarterPage.BarterEventData.class, BarterPage.BarterEventData::new + ) + .append(new KeyedCodec<>("TradeIndex", Codec.STRING), (data, s) -> data.tradeIndex = Integer.parseInt(s), data -> String.valueOf(data.tradeIndex)) + .add() + .append(new KeyedCodec<>("Quantity", Codec.STRING), (data, s) -> data.quantity = Integer.parseInt(s), data -> String.valueOf(data.quantity)) + .add() + .append(new KeyedCodec<>("ShiftHeld", Codec.BOOLEAN), (data, b) -> { + if (b != null) { + data.shiftHeld = b; + } + }, data -> data.shiftHeld) + .add() + .build(); + private int tradeIndex; + private int quantity = 1; + private boolean shiftHeld = false; + + public BarterEventData() { + } + + public int getTradeIndex() { + return this.tradeIndex; + } + + public int getQuantity() { + return this.shiftHeld ? 10 : this.quantity; + } + + public boolean isShiftHeld() { + return this.shiftHeld; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/shop/barter/BarterShopAsset.java b/src/com/hypixel/hytale/builtin/adventure/shop/barter/BarterShopAsset.java new file mode 100644 index 0000000..855d305 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/shop/barter/BarterShopAsset.java @@ -0,0 +1,125 @@ +package com.hypixel.hytale.builtin.adventure.shop.barter; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BarterShopAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + BarterShopAsset.class, + BarterShopAsset::new, + Codec.STRING, + (asset, s) -> asset.id = s, + asset -> asset.id, + (asset, data) -> asset.extraData = data, + asset -> asset.extraData + ) + .addField(new KeyedCodec<>("DisplayNameKey", Codec.STRING), (asset, s) -> asset.displayNameKey = s, asset -> asset.displayNameKey) + .addField( + new KeyedCodec<>("RefreshInterval", RefreshInterval.CODEC), (asset, interval) -> asset.refreshInterval = interval, asset -> asset.refreshInterval + ) + .addField( + new KeyedCodec<>("Trades", new ArrayCodec<>(BarterTrade.CODEC, BarterTrade[]::new)), (asset, trades) -> asset.trades = trades, asset -> asset.trades + ) + .addField( + new KeyedCodec<>("TradeSlots", new ArrayCodec<>(TradeSlot.CODEC, TradeSlot[]::new)), + (asset, slots) -> asset.tradeSlots = slots, + asset -> asset.tradeSlots + ) + .addField(new KeyedCodec<>("RestockHour", Codec.INTEGER, true), (asset, hour) -> asset.restockHour = hour, asset -> asset.restockHour) + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(BarterShopAsset::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data extraData; + public static final int DEFAULT_RESTOCK_HOUR = 7; + protected String id; + protected String displayNameKey; + protected RefreshInterval refreshInterval; + protected BarterTrade[] trades; + protected TradeSlot[] tradeSlots; + protected Integer restockHour; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(BarterShopAsset.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public BarterShopAsset( + String id, String displayNameKey, RefreshInterval refreshInterval, BarterTrade[] trades, TradeSlot[] tradeSlots, @Nullable Integer restockHour + ) { + this.id = id; + this.displayNameKey = displayNameKey; + this.refreshInterval = refreshInterval; + this.trades = trades; + this.tradeSlots = tradeSlots; + this.restockHour = restockHour; + } + + protected BarterShopAsset() { + } + + public String getId() { + return this.id; + } + + public String getDisplayNameKey() { + return this.displayNameKey; + } + + public RefreshInterval getRefreshInterval() { + return this.refreshInterval; + } + + public BarterTrade[] getTrades() { + return this.trades; + } + + @Nullable + public TradeSlot[] getTradeSlots() { + return this.tradeSlots; + } + + public boolean hasTradeSlots() { + return this.tradeSlots != null && this.tradeSlots.length > 0; + } + + public int getRestockHour() { + return this.restockHour != null ? this.restockHour : 7; + } + + @Nonnull + @Override + public String toString() { + return "BarterShopAsset{id='" + + this.id + + "', displayNameKey='" + + this.displayNameKey + + "', refreshInterval=" + + this.refreshInterval + + ", restockHour=" + + this.getRestockHour() + + ", trades=" + + Arrays.toString((Object[])this.trades) + + ", tradeSlots=" + + Arrays.toString((Object[])this.tradeSlots) + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/shop/barter/BarterShopState.java b/src/com/hypixel/hytale/builtin/adventure/shop/barter/BarterShopState.java new file mode 100644 index 0000000..afd1a0e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/shop/barter/BarterShopState.java @@ -0,0 +1,324 @@ +package com.hypixel.hytale.builtin.adventure.shop.barter; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.util.BsonUtil; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public class BarterShopState { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static BarterShopState instance; + private static Path saveDirectory; + public static final BuilderCodec SHOP_INSTANCE_CODEC = BuilderCodec.builder( + BarterShopState.ShopInstanceState.class, BarterShopState.ShopInstanceState::new + ) + .append(new KeyedCodec<>("Stock", Codec.INT_ARRAY), (state, stock) -> state.currentStock = stock, state -> state.currentStock) + .add() + .append(new KeyedCodec<>("NextRefresh", Codec.INSTANT, true), (state, instant) -> state.nextRefreshTime = instant, state -> state.nextRefreshTime) + .add() + .append(new KeyedCodec<>("ResolveSeed", Codec.LONG, true), (state, seed) -> state.resolveSeed = seed, state -> state.resolveSeed) + .add() + .build(); + public static final BuilderCodec CODEC = BuilderCodec.builder(BarterShopState.class, BarterShopState::new) + .append( + new KeyedCodec<>("Shops", new MapCodec<>(SHOP_INSTANCE_CODEC, Object2ObjectOpenHashMap::new, false)), + (state, shops) -> state.shopStates.putAll(shops), + state -> new Object2ObjectOpenHashMap<>(state.shopStates) + ) + .add() + .build(); + private final Map shopStates = new ConcurrentHashMap<>(); + + public static void initialize(@Nonnull Path dataDirectory) { + saveDirectory = dataDirectory; + load(); + } + + @Nonnull + public static BarterShopState get() { + if (instance == null) { + instance = new BarterShopState(); + } + + return instance; + } + + public static void load() { + if (saveDirectory == null) { + LOGGER.at(Level.WARNING).log("Cannot load barter shop state: save directory not set"); + instance = new BarterShopState(); + } else { + Path file = saveDirectory.resolve("barter_shop_state.json"); + if (!Files.exists(file)) { + LOGGER.at(Level.INFO).log("No saved barter shop state found, starting fresh"); + instance = new BarterShopState(); + } else { + try { + BsonDocument document = BsonUtil.readDocumentNow(file); + if (document != null) { + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + instance = CODEC.decode(document, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER); + LOGGER.at(Level.INFO).log("Loaded barter shop state with %d shops", instance.shopStates.size()); + } else { + instance = new BarterShopState(); + } + } catch (Exception var3) { + LOGGER.at(Level.WARNING).withCause(var3).log("Failed to load barter shop state, starting fresh"); + instance = new BarterShopState(); + } + } + } + } + + public static void save() { + if (saveDirectory != null && instance != null) { + try { + if (!Files.exists(saveDirectory)) { + Files.createDirectories(saveDirectory); + } + + Path file = saveDirectory.resolve("barter_shop_state.json"); + BsonUtil.writeSync(file, CODEC, instance, LOGGER); + LOGGER.at(Level.FINE).log("Saved barter shop state"); + } catch (IOException var1) { + LOGGER.at(Level.WARNING).withCause(var1).log("Failed to save barter shop state"); + } + } + } + + public static void shutdown() { + save(); + instance = null; + } + + public BarterShopState() { + } + + private static Instant calculateNextScheduledRestock(@Nonnull Instant gameTime, int intervalDays, int restockHour) { + LocalDateTime dateTime = LocalDateTime.ofInstant(gameTime, ZoneOffset.UTC); + long daysSinceEpoch = Duration.between(WorldTimeResource.ZERO_YEAR, gameTime).toDays(); + long currentCycle = daysSinceEpoch / intervalDays; + long nextRestockDaySinceEpoch = (currentCycle + 1L) * intervalDays; + boolean isTodayRestockDay = daysSinceEpoch % intervalDays == 0L; + if (isTodayRestockDay && dateTime.getHour() < restockHour) { + nextRestockDaySinceEpoch = daysSinceEpoch; + } + + return WorldTimeResource.ZERO_YEAR.plus(Duration.ofDays(nextRestockDaySinceEpoch)).plus(Duration.ofHours(restockHour)); + } + + @Nonnull + public BarterShopState.ShopInstanceState getOrCreateShopState(BarterShopAsset asset, @Nonnull Instant gameTime) { + return this.shopStates.computeIfAbsent(asset.getId(), id -> { + BarterShopState.ShopInstanceState state = new BarterShopState.ShopInstanceState(); + state.resetStockAndResolve(asset); + RefreshInterval interval = asset.getRefreshInterval(); + if (interval != null) { + state.setNextRefreshTime(calculateNextScheduledRestock(gameTime, interval.getDays(), asset.getRestockHour())); + } + + return state; + }); + } + + public void checkRefresh(BarterShopAsset asset, @Nonnull Instant gameTime) { + RefreshInterval interval = asset.getRefreshInterval(); + if (interval != null) { + BarterShopState.ShopInstanceState state = this.getOrCreateShopState(asset, gameTime); + Instant nextRefresh = state.getNextRefreshTime(); + if (nextRefresh == null) { + state.setNextRefreshTime(calculateNextScheduledRestock(gameTime, interval.getDays(), asset.getRestockHour())); + save(); + } else { + if (!gameTime.isBefore(nextRefresh)) { + state.resetStockAndResolve(asset); + state.setNextRefreshTime(calculateNextScheduledRestock(gameTime, interval.getDays(), asset.getRestockHour())); + save(); + } + } + } + } + + public int[] getStockArray(BarterShopAsset asset, @Nonnull Instant gameTime) { + this.checkRefresh(asset, gameTime); + BarterShopState.ShopInstanceState state = this.getOrCreateShopState(asset, gameTime); + if (state.expandStockIfNeeded(asset)) { + save(); + } + + return (int[])state.getCurrentStock().clone(); + } + + @Nonnull + public BarterTrade[] getResolvedTrades(BarterShopAsset asset, @Nonnull Instant gameTime) { + this.checkRefresh(asset, gameTime); + BarterShopState.ShopInstanceState state = this.getOrCreateShopState(asset, gameTime); + return state.getResolvedTrades(asset); + } + + public boolean executeTrade(BarterShopAsset asset, int tradeIndex, int quantity, @Nonnull Instant gameTime) { + this.checkRefresh(asset, gameTime); + BarterShopState.ShopInstanceState state = this.getOrCreateShopState(asset, gameTime); + boolean success = state.decrementStock(tradeIndex, quantity); + if (success) { + save(); + } + + return success; + } + + public static class ShopInstanceState { + private int[] currentStock = new int[0]; + private Instant nextRefreshTime; + private Long resolveSeed; + private transient BarterTrade[] resolvedTrades; + + public ShopInstanceState() { + } + + public ShopInstanceState(int tradeCount) { + this.currentStock = new int[tradeCount]; + this.nextRefreshTime = null; + } + + public int[] getCurrentStock() { + return this.currentStock; + } + + @Nullable + public Instant getNextRefreshTime() { + return this.nextRefreshTime; + } + + public void setNextRefreshTime(Instant time) { + this.nextRefreshTime = time; + } + + @Nullable + public Long getResolveSeed() { + return this.resolveSeed; + } + + public void setResolveSeed(Long seed) { + this.resolveSeed = seed; + } + + @Nonnull + public BarterTrade[] getResolvedTrades(@Nonnull BarterShopAsset asset) { + if (!asset.hasTradeSlots()) { + return asset.getTrades() != null ? asset.getTrades() : new BarterTrade[0]; + } else if (this.resolvedTrades != null) { + return this.resolvedTrades; + } else { + if (this.resolveSeed == null) { + this.resolveSeed = ThreadLocalRandom.current().nextLong(); + } + + this.resolvedTrades = resolveTradeSlots(asset, this.resolveSeed); + return this.resolvedTrades; + } + } + + @Nonnull + private static BarterTrade[] resolveTradeSlots(@Nonnull BarterShopAsset asset, long seed) { + TradeSlot[] slots = asset.getTradeSlots(); + if (slots != null && slots.length != 0) { + Random random = new Random(seed); + List result = new ObjectArrayList<>(); + + for (TradeSlot slot : slots) { + result.addAll(slot.resolve(random)); + } + + return result.toArray(new BarterTrade[0]); + } else { + return new BarterTrade[0]; + } + } + + public void resetStockAndResolve(@Nonnull BarterShopAsset asset) { + if (asset.hasTradeSlots()) { + this.resolveSeed = ThreadLocalRandom.current().nextLong(); + this.resolvedTrades = resolveTradeSlots(asset, this.resolveSeed); + } else { + this.resolvedTrades = null; + } + + BarterTrade[] trades = this.getResolvedTrades(asset); + this.currentStock = new int[trades.length]; + + for (int i = 0; i < trades.length; i++) { + this.currentStock[i] = trades[i].getMaxStock(); + } + } + + /** @deprecated */ + public void resetStock(BarterShopAsset asset) { + BarterTrade[] trades = this.getResolvedTrades(asset); + if (this.currentStock.length != trades.length) { + this.currentStock = new int[trades.length]; + } + + for (int i = 0; i < trades.length; i++) { + this.currentStock[i] = trades[i].getMaxStock(); + } + } + + public boolean expandStockIfNeeded(BarterShopAsset asset) { + BarterTrade[] trades = this.getResolvedTrades(asset); + if (this.currentStock.length >= trades.length) { + return false; + } else { + int[] newStock = new int[trades.length]; + System.arraycopy(this.currentStock, 0, newStock, 0, this.currentStock.length); + + for (int i = this.currentStock.length; i < trades.length; i++) { + newStock[i] = trades[i].getMaxStock(); + } + + this.currentStock = newStock; + return true; + } + } + + public boolean hasStock(int tradeIndex, int quantity) { + return tradeIndex >= 0 && tradeIndex < this.currentStock.length ? this.currentStock[tradeIndex] >= quantity : false; + } + + public boolean decrementStock(int tradeIndex, int quantity) { + if (!this.hasStock(tradeIndex, quantity)) { + return false; + } else { + this.currentStock[tradeIndex] = this.currentStock[tradeIndex] - quantity; + return true; + } + } + + public int getStock(int tradeIndex) { + return tradeIndex >= 0 && tradeIndex < this.currentStock.length ? this.currentStock[tradeIndex] : 0; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/shop/barter/BarterTrade.java b/src/com/hypixel/hytale/builtin/adventure/shop/barter/BarterTrade.java new file mode 100644 index 0000000..534e7f7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/shop/barter/BarterTrade.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.builtin.adventure.shop.barter; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class BarterTrade { + public static final BuilderCodec CODEC = BuilderCodec.builder(BarterTrade.class, BarterTrade::new) + .append(new KeyedCodec<>("Output", BarterItemStack.CODEC), (trade, stack) -> trade.output = stack, trade -> trade.output) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("Input", new ArrayCodec<>(BarterItemStack.CODEC, BarterItemStack[]::new)), + (trade, stacks) -> trade.input = stacks, + trade -> trade.input + ) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Stock", Codec.INTEGER), (trade, i) -> trade.maxStock = i, trade -> trade.maxStock) + .addValidator(Validators.greaterThanOrEqual(1)) + .add() + .build(); + protected BarterItemStack output; + protected BarterItemStack[] input; + protected int maxStock = 10; + + public BarterTrade(BarterItemStack output, BarterItemStack[] input, int maxStock) { + this.output = output; + this.input = input; + this.maxStock = maxStock; + } + + protected BarterTrade() { + } + + public BarterItemStack getOutput() { + return this.output; + } + + public BarterItemStack[] getInput() { + return this.input; + } + + public int getMaxStock() { + return this.maxStock; + } + + @Nonnull + @Override + public String toString() { + return "BarterTrade{output=" + this.output + ", input=" + Arrays.toString((Object[])this.input) + ", maxStock=" + this.maxStock + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/shop/barter/FixedTradeSlot.java b/src/com/hypixel/hytale/builtin/adventure/shop/barter/FixedTradeSlot.java new file mode 100644 index 0000000..0b3789a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/shop/barter/FixedTradeSlot.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.adventure.shop.barter; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import javax.annotation.Nonnull; + +public class FixedTradeSlot extends TradeSlot { + public static final BuilderCodec CODEC = BuilderCodec.builder(FixedTradeSlot.class, FixedTradeSlot::new) + .append(new KeyedCodec<>("Trade", BarterTrade.CODEC), (slot, trade) -> slot.trade = trade, slot -> slot.trade) + .addValidator(Validators.nonNull()) + .add() + .build(); + protected BarterTrade trade; + + public FixedTradeSlot(@Nonnull BarterTrade trade) { + this.trade = trade; + } + + protected FixedTradeSlot() { + } + + @Nonnull + public BarterTrade getTrade() { + return this.trade; + } + + @Nonnull + @Override + public List resolve(@Nonnull Random random) { + return Collections.singletonList(this.trade); + } + + @Override + public int getSlotCount() { + return 1; + } + + @Nonnull + @Override + public String toString() { + return "FixedTradeSlot{trade=" + this.trade + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/shop/barter/PoolTradeSlot.java b/src/com/hypixel/hytale/builtin/adventure/shop/barter/PoolTradeSlot.java new file mode 100644 index 0000000..96103bc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/shop/barter/PoolTradeSlot.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.builtin.adventure.shop.barter; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import javax.annotation.Nonnull; + +public class PoolTradeSlot extends TradeSlot { + public static final BuilderCodec CODEC = BuilderCodec.builder(PoolTradeSlot.class, PoolTradeSlot::new) + .append(new KeyedCodec<>("SlotCount", Codec.INTEGER), (slot, count) -> slot.slotCount = count, slot -> slot.slotCount) + .addValidator(Validators.greaterThanOrEqual(1)) + .add() + .append( + new KeyedCodec<>("Trades", new ArrayCodec<>(WeightedTrade.CODEC, WeightedTrade[]::new)), (slot, trades) -> slot.trades = trades, slot -> slot.trades + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + protected int slotCount = 1; + protected WeightedTrade[] trades = WeightedTrade.EMPTY_ARRAY; + + public PoolTradeSlot(int slotCount, @Nonnull WeightedTrade[] trades) { + this.slotCount = slotCount; + this.trades = trades; + } + + protected PoolTradeSlot() { + } + + public int getPoolSlotCount() { + return this.slotCount; + } + + @Nonnull + public WeightedTrade[] getTrades() { + return this.trades; + } + + @Nonnull + @Override + public List resolve(@Nonnull Random random) { + List result = new ObjectArrayList<>(this.slotCount); + if (this.trades.length == 0) { + return result; + } else { + ObjectArrayList available = new ObjectArrayList<>(this.trades.length); + available.addAll(Arrays.asList(this.trades)); + int toSelect = Math.min(this.slotCount, available.size()); + + for (int i = 0; i < toSelect; i++) { + int selectedIndex = this.selectWeightedIndex(available, random); + if (selectedIndex >= 0) { + WeightedTrade selected = available.remove(selectedIndex); + result.add(selected.toBarterTrade(random)); + } + } + + return result; + } + } + + @Override + public int getSlotCount() { + return this.slotCount; + } + + private int selectWeightedIndex(@Nonnull List trades, @Nonnull Random random) { + if (trades.isEmpty()) { + return -1; + } else { + double totalWeight = 0.0; + + for (WeightedTrade trade : trades) { + totalWeight += trade.getWeight(); + } + + if (totalWeight <= 0.0) { + return random.nextInt(trades.size()); + } else { + double roll = random.nextDouble() * totalWeight; + double cumulative = 0.0; + + for (int i = 0; i < trades.size(); i++) { + cumulative += trades.get(i).getWeight(); + if (roll < cumulative) { + return i; + } + } + + return trades.size() - 1; + } + } + } + + @Nonnull + @Override + public String toString() { + return "PoolTradeSlot{slotCount=" + this.slotCount + ", trades=" + Arrays.toString((Object[])this.trades) + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/shop/barter/RefreshInterval.java b/src/com/hypixel/hytale/builtin/adventure/shop/barter/RefreshInterval.java new file mode 100644 index 0000000..acd57de --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/shop/barter/RefreshInterval.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.adventure.shop.barter; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class RefreshInterval { + public static final BuilderCodec CODEC = BuilderCodec.builder(RefreshInterval.class, RefreshInterval::new) + .append(new KeyedCodec<>("Days", Codec.INTEGER), (interval, i) -> interval.days = i, interval -> interval.days) + .addValidator(Validators.greaterThanOrEqual(1)) + .add() + .build(); + protected int days = 1; + + public RefreshInterval(int days) { + this.days = days; + } + + protected RefreshInterval() { + } + + public int getDays() { + return this.days; + } + + @Nonnull + @Override + public String toString() { + return "RefreshInterval{days=" + this.days + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/shop/barter/TradeSlot.java b/src/com/hypixel/hytale/builtin/adventure/shop/barter/TradeSlot.java new file mode 100644 index 0000000..1d1fdd3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/shop/barter/TradeSlot.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.builtin.adventure.shop.barter; + +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import java.util.List; +import java.util.Random; +import javax.annotation.Nonnull; + +public abstract class TradeSlot { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final TradeSlot[] EMPTY_ARRAY = new TradeSlot[0]; + + protected TradeSlot() { + } + + @Nonnull + public abstract List resolve(@Nonnull Random var1); + + public abstract int getSlotCount(); + + static { + CODEC.register("Fixed", FixedTradeSlot.class, FixedTradeSlot.CODEC); + CODEC.register("Pool", PoolTradeSlot.class, PoolTradeSlot.CODEC); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/shop/barter/WeightedTrade.java b/src/com/hypixel/hytale/builtin/adventure/shop/barter/WeightedTrade.java new file mode 100644 index 0000000..de75ee3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/shop/barter/WeightedTrade.java @@ -0,0 +1,118 @@ +package com.hypixel.hytale.builtin.adventure.shop.barter; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.map.IWeightedElement; +import java.util.Arrays; +import java.util.Random; +import javax.annotation.Nonnull; + +public class WeightedTrade implements IWeightedElement { + public static final BuilderCodec CODEC = BuilderCodec.builder(WeightedTrade.class, WeightedTrade::new) + .append(new KeyedCodec<>("Weight", Codec.DOUBLE), (wt, w) -> wt.weight = w, wt -> wt.weight) + .add() + .append(new KeyedCodec<>("Output", BarterItemStack.CODEC), (wt, stack) -> wt.output = stack, wt -> wt.output) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("Input", new ArrayCodec<>(BarterItemStack.CODEC, BarterItemStack[]::new)), (wt, stacks) -> wt.input = stacks, wt -> wt.input + ) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Stock", Codec.INT_ARRAY), (wt, arr) -> wt.stockRange = arr, wt -> wt.stockRange) + .add() + .build(); + public static final WeightedTrade[] EMPTY_ARRAY = new WeightedTrade[0]; + protected double weight = 100.0; + protected BarterItemStack output; + protected BarterItemStack[] input; + protected int[] stockRange = new int[]{10}; + + public WeightedTrade(double weight, @Nonnull BarterItemStack output, @Nonnull BarterItemStack[] input, int stock) { + this.weight = weight; + this.output = output; + this.input = input; + this.stockRange = new int[]{stock}; + } + + public WeightedTrade(double weight, @Nonnull BarterItemStack output, @Nonnull BarterItemStack[] input, int stockMin, int stockMax) { + this.weight = weight; + this.output = output; + this.input = input; + this.stockRange = new int[]{stockMin, stockMax}; + } + + protected WeightedTrade() { + } + + @Override + public double getWeight() { + return this.weight; + } + + @Nonnull + public BarterItemStack getOutput() { + return this.output; + } + + @Nonnull + public BarterItemStack[] getInput() { + return this.input; + } + + @Nonnull + public int[] getStockRange() { + return this.stockRange; + } + + public boolean hasStockRange() { + return this.stockRange != null && this.stockRange.length == 2; + } + + public int getStockMin() { + return this.stockRange != null && this.stockRange.length > 0 ? this.stockRange[0] : 10; + } + + public int getStockMax() { + return this.stockRange != null && this.stockRange.length > 1 ? this.stockRange[1] : this.getStockMin(); + } + + public int resolveStock(@Nonnull Random random) { + if (!this.hasStockRange()) { + return this.getStockMin(); + } else { + int min = this.getStockMin(); + int max = this.getStockMax(); + return min >= max ? min : min + random.nextInt(max - min + 1); + } + } + + @Nonnull + public BarterTrade toBarterTrade(@Nonnull Random random) { + return new BarterTrade(this.output, this.input, this.resolveStock(random)); + } + + /** @deprecated */ + @Nonnull + public BarterTrade toBarterTrade() { + return new BarterTrade(this.output, this.input, this.getStockMin()); + } + + @Nonnull + @Override + public String toString() { + String stockStr = this.hasStockRange() ? "[" + this.getStockMin() + ", " + this.getStockMax() + "]" : String.valueOf(this.getStockMin()); + return "WeightedTrade{weight=" + + this.weight + + ", output=" + + this.output + + ", input=" + + Arrays.toString((Object[])this.input) + + ", stock=" + + stockStr + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/shopreputation/ShopReputationPlugin.java b/src/com/hypixel/hytale/builtin/adventure/shopreputation/ShopReputationPlugin.java new file mode 100644 index 0000000..30d8939 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/shopreputation/ShopReputationPlugin.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.builtin.adventure.shopreputation; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.builtin.adventure.reputation.assets.ReputationGroup; +import com.hypixel.hytale.builtin.adventure.reputation.assets.ReputationRank; +import com.hypixel.hytale.builtin.adventure.shop.ShopAsset; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import javax.annotation.Nonnull; + +public class ShopReputationPlugin extends JavaPlugin { + public ShopReputationPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + AssetRegistry.getAssetStore(ShopAsset.class).injectLoadsAfter(ReputationGroup.class); + AssetRegistry.getAssetStore(ShopAsset.class).injectLoadsAfter(ReputationRank.class); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/stash/StashGameplayConfig.java b/src/com/hypixel/hytale/builtin/adventure/stash/StashGameplayConfig.java new file mode 100644 index 0000000..7efe6a1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/stash/StashGameplayConfig.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.adventure.stash; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StashGameplayConfig { + public static final String ID = "Stash"; + public static final BuilderCodec CODEC = BuilderCodec.builder(StashGameplayConfig.class, StashGameplayConfig::new) + .appendInherited( + new KeyedCodec<>("ClearContainerDropList", Codec.BOOLEAN), + (gameplayConfig, clearContainerDropList) -> gameplayConfig.clearContainerDropList = clearContainerDropList, + gameplayConfig -> gameplayConfig.clearContainerDropList, + (gameplayConfig, parent) -> gameplayConfig.clearContainerDropList = parent.clearContainerDropList + ) + .add() + .build(); + private static final StashGameplayConfig DEFAULT_STASH_GAMEPLAY_CONFIG = new StashGameplayConfig(); + protected boolean clearContainerDropList = true; + + public StashGameplayConfig() { + } + + @Nullable + public static StashGameplayConfig get(@Nonnull GameplayConfig config) { + return config.getPluginConfig().get(StashGameplayConfig.class); + } + + public static StashGameplayConfig getOrDefault(@Nonnull GameplayConfig config) { + StashGameplayConfig stashGameplayConfig = get(config); + return stashGameplayConfig != null ? stashGameplayConfig : DEFAULT_STASH_GAMEPLAY_CONFIG; + } + + public boolean isClearContainerDropList() { + return this.clearContainerDropList; + } + + @Nonnull + @Override + public String toString() { + return "StashGameplayConfig{clearContainerDropList=" + this.clearContainerDropList + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/stash/StashPlugin.java b/src/com/hypixel/hytale/builtin/adventure/stash/StashPlugin.java new file mode 100644 index 0000000..150b2d6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/stash/StashPlugin.java @@ -0,0 +1,136 @@ +package com.hypixel.hytale.builtin.adventure.stash; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction; +import com.hypixel.hytale.server.core.modules.item.ItemModule; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule; +import com.hypixel.hytale.server.core.universe.world.meta.state.ItemContainerState; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.shorts.ShortArrayList; +import it.unimi.dsi.fastutil.shorts.ShortLists; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StashPlugin extends JavaPlugin { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + + public StashPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + this.getChunkStoreRegistry().registerSystem(new StashPlugin.StashSystem(BlockStateModule.get().getComponentType(ItemContainerState.class))); + this.getCodecRegistry(GameplayConfig.PLUGIN_CODEC).register(StashGameplayConfig.class, "Stash", StashGameplayConfig.CODEC); + } + + @Nullable + public static ListTransaction stash(@Nonnull ItemContainerState containerState, boolean clearDropList) { + String droplist = containerState.getDroplist(); + if (droplist == null) { + return null; + } else { + List stacks = ItemModule.get().getRandomItemDrops(droplist); + if (stacks.isEmpty()) { + return ListTransaction.getEmptyTransaction(true); + } else { + ItemContainer itemContainer = containerState.getItemContainer(); + short capacity = itemContainer.getCapacity(); + ShortArrayList slots = new ShortArrayList(capacity); + + for (short s = 0; s < capacity; s++) { + slots.add(s); + } + + Vector3i blockPosition = containerState.getBlockPosition(); + long positionHash = blockPosition.hashCode(); + Random rnd = new Random(positionHash); + ShortLists.shuffle(slots, rnd); + boolean anySucceeded = false; + + for (int idx = 0; idx < stacks.size() && idx < slots.size(); idx++) { + short slot = slots.getShort(idx); + ItemStackSlotTransaction transaction = itemContainer.addItemStackToSlot(slot, stacks.get(idx)); + if (transaction.getRemainder() != null && !transaction.getRemainder().isEmpty()) { + LOGGER.at(Level.WARNING) + .log("Could not add Item to Stash at %d, %d, %d: %s", blockPosition.x, blockPosition.y, blockPosition.z, transaction.getRemainder()); + } else { + anySucceeded = true; + } + } + + if (clearDropList && anySucceeded) { + containerState.setDroplist(null); + } + + return new ListTransaction<>(anySucceeded, new ObjectArrayList<>()); + } + } + } + + private static class StashSystem extends RefSystem { + private final ComponentType componentType; + @Nonnull + private final Set> dependencies; + + public StashSystem(ComponentType componentType) { + this.componentType = componentType; + this.dependencies = Set.of(new SystemDependency<>(Order.AFTER, BlockStateModule.LegacyBlockStateRefSystem.class)); + } + + @Override + public Query getQuery() { + return this.componentType; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + World world = store.getExternalData().getWorld(); + if (world.getWorldConfig().getGameMode() != GameMode.Creative) { + StashGameplayConfig stashGameplayConfig = StashGameplayConfig.getOrDefault(world.getGameplayConfig()); + boolean clearContainerDropList = stashGameplayConfig.isClearContainerDropList(); + StashPlugin.stash(store.getComponent(ref, this.componentType), clearContainerDropList); + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/teleporter/TeleporterPlugin.java b/src/com/hypixel/hytale/builtin/adventure/teleporter/TeleporterPlugin.java new file mode 100644 index 0000000..1eb4d92 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/teleporter/TeleporterPlugin.java @@ -0,0 +1,141 @@ +package com.hypixel.hytale.builtin.adventure.teleporter; + +import com.hypixel.hytale.builtin.adventure.teleporter.component.Teleporter; +import com.hypixel.hytale.builtin.adventure.teleporter.interaction.server.TeleporterInteraction; +import com.hypixel.hytale.builtin.adventure.teleporter.page.TeleporterSettingsPageSupplier; +import com.hypixel.hytale.builtin.adventure.teleporter.system.CreateWarpWhenTeleporterPlacedSystem; +import com.hypixel.hytale.builtin.teleport.TeleportPlugin; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenCustomUIInteraction; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TeleporterPlugin extends JavaPlugin { + private static TeleporterPlugin instance; + private ComponentType teleporterComponentType; + + public static TeleporterPlugin get() { + return instance; + } + + public TeleporterPlugin(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + this.teleporterComponentType = this.getChunkStoreRegistry().registerComponent(Teleporter.class, "Teleporter", Teleporter.CODEC); + this.getChunkStoreRegistry().registerSystem(new TeleporterPlugin.TeleporterOwnedWarpRefChangeSystem()); + this.getChunkStoreRegistry().registerSystem(new TeleporterPlugin.TeleporterOwnedWarpRefSystem()); + this.getChunkStoreRegistry().registerSystem(new CreateWarpWhenTeleporterPlacedSystem()); + this.getCodecRegistry(Interaction.CODEC).register("Teleporter", TeleporterInteraction.class, TeleporterInteraction.CODEC); + this.getCodecRegistry(OpenCustomUIInteraction.PAGE_CODEC) + .register("Teleporter", TeleporterSettingsPageSupplier.class, TeleporterSettingsPageSupplier.CODEC); + } + + public ComponentType getTeleporterComponentType() { + return this.teleporterComponentType; + } + + private static class TeleporterOwnedWarpRefChangeSystem extends RefChangeSystem { + private TeleporterOwnedWarpRefChangeSystem() { + } + + @Nonnull + @Override + public ComponentType componentType() { + return Teleporter.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull Teleporter component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentSet( + @Nonnull Ref ref, + @Nullable Teleporter oldComponent, + @Nonnull Teleporter newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + String ownedWarp = oldComponent.getOwnedWarp(); + if (ownedWarp != null && !ownedWarp.isEmpty() && !ownedWarp.equals(newComponent.getOwnedWarp())) { + TeleportPlugin.get().getWarps().remove(ownedWarp.toLowerCase()); + TeleportPlugin.get().saveWarps(); + oldComponent.setOwnedWarp(null); + } + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull Teleporter component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + String ownedWarp = component.getOwnedWarp(); + if (ownedWarp != null && !ownedWarp.isEmpty()) { + TeleportPlugin.get().getWarps().remove(ownedWarp.toLowerCase()); + TeleportPlugin.get().saveWarps(); + component.setOwnedWarp(null); + } + } + + @Nonnull + @Override + public Query getQuery() { + return Query.any(); + } + } + + private static class TeleporterOwnedWarpRefSystem extends RefSystem { + public static final ComponentType COMPONENT_TYPE = Teleporter.getComponentType(); + + private TeleporterOwnedWarpRefSystem() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + switch (reason) { + case LOAD: + Teleporter component = commandBuffer.getComponent(ref, COMPONENT_TYPE); + String ownedWarp = component.getOwnedWarp(); + if (ownedWarp != null && !ownedWarp.isEmpty() && !TeleportPlugin.get().getWarps().containsKey(ownedWarp.toLowerCase())) { + } + case SPAWN: + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + if (reason == RemoveReason.REMOVE) { + Teleporter component = commandBuffer.getComponent(ref, COMPONENT_TYPE); + String ownedWarp = component.getOwnedWarp(); + if (ownedWarp != null && !ownedWarp.isEmpty()) { + TeleportPlugin.get().getWarps().remove(ownedWarp.toLowerCase()); + TeleportPlugin.get().saveWarps(); + component.setOwnedWarp(null); + } + } + } + + @Override + public Query getQuery() { + return COMPONENT_TYPE; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/teleporter/component/Teleporter.java b/src/com/hypixel/hytale/builtin/adventure/teleporter/component/Teleporter.java new file mode 100644 index 0000000..0658d99 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/teleporter/component/Teleporter.java @@ -0,0 +1,188 @@ +package com.hypixel.hytale.builtin.adventure.teleporter.component; + +import com.hypixel.hytale.builtin.adventure.teleporter.TeleporterPlugin; +import com.hypixel.hytale.builtin.teleport.TeleportPlugin; +import com.hypixel.hytale.builtin.teleport.Warp; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.wordlist.WordList; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Teleporter implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(Teleporter.class, Teleporter::new) + .append(new KeyedCodec<>("World", Codec.UUID_BINARY), (teleporter, uuid) -> teleporter.worldUuid = uuid, teleporter -> teleporter.worldUuid) + .add() + .append(new KeyedCodec<>("Transform", Transform.CODEC), (teleporter, transform) -> teleporter.transform = transform, teleporter -> teleporter.transform) + .add() + .append(new KeyedCodec<>("Relative", Codec.BYTE), (teleporter, b) -> teleporter.relativeMask = b, teleporter -> teleporter.relativeMask) + .add() + .append(new KeyedCodec<>("Warp", Codec.STRING), (teleporter, s) -> teleporter.warp = s, teleporter -> teleporter.warp) + .add() + .append(new KeyedCodec<>("OwnedWarp", Codec.STRING), (teleporter, s) -> teleporter.ownedWarp = s, teleporter -> teleporter.ownedWarp) + .add() + .append(new KeyedCodec<>("IsCustomName", Codec.BOOLEAN), (teleporter, s) -> teleporter.isCustomName = s, teleporter -> teleporter.isCustomName) + .add() + .append( + new KeyedCodec<>("WarpNameWordList", Codec.STRING), + (teleporter, s) -> teleporter.warpNameWordListKey = s, + teleporter -> teleporter.warpNameWordListKey + ) + .documentation("The ID of the Word list to select default warp names from") + .add() + .build(); + @Nullable + private UUID worldUuid; + @Nullable + private Transform transform; + private byte relativeMask = 0; + @Nullable + private String warp; + @Deprecated + private String ownedWarp; + private boolean isCustomName; + private String warpNameWordListKey; + + public Teleporter() { + } + + public static ComponentType getComponentType() { + return TeleporterPlugin.get().getTeleporterComponentType(); + } + + @Nullable + public UUID getWorldUuid() { + return this.worldUuid; + } + + public void setWorldUuid(@Nullable UUID worldUuid) { + this.worldUuid = worldUuid; + } + + @Nullable + public Transform getTransform() { + return this.transform; + } + + public void setTransform(@Nullable Transform transform) { + this.transform = transform; + } + + public byte getRelativeMask() { + return this.relativeMask; + } + + public void setRelativeMask(byte relativeMask) { + this.relativeMask = relativeMask; + } + + @Nullable + public String getWarp() { + return this.warp; + } + + public void setWarp(@Nullable String warp) { + this.warp = warp != null && !warp.isEmpty() ? warp : null; + } + + public String getOwnedWarp() { + return this.ownedWarp; + } + + public void setOwnedWarp(String ownedWarp) { + this.ownedWarp = ownedWarp; + } + + public boolean hasOwnedWarp() { + return this.ownedWarp != null && !this.ownedWarp.isEmpty(); + } + + public void setWarpNameWordListKey(String warpNameWordListKey) { + this.warpNameWordListKey = warpNameWordListKey; + } + + public boolean isCustomName() { + return this.isCustomName; + } + + public void setIsCustomName(boolean customName) { + this.isCustomName = customName; + } + + @Nullable + public String getWarpNameWordListKey() { + return this.warpNameWordListKey; + } + + @Nullable + public WordList getWarpNameWordList() { + return WordList.getWordList(this.warpNameWordListKey); + } + + public boolean isValid() { + if (this.warp != null && !this.warp.isEmpty()) { + return TeleportPlugin.get().getWarps().get(this.warp.toLowerCase()) != null; + } else if (this.transform != null) { + return this.worldUuid != null ? Universe.get().getWorld(this.worldUuid) != null : true; + } else { + return false; + } + } + + @Nonnull + @Override + public Component clone() { + Teleporter teleporter = new Teleporter(); + teleporter.worldUuid = this.worldUuid; + teleporter.transform = this.transform != null ? this.transform.clone() : null; + teleporter.relativeMask = this.relativeMask; + teleporter.warp = this.warp; + teleporter.ownedWarp = this.ownedWarp; + teleporter.isCustomName = this.isCustomName; + teleporter.warpNameWordListKey = this.warpNameWordListKey; + return teleporter; + } + + @Nullable + public Teleport toTeleport(@Nonnull Vector3d currentPosition, @Nonnull Vector3f currentRotation, @Nonnull Vector3i blockPosition) { + if (this.warp != null && !this.warp.isEmpty()) { + Warp targetWarp = TeleportPlugin.get().getWarps().get(this.warp.toLowerCase()); + return targetWarp != null ? targetWarp.toTeleport() : null; + } else if (this.transform != null) { + if (this.worldUuid != null) { + World world = Universe.get().getWorld(this.worldUuid); + if (world != null) { + if (this.relativeMask != 0) { + Transform teleportTransform = this.transform.clone(); + Transform.applyMaskedRelativeTransform(teleportTransform, this.relativeMask, currentPosition, currentRotation, blockPosition); + return new Teleport(world, teleportTransform); + } + + return new Teleport(world, this.transform); + } + } + + if (this.relativeMask != 0) { + Transform teleportTransform = this.transform.clone(); + Transform.applyMaskedRelativeTransform(teleportTransform, this.relativeMask, currentPosition, currentRotation, blockPosition); + return new Teleport(teleportTransform); + } else { + return new Teleport(this.transform); + } + } else { + return null; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/teleporter/interaction/server/TeleporterInteraction.java b/src/com/hypixel/hytale/builtin/adventure/teleporter/interaction/server/TeleporterInteraction.java new file mode 100644 index 0000000..73ca0ad --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/teleporter/interaction/server/TeleporterInteraction.java @@ -0,0 +1,137 @@ +package com.hypixel.hytale.builtin.adventure.teleporter.interaction.server; + +import com.hypixel.hytale.builtin.adventure.teleporter.component.Teleporter; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.PendingTeleport; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.universe.world.ParticleUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TeleporterInteraction extends SimpleBlockInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + TeleporterInteraction.class, TeleporterInteraction::new, SimpleBlockInteraction.CODEC + ) + .appendInherited( + new KeyedCodec<>("Particle", Codec.STRING), + (interaction, s) -> interaction.particle = s, + interaction -> interaction.particle, + (interaction, parent) -> interaction.particle = parent.particle + ) + .documentation("The particle to play on the entity when teleporting.") + .add() + .build(); + @Nullable + private String particle; + + public TeleporterInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + ChunkStore chunkStore = world.getChunkStore(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(targetBlock.getX(), targetBlock.getZ()); + BlockComponentChunk blockComponentChunk = chunkStore.getChunkComponent(chunkIndex, BlockComponentChunk.getComponentType()); + if (blockComponentChunk != null) { + int blockIndex = ChunkUtil.indexBlockInColumn(targetBlock.x, targetBlock.y, targetBlock.z); + Ref blockRef = blockComponentChunk.getEntityReference(blockIndex); + if (blockRef != null && blockRef.isValid()) { + BlockModule.BlockStateInfo blockStateInfoComponent = blockRef.getStore().getComponent(blockRef, BlockModule.BlockStateInfo.getComponentType()); + if (blockStateInfoComponent != null) { + Ref chunkRef = blockStateInfoComponent.getChunkRef(); + if (chunkRef != null || chunkRef.isValid()) { + Teleporter teleporter = chunkStore.getStore().getComponent(blockRef, Teleporter.getComponentType()); + if (teleporter != null) { + Ref ref = context.getEntity(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent == null || !playerComponent.isWaitingForClientReady()) { + Archetype archetype = commandBuffer.getArchetype(ref); + if (!archetype.contains(Teleport.getComponentType()) && !archetype.contains(PendingTeleport.getComponentType())) { + if (!teleporter.isValid()) { + WorldChunk worldChunkComponent = chunkRef.getStore().getComponent(chunkRef, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + BlockType blockType = worldChunkComponent.getBlockType(targetBlock.x, targetBlock.y, targetBlock.z); + String currentState = blockType.getStateForBlock(blockType); + if (!"default".equals(currentState)) { + BlockType variantBlockType = blockType.getBlockForState("default"); + if (variantBlockType != null) { + worldChunkComponent.setBlockInteractionState(targetBlock.x, targetBlock.y, targetBlock.z, variantBlockType, "default", true); + } + } + } + + TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Teleport teleportComponent = teleporter.toTeleport(transformComponent.getPosition(), transformComponent.getRotation(), targetBlock); + if (teleportComponent != null) { + commandBuffer.addComponent(ref, Teleport.getComponentType(), teleportComponent); + if (this.particle != null) { + Vector3d particlePosition = transformComponent.getPosition(); + SpatialResource, EntityStore> playerSpatialResource = commandBuffer.getResource( + EntityModule.get().getPlayerSpatialResourceType() + ); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(particlePosition, 75.0, results); + ParticleUtil.spawnParticleEffect(this.particle, particlePosition, results, commandBuffer); + } + } + } + } + } + } + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/teleporter/page/TeleporterSettingsPage.java b/src/com/hypixel/hytale/builtin/adventure/teleporter/page/TeleporterSettingsPage.java new file mode 100644 index 0000000..e189c00 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/teleporter/page/TeleporterSettingsPage.java @@ -0,0 +1,385 @@ +package com.hypixel.hytale.builtin.adventure.teleporter.page; + +import com.hypixel.hytale.builtin.adventure.teleporter.component.Teleporter; +import com.hypixel.hytale.builtin.adventure.teleporter.system.CreateWarpWhenTeleporterPlacedSystem; +import com.hypixel.hytale.builtin.adventure.teleporter.util.CannedWarpNames; +import com.hypixel.hytale.builtin.teleport.TeleportPlugin; +import com.hypixel.hytale.builtin.teleport.Warp; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.ui.DropdownEntryInfo; +import com.hypixel.hytale.server.core.ui.LocalizableString; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TeleporterSettingsPage extends InteractiveCustomUIPage { + @Nonnull + private final Ref blockRef; + private final TeleporterSettingsPage.Mode mode; + @Nullable + private final String activeState; + + public TeleporterSettingsPage( + @Nonnull PlayerRef playerRef, @Nonnull Ref blockRef, TeleporterSettingsPage.Mode mode, @Nullable String activeState + ) { + super(playerRef, CustomPageLifetime.CanDismissOrCloseThroughInteraction, TeleporterSettingsPage.PageEventData.CODEC); + this.blockRef = blockRef; + this.mode = mode; + this.activeState = activeState; + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + String language = this.playerRef.getLanguage(); + Teleporter teleporter = this.blockRef.getStore().getComponent(this.blockRef, Teleporter.getComponentType()); + commandBuilder.append("Pages/Teleporter.ui"); + if (teleporter == null) { + commandBuilder.set("#ErrorScreen.Visible", true); + commandBuilder.set("#FullSettings.Visible", false); + commandBuilder.set("#WarpSettings.Visible", false); + commandBuilder.set("#Buttons.Visible", false); + } else { + commandBuilder.set("#ErrorScreen.Visible", false); + commandBuilder.set("#FullSettings.Visible", this.mode == TeleporterSettingsPage.Mode.FULL); + switch (this.mode) { + case FULL: + byte relativeMask = teleporter.getRelativeMask(); + commandBuilder.set("#BlockRelative #CheckBox.Value", (relativeMask & 64) != 0); + Transform transform = teleporter.getTransform(); + if (transform != null) { + commandBuilder.set("#X #Input.Value", transform.getPosition().getX()); + commandBuilder.set("#Y #Input.Value", transform.getPosition().getY()); + commandBuilder.set("#Z #Input.Value", transform.getPosition().getZ()); + } + + commandBuilder.set("#X #CheckBox.Value", (relativeMask & 1) != 0); + commandBuilder.set("#Y #CheckBox.Value", (relativeMask & 2) != 0); + commandBuilder.set("#Z #CheckBox.Value", (relativeMask & 4) != 0); + if (transform != null) { + commandBuilder.set("#Yaw #Input.Value", transform.getRotation().getYaw()); + commandBuilder.set("#Pitch #Input.Value", transform.getRotation().getPitch()); + commandBuilder.set("#Roll #Input.Value", transform.getRotation().getRoll()); + } + + commandBuilder.set("#Yaw #CheckBox.Value", (relativeMask & 8) != 0); + commandBuilder.set("#Pitch #CheckBox.Value", (relativeMask & 16) != 0); + commandBuilder.set("#Roll #CheckBox.Value", (relativeMask & 32) != 0); + ObjectArrayList worlds = new ObjectArrayList<>(); + worlds.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.teleporter.noWorld"), "")); + + for (World world : Universe.get().getWorlds().values()) { + worlds.add(new DropdownEntryInfo(LocalizableString.fromString(world.getName()), world.getWorldConfig().getUuid().toString())); + } + + commandBuilder.set("#WorldDropdown.Entries", worlds); + UUID worldUuid = teleporter.getWorldUuid(); + commandBuilder.set("#WorldDropdown.Value", worldUuid != null ? worldUuid.toString() : ""); + ObjectArrayList warps = new ObjectArrayList<>(); + warps.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.teleporter.noWarp"), "")); + + for (Warp warp : TeleportPlugin.get().getWarps().values()) { + if (!warp.getId().equalsIgnoreCase(teleporter.getOwnedWarp())) { + warps.add(new DropdownEntryInfo(LocalizableString.fromString(warp.getId()), warp.getId().toLowerCase())); + } + } + + commandBuilder.set("#WarpDropdown.Entries", warps); + commandBuilder.set("#WarpDropdown.Value", teleporter.getWarp() != null ? teleporter.getWarp() : ""); + commandBuilder.set("#NewWarp.Value", teleporter.getOwnedWarp() != null ? teleporter.getOwnedWarp() : ""); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#SaveButton", + new EventData() + .append("@BlockRelative", "#BlockRelative #CheckBox.Value") + .append("@X", "#X #Input.Value") + .append("@Y", "#Y #Input.Value") + .append("@Z", "#Z #Input.Value") + .append("@XIsRelative", "#X #CheckBox.Value") + .append("@YIsRelative", "#Y #CheckBox.Value") + .append("@ZIsRelative", "#Z #CheckBox.Value") + .append("@Yaw", "#Yaw #Input.Value") + .append("@Pitch", "#Pitch #Input.Value") + .append("@Roll", "#Roll #Input.Value") + .append("@YawIsRelative", "#Yaw #CheckBox.Value") + .append("@PitchIsRelative", "#Pitch #CheckBox.Value") + .append("@RollIsRelative", "#Roll #CheckBox.Value") + .append("@World", "#WorldDropdown.Value") + .append("@Warp", "#WarpDropdown.Value") + .append("@NewWarp", "#NewWarp.Value") + ); + break; + case WARP: + List warps = new ObjectArrayList<>(); + warps.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.teleporter.noWarp"), "")); + + for (Warp warpx : TeleportPlugin.get().getWarps().values()) { + if (warpx.getWorld().equals(store.getExternalData().getWorld().getName()) && !warpx.getId().equalsIgnoreCase(teleporter.getOwnedWarp())) { + warps.add(new DropdownEntryInfo(LocalizableString.fromString(warpx.getId()), warpx.getId().toLowerCase())); + } + } + + commandBuilder.set("#WarpDropdown.Entries", warps); + commandBuilder.set("#WarpDropdown.Value", teleporter.getWarp() != null ? teleporter.getWarp() : ""); + Message placeholder; + if (teleporter.hasOwnedWarp() && !teleporter.isCustomName()) { + placeholder = Message.translation(teleporter.getOwnedWarp()); + } else { + String cannedName = CannedWarpNames.generateCannedWarpNameKey(this.blockRef, language); + placeholder = cannedName == null ? Message.translation("server.customUI.teleporter.warpName") : Message.translation(cannedName); + } + + commandBuilder.set("#NewWarp.PlaceholderText", placeholder); + String value = teleporter.isCustomName() && teleporter.getOwnedWarp() != null ? teleporter.getOwnedWarp() : ""; + commandBuilder.set("#NewWarp.Value", value); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#SaveButton", + new EventData().append("@Warp", "#WarpDropdown.Value").append("@NewWarp", "#NewWarp.Value") + ); + } + } + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull TeleporterSettingsPage.PageEventData data) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + String language = this.playerRef.getLanguage(); + BlockModule.BlockStateInfo blockStateInfo = this.blockRef.getStore().getComponent(this.blockRef, BlockModule.BlockStateInfo.getComponentType()); + if (blockStateInfo == null) { + playerComponent.getPageManager().setPage(ref, store, Page.None); + } else { + Ref chunkRef = blockStateInfo.getChunkRef(); + if (!chunkRef.isValid()) { + playerComponent.getPageManager().setPage(ref, store, Page.None); + } else { + WorldChunk worldChunkComponent = chunkRef.getStore().getComponent(chunkRef, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + int index = blockStateInfo.getIndex(); + int targetX = ChunkUtil.xFromBlockInColumn(index); + int targetY = ChunkUtil.yFromBlockInColumn(index); + int targetZ = ChunkUtil.zFromBlockInColumn(index); + new Vector3i(targetX, targetY, targetZ); + Teleporter teleporter = this.blockRef.getStore().getComponent(this.blockRef, Teleporter.getComponentType()); + String oldOwnedWarp = teleporter.getOwnedWarp(); + boolean customName = true; + if (data.ownedWarp == null || data.ownedWarp.isEmpty()) { + data.ownedWarp = CannedWarpNames.generateCannedWarpName(this.blockRef, language); + customName = false; + if (data.ownedWarp == null) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#NewWarp.PlaceholderText", Message.translation("server.customUI.teleporter.warpNameRightHereHint")); + commandBuilder.set("#ErrorLabel.Text", Message.translation("server.customUI.teleporter.errorMissingWarpName")); + commandBuilder.set("#ErrorLabel.Visible", true); + this.sendUpdate(commandBuilder); + return; + } + } + + if (!data.ownedWarp.equalsIgnoreCase(oldOwnedWarp)) { + boolean alreadyExists = TeleportPlugin.get().getWarps().containsKey(data.ownedWarp.toLowerCase()); + if (alreadyExists) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#ErrorLabel.Text", Message.translation("server.customUI.teleporter.errorWarpAlreadyExists")); + commandBuilder.set("#ErrorLabel.Visible", true); + this.sendUpdate(commandBuilder); + return; + } + } + + if (oldOwnedWarp != null && !oldOwnedWarp.isEmpty()) { + TeleportPlugin.get().getWarps().remove(oldOwnedWarp.toLowerCase()); + } + + playerComponent.getPageManager().setPage(ref, store, Page.None); + CreateWarpWhenTeleporterPlacedSystem.createWarp(worldChunkComponent, blockStateInfo, data.ownedWarp); + teleporter.setOwnedWarp(data.ownedWarp); + teleporter.setIsCustomName(customName); + switch (this.mode) { + case FULL: + teleporter.setWorldUuid(data.world != null && !data.world.isEmpty() ? UUID.fromString(data.world) : null); + Transform transform = new Transform(); + transform.getPosition().setX(data.x); + transform.getPosition().setY(data.y); + transform.getPosition().setZ(data.z); + transform.getRotation().setYaw(data.yaw); + transform.getRotation().setPitch(data.pitch); + transform.getRotation().setRoll(data.roll); + teleporter.setTransform(transform); + teleporter.setRelativeMask( + (byte)( + (data.xIsRelative ? 1 : 0) + | (data.yIsRelative ? 2 : 0) + | (data.zIsRelative ? 4 : 0) + | (data.yawIsRelative ? 8 : 0) + | (data.pitchIsRelative ? 16 : 0) + | (data.rollIsRelative ? 32 : 0) + | (data.isBlockRelative ? 64 : 0) + ) + ); + teleporter.setWarp(data.warp != null && !data.warp.isEmpty() ? data.warp : null); + break; + case WARP: + teleporter.setWorldUuid(null); + teleporter.setTransform(null); + teleporter.setWarp(data.warp != null && !data.warp.isEmpty() ? data.warp : null); + } + + String newState = "default"; + if (teleporter.isValid()) { + newState = this.activeState != null ? this.activeState : "default"; + } + + BlockType blockType = worldChunkComponent.getBlockType(targetX, targetY, targetZ); + String currentState = blockType.getStateForBlock(blockType); + if (currentState == null || !currentState.equals(newState)) { + BlockType variantBlockType = blockType.getBlockForState(newState); + if (variantBlockType != null) { + worldChunkComponent.setBlockInteractionState(targetX, targetY, targetZ, variantBlockType, newState, true); + } + } + + blockStateInfo.markNeedsSaving(); + } + } + } + + public static enum Mode { + FULL, + WARP; + + public static final Codec CODEC = new EnumCodec<>(TeleporterSettingsPage.Mode.class); + + private Mode() { + } + } + + public static class PageEventData { + public static final String KEY_BLOCK_RELATIVE = "@BlockRelative"; + public static final String KEY_X = "@X"; + public static final String KEY_Y = "@Y"; + public static final String KEY_Z = "@Z"; + public static final String KEY_X_IS_RELATIVE = "@XIsRelative"; + public static final String KEY_Y_IS_RELATIVE = "@YIsRelative"; + public static final String KEY_Z_IS_RELATIVE = "@ZIsRelative"; + public static final String KEY_YAW = "@Yaw"; + public static final String KEY_PITCH = "@Pitch"; + public static final String KEY_ROLL = "@Roll"; + public static final String KEY_YAW_IS_RELATIVE = "@YawIsRelative"; + public static final String KEY_PITCH_IS_RELATIVE = "@PitchIsRelative"; + public static final String KEY_ROLL_IS_RELATIVE = "@RollIsRelative"; + public static final String KEY_WORLD = "@World"; + public static final String KEY_WARP = "@Warp"; + public static final String KEY_NEW_WARP = "@NewWarp"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + TeleporterSettingsPage.PageEventData.class, TeleporterSettingsPage.PageEventData::new + ) + .append( + new KeyedCodec<>("@BlockRelative", Codec.BOOLEAN), + (pageEventData, o) -> pageEventData.isBlockRelative = o, + pageEventData -> pageEventData.isBlockRelative + ) + .add() + .append(new KeyedCodec<>("@X", Codec.DOUBLE), (pageEventData, o) -> pageEventData.x = o, pageEventData -> pageEventData.x) + .add() + .append(new KeyedCodec<>("@Y", Codec.DOUBLE), (pageEventData, o) -> pageEventData.y = o, pageEventData -> pageEventData.y) + .add() + .append(new KeyedCodec<>("@Z", Codec.DOUBLE), (pageEventData, o) -> pageEventData.z = o, pageEventData -> pageEventData.z) + .add() + .append( + new KeyedCodec<>("@XIsRelative", Codec.BOOLEAN), (pageEventData, o) -> pageEventData.xIsRelative = o, pageEventData -> pageEventData.xIsRelative + ) + .add() + .append( + new KeyedCodec<>("@YIsRelative", Codec.BOOLEAN), (pageEventData, o) -> pageEventData.yIsRelative = o, pageEventData -> pageEventData.yIsRelative + ) + .add() + .append( + new KeyedCodec<>("@ZIsRelative", Codec.BOOLEAN), (pageEventData, o) -> pageEventData.zIsRelative = o, pageEventData -> pageEventData.zIsRelative + ) + .add() + .append(new KeyedCodec<>("@Yaw", Codec.FLOAT), (pageEventData, o) -> pageEventData.yaw = o, pageEventData -> pageEventData.yaw) + .add() + .append(new KeyedCodec<>("@Pitch", Codec.FLOAT), (pageEventData, o) -> pageEventData.pitch = o, pageEventData -> pageEventData.pitch) + .add() + .append(new KeyedCodec<>("@Roll", Codec.FLOAT), (pageEventData, o) -> pageEventData.roll = o, pageEventData -> pageEventData.roll) + .add() + .append( + new KeyedCodec<>("@YawIsRelative", Codec.BOOLEAN), + (pageEventData, o) -> pageEventData.yawIsRelative = o, + pageEventData -> pageEventData.yawIsRelative + ) + .add() + .append( + new KeyedCodec<>("@PitchIsRelative", Codec.BOOLEAN), + (pageEventData, o) -> pageEventData.pitchIsRelative = o, + pageEventData -> pageEventData.pitchIsRelative + ) + .add() + .append( + new KeyedCodec<>("@RollIsRelative", Codec.BOOLEAN), + (pageEventData, o) -> pageEventData.rollIsRelative = o, + pageEventData -> pageEventData.pitchIsRelative + ) + .add() + .append(new KeyedCodec<>("@World", Codec.STRING), (pageEventData, o) -> pageEventData.world = o, pageEventData -> pageEventData.world) + .add() + .append(new KeyedCodec<>("@Warp", Codec.STRING), (pageEventData, o) -> pageEventData.warp = o, pageEventData -> pageEventData.warp) + .add() + .append(new KeyedCodec<>("@NewWarp", Codec.STRING), (pageEventData, o) -> pageEventData.ownedWarp = o, pageEventData -> pageEventData.ownedWarp) + .add() + .build(); + public boolean isBlockRelative; + public double x; + public double y; + public double z; + public boolean xIsRelative; + public boolean yIsRelative; + public boolean zIsRelative; + public float yaw; + public float pitch; + public float roll; + public boolean yawIsRelative; + public boolean pitchIsRelative; + public boolean rollIsRelative; + public String world; + public String warp; + @Nullable + public String ownedWarp; + + public PageEventData() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/teleporter/page/TeleporterSettingsPageSupplier.java b/src/com/hypixel/hytale/builtin/adventure/teleporter/page/TeleporterSettingsPageSupplier.java new file mode 100644 index 0000000..68474cc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/teleporter/page/TeleporterSettingsPageSupplier.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.builtin.adventure.teleporter.page; + +import com.hypixel.hytale.builtin.adventure.teleporter.component.Teleporter; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.player.pages.CustomUIPage; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenCustomUIInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TeleporterSettingsPageSupplier implements OpenCustomUIInteraction.CustomPageSupplier { + public static final BuilderCodec CODEC = BuilderCodec.builder( + TeleporterSettingsPageSupplier.class, TeleporterSettingsPageSupplier::new + ) + .appendInherited( + new KeyedCodec<>("Create", Codec.BOOLEAN), + (supplier, b) -> supplier.create = b, + supplier -> supplier.create, + (supplier, parent) -> supplier.create = parent.create + ) + .add() + .appendInherited( + new KeyedCodec<>("Mode", TeleporterSettingsPage.Mode.CODEC), + (supplier, o) -> supplier.mode = o, + supplier -> supplier.mode, + (supplier, parent) -> supplier.mode = parent.mode + ) + .add() + .appendInherited( + new KeyedCodec<>("ActiveState", Codec.STRING), + (supplier, o) -> supplier.activeState = o, + supplier -> supplier.activeState, + (supplier, parent) -> supplier.activeState = parent.activeState + ) + .add() + .build(); + private boolean create = true; + private TeleporterSettingsPage.Mode mode = TeleporterSettingsPage.Mode.FULL; + @Nullable + private String activeState; + + public TeleporterSettingsPageSupplier() { + } + + @Nullable + @Override + public CustomUIPage tryCreate( + @Nonnull Ref ref, ComponentAccessor componentAccessor, @Nonnull PlayerRef playerRef, @Nonnull InteractionContext context + ) { + BlockPosition targetBlock = context.getTargetBlock(); + if (targetBlock == null) { + return null; + } else { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + ChunkStore chunkStore = world.getChunkStore(); + Ref chunkRef = chunkStore.getChunkReference(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)); + BlockComponentChunk blockComponentChunk = chunkRef == null + ? null + : chunkStore.getStore().getComponent(chunkRef, BlockComponentChunk.getComponentType()); + if (blockComponentChunk == null) { + return null; + } else { + int blockIndex = ChunkUtil.indexBlockInColumn(targetBlock.x, targetBlock.y, targetBlock.z); + Ref blockRef = blockComponentChunk.getEntityReference(blockIndex); + if (blockRef == null || !blockRef.isValid()) { + if (!this.create) { + return null; + } + + Holder holder = ChunkStore.REGISTRY.newHolder(); + holder.putComponent(BlockModule.BlockStateInfo.getComponentType(), new BlockModule.BlockStateInfo(blockIndex, chunkRef)); + holder.ensureComponent(Teleporter.getComponentType()); + blockRef = world.getChunkStore().getStore().addEntity(holder, AddReason.SPAWN); + } + + return new TeleporterSettingsPage(playerRef, blockRef, this.mode, this.activeState); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/teleporter/system/CreateWarpWhenTeleporterPlacedSystem.java b/src/com/hypixel/hytale/builtin/adventure/teleporter/system/CreateWarpWhenTeleporterPlacedSystem.java new file mode 100644 index 0000000..1a17e32 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/teleporter/system/CreateWarpWhenTeleporterPlacedSystem.java @@ -0,0 +1,144 @@ +package com.hypixel.hytale.builtin.adventure.teleporter.system; + +import com.hypixel.hytale.builtin.adventure.teleporter.component.Teleporter; +import com.hypixel.hytale.builtin.adventure.teleporter.util.CannedWarpNames; +import com.hypixel.hytale.builtin.teleport.TeleportPlugin; +import com.hypixel.hytale.builtin.teleport.Warp; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.interaction.components.PlacedByInteractionComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CreateWarpWhenTeleporterPlacedSystem extends RefChangeSystem { + @Nonnull + public static final Vector3d WARP_OFFSET = new Vector3d(-3.5, 0.0, -3.5); + + public CreateWarpWhenTeleporterPlacedSystem() { + } + + public void onComponentAdded( + @Nonnull Ref ref, + @Nonnull PlacedByInteractionComponent placedBy, + @Nonnull Store chunkStore, + @Nonnull CommandBuffer commandBuffer + ) { + World world = chunkStore.getExternalData().getWorld(); + EntityStore entityStore = world.getEntityStore(); + UUID whoPlacedUuid = placedBy.getWhoPlacedUuid(); + Ref whoPlacedRef = entityStore.getRefFromUUID(whoPlacedUuid); + if (whoPlacedRef != null && whoPlacedRef.isValid()) { + PlayerRef playerRefComponent = entityStore.getStore().getComponent(whoPlacedRef, PlayerRef.getComponentType()); + String language = playerRefComponent == null ? null : playerRefComponent.getLanguage(); + if (language != null) { + BlockModule.BlockStateInfo blockStateInfoComponent = commandBuffer.getComponent(ref, BlockModule.BlockStateInfo.getComponentType()); + + assert blockStateInfoComponent != null; + + Ref chunkRef = blockStateInfoComponent.getChunkRef(); + if (chunkRef != null && chunkRef.isValid()) { + WorldChunk worldChunk = chunkStore.getComponent(chunkRef, WorldChunk.getComponentType()); + if (worldChunk != null) { + String cannedName = CannedWarpNames.generateCannedWarpName(ref, language); + if (cannedName != null) { + createWarp(worldChunk, blockStateInfoComponent, cannedName); + Teleporter teleporterComponent = commandBuffer.getComponent(ref, Teleporter.getComponentType()); + + assert teleporterComponent != null; + + teleporterComponent.setOwnedWarp(cannedName); + } + } + } + } + } + } + + public static void createWarp(@Nonnull WorldChunk worldChunk, @Nonnull BlockModule.BlockStateInfo blockStateInfo, @Nonnull String name) { + int chunkBlockX = worldChunk.getX() << 5; + int chunkBlockZ = worldChunk.getZ() << 5; + int index = blockStateInfo.getIndex(); + int x = chunkBlockX + ChunkUtil.xFromBlockInColumn(index); + int y = ChunkUtil.yFromBlockInColumn(index); + int z = chunkBlockZ + ChunkUtil.zFromBlockInColumn(index); + BlockChunk blockChunkComponent = worldChunk.getBlockChunk(); + + assert blockChunkComponent != null; + + BlockSection section = blockChunkComponent.getSectionAtBlockY(y); + int rotationIndex = section.getRotationIndex(x, y, z); + RotationTuple rotationTuple = RotationTuple.get(rotationIndex); + Rotation rotationYaw = rotationTuple.yaw(); + Vector3i rotationTupleAxis = rotationTuple.yaw().getAxisDirection(); + Vector3d offset = new Vector3d(WARP_OFFSET.getX() * rotationTupleAxis.x, 0.0, WARP_OFFSET.getZ() * rotationTupleAxis.z); + float warpRotationYaw = (float)rotationYaw.getRadians() + (float)Math.toRadians(180.0); + Vector3d warpPosition = new Vector3d(x, y, z).add(offset).add(0.5, 0.0, 0.5); + String warpId = name.toLowerCase(); + TeleportPlugin.get() + .getWarps() + .put( + warpId, + new Warp( + warpPosition.getX(), + warpPosition.getY(), + warpPosition.getZ(), + warpRotationYaw, + Float.NaN, + Float.NaN, + name, + worldChunk.getWorld(), + "*Teleporter", + Instant.now() + ) + ); + TeleportPlugin.get().saveWarps(); + } + + public void onComponentSet( + @Nonnull Ref ref, + @Nullable PlacedByInteractionComponent oldComponent, + @Nonnull PlacedByInteractionComponent newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, + @Nonnull PlacedByInteractionComponent component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + @Nonnull + @Override + public ComponentType componentType() { + return PlacedByInteractionComponent.getComponentType(); + } + + @Nullable + @Override + public Query getQuery() { + return Query.and(PlacedByInteractionComponent.getComponentType(), Teleporter.getComponentType(), BlockModule.BlockStateInfo.getComponentType()); + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/teleporter/util/CannedWarpNames.java b/src/com/hypixel/hytale/builtin/adventure/teleporter/util/CannedWarpNames.java new file mode 100644 index 0000000..cf8257a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/teleporter/util/CannedWarpNames.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.builtin.adventure.teleporter.util; + +import com.hypixel.hytale.builtin.adventure.teleporter.component.Teleporter; +import com.hypixel.hytale.builtin.teleport.TeleportPlugin; +import com.hypixel.hytale.builtin.teleport.Warp; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.wordlist.WordList; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.i18n.I18nModule; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class CannedWarpNames { + private CannedWarpNames() { + } + + @Nullable + public static String generateCannedWarpName(Ref blockRef, String language) { + String translationKey = generateCannedWarpNameKey(blockRef, language); + return translationKey == null ? null : I18nModule.get().getMessage(language, translationKey); + } + + @Nullable + public static String generateCannedWarpNameKey(Ref blockRef, String language) { + Store store = blockRef.getStore(); + World world = store.getExternalData().getWorld(); + BlockModule.BlockStateInfo blockStateInfo = store.getComponent(blockRef, BlockModule.BlockStateInfo.getComponentType()); + Random random = blockStateInfo == null ? new Random() : new Random(blockStateInfo.getIndex()); + Teleporter teleporter = store.getComponent(blockRef, Teleporter.getComponentType()); + String wordListKey = teleporter == null ? null : teleporter.getWarpNameWordListKey(); + Set existingNames = getWarpNamesInWorld(world); + if (teleporter != null) { + String ownName = teleporter.getOwnedWarp(); + if (ownName != null && !teleporter.isCustomName()) { + existingNames.remove(ownName); + } + } + + return WordList.getWordList(wordListKey).pickTranslationKey(random, existingNames, language); + } + + @Nonnull + private static Set getWarpNamesInWorld(World world) { + Set existingNames = new HashSet<>(); + + for (Warp warp : TeleportPlugin.get().getWarps().values()) { + if (warp.getWorld().equals(world.getName())) { + existingNames.add(warp.getId()); + } + } + + return existingNames; + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/worldlocationcondition/NeighbourBlockTagsLocationCondition.java b/src/com/hypixel/hytale/builtin/adventure/worldlocationcondition/NeighbourBlockTagsLocationCondition.java new file mode 100644 index 0000000..61880d8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/worldlocationcondition/NeighbourBlockTagsLocationCondition.java @@ -0,0 +1,159 @@ +package com.hypixel.hytale.builtin.adventure.worldlocationcondition; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.range.IntRange; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.tagpattern.config.TagPattern; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.accessor.BlockAccessor; +import com.hypixel.hytale.server.core.universe.world.accessor.ChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.worldlocationcondition.WorldLocationCondition; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NeighbourBlockTagsLocationCondition extends WorldLocationCondition { + public static final BuilderCodec CODEC = BuilderCodec.builder( + NeighbourBlockTagsLocationCondition.class, NeighbourBlockTagsLocationCondition::new, WorldLocationCondition.BASE_CODEC + ) + .append( + new KeyedCodec<>("TagPattern", Codec.STRING), + (neighbourBlockTagsLocationCondition, s) -> neighbourBlockTagsLocationCondition.tagPatternId = s, + neighbourBlockTagsLocationCondition -> neighbourBlockTagsLocationCondition.tagPatternId + ) + .documentation("A TagPattern can be used if the block at the chosen location needs to fulfill specific conditions.") + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("NeighbourBlock", new EnumCodec<>(NeighbourBlockTagsLocationCondition.NeighbourDirection.class)), + (neighbourBlockTagsLocationCondition, neighbourDirection) -> neighbourBlockTagsLocationCondition.neighbourDirection = neighbourDirection, + neighbourBlockTagsLocationCondition -> neighbourBlockTagsLocationCondition.neighbourDirection + ) + .documentation("Defines which block has to be checked related to original location. Possible values: Above, Below, Sideways.") + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("Support", IntRange.CODEC), + (neighbourBlockTagsLocationCondition, blockSupport) -> neighbourBlockTagsLocationCondition.support = blockSupport, + neighbourBlockTagsLocationCondition -> neighbourBlockTagsLocationCondition.support + ) + .documentation("Additional field used if NeighbourBlock is set to Sideways.") + .add() + .build(); + protected String tagPatternId; + protected NeighbourBlockTagsLocationCondition.NeighbourDirection neighbourDirection; + protected IntRange support = new IntRange(1, 4); + + public NeighbourBlockTagsLocationCondition() { + } + + @Override + public boolean test(World world, int worldX, int worldY, int worldZ) { + if (worldY <= 0) { + return false; + } else { + WorldChunk worldChunk = world.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(worldX, worldZ)); + if (worldChunk == null) { + return false; + } else if (this.neighbourDirection == NeighbourBlockTagsLocationCondition.NeighbourDirection.SIDEWAYS) { + int count = 0; + ChunkAccessor chunkAccessor = worldChunk.getChunkAccessor(); + if (this.checkBlockHasTag(worldX - 1, worldY, worldZ, chunkAccessor.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(worldX - 1, worldZ)))) { + count++; + } + + if (this.checkBlockHasTag(worldX + 1, worldY, worldZ, chunkAccessor.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(worldX + 1, worldZ)))) { + count++; + } + + if (this.checkBlockHasTag(worldX, worldY, worldZ - 1, chunkAccessor.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(worldX, worldZ - 1)))) { + count++; + } + + if (this.checkBlockHasTag(worldX, worldY, worldZ + 1, chunkAccessor.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(worldX, worldZ + 1)))) { + count++; + } + + return this.support.includes(count); + } else { + int yPos = worldY; + switch (this.neighbourDirection) { + case ABOVE: + yPos = worldY + 1; + break; + case BELOW: + yPos = worldY - 1; + } + + return this.checkBlockHasTag(worldX, yPos, worldZ, worldChunk); + } + } + } + + private boolean checkBlockHasTag(int x, int y, int z, @Nonnull BlockAccessor worldChunk) { + int blockIndex = worldChunk.getBlock(x, y, z); + TagPattern tagPattern = TagPattern.getAssetMap().getAsset(this.tagPatternId); + if (tagPattern != null) { + AssetExtraInfo.Data data = BlockType.getAssetMap().getAsset(blockIndex).getData(); + return data == null ? false : tagPattern.test(data.getTags()); + } else { + HytaleLogger.getLogger().at(Level.WARNING).log("No TagPattern asset found for id: " + this.tagPatternId); + return false; + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + NeighbourBlockTagsLocationCondition that = (NeighbourBlockTagsLocationCondition)o; + if (!this.tagPatternId.equals(that.tagPatternId)) { + return false; + } else if (this.neighbourDirection != that.neighbourDirection) { + return false; + } else { + return this.support != null ? this.support.equals(that.support) : that.support == null; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.tagPatternId.hashCode(); + result = 31 * result + this.neighbourDirection.hashCode(); + return 31 * result + (this.support != null ? this.support.hashCode() : 0); + } + + @Nonnull + @Override + public String toString() { + return "NeighbourBlockTagsLocationCondition{tagPatternId='" + + this.tagPatternId + + "', neighbourDirection=" + + this.neighbourDirection + + ", support=" + + this.support + + "} " + + super.toString(); + } + + private static enum NeighbourDirection { + ABOVE, + BELOW, + SIDEWAYS; + + private NeighbourDirection() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/adventure/worldlocationcondition/WorldLocationConditionPlugin.java b/src/com/hypixel/hytale/builtin/adventure/worldlocationcondition/WorldLocationConditionPlugin.java new file mode 100644 index 0000000..d8e5613 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/adventure/worldlocationcondition/WorldLocationConditionPlugin.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.builtin.adventure.worldlocationcondition; + +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.worldlocationcondition.WorldLocationCondition; +import javax.annotation.Nonnull; + +public class WorldLocationConditionPlugin extends JavaPlugin { + public WorldLocationConditionPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + WorldLocationCondition.CODEC.register("NeighbourBlockTags", NeighbourBlockTagsLocationCondition.class, NeighbourBlockTagsLocationCondition.CODEC); + } +} diff --git a/src/com/hypixel/hytale/builtin/ambience/AmbiencePlugin.java b/src/com/hypixel/hytale/builtin/ambience/AmbiencePlugin.java new file mode 100644 index 0000000..0075ec0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/ambience/AmbiencePlugin.java @@ -0,0 +1,101 @@ +package com.hypixel.hytale.builtin.ambience; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.builtin.ambience.commands.AmbienceCommands; +import com.hypixel.hytale.builtin.ambience.components.AmbienceTracker; +import com.hypixel.hytale.builtin.ambience.components.AmbientEmitterComponent; +import com.hypixel.hytale.builtin.ambience.resources.AmbienceResource; +import com.hypixel.hytale.builtin.ambience.systems.AmbientEmitterSystems; +import com.hypixel.hytale.builtin.ambience.systems.ForcedMusicSystems; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.Config; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class AmbiencePlugin extends JavaPlugin { + private static final String DEFAULT_AMBIENT_EMITTER_MODEL = "NPC_Spawn_Marker"; + private static AmbiencePlugin instance; + private ComponentType ambienceTrackerComponentType; + private ComponentType ambientEmitterComponentType; + private ResourceType ambienceResourceType; + private final Config config = this.withConfig("AmbiencePlugin", AmbiencePlugin.AmbiencePluginConfig.CODEC); + private Model ambientEmitterModel; + + public static AmbiencePlugin get() { + return instance; + } + + public AmbiencePlugin(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + this.ambienceTrackerComponentType = this.getEntityStoreRegistry().registerComponent(AmbienceTracker.class, AmbienceTracker::new); + this.ambientEmitterComponentType = this.getEntityStoreRegistry() + .registerComponent(AmbientEmitterComponent.class, "AmbientEmitter", AmbientEmitterComponent.CODEC); + this.ambienceResourceType = this.getEntityStoreRegistry().registerResource(AmbienceResource.class, AmbienceResource::new); + this.getEntityStoreRegistry().registerSystem(new AmbientEmitterSystems.EntityAdded()); + this.getEntityStoreRegistry().registerSystem(new AmbientEmitterSystems.EntityRefAdded()); + this.getEntityStoreRegistry().registerSystem(new AmbientEmitterSystems.Ticking()); + this.getEntityStoreRegistry().registerSystem(new ForcedMusicSystems.Tick()); + this.getEntityStoreRegistry().registerSystem(new ForcedMusicSystems.PlayerAdded()); + this.getCommandRegistry().registerCommand(new AmbienceCommands()); + } + + @Override + protected void start() { + AmbiencePlugin.AmbiencePluginConfig config = this.config.get(); + String ambientEmitterModelId = config.ambientEmitterModel; + DefaultAssetMap modelAssetMap = ModelAsset.getAssetMap(); + ModelAsset modelAsset = modelAssetMap.getAsset(ambientEmitterModelId); + if (modelAsset == null) { + this.getLogger().at(Level.SEVERE).log("Ambient emitter model %s does not exist"); + modelAsset = modelAssetMap.getAsset("NPC_Spawn_Marker"); + if (modelAsset == null) { + throw new IllegalStateException(String.format("Default ambient emitter marker '%s' not found", "NPC_Spawn_Marker")); + } + } + + this.ambientEmitterModel = Model.createUnitScaleModel(modelAsset); + } + + public ComponentType getAmbienceTrackerComponentType() { + return this.ambienceTrackerComponentType; + } + + public ComponentType getAmbientEmitterComponentType() { + return this.ambientEmitterComponentType; + } + + public ResourceType getAmbienceResourceType() { + return this.ambienceResourceType; + } + + public Model getAmbientEmitterModel() { + return this.ambientEmitterModel; + } + + public static class AmbiencePluginConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder( + AmbiencePlugin.AmbiencePluginConfig.class, AmbiencePlugin.AmbiencePluginConfig::new + ) + .append(new KeyedCodec<>("AmbientEmitterModel", Codec.STRING), (o, i) -> o.ambientEmitterModel = i, o -> o.ambientEmitterModel) + .add() + .build(); + private String ambientEmitterModel = "NPC_Spawn_Marker"; + + public AmbiencePluginConfig() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/ambience/commands/AmbienceClearCommand.java b/src/com/hypixel/hytale/builtin/ambience/commands/AmbienceClearCommand.java new file mode 100644 index 0000000..0b14ab5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/ambience/commands/AmbienceClearCommand.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.builtin.ambience.commands; + +import com.hypixel.hytale.builtin.ambience.resources.AmbienceResource; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class AmbienceClearCommand extends AbstractWorldCommand { + public AmbienceClearCommand() { + super("clear", "server.commands.ambience.clear.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + AmbienceResource resource = store.getResource(AmbienceResource.getResourceType()); + resource.setForcedMusicAmbience(null); + context.sendMessage(Message.translation("server.commands.ambience.clear.success")); + } +} diff --git a/src/com/hypixel/hytale/builtin/ambience/commands/AmbienceCommands.java b/src/com/hypixel/hytale/builtin/ambience/commands/AmbienceCommands.java new file mode 100644 index 0000000..02003b2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/ambience/commands/AmbienceCommands.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.builtin.ambience.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class AmbienceCommands extends AbstractCommandCollection { + public AmbienceCommands() { + super("ambience", "server.commands.ambience.desc"); + this.addAliases("ambiance"); + this.addAliases("ambient"); + this.addSubCommand(new AmbienceCommands.AmbienceEmitterCommands()); + this.addSubCommand(new AmbienceSetMusicCommand()); + this.addSubCommand(new AmbienceClearCommand()); + } + + public static class AmbienceEmitterCommands extends AbstractCommandCollection { + public AmbienceEmitterCommands() { + super("emitter", "server.commands.ambience.emitter.desc"); + this.addSubCommand(new AmbienceEmitterAddCommand()); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/ambience/commands/AmbienceEmitterAddCommand.java b/src/com/hypixel/hytale/builtin/ambience/commands/AmbienceEmitterAddCommand.java new file mode 100644 index 0000000..cbc855a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/ambience/commands/AmbienceEmitterAddCommand.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.builtin.ambience.commands; + +import com.hypixel.hytale.builtin.ambience.AmbiencePlugin; +import com.hypixel.hytale.builtin.ambience.components.AmbientEmitterComponent; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEventLayer; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.AssetArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.nameplate.Nameplate; +import com.hypixel.hytale.server.core.modules.entity.component.HiddenFromAdventurePlayers; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.PersistentModel; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class AmbienceEmitterAddCommand extends AbstractPlayerCommand { + @Nonnull + private static final AssetArgumentType SOUND_EVENT_ASSET_TYPE = new AssetArgumentType( + "server.commands.ambience.emitter.add.arg.soundEvent.name", SoundEvent.class, "server.commands.ambience.emitter.add.arg.soundEvent.usage" + ); + @Nonnull + private final RequiredArg soundEventArg = this.withRequiredArg( + "soundEvent", "server.commands.ambience.emitter.add.arg.soundEvent.desc", SOUND_EVENT_ASSET_TYPE + ); + + public AmbienceEmitterAddCommand() { + super("add", "server.commands.ambience.emitter.add.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + if (!context.isPlayer()) { + throw new GeneralCommandException(Message.translation("server.commands.errors.playerOnly")); + } else { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Holder holder = EntityStore.REGISTRY.newHolder(); + SoundEvent soundEvent = this.soundEventArg.get(context); + boolean looping = false; + + for (SoundEventLayer layer : soundEvent.getLayers()) { + if (layer.isLooping()) { + looping = true; + break; + } + } + + if (!looping) { + context.sendMessage(Message.translation("server.commands.ambience.emitter.add.notLooping").param("soundEventId", soundEvent.getId())); + } else { + AmbientEmitterComponent emitterComponent = new AmbientEmitterComponent(); + emitterComponent.setSoundEventId(soundEvent.getId()); + holder.addComponent(AmbientEmitterComponent.getComponentType(), emitterComponent); + TransformComponent emitterTransform = transformComponent.clone(); + holder.addComponent(TransformComponent.getComponentType(), emitterTransform); + holder.addComponent(Nameplate.getComponentType(), new Nameplate(soundEvent.getId())); + holder.ensureComponent(UUIDComponent.getComponentType()); + holder.ensureComponent(HiddenFromAdventurePlayers.getComponentType()); + Model model = AmbiencePlugin.get().getAmbientEmitterModel(); + holder.addComponent(ModelComponent.getComponentType(), new ModelComponent(model)); + holder.addComponent(PersistentModel.getComponentType(), new PersistentModel(model.toReference())); + Ref emitterRef = store.addEntity(holder, AddReason.SPAWN); + if (emitterRef != null && emitterRef.isValid()) { + context.sendMessage(Message.translation("server.commands.ambience.emitter.add.added").param("soundEventId", soundEvent.getId())); + } else { + context.sendMessage(Message.translation("server.commands.ambience.emitter.add.failed").param("soundEventId", soundEvent.getId())); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/ambience/commands/AmbienceSetMusicCommand.java b/src/com/hypixel/hytale/builtin/ambience/commands/AmbienceSetMusicCommand.java new file mode 100644 index 0000000..fc80935 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/ambience/commands/AmbienceSetMusicCommand.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.builtin.ambience.commands; + +import com.hypixel.hytale.builtin.ambience.resources.AmbienceResource; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.ambiencefx.config.AmbienceFX; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class AmbienceSetMusicCommand extends AbstractWorldCommand { + private final RequiredArg ambienceFxIdArg = this.withRequiredArg( + "ambienceFxId", "server.commands.ambience.setmusic.arg.ambiencefxid.desc", ArgTypes.AMBIENCE_FX_ASSET + ); + + public AmbienceSetMusicCommand() { + super("setmusic", "server.commands.ambience.setmusic.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + AmbienceFX ambienceFX = this.ambienceFxIdArg.get(context); + AmbienceResource resource = store.getResource(AmbienceResource.getResourceType()); + resource.setForcedMusicAmbience(ambienceFX.getId()); + context.sendMessage(Message.translation("server.commands.ambience.setmusic.success").param("ambience", ambienceFX.getId())); + } +} diff --git a/src/com/hypixel/hytale/builtin/ambience/components/AmbienceTracker.java b/src/com/hypixel/hytale/builtin/ambience/components/AmbienceTracker.java new file mode 100644 index 0000000..06b05a9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/ambience/components/AmbienceTracker.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.builtin.ambience.components; + +import com.hypixel.hytale.builtin.ambience.AmbiencePlugin; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.protocol.packets.world.UpdateEnvironmentMusic; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nullable; + +public class AmbienceTracker implements Component { + private final UpdateEnvironmentMusic musicPacket = new UpdateEnvironmentMusic(0); + private int forcedMusicIndex; + + public AmbienceTracker() { + } + + public static ComponentType getComponentType() { + return AmbiencePlugin.get().getAmbienceTrackerComponentType(); + } + + public void setForcedMusicIndex(int forcedMusicIndex) { + this.forcedMusicIndex = forcedMusicIndex; + } + + public int getForcedMusicIndex() { + return this.forcedMusicIndex; + } + + public UpdateEnvironmentMusic getMusicPacket() { + return this.musicPacket; + } + + @Nullable + @Override + public Component clone() { + AmbienceTracker clone = new AmbienceTracker(); + clone.forcedMusicIndex = this.forcedMusicIndex; + return clone; + } +} diff --git a/src/com/hypixel/hytale/builtin/ambience/components/AmbientEmitterComponent.java b/src/com/hypixel/hytale/builtin/ambience/components/AmbientEmitterComponent.java new file mode 100644 index 0000000..12be0a0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/ambience/components/AmbientEmitterComponent.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.ambience.components; + +import com.hypixel.hytale.builtin.ambience.AmbiencePlugin; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nullable; + +public class AmbientEmitterComponent implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(AmbientEmitterComponent.class, AmbientEmitterComponent::new) + .append(new KeyedCodec<>("SoundEventId", Codec.STRING), (emitter, s) -> emitter.soundEventId = s, emitter -> emitter.soundEventId) + .add() + .build(); + private String soundEventId; + private Ref spawnedEmitter; + + public AmbientEmitterComponent() { + } + + public static ComponentType getComponentType() { + return AmbiencePlugin.get().getAmbientEmitterComponentType(); + } + + public String getSoundEventId() { + return this.soundEventId; + } + + public void setSoundEventId(String soundEventId) { + this.soundEventId = soundEventId; + } + + public Ref getSpawnedEmitter() { + return this.spawnedEmitter; + } + + public void setSpawnedEmitter(Ref spawnedEmitter) { + this.spawnedEmitter = spawnedEmitter; + } + + @Nullable + @Override + public Component clone() { + AmbientEmitterComponent clone = new AmbientEmitterComponent(); + clone.soundEventId = this.soundEventId; + return clone; + } +} diff --git a/src/com/hypixel/hytale/builtin/ambience/resources/AmbienceResource.java b/src/com/hypixel/hytale/builtin/ambience/resources/AmbienceResource.java new file mode 100644 index 0000000..97bc6cc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/ambience/resources/AmbienceResource.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.builtin.ambience.resources; + +import com.hypixel.hytale.builtin.ambience.AmbiencePlugin; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.asset.type.ambiencefx.config.AmbienceFX; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nullable; + +public class AmbienceResource implements Resource { + private int forcedMusicIndex; + + public AmbienceResource() { + } + + public static ResourceType getResourceType() { + return AmbiencePlugin.get().getAmbienceResourceType(); + } + + public void setForcedMusicAmbience(@Nullable String musicAmbienceId) { + if (musicAmbienceId == null) { + this.forcedMusicIndex = 0; + } else { + this.forcedMusicIndex = AmbienceFX.getAssetMap().getIndex(musicAmbienceId); + } + } + + public int getForcedMusicIndex() { + return this.forcedMusicIndex; + } + + @Override + public Resource clone() { + AmbienceResource clone = new AmbienceResource(); + clone.forcedMusicIndex = this.forcedMusicIndex; + return null; + } +} diff --git a/src/com/hypixel/hytale/builtin/ambience/systems/AmbientEmitterSystems.java b/src/com/hypixel/hytale/builtin/ambience/systems/AmbientEmitterSystems.java new file mode 100644 index 0000000..a78a3ed --- /dev/null +++ b/src/com/hypixel/hytale/builtin/ambience/systems/AmbientEmitterSystems.java @@ -0,0 +1,152 @@ +package com.hypixel.hytale.builtin.ambience.systems; + +import com.hypixel.hytale.builtin.ambience.AmbiencePlugin; +import com.hypixel.hytale.builtin.ambience.components.AmbientEmitterComponent; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.NonSerialized; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.modules.entity.component.AudioComponent; +import com.hypixel.hytale.server.core.modules.entity.component.Intangible; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.prefab.PrefabCopyableComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AmbientEmitterSystems { + public AmbientEmitterSystems() { + } + + public static class EntityAdded extends HolderSystem { + private final Query query = Query.and(AmbientEmitterComponent.getComponentType(), TransformComponent.getComponentType()); + + public EntityAdded() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + if (!holder.getArchetype().contains(NetworkId.getComponentType())) { + holder.addComponent(NetworkId.getComponentType(), new NetworkId(store.getExternalData().takeNextNetworkId())); + } + + holder.ensureComponent(Intangible.getComponentType()); + holder.ensureComponent(PrefabCopyableComponent.getComponentType()); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Override + public Query getQuery() { + return this.query; + } + } + + public static class EntityRefAdded extends RefSystem { + private final Query query = Query.and(AmbientEmitterComponent.getComponentType(), TransformComponent.getComponentType()); + + public EntityRefAdded() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + AmbientEmitterComponent emitterComponent = store.getComponent(ref, AmbientEmitterComponent.getComponentType()); + + assert emitterComponent != null; + + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Holder emitterHolder = EntityStore.REGISTRY.newHolder(); + emitterHolder.addComponent(TransformComponent.getComponentType(), transformComponent.clone()); + AudioComponent audioComponent = new AudioComponent(); + audioComponent.addSound(SoundEvent.getAssetMap().getIndex(emitterComponent.getSoundEventId())); + emitterHolder.addComponent(AudioComponent.getComponentType(), audioComponent); + emitterHolder.addComponent(NetworkId.getComponentType(), new NetworkId(store.getExternalData().takeNextNetworkId())); + emitterHolder.ensureComponent(Intangible.getComponentType()); + emitterHolder.addComponent(EntityStore.REGISTRY.getNonSerializedComponentType(), NonSerialized.get()); + emitterComponent.setSpawnedEmitter(commandBuffer.addEntity(emitterHolder, AddReason.SPAWN)); + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + if (reason == RemoveReason.REMOVE) { + AmbientEmitterComponent emitterComponent = store.getComponent(ref, AmbientEmitterComponent.getComponentType()); + + assert emitterComponent != null; + + Ref emitterRef = emitterComponent.getSpawnedEmitter(); + if (emitterRef != null) { + commandBuffer.removeEntity(emitterRef, RemoveReason.REMOVE); + } + } + } + + @Nullable + @Override + public Query getQuery() { + return this.query; + } + } + + public static class Ticking extends EntityTickingSystem { + private final Query query = Query.and(AmbientEmitterComponent.getComponentType(), TransformComponent.getComponentType()); + + public Ticking() { + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + AmbientEmitterComponent emitter = archetypeChunk.getComponent(index, AmbientEmitterComponent.getComponentType()); + + assert emitter != null; + + TransformComponent transform = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transform != null; + + if (emitter.getSpawnedEmitter() != null && emitter.getSpawnedEmitter().isValid()) { + TransformComponent ownedEmitterTransform = store.getComponent(emitter.getSpawnedEmitter(), TransformComponent.getComponentType()); + if (transform.getPosition().distanceSquaredTo(ownedEmitterTransform.getPosition()) > 1.0) { + ownedEmitterTransform.setPosition(transform.getPosition()); + } + } else { + AmbiencePlugin.get() + .getLogger() + .at(Level.WARNING) + .log("Ambient emitter lost at %s: %d %s", transform.getPosition(), archetypeChunk.getReferenceTo(index).getIndex(), emitter.getSoundEventId()); + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + } + } + + @Nullable + @Override + public Query getQuery() { + return this.query; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/ambience/systems/ForcedMusicSystems.java b/src/com/hypixel/hytale/builtin/ambience/systems/ForcedMusicSystems.java new file mode 100644 index 0000000..2a05a40 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/ambience/systems/ForcedMusicSystems.java @@ -0,0 +1,86 @@ +package com.hypixel.hytale.builtin.ambience.systems; + +import com.hypixel.hytale.builtin.ambience.components.AmbienceTracker; +import com.hypixel.hytale.builtin.ambience.resources.AmbienceResource; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.protocol.packets.world.UpdateEnvironmentMusic; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ForcedMusicSystems { + private static final Query TICK_QUERY = Archetype.of( + Player.getComponentType(), PlayerRef.getComponentType(), AmbienceTracker.getComponentType() + ); + + public ForcedMusicSystems() { + } + + public static class PlayerAdded extends HolderSystem { + public PlayerAdded() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(AmbienceTracker.getComponentType()); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + AmbienceTracker tracker = holder.getComponent(AmbienceTracker.getComponentType()); + PlayerRef playerRef = holder.getComponent(PlayerRef.getComponentType()); + UpdateEnvironmentMusic pooledPacket = tracker.getMusicPacket(); + pooledPacket.environmentIndex = 0; + playerRef.getPacketHandler().write(pooledPacket); + } + + @Nullable + @Override + public Query getQuery() { + return PlayerRef.getComponentType(); + } + } + + public static class Tick extends EntityTickingSystem { + public Tick() { + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + AmbienceResource ambienceResource = store.getResource(AmbienceResource.getResourceType()); + AmbienceTracker tracker = archetypeChunk.getComponent(index, AmbienceTracker.getComponentType()); + PlayerRef playerRef = archetypeChunk.getComponent(index, PlayerRef.getComponentType()); + int have = tracker.getForcedMusicIndex(); + int desired = ambienceResource.getForcedMusicIndex(); + if (have != desired) { + tracker.setForcedMusicIndex(desired); + UpdateEnvironmentMusic pooledPacket = tracker.getMusicPacket(); + pooledPacket.environmentIndex = desired; + playerRef.getPacketHandler().write(pooledPacket); + } + } + + @Nullable + @Override + public Query getQuery() { + return ForcedMusicSystems.TICK_QUERY; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/AssetEditorGamePacketHandler.java b/src/com/hypixel/hytale/builtin/asseteditor/AssetEditorGamePacketHandler.java new file mode 100644 index 0000000..a09509d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/AssetEditorGamePacketHandler.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.builtin.asseteditor; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAuthorization; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorInitialize; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUpdateJsonAsset; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.io.handlers.IPacketHandler; +import com.hypixel.hytale.server.core.io.handlers.SubPacketHandler; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class AssetEditorGamePacketHandler implements SubPacketHandler { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final IPacketHandler packetHandler; + + public AssetEditorGamePacketHandler(IPacketHandler packetHandler) { + this.packetHandler = packetHandler; + } + + @Override + public void registerHandlers() { + if (AssetEditorPlugin.get().isDisabled()) { + this.packetHandler.registerNoOpHandlers(302); + this.packetHandler.registerNoOpHandlers(325); + } else { + this.packetHandler.registerHandler(302, p -> this.handle((AssetEditorInitialize)p)); + this.packetHandler.registerHandler(323, p -> this.handle((AssetEditorUpdateJsonAsset)p)); + } + } + + public void handle(AssetEditorInitialize packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (this.lacksPermission(playerComponent, false)) { + this.packetHandler.getPlayerRef().getPacketHandler().write(new AssetEditorAuthorization(false)); + } else { + this.packetHandler.getPlayerRef().getPacketHandler().write(new AssetEditorAuthorization(true)); + AssetEditorPlugin.get().handleInitializeEditor(ref, store); + } + }); + } else { + throw new RuntimeException("Unable to process AssetEditorInitialize packet. Player ref is invalid!"); + } + } + + @Deprecated + public void handle(@Nonnull AssetEditorUpdateJsonAsset packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + CompletableFuture.runAsync(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (!this.lacksPermission(playerComponent, true)) { + ; + } + }, world) + .thenRunAsync( + () -> { + LOGGER.at(Level.INFO).log("%s updating json asset at %s", this.packetHandler.getPlayerRef().getUsername(), packet.path); + EditorClient mockClient = new EditorClient(playerRef); + AssetEditorPlugin.get() + .handleJsonAssetUpdate( + mockClient, packet.path != null ? new AssetPath(packet.path) : null, packet.assetType, packet.assetIndex, packet.commands, packet.token + ); + } + ); + } else { + throw new RuntimeException("Unable to process AssetEditorUpdateJsonAsset packet. Player ref is invalid!"); + } + } + + private boolean lacksPermission(@Nonnull Player player, boolean shouldShowDenialMessage) { + if (!player.hasPermission("hytale.editor.asset")) { + if (shouldShowDenialMessage) { + player.sendMessage(Messages.USAGE_DENIED_MESSAGE); + } + + return true; + } else { + return false; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/AssetEditorPacketHandler.java b/src/com/hypixel/hytale/builtin/asseteditor/AssetEditorPacketHandler.java new file mode 100644 index 0000000..d692ed3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/AssetEditorPacketHandler.java @@ -0,0 +1,440 @@ +package com.hypixel.hytale.builtin.asseteditor; + +import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorActivateButtonEvent; +import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorFetchAutoCompleteDataEvent; +import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorRequestDataSetEvent; +import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorUpdateWeatherPreviewLockEvent; +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.HostAddress; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorActivateButton; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorCreateAsset; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorCreateAssetPack; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorCreateDirectory; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorDeleteAsset; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorDeleteAssetPack; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorDeleteDirectory; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorExportAssets; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFetchAsset; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFetchAutoCompleteData; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFetchAutoCompleteDataReply; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFetchJsonAssetWithParents; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFetchLastModifiedAssets; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorPopupNotificationType; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRedoChanges; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRenameAsset; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRenameDirectory; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRequestChildrenList; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRequestDataset; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRequestDatasetReply; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorSelectAsset; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorSetGameTime; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorSubscribeModifiedAssetsChanges; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUndoChanges; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUpdateAsset; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUpdateAssetPack; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUpdateJsonAsset; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUpdateWeatherPreviewLock; +import com.hypixel.hytale.protocol.packets.connection.Disconnect; +import com.hypixel.hytale.protocol.packets.connection.DisconnectType; +import com.hypixel.hytale.protocol.packets.connection.Pong; +import com.hypixel.hytale.protocol.packets.interface_.UpdateLanguage; +import com.hypixel.hytale.protocol.packets.world.UpdateEditorTimeOverride; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.auth.PlayerAuthentication; +import com.hypixel.hytale.server.core.io.ProtocolVersion; +import com.hypixel.hytale.server.core.io.handlers.GenericPacketHandler; +import com.hypixel.hytale.server.core.io.netty.NettyUtil; +import com.hypixel.hytale.server.core.modules.i18n.I18nModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.nio.file.Path; +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class AssetEditorPacketHandler extends GenericPacketHandler { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private final EditorClient editorClient; + + public AssetEditorPacketHandler(@Nonnull Channel channel, @Nonnull ProtocolVersion protocolVersion, String language, @Nonnull PlayerAuthentication auth) { + super(channel, protocolVersion); + this.auth = auth; + this.editorClient = new EditorClient(language, auth, this); + this.init(); + } + + public AssetEditorPacketHandler(@Nonnull Channel channel, @Nonnull ProtocolVersion protocolVersion, String language, UUID uuid, String username) { + this(channel, protocolVersion, language, uuid, username, null, null); + } + + public AssetEditorPacketHandler( + @Nonnull Channel channel, + @Nonnull ProtocolVersion protocolVersion, + String language, + UUID uuid, + String username, + byte[] referralData, + HostAddress referralSource + ) { + super(channel, protocolVersion); + this.auth = null; + this.editorClient = new EditorClient(language, uuid, username, this); + this.init(); + } + + private void init() { + this.registerHandlers(); + AssetEditorPlugin.get().handleInitializeClient(this.editorClient); + } + + @Nonnull + public EditorClient getEditorClient() { + return this.editorClient; + } + + @Nonnull + @Override + public String getIdentifier() { + return "{Editor(" + NettyUtil.formatRemoteAddress(this.channel) + "), " + this.editorClient.getUuid() + ", " + this.editorClient.getUsername() + "}"; + } + + @Override + public void closed(ChannelHandlerContext ctx) { + AssetEditorPlugin.get().handleEditorClientDisconnected(this.editorClient, this.disconnectReason); + } + + public void registerHandlers() { + this.registerHandler(1, p -> this.handle((Disconnect)p)); + this.registerHandler(3, p -> this.handlePong((Pong)p)); + this.registerHandler(321, p -> this.handle((AssetEditorRequestChildrenList)p)); + this.registerHandler(324, p -> this.handle((AssetEditorUpdateAsset)p)); + this.registerHandler(323, p -> this.handle((AssetEditorUpdateJsonAsset)p)); + this.registerHandler(336, p -> this.handle((AssetEditorSelectAsset)p)); + this.registerHandler(310, p -> this.handle((AssetEditorFetchAsset)p)); + this.registerHandler(311, p -> this.handle((AssetEditorFetchJsonAssetWithParents)p)); + this.registerHandler(327, p -> this.handle((AssetEditorCreateAsset)p)); + this.registerHandler(307, p -> this.handle((AssetEditorCreateDirectory)p)); + this.registerHandler(333, p -> this.handle((AssetEditorRequestDataset)p)); + this.registerHandler(331, p -> this.handle((AssetEditorFetchAutoCompleteData)p)); + this.registerHandler(335, p -> this.handle((AssetEditorActivateButton)p)); + this.registerHandler(329, p -> this.handle((AssetEditorDeleteAsset)p)); + this.registerHandler(328, p -> this.handle((AssetEditorRenameAsset)p)); + this.registerHandler(308, p -> this.handle((AssetEditorDeleteDirectory)p)); + this.registerHandler(309, p -> this.handle((AssetEditorRenameDirectory)p)); + this.registerHandler(342, p -> this.handle((AssetEditorExportAssets)p)); + this.registerHandler(338, p -> this.handle((AssetEditorFetchLastModifiedAssets)p)); + this.registerHandler(349, p -> this.handle((AssetEditorUndoChanges)p)); + this.registerHandler(350, p -> this.handle((AssetEditorRedoChanges)p)); + this.registerHandler(341, p -> this.handle((AssetEditorSubscribeModifiedAssetsChanges)p)); + this.registerHandler(352, p -> this.handle((AssetEditorSetGameTime)p)); + this.registerHandler(354, p -> this.handle((AssetEditorUpdateWeatherPreviewLock)p)); + this.registerHandler(316, p -> this.handle((AssetEditorCreateAssetPack)p)); + this.registerHandler(315, p -> this.handle((AssetEditorUpdateAssetPack)p)); + this.registerHandler(317, p -> this.handle((AssetEditorDeleteAssetPack)p)); + } + + public void handle(@Nonnull AssetEditorSubscribeModifiedAssetsChanges packet) { + if (!this.lacksPermission()) { + if (packet.subscribe) { + AssetEditorPlugin.get().handleSubscribeToModifiedAssetsChanges(this.editorClient); + } else { + AssetEditorPlugin.get().handleUnsubscribeFromModifiedAssetsChanges(this.editorClient); + } + } + } + + public void handle(@Nonnull AssetEditorUndoChanges packet) { + if (!this.lacksPermission(packet.token)) { + LOGGER.at(Level.INFO).log("%s undoing last change", this.editorClient.getUsername()); + AssetEditorPlugin.get().handleUndo(this.editorClient, new AssetPath(packet.path.pack, Path.of(packet.path.path)), packet.token); + } + } + + public void handle(@Nonnull AssetEditorRedoChanges packet) { + if (!this.lacksPermission(packet.token)) { + LOGGER.at(Level.INFO).log("%s redoing last change", this.editorClient.getUsername()); + AssetEditorPlugin.get().handleRedo(this.editorClient, new AssetPath(packet.path.pack, Path.of(packet.path.path)), packet.token); + } + } + + public void handle(AssetEditorFetchLastModifiedAssets packet) { + if (!this.lacksPermission()) { + AssetEditorPlugin.get().handleFetchLastModifiedAssets(this.editorClient); + } + } + + public void handle(@Nonnull AssetEditorExportAssets packet) { + if (!this.lacksPermission()) { + StringBuilder assets = new StringBuilder(); + + for (com.hypixel.hytale.protocol.packets.asseteditor.AssetPath assetPath : packet.paths) { + if (!assets.isEmpty()) { + assets.append(", "); + } + + assets.append(assetPath.toString()); + } + + LOGGER.at(Level.INFO).log("%s is exporting: %s", this.editorClient.getUsername(), assets.toString()); + List paths = new ObjectArrayList<>(); + + for (com.hypixel.hytale.protocol.packets.asseteditor.AssetPath assetPath : packet.paths) { + paths.add(new AssetPath(assetPath.pack, Path.of(assetPath.path))); + } + + AssetEditorPlugin.get().handleExportAssets(this.editorClient, paths); + } + } + + public void handle(@Nonnull AssetEditorCreateAsset packet) { + if (!this.lacksPermission(packet.token)) { + LOGGER.at(Level.INFO).log("%s is creating asset %s", this.editorClient.getUsername(), packet.path); + AssetEditorPlugin.get() + .handleCreateAsset( + this.editorClient, new AssetPath(packet.path.pack, Path.of(packet.path.path)), packet.data, packet.rebuildCaches, packet.buttonId, packet.token + ); + } + } + + public void handle(@Nonnull AssetEditorFetchAsset packet) { + if (!this.lacksPermission(packet.token)) { + LOGGER.at(Level.INFO).log("%s is fetching asset %s, from opened tab: %s", this.editorClient.getUsername(), packet.path, packet.isFromOpenedTab); + AssetEditorPlugin.get().handleFetchAsset(this.editorClient, new AssetPath(packet.path.pack, Path.of(packet.path.path)), packet.token); + } + } + + public void handle(@Nonnull AssetEditorFetchJsonAssetWithParents packet) { + if (!this.lacksPermission(packet.token)) { + LOGGER.at(Level.INFO).log("%s is fetching json asset %s, from opened tab: %s", this.editorClient.getUsername(), packet.path, packet.isFromOpenedTab); + AssetEditorPlugin.get() + .handleFetchJsonAssetWithParents( + this.editorClient, new AssetPath(packet.path.pack, Path.of(packet.path.path)), packet.isFromOpenedTab, packet.token + ); + } + } + + public void handle(@Nonnull AssetEditorRequestChildrenList packet) { + if (!this.lacksPermission()) { + LOGGER.at(Level.INFO).log("%s is requesting child ids for %s", this.editorClient.getUsername(), packet.path); + AssetEditorPlugin.get().handleRequestChildIds(this.editorClient, new AssetPath(packet.path.pack, Path.of(packet.path.path))); + } + } + + public void handle(@Nonnull AssetEditorUpdateAsset packet) { + if (!this.lacksPermission(packet.token)) { + LOGGER.at(Level.INFO).log("%s updating asset at %s", this.editorClient.getUsername(), packet.path); + AssetEditorPlugin.get().handleAssetUpdate(this.editorClient, new AssetPath(packet.path.pack, Path.of(packet.path.path)), packet.data, packet.token); + } + } + + public void handle(@Nonnull AssetEditorUpdateJsonAsset packet) { + if (!this.lacksPermission(packet.token)) { + LOGGER.at(Level.INFO).log("%s updating json asset at %s", this.editorClient.getUsername(), packet.path); + AssetEditorPlugin.get() + .handleJsonAssetUpdate( + this.editorClient, + new AssetPath(packet.path.pack, Path.of(packet.path.path)), + packet.assetType, + packet.assetIndex, + packet.commands, + packet.token + ); + } + } + + public void handle(@Nonnull AssetEditorFetchAutoCompleteData packet) { + if (!this.lacksPermission(packet.token)) { + CompletableFutureUtil._catch( + HytaleServer.get() + .getEventBus() + .dispatchForAsync(AssetEditorFetchAutoCompleteDataEvent.class, packet.dataset) + .dispatch(new AssetEditorFetchAutoCompleteDataEvent(this.editorClient, packet.dataset, packet.query)) + .thenAccept(event -> { + if (event.getResults() == null) { + HytaleLogger.getLogger().at(Level.WARNING).log("Tried to request unknown autocomplete dataset for asset editor: %s", packet.dataset); + } else { + this.editorClient.getPacketHandler().write(new AssetEditorFetchAutoCompleteDataReply(packet.token, event.getResults())); + } + }) + ); + } + } + + public void handle(@Nonnull AssetEditorRenameAsset packet) { + if (!this.lacksPermission(packet.token)) { + LOGGER.at(Level.WARNING).log("%s is renaming %s to %s", this.editorClient.getUsername(), packet.path, packet.newPath); + AssetEditorPlugin.get().handleRenameAsset(this.editorClient, new AssetPath(packet.path), new AssetPath(packet.newPath), packet.token); + } + } + + public void handle(@Nonnull AssetEditorDeleteAsset packet) { + if (!this.lacksPermission(packet.token)) { + LOGGER.at(Level.INFO).log("%s is deleting asset %s", this.editorClient.getUsername(), packet.path); + AssetEditorPlugin.get().handleDeleteAsset(this.editorClient, new AssetPath(packet.path), packet.token); + } + } + + public void handle(@Nonnull AssetEditorActivateButton packet) { + if (!this.lacksPermission()) { + AssetEditorPlugin.get().getLogger().at(Level.INFO).log("%s is activating button %s", this.editorClient.getUsername(), packet.buttonId); + IEventDispatcher dispatch = HytaleServer.get() + .getEventBus() + .dispatchFor(AssetEditorActivateButtonEvent.class, packet.buttonId); + if (dispatch.hasListener()) { + dispatch.dispatch(new AssetEditorActivateButtonEvent(this.editorClient, packet.buttonId)); + } + } + } + + public void handle(@Nonnull AssetEditorRequestDataset packet) { + if (!this.lacksPermission()) { + CompletableFutureUtil._catch( + HytaleServer.get() + .getEventBus() + .dispatchForAsync(AssetEditorRequestDataSetEvent.class, packet.name) + .dispatch(new AssetEditorRequestDataSetEvent(this.editorClient, packet.name, null)) + .thenAccept(event -> { + if (event.getResults() == null) { + HytaleLogger.getLogger().at(Level.WARNING).log("Tried to request unknown dataset list for asset editor: %s", packet.name); + } else { + this.editorClient.getPacketHandler().write(new AssetEditorRequestDatasetReply(packet.name, event.getResults())); + } + }) + ); + } + } + + public void handle(@Nonnull AssetEditorSelectAsset packet) { + if (!this.lacksPermission()) { + LOGGER.at(Level.INFO).log("%s selecting %s", this.editorClient.getUsername(), packet.path); + AssetEditorPlugin.get().handleSelectAsset(this.editorClient, packet.path != null ? new AssetPath(packet.path) : null); + } + } + + public void handle(@Nonnull AssetEditorCreateDirectory packet) { + if (!this.lacksPermission(packet.token)) { + LOGGER.at(Level.INFO).log("%s is creating directory %s", this.editorClient.getUsername(), packet.path); + AssetEditorPlugin.get().handleCreateDirectory(this.editorClient, new AssetPath(packet.path), packet.token); + } + } + + public void handle(@Nonnull AssetEditorDeleteDirectory packet) { + if (!this.lacksPermission(packet.token)) { + LOGGER.at(Level.INFO).log("%s is deleting directory %s", this.editorClient.getUsername(), packet.path); + AssetEditorPlugin.get().handleDeleteDirectory(this.editorClient, new AssetPath(packet.path), packet.token); + } + } + + public void handle(@Nonnull AssetEditorRenameDirectory packet) { + if (!this.lacksPermission(packet.token)) { + LOGGER.at(Level.INFO).log("%s is renaming directory %s to $s", this.editorClient.getUsername(), packet.path, packet.newPath); + AssetEditorPlugin.get().handleRenameDirectory(this.editorClient, new AssetPath(packet.path), new AssetPath(packet.newPath), packet.token); + } + } + + public void handle(@Nonnull UpdateLanguage packet) { + if (!this.lacksPermission()) { + this.editorClient.setLanguage(packet.language); + I18nModule.get().sendTranslations(this.editorClient.getPacketHandler(), this.editorClient.getLanguage()); + } + } + + public void handle(@Nonnull AssetEditorSetGameTime packet) { + if (!this.lacksPermission()) { + PlayerRef player = this.editorClient.tryGetPlayer(); + if (player != null) { + player.getPacketHandler().write(new UpdateEditorTimeOverride(packet.gameTime, packet.paused)); + } + } + } + + public void handle(@Nonnull AssetEditorUpdateWeatherPreviewLock packet) { + if (!this.lacksPermission()) { + IEventDispatcher dispatch = HytaleServer.get() + .getEventBus() + .dispatchFor(AssetEditorUpdateWeatherPreviewLockEvent.class); + if (dispatch.hasListener()) { + dispatch.dispatch(new AssetEditorUpdateWeatherPreviewLockEvent(this.editorClient, packet.locked)); + } + } + } + + public void handle(@Nonnull AssetEditorUpdateAssetPack packet) { + if (!this.lacksPermission("hytale.editor.packs.edit")) { + LOGGER.at(Level.INFO).log("%s is updating the asset pack manifest for %s", this.editorClient.getUsername(), packet.id); + AssetEditorPlugin.get().handleUpdateAssetPack(this.editorClient, packet.id, packet.manifest); + } + } + + public void handle(@Nonnull AssetEditorDeleteAssetPack packet) { + if (!this.lacksPermission("hytale.editor.packs.delete")) { + LOGGER.at(Level.INFO).log("%s is deleting the asset pack %s", this.editorClient.getUsername(), packet.id); + AssetEditorPlugin.get().handleDeleteAssetPack(this.editorClient, packet.id); + } + } + + public void handle(@Nonnull AssetEditorCreateAssetPack packet) { + if (!this.lacksPermission(packet.token, "hytale.editor.packs.create")) { + LOGGER.at(Level.INFO).log("%s is creating a new asset pack: %s:%s", this.editorClient.getUsername(), packet.manifest.group, packet.manifest.name); + AssetEditorPlugin.get().handleCreateAssetPack(this.editorClient, packet.manifest, packet.token); + } + } + + public void handle(@Nonnull Disconnect packet) { + switch (packet.type) { + case Disconnect: + this.disconnectReason.setClientDisconnectType(DisconnectType.Disconnect); + break; + case Crash: + this.disconnectReason.setClientDisconnectType(DisconnectType.Crash); + } + + LOGGER.at(Level.INFO) + .log( + "%s - %s at %s left with reason: %s - %s", + this.editorClient.getUuid(), + this.editorClient.getUsername(), + NettyUtil.formatRemoteAddress(this.channel), + packet.type.name(), + packet.reason + ); + this.channel.close(); + } + + private boolean lacksPermission(int token) { + if (!this.editorClient.hasPermission("hytale.editor.asset")) { + this.editorClient.sendFailureReply(token, Messages.USAGE_DENIED_MESSAGE); + return true; + } else { + return false; + } + } + + private boolean lacksPermission() { + return this.lacksPermission("hytale.editor.asset"); + } + + private boolean lacksPermission(String permissionId) { + if (!this.editorClient.hasPermission(permissionId)) { + this.editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Messages.USAGE_DENIED_MESSAGE); + return true; + } else { + return false; + } + } + + private boolean lacksPermission(int token, String permissionId) { + if (!this.editorClient.hasPermission(permissionId)) { + this.editorClient.sendFailureReply(token, Messages.USAGE_DENIED_MESSAGE); + return true; + } else { + return false; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/AssetEditorPlugin.java b/src/com/hypixel/hytale/builtin/asseteditor/AssetEditorPlugin.java new file mode 100644 index 0000000..4116c6c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/AssetEditorPlugin.java @@ -0,0 +1,1875 @@ +package com.hypixel.hytale.builtin.asseteditor; + +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.event.AssetMonitorEvent; +import com.hypixel.hytale.assetstore.event.AssetStoreMonitorEvent; +import com.hypixel.hytale.assetstore.event.RegisterAssetStoreEvent; +import com.hypixel.hytale.assetstore.event.RemoveAssetStoreEvent; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.asseteditor.assettypehandler.AssetStoreTypeHandler; +import com.hypixel.hytale.builtin.asseteditor.assettypehandler.AssetTypeHandler; +import com.hypixel.hytale.builtin.asseteditor.assettypehandler.CommonAssetTypeHandler; +import com.hypixel.hytale.builtin.asseteditor.assettypehandler.JsonTypeHandler; +import com.hypixel.hytale.builtin.asseteditor.data.AssetUndoRedoInfo; +import com.hypixel.hytale.builtin.asseteditor.data.ModifiedAsset; +import com.hypixel.hytale.builtin.asseteditor.datasource.DataSource; +import com.hypixel.hytale.builtin.asseteditor.datasource.StandardDataSource; +import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorAssetCreatedEvent; +import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorClientDisconnectEvent; +import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorSelectAssetEvent; +import com.hypixel.hytale.builtin.asseteditor.util.AssetPathUtil; +import com.hypixel.hytale.builtin.asseteditor.util.AssetStoreUtil; +import com.hypixel.hytale.builtin.asseteditor.util.BsonTransformationUtil; +import com.hypixel.hytale.codec.EmptyExtraInfo; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.common.plugin.AuthorInfo; +import com.hypixel.hytale.common.plugin.PluginIdentifier; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.common.semver.Semver; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAsset; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetListUpdate; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetPackSetup; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetUpdated; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorCapabilities; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorDeleteAssetPack; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorEditorType; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorExportAssetFinalize; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorExportAssetInitialize; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorExportAssetPart; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorExportComplete; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorExportDeleteAssets; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFetchAssetReply; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFetchJsonAssetWithParentsReply; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFileEntry; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorJsonAssetUpdated; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorLastModifiedAssets; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorPopupNotificationType; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRebuildCaches; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRequestChildrenListReply; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorSetupSchemas; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUndoRedoReply; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUpdateAssetPack; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetInfo; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetPackManifest; +import com.hypixel.hytale.protocol.packets.asseteditor.JsonUpdateCommand; +import com.hypixel.hytale.protocol.packets.asseteditor.JsonUpdateType; +import com.hypixel.hytale.protocol.packets.asseteditor.SchemaFile; +import com.hypixel.hytale.protocol.packets.asseteditor.TimestampedAssetReference; +import com.hypixel.hytale.protocol.packets.assets.UpdateTranslations; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.asset.AssetPackRegisterEvent; +import com.hypixel.hytale.server.core.asset.AssetPackUnregisterEvent; +import com.hypixel.hytale.server.core.asset.AssetRegistryLoader; +import com.hypixel.hytale.server.core.asset.common.events.CommonAssetMonitorEvent; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.io.ServerManager; +import com.hypixel.hytale.server.core.io.handlers.InitialPacketHandler; +import com.hypixel.hytale.server.core.modules.i18n.I18nModule; +import com.hypixel.hytale.server.core.modules.i18n.event.MessagesUpdated; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.plugin.PluginManager; +import com.hypixel.hytale.server.core.plugin.PluginState; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.BsonUtil; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.StampedLock; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonValue; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class AssetEditorPlugin extends JavaPlugin { + private static AssetEditorPlugin instance; + private final StampedLock globalEditLock = new StampedLock(); + private final Map> uuidToEditorClients = new ConcurrentHashMap<>(); + private final Map clientOpenAssetPathMapping = new ConcurrentHashMap<>(); + private final Set clientsSubscribedToModifiedAssetsChanges = ConcurrentHashMap.newKeySet(); + @Nonnull + private Map schemas = new Object2ObjectOpenHashMap<>(); + private AssetEditorSetupSchemas setupSchemasPacket; + private final StampedLock initLock = new StampedLock(); + private final Set initQueue = new HashSet<>(); + @Nonnull + private AssetEditorPlugin.InitState initState = AssetEditorPlugin.InitState.NOT_INITIALIZED; + @Nullable + private ScheduledFuture scheduledReinitFuture; + private final Map assetPackDataSources = new ConcurrentHashMap<>(); + private final AssetTypeRegistry assetTypeRegistry = new AssetTypeRegistry(); + private final UndoRedoManager undoRedoManager = new UndoRedoManager(); + private ScheduledFuture pingClientsTask; + + public static AssetEditorPlugin get() { + return instance; + } + + public AssetEditorPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @NullableDecl + DataSource registerDataSourceForPack(AssetPack assetPack) { + PluginManifest manifest = assetPack.getManifest(); + if (manifest == null) { + this.getLogger().at(Level.SEVERE).log("Could not load asset pack manifest for " + assetPack.getName()); + return null; + } else { + StandardDataSource dataSource = new StandardDataSource(assetPack.getName(), assetPack.getRoot(), assetPack.isImmutable(), manifest); + this.assetPackDataSources.put(assetPack.getName(), dataSource); + return dataSource; + } + } + + @Override + protected void setup() { + instance = this; + + for (AssetPack assetPack : AssetModule.get().getAssetPacks()) { + this.registerDataSourceForPack(assetPack); + } + + ServerManager.get().registerSubPacketHandlers(AssetEditorGamePacketHandler::new); + InitialPacketHandler.EDITOR_PACKET_HANDLER_SUPPLIER = AssetEditorPacketHandler::new; + + for (AssetStore assetStore : AssetRegistry.getStoreMap().values()) { + if (assetStore.getPath() != null && !assetStore.getPath().startsWith("../")) { + this.assetTypeRegistry.registerAssetType(new AssetStoreTypeHandler(assetStore)); + } + } + + this.assetTypeRegistry.registerAssetType(new CommonAssetTypeHandler("Texture", "Texture.png", ".png", AssetEditorEditorType.Texture)); + this.assetTypeRegistry.registerAssetType(new CommonAssetTypeHandler("Model", "Model.png", ".blockymodel", AssetEditorEditorType.JsonSource)); + this.assetTypeRegistry.registerAssetType(new CommonAssetTypeHandler("Animation", "Animation.png", ".blockyanim", AssetEditorEditorType.JsonSource)); + this.assetTypeRegistry.registerAssetType(new CommonAssetTypeHandler("Sound", null, ".ogg", AssetEditorEditorType.None)); + this.assetTypeRegistry.registerAssetType(new CommonAssetTypeHandler("UI", null, ".ui", AssetEditorEditorType.Text)); + this.assetTypeRegistry.registerAssetType(new CommonAssetTypeHandler("Language", null, ".lang", AssetEditorEditorType.Text)); + this.getEventRegistry().register(RegisterAssetStoreEvent.class, this::onRegisterAssetStore); + this.getEventRegistry().register(RemoveAssetStoreEvent.class, this::onUnregisterAssetStore); + this.getEventRegistry().register(AssetPackRegisterEvent.class, this::onRegisterAssetPack); + this.getEventRegistry().register(AssetPackUnregisterEvent.class, this::onUnregisterAssetPack); + this.getEventRegistry().register(AssetStoreMonitorEvent.class, this::onAssetMonitor); + this.getEventRegistry().register(CommonAssetMonitorEvent.class, this::onAssetMonitor); + this.getEventRegistry().register(MessagesUpdated.class, this::onI18nMessagesUpdated); + AssetSpecificFunctionality.setup(); + } + + @Override + protected void start() { + for (DataSource dataSource : this.assetPackDataSources.values()) { + dataSource.start(); + } + + this.pingClientsTask = HytaleServer.SCHEDULED_EXECUTOR.scheduleAtFixedRate(this::sendPingPackets, 1L, 1L, PacketHandler.PingInfo.PING_FREQUENCY_UNIT); + } + + @Override + protected void shutdown() { + InitialPacketHandler.EDITOR_PACKET_HANDLER_SUPPLIER = null; + String message = HytaleServer.get().isShuttingDown() ? "Server is shutting down!" : "Asset editor was disabled!"; + + for (Set clients : this.uuidToEditorClients.values()) { + for (EditorClient client : clients) { + client.getPacketHandler().disconnect(message); + } + } + + this.pingClientsTask.cancel(false); + + for (DataSource dataSource : this.assetPackDataSources.values()) { + dataSource.shutdown(); + } + } + + public DataSource getDataSourceForPath(AssetPath path) { + return this.getDataSourceForPack(path.packId()); + } + + public DataSource getDataSourceForPack(String assetPack) { + return this.assetPackDataSources.get(assetPack); + } + + public Collection getDataSources() { + return this.assetPackDataSources.values(); + } + + public AssetTypeRegistry getAssetTypeRegistry() { + return this.assetTypeRegistry; + } + + public Schema getSchema(String id) { + return this.schemas.get(id); + } + + public Map getClientOpenAssetPathMapping() { + return this.clientOpenAssetPathMapping; + } + + public Set getEditorClients(UUID uuid) { + return this.uuidToEditorClients.get(uuid); + } + + private void sendPingPackets() { + for (Set clients : this.uuidToEditorClients.values()) { + for (EditorClient client : clients) { + try { + client.getPacketHandler().sendPing(); + } catch (Exception var6) { + this.getLogger().at(Level.SEVERE).withCause(var6).log("Failed to send ping to " + client); + client.getPacketHandler().disconnect("Exception when sending ping packet!"); + } + } + } + } + + @Nonnull + private List getClientsWithOpenAssetPath(AssetPath path) { + if (this.clientOpenAssetPathMapping.isEmpty()) { + return Collections.emptyList(); + } else { + List list = new ObjectArrayList<>(); + + for (Entry entry : this.clientOpenAssetPathMapping.entrySet()) { + if (entry.getValue().equals(path)) { + list.add(entry.getKey()); + } + } + + return list; + } + } + + public AssetPath getOpenAssetPath(EditorClient editorClient) { + return this.clientOpenAssetPathMapping.get(editorClient); + } + + private void onRegisterAssetPack(AssetPackRegisterEvent event) { + if (!this.assetPackDataSources.containsKey(event.getAssetPack().getName())) { + DataSource dataSource = this.registerDataSourceForPack(event.getAssetPack()); + if (dataSource != null) { + if (this.getState() == PluginState.ENABLED) { + dataSource.start(); + } + + AssetTree tempAssetTree = dataSource.loadAssetTree(this.assetTypeRegistry.getRegisteredAssetTypeHandlers().values()); + long globalEditStamp = this.globalEditLock.writeLock(); + + try { + dataSource.getAssetTree().replaceAssetTree(tempAssetTree); + } finally { + this.globalEditLock.unlockWrite(globalEditStamp); + } + + this.broadcastPackAddedOrUpdated(event.getAssetPack().getName(), dataSource.getManifest()); + + for (Set clients : this.uuidToEditorClients.values()) { + for (EditorClient client : clients) { + dataSource.getAssetTree().sendPackets(client); + } + } + } + } + } + + private void onUnregisterAssetPack(AssetPackUnregisterEvent event) { + if (this.assetPackDataSources.containsKey(event.getAssetPack().getName())) { + DataSource dataSource = this.assetPackDataSources.remove(event.getAssetPack().getName()); + dataSource.shutdown(); + + for (Set clients : this.uuidToEditorClients.values()) { + for (EditorClient client : clients) { + client.getPacketHandler().write(new AssetEditorDeleteAssetPack(event.getAssetPack().getName())); + } + } + } + } + + private void onI18nMessagesUpdated(@Nonnull MessagesUpdated event) { + if (!this.clientOpenAssetPathMapping.isEmpty()) { + I18nModule i18nModule = I18nModule.get(); + Map> changed = event.getChangedMessages(); + Map> removed = event.getRemovedMessages(); + Map updatePackets = new Object2ObjectOpenHashMap<>(); + + for (EditorClient client : this.clientOpenAssetPathMapping.keySet()) { + String languageKey = client.getLanguage(); + UpdateTranslations[] packets = updatePackets.get(languageKey); + if (packets == null) { + packets = i18nModule.getUpdatePacketsForChanges(languageKey, changed, removed); + updatePackets.put(languageKey, packets); + } + + if (packets.length != 0) { + client.getPacketHandler().write(packets); + } + } + } + } + + private void onRegisterAssetStore(@Nonnull RegisterAssetStoreEvent event) { + AssetStore>, ? extends AssetMap>> assetStore = (AssetStore>, ? extends AssetMap>>)event.getAssetStore(); + if (assetStore.getPath() != null && !assetStore.getPath().startsWith("../")) { + this.assetTypeRegistry.registerAssetType(new AssetStoreTypeHandler(assetStore)); + long stamp = this.initLock.readLock(); + + try { + if (this.initState != AssetEditorPlugin.InitState.NOT_INITIALIZED) { + if (this.scheduledReinitFuture != null) { + this.scheduledReinitFuture.cancel(false); + } + + this.scheduledReinitFuture = HytaleServer.SCHEDULED_EXECUTOR.schedule(this::tryReinitializeAssetEditor, 1L, TimeUnit.SECONDS); + } + } finally { + this.initLock.unlockRead(stamp); + } + } + } + + private void onUnregisterAssetStore(@Nonnull RemoveAssetStoreEvent event) { + AssetStore>, ? extends AssetMap>> assetStore = (AssetStore>, ? extends AssetMap>>)event.getAssetStore(); + if (assetStore.getPath() != null && !assetStore.getPath().startsWith("../")) { + this.assetTypeRegistry.unregisterAssetType(new AssetStoreTypeHandler(assetStore)); + long stamp = this.initLock.readLock(); + + try { + if (this.initState != AssetEditorPlugin.InitState.NOT_INITIALIZED) { + if (this.scheduledReinitFuture != null) { + this.scheduledReinitFuture.cancel(false); + } + + this.scheduledReinitFuture = HytaleServer.SCHEDULED_EXECUTOR.schedule(this::tryReinitializeAssetEditor, 1L, TimeUnit.SECONDS); + } + } finally { + this.initLock.unlockRead(stamp); + } + } + } + + private void tryReinitializeAssetEditor() { + long stamp = this.initLock.writeLock(); + + try { + switch (this.initState) { + case INITIALIZING: + this.scheduledReinitFuture = HytaleServer.SCHEDULED_EXECUTOR.schedule(this::tryReinitializeAssetEditor, 1L, TimeUnit.SECONDS); + break; + case INITIALIZED: + this.initState = AssetEditorPlugin.InitState.INITIALIZING; + this.scheduledReinitFuture = null; + this.getLogger().at(Level.INFO).log("Starting asset editor re-initialization"); + ForkJoinPool.commonPool().execute(() -> this.initializeAssetEditor(true)); + } + } finally { + this.initLock.unlockWrite(stamp); + } + } + + private void onAssetMonitor(@Nonnull AssetMonitorEvent event) { + AssetEditorAssetListUpdate packet = new AssetEditorAssetListUpdate(); + packet.pack = event.getAssetPack(); + ObjectArrayList newFiles = new ObjectArrayList<>(); + DataSource dataSource = this.getDataSourceForPack(event.getAssetPack()); + if (dataSource != null) { + if (!event.getRemovedFilesAndDirectories().isEmpty()) { + ObjectArrayList deletions = new ObjectArrayList<>(); + + for (Path path : event.getRemovedFilesAndDirectories()) { + Path relativePath = PathUtil.relativizePretty(dataSource.getRootPath(), path); + AssetEditorFileEntry assetFile = dataSource.getAssetTree().removeAsset(relativePath); + if (assetFile != null) { + deletions.add(assetFile); + } + } + + packet.deletions = deletions.toArray(AssetEditorFileEntry[]::new); + } + + if (!event.getRemovedFilesToUnload().isEmpty()) { + event.getRemovedFilesToUnload().removeIf(p -> { + Path relativePathx = PathUtil.relativizePretty(dataSource.getRootPath(), p); + if (!dataSource.shouldReloadAssetFromDisk(relativePathx)) { + this.getLogger().at(Level.INFO).log("Skipping reloading %s from file monitor event because there is changes made via the asset editor", p); + return true; + } else { + long globalEditStamp = this.globalEditLock.writeLock(); + + try { + this.undoRedoManager.clearUndoRedoStack(new AssetPath(event.getAssetPack(), relativePathx)); + } finally { + this.globalEditLock.unlockWrite(globalEditStamp); + } + + return false; + } + }); + } + + if (!event.getCreatedOrModifiedDirectories().isEmpty()) { + for (Path assetFile : event.getCreatedOrModifiedDirectories()) { + Path relativePath = PathUtil.relativizePretty(dataSource.getRootPath(), assetFile); + AssetEditorFileEntry addedAsset = dataSource.getAssetTree().ensureAsset(relativePath, true); + if (addedAsset != null) { + newFiles.add(addedAsset); + } + } + } + + if (!event.getCreatedOrModifiedFilesToLoad().isEmpty()) { + event.getCreatedOrModifiedFilesToLoad() + .removeIf( + pathx -> { + Path relativePathx = PathUtil.relativizePretty(dataSource.getRootPath(), pathx); + AssetEditorFileEntry addedAssetx = dataSource.getAssetTree().ensureAsset(relativePathx, false); + if (addedAssetx != null) { + newFiles.add(addedAssetx); + return false; + } else if (!dataSource.shouldReloadAssetFromDisk(relativePathx)) { + this.getLogger() + .at(Level.INFO) + .log("Skipping reloading %s from file monitor event because there is changes made via the asset editor", pathx); + return true; + } else { + AssetPath assetPath = new AssetPath(event.getAssetPack(), relativePathx); + long globalEditStamp = this.globalEditLock.writeLock(); + + try { + this.undoRedoManager.clearUndoRedoStack(assetPath); + } finally { + this.globalEditLock.unlockWrite(globalEditStamp); + } + + List clientsWithOpenAssetPath = this.getClientsWithOpenAssetPath(assetPath); + if (!clientsWithOpenAssetPath.isEmpty()) { + AssetEditorAssetUpdated updatePacket = new AssetEditorAssetUpdated(assetPath.toPacket(), dataSource.getAssetBytes(relativePathx)); + + for (EditorClient editorClient : clientsWithOpenAssetPath) { + editorClient.getPacketHandler().write(updatePacket); + } + } + + return false; + } + } + ); + if (!newFiles.isEmpty()) { + packet.additions = newFiles.toArray(AssetEditorFileEntry[]::new); + } + } + + if (!newFiles.isEmpty()) { + packet.additions = newFiles.toArray(AssetEditorFileEntry[]::new); + } + + if (packet.deletions != null || packet.additions != null) { + this.sendPacketToAllEditorUsers(packet); + } + } + } + + public void handleInitializeEditor(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + String username = playerRefComponent.getUsername(); + this.getLogger().at(Level.INFO).log("%s is attempting to initialize asset editor", username); + long stamp = this.initLock.writeLock(); + + try { + if (this.initState == AssetEditorPlugin.InitState.NOT_INITIALIZED) { + this.initState = AssetEditorPlugin.InitState.INITIALIZING; + ForkJoinPool.commonPool().execute(() -> this.initializeAssetEditor(false)); + this.getLogger().at(Level.INFO).log("%s starting asset editor initialization", username); + } + } finally { + this.initLock.unlockWrite(stamp); + } + } + + public void handleInitializeClient(@Nonnull EditorClient editorClient) { + this.getLogger().at(Level.INFO).log("Initializing %s", editorClient.getUsername()); + this.uuidToEditorClients.computeIfAbsent(editorClient.getUuid(), k -> ConcurrentHashMap.newKeySet()).add(editorClient); + this.clientOpenAssetPathMapping.put(editorClient, new AssetPath("", Path.of(""))); + I18nModule.get().sendTranslations(editorClient.getPacketHandler(), editorClient.getLanguage()); + long stamp = this.initLock.writeLock(); + + try { + switch (this.initState) { + case NOT_INITIALIZED: + this.initState = AssetEditorPlugin.InitState.INITIALIZING; + this.initQueue.add(editorClient); + ForkJoinPool.commonPool().execute(() -> this.initializeAssetEditor(false)); + this.getLogger().at(Level.INFO).log("%s starting asset editor initialization", editorClient.getUsername()); + return; + case INITIALIZING: + this.getLogger().at(Level.INFO).log("%s waiting for asset editor initialization to complete", editorClient.getUsername()); + this.initQueue.add(editorClient); + return; + case INITIALIZED: + } + } finally { + this.initLock.unlockWrite(stamp); + } + + this.initializeClient(editorClient); + } + + private void initializeAssetEditor(boolean updateLoadedAssets) { + long start = System.nanoTime(); + Map schemas = AssetRegistryLoader.generateSchemas(new SchemaContext(), new BsonDocument()); + schemas.remove("NPCRole.json"); + schemas.remove("other.json"); + AssetEditorSetupSchemas setupSchemasPacket = new AssetEditorSetupSchemas(new SchemaFile[schemas.size()]); + int i = 0; + + for (Schema schema : schemas.values()) { + String bytes = Schema.CODEC.encode(schema, EmptyExtraInfo.EMPTY).asDocument().toJson(); + setupSchemasPacket.schemas[i++] = new SchemaFile(bytes); + } + + for (DataSource dataSource : this.assetPackDataSources.values()) { + AssetTree tempAssetTree = dataSource.loadAssetTree(this.assetTypeRegistry.getRegisteredAssetTypeHandlers().values()); + long globalEditStamp = this.globalEditLock.writeLock(); + + try { + dataSource.getAssetTree().replaceAssetTree(tempAssetTree); + this.assetTypeRegistry.setupPacket(); + if (updateLoadedAssets) { + dataSource.updateRuntimeAssets(); + } + } finally { + this.globalEditLock.unlockWrite(globalEditStamp); + } + } + + long globalEditStamp = this.globalEditLock.writeLock(); + + try { + this.schemas = schemas; + this.setupSchemasPacket = setupSchemasPacket; + this.assetTypeRegistry.setupPacket(); + } finally { + this.globalEditLock.unlockWrite(globalEditStamp); + } + + long var31 = this.initLock.writeLock(); + + try { + this.initState = AssetEditorPlugin.InitState.INITIALIZED; + this.getLogger().at(Level.INFO).log("Asset editor initialization complete! Took: %s", FormatUtil.nanosToString(System.nanoTime() - start)); + + for (EditorClient editorClient : this.clientOpenAssetPathMapping.keySet()) { + this.initializeClient(editorClient); + } + + this.initQueue.clear(); + } finally { + this.initLock.unlockWrite(var31); + } + } + + private void initializeClient(@Nonnull EditorClient editorClient) { + DataSource defaultDataSource = this.assetPackDataSources.get("Hytale:Hytale"); + boolean canDiscard = false; + boolean canEditAssets = editorClient.hasPermission("hytale.editor.asset"); + boolean canEditAssetPacks = editorClient.hasPermission("hytale.editor.packs.edit"); + boolean canCreateAssetPacks = editorClient.hasPermission("hytale.editor.packs.create"); + boolean canDeleteAssetPacks = editorClient.hasPermission("hytale.editor.packs.delete"); + editorClient.getPacketHandler().write(new AssetEditorCapabilities(false, canEditAssets, canCreateAssetPacks, canEditAssetPacks, canDeleteAssetPacks)); + editorClient.getPacketHandler().write(this.setupSchemasPacket); + this.assetTypeRegistry.sendPacket(editorClient); + AssetEditorAssetPackSetup packSetupPacket = new AssetEditorAssetPackSetup(); + packSetupPacket.packs = new Object2ObjectOpenHashMap<>(); + + for (Entry dataSourceEntry : this.assetPackDataSources.entrySet()) { + DataSource dataSource = dataSourceEntry.getValue(); + PluginManifest manifest = dataSource.getManifest(); + packSetupPacket.packs.put(dataSourceEntry.getKey(), toManifestPacket(manifest)); + } + + editorClient.getPacketHandler().write(packSetupPacket); + + for (DataSource dataSource : this.assetPackDataSources.values()) { + dataSource.getAssetTree().sendPackets(editorClient); + } + + this.getLogger().at(Level.INFO).log("Done Initializing %s", editorClient.getUsername()); + } + + public void handleEditorClientDisconnected(@Nonnull EditorClient editorClient, PacketHandler.DisconnectReason disconnectReason) { + IEventDispatcher dispatch = HytaleServer.get() + .getEventBus() + .dispatchFor(AssetEditorClientDisconnectEvent.class); + if (dispatch.hasListener()) { + dispatch.dispatch(new AssetEditorClientDisconnectEvent(editorClient, disconnectReason)); + } + + this.uuidToEditorClients.compute(editorClient.getUuid(), (uuid, clients) -> { + if (clients == null) { + return null; + } else { + clients.remove(editorClient); + return clients.isEmpty() ? null : clients; + } + }); + this.clientOpenAssetPathMapping.remove(editorClient); + this.clientsSubscribedToModifiedAssetsChanges.remove(editorClient); + } + + public void handleDeleteAssetPack(@Nonnull EditorClient editorClient, @Nonnull String packId) { + if (packId.equalsIgnoreCase("Hytale:Hytale")) { + editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Messages.UNKNOWN_ASSETPACK_MESSAGE); + } else { + DataSource dataSource = this.getDataSourceForPack(packId); + if (dataSource == null) { + editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Messages.UNKNOWN_ASSETPACK_MESSAGE); + } else { + AssetModule.get().unregisterPack(packId); + + Path targetPath; + try { + targetPath = dataSource.getRootPath().toRealPath(); + } catch (IOException var11) { + throw new RuntimeException("Failed to resolve the real path for asset pack directory while deleting asset pack '" + packId + "'.", var11); + } + + boolean isInModsDirectory = false; + + try { + if (targetPath.startsWith(PluginManager.MODS_PATH.toRealPath())) { + isInModsDirectory = true; + } + } catch (IOException var10) { + } + + if (!isInModsDirectory) { + for (Path modsPath : Options.getOptionSet().valuesOf(Options.MODS_DIRECTORIES)) { + try { + if (targetPath.startsWith(modsPath.toRealPath())) { + isInModsDirectory = true; + break; + } + } catch (IOException var12) { + } + } + } + + if (!isInModsDirectory) { + editorClient.sendPopupNotification( + AssetEditorPopupNotificationType.Error, Message.translation("server.assetEditor.messages.packOutsideDirectory") + ); + } else { + try { + FileUtil.deleteDirectory(targetPath); + } catch (Exception var9) { + this.getLogger().at(Level.SEVERE).withCause(var9).log("Failed to delete asset pack %s from disk", packId); + } + } + } + } + } + + public void handleUpdateAssetPack(@Nonnull EditorClient editorClient, @Nonnull String packId, @Nonnull AssetPackManifest packetManifest) { + if (packId.equals("Hytale:Hytale")) { + editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Messages.UNKNOWN_ASSETPACK_MESSAGE); + } else { + DataSource dataSource = this.getDataSourceForPack(packId); + if (dataSource == null) { + editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Messages.UNKNOWN_ASSETPACK_MESSAGE); + } else if (dataSource.isImmutable()) { + editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Message.translation("server.assetEditor.messages.assetsReadOnly")); + } else { + PluginManifest manifest = dataSource.getManifest(); + if (manifest == null) { + editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Message.translation("server.assetEditor.messages.manifestNotFound")); + } else { + boolean didIdentifierChange = false; + if (packetManifest.name != null && !packetManifest.name.isEmpty() && !manifest.getName().equals(packetManifest.name)) { + manifest.setName(packetManifest.name); + didIdentifierChange = true; + } + + if (packetManifest.group != null && !packetManifest.group.isEmpty() && !manifest.getGroup().equals(packetManifest.group)) { + manifest.setGroup(packetManifest.group); + didIdentifierChange = true; + } + + if (packetManifest.description != null) { + manifest.setDescription(packetManifest.description); + } + + if (packetManifest.website != null) { + manifest.setWebsite(packetManifest.website); + } + + if (packetManifest.version != null && !packetManifest.version.isEmpty()) { + try { + manifest.setVersion(Semver.fromString(packetManifest.version)); + } catch (IllegalArgumentException var14) { + this.getLogger().at(Level.WARNING).withCause(var14).log("Invalid version format: %s", packetManifest.version); + editorClient.sendPopupNotification( + AssetEditorPopupNotificationType.Error, Message.translation("server.assetEditor.messages.invalidVersionFormat") + ); + return; + } + } + + if (packetManifest.authors != null) { + List authors = new ObjectArrayList<>(); + + for (com.hypixel.hytale.protocol.packets.asseteditor.AuthorInfo packetAuthor : packetManifest.authors) { + AuthorInfo author = new AuthorInfo(); + author.setName(packetAuthor.name); + author.setEmail(packetAuthor.email); + author.setUrl(packetAuthor.url); + authors.add(author); + } + + manifest.setAuthors(authors); + } + + Path manifestPath = dataSource.getRootPath().resolve("manifest.json"); + + try { + BsonUtil.writeSync(manifestPath, PluginManifest.CODEC, manifest, this.getLogger()); + this.getLogger().at(Level.INFO).log("Saved manifest for pack %s", packId); + editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Success, Message.translation("server.assetEditor.messages.manifestSaved")); + } catch (IOException var13) { + this.getLogger().at(Level.SEVERE).withCause(var13).log("Failed to save manifest for pack %s", packId); + editorClient.sendPopupNotification( + AssetEditorPopupNotificationType.Error, Message.translation("server.assetEditor.messages.manifestSaveFailed") + ); + } + + this.broadcastPackAddedOrUpdated(packId, manifest); + if (didIdentifierChange) { + String newPackId = new PluginIdentifier(manifest).toString(); + Path packPath = dataSource.getRootPath(); + AssetModule assetModule = AssetModule.get(); + assetModule.unregisterPack(packId); + assetModule.registerPack(newPackId, packPath, manifest); + } + } + } + } + } + + public void handleCreateAssetPack(@Nonnull EditorClient editorClient, @Nonnull AssetPackManifest packetManifest, int requestToken) { + if (packetManifest.name == null || packetManifest.name.isEmpty()) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.packNameRequired")); + } else if (packetManifest.group != null && !packetManifest.group.isEmpty()) { + PluginManifest manifest = new PluginManifest(); + manifest.setName(packetManifest.name); + manifest.setGroup(packetManifest.group); + if (packetManifest.description != null) { + manifest.setDescription(packetManifest.description); + } + + if (packetManifest.website != null) { + manifest.setWebsite(packetManifest.website); + } + + if (packetManifest.version != null && !packetManifest.version.isEmpty()) { + try { + manifest.setVersion(Semver.fromString(packetManifest.version)); + } catch (IllegalArgumentException var12) { + this.getLogger().at(Level.WARNING).withCause(var12).log("Invalid version format: %s", packetManifest.version); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.invalidVersionFormat")); + return; + } + } + + if (packetManifest.authors != null) { + List authors = new ObjectArrayList<>(); + + for (com.hypixel.hytale.protocol.packets.asseteditor.AuthorInfo packetAuthor : packetManifest.authors) { + AuthorInfo author = new AuthorInfo(); + author.setName(packetAuthor.name); + author.setEmail(packetAuthor.email); + author.setUrl(packetAuthor.url); + authors.add(author); + } + + manifest.setAuthors(authors); + } + + String packId = new PluginIdentifier(manifest).toString(); + if (this.assetPackDataSources.containsKey(packId)) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.packAlreadyExists")); + } else { + Path modsPath = PluginManager.MODS_PATH; + String dirName = AssetPathUtil.removeInvalidFileNameChars( + packetManifest.group != null ? packetManifest.group + "." + packetManifest.name : packetManifest.name + ); + Path normalized = Path.of(dirName).normalize(); + if (AssetPathUtil.isInvalidFileName(normalized)) { + editorClient.sendFailureReply(requestToken, Messages.INVALID_FILENAME_MESSAGE); + } else { + Path packPath = modsPath.resolve(normalized).normalize(); + if (!packPath.startsWith(modsPath)) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.packOutsideDirectory")); + } else if (Files.exists(packPath)) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.packAlreadyExistsAtPath")); + } else { + try { + Files.createDirectories(packPath); + Path manifestPath = packPath.resolve("manifest.json"); + BsonUtil.writeSync(manifestPath, PluginManifest.CODEC, manifest, this.getLogger()); + AssetModule.get().registerPack(packId, packPath, manifest); + editorClient.sendSuccessReply(requestToken, Message.translation("server.assetEditor.messages.packCreated")); + this.getLogger().at(Level.INFO).log("Created new pack: %s at %s", packId, packPath); + } catch (IOException var11) { + this.getLogger().at(Level.SEVERE).withCause(var11).log("Failed to create pack %s", packId); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.packCreationFailed")); + } + } + } + } + } else { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.packGroupRequired")); + } + } + + private static AssetPackManifest toManifestPacket(@Nonnull PluginManifest manifest) { + AssetPackManifest packet = new AssetPackManifest(); + packet.name = manifest.getName(); + packet.description = manifest.getDescription() != null ? manifest.getDescription() : ""; + packet.group = manifest.getGroup(); + packet.version = manifest.getVersion() != null ? manifest.getVersion().toString() : ""; + packet.website = manifest.getWebsite() != null ? manifest.getWebsite() : ""; + List authors = new ObjectArrayList<>(); + + for (AuthorInfo a : manifest.getAuthors()) { + com.hypixel.hytale.protocol.packets.asseteditor.AuthorInfo authorInfo = new com.hypixel.hytale.protocol.packets.asseteditor.AuthorInfo( + a.getName(), a.getEmail(), a.getUrl() + ); + authors.add(authorInfo); + } + + packet.authors = authors.toArray(new com.hypixel.hytale.protocol.packets.asseteditor.AuthorInfo[0]); + return packet; + } + + private void broadcastPackAddedOrUpdated(String packId, PluginManifest manifest) { + AssetPackManifest manifestPacket = toManifestPacket(manifest); + + for (Set clients : this.uuidToEditorClients.values()) { + for (EditorClient client : clients) { + client.getPacketHandler().write(new AssetEditorUpdateAssetPack(packId, manifestPacket)); + } + } + } + + public void handleExportAssets(@Nonnull EditorClient editorClient, @Nonnull List paths) { + ObjectArrayList exportedAssets = new ObjectArrayList<>(); + + for (AssetPath assetPath : paths) { + DataSource dataSource = this.getDataSourceForPath(assetPath); + if (dataSource == null) { + this.getLogger().at(Level.WARNING).log("%s has no valid data source", assetPath); + AssetEditorAsset asset = new AssetEditorAsset(null, assetPath.toPacket()); + editorClient.getPacketHandler().write(new AssetEditorExportAssetInitialize(asset, null, 0, true)); + } else if (!this.isValidPath(dataSource, assetPath)) { + this.getLogger().at(Level.WARNING).log("%s is an invalid path", assetPath); + AssetEditorAsset asset = new AssetEditorAsset(null, assetPath.toPacket()); + editorClient.getPacketHandler().write(new AssetEditorExportAssetInitialize(asset, null, 0, true)); + } else if (this.assetTypeRegistry.getAssetTypeHandlerForPath(assetPath.path()) == null) { + this.getLogger().at(Level.WARNING).log("%s is not a valid asset type", assetPath); + AssetEditorAsset asset = new AssetEditorAsset(null, assetPath.toPacket()); + editorClient.getPacketHandler().write(new AssetEditorExportAssetInitialize(asset, null, 0, true)); + } else if (!dataSource.doesAssetExist(assetPath.path())) { + editorClient.getPacketHandler().write(new AssetEditorExportDeleteAssets(new AssetEditorAsset[]{new AssetEditorAsset(null, assetPath.toPacket())})); + } else { + byte[] bytes = dataSource.getAssetBytes(assetPath.path()); + if (bytes == null) { + this.getLogger().at(Level.WARNING).log("Tried to load %s for export but failed", assetPath); + editorClient.getPacketHandler().write(new AssetEditorExportAssetInitialize(new AssetEditorAsset(null, assetPath.toPacket()), null, 0, false)); + } else { + byte[][] parts = ArrayUtil.split(bytes, 2621440); + Packet[] packets = new Packet[2 + parts.length]; + packets[0] = new AssetEditorExportAssetInitialize(new AssetEditorAsset(null, assetPath.toPacket()), null, bytes.length, false); + + for (int partIndex = 0; partIndex < parts.length; partIndex++) { + packets[1 + partIndex] = new AssetEditorExportAssetPart(parts[partIndex]); + } + + packets[packets.length - 1] = new AssetEditorExportAssetFinalize(); + editorClient.getPacketHandler().write(packets); + Instant timestamp = dataSource.getLastModificationTimestamp(assetPath.path()); + exportedAssets.add(new TimestampedAssetReference(assetPath.toPacket(), timestamp != null ? timestamp.toString() : null)); + } + } + } + + editorClient.getPacketHandler().write(new AssetEditorExportComplete(exportedAssets.toArray(TimestampedAssetReference[]::new))); + } + + public void handleSelectAsset(@Nonnull EditorClient editorClient, @Nullable AssetPath assetPath) { + if (assetPath != null) { + DataSource dataSource = this.getDataSourceForPath(assetPath); + if (dataSource == null) { + return; + } + } + + String assetType = null; + String currentAssetType = null; + AssetPath currentPath = this.clientOpenAssetPathMapping.get(editorClient); + if (currentPath != null && !currentPath.equals(AssetPath.EMPTY_PATH)) { + AssetTypeHandler currentAssetTypeHandler = this.assetTypeRegistry.tryGetAssetTypeHandler(currentPath.path(), editorClient, -1); + if (currentAssetTypeHandler != null) { + currentAssetType = currentAssetTypeHandler.getConfig().id; + } + } + + if (assetPath != null) { + AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.tryGetAssetTypeHandler(assetPath.path(), editorClient, -1); + if (assetTypeHandler == null) { + return; + } + + assetType = assetTypeHandler.getConfig().id; + this.clientOpenAssetPathMapping.put(editorClient, assetPath); + } else { + this.clientOpenAssetPathMapping.put(editorClient, AssetPath.EMPTY_PATH); + } + + IEventDispatcher dispatch = HytaleServer.get() + .getEventBus() + .dispatchFor(AssetEditorSelectAssetEvent.class); + if (dispatch.hasListener()) { + dispatch.dispatch(new AssetEditorSelectAssetEvent(editorClient, assetType, assetPath, currentAssetType, currentPath)); + } + } + + public void handleFetchLastModifiedAssets(@Nonnull EditorClient editorClient) { + long stamp = this.globalEditLock.readLock(); + + try { + AssetEditorLastModifiedAssets packet = this.buildAssetEditorLastModifiedAssetsPacket(); + editorClient.getPacketHandler().write(packet); + } finally { + this.globalEditLock.unlockRead(stamp); + } + } + + public void handleAssetUpdate(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath, @Nonnull byte[] data, int requestToken) { + DataSource dataSource = this.getDataSourceForPath(assetPath); + if (dataSource == null) { + editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE); + } else if (dataSource.isImmutable()) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly")); + } else if (!this.isValidPath(dataSource, assetPath)) { + editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE); + } else { + AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.tryGetAssetTypeHandler(assetPath.path(), editorClient, requestToken); + if (assetTypeHandler != null) { + long stamp = this.globalEditLock.writeLock(); + + label79: { + try { + if (!dataSource.doesAssetExist(assetPath.path())) { + this.getLogger().at(Level.WARNING).log("%s does not exist", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.update.doesntExist")); + return; + } + + if (!assetTypeHandler.isValidData(data)) { + this.getLogger().at(Level.WARNING).log("Failed to validate data for %s", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.createAsset.failed")); + return; + } + + if (dataSource.updateAsset(assetPath.path(), data, editorClient)) { + this.updateAssetForConnectedClients(assetPath, data, editorClient); + this.sendModifiedAssetsUpdateToConnectedUsers(); + editorClient.sendSuccessReply(requestToken); + assetTypeHandler.loadAsset(assetPath, dataSource.getFullPathToAssetData(assetPath.path()), data, editorClient); + break label79; + } + + this.getLogger().at(Level.WARNING).log("Failed to update asset %s in data source!", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.update.failed")); + } finally { + this.globalEditLock.unlockWrite(stamp); + } + + return; + } + + this.getLogger().at(Level.INFO).log("Updated asset at %s", assetPath); + } + } + } + + public void handleJsonAssetUpdate( + @Nonnull EditorClient editorClient, + AssetPath assetPath, + @Nonnull String assetType, + int assetIndex, + @Nonnull JsonUpdateCommand[] commands, + int requestToken + ) { + AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.getAssetTypeHandler(assetType); + if (!(assetTypeHandler instanceof JsonTypeHandler)) { + this.getLogger().at(Level.WARNING).log("Invalid asset type %s", assetType); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.unknownAssetType").param("assetType", assetType)); + } else { + DataSource dataSource; + if (assetIndex > -1 && assetTypeHandler instanceof AssetStoreTypeHandler) { + AssetStore assetStore = ((AssetStoreTypeHandler)assetTypeHandler).getAssetStore(); + AssetMap assetMap = assetStore.getAssetMap(); + String keyString = AssetStoreUtil.getIdFromIndex(assetStore, assetIndex); + Object key = assetStore.decodeStringKey(keyString); + Path storedPath = assetMap.getPath(key); + String storedAssetPack = assetMap.getAssetPack(key); + if (storedPath == null || storedAssetPack == null) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.unknownAssetIndex")); + return; + } + + dataSource = this.getDataSourceForPack(storedAssetPack); + if (dataSource == null) { + editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE); + return; + } + + assetPath = new AssetPath(storedAssetPack, PathUtil.relativizePretty(dataSource.getRootPath(), storedPath)); + } else { + dataSource = this.getDataSourceForPath(assetPath); + if (dataSource == null) { + editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE); + return; + } + } + + if (dataSource.isImmutable()) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly")); + } else if (!this.isValidPath(dataSource, assetPath)) { + editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE); + } else if (!assetPath.path().startsWith(assetTypeHandler.getRootPath())) { + this.getLogger().at(Level.WARNING).log("%s is not within valid asset directory", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.directoryOutsideRoot")); + } else { + String fileExtension = PathUtil.getFileExtension(assetPath.path()); + if (!fileExtension.equalsIgnoreCase(assetTypeHandler.getConfig().fileExtension)) { + this.getLogger() + .at(Level.WARNING) + .log("File extension not matching. Expected %s, got %s", assetTypeHandler.getConfig().fileExtension, fileExtension); + this.getLogger() + .at(Level.WARNING) + .log("File extension not matching. Expected %s, got %s", assetTypeHandler.getConfig().fileExtension, fileExtension); + editorClient.sendFailureReply( + requestToken, + Message.translation("server.assetEditor.messages.fileExtensionMismatch").param("fileExtension", assetTypeHandler.getConfig().fileExtension) + ); + } else { + long stamp = this.globalEditLock.writeLock(); + + try { + byte[] bytes = dataSource.getAssetBytes(assetPath.path()); + if (bytes == null) { + this.getLogger().at(Level.WARNING).log("%s does not exist", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.update.doesntExist")); + return; + } + + AssetUpdateQuery.RebuildCacheBuilder rebuildCacheBuilder = AssetUpdateQuery.RebuildCache.builder(); + + BsonDocument asset; + try { + asset = this.applyCommandsToAsset(bytes, assetPath, commands, rebuildCacheBuilder); + String json = BsonUtil.toJson(asset) + "\n"; + bytes = json.getBytes(StandardCharsets.UTF_8); + } catch (Exception var23) { + this.getLogger().at(Level.WARNING).withCause(var23).log("Failed to apply commands to %s", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.update.failed")); + return; + } + + if (dataSource.updateAsset(assetPath.path(), bytes, editorClient)) { + AssetUndoRedoInfo undoRedo = this.undoRedoManager.getOrCreateUndoRedoStack(assetPath); + undoRedo.redoStack.clear(); + + for (JsonUpdateCommand command : commands) { + undoRedo.undoStack.push(command); + } + + this.updateJsonAssetForConnectedClients(assetPath, commands, editorClient); + editorClient.sendSuccessReply(requestToken); + this.sendModifiedAssetsUpdateToConnectedUsers(); + ((JsonTypeHandler)assetTypeHandler) + .loadAssetFromDocument( + assetPath, + dataSource.getFullPathToAssetData(assetPath.path()), + asset.clone(), + new AssetUpdateQuery(rebuildCacheBuilder.build()), + editorClient + ); + return; + } + + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.update.failed")); + } finally { + this.globalEditLock.unlockWrite(stamp); + } + } + } + } + } + + public void handleUndo(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath, int requestToken) { + DataSource dataSource = this.getDataSourceForPath(assetPath); + if (dataSource == null) { + editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE); + } else if (dataSource.isImmutable()) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly")); + } else if (!this.isValidPath(dataSource, assetPath)) { + editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE); + } else { + AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.tryGetAssetTypeHandler(assetPath.path(), editorClient, requestToken); + if (assetTypeHandler != null) { + if (!(assetTypeHandler instanceof JsonTypeHandler)) { + this.getLogger().at(Level.WARNING).log("Undo can only be applied to an instance of JsonTypeHandler"); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.invalidAssetType")); + } else { + long stamp = this.globalEditLock.writeLock(); + + try { + AssetUndoRedoInfo undoRedo = this.undoRedoManager.getUndoRedoStack(assetPath); + if (undoRedo == null || undoRedo.undoStack.isEmpty()) { + this.getLogger().at(Level.INFO).log("Nothing to undo"); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.undo.empty")); + return; + } + + JsonUpdateCommand command = undoRedo.undoStack.peek(); + JsonUpdateCommand undoCommand = new JsonUpdateCommand(); + undoCommand.rebuildCaches = command.rebuildCaches; + if (command.firstCreatedProperty != null) { + undoCommand.type = JsonUpdateType.RemoveProperty; + undoCommand.path = command.firstCreatedProperty; + } else { + undoCommand.type = command.type == JsonUpdateType.RemoveProperty ? JsonUpdateType.InsertProperty : JsonUpdateType.SetProperty; + undoCommand.path = command.path; + undoCommand.value = command.previousValue; + } + + byte[] bytes = dataSource.getAssetBytes(assetPath.path()); + if (bytes == null) { + this.getLogger().at(Level.WARNING).log("%s does not exist", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.update.doesntExist")); + return; + } + + AssetUpdateQuery.RebuildCacheBuilder rebuildCacheBuilder = AssetUpdateQuery.RebuildCache.builder(); + + BsonDocument asset; + try { + asset = this.applyCommandsToAsset(bytes, assetPath, new JsonUpdateCommand[]{undoCommand}, rebuildCacheBuilder); + String json = BsonUtil.toJson(asset) + "\n"; + bytes = json.getBytes(StandardCharsets.UTF_8); + } catch (Exception var18) { + this.getLogger().at(Level.WARNING).withCause(var18).log("Failed to undo for %s", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.undo.failed")); + return; + } + + if (dataSource.updateAsset(assetPath.path(), bytes, editorClient)) { + undoRedo.undoStack.poll(); + undoRedo.redoStack.push(command); + this.updateJsonAssetForConnectedClients(assetPath, new JsonUpdateCommand[]{undoCommand}, editorClient); + editorClient.getPacketHandler().write(new AssetEditorUndoRedoReply(requestToken, undoCommand)); + this.sendModifiedAssetsUpdateToConnectedUsers(); + ((JsonTypeHandler)assetTypeHandler) + .loadAssetFromDocument( + assetPath, + dataSource.getFullPathToAssetData(assetPath.path()), + asset.clone(), + new AssetUpdateQuery(rebuildCacheBuilder.build()), + editorClient + ); + return; + } + + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.undo.failed")); + } finally { + this.globalEditLock.unlockWrite(stamp); + } + } + } + } + } + + public void handleRedo(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath, int requestToken) { + DataSource dataSource = this.getDataSourceForPath(assetPath); + if (dataSource == null) { + editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE); + } else if (dataSource.isImmutable()) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly")); + } else if (!this.isValidPath(dataSource, assetPath)) { + editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE); + } else { + AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.tryGetAssetTypeHandler(assetPath.path(), editorClient, requestToken); + if (assetTypeHandler != null) { + if (!(assetTypeHandler instanceof JsonTypeHandler)) { + this.getLogger().at(Level.WARNING).log("Redo can only be applied to an instance of JsonTypeHandler"); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.invalidAssetType")); + } else { + long stamp = this.globalEditLock.writeLock(); + + try { + AssetUndoRedoInfo undoRedo = this.undoRedoManager.getUndoRedoStack(assetPath); + if (undoRedo == null || undoRedo.redoStack.isEmpty()) { + this.getLogger().at(Level.WARNING).log("Nothing to redo"); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.redo.empty")); + return; + } + + byte[] bytes = dataSource.getAssetBytes(assetPath.path()); + if (bytes == null) { + this.getLogger().at(Level.WARNING).log("%s does not exist", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.update.doesntExist")); + return; + } + + JsonUpdateCommand command = undoRedo.redoStack.peek(); + AssetUpdateQuery.RebuildCacheBuilder rebuildCacheBuilder = AssetUpdateQuery.RebuildCache.builder(); + + BsonDocument asset; + try { + asset = this.applyCommandsToAsset(bytes, assetPath, new JsonUpdateCommand[]{command}, rebuildCacheBuilder); + String json = BsonUtil.toJson(asset) + "\n"; + bytes = json.getBytes(StandardCharsets.UTF_8); + } catch (Exception var17) { + this.getLogger().at(Level.WARNING).withCause(var17).log("Failed to redo for %s", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.redo.failed")); + return; + } + + if (dataSource.updateAsset(assetPath.path(), bytes, editorClient)) { + undoRedo.redoStack.poll(); + undoRedo.undoStack.push(command); + this.updateJsonAssetForConnectedClients(assetPath, new JsonUpdateCommand[]{command}, editorClient); + editorClient.getPacketHandler().write(new AssetEditorUndoRedoReply(requestToken, command)); + this.sendModifiedAssetsUpdateToConnectedUsers(); + ((JsonTypeHandler)assetTypeHandler) + .loadAssetFromDocument( + assetPath, + dataSource.getFullPathToAssetData(assetPath.path()), + asset.clone(), + new AssetUpdateQuery(rebuildCacheBuilder.build()), + editorClient + ); + return; + } + + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.redo.failed")); + } finally { + this.globalEditLock.unlockWrite(stamp); + } + } + } + } + } + + public void handleFetchAsset(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath, int requestToken) { + DataSource dataSource = this.getDataSourceForPath(assetPath); + if (dataSource == null) { + editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE); + } else if (!this.isValidPath(dataSource, assetPath)) { + editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE); + } else if (this.assetTypeRegistry.tryGetAssetTypeHandler(assetPath.path(), editorClient, requestToken) != null) { + long stamp = this.globalEditLock.readLock(); + + try { + if (!dataSource.doesAssetExist(assetPath.path())) { + this.getLogger().at(Level.WARNING).log("%s is not a regular file", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.fetchAsset.doesntExist")); + return; + } + + byte[] asset = dataSource.getAssetBytes(assetPath.path()); + if (asset != null) { + this.getLogger().at(Level.INFO).log("Got '%s'", assetPath); + editorClient.getPacketHandler().write(new AssetEditorFetchAssetReply(requestToken, asset)); + return; + } + + this.getLogger().at(Level.INFO).log("Failed to get '%s'", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.fetchAsset.failed")); + } finally { + this.globalEditLock.unlockRead(stamp); + } + } + } + + public void handleFetchJsonAssetWithParents(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath, boolean isFromOpenedTab, int requestToken) { + DataSource dataSource = this.getDataSourceForPath(assetPath); + if (dataSource == null) { + editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE); + } else if (!this.isValidPath(dataSource, assetPath)) { + editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE); + } else if (this.assetTypeRegistry.tryGetAssetTypeHandler(assetPath.path(), editorClient, requestToken) != null) { + long stamp = this.globalEditLock.readLock(); + + try { + byte[] asset = dataSource.getAssetBytes(assetPath.path()); + if (asset != null) { + this.getLogger().at(Level.INFO).log("Got '%s'", assetPath); + BsonDocument bson = BsonDocument.parse(new String(asset, StandardCharsets.UTF_8)); + Object2ObjectOpenHashMap assets = new Object2ObjectOpenHashMap<>(); + assets.put(assetPath.toPacket(), BsonUtil.translateBsonToJson(bson).getAsJsonObject().toString()); + editorClient.getPacketHandler().write(new AssetEditorFetchJsonAssetWithParentsReply(requestToken, assets)); + return; + } + + this.getLogger().at(Level.INFO).log("Failed to get '%s'", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.fetchAsset.failed")); + } finally { + this.globalEditLock.unlockRead(stamp); + } + } + } + + public void handleRequestChildIds(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath) { + DataSource dataSource = this.getDataSourceForPath(assetPath); + if (dataSource == null) { + editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Messages.UNKNOWN_ASSETPACK_MESSAGE); + } else if (!this.isValidPath(dataSource, assetPath)) { + editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Messages.OUTSIDE_ASSET_ROOT_MESSAGE); + } else { + AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.getAssetTypeHandlerForPath(assetPath.path()); + if (!(assetTypeHandler instanceof AssetStoreTypeHandler)) { + this.getLogger().at(Level.WARNING).log("Invalid asset type for %s", assetPath); + editorClient.sendPopupNotification( + AssetEditorPopupNotificationType.Error, Message.translation("server.assetEditor.messages.requestChildIds.assetTypeMissing") + ); + } else { + AssetStore assetStore = ((AssetStoreTypeHandler)assetTypeHandler).getAssetStore(); + Object key = assetStore.decodeFilePathKey(assetPath.path()); + Set children = assetStore.getAssetMap().getChildren(key); + HashSet childrenIds = new HashSet<>(); + if (children != null) { + for (Object child : children) { + if (assetStore.getAssetMap().getPath(child) != null) { + childrenIds.add(child.toString()); + } + } + } + + this.getLogger().at(Level.INFO).log("Children ids for '%s': %s", key.toString(), childrenIds); + editorClient.getPacketHandler().write(new AssetEditorRequestChildrenListReply(assetPath.toPacket(), childrenIds.toArray(String[]::new))); + } + } + } + + public void handleDeleteAsset(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath, int requestToken) { + DataSource dataSource = this.getDataSourceForPath(assetPath); + if (dataSource == null) { + editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE); + } else if (dataSource.isImmutable()) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly")); + } else if (!this.isValidPath(dataSource, assetPath)) { + editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE); + } else { + AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.tryGetAssetTypeHandler(assetPath.path(), editorClient, requestToken); + if (assetTypeHandler != null) { + long stamp = this.globalEditLock.writeLock(); + + label68: { + try { + if (!dataSource.doesAssetExist(assetPath.path())) { + this.getLogger().at(Level.WARNING).log("%s does not exist", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.deleteAsset.alreadyDeleted")); + return; + } + + if (dataSource.deleteAsset(assetPath.path(), editorClient)) { + this.undoRedoManager.clearUndoRedoStack(assetPath); + AssetEditorFileEntry entry = dataSource.getAssetTree().removeAsset(assetPath.path()); + AssetEditorAssetListUpdate packet = new AssetEditorAssetListUpdate(assetPath.packId(), null, new AssetEditorFileEntry[]{entry}); + editorClient.sendSuccessReply(requestToken); + this.sendPacketToAllEditorUsersExcept(packet, editorClient); + this.sendModifiedAssetsUpdateToConnectedUsers(); + assetTypeHandler.unloadAsset(assetPath); + break label68; + } + + this.getLogger().at(Level.WARNING).log("Failed to delete %s from data source", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.failedToDeleteAsset")); + } finally { + this.globalEditLock.unlockWrite(stamp); + } + + return; + } + + this.getLogger().at(Level.INFO).log("Deleted asset %s", assetPath); + } + } + } + + public void handleSubscribeToModifiedAssetsChanges(EditorClient editorClient) { + this.clientsSubscribedToModifiedAssetsChanges.add(editorClient); + } + + public void handleUnsubscribeFromModifiedAssetsChanges(EditorClient editorClient) { + this.clientsSubscribedToModifiedAssetsChanges.remove(editorClient); + } + + public void handleRenameAsset(@Nonnull EditorClient editorClient, @Nonnull AssetPath oldAssetPath, @Nonnull AssetPath newAssetPath, int requestToken) { + DataSource dataSource = this.getDataSourceForPath(oldAssetPath); + if (dataSource == null) { + editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE); + } else if (dataSource.isImmutable()) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly")); + } else if (!this.isValidPath(dataSource, oldAssetPath)) { + editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE); + } else if (!this.isValidPath(dataSource, newAssetPath)) { + editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE); + } else { + AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.tryGetAssetTypeHandler(oldAssetPath.path(), editorClient, requestToken); + if (assetTypeHandler != null) { + String fileExtensionNew = PathUtil.getFileExtension(newAssetPath.path()); + if (!fileExtensionNew.equalsIgnoreCase(assetTypeHandler.getConfig().fileExtension)) { + this.getLogger() + .at(Level.WARNING) + .log("File extension not matching. Expected %s, got %s", assetTypeHandler.getConfig().fileExtension, fileExtensionNew); + editorClient.sendFailureReply( + requestToken, + Message.translation("server.assetEditor.messages.fileExtensionMismatch").param("fileExtension", assetTypeHandler.getConfig().fileExtension) + ); + } else if (!newAssetPath.path().startsWith(assetTypeHandler.getRootPath())) { + this.getLogger().at(Level.WARNING).log("%s is not within valid asset directory", newAssetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.directoryOutsideRoot")); + } else { + long stamp = this.globalEditLock.writeLock(); + + try { + if (dataSource.doesAssetExist(newAssetPath.path())) { + this.getLogger().at(Level.WARNING).log("%s already exists", newAssetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.renameAsset.alreadyExists")); + return; + } + + byte[] oldAsset = dataSource.getAssetBytes(oldAssetPath.path()); + if (oldAsset == null) { + this.getLogger().at(Level.WARNING).log("%s is not a regular file", oldAssetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.renameAsset.doesntExist")); + return; + } + + if (dataSource.moveAsset(oldAssetPath.path(), newAssetPath.path(), editorClient)) { + AssetUndoRedoInfo undoRedo = this.undoRedoManager.clearUndoRedoStack(oldAssetPath); + if (undoRedo != null) { + this.undoRedoManager.putUndoRedoStack(newAssetPath, undoRedo); + } + + this.getLogger().at(Level.WARNING).log("Moved %s to %s", oldAssetPath, newAssetPath); + AssetEditorFileEntry oldEntry = dataSource.getAssetTree().removeAsset(oldAssetPath.path()); + AssetEditorFileEntry newEntry = dataSource.getAssetTree().ensureAsset(newAssetPath.path(), false); + AssetEditorAssetListUpdate packet = new AssetEditorAssetListUpdate( + oldAssetPath.packId(), new AssetEditorFileEntry[]{newEntry}, new AssetEditorFileEntry[]{oldEntry} + ); + this.sendPacketToAllEditorUsersExcept(packet, editorClient); + editorClient.sendSuccessReply(requestToken); + assetTypeHandler.unloadAsset(oldAssetPath); + assetTypeHandler.loadAsset(newAssetPath, dataSource.getFullPathToAssetData(newAssetPath.path()), oldAsset, editorClient); + return; + } + + this.getLogger().at(Level.WARNING).log("Failed to move file %s to %s", oldAssetPath, newAssetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.renameAsset.failed")); + } finally { + this.globalEditLock.unlockWrite(stamp); + } + } + } + } + } + + public void handleDeleteDirectory(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath, int requestToken) { + DataSource dataSource = this.getDataSourceForPath(assetPath); + if (dataSource.isImmutable()) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly")); + } else if (!this.isValidPath(dataSource, assetPath)) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.directoryOutsideRoot")); + } else if (!this.getAssetTypeRegistry().isPathInAssetTypeFolder(assetPath.path())) { + editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE); + } else { + long stamp = this.globalEditLock.writeLock(); + + try { + if (!dataSource.doesDirectoryExist(assetPath.path())) { + this.getLogger().at(Level.WARNING).log("Directory doesn't exist %s", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.createDirectory.alreadyExists")); + return; + } + + if (!dataSource.getAssetTree().isDirectoryEmpty(assetPath.path())) { + this.getLogger().at(Level.WARNING).log("%s must be empty", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.deleteDirectory.notEmpty")); + return; + } + + if (dataSource.deleteDirectory(assetPath.path())) { + AssetEditorFileEntry entry = dataSource.getAssetTree().removeAsset(assetPath.path()); + AssetEditorAssetListUpdate packet = new AssetEditorAssetListUpdate(assetPath.packId(), null, new AssetEditorFileEntry[]{entry}); + this.sendPacketToAllEditorUsersExcept(packet, editorClient); + editorClient.sendSuccessReply(requestToken); + this.getLogger().at(Level.INFO).log("Deleted directory %s", assetPath); + return; + } + + this.getLogger().at(Level.WARNING).log("Directory %s could not be deleted!", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.deleteDirectory.failed")); + } finally { + this.globalEditLock.unlockWrite(stamp); + } + } + } + + public void handleRenameDirectory(@Nonnull EditorClient editorClient, AssetPath path, AssetPath newPath, int requestToken) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.renameDirectory.unsupported")); + } + + public void handleCreateDirectory(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath, int requestToken) { + DataSource dataSource = this.getDataSourceForPath(assetPath); + if (dataSource == null) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.createDirectory.noDataSource")); + } else if (dataSource.isImmutable()) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly")); + } else if (!this.isValidPath(dataSource, assetPath)) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.createDirectory.noPath")); + } else { + long stamp = this.globalEditLock.writeLock(); + + try { + if (dataSource.doesDirectoryExist(assetPath.path())) { + this.getLogger().at(Level.WARNING).log("Directory already exists at %s", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.createDirectory.alreadyExists")); + return; + } + + Path parentDirectoryPath = assetPath.path().getParent(); + if (!dataSource.doesDirectoryExist(parentDirectoryPath)) { + this.getLogger().at(Level.WARNING).log("Parent directory is missing for %s", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.parentDirectoryMissing")); + return; + } + + if (dataSource.createDirectory(assetPath.path(), editorClient)) { + AssetEditorFileEntry entry = dataSource.getAssetTree().ensureAsset(assetPath.path(), true); + if (entry != null) { + AssetEditorAssetListUpdate packet = new AssetEditorAssetListUpdate(assetPath.packId(), new AssetEditorFileEntry[]{entry}, null); + this.sendPacketToAllEditorUsersExcept(packet, editorClient); + } + + editorClient.sendSuccessReply(requestToken); + this.getLogger().at(Level.WARNING).log("Created directory %s", assetPath); + return; + } + + this.getLogger().at(Level.WARNING).log("Failed to create directory %s", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.failedToCreateDirectory")); + } finally { + this.globalEditLock.unlockWrite(stamp); + } + } + } + + public void handleCreateAsset( + @Nonnull EditorClient editorClient, + @Nonnull AssetPath assetPath, + @Nonnull byte[] data, + @Nonnull AssetEditorRebuildCaches rebuildCaches, + String buttonId, + int requestToken + ) { + DataSource dataSource = this.getDataSourceForPath(assetPath); + if (dataSource == null) { + editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE); + } else if (dataSource.isImmutable()) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly")); + } else if (!this.isValidPath(dataSource, assetPath)) { + editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE); + } else { + AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.tryGetAssetTypeHandler(assetPath.path(), editorClient, requestToken); + if (assetTypeHandler != null) { + long stamp = this.globalEditLock.writeLock(); + + try { + if (dataSource.doesAssetExist(assetPath.path())) { + this.getLogger().at(Level.WARNING).log("%s already exists", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.createAsset.idAlreadyExists")); + return; + } + + if (!assetTypeHandler.isValidData(data)) { + this.getLogger().at(Level.WARNING).log("Failed to validate data for %s", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.createAsset.failed")); + return; + } + + if (dataSource.createAsset(assetPath.path(), data, editorClient)) { + this.getLogger().at(Level.INFO).log("Created asset %s", assetPath); + AssetEditorFileEntry entry = dataSource.getAssetTree().ensureAsset(assetPath.path(), false); + if (entry != null) { + AssetEditorAssetListUpdate updatePacket = new AssetEditorAssetListUpdate(assetPath.packId(), new AssetEditorFileEntry[]{entry}, null); + this.sendPacketToAllEditorUsersExcept(updatePacket, editorClient); + } + + this.sendModifiedAssetsUpdateToConnectedUsers(); + AssetUpdateQuery.RebuildCache rebuildCache = new AssetUpdateQuery.RebuildCache( + rebuildCaches.blockTextures, + rebuildCaches.models, + rebuildCaches.modelTextures, + rebuildCaches.mapGeometry, + rebuildCaches.itemIcons, + assetPath.path().startsWith(AssetPathUtil.PATH_DIR_COMMON) + ); + assetTypeHandler.loadAsset( + assetPath, dataSource.getFullPathToAssetData(assetPath.path()), data, new AssetUpdateQuery(rebuildCache), editorClient + ); + IEventDispatcher dispatch = HytaleServer.get() + .getEventBus() + .dispatchFor(AssetEditorAssetCreatedEvent.class, assetTypeHandler.getConfig().id); + if (dispatch.hasListener()) { + dispatch.dispatch(new AssetEditorAssetCreatedEvent(editorClient, assetTypeHandler.getConfig().id, assetPath.path(), data, buttonId)); + } + + editorClient.sendSuccessReply(requestToken); + return; + } + + this.getLogger().at(Level.WARNING).log("Failed to create asset %s", assetPath); + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.createAsset.failed")); + } finally { + this.globalEditLock.unlockWrite(stamp); + } + } + } + } + + private BsonDocument applyCommandsToAsset( + @Nonnull byte[] bytes, AssetPath path, @Nonnull JsonUpdateCommand[] commands, @Nonnull AssetUpdateQuery.RebuildCacheBuilder rebuildCache + ) { + BsonDocument asset = BsonDocument.parse(new String(bytes, StandardCharsets.UTF_8)); + this.getLogger().at(Level.INFO).log("Applying commands to %s with %s", path, asset); + + for (JsonUpdateCommand command : commands) { + switch (command.type) { + case SetProperty: { + BsonValue value = BsonDocument.parse(command.value).get("value"); + this.getLogger().at(Level.INFO).log("Setting property %s to %s", String.join(".", command.path), value); + if (command.path.length > 0) { + BsonTransformationUtil.setProperty(asset, command.path, value); + } else { + asset = (BsonDocument)value; + } + break; + } + case InsertProperty: { + BsonValue value = BsonDocument.parse(command.value).get("value"); + this.getLogger().at(Level.INFO).log("Inserting property %s with %s", String.join(".", command.path), value); + BsonTransformationUtil.insertProperty(asset, command.path, value); + break; + } + case RemoveProperty: + this.getLogger().at(Level.INFO).log("Removing property %s", String.join(".", command.path)); + BsonTransformationUtil.removeProperty(asset, command.path); + } + } + + this.getLogger().at(Level.INFO).log("Updated %s resulting: %s", path, asset); + + for (JsonUpdateCommand command : commands) { + if (command.rebuildCaches != null) { + if (command.rebuildCaches.blockTextures) { + rebuildCache.setBlockTextures(true); + } + + if (command.rebuildCaches.modelTextures) { + rebuildCache.setModelTextures(true); + } + + if (command.rebuildCaches.models) { + rebuildCache.setModels(true); + } + + if (command.rebuildCaches.mapGeometry) { + rebuildCache.setMapGeometry(true); + } + + if (command.rebuildCaches.itemIcons) { + rebuildCache.setItemIcons(true); + } + } + } + + return asset; + } + + private void sendModifiedAssetsUpdateToConnectedUsers() { + if (!this.clientOpenAssetPathMapping.isEmpty()) { + if (!this.clientsSubscribedToModifiedAssetsChanges.isEmpty()) { + AssetEditorLastModifiedAssets lastModifiedAssetsPacket = this.buildAssetEditorLastModifiedAssetsPacket(); + + for (EditorClient p : this.clientsSubscribedToModifiedAssetsChanges) { + p.getPacketHandler().write(lastModifiedAssetsPacket); + } + } + } + } + + private void sendPacketToAllEditorUsers(@Nonnull Packet packet) { + for (EditorClient editorClient : this.clientOpenAssetPathMapping.keySet()) { + editorClient.getPacketHandler().write(packet); + } + } + + private void sendPacketToAllEditorUsersExcept(@Nonnull Packet packet, EditorClient ignoreEditorClient) { + for (EditorClient editorClient : this.clientOpenAssetPathMapping.keySet()) { + if (!editorClient.equals(ignoreEditorClient)) { + editorClient.getPacketHandler().write(packet); + } + } + } + + private void updateAssetForConnectedClients(@Nonnull AssetPath assetPath) { + this.updateAssetForConnectedClients(assetPath, null); + } + + private void updateAssetForConnectedClients(@Nonnull AssetPath assetPath, EditorClient ignoreEditorClient) { + DataSource dataSource = this.getDataSourceForPath(assetPath); + byte[] bytes = dataSource.getAssetBytes(assetPath.path()); + this.updateAssetForConnectedClients(assetPath, bytes, ignoreEditorClient); + } + + private void updateAssetForConnectedClients(@Nonnull AssetPath assetPath, byte[] bytes, EditorClient ignoreEditorClient) { + AssetEditorAssetUpdated updatePacket = new AssetEditorAssetUpdated(assetPath.toPacket(), bytes); + + for (Entry entry : this.clientOpenAssetPathMapping.entrySet()) { + if (!entry.getKey().equals(ignoreEditorClient) && assetPath.equals(entry.getValue())) { + entry.getKey().getPacketHandler().write(updatePacket); + } + } + } + + private void updateJsonAssetForConnectedClients(@Nonnull AssetPath assetPath, JsonUpdateCommand[] commands) { + this.updateJsonAssetForConnectedClients(assetPath, commands, null); + } + + private void updateJsonAssetForConnectedClients(@Nonnull AssetPath assetPath, JsonUpdateCommand[] commands, EditorClient ignoreEditorClient) { + AssetEditorJsonAssetUpdated updatePacket = new AssetEditorJsonAssetUpdated(assetPath.toPacket(), commands); + + for (Entry connectedPlayer : this.clientOpenAssetPathMapping.entrySet()) { + if (!connectedPlayer.getKey().equals(ignoreEditorClient) && assetPath.equals(connectedPlayer.getValue())) { + connectedPlayer.getKey().getPacketHandler().write(updatePacket); + } + } + } + + @Nonnull + private AssetEditorLastModifiedAssets buildAssetEditorLastModifiedAssetsPacket() { + ArrayList allAssets = new ArrayList<>(); + + for (Entry dataSource : this.assetPackDataSources.entrySet()) { + if (dataSource.getValue() instanceof StandardDataSource standardDataSource) { + for (ModifiedAsset assetInfo : standardDataSource.getRecentlyModifiedAssets().values()) { + allAssets.add(assetInfo.toAssetInfoPacket(dataSource.getKey())); + } + } + } + + return new AssetEditorLastModifiedAssets(allAssets.toArray(new AssetInfo[0])); + } + + boolean isValidPath(@Nonnull DataSource dataSource, @Nonnull AssetPath assetPath) { + String assetPathString = PathUtil.toUnixPathString(assetPath.path()); + Path rootPath = dataSource.getRootPath(); + Path absolutePath = rootPath.resolve(assetPathString).toAbsolutePath().normalize(); + if (!absolutePath.startsWith(rootPath)) { + return false; + } else { + String relativePath = PathUtil.toUnixPathString(rootPath.relativize(absolutePath)); + return relativePath.equals(assetPathString); + } + } + + static { + new SchemaContext(); + } + + public static class AssetToDiscard { + public final AssetPath path; + @Nullable + public final Instant lastModificationDate; + + public AssetToDiscard(AssetPath path, @Nullable String lastModificationDate) { + this.path = path; + if (lastModificationDate != null) { + this.lastModificationDate = Instant.parse(lastModificationDate); + } else { + this.lastModificationDate = null; + } + } + } + + static enum DiscardResult { + FAILED, + SUCCEEDED, + SUCCEEDED_COMMON_ASSETS_CHANGED; + + private DiscardResult() { + } + } + + static enum InitState { + NOT_INITIALIZED, + INITIALIZING, + INITIALIZED; + + private InitState() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/AssetPath.java b/src/com/hypixel/hytale/builtin/asseteditor/AssetPath.java new file mode 100644 index 0000000..0fc181f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/AssetPath.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.builtin.asseteditor; + +import com.hypixel.hytale.common.util.PathUtil; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public record AssetPath(@Nonnull String packId, @Nonnull Path path) { + public static final AssetPath EMPTY_PATH = new AssetPath("", Path.of("")); + + public AssetPath(com.hypixel.hytale.protocol.packets.asseteditor.AssetPath assetPath) { + this(assetPath.pack, Path.of(assetPath.path)); + } + + public com.hypixel.hytale.protocol.packets.asseteditor.AssetPath toPacket() { + return new com.hypixel.hytale.protocol.packets.asseteditor.AssetPath(this.packId, PathUtil.toUnixPathString(this.path)); + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/AssetSpecificFunctionality.java b/src/com/hypixel/hytale/builtin/asseteditor/AssetSpecificFunctionality.java new file mode 100644 index 0000000..a0f1f67 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/AssetSpecificFunctionality.java @@ -0,0 +1,502 @@ +package com.hypixel.hytale.builtin.asseteditor; + +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.builtin.asseteditor.assettypehandler.AssetStoreTypeHandler; +import com.hypixel.hytale.builtin.asseteditor.assettypehandler.AssetTypeHandler; +import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorActivateButtonEvent; +import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorAssetCreatedEvent; +import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorClientDisconnectEvent; +import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorFetchAutoCompleteDataEvent; +import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorRequestDataSetEvent; +import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorSelectAssetEvent; +import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorUpdateWeatherPreviewLockEvent; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.EventRegistry; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.InstantData; +import com.hypixel.hytale.protocol.Model; +import com.hypixel.hytale.protocol.Vector2f; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorPopupNotificationType; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorPreviewCameraSettings; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUpdateModelPreview; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUpdateSecondsPerGameDay; +import com.hypixel.hytale.protocol.packets.world.ClearEditorTimeOverride; +import com.hypixel.hytale.protocol.packets.world.UpdateEditorTimeOverride; +import com.hypixel.hytale.protocol.packets.world.UpdateEditorWeatherOverride; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.core.asset.type.item.config.AssetIconProperties; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemArmor; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSkinComponent; +import com.hypixel.hytale.server.core.modules.i18n.I18nModule; +import com.hypixel.hytale.server.core.modules.item.ItemModule; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.PlayerUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.nio.file.Path; +import java.time.Instant; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@Deprecated +public class AssetSpecificFunctionality { + private static final Message NO_GAME_CLIENT_MESSAGE = Message.translation("server.assetEditor.messages.noGameClient"); + private static final ClearEditorTimeOverride CLEAR_EDITOR_TIME_OVERRIDE_PACKET = new ClearEditorTimeOverride(); + private static final UpdateEditorWeatherOverride CLEAR_WEATHER_OVERRIDE_PACKET = new UpdateEditorWeatherOverride(0); + private static final String MODEL_ASSET_ID = ModelAsset.class.getSimpleName(); + private static final String ITEM_ASSET_ID = Item.class.getSimpleName(); + private static final String WEATHER_ASSET_ID = Weather.class.getSimpleName(); + private static final String ENVIRONMENT_ASSET_ID = Environment.class.getSimpleName(); + private static final Map activeWeatherPreviewMapping = new ConcurrentHashMap<>(); + private static final AssetEditorPreviewCameraSettings DEFAULT_PREVIEW_CAMERA_SETTINGS = new AssetEditorPreviewCameraSettings( + 0.25F, new Vector3f(0.0F, 75.0F, 0.0F), new Vector3f(0.0F, (float)Math.toRadians(45.0), 0.0F) + ); + + public AssetSpecificFunctionality() { + } + + public static void setup() { + getEventRegistry().register(LoadedAssetsEvent.class, ModelAsset.class, AssetSpecificFunctionality::onModelAssetLoaded); + getEventRegistry().register(LoadedAssetsEvent.class, Item.class, AssetSpecificFunctionality::onItemAssetLoaded); + getEventRegistry().register(AssetEditorActivateButtonEvent.class, "EquipItem", AssetSpecificFunctionality::onEquipItem); + getEventRegistry().register(AssetEditorActivateButtonEvent.class, "UseModel", AssetSpecificFunctionality::onUseModel); + getEventRegistry().register(AssetEditorActivateButtonEvent.class, "ResetModel", AssetSpecificFunctionality::onResetModel); + getEventRegistry().register(AssetEditorUpdateWeatherPreviewLockEvent.class, AssetSpecificFunctionality::onUpdateWeatherPreviewLockEvent); + getEventRegistry().register(AssetEditorAssetCreatedEvent.class, ITEM_ASSET_ID, AssetSpecificFunctionality::onItemAssetCreated); + getEventRegistry().register(AssetEditorAssetCreatedEvent.class, MODEL_ASSET_ID, AssetSpecificFunctionality::onModelAssetCreated); + getEventRegistry().register(AssetEditorFetchAutoCompleteDataEvent.class, "BlockGroups", AssetSpecificFunctionality::onRequestBlockGroupsDataSet); + getEventRegistry().register(AssetEditorFetchAutoCompleteDataEvent.class, "LocalizationKeys", AssetSpecificFunctionality::onRequestLocalizationKeyDataSet); + getEventRegistry().register(AssetEditorRequestDataSetEvent.class, "ItemCategories", AssetSpecificFunctionality::onRequestItemCategoriesDataSet); + getEventRegistry().registerGlobal(AssetEditorSelectAssetEvent.class, AssetSpecificFunctionality::onSelectAsset); + getEventRegistry().registerGlobal(AssetEditorClientDisconnectEvent.class, AssetSpecificFunctionality::onClientDisconnected); + } + + @Nullable + private static PlayerRef tryGetPlayer(@Nonnull EditorClient editorClient) { + PlayerRef playerRef = editorClient.tryGetPlayer(); + if (playerRef == null) { + editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Warning, NO_GAME_CLIENT_MESSAGE); + return null; + } else { + return playerRef; + } + } + + private static void onModelAssetLoaded(@Nonnull LoadedAssetsEvent event) { + if (!event.isInitial()) { + Map clientOpenAssetPathMapping = AssetEditorPlugin.get().getClientOpenAssetPathMapping(); + if (!clientOpenAssetPathMapping.isEmpty()) { + for (ModelAsset modelAsset : event.getLoadedAssets().values()) { + for (Entry editor : clientOpenAssetPathMapping.entrySet()) { + Path path = editor.getValue().path(); + if (!path.toString().isEmpty()) { + AssetTypeHandler assetType = AssetEditorPlugin.get().getAssetTypeRegistry().getAssetTypeHandlerForPath(path); + if (assetType instanceof AssetStoreTypeHandler + && ((AssetStoreTypeHandler)assetType).getAssetStore().getAssetClass().equals(ModelAsset.class)) { + String id = ModelAsset.getAssetStore().decodeFilePathKey(path); + if (modelAsset.getId().equals(id)) { + Model modelPacket = com.hypixel.hytale.server.core.asset.type.model.config.Model.createUnitScaleModel(modelAsset).toPacket(); + AssetEditorUpdateModelPreview packet = new AssetEditorUpdateModelPreview( + editor.getValue().toPacket(), modelPacket, null, DEFAULT_PREVIEW_CAMERA_SETTINGS + ); + editor.getKey().getPacketHandler().write(packet); + } + } + } + } + } + } + } + } + + private static void onItemAssetLoaded(@Nonnull LoadedAssetsEvent event) { + if (!event.isInitial()) { + Map clientOpenAssetPathMapping = AssetEditorPlugin.get().getClientOpenAssetPathMapping(); + if (!clientOpenAssetPathMapping.isEmpty()) { + AssetUpdateQuery.RebuildCache rebuildCache = event.getQuery().getRebuildCache(); + if (rebuildCache.isBlockTextures() || rebuildCache.isModelTextures() || rebuildCache.isItemIcons() || rebuildCache.isModels()) { + for (Item item : event.getLoadedAssets().values()) { + for (Entry editor : clientOpenAssetPathMapping.entrySet()) { + Path path = editor.getValue().path(); + if (!path.toString().isEmpty()) { + AssetTypeHandler assetType = AssetEditorPlugin.get().getAssetTypeRegistry().getAssetTypeHandlerForPath(path); + if (assetType instanceof AssetStoreTypeHandler && ((AssetStoreTypeHandler)assetType).getAssetStore().getAssetClass().equals(Item.class) + ) + { + String id = Item.getAssetStore().decodeFilePathKey(path); + if (item.getId().equals(id)) { + AssetEditorUpdateModelPreview packet = getModelPreviewPacketForItem(editor.getValue(), item); + if (packet != null) { + editor.getKey().getPacketHandler().write(packet); + } + } + } + } + } + } + } + } + } + } + + private static void onItemAssetCreated(@Nonnull AssetEditorAssetCreatedEvent event) { + if ("EquipItem".equals(event.getButtonId())) { + equipItem(event.getAssetPath(), event.getEditorClient()); + } + } + + private static void onModelAssetCreated(@Nonnull AssetEditorAssetCreatedEvent event) { + if ("UseModel".equals(event.getButtonId())) { + useModel(event.getAssetPath(), event.getEditorClient()); + } + } + + private static void onEquipItem(@Nonnull AssetEditorActivateButtonEvent event) { + AssetPath currentAssetPath = AssetEditorPlugin.get().getOpenAssetPath(event.getEditorClient()); + if (currentAssetPath != null && !currentAssetPath.path().toString().isEmpty()) { + equipItem(currentAssetPath.path(), event.getEditorClient()); + } + } + + private static void onUseModel(@Nonnull AssetEditorActivateButtonEvent event) { + AssetPath currentAssetPath = AssetEditorPlugin.get().getOpenAssetPath(event.getEditorClient()); + if (currentAssetPath != null && !currentAssetPath.path().toString().isEmpty()) { + useModel(currentAssetPath.path(), event.getEditorClient()); + } + } + + private static void onUpdateWeatherPreviewLockEvent(@Nonnull AssetEditorUpdateWeatherPreviewLockEvent event) { + AssetSpecificFunctionality.PlayerPreviewData currentPreviewSettings = activeWeatherPreviewMapping.computeIfAbsent( + event.getEditorClient().getUuid(), k -> new AssetSpecificFunctionality.PlayerPreviewData() + ); + currentPreviewSettings.keepPreview = event.isLocked(); + } + + private static void onResetModel(@Nonnull AssetEditorActivateButtonEvent event) { + EditorClient editorClient = event.getEditorClient(); + PlayerRef playerRef = tryGetPlayer(editorClient); + if (playerRef != null) { + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + if (!store.getArchetype(ref).contains(PlayerSkinComponent.getComponentType())) { + Message message = Message.translation("server.assetEditor.messages.model.noAuthSkinForPlayer").param("model", "Player"); + editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, message); + } else { + PlayerUtil.resetPlayerModel(ref, store); + } + }); + } + } + } + + private static void equipItem(@Nonnull Path assetPath, @Nonnull EditorClient editorClient) { + PlayerRef playerRef = tryGetPlayer(editorClient); + if (playerRef != null) { + Player player = playerRef.getComponent(Player.getComponentType()); + String key = Item.getAssetStore().decodeFilePathKey(assetPath); + Item item = Item.getAssetMap().getAsset(key); + if (item == null) { + editorClient.sendPopupNotification( + AssetEditorPopupNotificationType.Error, Message.translation("server.assetEditor.messages.unknownItem").param("id", key.toString()) + ); + } else { + ItemArmor itemArmor = item.getArmor(); + if (itemArmor != null) { + player.getInventory().getArmor().setItemStackForSlot((short)itemArmor.getArmorSlot().ordinal(), new ItemStack(key)); + } else { + player.getInventory().getCombinedHotbarFirst().addItemStack(new ItemStack(key)); + } + } + } + } + + private static void useModel(@Nonnull Path assetPath, @Nonnull EditorClient editorClient) { + PlayerRef playerRef = tryGetPlayer(editorClient); + if (playerRef != null) { + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + String key = ModelAsset.getAssetStore().decodeFilePathKey(assetPath); + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(key); + if (modelAsset == null) { + Message unknownModelMessage = Message.translation("server.assetEditor.messages.unknownModel").param("id", key); + editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, unknownModelMessage); + } else { + com.hypixel.hytale.server.core.asset.type.model.config.Model model = com.hypixel.hytale.server.core.asset.type.model.config.Model.createRandomScaleModel( + modelAsset + ); + store.putComponent(ref, ModelComponent.getComponentType(), new ModelComponent(model)); + } + } + ); + } + } + } + + private static void onRequestLocalizationKeyDataSet(@Nonnull AssetEditorFetchAutoCompleteDataEvent event) { + ObjectArrayList results = new ObjectArrayList<>(); + String query = event.getQuery().toLowerCase(); + + for (String key : I18nModule.get().getMessages("en-US").keySet()) { + if (key.toLowerCase().startsWith(query)) { + results.add(key); + } + + if (results.size() >= 25) { + break; + } + } + + event.setResults(results.toArray(String[]::new)); + } + + private static void onRequestBlockGroupsDataSet(@Nonnull AssetEditorFetchAutoCompleteDataEvent event) { + ObjectArrayList results = new ObjectArrayList<>(); + String query = event.getQuery().toLowerCase(); + + for (String group : BlockType.getAssetMap().getGroups()) { + if (group != null && !group.trim().isEmpty() && (query.isEmpty() || group.toLowerCase().contains(query))) { + results.add(group); + } + } + + event.setResults(results.toArray(String[]::new)); + } + + private static void onRequestItemCategoriesDataSet(@Nonnull AssetEditorRequestDataSetEvent event) { + ItemModule itemModule = ItemModule.get(); + if (itemModule.isDisabled()) { + HytaleLogger.getLogger().at(Level.WARNING).log("Received ItemCategories dataset request but ItemModule is disabled!"); + } else { + event.setResults(itemModule.getFlatItemCategoryList().toArray(String[]::new)); + } + } + + private static void onClientDisconnected(@Nonnull AssetEditorClientDisconnectEvent event) { + AssetEditorPlugin plugin = AssetEditorPlugin.get(); + EditorClient editorClient = event.getEditorClient(); + PlayerRef player = editorClient.tryGetPlayer(); + UUID uuid = editorClient.getUuid(); + Set editorClients = plugin.getEditorClients(uuid); + if (editorClients != null && editorClients.size() != 1) { + AssetPath openAssetPath = plugin.getOpenAssetPath(editorClient); + if (openAssetPath != null && !openAssetPath.equals(AssetPath.EMPTY_PATH)) { + AssetTypeHandler assetType = plugin.getAssetTypeRegistry().getAssetTypeHandlerForPath(openAssetPath.path()); + if (assetType != null && Weather.class.getSimpleName().equals(assetType.getConfig().id)) { + activeWeatherPreviewMapping.remove(uuid); + if (player != null) { + player.getPacketHandler().write(new UpdateEditorWeatherOverride(0)); + } + } + } + } else { + if (player != null) { + player.getPacketHandler().write(CLEAR_EDITOR_TIME_OVERRIDE_PACKET); + player.getPacketHandler().write(CLEAR_WEATHER_OVERRIDE_PACKET); + } + + activeWeatherPreviewMapping.remove(uuid); + } + } + + static void resetTimeSettings(@Nonnull EditorClient editorClient, @Nonnull PlayerRef playerRef) { + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + Player playerComponent = playerRef.getComponent(Player.getComponentType()); + + assert playerComponent != null; + + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + PacketHandler packetHandler = editorClient.getPacketHandler(); + AssetEditorUpdateSecondsPerGameDay settingsPacket = new AssetEditorUpdateSecondsPerGameDay( + world.getDaytimeDurationSeconds(), world.getNighttimeDurationSeconds() + ); + packetHandler.write(settingsPacket); + Instant gameTime = worldTimeResource.getGameTime(); + UpdateEditorTimeOverride packet = new UpdateEditorTimeOverride( + new InstantData(gameTime.getEpochSecond(), gameTime.getNano()), world.getWorldConfig().isGameTimePaused() + ); + packetHandler.write(packet); + playerRef.getPacketHandler().write(CLEAR_EDITOR_TIME_OVERRIDE_PACKET); + } + } + + static void handleWeatherOrEnvironmentUnselected(@Nonnull EditorClient editorClient, @Nonnull Path assetPath, boolean wasWeather) { + PlayerRef player = editorClient.tryGetPlayer(); + if (player != null) { + AssetSpecificFunctionality.PlayerPreviewData currentPreviewSettings = activeWeatherPreviewMapping.computeIfAbsent( + editorClient.getUuid(), k -> new AssetSpecificFunctionality.PlayerPreviewData() + ); + if (!currentPreviewSettings.keepPreview) { + resetTimeSettings(editorClient, player); + if (wasWeather) { + if (!assetPath.equals(currentPreviewSettings.weatherAssetPath)) { + return; + } + + currentPreviewSettings.weatherAssetPath = null; + player.getPacketHandler().write(CLEAR_WEATHER_OVERRIDE_PACKET); + } + } + } + } + + static void handleWeatherOrEnvironmentSelected(@Nonnull EditorClient editorClient, @Nonnull Path assetPath, boolean isWeather) { + PlayerRef player = editorClient.tryGetPlayer(); + if (player != null) { + AssetSpecificFunctionality.PlayerPreviewData currentPreviewSettings = activeWeatherPreviewMapping.computeIfAbsent( + editorClient.getUuid(), k -> new AssetSpecificFunctionality.PlayerPreviewData() + ); + if (!currentPreviewSettings.keepPreview) { + resetTimeSettings(editorClient, player); + } + + if (isWeather) { + AssetStore> assetStore = Weather.getAssetStore(); + String key = assetStore.decodeFilePathKey(assetPath); + int weatherIndex = ((IndexedLookupTableAssetMap)assetStore.getAssetMap()).getIndex(key); + currentPreviewSettings.weatherAssetPath = assetPath; + player.getPacketHandler().write(new UpdateEditorWeatherOverride(weatherIndex)); + } + } + } + + private static void onSelectAsset(@Nonnull AssetEditorSelectAssetEvent event) { + String assetType = event.getAssetType(); + if (MODEL_ASSET_ID.equals(assetType)) { + String key = ModelAsset.getAssetStore().decodeFilePathKey(event.getAssetFilePath().path()); + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(key); + if (modelAsset != null) { + Model modelPacket = com.hypixel.hytale.server.core.asset.type.model.config.Model.createUnitScaleModel(modelAsset).toPacket(); + event.getEditorClient() + .getPacketHandler() + .write(new AssetEditorUpdateModelPreview(event.getAssetFilePath().toPacket(), modelPacket, null, DEFAULT_PREVIEW_CAMERA_SETTINGS)); + } + } + + if (ITEM_ASSET_ID.equals(assetType)) { + AssetPath assetPath = event.getAssetFilePath(); + String key = Item.getAssetStore().decodeFilePathKey(assetPath.path()); + Item item = Item.getAssetMap().getAsset(key); + if (item != null) { + AssetEditorUpdateModelPreview packet = getModelPreviewPacketForItem(assetPath, item); + if (packet != null) { + event.getEditorClient().getPacketHandler().write(packet); + } + } + } + + String previousAssetType = event.getPreviousAssetType(); + boolean wasWeather = WEATHER_ASSET_ID.equals(previousAssetType); + if (wasWeather || ENVIRONMENT_ASSET_ID.equals(previousAssetType)) { + handleWeatherOrEnvironmentUnselected(event.getEditorClient(), event.getPreviousAssetFilePath().path(), wasWeather); + } + + boolean isWeather = WEATHER_ASSET_ID.equals(assetType); + if (isWeather || ENVIRONMENT_ASSET_ID.equals(assetType)) { + handleWeatherOrEnvironmentSelected(event.getEditorClient(), event.getAssetFilePath().path(), isWeather); + } + } + + public static AssetEditorUpdateModelPreview getModelPreviewPacketForItem(@Nonnull AssetPath assetPath, @Nullable Item item) { + if (item == null) { + return null; + } else { + AssetIconProperties iconProperties = item.getIconProperties(); + AssetIconProperties defaultIconProperties = getDefaultItemIconProperties(item); + if (iconProperties == null) { + iconProperties = defaultIconProperties; + } + + AssetEditorPreviewCameraSettings camera = new AssetEditorPreviewCameraSettings(); + camera.modelScale = iconProperties.getScale() * item.getScale(); + Vector2f translation = iconProperties.getTranslation() != null ? iconProperties.getTranslation() : defaultIconProperties.getTranslation(); + camera.cameraPosition = new Vector3f(-translation.x, -translation.y, 0.0F); + Vector3f rotation = iconProperties.getRotation() != null ? iconProperties.getRotation() : defaultIconProperties.getRotation(); + camera.cameraOrientation = new Vector3f( + (float)(-Math.toRadians(rotation.x)), (float)(-Math.toRadians(rotation.y)), (float)(-Math.toRadians(rotation.z)) + ); + if (item.getBlockId() != null) { + BlockType blockType = (BlockType)((BlockTypeAssetMap)BlockType.getAssetStore().getAssetMap()).getAsset(item.getBlockId()); + if (blockType != null) { + camera.modelScale = camera.modelScale * blockType.getCustomModelScale(); + return new AssetEditorUpdateModelPreview(assetPath.toPacket(), null, blockType.toPacket(), camera); + } + } + + Model modelPacket = convertToModelPacket(item); + return new AssetEditorUpdateModelPreview(assetPath.toPacket(), modelPacket, null, camera); + } + } + + @Nonnull + public static AssetIconProperties getDefaultItemIconProperties(@Nonnull Item item) { + if (item.getWeapon() != null) { + return new AssetIconProperties(0.37F, new Vector2f(-24.6F, -24.6F), new Vector3f(45.0F, 90.0F, 0.0F)); + } else if (item.getTool() != null) { + return new AssetIconProperties(0.5F, new Vector2f(-17.4F, -12.0F), new Vector3f(45.0F, 270.0F, 0.0F)); + } else if (item.getArmor() != null) { + return switch (item.getArmor().getArmorSlot()) { + case Chest -> new AssetIconProperties(0.5F, new Vector2f(0.0F, -5.0F), new Vector3f(22.5F, 45.0F, 22.5F)); + case Head -> new AssetIconProperties(0.5F, new Vector2f(0.0F, -3.0F), new Vector3f(22.5F, 45.0F, 22.5F)); + case Legs -> new AssetIconProperties(0.5F, new Vector2f(0.0F, -25.8F), new Vector3f(22.5F, 45.0F, 22.5F)); + case Hands -> new AssetIconProperties(0.92F, new Vector2f(0.0F, -10.8F), new Vector3f(22.5F, 45.0F, 22.5F)); + }; + } else { + return new AssetIconProperties(0.58823F, new Vector2f(0.0F, -13.5F), new Vector3f(22.5F, 45.0F, 22.5F)); + } + } + + @Nonnull + public static Model convertToModelPacket(@Nonnull Item item) { + Model packet = new Model(); + packet.path = item.getModel(); + packet.texture = item.getTexture(); + return packet; + } + + @Nonnull + private static EventRegistry getEventRegistry() { + return AssetEditorPlugin.get().getEventRegistry(); + } + + public static class PlayerPreviewData { + @Nullable + private Path weatherAssetPath; + private boolean keepPreview; + + public PlayerPreviewData() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/AssetTree.java b/src/com/hypixel/hytale/builtin/asseteditor/AssetTree.java new file mode 100644 index 0000000..2e7e15f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/AssetTree.java @@ -0,0 +1,330 @@ +package com.hypixel.hytale.builtin.asseteditor; + +import com.hypixel.hytale.builtin.asseteditor.assettypehandler.AssetTypeHandler; +import com.hypixel.hytale.builtin.asseteditor.data.AssetState; +import com.hypixel.hytale.builtin.asseteditor.data.ModifiedAsset; +import com.hypixel.hytale.builtin.asseteditor.util.AssetPathUtil; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.common.util.ListUtil; +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetListSetup; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFileEntry; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFileTree; +import com.hypixel.hytale.server.core.asset.common.CommonAssetModule; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.StampedLock; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetTree { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final StampedLock lock = new StampedLock(); + private final Path rootPath; + private final String packKey; + private final boolean isReadOnly; + private final boolean canBeDeleted; + List serverAssets = new ObjectArrayList<>(); + List commonAssets = new ObjectArrayList<>(); + + public AssetTree(Path rootPath, String packKey, boolean isReadOnly, boolean canBeDeleted) { + this.rootPath = rootPath; + this.packKey = packKey; + this.isReadOnly = isReadOnly; + this.canBeDeleted = canBeDeleted; + } + + public AssetTree(Path rootPath, String packKey, boolean isReadOnly, boolean canBeDeleted, @Nonnull Collection assetTypes) { + this.rootPath = rootPath; + this.packKey = packKey; + this.isReadOnly = isReadOnly; + this.canBeDeleted = canBeDeleted; + this.load(assetTypes); + } + + public void replaceAssetTree(@Nonnull AssetTree assetTree) { + long stamp = this.lock.writeLock(); + + try { + this.serverAssets = assetTree.serverAssets; + this.commonAssets = assetTree.commonAssets; + } finally { + this.lock.unlockWrite(stamp); + } + } + + public void sendPackets(@Nonnull EditorClient editorClient) { + long stamp = this.lock.readLock(); + + try { + editorClient.getPacketHandler() + .write( + new AssetEditorAssetListSetup( + this.packKey, this.isReadOnly, this.canBeDeleted, AssetEditorFileTree.Server, this.serverAssets.toArray(AssetEditorFileEntry[]::new) + ) + ); + editorClient.getPacketHandler() + .write( + new AssetEditorAssetListSetup( + this.packKey, this.isReadOnly, this.canBeDeleted, AssetEditorFileTree.Common, this.commonAssets.toArray(AssetEditorFileEntry[]::new) + ) + ); + } finally { + this.lock.unlockRead(stamp); + } + } + + public boolean isDirectoryEmpty(@Nonnull Path path) { + String pathString = PathUtil.toUnixPathString(path); + long stamp = this.lock.readLock(); + + boolean hasFile; + try { + List assets = this.getAssetListForPath(path); + int index = ListUtil.binarySearch(assets, o -> o.path, pathString, String::compareTo); + if (index < 0) { + return true; + } + + if (!assets.get(index).isDirectory) { + return false; + } + + int fileIndex = index + 1; + if (fileIndex < assets.size()) { + hasFile = assets.get(fileIndex).path.startsWith(pathString + "/"); + return !hasFile; + } + + hasFile = false; + } finally { + this.lock.unlockRead(stamp); + } + + return hasFile; + } + + @Nullable + public AssetEditorFileEntry ensureAsset(@Nonnull Path path, boolean isDirectory) { + String pathString = PathUtil.toUnixPathString(path); + long stamp = this.lock.writeLock(); + + Object insertionPoint; + try { + List assets = this.getAssetListForPath(path); + int index = ListUtil.binarySearch(assets, o -> o.path, pathString, String::compareTo); + if (index < 0) { + int insertionPointx = -(index + 1); + if (path.getNameCount() > 1) { + Path parentPath = path.getName(0); + + for (int i = 1; i < path.getNameCount() - 1; i++) { + parentPath = parentPath.resolve(path.getName(i)); + String name = PathUtil.toUnixPathString(parentPath); + if (insertionPointx <= 0 || !assets.get(insertionPointx - 1).path.startsWith(name)) { + assets.add(insertionPointx++, new AssetEditorFileEntry(name, true)); + } + } + } + + AssetEditorFileEntry entry = new AssetEditorFileEntry(pathString, isDirectory); + assets.add(insertionPointx, entry); + return entry; + } + + insertionPoint = null; + } finally { + this.lock.unlockWrite(stamp); + } + + return (AssetEditorFileEntry)insertionPoint; + } + + @Nullable + public AssetEditorFileEntry getAssetFile(@Nonnull Path path) { + String pathString = PathUtil.toUnixPathString(path); + long stamp = this.lock.readLock(); + + AssetEditorFileEntry var7; + try { + List assets = this.getAssetListForPath(path); + int index = ListUtil.binarySearch(assets, o -> o.path, pathString, String::compareTo); + var7 = index >= 0 ? assets.get(index) : null; + } finally { + this.lock.unlockRead(stamp); + } + + return var7; + } + + @Nullable + public AssetEditorFileEntry removeAsset(@Nonnull Path path) { + String pathString = PathUtil.toUnixPathString(path); + long stamp = this.lock.writeLock(); + + AssetEditorFileEntry entry; + try { + List assets = this.getAssetListForPath(path); + int index = ListUtil.binarySearch(assets, o -> o.path, pathString, String::compareTo); + if (index >= 0) { + entry = assets.remove(index); + if (entry.isDirectory) { + String pathPrefix = pathString + "/"; + int removeCount = 0; + + for (int i = index; i < assets.size(); i++) { + AssetEditorFileEntry asset = assets.get(i); + if (!asset.path.startsWith(pathPrefix)) { + break; + } + + removeCount++; + } + + for (int i = 0; i < removeCount; i++) { + assets.remove(index); + } + } + + return entry; + } + + entry = null; + } finally { + this.lock.unlockWrite(stamp); + } + + return entry; + } + + public void applyAssetChanges(@Nonnull Map createdDirectories, @Nonnull Map modifiedAssets) { + for (ModifiedAsset dir : createdDirectories.values()) { + this.ensureAsset(dir.path, true); + } + + for (ModifiedAsset file : modifiedAssets.values()) { + if (file.state == AssetState.NEW) { + this.ensureAsset(file.path, false); + } else if (file.state == AssetState.DELETED) { + this.removeAsset(file.oldPath != null ? file.oldPath : file.path); + } else if (file.oldPath != null) { + this.removeAsset(file.oldPath); + this.ensureAsset(file.path, false); + } + } + } + + private List getAssetListForPath(@Nonnull Path path) { + if (path.getNameCount() > 0) { + String firstName = path.getName(0).toString(); + if (firstName.equals("..")) { + try { + firstName = path.getName(2).toString(); + } catch (IllegalArgumentException var4) { + } + } + + if ("Server".equals(firstName)) { + return this.serverAssets; + } + + if ("Common".equals(firstName)) { + return this.commonAssets; + } + } + + throw new IllegalArgumentException("Invalid path " + path); + } + + private void load(@Nonnull Collection assetTypes) { + try { + long start = System.nanoTime(); + loadServerAssets(this.rootPath, assetTypes, this.serverAssets); + LOGGER.at(Level.INFO).log("Loaded Server/ asset tree! Took: %s", FormatUtil.nanosToString(System.nanoTime() - start)); + } catch (IOException var5) { + LOGGER.at(Level.WARNING).withCause(var5).log("Failed to load server asset tree!"); + } + + try { + long start = System.nanoTime(); + walkFileTree(this.rootPath, this.rootPath.resolve("Common"), this.commonAssets); + LOGGER.at(Level.INFO).log("Loaded Common/ asset tree! Took: %s", FormatUtil.nanosToString(System.nanoTime() - start)); + } catch (IOException var4) { + LOGGER.at(Level.WARNING).withCause(var4).log("Failed to load common asset tree!"); + } + + long start = System.nanoTime(); + this.serverAssets.sort(Comparator.comparing(o -> o.path)); + this.commonAssets.sort(Comparator.comparing(o -> o.path)); + LOGGER.at(Level.INFO).log("Sorted asset tree! Took: %s", FormatUtil.nanosToString(System.nanoTime() - start)); + } + + private static void loadServerAssets(@Nonnull Path root, @Nonnull Collection assetTypes, @Nonnull List files) throws IOException { + Set assetTypePaths = new HashSet<>(); + Set subPaths = new HashSet<>(); + + for (AssetTypeHandler assetTypeHandler : assetTypes) { + if (assetTypeHandler.getRootPath().startsWith(AssetPathUtil.PATH_DIR_SERVER)) { + String assetTypePath = assetTypeHandler.getConfig().path; + if (assetTypePaths.add(assetTypePath)) { + Path path = Path.of(assetTypePath); + Path subpath = AssetPathUtil.PATH_DIR_SERVER; + + for (int i = 1; i < path.getNameCount() - 1; i++) { + subpath = subpath.resolve(path.getName(i)); + String name = PathUtil.toUnixPathString(subpath); + if (subPaths.add(name)) { + files.add(new AssetEditorFileEntry(name, true)); + } + } + } + } + } + + for (String path : assetTypePaths) { + Path dirPath = root.resolve(path); + walkFileTree(root, dirPath, files); + String name = PathUtil.toUnixPathString(root.relativize(dirPath)); + files.add(new AssetEditorFileEntry(name, true)); + } + } + + private static void walkFileTree(@Nonnull final Path root, @Nonnull final Path dirPath, @Nonnull final List files) throws IOException { + if (Files.isDirectory(dirPath)) { + Files.walkFileTree(dirPath, FileUtil.DEFAULT_WALK_TREE_OPTIONS_SET, Integer.MAX_VALUE, new SimpleFileVisitor() { + public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attrs) throws IOException { + if (path.equals(dirPath)) { + return FileVisitResult.CONTINUE; + } else { + files.add(new AssetEditorFileEntry(PathUtil.toUnixPathString(root.relativize(path)), true)); + return super.preVisitDirectory(path, attrs); + } + } + + @Nonnull + public FileVisitResult visitFile(@Nonnull Path path, @Nonnull BasicFileAttributes attrs) { + if (CommonAssetModule.IGNORED_FILES.contains(path.getFileName())) { + return FileVisitResult.CONTINUE; + } else { + files.add(new AssetEditorFileEntry(PathUtil.toUnixPathString(root.relativize(path)), false)); + return FileVisitResult.CONTINUE; + } + } + }); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/AssetTypeRegistry.java b/src/com/hypixel/hytale/builtin/asseteditor/AssetTypeRegistry.java new file mode 100644 index 0000000..7b28f47 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/AssetTypeRegistry.java @@ -0,0 +1,102 @@ +package com.hypixel.hytale.builtin.asseteditor; + +import com.hypixel.hytale.builtin.asseteditor.assettypehandler.AssetTypeHandler; +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetType; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorPopupNotificationType; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorSetupAssetTypes; +import com.hypixel.hytale.server.core.Message; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetTypeRegistry { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final ConcurrentHashMap assetTypeHandlers = new ConcurrentHashMap<>(); + private AssetEditorSetupAssetTypes setupPacket; + + public AssetTypeRegistry() { + } + + @Nonnull + public Map getRegisteredAssetTypeHandlers() { + return this.assetTypeHandlers; + } + + public void registerAssetType(@Nonnull AssetTypeHandler assetType) { + if (this.assetTypeHandlers.putIfAbsent(assetType.getConfig().id, assetType) != null) { + throw new IllegalArgumentException("An asset type with id '" + assetType.getConfig().id + "' is already registered"); + } + } + + public void unregisterAssetType(@Nonnull AssetTypeHandler assetType) { + this.assetTypeHandlers.remove(assetType.getConfig().id); + } + + public AssetTypeHandler getAssetTypeHandler(String id) { + return this.assetTypeHandlers.get(id); + } + + @Nullable + public AssetTypeHandler getAssetTypeHandlerForPath(@Nonnull Path path) { + String extension = PathUtil.getFileExtension(path); + if (extension.isEmpty()) { + return null; + } else { + for (AssetTypeHandler handler : this.assetTypeHandlers.values()) { + if (handler.getConfig().fileExtension.equalsIgnoreCase(extension) && path.startsWith(handler.getConfig().path)) { + return handler; + } + } + + return null; + } + } + + public boolean isPathInAssetTypeFolder(@Nonnull Path path) { + for (AssetTypeHandler assetTypeHandler : this.assetTypeHandlers.values()) { + if (path.startsWith(assetTypeHandler.getRootPath()) && !path.equals(assetTypeHandler.getRootPath())) { + return true; + } + } + + return false; + } + + @Nullable + public AssetTypeHandler tryGetAssetTypeHandler(@Nonnull Path assetPath, @Nonnull EditorClient editorClient, int requestToken) { + AssetTypeHandler assetTypeHandler = this.getAssetTypeHandlerForPath(assetPath); + if (assetTypeHandler == null) { + LOGGER.at(Level.WARNING).log("Invalid asset type for %s", assetPath); + if (requestToken != -1) { + editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.invalidAssetType")); + } else { + editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Message.translation("server.assetEditor.messages.invalidAssetType")); + } + + return null; + } else { + return assetTypeHandler; + } + } + + public void sendPacket(@Nonnull EditorClient editorClient) { + editorClient.getPacketHandler().write(this.setupPacket); + } + + public void setupPacket() { + List types = new ObjectArrayList<>(); + + for (AssetTypeHandler assetTypeHandler : this.assetTypeHandlers.values()) { + types.add(assetTypeHandler.getConfig()); + } + + this.setupPacket = new AssetEditorSetupAssetTypes(types.toArray(AssetEditorAssetType[]::new)); + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/EditorClient.java b/src/com/hypixel/hytale/builtin/asseteditor/EditorClient.java new file mode 100644 index 0000000..5f2278e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/EditorClient.java @@ -0,0 +1,110 @@ +package com.hypixel.hytale.builtin.asseteditor; + +import com.hypixel.hytale.protocol.FormattedMessage; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorPopupNotification; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorPopupNotificationType; +import com.hypixel.hytale.protocol.packets.asseteditor.FailureReply; +import com.hypixel.hytale.protocol.packets.asseteditor.SuccessReply; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.auth.PlayerAuthentication; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.permissions.PermissionHolder; +import com.hypixel.hytale.server.core.permissions.PermissionsModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EditorClient implements PermissionHolder { + private String language; + private final UUID uuid; + private final String username; + @Nullable + private final PlayerAuthentication auth; + private final PacketHandler packetHandler; + + public EditorClient(String language, @Nonnull PlayerAuthentication auth, PacketHandler packetHandler) { + this.language = language; + this.uuid = auth.getUuid(); + this.username = auth.getUsername(); + this.auth = auth; + this.packetHandler = packetHandler; + } + + public EditorClient(String language, UUID uuid, String username, PacketHandler packetHandler) { + this.language = language; + this.uuid = uuid; + this.username = username; + this.auth = null; + this.packetHandler = packetHandler; + } + + @Deprecated + public EditorClient(@Nonnull PlayerRef playerRef) { + this.language = playerRef.getLanguage(); + this.uuid = playerRef.getUuid(); + this.username = playerRef.getUsername(); + this.auth = null; + this.packetHandler = playerRef.getPacketHandler(); + } + + public String getLanguage() { + return this.language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public UUID getUuid() { + return this.uuid; + } + + public String getUsername() { + return this.username; + } + + @Nullable + public PlayerAuthentication getAuth() { + return this.auth; + } + + public PacketHandler getPacketHandler() { + return this.packetHandler; + } + + @Nullable + public PlayerRef tryGetPlayer() { + return Universe.get().getPlayer(this.uuid); + } + + @Override + public boolean hasPermission(@Nonnull String id) { + return PermissionsModule.get().hasPermission(this.uuid, id); + } + + @Override + public boolean hasPermission(@Nonnull String id, boolean def) { + return PermissionsModule.get().hasPermission(this.uuid, id, def); + } + + public void sendPopupNotification(AssetEditorPopupNotificationType type, @Nonnull Message message) { + FormattedMessage msg = message.getFormattedMessage(); + this.getPacketHandler().write(new AssetEditorPopupNotification(type, msg)); + } + + public void sendSuccessReply(int token) { + this.sendSuccessReply(token, null); + } + + public void sendSuccessReply(int token, @Nullable Message message) { + FormattedMessage msg = message != null ? message.getFormattedMessage() : null; + this.getPacketHandler().write(new SuccessReply(token, msg)); + } + + public void sendFailureReply(int token, @Nonnull Message message) { + FormattedMessage msg = message.getFormattedMessage(); + this.getPacketHandler().write(new FailureReply(token, msg)); + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/Messages.java b/src/com/hypixel/hytale/builtin/asseteditor/Messages.java new file mode 100644 index 0000000..3ee9380 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/Messages.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.builtin.asseteditor; + +import com.hypixel.hytale.server.core.Message; + +public class Messages { + public static final Message USAGE_DENIED_MESSAGE = Message.translation("server.assetEditor.messages.usageDenied"); + public static final Message INVALID_FILENAME_MESSAGE = Message.translation("server.assetEditor.messages.invalidFileName"); + public static final Message OUTSIDE_ASSET_ROOT_MESSAGE = Message.translation("server.assetEditor.messages.directoryOutsideAssetTypeRoot"); + public static final Message UNKNOWN_ASSETPACK_MESSAGE = Message.translation("server.assetEditor.messages.unknownAssetPack"); + + public Messages() { + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/UndoRedoManager.java b/src/com/hypixel/hytale/builtin/asseteditor/UndoRedoManager.java new file mode 100644 index 0000000..4baf420 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/UndoRedoManager.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.asseteditor; + +import com.hypixel.hytale.builtin.asseteditor.data.AssetUndoRedoInfo; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; + +public class UndoRedoManager { + private final Map assetUndoRedoInfo = new Object2ObjectOpenHashMap<>(); + + public UndoRedoManager() { + } + + public AssetUndoRedoInfo getOrCreateUndoRedoStack(AssetPath path) { + return this.assetUndoRedoInfo.computeIfAbsent(path, k -> new AssetUndoRedoInfo()); + } + + public AssetUndoRedoInfo getUndoRedoStack(AssetPath path) { + return this.assetUndoRedoInfo.get(path); + } + + public void putUndoRedoStack(AssetPath path, AssetUndoRedoInfo undoRedoInfo) { + this.assetUndoRedoInfo.put(path, undoRedoInfo); + } + + public AssetUndoRedoInfo clearUndoRedoStack(AssetPath path) { + return this.assetUndoRedoInfo.remove(path); + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/assettypehandler/AssetStoreTypeHandler.java b/src/com/hypixel/hytale/builtin/asseteditor/assettypehandler/AssetStoreTypeHandler.java new file mode 100644 index 0000000..d0d848a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/assettypehandler/AssetStoreTypeHandler.java @@ -0,0 +1,129 @@ +package com.hypixel.hytale.builtin.asseteditor.assettypehandler; + +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.asseteditor.AssetEditorPlugin; +import com.hypixel.hytale.builtin.asseteditor.AssetPath; +import com.hypixel.hytale.builtin.asseteditor.EditorClient; +import com.hypixel.hytale.builtin.asseteditor.util.AssetPathUtil; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.metadata.ui.UIRebuildCaches; +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetType; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorEditorType; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorPopupNotificationType; +import com.hypixel.hytale.server.core.Message; +import java.nio.file.Path; +import java.util.Collections; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; + +public class AssetStoreTypeHandler extends JsonTypeHandler { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private final AssetStore assetStore; + + public AssetStoreTypeHandler(@Nonnull AssetStore assetStore) { + super( + new AssetEditorAssetType( + assetStore.getAssetClass().getSimpleName(), + null, + false, + PathUtil.toUnixPathString(AssetPathUtil.PATH_DIR_SERVER.resolve(assetStore.getPath())), + assetStore.getExtension(), + AssetEditorEditorType.JsonConfig + ) + ); + this.assetStore = assetStore; + } + + @Nonnull + public AssetStore getAssetStore() { + return this.assetStore; + } + + @Nonnull + @Override + public AssetTypeHandler.AssetLoadResult loadAssetFromDocument( + AssetPath path, Path dataPath, BsonDocument document, AssetUpdateQuery updateQuery, EditorClient editorClient + ) { + try { + Object key = this.assetStore.decodeFilePathKey(path.path()); + JsonAssetWithMap decodedAsset = this.assetStore.decode(path.packId(), key, document.clone()); + this.assetStore.loadAssets(path.packId(), Collections.singletonList(decodedAsset), updateQuery, true); + } catch (Exception var8) { + LOGGER.at(Level.WARNING).withCause(new SkipSentryException(var8)).log("Failed to load asset", path); + if (editorClient != null) { + editorClient.sendPopupNotification( + AssetEditorPopupNotificationType.Error, + Message.translation("server.assetEditor.messages.failedToDecodeAsset").param("message", var8.getMessage()) + ); + } + + return AssetTypeHandler.AssetLoadResult.ASSETS_UNCHANGED; + } + + return AssetTypeHandler.AssetLoadResult.ASSETS_CHANGED; + } + + @Nonnull + @Override + public AssetTypeHandler.AssetLoadResult unloadAsset(@Nonnull AssetPath path, @Nonnull AssetUpdateQuery updateQuery) { + this.assetStore.removeAssets(path.packId(), true, Collections.singleton(this.assetStore.decodeFilePathKey(path.path())), updateQuery); + return AssetTypeHandler.AssetLoadResult.ASSETS_CHANGED; + } + + @Nonnull + @Override + public AssetTypeHandler.AssetLoadResult restoreOriginalAsset(@Nonnull AssetPath originalAssetPath, @Nonnull AssetUpdateQuery updateQuery) { + try { + this.assetStore.loadAssetsFromPaths(originalAssetPath.packId(), Collections.singletonList(originalAssetPath.path()), updateQuery, true); + } catch (Exception var4) { + LOGGER.at(Level.WARNING).withCause(new SkipSentryException(var4)).log("Failed to restore asset", originalAssetPath); + return AssetTypeHandler.AssetLoadResult.ASSETS_UNCHANGED; + } + + return AssetTypeHandler.AssetLoadResult.ASSETS_CHANGED; + } + + @Nonnull + @Override + public AssetUpdateQuery getDefaultUpdateQuery() { + if (this.cachedDefaultUpdateQuery == null) { + Schema schema = AssetEditorPlugin.get().getSchema(this.config.id + ".json"); + if (schema == null) { + return AssetUpdateQuery.DEFAULT; + } + + AssetUpdateQuery.RebuildCacheBuilder rebuildCacheBuilder = AssetUpdateQuery.RebuildCache.builder(); + if (schema.getHytale().getUiRebuildCaches() != null) { + for (UIRebuildCaches.ClientCache cache : schema.getHytale().getUiRebuildCaches()) { + switch (cache) { + case MODELS: + rebuildCacheBuilder.setModels(true); + break; + case MODEL_TEXTURES: + rebuildCacheBuilder.setModelTextures(true); + break; + case ITEM_ICONS: + rebuildCacheBuilder.setItemIcons(true); + break; + case BLOCK_TEXTURES: + rebuildCacheBuilder.setBlockTextures(true); + break; + case MAP_GEOMETRY: + rebuildCacheBuilder.setMapGeometry(true); + } + } + } + + this.cachedDefaultUpdateQuery = new AssetUpdateQuery(rebuildCacheBuilder.build()); + } + + return this.cachedDefaultUpdateQuery; + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/assettypehandler/AssetTypeHandler.java b/src/com/hypixel/hytale/builtin/asseteditor/assettypehandler/AssetTypeHandler.java new file mode 100644 index 0000000..c8a9cde --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/assettypehandler/AssetTypeHandler.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.builtin.asseteditor.assettypehandler; + +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.builtin.asseteditor.AssetPath; +import com.hypixel.hytale.builtin.asseteditor.EditorClient; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetType; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public abstract class AssetTypeHandler { + @Nonnull + protected final AssetEditorAssetType config; + @Nonnull + protected final Path rootPath; + protected AssetUpdateQuery cachedDefaultUpdateQuery; + + protected AssetTypeHandler(@Nonnull AssetEditorAssetType config) { + this.config = config; + this.rootPath = Path.of(config.path); + } + + public abstract AssetTypeHandler.AssetLoadResult loadAsset(AssetPath var1, Path var2, byte[] var3, AssetUpdateQuery var4, EditorClient var5); + + public abstract AssetTypeHandler.AssetLoadResult unloadAsset(AssetPath var1, AssetUpdateQuery var2); + + public abstract AssetTypeHandler.AssetLoadResult restoreOriginalAsset(AssetPath var1, AssetUpdateQuery var2); + + public abstract AssetUpdateQuery getDefaultUpdateQuery(); + + public AssetTypeHandler.AssetLoadResult loadAsset(AssetPath path, Path dataPath, byte[] data, EditorClient editorClient) { + return this.loadAsset(path, dataPath, data, this.getDefaultUpdateQuery(), editorClient); + } + + public AssetTypeHandler.AssetLoadResult unloadAsset(AssetPath path) { + return this.unloadAsset(path, this.getDefaultUpdateQuery()); + } + + public AssetTypeHandler.AssetLoadResult restoreOriginalAsset(AssetPath originalAssetPath) { + return this.restoreOriginalAsset(originalAssetPath, this.getDefaultUpdateQuery()); + } + + public boolean isValidData(byte[] data) { + return true; + } + + @Nonnull + public AssetEditorAssetType getConfig() { + return this.config; + } + + @Nonnull + public Path getRootPath() { + return this.rootPath; + } + + public static enum AssetLoadResult { + ASSETS_UNCHANGED, + ASSETS_CHANGED, + COMMON_ASSETS_CHANGED; + + private AssetLoadResult() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/assettypehandler/CommonAssetTypeHandler.java b/src/com/hypixel/hytale/builtin/asseteditor/assettypehandler/CommonAssetTypeHandler.java new file mode 100644 index 0000000..1c5ec89 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/assettypehandler/CommonAssetTypeHandler.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.builtin.asseteditor.assettypehandler; + +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.builtin.asseteditor.AssetPath; +import com.hypixel.hytale.builtin.asseteditor.EditorClient; +import com.hypixel.hytale.builtin.asseteditor.util.AssetPathUtil; +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetType; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorEditorType; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.asset.common.CommonAssetModule; +import com.hypixel.hytale.server.core.asset.common.CommonAssetRegistry; +import com.hypixel.hytale.server.core.asset.common.asset.FileCommonAsset; +import com.hypixel.hytale.server.core.universe.Universe; +import it.unimi.dsi.fastutil.booleans.BooleanObjectPair; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class CommonAssetTypeHandler extends AssetTypeHandler { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + + public CommonAssetTypeHandler(String id, String icon, String fileExtension, AssetEditorEditorType editorType) { + super(new AssetEditorAssetType(id, icon, true, "Common", fileExtension, editorType)); + } + + @Nonnull + @Override + public AssetTypeHandler.AssetLoadResult loadAsset(AssetPath path, Path dataPath, byte[] data, AssetUpdateQuery updateQuery, EditorClient editorClient) { + String relativePath = PathUtil.toUnixPathString(AssetPathUtil.PATH_DIR_COMMON.relativize(path.path())); + FileCommonAsset newAsset = new FileCommonAsset(dataPath, relativePath, data); + CommonAssetRegistry.AddCommonAssetResult result = CommonAssetRegistry.addCommonAsset(path.packId(), newAsset); + CommonAssetRegistry.PackAsset asset = result.getNewPackAsset(); + CommonAssetRegistry.PackAsset oldAsset = result.getPreviousNameAsset(); + return oldAsset != null && oldAsset.asset().getHash().equals(asset.asset().getHash()) + ? AssetTypeHandler.AssetLoadResult.ASSETS_UNCHANGED + : AssetTypeHandler.AssetLoadResult.COMMON_ASSETS_CHANGED; + } + + @Nonnull + @Override + public AssetTypeHandler.AssetLoadResult unloadAsset(@Nonnull AssetPath path, @Nonnull AssetUpdateQuery updateQuery) { + BooleanObjectPair removedCommonAsset = CommonAssetRegistry.removeCommonAssetByName( + path.packId(), PathUtil.toUnixPathString(AssetPathUtil.PATH_DIR_COMMON.relativize(path.path())) + ); + if (removedCommonAsset != null) { + if (Universe.get().getPlayerCount() > 0) { + if (removedCommonAsset.firstBoolean()) { + CommonAssetModule.get().sendAsset(removedCommonAsset.second().asset(), updateQuery.getRebuildCache().isCommonAssetsRebuild()); + } else { + CommonAssetModule.get() + .sendRemoveAssets(Collections.singletonList(removedCommonAsset.second()), updateQuery.getRebuildCache().isCommonAssetsRebuild()); + } + } + + return AssetTypeHandler.AssetLoadResult.COMMON_ASSETS_CHANGED; + } else { + return AssetTypeHandler.AssetLoadResult.ASSETS_UNCHANGED; + } + } + + @Nonnull + @Override + public AssetTypeHandler.AssetLoadResult restoreOriginalAsset(@Nonnull AssetPath originalAssetPath, AssetUpdateQuery updateQuery) { + AssetPack pack = AssetModule.get().getAssetPack(originalAssetPath.packId()); + Path absolutePath = pack.getRoot().resolve(originalAssetPath.path()).toAbsolutePath(); + byte[] bytes = null; + + try { + bytes = Files.readAllBytes(absolutePath); + } catch (IOException var11) { + LOGGER.at(Level.WARNING).withCause(var11).log("Failed to load file %s", absolutePath); + } + + if (bytes == null) { + return AssetTypeHandler.AssetLoadResult.ASSETS_UNCHANGED; + } else { + String relativePath = PathUtil.toUnixPathString(AssetPathUtil.PATH_DIR_COMMON.relativize(originalAssetPath.path())); + FileCommonAsset commonAsset = new FileCommonAsset(absolutePath, relativePath, bytes); + CommonAssetRegistry.AddCommonAssetResult result = CommonAssetRegistry.addCommonAsset(originalAssetPath.packId(), commonAsset); + CommonAssetRegistry.PackAsset oldAsset = result.getPreviousNameAsset(); + CommonAssetRegistry.PackAsset newAsset = result.getNewPackAsset(); + return oldAsset != null && oldAsset.asset().getHash().equals(newAsset.asset().getHash()) + ? AssetTypeHandler.AssetLoadResult.ASSETS_UNCHANGED + : AssetTypeHandler.AssetLoadResult.COMMON_ASSETS_CHANGED; + } + } + + @Nonnull + @Override + public AssetUpdateQuery getDefaultUpdateQuery() { + if (this.cachedDefaultUpdateQuery == null) { + this.cachedDefaultUpdateQuery = new AssetUpdateQuery(new AssetUpdateQuery.RebuildCache(false, false, false, false, false, true)); + } + + return this.cachedDefaultUpdateQuery; + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/assettypehandler/JsonTypeHandler.java b/src/com/hypixel/hytale/builtin/asseteditor/assettypehandler/JsonTypeHandler.java new file mode 100644 index 0000000..c7edd93 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/assettypehandler/JsonTypeHandler.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.asseteditor.assettypehandler; + +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.builtin.asseteditor.AssetPath; +import com.hypixel.hytale.builtin.asseteditor.EditorClient; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetType; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; + +public abstract class JsonTypeHandler extends AssetTypeHandler { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + + protected JsonTypeHandler(@Nonnull AssetEditorAssetType config) { + super(config); + } + + @Override + public AssetTypeHandler.AssetLoadResult loadAsset(AssetPath path, Path dataPath, byte[] data, AssetUpdateQuery updateQuery, EditorClient editorClient) { + BsonDocument doc; + try { + doc = BsonDocument.parse(new String(data, StandardCharsets.UTF_8)); + } catch (Exception var8) { + LOGGER.at(Level.WARNING).withCause(var8).log("Failed to parse JSON for " + path); + return AssetTypeHandler.AssetLoadResult.ASSETS_UNCHANGED; + } + + return this.loadAssetFromDocument(path, dataPath, doc, updateQuery, editorClient); + } + + public abstract AssetTypeHandler.AssetLoadResult loadAssetFromDocument( + AssetPath var1, Path var2, BsonDocument var3, AssetUpdateQuery var4, EditorClient var5 + ); + + public AssetTypeHandler.AssetLoadResult loadAssetFromDocument(AssetPath path, Path dataPath, BsonDocument document, EditorClient editorClient) { + return this.loadAssetFromDocument(path, dataPath, document, this.getDefaultUpdateQuery(), editorClient); + } + + @Override + public boolean isValidData(@Nonnull byte[] data) { + try { + String str = new String(data, StandardCharsets.UTF_8); + char[] buffer = str.toCharArray(); + RawJsonReader.validateBsonDocument(new RawJsonReader(buffer)); + return true; + } catch (Exception var4) { + return false; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/data/AssetState.java b/src/com/hypixel/hytale/builtin/asseteditor/data/AssetState.java new file mode 100644 index 0000000..f3ec6cb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/data/AssetState.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.builtin.asseteditor.data; + +public enum AssetState { + CHANGED, + NEW, + DELETED; + + private AssetState() { + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/data/AssetUndoRedoInfo.java b/src/com/hypixel/hytale/builtin/asseteditor/data/AssetUndoRedoInfo.java new file mode 100644 index 0000000..56ee22c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/data/AssetUndoRedoInfo.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.builtin.asseteditor.data; + +import com.hypixel.hytale.protocol.packets.asseteditor.JsonUpdateCommand; +import java.util.ArrayDeque; +import java.util.Deque; + +public class AssetUndoRedoInfo { + public final Deque undoStack = new ArrayDeque<>(); + public final Deque redoStack = new ArrayDeque<>(); + + public AssetUndoRedoInfo() { + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/data/ModifiedAsset.java b/src/com/hypixel/hytale/builtin/asseteditor/data/ModifiedAsset.java new file mode 100644 index 0000000..a8459ac --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/data/ModifiedAsset.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.builtin.asseteditor.data; + +import com.hypixel.hytale.builtin.asseteditor.EditorClient; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetInfo; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetPath; +import java.nio.file.Path; +import java.time.Instant; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ModifiedAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(ModifiedAsset.class, ModifiedAsset::new) + .append(new KeyedCodec<>("File", Codec.PATH), (asset, s) -> asset.dataFile = s, asset -> asset.dataFile) + .add() + .append(new KeyedCodec<>("Path", Codec.PATH), (asset, s) -> asset.path = s, asset -> asset.path) + .add() + .append(new KeyedCodec<>("OldPath", Codec.PATH), (asset, s) -> asset.oldPath = s, asset -> asset.oldPath) + .add() + .append(new KeyedCodec<>("IsNew", Codec.BOOLEAN), (asset, s) -> asset.state = s ? AssetState.NEW : asset.state, asset -> null) + .add() + .append(new KeyedCodec<>("IsDeleted", Codec.BOOLEAN), (asset, s) -> asset.state = s ? AssetState.DELETED : asset.state, asset -> null) + .add() + .append(new KeyedCodec<>("State", new EnumCodec<>(AssetState.class)), (asset, s) -> asset.state = s, asset -> asset.state) + .add() + .append( + new KeyedCodec<>("LastModificationTimestamp", Codec.INSTANT, true), + (asset, s) -> asset.lastModificationTimestamp = s, + asset -> asset.lastModificationTimestamp + ) + .add() + .append( + new KeyedCodec<>("LastModificationPlayerUuid", Codec.UUID_STRING, true), + (asset, s) -> asset.lastModificationPlayerUuid = s, + asset -> asset.lastModificationPlayerUuid + ) + .add() + .append( + new KeyedCodec<>("LastModificationUsername", Codec.STRING, true), + (asset, s) -> asset.lastModificationUsername = s, + asset -> asset.lastModificationUsername + ) + .add() + .build(); + @Nullable + public Path dataFile; + public Path path; + @Nullable + public Path oldPath; + public AssetState state = AssetState.CHANGED; + public Instant lastModificationTimestamp; + public UUID lastModificationPlayerUuid; + public String lastModificationUsername; + + public ModifiedAsset() { + } + + public void markEditedBy(@Nonnull EditorClient editorClient) { + this.lastModificationTimestamp = Instant.now(); + this.lastModificationUsername = editorClient.getUsername(); + this.lastModificationPlayerUuid = editorClient.getUuid(); + } + + @Nonnull + public AssetInfo toAssetInfoPacket(String assetPack) { + return new AssetInfo( + new AssetPath(assetPack, PathUtil.toUnixPathString(this.path)), + this.oldPath != null ? new AssetPath(assetPack, PathUtil.toUnixPathString(this.oldPath)) : null, + this.state == AssetState.DELETED, + this.state == AssetState.NEW, + this.lastModificationTimestamp.toEpochMilli(), + this.lastModificationUsername + ); + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/datasource/DataSource.java b/src/com/hypixel/hytale/builtin/asseteditor/datasource/DataSource.java new file mode 100644 index 0000000..016fa68 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/datasource/DataSource.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.asseteditor.datasource; + +import com.hypixel.hytale.builtin.asseteditor.AssetTree; +import com.hypixel.hytale.builtin.asseteditor.EditorClient; +import com.hypixel.hytale.builtin.asseteditor.assettypehandler.AssetTypeHandler; +import com.hypixel.hytale.common.plugin.PluginManifest; +import java.nio.file.Path; +import java.time.Instant; +import java.util.Collection; + +public interface DataSource { + void start(); + + void shutdown(); + + AssetTree getAssetTree(); + + AssetTree loadAssetTree(Collection var1); + + boolean doesDirectoryExist(Path var1); + + boolean createDirectory(Path var1, EditorClient var2); + + boolean deleteDirectory(Path var1); + + boolean moveDirectory(Path var1, Path var2); + + boolean doesAssetExist(Path var1); + + byte[] getAssetBytes(Path var1); + + boolean updateAsset(Path var1, byte[] var2, EditorClient var3); + + boolean createAsset(Path var1, byte[] var2, EditorClient var3); + + boolean deleteAsset(Path var1, EditorClient var2); + + boolean moveAsset(Path var1, Path var2, EditorClient var3); + + boolean shouldReloadAssetFromDisk(Path var1); + + Instant getLastModificationTimestamp(Path var1); + + default void updateRuntimeAssets() { + } + + Path getFullPathToAssetData(Path var1); + + boolean isImmutable(); + + Path getRootPath(); + + PluginManifest getManifest(); +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/datasource/StandardDataSource.java b/src/com/hypixel/hytale/builtin/asseteditor/datasource/StandardDataSource.java new file mode 100644 index 0000000..b0306db --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/datasource/StandardDataSource.java @@ -0,0 +1,365 @@ +package com.hypixel.hytale.builtin.asseteditor.datasource; + +import com.hypixel.hytale.builtin.asseteditor.AssetTree; +import com.hypixel.hytale.builtin.asseteditor.EditorClient; +import com.hypixel.hytale.builtin.asseteditor.assettypehandler.AssetTypeHandler; +import com.hypixel.hytale.builtin.asseteditor.data.AssetState; +import com.hypixel.hytale.builtin.asseteditor.data.ModifiedAsset; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.plugin.PluginManager; +import com.hypixel.hytale.server.core.util.BsonUtil; +import com.hypixel.hytale.server.core.util.HashUtil; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.time.Instant; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import org.bson.BsonArray; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class StandardDataSource implements DataSource { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final Path rootPath; + private final ConcurrentHashMap> editorSaves; + private final AssetTree assetTree; + private final String packKey; + private final PluginManifest manifest; + private final boolean isImmutable; + private final Path recentModificationsFilePath; + private final AtomicBoolean indexNeedsSaving = new AtomicBoolean(); + private final Map modifiedAssets = new ConcurrentHashMap<>(); + private ScheduledFuture saveSchedule; + private boolean isAssetPackBeDeleteable; + + public StandardDataSource(String packKey, Path rootPath, boolean isImmutable, PluginManifest manifest) { + this.rootPath = rootPath; + this.editorSaves = new ConcurrentHashMap<>(); + this.packKey = packKey; + this.isImmutable = isImmutable; + this.manifest = manifest; + this.isAssetPackBeDeleteable = !isImmutable && isInModsDirectory(rootPath); + this.assetTree = new AssetTree(rootPath, packKey, isImmutable, this.isAssetPackBeDeleteable); + this.recentModificationsFilePath = Path.of("assetEditor", "recentAssetEdits_" + packKey.replace(':', '-') + ".json"); + } + + private static boolean isInModsDirectory(Path path) { + if (path.startsWith(PluginManager.MODS_PATH)) { + return true; + } else { + for (Path modsPath : Options.getOptionSet().valuesOf(Options.MODS_DIRECTORIES)) { + if (path.startsWith(modsPath)) { + return true; + } + } + + return false; + } + } + + @Override + public void start() { + this.loadRecentModifications(); + this.saveSchedule = HytaleServer.SCHEDULED_EXECUTOR.scheduleWithFixedDelay(() -> { + try { + this.saveRecentModifications(); + } catch (Exception var2) { + LOGGER.at(Level.SEVERE).withCause(var2).log("Failed to save assets index"); + } + }, 1L, 1L, TimeUnit.MINUTES); + } + + @Override + public void shutdown() { + this.saveSchedule.cancel(false); + this.saveRecentModifications(); + } + + private void loadRecentModifications() { + Path path = this.recentModificationsFilePath; + if (!Files.exists(path)) { + path = path.resolveSibling(path.getFileName() + ".bak"); + if (!Files.exists(path)) { + return; + } + } + + BsonDocument doc = BsonUtil.readDocument(path).join(); + + for (BsonValue asset : doc.getArray("Assets")) { + ModifiedAsset modifiedAsset = ModifiedAsset.CODEC.decode(asset, new ExtraInfo()); + if (modifiedAsset != null) { + this.modifiedAssets.put(modifiedAsset.path, modifiedAsset); + } + } + } + + public void saveRecentModifications() { + if (this.indexNeedsSaving.getAndSet(false)) { + LOGGER.at(Level.INFO).log("Saving recent asset modification index..."); + BsonDocument doc = new BsonDocument(); + BsonArray assetsArray = new BsonArray(); + + for (Entry modifiedAsset : this.modifiedAssets.entrySet()) { + assetsArray.add((BsonValue)ModifiedAsset.CODEC.encode(modifiedAsset.getValue(), new ExtraInfo())); + } + + doc.append("Assets", assetsArray); + + try { + BsonUtil.writeDocument(this.recentModificationsFilePath, doc); + } catch (Exception var5) { + LOGGER.at(Level.SEVERE).withCause(var5).log("Failed to save recent asset modification index..."); + this.indexNeedsSaving.set(true); + } + } + } + + public boolean canAssetPackBeDeleted() { + return this.isAssetPackBeDeleteable; + } + + public Path resolveAbsolutePath(Path path) { + return this.rootPath.resolve(path.toString()).toAbsolutePath(); + } + + @Override + public Path getFullPathToAssetData(Path assetPath) { + return this.resolveAbsolutePath(assetPath); + } + + @Override + public AssetTree getAssetTree() { + return this.assetTree; + } + + @Override + public boolean isImmutable() { + return this.isImmutable; + } + + @Override + public Path getRootPath() { + return this.rootPath; + } + + @Override + public PluginManifest getManifest() { + return this.manifest; + } + + @Override + public boolean doesDirectoryExist(Path folderPath) { + return Files.isDirectory(this.resolveAbsolutePath(folderPath)); + } + + @Override + public boolean createDirectory(Path dirPath, EditorClient editorClient) { + try { + Files.createDirectory(this.resolveAbsolutePath(dirPath)); + return true; + } catch (IOException var4) { + LOGGER.at(Level.WARNING).withCause(var4).log("Failed to create directory %s", dirPath); + return false; + } + } + + @Override + public boolean deleteDirectory(Path dirPath) { + try { + Files.deleteIfExists(this.resolveAbsolutePath(dirPath)); + return true; + } catch (IOException var3) { + LOGGER.at(Level.WARNING).withCause(var3).log("Failed to delete directory %s", dirPath); + return false; + } + } + + @Override + public boolean moveDirectory(Path oldDirPath, Path newDirPath) { + try { + Files.move(this.resolveAbsolutePath(oldDirPath), this.resolveAbsolutePath(newDirPath)); + return true; + } catch (IOException var4) { + LOGGER.at(Level.WARNING).withCause(var4).log("Failed to move directory %s to %s", oldDirPath, newDirPath); + return false; + } + } + + @Override + public boolean doesAssetExist(Path assetPath) { + return Files.isRegularFile(this.resolveAbsolutePath(assetPath)); + } + + @Override + public byte[] getAssetBytes(Path assetPath) { + try { + return Files.readAllBytes(this.resolveAbsolutePath(assetPath)); + } catch (IOException var3) { + LOGGER.at(Level.WARNING).withCause(var3).log("Failed to read asset %s", assetPath); + return null; + } + } + + @Override + public boolean updateAsset(Path assetPath, byte[] bytes, EditorClient editorClient) { + Path path = this.resolveAbsolutePath(assetPath); + + try { + String hash = HashUtil.sha256(bytes); + this.trackEditorFileSave(assetPath, hash); + Files.write(path, bytes, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); + ModifiedAsset modifiedAsset = new ModifiedAsset(); + modifiedAsset.path = assetPath; + modifiedAsset.state = AssetState.CHANGED; + modifiedAsset.markEditedBy(editorClient); + this.putModifiedAsset(modifiedAsset); + return true; + } catch (IOException var7) { + LOGGER.at(Level.WARNING).withCause(var7).log("Failed to update asset %s", assetPath); + return false; + } + } + + @Override + public boolean createAsset(Path assetPath, byte[] bytes, EditorClient editorClient) { + Path path = this.resolveAbsolutePath(assetPath); + + try { + String hash = HashUtil.sha256(bytes); + this.trackEditorFileSave(assetPath, hash); + Files.createDirectories(path.getParent()); + Files.write(path, bytes, StandardOpenOption.CREATE); + ModifiedAsset modifiedAsset = new ModifiedAsset(); + modifiedAsset.path = assetPath; + modifiedAsset.state = AssetState.NEW; + modifiedAsset.markEditedBy(editorClient); + this.putModifiedAsset(modifiedAsset); + return true; + } catch (IOException var7) { + LOGGER.at(Level.WARNING).withCause(var7).log("Failed to create asset %s", assetPath); + return false; + } + } + + @Override + public boolean deleteAsset(Path assetPath, EditorClient editorClient) { + try { + Files.deleteIfExists(this.resolveAbsolutePath(assetPath)); + ModifiedAsset modifiedAsset = new ModifiedAsset(); + modifiedAsset.path = assetPath; + modifiedAsset.state = AssetState.DELETED; + modifiedAsset.markEditedBy(editorClient); + this.putModifiedAsset(modifiedAsset); + return true; + } catch (IOException var4) { + LOGGER.at(Level.WARNING).withCause(var4).log("Failed to delete asset %s", assetPath); + return false; + } + } + + @Override + public boolean shouldReloadAssetFromDisk(Path assetPath) { + Deque fileSaveInfos = this.editorSaves.get(assetPath); + if (fileSaveInfos != null && !fileSaveInfos.isEmpty()) { + byte[] bytes = this.getAssetBytes(assetPath); + if (bytes == null) { + return true; + } else { + String hash = HashUtil.sha256(bytes); + long now = System.currentTimeMillis(); + synchronized (fileSaveInfos) { + fileSaveInfos.removeIf(mx -> mx.expiryMs <= now); + + for (StandardDataSource.EditorFileSaveInfo m : fileSaveInfos) { + if (m.hash.equals(hash)) { + return false; + } + } + + return true; + } + } + } else { + return true; + } + } + + @Override + public Instant getLastModificationTimestamp(Path assetPath) { + return null; + } + + @Override + public boolean moveAsset(Path oldAssetPath, Path newAssetPath, EditorClient editorClient) { + try { + Files.move(this.resolveAbsolutePath(oldAssetPath), this.resolveAbsolutePath(newAssetPath)); + ModifiedAsset modifiedAsset = new ModifiedAsset(); + modifiedAsset.path = newAssetPath; + modifiedAsset.oldPath = oldAssetPath; + modifiedAsset.state = AssetState.CHANGED; + modifiedAsset.markEditedBy(editorClient); + this.putModifiedAsset(modifiedAsset); + return true; + } catch (IOException var5) { + LOGGER.at(Level.WARNING).withCause(var5).log("Failed to move asset %s to %s", oldAssetPath, newAssetPath); + return false; + } + } + + @Override + public AssetTree loadAssetTree(Collection assetTypes) { + return new AssetTree(this.rootPath, this.packKey, this.isImmutable, this.isAssetPackBeDeleteable, assetTypes); + } + + public void putModifiedAsset(ModifiedAsset modifiedAsset) { + this.modifiedAssets.put(modifiedAsset.path, modifiedAsset); + if (this.modifiedAssets.size() > 50) { + ModifiedAsset oldestAsset = null; + + for (ModifiedAsset asset : this.modifiedAssets.values()) { + if (oldestAsset == null) { + oldestAsset = asset; + } else if (asset.lastModificationTimestamp.isBefore(oldestAsset.lastModificationTimestamp)) { + oldestAsset = asset; + } + } + + this.modifiedAssets.remove(oldestAsset.path); + } + + this.indexNeedsSaving.set(true); + } + + public Map getRecentlyModifiedAssets() { + return this.modifiedAssets; + } + + private void trackEditorFileSave(Path path, String hash) { + Deque fileSaves = this.editorSaves.computeIfAbsent(path, p -> new ArrayDeque<>()); + synchronized (fileSaves) { + fileSaves.addLast(new StandardDataSource.EditorFileSaveInfo(hash, System.currentTimeMillis() + 30000L)); + + while (fileSaves.size() > 20) { + fileSaves.removeFirst(); + } + } + } + + record EditorFileSaveInfo(String hash, long expiryMs) { + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorActivateButtonEvent.java b/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorActivateButtonEvent.java new file mode 100644 index 0000000..cbeaf44 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorActivateButtonEvent.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.builtin.asseteditor.event; + +import com.hypixel.hytale.builtin.asseteditor.EditorClient; + +public class AssetEditorActivateButtonEvent extends EditorClientEvent { + private final String buttonId; + + public AssetEditorActivateButtonEvent(EditorClient editorClient, String buttonId) { + super(editorClient); + this.buttonId = buttonId; + } + + public String getButtonId() { + return this.buttonId; + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorAssetCreatedEvent.java b/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorAssetCreatedEvent.java new file mode 100644 index 0000000..362f59c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorAssetCreatedEvent.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.asseteditor.event; + +import com.hypixel.hytale.builtin.asseteditor.EditorClient; +import java.nio.file.Path; + +public class AssetEditorAssetCreatedEvent extends EditorClientEvent { + private final String assetType; + private final Path assetPath; + private final byte[] data; + private final String buttonId; + + public AssetEditorAssetCreatedEvent(EditorClient editorClient, String assetType, Path assetPath, byte[] data, String buttonId) { + super(editorClient); + this.assetType = assetType; + this.assetPath = assetPath; + this.data = data; + this.buttonId = buttonId; + } + + public String getAssetType() { + return this.assetType; + } + + public Path getAssetPath() { + return this.assetPath; + } + + public byte[] getData() { + return this.data; + } + + public String getButtonId() { + return this.buttonId; + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorClientDisconnectEvent.java b/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorClientDisconnectEvent.java new file mode 100644 index 0000000..0fe969d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorClientDisconnectEvent.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.builtin.asseteditor.event; + +import com.hypixel.hytale.builtin.asseteditor.EditorClient; +import com.hypixel.hytale.server.core.io.PacketHandler; +import javax.annotation.Nonnull; + +public class AssetEditorClientDisconnectEvent extends EditorClientEvent { + private final PacketHandler.DisconnectReason disconnectReason; + + public AssetEditorClientDisconnectEvent(EditorClient editorClient, PacketHandler.DisconnectReason disconnectReason) { + super(editorClient); + this.disconnectReason = disconnectReason; + } + + public PacketHandler.DisconnectReason getDisconnectReason() { + return this.disconnectReason; + } + + @Nonnull + @Override + public String toString() { + return "AssetEditorClientDisconnectedEvent{disconnectReason=" + this.disconnectReason + "}" + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorFetchAutoCompleteDataEvent.java b/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorFetchAutoCompleteDataEvent.java new file mode 100644 index 0000000..3a1d4fc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorFetchAutoCompleteDataEvent.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.builtin.asseteditor.event; + +import com.hypixel.hytale.builtin.asseteditor.EditorClient; +import com.hypixel.hytale.event.IAsyncEvent; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class AssetEditorFetchAutoCompleteDataEvent implements IAsyncEvent { + private final EditorClient editorClient; + private final String dataSet; + private final String query; + private String[] results; + + public AssetEditorFetchAutoCompleteDataEvent(EditorClient editorClient, String dataSet, String query) { + this.editorClient = editorClient; + this.dataSet = dataSet; + this.query = query; + } + + public String getQuery() { + return this.query; + } + + public String getDataSet() { + return this.dataSet; + } + + public EditorClient getEditorClient() { + return this.editorClient; + } + + public String[] getResults() { + return this.results; + } + + public void setResults(String[] results) { + this.results = results; + } + + @Nonnull + @Override + public String toString() { + return "AssetEditorFetchAutoCompleteDataEvent{editorClient=" + + this.editorClient + + ", dataSet='" + + this.dataSet + + "', query='" + + this.query + + "', results=" + + Arrays.toString((Object[])this.results) + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorRequestDataSetEvent.java b/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorRequestDataSetEvent.java new file mode 100644 index 0000000..27eac2a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorRequestDataSetEvent.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.asseteditor.event; + +import com.hypixel.hytale.builtin.asseteditor.EditorClient; +import com.hypixel.hytale.event.IAsyncEvent; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class AssetEditorRequestDataSetEvent implements IAsyncEvent { + private final EditorClient editorClient; + private final String dataSet; + private String[] results; + + public AssetEditorRequestDataSetEvent(EditorClient editorClient, String dataSet, String[] results) { + this.editorClient = editorClient; + this.dataSet = dataSet; + this.results = results; + } + + public String getDataSet() { + return this.dataSet; + } + + public EditorClient getEditorClient() { + return this.editorClient; + } + + public String[] getResults() { + return this.results; + } + + public void setResults(String[] results) { + this.results = results; + } + + @Nonnull + @Override + public String toString() { + return "AssetEditorRequestDataSetEvent{editorClient=" + + this.editorClient + + ", dataSet='" + + this.dataSet + + "', results=" + + Arrays.toString((Object[])this.results) + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorSelectAssetEvent.java b/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorSelectAssetEvent.java new file mode 100644 index 0000000..478026d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorSelectAssetEvent.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.asseteditor.event; + +import com.hypixel.hytale.builtin.asseteditor.AssetPath; +import com.hypixel.hytale.builtin.asseteditor.EditorClient; + +public class AssetEditorSelectAssetEvent extends EditorClientEvent { + private final String assetType; + private final AssetPath assetFilePath; + private final String previousAssetType; + private final AssetPath previousAssetFilePath; + + public AssetEditorSelectAssetEvent( + EditorClient editorClient, String assetType, AssetPath assetFilePath, String previousAssetType, AssetPath previousAssetFilePath + ) { + super(editorClient); + this.assetType = assetType; + this.assetFilePath = assetFilePath; + this.previousAssetType = previousAssetType; + this.previousAssetFilePath = previousAssetFilePath; + } + + public String getAssetType() { + return this.assetType; + } + + public AssetPath getAssetFilePath() { + return this.assetFilePath; + } + + public String getPreviousAssetType() { + return this.previousAssetType; + } + + public AssetPath getPreviousAssetFilePath() { + return this.previousAssetFilePath; + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorUpdateWeatherPreviewLockEvent.java b/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorUpdateWeatherPreviewLockEvent.java new file mode 100644 index 0000000..1317bb7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/event/AssetEditorUpdateWeatherPreviewLockEvent.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.builtin.asseteditor.event; + +import com.hypixel.hytale.builtin.asseteditor.EditorClient; + +public class AssetEditorUpdateWeatherPreviewLockEvent extends EditorClientEvent { + private final boolean locked; + + public AssetEditorUpdateWeatherPreviewLockEvent(EditorClient editorClient, boolean locked) { + super(editorClient); + this.locked = locked; + } + + public boolean isLocked() { + return this.locked; + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/event/EditorClientEvent.java b/src/com/hypixel/hytale/builtin/asseteditor/event/EditorClientEvent.java new file mode 100644 index 0000000..af1625e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/event/EditorClientEvent.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.builtin.asseteditor.event; + +import com.hypixel.hytale.builtin.asseteditor.EditorClient; +import com.hypixel.hytale.event.IEvent; +import javax.annotation.Nonnull; + +public abstract class EditorClientEvent implements IEvent { + private final EditorClient editorClient; + + public EditorClientEvent(EditorClient editorClient) { + this.editorClient = editorClient; + } + + public EditorClient getEditorClient() { + return this.editorClient; + } + + @Nonnull + @Override + public String toString() { + return "EditorClientEvent{editorClient=" + this.editorClient + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/util/AssetPathUtil.java b/src/com/hypixel/hytale/builtin/asseteditor/util/AssetPathUtil.java new file mode 100644 index 0000000..fb15f94 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/util/AssetPathUtil.java @@ -0,0 +1,101 @@ +package com.hypixel.hytale.builtin.asseteditor.util; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; + +public class AssetPathUtil { + public static final String UNIX_FILE_SEPARATOR = "/"; + public static final String FILE_EXTENSION_JSON = ".json"; + public static final String DIR_SERVER = "Server"; + public static final String DIR_COMMON = "Common"; + public static final Path PATH_DIR_COMMON = Paths.get("Common"); + public static final Path PATH_DIR_SERVER = Paths.get("Server"); + public static final Path EMPTY_PATH = Path.of(""); + private static final Pattern INVALID_FILENAME_CHAR_REGEX = Pattern.compile("[<>:\"|?*/\\\\]"); + private static final String[] RESERVED_NAMES = new String[]{ + "CON", + "PRN", + "AUX", + "NUL", + "COM1", + "COM2", + "COM3", + "COM4", + "COM5", + "COM6", + "COM7", + "COM8", + "COM9", + "LPT1", + "LPT2", + "LPT3", + "LPT4", + "LPT5", + "LPT6", + "LPT7", + "LPT8", + "LPT9" + }; + + public AssetPathUtil() { + } + + public static boolean isInvalidFileName(@Nonnull Path path) { + String fileName = path.getFileName().toString(); + if (fileName.isEmpty()) { + return true; + } else if (fileName.charAt(fileName.length() - 1) == '.') { + return true; + } else { + int i = 0; + + while (i < fileName.length()) { + int codePoint = fileName.codePointAt(i); + if (codePoint < 31) { + return true; + } + + switch (codePoint) { + case 34: + case 42: + case 58: + case 60: + case 62: + case 63: + case 124: + return true; + default: + i += Character.charCount(codePoint); + } + } + + int pos = fileName.indexOf(46); + if (pos == 0) { + return false; + } else { + String baseFileName = pos < 0 ? fileName : fileName.substring(0, pos); + + for (String str : RESERVED_NAMES) { + if (str.equals(baseFileName)) { + return true; + } + } + + return false; + } + } + } + + public static String removeInvalidFileNameChars(String name) { + return INVALID_FILENAME_CHAR_REGEX.matcher(name).replaceAll(""); + } + + @Nonnull + private static String getIdFromPath(@Nonnull Path path) { + String fileName = path.getFileName().toString(); + int extensionIndex = fileName.lastIndexOf(46); + return extensionIndex == -1 ? fileName : fileName.substring(0, extensionIndex); + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/util/AssetStoreUtil.java b/src/com/hypixel/hytale/builtin/asseteditor/util/AssetStoreUtil.java new file mode 100644 index 0000000..3f2f90a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/util/AssetStoreUtil.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.asseteditor.util; + +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.assetstore.map.LookupTableAssetMap; +import javax.annotation.Nonnull; + +public class AssetStoreUtil { + public AssetStoreUtil() { + } + + @Deprecated + public static , M extends AssetMap> String getIdFromIndex(@Nonnull AssetStore assetStore, int assetIndex) { + M assetMap = assetStore.getAssetMap(); + if (assetMap instanceof BlockTypeAssetMap) { + return ((BlockTypeAssetMap)assetMap).getAsset(assetIndex).getId().toString(); + } else if (assetMap instanceof IndexedLookupTableAssetMap) { + return ((IndexedLookupTableAssetMap)assetMap).getAsset(assetIndex).getId().toString(); + } else if (assetMap instanceof LookupTableAssetMap) { + return ((LookupTableAssetMap)assetMap).getAsset(assetIndex).getId().toString(); + } else { + throw new IllegalArgumentException("Asset can't be looked up by index! " + assetIndex); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/asseteditor/util/BsonTransformationUtil.java b/src/com/hypixel/hytale/builtin/asseteditor/util/BsonTransformationUtil.java new file mode 100644 index 0000000..da7f51c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/asseteditor/util/BsonTransformationUtil.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.builtin.asseteditor.util; + +import com.hypixel.hytale.common.util.StringUtil; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonDocument; +import org.bson.BsonNull; +import org.bson.BsonValue; + +public class BsonTransformationUtil { + public BsonTransformationUtil() { + } + + private static void actionOnProperty(BsonDocument entity, @Nonnull String[] propertyPath, @Nonnull BiConsumer biConsumer, boolean create) { + BsonValue current = entity; + + for (int i = 0; i < propertyPath.length - 1; i++) { + BsonValue jsonElement; + if (current instanceof BsonDocument) { + jsonElement = ((BsonDocument)current).get(propertyPath[i]); + if (jsonElement == null || jsonElement instanceof BsonNull) { + if (!create) { + return; + } + + if (StringUtil.isNumericString(propertyPath[i + 1])) { + jsonElement = new BsonArray(); + } else { + jsonElement = new BsonDocument(); + } + + ((BsonDocument)current).put(propertyPath[i], jsonElement); + } + } else { + if (!(current instanceof BsonArray) || !StringUtil.isNumericString(propertyPath[i])) { + throw new IllegalArgumentException( + "Element is not Object or (Array or invalid index)! " + String.join(".", propertyPath) + ", " + propertyPath[i] + ", " + current + ); + } + + int index = Integer.parseInt(propertyPath[i]); + jsonElement = ((BsonArray)current).get(index); + if (jsonElement == null || jsonElement instanceof BsonNull) { + if (!create) { + return; + } + + if (StringUtil.isNumericString(propertyPath[i + 1])) { + jsonElement = new BsonArray(); + } else { + jsonElement = new BsonDocument(); + } + + ((BsonArray)current).set(index, jsonElement); + } + } + + current = jsonElement; + } + + biConsumer.accept(current, propertyPath[propertyPath.length - 1]); + } + + public static void removeProperty(BsonDocument entity, @Nonnull String[] propertyPath) { + actionOnProperty(entity, propertyPath, (parent, key) -> { + if (parent instanceof BsonDocument) { + ((BsonDocument)parent).remove(key); + } else { + if (!(parent instanceof BsonArray) || !StringUtil.isNumericString(key)) { + throw new IllegalArgumentException("Element is not Object or (Array or invalid index)! " + key + ", " + key + ", " + parent); + } + + ((BsonArray)parent).remove(Integer.parseInt(key)); + } + }, false); + } + + public static void setProperty(BsonDocument entity, @Nonnull String[] pathElements, BsonValue value) { + actionOnProperty(entity, pathElements, (parent, key) -> { + if (parent instanceof BsonDocument) { + ((BsonDocument)parent).put(key, value); + } else { + if (!(parent instanceof BsonArray) || !StringUtil.isNumericString(key)) { + throw new IllegalArgumentException("Element is not Object or (Array or invalid index)! " + key + ", " + key + ", " + parent); + } + + ((BsonArray)parent).set(Integer.parseInt(key), value); + } + }, true); + } + + public static void insertProperty(BsonDocument entity, @Nonnull String[] pathElements, BsonValue value) { + actionOnProperty(entity, pathElements, (parent, key) -> { + if (parent instanceof BsonDocument) { + ((BsonDocument)parent).put(key, value); + } else { + if (!(parent instanceof BsonArray) || !StringUtil.isNumericString(key)) { + throw new IllegalArgumentException("Element is not Object or (Array or invalid index)! " + key + ", " + key + ", " + parent); + } + + ((BsonArray)parent).add(Integer.parseInt(key), value); + } + }, true); + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/BedsPlugin.java b/src/com/hypixel/hytale/builtin/beds/BedsPlugin.java new file mode 100644 index 0000000..f4cbb5c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/BedsPlugin.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.builtin.beds; + +import com.hypixel.hytale.builtin.beds.interactions.BedInteraction; +import com.hypixel.hytale.builtin.beds.sleep.components.PlayerSomnolence; +import com.hypixel.hytale.builtin.beds.sleep.components.SleepTracker; +import com.hypixel.hytale.builtin.beds.sleep.resources.WorldSomnolence; +import com.hypixel.hytale.builtin.beds.sleep.systems.player.EnterBedSystem; +import com.hypixel.hytale.builtin.beds.sleep.systems.player.RegisterTrackerSystem; +import com.hypixel.hytale.builtin.beds.sleep.systems.player.UpdateSleepPacketSystem; +import com.hypixel.hytale.builtin.beds.sleep.systems.player.WakeUpOnDismountSystem; +import com.hypixel.hytale.builtin.beds.sleep.systems.world.StartSlumberSystem; +import com.hypixel.hytale.builtin.beds.sleep.systems.world.UpdateWorldSlumberSystem; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public class BedsPlugin extends JavaPlugin { + private static BedsPlugin instance; + private ComponentType playerSomnolenceComponentType; + private ComponentType sleepTrackerComponentType; + private ResourceType worldSomnolenceResourceType; + + public static BedsPlugin getInstance() { + return instance; + } + + public BedsPlugin(JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + instance = this; + this.playerSomnolenceComponentType = this.getEntityStoreRegistry().registerComponent(PlayerSomnolence.class, PlayerSomnolence::new); + this.sleepTrackerComponentType = this.getEntityStoreRegistry().registerComponent(SleepTracker.class, SleepTracker::new); + this.worldSomnolenceResourceType = this.getEntityStoreRegistry().registerResource(WorldSomnolence.class, WorldSomnolence::new); + this.getEntityStoreRegistry().registerSystem(new StartSlumberSystem()); + this.getEntityStoreRegistry().registerSystem(new UpdateSleepPacketSystem()); + this.getEntityStoreRegistry().registerSystem(new WakeUpOnDismountSystem()); + this.getEntityStoreRegistry().registerSystem(new RegisterTrackerSystem()); + this.getEntityStoreRegistry().registerSystem(new UpdateWorldSlumberSystem()); + this.getEntityStoreRegistry().registerSystem(new EnterBedSystem()); + Interaction.CODEC.register("Bed", BedInteraction.class, BedInteraction.CODEC); + } + + public ComponentType getPlayerSomnolenceComponentType() { + return this.playerSomnolenceComponentType; + } + + public ComponentType getSleepTrackerComponentType() { + return this.sleepTrackerComponentType; + } + + public ResourceType getWorldSomnolenceResourceType() { + return this.worldSomnolenceResourceType; + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/interactions/BedInteraction.java b/src/com/hypixel/hytale/builtin/beds/interactions/BedInteraction.java new file mode 100644 index 0000000..50f60a1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/interactions/BedInteraction.java @@ -0,0 +1,170 @@ +package com.hypixel.hytale.builtin.beds.interactions; + +import com.hypixel.hytale.builtin.beds.respawn.OverrideNearbyRespawnPointPage; +import com.hypixel.hytale.builtin.beds.respawn.SelectOverrideRespawnPointPage; +import com.hypixel.hytale.builtin.beds.respawn.SetNameRespawnPointPage; +import com.hypixel.hytale.builtin.beds.sleep.components.PlayerSleep; +import com.hypixel.hytale.builtin.beds.sleep.components.PlayerSomnolence; +import com.hypixel.hytale.builtin.mounts.BlockMountAPI; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.gameplay.RespawnConfig; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerRespawnPointData; +import com.hypixel.hytale.server.core.entity.entities.player.pages.PageManager; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.meta.state.RespawnBlock; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class BedInteraction extends SimpleBlockInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder(BedInteraction.class, BedInteraction::new, SimpleBlockInteraction.CODEC) + .documentation("Interact with a bed block, ostensibly to sleep in it.") + .build(); + + public BedInteraction() { + } + + @Override + protected void interactWithBlock( + @NonNullDecl World world, + @NonNullDecl CommandBuffer commandBuffer, + @NonNullDecl InteractionType type, + @NonNullDecl InteractionContext context, + @NullableDecl ItemStack itemInHand, + @NonNullDecl Vector3i pos, + @NonNullDecl CooldownHandler cooldownHandler + ) { + Ref ref = context.getEntity(); + Player player = commandBuffer.getComponent(ref, Player.getComponentType()); + if (player != null) { + Store store = commandBuffer.getStore(); + PlayerRef playerRefComponent = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + UUIDComponent playerUuidComponent = commandBuffer.getComponent(ref, UUIDComponent.getComponentType()); + + assert playerUuidComponent != null; + + UUID playerUuid = playerUuidComponent.getUuid(); + Ref chunkReference = world.getChunkStore().getChunkReference(ChunkUtil.indexChunkFromBlock(pos.x, pos.z)); + if (chunkReference != null) { + Store chunkStore = chunkReference.getStore(); + BlockComponentChunk blockComponentChunk = chunkStore.getComponent(chunkReference, BlockComponentChunk.getComponentType()); + + assert blockComponentChunk != null; + + int blockIndex = ChunkUtil.indexBlockInColumn(pos.x, pos.y, pos.z); + Ref blockRef = blockComponentChunk.getEntityReference(blockIndex); + if (blockRef == null) { + Holder holder = ChunkStore.REGISTRY.newHolder(); + holder.putComponent(BlockModule.BlockStateInfo.getComponentType(), new BlockModule.BlockStateInfo(blockIndex, chunkReference)); + holder.ensureComponent(RespawnBlock.getComponentType()); + blockRef = chunkStore.addEntity(holder, AddReason.SPAWN); + } + + RespawnBlock respawnBlock = chunkStore.getComponent(blockRef, RespawnBlock.getComponentType()); + if (respawnBlock != null) { + UUID ownerUUID = respawnBlock.getOwnerUUID(); + PageManager pageManager = player.getPageManager(); + boolean isOwner = playerUuid.equals(ownerUUID); + if (isOwner) { + BlockPosition rawTarget = context.getMetaStore().getMetaObject(TARGET_BLOCK_RAW); + Vector3f whereWasHit = new Vector3f(rawTarget.x + 0.5F, rawTarget.y + 0.5F, rawTarget.z + 0.5F); + BlockMountAPI.BlockMountResult result = BlockMountAPI.mountOnBlock(ref, commandBuffer, pos, whereWasHit); + if (result instanceof BlockMountAPI.DidNotMount) { + player.sendMessage(Message.translation("server.interactions.didNotMount").param("state", result.toString())); + } else if (result instanceof BlockMountAPI.Mounted) { + commandBuffer.putComponent(ref, PlayerSomnolence.getComponentType(), PlayerSleep.NoddingOff.createComponent()); + } + } else if (ownerUUID != null) { + player.sendMessage(Message.translation("server.customUI.respawnPointClaimed")); + } else { + PlayerRespawnPointData[] respawnPoints = player.getPlayerConfigData().getPerWorldData(world.getName()).getRespawnPoints(); + RespawnConfig respawnConfig = world.getGameplayConfig().getRespawnConfig(); + int radiusLimitRespawnPoint = respawnConfig.getRadiusLimitRespawnPoint(); + PlayerRespawnPointData[] nearbyRespawnPoints = this.getNearbySavedRespawnPoints(pos, respawnBlock, respawnPoints, radiusLimitRespawnPoint); + if (nearbyRespawnPoints != null) { + pageManager.openCustomPage( + ref, + store, + new OverrideNearbyRespawnPointPage(playerRefComponent, type, pos, respawnBlock, nearbyRespawnPoints, radiusLimitRespawnPoint) + ); + } else if (respawnPoints != null && respawnPoints.length >= respawnConfig.getMaxRespawnPointsPerPlayer()) { + pageManager.openCustomPage(ref, store, new SelectOverrideRespawnPointPage(playerRefComponent, type, pos, respawnBlock, respawnPoints)); + } else { + pageManager.openCustomPage(ref, store, new SetNameRespawnPointPage(playerRefComponent, type, pos, respawnBlock)); + } + } + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @NonNullDecl InteractionType type, + @NonNullDecl InteractionContext context, + @NullableDecl ItemStack itemInHand, + @NonNullDecl World world, + @NonNullDecl Vector3i targetBlock + ) { + } + + @Nullable + private PlayerRespawnPointData[] getNearbySavedRespawnPoints( + @Nonnull Vector3i currentRespawnPointPosition, + @Nonnull RespawnBlock respawnBlock, + @Nullable PlayerRespawnPointData[] respawnPoints, + int radiusLimitRespawnPoint + ) { + if (respawnPoints != null && respawnPoints.length != 0) { + ObjectArrayList nearbyRespawnPointList = new ObjectArrayList<>(); + + for (int i = 0; i < respawnPoints.length; i++) { + PlayerRespawnPointData respawnPoint = respawnPoints[i]; + Vector3i respawnPointPosition = respawnPoint.getBlockPosition(); + if (respawnPointPosition.distanceTo(currentRespawnPointPosition.x, respawnPointPosition.y, currentRespawnPointPosition.z) < radiusLimitRespawnPoint + ) + { + nearbyRespawnPointList.add(respawnPoint); + } + } + + return nearbyRespawnPointList.isEmpty() ? null : nearbyRespawnPointList.toArray(PlayerRespawnPointData[]::new); + } else { + return null; + } + } + + @NonNullDecl + @Override + public String toString() { + return "BedInteraction{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/respawn/OverrideNearbyRespawnPointPage.java b/src/com/hypixel/hytale/builtin/beds/respawn/OverrideNearbyRespawnPointPage.java new file mode 100644 index 0000000..a02520b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/respawn/OverrideNearbyRespawnPointPage.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.builtin.beds.respawn; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerRespawnPointData; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.meta.state.RespawnBlock; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class OverrideNearbyRespawnPointPage extends RespawnPointPage { + private final Vector3i respawnPointPosition; + private final RespawnBlock respawnPointToAdd; + private final PlayerRespawnPointData[] nearbyRespawnPoints; + private int radiusLimitRespawnPoint; + + public OverrideNearbyRespawnPointPage( + @Nonnull PlayerRef playerRef, + InteractionType interactionType, + Vector3i respawnPointPosition, + RespawnBlock respawnPointToAdd, + PlayerRespawnPointData[] nearbyRespawnPoints, + int radiusLimitRespawnPoint + ) { + super(playerRef, interactionType); + this.respawnPointPosition = respawnPointPosition; + this.respawnPointToAdd = respawnPointToAdd; + this.nearbyRespawnPoints = nearbyRespawnPoints; + this.radiusLimitRespawnPoint = radiusLimitRespawnPoint; + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/OverrideNearbyRespawnPointPage.ui"); + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + double direction = Math.toDegrees(headRotationComponent.getRotation().getYaw()); + commandBuilder.set( + "#DescriptionLabel.Text", + Message.translation("server.customUI.overrideNearbyRespawnPoint.label") + .param("respawnPointCount", this.nearbyRespawnPoints.length) + .param("minDistance", this.radiusLimitRespawnPoint) + ); + + for (int i = 0; i < this.nearbyRespawnPoints.length; i++) { + String selector = "#RespawnPointList[" + i + "]"; + PlayerRespawnPointData nearbyRespawnPoint = this.nearbyRespawnPoints[i]; + commandBuilder.append("#RespawnPointList", "Pages/OverrideRespawnPointButton.ui"); + commandBuilder.set(selector + ".Disabled", true); + commandBuilder.set(selector + " #Name.Text", nearbyRespawnPoint.getName()); + Vector3i nearbyRespawnPointPosition = nearbyRespawnPoint.getBlockPosition(); + int distance = (int)this.respawnPointPosition.distanceTo(nearbyRespawnPointPosition.x, this.respawnPointPosition.y, nearbyRespawnPointPosition.z); + commandBuilder.set(selector + " #Distance.Text", Message.translation("server.customUI.respawnPointDistance").param("distance", distance)); + double angle = Math.atan2(nearbyRespawnPointPosition.z - this.respawnPointPosition.z, nearbyRespawnPointPosition.x - this.respawnPointPosition.x); + commandBuilder.set(selector + " #Icon.Angle", Math.toDegrees(angle) + direction + 90.0); + } + + commandBuilder.set("#NameInput.Value", Message.translation("server.customUI.defaultRespawnPointName").param("name", playerRefComponent.getUsername())); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ConfirmButton", EventData.of("@RespawnPointName", "#NameInput.Value")); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#CancelButton", EventData.of("Action", "Cancel")); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull RespawnPointPage.RespawnPointEventData data) { + String respawnPointName = data.getRespawnPointName(); + if (respawnPointName != null) { + this.setRespawnPointForPlayer(ref, store, this.respawnPointPosition, this.respawnPointToAdd, respawnPointName, this.nearbyRespawnPoints); + } else if ("Cancel".equals(data.getAction())) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + playerComponent.getPageManager().setPage(ref, store, Page.None); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/respawn/RespawnPointPage.java b/src/com/hypixel/hytale/builtin/beds/respawn/RespawnPointPage.java new file mode 100644 index 0000000..1c57427 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/respawn/RespawnPointPage.java @@ -0,0 +1,195 @@ +package com.hypixel.hytale.builtin.beds.respawn; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerRespawnPointData; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerWorldData; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.meta.state.RespawnBlock; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class RespawnPointPage extends InteractiveCustomUIPage { + private final int RESPAWN_NAME_MAX_LENGTH = 32; + + public RespawnPointPage(@Nonnull PlayerRef playerRef, InteractionType interactionType) { + super( + playerRef, + interactionType == InteractionType.Use ? CustomPageLifetime.CanDismissOrCloseThroughInteraction : CustomPageLifetime.CanDismiss, + RespawnPointPage.RespawnPointEventData.CODEC + ); + } + + @Override + public abstract void build(@Nonnull Ref var1, @Nonnull UICommandBuilder var2, @Nonnull UIEventBuilder var3, @Nonnull Store var4); + + protected void setRespawnPointForPlayer( + @Nonnull Ref ref, + @Nonnull Store store, + @Nonnull Vector3i blockPosition, + @Nonnull RespawnBlock respawnBlock, + @Nonnull String respawnPointName, + @Nullable PlayerRespawnPointData... respawnPointsToRemove + ) { + respawnPointName = respawnPointName.trim(); + if (respawnPointName.isEmpty()) { + this.displayError(Message.translation("server.customUI.needToSetName")); + } else if (respawnPointName.length() > 32) { + this.displayError(Message.translation("server.customUI.respawnNameTooLong").param("maxLength", 32)); + } else { + respawnBlock.setOwnerUUID(this.playerRef.getUuid()); + World world = store.getExternalData().getWorld(); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(blockPosition.x, blockPosition.z)); + if (chunk != null) { + chunk.markNeedsSaving(); + BlockType blockType = chunk.getBlockType(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); + int rotationIndex = chunk.getRotationIndex(blockPosition.x, blockPosition.y, blockPosition.z); + Box hitbox = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex()).get(rotationIndex).getBoundingBox(); + double blockCenterWidthOffset = hitbox.min.x + hitbox.width() / 2.0; + double blockCenterDepthOffset = hitbox.min.z + hitbox.depth() / 2.0; + Vector3d respawnPosition = new Vector3d( + blockPosition.getX() + blockCenterWidthOffset, blockPosition.getY() + hitbox.height(), blockPosition.getZ() + blockCenterDepthOffset + ); + PlayerRespawnPointData respawnPointData = new PlayerRespawnPointData(blockPosition, respawnPosition, respawnPointName); + PlayerWorldData perWorldData = playerComponent.getPlayerConfigData().getPerWorldData(world.getName()); + PlayerRespawnPointData[] respawnPoints = this.handleRespawnPointsToRemove(perWorldData.getRespawnPoints(), respawnPointsToRemove, world); + if (respawnPoints != null) { + if (ArrayUtil.contains(respawnPoints, respawnPointData)) { + return; + } + + if (respawnPointsToRemove == null || respawnPointsToRemove.length == 0) { + for (int i = 0; i < respawnPoints.length; i++) { + PlayerRespawnPointData savedRespawnPointData = respawnPoints[i]; + if (savedRespawnPointData.getBlockPosition().equals(blockPosition)) { + savedRespawnPointData.setName(respawnPointName); + this.playerRef.sendMessage(Message.translation("server.customUI.updatedRespawnPointName").param("name", respawnPointName)); + playerComponent.getPageManager().setPage(ref, store, Page.None); + return; + } + } + } + } + + perWorldData.setRespawnPoints(ArrayUtil.append(respawnPoints, respawnPointData)); + this.playerRef.sendMessage(Message.translation("server.customUI.respawnPointSet").param("name", respawnPointName)); + playerComponent.getPageManager().setPage(ref, store, Page.None); + } + } + } + + @Nonnull + private PlayerRespawnPointData[] handleRespawnPointsToRemove( + @Nonnull PlayerRespawnPointData[] respawnPoints, @Nullable PlayerRespawnPointData[] respawnPointsToRemove, @Nonnull World world + ) { + if (respawnPointsToRemove == null) { + return respawnPoints; + } else { + ChunkStore chunkStore = world.getChunkStore(); + + for (int i = 0; i < respawnPointsToRemove.length; i++) { + PlayerRespawnPointData respawnPointToRemove = respawnPointsToRemove[i]; + + for (int j = 0; j < respawnPoints.length; j++) { + PlayerRespawnPointData respawnPoint = respawnPoints[j]; + if (respawnPoint.getBlockPosition().equals(respawnPointToRemove.getBlockPosition())) { + respawnPoints = ArrayUtil.remove(respawnPoints, j); + break; + } + } + + Vector3i position = respawnPointToRemove.getBlockPosition(); + Ref chunkReference = chunkStore.getChunkReference(ChunkUtil.indexChunkFromBlock(position.x, position.z)); + if (chunkReference != null) { + BlockComponentChunk blockComponentChunk = chunkStore.getStore().getComponent(chunkReference, BlockComponentChunk.getComponentType()); + Ref blockRef = blockComponentChunk.getEntityReference(ChunkUtil.indexBlockInColumn(position.x, position.y, position.z)); + if (blockRef != null) { + RespawnBlock respawnBlock = chunkStore.getStore().getComponent(blockRef, RespawnBlock.getComponentType()); + if (respawnBlock != null) { + respawnBlock.setOwnerUUID(null); + WorldChunk worldChunk = chunkStore.getStore().getComponent(chunkReference, WorldChunk.getComponentType()); + if (worldChunk != null) { + worldChunk.markNeedsSaving(); + } + } + } + } + } + + return respawnPoints; + } + } + + protected void displayError(@Nonnull Message errorMessage) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#Error.Visible", true); + commandBuilder.set("#Error.Text", errorMessage); + this.sendUpdate(commandBuilder); + } + + public static class RespawnPointEventData { + static final String KEY_ACTION = "Action"; + static final String ACTION_CANCEL = "Cancel"; + static final String KEY_INDEX = "Index"; + static final String KEY_RESPAWN_POINT_NAME = "@RespawnPointName"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + RespawnPointPage.RespawnPointEventData.class, RespawnPointPage.RespawnPointEventData::new + ) + .append(new KeyedCodec<>("Action", Codec.STRING), (entry, s) -> entry.action = s, entry -> entry.action) + .add() + .append(new KeyedCodec<>("Index", Codec.STRING), (entry, s) -> { + entry.indexStr = s; + entry.index = Integer.parseInt(s); + }, entry -> entry.indexStr) + .add() + .append(new KeyedCodec<>("@RespawnPointName", Codec.STRING), (entry, s) -> entry.respawnPointName = s, entry -> entry.respawnPointName) + .add() + .build(); + private String action; + private String indexStr; + private int index = -1; + private String respawnPointName; + + public RespawnPointEventData() { + } + + public String getAction() { + return this.action; + } + + public int getIndex() { + return this.index; + } + + public String getRespawnPointName() { + return this.respawnPointName; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/respawn/SelectOverrideRespawnPointPage.java b/src/com/hypixel/hytale/builtin/beds/respawn/SelectOverrideRespawnPointPage.java new file mode 100644 index 0000000..0a6fe52 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/respawn/SelectOverrideRespawnPointPage.java @@ -0,0 +1,108 @@ +package com.hypixel.hytale.builtin.beds.respawn; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerRespawnPointData; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.ui.Value; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.meta.state.RespawnBlock; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SelectOverrideRespawnPointPage extends RespawnPointPage { + private static final Value DEFAULT_RESPAWN_BUTTON_STYLE = Value.ref("Pages/OverrideRespawnPointButton.ui", "DefaultRespawnButtonStyle"); + private static final Value SELECTED_RESPAWN_BUTTON_STYLE = Value.ref("Pages/OverrideRespawnPointButton.ui", "SelectedRespawnButtonStyle"); + private final Vector3i respawnPointToAddPosition; + private final RespawnBlock respawnPointToAdd; + private final PlayerRespawnPointData[] respawnPoints; + private int selectedRespawnPointIndex = -1; + + public SelectOverrideRespawnPointPage( + @Nonnull PlayerRef playerRef, + InteractionType interactionType, + Vector3i respawnPointToAddPosition, + RespawnBlock respawnPointToAdd, + PlayerRespawnPointData[] respawnPoints + ) { + super(playerRef, interactionType); + this.respawnPointToAddPosition = respawnPointToAddPosition; + this.respawnPointToAdd = respawnPointToAdd; + this.respawnPoints = respawnPoints; + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/SelectOverrideRespawnPointPage.ui"); + commandBuilder.clear("#RespawnPointList"); + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + HeadRotation rotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert rotationComponent != null; + + float lookYaw = rotationComponent.getRotation().getYaw(); + double direction = Math.toDegrees(lookYaw); + + for (int i = 0; i < this.respawnPoints.length; i++) { + String selector = "#RespawnPointList[" + i + "]"; + PlayerRespawnPointData respawnPoint = this.respawnPoints[i]; + commandBuilder.append("#RespawnPointList", "Pages/OverrideRespawnPointButton.ui"); + commandBuilder.set(selector + " #Name.Text", respawnPoint.getName()); + Vector3i respawnPointPosition = respawnPoint.getBlockPosition(); + int distance = (int)this.respawnPointToAddPosition.distanceTo(respawnPointPosition.x, this.respawnPointToAddPosition.y, respawnPointPosition.z); + commandBuilder.set(selector + " #Distance.Text", Message.translation("server.customUI.respawnPointDistance").param("distance", distance)); + double angle = Math.atan2(respawnPointPosition.z - this.respawnPointToAddPosition.z, respawnPointPosition.x - this.respawnPointToAddPosition.x); + commandBuilder.set(selector + " #Icon.Angle", Math.toDegrees(angle) + direction + 90.0); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, selector, EventData.of("Index", Integer.toString(i)), false); + } + + commandBuilder.set("#NameInput.Value", Message.translation("server.customUI.defaultRespawnPointName").param("name", playerRefComponent.getUsername())); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ConfirmButton", EventData.of("@RespawnPointName", "#NameInput.Value")); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#CancelButton", EventData.of("Action", "Cancel")); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull RespawnPointPage.RespawnPointEventData data) { + if (data.getIndex() != -1) { + this.setSelectedRespawnPoint(data); + this.sendUpdate(); + } else if (data.getRespawnPointName() != null) { + if (this.selectedRespawnPointIndex == -1) { + this.displayError(Message.translation("server.customUI.needToSelectRespawnPoint")); + return; + } + + this.setRespawnPointForPlayer( + ref, store, this.respawnPointToAddPosition, this.respawnPointToAdd, data.getRespawnPointName(), this.respawnPoints[this.selectedRespawnPointIndex] + ); + } else if ("Cancel".equals(data.getAction())) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + playerComponent.getPageManager().setPage(ref, store, Page.None); + } + } + + private void setSelectedRespawnPoint(@Nonnull RespawnPointPage.RespawnPointEventData data) { + this.selectedRespawnPointIndex = data.getIndex(); + UICommandBuilder commandBuilder = new UICommandBuilder(); + + for (int i = 0; i < this.respawnPoints.length; i++) { + commandBuilder.set("#RespawnPointList[" + i + "].Style", DEFAULT_RESPAWN_BUTTON_STYLE); + } + + commandBuilder.set("#RespawnPointList[" + this.selectedRespawnPointIndex + "].Style", SELECTED_RESPAWN_BUTTON_STYLE); + this.sendUpdate(commandBuilder, null, false); + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/respawn/SetNameRespawnPointPage.java b/src/com/hypixel/hytale/builtin/beds/respawn/SetNameRespawnPointPage.java new file mode 100644 index 0000000..bfec4ed --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/respawn/SetNameRespawnPointPage.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.builtin.beds.respawn; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerRespawnPointData; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.meta.state.RespawnBlock; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SetNameRespawnPointPage extends RespawnPointPage { + private final Vector3i respawnBlockPosition; + private final RespawnBlock respawnBlock; + + public SetNameRespawnPointPage(@Nonnull PlayerRef playerRef, InteractionType interactionType, Vector3i respawnBlockPosition, RespawnBlock respawnBlock) { + super(playerRef, interactionType); + this.respawnBlockPosition = respawnBlockPosition; + this.respawnBlock = respawnBlock; + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/NameRespawnPointPage.ui"); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + World world = store.getExternalData().getWorld(); + PlayerRespawnPointData[] respawnPoints = playerComponent.getPlayerConfigData().getPerWorldData(world.getName()).getRespawnPoints(); + String respawnPointName = null; + if (respawnPoints != null) { + for (PlayerRespawnPointData respawnPoint : respawnPoints) { + if (respawnPoint.getBlockPosition().equals(this.respawnBlockPosition)) { + respawnPointName = respawnPoint.getName(); + break; + } + } + } + + if (respawnPointName == null) { + commandBuilder.set("#NameInput.Value", Message.translation("server.customUI.defaultRespawnPointName").param("name", playerRefComponent.getUsername())); + } else { + commandBuilder.set("#NameInput.Value", respawnPointName); + } + + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#SetButton", EventData.of("@RespawnPointName", "#NameInput.Value")); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#CancelButton", EventData.of("Action", "Cancel")); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull RespawnPointPage.RespawnPointEventData data) { + String respawnPointName = data.getRespawnPointName(); + if (respawnPointName != null) { + this.setRespawnPointForPlayer(ref, store, this.respawnBlockPosition, this.respawnBlock, respawnPointName); + } else if ("Cancel".equals(data.getAction())) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + playerComponent.getPageManager().setPage(ref, store, Page.None); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/sleep/components/PlayerSleep.java b/src/com/hypixel/hytale/builtin/beds/sleep/components/PlayerSleep.java new file mode 100644 index 0000000..fb13c18 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/sleep/components/PlayerSleep.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.beds.sleep.components; + +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import java.time.Instant; + +public sealed interface PlayerSleep permits PlayerSleep.FullyAwake, PlayerSleep.MorningWakeUp, PlayerSleep.NoddingOff, PlayerSleep.Slumber { + public static enum FullyAwake implements PlayerSleep { + INSTANCE; + + private FullyAwake() { + } + } + + public record MorningWakeUp(Instant gameTimeStart) implements PlayerSleep { + public static PlayerSomnolence createComponent(WorldTimeResource worldTimeResource) { + Instant now = worldTimeResource.getGameTime(); + PlayerSleep.MorningWakeUp state = new PlayerSleep.MorningWakeUp(now); + return new PlayerSomnolence(state); + } + } + + public record NoddingOff(Instant realTimeStart) implements PlayerSleep { + public static PlayerSomnolence createComponent() { + Instant now = Instant.now(); + PlayerSleep.NoddingOff state = new PlayerSleep.NoddingOff(now); + return new PlayerSomnolence(state); + } + } + + public record Slumber(Instant gameTimeStart) implements PlayerSleep { + public static PlayerSomnolence createComponent(WorldTimeResource worldTimeResource) { + Instant now = worldTimeResource.getGameTime(); + PlayerSleep.Slumber state = new PlayerSleep.Slumber(now); + return new PlayerSomnolence(state); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/sleep/components/PlayerSomnolence.java b/src/com/hypixel/hytale/builtin/beds/sleep/components/PlayerSomnolence.java new file mode 100644 index 0000000..18cee16 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/sleep/components/PlayerSomnolence.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.beds.sleep.components; + +import com.hypixel.hytale.builtin.beds.BedsPlugin; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class PlayerSomnolence implements Component { + public static PlayerSomnolence AWAKE = new PlayerSomnolence(PlayerSleep.FullyAwake.INSTANCE); + private PlayerSleep state = PlayerSleep.FullyAwake.INSTANCE; + + public static ComponentType getComponentType() { + return BedsPlugin.getInstance().getPlayerSomnolenceComponentType(); + } + + public PlayerSomnolence() { + } + + public PlayerSomnolence(PlayerSleep state) { + this.state = state; + } + + public PlayerSleep getSleepState() { + return this.state; + } + + @NullableDecl + @Override + public Component clone() { + PlayerSomnolence clone = new PlayerSomnolence(); + clone.state = this.state; + return clone; + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/sleep/components/SleepTracker.java b/src/com/hypixel/hytale/builtin/beds/sleep/components/SleepTracker.java new file mode 100644 index 0000000..2c052e9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/sleep/components/SleepTracker.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.beds.sleep.components; + +import com.hypixel.hytale.builtin.beds.BedsPlugin; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.protocol.packets.world.UpdateSleepState; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class SleepTracker implements Component { + private UpdateSleepState lastSentPacket = new UpdateSleepState(false, false, null, null); + + public SleepTracker() { + } + + public static ComponentType getComponentType() { + return BedsPlugin.getInstance().getSleepTrackerComponentType(); + } + + @Nullable + public UpdateSleepState generatePacketToSend(UpdateSleepState state) { + if (this.lastSentPacket.equals(state)) { + return null; + } else { + this.lastSentPacket = state; + return this.lastSentPacket; + } + } + + @NullableDecl + @Override + public Component clone() { + return new SleepTracker(); + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/sleep/resources/WorldSleep.java b/src/com/hypixel/hytale/builtin/beds/sleep/resources/WorldSleep.java new file mode 100644 index 0000000..55406b5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/sleep/resources/WorldSleep.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.builtin.beds.sleep.resources; + +public sealed interface WorldSleep permits WorldSleep.Awake, WorldSlumber { + public static enum Awake implements WorldSleep { + INSTANCE; + + private Awake() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/sleep/resources/WorldSlumber.java b/src/com/hypixel/hytale/builtin/beds/sleep/resources/WorldSlumber.java new file mode 100644 index 0000000..b1e60e4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/sleep/resources/WorldSlumber.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.builtin.beds.sleep.resources; + +import com.hypixel.hytale.protocol.InstantData; +import com.hypixel.hytale.protocol.packets.world.SleepClock; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import java.time.Instant; + +public final class WorldSlumber implements WorldSleep { + private final Instant startInstant; + private final Instant targetInstant; + private final InstantData startInstantData; + private final InstantData targetInstantData; + private final float irlDurationSeconds; + private float progressSeconds = 0.0F; + + public WorldSlumber(Instant startInstant, Instant targetInstant, float irlDurationSeconds) { + this.startInstant = startInstant; + this.targetInstant = targetInstant; + this.startInstantData = WorldTimeResource.instantToInstantData(startInstant); + this.targetInstantData = WorldTimeResource.instantToInstantData(targetInstant); + this.irlDurationSeconds = irlDurationSeconds; + } + + public Instant getStartInstant() { + return this.startInstant; + } + + public Instant getTargetInstant() { + return this.targetInstant; + } + + public InstantData getStartInstantData() { + return this.startInstantData; + } + + public InstantData getTargetInstantData() { + return this.targetInstantData; + } + + public float getProgressSeconds() { + return this.progressSeconds; + } + + public void incProgressSeconds(float seconds) { + this.progressSeconds += seconds; + this.progressSeconds = Math.min(this.progressSeconds, this.irlDurationSeconds); + } + + public float getIrlDurationSeconds() { + return this.irlDurationSeconds; + } + + public SleepClock createSleepClock() { + float progress = this.progressSeconds / this.irlDurationSeconds; + return new SleepClock(this.startInstantData, this.targetInstantData, progress, this.irlDurationSeconds); + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/sleep/resources/WorldSomnolence.java b/src/com/hypixel/hytale/builtin/beds/sleep/resources/WorldSomnolence.java new file mode 100644 index 0000000..f66d33c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/sleep/resources/WorldSomnolence.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.builtin.beds.sleep.resources; + +import com.hypixel.hytale.builtin.beds.BedsPlugin; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class WorldSomnolence implements Resource { + private WorldSleep state = WorldSleep.Awake.INSTANCE; + + public WorldSomnolence() { + } + + public static ResourceType getResourceType() { + return BedsPlugin.getInstance().getWorldSomnolenceResourceType(); + } + + public WorldSleep getState() { + return this.state; + } + + public void setState(WorldSleep state) { + this.state = state; + } + + @NullableDecl + @Override + public Resource clone() { + WorldSomnolence clone = new WorldSomnolence(); + clone.state = this.state; + return clone; + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/sleep/systems/player/EnterBedSystem.java b/src/com/hypixel/hytale/builtin/beds/sleep/systems/player/EnterBedSystem.java new file mode 100644 index 0000000..1065202 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/sleep/systems/player/EnterBedSystem.java @@ -0,0 +1,152 @@ +package com.hypixel.hytale.builtin.beds.sleep.systems.player; + +import com.hypixel.hytale.builtin.beds.sleep.systems.world.CanSleepInWorld; +import com.hypixel.hytale.builtin.mounts.MountedComponent; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.protocol.BlockMountType; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.gameplay.SleepConfig; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.LocalTime; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class EnterBedSystem extends RefChangeSystem { + public static final Query QUERY = Query.and(MountedComponent.getComponentType(), PlayerRef.getComponentType()); + + public EnterBedSystem() { + } + + @Override + public ComponentType componentType() { + return MountedComponent.getComponentType(); + } + + @Override + public Query getQuery() { + return QUERY; + } + + public void onComponentAdded( + @NonNullDecl Ref ref, + @NonNullDecl MountedComponent component, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + this.check(ref, component, store); + } + + public void onComponentSet( + @NonNullDecl Ref ref, + @NullableDecl MountedComponent oldComponent, + @NonNullDecl MountedComponent newComponent, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + this.check(ref, newComponent, store); + } + + public void onComponentRemoved( + @NonNullDecl Ref ref, + @NonNullDecl MountedComponent component, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + } + + public void check(Ref ref, MountedComponent component, Store store) { + if (component.getBlockMountType() == BlockMountType.Bed) { + this.onEnterBed(ref, store); + } + } + + public void onEnterBed(Ref ref, Store store) { + World world = store.getExternalData().getWorld(); + CanSleepInWorld.Result canSleepResult = CanSleepInWorld.check(world); + if (canSleepResult.isNegative()) { + PlayerRef playerRef = store.getComponent(ref, PlayerRef.getComponentType()); + if (canSleepResult instanceof CanSleepInWorld.NotDuringSleepHoursRange(LocalDateTime msg, SleepConfig var14)) { + LocalTime startTime = var14.getSleepStartTime(); + Duration untilSleep = var14.computeDurationUntilSleep(msg); + Message msgx = Message.translation("server.interactions.sleep.sleepAtTheseHours") + .param("time", formatTime(startTime)) + .param("until", formatDuration(untilSleep)); + playerRef.sendMessage(msgx.color("#F2D729")); + } else { + Message msg = this.getMessage(canSleepResult); + playerRef.sendMessage(msg); + } + } + } + + private Message getMessage(CanSleepInWorld.Result param1) { + // $VF: Couldn't be decompiled + // Please report this to the Vineflower issue tracker, at https://github.com/Vineflower/vineflower/issues with a copy of the class file (if you have the rights to distribute it!) + // java.lang.IllegalStateException: Invalid switch case set: [[const(0)], [var1_1 instanceof x], [null]] for selector of type Lcom/hypixel/hytale/builtin/beds/sleep/systems/world/CanSleepInWorld$Result; + // at org.jetbrains.java.decompiler.modules.decompiler.exps.SwitchHeadExprent.checkExprTypeBounds(SwitchHeadExprent.java:66) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor.checkTypeExpr(VarTypeProcessor.java:140) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor.checkTypeExprent(VarTypeProcessor.java:126) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor.lambda$processVarTypes$2(VarTypeProcessor.java:114) + // at org.jetbrains.java.decompiler.modules.decompiler.flow.DirectGraph.iterateExprents(DirectGraph.java:107) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor.processVarTypes(VarTypeProcessor.java:114) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor.calculateVarTypes(VarTypeProcessor.java:44) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionsProcessor.setVarVersions(VarVersionsProcessor.java:68) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarProcessor.setVarVersions(VarProcessor.java:47) + // at org.jetbrains.java.decompiler.main.rels.MethodProcessor.codeToJava(MethodProcessor.java:302) + // + // Bytecode: + // 00: aload 1 + // 01: dup + // 02: invokestatic java/util/Objects.requireNonNull (Ljava/lang/Object;)Ljava/lang/Object; + // 05: pop + // 06: astore 3 + // 07: bipush 0 + // 08: istore 4 + // 0a: aload 3 + // 0b: iload 4 + // 0d: invokedynamic typeSwitch (Ljava/lang/Object;I)I bsm=java/lang/runtime/SwitchBootstraps.typeSwitch (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; args=[ null.invoke Ljava/lang/Enum$EnumDesc;, com/hypixel/hytale/builtin/beds/sleep/systems/world/CanSleepInWorld$NotDuringSleepHoursRange ] + // 12: lookupswitch 42 2 0 26 1 31 + // 2c: ldc "server.interactions.sleep.gameTimePaused" + // 2e: goto 3e + // 31: aload 3 + // 32: checkcast com/hypixel/hytale/builtin/beds/sleep/systems/world/CanSleepInWorld$NotDuringSleepHoursRange + // 35: astore 5 + // 37: ldc "server.interactions.sleep.notWithinHours" + // 39: goto 3e + // 3c: ldc "server.interactions.sleep.disabled" + // 3e: astore 2 + // 3f: aload 2 + // 40: invokestatic com/hypixel/hytale/server/core/Message.translation (Ljava/lang/String;)Lcom/hypixel/hytale/server/core/Message; + // 43: areturn + } + + private static Message formatTime(LocalTime time) { + int hour = time.getHour(); + int minute = time.getMinute(); + boolean isPM = hour >= 12; + int displayHour = hour % 12; + if (displayHour == 0) { + displayHour = 12; + } + + String msgKey = isPM ? "server.interactions.sleep.timePM" : "server.interactions.sleep.timeAM"; + return Message.translation(msgKey).param("h", displayHour).param("m", String.format("%02d", minute)); + } + + private static Message formatDuration(Duration duration) { + long totalMinutes = duration.toMinutes(); + long hours = totalMinutes / 60L; + long minutes = totalMinutes % 60L; + String msgKey = hours > 0L ? "server.interactions.sleep.durationHours" : "server.interactions.sleep.durationMins"; + return Message.translation(msgKey).param("hours", hours).param("mins", hours == 0L ? String.valueOf(minutes) : String.format("%02d", minutes)); + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/sleep/systems/player/RegisterTrackerSystem.java b/src/com/hypixel/hytale/builtin/beds/sleep/systems/player/RegisterTrackerSystem.java new file mode 100644 index 0000000..e16c5a3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/sleep/systems/player/RegisterTrackerSystem.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.beds.sleep.systems.player; + +import com.hypixel.hytale.builtin.beds.sleep.components.SleepTracker; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class RegisterTrackerSystem extends HolderSystem { + public RegisterTrackerSystem() { + } + + @Override + public void onEntityAdd(@NonNullDecl Holder holder, @NonNullDecl AddReason reason, @NonNullDecl Store store) { + holder.ensureComponent(SleepTracker.getComponentType()); + } + + @Override + public void onEntityRemoved(@NonNullDecl Holder holder, @NonNullDecl RemoveReason reason, @NonNullDecl Store store) { + } + + @NullableDecl + @Override + public Query getQuery() { + return PlayerRef.getComponentType(); + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/sleep/systems/player/UpdateSleepPacketSystem.java b/src/com/hypixel/hytale/builtin/beds/sleep/systems/player/UpdateSleepPacketSystem.java new file mode 100644 index 0000000..91f226f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/sleep/systems/player/UpdateSleepPacketSystem.java @@ -0,0 +1,119 @@ +package com.hypixel.hytale.builtin.beds.sleep.systems.player; + +import com.hypixel.hytale.builtin.beds.sleep.components.PlayerSleep; +import com.hypixel.hytale.builtin.beds.sleep.components.PlayerSomnolence; +import com.hypixel.hytale.builtin.beds.sleep.components.SleepTracker; +import com.hypixel.hytale.builtin.beds.sleep.resources.WorldSleep; +import com.hypixel.hytale.builtin.beds.sleep.resources.WorldSlumber; +import com.hypixel.hytale.builtin.beds.sleep.resources.WorldSomnolence; +import com.hypixel.hytale.builtin.beds.sleep.systems.world.CanSleepInWorld; +import com.hypixel.hytale.builtin.beds.sleep.systems.world.StartSlumberSystem; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.DelayedEntitySystem; +import com.hypixel.hytale.protocol.packets.world.SleepClock; +import com.hypixel.hytale.protocol.packets.world.SleepMultiplayer; +import com.hypixel.hytale.protocol.packets.world.UpdateSleepState; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class UpdateSleepPacketSystem extends DelayedEntitySystem { + public static final Query QUERY = Query.and(PlayerRef.getComponentType(), PlayerSomnolence.getComponentType(), SleepTracker.getComponentType()); + public static final Duration SPAN_BEFORE_BLACK_SCREEN = Duration.ofMillis(1200L); + public static final int MAX_SAMPLE_COUNT = 5; + private static final UUID[] EMPTY_UUIDS = new UUID[0]; + private static final UpdateSleepState PACKET_NO_SLEEP_UI = new UpdateSleepState(false, false, null, null); + + @Override + public Query getQuery() { + return QUERY; + } + + public UpdateSleepPacketSystem() { + super(0.25F); + } + + @Override + public void tick( + float dt, + int index, + @NonNullDecl ArchetypeChunk archetypeChunk, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + UpdateSleepState packet = this.createSleepPacket(store, index, archetypeChunk); + SleepTracker sleepTracker = archetypeChunk.getComponent(index, SleepTracker.getComponentType()); + packet = sleepTracker.generatePacketToSend(packet); + if (packet != null) { + PlayerRef playerRef = archetypeChunk.getComponent(index, PlayerRef.getComponentType()); + playerRef.getPacketHandler().write(packet); + } + } + + private UpdateSleepState createSleepPacket(Store store, int index, ArchetypeChunk archetypeChunk) { + World world = store.getExternalData().getWorld(); + WorldSomnolence worldSomnolence = store.getResource(WorldSomnolence.getResourceType()); + WorldSleep worldSleepState = worldSomnolence.getState(); + PlayerSomnolence playerSomnolence = archetypeChunk.getComponent(index, PlayerSomnolence.getComponentType()); + PlayerSleep playerSleepState = playerSomnolence.getSleepState(); + SleepClock clock = worldSleepState instanceof WorldSlumber slumber ? slumber.createSleepClock() : null; + + return switch (playerSleepState) { + case PlayerSleep.FullyAwake ignored -> PACKET_NO_SLEEP_UI; + case PlayerSleep.MorningWakeUp ignoredx -> PACKET_NO_SLEEP_UI; + case PlayerSleep.NoddingOff noddingOff -> { + if (CanSleepInWorld.check(world).isNegative()) { + yield PACKET_NO_SLEEP_UI; + } else { + long elapsedMs = Duration.between(noddingOff.realTimeStart(), Instant.now()).toMillis(); + boolean grayFade = elapsedMs > SPAN_BEFORE_BLACK_SCREEN.toMillis(); + Ref ref = archetypeChunk.getReferenceTo(index); + boolean readyToSleep = StartSlumberSystem.isReadyToSleep(store, ref); + yield new UpdateSleepState(grayFade, false, clock, readyToSleep ? this.createSleepMultiplayer(store) : null); + } + } + case PlayerSleep.Slumber ignoredxx -> new UpdateSleepState(true, true, clock, null); + default -> throw new MatchException(null, null); + }; + } + + @Nullable + private SleepMultiplayer createSleepMultiplayer(Store store) { + World world = store.getExternalData().getWorld(); + List playerRefs = new ArrayList<>(world.getPlayerRefs()); + if (playerRefs.size() <= 1) { + return null; + } else { + playerRefs.sort(Comparator.comparingLong(refx -> refx.getUuid().hashCode() + world.hashCode())); + int sleepersCount = 0; + int awakeCount = 0; + List awakeSampleList = new ArrayList<>(playerRefs.size()); + + for (PlayerRef playerRef : playerRefs) { + Ref ref = playerRef.getReference(); + boolean readyToSleep = StartSlumberSystem.isReadyToSleep(store, ref); + if (readyToSleep) { + sleepersCount++; + } else { + awakeCount++; + awakeSampleList.add(playerRef.getUuid()); + } + } + + UUID[] awakeSample = awakeSampleList.size() > 5 ? EMPTY_UUIDS : awakeSampleList.toArray(UUID[]::new); + return new SleepMultiplayer(sleepersCount, awakeCount, awakeSample); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/sleep/systems/player/WakeUpOnDismountSystem.java b/src/com/hypixel/hytale/builtin/beds/sleep/systems/player/WakeUpOnDismountSystem.java new file mode 100644 index 0000000..8568c44 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/sleep/systems/player/WakeUpOnDismountSystem.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.builtin.beds.sleep.systems.player; + +import com.hypixel.hytale.builtin.beds.sleep.components.PlayerSomnolence; +import com.hypixel.hytale.builtin.mounts.MountedComponent; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.protocol.BlockMountType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class WakeUpOnDismountSystem extends RefChangeSystem { + public WakeUpOnDismountSystem() { + } + + @Override + public ComponentType componentType() { + return MountedComponent.getComponentType(); + } + + @Override + public Query getQuery() { + return MountedComponent.getComponentType(); + } + + public void onComponentAdded( + @NonNullDecl Ref ref, + @NonNullDecl MountedComponent component, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + } + + public void onComponentSet( + @NonNullDecl Ref ref, + @NullableDecl MountedComponent oldComponent, + @NonNullDecl MountedComponent newComponent, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @NonNullDecl Ref ref, + @NonNullDecl MountedComponent component, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + if (component.getBlockMountType() == BlockMountType.Bed) { + commandBuffer.putComponent(ref, PlayerSomnolence.getComponentType(), PlayerSomnolence.AWAKE); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/sleep/systems/world/CanSleepInWorld.java b/src/com/hypixel/hytale/builtin/beds/sleep/systems/world/CanSleepInWorld.java new file mode 100644 index 0000000..ee31769 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/sleep/systems/world/CanSleepInWorld.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.builtin.beds.sleep.systems.world; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.gameplay.SleepConfig; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.LocalDateTime; + +public final class CanSleepInWorld { + public CanSleepInWorld() { + } + + public static CanSleepInWorld.Result check(World world) { + if (world.getWorldConfig().isGameTimePaused()) { + return CanSleepInWorld.Status.GAME_TIME_PAUSED; + } else { + Store store = world.getEntityStore().getStore(); + LocalDateTime worldTime = store.getResource(WorldTimeResource.getResourceType()).getGameDateTime(); + SleepConfig sleepConfig = world.getGameplayConfig().getWorldConfig().getSleepConfig(); + return (CanSleepInWorld.Result)(!sleepConfig.isWithinSleepHoursRange(worldTime) + ? new CanSleepInWorld.NotDuringSleepHoursRange(worldTime, sleepConfig) + : CanSleepInWorld.Status.CAN_SLEEP); + } + } + + public record NotDuringSleepHoursRange(LocalDateTime worldTime, SleepConfig sleepConfig) implements CanSleepInWorld.Result { + @Override + public boolean isNegative() { + return true; + } + } + + public sealed interface Result permits CanSleepInWorld.NotDuringSleepHoursRange, CanSleepInWorld.Status { + boolean isNegative(); + } + + public static enum Status implements CanSleepInWorld.Result { + CAN_SLEEP, + GAME_TIME_PAUSED; + + private Status() { + } + + @Override + public boolean isNegative() { + return this != CAN_SLEEP; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/sleep/systems/world/StartSlumberSystem.java b/src/com/hypixel/hytale/builtin/beds/sleep/systems/world/StartSlumberSystem.java new file mode 100644 index 0000000..916eab3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/sleep/systems/world/StartSlumberSystem.java @@ -0,0 +1,120 @@ +package com.hypixel.hytale.builtin.beds.sleep.systems.world; + +import com.hypixel.hytale.builtin.beds.sleep.components.PlayerSleep; +import com.hypixel.hytale.builtin.beds.sleep.components.PlayerSomnolence; +import com.hypixel.hytale.builtin.beds.sleep.resources.WorldSleep; +import com.hypixel.hytale.builtin.beds.sleep.resources.WorldSlumber; +import com.hypixel.hytale.builtin.beds.sleep.resources.WorldSomnolence; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.DelayedSystem; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Collection; +import java.util.concurrent.TimeUnit; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class StartSlumberSystem extends DelayedSystem { + public static final Duration NODDING_OFF_DURATION = Duration.ofMillis(3200L); + public static final Duration WAKE_UP_AUTOSLEEP_DELAY = Duration.ofHours(1L); + + public StartSlumberSystem() { + super(0.3F); + } + + @Override + public void delayedTick(float dt, int systemIndex, @NonNullDecl Store store) { + this.checkIfEveryoneIsReadyToSleep(store); + } + + private void checkIfEveryoneIsReadyToSleep(Store store) { + World world = store.getExternalData().getWorld(); + Collection playerRefs = world.getPlayerRefs(); + if (!playerRefs.isEmpty()) { + if (!CanSleepInWorld.check(world).isNegative()) { + float wakeUpHour = world.getGameplayConfig().getWorldConfig().getSleepConfig().getWakeUpHour(); + WorldSomnolence worldSomnolence = store.getResource(WorldSomnolence.getResourceType()); + WorldSleep worldState = worldSomnolence.getState(); + if (worldState == WorldSleep.Awake.INSTANCE) { + if (this.isEveryoneReadyToSleep(store)) { + WorldTimeResource timeResource = store.getResource(WorldTimeResource.getResourceType()); + Instant now = timeResource.getGameTime(); + Instant target = this.computeWakeupInstant(now, wakeUpHour); + float irlSeconds = computeIrlSeconds(now, target); + worldSomnolence.setState(new WorldSlumber(now, target, irlSeconds)); + store.forEachEntityParallel(PlayerSomnolence.getComponentType(), (index, archetypeChunk, commandBuffer) -> { + Ref ref = archetypeChunk.getReferenceTo(index); + commandBuffer.putComponent(ref, PlayerSomnolence.getComponentType(), PlayerSleep.Slumber.createComponent(timeResource)); + }); + } + } + } + } + } + + private Instant computeWakeupInstant(Instant now, float wakeUpHour) { + LocalDateTime ldt = LocalDateTime.ofInstant(now, ZoneOffset.UTC); + int hours = (int)wakeUpHour; + float fractionalHour = wakeUpHour - hours; + LocalDateTime wakeUpTime = ldt.toLocalDate().atTime(hours, (int)(fractionalHour * 60.0F)); + if (!ldt.isBefore(wakeUpTime)) { + wakeUpTime = wakeUpTime.plusDays(1L); + } + + return wakeUpTime.toInstant(ZoneOffset.UTC); + } + + private static float computeIrlSeconds(Instant startInstant, Instant targetInstant) { + long ms = Duration.between(startInstant, targetInstant).toMillis(); + long hours = TimeUnit.MILLISECONDS.toHours(ms); + double seconds = Math.max(3.0, hours / 6.0); + return (float)Math.ceil(seconds); + } + + private boolean isEveryoneReadyToSleep(ComponentAccessor store) { + World world = store.getExternalData().getWorld(); + Collection playerRefs = world.getPlayerRefs(); + if (playerRefs.isEmpty()) { + return false; + } else { + for (PlayerRef playerRef : playerRefs) { + if (!isReadyToSleep(store, playerRef.getReference())) { + return false; + } + } + + return true; + } + } + + public static boolean isReadyToSleep(ComponentAccessor store, Ref ref) { + PlayerSomnolence somnolence = store.getComponent(ref, PlayerSomnolence.getComponentType()); + if (somnolence == null) { + return false; + } else { + PlayerSleep sleepState = somnolence.getSleepState(); + + return switch (sleepState) { + case PlayerSleep.FullyAwake fullyAwake -> false; + case PlayerSleep.MorningWakeUp morningWakeUp -> { + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + Instant readyTime = morningWakeUp.gameTimeStart().plus(WAKE_UP_AUTOSLEEP_DELAY); + yield worldTimeResource.getGameTime().isAfter(readyTime); + } + case PlayerSleep.NoddingOff noddingOff -> { + Instant sleepStart = noddingOff.realTimeStart().plus(NODDING_OFF_DURATION); + yield Instant.now().isAfter(sleepStart); + } + case PlayerSleep.Slumber slumber -> true; + default -> throw new MatchException(null, null); + }; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/beds/sleep/systems/world/UpdateWorldSlumberSystem.java b/src/com/hypixel/hytale/builtin/beds/sleep/systems/world/UpdateWorldSlumberSystem.java new file mode 100644 index 0000000..244d0a9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/beds/sleep/systems/world/UpdateWorldSlumberSystem.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.builtin.beds.sleep.systems.world; + +import com.hypixel.hytale.builtin.beds.sleep.components.PlayerSleep; +import com.hypixel.hytale.builtin.beds.sleep.components.PlayerSomnolence; +import com.hypixel.hytale.builtin.beds.sleep.resources.WorldSleep; +import com.hypixel.hytale.builtin.beds.sleep.resources.WorldSlumber; +import com.hypixel.hytale.builtin.beds.sleep.resources.WorldSomnolence; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Duration; +import java.time.Instant; +import java.util.Collection; +import java.util.Iterator; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class UpdateWorldSlumberSystem extends TickingSystem { + public UpdateWorldSlumberSystem() { + } + + @Override + public void tick(float dt, int systemIndex, @NonNullDecl Store store) { + World world = store.getExternalData().getWorld(); + WorldSomnolence worldSomnolence = store.getResource(WorldSomnolence.getResourceType()); + if (worldSomnolence.getState() instanceof WorldSlumber slumber) { + slumber.incProgressSeconds(dt); + boolean sleepingIsOver = slumber.getProgressSeconds() >= slumber.getIrlDurationSeconds() || isSomeoneAwake(store); + if (sleepingIsOver) { + worldSomnolence.setState(WorldSleep.Awake.INSTANCE); + WorldTimeResource timeResource = store.getResource(WorldTimeResource.getResourceType()); + Instant wakeUpTime = computeWakeupTime(slumber); + timeResource.setGameTime(wakeUpTime, world, store); + store.forEachEntityParallel(PlayerSomnolence.getComponentType(), (index, archetypeChunk, commandBuffer) -> { + PlayerSomnolence somnolence = archetypeChunk.getComponent(index, PlayerSomnolence.getComponentType()); + if (somnolence.getSleepState() instanceof PlayerSleep.Slumber) { + Ref ref = archetypeChunk.getReferenceTo(index); + commandBuffer.putComponent(ref, PlayerSomnolence.getComponentType(), PlayerSleep.MorningWakeUp.createComponent(timeResource)); + } + }); + } + } + } + + private static Instant computeWakeupTime(WorldSlumber slumber) { + float progress = slumber.getProgressSeconds() / slumber.getIrlDurationSeconds(); + long totalNanos = Duration.between(slumber.getStartInstant(), slumber.getTargetInstant()).toNanos(); + long progressNanos = (long)((float)totalNanos * progress); + return slumber.getStartInstant().plusNanos(progressNanos); + } + + private static boolean isSomeoneAwake(ComponentAccessor store) { + World world = store.getExternalData().getWorld(); + Collection playerRefs = world.getPlayerRefs(); + if (playerRefs.isEmpty()) { + return false; + } else { + Iterator var3 = playerRefs.iterator(); + if (var3.hasNext()) { + PlayerRef playerRef = (PlayerRef)var3.next(); + PlayerSomnolence somnolence = store.getComponent(playerRef.getReference(), PlayerSomnolence.getComponentType()); + if (somnolence == null) { + return true; + } else { + PlayerSleep sleepState = somnolence.getSleepState(); + return sleepState instanceof PlayerSleep.FullyAwake; + } + } else { + return false; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/blockphysics/BlockPhysicsPlugin.java b/src/com/hypixel/hytale/builtin/blockphysics/BlockPhysicsPlugin.java new file mode 100644 index 0000000..568541e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blockphysics/BlockPhysicsPlugin.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.builtin.blockphysics; + +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.asset.LoadAssetEvent; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.ValidationOption; +import java.util.List; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class BlockPhysicsPlugin extends JavaPlugin { + public BlockPhysicsPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + this.getEventRegistry().register(LoadAssetEvent.class, BlockPhysicsPlugin::validatePrefabs); + this.getChunkStoreRegistry().registerSystem(new BlockPhysicsSystems.Ticking()); + } + + public static void validatePrefabs(@Nonnull LoadAssetEvent event) { + if (Options.getOptionSet().has(Options.VALIDATE_PREFABS) && !event.isShouldShutdown()) { + long start = System.nanoTime(); + List validatePrefabs = Options.getOptionSet().valuesOf(Options.VALIDATE_PREFABS); + List failedToValidatePrefabs = PrefabBufferValidator.validateAllPrefabs(validatePrefabs); + if (!failedToValidatePrefabs.isEmpty()) { + HytaleLogger.getLogger().at(Level.SEVERE).log("One or more prefabs failed to validate, Exiting!\n" + String.join("\n", failedToValidatePrefabs)); + event.failed(true, "failed to validate prefabs"); + } + + HytaleLogger.getLogger() + .at(Level.INFO) + .log( + "Validate prefabs phase completed! Boot time %s, Took %s", + FormatUtil.nanosToString(System.nanoTime() - event.getBootStart()), + FormatUtil.nanosToString(System.nanoTime() - start) + ); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/blockphysics/BlockPhysicsSystems.java b/src/com/hypixel/hytale/builtin/blockphysics/BlockPhysicsSystems.java new file mode 100644 index 0000000..f960761 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blockphysics/BlockPhysicsSystems.java @@ -0,0 +1,232 @@ +package com.hypixel.hytale.builtin.blockphysics; + +import com.hypixel.hytale.builtin.blocktick.system.ChunkBlockTickSystem; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.DisableProcessingAssert; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.AbstractCachedAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockPhysicsSystems { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final int MAX_SUPPORT_RADIUS = 14; + + public BlockPhysicsSystems() { + } + + public static class CachedAccessor extends AbstractCachedAccessor { + private static final ThreadLocal THREAD_LOCAL = ThreadLocal.withInitial(BlockPhysicsSystems.CachedAccessor::new); + private static final int PHYSICS_COMPONENT = 0; + private static final int FLUID_COMPONENT = 1; + private static final int BLOCK_COMPONENT = 2; + protected BlockSection selfBlockSection; + protected BlockPhysics selfPhysics; + protected FluidSection selfFluidSection; + + protected CachedAccessor() { + super(3); + } + + @Nonnull + public static BlockPhysicsSystems.CachedAccessor of( + ComponentAccessor commandBuffer, + BlockSection blockSection, + BlockPhysics section, + FluidSection fluidSection, + int cx, + int cy, + int cz, + int radius + ) { + BlockPhysicsSystems.CachedAccessor accessor = THREAD_LOCAL.get(); + accessor.init(commandBuffer, cx, cy, cz, radius); + accessor.insertSectionComponent(0, section, cx, cy, cz); + accessor.insertSectionComponent(1, fluidSection, cx, cy, cz); + accessor.insertSectionComponent(2, blockSection, cx, cy, cz); + accessor.selfBlockSection = blockSection; + accessor.selfPhysics = section; + accessor.selfFluidSection = fluidSection; + return accessor; + } + + @Nullable + public BlockPhysics getBlockPhysics(int cx, int cy, int cz) { + return this.getComponentSection(cx, cy, cz, 0, BlockPhysics.getComponentType()); + } + + @Nullable + public FluidSection getFluidSection(int cx, int cy, int cz) { + return this.getComponentSection(cx, cy, cz, 1, FluidSection.getComponentType()); + } + + @Nullable + public BlockSection getBlockSection(int cx, int cy, int cz) { + return this.getComponentSection(cx, cy, cz, 2, BlockSection.getComponentType()); + } + + public void performBlockUpdate(int x, int y, int z, int maxSupportDistance) { + for (int ix = -1; ix < 2; ix++) { + int wx = x + ix; + + for (int iz = -1; iz < 2; iz++) { + int wz = z + iz; + + for (int iy = -1; iy < 2; iy++) { + int wy = y + iy; + BlockPhysics physics = this.getBlockPhysics(ChunkUtil.chunkCoordinate(wx), ChunkUtil.chunkCoordinate(wy), ChunkUtil.chunkCoordinate(wz)); + int support = physics != null ? physics.get(wx, wy, wz) : 0; + if (support <= maxSupportDistance) { + BlockSection blockChunk = this.getBlockSection(ChunkUtil.chunkCoordinate(wx), ChunkUtil.chunkCoordinate(wy), ChunkUtil.chunkCoordinate(wz)); + if (blockChunk != null) { + blockChunk.setTicking(wx, wy, wz, true); + } + } + } + } + } + } + + public void performBlockUpdate(int x, int y, int z) { + for (int ix = -1; ix < 2; ix++) { + int wx = x + ix; + + for (int iz = -1; iz < 2; iz++) { + int wz = z + iz; + + for (int iy = -1; iy < 2; iy++) { + int wy = y + iy; + BlockSection blockChunk = this.getBlockSection(ChunkUtil.chunkCoordinate(wx), ChunkUtil.chunkCoordinate(wy), ChunkUtil.chunkCoordinate(wz)); + if (blockChunk != null) { + blockChunk.setTicking(wx, wy, wz, true); + } + } + } + } + } + } + + public static class Ticking extends EntityTickingSystem implements DisableProcessingAssert { + private static final Query QUERY = Query.and( + ChunkSection.getComponentType(), BlockSection.getComponentType(), BlockPhysics.getComponentType(), FluidSection.getComponentType() + ); + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, ChunkBlockTickSystem.PreTick.class), new SystemDependency<>(Order.BEFORE, ChunkBlockTickSystem.Ticking.class) + ); + + public Ticking() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + ChunkSection section = archetypeChunk.getComponent(index, ChunkSection.getComponentType()); + + assert section != null; + + try { + BlockSection blockSection = archetypeChunk.getComponent(index, BlockSection.getComponentType()); + + assert blockSection != null; + + if (blockSection.getTickingBlocksCountCopy() <= 0) { + return; + } + + BlockPhysics blockPhysics = archetypeChunk.getComponent(index, BlockPhysics.getComponentType()); + + assert blockPhysics != null; + + FluidSection fluidSection = archetypeChunk.getComponent(index, FluidSection.getComponentType()); + + assert fluidSection != null; + + WorldChunk worldChunk = commandBuffer.getComponent(section.getChunkColumnReference(), WorldChunk.getComponentType()); + BlockPhysicsSystems.CachedAccessor accessor = BlockPhysicsSystems.CachedAccessor.of( + commandBuffer, blockSection, blockPhysics, fluidSection, section.getX(), section.getY(), section.getZ(), 14 + ); + blockSection.forEachTicking( + worldChunk, + accessor, + section.getY(), + (wc, accessor1, localX, localY, localZ, blockId) -> { + BlockPhysics phys = accessor1.selfPhysics; + boolean isDeco = phys.isDeco(localX, localY, localZ); + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + if (blockType == null || blockId == 0) { + return BlockTickStrategy.IGNORED; + } else if (blockType.canBePlacedAsDeco() && isDeco) { + return BlockTickStrategy.IGNORED; + } else { + World world = wc.getWorld(); + Store entityStore = world.getEntityStore().getStore(); + int blockX = wc.getX() << 5 | localX; + int blockZ = wc.getZ() << 5 | localZ; + int filler = accessor1.selfBlockSection.getFiller(localX, localY, localZ); + int rotation = accessor1.selfBlockSection.getRotationIndex(localX, localY, localZ); + + return switch (BlockPhysicsUtil.applyBlockPhysics( + entityStore, + wc.getReference(), + accessor, + accessor1.selfBlockSection, + accessor1.selfPhysics, + accessor1.selfFluidSection, + blockX, + localY, + blockZ, + blockType, + rotation, + filler + )) { + case WAITING_CHUNK -> BlockTickStrategy.WAIT_FOR_ADJACENT_CHUNK_LOAD; + case VALID -> BlockTickStrategy.IGNORED; + case INVALID -> BlockTickStrategy.SLEEP; + }; + } + } + ); + } catch (Exception var12) { + BlockPhysicsSystems.LOGGER.at(Level.SEVERE).withCause(var12).log("Failed to tick chunk: %s", section); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/blockphysics/BlockPhysicsUtil.java b/src/com/hypixel/hytale/builtin/blockphysics/BlockPhysicsUtil.java new file mode 100644 index 0000000..9ef5c32 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blockphysics/BlockPhysicsUtil.java @@ -0,0 +1,454 @@ +package com.hypixel.hytale.builtin.blockphysics; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFace; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFaceSupport; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RequiredBlockFaceSupport; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; +import com.hypixel.hytale.server.core.modules.blockset.BlockSetModule; +import com.hypixel.hytale.server.core.modules.interaction.BlockHarvestUtils; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockPhysicsUtil { + public static final int DOESNT_SATISFY = 0; + public static final int IGNORE = -1; + public static final int SATISFIES_SUPPORT = -2; + public static final int WAITING_CHUNK = -3; + + public BlockPhysicsUtil() { + } + + @Nonnull + public static BlockPhysicsUtil.Result applyBlockPhysics( + @Nullable ComponentAccessor commandBuffer, + @Nonnull Ref chunkReference, + @Nonnull BlockPhysicsSystems.CachedAccessor chunkAccessor, + BlockSection blockSection, + @Nonnull BlockPhysics blockPhysics, + @Nonnull FluidSection fluidSection, + int blockX, + int blockY, + int blockZ, + @Nonnull BlockType blockType, + int rotation, + int filler + ) { + if (filler != 0) { + return BlockPhysicsUtil.Result.VALID; + } else { + int supportDistance = -1; + if (blockType.getHitboxTypeIndex() == 0) { + supportDistance = testBlockPhysics(chunkAccessor, blockSection, blockPhysics, fluidSection, blockX, blockY, blockZ, blockType, rotation, filler); + } else { + BlockBoundingBoxes boundingBoxes = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex()); + if (!boundingBoxes.protrudesUnitBox()) { + supportDistance = testBlockPhysics(chunkAccessor, blockSection, blockPhysics, fluidSection, blockX, blockY, blockZ, blockType, rotation, filler); + } else { + BlockBoundingBoxes.RotatedVariantBoxes rotatedBox = boundingBoxes.get(rotation); + Box boundingBox = rotatedBox.getBoundingBox(); + int minX = (int)boundingBox.min.x; + int minY = (int)boundingBox.min.y; + int minZ = (int)boundingBox.min.z; + if (minX - boundingBox.min.x > 0.0) { + minX--; + } + + if (minY - boundingBox.min.y > 0.0) { + minY--; + } + + if (minZ - boundingBox.min.z > 0.0) { + minZ--; + } + + int maxX = (int)boundingBox.max.x; + int maxY = (int)boundingBox.max.y; + int maxZ = (int)boundingBox.max.z; + if (boundingBox.max.x - maxX > 0.0) { + maxX++; + } + + if (boundingBox.max.y - maxY > 0.0) { + maxY++; + } + + if (boundingBox.max.z - maxZ > 0.0) { + maxZ++; + } + + int blockWidth = Math.max(maxX - minX, 1); + int blockHeight = Math.max(maxY - minY, 1); + int blockDepth = Math.max(maxZ - minZ, 1); + + label138: + for (int x = 0; x < blockWidth; x++) { + for (int y = 0; y < blockHeight; y++) { + for (int z = 0; z < blockDepth; z++) { + int fillerX = blockX + minX + x; + int fillerY = blockY + minY + y; + int fillerZ = blockZ + minZ + z; + BlockSection neighbourBlockSection; + FluidSection neighbourFluidSection; + BlockPhysics neighbourBlockPhysics; + if (ChunkUtil.isSameChunkSection(blockX, blockY, blockZ, fillerX, fillerY, fillerZ)) { + neighbourBlockSection = blockSection; + neighbourFluidSection = fluidSection; + neighbourBlockPhysics = blockPhysics; + } else { + int nx = ChunkUtil.chunkCoordinate(fillerX); + int ny = ChunkUtil.chunkCoordinate(fillerY); + int nz = ChunkUtil.chunkCoordinate(fillerZ); + neighbourBlockSection = chunkAccessor.getBlockSection(nx, ny, nz); + neighbourFluidSection = chunkAccessor.getFluidSection(nx, ny, nz); + neighbourBlockPhysics = chunkAccessor.getBlockPhysics(nx, ny, nz); + } + + if (neighbourBlockSection == null || neighbourFluidSection == null) { + return BlockPhysicsUtil.Result.WAITING_CHUNK; + } + + int neighbourFiller = FillerBlockUtil.pack(minX + x, minY + y, minZ + z); + int neighbourRotation = neighbourBlockSection.getRotationIndex(fillerX, fillerY, fillerZ); + int fillerSupportDistance = testBlockPhysics( + chunkAccessor, + neighbourBlockSection, + neighbourBlockPhysics, + neighbourFluidSection, + fillerX, + fillerY, + fillerZ, + blockType, + neighbourRotation, + neighbourFiller + ); + if (fillerSupportDistance != -1) { + switch (blockType.getBlockSupportsRequiredFor()) { + case Any: + if (fillerSupportDistance == -2) { + supportDistance = -2; + break label138; + } + + if (fillerSupportDistance == 0) { + supportDistance = 0; + } else if (supportDistance < fillerSupportDistance) { + supportDistance = fillerSupportDistance; + } + break; + case All: + if (fillerSupportDistance == 0) { + supportDistance = 0; + break label138; + } + + if (fillerSupportDistance == -2) { + supportDistance = -2; + } else if (supportDistance == -1 && supportDistance < fillerSupportDistance) { + supportDistance = fillerSupportDistance; + } + } + } + } + } + } + } + } + + if (supportDistance == 0) { + World world = commandBuffer.getExternalData().getWorld(); + Store chunkStore = world.getChunkStore().getStore(); + switch (blockType.getSupportDropType()) { + case BREAK: + BlockHarvestUtils.naturallyRemoveBlockByPhysics( + new Vector3i(blockX, blockY, blockZ), blockType, filler, 256, chunkReference, commandBuffer, chunkStore + ); + break; + case DESTROY: + BlockHarvestUtils.naturallyRemoveBlockByPhysics( + new Vector3i(blockX, blockY, blockZ), blockType, filler, 2304, chunkReference, commandBuffer, chunkStore + ); + } + + return BlockPhysicsUtil.Result.INVALID; + } else if (supportDistance == -1) { + return BlockPhysicsUtil.Result.VALID; + } else if (supportDistance == -3) { + return BlockPhysicsUtil.Result.WAITING_CHUNK; + } else { + int currentSupport = blockPhysics.get(blockX, blockY, blockZ); + if (supportDistance == -2) { + if (currentSupport != 0) { + blockPhysics.set(blockX, blockY, blockZ, 0); + chunkAccessor.performBlockUpdate(blockX, blockY, blockZ); + } + + return BlockPhysicsUtil.Result.VALID; + } else { + if (currentSupport == supportDistance) { + chunkAccessor.performBlockUpdate(blockX, blockY, blockZ, supportDistance - 1); + } else { + blockPhysics.set(blockX, blockY, blockZ, supportDistance); + chunkAccessor.performBlockUpdate(blockX, blockY, blockZ); + } + + return BlockPhysicsUtil.Result.VALID; + } + } + } + } + + public static int testBlockPhysics( + @Nonnull BlockPhysicsSystems.CachedAccessor chunkAccessor, + BlockSection blockSection, + @Nullable BlockPhysics blockPhysics, + @Nonnull FluidSection fluidSection, + int blockX, + int blockY, + int blockZ, + @Nonnull BlockType blockType, + int rotation, + int filler + ) { + if (blockType.isUnknown()) { + return -1; + } else { + Map requiredBlockFaceSupportMap = blockType.getSupport(rotation); + if (requiredBlockFaceSupportMap != null && !requiredBlockFaceSupportMap.isEmpty()) { + Vector3i blockFillerOffset = new Vector3i(FillerBlockUtil.unpackX(filler), FillerBlockUtil.unpackY(filler), FillerBlockUtil.unpackZ(filler)); + Vector3i neighbourFillerOffset = new Vector3i(); + Fluid fluid = Fluid.getAssetMap().getAsset(fluidSection.getFluidId(blockX, blockY, blockZ)); + BlockBoundingBoxes hitbox = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex()); + Box boundingBox = hitbox.get(rotation).getBoundingBox(); + Vector3i origin = new Vector3i( + blockX - FillerBlockUtil.unpackX(filler), blockY - FillerBlockUtil.unpackY(filler), blockZ - FillerBlockUtil.unpackZ(filler) + ); + boolean hasTestedForSupport = false; + int requiredSupportDistance = blockType.getMaxSupportDistance(); + int lowestSupportDistance = Integer.MAX_VALUE; + + for (BlockFace blockFace : BlockFace.VALUES) { + RequiredBlockFaceSupport[] requiredBlockFaceSupports = requiredBlockFaceSupportMap.get(blockFace); + if (requiredBlockFaceSupports != null && requiredBlockFaceSupports.length != 0) { + BlockFace[] connectingFaces = blockFace.getConnectingFaces(); + Vector3i[] connectingFaceOffsets = blockFace.getConnectingFaceOffsets(); + + for (int i = 0; i < connectingFaces.length; i++) { + BlockFace neighbourBlockFace = connectingFaces[i]; + Vector3i neighbourDirection = connectingFaceOffsets[i]; + int neighbourX = blockX + neighbourDirection.x; + int neighbourY = blockY + neighbourDirection.y; + int neighbourZ = blockZ + neighbourDirection.z; + if (!boundingBox.containsBlock(origin, neighbourX, neighbourY, neighbourZ)) { + BlockSection neighbourBlockSection; + FluidSection neighbourFluidSection; + BlockPhysics neighbourBlockPhysics; + if (ChunkUtil.isSameChunkSection(blockX, blockY, blockZ, neighbourX, neighbourY, neighbourZ)) { + neighbourBlockSection = blockSection; + neighbourFluidSection = fluidSection; + neighbourBlockPhysics = blockPhysics; + } else { + int nx = ChunkUtil.chunkCoordinate(neighbourX); + int ny = ChunkUtil.chunkCoordinate(neighbourY); + int nz = ChunkUtil.chunkCoordinate(neighbourZ); + neighbourBlockSection = chunkAccessor.getBlockSection(nx, ny, nz); + neighbourFluidSection = chunkAccessor.getFluidSection(nx, ny, nz); + neighbourBlockPhysics = chunkAccessor.getBlockPhysics(nx, ny, nz); + } + + if (neighbourFluidSection == null || neighbourBlockSection == null) { + return -3; + } + + int neighbourFluidId = neighbourFluidSection.getFluidId(neighbourX, neighbourY, neighbourZ); + int neighbourBlockId = neighbourBlockSection.get(neighbourX, neighbourY, neighbourZ); + int neighbourFiller = neighbourBlockSection.getFiller(neighbourX, neighbourY, neighbourZ); + int neighbourRotation = neighbourBlockSection.getRotationIndex(neighbourX, neighbourY, neighbourZ); + BlockType neighbourBlockType = BlockType.getAssetMap().getAsset(neighbourBlockId); + Fluid neighbourFluid = Fluid.getAssetMap().getAsset(neighbourFluidId); + neighbourFillerOffset.assign( + FillerBlockUtil.unpackX(neighbourFiller), FillerBlockUtil.unpackY(neighbourFiller), FillerBlockUtil.unpackZ(neighbourFiller) + ); + boolean doesSatisfySupport = false; + boolean failedSatisfySupport = false; + + for (RequiredBlockFaceSupport requiredBlockFaceSupport : requiredBlockFaceSupports) { + if (requiredBlockFaceSupport.isAppliedToFiller(blockFillerOffset)) { + boolean doesSatisfyRequirements = doesSatisfyRequirements( + blockType, + fluid, + blockFillerOffset, + neighbourFillerOffset, + blockFace, + neighbourBlockFace, + neighbourBlockId, + neighbourBlockType, + neighbourRotation, + neighbourFluidId, + neighbourFluid, + requiredBlockFaceSupport + ); + if (doesSatisfyRequirements && requiredSupportDistance > 0 && requiredBlockFaceSupport.allowsSupportPropagation()) { + int supportDistance = neighbourBlockPhysics != null ? neighbourBlockPhysics.get(neighbourX, neighbourY, neighbourZ) : 0; + if (supportDistance == 15) { + lowestSupportDistance = 1; + } else if (supportDistance < lowestSupportDistance) { + lowestSupportDistance = supportDistance; + } + } + + switch (requiredBlockFaceSupport.getSupport()) { + case IGNORED: + break; + case REQUIRED: + if (doesSatisfyRequirements) { + doesSatisfySupport = true; + } + + hasTestedForSupport = true; + break; + case DISALLOWED: + if (doesSatisfyRequirements) { + failedSatisfySupport = true; + } + + hasTestedForSupport = true; + break; + default: + throw new IllegalArgumentException("Unknown Support Match type: " + requiredBlockFaceSupport.getMatchSelf()); + } + } + } + + if (!failedSatisfySupport && doesSatisfySupport) { + return -2; + } + } + } + } + } + + if (!hasTestedForSupport) { + return -1; + } else { + if (lowestSupportDistance < Integer.MAX_VALUE && lowestSupportDistance >= 0) { + int supportDistance = lowestSupportDistance + 1; + if (requiredSupportDistance >= supportDistance) { + return supportDistance; + } + } + + return 0; + } + } else { + return -1; + } + } + } + + public static boolean doesSatisfyRequirements( + @Nonnull BlockType blockType, + Fluid fluid, + Vector3i blockFillerOffset, + Vector3i neighbourFillerOffset, + BlockFace blockFace, + BlockFace neighbourBlockFace, + int neighbourBlockId, + @Nonnull BlockType neighbourBlockType, + int neighbourRotation, + int neighbourFluidId, + @Nonnull Fluid neighbourFluid, + @Nonnull RequiredBlockFaceSupport requiredBlockFaceSupport + ) { + String neighbourBlockTypeKey = neighbourBlockType.getId(); + boolean hasSupport = true; + int blockSetId = requiredBlockFaceSupport.getBlockSetIndex(); + if (blockSetId >= 0 && !BlockSetModule.getInstance().blockInSet(blockSetId, neighbourBlockId)) { + hasSupport = false; + } + + String requiredBlockTypeId = requiredBlockFaceSupport.getBlockTypeId(); + if (hasSupport && requiredBlockTypeId != null && !requiredBlockTypeId.equals(neighbourBlockTypeKey)) { + hasSupport = false; + } + + String fluidId = requiredBlockFaceSupport.getFluidId(); + if (hasSupport + && fluidId != null + && (neighbourBlockType.getMaterial() != BlockMaterial.Empty || neighbourFluidId == 0 || !fluidId.equals(neighbourFluid.getId()))) { + hasSupport = false; + } + + int tagIndex = requiredBlockFaceSupport.getTagIndex(); + if (tagIndex >= 0 && !BlockType.getAssetMap().getKeysForTag(tagIndex).contains(neighbourBlockTypeKey)) { + hasSupport = false; + } + + if (hasSupport && requiredBlockFaceSupport.getFaceType() != null) { + hasSupport = doesMatchFaceType( + neighbourFillerOffset, requiredBlockFaceSupport.getFaceType(), neighbourBlockFace, neighbourBlockType.getSupporting(neighbourRotation) + ); + } + + if (hasSupport && requiredBlockFaceSupport.getSelfFaceType() != null) { + hasSupport = doesMatchFaceType(blockFillerOffset, requiredBlockFaceSupport.getSelfFaceType(), blockFace, blockType.getSupporting(neighbourRotation)); + } + return switch (requiredBlockFaceSupport.getMatchSelf()) { + case IGNORED -> { + } + case REQUIRED -> { + if (hasSupport) { + yield blockType.getId().equals(neighbourBlockTypeKey); + } + } + case DISALLOWED -> { + if (hasSupport) { + yield !blockType.getId().equals(neighbourBlockTypeKey); + } + } + default -> throw new IllegalArgumentException("Unknown MatchSelf type: " + requiredBlockFaceSupport.getMatchSelf()); + }; + } + + public static boolean doesMatchFaceType( + Vector3i fillerOffset, @Nonnull String faceType, BlockFace blockFace, @Nonnull Map supporting + ) { + boolean faceHasSupport = false; + BlockFaceSupport[] blockFaceSupports = supporting.get(blockFace); + if (blockFaceSupports != null) { + for (BlockFaceSupport blockFaceSupport : blockFaceSupports) { + if (blockFaceSupport.providesSupportFromFiller(fillerOffset) && faceType.equals(blockFaceSupport.getFaceType())) { + faceHasSupport = true; + break; + } + } + } + + return faceHasSupport; + } + + public static enum Result { + INVALID, + VALID, + WAITING_CHUNK; + + private Result() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/blockphysics/PrefabBufferValidator.java b/src/com/hypixel/hytale/builtin/blockphysics/PrefabBufferValidator.java new file mode 100644 index 0000000..91b4144 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blockphysics/PrefabBufferValidator.java @@ -0,0 +1,161 @@ +package com.hypixel.hytale.builtin.blockphysics; + +import com.hypixel.hytale.common.util.ExceptionUtil; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.data.unknown.UnknownComponents; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.prefab.PrefabStore; +import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabBufferUtil; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.IPrefabBuffer; +import com.hypixel.hytale.server.core.universe.world.ValidationOption; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabBufferValidator { + private static final FillerBlockUtil.FillerFetcher FILLER_FETCHER = new FillerBlockUtil.FillerFetcher() { + public int getBlock(IPrefabBuffer iPrefabBuffer, Void unused, int x, int y, int z) { + return iPrefabBuffer.getBlockId(x, y, z); + } + + public int getFiller(IPrefabBuffer iPrefabBuffer, Void unused, int x, int y, int z) { + return iPrefabBuffer.getFiller(x, y, z); + } + + public int getRotationIndex(IPrefabBuffer iPrefabBuffer, Void unused, int x, int y, int z) { + return iPrefabBuffer.getRotationIndex(x, y, z); + } + }; + + public PrefabBufferValidator() { + } + + @Nonnull + public static List validateAllPrefabs(@Nonnull List list) { + Set options = !list.isEmpty() + ? EnumSet.copyOf(list) + : EnumSet.of(ValidationOption.BLOCK_STATES, ValidationOption.ENTITIES, ValidationOption.BLOCKS, ValidationOption.BLOCK_FILLER); + List out = validatePrefabsInPath(PrefabStore.get().getWorldGenPrefabsPath(), options); + out.addAll(validatePrefabsInPath(PrefabStore.get().getAssetPrefabsPath(), options)); + out.addAll(validatePrefabsInPath(PrefabStore.get().getServerPrefabsPath(), options)); + return out; + } + + @Nonnull + public static List validatePrefabsInPath(@Nonnull Path dataFolder, @Nonnull Set options) { + if (!Files.exists(dataFolder)) { + return new ArrayList<>(); + } else { + try { + List var3; + try (Stream stream = Files.walk(dataFolder, FileUtil.DEFAULT_WALK_TREE_OPTIONS_ARRAY)) { + var3 = stream.map(path -> { + if (Files.isRegularFile(path) && path.toString().endsWith(".prefab.json")) { + try { + IPrefabBuffer prefab = PrefabBufferUtil.getCached(path); + + String var4; + try { + String results = validate(prefab, options); + var4 = results != null ? path + "\n" + results : null; + } finally { + prefab.release(); + } + + return var4; + } catch (Throwable var9) { + return path + "\n\t" + ExceptionUtil.combineMessages(var9, "\n\t"); + } + } else { + return null; + } + }).filter(Objects::nonNull).collect(Collectors.toList()); + } + + return var3; + } catch (IOException var7) { + throw SneakyThrow.sneakyThrow(var7); + } + } + } + + @Nullable + public static String validate(@Nonnull IPrefabBuffer prefab, @Nonnull Set options) { + ComponentType> unknownComponentType = EntityStore.REGISTRY.getUnknownComponentType(); + StringBuilder sb = new StringBuilder(); + int offsetX = prefab.getAnchorX(); + int offsetY = prefab.getAnchorY(); + int offsetZ = prefab.getAnchorZ(); + IPrefabBuffer.RawBlockConsumer legacyValidator = WorldValidationUtil.blockValidator(offsetX, offsetY, offsetZ, sb, options); + prefab.forEachRaw( + IPrefabBuffer.iterateAllColumns(), + (x, y, z, mask, blockId, chance, holder, supportValue, rotation, filler, o) -> { + legacyValidator.accept(x, y, z, mask, blockId, chance, holder, supportValue, rotation, filler, o); + if (options.contains(ValidationOption.BLOCK_FILLER)) { + FillerBlockUtil.ValidationResult fillerResult = FillerBlockUtil.validateBlock(x, y, z, blockId, rotation, filler, prefab, null, FILLER_FETCHER); + switch (fillerResult) { + case OK: + default: + break; + case INVALID_BLOCK: { + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + sb.append("\tBlock ") + .append(blockType != null ? blockType.getId() : "") + .append(" at ") + .append(x) + .append(", ") + .append(y) + .append(", ") + .append(z) + .append(" is not valid filler") + .append('\n'); + break; + } + case INVALID_FILLER: { + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + sb.append("\tBlock ") + .append(blockType != null ? blockType.getId() : "") + .append(" at ") + .append(x) + .append(", ") + .append(y) + .append(", ") + .append(z) + .append(" has invalid/missing filler blocks") + .append('\n'); + } + } + } + }, + (x, y, z, fluidId, level, unused) -> {}, + (x, z, holders, o) -> { + if (holders != null) { + if (options.contains(ValidationOption.ENTITIES)) { + for (Holder entityHolder : holders) { + UnknownComponents unknownComponents = entityHolder.getComponent(unknownComponentType); + if (unknownComponents != null && !unknownComponents.getUnknownComponents().isEmpty()) { + sb.append("\tUnknown Entity Components: ").append(unknownComponents.getUnknownComponents()).append("\n"); + } + } + } + } + }, + (Void)null + ); + return !sb.isEmpty() ? sb.toString() : null; + } +} diff --git a/src/com/hypixel/hytale/builtin/blockphysics/WorldValidationUtil.java b/src/com/hypixel/hytale/builtin/blockphysics/WorldValidationUtil.java new file mode 100644 index 0000000..94ac636 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blockphysics/WorldValidationUtil.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.blockphysics; + +import com.hypixel.hytale.component.data.unknown.UnknownComponents; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.IPrefabBuffer; +import com.hypixel.hytale.server.core.universe.world.ValidationOption; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.Set; +import javax.annotation.Nonnull; + +public class WorldValidationUtil { + public WorldValidationUtil() { + } + + @Nonnull + public static IPrefabBuffer.RawBlockConsumer blockValidator(@Nonnull StringBuilder sb, @Nonnull Set options) { + return blockValidator(0, 0, 0, sb, options); + } + + @Nonnull + public static IPrefabBuffer.RawBlockConsumer blockValidator( + int offsetX, int offsetY, int offsetZ, @Nonnull StringBuilder sb, @Nonnull Set options + ) { + return (x, y, z, mask, blockId, chance, holder, support, rotation, filler, aVoid) -> { + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + if (options.contains(ValidationOption.PHYSICS)) { + } + + if (options.contains(ValidationOption.BLOCKS) && (blockType == null || blockType.isUnknown())) { + sb.append("\tInvalid Block Type: ") + .append(blockType == null ? "null" : blockType.getId()) + .append(" at ") + .append('(') + .append(x + offsetX) + .append(',') + .append(y + offsetY) + .append(',') + .append(z + offsetZ) + .append(')') + .append('\n'); + } + + if (options.contains(ValidationOption.BLOCK_STATES) && holder != null) { + UnknownComponents unknownComponents = holder.getComponent(ChunkStore.REGISTRY.getUnknownComponentType()); + if (unknownComponents != null && !unknownComponents.getUnknownComponents().isEmpty()) { + sb.append("\tUnknown Components: ").append(holder).append("\n"); + } + } + }; + } +} diff --git a/src/com/hypixel/hytale/builtin/blockspawner/BlockSpawnerEntry.java b/src/com/hypixel/hytale/builtin/blockspawner/BlockSpawnerEntry.java new file mode 100644 index 0000000..aae0375 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blockspawner/BlockSpawnerEntry.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.builtin.blockspawner; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.store.StoredCodec; +import com.hypixel.hytale.common.map.IWeightedElement; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.prefab.config.SelectionPrefabSerializer; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; + +public class BlockSpawnerEntry implements IWeightedElement { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder(BlockSpawnerEntry.class, BlockSpawnerEntry::new) + .append(new KeyedCodec<>("Name", Codec.STRING), (entry, key) -> entry.blockName = key, entry -> entry.blockName) + .addValidatorLate(() -> BlockType.VALIDATOR_CACHE.getValidator().late()) + .add() + .append(new KeyedCodec<>("RotationMode", BlockSpawnerEntry.RotationMode.CODEC), (entry, b) -> entry.rotationMode = b, entry -> entry.rotationMode) + .add() + .append(new KeyedCodec<>("Weight", Codec.DOUBLE), (entry, d) -> entry.weight = d, entry -> entry.weight) + .add() + .append( + new KeyedCodec<>("State", Codec.BSON_DOCUMENT), + (entry, wrapper, extraInfo) -> entry.blockComponents = SelectionPrefabSerializer.legacyStateDecode(wrapper), + (entry, extraInfo) -> null + ) + .add() + .append( + new KeyedCodec<>("Components", new StoredCodec<>(ChunkStore.HOLDER_CODEC_KEY)), + (entry, holder) -> entry.blockComponents = holder, + entry -> entry.blockComponents + ) + .add() + .build(); + public static final BlockSpawnerEntry[] EMPTY_ARRAY = new BlockSpawnerEntry[0]; + private String blockName; + private Holder blockComponents; + private double weight; + private BlockSpawnerEntry.RotationMode rotationMode = BlockSpawnerEntry.RotationMode.INHERIT; + + public BlockSpawnerEntry() { + } + + public String getBlockName() { + return this.blockName; + } + + public Holder getBlockComponents() { + return this.blockComponents; + } + + public BlockSpawnerEntry.RotationMode getRotationMode() { + return this.rotationMode; + } + + @Override + public double getWeight() { + return this.weight; + } + + public static enum RotationMode { + NONE, + RANDOM, + INHERIT; + + public static final Codec CODEC = new EnumCodec<>(BlockSpawnerEntry.RotationMode.class); + + private RotationMode() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/blockspawner/BlockSpawnerPlugin.java b/src/com/hypixel/hytale/builtin/blockspawner/BlockSpawnerPlugin.java new file mode 100644 index 0000000..81b214e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blockspawner/BlockSpawnerPlugin.java @@ -0,0 +1,201 @@ +package com.hypixel.hytale.builtin.blockspawner; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.builtin.blockspawner.command.BlockSpawnerCommand; +import com.hypixel.hytale.builtin.blockspawner.state.BlockSpawner; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.data.unknown.UnknownComponents; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.VariantRotation; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockRotationUtil; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockSpawnerPlugin extends JavaPlugin { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private ComponentType blockSpawnerComponentType; + private static BlockSpawnerPlugin INSTANCE; + + public static BlockSpawnerPlugin get() { + return INSTANCE; + } + + public BlockSpawnerPlugin(@Nonnull JavaPluginInit init) { + super(init); + INSTANCE = this; + } + + @Override + protected void setup() { + this.getCommandRegistry().registerCommand(new BlockSpawnerCommand()); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + BlockSpawnerTable.class, new DefaultAssetMap() + ) + .setPath("Item/Block/Spawners")) + .setCodec(BlockSpawnerTable.CODEC)) + .setKeyFunction(BlockSpawnerTable::getId)) + .loadsAfter(Item.class, BlockType.class)) + .build() + ); + this.blockSpawnerComponentType = this.getChunkStoreRegistry().registerComponent(BlockSpawner.class, "BlockSpawner", BlockSpawner.CODEC); + this.getChunkStoreRegistry().registerSystem(new BlockSpawnerPlugin.BlockSpawnerSystem()); + this.getChunkStoreRegistry().registerSystem(new BlockSpawnerPlugin.MigrateBlockSpawner()); + } + + public ComponentType getBlockSpawnerComponentType() { + return this.blockSpawnerComponentType; + } + + private static class BlockSpawnerSystem extends RefSystem { + private static final ComponentType COMPONENT_TYPE = BlockSpawner.getComponentType(); + private static final ComponentType BLOCK_INFO_COMPONENT_TYPE = BlockModule.BlockStateInfo.getComponentType(); + private static final Query QUERY = Query.and(COMPONENT_TYPE, BLOCK_INFO_COMPONENT_TYPE); + + public BlockSpawnerSystem() { + } + + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + WorldConfig worldConfig = store.getExternalData().getWorld().getWorldConfig(); + if (worldConfig.getGameMode() != GameMode.Creative) { + BlockSpawner state = commandBuffer.getComponent(ref, COMPONENT_TYPE); + + assert state != null; + + BlockModule.BlockStateInfo info = commandBuffer.getComponent(ref, BLOCK_INFO_COMPONENT_TYPE); + + assert info != null; + + String blockSpawnerId = state.getBlockSpawnerId(); + if (blockSpawnerId != null) { + BlockSpawnerTable table = BlockSpawnerTable.getAssetMap().getAsset(blockSpawnerId); + if (table == null) { + BlockSpawnerPlugin.LOGGER.at(Level.WARNING).log("Failed to find BlockSpawner Asset by name: %s", blockSpawnerId); + } else { + Ref chunk = info.getChunkRef(); + if (chunk != null) { + WorldChunk wc = commandBuffer.getComponent(chunk, WorldChunk.getComponentType()); + int x = ChunkUtil.worldCoordFromLocalCoord(wc.getX(), ChunkUtil.xFromBlockInColumn(info.getIndex())); + int y = ChunkUtil.yFromBlockInColumn(info.getIndex()); + int z = ChunkUtil.worldCoordFromLocalCoord(wc.getZ(), ChunkUtil.zFromBlockInColumn(info.getIndex())); + long seed = worldConfig.getSeed(); + double randomRnd = HashUtil.random(x, y, z, seed + -1699164769L); + BlockSpawnerEntry entry = table.getEntries().get(randomRnd); + if (entry != null) { + String blockKey = entry.getBlockName(); + + RotationTuple rotation = switch (entry.getRotationMode()) { + case NONE -> RotationTuple.NONE; + case RANDOM -> { + String key = entry.getBlockName(); + VariantRotation variantRotation = BlockType.getAssetMap().getAsset(key).getVariantRotation(); + if (variantRotation == VariantRotation.None) { + yield RotationTuple.NONE; + } else { + int randomHash = (int)HashUtil.rehash(x, y, z, seed + -1699164769L); + Rotation rotationYaw = Rotation.NORMAL[(randomHash & 65535) % Rotation.NORMAL.length]; + yield BlockRotationUtil.getRotated(RotationTuple.NONE, Axis.Y, rotationYaw, variantRotation); + } + } + case INHERIT -> { + String key = entry.getBlockName(); + VariantRotation variantRotation = BlockType.getAssetMap().getAsset(key).getVariantRotation(); + if (variantRotation == VariantRotation.None) { + yield RotationTuple.NONE; + } else { + RotationTuple spawnerRotation = RotationTuple.get(wc.getRotationIndex(x, y, z)); + Rotation spawnerYaw = spawnerRotation.yaw(); + yield BlockRotationUtil.getRotated(RotationTuple.NONE, Axis.Y, spawnerYaw, variantRotation); + } + } + }; + Holder holder = entry.getBlockComponents(); + commandBuffer.removeEntity(ref, RemoveReason.REMOVE); + commandBuffer.run(_store -> { + int flags = 4; + if (holder != null) { + flags |= 2; + } + + int blockId = BlockType.getAssetMap().getIndex(blockKey); + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + wc.setBlock(x, y, z, blockId, blockType, rotation.index(), 0, flags); + if (holder != null) { + wc.setState(x, y, z, holder.clone()); + } + }); + } + } + } + } + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + } + + @Deprecated(forRemoval = true) + public static class MigrateBlockSpawner extends BlockModule.MigrationSystem { + public MigrateBlockSpawner() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + UnknownComponents unknown = holder.getComponent(ChunkStore.REGISTRY.getUnknownComponentType()); + + assert unknown != null; + + BlockSpawner blockSpawner = unknown.removeComponent("blockspawner", BlockSpawner.CODEC); + if (blockSpawner != null) { + holder.putComponent(BlockSpawner.getComponentType(), blockSpawner); + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nullable + @Override + public Query getQuery() { + return ChunkStore.REGISTRY.getUnknownComponentType(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/blockspawner/BlockSpawnerTable.java b/src/com/hypixel/hytale/builtin/blockspawner/BlockSpawnerTable.java new file mode 100644 index 0000000..b745d5c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blockspawner/BlockSpawnerTable.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.builtin.blockspawner; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDefaultCollapsedState; +import com.hypixel.hytale.common.map.IWeightedMap; +import com.hypixel.hytale.common.map.WeightedMap; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.codec.WeightedMapCodec; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockSpawnerTable implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + BlockSpawnerTable.class, + BlockSpawnerTable::new, + Codec.STRING, + (blockSpawnerTable, id) -> blockSpawnerTable.id = id, + blockSpawnerTable -> blockSpawnerTable.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("Entries", new WeightedMapCodec<>(BlockSpawnerEntry.CODEC, BlockSpawnerEntry.EMPTY_ARRAY)), + (blockSpawnerTable, o) -> blockSpawnerTable.entries = o, + blockSpawnerTable -> blockSpawnerTable.entries, + (blockSpawnerTable, parent) -> blockSpawnerTable.entries = WeightedMap.builder(BlockSpawnerEntry.EMPTY_ARRAY).putAll(parent.entries).build() + ) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .validator((asset, results) -> { + for (BlockSpawnerEntry entry : asset.getEntries().internalKeys()) { + if (BlockType.getAssetMap().getIndex(entry.getBlockName()) == Integer.MIN_VALUE) { + results.fail("BlockName \"" + entry.getBlockName() + "\" does not exist in BlockSpawnerEntry"); + } + } + }) + .build(); + private static DefaultAssetMap ASSET_MAP; + protected AssetExtraInfo.Data data; + protected String id; + protected IWeightedMap entries; + + public static DefaultAssetMap getAssetMap() { + if (ASSET_MAP == null) { + ASSET_MAP = (DefaultAssetMap)AssetRegistry.getAssetStore(BlockSpawnerTable.class).getAssetMap(); + } + + return ASSET_MAP; + } + + public BlockSpawnerTable(String id, @Nullable IWeightedMap entries) { + this.id = id; + this.entries = entries == null ? WeightedMap.builder(BlockSpawnerEntry.EMPTY_ARRAY).build() : entries; + } + + protected BlockSpawnerTable() { + } + + public String getId() { + return this.id; + } + + public IWeightedMap getEntries() { + return this.entries; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + BlockSpawnerTable table = (BlockSpawnerTable)o; + if (this.id != null ? this.id.equals(table.id) : table.id == null) { + return this.entries != null ? this.entries.equals(table.entries) : table.entries == null; + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.id != null ? this.id.hashCode() : 0; + return 31 * result + (this.entries != null ? this.entries.hashCode() : 0); + } + + @Nonnull + @Override + public String toString() { + return "BlockSpawnerTable{id='" + this.id + "'}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/blockspawner/command/BlockSpawnerCommand.java b/src/com/hypixel/hytale/builtin/blockspawner/command/BlockSpawnerCommand.java new file mode 100644 index 0000000..a3174fd --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blockspawner/command/BlockSpawnerCommand.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.builtin.blockspawner.command; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class BlockSpawnerCommand extends AbstractCommandCollection { + public BlockSpawnerCommand() { + super("blockspawner", "server.commands.blockspawner.desc"); + this.addSubCommand(new BlockSpawnerSetCommand()); + this.addSubCommand(new BlockSpawnerGetCommand()); + } +} diff --git a/src/com/hypixel/hytale/builtin/blockspawner/command/BlockSpawnerGetCommand.java b/src/com/hypixel/hytale/builtin/blockspawner/command/BlockSpawnerGetCommand.java new file mode 100644 index 0000000..c0508c0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blockspawner/command/BlockSpawnerGetCommand.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.builtin.blockspawner.command; + +import com.hypixel.hytale.builtin.blockspawner.state.BlockSpawner; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeIntPosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import javax.annotation.Nonnull; + +public class BlockSpawnerGetCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_GENERAL_BLOCK_TARGET_NOT_IN_RANGE = Message.translation("server.general.blockTargetNotInRange"); + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PROVIDE_POSITION = Message.translation("server.commands.errors.providePosition"); + @Nonnull + private static final Message MESSAGE_COMMANDS_BLOCK_SPAWNER_NO_BLOCK_SPAWNER_SET = Message.translation("server.commands.blockspawner.noBlockSpawnerSet"); + @Nonnull + private final OptionalArg positionArg = this.withOptionalArg( + "position", "server.commands.blockspawner.position.desc", ArgTypes.RELATIVE_BLOCK_POSITION + ); + + public BlockSpawnerGetCommand() { + super("get", "server.commands.blockspawner.get.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Vector3i position; + if (this.positionArg.provided(context)) { + RelativeIntPosition relativePosition = this.positionArg.get(context); + position = relativePosition.getBlockPosition(context, store); + } else { + if (!context.isPlayer()) { + throw new GeneralCommandException(MESSAGE_COMMANDS_ERRORS_PROVIDE_POSITION); + } + + Ref ref = context.senderAsPlayerRef(); + Vector3i targetBlock = TargetUtil.getTargetBlock(ref, 10.0, store); + if (targetBlock == null) { + throw new GeneralCommandException(MESSAGE_GENERAL_BLOCK_TARGET_NOT_IN_RANGE); + } + + position = targetBlock; + } + + ChunkStore chunkStore = world.getChunkStore(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(position.x, position.z); + Ref chunkRef = chunkStore.getChunkReference(chunkIndex); + if (chunkRef != null && chunkRef.isValid()) { + WorldChunk worldChunkComponent = chunkStore.getStore().getComponent(chunkRef, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + Ref blockRef = worldChunkComponent.getBlockComponentEntity(position.x, position.y, position.z); + if (blockRef == null) { + context.sendMessage(Message.translation("server.general.containerNotFound").param("block", position.toString())); + } else { + BlockSpawner spawnerState = chunkStore.getStore().getComponent(blockRef, BlockSpawner.getComponentType()); + if (spawnerState == null) { + context.sendMessage(Message.translation("server.general.containerNotFound").param("block", position.toString())); + } else { + if (spawnerState.getBlockSpawnerId() == null) { + context.sendMessage(MESSAGE_COMMANDS_BLOCK_SPAWNER_NO_BLOCK_SPAWNER_SET); + } else { + context.sendMessage(Message.translation("server.commands.blockspawner.currentBlockSpawner").param("id", spawnerState.getBlockSpawnerId())); + } + } + } + } else { + context.sendMessage(Message.translation("server.general.containerNotFound").param("block", position.toString())); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/blockspawner/command/BlockSpawnerSetCommand.java b/src/com/hypixel/hytale/builtin/blockspawner/command/BlockSpawnerSetCommand.java new file mode 100644 index 0000000..de3885a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blockspawner/command/BlockSpawnerSetCommand.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.builtin.blockspawner.command; + +import com.hypixel.hytale.builtin.blockspawner.BlockSpawnerTable; +import com.hypixel.hytale.builtin.blockspawner.state.BlockSpawner; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.AssetArgumentType; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeIntPosition; +import com.hypixel.hytale.server.core.command.system.arguments.types.SingleArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import javax.annotation.Nonnull; + +public class BlockSpawnerSetCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_GENERAL_BLOCK_TARGET_NOT_IN_RANGE = Message.translation("server.general.blockTargetNotInRange"); + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PROVIDE_POSITION = Message.translation("server.commands.errors.providePosition"); + @Nonnull + private static final SingleArgumentType BLOCK_SPAWNER_ASSET_TYPE = new AssetArgumentType( + "server.commands.parsing.argtype.asset.blockspawnertable.name", BlockSpawnerTable.class, "server.commands.parsing.argtype.asset.blockspawnertable.usage" + ); + @Nonnull + private final RequiredArg blockSpawnerIdArg = this.withRequiredArg( + "blockSpawnerId", "server.commands.blockspawner.set.blockSpawnerId.desc", BLOCK_SPAWNER_ASSET_TYPE + ); + @Nonnull + private final OptionalArg positionArg = this.withOptionalArg( + "position", "server.commands.blockspawner.position.desc", ArgTypes.RELATIVE_BLOCK_POSITION + ); + @Nonnull + private final FlagArg ignoreChecksFlag = this.withFlagArg("ignoreChecks", "server.commands.blockspawner.arg.ignoreChecks"); + + public BlockSpawnerSetCommand() { + super("set", "server.commands.blockspawner.set.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Vector3i position; + if (this.positionArg.provided(context)) { + RelativeIntPosition relativePosition = this.positionArg.get(context); + position = relativePosition.getBlockPosition(context, store); + } else { + if (!context.isPlayer()) { + throw new GeneralCommandException(MESSAGE_COMMANDS_ERRORS_PROVIDE_POSITION); + } + + Ref ref = context.senderAsPlayerRef(); + Vector3i targetBlock = TargetUtil.getTargetBlock(ref, 10.0, store); + if (targetBlock == null) { + throw new GeneralCommandException(MESSAGE_GENERAL_BLOCK_TARGET_NOT_IN_RANGE); + } + + position = targetBlock; + } + + WorldChunk chunk = world.getChunk(ChunkUtil.indexChunkFromBlock(position.x, position.z)); + Ref blockRef = chunk.getBlockComponentEntity(position.x, position.y, position.z); + if (blockRef == null) { + context.sendMessage(Message.translation("server.general.containerNotFound").param("block", position.toString())); + } else { + BlockSpawner spawnerState = world.getChunkStore().getStore().getComponent(blockRef, BlockSpawner.getComponentType()); + if (spawnerState == null) { + context.sendMessage(Message.translation("server.general.containerNotFound").param("block", position.toString())); + } else { + String spawnerId; + if (this.ignoreChecksFlag.get(context)) { + String[] input = context.getInput(this.blockSpawnerIdArg); + spawnerId = input != null && input.length > 0 ? input[0] : null; + if (spawnerId == null) { + context.sendMessage( + Message.translation("errors.validation_failure").param("message", "blockSpawnerId is required when --ignoreChecks is set") + ); + return; + } + } else { + spawnerId = this.blockSpawnerIdArg.get(context).getId(); + } + + spawnerState.setBlockSpawnerId(spawnerId); + chunk.markNeedsSaving(); + context.sendMessage(Message.translation("server.commands.blockspawner.blockSpawnerSet").param("id", spawnerId)); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/blockspawner/state/BlockSpawner.java b/src/com/hypixel/hytale/builtin/blockspawner/state/BlockSpawner.java new file mode 100644 index 0000000..66b7958 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blockspawner/state/BlockSpawner.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.builtin.blockspawner.state; + +import com.hypixel.hytale.builtin.blockspawner.BlockSpawnerPlugin; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockSpawner implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockSpawner.class, BlockSpawner::new) + .addField(new KeyedCodec<>("BlockSpawnerId", Codec.STRING), (state, s) -> state.blockSpawnerId = s, state -> state.blockSpawnerId) + .build(); + private String blockSpawnerId; + + public static ComponentType getComponentType() { + return BlockSpawnerPlugin.get().getBlockSpawnerComponentType(); + } + + public BlockSpawner() { + } + + public BlockSpawner(String blockSpawnerId) { + this.blockSpawnerId = blockSpawnerId; + } + + public String getBlockSpawnerId() { + return this.blockSpawnerId; + } + + public void setBlockSpawnerId(String blockSpawnerId) { + this.blockSpawnerId = blockSpawnerId; + } + + @Nonnull + @Override + public String toString() { + return "BlockSpawnerState{blockSpawnerId='" + this.blockSpawnerId + "'} " + super.toString(); + } + + @Nullable + @Override + public Component clone() { + return new BlockSpawner(this.blockSpawnerId); + } +} diff --git a/src/com/hypixel/hytale/builtin/blocktick/BlockTickPlugin.java b/src/com/hypixel/hytale/builtin/blocktick/BlockTickPlugin.java new file mode 100644 index 0000000..06445e1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blocktick/BlockTickPlugin.java @@ -0,0 +1,99 @@ +package com.hypixel.hytale.builtin.blocktick; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.builtin.blocktick.procedure.BasicChanceBlockGrowthProcedure; +import com.hypixel.hytale.builtin.blocktick.procedure.SplitChanceBlockGrowthProcedure; +import com.hypixel.hytale.builtin.blocktick.system.ChunkBlockTickSystem; +import com.hypixel.hytale.builtin.blocktick.system.MergeWaitingBlocksSystem; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.event.EventPriority; +import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickManager; +import com.hypixel.hytale.server.core.asset.type.blocktick.IBlockTickProvider; +import com.hypixel.hytale.server.core.asset.type.blocktick.config.TickProcedure; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.events.ChunkPreLoadProcessEvent; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; + +public class BlockTickPlugin extends JavaPlugin implements IBlockTickProvider { + private static BlockTickPlugin instance; + + public static BlockTickPlugin get() { + return instance; + } + + public BlockTickPlugin(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + TickProcedure.CODEC.register("BasicChance", BasicChanceBlockGrowthProcedure.class, BasicChanceBlockGrowthProcedure.CODEC); + TickProcedure.CODEC.register("SplitChance", SplitChanceBlockGrowthProcedure.class, SplitChanceBlockGrowthProcedure.CODEC); + this.getEventRegistry().registerGlobal(EventPriority.EARLY, ChunkPreLoadProcessEvent.class, this::discoverTickingBlocks); + ChunkStore.REGISTRY.registerSystem(new ChunkBlockTickSystem.PreTick()); + ChunkStore.REGISTRY.registerSystem(new ChunkBlockTickSystem.Ticking()); + ChunkStore.REGISTRY.registerSystem(new MergeWaitingBlocksSystem()); + BlockTickManager.setBlockTickProvider(this); + } + + @Override + public TickProcedure getTickProcedure(int blockId) { + return BlockType.getAssetMap().getAsset(blockId).getTickProcedure(); + } + + private void discoverTickingBlocks(@Nonnull ChunkPreLoadProcessEvent event) { + if (event.isNewlyGenerated()) { + this.discoverTickingBlocks(event.getHolder(), event.getChunk()); + } + } + + public int discoverTickingBlocks(@Nonnull Holder holder, @Nonnull WorldChunk chunk) { + if (!this.isEnabled()) { + return 0; + } else { + BlockChunk bc = chunk.getBlockChunk(); + if (!bc.consumeNeedsPhysics()) { + return 0; + } else { + ChunkColumn column = holder.getComponent(ChunkColumn.getComponentType()); + if (column == null) { + return 0; + } else { + Holder[] sections = column.getSectionHolders(); + if (sections == null) { + return 0; + } else { + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + int count = 0; + + for (int i = 0; i < sections.length; i++) { + Holder sectionHolder = sections[i]; + BlockSection section = sectionHolder.ensureAndGetComponent(BlockSection.getComponentType()); + if (!section.isSolidAir()) { + for (int blockIdx = 0; blockIdx < 32768; blockIdx++) { + int blockId = section.get(blockIdx); + BlockType blockType = assetMap.getAsset(blockId); + if (blockType != null && blockType.getTickProcedure() != null) { + section.setTicking(blockIdx, true); + bc.markNeedsSaving(); + count++; + } + } + } + } + + return count; + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/blocktick/procedure/BasicChanceBlockGrowthProcedure.java b/src/com/hypixel/hytale/builtin/blocktick/procedure/BasicChanceBlockGrowthProcedure.java new file mode 100644 index 0000000..6c24dc5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blocktick/procedure/BasicChanceBlockGrowthProcedure.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.builtin.blocktick.procedure; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy; +import com.hypixel.hytale.server.core.asset.type.blocktick.config.TickProcedure; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import javax.annotation.Nonnull; + +public class BasicChanceBlockGrowthProcedure extends TickProcedure { + public static final BuilderCodec CODEC = BuilderCodec.builder( + BasicChanceBlockGrowthProcedure.class, BasicChanceBlockGrowthProcedure::new, TickProcedure.BASE_CODEC + ) + .addField(new KeyedCodec<>("NextId", Codec.STRING), (proc, v) -> proc.to = v, proc -> proc.to) + .addField(new KeyedCodec<>("ChanceMin", Codec.INTEGER), (proc, v) -> proc.chanceMin = v, proc -> proc.chanceMin) + .addField(new KeyedCodec<>("Chance", Codec.INTEGER), (proc, v) -> proc.chance = v, proc -> proc.chance) + .addField(new KeyedCodec<>("NextTicking", Codec.BOOLEAN), (proc, v) -> proc.nextTicking = v, proc -> proc.nextTicking) + .build(); + protected int chanceMin; + protected int chance; + protected String to; + protected boolean nextTicking; + + public BasicChanceBlockGrowthProcedure() { + } + + public BasicChanceBlockGrowthProcedure(int chanceMin, int chance, String to, boolean nextTicking) { + this.chanceMin = chanceMin; + this.chance = chance; + this.to = to; + this.nextTicking = nextTicking; + } + + @Nonnull + @Override + public BlockTickStrategy onTick(@Nonnull World world, WorldChunk wc, int worldX, int worldY, int worldZ, int blockId) { + if (!this.runChance()) { + return BlockTickStrategy.CONTINUE; + } else { + return this.executeToBlock(world, worldX, worldY, worldZ, this.to) ? BlockTickStrategy.CONTINUE : BlockTickStrategy.SLEEP; + } + } + + protected boolean runChance() { + return this.getRandom().nextInt(this.chance) < this.chanceMin; + } + + protected boolean executeToBlock(@Nonnull World world, int worldX, int worldY, int worldZ, String to) { + world.setBlock(worldX, worldY, worldZ, to); + return !this.nextTicking; + } + + @Nonnull + @Override + public String toString() { + return "BasicChanceBlockGrowthProcedure{chanceMin=" + + this.chanceMin + + ", chance=" + + this.chance + + ", to=" + + this.to + + ", nextTicking=" + + this.nextTicking + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/blocktick/procedure/SplitChanceBlockGrowthProcedure.java b/src/com/hypixel/hytale/builtin/blocktick/procedure/SplitChanceBlockGrowthProcedure.java new file mode 100644 index 0000000..c706528 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blocktick/procedure/SplitChanceBlockGrowthProcedure.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.builtin.blocktick.procedure; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.RandomUtil; +import com.hypixel.hytale.server.core.asset.type.blocktick.config.TickProcedure; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.Arrays; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class SplitChanceBlockGrowthProcedure extends BasicChanceBlockGrowthProcedure { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SplitChanceBlockGrowthProcedure.class, SplitChanceBlockGrowthProcedure::new, TickProcedure.BASE_CODEC + ) + .append(new KeyedCodec<>("NextIds", Codec.BSON_DOCUMENT), (proc, v, extraInfo) -> { + proc.data = new String[v.size()]; + proc.chances = new int[proc.data.length]; + int i = 0; + + for (Entry entry : v.entrySet()) { + proc.data[i] = entry.getKey(); + proc.chances[i] = Codec.INTEGER.decode(entry.getValue(), extraInfo); + proc.sumChances = proc.sumChances + proc.chances[i]; + i++; + } + }, (proc, extraInfo) -> { + if (proc.data != null && proc.chances != null) { + BsonDocument document = new BsonDocument(); + + for (int i = 0; i < proc.data.length; i++) { + document.append(proc.data[i], Codec.INTEGER.encode(proc.chances[i], extraInfo)); + } + + return document; + } else { + return null; + } + }) + .addValidator(Validators.nonNull()) + .add() + .addField(new KeyedCodec<>("ChanceMin", Codec.INTEGER), (proc, v) -> proc.chanceMin = v, proc -> proc.chanceMin) + .addField(new KeyedCodec<>("Data", Codec.INTEGER), (proc, v) -> proc.chance = v, proc -> proc.chance) + .addField(new KeyedCodec<>("NextTicking", Codec.BOOLEAN), (proc, v) -> proc.nextTicking = v, proc -> proc.nextTicking) + .build(); + protected int[] chances; + protected String[] data; + protected int sumChances; + + public SplitChanceBlockGrowthProcedure() { + } + + public SplitChanceBlockGrowthProcedure(int chanceMin, int chance, @Nonnull int[] chances, @Nonnull String[] data, boolean nextTicking) { + super(chanceMin, chance, null, nextTicking); + this.chances = chances; + this.data = data; + if (chances.length != data.length) { + throw new IllegalArgumentException(String.valueOf(data.length)); + } else { + int localSumChances = 0; + + for (int c : chances) { + if (c < 0) { + throw new IllegalArgumentException(String.valueOf(c)); + } + + localSumChances += c; + } + + this.sumChances = localSumChances; + } + } + + @Override + protected boolean executeToBlock(@Nonnull World world, int worldX, int worldY, int worldZ, String to) { + String block = RandomUtil.roll(this.getRandom().nextInt(this.sumChances), this.data, this.chances); + return super.executeToBlock(world, worldX, worldY, worldZ, block); + } + + @Nonnull + @Override + public String toString() { + return "SplitChanceBlockGrowthProcedure{chanceMin=" + + this.chanceMin + + ", chance=" + + this.chance + + ", to=" + + this.to + + ", nextTicking=" + + this.nextTicking + + ", chances=" + + Arrays.toString(this.chances) + + ", data=" + + Arrays.toString((Object[])this.data) + + ", sumChances=" + + this.sumChances + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/blocktick/system/ChunkBlockTickSystem.java b/src/com/hypixel/hytale/builtin/blocktick/system/ChunkBlockTickSystem.java new file mode 100644 index 0000000..e7d6449 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blocktick/system/ChunkBlockTickSystem.java @@ -0,0 +1,142 @@ +package com.hypixel.hytale.builtin.blocktick.system; + +import com.hypixel.hytale.builtin.blocktick.BlockTickPlugin; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickManager; +import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy; +import com.hypixel.hytale.server.core.asset.type.blocktick.config.TickProcedure; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.time.Instant; +import java.util.Set; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class ChunkBlockTickSystem { + protected static final HytaleLogger LOGGER = BlockTickPlugin.get().getLogger(); + + public ChunkBlockTickSystem() { + } + + public static class PreTick extends EntityTickingSystem { + private static final ComponentType COMPONENT_TYPE = BlockChunk.getComponentType(); + + public PreTick() { + } + + @Override + public Query getQuery() { + return COMPONENT_TYPE; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Instant time = commandBuffer.getExternalData().getWorld().getEntityStore().getStore().getResource(WorldTimeResource.getResourceType()).getGameTime(); + BlockChunk chunk = archetypeChunk.getComponent(index, COMPONENT_TYPE); + + assert chunk != null; + + try { + chunk.preTick(time); + } catch (Throwable var9) { + ChunkBlockTickSystem.LOGGER.at(Level.SEVERE).withCause(var9).log("Failed to pre-tick chunk: %s", chunk); + } + } + } + + public static class Ticking extends EntityTickingSystem { + private static final ComponentType COMPONENT_TYPE = WorldChunk.getComponentType(); + private static final Set> DEPENDENCIES = Set.of(new SystemDependency<>(Order.AFTER, ChunkBlockTickSystem.PreTick.class)); + + public Ticking() { + } + + @Override + public Query getQuery() { + return COMPONENT_TYPE; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Ref reference = archetypeChunk.getReferenceTo(index); + WorldChunk worldChunk = archetypeChunk.getComponent(index, COMPONENT_TYPE); + + try { + tick(reference, worldChunk); + } catch (Throwable var9) { + ChunkBlockTickSystem.LOGGER.at(Level.SEVERE).withCause(var9).log("Failed to tick chunk: %s", worldChunk); + } + } + + protected static void tick(Ref ref, @Nonnull WorldChunk worldChunk) { + int ticked = worldChunk.getBlockChunk().forEachTicking(ref, worldChunk, (r, c, localX, localY, localZ, blockId) -> { + World world = c.getWorld(); + int blockX = c.getX() << 5 | localX; + int blockZ = c.getZ() << 5 | localZ; + return tickProcedure(world, c, blockX, localY, blockZ, blockId); + }); + if (ticked > 0) { + ChunkBlockTickSystem.LOGGER.at(Level.FINER).log("Ticked %d blocks in chunk (%d, %d)", ticked, worldChunk.getX(), worldChunk.getZ()); + } + } + + protected static BlockTickStrategy tickProcedure(@Nonnull World world, @Nonnull WorldChunk chunk, int blockX, int blockY, int blockZ, int blockId) { + if (world.getWorldConfig().isBlockTicking() && BlockTickManager.hasBlockTickProvider()) { + TickProcedure procedure = BlockTickPlugin.get().getTickProcedure(blockId); + if (procedure == null) { + return BlockTickStrategy.IGNORED; + } else { + try { + return procedure.onTick(world, chunk, blockX, blockY, blockZ, blockId); + } catch (Throwable var9) { + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + ChunkBlockTickSystem.LOGGER + .at(Level.WARNING) + .withCause(var9) + .log("Failed to tick block at (%d, %d, %d) ID %s in world %s:", blockX, blockY, blockZ, blockType.getId(), world.getName()); + return BlockTickStrategy.SLEEP; + } + } + } else { + return BlockTickStrategy.IGNORED; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/blocktick/system/MergeWaitingBlocksSystem.java b/src/com/hypixel/hytale/builtin/blocktick/system/MergeWaitingBlocksSystem.java new file mode 100644 index 0000000..9ec4142 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/blocktick/system/MergeWaitingBlocksSystem.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.blocktick.system; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; + +public class MergeWaitingBlocksSystem extends RefSystem { + private static final ComponentType COMPONENT_TYPE = WorldChunk.getComponentType(); + + public MergeWaitingBlocksSystem() { + } + + @Override + public Query getQuery() { + return COMPONENT_TYPE; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + ChunkStore chunkStore = store.getExternalData(); + WorldChunk chunk = store.getComponent(ref, COMPONENT_TYPE); + int x = chunk.getX(); + int z = chunk.getZ(); + mergeTickingBlocks(chunkStore, x - 1, z); + mergeTickingBlocks(chunkStore, x + 1, z); + mergeTickingBlocks(chunkStore, x, z - 1); + mergeTickingBlocks(chunkStore, x, z + 1); + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + public static void mergeTickingBlocks(@Nonnull ChunkStore store, int x, int z) { + BlockChunk blockChunk = store.getChunkComponent(ChunkUtil.indexChunk(x, z), BlockChunk.getComponentType()); + if (blockChunk != null) { + blockChunk.mergeTickingBlocks(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/BlockColorIndex.java b/src/com/hypixel/hytale/builtin/buildertools/BlockColorIndex.java new file mode 100644 index 0000000..5028a65 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/BlockColorIndex.java @@ -0,0 +1,194 @@ +package com.hypixel.hytale.builtin.buildertools; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.DrawType; +import com.hypixel.hytale.protocol.Opacity; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class BlockColorIndex { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final List entries = new ArrayList<>(); + private boolean initialized = false; + + public BlockColorIndex() { + } + + private void ensureInitialized() { + if (!this.initialized) { + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + + for (String key : assetMap.getAssetMap().keySet()) { + BlockType blockType = assetMap.getAsset(key); + if (blockType != null && this.isSolidCube(blockType)) { + Color particleColor = blockType.getParticleColor(); + if (particleColor != null) { + int blockId = assetMap.getIndex(key); + int r = particleColor.red & 255; + int g = particleColor.green & 255; + int b = particleColor.blue & 255; + double[] lab = rgbToLab(r, g, b); + this.entries.add(new BlockColorIndex.BlockColorEntry(blockId, key, r, g, b, lab[0], lab[1], lab[2])); + } + } + } + + this.entries.sort(Comparator.comparingDouble(e -> e.labL)); + this.initialized = true; + LOGGER.at(Level.INFO).log("BlockColorIndex initialized with %d solid cube blocks", this.entries.size()); + } + } + + private boolean isSolidCube(@Nonnull BlockType blockType) { + DrawType drawType = blockType.getDrawType(); + Opacity opacity = blockType.getOpacity(); + return drawType == DrawType.Cube && opacity == Opacity.Solid; + } + + public int findClosestBlock(int r, int g, int b) { + this.ensureInitialized(); + if (this.entries.isEmpty()) { + return -1; + } else { + double[] lab = rgbToLab(r, g, b); + double targetL = lab[0]; + double targetA = lab[1]; + double targetB = lab[2]; + double minDist = Double.MAX_VALUE; + int bestId = -1; + + for (BlockColorIndex.BlockColorEntry entry : this.entries) { + double dist = colorDistanceLab(targetL, targetA, targetB, entry.labL, entry.labA, entry.labB); + if (dist < minDist) { + minDist = dist; + bestId = entry.blockId; + } + } + + return bestId; + } + } + + public int findDarkerVariant(int blockId, float darkenAmount) { + this.ensureInitialized(); + BlockColorIndex.BlockColorEntry source = this.findEntry(blockId); + if (source == null) { + return blockId; + } else { + double targetL = source.labL * (1.0 - darkenAmount); + double targetA = source.labA; + double targetB = source.labB; + double minDist = Double.MAX_VALUE; + int bestId = blockId; + + for (BlockColorIndex.BlockColorEntry entry : this.entries) { + if (!(entry.labL > source.labL)) { + double dist = colorDistanceLab(targetL, targetA, targetB, entry.labL, entry.labA, entry.labB); + if (dist < minDist) { + minDist = dist; + bestId = entry.blockId; + } + } + } + + return bestId; + } + } + + public int getBlockColor(int blockId) { + this.ensureInitialized(); + BlockColorIndex.BlockColorEntry entry = this.findEntry(blockId); + return entry == null ? -1 : entry.r << 16 | entry.g << 8 | entry.b; + } + + public int findBlockForLerpedColor(int rA, int gA, int bA, int rB, int gB, int bB, float t) { + this.ensureInitialized(); + double[] labA = rgbToLab(rA, gA, bA); + double[] labB = rgbToLab(rB, gB, bB); + double l = labA[0] + (labB[0] - labA[0]) * t; + double a = labA[1] + (labB[1] - labA[1]) * t; + double b = labA[2] + (labB[2] - labA[2]) * t; + int[] rgb = labToRgb(l, a, b); + return this.findClosestBlock(rgb[0], rgb[1], rgb[2]); + } + + public boolean isEmpty() { + this.ensureInitialized(); + return this.entries.isEmpty(); + } + + @Nullable + private BlockColorIndex.BlockColorEntry findEntry(int blockId) { + for (BlockColorIndex.BlockColorEntry entry : this.entries) { + if (entry.blockId == blockId) { + return entry; + } + } + + return null; + } + + private static double colorDistanceLab(double l1, double a1, double b1, double l2, double a2, double b2) { + double dL = l1 - l2; + double dA = a1 - a2; + double dB = b1 - b2; + return dL * dL + dA * dA + dB * dB; + } + + private static double[] rgbToLab(int r, int g, int b) { + double rn = r / 255.0; + double gn = g / 255.0; + double bn = b / 255.0; + rn = rn > 0.04045 ? Math.pow((rn + 0.055) / 1.055, 2.4) : rn / 12.92; + gn = gn > 0.04045 ? Math.pow((gn + 0.055) / 1.055, 2.4) : gn / 12.92; + bn = bn > 0.04045 ? Math.pow((bn + 0.055) / 1.055, 2.4) : bn / 12.92; + double x = rn * 0.4124564 + gn * 0.3575761 + bn * 0.1804375; + double y = rn * 0.2126729 + gn * 0.7151522 + bn * 0.072175; + double z = rn * 0.0193339 + gn * 0.119192 + bn * 0.9503041; + x /= 0.95047; + y /= 1.0; + z /= 1.08883; + x = x > 0.008856 ? Math.cbrt(x) : 7.787 * x + 0.13793103448275862; + y = y > 0.008856 ? Math.cbrt(y) : 7.787 * y + 0.13793103448275862; + z = z > 0.008856 ? Math.cbrt(z) : 7.787 * z + 0.13793103448275862; + double labL = 116.0 * y - 16.0; + double labA = 500.0 * (x - y); + double labB = 200.0 * (y - z); + return new double[]{labL, labA, labB}; + } + + private static int[] labToRgb(double labL, double labA, double labB) { + double y = (labL + 16.0) / 116.0; + double x = labA / 500.0 + y; + double z = y - labB / 200.0; + double x3 = x * x * x; + double y3 = y * y * y; + double z3 = z * z * z; + x = x3 > 0.008856 ? x3 : (x - 0.13793103448275862) / 7.787; + y = y3 > 0.008856 ? y3 : (y - 0.13793103448275862) / 7.787; + z = z3 > 0.008856 ? z3 : (z - 0.13793103448275862) / 7.787; + x *= 0.95047; + y *= 1.0; + z *= 1.08883; + double rn = x * 3.2404542 + y * -1.5371385 + z * -0.4985314; + double gn = x * -0.969266 + y * 1.8760108 + z * 0.041556; + double bn = x * 0.0556434 + y * -0.2040259 + z * 1.0572252; + rn = rn > 0.0031308 ? 1.055 * Math.pow(rn, 0.4166666666666667) - 0.055 : 12.92 * rn; + gn = gn > 0.0031308 ? 1.055 * Math.pow(gn, 0.4166666666666667) - 0.055 : 12.92 * gn; + bn = bn > 0.0031308 ? 1.055 * Math.pow(bn, 0.4166666666666667) - 0.055 : 12.92 * bn; + int r = Math.max(0, Math.min(255, (int)Math.round(rn * 255.0))); + int g = Math.max(0, Math.min(255, (int)Math.round(gn * 255.0))); + int b = Math.max(0, Math.min(255, (int)Math.round(bn * 255.0))); + return new int[]{r, g, b}; + } + + private record BlockColorEntry(int blockId, String key, int r, int g, int b, double labL, double labA, double labB) { + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/BuilderToolsPacketHandler.java b/src/com/hypixel/hytale/builtin/buildertools/BuilderToolsPacketHandler.java new file mode 100644 index 0000000..aa36388 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/BuilderToolsPacketHandler.java @@ -0,0 +1,912 @@ +package com.hypixel.hytale.builtin.buildertools; + +import com.hypixel.hytale.builtin.buildertools.commands.CopyCommand; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSession; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSessionManager; +import com.hypixel.hytale.builtin.buildertools.tooloperations.ToolOperation; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.matrix.Matrix4d; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.ColorLight; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.ModelTransform; +import com.hypixel.hytale.protocol.packets.buildertools.BrushOrigin; +import com.hypixel.hytale.protocol.packets.buildertools.BrushShape; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolAction; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArgUpdate; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolEntityAction; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolExtrudeAction; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolGeneralAction; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolLineAction; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolOnUseInteraction; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolPasteClipboard; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolRotateClipboard; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSelectionToolAskForClipboard; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSelectionToolReplyWithClipboard; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSelectionTransform; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSelectionUpdate; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSetEntityLight; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSetEntityPickupEnabled; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSetEntityScale; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSetEntityTransform; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSetNPCDebug; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSetTransformationModeState; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolStackArea; +import com.hypixel.hytale.protocol.packets.buildertools.PrefabUnselectPrefab; +import com.hypixel.hytale.protocol.packets.interface_.BlockChange; +import com.hypixel.hytale.protocol.packets.interface_.EditorBlocksChange; +import com.hypixel.hytale.protocol.packets.interface_.FluidChange; +import com.hypixel.hytale.protocol.packets.player.LoadHotbar; +import com.hypixel.hytale.protocol.packets.player.SaveHotbar; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BrushData; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BuilderTool; +import com.hypixel.hytale.server.core.command.commands.world.entity.EntityCloneCommand; +import com.hypixel.hytale.server.core.command.commands.world.entity.EntityRemoveCommand; +import com.hypixel.hytale.server.core.command.system.CommandManager; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.io.handlers.IPacketHandler; +import com.hypixel.hytale.server.core.io.handlers.SubPacketHandler; +import com.hypixel.hytale.server.core.modules.entity.component.DynamicLight; +import com.hypixel.hytale.server.core.modules.entity.component.EntityScaleComponent; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.Interactable; +import com.hypixel.hytale.server.core.modules.entity.component.PersistentDynamicLight; +import com.hypixel.hytale.server.core.modules.entity.component.PropComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.item.PreventPickup; +import com.hypixel.hytale.server.core.modules.interaction.Interactions; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.ArrayList; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class BuilderToolsPacketHandler implements SubPacketHandler { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final IPacketHandler packetHandler; + + public BuilderToolsPacketHandler(IPacketHandler packetHandler) { + this.packetHandler = packetHandler; + } + + @Override + public void registerHandlers() { + if (BuilderToolsPlugin.get().isDisabled()) { + this.packetHandler.registerNoOpHandlers(400, 401, 412, 409, 403, 406, 407, 413, 414, 417); + } else { + this.packetHandler.registerHandler(106, p -> this.handle((LoadHotbar)p)); + this.packetHandler.registerHandler(107, p -> this.handle((SaveHotbar)p)); + this.packetHandler.registerHandler(400, p -> this.handle((BuilderToolArgUpdate)p)); + this.packetHandler.registerHandler(401, p -> this.handle((BuilderToolEntityAction)p)); + this.packetHandler.registerHandler(412, p -> this.handle((BuilderToolGeneralAction)p)); + this.packetHandler.registerHandler(409, p -> this.handle((BuilderToolSelectionUpdate)p)); + this.packetHandler.registerHandler(403, p -> this.handle((BuilderToolExtrudeAction)p)); + this.packetHandler.registerHandler(406, p -> this.handle((BuilderToolRotateClipboard)p)); + this.packetHandler.registerHandler(407, p -> this.handle((BuilderToolPasteClipboard)p)); + this.packetHandler.registerHandler(413, p -> this.handle((BuilderToolOnUseInteraction)p)); + this.packetHandler.registerHandler(410, p -> this.handle((BuilderToolSelectionToolAskForClipboard)p)); + this.packetHandler.registerHandler(414, p -> this.handle((BuilderToolLineAction)p)); + this.packetHandler.registerHandler(402, p -> this.handle((BuilderToolSetEntityTransform)p)); + this.packetHandler.registerHandler(420, p -> this.handle((BuilderToolSetEntityScale)p)); + this.packetHandler.registerHandler(405, p -> this.handle((BuilderToolSelectionTransform)p)); + this.packetHandler.registerHandler(404, p -> this.handle((BuilderToolStackArea)p)); + this.packetHandler.registerHandler(408, p -> this.handle((BuilderToolSetTransformationModeState)p)); + this.packetHandler.registerHandler(417, p -> this.handle((PrefabUnselectPrefab)p)); + this.packetHandler.registerHandler(421, p -> this.handle((BuilderToolSetEntityPickupEnabled)p)); + this.packetHandler.registerHandler(422, p -> this.handle((BuilderToolSetEntityLight)p)); + this.packetHandler.registerHandler(423, p -> this.handle((BuilderToolSetNPCDebug)p)); + } + } + + static boolean hasPermission(@Nonnull Player player) { + if (!player.hasPermission("hytale.editor.builderTools")) { + player.sendMessage(Message.translation("server.builderTools.usageDenied")); + return false; + } else { + return true; + } + } + + static boolean hasPermission(@Nonnull Player player, @Nonnull String permission) { + if (!player.hasPermission(permission) && !player.hasPermission("hytale.editor.builderTools")) { + player.sendMessage(Message.translation("server.builderTools.usageDenied")); + return false; + } else { + return true; + } + } + + public void handle(@Nonnull BuilderToolSetTransformationModeState packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (hasPermission(playerComponent)) { + LOGGER.at(Level.INFO).log("%s: %s", this.packetHandler.getIdentifier(), packet); + ToolOperation.getOrCreatePrototypeSettings(playerRef.getUuid()).setInSelectionTransformationMode(packet.enabled); + } + }); + } else { + throw new RuntimeException("Unable to process BuilderToolSetTransformationModeState packet. Player ref is invalid!"); + } + } + + public void handle(@Nonnull BuilderToolArgUpdate packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (hasPermission(playerComponent, "hytale.editor.brush.config")) { + BuilderToolsPlugin.get().onToolArgUpdate(playerRef, playerComponent, packet); + } + }); + } else { + throw new RuntimeException("Unable to process BuilderToolArgUpdate packet. Player ref is invalid!"); + } + } + + public void handle(@Nonnull LoadHotbar packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getHotbarManager().loadHotbar(ref, packet.inventoryRow, store); + }); + } else { + throw new RuntimeException("Unable to process LoadHotbar packet. Player ref is invalid!"); + } + } + + public void handle(@Nonnull SaveHotbar packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getHotbarManager().saveHotbar(ref, packet.inventoryRow, store); + }); + } else { + throw new RuntimeException("Unable to process SaveHotbar packet. Player ref is invalid!"); + } + } + + public void handle(@Nonnull BuilderToolEntityAction packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (hasPermission(playerComponent)) { + int entityId = packet.entityId; + Ref entityReference = world.getEntityStore().getRefFromNetworkId(entityId); + if (entityReference == null) { + playerComponent.sendMessage(Message.translation("server.general.entityNotFound").param("id", entityId)); + } else { + LOGGER.at(Level.INFO).log("%s: %s", this.packetHandler.getIdentifier(), packet); + switch (packet.action) { + case Freeze: + UUIDComponent uuidComponent = store.getComponent(entityReference, UUIDComponent.getComponentType()); + if (uuidComponent != null) { + CommandManager.get().handleCommand(playerComponent, "npc freeze --toggle --entity " + uuidComponent.getUuid()); + } + break; + case Clone: + world.execute(() -> EntityCloneCommand.cloneEntity(playerComponent, entityReference, store)); + break; + case Remove: + world.execute(() -> EntityRemoveCommand.removeEntity(ref, entityReference, store)); + } + } + } + }); + } else { + throw new RuntimeException("Unable to process BuilderToolEntityAction packet. Player ref is invalid!"); + } + } + + public void handle(@Nonnull BuilderToolGeneralAction packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + LOGGER.at(Level.INFO).log("%s: %s", this.packetHandler.getIdentifier(), packet); + switch (packet.action) { + case HistoryUndo: + if (!hasPermission(playerComponent, "hytale.editor.history")) { + return; + } + + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.undo(r, 1, componentAccessor)); + break; + case HistoryRedo: + if (!hasPermission(playerComponent, "hytale.editor.history")) { + return; + } + + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.redo(r, 1, componentAccessor)); + break; + case SelectionCopy: + if (!hasPermission(playerComponent, "hytale.editor.selection.clipboard")) { + return; + } + + CopyCommand.copySelection(ref, store); + break; + case SelectionPosition1: + case SelectionPosition2: + if (!hasPermission(playerComponent, "hytale.editor.selection.use")) { + return; + } + + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + BuilderToolsPlugin.BuilderState builderState = BuilderToolsPlugin.getState(playerComponent, playerRef); + Vector3d position = transformComponent.getPosition(); + Vector3i intTriple = new Vector3i(MathUtil.floor(position.getX()), MathUtil.floor(position.getY()), MathUtil.floor(position.getZ())); + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> { + if (packet.action == BuilderToolAction.SelectionPosition1) { + builderState.pos1(intTriple, componentAccessor); + } else { + builderState.pos2(intTriple, componentAccessor); + } + }); + break; + case ActivateToolMode: + if (!hasPermission(playerComponent)) { + return; + } + + playerComponent.getInventory().setUsingToolsItem(true); + break; + case DeactivateToolMode: + if (!hasPermission(playerComponent)) { + return; + } + + playerComponent.getInventory().setUsingToolsItem(false); + } + }); + } else { + throw new RuntimeException("Unable to process BuilderToolGeneralAction packet. Player ref is invalid!"); + } + } + + public void handle(@Nonnull BuilderToolSelectionUpdate packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (hasPermission(playerComponent, "hytale.editor.selection.use")) { + LOGGER.at(Level.INFO).log("%s: %s", this.packetHandler.getIdentifier(), packet); + BuilderToolsPlugin.addToQueue( + playerComponent, + playerRef, + (r, s, componentAccessor) -> s.update(packet.xMin, packet.yMin, packet.zMin, packet.xMax, packet.yMax, packet.zMax) + ); + } + } + ); + } else { + throw new RuntimeException("Unable to process BuilderToolSelectionUpdate packet. Player ref is invalid!"); + } + } + + public void handle(BuilderToolSelectionToolAskForClipboard packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (hasPermission(playerComponent, "hytale.editor.selection.clipboard")) { + LOGGER.at(Level.INFO).log("%s: %s", this.packetHandler.getIdentifier(), packet); + PrototypePlayerBuilderToolSettings prototypeSettings = ToolOperation.getOrCreatePrototypeSettings(playerRef.getUuid()); + BuilderToolsPlugin.addToQueue( + playerComponent, + playerRef, + (r, s, componentAccessor) -> { + BlockSelection selection = s.getSelection(); + if (selection != null) { + EditorBlocksChange editorPacket = selection.toPacket(); + BlockChange[] blocksChange = editorPacket.blocksChange; + prototypeSettings.setBlockChangesForPlaySelectionToolPasteMode(blocksChange); + ArrayList fluidChanges = new ArrayList<>(); + int anchorX = selection.getAnchorX(); + int anchorY = selection.getAnchorY(); + int anchorZ = selection.getAnchorZ(); + selection.forEachFluid( + (x, y, z, fluidId, fluidLevel) -> fluidChanges.add( + new PrototypePlayerBuilderToolSettings.FluidChange(x - anchorX, y - anchorY, z - anchorZ, fluidId, fluidLevel) + ) + ); + PrototypePlayerBuilderToolSettings.FluidChange[] fluidChangesArray = fluidChanges.toArray( + PrototypePlayerBuilderToolSettings.FluidChange[]::new + ); + prototypeSettings.setFluidChangesForPlaySelectionToolPasteMode(fluidChangesArray); + FluidChange[] packetFluids = new FluidChange[fluidChangesArray.length]; + + for (int i = 0; i < fluidChangesArray.length; i++) { + PrototypePlayerBuilderToolSettings.FluidChange fc = fluidChangesArray[i]; + packetFluids[i] = new FluidChange(fc.x(), fc.y(), fc.z(), fc.fluidId(), fc.fluidLevel()); + } + + playerRef.getPacketHandler().write(new BuilderToolSelectionToolReplyWithClipboard(blocksChange, packetFluids)); + } + } + ); + } + } + ); + } else { + throw new RuntimeException("Unable to process BuilderToolSelectionToolAskForClipboard packet. Player ref is invalid!"); + } + } + + public int toInt(float value) { + return (int)Math.floor(value + 0.1); + } + + private void handle(@Nonnull BuilderToolSelectionTransform packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (hasPermission(playerComponent, "hytale.editor.selection.clipboard")) { + LOGGER.at(Level.INFO).log("%s: %s", this.packetHandler.getIdentifier(), packet); + float[] tmx = new float[16]; + + for (int i = 0; i < packet.transformationMatrix.length; i++) { + tmx[i] = this.toInt(packet.transformationMatrix[i]); + } + + Matrix4d transformationMatrix = new Matrix4d() + .assign( + tmx[0], tmx[4], tmx[8], tmx[12], tmx[1], tmx[5], tmx[9], tmx[13], tmx[2], tmx[6], tmx[10], tmx[14], tmx[3], tmx[7], tmx[11], tmx[15] + ); + Vector3i initialSelectionMin = new Vector3i(packet.initialSelectionMin.x, packet.initialSelectionMin.y, packet.initialSelectionMin.z); + Vector3i initialSelectionMax = new Vector3i(packet.initialSelectionMax.x, packet.initialSelectionMax.y, packet.initialSelectionMax.z); + Vector3f rotationOrigin = new Vector3f(packet.initialRotationOrigin.x, packet.initialRotationOrigin.y, packet.initialRotationOrigin.z); + PrototypePlayerBuilderToolSettings prototypeSettings = ToolOperation.getOrCreatePrototypeSettings(playerRef.getUuid()); + BuilderToolsPlugin.addToQueue( + playerComponent, + playerRef, + (r, s, componentAccessor) -> { + int blockCount = s.getSelection().getSelectionVolume(); + boolean large = blockCount > 20000; + if (large) { + playerComponent.sendMessage(Message.translation("server.builderTools.selection.large.warning")); + } + + if (prototypeSettings.getBlockChangesForPlaySelectionToolPasteMode() == null) { + s.select(initialSelectionMin, initialSelectionMax, "SelectionTranslatePacket", componentAccessor); + if (packet.cutOriginal) { + s.copyOrCut( + r, + initialSelectionMin.x, + initialSelectionMin.y, + initialSelectionMin.z, + initialSelectionMax.x, + initialSelectionMax.y, + initialSelectionMax.z, + 138, + store + ); + } else { + s.copyOrCut( + r, + initialSelectionMin.x, + initialSelectionMin.y, + initialSelectionMin.z, + initialSelectionMax.x, + initialSelectionMax.y, + initialSelectionMax.z, + 136, + store + ); + } + + BlockSelection selection = s.getSelection(); + BlockChange[] blocksChange = selection.toPacket().blocksChange; + prototypeSettings.setBlockChangesForPlaySelectionToolPasteMode(blocksChange); + ArrayList fluidChanges = new ArrayList<>(); + int anchorX = selection.getAnchorX(); + int anchorY = selection.getAnchorY(); + int anchorZ = selection.getAnchorZ(); + selection.forEachFluid( + (x, y, z, fluidId, fluidLevel) -> fluidChanges.add( + new PrototypePlayerBuilderToolSettings.FluidChange(x - anchorX, y - anchorY, z - anchorZ, fluidId, fluidLevel) + ) + ); + prototypeSettings.setFluidChangesForPlaySelectionToolPasteMode( + fluidChanges.toArray(PrototypePlayerBuilderToolSettings.FluidChange[]::new) + ); + prototypeSettings.setBlockChangeOffsetOrigin(new Vector3i(selection.getX(), selection.getY(), selection.getZ())); + } + + Vector3i blockChangeOffsetOrigin = prototypeSettings.getBlockChangeOffsetOrigin(); + if (packet.initialPastePointForClipboardPaste != null) { + blockChangeOffsetOrigin = new Vector3i( + packet.initialPastePointForClipboardPaste.x, + packet.initialPastePointForClipboardPaste.y, + packet.initialPastePointForClipboardPaste.z + ); + } + + if (blockChangeOffsetOrigin == null) { + playerComponent.sendMessage(Message.translation("server.builderTools.selection.noBlockChangeOffsetOrigin")); + } else { + s.transformThenPasteClipboard( + prototypeSettings.getBlockChangesForPlaySelectionToolPasteMode(), + prototypeSettings.getFluidChangesForPlaySelectionToolPasteMode(), + transformationMatrix, + rotationOrigin, + blockChangeOffsetOrigin, + componentAccessor + ); + s.select(initialSelectionMin, initialSelectionMax, "SelectionTranslatePacket", componentAccessor); + s.transformSelectionPoints(transformationMatrix, rotationOrigin); + if (large) { + playerComponent.sendMessage(Message.translation("server.builderTools.selection.large.complete")); + } + + if (packet.isExitingTransformMode) { + prototypeSettings.setInSelectionTransformationMode(false); + } + } + } + ); + } + } + ); + } else { + throw new RuntimeException("Unable to process BuilderToolSelectionTransform packet. Player ref is invalid!"); + } + } + + public void handle(@Nonnull BuilderToolExtrudeAction packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (hasPermission(playerComponent, "hytale.editor.selection.modify")) { + BuilderTool builderTool = BuilderTool.getActiveBuilderTool(playerComponent); + if (builderTool != null && builderTool.getId().equals("Extrude")) { + ItemStack activeItemStack = playerComponent.getInventory().getItemInHand(); + BuilderTool.ArgData args = builderTool.getItemArgData(activeItemStack); + int extrudeDepth = (Integer)args.tool().get("ExtrudeDepth"); + int extrudeRadius = (Integer)args.tool().get("ExtrudeRadius"); + int blockId = ((BlockPattern)args.tool().get("ExtrudeMaterial")).firstBlock(); + LOGGER.at(Level.INFO).log("%s: %s", this.packetHandler.getIdentifier(), packet); + BuilderToolsPlugin.addToQueue( + playerComponent, + playerRef, + (r, s, componentAccessor) -> s.extendFace( + packet.x, + packet.y, + packet.z, + packet.xNormal, + packet.yNormal, + packet.zNormal, + extrudeDepth, + extrudeRadius, + blockId, + null, + null, + componentAccessor + ) + ); + } + } + } + ); + } else { + throw new RuntimeException("Unable to process BuilderToolExtrudeAction packet. Player ref is invalid!"); + } + } + + public void handle(@Nonnull BuilderToolStackArea packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (hasPermission(playerComponent, "hytale.editor.selection.clipboard")) { + LOGGER.at(Level.INFO).log("%s: %s", this.packetHandler.getIdentifier(), packet); + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> { + s.select(this.fromBlockPosition(packet.selectionMin), this.fromBlockPosition(packet.selectionMax), "Extrude", componentAccessor); + s.stack(r, new Vector3i(packet.xNormal, packet.yNormal, packet.zNormal), packet.numStacks, true, 0, componentAccessor); + }); + } + }); + } else { + throw new RuntimeException("Unable to process BuilderToolStackArea packet. Player ref is invalid!"); + } + } + + @Nonnull + public Vector3i fromBlockPosition(@Nonnull BlockPosition position) { + return new Vector3i(position.x, position.y, position.z); + } + + public void handle(@Nonnull BuilderToolRotateClipboard packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (hasPermission(playerComponent, "hytale.editor.selection.clipboard")) { + Axis axis = packet.axis == com.hypixel.hytale.protocol.packets.buildertools.Axis.X + ? Axis.X + : (packet.axis == com.hypixel.hytale.protocol.packets.buildertools.Axis.Y ? Axis.Y : Axis.Z); + LOGGER.at(Level.INFO).log("%s: %s", this.packetHandler.getIdentifier(), packet); + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.rotate(r, axis, packet.angle, componentAccessor)); + } + } + ); + } else { + throw new RuntimeException("Unable to process BuilderToolPasteClipboard packet. Player ref is invalid!"); + } + } + + public void handle(@Nonnull BuilderToolPasteClipboard packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (hasPermission(playerComponent, "hytale.editor.selection.clipboard")) { + LOGGER.at(Level.INFO).log("%s: %s", this.packetHandler.getIdentifier(), packet); + BuilderToolsPlugin.addToQueue( + playerComponent, playerRef, (r, s, componentAccessor) -> s.paste(r, packet.x, packet.y, packet.z, componentAccessor) + ); + } + } + ); + } else { + throw new RuntimeException("Unable to process BuilderToolPasteClipboard packet. Player ref is invalid!"); + } + } + + public void handle(@Nonnull BuilderToolLineAction packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (hasPermission(playerComponent, "hytale.editor.brush.use")) { + BuilderTool builderTool = BuilderTool.getActiveBuilderTool(playerComponent); + if (builderTool != null && builderTool.getId().equals("Line")) { + BuilderTool.ArgData args = builderTool.getItemArgData(playerComponent.getInventory().getItemInHand()); + BrushData.Values brushData = args.brush(); + int lineWidth = (Integer)args.tool().get("bLineWidth"); + int lineHeight = (Integer)args.tool().get("cLineHeight"); + BrushShape lineShape = BrushShape.valueOf((String)args.tool().get("dLineShape")); + BrushOrigin lineOrigin = BrushOrigin.valueOf((String)args.tool().get("eLineOrigin")); + int lineWallThickness = (Integer)args.tool().get("fLineWallThickness"); + int lineSpacing = (Integer)args.tool().get("gLineSpacing"); + int lineDensity = (Integer)args.tool().get("hLineDensity"); + BlockPattern lineMaterial = (BlockPattern)args.tool().get("aLineMaterial"); + LOGGER.at(Level.INFO).log("%s: %s", this.packetHandler.getIdentifier(), packet); + BuilderToolsPlugin.addToQueue( + playerComponent, + playerRef, + (r, s, componentAccessor) -> s.editLine( + packet.xStart, + packet.yStart, + packet.zStart, + packet.xEnd, + packet.yEnd, + packet.zEnd, + lineMaterial, + lineWidth, + lineHeight, + lineWallThickness, + lineShape, + lineOrigin, + lineSpacing, + lineDensity, + ToolOperation.combineMasks(brushData, s.getGlobalMask()), + componentAccessor + ) + ); + } + } + } + ); + } else { + throw new RuntimeException("Unable to process BuilderToolLineAction packet. Player ref is invalid!"); + } + } + + public void handle(@Nonnull BuilderToolOnUseInteraction packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (hasPermission(playerComponent, "hytale.editor.brush.use")) { + LOGGER.at(Level.INFO).log("%s: %s", this.packetHandler.getIdentifier(), packet); + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.edit(ref, packet, componentAccessor)); + } + }); + } else { + throw new RuntimeException("Unable to process BuilderToolOnUseInteraction packet. Player ref is invalid!"); + } + } + + public void handle(@Nonnull BuilderToolSetEntityTransform packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (hasPermission(playerComponent)) { + Ref entityReference = world.getEntityStore().getRefFromNetworkId(packet.entityId); + if (entityReference != null) { + TransformComponent transformComponent = store.getComponent(entityReference, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotation = store.getComponent(entityReference, HeadRotation.getComponentType()); + ModelTransform modelTransform = packet.modelTransform; + if (modelTransform != null) { + boolean hasPosition = modelTransform.position != null; + boolean hasLookOrientation = modelTransform.lookOrientation != null; + boolean hasBodyOrientation = modelTransform.bodyOrientation != null; + if (hasPosition) { + transformComponent.getPosition().assign(modelTransform.position.x, modelTransform.position.y, modelTransform.position.z); + } + + if (hasLookOrientation && headRotation != null) { + headRotation.getRotation() + .assign(modelTransform.lookOrientation.pitch, modelTransform.lookOrientation.yaw, modelTransform.lookOrientation.roll); + } + + if (hasBodyOrientation) { + transformComponent.getRotation() + .assign(modelTransform.bodyOrientation.pitch, modelTransform.bodyOrientation.yaw, modelTransform.bodyOrientation.roll); + } + + if (hasPosition || hasLookOrientation || hasBodyOrientation) { + transformComponent.markChunkDirty(store); + } + } + } + } + } + ); + } else { + throw new RuntimeException("Unable to process BuilderToolSetEntityTransform packet. Player ref is invalid!"); + } + } + + public void handle(@Nonnull PrefabUnselectPrefab packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (hasPermission(playerComponent)) { + LOGGER.at(Level.INFO).log("%s: %s", this.packetHandler.getIdentifier(), packet); + PrefabEditSessionManager prefabEditSessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + PrefabEditSession prefabEditSession = prefabEditSessionManager.getPrefabEditSession(playerRef.getUuid()); + if (prefabEditSession == null) { + playerComponent.sendMessage(Message.translation("server.commands.editprefab.notInEditSession")); + } else { + if (prefabEditSession.clearSelectedPrefab(ref, store)) { + playerComponent.sendMessage(Message.translation("server.commands.editprefab.unselected")); + } else { + playerComponent.sendMessage(Message.translation("server.commands.editprefab.noPrefabSelected")); + } + } + } + }); + } else { + throw new RuntimeException("Unable to process PrefabUnselectPrefab packet. Player ref is invalid!"); + } + } + + public void handle(@Nonnull BuilderToolSetEntityScale packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (hasPermission(playerComponent)) { + Ref entityReference = world.getEntityStore().getRefFromNetworkId(packet.entityId); + if (entityReference != null) { + PropComponent propComponent = store.getComponent(entityReference, PropComponent.getComponentType()); + if (propComponent != null) { + EntityScaleComponent scaleComponent = store.getComponent(entityReference, EntityScaleComponent.getComponentType()); + if (scaleComponent == null) { + scaleComponent = new EntityScaleComponent(packet.scale); + store.addComponent(entityReference, EntityScaleComponent.getComponentType(), scaleComponent); + } else { + scaleComponent.setScale(packet.scale); + } + } + } + } + }); + } else { + throw new RuntimeException("Unable to process BuilderToolSetEntityScale packet. Player ref is invalid!"); + } + } + + public void handle(@Nonnull BuilderToolSetEntityPickupEnabled packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (hasPermission(playerComponent)) { + Ref entityReference = world.getEntityStore().getRefFromNetworkId(packet.entityId); + if (entityReference != null) { + PropComponent propComponent = store.getComponent(entityReference, PropComponent.getComponentType()); + if (propComponent != null) { + if (packet.enabled) { + store.ensureComponent(entityReference, Interactable.getComponentType()); + if (store.getComponent(entityReference, PreventPickup.getComponentType()) != null) { + store.removeComponent(entityReference, PreventPickup.getComponentType()); + } + + Interactions interactionsComponent = store.getComponent(entityReference, Interactions.getComponentType()); + if (interactionsComponent == null) { + interactionsComponent = new Interactions(); + store.addComponent(entityReference, Interactions.getComponentType(), interactionsComponent); + } + + interactionsComponent.setInteractionId(InteractionType.Use, "*PickupItem"); + interactionsComponent.setInteractionHint("server.interactionHints.pickup"); + } else { + if (store.getComponent(entityReference, Interactable.getComponentType()) != null) { + store.removeComponent(entityReference, Interactable.getComponentType()); + } + + if (store.getComponent(entityReference, Interactions.getComponentType()) != null) { + store.removeComponent(entityReference, Interactions.getComponentType()); + } + + store.ensureComponent(entityReference, PreventPickup.getComponentType()); + } + } + } + } + }); + } else { + throw new RuntimeException("Unable to process BuilderToolSetEntityPickupEnabled packet. Player ref is invalid!"); + } + } + + public void handle(@Nonnull BuilderToolSetEntityLight packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (hasPermission(playerComponent)) { + Ref entityReference = world.getEntityStore().getRefFromNetworkId(packet.entityId); + if (entityReference != null) { + if (packet.light == null) { + store.removeComponent(entityReference, DynamicLight.getComponentType()); + store.removeComponent(entityReference, PersistentDynamicLight.getComponentType()); + } else { + ColorLight colorLight = new ColorLight(packet.light.radius, packet.light.red, packet.light.green, packet.light.blue); + store.putComponent(entityReference, DynamicLight.getComponentType(), new DynamicLight(colorLight)); + store.putComponent(entityReference, PersistentDynamicLight.getComponentType(), new PersistentDynamicLight(colorLight)); + } + } + } + }); + } else { + throw new RuntimeException("Unable to process BuilderToolSetEntityLight packet. Player ref is invalid!"); + } + } + + public void handle(@Nonnull BuilderToolSetNPCDebug packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (hasPermission(playerComponent)) { + Ref entityReference = world.getEntityStore().getRefFromNetworkId(packet.entityId); + if (entityReference != null) { + UUIDComponent uuidComponent = store.getComponent(entityReference, UUIDComponent.getComponentType()); + if (uuidComponent != null) { + UUID uuid = uuidComponent.getUuid(); + String command = packet.enabled ? "npc debug set display --entity " + uuid : "npc debug clear --entity " + uuid; + CommandManager.get().handleCommand(playerComponent, command); + } + } + } + }); + } else { + throw new RuntimeException("Unable to process BuilderToolSetNPCDebug packet. Player ref is invalid!"); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/BuilderToolsPlugin.java b/src/com/hypixel/hytale/builtin/buildertools/BuilderToolsPlugin.java new file mode 100644 index 0000000..a527706 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/BuilderToolsPlugin.java @@ -0,0 +1,4482 @@ +package com.hypixel.hytale.builtin.buildertools; + +import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.builtin.buildertools.commands.ClearBlocksCommand; +import com.hypixel.hytale.builtin.buildertools.commands.ClearEditHistory; +import com.hypixel.hytale.builtin.buildertools.commands.ClearEntitiesCommand; +import com.hypixel.hytale.builtin.buildertools.commands.ContractSelectionCommand; +import com.hypixel.hytale.builtin.buildertools.commands.CopyCommand; +import com.hypixel.hytale.builtin.buildertools.commands.CutCommand; +import com.hypixel.hytale.builtin.buildertools.commands.DeselectCommand; +import com.hypixel.hytale.builtin.buildertools.commands.EditLineCommand; +import com.hypixel.hytale.builtin.buildertools.commands.EnvironmentCommand; +import com.hypixel.hytale.builtin.buildertools.commands.ExpandCommand; +import com.hypixel.hytale.builtin.buildertools.commands.ExtendFaceCommand; +import com.hypixel.hytale.builtin.buildertools.commands.FillCommand; +import com.hypixel.hytale.builtin.buildertools.commands.FlipCommand; +import com.hypixel.hytale.builtin.buildertools.commands.GlobalMaskCommand; +import com.hypixel.hytale.builtin.buildertools.commands.HollowCommand; +import com.hypixel.hytale.builtin.buildertools.commands.HotbarSwitchCommand; +import com.hypixel.hytale.builtin.buildertools.commands.MoveCommand; +import com.hypixel.hytale.builtin.buildertools.commands.PasteCommand; +import com.hypixel.hytale.builtin.buildertools.commands.Pos1Command; +import com.hypixel.hytale.builtin.buildertools.commands.Pos2Command; +import com.hypixel.hytale.builtin.buildertools.commands.PrefabCommand; +import com.hypixel.hytale.builtin.buildertools.commands.RedoCommand; +import com.hypixel.hytale.builtin.buildertools.commands.RepairFillersCommand; +import com.hypixel.hytale.builtin.buildertools.commands.ReplaceCommand; +import com.hypixel.hytale.builtin.buildertools.commands.RotateCommand; +import com.hypixel.hytale.builtin.buildertools.commands.SelectChunkCommand; +import com.hypixel.hytale.builtin.buildertools.commands.SelectChunkSectionCommand; +import com.hypixel.hytale.builtin.buildertools.commands.SelectionHistoryCommand; +import com.hypixel.hytale.builtin.buildertools.commands.SetCommand; +import com.hypixel.hytale.builtin.buildertools.commands.SetToolHistorySizeCommand; +import com.hypixel.hytale.builtin.buildertools.commands.ShiftCommand; +import com.hypixel.hytale.builtin.buildertools.commands.StackCommand; +import com.hypixel.hytale.builtin.buildertools.commands.SubmergeCommand; +import com.hypixel.hytale.builtin.buildertools.commands.TintCommand; +import com.hypixel.hytale.builtin.buildertools.commands.UndoCommand; +import com.hypixel.hytale.builtin.buildertools.commands.UpdateSelectionCommand; +import com.hypixel.hytale.builtin.buildertools.commands.WallsCommand; +import com.hypixel.hytale.builtin.buildertools.imageimport.ImageImportCommand; +import com.hypixel.hytale.builtin.buildertools.interactions.PickupItemInteraction; +import com.hypixel.hytale.builtin.buildertools.objimport.ObjImportCommand; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabAnchor; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabDirtySystems; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSession; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSessionManager; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditorCreationSettings; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabMarkerProvider; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabSelectionInteraction; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabSetAnchorInteraction; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.commands.PrefabEditCommand; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigEditStore; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.ScriptedBrushAsset; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.commands.BrushConfigCommand; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.global.DebugBrushOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.global.DisableHoldInteractionOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.global.IgnoreExistingBrushDataOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.BlockPatternOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.BreakpointOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.ClearOperationMaskOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.DeleteOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.EchoOnceOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.EchoOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.ErodeOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.HeightmapLayerOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.LayerOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.LiftOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.LoadIntFromToolArgOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.LoadMaterialFromToolArgOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.MaterialOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.MeltOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.PastePrefabOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.ReplaceOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.RunCommandOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.SetDensity; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.SetOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.ShapeOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.SmoothOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.dimensions.DimensionsOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.dimensions.RandomizeDimensionsOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.ExitOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.JumpIfBlockTypeOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.JumpIfClickType; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.JumpIfCompareOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.JumpIfStringMatchOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.JumpIfToolArgOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.JumpToIndexOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.JumpToRandomIndex; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.loops.CircleOffsetAndLoopOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.loops.CircleOffsetFromArgOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.loops.LoadLoopFromToolArgOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.loops.LoopOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.loops.LoopRandomOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks.AppendMaskFromToolArgOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks.AppendMaskOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks.HistoryMaskOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks.MaskOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks.UseBrushMaskOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks.UseOperationMaskOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.offsets.OffsetOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.offsets.RandomOffsetOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.saveandload.LoadBrushConfigOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.saveandload.LoadOperationsFromAssetOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.saveandload.PersistentDataOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.saveandload.SaveBrushConfigOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.saveandload.SaveIndexOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.BrushOperation; +import com.hypixel.hytale.builtin.buildertools.snapshot.BlockSelectionSnapshot; +import com.hypixel.hytale.builtin.buildertools.snapshot.ClipboardBoundsSnapshot; +import com.hypixel.hytale.builtin.buildertools.snapshot.ClipboardContentsSnapshot; +import com.hypixel.hytale.builtin.buildertools.snapshot.EntityAddSnapshot; +import com.hypixel.hytale.builtin.buildertools.snapshot.EntityRemoveSnapshot; +import com.hypixel.hytale.builtin.buildertools.snapshot.EntityTransformSnapshot; +import com.hypixel.hytale.builtin.buildertools.snapshot.SelectionSnapshot; +import com.hypixel.hytale.builtin.buildertools.tooloperations.PaintOperation; +import com.hypixel.hytale.builtin.buildertools.tooloperations.ToolOperation; +import com.hypixel.hytale.builtin.buildertools.utils.Material; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.WorldEventSystem; +import com.hypixel.hytale.event.EventRegistry; +import com.hypixel.hytale.function.predicate.TriIntObjPredicate; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.block.BlockCubeUtil; +import com.hypixel.hytale.math.block.BlockSphereUtil; +import com.hypixel.hytale.math.block.BlockUtil; +import com.hypixel.hytale.math.iterator.LineIterator; +import com.hypixel.hytale.math.matrix.Matrix4d; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.math.vector.Vector4d; +import com.hypixel.hytale.math.vector.VectorBoxUtil; +import com.hypixel.hytale.metrics.MetricProvider; +import com.hypixel.hytale.metrics.MetricResults; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.protocol.DrawType; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.buildertools.BrushOrigin; +import com.hypixel.hytale.protocol.packets.buildertools.BrushShape; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArgUpdate; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolOnUseInteraction; +import com.hypixel.hytale.protocol.packets.interface_.BlockChange; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.EditorBlocksChange; +import com.hypixel.hytale.protocol.packets.interface_.NotificationStyle; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BuilderTool; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BuilderToolData; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.BlockArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.BoolArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.BrushOriginArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.BrushShapeArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.FloatArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.IntArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.MaskArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.OptionArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.StringArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.ToolArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.ToolArgException; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; +import com.hypixel.hytale.server.core.command.system.CommandManager; +import com.hypixel.hytale.server.core.command.system.CommandRegistry; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerDisconnectEvent; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.io.ServerManager; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenCustomUIInteraction; +import com.hypixel.hytale.server.core.modules.prefabspawner.PrefabSpawnerState; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.prefab.PrefabCopyableComponent; +import com.hypixel.hytale.server.core.prefab.PrefabLoadException; +import com.hypixel.hytale.server.core.prefab.PrefabSaveException; +import com.hypixel.hytale.server.core.prefab.PrefabStore; +import com.hypixel.hytale.server.core.prefab.event.PrefabPasteEvent; +import com.hypixel.hytale.server.core.prefab.selection.SelectionManager; +import com.hypixel.hytale.server.core.prefab.selection.SelectionProvider; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockMask; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.prefab.selection.standard.FeedbackConsumer; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.accessor.BlockAccessor; +import com.hypixel.hytale.server.core.universe.world.accessor.ChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.accessor.LocalCachedChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.accessor.OverridableChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.AbstractCachedAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.events.AddWorldEvent; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.Config; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import com.hypixel.hytale.server.core.util.MessageUtil; +import com.hypixel.hytale.server.core.util.NotificationUtil; +import com.hypixel.hytale.server.core.util.PrefabUtil; +import com.hypixel.hytale.server.core.util.TargetUtil; +import com.hypixel.hytale.server.core.util.TempAssetIdUtil; +import com.hypixel.hytale.sneakythrow.consumer.ThrowableConsumer; +import com.hypixel.hytale.sneakythrow.consumer.ThrowableTriConsumer; +import it.unimi.dsi.fastutil.ints.Int2IntFunction; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntMaps; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.Set; +import java.util.UUID; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.StampedLock; +import java.util.function.Consumer; +import java.util.function.IntPredicate; +import java.util.function.Predicate; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderToolsPlugin extends JavaPlugin implements SelectionProvider, MetricProvider { + public static final String EDITOR_BLOCK = "Editor_Block"; + public static final String EDITOR_BLOCK_PREFAB_AIR = "Editor_Empty"; + public static final String EDITOR_BLOCK_PREFAB_ANCHOR = "Editor_Anchor"; + protected static final float SPHERE_SIZE = 1.0F; + private static final FeedbackConsumer FEEDBACK_CONSUMER = BuilderToolsPlugin::sendFeedback; + private static final MetricsRegistry PLUGIN_METRICS_REGISTRY = new MetricsRegistry() + .register( + "BuilderStates", + plugin -> plugin.builderStates.values().toArray(BuilderToolsPlugin.BuilderState[]::new), + new ArrayCodec<>(BuilderToolsPlugin.BuilderState.STATE_METRICS_REGISTRY, BuilderToolsPlugin.BuilderState[]::new) + ); + private static final long RETAIN_BUILDER_STATE_TIMESTAMP = Long.MAX_VALUE; + private static final long MIN_CLEANUP_INTERVAL_NANOS = TimeUnit.MINUTES.toNanos(1L); + private final Map builderStates = new ConcurrentHashMap<>(); + private PrefabEditSessionManager prefabEditSessionManager; + private final BlockColorIndex blockColorIndex = new BlockColorIndex(); + private static BuilderToolsPlugin instance; + private int historyCount; + private long toolExpireTimeNanos; + @Nullable + private ScheduledFuture cleanupTask; + private ComponentType userDataComponentType; + private ComponentType prefabAnchorComponentType; + private final Int2ObjectConcurrentHashMap> pastedPrefabPathUUIDMap = new Int2ObjectConcurrentHashMap<>(); + private final Int2ObjectConcurrentHashMap> pastedPrefabPathNameToUUIDMap = new Int2ObjectConcurrentHashMap<>(); + private static final float SMOOTHING_KERNEL_TOTAL = 27.0F; + private static final int[] SMOOTHING_KERNEL = new int[]{1, 2, 1, 2, 3, 2, 1, 2, 1, 2, 3, 2, 3, 4, 3, 2, 3, 2, 1, 2, 1, 2, 3, 2, 1, 2, 1}; + private final Config config = this.withConfig("BuilderToolsModule", BuilderToolsPlugin.BuilderToolsConfig.CODEC); + private ResourceType prefabEditSessionResourceType; + + public BuilderToolsPlugin(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + this.getLogger().setLevel(Level.FINE); + } + + public static BuilderToolsPlugin get() { + return instance; + } + + @Nonnull + public BlockColorIndex getBlockColorIndex() { + return this.blockColorIndex; + } + + public static void invalidateWorldMapForSelection(@Nonnull BlockSelection selection, @Nonnull World world) { + invalidateWorldMapForBounds(selection.getSelectionMin(), selection.getSelectionMax(), world); + } + + static void invalidateWorldMapForBounds(@Nonnull Vector3i min, @Nonnull Vector3i max, @Nonnull World world) { + LongSet affectedChunks = new LongOpenHashSet(); + int minChunkX = min.x >> 5; + int maxChunkX = max.x >> 5; + int minChunkZ = min.z >> 5; + int maxChunkZ = max.z >> 5; + + for (int cx = minChunkX; cx <= maxChunkX; cx++) { + for (int cz = minChunkZ; cz <= maxChunkZ; cz++) { + affectedChunks.add(ChunkUtil.indexChunk(cx, cz)); + } + } + + world.getWorldMapManager().clearImagesInChunks(affectedChunks); + + for (Player worldPlayer : world.getPlayers()) { + worldPlayer.getWorldMapTracker().clearChunks(affectedChunks); + } + } + + @Nonnull + public static BuilderToolsPlugin.BuilderState getState(@Nonnull Player player, @Nonnull PlayerRef playerRef) { + return instance.getBuilderState(player, playerRef); + } + + public static void addToQueue( + @Nonnull Player player, + @Nonnull PlayerRef playerRef, + @Nonnull ThrowableTriConsumer, BuilderToolsPlugin.BuilderState, ComponentAccessor, T> task + ) { + getState(player, playerRef).addToQueue(task); + } + + @Override + protected void setup() { + CommandRegistry commandRegistry = this.getCommandRegistry(); + EventRegistry eventRegistry = this.getEventRegistry(); + ComponentRegistryProxy entityStoreRegistry = this.getEntityStoreRegistry(); + ServerManager.get().registerSubPacketHandlers(BuilderToolsPacketHandler::new); + eventRegistry.register(PlayerConnectEvent.class, this::onPlayerConnect); + eventRegistry.register(PlayerDisconnectEvent.class, this::onPlayerDisconnect); + eventRegistry.registerGlobal( + AddWorldEvent.class, event -> event.getWorld().getWorldMapManager().addMarkerProvider("prefabs", PrefabMarkerProvider.INSTANCE) + ); + entityStoreRegistry.registerSystem(new BuilderToolsPlugin.PrefabPasteEventSystem(this)); + entityStoreRegistry.registerSystem(new PrefabDirtySystems.BlockBreakDirtySystem()); + entityStoreRegistry.registerSystem(new PrefabDirtySystems.BlockPlaceDirtySystem()); + this.prefabAnchorComponentType = entityStoreRegistry.registerComponent(PrefabAnchor.class, "PrefabAnchor", PrefabAnchor.CODEC); + Interaction.CODEC.register("PrefabSelectionInteraction", PrefabSelectionInteraction.class, PrefabSelectionInteraction.CODEC); + Interaction.CODEC.register("PrefabSetAnchorInteraction", PrefabSetAnchorInteraction.class, PrefabSetAnchorInteraction.CODEC); + Interaction.CODEC.register("PickupItem", PickupItemInteraction.class, PickupItemInteraction.CODEC); + Interaction.getAssetStore().loadAssets("Hytale:Hytale", List.of(new PickupItemInteraction("*PickupItem"))); + RootInteraction.getAssetStore().loadAssets("Hytale:Hytale", List.of(PickupItemInteraction.DEFAULT_ROOT)); + this.prefabEditSessionManager = new PrefabEditSessionManager(this); + this.prefabEditSessionResourceType = entityStoreRegistry.registerResource(PrefabEditSession.class, "PrefabEditSession", PrefabEditSession.CODEC); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + PrefabEditorCreationSettings.class, new DefaultAssetMap() + ) + .setPath("PrefabEditorCreationSettings")) + .setKeyFunction(PrefabEditorCreationSettings::getId)) + .setCodec(PrefabEditorCreationSettings.CODEC)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ScriptedBrushAsset.class, new DefaultAssetMap() + ) + .setPath("ScriptedBrushes")) + .setKeyFunction(ScriptedBrushAsset::getId)) + .setCodec(ScriptedBrushAsset.CODEC)) + .build() + ); + commandRegistry.registerCommand(new ClearBlocksCommand()); + commandRegistry.registerCommand(new ClearEntitiesCommand()); + commandRegistry.registerCommand(new ClearEditHistory()); + commandRegistry.registerCommand(new ContractSelectionCommand()); + commandRegistry.registerCommand(new CopyCommand()); + commandRegistry.registerCommand(new DeselectCommand()); + commandRegistry.registerCommand(new CutCommand()); + commandRegistry.registerCommand(new EditLineCommand()); + commandRegistry.registerCommand(new EnvironmentCommand()); + commandRegistry.registerCommand(new ExpandCommand()); + commandRegistry.registerCommand(new ExtendFaceCommand()); + commandRegistry.registerCommand(new FlipCommand()); + commandRegistry.registerCommand(new MoveCommand()); + commandRegistry.registerCommand(new PasteCommand()); + commandRegistry.registerCommand(new Pos1Command()); + commandRegistry.registerCommand(new Pos2Command()); + commandRegistry.registerCommand(new PrefabCommand()); + commandRegistry.registerCommand(new RedoCommand()); + commandRegistry.registerCommand(new ReplaceCommand()); + commandRegistry.registerCommand(new RotateCommand()); + commandRegistry.registerCommand(new SelectChunkCommand()); + commandRegistry.registerCommand(new SelectChunkSectionCommand()); + commandRegistry.registerCommand(new SelectionHistoryCommand()); + commandRegistry.registerCommand(new SetCommand()); + commandRegistry.registerCommand(new ShiftCommand()); + commandRegistry.registerCommand(new StackCommand()); + commandRegistry.registerCommand(new SubmergeCommand()); + commandRegistry.registerCommand(new TintCommand()); + commandRegistry.registerCommand(new UndoCommand()); + commandRegistry.registerCommand(new UpdateSelectionCommand()); + commandRegistry.registerCommand(new GlobalMaskCommand()); + commandRegistry.registerCommand(new RepairFillersCommand()); + commandRegistry.registerCommand(new PrefabEditCommand()); + commandRegistry.registerCommand(new HotbarSwitchCommand()); + commandRegistry.registerCommand(new WallsCommand()); + commandRegistry.registerCommand(new HollowCommand()); + commandRegistry.registerCommand(new FillCommand()); + commandRegistry.registerCommand(new BrushConfigCommand()); + commandRegistry.registerCommand(new SetToolHistorySizeCommand()); + commandRegistry.registerCommand(new ObjImportCommand()); + commandRegistry.registerCommand(new ImageImportCommand()); + OpenCustomUIInteraction.registerBlockCustomPage( + this, + PrefabSpawnerState.PrefabSpawnerSettingsPage.class, + "PrefabSpawner", + PrefabSpawnerState.class, + (playerRef, state) -> new PrefabSpawnerState.PrefabSpawnerSettingsPage(playerRef, state, CustomPageLifetime.CanDismissOrCloseThroughInteraction) + ); + SelectionManager.setSelectionProvider(this); + ToolArg.CODEC.register("Bool", BoolArg.class, BoolArg.CODEC); + ToolArg.CODEC.register("String", StringArg.class, StringArg.CODEC); + ToolArg.CODEC.register("Int", IntArg.class, IntArg.CODEC); + ToolArg.CODEC.register("Float", FloatArg.class, FloatArg.CODEC); + ToolArg.CODEC.register("Block", BlockArg.class, BlockArg.CODEC); + ToolArg.CODEC.register("Mask", MaskArg.class, MaskArg.CODEC); + ToolArg.CODEC.register("BrushShape", BrushShapeArg.class, BrushShapeArg.CODEC); + ToolArg.CODEC.register("BrushOrigin", BrushOriginArg.class, BrushOriginArg.CODEC); + ToolArg.CODEC.register("Option", OptionArg.class, OptionArg.CODEC); + this.registerBrushOperations(); + this.userDataComponentType = entityStoreRegistry.registerComponent(BuilderToolsUserData.class, "BuilderTools", BuilderToolsUserData.CODEC); + entityStoreRegistry.registerSystem(new BuilderToolsSystems.EnsureBuilderTools()); + entityStoreRegistry.registerSystem(new BuilderToolsUserDataSystem()); + } + + private void registerBrushOperations() { + BrushOperation.OPERATION_CODEC.register("dimensions", DimensionsOperation.class, DimensionsOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("randomdimensions", RandomizeDimensionsOperation.class, RandomizeDimensionsOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("runcommand", RunCommandOperation.class, RunCommandOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("historymask", HistoryMaskOperation.class, HistoryMaskOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("mask", MaskOperation.class, MaskOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("clearoperationmask", ClearOperationMaskOperation.class, ClearOperationMaskOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("usebrushmask", UseBrushMaskOperation.class, UseBrushMaskOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("useoperationmask", UseOperationMaskOperation.class, UseOperationMaskOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("appendmask", AppendMaskOperation.class, AppendMaskOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("appendmaskfromtoolarg", AppendMaskFromToolArgOperation.class, AppendMaskFromToolArgOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("ignorebrushsettings", IgnoreExistingBrushDataOperation.class, IgnoreExistingBrushDataOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("debug", DebugBrushOperation.class, DebugBrushOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("loop", LoopOperation.class, LoopOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("loadloop", LoadLoopFromToolArgOperation.class, LoadLoopFromToolArgOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("looprandom", LoopRandomOperation.class, LoopRandomOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("loopcircle", CircleOffsetAndLoopOperation.class, CircleOffsetAndLoopOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("loopcirclefromarg", CircleOffsetFromArgOperation.class, CircleOffsetFromArgOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("savebrushconfig", SaveBrushConfigOperation.class, SaveBrushConfigOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("loadbrushconfig", LoadBrushConfigOperation.class, LoadBrushConfigOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("saveindex", SaveIndexOperation.class, SaveIndexOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("loadoperationsfromasset", LoadOperationsFromAssetOperation.class, LoadOperationsFromAssetOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("jump", JumpToIndexOperation.class, JumpToIndexOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("exit", ExitOperation.class, ExitOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("jumprandom", JumpToRandomIndex.class, JumpToRandomIndex.CODEC); + BrushOperation.OPERATION_CODEC.register("jumpifequal", JumpIfStringMatchOperation.class, JumpIfStringMatchOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("jumpifclicktype", JumpIfClickType.class, JumpIfClickType.CODEC); + BrushOperation.OPERATION_CODEC.register("jumpifcompare", JumpIfCompareOperation.class, JumpIfCompareOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("jumpifblocktype", JumpIfBlockTypeOperation.class, JumpIfBlockTypeOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("jumpiftoolarg", JumpIfToolArgOperation.class, JumpIfToolArgOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("pattern", BlockPatternOperation.class, BlockPatternOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("loadmaterial", LoadMaterialFromToolArgOperation.class, LoadMaterialFromToolArgOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("loadint", LoadIntFromToolArgOperation.class, LoadIntFromToolArgOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("lift", LiftOperation.class, LiftOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("density", SetDensity.class, SetDensity.CODEC); + BrushOperation.OPERATION_CODEC.register("set", SetOperation.class, SetOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("smooth", SmoothOperation.class, SmoothOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("shape", ShapeOperation.class, ShapeOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("offset", OffsetOperation.class, OffsetOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("layer", LayerOperation.class, LayerOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("heightmaplayer", HeightmapLayerOperation.class, HeightmapLayerOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("melt", MeltOperation.class, MeltOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("material", MaterialOperation.class, MaterialOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("delete", DeleteOperation.class, DeleteOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("disableonhold", DisableHoldInteractionOperation.class, DisableHoldInteractionOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("randomoffset", RandomOffsetOperation.class, RandomOffsetOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("erode", ErodeOperation.class, ErodeOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("persistentdata", PersistentDataOperation.class, PersistentDataOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("pasteprefab", PastePrefabOperation.class, PastePrefabOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("echo", EchoOperation.class, EchoOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("echoonce", EchoOnceOperation.class, EchoOnceOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("replace", ReplaceOperation.class, ReplaceOperation.CODEC); + BrushOperation.OPERATION_CODEC.register("breakpoint", BreakpointOperation.class, BreakpointOperation.CODEC); + } + + public ResourceType getPrefabEditSessionResourceType() { + return this.prefabEditSessionResourceType; + } + + @Override + protected void start() { + BuilderToolsPlugin.BuilderToolsConfig config = this.config.get(); + this.historyCount = config.historyCount; + this.toolExpireTimeNanos = TimeUnit.SECONDS.toNanos(config.toolExpireTime); + if (this.toolExpireTimeNanos > 0L) { + long intervalNanos = Math.max(MIN_CLEANUP_INTERVAL_NANOS, this.toolExpireTimeNanos); + this.cleanupTask = HytaleServer.SCHEDULED_EXECUTOR.scheduleWithFixedDelay(this::cleanup, intervalNanos, intervalNanos, TimeUnit.NANOSECONDS); + } + } + + @Override + protected void shutdown() { + if (this.cleanupTask != null) { + this.cleanupTask.cancel(false); + } + } + + private void cleanup() { + long expire = System.nanoTime() - this.toolExpireTimeNanos; + Iterator> iterator = this.builderStates.entrySet().iterator(); + + while (iterator.hasNext()) { + Entry entry = iterator.next(); + BuilderToolsPlugin.BuilderState state = entry.getValue(); + if (state.timestamp < expire) { + iterator.remove(); + this.getLogger().at(Level.FINE).log("[%s] Expired and removed builder tool", state.getDisplayName()); + } + } + } + + public void setToolHistorySize(int size) { + this.historyCount = size; + } + + private void onPlayerConnect(@Nonnull PlayerConnectEvent event) { + this.retainBuilderState(event.getPlayer(), event.getPlayerRef()); + } + + private void onPlayerDisconnect(@Nonnull PlayerDisconnectEvent event) { + this.releaseBuilderState(event.getPlayerRef().getUuid()); + } + + public void onToolArgUpdate(@Nonnull PlayerRef playerRef, @Nonnull Player player, @Nonnull BuilderToolArgUpdate packet) { + ItemContainer section = player.getInventory().getSectionById(packet.section); + ItemStack itemStack = section.getItemStack((short)packet.slot); + if (itemStack == null) { + MessageUtil.sendFailureReply(playerRef, packet.token, Message.translation("server.builderTools.invalidTool").param("item", "Empty")); + } else { + Item item = itemStack.getItem(); + BuilderToolData builderToolData = item.getBuilderToolData(); + if (builderToolData == null) { + Message itemMessage = Message.translation(item.getTranslationKey()); + MessageUtil.sendFailureReply(playerRef, packet.token, Message.translation("server.builderTools.invalidTool").param("item", itemMessage)); + } else { + BuilderTool tool = builderToolData.getTools()[0]; + + try { + ItemStack updatedItemStack = tool.updateArgMetadata(itemStack, packet.group, packet.id, packet.value); + section.setItemStackForSlot((short)packet.slot, updatedItemStack); + MessageUtil.sendSuccessReply(playerRef, packet.token); + } catch (ToolArgException var10) { + MessageUtil.sendFailureReply(playerRef, packet.token, var10.getTranslationMessage()); + } catch (IllegalArgumentException var11) { + MessageUtil.sendFailureReply( + playerRef, packet.token, Message.translation("server.builderTools.toolArgParseError").param("arg", packet.id).param("value", packet.value) + ); + } + } + } + } + + @Nonnull + public BuilderToolsPlugin.BuilderState getBuilderState(@Nonnull Player player, @Nonnull PlayerRef playerRef) { + return this.builderStates.computeIfAbsent(playerRef.getUuid(), k -> new BuilderToolsPlugin.BuilderState(player, playerRef)); + } + + @Nullable + public BuilderToolsPlugin.BuilderState clearBuilderState(UUID uuid) { + BuilderToolsPlugin.BuilderState state = this.builderStates.remove(uuid); + if (state != null) { + this.getLogger().at(Level.FINE).log("[%s] Removed builder tool for", state.getDisplayName()); + } + + return state; + } + + private void retainBuilderState(@Nonnull Player player, @Nonnull PlayerRef playerRef) { + this.builderStates.compute(playerRef.getUuid(), (id, state) -> { + if (state == null) { + return null; + } else { + state.retain(player, playerRef); + this.getLogger().at(Level.FINE).log("[%s] Retained builder tool", state.getDisplayName()); + return (BuilderToolsPlugin.BuilderState)state; + } + }); + } + + private void releaseBuilderState(@Nonnull UUID uuid) { + if (this.toolExpireTimeNanos <= 0L) { + this.clearBuilderState(uuid); + } else { + this.builderStates.compute(uuid, (id, state) -> { + if (state == null) { + return null; + } else { + state.release(); + this.getLogger().at(Level.FINE).log("[%s] Marked builder tool for removal", state.getDisplayName()); + return (BuilderToolsPlugin.BuilderState)state; + } + }); + } + } + + public ComponentType getUserDataComponentType() { + return this.userDataComponentType; + } + + public static void sendFeedback( + @Nonnull Message message, + @Nullable CommandSender feedback, + @Nonnull NotificationStyle notificationStyle, + @Nonnull ComponentAccessor componentAccessor + ) { + if (feedback instanceof Player playerComponent) { + Ref ref = playerComponent.getReference(); + if (ref == null || !ref.isValid()) { + return; + } + + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + NotificationUtil.sendNotification(playerRefComponent.getPacketHandler(), message, notificationStyle); + } else if (feedback != null) { + feedback.sendMessage(message); + } + } + + public static void sendFeedback(@Nonnull String key, int total, CommandSender feedback, ComponentAccessor componentAccessor) { + if (feedback instanceof Player playerComponent) { + Ref ref = playerComponent.getReference(); + if (ref == null || !ref.isValid()) { + return; + } + + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + NotificationUtil.sendNotification( + playerRefComponent.getPacketHandler(), + Message.translation("server.builderTools.blocksEdited").param("key", key), + Message.raw(String.valueOf(total)), + NotificationStyle.Success + ); + } else if (feedback != null) { + feedback.sendMessage(Message.translation("server.builderTools.blocksEdited").param("key", key)); + } + } + + public static void sendFeedback(@Nonnull String key, int total, int num, CommandSender feedback, ComponentAccessor componentAccessor) { + if (num % 100000 == 0) { + if (feedback instanceof Player playerComponent) { + Ref ref = playerComponent.getReference(); + if (ref == null || !ref.isValid()) { + return; + } + + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + NotificationUtil.sendNotification( + playerRefComponent.getPacketHandler(), + Message.translation("server.builderTools.doneEditing").param("key", key), + Message.translation("server.builderTools.blocksChanged").param("total", total), + NotificationStyle.Success + ); + } else if (feedback != null) { + feedback.sendMessage( + Message.translation("server.builderTools.editingStatus") + .param("key", key) + .param("percent", MathUtil.round((double)num / total * 100.0, 2)) + .param("count", num) + .param("total", total) + ); + } + } + } + + @Override + public void computeSelectionCopy( + @Nonnull Ref ref, + @Nonnull Player player, + @Nonnull ThrowableConsumer task, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.isEnabled()) { + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + this.getBuilderState(player, playerRefComponent).computeSelectionCopy(task); + } + } + + @Nonnull + @Override + public MetricResults toMetricResults() { + return PLUGIN_METRICS_REGISTRY.toMetricResults(this); + } + + public ComponentType getPrefabAnchorComponentType() { + return this.prefabAnchorComponentType; + } + + public PrefabEditSessionManager getPrefabEditSessionManager() { + return this.prefabEditSessionManager; + } + + @Nullable + @Deprecated + public static Holder createBlockComponent( + WorldChunk chunk, int x, int y, int z, int newId, int oldId, @Nullable Holder oldHolder, boolean copy + ) { + if (newId == 0) { + return null; + } else { + BlockType type = BlockType.getAssetMap().getAsset(newId); + if (type.getBlockEntity() != null) { + return type.getBlockEntity().clone(); + } else { + String stateId = type.getState() != null ? type.getState().getId() : null; + if (stateId == null) { + return null; + } else { + if (copy && oldHolder != null) { + BlockType currentType = BlockType.getAssetMap().getAsset(oldId); + String currentStateId = currentType.getState() != null ? currentType.getState().getId() : null; + if (stateId.equals(currentStateId)) { + return oldHolder.clone(); + } + } + + Vector3i position = new Vector3i(x, y, z); + BlockState state = BlockStateModule.get().createBlockState(stateId, chunk, position, type); + return state == null ? null : state.toHolder(); + } + } + } + } + + public static void forEachCopyableInSelection( + @Nonnull World world, int minX, int minY, int minZ, int width, int height, int depth, @Nonnull Consumer> action + ) { + int encompassingWidth = width + 1; + int encompassingHeight = height + 1; + int encompassingDepth = depth + 1; + if (world.isInThread()) { + internalForEachCopyableInSelection(world, minX, minY, minZ, encompassingWidth, encompassingHeight, encompassingDepth, action); + } else { + CompletableFuture.runAsync( + () -> internalForEachCopyableInSelection(world, minX, minY, minZ, encompassingWidth, encompassingHeight, encompassingDepth, action), world + ) + .join(); + } + } + + private static void internalForEachCopyableInSelection( + @Nonnull World world, + int minX, + int minY, + int minZ, + int encompassingWidth, + int encompassingHeight, + int encompassingDepth, + @Nonnull Consumer> action + ) { + world.getEntityStore() + .getStore() + .forEachChunk(Archetype.of(PrefabCopyableComponent.getComponentType(), TransformComponent.getComponentType()), (archetypeChunk, commandBuffer) -> { + int size = archetypeChunk.size(); + + for (int index = 0; index < size; index++) { + Vector3d vector = archetypeChunk.getComponent(index, TransformComponent.getComponentType()).getPosition(); + Ref ref = archetypeChunk.getReferenceTo(index); + if (VectorBoxUtil.isInside(minX, minY, minZ, 0.0, 0.0, 0.0, encompassingWidth, encompassingHeight, encompassingDepth, vector)) { + action.accept(ref); + } + } + }); + } + + private static int getNonEmptyNeighbourBlock(@Nonnull ChunkAccessor accessor, int x, int y, int z) { + int blockId; + if ((blockId = accessor.getBlock(x, y, z + 1)) > 0) { + return blockId; + } else if ((blockId = accessor.getBlock(x, y, z - 1)) > 0) { + return blockId; + } else if ((blockId = accessor.getBlock(x, y + 1, z)) > 0) { + return blockId; + } else if ((blockId = accessor.getBlock(x, y - 1, z)) > 0) { + return blockId; + } else if ((blockId = accessor.getBlock(x - 1, y, z)) > 0) { + return blockId; + } else { + return (blockId = accessor.getBlock(x + 1, y, z)) > 0 ? blockId : 0; + } + } + + @Nonnull + public UUID getNewPathIdOnPrefabPasted(@Nullable UUID id, String name, int prefabId) { + ConcurrentHashMap prefabIdMap = this.pastedPrefabPathUUIDMap.get(prefabId); + if (id != null) { + return prefabIdMap.computeIfAbsent(id, k -> UUID.randomUUID()); + } else { + ConcurrentHashMap prefabNameMap = this.pastedPrefabPathNameToUUIDMap.get(prefabId); + UUID newId = prefabNameMap.computeIfAbsent(name, k -> UUID.randomUUID()); + prefabIdMap.put(newId, newId); + return newId; + } + } + + public static boolean onPasteStart(int prefabId, @Nonnull ComponentAccessor componentAccessor) { + PrefabPasteEvent event = new PrefabPasteEvent(prefabId, true); + componentAccessor.invoke(event); + return !event.isCancelled(); + } + + public void onPasteEnd(int prefabId, @Nonnull ComponentAccessor componentAccessor) { + PrefabPasteEvent event = new PrefabPasteEvent(prefabId, false); + componentAccessor.invoke(event); + } + + public Int2ObjectConcurrentHashMap> getPastedPrefabPathUUIDMap() { + return this.pastedPrefabPathUUIDMap; + } + + public static enum Action { + EDIT, + EDIT_SELECTION, + EDIT_LINE, + CUT_COPY, + CUT_REMOVE, + COPY, + PASTE, + CLEAR, + ROTATE, + FLIP, + MOVE, + STACK, + SET, + REPLACE, + EXTRUDE, + UPDATE_SELECTION, + WALLS, + HOLLOW; + + private Action() { + } + } + + public static class ActionEntry { + private final BuilderToolsPlugin.Action action; + private final List> snapshots; + + public ActionEntry(BuilderToolsPlugin.Action action, SelectionSnapshot snapshots) { + this(action, Collections.singletonList(snapshots)); + } + + public ActionEntry(BuilderToolsPlugin.Action action, List> snapshots) { + this.action = action; + this.snapshots = snapshots; + } + + public BuilderToolsPlugin.Action getAction() { + return this.action; + } + + @Nonnull + public BuilderToolsPlugin.ActionEntry restore(Ref ref, Player player, World world, ComponentAccessor componentAccessor) { + List> collector = Collections.emptyList(); + + for (SelectionSnapshot snapshot : this.snapshots) { + SelectionSnapshot nextSnapshot = snapshot.restore(ref, player, world, componentAccessor); + if (nextSnapshot != null) { + collector = (List>)(collector.isEmpty() ? new ObjectArrayList<>() : collector); + collector.add(nextSnapshot); + } + } + + return new BuilderToolsPlugin.ActionEntry(this.action, collector); + } + } + + public static class BuilderState { + private static final MetricsRegistry STATE_METRICS_REGISTRY = new MetricsRegistry() + .register("Uuid", state -> state.player.getUuid(), Codec.UUID_STRING) + .register("Username", BuilderToolsPlugin.BuilderState::getDisplayName, Codec.STRING) + .register("ActivePrefabPath", BuilderToolsPlugin.BuilderState::getActivePrefabPath, Codec.UUID_STRING) + .register("Selection", BuilderToolsPlugin.BuilderState::getSelection, BlockSelection.METRICS_REGISTRY) + .register("TaskFuture", state -> Objects.toString(state.getTaskFuture()), Codec.STRING) + .register("TaskCount", BuilderToolsPlugin.BuilderState::getTaskCount, Codec.INTEGER) + .register("UndoCount", BuilderToolsPlugin.BuilderState::getUndoCount, Codec.INTEGER) + .register("RedoCount", BuilderToolsPlugin.BuilderState::getRedoCount, Codec.INTEGER); + private Player player; + private PlayerRef playerRef; + @Nonnull + private final BuilderToolsUserData userData; + private final StampedLock undoLock = new StampedLock(); + private final ObjectArrayFIFOQueue undo = new ObjectArrayFIFOQueue<>(); + private final ObjectArrayFIFOQueue redo = new ObjectArrayFIFOQueue<>(); + private final StampedLock taskLock = new StampedLock(); + private final ObjectArrayFIFOQueue tasks = new ObjectArrayFIFOQueue<>(); + @Nullable + private volatile CompletableFuture taskFuture; + private volatile long timestamp = Long.MAX_VALUE; + private BlockSelection selection; + private BlockMask globalMask; + @Nonnull + private Random random = new Random(26061984L); + private UUID activePrefabPath; + @Nullable + private Path prefabListRoot; + @Nullable + private Path prefabListPath; + @Nullable + private String prefabListSearchQuery; + + private BuilderState(@Nonnull Player player, @Nonnull PlayerRef playerRef) { + this.player = player; + this.playerRef = playerRef; + this.userData = BuilderToolsUserData.get(player); + } + + private void release() { + this.timestamp = System.nanoTime(); + } + + private void retain(@Nonnull Player player, @Nonnull PlayerRef playerRef) { + long stamp = this.taskLock.writeLock(); + + try { + this.player = player; + this.playerRef = playerRef; + this.timestamp = Long.MAX_VALUE; + if (this.selection != null) { + this.sendArea(); + } + } finally { + this.taskLock.unlockWrite(stamp); + } + } + + public void addToQueue( + @Nonnull ThrowableTriConsumer, BuilderToolsPlugin.BuilderState, ComponentAccessor, T> task + ) { + long stamp = this.taskLock.writeLock(); + + try { + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("[%s] Add task with ComponentAccessor to queue %s: %s, %s, %s", this.getDisplayName(), task, this.taskFuture, this.tasks); + this.tasks.enqueue(new BuilderToolsPlugin.QueuedTask(task)); + if (this.taskFuture == null || this.taskFuture.isDone()) { + this.taskFuture = CompletableFutureUtil._catch(CompletableFuture.runAsync(this::runTask, this.player.getWorld())); + } + } finally { + this.taskLock.unlockWrite(stamp); + } + } + + public void computeSelectionCopy(@Nonnull ThrowableConsumer task) { + this.addToQueue( + (r, b, componentAccessor) -> { + long start = System.nanoTime(); + if (this.selection == null) { + this.selection = new BlockSelection(); + } + + BlockSelection oldSelection = this.selection; + this.pushHistory(BuilderToolsPlugin.Action.COPY, BlockSelectionSnapshot.copyOf(this.selection)); + this.selection = new BlockSelection(); + this.selection.setPosition(oldSelection.getX(), oldSelection.getY(), oldSelection.getZ()); + this.selection.setSelectionArea(oldSelection.getSelectionMin(), oldSelection.getSelectionMax()); + task.accept(this.selection); + long diff = System.nanoTime() - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log( + "Took: %dns (%dms) to execute computeSelectionCopy for %s which copied %d blocks", + diff, + TimeUnit.NANOSECONDS.toMillis(diff), + task, + this.selection.getBlockCount() + ); + this.sendUpdate(); + } + ); + } + + public void runTask() { + Ref ref = this.player.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + + while (true) { + long stamp = this.taskLock.readLock(); + + try { + if (this.tasks.isEmpty()) { + break; + } + } finally { + this.taskLock.unlockRead(stamp); + } + + try { + long stamp2 = this.taskLock.writeLock(); + + BuilderToolsPlugin.QueuedTask task; + try { + task = this.tasks.dequeue(); + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("[%s] Run task from queue: %s, %s, %s", this.getDisplayName(), task, this.taskFuture, this.tasks); + } finally { + this.taskLock.unlockWrite(stamp2); + } + + task.execute(ref, this, store); + } catch (Throwable var16) { + BuilderToolsPlugin.get() + .getLogger() + .at(Level.SEVERE) + .withCause(var16) + .log("Failed to execute builder tools task for: %s", this.getDisplayName()); + } + } + + this.taskFuture = null; + } else { + this.taskFuture = null; + } + } + + public int getTaskCount() { + long stamp = this.taskLock.readLock(); + + int var3; + try { + var3 = this.tasks.size(); + } finally { + this.taskLock.unlockRead(stamp); + } + + return var3; + } + + public int getUndoCount() { + long stamp = this.taskLock.readLock(); + + int var3; + try { + var3 = this.undo.size(); + } finally { + this.taskLock.unlockRead(stamp); + } + + return var3; + } + + public int getRedoCount() { + long stamp = this.taskLock.readLock(); + + int var3; + try { + var3 = this.redo.size(); + } finally { + this.taskLock.unlockRead(stamp); + } + + return var3; + } + + public String getDisplayName() { + return this.playerRef.getUsername(); + } + + @Nonnull + public BuilderToolsUserData getUserData() { + return this.userData; + } + + @Nullable + public CompletableFuture getTaskFuture() { + return this.taskFuture; + } + + public BlockSelection getSelection() { + return this.selection; + } + + public BlockMask getGlobalMask() { + return this.globalMask; + } + + @Nonnull + public Random getRandom() { + return this.random; + } + + public void setSelection(@Nonnull BlockSelection selection) { + this.selection = selection; + } + + public void sendSelectionToClient() { + this.sendUpdate(); + } + + private void sendErrorFeedback(@Nonnull Ref ref, @Nonnull Message message, @Nonnull ComponentAccessor componentAccessor) { + this.sendFeedback(ref, message, "CREATE_ERROR", NotificationStyle.Warning, componentAccessor); + } + + private void sendFeedback( + @Nonnull Ref ref, @Nonnull Message message, @Nullable String sound, @Nonnull ComponentAccessor componentAccessor + ) { + this.sendFeedback(message, componentAccessor); + if (sound != null) { + SoundUtil.playSoundEvent2d(ref, TempAssetIdUtil.getSoundEventIndex(sound), SoundCategory.UI, componentAccessor); + } + } + + private void sendFeedback( + @Nonnull Ref ref, + @Nonnull Message message, + @Nullable String sound, + @Nonnull NotificationStyle notificationStyle, + @Nonnull ComponentAccessor componentAccessor + ) { + this.sendFeedback(message, notificationStyle, componentAccessor); + if (sound != null) { + SoundUtil.playSoundEvent2d(ref, TempAssetIdUtil.getSoundEventIndex(sound), SoundCategory.UI, componentAccessor); + } + } + + private void sendFeedback(@Nonnull Message message, @Nonnull ComponentAccessor componentAccessor) { + BuilderToolsPlugin.sendFeedback(message, this.player, NotificationStyle.Default, componentAccessor); + } + + private void sendFeedback( + @Nonnull Message message, @Nonnull NotificationStyle notificationStyle, @Nonnull ComponentAccessor componentAccessor + ) { + BuilderToolsPlugin.sendFeedback(message, this.player, notificationStyle, componentAccessor); + } + + private void sendFeedback(@Nonnull String key, int total, @Nonnull ComponentAccessor componentAccessor) { + BuilderToolsPlugin.sendFeedback(key, total, this.player, componentAccessor); + } + + private void sendFeedback(@Nonnull String key, int total, int num, @Nonnull ComponentAccessor componentAccessor) { + BuilderToolsPlugin.sendFeedback(key, total, num, this.player, componentAccessor); + } + + public void setActivePrefabPath(UUID path) { + this.activePrefabPath = path; + } + + public UUID getActivePrefabPath() { + return this.activePrefabPath; + } + + @Nullable + public Path getPrefabListRoot() { + return this.prefabListRoot; + } + + public void setPrefabListRoot(@Nullable Path prefabListRoot) { + this.prefabListRoot = prefabListRoot; + } + + @Nullable + public Path getPrefabListPath() { + return this.prefabListPath; + } + + public void setPrefabListPath(@Nullable Path prefabListPath) { + this.prefabListPath = prefabListPath; + } + + @Nullable + public String getPrefabListSearchQuery() { + return this.prefabListSearchQuery; + } + + public void setPrefabListSearchQuery(@Nullable String prefabListSearchQuery) { + this.prefabListSearchQuery = prefabListSearchQuery; + } + + public int edit(@Nonnull Ref ref, @Nonnull BuilderToolOnUseInteraction packet, @Nonnull ComponentAccessor componentAccessor) { + World world = componentAccessor.getExternalData().getWorld(); + UUIDComponent uuidComponent = componentAccessor.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + long start = System.nanoTime(); + + ToolOperation toolOperation; + try { + toolOperation = ToolOperation.fromPacket(ref, this.player, packet, componentAccessor); + } catch (Exception var22) { + this.player.sendMessage(Message.translation("server.builderTools.interaction.toolParseError").param("error", var22.getMessage())); + return 0; + } + + PrototypePlayerBuilderToolSettings protoSettings = ToolOperation.PROTOTYPE_TOOL_SETTINGS.get(uuidComponent.getUuid()); + if (protoSettings != null && toolOperation instanceof PaintOperation) { + BuilderTool builderTool = BuilderTool.getActiveBuilderTool(this.player); + if (protoSettings.isLoadingBrush()) { + return 0; + } + + if (builderTool != null && builderTool.getBrushConfigurationCommand() != null && !builderTool.getBrushConfigurationCommand().isEmpty()) { + String brushConfigId = builderTool.getBrushConfigurationCommand(); + String loadedBrushConfig = protoSettings.getCurrentlyLoadedBrushConfigName(); + if (loadedBrushConfig.equalsIgnoreCase(brushConfigId)) { + toolOperation.executeAsBrushConfig(protoSettings, packet, componentAccessor); + } else { + ScriptedBrushAsset scriptedBrush = ScriptedBrushAsset.get(brushConfigId); + if (scriptedBrush != null) { + protoSettings.setCurrentlyLoadedBrushConfigName(brushConfigId); + BrushConfigCommandExecutor brushConfigCommandExecutor = protoSettings.getBrushConfigCommandExecutor(); + scriptedBrush.loadIntoExecutor(brushConfigCommandExecutor); + protoSettings.setUsePrototypeBrushConfigurations(false); + toolOperation.executeAsBrushConfig(protoSettings, packet, componentAccessor); + } else { + protoSettings.setCurrentlyLoadedBrushConfigName(brushConfigId); + BrushConfigCommandExecutor brushConfigCommandExecutor = protoSettings.getBrushConfigCommandExecutor(); + brushConfigCommandExecutor.getSequentialOperations().clear(); + brushConfigCommandExecutor.getGlobalOperations().clear(); + protoSettings.setLoadingBrush(true); + CommandManager.get().handleCommand(this.player, brushConfigId).thenAccept(unused -> { + PrototypePlayerBuilderToolSettings protoSettingsIntl = ToolOperation.PROTOTYPE_TOOL_SETTINGS.get(uuidComponent.getUuid()); + protoSettingsIntl.setLoadingBrush(false); + protoSettingsIntl.setUsePrototypeBrushConfigurations(false); + toolOperation.executeAsBrushConfig(protoSettingsIntl, packet, componentAccessor); + }); + } + } + + return 0; + } + + if (protoSettings.usePrototypeBrushConfigurations()) { + toolOperation.executeAsBrushConfig(protoSettings, packet, componentAccessor); + return 0; + } + } + + Vector3i currentPosition = toolOperation.getPosition(); + Vector3i lastPosition = protoSettings != null && packet.isHoldDownInteraction ? protoSettings.getLastBrushPosition() : null; + List positionsToExecute = ToolOperation.calculateInterpolatedPositions( + lastPosition, currentPosition, toolOperation.getBrushWidth(), toolOperation.getBrushHeight(), toolOperation.getBrushSpacing() + ); + if (positionsToExecute.isEmpty()) { + return 0; + } else { + for (Vector3i position : positionsToExecute) { + toolOperation.executeAt(position.getX(), position.getY(), position.getZ(), componentAccessor); + } + + if (protoSettings != null) { + if (packet.isHoldDownInteraction) { + protoSettings.setLastBrushPosition(positionsToExecute.get(positionsToExecute.size() - 1)); + } else { + protoSettings.clearLastBrushPosition(); + } + } + + EditOperation edit = toolOperation.getEditOperation(); + BlockSelection before = edit.getBefore(); + BlockSelection after = edit.getAfter(); + this.pushHistory(BuilderToolsPlugin.Action.EDIT, new BlockSelectionSnapshot(before)); + after.placeNoReturn("Use Builder Tool ?/?", this.player, BuilderToolsPlugin.FEEDBACK_CONSUMER, world, componentAccessor); + BuilderToolsPlugin.invalidateWorldMapForSelection(after, world); + long end = System.nanoTime(); + long diff = end - start; + int size = after.getBlockCount(); + int interpolatedCount = positionsToExecute.size(); + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute edit of %d blocks (%d positions)", diff, TimeUnit.NANOSECONDS.toMillis(diff), size, interpolatedCount); + if (protoSettings != null && protoSettings.isShouldShowEditorSettings()) { + this.sendFeedback("Edit", size, componentAccessor); + } + + return size; + } + } + + public void placeBrushConfig( + @Nonnull Ref ref, + long startTime, + @Nonnull BrushConfigEditStore brushConfigEditStore, + @Nonnull ComponentAccessor componentAccessor + ) { + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + World world = componentAccessor.getExternalData().getWorld(); + BlockSelection after = brushConfigEditStore.getAfter(); + BlockSelection before = brushConfigEditStore.getBefore(); + this.pushHistory(BuilderToolsPlugin.Action.EDIT, new BlockSelectionSnapshot(before)); + after.placeNoReturn("Use Builder Tool ?/?", this.player, BuilderToolsPlugin.FEEDBACK_CONSUMER, world, componentAccessor); + BuilderToolsPlugin.invalidateWorldMapForSelection(after, world); + long end = System.nanoTime(); + long diff = end - startTime; + int size = after.getBlockCount(); + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute edit of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), size); + PrototypePlayerBuilderToolSettings prototypePlayerBuilderToolSettings = ToolOperation.PROTOTYPE_TOOL_SETTINGS.get(playerRefComponent.getUuid()); + if (prototypePlayerBuilderToolSettings != null && prototypePlayerBuilderToolSettings.isShouldShowEditorSettings()) { + this.sendFeedback("Edit", size, componentAccessor); + } + } + + public void flood( + @Nonnull EditOperation editOperation, int x, int y, int z, int shapeWidth, int shapeHeight, @Nonnull BlockPattern pattern, int targetBlockId + ) { + int halfWidth = shapeWidth / 2; + int halfHeight = shapeHeight / 2; + Vector3i min = new Vector3i(x - halfWidth, y - halfHeight, z - halfWidth); + Vector3i max = new Vector3i(x + halfWidth, y + halfHeight, z + halfWidth); + OverridableChunkAccessor accessor = editOperation.getAccessor(); + LongOpenHashSet checkedPositions = new LongOpenHashSet(); + LongArrayList floodPositions = new LongArrayList(); + floodPositions.push(BlockUtil.pack(x, y, z)); + + do { + long packedPosition = floodPositions.popLong(); + checkedPositions.add(packedPosition); + int px = BlockUtil.unpackX(packedPosition); + int py = BlockUtil.unpackY(packedPosition); + int pz = BlockUtil.unpackZ(packedPosition); + int blockId = pattern.nextBlock(this.random); + long east = BlockUtil.pack(px + 1, py, pz); + if (this.isFloodPossible(accessor, east, min, max, blockId, targetBlockId) && !checkedPositions.contains(east)) { + floodPositions.push(east); + } + + long west = BlockUtil.pack(px - 1, py, pz); + if (this.isFloodPossible(accessor, west, min, max, blockId, targetBlockId) && !checkedPositions.contains(west)) { + floodPositions.push(west); + } + + long top = BlockUtil.pack(px, py + 1, pz); + if (this.isFloodPossible(accessor, top, min, max, blockId, targetBlockId) && !checkedPositions.contains(top)) { + floodPositions.push(top); + } + + long bottom = BlockUtil.pack(px, py - 1, pz); + if (this.isFloodPossible(accessor, bottom, min, max, blockId, targetBlockId) && !checkedPositions.contains(bottom)) { + floodPositions.push(bottom); + } + + long north = BlockUtil.pack(px, py, pz + 1); + if (this.isFloodPossible(accessor, north, min, max, blockId, targetBlockId) && !checkedPositions.contains(north)) { + floodPositions.push(north); + } + + long south = BlockUtil.pack(px, py, pz - 1); + if (this.isFloodPossible(accessor, south, min, max, blockId, targetBlockId) && !checkedPositions.contains(south)) { + floodPositions.push(south); + } + + if (this.isFloodPossible(accessor, packedPosition, min, max, blockId, targetBlockId)) { + editOperation.setBlock(px, py, pz, blockId); + } + } while (!floodPositions.isEmpty()); + } + + private boolean isFloodPossible( + @Nonnull ChunkAccessor accessor, long blockPosition, @Nonnull Vector3i min, @Nonnull Vector3i max, int blockId, int targetBlockId + ) { + int x = BlockUtil.unpackX(blockPosition); + int y = BlockUtil.unpackY(blockPosition); + int z = BlockUtil.unpackZ(blockPosition); + if (x >= min.getX() && y >= min.getY() && z >= min.getZ() && x <= max.getX() && y <= max.getY() && z <= max.getZ()) { + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + BlockType blockType = assetMap.getAsset(accessor.getBlock(x, y, z)); + return accessor.getBlock(x, y, z) == targetBlockId || blockType.getDrawType() != DrawType.Cube && blockType.getDrawType() != DrawType.CubeWithModel; + } else { + return false; + } + } + + public boolean isAsideAir(@Nonnull ChunkAccessor accessor, int x, int y, int z) { + return accessor.getBlock(x + 1, y, z) <= 0 + || accessor.getBlock(x - 1, y, z) <= 0 + || accessor.getBlock(x, y + 1, z) <= 0 + || accessor.getBlock(x, y - 1, z) <= 0 + || accessor.getBlock(x, y, z + 1) <= 0 + || accessor.getBlock(x, y, z - 1) <= 0; + } + + public boolean isAsideBlock(@Nonnull ChunkAccessor accessor, int x, int y, int z) { + return accessor.getBlock(x, y, z) <= 0 + && ( + accessor.getBlock(x + 1, y, z) > 0 + || accessor.getBlock(x - 1, y, z) > 0 + || accessor.getBlock(x, y + 1, z) > 0 + || accessor.getBlock(x, y - 1, z) > 0 + || accessor.getBlock(x, y, z + 1) > 0 + || accessor.getBlock(x, y, z - 1) > 0 + ); + } + + @Nonnull + public BuilderToolsPlugin.BuilderState.BlocksSampleData getBlocksSampleData(@Nonnull ChunkAccessor accessor, int x, int y, int z, int radius) { + BuilderToolsPlugin.BuilderState.BlocksSampleData data = new BuilderToolsPlugin.BuilderState.BlocksSampleData(); + Int2IntMap blockCounts = new Int2IntOpenHashMap(); + + for (int ix = x - radius; ix <= x + radius; ix++) { + for (int iz = z - radius; iz <= z + radius; iz++) { + for (int iy = y - radius; iy <= y + radius; iy++) { + int currentBlock = accessor.getBlock(ix, iy, iz); + blockCounts.put(currentBlock, blockCounts.getOrDefault(currentBlock, 0) + 1); + } + } + } + + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + + for (it.unimi.dsi.fastutil.ints.Int2IntMap.Entry pair : Int2IntMaps.fastIterable(blockCounts)) { + int block = pair.getIntKey(); + int count = pair.getIntValue(); + if (count > data.mainBlockCount) { + data.mainBlock = block; + data.mainBlockCount = count; + } + + BlockType blockType = assetMap.getAsset(block); + if (count > data.mainBlockNotAirCount && block != 0) { + data.mainBlockNotAir = block; + data.mainBlockNotAirCount = count; + } + } + + return data; + } + + @Nonnull + public BuilderToolsPlugin.BuilderState.SmoothSampleData getBlocksSmoothData(@Nonnull ChunkAccessor accessor, int x, int y, int z) { + BuilderToolsPlugin.BuilderState.SmoothSampleData data = new BuilderToolsPlugin.BuilderState.SmoothSampleData(); + Int2IntMap blockCounts = new Int2IntOpenHashMap(); + int kernelIndex = 0; + + for (int ix = x - 1; ix <= x + 1; ix++) { + for (int iy = y - 1; iy <= y + 1; iy++) { + for (int iz = z - 1; iz <= z + 1; iz++) { + int currentBlock = accessor.getBlock(ix, iy, iz); + blockCounts.put(currentBlock, blockCounts.getOrDefault(currentBlock, 0) + BuilderToolsPlugin.SMOOTHING_KERNEL[kernelIndex++]); + } + } + } + + float solidCount = 0.0F; + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + + for (it.unimi.dsi.fastutil.ints.Int2IntMap.Entry pair : Int2IntMaps.fastIterable(blockCounts)) { + int block = pair.getIntKey(); + int count = pair.getIntValue(); + BlockType blockType = assetMap.getAsset(block); + if (blockType.getMaterial() == BlockMaterial.Solid) { + solidCount += count; + if (count > data.solidBlockCount) { + data.solidBlock = block; + data.solidBlockCount = count; + } + } else if (count > data.fillerBlockCount) { + data.fillerBlock = block; + data.fillerBlockCount = count; + } + } + + data.solidStrength = solidCount / 27.0F; + return data; + } + + public void editLine( + int x1, + int y1, + int z1, + int x2, + int y2, + int z2, + BlockPattern material, + int lineWidth, + int lineHeight, + int wallThickness, + BrushShape shape, + BrushOrigin origin, + int spacing, + int density, + ComponentAccessor componentAccessor + ) { + this.editLine( + x1, y1, z1, x2, y2, z2, material, lineWidth, lineHeight, wallThickness, shape, origin, spacing, density, this.getGlobalMask(), componentAccessor + ); + } + + public void editLine( + int x1, + int y1, + int z1, + int x2, + int y2, + int z2, + BlockPattern material, + int lineWidth, + int lineHeight, + int wallThickness, + BrushShape shape, + BrushOrigin origin, + int spacing, + int density, + @Nullable BlockMask mask, + ComponentAccessor componentAccessor + ) { + World world = componentAccessor.getExternalData().getWorld(); + long start = System.nanoTime(); + float halfWidth = lineWidth / 2.0F; + float halfHeight = lineHeight / 2.0F; + int iHalfWidth = MathUtil.fastCeil(halfWidth); + int iHalfHeight = MathUtil.fastCeil(halfHeight); + int maxRadius = Math.max(iHalfWidth, iHalfHeight); + Vector3i min = new Vector3i(Math.min(x1, x2) - maxRadius, Math.min(y1, y2) - maxRadius, Math.min(z1, z2) - maxRadius); + Vector3i max = new Vector3i(Math.max(x1, x2) + maxRadius, Math.max(y1, y2) + maxRadius, Math.max(z1, z2) + maxRadius); + BlockSelection before = new BlockSelection(); + before.setPosition(x1, y1, z1); + before.setSelectionArea(min, max); + this.pushHistory(BuilderToolsPlugin.Action.EDIT_LINE, new BlockSelectionSnapshot(before)); + BlockSelection after = new BlockSelection(before); + int originOffset = 0; + if (origin == BrushOrigin.Bottom) { + originOffset = iHalfHeight + 1; + } else if (origin == BrushOrigin.Top) { + originOffset = -iHalfHeight; + } + + float innerHalfWidth = Math.max(0.0F, halfWidth - wallThickness); + float innerHalfHeight = Math.max(0.0F, halfHeight - wallThickness); + Predicate isInShape = this.createShapePredicate(shape, halfWidth, halfHeight, innerHalfWidth, innerHalfHeight, wallThickness > 0); + int lineDistX = x2 - x1; + int lineDistZ = z2 - z1; + int halfLineDistX = lineDistX / 2; + int halfLineDistZ = lineDistZ / 2; + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords( + world, x1 + halfLineDistX, z1 + halfLineDistZ, Math.max(Math.abs(lineDistX), Math.abs(lineDistZ)) + maxRadius + 1 + ); + Vector3i rel = new Vector3i(); + LineIterator line = new LineIterator(x1, y1, z1, x2, y2, z2); + int stepCount = 0; + + while (line.hasNext()) { + Vector3i coord = line.next(); + if (stepCount % spacing != 0) { + stepCount++; + } else { + stepCount++; + + for (int sx = -iHalfWidth; sx <= iHalfWidth; sx++) { + for (int sz = iHalfWidth; sz >= -iHalfWidth; sz--) { + int blockX = coord.getX() + sx; + int blockZ = coord.getZ() + sz; + WorldChunk chunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(blockX, blockZ)); + + for (int sy = -iHalfHeight; sy <= iHalfHeight; sy++) { + rel.assign(sx, sy, sz); + if (isInShape.test(rel)) { + int blockY = coord.getY() + sy + originOffset; + int currentBlockId = chunk.getBlock(blockX, blockY, blockZ); + int currentFluidId = chunk.getFluidId(blockX, blockY, blockZ); + if ((mask == null || !mask.isExcluded(accessor, blockX, blockY, blockZ, min, max, currentBlockId, currentFluidId)) + && this.random.nextInt(100) < density) { + int blockId = material.nextBlock(this.random); + before.addBlockAtWorldPos( + blockX, + blockY, + blockZ, + currentBlockId, + chunk.getRotationIndex(blockX, blockY, blockZ), + chunk.getFiller(blockX, blockY, blockZ), + chunk.getSupportValue(blockX, blockY, blockZ), + chunk.getBlockComponentHolder(blockX, blockY, blockZ) + ); + after.addBlockAtWorldPos(blockX, blockY, blockZ, blockId, 0, 0, 0); + } + } + } + } + } + } + } + + after.placeNoReturn("Edit 1/1", this.player, BuilderToolsPlugin.FEEDBACK_CONSUMER, world, componentAccessor); + BuilderToolsPlugin.invalidateWorldMapForSelection(after, world); + long end = System.nanoTime(); + long diff = end - start; + int size = after.getBlockCount(); + double length = new Vector3i(x1, y1, z1).distanceTo(x2, y2, z2); + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute editLine of %d blocks with length %s", diff, TimeUnit.NANOSECONDS.toMillis(diff), size, length); + this.sendFeedback(Message.translation("server.builderTools.drawLineOf").param("count", length), componentAccessor); + } + + private Predicate createShapePredicate( + BrushShape shape, float halfWidth, float halfHeight, float innerHalfWidth, float innerHalfHeight, boolean hollow + ) { + float hw = halfWidth + 0.41F; + float hh = halfHeight + 0.41F; + float ihw = innerHalfWidth + 0.41F; + float ihh = innerHalfHeight + 0.41F; + + return switch (shape) { + case Cube -> coord -> { + double ax = Math.abs(coord.getX()); + double ay = Math.abs(coord.getY()); + double az = Math.abs(coord.getZ()); + boolean inOuter = ax <= hw && ay <= hh && az <= hw; + if (!hollow) { + return inOuter; + } else { + boolean inInner = ax < ihw && ay < ihh && az < ihw; + return inOuter && !inInner; + } + }; + case Sphere -> coord -> { + double sx = coord.getX(); + double sy = coord.getY(); + double sz = coord.getZ(); + double outerDist = sx * sx / (hw * hw) + sy * sy / (hh * hh) + sz * sz / (hw * hw); + boolean inOuter = outerDist <= 1.0; + if (!hollow) { + return inOuter; + } else { + double innerDist = sx * sx / (ihw * ihw) + sy * sy / (ihh * ihh) + sz * sz / (ihw * ihw); + boolean inInner = ihw > 0.0F && ihh > 0.0F && innerDist <= 1.0; + return inOuter && !inInner; + } + }; + case Cylinder -> coord -> { + double sx = coord.getX(); + double sy = coord.getY(); + double sz = coord.getZ(); + double outerRadialDist = (sx * sx + sz * sz) / (hw * hw); + boolean inOuterRadius = outerRadialDist <= 1.0 && Math.abs(sy) <= hh; + if (!hollow) { + return inOuterRadius; + } else { + double innerRadialDist = (sx * sx + sz * sz) / (ihw * ihw); + boolean inInnerRadius = ihw > 0.0F && innerRadialDist <= 1.0 && Math.abs(sy) < ihh; + return inOuterRadius && !inInnerRadius; + } + }; + case Cone -> coord -> { + double sx = coord.getX(); + double sy = coord.getY(); + double sz = coord.getZ(); + double normalizedY = (sy + hh) / (2.0F * hh); + if (!(normalizedY < 0.0) && !(normalizedY > 1.0)) { + double currentRadius = hw * (1.0 - normalizedY); + double radialDist = Math.sqrt(sx * sx + sz * sz); + boolean inOuter = radialDist <= currentRadius; + if (!hollow) { + return inOuter; + } else { + double innerRadius = Math.max(0.0, currentRadius - (hw - ihw)); + boolean inInner = radialDist < innerRadius; + return inOuter && !inInner; + } + } else { + return false; + } + }; + case InvertedCone -> coord -> { + double sx = coord.getX(); + double sy = coord.getY(); + double sz = coord.getZ(); + double normalizedY = (sy + hh) / (2.0F * hh); + if (!(normalizedY < 0.0) && !(normalizedY > 1.0)) { + double currentRadius = hw * normalizedY; + double radialDist = Math.sqrt(sx * sx + sz * sz); + boolean inOuter = radialDist <= currentRadius; + if (!hollow) { + return inOuter; + } else { + double innerRadius = Math.max(0.0, currentRadius - (hw - ihw)); + boolean inInner = radialDist < innerRadius; + return inOuter && !inInner; + } + } else { + return false; + } + }; + case Pyramid -> coord -> { + double sx = coord.getX(); + double sy = coord.getY(); + double sz = coord.getZ(); + double normalizedY = (sy + hh) / (2.0F * hh); + if (!(normalizedY < 0.0) && !(normalizedY > 1.0)) { + double currentHalfSize = hw * (1.0 - normalizedY); + boolean inOuter = Math.abs(sx) <= currentHalfSize && Math.abs(sz) <= currentHalfSize; + if (!hollow) { + return inOuter; + } else { + double innerHalfSize = Math.max(0.0, currentHalfSize - (hw - ihw)); + boolean inInner = Math.abs(sx) < innerHalfSize && Math.abs(sz) < innerHalfSize; + return inOuter && !inInner; + } + } else { + return false; + } + }; + case InvertedPyramid -> coord -> { + double sx = coord.getX(); + double sy = coord.getY(); + double sz = coord.getZ(); + double normalizedY = (sy + hh) / (2.0F * hh); + if (!(normalizedY < 0.0) && !(normalizedY > 1.0)) { + double currentHalfSize = hw * normalizedY; + boolean inOuter = Math.abs(sx) <= currentHalfSize && Math.abs(sz) <= currentHalfSize; + if (!hollow) { + return inOuter; + } else { + double innerHalfSize = Math.max(0.0, currentHalfSize - (hw - ihw)); + boolean inInner = Math.abs(sx) < innerHalfSize && Math.abs(sz) < innerHalfSize; + return inOuter && !inInner; + } + } else { + return false; + } + }; + case Dome -> coord -> { + double sx = coord.getX(); + double sy = coord.getY(); + double sz = coord.getZ(); + if (sy < 0.0) { + return false; + } else { + double outerDist = sx * sx / (hw * hw) + sy * sy / (hh * hh) + sz * sz / (hw * hw); + boolean inOuter = outerDist <= 1.0; + if (!hollow) { + return inOuter; + } else { + double innerDist = sx * sx / (ihw * ihw) + sy * sy / (ihh * ihh) + sz * sz / (ihw * ihw); + boolean inInner = ihw > 0.0F && ihh > 0.0F && innerDist <= 1.0; + return inOuter && !inInner; + } + } + }; + case InvertedDome -> coord -> { + double sx = coord.getX(); + double sy = coord.getY(); + double sz = coord.getZ(); + if (sy > 0.0) { + return false; + } else { + double outerDist = sx * sx / (hw * hw) + sy * sy / (hh * hh) + sz * sz / (hw * hw); + boolean inOuter = outerDist <= 1.0; + if (!hollow) { + return inOuter; + } else { + double innerDist = sx * sx / (ihw * ihw) + sy * sy / (ihh * ihh) + sz * sz / (ihw * ihw); + boolean inInner = ihw > 0.0F && ihh > 0.0F && innerDist <= 1.0; + return inOuter && !inInner; + } + } + }; + case Diamond -> coord -> { + double sx = coord.getX(); + double sy = coord.getY(); + double sz = coord.getZ(); + double normalizedY = Math.abs(sy) / hh; + if (normalizedY > 1.0) { + return false; + } else { + double currentHalfSize = hw * (1.0 - normalizedY); + boolean inOuter = Math.abs(sx) <= currentHalfSize && Math.abs(sz) <= currentHalfSize; + if (!hollow) { + return inOuter; + } else { + double innerHalfSize = Math.max(0.0, currentHalfSize - (hw - ihw)); + boolean inInner = Math.abs(sx) < innerHalfSize && Math.abs(sz) < innerHalfSize; + return inOuter && !inInner; + } + } + }; + case Torus -> coord -> { + double sx = coord.getX(); + double sy = coord.getY(); + double sz = coord.getZ(); + double minorRadius = Math.max(1.0F, hh / 2.0F); + double majorRadius = Math.max(1.0, hw - minorRadius); + double minorRadiusAdjusted = minorRadius + 0.41F; + double distFromCenter = Math.sqrt(sx * sx + sz * sz); + double distFromRing = distFromCenter - majorRadius; + double distFromTube = Math.sqrt(distFromRing * distFromRing + sy * sy); + boolean inOuter = distFromTube <= minorRadiusAdjusted; + if (!hollow) { + return inOuter; + } else { + double innerMinorRadius = Math.max(0.0, minorRadiusAdjusted - (ihw > 0.0F ? hw - ihw : 0.0F)); + boolean inInner = innerMinorRadius > 0.0 && distFromTube < innerMinorRadius; + return inOuter && !inInner; + } + }; + }; + } + + public void extendFace( + int x, + int y, + int z, + int normalX, + int normalY, + int normalZ, + int extrudeDepth, + int radiusAllowed, + int blockId, + @Nullable Vector3i min, + @Nullable Vector3i max, + ComponentAccessor componentAccessor + ) { + World world = componentAccessor.getExternalData().getWorld(); + long start = System.nanoTime(); + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords(world, x, z, radiusAllowed); + if (min == null) { + min = new Vector3i(x - radiusAllowed, y - radiusAllowed, z - radiusAllowed); + } else { + int minX = min.getX(); + if (x - radiusAllowed > minX) { + minX = x - radiusAllowed; + } + + int minY = min.getY(); + if (y - radiusAllowed > minY) { + minY = y - radiusAllowed; + } + + int minZ = min.getZ(); + if (z - radiusAllowed > minZ) { + minZ = z - radiusAllowed; + } + + min = new Vector3i(minX, minY, minZ); + } + + if (max == null) { + max = new Vector3i(x + radiusAllowed, y + radiusAllowed, z + radiusAllowed); + } else { + int maxX = max.getX(); + if (x + radiusAllowed < maxX) { + maxX = x + radiusAllowed; + } + + int maxY = max.getY(); + if (y + radiusAllowed < maxY) { + maxY = y + radiusAllowed; + } + + int maxZ = max.getZ(); + if (z + radiusAllowed < maxZ) { + maxZ = z + radiusAllowed; + } + + max = new Vector3i(maxX, maxY, maxZ); + } + + int totalBlocks = (max.getX() - min.getX() + 1) * (max.getZ() - min.getZ() + 1) * (max.getY() - min.getY() + 1); + BlockSelection before = new BlockSelection(totalBlocks, 0); + before.setPosition(x + normalX, y + normalY, z + normalZ); + before.setSelectionArea(min, max); + this.pushHistory(BuilderToolsPlugin.Action.EXTRUDE, new BlockSelectionSnapshot(before)); + BlockSelection after = new BlockSelection(totalBlocks, 0); + after.copyPropertiesFrom(before); + this.extendFaceFindBlocks( + accessor, BlockType.getAssetMap(), before, after, x + normalX, y + normalY, z + normalZ, normalX, normalY, normalZ, extrudeDepth, blockId, min, max + ); + Vector3i offset = new Vector3i(0, 0, 0); + + for (int i = 0; i < extrudeDepth; i++) { + offset.x = normalX * i; + offset.y = normalY * i; + offset.z = normalZ * i; + after.placeNoReturn("Set", this.player, BuilderToolsPlugin.FEEDBACK_CONSUMER, world, offset, null, componentAccessor); + } + + BuilderToolsPlugin.invalidateWorldMapForSelection(after, world); + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute set of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), after.getBlockCount()); + this.sendUpdate(); + this.sendArea(); + } + + private void extendFaceFindBlocks( + @Nonnull ChunkAccessor accessor, + @Nonnull BlockTypeAssetMap assetMap, + @Nonnull BlockSelection before, + @Nonnull BlockSelection after, + int x, + int y, + int z, + int normalX, + int normalY, + int normalZ, + int extrudeDepth, + int blockId, + @Nonnull Vector3i min, + @Nonnull Vector3i max + ) { + if (x >= min.getX() && x <= max.getX()) { + if (y >= min.getY() && y <= max.getY()) { + if (z >= min.getZ() && z <= max.getZ()) { + int block = accessor.getBlock(x, y, z); + int testBlock = accessor.getBlock(x - normalX, y - normalY, z - normalZ); + BlockType testBlockType = assetMap.getAsset(testBlock); + if (testBlockType != null && (testBlockType.getDrawType() == DrawType.Cube || testBlockType.getDrawType() == DrawType.CubeWithModel)) { + if (!before.hasBlockAtWorldPos(x, y, z)) { + BlockAccessor blocks = accessor.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(x, z)); + if (blocks != null) { + before.addBlockAtWorldPos( + x, + y, + z, + block, + blocks.getRotationIndex(x, y, z), + blocks.getFiller(x, y, z), + blocks.getSupportValue(x, y, z), + blocks.getBlockComponentHolder(x, y, z) + ); + after.addBlockAtWorldPos(x, y, z, blockId, 0, 0, 0); + + for (Vector3i side : Vector3i.BLOCK_SIDES) { + if ((normalX == 0 || side.getX() == 0) && (normalY == 0 || side.getY() == 0) && (normalZ == 0 || side.getZ() == 0)) { + this.extendFaceFindBlocks( + accessor, + assetMap, + before, + after, + x + side.getX(), + y + side.getY(), + z + side.getZ(), + normalX, + normalY, + normalZ, + extrudeDepth, + blockId, + min, + max + ); + } + } + } + } + } + } + } + } + } + + public void update(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax) { + if (this.selection == null) { + this.selection = new BlockSelection(); + } + + this.pushHistory(BuilderToolsPlugin.Action.UPDATE_SELECTION, new ClipboardBoundsSnapshot(this.selection)); + this.selection.setSelectionArea(new Vector3i(xMin, yMin, zMin), new Vector3i(xMax, yMax, zMax)); + } + + public void tint(@Nonnull Ref ref, int color, @Nonnull ComponentAccessor componentAccessor) { + if (this.selection == null) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelection"), componentAccessor); + } else if (!this.selection.hasSelectionBounds()) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionBounds"), componentAccessor); + } else { + World world = componentAccessor.getExternalData().getWorld(); + int count = 0; + int minX = this.selection.getSelectionMin().getX(); + int minZ = this.selection.getSelectionMin().getZ(); + int maxX = this.selection.getSelectionMax().getX(); + int maxZ = this.selection.getSelectionMax().getZ(); + + for (int cx = ChunkUtil.chunkCoordinate(minX); cx <= ChunkUtil.chunkCoordinate(maxX); cx++) { + for (int cz = ChunkUtil.chunkCoordinate(minZ); cz <= ChunkUtil.chunkCoordinate(maxZ); cz++) { + int startX = Math.max(0, minX - ChunkUtil.minBlock(cx)); + int startZ = Math.max(0, minZ - ChunkUtil.minBlock(cz)); + int endX = Math.min(32, maxX - ChunkUtil.minBlock(cx)); + int endZ = Math.min(32, maxZ - ChunkUtil.minBlock(cz)); + WorldChunk chunk = world.getNonTickingChunk(ChunkUtil.indexChunk(cx, cz)); + + for (int z = startZ; z < endZ; z++) { + for (int x = startX; x < endX; x++) { + chunk.getBlockChunk().setTint(x, z, color); + count++; + } + } + + world.getNotificationHandler().updateChunk(chunk.getIndex()); + } + } + + this.sendFeedback(Message.translation("server.builderTools.setColumnsTint").param("count", count), componentAccessor); + } + } + + public void tint(int x, int y, int z, int color, @Nonnull BrushShape shape, int shapeRange, ComponentAccessor componentAccessor) { + if (y >= 0 && y < 320) { + World world = componentAccessor.getExternalData().getWorld(); + LongSet dirtyChunks = new LongOpenHashSet(); + AtomicInteger count = new AtomicInteger(0); + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords(world, x, z, shapeRange + 1); + TriIntObjPredicate tintBlock = (pxx, py, pzx, aVoid) -> { + WorldChunk chunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(pxx, pzx)); + chunk.getBlockChunk().setTint(pxx, pzx, color); + dirtyChunks.add(chunk.getIndex()); + count.getAndIncrement(); + return true; + }; + if (shapeRange <= 1) { + tintBlock.test(x, y, z, null); + } else { + int radiusXZ = shapeRange / 2; + switch (shape) { + case Cube: + case Pyramid: + case InvertedPyramid: + label33: + for (int px = -radiusXZ; px <= radiusXZ; px++) { + for (int pz = -radiusXZ; pz <= radiusXZ; pz++) { + if (!tintBlock.test(x + px, y, z + pz, null)) { + break label33; + } + } + } + break; + case Sphere: + case Cylinder: + case Cone: + case InvertedCone: + BlockSphereUtil.forEachBlock(x, y, z, radiusXZ, 1, radiusXZ, null, tintBlock); + break; + default: + this.sendFeedback(Message.translation("server.builderTools.errorWithUsedShape"), componentAccessor); + return; + } + } + + dirtyChunks.forEach(value -> world.getNotificationHandler().updateChunk(value)); + this.sendFeedback(Message.translation("server.builderTools.setColumnsTint").param("count", count.intValue()), componentAccessor); + } + } + + public void environment(@Nonnull Ref ref, int environmentId, @Nonnull ComponentAccessor componentAccessor) { + if (this.selection == null) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelection"), componentAccessor); + } else if (!this.selection.hasSelectionBounds()) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionBounds"), componentAccessor); + } else { + World world = componentAccessor.getExternalData().getWorld(); + LongSet dirtyChunks = new LongOpenHashSet(); + int count = 0; + + for (int x = this.selection.getSelectionMin().getX(); x < this.selection.getSelectionMax().getX(); x++) { + for (int z = this.selection.getSelectionMin().getZ(); z < this.selection.getSelectionMax().getZ(); z++) { + WorldChunk chunk = world.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + dirtyChunks.add(chunk.getIndex()); + + for (int y = this.selection.getSelectionMin().getY(); y < this.selection.getSelectionMax().getY(); y++) { + chunk.getBlockChunk().setEnvironment(x, y, z, environmentId); + count++; + } + } + } + + dirtyChunks.forEach(value -> world.getNotificationHandler().updateChunk(value)); + this.sendFeedback(Message.translation("server.builderTools.setEnvironment").param("count", count), componentAccessor); + } + } + + public int copyOrCut( + @Nonnull Ref ref, + int xMin, + int yMin, + int zMin, + int xMax, + int yMax, + int zMax, + int settings, + @Nonnull ComponentAccessor componentAccessor + ) throws PrefabCopyException { + World world = componentAccessor.getExternalData().getWorld(); + long start = System.nanoTime(); + if (this.selection == null) { + this.selection = new BlockSelection(); + } + + BlockSelection before = null; + BlockSelection after = null; + List> snapshots = Collections.emptyList(); + boolean cut = (settings & 2) != 0; + boolean empty = (settings & 4) != 0; + boolean blocks = (settings & 8) != 0; + boolean entities = (settings & 16) != 0; + boolean keepAnchors = (settings & 64) != 0; + int width = xMax - xMin; + int height = yMax - yMin; + int depth = zMax - zMin; + int halfWidth = width / 2; + int halfDepth = depth / 2; + if (cut) { + before = new BlockSelection(); + before.setPosition(xMin + halfWidth, yMin, zMin + halfDepth); + after = new BlockSelection(before); + snapshots = new ObjectArrayList<>(); + this.pushHistory(BuilderToolsPlugin.Action.CUT_COPY, ClipboardContentsSnapshot.copyOf(this.selection)); + } else { + this.pushHistory(BuilderToolsPlugin.Action.COPY, ClipboardContentsSnapshot.copyOf(this.selection)); + } + + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords(world, xMin + halfWidth, zMin + halfDepth, Math.max(width, depth)); + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + int editorBlock = assetMap.getIndex("Editor_Block"); + if (editorBlock == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! Editor_Block"); + } else { + int editorBlockPrefabAir = assetMap.getIndex("Editor_Empty"); + if (editorBlockPrefabAir == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! Editor_Empty"); + } else { + int editorBlockPrefabAnchor = assetMap.getIndex("Editor_Anchor"); + if (editorBlockPrefabAnchor == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! Editor_Anchor"); + } else { + Set anchors = new HashSet<>(); + Vector3i min = Vector3i.min(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + Vector3i max = Vector3i.max(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + this.selection = new BlockSelection(); + this.selection.setPosition(xMin + halfWidth, yMin, zMin + halfDepth); + this.selection.setSelectionArea(min, max); + int count = 0; + int counter = 0; + int top = Math.max(yMin, yMax); + int bottom = Math.min(yMin, yMax); + int totalBlocks = (width + 1) * (depth + 1) * (top - bottom + 1); + + for (int x = xMin; x <= xMax; x++) { + for (int z = zMin; z <= zMax; z++) { + WorldChunk chunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + Store store = chunk.getReference().getStore(); + ChunkColumn chunkColumn = store.getComponent(chunk.getReference(), ChunkColumn.getComponentType()); + int lastSection = -1; + BlockPhysics blockPhysics = null; + + for (int y = top; y >= bottom; y--) { + int block = chunk.getBlock(x, y, z); + int fluid = chunk.getFluidId(x, y, z); + if (lastSection != ChunkUtil.chunkCoordinate(y)) { + lastSection = ChunkUtil.chunkCoordinate(y); + Ref section = chunkColumn.getSection(lastSection); + if (section != null) { + blockPhysics = store.getComponent(section, BlockPhysics.getComponentType()); + } else { + blockPhysics = null; + } + } + + if (blocks && cut && (block != 0 || fluid != 0 || empty)) { + before.copyFromAtWorld(x, y, z, chunk, blockPhysics); + after.addEmptyAtWorldPos(x, y, z); + } + + if (block == editorBlockPrefabAnchor && !keepAnchors) { + anchors.add(new Vector3i(x, y, z)); + this.selection.setAnchorAtWorldPos(x, y, z); + if (blocks) { + int id = BuilderToolsPlugin.getNonEmptyNeighbourBlock(accessor, x, y, z); + if (id > 0 && id != editorBlockPrefabAir) { + this.selection.addBlockAtWorldPos(x, y, z, id, 0, 0, 0); + count++; + } else if (id == editorBlockPrefabAir) { + this.selection.addBlockAtWorldPos(x, y, z, 0, 0, 0, 0); + count++; + } + } + } else if (blocks && (block != 0 || fluid != 0 || empty) && block != editorBlock) { + if (block == editorBlockPrefabAir) { + this.selection.addBlockAtWorldPos(x, y, z, 0, 0, 0, 0); + } else { + this.selection.copyFromAtWorld(x, y, z, chunk, blockPhysics); + } + + count++; + } + + counter++; + this.sendFeedback(cut ? "Gather 1/2" : "Gather 1/1", totalBlocks, counter, componentAccessor); + } + } + } + + if (anchors.size() > 1) { + StringBuilder sb = new StringBuilder("Anchors: "); + boolean first = true; + + for (Vector3i anchor : anchors) { + if (!first) { + sb.append(", "); + } + + first = false; + sb.append('[').append(anchor.getX()).append(", ").append(anchor.getY()).append(", ").append(anchor.getZ()).append(']'); + } + + throw new PrefabCopyException("Prefab has multiple anchor blocks!\n" + sb); + } else { + if (entities) { + Store store = world.getEntityStore().getStore(); + ArrayList> entitiesToRemove = cut ? new ArrayList<>() : null; + BuilderToolsPlugin.forEachCopyableInSelection(world, xMin, yMin, zMin, width, height, depth, e -> { + Holder holder = store.copyEntity(e); + this.selection.addEntityFromWorld(holder); + if (cut) { + snapshots.add(new EntityRemoveSnapshot(e)); + entitiesToRemove.add(e); + } + }); + if (cut && entitiesToRemove != null) { + for (Ref e : entitiesToRemove) { + store.removeEntity(e, RemoveReason.UNLOAD); + } + } + } + + if (cut) { + snapshots.add(new BlockSelectionSnapshot(before)); + this.pushHistory(BuilderToolsPlugin.Action.CUT_REMOVE, snapshots); + } + + if (after != null) { + after.placeNoReturn("Cut 2/2", this.player, BuilderToolsPlugin.FEEDBACK_CONSUMER, world, componentAccessor); + BuilderToolsPlugin.invalidateWorldMapForSelection(after, world); + } + + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute copy of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), count); + if (cut) { + this.sendUpdate(); + } else { + this.player.getPlayerConnection().write(Objects.requireNonNullElseGet(this.selection, BlockSelection::new).toPacketWithSelection()); + } + + int entityCount = entities ? this.selection.getEntityCount() : 0; + String translationKey; + if (cut) { + translationKey = entityCount > 0 ? "server.builderTools.cutWithEntities" : "server.builderTools.cut"; + } else { + translationKey = entityCount > 0 ? "server.builderTools.copiedWithEntities" : "server.builderTools.copied"; + } + + this.sendFeedback( + ref, + Message.translation(translationKey).param("blockCount", count).param("entityCount", entityCount), + cut ? "SFX_CREATE_CUT" : "SFX_CREATE_COPY", + componentAccessor + ); + return count; + } + } + } + } + } + + public int clear(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, @Nonnull ComponentAccessor componentAccessor) { + World world = componentAccessor.getExternalData().getWorld(); + long start = System.nanoTime(); + BlockSelection before = new BlockSelection(); + int width = xMax - xMin; + int depth = zMax - zMin; + int halfWidth = width / 2; + int halfDepth = depth / 2; + before.setPosition(xMin + halfWidth, yMin, zMin + halfDepth); + before.setSelectionArea(new Vector3i(xMin, yMin, zMin), new Vector3i(xMax, yMax, zMax)); + this.pushHistory(BuilderToolsPlugin.Action.CLEAR, new BlockSelectionSnapshot(before)); + BlockSelection after = new BlockSelection(before); + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords(world, xMin + halfWidth, zMin + halfDepth, Math.max(width, depth)); + int top = Math.max(yMin, yMax); + int bottom = Math.min(yMin, yMax); + int height = top - bottom; + int totalBlocks = (width + 1) * (depth + 1) * (height + 1); + int counter = 0; + + for (int x = xMin; x <= xMax; x++) { + for (int z = zMin; z <= zMax; z++) { + WorldChunk chunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + Store store = chunk.getReference().getStore(); + ChunkColumn chunkColumn = store.getComponent(chunk.getReference(), ChunkColumn.getComponentType()); + int lastSection = -1; + BlockPhysics blockPhysics = null; + + for (int y = top; y >= bottom; y--) { + int block = chunk.getBlock(x, y, z); + int fluid = chunk.getFluidId(x, y, z); + if (lastSection != ChunkUtil.chunkCoordinate(y)) { + lastSection = ChunkUtil.chunkCoordinate(y); + Ref section = chunkColumn.getSection(lastSection); + if (section != null) { + blockPhysics = store.getComponent(section, BlockPhysics.getComponentType()); + } else { + blockPhysics = null; + } + } + + if (block != 0 || fluid != 0) { + before.copyFromAtWorld(x, y, z, chunk, blockPhysics); + after.addEmptyAtWorldPos(x, y, z); + } + + this.sendFeedback("Gather 1/2", totalBlocks, ++counter, componentAccessor); + } + } + } + + after.placeNoReturn("Clear 2/2", this.player, BuilderToolsPlugin.FEEDBACK_CONSUMER, world, componentAccessor); + BuilderToolsPlugin.invalidateWorldMapForSelection(after, world); + long end = System.nanoTime(); + long diff = end - start; + int size = after.getBlockCount(); + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute clear of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), size); + this.sendFeedback("Clear", size, componentAccessor); + return size; + } + + public static RotationTuple transformRotation(RotationTuple prevRot, Matrix4d transformationMatrix) { + Vector3f forwardVec = new Vector3f(1.0F, 0.0F, 0.0F); + Vector3f upVec = new Vector3f(0.0F, 1.0F, 0.0F); + forwardVec = Rotation.rotate(forwardVec, prevRot.yaw(), prevRot.pitch(), prevRot.roll()); + upVec = Rotation.rotate(upVec, prevRot.yaw(), prevRot.pitch(), prevRot.roll()); + Vector3f newForward = transformationMatrix.multiplyDirection(forwardVec.toVector3d()).toVector3f(); + Vector3f newUp = transformationMatrix.multiplyDirection(upVec.toVector3d()).toVector3f(); + float bestScore = Float.MIN_VALUE; + RotationTuple bestRot = prevRot; + + for (RotationTuple rotation : RotationTuple.VALUES) { + Vector3f rotForward = Rotation.rotate(new Vector3f(1.0F, 0.0F, 0.0F), rotation.yaw(), rotation.pitch(), rotation.roll()); + Vector3f rotUp = Rotation.rotate(new Vector3f(0.0F, 1.0F, 0.0F), rotation.yaw(), rotation.pitch(), rotation.roll()); + float score = rotForward.dot(newForward) + rotUp.dot(newUp); + if (score > bestScore) { + bestScore = score; + bestRot = rotation; + } + } + + return bestRot; + } + + public void transformThenPasteClipboard( + @Nonnull BlockChange[] blockChanges, + @Nullable PrototypePlayerBuilderToolSettings.FluidChange[] fluidChanges, + @Nonnull Matrix4d transformationMatrix, + @Nonnull Vector3f rotationOrigin, + @Nonnull Vector3i initialPastePoint, + ComponentAccessor componentAccessor + ) { + World world = componentAccessor.getExternalData().getWorld(); + long start = System.nanoTime(); + int yOffsetOutOfGround = 0; + + for (BlockChange blockChange : blockChanges) { + if (blockChange.y < 0 && Math.abs(blockChange.y) > yOffsetOutOfGround) { + yOffsetOutOfGround = Math.abs(blockChange.y); + } + } + + Vector4d translationEndResult = new Vector4d(0.0, 0.0, 0.0, 1.0); + transformationMatrix.multiply(translationEndResult); + translationEndResult.x = translationEndResult.x + rotationOrigin.x; + translationEndResult.y = translationEndResult.y + rotationOrigin.y; + translationEndResult.z = translationEndResult.z + rotationOrigin.z; + BlockSelection before = new BlockSelection(); + before.setPosition((int)translationEndResult.x, (int)translationEndResult.y, (int)translationEndResult.z); + BlockSelection after = new BlockSelection(before); + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords(world, (int)translationEndResult.x, (int)translationEndResult.z, 50); + int minX = Integer.MAX_VALUE; + int minY = Integer.MAX_VALUE; + int minZ = Integer.MAX_VALUE; + int maxX = Integer.MIN_VALUE; + int maxY = Integer.MIN_VALUE; + int maxZ = Integer.MIN_VALUE; + Vector4d mutable4d = new Vector4d(0.0, 0.0, 0.0, 1.0); + + for (BlockChange blockChangex : blockChanges) { + mutable4d.assign( + blockChangex.x - rotationOrigin.x + initialPastePoint.x + 0.5, + blockChangex.y - rotationOrigin.y + initialPastePoint.y + 0.5 + yOffsetOutOfGround, + blockChangex.z - rotationOrigin.z + initialPastePoint.z + 0.5, + 1.0 + ); + transformationMatrix.multiply(mutable4d); + Vector3i rotatedLocation = new Vector3i( + (int)Math.floor(mutable4d.x + 0.1 + rotationOrigin.x - 0.5), + (int)Math.floor(mutable4d.y + 0.1 + rotationOrigin.y - 0.5), + (int)Math.floor(mutable4d.z + 0.1 + rotationOrigin.z - 0.5) + ); + minX = Math.min(minX, rotatedLocation.x); + minY = Math.min(minY, rotatedLocation.y); + minZ = Math.min(minZ, rotatedLocation.z); + maxX = Math.max(maxX, rotatedLocation.x); + maxY = Math.max(maxY, rotatedLocation.y); + maxZ = Math.max(maxZ, rotatedLocation.z); + WorldChunk currentChunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(rotatedLocation.x, rotatedLocation.z)); + Holder holder = currentChunk.getBlockComponentHolder(rotatedLocation.x, rotatedLocation.y, rotatedLocation.z); + int blockIdInRotatedLocation = currentChunk.getBlock(rotatedLocation.x, rotatedLocation.y, rotatedLocation.z); + int filler = currentChunk.getFiller(rotatedLocation.x, rotatedLocation.y, rotatedLocation.z); + int rotation = currentChunk.getRotationIndex(rotatedLocation.x, rotatedLocation.y, rotatedLocation.z); + before.addBlockAtWorldPos(rotatedLocation.x, rotatedLocation.y, rotatedLocation.z, blockIdInRotatedLocation, rotation, filler, 0, holder); + int originalFluidId = currentChunk.getFluidId(rotatedLocation.x, rotatedLocation.y, rotatedLocation.z); + byte originalFluidLevel = currentChunk.getFluidLevel(rotatedLocation.x, rotatedLocation.y, rotatedLocation.z); + before.addFluidAtWorldPos(rotatedLocation.x, rotatedLocation.y, rotatedLocation.z, originalFluidId, originalFluidLevel); + int newRotation = transformRotation(RotationTuple.get(blockChangex.rotation), transformationMatrix).index(); + BlockType blockType = BlockType.getAssetMap().getAsset(blockChangex.block); + if (blockType != null) { + BlockBoundingBoxes hitbox = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex()); + if (hitbox != null) { + if (hitbox.protrudesUnitBox()) { + FillerBlockUtil.forEachFillerBlock( + hitbox.get(newRotation), + (x, y, z) -> after.addBlockAtWorldPos( + rotatedLocation.x + x, + rotatedLocation.y + y, + rotatedLocation.z + z, + blockChange.block, + newRotation, + FillerBlockUtil.pack(x, y, z), + 0, + holder + ) + ); + } else { + after.addBlockAtWorldPos(rotatedLocation.x, rotatedLocation.y, rotatedLocation.z, blockChangex.block, newRotation, 0, 0, holder); + } + } + } + } + + int finalYOffsetOutOfGround = yOffsetOutOfGround; + if (fluidChanges != null) { + for (PrototypePlayerBuilderToolSettings.FluidChange fluidChange : fluidChanges) { + mutable4d.assign( + fluidChange.x() - rotationOrigin.x + initialPastePoint.x + 0.5, + fluidChange.y() - rotationOrigin.y + initialPastePoint.y + 0.5 + finalYOffsetOutOfGround, + fluidChange.z() - rotationOrigin.z + initialPastePoint.z + 0.5, + 1.0 + ); + transformationMatrix.multiply(mutable4d); + Vector3i rotatedLocation = new Vector3i( + (int)Math.floor(mutable4d.x + 0.1 + rotationOrigin.x - 0.5), + (int)Math.floor(mutable4d.y + 0.1 + rotationOrigin.y - 0.5), + (int)Math.floor(mutable4d.z + 0.1 + rotationOrigin.z - 0.5) + ); + after.addFluidAtWorldPos(rotatedLocation.x, rotatedLocation.y, rotatedLocation.z, fluidChange.fluidId(), fluidChange.fluidLevel()); + } + } + + if (minX != Integer.MAX_VALUE) { + before.setSelectionArea(new Vector3i(minX, minY, minZ), new Vector3i(maxX, maxY, maxZ)); + } + + this.pushHistory(BuilderToolsPlugin.Action.ROTATE, new BlockSelectionSnapshot(before)); + after.placeNoReturn("Transform 1/1", this.player, BuilderToolsPlugin.FEEDBACK_CONSUMER, world, componentAccessor); + BuilderToolsPlugin.invalidateWorldMapForSelection(after, world); + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute set of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), after.getBlockCount()); + this.sendUpdate(); + this.sendArea(); + } + + public void transformSelectionPoints(@Nonnull Matrix4d transformationMatrix, @Nonnull Vector3f rotationOrigin) { + Vector3i newMin = this.transformBlockLocation(this.selection.getSelectionMin(), transformationMatrix, rotationOrigin); + Vector3i newMax = this.transformBlockLocation(this.selection.getSelectionMax(), transformationMatrix, rotationOrigin); + this.selection.setSelectionArea(Vector3i.min(newMin, newMax), Vector3i.max(newMin, newMax)); + this.sendUpdate(); + this.sendArea(); + } + + @Nonnull + public Vector3i transformBlockLocation(@Nonnull Vector3i blockLocation, @Nonnull Matrix4d transformationMatrix, @Nonnull Vector3f rotationOrigin) { + Vector4d relativeOffset = new Vector4d( + blockLocation.x - rotationOrigin.x + 0.5, blockLocation.y - rotationOrigin.y + 0.5, blockLocation.z - rotationOrigin.z + 0.5, 1.0 + ); + transformationMatrix.multiply(relativeOffset); + return new Vector3i( + (int)Math.floor(relativeOffset.x + rotationOrigin.x - 0.5 + 0.1), + (int)Math.floor(relativeOffset.y + rotationOrigin.y - 0.5 + 0.1), + (int)Math.floor(relativeOffset.z + rotationOrigin.z - 0.5 + 0.1) + ); + } + + public int paste(@Nonnull Ref ref, int x, int y, int z, @Nonnull ComponentAccessor componentAccessor) { + World world = componentAccessor.getExternalData().getWorld(); + if (this.selection != null) { + long start = System.nanoTime(); + Vector3i selMin = this.selection.getSelectionMin(); + Vector3i selMax = this.selection.getSelectionMax(); + int origPosX = (selMin.x + selMax.x) / 2; + int origPosY = selMin.y; + int origPosZ = (selMin.z + selMax.z) / 2; + int offsetX = x - origPosX; + int offsetY = y - origPosY; + int offsetZ = z - origPosZ; + Vector3i pasteMin = new Vector3i(selMin.x + offsetX, selMin.y + offsetY, selMin.z + offsetZ); + Vector3i pasteMax = new Vector3i(selMax.x + offsetX, selMax.y + offsetY, selMax.z + offsetZ); + this.selection.setPosition(x, y, z); + int prefabId = PrefabUtil.getNextPrefabId(); + this.selection.setPrefabId(prefabId); + if (!BuilderToolsPlugin.onPasteStart(prefabId, componentAccessor)) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.pasteCancelledByEvent"), componentAccessor); + return 0; + } else { + int entityCount = this.selection.getEntityCount(); + List> snapshots = new ObjectArrayList<>(entityCount + 1); + Consumer> collector = BlockSelection.DEFAULT_ENTITY_CONSUMER; + if (entityCount > 0) { + collector = e -> snapshots.add(new EntityAddSnapshot(e)); + } + + BlockSelection before = this.selection.place(this.player, world, Vector3i.ZERO, this.globalMask, collector); + before.setSelectionArea(pasteMin, pasteMax); + snapshots.add(new BlockSelectionSnapshot(before)); + this.pushHistory(BuilderToolsPlugin.Action.PASTE, snapshots); + BuilderToolsPlugin.invalidateWorldMapForBounds(pasteMin, pasteMax, world); + BuilderToolsPlugin.get().onPasteEnd(prefabId, componentAccessor); + this.selection.setPrefabId(-1); + this.selection.setPosition(0, 0, 0); + long end = System.nanoTime(); + long diff = end - start; + int size = this.selection.getBlockCount(); + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute paste of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), size); + this.sendFeedback(Message.translation("server.builderTools.pastedBlocks").param("count", size), componentAccessor); + return size; + } + } else { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionClipboardEmpty"), componentAccessor); + return 0; + } + } + + public void rotate(@Nonnull Ref ref, @Nonnull Axis axis, int angle, @Nonnull ComponentAccessor componentAccessor) { + if (this.selection != null) { + long start = System.nanoTime(); + this.pushHistory(BuilderToolsPlugin.Action.ROTATE, ClipboardContentsSnapshot.copyOf(this.selection)); + this.selection = this.selection.rotate(axis, angle); + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute rotate of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), this.selection.getBlockCount()); + this.sendUpdate(); + this.sendFeedback( + Message.translation("server.builderTools.clipboardRotatedBy").param("angle", angle).param("axis", axis.toString()), componentAccessor + ); + } else { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionClipboardEmpty"), componentAccessor); + } + } + + public void rotate( + @Nonnull Ref ref, + @Nonnull Axis axis, + int angle, + @Nonnull Vector3f originOfRotation, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.selection != null) { + long start = System.nanoTime(); + this.pushHistory(BuilderToolsPlugin.Action.ROTATE, ClipboardContentsSnapshot.copyOf(this.selection)); + this.selection = this.selection.rotate(axis, angle, originOfRotation); + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute rotate of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), this.selection.getBlockCount()); + this.sendUpdate(); + this.sendFeedback( + Message.translation("server.builderTools.clipboardRotatedBy").param("angle", angle).param("axis", axis.toString()), componentAccessor + ); + } else { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionClipboardEmpty"), componentAccessor); + } + } + + public void rotateArbitrary(@Nonnull Ref ref, float yaw, float pitch, float roll, @Nonnull ComponentAccessor componentAccessor) { + if (this.selection != null) { + long start = System.nanoTime(); + this.pushHistory(BuilderToolsPlugin.Action.ROTATE, ClipboardContentsSnapshot.copyOf(this.selection)); + this.selection = this.selection.rotateArbitrary(yaw, pitch, roll); + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute arbitrary rotate of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), this.selection.getBlockCount()); + this.sendUpdate(); + Message message = Message.translation("server.builderTools.clipboardRotatedArbitrary").param("yaw", yaw).param("pitch", pitch).param("roll", roll); + this.sendFeedback(message, componentAccessor); + } else { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionClipboardEmpty"), componentAccessor); + } + } + + public void flip(@Nonnull Ref ref, @Nonnull Axis axis, @Nonnull ComponentAccessor componentAccessor) { + if (this.selection != null) { + long start = System.nanoTime(); + this.pushHistory(BuilderToolsPlugin.Action.FLIP, ClipboardContentsSnapshot.copyOf(this.selection)); + this.selection = this.selection.flip(axis); + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute flip of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), this.selection.getBlockCount()); + this.sendUpdate(); + this.sendFeedback(Message.translation("server.builderTools.clipboardFlipped").param("axis", axis.toString()), componentAccessor); + } else { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionClipboardEmpty"), componentAccessor); + } + } + + public void hollow( + @Nonnull Ref ref, + final int blockId, + int thickness, + boolean setTop, + boolean setBottom, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.selection == null) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelection"), componentAccessor); + } else if (!this.selection.hasSelectionBounds()) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionBounds"), componentAccessor); + } else { + long start = System.nanoTime(); + final Vector3i min = Vector3i.min(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + final Vector3i max = Vector3i.max(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + final BlockSelection before = new BlockSelection(); + before.setPosition(min.x, min.y, min.z); + before.setSelectionArea(min, max); + final BlockSelection after = new BlockSelection(before); + World world = componentAccessor.getExternalData().getWorld(); + final LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords( + world, max.x + 1 - min.x, max.z + 1 - min.z, Math.max(max.x + 1 - min.x, Math.max(max.y + 1 - min.y, max.z + 1 - min.z)) + ); + BlockCubeUtil.forEachBlock( + min, + max, + thickness, + !setTop, + !setBottom, + true, + null, + new TriIntObjPredicate() { + private int previousX = Integer.MIN_VALUE; + private int previousZ = Integer.MIN_VALUE; + @Nullable + private WorldChunk currentChunk; + + public boolean test(int x, int y, int z, Void unused) { + if (this.previousX != x || this.previousZ != z) { + this.previousX = x; + this.previousZ = z; + this.currentChunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + } + + int currentBlockId = this.currentChunk.getBlock(x, y, z); + int currentFluidId = this.currentChunk.getFluidId(x, y, z); + if (BuilderState.this.globalMask != null + && BuilderState.this.globalMask.isExcluded(accessor, x, y, z, min, max, currentBlockId, currentFluidId)) { + return true; + } else { + Holder holder = this.currentChunk.getBlockComponentHolder(x, y, z); + Holder newHolder = BuilderToolsPlugin.createBlockComponent( + this.currentChunk, x, y, z, blockId, currentBlockId, holder, false + ); + int supportValue = this.currentChunk.getSupportValue(x, y, z); + int filler = this.currentChunk.getFiller(x, y, z); + int rotation = this.currentChunk.getRotationIndex(x, y, z); + before.addBlockAtWorldPos(x, y, z, currentBlockId, filler, rotation, supportValue, holder); + after.addBlockAtWorldPos(x, y, z, blockId, 0, 0, 0, newHolder); + return true; + } + } + } + ); + this.pushHistory(BuilderToolsPlugin.Action.HOLLOW, new BlockSelectionSnapshot(before)); + after.placeNoReturn("Hollow 1/1", this.player, BuilderToolsPlugin.FEEDBACK_CONSUMER, world, componentAccessor); + BuilderToolsPlugin.invalidateWorldMapForSelection(after, world); + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute set of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), after.getBlockCount()); + this.sendUpdate(); + this.sendArea(); + } + } + + public void walls( + @Nonnull Ref ref, + int blockId, + int thickness, + boolean cappedTop, + boolean cappedBottom, + @Nonnull ComponentAccessor componentAccessor + ) { + this.walls(ref, BlockPattern.parse(BlockType.getAssetMap().getAsset(blockId).getId()), thickness, cappedTop, cappedBottom, componentAccessor); + } + + public void walls( + @Nonnull Ref ref, + @Nonnull final BlockPattern pattern, + int thickness, + boolean cappedTop, + boolean cappedBottom, + @Nonnull ComponentAccessor componentAccessor + ) { + if (!pattern.isEmpty()) { + if (this.selection == null) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelection"), componentAccessor); + } else if (!this.selection.hasSelectionBounds()) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionBounds"), componentAccessor); + } else { + World world = componentAccessor.getExternalData().getWorld(); + long start = System.nanoTime(); + final Vector3i min = Vector3i.min(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + final Vector3i max = Vector3i.max(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + final BlockSelection before = new BlockSelection(); + before.setPosition(min.x, min.y, min.z); + before.setSelectionArea(min, max); + final BlockSelection after = new BlockSelection(before); + final LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords( + world, max.x + 1 - min.x, max.z + 1 - min.z, Math.max(max.x + 1 - min.x, Math.max(max.y + 1 - min.y, max.z + 1 - min.z)) + ); + BlockCubeUtil.forEachBlock( + min, + max, + thickness, + cappedTop, + cappedBottom, + false, + null, + new TriIntObjPredicate() { + private int previousX = Integer.MIN_VALUE; + private int previousZ = Integer.MIN_VALUE; + @Nullable + private WorldChunk currentChunk; + + public boolean test(int x, int y, int z, Void unused) { + if (this.previousX != x || this.previousZ != z) { + this.previousX = x; + this.previousZ = z; + this.currentChunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + } + + int currentBlockId = this.currentChunk.getBlock(x, y, z); + int currentFluidId = this.currentChunk.getFluidId(x, y, z); + if (BuilderState.this.globalMask != null + && BuilderState.this.globalMask.isExcluded(accessor, x, y, z, min, max, currentBlockId, currentFluidId)) { + return true; + } else { + Material material = Material.fromPattern(pattern, BuilderState.this.random); + if (material.isFluid()) { + byte currentFluidLevel = this.currentChunk.getFluidLevel(x, y, z); + before.addFluidAtWorldPos(x, y, z, currentFluidId, currentFluidLevel); + after.addFluidAtWorldPos(x, y, z, material.getFluidId(), material.getFluidLevel()); + } else { + int newBlockId = material.getBlockId(); + int newRotation = material.getRotation(); + Holder holder = this.currentChunk.getBlockComponentHolder(x, y, z); + Holder newHolder = BuilderToolsPlugin.createBlockComponent( + this.currentChunk, x, y, z, newBlockId, currentBlockId, holder, false + ); + int supportValue = this.currentChunk.getSupportValue(x, y, z); + int filler = this.currentChunk.getFiller(x, y, z); + int rotation = this.currentChunk.getRotationIndex(x, y, z); + before.addBlockAtWorldPos(x, y, z, currentBlockId, rotation, filler, supportValue, holder); + after.addBlockAtWorldPos(x, y, z, newBlockId, newRotation, 0, 0, newHolder); + if (newBlockId == 0) { + int fluidId = this.currentChunk.getFluidId(x, y, z); + byte fluidLevel = this.currentChunk.getFluidLevel(x, y, z); + if (fluidId != 0) { + before.addFluidAtWorldPos(x, y, z, fluidId, fluidLevel); + after.addFluidAtWorldPos(x, y, z, 0, (byte)0); + } + } + } + + return true; + } + } + } + ); + this.pushHistory(BuilderToolsPlugin.Action.WALLS, new BlockSelectionSnapshot(before)); + after.placeNoReturn("Walls 1/1", this.player, BuilderToolsPlugin.FEEDBACK_CONSUMER, world, componentAccessor); + BuilderToolsPlugin.invalidateWorldMapForSelection(after, world); + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute walls of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), after.getBlockCount()); + this.sendUpdate(); + this.sendArea(); + } + } + } + + public void set(int blockId, ComponentAccessor componentAccessor) { + this.set(BlockPattern.parse(BlockType.getAssetMap().getAsset(blockId).getId()), componentAccessor); + } + + public void set(@Nonnull BlockPattern pattern, ComponentAccessor componentAccessor) { + if (!pattern.isEmpty()) { + if (this.selection == null) { + this.sendFeedback(Message.translation("server.builderTools.noSelection"), componentAccessor); + } else if (!this.selection.hasSelectionBounds()) { + this.sendFeedback(Message.translation("server.builderTools.noSelectionBounds"), componentAccessor); + } else { + long start = System.nanoTime(); + Vector3i min = Vector3i.min(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + Vector3i max = Vector3i.max(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + int xMin = min.getX(); + int xMax = max.getX(); + int yMin = min.getY(); + int yMax = max.getY(); + int zMin = min.getZ(); + int zMax = max.getZ(); + int totalBlocks = (xMax - xMin + 1) * (zMax - zMin + 1) * (yMax - yMin + 1); + int width = xMax - xMin; + int depth = zMax - zMin; + int halfWidth = width / 2; + int halfDepth = depth / 2; + BlockSelection before = new BlockSelection(totalBlocks, 0); + before.setPosition(xMin + halfWidth, yMin, zMin + halfDepth); + before.setSelectionArea(min, max); + this.pushHistory(BuilderToolsPlugin.Action.SET, new BlockSelectionSnapshot(before)); + BlockSelection after = new BlockSelection(totalBlocks, 0); + after.copyPropertiesFrom(before); + World world = componentAccessor.getExternalData().getWorld(); + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords(world, xMin + halfWidth, zMin + halfDepth, Math.max(width, depth)); + int counter = 0; + + for (int x = xMin; x <= xMax; x++) { + for (int z = zMin; z <= zMax; z++) { + WorldChunk chunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + + for (int y = yMax; y >= yMin; y--) { + int currentBlock = chunk.getBlock(x, y, z); + int currentFluid = chunk.getFluidId(x, y, z); + if (this.globalMask != null && this.globalMask.isExcluded(accessor, x, y, z, min, max, currentBlock, currentFluid)) { + counter++; + } else { + Material material = Material.fromPattern(pattern, this.random); + if (material.isFluid()) { + byte currentFluidLevel = chunk.getFluidLevel(x, y, z); + before.addFluidAtWorldPos(x, y, z, currentFluid, currentFluidLevel); + after.addFluidAtWorldPos(x, y, z, material.getFluidId(), material.getFluidLevel()); + } else { + int newBlockId = material.getBlockId(); + int newRotation = material.getRotation(); + Holder holder = chunk.getBlockComponentHolder(x, y, z); + Holder newHolder = BuilderToolsPlugin.createBlockComponent(chunk, x, y, z, newBlockId, currentBlock, holder, false); + int supportValue = chunk.getSupportValue(x, y, z); + int filler = chunk.getFiller(x, y, z); + int rotation = chunk.getRotationIndex(x, y, z); + before.addBlockAtWorldPos(x, y, z, currentBlock, rotation, filler, supportValue, holder); + after.addBlockAtWorldPos(x, y, z, newBlockId, newRotation, 0, 0, newHolder); + if (newBlockId == 0) { + int fluidId = chunk.getFluidId(x, y, z); + byte fluidLevel = chunk.getFluidLevel(x, y, z); + if (fluidId != 0) { + before.addFluidAtWorldPos(x, y, z, fluidId, fluidLevel); + after.addFluidAtWorldPos(x, y, z, 0, (byte)0); + } + } + } + + this.sendFeedback("Gather 1/2", totalBlocks, ++counter, componentAccessor); + } + } + } + } + + after.placeNoReturn("Set 2/2", this.player, BuilderToolsPlugin.FEEDBACK_CONSUMER, world, componentAccessor); + BuilderToolsPlugin.invalidateWorldMapForSelection(after, world); + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute set of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), counter); + this.sendUpdate(); + this.sendArea(); + } + } + } + + public void fill(@Nonnull BlockPattern pattern, ComponentAccessor componentAccessor) { + if (!pattern.isEmpty()) { + if (this.selection == null) { + this.sendFeedback(Message.translation("server.builderTools.noSelection"), componentAccessor); + } else if (!this.selection.hasSelectionBounds()) { + this.sendFeedback(Message.translation("server.builderTools.noSelectionBounds"), componentAccessor); + } else { + long start = System.nanoTime(); + Vector3i min = Vector3i.min(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + Vector3i max = Vector3i.max(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + int xMin = min.getX(); + int xMax = max.getX(); + int yMin = min.getY(); + int yMax = max.getY(); + int zMin = min.getZ(); + int zMax = max.getZ(); + int totalBlocks = (xMax - xMin + 1) * (zMax - zMin + 1) * (yMax - yMin + 1); + int width = xMax - xMin; + int depth = zMax - zMin; + int halfWidth = width / 2; + int halfDepth = depth / 2; + BlockSelection before = new BlockSelection(totalBlocks, 0); + before.setPosition(xMin + halfWidth, yMin, zMin + halfDepth); + before.setSelectionArea(min, max); + this.pushHistory(BuilderToolsPlugin.Action.EDIT, new BlockSelectionSnapshot(before)); + BlockSelection after = new BlockSelection(totalBlocks, 0); + after.copyPropertiesFrom(before); + World world = componentAccessor.getExternalData().getWorld(); + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords(world, xMin + halfWidth, zMin + halfDepth, Math.max(width, depth)); + int counter = 0; + + for (int x = xMin; x <= xMax; x++) { + for (int z = zMin; z <= zMax; z++) { + WorldChunk chunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + + for (int y = yMax; y >= yMin; y--) { + Material material = Material.fromPattern(pattern, this.random); + if (material.isFluid()) { + int currentFluidId = chunk.getFluidId(x, y, z); + if (currentFluidId == 0) { + byte currentFluidLevel = chunk.getFluidLevel(x, y, z); + before.addFluidAtWorldPos(x, y, z, currentFluidId, currentFluidLevel); + after.addFluidAtWorldPos(x, y, z, material.getFluidId(), material.getFluidLevel()); + } + } else { + int currentBlock = chunk.getBlock(x, y, z); + if (currentBlock == 0) { + int newBlockId = material.getBlockId(); + int newRotation = material.getRotation(); + Holder holder = chunk.getBlockComponentHolder(x, y, z); + Holder newHolder = BuilderToolsPlugin.createBlockComponent(chunk, x, y, z, newBlockId, currentBlock, holder, false); + int supportValue = chunk.getSupportValue(x, y, z); + int filler = chunk.getFiller(x, y, z); + int rotation = chunk.getRotationIndex(x, y, z); + before.addBlockAtWorldPos(x, y, z, currentBlock, rotation, filler, supportValue, holder); + after.addBlockAtWorldPos(x, y, z, newBlockId, newRotation, 0, 0, newHolder); + } + } + + this.sendFeedback("Gather 1/2", totalBlocks, ++counter, componentAccessor); + } + } + } + + after.placeNoReturn("Fill 2/2", this.player, BuilderToolsPlugin.FEEDBACK_CONSUMER, world, componentAccessor); + BuilderToolsPlugin.invalidateWorldMapForSelection(after, world); + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute fill of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), counter); + this.sendUpdate(); + this.sendArea(); + } + } + } + + public void replace( + @Nonnull Ref ref, @Nonnull Material from, @Nonnull Material to, @Nonnull ComponentAccessor componentAccessor + ) { + if (this.selection == null) { + this.sendFeedback(Message.translation("server.builderTools.noSelection"), componentAccessor); + } else if (!this.selection.hasSelectionBounds()) { + this.sendFeedback(Message.translation("server.builderTools.noSelectionBounds"), componentAccessor); + } else { + long start = System.nanoTime(); + Vector3i min = Vector3i.min(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + Vector3i max = Vector3i.max(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + int xMin = min.getX(); + int xMax = max.getX(); + int yMin = min.getY(); + int yMax = max.getY(); + int zMin = min.getZ(); + int zMax = max.getZ(); + BlockSelection before = new BlockSelection(); + int width = xMax - xMin; + int depth = zMax - zMin; + int halfWidth = width / 2; + int halfDepth = depth / 2; + before.setPosition(xMin + halfWidth, yMin, zMin + halfDepth); + before.setSelectionArea(min, max); + this.pushHistory(BuilderToolsPlugin.Action.REPLACE, new BlockSelectionSnapshot(before)); + BlockSelection after = new BlockSelection(before); + World world = componentAccessor.getExternalData().getWorld(); + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords(world, xMin + halfWidth, zMin + halfDepth, Math.max(width, depth)); + int totalBlocks = (width + 1) * (depth + 1) * (yMax - yMin + 1); + int counter = 0; + + for (int x = xMin; x <= xMax; x++) { + for (int z = zMin; z <= zMax; z++) { + WorldChunk chunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + + for (int y = yMax; y >= yMin; y--) { + int currentFiller = chunk.getFiller(x, y, z); + if (currentFiller != 0) { + this.sendFeedback("Gather 1/2", totalBlocks, ++counter, componentAccessor); + } else { + boolean shouldReplace = false; + if (from.isFluid()) { + int currentFluidId = chunk.getFluidId(x, y, z); + shouldReplace = currentFluidId == from.getFluidId(); + } else { + int currentBlock = chunk.getBlock(x, y, z); + shouldReplace = currentBlock == from.getBlockId(); + } + + if (shouldReplace) { + int currentBlock = chunk.getBlock(x, y, z); + int currentFluidId = chunk.getFluidId(x, y, z); + byte currentFluidLevel = chunk.getFluidLevel(x, y, z); + if (to.isFluid()) { + if (currentBlock != 0) { + Holder holder = chunk.getBlockComponentHolder(x, y, z); + int rotation = chunk.getRotationIndex(x, y, z); + int supportValue = chunk.getSupportValue(x, y, z); + before.addBlockAtWorldPos(x, y, z, currentBlock, rotation, currentFiller, supportValue, holder); + after.addBlockAtWorldPos(x, y, z, 0, 0, 0, 0); + this.clearFillerBlocksIfNeeded(x, y, z, currentBlock, rotation, accessor, before, after); + } + + before.addFluidAtWorldPos(x, y, z, currentFluidId, currentFluidLevel); + after.addFluidAtWorldPos(x, y, z, to.getFluidId(), to.getFluidLevel()); + } else if (to.isEmpty()) { + Holder holder = chunk.getBlockComponentHolder(x, y, z); + int rotation = chunk.getRotationIndex(x, y, z); + int supportValue = chunk.getSupportValue(x, y, z); + before.addBlockAtWorldPos(x, y, z, currentBlock, rotation, currentFiller, supportValue, holder); + after.addBlockAtWorldPos(x, y, z, 0, 0, 0, 0); + this.clearFillerBlocksIfNeeded(x, y, z, currentBlock, rotation, accessor, before, after); + if (currentFluidId != 0) { + before.addFluidAtWorldPos(x, y, z, currentFluidId, currentFluidLevel); + after.addFluidAtWorldPos(x, y, z, 0, (byte)0); + } + } else { + if (currentFluidId != 0) { + before.addFluidAtWorldPos(x, y, z, currentFluidId, currentFluidLevel); + after.addFluidAtWorldPos(x, y, z, 0, (byte)0); + } + + Holder holder = chunk.getBlockComponentHolder(x, y, z); + Holder newHolder = BuilderToolsPlugin.createBlockComponent( + chunk, x, y, z, to.getBlockId(), currentBlock, holder, true + ); + int rotation = chunk.getRotationIndex(x, y, z); + int supportValue = chunk.getSupportValue(x, y, z); + before.addBlockAtWorldPos(x, y, z, currentBlock, rotation, currentFiller, supportValue, holder); + after.addBlockAtWorldPos(x, y, z, to.getBlockId(), rotation, 0, 0, newHolder); + this.replaceMultiBlockStructure(x, y, z, currentBlock, to.getBlockId(), rotation, accessor, before, after); + } + } + + this.sendFeedback("Gather 1/2", totalBlocks, ++counter, componentAccessor); + } + } + } + } + + after.placeNoReturn("Replace 2/2", this.player, BuilderToolsPlugin.FEEDBACK_CONSUMER, world, componentAccessor); + BuilderToolsPlugin.invalidateWorldMapForSelection(after, world); + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get().getLogger().at(Level.FINE).log("Took: %dns (%dms) to execute replace", diff, TimeUnit.NANOSECONDS.toMillis(diff)); + this.sendUpdate(); + this.sendArea(); + SoundUtil.playSoundEvent2d(ref, TempAssetIdUtil.getSoundEventIndex("CREATE_SELECTION_FILL"), SoundCategory.SFX, componentAccessor); + } + } + + private void clearFillerBlocksIfNeeded( + int baseX, int baseY, int baseZ, int oldBlockId, int rotationIndex, LocalCachedChunkAccessor accessor, BlockSelection before, BlockSelection after + ) { + this.replaceMultiBlockStructure(baseX, baseY, baseZ, oldBlockId, 0, rotationIndex, accessor, before, after); + } + + private void replaceMultiBlockStructure( + int baseX, + int baseY, + int baseZ, + int oldBlockId, + int newBlockId, + int rotationIndex, + LocalCachedChunkAccessor accessor, + BlockSelection before, + BlockSelection after + ) { + BlockTypeAssetMap blockTypeAssetMap = BlockType.getAssetMap(); + IndexedLookupTableAssetMap hitboxAssetMap = BlockBoundingBoxes.getAssetMap(); + BlockType oldBlockType = blockTypeAssetMap.getAsset(oldBlockId); + BlockBoundingBoxes oldHitbox = null; + if (oldBlockType != null) { + oldHitbox = hitboxAssetMap.getAsset(oldBlockType.getHitboxTypeIndex()); + } + + BlockType newBlockType = blockTypeAssetMap.getAsset(newBlockId); + BlockBoundingBoxes newHitbox = null; + if (newBlockType != null) { + newHitbox = hitboxAssetMap.getAsset(newBlockType.getHitboxTypeIndex()); + } + + if (oldHitbox != null && oldHitbox.protrudesUnitBox()) { + BlockBoundingBoxes finalNewHitbox = newHitbox; + FillerBlockUtil.forEachFillerBlock( + oldHitbox.get(rotationIndex), + (fx, fy, fz) -> { + if (fx != 0 || fy != 0 || fz != 0) { + int fillerX = baseX + fx; + int fillerY = baseY + fy; + int fillerZ = baseZ + fz; + WorldChunk fillerChunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(fillerX, fillerZ)); + int fillerBlock = fillerChunk.getBlock(fillerX, fillerY, fillerZ); + int fillerFiller = fillerChunk.getFiller(fillerX, fillerY, fillerZ); + if (fillerFiller != 0) { + Holder fillerHolder = fillerChunk.getBlockComponentHolder(fillerX, fillerY, fillerZ); + before.addBlockAtWorldPos( + fillerX, + fillerY, + fillerZ, + fillerBlock, + rotationIndex, + fillerFiller, + fillerChunk.getSupportValue(fillerX, fillerY, fillerZ), + fillerHolder + ); + boolean willBeFilledByNewStructure = finalNewHitbox != null + && finalNewHitbox.protrudesUnitBox() + && finalNewHitbox.get(rotationIndex).getBoundingBox().containsBlock(fx, fy, fz); + if (!willBeFilledByNewStructure) { + after.addBlockAtWorldPos(fillerX, fillerY, fillerZ, 0, 0, 0, 0); + } + } + } + } + ); + } + + if (newHitbox != null && newHitbox.protrudesUnitBox()) { + FillerBlockUtil.forEachFillerBlock( + newHitbox.get(rotationIndex), + (fx, fy, fz) -> { + if (fx != 0 || fy != 0 || fz != 0) { + int fillerX = baseX + fx; + int fillerY = baseY + fy; + int fillerZ = baseZ + fz; + WorldChunk fillerChunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(fillerX, fillerZ)); + int existingBlock = fillerChunk.getBlock(fillerX, fillerY, fillerZ); + int existingFiller = fillerChunk.getFiller(fillerX, fillerY, fillerZ); + if (existingFiller == 0 && !before.hasBlockAtWorldPos(fillerX, fillerY, fillerZ)) { + Holder fillerHolder = fillerChunk.getBlockComponentHolder(fillerX, fillerY, fillerZ); + before.addBlockAtWorldPos( + fillerX, + fillerY, + fillerZ, + existingBlock, + rotationIndex, + existingFiller, + fillerChunk.getSupportValue(fillerX, fillerY, fillerZ), + fillerHolder + ); + } + + int newFiller = FillerBlockUtil.pack(fx, fy, fz); + after.addBlockAtWorldPos(fillerX, fillerY, fillerZ, newBlockId, rotationIndex, newFiller, 0); + } + } + ); + } + } + + public void replace(@Nonnull Ref ref, @Nullable IntPredicate doReplace, int to, @Nonnull ComponentAccessor componentAccessor) { + this.replace(ref, doReplace, new int[]{to}, componentAccessor); + } + + public void replace( + @Nonnull Ref ref, @Nullable IntPredicate doReplace, @Nonnull int[] toBlockIds, @Nonnull ComponentAccessor componentAccessor + ) { + if (this.selection == null) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelection"), componentAccessor); + } else if (!this.selection.hasSelectionBounds()) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionBounds"), componentAccessor); + } else { + long start = System.nanoTime(); + Vector3i min = Vector3i.min(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + Vector3i max = Vector3i.max(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + int xMin = min.getX(); + int xMax = max.getX(); + int yMin = min.getY(); + int yMax = max.getY(); + int zMin = min.getZ(); + int zMax = max.getZ(); + BlockSelection before = new BlockSelection(); + int width = xMax - xMin; + int depth = zMax - zMin; + int halfWidth = width / 2; + int halfDepth = depth / 2; + before.setPosition(xMin + halfWidth, yMin, zMin + halfDepth); + before.setSelectionArea(min, max); + this.pushHistory(BuilderToolsPlugin.Action.REPLACE, new BlockSelectionSnapshot(before)); + BlockSelection after = new BlockSelection(before); + World world = componentAccessor.getExternalData().getWorld(); + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords(world, xMin + halfWidth, zMin + halfDepth, Math.max(width, depth)); + int totalBlocks = (width + 1) * (depth + 1) * (yMax - yMin + 1); + int counter = 0; + + for (int x = xMin; x <= xMax; x++) { + for (int z = zMin; z <= zMax; z++) { + WorldChunk chunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + + for (int y = yMax; y >= yMin; y--) { + int filler = chunk.getFiller(x, y, z); + if (filler != 0) { + this.sendFeedback("Gather 1/2", totalBlocks, ++counter, componentAccessor); + } else { + int block = chunk.getBlock(x, y, z); + if (doReplace == null && block != 0 || doReplace != null && doReplace.test(block)) { + Holder holder = chunk.getBlockComponentHolder(x, y, z); + int newBlockId = toBlockIds.length == 1 ? toBlockIds[0] : toBlockIds[this.random.nextInt(toBlockIds.length)]; + Holder newHolder = BuilderToolsPlugin.createBlockComponent(chunk, x, y, z, newBlockId, block, holder, true); + int rotationIndex = chunk.getRotationIndex(x, y, z); + before.addBlockAtWorldPos( + x, y, z, block, rotationIndex, filler, chunk.getSupportValue(x, y, z), chunk.getBlockComponentHolder(x, y, z) + ); + after.addBlockAtWorldPos(x, y, z, newBlockId, rotationIndex, 0, 0, newHolder); + this.replaceMultiBlockStructure(x, y, z, block, newBlockId, rotationIndex, accessor, before, after); + if (newBlockId == 0) { + int fluidId = chunk.getFluidId(x, y, z); + byte fluidLevel = chunk.getFluidLevel(x, y, z); + if (fluidId != 0) { + before.addFluidAtWorldPos(x, y, z, fluidId, fluidLevel); + after.addFluidAtWorldPos(x, y, z, 0, (byte)0); + } + } + } + + this.sendFeedback("Gather 1/2", totalBlocks, ++counter, componentAccessor); + } + } + } + } + + after.placeNoReturn("Replace 2/2", this.player, BuilderToolsPlugin.FEEDBACK_CONSUMER, world, componentAccessor); + BuilderToolsPlugin.invalidateWorldMapForSelection(after, world); + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute replace of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), after.getBlockCount()); + this.sendUpdate(); + this.sendArea(); + SoundUtil.playSoundEvent2d(ref, TempAssetIdUtil.getSoundEventIndex("CREATE_SELECTION_FILL"), SoundCategory.SFX, componentAccessor); + } + } + + public void replace(@Nonnull Ref ref, @Nonnull Int2IntFunction function, @Nonnull ComponentAccessor componentAccessor) { + if (this.selection == null) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelection"), componentAccessor); + } else if (!this.selection.hasSelectionBounds()) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionBounds"), componentAccessor); + } else { + long start = System.nanoTime(); + Vector3i min = Vector3i.min(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + Vector3i max = Vector3i.max(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + int xMin = min.getX(); + int xMax = max.getX(); + int yMin = min.getY(); + int yMax = max.getY(); + int zMin = min.getZ(); + int zMax = max.getZ(); + BlockSelection before = new BlockSelection(); + int width = xMax - xMin; + int depth = zMax - zMin; + int halfWidth = width / 2; + int halfDepth = depth / 2; + before.setPosition(xMin + halfWidth, yMin, zMin + halfDepth); + before.setSelectionArea(min, max); + this.pushHistory(BuilderToolsPlugin.Action.REPLACE, new BlockSelectionSnapshot(before)); + BlockSelection after = new BlockSelection(before); + World world = componentAccessor.getExternalData().getWorld(); + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords(world, xMin + halfWidth, zMin + halfDepth, Math.max(width, depth)); + int totalBlocks = (width + 1) * (depth + 1) * (yMax - yMin + 1); + int counter = 0; + + for (int x = xMin; x <= xMax; x++) { + for (int z = zMin; z <= zMax; z++) { + WorldChunk chunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + + for (int y = yMax; y >= yMin; y--) { + int filler = chunk.getFiller(x, y, z); + if (filler != 0) { + this.sendFeedback("Gather 1/2", totalBlocks, ++counter, componentAccessor); + } else { + int block = chunk.getBlock(x, y, z); + int replace = function.applyAsInt(block); + if (block != replace) { + Holder holder = chunk.getBlockComponentHolder(x, y, z); + Holder newHolder = BuilderToolsPlugin.createBlockComponent(chunk, x, y, z, replace, block, holder, true); + int rotationIndex = chunk.getRotationIndex(x, y, z); + before.addBlockAtWorldPos(x, y, z, block, rotationIndex, filler, chunk.getSupportValue(x, y, z), holder); + after.addBlockAtWorldPos(x, y, z, replace, rotationIndex, 0, 0, newHolder); + this.replaceMultiBlockStructure(x, y, z, block, replace, rotationIndex, accessor, before, after); + if (replace == 0) { + int fluidId = chunk.getFluidId(x, y, z); + byte fluidLevel = chunk.getFluidLevel(x, y, z); + if (fluidId != 0) { + before.addFluidAtWorldPos(x, y, z, fluidId, fluidLevel); + after.addFluidAtWorldPos(x, y, z, 0, (byte)0); + } + } + } + + this.sendFeedback("Gather 1/2", totalBlocks, ++counter, componentAccessor); + } + } + } + } + + after.placeNoReturn("Replace 2/2", this.player, BuilderToolsPlugin.FEEDBACK_CONSUMER, world, componentAccessor); + BuilderToolsPlugin.invalidateWorldMapForSelection(after, world); + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute replace of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), after.getBlockCount()); + this.sendUpdate(); + this.sendArea(); + } + } + + public void move( + @Nonnull Ref ref, @Nonnull Vector3i direction, boolean empty, boolean entities, @Nonnull ComponentAccessor componentAccessor + ) { + if (this.selection == null) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelection"), componentAccessor); + } else if (!this.selection.hasSelectionBounds()) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionBounds"), componentAccessor); + } else { + long start = System.nanoTime(); + Vector3i min = Vector3i.min(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + Vector3i max = Vector3i.max(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + int xMin = min.getX(); + int xMax = max.getX(); + int yMin = min.getY(); + int yMax = max.getY(); + int zMin = min.getZ(); + int zMax = max.getZ(); + BlockSelection selected = new BlockSelection(); + int width = xMax - xMin; + int height = yMax - yMin; + int depth = zMax - zMin; + int halfWidth = width / 2; + int halfDepth = depth / 2; + int xPos = xMin + halfWidth; + int zPos = zMin + halfDepth; + selected.setPosition(xPos, yMin, zPos); + BlockSelection cleared = new BlockSelection(selected); + World world = componentAccessor.getExternalData().getWorld(); + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords(world, xMin + halfWidth, zMin + halfDepth, Math.max(width, depth) + 16); + BlockTypeAssetMap blockTypeAssetMap = BlockType.getAssetMap(); + IndexedLookupTableAssetMap hitboxAssetMap = BlockBoundingBoxes.getAssetMap(); + + for (int x = xMin; x <= xMax; x++) { + for (int z = zMin; z <= zMax; z++) { + WorldChunk chunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + + for (int y = yMax; y >= yMin; y--) { + int block = chunk.getBlock(x, y, z); + int fluidId = chunk.getFluidId(x, y, z); + byte fluidLevel = chunk.getFluidLevel(x, y, z); + if ((block != 0 || fluidId != 0 || empty) + && (this.globalMask == null || !this.globalMask.isExcluded(accessor, x, y, z, min, max, block, fluidId))) { + int filler = chunk.getFiller(x, y, z); + int rotationIndex = chunk.getRotationIndex(x, y, z); + selected.addBlockAtWorldPos( + x, y, z, block, rotationIndex, filler, chunk.getSupportValue(x, y, z), chunk.getBlockComponentHolder(x, y, z) + ); + selected.addFluidAtWorldPos(x, y, z, fluidId, fluidLevel); + cleared.addBlockAtWorldPos(x, y, z, 0, 0, 0, 0); + cleared.addFluidAtWorldPos(x, y, z, 0, (byte)0); + if (filler == 0 && block != 0) { + BlockType blockType = blockTypeAssetMap.getAsset(block); + if (blockType != null) { + BlockBoundingBoxes hitbox = hitboxAssetMap.getAsset(blockType.getHitboxTypeIndex()); + if (hitbox != null && hitbox.protrudesUnitBox()) { + int baseX = x; + int baseY = y; + int baseZ = z; + FillerBlockUtil.forEachFillerBlock( + hitbox.get(rotationIndex), + (fx, fy, fz) -> { + if (fx != 0 || fy != 0 || fz != 0) { + int fillerX = baseX + fx; + int fillerY = baseY + fy; + int fillerZ = baseZ + fz; + if (fillerX < xMin || fillerX > xMax || fillerY < yMin || fillerY > yMax || fillerZ < zMin || fillerZ > zMax) { + WorldChunk fillerChunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(fillerX, fillerZ)); + int fillerBlock = fillerChunk.getBlock(fillerX, fillerY, fillerZ); + int fillerFiller = fillerChunk.getFiller(fillerX, fillerY, fillerZ); + if (fillerFiller != 0) { + int fillerRotation = fillerChunk.getRotationIndex(fillerX, fillerY, fillerZ); + selected.addBlockAtWorldPos( + fillerX, + fillerY, + fillerZ, + fillerBlock, + fillerRotation, + fillerFiller, + fillerChunk.getSupportValue(fillerX, fillerY, fillerZ), + fillerChunk.getBlockComponentHolder(fillerX, fillerY, fillerZ) + ); + cleared.addBlockAtWorldPos(fillerX, fillerY, fillerZ, 0, 0, 0, 0); + } + } + } + } + ); + } + } + } + } + } + } + } + + BlockSelection beforeCleared = cleared.place(this.player, world); + selected.setPosition(xPos + direction.getX(), yMin + direction.getY(), zPos + direction.getZ()); + BlockSelection beforePlace = selected.place(this.player, world); + List> snapshots = new ObjectArrayList<>(); + if (entities) { + for (Ref targetEntityRef : TargetUtil.getAllEntitiesInBox(min.toVector3d(), max.toVector3d(), componentAccessor)) { + snapshots.add(new EntityTransformSnapshot(targetEntityRef, componentAccessor)); + TransformComponent transformComponent = componentAccessor.getComponent(targetEntityRef, TransformComponent.getComponentType()); + if (transformComponent != null) { + transformComponent.getPosition().add(direction); + } + } + } + + beforePlace.add(beforeCleared); + ClipboardBoundsSnapshot clipboardSnapshot = new ClipboardBoundsSnapshot(min, max); + Vector3i destMin = min.clone().add(direction); + Vector3i destMax = max.clone().add(direction); + beforePlace.setSelectionArea(Vector3i.min(min, destMin), Vector3i.max(max, destMax)); + snapshots.add(new BlockSelectionSnapshot(beforePlace)); + snapshots.add(clipboardSnapshot); + this.pushHistory(BuilderToolsPlugin.Action.MOVE, snapshots); + BuilderToolsPlugin.invalidateWorldMapForSelection(cleared, world); + BuilderToolsPlugin.invalidateWorldMapForSelection(selected, world); + this.selection.setSelectionArea(min.add(direction), max.add(direction)); + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute move of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), cleared.getBlockCount()); + this.sendUpdate(); + this.sendArea(); + this.sendFeedback( + Message.translation("server.builderTools.selectionMovedBy") + .param("x", direction.getX()) + .param("y", direction.getY()) + .param("z", direction.getZ()), + componentAccessor + ); + } + } + + public void shift(@Nonnull Ref ref, @Nonnull Vector3i direction, @Nonnull ComponentAccessor componentAccessor) { + if (this.selection == null) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelection"), componentAccessor); + } else if (!this.selection.hasSelectionBounds()) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionBounds"), componentAccessor); + } else { + this.pushHistory(BuilderToolsPlugin.Action.UPDATE_SELECTION, new ClipboardBoundsSnapshot(this.selection)); + this.selection.setSelectionArea(this.selection.getSelectionMin().add(direction), this.selection.getSelectionMax().add(direction)); + this.sendArea(); + this.sendFeedback( + Message.translation("server.builderTools.selectionShiftedBy") + .param("x", direction.getX()) + .param("y", direction.getY()) + .param("z", direction.getZ()), + componentAccessor + ); + } + } + + public void pos1(@Nonnull Vector3i pos1, ComponentAccessor componentAccessor) { + if (this.selection != null && !this.selection.getSelectionMax().equals(Vector3i.ZERO)) { + this.pushHistory(BuilderToolsPlugin.Action.UPDATE_SELECTION, new ClipboardBoundsSnapshot(this.selection)); + this.selection.setSelectionArea(pos1, this.selection.getSelectionMax()); + this.sendArea(); + } else { + if (this.selection == null) { + this.selection = new BlockSelection(); + } + + this.pushHistory(BuilderToolsPlugin.Action.UPDATE_SELECTION, ClipboardBoundsSnapshot.EMPTY); + this.selection.setSelectionArea(pos1, pos1); + this.sendArea(); + } + + this.sendFeedback( + Message.translation("server.builderTools.setPosTo").param("num", 1).param("x", pos1.getX()).param("y", pos1.getY()).param("z", pos1.getZ()), + componentAccessor + ); + } + + public void pos2(@Nonnull Vector3i pos2, ComponentAccessor componentAccessor) { + if (this.selection != null && !this.selection.getSelectionMin().equals(Vector3i.ZERO)) { + this.pushHistory(BuilderToolsPlugin.Action.UPDATE_SELECTION, new ClipboardBoundsSnapshot(this.selection)); + this.selection.setSelectionArea(this.selection.getSelectionMin(), pos2); + this.sendArea(); + } else { + if (this.selection == null) { + this.selection = new BlockSelection(); + } + + this.pushHistory(BuilderToolsPlugin.Action.UPDATE_SELECTION, ClipboardBoundsSnapshot.EMPTY); + this.selection.setSelectionArea(pos2, pos2); + this.sendArea(); + } + + this.sendFeedback( + Message.translation("server.builderTools.setPosTo").param("num", 2).param("x", pos2.getX()).param("y", pos2.getY()).param("z", pos2.getZ()), + componentAccessor + ); + } + + public void select(@Nonnull Vector3i pos1, @Nonnull Vector3i pos2, @Nullable String reason, ComponentAccessor componentAccessor) { + if (this.selection != null && !this.selection.getSelectionMax().equals(Vector3i.ZERO)) { + this.pushHistory(BuilderToolsPlugin.Action.UPDATE_SELECTION, new ClipboardBoundsSnapshot(this.selection)); + this.selection.setSelectionArea(pos1, pos2); + this.sendArea(); + } else { + if (this.selection == null) { + this.selection = new BlockSelection(); + } + + this.pushHistory(BuilderToolsPlugin.Action.UPDATE_SELECTION, ClipboardBoundsSnapshot.EMPTY); + this.selection.setSelectionArea(pos1, pos2); + this.sendArea(); + } + + if (reason != null) { + Message reasonMessage = Message.translation(reason); + this.sendFeedback( + Message.translation("server.builderTools.selectedWithReason") + .param("reason", reasonMessage) + .param("x1", pos1.getX()) + .param("y1", pos1.getY()) + .param("z1", pos1.getZ()) + .param("x2", pos2.getX()) + .param("y2", pos2.getY()) + .param("z2", pos2.getZ()), + componentAccessor + ); + } else { + this.sendFeedback( + Message.translation("server.builderTools.selected") + .param("x1", pos1.getX()) + .param("y1", pos1.getY()) + .param("z1", pos1.getZ()) + .param("x2", pos2.getX()) + .param("y2", pos2.getY()) + .param("z2", pos2.getZ()), + componentAccessor + ); + } + } + + public void deselect(ComponentAccessor componentAccessor) { + if (this.selection != null && this.selection.hasSelectionBounds()) { + this.selection.setSelectionArea(Vector3i.ZERO, Vector3i.ZERO); + EditorBlocksChange packet = new EditorBlocksChange(); + packet.selection = null; + this.player.getPlayerConnection().write(packet); + this.sendFeedback(Message.translation("server.builderTools.deselected"), componentAccessor); + } else { + this.sendFeedback(Message.translation("server.builderTools.noSelectionToDeselect"), componentAccessor); + } + } + + public void stack( + @Nonnull Ref ref, + @Nonnull Vector3i direction, + int count, + boolean empty, + int spacing, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.selection == null) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelection"), componentAccessor); + } else if (!this.selection.hasSelectionBounds()) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionBounds"), componentAccessor); + } else { + long start = System.nanoTime(); + Vector3i min = Vector3i.min(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + Vector3i max = Vector3i.max(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + int xMin = min.getX(); + int xMax = max.getX(); + int yMin = min.getY(); + int yMax = max.getY(); + int zMin = min.getZ(); + int zMax = max.getZ(); + BlockSelection selected = new BlockSelection(); + int width = xMax - xMin; + int depth = zMax - zMin; + int halfWidth = width / 2; + int halfDepth = depth / 2; + selected.setPosition(xMin + halfWidth, yMin, zMin + halfDepth); + World world = componentAccessor.getExternalData().getWorld(); + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords(world, xMin + halfWidth, zMin + halfDepth, Math.max(width, depth)); + + for (int x = xMin; x <= xMax; x++) { + for (int z = zMin; z <= zMax; z++) { + WorldChunk chunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + + for (int y = yMax; y >= yMin; y--) { + int block = chunk.getBlock(x, y, z); + int fluidId = chunk.getFluidId(x, y, z); + byte fluidLevel = chunk.getFluidLevel(x, y, z); + if ((block != 0 || fluidId != 0 || empty) + && (this.globalMask == null || !this.globalMask.isExcluded(accessor, x, y, z, min, max, block, fluidId))) { + selected.addBlockAtWorldPos( + x, + y, + z, + block, + chunk.getRotationIndex(x, y, z), + chunk.getFiller(x, y, z), + chunk.getSupportValue(x, y, z), + chunk.getBlockComponentHolder(x, y, z) + ); + selected.addFluidAtWorldPos(x, y, z, fluidId, fluidLevel); + } + } + } + } + + BlockSelection before = new BlockSelection(); + before.setAnchor(selected.getAnchorX(), selected.getAnchorY(), selected.getAnchorZ()); + before.setPosition(selected.getX(), selected.getY(), selected.getZ()); + Vector3i size = max.subtract(min).add(1, 1, 1); + + for (int i = 1; i <= count; i++) { + selected.setPosition( + before.getX() + (size.getX() + spacing) * direction.getX() * i, + before.getY() + (size.getY() + spacing) * direction.getY() * i, + before.getZ() + (size.getZ() + spacing) * direction.getZ() * i + ); + before.add(selected.place(this.player, world)); + } + + Vector3i stackOffset = new Vector3i( + (size.getX() + spacing) * direction.getX() * count, + (size.getY() + spacing) * direction.getY() * count, + (size.getZ() + spacing) * direction.getZ() * count + ); + Vector3i totalMin = Vector3i.min(min, min.add(stackOffset)); + Vector3i totalMax = Vector3i.max(max, max.add(stackOffset)); + before.setSelectionArea(totalMin, totalMax); + this.pushHistory(BuilderToolsPlugin.Action.STACK, new BlockSelectionSnapshot(before)); + BuilderToolsPlugin.invalidateWorldMapForSelection(before, world); + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute stack of %d blocks %d times", diff, TimeUnit.NANOSECONDS.toMillis(diff), selected.getBlockCount(), count); + this.sendUpdate(); + this.sendArea(); + this.sendFeedback( + Message.translation("server.builderTools.selectionStacked") + .param("count", count) + .param("x", direction.getX()) + .param("y", direction.getY()) + .param("z", direction.getZ()), + componentAccessor + ); + } + } + + public void expand(@Nonnull Ref ref, @Nonnull Vector3i direction, @Nonnull ComponentAccessor componentAccessor) { + if (this.selection == null) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelection"), componentAccessor); + } else if (!this.selection.hasSelectionBounds()) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionBounds"), componentAccessor); + } else { + this.pushHistory(BuilderToolsPlugin.Action.UPDATE_SELECTION, new ClipboardBoundsSnapshot(this.selection)); + Vector3i min = Vector3i.min(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + Vector3i max = Vector3i.max(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + if (direction.getX() < 0) { + min = min.add(direction.getX(), 0, 0); + } else if (direction.getX() > 0) { + max = max.add(direction.getX(), 0, 0); + } + + if (direction.getY() < 0) { + min = min.add(0, direction.getY(), 0); + } else if (direction.getY() > 0) { + max = max.add(0, direction.getY(), 0); + } + + if (direction.getZ() < 0) { + min = min.add(0, 0, direction.getZ()); + } else if (direction.getZ() > 0) { + max = max.add(0, 0, direction.getZ()); + } + + this.selection.setSelectionArea(min, max); + this.sendArea(); + this.sendFeedback( + Message.translation("server.builderTools.selectionExpanded") + .param("x", direction.getX()) + .param("y", direction.getY()) + .param("z", direction.getZ()), + componentAccessor + ); + } + } + + public void contract(@Nonnull Ref ref, @Nonnull Vector3i direction, @Nonnull ComponentAccessor componentAccessor) { + if (this.selection == null) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelection"), componentAccessor); + } else if (!this.selection.hasSelectionBounds()) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionBounds"), componentAccessor); + } else { + this.pushHistory(BuilderToolsPlugin.Action.UPDATE_SELECTION, new ClipboardBoundsSnapshot(this.selection)); + Vector3i min = Vector3i.min(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + Vector3i max = Vector3i.max(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + if (direction.getX() > 0) { + min = min.add(direction.getX(), 0, 0); + } else if (direction.getX() < 0) { + max = max.add(direction.getX(), 0, 0); + } + + if (direction.getY() > 0) { + min = min.add(0, direction.getY(), 0); + } else if (direction.getY() < 0) { + max = max.add(0, direction.getY(), 0); + } + + if (direction.getZ() > 0) { + min = min.add(0, 0, direction.getZ()); + } else if (direction.getZ() < 0) { + max = max.add(0, 0, direction.getZ()); + } + + this.selection.setSelectionArea(min, max); + this.sendArea(); + this.sendFeedback( + ref, + Message.translation("server.builderTools.selectionContracted") + .param("x", direction.getX()) + .param("y", direction.getY()) + .param("z", direction.getZ()), + direction.length() > 0.0 ? "CREATE_SCALE_INCREASE" : "CREATE_SCALE_DECREASE", + componentAccessor + ); + } + } + + public void repairFillers(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + if (this.selection == null) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelection"), componentAccessor); + } else if (!this.selection.hasSelectionBounds()) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionBounds"), componentAccessor); + } else { + long start = System.nanoTime(); + Vector3i min = Vector3i.min(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + Vector3i max = Vector3i.max(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + int xMin = min.getX(); + int xMax = max.getX(); + int yMin = min.getY(); + int yMax = max.getY(); + int zMin = min.getZ(); + int zMax = max.getZ(); + int totalBlocks = (xMax - xMin + 1) * (zMax - zMin + 1) * (yMax - yMin + 1); + int width = xMax - xMin; + int height = yMax - yMin; + int depth = zMax - zMin; + int halfWidth = width / 2; + int halfHeight = height / 2; + int halfDepth = depth / 2; + BlockSelection before = new BlockSelection(totalBlocks, 0); + before.setPosition(xMin + halfWidth, yMin, zMin + halfDepth); + before.setSelectionArea(min, max); + this.pushHistory(BuilderToolsPlugin.Action.SET, new BlockSelectionSnapshot(before)); + BlockSelection after = new BlockSelection(totalBlocks, 0); + after.copyPropertiesFrom(before); + World world = componentAccessor.getExternalData().getWorld(); + Store chunkStore = world.getChunkStore().getStore(); + BuilderToolsPlugin.CachedAccessor cachedAccessor = BuilderToolsPlugin.CachedAccessor.of( + chunkStore, + ChunkUtil.chunkCoordinate(xMin + halfWidth), + ChunkUtil.chunkCoordinate(yMin + halfHeight), + ChunkUtil.chunkCoordinate(zMin + halfDepth), + Math.max(Math.max(width, depth), height) + ); + BlockTypeAssetMap blockTypeMap = BlockType.getAssetMap(); + IndexedLookupTableAssetMap blockHitboxMap = BlockBoundingBoxes.getAssetMap(); + int counter = 0; + + for (int x = xMin; x <= xMax; x++) { + int cx = ChunkUtil.chunkCoordinate(x); + + for (int z = zMin; z <= zMax; z++) { + int cz = ChunkUtil.chunkCoordinate(z); + Ref chunkRef = cachedAccessor.getChunk(cx, cz); + WorldChunk wc = chunkStore.getComponent(chunkRef, WorldChunk.getComponentType()); + + for (int y = yMax; y >= yMin; y--) { + int cy = ChunkUtil.chunkCoordinate(y); + BlockSection chunk = cachedAccessor.getBlockSection(cx, cy, cz); + if (chunk != null) { + int block = chunk.get(x, y, z); + BlockType blockType = blockTypeMap.getAsset(block); + if (blockType != null) { + BlockPhysics physics = cachedAccessor.getBlockPhysics(cx, cy, cz); + BlockBoundingBoxes hitbox = blockHitboxMap.getAsset(blockType.getHitboxTypeIndex()); + if (chunk.getFiller(x, y, z) != 0) { + before.copyFromAtWorld(x, y, z, wc, physics); + after.copyFromAtWorld(x, y, z, wc, physics); + } else if (hitbox != null && hitbox.protrudesUnitBox()) { + before.copyFromAtWorld(x, y, z, wc, physics); + after.copyFromAtWorld(x, y, z, wc, physics); + int finalX = x; + int finalY = y; + int finalZ = z; + FillerBlockUtil.forEachFillerBlock( + hitbox.get(chunk.getRotationIndex(x, y, z)), + (x1, y1, z1) -> before.copyFromAtWorld(finalX + x1, finalY + y1, finalZ + z1, wc, physics) + ); + } + + this.sendFeedback("Gather 1/2", totalBlocks, ++counter, componentAccessor); + } + } + } + } + } + + after.tryFixFiller(false); + after.placeNoReturn("Set 2/2", this.player, BuilderToolsPlugin.FEEDBACK_CONSUMER, world, componentAccessor); + BuilderToolsPlugin.invalidateWorldMapForSelection(after, world); + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute repair of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), after.getBlockCount()); + this.sendUpdate(); + this.sendArea(); + } + } + + @Nonnull + public List undo(@Nonnull Ref ref, int count, @Nonnull ComponentAccessor componentAccessor) { + long start = System.nanoTime(); + BlockSelection before = this.selection; + List list = new ObjectArrayList<>(); + + for (int i = 0; i < count; i++) { + BuilderToolsPlugin.ActionEntry action = this.historyAction(ref, this.undo, this.redo, componentAccessor); + if (action == null) { + break; + } + + list.add(action); + } + + if (before != this.selection) { + this.sendUpdate(); + } + + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute undo of %d actions", diff, TimeUnit.NANOSECONDS.toMillis(diff), count); + if (list.isEmpty()) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.nothingToUndo"), componentAccessor); + } else { + int i = 0; + + for (BuilderToolsPlugin.ActionEntry pair : list) { + this.sendFeedback( + ref, + Message.translation("server.builderTools.undoStatus").param("index", ++i).param("action", pair.getAction().name()), + "CREATE_UNDO", + componentAccessor + ); + } + } + + return list; + } + + @Nonnull + public List redo(@Nonnull Ref ref, int count, @Nonnull ComponentAccessor componentAccessor) { + long start = System.nanoTime(); + List list = new ObjectArrayList<>(); + + for (int i = 0; i < count; i++) { + BuilderToolsPlugin.ActionEntry action = this.historyAction(ref, this.redo, this.undo, componentAccessor); + if (action == null) { + break; + } + + list.add(action); + } + + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute redo of %d actions", diff, TimeUnit.NANOSECONDS.toMillis(diff), count); + if (list.isEmpty()) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.nothingToRedo"), componentAccessor); + } else { + int i = 0; + + for (BuilderToolsPlugin.ActionEntry pair : list) { + this.sendFeedback( + ref, + Message.translation("server.builderTools.redoStatus").param("index", ++i).param("action", pair.getAction().name()), + "CREATE_REDO", + componentAccessor + ); + } + } + + return list; + } + + public void save( + @Nonnull Ref ref, @Nonnull String name, boolean relativize, boolean overwrite, ComponentAccessor componentAccessor + ) { + if (this.selection == null) { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelection"), componentAccessor); + } else { + long start = System.nanoTime(); + if (!name.endsWith(".prefab.json")) { + name = name + ".prefab.json"; + } + + PrefabStore prefabStore = PrefabStore.get(); + Path serverPrefabsPath = prefabStore.getServerPrefabsPath(); + if (!PathUtil.isChildOf(serverPrefabsPath, serverPrefabsPath.resolve(name)) && !SingleplayerModule.isOwner(this.playerRef)) { + this.sendFeedback(Message.translation("server.builderTools.attemptedToSaveOutsidePrefabsDir"), componentAccessor); + } else { + try { + BlockSelection postClone = relativize ? this.selection.relativize() : this.selection.cloneSelection(); + prefabStore.saveServerPrefab(name, postClone, overwrite); + this.sendUpdate(); + this.sendFeedback(Message.translation("server.builderTools.savedSelectionToPrefab").param("name", name), componentAccessor); + } catch (PrefabSaveException var14) { + switch (var14.getType()) { + case ERROR: + BuilderToolsPlugin.get().getLogger().at(Level.WARNING).withCause(var14).log("Exception saving prefab %s", name); + this.sendFeedback( + Message.translation("server.builderTools.errorSavingPrefab").param("name", name).param("message", var14.getCause().getMessage()), + componentAccessor + ); + break; + case ALREADY_EXISTS: + BuilderToolsPlugin.get().getLogger().at(Level.WARNING).log("Prefab already exists %s", name); + this.sendFeedback(Message.translation("server.builderTools.prefabAlreadyExists"), componentAccessor); + } + } + + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute save of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), this.selection.getBlockCount()); + } + } + } + + public void saveFromSelection( + @Nonnull Ref ref, + @Nonnull String name, + boolean relativize, + boolean overwrite, + boolean includeEntities, + boolean includeEmpty, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.selection != null && (!this.selection.getSelectionMin().equals(Vector3i.ZERO) || !this.selection.getSelectionMax().equals(Vector3i.ZERO))) { + World world = componentAccessor.getExternalData().getWorld(); + long start = System.nanoTime(); + if (!name.endsWith(".prefab.json")) { + name = name + ".prefab.json"; + } + + PrefabStore prefabStore = PrefabStore.get(); + Path serverPrefabsPath = prefabStore.getServerPrefabsPath(); + if (!PathUtil.isChildOf(serverPrefabsPath, serverPrefabsPath.resolve(name)) && !SingleplayerModule.isOwner(this.playerRef)) { + this.sendFeedback(Message.translation("server.builderTools.attemptedToSaveOutsidePrefabsDir"), componentAccessor); + } else { + Vector3i min = Vector3i.min(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + Vector3i max = Vector3i.max(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + int xMin = min.getX(); + int yMin = min.getY(); + int zMin = min.getZ(); + int xMax = max.getX(); + int yMax = max.getY(); + int zMax = max.getZ(); + int width = xMax - xMin; + int height = yMax - yMin; + int depth = zMax - zMin; + int halfWidth = width / 2; + int halfDepth = depth / 2; + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords(world, xMin + halfWidth, zMin + halfDepth, Math.max(width, depth)); + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + int editorBlock = assetMap.getIndex("Editor_Block"); + int editorBlockPrefabAir = assetMap.getIndex("Editor_Empty"); + int editorBlockPrefabAnchor = assetMap.getIndex("Editor_Anchor"); + BlockSelection tempSelection = new BlockSelection(); + tempSelection.setPosition(xMin + halfWidth, yMin, zMin + halfDepth); + tempSelection.setSelectionArea(min, max); + int count = 0; + int top = Math.max(yMin, yMax); + int bottom = Math.min(yMin, yMax); + + for (int x = xMin; x <= xMax; x++) { + for (int z = zMin; z <= zMax; z++) { + WorldChunk chunk = accessor.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + Store store = chunk.getReference().getStore(); + ChunkColumn chunkColumn = store.getComponent(chunk.getReference(), ChunkColumn.getComponentType()); + int lastSection = -1; + BlockPhysics blockPhysics = null; + + for (int y = top; y >= bottom; y--) { + int block = chunk.getBlock(x, y, z); + int fluid = chunk.getFluidId(x, y, z); + if (lastSection != ChunkUtil.chunkCoordinate(y)) { + lastSection = ChunkUtil.chunkCoordinate(y); + Ref section = chunkColumn.getSection(lastSection); + if (section != null) { + blockPhysics = store.getComponent(section, BlockPhysics.getComponentType()); + } else { + blockPhysics = null; + } + } + + if (block == editorBlockPrefabAnchor) { + tempSelection.setAnchorAtWorldPos(x, y, z); + int id = BuilderToolsPlugin.getNonEmptyNeighbourBlock(accessor, x, y, z); + if (id > 0 && id != editorBlockPrefabAir) { + tempSelection.addBlockAtWorldPos(x, y, z, id, 0, 0, 0); + count++; + } else if (id == editorBlockPrefabAir) { + tempSelection.addBlockAtWorldPos(x, y, z, 0, 0, 0, 0); + count++; + } + } else if ((block != 0 || fluid != 0 || includeEmpty) && block != editorBlock) { + if (block == editorBlockPrefabAir) { + tempSelection.addBlockAtWorldPos(x, y, z, 0, 0, 0, 0); + } else { + tempSelection.copyFromAtWorld(x, y, z, chunk, blockPhysics); + } + + count++; + } + } + } + } + + if (includeEntities) { + Store entityStore = world.getEntityStore().getStore(); + BuilderToolsPlugin.forEachCopyableInSelection(world, xMin, yMin, zMin, width, height, depth, e -> { + Holder holder = entityStore.copyEntity(e); + tempSelection.addEntityFromWorld(holder); + }); + } + + try { + BlockSelection postClone = relativize ? tempSelection.relativize() : tempSelection.cloneSelection(); + prefabStore.saveServerPrefab(name, postClone, overwrite); + this.sendFeedback(Message.translation("server.builderTools.savedSelectionToPrefab").param("name", name), componentAccessor); + } catch (PrefabSaveException var46) { + switch (var46.getType()) { + case ERROR: + BuilderToolsPlugin.get().getLogger().at(Level.WARNING).withCause(var46).log("Exception saving prefab %s", name); + this.sendFeedback( + Message.translation("server.builderTools.errorSavingPrefab").param("name", name).param("message", var46.getCause().getMessage()), + componentAccessor + ); + break; + case ALREADY_EXISTS: + BuilderToolsPlugin.get().getLogger().at(Level.WARNING).log("Prefab already exists %s", name); + this.sendFeedback(Message.translation("server.builderTools.prefabAlreadyExists"), componentAccessor); + } + } + + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute saveFromSelection of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), count); + } + } else { + this.sendErrorFeedback(ref, Message.translation("server.builderTools.noSelectionBounds"), componentAccessor); + } + } + + public void load(@Nonnull String name, ComponentAccessor componentAccessor) { + if (!name.endsWith(".prefab.json")) { + name = name + ".prefab.json"; + } + + this.load(name, PrefabStore.get().getServerPrefab(name), componentAccessor); + } + + public void load(@Nonnull String name, @Nonnull BlockSelection serverPrefab, ComponentAccessor componentAccessor) { + long start = System.nanoTime(); + + try { + Vector3i min = Vector3i.ZERO; + Vector3i max = Vector3i.ZERO; + if (this.selection != null) { + Objects.requireNonNull(this.selection.getSelectionMin(), "min is null"); + Objects.requireNonNull(this.selection.getSelectionMax(), "max is null"); + min = Vector3i.min(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + max = Vector3i.max(this.selection.getSelectionMin(), this.selection.getSelectionMax()); + } + + this.selection = serverPrefab.cloneSelection(); + this.selection.setSelectionArea(min, max); + this.sendUpdate(); + this.sendFeedback(Message.translation("server.general.loadedPrefab").param("name", name), componentAccessor); + } catch (PrefabLoadException var10) { + switch (var10.getType()) { + case ERROR: + BuilderToolsPlugin.get().getLogger().at(Level.WARNING).withCause(var10).log("Exception loading prefab %s", name); + this.sendFeedback( + Message.translation("server.builderTools.errorSavingPrefab").param("name", name).param("message", var10.getCause().getMessage()), + componentAccessor + ); + break; + case NOT_FOUND: + BuilderToolsPlugin.get().getLogger().at(Level.WARNING).log("Prefab doesn't exist %s", name); + this.sendFeedback(Message.translation("server.builderTools.prefabDoesNotExist").param("name", name), componentAccessor); + } + } + + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute load of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), this.selection.getBlockCount()); + } + + public void clearHistory(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + long stamp = this.undoLock.writeLock(); + + try { + this.undo.clear(); + this.redo.clear(); + } finally { + this.undoLock.unlockWrite(stamp); + } + + this.sendFeedback(Message.translation("server.builderTools.historyCleared"), componentAccessor); + } + + public void setGlobalMask(@Nullable BlockMask mask, @Nonnull ComponentAccessor componentAccessor) { + this.globalMask = mask; + if (this.globalMask == null) { + this.sendFeedback(Message.translation("server.builderTools.maskDisabled"), componentAccessor); + } else { + this.sendFeedback(Message.translation("server.builderTools.maskSet"), componentAccessor); + } + } + + private void sendUpdate() { + this.player.getPlayerConnection().write(Objects.requireNonNullElseGet(this.selection, BlockSelection::new).toPacket()); + } + + public void sendArea() { + if (this.selection != null) { + this.player.getPlayerConnection().write(this.selection.toSelectionPacket()); + } else { + EditorBlocksChange packet = new EditorBlocksChange(); + packet.selection = null; + this.player.getPlayerConnection().write(packet); + } + } + + private void pushHistory(BuilderToolsPlugin.Action action, SelectionSnapshot snapshot) { + this.pushHistory(action, Collections.singletonList(snapshot)); + } + + private void pushHistory(BuilderToolsPlugin.Action action, List> snapshots) { + if (action != BuilderToolsPlugin.Action.UPDATE_SELECTION || this.getUserData().isRecordingSelectionHistory()) { + long stamp = this.undoLock.writeLock(); + + try { + this.undo.enqueue(new BuilderToolsPlugin.ActionEntry(action, snapshots)); + this.redo.clear(); + + while (this.undo.size() > BuilderToolsPlugin.get().historyCount) { + this.undo.dequeue(); + } + } finally { + this.undoLock.unlockWrite(stamp); + } + + if (action != BuilderToolsPlugin.Action.UPDATE_SELECTION + && action != BuilderToolsPlugin.Action.COPY + && action != BuilderToolsPlugin.Action.CUT_COPY) { + this.markPrefabsDirtyFromSnapshots(snapshots); + } + } + } + + private void markPrefabsDirtyFromSnapshots(@Nonnull List> snapshots) { + PrefabEditSessionManager prefabEditSessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + Map activeEditSessions = prefabEditSessionManager.getActiveEditSessions(); + if (!activeEditSessions.isEmpty()) { + for (SelectionSnapshot snapshot : snapshots) { + if (snapshot instanceof BlockSelectionSnapshot blockSnapshot) { + BlockSelection blockSelection = blockSnapshot.getBlockSelection(); + Vector3i min = blockSelection.getSelectionMin(); + Vector3i max = blockSelection.getSelectionMax(); + + for (Entry entry : activeEditSessions.entrySet()) { + entry.getValue().markPrefabsDirtyInBounds(min, max); + } + } + } + } + } + + @Nullable + private BuilderToolsPlugin.ActionEntry historyAction( + Ref ref, + @Nonnull ObjectArrayFIFOQueue from, + @Nonnull ObjectArrayFIFOQueue to, + ComponentAccessor componentAccessor + ) { + long stamp = this.undoLock.writeLock(); + + BuilderToolsPlugin.ActionEntry builderAction; + try { + if (!from.isEmpty()) { + builderAction = from.dequeueLast(); + to.enqueue(builderAction.restore(ref, this.player, componentAccessor.getExternalData().getWorld(), componentAccessor)); + + while (to.size() > BuilderToolsPlugin.get().historyCount) { + to.dequeue(); + } + + return builderAction; + } + + builderAction = null; + } finally { + this.undoLock.unlockWrite(stamp); + } + + return builderAction; + } + + public static class BlocksSampleData { + public int mainBlock = 0; + public int mainBlockCount = 0; + public int mainBlockNotAir = 0; + public int mainBlockNotAirCount = 0; + + public BlocksSampleData() { + } + } + + public static class SmoothSampleData { + public float solidStrength = 0.0F; + public int solidBlock = 0; + public int solidBlockCount = 0; + public int fillerBlock = 0; + public int fillerBlockCount = 0; + + public SmoothSampleData() { + } + } + } + + public static class BuilderToolsConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder( + BuilderToolsPlugin.BuilderToolsConfig.class, BuilderToolsPlugin.BuilderToolsConfig::new + ) + .append(new KeyedCodec<>("HistoryCount", Codec.INTEGER), (o, i) -> o.historyCount = i, o -> o.historyCount) + .documentation("The number of builder tool edit operations to keep in the undo/redo history") + .add() + .append(new KeyedCodec<>("ToolExpireTime", Codec.LONG), (o, l) -> o.toolExpireTime = l, o -> o.toolExpireTime) + .documentation( + "The minimum time (in seconds) that a user's builder tool data will be persisted for after they disconnect from the server. If set to zero the player's data is removed immediately on disconnect" + ) + .addValidator(Validators.greaterThanOrEqual(0L)) + .add() + .build(); + private int historyCount = 50; + private long toolExpireTime = 3600L; + + public BuilderToolsConfig() { + } + } + + public static class CachedAccessor extends AbstractCachedAccessor { + private static final ThreadLocal THREAD_LOCAL = ThreadLocal.withInitial(BuilderToolsPlugin.CachedAccessor::new); + private static final int FLUID_COMPONENT = 0; + private static final int PHYSICS_COMPONENT = 1; + private static final int BLOCKS_COMPONENT = 2; + + public CachedAccessor() { + super(3); + } + + @Nonnull + public static BuilderToolsPlugin.CachedAccessor of(ComponentAccessor accessor, int cx, int cy, int cz, int radius) { + BuilderToolsPlugin.CachedAccessor cachedAccessor = THREAD_LOCAL.get(); + cachedAccessor.init(accessor, cx, cy, cz, radius); + return cachedAccessor; + } + + @Nullable + public FluidSection getFluidSection(int cx, int cy, int cz) { + return this.getComponentSection(cx, cy, cz, 0, FluidSection.getComponentType()); + } + + @Nullable + public BlockPhysics getBlockPhysics(int cx, int cy, int cz) { + return this.getComponentSection(cx, cy, cz, 1, BlockPhysics.getComponentType()); + } + + @Nullable + public BlockSection getBlockSection(int cx, int cy, int cz) { + return this.getComponentSection(cx, cy, cz, 2, BlockSection.getComponentType()); + } + } + + public static class PrefabPasteEventSystem extends WorldEventSystem { + @Nonnull + private final BuilderToolsPlugin plugin; + + protected PrefabPasteEventSystem(@Nonnull BuilderToolsPlugin plugin) { + super(PrefabPasteEvent.class); + this.plugin = plugin; + } + + public void handle(@Nonnull Store store, @Nonnull CommandBuffer commandBuffer, @Nonnull PrefabPasteEvent event) { + if (event.isPasteStart()) { + this.plugin.pastedPrefabPathUUIDMap.put(event.getPrefabId(), new ConcurrentHashMap<>()); + this.plugin.pastedPrefabPathNameToUUIDMap.put(event.getPrefabId(), new ConcurrentHashMap<>()); + } else { + this.plugin.pastedPrefabPathUUIDMap.remove(event.getPrefabId()); + this.plugin.pastedPrefabPathNameToUUIDMap.remove(event.getPrefabId()); + } + } + } + + private static final class QueuedTask { + @Nonnull + private final ThrowableTriConsumer, BuilderToolsPlugin.BuilderState, ComponentAccessor, ? extends Throwable> task; + + private QueuedTask( + @Nonnull ThrowableTriConsumer, BuilderToolsPlugin.BuilderState, ComponentAccessor, ? extends Throwable> biTask + ) { + this.task = biTask; + } + + void execute( + @Nonnull Ref ref, @Nonnull BuilderToolsPlugin.BuilderState state, @Nonnull ComponentAccessor defaultComponentAccessor + ) throws Throwable { + this.task.acceptNow(ref, state, defaultComponentAccessor); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/BuilderToolsSystems.java b/src/com/hypixel/hytale/builtin/buildertools/BuilderToolsSystems.java new file mode 100644 index 0000000..5966a25 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/BuilderToolsSystems.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.builtin.buildertools; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.server.core.asset.type.item.config.BuilderToolItemReferenceAsset; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +public class BuilderToolsSystems { + public BuilderToolsSystems() { + } + + public static class EnsureBuilderTools extends HolderSystem { + @Nonnull + private static final ComponentType PLAYER_COMPONENT_TYPE = Player.getComponentType(); + + public EnsureBuilderTools() { + } + + @Nonnull + @Override + public Query getQuery() { + return PLAYER_COMPONENT_TYPE; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + Player playerComponent = holder.getComponent(PLAYER_COMPONENT_TYPE); + + assert playerComponent != null; + + Map builderTools = BuilderToolItemReferenceAsset.getAssetMap().getAssetMap(); + Inventory playerInventory = playerComponent.getInventory(); + ItemContainer playerTools = playerInventory.getTools(); + playerTools.clear(); + List toolsToAdd = new ObjectArrayList<>(); + + for (BuilderToolItemReferenceAsset builderTool : builderTools.values()) { + String[] builderToolItems = builderTool.getItems(); + + for (String builderToolItem : builderToolItems) { + toolsToAdd.add(new ItemStack(builderToolItem)); + } + } + + if (!playerTools.addItemStacks(toolsToAdd).succeeded()) { + throw new IllegalArgumentException("Could not add items to the Tools container"); + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/BuilderToolsUserData.java b/src/com/hypixel/hytale/builtin/buildertools/BuilderToolsUserData.java new file mode 100644 index 0000000..c157f7c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/BuilderToolsUserData.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.builtin.buildertools; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderToolsUserData implements Component { + public static final String ID = "BuilderTools"; + private static final String SELECTION_HISTORY_KEY = "SelectionHistory"; + private static final String SELECTION_HISTORY_DOC = "Controls whether changes to the block selection box are recorded in the undo/redo history."; + public static final BuilderCodec CODEC = BuilderCodec.builder(BuilderToolsUserData.class, BuilderToolsUserData::new) + .append( + new KeyedCodec<>("SelectionHistory", Codec.BOOLEAN), + BuilderToolsUserData::setRecordSelectionHistory, + BuilderToolsUserData::isRecordingSelectionHistory + ) + .addValidator(Validators.nonNull()) + .documentation("Controls whether changes to the block selection box are recorded in the undo/redo history.") + .add() + .build(); + private boolean selectionHistory = true; + + @Nonnull + public static BuilderToolsUserData get(@Nonnull Player player) { + BuilderToolsUserData userData = player.toHolder().getComponent(getComponentType()); + return userData == null ? new BuilderToolsUserData() : userData; + } + + public static ComponentType getComponentType() { + return BuilderToolsPlugin.get().getUserDataComponentType(); + } + + public BuilderToolsUserData() { + } + + public boolean isRecordingSelectionHistory() { + return this.selectionHistory; + } + + public void setRecordSelectionHistory(boolean selectionHistory) { + this.selectionHistory = selectionHistory; + } + + @Nonnull + @Override + public String toString() { + return "BuilderToolsUserData{selectionHistory=" + this.selectionHistory + "}"; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + BuilderToolsUserData that = (BuilderToolsUserData)o; + return this.selectionHistory == that.selectionHistory; + } else { + return false; + } + } + + @Override + public int hashCode() { + return this.selectionHistory ? 1 : 0; + } + + @Nonnull + @Override + public Component clone() { + BuilderToolsUserData settings = new BuilderToolsUserData(); + settings.selectionHistory = this.selectionHistory; + return settings; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/BuilderToolsUserDataSystem.java b/src/com/hypixel/hytale/builtin/buildertools/BuilderToolsUserDataSystem.java new file mode 100644 index 0000000..e5b8ed8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/BuilderToolsUserDataSystem.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.buildertools; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class BuilderToolsUserDataSystem extends HolderSystem { + private static final Query QUERY = Query.and(Player.getComponentType(), Query.not(BuilderToolsUserData.getComponentType())); + + public BuilderToolsUserDataSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(BuilderToolsUserData.getComponentType()); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/CopyCutSettings.java b/src/com/hypixel/hytale/builtin/buildertools/CopyCutSettings.java new file mode 100644 index 0000000..47160b0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/CopyCutSettings.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.builtin.buildertools; + +public class CopyCutSettings { + public static final int NONE = 0; + public static final int CUT = 2; + public static final int EMPTY = 4; + public static final int BLOCKS = 8; + public static final int ENTITIES = 16; + public static final int TINT_MAP = 32; + public static final int KEEP_ANCHORS = 64; + public static final int FLUIDS = 128; + + public CopyCutSettings() { + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/EditOperation.java b/src/com/hypixel/hytale/builtin/buildertools/EditOperation.java new file mode 100644 index 0000000..db92e8a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/EditOperation.java @@ -0,0 +1,133 @@ +package com.hypixel.hytale.builtin.buildertools; + +import com.hypixel.hytale.builtin.buildertools.utils.Material; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockMask; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.accessor.BlockAccessor; +import com.hypixel.hytale.server.core.universe.world.accessor.LocalCachedChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.accessor.OverridableChunkAccessor; +import javax.annotation.Nonnull; + +public class EditOperation { + private final BlockMask blockMask; + @Nonnull + private final OverridableChunkAccessor accessor; + @Nonnull + private final BlockSelection before; + @Nonnull + private final BlockSelection after; + private final Vector3i min; + private final Vector3i max; + + public EditOperation(@Nonnull World world, int x, int y, int z, int editRange, Vector3i min, Vector3i max, BlockMask blockMask) { + this.blockMask = blockMask; + this.accessor = LocalCachedChunkAccessor.atWorldCoords(world, x, z, editRange); + this.min = min; + this.max = max; + this.before = new BlockSelection(); + this.before.setPosition(x, y, z); + if (min != null && max != null) { + this.before.setSelectionArea(min, max); + } + + this.after = new BlockSelection(this.before); + } + + public BlockMask getBlockMask() { + return this.blockMask; + } + + @Nonnull + public BlockSelection getBefore() { + return this.before; + } + + @Nonnull + public BlockSelection getAfter() { + return this.after; + } + + @Nonnull + public OverridableChunkAccessor getAccessor() { + return this.accessor; + } + + public int getBlock(int x, int y, int z) { + return this.accessor.getBlock(x, y, z); + } + + public boolean setBlock(int x, int y, int z, int blockId) { + return this.setBlock(x, y, z, blockId, 0); + } + + public boolean setBlock(int x, int y, int z, int blockId, int rotation) { + int currentBlock = this.getBlock(x, y, z); + int currentFluid = this.getFluid(x, y, z); + if (this.blockMask != null && this.blockMask.isExcluded(this.accessor, x, y, z, this.min, this.max, currentBlock, currentFluid)) { + return false; + } else { + BlockAccessor blocks = this.accessor.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(x, z)); + if (blocks == null) { + return false; + } else { + if (!this.before.hasBlockAtWorldPos(x, y, z)) { + this.before + .addBlockAtWorldPos( + x, + y, + z, + currentBlock, + blocks.getRotationIndex(x, y, z), + blocks.getFiller(x, y, z), + blocks.getSupportValue(x, y, z), + blocks.getBlockComponentHolder(x, y, z) + ); + } + + this.after.addBlockAtWorldPos(x, y, z, blockId, rotation, 0, 0); + if (blockId == 0) { + this.setFluid(x, y, z, 0, (byte)0); + } + + return true; + } + } + } + + private boolean setFluid(int x, int y, int z, int fluidId, byte fluidLevel) { + BlockAccessor chunk = this.accessor.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(x, z)); + if (chunk == null) { + return false; + } else { + int currentBlock = this.getBlock(x, y, z); + int currentFluid = this.getFluid(x, y, z); + if (this.blockMask != null && this.blockMask.isExcluded(this.accessor, x, y, z, this.min, this.max, currentBlock, currentFluid)) { + return false; + } else { + int beforeFluid = this.before.getFluidAtWorldPos(x, y, z); + if (beforeFluid < 0) { + int originalFluidId = chunk.getFluidId(x, y, z); + byte originalFluidLevel = chunk.getFluidLevel(x, y, z); + this.before.addFluidAtWorldPos(x, y, z, originalFluidId, originalFluidLevel); + } + + this.after.addFluidAtWorldPos(x, y, z, fluidId, fluidLevel); + return true; + } + } + } + + public int getFluid(int x, int y, int z) { + BlockAccessor chunk = this.accessor.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(x, z)); + return chunk != null ? chunk.getFluidId(x, y, z) : 0; + } + + public boolean setMaterial(int x, int y, int z, @Nonnull Material material) { + return material.isFluid() + ? this.setFluid(x, y, z, material.getFluidId(), material.getFluidLevel()) + : this.setBlock(x, y, z, material.getBlockId(), material.getRotation()); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/PrefabCopyException.java b/src/com/hypixel/hytale/builtin/buildertools/PrefabCopyException.java new file mode 100644 index 0000000..f3eb0c1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/PrefabCopyException.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.builtin.buildertools; + +public class PrefabCopyException extends Exception { + public PrefabCopyException(String message) { + super(message); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/PrototypePlayerBuilderToolSettings.java b/src/com/hypixel/hytale/builtin/buildertools/PrototypePlayerBuilderToolSettings.java new file mode 100644 index 0000000..7f22a4d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/PrototypePlayerBuilderToolSettings.java @@ -0,0 +1,207 @@ +package com.hypixel.hytale.builtin.buildertools; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.tooloperations.ToolOperation; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.block.BlockUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.packets.interface_.BlockChange; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import java.util.LinkedList; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrototypePlayerBuilderToolSettings { + @Nonnull + private static final Message MESSAGE_BUILDER_TOOLS_CANNOT_PERFORM_COMMAND_IN_TRANSFORMATION_MODE = Message.translation( + "server.builderTools.cannotPerformCommandInTransformationMode" + ); + private final UUID player; + private final LinkedList ignoredPaintOperations = new LinkedList<>(); + private int maxLengthOfIgnoredPaintOperations; + private boolean shouldShowEditorSettings; + private boolean isLoadingBrush; + private boolean usePrototypeBrushConfigurations; + private String currentlyLoadedBrushConfigName = ""; + private BrushConfig brushConfig = new BrushConfig(); + private BrushConfigCommandExecutor brushConfigCommandExecutor; + private boolean isInSelectionTransformationMode = false; + @Nullable + private BlockChange[] blockChangesForPlaySelectionToolPasteMode = null; + @Nullable + private PrototypePlayerBuilderToolSettings.FluidChange[] fluidChangesForPlaySelectionToolPasteMode = null; + @Nullable + private Vector3i lastBrushPosition = null; + @Nullable + private Vector3i blockChangeOffsetOrigin = null; + + public PrototypePlayerBuilderToolSettings(UUID player) { + this.player = player; + this.brushConfigCommandExecutor = new BrushConfigCommandExecutor(this.brushConfig); + } + + public UUID getPlayer() { + return this.player; + } + + public boolean isInSelectionTransformationMode() { + return this.isInSelectionTransformationMode; + } + + public void setInSelectionTransformationMode(boolean inSelectionTransformationMode) { + this.isInSelectionTransformationMode = inSelectionTransformationMode; + if (!this.isInSelectionTransformationMode) { + this.blockChangesForPlaySelectionToolPasteMode = null; + this.fluidChangesForPlaySelectionToolPasteMode = null; + this.blockChangeOffsetOrigin = null; + } + } + + public void setBlockChangesForPlaySelectionToolPasteMode(@Nullable BlockChange[] blockChangesForPlaySelectionToolPasteMode) { + this.blockChangesForPlaySelectionToolPasteMode = blockChangesForPlaySelectionToolPasteMode; + } + + public String getCurrentlyLoadedBrushConfigName() { + return this.currentlyLoadedBrushConfigName; + } + + public void setCurrentlyLoadedBrushConfigName(String currentlyLoadedBrushConfigName) { + this.currentlyLoadedBrushConfigName = currentlyLoadedBrushConfigName; + } + + public boolean isLoadingBrush() { + return this.isLoadingBrush; + } + + public void setLoadingBrush(boolean loadingBrush) { + this.isLoadingBrush = loadingBrush; + } + + @Nullable + public BlockChange[] getBlockChangesForPlaySelectionToolPasteMode() { + return this.blockChangesForPlaySelectionToolPasteMode; + } + + public void setFluidChangesForPlaySelectionToolPasteMode(@Nullable PrototypePlayerBuilderToolSettings.FluidChange[] fluidChanges) { + this.fluidChangesForPlaySelectionToolPasteMode = fluidChanges; + } + + @Nullable + public PrototypePlayerBuilderToolSettings.FluidChange[] getFluidChangesForPlaySelectionToolPasteMode() { + return this.fluidChangesForPlaySelectionToolPasteMode; + } + + public void setBlockChangeOffsetOrigin(@Nullable Vector3i blockChangeOffsetOrigin) { + this.blockChangeOffsetOrigin = blockChangeOffsetOrigin; + } + + @Nullable + public Vector3i getBlockChangeOffsetOrigin() { + return this.blockChangeOffsetOrigin; + } + + @Nonnull + public LongOpenHashSet addIgnoredPaintOperation() { + LongOpenHashSet longs = new LongOpenHashSet(); + this.ignoredPaintOperations.add(longs); + this.clearHistoryUntilFitMaxLength(); + return longs; + } + + public void clearHistoryUntilFitMaxLength() { + while (this.ignoredPaintOperations.size() > this.maxLengthOfIgnoredPaintOperations) { + this.ignoredPaintOperations.removeFirst(); + } + } + + public boolean containsLocation(int x, int y, int z) { + long packedBlockLocation = BlockUtil.pack(x, y, z); + + for (LongOpenHashSet locations : this.ignoredPaintOperations) { + if (locations.contains(packedBlockLocation)) { + return true; + } + } + + return false; + } + + @Nonnull + public LinkedList getIgnoredPaintOperations() { + return this.ignoredPaintOperations; + } + + public int getMaxLengthOfIgnoredPaintOperations() { + return this.maxLengthOfIgnoredPaintOperations; + } + + public void setMaxLengthOfIgnoredPaintOperations(int maxLengthOfIgnoredPaintOperations) { + this.maxLengthOfIgnoredPaintOperations = maxLengthOfIgnoredPaintOperations; + this.clearHistoryUntilFitMaxLength(); + } + + public boolean usePrototypeBrushConfigurations() { + return this.usePrototypeBrushConfigurations; + } + + public void setUsePrototypeBrushConfigurations(boolean usePrototypeBrushConfigurations) { + this.usePrototypeBrushConfigurations = usePrototypeBrushConfigurations; + } + + public BrushConfig getBrushConfig() { + return this.brushConfig; + } + + public BrushConfigCommandExecutor getBrushConfigCommandExecutor() { + return this.brushConfigCommandExecutor; + } + + public void setBrushConfig(BrushConfig brushConfig) { + this.brushConfig = brushConfig; + } + + public boolean isShouldShowEditorSettings() { + return this.shouldShowEditorSettings; + } + + public void setShouldShowEditorSettings(boolean shouldShowEditorSettings) { + this.shouldShowEditorSettings = shouldShowEditorSettings; + } + + @Nullable + public Vector3i getLastBrushPosition() { + return this.lastBrushPosition; + } + + public void setLastBrushPosition(@Nullable Vector3i lastBrushPosition) { + this.lastBrushPosition = lastBrushPosition; + } + + public void clearLastBrushPosition() { + this.lastBrushPosition = null; + } + + public static boolean isOkayToDoCommandsOnSelection(Ref ref, @Nonnull Player player, ComponentAccessor componentAccessor) { + UUIDComponent uuidComponent = componentAccessor.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + PrototypePlayerBuilderToolSettings prototypeSettings = ToolOperation.getOrCreatePrototypeSettings(uuidComponent.getUuid()); + if (prototypeSettings.isInSelectionTransformationMode()) { + player.sendMessage(MESSAGE_BUILDER_TOOLS_CANNOT_PERFORM_COMMAND_IN_TRANSFORMATION_MODE); + return false; + } else { + return true; + } + } + + public record FluidChange(int x, int y, int z, int fluidId, byte fluidLevel) { + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/ClearBlocksCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/ClearBlocksCommand.java new file mode 100644 index 0000000..acd7f25 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/ClearBlocksCommand.java @@ -0,0 +1,95 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeIntPosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ClearBlocksCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CLEAR_NO_SELECTION = Message.translation("server.commands.clear.noSelection"); + + public ClearBlocksCommand() { + super("clearBlocks", "server.commands.clear.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addAliases("clear"); + this.addUsageVariant( + new AbstractPlayerCommand("server.commands.clear.desc") { + @Nonnull + private final RequiredArg positionOneArg = this.withRequiredArg( + "positionOne", "server.commands.clear.positionOne.desc", ArgTypes.RELATIVE_BLOCK_POSITION + ); + @Nonnull + private final RequiredArg positionTwoArg = this.withRequiredArg( + "positionTwo", "server.commands.clear.positionTwo.desc", ArgTypes.RELATIVE_BLOCK_POSITION + ); + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nonnull Store store, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world + ) { + ChunkStore chunkStore = world.getChunkStore(); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + Vector3d position = transformComponent.getPosition(); + RelativeIntPosition relativeIntPositionOne = this.positionOneArg.get(context); + RelativeIntPosition relativeIntPositionTwo = this.positionTwoArg.get(context); + Vector3i posOne = relativeIntPositionOne.getBlockPosition(position, chunkStore); + Vector3i posTwo = relativeIntPositionTwo.getBlockPosition(position, chunkStore); + Vector3i min = Vector3i.min(posOne, posTwo); + Vector3i max = Vector3i.max(posOne, posTwo); + BuilderToolsPlugin.addToQueue( + playerComponent, playerRef, (r, s, componentAccessor) -> s.clear(min.x, min.y, min.z, max.x, max.y, max.z, componentAccessor) + ); + } + } + } + ); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + BuilderToolsPlugin.BuilderState builderState = BuilderToolsPlugin.getState(playerComponent, playerRef); + if (builderState.getSelection() == null) { + playerRef.sendMessage(MESSAGE_COMMANDS_CLEAR_NO_SELECTION); + } else { + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.set(BlockPattern.EMPTY, componentAccessor)); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/ClearEditHistory.java b/src/com/hypixel/hytale/builtin/buildertools/commands/ClearEditHistory.java new file mode 100644 index 0000000..90f80c0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/ClearEditHistory.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ClearEditHistory extends AbstractPlayerCommand { + public ClearEditHistory() { + super("clearEditHistory", "server.commands.clearhistory.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addAliases("clearHistory", "clearToolHistory"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, c) -> s.clearHistory(r, c)); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/ClearEntitiesCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/ClearEntitiesCommand.java new file mode 100644 index 0000000..31bac75 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/ClearEntitiesCommand.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class ClearEntitiesCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_NO_SELECTION = Message.translation("server.commands.clearEntities.noSelection"); + @Nonnull + private static final Message MESSAGE_CLEARED = Message.translation("server.commands.clearEntities.cleared"); + + public ClearEntitiesCommand() { + super("clearEntities", "server.commands.clearEntities.desc"); + this.setPermissionGroup(GameMode.Creative); + this.requirePermission("hytale.editor.selection.clipboard"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + BuilderToolsPlugin.BuilderState builderState = BuilderToolsPlugin.getState(playerComponent, playerRef); + BlockSelection selection = builderState.getSelection(); + if (selection == null) { + context.sendMessage(MESSAGE_NO_SELECTION); + } else { + Vector3i min = selection.getSelectionMin(); + Vector3i max = selection.getSelectionMax(); + int width = max.getX() - min.getX(); + int height = max.getY() - min.getY(); + int depth = max.getZ() - min.getZ(); + ArrayList> entitiesToRemove = new ArrayList<>(); + BuilderToolsPlugin.forEachCopyableInSelection(world, min.getX(), min.getY(), min.getZ(), width, height, depth, entitiesToRemove::add); + Store entityStore = world.getEntityStore().getStore(); + + for (Ref entityRef : entitiesToRemove) { + entityStore.removeEntity(entityRef, RemoveReason.REMOVE); + } + + context.sendMessage(MESSAGE_CLEARED.param("count", entitiesToRemove.size())); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/ContractSelectionCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/ContractSelectionCommand.java new file mode 100644 index 0000000..406959f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/ContractSelectionCommand.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class ContractSelectionCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg distanceArg = this.withRequiredArg("distance", "server.commands.contract.arg.distance.desc", ArgTypes.INTEGER); + @Nonnull + private final OptionalArg> axisArg = this.withListOptionalArg("axis", "command.contract.arg.axis.desc", ArgTypes.forEnum("Axis", Axis.class)); + + public ContractSelectionCommand() { + super("contractSelection", "server.commands.contract.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addAliases("contract"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + int distance = this.distanceArg.get(context); + List directions = new ObjectArrayList<>(); + if (this.axisArg.provided(context)) { + for (Axis axis : this.axisArg.get(context)) { + directions.add(axis.getDirection().scale(distance)); + } + } else { + directions.add(headRotationComponent.getAxisDirection().scale(distance)); + } + + for (Vector3i direction : directions) { + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.contract(r, direction, componentAccessor)); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/CopyCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/CopyCommand.java new file mode 100644 index 0000000..e679760 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/CopyCommand.java @@ -0,0 +1,210 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrefabCopyException; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TempAssetIdUtil; +import javax.annotation.Nonnull; + +public class CopyCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_BUILDER_TOOLS_COPY_CUT_NO_SELECTION = Message.translation("server.builderTools.copycut.noSelection"); + @Nonnull + private final FlagArg noEntitiesFlag = this.withFlagArg("noEntities", "server.commands.copy.noEntities.desc"); + @Nonnull + private final FlagArg entitiesOnlyFlag = this.withFlagArg("onlyEntities", "server.commands.copy.entitiesonly.desc"); + @Nonnull + private final FlagArg emptyFlag = this.withFlagArg("empty", "server.commands.copy.empty.desc"); + @Nonnull + private final FlagArg keepAnchorsFlag = this.withFlagArg("keepanchors", "server.commands.copy.keepanchors.desc"); + + public CopyCommand() { + super("copy", "server.commands.copy.desc"); + this.setPermissionGroup(GameMode.Creative); + this.requirePermission("hytale.editor.selection.clipboard"); + this.addUsageVariant(new CopyCommand.CopyRegionCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + BuilderToolsPlugin.BuilderState builderState = BuilderToolsPlugin.getState(playerComponent, playerRef); + boolean entitiesOnly = this.entitiesOnlyFlag.get(context); + boolean noEntities = this.noEntitiesFlag.get(context); + int settings = 0; + if (!entitiesOnly) { + settings |= 8; + } + + if (this.emptyFlag.get(context)) { + settings |= 4; + } + + if (this.keepAnchorsFlag.get(context)) { + settings |= 64; + } + + if (!noEntities || entitiesOnly) { + settings |= 16; + } + + int settingsFinal = settings; + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> { + try { + BlockSelection selection = builderState.getSelection(); + if (selection == null || !selection.hasSelectionBounds()) { + context.sendMessage(MESSAGE_BUILDER_TOOLS_COPY_CUT_NO_SELECTION); + return; + } + + Vector3i min = selection.getSelectionMin(); + Vector3i max = selection.getSelectionMax(); + builderState.copyOrCut(r, min.getX(), min.getY(), min.getZ(), max.getX(), max.getY(), max.getZ(), settingsFinal, componentAccessor); + } catch (PrefabCopyException var9x) { + context.sendMessage(Message.translation("server.builderTools.copycut.copyFailedReason").param("reason", var9x.getMessage())); + } + }); + } + } + + public static void copySelection(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + copySelection(ref, componentAccessor, BuilderToolsPlugin.getState(playerComponent, playerRefComponent), 24); + } + + public static void copySelection( + @Nonnull Ref ref, + @Nonnull ComponentAccessor componentAccessor, + @Nonnull BuilderToolsPlugin.BuilderState builderState, + int settings + ) { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + BuilderToolsPlugin.addToQueue(playerComponent, playerRefComponent, (r, s, c) -> { + try { + BlockSelection selection = builderState.getSelection(); + if (selection == null || !selection.hasSelectionBounds()) { + playerComponent.sendMessage(MESSAGE_BUILDER_TOOLS_COPY_CUT_NO_SELECTION); + return; + } + + Vector3i min = selection.getSelectionMin(); + Vector3i max = selection.getSelectionMax(); + builderState.copyOrCut(r, min.getX(), min.getY(), min.getZ(), max.getX(), max.getY(), max.getZ(), settings, c); + } catch (PrefabCopyException var9) { + playerComponent.sendMessage(Message.translation("server.builderTools.copycut.copyFailedReason").param("reason", var9.getMessage())); + } + }); + } + + private static class CopyRegionCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg xMinArg = this.withRequiredArg("xMin", "server.commands.copy.xMin.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg yMinArg = this.withRequiredArg("yMin", "server.commands.copy.yMin.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg zMinArg = this.withRequiredArg("zMin", "server.commands.copy.zMin.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg xMaxArg = this.withRequiredArg("xMax", "server.commands.copy.xMax.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg yMaxArg = this.withRequiredArg("yMax", "server.commands.copy.yMax.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg zMaxArg = this.withRequiredArg("zMax", "server.commands.copy.zMax.desc", ArgTypes.INTEGER); + @Nonnull + private final FlagArg noEntitiesFlag = this.withFlagArg("noEntities", "server.commands.copy.noEntities.desc"); + @Nonnull + private final FlagArg entitiesOnlyFlag = this.withFlagArg("onlyEntities", "server.commands.copy.entitiesonly.desc"); + @Nonnull + private final FlagArg emptyFlag = this.withFlagArg("empty", "server.commands.copy.empty.desc"); + @Nonnull + private final FlagArg keepAnchorsFlag = this.withFlagArg("keepanchors", "server.commands.copy.keepanchors.desc"); + + public CopyRegionCommand() { + super("server.commands.copy.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + BuilderToolsPlugin.BuilderState builderState = BuilderToolsPlugin.getState(playerComponent, playerRef); + boolean entitiesOnly = this.entitiesOnlyFlag.get(context); + boolean noEntities = this.noEntitiesFlag.get(context); + int settings = 0; + if (!entitiesOnly) { + settings |= 8; + } + + if (this.emptyFlag.get(context)) { + settings |= 4; + } + + if (this.keepAnchorsFlag.get(context)) { + settings |= 64; + } + + if (!noEntities || entitiesOnly) { + settings |= 16; + } + + int xMin = this.xMinArg.get(context); + int yMin = this.yMinArg.get(context); + int zMin = this.zMinArg.get(context); + int xMax = this.xMaxArg.get(context); + int yMax = this.yMaxArg.get(context); + int zMax = this.zMaxArg.get(context); + int copySettings = settings; + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> { + try { + builderState.copyOrCut(r, xMin, yMin, zMin, xMax, yMax, zMax, copySettings, componentAccessor); + } catch (PrefabCopyException var13x) { + context.sendMessage(Message.translation("server.builderTools.copycut.copyFailedReason").param("reason", var13x.getMessage())); + SoundUtil.playSoundEvent2d(r, TempAssetIdUtil.getSoundEventIndex("CREATE_ERROR"), SoundCategory.UI, componentAccessor); + } + }); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/CutCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/CutCommand.java new file mode 100644 index 0000000..ec10f62 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/CutCommand.java @@ -0,0 +1,166 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrefabCopyException; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TempAssetIdUtil; +import javax.annotation.Nonnull; + +public class CutCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_BUILDER_TOOLS_COPY_CUT_NO_SELECTION = Message.translation("server.builderTools.copycut.noSelection"); + @Nonnull + private final FlagArg noEntitiesFlag = this.withFlagArg("noEntities", "server.commands.cut.noEntities.desc"); + @Nonnull + private final FlagArg entitiesOnlyFlag = this.withFlagArg("onlyEntities", "server.commands.cut.entitiesonly.desc"); + @Nonnull + private final FlagArg emptyFlag = this.withFlagArg("empty", "server.commands.cut.empty.desc"); + @Nonnull + private final FlagArg keepAnchorsFlag = this.withFlagArg("keepanchors", "server.commands.cut.keepanchors.desc"); + + public CutCommand() { + super("cut", "server.commands.cut.desc"); + this.setPermissionGroup(GameMode.Creative); + this.requirePermission("hytale.editor.selection.clipboard"); + this.addUsageVariant(new CutCommand.CutRegionCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + BuilderToolsPlugin.BuilderState builderState = BuilderToolsPlugin.getState(playerComponent, playerRef); + boolean entitiesOnly = this.entitiesOnlyFlag.get(context); + boolean noEntities = this.noEntitiesFlag.get(context); + int settings = 2; + if (!entitiesOnly) { + settings |= 8; + } + + if (this.emptyFlag.get(context)) { + settings |= 4; + } + + if (this.keepAnchorsFlag.get(context)) { + settings |= 64; + } + + if (!noEntities || entitiesOnly) { + settings |= 16; + } + + int settingsFinal = settings; + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> { + try { + BlockSelection selection = builderState.getSelection(); + if (selection == null || !selection.hasSelectionBounds()) { + context.sendMessage(MESSAGE_BUILDER_TOOLS_COPY_CUT_NO_SELECTION); + return; + } + + Vector3i min = selection.getSelectionMin(); + Vector3i max = selection.getSelectionMax(); + builderState.copyOrCut(r, min.getX(), min.getY(), min.getZ(), max.getX(), max.getY(), max.getZ(), settingsFinal, componentAccessor); + } catch (PrefabCopyException var9x) { + context.sendMessage(Message.translation("server.builderTools.copycut.copyFailedReason").param("reason", var9x.getMessage())); + } + }); + } + } + + private static class CutRegionCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg xMinArg = this.withRequiredArg("xMin", "server.commands.cut.xMin.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg yMinArg = this.withRequiredArg("yMin", "server.commands.cut.yMin.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg zMinArg = this.withRequiredArg("zMin", "server.commands.cut.zMin.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg xMaxArg = this.withRequiredArg("xMax", "server.commands.cut.xMax.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg yMaxArg = this.withRequiredArg("yMax", "server.commands.cut.yMax.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg zMaxArg = this.withRequiredArg("zMax", "server.commands.cut.zMax.desc", ArgTypes.INTEGER); + @Nonnull + private final FlagArg noEntitiesFlag = this.withFlagArg("noEntities", "server.commands.cut.noEntities.desc"); + @Nonnull + private final FlagArg entitiesOnlyFlag = this.withFlagArg("onlyEntities", "server.commands.cut.entitiesonly.desc"); + @Nonnull + private final FlagArg emptyFlag = this.withFlagArg("empty", "server.commands.cut.empty.desc"); + @Nonnull + private final FlagArg keepAnchorsFlag = this.withFlagArg("keepanchors", "server.commands.cut.keepanchors.desc"); + + public CutRegionCommand() { + super("server.commands.cut.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + BuilderToolsPlugin.BuilderState builderState = BuilderToolsPlugin.getState(playerComponent, playerRef); + boolean entitiesOnly = this.entitiesOnlyFlag.get(context); + boolean noEntities = this.noEntitiesFlag.get(context); + int settings = 2; + if (!entitiesOnly) { + settings |= 8; + } + + if (this.emptyFlag.get(context)) { + settings |= 4; + } + + if (this.keepAnchorsFlag.get(context)) { + settings |= 64; + } + + if (!noEntities || entitiesOnly) { + settings |= 16; + } + + int xMin = this.xMinArg.get(context); + int yMin = this.yMinArg.get(context); + int zMin = this.zMinArg.get(context); + int xMax = this.xMaxArg.get(context); + int yMax = this.yMaxArg.get(context); + int zMax = this.zMaxArg.get(context); + int cutSettings = settings; + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> { + try { + builderState.copyOrCut(r, xMin, yMin, zMin, xMax, yMax, zMax, cutSettings, componentAccessor); + } catch (PrefabCopyException var13x) { + context.sendMessage(Message.translation("server.builderTools.copycut.copyFailedReason").param("reason", var13x.getMessage())); + SoundUtil.playSoundEvent2d(r, TempAssetIdUtil.getSoundEventIndex("CREATE_ERROR"), SoundCategory.UI, componentAccessor); + } + }); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/DeselectCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/DeselectCommand.java new file mode 100644 index 0000000..d426360 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/DeselectCommand.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class DeselectCommand extends AbstractPlayerCommand { + public DeselectCommand() { + super("deselect", "server.commands.deselect.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addAliases("clearselection"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.deselect(componentAccessor)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/EditLineCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/EditLineCommand.java new file mode 100644 index 0000000..31dc985 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/EditLineCommand.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.packets.buildertools.BrushOrigin; +import com.hypixel.hytale.protocol.packets.buildertools.BrushShape; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeVector3i; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EditLineCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg startArg = this.withRequiredArg("start", "server.commands.editline.start.desc", ArgTypes.RELATIVE_VECTOR3I); + @Nonnull + private final RequiredArg endArg = this.withRequiredArg("end", "server.commands.editline.end.desc", ArgTypes.RELATIVE_VECTOR3I); + @Nonnull + private final RequiredArg materialArg = this.withRequiredArg("material", "server.commands.editline.material.desc", ArgTypes.STRING); + @Nonnull + private final DefaultArg widthArg = this.withDefaultArg("width", "server.commands.editline.width.desc", ArgTypes.INTEGER, 1, "1"); + @Nonnull + private final DefaultArg heightArg = this.withDefaultArg("height", "server.commands.editline.height.desc", ArgTypes.INTEGER, 1, "1"); + @Nonnull + private final DefaultArg wallThicknessArg = this.withDefaultArg( + "wallThickness", "server.commands.editline.wallThickness.desc", ArgTypes.INTEGER, 0, "0" + ); + @Nonnull + private final DefaultArg shapeArg = this.withDefaultArg("shape", "server.commands.editline.shape.desc", ArgTypes.STRING, "Cube", "Cube"); + @Nonnull + private final DefaultArg originArg = this.withDefaultArg("origin", "server.commands.editline.origin.desc", ArgTypes.STRING, "Center", "Center"); + @Nonnull + private final DefaultArg spacingArg = this.withDefaultArg("spacing", "server.commands.editline.spacing.desc", ArgTypes.INTEGER, 1, "1"); + @Nonnull + private final DefaultArg densityArg = this.withDefaultArg("density", "server.commands.editline.density.desc", ArgTypes.INTEGER, 100, "100"); + + public EditLineCommand() { + super("editline", "server.commands.editline.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d playerPos = transformComponent.getPosition(); + int baseX = MathUtil.floor(playerPos.getX()); + int baseY = MathUtil.floor(playerPos.getY()); + int baseZ = MathUtil.floor(playerPos.getZ()); + Vector3i start = this.startArg.get(context).resolve(baseX, baseY, baseZ); + Vector3i end = this.endArg.get(context).resolve(baseX, baseY, baseZ); + BlockPattern material = BlockPattern.parse(this.materialArg.get(context)); + int width = this.widthArg.get(context); + int height = this.heightArg.get(context); + int wallThickness = this.wallThicknessArg.get(context); + BrushShape shape = BrushShape.valueOf(this.shapeArg.get(context)); + BrushOrigin origin = BrushOrigin.valueOf(this.originArg.get(context)); + int spacing = this.spacingArg.get(context); + int density = this.densityArg.get(context); + BuilderToolsPlugin.addToQueue( + playerComponent, + playerRef, + (r, s, componentAccessor) -> s.editLine( + start.x, + start.y, + start.z, + end.x, + end.y, + end.z, + material, + width, + height, + wallThickness, + shape, + origin, + spacing, + density, + s.getGlobalMask(), + componentAccessor + ) + ); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/EnvironmentCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/EnvironmentCommand.java new file mode 100644 index 0000000..bfc26ca --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/EnvironmentCommand.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EnvironmentCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg environmentArg = this.withRequiredArg("environment", "server.commands.environment.environment.desc", ArgTypes.STRING); + + public EnvironmentCommand() { + super("environment", "server.commands.environment.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addAliases("setenv", "setenvironment"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + String envName = this.environmentArg.get(context); + Environment environment = Environment.getAssetMap().getAsset(envName); + if (environment == null) { + context.sendMessage(Message.translation("server.builderTools.environment.envNotFound").param("name", envName)); + context.sendMessage( + Message.translation("server.general.failed.didYouMean") + .param( + "choices", StringUtil.sortByFuzzyDistance(envName, Environment.getAssetMap().getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT).toString() + ) + ); + } else { + String key = environment.getId(); + int index = Environment.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } else { + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.environment(r, index, componentAccessor)); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/ExpandCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/ExpandCommand.java new file mode 100644 index 0000000..23be46f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/ExpandCommand.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EnumArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ExpandCommand extends AbstractPlayerCommand { + @Nonnull + private final DefaultArg distanceArg = this.withDefaultArg("distance", "server.commands.expand.distance.desc", ArgTypes.INTEGER, 1, "1"); + @Nonnull + private final OptionalArg axisArg = this.withOptionalArg( + "axis", "server.commands.expand.axis.desc", new EnumArgumentType<>("server.commands.parsing.argtype.axis.name", Axis.class) + ); + + public ExpandCommand() { + super("expand", "server.commands.expand.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + Integer distance = this.distanceArg.get(context); + Vector3i direction; + if (this.axisArg.provided(context)) { + direction = this.axisArg.get(context).getDirection().scale(distance); + } else { + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + direction = headRotationComponent.getAxisDirection().scale(distance); + } + + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.expand(r, direction, componentAccessor)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/ExtendFaceCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/ExtendFaceCommand.java new file mode 100644 index 0000000..f4730a1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/ExtendFaceCommand.java @@ -0,0 +1,169 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ExtendFaceCommand extends AbstractCommandCollection { + public ExtendFaceCommand() { + super("extendface", "server.commands.extendface.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addUsageVariant(new ExtendFaceCommand.ExtendFaceBasicCommand()); + this.addUsageVariant(new ExtendFaceCommand.ExtendFaceWithRegionCommand()); + } + + private static class ExtendFaceBasicCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg xArg = this.withRequiredArg("x", "server.commands.extendface.x.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg yArg = this.withRequiredArg("y", "server.commands.extendface.y.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg zArg = this.withRequiredArg("z", "server.commands.extendface.z.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg normalXArg = this.withRequiredArg("normalX", "server.commands.extendface.normalX.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg normalYArg = this.withRequiredArg("normalY", "server.commands.extendface.normalY.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg normalZArg = this.withRequiredArg("normalZ", "server.commands.extendface.normalZ.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg toolParamArg = this.withRequiredArg("toolParam", "server.commands.extendface.toolParam.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg shapeRangeArg = this.withRequiredArg("shapeRange", "server.commands.extendface.shapeRange.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg blockTypeArg = this.withRequiredArg("blockType", "server.commands.extendface.blockType.desc", ArgTypes.STRING); + + public ExtendFaceBasicCommand() { + super("server.commands.extendface.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + int x = this.xArg.get(context); + int y = this.yArg.get(context); + int z = this.zArg.get(context); + int normalX = this.normalXArg.get(context); + int normalY = this.normalYArg.get(context); + int normalZ = this.normalZArg.get(context); + int toolParam = this.toolParamArg.get(context); + int shapeRange = this.shapeRangeArg.get(context); + String key = this.blockTypeArg.get(context); + if (BlockType.getAssetMap().getAsset(key) == null) { + context.sendMessage(Message.translation("server.builderTools.invalidBlockType").param("name", key).param("key", key)); + } else { + int index = BlockType.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + context.sendMessage(Message.translation("server.builderTools.invalidBlockType").param("name", key).param("key", key)); + } else { + BuilderToolsPlugin.addToQueue( + playerComponent, + playerRef, + (r, s, componentAccessor) -> s.extendFace(x, y, z, normalX, normalY, normalZ, toolParam, shapeRange, index, null, null, componentAccessor) + ); + } + } + } + } + } + + private static class ExtendFaceWithRegionCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg xArg = this.withRequiredArg("x", "server.commands.extendface.x.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg yArg = this.withRequiredArg("y", "server.commands.extendface.y.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg zArg = this.withRequiredArg("z", "server.commands.extendface.z.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg normalXArg = this.withRequiredArg("normalX", "server.commands.extendface.normalX.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg normalYArg = this.withRequiredArg("normalY", "server.commands.extendface.normalY.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg normalZArg = this.withRequiredArg("normalZ", "server.commands.extendface.normalZ.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg toolParamArg = this.withRequiredArg("toolParam", "server.commands.extendface.toolParam.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg shapeRangeArg = this.withRequiredArg("shapeRange", "server.commands.extendface.shapeRange.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg blockTypeArg = this.withRequiredArg("blockType", "server.commands.extendface.blockType.desc", ArgTypes.STRING); + @Nonnull + private final RequiredArg xMinArg = this.withRequiredArg("xMin", "server.commands.extendface.xMin.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg yMinArg = this.withRequiredArg("yMin", "server.commands.extendface.yMin.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg zMinArg = this.withRequiredArg("zMin", "server.commands.extendface.zMin.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg xMaxArg = this.withRequiredArg("xMax", "server.commands.extendface.xMax.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg yMaxArg = this.withRequiredArg("yMax", "server.commands.extendface.yMax.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg zMaxArg = this.withRequiredArg("zMax", "server.commands.extendface.zMax.desc", ArgTypes.INTEGER); + + public ExtendFaceWithRegionCommand() { + super("server.commands.extendface.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + int x = this.xArg.get(context); + int y = this.yArg.get(context); + int z = this.zArg.get(context); + int normalX = this.normalXArg.get(context); + int normalY = this.normalYArg.get(context); + int normalZ = this.normalZArg.get(context); + int toolParam = this.toolParamArg.get(context); + int shapeRange = this.shapeRangeArg.get(context); + String key = this.blockTypeArg.get(context); + if (BlockType.getAssetMap().getAsset(key) == null) { + context.sendMessage(Message.translation("server.builderTools.invalidBlockType").param("name", key).param("key", key)); + } else { + int index = BlockType.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + context.sendMessage(Message.translation("server.builderTools.invalidBlockType").param("name", key).param("key", key)); + } else { + int xMin = this.xMinArg.get(context); + int yMin = this.yMinArg.get(context); + int zMin = this.zMinArg.get(context); + int xMax = this.xMaxArg.get(context); + int yMax = this.yMaxArg.get(context); + int zMax = this.zMaxArg.get(context); + Vector3i min = new Vector3i(xMin, yMin, zMin); + Vector3i max = new Vector3i(xMax, yMax, zMax); + BuilderToolsPlugin.addToQueue( + playerComponent, + playerRef, + (r, s, componentAccessor) -> s.extendFace(x, y, z, normalX, normalY, normalZ, toolParam, shapeRange, index, min, max, componentAccessor) + ); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/FillCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/FillCommand.java new file mode 100644 index 0000000..30e0f04 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/FillCommand.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class FillCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg patternArg = this.withRequiredArg("pattern", "server.commands.fill.args.replacement.desc", ArgTypes.BLOCK_PATTERN); + + public FillCommand() { + super("fillBlocks", "server.commands.fill.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addAliases("fill"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + BlockPattern pattern = this.patternArg.get(context); + if (pattern != null && !pattern.isEmpty()) { + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.fill(pattern, componentAccessor)); + context.sendMessage(Message.translation("server.commands.fill.success").param("key", pattern.toString())); + } else { + context.sendMessage(Message.translation("server.builderTools.invalidBlockType").param("name", "").param("key", "")); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/FlipCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/FlipCommand.java new file mode 100644 index 0000000..4edca81 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/FlipCommand.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeDirection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FlipCommand extends AbstractPlayerCommand { + public FlipCommand() { + super("flip", "server.commands.flip.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addUsageVariant(new FlipCommand.FlipWithDirectionCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + executeFlip(store, ref, playerRef, null); + } + + private static void executeFlip( + @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nullable RelativeDirection direction + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + if (headRotationComponent != null) { + Axis axis; + if (direction != null) { + axis = RelativeDirection.toAxis(direction, headRotationComponent); + } else { + axis = headRotationComponent.getAxis(); + } + + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.flip(r, axis, componentAccessor)); + } + } + } + + private static class FlipWithDirectionCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg directionArg = this.withRequiredArg( + "direction", "server.commands.flip.direction.desc", RelativeDirection.ARGUMENT_TYPE + ); + + public FlipWithDirectionCommand() { + super("server.commands.flip.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + FlipCommand.executeFlip(store, ref, playerRef, this.directionArg.get(context)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/GlobalMaskCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/GlobalMaskCommand.java new file mode 100644 index 0000000..fcd8b80 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/GlobalMaskCommand.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockMask; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class GlobalMaskCommand extends AbstractPlayerCommand { + public GlobalMaskCommand() { + super("gmask", "server.commands.globalmask.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addUsageVariant(new GlobalMaskCommand.GlobalMaskSetCommand()); + this.addSubCommand(new GlobalMaskCommand.GlobalMaskClearCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> { + BlockMask currentMask = s.getGlobalMask(); + if (currentMask == null) { + context.sendMessage(Message.translation("server.builderTools.globalmask.current.none")); + } else { + context.sendMessage(Message.translation("server.builderTools.globalmask.current").param("mask", currentMask.informativeToString())); + } + }); + } + + private static class GlobalMaskClearCommand extends AbstractPlayerCommand { + public GlobalMaskClearCommand() { + super("clear", "server.commands.globalmask.clear.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addAliases("disable", "c"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.setGlobalMask(null, componentAccessor)); + } + } + + private static class GlobalMaskSetCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg maskArg = this.withRequiredArg("mask", "server.commands.globalmask.mask.desc", ArgTypes.BLOCK_MASK); + + public GlobalMaskSetCommand() { + super("server.commands.globalmask.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + BlockMask mask = this.maskArg.get(context); + + try { + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.setGlobalMask(mask, componentAccessor)); + } catch (IllegalArgumentException var9) { + context.sendMessage(Message.translation("server.builderTools.globalmask.setFailed").param("reason", var9.getMessage())); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/HollowCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/HollowCommand.java new file mode 100644 index 0000000..9d1d770 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/HollowCommand.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class HollowCommand extends AbstractPlayerCommand { + @Nonnull + private final DefaultArg blockTypeArg = this.withDefaultArg( + "blockType", "server.commands.hollow.blockType.desc", ArgTypes.BLOCK_TYPE_KEY, "Empty", "Air" + ); + @Nonnull + private final DefaultArg thicknessArg = this.withDefaultArg( + "thickness", "server.commands.hollow.thickness.desc", ArgTypes.INTEGER, 1, "Thickness of 1" + ) + .addValidator(Validators.range(1, 128)); + @Nonnull + private final FlagArg floorArg = this.withFlagArg("floor", "server.commands.hollow.floor.desc").addAliases("bottom"); + @Nonnull + private final FlagArg roofArg = this.withFlagArg("roof", "server.commands.hollow.roof.desc").addAliases("ceiling", "top"); + @Nonnull + private final FlagArg perimeterArg = this.withFlagArg("perimeter", "server.commands.hollow.perimeter.desc").addAliases("all"); + + public HollowCommand() { + super("hollow", "server.commands.hollow.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + int blockTypeIndex = BlockType.getAssetMap().getIndex(this.blockTypeArg.get(context)); + Boolean floor = this.floorArg.get(context); + Boolean roof = this.roofArg.get(context); + Boolean perimeter = this.perimeterArg.get(context); + BuilderToolsPlugin.addToQueue( + playerComponent, + playerRef, + (r, s, componentAccessor) -> s.hollow(r, blockTypeIndex, this.thicknessArg.get(context), roof || perimeter, floor || perimeter, componentAccessor) + ); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/HotbarSwitchCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/HotbarSwitchCommand.java new file mode 100644 index 0000000..7390609 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/HotbarSwitchCommand.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.HotbarManager; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class HotbarSwitchCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg hotbarSlotArg = this.withRequiredArg("hotbarSlot", "server.commands.hotbar.hotbarSlot.desc", ArgTypes.INTEGER) + .addValidator(Validators.range(0, 9)); + @Nonnull + private final FlagArg saveInsteadOfLoadArg = this.withFlagArg("save", "server.commands.hotbar.save.desc"); + + public HotbarSwitchCommand() { + super("hotbar", "server.commands.hotbar.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + HotbarManager hotbarManager = playerComponent.getHotbarManager(); + if (this.saveInsteadOfLoadArg.get(context)) { + hotbarManager.saveHotbar(ref, this.hotbarSlotArg.get(context).shortValue(), store); + } else { + hotbarManager.loadHotbar(ref, this.hotbarSlotArg.get(context).shortValue(), store); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/MoveCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/MoveCommand.java new file mode 100644 index 0000000..4e40d0f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/MoveCommand.java @@ -0,0 +1,113 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeDirection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MoveCommand extends AbstractPlayerCommand { + @Nonnull + private final FlagArg emptyFlag = this.withFlagArg("empty", "server.commands.move.empty.desc"); + @Nonnull + private final FlagArg entitiesFlag = this.withFlagArg("entities", "server.commands.move.entities.desc"); + + public MoveCommand() { + super("move", "server.commands.move.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addUsageVariant(new MoveCommand.MoveWithDistanceCommand()); + this.addUsageVariant(new MoveCommand.MoveWithDirectionAndDistanceCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + executeMove(store, ref, playerRef, null, 1, this.emptyFlag.get(context), this.entitiesFlag.get(context)); + } + + private static void executeMove( + @Nonnull Store store, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nullable RelativeDirection direction, + int distance, + boolean empty, + boolean entities + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3i directionVector = RelativeDirection.toDirectionVector(direction, headRotationComponent).scale(distance); + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.move(r, directionVector, empty, entities, componentAccessor)); + } + } + + private static class MoveWithDirectionAndDistanceCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg directionArg = this.withRequiredArg( + "direction", "server.commands.move.direction.desc", RelativeDirection.ARGUMENT_TYPE + ); + @Nonnull + private final RequiredArg distanceArg = this.withRequiredArg("distance", "server.commands.move.distance.desc", ArgTypes.INTEGER); + @Nonnull + private final FlagArg emptyFlag = this.withFlagArg("empty", "server.commands.move.empty.desc"); + @Nonnull + private final FlagArg entitiesFlag = this.withFlagArg("entities", "server.commands.move.entities.desc"); + + public MoveWithDirectionAndDistanceCommand() { + super("server.commands.move.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + MoveCommand.executeMove( + store, ref, playerRef, this.directionArg.get(context), this.distanceArg.get(context), this.emptyFlag.get(context), this.entitiesFlag.get(context) + ); + } + } + + private static class MoveWithDistanceCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg distanceArg = this.withRequiredArg("distance", "server.commands.move.distance.desc", ArgTypes.INTEGER); + @Nonnull + private final FlagArg emptyFlag = this.withFlagArg("empty", "server.commands.move.empty.desc"); + @Nonnull + private final FlagArg entitiesFlag = this.withFlagArg("entities", "server.commands.move.entities.desc"); + + public MoveWithDistanceCommand() { + super("server.commands.move.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + MoveCommand.executeMove(store, ref, playerRef, null, this.distanceArg.get(context), this.emptyFlag.get(context), this.entitiesFlag.get(context)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/PasteCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/PasteCommand.java new file mode 100644 index 0000000..db2e3c4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/PasteCommand.java @@ -0,0 +1,82 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeIntPosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PasteCommand extends AbstractPlayerCommand { + public PasteCommand() { + super("paste", "server.commands.paste.desc"); + this.setPermissionGroup(GameMode.Creative); + this.requirePermission("hytale.editor.selection.clipboard"); + this.addUsageVariant(new PasteCommand.PasteAtPositionCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + ChunkStore chunkStore = world.getChunkStore(); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + int x = MathUtil.floor(position.x); + int y = MathUtil.floor(position.y); + int z = MathUtil.floor(position.z); + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.paste(r, x, y, z, componentAccessor)); + } + + private static class PasteAtPositionCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg positionArg = this.withRequiredArg( + "position", "server.commands.paste.position.desc", ArgTypes.RELATIVE_BLOCK_POSITION + ); + + public PasteAtPositionCommand() { + super("server.commands.paste.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + ChunkStore chunkStore = world.getChunkStore(); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + RelativeIntPosition relativePos = this.positionArg.get(context); + Vector3i blockPos = relativePos.getBlockPosition(position, chunkStore); + BuilderToolsPlugin.addToQueue( + playerComponent, playerRef, (r, s, componentAccessor) -> s.paste(r, blockPos.x, blockPos.y, blockPos.z, componentAccessor) + ); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/Pos1Command.java b/src/com/hypixel/hytale/builtin/buildertools/commands/Pos1Command.java new file mode 100644 index 0000000..366a863 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/Pos1Command.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class Pos1Command extends AbstractPlayerCommand { + @Nonnull + private final OptionalArg xArg = this.withOptionalArg("x", "server.commands.pos1.x.desc", ArgTypes.INTEGER); + @Nonnull + private final OptionalArg yArg = this.withOptionalArg("y", "server.commands.pos1.y.desc", ArgTypes.INTEGER); + @Nonnull + private final OptionalArg zArg = this.withOptionalArg("z", "server.commands.pos1.z.desc", ArgTypes.INTEGER); + + public Pos1Command() { + super("pos1", "server.commands.pos1.desc"); + this.setPermissionGroup(GameMode.Creative); + this.requirePermission("hytale.editor.selection.use"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + Vector3i intTriple; + if (this.xArg.provided(context) && this.yArg.provided(context) && this.zArg.provided(context)) { + intTriple = new Vector3i(this.xArg.get(context), this.yArg.get(context), this.zArg.get(context)); + } else { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + if (transformComponent == null) { + return; + } + + Vector3d position = transformComponent.getPosition(); + intTriple = new Vector3i(MathUtil.floor(position.getX()), MathUtil.floor(position.getY()), MathUtil.floor(position.getZ())); + } + + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.pos1(intTriple, componentAccessor)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/Pos2Command.java b/src/com/hypixel/hytale/builtin/buildertools/commands/Pos2Command.java new file mode 100644 index 0000000..fcc801a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/Pos2Command.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class Pos2Command extends AbstractPlayerCommand { + @Nonnull + private final OptionalArg xArg = this.withOptionalArg("x", "server.commands.pos2.x.desc", ArgTypes.INTEGER); + @Nonnull + private final OptionalArg yArg = this.withOptionalArg("y", "server.commands.pos2.y.desc", ArgTypes.INTEGER); + @Nonnull + private final OptionalArg zArg = this.withOptionalArg("z", "server.commands.pos2.z.desc", ArgTypes.INTEGER); + + public Pos2Command() { + super("pos2", "server.commands.pos2.desc"); + this.setPermissionGroup(GameMode.Creative); + this.requirePermission("hytale.editor.selection.use"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + Vector3i intTriple; + if (this.xArg.provided(context) && this.yArg.provided(context) && this.zArg.provided(context)) { + intTriple = new Vector3i(this.xArg.get(context), this.yArg.get(context), this.zArg.get(context)); + } else { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + intTriple = new Vector3i(MathUtil.floor(position.getX()), MathUtil.floor(position.getY()), MathUtil.floor(position.getZ())); + } + + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.pos2(intTriple, componentAccessor)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/PrefabCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/PrefabCommand.java new file mode 100644 index 0000000..1a252da --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/PrefabCommand.java @@ -0,0 +1,307 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.prefablist.PrefabPage; +import com.hypixel.hytale.builtin.buildertools.prefablist.PrefabSavePage; +import com.hypixel.hytale.builtin.buildertools.utils.RecursivePrefabLoader; +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import com.hypixel.hytale.server.core.prefab.PrefabStore; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.List; +import java.util.Random; +import java.util.function.BiFunction; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public class PrefabCommand extends AbstractCommandCollection { + public PrefabCommand() { + super("prefab", "server.commands.prefab.desc"); + this.addAliases("p"); + this.setPermissionGroup(GameMode.Creative); + this.addSubCommand(new PrefabCommand.PrefabSaveCommand()); + this.addSubCommand(new PrefabCommand.PrefabLoadCommand()); + this.addSubCommand(new PrefabCommand.PrefabDeleteCommand()); + this.addSubCommand(new PrefabCommand.PrefabListCommand()); + } + + private static class PrefabDeleteCommand extends CommandBase { + @Nonnull + private final RequiredArg nameArg = this.withRequiredArg("name", "server.commands.prefab.delete.name.desc", ArgTypes.STRING); + + public PrefabDeleteCommand() { + super("delete", "server.commands.prefab.delete.desc", true); + this.requirePermission("hytale.editor.prefab.manage"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + String name = this.nameArg.get(context); + if (!name.endsWith(".prefab.json")) { + name = name + ".prefab.json"; + } + + PrefabStore module = PrefabStore.get(); + Path serverPrefabsPath = module.getServerPrefabsPath(); + Path resolve = serverPrefabsPath.resolve(name); + + try { + Ref ref = context.senderAsPlayerRef(); + boolean isOwner = false; + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + if (playerRefComponent != null) { + isOwner = SingleplayerModule.isOwner(playerRefComponent); + } + } + + if (!PathUtil.isChildOf(serverPrefabsPath, resolve) && !isOwner) { + context.sendMessage(Message.translation("server.builderTools.attemptedToSaveOutsidePrefabsDir")); + return; + } + + Path relativize = PathUtil.relativize(serverPrefabsPath, resolve); + if (Files.isRegularFile(resolve)) { + Files.delete(resolve); + context.sendMessage(Message.translation("server.builderTools.prefab.deleted").param("name", relativize.toString())); + } else { + context.sendMessage(Message.translation("server.builderTools.prefab.prefabNotFound").param("name", relativize.toString())); + } + } catch (IOException var10) { + context.sendMessage(Message.translation("server.builderTools.prefab.errorOccured").param("reason", var10.getMessage())); + } + } + } + + private static class PrefabListCommand extends CommandBase { + @Nonnull + private final DefaultArg storeTypeArg = this.withDefaultArg( + "storeType", "server.commands.prefab.list.storeType.desc", ArgTypes.STRING, "asset", "server.commands.prefab.list.storeType.desc" + ); + @Nonnull + private final FlagArg textFlag = this.withFlagArg("text", "server.commands.prefab.list.text.desc"); + + public PrefabListCommand() { + super("list", "server.commands.prefab.list.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + String storeType = this.storeTypeArg.get(context); + + final Path prefabStorePath = switch (storeType) { + case "server" -> PrefabStore.get().getServerPrefabsPath(); + case "asset" -> { + List assetPaths = PrefabStore.get().getAllAssetPrefabPaths(); + yield assetPaths.isEmpty() ? PrefabStore.get().getAssetPrefabsPath() : assetPaths.getFirst().prefabsPath(); + } + case "worldgen" -> PrefabStore.get().getWorldGenPrefabsPath(); + default -> throw new IllegalStateException("Unexpected value: " + storeType); + }; + Ref ref = context.senderAsPlayerRef(); + if (ref != null && ref.isValid() && !this.textFlag.get(context)) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + BuilderToolsPlugin.BuilderState builderState = BuilderToolsPlugin.getState(playerComponent, playerRefComponent); + playerComponent.getPageManager().openCustomPage(ref, store, new PrefabPage(playerRefComponent, prefabStorePath, builderState)); + }); + } else { + try { + final List prefabFiles = new ObjectArrayList<>(); + if ("asset".equals(storeType)) { + for (PrefabStore.AssetPackPrefabPath packPath : PrefabStore.get().getAllAssetPrefabPaths()) { + final Path path = packPath.prefabsPath(); + final String packPrefix = packPath.isBasePack() ? "" : "[" + packPath.getPackName() + "] "; + if (Files.isDirectory(path)) { + Files.walkFileTree(path, FileUtil.DEFAULT_WALK_TREE_OPTIONS_SET, Integer.MAX_VALUE, new SimpleFileVisitor() { + @Nonnull + public FileVisitResult visitFile(@Nonnull Path file, @Nonnull BasicFileAttributes attrs) { + String fileName = file.getFileName().toString(); + if (fileName.endsWith(".prefab.json")) { + prefabFiles.add(Message.raw(packPrefix + PathUtil.relativize(path, file).toString())); + } + + return FileVisitResult.CONTINUE; + } + }); + } + } + } else if (Files.isDirectory(prefabStorePath)) { + Files.walkFileTree(prefabStorePath, FileUtil.DEFAULT_WALK_TREE_OPTIONS_SET, Integer.MAX_VALUE, new SimpleFileVisitor() { + @Nonnull + public FileVisitResult visitFile(@Nonnull Path file, @Nonnull BasicFileAttributes attrs) { + String fileName = file.getFileName().toString(); + if (fileName.endsWith(".prefab.json")) { + prefabFiles.add(Message.raw(PathUtil.relativize(prefabStorePath, file).toString())); + } + + return FileVisitResult.CONTINUE; + } + }); + } + + context.sendMessage(MessageFormat.list(Message.translation("server.commands.prefab.list.header"), prefabFiles)); + } catch (IOException var10) { + context.sendMessage(Message.translation("server.builderTools.prefab.errorListingPrefabs").param("reason", var10.getMessage())); + } + } + } + } + + private static class PrefabLoadByNameCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg nameArg = this.withRequiredArg("name", "server.commands.prefab.load.name.desc", ArgTypes.STRING); + @Nonnull + private final DefaultArg storeTypeArg = this.withDefaultArg( + "storeType", "server.commands.prefab.load.storeType.desc", ArgTypes.STRING, "asset", "server.commands.prefab.load.storeType.desc" + ); + @Nonnull + private final DefaultArg storeNameArg = this.withDefaultArg("storeName", "server.commands.prefab.load.storeName.desc", ArgTypes.STRING, null, ""); + @Nonnull + private final FlagArg childrenFlag = this.withFlagArg("children", "server.commands.prefab.load.children.desc"); + + public PrefabLoadByNameCommand() { + super("server.commands.prefab.load.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + String storeType = this.storeTypeArg.get(context); + String storeName = this.storeNameArg.get(context); + String name = this.nameArg.get(context); + if (!name.endsWith(".prefab.json")) { + name = name + ".prefab.json"; + } + + Path prefabStorePath = null; + Path resolvedPrefabPath = null; + String finalName = name; + + Function prefabGetter = switch (storeType) { + case "server" -> { + prefabStorePath = PrefabStore.get().getServerPrefabsPath(); + yield PrefabStore.get()::getServerPrefab; + } + case "asset" -> { + Path foundPath = PrefabStore.get().findAssetPrefabPath(finalName); + if (foundPath != null) { + resolvedPrefabPath = foundPath; + prefabStorePath = foundPath.getParent(); + yield key -> PrefabStore.get().getPrefab(foundPath); + } else { + prefabStorePath = PrefabStore.get().getAssetPrefabsPath(); + yield PrefabStore.get()::getAssetPrefab; + } + } + case "worldgen" -> { + Path storePath = PrefabStore.get().getWorldGenPrefabsPath(storeName); + prefabStorePath = PrefabStore.get().getWorldGenPrefabsPath(storeName); + yield key -> PrefabStore.get().getWorldGenPrefab(storePath, key); + } + default -> { + context.sendMessage(Message.translation("server.commands.prefab.invalidStoreType").param("storeType", storeType)); + yield null; + } + }; + if (prefabGetter != null) { + BiFunction loader; + if (this.childrenFlag.get(context)) { + loader = new RecursivePrefabLoader.BlockSelectionLoader(prefabStorePath, prefabGetter); + } else { + loader = (prefabFile, rand) -> prefabGetter.apply(prefabFile); + } + + boolean prefabExists = resolvedPrefabPath != null && Files.isRegularFile(resolvedPrefabPath) || Files.isRegularFile(prefabStorePath.resolve(name)); + if (prefabExists) { + BuilderToolsPlugin.addToQueue( + playerComponent, playerRef, (r, s, componentAccessor) -> s.load(finalName, loader.apply(finalName, s.getRandom()), componentAccessor) + ); + } else { + context.sendMessage(Message.translation("server.builderTools.prefab.prefabNotFound").param("name", name)); + } + } + } + } + + private static class PrefabLoadCommand extends AbstractPlayerCommand { + public PrefabLoadCommand() { + super("load", "server.commands.prefab.load.desc"); + this.requirePermission("hytale.editor.prefab.use"); + this.addUsageVariant(new PrefabCommand.PrefabLoadByNameCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + List assetPaths = PrefabStore.get().getAllAssetPrefabPaths(); + Path defaultRoot = assetPaths.isEmpty() ? PrefabStore.get().getServerPrefabsPath() : assetPaths.getFirst().prefabsPath(); + BuilderToolsPlugin.BuilderState builderState = BuilderToolsPlugin.getState(playerComponent, playerRef); + playerComponent.getPageManager().openCustomPage(ref, store, new PrefabPage(playerRef, defaultRoot, builderState)); + } + } + + private static class PrefabSaveCommand extends AbstractPlayerCommand { + public PrefabSaveCommand() { + super("save", "server.commands.prefab.save.desc"); + this.requirePermission("hytale.editor.prefab.manage"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + BuilderToolsPlugin.BuilderState builderState = BuilderToolsPlugin.getState(playerComponent, playerRef); + playerComponent.getPageManager().openCustomPage(ref, store, new PrefabSavePage(playerRef)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/RedoCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/RedoCommand.java new file mode 100644 index 0000000..d3cff36 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/RedoCommand.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class RedoCommand extends AbstractPlayerCommand { + public RedoCommand() { + super("redo", "server.commands.redo.desc"); + this.setPermissionGroup(GameMode.Creative); + this.requirePermission("hytale.editor.history"); + this.addAliases("r"); + this.addUsageVariant(new RedoCommand.RedoWithCountCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + executeRedo(store, ref, 1); + } + + private static void executeRedo(@Nonnull Store store, @Nonnull Ref ref, int count) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + BuilderToolsPlugin.addToQueue(playerComponent, playerRefComponent, (r, s, componentAccessor) -> s.redo(r, count, componentAccessor)); + } + + private static class RedoWithCountCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg countArg = this.withRequiredArg("count", "server.commands.redo.count.desc", ArgTypes.INTEGER); + + public RedoWithCountCommand() { + super("server.commands.redo.desc"); + this.setPermissionGroup(GameMode.Creative); + this.requirePermission("hytale.editor.history"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + RedoCommand.executeRedo(store, ref, this.countArg.get(context)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/RepairFillersCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/RepairFillersCommand.java new file mode 100644 index 0000000..cb6db08 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/RepairFillersCommand.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class RepairFillersCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_BUILDER_TOOLS_REPAIR_FILLERS = Message.translation("server.builderTools.repairFillers"); + + public RepairFillersCommand() { + super("repairfillers", "server.commands.repairfillers.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, c) -> s.repairFillers(r, c)); + context.sendMessage(MESSAGE_BUILDER_TOOLS_REPAIR_FILLERS); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/ReplaceCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/ReplaceCommand.java new file mode 100644 index 0000000..3e09600 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/ReplaceCommand.java @@ -0,0 +1,197 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.builtin.buildertools.utils.Material; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ReplaceCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg toArg = this.withRequiredArg("to", "server.commands.replace.toBlock.desc", ArgTypes.BLOCK_PATTERN); + @Nonnull + private final FlagArg substringSwapFlag = this.withFlagArg("substringSwap", "server.commands.replace.substringSwap.desc"); + @Nonnull + private final FlagArg regexFlag = this.withFlagArg("regex", "server.commands.replace.regex.desc"); + + public ReplaceCommand() { + super("replace", "server.commands.replace.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addUsageVariant(new ReplaceCommand.ReplaceFromToCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + executeReplace(context, store, ref, playerRef, null, this.toArg.get(context), this.substringSwapFlag.get(context), this.regexFlag.get(context)); + } + + private static void executeReplace( + @Nonnull CommandContext context, + @Nonnull Store store, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nullable String fromValue, + @Nonnull BlockPattern toPattern, + boolean substringSwap, + boolean regex + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + if (toPattern != null && !toPattern.isEmpty()) { + String toValue = toPattern.toString(); + Integer[] toBlockIds = toPattern.getResolvedKeys(); + Material fromMaterial = fromValue != null ? Material.fromKey(fromValue) : null; + if (fromMaterial != null && fromMaterial.isFluid()) { + Material toMaterial = Material.fromKey(toValue); + if (toMaterial == null) { + context.sendMessage(Message.translation("server.builderTools.invalidBlockType").param("name", toValue).param("key", toValue)); + } else { + BuilderToolsPlugin.addToQueue( + playerComponent, playerRef, (r, s, componentAccessor) -> s.replace(r, fromMaterial, toMaterial, componentAccessor) + ); + context.sendMessage(Message.translation("server.builderTools.replace.replacementBlockDone").param("from", fromValue).param("to", toValue)); + } + } else { + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + if (fromValue == null && !substringSwap && !regex) { + int[] toIds = toIntArray(toBlockIds); + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.replace(r, null, toIds, componentAccessor)); + context.sendMessage(Message.translation("server.builderTools.replace.replacementAllDone").param("to", toValue)); + } else if (fromValue == null) { + context.sendMessage(Message.translation("server.commands.replace.fromRequired")); + } else if (fromMaterial == null) { + context.sendMessage(Message.translation("server.builderTools.invalidBlockType").param("name", fromValue).param("key", fromValue)); + } else if (!substringSwap) { + if (regex) { + Pattern pattern; + try { + pattern = Pattern.compile(fromValue); + } catch (PatternSyntaxException var24) { + context.sendMessage(Message.translation("server.commands.replace.invalidRegex").param("error", var24.getMessage())); + return; + } + + int[] toIds = toIntArray(toBlockIds); + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> { + s.replace(r, value -> { + String valueKey = assetMap.getAsset(value).getId(); + return pattern.matcher(valueKey).matches(); + }, toIds, componentAccessor); + context.sendMessage(Message.translation("server.commands.replace.success").param("regex", fromValue).param("to", toValue)); + }); + } else { + int[] toIds = toIntArray(toBlockIds); + int fromBlockId = fromMaterial.getBlockId(); + BuilderToolsPlugin.addToQueue( + playerComponent, playerRef, (r, s, componentAccessor) -> s.replace(r, block -> block == fromBlockId, toIds, componentAccessor) + ); + context.sendMessage(Message.translation("server.builderTools.replace.replacementBlockDone").param("from", fromValue).param("to", toValue)); + } + } else { + String[] blockKeys = fromValue.split(","); + Int2IntArrayMap swapMap = new Int2IntArrayMap(); + + for (int blockId = 0; blockId < assetMap.getAssetCount(); blockId++) { + BlockType blockType = assetMap.getAsset(blockId); + String blockKeyStr = blockType.getId(); + + for (String from : blockKeys) { + if (blockKeyStr.contains(from.trim())) { + String replacedKey; + try { + replacedKey = blockKeyStr.replace(from.trim(), toValue); + } catch (Exception var25) { + continue; + } + + int index = assetMap.getIndex(replacedKey); + if (index != Integer.MIN_VALUE) { + swapMap.put(blockId, index); + break; + } + } + } + } + + if (!swapMap.isEmpty()) { + BuilderToolsPlugin.addToQueue( + playerComponent, playerRef, (r, s, componentAccessor) -> s.replace(r, value -> swapMap.getOrDefault(value, value), componentAccessor) + ); + context.sendMessage(Message.translation("server.builderTools.replace.replacementDone").param("nb", swapMap.size()).param("to", toValue)); + } else { + context.sendMessage(Message.translation("server.commands.replace.noMatchingBlocks").param("blockType", fromValue)); + } + } + } + } else { + context.sendMessage(Message.translation("server.builderTools.invalidBlockType").param("name", "").param("key", "")); + } + } + } + + private static int[] toIntArray(Integer[] arr) { + int[] result = new int[arr.length]; + + for (int i = 0; i < arr.length; i++) { + result[i] = arr[i]; + } + + return result; + } + + private static class ReplaceFromToCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg fromArg = this.withRequiredArg("from", "server.commands.replace.from.desc", ArgTypes.STRING); + @Nonnull + private final RequiredArg toArg = this.withRequiredArg("to", "server.commands.replace.toBlock.desc", ArgTypes.BLOCK_PATTERN); + @Nonnull + private final FlagArg substringSwapFlag = this.withFlagArg("substringSwap", "server.commands.replace.substringSwap.desc"); + @Nonnull + private final FlagArg regexFlag = this.withFlagArg("regex", "server.commands.replace.regex.desc"); + + public ReplaceFromToCommand() { + super("server.commands.replace.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + ReplaceCommand.executeReplace( + context, + store, + ref, + playerRef, + this.fromArg.get(context), + this.toArg.get(context), + this.substringSwapFlag.get(context), + this.regexFlag.get(context) + ); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/RotateCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/RotateCommand.java new file mode 100644 index 0000000..ccef0e6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/RotateCommand.java @@ -0,0 +1,98 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EnumArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class RotateCommand extends AbstractCommandCollection { + public RotateCommand() { + super("rotate", "server.commands.rotate.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addUsageVariant(new RotateCommand.RotateArbitraryVariant()); + this.addUsageVariant(new RotateCommand.RotateAxisVariant()); + } + + private static class RotateArbitraryVariant extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg yawArg = this.withRequiredArg("yaw", "server.commands.rotate.yaw.desc", ArgTypes.FLOAT); + @Nonnull + private final RequiredArg pitchArg = this.withRequiredArg("pitch", "server.commands.rotate.pitch.desc", ArgTypes.FLOAT); + @Nonnull + private final RequiredArg rollArg = this.withRequiredArg("roll", "server.commands.rotate.roll.desc", ArgTypes.FLOAT); + + RotateArbitraryVariant() { + super("server.commands.rotate.arbitrary.variant.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + float yaw = this.yawArg.get(context); + float pitch = this.pitchArg.get(context); + float roll = this.rollArg.get(context); + boolean isSimple90Degree = yaw % 90.0F == 0.0F && pitch % 90.0F == 0.0F && roll % 90.0F == 0.0F; + if (isSimple90Degree && pitch == 0.0F && roll == 0.0F) { + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.rotate(r, Axis.Y, (int)yaw, componentAccessor)); + } else { + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.rotateArbitrary(r, yaw, pitch, roll, componentAccessor)); + } + } + } + } + + private static class RotateAxisVariant extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_BUILDER_TOOLS_ROTATE_ANGLE_USAGE = Message.translation("server.builderTools.rotate.angleUsage"); + @Nonnull + private final RequiredArg angleArg = this.withRequiredArg("angle", "server.commands.rotate.angle.desc", ArgTypes.INTEGER); + @Nonnull + private final DefaultArg axisArg = this.withDefaultArg( + "axis", "server.commands.rotate.axis.desc", new EnumArgumentType<>("server.commands.parsing.argtype.axis.name", Axis.class), Axis.Y, "Y" + ); + + RotateAxisVariant() { + super("server.commands.rotate.axis.variant.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + int angle = this.angleArg.get(context); + Axis axis = this.axisArg.get(context); + if (angle % 90 != 0) { + context.sendMessage(MESSAGE_BUILDER_TOOLS_ROTATE_ANGLE_USAGE); + } else { + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.rotate(r, axis, angle, componentAccessor)); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/SelectChunkCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/SelectChunkCommand.java new file mode 100644 index 0000000..9879076 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/SelectChunkCommand.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SelectChunkCommand extends AbstractPlayerCommand { + public SelectChunkCommand() { + super("selectchunk", "server.commands.selectchunk.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + int chunkX = MathUtil.floor(position.getX()) >> 5; + int chunkZ = MathUtil.floor(position.getZ()) >> 5; + Vector3i min = new Vector3i(chunkX << 5, 0, chunkZ << 5); + Vector3i max = new Vector3i((chunkX + 1 << 5) - 1, 319, (chunkZ + 1 << 5) - 1); + BuilderToolsPlugin.addToQueue( + playerComponent, playerRef, (r, s, componentAccessor) -> s.select(min, max, "server.builderTools.selectReasons.chunk", componentAccessor) + ); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/SelectChunkSectionCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/SelectChunkSectionCommand.java new file mode 100644 index 0000000..8ef33b1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/SelectChunkSectionCommand.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SelectChunkSectionCommand extends AbstractPlayerCommand { + public SelectChunkSectionCommand() { + super("selectchunksection", "server.commands.selectchunksection.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + int chunkX = MathUtil.floor(position.getX()) >> 5; + int chunkY = MathUtil.floor(position.getY()) >> 5; + int chunkZ = MathUtil.floor(position.getZ()) >> 5; + Vector3i min = new Vector3i(chunkX << 5, chunkY << 5, chunkZ << 5); + Vector3i max = new Vector3i((chunkX + 1 << 5) - 1, (chunkY + 1 << 5) - 1, (chunkZ + 1 << 5) - 1); + BuilderToolsPlugin.addToQueue( + playerComponent, playerRef, (r, s, componentAccessor) -> s.select(min, max, "server.builderTools.selectReasons.chunkSection", componentAccessor) + ); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/SelectionHistoryCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/SelectionHistoryCommand.java new file mode 100644 index 0000000..a41c4b0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/SelectionHistoryCommand.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.BuilderToolsUserData; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SelectionHistoryCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg enabledArg = this.withRequiredArg("enabled", "server.commands.selectionHistory.enabled.desc", ArgTypes.BOOLEAN); + + public SelectionHistoryCommand() { + super("selectionHistory", "server.commands.selectionHistory.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + boolean enabled = this.enabledArg.get(context); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + BuilderToolsPlugin.BuilderState builderState = BuilderToolsPlugin.getState(playerComponent, playerRef); + BuilderToolsUserData userData = builderState.getUserData(); + userData.setRecordSelectionHistory(enabled); + context.sendMessage(Message.translation("server.commands.selectionHistory.set").param("enabled", enabled)); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/SetCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/SetCommand.java new file mode 100644 index 0000000..6962a44 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/SetCommand.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SetCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg patternArg = this.withRequiredArg("pattern", "server.commands.set.args.blocktype.desc", ArgTypes.BLOCK_PATTERN); + + public SetCommand() { + super("setBlocks", "server.commands.set.desc"); + this.setPermissionGroup(GameMode.Creative); + this.requirePermission("hytale.editor.selection.modify"); + this.addAliases("set"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + BlockPattern pattern = this.patternArg.get(context); + if (pattern != null && !pattern.isEmpty()) { + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.set(pattern, componentAccessor)); + context.sendMessage(Message.translation("server.builderTools.set.selectionSet").param("key", pattern.toString())); + } else { + context.sendMessage(Message.translation("server.builderTools.invalidBlockType").param("name", "").param("key", "")); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/SetToolHistorySizeCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/SetToolHistorySizeCommand.java new file mode 100644 index 0000000..27e607c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/SetToolHistorySizeCommand.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.codec.validation.validator.RangeValidator; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import javax.annotation.Nonnull; + +public class SetToolHistorySizeCommand extends CommandBase { + @Nonnull + private final RequiredArg historyLengthArg = this.withRequiredArg( + "historyLength", "server.commands.settoolhistorysize.historyLength.desc", ArgTypes.INTEGER + ) + .addValidator(new RangeValidator<>(10, 250, true)); + + public SetToolHistorySizeCommand() { + super("setToolHistorySize", "server.commands.settoolhistorysize.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + BuilderToolsPlugin.get().setToolHistorySize(this.historyLengthArg.get(context)); + context.sendMessage(Message.translation("server.commands.settoolhistorysize.set").param("size", this.historyLengthArg.get(context))); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/ShiftCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/ShiftCommand.java new file mode 100644 index 0000000..8c99346 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/ShiftCommand.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EnumArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ShiftCommand extends AbstractPlayerCommand { + @Nonnull + private final DefaultArg distanceArg = this.withDefaultArg("distance", "server.commands.shift.distance.desc", ArgTypes.INTEGER, 1, "1"); + @Nonnull + private final OptionalArg axisArg = this.withOptionalArg( + "axis", "server.commands.shift.axis.desc", new EnumArgumentType<>("server.commands.parsing.argtype.axis.name", Axis.class) + ); + + public ShiftCommand() { + super("shift", "server.commands.shift.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + Integer distance = this.distanceArg.get(context); + Vector3i direction; + if (this.axisArg.provided(context)) { + direction = this.axisArg.get(context).getDirection().scale(distance); + } else { + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + direction = headRotationComponent.getAxisDirection().scale(distance); + } + + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.shift(r, direction, componentAccessor)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/StackCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/StackCommand.java new file mode 100644 index 0000000..527458f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/StackCommand.java @@ -0,0 +1,121 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeDirection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StackCommand extends AbstractPlayerCommand { + @Nonnull + private final FlagArg emptyFlag = this.withFlagArg("empty", "server.commands.stack.empty.desc"); + @Nonnull + private final OptionalArg spacingArg = this.withOptionalArg("spacing", "server.commands.stack.spacing.desc", ArgTypes.INTEGER); + + public StackCommand() { + super("stack", "server.commands.stack.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addUsageVariant(new StackCommand.StackWithCountCommand()); + this.addUsageVariant(new StackCommand.StackWithDirectionAndCountCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + executeStack(store, ref, null, 1, this.emptyFlag.get(context), this.spacingArg.provided(context) ? this.spacingArg.get(context) : 0); + } + + private static void executeStack( + @Nonnull Store store, @Nonnull Ref ref, @Nullable RelativeDirection direction, int count, boolean empty, int spacing + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3i directionVector = RelativeDirection.toDirectionVector(direction, headRotationComponent); + BuilderToolsPlugin.addToQueue( + playerComponent, playerRefComponent, (r, s, componentAccessor) -> s.stack(r, directionVector, count, empty, spacing, componentAccessor) + ); + } + } + + private static class StackWithCountCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg countArg = this.withRequiredArg("count", "server.commands.stack.count.desc", ArgTypes.INTEGER); + @Nonnull + private final FlagArg emptyFlag = this.withFlagArg("empty", "server.commands.stack.empty.desc"); + @Nonnull + private final OptionalArg spacingArg = this.withOptionalArg("spacing", "server.commands.stack.spacing.desc", ArgTypes.INTEGER); + + public StackWithCountCommand() { + super("server.commands.stack.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + StackCommand.executeStack( + store, ref, null, this.countArg.get(context), this.emptyFlag.get(context), this.spacingArg.provided(context) ? this.spacingArg.get(context) : 0 + ); + } + } + + private static class StackWithDirectionAndCountCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg directionArg = this.withRequiredArg( + "direction", "server.commands.stack.direction.desc", RelativeDirection.ARGUMENT_TYPE + ); + @Nonnull + private final RequiredArg countArg = this.withRequiredArg("count", "server.commands.stack.count.desc", ArgTypes.INTEGER); + @Nonnull + private final FlagArg emptyFlag = this.withFlagArg("empty", "server.commands.stack.empty.desc"); + @Nonnull + private final OptionalArg spacingArg = this.withOptionalArg("spacing", "server.commands.stack.spacing.desc", ArgTypes.INTEGER); + + public StackWithDirectionAndCountCommand() { + super("server.commands.stack.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + StackCommand.executeStack( + store, + ref, + this.directionArg.get(context), + this.countArg.get(context), + this.emptyFlag.get(context), + this.spacingArg.provided(context) ? this.spacingArg.get(context) : 0 + ); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/SubmergeCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/SubmergeCommand.java new file mode 100644 index 0000000..17777f2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/SubmergeCommand.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.builtin.buildertools.utils.FluidPatternHelper; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SubmergeCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg fluidItemArg = this.withRequiredArg("fluid-item", "server.commands.submerge.fluidType.desc", ArgTypes.BLOCK_TYPE_KEY); + + public SubmergeCommand() { + super("submerge", "server.commands.submerge.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addAliases("flood"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + String fluidItemKey = this.fluidItemArg.get(context); + if (!FluidPatternHelper.isFluidItem(fluidItemKey)) { + context.sendMessage(Message.translation("server.builderTools.invalidBlockType").param("name", fluidItemKey).param("key", fluidItemKey)); + } else { + BlockPattern pattern = BlockPattern.parse(fluidItemKey); + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.set(pattern, componentAccessor)); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/TintCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/TintCommand.java new file mode 100644 index 0000000..c2c97a5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/TintCommand.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class TintCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg colorArg = this.withRequiredArg("color", "server.commands.tint.color.desc", ArgTypes.STRING); + + public TintCommand() { + super("tint", "server.commands.tint.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + String color = this.colorArg.get(context); + + int colorI; + try { + if (color.charAt(0) == '#') { + colorI = Integer.parseInt(color.substring(1), 16); + } else { + colorI = Integer.parseInt(color); + } + } catch (NumberFormatException var10) { + context.sendMessage(Message.translation("server.builderTools.tint.colorFormatError").param("color", color)); + return; + } + + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.tint(r, colorI, componentAccessor)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/UndoCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/UndoCommand.java new file mode 100644 index 0000000..04ded5c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/UndoCommand.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class UndoCommand extends AbstractPlayerCommand { + public UndoCommand() { + super("undo", "server.commands.undo.desc"); + this.setPermissionGroup(GameMode.Creative); + this.requirePermission("hytale.editor.history"); + this.addAliases("u"); + this.addUsageVariant(new UndoCommand.UndoWithCountCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + executeUndo(store, ref, 1); + } + + private static void executeUndo(@Nonnull Store store, @Nonnull Ref ref, int count) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + BuilderToolsPlugin.addToQueue(playerComponent, playerRefComponent, (r, s, c) -> s.undo(r, count, c)); + } + + private static class UndoWithCountCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg countArg = this.withRequiredArg("count", "server.commands.undo.count.desc", ArgTypes.INTEGER); + + public UndoWithCountCommand() { + super("server.commands.undo.desc"); + this.setPermissionGroup(GameMode.Creative); + this.requirePermission("hytale.editor.history"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + UndoCommand.executeUndo(store, ref, this.countArg.get(context)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/UpdateSelectionCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/UpdateSelectionCommand.java new file mode 100644 index 0000000..3f9bfd3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/UpdateSelectionCommand.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class UpdateSelectionCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg xMinArg = this.withRequiredArg("xMin", "server.commands.updateselection.xMin.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg yMinArg = this.withRequiredArg("yMin", "server.commands.updateselection.yMin.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg zMinArg = this.withRequiredArg("zMin", "server.commands.updateselection.zMin.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg xMaxArg = this.withRequiredArg("xMax", "server.commands.updateselection.xMax.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg yMaxArg = this.withRequiredArg("yMax", "server.commands.updateselection.yMax.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg zMaxArg = this.withRequiredArg("zMax", "server.commands.updateselection.zMax.desc", ArgTypes.INTEGER); + + public UpdateSelectionCommand() { + super("updateselection", "server.commands.updateselection.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + int xMin = this.xMinArg.get(context); + int yMin = this.yMinArg.get(context); + int zMin = this.zMinArg.get(context); + int xMax = this.xMaxArg.get(context); + int yMax = this.yMaxArg.get(context); + int zMax = this.zMaxArg.get(context); + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> s.update(xMin, yMin, zMin, xMax, yMax, zMax)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/commands/WallsCommand.java b/src/com/hypixel/hytale/builtin/buildertools/commands/WallsCommand.java new file mode 100644 index 0000000..a4d65db --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/commands/WallsCommand.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.builtin.buildertools.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WallsCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg patternArg = this.withRequiredArg("pattern", "server.commands.walls.blockType.desc", ArgTypes.BLOCK_PATTERN); + @Nonnull + private final DefaultArg thicknessArg = this.withDefaultArg( + "thickness", "server.commands.walls.thickness.desc", ArgTypes.INTEGER, 1, "Thickness of one" + ) + .addValidator(Validators.range(1, 128)); + @Nonnull + private final FlagArg floorArg = this.withFlagArg("floor", "server.commands.walls.floor.desc").addAliases("bottom"); + @Nonnull + private final FlagArg roofArg = this.withFlagArg("roof", "server.commands.walls.roof.desc").addAliases("ceiling", "top"); + @Nonnull + private final FlagArg perimeterArg = this.withFlagArg("perimeter", "server.commands.walls.perimeter.desc").addAliases("all"); + + public WallsCommand() { + super("wall", "server.commands.walls.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addAliases("walls", "side", "sides"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (PrototypePlayerBuilderToolSettings.isOkayToDoCommandsOnSelection(ref, playerComponent, store)) { + BlockPattern pattern = this.patternArg.get(context); + if (pattern != null && !pattern.isEmpty()) { + Boolean floor = this.floorArg.get(context); + Boolean roof = this.roofArg.get(context); + Boolean perimeter = this.perimeterArg.get(context); + BuilderToolsPlugin.addToQueue( + playerComponent, + playerRef, + (r, s, componentAccessor) -> s.walls(r, pattern, this.thicknessArg.get(context), roof || perimeter, floor || perimeter, componentAccessor) + ); + } else { + context.sendMessage(Message.translation("server.builderTools.invalidBlockType").param("name", "").param("key", "")); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/imageimport/ImageImportCommand.java b/src/com/hypixel/hytale/builtin/buildertools/imageimport/ImageImportCommand.java new file mode 100644 index 0000000..ffade47 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/imageimport/ImageImportCommand.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.builtin.buildertools.imageimport; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ImageImportCommand extends AbstractPlayerCommand { + public ImageImportCommand() { + super("importimage", "server.commands.importimage.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + playerComponent.getPageManager().openCustomPage(ref, store, new ImageImportPage(playerRef)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/imageimport/ImageImportPage.java b/src/com/hypixel/hytale/builtin/buildertools/imageimport/ImageImportPage.java new file mode 100644 index 0000000..c7f190a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/imageimport/ImageImportPage.java @@ -0,0 +1,589 @@ +package com.hypixel.hytale.builtin.buildertools.imageimport; + +import com.hypixel.hytale.builtin.buildertools.BlockColorIndex; +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.protocol.packets.inventory.SetActiveSlot; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.ui.DropdownEntryInfo; +import com.hypixel.hytale.server.core.ui.LocalizableString; +import com.hypixel.hytale.server.core.ui.browser.FileBrowserConfig; +import com.hypixel.hytale.server.core.ui.browser.ServerFileBrowser; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.imageio.ImageIO; + +public class ImageImportPage extends InteractiveCustomUIPage { + private static final int DEFAULT_MAX_SIZE = 128; + private static final int MIN_SIZE = 1; + private static final int MAX_SIZE = 512; + private static final String PASTE_TOOL_ID = "EditorTool_Paste"; + private static final Path IMPORTS_DIR = Paths.get("imports", "images"); + @Nonnull + private String imagePath = ""; + private int maxDimension = 128; + @Nonnull + private String orientationStr = "wall_xy"; + @Nonnull + private ImageImportPage.Orientation orientation = ImageImportPage.Orientation.VERTICAL_XY; + @Nonnull + private String originStr = "bottom_center"; + @Nonnull + private ImageImportPage.Origin origin = ImageImportPage.Origin.BOTTOM_CENTER; + @Nullable + private String statusMessage = null; + private boolean isError = false; + private boolean isProcessing = false; + private boolean showBrowser = false; + @Nonnull + private final ServerFileBrowser browser; + + public ImageImportPage(@Nonnull PlayerRef playerRef) { + super(playerRef, CustomPageLifetime.CanDismiss, ImageImportPage.PageData.CODEC); + FileBrowserConfig config = FileBrowserConfig.builder() + .listElementId("#BrowserPage #FileList") + .searchInputId("#BrowserPage #SearchInput") + .currentPathId("#BrowserPage #CurrentPath") + .roots(List.of(new FileBrowserConfig.RootEntry("Imports", IMPORTS_DIR))) + .allowedExtensions(".png", ".jpg", ".jpeg", ".gif", ".bmp") + .enableRootSelector(false) + .enableSearch(true) + .enableDirectoryNav(true) + .maxResults(50) + .build(); + + try { + Files.createDirectories(IMPORTS_DIR); + } catch (IOException var4) { + } + + this.browser = new ServerFileBrowser(config); + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/ImageImportPage.ui"); + commandBuilder.set("#ImagePath #Input.Value", this.imagePath); + commandBuilder.set("#MaxSizeInput #Input.Value", this.maxDimension); + List orientationEntries = new ArrayList<>(); + orientationEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.imageImport.orientation.wall_xy"), "wall_xy")); + orientationEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.imageImport.orientation.wall_xz"), "wall_xz")); + orientationEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.imageImport.orientation.floor"), "floor")); + commandBuilder.set("#OrientationInput #Input.Entries", orientationEntries); + commandBuilder.set("#OrientationInput #Input.Value", this.orientationStr); + List originEntries = new ArrayList<>(); + originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.bottom_front_left"), "bottom_front_left")); + originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.bottom_center"), "bottom_center")); + originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.center"), "center")); + originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.top_center"), "top_center")); + commandBuilder.set("#OriginInput #Input.Entries", originEntries); + commandBuilder.set("#OriginInput #Input.Value", this.originStr); + this.updateStatus(commandBuilder); + eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#ImagePath #Input", EventData.of("@ImagePath", "#ImagePath #Input.Value"), false); + eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#MaxSizeInput #Input", EventData.of("@MaxSize", "#MaxSizeInput #Input.Value"), false); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, "#OrientationInput #Input", EventData.of("@Orientation", "#OrientationInput #Input.Value"), false + ); + eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#OriginInput #Input", EventData.of("@Origin", "#OriginInput #Input.Value"), false); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ImportButton", EventData.of("Import", "true")); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ImagePath #BrowseButton", EventData.of("Browse", "true")); + commandBuilder.set("#FormContainer.Visible", !this.showBrowser); + commandBuilder.set("#BrowserPage.Visible", this.showBrowser); + if (this.showBrowser) { + this.buildBrowserPage(commandBuilder, eventBuilder); + } + } + + private void buildBrowserPage(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) { + this.browser.buildSearchInput(commandBuilder, eventBuilder); + this.browser.buildCurrentPath(commandBuilder); + this.browser.buildFileList(commandBuilder, eventBuilder); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#BrowserPage #SelectButton", EventData.of("BrowserSelect", "true")); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#BrowserPage #CancelButton", EventData.of("BrowserCancel", "true")); + } + + private void updateStatus(@Nonnull UICommandBuilder commandBuilder) { + if (this.statusMessage != null) { + commandBuilder.set("#StatusText.Text", this.statusMessage); + commandBuilder.set("#StatusText.Visible", true); + commandBuilder.set("#StatusText.Style.TextColor", this.isError ? "#e74c3c" : "#cfd8e3"); + } else { + commandBuilder.set("#StatusText.Visible", false); + } + } + + private void setError(@Nonnull String message) { + this.statusMessage = message; + this.isError = true; + this.isProcessing = false; + this.rebuild(); + } + + private void setStatus(@Nonnull String message) { + this.statusMessage = message; + this.isError = false; + this.rebuild(); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull ImageImportPage.PageData data) { + boolean needsUpdate = false; + if (data.browse != null && data.browse) { + this.showBrowser = true; + this.rebuild(); + } else if (data.browserCancel != null && data.browserCancel) { + this.showBrowser = false; + this.rebuild(); + } else if (data.browserSelect != null && data.browserSelect) { + if (!this.browser.getSelectedItems().isEmpty()) { + String selectedPath = this.browser.getSelectedItems().iterator().next(); + this.imagePath = this.browser.getRoot().resolve(selectedPath).toString(); + } + + this.showBrowser = false; + this.rebuild(); + } else { + if (this.showBrowser && (data.file != null || data.searchQuery != null || data.searchResult != null)) { + boolean handled = false; + if (data.searchQuery != null) { + this.browser.setSearchQuery(data.searchQuery.trim().toLowerCase()); + handled = true; + } + + if (data.file != null) { + String fileName = data.file; + if ("..".equals(fileName)) { + this.browser.navigateUp(); + handled = true; + } else { + Path targetPath = this.browser.resolveFromCurrent(fileName); + if (targetPath != null && Files.isDirectory(targetPath)) { + this.browser.navigateTo(Paths.get(fileName)); + handled = true; + } else if (targetPath != null && Files.isRegularFile(targetPath)) { + this.imagePath = targetPath.toString(); + this.showBrowser = false; + this.rebuild(); + return; + } + } + } + + if (data.searchResult != null) { + Path resolvedPath = this.browser.resolveSecure(data.searchResult); + if (resolvedPath != null && Files.isRegularFile(resolvedPath)) { + this.imagePath = resolvedPath.toString(); + this.showBrowser = false; + this.rebuild(); + return; + } + } + + if (handled) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.browser.buildFileList(commandBuilder, eventBuilder); + this.browser.buildCurrentPath(commandBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + return; + } + } + + if (data.imagePath != null) { + this.imagePath = StringUtil.stripQuotes(data.imagePath.trim()); + this.statusMessage = null; + needsUpdate = true; + } + + if (data.maxSize != null) { + this.maxDimension = Math.max(1, Math.min(512, data.maxSize)); + needsUpdate = true; + } + + if (data.orientation != null) { + this.orientationStr = data.orientation.trim().toLowerCase(); + String var8 = this.orientationStr; + + this.orientation = switch (var8) { + case "wall_xz", "xz", "vertical_xz" -> ImageImportPage.Orientation.VERTICAL_XZ; + case "floor", "horizontal", "horizontal_xz" -> ImageImportPage.Orientation.HORIZONTAL_XZ; + default -> ImageImportPage.Orientation.VERTICAL_XY; + }; + needsUpdate = true; + } + + if (data.origin != null) { + this.originStr = data.origin.trim().toLowerCase(); + String var9 = this.originStr; + + this.origin = switch (var9) { + case "bottom_front_left" -> ImageImportPage.Origin.BOTTOM_FRONT_LEFT; + case "center" -> ImageImportPage.Origin.CENTER; + case "top_center" -> ImageImportPage.Origin.TOP_CENTER; + default -> ImageImportPage.Origin.BOTTOM_CENTER; + }; + needsUpdate = true; + } + + if (data.doImport != null && data.doImport && !this.isProcessing) { + this.performImport(ref, store); + } else { + if (needsUpdate) { + this.sendUpdate(); + } + } + } + } + + private void performImport(@Nonnull Ref ref, @Nonnull Store store) { + if (this.imagePath.isEmpty()) { + this.setError("Please enter a path to an image file"); + } else { + Path path = Paths.get(this.imagePath); + if (!SingleplayerModule.isOwner(this.playerRef)) { + Path normalizedPath = path.toAbsolutePath().normalize(); + Path normalizedImports = IMPORTS_DIR.toAbsolutePath().normalize(); + if (!normalizedPath.startsWith(normalizedImports)) { + this.setError("Files must be in the server's imports/images directory"); + return; + } + } + + if (!Files.exists(path)) { + this.setError("File not found: " + this.imagePath); + } else { + this.isProcessing = true; + this.setStatus("Processing..."); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + if (playerComponent != null && playerRefComponent != null) { + String finalPath = this.imagePath; + int finalMaxSize = this.maxDimension; + ImageImportPage.Orientation finalOrientation = this.orientation; + ImageImportPage.Origin finalOrigin = this.origin; + BuilderToolsPlugin.addToQueue( + playerComponent, + playerRefComponent, + (r, builderState, componentAccessor) -> { + try { + BufferedImage image = null; + + try { + image = ImageIO.read(Paths.get(finalPath).toFile()); + } catch (Exception var38) { + } + + if (image == null) { + this.setError("Unable to read image file (unsupported format or corrupted). Try PNG format."); + return; + } + + int width = image.getWidth(); + int height = image.getHeight(); + float scale = 1.0F; + if (width > finalMaxSize || height > finalMaxSize) { + scale = (float)finalMaxSize / Math.max(width, height); + width = Math.round(width * scale); + height = Math.round(height * scale); + } + + BlockColorIndex colorIndex = BuilderToolsPlugin.get().getBlockColorIndex(); + if (colorIndex.isEmpty()) { + this.setError("Block color index not initialized"); + return; + } + + int sizeX; + int sizeY; + int sizeZ; + switch (finalOrientation) { + case VERTICAL_XY: + sizeX = width; + sizeY = height; + sizeZ = 1; + break; + case VERTICAL_XZ: + sizeX = width; + sizeY = 1; + sizeZ = height; + break; + case HORIZONTAL_XZ: + sizeX = width; + sizeY = 1; + sizeZ = height; + break; + default: + sizeX = width; + sizeY = height; + sizeZ = 1; + } + + int offsetX = 0; + int offsetY = 0; + int offsetZ = 0; + switch (finalOrigin) { + case BOTTOM_FRONT_LEFT: + default: + break; + case BOTTOM_CENTER: + offsetX = -sizeX / 2; + offsetZ = -sizeZ / 2; + break; + case CENTER: + offsetX = -sizeX / 2; + offsetY = -sizeY / 2; + offsetZ = -sizeZ / 2; + break; + case TOP_CENTER: + offsetX = -sizeX / 2; + offsetY = -sizeY; + offsetZ = -sizeZ / 2; + } + + BlockSelection selection = new BlockSelection(width * height, 0); + selection.setPosition(0, 0, 0); + int blockCount = 0; + float finalScale = scale; + + for (int imgY = 0; imgY < height; imgY++) { + for (int imgX = 0; imgX < width; imgX++) { + int srcX = Math.min((int)(imgX / finalScale), image.getWidth() - 1); + int srcY = Math.min((int)(imgY / finalScale), image.getHeight() - 1); + int rgba = image.getRGB(srcX, srcY); + int alpha = rgba >> 24 & 0xFF; + if (alpha >= 128) { + int red = rgba >> 16 & 0xFF; + int green = rgba >> 8 & 0xFF; + int blue = rgba & 0xFF; + int blockId = colorIndex.findClosestBlock(red, green, blue); + if (blockId > 0) { + int blockX; + int blockY; + int blockZ; + switch (finalOrientation) { + case VERTICAL_XY: + blockX = imgX; + blockY = height - 1 - imgY; + blockZ = 0; + break; + case VERTICAL_XZ: + blockX = imgX; + blockY = 0; + blockZ = height - 1 - imgY; + break; + case HORIZONTAL_XZ: + blockX = imgX; + blockY = 0; + blockZ = imgY; + break; + default: + blockX = imgX; + blockY = height - 1 - imgY; + blockZ = 0; + } + + selection.addBlockAtLocalPos(blockX + offsetX, blockY + offsetY, blockZ + offsetZ, blockId, 0, 0, 0); + blockCount++; + } + } + } + } + + selection.setSelectionArea( + new Vector3i(offsetX, offsetY, offsetZ), new Vector3i(sizeX - 1 + offsetX, sizeY - 1 + offsetY, sizeZ - 1 + offsetZ) + ); + builderState.setSelection(selection); + builderState.sendSelectionToClient(); + this.statusMessage = String.format("Success! %d blocks copied to clipboard (%dx%dx%d)", blockCount, sizeX, sizeY, sizeZ); + this.isProcessing = false; + playerRefComponent.sendMessage( + Message.translation("server.builderTools.imageImport.success") + .param("count", blockCount) + .param("width", sizeX) + .param("height", sizeY) + .param("depth", sizeZ) + ); + playerComponent.getPageManager().setPage(r, store, Page.None); + this.switchToPasteTool(playerComponent, playerRefComponent); + } catch (Exception var39) { + BuilderToolsPlugin.get().getLogger().at(Level.WARNING).withCause(var39).log("Image import error"); + this.setError("Error: " + var39.getMessage()); + } + } + ); + } else { + this.setError("Player not found"); + } + } + } + } + + private void switchToPasteTool(@Nonnull Player playerComponent, @Nonnull PlayerRef playerRef) { + Inventory inventory = playerComponent.getInventory(); + ItemContainer hotbar = inventory.getHotbar(); + ItemContainer storage = inventory.getStorage(); + ItemContainer tools = inventory.getTools(); + int hotbarSize = hotbar.getCapacity(); + + for (short slot = 0; slot < hotbarSize; slot++) { + ItemStack itemStack = hotbar.getItemStack(slot); + if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) { + inventory.setActiveHotbarSlot((byte)slot); + playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)slot)); + return; + } + } + + short emptySlot = -1; + + for (short slotx = 0; slotx < hotbarSize; slotx++) { + ItemStack itemStack = hotbar.getItemStack(slotx); + if (itemStack == null || itemStack.isEmpty()) { + emptySlot = slotx; + break; + } + } + + if (emptySlot != -1) { + for (short slotxx = 0; slotxx < storage.getCapacity(); slotxx++) { + ItemStack itemStack = storage.getItemStack(slotxx); + if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) { + storage.moveItemStackFromSlotToSlot(slotxx, 1, hotbar, emptySlot); + inventory.setActiveHotbarSlot((byte)emptySlot); + playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)emptySlot)); + return; + } + } + + ItemStack pasteToolStack = null; + + for (short slotxxx = 0; slotxxx < tools.getCapacity(); slotxxx++) { + ItemStack itemStack = tools.getItemStack(slotxxx); + if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) { + pasteToolStack = itemStack; + break; + } + } + + if (pasteToolStack != null) { + hotbar.setItemStackForSlot(emptySlot, new ItemStack(pasteToolStack.getItemId())); + inventory.setActiveHotbarSlot((byte)emptySlot); + playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)emptySlot)); + } + } + } + + public static enum Orientation { + VERTICAL_XY, + VERTICAL_XZ, + HORIZONTAL_XZ; + + private Orientation() { + } + } + + public static enum Origin { + BOTTOM_FRONT_LEFT, + BOTTOM_CENTER, + CENTER, + TOP_CENTER; + + private Origin() { + } + } + + public static class PageData { + static final String KEY_IMAGE_PATH = "@ImagePath"; + static final String KEY_MAX_SIZE = "@MaxSize"; + static final String KEY_ORIENTATION = "@Orientation"; + static final String KEY_ORIGIN = "@Origin"; + static final String KEY_IMPORT = "Import"; + static final String KEY_BROWSE = "Browse"; + static final String KEY_BROWSER_SELECT = "BrowserSelect"; + static final String KEY_BROWSER_CANCEL = "BrowserCancel"; + public static final BuilderCodec CODEC = BuilderCodec.builder(ImageImportPage.PageData.class, ImageImportPage.PageData::new) + .addField(new KeyedCodec<>("@ImagePath", Codec.STRING), (entry, s) -> entry.imagePath = s, entry -> entry.imagePath) + .addField(new KeyedCodec<>("@MaxSize", Codec.INTEGER), (entry, i) -> entry.maxSize = i, entry -> entry.maxSize) + .addField(new KeyedCodec<>("@Orientation", Codec.STRING), (entry, s) -> entry.orientation = s, entry -> entry.orientation) + .addField(new KeyedCodec<>("@Origin", Codec.STRING), (entry, s) -> entry.origin = s, entry -> entry.origin) + .addField( + new KeyedCodec<>("Import", Codec.STRING), + (entry, s) -> entry.doImport = "true".equalsIgnoreCase(s), + entry -> entry.doImport != null && entry.doImport ? "true" : null + ) + .addField( + new KeyedCodec<>("Browse", Codec.STRING), + (entry, s) -> entry.browse = "true".equalsIgnoreCase(s), + entry -> entry.browse != null && entry.browse ? "true" : null + ) + .addField( + new KeyedCodec<>("BrowserSelect", Codec.STRING), + (entry, s) -> entry.browserSelect = "true".equalsIgnoreCase(s), + entry -> entry.browserSelect != null && entry.browserSelect ? "true" : null + ) + .addField( + new KeyedCodec<>("BrowserCancel", Codec.STRING), + (entry, s) -> entry.browserCancel = "true".equalsIgnoreCase(s), + entry -> entry.browserCancel != null && entry.browserCancel ? "true" : null + ) + .addField(new KeyedCodec<>("File", Codec.STRING), (entry, s) -> entry.file = s, entry -> entry.file) + .addField(new KeyedCodec<>("@SearchQuery", Codec.STRING), (entry, s) -> entry.searchQuery = s, entry -> entry.searchQuery) + .addField(new KeyedCodec<>("SearchResult", Codec.STRING), (entry, s) -> entry.searchResult = s, entry -> entry.searchResult) + .build(); + @Nullable + private String imagePath; + @Nullable + private Integer maxSize; + @Nullable + private String orientation; + @Nullable + private String origin; + @Nullable + private Boolean doImport; + @Nullable + private Boolean browse; + @Nullable + private Boolean browserSelect; + @Nullable + private Boolean browserCancel; + @Nullable + private String file; + @Nullable + private String searchQuery; + @Nullable + private String searchResult; + + public PageData() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/interactions/PickupItemInteraction.java b/src/com/hypixel/hytale/builtin/buildertools/interactions/PickupItemInteraction.java new file mode 100644 index 0000000..f0bb779 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/interactions/PickupItemInteraction.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.builtin.buildertools.interactions; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PickupItemInteraction extends SimpleInstantInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + PickupItemInteraction.class, PickupItemInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Picks up an item entity and adds it to the player's inventory.") + .build(); + public static final String DEFAULT_ID = "*PickupItem"; + public static final RootInteraction DEFAULT_ROOT = new RootInteraction("*PickupItem", "*PickupItem"); + + public PickupItemInteraction(String id) { + super(id); + } + + protected PickupItemInteraction() { + } + + @Override + protected final void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + Ref ref = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.getState().state = InteractionState.Failed; + } else { + Ref targetRef = context.getTargetEntity(); + if (targetRef != null && targetRef.isValid()) { + ItemComponent itemComponent = commandBuffer.getComponent(targetRef, ItemComponent.getComponentType()); + if (itemComponent == null) { + context.getState().state = InteractionState.Failed; + } else { + TransformComponent transformComponent = commandBuffer.getComponent(targetRef, TransformComponent.getComponentType()); + if (transformComponent == null) { + context.getState().state = InteractionState.Failed; + } else { + ItemStack itemStack = itemComponent.getItemStack(); + Item item = itemStack.getItem(); + Vector3d itemEntityPosition = transformComponent.getPosition(); + PlayerSettings playerSettings = commandBuffer.getComponent(ref, PlayerSettings.getComponentType()); + if (playerSettings == null) { + playerSettings = PlayerSettings.defaults(); + } + + ItemContainer itemContainer = playerComponent.getInventory().getContainerForItemPickup(item, playerSettings); + ItemStackTransaction transaction = itemContainer.addItemStack(itemStack); + ItemStack remainder = transaction.getRemainder(); + if (ItemStack.isEmpty(remainder)) { + itemComponent.setRemovedByPlayerPickup(true); + commandBuffer.removeEntity(targetRef, RemoveReason.REMOVE); + playerComponent.notifyPickupItem(ref, itemStack, itemEntityPosition, commandBuffer); + Holder pickupItemHolder = ItemComponent.generatePickedUpItem(targetRef, commandBuffer, ref, itemEntityPosition); + commandBuffer.addEntity(pickupItemHolder, AddReason.SPAWN); + } else if (!remainder.equals(itemStack)) { + int quantity = itemStack.getQuantity() - remainder.getQuantity(); + itemComponent.setItemStack(remainder); + Holder pickupItemHolder = ItemComponent.generatePickedUpItem(targetRef, commandBuffer, ref, itemEntityPosition); + commandBuffer.addEntity(pickupItemHolder, AddReason.SPAWN); + if (quantity > 0) { + playerComponent.notifyPickupItem(ref, itemStack.withQuantity(quantity), itemEntityPosition, commandBuffer); + } + } + } + } + } else { + context.getState().state = InteractionState.Failed; + } + } + } + + @Nonnull + @Override + public String toString() { + return "PickupItemInteraction{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/objimport/MeshVoxelizer.java b/src/com/hypixel/hytale/builtin/buildertools/objimport/MeshVoxelizer.java new file mode 100644 index 0000000..2491ad4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/objimport/MeshVoxelizer.java @@ -0,0 +1,640 @@ +package com.hypixel.hytale.builtin.buildertools.objimport; + +import com.hypixel.hytale.builtin.buildertools.BlockColorIndex; +import java.awt.image.BufferedImage; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class MeshVoxelizer { + private MeshVoxelizer() { + } + + @Nonnull + public static MeshVoxelizer.VoxelResult voxelize(@Nonnull ObjParser.ObjMesh mesh, int targetHeight, boolean fillSolid) { + return voxelize(mesh, targetHeight, fillSolid, null, null, null, 0, false); + } + + @Nonnull + public static MeshVoxelizer.VoxelResult voxelize( + @Nonnull ObjParser.ObjMesh mesh, int targetHeight, boolean fillSolid, @Nullable Map materialToBlockId + ) { + return voxelize(mesh, targetHeight, fillSolid, null, materialToBlockId, null, 0, false); + } + + @Nonnull + public static MeshVoxelizer.VoxelResult voxelize( + @Nonnull ObjParser.ObjMesh mesh, int targetHeight, boolean fillSolid, @Nullable Map materialToBlockId, int defaultBlockId + ) { + return voxelize(mesh, targetHeight, fillSolid, null, materialToBlockId, null, defaultBlockId, false); + } + + @Nonnull + public static MeshVoxelizer.VoxelResult voxelize( + @Nonnull ObjParser.ObjMesh mesh, + int targetHeight, + boolean fillSolid, + @Nullable Map materialTextures, + @Nullable Map materialToBlockId, + @Nullable BlockColorIndex colorIndex, + int defaultBlockId + ) { + return voxelize(mesh, targetHeight, fillSolid, materialTextures, materialToBlockId, colorIndex, defaultBlockId, false); + } + + @Nonnull + public static MeshVoxelizer.VoxelResult voxelize( + @Nonnull ObjParser.ObjMesh mesh, + int targetHeight, + boolean fillSolid, + @Nullable Map materialTextures, + @Nullable Map materialToBlockId, + @Nullable BlockColorIndex colorIndex, + int defaultBlockId, + boolean preserveOrigin + ) { + float[] bounds = mesh.getBounds(); + float meshHeight = bounds[4] - bounds[1]; + float meshWidth = bounds[3] - bounds[0]; + float meshDepth = bounds[5] - bounds[2]; + if (meshHeight <= 0.0F) { + return new MeshVoxelizer.VoxelResult(new boolean[1][1][1], null, 1, 1, 1); + } else { + float scale = targetHeight / meshHeight; + float[][] scaledVertices = new float[mesh.vertices().size()][3]; + int sizeX; + int sizeY; + int sizeZ; + if (preserveOrigin) { + float scaledMinX = bounds[0] * scale; + float scaledMaxX = bounds[3] * scale; + float scaledMinY = bounds[1] * scale; + float scaledMaxY = bounds[4] * scale; + float scaledMinZ = bounds[2] * scale; + float scaledMaxZ = bounds[5] * scale; + float offsetX = scaledMinX < 0.0F ? -scaledMinX + 1.0F : 1.0F; + float offsetY = scaledMinY < 0.0F ? -scaledMinY + 1.0F : 1.0F; + float offsetZ = scaledMinZ < 0.0F ? -scaledMinZ + 1.0F : 1.0F; + + for (int i = 0; i < mesh.vertices().size(); i++) { + float[] v = mesh.vertices().get(i); + scaledVertices[i][0] = v[0] * scale + offsetX; + scaledVertices[i][1] = v[1] * scale + offsetY; + scaledVertices[i][2] = v[2] * scale + offsetZ; + } + + sizeX = Math.max(1, (int)Math.ceil(scaledMaxX + offsetX)) + 2; + sizeY = Math.max(1, (int)Math.ceil(scaledMaxY + offsetY)) + 2; + sizeZ = Math.max(1, (int)Math.ceil(scaledMaxZ + offsetZ)) + 2; + } else { + sizeX = Math.max(1, (int)Math.ceil(meshWidth * scale)) + 2; + sizeY = Math.max(1, targetHeight) + 2; + sizeZ = Math.max(1, (int)Math.ceil(meshDepth * scale)) + 2; + + for (int i = 0; i < mesh.vertices().size(); i++) { + float[] v = mesh.vertices().get(i); + scaledVertices[i][0] = (v[0] - bounds[0]) * scale + 1.0F; + scaledVertices[i][1] = (v[1] - bounds[1]) * scale + 1.0F; + scaledVertices[i][2] = (v[2] - bounds[2]) * scale + 1.0F; + } + } + + boolean[][][] shell = new boolean[sizeX][sizeY][sizeZ]; + boolean hasTextures = materialTextures != null && !materialTextures.isEmpty() && colorIndex != null; + int[][][] blockIds = !hasTextures && materialToBlockId == null && defaultBlockId == 0 ? null : new int[sizeX][sizeY][sizeZ]; + rasterizeSurface(shell, blockIds, scaledVertices, mesh, materialTextures, materialToBlockId, colorIndex, defaultBlockId, sizeX, sizeY, sizeZ); + if (fillSolid) { + boolean[][][] solid = floodFillSolid(shell, sizeX, sizeY, sizeZ); + if (blockIds != null) { + fillInteriorBlockIds(solid, shell, blockIds, defaultBlockId, sizeX, sizeY, sizeZ); + } + + return cropToSolidBounds(solid, blockIds, sizeX, sizeY, sizeZ); + } else { + return cropToSolidBounds(shell, blockIds, sizeX, sizeY, sizeZ); + } + } + } + + private static int resolveIndex(int index, int count) { + return index < 0 ? count + index : index; + } + + private static void rasterizeSurface( + boolean[][][] voxels, + @Nullable int[][][] blockIds, + float[][] vertices, + ObjParser.ObjMesh mesh, + @Nullable Map materialTextures, + @Nullable Map materialToBlockId, + @Nullable BlockColorIndex colorIndex, + int defaultBlockId, + int sizeX, + int sizeY, + int sizeZ + ) { + List faces = mesh.faces(); + List faceUvIndices = mesh.faceUvIndices(); + List uvCoordinates = mesh.uvCoordinates(); + List faceMaterials = mesh.faceMaterials(); + boolean hasTextures = materialTextures != null && !materialTextures.isEmpty() && colorIndex != null; + + for (int faceIdx = 0; faceIdx < faces.size(); faceIdx++) { + int[] face = faces.get(faceIdx); + int i0 = resolveIndex(face[0], vertices.length); + int i1 = resolveIndex(face[1], vertices.length); + int i2 = resolveIndex(face[2], vertices.length); + float[] v0 = vertices[i0]; + float[] v1 = vertices[i1]; + float[] v2 = vertices[i2]; + String material = faceIdx < faceMaterials.size() ? faceMaterials.get(faceIdx) : null; + BufferedImage texture = null; + int faceBlockId = defaultBlockId; + if (material != null) { + if (hasTextures) { + texture = materialTextures.get(material); + } + + if (texture == null && materialToBlockId != null) { + faceBlockId = materialToBlockId.getOrDefault(material, defaultBlockId); + } + } + + float[] uv0 = null; + float[] uv1 = null; + float[] uv2 = null; + if (texture != null && faceIdx < faceUvIndices.size()) { + int[] uvIndices = faceUvIndices.get(faceIdx); + if (uvIndices != null && uvIndices.length >= 3) { + int uvCount = uvCoordinates.size(); + int ui0 = resolveIndex(uvIndices[0], uvCount); + int ui1 = resolveIndex(uvIndices[1], uvCount); + int ui2 = resolveIndex(uvIndices[2], uvCount); + if (ui0 >= 0 && ui0 < uvCount) { + uv0 = uvCoordinates.get(ui0); + } + + if (ui1 >= 0 && ui1 < uvCount) { + uv1 = uvCoordinates.get(ui1); + } + + if (ui2 >= 0 && ui2 < uvCount) { + uv2 = uvCoordinates.get(ui2); + } + } + } + + rasterizeLine(voxels, blockIds, v0, v1, uv0, uv1, texture, colorIndex, faceBlockId, sizeX, sizeY, sizeZ); + rasterizeLine(voxels, blockIds, v1, v2, uv1, uv2, texture, colorIndex, faceBlockId, sizeX, sizeY, sizeZ); + rasterizeLine(voxels, blockIds, v2, v0, uv2, uv0, texture, colorIndex, faceBlockId, sizeX, sizeY, sizeZ); + rasterizeTriangle(voxels, blockIds, v0, v1, v2, uv0, uv1, uv2, texture, colorIndex, faceBlockId, sizeX, sizeY, sizeZ); + } + } + + private static void rasterizeLine( + boolean[][][] voxels, + @Nullable int[][][] blockIds, + float[] a, + float[] b, + @Nullable float[] uvA, + @Nullable float[] uvB, + @Nullable BufferedImage texture, + @Nullable BlockColorIndex colorIndex, + int fallbackBlockId, + int sizeX, + int sizeY, + int sizeZ + ) { + float dx = b[0] - a[0]; + float dy = b[1] - a[1]; + float dz = b[2] - a[2]; + float len = (float)Math.sqrt(dx * dx + dy * dy + dz * dz); + if (len < 0.001F) { + int blockId = sampleBlockId(uvA, texture, colorIndex, fallbackBlockId); + setVoxel(voxels, blockIds, (int)a[0], (int)a[1], (int)a[2], blockId, sizeX, sizeY, sizeZ); + } else { + int steps = (int)Math.ceil(len * 2.0F) + 1; + + for (int i = 0; i <= steps; i++) { + float t = (float)i / steps; + float x = a[0] + dx * t; + float y = a[1] + dy * t; + float z = a[2] + dz * t; + float[] uv = interpolateUv(uvA, uvB, t); + int blockId = sampleBlockId(uv, texture, colorIndex, fallbackBlockId); + setVoxel(voxels, blockIds, (int)x, (int)y, (int)z, blockId, sizeX, sizeY, sizeZ); + } + } + } + + @Nullable + private static float[] interpolateUv(@Nullable float[] uvA, @Nullable float[] uvB, float t) { + return uvA != null && uvB != null ? new float[]{uvA[0] + (uvB[0] - uvA[0]) * t, uvA[1] + (uvB[1] - uvA[1]) * t} : uvA; + } + + private static int sampleBlockId(@Nullable float[] uv, @Nullable BufferedImage texture, @Nullable BlockColorIndex colorIndex, int fallbackBlockId) { + if (uv != null && texture != null && colorIndex != null) { + int alpha = TextureSampler.sampleAlphaAt(texture, uv[0], uv[1]); + if (alpha < 128) { + return 0; + } else { + int[] rgb = TextureSampler.sampleAt(texture, uv[0], uv[1]); + int blockId = colorIndex.findClosestBlock(rgb[0], rgb[1], rgb[2]); + return blockId > 0 ? blockId : fallbackBlockId; + } + } else { + return fallbackBlockId; + } + } + + private static void setVoxel(boolean[][][] voxels, @Nullable int[][][] blockIds, int x, int y, int z, int blockId, int sizeX, int sizeY, int sizeZ) { + if (x >= 0 && x < sizeX && y >= 0 && y < sizeY && z >= 0 && z < sizeZ) { + voxels[x][y][z] = true; + if (blockIds != null && blockId != 0 && blockIds[x][y][z] == 0) { + blockIds[x][y][z] = blockId; + } + } + } + + private static void rasterizeTriangle( + boolean[][][] voxels, + @Nullable int[][][] blockIds, + float[] v0, + float[] v1, + float[] v2, + @Nullable float[] uv0, + @Nullable float[] uv1, + @Nullable float[] uv2, + @Nullable BufferedImage texture, + @Nullable BlockColorIndex colorIndex, + int fallbackBlockId, + int sizeX, + int sizeY, + int sizeZ + ) { + float minX = Math.min(v0[0], Math.min(v1[0], v2[0])); + float maxX = Math.max(v0[0], Math.max(v1[0], v2[0])); + float minY = Math.min(v0[1], Math.min(v1[1], v2[1])); + float maxY = Math.max(v0[1], Math.max(v1[1], v2[1])); + float minZ = Math.min(v0[2], Math.min(v1[2], v2[2])); + float maxZ = Math.max(v0[2], Math.max(v1[2], v2[2])); + int startX = Math.max(0, (int)Math.floor(minX) - 1); + int endX = Math.min(sizeX - 1, (int)Math.ceil(maxX) + 1); + int startY = Math.max(0, (int)Math.floor(minY) - 1); + int endY = Math.min(sizeY - 1, (int)Math.ceil(maxY) + 1); + int startZ = Math.max(0, (int)Math.floor(minZ) - 1); + int endZ = Math.min(sizeZ - 1, (int)Math.ceil(maxZ) + 1); + boolean hasUvSampling = uv0 != null && uv1 != null && uv2 != null && texture != null && colorIndex != null; + + for (int x = startX; x <= endX; x++) { + for (int y = startY; y <= endY; y++) { + for (int z = startZ; z <= endZ; z++) { + float px = x + 0.5F; + float py = y + 0.5F; + float pz = z + 0.5F; + if (pointNearTriangle(px, py, pz, v0, v1, v2, 0.87F)) { + int blockId = fallbackBlockId; + if (hasUvSampling) { + float[] bary = barycentric(px, py, pz, v0, v1, v2); + if (bary != null) { + float u = bary[0] * uv0[0] + bary[1] * uv1[0] + bary[2] * uv2[0]; + float v = bary[0] * uv0[1] + bary[1] * uv1[1] + bary[2] * uv2[1]; + int alpha = TextureSampler.sampleAlphaAt(texture, u, v); + if (alpha < 128) { + continue; + } + + int[] rgb = TextureSampler.sampleAt(texture, u, v); + int sampledId = colorIndex.findClosestBlock(rgb[0], rgb[1], rgb[2]); + if (sampledId > 0) { + blockId = sampledId; + } + } + } + + voxels[x][y][z] = true; + if (blockIds != null && blockId != 0 && blockIds[x][y][z] == 0) { + blockIds[x][y][z] = blockId; + } + } + } + } + } + } + + @Nullable + private static float[] barycentric(float px, float py, float pz, float[] v0, float[] v1, float[] v2) { + float[] e1 = new float[]{v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]}; + float[] e2 = new float[]{v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]}; + float nx = e1[1] * e2[2] - e1[2] * e2[1]; + float ny = e1[2] * e2[0] - e1[0] * e2[2]; + float nz = e1[0] * e2[1] - e1[1] * e2[0]; + float ax = Math.abs(nx); + float ay = Math.abs(ny); + float az = Math.abs(nz); + float u0; + float u1; + float u2; + float v0c; + float v1c; + float v2c; + float pu; + float pv; + if (ax >= ay && ax >= az) { + u0 = v0[1]; + v0c = v0[2]; + u1 = v1[1]; + v1c = v1[2]; + u2 = v2[1]; + v2c = v2[2]; + pu = py; + pv = pz; + } else if (ay >= ax && ay >= az) { + u0 = v0[0]; + v0c = v0[2]; + u1 = v1[0]; + v1c = v1[2]; + u2 = v2[0]; + v2c = v2[2]; + pu = px; + pv = pz; + } else { + u0 = v0[0]; + v0c = v0[1]; + u1 = v1[0]; + v1c = v1[1]; + u2 = v2[0]; + v2c = v2[1]; + pu = px; + pv = py; + } + + float denom = (v1c - v2c) * (u0 - u2) + (u2 - u1) * (v0c - v2c); + if (Math.abs(denom) < 1.0E-10F) { + return null; + } else { + float w0 = ((v1c - v2c) * (pu - u2) + (u2 - u1) * (pv - v2c)) / denom; + float w1 = ((v2c - v0c) * (pu - u2) + (u0 - u2) * (pv - v2c)) / denom; + float w2 = 1.0F - w0 - w1; + return new float[]{w0, w1, w2}; + } + } + + private static boolean pointNearTriangle(float px, float py, float pz, float[] v0, float[] v1, float[] v2, float threshold) { + float e1x = v1[0] - v0[0]; + float e1y = v1[1] - v0[1]; + float e1z = v1[2] - v0[2]; + float e2x = v2[0] - v0[0]; + float e2y = v2[1] - v0[1]; + float e2z = v2[2] - v0[2]; + float nx = e1y * e2z - e1z * e2y; + float ny = e1z * e2x - e1x * e2z; + float nz = e1x * e2y - e1y * e2x; + float lenSq = nx * nx + ny * ny + nz * nz; + if (lenSq < 1.0E-12F) { + return false; + } else { + float len = (float)Math.sqrt(lenSq); + float dpx = px - v0[0]; + float dpy = py - v0[1]; + float dpz = pz - v0[2]; + float dotNP = nx * dpx + ny * dpy + nz * dpz; + float dist = Math.abs(dotNP) / len; + if (dist > threshold) { + return false; + } else { + float t = dotNP / lenSq; + float projX = px - t * nx; + float projY = py - t * ny; + float projZ = pz - t * nz; + return pointInTriangleWithTolerance(projX, projY, projZ, v0, v1, v2, 0.1F); + } + } + } + + private static boolean pointInTriangleWithTolerance(float px, float py, float pz, float[] v0, float[] v1, float[] v2, float tolerance) { + float vax = v1[0] - v0[0]; + float vay = v1[1] - v0[1]; + float vaz = v1[2] - v0[2]; + float vbx = v2[0] - v0[0]; + float vby = v2[1] - v0[1]; + float vbz = v2[2] - v0[2]; + float vpx = px - v0[0]; + float vpy = py - v0[1]; + float vpz = pz - v0[2]; + float d00 = vax * vax + vay * vay + vaz * vaz; + float d01 = vax * vbx + vay * vby + vaz * vbz; + float d11 = vbx * vbx + vby * vby + vbz * vbz; + float d20 = vpx * vax + vpy * vay + vpz * vaz; + float d21 = vpx * vbx + vpy * vby + vpz * vbz; + float denom = d00 * d11 - d01 * d01; + if (Math.abs(denom) < 1.0E-12F) { + return false; + } else { + float u = (d11 * d20 - d01 * d21) / denom; + float v = (d00 * d21 - d01 * d20) / denom; + return u >= -tolerance && v >= -tolerance && u + v <= 1.0F + tolerance; + } + } + + private static boolean[][][] floodFillSolid(boolean[][][] shell, int sizeX, int sizeY, int sizeZ) { + int dx = sizeX + 2; + int dy = sizeY + 2; + int dz = sizeZ + 2; + int plane = dx * dy; + int total = plane * dz; + boolean[] visited = new boolean[total]; + int[] queue = new int[total]; + int qh = 0; + int qt = 0; + visited[0] = true; + queue[qt++] = 0; + + while (qh < qt) { + int idx = queue[qh++]; + int x = idx % dx; + int y = idx / dx % dy; + int z = idx / plane; + if (x + 1 < dx && tryEnqueue(shell, sizeX, sizeY, sizeZ, visited, queue, x + 1, y, z, dx, plane, qt)) { + qt++; + } + + if (x - 1 >= 0 && tryEnqueue(shell, sizeX, sizeY, sizeZ, visited, queue, x - 1, y, z, dx, plane, qt)) { + qt++; + } + + if (y + 1 < dy && tryEnqueue(shell, sizeX, sizeY, sizeZ, visited, queue, x, y + 1, z, dx, plane, qt)) { + qt++; + } + + if (y - 1 >= 0 && tryEnqueue(shell, sizeX, sizeY, sizeZ, visited, queue, x, y - 1, z, dx, plane, qt)) { + qt++; + } + + if (z + 1 < dz && tryEnqueue(shell, sizeX, sizeY, sizeZ, visited, queue, x, y, z + 1, dx, plane, qt)) { + qt++; + } + + if (z - 1 >= 0 && tryEnqueue(shell, sizeX, sizeY, sizeZ, visited, queue, x, y, z - 1, dx, plane, qt)) { + qt++; + } + } + + boolean[][][] solid = new boolean[sizeX][sizeY][sizeZ]; + + for (int xx = 0; xx < sizeX; xx++) { + for (int yx = 0; yx < sizeY; yx++) { + for (int zx = 0; zx < sizeZ; zx++) { + int ex = xx + 1; + int ey = yx + 1; + int ez = zx + 1; + int eIdx = ex + ey * dx + ez * plane; + solid[xx][yx][zx] = !visited[eIdx]; + } + } + } + + return solid; + } + + private static boolean tryEnqueue( + boolean[][][] shell, int sizeX, int sizeY, int sizeZ, boolean[] visited, int[] queue, int ex, int ey, int ez, int dx, int plane, int writeIndex + ) { + int idx = ex + ey * dx + ez * plane; + if (visited[idx]) { + return false; + } else { + int x = ex - 1; + int y = ey - 1; + int z = ez - 1; + if (x >= 0 && y >= 0 && z >= 0 && x < sizeX && y < sizeY && z < sizeZ && shell[x][y][z]) { + return false; + } else { + visited[idx] = true; + queue[writeIndex] = idx; + return true; + } + } + } + + private static MeshVoxelizer.VoxelResult cropToSolidBounds(boolean[][][] voxels, @Nullable int[][][] blockIds, int sizeX, int sizeY, int sizeZ) { + int minX = sizeX; + int minY = sizeY; + int minZ = sizeZ; + int maxX = -1; + int maxY = -1; + int maxZ = -1; + + for (int x = 0; x < sizeX; x++) { + for (int y = 0; y < sizeY; y++) { + for (int z = 0; z < sizeZ; z++) { + if (voxels[x][y][z]) { + if (x < minX) { + minX = x; + } + + if (y < minY) { + minY = y; + } + + if (z < minZ) { + minZ = z; + } + + if (x > maxX) { + maxX = x; + } + + if (y > maxY) { + maxY = y; + } + + if (z > maxZ) { + maxZ = z; + } + } + } + } + } + + if (maxX >= minX && maxY >= minY && maxZ >= minZ) { + int outX = maxX - minX + 1; + int outY = maxY - minY + 1; + int outZ = maxZ - minZ + 1; + boolean[][][] out = new boolean[outX][outY][outZ]; + int[][][] outBlockIds = blockIds != null ? new int[outX][outY][outZ] : null; + + for (int x = 0; x < outX; x++) { + for (int y = 0; y < outY; y++) { + System.arraycopy(voxels[minX + x][minY + y], minZ, out[x][y], 0, outZ); + if (outBlockIds != null && blockIds != null) { + System.arraycopy(blockIds[minX + x][minY + y], minZ, outBlockIds[x][y], 0, outZ); + } + } + } + + return new MeshVoxelizer.VoxelResult(out, outBlockIds, outX, outY, outZ); + } else { + return new MeshVoxelizer.VoxelResult(new boolean[1][1][1], null, 1, 1, 1); + } + } + + private static void fillInteriorBlockIds(boolean[][][] solid, boolean[][][] shell, int[][][] blockIds, int defaultBlockId, int sizeX, int sizeY, int sizeZ) { + for (int x = 0; x < sizeX; x++) { + for (int y = 0; y < sizeY; y++) { + for (int z = 0; z < sizeZ; z++) { + if (solid[x][y][z] && !shell[x][y][z] && blockIds[x][y][z] == 0) { + int bestId = findNearestSurfaceBlockId(blockIds, shell, x, y, z, sizeX, sizeY, sizeZ); + blockIds[x][y][z] = bestId != 0 ? bestId : defaultBlockId; + } + } + } + } + } + + private static int findNearestSurfaceBlockId(int[][][] blockIds, boolean[][][] shell, int cx, int cy, int cz, int sizeX, int sizeY, int sizeZ) { + for (int radius = 1; radius <= 5; radius++) { + for (int dx = -radius; dx <= radius; dx++) { + for (int dy = -radius; dy <= radius; dy++) { + for (int dz = -radius; dz <= radius; dz++) { + int nx = cx + dx; + int ny = cy + dy; + int nz = cz + dz; + if (nx >= 0 && nx < sizeX && ny >= 0 && ny < sizeY && nz >= 0 && nz < sizeZ && shell[nx][ny][nz] && blockIds[nx][ny][nz] != 0) { + return blockIds[nx][ny][nz]; + } + } + } + } + } + + return 0; + } + + public record VoxelResult(boolean[][][] voxels, @Nullable int[][][] blockIds, int sizeX, int sizeY, int sizeZ) { + public int countSolid() { + int count = 0; + + for (int x = 0; x < this.sizeX; x++) { + for (int y = 0; y < this.sizeY; y++) { + for (int z = 0; z < this.sizeZ; z++) { + if (this.voxels[x][y][z]) { + count++; + } + } + } + } + + return count; + } + + public int getBlockId(int x, int y, int z) { + if (this.blockIds == null) { + return 0; + } else { + return x >= 0 && x < this.sizeX && y >= 0 && y < this.sizeY && z >= 0 && z < this.sizeZ ? this.blockIds[x][y][z] : 0; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/objimport/MtlParser.java b/src/com/hypixel/hytale/builtin/buildertools/objimport/MtlParser.java new file mode 100644 index 0000000..0f39578 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/objimport/MtlParser.java @@ -0,0 +1,92 @@ +package com.hypixel.hytale.builtin.buildertools.objimport; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class MtlParser { + private MtlParser() { + } + + @Nonnull + public static Map parse(@Nonnull Path path) throws IOException { + Map materials = new HashMap<>(); + String currentMaterial = null; + float[] currentKd = null; + String currentMapKd = null; + + try (BufferedReader reader = Files.newBufferedReader(path)) { + String line; + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (!line.isEmpty() && !line.startsWith("#")) { + String[] parts = line.split("\\s+", 2); + if (parts.length != 0) { + String var8 = parts[0]; + switch (var8) { + case "newmtl": + if (currentMaterial != null) { + materials.put(currentMaterial, new MtlParser.MtlMaterial(currentMaterial, currentKd, currentMapKd)); + } + + currentMaterial = parts.length > 1 ? parts[1].trim() : ""; + currentKd = null; + currentMapKd = null; + break; + case "Kd": + if (parts.length > 1) { + currentKd = parseColor(parts[1]); + } + break; + case "map_Kd": + if (parts.length > 1) { + currentMapKd = parts[1].trim(); + } + } + } + } + } + + if (currentMaterial != null) { + materials.put(currentMaterial, new MtlParser.MtlMaterial(currentMaterial, currentKd, currentMapKd)); + } + } + + return materials; + } + + @Nullable + private static float[] parseColor(String colorStr) { + String[] parts = colorStr.trim().split("\\s+"); + if (parts.length < 3) { + return null; + } else { + try { + float r = Float.parseFloat(parts[0]); + float g = Float.parseFloat(parts[1]); + float b = Float.parseFloat(parts[2]); + return new float[]{r, g, b}; + } catch (NumberFormatException var5) { + return null; + } + } + } + + public record MtlMaterial(@Nonnull String name, @Nullable float[] diffuseColor, @Nullable String diffuseTexturePath) { + @Nullable + public int[] getDiffuseColorRGB() { + return this.diffuseColor == null + ? null + : new int[]{ + Math.max(0, Math.min(255, Math.round(this.diffuseColor[0] * 255.0F))), + Math.max(0, Math.min(255, Math.round(this.diffuseColor[1] * 255.0F))), + Math.max(0, Math.min(255, Math.round(this.diffuseColor[2] * 255.0F))) + }; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/objimport/ObjImportCommand.java b/src/com/hypixel/hytale/builtin/buildertools/objimport/ObjImportCommand.java new file mode 100644 index 0000000..8e1f763 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/objimport/ObjImportCommand.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.builtin.buildertools.objimport; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ObjImportCommand extends AbstractPlayerCommand { + public ObjImportCommand() { + super("importobj", "server.commands.importobj.desc"); + this.addAliases("obj"); + this.setPermissionGroup(GameMode.Creative); + this.requirePermission("hytale.editor.selection.clipboard"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + playerComponent.getPageManager().openCustomPage(ref, store, new ObjImportPage(playerRef)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/objimport/ObjImportPage.java b/src/com/hypixel/hytale/builtin/buildertools/objimport/ObjImportPage.java new file mode 100644 index 0000000..939297b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/objimport/ObjImportPage.java @@ -0,0 +1,819 @@ +package com.hypixel.hytale.builtin.buildertools.objimport; + +import com.hypixel.hytale.builtin.buildertools.BlockColorIndex; +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.protocol.packets.inventory.SetActiveSlot; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.ui.DropdownEntryInfo; +import com.hypixel.hytale.server.core.ui.LocalizableString; +import com.hypixel.hytale.server.core.ui.browser.FileBrowserConfig; +import com.hypixel.hytale.server.core.ui.browser.ServerFileBrowser; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Map.Entry; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ObjImportPage extends InteractiveCustomUIPage { + private static final String DEFAULT_BLOCK = "Rock_Stone"; + private static final int DEFAULT_HEIGHT = 20; + private static final int MIN_HEIGHT = 1; + private static final int MAX_HEIGHT = 320; + private static final float DEFAULT_SCALE = 1.0F; + private static final float MIN_SCALE = 0.01F; + private static final float MAX_SCALE = 100.0F; + private static final String PASTE_TOOL_ID = "EditorTool_Paste"; + private static final Path IMPORTS_DIR = Paths.get("imports", "models"); + @Nonnull + private String objPath = ""; + private int targetHeight = 20; + private boolean useScaleMode = false; + private float scale = 1.0F; + @Nonnull + private String blockPattern = "Rock_Stone"; + private boolean fillSolid = true; + private boolean useMaterials = true; + private boolean autoDetectTextures = false; + @Nonnull + private String originStr = "bottom_center"; + @Nonnull + private ObjImportPage.Origin origin = ObjImportPage.Origin.BOTTOM_CENTER; + @Nonnull + private String rotationStr = "y_up"; + @Nonnull + private ObjImportPage.MeshRotation rotation = ObjImportPage.MeshRotation.NONE; + @Nullable + private String statusMessage = null; + private boolean isError = false; + private boolean isProcessing = false; + private boolean showBrowser = false; + @Nonnull + private final ServerFileBrowser browser; + private static final String[] AUTO_DETECT_SUFFIXES = new String[]{"", "_dif", "_diffuse"}; + private static final String[] AUTO_DETECT_EXTENSIONS = new String[]{".png", ".jpg", ".jpeg"}; + + public ObjImportPage(@Nonnull PlayerRef playerRef) { + super(playerRef, CustomPageLifetime.CanDismiss, ObjImportPage.PageData.CODEC); + FileBrowserConfig config = FileBrowserConfig.builder() + .listElementId("#BrowserPage #FileList") + .searchInputId("#BrowserPage #SearchInput") + .currentPathId("#BrowserPage #CurrentPath") + .roots(List.of(new FileBrowserConfig.RootEntry("Imports", IMPORTS_DIR))) + .allowedExtensions(".obj") + .enableRootSelector(false) + .enableSearch(true) + .enableDirectoryNav(true) + .maxResults(50) + .build(); + + try { + Files.createDirectories(IMPORTS_DIR); + } catch (IOException var4) { + } + + this.browser = new ServerFileBrowser(config); + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/ObjImportPage.ui"); + commandBuilder.set("#ObjPath #Input.Value", this.objPath); + commandBuilder.set("#HeightInput #Input.Value", this.targetHeight); + commandBuilder.set("#ScaleInput #Input.Value", this.scale); + commandBuilder.set("#BlockPattern #Input.Value", this.blockPattern); + commandBuilder.set("#FillModeCheckbox #CheckBox.Value", this.fillSolid); + commandBuilder.set("#UseMaterialsCheckbox #CheckBox.Value", this.useMaterials); + commandBuilder.set("#AutoDetectTexturesCheckbox #CheckBox.Value", this.autoDetectTextures); + commandBuilder.set("#HeightInput.Visible", !this.useScaleMode); + commandBuilder.set("#ScaleInput.Visible", this.useScaleMode); + List sizeModeEntries = new ArrayList<>(); + sizeModeEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.objImport.sizeMode.height"), "height")); + sizeModeEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.objImport.sizeMode.scale"), "scale")); + commandBuilder.set("#SizeModeInput #Input.Entries", sizeModeEntries); + commandBuilder.set("#SizeModeInput #Input.Value", this.useScaleMode ? "scale" : "height"); + List originEntries = new ArrayList<>(); + originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.bottom_front_left"), "bottom_front_left")); + originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.bottom_center"), "bottom_center")); + originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.center"), "center")); + originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.top_center"), "top_center")); + commandBuilder.set("#OriginInput #Input.Entries", originEntries); + commandBuilder.set("#OriginInput #Input.Value", this.originStr); + List axisEntries = new ArrayList<>(); + axisEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.objImport.axis.yUp"), "y_up")); + axisEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.objImport.axis.zUp"), "z_up")); + axisEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.objImport.axis.xUp"), "x_up")); + commandBuilder.set("#RotationInput #Input.Entries", axisEntries); + commandBuilder.set("#RotationInput #Input.Value", this.rotationStr); + this.updateStatus(commandBuilder); + eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#ObjPath #Input", EventData.of("@ObjPath", "#ObjPath #Input.Value"), false); + eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#HeightInput #Input", EventData.of("@Height", "#HeightInput #Input.Value"), false); + eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#ScaleInput #Input", EventData.of("@Scale", "#ScaleInput #Input.Value"), false); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, "#SizeModeInput #Input", EventData.of("SizeMode", "#SizeModeInput #Input.Value"), false + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, "#BlockPattern #Input", EventData.of("@BlockPattern", "#BlockPattern #Input.Value"), false + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, "#FillModeCheckbox #CheckBox", EventData.of("@FillSolid", "#FillModeCheckbox #CheckBox.Value"), false + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, + "#UseMaterialsCheckbox #CheckBox", + EventData.of("@UseMaterials", "#UseMaterialsCheckbox #CheckBox.Value"), + false + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, + "#AutoDetectTexturesCheckbox #CheckBox", + EventData.of("@AutoDetectTextures", "#AutoDetectTexturesCheckbox #CheckBox.Value"), + false + ); + eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#OriginInput #Input", EventData.of("@Origin", "#OriginInput #Input.Value"), false); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, "#RotationInput #Input", EventData.of("@Rotation", "#RotationInput #Input.Value"), false + ); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ImportButton", EventData.of("Import", "true")); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ObjPath #BrowseButton", EventData.of("Browse", "true")); + commandBuilder.set("#FormContainer.Visible", !this.showBrowser); + commandBuilder.set("#BrowserPage.Visible", this.showBrowser); + if (this.showBrowser) { + this.buildBrowserPage(commandBuilder, eventBuilder); + } + } + + private void buildBrowserPage(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) { + this.browser.buildSearchInput(commandBuilder, eventBuilder); + this.browser.buildCurrentPath(commandBuilder); + this.browser.buildFileList(commandBuilder, eventBuilder); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#BrowserPage #SelectButton", EventData.of("BrowserSelect", "true")); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#BrowserPage #CancelButton", EventData.of("BrowserCancel", "true")); + } + + private void updateStatus(@Nonnull UICommandBuilder commandBuilder) { + if (this.statusMessage != null) { + commandBuilder.set("#StatusText.Text", this.statusMessage); + commandBuilder.set("#StatusText.Visible", true); + commandBuilder.set("#StatusText.Style.TextColor", this.isError ? "#e74c3c" : "#cfd8e3"); + } else { + commandBuilder.set("#StatusText.Visible", false); + } + } + + private void setError(@Nonnull String message) { + this.statusMessage = message; + this.isError = true; + this.isProcessing = false; + this.rebuild(); + } + + private void setStatus(@Nonnull String message) { + this.statusMessage = message; + this.isError = false; + this.rebuild(); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull ObjImportPage.PageData data) { + boolean needsUpdate = false; + if (data.browse != null && data.browse) { + this.showBrowser = true; + this.rebuild(); + } else if (data.browserCancel != null && data.browserCancel) { + this.showBrowser = false; + this.rebuild(); + } else if (data.browserSelect != null && data.browserSelect) { + if (!this.browser.getSelectedItems().isEmpty()) { + String selectedPath = this.browser.getSelectedItems().iterator().next(); + this.objPath = this.browser.getRoot().resolve(selectedPath).toString(); + } + + this.showBrowser = false; + this.rebuild(); + } else { + if (this.showBrowser && (data.file != null || data.searchQuery != null || data.searchResult != null)) { + boolean handled = false; + if (data.searchQuery != null) { + this.browser.setSearchQuery(data.searchQuery.trim().toLowerCase()); + handled = true; + } + + if (data.file != null) { + String fileName = data.file; + if ("..".equals(fileName)) { + this.browser.navigateUp(); + handled = true; + } else { + Path targetPath = this.browser.resolveFromCurrent(fileName); + if (targetPath != null && Files.isDirectory(targetPath)) { + this.browser.navigateTo(Paths.get(fileName)); + handled = true; + } else if (targetPath != null && Files.isRegularFile(targetPath)) { + this.objPath = targetPath.toString(); + this.showBrowser = false; + this.rebuild(); + return; + } + } + } + + if (data.searchResult != null) { + Path resolvedPath = this.browser.resolveSecure(data.searchResult); + if (resolvedPath != null && Files.isRegularFile(resolvedPath)) { + this.objPath = resolvedPath.toString(); + this.showBrowser = false; + this.rebuild(); + return; + } + } + + if (handled) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.browser.buildFileList(commandBuilder, eventBuilder); + this.browser.buildCurrentPath(commandBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + return; + } + } + + if (data.objPath != null) { + this.objPath = StringUtil.stripQuotes(data.objPath.trim()); + this.statusMessage = null; + needsUpdate = true; + } + + if (data.height != null) { + this.targetHeight = Math.max(1, Math.min(320, data.height)); + needsUpdate = true; + } + + if (data.scale != null) { + this.scale = Math.max(0.01F, Math.min(100.0F, data.scale)); + needsUpdate = true; + } + + if (data.sizeMode != null) { + this.useScaleMode = "scale".equalsIgnoreCase(data.sizeMode); + this.rebuild(); + } else { + if (data.blockPattern != null) { + this.blockPattern = data.blockPattern.trim(); + needsUpdate = true; + } + + if (data.fillSolid != null) { + this.fillSolid = data.fillSolid; + needsUpdate = true; + } + + if (data.useMaterials != null) { + this.useMaterials = data.useMaterials; + needsUpdate = true; + } + + if (data.autoDetectTextures != null) { + this.autoDetectTextures = data.autoDetectTextures; + needsUpdate = true; + } + + if (data.origin != null) { + this.originStr = data.origin.trim().toLowerCase(); + String var8 = this.originStr; + + this.origin = switch (var8) { + case "bottom_front_left" -> ObjImportPage.Origin.BOTTOM_FRONT_LEFT; + case "center" -> ObjImportPage.Origin.CENTER; + case "top_center" -> ObjImportPage.Origin.TOP_CENTER; + default -> ObjImportPage.Origin.BOTTOM_CENTER; + }; + needsUpdate = true; + } + + if (data.rotation != null) { + this.rotationStr = data.rotation.trim().toLowerCase(); + String var9 = this.rotationStr; + + this.rotation = switch (var9) { + case "z_up" -> ObjImportPage.MeshRotation.Z_UP_TO_Y_UP; + case "x_up" -> ObjImportPage.MeshRotation.X_UP_TO_Y_UP; + default -> ObjImportPage.MeshRotation.NONE; + }; + needsUpdate = true; + } + + if (data.doImport != null && data.doImport && !this.isProcessing) { + this.performImport(ref, store); + } else { + if (needsUpdate) { + this.sendUpdate(); + } + } + } + } + } + + @Nullable + private List parseBlockPattern(@Nonnull String pattern) { + List result = new ArrayList<>(); + String[] parts = pattern.split(","); + + for (String part : parts) { + part = part.trim(); + if (!part.isEmpty()) { + int weight = 100; + String blockName = part; + int pctIdx = part.indexOf(37); + if (pctIdx > 0) { + try { + weight = Integer.parseInt(part.substring(0, pctIdx).trim()); + blockName = part.substring(pctIdx + 1).trim(); + } catch (NumberFormatException var12) { + return null; + } + } + + int blockId = BlockType.getAssetMap().getIndex(blockName); + if (blockId == Integer.MIN_VALUE) { + return null; + } + + result.add(new ObjImportPage.WeightedBlock(blockId, weight)); + } + } + + return result.isEmpty() ? null : result; + } + + private int selectRandomBlock(@Nonnull List blocks, @Nonnull Random random) { + if (blocks.isEmpty()) { + throw new IllegalStateException("Cannot select from empty blocks list"); + } else if (blocks.size() == 1) { + return blocks.get(0).blockId; + } else { + int totalWeight = 0; + + for (ObjImportPage.WeightedBlock wb : blocks) { + totalWeight += wb.weight; + } + + if (totalWeight <= 0) { + return blocks.get(0).blockId; + } else { + int roll = random.nextInt(totalWeight); + int cumulative = 0; + + for (ObjImportPage.WeightedBlock wb : blocks) { + cumulative += wb.weight; + if (roll < cumulative) { + return wb.blockId; + } + } + + return blocks.get(0).blockId; + } + } + } + + private void performImport(@Nonnull Ref ref, @Nonnull Store store) { + if (this.objPath.isEmpty()) { + this.setError("Please enter a path to an OBJ file"); + } else { + Path path = Paths.get(this.objPath); + if (!SingleplayerModule.isOwner(this.playerRef)) { + Path normalizedPath = path.toAbsolutePath().normalize(); + Path normalizedImports = IMPORTS_DIR.toAbsolutePath().normalize(); + if (!normalizedPath.startsWith(normalizedImports)) { + this.setError("Files must be in the server's imports/models directory"); + return; + } + } + + if (!Files.exists(path)) { + this.setError("File not found: " + this.objPath); + } else if (!this.objPath.toLowerCase().endsWith(".obj")) { + this.setError("File must be a .obj file"); + } else { + List blocks = this.parseBlockPattern(this.blockPattern); + if (blocks == null) { + this.setError("Invalid block pattern: " + this.blockPattern); + } else { + this.isProcessing = true; + this.setStatus("Processing..."); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + if (playerComponent != null && playerRefComponent != null) { + int finalHeight = this.targetHeight; + boolean finalUseScaleMode = this.useScaleMode; + float finalScale = this.scale; + String finalPath = this.objPath; + boolean finalFillSolid = this.fillSolid; + boolean finalUseMaterials = this.useMaterials; + boolean finalAutoDetectTextures = this.autoDetectTextures; + ObjImportPage.Origin finalOrigin = this.origin; + ObjImportPage.MeshRotation finalRotation = this.rotation; + BuilderToolsPlugin.addToQueue( + playerComponent, + playerRefComponent, + (r, builderState, componentAccessor) -> { + try { + Path objFilePath = Paths.get(finalPath); + ObjParser.ObjMesh mesh = ObjParser.parse(objFilePath); + switch (finalRotation) { + case Z_UP_TO_Y_UP: + mesh.transformZUpToYUp(); + break; + case X_UP_TO_Y_UP: + mesh.transformXUpToYUp(); + } + + int computedHeight; + if (finalUseScaleMode) { + float[] bounds = mesh.getBounds(); + float meshHeight = bounds[4] - bounds[1]; + computedHeight = Math.max(1, (int)Math.ceil(meshHeight * finalScale)); + } else { + computedHeight = finalHeight; + } + + if (blocks.isEmpty()) { + this.setError("No blocks available for import"); + return; + } + + BlockColorIndex colorIndex = BuilderToolsPlugin.get().getBlockColorIndex(); + Map materialTextures = new HashMap<>(); + Map materialToBlockId = new HashMap<>(); + int defaultBlockId = blocks.get(0).blockId; + if (finalUseMaterials && mesh.mtlLib() != null) { + this.loadMaterialData(objFilePath, mesh, colorIndex, materialTextures, materialToBlockId, finalAutoDetectTextures); + if (!materialToBlockId.isEmpty()) { + defaultBlockId = materialToBlockId.values().iterator().next(); + } + } + + boolean hasUvTextures = mesh.hasUvCoordinates() && !materialTextures.isEmpty(); + boolean preserveOrigin = finalOrigin == ObjImportPage.Origin.BOTTOM_FRONT_LEFT; + MeshVoxelizer.VoxelResult result; + if (hasUvTextures) { + result = MeshVoxelizer.voxelize( + mesh, computedHeight, finalFillSolid, materialTextures, materialToBlockId, colorIndex, defaultBlockId, preserveOrigin + ); + } else { + result = MeshVoxelizer.voxelize( + mesh, computedHeight, finalFillSolid, null, materialToBlockId, colorIndex, defaultBlockId, preserveOrigin + ); + } + + TextureSampler.clearCache(); + int offsetX = 0; + int offsetY = 0; + int offsetZ = 0; + switch (finalOrigin) { + case BOTTOM_FRONT_LEFT: + default: + break; + case BOTTOM_CENTER: + offsetX = -result.sizeX() / 2; + offsetZ = -result.sizeZ() / 2; + break; + case CENTER: + offsetX = -result.sizeX() / 2; + offsetY = -result.sizeY() / 2; + offsetZ = -result.sizeZ() / 2; + break; + case TOP_CENTER: + offsetX = -result.sizeX() / 2; + offsetY = -result.sizeY(); + offsetZ = -result.sizeZ() / 2; + } + + BlockSelection selection = new BlockSelection(result.countSolid(), 0); + selection.setPosition(0, 0, 0); + Random random = new Random(); + boolean hasMaterialBlockIds = result.blockIds() != null; + + for (int x = 0; x < result.sizeX(); x++) { + for (int y = 0; y < result.sizeY(); y++) { + for (int z = 0; z < result.sizeZ(); z++) { + if (result.voxels()[x][y][z]) { + int blockId; + if (hasMaterialBlockIds) { + blockId = result.getBlockId(x, y, z); + if (blockId == 0) { + blockId = this.selectRandomBlock(blocks, random); + } + } else { + blockId = this.selectRandomBlock(blocks, random); + } + + selection.addBlockAtLocalPos(x + offsetX, y + offsetY, z + offsetZ, blockId, 0, 0, 0); + } + } + } + } + + selection.setSelectionArea( + new Vector3i(offsetX, offsetY, offsetZ), + new Vector3i(result.sizeX() - 1 + offsetX, result.sizeY() - 1 + offsetY, result.sizeZ() - 1 + offsetZ) + ); + builderState.setSelection(selection); + builderState.sendSelectionToClient(); + int blockCount = result.countSolid(); + String textureInfo = hasUvTextures ? " (UV textured)" : ""; + this.statusMessage = String.format( + "Success! %d blocks copied to clipboard (%dx%dx%d)%s", blockCount, result.sizeX(), result.sizeY(), result.sizeZ(), textureInfo + ); + this.isProcessing = false; + playerRefComponent.sendMessage( + Message.translation("server.builderTools.objImport.success") + .param("count", blockCount) + .param("width", result.sizeX()) + .param("height", result.sizeY()) + .param("depth", result.sizeZ()) + ); + playerComponent.getPageManager().setPage(r, store, Page.None); + this.switchToPasteTool(playerComponent, playerRefComponent); + } catch (ObjParser.ObjParseException var37) { + BuilderToolsPlugin.get().getLogger().at(Level.WARNING).log("OBJ parse error: %s", var37.getMessage()); + this.setError("Parse error: " + var37.getMessage()); + } catch (IOException var38) { + BuilderToolsPlugin.get().getLogger().at(Level.WARNING).withCause(var38).log("OBJ import IO error"); + this.setError("IO error: " + var38.getMessage()); + } catch (Exception var39) { + BuilderToolsPlugin.get().getLogger().at(Level.WARNING).withCause(var39).log("OBJ import error"); + this.setError("Error: " + var39.getMessage()); + } + } + ); + } else { + this.setError("Player not found"); + } + } + } + } + } + + private void switchToPasteTool(@Nonnull Player playerComponent, @Nonnull PlayerRef playerRef) { + Inventory inventory = playerComponent.getInventory(); + ItemContainer hotbar = inventory.getHotbar(); + ItemContainer storage = inventory.getStorage(); + ItemContainer tools = inventory.getTools(); + int hotbarSize = hotbar.getCapacity(); + + for (short slot = 0; slot < hotbarSize; slot++) { + ItemStack itemStack = hotbar.getItemStack(slot); + if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) { + inventory.setActiveHotbarSlot((byte)slot); + playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)slot)); + return; + } + } + + short emptySlot = -1; + + for (short slotx = 0; slotx < hotbarSize; slotx++) { + ItemStack itemStack = hotbar.getItemStack(slotx); + if (itemStack == null || itemStack.isEmpty()) { + emptySlot = slotx; + break; + } + } + + if (emptySlot != -1) { + for (short slotxx = 0; slotxx < storage.getCapacity(); slotxx++) { + ItemStack itemStack = storage.getItemStack(slotxx); + if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) { + storage.moveItemStackFromSlotToSlot(slotxx, 1, hotbar, emptySlot); + inventory.setActiveHotbarSlot((byte)emptySlot); + playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)emptySlot)); + return; + } + } + + ItemStack pasteToolStack = null; + + for (short slotxxx = 0; slotxxx < tools.getCapacity(); slotxxx++) { + ItemStack itemStack = tools.getItemStack(slotxxx); + if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) { + pasteToolStack = itemStack; + break; + } + } + + if (pasteToolStack != null) { + hotbar.setItemStackForSlot(emptySlot, new ItemStack(pasteToolStack.getItemId())); + inventory.setActiveHotbarSlot((byte)emptySlot); + playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)emptySlot)); + } + } + } + + private void loadMaterialData( + @Nonnull Path objPath, + @Nonnull ObjParser.ObjMesh mesh, + @Nonnull BlockColorIndex colorIndex, + @Nonnull Map materialTextures, + @Nonnull Map materialToBlockId, + boolean autoDetectTextures + ) throws IOException { + if (mesh.mtlLib() != null) { + Path mtlPath = objPath.getParent().resolve(mesh.mtlLib()); + if (Files.exists(mtlPath)) { + Map materials = MtlParser.parse(mtlPath); + Path textureDir = mtlPath.getParent(); + + for (Entry entry : materials.entrySet()) { + String materialName = entry.getKey(); + MtlParser.MtlMaterial material = entry.getValue(); + String texturePath = material.diffuseTexturePath(); + if (texturePath == null && autoDetectTextures) { + texturePath = findMatchingTexture(textureDir, materialName); + } + + if (texturePath != null) { + Path resolvedPath = textureDir.resolve(texturePath); + BufferedImage texture = TextureSampler.loadTexture(resolvedPath); + if (texture != null) { + materialTextures.put(materialName, texture); + int[] avgColor = TextureSampler.getAverageColor(resolvedPath); + if (avgColor != null) { + int blockId = colorIndex.findClosestBlock(avgColor[0], avgColor[1], avgColor[2]); + if (blockId > 0) { + materialToBlockId.put(materialName, blockId); + } + } + continue; + } + } + + int[] rgb = material.getDiffuseColorRGB(); + if (rgb != null) { + int blockId = colorIndex.findClosestBlock(rgb[0], rgb[1], rgb[2]); + if (blockId > 0) { + materialToBlockId.put(materialName, blockId); + } + } + } + } + } + } + + @Nullable + private static String findMatchingTexture(@Nonnull Path directory, @Nonnull String materialName) { + for (String suffix : AUTO_DETECT_SUFFIXES) { + for (String ext : AUTO_DETECT_EXTENSIONS) { + String filename = materialName + suffix + ext; + if (Files.exists(directory.resolve(filename))) { + return filename; + } + } + } + + return null; + } + + public static enum MeshRotation { + NONE, + Z_UP_TO_Y_UP, + X_UP_TO_Y_UP; + + private MeshRotation() { + } + } + + public static enum Origin { + BOTTOM_FRONT_LEFT, + BOTTOM_CENTER, + CENTER, + TOP_CENTER; + + private Origin() { + } + } + + public static class PageData { + static final String KEY_OBJ_PATH = "@ObjPath"; + static final String KEY_HEIGHT = "@Height"; + static final String KEY_SCALE = "@Scale"; + static final String KEY_SIZE_MODE = "SizeMode"; + static final String KEY_BLOCK_PATTERN = "@BlockPattern"; + static final String KEY_FILL_SOLID = "@FillSolid"; + static final String KEY_USE_MATERIALS = "@UseMaterials"; + static final String KEY_AUTO_DETECT_TEXTURES = "@AutoDetectTextures"; + static final String KEY_ORIGIN = "@Origin"; + static final String KEY_ROTATION = "@Rotation"; + static final String KEY_IMPORT = "Import"; + static final String KEY_BROWSE = "Browse"; + static final String KEY_BROWSER_SELECT = "BrowserSelect"; + static final String KEY_BROWSER_CANCEL = "BrowserCancel"; + public static final BuilderCodec CODEC = BuilderCodec.builder(ObjImportPage.PageData.class, ObjImportPage.PageData::new) + .addField(new KeyedCodec<>("@ObjPath", Codec.STRING), (entry, s) -> entry.objPath = s, entry -> entry.objPath) + .addField(new KeyedCodec<>("@Height", Codec.INTEGER), (entry, i) -> entry.height = i, entry -> entry.height) + .addField(new KeyedCodec<>("@Scale", Codec.FLOAT), (entry, f) -> entry.scale = f, entry -> entry.scale) + .addField(new KeyedCodec<>("SizeMode", Codec.STRING), (entry, s) -> entry.sizeMode = s, entry -> entry.sizeMode) + .addField(new KeyedCodec<>("@BlockPattern", Codec.STRING), (entry, s) -> entry.blockPattern = s, entry -> entry.blockPattern) + .addField(new KeyedCodec<>("@FillSolid", Codec.BOOLEAN), (entry, b) -> entry.fillSolid = b, entry -> entry.fillSolid) + .addField(new KeyedCodec<>("@UseMaterials", Codec.BOOLEAN), (entry, b) -> entry.useMaterials = b, entry -> entry.useMaterials) + .addField(new KeyedCodec<>("@AutoDetectTextures", Codec.BOOLEAN), (entry, b) -> entry.autoDetectTextures = b, entry -> entry.autoDetectTextures) + .addField(new KeyedCodec<>("@Origin", Codec.STRING), (entry, s) -> entry.origin = s, entry -> entry.origin) + .addField(new KeyedCodec<>("@Rotation", Codec.STRING), (entry, s) -> entry.rotation = s, entry -> entry.rotation) + .addField( + new KeyedCodec<>("Import", Codec.STRING), + (entry, s) -> entry.doImport = "true".equalsIgnoreCase(s), + entry -> entry.doImport != null && entry.doImport ? "true" : null + ) + .addField( + new KeyedCodec<>("Browse", Codec.STRING), + (entry, s) -> entry.browse = "true".equalsIgnoreCase(s), + entry -> entry.browse != null && entry.browse ? "true" : null + ) + .addField( + new KeyedCodec<>("BrowserSelect", Codec.STRING), + (entry, s) -> entry.browserSelect = "true".equalsIgnoreCase(s), + entry -> entry.browserSelect != null && entry.browserSelect ? "true" : null + ) + .addField( + new KeyedCodec<>("BrowserCancel", Codec.STRING), + (entry, s) -> entry.browserCancel = "true".equalsIgnoreCase(s), + entry -> entry.browserCancel != null && entry.browserCancel ? "true" : null + ) + .addField(new KeyedCodec<>("File", Codec.STRING), (entry, s) -> entry.file = s, entry -> entry.file) + .addField(new KeyedCodec<>("@SearchQuery", Codec.STRING), (entry, s) -> entry.searchQuery = s, entry -> entry.searchQuery) + .addField(new KeyedCodec<>("SearchResult", Codec.STRING), (entry, s) -> entry.searchResult = s, entry -> entry.searchResult) + .build(); + @Nullable + private String objPath; + @Nullable + private Integer height; + @Nullable + private Float scale; + @Nullable + private String sizeMode; + @Nullable + private String blockPattern; + @Nullable + private Boolean fillSolid; + @Nullable + private Boolean useMaterials; + @Nullable + private Boolean autoDetectTextures; + @Nullable + private String origin; + @Nullable + private String rotation; + @Nullable + private Boolean doImport; + @Nullable + private Boolean browse; + @Nullable + private Boolean browserSelect; + @Nullable + private Boolean browserCancel; + @Nullable + private String file; + @Nullable + private String searchQuery; + @Nullable + private String searchResult; + + public PageData() { + } + } + + private record WeightedBlock(int blockId, int weight) { + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/objimport/ObjParser.java b/src/com/hypixel/hytale/builtin/buildertools/objimport/ObjParser.java new file mode 100644 index 0000000..aa5b714 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/objimport/ObjParser.java @@ -0,0 +1,224 @@ +package com.hypixel.hytale.builtin.buildertools.objimport; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class ObjParser { + private ObjParser() { + } + + @Nonnull + public static ObjParser.ObjMesh parse(@Nonnull Path path) throws IOException, ObjParser.ObjParseException { + List vertices = new ArrayList<>(); + List uvCoordinates = new ArrayList<>(); + List faces = new ArrayList<>(); + List faceUvIndices = new ArrayList<>(); + List faceMaterials = new ArrayList<>(); + String mtlLib = null; + String currentMaterial = null; + + try (BufferedReader reader = Files.newBufferedReader(path)) { + int lineNum = 0; + + String line; + while ((line = reader.readLine()) != null) { + lineNum++; + line = line.trim(); + if (!line.isEmpty() && !line.startsWith("#")) { + String[] parts = line.split("\\s+"); + if (parts.length != 0) { + String var12 = parts[0]; + switch (var12) { + case "v": + parseVertex(parts, vertices, lineNum); + break; + case "vt": + parseUvCoordinate(parts, uvCoordinates, lineNum); + break; + case "f": + int faceCountBefore = faces.size(); + parseFace(parts, faces, faceUvIndices, uvCoordinates.size(), lineNum); + int facesAdded = faces.size() - faceCountBefore; + + for (int i = 0; i < facesAdded; i++) { + faceMaterials.add(currentMaterial); + } + break; + case "mtllib": + if (parts.length > 1) { + mtlLib = parts[1].trim(); + } + break; + case "usemtl": + if (parts.length > 1) { + currentMaterial = parts[1].trim(); + } + } + } + } + } + } + + if (vertices.isEmpty()) { + throw new ObjParser.ObjParseException("OBJ file contains no vertices"); + } else if (faces.isEmpty()) { + throw new ObjParser.ObjParseException("OBJ file contains no faces"); + } else { + return new ObjParser.ObjMesh(vertices, uvCoordinates, faces, faceUvIndices, faceMaterials, mtlLib); + } + } + + private static void parseVertex(String[] parts, List vertices, int lineNum) throws ObjParser.ObjParseException { + if (parts.length < 4) { + throw new ObjParser.ObjParseException("Invalid vertex at line " + lineNum + ": expected at least 3 coordinates"); + } else { + try { + float x = Float.parseFloat(parts[1]); + float y = Float.parseFloat(parts[2]); + float z = Float.parseFloat(parts[3]); + vertices.add(new float[]{x, y, z}); + } catch (NumberFormatException var6) { + throw new ObjParser.ObjParseException("Invalid vertex coordinates at line " + lineNum); + } + } + } + + private static void parseUvCoordinate(String[] parts, List uvCoordinates, int lineNum) throws ObjParser.ObjParseException { + if (parts.length < 3) { + throw new ObjParser.ObjParseException("Invalid UV coordinate at line " + lineNum + ": expected at least 2 values"); + } else { + try { + float u = Float.parseFloat(parts[1]); + float v = Float.parseFloat(parts[2]); + uvCoordinates.add(new float[]{u, v}); + } catch (NumberFormatException var5) { + throw new ObjParser.ObjParseException("Invalid UV coordinates at line " + lineNum); + } + } + } + + private static void parseFace(String[] parts, List faces, List faceUvIndices, int uvCount, int lineNum) throws ObjParser.ObjParseException { + if (parts.length < 4) { + throw new ObjParser.ObjParseException("Invalid face at line " + lineNum + ": expected at least 3 vertices"); + } else { + int[] vertexIndices = new int[parts.length - 1]; + int[] uvIndices = new int[parts.length - 1]; + boolean hasUvs = false; + + for (int i = 1; i < parts.length; i++) { + String vertexData = parts[i]; + String[] components = vertexData.split("/"); + + try { + int vIndex = Integer.parseInt(components[0]); + vertexIndices[i - 1] = vIndex > 0 ? vIndex - 1 : vIndex; + } catch (NumberFormatException var13) { + throw new ObjParser.ObjParseException("Invalid face vertex index at line " + lineNum); + } + + if (components.length >= 2 && !components[1].isEmpty()) { + try { + int uvIndex = Integer.parseInt(components[1]); + uvIndices[i - 1] = uvIndex > 0 ? uvIndex - 1 : uvIndex + uvCount; + hasUvs = true; + } catch (NumberFormatException var12) { + uvIndices[i - 1] = -1; + } + } else { + uvIndices[i - 1] = -1; + } + } + + if (vertexIndices.length == 3) { + faces.add(vertexIndices); + faceUvIndices.add(hasUvs ? uvIndices : null); + } else if (vertexIndices.length == 4) { + faces.add(new int[]{vertexIndices[0], vertexIndices[1], vertexIndices[2]}); + faces.add(new int[]{vertexIndices[0], vertexIndices[2], vertexIndices[3]}); + if (hasUvs) { + faceUvIndices.add(new int[]{uvIndices[0], uvIndices[1], uvIndices[2]}); + faceUvIndices.add(new int[]{uvIndices[0], uvIndices[2], uvIndices[3]}); + } else { + faceUvIndices.add(null); + faceUvIndices.add(null); + } + } else { + for (int i = 1; i < vertexIndices.length - 1; i++) { + faces.add(new int[]{vertexIndices[0], vertexIndices[i], vertexIndices[i + 1]}); + if (hasUvs) { + faceUvIndices.add(new int[]{uvIndices[0], uvIndices[i], uvIndices[i + 1]}); + } else { + faceUvIndices.add(null); + } + } + } + } + } + + public record ObjMesh( + List vertices, List uvCoordinates, List faces, List faceUvIndices, List faceMaterials, @Nullable String mtlLib + ) { + public float[] getBounds() { + float minX = Float.MAX_VALUE; + float minY = Float.MAX_VALUE; + float minZ = Float.MAX_VALUE; + float maxX = -Float.MAX_VALUE; + float maxY = -Float.MAX_VALUE; + float maxZ = -Float.MAX_VALUE; + + for (float[] v : this.vertices) { + minX = Math.min(minX, v[0]); + minY = Math.min(minY, v[1]); + minZ = Math.min(minZ, v[2]); + maxX = Math.max(maxX, v[0]); + maxY = Math.max(maxY, v[1]); + maxZ = Math.max(maxZ, v[2]); + } + + return new float[]{minX, minY, minZ, maxX, maxY, maxZ}; + } + + public float getHeight() { + float[] bounds = this.getBounds(); + return bounds[4] - bounds[1]; + } + + public boolean hasMaterials() { + return this.mtlLib != null && !this.faceMaterials.isEmpty() && this.faceMaterials.stream().anyMatch(m -> m != null); + } + + public boolean hasUvCoordinates() { + return !this.uvCoordinates.isEmpty() && this.faceUvIndices.stream().anyMatch(uv -> uv != null); + } + + public void transformZUpToYUp() { + for (float[] v : this.vertices) { + float y = v[1]; + float z = v[2]; + v[1] = z; + v[2] = -y; + } + } + + public void transformXUpToYUp() { + for (float[] v : this.vertices) { + float x = v[0]; + float y = v[1]; + v[0] = y; + v[1] = x; + } + } + } + + public static class ObjParseException extends Exception { + public ObjParseException(String message) { + super(message); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/objimport/TextureSampler.java b/src/com/hypixel/hytale/builtin/buildertools/objimport/TextureSampler.java new file mode 100644 index 0000000..80c1fd8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/objimport/TextureSampler.java @@ -0,0 +1,120 @@ +package com.hypixel.hytale.builtin.buildertools.objimport; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.imageio.ImageIO; + +public final class TextureSampler { + private static final Map textureCache = new HashMap<>(); + + private TextureSampler() { + } + + @Nullable + public static BufferedImage loadTexture(@Nonnull Path path) { + if (!Files.exists(path)) { + return null; + } else { + BufferedImage cached = textureCache.get(path); + if (cached != null) { + return cached; + } else { + try { + BufferedImage image = ImageIO.read(path.toFile()); + if (image != null) { + textureCache.put(path, image); + } + + return image; + } catch (IOException var3) { + return null; + } + } + } + } + + @Nonnull + public static int[] sampleAt(@Nonnull BufferedImage texture, float u, float v) { + u -= (float)Math.floor(u); + v -= (float)Math.floor(v); + v = 1.0F - v; + int width = texture.getWidth(); + int height = texture.getHeight(); + int x = Math.min((int)(u * width), width - 1); + int y = Math.min((int)(v * height), height - 1); + int rgb = texture.getRGB(x, y); + return new int[]{rgb >> 16 & 0xFF, rgb >> 8 & 0xFF, rgb & 0xFF}; + } + + public static int sampleAlphaAt(@Nonnull BufferedImage texture, float u, float v) { + if (!texture.getColorModel().hasAlpha()) { + return 255; + } else { + u -= (float)Math.floor(u); + v -= (float)Math.floor(v); + v = 1.0F - v; + int width = texture.getWidth(); + int height = texture.getHeight(); + int x = Math.min((int)(u * width), width - 1); + int y = Math.min((int)(v * height), height - 1); + int rgba = texture.getRGB(x, y); + return rgba >> 24 & 0xFF; + } + } + + public static void clearCache() { + textureCache.clear(); + } + + @Nullable + public static int[] getAverageColor(@Nonnull Path path) { + if (!Files.exists(path)) { + return null; + } else { + try { + BufferedImage image = ImageIO.read(path.toFile()); + if (image == null) { + return null; + } else { + long totalR = 0L; + long totalG = 0L; + long totalB = 0L; + int count = 0; + int width = image.getWidth(); + int height = image.getHeight(); + boolean hasAlpha = image.getColorModel().hasAlpha(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int rgba = image.getRGB(x, y); + if (hasAlpha) { + int alpha = rgba >> 24 & 0xFF; + if (alpha == 0) { + continue; + } + } + + int r = rgba >> 16 & 0xFF; + int g = rgba >> 8 & 0xFF; + int b = rgba & 0xFF; + totalR += r; + totalG += g; + totalB += b; + count++; + } + } + + return count == 0 ? null : new int[]{(int)(totalR / count), (int)(totalG / count), (int)(totalB / count)}; + } + } catch (IOException var18) { + return null; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabAnchor.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabAnchor.java new file mode 100644 index 0000000..c8bfbf6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabAnchor.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public class PrefabAnchor implements Component { + public static final PrefabAnchor INSTANCE = new PrefabAnchor(); + public static final BuilderCodec CODEC = BuilderCodec.builder(PrefabAnchor.class, () -> INSTANCE).build(); + + public static ComponentType getComponentType() { + return BuilderToolsPlugin.get().getPrefabAnchorComponentType(); + } + + private PrefabAnchor() { + } + + @Override + public Component clone() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabDirtySystems.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabDirtySystems.java new file mode 100644 index 0000000..2322406 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabDirtySystems.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.EntityEventSystem; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.event.events.ecs.BreakBlockEvent; +import com.hypixel.hytale.server.core.event.events.ecs.PlaceBlockEvent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class PrefabDirtySystems { + private PrefabDirtySystems() { + } + + private static void markDirtyAtPosition(@Nonnull Vector3i position) { + PrefabEditSessionManager prefabEditSessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + + for (Entry entry : prefabEditSessionManager.getActiveEditSessions().entrySet()) { + PrefabEditSession editSession = entry.getValue(); + editSession.markPrefabsDirtyAtPosition(position); + } + } + + public static class BlockBreakDirtySystem extends EntityEventSystem { + public BlockBreakDirtySystem() { + super(BreakBlockEvent.class); + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull BreakBlockEvent event + ) { + PrefabDirtySystems.markDirtyAtPosition(event.getTargetBlock()); + } + + @Nullable + @Override + public Query getQuery() { + return Archetype.empty(); + } + } + + public static class BlockPlaceDirtySystem extends EntityEventSystem { + public BlockPlaceDirtySystem() { + super(PlaceBlockEvent.class); + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull PlaceBlockEvent event + ) { + PrefabDirtySystems.markDirtyAtPosition(event.getTargetBlock()); + } + + @Nullable + @Override + public Query getQuery() { + return Archetype.empty(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabEditSession.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabEditSession.java new file mode 100644 index 0000000..21d9226 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabEditSession.java @@ -0,0 +1,268 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.math.codec.Vector3iArrayCodec; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolHideAnchors; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.PositionUtil; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.nio.file.Path; +import java.util.Map; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabEditSession implements Resource { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(PrefabEditSession.class, PrefabEditSession::new) + .append(new KeyedCodec<>("WorldName", Codec.STRING, true), (o, worldName) -> o.worldName = worldName, o -> o.worldName) + .add() + .append(new KeyedCodec<>("WorldArrivedFrom", Codec.UUID_STRING, true), (o, worldName) -> o.worldArrivedFrom = worldName, o -> o.worldArrivedFrom) + .add() + .append( + new KeyedCodec<>("TransformArrivedFrom", Transform.CODEC, true), + (o, positionArrivedFrom) -> o.transformArrivedFrom = positionArrivedFrom, + o -> o.transformArrivedFrom + ) + .add() + .append(new KeyedCodec<>("WorldCreatorUUID", Codec.UUID_STRING, true), (o, worldCreatorUuid) -> o.worldCreator = worldCreatorUuid, o -> o.worldCreator) + .add() + .append(new KeyedCodec<>("SpawnPoint", new Vector3iArrayCodec(), true), (o, spawnPoint) -> o.spawnPoint = spawnPoint, o -> o.spawnPoint) + .add() + .append( + new KeyedCodec<>("LoadedPrefabMetadata", new ArrayCodec<>(PrefabEditingMetadata.CODEC, PrefabEditingMetadata[]::new), false), + (editSession, prefabEditingMetadata) -> { + for (PrefabEditingMetadata prefabEditMetadata : prefabEditingMetadata) { + editSession.loadedPrefabMetadata.put(prefabEditMetadata.getUuid(), prefabEditMetadata); + } + }, + editSession -> editSession.loadedPrefabMetadata.values().toArray(PrefabEditingMetadata[]::new) + ) + .add() + .afterDecode(editSession -> { + PrefabEditSessionManager prefabEditSessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + prefabEditSessionManager.populateActiveEditSession(editSession.getWorldCreator(), editSession); + + for (PrefabEditingMetadata value : editSession.loadedPrefabMetadata.values()) { + prefabEditSessionManager.populatePrefabsBeingEdited(value.getPrefabPath()); + } + + prefabEditSessionManager.scheduleAnchorEntityRecreation(editSession); + }) + .build(); + private String worldName; + private UUID worldArrivedFrom; + @Nullable + private Transform transformArrivedFrom; + private UUID worldCreator; + @Nonnull + private final Map loadedPrefabMetadata = new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map selectedPrefab = new Object2ObjectOpenHashMap<>(); + @Nonnull + private Vector3i spawnPoint = new Vector3i(0, 0, 0); + + @Nonnull + public static ResourceType getResourceType() { + return BuilderToolsPlugin.get().getPrefabEditSessionResourceType(); + } + + private PrefabEditSession() { + } + + public PrefabEditSession(@Nonnull String worldName, @Nonnull UUID worldCreator, @Nonnull UUID worldArrivedFrom, @Nonnull Transform transformArrivedFrom) { + this.worldName = worldName; + this.worldCreator = worldCreator; + this.worldArrivedFrom = worldArrivedFrom; + this.transformArrivedFrom = transformArrivedFrom; + } + + public PrefabEditSession(@Nonnull PrefabEditSession other) { + this.worldName = other.worldName; + this.worldArrivedFrom = other.worldArrivedFrom; + this.transformArrivedFrom = other.transformArrivedFrom; + this.worldCreator = other.worldCreator; + this.spawnPoint = other.spawnPoint; + } + + public void addPrefab( + @Nonnull Path prefabPath, @Nonnull Vector3i minPoint, @Nonnull Vector3i maxPoint, @Nonnull Vector3i anchorPoint, @Nonnull Vector3i pastePosition + ) { + if (this.loadedPrefabMetadata.isEmpty()) { + this.spawnPoint.assign(maxPoint); + } + + PrefabEditingMetadata prefabEditingMetadata = new PrefabEditingMetadata( + prefabPath, minPoint, maxPoint, anchorPoint, pastePosition, Universe.get().getWorld(this.worldName) + ); + this.loadedPrefabMetadata.put(prefabEditingMetadata.getUuid(), prefabEditingMetadata); + } + + @Nullable + public PrefabEditingMetadata updatePrefabBounds(@Nonnull UUID prefab, @Nonnull Vector3i newMin, @Nonnull Vector3i newMax) { + PrefabEditingMetadata prefabEditingMetadata = this.loadedPrefabMetadata.get(prefab); + if (prefabEditingMetadata == null) { + return null; + } else { + prefabEditingMetadata.setMaxPoint(newMax); + prefabEditingMetadata.setMinPoint(newMin); + prefabEditingMetadata.setDirty(true); + return prefabEditingMetadata; + } + } + + public void setSelectedPrefab( + @Nonnull Ref ref, @Nonnull PrefabEditingMetadata prefabEditingMetadata, @Nonnull ComponentAccessor componentAccessor + ) { + UUIDComponent uuidComponent = componentAccessor.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + UUID playerUUID = uuidComponent.getUuid(); + if (this.selectedPrefab.get(playerUUID) != null && this.selectedPrefab.get(playerUUID).equals(prefabEditingMetadata.getUuid())) { + BlockSelection selection = BuilderToolsPlugin.getState(playerComponent, playerRefComponent).getSelection(); + if (selection != null + && prefabEditingMetadata.getMinPoint().equals(selection.getSelectionMin()) + && prefabEditingMetadata.getMaxPoint().equals(selection.getSelectionMax())) { + return; + } + } + + this.selectedPrefab.put(playerUUID, prefabEditingMetadata.getUuid()); + prefabEditingMetadata.sendAnchorHighlightingPacket(playerRefComponent.getPacketHandler()); + BuilderToolsPlugin.addToQueue( + playerComponent, + playerRefComponent, + (r, s, compAccess) -> s.select(prefabEditingMetadata.getMinPoint(), prefabEditingMetadata.getMaxPoint(), null, compAccess) + ); + } + + public void hidePrefabAnchors(@Nonnull PacketHandler packetHandler) { + packetHandler.writeNoCache(new BuilderToolHideAnchors()); + } + + @Nullable + public PrefabEditingMetadata getSelectedPrefab(@Nonnull UUID playerUuid) { + UUID prefabUuid = this.selectedPrefab.get(playerUuid); + return prefabUuid == null ? null : this.loadedPrefabMetadata.get(prefabUuid); + } + + public boolean clearSelectedPrefab(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + UUIDComponent uuidComponent = componentAccessor.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID playerUUID = uuidComponent.getUuid(); + if (this.selectedPrefab.remove(playerUUID) == null) { + return false; + } else { + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + this.hidePrefabAnchors(playerRefComponent.getPacketHandler()); + BuilderToolsPlugin.addToQueue(playerComponent, playerRefComponent, (r, s, compAccess) -> s.deselect(compAccess)); + return true; + } + } + + @Nonnull + public String getWorldName() { + return this.worldName; + } + + public UUID getWorldArrivedFrom() { + return this.worldArrivedFrom; + } + + @Nullable + public Transform getTransformArrivedFrom() { + return this.transformArrivedFrom; + } + + public UUID getWorldCreator() { + return this.worldCreator; + } + + @Nonnull + public Vector3i getSpawnPoint() { + return this.spawnPoint; + } + + @Nonnull + public Map getLoadedPrefabMetadata() { + return this.loadedPrefabMetadata; + } + + public void markPrefabsDirtyAtPosition(@Nonnull Vector3i position) { + for (PrefabEditingMetadata metadata : this.loadedPrefabMetadata.values()) { + if (metadata.isLocationWithinPrefabBoundingBox(position)) { + metadata.setDirty(true); + } + } + } + + public void markPrefabsDirtyInBounds(@Nonnull Vector3i min, @Nonnull Vector3i max) { + for (PrefabEditingMetadata metadata : this.loadedPrefabMetadata.values()) { + if (boundsIntersect(metadata.getMinPoint(), metadata.getMaxPoint(), min, max)) { + metadata.setDirty(true); + } + } + } + + private static boolean boundsIntersect(@Nonnull Vector3i aMin, @Nonnull Vector3i aMax, @Nonnull Vector3i bMin, @Nonnull Vector3i bMax) { + return aMin.x <= bMax.x && aMax.x >= bMin.x && aMin.y <= bMax.y && aMax.y >= bMin.y && aMin.z <= bMax.z && aMax.z >= bMin.z; + } + + @Nonnull + public MapMarker[] createPrefabMarkers() { + return this.loadedPrefabMetadata.values().stream().map(PrefabEditSession::createPrefabMarker).toArray(MapMarker[]::new); + } + + @Nonnull + public static MapMarker createPrefabMarker(@Nonnull PrefabEditingMetadata metadata) { + String fileName = metadata.getPrefabPath().getFileName().toString(); + String prefabName = fileName.replace(".prefab.json", ""); + return new MapMarker( + "prefab-" + metadata.getUuid(), + prefabName, + "Prefab.png", + PositionUtil.toTransformPacket(new Transform(metadata.getAnchorEntityPosition().toVector3d())), + null + ); + } + + @Nonnull + public PrefabEditSession clone() { + return new PrefabEditSession(this); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabEditSessionManager.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabEditSessionManager.java new file mode 100644 index 0000000..42b2585 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabEditSessionManager.java @@ -0,0 +1,1003 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor; + +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabAlignment; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabRowSplitMode; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabStackingAxis; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.WorldGenType; +import com.hypixel.hytale.common.map.IWeightedMap; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.EventRegistry; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.FastRandom; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.protocol.SavedMovementStates; +import com.hypixel.hytale.protocol.packets.inventory.SetActiveSlot; +import com.hypixel.hytale.server.core.Constants; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.core.asset.type.environment.config.WeatherForecast; +import com.hypixel.hytale.server.core.asset.util.ColorParseUtil; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.event.events.player.AddPlayerToWorldEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerReadyEvent; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabBufferUtil; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.IPrefabBuffer; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.spawn.GlobalSpawnProvider; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.worldgen.provider.FlatWorldGenProvider; +import com.hypixel.hytale.server.core.universe.world.worldgen.provider.VoidWorldGenProvider; +import com.hypixel.hytale.server.core.util.PrefabUtil; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabEditSessionManager { + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private static final Message MESSAGE_COMMANDS_PREFAB_EDIT_SESSION_MANAGER_EXISTING_EDIT_SESSION = Message.translation( + "server.commands.prefabeditsessionmanager.existingEditSession" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_SOMETHING_WENT_WRONG = Message.translation("server.commands.editprefab.somethingWentWrong"); + public static final float NOON_TIME = 0.5F; + public static final String DEFAULT_NEW_WORLD_ZERO_COORDINATE_BLOCK_NAME = "Rock_Stone"; + public static final String DEFAULT_ENVIRONMENT = "Zone1_Sunny"; + private static final String PREFAB_SELECTOR_TOOL_ID = "EditorTool_PrefabEditing_SelectPrefab"; + public static final String DEFAULT_CHUNK_ENVIRONMENT = "Env_Zone1_Plains"; + public static final String PREFAB_EDITING_WORLD_NAME_PREFIX = "prefabEditor-"; + @Nonnull + public static final Color DEFAULT_TINT = new Color((byte)91, (byte)-98, (byte)40); + private static final long PROGRESS_UPDATE_INTERVAL_NANOS = 100000000L; + public static final String DEFAULT_GRASS_TINT_HEX = "#5B9E28"; + @Nonnull + private final Map activeEditSessions = new Object2ObjectOpenHashMap<>(); + @Nonnull + private final HashSet prefabsBeingEdited = new HashSet<>(); + @Nonnull + private final Map inProgressTeleportations = new Object2ObjectOpenHashMap<>(); + @Nonnull + private final HashSet inProgressLoading = new HashSet<>(); + @Nonnull + private final HashSet cancelledLoading = new HashSet<>(); + + public PrefabEditSessionManager(@Nonnull JavaPlugin plugin) { + EventRegistry eventRegistry = plugin.getEventRegistry(); + eventRegistry.registerGlobal(AddPlayerToWorldEvent.class, this::onPlayerAddedToWorld); + eventRegistry.registerGlobal(PlayerReadyEvent.class, this::onPlayerReady); + } + + private void onPlayerReady(@Nonnull PlayerReadyEvent event) { + Ref playerRef = event.getPlayer().getReference(); + + assert playerRef != null && !playerRef.isValid(); + + Store store = playerRef.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + UUIDComponent uuidComponent = store.getComponent(playerRef, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID playerUUID = uuidComponent.getUuid(); + if (this.inProgressTeleportations.containsKey(playerUUID)) { + this.inProgressTeleportations.remove(playerUUID); + MovementStatesComponent movementStatesComponent = store.getComponent(playerRef, MovementStatesComponent.getComponentType()); + + assert movementStatesComponent != null; + + MovementStates movementStates = movementStatesComponent.getMovementStates(); + Player playerComponent = store.getComponent(playerRef, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.applyMovementStates(playerRef, new SavedMovementStates(true), movementStates, store); + PlayerRef playerRefComponent = store.getComponent(playerRef, PlayerRef.getComponentType()); + if (playerRefComponent != null) { + this.givePrefabSelectorTool(playerComponent, playerRefComponent); + } + } + }); + } + + private void givePrefabSelectorTool(@Nonnull Player playerComponent, @Nonnull PlayerRef playerRef) { + Inventory inventory = playerComponent.getInventory(); + ItemContainer hotbar = inventory.getHotbar(); + int hotbarSize = hotbar.getCapacity(); + + for (short slot = 0; slot < hotbarSize; slot++) { + ItemStack itemStack = hotbar.getItemStack(slot); + if (itemStack != null && !itemStack.isEmpty() && "EditorTool_PrefabEditing_SelectPrefab".equals(itemStack.getItemId())) { + inventory.setActiveHotbarSlot((byte)slot); + playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)slot)); + return; + } + } + + short emptySlot = -1; + + for (short slotx = 0; slotx < hotbarSize; slotx++) { + ItemStack itemStack = hotbar.getItemStack(slotx); + if (itemStack == null || itemStack.isEmpty()) { + emptySlot = slotx; + break; + } + } + + if (emptySlot == -1) { + emptySlot = 0; + } + + hotbar.setItemStackForSlot(emptySlot, new ItemStack("EditorTool_PrefabEditing_SelectPrefab")); + inventory.setActiveHotbarSlot((byte)emptySlot); + playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)emptySlot)); + } + + public void onPlayerAddedToWorld(@Nonnull AddPlayerToWorldEvent event) { + World world = event.getWorld(); + if (world.getName().startsWith("prefabEditor-")) { + world.execute(() -> { + Holder playerHolder = event.getHolder(); + UUIDComponent uuidComponent = playerHolder.getComponent(UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + this.inProgressTeleportations.put(uuidComponent.getUuid(), world.getWorldConfig().getUuid()); + }); + } + } + + public void updatePathOfLoadedPrefab(@Nonnull Path oldPath, @Nonnull Path newPath) { + this.prefabsBeingEdited.remove(oldPath); + this.prefabsBeingEdited.add(newPath); + } + + public boolean isEditingAPrefab(@Nonnull UUID playerUUID) { + return this.activeEditSessions.containsKey(playerUUID); + } + + public PrefabEditSession getPrefabEditSession(@Nonnull UUID playerUUID) { + return this.activeEditSessions.get(playerUUID); + } + + @Nonnull + public Map getActiveEditSessions() { + return this.activeEditSessions; + } + + void populateActiveEditSession(@Nonnull UUID playerUuid, @Nonnull PrefabEditSession editSession) { + this.activeEditSessions.put(playerUuid, editSession); + } + + void populatePrefabsBeingEdited(@Nonnull Path prefabPath) { + this.prefabsBeingEdited.add(prefabPath); + } + + void scheduleAnchorEntityRecreation(@Nonnull PrefabEditSession editSession) { + CompletableFuture.runAsync(() -> { + World world = Universe.get().getWorld(editSession.getWorldName()); + if (world != null) { + world.execute(() -> { + for (PrefabEditingMetadata metadata : editSession.getLoadedPrefabMetadata().values()) { + metadata.recreateAnchorEntity(world); + } + }); + } + }); + } + + public boolean hasInProgressLoading(@Nonnull UUID playerUuid) { + return this.inProgressLoading.contains(playerUuid); + } + + public void cancelLoading(@Nonnull UUID playerUuid) { + this.cancelledLoading.add(playerUuid); + } + + public boolean isLoadingCancelled(@Nonnull UUID playerUuid) { + return this.cancelledLoading.contains(playerUuid); + } + + public void clearLoadingState(@Nonnull UUID playerUuid) { + this.inProgressLoading.remove(playerUuid); + this.cancelledLoading.remove(playerUuid); + } + + @Nonnull + public CompletableFuture createEditSessionForNewPrefab( + @Nonnull Ref ref, + @Nonnull Player editor, + @Nonnull PrefabEditorCreationSettings settings, + @Nonnull ComponentAccessor componentAccessor + ) { + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + PrefabEditorCreationContext prefabEditorCreationContext = settings.finishProcessing(editor, playerRefComponent, true); + if (prefabEditorCreationContext == null) { + playerRefComponent.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_SOMETHING_WENT_WRONG); + return CompletableFuture.completedFuture(null); + } else { + return this.createEditSession(ref, prefabEditorCreationContext, true, componentAccessor); + } + } + + @Nullable + public CompletableFuture loadPrefabAndCreateEditSession( + @Nonnull Ref ref, + @Nonnull Player editor, + @Nonnull PrefabEditorCreationSettings settings, + @Nonnull ComponentAccessor componentAccessor + ) { + return this.loadPrefabAndCreateEditSession(ref, editor, settings, componentAccessor, null); + } + + @Nullable + public CompletableFuture loadPrefabAndCreateEditSession( + @Nonnull Ref ref, + @Nonnull Player editor, + @Nonnull PrefabEditorCreationSettings settings, + @Nonnull ComponentAccessor componentAccessor, + @Nullable Consumer progressCallback + ) { + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + UUID playerUuid = playerRefComponent.getUuid(); + if (this.inProgressLoading.contains(playerUuid)) { + PrefabLoadingState loadingState = new PrefabLoadingState(); + loadingState.addError("server.commands.editprefab.error.loadingInProgress"); + this.notifyProgress(progressCallback, loadingState); + playerRefComponent.sendMessage(Message.translation("server.commands.editprefab.error.loadingInProgress")); + return null; + } else { + this.inProgressLoading.add(playerUuid); + this.cancelledLoading.remove(playerUuid); + PrefabLoadingState loadingState = new PrefabLoadingState(); + loadingState.setPhase(PrefabLoadingState.Phase.INITIALIZING); + this.notifyProgress(progressCallback, loadingState); + PrefabEditorCreationContext prefabEditorCreationContext = settings.finishProcessing(editor, playerRefComponent, false); + if (prefabEditorCreationContext == null) { + loadingState.addError("server.commands.editprefab.error.processingFailed"); + this.notifyProgress(progressCallback, loadingState); + playerRefComponent.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_SOMETHING_WENT_WRONG); + return null; + } else if (prefabEditorCreationContext.getPrefabPaths().isEmpty()) { + loadingState.addError("server.commands.editprefab.error.noPrefabsFound"); + this.notifyProgress(progressCallback, loadingState); + playerRefComponent.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_SOMETHING_WENT_WRONG); + return null; + } else { + loadingState.setTotalPrefabs(prefabEditorCreationContext.getPrefabPaths().size()); + this.notifyProgress(progressCallback, loadingState); + return this.createEditSession(ref, prefabEditorCreationContext, false, componentAccessor, loadingState, progressCallback); + } + } + } + + private void notifyProgress(@Nullable Consumer progressCallback, @Nonnull PrefabLoadingState loadingState) { + if (progressCallback != null) { + PrefabLoadingState.Phase phase = loadingState.getCurrentPhase(); + if (phase != PrefabLoadingState.Phase.LOADING_PREFABS && phase != PrefabLoadingState.Phase.PASTING_PREFABS) { + progressCallback.accept(loadingState); + } else { + long now = System.nanoTime(); + if (now - loadingState.getLastNotifyTimeNanos() >= 100000000L) { + loadingState.setLastNotifyTimeNanos(now); + progressCallback.accept(loadingState); + } + } + } + } + + @Nonnull + private CompletableFuture createEditSession( + @Nonnull Ref ref, + @Nonnull PrefabEditorCreationContext context, + boolean createNewPrefab, + @Nonnull ComponentAccessor componentAccessor + ) { + return this.createEditSession(ref, context, createNewPrefab, componentAccessor, null, null); + } + + @Nonnull + private CompletableFuture createEditSession( + @Nonnull Ref ref, + @Nonnull PrefabEditorCreationContext context, + boolean createNewPrefab, + @Nonnull ComponentAccessor componentAccessor, + @Nullable PrefabLoadingState loadingState, + @Nullable Consumer progressCallback + ) { + World sourceWorld = componentAccessor.getExternalData().getWorld(); + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + UUID playerUUID = playerRefComponent.getUuid(); + if (this.activeEditSessions.containsKey(playerUUID)) { + if (loadingState != null) { + loadingState.addError("server.commands.editprefab.error.existingSession"); + this.notifyProgress(progressCallback, loadingState); + } + + playerRefComponent.sendMessage(MESSAGE_COMMANDS_PREFAB_EDIT_SESSION_MANAGER_EXISTING_EDIT_SESSION); + return CompletableFuture.completedFuture(null); + } else { + for (Path prefabPath : context.getPrefabPaths()) { + if (this.prefabsBeingEdited.contains(prefabPath)) { + if (loadingState != null) { + loadingState.addError("server.commands.editprefab.error.alreadyBeingEdited", prefabPath.toString()); + this.notifyProgress(progressCallback, loadingState); + } + + playerRefComponent.sendMessage( + Message.translation("server.commands.prefabeditsessionmanager.alreadyBeingEdited").param("path", prefabPath.toString()) + ); + return CompletableFuture.completedFuture(null); + } + } + + if (loadingState != null) { + loadingState.setPhase(PrefabLoadingState.Phase.CREATING_WORLD); + this.notifyProgress(progressCallback, loadingState); + } + + WorldConfig config = new WorldConfig(); + boolean enableTicking = context.isWorldTickingEnabled(); + config.setBlockTicking(enableTicking); + config.setSpawningNPC(false); + config.setIsSpawnMarkersEnabled(false); + config.setObjectiveMarkersEnabled(false); + config.setGameMode(GameMode.Creative); + config.setDeleteOnRemove(true); + config.setUuid(UUID.randomUUID()); + config.setGameTimePaused(true); + config.setIsAllNPCFrozen(true); + config.setSavingPlayers(true); + config.setCanSaveChunks(true); + config.setTicking(enableTicking); + config.setForcedWeather(this.getWeatherFromEnvironment(context.getEnvironment())); + String worldName = this.getWorldName(context); + + try { + Files.createDirectories(this.getSavePath(context)); + } catch (IOException var17) { + if (loadingState != null) { + loadingState.addError("server.commands.editprefab.error.createDirectoryFailed", var17.getMessage()); + this.notifyProgress(progressCallback, loadingState); + } + + playerRefComponent.sendMessage(Message.translation("server.commands.instances.createDirectory.failed").param("errormsg", var17.getMessage())); + return CompletableFuture.completedFuture(null); + } + + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Transform transform = transformComponent.getTransform().clone(); + PrefabEditSession prefabEditSession = new PrefabEditSession(worldName, playerUUID, sourceWorld.getWorldConfig().getUuid(), transform); + CompletableFuture future; + if (createNewPrefab) { + future = this.getPrefabCreatingCompletableFuture(context, prefabEditSession, config); + } else { + future = this.getPrefabLoadingCompletableFuture(context, prefabEditSession, config, loadingState, progressCallback, playerUUID); + } + + if (future == null) { + if (loadingState != null) { + loadingState.addError("server.commands.editprefab.error.loadFailed"); + this.notifyProgress(progressCallback, loadingState); + } + + return CompletableFuture.completedFuture(null); + } else { + return future.exceptionally( + throwable -> { + if (this.isLoadingCancelled(playerUUID)) { + return null; + } else { + LOGGER.at(Level.SEVERE).withCause(throwable).log("Error occurred during prefab editor session creation"); + if (loadingState != null) { + loadingState.addError("server.commands.editprefab.error.exception", throwable.getMessage()); + this.notifyProgress(progressCallback, loadingState); + } + + playerRefComponent.sendMessage( + Message.translation("server.commands.editprefab.error.exception") + .param("details", throwable.getMessage() != null ? throwable.getMessage() : "Unknown error") + ); + return null; + } + } + ) + .thenAcceptAsync( + targetWorld -> { + if (!this.isLoadingCancelled(playerUUID)) { + if (targetWorld != null) { + if (loadingState != null) { + loadingState.setPhase(PrefabLoadingState.Phase.FINALIZING); + this.notifyProgress(progressCallback, loadingState); + } + + Vector3i spawnPoint = prefabEditSession.getSpawnPoint(); + targetWorld.getWorldConfig().setSpawnProvider(new GlobalSpawnProvider(new Transform(spawnPoint))); + CompletableFuture.runAsync( + () -> targetWorld.getEntityStore().getStore().replaceResource(PrefabEditSession.getResourceType(), prefabEditSession), + targetWorld + ); + CompletableFuture.runAsync(() -> { + Teleport teleportComponent = new Teleport(targetWorld, new Transform(spawnPoint)); + componentAccessor.putComponent(ref, Teleport.getComponentType(), teleportComponent); + }, sourceWorld); + } + } + } + ) + .thenRun( + () -> { + if (!this.isLoadingCancelled(playerUUID)) { + this.prefabsBeingEdited.addAll(context.getPrefabPaths()); + this.activeEditSessions.put(playerUUID, prefabEditSession); + if (loadingState != null) { + loadingState.markComplete(); + this.notifyProgress(progressCallback, loadingState); + } + + playerRefComponent.sendMessage( + Message.translation("server.commands.prefabeditsessionmanager.success." + (createNewPrefab ? "new" : "load")) + ); + } + } + ) + .whenComplete((result, throwable) -> this.inProgressLoading.remove(playerUUID)); + } + } + } + + @Nonnull + private CompletableFuture getWorldCreatingFuture(@Nonnull PrefabEditorCreationContext context, @Nonnull WorldConfig config) { + return Universe.get().makeWorld(this.getWorldName(context), this.getSavePath(context), config, true); + } + + @Nonnull + private String getWorldName(@Nonnull PrefabEditorCreationContext context) { + return "prefabEditor-" + context.getEditorRef().getUsername(); + } + + @Nonnull + private String getWeatherFromEnvironment(@Nullable String environmentId) { + if (environmentId != null && !environmentId.isEmpty()) { + Environment environment = Environment.getAssetMap().getAsset(environmentId); + if (environment == null) { + return "Zone1_Sunny"; + } else { + IWeightedMap forecast = environment.getWeatherForecast(12); + if (forecast != null && forecast.size() != 0) { + String[] bestWeatherId = new String[]{null}; + double[] highestWeight = new double[]{Double.NEGATIVE_INFINITY}; + forecast.forEachEntry((weatherForecast, weight) -> { + if (weight > highestWeight[0]) { + highestWeight[0] = weight; + bestWeatherId[0] = weatherForecast.getWeatherId(); + } + }); + return bestWeatherId[0] != null ? bestWeatherId[0] : "Zone1_Sunny"; + } else { + return "Zone1_Sunny"; + } + } + } else { + return "Zone1_Sunny"; + } + } + + @Nonnull + private Path getSavePath(@Nonnull PrefabEditorCreationContext context) { + return Constants.UNIVERSE_PATH.resolve("worlds").resolve(this.getWorldName(context)); + } + + private void applyWorldGenWorldConfig(@Nonnull PrefabEditorCreationContext context, int yLevelToPastePrefabsAt, @Nonnull WorldConfig worldConfig) { + String environment = context.getEnvironment() != null ? context.getEnvironment() : "Env_Zone1_Plains"; + Color tint = DEFAULT_TINT; + if (context.getGrassTint() != null && !context.getGrassTint().isEmpty()) { + Color parsed = ColorParseUtil.parseColor(context.getGrassTint()); + if (parsed != null) { + tint = parsed; + } + } + + if (context.getWorldGenType().equals(WorldGenType.FLAT)) { + int yLevelForFlatWorldExclusive = Math.max(1, yLevelToPastePrefabsAt - context.getBlocksAboveSurface()); + FlatWorldGenProvider.Layer topLayer = new FlatWorldGenProvider.Layer(); + topLayer.blockType = "Soil_Grass"; + topLayer.to = yLevelForFlatWorldExclusive; + topLayer.from = yLevelForFlatWorldExclusive - 1; + topLayer.environment = environment; + FlatWorldGenProvider.Layer airLayer = new FlatWorldGenProvider.Layer(); + airLayer.blockType = "Empty"; + airLayer.to = 320; + airLayer.from = yLevelForFlatWorldExclusive; + airLayer.environment = environment; + if (yLevelForFlatWorldExclusive - 2 >= 0) { + FlatWorldGenProvider.Layer bottomLayer = new FlatWorldGenProvider.Layer(); + bottomLayer.blockType = "Soil_Clay"; + bottomLayer.to = yLevelForFlatWorldExclusive - 1; + bottomLayer.from = 0; + bottomLayer.environment = environment; + worldConfig.setWorldGenProvider(new FlatWorldGenProvider(tint, new FlatWorldGenProvider.Layer[]{airLayer, topLayer, bottomLayer})); + } else { + worldConfig.setWorldGenProvider(new FlatWorldGenProvider(tint, new FlatWorldGenProvider.Layer[]{airLayer, topLayer})); + } + } else { + worldConfig.setWorldGenProvider(new VoidWorldGenProvider(tint, environment)); + } + } + + @Nonnull + private CompletableFuture getPrefabCreatingCompletableFuture( + @Nonnull PrefabEditorCreationContext context, @Nonnull PrefabEditSession editSession, @Nonnull WorldConfig worldConfig + ) { + this.applyWorldGenWorldConfig(context, context.getPasteLevelGoal() - 1, worldConfig); + return this.getWorldCreatingFuture(context, worldConfig) + .thenCompose( + world -> CompletableFuture.supplyAsync( + () -> { + Vector3i pastePosition = new Vector3i(0, context.getPasteLevelGoal(), 0); + Vector3i anchorPosition = pastePosition.clone(); + editSession.addPrefab( + context.getPrefabPaths().getFirst(), + new Vector3i(-1, context.getPasteLevelGoal() - 1, -1), + new Vector3i(1, context.getPasteLevelGoal() + 1, 1), + anchorPosition, + pastePosition + ); + Store store = world.getEntityStore().getStore(); + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + worldTimeResource.setDayTime(0.5, world, store); + world.setBlock(0, context.getPasteLevelGoal(), 0, "Rock_Stone"); + return world; + }, + world + ) + ); + } + + @Nullable + private CompletableFuture getPrefabLoadingCompletableFuture( + @Nonnull PrefabEditorCreationContext context, + @Nonnull PrefabEditSession editSession, + @Nonnull WorldConfig worldConfig, + @Nullable PrefabLoadingState loadingState, + @Nullable Consumer progressCallback, + @Nonnull UUID playerUuid + ) { + CompletableFuture[] initializationFutures = new CompletableFuture[context.getPrefabPaths().size()]; + if (loadingState != null) { + loadingState.setPhase(PrefabLoadingState.Phase.LOADING_PREFABS); + this.notifyProgress(progressCallback, loadingState); + } + + for (int i = 0; i < context.getPrefabPaths().size(); i++) { + Path prefabPath = context.getPrefabPaths().get(i); + CompletableFuture prefabLoadingFuture = this.getPrefabBuffer(context.getEditor(), prefabPath); + if (prefabLoadingFuture == null) { + if (loadingState != null) { + loadingState.addError("server.commands.editprefab.error.prefabLoadFailed", prefabPath.toString()); + this.notifyProgress(progressCallback, loadingState); + } + + return null; + } + + initializationFutures[i] = prefabLoadingFuture.thenApply(buffer -> { + if (loadingState != null) { + loadingState.onPrefabLoaded(prefabPath); + this.notifyProgress(progressCallback, loadingState); + } + + return (IPrefabBuffer)buffer; + }); + } + + return CompletableFuture.allOf(initializationFutures) + .thenApply(unused -> { + if (this.isLoadingCancelled(playerUuid)) { + return null; + } else { + List prefabAccessors = new ObjectArrayList<>(initializationFutures.length); + int heightOfTallestPrefab = 0; + + for (CompletableFuture initializationFuture : initializationFutures) { + IPrefabBuffer prefabAccessor = (IPrefabBuffer)initializationFuture.join(); + prefabAccessors.add(prefabAccessor); + if (context.loadChildPrefabs()) { + for (PrefabBuffer.ChildPrefab var18 : prefabAccessor.getChildPrefabs()) { + ; + } + } + + int prefabHeight = Math.abs(prefabAccessor.getMaxY() - prefabAccessor.getMinY()); + if (prefabHeight > heightOfTallestPrefab) { + heightOfTallestPrefab = prefabHeight; + } + } + + int yLevelToPastePrefabsAt = this.getAmountOfBlocksBelowPrefab(heightOfTallestPrefab, context.getPasteLevelGoal()); + this.applyWorldGenWorldConfig(context, yLevelToPastePrefabsAt, worldConfig); + if (loadingState != null) { + loadingState.setPhase(PrefabLoadingState.Phase.PASTING_PREFABS); + this.notifyProgress(progressCallback, loadingState); + } + + if (this.isLoadingCancelled(playerUuid)) { + return null; + } else { + String worldName = this.getWorldName(context); + if (Universe.get().getWorld(worldName) != null) { + LOGGER.at(Level.WARNING).log("Aborting prefab editor creation for %s: world '%s' already exists", playerUuid, worldName); + return null; + } else { + return new Tri<>(prefabAccessors, yLevelToPastePrefabsAt, this.getWorldCreatingFuture(context, worldConfig).join()); + } + } + } + }) + .thenCompose( + passedData -> passedData == null + ? CompletableFuture.completedFuture(null) + : CompletableFuture.supplyAsync( + () -> { + if (this.isLoadingCancelled(playerUuid)) { + return null; + } else { + World world = (World)passedData.getRight(); + int yLevelToPastePrefabsAt = (Integer)passedData.getMiddle(); + List prefabAccessors = (List)passedData.getLeft(); + if (world != null && world.isAlive()) { + Store store = world.getEntityStore().getStore(); + int[] rowGroupIndices = this.calculateRowGroups(context, prefabAccessors.size()); + IntArrayList rowDepths = new IntArrayList(); + int currentRowGroup = -1; + int currentRowIndex = -1; + + for (int ix = 0; ix < prefabAccessors.size(); ix++) { + IPrefabBuffer prefabAccessor = prefabAccessors.get(ix); + int rowGroup = rowGroupIndices[ix]; + if (rowGroup != currentRowGroup) { + currentRowGroup = rowGroup; + currentRowIndex++; + rowDepths.add(0); + } + + int depth; + if (context.getStackingAxis().equals(PrefabStackingAxis.X)) { + depth = prefabAccessor.getMaxZ() - prefabAccessor.getMinZ(); + } else { + depth = prefabAccessor.getMaxX() - prefabAccessor.getMinX(); + } + + rowDepths.set(currentRowIndex, Math.max(rowDepths.getInt(currentRowIndex), depth)); + } + + int[] rowStarts = new int[rowDepths.size()]; + int cumulativeDepth = 0; + + for (int r = 0; r < rowDepths.size(); r++) { + rowStarts[r] = cumulativeDepth; + cumulativeDepth += rowDepths.getInt(r) + context.getBlocksBetweenEachPrefab() + 1; + } + + currentRowGroup = -1; + currentRowIndex = -1; + int lineOffset = 0; + + for (int ixx = 0; ixx < prefabAccessors.size(); ixx++) { + if (this.isLoadingCancelled(playerUuid) || !world.isAlive()) { + return null; + } + + IPrefabBuffer prefabAccessorx = prefabAccessors.get(ixx); + Path prefabPathx = context.getPrefabPaths().get(ixx); + int rowGroupx = rowGroupIndices[ixx]; + if (rowGroupx != currentRowGroup) { + currentRowGroup = rowGroupx; + currentRowIndex++; + lineOffset = 0; + } + + int rowOffset = rowStarts[currentRowIndex]; + int prefabXSize = prefabAccessorx.getMaxX() - prefabAccessorx.getMinX(); + int prefabZSize = prefabAccessorx.getMaxZ() - prefabAccessorx.getMinZ(); + Vector3i pastePosition; + if (context.getAlignment().equals(PrefabAlignment.ZERO)) { + pastePosition = new Vector3i(0, yLevelToPastePrefabsAt, 0); + pastePosition.subtract( + Math.min(prefabAccessorx.getMinX(), 0), prefabAccessorx.getMinY(), Math.min(prefabAccessorx.getMinZ(), 0) + ); + if (context.getStackingAxis().equals(PrefabStackingAxis.X)) { + pastePosition.add(lineOffset, 0, rowOffset); + lineOffset += prefabXSize + context.getBlocksBetweenEachPrefab() + 1; + } else { + pastePosition.add(rowOffset, 0, lineOffset); + lineOffset += prefabZSize + context.getBlocksBetweenEachPrefab() + 1; + } + } else { + pastePosition = new Vector3i(0, yLevelToPastePrefabsAt - Math.min(prefabAccessorx.getMinY(), 0), 0); + if (context.getStackingAxis().equals(PrefabStackingAxis.X)) { + int xPos = lineOffset + Math.abs(Math.min(prefabAccessorx.getMinX(), 0)); + int zPos = rowOffset + Math.abs(Math.min(prefabAccessorx.getMinZ(), 0)); + pastePosition.add(xPos, 0, zPos); + lineOffset += prefabXSize + context.getBlocksBetweenEachPrefab() + 1; + } else { + int xPos = rowOffset + Math.abs(Math.min(prefabAccessorx.getMinX(), 0)); + int zPos = lineOffset + Math.abs(Math.min(prefabAccessorx.getMinZ(), 0)); + pastePosition.add(xPos, 0, zPos); + lineOffset += prefabZSize + context.getBlocksBetweenEachPrefab() + 1; + } + } + + Vector3i minPoint = new Vector3i( + pastePosition.x + prefabAccessorx.getMinX(), + pastePosition.y + prefabAccessorx.getMinY(), + pastePosition.z + prefabAccessorx.getMinZ() + ); + Vector3i maxPoint = new Vector3i( + pastePosition.x + prefabAccessorx.getMaxX(), + pastePosition.y + prefabAccessorx.getMaxY(), + pastePosition.z + prefabAccessorx.getMaxZ() + ); + Vector3i anchorPosition = new Vector3i( + pastePosition.x + prefabAccessorx.getAnchorX(), + pastePosition.y + prefabAccessorx.getAnchorY(), + pastePosition.z + prefabAccessorx.getAnchorZ() + ); + + try { + PrefabUtil.paste( + prefabAccessorx, + world, + pastePosition, + Rotation.None, + true, + new FastRandom(), + 0, + true, + false, + context.shouldLoadEntities(), + store + ); + editSession.addPrefab(prefabPathx, minPoint, maxPoint, anchorPosition, pastePosition.clone()); + if (loadingState != null) { + loadingState.onPrefabPasted(prefabPathx); + this.notifyProgress(progressCallback, loadingState); + } + } catch (Exception var30) { + if (!this.isLoadingCancelled(playerUuid) && world.isAlive()) { + LOGGER.at(Level.SEVERE).withCause(var30).log("Error pasting prefab: %s", prefabPathx); + if (loadingState != null) { + loadingState.addError( + "server.commands.editprefab.error.pasteFailed", prefabPathx.getFileName().toString() + ": " + var30.getMessage() + ); + this.notifyProgress(progressCallback, loadingState); + } + + throw var30; + } + + return null; + } + } + + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + worldTimeResource.setDayTime(0.5, world, store); + return world; + } else { + return null; + } + } + }, + passedData.getRight() + ) + ); + } + + @Nonnull + private int[] calculateRowGroups(@Nonnull PrefabEditorCreationContext context, int prefabCount) { + int[] rowGroups = new int[prefabCount]; + PrefabRowSplitMode rowSplitMode = context.getRowSplitMode(); + List prefabPaths = context.getPrefabPaths(); + if (rowSplitMode != PrefabRowSplitMode.NONE && prefabCount != 0) { + if (rowSplitMode == PrefabRowSplitMode.BY_SPECIFIED_FOLDER) { + List unprocessedPaths = context.getUnprocessedPrefabPaths(); + Path rootPath = context.getPrefabRootDirectory().getPrefabPath(); + int currentGroup = 0; + int prefabIndex = 0; + + for (String unprocessedPath : unprocessedPaths) { + for (Path resolvedPath = rootPath.resolve(unprocessedPath.replace('/', File.separatorChar).replace('\\', File.separatorChar)); + prefabIndex < prefabCount; + prefabIndex++ + ) { + Path prefabPath = prefabPaths.get(prefabIndex); + if (!prefabPath.startsWith(resolvedPath) + && ( + !unprocessedPath.endsWith("/") && !unprocessedPath.endsWith("\\") + ? !prefabPath.equals(resolvedPath) + : !prefabPath.startsWith(resolvedPath) + )) { + break; + } + + rowGroups[prefabIndex] = currentGroup; + } + + currentGroup++; + } + } else if (rowSplitMode == PrefabRowSplitMode.BY_ALL_SUBFOLDERS) { + Object2ObjectOpenHashMap parentDirToGroup = new Object2ObjectOpenHashMap<>(); + int nextGroup = 0; + + for (int i = 0; i < prefabCount; i++) { + Path prefabPath = prefabPaths.get(i); + Path parentDir = prefabPath.getParent(); + if (parentDir != null) { + Integer group = parentDirToGroup.get(parentDir); + if (group == null) { + parentDirToGroup.put(parentDir, nextGroup); + rowGroups[i] = nextGroup++; + } else { + rowGroups[i] = group; + } + } else { + rowGroups[i] = 0; + } + } + } + + return rowGroups; + } else { + return rowGroups; + } + } + + private int getAmountOfBlocksBelowPrefab(int prefabHeight, int desiredYLevel) { + if (desiredYLevel < 0) { + throw new IllegalArgumentException("Cannot have a negative y level for pasting prefabs"); + } else if (desiredYLevel >= 320) { + throw new IllegalArgumentException("Cannot paste above or at the world height"); + } else { + return Math.min(desiredYLevel, 320 - prefabHeight); + } + } + + @Nullable + public CompletableFuture exitEditSession( + @Nonnull Ref ref, @Nonnull World world, @Nonnull PlayerRef playerRef, @Nonnull ComponentAccessor componentAccessor + ) { + PrefabEditSession prefabEditSession = this.activeEditSessions.get(playerRef.getUuid()); + if (prefabEditSession == null) { + return null; + } else { + prefabEditSession.hidePrefabAnchors(playerRef.getPacketHandler()); + World returnWorld = Universe.get().getWorld(prefabEditSession.getWorldArrivedFrom()); + Transform returnLocation = prefabEditSession.getTransformArrivedFrom(); + if (returnWorld == null || returnLocation == null) { + LOGGER.at(Level.WARNING) + .log( + "Prefab editor exit fallback triggered for player %s: returnWorld=%s (worldArrivedFrom=%s), returnLocation=%s. Using default world spawn.", + playerRef.getUuid(), + returnWorld != null ? returnWorld.getName() : "null", + prefabEditSession.getWorldArrivedFrom(), + returnLocation + ); + returnWorld = Universe.get().getDefaultWorld(); + returnLocation = returnWorld.getWorldConfig().getSpawnProvider().getSpawnPoint(ref, componentAccessor); + } + + World finalReturnWorld = returnWorld; + Transform finalReturnLocation = returnLocation; + return CompletableFuture.runAsync( + () -> componentAccessor.putComponent(ref, Teleport.getComponentType(), new Teleport(finalReturnWorld, finalReturnLocation)), world + ) + .thenRunAsync(() -> { + World worldToRemove = Universe.get().getWorld(prefabEditSession.getWorldName()); + if (worldToRemove != null) { + Universe.get().removeWorld(prefabEditSession.getWorldName()); + } + + for (PrefabEditingMetadata prefab : prefabEditSession.getLoadedPrefabMetadata().values()) { + this.prefabsBeingEdited.remove(prefab.getPrefabPath()); + } + + this.activeEditSessions.remove(playerRef.getUuid()); + }); + } + } + + @Nonnull + public CompletableFuture cleanupCancelledSession( + @Nonnull UUID playerUuid, @Nonnull String worldName, @Nullable Consumer progressCallback + ) { + this.cancelLoading(playerUuid); + PrefabLoadingState loadingState = new PrefabLoadingState(); + loadingState.setPhase(PrefabLoadingState.Phase.CANCELLING); + this.notifyProgress(progressCallback, loadingState); + return CompletableFuture.runAsync(() -> { + World world = Universe.get().getWorld(worldName); + if (world != null) { + loadingState.setPhase(PrefabLoadingState.Phase.SHUTTING_DOWN_WORLD); + this.notifyProgress(progressCallback, loadingState); + world.getWorldConfig().setDeleteOnRemove(true); + loadingState.setPhase(PrefabLoadingState.Phase.DELETING_WORLD); + this.notifyProgress(progressCallback, loadingState); + Universe.get().removeWorld(worldName); + } + + PrefabEditSession session = this.activeEditSessions.remove(playerUuid); + if (session != null) { + for (PrefabEditingMetadata prefab : session.getLoadedPrefabMetadata().values()) { + this.prefabsBeingEdited.remove(prefab.getPrefabPath()); + } + } + + this.inProgressLoading.remove(playerUuid); + loadingState.setPhase(PrefabLoadingState.Phase.SHUTDOWN_COMPLETE); + this.notifyProgress(progressCallback, loadingState); + }); + } + + @Nonnull + public CompletableFuture cleanupCancelledSession(@Nonnull UUID playerUuid, @Nonnull String worldName) { + return this.cleanupCancelledSession(playerUuid, worldName, null); + } + + @Nullable + private CompletableFuture getPrefabBuffer(@Nonnull CommandSender sender, @Nonnull Path path) { + if (!Files.exists(path)) { + sender.sendMessage(Message.translation("server.commands.editprefab.prefabNotFound").param("name", path.toString())); + return null; + } else { + return CompletableFuture.supplyAsync(() -> PrefabBufferUtil.getCached(path)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabEditingMetadata.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabEditingMetadata.java new file mode 100644 index 0000000..11c6745 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabEditingMetadata.java @@ -0,0 +1,206 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.codec.Vector3iArrayCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolShowAnchor; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.BlockEntity; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.modules.entity.component.EntityScaleComponent; +import com.hypixel.hytale.server.core.modules.entity.component.Intangible; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabEditingMetadata { + private static final float PREFAB_ANCHOR_ENTITY_SCALE = 2.1F; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(PrefabEditingMetadata.class, PrefabEditingMetadata::new) + .append(new KeyedCodec<>("Path", Codec.PATH), (o, path) -> o.prefabPath = path, o -> o.prefabPath) + .add() + .append(new KeyedCodec<>("MinPoint", new Vector3iArrayCodec()), (o, minPoint) -> o.minPoint = minPoint, o -> o.minPoint) + .add() + .append(new KeyedCodec<>("MaxPoint", new Vector3iArrayCodec()), (o, maxPoint) -> o.maxPoint = maxPoint, o -> o.maxPoint) + .add() + .append(new KeyedCodec<>("AnchorPoint", new Vector3iArrayCodec()), (o, anchorPoint) -> o.anchorPoint = anchorPoint, o -> o.anchorPoint) + .add() + .append(new KeyedCodec<>("PastePosition", new Vector3iArrayCodec()), (o, pastePosition) -> o.pastePosition = pastePosition, o -> o.pastePosition) + .add() + .append( + new KeyedCodec<>("AnchorEntityUuid", Codec.UUID_STRING, false), + (o, anchorEntityUuid) -> o.anchorEntityUuid = anchorEntityUuid, + o -> o.anchorEntityUuid + ) + .add() + .append( + new KeyedCodec<>("AnchorEntityPosition", new Vector3iArrayCodec(), false), + (o, anchorEntityPosition) -> o.anchorEntityPosition = anchorEntityPosition, + o -> o.anchorEntityPosition + ) + .add() + .append(new KeyedCodec<>("Uuid", Codec.UUID_STRING), (o, uuid) -> o.uuid = uuid, o -> o.uuid) + .add() + .append(new KeyedCodec<>("Dirty", Codec.BOOLEAN, true), (o, dirty) -> o.dirty = dirty, o -> o.dirty) + .add() + .build(); + private UUID uuid; + private Path prefabPath; + private Vector3i minPoint; + private Vector3i maxPoint; + private Vector3i anchorPoint; + private Vector3i pastePosition; + @Nullable + private UUID anchorEntityUuid; + private Vector3i anchorEntityPosition; + private Vector3i originalFileAnchor; + private boolean dirty = false; + + private PrefabEditingMetadata() { + } + + public PrefabEditingMetadata( + @Nonnull Path prefabPath, + @Nonnull Vector3i minPoint, + @Nonnull Vector3i maxPoint, + @Nonnull Vector3i anchorPoint, + @Nonnull Vector3i pastePosition, + @Nonnull World world + ) { + this.prefabPath = prefabPath; + this.minPoint = minPoint; + this.maxPoint = maxPoint; + if (minPoint.x > maxPoint.x) { + throw new IllegalStateException("minX must be less than or equal to maxX: " + prefabPath); + } else if (minPoint.y > maxPoint.y) { + throw new IllegalStateException("minY must be less than or equal to maxY: " + prefabPath); + } else if (minPoint.z > maxPoint.z) { + throw new IllegalStateException("minZ must be less than or equal to maxZ: " + prefabPath); + } else { + this.uuid = UUID.randomUUID(); + this.anchorPoint = anchorPoint; + this.pastePosition = pastePosition; + this.originalFileAnchor = new Vector3i(anchorPoint.x - pastePosition.x, anchorPoint.y - pastePosition.y, anchorPoint.z - pastePosition.z); + this.createAnchorEntityAt(pastePosition, world); + } + } + + private void createAnchorEntityAt(@Nonnull Vector3i position, @Nonnull World world) { + this.anchorEntityPosition = position.clone(); + Store store = world.getEntityStore().getStore(); + if (this.anchorEntityUuid != null) { + Ref entityReference = store.getExternalData().getRefFromUUID(this.anchorEntityUuid); + if (entityReference != null && entityReference.isValid()) { + world.execute(() -> store.removeEntity(entityReference, RemoveReason.REMOVE)); + } + } + + TimeResource timeResource = store.getResource(TimeResource.getResourceType()); + Holder blockEntityHolder = BlockEntity.assembleDefaultBlockEntity(timeResource, "Editor_Anchor", position.toVector3d().add(0.5, 0.0, 0.5)); + blockEntityHolder.addComponent(Intangible.getComponentType(), Intangible.INSTANCE); + blockEntityHolder.addComponent(PrefabAnchor.getComponentType(), PrefabAnchor.INSTANCE); + blockEntityHolder.addComponent(EntityScaleComponent.getComponentType(), new EntityScaleComponent(2.1F)); + this.anchorEntityUuid = blockEntityHolder.ensureAndGetComponent(UUIDComponent.getComponentType()).getUuid(); + world.execute(() -> store.addEntity(blockEntityHolder, AddReason.SPAWN)); + } + + public void setPrefabPath(@Nonnull Path prefabPath) { + this.prefabPath = prefabPath; + } + + public void setAnchorPoint(@Nonnull Vector3i newEntityPosition, @Nonnull World world) { + int deltaX = newEntityPosition.x - this.anchorEntityPosition.x; + int deltaY = newEntityPosition.y - this.anchorEntityPosition.y; + int deltaZ = newEntityPosition.z - this.anchorEntityPosition.z; + this.anchorPoint = new Vector3i(this.anchorPoint.x + deltaX, this.anchorPoint.y + deltaY, this.anchorPoint.z + deltaZ); + this.createAnchorEntityAt(newEntityPosition, world); + } + + public void recreateAnchorEntity(@Nonnull World world) { + if (this.anchorEntityPosition != null) { + this.createAnchorEntityAt(this.anchorEntityPosition, world); + } + } + + public void sendAnchorHighlightingPacket(@Nonnull PacketHandler displayTo) { + displayTo.writeNoCache(new BuilderToolShowAnchor(this.anchorEntityPosition.x, this.anchorEntityPosition.y, this.anchorEntityPosition.z)); + } + + public boolean isLocationWithinPrefabBoundingBox(@Nonnull Vector3i location) { + return location.x >= this.getMinPoint().x + && location.x <= this.getMaxPoint().x + && location.y >= this.getMinPoint().y + && location.y <= this.getMaxPoint().y + && location.z >= this.getMinPoint().z + && location.z <= this.getMaxPoint().z; + } + + void setMinPoint(Vector3i minPoint) { + this.minPoint = minPoint; + } + + void setMaxPoint(Vector3i maxPoint) { + this.maxPoint = maxPoint; + } + + public Vector3i getAnchorPoint() { + return this.anchorPoint; + } + + public Vector3i getPastePosition() { + return this.pastePosition; + } + + public Vector3i getOriginalFileAnchor() { + return this.originalFileAnchor; + } + + public Path getPrefabPath() { + return this.prefabPath; + } + + public Vector3i getMinPoint() { + return this.minPoint; + } + + public Vector3i getMaxPoint() { + return this.maxPoint; + } + + @Nullable + public UUID getAnchorEntityUuid() { + return this.anchorEntityUuid; + } + + public Vector3i getAnchorEntityPosition() { + return this.anchorEntityPosition; + } + + public UUID getUuid() { + return this.uuid; + } + + public boolean isDirty() { + return this.dirty; + } + + public void setDirty(boolean dirty) { + this.dirty = dirty; + } + + public boolean isReadOnly() { + return this.prefabPath != null && this.prefabPath.getFileSystem() != FileSystems.getDefault(); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabEditorCreationContext.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabEditorCreationContext.java new file mode 100644 index 0000000..fca5e42 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabEditorCreationContext.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor; + +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabAlignment; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabRootDirectory; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabRowSplitMode; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabStackingAxis; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.WorldGenType; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import java.nio.file.Path; +import java.util.List; + +public interface PrefabEditorCreationContext { + Player getEditor(); + + PlayerRef getEditorRef(); + + List getPrefabPaths(); + + int getBlocksBetweenEachPrefab(); + + int getPasteLevelGoal(); + + boolean loadChildPrefabs(); + + boolean shouldLoadEntities(); + + PrefabStackingAxis getStackingAxis(); + + WorldGenType getWorldGenType(); + + int getBlocksAboveSurface(); + + PrefabAlignment getAlignment(); + + PrefabRootDirectory getPrefabRootDirectory(); + + boolean isWorldTickingEnabled(); + + PrefabRowSplitMode getRowSplitMode(); + + List getUnprocessedPrefabPaths(); + + String getEnvironment(); + + String getGrassTint(); +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabEditorCreationSettings.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabEditorCreationSettings.java new file mode 100644 index 0000000..e43125c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabEditorCreationSettings.java @@ -0,0 +1,416 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.commands.PrefabEditLoadCommand; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabAlignment; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabRootDirectory; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabRowSplitMode; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabStackingAxis; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.WorldGenType; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import com.hypixel.hytale.server.core.prefab.PrefabStore; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabEditorCreationSettings + implements PrefabEditorCreationContext, + JsonAssetWithMap> { + private static final int RECURSIVE_SEARCH_MAX_DEPTH = 10; + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + PrefabEditorCreationSettings.class, + PrefabEditorCreationSettings::new, + Codec.STRING, + (builder, id) -> builder.id = id, + builder -> builder.id, + (builder, data) -> builder.data = data, + builder -> builder.data + ) + .append( + new KeyedCodec<>("RootDirectory", new EnumCodec<>(PrefabRootDirectory.class)), + (o, rootDirectory) -> o.prefabRootDirectory = rootDirectory, + o -> o.prefabRootDirectory + ) + .add() + .append( + new KeyedCodec<>("UnprocessedPrefabPaths", new ArrayCodec<>(Codec.STRING, String[]::new)), + (o, unprocessedPrefabPaths) -> o.unprocessedPrefabPaths = List.of(unprocessedPrefabPaths), + o -> o.unprocessedPrefabPaths.toArray(String[]::new) + ) + .add() + .append(new KeyedCodec<>("PasteYLevelGoal", Codec.INTEGER), (o, pasteYLevelGoal) -> o.pasteYLevelGoal = pasteYLevelGoal, o -> o.pasteYLevelGoal) + .add() + .append( + new KeyedCodec<>("BlocksBetweenEachPrefab", Codec.INTEGER), + (o, blocksBetweenEachPrefab) -> o.blocksBetweenEachPrefab = blocksBetweenEachPrefab, + o -> o.blocksBetweenEachPrefab + ) + .add() + .append(new KeyedCodec<>("WorldGenType", new EnumCodec<>(WorldGenType.class)), (o, worldGenType) -> o.worldGenType = worldGenType, o -> o.worldGenType) + .add() + .append( + new KeyedCodec<>("BlocksAboveSurface", Codec.INTEGER), (o, blocksAboveSurface) -> o.blocksAboveSurface = blocksAboveSurface, o -> o.blocksAboveSurface + ) + .add() + .append( + new KeyedCodec<>("PrefabStackingAxis", new EnumCodec<>(PrefabStackingAxis.class)), + (o, stackingAxis) -> o.stackingAxis = stackingAxis, + o -> o.stackingAxis + ) + .add() + .append(new KeyedCodec<>("PrefabAlignment", new EnumCodec<>(PrefabAlignment.class)), (o, alignment) -> o.alignment = alignment, o -> o.alignment) + .add() + .append(new KeyedCodec<>("RecursiveSearch", Codec.BOOLEAN), (o, recursive) -> o.recursive = recursive, o -> o.recursive) + .add() + .append(new KeyedCodec<>("LoadChildren", Codec.BOOLEAN), (o, loadChildren) -> o.loadChildren = loadChildren, o -> o.loadChildren) + .add() + .append(new KeyedCodec<>("LoadEntities", Codec.BOOLEAN), (o, loadEntities) -> o.loadEntities = loadEntities, o -> o.loadEntities) + .add() + .append( + new KeyedCodec<>("EnableWorldTicking", Codec.BOOLEAN), (o, enableWorldTicking) -> o.enableWorldTicking = enableWorldTicking, o -> o.enableWorldTicking + ) + .add() + .append( + new KeyedCodec<>("RowSplitMode", new EnumCodec<>(PrefabRowSplitMode.class)), (o, rowSplitMode) -> o.rowSplitMode = rowSplitMode, o -> o.rowSplitMode + ) + .add() + .append(new KeyedCodec<>("Environment", Codec.STRING), (o, environment) -> o.environment = environment, o -> o.environment) + .addValidator(Environment.VALIDATOR_CACHE.getValidator()) + .add() + .append(new KeyedCodec<>("GrassTint", Codec.STRING), (o, grassTint) -> o.grassTint = grassTint, o -> o.grassTint) + .add() + .build(); + private static AssetStore> ASSET_STORE; + private String id; + private AssetExtraInfo.Data data; + private transient Player player; + private transient PlayerRef playerRef; + private PrefabRootDirectory prefabRootDirectory = PrefabRootDirectory.ASSET; + private final transient List prefabPaths = new ObjectArrayList<>(); + private List unprocessedPrefabPaths = new ObjectArrayList<>(); + private int pasteYLevelGoal = 55; + private int blocksBetweenEachPrefab = 15; + private WorldGenType worldGenType = PrefabEditLoadCommand.DEFAULT_WORLD_GEN_TYPE; + private int blocksAboveSurface = 0; + private PrefabStackingAxis stackingAxis = PrefabEditLoadCommand.DEFAULT_PREFAB_STACKING_AXIS; + private PrefabAlignment alignment = PrefabEditLoadCommand.DEFAULT_PREFAB_ALIGNMENT; + private boolean recursive; + private boolean loadChildren; + private boolean loadEntities; + private boolean enableWorldTicking = false; + private PrefabRowSplitMode rowSplitMode = PrefabRowSplitMode.BY_ALL_SUBFOLDERS; + private String environment = "Env_Zone1_Plains"; + private String grassTint = "#5B9E28"; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(PrefabEditorCreationSettings.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + private PrefabEditorCreationSettings() { + } + + public PrefabEditorCreationSettings( + PrefabRootDirectory prefabRootDirectory, + List unprocessedPrefabPaths, + int pasteYLevelGoal, + int blocksBetweenEachPrefab, + WorldGenType worldGenType, + int blocksAboveSurface, + PrefabStackingAxis stackingAxis, + PrefabAlignment alignment, + boolean recursive, + boolean loadChildren, + boolean loadEntities, + boolean enableWorldTicking, + PrefabRowSplitMode rowSplitMode, + String environment, + String grassTint + ) { + this.prefabRootDirectory = prefabRootDirectory; + this.unprocessedPrefabPaths = unprocessedPrefabPaths; + this.pasteYLevelGoal = pasteYLevelGoal; + this.blocksBetweenEachPrefab = blocksBetweenEachPrefab; + this.worldGenType = worldGenType; + this.blocksAboveSurface = blocksAboveSurface; + this.stackingAxis = stackingAxis; + this.alignment = alignment; + this.recursive = recursive; + this.loadChildren = loadChildren; + this.loadEntities = loadEntities; + this.enableWorldTicking = enableWorldTicking; + this.rowSplitMode = rowSplitMode; + this.environment = environment; + this.grassTint = grassTint; + } + + @Nullable + PrefabEditorCreationContext finishProcessing(Player editor, PlayerRef playerRef, boolean creatingNewPrefab) { + this.prefabPaths.clear(); + this.player = editor; + this.playerRef = playerRef; + + for (String inputPrefabName : this.unprocessedPrefabPaths) { + inputPrefabName = StringUtil.stripQuotes(inputPrefabName); + inputPrefabName = inputPrefabName.replace('/', File.separatorChar); + inputPrefabName = inputPrefabName.replace('\\', File.separatorChar); + if (!SingleplayerModule.isOwner(playerRef) && !inputPrefabName.isEmpty() && Path.of(inputPrefabName).isAbsolute()) { + this.player.sendMessage(Message.translation("server.commands.editprefab.error.absolutePathNotAllowed")); + return null; + } + + if (!inputPrefabName.endsWith(File.separator)) { + if (!stringEndsWithPrefabPath(inputPrefabName)) { + inputPrefabName = inputPrefabName + ".prefab.json"; + } + + try { + Path rootPath = this.resolveRootPathForInput(inputPrefabName); + String relativePath = this.getRelativePathForInput(inputPrefabName); + Path resolvedPath = rootPath.resolve(relativePath); + if (!SingleplayerModule.isOwner(playerRef) && !PathUtil.isChildOf(rootPath, resolvedPath)) { + this.player.sendMessage(Message.translation("server.commands.editprefab.error.pathTraversal")); + return null; + } + + this.prefabPaths.add(resolvedPath); + } catch (Exception var13) { + var13.printStackTrace(); + this.player.sendMessage(Message.translation("server.commands.editprefab.finishProcessingError").param("error", var13.getMessage())); + return null; + } + } else { + Path rootPath = this.resolveRootPathForInput(inputPrefabName); + String relativePath = this.getRelativePathForInput(inputPrefabName); + Path resolvedDir = rootPath.resolve(relativePath); + if (!SingleplayerModule.isOwner(playerRef) && !PathUtil.isChildOf(rootPath, resolvedDir)) { + this.player.sendMessage(Message.translation("server.commands.editprefab.error.pathTraversal")); + return null; + } + + try (Stream walk = Files.walk(resolvedDir, this.recursive ? 10 : 1)) { + walk.filter(x$0 -> Files.isRegularFile(x$0)).filter(path -> path.toString().endsWith(".prefab.json")).forEach(this.prefabPaths::add); + } catch (IOException var15) { + var15.printStackTrace(); + } + } + } + + if (!creatingNewPrefab) { + for (Path processedPrefabPath : this.prefabPaths) { + if (!Files.exists(processedPrefabPath)) { + this.player + .sendMessage(Message.translation("server.commands.editprefab.load.error.prefabNotFound").param("path", processedPrefabPath.toString())); + return null; + } + } + } + + if (this.prefabPaths.isEmpty()) { + Message header = Message.translation("server.commands.editprefab.noPrefabsInPath"); + Set values = this.unprocessedPrefabPaths + .stream() + .map(p -> this.prefabRootDirectory.getPrefabPath().resolve(p)) + .map(Path::toString) + .map(Message::raw) + .collect(Collectors.toSet()); + this.player.sendMessage(MessageFormat.list(header, values)); + return null; + } else { + return this; + } + } + + @Nonnull + private Path resolveRootPathForInput(@Nonnull String inputPath) { + if (this.prefabRootDirectory != PrefabRootDirectory.ASSET) { + return this.prefabRootDirectory.getPrefabPath(); + } else { + String firstComponent = inputPath.contains(File.separator) ? inputPath.substring(0, inputPath.indexOf(File.separator)) : inputPath; + + for (PrefabStore.AssetPackPrefabPath packPath : PrefabStore.get().getAllAssetPrefabPaths()) { + if (packPath.getDisplayName().equals(firstComponent)) { + return packPath.prefabsPath(); + } + } + + return this.prefabRootDirectory.getPrefabPath(); + } + } + + @Nonnull + private String getRelativePathForInput(@Nonnull String inputPath) { + if (this.prefabRootDirectory != PrefabRootDirectory.ASSET) { + return inputPath; + } else { + String firstComponent = inputPath.contains(File.separator) ? inputPath.substring(0, inputPath.indexOf(File.separator)) : inputPath; + + for (PrefabStore.AssetPackPrefabPath packPath : PrefabStore.get().getAllAssetPrefabPaths()) { + if (packPath.getDisplayName().equals(firstComponent)) { + if (inputPath.contains(File.separator)) { + return inputPath.substring(inputPath.indexOf(File.separator) + 1); + } + + return ""; + } + } + + return inputPath; + } + } + + public static boolean stringEndsWithPrefabPath(@Nonnull String input) { + return input.endsWith(".prefab.json") || input.endsWith(".prefab.json.lpf") || input.endsWith(".lpf"); + } + + @Nonnull + public static CompletableFuture load(@Nonnull String name) { + return CompletableFuture.supplyAsync(() -> getAssetMap().getAsset(name)); + } + + @Nonnull + public static CompletableFuture save(@Nonnull String name, PrefabEditorCreationSettings settings) { + return CompletableFuture.runAsync(() -> { + try { + getAssetStore().writeAssetToDisk(AssetModule.get().getBaseAssetPack(), Map.of(Path.of(name + ".json"), settings)); + } catch (IOException var3) { + var3.printStackTrace(); + } + }); + } + + @Override + public Player getEditor() { + return this.player; + } + + @Override + public PlayerRef getEditorRef() { + return this.playerRef; + } + + @Override + public List getPrefabPaths() { + return this.prefabPaths; + } + + @Override + public int getBlocksBetweenEachPrefab() { + return this.blocksBetweenEachPrefab; + } + + @Override + public int getPasteLevelGoal() { + return this.pasteYLevelGoal; + } + + @Override + public boolean loadChildPrefabs() { + return this.loadChildren; + } + + @Override + public boolean shouldLoadEntities() { + return this.loadEntities; + } + + @Override + public PrefabStackingAxis getStackingAxis() { + return this.stackingAxis; + } + + @Override + public WorldGenType getWorldGenType() { + return this.worldGenType; + } + + @Override + public int getBlocksAboveSurface() { + return this.blocksAboveSurface; + } + + @Override + public PrefabAlignment getAlignment() { + return this.alignment; + } + + public String getId() { + return this.id; + } + + @Override + public PrefabRootDirectory getPrefabRootDirectory() { + return this.prefabRootDirectory; + } + + @Override + public List getUnprocessedPrefabPaths() { + return this.unprocessedPrefabPaths; + } + + public int getPasteYLevelGoal() { + return this.pasteYLevelGoal; + } + + public boolean isRecursive() { + return this.recursive; + } + + public boolean isLoadChildren() { + return this.loadChildren; + } + + @Override + public boolean isWorldTickingEnabled() { + return this.enableWorldTicking; + } + + @Override + public PrefabRowSplitMode getRowSplitMode() { + return this.rowSplitMode; + } + + @Override + public String getEnvironment() { + return this.environment; + } + + @Override + public String getGrassTint() { + return this.grassTint; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabLoadingState.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabLoadingState.java new file mode 100644 index 0000000..ee81fee --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabLoadingState.java @@ -0,0 +1,213 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor; + +import com.hypixel.hytale.server.core.Message; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.nio.file.Path; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabLoadingState { + @Nonnull + private PrefabLoadingState.Phase currentPhase = PrefabLoadingState.Phase.INITIALIZING; + private int totalPrefabs; + private int loadedPrefabs; + private int pastedPrefabs; + @Nullable + private Path currentPrefabPath; + @Nonnull + private final List errors = new ObjectArrayList<>(); + private long startTimeNanos = System.nanoTime(); + private long lastUpdateTimeNanos = this.startTimeNanos; + private long lastNotifyTimeNanos; + + public PrefabLoadingState() { + } + + public void setTotalPrefabs(int totalPrefabs) { + this.totalPrefabs = totalPrefabs; + this.lastUpdateTimeNanos = System.nanoTime(); + } + + public void setPhase(@Nonnull PrefabLoadingState.Phase phase) { + this.currentPhase = phase; + this.lastUpdateTimeNanos = System.nanoTime(); + } + + public void onPrefabLoaded(@Nullable Path path) { + this.loadedPrefabs++; + this.currentPrefabPath = path; + this.lastUpdateTimeNanos = System.nanoTime(); + } + + public void onPrefabPasted(@Nullable Path path) { + this.pastedPrefabs++; + this.currentPrefabPath = path; + this.lastUpdateTimeNanos = System.nanoTime(); + } + + public void addError(@Nonnull PrefabLoadingState.LoadingError error) { + this.errors.add(error); + this.currentPhase = PrefabLoadingState.Phase.ERROR; + this.lastUpdateTimeNanos = System.nanoTime(); + } + + public void addError(@Nonnull String translationKey) { + this.addError(new PrefabLoadingState.LoadingError(translationKey)); + } + + public void addError(@Nonnull String translationKey, @Nullable String details) { + this.addError(new PrefabLoadingState.LoadingError(translationKey, details)); + } + + @Nonnull + public PrefabLoadingState.Phase getCurrentPhase() { + return this.currentPhase; + } + + public int getTotalPrefabs() { + return this.totalPrefabs; + } + + public int getLoadedPrefabs() { + return this.loadedPrefabs; + } + + public int getPastedPrefabs() { + return this.pastedPrefabs; + } + + @Nullable + public Path getCurrentPrefabPath() { + return this.currentPrefabPath; + } + + @Nonnull + public List getErrors() { + return this.errors; + } + + public boolean hasErrors() { + return !this.errors.isEmpty(); + } + + public boolean isShuttingDown() { + return this.currentPhase == PrefabLoadingState.Phase.CANCELLING + || this.currentPhase == PrefabLoadingState.Phase.SHUTTING_DOWN_WORLD + || this.currentPhase == PrefabLoadingState.Phase.DELETING_WORLD; + } + + public boolean isShutdownComplete() { + return this.currentPhase == PrefabLoadingState.Phase.SHUTDOWN_COMPLETE; + } + + public float getProgressPercentage() { + if (this.totalPrefabs == 0) { + return switch (this.currentPhase) { + case INITIALIZING -> 0.0F; + case CREATING_WORLD -> 0.1F; + case LOADING_PREFABS -> 0.2F; + case PASTING_PREFABS -> 0.5F; + case FINALIZING -> 0.99F; + case COMPLETE -> 1.0F; + case ERROR -> 0.0F; + case CANCELLING -> 0.1F; + case SHUTTING_DOWN_WORLD -> 0.4F; + case DELETING_WORLD -> 0.8F; + case SHUTDOWN_COMPLETE -> 1.0F; + }; + } else { + return switch (this.currentPhase) { + case INITIALIZING -> 0.0F; + case CREATING_WORLD -> 0.02F; + case LOADING_PREFABS -> 0.02F + 0.08F * this.loadedPrefabs / this.totalPrefabs; + case PASTING_PREFABS -> 0.1F + 0.89F * this.pastedPrefabs / this.totalPrefabs; + case FINALIZING -> 0.99F; + case COMPLETE -> 1.0F; + case ERROR -> 0.0F; + case CANCELLING -> 0.1F; + case SHUTTING_DOWN_WORLD -> 0.4F; + case DELETING_WORLD -> 0.8F; + case SHUTDOWN_COMPLETE -> 1.0F; + }; + } + } + + public long getElapsedTimeMillis() { + return (System.nanoTime() - this.startTimeNanos) / 1000000L; + } + + public long getLastNotifyTimeNanos() { + return this.lastNotifyTimeNanos; + } + + public void setLastNotifyTimeNanos(long nanos) { + this.lastNotifyTimeNanos = nanos; + } + + @Nonnull + public Message getStatusMessage() { + if (this.hasErrors()) { + return this.errors.getLast().toMessage(); + } else { + Message message = Message.translation(this.currentPhase.getTranslationKey()); + if (this.currentPhase == PrefabLoadingState.Phase.LOADING_PREFABS && this.totalPrefabs > 0) { + message = message.param("current", this.loadedPrefabs).param("total", this.totalPrefabs); + } else if (this.currentPhase == PrefabLoadingState.Phase.PASTING_PREFABS && this.totalPrefabs > 0) { + message = message.param("current", this.pastedPrefabs).param("total", this.totalPrefabs); + } + + if (this.currentPrefabPath != null) { + message = message.param("path", this.currentPrefabPath.getFileName().toString()); + } + + return message; + } + } + + public void markComplete() { + this.currentPhase = PrefabLoadingState.Phase.COMPLETE; + this.lastUpdateTimeNanos = System.nanoTime(); + } + + public record LoadingError(@Nonnull String translationKey, @Nullable String details) { + public LoadingError(@Nonnull String translationKey) { + this(translationKey, null); + } + + @Nonnull + public Message toMessage() { + Message message = Message.translation(this.translationKey); + if (this.details != null) { + message = message.param("details", this.details); + } + + return message; + } + } + + public static enum Phase { + INITIALIZING("server.commands.editprefab.loading.phase.initializing"), + CREATING_WORLD("server.commands.editprefab.loading.phase.creatingWorld"), + LOADING_PREFABS("server.commands.editprefab.loading.phase.loadingPrefabs"), + PASTING_PREFABS("server.commands.editprefab.loading.phase.pastingPrefabs"), + FINALIZING("server.commands.editprefab.loading.phase.finalizing"), + COMPLETE("server.commands.editprefab.loading.phase.complete"), + ERROR("server.commands.editprefab.loading.phase.error"), + CANCELLING("server.commands.editprefab.loading.phase.cancelling"), + SHUTTING_DOWN_WORLD("server.commands.editprefab.loading.phase.shuttingDownWorld"), + DELETING_WORLD("server.commands.editprefab.loading.phase.deletingWorld"), + SHUTDOWN_COMPLETE("server.commands.editprefab.loading.phase.shutdownComplete"); + + private final String translationKey; + + private Phase(String translationKey) { + this.translationKey = translationKey; + } + + @Nonnull + public String getTranslationKey() { + return this.translationKey; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabMarkerProvider.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabMarkerProvider.java new file mode 100644 index 0000000..2fc8dc1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabMarkerProvider.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; +import javax.annotation.Nonnull; + +public class PrefabMarkerProvider implements WorldMapManager.MarkerProvider { + public static final PrefabMarkerProvider INSTANCE = new PrefabMarkerProvider(); + + public PrefabMarkerProvider() { + } + + @Override + public void update( + @Nonnull World world, @Nonnull GameplayConfig gameplayConfig, @Nonnull WorldMapTracker tracker, int chunkViewRadius, int playerChunkX, int playerChunkZ + ) { + PrefabEditSessionManager sessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + Player player = tracker.getPlayer(); + PrefabEditSession session = sessionManager.getPrefabEditSession(player.getUuid()); + if (session != null && session.getWorldName().equals(world.getName())) { + for (PrefabEditingMetadata metadata : session.getLoadedPrefabMetadata().values()) { + MapMarker marker = PrefabEditSession.createPrefabMarker(metadata); + tracker.trySendMarker(chunkViewRadius, playerChunkX, playerChunkZ, marker); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabSelectionInteraction.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabSelectionInteraction.java new file mode 100644 index 0000000..d6c7af7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabSelectionInteraction.java @@ -0,0 +1,128 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabSelectionInteraction extends SimpleInstantInteraction { + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_SELECT_ERROR_NO_TARGET_FOUND = Message.translation( + "server.commands.editprefab.select.error.noTargetFound" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_SELECT_ERROR_NO_PREFAB_FOUND = Message.translation( + "server.commands.editprefab.select.error.noPrefabFound" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION = Message.translation("server.commands.editprefab.notInEditSession"); + private static final float ENTITY_TARGET_RADIUS = 50.0F; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + PrefabSelectionInteraction.class, PrefabSelectionInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Interaction that handles the selection functionally for the prefab selection tool.") + .build(); + + public PrefabSelectionInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getEntity(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + if (type == InteractionType.Primary || type == InteractionType.Secondary) { + UUIDComponent uuidComponent = commandBuffer.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID uuid = uuidComponent.getUuid(); + PrefabEditSessionManager prefabEditSessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + PrefabEditSession prefabEditSession = prefabEditSessionManager.getPrefabEditSession(uuid); + if (prefabEditSession == null) { + playerComponent.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION); + } else { + TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d playerPosition = transformComponent.getPosition(); + PrefabEditingMetadata prefabEditingMetadata = null; + if (type == InteractionType.Secondary) { + Vector3d playerLocation = playerPosition.clone(); + playerLocation.setY(0.0); + double distance = 2.147483647E9; + + for (PrefabEditingMetadata value : prefabEditSession.getLoadedPrefabMetadata().values()) { + Vector3d centerPoint = new Vector3d( + (value.getMaxPoint().x + value.getMinPoint().x) / 2.0, 0.0, (value.getMaxPoint().z + value.getMinPoint().z) / 2.0 + ); + double distanceTo = centerPoint.distanceTo(playerLocation); + if (distance > distanceTo) { + distance = distanceTo; + prefabEditingMetadata = value; + } + } + } else { + Vector3i targetLocation = getTargetLocation(ref, commandBuffer); + if (targetLocation == null) { + playerComponent.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_SELECT_ERROR_NO_TARGET_FOUND); + return; + } + + for (PrefabEditingMetadata valuex : prefabEditSession.getLoadedPrefabMetadata().values()) { + boolean isWithinPrefab = valuex.isLocationWithinPrefabBoundingBox(targetLocation); + if (isWithinPrefab) { + prefabEditingMetadata = valuex; + break; + } + } + } + + if (prefabEditingMetadata == null) { + playerComponent.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_SELECT_ERROR_NO_PREFAB_FOUND); + } else { + prefabEditSession.setSelectedPrefab(ref, prefabEditingMetadata, commandBuffer); + } + } + } + } + } + + @Nullable + private static Vector3i getTargetLocation(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + Vector3i targetBlock = TargetUtil.getTargetBlock(ref, 200.0, componentAccessor); + if (targetBlock != null) { + return targetBlock; + } else { + Ref targetEntityRef = TargetUtil.getTargetEntity(ref, 50.0F, componentAccessor); + if (targetEntityRef != null && targetEntityRef.isValid()) { + TransformComponent entityTransformComponent = componentAccessor.getComponent(targetEntityRef, TransformComponent.getComponentType()); + return entityTransformComponent == null ? null : entityTransformComponent.getPosition().toVector3i(); + } else { + return null; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabSetAnchorInteraction.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabSetAnchorInteraction.java new file mode 100644 index 0000000..2c0fdd2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabSetAnchorInteraction.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class PrefabSetAnchorInteraction extends SimpleInstantInteraction { + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION = Message.translation("server.commands.editprefab.notInEditSession"); + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_ANCHOR_ERROR_NO_ANCHOR_FOUND = Message.translation( + "server.commands.editprefab.anchor.error.noAnchorFound" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_SELECT_ERROR_NO_PREFAB_FOUND = Message.translation( + "server.commands.editprefab.select.error.noPrefabFound" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_ANCHOR_SUCCESS = Message.translation("server.commands.editprefab.anchor.success"); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + PrefabSetAnchorInteraction.class, PrefabSetAnchorInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Sets the prefab anchor.") + .build(); + + public PrefabSetAnchorInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getEntity(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + if (type == InteractionType.Primary || type == InteractionType.Secondary) { + UUIDComponent uuidComponent = commandBuffer.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + PlayerRef playerRefComponent = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + UUID playerUuid = uuidComponent.getUuid(); + PrefabEditSessionManager prefabEditSessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + PrefabEditSession prefabEditSession = prefabEditSessionManager.getPrefabEditSession(playerUuid); + if (prefabEditSession == null) { + playerRefComponent.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION); + } else { + PrefabEditingMetadata prefabEditingMetadata = null; + BlockPosition targetBlock = context.getTargetBlock(); + if (targetBlock == null) { + playerRefComponent.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_ANCHOR_ERROR_NO_ANCHOR_FOUND); + } else { + Vector3i targetBlockPos = new Vector3i(targetBlock.x, targetBlock.y, targetBlock.z); + + for (PrefabEditingMetadata value : prefabEditSession.getLoadedPrefabMetadata().values()) { + boolean isWithinPrefab = value.isLocationWithinPrefabBoundingBox(targetBlockPos); + if (isWithinPrefab) { + prefabEditingMetadata = value; + break; + } + } + + if (prefabEditingMetadata == null) { + playerRefComponent.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_SELECT_ERROR_NO_PREFAB_FOUND); + } else { + prefabEditSession.setSelectedPrefab(ref, prefabEditingMetadata, commandBuffer); + prefabEditingMetadata.setAnchorPoint(targetBlockPos, commandBuffer.getExternalData().getWorld()); + prefabEditingMetadata.sendAnchorHighlightingPacket(playerRefComponent.getPacketHandler()); + playerRefComponent.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_ANCHOR_SUCCESS); + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/Tri.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/Tri.java new file mode 100644 index 0000000..be297b2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/Tri.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor; + +public class Tri { + private final A left; + private final B middle; + private final C right; + + public Tri(A left, B middle, C right) { + this.left = left; + this.middle = middle; + this.right = right; + } + + public A getLeft() { + return this.left; + } + + public B getMiddle() { + return this.middle; + } + + public C getRight() { + return this.right; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditCommand.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditCommand.java new file mode 100644 index 0000000..0ea495c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditCommand.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class PrefabEditCommand extends AbstractCommandCollection { + public PrefabEditCommand() { + super("editprefab", "server.commands.editprefab.desc"); + this.addAliases("prefabedit", "pedit"); + this.addSubCommand(new PrefabEditExitCommand()); + this.addSubCommand(new PrefabEditLoadCommand()); + this.addSubCommand(new PrefabEditCreateNewCommand()); + this.addSubCommand(new PrefabEditSelectCommand()); + this.addSubCommand(new PrefabEditSaveCommand()); + this.addSubCommand(new PrefabEditSaveUICommand()); + this.addSubCommand(new PrefabEditKillEntitiesCommand()); + this.addSubCommand(new PrefabEditSaveAsCommand()); + this.addSubCommand(new PrefabEditUpdateBoxCommand()); + this.addSubCommand(new PrefabEditInfoCommand()); + this.addSubCommand(new PrefabEditTeleportCommand()); + this.addSubCommand(new PrefabEditModifiedCommand()); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditCreateNewCommand.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditCreateNewCommand.java new file mode 100644 index 0000000..65dd783 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditCreateNewCommand.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditorCreationSettings; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabRootDirectory; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.WorldGenType; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class PrefabEditCreateNewCommand extends AbstractAsyncPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_NEW_ERRORS_NOT_A_FILE = Message.translation("server.commands.editprefab.new.errors.notAFile"); + @Nonnull + private final RequiredArg prefabNameArg = this.withRequiredArg("prefabName", "server.commands.editprefab.new.name.desc", ArgTypes.STRING); + @Nonnull + private final DefaultArg worldGenTypeArg = this.withDefaultArg( + "worldgen", + "server.commands.editprefab.load.worldGenType.desc", + ArgTypes.forEnum("WorldGenType", WorldGenType.class), + PrefabEditLoadCommand.DEFAULT_WORLD_GEN_TYPE, + "server.commands.editprefab.load.worldGenType.default.desc" + ); + @Nonnull + private final DefaultArg flatNumBlocksBelowArg = this.withDefaultArg( + "numBlocksToSurface", + "server.commands.editprefab.load.numBlocksToSurface.desc", + ArgTypes.INTEGER, + 0, + "server.commands.editprefab.load.numBlocksToSurface.default.desc" + ) + .addValidator(Validators.range(0, 120)); + @Nonnull + private final DefaultArg prefabPathArg = this.withDefaultArg( + "prefabPath", + "server.commands.editprefab.load.path.desc", + ArgTypes.forEnum("PrefabPath", PrefabRootDirectory.class), + PrefabRootDirectory.ASSET, + "server.commands.editprefab.load.path.default.desc" + ); + + public PrefabEditCreateNewCommand() { + super("new", "server.commands.editprefab.new.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Path prefabBaseDirectory = this.prefabPathArg.get(context).getPrefabPath(); + String prefabName = this.prefabNameArg.get(context); + if (!prefabName.endsWith(".prefab.json")) { + prefabName = prefabName + ".prefab.json"; + } + + Path prefabPath = prefabBaseDirectory.resolve(prefabName); + if (prefabPath.toString().endsWith("/")) { + context.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_NEW_ERRORS_NOT_A_FILE); + return CompletableFuture.completedFuture(null); + } else { + PrefabEditorCreationSettings prefabEditorLoadCommandSettings = new PrefabEditorCreationSettings( + this.prefabPathArg.get(context), + List.of(prefabName), + 55, + 15, + this.worldGenTypeArg.get(context), + this.flatNumBlocksBelowArg.get(context), + PrefabEditLoadCommand.DEFAULT_PREFAB_STACKING_AXIS, + PrefabEditLoadCommand.DEFAULT_PREFAB_ALIGNMENT, + false, + false, + true, + true, + PrefabEditLoadCommand.DEFAULT_ROW_SPLIT_MODE, + "Env_Zone1_Plains", + "#5B9E28" + ); + context.sendMessage(Message.translation("server.commands.editprefab.new.success").param("path", prefabPath.toString())); + return BuilderToolsPlugin.get() + .getPrefabEditSessionManager() + .createEditSessionForNewPrefab(ref, playerComponent, prefabEditorLoadCommandSettings, store); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditExitCommand.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditExitCommand.java new file mode 100644 index 0000000..5155847 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditExitCommand.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSession; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSessionManager; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditingMetadata; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.ui.PrefabEditorExitConfirmPage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class PrefabEditExitCommand extends AbstractAsyncPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_EDIT_NOT_EDITING_A_PREFAB = Message.translation( + "server.commands.editprefab.exit.notEditingAPrefab" + ); + + public PrefabEditExitCommand() { + super("exit", "server.commands.editprefab.exit.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PrefabEditSessionManager prefabEditSessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + if (!prefabEditSessionManager.isEditingAPrefab(playerRef.getUuid())) { + context.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_EDIT_NOT_EDITING_A_PREFAB); + return CompletableFuture.completedFuture(null); + } else { + PrefabEditSession prefabEditSession = prefabEditSessionManager.getPrefabEditSession(playerRef.getUuid()); + if (prefabEditSession != null) { + List dirtyPrefabs = prefabEditSession.getLoadedPrefabMetadata() + .values() + .stream() + .filter(PrefabEditingMetadata::isDirty) + .collect(Collectors.toList()); + if (!dirtyPrefabs.isEmpty()) { + playerComponent.getPageManager().openCustomPage(ref, store, new PrefabEditorExitConfirmPage(playerRef, prefabEditSession, world, dirtyPrefabs)); + return CompletableFuture.completedFuture(null); + } + } + + CompletableFuture result = prefabEditSessionManager.exitEditSession(ref, world, playerRef, store); + return result != null ? result : CompletableFuture.completedFuture(null); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditInfoCommand.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditInfoCommand.java new file mode 100644 index 0000000..cff246b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditInfoCommand.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSession; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSessionManager; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditingMetadata; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class PrefabEditInfoCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION = Message.translation("server.commands.editprefab.notInEditSession"); + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_NO_PREFAB_SELECTED = Message.translation("server.commands.editprefab.noPrefabSelected"); + + public PrefabEditInfoCommand() { + super("info", "server.commands.editprefab.info.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID playerUUID = uuidComponent.getUuid(); + PrefabEditSessionManager prefabEditSessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + PrefabEditSession prefabEditSession = prefabEditSessionManager.getPrefabEditSession(playerUUID); + if (prefabEditSession == null) { + context.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION); + } else { + PrefabEditingMetadata selectedPrefab = prefabEditSession.getSelectedPrefab(playerUUID); + if (selectedPrefab == null) { + context.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_NO_PREFAB_SELECTED); + } else { + Vector3i minPoint = selectedPrefab.getMinPoint(); + Vector3i maxPoint = selectedPrefab.getMaxPoint(); + int xWidth = maxPoint.getX() - minPoint.getX(); + int zWidth = maxPoint.getZ() - minPoint.getZ(); + int yHeight = maxPoint.getY() - minPoint.getY(); + context.sendMessage( + Message.translation("server.commands.editprefab.info.format") + .param("path", selectedPrefab.getPrefabPath().toString()) + .param("dimensions", "X: " + xWidth + ", Y: " + yHeight + ", Z: " + zWidth) + .param( + "dirty", + selectedPrefab.isDirty() + ? Message.translation("server.commands.editprefab.info.dirty.yes") + : Message.translation("server.commands.editprefab.info.dirty.no") + ) + ); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditKillEntitiesCommand.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditKillEntitiesCommand.java new file mode 100644 index 0000000..c7290e6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditKillEntitiesCommand.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSession; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSessionManager; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditingMetadata; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import java.util.List; +import javax.annotation.Nonnull; + +public class PrefabEditKillEntitiesCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION = Message.translation("servers.commands.editprefab.notInEditSession"); + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_NO_PREFAB_SELECTED = Message.translation("server.commands.editprefab.noPrefabSelected"); + + public PrefabEditKillEntitiesCommand() { + super("kill", "server.commands.editprefab.kill.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + PrefabEditSessionManager prefabEditSessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + PrefabEditSession prefabEditSession = prefabEditSessionManager.getPrefabEditSession(playerRef.getUuid()); + if (prefabEditSession == null) { + context.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION); + } else { + PrefabEditingMetadata selectedPrefab = prefabEditSession.getSelectedPrefab(playerRef.getUuid()); + if (selectedPrefab == null) { + context.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_NO_PREFAB_SELECTED); + } else { + Vector3i selectionMax = selectedPrefab.getMaxPoint(); + Vector3i selectionMin = selectedPrefab.getMinPoint(); + Vector3i lengths = selectionMax.subtract(selectionMin); + Vector3d min = new Vector3d(selectionMin.x, selectionMin.y, selectionMin.z); + Vector3d max = new Vector3d(selectionMax.x + 1, selectionMax.y + 1, selectionMax.z + 1); + List> entitiesInBox = TargetUtil.getAllEntitiesInBox(min, max, store); + + for (Ref entityRef : entitiesInBox) { + store.removeEntity(entityRef, RemoveReason.REMOVE); + } + + context.sendMessage(Message.translation("server.commands.editprefab.kill.done").param("amount", entitiesInBox.size())); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditLoadCommand.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditLoadCommand.java new file mode 100644 index 0000000..ab49e40 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditLoadCommand.java @@ -0,0 +1,181 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditorCreationSettings; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabAlignment; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabRootDirectory; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabRowSplitMode; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabStackingAxis; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.WorldGenType; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.ui.PrefabEditorLoadSettingsPage; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class PrefabEditLoadCommand extends AbstractAsyncPlayerCommand { + public static final int DEFAULT_PASTE_LEVEL_GOAL = 55; + public static final int DEFAULT_BLOCKS_BETWEEN_MULTI_PREFABS = 15; + @Nonnull + public static final WorldGenType DEFAULT_WORLD_GEN_TYPE = WorldGenType.FLAT; + public static final int DEFAULT_BLOCKS_ABOVE_SURFACE = 0; + @Nonnull + public static final PrefabStackingAxis DEFAULT_PREFAB_STACKING_AXIS = PrefabStackingAxis.X; + @Nonnull + public static final PrefabAlignment DEFAULT_PREFAB_ALIGNMENT = PrefabAlignment.ANCHOR; + public static final int MAX_BLOCKS_BETWEEN_EACH_PREFAB = 100; + public static final int MAX_BLOCKS_UNTIL_SURFACE = 120; + @Nonnull + public static final PrefabRootDirectory DEFAULT_PREFAB_ROOT_DIRECTORY = PrefabRootDirectory.ASSET; + @Nonnull + public static final PrefabRowSplitMode DEFAULT_ROW_SPLIT_MODE = PrefabRowSplitMode.BY_ALL_SUBFOLDERS; + @Nonnull + private static final Message MESSAGE_COMMANDS_PREFAB_EDIT_SESSION_MANAGER_EXISTING_EDIT_SESSION = Message.translation( + "server.commands.prefabeditsessionmanager.existingEditSession" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_LOADING = Message.translation("server.commands.editprefab.loading"); + @Nonnull + private final RequiredArg prefabPathArg = this.withRequiredArg( + "prefabPath", "server.commands.editprefab.load.path.desc", ArgTypes.forEnum("PrefabPath", PrefabRootDirectory.class) + ); + @Nonnull + private final RequiredArg> prefabNameArg = this.withListRequiredArg("prefabName", "server.commands.editprefab.load.name.desc", ArgTypes.STRING); + @Nonnull + private final DefaultArg pasteLevelGoalArg = this.withDefaultArg( + "pasteLevelGoal", + "server.commands.editprefab.load.pasteLevelGoal.desc", + ArgTypes.INTEGER, + 55, + "server.commands.editprefab.load.pasteLevelGoal.default.desc" + ) + .addValidator(Validators.range(0, 320)); + @Nonnull + private final DefaultArg blocksBetweenMultiPrefabsArg = this.withDefaultArg( + "spacing", "server.commands.editprefab.load.spacing.desc", ArgTypes.INTEGER, 15, "server.commands.editprefab.load.spacing.default.desc" + ) + .addValidator(Validators.range(0, 100)); + @Nonnull + private final DefaultArg worldGenTypeArg = this.withDefaultArg( + "worldgen", + "server.commands.editprefab.load.worldGenType.desc", + ArgTypes.forEnum("WorldGenType", WorldGenType.class), + DEFAULT_WORLD_GEN_TYPE, + "server.commands.editprefab.load.worldGenType.default.desc" + ); + @Nonnull + private final DefaultArg flatNumBlocksBelowArg = this.withDefaultArg( + "blocksAboveSurface", + "server.commands.editprefab.load.numBlocksToSurface.desc", + ArgTypes.INTEGER, + 0, + "server.commands.editprefab.load.numBlocksToSurface.default.desc" + ) + .addValidator(Validators.range(0, 120)) + .availableOnlyIfAll(this.worldGenTypeArg); + @Nonnull + private final DefaultArg axisArg = this.withDefaultArg( + "stackingAxis", + "server.commands.editprefab.load.axis.desc", + ArgTypes.forEnum("Stacking Axis", PrefabStackingAxis.class), + DEFAULT_PREFAB_STACKING_AXIS, + "server.commands.editprefab.load.axis.default.desc" + ) + .addAliases("axis"); + @Nonnull + private final DefaultArg alignmentArg = this.withDefaultArg( + "alignment", + "server.commands.editprefab.load.alignment.desc", + ArgTypes.forEnum("Alignment", PrefabAlignment.class), + PrefabAlignment.ANCHOR, + "server.commands.editprefab.load.alignment.default.desc" + ); + @Nonnull + private final FlagArg recursiveArg = this.withFlagArg("recursive", "server.commands.editprefab.load.recursive.desc"); + @Nonnull + private final FlagArg loadChildrenArg = this.withFlagArg("loadChildren", "server.commands.editprefab.load.loadChildren.desc").addAliases("children"); + @Nonnull + private final FlagArg loadEntitiesArg = this.withFlagArg("loadEntities", "server.commands.editprefab.load.loadEntities.desc").addAliases("entities"); + + public PrefabEditLoadCommand() { + super("load", "server.commands.editprefab.load.desc"); + this.addUsageVariant( + new AbstractPlayerCommand("server.commands.editprefab.load.desc") { + @Nonnull + private static final Message MESSAGE_COMMANDS_PREFAB_EDIT_SESSION_MANAGER_EXISTING_EDIT_SESSION = Message.translation( + "server.commands.prefabeditsessionmanager.existingEditSession" + ); + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nonnull Store store, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world + ) { + if (BuilderToolsPlugin.get().getPrefabEditSessionManager().isEditingAPrefab(playerRef.getUuid())) { + context.sendMessage(MESSAGE_COMMANDS_PREFAB_EDIT_SESSION_MANAGER_EXISTING_EDIT_SESSION); + } else { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getPageManager().openCustomPage(ref, store, new PrefabEditorLoadSettingsPage(playerRef)); + } + } + } + ); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + if (BuilderToolsPlugin.get().getPrefabEditSessionManager().isEditingAPrefab(playerRef.getUuid())) { + context.sendMessage(MESSAGE_COMMANDS_PREFAB_EDIT_SESSION_MANAGER_EXISTING_EDIT_SESSION); + return CompletableFuture.completedFuture(null); + } else { + PrefabEditorCreationSettings prefabEditorLoadCommandSettings = new PrefabEditorCreationSettings( + this.prefabPathArg.get(context), + this.prefabNameArg.get(context), + this.pasteLevelGoalArg.get(context), + this.blocksBetweenMultiPrefabsArg.get(context), + this.worldGenTypeArg.get(context), + this.flatNumBlocksBelowArg.get(context), + this.axisArg.get(context), + this.alignmentArg.get(context), + this.recursiveArg.get(context), + this.loadChildrenArg.get(context), + this.loadEntitiesArg.get(context), + false, + DEFAULT_ROW_SPLIT_MODE, + "Env_Zone1_Plains", + "#5B9E28" + ); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + context.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_LOADING); + return BuilderToolsPlugin.get() + .getPrefabEditSessionManager() + .loadPrefabAndCreateEditSession(ref, playerComponent, prefabEditorLoadCommandSettings, store); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditModifiedCommand.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditModifiedCommand.java new file mode 100644 index 0000000..90d9bf2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditModifiedCommand.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSession; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSessionManager; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditingMetadata; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class PrefabEditModifiedCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION = Message.translation("server.commands.editprefab.notInEditSession"); + + public PrefabEditModifiedCommand() { + super("modified", "server.commands.editprefab.modified.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID playerUUID = uuidComponent.getUuid(); + PrefabEditSessionManager prefabEditSessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + PrefabEditSession prefabEditSession = prefabEditSessionManager.getPrefabEditSession(playerUUID); + if (prefabEditSession == null) { + context.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION); + } else { + Collection loadedPrefabs = prefabEditSession.getLoadedPrefabMetadata().values(); + List modifiedPrefabs = loadedPrefabs.stream().filter(metadata -> metadata.isDirty()).collect(Collectors.toList()); + if (modifiedPrefabs.isEmpty()) { + context.sendMessage(Message.translation("server.commands.editprefab.modified.none")); + } else { + context.sendMessage( + Message.translation("server.commands.editprefab.modified.header").param("count", modifiedPrefabs.size()).param("total", loadedPrefabs.size()) + ); + + for (PrefabEditingMetadata prefab : modifiedPrefabs) { + context.sendMessage(Message.translation("server.commands.editprefab.modified.entry").param("path", prefab.getPrefabPath().toString())); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditSaveAsCommand.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditSaveAsCommand.java new file mode 100644 index 0000000..1e04772 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditSaveAsCommand.java @@ -0,0 +1,122 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSession; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSessionManager; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditingMetadata; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabRootDirectory; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.saving.PrefabSaver; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.saving.PrefabSaverSettings; +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.nio.file.Path; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class PrefabEditSaveAsCommand extends AbstractAsyncPlayerCommand { + private final RequiredArg fileNameArg = this.withRequiredArg("fileNameArg", "server.commands.editprefab.save.saveAs.desc", ArgTypes.STRING); + private final DefaultArg prefabPathArg = this.withDefaultArg( + "prefabPath", + "server.commands.editprefab.save.path.desc", + ArgTypes.forEnum("PrefabPath", PrefabRootDirectory.class), + PrefabRootDirectory.SERVER, + "server.commands.editprefab.save.path.default.desc" + ); + private final FlagArg noEntitiesArg = this.withFlagArg("noEntities", "server.commands.editprefab.save.noEntities.desc"); + private final FlagArg overwriteArg = this.withFlagArg("overwrite", "server.commands.editprefab.save.overwrite.desc"); + private final FlagArg emptyArg = this.withFlagArg("empty", "server.commands.editprefab.save.empty.desc"); + private final FlagArg noUpdateArg = this.withFlagArg("noUpdate", "server.commands.editprefab.saveAs.noUpdate.desc"); + + public PrefabEditSaveAsCommand() { + super("saveas", "server.commands.editprefab.saveAs.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + UUID uuid = playerRef.getUuid(); + PrefabEditSessionManager prefabEditSessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + PrefabEditSession prefabEditSession = prefabEditSessionManager.getPrefabEditSession(uuid); + if (prefabEditSession == null) { + context.sendMessage(Message.translation("server.commands.editprefab.notInEditSession")); + return CompletableFuture.completedFuture(null); + } else { + PrefabSaverSettings prefabSaverSettings = new PrefabSaverSettings(); + prefabSaverSettings.setBlocks(true); + prefabSaverSettings.setEntities(!this.noEntitiesArg.provided(context)); + prefabSaverSettings.setOverwriteExisting(this.overwriteArg.get(context)); + prefabSaverSettings.setEmpty(this.emptyArg.get(context)); + Path prefabRootPath = this.prefabPathArg.get(context).getPrefabPath(); + if (!PathUtil.isChildOf(prefabRootPath, prefabRootPath.resolve(this.fileNameArg.get(context))) && !SingleplayerModule.isOwner(playerRef)) { + context.sendMessage(Message.translation("server.builderTools.attemptedToSaveOutsidePrefabsDir")); + return CompletableFuture.completedFuture(null); + } else { + Path prefabSavePath = prefabRootPath.resolve(this.fileNameArg.get(context)); + if (prefabSavePath.toString().endsWith("/")) { + context.sendMessage(Message.translation("server.commands.editprefab.saveAs.errors.notAFile")); + return CompletableFuture.completedFuture(null); + } else { + if (!prefabEditSession.toString().endsWith(".prefab.json")) { + prefabSavePath = Path.of(prefabSavePath + ".prefab.json"); + } + + PrefabEditingMetadata selectedPrefab = prefabEditSession.getSelectedPrefab(uuid); + if (selectedPrefab == null) { + context.sendMessage(Message.translation("server.commands.editprefab.noPrefabSelected")); + return CompletableFuture.completedFuture(null); + } else { + BlockSelection selection = BuilderToolsPlugin.getState(playerComponent, playerRef).getSelection(); + if (selectedPrefab.getMinPoint().equals(selection.getSelectionMin()) && selectedPrefab.getMaxPoint().equals(selection.getSelectionMax())) { + if (!this.noUpdateArg.provided(context)) { + prefabEditSessionManager.updatePathOfLoadedPrefab(selectedPrefab.getPrefabPath(), prefabSavePath); + selectedPrefab.setPrefabPath(prefabSavePath); + } + + return PrefabSaver.savePrefab( + playerComponent, + world, + prefabSavePath, + selectedPrefab.getAnchorPoint(), + selectedPrefab.getMinPoint(), + selectedPrefab.getMaxPoint(), + selectedPrefab.getPastePosition(), + selectedPrefab.getOriginalFileAnchor(), + prefabSaverSettings + ) + .thenAccept( + success -> context.sendMessage( + Message.translation("server.commands.editprefab.save." + (success ? "success" : "failure")) + .param("name", selectedPrefab.getPrefabPath().toString()) + ) + ); + } else { + context.sendMessage(Message.translation("server.commands.editprefab.save.selectionMismatch")); + return CompletableFuture.completedFuture(null); + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditSaveCommand.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditSaveCommand.java new file mode 100644 index 0000000..4e30f9d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditSaveCommand.java @@ -0,0 +1,208 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSession; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSessionManager; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditingMetadata; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.saving.PrefabSaver; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.saving.PrefabSaverSettings; +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import com.hypixel.hytale.server.core.prefab.PrefabStore; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class PrefabEditSaveCommand extends AbstractAsyncPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION = Message.translation("server.commands.editprefab.notInEditSession"); + @Nonnull + private static final Message MESSAGE_PATH_OUTSIDE_PREFABS_DIR = Message.translation("server.builderTools.attemptedToSaveOutsidePrefabsDir"); + @Nonnull + private final FlagArg saveAllArg = this.withFlagArg("saveAll", "server.commands.editprefab.save.saveAll.desc").addAliases("all"); + @Nonnull + private final FlagArg noEntitiesArg = this.withFlagArg("noEntities", "server.commands.editprefab.save.noEntities.desc"); + @Nonnull + private final FlagArg emptyArg = this.withFlagArg("empty", "server.commands.editprefab.save.empty.desc"); + @Nonnull + private final FlagArg confirmArg = this.withFlagArg("confirm", "server.commands.editprefab.save.confirm.desc"); + + private static boolean isPathInAllowedPrefabDirectory(@Nonnull Path path) { + PrefabStore prefabStore = PrefabStore.get(); + return PathUtil.isChildOf(prefabStore.getServerPrefabsPath(), path) + || PathUtil.isChildOf(prefabStore.getAssetPrefabsPath(), path) + || PathUtil.isChildOf(prefabStore.getWorldGenPrefabsPath(), path); + } + + public PrefabEditSaveCommand() { + super("save", "server.commands.editprefab.save.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PrefabEditSessionManager prefabEditSessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + PrefabEditSession prefabEditSession = prefabEditSessionManager.getPrefabEditSession(playerRef.getUuid()); + if (prefabEditSession == null) { + context.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION); + return CompletableFuture.completedFuture(null); + } else { + PrefabSaverSettings prefabSaverSettings = new PrefabSaverSettings(); + prefabSaverSettings.setBlocks(true); + prefabSaverSettings.setEntities(!this.noEntitiesArg.provided(context)); + prefabSaverSettings.setOverwriteExisting(true); + prefabSaverSettings.setEmpty(this.emptyArg.get(context)); + boolean confirm = this.confirmArg.provided(context); + if (!this.saveAllArg.provided(context)) { + PrefabEditingMetadata selectedPrefab = prefabEditSession.getSelectedPrefab(playerRef.getUuid()); + if (selectedPrefab == null) { + context.sendMessage(Message.translation("server.commands.editprefab.noPrefabSelected")); + return CompletableFuture.completedFuture(null); + } else if (selectedPrefab.isReadOnly() && !confirm) { + Path redirectPath = getWritableSavePath(selectedPrefab, true); + context.sendMessage( + Message.translation("server.commands.editprefab.save.readOnlyNeedsConfirmSingle") + .param("path", selectedPrefab.getPrefabPath().toString()) + .param("redirectPath", redirectPath.toString()) + ); + return CompletableFuture.completedFuture(null); + } else { + BlockSelection selection = BuilderToolsPlugin.getState(playerComponent, playerRef).getSelection(); + if (selectedPrefab.getMinPoint().equals(selection.getSelectionMin()) && selectedPrefab.getMaxPoint().equals(selection.getSelectionMax())) { + Path savePath = getWritableSavePath(selectedPrefab, confirm); + if (!SingleplayerModule.isOwner(playerRef) && !isPathInAllowedPrefabDirectory(savePath)) { + context.sendMessage(MESSAGE_PATH_OUTSIDE_PREFABS_DIR); + return CompletableFuture.completedFuture(null); + } else { + return PrefabSaver.savePrefab( + playerComponent, + world, + savePath, + selectedPrefab.getAnchorPoint(), + selectedPrefab.getMinPoint(), + selectedPrefab.getMaxPoint(), + selectedPrefab.getPastePosition(), + selectedPrefab.getOriginalFileAnchor(), + prefabSaverSettings + ) + .thenAccept( + success -> { + if (success) { + selectedPrefab.setDirty(false); + } + + context.sendMessage( + Message.translation("server.commands.editprefab.save." + (success ? "success" : "failure")).param("name", savePath.toString()) + ); + } + ); + } + } else { + context.sendMessage(Message.translation("server.commands.editprefab.save.selectionMismatch")); + return CompletableFuture.completedFuture(null); + } + } + } else { + PrefabEditingMetadata[] values = prefabEditSession.getLoadedPrefabMetadata().values().toArray(new PrefabEditingMetadata[0]); + int readOnlyCount = 0; + + for (PrefabEditingMetadata value : values) { + if (value.isReadOnly()) { + readOnlyCount++; + } + } + + if (readOnlyCount > 0 && !confirm) { + context.sendMessage(Message.translation("server.commands.editprefab.save.readOnlyNeedsConfirm").param("count", readOnlyCount)); + return CompletableFuture.completedFuture(null); + } else { + if (!SingleplayerModule.isOwner(playerRef)) { + for (PrefabEditingMetadata valuex : values) { + Path savePath = getWritableSavePath(valuex, confirm); + if (!isPathInAllowedPrefabDirectory(savePath)) { + context.sendMessage(MESSAGE_PATH_OUTSIDE_PREFABS_DIR); + return CompletableFuture.completedFuture(null); + } + } + } + + context.sendMessage(Message.translation("server.commands.editprefab.save.saveAll.start").param("amount", values.length)); + CompletableFuture[] prefabSavingFutures = new CompletableFuture[values.length]; + + for (int i = 0; i < values.length; i++) { + PrefabEditingMetadata valuexx = values[i]; + Path savePath = getWritableSavePath(valuexx, confirm); + prefabSavingFutures[i] = PrefabSaver.savePrefab( + playerComponent, + world, + savePath, + valuexx.getAnchorPoint(), + valuexx.getMinPoint(), + valuexx.getMaxPoint(), + valuexx.getPastePosition(), + valuexx.getOriginalFileAnchor(), + prefabSaverSettings + ); + } + + return CompletableFuture.allOf(prefabSavingFutures) + .thenAccept( + unused -> { + List failedPrefabFutures = new IntArrayList(); + + for (int i1 = 0; i1 < prefabSavingFutures.length; i1++) { + if (prefabSavingFutures[i1].join()) { + values[i1].setDirty(false); + } else { + failedPrefabFutures.add(i1); + } + } + + context.sendMessage( + Message.translation("server.commands.editprefab.save.saveAll.success") + .param("successes", prefabSavingFutures.length - failedPrefabFutures.size()) + .param("failures", failedPrefabFutures.size()) + ); + } + ); + } + } + } + } + + @Nonnull + private static Path getWritableSavePath(@Nonnull PrefabEditingMetadata metadata, boolean confirm) { + if (metadata.isReadOnly() && confirm) { + Path originalPath = metadata.getPrefabPath(); + String fileName = originalPath.getFileName().toString(); + Path parent = originalPath.getParent(); + if (parent != null && parent.getFileName() != null) { + String parentName = parent.getFileName().toString(); + return PrefabStore.get().getServerPrefabsPath().resolve(parentName).resolve(fileName); + } else { + return PrefabStore.get().getServerPrefabsPath().resolve(fileName); + } + } else { + return metadata.getPrefabPath(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditSaveUICommand.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditSaveUICommand.java new file mode 100644 index 0000000..750d44a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditSaveUICommand.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSession; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSessionManager; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.ui.PrefabEditorSaveSettingsPage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PrefabEditSaveUICommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION = Message.translation("server.commands.editprefab.notInEditSession"); + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_NO_PREFABS_LOADED = Message.translation("server.commands.editprefab.save.noPrefabsLoaded"); + + public PrefabEditSaveUICommand() { + super("saveui", "server.commands.editprefab.saveui.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + PrefabEditSessionManager prefabEditSessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + PrefabEditSession prefabEditSession = prefabEditSessionManager.getPrefabEditSession(playerRef.getUuid()); + if (prefabEditSession == null) { + context.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION); + } else if (prefabEditSession.getLoadedPrefabMetadata().isEmpty()) { + context.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_NO_PREFABS_LOADED); + } else { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getPageManager().openCustomPage(ref, store, new PrefabEditorSaveSettingsPage(playerRef, prefabEditSession)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditSelectCommand.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditSelectCommand.java new file mode 100644 index 0000000..0b588e1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditSelectCommand.java @@ -0,0 +1,115 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSession; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSessionManager; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditingMetadata; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabEditSelectCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_SELECT_ERROR_NO_TARGET_FOUND = Message.translation( + "server.commands.editprefab.select.error.noTargetFound" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_SELECT_ERROR_NO_PREFAB_FOUND = Message.translation( + "server.commands.editprefab.select.error.noPrefabFound" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION = Message.translation("server.commands.editprefab.notInEditSession"); + @Nonnull + private final FlagArg nearestArg = this.withFlagArg("nearest", "server.commands.editprefab.select.nearest.desc"); + + public PrefabEditSelectCommand() { + super("select", "server.commands.editprefab.select.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + PrefabEditSessionManager prefabEditSessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + PrefabEditSession prefabEditSession = prefabEditSessionManager.getPrefabEditSession(playerRef.getUuid()); + if (prefabEditSession == null) { + context.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION); + } else { + PrefabEditingMetadata prefabEditingMetadata = null; + if (this.nearestArg.get(context)) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d playerLocation = transformComponent.getPosition().clone(); + playerLocation.setY(0.0); + double distance = 2.147483647E9; + + for (PrefabEditingMetadata value : prefabEditSession.getLoadedPrefabMetadata().values()) { + Vector3d centerPoint = new Vector3d( + (value.getMaxPoint().x + value.getMinPoint().x) / 2.0, 0.0, (value.getMaxPoint().z + value.getMinPoint().z) / 2.0 + ); + double distanceTo = centerPoint.distanceTo(playerLocation); + if (distance > distanceTo) { + distance = distanceTo; + prefabEditingMetadata = value; + } + } + } else { + Vector3i targetLocation = getTargetLocation(ref, store); + if (targetLocation == null) { + context.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_SELECT_ERROR_NO_TARGET_FOUND); + return; + } + + for (PrefabEditingMetadata valuex : prefabEditSession.getLoadedPrefabMetadata().values()) { + boolean isWithinPrefab = valuex.isLocationWithinPrefabBoundingBox(targetLocation); + if (isWithinPrefab) { + prefabEditingMetadata = valuex; + break; + } + } + } + + if (prefabEditingMetadata == null) { + context.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_SELECT_ERROR_NO_PREFAB_FOUND); + } else { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + prefabEditSession.setSelectedPrefab(ref, prefabEditingMetadata, store); + } + } + } + + @Nullable + private static Vector3i getTargetLocation(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + Vector3i targetBlock = TargetUtil.getTargetBlock(ref, 200.0, componentAccessor); + if (targetBlock != null) { + return targetBlock; + } else { + Ref targetEntityRef = TargetUtil.getTargetEntity(ref, componentAccessor); + if (targetEntityRef != null && targetEntityRef.isValid()) { + TransformComponent entityTransformComponent = componentAccessor.getComponent(targetEntityRef, TransformComponent.getComponentType()); + return entityTransformComponent == null ? null : entityTransformComponent.getPosition().toVector3i(); + } else { + return null; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditTeleportCommand.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditTeleportCommand.java new file mode 100644 index 0000000..81f87ce --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditTeleportCommand.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSession; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSessionManager; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.ui.PrefabTeleportPage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PrefabEditTeleportCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION = Message.translation("server.commands.editprefab.notInEditSession"); + @Nonnull + private static final Message MESSAGE_COMMANDS_EDIT_PREFAB_NO_PREFABS_LOADED = Message.translation("server.commands.editprefab.tp.noPrefabsLoaded"); + + public PrefabEditTeleportCommand() { + super("tp", "server.commands.editprefab.tp.desc"); + this.addAliases("teleport"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + PrefabEditSessionManager prefabEditSessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + PrefabEditSession prefabEditSession = prefabEditSessionManager.getPrefabEditSession(playerRef.getUuid()); + if (prefabEditSession == null) { + context.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_NOT_IN_EDIT_SESSION); + } else if (prefabEditSession.getLoadedPrefabMetadata().isEmpty()) { + context.sendMessage(MESSAGE_COMMANDS_EDIT_PREFAB_NO_PREFABS_LOADED); + } else { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getPageManager().openCustomPage(ref, store, new PrefabTeleportPage(playerRef, prefabEditSession)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditUpdateBoxCommand.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditUpdateBoxCommand.java new file mode 100644 index 0000000..c1b21bb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/commands/PrefabEditUpdateBoxCommand.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSession; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSessionManager; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditingMetadata; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class PrefabEditUpdateBoxCommand extends AbstractPlayerCommand { + @Nonnull + private final FlagArg confirmAnchorDeletionArg = this.withFlagArg("confirm", "server.commands.editprefab.setbox.confirm.desc"); + + public PrefabEditUpdateBoxCommand() { + super("setBox", "server.commands.editprefab.setbox.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + UUID playerUUID = playerRef.getUuid(); + PrefabEditSessionManager prefabEditSessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + PrefabEditSession prefabEditSession = prefabEditSessionManager.getPrefabEditSession(playerUUID); + if (prefabEditSession == null) { + context.sendMessage(Message.translation("server.commands.editprefab.notInEditSession")); + } else { + PrefabEditingMetadata selectedPrefab = prefabEditSession.getSelectedPrefab(playerUUID); + if (selectedPrefab == null) { + context.sendMessage(Message.translation("server.commands.editprefab.noPrefabSelected")); + } else { + boolean didMoveAnchor = false; + BlockSelection currSelection = BuilderToolsPlugin.getState(playerComponent, playerRef).getSelection(); + if (currSelection != null && !this.isLocationWithinSelection(selectedPrefab.getAnchorEntityPosition(), currSelection)) { + if (!this.confirmAnchorDeletionArg.get(context)) { + context.sendMessage(Message.translation("server.commands.editprefab.setbox.anchorOutsideNewSelection")); + return; + } + + didMoveAnchor = true; + selectedPrefab.setAnchorPoint(currSelection.getSelectionMin(), world); + selectedPrefab.sendAnchorHighlightingPacket(playerRef.getPacketHandler()); + } + + boolean finalDidMoveAnchor = didMoveAnchor; + BuilderToolsPlugin.addToQueue(playerComponent, playerRef, (r, s, componentAccessor) -> { + BlockSelection selection = s.getSelection(); + if (selection == null) { + context.sendMessage(Message.translation("server.commands.editprefab.noSelection")); + } else { + Vector3i selectionMin = selection.getSelectionMin(); + Vector3i selectionMax = selection.getSelectionMax(); + prefabEditSession.updatePrefabBounds(selectedPrefab.getUuid(), selectionMin, selectionMax); + context.sendMessage(Message.translation("server.commands.editprefab.setbox.success")); + if (finalDidMoveAnchor) { + context.sendMessage(Message.translation("server.commands.editprefab.setbox.success.movedAnchor")); + } + } + }); + } + } + } + + public boolean isLocationWithinSelection(@Nonnull Vector3i location, @Nonnull BlockSelection selection) { + Vector3i selectionMin = selection.getSelectionMin(); + Vector3i selectionMax = selection.getSelectionMax(); + return location.x >= selectionMin.x + && location.x <= selectionMax.x + && location.y >= selectionMin.y + && location.y <= selectionMax.y + && location.z >= selectionMin.z + && location.z <= selectionMax.z; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/enums/PrefabAlignment.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/enums/PrefabAlignment.java new file mode 100644 index 0000000..38efc14 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/enums/PrefabAlignment.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.enums; + +public enum PrefabAlignment { + ANCHOR("server.commands.editprefab.ui.alignment.anchor"), + ZERO("server.commands.editprefab.ui.alignment.zero"); + + private final String localizationString; + + private PrefabAlignment(String localizationString) { + this.localizationString = localizationString; + } + + public String getLocalizationString() { + return this.localizationString; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/enums/PrefabRootDirectory.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/enums/PrefabRootDirectory.java new file mode 100644 index 0000000..d529969 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/enums/PrefabRootDirectory.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.enums; + +import com.hypixel.hytale.server.core.prefab.PrefabStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.nio.file.Path; +import java.util.List; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public enum PrefabRootDirectory { + SERVER(() -> PrefabStore.get().getServerPrefabsPath(), "server.commands.editprefab.ui.rootDirectory.server", false), + ASSET(() -> PrefabStore.get().getAssetPrefabsPath(), "server.commands.editprefab.ui.rootDirectory.asset", true), + WORLDGEN(() -> PrefabStore.get().getWorldGenPrefabsPath(), "server.commands.editprefab.ui.rootDirectory.worldGen", false), + ASSET_ROOT(() -> PrefabStore.get().getAssetRootPath(), "server.commands.editprefab.ui.rootDirectory.assetRoot", false); + + private final Supplier prefabPath; + private final String localizationString; + private final boolean supportsMultiPack; + + private PrefabRootDirectory(Supplier prefabPath, String localizationString, boolean supportsMultiPack) { + this.prefabPath = prefabPath; + this.localizationString = localizationString; + this.supportsMultiPack = supportsMultiPack; + } + + public Path getPrefabPath() { + return this.prefabPath.get(); + } + + public String getLocalizationString() { + return this.localizationString; + } + + public boolean supportsMultiPack() { + return this.supportsMultiPack; + } + + @Nonnull + public List getAllPrefabPaths() { + if (this.supportsMultiPack) { + return PrefabStore.get().getAllAssetPrefabPaths(); + } else { + List result = new ObjectArrayList<>(1); + result.add(new PrefabStore.AssetPackPrefabPath(null, this.getPrefabPath())); + return result; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/enums/PrefabRowSplitMode.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/enums/PrefabRowSplitMode.java new file mode 100644 index 0000000..28aaac7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/enums/PrefabRowSplitMode.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.enums; + +public enum PrefabRowSplitMode { + BY_ALL_SUBFOLDERS("server.commands.editprefab.ui.rowSplit.byAllSubfolders"), + BY_SPECIFIED_FOLDER("server.commands.editprefab.ui.rowSplit.bySpecifiedFolder"), + NONE("server.commands.editprefab.ui.rowSplit.none"); + + private final String localizationString; + + private PrefabRowSplitMode(String localizationString) { + this.localizationString = localizationString; + } + + public String getLocalizationString() { + return this.localizationString; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/enums/PrefabStackingAxis.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/enums/PrefabStackingAxis.java new file mode 100644 index 0000000..ead8107 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/enums/PrefabStackingAxis.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.enums; + +public enum PrefabStackingAxis { + X, + Z; + + private PrefabStackingAxis() { + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/enums/WorldGenType.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/enums/WorldGenType.java new file mode 100644 index 0000000..527c586 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/enums/WorldGenType.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.enums; + +public enum WorldGenType { + FLAT("server.commands.editprefab.ui.worldGenType.flat"), + VOID("server.commands.editprefab.ui.worldGenType.void"); + + private final String localizationString; + + private WorldGenType(String localizationString) { + this.localizationString = localizationString; + } + + public String getLocalizationString() { + return this.localizationString; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/saving/PrefabSaver.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/saving/PrefabSaver.java new file mode 100644 index 0000000..4254a47 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/saving/PrefabSaver.java @@ -0,0 +1,270 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.saving; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.prefab.PrefabSaveException; +import com.hypixel.hytale.server.core.prefab.PrefabStore; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabSaver { + protected static final String EDITOR_BLOCK = "Editor_Block"; + protected static final String EDITOR_BLOCK_PREFAB_AIR = "Editor_Empty"; + protected static final String EDITOR_BLOCK_PREFAB_ANCHOR = "Editor_Anchor"; + + public PrefabSaver() { + } + + @Nonnull + public static CompletableFuture savePrefab( + @Nonnull CommandSender sender, + @Nonnull World world, + @Nonnull Path pathToSave, + @Nonnull Vector3i anchorPoint, + @Nonnull Vector3i minPoint, + @Nonnull Vector3i maxPoint, + @Nonnull Vector3i pastePosition, + @Nonnull Vector3i originalFileAnchor, + @Nonnull PrefabSaverSettings settings + ) { + return CompletableFuture.supplyAsync(() -> { + BlockSelection blockSelection = copyBlocks(sender, world, anchorPoint, minPoint, maxPoint, pastePosition, originalFileAnchor, settings); + return blockSelection == null ? false : save(sender, blockSelection, pathToSave, settings); + }, world); + } + + @Nullable + private static BlockSelection copyBlocks( + @Nonnull CommandSender sender, + @Nonnull World world, + @Nonnull Vector3i anchorPoint, + @Nonnull Vector3i minPoint, + @Nonnull Vector3i maxPoint, + @Nonnull Vector3i pastePosition, + @Nonnull Vector3i originalFileAnchor, + @Nonnull PrefabSaverSettings settings + ) { + ChunkStore chunkStore = world.getChunkStore(); + long start = System.nanoTime(); + int width = maxPoint.x - minPoint.x; + int height = maxPoint.y - minPoint.y; + int depth = maxPoint.z - minPoint.z; + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + int editorBlock = assetMap.getIndex("Editor_Block"); + if (editorBlock == Integer.MIN_VALUE) { + sender.sendMessage(Message.translation("server.commands.editprefab.save.error.unknownBlockIdKey").param("key", "Editor_Block".toString())); + return null; + } else { + int editorBlockPrefabAir = assetMap.getIndex("Editor_Empty"); + if (editorBlockPrefabAir == Integer.MIN_VALUE) { + sender.sendMessage(Message.translation("server.commands.editprefab.save.error.unknownBlockIdKey").param("key", "Editor_Empty".toString())); + return null; + } else { + int editorBlockPrefabAnchor = assetMap.getIndex("Editor_Anchor"); + if (editorBlockPrefabAnchor == Integer.MIN_VALUE) { + sender.sendMessage(Message.translation("server.commands.editprefab.save.error.unknownBlockIdKey").param("key", "Editor_Anchor".toString())); + return null; + } else { + int newAnchorX = anchorPoint.x - pastePosition.x; + int newAnchorY = anchorPoint.y - pastePosition.y; + int newAnchorZ = anchorPoint.z - pastePosition.z; + BlockSelection selection = new BlockSelection(); + selection.setPosition(pastePosition.x - originalFileAnchor.x, pastePosition.y - originalFileAnchor.y, pastePosition.z - originalFileAnchor.z); + selection.setSelectionArea(minPoint, maxPoint); + selection.setAnchor(newAnchorX, newAnchorY, newAnchorZ); + int blockCount = 0; + int fluidCount = 0; + int top = Math.max(minPoint.y, maxPoint.y); + int bottom = Math.min(minPoint.y, maxPoint.y); + Long2ObjectMap> loadedChunks = preloadChunksInSelection(world, chunkStore, minPoint, maxPoint); + + for (int x = minPoint.x; x <= maxPoint.x; x++) { + for (int z = minPoint.z; z <= maxPoint.z; z++) { + long chunkIndex = ChunkUtil.indexChunkFromBlock(x, z); + Ref chunkRef = loadedChunks.get(chunkIndex); + if (chunkRef != null && chunkRef.isValid()) { + WorldChunk worldChunkComponent = chunkStore.getStore().getComponent(chunkRef, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + ChunkColumn chunkColumnComponent = chunkStore.getStore().getComponent(chunkRef, ChunkColumn.getComponentType()); + + assert chunkColumnComponent != null; + + for (int y = top; y >= bottom; y--) { + int sectionIndex = ChunkUtil.indexSection(y); + Ref sectionRef = chunkColumnComponent.getSection(sectionIndex); + if (sectionRef != null && sectionRef.isValid()) { + BlockSection sectionComponent = chunkStore.getStore().getComponent(sectionRef, BlockSection.getComponentType()); + + assert sectionComponent != null; + + BlockPhysics blockPhysicsComponent = chunkStore.getStore().getComponent(sectionRef, BlockPhysics.getComponentType()); + int block = sectionComponent.get(x, y, z); + if (settings.isBlocks() && (block != 0 || settings.isEmpty()) && block != editorBlock) { + if (block == editorBlockPrefabAir) { + block = 0; + } + + Holder holder = worldChunkComponent.getBlockComponentHolder(x, y, z); + if (holder != null) { + holder = holder.clone(); + BlockState blockState = BlockState.getBlockState(holder); + if (blockState != null) { + int localX = x - pastePosition.x; + int localY = y - pastePosition.y; + int localZ = z - pastePosition.z; + Vector3i position = blockState.__internal_getPosition(); + if (position != null) { + position.assign(localX, localY, localZ); + } + } + } + + selection.addBlockAtWorldPos( + x, + y, + z, + block, + sectionComponent.getRotationIndex(x, y, z), + sectionComponent.getFiller(x, y, z), + blockPhysicsComponent != null ? blockPhysicsComponent.get(x, y, z) : 0, + holder + ); + blockCount++; + } + + FluidSection fluidSectionComponent = chunkStore.getStore().getComponent(sectionRef, FluidSection.getComponentType()); + + assert fluidSectionComponent != null; + + int fluid = fluidSectionComponent.getFluidId(x, y, z); + if (settings.isBlocks() && (fluid != 0 || settings.isEmpty())) { + byte fluidLevel = fluidSectionComponent.getFluidLevel(x, y, z); + selection.addFluidAtWorldPos(x, y, z, fluid, fluidLevel); + fluidCount++; + } + } + } + } + } + } + + if (settings.isEntities()) { + Store store = world.getEntityStore().getStore(); + BuilderToolsPlugin.forEachCopyableInSelection(world, minPoint.x, minPoint.y, minPoint.z, width, height, depth, e -> { + Holder holder = store.copyEntity(e); + selection.addEntityFromWorld(holder); + }); + } + + long end = System.nanoTime(); + long diff = end - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute copy of %d blocks, %d fluids", diff, TimeUnit.NANOSECONDS.toMillis(diff), blockCount, fluidCount); + return selection; + } + } + } + } + + @Nonnull + private static Long2ObjectMap> preloadChunksInSelection( + @Nonnull World world, @Nonnull ChunkStore chunkStore, @Nonnull Vector3i minPoint, @Nonnull Vector3i maxPoint + ) { + LongSet chunkIndices = new LongOpenHashSet(); + int minChunkX = minPoint.x >> 5; + int maxChunkX = maxPoint.x >> 5; + int minChunkZ = minPoint.z >> 5; + int maxChunkZ = maxPoint.z >> 5; + + for (int cx = minChunkX; cx <= maxChunkX; cx++) { + for (int cz = minChunkZ; cz <= maxChunkZ; cz++) { + chunkIndices.add(ChunkUtil.indexChunk(cx, cz)); + } + } + + Long2ObjectMap> loadedChunks = new Long2ObjectOpenHashMap<>(chunkIndices.size()); + + for (long chunkIndex : chunkIndices) { + CompletableFuture> future = chunkStore.getChunkReferenceAsync(chunkIndex); + + while (!future.isDone()) { + world.consumeTaskQueue(); + } + + Ref reference = future.join(); + if (reference != null && reference.isValid()) { + loadedChunks.put(chunkIndex, reference); + } + } + + return loadedChunks; + } + + private static boolean save( + @Nonnull CommandSender sender, @Nonnull BlockSelection copiedSelection, @Nonnull Path saveFilePath, @Nonnull PrefabSaverSettings settings + ) { + if (saveFilePath.getFileSystem() != FileSystems.getDefault()) { + sender.sendMessage(Message.translation("server.builderTools.cannotSaveToReadOnlyPath").param("path", saveFilePath.toString())); + return false; + } else { + try { + long start = System.nanoTime(); + BlockSelection postClone = settings.isRelativize() ? copiedSelection.relativize() : copiedSelection.cloneSelection(); + PrefabStore.get().savePrefab(saveFilePath, postClone, settings.isOverwriteExisting()); + long diff = System.nanoTime() - start; + BuilderToolsPlugin.get() + .getLogger() + .at(Level.FINE) + .log("Took: %dns (%dms) to execute save of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), copiedSelection.getBlockCount()); + return true; + } catch (PrefabSaveException var9) { + switch (var9.getType()) { + case ERROR: + BuilderToolsPlugin.get().getLogger().at(Level.WARNING).withCause(var9).log("Exception saving prefab %s", saveFilePath); + sender.sendMessage( + Message.translation("server.builderTools.errorSavingPrefab") + .param("name", saveFilePath.toString()) + .param("message", var9.getCause().getMessage()) + ); + break; + case ALREADY_EXISTS: + BuilderToolsPlugin.get().getLogger().at(Level.WARNING).log("Prefab already exists %s", saveFilePath.toString()); + sender.sendMessage(Message.translation("server.builderTools.prefabAlreadyExists")); + } + + return false; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/saving/PrefabSaverSettings.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/saving/PrefabSaverSettings.java new file mode 100644 index 0000000..4df9d7a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/saving/PrefabSaverSettings.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.saving; + +public class PrefabSaverSettings { + private boolean relativize; + private boolean overwriteExisting; + private boolean empty; + private boolean blocks; + private boolean entities; + private boolean keepAnchors; + + public PrefabSaverSettings() { + } + + public boolean isRelativize() { + return this.relativize; + } + + public void setRelativize(boolean relativize) { + this.relativize = relativize; + } + + public boolean isOverwriteExisting() { + return this.overwriteExisting; + } + + public void setOverwriteExisting(boolean overwriteExisting) { + this.overwriteExisting = overwriteExisting; + } + + public boolean isEmpty() { + return this.empty; + } + + public void setEmpty(boolean empty) { + this.empty = empty; + } + + public boolean isBlocks() { + return this.blocks; + } + + public void setBlocks(boolean blocks) { + this.blocks = blocks; + } + + public boolean isEntities() { + return this.entities; + } + + public void setEntities(boolean entities) { + this.entities = entities; + } + + public boolean isKeepAnchors() { + return this.keepAnchors; + } + + public void setKeepAnchors(boolean keepAnchors) { + this.keepAnchors = keepAnchors; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/ui/PrefabEditorExitConfirmPage.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/ui/PrefabEditorExitConfirmPage.java new file mode 100644 index 0000000..f333ca6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/ui/PrefabEditorExitConfirmPage.java @@ -0,0 +1,120 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.ui; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSession; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSessionManager; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditingMetadata; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.List; +import javax.annotation.Nonnull; + +public class PrefabEditorExitConfirmPage extends InteractiveCustomUIPage { + @Nonnull + private final PrefabEditSession prefabEditSession; + @Nonnull + private final World world; + @Nonnull + private final List dirtyPrefabs; + + public PrefabEditorExitConfirmPage( + @Nonnull PlayerRef playerRef, @Nonnull PrefabEditSession prefabEditSession, @Nonnull World world, @Nonnull List dirtyPrefabs + ) { + super(playerRef, CustomPageLifetime.CanDismissOrCloseThroughInteraction, PrefabEditorExitConfirmPage.PageData.CODEC); + this.prefabEditSession = prefabEditSession; + this.world = world; + this.dirtyPrefabs = dirtyPrefabs; + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/PrefabEditorExitConfirm.ui"); + commandBuilder.set("#WarningTitle.TextSpans", Message.translation("server.commands.editprefab.exit.unsavedWarning.title")); + commandBuilder.set( + "#WarningMessage.TextSpans", Message.translation("server.commands.editprefab.exit.unsavedWarning.message").param("count", this.dirtyPrefabs.size()) + ); + int index = 0; + + for (PrefabEditingMetadata prefab : this.dirtyPrefabs) { + String fullPath = prefab.getPrefabPath().toString().replace('\\', '/'); + String fileName = prefab.getPrefabPath().getFileName().toString(); + String displayName = fileName.endsWith(".prefab.json") ? fileName.substring(0, fileName.length() - ".prefab.json".length()) : fileName; + commandBuilder.append("#PrefabList", "Pages/BasicTextButton.ui"); + commandBuilder.set("#PrefabList[" + index + "].Text", "\u2022 " + displayName); + commandBuilder.set("#PrefabList[" + index + "].TooltipText", fullPath); + index++; + } + + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, "#ConfirmExitButton", new EventData().append("Action", PrefabEditorExitConfirmPage.Action.ConfirmExit.name()) + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, "#CancelButton", new EventData().append("Action", PrefabEditorExitConfirmPage.Action.Cancel.name()) + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, "#SaveAndExitButton", new EventData().append("Action", PrefabEditorExitConfirmPage.Action.SaveAndExit.name()) + ); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull PrefabEditorExitConfirmPage.PageData data) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + switch (data.action) { + case ConfirmExit: + playerComponent.getPageManager().setPage(ref, store, Page.None); + PrefabEditSessionManager prefabEditSessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + prefabEditSessionManager.exitEditSession(ref, this.world, this.playerRef, store); + break; + case Cancel: + playerComponent.getPageManager().setPage(ref, store, Page.None); + break; + case SaveAndExit: + playerComponent.getPageManager().openCustomPage(ref, store, new PrefabEditorSaveSettingsPage(this.playerRef, this.prefabEditSession)); + } + } + + public static enum Action { + ConfirmExit, + Cancel, + SaveAndExit; + + private Action() { + } + } + + protected static class PageData { + public static final BuilderCodec CODEC = BuilderCodec.builder( + PrefabEditorExitConfirmPage.PageData.class, PrefabEditorExitConfirmPage.PageData::new + ) + .append( + new KeyedCodec<>("Action", new EnumCodec<>(PrefabEditorExitConfirmPage.Action.class, EnumCodec.EnumStyle.LEGACY)), + (o, action) -> o.action = action, + o -> o.action + ) + .add() + .build(); + public PrefabEditorExitConfirmPage.Action action; + + public PageData() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/ui/PrefabEditorLoadSettingsPage.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/ui/PrefabEditorLoadSettingsPage.java new file mode 100644 index 0000000..46fdc2c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/ui/PrefabEditorLoadSettingsPage.java @@ -0,0 +1,1021 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.ui; + +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSessionManager; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditorCreationSettings; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabLoadingState; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.commands.PrefabEditLoadCommand; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabAlignment; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabRootDirectory; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabRowSplitMode; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabStackingAxis; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.WorldGenType; +import com.hypixel.hytale.builtin.buildertools.prefablist.AssetPrefabFileProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import com.hypixel.hytale.server.core.prefab.PrefabStore; +import com.hypixel.hytale.server.core.ui.DropdownEntryInfo; +import com.hypixel.hytale.server.core.ui.LocalizableString; +import com.hypixel.hytale.server.core.ui.Value; +import com.hypixel.hytale.server.core.ui.browser.FileListProvider; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.File; +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabEditorLoadSettingsPage extends InteractiveCustomUIPage { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final Value BUTTON_HIGHLIGHTED = Value.ref("Pages/BasicTextButton.ui", "SelectedLabelStyle"); + private static final String ASSETS_ROOT_KEY = "Assets"; + private final List savedConfigsDropdown = new ObjectArrayList<>(); + private volatile boolean isLoading; + private volatile boolean loadingCancelled; + private volatile boolean isShuttingDown; + private PrefabLoadingState currentLoadingState; + private String loadingWorldName; + private Path browserRoot; + private Path browserCurrent; + private String selectedPath; + @Nonnull + private String browserSearchQuery = ""; + private final List selectedItems = new ObjectArrayList<>(); + @Nonnull + private final AssetPrefabFileProvider assetProvider = new AssetPrefabFileProvider(); + private boolean inAssetsRoot = false; + @Nonnull + private Path assetsCurrentDir = Paths.get(""); + + public PrefabEditorLoadSettingsPage(@Nonnull PlayerRef playerRef) { + super(playerRef, CustomPageLifetime.CanDismissOrCloseThroughInteraction, PrefabEditorLoadSettingsPage.PageData.CODEC); + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/PrefabEditorSettings.ui"); + this.savedConfigsDropdown.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.commands.editprefab.ui.savedConfigs.noneSelected"), "")); + + for (String assetId : PrefabEditorCreationSettings.getAssetMap().getAssetMap().keySet()) { + this.savedConfigsDropdown.add(new DropdownEntryInfo(LocalizableString.fromString(assetId), assetId)); + } + + commandBuilder.set("#SavedConfigs #Input.Entries", this.savedConfigsDropdown); + commandBuilder.set("#SavedConfigs #Input.Value", ""); + ObjectArrayList rootDirectoryDropdown = new ObjectArrayList<>(); + + for (PrefabRootDirectory value : PrefabRootDirectory.values()) { + if (value != PrefabRootDirectory.WORLDGEN) { + rootDirectoryDropdown.add(new DropdownEntryInfo(LocalizableString.fromMessageId(value.getLocalizationString()), value.name())); + } + } + + commandBuilder.set("#MainPage #RootDir #Input.Entries", rootDirectoryDropdown); + commandBuilder.set("#MainPage #RootDir #Input.Value", PrefabEditLoadCommand.DEFAULT_PREFAB_ROOT_DIRECTORY.name()); + ObjectArrayList worldGenTypeDropdown = new ObjectArrayList<>(); + + for (WorldGenType valuex : WorldGenType.values()) { + worldGenTypeDropdown.add(new DropdownEntryInfo(LocalizableString.fromMessageId(valuex.getLocalizationString()), valuex.name())); + } + + commandBuilder.set("#MainPage #WorldGenType #Input.Entries", worldGenTypeDropdown); + commandBuilder.set("#MainPage #WorldGenType #Input.Value", PrefabEditLoadCommand.DEFAULT_WORLD_GEN_TYPE.name()); + ObjectArrayList environmentDropdown = new ObjectArrayList<>(); + Environment.getAssetMap() + .getAssetMap() + .keySet() + .stream() + .sorted() + .forEach(envId -> environmentDropdown.add(new DropdownEntryInfo(LocalizableString.fromString(envId), envId))); + commandBuilder.set("#MainPage #Environment #Input.Entries", environmentDropdown); + commandBuilder.set("#MainPage #Environment #Input.Value", "Env_Zone1_Plains"); + commandBuilder.set("#MainPage #GrassTint #Input.Color", "#5B9E28"); + ObjectArrayList axisToPasteOnDropdown = new ObjectArrayList<>(); + + for (PrefabStackingAxis valuex : PrefabStackingAxis.values()) { + axisToPasteOnDropdown.add(new DropdownEntryInfo(LocalizableString.fromString(valuex.name()), valuex.name())); + } + + commandBuilder.set("#MainPage #PasteAxis #Input.Entries", axisToPasteOnDropdown); + commandBuilder.set("#MainPage #PasteAxis #Input.Value", PrefabEditLoadCommand.DEFAULT_PREFAB_STACKING_AXIS.name()); + ObjectArrayList alignmentMethodDropdown = new ObjectArrayList<>(); + + for (PrefabAlignment valuex : PrefabAlignment.values()) { + alignmentMethodDropdown.add(new DropdownEntryInfo(LocalizableString.fromMessageId(valuex.getLocalizationString()), valuex.name())); + } + + commandBuilder.set("#MainPage #AlignmentMethod #Input.Entries", alignmentMethodDropdown); + commandBuilder.set("#MainPage #AlignmentMethod #Input.Value", PrefabEditLoadCommand.DEFAULT_PREFAB_ALIGNMENT.name()); + ObjectArrayList rowSplitModeDropdown = new ObjectArrayList<>(); + + for (PrefabRowSplitMode valuex : PrefabRowSplitMode.values()) { + rowSplitModeDropdown.add(new DropdownEntryInfo(LocalizableString.fromMessageId(valuex.getLocalizationString()), valuex.name())); + } + + commandBuilder.set("#MainPage #RowSplitMode #Input.Entries", rowSplitModeDropdown); + commandBuilder.set("#MainPage #RowSplitMode #Input.Value", PrefabEditLoadCommand.DEFAULT_ROW_SPLIT_MODE.name()); + commandBuilder.set("#MainPage #DesiredYLevel #Input.Value", 55); + commandBuilder.set("#MainPage #BlocksBetweenPrefabs #Input.Value", 15); + commandBuilder.set("#MainPage #NumAirBeforeGround #Input.Value", 0); + commandBuilder.set("#MainPage #EnableWorldTicking #CheckBox.Value", false); + commandBuilder.set("#MainPage #Children.Visible", false); + commandBuilder.set("#LoadingPage.Visible", false); + commandBuilder.set("#LoadingPage #ProgressBar.Value", 0.0F); + commandBuilder.set("#LoadingPage #StatusText.TextSpans", Message.translation("server.commands.editprefab.loading.phase.initializing")); + commandBuilder.set("#LoadingPage #ErrorText.Visible", false); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#MainPage #LoadButton", + new EventData() + .append("Action", PrefabEditorLoadSettingsPage.Action.Load.name()) + .append("@RootDir", "#MainPage #RootDir #Input.Value") + .append("@PrefabPaths", "#MainPage #PrefabPaths #Input.Value") + .append("@Recursive", "#MainPage #Recursive #CheckBox.Value") + .append("@Children", "#MainPage #Children #CheckBox.Value") + .append("@Entities", "#MainPage #Entities #CheckBox.Value") + .append("@EnableWorldTicking", "#MainPage #EnableWorldTicking #CheckBox.Value") + .append("@DesiredYLevel", "#MainPage #DesiredYLevel #Input.Value") + .append("@BlocksBetweenPrefabs", "#MainPage #BlocksBetweenPrefabs #Input.Value") + .append("@WorldGenType", "#MainPage #WorldGenType #Input.Value") + .append("@Environment", "#MainPage #Environment #Input.Value") + .append("@GrassTint", "#MainPage #GrassTint #Input.Color") + .append("@PasteAxis", "#MainPage #PasteAxis #Input.Value") + .append("@NumAirBeforeGround", "#MainPage #NumAirBeforeGround #Input.Value") + .append("@AlignmentMethod", "#MainPage #AlignmentMethod #Input.Value") + .append("@RowSplitMode", "#MainPage #RowSplitMode #Input.Value") + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, + "#MainPage #SavedConfigs #Input", + new EventData() + .append("Action", PrefabEditorLoadSettingsPage.Action.ApplySavedProperties.name()) + .append("@ConfigName", "#MainPage #SavedConfigs #Input.Value") + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, "#MainPage #CancelButton", new EventData().append("Action", PrefabEditorLoadSettingsPage.Action.Cancel.name()) + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#MainPage #SavePropertiesButton", + new EventData().append("Action", PrefabEditorLoadSettingsPage.Action.OpenSavePropertiesDialog.name()) + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#SaveConfigPage #CancelButton", + new EventData().append("Action", PrefabEditorLoadSettingsPage.Action.CancelSavePropertiesDialog.name()) + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, + "#SaveConfigPage #SaveName #Input", + new EventData() + .append("Action", PrefabEditorLoadSettingsPage.Action.SavePropertiesNameChanged.name()) + .append("@ConfigName", "#SaveConfigPage #SaveName #Input.Value") + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#SaveConfigPage #SavePropertiesButton", + new EventData() + .append("Action", PrefabEditorLoadSettingsPage.Action.SavePropertiesConfig.name()) + .append("@ConfigName", "#SaveConfigPage #SaveName #Input.Value") + .append("@RootDir", "#MainPage #RootDir #Input.Value") + .append("@PrefabPaths", "#MainPage #PrefabPaths #Input.Value") + .append("@Recursive", "#MainPage #Recursive #CheckBox.Value") + .append("@Children", "#MainPage #Children #CheckBox.Value") + .append("@Entities", "#MainPage #Entities #CheckBox.Value") + .append("@EnableWorldTicking", "#MainPage #EnableWorldTicking #CheckBox.Value") + .append("@DesiredYLevel", "#MainPage #DesiredYLevel #Input.Value") + .append("@BlocksBetweenPrefabs", "#MainPage #BlocksBetweenPrefabs #Input.Value") + .append("@WorldGenType", "#MainPage #WorldGenType #Input.Value") + .append("@Environment", "#MainPage #Environment #Input.Value") + .append("@GrassTint", "#MainPage #GrassTint #Input.Color") + .append("@PasteAxis", "#MainPage #PasteAxis #Input.Value") + .append("@NumAirBeforeGround", "#MainPage #NumAirBeforeGround #Input.Value") + .append("@AlignmentMethod", "#MainPage #AlignmentMethod #Input.Value") + .append("@RowSplitMode", "#MainPage #RowSplitMode #Input.Value") + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#LoadingPage #CancelButton", + new EventData().append("Action", PrefabEditorLoadSettingsPage.Action.CancelLoading.name()) + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#MainPage #PrefabPaths #BrowseButton", + new EventData().append("Action", PrefabEditorLoadSettingsPage.Action.OpenBrowser.name()) + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, + "#BrowserPage #BrowserContent #RootSelector", + new EventData() + .append("Action", PrefabEditorLoadSettingsPage.Action.BrowserRootChanged.name()) + .append("@BrowserRoot", "#BrowserPage #BrowserContent #RootSelector.Value"), + false + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, + "#BrowserPage #BrowserContent #SearchInput", + new EventData() + .append("Action", PrefabEditorLoadSettingsPage.Action.BrowserSearch.name()) + .append("@BrowserSearch", "#BrowserPage #BrowserContent #SearchInput.Value"), + false + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#BrowserPage #AddToListButton", + new EventData().append("Action", PrefabEditorLoadSettingsPage.Action.AddFolderToList.name()) + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#BrowserPage #ConfirmButton", + new EventData().append("Action", PrefabEditorLoadSettingsPage.Action.ConfirmBrowser.name()) + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#BrowserPage #CancelButton", + new EventData().append("Action", PrefabEditorLoadSettingsPage.Action.CancelBrowser.name()) + ); + commandBuilder.set("#BrowserPage.Visible", false); + commandBuilder.set("#BrowserPage #SelectedSection #SelectedItems.Value", ""); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull PrefabEditorLoadSettingsPage.PageData data) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + switch (data.uiAction) { + case Load: + if (this.isLoading || this.isShuttingDown) { + return; + } + + this.isLoading = true; + this.loadingCancelled = false; + this.currentLoadingState = new PrefabLoadingState(); + this.loadingWorldName = "prefabEditor-" + playerRefComponent.getUsername(); + UICommandBuilder showLoadingBuilder = new UICommandBuilder(); + showLoadingBuilder.set("#MainPage.Visible", false); + showLoadingBuilder.set("#SaveConfigPage.Visible", false); + showLoadingBuilder.set("#LoadingPage.Visible", true); + showLoadingBuilder.set("#LoadingPage #ProgressBar.Value", 0.0F); + showLoadingBuilder.set("#LoadingPage #StatusText.TextSpans", Message.translation("server.commands.editprefab.loading.phase.initializing")); + showLoadingBuilder.set("#LoadingPage #ErrorText.Visible", false); + showLoadingBuilder.set("#LoadingPage #CancelButton.Visible", true); + this.sendUpdate(showLoadingBuilder); + this.playerRef.sendMessage(Message.translation("server.commands.editprefab.loading")); + CompletableFuture result = BuilderToolsPlugin.get() + .getPrefabEditSessionManager() + .loadPrefabAndCreateEditSession(ref, playerComponent, data.toCreationSettings(), store, this::onLoadingProgress); + if (result == null) { + this.onLoadingFailed(Message.translation("server.commands.editprefab.error.failedToStart")); + return; + } + + result.whenComplete((unused, throwable) -> { + if (!this.loadingCancelled) { + if (throwable != null) { + this.onLoadingFailed(Message.raw(throwable.getMessage() != null ? throwable.getMessage() : "Unknown error")); + } else if (this.currentLoadingState != null && this.currentLoadingState.hasErrors()) { + this.onLoadingFailed(this.currentLoadingState.getStatusMessage()); + } else { + this.isLoading = false; + this.loadingWorldName = null; + this.currentLoadingState = null; + playerComponent.getPageManager().setPage(ref, store, Page.ContentCreation); + } + } + }); + break; + case OpenSavePropertiesDialog: { + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#MainPage.Visible", false); + commandBuilder.set("#SaveConfigPage.Visible", true); + this.sendUpdate(commandBuilder); + break; + } + case CancelSavePropertiesDialog: { + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#MainPage.Visible", true); + commandBuilder.set("#SaveConfigPage.Visible", false); + this.sendUpdate(commandBuilder); + break; + } + case SavePropertiesConfig: + PrefabEditorCreationSettings.save(data.configName, data.toCreationSettings()).thenRun(() -> { + UICommandBuilder builderx = new UICommandBuilder(); + builderx.set("#MainPage.Visible", true); + builderx.set("#SaveConfigPage.Visible", false); + builderx.set("#SaveConfigPage #Buttons.Visible", true); + builderx.set("#SaveConfigPage #SaveName #Input.Value", ""); + this.savedConfigsDropdown.add(new DropdownEntryInfo(LocalizableString.fromString(data.configName), data.configName)); + builderx.set("#SavedConfigs #Input.Entries", this.savedConfigsDropdown); + builderx.set("#SavedConfigs #Input.Value", data.configName); + this.sendUpdate(builderx); + }); + break; + case ApplySavedProperties: + if (data.configName == null || data.configName.isBlank()) { + UICommandBuilder builderx = new UICommandBuilder(); + builderx.set("#MainPage #RootDir #Input.Value", PrefabEditLoadCommand.DEFAULT_PREFAB_ROOT_DIRECTORY.name()); + builderx.set("#MainPage #PrefabPaths #Input.Value", ""); + builderx.set("#MainPage #Recursive #CheckBox.Value", false); + builderx.set("#MainPage #Children #CheckBox.Value", false); + builderx.set("#MainPage #Entities #CheckBox.Value", false); + builderx.set("#MainPage #EnableWorldTicking #CheckBox.Value", false); + builderx.set("#MainPage #DesiredYLevel #Input.Value", 55); + builderx.set("#MainPage #BlocksBetweenPrefabs #Input.Value", 15); + builderx.set("#MainPage #WorldGenType #Input.Value", PrefabEditLoadCommand.DEFAULT_WORLD_GEN_TYPE.name()); + builderx.set("#MainPage #Environment #Input.Value", "Env_Zone1_Plains"); + builderx.set("#MainPage #GrassTint #Input.Color", "#5B9E28"); + builderx.set("#MainPage #NumAirBeforeGround #Input.Value", 0); + builderx.set("#MainPage #PasteAxis #Input.Value", PrefabEditLoadCommand.DEFAULT_PREFAB_STACKING_AXIS.name()); + builderx.set("#MainPage #AlignmentMethod #Input.Value", PrefabEditLoadCommand.DEFAULT_PREFAB_ALIGNMENT.name()); + builderx.set("#MainPage #RowSplitMode #Input.Value", PrefabEditLoadCommand.DEFAULT_ROW_SPLIT_MODE.name()); + this.sendUpdate(builderx); + return; + } + + PrefabEditorCreationSettings.load(data.configName).thenAccept(settings -> { + if (settings != null) { + UICommandBuilder builderx = new UICommandBuilder(); + builderx.set("#MainPage #RootDir #Input.Value", settings.getPrefabRootDirectory().name()); + builderx.set("#MainPage #PrefabPaths #Input.Value", String.join(",", settings.getUnprocessedPrefabPaths())); + builderx.set("#MainPage #Recursive #CheckBox.Value", settings.isRecursive()); + builderx.set("#MainPage #Children #CheckBox.Value", settings.isLoadChildren()); + builderx.set("#MainPage #Entities #CheckBox.Value", settings.shouldLoadEntities()); + builderx.set("#MainPage #EnableWorldTicking #CheckBox.Value", settings.isWorldTickingEnabled()); + builderx.set("#MainPage #DesiredYLevel #Input.Value", settings.getPasteYLevelGoal()); + builderx.set("#MainPage #BlocksBetweenPrefabs #Input.Value", settings.getBlocksBetweenEachPrefab()); + builderx.set("#MainPage #WorldGenType #Input.Value", settings.getWorldGenType().name()); + builderx.set("#MainPage #Environment #Input.Value", settings.getEnvironment()); + builderx.set("#MainPage #GrassTint #Input.Color", settings.getGrassTint()); + builderx.set("#MainPage #NumAirBeforeGround #Input.Value", settings.getBlocksAboveSurface()); + builderx.set("#MainPage #PasteAxis #Input.Value", settings.getStackingAxis().name()); + builderx.set("#MainPage #AlignmentMethod #Input.Value", settings.getAlignment().name()); + builderx.set("#MainPage #RowSplitMode #Input.Value", settings.getRowSplitMode().name()); + this.sendUpdate(builderx); + } + }); + break; + case Cancel: + playerComponent.getPageManager().setPage(ref, store, Page.None); + break; + case CancelLoading: + if (this.isShuttingDown) { + return; + } + + this.loadingCancelled = true; + this.isLoading = false; + this.isShuttingDown = true; + UICommandBuilder cancellingBuilder = new UICommandBuilder(); + cancellingBuilder.set("#LoadingPage #CancelButton.Disabled", true); + cancellingBuilder.set("#LoadingPage #StatusText.TextSpans", Message.translation("server.commands.editprefab.loading.phase.cancelling")); + cancellingBuilder.set("#LoadingPage #ProgressBar.Value", 0.1F); + cancellingBuilder.set("#LoadingPage #ErrorText.Visible", false); + this.sendUpdate(cancellingBuilder); + PrefabEditSessionManager sessionManager = BuilderToolsPlugin.get().getPrefabEditSessionManager(); + if (this.loadingWorldName != null) { + String worldNameToClean = this.loadingWorldName; + this.loadingWorldName = null; + sessionManager.cleanupCancelledSession(this.playerRef.getUuid(), worldNameToClean, this::onShutdownProgress) + .whenComplete((unused, throwable) -> { + this.isShuttingDown = false; + this.currentLoadingState = null; + UICommandBuilder builderx = new UICommandBuilder(); + builderx.set("#LoadingPage.Visible", false); + builderx.set("#LoadingPage #CancelButton.Disabled", false); + builderx.set("#MainPage.Visible", true); + this.sendUpdate(builderx); + if (throwable != null) { + this.playerRef.sendMessage(Message.translation("server.commands.editprefab.error.shutdownFailed")); + } + }); + } else { + this.isShuttingDown = false; + this.currentLoadingState = null; + UICommandBuilder builderx = new UICommandBuilder(); + builderx.set("#LoadingPage.Visible", false); + builderx.set("#MainPage.Visible", true); + this.sendUpdate(builderx); + } + break; + case SavePropertiesNameChanged: + UICommandBuilder builder = new UICommandBuilder(); + builder.set("#SaveConfigPage #Buttons #SavePropertiesButton.Disabled", data.configName.isBlank()); + this.sendUpdate(builder); + break; + case OpenBrowser: { + this.inAssetsRoot = true; + this.assetsCurrentDir = Paths.get(""); + this.browserRoot = Paths.get("Assets"); + this.browserCurrent = Paths.get(""); + this.selectedPath = null; + this.browserSearchQuery = ""; + this.selectedItems.clear(); + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + commandBuilder.set("#MainPage.Visible", false); + commandBuilder.set("#BrowserPage.Visible", true); + List roots = this.buildBrowserRootEntries(); + commandBuilder.set("#BrowserPage #BrowserContent #RootSelector.Entries", roots); + commandBuilder.set("#BrowserPage #BrowserContent #RootSelector.Value", "Assets"); + commandBuilder.set("#BrowserPage #BrowserContent #SearchInput.Value", ""); + commandBuilder.set("#BrowserPage #SelectedSection #SelectedItems.Value", ""); + this.buildBrowserList(commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + break; + } + case BrowserNavigate: + if (data.browserFile == null) { + return; + } + + String fileName = data.browserFile; + if (this.inAssetsRoot) { + this.handleAssetsNavigation(fileName); + } else { + this.handleRegularNavigation(fileName); + } + break; + case BrowserRootChanged: { + if (data.browserRootStr == null) { + return; + } + + if (!this.isAllowedBrowserRoot(data.browserRootStr)) { + return; + } + + this.inAssetsRoot = "Assets".equals(data.browserRootStr); + this.assetsCurrentDir = Paths.get(""); + if (this.inAssetsRoot) { + this.browserRoot = Paths.get("Assets"); + this.browserCurrent = Paths.get(""); + } else { + this.browserRoot = this.findActualRootPath(data.browserRootStr); + if (this.browserRoot == null) { + this.browserRoot = Path.of(data.browserRootStr); + } + + this.browserCurrent = this.browserRoot.getFileSystem().getPath(""); + } + + this.selectedPath = null; + this.browserSearchQuery = ""; + this.selectedItems.clear(); + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + commandBuilder.set("#BrowserPage #BrowserContent #SearchInput.Value", ""); + commandBuilder.set("#BrowserPage #SelectedSection #SelectedItems.Value", ""); + PrefabRootDirectory rootDirValue = this.getRootDirectoryForPath(data.browserRootStr); + if (rootDirValue != null) { + commandBuilder.set("#MainPage #RootDir #Input.Value", rootDirValue.name()); + } + + this.buildBrowserList(commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + break; + } + case BrowserSearch: { + this.browserSearchQuery = data.browserSearchStr != null ? data.browserSearchStr.trim().toLowerCase() : ""; + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.buildBrowserList(commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + break; + } + case AddFolderToList: { + String pathToAdd = this.getCurrentBrowserPath(); + if (!pathToAdd.isEmpty() && !this.selectedItems.contains(pathToAdd)) { + this.selectedItems.add(pathToAdd); + } + + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#BrowserPage #SelectedSection #SelectedItems.Value", String.join("\n", this.selectedItems)); + this.sendUpdate(commandBuilder); + break; + } + case ConfirmBrowser: { + String pathsToSet; + if (!this.selectedItems.isEmpty()) { + pathsToSet = String.join(",", this.selectedItems); + } else { + pathsToSet = this.getCurrentBrowserPath(); + } + + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#MainPage #PrefabPaths #Input.Value", pathsToSet); + PrefabRootDirectory rootDirValue = this.inAssetsRoot ? PrefabRootDirectory.ASSET : this.getRootDirectoryForPath(this.browserRoot.toString()); + if (rootDirValue != null) { + commandBuilder.set("#MainPage #RootDir #Input.Value", rootDirValue.name()); + } + + commandBuilder.set("#BrowserPage.Visible", false); + commandBuilder.set("#MainPage.Visible", true); + this.sendUpdate(commandBuilder); + break; + } + case CancelBrowser: { + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#BrowserPage.Visible", false); + commandBuilder.set("#MainPage.Visible", true); + this.sendUpdate(commandBuilder); + } + } + } + + private void onLoadingProgress(@Nonnull PrefabLoadingState state) { + if (!this.loadingCancelled) { + this.currentLoadingState = state; + UICommandBuilder builder = new UICommandBuilder(); + builder.set("#LoadingPage #ProgressBar.Value", state.getProgressPercentage()); + builder.set("#LoadingPage #StatusText.TextSpans", state.getStatusMessage()); + if (state.hasErrors()) { + builder.set("#LoadingPage #ErrorText.Visible", true); + builder.set("#LoadingPage #ErrorText.TextSpans", state.getErrors().getLast().toMessage()); + builder.set("#LoadingPage #CancelButton.Visible", true); + } + + this.sendUpdate(builder); + } + } + + private void onLoadingFailed(@Nonnull Message errorMessage) { + this.isLoading = false; + UICommandBuilder builder = new UICommandBuilder(); + builder.set("#LoadingPage #ProgressBar.Value", 0.0F); + builder.set("#LoadingPage #StatusText.TextSpans", Message.translation("server.commands.editprefab.loading.phase.error")); + builder.set("#LoadingPage #ErrorText.Visible", true); + builder.set("#LoadingPage #ErrorText.TextSpans", errorMessage); + builder.set("#LoadingPage #CancelButton.Visible", true); + this.sendUpdate(builder); + } + + private void onShutdownProgress(@Nonnull PrefabLoadingState state) { + UICommandBuilder builder = new UICommandBuilder(); + builder.set("#LoadingPage #ProgressBar.Value", state.getProgressPercentage()); + builder.set("#LoadingPage #StatusText.TextSpans", state.getStatusMessage()); + this.sendUpdate(builder); + } + + private void handleAssetsNavigation(@Nonnull String fileName) { + if ("..".equals(fileName)) { + if (!this.assetsCurrentDir.toString().isEmpty()) { + Path parent = this.assetsCurrentDir.getParent(); + this.assetsCurrentDir = parent != null ? parent : Paths.get(""); + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.buildBrowserList(commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + } + } else { + String currentDirStr = this.assetsCurrentDir.toString().replace('\\', '/'); + String targetVirtualPath = currentDirStr.isEmpty() ? fileName : currentDirStr + "/" + fileName; + Path resolvedPath = this.assetProvider.resolveVirtualPath(targetVirtualPath); + if (resolvedPath == null) { + this.sendUpdate(); + } else { + if (Files.isDirectory(resolvedPath)) { + this.assetsCurrentDir = Paths.get(targetVirtualPath); + this.selectedPath = targetVirtualPath + "/"; + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.buildBrowserList(commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + } else { + this.selectedPath = targetVirtualPath; + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#BrowserPage #CurrentPath.Text", "Assets/" + targetVirtualPath); + this.sendUpdate(commandBuilder); + } + } + } + } + + private void handleRegularNavigation(@Nonnull String fileName) { + Path file = this.browserRoot.resolve(this.browserCurrent).resolve(fileName); + if (!file.normalize().startsWith(this.browserRoot.normalize())) { + this.sendUpdate(); + } else { + if (Files.isDirectory(file)) { + this.browserCurrent = PathUtil.relativize(this.browserRoot, file); + String pathStr = this.browserCurrent.toString().replace('\\', '/'); + this.selectedPath = pathStr.isEmpty() ? "/" : (pathStr.endsWith("/") ? pathStr : pathStr + "/"); + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.buildBrowserList(commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + } else { + this.selectedPath = PathUtil.relativize(this.browserRoot, file).toString().replace('\\', '/'); + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#BrowserPage #CurrentPath.Text", this.selectedPath); + this.sendUpdate(commandBuilder); + } + } + } + + @Nonnull + private String getCurrentBrowserPath() { + if (this.selectedPath != null) { + return this.selectedPath; + } else if (this.inAssetsRoot) { + String currentDirStr = this.assetsCurrentDir.toString().replace('\\', '/'); + return currentDirStr.isEmpty() ? "/" : (currentDirStr.endsWith("/") ? currentDirStr : currentDirStr + "/"); + } else { + String pathStr = this.browserCurrent.toString().replace('\\', '/'); + return pathStr.isEmpty() ? "/" : (pathStr.endsWith("/") ? pathStr : pathStr + "/"); + } + } + + private void buildBrowserList(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) { + commandBuilder.clear("#BrowserPage #BrowserContent #FileList"); + if (this.inAssetsRoot) { + this.buildAssetsBrowserList(commandBuilder, eventBuilder); + } else { + this.buildRegularBrowserList(commandBuilder, eventBuilder); + } + } + + private void buildAssetsBrowserList(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) { + String currentDirStr = this.assetsCurrentDir.toString().replace('\\', '/'); + String displayPath = currentDirStr.isEmpty() ? "Assets" : "Assets/" + currentDirStr; + commandBuilder.set("#BrowserPage #CurrentPath.Text", displayPath); + List entries = this.assetProvider.getFiles(this.assetsCurrentDir, this.browserSearchQuery); + int buttonIndex = 0; + if (!currentDirStr.isEmpty() && this.browserSearchQuery.isEmpty()) { + commandBuilder.append("#BrowserPage #BrowserContent #FileList", "Pages/BasicTextButton.ui"); + commandBuilder.set("#BrowserPage #BrowserContent #FileList[0].Text", "../"); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#BrowserPage #BrowserContent #FileList[0]", + new EventData().append("Action", PrefabEditorLoadSettingsPage.Action.BrowserNavigate.name()).append("File", "..") + ); + buttonIndex++; + } + + for (FileListProvider.FileEntry entry : entries) { + String displayText = entry.isDirectory() ? entry.displayName() + "/" : entry.displayName(); + commandBuilder.append("#BrowserPage #BrowserContent #FileList", "Pages/BasicTextButton.ui"); + commandBuilder.set("#BrowserPage #BrowserContent #FileList[" + buttonIndex + "].Text", displayText); + if (!entry.isDirectory()) { + commandBuilder.set("#BrowserPage #BrowserContent #FileList[" + buttonIndex + "].Style", BUTTON_HIGHLIGHTED); + } + + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#BrowserPage #BrowserContent #FileList[" + buttonIndex + "]", + new EventData().append("Action", PrefabEditorLoadSettingsPage.Action.BrowserNavigate.name()).append("File", entry.name()) + ); + buttonIndex++; + } + } + + private void buildRegularBrowserList(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) { + String rootDisplayPath = this.getRootDisplayPath(this.browserRoot); + String currentPath = this.browserCurrent.toString().replace('\\', '/'); + String currentPathDisplay = currentPath.isEmpty() ? rootDisplayPath : rootDisplayPath + "/" + currentPath; + commandBuilder.set("#BrowserPage #CurrentPath.Text", currentPathDisplay); + List files = new ObjectArrayList<>(); + Path path = this.browserRoot.resolve(this.browserCurrent); + if (Files.isDirectory(path)) { + try (DirectoryStream stream = Files.newDirectoryStream(path)) { + for (Path file : stream) { + String fileName = file.getFileName().toString(); + if (fileName.charAt(0) == '/') { + fileName = fileName.substring(1); + } + + if ((fileName.endsWith(".prefab.json") || Files.isDirectory(file)) + && (this.browserSearchQuery.isEmpty() || fileName.toLowerCase().contains(this.browserSearchQuery))) { + files.add(file.toFile()); + } + } + } catch (IOException var15) { + LOGGER.atSevere().log("Error reading directory for browser", var15); + } + } + + files.sort((a, b) -> { + if (a.isDirectory() == b.isDirectory()) { + return a.compareTo(b); + } else { + return a.isDirectory() ? -1 : 1; + } + }); + int buttonIndex = 0; + if (!this.browserCurrent.toString().isEmpty() && this.browserSearchQuery.isEmpty()) { + commandBuilder.append("#BrowserPage #BrowserContent #FileList", "Pages/BasicTextButton.ui"); + commandBuilder.set("#BrowserPage #BrowserContent #FileList[0].Text", "../"); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#BrowserPage #BrowserContent #FileList[0]", + new EventData().append("Action", PrefabEditorLoadSettingsPage.Action.BrowserNavigate.name()).append("File", "..") + ); + buttonIndex++; + } + + for (File file : files) { + boolean isDirectory = file.isDirectory(); + String fileNamex = file.getName(); + commandBuilder.append("#BrowserPage #BrowserContent #FileList", "Pages/BasicTextButton.ui"); + commandBuilder.set("#BrowserPage #BrowserContent #FileList[" + buttonIndex + "].Text", !isDirectory ? fileNamex : fileNamex + "/"); + if (!isDirectory) { + commandBuilder.set("#BrowserPage #BrowserContent #FileList[" + buttonIndex + "].Style", BUTTON_HIGHLIGHTED); + } + + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#BrowserPage #BrowserContent #FileList[" + buttonIndex + "]", + new EventData().append("Action", PrefabEditorLoadSettingsPage.Action.BrowserNavigate.name()).append("File", fileNamex) + ); + buttonIndex++; + } + } + + @Nonnull + private List buildBrowserRootEntries() { + List roots = new ObjectArrayList<>(); + roots.add(new DropdownEntryInfo(LocalizableString.fromString("Assets"), "Assets")); + roots.add(new DropdownEntryInfo(LocalizableString.fromString("Server"), PrefabStore.get().getServerPrefabsPath().toString())); + return roots; + } + + @Nullable + private Path findActualRootPath(@Nonnull String pathStr) { + for (PrefabStore.AssetPackPrefabPath packPath : PrefabStore.get().getAllAssetPrefabPaths()) { + if (packPath.prefabsPath().toString().equals(pathStr)) { + return packPath.prefabsPath(); + } + } + + if (PrefabStore.get().getServerPrefabsPath().toString().equals(pathStr)) { + return PrefabStore.get().getServerPrefabsPath(); + } else { + return PrefabStore.get().getWorldGenPrefabsPath().toString().equals(pathStr) ? PrefabStore.get().getWorldGenPrefabsPath() : null; + } + } + + @Nullable + private AssetPack findAssetPackForPath(@Nonnull String pathStr) { + Path path = Path.of(pathStr).toAbsolutePath().normalize(); + + for (AssetPack pack : AssetModule.get().getAssetPacks()) { + Path packPrefabsPath = PrefabStore.get().getAssetPrefabsPathForPack(pack).toAbsolutePath().normalize(); + if (path.equals(packPrefabsPath) || path.startsWith(packPrefabsPath)) { + return pack; + } + } + + return null; + } + + @Nullable + private PrefabRootDirectory getRootDirectoryForPath(@Nonnull String pathStr) { + if ("Assets".equals(pathStr)) { + return PrefabRootDirectory.ASSET; + } else if (pathStr.equals(PrefabStore.get().getServerPrefabsPath().toString())) { + return PrefabRootDirectory.SERVER; + } else if (pathStr.equals(PrefabStore.get().getWorldGenPrefabsPath().toString())) { + return PrefabRootDirectory.WORLDGEN; + } else { + return this.findAssetPackForPath(pathStr) != null ? PrefabRootDirectory.ASSET : null; + } + } + + private boolean isAllowedBrowserRoot(@Nonnull String pathStr) { + return SingleplayerModule.isOwner(this.playerRef) ? true : this.getRootDirectoryForPath(pathStr) != null; + } + + @Nonnull + private String getRootDisplayPath(@Nonnull Path root) { + String rootStr = root.toString(); + if (rootStr.equals(PrefabStore.get().getServerPrefabsPath().toString())) { + return "ServerRoot/" + root.getFileName(); + } else if (rootStr.equals(PrefabStore.get().getWorldGenPrefabsPath().toString())) { + Path parent = root.getParent(); + return parent != null && parent.getFileName() != null + ? "WorldgenRoot/" + parent.getFileName() + "/" + root.getFileName() + : "WorldgenRoot/" + root.getFileName(); + } else { + AssetPack pack = this.findAssetPackForPath(rootStr); + if (pack != null) { + String packPrefix = pack.equals(AssetModule.get().getBaseAssetPack()) ? "Assets" : "[" + pack.getName() + "]"; + Path parent = root.getParent(); + return parent != null && parent.getFileName() != null + ? packPrefix + "/" + parent.getFileName() + "/" + root.getFileName() + : packPrefix + "/" + root.getFileName(); + } else { + return root.toString(); + } + } + } + + public static enum Action { + Load, + OpenSavePropertiesDialog, + CancelSavePropertiesDialog, + SavePropertiesConfig, + ApplySavedProperties, + Cancel, + CancelLoading, + SavePropertiesNameChanged, + OpenBrowser, + BrowserNavigate, + BrowserRootChanged, + BrowserSearch, + AddFolderToList, + ConfirmBrowser, + CancelBrowser; + + private Action() { + } + } + + protected static class PageData { + public static final String CONFIG_NAME = "@ConfigName"; + public static final String ROOT_DIR = "@RootDir"; + public static final String PREFAB_PATHS = "@PrefabPaths"; + public static final String RECURSIVE = "@Recursive"; + public static final String CHILDREN = "@Children"; + public static final String ENTITIES = "@Entities"; + public static final String ENABLE_WORLD_TICKING = "@EnableWorldTicking"; + public static final String DESIRED_Y_LEVEL = "@DesiredYLevel"; + public static final String BLOCKS_BETWEEN_PREFABS = "@BlocksBetweenPrefabs"; + public static final String WORLD_GEN_TYPE = "@WorldGenType"; + public static final String ENVIRONMENT = "@Environment"; + public static final String GRASS_TINT = "@GrassTint"; + public static final String NUM_AIR_BEFORE_GROUND = "@NumAirBeforeGround"; + public static final String PASTE_AXIS = "@PasteAxis"; + public static final String ALIGNMENT_METHOD = "@AlignmentMethod"; + public static final String ROW_SPLIT_MODE = "@RowSplitMode"; + public static final String BROWSER_FILE = "File"; + public static final String BROWSER_ROOT = "@BrowserRoot"; + public static final String BROWSER_SEARCH = "@BrowserSearch"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + PrefabEditorLoadSettingsPage.PageData.class, PrefabEditorLoadSettingsPage.PageData::new + ) + .append( + new KeyedCodec<>("Action", new EnumCodec<>(PrefabEditorLoadSettingsPage.Action.class, EnumCodec.EnumStyle.LEGACY)), + (o, uiAction) -> o.uiAction = uiAction, + o -> o.uiAction + ) + .add() + .append(new KeyedCodec<>("@ConfigName", Codec.STRING), (o, configName) -> o.configName = configName, o -> o.configName) + .add() + .append( + new KeyedCodec<>("@RootDir", new EnumCodec<>(PrefabRootDirectory.class, EnumCodec.EnumStyle.LEGACY)), + (o, rootDirectory) -> o.prefabRootDirectory = rootDirectory, + o -> o.prefabRootDirectory + ) + .add() + .append( + new KeyedCodec<>("@PrefabPaths", Codec.STRING), + (o, unprocessedPrefabPaths) -> o.unprocessedPrefabPaths = unprocessedPrefabPaths, + o -> o.unprocessedPrefabPaths + ) + .add() + .append(new KeyedCodec<>("@DesiredYLevel", Codec.INTEGER), (o, pasteYLevelGoal) -> o.pasteYLevelGoal = pasteYLevelGoal, o -> o.pasteYLevelGoal) + .add() + .append( + new KeyedCodec<>("@BlocksBetweenPrefabs", Codec.INTEGER), + (o, blocksBetweenEachPrefab) -> o.blocksBetweenEachPrefab = blocksBetweenEachPrefab, + o -> o.blocksBetweenEachPrefab + ) + .add() + .append( + new KeyedCodec<>("@WorldGenType", new EnumCodec<>(WorldGenType.class, EnumCodec.EnumStyle.LEGACY)), + (o, worldGenType) -> o.worldGenType = worldGenType, + o -> o.worldGenType + ) + .add() + .append(new KeyedCodec<>("@Environment", Codec.STRING), (o, environment) -> o.environment = environment, o -> o.environment) + .add() + .append(new KeyedCodec<>("@GrassTint", Codec.STRING), (o, grassTint) -> o.grassTint = grassTint, o -> o.grassTint) + .add() + .append( + new KeyedCodec<>("@NumAirBeforeGround", Codec.INTEGER), + (o, blocksAboveSurface) -> o.blocksAboveSurface = blocksAboveSurface, + o -> o.blocksAboveSurface + ) + .add() + .append( + new KeyedCodec<>("@PasteAxis", new EnumCodec<>(PrefabStackingAxis.class, EnumCodec.EnumStyle.LEGACY)), + (o, stackingAxis) -> o.stackingAxis = stackingAxis, + o -> o.stackingAxis + ) + .add() + .append( + new KeyedCodec<>("@AlignmentMethod", new EnumCodec<>(PrefabAlignment.class, EnumCodec.EnumStyle.LEGACY)), + (o, alignment) -> o.alignment = alignment, + o -> o.alignment + ) + .add() + .append( + new KeyedCodec<>("@RowSplitMode", new EnumCodec<>(PrefabRowSplitMode.class, EnumCodec.EnumStyle.LEGACY)), + (o, rowSplitMode) -> o.rowSplitMode = rowSplitMode, + o -> o.rowSplitMode + ) + .add() + .append(new KeyedCodec<>("@Recursive", Codec.BOOLEAN), (o, recursive) -> o.recursive = recursive, o -> o.recursive) + .add() + .append(new KeyedCodec<>("@Children", Codec.BOOLEAN), (o, loadChildren) -> o.loadChildren = loadChildren, o -> o.loadChildren) + .add() + .append(new KeyedCodec<>("@Entities", Codec.BOOLEAN), (o, loadEntities) -> o.loadEntities = loadEntities, o -> o.loadEntities) + .add() + .append( + new KeyedCodec<>("@EnableWorldTicking", Codec.BOOLEAN), + (o, enableWorldTicking) -> o.enableWorldTicking = enableWorldTicking, + o -> o.enableWorldTicking + ) + .add() + .append(new KeyedCodec<>("File", Codec.STRING), (o, browserFile) -> o.browserFile = browserFile, o -> o.browserFile) + .add() + .append(new KeyedCodec<>("@BrowserRoot", Codec.STRING), (o, browserRootStr) -> o.browserRootStr = browserRootStr, o -> o.browserRootStr) + .add() + .append(new KeyedCodec<>("@BrowserSearch", Codec.STRING), (o, browserSearchStr) -> o.browserSearchStr = browserSearchStr, o -> o.browserSearchStr) + .add() + .build(); + public String configName; + public PrefabEditorLoadSettingsPage.Action uiAction; + public PrefabRootDirectory prefabRootDirectory = PrefabRootDirectory.ASSET; + public String unprocessedPrefabPaths = ""; + public int pasteYLevelGoal = 55; + public int blocksBetweenEachPrefab = 15; + public WorldGenType worldGenType = PrefabEditLoadCommand.DEFAULT_WORLD_GEN_TYPE; + public String environment = "Env_Zone1_Plains"; + public String grassTint = "#5B9E28"; + public int blocksAboveSurface = 0; + public PrefabStackingAxis stackingAxis = PrefabEditLoadCommand.DEFAULT_PREFAB_STACKING_AXIS; + public PrefabAlignment alignment = PrefabEditLoadCommand.DEFAULT_PREFAB_ALIGNMENT; + public PrefabRowSplitMode rowSplitMode = PrefabEditLoadCommand.DEFAULT_ROW_SPLIT_MODE; + public boolean recursive; + public boolean loadChildren; + public boolean loadEntities; + public boolean enableWorldTicking = false; + public String browserFile; + public String browserRootStr; + public String browserSearchStr; + + public PageData() { + } + + @Nonnull + public PrefabEditorCreationSettings toCreationSettings() { + String normalizedGrassTint = this.grassTint; + if (normalizedGrassTint != null && normalizedGrassTint.length() > 7) { + normalizedGrassTint = normalizedGrassTint.substring(0, 7); + } + + return new PrefabEditorCreationSettings( + this.prefabRootDirectory, + List.of(this.unprocessedPrefabPaths.split(",")), + this.pasteYLevelGoal, + this.blocksBetweenEachPrefab, + this.worldGenType, + this.blocksAboveSurface, + this.stackingAxis, + this.alignment, + this.recursive, + this.loadChildren, + this.loadEntities, + this.enableWorldTicking, + this.rowSplitMode, + this.environment, + normalizedGrassTint + ); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/ui/PrefabEditorSaveSettingsPage.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/ui/PrefabEditorSaveSettingsPage.java new file mode 100644 index 0000000..1ecf1cb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/ui/PrefabEditorSaveSettingsPage.java @@ -0,0 +1,526 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.ui; + +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSession; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditingMetadata; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.saving.PrefabSaver; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.saving.PrefabSaverSettings; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.common.util.StringCompareUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.prefab.PrefabStore; +import com.hypixel.hytale.server.core.ui.Value; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class PrefabEditorSaveSettingsPage extends InteractiveCustomUIPage { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final Value BUTTON_HIGHLIGHTED = Value.ref("Pages/BasicTextButton.ui", "SelectedLabelStyle"); + private static final Value BUTTON_SELECTED = Value.ref("Pages/BasicTextButton.ui", "SelectedLabelStyle"); + @Nonnull + private final PrefabEditSession prefabEditSession; + private volatile boolean isSaving = false; + @Nonnull + private String browserSearchQuery = ""; + private final Set selectedPrefabUuids = new HashSet<>(); + + public PrefabEditorSaveSettingsPage(@Nonnull PlayerRef playerRef, @Nonnull PrefabEditSession prefabEditSession) { + super(playerRef, CustomPageLifetime.CanDismissOrCloseThroughInteraction, PrefabEditorSaveSettingsPage.PageData.CODEC); + this.prefabEditSession = prefabEditSession; + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/PrefabEditorSaveSettings.ui"); + PrefabEditingMetadata selectedPrefab = this.prefabEditSession.getSelectedPrefab(this.playerRef.getUuid()); + if (selectedPrefab != null) { + String prefabPath = selectedPrefab.getPrefabPath().toString().replace('\\', '/'); + commandBuilder.set("#MainPage #PrefabsToSave #Input.Value", prefabPath); + this.selectedPrefabUuids.add(selectedPrefab.getUuid()); + } + + commandBuilder.set("#MainPage #Entities #CheckBox.Value", true); + commandBuilder.set("#MainPage #Empty #CheckBox.Value", false); + commandBuilder.set("#MainPage #Overwrite #CheckBox.Value", true); + commandBuilder.set("#SavingPage.Visible", false); + commandBuilder.set("#SavingPage #ProgressBar.Value", 0.0F); + commandBuilder.set("#SavingPage #StatusText.TextSpans", Message.translation("server.commands.editprefab.save.saving")); + commandBuilder.set("#SavingPage #ErrorText.Visible", false); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#MainPage #SaveButton", + new EventData() + .append("Action", PrefabEditorSaveSettingsPage.Action.Save.name()) + .append("@PrefabsToSave", "#MainPage #PrefabsToSave #Input.Value") + .append("@Entities", "#MainPage #Entities #CheckBox.Value") + .append("@Empty", "#MainPage #Empty #CheckBox.Value") + .append("@Overwrite", "#MainPage #Overwrite #CheckBox.Value") + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, "#MainPage #CancelButton", new EventData().append("Action", PrefabEditorSaveSettingsPage.Action.Cancel.name()) + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#MainPage #PrefabsToSave #BrowseButton", + new EventData().append("Action", PrefabEditorSaveSettingsPage.Action.OpenBrowser.name()) + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#MainPage #PrefabsToSave #SelectAllButton", + new EventData().append("Action", PrefabEditorSaveSettingsPage.Action.SelectAll.name()) + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#MainPage #PrefabsToSave #SelectEditedButton", + new EventData().append("Action", PrefabEditorSaveSettingsPage.Action.SelectEdited.name()) + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, + "#BrowserPage #SearchInput", + new EventData().append("Action", PrefabEditorSaveSettingsPage.Action.BrowserSearch.name()).append("@BrowserSearch", "#BrowserPage #SearchInput.Value"), + false + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#BrowserPage #BrowserButtons #SelectAllBrowserButton", + new EventData().append("Action", PrefabEditorSaveSettingsPage.Action.BrowserSelectAll.name()) + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#BrowserPage #BrowserButtons #ConfirmButton", + new EventData().append("Action", PrefabEditorSaveSettingsPage.Action.ConfirmBrowser.name()) + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#BrowserPage #BrowserButtons #CancelButton", + new EventData().append("Action", PrefabEditorSaveSettingsPage.Action.CancelBrowser.name()) + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#SavingPage #BackButton", + new EventData().append("Action", PrefabEditorSaveSettingsPage.Action.BackFromSaving.name()) + ); + commandBuilder.set("#BrowserPage.Visible", false); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull PrefabEditorSaveSettingsPage.PageData data) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + switch (data.action) { + case Save: + if (this.isSaving) { + return; + } + + String prefabsToSaveStr = data.prefabsToSave; + if (prefabsToSaveStr == null || prefabsToSaveStr.isBlank()) { + this.playerRef.sendMessage(Message.translation("server.commands.editprefab.save.noPrefabsSelected")); + return; + } + + this.isSaving = true; + UICommandBuilder showSavingBuilder = new UICommandBuilder(); + showSavingBuilder.set("#MainPage.Visible", false); + showSavingBuilder.set("#BrowserPage.Visible", false); + showSavingBuilder.set("#SavingPage.Visible", true); + showSavingBuilder.set("#SavingPage #ProgressBar.Value", 0.0F); + showSavingBuilder.set("#SavingPage #StatusText.TextSpans", Message.translation("server.commands.editprefab.save.saving")); + showSavingBuilder.set("#SavingPage #ErrorText.Visible", false); + showSavingBuilder.set("#SavingPage #BackButton.Visible", false); + this.sendUpdate(showSavingBuilder); + PrefabSaverSettings prefabSaverSettings = new PrefabSaverSettings(); + prefabSaverSettings.setBlocks(true); + prefabSaverSettings.setEntities(data.entities); + prefabSaverSettings.setEmpty(data.empty); + prefabSaverSettings.setOverwriteExisting(data.overwrite); + String[] prefabPaths = prefabsToSaveStr.split(","); + List prefabsToSave = new ObjectArrayList<>(); + + for (String pathStr : prefabPaths) { + pathStr = pathStr.trim(); + if (!pathStr.isEmpty()) { + for (PrefabEditingMetadata metadatax : this.prefabEditSession.getLoadedPrefabMetadata().values()) { + String prefabPath = metadatax.getPrefabPath().toString().replace('\\', '/'); + if (prefabPath.equals(pathStr) || prefabPath.endsWith(pathStr)) { + prefabsToSave.add(metadatax); + break; + } + } + } + } + + if (prefabsToSave.isEmpty()) { + this.onSavingFailed(Message.translation("server.commands.editprefab.save.noPrefabsFound")); + return; + } + + int readOnlyCount = 0; + + for (PrefabEditingMetadata metadataxx : prefabsToSave) { + if (metadataxx.isReadOnly()) { + readOnlyCount++; + } + } + + if (readOnlyCount > 0) { + this.playerRef.sendMessage(Message.translation("server.commands.editprefab.save.readOnlyRedirect").param("count", readOnlyCount)); + } + + World world = store.getExternalData().getWorld(); + int totalPrefabs = prefabsToSave.size(); + CompletableFuture[] saveFutures = new CompletableFuture[totalPrefabs]; + + for (int i = 0; i < totalPrefabs; i++) { + PrefabEditingMetadata metadataxxx = prefabsToSave.get(i); + int index = i; + Path savePath = this.getWritableSavePath(metadataxxx); + saveFutures[i] = PrefabSaver.savePrefab( + playerComponent, + world, + savePath, + metadataxxx.getAnchorPoint(), + metadataxxx.getMinPoint(), + metadataxxx.getMaxPoint(), + metadataxxx.getPastePosition(), + metadataxxx.getOriginalFileAnchor(), + prefabSaverSettings + ) + .thenApply( + success -> { + float progress = (float)(index + 1) / totalPrefabs; + UICommandBuilder progressBuilder = new UICommandBuilder(); + progressBuilder.set("#SavingPage #ProgressBar.Value", progress); + progressBuilder.set( + "#SavingPage #StatusText.TextSpans", + Message.translation("server.commands.editprefab.save.progress").param("current", index + 1).param("total", totalPrefabs) + ); + this.sendUpdate(progressBuilder); + return (Boolean)success; + } + ); + } + + CompletableFuture.allOf(saveFutures) + .thenAccept( + unused -> { + int successes = 0; + int failures = 0; + + for (CompletableFuture future : saveFutures) { + if (future.join()) { + successes++; + } else { + failures++; + } + } + + this.isSaving = false; + if (failures == 0) { + this.playerRef + .sendMessage( + Message.translation("server.commands.editprefab.save.saveAll.success").param("successes", successes).param("failures", failures) + ); + playerComponent.getPageManager().setPage(ref, store, Page.None); + } else { + this.onSavingFailed( + Message.translation("server.commands.editprefab.save.saveAll.success").param("successes", successes).param("failures", failures) + ); + } + } + ) + .exceptionally(throwable -> { + this.isSaving = false; + this.onSavingFailed(Message.raw(throwable.getMessage() != null ? throwable.getMessage() : "Unknown error")); + return null; + }); + break; + case Cancel: + playerComponent.getPageManager().setPage(ref, store, Page.None); + break; + case SelectAll: { + String allPaths = this.prefabEditSession + .getLoadedPrefabMetadata() + .values() + .stream() + .map(m -> m.getPrefabPath().toString().replace('\\', '/')) + .collect(Collectors.joining(",")); + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#MainPage #PrefabsToSave #Input.Value", allPaths); + this.sendUpdate(commandBuilder); + break; + } + case SelectEdited: { + String editedPaths = this.prefabEditSession + .getLoadedPrefabMetadata() + .values() + .stream() + .filter(PrefabEditingMetadata::isDirty) + .map(m -> m.getPrefabPath().toString().replace('\\', '/')) + .collect(Collectors.joining(",")); + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#MainPage #PrefabsToSave #Input.Value", editedPaths); + this.sendUpdate(commandBuilder); + if (editedPaths.isEmpty()) { + this.playerRef.sendMessage(Message.translation("server.commands.editprefab.save.noEditedPrefabs")); + } + break; + } + case OpenBrowser: { + this.browserSearchQuery = ""; + this.selectedPrefabUuids.clear(); + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + commandBuilder.set("#MainPage.Visible", false); + commandBuilder.set("#BrowserPage.Visible", true); + commandBuilder.set("#BrowserPage #SearchInput.Value", ""); + this.buildPrefabList(commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + break; + } + case BrowserSearch: { + this.browserSearchQuery = data.browserSearchStr != null ? data.browserSearchStr.trim().toLowerCase() : ""; + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.buildPrefabList(commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + break; + } + case BrowserTogglePrefab: + if (data.prefabUuid != null) { + try { + UUID uuid = UUID.fromString(data.prefabUuid); + if (this.selectedPrefabUuids.contains(uuid)) { + this.selectedPrefabUuids.remove(uuid); + } else { + this.selectedPrefabUuids.add(uuid); + } + + UICommandBuilder commandBuilderx = new UICommandBuilder(); + UIEventBuilder eventBuilderx = new UIEventBuilder(); + this.buildPrefabList(commandBuilderx, eventBuilderx); + this.sendUpdate(commandBuilderx, eventBuilderx, false); + } catch (IllegalArgumentException var18) { + } + } + break; + case BrowserSelectAll: { + this.selectedPrefabUuids.clear(); + + for (PrefabEditingMetadata metadatax : this.prefabEditSession.getLoadedPrefabMetadata().values()) { + this.selectedPrefabUuids.add(metadatax.getUuid()); + } + + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.buildPrefabList(commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + break; + } + case ConfirmBrowser: { + List selectedPaths = new ObjectArrayList<>(); + + for (PrefabEditingMetadata metadata : this.prefabEditSession.getLoadedPrefabMetadata().values()) { + if (this.selectedPrefabUuids.contains(metadata.getUuid())) { + selectedPaths.add(metadata.getPrefabPath().toString().replace('\\', '/')); + } + } + + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#MainPage #PrefabsToSave #Input.Value", String.join(",", selectedPaths)); + commandBuilder.set("#BrowserPage.Visible", false); + commandBuilder.set("#MainPage.Visible", true); + this.sendUpdate(commandBuilder); + break; + } + case CancelBrowser: { + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#BrowserPage.Visible", false); + commandBuilder.set("#MainPage.Visible", true); + this.sendUpdate(commandBuilder); + break; + } + case BackFromSaving: { + this.isSaving = false; + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#SavingPage.Visible", false); + commandBuilder.set("#MainPage.Visible", true); + this.sendUpdate(commandBuilder); + } + } + } + + private void buildPrefabList(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) { + commandBuilder.clear("#BrowserPage #FileList"); + Map loadedPrefabs = this.prefabEditSession.getLoadedPrefabMetadata(); + if (!loadedPrefabs.isEmpty()) { + List prefabsToDisplay; + if (!this.browserSearchQuery.isEmpty()) { + Object2IntMap matchScores = new Object2IntOpenHashMap<>(loadedPrefabs.size()); + + for (PrefabEditingMetadata metadata : loadedPrefabs.values()) { + String fileName = metadata.getPrefabPath().getFileName().toString(); + String baseName = fileName.endsWith(".prefab.json") ? fileName.substring(0, fileName.length() - ".prefab.json".length()) : fileName; + int fuzzyDistance = StringCompareUtil.getFuzzyDistance(baseName, this.browserSearchQuery, Locale.ENGLISH); + if (fuzzyDistance > 0) { + matchScores.put(metadata, fuzzyDistance); + } + } + + prefabsToDisplay = matchScores.keySet().stream().sorted(Comparator.comparingInt(matchScores::getInt).reversed()).collect(Collectors.toList()); + } else { + prefabsToDisplay = loadedPrefabs.values() + .stream() + .sorted(Comparator.comparing(m -> m.getPrefabPath().getFileName().toString().toLowerCase())) + .collect(Collectors.toList()); + } + + int buttonIndex = 0; + + for (PrefabEditingMetadata metadatax : prefabsToDisplay) { + Path prefabPath = metadatax.getPrefabPath(); + String fileName = prefabPath.getFileName().toString(); + String displayName = fileName.endsWith(".prefab.json") ? fileName.substring(0, fileName.length() - ".prefab.json".length()) : fileName; + boolean isSelected = this.selectedPrefabUuids.contains(metadatax.getUuid()); + boolean isReadOnly = metadatax.isReadOnly(); + String checkPrefix = isSelected ? "[x] " : "[ ] "; + String readOnlySuffix = isReadOnly ? " (read-only)" : ""; + String displayText = checkPrefix + displayName + readOnlySuffix; + String relativePath = prefabPath.toString().replace('\\', '/'); + String tooltipText = isReadOnly ? relativePath + "\n(Will save to Server/Prefabs)" : relativePath; + commandBuilder.append("#BrowserPage #FileList", "Pages/BasicTextButton.ui"); + commandBuilder.set("#BrowserPage #FileList[" + buttonIndex + "].Text", displayText); + commandBuilder.set("#BrowserPage #FileList[" + buttonIndex + "].TooltipText", tooltipText); + if (isSelected) { + commandBuilder.set("#BrowserPage #FileList[" + buttonIndex + "].Style", BUTTON_SELECTED); + } + + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#BrowserPage #FileList[" + buttonIndex + "]", + new EventData() + .append("Action", PrefabEditorSaveSettingsPage.Action.BrowserTogglePrefab.name()) + .append("PrefabUuid", metadatax.getUuid().toString()) + ); + buttonIndex++; + } + } + } + + @Nonnull + private Path getWritableSavePath(@Nonnull PrefabEditingMetadata metadata) { + if (!metadata.isReadOnly()) { + return metadata.getPrefabPath(); + } else { + Path originalPath = metadata.getPrefabPath(); + String fileName = originalPath.getFileName().toString(); + Path parent = originalPath.getParent(); + if (parent != null && parent.getFileName() != null) { + String parentName = parent.getFileName().toString(); + return PrefabStore.get().getServerPrefabsPath().resolve(parentName).resolve(fileName); + } else { + return PrefabStore.get().getServerPrefabsPath().resolve(fileName); + } + } + } + + private void onSavingFailed(@Nonnull Message errorMessage) { + this.isSaving = false; + UICommandBuilder builder = new UICommandBuilder(); + builder.set("#SavingPage #ProgressBar.Value", 0.0F); + builder.set("#SavingPage #StatusText.TextSpans", Message.translation("server.commands.editprefab.save.error")); + builder.set("#SavingPage #ErrorText.Visible", true); + builder.set("#SavingPage #ErrorText.TextSpans", errorMessage); + builder.set("#SavingPage #BackButton.Visible", true); + this.sendUpdate(builder); + } + + public static enum Action { + Save, + Cancel, + SelectAll, + SelectEdited, + OpenBrowser, + BrowserSearch, + BrowserTogglePrefab, + BrowserSelectAll, + ConfirmBrowser, + CancelBrowser, + BackFromSaving; + + private Action() { + } + } + + protected static class PageData { + public static final String PREFABS_TO_SAVE = "@PrefabsToSave"; + public static final String ENTITIES = "@Entities"; + public static final String EMPTY = "@Empty"; + public static final String OVERWRITE = "@Overwrite"; + public static final String BROWSER_SEARCH = "@BrowserSearch"; + public static final String PREFAB_UUID = "PrefabUuid"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + PrefabEditorSaveSettingsPage.PageData.class, PrefabEditorSaveSettingsPage.PageData::new + ) + .append( + new KeyedCodec<>("Action", new EnumCodec<>(PrefabEditorSaveSettingsPage.Action.class, EnumCodec.EnumStyle.LEGACY)), + (o, action) -> o.action = action, + o -> o.action + ) + .add() + .append(new KeyedCodec<>("@PrefabsToSave", Codec.STRING), (o, prefabsToSave) -> o.prefabsToSave = prefabsToSave, o -> o.prefabsToSave) + .add() + .append(new KeyedCodec<>("@Entities", Codec.BOOLEAN), (o, entities) -> o.entities = entities, o -> o.entities) + .add() + .append(new KeyedCodec<>("@Empty", Codec.BOOLEAN), (o, empty) -> o.empty = empty, o -> o.empty) + .add() + .append(new KeyedCodec<>("@Overwrite", Codec.BOOLEAN), (o, overwrite) -> o.overwrite = overwrite, o -> o.overwrite) + .add() + .append(new KeyedCodec<>("@BrowserSearch", Codec.STRING), (o, browserSearchStr) -> o.browserSearchStr = browserSearchStr, o -> o.browserSearchStr) + .add() + .append(new KeyedCodec<>("PrefabUuid", Codec.STRING), (o, prefabUuid) -> o.prefabUuid = prefabUuid, o -> o.prefabUuid) + .add() + .build(); + public PrefabEditorSaveSettingsPage.Action action; + public String prefabsToSave; + public boolean entities = true; + public boolean empty = false; + public boolean overwrite = true; + public String browserSearchStr; + public String prefabUuid; + + public PageData() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/ui/PrefabTeleportPage.java b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/ui/PrefabTeleportPage.java new file mode 100644 index 0000000..9553113 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefabeditor/ui/PrefabTeleportPage.java @@ -0,0 +1,161 @@ +package com.hypixel.hytale.builtin.buildertools.prefabeditor.ui; + +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditSession; +import com.hypixel.hytale.builtin.buildertools.prefabeditor.PrefabEditingMetadata; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.StringCompareUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.ui.Value; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class PrefabTeleportPage extends InteractiveCustomUIPage { + private static final Value BUTTON_HIGHLIGHTED = Value.ref("Pages/BasicTextButton.ui", "SelectedLabelStyle"); + @Nonnull + private final PrefabEditSession prefabEditSession; + @Nonnull + private String searchQuery = ""; + + public PrefabTeleportPage(@Nonnull PlayerRef playerRef, @Nonnull PrefabEditSession prefabEditSession) { + super(playerRef, CustomPageLifetime.CanDismiss, PrefabTeleportPage.PageData.CODEC); + this.prefabEditSession = prefabEditSession; + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/PrefabTeleportPage.ui"); + eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#SearchInput", EventData.of("@SearchQuery", "#SearchInput.Value"), false); + this.buildPrefabList(commandBuilder, eventBuilder); + } + + private void buildPrefabList(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) { + commandBuilder.clear("#FileList"); + Map loadedPrefabs = this.prefabEditSession.getLoadedPrefabMetadata(); + if (!loadedPrefabs.isEmpty()) { + List prefabsToDisplay; + if (!this.searchQuery.isEmpty()) { + Object2IntMap matchScores = new Object2IntOpenHashMap<>(loadedPrefabs.size()); + + for (PrefabEditingMetadata metadata : loadedPrefabs.values()) { + String fileName = metadata.getPrefabPath().getFileName().toString(); + String baseName = fileName.endsWith(".prefab.json") ? fileName.substring(0, fileName.length() - ".prefab.json".length()) : fileName; + int fuzzyDistance = StringCompareUtil.getFuzzyDistance(baseName, this.searchQuery, Locale.ENGLISH); + if (fuzzyDistance > 0) { + matchScores.put(metadata, fuzzyDistance); + } + } + + prefabsToDisplay = matchScores.keySet().stream().sorted(Comparator.comparingInt(matchScores::getInt).reversed()).collect(Collectors.toList()); + } else { + prefabsToDisplay = loadedPrefabs.values() + .stream() + .sorted(Comparator.comparing(m -> m.getPrefabPath().getFileName().toString().toLowerCase())) + .collect(Collectors.toList()); + } + + int buttonIndex = 0; + + for (PrefabEditingMetadata metadatax : prefabsToDisplay) { + Path prefabPath = metadatax.getPrefabPath(); + String fileName = prefabPath.getFileName().toString(); + String displayName = fileName.endsWith(".prefab.json") ? fileName.substring(0, fileName.length() - ".prefab.json".length()) : fileName; + commandBuilder.append("#FileList", "Pages/BasicTextButton.ui"); + commandBuilder.set("#FileList[" + buttonIndex + "].Text", displayName); + commandBuilder.set("#FileList[" + buttonIndex + "].Style", BUTTON_HIGHLIGHTED); + commandBuilder.set("#FileList[" + buttonIndex + "].TooltipText", prefabPath.toString()); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, "#FileList[" + buttonIndex + "]", EventData.of("PrefabUuid", metadatax.getUuid().toString()) + ); + buttonIndex++; + } + } + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull PrefabTeleportPage.PageData data) { + if (data.getSearchQuery() != null) { + this.searchQuery = data.getSearchQuery().trim().toLowerCase(); + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.buildPrefabList(commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + } else { + if (data.getPrefabUuid() != null) { + try { + UUID prefabUuid = UUID.fromString(data.getPrefabUuid()); + PrefabEditingMetadata metadata = this.prefabEditSession.getLoadedPrefabMetadata().get(prefabUuid); + if (metadata != null) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Vector3i minPoint = metadata.getMinPoint(); + Vector3i maxPoint = metadata.getMaxPoint(); + int centerX = (minPoint.x + maxPoint.x) / 2; + int centerZ = (minPoint.z + maxPoint.z) / 2; + World world = store.getExternalData().getWorld(); + WorldChunk worldChunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(centerX, centerZ)); + int teleportY = worldChunk != null ? worldChunk.getHeight(centerX, centerZ) + 8 : maxPoint.y + 8; + Vector3d teleportPosition = new Vector3d(centerX + 0.5, teleportY, centerZ + 0.5); + store.addComponent(ref, Teleport.getComponentType(), new Teleport(teleportPosition, new Vector3f())); + playerComponent.getPageManager().setPage(ref, store, Page.None); + } + } catch (IllegalArgumentException var15) { + } + } + } + } + + public static class PageData { + static final String KEY_SEARCH_QUERY = "@SearchQuery"; + static final String KEY_PREFAB_UUID = "PrefabUuid"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + PrefabTeleportPage.PageData.class, PrefabTeleportPage.PageData::new + ) + .addField(new KeyedCodec<>("@SearchQuery", Codec.STRING), (entry, s) -> entry.searchQuery = s, entry -> entry.searchQuery) + .addField(new KeyedCodec<>("PrefabUuid", Codec.STRING), (entry, s) -> entry.prefabUuid = s, entry -> entry.prefabUuid) + .build(); + private String searchQuery; + private String prefabUuid; + + public PageData() { + } + + public String getSearchQuery() { + return this.searchQuery; + } + + public String getPrefabUuid() { + return this.prefabUuid; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefablist/AssetPrefabFileProvider.java b/src/com/hypixel/hytale/builtin/buildertools/prefablist/AssetPrefabFileProvider.java new file mode 100644 index 0000000..9768ac3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefablist/AssetPrefabFileProvider.java @@ -0,0 +1,213 @@ +package com.hypixel.hytale.builtin.buildertools.prefablist; + +import com.hypixel.hytale.common.util.StringCompareUtil; +import com.hypixel.hytale.server.core.prefab.PrefabStore; +import com.hypixel.hytale.server.core.ui.browser.FileListProvider; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetPrefabFileProvider implements FileListProvider { + private static final String PREFAB_EXTENSION = ".prefab.json"; + private static final int MAX_SEARCH_RESULTS = 50; + + public AssetPrefabFileProvider() { + } + + @Nonnull + @Override + public List getFiles(@Nonnull Path currentDir, @Nonnull String searchQuery) { + String currentDirStr = currentDir.toString().replace('\\', '/'); + if (!searchQuery.isEmpty()) { + return this.buildSearchResults(currentDirStr, searchQuery); + } else { + return currentDirStr.isEmpty() ? this.buildPackListings() : this.buildPackDirectoryListing(currentDirStr); + } + } + + @Nonnull + private List buildPackListings() { + List entries = new ObjectArrayList<>(); + + for (PrefabStore.AssetPackPrefabPath packPath : PrefabStore.get().getAllAssetPrefabPaths()) { + String displayName = packPath.getDisplayName(); + String packKey = this.getPackKey(packPath); + entries.add(new FileListProvider.FileEntry(packKey, displayName, true)); + } + + entries.sort(Comparator.comparing(FileListProvider.FileEntry::displayName, String.CASE_INSENSITIVE_ORDER)); + return entries; + } + + @Nonnull + private List buildPackDirectoryListing(@Nonnull String currentDirStr) { + List entries = new ObjectArrayList<>(); + String[] parts = currentDirStr.split("/", 2); + String packKey = parts[0]; + String subPath = parts.length > 1 ? parts[1] : ""; + PrefabStore.AssetPackPrefabPath packPath = this.findPackByKey(packKey); + if (packPath == null) { + return entries; + } else { + Path targetPath = packPath.prefabsPath(); + if (!subPath.isEmpty()) { + targetPath = targetPath.resolve(subPath); + } + + if (!Files.isDirectory(targetPath)) { + return entries; + } else { + try (DirectoryStream stream = Files.newDirectoryStream(targetPath)) { + for (Path file : stream) { + String fileName = file.getFileName().toString(); + if (!fileName.startsWith(".")) { + boolean isDirectory = Files.isDirectory(file); + if (isDirectory || fileName.endsWith(".prefab.json")) { + String displayName = isDirectory ? fileName : this.removeExtension(fileName); + entries.add(new FileListProvider.FileEntry(fileName, displayName, isDirectory)); + } + } + } + } catch (IOException var16) { + } + + entries.sort((a, b) -> { + if (a.isDirectory() == b.isDirectory()) { + return a.displayName().compareToIgnoreCase(b.displayName()); + } else { + return a.isDirectory() ? -1 : 1; + } + }); + return entries; + } + } + } + + @Nonnull + private List buildSearchResults(@Nonnull String currentDirStr, @Nonnull String searchQuery) { + List allResults = new ObjectArrayList<>(); + String lowerQuery = searchQuery.toLowerCase(); + if (currentDirStr.isEmpty()) { + for (PrefabStore.AssetPackPrefabPath packPath : PrefabStore.get().getAllAssetPrefabPaths()) { + String packKey = this.getPackKey(packPath); + this.searchInDirectory(packPath.prefabsPath(), packKey, "", lowerQuery, allResults); + } + } else { + String[] parts = currentDirStr.split("/", 2); + String packKey = parts[0]; + String subPath = parts.length > 1 ? parts[1] : ""; + PrefabStore.AssetPackPrefabPath packPath = this.findPackByKey(packKey); + if (packPath != null) { + Path searchRoot = packPath.prefabsPath(); + if (!subPath.isEmpty()) { + searchRoot = searchRoot.resolve(subPath); + } + + this.searchInDirectory(searchRoot, packKey, subPath, lowerQuery, allResults); + } + } + + allResults.sort(Comparator.comparingInt(AssetPrefabFileProvider.SearchResult::score).reversed()); + List entries = new ObjectArrayList<>(); + + for (int i = 0; i < Math.min(allResults.size(), 50); i++) { + AssetPrefabFileProvider.SearchResult result = allResults.get(i); + entries.add(new FileListProvider.FileEntry(result.relativePath(), result.displayName(), false, result.score())); + } + + return entries; + } + + private void searchInDirectory( + @Nonnull final Path root, + @Nonnull final String packKey, + @Nonnull final String basePath, + @Nonnull final String searchQuery, + @Nonnull final List results + ) { + if (Files.isDirectory(root)) { + try { + Files.walkFileTree( + root, + new SimpleFileVisitor() { + @Nonnull + public FileVisitResult visitFile(@Nonnull Path file, @Nonnull BasicFileAttributes attrs) { + String fileName = file.getFileName().toString(); + if (fileName.endsWith(".prefab.json")) { + String baseName = AssetPrefabFileProvider.this.removeExtension(fileName); + int score = StringCompareUtil.getFuzzyDistance(baseName.toLowerCase(), searchQuery, Locale.ENGLISH); + if (score > 0) { + Path relativePath = root.relativize(file); + String fullRelativePath = basePath.isEmpty() + ? packKey + "/" + relativePath.toString().replace('\\', '/') + : packKey + "/" + basePath + "/" + relativePath.toString().replace('\\', '/'); + results.add(new AssetPrefabFileProvider.SearchResult(fullRelativePath, baseName, score)); + } + } + + return FileVisitResult.CONTINUE; + } + } + ); + } catch (IOException var7) { + } + } + } + + @Nonnull + private String getPackKey(@Nonnull PrefabStore.AssetPackPrefabPath packPath) { + return packPath.getDisplayName(); + } + + @Nullable + private PrefabStore.AssetPackPrefabPath findPackByKey(@Nonnull String packKey) { + for (PrefabStore.AssetPackPrefabPath packPath : PrefabStore.get().getAllAssetPrefabPaths()) { + if (this.getPackKey(packPath).equals(packKey)) { + return packPath; + } + } + + return null; + } + + @Nonnull + private String removeExtension(@Nonnull String fileName) { + return fileName.endsWith(".prefab.json") ? fileName.substring(0, fileName.length() - ".prefab.json".length()) : fileName; + } + + @Nullable + public Path resolveVirtualPath(@Nonnull String virtualPath) { + if (virtualPath.isEmpty()) { + return null; + } else { + String[] parts = virtualPath.split("/", 2); + String packKey = parts[0]; + String subPath = parts.length > 1 ? parts[1] : ""; + PrefabStore.AssetPackPrefabPath packPath = this.findPackByKey(packKey); + if (packPath == null) { + return null; + } else { + return subPath.isEmpty() ? packPath.prefabsPath() : packPath.prefabsPath().resolve(subPath); + } + } + } + + @Nonnull + public String getPackDisplayName(@Nonnull String packKey) { + PrefabStore.AssetPackPrefabPath packPath = this.findPackByKey(packKey); + return packPath != null ? packPath.getDisplayName() : packKey; + } + + private record SearchResult(@Nonnull String relativePath, @Nonnull String displayName, int score) { + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefablist/PrefabPage.java b/src/com/hypixel/hytale/builtin/buildertools/prefablist/PrefabPage.java new file mode 100644 index 0000000..3f72feb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefablist/PrefabPage.java @@ -0,0 +1,319 @@ +package com.hypixel.hytale.builtin.buildertools.prefablist; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.protocol.packets.inventory.SetActiveSlot; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.prefab.PrefabStore; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.ui.Value; +import com.hypixel.hytale.server.core.ui.browser.FileBrowserConfig; +import com.hypixel.hytale.server.core.ui.browser.FileBrowserEventData; +import com.hypixel.hytale.server.core.ui.browser.FileListProvider; +import com.hypixel.hytale.server.core.ui.browser.ServerFileBrowser; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import javax.annotation.Nonnull; + +public class PrefabPage extends InteractiveCustomUIPage { + private static final String PASTE_TOOL_ID = "EditorTool_Paste"; + private static final String ASSETS_ROOT_KEY = "Assets"; + @Nonnull + private final ServerFileBrowser browser; + @Nonnull + private final BuilderToolsPlugin.BuilderState builderState; + @Nonnull + private final AssetPrefabFileProvider assetProvider; + private boolean inAssetsRoot = true; + @Nonnull + private Path assetsCurrentDir = Paths.get(""); + + public PrefabPage(@Nonnull PlayerRef playerRef, Path defaultRoot, @Nonnull BuilderToolsPlugin.BuilderState builderState) { + super(playerRef, CustomPageLifetime.CanDismiss, FileBrowserEventData.CODEC); + this.builderState = builderState; + this.assetProvider = new AssetPrefabFileProvider(); + PrefabStore prefabStore = PrefabStore.get(); + List roots = buildRootEntries(prefabStore); + FileBrowserConfig config = FileBrowserConfig.builder() + .listElementId("#FileList") + .rootSelectorId("#RootSelector") + .searchInputId("#SearchInput") + .roots(roots) + .allowedExtensions(".prefab.json") + .enableRootSelector(true) + .enableSearch(true) + .enableDirectoryNav(true) + .maxResults(50) + .build(); + String savedSearchQuery = builderState.getPrefabListSearchQuery(); + Path initialRoot = roots.get(0).path(); + this.browser = new ServerFileBrowser(config, initialRoot, null); + if (savedSearchQuery != null && !savedSearchQuery.isEmpty()) { + this.browser.setSearchQuery(savedSearchQuery); + } + } + + @Nonnull + private static List buildRootEntries(@Nonnull PrefabStore prefabStore) { + List roots = new ObjectArrayList<>(); + roots.add(new FileBrowserConfig.RootEntry("Assets", Paths.get("Assets"))); + roots.add(new FileBrowserConfig.RootEntry("Server", prefabStore.getServerPrefabsPath())); + return roots; + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/PrefabListPage.ui"); + this.browser.buildRootSelector(commandBuilder, eventBuilder); + this.browser.buildSearchInput(commandBuilder, eventBuilder); + this.buildCurrentPath(commandBuilder); + this.buildFileList(commandBuilder, eventBuilder); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull FileBrowserEventData data) { + if (data.getRoot() != null) { + this.inAssetsRoot = "Assets".equals(data.getRoot()); + this.assetsCurrentDir = Paths.get(""); + this.browser.handleEvent(data); + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.buildCurrentPath(commandBuilder); + this.buildFileList(commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + } else if (data.getSearchQuery() != null) { + this.browser.handleEvent(data); + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.buildCurrentPath(commandBuilder); + this.buildFileList(commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + } else { + String selectedPath = data.getSearchResult() != null ? data.getSearchResult() : data.getFile(); + if (selectedPath != null) { + if (this.inAssetsRoot) { + this.handleAssetsNavigation(ref, store, selectedPath, data.getSearchResult() != null); + } else { + this.handleRegularNavigation(ref, store, selectedPath, data.getSearchResult() != null); + } + } + } + } + + private void handleAssetsNavigation(@Nonnull Ref ref, @Nonnull Store store, @Nonnull String selectedPath, boolean isSearchResult) { + if ("..".equals(selectedPath)) { + if (!this.assetsCurrentDir.toString().isEmpty()) { + Path parent = this.assetsCurrentDir.getParent(); + this.assetsCurrentDir = parent != null ? parent : Paths.get(""); + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.buildCurrentPath(commandBuilder); + this.buildFileList(commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + } + } else { + String targetVirtualPath; + if (isSearchResult) { + targetVirtualPath = selectedPath; + } else { + targetVirtualPath = this.assetsCurrentDir.toString().isEmpty() + ? selectedPath + : this.assetsCurrentDir.toString().replace('\\', '/') + "/" + selectedPath; + } + + Path resolvedPath = this.assetProvider.resolveVirtualPath(targetVirtualPath); + if (resolvedPath == null) { + this.sendUpdate(); + } else { + if (Files.isDirectory(resolvedPath)) { + this.assetsCurrentDir = Paths.get(targetVirtualPath); + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.buildCurrentPath(commandBuilder); + this.buildFileList(commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + } else { + this.handlePrefabSelection(ref, store, resolvedPath, targetVirtualPath); + } + } + } + } + + private void handleRegularNavigation(@Nonnull Ref ref, @Nonnull Store store, @Nonnull String selectedPath, boolean isSearchResult) { + if (this.browser.handleEvent(FileBrowserEventData.file(selectedPath))) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.buildCurrentPath(commandBuilder); + this.buildFileList(commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + } else { + Path file; + if (isSearchResult) { + file = this.browser.resolveSecure(selectedPath); + } else { + file = this.browser.resolveFromCurrent(selectedPath); + } + + if (file != null && !Files.isDirectory(file)) { + this.handlePrefabSelection(ref, store, file, selectedPath); + } else { + this.sendUpdate(); + } + } + } + + private void handlePrefabSelection(@Nonnull Ref ref, @Nonnull Store store, @Nonnull Path file, @Nonnull String displayPath) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (playerComponent.getGameMode() != GameMode.Creative) { + playerComponent.getPageManager().setPage(ref, store, Page.None); + } else { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerComponent.getPageManager().setPage(ref, store, Page.None); + BlockSelection prefab = PrefabStore.get().getPrefab(file); + BuilderToolsPlugin.addToQueue(playerComponent, playerRefComponent, (r, s, componentAccessor) -> s.load(displayPath, prefab, componentAccessor)); + this.switchToPasteTool(playerComponent, playerRefComponent); + } + } + + private void buildCurrentPath(@Nonnull UICommandBuilder commandBuilder) { + String displayPath; + if (this.inAssetsRoot) { + String currentDirStr = this.assetsCurrentDir.toString().replace('\\', '/'); + displayPath = currentDirStr.isEmpty() ? "Assets" : "Assets/" + currentDirStr; + } else { + Path root = this.browser.getRoot(); + String rootDisplay = this.getRootDisplayName(root); + String currentPath = this.browser.getCurrentDir().toString().replace('\\', '/'); + displayPath = currentPath.isEmpty() ? rootDisplay : rootDisplay + "/" + currentPath; + } + + commandBuilder.set("#CurrentPath.Text", displayPath); + } + + @Nonnull + private String getRootDisplayName(@Nonnull Path root) { + PrefabStore prefabStore = PrefabStore.get(); + String rootStr = root.toString(); + if ("Assets".equals(rootStr)) { + return "Assets"; + } else if (rootStr.equals(prefabStore.getServerPrefabsPath().toString())) { + return "Server"; + } else if (rootStr.equals(prefabStore.getWorldGenPrefabsPath().toString())) { + return "WorldGen"; + } else { + return root.getFileName() != null ? root.getFileName().toString() : rootStr; + } + } + + private void buildFileList(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) { + if (this.inAssetsRoot) { + this.buildAssetsFileList(commandBuilder, eventBuilder); + } else { + this.browser.buildFileList(commandBuilder, eventBuilder); + } + } + + private void buildAssetsFileList(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) { + commandBuilder.clear("#FileList"); + List entries = this.assetProvider.getFiles(this.assetsCurrentDir, this.browser.getSearchQuery()); + int buttonIndex = 0; + if (!this.assetsCurrentDir.toString().isEmpty() && this.browser.getSearchQuery().isEmpty()) { + commandBuilder.append("#FileList", "Pages/BasicTextButton.ui"); + commandBuilder.set("#FileList[0].Text", "../"); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#FileList[0]", EventData.of("File", "..")); + buttonIndex++; + } + + for (FileListProvider.FileEntry entry : entries) { + String displayText = entry.isDirectory() ? entry.displayName() + "/" : entry.displayName(); + commandBuilder.append("#FileList", "Pages/BasicTextButton.ui"); + commandBuilder.set("#FileList[" + buttonIndex + "].Text", displayText); + if (!entry.isDirectory()) { + commandBuilder.set("#FileList[" + buttonIndex + "].Style", Value.ref("Pages/BasicTextButton.ui", "SelectedLabelStyle")); + } + + String eventKey = !this.browser.getSearchQuery().isEmpty() && !entry.isDirectory() ? "SearchResult" : "File"; + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#FileList[" + buttonIndex + "]", EventData.of(eventKey, entry.name())); + buttonIndex++; + } + } + + private void switchToPasteTool(@Nonnull Player playerComponent, @Nonnull PlayerRef playerRef) { + Inventory inventory = playerComponent.getInventory(); + ItemContainer hotbar = inventory.getHotbar(); + ItemContainer storage = inventory.getStorage(); + ItemContainer tools = inventory.getTools(); + int hotbarSize = hotbar.getCapacity(); + + for (short slot = 0; slot < hotbarSize; slot++) { + ItemStack itemStack = hotbar.getItemStack(slot); + if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) { + inventory.setActiveHotbarSlot((byte)slot); + playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)slot)); + return; + } + } + + short emptySlot = -1; + + for (short slotx = 0; slotx < hotbarSize; slotx++) { + ItemStack itemStack = hotbar.getItemStack(slotx); + if (itemStack == null || itemStack.isEmpty()) { + emptySlot = slotx; + break; + } + } + + if (emptySlot != -1) { + for (short slotxx = 0; slotxx < storage.getCapacity(); slotxx++) { + ItemStack itemStack = storage.getItemStack(slotxx); + if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) { + storage.moveItemStackFromSlotToSlot(slotxx, 1, hotbar, emptySlot); + inventory.setActiveHotbarSlot((byte)emptySlot); + playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)emptySlot)); + return; + } + } + + ItemStack pasteToolStack = null; + + for (short slotxxx = 0; slotxxx < tools.getCapacity(); slotxxx++) { + ItemStack itemStack = tools.getItemStack(slotxxx); + if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) { + pasteToolStack = itemStack; + break; + } + } + + if (pasteToolStack != null) { + hotbar.setItemStackForSlot(emptySlot, new ItemStack(pasteToolStack.getItemId())); + inventory.setActiveHotbarSlot((byte)emptySlot); + playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)emptySlot)); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/prefablist/PrefabSavePage.java b/src/com/hypixel/hytale/builtin/buildertools/prefablist/PrefabSavePage.java new file mode 100644 index 0000000..9f755db --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/prefablist/PrefabSavePage.java @@ -0,0 +1,123 @@ +package com.hypixel.hytale.builtin.buildertools.prefablist; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PrefabSavePage extends InteractiveCustomUIPage { + @Nonnull + private static final Message MESSAGE_SERVER_BUILDER_TOOLS_PREFAB_SAVE_NAME_REQUIRED = Message.translation("server.builderTools.prefabSave.nameRequired"); + + public PrefabSavePage(@Nonnull PlayerRef playerRef) { + super(playerRef, CustomPageLifetime.CanDismissOrCloseThroughInteraction, PrefabSavePage.PageData.CODEC); + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/PrefabSavePage.ui"); + commandBuilder.set("#Entities #CheckBox.Value", true); + commandBuilder.set("#Empty #CheckBox.Value", false); + commandBuilder.set("#Overwrite #CheckBox.Value", false); + commandBuilder.set("#FromClipboard #CheckBox.Value", false); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#SaveButton", + new EventData() + .append("Action", PrefabSavePage.Action.Save.name()) + .append("@Name", "#NameInput.Value") + .append("@Entities", "#Entities #CheckBox.Value") + .append("@Empty", "#Empty #CheckBox.Value") + .append("@Overwrite", "#Overwrite #CheckBox.Value") + .append("@FromClipboard", "#FromClipboard #CheckBox.Value") + ); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#CancelButton", new EventData().append("Action", PrefabSavePage.Action.Cancel.name())); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull PrefabSavePage.PageData data) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + switch (data.action) { + case Save: + if (data.name == null || data.name.isBlank()) { + this.playerRef.sendMessage(MESSAGE_SERVER_BUILDER_TOOLS_PREFAB_SAVE_NAME_REQUIRED); + this.sendUpdate(); + return; + } + + playerComponent.getPageManager().setPage(ref, store, Page.None); + BuilderToolsPlugin.addToQueue(playerComponent, this.playerRef, (r, s, componentAccessor) -> { + if (data.fromClipboard) { + s.save(r, data.name, true, data.overwrite, componentAccessor); + } else { + s.saveFromSelection(r, data.name, true, data.overwrite, data.entities, data.empty, componentAccessor); + } + }); + break; + case Cancel: + playerComponent.getPageManager().setPage(ref, store, Page.None); + } + } + + public static enum Action { + Save, + Cancel; + + private Action() { + } + } + + protected static class PageData { + public static final String NAME = "@Name"; + public static final String ENTITIES = "@Entities"; + public static final String EMPTY = "@Empty"; + public static final String OVERWRITE = "@Overwrite"; + public static final String FROM_CLIPBOARD = "@FromClipboard"; + public static final BuilderCodec CODEC = BuilderCodec.builder(PrefabSavePage.PageData.class, PrefabSavePage.PageData::new) + .append( + new KeyedCodec<>("Action", new EnumCodec<>(PrefabSavePage.Action.class, EnumCodec.EnumStyle.LEGACY)), + (o, action) -> o.action = action, + o -> o.action + ) + .add() + .append(new KeyedCodec<>("@Name", Codec.STRING), (o, name) -> o.name = name, o -> o.name) + .add() + .append(new KeyedCodec<>("@Entities", Codec.BOOLEAN), (o, entities) -> o.entities = entities, o -> o.entities) + .add() + .append(new KeyedCodec<>("@Empty", Codec.BOOLEAN), (o, empty) -> o.empty = empty, o -> o.empty) + .add() + .append(new KeyedCodec<>("@Overwrite", Codec.BOOLEAN), (o, overwrite) -> o.overwrite = overwrite, o -> o.overwrite) + .add() + .append(new KeyedCodec<>("@FromClipboard", Codec.BOOLEAN), (o, fromClipboard) -> o.fromClipboard = fromClipboard, o -> o.fromClipboard) + .add() + .build(); + public PrefabSavePage.Action action; + public String name; + public boolean entities = true; + public boolean empty = false; + public boolean overwrite = false; + public boolean fromClipboard = false; + + public PageData() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/BrushConfig.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/BrushConfig.java new file mode 100644 index 0000000..725ae5f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/BrushConfig.java @@ -0,0 +1,454 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes; + +import com.hypixel.hytale.builtin.buildertools.utils.FluidPatternHelper; +import com.hypixel.hytale.builtin.buildertools.utils.Material; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.packets.buildertools.BrushShape; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockMask; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import java.util.Random; +import java.util.function.BiConsumer; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BrushConfig { + private static final Random random = new Random(); + @Nullable + private InteractionType interactionType; + private boolean isHoldDownInteraction; + private Vector3i origin; + private boolean isCurrentlyExecuting; + private boolean hasExecutionContextEncounteredError; + @Nullable + private String executionErrorMessage; + private Vector3i originOffset = new Vector3i(0, 0, 0); + @Nullable + private Vector3i originAfterOffset; + private BrushShape shape; + private int shapeWidth; + private int shapeHeight; + private int shapeThickness; + private boolean capped; + private BlockPattern pattern; + private int density; + private boolean enableBrushMask; + private BlockMask brushMask; + private boolean enableOperationMask; + private BlockMask operationMask; + private BlockMask combinedMasks; + private BrushConfig.HistoryMask historyMask; + + public BrushConfig() { + this.resetToDefaultValues(); + } + + public BrushConfig(@Nonnull BrushConfig other) { + this.interactionType = other.interactionType; + this.origin = other.origin; + this.isCurrentlyExecuting = other.isCurrentlyExecuting; + this.hasExecutionContextEncounteredError = other.hasExecutionContextEncounteredError; + this.executionErrorMessage = other.executionErrorMessage; + this.originOffset = other.originOffset.clone(); + this.originAfterOffset = other.originAfterOffset.clone(); + this.shape = other.shape; + this.shapeWidth = other.shapeWidth; + this.shapeHeight = other.shapeHeight; + this.shapeThickness = other.shapeThickness; + this.capped = other.capped; + this.pattern = other.pattern; + this.density = other.density; + this.enableBrushMask = other.enableBrushMask; + this.brushMask = other.brushMask; + this.enableOperationMask = other.enableOperationMask; + this.operationMask = other.operationMask; + this.combinedMasks = other.combinedMasks; + this.historyMask = other.historyMask; + this.isHoldDownInteraction = other.isHoldDownInteraction; + } + + public void beginExecution(Vector3i origin, boolean isHoldDownInteraction, InteractionType interactionType) { + this.isCurrentlyExecuting = true; + this.origin = origin; + this.isHoldDownInteraction = isHoldDownInteraction; + this.interactionType = interactionType; + this.updateOriginWithOffsets(); + } + + public void endExecution() { + this.resetToDefaultValues(); + } + + public void resetToDefaultValues() { + this.interactionType = null; + this.origin = new Vector3i(0, 0, 0); + this.isCurrentlyExecuting = false; + this.hasExecutionContextEncounteredError = false; + this.executionErrorMessage = null; + this.originOffset = new Vector3i(0, 0, 0); + this.originAfterOffset = null; + this.shape = BrushShape.Cube; + this.shapeWidth = 5; + this.shapeHeight = 5; + this.shapeThickness = 0; + this.capped = false; + this.pattern = BlockPattern.parse("Rock_Stone"); + this.density = 100; + this.enableBrushMask = true; + this.brushMask = BlockMask.EMPTY; + this.enableOperationMask = true; + this.operationMask = BlockMask.EMPTY; + this.combinedMasks = BlockMask.EMPTY; + this.historyMask = BrushConfig.HistoryMask.None; + } + + public boolean isHoldDownInteraction() { + return this.isHoldDownInteraction; + } + + public boolean isCurrentlyExecuting() { + return this.isCurrentlyExecuting; + } + + @Nullable + public InteractionType getInteractionType() { + return this.interactionType; + } + + @Nullable + public Vector3i getOrigin() { + return this.originAfterOffset; + } + + @Nonnull + public Vector3i getOriginOffset() { + return this.originOffset.clone(); + } + + public void setOriginOffset(Vector3i originOffset) { + this.originOffset = originOffset; + this.updateOriginWithOffsets(); + } + + public void modifyOriginOffset(@Nonnull Vector3i originOffsetOffset) { + this.originOffset = this.originOffset.add(originOffsetOffset); + this.updateOriginWithOffsets(); + } + + public void updateOriginWithOffsets() { + if (this.origin != null) { + this.originAfterOffset = this.origin.clone().add(this.originOffset); + } + } + + @Nonnull + public Random getRandom() { + return random; + } + + public int getNextBlock() { + return this.pattern.nextBlock(random); + } + + @Nonnull + public Material getNextMaterial() { + BlockPattern.BlockEntry blockEntry = this.pattern.nextBlockTypeKey(random); + if (blockEntry != null) { + FluidPatternHelper.FluidInfo fluidInfo = FluidPatternHelper.getFluidInfo(blockEntry.blockTypeKey()); + if (fluidInfo != null) { + return Material.fluid(fluidInfo.fluidId(), fluidInfo.fluidLevel()); + } + } + + return Material.block(this.pattern.nextBlock(random)); + } + + public BlockMask getBlockMask() { + return this.combinedMasks; + } + + public void setOperationMask(BlockMask mask) { + this.operationMask = mask; + this.refreshCombinedMasks(); + } + + public void appendOperationMask(BlockMask mask) { + this.operationMask = BlockMask.combine(mask, this.operationMask); + this.refreshCombinedMasks(); + } + + public void clearOperationMask() { + this.operationMask = BlockMask.EMPTY; + this.refreshCombinedMasks(); + } + + public void setUseBrushMask(boolean useBrushMask) { + this.enableBrushMask = useBrushMask; + this.refreshCombinedMasks(); + } + + public void setUseOperationMask(boolean useOperationMask) { + this.enableOperationMask = useOperationMask; + this.refreshCombinedMasks(); + } + + public void setBrushMask(BlockMask mask) { + this.brushMask = mask; + this.refreshCombinedMasks(); + } + + private void refreshCombinedMasks() { + if (this.enableBrushMask && this.enableOperationMask) { + this.combinedMasks = BlockMask.combine(this.brushMask, this.operationMask); + } else if (this.enableBrushMask) { + this.combinedMasks = this.brushMask; + } else if (this.enableOperationMask) { + this.combinedMasks = this.operationMask; + } + } + + public int getDensity() { + return this.density; + } + + public void setDensity(int density) { + this.density = MathUtil.clamp(density, 1, 100); + } + + public BrushConfig.HistoryMask getHistoryMask() { + return this.historyMask; + } + + public void setHistoryMask(BrushConfig.HistoryMask historyMask) { + this.historyMask = historyMask; + } + + public int getShapeWidth() { + return this.shapeWidth; + } + + public void setShapeWidth(int shapeWidth) { + if (shapeWidth <= 0) { + this.setErrorFlag("You cannot set shape width to be less than or equal to zero. Width: " + shapeWidth); + } else { + this.shapeWidth = shapeWidth; + } + } + + public int getShapeHeight() { + return this.shapeHeight; + } + + public void setShapeHeight(int shapeHeight) { + if (shapeHeight <= 0) { + this.setErrorFlag("You cannot set shape height to be less than or equal to zero. Height: " + shapeHeight); + } else { + this.shapeHeight = shapeHeight; + } + } + + public int getShapeThickness() { + return this.shapeThickness; + } + + public void setShapeThickness(int shapeThickness) { + this.shapeThickness = shapeThickness; + } + + public boolean isCapped() { + return this.capped; + } + + public void setCapped(boolean capped) { + this.capped = capped; + } + + public BrushShape getShape() { + return this.shape; + } + + public void setShape(BrushShape shape) { + this.shape = shape; + } + + public BlockPattern getPattern() { + return this.pattern; + } + + public void setPattern(BlockPattern pattern) { + this.pattern = pattern; + } + + public void setErrorFlag(String errorMessage) { + this.hasExecutionContextEncounteredError = true; + this.executionErrorMessage = errorMessage; + } + + public void clearError() { + this.hasExecutionContextEncounteredError = false; + this.executionErrorMessage = null; + } + + public boolean isHasExecutionContextEncounteredError() { + return this.hasExecutionContextEncounteredError; + } + + @Nullable + public String getExecutionErrorMessage() { + return this.executionErrorMessage; + } + + @Nullable + public Vector3i getOriginAfterOffset() { + return this.originAfterOffset; + } + + @Nonnull + @Override + public String toString() { + return "BrushConfig{, interactionType=" + + this.interactionType + + ", origin=" + + this.origin + + ", originOffset=" + + this.originOffset + + ", originAfterOffset=" + + this.originAfterOffset + + ", shapeWidth=" + + this.shapeWidth + + ", shapeHeight=" + + this.shapeHeight + + ", shapeThickness=" + + this.shapeThickness + + ", capped=" + + this.capped + + ", density=" + + this.density + + ", shape=" + + this.shape + + ", pattern=" + + this.pattern + + ", historyMask=" + + this.historyMask + + ", brushMask=" + + this.brushMask + + ", operationMask=" + + this.operationMask + + ", combinedMasks=" + + this.combinedMasks + + ", enableOperationMask=" + + this.enableOperationMask + + ", enableBrushMask=" + + this.enableBrushMask + + ", random=" + + random + + ", isCurrentlyExecuting=" + + this.isCurrentlyExecuting + + "}"; + } + + @Nonnull + public String getInfo() { + StringBuilder builder = new StringBuilder("Brush Config Information:"); + builder.append("\nOrigin with Offset: {"); + if (this.originAfterOffset == null) { + builder.append("Not currently executing, so no origin"); + } else { + builder.append(this.originAfterOffset.x).append(", ").append(this.originAfterOffset.y).append(", ").append(this.originAfterOffset.z); + } + + builder.append("}"); + builder.append("\nOffset: {").append(this.originOffset.x).append(", ").append(this.originOffset.y).append(", ").append(this.originOffset.z).append("}"); + builder.append("\nDimensions: {Width: ").append(this.shapeWidth).append(", Height: ").append(this.shapeHeight).append("}"); + builder.append("\nShape Properties: {Shape: ") + .append(this.shape.name()) + .append(", Thickness: ") + .append(this.shapeThickness) + .append(", Capped: ") + .append(this.capped) + .append("}"); + builder.append("\nPattern: ").append(this.pattern.toString()); + builder.append("\nMasks: {HistoryMask: ") + .append(this.historyMask.name()) + .append(", EnableOperationMask: ") + .append(this.enableOperationMask) + .append(", EnableBrushMask: ") + .append(this.enableBrushMask) + .append(", CombinedMasks: ") + .append(this.combinedMasks.informativeToString()) + .append("}"); + builder.append("\nIs Currently Executing: ").append(this.isCurrentlyExecuting); + return builder.toString(); + } + + public static enum BCExecutionStatus { + Continue, + Error, + Complete; + + private BCExecutionStatus() { + } + } + + public static enum DataGettingFlags { + OffsetX(brushConfig -> brushConfig.getOriginOffset().x), + OffsetY(brushConfig -> brushConfig.getOriginOffset().y), + OffsetZ(brushConfig -> brushConfig.getOriginOffset().z), + Height(brushConfig -> brushConfig.getShapeHeight()), + Width(brushConfig -> brushConfig.getShapeWidth()), + Density(brushConfig -> brushConfig.getDensity()); + + private final Function valueGetter; + + private DataGettingFlags(Function valueGetter) { + this.valueGetter = valueGetter; + } + + public int getValue(BrushConfig brushConfig) { + return this.valueGetter.apply(brushConfig); + } + } + + public static enum DataSettingFlags { + Offset((copyTo, copyFrom) -> copyTo.setOriginOffset(copyFrom.getOriginOffset())), + Shape((copyTo, copyFrom) -> copyTo.setShape(copyFrom.getShape())), + Dimensions((copyTo, copyFrom) -> { + copyTo.setShapeWidth(copyFrom.getShapeWidth()); + copyTo.setShapeHeight(copyFrom.getShapeHeight()); + }), + Thickness((copyTo, copyFrom) -> copyTo.setShapeThickness(copyFrom.getShapeThickness())), + Capped((copyTo, copyFrom) -> copyTo.setCapped(copyFrom.isCapped())), + Pattern((copyTo, copyFrom) -> copyTo.setPattern(copyFrom.getPattern())), + Density((copyTo, copyFrom) -> copyTo.setDensity(copyFrom.getDensity())), + BrushMask((copyTo, copyFrom) -> { + copyTo.setBrushMask(copyFrom.brushMask); + copyTo.setUseBrushMask(copyFrom.enableBrushMask); + }), + OperationMask((copyTo, copyFrom) -> { + copyTo.setOperationMask(copyFrom.operationMask); + copyTo.setUseOperationMask(copyFrom.enableOperationMask); + }), + HistoryMask((copyTo, copyFrom) -> copyTo.setHistoryMask(copyFrom.getHistoryMask())); + + private final BiConsumer stateLoader; + + private DataSettingFlags(BiConsumer stateLoader) { + this.stateLoader = stateLoader; + } + + public void loadData(BrushConfig copyTo, BrushConfig copyFrom) { + this.stateLoader.accept(copyTo, copyFrom); + } + } + + public static enum HistoryMask { + None, + Only, + Not; + + private HistoryMask() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/BrushConfigChunkAccessor.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/BrushConfigChunkAccessor.java new file mode 100644 index 0000000..30f88d8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/BrushConfigChunkAccessor.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes; + +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.universe.world.accessor.ChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.accessor.LocalCachedChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import javax.annotation.Nonnull; + +public class BrushConfigChunkAccessor extends LocalCachedChunkAccessor { + private final BrushConfigEditStore editOperation; + + @Nonnull + public static BrushConfigChunkAccessor atWorldCoords( + BrushConfigEditStore editOperation, ChunkAccessor delegate, int centerX, int centerZ, int blockRadius + ) { + int chunkRadius = ChunkUtil.chunkCoordinate(blockRadius) + 1; + return atChunkCoords(editOperation, delegate, ChunkUtil.chunkCoordinate(centerX), ChunkUtil.chunkCoordinate(centerZ), chunkRadius); + } + + @Nonnull + public static BrushConfigChunkAccessor atChunkCoords( + BrushConfigEditStore editOperation, ChunkAccessor delegate, int centerX, int centerZ, int chunkRadius + ) { + return new BrushConfigChunkAccessor(editOperation, delegate, centerX, centerZ, chunkRadius); + } + + protected BrushConfigChunkAccessor(BrushConfigEditStore editOperation, ChunkAccessor delegate, int centerX, int centerZ, int radius) { + super(delegate, centerX, centerZ, radius); + this.editOperation = editOperation; + } + + @Override + public int getBlock(@Nonnull Vector3i pos) { + return this.editOperation.getAfter().hasBlockAtWorldPos(pos.x, pos.y, pos.z) + ? this.editOperation.getAfter().getBlockAtWorldPos(pos.x, pos.y, pos.z) + : this.getBlockIgnoringHistory(pos); + } + + @Override + public int getBlock(int x, int y, int z) { + return this.editOperation.getAfter().hasBlockAtWorldPos(x, y, z) + ? this.editOperation.getAfter().getBlockAtWorldPos(x, y, z) + : this.getBlockIgnoringHistory(x, y, z); + } + + public int getBlockIgnoringHistory(@Nonnull Vector3i pos) { + return this.getBlockIgnoringHistory(pos.x, pos.y, pos.z); + } + + public int getBlockIgnoringHistory(int x, int y, int z) { + return this.editOperation.getBefore().hasBlockAtWorldPos(x, y, z) + ? this.editOperation.getBefore().getBlockAtWorldPos(x, y, z) + : this.getChunk(ChunkUtil.indexChunkFromBlock(x, z)).getBlock(x, y, z); + } + + @Override + public int getFluidId(int x, int y, int z) { + return this.editOperation.getFluid(x, y, z); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/BrushConfigCommandExecutor.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/BrushConfigCommandExecutor.java new file mode 100644 index 0000000..550fe7c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/BrushConfigCommandExecutor.java @@ -0,0 +1,436 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.GlobalBrushOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.builtin.buildertools.tooloperations.ToolOperation; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BrushConfigCommandExecutor { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private final Map persistentStoredVariables; + private final BrushConfig brushConfig; + @Nonnull + private final Map globalOperations; + private int currentOperationIndex = 0; + @Nonnull + private final List sequentialOperations; + private boolean inDebugSteppingMode; + private boolean printOperations; + private boolean enableBreakpoints; + private BrushConfigCommandExecutor.DebugOutputTarget debugOutputTarget = BrushConfigCommandExecutor.DebugOutputTarget.Chat; + private boolean breakOnError; + @Nonnull + private final Map brushConfigStoredSnapshots; + private boolean allowOverwritingSavedSnapshots = true; + @Nonnull + private final Map storedIndexes; + private boolean ignoreExistingBrushData; + private BrushConfigEditStore edit; + private long startTime; + + public BrushConfigCommandExecutor(BrushConfig brushConfig) { + this.persistentStoredVariables = new Object2IntOpenHashMap<>(); + this.sequentialOperations = new ObjectArrayList<>(); + this.globalOperations = new Object2ObjectOpenHashMap<>(); + this.brushConfigStoredSnapshots = new Object2ObjectOpenHashMap<>(); + this.storedIndexes = new Object2IntOpenHashMap<>(); + this.brushConfig = brushConfig; + } + + public void resetInternalState() { + this.currentOperationIndex = 0; + this.brushConfigStoredSnapshots.clear(); + this.ignoreExistingBrushData = false; + this.printOperations = false; + this.inDebugSteppingMode = false; + this.enableBreakpoints = false; + this.debugOutputTarget = BrushConfigCommandExecutor.DebugOutputTarget.Chat; + this.breakOnError = false; + this.startTime = System.nanoTime(); + this.storedIndexes.clear(); + } + + public void execute( + @Nonnull Ref ref, + @Nonnull World world, + @Nonnull Vector3i origin, + boolean isHoldDownInteraction, + @Nonnull InteractionType interactionType, + @Nullable Consumer existingBrushDataLoadingConsumer, + @Nonnull ComponentAccessor componentAccessor + ) { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + UUIDComponent uuidComponent = componentAccessor.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + this.resetInternalState(); + this.brushConfig.resetToDefaultValues(); + this.brushConfig.beginExecution(origin, isHoldDownInteraction, interactionType); + PrototypePlayerBuilderToolSettings prototypePlayerBuilderToolSettings = ToolOperation.PROTOTYPE_TOOL_SETTINGS.get(uuidComponent.getUuid()); + if (!isHoldDownInteraction) { + prototypePlayerBuilderToolSettings.getIgnoredPaintOperations().clear(); + } + + this.edit = new BrushConfigEditStore(prototypePlayerBuilderToolSettings.addIgnoredPaintOperation(), this.brushConfig, world); + + for (int i = 0; i < this.sequentialOperations.size(); i++) { + this.sequentialOperations.get(i).resetInternalState(); + this.sequentialOperations.get(i).preExecutionModifyBrushConfig(this, i); + } + + for (GlobalBrushOperation globalOperation : this.globalOperations.values()) { + globalOperation.resetInternalState(); + globalOperation.modifyBrushConfig(ref, this.brushConfig, this, componentAccessor); + } + + if (!this.ignoreExistingBrushData && existingBrushDataLoadingConsumer != null) { + existingBrushDataLoadingConsumer.accept(this.brushConfig); + } + + if (!this.inDebugSteppingMode) { + while ( + this.brushConfig.isCurrentlyExecuting() + && !this.inDebugSteppingMode + && this.step(ref, false, componentAccessor).equals(BrushConfig.BCExecutionStatus.Continue) + ) { + } + } + } + + public void execute( + @Nonnull Ref ref, + World world, + Vector3i origin, + boolean isHoldDownInteraction, + InteractionType interactionType, + ComponentAccessor componentAccessor + ) { + this.execute(ref, world, origin, isHoldDownInteraction, interactionType, null, componentAccessor); + } + + @Nonnull + public BrushConfig.BCExecutionStatus step(Ref ref, boolean placePreviewAfterStep, ComponentAccessor componentAccessor) { + if (!this.brushConfig.isCurrentlyExecuting()) { + return BrushConfig.BCExecutionStatus.Error; + } else if (this.sequentialOperations.isEmpty()) { + this.brushConfig.setErrorFlag("No operations to execute"); + return this.completeStep(ref, placePreviewAfterStep, componentAccessor); + } else { + try { + SequenceBrushOperation brushOperation = this.sequentialOperations.get(this.currentOperationIndex); + if (this.printOperations) { + LOGGER.at(Level.INFO).log("[%d] %s", this.currentOperationIndex, brushOperation.getName()); + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + if (playerRefComponent != null) { + playerRefComponent.sendMessage( + Message.translation("server.builderTools.brushConfig.debug.operationExecuting") + .param("index", this.currentOperationIndex) + .param("name", brushOperation.getName()) + ); + } + } + + brushOperation.modifyBrushConfig(ref, this.brushConfig, this, componentAccessor); + if (this.brushConfig.isHasExecutionContextEncounteredError() || !brushOperation.doesOperateOnBlocks()) { + return this.completeStep(ref, placePreviewAfterStep, componentAccessor); + } + + int numModifyBlockIterations = brushOperation.getNumModifyBlockIterations(); + + for (int i = 0; i < numModifyBlockIterations; i++) { + brushOperation.beginIterationIndex(i); + ToolOperation.executeShapeOperation( + this.brushConfig.getOriginAfterOffset().x, + this.brushConfig.getOriginAfterOffset().y, + this.brushConfig.getOriginAfterOffset().z, + (x, y, z, unused) -> brushOperation.modifyBlocks(ref, this.brushConfig, this, this.edit, x, y, z, componentAccessor), + this.brushConfig.getShape(), + this.brushConfig.getShapeWidth(), + this.brushConfig.getShapeHeight(), + this.brushConfig.getShapeThickness(), + this.brushConfig.isCapped() + ); + this.edit.flushCurrentEditsToPrevious(); + } + } catch (Exception var7) { + var7.printStackTrace(); + this.brushConfig.setErrorFlag(var7.getMessage()); + } + + return this.completeStep(ref, placePreviewAfterStep, componentAccessor); + } + } + + @Nonnull + private BrushConfig.BCExecutionStatus completeStep(Ref ref, boolean placePreviewAfterStep, ComponentAccessor componentAccessor) { + if (this.brushConfig.isHasExecutionContextEncounteredError()) { + if (this.breakOnError) { + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + if (playerRefComponent != null) { + playerRefComponent.sendMessage( + Message.translation("server.builderTools.brushConfig.debug.breakOnErrorTriggered").param("index", this.currentOperationIndex) + ); + } + + LOGGER.at(Level.INFO).log("[Breakpoint] Error at operation #%d - Entering step-through mode", this.currentOperationIndex); + this.inDebugSteppingMode = true; + this.brushConfig.clearError(); + return BrushConfig.BCExecutionStatus.Continue; + } else { + this.exitExecution(ref, componentAccessor); + return BrushConfig.BCExecutionStatus.Error; + } + } else { + this.currentOperationIndex++; + if (this.currentOperationIndex >= this.sequentialOperations.size()) { + this.exitExecution(ref, componentAccessor); + return BrushConfig.BCExecutionStatus.Complete; + } else { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + if (placePreviewAfterStep) { + BrushConfigEditStore returnEdit = this.edit; + BuilderToolsPlugin.getState(playerComponent, playerRefComponent).addToQueue((r, s, c) -> s.placeBrushConfig(r, this.startTime, returnEdit, c)); + } + + return BrushConfig.BCExecutionStatus.Continue; + } + } + } + + public void exitExecution(Ref ref, ComponentAccessor componentAccessor) { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + if (this.brushConfig.isHasExecutionContextEncounteredError() && this.currentOperationIndex < this.sequentialOperations.size()) { + this.sendExecutionErrorMessage(playerRefComponent, this.sequentialOperations.get(this.currentOperationIndex)); + } + + if (this.currentOperationIndex != 0) { + BrushConfigEditStore returnEdit = this.edit; + BuilderToolsPlugin.getState(playerComponent, playerRefComponent) + .addToQueue((r, s, compAccess) -> s.placeBrushConfig(r, this.startTime, returnEdit, compAccess)); + } + + this.brushConfig.endExecution(); + } + + private void sendExecutionErrorMessage(PlayerRef playerRef, @Nonnull SequenceBrushOperation brushOperation) { + playerRef.sendMessage( + Message.translation("server.builderTools.brushConfig.executionError") + .param("index", this.currentOperationIndex) + .param("type", brushOperation.getName()) + .param("error", this.brushConfig.getExecutionErrorMessage()) + ); + Message header = Message.translation("server.builderTools.brushConfig.settings.header"); + Set items = brushOperation.getRegisteredOperationSettings() + .values() + .stream() + .map( + value -> Message.translation("server.builderTools.brushConfig.settings.item").param("name", value.getName()).param("value", value.getValueString()) + ) + .collect(Collectors.toSet()); + playerRef.sendMessage(MessageFormat.list(header, items)); + } + + public void storeOperatingIndex(String name, int index) { + name = name.toLowerCase(); + if (this.storedIndexes.containsKey(name)) { + this.brushConfig.setErrorFlag("You already have a stored index with the name: '" + name + "'."); + } else if (index >= this.sequentialOperations.size()) { + this.brushConfig + .setErrorFlag("Tried to store an index greater than the total size of sequential operations. Name: '" + name + "', index: '" + index + "'."); + } else { + this.storedIndexes.put(name, index); + } + } + + public void loadOperatingIndex(String name) { + this.loadOperatingIndex(name, true); + } + + public void loadOperatingIndex(String name, boolean allowFutureJump) { + name = name.toLowerCase(); + if (!this.storedIndexes.containsKey(name)) { + this.brushConfig.setErrorFlag("Could not find a stored index with the name: '" + name + "'."); + } else { + int newIndex = this.storedIndexes.get(name); + if (!allowFutureJump && newIndex > this.currentOperationIndex) { + this.brushConfig.setErrorFlag("This operation does not allow you to jump to an operation in the future, only the past. Index name: " + name + "'."); + } else { + this.currentOperationIndex = newIndex; + } + } + } + + public void clearAllPersistentVariables() { + this.persistentStoredVariables.clear(); + } + + public void clearPersistentVariable(String variableName) { + variableName = variableName.toLowerCase(); + if (!this.persistentStoredVariables.containsKey(variableName)) { + this.brushConfig.setErrorFlag("Could not find a stored persistent variable with the name: '" + variableName + "'."); + } else { + this.persistentStoredVariables.remove(variableName); + } + } + + public void setPersistentVariable(String variableName, int value) { + variableName = variableName.toLowerCase(); + this.persistentStoredVariables.put(variableName, value); + } + + public int getPersistentVariableOrDefault(String variableName, int defaultValue) { + variableName = variableName.toLowerCase(); + return !this.persistentStoredVariables.containsKey(variableName) ? defaultValue : this.persistentStoredVariables.get(variableName); + } + + public void storeBrushConfigSnapshot(@Nonnull String name) { + this.brushConfigStoredSnapshots.put(name.toLowerCase(), new BrushConfig(this.brushConfig)); + } + + public void loadBrushConfigSnapshot(String name, @Nonnull BrushConfig.DataSettingFlags... dataToLoad) { + name = name.toLowerCase(); + if (!this.brushConfigStoredSnapshots.containsKey(name)) { + this.brushConfig.setErrorFlag("Could not find a stored brush config snapshot with the name: '" + name + "'."); + } else { + BrushConfig loadedBrushConfig = this.brushConfigStoredSnapshots.get(name); + + for (BrushConfig.DataSettingFlags dataLoadFlag : dataToLoad) { + dataLoadFlag.loadData(this.brushConfig, loadedBrushConfig); + } + } + } + + public void setAllowOverwritingSavedSnapshots(boolean allowOverwritingSavedSnapshots) { + this.allowOverwritingSavedSnapshots = allowOverwritingSavedSnapshots; + } + + @Nonnull + public List getSequentialOperations() { + return this.sequentialOperations; + } + + @Nonnull + public Map getGlobalOperations() { + return this.globalOperations; + } + + public boolean isIgnoreExistingBrushData() { + return this.ignoreExistingBrushData; + } + + public boolean isInDebugSteppingMode() { + return this.inDebugSteppingMode; + } + + public BrushConfigEditStore getEdit() { + return this.edit; + } + + public void setInDebugSteppingMode(boolean inDebugSteppingMode) { + this.inDebugSteppingMode = inDebugSteppingMode; + } + + public void setPrintOperations(boolean printOperations) { + this.printOperations = printOperations; + } + + public void setIgnoreExistingBrushData(boolean ignoreExistingBrushData) { + this.ignoreExistingBrushData = ignoreExistingBrushData; + } + + public void setCurrentlyExecutingActionIndex(int newCurrentOperationIndex) { + if (newCurrentOperationIndex < 0) { + this.brushConfig.setErrorFlag("Cannot set a negative operation index: " + newCurrentOperationIndex); + } else if (newCurrentOperationIndex >= this.sequentialOperations.size()) { + this.brushConfig + .setErrorFlag( + "Cannot set an operation index higher than the highest operation index: " + + newCurrentOperationIndex + + ". Highest operation index: " + + (this.sequentialOperations.size() - 1) + ); + } else { + this.currentOperationIndex = newCurrentOperationIndex - 1; + } + } + + public int getCurrentOperationIndex() { + return this.currentOperationIndex; + } + + public boolean isEnableBreakpoints() { + return this.enableBreakpoints; + } + + public void setEnableBreakpoints(boolean enableBreakpoints) { + this.enableBreakpoints = enableBreakpoints; + } + + public BrushConfigCommandExecutor.DebugOutputTarget getDebugOutputTarget() { + return this.debugOutputTarget; + } + + public void setDebugOutputTarget(BrushConfigCommandExecutor.DebugOutputTarget debugOutputTarget) { + this.debugOutputTarget = debugOutputTarget; + } + + public boolean isBreakOnError() { + return this.breakOnError; + } + + public void setBreakOnError(boolean breakOnError) { + this.breakOnError = breakOnError; + } + + public static enum DebugOutputTarget { + Chat, + Console, + Both; + + private DebugOutputTarget() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/BrushConfigEditStore.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/BrushConfigEditStore.java new file mode 100644 index 0000000..bf4f7ab --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/BrushConfigEditStore.java @@ -0,0 +1,231 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.utils.Material; +import com.hypixel.hytale.math.block.BlockUtil; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockMask; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntMaps; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2IntMap.Entry; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import javax.annotation.Nonnull; + +public class BrushConfigEditStore { + @Nonnull + private final BrushConfig brushConfig; + @Nonnull + private final BrushConfigChunkAccessor accessor; + @Nonnull + private final BlockSelection before; + @Nonnull + private final BlockSelection previous; + private BlockSelection current; + private final LongOpenHashSet packedPlacedBlockPositions; + + public BrushConfigEditStore(LongOpenHashSet packedPlacedBlockPositions, @Nonnull BrushConfig brushConfig, World world) { + this.brushConfig = brushConfig; + this.packedPlacedBlockPositions = packedPlacedBlockPositions; + Vector3i origin = brushConfig.getOrigin(); + int shapeWidth = brushConfig.getShapeWidth(); + int shapeHeight = brushConfig.getShapeHeight(); + int halfWidth = shapeWidth / 2; + int halfHeight = shapeHeight / 2; + this.accessor = BrushConfigChunkAccessor.atWorldCoords(this, world, origin.x, origin.z, shapeWidth * 2); + this.before = new BlockSelection(); + this.before.setPosition(origin.x, origin.y, origin.z); + this.before + .setSelectionArea( + new Vector3i(origin.x - halfWidth, origin.y - halfHeight, origin.z - halfWidth), + new Vector3i(origin.x + halfWidth, origin.y + halfHeight, origin.z + halfWidth) + ); + this.previous = new BlockSelection(this.before); + this.current = new BlockSelection(this.before); + } + + @Nonnull + public BrushConfigChunkAccessor getAccessor() { + return this.accessor; + } + + public int getOriginalBlock(int x, int y, int z) { + return this.accessor.getBlockIgnoringHistory(x, y, z); + } + + public int getBlock(int x, int y, int z) { + return this.previous.hasBlockAtWorldPos(x, y, z) ? this.previous.getBlockAtWorldPos(x, y, z) : this.getOriginalBlock(x, y, z); + } + + public int getBlockIncludingCurrent(int x, int y, int z) { + return this.current.hasBlockAtWorldPos(x, y, z) ? this.current.getBlockAtWorldPos(x, y, z) : this.getBlock(x, y, z); + } + + public boolean setBlock(int x, int y, int z, int blockId) { + boolean hasHistory = this.previous.hasBlockAtWorldPos(x, y, z) || this.previous.getFluidAtWorldPos(x, y, z) >= 0; + switch (this.brushConfig.getHistoryMask()) { + case Only: + if (!hasHistory) { + return false; + } + break; + case Not: + if (hasHistory) { + return false; + } + } + + if (this.brushConfig.getRandom().nextInt(100) >= this.brushConfig.getDensity()) { + return false; + } else { + if (this.getOriginalBlock(x, y, z) == 0) { + this.packedPlacedBlockPositions.add(BlockUtil.pack(x, y, z)); + } + + int currentBlock = this.getBlock(x, y, z); + int currentFluid = this.getFluid(x, y, z); + BlockMask blockMask = this.brushConfig.getBlockMask(); + if (blockMask != null && blockMask.isExcluded(this.accessor, x, y, z, null, null, currentBlock, currentFluid)) { + return false; + } else { + if (!this.before.hasBlockAtWorldPos(x, y, z)) { + WorldChunk blocks = this.accessor.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(x, z)); + if (blocks != null) { + this.before + .addBlockAtWorldPos( + x, + y, + z, + currentBlock, + blocks.getRotationIndex(x, y, z), + blocks.getFiller(x, y, z), + blocks.getSupportValue(x, y, z), + blocks.getBlockComponentHolder(x, y, z) + ); + } + } + + this.current.addBlockAtWorldPos(x, y, z, blockId, 0, 0, 0); + return true; + } + } + } + + private boolean setFluid(int x, int y, int z, int fluidId, byte fluidLevel) { + boolean hasHistory = this.previous.hasBlockAtWorldPos(x, y, z) || this.previous.getFluidAtWorldPos(x, y, z) >= 0; + switch (this.brushConfig.getHistoryMask()) { + case Only: + if (!hasHistory) { + return false; + } + break; + case Not: + if (hasHistory) { + return false; + } + } + + if (this.brushConfig.getRandom().nextInt(100) >= this.brushConfig.getDensity()) { + return false; + } else { + int currentBlock = this.getBlock(x, y, z); + int currentFluid = this.getFluid(x, y, z); + BlockMask blockMask = this.brushConfig.getBlockMask(); + if (blockMask != null && blockMask.isExcluded(this.accessor, x, y, z, null, null, currentBlock, currentFluid)) { + return false; + } else { + int beforeFluid = this.before.getFluidAtWorldPos(x, y, z); + if (beforeFluid < 0) { + WorldChunk chunk = this.accessor.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(x, z)); + if (chunk != null) { + int originalFluidId = chunk.getFluidId(x, y, z); + byte originalFluidLevel = chunk.getFluidLevel(x, y, z); + this.before.addFluidAtWorldPos(x, y, z, originalFluidId, originalFluidLevel); + } + } + + this.current.addFluidAtWorldPos(x, y, z, fluidId, fluidLevel); + return true; + } + } + } + + private int getOriginalFluid(int x, int y, int z) { + WorldChunk chunk = this.accessor.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(x, z)); + return chunk != null ? chunk.getFluidId(x, y, z) : 0; + } + + public int getFluid(int x, int y, int z) { + int previousFluid = this.previous.getFluidAtWorldPos(x, y, z); + return previousFluid >= 0 ? previousFluid : this.getOriginalFluid(x, y, z); + } + + public boolean setMaterial(int x, int y, int z, @Nonnull Material material) { + if (material.isFluid()) { + return this.setFluid(x, y, z, material.getFluidId(), material.getFluidLevel()); + } else { + boolean result = this.setBlock(x, y, z, material.getBlockId()); + if (result && material.isEmpty()) { + this.setFluid(x, y, z, 0, (byte)0); + } + + return result; + } + } + + @Nonnull + public BuilderToolsPlugin.BuilderState.BlocksSampleData getBlockSampledataIncludingPreviousStages(int x, int y, int z, int radius) { + BuilderToolsPlugin.BuilderState.BlocksSampleData data = new BuilderToolsPlugin.BuilderState.BlocksSampleData(); + Int2IntMap blockCounts = new Int2IntOpenHashMap(); + + for (int ix = x - radius; ix <= x + radius; ix++) { + for (int iz = z - radius; iz <= z + radius; iz++) { + for (int iy = y - radius; iy <= y + radius; iy++) { + int currentBlock = this.getBlock(ix, iy, iz); + blockCounts.put(currentBlock, blockCounts.getOrDefault(currentBlock, 0) + 1); + } + } + } + + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + + for (Entry pair : Int2IntMaps.fastIterable(blockCounts)) { + int block = pair.getIntKey(); + int count = pair.getIntValue(); + if (count > data.mainBlockCount) { + data.mainBlock = block; + data.mainBlockCount = count; + } + + BlockType blockType = assetMap.getAsset(block); + if (count > data.mainBlockNotAirCount && block != 0) { + data.mainBlockNotAir = block; + data.mainBlockNotAirCount = count; + } + } + + return data; + } + + public void flushCurrentEditsToPrevious() { + this.previous.add(this.current); + this.current = new BlockSelection(); + this.current.setPosition(this.brushConfig.getOrigin().x, this.brushConfig.getOrigin().y, this.brushConfig.getOrigin().z); + } + + @Nonnull + public BlockSelection getAfter() { + return this.previous; + } + + @Nonnull + public BlockSelection getBefore() { + return this.before; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/ScriptedBrushAsset.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/ScriptedBrushAsset.java new file mode 100644 index 0000000..f016cb5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/ScriptedBrushAsset.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.saveandload.LoadOperationsFromAssetOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.BrushOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.GlobalBrushOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ScriptedBrushAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = ((AssetBuilderCodec.Builder)AssetBuilderCodec.builder( + ScriptedBrushAsset.class, + ScriptedBrushAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + asset -> asset.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .append(new KeyedCodec<>("Operations", new ArrayCodec<>(BrushOperation.OPERATION_CODEC, BrushOperation[]::new)), (asset, operations) -> { + asset.operations = new ObjectArrayList<>(); + if (operations != null) { + Collections.addAll(asset.operations, operations); + } + }, asset -> asset.operations != null ? asset.operations.toArray(new BrushOperation[0]) : new BrushOperation[0]) + .documentation("The list of brush operations to execute sequentially") + .add() + .documentation("A scripted brush asset containing multiple brush operations that will be executed sequentially")) + .build(); + private static DefaultAssetMap ASSET_MAP; + protected AssetExtraInfo.Data data; + protected String id; + protected List operations = new ObjectArrayList<>(); + + @Nonnull + public static DefaultAssetMap getAssetMap() { + if (ASSET_MAP == null) { + ASSET_MAP = (DefaultAssetMap)AssetRegistry.getAssetStore(ScriptedBrushAsset.class).getAssetMap(); + } + + return ASSET_MAP; + } + + @Nullable + public static ScriptedBrushAsset get(@Nonnull String id) { + return getAssetMap().getAsset(id); + } + + public ScriptedBrushAsset() { + } + + @Nonnull + public String getId() { + return this.id; + } + + @Nonnull + public List getOperations() { + return this.operations; + } + + public void loadIntoExecutor(@Nonnull BrushConfigCommandExecutor executor) { + executor.getSequentialOperations().clear(); + executor.getGlobalOperations().clear(); + + for (BrushOperation operation : this.operations) { + if (operation instanceof LoadOperationsFromAssetOperation loadOp) { + ScriptedBrushAsset targetAsset = get(loadOp.getAssetId()); + if (targetAsset != null) { + for (BrushOperation targetOp : targetAsset.getOperations()) { + if (targetOp instanceof GlobalBrushOperation) { + executor.getGlobalOperations().put(targetOp.getName().toLowerCase(), (GlobalBrushOperation)targetOp); + } else if (targetOp instanceof SequenceBrushOperation) { + executor.getSequentialOperations().add((SequenceBrushOperation)targetOp); + } + } + } + } else if (operation instanceof GlobalBrushOperation) { + executor.getGlobalOperations().put(operation.getName().toLowerCase(), (GlobalBrushOperation)operation); + } else if (operation instanceof SequenceBrushOperation) { + executor.getSequentialOperations().add((SequenceBrushOperation)operation); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigClearCommand.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigClearCommand.java new file mode 100644 index 0000000..a4f6835 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigClearCommand.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.commands; + +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.tooloperations.ToolOperation; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class BrushConfigClearCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_BRUSH_CONFIG_CANNOT_USE_COMMAND_DURING_EXEC = Message.translation( + "server.commands.brushConfig.cannotUseCommandDuringExec" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_BRUSH_CONFIG_CLEARED = Message.translation("server.commands.brushConfig.cleared"); + + public BrushConfigClearCommand() { + super("clear", "Clear your brush config and disable it"); + this.addAliases("disable"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + UUID playerUUID = playerRef.getUuid(); + PrototypePlayerBuilderToolSettings prototypeSettings = ToolOperation.getOrCreatePrototypeSettings(playerUUID); + BrushConfigCommandExecutor brushConfigCommandExecutor = ToolOperation.getOrCreatePrototypeSettings(playerUUID).getBrushConfigCommandExecutor(); + if (prototypeSettings.getBrushConfig().isCurrentlyExecuting()) { + playerRef.sendMessage(MESSAGE_COMMANDS_BRUSH_CONFIG_CANNOT_USE_COMMAND_DURING_EXEC); + } else { + brushConfigCommandExecutor.getSequentialOperations().clear(); + brushConfigCommandExecutor.getGlobalOperations().clear(); + prototypeSettings.setUsePrototypeBrushConfigurations(false); + playerRef.sendMessage(MESSAGE_COMMANDS_BRUSH_CONFIG_CLEARED); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigCommand.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigCommand.java new file mode 100644 index 0000000..2eabaae --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigCommand.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.commands; + +import com.hypixel.hytale.builtin.buildertools.tooloperations.ToolOperation; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class BrushConfigCommand extends AbstractCommandCollection { + public BrushConfigCommand() { + super("scriptedbrushes", "Scripted brushes related commands"); + this.addAliases("scriptbrush", "scriptedbrush", "sb"); + this.requirePermission("hytale.editor.brush.config"); + this.addSubCommand(new BrushConfigClearCommand()); + this.addSubCommand(new BrushConfigListCommand()); + this.addSubCommand(new BrushConfigDebugStepCommand()); + this.addSubCommand(new BrushConfigExitCommand()); + this.addSubCommand(new BrushConfigLoadCommand()); + this.addSubCommand( + new AbstractPlayerCommand("info", "Information on the current brush configuration") { + @Override + protected void execute( + @Nonnull CommandContext context, + @Nonnull Store store, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world + ) { + String infoString = ToolOperation.getOrCreatePrototypeSettings(playerRef.getUuid()).getBrushConfig().getInfo(); + context.sendMessage(Message.raw(infoString)); + } + } + ); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigDebugStepCommand.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigDebugStepCommand.java new file mode 100644 index 0000000..6cf6ae4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigDebugStepCommand.java @@ -0,0 +1,87 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.commands; + +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.BrushOperationSetting; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.builtin.buildertools.tooloperations.ToolOperation; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class BrushConfigDebugStepCommand extends AbstractPlayerCommand { + private final DefaultArg numStepsArg = this.withDefaultArg( + "steps", "The number of operations to step through", ArgTypes.INTEGER, 1, "A single step" + ) + .addValidator(Validators.range(1, 100)); + + public BrushConfigDebugStepCommand() { + super("step", "Advance one or more steps into your order of operations brush config debug"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + int numSteps = this.numStepsArg.get(context); + PrototypePlayerBuilderToolSettings prototypeSettings = ToolOperation.getOrCreatePrototypeSettings(uuidComponent.getUuid()); + BrushConfig brushConfig = prototypeSettings.getBrushConfig(); + BrushConfigCommandExecutor brushConfigCommandExecutor = prototypeSettings.getBrushConfigCommandExecutor(); + if (!brushConfig.isCurrentlyExecuting()) { + playerRef.sendMessage(Message.translation("server.commands.brushConfig.debug.notStarted")); + } else { + int indexAtStart = brushConfigCommandExecutor.getCurrentOperationIndex(); + int indexAtEnd = 0; + BrushConfig.BCExecutionStatus status = null; + + for (int i = 0; i < numSteps; i++) { + indexAtEnd = brushConfigCommandExecutor.getCurrentOperationIndex(); + status = brushConfigCommandExecutor.step(ref, true, store); + if (!status.equals(BrushConfig.BCExecutionStatus.Continue)) { + break; + } + } + + if (status == BrushConfig.BCExecutionStatus.Complete) { + playerRef.sendMessage(Message.translation("server.commands.brushConfig.debug.finished")); + } + + Message header = Message.translation("server.commands.brushConfig.debug.executed"); + List values = new ObjectArrayList<>(); + + for (int ix = indexAtStart; ix <= indexAtEnd; ix++) { + SequenceBrushOperation brushOperation = brushConfigCommandExecutor.getSequentialOperations().get(ix); + values.add(Message.translation("server.commands.brushConfig.list.sequentialOperation").param("index", ix).param("name", brushOperation.getName())); + + for (Entry> entry : brushOperation.getRegisteredOperationSettings().entrySet()) { + values.add( + Message.translation("server.commands.brushConfig.list.setting") + .param("name", entry.getKey()) + .param("value", entry.getValue().getValueString()) + ); + } + } + + playerRef.sendMessage(MessageFormat.list(header, values)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigExitCommand.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigExitCommand.java new file mode 100644 index 0000000..878e3c2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigExitCommand.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.commands; + +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.builtin.buildertools.tooloperations.ToolOperation; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class BrushConfigExitCommand extends AbstractPlayerCommand { + public BrushConfigExitCommand() { + super("exit", "Exit a running debug state or stop an incomplete execution"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + PrototypePlayerBuilderToolSettings prototypeSettings = ToolOperation.getOrCreatePrototypeSettings(playerRef.getUuid()); + prototypeSettings.getBrushConfigCommandExecutor().exitExecution(ref, store); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigListCommand.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigListCommand.java new file mode 100644 index 0000000..ab4305f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigListCommand.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.commands; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.BrushOperationSetting; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.GlobalBrushOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.builtin.buildertools.tooloperations.ToolOperation; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class BrushConfigListCommand extends AbstractPlayerCommand { + public BrushConfigListCommand() { + super("list", "List the brush config operations that are currently set"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + BrushConfigCommandExecutor brushConfigCommandExecutor = ToolOperation.getOrCreatePrototypeSettings(uuidComponent.getUuid()) + .getBrushConfigCommandExecutor(); + Message header = Message.translation("server.commands.brushConfig.list.globalOperation.header"); + List values = new ObjectArrayList<>(); + + for (GlobalBrushOperation operation : brushConfigCommandExecutor.getGlobalOperations().values()) { + values.add(Message.translation("server.commands.brushConfig.list.globalOperation").param("name", operation.getName())); + + for (Entry> entry : operation.getRegisteredOperationSettings().entrySet()) { + values.add( + Message.translation("server.commands.brushConfig.list.setting").param("name", entry.getKey()).param("value", entry.getValue().getValueString()) + ); + } + } + + playerRef.sendMessage(MessageFormat.list(header, values)); + header = Message.translation("server.commands.brushConfig.list.sequentialOperation.header"); + values = new ObjectArrayList<>(); + + for (int i = 0; i < brushConfigCommandExecutor.getSequentialOperations().size(); i++) { + SequenceBrushOperation operation = brushConfigCommandExecutor.getSequentialOperations().get(i); + values.add(Message.translation("server.commands.brushConfig.list.sequentialOperation").param("index", i).param("name", operation.getName())); + + for (Entry> entry : operation.getRegisteredOperationSettings().entrySet()) { + values.add( + Message.translation("server.commands.brushConfig.list.setting").param("name", entry.getKey()).param("value", entry.getValue().getValueString()) + ); + } + } + + playerRef.sendMessage(MessageFormat.list(header, values)); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigLoadCommand.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigLoadCommand.java new file mode 100644 index 0000000..4072961 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/commands/BrushConfigLoadCommand.java @@ -0,0 +1,91 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.commands; + +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.ScriptedBrushAsset; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.ui.ScriptedBrushPage; +import com.hypixel.hytale.builtin.buildertools.tooloperations.ToolOperation; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.AssetArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class BrushConfigLoadCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_BRUSH_CONFIG_CANNOT_USE_COMMAND_DURING_EXEC = Message.translation( + "server.commands.brushConfig.cannotUseCommandDuringExec" + ); + + public BrushConfigLoadCommand() { + super("load", "Load a scripted brush by name, or open the brush picker UI if no name is provided"); + this.addUsageVariant(new BrushConfigLoadCommand.LoadByNameCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + UUID playerUUID = playerRef.getUuid(); + PrototypePlayerBuilderToolSettings prototypeSettings = ToolOperation.getOrCreatePrototypeSettings(playerUUID); + BrushConfig brushConfig = prototypeSettings.getBrushConfig(); + if (brushConfig.isCurrentlyExecuting()) { + playerRef.sendMessage(MESSAGE_COMMANDS_BRUSH_CONFIG_CANNOT_USE_COMMAND_DURING_EXEC); + } else { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getPageManager().openCustomPage(ref, store, new ScriptedBrushPage(playerRef)); + } + } + + private static class LoadByNameCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_BRUSH_CONFIG_LOADED = Message.translation("server.commands.brushConfig.loaded"); + @Nonnull + private static final Message MESSAGE_COMMANDS_BRUSH_CONFIG_CANNOT_USE_COMMAND_DURING_EXEC = Message.translation( + "server.commands.brushConfig.cannotUseCommandDuringExec" + ); + @Nonnull + private final RequiredArg brushNameArg = this.withRequiredArg( + "brushName", + "The name of the scripted brush asset to load", + new AssetArgumentType( + "server.commands.parsing.argtype.asset.scriptedbrush.name", ScriptedBrushAsset.class, "server.commands.parsing.argtype.asset.scriptedbrush.usage" + ) + ); + + public LoadByNameCommand() { + super("Load a scripted brush by name"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + UUID playerUUID = playerRef.getUuid(); + PrototypePlayerBuilderToolSettings prototypeSettings = ToolOperation.getOrCreatePrototypeSettings(playerUUID); + BrushConfig brushConfig = prototypeSettings.getBrushConfig(); + BrushConfigCommandExecutor brushConfigCommandExecutor = prototypeSettings.getBrushConfigCommandExecutor(); + if (brushConfig.isCurrentlyExecuting()) { + playerRef.sendMessage(MESSAGE_COMMANDS_BRUSH_CONFIG_CANNOT_USE_COMMAND_DURING_EXEC); + } else { + ScriptedBrushAsset brushAssetArg = this.brushNameArg.get(context); + brushAssetArg.loadIntoExecutor(brushConfigCommandExecutor); + prototypeSettings.setCurrentlyLoadedBrushConfigName(brushAssetArg.getId()); + prototypeSettings.setUsePrototypeBrushConfigurations(true); + playerRef.sendMessage(MESSAGE_COMMANDS_BRUSH_CONFIG_LOADED.param("name", brushAssetArg.getId())); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/global/DebugBrushOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/global/DebugBrushOperation.java new file mode 100644 index 0000000..7733221 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/global/DebugBrushOperation.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.global; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.GlobalBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class DebugBrushOperation extends GlobalBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(DebugBrushOperation.class, DebugBrushOperation::new) + .append(new KeyedCodec<>("PrintOperations", Codec.BOOLEAN), (op, val) -> op.printOperations = val, op -> op.printOperations) + .documentation("Prints the index and name of each operation as it executes") + .add() + .append(new KeyedCodec<>("StepThrough", Codec.BOOLEAN), (op, val) -> op.stepThrough = val, op -> op.stepThrough) + .documentation("Enables manual step-through mode (pause after each operation)") + .add() + .append(new KeyedCodec<>("EnableBreakpoints", Codec.BOOLEAN), (op, val) -> op.enableBreakpoints = val, op -> op.enableBreakpoints) + .documentation("Master toggle for breakpoint operations") + .add() + .append( + new KeyedCodec<>("OutputTarget", new EnumCodec<>(BrushConfigCommandExecutor.DebugOutputTarget.class)), + (op, val) -> op.outputTarget = val, + op -> op.outputTarget + ) + .documentation("Where debug messages are sent (Chat, Console, or Both)") + .add() + .append(new KeyedCodec<>("BreakOnError", Codec.BOOLEAN), (op, val) -> op.breakOnError = val, op -> op.breakOnError) + .documentation("Pause on error instead of terminating execution") + .add() + .documentation("Debug options for scripted brushes") + .build(); + @Nonnull + private Boolean printOperations = false; + @Nonnull + private Boolean stepThrough = false; + @Nonnull + private Boolean enableBreakpoints = false; + @Nonnull + private BrushConfigCommandExecutor.DebugOutputTarget outputTarget = BrushConfigCommandExecutor.DebugOutputTarget.Chat; + @Nonnull + private Boolean breakOnError = false; + + public DebugBrushOperation() { + super("Debug Step-Through", "Debug options for scripted brushes"); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + brushConfigCommandExecutor.setInDebugSteppingMode(this.stepThrough); + brushConfigCommandExecutor.setPrintOperations(this.printOperations); + brushConfigCommandExecutor.setEnableBreakpoints(this.enableBreakpoints); + brushConfigCommandExecutor.setDebugOutputTarget(this.outputTarget); + brushConfigCommandExecutor.setBreakOnError(this.breakOnError); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/global/DisableHoldInteractionOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/global/DisableHoldInteractionOperation.java new file mode 100644 index 0000000..3c3a392 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/global/DisableHoldInteractionOperation.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.global; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.GlobalBrushOperation; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class DisableHoldInteractionOperation extends GlobalBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DisableHoldInteractionOperation.class, DisableHoldInteractionOperation::new + ) + .documentation("Disables the ability of the brush to activate multiple times on holding a button") + .build(); + + public DisableHoldInteractionOperation() { + super("Disable Activate-On-Hold", "Disables the ability of the brush to activate multiple times on holding a button"); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + if (brushConfig.isHoldDownInteraction()) { + brushConfigCommandExecutor.exitExecution(ref, componentAccessor); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/global/IgnoreExistingBrushDataOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/global/IgnoreExistingBrushDataOperation.java new file mode 100644 index 0000000..da450c5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/global/IgnoreExistingBrushDataOperation.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.global; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.GlobalBrushOperation; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class IgnoreExistingBrushDataOperation extends GlobalBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder( + IgnoreExistingBrushDataOperation.class, IgnoreExistingBrushDataOperation::new + ) + .documentation("Ignores any existing brush settings specified on the tool") + .build(); + + public IgnoreExistingBrushDataOperation() { + super("Ignore Existing Brush Settings", "Ignores any existing brush settings specified on the tool"); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + brushConfigCommandExecutor.setIgnoreExistingBrushData(true); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/BlockPatternOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/BlockPatternOperation.java new file mode 100644 index 0000000..a03f3df --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/BlockPatternOperation.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class BlockPatternOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockPatternOperation.class, BlockPatternOperation::new) + .append(new KeyedCodec<>("BlockPattern", BlockPattern.CODEC), (op, val) -> op.blockPatternArg = val, op -> op.blockPatternArg) + .documentation("The pattern of blocks to use in your set") + .add() + .documentation("Change the material of the brush to a pattern of blocks") + .build(); + @Nonnull + public BlockPattern blockPatternArg = BlockPattern.parse("Rock_Stone"); + + public BlockPatternOperation() { + super("Block Pattern", "Change the material of the brush to a pattern of blocks", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + brushConfig.setPattern(this.blockPatternArg); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/BreakpointOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/BreakpointOperation.java new file mode 100644 index 0000000..0be0053 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/BreakpointOperation.java @@ -0,0 +1,120 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.JumpIfCompareOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BreakpointOperation extends SequenceBrushOperation { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final BuilderCodec CODEC = BuilderCodec.builder(BreakpointOperation.class, BreakpointOperation::new) + .append(new KeyedCodec<>("Label", Codec.STRING), (op, val) -> op.label = val, op -> op.label) + .documentation("Identifier for this breakpoint") + .add() + .append(new KeyedCodec<>("PrintMessage", Codec.BOOLEAN), (op, val) -> op.printMessage = val, op -> op.printMessage) + .documentation("Print a message when breakpoint is reached") + .add() + .append(new KeyedCodec<>("PrintState", Codec.BOOLEAN), (op, val) -> op.printState = val, op -> op.printState) + .documentation("Print brush state when breakpoint is reached") + .add() + .append(new KeyedCodec<>("EnterStepMode", Codec.BOOLEAN), (op, val) -> op.enterStepMode = val, op -> op.enterStepMode) + .documentation("Enter step-through mode (use /sb step to continue)") + .add() + .append( + new KeyedCodec<>("Condition", JumpIfCompareOperation.BrushConfigIntegerComparison.CODEC), (op, val) -> op.condition = val, op -> op.condition + ) + .documentation("Optional condition - breakpoint only triggers if condition passes") + .add() + .documentation("Debug breakpoint for scripted brushes") + .build(); + @Nonnull + private String label = ""; + @Nonnull + private Boolean printMessage = false; + @Nonnull + private Boolean printState = false; + @Nonnull + private Boolean enterStepMode = false; + @Nullable + private JumpIfCompareOperation.BrushConfigIntegerComparison condition = null; + + public BreakpointOperation() { + super("Breakpoint", "Debug breakpoint for scripted brushes", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor executor, + @Nonnull ComponentAccessor componentAccessor + ) { + if (executor.isEnableBreakpoints()) { + if (this.condition == null || this.condition.apply(brushConfig)) { + int currentIndex = executor.getCurrentOperationIndex(); + BrushConfigCommandExecutor.DebugOutputTarget outputTarget = executor.getDebugOutputTarget(); + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + String labelDisplay = this.label.isEmpty() ? "unnamed" : this.label; + boolean hasAnyOutput = this.printMessage || this.printState || this.enterStepMode; + if (hasAnyOutput) { + if (this.shouldSendToChat(outputTarget) && playerRefComponent != null) { + playerRefComponent.sendMessage( + Message.translation("server.builderTools.brushConfig.debug.breakpointReached").param("label", labelDisplay).param("index", currentIndex) + ); + } + + if (this.shouldSendToConsole(outputTarget)) { + LOGGER.at(Level.INFO).log("[Breakpoint] '%s' reached at operation #%d", labelDisplay, currentIndex); + } + } + + if (this.printState) { + String stateInfo = brushConfig.getInfo(); + if (this.shouldSendToChat(outputTarget) && playerRefComponent != null) { + playerRefComponent.sendMessage( + Message.translation("server.builderTools.brushConfig.debug.breakpointState").param("index", currentIndex).param("state", stateInfo) + ); + } + + if (this.shouldSendToConsole(outputTarget)) { + LOGGER.at(Level.INFO).log("[Breakpoint] [Operation #%d] %s", currentIndex, stateInfo); + } + } + + if (this.enterStepMode) { + if (this.shouldSendToChat(outputTarget) && playerRefComponent != null) { + playerRefComponent.sendMessage( + Message.translation("server.builderTools.brushConfig.debug.breakpointEnteringStepMode").param("label", labelDisplay) + ); + } + + if (this.shouldSendToConsole(outputTarget)) { + LOGGER.at(Level.INFO).log("[Breakpoint] '%s' - Entering step-through mode", labelDisplay); + } + + executor.setInDebugSteppingMode(true); + } + } + } + } + + private boolean shouldSendToChat(BrushConfigCommandExecutor.DebugOutputTarget target) { + return target == BrushConfigCommandExecutor.DebugOutputTarget.Chat || target == BrushConfigCommandExecutor.DebugOutputTarget.Both; + } + + private boolean shouldSendToConsole(BrushConfigCommandExecutor.DebugOutputTarget target) { + return target == BrushConfigCommandExecutor.DebugOutputTarget.Console || target == BrushConfigCommandExecutor.DebugOutputTarget.Both; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/ClearOperationMaskOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/ClearOperationMaskOperation.java new file mode 100644 index 0000000..4063aa4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/ClearOperationMaskOperation.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ClearOperationMaskOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ClearOperationMaskOperation.class, ClearOperationMaskOperation::new + ) + .documentation("Reset the Brush-Config-provided mask to nothing, keeping the brush tool's mask") + .build(); + + public ClearOperationMaskOperation() { + super("Clear Operation Mask", "Reset the Brush-Config-provided mask to nothing, keeping the brush tool's mask", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + brushConfig.clearOperationMask(); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/DeleteOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/DeleteOperation.java new file mode 100644 index 0000000..b2ed4c5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/DeleteOperation.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigEditStore; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.builtin.buildertools.utils.Material; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class DeleteOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(DeleteOperation.class, DeleteOperation::new) + .documentation("Remove all blocks and fluids in the area") + .build(); + + public DeleteOperation() { + super("Delete", "Remove all blocks and fluids in the area", true); + } + + @Override + public boolean modifyBlocks( + Ref ref, + BrushConfig brushConfig, + BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull BrushConfigEditStore edit, + int x, + int y, + int z, + ComponentAccessor componentAccessor + ) { + edit.setMaterial(x, y, z, Material.EMPTY); + return true; + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/EchoOnceOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/EchoOnceOperation.java new file mode 100644 index 0000000..d84d94e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/EchoOnceOperation.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EchoOnceOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(EchoOnceOperation.class, EchoOnceOperation::new) + .append(new KeyedCodec<>("Message", Codec.STRING), (op, val) -> op.messageArg = val, op -> op.messageArg) + .documentation("A message to print to chat when this operation is first executed") + .add() + .documentation("Print text to chat only on the first execution after brush load") + .build(); + private String messageArg = "Default message"; + private boolean hasBeenExecuted = false; + + public EchoOnceOperation() { + super("Echo Once to Chat", "Print text to chat only on the first execution after brush load", false); + } + + @Override + public void resetInternalState() { + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + if (!this.hasBeenExecuted) { + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerRefComponent.sendMessage(Message.raw(this.messageArg)); + this.hasBeenExecuted = true; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/EchoOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/EchoOperation.java new file mode 100644 index 0000000..8c4b202 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/EchoOperation.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EchoOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(EchoOperation.class, EchoOperation::new) + .append(new KeyedCodec<>("Message", Codec.STRING), (op, val) -> op.messageArg = val, op -> op.messageArg) + .documentation("A message to print to chat when this operation is ran") + .add() + .documentation("Print some text to chat") + .build(); + private String messageArg = "Default message"; + + public EchoOperation() { + super("Echo to Chat", "Print some text to chat", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerRefComponent.sendMessage(Message.raw(this.messageArg)); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/ErodeOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/ErodeOperation.java new file mode 100644 index 0000000..cf5440f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/ErodeOperation.java @@ -0,0 +1,144 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigEditStore; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.builtin.buildertools.utils.Material; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import javax.annotation.Nonnull; + +public class ErodeOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(ErodeOperation.class, ErodeOperation::new) + .append(new KeyedCodec<>("ErodePreset", new EnumCodec<>(ErodeOperation.ErodePreset.class)), (op, val) -> op.erodePresetArg = val, op -> op.erodePresetArg) + .documentation("An erosion preset to use with the operation") + .add() + .documentation("Erodes blocks following a preset") + .build(); + private ErodeOperation.ErodePreset erodePresetArg = ErodeOperation.ErodePreset.Default; + private static final Vector3i[] FACES_TO_CHECK = new Vector3i[]{ + new Vector3i(0, -1, 0), new Vector3i(0, 1, 0), new Vector3i(0, 0, 1), new Vector3i(0, 0, -1), new Vector3i(1, 0, 0), new Vector3i(-1, 0, 0) + }; + int iterationIndex; + + public ErodeOperation() { + super("erode", "Erodes blocks following a preset", true); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + } + + @Override + public boolean modifyBlocks( + Ref ref, + BrushConfig brushConfig, + BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull BrushConfigEditStore edit, + int x, + int y, + int z, + ComponentAccessor componentAccessor + ) { + if (this.iterationIndex < this.erodePresetArg.erosionIterations) { + this.iterateErosion(edit, x, y, z); + } else { + this.iterateFill(edit, x, y, z); + } + + return true; + } + + @Override + public void beginIterationIndex(int iterationIndex) { + this.iterationIndex = iterationIndex; + } + + @Override + public int getNumModifyBlockIterations() { + return this.erodePresetArg.erosionIterations + this.erodePresetArg.fillIterations; + } + + private void iterateFill(@Nonnull BrushConfigEditStore edit, int x, int y, int z) { + int block = edit.getBlock(x, y, z); + if (block == 0) { + int numFacesFound = 0; + Int2IntMap blockCount = new Int2IntOpenHashMap(); + + for (Vector3i direction : FACES_TO_CHECK) { + int blockAtRelativePosition = edit.getBlock(x + direction.x, y + direction.y, z + direction.z); + if (blockAtRelativePosition != 0) { + numFacesFound++; + blockCount.put(blockAtRelativePosition, blockCount.getOrDefault(blockAtRelativePosition, 0) + 1); + } + } + + if (numFacesFound >= this.erodePresetArg.fillFaces) { + int blockIdWithHighestQuantity = block; + int blockIdWithHighestQuantityAmount = 0; + + for (int blockId : blockCount.keySet()) { + int countOfType = blockCount.get(blockId); + if (countOfType > blockIdWithHighestQuantityAmount) { + blockIdWithHighestQuantity = blockId; + blockIdWithHighestQuantityAmount = countOfType; + } + } + + edit.setMaterial(x, y, z, Material.block(blockIdWithHighestQuantity)); + } + } + } + + private void iterateErosion(@Nonnull BrushConfigEditStore edit, int x, int y, int z) { + int block = edit.getBlock(x, y, z); + if (block != 0) { + int numFacesFound = 0; + + for (Vector3i direction : FACES_TO_CHECK) { + int blockAtRelativePosition = edit.getBlock(x + direction.x, y + direction.y, z + direction.z); + if (blockAtRelativePosition == 0) { + numFacesFound++; + } + } + + if (numFacesFound >= this.erodePresetArg.erosionFaces) { + edit.setMaterial(x, y, z, Material.EMPTY); + } + } + } + + public static enum ErodePreset { + Default(0, 1, 0, 1), + Melt(2, 1, 5, 1), + Fill(5, 1, 2, 1), + Smooth(3, 1, 3, 1), + Lift(6, 0, 1, 1), + FloatClean(6, 1, 6, 1); + + public final int erosionFaces; + public final int erosionIterations; + public final int fillFaces; + public final int fillIterations; + + private ErodePreset(int erosionFaces, int erosionIterations, int fillFaces, int fillIterations) { + this.erosionFaces = erosionFaces; + this.erosionIterations = erosionIterations; + this.fillFaces = fillFaces; + this.fillIterations = fillIterations; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/HeightmapLayerOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/HeightmapLayerOperation.java new file mode 100644 index 0000000..0baf396 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/HeightmapLayerOperation.java @@ -0,0 +1,127 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigEditStore; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BuilderTool; +import com.hypixel.hytale.server.core.codec.LayerEntryCodec; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HeightmapLayerOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(HeightmapLayerOperation.class, HeightmapLayerOperation::new) + .append( + new KeyedCodec<>("Layers", new ArrayCodec<>(LayerEntryCodec.CODEC, LayerEntryCodec[]::new)), + (op, val) -> op.layerArgs = (List)(val != null ? new ArrayList<>(Arrays.asList(val)) : List.of()), + op -> op.layerArgs != null ? op.layerArgs.toArray(new LayerEntryCodec[0]) : new LayerEntryCodec[0] + ) + .documentation("The layers to set") + .add() + .documentation("Replace blocks according to the specified layers in terms of their depth from the tallest block in its column") + .build(); + private List layerArgs = new ArrayList<>(); + + public HeightmapLayerOperation() { + super("Heightmap Layer", "Replace blocks according to the specified layers in terms of their depth from the tallest block in its column", true); + } + + @Override + public boolean modifyBlocks( + Ref ref, + BrushConfig brushConfig, + BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull BrushConfigEditStore edit, + int x, + int y, + int z, + ComponentAccessor componentAccessor + ) { + WorldChunk chunk = edit.getAccessor().getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + int depth = chunk.getHeight(x, z) - y; + if (depth >= 0 && edit.getBlock(x, y, z) > 0) { + Map toolArgs = this.getToolArgs(ref, componentAccessor); + int depthTestingAt = 0; + + for (LayerEntryCodec entry : this.layerArgs) { + depthTestingAt += entry.getDepth(); + if (depth < depthTestingAt) { + int blockId = this.resolveBlockId(entry, toolArgs, brushConfig); + if (blockId >= 0) { + edit.setBlock(x, y, z, blockId); + } + + return true; + } + } + + return true; + } else { + return true; + } + } + + private int resolveBlockId(LayerEntryCodec entry, @Nullable Map toolArgs, BrushConfig brushConfig) { + if (entry.isUseToolArg()) { + if (toolArgs != null && toolArgs.containsKey(entry.getMaterial())) { + if (toolArgs.get(entry.getMaterial()) instanceof BlockPattern blockPattern) { + return blockPattern.nextBlock(brushConfig.getRandom()); + } else { + brushConfig.setErrorFlag("HeightmapLayer: Tool arg '" + entry.getMaterial() + "' is not a Block type"); + return -1; + } + } else { + brushConfig.setErrorFlag("HeightmapLayer: Tool arg '" + entry.getMaterial() + "' not found"); + return -1; + } + } else { + return BlockType.getAssetMap().getIndex(entry.getMaterial()); + } + } + + @Nullable + private Map getToolArgs(Ref ref, ComponentAccessor componentAccessor) { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + return null; + } else { + BuilderTool builderTool = BuilderTool.getActiveBuilderTool(playerComponent); + if (builderTool == null) { + return null; + } else { + ItemStack itemStack = playerComponent.getInventory().getItemInHand(); + if (itemStack == null) { + return null; + } else { + BuilderTool.ArgData argData = builderTool.getItemArgData(itemStack); + return argData.tool(); + } + } + } + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/LayerOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/LayerOperation.java new file mode 100644 index 0000000..0b083cb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/LayerOperation.java @@ -0,0 +1,134 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigEditStore; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BuilderTool; +import com.hypixel.hytale.server.core.codec.LayerEntryCodec; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class LayerOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(LayerOperation.class, LayerOperation::new) + .append( + new KeyedCodec<>("Layers", new ArrayCodec<>(LayerEntryCodec.CODEC, LayerEntryCodec[]::new)), + (op, val) -> op.layerArgs = (List)(val != null ? new ArrayList<>(Arrays.asList(val)) : List.of()), + op -> op.layerArgs != null ? op.layerArgs.toArray(new LayerEntryCodec[0]) : new LayerEntryCodec[0] + ) + .documentation("The layers to set") + .add() + .documentation("Replace blocks according to the specified layers in terms of their depth from the nearest air block") + .build(); + private List layerArgs = new ArrayList<>(); + + public LayerOperation() { + super("Layer", "Replace blocks according to the specified layers in terms of their depth from the nearest air block", true); + } + + @Override + public boolean modifyBlocks( + Ref ref, + BrushConfig brushConfig, + BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull BrushConfigEditStore edit, + int x, + int y, + int z, + ComponentAccessor componentAccessor + ) { + int maxDepth = 0; + + for (LayerEntryCodec entry : this.layerArgs) { + maxDepth += entry.getDepth(); + } + + if (edit.getBlock(x, y, z) <= 0) { + return true; + } else { + Map toolArgs = this.getToolArgs(ref, componentAccessor); + + for (int depth = 0; depth < maxDepth; depth++) { + if (edit.getBlock(x, y + depth + 1, z) <= 0) { + int depthTestingAt = 0; + + for (LayerEntryCodec entry : this.layerArgs) { + depthTestingAt += entry.getDepth(); + if (depth < depthTestingAt) { + int blockId = this.resolveBlockId(entry, toolArgs, brushConfig); + if (blockId >= 0) { + edit.setBlock(x, y, z, blockId); + } + + return true; + } + } + } + } + + return true; + } + } + + private int resolveBlockId(LayerEntryCodec entry, @Nullable Map toolArgs, BrushConfig brushConfig) { + if (entry.isUseToolArg()) { + if (toolArgs != null && toolArgs.containsKey(entry.getMaterial())) { + if (toolArgs.get(entry.getMaterial()) instanceof BlockPattern blockPattern) { + return blockPattern.nextBlock(brushConfig.getRandom()); + } else { + brushConfig.setErrorFlag("Layer: Tool arg '" + entry.getMaterial() + "' is not a Block type"); + return -1; + } + } else { + brushConfig.setErrorFlag("Layer: Tool arg '" + entry.getMaterial() + "' not found"); + return -1; + } + } else { + return BlockType.getAssetMap().getIndex(entry.getMaterial()); + } + } + + @Nullable + private Map getToolArgs(Ref ref, ComponentAccessor componentAccessor) { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + return null; + } else { + BuilderTool builderTool = BuilderTool.getActiveBuilderTool(playerComponent); + if (builderTool == null) { + return null; + } else { + ItemStack itemStack = playerComponent.getInventory().getItemInHand(); + if (itemStack == null) { + return null; + } else { + BuilderTool.ArgData argData = builderTool.getItemArgData(itemStack); + return argData.tool(); + } + } + } + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/LiftOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/LiftOperation.java new file mode 100644 index 0000000..4003ae6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/LiftOperation.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigEditStore; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class LiftOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(LiftOperation.class, LiftOperation::new) + .documentation("Lift all blocks up by one (duplicating the block) that are touching air, preserving the material") + .build(); + + public LiftOperation() { + super("Lift Blocks", "Lift all blocks up by one (duplicating the block) that are touching air, preserving the material", true); + } + + @Override + public boolean modifyBlocks( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull BrushConfigEditStore edit, + int x, + int y, + int z, + ComponentAccessor componentAccessor + ) { + int currentBlock = edit.getBlock(x, y, z); + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + BuilderToolsPlugin.BuilderState builderState = BuilderToolsPlugin.getState(playerComponent, playerRefComponent); + if (currentBlock <= 0 && builderState.isAsideBlock(edit.getAccessor(), x, y, z)) { + int blockId = brushConfig.getNextBlock(); + if (blockId == 0) { + BuilderToolsPlugin.BuilderState.BlocksSampleData data = builderState.getBlocksSampleData(edit.getAccessor(), x, y, z, 1); + blockId = data.mainBlockNotAir; + } + + edit.setBlock(x, y, z, blockId); + } + + return true; + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/LoadIntFromToolArgOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/LoadIntFromToolArgOperation.java new file mode 100644 index 0000000..e2c79a9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/LoadIntFromToolArgOperation.java @@ -0,0 +1,125 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BuilderTool; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public class LoadIntFromToolArgOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder( + LoadIntFromToolArgOperation.class, LoadIntFromToolArgOperation::new + ) + .append(new KeyedCodec<>("ArgName", Codec.STRING, true), (op, val) -> op.argNameArg = val, op -> op.argNameArg) + .documentation("The name of the Int tool arg to load the value from") + .add() + .append( + new KeyedCodec<>("TargetField", new EnumCodec<>(LoadIntFromToolArgOperation.TargetField.class)), + (op, val) -> op.targetFieldArg = val, + op -> op.targetFieldArg + ) + .documentation("The brush config field to set (Width, Height, Density, Thickness, OffsetX, OffsetY, OffsetZ)") + .add() + .append(new KeyedCodec<>("Relative", Codec.BOOLEAN), (op, val) -> op.relativeArg = val, op -> op.relativeArg) + .documentation("When true, adds the loaded value to the current field value instead of replacing it") + .add() + .append(new KeyedCodec<>("Negate", Codec.BOOLEAN), (op, val) -> op.negateArg = val, op -> op.negateArg) + .documentation("When true, turns the sign of the value to negative") + .add() + .documentation("Load an integer from an Int tool arg and apply it to a brush config field") + .build(); + @Nonnull + public String argNameArg = ""; + @Nonnull + public LoadIntFromToolArgOperation.TargetField targetFieldArg = LoadIntFromToolArgOperation.TargetField.Width; + public boolean relativeArg; + public boolean negateArg; + + public LoadIntFromToolArgOperation() { + super("Load Int", "Load an integer from an Int tool arg and apply it to a brush config field", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + BuilderTool builderTool = BuilderTool.getActiveBuilderTool(playerComponent); + if (builderTool == null) { + brushConfig.setErrorFlag("LoadInt: No active builder tool"); + } else { + ItemStack itemStack = playerComponent.getInventory().getItemInHand(); + if (itemStack == null) { + brushConfig.setErrorFlag("LoadInt: No item in hand"); + } else { + BuilderTool.ArgData argData = builderTool.getItemArgData(itemStack); + Map toolArgs = argData.tool(); + if (toolArgs != null && toolArgs.containsKey(this.argNameArg)) { + Object argValue = toolArgs.get(this.argNameArg); + if (argValue instanceof Integer intValue) { + if (this.negateArg) { + intValue = intValue * -1; + } + + if (this.relativeArg) { + int currentValue = this.targetFieldArg.getValue(brushConfig); + this.targetFieldArg.setValue(brushConfig, currentValue + intValue); + } else { + this.targetFieldArg.setValue(brushConfig, intValue); + } + } else { + brushConfig.setErrorFlag("LoadInt: Tool arg '" + this.argNameArg + "' is not an Int type (found " + argValue.getClass().getSimpleName() + ")"); + } + } else { + brushConfig.setErrorFlag("LoadInt: Tool arg '" + this.argNameArg + "' not found"); + } + } + } + } + + public static enum TargetField { + None(null, null), + Width(BrushConfig::getShapeWidth, BrushConfig::setShapeWidth), + Height(BrushConfig::getShapeHeight, BrushConfig::setShapeHeight), + Density(BrushConfig::getDensity, BrushConfig::setDensity), + Thickness(BrushConfig::getShapeThickness, BrushConfig::setShapeThickness), + OffsetX(bc -> bc.getOriginOffset().x, (bc, val) -> bc.setOriginOffset(new Vector3i(val, bc.getOriginOffset().y, bc.getOriginOffset().z))), + OffsetY(bc -> bc.getOriginOffset().y, (bc, val) -> bc.setOriginOffset(new Vector3i(bc.getOriginOffset().x, val, bc.getOriginOffset().z))), + OffsetZ(bc -> bc.getOriginOffset().z, (bc, val) -> bc.setOriginOffset(new Vector3i(bc.getOriginOffset().x, bc.getOriginOffset().y, val))); + + private final Function getter; + private final BiConsumer setter; + + private TargetField(Function getter, BiConsumer setter) { + this.getter = getter; + this.setter = setter; + } + + public int getValue(BrushConfig brushConfig) { + return this.getter.apply(brushConfig); + } + + public void setValue(BrushConfig brushConfig, int value) { + this.setter.accept(brushConfig, value); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/LoadMaterialFromToolArgOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/LoadMaterialFromToolArgOperation.java new file mode 100644 index 0000000..d37ad9a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/LoadMaterialFromToolArgOperation.java @@ -0,0 +1,71 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BuilderTool; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import javax.annotation.Nonnull; + +public class LoadMaterialFromToolArgOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder( + LoadMaterialFromToolArgOperation.class, LoadMaterialFromToolArgOperation::new + ) + .append(new KeyedCodec<>("ArgName", Codec.STRING), (op, val) -> op.argNameArg = val, op -> op.argNameArg) + .documentation("The name of the Block tool arg to load the material pattern from") + .add() + .documentation("Load a block pattern from a Block tool arg and set it as the brush material") + .build(); + @Nonnull + public String argNameArg = ""; + + public LoadMaterialFromToolArgOperation() { + super("Load Material", "Load material pattern from a Block tool arg", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + BuilderTool builderTool = BuilderTool.getActiveBuilderTool(playerComponent); + if (builderTool == null) { + brushConfig.setErrorFlag("LoadMaterial: No active builder tool"); + } else { + ItemStack itemStack = playerComponent.getInventory().getItemInHand(); + if (itemStack == null) { + brushConfig.setErrorFlag("LoadMaterial: No item in hand"); + } else { + BuilderTool.ArgData argData = builderTool.getItemArgData(itemStack); + Map toolArgs = argData.tool(); + if (toolArgs != null && toolArgs.containsKey(this.argNameArg)) { + Object argValue = toolArgs.get(this.argNameArg); + if (argValue instanceof BlockPattern blockPattern) { + brushConfig.setPattern(blockPattern); + } else { + brushConfig.setErrorFlag( + "LoadMaterial: Tool arg '" + this.argNameArg + "' is not a Block type (found " + argValue.getClass().getSimpleName() + ")" + ); + } + } else { + brushConfig.setErrorFlag("LoadMaterial: Tool arg '" + this.argNameArg + "' not found"); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/MaterialOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/MaterialOperation.java new file mode 100644 index 0000000..2670bc2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/MaterialOperation.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.map.WeightedMap; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class MaterialOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(MaterialOperation.class, MaterialOperation::new) + .append(new KeyedCodec<>("BlockType", Codec.STRING), (op, val) -> op.blockTypeArg = val, op -> op.blockTypeArg) + .documentation("A single material to set the block type to. You can also use Block Pattern operation to set a pattern of blocks") + .add() + .documentation("Change the brush's material") + .build(); + @Nonnull + public String blockTypeArg = "Rock_Stone"; + + public MaterialOperation() { + super("Material", "Change the brush's material", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + WeightedMap.Builder builder = WeightedMap.builder(ArrayUtil.EMPTY_STRING_ARRAY); + builder.put(this.blockTypeArg, 1.0); + brushConfig.setPattern(new BlockPattern(builder.build())); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/MeltOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/MeltOperation.java new file mode 100644 index 0000000..39bb92f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/MeltOperation.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigEditStore; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.builtin.buildertools.utils.Material; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class MeltOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(MeltOperation.class, MeltOperation::new) + .documentation("Remove the top layer of blocks in the brush editing area") + .build(); + + public MeltOperation() { + super("Melt", "Remove the top layer of blocks in the brush editing area", true); + } + + @Override + public boolean modifyBlocks( + @Nonnull Ref ref, + BrushConfig brushConfig, + BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull BrushConfigEditStore edit, + int x, + int y, + int z, + ComponentAccessor componentAccessor + ) { + int currentBlock = edit.getBlock(x, y, z); + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + BuilderToolsPlugin.BuilderState builderState = BuilderToolsPlugin.getState(playerComponent, playerRefComponent); + if (currentBlock > 0 && builderState.isAsideAir(edit.getAccessor(), x, y, z)) { + edit.setMaterial(x, y, z, Material.EMPTY); + } else if (currentBlock > 0 && edit.getFluid(x, y + 1, z) != 0) { + edit.setMaterial(x, y, z, Material.EMPTY); + } + + return true; + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/PastePrefabOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/PastePrefabOperation.java new file mode 100644 index 0000000..89aa190 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/PastePrefabOperation.java @@ -0,0 +1,139 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigEditStore; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.PrefabListAsset; +import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabBufferCall; +import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabBufferUtil; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.IPrefabBuffer; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.accessor.LocalCachedChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.nio.file.Path; +import java.util.Random; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PastePrefabOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(PastePrefabOperation.class, PastePrefabOperation::new) + .append(new KeyedCodec<>("PrefabListAssetName", Codec.STRING), (op, val) -> op.prefabListAssetId = val, op -> op.prefabListAssetId) + .documentation("The name of a PrefabList asset") + .add() + .documentation("Paste a prefab at the origin+offset point") + .build(); + @Nullable + public String prefabListAssetId = null; + private boolean hasBeenPlacedAlready = false; + + public PastePrefabOperation() { + super("Paste Prefab", "Paste a prefab at the origin+offset point", true); + } + + @Override + public void resetInternalState() { + this.hasBeenPlacedAlready = false; + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + this.hasBeenPlacedAlready = false; + } + + @Override + public boolean modifyBlocks( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + BrushConfigCommandExecutor brushConfigCommandExecutor, + BrushConfigEditStore edit, + int x, + int y, + int z, + ComponentAccessor componentAccessor + ) { + if (this.hasBeenPlacedAlready) { + return false; + } else { + PrefabListAsset prefabListAsset = this.prefabListAssetId != null ? PrefabListAsset.getAssetMap().getAsset(this.prefabListAssetId) : null; + if (prefabListAsset == null) { + brushConfig.setErrorFlag("PrefabList asset not found: " + this.prefabListAssetId); + return false; + } else { + Path prefabPath = prefabListAsset.getRandomPrefab(); + if (prefabPath == null) { + brushConfig.setErrorFlag("No prefab found in prefab list. Please double check your PrefabList asset."); + return false; + } else { + World world = componentAccessor.getExternalData().getWorld(); + PrefabBuffer.PrefabBufferAccessor accessor = PrefabBufferUtil.loadBuffer(prefabPath).newAccess(); + this.hasBeenPlacedAlready = true; + double xLength = accessor.getMaxX() - accessor.getMinX(); + double zLength = accessor.getMaxZ() - accessor.getMinZ(); + int prefabRadius = (int)MathUtil.fastFloor(0.5 * Math.sqrt(xLength * xLength + zLength * zLength)); + LocalCachedChunkAccessor chunkAccessor = LocalCachedChunkAccessor.atWorldCoords(world, x, z, prefabRadius); + BlockTypeAssetMap blockTypeMap = BlockType.getAssetMap(); + accessor.forEach( + IPrefabBuffer.iterateAllColumns(), + (xi, yi, zi, blockId, holder, supportValue, rotation, filler, call, fluidId, fluidLevel) -> { + int bx = x + xi; + int by = y + yi; + int bz = z + zi; + WorldChunk chunk = chunkAccessor.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(bx, bz)); + Store store = chunk.getWorld().getChunkStore().getStore(); + ChunkColumn column = store.getComponent(chunk.getReference(), ChunkColumn.getComponentType()); + Ref section = column.getSection(ChunkUtil.chunkCoordinate(by)); + FluidSection fluidSection = store.ensureAndGetComponent(section, FluidSection.getComponentType()); + fluidSection.setFluid(bx, by, bz, fluidId, (byte)fluidLevel); + BlockType block = blockTypeMap.getAsset(blockId); + String blockKey = block.getId(); + if (filler == 0) { + RotationTuple rot = RotationTuple.get(rotation); + chunk.placeBlock(bx, by, bz, blockKey, rot.yaw(), rot.pitch(), rot.roll(), 0); + if (supportValue != 0) { + Ref chunkRef = chunk.getReference(); + store = chunkRef.getStore(); + column = store.getComponent(chunkRef, ChunkColumn.getComponentType()); + BlockPhysics.setSupportValue(store, column.getSection(ChunkUtil.chunkCoordinate(by)), bx, by, bz, supportValue); + } + + if (holder != null) { + chunk.setState(bx, by, bz, holder.clone()); + } + } + }, + (xi, zi, entityWrappers, t) -> {}, + (xi, yi, zi, path, fitHeightmap, inheritSeed, inheritHeightCondition, weights, rotation, t) -> {}, + new PrefabBufferCall(new Random(), PrefabRotation.fromRotation(Rotation.None)) + ); + accessor.release(); + return false; + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/ReplaceOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/ReplaceOperation.java new file mode 100644 index 0000000..f100b89 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/ReplaceOperation.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigEditStore; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.builtin.buildertools.utils.Material; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ReplaceOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(ReplaceOperation.class, ReplaceOperation::new) + .append(new KeyedCodec<>("FromBlockType", Codec.STRING), (op, val) -> op.blockTypeKeyToReplace = val, op -> op.blockTypeKeyToReplace) + .documentation("The block type to get replaced") + .add() + .append(new KeyedCodec<>("ToBlockPattern", BlockPattern.CODEC), (op, val) -> op.replacementBlocks = val, op -> op.replacementBlocks) + .documentation("The pattern of blocks set to") + .add() + .documentation("Replace one kind of block with another pattern of blocks within the current brush editing area") + .build(); + @Nonnull + public String blockTypeKeyToReplace = "Rock_Stone"; + @Nonnull + public BlockPattern replacementBlocks = BlockPattern.parse("Rock_Stone"); + + public ReplaceOperation() { + super("Replace Blocks", "Replace one kind of block with another pattern of blocks within the current brush editing area", true); + } + + @Override + public boolean modifyBlocks( + Ref ref, + @Nonnull BrushConfig brushConfig, + BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull BrushConfigEditStore edit, + int x, + int y, + int z, + ComponentAccessor componentAccessor + ) { + int block = edit.getBlock(x, y, z); + if (block == BlockType.getAssetMap().getIndex(this.blockTypeKeyToReplace)) { + edit.setMaterial(x, y, z, Material.fromPattern(this.replacementBlocks, brushConfig.getRandom())); + } + + return true; + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/RunCommandOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/RunCommandOperation.java new file mode 100644 index 0000000..b3a4900 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/RunCommandOperation.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.command.system.CommandManager; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; + +public class RunCommandOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(RunCommandOperation.class, RunCommandOperation::new) + .append(new KeyedCodec<>("CommandToRun", Codec.STRING), (op, val) -> op.commandArg = val, op -> op.commandArg) + .documentation( + "Runs a command, substituting the strings... \n'{x}', '{y}', and '{z}' for the origin coordinates\n'{radius}' with width/2, '{width}' with width, and '{height}' with height\n'{var:} with the value of the persistent variable'" + ) + .add() + .documentation("Runs a command, see help for argument replacements") + .build(); + private String commandArg = ""; + private static final Pattern regexBracketPattern = Pattern.compile("\\{var:(\\w*)}"); + + public RunCommandOperation() { + super("runCommand", "Runs a command, see help for argument replacements", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + String commandString = this.commandArg; + if (commandString.startsWith("/")) { + commandString = commandString.substring(1); + } + + Vector3i origin = brushConfig.getOrigin(); + commandString = commandString.replace("{x}", String.valueOf(origin.x)) + .replace("{y}", String.valueOf(origin.y)) + .replace("{z}", String.valueOf(origin.z)) + .replace("{width}", String.valueOf(brushConfig.getShapeWidth())) + .replace("{height}", String.valueOf(brushConfig.getShapeHeight())) + .replace("{radius}", String.valueOf(brushConfig.getShapeWidth() / 2.0)); + Matcher matcher = regexBracketPattern.matcher(commandString); + + while (matcher.find()) { + String variableName = commandString.substring(matcher.start(1), matcher.end(1)); + String replacementValue = String.valueOf(brushConfigCommandExecutor.getPersistentVariableOrDefault(variableName, Integer.MIN_VALUE)); + commandString = commandString.replaceFirst(regexBracketPattern.pattern(), replacementValue); + } + + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + CommandManager.get().handleCommand(playerComponent, commandString); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/SetDensity.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/SetDensity.java new file mode 100644 index 0000000..d7311c7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/SetDensity.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SetDensity extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(SetDensity.class, SetDensity::new) + .append(new KeyedCodec<>("Density", Codec.INTEGER), (op, val) -> op.density = val, op -> op.density) + .documentation("Changes the likelyhood that a given block will be processed") + .add() + .documentation( + "Sets the random chance that any given block being set will actually get set, otherwise getting cancelled. Ex: a value of 30 is a 30% chance blocks will appear with a set operation." + ) + .build(); + public Integer density = 100; + + public SetDensity() { + super( + "Density", + "Sets the random chance that any given block being set will actually get set, otherwise getting cancelled. Ex: a value of 30 is a 30% chance blocks will appear with a set operation.", + false + ); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + brushConfig.setDensity(MathUtil.clamp(this.density, 1, 100)); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/SetOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/SetOperation.java new file mode 100644 index 0000000..92ba5fd --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/SetOperation.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigEditStore; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SetOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(SetOperation.class, SetOperation::new) + .documentation( + "Runs a 'set' operation using the parameters of the brush configuration. Supports both blocks and fluids - if the pattern contains fluid items, it sets the fluid layer instead." + ) + .build(); + + public SetOperation() { + super("Set", "Runs a 'set' operation using the parameters of the brush configuration", true); + } + + @Override + public boolean modifyBlocks( + Ref ref, + @Nonnull BrushConfig brushConfig, + BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull BrushConfigEditStore edit, + int x, + int y, + int z, + ComponentAccessor componentAccessor + ) { + edit.setMaterial(x, y, z, brushConfig.getNextMaterial()); + return true; + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/ShapeOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/ShapeOperation.java new file mode 100644 index 0000000..4be9ed6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/ShapeOperation.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.packets.buildertools.BrushShape; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ShapeOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(ShapeOperation.class, ShapeOperation::new) + .append(new KeyedCodec<>("Shape", new EnumCodec<>(BrushShape.class)), (op, val) -> op.brushShapeArg = val, op -> op.brushShapeArg) + .documentation("Changes the brush shape") + .add() + .documentation("Changes the shape of the brush editing area") + .build(); + @Nonnull + public BrushShape brushShapeArg = BrushShape.Cube; + + public ShapeOperation() { + super("Shape", "Changes the shape of the brush editing area", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + brushConfig.setShape(this.brushShapeArg); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/SmoothOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/SmoothOperation.java new file mode 100644 index 0000000..9c2bbdc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/SmoothOperation.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigEditStore; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SmoothOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(SmoothOperation.class, SmoothOperation::new) + .append(new KeyedCodec<>("SmoothStrength", Codec.INTEGER), (op, val) -> op.smoothStrength = val, op -> op.smoothStrength) + .documentation("The strength of smoothing") + .add() + .documentation("Smooths the blocks within the brush area as to make the area more natural looking") + .build(); + @Nonnull + public Integer smoothStrength = 2; + private int smoothVolume; + private int smoothRadius; + + public SmoothOperation() { + super("Smooth Blocks", "Smooths the blocks within the brush area as to make the area more natural looking", true); + } + + private void updateVolumeAndRadius() { + int strength = this.smoothStrength; + this.smoothRadius = Math.min(strength, 4); + int smoothRange = this.smoothRadius * 2 + 1; + this.smoothVolume = smoothRange * smoothRange * smoothRange; + } + + @Override + public boolean modifyBlocks( + Ref ref, + BrushConfig brushConfig, + BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull BrushConfigEditStore edit, + int x, + int y, + int z, + ComponentAccessor componentAccessor + ) { + int currentBlock = edit.getBlock(x, y, z); + BuilderToolsPlugin.BuilderState.BlocksSampleData data = edit.getBlockSampledataIncludingPreviousStages(x, y, z, 2); + if (currentBlock != data.mainBlock && data.mainBlockCount > this.smoothVolume * 0.5F) { + edit.setBlock(x, y, z, data.mainBlock); + } + + return true; + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + this.updateVolumeAndRadius(); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/dimensions/DimensionsOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/dimensions/DimensionsOperation.java new file mode 100644 index 0000000..0c87aee --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/dimensions/DimensionsOperation.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.dimensions; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeInteger; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class DimensionsOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(DimensionsOperation.class, DimensionsOperation::new) + .append(new KeyedCodec<>("Width", RelativeInteger.CODEC), (op, val) -> op.widthArg = val, op -> op.widthArg) + .documentation("Sets the width of the brush to the specified amount, optionally relative to the existing amount when using prefixing with tilde") + .add() + .append(new KeyedCodec<>("Height", RelativeInteger.CODEC), (op, val) -> op.heightArg = val, op -> op.heightArg) + .documentation("Sets the height of the brush to the specified amount, optionally relative to the existing amount when using prefixing with tilde") + .add() + .documentation("Set, add, or subtract from the dimensions of the brush area") + .build(); + @Nonnull + public RelativeInteger widthArg = new RelativeInteger(3, false); + @Nonnull + public RelativeInteger heightArg = new RelativeInteger(3, false); + + public DimensionsOperation() { + super("Modify Dimensions", "Set, add, or subtract from the dimensions of the brush area", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + int width = this.widthArg.resolve(brushConfig.getShapeWidth()); + int height = this.heightArg.resolve(brushConfig.getShapeHeight()); + brushConfig.setShapeWidth(MathUtil.clamp(width, 1, 75)); + brushConfig.setShapeHeight(MathUtil.clamp(height, 1, 75)); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/dimensions/RandomizeDimensionsOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/dimensions/RandomizeDimensionsOperation.java new file mode 100644 index 0000000..362c730 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/dimensions/RandomizeDimensionsOperation.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.dimensions; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeIntegerRange; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class RandomizeDimensionsOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder( + RandomizeDimensionsOperation.class, RandomizeDimensionsOperation::new + ) + .append(new KeyedCodec<>("WidthRange", RelativeIntegerRange.CODEC), (op, val) -> op.widthRangeArg = val, op -> op.widthRangeArg) + .documentation("The range of values for the width, optionally relative using tilde") + .add() + .append(new KeyedCodec<>("HeightRange", RelativeIntegerRange.CODEC), (op, val) -> op.heightRangeArg = val, op -> op.heightRangeArg) + .documentation("The range of values for the height, optionally relative using tilde") + .add() + .documentation("Randomize the dimensions of the brush area") + .build(); + @Nonnull + public RelativeIntegerRange widthRangeArg = new RelativeIntegerRange(1, 1); + @Nonnull + public RelativeIntegerRange heightRangeArg = new RelativeIntegerRange(1, 1); + + public RandomizeDimensionsOperation() { + super("Randomize Dimensions", "Randomize the dimensions of the brush area", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + brushConfig.setShapeWidth(this.widthRangeArg.getNumberInRange(brushConfig.getShapeWidth())); + brushConfig.setShapeHeight(this.heightRangeArg.getNumberInRange(brushConfig.getShapeHeight())); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/ExitOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/ExitOperation.java new file mode 100644 index 0000000..b73def8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/ExitOperation.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ExitOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(ExitOperation.class, ExitOperation::new) + .documentation("Exit the execution of the stack") + .build(); + + public ExitOperation() { + super("exit", "Exit the execution of the stack", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + brushConfigCommandExecutor.exitExecution(ref, componentAccessor); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpIfBlockTypeOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpIfBlockTypeOperation.java new file mode 100644 index 0000000..5975f3d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpIfBlockTypeOperation.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeVector3i; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockMask; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nonnull; + +public class JumpIfBlockTypeOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(JumpIfBlockTypeOperation.class, JumpIfBlockTypeOperation::new) + .append( + new KeyedCodec<>("Offset", new ArrayCodec<>(RelativeVector3i.CODEC, RelativeVector3i[]::new)), + (op, val) -> op.offsetListArg = val != null ? Arrays.asList(val) : List.of(), + op -> op.offsetListArg.toArray(new RelativeVector3i[0]) + ) + .documentation("The offset(s) to compare from. In 3 dimensions. Each value is optionally relative by prefixing it with a tilde.") + .add() + .append(new KeyedCodec<>("Mask", BlockMask.CODEC), (op, val) -> op.blockMaskArg = val, op -> op.blockMaskArg) + .documentation("The block mask for the comparison.") + .add() + .append(new KeyedCodec<>("StoredIndexName", Codec.STRING), (op, val) -> op.indexVariableNameArg = val, op -> op.indexVariableNameArg) + .documentation("The labeled index to jump to, previous or future") + .add() + .documentation("Jump the execution of the stack based on a block type comparison") + .build(); + @Nonnull + public List offsetListArg = List.of(); + @Nonnull + public BlockMask blockMaskArg = BlockMask.EMPTY; + @Nonnull + public String indexVariableNameArg = "Undefined"; + + public JumpIfBlockTypeOperation() { + super("Jump If Block Type Comparison", "Jump the execution of the stack based on a block type comparison", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + Vector3i currentBrushOrigin = brushConfig.getOriginAfterOffset(); + if (currentBrushOrigin == null) { + brushConfig.setErrorFlag("Could not find the origin for the operation."); + } else { + for (RelativeVector3i offset : this.offsetListArg) { + Vector3i brushOriginAfterOffset = offset.resolve(currentBrushOrigin); + int targetBlockId = brushConfigCommandExecutor.getEdit().getBlock(brushOriginAfterOffset.x, brushOriginAfterOffset.y, brushOriginAfterOffset.z); + int targetFluidId = brushConfigCommandExecutor.getEdit().getFluid(brushOriginAfterOffset.x, brushOriginAfterOffset.y, brushOriginAfterOffset.z); + if (!this.blockMaskArg + .isExcluded( + brushConfigCommandExecutor.getEdit().getAccessor(), + brushOriginAfterOffset.x, + brushOriginAfterOffset.y, + brushOriginAfterOffset.z, + null, + null, + targetBlockId, + targetFluidId + )) { + brushConfigCommandExecutor.loadOperatingIndex(this.indexVariableNameArg); + return; + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpIfClickType.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpIfClickType.java new file mode 100644 index 0000000..3e4f0c6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpIfClickType.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class JumpIfClickType extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(JumpIfClickType.class, JumpIfClickType::new) + .append(new KeyedCodec<>("StoredIndexName", Codec.STRING), (op, val) -> op.indexVariableNameArg = val, op -> op.indexVariableNameArg) + .documentation("The labeled index to jump to, previous or future") + .add() + .append( + new KeyedCodec<>("ClickType", new EnumCodec<>(JumpIfClickType.ClickType.class)), (op, val) -> op.clickTypeArg = val, op -> op.clickTypeArg + ) + .documentation("The click type (left or right) to compare with to jump") + .add() + .documentation("Jump the execution of the stack based on the click type") + .build(); + @Nonnull + public String indexVariableNameArg = "Undefined"; + @Nonnull + public JumpIfClickType.ClickType clickTypeArg = JumpIfClickType.ClickType.Left; + + public JumpIfClickType() { + super("Jump If Click Type", "Jump the execution of the stack based on the click type", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.clickTypeArg.equals(JumpIfClickType.ClickType.Left) && brushConfig.getInteractionType().equals(InteractionType.Primary)) { + brushConfigCommandExecutor.loadOperatingIndex(this.indexVariableNameArg); + } else if (this.clickTypeArg.equals(JumpIfClickType.ClickType.Right) && brushConfig.getInteractionType().equals(InteractionType.Secondary)) { + brushConfigCommandExecutor.loadOperatingIndex(this.indexVariableNameArg); + } + } + + public static enum ClickType { + Left, + Right; + + private ClickType() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpIfCompareOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpIfCompareOperation.java new file mode 100644 index 0000000..d9cc3bd --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpIfCompareOperation.java @@ -0,0 +1,112 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public class JumpIfCompareOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(JumpIfCompareOperation.class, JumpIfCompareOperation::new) + .append( + new KeyedCodec<>( + "Comparisons", + new ArrayCodec<>(JumpIfCompareOperation.BrushConfigIntegerComparison.CODEC, JumpIfCompareOperation.BrushConfigIntegerComparison[]::new) + ), + (op, val) -> op.comparisonsArg = val != null ? Arrays.asList(val) : List.of(), + op -> op.comparisonsArg.toArray(new JumpIfCompareOperation.BrushConfigIntegerComparison[0]) + ) + .documentation("The comparison(s) that will be executed using AND between them to see if you should jump or not") + .add() + .append(new KeyedCodec<>("StoredIndexName", Codec.STRING), (op, val) -> op.indexVariableNameArg = val, op -> op.indexVariableNameArg) + .documentation("The labeled index to jump to, previous or future") + .add() + .documentation("Jump stack execution to a stored index operation based on a specified conditional using the brush config data") + .build(); + @Nonnull + public List comparisonsArg = List.of(); + @Nonnull + public String indexVariableNameArg = "Undefined"; + + public JumpIfCompareOperation() { + super("Jump If Int Comparison", "Jump stack execution to a stored index operation based on a specified conditional using the brush config data", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + boolean success = true; + + for (JumpIfCompareOperation.BrushConfigIntegerComparison brushConfigIntegerComparison : this.comparisonsArg) { + boolean result = brushConfigIntegerComparison.apply(brushConfig); + if (!result) { + success = false; + } + } + + if (success) { + brushConfigCommandExecutor.loadOperatingIndex(this.indexVariableNameArg); + } + } + + public static class BrushConfigIntegerComparison implements Function { + public static final BuilderCodec CODEC = BuilderCodec.builder( + JumpIfCompareOperation.BrushConfigIntegerComparison.class, JumpIfCompareOperation.BrushConfigIntegerComparison::new + ) + .append( + new KeyedCodec<>("DataGettingFlag", new EnumCodec<>(BrushConfig.DataGettingFlags.class)), + (comp, val) -> comp.dataGettingFlag = val, + comp -> comp.dataGettingFlag + ) + .add() + .append( + new KeyedCodec<>("IntegerComparisonOperator", new EnumCodec<>(ArgTypes.IntegerComparisonOperator.class)), + (comp, val) -> comp.integerComparisonOperator = val, + comp -> comp.integerComparisonOperator + ) + .add() + .append(new KeyedCodec<>("ValueToCompareTo", Codec.INTEGER), (comp, val) -> comp.valueToCompareTo = val, comp -> comp.valueToCompareTo) + .add() + .build(); + private BrushConfig.DataGettingFlags dataGettingFlag; + private ArgTypes.IntegerComparisonOperator integerComparisonOperator; + private int valueToCompareTo; + + public BrushConfigIntegerComparison() { + } + + public BrushConfigIntegerComparison( + BrushConfig.DataGettingFlags dataGettingFlag, ArgTypes.IntegerComparisonOperator integerComparisonOperator, int valueToCompareTo + ) { + this.dataGettingFlag = dataGettingFlag; + this.integerComparisonOperator = integerComparisonOperator; + this.valueToCompareTo = valueToCompareTo; + } + + @Nonnull + public Boolean apply(BrushConfig brushConfig) { + return this.integerComparisonOperator.compare(this.dataGettingFlag.getValue(brushConfig), this.valueToCompareTo); + } + + @Nonnull + @Override + public String toString() { + return this.dataGettingFlag.name() + " " + this.integerComparisonOperator.getStringRepresentation() + " " + this.valueToCompareTo; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpIfStringMatchOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpIfStringMatchOperation.java new file mode 100644 index 0000000..f660ace --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpIfStringMatchOperation.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class JumpIfStringMatchOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(JumpIfStringMatchOperation.class, JumpIfStringMatchOperation::new) + .append(new KeyedCodec<>("StoredIndexName", Codec.STRING), (op, val) -> op.indexVariableNameArg = val, op -> op.indexVariableNameArg) + .documentation("The labeled index to jump to, previous or future") + .add() + .append(new KeyedCodec<>("LeftSideOfStatement", Codec.STRING), (op, val) -> op.sideOneArg = val, op -> op.sideOneArg) + .documentation("The left side of the statement for checking case-insensitive equals") + .add() + .append(new KeyedCodec<>("RightSideOfStatement", Codec.STRING), (op, val) -> op.sideTwoArg = val, op -> op.sideTwoArg) + .documentation("The right side of the statement for checking case-insensitive equals") + .add() + .documentation("Jump the execution of the stack to the stored point if a string matches, useful for macro commands.") + .build(); + @Nonnull + public String indexVariableNameArg = "Undefined"; + @Nonnull + public String sideOneArg = "Undefined"; + @Nonnull + public String sideTwoArg = "Undefined"; + + public JumpIfStringMatchOperation() { + super("Jump If String Matches", "Jump the execution of the stack to the stored point if a string matches, useful for macro commands.", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.sideOneArg.equalsIgnoreCase(this.sideTwoArg)) { + brushConfigCommandExecutor.loadOperatingIndex(this.indexVariableNameArg); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpIfToolArgOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpIfToolArgOperation.java new file mode 100644 index 0000000..8da06b4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpIfToolArgOperation.java @@ -0,0 +1,129 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BuilderTool; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import javax.annotation.Nonnull; + +public class JumpIfToolArgOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(JumpIfToolArgOperation.class, JumpIfToolArgOperation::new) + .append(new KeyedCodec<>("ArgName", Codec.STRING), (op, val) -> op.argNameArg = val, op -> op.argNameArg) + .documentation("The name of the tool arg to compare") + .add() + .append( + new KeyedCodec<>("ComparisonType", new EnumCodec<>(JumpIfToolArgOperation.ComparisonType.class)), + (op, val) -> op.comparisonTypeArg = val, + op -> op.comparisonTypeArg + ) + .documentation("The type of comparison to perform") + .add() + .append(new KeyedCodec<>("ComparisonValue", Codec.STRING), (op, val) -> op.comparisonValueArg = val, op -> op.comparisonValueArg) + .documentation("The value to compare against (for boolean: 'true' or 'false', for string: the exact string or dropdown option)") + .add() + .append(new KeyedCodec<>("StoredIndexName", Codec.STRING), (op, val) -> op.indexVariableNameArg = val, op -> op.indexVariableNameArg) + .documentation("The labeled index to jump to, previous or future") + .add() + .documentation("Jump stack execution based on a builder tool argument comparison (supports checkbox/bool and dropdown/option types)") + .build(); + @Nonnull + public String argNameArg = ""; + @Nonnull + public JumpIfToolArgOperation.ComparisonType comparisonTypeArg = JumpIfToolArgOperation.ComparisonType.Equals; + @Nonnull + public String comparisonValueArg = ""; + @Nonnull + public String indexVariableNameArg = "Undefined"; + + public JumpIfToolArgOperation() { + super("Jump If Tool Arg", "Jump stack execution based on a builder tool argument comparison", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + BuilderTool builderTool = BuilderTool.getActiveBuilderTool(playerComponent); + if (builderTool == null) { + brushConfig.setErrorFlag("JumpIfToolArg: No active builder tool"); + } else { + ItemStack itemStack = playerComponent.getInventory().getItemInHand(); + if (itemStack == null) { + brushConfig.setErrorFlag("JumpIfToolArg: No item in hand"); + } else { + BuilderTool.ArgData argData = builderTool.getItemArgData(itemStack); + Map toolArgs = argData.tool(); + if (toolArgs != null && toolArgs.containsKey(this.argNameArg)) { + Object argValue = toolArgs.get(this.argNameArg); + boolean shouldJump = false; + if (argValue instanceof Boolean) { + boolean boolValue = (Boolean)argValue; + boolean expectedValue = Boolean.parseBoolean(this.comparisonValueArg); + switch (this.comparisonTypeArg) { + case Equals: + shouldJump = boolValue == expectedValue; + break; + case NotEquals: + shouldJump = boolValue != expectedValue; + } + } else if (argValue instanceof String stringValue) { + switch (this.comparisonTypeArg) { + case Equals: + shouldJump = stringValue.equalsIgnoreCase(this.comparisonValueArg); + break; + case NotEquals: + shouldJump = !stringValue.equalsIgnoreCase(this.comparisonValueArg); + break; + case Contains: + shouldJump = stringValue.toLowerCase().contains(this.comparisonValueArg.toLowerCase()); + } + } else { + String stringValue = argValue.toString(); + switch (this.comparisonTypeArg) { + case Equals: + shouldJump = stringValue.equalsIgnoreCase(this.comparisonValueArg); + break; + case NotEquals: + shouldJump = !stringValue.equalsIgnoreCase(this.comparisonValueArg); + break; + case Contains: + shouldJump = stringValue.toLowerCase().contains(this.comparisonValueArg.toLowerCase()); + } + } + + if (shouldJump) { + brushConfigCommandExecutor.loadOperatingIndex(this.indexVariableNameArg); + } + } else { + brushConfig.setErrorFlag("JumpIfToolArg: Tool arg '" + this.argNameArg + "' not found"); + } + } + } + } + + public static enum ComparisonType { + Equals, + NotEquals, + Contains; + + private ComparisonType() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpToIndexOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpToIndexOperation.java new file mode 100644 index 0000000..bee70d3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpToIndexOperation.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class JumpToIndexOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(JumpToIndexOperation.class, JumpToIndexOperation::new) + .append(new KeyedCodec<>("StoredIndexName", Codec.STRING), (op, val) -> op.variableNameArg = val, op -> op.variableNameArg) + .documentation("The labeled index to jump to, previous or future") + .add() + .documentation("Jump the stack execution to the point in the stack of the given saved index name") + .build(); + @Nonnull + public String variableNameArg = "Undefined"; + + public JumpToIndexOperation() { + super("Jump to Index", "Jump the stack execution to the point in the stack of the given saved index name", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + brushConfigCommandExecutor.loadOperatingIndex(this.variableNameArg); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpToRandomIndex.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpToRandomIndex.java new file mode 100644 index 0000000..4ec5826 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/JumpToRandomIndex.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.common.map.IWeightedMap; +import com.hypixel.hytale.common.map.WeightedMap; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.codec.PairCodec; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.Pair; +import java.util.ArrayList; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class JumpToRandomIndex extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(JumpToRandomIndex.class, JumpToRandomIndex::new) + .append( + new KeyedCodec<>("WeightedListOfIndexNames", new ArrayCodec<>(PairCodec.IntegerStringPair.CODEC, PairCodec.IntegerStringPair[]::new)), (op, val) -> { + if (val != null && val.length != 0) { + WeightedMap.Builder builder = WeightedMap.builder(new String[0]); + + for (PairCodec.IntegerStringPair pair : val) { + builder.put(pair.getRight(), pair.getLeft().doubleValue()); + } + + op.variableNameArg = builder.build(); + } else { + op.variableNameArg = null; + } + }, op -> { + if (op.variableNameArg == null) { + return new PairCodec.IntegerStringPair[0]; + } else { + ArrayList pairs = new ArrayList<>(); + op.variableNameArg.forEachEntry((str, weight) -> pairs.add(PairCodec.IntegerStringPair.fromPair(Pair.of((int)weight, str)))); + return pairs.toArray(new PairCodec.IntegerStringPair[0]); + } + } + ) + .documentation("A weighted list of weights and their corresponding index names") + .add() + .documentation("Jump the stack execution to a random location in the stack using the specified weights and saved index names") + .build(); + @Nullable + public IWeightedMap variableNameArg = null; + + public JumpToRandomIndex() { + super( + "Jump to Random Stored Index", "Jump the stack execution to a random location in the stack using the specified weights and saved index names", false + ); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.variableNameArg != null) { + String indexName = this.variableNameArg.get(brushConfig.getRandom()); + brushConfigCommandExecutor.loadOperatingIndex(indexName); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/loops/CircleOffsetAndLoopOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/loops/CircleOffsetAndLoopOperation.java new file mode 100644 index 0000000..2c7ef87 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/loops/CircleOffsetAndLoopOperation.java @@ -0,0 +1,129 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.loops; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class CircleOffsetAndLoopOperation extends SequenceBrushOperation { + public static final int MAX_REPETITIONS = 100; + public static final int IDLE_STATE = -1; + public static final double TWO_PI = Math.PI * 2; + public static final BuilderCodec CODEC = BuilderCodec.builder( + CircleOffsetAndLoopOperation.class, CircleOffsetAndLoopOperation::new + ) + .append(new KeyedCodec<>("StoredIndexName", Codec.STRING), (op, val) -> op.indexNameArg = val, op -> op.indexNameArg) + .documentation("The name of the previously stored index to begin the loop at. Note: This can only be an index previous to the current.") + .add() + .append(new KeyedCodec<>("NumberOfCirclePoints", Codec.INTEGER), (op, val) -> op.numberOfCirclePointsArg = val, op -> op.numberOfCirclePointsArg) + .documentation("The amount of equidistant points on the circle to loop at") + .add() + .append(new KeyedCodec<>("CircleRadius", Codec.INTEGER), (op, val) -> op.circleRadiusArg = val, op -> op.circleRadiusArg) + .documentation("The radius of the circle") + .add() + .append(new KeyedCodec<>("FlipDirection", Codec.BOOLEAN, true), (op, val) -> op.flipArg = val, op -> op.flipArg) + .documentation("Whether to invert the direction of the circle. Useful for non-zero offset modifiers.") + .add() + .append(new KeyedCodec<>("RotateDirection", Codec.BOOLEAN, true), (op, val) -> op.rotateArg = val, op -> op.rotateArg) + .documentation("Whether to invert the direction of the circle. Useful for non-zero offset modifiers.") + .add() + .documentation("Loops specified instructions and changes the offset after each loop in order to execute around a circle") + .build(); + @Nonnull + public String indexNameArg = "Undefined"; + @Nonnull + public Integer numberOfCirclePointsArg = 3; + @Nonnull + public Integer circleRadiusArg = 5; + public boolean flipArg = false; + public boolean rotateArg = false; + private int repetitionsRemaining = -1; + @Nonnull + private List offsetsInCircle = new ObjectArrayList<>(); + @Nonnull + private Vector3i offsetWhenFirstReachedOperation = Vector3i.ZERO; + @Nonnull + private Vector3i previousCircleOffset = Vector3i.ZERO; + + public CircleOffsetAndLoopOperation() { + super( + "Loop Previous And Set Offset In Circle", + "Loops specified instructions and changes the offset after each loop in order to execute around a circle", + false + ); + } + + @Override + public void resetInternalState() { + this.repetitionsRemaining = -1; + this.offsetsInCircle.clear(); + this.offsetWhenFirstReachedOperation = Vector3i.ZERO; + this.previousCircleOffset = Vector3i.ZERO; + int numPointsOnCircle = this.numberOfCirclePointsArg; + int circleRadius = this.circleRadiusArg; + double theta = (Math.PI * 2) / numPointsOnCircle; + + for (int i = 0; i < numPointsOnCircle; i++) { + if (this.rotateArg) { + this.offsetsInCircle + .add( + new Vector3i( + this.doubleToNearestInt(circleRadius * Math.cos(theta * i)) * -1, 0, this.doubleToNearestInt(circleRadius * Math.sin(theta * i)) * -1 + ) + ); + } else { + this.offsetsInCircle + .add(new Vector3i(this.doubleToNearestInt(circleRadius * Math.cos(theta * i)), 0, this.doubleToNearestInt(circleRadius * Math.sin(theta * i)))); + } + } + + if (this.flipArg) { + this.offsetsInCircle = this.offsetsInCircle.reversed(); + } + } + + private int doubleToNearestInt(double number) { + return (int)Math.floor(number + 0.5); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.repetitionsRemaining == -1) { + this.resetInternalState(); + this.offsetWhenFirstReachedOperation = brushConfig.getOriginOffset(); + if (this.numberOfCirclePointsArg > 100) { + brushConfig.setErrorFlag("Cannot have more than 100 repetitions"); + return; + } + + this.repetitionsRemaining = this.numberOfCirclePointsArg; + } + + if (this.repetitionsRemaining == 0) { + this.repetitionsRemaining = -1; + brushConfig.setOriginOffset(this.offsetWhenFirstReachedOperation); + } else { + Vector3i offsetVector = brushConfig.getOriginOffset() + .subtract(this.previousCircleOffset) + .add(this.offsetsInCircle.get(this.repetitionsRemaining - 1).clone()); + this.previousCircleOffset = this.offsetsInCircle.get(this.repetitionsRemaining - 1).clone(); + brushConfig.setOriginOffset(offsetVector); + brushConfigCommandExecutor.loadOperatingIndex(this.indexNameArg, false); + this.repetitionsRemaining--; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/loops/CircleOffsetFromArgOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/loops/CircleOffsetFromArgOperation.java new file mode 100644 index 0000000..ae84bed --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/loops/CircleOffsetFromArgOperation.java @@ -0,0 +1,186 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.loops; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BuilderTool; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +public class CircleOffsetFromArgOperation extends SequenceBrushOperation { + public static final int MAX_REPETITIONS = 100; + public static final int IDLE_STATE = -1; + public static final double TWO_PI = Math.PI * 2; + public static final BuilderCodec CODEC = BuilderCodec.builder( + CircleOffsetFromArgOperation.class, CircleOffsetFromArgOperation::new + ) + .append(new KeyedCodec<>("StoredIndexName", Codec.STRING), (op, val) -> op.indexNameArg = val, op -> op.indexNameArg) + .documentation("The name of the previously stored index to begin the loop at. Note: This can only be an index previous to the current.") + .add() + .append(new KeyedCodec<>("NumberCirclePointsArg", Codec.STRING, true), (op, val) -> op.numCirclePointsArg = val, op -> op.numCirclePointsArg) + .documentation("The name of the Int tool arg to load the value from") + .add() + .append(new KeyedCodec<>("CircleRadiusArg", Codec.STRING, true), (op, val) -> op.circleRadiusArg = val, op -> op.circleRadiusArg) + .documentation("The name of the Int tool arg to load the value from") + .add() + .append(new KeyedCodec<>("FlipDirection", Codec.BOOLEAN, true), (op, val) -> op.flipArg = val, op -> op.flipArg) + .documentation("Whether to invert the direction of the circle. Useful for non-zero offset modifiers.") + .add() + .append(new KeyedCodec<>("RotateDirection", Codec.BOOLEAN, true), (op, val) -> op.rotateArg = val, op -> op.rotateArg) + .documentation("Whether to invert the direction of the circle. Useful for non-zero offset modifiers.") + .add() + .build(); + @Nonnull + public String indexNameArg = "Undefined"; + public String numCirclePointsArg = ""; + private int numCirclePointsArgVal = 3; + public String circleRadiusArg = ""; + private int circleRadiusArgVal = 5; + private int previousCirclePointsVal = 3; + private int previousCircleRadiusVal = 5; + public boolean flipArg = false; + public boolean rotateArg = false; + private int repetitionsRemaining = -1; + @Nonnull + private List offsetsInCircle = new ObjectArrayList<>(); + @Nonnull + private Vector3i offsetWhenFirstReachedOperation = Vector3i.ZERO; + @Nonnull + private Vector3i previousCircleOffset = Vector3i.ZERO; + + public CircleOffsetFromArgOperation() { + super( + "Loop Previous And Set Offset In Circle", + "Loops specified instructions and changes the offset after each loop in order to execute around a circle", + false + ); + } + + @Override + public void resetInternalState() { + this.repetitionsRemaining = -1; + this.offsetsInCircle.clear(); + this.offsetWhenFirstReachedOperation = Vector3i.ZERO; + this.previousCircleOffset = Vector3i.ZERO; + int numPointsOnCircle = this.numCirclePointsArgVal; + int circleRadius = this.circleRadiusArgVal; + double theta = (Math.PI * 2) / numPointsOnCircle; + + for (int i = 0; i < numPointsOnCircle; i++) { + if (this.rotateArg) { + this.offsetsInCircle + .add( + new Vector3i( + this.doubleToNearestInt(circleRadius * Math.cos(theta * i)) * -1, 0, this.doubleToNearestInt(circleRadius * Math.sin(theta * i)) * -1 + ) + ); + } else { + this.offsetsInCircle + .add(new Vector3i(this.doubleToNearestInt(circleRadius * Math.cos(theta * i)), 0, this.doubleToNearestInt(circleRadius * Math.sin(theta * i)))); + } + } + + if (this.flipArg) { + this.offsetsInCircle = this.offsetsInCircle.reversed(); + } + } + + private int doubleToNearestInt(double number) { + return (int)Math.floor(number + 0.5); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.repetitionsRemaining == -1) { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + BuilderTool builderTool = BuilderTool.getActiveBuilderTool(playerComponent); + if (builderTool == null) { + brushConfig.setErrorFlag("CircleOffsetFromArg: No active builder tool"); + return; + } + + ItemStack itemStack = playerComponent.getInventory().getItemInHand(); + if (itemStack == null) { + brushConfig.setErrorFlag("CircleOffsetFromArg: No item in hand"); + return; + } + + BuilderTool.ArgData argData = builderTool.getItemArgData(itemStack); + Map toolArgs = argData.tool(); + if (toolArgs == null || !toolArgs.containsKey(this.numCirclePointsArg)) { + brushConfig.setErrorFlag("CircleOffsetFromArg: Tool arg '" + this.numCirclePointsArg + "' not found"); + return; + } + + if (toolArgs == null || !toolArgs.containsKey(this.numCirclePointsArg)) { + brushConfig.setErrorFlag("CircleOffsetFromArg: Tool arg '" + this.numCirclePointsArg + "' not found"); + return; + } + + Object numCirclePointsArgValue = toolArgs.get(this.numCirclePointsArg); + Object circleRadiusArgValue = toolArgs.get(this.circleRadiusArg); + if (!(numCirclePointsArgValue instanceof Integer)) { + brushConfig.setErrorFlag( + "LoadCircleLoop: Tool arg '" + + this.numCirclePointsArg + + "' is not an Int type (found " + + numCirclePointsArgValue.getClass().getSimpleName() + + ")" + ); + return; + } + + if (!(circleRadiusArgValue instanceof Integer)) { + brushConfig.setErrorFlag( + "LoadCircleLoop: Tool arg '" + this.circleRadiusArg + "' is not an Int type (found " + circleRadiusArgValue.getClass().getSimpleName() + ")" + ); + return; + } + + this.numCirclePointsArgVal = (Integer)numCirclePointsArgValue; + this.circleRadiusArgVal = (Integer)circleRadiusArgValue; + this.previousCirclePointsVal = this.numCirclePointsArgVal; + this.previousCircleRadiusVal = this.circleRadiusArgVal; + this.resetInternalState(); + this.offsetWhenFirstReachedOperation = brushConfig.getOriginOffset(); + if (this.numCirclePointsArgVal > 100) { + brushConfig.setErrorFlag("Cannot have more than 100 repetitions"); + return; + } + + this.repetitionsRemaining = this.numCirclePointsArgVal; + } + + if (this.repetitionsRemaining == 0) { + this.repetitionsRemaining = -1; + brushConfig.setOriginOffset(this.offsetWhenFirstReachedOperation); + } else { + Vector3i offsetVector = brushConfig.getOriginOffset() + .subtract(this.previousCircleOffset) + .add(this.offsetsInCircle.get(this.repetitionsRemaining - 1).clone()); + this.previousCircleOffset = this.offsetsInCircle.get(this.repetitionsRemaining - 1).clone(); + brushConfig.setOriginOffset(offsetVector); + brushConfigCommandExecutor.loadOperatingIndex(this.indexNameArg, false); + this.repetitionsRemaining--; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/loops/LoadLoopFromToolArgOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/loops/LoadLoopFromToolArgOperation.java new file mode 100644 index 0000000..7a858d5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/loops/LoadLoopFromToolArgOperation.java @@ -0,0 +1,99 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.loops; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BuilderTool; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import javax.annotation.Nonnull; + +public class LoadLoopFromToolArgOperation extends SequenceBrushOperation { + public static final int MAX_REPETITIONS = 100; + public static final int IDLE_STATE = -1; + public static final BuilderCodec CODEC = BuilderCodec.builder( + LoadLoopFromToolArgOperation.class, LoadLoopFromToolArgOperation::new + ) + .append(new KeyedCodec<>("StoredIndexName", Codec.STRING), (op, val) -> op.indexNameArg = val, op -> op.indexNameArg) + .documentation("The name of the previously stored index to begin the loop at. Note: This can only be an index previous to the current.") + .add() + .append(new KeyedCodec<>("ArgName", Codec.STRING), (op, val) -> op.argNameArg = val, op -> op.argNameArg) + .documentation("The amount of additional times to repeat the loop after the initial, normal execution") + .add() + .documentation("Loop the execution of instructions a set amount of times") + .build(); + @Nonnull + public String indexNameArg = "Undefined"; + @Nonnull + public String argNameArg = ""; + private int repetitionsRemaining = -1; + + public LoadLoopFromToolArgOperation() { + super("Loop Operations", "Loop the execution of instructions a variable amount of times", false); + } + + @Override + public void resetInternalState() { + this.repetitionsRemaining = -1; + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.repetitionsRemaining == -1) { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + BuilderTool builderTool = BuilderTool.getActiveBuilderTool(playerComponent); + if (builderTool == null) { + brushConfig.setErrorFlag("LoadLoop: No active builder tool"); + return; + } + + ItemStack itemStack = playerComponent.getInventory().getItemInHand(); + if (itemStack == null) { + brushConfig.setErrorFlag("LoadLoop: No item in hand"); + return; + } + + BuilderTool.ArgData argData = builderTool.getItemArgData(itemStack); + Map toolArgs = argData.tool(); + if (toolArgs == null || !toolArgs.containsKey(this.argNameArg)) { + brushConfig.setErrorFlag("LoadLoop: Tool arg '" + this.argNameArg + "' not found"); + return; + } + + Object argValue = toolArgs.get(this.argNameArg); + if (!(argValue instanceof Integer intValue)) { + brushConfig.setErrorFlag("LoadLoop: Tool arg '" + this.argNameArg + "' is not an Int type (found " + argValue.getClass().getSimpleName() + ")"); + return; + } + + if (intValue > 100 || intValue < 0) { + brushConfig.setErrorFlag("Cannot have more than 100 repetitions, or negative repetitions"); + return; + } + + this.repetitionsRemaining = intValue; + } + + if (this.repetitionsRemaining == 0) { + this.repetitionsRemaining = -1; + } else { + this.repetitionsRemaining--; + brushConfigCommandExecutor.loadOperatingIndex(this.indexNameArg, false); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/loops/LoopOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/loops/LoopOperation.java new file mode 100644 index 0000000..f3b0608 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/loops/LoopOperation.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.loops; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class LoopOperation extends SequenceBrushOperation { + public static final int MAX_REPETITIONS = 100; + public static final int IDLE_STATE = -1; + public static final BuilderCodec CODEC = BuilderCodec.builder(LoopOperation.class, LoopOperation::new) + .append(new KeyedCodec<>("StoredIndexName", Codec.STRING), (op, val) -> op.indexNameArg = val, op -> op.indexNameArg) + .documentation("The name of the previously stored index to begin the loop at. Note: This can only be an index previous to the current.") + .add() + .append(new KeyedCodec<>("AdditionalRepetitions", Codec.INTEGER), (op, val) -> op.repetitionsArg = val, op -> op.repetitionsArg) + .documentation("The amount of additional times to repeat the loop after the initial, normal execution") + .add() + .documentation("Loop the execution of instructions a set amount of times") + .build(); + @Nonnull + public String indexNameArg = "Undefined"; + @Nonnull + public Integer repetitionsArg = 0; + private int repetitionsRemaining = -1; + + public LoopOperation() { + super("Loop Operations", "Loop the execution of instructions a set amount of times", false); + } + + @Override + public void resetInternalState() { + this.repetitionsRemaining = -1; + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.repetitionsRemaining == -1) { + if (this.repetitionsArg > 100 || this.repetitionsArg < 0) { + brushConfig.setErrorFlag("Cannot have more than 100 repetitions, or negative repetitions"); + return; + } + + this.repetitionsRemaining = this.repetitionsArg; + } + + if (this.repetitionsRemaining == 0) { + this.repetitionsRemaining = -1; + } else { + this.repetitionsRemaining--; + brushConfigCommandExecutor.loadOperatingIndex(this.indexNameArg, false); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/loops/LoopRandomOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/loops/LoopRandomOperation.java new file mode 100644 index 0000000..7a9f5e1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/flowcontrol/loops/LoopRandomOperation.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.loops; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.codec.PairCodec; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.Pair; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; + +public class LoopRandomOperation extends SequenceBrushOperation { + public static final int MAX_REPETITIONS = 100; + public static final int IDLE_STATE = -1; + public static final BuilderCodec CODEC = BuilderCodec.builder(LoopRandomOperation.class, LoopRandomOperation::new) + .append(new KeyedCodec<>("StoredIndexName", Codec.STRING), (op, val) -> op.indexNameArg = val, op -> op.indexNameArg) + .documentation("The name of the previously stored index to begin the loop at. Note: This can only be an index previous to the current.") + .add() + .append( + new KeyedCodec<>("RangeOfAdditionalRepetitions", PairCodec.IntegerPair.CODEC), + (op, val) -> op.repetitionsArg = val.toPair(), + op -> PairCodec.IntegerPair.fromPair(op.repetitionsArg) + ) + .documentation( + "The minimum and maximum of a range, randomly choosing the amount of additional times to repeat the loop after the initial, normal execution" + ) + .add() + .documentation("Loop the execution of instructions a random amount of times") + .build(); + @Nonnull + public String indexNameArg = "Undefined"; + @Nonnull + public Pair repetitionsArg = Pair.of(1, 1); + private int repetitionsRemaining = -1; + + public LoopRandomOperation() { + super("Loop Operations Random Amount", "Loop the execution of instructions a random amount of times", false); + } + + @Override + public void resetInternalState() { + this.repetitionsRemaining = -1; + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.repetitionsRemaining == -1) { + int repetitions = this.randomlyChooseRepetitionsAmount(); + if (repetitions > 100) { + brushConfig.setErrorFlag("Cannot have more than 100 repetitions"); + return; + } + + this.repetitionsRemaining = repetitions; + } + + if (this.repetitionsRemaining == 0) { + this.repetitionsRemaining = -1; + } else { + this.repetitionsRemaining--; + brushConfigCommandExecutor.loadOperatingIndex(this.indexNameArg, false); + } + } + + private int randomlyChooseRepetitionsAmount() { + return this.repetitionsArg.left().equals(this.repetitionsArg.right()) + ? this.repetitionsArg.left() + : ThreadLocalRandom.current().nextInt(this.repetitionsArg.left(), this.repetitionsArg.right() + 1); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/AppendMaskFromToolArgOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/AppendMaskFromToolArgOperation.java new file mode 100644 index 0000000..cc0fb6e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/AppendMaskFromToolArgOperation.java @@ -0,0 +1,120 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BuilderTool; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockFilter; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockMask; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.ArrayList; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AppendMaskFromToolArgOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder( + AppendMaskFromToolArgOperation.class, AppendMaskFromToolArgOperation::new + ) + .append(new KeyedCodec<>("ArgName", Codec.STRING, true), (op, val) -> op.argNameArg = val, op -> op.argNameArg) + .documentation("The name of the Block tool arg to read the material from") + .add() + .append( + new KeyedCodec<>("FilterType", new EnumCodec<>(BlockFilter.FilterType.class)), (op, val) -> op.filterTypeArg = val, op -> op.filterTypeArg + ) + .documentation("The type of block filter mask to apply (e.g., TARGET_BLOCK, ABOVE_BLOCK, BELOW_BLOCK)") + .add() + .append(new KeyedCodec<>("Invert", Codec.BOOLEAN, true), (op, val) -> op.invertArg = val, op -> op.invertArg) + .documentation("Whether to invert the block filter mask or not") + .add() + .append(new KeyedCodec<>("AdditionalBlocks", Codec.STRING), (op, val) -> op.additionalBlocksArg = val, op -> op.additionalBlocksArg) + .documentation("Additional block names to append to the mask, comma separated (e.g., Rock_Stone,Rock_Granite)") + .add() + .documentation("Append a mask from a Block tool arg with configurable filter type and optional additional blocks") + .build(); + @Nonnull + public String argNameArg = ""; + @Nonnull + public boolean invertArg = false; + @Nonnull + public BlockFilter.FilterType filterTypeArg = BlockFilter.FilterType.TargetBlock; + @Nullable + public String additionalBlocksArg; + + public AppendMaskFromToolArgOperation() { + super("Append Mask From Tool Arg", "Append a mask from a Block tool arg with configurable filter type", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + BuilderTool builderTool = BuilderTool.getActiveBuilderTool(playerComponent); + if (builderTool == null) { + brushConfig.setErrorFlag("AppendMaskFromToolArg: No active builder tool"); + } else { + ItemStack itemStack = playerComponent.getInventory().getItemInHand(); + if (itemStack == null) { + brushConfig.setErrorFlag("AppendMaskFromToolArg: No item in hand"); + } else { + BuilderTool.ArgData argData = builderTool.getItemArgData(itemStack); + Map toolArgs = argData.tool(); + if (toolArgs != null && toolArgs.containsKey(this.argNameArg)) { + Object argValue = toolArgs.get(this.argNameArg); + if (!(argValue instanceof BlockPattern blockPattern)) { + brushConfig.setErrorFlag( + "AppendMaskFromToolArg: Tool arg '" + this.argNameArg + "' is not a Block type (found " + argValue.getClass().getSimpleName() + ")" + ); + } else { + ArrayList blockNames = new ArrayList(); + String patternStr = blockPattern.toString(); + if (!patternStr.isEmpty() && !patternStr.equals("-")) { + for (String entry : patternStr.split(",")) { + int percentIdx = entry.indexOf(37); + String blockName = percentIdx >= 0 ? entry.substring(percentIdx + 1) : entry; + if (!blockName.isEmpty()) { + blockNames.add(blockName); + } + } + } + + if (this.additionalBlocksArg != null && !this.additionalBlocksArg.isEmpty()) { + for (String block : this.additionalBlocksArg.split(",")) { + String trimmed = block.trim(); + if (!trimmed.isEmpty()) { + blockNames.add(trimmed); + } + } + } + + if (blockNames.isEmpty()) { + brushConfig.setErrorFlag("AppendMaskFromToolArg: No blocks to add to mask"); + } else { + BlockFilter filter = new BlockFilter(this.filterTypeArg, blockNames.toArray(new String[0]), this.invertArg); + BlockMask mask = new BlockMask(new BlockFilter[]{filter}); + brushConfig.appendOperationMask(mask); + } + } + } else { + brushConfig.setErrorFlag("AppendMaskFromToolArg: Tool arg '" + this.argNameArg + "' not found"); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/AppendMaskOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/AppendMaskOperation.java new file mode 100644 index 0000000..4d968ef --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/AppendMaskOperation.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockMask; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class AppendMaskOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(AppendMaskOperation.class, AppendMaskOperation::new) + .append(new KeyedCodec<>("AppendMask", BlockMask.CODEC), (op, val) -> op.operationMaskArg = val, op -> op.operationMaskArg) + .documentation("Combines the new mask with the current operation mask") + .add() + .documentation("Append new masks to the current operation mask") + .build(); + @Nonnull + public BlockMask operationMaskArg = BlockMask.EMPTY; + + public AppendMaskOperation() { + super("Append Mask", "Append new masks to the current operation mask", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + brushConfig.appendOperationMask(this.operationMaskArg); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/HistoryMaskOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/HistoryMaskOperation.java new file mode 100644 index 0000000..5170c91 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/HistoryMaskOperation.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class HistoryMaskOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(HistoryMaskOperation.class, HistoryMaskOperation::new) + .append(new KeyedCodec<>("HistoryMask", new EnumCodec<>(BrushConfig.HistoryMask.class)), (op, val) -> op.historyMaskArg = val, op -> op.historyMaskArg) + .documentation("Changes the mask to block history, enable only history, or ignore history") + .add() + .documentation("Sets the history mask, allowing you to mask to previously edited or non-edited blocks") + .build(); + @Nonnull + public BrushConfig.HistoryMask historyMaskArg = BrushConfig.HistoryMask.None; + + public HistoryMaskOperation() { + super("History Mask", "Sets the history mask, allowing you to mask to previously edited or non-edited blocks", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + brushConfig.setHistoryMask(this.historyMaskArg); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/MaskOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/MaskOperation.java new file mode 100644 index 0000000..0f8065e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/MaskOperation.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockMask; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class MaskOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(MaskOperation.class, MaskOperation::new) + .append(new KeyedCodec<>("Mask", BlockMask.CODEC), (op, val) -> op.operationMaskArg = val, op -> op.operationMaskArg) + .documentation("Sets the operation mask") + .add() + .documentation("Sets the operation mask to only modify blocks that match the mask") + .build(); + @Nonnull + public BlockMask operationMaskArg = BlockMask.EMPTY; + + public MaskOperation() { + super("Set Operation Mask", "Sets the operation mask to only modify blocks that match the mask", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + brushConfig.setOperationMask(this.operationMaskArg); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/UseBrushMaskOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/UseBrushMaskOperation.java new file mode 100644 index 0000000..cdf0f37 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/UseBrushMaskOperation.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class UseBrushMaskOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(UseBrushMaskOperation.class, UseBrushMaskOperation::new) + .append(new KeyedCodec<>("UseBrushMask", Codec.BOOLEAN), (op, val) -> op.useBrushMask = val, op -> op.useBrushMask) + .documentation("Enables or disables the brush's mask") + .add() + .documentation("Enable the brush tool's mask (the mask placed on the tool)") + .build(); + @Nonnull + public Boolean useBrushMask = true; + + public UseBrushMaskOperation() { + super("Use Brush Mask", "Enable the brush tool's mask (the mask placed on the tool)", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + brushConfig.setUseBrushMask(this.useBrushMask); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/UseOperationMaskOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/UseOperationMaskOperation.java new file mode 100644 index 0000000..3a99aeb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/masks/UseOperationMaskOperation.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class UseOperationMaskOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(UseOperationMaskOperation.class, UseOperationMaskOperation::new) + .append(new KeyedCodec<>("UseOperationMask", Codec.BOOLEAN), (op, val) -> op.useOperationMask = val, op -> op.useOperationMask) + .documentation("Enables or disables the operation mask") + .add() + .documentation("Enable or disable the use of the operation mask") + .build(); + @Nonnull + public Boolean useOperationMask = true; + + public UseOperationMaskOperation() { + super("Use Operation Mask", "Enable or disable the use of the operation mask", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + brushConfig.setUseOperationMask(this.useOperationMask); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/offsets/OffsetOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/offsets/OffsetOperation.java new file mode 100644 index 0000000..5f889f2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/offsets/OffsetOperation.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.offsets; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.LoadIntFromToolArgOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeVector3i; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class OffsetOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(OffsetOperation.class, OffsetOperation::new) + .append(new KeyedCodec<>("Offset", RelativeVector3i.CODEC), (op, val) -> op.offsetArg = val, op -> op.offsetArg) + .documentation("Sets the offset in 3 dimensions, each value is optionally relative by prefixing it with a tilde") + .add() + .documentation("Offset the brush location by a specified amount from the clicked origin") + .append( + new KeyedCodec<>("TargetField", new EnumCodec<>(LoadIntFromToolArgOperation.TargetField.class)), + (op, val) -> op.targetFieldArg = val, + op -> op.targetFieldArg + ) + .documentation("The brush config field to set (Width, Height, Density, Thickness, OffsetX, OffsetY, OffsetZ)") + .add() + .append(new KeyedCodec<>("Negate", Codec.BOOLEAN, true), (op, val) -> op.negateArg = val, op -> op.negateArg) + .documentation("Whether to invert the sign of the relative field") + .add() + .build(); + @Nonnull + public RelativeVector3i offsetArg = RelativeVector3i.ZERO; + @Nonnull + public LoadIntFromToolArgOperation.TargetField targetFieldArg = LoadIntFromToolArgOperation.TargetField.None; + public boolean negateArg = false; + + public OffsetOperation() { + super("Modify Offset", "Offset the brush location by a specified amount from the clicked origin", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + Vector3i offsetVector = this.offsetArg.resolve(brushConfig.getOriginOffset()); + if (this.targetFieldArg != LoadIntFromToolArgOperation.TargetField.None) { + int relativeFieldValue = this.targetFieldArg.getValue(brushConfig); + if (this.negateArg) { + relativeFieldValue *= -1; + } + + if (this.offsetArg.isRelativeX()) { + offsetVector.setX(offsetVector.getX() + relativeFieldValue); + } + + if (this.offsetArg.isRelativeY()) { + offsetVector.setY(offsetVector.getY() + relativeFieldValue); + } + + if (this.offsetArg.isRelativeZ()) { + offsetVector.setZ(offsetVector.getZ() + relativeFieldValue); + } + } + + brushConfig.setOriginOffset(offsetVector); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/offsets/RandomOffsetOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/offsets/RandomOffsetOperation.java new file mode 100644 index 0000000..0005e24 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/offsets/RandomOffsetOperation.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.offsets; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeIntegerRange; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class RandomOffsetOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(RandomOffsetOperation.class, RandomOffsetOperation::new) + .append(new KeyedCodec<>("XOffsetRange", RelativeIntegerRange.CODEC), (op, val) -> op.xOffsetArg = val, op -> op.xOffsetArg) + .documentation("The range of allowed values for the X offset") + .add() + .append(new KeyedCodec<>("YOffsetRange", RelativeIntegerRange.CODEC), (op, val) -> op.yOffsetArg = val, op -> op.yOffsetArg) + .documentation("The range of allowed values for the Z offset") + .add() + .append(new KeyedCodec<>("ZOffsetRange", RelativeIntegerRange.CODEC), (op, val) -> op.zOffsetArg = val, op -> op.zOffsetArg) + .documentation("The range of allowed values for the Y offset") + .add() + .documentation("Randomly offset the brush location from the clicked origin") + .build(); + @Nonnull + public RelativeIntegerRange xOffsetArg = new RelativeIntegerRange(1, 1); + @Nonnull + public RelativeIntegerRange yOffsetArg = new RelativeIntegerRange(1, 1); + @Nonnull + public RelativeIntegerRange zOffsetArg = new RelativeIntegerRange(1, 1); + + public RandomOffsetOperation() { + super("Randomize Offset", "Randomly offset the brush location from the clicked origin", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + Vector3i offset = new Vector3i( + this.xOffsetArg.getNumberInRange(brushConfig.getOriginOffset().x), + this.yOffsetArg.getNumberInRange(brushConfig.getOriginOffset().y), + this.zOffsetArg.getNumberInRange(brushConfig.getOriginOffset().z) + ); + brushConfig.setOriginOffset(offset); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/saveandload/LoadBrushConfigOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/saveandload/LoadBrushConfigOperation.java new file mode 100644 index 0000000..c8dba54 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/saveandload/LoadBrushConfigOperation.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.saveandload; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nonnull; + +public class LoadBrushConfigOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(LoadBrushConfigOperation.class, LoadBrushConfigOperation::new) + .append(new KeyedCodec<>("StoredName", Codec.STRING), (op, val) -> op.variableNameArg = val, op -> op.variableNameArg) + .documentation("The name to store the snapshot of this brush config under") + .add() + .append( + new KeyedCodec<>("ParametersToLoad", new ArrayCodec<>(new EnumCodec<>(BrushConfig.DataSettingFlags.class), BrushConfig.DataSettingFlags[]::new)), + (op, val) -> op.dataSettingFlagArg = val != null ? Arrays.asList(val) : List.of(), + op -> op.dataSettingFlagArg.toArray(new BrushConfig.DataSettingFlags[0]) + ) + .documentation("A list of the different parameters to load from the stored config") + .add() + .documentation("Restore a saved brush config snapshot") + .build(); + @Nonnull + public String variableNameArg = "Undefined"; + @Nonnull + public List dataSettingFlagArg = List.of(); + + public LoadBrushConfigOperation() { + super("Load Brush Config Snapshot", "Restore a saved brush config snapshot", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + brushConfigCommandExecutor.loadBrushConfigSnapshot(this.variableNameArg, this.dataSettingFlagArg.toArray(BrushConfig.DataSettingFlags[]::new)); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/saveandload/LoadOperationsFromAssetOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/saveandload/LoadOperationsFromAssetOperation.java new file mode 100644 index 0000000..5fa167d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/saveandload/LoadOperationsFromAssetOperation.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.saveandload; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class LoadOperationsFromAssetOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder( + LoadOperationsFromAssetOperation.class, LoadOperationsFromAssetOperation::new + ) + .append(new KeyedCodec<>("AssetId", Codec.STRING), (op, val) -> op.assetId = val, op -> op.assetId) + .documentation("The ID of the ScriptedBrushAsset to load operations from") + .add() + .documentation("Load and inline operations from another ScriptedBrushAsset") + .build(); + @Nonnull + private String assetId = ""; + + public LoadOperationsFromAssetOperation() { + super("Load Operations From Asset", "Load and inline operations from another ScriptedBrushAsset", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + } + + @Nonnull + public String getAssetId() { + return this.assetId; + } + + public void setAssetId(@Nonnull String assetId) { + this.assetId = assetId; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/saveandload/PersistentDataOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/saveandload/PersistentDataOperation.java new file mode 100644 index 0000000..6094130 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/saveandload/PersistentDataOperation.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.saveandload; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PersistentDataOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(PersistentDataOperation.class, PersistentDataOperation::new) + .append(new KeyedCodec<>("StoredName", Codec.STRING), (op, val) -> op.variableNameArg = val, op -> op.variableNameArg) + .documentation("The name of the variable to modify") + .add() + .append( + new KeyedCodec<>("Operation", new EnumCodec<>(ArgTypes.IntegerOperation.class)), (op, val) -> op.operationArg = val, op -> op.operationArg + ) + .documentation("The operation to perform on the variable using the modifier") + .add() + .append(new KeyedCodec<>("Modifier", Codec.INTEGER), (op, val) -> op.modifierArg = val, op -> op.modifierArg) + .documentation("The value to modify the variable by") + .add() + .documentation("Store and operate on data that sticks around between executions") + .build(); + @Nonnull + public String variableNameArg = "Undefined"; + @Nonnull + public ArgTypes.IntegerOperation operationArg = ArgTypes.IntegerOperation.SET; + @Nonnull + public Integer modifierArg = 0; + + public PersistentDataOperation() { + super("Persistent Data", "Store and operate on data that sticks around between executions", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + int persistentVariable = brushConfigCommandExecutor.getPersistentVariableOrDefault(this.variableNameArg, 0); + System.out.println(this.variableNameArg + ": " + persistentVariable); + int newValue = this.operationArg.operate(persistentVariable, this.modifierArg); + brushConfigCommandExecutor.setPersistentVariable(this.variableNameArg, newValue); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/saveandload/SaveBrushConfigOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/saveandload/SaveBrushConfigOperation.java new file mode 100644 index 0000000..c98a2f0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/saveandload/SaveBrushConfigOperation.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.saveandload; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SaveBrushConfigOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(SaveBrushConfigOperation.class, SaveBrushConfigOperation::new) + .append(new KeyedCodec<>("StoredName", Codec.STRING), (op, val) -> op.variableNameArg = val, op -> op.variableNameArg) + .documentation("The name to store the snapshot of this brush config under") + .add() + .documentation("Save a snapshot of the current brush config in order to restore it later") + .build(); + @Nonnull + public String variableNameArg = "Undefined"; + + public SaveBrushConfigOperation() { + super("Save Brush Config Snapshot", "Save a snapshot of the current brush config in order to restore it later", false); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + brushConfigCommandExecutor.storeBrushConfigSnapshot(this.variableNameArg); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/saveandload/SaveIndexOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/saveandload/SaveIndexOperation.java new file mode 100644 index 0000000..afe0cdf --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/sequential/saveandload/SaveIndexOperation.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.saveandload; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system.SequenceBrushOperation; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SaveIndexOperation extends SequenceBrushOperation { + public static final BuilderCodec CODEC = BuilderCodec.builder(SaveIndexOperation.class, SaveIndexOperation::new) + .append(new KeyedCodec<>("StoredIndexName", Codec.STRING), (op, val) -> op.variableNameArg = val, op -> op.variableNameArg) + .documentation("The name to store the current execution index at") + .add() + .documentation("Mark this spot in the stack in order to loop or jump to it") + .build(); + @Nonnull + public String variableNameArg = "Undefined"; + + public SaveIndexOperation() { + super("Store Current Operation Index", "Mark this spot in the stack in order to loop or jump to it", false); + } + + @Override + public void preExecutionModifyBrushConfig(@Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, int operationIndex) { + brushConfigCommandExecutor.storeOperatingIndex(this.variableNameArg, operationIndex); + } + + @Override + public void modifyBrushConfig( + @Nonnull Ref ref, + @Nonnull BrushConfig brushConfig, + @Nonnull BrushConfigCommandExecutor brushConfigCommandExecutor, + @Nonnull ComponentAccessor componentAccessor + ) { + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/system/BrushOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/system/BrushOperation.java new file mode 100644 index 0000000..7fe760b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/system/BrushOperation.java @@ -0,0 +1,174 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.global.DebugBrushOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.global.DisableHoldInteractionOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.global.IgnoreExistingBrushDataOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.BlockPatternOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.BreakpointOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.ClearOperationMaskOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.DeleteOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.EchoOnceOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.EchoOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.ErodeOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.HeightmapLayerOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.LayerOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.LiftOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.LoadIntFromToolArgOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.LoadMaterialFromToolArgOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.MaterialOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.MeltOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.PastePrefabOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.ReplaceOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.RunCommandOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.SetDensity; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.SetOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.ShapeOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.SmoothOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.dimensions.DimensionsOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.dimensions.RandomizeDimensionsOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.ExitOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.JumpIfBlockTypeOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.JumpIfClickType; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.JumpIfCompareOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.JumpIfStringMatchOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.JumpIfToolArgOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.JumpToIndexOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.JumpToRandomIndex; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.loops.CircleOffsetAndLoopOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.loops.CircleOffsetFromArgOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.loops.LoadLoopFromToolArgOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.loops.LoopOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.flowcontrol.loops.LoopRandomOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks.AppendMaskFromToolArgOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks.AppendMaskOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks.HistoryMaskOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks.MaskOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks.UseBrushMaskOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.masks.UseOperationMaskOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.offsets.OffsetOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.offsets.RandomOffsetOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.saveandload.LoadBrushConfigOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.saveandload.LoadOperationsFromAssetOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.saveandload.PersistentDataOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.saveandload.SaveBrushConfigOperation; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.sequential.saveandload.SaveIndexOperation; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgumentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public abstract class BrushOperation { + public static final CodecMapCodec OPERATION_CODEC = new CodecMapCodec<>("Id"); + public static final Map> BRUSH_OPERATION_REGISTRY = new ConcurrentHashMap<>(); + private final String name; + private final String description; + private final Map> registeredOperationSettings = new LinkedHashMap<>(); + + public BrushOperation(String name, String description) { + this.name = name; + this.description = description; + } + + public abstract void modifyBrushConfig( + @Nonnull Ref var1, @Nonnull BrushConfig var2, @Nonnull BrushConfigCommandExecutor var3, @Nonnull ComponentAccessor var4 + ); + + public void resetInternalState() { + } + + public void preExecutionModifyBrushConfig(BrushConfigCommandExecutor brushConfigCommandExecutor, int operationIndex) { + } + + @Nonnull + public BrushOperationSetting createBrushSetting(@Nonnull String name, String description, T defaultValue, ArgumentType argumentType) { + BrushOperationSetting brushOperationSetting = new BrushOperationSetting<>(name, description, defaultValue, argumentType); + this.registeredOperationSettings.put(name.toLowerCase(), brushOperationSetting); + return brushOperationSetting; + } + + @Nonnull + public BrushOperationSetting createBrushSetting( + @Nonnull String name, String description, T defaultValue, ArgumentType argumentType, Function, String> toStringFunction + ) { + BrushOperationSetting brushOperationSetting = new BrushOperationSetting<>(name, description, defaultValue, argumentType, toStringFunction); + this.registeredOperationSettings.put(name.toLowerCase(), brushOperationSetting); + return brushOperationSetting; + } + + public String getName() { + return this.name; + } + + public String getDescription() { + return this.description; + } + + @Nonnull + public Map> getRegisteredOperationSettings() { + return this.registeredOperationSettings; + } + + static { + BRUSH_OPERATION_REGISTRY.put("dimensions", DimensionsOperation::new); + BRUSH_OPERATION_REGISTRY.put("randomdimensions", RandomizeDimensionsOperation::new); + BRUSH_OPERATION_REGISTRY.put("runcommand", RunCommandOperation::new); + BRUSH_OPERATION_REGISTRY.put("historymask", HistoryMaskOperation::new); + BRUSH_OPERATION_REGISTRY.put("mask", MaskOperation::new); + BRUSH_OPERATION_REGISTRY.put("clearoperationmask", ClearOperationMaskOperation::new); + BRUSH_OPERATION_REGISTRY.put("usebrushmask", UseBrushMaskOperation::new); + BRUSH_OPERATION_REGISTRY.put("useoperationmask", UseOperationMaskOperation::new); + BRUSH_OPERATION_REGISTRY.put("appendmask", AppendMaskOperation::new); + BRUSH_OPERATION_REGISTRY.put("appendmaskfromtoolarg", AppendMaskFromToolArgOperation::new); + BRUSH_OPERATION_REGISTRY.put("ignorebrushsettings", IgnoreExistingBrushDataOperation::new); + BRUSH_OPERATION_REGISTRY.put("debug", DebugBrushOperation::new); + BRUSH_OPERATION_REGISTRY.put("loop", LoopOperation::new); + BRUSH_OPERATION_REGISTRY.put("loadloop", LoadLoopFromToolArgOperation::new); + BRUSH_OPERATION_REGISTRY.put("looprandom", LoopRandomOperation::new); + BRUSH_OPERATION_REGISTRY.put("loopcircle", CircleOffsetAndLoopOperation::new); + BRUSH_OPERATION_REGISTRY.put("loopcirclefromarg", CircleOffsetFromArgOperation::new); + BRUSH_OPERATION_REGISTRY.put("savebrushconfig", SaveBrushConfigOperation::new); + BRUSH_OPERATION_REGISTRY.put("loadbrushconfig", LoadBrushConfigOperation::new); + BRUSH_OPERATION_REGISTRY.put("saveindex", SaveIndexOperation::new); + BRUSH_OPERATION_REGISTRY.put("loadoperationsfromasset", LoadOperationsFromAssetOperation::new); + BRUSH_OPERATION_REGISTRY.put("jump", JumpToIndexOperation::new); + BRUSH_OPERATION_REGISTRY.put("exit", ExitOperation::new); + BRUSH_OPERATION_REGISTRY.put("jumprandom", JumpToRandomIndex::new); + BRUSH_OPERATION_REGISTRY.put("jumpifequal", JumpIfStringMatchOperation::new); + BRUSH_OPERATION_REGISTRY.put("jumpifclicktype", JumpIfClickType::new); + BRUSH_OPERATION_REGISTRY.put("jumpifcompare", JumpIfCompareOperation::new); + BRUSH_OPERATION_REGISTRY.put("jumpifblocktype", JumpIfBlockTypeOperation::new); + BRUSH_OPERATION_REGISTRY.put("jumpiftoolarg", JumpIfToolArgOperation::new); + BRUSH_OPERATION_REGISTRY.put("pattern", BlockPatternOperation::new); + BRUSH_OPERATION_REGISTRY.put("loadmaterial", LoadMaterialFromToolArgOperation::new); + BRUSH_OPERATION_REGISTRY.put("loadint", LoadIntFromToolArgOperation::new); + BRUSH_OPERATION_REGISTRY.put("lift", LiftOperation::new); + BRUSH_OPERATION_REGISTRY.put("density", SetDensity::new); + BRUSH_OPERATION_REGISTRY.put("set", SetOperation::new); + BRUSH_OPERATION_REGISTRY.put("smooth", SmoothOperation::new); + BRUSH_OPERATION_REGISTRY.put("shape", ShapeOperation::new); + BRUSH_OPERATION_REGISTRY.put("offset", OffsetOperation::new); + BRUSH_OPERATION_REGISTRY.put("layer", LayerOperation::new); + BRUSH_OPERATION_REGISTRY.put("heightmaplayer", HeightmapLayerOperation::new); + BRUSH_OPERATION_REGISTRY.put("melt", MeltOperation::new); + BRUSH_OPERATION_REGISTRY.put("material", MaterialOperation::new); + BRUSH_OPERATION_REGISTRY.put("delete", DeleteOperation::new); + BRUSH_OPERATION_REGISTRY.put("disableonhold", DisableHoldInteractionOperation::new); + BRUSH_OPERATION_REGISTRY.put("randomoffset", RandomOffsetOperation::new); + BRUSH_OPERATION_REGISTRY.put("erode", ErodeOperation::new); + BRUSH_OPERATION_REGISTRY.put("persistentdata", PersistentDataOperation::new); + BRUSH_OPERATION_REGISTRY.put("pasteprefab", PastePrefabOperation::new); + BRUSH_OPERATION_REGISTRY.put("echo", EchoOperation::new); + BRUSH_OPERATION_REGISTRY.put("echoonce", EchoOnceOperation::new); + BRUSH_OPERATION_REGISTRY.put("replace", ReplaceOperation::new); + BRUSH_OPERATION_REGISTRY.put("breakpoint", BreakpointOperation::new); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/system/BrushOperationSetting.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/system/BrushOperationSetting.java new file mode 100644 index 0000000..38d715f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/system/BrushOperationSetting.java @@ -0,0 +1,108 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system; + +import com.hypixel.hytale.codec.validation.Validator; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgumentType; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BrushOperationSetting { + private final String name; + private final String description; + private String input; + private final T defaultValue; + @Nullable + private T value; + private final ArgumentType argumentType; + @Nullable + private final Validator valueValidator; + @Nullable + private final Function, String> toStringFunction; + + public BrushOperationSetting(String name, String description, T defaultValue, ArgumentType argumentType) { + this(name, description, defaultValue, argumentType, null, null); + } + + public BrushOperationSetting( + String name, String description, T defaultValue, ArgumentType argumentType, Function, String> toStringFunction + ) { + this(name, description, defaultValue, argumentType, null, toStringFunction); + } + + public BrushOperationSetting( + String name, + String description, + T defaultValue, + ArgumentType argumentType, + @Nullable Validator valueValidator, + @Nullable Function, String> toStringFunction + ) { + this.name = name; + this.description = description; + this.defaultValue = defaultValue; + this.value = defaultValue; + this.argumentType = argumentType; + this.valueValidator = valueValidator; + this.toStringFunction = toStringFunction; + } + + @Nonnull + public BrushOperationSetting setValue(T value) { + this.value = value; + return this; + } + + @Nonnull + public BrushOperationSetting setValueUnsafe(String input, Object value) { + this.input = input; + this.value = (T)value; + return this; + } + + @Nonnull + public ParseResult parseAndSetValue(String[] input) { + ParseResult parseResult = new ParseResult(); + T newValue = this.argumentType.parse(input, parseResult); + if (!parseResult.failed()) { + this.value = newValue; + } + + return parseResult; + } + + @Nullable + public String getInput() { + return this.input; + } + + public String getName() { + return this.name; + } + + public String getDescription() { + return this.description; + } + + public T getDefaultValue() { + return this.defaultValue; + } + + public ArgumentType getArgumentType() { + return this.argumentType; + } + + @Nullable + public Validator getValueValidator() { + return this.valueValidator; + } + + @Nullable + public T getValue() { + return this.value; + } + + public String getValueString() { + return this.toStringFunction != null ? this.toStringFunction.apply(this) : this.value.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/system/GlobalBrushOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/system/GlobalBrushOperation.java new file mode 100644 index 0000000..8b5698c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/system/GlobalBrushOperation.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system; + +public abstract class GlobalBrushOperation extends BrushOperation { + public GlobalBrushOperation(String name, String description) { + super(name, description); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/system/SequenceBrushOperation.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/system/SequenceBrushOperation.java new file mode 100644 index 0000000..24187e9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/operations/system/SequenceBrushOperation.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.operations.system; + +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigEditStore; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public abstract class SequenceBrushOperation extends BrushOperation { + private final boolean doesOperateOnBlocks; + + public SequenceBrushOperation(String name, String description, boolean doesOperateOnBlocks) { + super(name, description); + this.doesOperateOnBlocks = doesOperateOnBlocks; + } + + public boolean modifyBlocks( + Ref ref, + BrushConfig brushConfig, + BrushConfigCommandExecutor brushConfigCommandExecutor, + BrushConfigEditStore edit, + int x, + int y, + int z, + ComponentAccessor componentAccessor + ) { + return false; + } + + public void beginIterationIndex(int iterationIndex) { + } + + public int getNumModifyBlockIterations() { + return 1; + } + + public boolean doesOperateOnBlocks() { + return this.doesOperateOnBlocks; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/ui/ScriptedBrushPage.java b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/ui/ScriptedBrushPage.java new file mode 100644 index 0000000..88037b3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/scriptedbrushes/ui/ScriptedBrushPage.java @@ -0,0 +1,136 @@ +package com.hypixel.hytale.builtin.buildertools.scriptedbrushes.ui; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfigCommandExecutor; +import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.ScriptedBrushAsset; +import com.hypixel.hytale.builtin.buildertools.tooloperations.ToolOperation; +import com.hypixel.hytale.common.util.StringCompareUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.ui.browser.FileBrowserConfig; +import com.hypixel.hytale.server.core.ui.browser.FileBrowserEventData; +import com.hypixel.hytale.server.core.ui.browser.FileListProvider; +import com.hypixel.hytale.server.core.ui.browser.ServerFileBrowser; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.UUID; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class ScriptedBrushPage extends InteractiveCustomUIPage { + private static final Message MESSAGE_BRUSH_LOADED = Message.translation("server.commands.brushConfig.loaded"); + private static final Message MESSAGE_BRUSH_NOT_FOUND = Message.translation("server.commands.brushConfig.load.error.notFound"); + private static final Message MESSAGE_BRUSH_LOAD_ERROR = Message.translation("server.commands.brushConfig.load.error.loadFailed"); + @Nonnull + private final ServerFileBrowser browser; + + public ScriptedBrushPage(@Nonnull PlayerRef playerRef) { + super(playerRef, CustomPageLifetime.CanDismiss, FileBrowserEventData.CODEC); + FileBrowserConfig config = FileBrowserConfig.builder() + .listElementId("#FileList") + .searchInputId("#SearchInput") + .enableRootSelector(false) + .enableSearch(true) + .enableDirectoryNav(false) + .maxResults(50) + .customProvider(new ScriptedBrushPage.ScriptedBrushListProvider()) + .build(); + this.browser = new ServerFileBrowser(config); + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/ScriptedBrushListPage.ui"); + commandBuilder.set("#RootSelector.Visible", false); + this.browser.buildSearchInput(commandBuilder, eventBuilder); + this.browser.buildFileList(commandBuilder, eventBuilder); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull FileBrowserEventData data) { + if (this.browser.handleEvent(data)) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.browser.buildFileList(commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + } else { + String brushName = data.getFile(); + if (brushName != null) { + this.handleBrushSelection(ref, store, brushName); + } + } + } + + private void handleBrushSelection(@Nonnull Ref ref, @Nonnull Store store, @Nonnull String brushName) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + ScriptedBrushAsset scriptedBrushAsset = ScriptedBrushAsset.get(brushName); + if (scriptedBrushAsset == null) { + playerRefComponent.sendMessage(MESSAGE_BRUSH_NOT_FOUND.param("name", brushName)); + this.sendUpdate(); + } else { + UUID playerUUID = playerRefComponent.getUuid(); + PrototypePlayerBuilderToolSettings prototypeSettings = ToolOperation.getOrCreatePrototypeSettings(playerUUID); + BrushConfigCommandExecutor brushConfigCommandExecutor = prototypeSettings.getBrushConfigCommandExecutor(); + + try { + scriptedBrushAsset.loadIntoExecutor(brushConfigCommandExecutor); + prototypeSettings.setCurrentlyLoadedBrushConfigName(scriptedBrushAsset.getId()); + prototypeSettings.setUsePrototypeBrushConfigurations(true); + playerComponent.getPageManager().setPage(ref, store, Page.None); + playerRefComponent.sendMessage(MESSAGE_BRUSH_LOADED.param("name", scriptedBrushAsset.getId())); + } catch (Exception var11) { + playerRefComponent.sendMessage( + MESSAGE_BRUSH_LOAD_ERROR.param("name", brushName).param("error", var11.getMessage() != null ? var11.getMessage() : "Unknown error") + ); + this.sendUpdate(); + } + } + } + + private static class ScriptedBrushListProvider implements FileListProvider { + private ScriptedBrushListProvider() { + } + + @Nonnull + @Override + public List getFiles(@Nonnull Path currentDir, @Nonnull String searchQuery) { + DefaultAssetMap assetMap = ScriptedBrushAsset.getAssetMap(); + if (searchQuery.isEmpty()) { + return assetMap.getAssetMap().keySet().stream().sorted().map(namex -> new FileListProvider.FileEntry(namex, false)).collect(Collectors.toList()); + } else { + List results = new ObjectArrayList<>(); + + for (String name : assetMap.getAssetMap().keySet()) { + int score = StringCompareUtil.getFuzzyDistance(name, searchQuery, Locale.ENGLISH); + if (score > 0) { + results.add(new FileListProvider.FileEntry(name, name, false, score)); + } + } + + results.sort(Comparator.comparingInt(FileListProvider.FileEntry::matchScore).reversed()); + return results.size() > 50 ? results.subList(0, 50) : results; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/snapshot/BlockSelectionSnapshot.java b/src/com/hypixel/hytale/builtin/buildertools/snapshot/BlockSelectionSnapshot.java new file mode 100644 index 0000000..8af8673 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/snapshot/BlockSelectionSnapshot.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.buildertools.snapshot; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class BlockSelectionSnapshot implements SelectionSnapshot { + private final BlockSelection selection; + + public BlockSelectionSnapshot(BlockSelection snapshot) { + this.selection = snapshot; + } + + public BlockSelection getBlockSelection() { + return this.selection; + } + + public BlockSelectionSnapshot restore(Ref ref, Player player, @Nonnull World world, ComponentAccessor componentAccessor) { + BlockSelection before = this.selection.place(player, world); + BuilderToolsPlugin.invalidateWorldMapForSelection(before, world); + return new BlockSelectionSnapshot(before); + } + + @Nonnull + public static BlockSelectionSnapshot copyOf(@Nonnull BlockSelection selection) { + return new BlockSelectionSnapshot(selection.cloneSelection()); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/snapshot/ClipboardBoundsSnapshot.java b/src/com/hypixel/hytale/builtin/buildertools/snapshot/ClipboardBoundsSnapshot.java new file mode 100644 index 0000000..4117633 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/snapshot/ClipboardBoundsSnapshot.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.builtin.buildertools.snapshot; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ClipboardBoundsSnapshot implements ClipboardSnapshot { + public static final ClipboardBoundsSnapshot EMPTY = new ClipboardBoundsSnapshot(Vector3i.ZERO, Vector3i.ZERO); + private final Vector3i min; + private final Vector3i max; + + public ClipboardBoundsSnapshot(@Nonnull BlockSelection selection) { + this(selection.getSelectionMin(), selection.getSelectionMax()); + } + + public ClipboardBoundsSnapshot(Vector3i min, Vector3i max) { + this.min = min; + this.max = max; + } + + public Vector3i getMin() { + return this.min; + } + + public Vector3i getMax() { + return this.max; + } + + public ClipboardBoundsSnapshot restoreClipboard( + Ref ref, Player player, World world, @Nonnull BuilderToolsPlugin.BuilderState state, ComponentAccessor componentAccessor + ) { + ClipboardBoundsSnapshot snapshot = new ClipboardBoundsSnapshot(state.getSelection()); + state.getSelection().setSelectionArea(this.min, this.max); + state.sendArea(); + return snapshot; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/snapshot/ClipboardContentsSnapshot.java b/src/com/hypixel/hytale/builtin/buildertools/snapshot/ClipboardContentsSnapshot.java new file mode 100644 index 0000000..f1ff8b8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/snapshot/ClipboardContentsSnapshot.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.builtin.buildertools.snapshot; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ClipboardContentsSnapshot implements ClipboardSnapshot { + private final BlockSelection selection; + + public ClipboardContentsSnapshot(BlockSelection selection) { + this.selection = selection; + } + + public ClipboardContentsSnapshot restoreClipboard( + Ref ref, Player player, World world, @Nonnull BuilderToolsPlugin.BuilderState builderState, ComponentAccessor componentAccessor + ) { + ClipboardContentsSnapshot snapshot = new ClipboardContentsSnapshot(builderState.getSelection()); + builderState.setSelection(this.selection); + return snapshot; + } + + @Nonnull + public static ClipboardContentsSnapshot copyOf(@Nonnull BlockSelection selection) { + return new ClipboardContentsSnapshot(selection.cloneSelection()); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/snapshot/ClipboardSnapshot.java b/src/com/hypixel/hytale/builtin/buildertools/snapshot/ClipboardSnapshot.java new file mode 100644 index 0000000..e194f75 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/snapshot/ClipboardSnapshot.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.buildertools.snapshot; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface ClipboardSnapshot> extends SelectionSnapshot { + @Nullable + T restoreClipboard(Ref var1, Player var2, World var3, BuilderToolsPlugin.BuilderState var4, ComponentAccessor var5); + + @Override + default T restore(Ref ref, @Nonnull Player player, World world, ComponentAccessor componentAccessor) { + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + if (!.$assertionsDisabled && playerRefComponent == null) { + throw new AssertionError(); + } else { + BuilderToolsPlugin.BuilderState state = BuilderToolsPlugin.getState(player, playerRefComponent); + return state == null ? null : this.restoreClipboard(ref, player, world, state, componentAccessor); + } + } + + static { + if (.$assertionsDisabled) { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/snapshot/EntityAddSnapshot.java b/src/com/hypixel/hytale/builtin/buildertools/snapshot/EntityAddSnapshot.java new file mode 100644 index 0000000..34577de --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/snapshot/EntityAddSnapshot.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.builtin.buildertools.snapshot; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EntityAddSnapshot implements EntitySnapshot { + private final Ref entityRef; + + public EntityAddSnapshot(Ref entityRef) { + this.entityRef = entityRef; + } + + public Ref getEntityRef() { + return this.entityRef; + } + + public EntityRemoveSnapshot restoreEntity(@Nonnull Player player, @Nonnull World world, @Nonnull ComponentAccessor componentAccessor) { + if (!this.entityRef.isValid()) { + return null; + } else { + EntityRemoveSnapshot snapshot = new EntityRemoveSnapshot(this.entityRef); + world.getEntityStore().getStore().removeEntity(this.entityRef, RemoveReason.UNLOAD); + return snapshot; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/snapshot/EntityRemoveSnapshot.java b/src/com/hypixel/hytale/builtin/buildertools/snapshot/EntityRemoveSnapshot.java new file mode 100644 index 0000000..b5b9c6a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/snapshot/EntityRemoveSnapshot.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.builtin.buildertools.snapshot; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EntityRemoveSnapshot implements EntitySnapshot { + @Nonnull + private final Holder holder; + + public EntityRemoveSnapshot(@Nonnull Ref ref) { + this.holder = ref.getStore().copyEntity(ref); + } + + @Nonnull + public Holder getHolder() { + return this.holder; + } + + public EntityAddSnapshot restoreEntity(@Nonnull Player player, @Nonnull World world, @Nonnull ComponentAccessor componentAccessor) { + Ref entityRef = componentAccessor.addEntity(this.holder, AddReason.LOAD); + return new EntityAddSnapshot(entityRef); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/snapshot/EntitySnapshot.java b/src/com/hypixel/hytale/builtin/buildertools/snapshot/EntitySnapshot.java new file mode 100644 index 0000000..fd96b21 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/snapshot/EntitySnapshot.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.builtin.buildertools.snapshot; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface EntitySnapshot> extends SelectionSnapshot { + @Nullable + T restoreEntity(@Nonnull Player var1, @Nonnull World var2, @Nonnull ComponentAccessor var3); + + @Override + default T restore(Ref ref, Player player, @Nonnull World world, ComponentAccessor componentAccessor) { + Store store = world.getEntityStore().getStore(); + return !world.isInThread() + ? CompletableFuture.supplyAsync(() -> this.restoreEntity(player, world, store), world).join() + : this.restoreEntity(player, world, store); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/snapshot/EntityTransformSnapshot.java b/src/com/hypixel/hytale/builtin/buildertools/snapshot/EntityTransformSnapshot.java new file mode 100644 index 0000000..9eb52c6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/snapshot/EntityTransformSnapshot.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.buildertools.snapshot; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EntityTransformSnapshot implements EntitySnapshot { + @Nonnull + private final Ref ref; + @Nonnull + private final Transform transform; + @Nonnull + private final Vector3f headRotation; + + public EntityTransformSnapshot(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + this.ref = ref; + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = componentAccessor.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + this.transform = transformComponent.getTransform().clone(); + this.headRotation = headRotationComponent.getRotation().clone(); + } + + public EntityTransformSnapshot restoreEntity(@Nonnull Player player, @Nonnull World world, @Nonnull ComponentAccessor componentAccessor) { + if (!this.ref.isValid()) { + return null; + } else { + TransformComponent transformComponent = componentAccessor.getComponent(this.ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + transformComponent.setPosition(this.transform.getPosition()); + transformComponent.setRotation(this.transform.getRotation()); + HeadRotation headRotationComponent = componentAccessor.getComponent(this.ref, HeadRotation.getComponentType()); + if (headRotationComponent != null) { + headRotationComponent.setRotation(this.headRotation); + } + + return new EntityTransformSnapshot(this.ref, componentAccessor); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/snapshot/SelectionSnapshot.java b/src/com/hypixel/hytale/builtin/buildertools/snapshot/SelectionSnapshot.java new file mode 100644 index 0000000..f020383 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/snapshot/SelectionSnapshot.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.builtin.buildertools.snapshot; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nullable; + +public interface SelectionSnapshot> { + @Nullable + T restore(Ref var1, Player var2, World var3, ComponentAccessor var4); +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/tooloperations/FloodOperation.java b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/FloodOperation.java new file mode 100644 index 0000000..4592931 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/FloodOperation.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.builtin.buildertools.tooloperations; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolOnUseInteraction; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class FloodOperation extends ToolOperation { + public FloodOperation( + @Nonnull Ref ref, + @Nonnull Player player, + @Nonnull BuilderToolOnUseInteraction packet, + @Nonnull ComponentAccessor componentAccessor + ) { + super(ref, packet, componentAccessor); + } + + @Override + public void execute(ComponentAccessor componentAccessor) { + BlockPattern targetPattern = (BlockPattern)this.args.tool().get("TargetBlock"); + int targetBlock = targetPattern.isEmpty() ? this.edit.getAccessor().getBlock(this.x, this.y, this.z) : targetPattern.firstBlock(); + Player playerComponent = componentAccessor.getComponent(this.playerRef, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = componentAccessor.getComponent(this.playerRef, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + BuilderToolsPlugin.getState(playerComponent, playerRefComponent) + .flood(this.edit, this.x, this.y + this.originOffsetY, this.z, this.shapeRange, this.shapeHeight, this.pattern, targetBlock); + } + + @Override + boolean execute0(int x, int y, int z) { + return true; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/tooloperations/LaserPointerOperation.java b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/LaserPointerOperation.java new file mode 100644 index 0000000..77b855a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/LaserPointerOperation.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.builtin.buildertools.tooloperations; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolLaserPointer; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolOnUseInteraction; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.util.ColorParseUtil; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.universe.world.PlayerUtil; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import javax.annotation.Nonnull; + +public class LaserPointerOperation extends ToolOperation { + private static final double MAX_DISTANCE = 128.0; + + public LaserPointerOperation( + @Nonnull Ref ref, + @Nonnull Player player, + @Nonnull BuilderToolOnUseInteraction packet, + @Nonnull ComponentAccessor componentAccessor + ) { + super(ref, packet, componentAccessor); + String colorText = (String)this.args.tool().get("LaserColor"); + + int laserColor; + try { + laserColor = ColorParseUtil.hexStringToRGBInt(colorText); + } catch (NumberFormatException var18) { + player.sendMessage(Message.translation("server.builderTools.laserPointer.colorParseError").param("value", colorText)); + throw var18; + } + + Object durationObj = this.args.tool().get("Duration"); + int duration; + if (durationObj instanceof Integer) { + duration = (Integer)durationObj; + } else if (durationObj instanceof String) { + try { + duration = Integer.parseInt((String)durationObj); + } catch (NumberFormatException var17) { + player.sendMessage(Message.translation("server.builderTools.laserPointer.durationParseError").param("value", String.valueOf(durationObj))); + throw var17; + } + } else { + duration = 300; + } + + NetworkId networkIdComponent = componentAccessor.getComponent(ref, NetworkId.getComponentType()); + + assert networkIdComponent != null; + + int playerNetworkId = networkIdComponent.getId(); + Transform lookVec = TargetUtil.getLook(ref, componentAccessor); + Vector3d lookVecPosition = lookVec.getPosition(); + Vector3d lookVecDirection = lookVec.getDirection(); + Vector3d hitLocation = TargetUtil.getTargetLocation(ref, blockId -> blockId != 0, 128.0, componentAccessor); + Vector3d endLocation = hitLocation != null ? hitLocation : lookVecPosition.add(lookVecDirection.scale(128.0)); + BuilderToolLaserPointer laserPacket = new BuilderToolLaserPointer(); + laserPacket.playerNetworkId = playerNetworkId; + laserPacket.startX = (float)lookVecPosition.x; + laserPacket.startY = (float)lookVecPosition.y; + laserPacket.startZ = (float)lookVecPosition.z; + laserPacket.endX = (float)endLocation.x; + laserPacket.endY = (float)endLocation.y; + laserPacket.endZ = (float)endLocation.z; + laserPacket.color = laserColor; + laserPacket.durationMs = duration; + PlayerUtil.broadcastPacketToPlayers(componentAccessor, laserPacket); + } + + @Override + public void execute(ComponentAccessor componentAccessor) { + } + + @Override + boolean execute0(int x, int y, int z) { + return false; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/tooloperations/LayersOperation.java b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/LayersOperation.java new file mode 100644 index 0000000..a4d3231 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/LayersOperation.java @@ -0,0 +1,154 @@ +package com.hypixel.hytale.builtin.buildertools.tooloperations; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolOnUseInteraction; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class LayersOperation extends ToolOperation { + private final Vector3i depthDirection; + private final int layerOneLength; + private final int layerTwoLength; + private final boolean enableLayerTwo; + private final int layerThreeLength; + private final boolean enableLayerThree; + private final BlockPattern layerOneBlockPattern; + private final BlockPattern layerTwoBlockPattern; + private final BlockPattern layerThreeBlockPattern; + private final int brushDensity; + private final int maxDepthNecessary; + private boolean failed; + private final int layerTwoDepthEnd; + private final int layerThreeDepthEnd; + + public LayersOperation( + @Nonnull Ref ref, + @Nonnull Player player, + @Nonnull BuilderToolOnUseInteraction packet, + @Nonnull ComponentAccessor componentAccessor + ) { + super(ref, packet, componentAccessor); + HeadRotation headRotationComponent = componentAccessor.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + String var6 = (String)this.args.tool().get("aDirection"); + switch (var6) { + case "Up": + this.depthDirection = Vector3i.UP; + break; + case "Down": + this.depthDirection = Vector3i.DOWN; + break; + case "North": + this.depthDirection = Vector3i.NORTH; + break; + case "South": + this.depthDirection = Vector3i.SOUTH; + break; + case "East": + this.depthDirection = Vector3i.EAST; + break; + case "West": + this.depthDirection = Vector3i.WEST; + break; + case "Camera": + this.depthDirection = headRotationComponent.getAxisDirection(); + break; + default: + this.depthDirection = Vector3i.DOWN; + } + + this.brushDensity = (Integer)this.args.tool().get("jBrushDensity"); + this.layerOneLength = (Integer)this.args.tool().get("bLayerOneLength"); + this.layerTwoLength = (Integer)this.args.tool().get("eLayerTwoLength"); + this.layerThreeLength = (Integer)this.args.tool().get("hLayerThreeLength"); + this.layerOneBlockPattern = (BlockPattern)this.args.tool().get("cLayerOneMaterial"); + this.layerTwoBlockPattern = (BlockPattern)this.args.tool().get("fLayerTwoMaterial"); + this.layerThreeBlockPattern = (BlockPattern)this.args.tool().get("iLayerThreeMaterial"); + this.enableLayerTwo = (Boolean)this.args.tool().get("dEnableLayerTwo"); + this.enableLayerThree = (Boolean)this.args.tool().get("gEnableLayerThree"); + this.maxDepthNecessary = this.layerOneLength + (this.enableLayerTwo ? this.layerTwoLength : 0) + (this.enableLayerThree ? this.layerThreeLength : 0); + this.layerTwoDepthEnd = this.layerOneLength + this.layerTwoLength; + this.layerThreeDepthEnd = this.layerTwoDepthEnd + this.layerThreeLength; + if (this.enableLayerThree && !this.enableLayerTwo) { + player.sendMessage(Message.translation("server.builderTools.layerOperation.layerTwoRequired")); + this.failed = true; + } + } + + @Override + boolean execute0(int x, int y, int z) { + if (this.failed) { + return false; + } else if (this.random.nextInt(100) > this.brushDensity) { + return true; + } else { + int currentBlock = this.edit.getBlock(x, y, z); + if (currentBlock <= 0) { + return true; + } else { + if (this.depthDirection.x == 1) { + for (int i = 0; i < this.maxDepthNecessary; i++) { + if (this.edit.getBlock(x - i - 1, y, z) <= 0 && this.attemptSetBlock(x, y, z, i)) { + return true; + } + } + } else if (this.depthDirection.x == -1) { + for (int ix = 0; ix < this.maxDepthNecessary; ix++) { + if (this.edit.getBlock(x + ix + 1, y, z) <= 0 && this.attemptSetBlock(x, y, z, ix)) { + return true; + } + } + } else if (this.depthDirection.y == 1) { + for (int ixx = 0; ixx < this.maxDepthNecessary; ixx++) { + if (this.edit.getBlock(x, y - ixx - 1, z) <= 0 && this.attemptSetBlock(x, y, z, ixx)) { + return true; + } + } + } else if (this.depthDirection.y == -1) { + for (int ixxx = 0; ixxx < this.maxDepthNecessary; ixxx++) { + if (this.edit.getBlock(x, y + ixxx + 1, z) <= 0 && this.attemptSetBlock(x, y, z, ixxx)) { + return true; + } + } + } else if (this.depthDirection.z == 1) { + for (int ixxxx = 0; ixxxx < this.maxDepthNecessary; ixxxx++) { + if (this.edit.getBlock(x, y, z - ixxxx - 1) <= 0 && this.attemptSetBlock(x, y, z, ixxxx)) { + return true; + } + } + } else if (this.depthDirection.z == -1) { + for (int ixxxxx = 0; ixxxxx < this.maxDepthNecessary; ixxxxx++) { + if (this.edit.getBlock(x, y, z + ixxxxx + 1) <= 0 && this.attemptSetBlock(x, y, z, ixxxxx)) { + return true; + } + } + } + + return true; + } + } + } + + public boolean attemptSetBlock(int x, int y, int z, int depth) { + if (depth < this.layerOneLength) { + this.edit.setBlock(x, y, z, this.layerOneBlockPattern.nextBlock(this.random)); + return true; + } else if (this.enableLayerTwo && depth < this.layerTwoDepthEnd && !this.layerThreeBlockPattern.isEmpty()) { + this.edit.setBlock(x, y, z, this.layerTwoBlockPattern.nextBlock(this.random)); + return true; + } else if (this.enableLayerThree && depth < this.layerThreeDepthEnd && !this.layerThreeBlockPattern.isEmpty()) { + this.edit.setBlock(x, y, z, this.layerThreeBlockPattern.nextBlock(this.random)); + return true; + } else { + return false; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/tooloperations/NoiseOperation.java b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/NoiseOperation.java new file mode 100644 index 0000000..f3867b7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/NoiseOperation.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.builtin.buildertools.tooloperations; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolOnUseInteraction; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class NoiseOperation extends ToolOperation { + private final int brushDensity = this.getBrushDensity(); + + public NoiseOperation( + @Nonnull Ref ref, + @Nonnull Player player, + @Nonnull BuilderToolOnUseInteraction packet, + @Nonnull ComponentAccessor componentAccessor + ) { + super(ref, packet, componentAccessor); + } + + @Override + boolean execute0(int x, int y, int z) { + int currentBlock = this.edit.getBlock(x, y, z); + if (currentBlock <= 0 && this.builderState.isAsideBlock(this.edit.getAccessor(), x, y, z) && this.random.nextInt(100) <= this.brushDensity) { + this.edit.setBlock(x, y, z, this.pattern.nextBlock(this.random)); + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/tooloperations/OperationFactory.java b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/OperationFactory.java new file mode 100644 index 0000000..144ec1b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/OperationFactory.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.builtin.buildertools.tooloperations; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolOnUseInteraction; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public interface OperationFactory { + @Nonnull + ToolOperation create( + @Nonnull Ref var1, @Nonnull Player var2, @Nonnull BuilderToolOnUseInteraction var3, @Nonnull ComponentAccessor var4 + ); +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/tooloperations/PaintOperation.java b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/PaintOperation.java new file mode 100644 index 0000000..3aa6c3a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/PaintOperation.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.buildertools.tooloperations; + +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.builtin.buildertools.utils.Material; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.block.BlockUtil; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolOnUseInteraction; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import javax.annotation.Nonnull; + +public class PaintOperation extends ToolOperation { + private final int brushDensity; + private LongOpenHashSet packedPlacedBlockPositions; + + public PaintOperation( + @Nonnull Ref ref, + @Nonnull Player player, + @Nonnull BuilderToolOnUseInteraction packet, + @Nonnull ComponentAccessor componentAccessor + ) { + super(ref, packet, componentAccessor); + UUIDComponent uuidComponent = componentAccessor.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + PrototypePlayerBuilderToolSettings prototypePlayerBuilderToolSettings = ToolOperation.PROTOTYPE_TOOL_SETTINGS.get(uuidComponent.getUuid()); + this.packedPlacedBlockPositions = prototypePlayerBuilderToolSettings.addIgnoredPaintOperation(); + this.brushDensity = this.getBrushDensity(); + } + + @Override + boolean execute0(int x, int y, int z) { + if (y >= 0 && y < 320) { + if (this.edit.getBlock(x, y, z) == 0) { + this.packedPlacedBlockPositions.add(BlockUtil.pack(x, y, z)); + } + + if (this.random.nextInt(100) <= this.brushDensity) { + this.edit.setMaterial(x, y, z, Material.fromPattern(this.pattern, this.random)); + } + + return true; + } else { + return true; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/tooloperations/ScatterOperation.java b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/ScatterOperation.java new file mode 100644 index 0000000..a200728 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/ScatterOperation.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.buildertools.tooloperations; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolOnUseInteraction; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ScatterOperation extends ToolOperation { + private final int brushDensity = this.getBrushDensity(); + private final BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + + public ScatterOperation( + @Nonnull Ref ref, + @Nonnull Player player, + @Nonnull BuilderToolOnUseInteraction packet, + @Nonnull ComponentAccessor componentAccessor + ) { + super(ref, packet, componentAccessor); + } + + @Override + boolean execute0(int x, int y, int z) { + int currentBlock = this.edit.getBlock(x, y, z); + if (currentBlock <= 0 + && this.builderState.isAsideBlock(this.edit.getAccessor(), x, y, z) + && this.assetMap.getAsset(this.edit.getBlock(x, y - 1, z)).getFlags().isStackable + && this.random.nextInt(100) <= this.brushDensity) { + this.edit.setBlock(x, y, z, this.pattern.nextBlock(this.random)); + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/tooloperations/SculptOperation.java b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/SculptOperation.java new file mode 100644 index 0000000..53e324a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/SculptOperation.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.builtin.buildertools.tooloperations; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.builtin.buildertools.utils.Material; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.block.BlockUtil; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolOnUseInteraction; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import javax.annotation.Nonnull; + +public class SculptOperation extends ToolOperation { + private final int smoothVolume; + private final int smoothRadius; + private final boolean isAltPlaySculptBrushModDown; + private final int brushDensity; + private LongOpenHashSet packedPlacedBlockPositions; + + public SculptOperation( + @Nonnull Ref ref, + @Nonnull Player player, + @Nonnull BuilderToolOnUseInteraction packet, + @Nonnull ComponentAccessor componentAccessor + ) { + super(ref, packet, componentAccessor); + UUIDComponent uuidComponent = componentAccessor.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + this.isAltPlaySculptBrushModDown = packet.isAltPlaySculptBrushModDown; + PrototypePlayerBuilderToolSettings prototypePlayerBuilderToolSettings = ToolOperation.PROTOTYPE_TOOL_SETTINGS.get(uuidComponent.getUuid()); + this.packedPlacedBlockPositions = prototypePlayerBuilderToolSettings.addIgnoredPaintOperation(); + int smoothStrength = (Integer)this.args.tool().get("SmoothStrength"); + this.smoothRadius = Math.min(smoothStrength, 4); + int smoothRange = this.smoothRadius * 2 + 1; + this.smoothVolume = smoothRange * smoothRange * smoothRange; + this.brushDensity = this.getBrushDensity(); + } + + @Override + boolean execute0(int x, int y, int z) { + int currentBlock = this.edit.getBlock(x, y, z); + if (this.isAltPlaySculptBrushModDown) { + BuilderToolsPlugin.BuilderState.BlocksSampleData data = BuilderToolsPlugin.getState(this.player, this.player.getPlayerRef()) + .getBlocksSampleData(this.edit.getAccessor(), x, y, z, 2); + if (currentBlock != data.mainBlock && data.mainBlockCount > this.smoothVolume * 0.5F) { + this.edit.setMaterial(x, y, z, Material.block(data.mainBlock)); + } + } else if (this.interactionType == InteractionType.Primary) { + if (currentBlock > 0 && this.builderState.isAsideAir(this.edit.getAccessor(), x, y, z) && this.random.nextInt(100) <= this.brushDensity) { + this.edit.setMaterial(x, y, z, Material.EMPTY); + } + } else if (this.interactionType == InteractionType.Secondary && currentBlock <= 0 && this.builderState.isAsideBlock(this.edit.getAccessor(), x, y, z)) { + if (this.edit.getBlock(x, y, z) == 0) { + this.packedPlacedBlockPositions.add(BlockUtil.pack(x, y, z)); + } + + if (this.random.nextInt(100) <= this.brushDensity) { + Material material = Material.fromPattern(this.pattern, this.random); + if (material.isEmpty()) { + BuilderToolsPlugin.BuilderState.BlocksSampleData data = this.builderState.getBlocksSampleData(this.edit.getAccessor(), x, y, z, 1); + material = Material.block(data.mainBlockNotAir); + } + + this.edit.setMaterial(x, y, z, material); + } + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/tooloperations/SmoothOperation.java b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/SmoothOperation.java new file mode 100644 index 0000000..1830636 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/SmoothOperation.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.builtin.buildertools.tooloperations; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolOnUseInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SmoothOperation extends ToolOperation { + private final int smoothVolume; + + public SmoothOperation(@Nonnull Ref ref, @Nonnull BuilderToolOnUseInteraction packet, @Nonnull ComponentAccessor componentAccessor) { + super(ref, packet, componentAccessor); + int smoothStrength = (Integer)this.args.tool().get("SmoothStrength"); + int smoothRange = Math.min(smoothStrength, 4) * 2 + 1; + this.smoothVolume = smoothRange * smoothRange * smoothRange; + } + + @Override + boolean execute0(int x, int y, int z) { + int currentBlock = this.edit.getBlock(x, y, z); + BuilderToolsPlugin.BuilderState.BlocksSampleData data = BuilderToolsPlugin.getState(this.player, this.player.getPlayerRef()) + .getBlocksSampleData(this.edit.getAccessor(), x, y, z, 2); + if (currentBlock != data.mainBlock && data.mainBlockCount > this.smoothVolume * 0.5F) { + this.edit.setBlock(x, y, z, data.mainBlock); + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/tooloperations/SmootherOperation.java b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/SmootherOperation.java new file mode 100644 index 0000000..1fa002c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/SmootherOperation.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.buildertools.tooloperations; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolOnUseInteraction; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SmootherOperation extends ToolOperation { + private final float strength; + + public SmootherOperation( + @Nonnull Ref ref, + @Nonnull Player player, + @Nonnull BuilderToolOnUseInteraction packet, + @Nonnull ComponentAccessor componentAccessor + ) { + super(ref, packet, componentAccessor); + boolean removing = packet.type == InteractionType.Primary; + int baseStrength = removing ? (Integer)this.args.tool().get("RemoveStrength") : (Integer)this.args.tool().get("AddStrength"); + this.strength = 1.0F + (removing ? baseStrength : -baseStrength) * 0.01F; + } + + @Override + boolean execute0(int x, int y, int z) { + int currentBlock = this.edit.getBlock(x, y, z); + BuilderToolsPlugin.BuilderState.SmoothSampleData data = this.builderState.getBlocksSmoothData(this.edit.getAccessor(), x, y, z); + if (data.solidStrength > this.strength) { + if (currentBlock != data.solidBlock) { + this.edit.setBlock(x, y, z, data.solidBlock); + } + } else if (currentBlock != data.fillerBlock) { + this.edit.setBlock(x, y, z, data.fillerBlock); + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/tooloperations/TintOperation.java b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/TintOperation.java new file mode 100644 index 0000000..498bbbe --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/TintOperation.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.buildertools.tooloperations; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolOnUseInteraction; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.util.ColorParseUtil; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class TintOperation extends ToolOperation { + private final int tintColor; + + public TintOperation( + @Nonnull Ref ref, + @Nonnull Player player, + @Nonnull BuilderToolOnUseInteraction packet, + @Nonnull ComponentAccessor componentAccessor + ) { + super(ref, packet, componentAccessor); + String colorText = (String)this.args.tool().get("TintColor"); + + try { + this.tintColor = ColorParseUtil.hexStringToRGBInt(colorText); + } catch (NumberFormatException var7) { + player.sendMessage(Message.translation("server.builderTools.tintOperation.colorParseError").param("value", colorText)); + throw var7; + } + } + + @Override + public void execute(ComponentAccessor componentAccessor) { + this.builderState.tint(this.x, this.y, this.z, this.tintColor, this.shape, this.shapeRange, componentAccessor); + } + + @Override + boolean execute0(int x, int y, int z) { + return true; + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/tooloperations/ToolOperation.java b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/ToolOperation.java new file mode 100644 index 0000000..e5401b1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/ToolOperation.java @@ -0,0 +1,515 @@ +package com.hypixel.hytale.builtin.buildertools.tooloperations; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.buildertools.EditOperation; +import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings; +import com.hypixel.hytale.builtin.buildertools.tooloperations.transform.Mirror; +import com.hypixel.hytale.builtin.buildertools.tooloperations.transform.Rotate; +import com.hypixel.hytale.builtin.buildertools.tooloperations.transform.Transform; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.function.predicate.TriIntObjPredicate; +import com.hypixel.hytale.math.block.BlockConeUtil; +import com.hypixel.hytale.math.block.BlockCubeUtil; +import com.hypixel.hytale.math.block.BlockCylinderUtil; +import com.hypixel.hytale.math.block.BlockDiamondUtil; +import com.hypixel.hytale.math.block.BlockDomeUtil; +import com.hypixel.hytale.math.block.BlockInvertedDomeUtil; +import com.hypixel.hytale.math.block.BlockPyramidUtil; +import com.hypixel.hytale.math.block.BlockSphereUtil; +import com.hypixel.hytale.math.block.BlockTorusUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.packets.buildertools.BrushAxis; +import com.hypixel.hytale.protocol.packets.buildertools.BrushOrigin; +import com.hypixel.hytale.protocol.packets.buildertools.BrushShape; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolOnUseInteraction; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BrushData; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BuilderTool; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockFilter; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockMask; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class ToolOperation implements TriIntObjPredicate { + protected static final int RANDOM_MAX = 100; + @Nonnull + public static final Map OPERATIONS = new ConcurrentHashMap<>(); + @Nonnull + public static final Map PROTOTYPE_TOOL_SETTINGS = new ConcurrentHashMap<>(); + public static final double MAX_DISTANCE = 400.0; + public static final int DEFAULT_BRUSH_SPACING = 0; + protected final int x; + protected final int y; + protected final int z; + protected final InteractionType interactionType; + protected final int shapeRange; + protected final int shapeHeight; + protected final int shapeThickness; + protected final boolean capped; + protected final int originOffsetX; + protected final int originOffsetY; + protected final int originOffsetZ; + protected final BrushShape shape; + protected final BlockPattern pattern; + @Nonnull + protected final EditOperation edit; + @Nonnull + protected final BuilderTool.ArgData args; + @Nonnull + protected final Random random; + @Nonnull + protected final Player player; + @Nonnull + protected final Ref playerRef; + @Nonnull + protected final BuilderToolsPlugin.BuilderState builderState; + private final Transform transform; + private final Vector3i vector = new Vector3i(); + @Nullable + private final BlockMask mask; + + public ToolOperation(@Nonnull Ref ref, @Nonnull BuilderToolOnUseInteraction packet, @Nonnull ComponentAccessor componentAccessor) { + this.playerRef = ref; + World world = componentAccessor.getExternalData().getWorld(); + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + this.player = playerComponent; + this.builderState = BuilderToolsPlugin.getState(playerComponent, playerRefComponent); + UUIDComponent uuidComponent = componentAccessor.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID uuid = uuidComponent.getUuid(); + PrototypePlayerBuilderToolSettings playerBuilderToolSettings = PROTOTYPE_TOOL_SETTINGS.get(uuid); + if (playerBuilderToolSettings == null) { + playerBuilderToolSettings = new PrototypePlayerBuilderToolSettings(uuid); + PROTOTYPE_TOOL_SETTINGS.put(uuid, playerBuilderToolSettings); + } + + playerBuilderToolSettings.setShouldShowEditorSettings(packet.isShowEditNotifications); + playerBuilderToolSettings.setMaxLengthOfIgnoredPaintOperations(packet.maxLengthToolIgnoreHistory); + if (!packet.isHoldDownInteraction && (this instanceof PaintOperation || this instanceof SculptOperation)) { + playerBuilderToolSettings.getIgnoredPaintOperations().clear(); + playerBuilderToolSettings.clearLastBrushPosition(); + } + + if (packet.isDoServerRaytraceForPosition && (this instanceof PaintOperation || this instanceof SculptOperation)) { + Vector3i targetBlockAvoidingPaint = this.getTargetBlockAvoidingPaint( + ref, + 400.0, + componentAccessor, + packet.raycastOriginX, + packet.raycastOriginY, + packet.raycastOriginZ, + packet.raycastDirectionX, + packet.raycastDirectionY, + packet.raycastDirectionZ + ); + if (targetBlockAvoidingPaint != null) { + this.x = targetBlockAvoidingPaint.x + packet.offsetForPaintModeX; + this.y = targetBlockAvoidingPaint.y + packet.offsetForPaintModeY; + this.z = targetBlockAvoidingPaint.z + packet.offsetForPaintModeZ; + } else { + this.x = packet.x; + this.y = packet.y; + this.z = packet.z; + } + } else { + this.x = packet.x; + this.y = packet.y; + this.z = packet.z; + } + + this.interactionType = packet.type; + BuilderTool builderTool = BuilderTool.getActiveBuilderTool(playerComponent); + BuilderTool.ArgData args = this.args = builderTool.getItemArgData(playerComponent.getInventory().getItemInHand()); + BrushData.Values brush = args.brush(); + if (brush == null) { + brush = new BrushData.Values(BrushData.DEFAULT); + } + + this.transform = getTransform(ref, brush, this.vector, componentAccessor); + this.shapeRange = brush.getWidth(); + this.shapeHeight = brush.getHeight(); + this.shapeThickness = brush.getThickness(); + this.capped = brush.isCapped(); + this.shape = brush.getShape(); + this.pattern = this.getPattern(packet, brush); + this.mask = combineMasks(brush, this.builderState.getGlobalMask()); + BrushOrigin shapeOrigin = brush.getOrigin(); + boolean originRotation = brush.getOriginRotation(); + Vector3i offsets = getOffsets(this.shapeRange, this.shapeHeight, originRotation, shapeOrigin, this.transform, this.vector, true); + this.originOffsetX = offsets.getX(); + this.originOffsetY = offsets.getY(); + this.originOffsetZ = offsets.getZ(); + this.random = this.builderState.getRandom(); + Vector3i brushMin = new Vector3i(this.x - this.shapeRange, this.y - this.shapeHeight, this.z - this.shapeRange); + Vector3i brushMax = new Vector3i(this.x + this.shapeRange, this.y + this.shapeHeight, this.z + this.shapeRange); + this.edit = new EditOperation(world, this.x, this.y, this.z, this.shapeRange, brushMin, brushMax, this.mask); + } + + @Nonnull + public static PrototypePlayerBuilderToolSettings getOrCreatePrototypeSettings(UUID playerUuid) { + PrototypePlayerBuilderToolSettings settings = PROTOTYPE_TOOL_SETTINGS.get(playerUuid); + if (settings == null) { + settings = new PrototypePlayerBuilderToolSettings(playerUuid); + PROTOTYPE_TOOL_SETTINGS.put(playerUuid, settings); + } + + return settings; + } + + @Nonnull + public static List calculateInterpolatedPositions( + @Nullable Vector3i lastPosition, @Nonnull Vector3i currentPosition, int brushWidth, int brushHeight, int brushSpacing + ) { + ArrayList positions = new ArrayList<>(); + if (lastPosition == null) { + positions.add(currentPosition); + return positions; + } else { + double dx = currentPosition.getX() - lastPosition.getX(); + double dy = currentPosition.getY() - lastPosition.getY(); + double dz = currentPosition.getZ() - lastPosition.getZ(); + double distance = Math.sqrt(dx * dx + dy * dy + dz * dz); + if (brushSpacing == 0) { + float maxBrushDimension = Math.max(brushWidth, brushHeight); + float spacingThreshold = Math.max(1.0F, maxBrushDimension * 0.5F); + if (distance <= spacingThreshold) { + positions.add(currentPosition); + return positions; + } + + int steps = (int)Math.ceil(distance / spacingThreshold); + + for (int i = 1; i <= steps; i++) { + float t = (float)i / steps; + int interpX = (int)Math.round(lastPosition.getX() + dx * t); + int interpY = (int)Math.round(lastPosition.getY() + dy * t); + int interpZ = (int)Math.round(lastPosition.getZ() + dz * t); + positions.add(new Vector3i(interpX, interpY, interpZ)); + } + } else if (distance >= brushSpacing) { + positions.add(currentPosition); + } + + return positions; + } + } + + @Nonnull + public Vector3i getPosition() { + return new Vector3i(this.x, this.y, this.z); + } + + public int getBrushWidth() { + return this.shapeRange; + } + + public int getBrushHeight() { + return this.shapeHeight; + } + + public int getBrushSpacing() { + Object spacingValue = this.args.tool().get("BrushSpacing"); + return spacingValue instanceof Number ? ((Number)spacingValue).intValue() : 0; + } + + public int getBrushDensity() { + return this.args.tool().get("BrushDensity") instanceof Number number ? number.intValue() : 100; + } + + public void executeAsBrushConfig( + @Nonnull PrototypePlayerBuilderToolSettings prototypePlayerBuilderToolSettings, + @Nonnull BuilderToolOnUseInteraction packet, + ComponentAccessor componentAccessor + ) { + World world = componentAccessor.getExternalData().getWorld(); + prototypePlayerBuilderToolSettings.getBrushConfigCommandExecutor() + .execute(this.playerRef, world, new Vector3i(this.x, this.y, this.z), packet.isHoldDownInteraction, packet.type, bc -> { + bc.setPattern(this.pattern); + bc.setDensity(this.getBrushDensity()); + bc.setShapeHeight(this.shapeHeight); + bc.setShapeWidth(this.shapeRange); + bc.setShape(this.shape); + bc.setCapped(this.capped); + bc.modifyOriginOffset(new Vector3i(this.originOffsetX, this.originOffsetY, this.originOffsetZ)); + bc.setBrushMask(this.mask); + bc.setShapeThickness(this.shapeThickness); + }, componentAccessor); + } + + private BlockPattern getPattern(@Nonnull BuilderToolOnUseInteraction packet, @Nonnull BrushData.Values brush) { + if (packet.type == InteractionType.Primary) { + return BlockPattern.EMPTY; + } else { + return (this instanceof PaintOperation || this instanceof PaintOperation) && brush.getMaterial().equals(BlockPattern.EMPTY) + ? BlockPattern.parse("Rock_Stone") + : brush.getMaterial(); + } + } + + @Nullable + public Vector3i getTargetBlockAvoidingPaint( + @Nonnull Ref ref, + double maxDistance, + @Nonnull ComponentAccessor componentAccessor, + float raycastOriginX, + float raycastOriginY, + float raycastOriginZ, + float raycastDirectionX, + float raycastDirectionY, + float raycastDirectionZ + ) { + World world = componentAccessor.getExternalData().getWorld(); + UUIDComponent uuidComponent = componentAccessor.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + PrototypePlayerBuilderToolSettings prototypePlayerBuilderToolSettings = PROTOTYPE_TOOL_SETTINGS.get(uuidComponent.getUuid()); + return prototypePlayerBuilderToolSettings != null && !prototypePlayerBuilderToolSettings.getIgnoredPaintOperations().isEmpty() + ? TargetUtil.getTargetBlockAvoidLocations( + world, + blockId -> blockId != 0, + raycastOriginX, + raycastOriginY, + raycastOriginZ, + raycastDirectionX, + raycastDirectionY, + raycastDirectionZ, + maxDistance, + prototypePlayerBuilderToolSettings.getIgnoredPaintOperations() + ) + : TargetUtil.getTargetBlock( + world, + (blockId, _fluidId) -> blockId != 0, + raycastOriginX, + raycastOriginY, + raycastOriginZ, + raycastDirectionX, + raycastDirectionY, + raycastDirectionZ, + maxDistance + ); + } + + @Nonnull + public EditOperation getEditOperation() { + return this.edit; + } + + public final boolean test(int x, int y, int z, Void aVoid) { + if (this.transform == Transform.NONE) { + return this.execute0(x, y + this.originOffsetY, z); + } else { + this.vector.assign(x - this.x, y - this.y, z - this.z); + this.transform.apply(this.vector); + x = this.x + this.originOffsetX + this.vector.x; + y = this.y + this.originOffsetY + this.vector.y; + z = this.z + this.originOffsetZ + this.vector.z; + return this.execute0(x, y, z); + } + } + + abstract boolean execute0(int var1, int var2, int var3); + + public void execute(ComponentAccessor componentAccessor) { + executeShapeOperation(this.x, this.y, this.z, this, this.shape, this.shapeRange, this.shapeHeight, this.shapeThickness, this.capped); + } + + public void executeAt(int posX, int posY, int posZ, ComponentAccessor componentAccessor) { + executeShapeOperation(posX, posY, posZ, this, this.shape, this.shapeRange, this.shapeHeight, this.shapeThickness, this.capped); + } + + public static void executeShapeOperation( + int x, + int y, + int z, + @Nonnull TriIntObjPredicate operation, + @Nonnull BrushShape shape, + int shapeRange, + int shapeHeight, + int shapeThickness, + boolean capped + ) { + if (shapeRange <= 1 && shapeHeight <= 1) { + operation.test(x, y, z, null); + } else { + int radiusXZ = Math.max(shapeRange / 2, 1); + int halfHeight = Math.max(shapeHeight / 2, 1); + switch (shape) { + case Cube: + default: + BlockCubeUtil.forEachBlock(x, y, z, radiusXZ, shapeHeight, radiusXZ, shapeThickness, capped, null, operation); + break; + case Sphere: + BlockSphereUtil.forEachBlock(x, y, z, radiusXZ, halfHeight, radiusXZ, shapeThickness, null, operation); + break; + case Cylinder: + BlockCylinderUtil.forEachBlock(x, y - halfHeight, z, radiusXZ, shapeHeight, radiusXZ, shapeThickness, capped, null, operation); + break; + case Cone: + BlockConeUtil.forEachBlock(x, y - halfHeight, z, radiusXZ, shapeHeight, radiusXZ, shapeThickness, capped, null, operation); + break; + case InvertedCone: + BlockConeUtil.forEachBlockInverted(x, y - halfHeight, z, radiusXZ, shapeHeight, radiusXZ, shapeThickness, capped, null, operation); + break; + case Pyramid: + BlockPyramidUtil.forEachBlock(x, y - halfHeight, z, radiusXZ, shapeHeight, radiusXZ, shapeThickness, capped, null, operation); + break; + case InvertedPyramid: + BlockPyramidUtil.forEachBlockInverted(x, y - halfHeight, z, radiusXZ, shapeHeight, radiusXZ, shapeThickness, capped, null, operation); + break; + case Dome: + BlockDomeUtil.forEachBlock(x, y - halfHeight, z, radiusXZ, shapeHeight, radiusXZ, shapeThickness, capped, null, operation); + break; + case InvertedDome: + BlockInvertedDomeUtil.forEachBlock(x, y + halfHeight, z, radiusXZ, shapeHeight, radiusXZ, shapeThickness, capped, null, operation); + break; + case Diamond: + BlockDiamondUtil.forEachBlock(x, y, z, radiusXZ, shapeHeight / 2, radiusXZ, shapeThickness, capped, null, operation); + break; + case Torus: + int minorRadius = Math.max(1, shapeHeight / 4); + BlockTorusUtil.forEachBlock(x, y, z, radiusXZ, minorRadius, shapeThickness, capped, null, operation); + } + } + } + + @Nonnull + private static Vector3i getOffsets( + int width, int height, boolean originRotation, BrushOrigin origin, @Nonnull Transform transform, @Nonnull Vector3i vector, boolean applyBottomOriginFix + ) { + int offsetY = height / 2; + int offsetXZ = originRotation ? width / 2 : 0; + vector.assign(0, offsetY, 0); + transform.apply(vector); + int ox = vector.getX(); + int oz = vector.getZ(); + vector.assign(offsetXZ, offsetY, -offsetXZ); + transform.apply(vector); + int oy = vector.getY(); + ox = origin == BrushOrigin.Center ? 0 : (origin == BrushOrigin.Bottom ? ox : -ox); + oy = origin == BrushOrigin.Center ? 0 : (origin == BrushOrigin.Bottom ? oy + (applyBottomOriginFix ? 1 : 0) : -oy); + oz = origin == BrushOrigin.Center ? 0 : (origin == BrushOrigin.Bottom ? oz : -oz); + return vector.assign(ox, oy, oz); + } + + private static Transform getTransform( + @Nonnull Ref ref, @Nonnull BrushData.Values brushData, @Nonnull Vector3i vector, @Nonnull ComponentAccessor componentAccessor + ) { + Transform rotate = getRotation(ref, brushData, vector, componentAccessor); + Transform mirror = getMirror(ref, brushData, vector, componentAccessor); + return rotate.then(mirror); + } + + private static Transform getRotation( + @Nonnull Ref ref, @Nonnull BrushData.Values brushData, @Nonnull Vector3i vector, @Nonnull ComponentAccessor componentAccessor + ) { + if (brushData.getRotationAxis() == BrushAxis.Auto) { + HeadRotation headRotationComponent = componentAccessor.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + return Rotate.forDirection(headRotationComponent.getAxisDirection(vector), brushData.getRotationAngle()); + } else { + return Rotate.forAxisAndAngle(brushData.getRotationAxis(), brushData.getRotationAngle()); + } + } + + private static Transform getMirror( + @Nonnull Ref ref, @Nonnull BrushData.Values brushData, @Nonnull Vector3i vector, @Nonnull ComponentAccessor componentAccessor + ) { + if (brushData.getMirrorAxis() == BrushAxis.Auto) { + HeadRotation headRotationComponent = componentAccessor.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + return Mirror.forDirection(headRotationComponent.getAxisDirection(vector), false); + } else { + return Mirror.forAxis(brushData.getMirrorAxis()); + } + } + + @Nonnull + public static ToolOperation fromPacket( + @Nonnull Ref ref, + @Nonnull Player player, + @Nonnull BuilderToolOnUseInteraction packet, + @Nonnull ComponentAccessor componentAccessor + ) throws Exception { + BuilderTool builderTool = BuilderTool.getActiveBuilderTool(player); + if (builderTool == null) { + throw new IllegalStateException("No builder tool active on player"); + } else { + String toolType = builderTool.getId(); + OperationFactory factory = OPERATIONS.get(toolType); + if (factory == null) { + throw new Exception("No tool found matching id " + toolType); + } else { + return factory.create(ref, player, packet, componentAccessor); + } + } + } + + @Nullable + public static BlockMask combineMasks(@Nullable BrushData.Values brush, @Nullable BlockMask globalMask) { + if (brush == null) { + return globalMask; + } else if (brush.shouldUseMaskCommands()) { + BlockMask mask = BlockMask.combine(brush.getParsedMaskCommands()); + if (mask != null) { + mask.setInverted(brush.shouldInvertMask()); + } + + return mask; + } else { + BlockMask brushMaskAbove = brush.getMaskAbove().withOptions(BlockFilter.FilterType.AboveBlock, false); + BlockMask brushMaskNot = brush.getMaskNot().withOptions(BlockFilter.FilterType.TargetBlock, true); + BlockMask brushMaskBelow = brush.getMaskBelow().withOptions(BlockFilter.FilterType.BelowBlock, false); + BlockMask brushMaskAdjacent = brush.getMaskAdjacent().withOptions(BlockFilter.FilterType.AdjacentBlock, false); + BlockMask brushMaskNeighbor = brush.getMaskNeighbor().withOptions(BlockFilter.FilterType.NeighborBlock, false); + BlockMask combinedMask = BlockMask.combine( + brush.getMask(), brushMaskAbove, brushMaskNot, brushMaskBelow, brushMaskAdjacent, brushMaskNeighbor, globalMask + ); + if (combinedMask != null) { + combinedMask.setInverted(brush.shouldInvertMask()); + } + + return combinedMask; + } + } + + static { + OPERATIONS.put("Flood", FloodOperation::new); + OPERATIONS.put("Noise", NoiseOperation::new); + OPERATIONS.put("Scatter", ScatterOperation::new); + OPERATIONS.put("Smooth", (ref, player1, packet, componentAccessor) -> new SmoothOperation(ref, packet, componentAccessor)); + OPERATIONS.put("Tint", TintOperation::new); + OPERATIONS.put("Paint", PaintOperation::new); + OPERATIONS.put("Sculpt", SculptOperation::new); + OPERATIONS.put("Layers", LayersOperation::new); + OPERATIONS.put("LaserPointer", LaserPointerOperation::new); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/tooloperations/transform/Composite.java b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/transform/Composite.java new file mode 100644 index 0000000..17b49db --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/transform/Composite.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.buildertools.tooloperations.transform; + +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class Composite implements Transform { + private final Transform first; + private final Transform second; + + private Composite(Transform first, Transform second) { + this.first = first; + this.second = second; + } + + @Override + public void apply(Vector3i vector3i) { + this.first.apply(vector3i); + this.second.apply(vector3i); + } + + @Nonnull + @Override + public String toString() { + return "Composite{first=" + this.first + ", second=" + this.second + "}"; + } + + public static Transform of(Transform first, Transform second) { + if (first == NONE && second == NONE) { + return NONE; + } else if (first == NONE) { + return second; + } else { + return (Transform)(second == NONE ? first : new Composite(first, second)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/tooloperations/transform/Mirror.java b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/transform/Mirror.java new file mode 100644 index 0000000..80727d2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/transform/Mirror.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.buildertools.tooloperations.transform; + +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.packets.buildertools.BrushAxis; +import javax.annotation.Nonnull; + +public class Mirror implements Transform { + public static final Transform X = new Mirror(Axis.X); + public static final Transform Y = new Mirror(Axis.Y); + public static final Transform Z = new Mirror(Axis.Z); + private final Axis axis; + + private Mirror(Axis axis) { + this.axis = axis; + } + + @Override + public void apply(@Nonnull Vector3i vector3i) { + this.axis.flip(vector3i); + } + + @Nonnull + @Override + public String toString() { + return "Mirror{axis=" + this.axis + "}"; + } + + public static Transform forAxis(BrushAxis axis) { + if (axis == BrushAxis.X) { + return X; + } else if (axis == BrushAxis.Y) { + return Y; + } else { + return axis == BrushAxis.Z ? Z : NONE; + } + } + + public static Transform forDirection(@Nonnull Vector3i direction) { + return forDirection(direction, true); + } + + public static Transform forDirection(@Nonnull Vector3i direction, boolean negativeY) { + if (direction.getX() != 0) { + return X; + } else if (direction.getZ() != 0) { + return Z; + } else if (direction.getY() > 0) { + return Y; + } else { + return direction.getY() < 0 && negativeY ? Y : NONE; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/tooloperations/transform/Rotate.java b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/transform/Rotate.java new file mode 100644 index 0000000..7c3fca2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/transform/Rotate.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.builtin.buildertools.tooloperations.transform; + +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.Rotation; +import com.hypixel.hytale.protocol.packets.buildertools.BrushAxis; +import javax.annotation.Nonnull; + +public class Rotate implements Transform { + public static final Transform X_90 = new Rotate(Axis.X, 90); + public static final Transform X_180 = new Rotate(Axis.X, 180); + public static final Transform X_270 = new Rotate(Axis.X, 270); + public static final Transform Y_90 = new Rotate(Axis.Y, 90); + public static final Transform Y_180 = new Rotate(Axis.Y, 180); + public static final Transform Y_270 = new Rotate(Axis.Y, 270); + public static final Transform Z_90 = new Rotate(Axis.Z, 90); + public static final Transform Z_180 = new Rotate(Axis.Z, 180); + public static final Transform Z_270 = new Rotate(Axis.Z, 270); + public static final Transform FACING_NORTH = X_90; + public static final Transform FACING_EAST = Z_90; + public static final Transform FACING_SOUTH = X_90.then(Y_180); + public static final Transform FACING_WEST = Z_90.then(Y_180); + private final Axis axis; + private final int rotations; + + public Rotate(Axis axis) { + this(axis, 90); + } + + public Rotate(Axis axis, int angle) { + angle = Math.floorMod(angle, 360); + int rotations = angle / 90; + this.axis = axis; + this.rotations = rotations; + } + + @Override + public void apply(@Nonnull Vector3i vector3i) { + if (this.rotations == 1) { + this.axis.rotate(vector3i); + } else if (this.rotations > 1) { + for (int i = 0; i < this.rotations; i++) { + this.axis.rotate(vector3i); + } + } + } + + @Nonnull + @Override + public String toString() { + return "Rotate{axis=" + this.axis + ", rotations=" + this.rotations + "}"; + } + + public static Transform forDirection(@Nonnull Vector3i direction, Rotation angle) { + if (direction.getX() < 0) { + return selectRotation(angle, FACING_WEST, FACING_NORTH, FACING_EAST, FACING_SOUTH); + } else if (direction.getX() > 0) { + return selectRotation(angle, FACING_EAST, FACING_SOUTH, FACING_WEST, FACING_NORTH); + } else if (direction.getZ() < 0) { + return selectRotation(angle, FACING_NORTH, FACING_EAST, FACING_SOUTH, FACING_WEST); + } else { + return direction.getZ() > 0 ? selectRotation(angle, FACING_SOUTH, FACING_WEST, FACING_NORTH, FACING_EAST) : NONE; + } + } + + public static Transform forAxisAndAngle(BrushAxis axis, Rotation angle) { + if (axis == BrushAxis.X) { + return selectRotation(angle, NONE, X_90, X_180, X_270); + } else if (axis == BrushAxis.Y) { + return selectRotation(angle, NONE, Y_90, Y_180, Y_270); + } else { + return axis == BrushAxis.Z ? selectRotation(angle, NONE, Z_90, Z_180, Z_270) : NONE; + } + } + + private static Transform selectRotation(Rotation angle, Transform rotate0, Transform rotate90, Transform rotate180, Transform rotate270) { + if (angle == Rotation.Ninety) { + return rotate90; + } else if (angle == Rotation.OneEighty) { + return rotate180; + } else { + return angle == Rotation.TwoSeventy ? rotate270 : rotate0; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/tooloperations/transform/Transform.java b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/transform/Transform.java new file mode 100644 index 0000000..3924fbc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/transform/Transform.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.builtin.buildertools.tooloperations.transform; + +import com.hypixel.hytale.math.vector.Vector3i; + +public interface Transform { + Transform NONE = vec -> {}; + + void apply(Vector3i var1); + + default Transform then(Transform next) { + return Composite.of(this, next); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/tooloperations/transform/Translate.java b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/transform/Translate.java new file mode 100644 index 0000000..7eb8f5f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/tooloperations/transform/Translate.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.buildertools.tooloperations.transform; + +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class Translate implements Transform { + private final int x; + private final int y; + private final int z; + + private Translate(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public void apply(@Nonnull Vector3i vector3i) { + vector3i.add(this.x, this.y, this.z); + } + + @Nonnull + @Override + public String toString() { + return "Translate{x=" + this.x + ", y=" + this.y + ", z=" + this.z + "}"; + } + + @Nonnull + public static Transform of(@Nonnull Vector3i vector) { + return of(vector.getX(), vector.getY(), vector.getZ()); + } + + @Nonnull + public static Transform of(int x, int y, int z) { + return (Transform)(x == 0 && y == 0 && z == 0 ? NONE : new Translate(x, y, z)); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/utils/FluidPatternHelper.java b/src/com/hypixel/hytale/builtin/buildertools/utils/FluidPatternHelper.java new file mode 100644 index 0000000..268ba6f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/utils/FluidPatternHelper.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.builtin.buildertools.utils; + +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.PlaceFluidInteraction; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class FluidPatternHelper { + private FluidPatternHelper() { + } + + @Nullable + public static FluidPatternHelper.FluidInfo getFluidInfo(@Nonnull String itemKey) { + Item item = Item.getAssetMap().getAsset(itemKey); + if (item == null) { + return null; + } else { + Map interactions = item.getInteractions(); + String secondaryRootId = interactions.get(InteractionType.Secondary); + if (secondaryRootId == null) { + return null; + } else { + RootInteraction rootInteraction = RootInteraction.getAssetMap().getAsset(secondaryRootId); + if (rootInteraction == null) { + return null; + } else { + for (String interactionId : rootInteraction.getInteractionIds()) { + Interaction interaction = Interaction.getAssetMap().getAsset(interactionId); + if (interaction instanceof PlaceFluidInteraction placeFluidInteraction) { + String fluidKey = placeFluidInteraction.getFluidKey(); + if (fluidKey != null) { + int fluidId = Fluid.getAssetMap().getIndex(fluidKey); + if (fluidId >= 0) { + Fluid fluid = Fluid.getAssetMap().getAsset(fluidId); + byte maxLevel = (byte)(fluid != null ? fluid.getMaxFluidLevel() : 8); + return new FluidPatternHelper.FluidInfo(fluidId, maxLevel); + } + } + } + } + + return null; + } + } + } + } + + public static boolean isFluidItem(@Nonnull String itemKey) { + return getFluidInfo(itemKey) != null; + } + + @Nullable + public static FluidPatternHelper.FluidInfo getFluidInfoFromBlockType(@Nonnull String blockTypeKey) { + return getFluidInfo(blockTypeKey); + } + + public record FluidInfo(int fluidId, byte fluidLevel) { + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/utils/Material.java b/src/com/hypixel/hytale/builtin/buildertools/utils/Material.java new file mode 100644 index 0000000..71afd34 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/utils/Material.java @@ -0,0 +1,147 @@ +package com.hypixel.hytale.builtin.buildertools.utils; + +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import java.util.Random; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class Material { + public static final Material EMPTY = new Material(0, 0, (byte)0, 0); + private final int blockId; + private final int fluidId; + private final byte fluidLevel; + private final int rotation; + + private Material(int blockId, int fluidId, byte fluidLevel, int rotation) { + this.blockId = blockId; + this.fluidId = fluidId; + this.fluidLevel = fluidLevel; + this.rotation = rotation; + } + + @Nonnull + public static Material block(int blockId) { + return block(blockId, 0); + } + + @Nonnull + public static Material block(int blockId, int rotation) { + return blockId == 0 ? EMPTY : new Material(blockId, 0, (byte)0, rotation); + } + + @Nonnull + public static Material fluid(int fluidId, byte fluidLevel) { + return fluidId == 0 ? EMPTY : new Material(0, fluidId, fluidLevel, 0); + } + + @Nullable + public static Material fromKey(@Nonnull String key) { + if (key.equalsIgnoreCase("empty")) { + return EMPTY; + } else { + BlockPattern.BlockEntry blockEntry = BlockPattern.tryParseBlockTypeKey(key); + if (blockEntry != null) { + FluidPatternHelper.FluidInfo fluidInfo = FluidPatternHelper.getFluidInfo(blockEntry.blockTypeKey()); + if (fluidInfo != null) { + return fluid(fluidInfo.fluidId(), fluidInfo.fluidLevel()); + } + + int blockId = BlockType.getAssetMap().getIndex(blockEntry.blockTypeKey()); + if (blockId != Integer.MIN_VALUE) { + return block(blockId, blockEntry.rotation()); + } + } + + FluidPatternHelper.FluidInfo fluidInfox = FluidPatternHelper.getFluidInfo(key); + if (fluidInfox != null) { + return fluid(fluidInfox.fluidId(), fluidInfox.fluidLevel()); + } else { + int blockId = BlockType.getAssetMap().getIndex(key); + return blockId != Integer.MIN_VALUE ? block(blockId) : null; + } + } + } + + public boolean isFluid() { + return this.fluidId != 0; + } + + public boolean isBlock() { + return this.blockId != 0 && this.fluidId == 0; + } + + public boolean isEmpty() { + return this.blockId == 0 && this.fluidId == 0; + } + + public int getBlockId() { + return this.blockId; + } + + public int getFluidId() { + return this.fluidId; + } + + public byte getFluidLevel() { + return this.fluidLevel; + } + + public int getRotation() { + return this.rotation; + } + + public boolean hasRotation() { + return this.rotation != 0; + } + + @Override + public String toString() { + if (this.isEmpty()) { + return "Material[empty]"; + } else if (this.isFluid()) { + Fluid fluid = Fluid.getAssetMap().getAsset(this.fluidId); + return "Material[fluid=" + (fluid != null ? fluid.getId() : this.fluidId) + ", level=" + this.fluidLevel + "]"; + } else { + BlockType block = BlockType.getAssetMap().getAsset(this.blockId); + String rotStr = this.hasRotation() ? ", rotation=" + RotationTuple.get(this.rotation) : ""; + return "Material[block=" + (block != null ? block.getId() : this.blockId) + rotStr + "]"; + } + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Material other) + ? false + : this.blockId == other.blockId && this.fluidId == other.fluidId && this.fluidLevel == other.fluidLevel && this.rotation == other.rotation; + } + } + + @Override + public int hashCode() { + return 31 * (31 * (31 * this.blockId + this.fluidId) + this.fluidLevel) + this.rotation; + } + + @Nonnull + public static Material fromPattern(@Nonnull BlockPattern pattern, @Nonnull Random random) { + BlockPattern.BlockEntry blockEntry = pattern.nextBlockTypeKey(random); + if (blockEntry != null) { + FluidPatternHelper.FluidInfo fluidInfo = FluidPatternHelper.getFluidInfo(blockEntry.blockTypeKey()); + if (fluidInfo != null) { + return fluid(fluidInfo.fluidId(), fluidInfo.fluidLevel()); + } + + int blockId = BlockType.getAssetMap().getIndex(blockEntry.blockTypeKey()); + if (blockId != Integer.MIN_VALUE) { + return block(blockId, blockEntry.rotation()); + } + } + + return block(pattern.nextBlock(random)); + } +} diff --git a/src/com/hypixel/hytale/builtin/buildertools/utils/RecursivePrefabLoader.java b/src/com/hypixel/hytale/builtin/buildertools/utils/RecursivePrefabLoader.java new file mode 100644 index 0000000..7b6d3e6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/buildertools/utils/RecursivePrefabLoader.java @@ -0,0 +1,184 @@ +package com.hypixel.hytale.builtin.buildertools.utils; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.modules.prefabspawner.PrefabSpawnerState; +import com.hypixel.hytale.server.core.prefab.PrefabLoadException; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import com.hypixel.hytale.server.core.prefab.PrefabWeights; +import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabLoader; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class RecursivePrefabLoader implements BiFunction { + private static final int MAX_RECURSION_DEPTH = 10; + protected final Path rootPrefabsDir; + protected final Function prefabsLoader; + protected final Set visitedFiles = new HashSet<>(); + @Nullable + protected final ComponentType prefabComponentType = BlockStateModule.get().getComponentType(PrefabSpawnerState.class); + private int depthTracker = 0; + + public RecursivePrefabLoader(Path rootPrefabsDir, Function prefabsLoader) { + this.rootPrefabsDir = rootPrefabsDir; + this.prefabsLoader = prefabsLoader; + } + + @Nonnull + public T apply(@Nonnull String name, @Nonnull Random random) { + return this.load(name, random); + } + + @Nonnull + public T load(@Nonnull String name, @Nonnull Random random) { + return this.load(0, 0, 0, name, PrefabRotation.ROTATION_0, PrefabWeights.NONE, random); + } + + @Nonnull + protected T load(int x, int y, int z, @Nonnull String name, PrefabRotation rotation, @Nonnull PrefabWeights weights, @Nonnull Random random) { + if (this.depthTracker >= 10) { + throw new PrefabLoadException(PrefabLoadException.Type.NOT_FOUND, "Prefab nesting limit exceeded!"); + } else { + Object var9; + try { + this.depthTracker++; + RecursivePrefabLoader.DistinctCollector prefabs = new RecursivePrefabLoader.DistinctCollector<>(); + PrefabLoader.resolvePrefabs(this.rootPrefabsDir, stripSuffix(name), prefabs); + if (prefabs.isEmpty()) { + throw new PrefabLoadException(PrefabLoadException.Type.NOT_FOUND, "Could not locate prefab: " + name); + } + + if (prefabs.size() == 1) { + return this.loadSinglePrefab(x, y, z, prefabs.getFirst(), rotation, random); + } + + if (weights.size() <= 0) { + return this.loadRandomPrefab(x, y, z, prefabs, rotation, random); + } + + var9 = this.loadWeightedPrefab(x, y, z, name, prefabs, rotation, weights, random); + } catch (IOException var13) { + throw new PrefabLoadException(PrefabLoadException.Type.ERROR, var13); + } finally { + this.depthTracker--; + } + + return (T)var9; + } + } + + protected T loadSinglePrefab(int x, int y, int z, @Nonnull Path file, PrefabRotation rotation, Random random) { + if (!this.visitedFiles.add(file)) { + throw new PrefabLoadException(PrefabLoadException.Type.ERROR, "Cyclic prefab dependency detected: " + file); + } else { + Object var8; + try { + String path = this.rootPrefabsDir.relativize(file).toString(); + var8 = this.loadPrefab(x, y, z, appendSuffix(path), rotation, random); + } finally { + this.visitedFiles.remove(file); + } + + return (T)var8; + } + } + + protected T loadWeightedPrefab( + int x, int y, int z, @Nonnull String name, @Nonnull List files, PrefabRotation rotation, @Nonnull PrefabWeights weights, @Nonnull Random random + ) { + Path[] prefabs = files.toArray(Path[]::new); + Path prefab = weights.get(prefabs, path -> PrefabLoader.resolveRelativeJsonPath(name, path, this.rootPrefabsDir), random); + if (prefab != null) { + return this.loadSinglePrefab(x, y, z, prefab, rotation, random); + } else { + throw new PrefabLoadException(PrefabLoadException.Type.ERROR, String.format("Unable to pick weighted prefab! Files: %s, Weights: %s", files, weights)); + } + } + + protected T loadRandomPrefab(int x, int y, int z, @Nonnull List files, PrefabRotation rotation, @Nonnull Random random) { + Path file = files.get(random.nextInt(files.size())); + return this.loadSinglePrefab(x, y, z, file, rotation, random); + } + + protected abstract T loadPrefab(int var1, int var2, int var3, String var4, PrefabRotation var5, Random var6); + + @Nonnull + private static String stripSuffix(@Nonnull String path) { + return path.replace(".prefab.json", ""); + } + + @Nonnull + private static String appendSuffix(@Nonnull String path) { + return path.endsWith(".prefab.json") ? path : path + ".prefab.json"; + } + + public static class BlockSelectionLoader extends RecursivePrefabLoader { + public BlockSelectionLoader(Path rootPrefabsDir, @Nonnull Function prefabsLoader) { + super(rootPrefabsDir, prefabsLoader.andThen(BlockSelection::cloneSelection)); + } + + @Nonnull + protected BlockSelection loadPrefab(int x, int y, int z, String file, @Nonnull PrefabRotation rotation, @Nonnull Random random) { + BlockSelection prefab = this.prefabsLoader.apply(file); + prefab.setPosition(x, y, z); + List children = new ObjectArrayList<>(); + prefab.forEachBlock((dx, dy, dz, block) -> { + Holder state = block.holder(); + if (state != null) { + PrefabSpawnerState spawner = state.getComponent(this.prefabComponentType); + if (spawner != null) { + BlockType blockType = BlockType.getAssetMap().getAsset(block.blockId()); + int childX = x + rotation.getX(dx, dz); + int childY = y + dy; + int childZ = z + rotation.getZ(dx, dz); + String childPath = spawner.getPrefabPath(); + PrefabWeights childWeights = spawner.getPrefabWeights(); + PrefabRotation childRotation = rotation.add(getRotation(blockType)); + BlockSelection child = (BlockSelection)this.load(childX, childY, childZ, childPath, childRotation, childWeights, random); + children.add(child); + } + } + }); + + for (int i = 0; i < children.size(); i++) { + prefab.add(children.get(i)); + } + + return prefab; + } + + @Nonnull + private static PrefabRotation getRotation(@Nonnull BlockType blockType) { + Rotation rotation = blockType.getRotationYawPlacementOffset(); + return rotation == null ? PrefabRotation.ROTATION_0 : PrefabRotation.fromRotation(rotation); + } + } + + protected static class DistinctCollector extends ArrayList implements Consumer { + protected DistinctCollector() { + } + + @Override + public void accept(T t) { + if (!this.contains(t)) { + this.add(t); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/commandmacro/EchoCommand.java b/src/com/hypixel/hytale/builtin/commandmacro/EchoCommand.java new file mode 100644 index 0000000..3703f08 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/commandmacro/EchoCommand.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.builtin.commandmacro; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import javax.annotation.Nonnull; + +public class EchoCommand extends CommandBase { + private final RequiredArg messageArg = this.withRequiredArg("message", "The message to send to the user of this command", ArgTypes.STRING); + + public EchoCommand() { + super("echo", "Echos the text you input to the user"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + context.sender().sendMessage(Message.raw(this.messageArg.get(context).replace("\"", ""))); + } +} diff --git a/src/com/hypixel/hytale/builtin/commandmacro/MacroCommandBase.java b/src/com/hypixel/hytale/builtin/commandmacro/MacroCommandBase.java new file mode 100644 index 0000000..ce5cb7a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/commandmacro/MacroCommandBase.java @@ -0,0 +1,166 @@ +package com.hypixel.hytale.builtin.commandmacro; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandManager; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import com.hypixel.hytale.server.core.command.system.arguments.system.AbstractOptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.Argument; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.console.ConsoleSender; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MacroCommandBase extends AbstractAsyncCommand { + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final Pattern regexBracketPattern = Pattern.compile("\\{(.*?)}"); + private final Map> arguments = new Object2ObjectOpenHashMap<>(); + private final List>> commandReplacements = new ObjectArrayList<>(); + private final Map defaultValueStrings = new Object2ObjectOpenHashMap<>(); + + public MacroCommandBase( + @Nonnull String name, @Nullable String[] aliases, @Nonnull String description, @Nullable MacroCommandParameter[] parameters, @Nonnull String[] commands + ) { + super(name, description); + if (aliases != null) { + this.addAliases(aliases); + } + + if (parameters != null) { + ParseResult parseResult = new ParseResult(); + + for (MacroCommandParameter parameter : parameters) { + this.arguments + .put( + parameter.getName(), + switch (parameter.getRequirement()) { + case REQUIRED -> this.withRequiredArg(parameter.getName(), parameter.getDescription(), parameter.getArgumentType().getArgumentType()); + case OPTIONAL -> this.withOptionalArg(parameter.getName(), parameter.getDescription(), parameter.getArgumentType().getArgumentType()); + case FLAG -> this.withFlagArg(parameter.getName(), parameter.getDescription()); + case DEFAULT -> this.withDefaultArg( + parameter.getName(), + parameter.getDescription(), + parameter.getArgumentType().getArgumentType(), + parameter.getDefaultValue(), + parameter.getDefaultValueDescription(), + parseResult + ); + default -> throw new IllegalStateException("Unexpected value for Requirement: " + parameter.getRequirement()); + } + ); + } + + if (parseResult.failed()) { + parseResult.sendMessages(ConsoleSender.INSTANCE); + return; + } + } + + Matcher matcher = regexBracketPattern.matcher(""); + + for (int i = 0; i < commands.length; i++) { + String command = commands[i]; + ObjectArrayList replacements = new ObjectArrayList<>(); + Matcher reset = matcher.reset(command); + + while (reset.find()) { + String result = reset.group(1); + String[] splitByColons = result.split(":"); + if (command.charAt(matcher.start(1) - 2) != '\\') { + String replacementSubstring = command.substring(matcher.start(1) - 1, matcher.end(1) + 1); + + MacroCommandReplacement replacement = switch (splitByColons.length) { + case 1 -> new MacroCommandReplacement(result, replacementSubstring); + case 2 -> new MacroCommandReplacement(splitByColons[1], replacementSubstring, splitByColons[0]); + default -> throw new IllegalArgumentException("Cannot have more than one colon in a macro command parameter: '" + result + "'"); + }; + if (!this.arguments.containsKey(replacement.getNameOfReplacingArg())) { + throw new IllegalArgumentException( + "Cannot define command with replacement token that does not refer to an argument: " + replacement.getNameOfReplacingArg() + ); + } + + replacements.add(replacement); + } + } + + command = command.replaceAll("\\\\\\{", "{"); + commands[i] = command; + this.commandReplacements.add(Pair.of(command, replacements)); + } + } + + @Nullable + private Argument withDefaultArg( + String name, + String description, + @Nonnull ArgumentType argumentType, + @Nonnull String defaultValue, + String defaultValueDescription, + @Nonnull ParseResult parseResult + ) { + D parsedData = argumentType.parse(defaultValue.split(" "), parseResult); + if (parseResult.failed()) { + LOGGER.at(Level.WARNING).log("Could not parse default argument value for argument: '" + name + "' on Macro Command: '" + this.getName() + "'."); + parseResult.sendMessages(ConsoleSender.INSTANCE); + return null; + } else { + this.defaultValueStrings.put(name, defaultValue); + return this.withDefaultArg(name, description, argumentType, parsedData, defaultValueDescription); + } + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + List commandsToExecute = new ObjectArrayList<>(); + CommandSender commandSender = context.sender(); + String macro = context.getCalledCommand().getName(); + LOGGER.at(Level.INFO).log("%s expanding command macro: %s", commandSender.getDisplayName(), macro); + + for (Pair> stringListPair : this.commandReplacements) { + String command = stringListPair.key(); + + for (MacroCommandReplacement replacement : stringListPair.value()) { + String stringToInject = ""; + boolean shouldInject = true; + Argument, ?> argument = (Argument, ?>)this.arguments.get(replacement.getNameOfReplacingArg()); + if (!(argument instanceof AbstractOptionalArg) || context.provided(argument)) { + stringToInject = String.join(" ", context.getInput(this.arguments.get(replacement.getNameOfReplacingArg()))); + } else if (argument instanceof DefaultArg) { + stringToInject = this.defaultValueStrings.get(argument.getName()); + } else { + shouldInject = false; + } + + if (shouldInject && replacement.getOptionalArgumentKey() != null) { + stringToInject = replacement.getOptionalArgumentKey() + stringToInject; + } + + command = command.replace(replacement.getStringToReplaceWithValue(), shouldInject ? stringToInject : ""); + } + + commandsToExecute.add(command); + } + + CompletableFuture completableFuture = CompletableFuture.completedFuture(null); + + for (String command : commandsToExecute) { + completableFuture = completableFuture.thenCompose(VOID -> CommandManager.get().handleCommand(commandSender, command)); + } + + return completableFuture; + } +} diff --git a/src/com/hypixel/hytale/builtin/commandmacro/MacroCommandBuilder.java b/src/com/hypixel/hytale/builtin/commandmacro/MacroCommandBuilder.java new file mode 100644 index 0000000..6b98e79 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/commandmacro/MacroCommandBuilder.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.builtin.commandmacro; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.server.core.command.system.CommandRegistration; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MacroCommandBuilder implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + MacroCommandBuilder.class, + MacroCommandBuilder::new, + Codec.STRING, + (builder, id) -> builder.id = id, + builder -> builder.id, + (builder, data) -> builder.data = data, + builder -> builder.data + ) + .append(new KeyedCodec<>("Name", Codec.STRING, true), (builder, name) -> builder.name = name, builder -> builder.name) + .add() + .append(new KeyedCodec<>("Aliases", Codec.STRING_ARRAY, false), (builder, aliases) -> builder.aliases = aliases, builder -> builder.aliases) + .add() + .append(new KeyedCodec<>("Description", Codec.STRING, true), (builder, description) -> builder.description = description, builder -> builder.description) + .add() + .append( + new KeyedCodec<>("Parameters", new ArrayCodec<>(MacroCommandParameter.CODEC, MacroCommandParameter[]::new), false), + (builder, parameters) -> builder.parameters = parameters, + builder -> builder.parameters + ) + .add() + .append(new KeyedCodec<>("Commands", Codec.STRING_ARRAY, true), (builder, commands) -> builder.commands = commands, builder -> builder.commands) + .add() + .build(); + private String id; + private String name; + private String[] aliases; + private String description; + private MacroCommandParameter[] parameters; + private String[] commands; + private AssetExtraInfo.Data data; + + public MacroCommandBuilder() { + } + + @Nullable + public static CommandRegistration createAndRegisterCommand(@Nonnull MacroCommandBuilder builder) { + if (builder.name == null) { + return null; + } else { + MacroCommandBase macroCommandBase = new MacroCommandBase(builder.name, builder.aliases, builder.description, builder.parameters, builder.commands); + return MacroCommandPlugin.get().getCommandRegistry().registerCommand(macroCommandBase); + } + } + + public String getName() { + return this.name; + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/builtin/commandmacro/MacroCommandParameter.java b/src/com/hypixel/hytale/builtin/commandmacro/MacroCommandParameter.java new file mode 100644 index 0000000..573cb72 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/commandmacro/MacroCommandParameter.java @@ -0,0 +1,130 @@ +package com.hypixel.hytale.builtin.commandmacro; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgumentType; + +public class MacroCommandParameter { + public static final BuilderCodec CODEC = BuilderCodec.builder(MacroCommandParameter.class, MacroCommandParameter::new) + .append(new KeyedCodec<>("Name", Codec.STRING, true), (macroParameter, name) -> macroParameter.name = name, macroParameter -> macroParameter.name) + .add() + .append( + new KeyedCodec<>("Description", Codec.STRING, true), + (macroParameter, description) -> macroParameter.description = description, + macroParameter -> macroParameter.description + ) + .add() + .append( + new KeyedCodec<>("Requirement", new EnumCodec<>(MacroCommandParameter.ParameterRequirement.class), true), + (macroParameter, requirement) -> macroParameter.requirement = requirement, + macroParameter -> macroParameter.requirement + ) + .add() + .append( + new KeyedCodec<>("ArgType", new EnumCodec<>(MacroCommandParameter.ArgumentTypeEnum.class)), + (macroParameter, argumentType) -> macroParameter.argumentType = argumentType, + macroParameter -> macroParameter.argumentType + ) + .add() + .append( + new KeyedCodec<>("DefaultValue", Codec.STRING), + (macroParameter, defaultValue) -> macroParameter.defaultValue = defaultValue, + macroParameter -> macroParameter.defaultValue + ) + .add() + .append( + new KeyedCodec<>("DefaultValueDescription", Codec.STRING), + (macroParameter, defaultValueDescription) -> macroParameter.defaultValueDescription = defaultValueDescription, + macroParameter -> macroParameter.defaultValueDescription + ) + .add() + .build(); + private String name; + private String description; + private MacroCommandParameter.ParameterRequirement requirement; + private MacroCommandParameter.ArgumentTypeEnum argumentType; + private String defaultValue; + private String defaultValueDescription; + + public MacroCommandParameter() { + } + + public MacroCommandParameter.ParameterRequirement getRequirement() { + return this.requirement; + } + + public String getName() { + return this.name; + } + + public String getDescription() { + return this.description; + } + + public MacroCommandParameter.ArgumentTypeEnum getArgumentType() { + return this.argumentType; + } + + public String getDefaultValue() { + return this.defaultValue; + } + + public String getDefaultValueDescription() { + return this.defaultValueDescription; + } + + public static enum ArgumentTypeEnum { + BOOLEAN(ArgTypes.BOOLEAN), + INTEGER(ArgTypes.INTEGER), + STRING(ArgTypes.STRING), + FLOAT(ArgTypes.FLOAT), + DOUBLE(ArgTypes.DOUBLE), + UUID(ArgTypes.UUID), + RELATIVE_DOUBLE_COORD(ArgTypes.RELATIVE_DOUBLE_COORD), + RELATIVE_INT_COORD(ArgTypes.RELATIVE_INT_COORD), + RELATIVE_INTEGER(ArgTypes.RELATIVE_INTEGER), + INT_RANGE(ArgTypes.INT_RANGE), + RELATIVE_INT_RANGE(ArgTypes.RELATIVE_INT_RANGE), + VECTOR3I(ArgTypes.VECTOR3I), + RELATIVE_VECTOR3I(ArgTypes.RELATIVE_VECTOR3I), + BLOCK_ID(ArgTypes.BLOCK_ID), + WEIGHTED_BLOCK_TYPE(ArgTypes.WEIGHTED_BLOCK_TYPE), + BLOCK_PATTERN(ArgTypes.BLOCK_PATTERN), + BLOCK_MASK(ArgTypes.BLOCK_MASK), + WORLD(ArgTypes.WORLD), + RELATIVE_BLOCK_POSITION(ArgTypes.RELATIVE_BLOCK_POSITION), + RELATIVE_POSITION(ArgTypes.RELATIVE_POSITION), + ROTATION(ArgTypes.ROTATION), + MODEL_ASSET(ArgTypes.MODEL_ASSET), + WEATHER_ASSET(ArgTypes.WEATHER_ASSET), + INTERACTION_ASSET(ArgTypes.INTERACTION_ASSET), + EFFECT_ASSET(ArgTypes.EFFECT_ASSET), + ENVIRONMENT_ASSET(ArgTypes.ENVIRONMENT_ASSET), + ITEM_ASSET(ArgTypes.ITEM_ASSET), + BLOCK_TYPE_ASSET(ArgTypes.BLOCK_TYPE_ASSET), + BLOCK_TYPE_KEY(ArgTypes.BLOCK_TYPE_KEY); + + private final ArgumentType argumentType; + + private ArgumentTypeEnum(ArgumentType argumentType) { + this.argumentType = argumentType; + } + + public ArgumentType getArgumentType() { + return this.argumentType; + } + } + + public static enum ParameterRequirement { + REQUIRED, + OPTIONAL, + DEFAULT, + FLAG; + + private ParameterRequirement() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/commandmacro/MacroCommandPlugin.java b/src/com/hypixel/hytale/builtin/commandmacro/MacroCommandPlugin.java new file mode 100644 index 0000000..0993f28 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/commandmacro/MacroCommandPlugin.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.builtin.commandmacro; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.command.system.CommandRegistration; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class MacroCommandPlugin extends JavaPlugin { + private static MacroCommandPlugin instance; + private final Map macroCommandRegistrations = new Object2ObjectOpenHashMap<>(); + + public static MacroCommandPlugin get() { + return instance; + } + + public MacroCommandPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + instance = this; + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + MacroCommandBuilder.class, new DefaultAssetMap() + ) + .setPath("MacroCommands")) + .setKeyFunction(MacroCommandBuilder::getId)) + .setCodec(MacroCommandBuilder.CODEC)) + .build() + ); + this.getEventRegistry().register(LoadedAssetsEvent.class, MacroCommandBuilder.class, this::loadCommandMacroAsset); + this.getCommandRegistry().registerCommand(new WaitCommand()); + this.getCommandRegistry().registerCommand(new EchoCommand()); + } + + public void loadCommandMacroAsset(@Nonnull LoadedAssetsEvent> event) { + for (MacroCommandBuilder value : event.getLoadedAssets().values()) { + if (this.macroCommandRegistrations.containsKey(value.getName())) { + this.macroCommandRegistrations.get(value.getName()).unregister(); + } + + CommandRegistration commandRegistration = MacroCommandBuilder.createAndRegisterCommand(value); + if (commandRegistration != null) { + this.macroCommandRegistrations.put(value.getName(), commandRegistration); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/commandmacro/MacroCommandReplacement.java b/src/com/hypixel/hytale/builtin/commandmacro/MacroCommandReplacement.java new file mode 100644 index 0000000..b6a01fa --- /dev/null +++ b/src/com/hypixel/hytale/builtin/commandmacro/MacroCommandReplacement.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.commandmacro; + +import javax.annotation.Nullable; + +public class MacroCommandReplacement { + private final String nameOfReplacingArg; + @Nullable + private final String optionalArgumentKey; + private final String stringToReplaceWithValue; + + public MacroCommandReplacement(String nameOfReplacingArg, String stringToReplaceWithValue, @Nullable String optionalArgumentKey) { + this.nameOfReplacingArg = nameOfReplacingArg; + this.stringToReplaceWithValue = stringToReplaceWithValue; + this.optionalArgumentKey = optionalArgumentKey == null ? null : "--" + optionalArgumentKey + (optionalArgumentKey.endsWith("=") ? "" : " "); + } + + public MacroCommandReplacement(String replacementKey, String stringToReplaceWithValue) { + this(replacementKey, stringToReplaceWithValue, null); + } + + public String getNameOfReplacingArg() { + return this.nameOfReplacingArg; + } + + @Nullable + public String getOptionalArgumentKey() { + return this.optionalArgumentKey; + } + + public String getStringToReplaceWithValue() { + return this.stringToReplaceWithValue; + } +} diff --git a/src/com/hypixel/hytale/builtin/commandmacro/WaitCommand.java b/src/com/hypixel/hytale/builtin/commandmacro/WaitCommand.java new file mode 100644 index 0000000..348fe1b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/commandmacro/WaitCommand.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.builtin.commandmacro; + +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + +public class WaitCommand extends AbstractAsyncCommand { + private static final long MILLISECONDS_TO_SECONDS_MULTIPLIER = 1000L; + public static final Runnable EMPTY_RUNNABLE = () -> {}; + private final RequiredArg timeArg = this.withRequiredArg("time", "server.commands.wait.arg.time", ArgTypes.FLOAT) + .addValidator(Validators.greaterThan(0.0F)) + .addValidator(Validators.lessThan(1000.0F)); + private final FlagArg printArg = this.withFlagArg("print", "server.commands.wait.arg.print"); + + public WaitCommand() { + super("wait", "server.commands.wait.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + CommandSender sender = context.sender(); + Runnable runnable = this.printArg.get(context) ? () -> sender.sendMessage(Message.translation("server.commands.wait.complete")) : EMPTY_RUNNABLE; + return CompletableFuture.runAsync(runnable, CompletableFuture.delayedExecutor((long)(this.timeArg.get(context) * 1000.0F), TimeUnit.MILLISECONDS)); + } +} diff --git a/src/com/hypixel/hytale/builtin/crafting/BenchRecipeRegistry.java b/src/com/hypixel/hytale/builtin/crafting/BenchRecipeRegistry.java new file mode 100644 index 0000000..86f5ead --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crafting/BenchRecipeRegistry.java @@ -0,0 +1,176 @@ +package com.hypixel.hytale.builtin.crafting; + +import com.hypixel.hytale.protocol.BenchRequirement; +import com.hypixel.hytale.protocol.ItemResourceType; +import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.MaterialQuantity; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BenchRecipeRegistry { + private final String benchId; + private final Map> categoryMap = new Object2ObjectOpenHashMap<>(); + private final Map> itemToIncomingRecipe = new Object2ObjectOpenHashMap<>(); + private final Set uncategorizedRecipes = new ObjectOpenHashSet<>(); + private final Set allMaterialIds = new ObjectOpenHashSet<>(); + private final Set allMaterialResourceType = new ObjectOpenHashSet<>(); + + public BenchRecipeRegistry(String benchId) { + this.benchId = benchId; + } + + public Iterable getIncomingRecipesForItem(@Nonnull String itemId) { + Set recipes = this.itemToIncomingRecipe.get(itemId); + return recipes == null ? Collections.emptySet() : recipes; + } + + public void removeRecipe(@Nonnull String id) { + this.uncategorizedRecipes.remove(id); + + for (Entry> entry : this.categoryMap.entrySet()) { + entry.getValue().remove(id); + } + } + + public void addRecipe(@Nonnull BenchRequirement benchRequirement, @Nonnull CraftingRecipe recipe) { + if (benchRequirement.categories != null && benchRequirement.categories.length != 0) { + for (String category : benchRequirement.categories) { + this.categoryMap.computeIfAbsent(category, k -> new ObjectOpenHashSet<>()).add(recipe.getId()); + } + } else { + this.uncategorizedRecipes.add(recipe.getId()); + } + } + + public CraftingRecipe[] getAllRecipes() { + Set allRecipeIds = new ObjectOpenHashSet<>(this.uncategorizedRecipes); + + for (Set recipes : this.categoryMap.values()) { + allRecipeIds.addAll(recipes); + } + + List allRecipes = new ObjectArrayList<>(allRecipeIds.size()); + + for (String recipeId : allRecipeIds) { + CraftingRecipe recipe = CraftingRecipe.getAssetMap().getAsset(recipeId); + if (recipe != null) { + allRecipes.add(recipe); + } + } + + return allRecipes.toArray(CraftingRecipe[]::new); + } + + @Nullable + public Set getRecipesForCategory(@Nonnull String benchCategoryId) { + return this.categoryMap.get(benchCategoryId); + } + + public void recompute() { + this.allMaterialIds.clear(); + this.allMaterialResourceType.clear(); + this.itemToIncomingRecipe.clear(); + + for (Set recipes : this.categoryMap.values()) { + this.extractMaterialFromRecipes(recipes); + } + + this.extractMaterialFromRecipes(this.uncategorizedRecipes); + } + + private void extractMaterialFromRecipes(Set recipes) { + for (String recipeId : recipes) { + CraftingRecipe recipe = CraftingRecipe.getAssetMap().getAsset(recipeId); + if (recipe != null) { + BenchRequirement[] benchRequirements = recipe.getBenchRequirement(); + if (benchRequirements != null) { + boolean matchesRegistry = false; + + for (BenchRequirement requirement : benchRequirements) { + if (requirement.id.equals(this.benchId)) { + matchesRegistry = true; + break; + } + } + + if (matchesRegistry) { + for (MaterialQuantity material : recipe.getInput()) { + if (material.getItemId() != null) { + this.allMaterialIds.add(material.getItemId()); + } + + if (material.getResourceTypeId() != null) { + this.allMaterialResourceType.add(material.getResourceTypeId()); + } + } + + for (MaterialQuantity output : recipe.getOutputs()) { + this.itemToIncomingRecipe.computeIfAbsent(output.getItemId(), k -> new ObjectOpenHashSet<>()).add(recipeId); + } + } + } + } + } + } + + public boolean isValidCraftingMaterial(@Nonnull ItemStack itemStack) { + if (this.allMaterialIds.contains(itemStack.getItemId())) { + return true; + } else { + ItemResourceType[] resourceTypeId = itemStack.getItem().getResourceTypes(); + if (resourceTypeId != null) { + for (ItemResourceType resTypeId : resourceTypeId) { + if (this.allMaterialResourceType.contains(resTypeId.id)) { + return true; + } + } + } + + return false; + } + } + + @Override + public boolean equals(Object o) { + if (o != null && this.getClass() == o.getClass()) { + BenchRecipeRegistry that = (BenchRecipeRegistry)o; + return Objects.equals(this.benchId, that.benchId) + && Objects.equals(this.categoryMap, that.categoryMap) + && Objects.equals(this.uncategorizedRecipes, that.uncategorizedRecipes) + && Objects.equals(this.allMaterialIds, that.allMaterialIds) + && Objects.equals(this.allMaterialResourceType, that.allMaterialResourceType); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.benchId, this.categoryMap, this.uncategorizedRecipes, this.allMaterialIds, this.allMaterialResourceType); + } + + @Override + public String toString() { + return "BenchRecipeRegistry{benchId='" + + this.benchId + + "', categoryMap=" + + this.categoryMap + + ", uncategorizedRecipes=" + + this.uncategorizedRecipes + + ", allMaterialIds=" + + this.allMaterialIds + + ", allMaterialResourceType=" + + this.allMaterialResourceType + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/crafting/CraftingPlugin.java b/src/com/hypixel/hytale/builtin/crafting/CraftingPlugin.java new file mode 100644 index 0000000..631b16b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crafting/CraftingPlugin.java @@ -0,0 +1,347 @@ +package com.hypixel.hytale.builtin.crafting; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.assetstore.event.RemovedAssetsEvent; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.builtin.crafting.commands.RecipeCommand; +import com.hypixel.hytale.builtin.crafting.component.CraftingManager; +import com.hypixel.hytale.builtin.crafting.interaction.LearnRecipeInteraction; +import com.hypixel.hytale.builtin.crafting.interaction.OpenBenchPageInteraction; +import com.hypixel.hytale.builtin.crafting.interaction.OpenProcessingBenchInteraction; +import com.hypixel.hytale.builtin.crafting.state.BenchState; +import com.hypixel.hytale.builtin.crafting.state.ProcessingBenchState; +import com.hypixel.hytale.builtin.crafting.system.PlayerCraftingSystems; +import com.hypixel.hytale.builtin.crafting.window.FieldCraftingWindow; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.protocol.BenchRequirement; +import com.hypixel.hytale.protocol.BenchType; +import com.hypixel.hytale.protocol.ItemResourceType; +import com.hypixel.hytale.protocol.packets.interface_.UpdateKnownRecipes; +import com.hypixel.hytale.protocol.packets.window.WindowType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.Bench; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.BenchUpgradeRequirement; +import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.command.system.CommandManager; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData; +import com.hypixel.hytale.server.core.entity.entities.player.windows.Window; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.MaterialQuantity; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.meta.BlockStateRegistry; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CraftingPlugin extends JavaPlugin { + private static CraftingPlugin instance; + private static final Map registries = new Object2ObjectOpenHashMap<>(); + private static final Map itemGeneratedRecipes = new Object2ObjectOpenHashMap<>(); + private ComponentType craftingManagerComponentType; + + public CraftingPlugin(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Nullable + public static Set getAvailableRecipesForCategory(String benchId, String benchCategoryId) { + BenchRecipeRegistry benchRecipeRegistry = registries.get(benchId); + return benchRecipeRegistry == null ? null : benchRecipeRegistry.getRecipesForCategory(benchCategoryId); + } + + public static boolean isValidCraftingMaterialForBench(BenchState benchState, ItemStack itemStack) { + BenchRecipeRegistry benchRecipeRegistry = registries.get(benchState.getBench().getId()); + return benchRecipeRegistry == null ? false : benchRecipeRegistry.isValidCraftingMaterial(itemStack); + } + + public static boolean isValidUpgradeMaterialForBench(BenchState benchState, ItemStack itemStack) { + BenchUpgradeRequirement nextLevelUpgradeMaterials = benchState.getNextLevelUpgradeMaterials(); + if (nextLevelUpgradeMaterials == null) { + return false; + } else { + for (MaterialQuantity upgradeMaterial : nextLevelUpgradeMaterials.getInput()) { + if (itemStack.getItemId().equals(upgradeMaterial.getItemId())) { + return true; + } + + ItemResourceType[] resourceTypeId = itemStack.getItem().getResourceTypes(); + if (resourceTypeId != null) { + for (ItemResourceType resTypeId : resourceTypeId) { + if (resTypeId.id.equals(upgradeMaterial.getResourceTypeId())) { + return true; + } + } + } + } + + return false; + } + } + + @Override + protected void setup() { + AssetRegistry.getAssetStore(Interaction.class) + .loadAssets( + "Hytale:Hytale", + List.of(OpenBenchPageInteraction.SIMPLE_CRAFTING, OpenBenchPageInteraction.DIAGRAM_CRAFTING, OpenBenchPageInteraction.STRUCTURAL_CRAFTING) + ); + AssetRegistry.getAssetStore(RootInteraction.class) + .loadAssets( + "Hytale:Hytale", + List.of( + OpenBenchPageInteraction.SIMPLE_CRAFTING_ROOT, OpenBenchPageInteraction.DIAGRAM_CRAFTING_ROOT, OpenBenchPageInteraction.STRUCTURAL_CRAFTING_ROOT + ) + ); + ComponentRegistryProxy entityStoreRegistry = this.getEntityStoreRegistry(); + this.craftingManagerComponentType = entityStoreRegistry.registerComponent(CraftingManager.class, CraftingManager::new); + entityStoreRegistry.registerSystem(new PlayerCraftingSystems.PlayerCraftingSystem(this.craftingManagerComponentType)); + entityStoreRegistry.registerSystem(new PlayerCraftingSystems.CraftingManagerAddSystem(this.craftingManagerComponentType)); + this.getCodecRegistry(Interaction.CODEC) + .register("OpenBenchPage", OpenBenchPageInteraction.class, OpenBenchPageInteraction.CODEC) + .register("OpenProcessingBench", OpenProcessingBenchInteraction.class, OpenProcessingBenchInteraction.CODEC); + Bench.registerRootInteraction(BenchType.Crafting, OpenBenchPageInteraction.SIMPLE_CRAFTING_ROOT); + Bench.registerRootInteraction(BenchType.DiagramCrafting, OpenBenchPageInteraction.DIAGRAM_CRAFTING_ROOT); + Bench.registerRootInteraction(BenchType.StructuralCrafting, OpenBenchPageInteraction.STRUCTURAL_CRAFTING_ROOT); + BlockStateRegistry blockStateRegistry = this.getBlockStateRegistry(); + blockStateRegistry.registerBlockState(ProcessingBenchState.class, "processingBench", ProcessingBenchState.CODEC); + blockStateRegistry.registerBlockState(BenchState.class, "crafting", BenchState.CODEC); + Window.CLIENT_REQUESTABLE_WINDOW_TYPES.put(WindowType.PocketCrafting, FieldCraftingWindow::new); + this.getEventRegistry().register(LoadedAssetsEvent.class, CraftingRecipe.class, CraftingPlugin::onRecipeLoad); + this.getEventRegistry().register(RemovedAssetsEvent.class, CraftingRecipe.class, CraftingPlugin::onRecipeRemove); + this.getEventRegistry().register(LoadedAssetsEvent.class, Item.class, CraftingPlugin::onItemAssetLoad); + this.getEventRegistry().register(RemovedAssetsEvent.class, Item.class, CraftingPlugin::onItemAssetRemove); + Interaction.CODEC.register("LearnRecipe", LearnRecipeInteraction.class, LearnRecipeInteraction.CODEC); + CommandManager.get().registerSystemCommand(new RecipeCommand()); + entityStoreRegistry.registerSystem(new CraftingPlugin.PlayerAddedSystem()); + } + + private static void onItemAssetLoad(LoadedAssetsEvent> event) { + List recipesToLoad = new ObjectArrayList<>(); + + for (Item item : event.getLoadedAssets().values()) { + if (item.hasRecipesToGenerate()) { + List generatedRecipes = new ObjectArrayList<>(); + item.collectRecipesToGenerate(generatedRecipes); + List generatedIds = new ObjectArrayList<>(); + + for (CraftingRecipe generatedRecipe : generatedRecipes) { + String id = generatedRecipe.getId(); + generatedIds.add(id); + } + + itemGeneratedRecipes.put(item.getId(), generatedIds.toArray(String[]::new)); + recipesToLoad.addAll(generatedRecipes); + } + } + + if (!recipesToLoad.isEmpty()) { + CraftingRecipe.getAssetStore().loadAssets("Hytale:Hytale", recipesToLoad); + } + } + + private static void onItemAssetRemove(@Nonnull RemovedAssetsEvent> event) { + for (String id : event.getRemovedAssets()) { + String[] generatedRecipes = itemGeneratedRecipes.get(id); + if (generatedRecipes != null) { + CraftingRecipe.getAssetStore().removeAssets(List.of(generatedRecipes)); + } + } + } + + private static void onRecipeLoad(LoadedAssetsEvent> event) { + for (CraftingRecipe recipe : event.getLoadedAssets().values()) { + for (BenchRecipeRegistry registry : registries.values()) { + registry.removeRecipe(recipe.getId()); + } + + if (recipe.getBenchRequirement() != null) { + for (BenchRequirement benchRequirement : recipe.getBenchRequirement()) { + BenchRecipeRegistry benchRecipeRegistry = registries.computeIfAbsent(benchRequirement.id, BenchRecipeRegistry::new); + benchRecipeRegistry.addRecipe(benchRequirement, recipe); + } + } + } + + computeBenchRecipeRegistries(); + } + + private static void onRecipeRemove(RemovedAssetsEvent> event) { + for (String removedRecipeId : event.getRemovedAssets()) { + for (BenchRecipeRegistry registry : registries.values()) { + registry.removeRecipe(removedRecipeId); + } + } + + computeBenchRecipeRegistries(); + } + + private static void computeBenchRecipeRegistries() { + for (BenchRecipeRegistry registry : registries.values()) { + registry.recompute(); + } + } + + @Nonnull + public static List getBenchRecipes(@Nonnull Bench bench) { + return getBenchRecipes(bench.getType(), bench.getId()); + } + + @Nonnull + public static List getBenchRecipes(BenchType benchType, String name) { + return getBenchRecipes(benchType, name, null); + } + + @Nonnull + public static List getBenchRecipes(BenchType benchType, String benchId, @Nullable String category) { + BenchRecipeRegistry registry = registries.get(benchId); + if (registry == null) { + return List.of(); + } else { + List list = new ObjectArrayList<>(); + + for (CraftingRecipe recipe : registry.getAllRecipes()) { + BenchRequirement[] benchRequirement = recipe.getBenchRequirement(); + if (benchRequirement != null) { + for (BenchRequirement requirement : benchRequirement) { + if (requirement.type == benchType && requirement.id.equals(benchId) && (category == null || hasCategory(recipe, category))) { + list.add(recipe); + break; + } + } + } + } + + return list; + } + } + + private static boolean hasCategory(@Nonnull CraftingRecipe recipe, String category) { + for (BenchRequirement benchRequirement : recipe.getBenchRequirement()) { + if (benchRequirement.categories != null && ArrayUtil.contains(benchRequirement.categories, category)) { + return true; + } + } + + return false; + } + + public static boolean learnRecipe(@Nonnull Ref ref, @Nonnull String recipeId, @Nonnull ComponentAccessor componentAccessor) { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerConfigData playerConfigData = playerComponent.getPlayerConfigData(); + Set knownRecipes = new HashSet<>(playerConfigData.getKnownRecipes()); + if (knownRecipes.add(recipeId)) { + playerConfigData.setKnownRecipes(knownRecipes); + sendKnownRecipes(ref, componentAccessor); + return true; + } else { + return false; + } + } + + public static boolean forgetRecipe(@Nonnull Ref ref, @Nonnull String itemId, @Nonnull ComponentAccessor componentAccessor) { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerConfigData playerConfigData = playerComponent.getPlayerConfigData(); + Set knownRecipes = new ObjectOpenHashSet<>(playerConfigData.getKnownRecipes()); + if (knownRecipes.remove(itemId)) { + playerConfigData.setKnownRecipes(knownRecipes); + sendKnownRecipes(ref, componentAccessor); + return true; + } else { + return false; + } + } + + public static void sendKnownRecipes(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerConfigData playerConfigData = playerComponent.getPlayerConfigData(); + DefaultAssetMap itemAssetMap = Item.getAssetMap(); + Map knownRecipes = new Object2ObjectOpenHashMap<>(); + + for (String id : playerConfigData.getKnownRecipes()) { + Item item = itemAssetMap.getAsset(id); + if (item != null) { + for (BenchRecipeRegistry registry : registries.values()) { + for (String recipeId : registry.getIncomingRecipesForItem(item.getId())) { + CraftingRecipe recipe = CraftingRecipe.getAssetMap().getAsset(recipeId); + if (recipe != null) { + knownRecipes.put(id, recipe.toPacket(id)); + } + } + } + } + } + + playerRefComponent.getPacketHandler().writeNoCache(new UpdateKnownRecipes(knownRecipes)); + } + + public ComponentType getCraftingManagerComponentType() { + return this.craftingManagerComponentType; + } + + public static CraftingPlugin get() { + return instance; + } + + public static class PlayerAddedSystem extends RefSystem { + private static final Query QUERY = Archetype.of(Player.getComponentType(), PlayerRef.getComponentType()); + + public PlayerAddedSystem() { + } + + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + CraftingPlugin.sendKnownRecipes(ref, commandBuffer); + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/crafting/commands/RecipeCommand.java b/src/com/hypixel/hytale/builtin/crafting/commands/RecipeCommand.java new file mode 100644 index 0000000..1c96d73 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crafting/commands/RecipeCommand.java @@ -0,0 +1,230 @@ +package com.hypixel.hytale.builtin.crafting.commands; + +import com.hypixel.hytale.builtin.crafting.CraftingPlugin; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.awt.Color; +import java.util.Set; +import javax.annotation.Nonnull; + +public class RecipeCommand extends AbstractCommandCollection { + public RecipeCommand() { + super("recipe", "server.commands.recipe.desc"); + this.addSubCommand(new RecipeCommand.Learn()); + this.addSubCommand(new RecipeCommand.Forget()); + this.addSubCommand(new RecipeCommand.List()); + } + + static class Forget extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg itemArg = this.withRequiredArg("item", "server.commands.recipe.forget.item.desc", ArgTypes.ITEM_ASSET); + + Forget() { + super("forget", "server.commands.recipe.forget.desc"); + this.addUsageVariant(new RecipeCommand.Forget.ForgetOther()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Item item = this.itemArg.get(context); + String itemId = item.getId(); + if (CraftingPlugin.forgetRecipe(ref, itemId, store)) { + context.sendMessage(Message.translation("server.commands.recipe.forgotten").param("id", itemId).color(Color.GREEN)); + } else { + context.sendMessage(Message.translation("server.commands.recipe.alreadyNotKnown").param("id", itemId).color(Color.RED)); + } + } + + private static class ForgetOther extends CommandBase { + @Nonnull + private final RequiredArg itemArg = this.withRequiredArg("item", "server.commands.recipe.forget.item.desc", ArgTypes.ITEM_ASSET); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + + ForgetOther() { + super("server.commands.recipe.forget.other.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef targetPlayerRef = this.playerArg.get(context); + Ref ref = targetPlayerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + } else { + Item item = this.itemArg.get(context); + String itemId = item.getId(); + if (CraftingPlugin.forgetRecipe(ref, itemId, store)) { + context.sendMessage( + Message.translation("server.commands.recipe.forgotten.other") + .param("username", targetPlayerRef.getUsername()) + .param("id", itemId) + .color(Color.GREEN) + ); + } else { + context.sendMessage( + Message.translation("server.commands.recipe.alreadyNotKnown.other") + .param("username", targetPlayerRef.getUsername()) + .param("id", itemId) + .color(Color.RED) + ); + } + } + } + ); + } else { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + } + } + } + } + + static class Learn extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg itemArg = this.withRequiredArg("item", "server.commands.recipe.learn.item.desc", ArgTypes.ITEM_ASSET); + + Learn() { + super("learn", "server.commands.recipe.learn.desc"); + this.addUsageVariant(new RecipeCommand.Learn.LearnOther()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Item item = this.itemArg.get(context); + String itemId = item.getId(); + Message itemMessage = Message.translation(item.getTranslationKey()); + if (CraftingPlugin.learnRecipe(ref, itemId, store)) { + context.sendMessage(Message.translation("server.modules.learnrecipe.success").param("name", itemMessage).color(Color.GREEN)); + } else { + context.sendMessage(Message.translation("server.modules.learnrecipe.alreadyKnown").param("name", itemMessage).color(Color.RED)); + } + } + + private static class LearnOther extends CommandBase { + @Nonnull + private final RequiredArg itemArg = this.withRequiredArg("item", "server.commands.recipe.learn.item.desc", ArgTypes.ITEM_ASSET); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + + LearnOther() { + super("server.commands.recipe.learn.other.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef targetPlayerRef = this.playerArg.get(context); + Ref ref = targetPlayerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + } else { + Item item = this.itemArg.get(context); + String itemId = item.getId(); + Message itemMessage = Message.translation(item.getTranslationKey()); + if (CraftingPlugin.learnRecipe(ref, itemId, store)) { + context.sendMessage( + Message.translation("server.commands.recipe.learn.success.other") + .param("username", targetPlayerRef.getUsername()) + .param("name", itemMessage) + .color(Color.GREEN) + ); + } else { + context.sendMessage( + Message.translation("server.commands.recipe.learn.alreadyKnown.other") + .param("username", targetPlayerRef.getUsername()) + .param("name", itemMessage) + .color(Color.RED) + ); + } + } + } + ); + } else { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + } + } + } + } + + static class List extends AbstractPlayerCommand { + List() { + super("list", "server.commands.recipe.list.desc"); + this.addUsageVariant(new RecipeCommand.List.ListOther()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Set knownRecipes = playerComponent.getPlayerConfigData().getKnownRecipes(); + context.sendMessage(Message.translation("server.commands.recipe.knownRecipes").param("list", knownRecipes.toString())); + } + + private static class ListOther extends CommandBase { + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + + ListOther() { + super("server.commands.recipe.list.other.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef targetPlayerRef = this.playerArg.get(context); + Ref ref = targetPlayerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + } else { + Set knownRecipes = playerComponent.getPlayerConfigData().getKnownRecipes(); + context.sendMessage( + Message.translation("server.commands.recipe.knownRecipes.other") + .param("username", targetPlayerRef.getUsername()) + .param("list", knownRecipes.toString()) + ); + } + } + ); + } else { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/crafting/component/CraftingManager.java b/src/com/hypixel/hytale/builtin/crafting/component/CraftingManager.java new file mode 100644 index 0000000..45eae1a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crafting/component/CraftingManager.java @@ -0,0 +1,887 @@ +package com.hypixel.hytale.builtin.crafting.component; + +import com.google.gson.JsonArray; +import com.hypixel.hytale.builtin.adventure.memories.MemoriesPlugin; +import com.hypixel.hytale.builtin.crafting.CraftingPlugin; +import com.hypixel.hytale.builtin.crafting.state.BenchState; +import com.hypixel.hytale.builtin.crafting.window.BenchWindow; +import com.hypixel.hytale.builtin.crafting.window.CraftingWindow; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.BenchRequirement; +import com.hypixel.hytale.protocol.BenchType; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.ItemQuantity; +import com.hypixel.hytale.protocol.ItemResourceType; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.interface_.NotificationStyle; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.Bench; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.BenchTierLevel; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.BenchUpgradeRequirement; +import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData; +import com.hypixel.hytale.server.core.entity.entities.player.windows.MaterialExtraResourcesSection; +import com.hypixel.hytale.server.core.event.events.ecs.CraftRecipeEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerCraftEvent; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.MaterialQuantity; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.inventory.container.DelegateItemContainer; +import com.hypixel.hytale.server.core.inventory.container.EmptyItemContainer; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterType; +import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.MaterialSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.MaterialTransaction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule; +import com.hypixel.hytale.server.core.universe.world.meta.state.ItemContainerState; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.NotificationUtil; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public class CraftingManager implements Component { + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private final BlockingQueue queuedCraftingJobs = new LinkedBlockingQueue<>(); + @Nullable + private CraftingManager.BenchUpgradingJob upgradingJob; + private int x; + private int y; + private int z; + @Nullable + private BlockType blockType; + + @Nonnull + public static ComponentType getComponentType() { + return CraftingPlugin.get().getCraftingManagerComponentType(); + } + + public CraftingManager() { + } + + private CraftingManager(@Nonnull CraftingManager other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.blockType = other.blockType; + this.queuedCraftingJobs.addAll(other.queuedCraftingJobs); + this.upgradingJob = other.upgradingJob; + } + + public boolean hasBenchSet() { + return this.blockType != null; + } + + public void setBench(int x, int y, int z, @Nonnull BlockType blockType) { + Bench bench = blockType.getBench(); + Objects.requireNonNull(bench, "blockType isn't a bench!"); + if (bench.getType() != BenchType.Crafting + && bench.getType() != BenchType.DiagramCrafting + && bench.getType() != BenchType.StructuralCrafting + && bench.getType() != BenchType.Processing) { + throw new IllegalArgumentException("blockType isn't a crafting bench!"); + } else if (this.blockType != null) { + throw new IllegalArgumentException("Bench blockType is already set! Must be cleared (close UI)."); + } else if (!this.queuedCraftingJobs.isEmpty()) { + throw new IllegalArgumentException("Queue already has jobs!"); + } else if (this.upgradingJob != null) { + throw new IllegalArgumentException("Upgrading job is already set!"); + } else { + this.x = x; + this.y = y; + this.z = z; + this.blockType = blockType; + } + } + + public boolean clearBench(@Nonnull Ref ref, @Nonnull Store store) { + boolean result = this.cancelAllCrafting(ref, store); + this.x = 0; + this.y = 0; + this.z = 0; + this.blockType = null; + this.upgradingJob = null; + return result; + } + + public boolean craftItem( + @Nonnull Ref ref, + @Nonnull ComponentAccessor store, + @Nonnull CraftingRecipe recipe, + int quantity, + @Nonnull ItemContainer itemContainer + ) { + if (this.upgradingJob != null) { + return false; + } else { + Objects.requireNonNull(recipe, "Recipe can't be null"); + CraftRecipeEvent.Pre preEvent = new CraftRecipeEvent.Pre(recipe, quantity); + store.invoke(ref, preEvent); + if (preEvent.isCancelled()) { + return false; + } else if (!this.isValidBenchForRecipe(ref, store, recipe)) { + return false; + } else { + World world = store.getExternalData().getWorld(); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (playerComponent.getGameMode() != GameMode.Creative && !removeInputFromInventory(itemContainer, recipe, quantity)) { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + String translationKey = getRecipeOutputTranslationKey(recipe); + NotificationUtil.sendNotification( + playerRefComponent.getPacketHandler(), + Message.translation("server.general.crafting.missingIngredient").param("item", Message.translation(translationKey)), + NotificationStyle.Danger + ); + LOGGER.at(Level.FINE).log("Missing items required to craft the item: %s", recipe); + return false; + } else { + CraftRecipeEvent.Post postEvent = new CraftRecipeEvent.Post(recipe, quantity); + store.invoke(ref, postEvent); + if (postEvent.isCancelled()) { + return true; + } else { + giveOutput(ref, store, recipe, quantity); + IEventDispatcher dispatcher = HytaleServer.get() + .getEventBus() + .dispatchFor(PlayerCraftEvent.class, world.getName()); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new PlayerCraftEvent(ref, playerComponent, recipe, quantity)); + } + + return true; + } + } + } + } + } + + private static String getRecipeOutputTranslationKey(CraftingRecipe recipe) { + String itemId = recipe.getPrimaryOutput().getItemId(); + Item item = Item.getAssetMap().getAsset(itemId); + return item != null ? item.getTranslationKey() : null; + } + + public boolean queueCraft( + @Nonnull Ref ref, + @Nonnull ComponentAccessor store, + @Nonnull CraftingWindow window, + int transactionId, + @Nonnull CraftingRecipe recipe, + int quantity, + @Nonnull ItemContainer inputItemContainer, + @Nonnull CraftingManager.InputRemovalType inputRemovalType + ) { + if (this.upgradingJob != null) { + return false; + } else { + Objects.requireNonNull(recipe, "Recipe can't be null"); + if (!this.isValidBenchForRecipe(ref, store, recipe)) { + return false; + } else { + float recipeTime = recipe.getTimeSeconds(); + if (recipeTime > 0.0F) { + int level = this.getBenchTierLevel(store); + if (level > 1) { + BenchTierLevel tierLevelData = this.getBenchTierLevelData(level); + if (tierLevelData != null) { + recipeTime -= recipeTime * tierLevelData.getCraftingTimeReductionModifier(); + } + } + } + + this.queuedCraftingJobs + .offer(new CraftingManager.CraftingJob(window, transactionId, recipe, quantity, recipeTime, inputItemContainer, inputRemovalType)); + return true; + } + } + } + + public void tick(@Nonnull Ref ref, @Nonnull ComponentAccessor store, float dt) { + if (this.upgradingJob != null) { + if (dt > 0.0F) { + this.upgradingJob.timeSecondsCompleted += dt; + } + + this.upgradingJob.window.updateBenchUpgradeJob(this.upgradingJob.computeLoadingPercent()); + if (this.upgradingJob.timeSecondsCompleted >= this.upgradingJob.timeSeconds) { + this.upgradingJob.window.updateBenchTierLevel(this.finishTierUpgrade(ref, store)); + this.upgradingJob = null; + } + } else { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + while (dt > 0.0F && !this.queuedCraftingJobs.isEmpty()) { + CraftingManager.CraftingJob currentJob = this.queuedCraftingJobs.peek(); + boolean isCreativeMode = playerComponent.getGameMode() == GameMode.Creative; + if (currentJob != null && currentJob.quantityStarted < currentJob.quantity && currentJob.quantityStarted <= currentJob.quantityCompleted) { + LOGGER.at(Level.FINE).log("Removing Items for next quantity: %s", currentJob); + int currentItemId = currentJob.quantityStarted++; + if (!isCreativeMode && !removeInputFromInventory(currentJob, currentItemId)) { + String translationKey = getRecipeOutputTranslationKey(currentJob.recipe); + NotificationUtil.sendNotification( + playerRefComponent.getPacketHandler(), + Message.translation("server.general.crafting.missingIngredient").param("item", Message.translation(translationKey)), + NotificationStyle.Danger + ); + LOGGER.at(Level.FINE).log("Missing items required to craft the item: %s", currentJob); + currentJob = null; + this.queuedCraftingJobs.poll(); + } + + if (!isCreativeMode + && currentJob != null + && currentJob.quantityStarted < currentJob.quantity + && currentJob.quantityStarted <= currentJob.quantityCompleted) { + NotificationUtil.sendNotification( + playerRefComponent.getPacketHandler(), + Message.translation("server.general.crafting.failedTakingCorrectQuantity"), + NotificationStyle.Danger + ); + LOGGER.at(Level.SEVERE).log("Failed to remove the correct quantity of input, removing crafting job %s", currentJob); + currentJob = null; + this.queuedCraftingJobs.poll(); + } + } + + if (currentJob != null) { + currentJob.timeSecondsCompleted += dt; + float percent = currentJob.timeSeconds <= 0.0F ? 1.0F : currentJob.timeSecondsCompleted / currentJob.timeSeconds; + if (percent > 1.0F) { + percent = 1.0F; + } + + currentJob.window.updateCraftingJob(percent); + LOGGER.at(Level.FINEST).log("Update time: %s", currentJob); + dt = 0.0F; + if (currentJob.timeSecondsCompleted >= currentJob.timeSeconds) { + dt = currentJob.timeSecondsCompleted - currentJob.timeSeconds; + int currentCompletedItemId = currentJob.quantityCompleted++; + currentJob.timeSecondsCompleted = 0.0F; + LOGGER.at(Level.FINE).log("Crafted 1 Quantity: %s", currentJob); + if (currentJob.quantityCompleted == currentJob.quantity) { + giveOutput(ref, store, currentJob, currentCompletedItemId); + LOGGER.at(Level.FINE).log("Crafting Finished: %s", currentJob); + this.queuedCraftingJobs.poll(); + } else { + if (currentJob.quantityCompleted > currentJob.quantity) { + this.queuedCraftingJobs.poll(); + throw new RuntimeException("QuantityCompleted is greater than the Quality! " + currentJob); + } + + giveOutput(ref, store, currentJob, currentCompletedItemId); + } + + if (this.queuedCraftingJobs.isEmpty()) { + currentJob.window.setBlockInteractionState("default", store.getExternalData().getWorld(), 6); + } + } + } + } + } + } + + public boolean cancelAllCrafting(@Nonnull Ref ref, @Nonnull ComponentAccessor store) { + LOGGER.at(Level.FINE).log("Cancel Crafting!"); + ObjectList oldJobs = new ObjectArrayList<>(this.queuedCraftingJobs.size()); + this.queuedCraftingJobs.drainTo(oldJobs); + if (!oldJobs.isEmpty()) { + CraftingManager.CraftingJob currentJob = oldJobs.getFirst(); + LOGGER.at(Level.FINE).log("Refunding Items for: %s", currentJob); + refundInputToInventory(ref, store, currentJob, currentJob.quantityStarted - 1); + return true; + } else { + return false; + } + } + + private boolean isValidBenchForRecipe(@Nonnull Ref ref, @Nonnull ComponentAccessor store, @Nonnull CraftingRecipe recipe) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerConfigData playerConfigData = playerComponent.getPlayerConfigData(); + String primaryOutputItemId = recipe.getPrimaryOutput() != null ? recipe.getPrimaryOutput().getItemId() : null; + if (!recipe.isKnowledgeRequired() || primaryOutputItemId != null && playerConfigData.getKnownRecipes().contains(primaryOutputItemId)) { + World world = store.getExternalData().getWorld(); + if (recipe.getRequiredMemoriesLevel() > 1 && MemoriesPlugin.get().getMemoriesLevel(world.getGameplayConfig()) < recipe.getRequiredMemoriesLevel()) { + LOGGER.at(Level.WARNING).log("Attempted to craft %s but doesn't have the required world memories level!", recipe.getId()); + return false; + } else { + BenchType benchType = this.blockType != null ? this.blockType.getBench().getType() : BenchType.Crafting; + String benchName = this.blockType != null ? this.blockType.getBench().getId() : "Fieldcraft"; + boolean meetsRequirements = false; + BlockState state = world.getState(this.x, this.y, this.z, true); + int benchTierLevel = state instanceof BenchState ? ((BenchState)state).getTierLevel() : 0; + BenchRequirement[] requirements = recipe.getBenchRequirement(); + if (requirements != null) { + for (BenchRequirement benchRequirement : requirements) { + if (benchRequirement.type == benchType && benchName.equals(benchRequirement.id) && benchRequirement.requiredTierLevel <= benchTierLevel) { + meetsRequirements = true; + break; + } + } + } + + if (!meetsRequirements) { + LOGGER.at(Level.WARNING) + .log("Attempted to craft %s using %s, %s but requires bench %s but a bench is NOT set!", recipe.getId(), benchType, benchName, requirements); + return false; + } else if (benchType == BenchType.Crafting && !"Fieldcraft".equals(benchName)) { + CraftingManager.CraftingJob craftingJob = this.queuedCraftingJobs.peek(); + return craftingJob == null || craftingJob.recipe.getId().equals(recipe.getId()); + } else { + return true; + } + } + } else { + LOGGER.at(Level.WARNING).log("%s - Attempted to craft %s but doesn't know the recipe!", recipe.getId()); + return false; + } + } + + private static void giveOutput( + @Nonnull Ref ref, @Nonnull ComponentAccessor store, @Nonnull CraftingManager.CraftingJob job, int currentItemId + ) { + job.removedItems.remove(currentItemId); + String recipeId = job.recipe.getId(); + CraftingRecipe recipeAsset = CraftingRecipe.getAssetMap().getAsset(recipeId); + if (recipeAsset == null) { + throw new RuntimeException("A non-existent item ID was provided! " + recipeId); + } else { + giveOutput(ref, store, recipeAsset, 1); + } + } + + private static void giveOutput( + @Nonnull Ref ref, @Nonnull ComponentAccessor store, @Nonnull CraftingRecipe craftingRecipe, int quantity + ) { + Player player = store.getComponent(ref, Player.getComponentType()); + List itemStacks = getOutputItemStacks(craftingRecipe, quantity); + SimpleItemContainer.addOrDropItemStacks(store, ref, player.getInventory().getCombinedArmorHotbarStorage(), itemStacks); + } + + private static boolean removeInputFromInventory(@Nonnull CraftingManager.CraftingJob job, int currentItemId) { + Objects.requireNonNull(job, "Job can't be null!"); + CraftingRecipe craftingRecipe = job.recipe; + Objects.requireNonNull(craftingRecipe, "CraftingRecipe can't be null!"); + List materialsToRemove = getInputMaterials(craftingRecipe); + if (materialsToRemove.isEmpty()) { + return true; + } else { + LOGGER.at(Level.FINEST).log("Removing Materials: %s - %s", job, materialsToRemove); + ObjectList itemStackList = new ObjectArrayList<>(); + + boolean succeeded = switch (job.inputRemovalType) { + case NORMAL -> { + ListTransaction materialTransactions = job.inputItemContainer.removeMaterials(materialsToRemove, true, true, true); + + for (MaterialTransaction transaction : materialTransactions.getList()) { + for (MaterialSlotTransaction slotTransaction : transaction.getList()) { + if (!ItemStack.isEmpty(slotTransaction.getOutput())) { + itemStackList.add(slotTransaction.getOutput()); + } + } + } + + yield materialTransactions.succeeded(); + } + case ORDERED -> { + ListTransaction materialTransactions = job.inputItemContainer + .removeMaterialsOrdered(materialsToRemove, true, true, true); + + for (MaterialSlotTransaction transaction : materialTransactions.getList()) { + if (!ItemStack.isEmpty(transaction.getOutput())) { + itemStackList.add(transaction.getOutput()); + } + } + + yield materialTransactions.succeeded(); + } + default -> throw new IllegalArgumentException("Unknown enum: " + job.inputRemovalType); + }; + job.removedItems.put(currentItemId, itemStackList); + job.window.invalidateExtraResources(); + return succeeded; + } + } + + private static boolean removeInputFromInventory(@Nonnull ItemContainer itemContainer, @Nonnull CraftingRecipe craftingRecipe, int quantity) { + List materialsToRemove = getInputMaterials(craftingRecipe, quantity); + if (materialsToRemove.isEmpty()) { + return true; + } else { + LOGGER.at(Level.FINEST).log("Removing Materials: %s - %s", craftingRecipe, materialsToRemove); + ListTransaction materialTransactions = itemContainer.removeMaterials(materialsToRemove, true, true, true); + return materialTransactions.succeeded(); + } + } + + private static void refundInputToInventory( + @Nonnull Ref ref, @Nonnull ComponentAccessor store, @Nonnull CraftingManager.CraftingJob job, int currentItemId + ) { + Objects.requireNonNull(job, "Job can't be null!"); + List itemStacks = job.removedItems.get(currentItemId); + if (itemStacks != null) { + Player player = store.getComponent(ref, Player.getComponentType()); + SimpleItemContainer.addOrDropItemStacks(store, ref, player.getInventory().getCombinedHotbarFirst(), itemStacks); + } + } + + @Nonnull + public static List getOutputItemStacks(@Nonnull CraftingRecipe recipe) { + return getOutputItemStacks(recipe, 1); + } + + @Nonnull + public static List getOutputItemStacks(@Nonnull CraftingRecipe recipe, int quantity) { + Objects.requireNonNull(recipe); + MaterialQuantity[] output = recipe.getOutputs(); + if (output == null) { + return List.of(); + } else { + ObjectList outputItemStacks = new ObjectArrayList<>(); + + for (MaterialQuantity outputMaterial : output) { + outputItemStacks.add(getOutputItemStack(outputMaterial, quantity)); + } + + return outputItemStacks; + } + } + + @Nonnull + public static ItemStack getOutputItemStack(@Nonnull MaterialQuantity outputMaterial, @Nonnull String id) { + return getOutputItemStack(outputMaterial, 1); + } + + @Nonnull + public static ItemStack getOutputItemStack(@Nonnull MaterialQuantity outputMaterial, int quantity) { + String itemId = outputMaterial.getItemId(); + int materialQuantity = outputMaterial.getQuantity() <= 0 ? 1 : outputMaterial.getQuantity(); + return new ItemStack(itemId, materialQuantity * quantity, outputMaterial.getMetadata()); + } + + @Nonnull + public static List getInputMaterials(@Nonnull CraftingRecipe recipe) { + return getInputMaterials(recipe, 1); + } + + @Nonnull + private static List getInputMaterials(@Nonnull MaterialQuantity[] input) { + return getInputMaterials(input, 1); + } + + @Nonnull + public static List getInputMaterials(@Nonnull CraftingRecipe recipe, int quantity) { + Objects.requireNonNull(recipe); + return recipe.getInput() == null ? Collections.emptyList() : getInputMaterials(recipe.getInput(), quantity); + } + + @Nonnull + private static List getInputMaterials(@Nonnull MaterialQuantity[] input, int quantity) { + ObjectList materials = new ObjectArrayList<>(); + + for (MaterialQuantity craftingMaterial : input) { + String itemId = craftingMaterial.getItemId(); + String resourceTypeId = craftingMaterial.getResourceTypeId(); + int materialQuantity = craftingMaterial.getQuantity(); + BsonDocument metadata = craftingMaterial.getMetadata(); + materials.add(new MaterialQuantity(itemId, resourceTypeId, null, materialQuantity * quantity, metadata)); + } + + return materials; + } + + public static boolean matches(@Nonnull MaterialQuantity craftingMaterial, @Nonnull ItemStack itemStack) { + String itemId = craftingMaterial.getItemId(); + if (itemId != null) { + return itemId.equals(itemStack.getItemId()); + } else { + String resourceTypeId = craftingMaterial.getResourceTypeId(); + if (resourceTypeId != null && itemStack.getItem().getResourceTypes() != null) { + for (ItemResourceType itemResourceType : itemStack.getItem().getResourceTypes()) { + if (itemResourceType.id.equals(resourceTypeId)) { + return true; + } + } + } + + return false; + } + } + + @Nonnull + public static JsonArray generateInventoryHints(@Nonnull List recipes, int inputSlotIndex, @Nonnull ItemContainer container) { + JsonArray inventoryHints = new JsonArray(); + short storageSlotIndex = 0; + + for (short bound = container.getCapacity(); storageSlotIndex < bound; storageSlotIndex++) { + ItemStack itemStack = container.getItemStack(storageSlotIndex); + if (itemStack != null && !itemStack.isEmpty() && matchesAnyRecipe(recipes, inputSlotIndex, itemStack)) { + inventoryHints.add(storageSlotIndex); + } + } + + return inventoryHints; + } + + public static boolean matchesAnyRecipe(@Nonnull List recipes, int inputSlotIndex, @Nonnull ItemStack slotItemStack) { + for (CraftingRecipe recipe : recipes) { + MaterialQuantity[] input = recipe.getInput(); + if (inputSlotIndex < input.length) { + MaterialQuantity slotCraftingMaterial = input[inputSlotIndex]; + if (slotCraftingMaterial.getItemId() != null && slotCraftingMaterial.getItemId().equals(slotItemStack.getItemId())) { + return true; + } + + if (slotCraftingMaterial.getResourceTypeId() != null && slotItemStack.getItem().getResourceTypes() != null) { + for (ItemResourceType itemResourceType : slotItemStack.getItem().getResourceTypes()) { + if (itemResourceType.id.equals(slotCraftingMaterial.getResourceTypeId())) { + return true; + } + } + } + } + } + + return false; + } + + public boolean startTierUpgrade(Ref ref, ComponentAccessor store, @Nonnull BenchWindow window) { + if (this.upgradingJob != null) { + return false; + } else { + BenchUpgradeRequirement requirements = this.getBenchUpgradeRequierement(this.getBenchTierLevel(store)); + if (requirements == null) { + return false; + } else { + List input = getInputMaterials(requirements.getInput()); + if (input.isEmpty()) { + return false; + } else { + Player player = store.getComponent(ref, Player.getComponentType()); + if (player.getGameMode() != GameMode.Creative) { + CombinedItemContainer combined = new CombinedItemContainer( + player.getInventory().getCombinedBackpackStorageHotbar(), window.getExtraResourcesSection().getItemContainer() + ); + if (!combined.canRemoveMaterials(input)) { + return false; + } + } + + this.upgradingJob = new CraftingManager.BenchUpgradingJob(window, requirements.getTimeSeconds()); + this.cancelAllCrafting(ref, store); + return true; + } + } + } + } + + private int finishTierUpgrade(Ref ref, ComponentAccessor store) { + if (this.upgradingJob == null) { + return 0; + } else { + BlockState state = store.getExternalData().getWorld().getState(this.x, this.y, this.z, true); + BenchState benchState = state instanceof BenchState ? (BenchState)state : null; + if (benchState != null && benchState.getTierLevel() != 0) { + BenchUpgradeRequirement requirements = this.getBenchUpgradeRequierement(benchState.getTierLevel()); + if (requirements == null) { + return benchState.getTierLevel(); + } else { + List input = getInputMaterials(requirements.getInput()); + if (input.isEmpty()) { + return benchState.getTierLevel(); + } else { + Player player = store.getComponent(ref, Player.getComponentType()); + boolean canUpgrade = player.getGameMode() == GameMode.Creative; + if (!canUpgrade) { + CombinedItemContainer combined = new CombinedItemContainer( + player.getInventory().getCombinedBackpackStorageHotbar(), this.upgradingJob.window.getExtraResourcesSection().getItemContainer() + ); + combined = new CombinedItemContainer(combined, this.upgradingJob.window.getExtraResourcesSection().getItemContainer()); + ListTransaction materialTransactions = combined.removeMaterials(input); + if (materialTransactions.succeeded()) { + List consumed = new ObjectArrayList<>(); + + for (MaterialTransaction transaction : materialTransactions.getList()) { + for (MaterialSlotTransaction matSlot : transaction.getList()) { + consumed.add(matSlot.getOutput()); + } + } + + benchState.addUpgradeItems(consumed); + canUpgrade = true; + } + } + + if (canUpgrade) { + benchState.setTierLevel(benchState.getTierLevel() + 1); + if (benchState.getBench().getBenchUpgradeCompletedSoundEventIndex() != 0) { + SoundUtil.playSoundEvent3d( + benchState.getBench().getBenchUpgradeCompletedSoundEventIndex(), SoundCategory.SFX, this.x + 0.5, this.y + 0.5, this.z + 0.5, store + ); + } + } + + return benchState.getTierLevel(); + } + } + } else { + return 0; + } + } + } + + private BenchTierLevel getBenchTierLevelData(int level) { + if (this.blockType == null) { + return null; + } else { + Bench bench = this.blockType.getBench(); + return bench == null ? null : bench.getTierLevel(level); + } + } + + private BenchUpgradeRequirement getBenchUpgradeRequierement(int tierLevel) { + BenchTierLevel tierData = this.getBenchTierLevelData(tierLevel); + return tierData == null ? null : tierData.getUpgradeRequirement(); + } + + private int getBenchTierLevel(ComponentAccessor store) { + BlockState state = store.getExternalData().getWorld().getState(this.x, this.y, this.z, true); + return state instanceof BenchState ? ((BenchState)state).getTierLevel() : 0; + } + + protected static List getContainersAroundBench(@Nonnull BenchState benchState) { + List containers = new ObjectArrayList<>(); + World world = benchState.getChunk().getWorld(); + Store store = world.getChunkStore().getStore(); + int limit = world.getGameplayConfig().getCraftingConfig().getBenchMaterialChestLimit(); + double horizontalRadius = world.getGameplayConfig().getCraftingConfig().getBenchMaterialHorizontalChestSearchRadius(); + double verticalRadius = world.getGameplayConfig().getCraftingConfig().getBenchMaterialVerticalChestSearchRadius(); + Vector3d blockPos = benchState.getBlockPosition().toVector3d(); + BlockBoundingBoxes hitboxAsset = BlockBoundingBoxes.getAssetMap().getAsset(benchState.getBlockType().getHitboxTypeIndex()); + BlockBoundingBoxes.RotatedVariantBoxes rotatedHitbox = hitboxAsset.get(benchState.getRotationIndex()); + Box boundingBox = rotatedHitbox.getBoundingBox(); + double benchWidth = boundingBox.width(); + double benchHeight = boundingBox.height(); + double benchDepth = boundingBox.depth(); + double extraSearchRadius = Math.max(benchWidth, Math.max(benchDepth, benchHeight)) - 1.0; + SpatialResource, ChunkStore> blockStateSpatialStructure = store.getResource(BlockStateModule.get().getItemContainerSpatialResourceType()); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + blockStateSpatialStructure.getSpatialStructure() + .ordered3DAxis(blockPos, horizontalRadius + extraSearchRadius, verticalRadius + extraSearchRadius, horizontalRadius + extraSearchRadius, results); + if (!results.isEmpty()) { + double minX = blockPos.x + boundingBox.min.x - horizontalRadius; + double minY = blockPos.y + boundingBox.min.y - verticalRadius; + double minZ = blockPos.z + boundingBox.min.z - horizontalRadius; + double maxX = blockPos.x + boundingBox.max.x + horizontalRadius; + double maxY = blockPos.y + boundingBox.max.y + verticalRadius; + double maxZ = blockPos.z + boundingBox.max.z + horizontalRadius; + + for (Ref ref : results) { + if (BlockState.getBlockState(ref, ref.getStore()) instanceof ItemContainerState chest) { + Vector3d chestPos = chest.getCenteredBlockPosition(); + if (chestPos.x >= minX && chestPos.x <= maxX && chestPos.y >= minY && chestPos.y <= maxY && chestPos.z >= minZ && chestPos.z <= maxZ) { + containers.add(chest.getItemContainer()); + if (containers.size() >= limit) { + break; + } + } + } + } + } + + return containers; + } + + public static void feedExtraResourcesSection(BenchState benchState, MaterialExtraResourcesSection extraResourcesSection) { + List chests = getContainersAroundBench(benchState); + ItemContainer itemContainer = EmptyItemContainer.INSTANCE; + if (!chests.isEmpty()) { + itemContainer = new CombinedItemContainer(chests.stream().map(container -> { + DelegateItemContainer delegate = new DelegateItemContainer<>(container); + delegate.setGlobalFilter(FilterType.ALLOW_OUTPUT_ONLY); + return delegate; + }).toArray(ItemContainer[]::new)); + } + + Map materials = new Object2ObjectOpenHashMap<>(); + + for (ItemContainer chest : chests) { + chest.forEach((i, itemStack) -> { + if (CraftingPlugin.isValidUpgradeMaterialForBench(benchState, itemStack) || CraftingPlugin.isValidCraftingMaterialForBench(benchState, itemStack)) { + ItemQuantity var10000 = materials.computeIfAbsent(itemStack.getItemId(), k -> new ItemQuantity(itemStack.getItemId(), 0)); + var10000.quantity = var10000.quantity + itemStack.getQuantity(); + } + }); + } + + extraResourcesSection.setItemContainer(itemContainer); + extraResourcesSection.setExtraMaterials(materials.values().toArray(new ItemQuantity[0])); + extraResourcesSection.setValid(true); + } + + @Nonnull + @Override + public String toString() { + return "CraftingManager{queuedCraftingJobs=" + + this.queuedCraftingJobs + + ", x=" + + this.x + + ", y=" + + this.y + + ", z=" + + this.z + + ", blockType=" + + this.blockType + + "}"; + } + + @Nonnull + @Override + public Component clone() { + return new CraftingManager(this); + } + + private static class BenchUpgradingJob { + @Nonnull + private final BenchWindow window; + private final float timeSeconds; + private float timeSecondsCompleted; + private float lastSentPercent; + + private BenchUpgradingJob(@Nonnull BenchWindow window, float timeSeconds) { + this.window = window; + this.timeSeconds = timeSeconds; + } + + @Override + public String toString() { + return "BenchUpgradingJob{window=" + this.window + ", timeSeconds=" + this.timeSeconds + "}"; + } + + public float computeLoadingPercent() { + return this.timeSeconds <= 0.0F ? 1.0F : Math.min(this.timeSecondsCompleted / this.timeSeconds, 1.0F); + } + } + + private static class CraftingJob { + @Nonnull + private final CraftingWindow window; + private final int transactionId; + @Nonnull + private final CraftingRecipe recipe; + private final int quantity; + private final float timeSeconds; + @Nonnull + private final ItemContainer inputItemContainer; + @Nonnull + private final CraftingManager.InputRemovalType inputRemovalType; + @Nonnull + private final Int2ObjectMap> removedItems = new Int2ObjectOpenHashMap<>(); + private int quantityStarted; + private int quantityCompleted; + private float timeSecondsCompleted; + + public CraftingJob( + @Nonnull CraftingWindow window, + int transactionId, + @Nonnull CraftingRecipe recipe, + int quantity, + float timeSeconds, + @Nonnull ItemContainer inputItemContainer, + @Nonnull CraftingManager.InputRemovalType inputRemovalType + ) { + this.window = window; + this.transactionId = transactionId; + this.recipe = recipe; + this.quantity = quantity; + this.timeSeconds = timeSeconds; + this.inputItemContainer = inputItemContainer; + this.inputRemovalType = inputRemovalType; + } + + @Nonnull + @Override + public String toString() { + return "CraftingJob{window=" + + this.window + + ", transactionId=" + + this.transactionId + + ", recipe=" + + this.recipe + + ", quantity=" + + this.quantity + + ", timeSeconds=" + + this.timeSeconds + + ", inputItemContainer=" + + this.inputItemContainer + + ", inputRemovalType=" + + this.inputRemovalType + + ", removedItems=" + + this.removedItems + + ", quantityStarted=" + + this.quantityStarted + + ", quantityCompleted=" + + this.quantityCompleted + + ", timeSecondsCompleted=" + + this.timeSecondsCompleted + + "}"; + } + } + + public static enum InputRemovalType { + NORMAL, + ORDERED; + + private InputRemovalType() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/crafting/interaction/LearnRecipeInteraction.java b/src/com/hypixel/hytale/builtin/crafting/interaction/LearnRecipeInteraction.java new file mode 100644 index 0000000..d026f55 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crafting/interaction/LearnRecipeInteraction.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.builtin.crafting.interaction; + +import com.hypixel.hytale.builtin.crafting.CraftingPlugin; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class LearnRecipeInteraction extends SimpleInstantInteraction { + public static final KeyedCodec ITEM_ID = new KeyedCodec<>("ItemId", Codec.STRING); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + LearnRecipeInteraction.class, LearnRecipeInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Causes the user to learn the given recipe.") + .appendInherited( + new KeyedCodec<>("ItemId", Codec.STRING), (data, o) -> data.itemId = o, data -> data.itemId, (data, parent) -> data.itemId = parent.itemId + ) + .add() + .build(); + public static final Message MESSAGE_MODULES_LEARN_RECIPE_INVALID_ITEM = Message.translation("server.modules.learnrecipe.invalidItem"); + @Nullable + protected String itemId; + + public LearnRecipeInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getEntity(); + PlayerRef playerRefComponent = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); + if (playerRefComponent == null) { + HytaleLogger.getLogger().at(Level.INFO).log("LearnRecipeInteraction requires a Player but was used for: %s", ref); + context.getState().state = InteractionState.Failed; + } else { + String itemId = null; + ItemContainer inventory = context.getHeldItemContainer(); + ItemStack itemInHand = context.getHeldItem(); + if (itemInHand != null) { + itemId = itemInHand.getFromMetadataOrNull(ITEM_ID); + } + + if (itemId == null) { + if (this.itemId == null) { + playerRefComponent.sendMessage(Message.translation("server.modules.learnrecipe.noIdSet")); + context.getState().state = InteractionState.Failed; + return; + } + + itemId = this.itemId; + } + + Item item = Item.getAssetMap().getAsset(itemId); + Message itemNameMessage = item != null ? Message.translation(item.getTranslationKey()) : Message.raw("?"); + if (CraftingPlugin.learnRecipe(ref, itemId, commandBuffer)) { + playerRefComponent.sendMessage(Message.translation("server.modules.learnrecipe.success").param("name", itemNameMessage)); + } else { + playerRefComponent.sendMessage(Message.translation("server.modules.learnrecipe.alreadyKnown").param("name", itemNameMessage)); + context.getState().state = InteractionState.Failed; + } + } + } + + @Nonnull + @Override + public String toString() { + return "LearnRecipeInteraction{itemId=" + this.itemId + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/crafting/interaction/OpenBenchPageInteraction.java b/src/com/hypixel/hytale/builtin/crafting/interaction/OpenBenchPageInteraction.java new file mode 100644 index 0000000..5f64f08 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crafting/interaction/OpenBenchPageInteraction.java @@ -0,0 +1,112 @@ +package com.hypixel.hytale.builtin.crafting.interaction; + +import com.hypixel.hytale.builtin.crafting.component.CraftingManager; +import com.hypixel.hytale.builtin.crafting.state.BenchState; +import com.hypixel.hytale.builtin.crafting.window.DiagramCraftingWindow; +import com.hypixel.hytale.builtin.crafting.window.SimpleCraftingWindow; +import com.hypixel.hytale.builtin.crafting.window.StructuralCraftingWindow; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.windows.Window; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class OpenBenchPageInteraction extends SimpleBlockInteraction { + public static final OpenBenchPageInteraction SIMPLE_CRAFTING = new OpenBenchPageInteraction( + "*Simple_Crafting_Default", OpenBenchPageInteraction.PageType.SIMPLE_CRAFTING + ); + public static final RootInteraction SIMPLE_CRAFTING_ROOT = new RootInteraction(SIMPLE_CRAFTING.getId(), SIMPLE_CRAFTING.getId()); + public static final OpenBenchPageInteraction DIAGRAM_CRAFTING = new OpenBenchPageInteraction( + "*Diagram_Crafting_Default", OpenBenchPageInteraction.PageType.DIAGRAM_CRAFTING + ); + public static final RootInteraction DIAGRAM_CRAFTING_ROOT = new RootInteraction(DIAGRAM_CRAFTING.getId(), DIAGRAM_CRAFTING.getId()); + public static final OpenBenchPageInteraction STRUCTURAL_CRAFTING = new OpenBenchPageInteraction( + "*Structural_Crafting_Default", OpenBenchPageInteraction.PageType.STRUCTURAL_CRAFTING + ); + public static final RootInteraction STRUCTURAL_CRAFTING_ROOT = new RootInteraction(STRUCTURAL_CRAFTING.getId(), STRUCTURAL_CRAFTING.getId()); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + OpenBenchPageInteraction.class, OpenBenchPageInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Opens the given crafting bench page.") + .appendInherited( + new KeyedCodec<>("Page", new EnumCodec<>(OpenBenchPageInteraction.PageType.class)), + (o, v) -> o.pageType = v, + o -> o.pageType, + (o, p) -> o.pageType = p.pageType + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + @Nonnull + private OpenBenchPageInteraction.PageType pageType = OpenBenchPageInteraction.PageType.SIMPLE_CRAFTING; + + public OpenBenchPageInteraction(@Nonnull String id, @Nonnull OpenBenchPageInteraction.PageType pageType) { + super(id); + this.pageType = pageType; + } + + protected OpenBenchPageInteraction() { + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + Ref ref = context.getEntity(); + Store store = ref.getStore(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + CraftingManager craftingManagerComponent = commandBuffer.getComponent(ref, CraftingManager.getComponentType()); + + assert craftingManagerComponent != null; + + if (!craftingManagerComponent.hasBenchSet()) { + if (world.getState(targetBlock.x, targetBlock.y, targetBlock.z, true) instanceof BenchState benchState) { + playerComponent.getPageManager().setPageWithWindows(ref, store, Page.Bench, true, (Window)(switch (this.pageType) { + case SIMPLE_CRAFTING -> new SimpleCraftingWindow(benchState); + case DIAGRAM_CRAFTING -> new DiagramCraftingWindow(commandBuffer, benchState); + case STRUCTURAL_CRAFTING -> new StructuralCraftingWindow(benchState); + })); + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } + + private static enum PageType { + SIMPLE_CRAFTING, + DIAGRAM_CRAFTING, + STRUCTURAL_CRAFTING; + + private PageType() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/crafting/interaction/OpenProcessingBenchInteraction.java b/src/com/hypixel/hytale/builtin/crafting/interaction/OpenProcessingBenchInteraction.java new file mode 100644 index 0000000..0f6a051 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crafting/interaction/OpenProcessingBenchInteraction.java @@ -0,0 +1,117 @@ +package com.hypixel.hytale.builtin.crafting.interaction; + +import com.hypixel.hytale.builtin.crafting.state.ProcessingBenchState; +import com.hypixel.hytale.builtin.crafting.window.ProcessingBenchWindow; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.Bench; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.accessor.BlockAccessor; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class OpenProcessingBenchInteraction extends SimpleBlockInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + OpenProcessingBenchInteraction.class, OpenProcessingBenchInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Opens the processing bench page.") + .build(); + + public OpenProcessingBenchInteraction() { + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i pos, + @Nonnull CooldownHandler cooldownHandler + ) { + Ref ref = context.getEntity(); + Store store = ref.getStore(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + BlockState state = world.getState(pos.x, pos.y, pos.z, true); + if (!(state instanceof ProcessingBenchState benchState)) { + playerComponent.sendMessage( + Message.translation("server.interactions.invalidBlockState") + .param("interaction", this.getClass().getSimpleName()) + .param("blockState", state != null ? state.getClass().getSimpleName() : "null") + ); + } else { + BlockType blockType = world.getBlockType(pos.x, pos.y, pos.z); + Bench blockTypeBench = blockType.getBench(); + if ((blockTypeBench == null || !blockTypeBench.equals(benchState.getBench())) && !benchState.initialize(blockType)) { + ProcessingBenchState.LOGGER.at(Level.WARNING).log("Failed to re-initialize: %s, %s", blockType.getId(), pos); + int x = pos.getX(); + int z = pos.getZ(); + world.getChunk(ChunkUtil.indexChunkFromBlock(x, z)).setState(x, pos.getY(), z, (BlockState)null); + } else { + UUIDComponent uuidComponent = commandBuffer.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID uuid = uuidComponent.getUuid(); + ProcessingBenchWindow window = new ProcessingBenchWindow(benchState); + Map windows = benchState.getWindows(); + if (windows.putIfAbsent(uuid, window) == null) { + benchState.updateFuelValues(); + if (playerComponent.getPageManager().setPageWithWindows(ref, store, Page.Bench, true, window)) { + window.registerCloseEvent(event -> { + windows.remove(uuid, window); + BlockType currentBlockType = world.getBlockType(pos); + String interactionState = BlockAccessor.getCurrentInteractionState(currentBlockType); + if (windows.isEmpty() && !"Processing".equals(interactionState) && !"ProcessCompleted".equals(interactionState)) { + world.setBlockInteractionState(pos, currentBlockType, "default"); + } + + int soundEventIndexx = blockType.getBench().getLocalCloseSoundEventIndex(); + if (soundEventIndexx != 0) { + SoundUtil.playSoundEvent2d(ref, soundEventIndexx, SoundCategory.UI, commandBuffer); + } + }); + int soundEventIndex = blockType.getBench().getLocalOpenSoundEventIndex(); + if (soundEventIndex == 0) { + return; + } + + SoundUtil.playSoundEvent2d(ref, soundEventIndex, SoundCategory.UI, commandBuffer); + } else { + windows.remove(uuid, window); + } + } + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } +} diff --git a/src/com/hypixel/hytale/builtin/crafting/state/BenchState.java b/src/com/hypixel/hytale/builtin/crafting/state/BenchState.java new file mode 100644 index 0000000..28af2a3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crafting/state/BenchState.java @@ -0,0 +1,130 @@ +package com.hypixel.hytale.builtin.crafting.state; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.Bench; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.BenchUpgradeRequirement; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.meta.state.DestroyableBlockState; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nonnull; + +public class BenchState extends BlockState implements DestroyableBlockState { + public static BuilderCodec CODEC = BuilderCodec.builder(BenchState.class, BenchState::new, BlockState.BASE_CODEC) + .appendInherited( + new KeyedCodec<>("TierLevel", Codec.INTEGER), + (state, o) -> state.tierLevel = o, + state -> state.tierLevel, + (state, parent) -> state.tierLevel = parent.tierLevel + ) + .add() + .appendInherited( + new KeyedCodec<>("UpgradeItems", new ArrayCodec<>(ItemStack.CODEC, ItemStack[]::new)), + (state, o) -> state.upgradeItems = o, + state -> state.upgradeItems, + (state, parent) -> state.upgradeItems = parent.upgradeItems + ) + .add() + .build(); + private int tierLevel = 1; + protected ItemStack[] upgradeItems = ItemStack.EMPTY_ARRAY; + protected Bench bench; + + public BenchState() { + } + + public int getTierLevel() { + return this.tierLevel; + } + + @Override + public boolean initialize(@Nonnull BlockType blockType) { + if (!super.initialize(blockType)) { + return false; + } else { + this.bench = blockType.getBench(); + if (this.bench == null) { + if (this.upgradeItems.length > 0) { + this.dropUpgradeItems(); + } + + return false; + } else { + return true; + } + } + } + + public void addUpgradeItems(List consumed) { + consumed.addAll(Arrays.asList(this.upgradeItems)); + this.upgradeItems = consumed.toArray(ItemStack[]::new); + this.markNeedsSave(); + } + + private void dropUpgradeItems() { + if (this.upgradeItems.length != 0) { + World world = this.getChunk().getWorld(); + Store entityStore = world.getEntityStore().getStore(); + Vector3d dropPosition = this.getBlockPosition().toVector3d().add(0.5, 0.0, 0.5); + Holder[] itemEntityHolders = ItemComponent.generateItemDrops(entityStore, List.of(this.upgradeItems), dropPosition, Vector3f.ZERO); + if (itemEntityHolders.length > 0) { + world.execute(() -> entityStore.addEntities(itemEntityHolders, AddReason.SPAWN)); + } + + this.upgradeItems = ItemStack.EMPTY_ARRAY; + } + } + + public Bench getBench() { + return this.bench; + } + + public void setTierLevel(int newTierLevel) { + if (this.tierLevel != newTierLevel) { + this.tierLevel = newTierLevel; + this.onTierLevelChange(); + this.markNeedsSave(); + } + } + + public BenchUpgradeRequirement getNextLevelUpgradeMaterials() { + return this.bench.getUpgradeRequirement(this.tierLevel); + } + + protected void onTierLevelChange() { + this.getChunk().setBlockInteractionState(this.getBlockPosition(), this.getBaseBlockType(), this.getTierStateName()); + } + + public BlockType getBaseBlockType() { + BlockType currentBlockType = this.getBlockType(); + String baseBlockKey = currentBlockType.getDefaultStateKey(); + BlockType baseBlockType = BlockType.getAssetMap().getAsset(baseBlockKey); + if (baseBlockType == null) { + baseBlockType = currentBlockType; + } + + return baseBlockType; + } + + public String getTierStateName() { + return this.tierLevel > 1 ? "Tier" + this.tierLevel : "default"; + } + + @Override + public void onDestroy() { + this.dropUpgradeItems(); + } +} diff --git a/src/com/hypixel/hytale/builtin/crafting/state/ProcessingBenchState.java b/src/com/hypixel/hytale/builtin/crafting/state/ProcessingBenchState.java new file mode 100644 index 0000000..331df03 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crafting/state/ProcessingBenchState.java @@ -0,0 +1,839 @@ +package com.hypixel.hytale.builtin.crafting.state; + +import com.google.common.flogger.LazyArgs; +import com.hypixel.hytale.builtin.crafting.CraftingPlugin; +import com.hypixel.hytale.builtin.crafting.component.CraftingManager; +import com.hypixel.hytale.builtin.crafting.window.ProcessingBenchWindow; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.EventPriority; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.Transform; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.BenchTierLevel; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.ProcessingBench; +import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.windows.WindowManager; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.MaterialQuantity; +import com.hypixel.hytale.server.core.inventory.ResourceQuantity; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.inventory.container.InternalContainerUtilMaterial; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer; +import com.hypixel.hytale.server.core.inventory.container.TestRemoveItemSlotResult; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterActionType; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterType; +import com.hypixel.hytale.server.core.inventory.container.filter.ResourceFilter; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.MaterialSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.MaterialTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ResourceTransaction; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.accessor.BlockAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.state.TickableBlockState; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.meta.state.DestroyableBlockState; +import com.hypixel.hytale.server.core.universe.world.meta.state.ItemContainerBlockState; +import com.hypixel.hytale.server.core.universe.world.meta.state.MarkerBlockState; +import com.hypixel.hytale.server.core.universe.world.meta.state.PlacedByBlockState; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; +import com.hypixel.hytale.server.core.util.PositionUtil; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public class ProcessingBenchState + extends BenchState + implements TickableBlockState, + ItemContainerBlockState, + DestroyableBlockState, + MarkerBlockState, + PlacedByBlockState { + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final boolean EXACT_RESOURCE_AMOUNTS = true; + public static final Codec CODEC = BuilderCodec.builder(ProcessingBenchState.class, ProcessingBenchState::new, BenchState.CODEC) + .append(new KeyedCodec<>("InputContainer", ItemContainer.CODEC), (state, o) -> state.inputContainer = o, state -> state.inputContainer) + .add() + .append(new KeyedCodec<>("FuelContainer", ItemContainer.CODEC), (state, o) -> state.fuelContainer = o, state -> state.fuelContainer) + .add() + .append(new KeyedCodec<>("OutputContainer", ItemContainer.CODEC), (state, o) -> state.outputContainer = o, state -> state.outputContainer) + .add() + .append(new KeyedCodec<>("Progress", Codec.DOUBLE), (state, d) -> state.inputProgress = d.floatValue(), state -> (double)state.inputProgress) + .add() + .append(new KeyedCodec<>("FuelTime", Codec.DOUBLE), (state, d) -> state.fuelTime = d.floatValue(), state -> (double)state.fuelTime) + .add() + .append(new KeyedCodec<>("Active", Codec.BOOLEAN), (state, b) -> state.active = b, state -> state.active) + .add() + .append(new KeyedCodec<>("NextExtra", Codec.INTEGER), (state, b) -> state.nextExtra = b, state -> state.nextExtra) + .add() + .append(new KeyedCodec<>("Marker", WorldMapManager.MarkerReference.CODEC), (state, o) -> state.marker = o, state -> state.marker) + .add() + .append(new KeyedCodec<>("RecipeId", Codec.STRING), (state, o) -> state.recipeId = o, state -> state.recipeId) + .add() + .build(); + private static final float EJECT_VELOCITY = 2.0F; + private static final float EJECT_SPREAD_VELOCITY = 1.0F; + private static final float EJECT_VERTICAL_VELOCITY = 3.25F; + public static final String PROCESSING = "Processing"; + public static final String PROCESS_COMPLETED = "ProcessCompleted"; + protected WorldMapManager.MarkerReference marker; + private final Map windows = new ConcurrentHashMap<>(); + private ProcessingBench processingBench; + private ItemContainer inputContainer; + private ItemContainer fuelContainer; + private ItemContainer outputContainer; + private CombinedItemContainer combinedItemContainer; + private float inputProgress; + private float fuelTime; + private int lastConsumedFuelTotal; + private int nextExtra = -1; + private final Set processingSlots = new HashSet<>(); + private final Set processingFuelSlots = new HashSet<>(); + @Nullable + private String recipeId; + @Nullable + private CraftingRecipe recipe; + private boolean active = false; + + public ProcessingBenchState() { + } + + @Override + public boolean initialize(@Nonnull BlockType blockType) { + if (!super.initialize(blockType)) { + if (this.bench == null) { + List itemStacks = new ObjectArrayList<>(); + if (this.inputContainer != null) { + itemStacks.addAll(this.inputContainer.dropAllItemStacks()); + } + + if (this.fuelContainer != null) { + itemStacks.addAll(this.fuelContainer.dropAllItemStacks()); + } + + if (this.outputContainer != null) { + itemStacks.addAll(this.outputContainer.dropAllItemStacks()); + } + + World world = this.getChunk().getWorld(); + Store store = world.getEntityStore().getStore(); + Holder[] itemEntityHolders = this.ejectItems(store, itemStacks); + if (itemEntityHolders.length > 0) { + world.execute(() -> store.addEntities(itemEntityHolders, AddReason.SPAWN)); + } + } + + return false; + } else if (!(this.bench instanceof ProcessingBench)) { + LOGGER.at(Level.SEVERE).log("Wrong bench type for processing. Got %s", this.bench.getClass().getName()); + return false; + } else { + this.processingBench = (ProcessingBench)this.bench; + if (this.nextExtra == -1) { + this.nextExtra = this.processingBench.getExtraOutput() != null ? this.processingBench.getExtraOutput().getPerFuelItemsConsumed() : 0; + } + + this.setupSlots(); + return true; + } + } + + private void setupSlots() { + List remainder = new ObjectArrayList<>(); + int tierLevel = this.getTierLevel(); + ProcessingBench.ProcessingSlot[] input = this.processingBench.getInput(tierLevel); + short inputSlotsCount = (short)input.length; + this.inputContainer = ItemContainer.ensureContainerCapacity(this.inputContainer, inputSlotsCount, SimpleItemContainer::getNewContainer, remainder); + this.inputContainer.registerChangeEvent(EventPriority.LAST, this::onItemChange); + + for (short slot = 0; slot < inputSlotsCount; slot++) { + ProcessingBench.ProcessingSlot inputSlot = input[slot]; + String resourceTypeId = inputSlot.getResourceTypeId(); + boolean shouldFilterValidIngredients = inputSlot.shouldFilterValidIngredients(); + if (resourceTypeId != null) { + this.inputContainer.setSlotFilter(FilterActionType.ADD, slot, new ResourceFilter(new ResourceQuantity(resourceTypeId, 1))); + } else if (shouldFilterValidIngredients) { + ObjectArrayList validIngredients = new ObjectArrayList<>(); + + for (CraftingRecipe recipe : CraftingPlugin.getBenchRecipes(this.bench.getType(), this.bench.getId())) { + if (!recipe.isRestrictedByBenchTierLevel(this.bench.getId(), tierLevel)) { + List inputMaterials = CraftingManager.getInputMaterials(recipe); + validIngredients.addAll(inputMaterials); + } + } + + this.inputContainer.setSlotFilter(FilterActionType.ADD, slot, (actionType, container, slotIndex, itemStack) -> { + if (itemStack == null) { + return true; + } else { + for (MaterialQuantity ingredient : validIngredients) { + if (CraftingManager.matches(ingredient, itemStack)) { + return true; + } + } + + return false; + } + }); + } + } + + input = this.processingBench.getFuel(); + inputSlotsCount = (short)(input != null ? input.length : 0); + this.fuelContainer = ItemContainer.ensureContainerCapacity(this.fuelContainer, inputSlotsCount, SimpleItemContainer::getNewContainer, remainder); + this.fuelContainer.registerChangeEvent(EventPriority.LAST, this::onItemChange); + if (inputSlotsCount > 0) { + for (int i = 0; i < input.length; i++) { + ProcessingBench.ProcessingSlot fuel = input[i]; + String resourceTypeId = fuel.getResourceTypeId(); + if (resourceTypeId != null) { + this.fuelContainer.setSlotFilter(FilterActionType.ADD, (short)i, new ResourceFilter(new ResourceQuantity(resourceTypeId, 1))); + } + } + } + + short outputSlotsCount = (short)this.processingBench.getOutputSlotsCount(tierLevel); + this.outputContainer = ItemContainer.ensureContainerCapacity(this.outputContainer, outputSlotsCount, SimpleItemContainer::getNewContainer, remainder); + this.outputContainer.registerChangeEvent(EventPriority.LAST, this::onItemChange); + if (outputSlotsCount > 0) { + this.outputContainer.setGlobalFilter(FilterType.ALLOW_OUTPUT_ONLY); + } + + this.combinedItemContainer = new CombinedItemContainer(this.fuelContainer, this.inputContainer, this.outputContainer); + World world = this.getChunk().getWorld(); + Store store = world.getEntityStore().getStore(); + Holder[] itemEntityHolders = this.ejectItems(store, remainder); + if (itemEntityHolders.length > 0) { + world.execute(() -> store.addEntities(itemEntityHolders, AddReason.SPAWN)); + } + + this.inputContainer.registerChangeEvent(EventPriority.LAST, event -> this.updateRecipe()); + if (this.processingBench.getFuel() == null) { + this.setActive(true); + } + } + + @Override + public void tick(float dt, int index, ArchetypeChunk archetypeChunk, @Nonnull Store store, CommandBuffer commandBuffer) { + World world = store.getExternalData().getWorld(); + Store entityStore = world.getEntityStore().getStore(); + BlockType blockType = this.getBlockType(); + String currentState = BlockAccessor.getCurrentInteractionState(blockType); + List outputItemStacks = null; + List inputMaterials = null; + this.processingSlots.clear(); + this.checkForRecipeUpdate(); + if (this.recipe != null) { + outputItemStacks = CraftingManager.getOutputItemStacks(this.recipe); + if (!this.outputContainer.canAddItemStacks(outputItemStacks, false, false)) { + if ("Processing".equals(currentState)) { + this.setBlockInteractionState("default", blockType); + this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore); + } else if ("ProcessCompleted".equals(currentState)) { + this.setBlockInteractionState("default", blockType); + this.playSound(world, this.processingBench.getEndSoundEventIndex(), entityStore); + } + + this.setActive(false); + return; + } + + inputMaterials = CraftingManager.getInputMaterials(this.recipe); + List result = this.inputContainer.getSlotMaterialsToRemove(inputMaterials, true, true); + if (result.isEmpty()) { + if ("Processing".equals(currentState)) { + this.setBlockInteractionState("default", blockType); + this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore); + } else if ("ProcessCompleted".equals(currentState)) { + this.setBlockInteractionState("default", blockType); + this.playSound(world, this.processingBench.getEndSoundEventIndex(), entityStore); + } + + this.inputProgress = 0.0F; + this.setActive(false); + this.recipeId = null; + this.recipe = null; + return; + } + + for (TestRemoveItemSlotResult item : result) { + this.processingSlots.addAll(item.getPickedSlots()); + } + + this.sendProcessingSlots(); + } else { + if (this.processingBench.getFuel() == null) { + if ("Processing".equals(currentState)) { + this.setBlockInteractionState("default", blockType); + this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore); + } else if ("ProcessCompleted".equals(currentState)) { + this.setBlockInteractionState("default", blockType); + this.playSound(world, this.processingBench.getEndSoundEventIndex(), entityStore); + } + + return; + } + + boolean allowNoInputProcessing = this.processingBench.shouldAllowNoInputProcessing(); + if (!allowNoInputProcessing && "Processing".equals(currentState)) { + this.setBlockInteractionState("default", blockType); + this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore); + } else if ("ProcessCompleted".equals(currentState)) { + this.setBlockInteractionState("default", blockType); + this.playSound(world, this.processingBench.getEndSoundEventIndex(), entityStore); + this.setActive(false); + this.sendProgress(0.0F); + return; + } + + this.sendProgress(0.0F); + if (!allowNoInputProcessing) { + this.setActive(false); + return; + } + } + + boolean needsUpdate = false; + if (this.fuelTime > 0.0F && this.active) { + this.fuelTime -= dt; + if (this.fuelTime < 0.0F) { + this.fuelTime = 0.0F; + } + + needsUpdate = true; + } + + ProcessingBench.ProcessingSlot[] fuelSlots = this.processingBench.getFuel(); + boolean hasFuelSlots = fuelSlots != null && fuelSlots.length > 0; + if ((this.processingBench.getMaxFuel() <= 0 || this.fuelTime < this.processingBench.getMaxFuel()) && !this.fuelContainer.isEmpty()) { + if (!hasFuelSlots) { + return; + } + + if (this.active) { + if (this.fuelTime > 0.0F) { + for (int i = 0; i < fuelSlots.length; i++) { + ItemStack itemInSlot = this.fuelContainer.getItemStack((short)i); + if (itemInSlot != null) { + this.processingFuelSlots.add((short)i); + break; + } + } + } else { + if (this.fuelTime < 0.0F) { + this.fuelTime = 0.0F; + } + + this.processingFuelSlots.clear(); + + for (int ix = 0; ix < fuelSlots.length; ix++) { + ProcessingBench.ProcessingSlot fuelSlot = fuelSlots[ix]; + String resourceTypeId = fuelSlot.getResourceTypeId() != null ? fuelSlot.getResourceTypeId() : "Fuel"; + ResourceQuantity resourceQuantity = new ResourceQuantity(resourceTypeId, 1); + ItemStack slot = this.fuelContainer.getItemStack((short)ix); + if (slot != null) { + double fuelQuality = slot.getItem().getFuelQuality(); + ResourceTransaction transaction = this.fuelContainer.removeResource(resourceQuantity, true, true, true); + this.processingFuelSlots.add((short)ix); + if (transaction.getRemainder() <= 0) { + ProcessingBench.ExtraOutput extra = this.processingBench.getExtraOutput(); + if (extra != null && !extra.isIgnoredFuelSource(slot.getItem())) { + this.nextExtra--; + if (this.nextExtra <= 0) { + this.nextExtra = extra.getPerFuelItemsConsumed(); + ObjectArrayList extraItemStacks = new ObjectArrayList<>(extra.getOutputs().length); + + for (MaterialQuantity e : extra.getOutputs()) { + extraItemStacks.add(e.toItemStack()); + } + + ListTransaction addTransaction = this.outputContainer.addItemStacks(extraItemStacks, false, false, false); + List remainderItems = new ObjectArrayList<>(); + + for (ItemStackTransaction itemStackTransaction : addTransaction.getList()) { + ItemStack remainder = itemStackTransaction.getRemainder(); + if (remainder != null && !remainder.isEmpty()) { + remainderItems.add(remainder); + } + } + + if (!remainderItems.isEmpty()) { + LOGGER.at(Level.WARNING).log("Dropping excess items at %s", this.getBlockPosition()); + Holder[] itemEntityHolders = this.ejectItems(entityStore, remainderItems); + entityStore.addEntities(itemEntityHolders, AddReason.SPAWN); + } + } + } + + this.fuelTime = (float)(this.fuelTime + transaction.getConsumed() * fuelQuality); + needsUpdate = true; + break; + } + } + } + } + } + } + + if (needsUpdate) { + this.updateFuelValues(); + } + + if (!hasFuelSlots || this.active && !(this.fuelTime <= 0.0F)) { + if (!"Processing".equals(currentState)) { + this.setBlockInteractionState("Processing", blockType); + } + + if (this.recipe != null && (this.fuelTime > 0.0F || this.processingBench.getFuel() == null)) { + this.inputProgress += dt; + } + + if (this.recipe != null) { + float recipeTime = this.recipe.getTimeSeconds(); + float craftingTimeReductionModifier = this.getCraftingTimeReductionModifier(); + if (craftingTimeReductionModifier > 0.0F) { + recipeTime -= recipeTime * craftingTimeReductionModifier; + } + + if (this.inputProgress > recipeTime) { + if (recipeTime > 0.0F) { + this.inputProgress -= recipeTime; + float progressPercent = this.inputProgress / recipeTime; + this.sendProgress(progressPercent); + } else { + this.inputProgress = 0.0F; + this.sendProgress(0.0F); + } + + LOGGER.at(Level.FINE).log("Do Process for %s %s", this.recipeId, this.recipe); + if (inputMaterials != null) { + List remainderItems = new ObjectArrayList<>(); + int success = 0; + IntArrayList slots = new IntArrayList(); + + for (int j = 0; j < this.inputContainer.getCapacity(); j++) { + slots.add(j); + } + + for (MaterialQuantity material : inputMaterials) { + for (int ixx = 0; ixx < slots.size(); ixx++) { + int slot = slots.getInt(ixx); + MaterialSlotTransaction transaction = this.inputContainer.removeMaterialFromSlot((short)slot, material, true, true, true); + if (transaction.succeeded()) { + success++; + slots.removeInt(ixx); + break; + } + } + } + + ListTransaction addTransaction = this.outputContainer.addItemStacks(outputItemStacks, false, false, false); + if (!addTransaction.succeeded()) { + return; + } + + for (ItemStackTransaction itemStackTransactionx : addTransaction.getList()) { + ItemStack remainder = itemStackTransactionx.getRemainder(); + if (remainder != null && !remainder.isEmpty()) { + remainderItems.add(remainder); + } + } + + if (success == inputMaterials.size()) { + this.setBlockInteractionState("ProcessCompleted", blockType); + this.playSound(world, this.bench.getCompletedSoundEventIndex(), entityStore); + if (!remainderItems.isEmpty()) { + LOGGER.at(Level.WARNING).log("Dropping excess items at %s", this.getBlockPosition()); + Holder[] itemEntityHolders = this.ejectItems(entityStore, remainderItems); + entityStore.addEntities(itemEntityHolders, AddReason.SPAWN); + } + + return; + } + } + + List remainderItems = new ObjectArrayList<>(); + ListTransaction transaction = this.inputContainer.removeMaterials(inputMaterials, true, true, true); + if (!transaction.succeeded()) { + LOGGER.at(Level.WARNING).log("Failed to remove input materials at %s", this.getBlockPosition()); + this.setBlockInteractionState("default", blockType); + this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore); + return; + } + + this.setBlockInteractionState("ProcessCompleted", blockType); + this.playSound(world, this.bench.getCompletedSoundEventIndex(), entityStore); + ListTransaction addTransactionx = this.outputContainer.addItemStacks(outputItemStacks, false, false, false); + if (addTransactionx.succeeded()) { + return; + } + + LOGGER.at(Level.WARNING).log("Dropping excess items at %s", this.getBlockPosition()); + + for (ItemStackTransaction itemStackTransactionxx : addTransactionx.getList()) { + ItemStack remainder = itemStackTransactionxx.getRemainder(); + if (remainder != null && !remainder.isEmpty()) { + remainderItems.add(remainder); + } + } + + Holder[] itemEntityHolders = this.ejectItems(entityStore, remainderItems); + entityStore.addEntities(itemEntityHolders, AddReason.SPAWN); + } else if (this.recipe != null && recipeTime > 0.0F) { + float progressPercent = this.inputProgress / recipeTime; + this.sendProgress(progressPercent); + } else { + this.sendProgress(0.0F); + } + } + } else { + this.lastConsumedFuelTotal = 0; + if ("Processing".equals(currentState)) { + this.setBlockInteractionState("default", blockType); + this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore); + if (this.processingBench.getFuel() != null) { + this.setActive(false); + } + } else if ("ProcessCompleted".equals(currentState)) { + this.setBlockInteractionState("default", blockType); + this.playSound(world, this.processingBench.getFailedSoundEventIndex(), entityStore); + if (this.processingBench.getFuel() != null) { + this.setActive(false); + } + } + } + } + + private float getCraftingTimeReductionModifier() { + BenchTierLevel levelData = this.bench.getTierLevel(this.getTierLevel()); + return levelData != null ? levelData.getCraftingTimeReductionModifier() : 0.0F; + } + + @Nonnull + private Holder[] ejectItems(@Nonnull ComponentAccessor accessor, @Nonnull List itemStacks) { + if (itemStacks.isEmpty()) { + return Holder.emptyArray(); + } else { + RotationTuple rotation = RotationTuple.get(this.getRotationIndex()); + Vector3d frontDir = new Vector3d(0.0, 0.0, 1.0); + rotation.yaw().rotateY(frontDir, frontDir); + BlockType blockType = this.getBlockType(); + Vector3d dropPosition; + if (blockType == null) { + dropPosition = this.getBlockPosition().toVector3d().add(0.5, 0.0, 0.5); + } else { + BlockBoundingBoxes hitboxAsset = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex()); + if (hitboxAsset == null) { + dropPosition = this.getBlockPosition().toVector3d().add(0.5, 0.0, 0.5); + } else { + double depth = hitboxAsset.get(0).getBoundingBox().depth(); + double frontOffset = depth / 2.0 + 0.1F; + dropPosition = this.getCenteredBlockPosition(); + dropPosition.add(frontDir.x * frontOffset, 0.0, frontDir.z * frontOffset); + } + } + + ThreadLocalRandom random = ThreadLocalRandom.current(); + ObjectArrayList> result = new ObjectArrayList<>(itemStacks.size()); + + for (ItemStack item : itemStacks) { + float velocityX = (float)(frontDir.x * 2.0 + 2.0 * (random.nextDouble() - 0.5)); + float velocityZ = (float)(frontDir.z * 2.0 + 2.0 * (random.nextDouble() - 0.5)); + Holder holder = ItemComponent.generateItemDrop(accessor, item, dropPosition, Vector3f.ZERO, velocityX, 3.25F, velocityZ); + if (holder != null) { + result.add(holder); + } + } + + return result.toArray(Holder[]::new); + } + } + + private void sendProgress(float progress) { + this.windows.forEach((uuid, window) -> window.setProgress(progress)); + } + + private void sendProcessingSlots() { + this.windows.forEach((uuid, window) -> window.setProcessingSlots(this.processingSlots)); + } + + private void sendProcessingFuelSlots() { + this.windows.forEach((uuid, window) -> window.setProcessingFuelSlots(this.processingFuelSlots)); + } + + public boolean isActive() { + return this.active; + } + + public boolean setActive(boolean active) { + if (this.active != active) { + if (active && this.processingBench.getFuel() != null && this.fuelContainer.isEmpty()) { + return false; + } else { + this.active = active; + if (!active) { + this.processingSlots.clear(); + this.processingFuelSlots.clear(); + this.sendProcessingSlots(); + this.sendProcessingFuelSlots(); + } + + this.updateRecipe(); + this.windows.forEach((uuid, window) -> window.setActive(active)); + this.markNeedsSave(); + return true; + } + } else { + return false; + } + } + + public void updateFuelValues() { + if (this.fuelTime > this.lastConsumedFuelTotal) { + this.lastConsumedFuelTotal = MathUtil.ceil(this.fuelTime); + } + + float fuelPercent = this.lastConsumedFuelTotal > 0 ? this.fuelTime / this.lastConsumedFuelTotal : 0.0F; + this.windows.forEach((uuid, window) -> { + window.setFuelTime(fuelPercent); + window.setMaxFuel(this.lastConsumedFuelTotal); + window.setProcessingFuelSlots(this.processingFuelSlots); + }); + } + + @Override + public void onDestroy() { + super.onDestroy(); + WindowManager.closeAndRemoveAll(this.windows); + if (this.combinedItemContainer != null) { + List itemStacks = this.combinedItemContainer.dropAllItemStacks(); + this.dropFuelItems(itemStacks); + World world = this.getChunk().getWorld(); + Store entityStore = world.getEntityStore().getStore(); + Vector3d dropPosition = this.getBlockPosition().toVector3d().add(0.5, 0.0, 0.5); + Holder[] itemEntityHolders = ItemComponent.generateItemDrops(entityStore, itemStacks, dropPosition, Vector3f.ZERO); + if (itemEntityHolders.length > 0) { + world.execute(() -> entityStore.addEntities(itemEntityHolders, AddReason.SPAWN)); + } + } + + if (this.marker != null) { + this.marker.remove(); + } + } + + public CombinedItemContainer getItemContainer() { + return this.combinedItemContainer; + } + + private void checkForRecipeUpdate() { + if (this.recipe == null && this.recipeId != null) { + this.updateRecipe(); + } + } + + private void updateRecipe() { + List recipes = CraftingPlugin.getBenchRecipes(this.bench.getType(), this.bench.getId()); + if (recipes.isEmpty()) { + this.clearRecipe(); + } else { + List matching = new ObjectArrayList<>(); + + for (CraftingRecipe recipe : recipes) { + if (!recipe.isRestrictedByBenchTierLevel(this.bench.getId(), this.getTierLevel())) { + MaterialQuantity[] input = recipe.getInput(); + int matches = 0; + IntArrayList slots = new IntArrayList(); + + for (int j = 0; j < this.inputContainer.getCapacity(); j++) { + slots.add(j); + } + + for (MaterialQuantity craftingMaterial : input) { + String itemId = craftingMaterial.getItemId(); + String resourceTypeId = craftingMaterial.getResourceTypeId(); + int materialQuantity = craftingMaterial.getQuantity(); + BsonDocument metadata = craftingMaterial.getMetadata(); + MaterialQuantity material = new MaterialQuantity(itemId, resourceTypeId, null, materialQuantity, metadata); + + for (int k = 0; k < slots.size(); k++) { + int j = slots.getInt(k); + int out = InternalContainerUtilMaterial.testRemoveMaterialFromSlot(this.inputContainer, (short)j, material, material.getQuantity(), true); + if (out == 0) { + matches++; + slots.removeInt(k); + break; + } + } + } + + if (matches == input.length) { + matching.add(recipe); + } + } + } + + if (matching.isEmpty()) { + this.clearRecipe(); + } else { + matching.sort(Comparator.comparingInt(o -> CraftingManager.getInputMaterials(o).size())); + Collections.reverse(matching); + if (this.recipeId != null) { + for (CraftingRecipe rec : matching) { + if (Objects.equals(this.recipeId, rec.getId())) { + LOGGER.at(Level.FINE).log("%s - Keeping existing Recipe %s %s", LazyArgs.lazy(this::getBlockPosition), this.recipeId, rec); + this.recipe = rec; + return; + } + } + } + + CraftingRecipe recipex = matching.getFirst(); + if (this.recipeId == null || !Objects.equals(this.recipeId, recipex.getId())) { + this.inputProgress = 0.0F; + this.sendProgress(0.0F); + } + + this.recipeId = recipex.getId(); + this.recipe = recipex; + LOGGER.at(Level.FINE).log("%s - Found Recipe %s %s", LazyArgs.lazy(this::getBlockPosition), this.recipeId, this.recipe); + } + } + } + + private void clearRecipe() { + this.recipeId = null; + this.recipe = null; + this.lastConsumedFuelTotal = 0; + this.inputProgress = 0.0F; + this.sendProgress(0.0F); + LOGGER.at(Level.FINE).log("%s - Cleared Recipe", LazyArgs.lazy(this::getBlockPosition)); + } + + public void dropFuelItems(@Nonnull List itemStacks) { + String fuelDropItemId = this.processingBench.getFuelDropItemId(); + if (fuelDropItemId != null) { + Item item = Item.getAssetMap().getAsset(fuelDropItemId); + int dropAmount = (int)this.fuelTime; + this.fuelTime = 0.0F; + + while (dropAmount > 0) { + int quantity = Math.min(dropAmount, item.getMaxStack()); + itemStacks.add(new ItemStack(fuelDropItemId, quantity)); + dropAmount -= quantity; + } + } else { + LOGGER.at(Level.WARNING).log("No FuelDropItemId defined for %s fuel value of %s will be lost!", this.bench.getId(), this.fuelTime); + } + } + + @Nullable + public CraftingRecipe getRecipe() { + return this.recipe; + } + + @Nonnull + public Map getWindows() { + return this.windows; + } + + public float getInputProgress() { + return this.inputProgress; + } + + public void onItemChange(ItemContainer.ItemContainerChangeEvent event) { + this.markNeedsSave(); + } + + public void setBlockInteractionState(@Nonnull String state, @Nonnull BlockType blockType) { + this.getChunk().setBlockInteractionState(this.getBlockPosition(), blockType, state); + } + + @Override + public void setMarker(WorldMapManager.MarkerReference marker) { + this.marker = marker; + this.markNeedsSave(); + } + + @Override + public void placedBy( + @Nonnull Ref playerRef, + @Nonnull String blockTypeKey, + @Nonnull BlockState blockState, + @Nonnull ComponentAccessor componentAccessor + ) { + if (blockTypeKey.equals(this.processingBench.getIconItem()) && this.processingBench.getIcon() != null) { + Player playerComponent = componentAccessor.getComponent(playerRef, Player.getComponentType()); + + assert playerComponent != null; + + TransformComponent transformComponent = componentAccessor.getComponent(playerRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Transform transformPacket = PositionUtil.toTransformPacket(transformComponent.getTransform()); + transformPacket.orientation.yaw = 0.0F; + transformPacket.orientation.pitch = 0.0F; + transformPacket.orientation.roll = 0.0F; + MapMarker marker = new MapMarker( + this.processingBench.getIconId() + "-" + UUID.randomUUID(), + this.processingBench.getIconName(), + this.processingBench.getIcon(), + transformPacket, + null + ); + ((MarkerBlockState)blockState).setMarker(WorldMapManager.createPlayerMarker(playerRef, marker, componentAccessor)); + } + } + + private void playSound(@Nonnull World world, int soundEventIndex, @Nonnull ComponentAccessor componentAccessor) { + if (soundEventIndex != 0) { + Vector3i pos = this.getBlockPosition(); + SoundUtil.playSoundEvent3d(soundEventIndex, SoundCategory.SFX, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, componentAccessor); + } + } + + @Override + protected void onTierLevelChange() { + super.onTierLevelChange(); + this.setupSlots(); + } +} diff --git a/src/com/hypixel/hytale/builtin/crafting/system/PlayerCraftingSystems.java b/src/com/hypixel/hytale/builtin/crafting/system/PlayerCraftingSystems.java new file mode 100644 index 0000000..5bd80e2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crafting/system/PlayerCraftingSystems.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.builtin.crafting.system; + +import com.hypixel.hytale.builtin.crafting.component.CraftingManager; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PlayerCraftingSystems { + public PlayerCraftingSystems() { + } + + public static class CraftingManagerAddSystem extends HolderSystem { + private final ComponentType playerComponentType = Player.getComponentType(); + private final ComponentType craftingManagerComponentType; + + public CraftingManagerAddSystem(ComponentType craftingManagerComponentType) { + this.craftingManagerComponentType = craftingManagerComponentType; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + Player player = holder.getComponent(Player.getComponentType()); + if (player == null) { + throw new UnsupportedOperationException("Cannot have null player component during crafting system creation"); + } else { + holder.ensureComponent(this.craftingManagerComponentType); + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + CraftingManager craftingManager = holder.getComponent(this.craftingManagerComponentType); + if (craftingManager != null) { + Player player = holder.getComponent(this.playerComponentType); + + try { + Ref ref = player.getReference(); + craftingManager.cancelAllCrafting(ref, store); + } finally { + World world = store.getExternalData().getWorld(); + if (world.getWorldConfig().isSavingPlayers() && player != null) { + player.saveConfig(world, holder); + } + } + } + } + + @Nonnull + @Override + public Query getQuery() { + return this.playerComponentType; + } + } + + public static class PlayerCraftingSystem extends EntityTickingSystem { + private final ComponentType craftingManagerComponentType; + + public PlayerCraftingSystem(ComponentType craftingManagerComponentType) { + this.craftingManagerComponentType = craftingManagerComponentType; + } + + @Override + public Query getQuery() { + return this.craftingManagerComponentType; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Ref ref = archetypeChunk.getReferenceTo(index); + CraftingManager craftingManagerComponent = archetypeChunk.getComponent(index, this.craftingManagerComponentType); + craftingManagerComponent.tick(ref, commandBuffer, dt); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/crafting/window/BenchWindow.java b/src/com/hypixel/hytale/builtin/crafting/window/BenchWindow.java new file mode 100644 index 0000000..cba3c37 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crafting/window/BenchWindow.java @@ -0,0 +1,134 @@ +package com.hypixel.hytale.builtin.crafting.window; + +import com.google.gson.JsonObject; +import com.hypixel.hytale.builtin.adventure.memories.MemoriesPlugin; +import com.hypixel.hytale.builtin.crafting.component.CraftingManager; +import com.hypixel.hytale.builtin.crafting.state.BenchState; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.window.WindowType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.Bench; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.entities.player.windows.BlockWindow; +import com.hypixel.hytale.server.core.entity.entities.player.windows.MaterialContainerWindow; +import com.hypixel.hytale.server.core.entity.entities.player.windows.MaterialExtraResourcesSection; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public abstract class BenchWindow extends BlockWindow implements MaterialContainerWindow { + private static final float CRAFTING_UPDATE_MIN_PERCENT = 0.05F; + private static final long CRAFTING_UPDATE_INTERVAL_MS = 500L; + protected static final String BENCH_UPGRADING = "BenchUpgrading"; + private float lastUpdatePercent; + private long lastUpdateTimeMs; + protected final Bench bench; + protected final BenchState benchState; + protected final JsonObject windowData = new JsonObject(); + @Nonnull + private MaterialExtraResourcesSection extraResourcesSection = new MaterialExtraResourcesSection(); + + public BenchWindow(@Nonnull WindowType windowType, @Nonnull BenchState benchState) { + super(windowType, benchState.getBlockX(), benchState.getBlockY(), benchState.getBlockZ(), benchState.getRotationIndex(), benchState.getBlockType()); + this.bench = this.blockType.getBench(); + this.benchState = benchState; + Item item = this.blockType.getItem(); + this.windowData.addProperty("type", this.bench.getType().ordinal()); + this.windowData.addProperty("id", this.bench.getId()); + this.windowData.addProperty("name", item.getTranslationKey()); + this.windowData.addProperty("blockItemId", item.getId()); + this.windowData.addProperty("tierLevel", this.getBenchTierLevel()); + } + + @Nonnull + @Override + public JsonObject getData() { + return this.windowData; + } + + @Override + protected boolean onOpen0() { + PlayerRef playerRef = this.getPlayerRef(); + Ref ref = playerRef.getReference(); + Store store = ref.getStore(); + CraftingManager craftingManager = store.getComponent(ref, CraftingManager.getComponentType()); + craftingManager.setBench(this.x, this.y, this.z, this.blockType); + World world = store.getExternalData().getWorld(); + this.windowData.addProperty("worldMemoriesLevel", MemoriesPlugin.get().getMemoriesLevel(world.getGameplayConfig())); + return true; + } + + protected int getBenchTierLevel() { + return this.benchState != null ? this.benchState.getTierLevel() : 1; + } + + @Override + public void onClose0() { + PlayerRef playerRef = this.getPlayerRef(); + Ref ref = playerRef.getReference(); + Store store = ref.getStore(); + CraftingManager craftingManagerComponent = store.getComponent(ref, CraftingManager.getComponentType()); + + assert craftingManagerComponent != null; + + if (craftingManagerComponent.clearBench(ref, store) && this.bench.getFailedSoundEventIndex() != 0) { + SoundUtil.playSoundEvent2d(ref, this.bench.getFailedSoundEventIndex(), SoundCategory.UI, store); + } + } + + public void updateCraftingJob(float percent) { + this.windowData.addProperty("progress", percent); + this.checkProgressInvalidate(percent); + } + + public void updateBenchUpgradeJob(float percent) { + this.windowData.addProperty("tierUpgradeProgress", percent); + this.checkProgressInvalidate(percent); + } + + private void checkProgressInvalidate(float percent) { + if (this.lastUpdatePercent != percent) { + long time = System.currentTimeMillis(); + if (percent >= 1.0F + || percent < this.lastUpdatePercent + || percent - this.lastUpdatePercent > 0.05F + || time - this.lastUpdateTimeMs > 500L + || this.lastUpdateTimeMs == 0L) { + this.lastUpdatePercent = percent; + this.lastUpdateTimeMs = time; + this.invalidate(); + } + } + } + + public void updateBenchTierLevel(int newValue) { + this.windowData.addProperty("tierLevel", newValue); + this.updateBenchUpgradeJob(0.0F); + this.setNeedRebuild(); + this.invalidate(); + } + + @Nonnull + @Override + public MaterialExtraResourcesSection getExtraResourcesSection() { + if (!this.extraResourcesSection.isValid()) { + CraftingManager.feedExtraResourcesSection(this.benchState, this.extraResourcesSection); + } + + return this.extraResourcesSection; + } + + @Override + public void invalidateExtraResources() { + this.extraResourcesSection.setValid(false); + this.invalidate(); + } + + @Override + public boolean isValid() { + return this.extraResourcesSection.isValid(); + } +} diff --git a/src/com/hypixel/hytale/builtin/crafting/window/CraftingWindow.java b/src/com/hypixel/hytale/builtin/crafting/window/CraftingWindow.java new file mode 100644 index 0000000..f08ee25 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crafting/window/CraftingWindow.java @@ -0,0 +1,142 @@ +package com.hypixel.hytale.builtin.crafting.window; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.hypixel.hytale.builtin.adventure.memories.MemoriesGameplayConfig; +import com.hypixel.hytale.builtin.crafting.CraftingPlugin; +import com.hypixel.hytale.builtin.crafting.component.CraftingManager; +import com.hypixel.hytale.builtin.crafting.state.BenchState; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.window.CraftRecipeAction; +import com.hypixel.hytale.protocol.packets.window.WindowType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.CraftingBench; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import javax.annotation.Nonnull; + +public abstract class CraftingWindow extends BenchWindow { + public static final int SET_BLOCK_SETTINGS = 6; + protected static final String CRAFT_COMPLETED = "CraftCompleted"; + protected static final String CRAFT_COMPLETED_INSTANT = "CraftCompletedInstant"; + + public CraftingWindow(@Nonnull WindowType windowType, BenchState benchState) { + super(windowType, benchState); + JsonArray categories = new JsonArray(); + if (this.bench instanceof CraftingBench craftingBench) { + for (CraftingBench.BenchCategory benchCategory : craftingBench.getCategories()) { + JsonObject category = new JsonObject(); + categories.add(category); + category.addProperty("id", benchCategory.getId()); + category.addProperty("name", benchCategory.getName()); + category.addProperty("icon", benchCategory.getIcon()); + Set recipes = CraftingPlugin.getAvailableRecipesForCategory(this.bench.getId(), benchCategory.getId()); + if (recipes != null) { + JsonArray recipesArray = new JsonArray(); + + for (String recipeId : recipes) { + recipesArray.add(recipeId); + } + + category.add("craftableRecipes", recipesArray); + } + + if (benchCategory.getItemCategories() != null) { + JsonArray itemCategories = new JsonArray(); + + for (CraftingBench.BenchItemCategory benchItemCategory : benchCategory.getItemCategories()) { + JsonObject itemCategory = new JsonObject(); + itemCategory.addProperty("id", benchItemCategory.getId()); + itemCategory.addProperty("icon", benchItemCategory.getIcon()); + itemCategory.addProperty("diagram", benchItemCategory.getDiagram()); + itemCategory.addProperty("slots", benchItemCategory.getSlots()); + itemCategory.addProperty("specialSlot", benchItemCategory.isSpecialSlot()); + itemCategories.add(itemCategory); + } + + category.add("itemCategories", itemCategories); + } + } + + this.windowData.add("categories", categories); + } + } + + @Override + protected boolean onOpen0() { + super.onOpen0(); + PlayerRef playerRef = this.getPlayerRef(); + Ref ref = playerRef.getReference(); + Store store = ref.getStore(); + GameplayConfig gameplayConfig = store.getExternalData().getWorld().getGameplayConfig(); + MemoriesGameplayConfig memoriesConfig = MemoriesGameplayConfig.get(gameplayConfig); + if (memoriesConfig != null) { + int[] memoriesAmountPerLevel = memoriesConfig.getMemoriesAmountPerLevel(); + if (memoriesAmountPerLevel != null && memoriesAmountPerLevel.length > 1) { + JsonArray memoriesPerLevel = new JsonArray(); + + for (int i = 0; i < memoriesAmountPerLevel.length; i++) { + memoriesPerLevel.add(memoriesAmountPerLevel[i]); + } + + this.windowData.add("memoriesPerLevel", memoriesPerLevel); + } + } + + if (this.bench.getLocalOpenSoundEventIndex() != 0) { + SoundUtil.playSoundEvent2d(ref, this.bench.getLocalOpenSoundEventIndex(), SoundCategory.UI, store); + } + + return true; + } + + @Override + public void onClose0() { + super.onClose0(); + PlayerRef playerRef = this.getPlayerRef(); + Ref ref = playerRef.getReference(); + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + this.setBlockInteractionState(this.benchState.getTierStateName(), world, 6); + if (this.bench.getLocalCloseSoundEventIndex() != 0) { + SoundUtil.playSoundEvent2d(ref, this.bench.getLocalCloseSoundEventIndex(), SoundCategory.UI, store); + } + } + + public void setBlockInteractionState(@Nonnull String state, @Nonnull World world, int setBlockSettings) { + WorldChunk worldChunk = world.getChunk(ChunkUtil.indexChunkFromBlock(this.x, this.z)); + BlockType blockType = worldChunk.getBlockType(this.x, this.y, this.z); + worldChunk.setBlockInteractionState(this.x, this.y, this.z, blockType, state, true); + } + + public static boolean craftSimpleItem( + @Nonnull Store store, @Nonnull Ref ref, @Nonnull CraftingManager craftingManager, @Nonnull CraftRecipeAction action + ) { + String recipeId = action.recipeId; + int quantity = action.quantity; + if (recipeId == null) { + return false; + } else { + CraftingRecipe recipe = CraftingRecipe.getAssetMap().getAsset(recipeId); + if (recipe == null) { + PlayerRef playerRef = store.getComponent(ref, PlayerRef.getComponentType()); + playerRef.getPacketHandler().disconnect("Attempted to craft unknown recipe!"); + return false; + } else { + Player player = store.getComponent(ref, Player.getComponentType()); + craftingManager.craftItem(ref, store, recipe, quantity, player.getInventory().getCombinedBackpackStorageHotbar()); + return true; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/crafting/window/DiagramCraftingWindow.java b/src/com/hypixel/hytale/builtin/crafting/window/DiagramCraftingWindow.java new file mode 100644 index 0000000..619fe68 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crafting/window/DiagramCraftingWindow.java @@ -0,0 +1,349 @@ +package com.hypixel.hytale.builtin.crafting.window; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.hypixel.hytale.builtin.crafting.CraftingPlugin; +import com.hypixel.hytale.builtin.crafting.component.CraftingManager; +import com.hypixel.hytale.builtin.crafting.state.BenchState; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.EventPriority; +import com.hypixel.hytale.event.EventRegistration; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.window.CancelCraftingAction; +import com.hypixel.hytale.protocol.packets.window.CraftItemAction; +import com.hypixel.hytale.protocol.packets.window.UpdateCategoryAction; +import com.hypixel.hytale.protocol.packets.window.WindowAction; +import com.hypixel.hytale.protocol.packets.window.WindowType; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.CraftingBench; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.DiagramCraftingBench; +import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.windows.ItemContainerWindow; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterActionType; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterType; +import com.hypixel.hytale.server.core.inventory.container.filter.SlotFilter; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DiagramCraftingWindow extends CraftingWindow implements ItemContainerWindow { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private String category; + private String itemCategory; + private CraftingBench.BenchItemCategory benchItemCategory; + private SimpleItemContainer inputPrimaryContainer; + private SimpleItemContainer inputSecondaryContainer; + private CombinedItemContainer combinedInputItemContainer; + private SimpleItemContainer outputContainer; + private CombinedItemContainer combinedItemContainer; + private EventRegistration inventoryRegistration; + + public DiagramCraftingWindow(@Nonnull ComponentAccessor store, BenchState benchState) { + super(WindowType.DiagramCrafting, benchState); + DiagramCraftingBench bench = (DiagramCraftingBench)this.bench; + if (bench.getCategories() != null && bench.getCategories().length > 0) { + CraftingBench.BenchCategory benchCategory = bench.getCategories()[0]; + this.category = benchCategory.getId(); + if (benchCategory.getItemCategories() != null && benchCategory.getItemCategories().length > 0) { + this.itemCategory = benchCategory.getItemCategories()[0].getId(); + } + } + + this.benchItemCategory = this.getBenchItemCategory(this.category, this.itemCategory); + if (this.benchItemCategory == null) { + throw new IllegalArgumentException("Failed to get category!"); + } else { + this.updateInventory(store, this.benchItemCategory); + } + } + + @Override + protected void finalize() { + if (this.inventoryRegistration.isRegistered()) { + throw new IllegalStateException("Failed to unregister inventory event!"); + } + } + + @Override + public boolean onOpen0() { + boolean result = super.onOpen0(); + PlayerRef playerRef = this.getPlayerRef(); + Ref ref = playerRef.getReference(); + Store store = ref.getStore(); + Player player = store.getComponent(ref, Player.getComponentType()); + Inventory inventory = player.getInventory(); + this.updateInput((ItemContainer)null); + this.inventoryRegistration = inventory.getCombinedHotbarFirst().registerChangeEvent(event -> { + ObjectList recipes = new ObjectArrayList<>(); + this.windowData.add("slots", this.generateSlots(inventory.getCombinedHotbarFirst(), recipes)); + this.invalidate(); + }); + return result; + } + + @Override + public void onClose0() { + PlayerRef playerRef = this.getPlayerRef(); + Ref ref = playerRef.getReference(); + Store store = ref.getStore(); + Player player = store.getComponent(ref, Player.getComponentType()); + List itemStacks = this.combinedInputItemContainer.dropAllItemStacks(); + SimpleItemContainer.addOrDropItemStacks(store, ref, player.getInventory().getCombinedHotbarFirst(), itemStacks); + CraftingManager craftingManager = store.getComponent(ref, CraftingManager.getComponentType()); + craftingManager.cancelAllCrafting(ref, store); + this.inventoryRegistration.unregister(); + super.onClose0(); + } + + @Override + public void handleAction(@Nonnull Ref ref, @Nonnull Store store, @Nonnull WindowAction action) { + World world = store.getExternalData().getWorld(); + PlayerRef playerRef = this.getPlayerRef(); + CraftingManager craftingManager = store.getComponent(ref, CraftingManager.getComponentType()); + switch (action) { + case CancelCraftingAction ignored: + craftingManager.cancelAllCrafting(ref, store); + break; + case UpdateCategoryAction updateAction: + this.category = updateAction.category; + this.itemCategory = updateAction.itemCategory; + this.benchItemCategory = this.getBenchItemCategory(this.category, this.itemCategory); + if (this.benchItemCategory != null) { + this.updateInventory(store, this.benchItemCategory); + } else { + this.getPlayerRef().sendMessage(Message.translation("server.ui.diagramcraftingwindow.invalidCategory")); + this.close(); + } + break; + case CraftItemAction ignoredx: + label45: { + ItemStack itemStack = this.outputContainer.getItemStack((short)0); + if (itemStack == null || itemStack.isEmpty()) { + playerRef.sendMessage(Message.translation("server.ui.diagramcraftingwindow.noOutputItem")); + return; + } + + ObjectList recipes = new ObjectArrayList<>(); + boolean allSlotsFull = this.collectRecipes(recipes); + if (recipes.size() != 1 || !allSlotsFull) { + playerRef.sendMessage(Message.translation("server.ui.diagramcraftingwindow.failedVerifyRecipy")); + return; + } + + CraftingRecipe recipe = recipes.getFirst(); + craftingManager.queueCraft(ref, store, this, 0, recipe, 1, this.combinedInputItemContainer, CraftingManager.InputRemovalType.ORDERED); + String completedState = recipe.getTimeSeconds() > 0.0F ? "CraftCompleted" : "CraftCompletedInstant"; + this.setBlockInteractionState(completedState, world, 70); + if (this.bench.getCompletedSoundEventIndex() != 0) { + SoundUtil.playSoundEvent3d(this.bench.getCompletedSoundEventIndex(), SoundCategory.SFX, this.x + 0.5, this.y + 0.5, this.z + 0.5, store); + } + + if (CraftingPlugin.learnRecipe(ref, recipe.getId(), store)) { + this.updateInput(this.outputContainer); + } + break label45; + } + default: + } + } + + @Nonnull + @Override + public ItemContainer getItemContainer() { + return this.combinedItemContainer; + } + + private CraftingBench.BenchItemCategory getBenchItemCategory(@Nullable String category, @Nullable String itemCategory) { + if (category != null && itemCategory != null) { + DiagramCraftingBench craftingBench = (DiagramCraftingBench)this.bench; + + for (CraftingBench.BenchCategory benchCategory : craftingBench.getCategories()) { + if (category.equals(benchCategory.getId())) { + for (CraftingBench.BenchItemCategory benchItemCategory : benchCategory.getItemCategories()) { + if (itemCategory.equals(benchItemCategory.getId())) { + return benchItemCategory; + } + } + } + } + + return null; + } else { + return null; + } + } + + private void updateInventory(@Nonnull ComponentAccessor store, @Nonnull CraftingBench.BenchItemCategory benchItemCategory) { + if (this.combinedInputItemContainer != null) { + PlayerRef playerRef = this.getPlayerRef(); + Ref ref = playerRef.getReference(); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + List itemStacks = this.combinedInputItemContainer.dropAllItemStacks(); + SimpleItemContainer.addOrDropItemStacks(store, ref, playerComponent.getInventory().getCombinedHotbarFirst(), itemStacks); + } + + this.inputPrimaryContainer = new SimpleItemContainer((short)1); + this.inputSecondaryContainer = new SimpleItemContainer((short)(benchItemCategory.getSlots() + (benchItemCategory.isSpecialSlot() ? 1 : 0))); + this.inputSecondaryContainer.setGlobalFilter(FilterType.ALLOW_OUTPUT_ONLY); + this.combinedInputItemContainer = new CombinedItemContainer(this.inputPrimaryContainer, this.inputSecondaryContainer); + this.combinedInputItemContainer.registerChangeEvent(EventPriority.LAST, this::updateInput); + this.outputContainer = new SimpleItemContainer((short)1); + this.outputContainer.setGlobalFilter(FilterType.DENY_ALL); + this.combinedItemContainer = new CombinedItemContainer(this.combinedInputItemContainer, this.outputContainer); + } + + private void updateInput(@Nonnull ItemContainer.ItemContainerChangeEvent event) { + this.updateInput(event.container()); + } + + private void updateInput(@Nullable ItemContainer container) { + PlayerRef playerRef = this.getPlayerRef(); + Ref ref = playerRef.getReference(); + Store store = ref.getStore(); + Player player = store.getComponent(ref, Player.getComponentType()); + ItemStack primaryItemStack = this.inputPrimaryContainer.getItemStack((short)0); + CombinedItemContainer combinedStorage = player.getInventory().getCombinedHotbarFirst(); + if (primaryItemStack != null && !primaryItemStack.isEmpty()) { + this.inputSecondaryContainer.setGlobalFilter(FilterType.ALLOW_ALL); + boolean needsDropSlot = true; + + for (short i = 0; i < this.inputSecondaryContainer.getCapacity(); i++) { + ItemStack itemStack = this.inputSecondaryContainer.getItemStack(i); + if (itemStack != null && !itemStack.isEmpty()) { + this.inputSecondaryContainer.setSlotFilter(FilterActionType.ADD, i, null); + } else if (needsDropSlot) { + this.inputSecondaryContainer.setSlotFilter(FilterActionType.ADD, i, null); + needsDropSlot = false; + } else { + this.inputSecondaryContainer.setSlotFilter(FilterActionType.ADD, i, SlotFilter.DENY); + } + } + } else { + this.inputSecondaryContainer.setGlobalFilter(FilterType.ALLOW_OUTPUT_ONLY); + if (container != this.inputSecondaryContainer && !this.inputSecondaryContainer.isEmpty()) { + List itemStacks = this.inputSecondaryContainer.dropAllItemStacks(); + SimpleItemContainer.addOrDropItemStacks(store, ref, combinedStorage, itemStacks); + } + } + + List recipes = new ObjectArrayList<>(); + boolean allSlotsFull = this.collectRecipes(recipes); + this.windowData.add("slots", this.generateSlots(combinedStorage, recipes)); + if (recipes.size() == 1 && allSlotsFull) { + CraftingRecipe recipe = recipes.getFirst(); + ItemStack output = CraftingManager.getOutputItemStacks(recipe).getFirst(); + if (player.getPlayerConfigData().getKnownRecipes().contains(recipe.getId())) { + this.outputContainer.setItemStackForSlot((short)0, output); + } else { + this.outputContainer.setItemStackForSlot((short)0, new ItemStack("Unknown", 1)); + } + } else { + if (!recipes.isEmpty() && allSlotsFull) { + LOGGER.at(Level.WARNING).log("Multiple recipes defined for the same materials! %s", recipes); + } + + this.outputContainer.setItemStackForSlot((short)0, ItemStack.EMPTY); + } + + this.invalidate(); + } + + private boolean collectRecipes(@Nonnull List recipes) { + ItemStack primaryItemStack = this.inputPrimaryContainer.getItemStack((short)0); + if (primaryItemStack != null && !primaryItemStack.isEmpty()) { + PlayerRef playerRef = this.getPlayerRef(); + Ref ref = playerRef.getReference(); + Store store = ref.getStore(); + Player player = store.getComponent(ref, Player.getComponentType()); + Set knownRecipes = player.getPlayerConfigData().getKnownRecipes(); + short inputCapacity = this.combinedInputItemContainer.getCapacity(); + boolean allSlotsFull = true; + + label54: + for (CraftingRecipe recipe : this.getBenchRecipes()) { + if (recipe.getInput().length != inputCapacity && (!this.benchItemCategory.isSpecialSlot() || recipe.getInput().length != inputCapacity - 1)) { + LOGGER.at(Level.WARNING) + .log( + "Recipe for %s has different input length than the diagram! %s - %s, %s, %s", + recipe.getId(), + recipe, + this.bench, + this.category, + this.itemCategory + ); + } else if (!recipe.isKnowledgeRequired() || knownRecipes.contains(recipe.getId())) { + for (short i = 0; i < inputCapacity; i++) { + ItemStack itemStack = this.combinedInputItemContainer.getItemStack(i); + if (itemStack != null && !itemStack.isEmpty()) { + if (!CraftingManager.matches(recipe.getInput()[i], itemStack)) { + continue label54; + } + } else if (!this.benchItemCategory.isSpecialSlot() && i == inputCapacity - 1) { + allSlotsFull = false; + } + } + + recipes.add(recipe); + } + } + + return allSlotsFull; + } else { + return false; + } + } + + @Nonnull + private JsonArray generateSlots(@Nonnull CombinedItemContainer combinedStorage, @Nonnull List recipes) { + JsonArray slots = new JsonArray(); + if (recipes.isEmpty()) { + List benchRecipes = this.getBenchRecipes(); + JsonObject slot = new JsonObject(); + slot.add("inventoryHints", CraftingManager.generateInventoryHints(benchRecipes, 0, combinedStorage)); + slots.add(slot); + } else { + for (short i = 0; i < this.combinedInputItemContainer.getCapacity(); i++) { + JsonObject slot = new JsonObject(); + ItemStack itemStack = this.combinedInputItemContainer.getItemStack(i); + if (itemStack == null || itemStack.isEmpty()) { + slot.add("inventoryHints", CraftingManager.generateInventoryHints(recipes, i, combinedStorage)); + } + + int requiredAmount = -1; + if (recipes.size() == 1) { + CraftingRecipe recipe = recipes.getFirst(); + if (i < recipe.getInput().length) { + requiredAmount = recipe.getInput()[i].getQuantity(); + } + } + + slot.addProperty("requiredAmount", requiredAmount); + slots.add(slot); + } + } + + return slots; + } + + @Nonnull + public List getBenchRecipes() { + return CraftingPlugin.getBenchRecipes(this.bench.getType(), this.bench.getId(), this.category + "." + this.itemCategory); + } +} diff --git a/src/com/hypixel/hytale/builtin/crafting/window/FieldCraftingWindow.java b/src/com/hypixel/hytale/builtin/crafting/window/FieldCraftingWindow.java new file mode 100644 index 0000000..6aaeb1d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crafting/window/FieldCraftingWindow.java @@ -0,0 +1,86 @@ +package com.hypixel.hytale.builtin.crafting.window; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.hypixel.hytale.builtin.adventure.memories.MemoriesPlugin; +import com.hypixel.hytale.builtin.crafting.CraftingPlugin; +import com.hypixel.hytale.builtin.crafting.component.CraftingManager; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.BenchType; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.window.CraftRecipeAction; +import com.hypixel.hytale.protocol.packets.window.WindowAction; +import com.hypixel.hytale.protocol.packets.window.WindowType; +import com.hypixel.hytale.server.core.asset.type.item.config.FieldcraftCategory; +import com.hypixel.hytale.server.core.entity.entities.player.windows.Window; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TempAssetIdUtil; +import java.util.Set; +import javax.annotation.Nonnull; + +public class FieldCraftingWindow extends Window { + @Nonnull + private final JsonObject windowData = new JsonObject(); + + public FieldCraftingWindow() { + super(WindowType.PocketCrafting); + this.windowData.addProperty("type", BenchType.Crafting.ordinal()); + this.windowData.addProperty("id", "Fieldcraft"); + this.windowData.addProperty("name", "server.ui.inventory.fieldcraft.title"); + JsonArray categories = new JsonArray(); + + for (FieldcraftCategory fieldcraftCategory : FieldcraftCategory.getAssetMap().getAssetMap().values()) { + JsonObject category = new JsonObject(); + category.addProperty("id", fieldcraftCategory.getId()); + category.addProperty("icon", fieldcraftCategory.getIcon()); + category.addProperty("name", fieldcraftCategory.getName()); + Set recipes = CraftingPlugin.getAvailableRecipesForCategory("Fieldcraft", fieldcraftCategory.getId()); + if (recipes != null) { + JsonArray itemsArray = new JsonArray(); + + for (String recipeId : recipes) { + itemsArray.add(recipeId); + } + + category.add("craftableRecipes", itemsArray); + } + } + + this.windowData.add("categories", categories); + } + + @Nonnull + @Override + public JsonObject getData() { + return this.windowData; + } + + @Override + public boolean onOpen0() { + PlayerRef playerRef = this.getPlayerRef(); + Ref ref = playerRef.getReference(); + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + this.windowData.addProperty("worldMemoriesLevel", MemoriesPlugin.get().getMemoriesLevel(world.getGameplayConfig())); + this.invalidate(); + return true; + } + + @Override + public void onClose0() { + } + + @Override + public void handleAction(@Nonnull Ref ref, @Nonnull Store store, @Nonnull WindowAction action) { + if (action instanceof CraftRecipeAction craftAction) { + CraftingManager craftingManager = store.getComponent(ref, CraftingManager.getComponentType()); + if (CraftingWindow.craftSimpleItem(store, ref, craftingManager, craftAction)) { + SoundUtil.playSoundEvent2d(ref, TempAssetIdUtil.getSoundEventIndex("SFX_Player_Craft_Item_Inventory"), SoundCategory.UI, store); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/crafting/window/ProcessingBenchWindow.java b/src/com/hypixel/hytale/builtin/crafting/window/ProcessingBenchWindow.java new file mode 100644 index 0000000..b9f467d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crafting/window/ProcessingBenchWindow.java @@ -0,0 +1,252 @@ +package com.hypixel.hytale.builtin.crafting.window; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.hypixel.hytale.builtin.crafting.CraftingPlugin; +import com.hypixel.hytale.builtin.crafting.component.CraftingManager; +import com.hypixel.hytale.builtin.crafting.state.ProcessingBenchState; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.EventRegistration; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.window.SetActiveAction; +import com.hypixel.hytale.protocol.packets.window.TierUpgradeAction; +import com.hypixel.hytale.protocol.packets.window.WindowAction; +import com.hypixel.hytale.protocol.packets.window.WindowType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.Bench; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.ProcessingBench; +import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.windows.ItemContainerWindow; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ProcessingBenchWindow extends BenchWindow implements ItemContainerWindow { + private CombinedItemContainer itemContainer; + @Nullable + private EventRegistration inventoryRegistration; + private float fuelTime; + private int maxFuel; + private float progress; + private boolean active; + private final Set processingSlots = new HashSet<>(); + private final Set processingFuelSlots = new HashSet<>(); + + public ProcessingBenchWindow(ProcessingBenchState benchState) { + super(WindowType.Processing, benchState); + ProcessingBench processingBench = (ProcessingBench)this.blockType.getBench(); + CraftingRecipe recipe = benchState.getRecipe(); + float inputProgress = benchState.getInputProgress(); + float progress = recipe != null && recipe.getTimeSeconds() > 0.0F ? inputProgress / recipe.getTimeSeconds() : 0.0F; + this.itemContainer = benchState.getItemContainer(); + this.active = benchState.isActive(); + this.progress = progress; + this.windowData.addProperty("active", this.active); + this.windowData.addProperty("progress", progress); + if (processingBench.getFuel() != null && processingBench.getFuel().length > 0) { + JsonArray fuelArray = new JsonArray(); + + for (ProcessingBench.ProcessingSlot benchSlot : processingBench.getFuel()) { + JsonObject fuelObj = new JsonObject(); + fuelObj.addProperty("icon", benchSlot.getIcon()); + fuelObj.addProperty("resourceTypeId", benchSlot.getResourceTypeId()); + fuelArray.add(fuelObj); + } + + this.windowData.add("fuel", fuelArray); + } + + if (processingBench.getMaxFuel() > 0) { + this.maxFuel = processingBench.getMaxFuel(); + } + + this.windowData.addProperty("maxFuel", this.maxFuel); + this.windowData.addProperty("fuelTime", this.fuelTime); + this.windowData.addProperty("progress", progress); + this.windowData.addProperty("processingFuelSlots", 0); + this.windowData.addProperty("processingSlots", 0); + int tierLevel = this.getBenchTierLevel(); + this.updateInputSlots(tierLevel); + this.updateOutputSlots(tierLevel); + } + + @Nonnull + @Override + public JsonObject getData() { + return this.windowData; + } + + @Nonnull + public CombinedItemContainer getItemContainer() { + return this.itemContainer; + } + + public void setActive(boolean active) { + if (this.active != active) { + this.active = active; + this.windowData.addProperty("active", active); + this.invalidate(); + } + } + + public void setFuelTime(float fuelTime) { + if (Float.isInfinite(fuelTime)) { + throw new IllegalArgumentException("Infinite fuelTime"); + } else if (Float.isNaN(fuelTime)) { + throw new IllegalArgumentException("Nan fuelTime"); + } else { + if (this.fuelTime != fuelTime) { + this.fuelTime = fuelTime; + this.windowData.addProperty("fuelTime", fuelTime); + this.invalidate(); + } + } + } + + public void setMaxFuel(int maxFuel) { + this.maxFuel = maxFuel; + this.windowData.addProperty("maxFuel", maxFuel); + this.invalidate(); + } + + public void setProgress(float progress) { + if (Float.isInfinite(progress)) { + throw new IllegalArgumentException("Infinite progress"); + } else if (Float.isNaN(progress)) { + throw new IllegalArgumentException("Nan fuelTime"); + } else { + if (this.progress != progress) { + this.progress = progress; + this.windowData.addProperty("progress", progress); + this.invalidate(); + } + } + } + + public void setProcessingSlots(Set slots) { + if (!this.processingSlots.equals(slots)) { + this.processingSlots.clear(); + this.processingSlots.addAll(slots); + int bitMask = 0; + + for (Short processingSlot : slots) { + bitMask |= 1 << processingSlot.intValue(); + } + + this.windowData.addProperty("processingSlots", (byte)bitMask); + this.invalidate(); + } + } + + public void setProcessingFuelSlots(Set slots) { + if (!this.processingFuelSlots.equals(slots)) { + this.processingFuelSlots.clear(); + this.processingFuelSlots.addAll(slots); + int bitMask = 0; + + for (Short processingFuelSlots : slots) { + bitMask |= 1 << processingFuelSlots.intValue(); + } + + this.windowData.addProperty("processingFuelSlots", (byte)bitMask); + this.invalidate(); + } + } + + @Override + public void handleAction(@Nonnull Ref ref, @Nonnull Store store, @Nonnull WindowAction action) { + World world = store.getExternalData().getWorld(); + if (world.getChunk(ChunkUtil.indexChunkFromBlock(this.x, this.z)).getState(this.x, this.y, this.z) instanceof ProcessingBenchState benchState) { + switch (action) { + case SetActiveAction setActiveAction: + if (!benchState.setActive(setActiveAction.state)) { + this.invalidate(); + } + break; + case TierUpgradeAction ignored: + label20: { + CraftingManager craftingManager = store.getComponent(ref, CraftingManager.getComponentType()); + if (craftingManager == null) { + return; + } + + if (craftingManager.startTierUpgrade(ref, store, this) && this.bench.getBenchUpgradeSoundEventIndex() != 0) { + SoundUtil.playSoundEvent3d(this.bench.getBenchUpgradeSoundEventIndex(), SoundCategory.SFX, this.x + 0.5, this.y + 0.5, this.z + 0.5, store); + } + break label20; + } + default: + } + } + } + + @Override + protected boolean onOpen0() { + super.onOpen0(); + PlayerRef playerRef = this.getPlayerRef(); + Ref ref = playerRef.getReference(); + Store store = ref.getStore(); + Player player = store.getComponent(ref, Player.getComponentType()); + Inventory inventory = player.getInventory(); + this.inventoryRegistration = inventory.getCombinedHotbarFirst().registerChangeEvent(event -> { + this.windowData.add("inventoryHints", generateInventoryHints(this.bench, inventory.getCombinedHotbarFirst())); + this.invalidate(); + }); + this.windowData.add("inventoryHints", generateInventoryHints(this.bench, inventory.getCombinedHotbarFirst())); + return true; + } + + private void updateOutputSlots(int tierLevel) { + this.windowData.addProperty("outputSlotsCount", ((ProcessingBench)this.blockType.getBench()).getOutputSlotsCount(tierLevel)); + } + + private void updateInputSlots(int tierLevel) { + ProcessingBench.ProcessingSlot[] input = ((ProcessingBench)this.blockType.getBench()).getInput(tierLevel); + if (input != null && input.length > 0) { + JsonArray inputArr = new JsonArray(); + + for (ProcessingBench.ProcessingSlot benchSlot : input) { + if (benchSlot != null) { + JsonObject slotObj = new JsonObject(); + slotObj.addProperty("icon", benchSlot.getIcon()); + inputArr.add(slotObj); + } + } + + this.windowData.add("input", inputArr); + } + } + + @Override + public void updateBenchTierLevel(int newValue) { + super.updateBenchTierLevel(newValue); + this.updateInputSlots(newValue); + this.updateOutputSlots(newValue); + if (this.benchState instanceof ProcessingBenchState processingBenchState) { + this.itemContainer = processingBenchState.getItemContainer(); + } + } + + @Override + public void onClose0() { + super.onClose0(); + if (this.inventoryRegistration != null) { + this.inventoryRegistration.unregister(); + this.inventoryRegistration = null; + } + } + + @Nonnull + private static JsonArray generateInventoryHints(@Nonnull Bench bench, @Nonnull CombinedItemContainer combinedInputItemContainer) { + return CraftingManager.generateInventoryHints(CraftingPlugin.getBenchRecipes(bench), 0, combinedInputItemContainer); + } +} diff --git a/src/com/hypixel/hytale/builtin/crafting/window/SimpleCraftingWindow.java b/src/com/hypixel/hytale/builtin/crafting/window/SimpleCraftingWindow.java new file mode 100644 index 0000000..0f375ef --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crafting/window/SimpleCraftingWindow.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.builtin.crafting.window; + +import com.hypixel.hytale.builtin.crafting.component.CraftingManager; +import com.hypixel.hytale.builtin.crafting.state.BenchState; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.window.CraftRecipeAction; +import com.hypixel.hytale.protocol.packets.window.TierUpgradeAction; +import com.hypixel.hytale.protocol.packets.window.WindowAction; +import com.hypixel.hytale.protocol.packets.window.WindowType; +import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.windows.MaterialContainerWindow; +import com.hypixel.hytale.server.core.entity.entities.player.windows.WindowManager; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SimpleCraftingWindow extends CraftingWindow implements MaterialContainerWindow { + public SimpleCraftingWindow(BenchState benchState) { + super(WindowType.BasicCrafting, benchState); + } + + @Override + public void init(@Nonnull PlayerRef playerRef, @Nonnull WindowManager manager) { + super.init(playerRef, manager); + } + + @Override + public void handleAction(@Nonnull Ref ref, @Nonnull Store store, @Nonnull WindowAction action) { + CraftingManager craftingManager = store.getComponent(ref, CraftingManager.getComponentType()); + if (craftingManager != null) { + World world = store.getExternalData().getWorld(); + if (action instanceof CraftRecipeAction craftAction) { + String recipeId = craftAction.recipeId; + int quantity = craftAction.quantity; + CraftingRecipe craftRecipe = CraftingRecipe.getAssetMap().getAsset(recipeId); + if (craftRecipe == null) { + PlayerRef playerRef = store.getComponent(ref, PlayerRef.getComponentType()); + playerRef.getPacketHandler().disconnect("Attempted to craft unknown recipe!"); + return; + } + + Player player = store.getComponent(ref, Player.getComponentType()); + CombinedItemContainer combined = player.getInventory().getCombinedBackpackStorageHotbar(); + CombinedItemContainer playerAndContainerInventory = new CombinedItemContainer(combined, this.getExtraResourcesSection().getItemContainer()); + boolean accepted; + if (craftRecipe.getTimeSeconds() > 0.0F) { + accepted = craftingManager.queueCraft( + ref, store, this, 0, craftRecipe, quantity, playerAndContainerInventory, CraftingManager.InputRemovalType.NORMAL + ); + } else { + accepted = craftingManager.craftItem(ref, store, craftRecipe, quantity, playerAndContainerInventory); + } + + this.invalidateExtraResources(); + if (accepted) { + String completedState = craftRecipe.getTimeSeconds() > 0.0F ? "CraftCompleted" : "CraftCompletedInstant"; + this.setBlockInteractionState(completedState, world, 70); + if (this.bench.getCompletedSoundEventIndex() != 0) { + Vector3d pos = new Vector3d(); + this.blockType.getBlockCenter(this.rotationIndex, pos); + pos.add(this.x, this.y, this.z); + SoundUtil.playSoundEvent3d(this.bench.getCompletedSoundEventIndex(), SoundCategory.SFX, pos, store); + } + } + } else if (action instanceof TierUpgradeAction && craftingManager.startTierUpgrade(ref, store, this)) { + this.setBlockInteractionState("BenchUpgrading", world, 70); + if (this.bench.getBenchUpgradeSoundEventIndex() != 0) { + Vector3d pos = new Vector3d(); + this.blockType.getBlockCenter(this.rotationIndex, pos); + pos.add(this.x, this.y, this.z); + SoundUtil.playSoundEvent3d(this.bench.getBenchUpgradeSoundEventIndex(), SoundCategory.SFX, pos, store); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/crafting/window/StructuralCraftingWindow.java b/src/com/hypixel/hytale/builtin/crafting/window/StructuralCraftingWindow.java new file mode 100644 index 0000000..b4e1c56 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crafting/window/StructuralCraftingWindow.java @@ -0,0 +1,338 @@ +package com.hypixel.hytale.builtin.crafting.window; + +import com.google.gson.JsonArray; +import com.hypixel.hytale.builtin.crafting.CraftingPlugin; +import com.hypixel.hytale.builtin.crafting.component.CraftingManager; +import com.hypixel.hytale.builtin.crafting.state.BenchState; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.EventRegistration; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.protocol.BenchRequirement; +import com.hypixel.hytale.protocol.ItemSoundEvent; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.window.ChangeBlockAction; +import com.hypixel.hytale.protocol.packets.window.CraftRecipeAction; +import com.hypixel.hytale.protocol.packets.window.SelectSlotAction; +import com.hypixel.hytale.protocol.packets.window.WindowAction; +import com.hypixel.hytale.protocol.packets.window.WindowType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.StructuralCraftingBench; +import com.hypixel.hytale.server.core.asset.type.item.config.BlockGroup; +import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.itemsound.config.ItemSoundSet; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.windows.ItemContainerWindow; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.MaterialQuantity; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterActionType; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterType; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class StructuralCraftingWindow extends CraftingWindow implements ItemContainerWindow { + private static final int MAX_OPTIONS = 64; + private final SimpleItemContainer inputContainer; + private final SimpleItemContainer optionsContainer; + private final CombinedItemContainer combinedItemContainer; + private final Int2ObjectMap optionSlotToRecipeMap = new Int2ObjectOpenHashMap<>(); + private int selectedSlot; + @Nullable + private EventRegistration inventoryRegistration; + + public StructuralCraftingWindow(BenchState benchState) { + super(WindowType.StructuralCrafting, benchState); + this.inputContainer = new SimpleItemContainer((short)1); + this.inputContainer.registerChangeEvent(e -> this.updateRecipes()); + this.inputContainer.setSlotFilter(FilterActionType.ADD, (short)0, this::isValidInput); + this.optionsContainer = new SimpleItemContainer((short)64); + this.optionsContainer.setGlobalFilter(FilterType.DENY_ALL); + this.combinedItemContainer = new CombinedItemContainer(this.inputContainer, this.optionsContainer); + this.windowData.addProperty("selected", this.selectedSlot); + StructuralCraftingBench structuralBench = (StructuralCraftingBench)this.bench; + this.windowData.addProperty("allowBlockGroupCycling", structuralBench.shouldAllowBlockGroupCycling()); + this.windowData.addProperty("alwaysShowInventoryHints", structuralBench.shouldAlwaysShowInventoryHints()); + } + + private boolean isValidInput(FilterActionType filterActionType, ItemContainer itemContainer, short i, ItemStack itemStack) { + if (filterActionType != FilterActionType.ADD) { + return true; + } else { + ObjectList matchingRecipes = this.getMatchingRecipes(itemStack); + return matchingRecipes != null && !matchingRecipes.isEmpty(); + } + } + + private static void sortRecipes(ObjectList matching, StructuralCraftingBench structuralBench) { + matching.sort((a, b) -> { + boolean aHasHeaderCategory = hasHeaderCategory(structuralBench, a); + boolean bHasHeaderCategory = hasHeaderCategory(structuralBench, b); + if (aHasHeaderCategory != bHasHeaderCategory) { + return aHasHeaderCategory ? -1 : 1; + } else { + int categoryA = getSortingPriority(structuralBench, a); + int categoryB = getSortingPriority(structuralBench, b); + int categoryCompare = Integer.compare(categoryA, categoryB); + return categoryCompare != 0 ? categoryCompare : a.getId().compareTo(b.getId()); + } + }); + } + + private static boolean hasHeaderCategory(StructuralCraftingBench bench, CraftingRecipe recipe) { + for (BenchRequirement requirement : recipe.getBenchRequirement()) { + if (requirement.type == bench.getType() && requirement.id.equals(bench.getId()) && requirement.categories != null) { + for (String category : requirement.categories) { + if (bench.isHeaderCategory(category)) { + return true; + } + } + } + } + + return false; + } + + private static int getSortingPriority(StructuralCraftingBench bench, CraftingRecipe recipe) { + int priority = Integer.MAX_VALUE; + + for (BenchRequirement requirement : recipe.getBenchRequirement()) { + if (requirement.type == bench.getType() && requirement.id.equals(bench.getId()) && requirement.categories != null) { + for (String category : requirement.categories) { + priority = Math.min(priority, bench.getCategoryIndex(category)); + } + break; + } + } + + return priority; + } + + @Override + public void handleAction(@Nonnull Ref ref, @Nonnull Store store, @Nonnull WindowAction action) { + CraftingManager craftingManager = store.getComponent(ref, CraftingManager.getComponentType()); + switch (action) { + case SelectSlotAction selectAction: + int newSlot = MathUtil.clamp(selectAction.slot, 0, this.optionsContainer.getCapacity()); + if (newSlot != this.selectedSlot) { + this.selectedSlot = newSlot; + this.windowData.addProperty("selected", this.selectedSlot); + this.invalidate(); + } + break; + case CraftRecipeAction craftAction: + ItemStack output = this.optionsContainer.getItemStack((short)this.selectedSlot); + if (output != null) { + int quantity = craftAction.quantity; + String recipeId = this.optionSlotToRecipeMap.get(this.selectedSlot); + if (recipeId == null) { + return; + } + + CraftingRecipe recipe = CraftingRecipe.getAssetMap().getAsset(recipeId); + if (recipe == null) { + return; + } + + MaterialQuantity primaryOutput = recipe.getPrimaryOutput(); + String primaryOutputItemId = primaryOutput.getItemId(); + if (primaryOutputItemId != null) { + Item primaryOutputItem = Item.getAssetMap().getAsset(primaryOutputItemId); + if (primaryOutputItem != null) { + this.playCraftSound(ref, store, primaryOutputItem); + } + } + + craftingManager.queueCraft(ref, store, this, 0, recipe, quantity, this.inputContainer, CraftingManager.InputRemovalType.ORDERED); + this.invalidate(); + } + break; + case ChangeBlockAction changeBlockAction: + if (((StructuralCraftingBench)this.bench).shouldAllowBlockGroupCycling()) { + this.changeBlockType(ref, changeBlockAction.down, store); + } + break; + default: + } + } + + private void playCraftSound(Ref ref, Store store, Item item) { + ItemSoundSet soundSet = ItemSoundSet.getAssetMap().getAsset(item.getItemSoundSetIndex()); + if (soundSet != null) { + String dragSound = soundSet.getSoundEventIds().get(ItemSoundEvent.Drop); + if (dragSound != null) { + int dragSoundIndex = SoundEvent.getAssetMap().getIndex(dragSound); + if (dragSoundIndex != 0) { + SoundUtil.playSoundEvent2d(ref, dragSoundIndex, SoundCategory.UI, store); + } + } + } + } + + private void changeBlockType(@Nonnull Ref ref, boolean down, @Nonnull Store store) { + ItemStack item = this.inputContainer.getItemStack((short)0); + if (item != null) { + BlockGroup set = BlockGroup.findItemGroup(item.getItem()); + if (set != null) { + int currentIndex = -1; + + for (int i = 0; i < set.size(); i++) { + if (set.get(i).equals(item.getItem().getId())) { + currentIndex = i; + break; + } + } + + if (currentIndex != -1) { + int newIndex; + if (down) { + newIndex = (currentIndex - 1 + set.size()) % set.size(); + } else { + newIndex = (currentIndex + 1) % set.size(); + } + + String next = set.get(newIndex); + Item desiredItem = Item.getAssetMap().getAsset(next); + if (desiredItem != null) { + this.inputContainer.replaceItemStackInSlot((short)0, item, new ItemStack(next, item.getQuantity())); + this.playCraftSound(ref, store, desiredItem); + } + } + } + } + } + + @Nonnull + @Override + public ItemContainer getItemContainer() { + return this.combinedItemContainer; + } + + @Override + public boolean onOpen0() { + super.onOpen0(); + PlayerRef playerRef = this.getPlayerRef(); + Ref ref = playerRef.getReference(); + Store store = ref.getStore(); + Player player = store.getComponent(ref, Player.getComponentType()); + Inventory inventory = player.getInventory(); + this.inventoryRegistration = inventory.getCombinedHotbarFirst() + .registerChangeEvent( + event -> { + this.windowData + .add( + "inventoryHints", + CraftingManager.generateInventoryHints(CraftingPlugin.getBenchRecipes(this.bench), 0, inventory.getCombinedHotbarFirst()) + ); + this.invalidate(); + } + ); + this.windowData + .add("inventoryHints", CraftingManager.generateInventoryHints(CraftingPlugin.getBenchRecipes(this.bench), 0, inventory.getCombinedHotbarFirst())); + return true; + } + + @Override + public void onClose0() { + super.onClose0(); + PlayerRef playerRef = this.getPlayerRef(); + Ref ref = playerRef.getReference(); + Store store = ref.getStore(); + Player player = store.getComponent(ref, Player.getComponentType()); + List itemStacks = this.inputContainer.dropAllItemStacks(); + SimpleItemContainer.addOrDropItemStacks(store, ref, player.getInventory().getCombinedHotbarFirst(), itemStacks); + CraftingManager craftingManager = store.getComponent(ref, CraftingManager.getComponentType()); + craftingManager.cancelAllCrafting(ref, store); + if (this.inventoryRegistration != null) { + this.inventoryRegistration.unregister(); + this.inventoryRegistration = null; + } + } + + private void updateRecipes() { + this.invalidate(); + this.optionsContainer.clear(); + this.optionSlotToRecipeMap.clear(); + ItemStack inputStack = this.inputContainer.getItemStack((short)0); + ObjectList matchingRecipes = this.getMatchingRecipes(inputStack); + if (matchingRecipes != null) { + StructuralCraftingBench structuralBench = (StructuralCraftingBench)this.bench; + sortRecipes(matchingRecipes, structuralBench); + + int dividerIndex; + for (dividerIndex = 0; dividerIndex < matchingRecipes.size(); dividerIndex++) { + CraftingRecipe recipe = matchingRecipes.get(dividerIndex); + if (!hasHeaderCategory(structuralBench, recipe)) { + break; + } + } + + this.windowData.addProperty("dividerIndex", dividerIndex); + this.optionsContainer.clear(); + short index = 0; + int i = 0; + + for (int bound = matchingRecipes.size(); i < bound; i++) { + CraftingRecipe match = matchingRecipes.get(i); + + for (BenchRequirement requirement : match.getBenchRequirement()) { + if (requirement.type == this.bench.getType() && requirement.id.equals(this.bench.getId())) { + List output = CraftingManager.getOutputItemStacks(match); + this.optionsContainer.setItemStackForSlot(index, output.getFirst(), false); + this.optionSlotToRecipeMap.put(index, match.getId()); + index++; + } + } + } + + JsonArray optionSlotRecipes = new JsonArray(); + + for (int ix = 0; ix < this.optionsContainer.getCapacity(); ix++) { + String recipeId = this.optionSlotToRecipeMap.get(ix); + if (recipeId != null) { + optionSlotRecipes.add(recipeId); + } + } + + this.windowData.add("optionSlotRecipes", optionSlotRecipes); + } + } + + @NullableDecl + private ObjectList getMatchingRecipes(@Nullable ItemStack inputStack) { + if (inputStack == null) { + return null; + } else { + List recipes = CraftingPlugin.getBenchRecipes(this.bench.getType(), this.bench.getId()); + if (recipes.isEmpty()) { + return null; + } else { + ObjectList matchingRecipes = new ObjectArrayList<>(); + int i = 0; + + for (int bound = recipes.size(); i < bound; i++) { + CraftingRecipe recipe = recipes.get(i); + List inputMaterials = CraftingManager.getInputMaterials(recipe); + if (inputMaterials.size() == 1 && CraftingManager.matches(inputMaterials.getFirst(), inputStack)) { + matchingRecipes.add(recipe); + } + } + + return matchingRecipes.isEmpty() ? null : matchingRecipes; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/creativehub/CreativeHubPlugin.java b/src/com/hypixel/hytale/builtin/creativehub/CreativeHubPlugin.java new file mode 100644 index 0000000..c663142 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/creativehub/CreativeHubPlugin.java @@ -0,0 +1,223 @@ +package com.hypixel.hytale.builtin.creativehub; + +import com.hypixel.hytale.builtin.creativehub.command.HubCommand; +import com.hypixel.hytale.builtin.creativehub.config.CreativeHubEntityConfig; +import com.hypixel.hytale.builtin.creativehub.config.CreativeHubWorldConfig; +import com.hypixel.hytale.builtin.creativehub.interactions.HubPortalInteraction; +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.builtin.instances.config.InstanceEntityConfig; +import com.hypixel.hytale.builtin.instances.config.InstanceWorldConfig; +import com.hypixel.hytale.builtin.instances.config.WorldReturnPoint; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.event.events.player.AddPlayerToWorldEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.events.RemoveWorldEvent; +import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CreativeHubPlugin extends JavaPlugin { + @Nonnull + private static final Message MESSAGE_HUB_RETURN_HINT = Message.translation("server.creativehub.portal.returnHint"); + private static CreativeHubPlugin instance; + private final Map activeHubInstances = new ConcurrentHashMap<>(); + private ComponentType creativeHubEntityConfigComponentType; + + public static CreativeHubPlugin get() { + return instance; + } + + public CreativeHubPlugin(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Nonnull + public World getOrSpawnHubInstance(@Nonnull World parentWorld, @Nonnull CreativeHubWorldConfig hubConfig, @Nonnull Transform returnPoint) { + UUID parentUuid = parentWorld.getWorldConfig().getUuid(); + return this.activeHubInstances.compute(parentUuid, (uuid, existingInstance) -> { + if (existingInstance != null && existingInstance.isAlive()) { + return (World)existingInstance; + } else { + try { + return InstancesPlugin.get().spawnInstance(hubConfig.getStartupInstance(), parentWorld, returnPoint).join(); + } catch (Exception var7) { + this.getLogger().at(Level.SEVERE).withCause(var7).log("Failed to spawn hub instance"); + throw new RuntimeException("Failed to spawn hub instance", var7); + } + } + }); + } + + @Nullable + public World getActiveHubInstance(@Nonnull World parentWorld) { + World hubInstance = this.activeHubInstances.get(parentWorld.getWorldConfig().getUuid()); + return hubInstance != null && hubInstance.isAlive() ? hubInstance : null; + } + + public void clearHubInstance(@Nonnull UUID parentWorldUuid) { + this.activeHubInstances.remove(parentWorldUuid); + } + + @Nonnull + public CompletableFuture spawnPermanentWorldFromTemplate(@Nonnull String instanceAssetName, @Nonnull String permanentWorldName) { + Universe universe = Universe.get(); + World existingWorld = universe.getWorld(permanentWorldName); + if (existingWorld != null) { + return CompletableFuture.completedFuture(existingWorld); + } else if (universe.isWorldLoadable(permanentWorldName)) { + return universe.loadWorld(permanentWorldName); + } else { + Path assetPath = InstancesPlugin.getInstanceAssetPath(instanceAssetName); + Path worldPath = universe.getPath().resolve("worlds").resolve(permanentWorldName); + return WorldConfig.load(assetPath.resolve("instance.bson")) + .thenApplyAsync( + SneakyThrow.sneakyFunction( + config -> { + config.setUuid(UUID.randomUUID()); + config.setDeleteOnRemove(false); + config.setDisplayName(WorldConfig.formatDisplayName(instanceAssetName)); + config.getPluginConfig().remove(InstanceWorldConfig.class); + config.markChanged(); + long start = System.nanoTime(); + this.getLogger().at(Level.INFO).log("Copying instance template %s to permanent world %s", instanceAssetName, permanentWorldName); + + try (Stream files = Files.walk(assetPath, FileUtil.DEFAULT_WALK_TREE_OPTIONS_ARRAY)) { + files.forEach(SneakyThrow.sneakyConsumer(filePath -> { + Path rel = assetPath.relativize(filePath); + Path toPath = worldPath.resolve(rel.toString()); + if (Files.isDirectory(filePath)) { + Files.createDirectories(toPath); + } else { + if (Files.isRegularFile(filePath)) { + Files.copy(filePath, toPath); + } + } + })); + } + + this.getLogger() + .at(Level.INFO) + .log( + "Completed copying instance template %s to permanent world %s in %s", + instanceAssetName, + permanentWorldName, + FormatUtil.nanosToString(System.nanoTime() - start) + ); + return config; + } + ) + ) + .thenCompose(config -> universe.makeWorld(permanentWorldName, worldPath, config)); + } + } + + @Nonnull + public ComponentType getCreativeHubEntityConfigComponentType() { + return this.creativeHubEntityConfigComponentType; + } + + @Override + protected void setup() { + this.getCommandRegistry().registerCommand(new HubCommand()); + this.getCodecRegistry(Interaction.CODEC).register("HubPortal", HubPortalInteraction.class, HubPortalInteraction.CODEC); + this.getCodecRegistry(WorldConfig.PLUGIN_CODEC).register(CreativeHubWorldConfig.class, "CreativeHub", CreativeHubWorldConfig.CODEC); + this.creativeHubEntityConfigComponentType = this.getEntityStoreRegistry() + .registerComponent(CreativeHubEntityConfig.class, "CreativeHub", CreativeHubEntityConfig.CODEC); + this.getEventRegistry().registerGlobal(PlayerConnectEvent.class, CreativeHubPlugin::onPlayerConnect); + this.getEventRegistry().registerGlobal(RemoveWorldEvent.class, CreativeHubPlugin::onWorldRemove); + this.getEventRegistry().registerGlobal(AddPlayerToWorldEvent.class, CreativeHubPlugin::onPlayerAddToWorld); + } + + private static void onWorldRemove(@Nonnull RemoveWorldEvent event) { + World world = event.getWorld(); + UUID worldUuid = world.getWorldConfig().getUuid(); + get().activeHubInstances.entrySet().removeIf(entry -> { + World hubInstance = entry.getValue(); + return hubInstance != null && hubInstance.getWorldConfig().getUuid().equals(worldUuid); + }); + } + + private static void onPlayerConnect(@Nonnull PlayerConnectEvent event) { + World targetWorld = event.getWorld(); + Holder holder = event.getHolder(); + CreativeHubEntityConfig existingHubConfig = CreativeHubEntityConfig.get(holder); + if (existingHubConfig != null && existingHubConfig.getParentHubWorldUuid() != null) { + World parentWorld = Universe.get().getWorld(existingHubConfig.getParentHubWorldUuid()); + if (parentWorld != null) { + CreativeHubWorldConfig parentHubConfig = CreativeHubWorldConfig.get(parentWorld.getWorldConfig()); + if (parentHubConfig != null && parentHubConfig.getStartupInstance() != null && targetWorld == null) { + event.setWorld(parentWorld); + targetWorld = parentWorld; + holder.removeComponent(TransformComponent.getComponentType()); + } + } + } + + if (targetWorld != null) { + WorldConfig worldConfig = targetWorld.getWorldConfig(); + CreativeHubWorldConfig hubConfig = CreativeHubWorldConfig.get(worldConfig); + if (hubConfig != null && hubConfig.getStartupInstance() != null) { + PlayerRef playerRef = event.getPlayerRef(); + ISpawnProvider spawnProvider = worldConfig.getSpawnProvider(); + Transform returnPoint = spawnProvider != null ? spawnProvider.getSpawnPoint(targetWorld, playerRef.getUuid()) : new Transform(); + + try { + World hubInstance = get().getOrSpawnHubInstance(targetWorld, hubConfig, returnPoint); + InstanceEntityConfig instanceConfig = InstanceEntityConfig.ensureAndGet(holder); + instanceConfig.setReturnPoint(new WorldReturnPoint(targetWorld.getWorldConfig().getUuid(), returnPoint, false)); + CreativeHubEntityConfig hubEntityConfig = CreativeHubEntityConfig.ensureAndGet(holder); + hubEntityConfig.setParentHubWorldUuid(targetWorld.getWorldConfig().getUuid()); + event.setWorld(hubInstance); + } catch (Exception var12) { + get() + .getLogger() + .at(Level.SEVERE) + .withCause(var12) + .log("Failed to get/spawn hub instance for player %s, falling back to default world", playerRef.getUuid()); + } + } + } + } + + private static void onPlayerAddToWorld(@Nonnull AddPlayerToWorldEvent event) { + Holder holder = event.getHolder(); + World world = event.getWorld(); + CreativeHubEntityConfig hubEntityConfig = holder.getComponent(CreativeHubEntityConfig.getComponentType()); + if (hubEntityConfig != null && hubEntityConfig.getParentHubWorldUuid() != null) { + World parentWorld = Universe.get().getWorld(hubEntityConfig.getParentHubWorldUuid()); + if (parentWorld != null) { + World hubInstance = get().getActiveHubInstance(parentWorld); + if (hubInstance == null || !world.equals(hubInstance)) { + PlayerRef playerRef = holder.getComponent(PlayerRef.getComponentType()); + if (playerRef != null) { + world.execute(() -> playerRef.sendMessage(MESSAGE_HUB_RETURN_HINT)); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/creativehub/command/HubCommand.java b/src/com/hypixel/hytale/builtin/creativehub/command/HubCommand.java new file mode 100644 index 0000000..cad6df7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/creativehub/command/HubCommand.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.builtin.creativehub.command; + +import com.hypixel.hytale.builtin.creativehub.CreativeHubPlugin; +import com.hypixel.hytale.builtin.creativehub.config.CreativeHubEntityConfig; +import com.hypixel.hytale.builtin.creativehub.config.CreativeHubWorldConfig; +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HubCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_NOT_IN_HUB_WORLD = Message.translation("server.commands.hub.notInHubWorld"); + @Nonnull + private static final Message MESSAGE_ALREADY_IN_HUB = Message.translation("server.commands.hub.alreadyInHub"); + + public HubCommand() { + super("hub", "server.commands.hub.desc"); + this.addAliases("converge", "convergence"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + World parentWorld = this.findParentHubWorld(store, ref); + if (parentWorld == null) { + playerRef.sendMessage(MESSAGE_NOT_IN_HUB_WORLD); + } else { + CreativeHubWorldConfig hubConfig = CreativeHubWorldConfig.get(parentWorld.getWorldConfig()); + if (hubConfig != null && hubConfig.getStartupInstance() != null) { + World currentHub = CreativeHubPlugin.get().getActiveHubInstance(parentWorld); + if (currentHub != null && world.equals(currentHub)) { + playerRef.sendMessage(MESSAGE_ALREADY_IN_HUB); + } else { + ISpawnProvider spawnProvider = parentWorld.getWorldConfig().getSpawnProvider(); + Transform returnPoint = spawnProvider != null ? spawnProvider.getSpawnPoint(parentWorld, playerRef.getUuid()) : new Transform(); + World hubInstance = CreativeHubPlugin.get().getOrSpawnHubInstance(parentWorld, hubConfig, returnPoint); + InstancesPlugin.teleportPlayerToInstance(ref, store, hubInstance, null); + } + } else { + playerRef.sendMessage(MESSAGE_NOT_IN_HUB_WORLD); + } + } + } + + @Nullable + private World findParentHubWorld(@Nonnull Store store, @Nonnull Ref ref) { + CreativeHubEntityConfig hubEntityConfig = store.getComponent(ref, CreativeHubEntityConfig.getComponentType()); + if (hubEntityConfig != null && hubEntityConfig.getParentHubWorldUuid() != null) { + World parentWorld = Universe.get().getWorld(hubEntityConfig.getParentHubWorldUuid()); + if (parentWorld != null) { + CreativeHubWorldConfig hubConfig = CreativeHubWorldConfig.get(parentWorld.getWorldConfig()); + if (hubConfig != null && hubConfig.getStartupInstance() != null) { + return parentWorld; + } + } + } + + return null; + } +} diff --git a/src/com/hypixel/hytale/builtin/creativehub/config/CreativeHubEntityConfig.java b/src/com/hypixel/hytale/builtin/creativehub/config/CreativeHubEntityConfig.java new file mode 100644 index 0000000..fd1cd33 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/creativehub/config/CreativeHubEntityConfig.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.builtin.creativehub.config; + +import com.hypixel.hytale.builtin.creativehub.CreativeHubPlugin; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CreativeHubEntityConfig implements Component { + public static final String ID = "CreativeHub"; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(CreativeHubEntityConfig.class, CreativeHubEntityConfig::new) + .appendInherited( + new KeyedCodec<>("ParentHubWorldUuid", Codec.UUID_STRING), + (o, i) -> o.parentHubWorldUuid = i, + o -> o.parentHubWorldUuid, + (o, p) -> o.parentHubWorldUuid = p.parentHubWorldUuid + ) + .add() + .build(); + @Nullable + private UUID parentHubWorldUuid; + + @Nonnull + public static ComponentType getComponentType() { + return CreativeHubPlugin.get().getCreativeHubEntityConfigComponentType(); + } + + @Nonnull + public static CreativeHubEntityConfig ensureAndGet(@Nonnull Holder holder) { + ComponentType type = getComponentType(); + return holder.ensureAndGetComponent(type); + } + + @Nullable + public static CreativeHubEntityConfig get(@Nonnull Holder holder) { + ComponentType type = getComponentType(); + return holder.getComponent(type); + } + + public CreativeHubEntityConfig() { + } + + @Nullable + public UUID getParentHubWorldUuid() { + return this.parentHubWorldUuid; + } + + public void setParentHubWorldUuid(@Nullable UUID parentHubWorldUuid) { + this.parentHubWorldUuid = parentHubWorldUuid; + } + + @Nonnull + public CreativeHubEntityConfig clone() { + CreativeHubEntityConfig v = new CreativeHubEntityConfig(); + v.parentHubWorldUuid = this.parentHubWorldUuid; + return v; + } +} diff --git a/src/com/hypixel/hytale/builtin/creativehub/config/CreativeHubWorldConfig.java b/src/com/hypixel/hytale/builtin/creativehub/config/CreativeHubWorldConfig.java new file mode 100644 index 0000000..cea27cc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/creativehub/config/CreativeHubWorldConfig.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.creativehub.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CreativeHubWorldConfig { + public static final String ID = "CreativeHub"; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(CreativeHubWorldConfig.class, CreativeHubWorldConfig::new) + .append(new KeyedCodec<>("StartupInstance", Codec.STRING), (o, i) -> o.startupInstance = i, o -> o.startupInstance) + .documentation("The name of the instance to spawn players into when they first join this world.") + .add() + .build(); + @Nullable + private String startupInstance; + + public CreativeHubWorldConfig() { + } + + @Nullable + public static CreativeHubWorldConfig get(@Nonnull WorldConfig config) { + return config.getPluginConfig().get(CreativeHubWorldConfig.class); + } + + @Nullable + public String getStartupInstance() { + return this.startupInstance; + } +} diff --git a/src/com/hypixel/hytale/builtin/creativehub/interactions/HubPortalInteraction.java b/src/com/hypixel/hytale/builtin/creativehub/interactions/HubPortalInteraction.java new file mode 100644 index 0000000..9663f8d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/creativehub/interactions/HubPortalInteraction.java @@ -0,0 +1,202 @@ +package com.hypixel.hytale.builtin.creativehub.interactions; + +import com.hypixel.hytale.builtin.creativehub.CreativeHubPlugin; +import com.hypixel.hytale.builtin.creativehub.config.CreativeHubEntityConfig; +import com.hypixel.hytale.builtin.creativehub.config.CreativeHubWorldConfig; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerWorldData; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.PendingTeleport; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class HubPortalInteraction extends SimpleInstantInteraction { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final BuilderCodec CODEC = BuilderCodec.builder( + HubPortalInteraction.class, HubPortalInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Teleports the **Player** to a permanent world, creating it if required.") + .appendInherited(new KeyedCodec<>("WorldName", Codec.STRING), (o, i) -> o.worldName = i, o -> o.worldName, (o, p) -> o.worldName = p.worldName) + .documentation("The name of the permanent world to teleport to.") + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("WorldGenType", Codec.STRING), (o, i) -> o.worldGenType = i, o -> o.worldGenType, (o, p) -> o.worldGenType = p.worldGenType + ) + .documentation("The world generator type to use when creating the world (e.g., 'Flat', 'Hytale'). Mutually exclusive with InstanceTemplate.") + .add() + .appendInherited( + new KeyedCodec<>("InstanceTemplate", Codec.STRING), + (o, i) -> o.instanceTemplate = i, + o -> o.instanceTemplate, + (o, p) -> o.instanceTemplate = p.instanceTemplate + ) + .documentation("Instance asset to use as template for creating the permanent world. Mutually exclusive with WorldGenType.") + .add() + .build(); + private String worldName; + private String worldGenType; + @Nullable + private String instanceTemplate; + + public HubPortalInteraction() { + } + + @NonNullDecl + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + Ref ref = context.getEntity(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent != null && !playerComponent.isWaitingForClientReady()) { + Archetype archetype = commandBuffer.getArchetype(ref); + if (!archetype.contains(Teleport.getComponentType()) && !archetype.contains(PendingTeleport.getComponentType())) { + World currentWorld = commandBuffer.getExternalData().getWorld(); + Universe universe = Universe.get(); + World targetWorld = universe.getWorld(this.worldName); + if (targetWorld != null) { + this.teleportToLoadedWorld(ref, commandBuffer, targetWorld, playerComponent); + } else { + CompletableFuture worldFuture; + if (this.instanceTemplate != null) { + worldFuture = CreativeHubPlugin.get().spawnPermanentWorldFromTemplate(this.instanceTemplate, this.worldName); + } else if (universe.isWorldLoadable(this.worldName)) { + worldFuture = universe.loadWorld(this.worldName); + } else { + worldFuture = universe.addWorld(this.worldName, this.worldGenType, null); + worldFuture.thenAccept(world -> { + if (world.getWorldConfig().getDisplayName() == null) { + world.getWorldConfig().setDisplayName(WorldConfig.formatDisplayName(this.worldName)); + } + }); + } + + this.teleportToLoadingWorld(ref, commandBuffer, worldFuture, currentWorld, playerComponent); + } + } + } + } + + private void teleportToLoadedWorld( + @Nonnull Ref playerRef, + @Nonnull ComponentAccessor componentAccessor, + @Nonnull World targetWorld, + @Nonnull Player playerComponent + ) { + Map perWorldData = playerComponent.getPlayerConfigData().getPerWorldData(); + PlayerWorldData worldData = perWorldData.get(targetWorld.getName()); + Transform spawnPoint; + if (worldData != null && worldData.getLastPosition() != null) { + spawnPoint = worldData.getLastPosition(); + } else { + UUIDComponent uuidComponent = componentAccessor.getComponent(playerRef, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + ISpawnProvider spawnProvider = targetWorld.getWorldConfig().getSpawnProvider(); + spawnPoint = spawnProvider != null ? spawnProvider.getSpawnPoint(targetWorld, uuidComponent.getUuid()) : new Transform(); + } + + componentAccessor.addComponent(playerRef, Teleport.getComponentType(), new Teleport(targetWorld, spawnPoint)); + } + + private void teleportToLoadingWorld( + @Nonnull Ref playerRef, + @Nonnull ComponentAccessor componentAccessor, + @Nonnull CompletableFuture worldFuture, + @Nonnull World originalWorld, + @Nonnull Player playerComponent + ) { + TransformComponent transformComponent = componentAccessor.getComponent(playerRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Transform originalPosition = transformComponent.getTransform().clone(); + PlayerRef playerRefComponent = componentAccessor.getComponent(playerRef, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + Map perWorldData = playerComponent.getPlayerConfigData().getPerWorldData(); + UUIDComponent uuidComponent = componentAccessor.getComponent(playerRef, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID playerUUID = uuidComponent.getUuid(); + CreativeHubEntityConfig hubEntityConfig = componentAccessor.getComponent(playerRef, CreativeHubEntityConfig.getComponentType()); + originalWorld.execute(playerRefComponent::removeFromStore); + worldFuture.orTimeout(1L, TimeUnit.MINUTES).thenCompose(world -> { + PlayerWorldData worldData = perWorldData.get(world.getName()); + if (worldData != null && worldData.getLastPosition() != null) { + return world.addPlayer(playerRefComponent, worldData.getLastPosition(), Boolean.TRUE, Boolean.FALSE); + } else { + ISpawnProvider spawnProvider = world.getWorldConfig().getSpawnProvider(); + Transform spawnPoint = spawnProvider != null ? spawnProvider.getSpawnPoint(world, playerUUID) : null; + return world.addPlayer(playerRefComponent, spawnPoint, Boolean.TRUE, Boolean.FALSE); + } + }).whenComplete((ret, ex) -> { + if (ex != null) { + LOGGER.at(Level.SEVERE).withCause(ex).log("Failed to teleport %s to permanent world", playerRefComponent.getUsername()); + } + + if (ret == null) { + if (originalWorld.isAlive()) { + originalWorld.addPlayer(playerRefComponent, originalPosition, Boolean.TRUE, Boolean.FALSE); + } else { + if (hubEntityConfig != null && hubEntityConfig.getParentHubWorldUuid() != null) { + World parentWorld = Universe.get().getWorld(hubEntityConfig.getParentHubWorldUuid()); + if (parentWorld != null) { + CreativeHubWorldConfig parentHubConfig = CreativeHubWorldConfig.get(parentWorld.getWorldConfig()); + if (parentHubConfig != null && parentHubConfig.getStartupInstance() != null) { + World hubInstance = CreativeHubPlugin.get().getOrSpawnHubInstance(parentWorld, parentHubConfig, new Transform()); + hubInstance.addPlayer(playerRefComponent, null, Boolean.TRUE, Boolean.FALSE); + return; + } + } + } + + World defaultWorld = Universe.get().getDefaultWorld(); + if (defaultWorld != null) { + defaultWorld.addPlayer(playerRefComponent, null, Boolean.TRUE, Boolean.FALSE); + } else { + LOGGER.at(Level.SEVERE).log("No fallback world available for %s, disconnecting", playerRefComponent.getUsername()); + playerRefComponent.getPacketHandler().disconnect("Failed to teleport - no world available"); + } + } + } + }); + } +} diff --git a/src/com/hypixel/hytale/builtin/crouchslide/CrouchSlidePlugin.java b/src/com/hypixel/hytale/builtin/crouchslide/CrouchSlidePlugin.java new file mode 100644 index 0000000..7e549c7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/crouchslide/CrouchSlidePlugin.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.builtin.crouchslide; + +import com.hypixel.hytale.protocol.packets.setup.ClientFeature; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import javax.annotation.Nonnull; + +public class CrouchSlidePlugin extends JavaPlugin { + public CrouchSlidePlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + this.getClientFeatureRegistry().registerClientTag("Allows=Movement"); + this.getClientFeatureRegistry().register(ClientFeature.CrouchSlide); + } +} diff --git a/src/com/hypixel/hytale/builtin/deployables/DeployablesPlugin.java b/src/com/hypixel/hytale/builtin/deployables/DeployablesPlugin.java new file mode 100644 index 0000000..9134993 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/deployables/DeployablesPlugin.java @@ -0,0 +1,91 @@ +package com.hypixel.hytale.builtin.deployables; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.builtin.deployables.component.DeployableComponent; +import com.hypixel.hytale.builtin.deployables.component.DeployableOwnerComponent; +import com.hypixel.hytale.builtin.deployables.component.DeployableProjectileComponent; +import com.hypixel.hytale.builtin.deployables.component.DeployableProjectileShooterComponent; +import com.hypixel.hytale.builtin.deployables.config.DeployableAoeConfig; +import com.hypixel.hytale.builtin.deployables.config.DeployableConfig; +import com.hypixel.hytale.builtin.deployables.config.DeployableSpawner; +import com.hypixel.hytale.builtin.deployables.config.DeployableTrapConfig; +import com.hypixel.hytale.builtin.deployables.config.DeployableTrapSpawnerConfig; +import com.hypixel.hytale.builtin.deployables.config.DeployableTurretConfig; +import com.hypixel.hytale.builtin.deployables.interaction.SpawnDeployableAtHitLocationInteraction; +import com.hypixel.hytale.builtin.deployables.interaction.SpawnDeployableFromRaycastInteraction; +import com.hypixel.hytale.builtin.deployables.system.DeployablesSystem; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public class DeployablesPlugin extends JavaPlugin { + private static DeployablesPlugin instance; + private ComponentType deployableComponentType; + private ComponentType deployableOwnerComponentType; + private ComponentType deployableProjectileShooterComponentType; + private ComponentType deployableProjectileComponentType; + + public DeployablesPlugin(JavaPluginInit init) { + super(init); + } + + public static DeployablesPlugin get() { + return instance; + } + + @Override + protected void setup() { + instance = this; + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + DeployableSpawner.class, new DefaultAssetMap() + ) + .setPath("DeployableSpawners")) + .setCodec(DeployableSpawner.CODEC)) + .setKeyFunction(DeployableSpawner::getId)) + .loadsAfter(ModelAsset.class, EntityEffect.class, SoundEvent.class)) + .loadsBefore(Interaction.class)) + .build() + ); + ComponentRegistryProxy entityStoreRegistry = this.getEntityStoreRegistry(); + this.deployableComponentType = entityStoreRegistry.registerComponent(DeployableComponent.class, DeployableComponent::new); + this.deployableOwnerComponentType = entityStoreRegistry.registerComponent(DeployableOwnerComponent.class, DeployableOwnerComponent::new); + this.deployableProjectileShooterComponentType = entityStoreRegistry.registerComponent( + DeployableProjectileShooterComponent.class, DeployableProjectileShooterComponent::new + ); + this.deployableProjectileComponentType = entityStoreRegistry.registerComponent(DeployableProjectileComponent.class, DeployableProjectileComponent::new); + DeployableConfig.CODEC.register("Trap", DeployableTrapConfig.class, DeployableTrapConfig.CODEC); + DeployableConfig.CODEC.register("TrapSpawner", DeployableTrapSpawnerConfig.class, DeployableTrapSpawnerConfig.CODEC); + DeployableConfig.CODEC.register("Aoe", DeployableAoeConfig.class, DeployableAoeConfig.CODEC); + DeployableConfig.CODEC.register("Turret", DeployableTurretConfig.class, DeployableTurretConfig.CODEC); + Interaction.CODEC.register("SpawnDeployableAtHitLocation", SpawnDeployableAtHitLocationInteraction.class, SpawnDeployableAtHitLocationInteraction.CODEC); + Interaction.CODEC.register("SpawnDeployableFromRaycast", SpawnDeployableFromRaycastInteraction.class, SpawnDeployableFromRaycastInteraction.CODEC); + entityStoreRegistry.registerSystem(new DeployablesSystem.DeployableTicker()); + entityStoreRegistry.registerSystem(new DeployablesSystem.DeployableRegisterer()); + entityStoreRegistry.registerSystem(new DeployablesSystem.DeployableOwnerTicker()); + } + + public ComponentType getDeployableComponentType() { + return this.deployableComponentType; + } + + public ComponentType getDeployableOwnerComponentType() { + return this.deployableOwnerComponentType; + } + + public ComponentType getDeployableProjectileShooterComponentType() { + return this.deployableProjectileShooterComponentType; + } + + public ComponentType getDeployableProjectileComponentType() { + return this.deployableProjectileComponentType; + } +} diff --git a/src/com/hypixel/hytale/builtin/deployables/DeployablesUtils.java b/src/com/hypixel/hytale/builtin/deployables/DeployablesUtils.java new file mode 100644 index 0000000..a3248cd --- /dev/null +++ b/src/com/hypixel/hytale/builtin/deployables/DeployablesUtils.java @@ -0,0 +1,179 @@ +package com.hypixel.hytale.builtin.deployables; + +import com.hypixel.hytale.builtin.deployables.component.DeployableComponent; +import com.hypixel.hytale.builtin.deployables.component.DeployableOwnerComponent; +import com.hypixel.hytale.builtin.deployables.config.DeployableConfig; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.AnimationSlot; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.entities.PlayAnimation; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.DespawnComponent; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.AudioComponent; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.Invulnerable; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.PersistentModel; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.hitboxcollision.HitboxCollision; +import com.hypixel.hytale.server.core.modules.entity.hitboxcollision.HitboxCollisionConfig; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.Modifier; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.StaticModifier; +import com.hypixel.hytale.server.core.modules.entityui.UIComponentList; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.PlayerUtil; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Duration; +import java.util.Map; +import java.util.UUID; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DeployablesUtils { + private static final String DEPLOYABLE_MAX_STAT_MODIFIER = "DEPLOYABLE_MAX"; + + public DeployablesUtils() { + } + + @Nonnull + public static Ref spawnDeployable( + @Nonnull CommandBuffer commandBuffer, + @Nonnull Store store, + @Nonnull DeployableConfig config, + @Nonnull Ref deployerRef, + @Nonnull Vector3f position, + @Nonnull Vector3f rotation, + @Nonnull String spawnFace + ) { + Holder holder = EntityStore.REGISTRY.newHolder(); + Vector3d spawnPos = new Vector3d(position.x, position.y, position.z); + Model model = config.getModel(); + AudioComponent audioComponent = new AudioComponent(); + if (config.getAmbientSoundEventIndex() != 0) { + audioComponent.addSound(config.getAmbientSoundEventIndex()); + } + + holder.addComponent(DeployableComponent.getComponentType(), new DeployableComponent()); + holder.addComponent(TransformComponent.getComponentType(), new TransformComponent()); + holder.addComponent(HeadRotation.getComponentType(), new HeadRotation(Vector3f.FORWARD)); + holder.addComponent(UUIDComponent.getComponentType(), new UUIDComponent(UUID.randomUUID())); + holder.addComponent(EntityStatMap.getComponentType(), new EntityStatMap()); + holder.addComponent(ModelComponent.getComponentType(), new ModelComponent(model)); + holder.addComponent(PersistentModel.getComponentType(), new PersistentModel(model.toReference())); + holder.addComponent(BoundingBox.getComponentType(), new BoundingBox(model.getBoundingBox())); + holder.addComponent(AudioComponent.getComponentType(), audioComponent); + holder.addComponent(NetworkId.getComponentType(), new NetworkId(store.getExternalData().takeNextNetworkId())); + holder.ensureComponent(DeployableComponent.getComponentType()); + holder.ensureComponent(EntityModule.get().getVisibleComponentType()); + holder.ensureComponent(EntityStore.REGISTRY.getNonSerializedComponentType()); + UIComponentList uiCompList = holder.ensureAndGetComponent(UIComponentList.getComponentType()); + uiCompList.update(); + if (config.getInvulnerable()) { + holder.ensureComponent(Invulnerable.getComponentType()); + } + + int hitboxCollisionConfigIndex = config.getHitboxCollisionConfigIndex(); + if (hitboxCollisionConfigIndex != -1) { + HitboxCollisionConfig hitboxCollisionAsset = HitboxCollisionConfig.getAssetMap().getAsset(hitboxCollisionConfigIndex); + holder.addComponent(HitboxCollision.getComponentType(), new HitboxCollision(hitboxCollisionAsset)); + } + + long liveDuration = config.getLiveDurationInMillis(); + if (liveDuration > 0L) { + holder.addComponent( + DespawnComponent.getComponentType(), + new DespawnComponent(store.getResource(TimeResource.getResourceType()).getNow().plus(Duration.ofMillis(liveDuration))) + ); + } + + EntityStatMap entityStatMapComponent = holder.ensureAndGetComponent(EntityStatMap.getComponentType()); + entityStatMapComponent.update(); + populateStats(config, entityStatMapComponent); + DeployableComponent deployableComponent = holder.ensureAndGetComponent(DeployableComponent.getComponentType()); + deployableComponent.init(deployerRef, store, config, store.getResource(TimeResource.getResourceType()).getNow(), spawnFace); + TransformComponent transformComponent = holder.ensureAndGetComponent(TransformComponent.getComponentType()); + transformComponent.setRotation(rotation); + transformComponent.setPosition(new Vector3d(spawnPos)); + HeadRotation headRotationComponent = holder.ensureAndGetComponent(HeadRotation.getComponentType()); + headRotationComponent.setRotation(rotation); + commandBuffer.ensureComponent(deployerRef, DeployableOwnerComponent.getComponentType()); + return commandBuffer.addEntity(holder, AddReason.SPAWN); + } + + static void populateStats(@Nonnull DeployableConfig config, @Nonnull EntityStatMap entityStatMapComponent) { + Map stats = config.getStatValues(); + if (stats != null) { + for (Entry statEntry : stats.entrySet()) { + DeployableConfig.StatConfig statConfig = statEntry.getValue(); + int statIndex = EntityStatType.getAssetMap().getIndex(statEntry.getKey()); + EntityStatValue stat = entityStatMapComponent.get(statIndex); + if (stat != null) { + EntityStatType asset = EntityStatType.getAssetMap().getAsset(statIndex); + StaticModifier modifier = new StaticModifier( + Modifier.ModifierTarget.MAX, StaticModifier.CalculationType.ADDITIVE, statConfig.getMax() - asset.getMax() + ); + entityStatMapComponent.putModifier(statIndex, "DEPLOYABLE_MAX", modifier); + float initialValue = statConfig.getInitial(); + if (initialValue == Float.MAX_VALUE) { + entityStatMapComponent.maximizeStatValue(statIndex); + } else { + entityStatMapComponent.setStatValue(statIndex, initialValue); + } + } + } + } + } + + public static void playAnimation( + @Nonnull Store store, + int networkId, + @Nonnull Ref ref, + @Nonnull DeployableConfig config, + @Nonnull AnimationSlot animationSlot, + @Nullable String itemAnimationsId, + @Nonnull String animationId + ) { + Model model = config.getModel(); + if (animationSlot == AnimationSlot.Action || model == null || model.getAnimationSetMap().containsKey(animationId)) { + PlayAnimation animationPacket = new PlayAnimation(networkId, itemAnimationsId, animationId, animationSlot); + PlayerUtil.forEachPlayerThatCanSeeEntity( + ref, (playerRef, playerRefComponent, ca) -> playerRefComponent.getPacketHandler().write(animationPacket), store + ); + } + } + + public static void stopAnimation(@Nonnull Store store, int networkId, @Nonnull Ref ref, @Nonnull AnimationSlot animationSlot) { + PlayAnimation animationPacket = new PlayAnimation(networkId, null, null, animationSlot); + PlayerUtil.forEachPlayerThatCanSeeEntity(ref, (playerRef, playerRefComponent, ca) -> playerRefComponent.getPacketHandler().write(animationPacket), store); + } + + public static void playSoundEventsAtEntity( + @Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor, int localIndex, int worldIndex, @Nonnull Vector3d pos + ) { + Player targetPlayerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + if (localIndex != 0 && targetPlayerComponent != null) { + SoundUtil.playSoundEvent2d(ref, localIndex, SoundCategory.SFX, componentAccessor); + } + + if (worldIndex != 0) { + SoundUtil.playSoundEvent3d(worldIndex, SoundCategory.SFX, pos, componentAccessor); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/deployables/component/DeployableComponent.java b/src/com/hypixel/hytale/builtin/deployables/component/DeployableComponent.java new file mode 100644 index 0000000..43b1aae --- /dev/null +++ b/src/com/hypixel/hytale/builtin/deployables/component/DeployableComponent.java @@ -0,0 +1,137 @@ +package com.hypixel.hytale.builtin.deployables.component; + +import com.hypixel.hytale.builtin.deployables.DeployablesPlugin; +import com.hypixel.hytale.builtin.deployables.config.DeployableConfig; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import java.util.EnumMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; + +public class DeployableComponent implements Component { + @Nonnull + private final Map flags = new EnumMap<>(DeployableComponent.DeployableFlag.class); + private DeployableConfig config; + private Ref owner; + private UUID ownerUUID; + private Instant spawnInstant; + private float timeSinceLastAttack; + private Vector3f debugColor = null; + private boolean firstTickRan; + private String spawnFace; + + public DeployableComponent() { + } + + @Nonnull + public static ComponentType getComponentType() { + return DeployablesPlugin.get().getDeployableComponentType(); + } + + @Override + public Component clone() { + return this; + } + + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + if (!this.firstTickRan) { + this.config.firstTick(this, dt, index, archetypeChunk, store, commandBuffer); + this.firstTickRan = true; + } + + this.config.tick(this, dt, index, archetypeChunk, store, commandBuffer); + } + + public void init( + @Nonnull Ref deployerRef, + @Nonnull Store store, + @Nonnull DeployableConfig config, + @Nonnull Instant spawnInstant, + @Nonnull String spawnFace + ) { + UUIDComponent uuidComponent = store.getComponent(deployerRef, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + this.config = config; + this.owner = deployerRef; + this.spawnInstant = spawnInstant; + this.spawnFace = spawnFace; + this.ownerUUID = uuidComponent.getUuid(); + } + + public Ref getOwner() { + return this.owner; + } + + public UUID getOwnerUUID() { + return this.ownerUUID; + } + + public DeployableConfig getConfig() { + return this.config; + } + + public Instant getSpawnInstant() { + return this.spawnInstant; + } + + public float getTimeSinceLastAttack() { + return this.timeSinceLastAttack; + } + + public void setTimeSinceLastAttack(float time) { + this.timeSinceLastAttack = time; + } + + public float incrementTimeSinceLastAttack(float time) { + return this.timeSinceLastAttack += time; + } + + public String getSpawnFace() { + return this.spawnFace; + } + + public int getFlag(@Nonnull DeployableComponent.DeployableFlag key) { + return this.flags.computeIfAbsent(key, k -> 0); + } + + public void setFlag(@Nonnull DeployableComponent.DeployableFlag key, int value) { + this.flags.put(key, value); + } + + public Vector3f getDebugColor() { + if (this.debugColor == null) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + this.debugColor = new Vector3f(random.nextFloat(), random.nextFloat(), random.nextFloat()); + } + + return this.debugColor; + } + + public static enum DeployableFlag { + STATE, + LIVE, + BURST_SHOTS, + TRIGGERED; + + private DeployableFlag() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/deployables/component/DeployableOwnerComponent.java b/src/com/hypixel/hytale/builtin/deployables/component/DeployableOwnerComponent.java new file mode 100644 index 0000000..c3788c4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/deployables/component/DeployableOwnerComponent.java @@ -0,0 +1,171 @@ +package com.hypixel.hytale.builtin.deployables.component; + +import com.hypixel.hytale.builtin.deployables.DeployablesPlugin; +import com.hypixel.hytale.builtin.deployables.config.DeployableConfig; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.entity.knockback.KnockbackComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class DeployableOwnerComponent implements Component { + @Nonnull + private final List>> deployables = new ObjectArrayList<>(); + @Nonnull + private final Object2IntMap deployableCountPerId = new Object2IntOpenHashMap<>(); + @Nonnull + private final List> deployablesForDestruction = new ObjectArrayList<>(); + private final List>> tempDestructionList = new ObjectArrayList<>(); + + public DeployableOwnerComponent() { + } + + @Nonnull + public static ComponentType getComponentType() { + return DeployablesPlugin.get().getDeployableOwnerComponentType(); + } + + private static int getMaxDeployablesForId(@Nonnull DeployableComponent comp) { + return comp.getConfig().getMaxLiveCount(); + } + + private static int getMaxDeployablesGlobal(@Nonnull Store store) { + World world = store.getExternalData().getWorld(); + GameplayConfig gameplayConfig = world.getGameplayConfig(); + return gameplayConfig.getPlayerConfig().getMaxDeployableEntities(); + } + + public void tick(@Nonnull CommandBuffer commandBuffer) { + this.handleOverMaxDeployableDestruction(commandBuffer); + } + + public void registerDeployable( + @Nonnull Ref owner, + @Nonnull DeployableComponent deployableComp, + @Nonnull String id, + @Nonnull Ref deployable, + @Nonnull Store store + ) { + this.deployables.add(Pair.of(id, deployable)); + this.incrementId(id); + this.handlePerDeployableLimit(id, deployableComp); + this.handleGlobalDeployableLimit(store, owner); + } + + public void deRegisterDeployable(@Nonnull String id, @Nonnull Ref deployable) { + this.deployables.remove(Pair.of(id, deployable)); + this.decrementId(id); + } + + private void incrementId(@Nonnull String id) { + if (!this.deployableCountPerId.containsKey(id)) { + this.deployableCountPerId.put(id, 1); + } else { + this.deployableCountPerId.put(id, this.deployableCountPerId.getInt(id) + 1); + } + } + + private void decrementId(@Nonnull String id) { + if (!this.deployableCountPerId.containsKey(id)) { + this.deployableCountPerId.put(id, 0); + } else { + this.deployableCountPerId.put(id, this.deployableCountPerId.getInt(id) - 1); + } + } + + private int getCurrentDeployablesById(@Nonnull String id) { + return this.deployableCountPerId.getOrDefault(id, 0); + } + + private void handlePerDeployableLimit(@Nonnull String id, @Nonnull DeployableComponent deployableComponent) { + int limit = getMaxDeployablesForId(deployableComponent); + int current = this.getCurrentDeployablesById(id); + if (current > limit) { + int diff = current - limit; + this.tempDestructionList.clear(); + + for (Pair> deployablePair : this.deployables) { + if (deployablePair.key().equals(id)) { + this.deployablesForDestruction.add(deployablePair.value()); + this.tempDestructionList.add(deployablePair); + diff--; + } + + if (diff <= 0) { + break; + } + } + + this.deployables.removeAll(this.tempDestructionList); + this.tempDestructionList.clear(); + } + } + + private void handleGlobalDeployableLimit(@Nonnull Store store, @Nonnull Ref owner) { + int limit = 1; + int current = 0; + + for (Pair> deployablePair : this.deployables) { + DeployableComponent deployableComponent = store.getComponent(deployablePair.value(), DeployableComponent.getComponentType()); + + assert deployableComponent != null; + + DeployableConfig deployableConfig = deployableComponent.getConfig(); + if (deployableConfig.getCountTowardsGlobalLimit()) { + current++; + } + } + + if (current > 1) { + int diff = current - 1; + this.tempDestructionList.clear(); + + for (Pair> deployablePair : this.deployables) { + Ref deployableRef = deployablePair.value(); + DeployableComponent deployableComponentx = store.getComponent(deployableRef, DeployableComponent.getComponentType()); + + assert deployableComponentx != null; + + DeployableConfig deployableConfig = deployableComponentx.getConfig(); + if (deployableConfig.getCountTowardsGlobalLimit()) { + this.deployablesForDestruction.add(deployableRef); + this.tempDestructionList.add(deployablePair); + if (--diff <= 0) { + break; + } + } + } + + this.deployables.removeAll(this.tempDestructionList); + this.tempDestructionList.clear(); + } + } + + private void handleOverMaxDeployableDestruction(@Nonnull CommandBuffer commandBuffer) { + if (!this.deployablesForDestruction.isEmpty()) { + for (Ref deployableEntityRef : this.deployablesForDestruction) { + DeathComponent.tryAddComponent(commandBuffer, deployableEntityRef, new Damage(Damage.NULL_SOURCE, DamageCause.COMMAND, 0.0F)); + } + + this.deployablesForDestruction.clear(); + } + } + + @Override + public Component clone() { + return new KnockbackComponent(); + } +} diff --git a/src/com/hypixel/hytale/builtin/deployables/component/DeployableProjectileComponent.java b/src/com/hypixel/hytale/builtin/deployables/component/DeployableProjectileComponent.java new file mode 100644 index 0000000..e12edf6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/deployables/component/DeployableProjectileComponent.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.builtin.deployables.component; + +import com.hypixel.hytale.builtin.deployables.DeployablesPlugin; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class DeployableProjectileComponent implements Component { + @Nonnull + protected Vector3d previousTickPosition; + + public DeployableProjectileComponent() { + this(Vector3d.ZERO.clone()); + } + + public DeployableProjectileComponent(@Nonnull Vector3d previousTickPosition) { + this.previousTickPosition = previousTickPosition; + } + + public static ComponentType getComponentType() { + return DeployablesPlugin.get().getDeployableProjectileComponentType(); + } + + @Override + public Component clone() { + return new DeployableProjectileComponent(this.previousTickPosition.clone()); + } + + @Nonnull + public Vector3d getPreviousTickPosition() { + return this.previousTickPosition.clone(); + } + + public void setPreviousTickPosition(@Nonnull Vector3d pos) { + this.previousTickPosition = pos.clone(); + } +} diff --git a/src/com/hypixel/hytale/builtin/deployables/component/DeployableProjectileShooterComponent.java b/src/com/hypixel/hytale/builtin/deployables/component/DeployableProjectileShooterComponent.java new file mode 100644 index 0000000..133a5c9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/deployables/component/DeployableProjectileShooterComponent.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.deployables.component; + +import com.hypixel.hytale.builtin.deployables.DeployablesPlugin; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.projectile.config.ProjectileConfig; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class DeployableProjectileShooterComponent implements Component { + protected final List> projectiles = new ObjectArrayList<>(); + protected final List> projectilesForRemoval = new ObjectArrayList<>(); + protected Ref activeTarget; + + public DeployableProjectileShooterComponent() { + } + + public static ComponentType getComponentType() { + return DeployablesPlugin.get().getDeployableProjectileShooterComponentType(); + } + + public void spawnProjectile( + Ref entityRef, + @Nonnull CommandBuffer commandBuffer, + @Nonnull ProjectileConfig projectileConfig, + @Nonnull UUID ownerUuid, + @Nonnull Vector3d spawnPos, + @Nonnull Vector3d direction + ) { + commandBuffer.getExternalData().getWorld().execute(() -> {}); + } + + public List> getProjectiles() { + return this.projectiles; + } + + public List> getProjectilesForRemoval() { + return this.projectilesForRemoval; + } + + public Ref getActiveTarget() { + return this.activeTarget; + } + + public void setActiveTarget(Ref target) { + this.activeTarget = target; + } + + @Override + public Component clone() { + return this; + } +} diff --git a/src/com/hypixel/hytale/builtin/deployables/config/DeployableAoeConfig.java b/src/com/hypixel/hytale/builtin/deployables/config/DeployableAoeConfig.java new file mode 100644 index 0000000..fe907a3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/deployables/config/DeployableAoeConfig.java @@ -0,0 +1,273 @@ +package com.hypixel.hytale.builtin.deployables.config; + +import com.hypixel.hytale.builtin.deployables.component.DeployableComponent; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import java.time.Duration; +import java.time.Instant; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +public class DeployableAoeConfig extends DeployableConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DeployableAoeConfig.class, DeployableAoeConfig::new, DeployableConfig.BASE_CODEC + ) + .append( + new KeyedCodec<>("Shape", new EnumCodec<>(DeployableAoeConfig.Shape.class)), + (DeployableAoeConfig, s) -> DeployableAoeConfig.shape = s, + DeployableAoeConfig -> DeployableAoeConfig.shape + ) + .documentation("The shape of the detection area") + .add() + .append( + new KeyedCodec<>("StartRadius", Codec.FLOAT), + (DeployableAoeConfig, s) -> DeployableAoeConfig.startRadius = s, + DeployableAoeConfig -> DeployableAoeConfig.startRadius + ) + .documentation("The initial detection radius") + .add() + .append( + new KeyedCodec<>("EndRadius", Codec.FLOAT), + (DeployableAoeConfig, s) -> DeployableAoeConfig.endRadius = s, + DeployableAoeConfig -> DeployableAoeConfig.endRadius + ) + .documentation("If set, the detection radius will expand to this size over the RadiusChangeTime (RadiusChangeTime must be set)") + .add() + .append( + new KeyedCodec<>("Height", Codec.FLOAT), (DeployableAoeConfig, s) -> DeployableAoeConfig.height = s, DeployableAoeConfig -> DeployableAoeConfig.height + ) + .documentation("The height of the Shape, if using a cylinder shape") + .add() + .append( + new KeyedCodec<>("RadiusChangeTime", Codec.FLOAT), + (DeployableAoeConfig, s) -> DeployableAoeConfig.radiusChangeTime = s, + DeployableAoeConfig -> DeployableAoeConfig.radiusChangeTime + ) + .documentation("The time (starting at spawn) it takes to change from StartRadius to EndRadius") + .add() + .append( + new KeyedCodec<>("DamageInterval", Codec.FLOAT), + (DeployableAoeConfig, s) -> DeployableAoeConfig.damageInterval = s, + DeployableAoeConfig -> DeployableAoeConfig.damageInterval + ) + .documentation("The interval between damage being applied to targets in seconds") + .add() + .append( + new KeyedCodec<>("DamageAmount", Codec.FLOAT), + (DeployableAoeConfig, s) -> DeployableAoeConfig.damageAmount = s, + DeployableAoeConfig -> DeployableAoeConfig.damageAmount + ) + .documentation("The amount of damage to apply to targets per interval") + .add() + .append( + new KeyedCodec<>("DamageCause", DamageCause.CHILD_ASSET_CODEC), + (DeployableAoeConfig, s) -> DeployableAoeConfig.damageCause = s, + DeployableAoeConfig -> DeployableAoeConfig.damageCause + ) + .documentation("The amount of damage to apply to targets per interval") + .add() + .append( + new KeyedCodec<>("ApplyEffects", new ArrayCodec<>(EntityEffect.CHILD_ASSET_CODEC, String[]::new)), + (DeployableAoeConfig, s) -> DeployableAoeConfig.effectsToApply = s, + DeployableAoeConfig -> DeployableAoeConfig.effectsToApply + ) + .add() + .appendInherited( + new KeyedCodec<>("AttackOwner", Codec.BOOLEAN), (o, i) -> o.attackOwner = i, o -> o.attackOwner, (i, o) -> i.attackOwner = o.attackOwner + ) + .documentation("Whether or not the owner is affected by the attack & effect of this deployable") + .add() + .appendInherited( + new KeyedCodec<>("AttackTeam", Codec.BOOLEAN), (o, i) -> o.attackTeam = i, o -> o.attackTeam, (i, o) -> i.attackTeam = o.attackTeam + ) + .documentation("Whether or not the team is affected by the attack & effect of this deployable") + .add() + .appendInherited( + new KeyedCodec<>("AttackEnemies", Codec.BOOLEAN), (o, i) -> o.attackEnemies = i, o -> o.attackEnemies, (i, o) -> i.attackEnemies = o.attackEnemies + ) + .documentation("Whether or not this deployable interacts with non-team entities") + .add() + .build(); + protected float startRadius = 1.0F; + protected float endRadius = -1.0F; + protected float radiusChangeTime = -1.0F; + protected float damageInterval = 1.0F; + protected float damageAmount = 1.0F; + protected String damageCause = "Physical"; + protected String[] effectsToApply; + protected boolean attackOwner; + protected boolean attackTeam; + protected boolean attackEnemies = true; + protected DeployableAoeConfig.Shape shape = DeployableAoeConfig.Shape.Sphere; + protected float height = 1.0F; + protected DamageCause processedDamageCause; + + protected DeployableAoeConfig() { + } + + @Override + public void tick( + @Nonnull DeployableComponent deployableComponent, + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Vector3d position = archetypeChunk.getComponent(index, TransformComponent.getComponentType()).getPosition(); + World world = store.getExternalData().getWorld(); + Ref entityRef = archetypeChunk.getReferenceTo(index); + float radius = this.getRadius(store, deployableComponent.getSpawnInstant()); + this.handleDebugGraphics(world, deployableComponent.getDebugColor(), position, radius * 2.0F); + switch (deployableComponent.getFlag(DeployableComponent.DeployableFlag.STATE)) { + case 0: + deployableComponent.setFlag(DeployableComponent.DeployableFlag.STATE, 1); + break; + case 1: + deployableComponent.setFlag(DeployableComponent.DeployableFlag.STATE, 2); + playAnimation(store, entityRef, this, "Grow"); + break; + case 2: + if (radius >= this.endRadius) { + deployableComponent.setFlag(DeployableComponent.DeployableFlag.STATE, 3); + playAnimation(store, entityRef, this, "Looping"); + } + } + + Ref deployableRef = archetypeChunk.getReferenceTo(index); + if (deployableComponent.incrementTimeSinceLastAttack(dt) > this.damageInterval) { + deployableComponent.setTimeSinceLastAttack(0.0F); + this.handleDetection(store, commandBuffer, deployableRef, deployableComponent, position, radius, DamageCause.PHYSICAL); + } + + super.tick(deployableComponent, dt, index, archetypeChunk, store, commandBuffer); + } + + protected void handleDetection( + final Store store, + final CommandBuffer commandBuffer, + final Ref deployableRef, + DeployableComponent deployableComponent, + Vector3d position, + float radius, + final DamageCause damageCause + ) { + var attackConsumer = new Consumer>() { + public void accept(Ref entityStoreRef) { + if (entityStoreRef != deployableRef) { + DeployableAoeConfig.this.attackTarget(entityStoreRef, deployableRef, damageCause, commandBuffer); + DeployableAoeConfig.this.applyEffectToTarget(store, entityStoreRef); + } + } + }; + switch (this.shape) { + case Sphere: + for (Ref targetRef : TargetUtil.getAllEntitiesInSphere(position, radius, store)) { + attackConsumer.accept(targetRef); + } + break; + case Cylinder: + for (Ref targetRef : TargetUtil.getAllEntitiesInCylinder(position, radius, this.height, store)) { + attackConsumer.accept(targetRef); + } + } + } + + protected void handleDebugGraphics(World world, Vector3f color, Vector3d position, float scale) { + if (this.getDebugVisuals()) { + ; + } + } + + protected void attackTarget(Ref targetRef, Ref ownerRef, DamageCause damageCause, CommandBuffer commandBuffer) { + if (!(this.damageAmount <= 0.0F)) { + Damage damageEntry = new Damage(new Damage.EntitySource(ownerRef), damageCause, this.damageAmount); + if (targetRef.equals(ownerRef)) { + damageEntry.setSource(Damage.NULL_SOURCE); + } + + DamageSystems.executeDamage(targetRef, commandBuffer, damageEntry); + } + } + + protected void applyEffectToTarget(Store store, Ref targetRef) { + if (this.effectsToApply != null) { + EffectControllerComponent effectController = store.getComponent(targetRef, EffectControllerComponent.getComponentType()); + if (effectController != null) { + for (String effect : this.effectsToApply) { + if (effect != null) { + EntityEffect effectAsset = EntityEffect.getAssetMap().getAsset(effect); + if (effectAsset != null) { + effectController.addEffect(targetRef, effectAsset, store); + } + } + } + } + } + } + + protected boolean canAttackEntity(Ref target, DeployableComponent deployable) { + boolean isOwner = target.equals(deployable.getOwner()); + return !isOwner || this.attackOwner; + } + + protected float getRadius(Store store, Instant startInstant) { + if (!(this.radiusChangeTime <= 0.0F) && !(this.endRadius < 0.0F)) { + float radiusDiff = this.endRadius - this.startRadius; + float increment = radiusDiff / this.radiusChangeTime; + Instant now = store.getResource(TimeResource.getResourceType()).getNow(); + float timeDiff = (float)Duration.between(startInstant, now).toMillis() / 1000.0F; + if (timeDiff > this.radiusChangeTime) { + return this.endRadius; + } else { + float nowIncrement = increment * timeDiff; + return this.startRadius + nowIncrement; + } + } else { + return this.startRadius; + } + } + + protected DamageCause getDamageCause() { + if (this.processedDamageCause == null) { + this.processedDamageCause = DamageCause.getAssetMap().getAsset(this.damageCause); + if (this.processedDamageCause == null) { + this.processedDamageCause = DamageCause.PHYSICAL; + } + } + + return this.processedDamageCause; + } + + @Override + public String toString() { + return "DeployableAoeConfig{}" + super.toString(); + } + + public static enum Shape { + Sphere, + Cylinder; + + private Shape() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/deployables/config/DeployableConfig.java b/src/com/hypixel/hytale/builtin/deployables/config/DeployableConfig.java new file mode 100644 index 0000000..32acdc5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/deployables/config/DeployableConfig.java @@ -0,0 +1,394 @@ +package com.hypixel.hytale.builtin.deployables.config; + +import com.hypixel.hytale.builtin.deployables.DeployablesUtils; +import com.hypixel.hytale.builtin.deployables.component.DeployableComponent; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.AnimationSlot; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelParticle; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.validator.SoundEventValidators; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.entity.hitboxcollision.HitboxCollisionConfig; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public abstract class DeployableConfig implements NetworkSerializable { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(DeployableConfig.class) + .appendInherited(new KeyedCodec<>("Id", Codec.STRING), (o, i) -> o.id = i, o -> o.id, (o, p) -> o.id = p.id) + .documentation("Used to identify this deployable for uses such as MaxLiveCount") + .add() + .appendInherited( + new KeyedCodec<>("MaxLiveCount", Codec.INTEGER), (o, i) -> o.maxLiveCount = i, o -> o.maxLiveCount, (o, p) -> o.maxLiveCount = p.maxLiveCount + ) + .documentation("The maximum amount of this deployable that can be live at once") + .add() + .appendInherited(new KeyedCodec<>("Model", Codec.STRING), (o, i) -> o.model = i, o -> o.model, (o, p) -> o.model = p.model) + .addValidator(Validators.nonNull()) + .addValidator(ModelAsset.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("ModelPreview", Codec.STRING), (o, i) -> o.modelPreview = i, o -> o.modelPreview, (o, p) -> o.modelPreview = p.modelPreview + ) + .addValidator(ModelAsset.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited(new KeyedCodec<>("ModelScale", Codec.FLOAT), (o, i) -> o.modelScale = i, o -> o.modelScale, (o, p) -> o.modelScale = p.modelScale) + .add() + .appendInherited( + new KeyedCodec<>("LiveDuration", Codec.FLOAT), (o, i) -> o.liveDuration = i, o -> o.liveDuration, (o, p) -> o.liveDuration = p.liveDuration + ) + .documentation("The duration of the lifetime of the deployable in seconds") + .add() + .appendInherited( + new KeyedCodec<>("Invulnerable", Codec.BOOLEAN), (o, i) -> o.invulnerable = i, o -> o.invulnerable, (o, p) -> o.invulnerable = p.invulnerable + ) + .documentation("Whether this deployable is invulnerable to damage or not") + .add() + .appendInherited( + new KeyedCodec<>("Stats", new MapCodec<>(DeployableConfig.StatConfig.CODEC, Object2ObjectOpenHashMap::new)), + (o, i) -> o.statValues = i, + o -> o.statValues, + (i, o) -> i.statValues = o.statValues + ) + .documentation("The default stat configuration for the deployable") + .add() + .appendInherited( + new KeyedCodec<>("DeploySoundEventId", Codec.STRING), + (o, i) -> o.deploySoundEventId = i, + o -> o.deploySoundEventId, + (i, o) -> i.deploySoundEventId = o.deploySoundEventId + ) + .documentation("The ID of the sound to play upon deployment (at deployment location)") + .addValidator(SoundEventValidators.ONESHOT) + .addValidator(SoundEventValidators.MONO) + .add() + .appendInherited( + new KeyedCodec<>("DespawnSoundEventId", Codec.STRING), + (o, i) -> o.despawnSoundEventId = i, + o -> o.despawnSoundEventId, + (i, o) -> i.despawnSoundEventId = o.despawnSoundEventId + ) + .documentation("The ID of the sound to play when despawning") + .addValidator(SoundEventValidators.ONESHOT) + .addValidator(SoundEventValidators.MONO) + .add() + .appendInherited( + new KeyedCodec<>("DieSoundEventId", Codec.STRING), + (o, i) -> o.dieSoundEventId = i, + o -> o.dieSoundEventId, + (i, o) -> i.dieSoundEventId = o.dieSoundEventId + ) + .documentation("The ID of the sound to play when despawning due to death") + .addValidator(SoundEventValidators.ONESHOT) + .addValidator(SoundEventValidators.MONO) + .add() + .appendInherited( + new KeyedCodec<>("AmbientSoundEventId", Codec.STRING), + (o, i) -> o.ambientSoundEventId = i, + o -> o.ambientSoundEventId, + (i, o) -> i.ambientSoundEventId = o.ambientSoundEventId + ) + .documentation("The ID of the sound to play ambiently from the deployable while it's in the world") + .addValidator(SoundEventValidators.LOOPING) + .addValidator(SoundEventValidators.MONO) + .add() + .appendInherited( + new KeyedCodec<>("SpawnParticles", ModelParticle.ARRAY_CODEC), + (o, i) -> o.spawnParticles = i, + o -> o.spawnParticles, + (i, o) -> i.spawnParticles = o.spawnParticles + ) + .documentation("A collection of model particles to play when this deployable is spawned.") + .add() + .appendInherited( + new KeyedCodec<>("DespawnParticles", ModelParticle.ARRAY_CODEC), + (o, i) -> o.despawnParticles = i, + o -> o.despawnParticles, + (i, o) -> i.despawnParticles = o.despawnParticles + ) + .documentation("A collection of model particles to play when this deployable is despawned.") + .add() + .appendInherited( + new KeyedCodec<>("DebugVisuals", Codec.BOOLEAN), (o, i) -> o.debugVisuals = i, o -> o.debugVisuals, (i, o) -> i.debugVisuals = o.debugVisuals + ) + .documentation("Whether or not to display debug visuals.") + .add() + .appendInherited( + new KeyedCodec<>("AllowPlaceOnWalls", Codec.BOOLEAN), + (o, i) -> o.allowPlaceOnWalls = i, + o -> o.allowPlaceOnWalls, + (i, o) -> i.allowPlaceOnWalls = o.allowPlaceOnWalls + ) + .documentation("Whether or not this deployable can be placed on walls.") + .add() + .appendInherited( + new KeyedCodec<>("WireframeDebugVisuals", Codec.BOOLEAN), + (o, i) -> o.wireframeDebugVisuals = i, + o -> o.wireframeDebugVisuals, + (i, o) -> i.wireframeDebugVisuals = o.wireframeDebugVisuals + ) + .documentation("Whether debug visuals will be wireframe or have color.") + .add() + .appendInherited( + new KeyedCodec<>("HitboxCollisionConfig", Codec.STRING), + (playerConfig, s) -> playerConfig.hitboxCollisionConfigId = s, + playerConfig -> playerConfig.hitboxCollisionConfigId, + (playerConfig, parent) -> playerConfig.hitboxCollisionConfigId = parent.hitboxCollisionConfigId + ) + .documentation("The HitboxCollision config to apply to the deployable.") + .addValidator(HitboxCollisionConfig.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("CountTowardsGlobalLimit", Codec.BOOLEAN), + (o, i) -> o.countTowardsGlobalLimit = i, + o -> o.countTowardsGlobalLimit, + (i, o) -> i.countTowardsGlobalLimit = o.countTowardsGlobalLimit + ) + .documentation("Whether or not this deployable counts towards global deployable limit") + .add() + .afterDecode(DeployableConfig::processConfig) + .build(); + protected Map statValues; + protected String deploySoundEventId; + protected String despawnSoundEventId; + protected String dieSoundEventId; + protected String ambientSoundEventId; + protected ModelParticle[] spawnParticles; + protected ModelParticle[] despawnParticles; + protected transient int deploySoundEventIndex = 0; + protected transient int despawnSoundEventIndex = 0; + protected transient int dieSoundEventIndex = 0; + protected transient int ambientSoundEventIndex = 0; + protected Model generatedModel; + protected Model generatedModelPreview; + protected String hitboxCollisionConfigId; + protected int hitboxCollisionConfigIndex = -1; + private String id; + private int maxLiveCount = Integer.MAX_VALUE; + private String model; + private String modelPreview; + private float modelScale = 1.0F; + private float liveDuration = 1.0F; + private boolean invulnerable; + private boolean debugVisuals; + private boolean allowPlaceOnWalls; + private boolean wireframeDebugVisuals; + private boolean countTowardsGlobalLimit = true; + + protected DeployableConfig() { + } + + private static void processConfig(DeployableConfig config) { + if (config.deploySoundEventId != null) { + config.deploySoundEventIndex = SoundEvent.getAssetMap().getIndex(config.deploySoundEventId); + } + + if (config.despawnSoundEventId != null) { + config.despawnSoundEventIndex = SoundEvent.getAssetMap().getIndex(config.despawnSoundEventId); + } + + if (config.dieSoundEventId != null) { + config.dieSoundEventIndex = SoundEvent.getAssetMap().getIndex(config.dieSoundEventId); + } + + if (config.ambientSoundEventId != null) { + config.ambientSoundEventIndex = SoundEvent.getAssetMap().getIndex(config.ambientSoundEventId); + } + + if (config.generatedModel != null) { + config.generatedModel = Model.createScaledModel(ModelAsset.getAssetMap().getAsset(config.model), config.modelScale); + } + + if (config.generatedModelPreview != null) { + config.generatedModelPreview = Model.createScaledModel(ModelAsset.getAssetMap().getAsset(config.modelPreview), config.modelScale); + } + + if (config.hitboxCollisionConfigId != null) { + config.hitboxCollisionConfigIndex = HitboxCollisionConfig.getAssetMap().getIndexOrDefault(config.hitboxCollisionConfigId, -1); + } + } + + protected static void playAnimation( + @Nonnull Store store, @Nonnull Ref ref, @Nonnull DeployableConfig config, @Nonnull String animationSetKey + ) { + EntityStore externalData = store.getExternalData(); + NetworkId networkIdComponent = store.getComponent(ref, NetworkId.getComponentType()); + DeployablesUtils.playAnimation(store, networkIdComponent.getId(), ref, config, AnimationSlot.Action, null, animationSetKey); + } + + protected static void stopAnimation(@Nonnull Store store, @Nonnull ArchetypeChunk archetypeChunk, int index) { + EntityStore externalData = store.getExternalData(); + Ref ref = archetypeChunk.getReferenceTo(index); + if (ref != null && ref.isValid()) { + NetworkId networkIdComponent = archetypeChunk.getComponent(index, NetworkId.getComponentType()); + DeployablesUtils.stopAnimation(store, networkIdComponent.getId(), ref, AnimationSlot.Action); + } + } + + public Model getModel() { + if (this.generatedModel != null) { + return this.generatedModel; + } else { + this.generatedModel = Model.createScaledModel(ModelAsset.getAssetMap().getAsset(this.model), this.modelScale); + return this.generatedModel; + } + } + + public Model getModelPreview() { + if (this.modelPreview == null) { + return null; + } else if (this.generatedModelPreview != null) { + return this.generatedModelPreview; + } else { + this.generatedModelPreview = Model.createScaledModel(ModelAsset.getAssetMap().getAsset(this.modelPreview), this.modelScale); + return this.generatedModelPreview; + } + } + + public int getHitboxCollisionConfigIndex() { + return this.hitboxCollisionConfigIndex; + } + + public long getLiveDurationInMillis() { + return (long)(this.liveDuration * 1000.0F); + } + + public float getLiveDuration() { + return this.liveDuration; + } + + public String getId() { + return this.id; + } + + public int getMaxLiveCount() { + return this.maxLiveCount; + } + + public boolean getInvulnerable() { + return this.invulnerable; + } + + public Map getStatValues() { + return this.statValues; + } + + public int getDespawnSoundEventIndex() { + return this.despawnSoundEventIndex; + } + + public int getDeploySoundEventIndex() { + return this.deploySoundEventIndex; + } + + public int getDieSoundEventIndex() { + return this.dieSoundEventIndex; + } + + public int getAmbientSoundEventIndex() { + return this.ambientSoundEventIndex; + } + + public ModelParticle[] getSpawnParticles() { + return this.spawnParticles; + } + + public ModelParticle[] getDespawnParticles() { + return this.despawnParticles; + } + + public boolean getDebugVisuals() { + return this.debugVisuals; + } + + public boolean getAllowPlaceOnWalls() { + return this.allowPlaceOnWalls; + } + + public boolean getWireframeDebugVisuals() { + return this.wireframeDebugVisuals; + } + + public boolean getCountTowardsGlobalLimit() { + return this.countTowardsGlobalLimit; + } + + public void tick( + @Nonnull DeployableComponent deployableComponent, + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void firstTick( + @Nonnull DeployableComponent deployableComponent, + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public com.hypixel.hytale.protocol.DeployableConfig toPacket() { + com.hypixel.hytale.protocol.DeployableConfig config = new com.hypixel.hytale.protocol.DeployableConfig(); + config.model = this.getModel().toPacket(); + if (this.modelPreview != null) { + config.modelPreview = this.getModelPreview().toPacket(); + } + + config.allowPlaceOnWalls = this.allowPlaceOnWalls; + return config; + } + + @Override + public String toString() { + return "DeployableConfig{}"; + } + + public static class StatConfig { + private static final BuilderCodec CODEC = BuilderCodec.builder( + DeployableConfig.StatConfig.class, DeployableConfig.StatConfig::new + ) + .documentation("Initial and maximum values for a stat.") + .append(new KeyedCodec<>("Max", Codec.FLOAT), (config, f) -> config.max = f, config -> config.max) + .addValidator(Validators.nonNull()) + .addValidator(Validators.greaterThan(0.0F)) + .documentation("The maximum value for the stat.") + .add() + .append(new KeyedCodec<>("Initial", Codec.FLOAT), (config, f) -> config.initial = f, config -> config.initial) + .documentation("The initial value for the stat. If omitted, will be set to max.") + .add() + .build(); + private float max; + private float initial = Float.MAX_VALUE; + + private StatConfig() { + } + + public float getMax() { + return this.max; + } + + public float getInitial() { + return this.initial; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/deployables/config/DeployableSpawner.java b/src/com/hypixel/hytale/builtin/deployables/config/DeployableSpawner.java new file mode 100644 index 0000000..fce6237 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/deployables/config/DeployableSpawner.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.builtin.deployables.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3d; + +public class DeployableSpawner implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + DeployableSpawner.class, DeployableSpawner::new, Codec.STRING, (t, k) -> t.id = k, t -> t.id, (asset, data) -> asset.data = data, asset -> asset.data + ) + .append(new KeyedCodec<>("Config", DeployableConfig.CODEC), (i, s) -> i.config = s, i -> i.config) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("PositionOffsets", new ArrayCodec<>(Vector3d.CODEC, Vector3d[]::new)), (i, s) -> i.positionOffsets = s, i -> i.positionOffsets) + .add() + .build(); + private static DefaultAssetMap ASSET_MAP; + protected String id; + protected AssetExtraInfo.Data data; + private DeployableConfig config; + private Vector3d[] positionOffsets; + + public DeployableSpawner(String id, DeployableConfig config, Vector3d[] positionOffsets) { + this.id = id; + this.config = config; + this.positionOffsets = positionOffsets; + } + + public DeployableSpawner() { + } + + public static DefaultAssetMap getAssetMap() { + if (ASSET_MAP == null) { + ASSET_MAP = (DefaultAssetMap)AssetRegistry.getAssetStore(DeployableSpawner.class).getAssetMap(); + } + + return ASSET_MAP; + } + + public Vector3d[] getPositionOffsets() { + return this.positionOffsets; + } + + public DeployableConfig getConfig() { + return this.config; + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/builtin/deployables/config/DeployableTrapConfig.java b/src/com/hypixel/hytale/builtin/deployables/config/DeployableTrapConfig.java new file mode 100644 index 0000000..4b84433 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/deployables/config/DeployableTrapConfig.java @@ -0,0 +1,171 @@ +package com.hypixel.hytale.builtin.deployables.config; + +import com.hypixel.hytale.builtin.deployables.component.DeployableComponent; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.DespawnComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +public class DeployableTrapConfig extends DeployableAoeConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DeployableTrapConfig.class, DeployableTrapConfig::new, DeployableAoeConfig.CODEC + ) + .appendInherited( + new KeyedCodec<>("FuzeDuration", Codec.FLOAT), (o, i) -> o.fuzeDuration = i, o -> o.fuzeDuration, (o, p) -> o.fuzeDuration = p.fuzeDuration + ) + .documentation("The time it will take for the trap to become active") + .add() + .appendInherited( + new KeyedCodec<>("ActiveDuration", Codec.FLOAT), (o, i) -> o.activeDuration = i, o -> o.activeDuration, (o, p) -> o.activeDuration = p.activeDuration + ) + .documentation("The time the trap will stay alive after getting triggered") + .add() + .appendInherited( + new KeyedCodec<>("DestroyOnTriggered", Codec.BOOLEAN), + (o, i) -> o.destroyOnTriggered = i, + o -> o.destroyOnTriggered, + (o, p) -> o.destroyOnTriggered = p.destroyOnTriggered + ) + .documentation("Whether the trap will disappear when it's triggered by a players") + .add() + .build(); + protected float fuzeDuration = 0.0F; + protected float activeDuration = 1.0F; + protected boolean destroyOnTriggered = false; + + protected DeployableTrapConfig() { + } + + @Override + public void tick( + @Nonnull DeployableComponent deployableComponent, + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Vector3d pos = archetypeChunk.getComponent(index, TransformComponent.getComponentType()).getPosition(); + World world = store.getExternalData().getWorld(); + Ref entityRef = archetypeChunk.getReferenceTo(index); + if (!deployableComponent.getOwner().isValid()) { + world.execute(() -> { + if (entityRef.isValid()) { + DespawnComponent despawn = store.ensureAndGetComponent(entityRef, DespawnComponent.getComponentType()); + WorldTimeResource timeManager = commandBuffer.getResource(WorldTimeResource.getResourceType()); + despawn.setDespawn(timeManager.getGameTime()); + } + }); + } else { + float radius = this.getRadius(store, deployableComponent.getSpawnInstant()); + this.handleDebugGraphics(world, deployableComponent.getDebugColor(), pos, radius * 2.0F); + switch (deployableComponent.getFlag(DeployableComponent.DeployableFlag.STATE)) { + case 0: + deployableComponent.setFlag(DeployableComponent.DeployableFlag.STATE, 1); + break; + case 1: + deployableComponent.setFlag(DeployableComponent.DeployableFlag.STATE, 2); + playAnimation(store, entityRef, this, "Grow"); + break; + case 2: + if (radius >= this.endRadius) { + deployableComponent.setFlag(DeployableComponent.DeployableFlag.STATE, 3); + playAnimation(store, entityRef, this, "Looping"); + } + } + + Ref trapRef = archetypeChunk.getReferenceTo(index); + deployableComponent.setTimeSinceLastAttack(deployableComponent.getTimeSinceLastAttack() + dt); + if (deployableComponent.getTimeSinceLastAttack() > this.damageInterval && this.isLive(store, deployableComponent)) { + deployableComponent.setTimeSinceLastAttack(0.0F); + this.handleDetection(store, commandBuffer, trapRef, deployableComponent, pos, radius, DamageCause.PHYSICAL); + } + } + } + + @Override + protected void handleDetection( + final Store store, + final CommandBuffer commandBuffer, + final Ref deployableRef, + final DeployableComponent deployableComponent, + Vector3d position, + float radius, + final DamageCause damageCause + ) { + World world = store.getExternalData().getWorld(); + var consumer = new Consumer>() { + public void accept(Ref entityStoreRef) { + if (entityStoreRef != deployableRef) { + if (store.getComponent(entityStoreRef, DeployableComponent.getComponentType()) == null) { + DeployableTrapConfig.this.attackTarget(entityStoreRef, deployableRef, damageCause, commandBuffer); + if (DeployableTrapConfig.this.destroyOnTriggered && deployableComponent.getFlag(DeployableComponent.DeployableFlag.TRIGGERED) == 0) { + DeployableTrapConfig.this.onTriggered(store, deployableRef); + deployableComponent.setFlag(DeployableComponent.DeployableFlag.TRIGGERED, 1); + } + + DeployableTrapConfig.this.applyEffectToTarget(store, entityStoreRef); + } + } + } + }; + switch (this.shape) { + case Sphere: + for (Ref targetRef : TargetUtil.getAllEntitiesInSphere(position, radius, store)) { + consumer.accept(targetRef); + } + break; + case Cylinder: + for (Ref targetRef : TargetUtil.getAllEntitiesInCylinder(position, radius, this.height, store)) { + consumer.accept(targetRef); + } + } + } + + protected boolean isLive(@Nonnull Store store, @Nonnull DeployableComponent comp) { + if (comp.getFlag(DeployableComponent.DeployableFlag.LIVE) == 1) { + return true; + } else if (this.fuzeDuration == 0.0F) { + comp.setFlag(DeployableComponent.DeployableFlag.LIVE, 1); + return true; + } else { + Instant now = store.getResource(TimeResource.getResourceType()).getNow(); + Instant spawnTime = comp.getSpawnInstant(); + float timeDiff = (float)Duration.between(spawnTime, now).toMillis() / 1000.0F; + if (timeDiff >= this.fuzeDuration) { + comp.setFlag(DeployableComponent.DeployableFlag.LIVE, 1); + return true; + } else { + return false; + } + } + } + + protected void onTriggered(@Nonnull Store store, @Nonnull Ref ref) { + Instant now = store.getResource(TimeResource.getResourceType()).getNow(); + DespawnComponent despawnComponent = store.getComponent(ref, DespawnComponent.getComponentType()); + despawnComponent.setDespawn(now.plus((long)this.activeDuration, ChronoUnit.SECONDS)); + } + + @Override + public String toString() { + return "DeployableTrapConfig{}" + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/deployables/config/DeployableTrapSpawnerConfig.java b/src/com/hypixel/hytale/builtin/deployables/config/DeployableTrapSpawnerConfig.java new file mode 100644 index 0000000..8b5a0e4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/deployables/config/DeployableTrapSpawnerConfig.java @@ -0,0 +1,151 @@ +package com.hypixel.hytale.builtin.deployables.config; + +import com.hypixel.hytale.builtin.deployables.DeployablesUtils; +import com.hypixel.hytale.builtin.deployables.component.DeployableComponent; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import javax.annotation.Nonnull; + +public class DeployableTrapSpawnerConfig extends DeployableTrapConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DeployableTrapSpawnerConfig.class, DeployableTrapSpawnerConfig::new, DeployableTrapConfig.CODEC + ) + .appendInherited( + new KeyedCodec<>("DeployableConfig", new ArrayCodec<>(Codec.STRING, String[]::new)), + (o, i) -> o.deployableSpawnerIds = i, + o -> o.deployableSpawnerIds, + (o, p) -> o.deployableSpawnerIds = p.deployableSpawnerIds + ) + .addValidator(Validators.nonNull()) + .add() + .afterDecode(config -> { + if (config.deployableSpawnerIds != null) { + int length = config.deployableSpawnerIds.length; + config.deployableSpawners = new DeployableSpawner[length]; + + for (int i = 0; i < length; i++) { + String key = config.deployableSpawnerIds[i]; + config.deployableSpawners[i] = DeployableSpawner.getAssetMap().getAsset(key); + } + } + }) + .build(); + private String[] deployableSpawnerIds; + private DeployableSpawner[] deployableSpawners; + + public DeployableTrapSpawnerConfig() { + } + + @Override + public void tick( + @Nonnull DeployableComponent deployableComponent, + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Ref entityRef = archetypeChunk.getReferenceTo(index); + switch (deployableComponent.getFlag(DeployableComponent.DeployableFlag.STATE)) { + case 0: + this.tickDeploymentState(store, deployableComponent, entityRef); + break; + case 1: + this.tickDeployAnimationState(store, deployableComponent, entityRef); + break; + case 2: + this.tickFuzeState(store, deployableComponent); + break; + case 3: + this.tickLiveState(store, deployableComponent, entityRef, commandBuffer, dt); + break; + case 4: + this.tickTriggeredState(commandBuffer, store, deployableComponent, entityRef); + break; + case 5: + this.tickDespawnState(deployableComponent, entityRef, store); + } + } + + private void tickDeploymentState(@Nonnull Store store, @Nonnull DeployableComponent component, @Nonnull Ref entityRef) { + component.setFlag(DeployableComponent.DeployableFlag.STATE, 1); + playAnimation(store, entityRef, this, "Deploy"); + } + + private void tickDeployAnimationState(Store store, DeployableComponent component, Ref entityRef) { + component.setFlag(DeployableComponent.DeployableFlag.STATE, 2); + playAnimation(store, entityRef, this, "Deploy"); + } + + private void tickFuzeState(@Nonnull Store store, @Nonnull DeployableComponent component) { + Instant now = store.getResource(TimeResource.getResourceType()).getNow(); + Instant readyTime = component.getSpawnInstant().plus((long)this.fuzeDuration, ChronoUnit.SECONDS); + if (now.isAfter(readyTime)) { + component.setFlag(DeployableComponent.DeployableFlag.STATE, 3); + } + } + + private void tickLiveState( + @Nonnull Store store, + @Nonnull DeployableComponent component, + @Nonnull Ref entityRef, + CommandBuffer commandBuffer, + float dt + ) { + Vector3d position = store.getComponent(entityRef, TransformComponent.getComponentType()).getPosition(); + float radius = this.getRadius(store, component.getSpawnInstant()); + component.setTimeSinceLastAttack(component.getTimeSinceLastAttack() + dt); + if (component.getTimeSinceLastAttack() > this.damageInterval && this.isLive(store, component)) { + component.setTimeSinceLastAttack(0.0F); + this.handleDetection(store, commandBuffer, entityRef, component, position, radius, DamageCause.PHYSICAL); + } + } + + private void tickTriggeredState( + CommandBuffer commandBuffer, @Nonnull Store store, @Nonnull DeployableComponent component, @Nonnull Ref entityRef + ) { + component.setFlag(DeployableComponent.DeployableFlag.STATE, 5); + Vector3d parentPosition = store.getComponent(entityRef, TransformComponent.getComponentType()).getPosition(); + Ref parentOwner = store.getComponent(entityRef, DeployableComponent.getComponentType()).getOwner(); + World world = store.getExternalData().getWorld(); + if (this.deployableSpawners != null) { + for (DeployableSpawner spawner : this.deployableSpawners) { + if (spawner != null) { + DeployableConfig config = spawner.getConfig(); + Vector3d[] positionOffsets = spawner.getPositionOffsets(); + + for (Vector3d offset : positionOffsets) { + Vector3f childPosition = Vector3d.add(parentPosition, offset).toVector3f(); + world.execute(() -> DeployablesUtils.spawnDeployable(commandBuffer, store, config, parentOwner, childPosition, new Vector3f(), "UP")); + } + } + } + } + } + + private void tickDespawnState(@Nonnull DeployableComponent component, @Nonnull Ref entityRef, @Nonnull Store store) { + component.setFlag(DeployableComponent.DeployableFlag.STATE, 6); + super.onTriggered(store, entityRef); + } + + @Override + protected void onTriggered(@Nonnull Store store, @Nonnull Ref ref) { + store.getComponent(ref, DeployableComponent.getComponentType()).setFlag(DeployableComponent.DeployableFlag.STATE, 4); + } +} diff --git a/src/com/hypixel/hytale/builtin/deployables/config/DeployableTurretConfig.java b/src/com/hypixel/hytale/builtin/deployables/config/DeployableTurretConfig.java new file mode 100644 index 0000000..4bd9a51 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/deployables/config/DeployableTurretConfig.java @@ -0,0 +1,551 @@ +package com.hypixel.hytale.builtin.deployables.config; + +import com.hypixel.hytale.builtin.deployables.DeployablesUtils; +import com.hypixel.hytale.builtin.deployables.component.DeployableComponent; +import com.hypixel.hytale.builtin.deployables.component.DeployableProjectileComponent; +import com.hypixel.hytale.builtin.deployables.component.DeployableProjectileShooterComponent; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.protocol.Opacity; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.knockback.KnockbackComponent; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.DespawnComponent; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.Knockback; +import com.hypixel.hytale.server.core.modules.projectile.config.ProjectileConfig; +import com.hypixel.hytale.server.core.modules.projectile.config.StandardPhysicsProvider; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nonnull; + +public class DeployableTurretConfig extends DeployableConfig { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + DeployableTurretConfig.class, DeployableTurretConfig::new, DeployableConfig.BASE_CODEC + ) + .appendInherited( + new KeyedCodec<>("TrackableRadius", Codec.FLOAT), + (o, i) -> o.trackableRadius = i, + o -> o.trackableRadius, + (o, p) -> o.trackableRadius = p.trackableRadius + ) + .documentation("The radius in which a targeted entity can be tracked") + .add() + .appendInherited( + new KeyedCodec<>("DetectionRadius", Codec.FLOAT), + (o, i) -> o.detectionRadius = i, + o -> o.detectionRadius, + (o, p) -> o.detectionRadius = p.detectionRadius + ) + .documentation("The radius in which an entity can be targeted") + .add() + .appendInherited( + new KeyedCodec<>("RotationSpeed", Codec.FLOAT), (o, i) -> o.rotationSpeed = i, o -> o.rotationSpeed, (o, p) -> o.rotationSpeed = p.rotationSpeed + ) + .documentation("The speed at which the turret can rotate to hit it's target") + .add() + .appendInherited( + new KeyedCodec<>("PreferOwnerTarget", Codec.BOOLEAN), + (o, i) -> o.preferOwnerTarget = i, + o -> o.preferOwnerTarget, + (o, p) -> o.preferOwnerTarget = p.preferOwnerTarget + ) + .documentation("If true, will prefer targeting entities that the owner is attacking") + .add() + .appendInherited(new KeyedCodec<>("Ammo", Codec.INTEGER), (o, i) -> o.ammo = i, o -> o.ammo, (o, p) -> o.ammo = p.ammo) + .documentation("The total ammo the turret has, each projectile will consume one") + .add() + .appendInherited( + new KeyedCodec<>("DeployDelay", Codec.FLOAT), (o, i) -> o.deployDelay = i, o -> o.deployDelay, (o, p) -> o.deployDelay = p.deployDelay + ) + .documentation("The delay in seconds until the deployable is ready to begin targeting logic") + .add() + .appendInherited( + new KeyedCodec<>("ProjectileConfig", ProjectileConfig.CODEC), + (o, i) -> o.projectileConfig = i, + o -> o.projectileConfig, + (o, p) -> o.projectileConfig = p.projectileConfig + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("ShotInterval", Codec.FLOAT), (o, i) -> o.shotInterval = i, o -> o.shotInterval, (o, p) -> o.shotInterval = p.shotInterval + ) + .add() + .appendInherited(new KeyedCodec<>("BurstCount", Codec.INTEGER), (o, i) -> o.burstCount = i, o -> o.burstCount, (o, p) -> o.burstCount = p.burstCount) + .add() + .appendInherited( + new KeyedCodec<>("BurstCooldown", Codec.FLOAT), (o, i) -> o.burstCooldown = i, o -> o.burstCooldown, (o, p) -> o.burstCooldown = p.burstCooldown + ) + .add() + .appendInherited( + new KeyedCodec<>("ProjectileDamage", Codec.FLOAT), + (o, i) -> o.projectileDamage = i, + o -> o.projectileDamage, + (o, p) -> o.projectileDamage = p.projectileDamage + ) + .add() + .appendInherited( + new KeyedCodec<>("CanShootOwner", Codec.BOOLEAN), (o, i) -> o.canShootOwner = i, o -> o.canShootOwner, (o, p) -> o.canShootOwner = p.canShootOwner + ) + .add() + .appendInherited( + new KeyedCodec<>("Knockback", Knockback.CODEC), + (i, s) -> i.projectileKnockback = s, + i -> i.projectileKnockback, + (i, parent) -> i.projectileKnockback = parent.projectileKnockback + ) + .add() + .appendInherited( + new KeyedCodec<>("TargetOffset", Vector3d.CODEC), + (i, s) -> i.targetOffset = s, + i -> i.targetOffset, + (i, parent) -> i.targetOffset = parent.targetOffset + ) + .add() + .appendInherited( + new KeyedCodec<>("DoLineOfSightTest", Codec.BOOLEAN), + (o, i) -> o.doLineOfSightTest = i, + o -> o.doLineOfSightTest, + (o, p) -> o.doLineOfSightTest = p.doLineOfSightTest + ) + .add() + .appendInherited( + new KeyedCodec<>("ProjectileHitWorldSoundEventId", Codec.STRING), + (o, i) -> o.projectileHitWorldSoundEventId = i, + o -> o.projectileHitWorldSoundEventId, + (o, p) -> o.projectileHitWorldSoundEventId = p.projectileHitWorldSoundEventId + ) + .documentation("The positioned sound event played to surrounding players when the projectile hits a player") + .add() + .appendInherited( + new KeyedCodec<>("ProjectileHitLocalSoundEventId", Codec.STRING), + (o, i) -> o.projectileHitLocalSoundEventId = i, + o -> o.projectileHitLocalSoundEventId, + (o, p) -> o.projectileHitLocalSoundEventId = p.projectileHitLocalSoundEventId + ) + .documentation("The positioned sound event played to a player hit by the projectile") + .add() + .appendInherited( + new KeyedCodec<>("RespectTeams", Codec.BOOLEAN), (o, i) -> o.respectTeams = i, o -> o.respectTeams, (o, p) -> o.respectTeams = p.respectTeams + ) + .add() + .appendInherited( + new KeyedCodec<>("ProjectileSpawnOffsets", new MapCodec<>(Vector3d.CODEC, Object2ObjectOpenHashMap::new, true)), + (o, i) -> o.projectileSpawnOffsets = i, + o -> o.projectileSpawnOffsets, + (o, p) -> o.projectileSpawnOffsets = p.projectileSpawnOffsets + ) + .add() + .afterDecode(DeployableTurretConfig::processConfig) + .build(); + protected float trackableRadius; + protected float detectionRadius; + protected float rotationSpeed; + protected float projectileDamage; + protected boolean preferOwnerTarget; + protected int ammo; + protected ProjectileConfig projectileConfig; + protected float deployDelay; + protected float shotInterval; + protected int burstCount; + protected float burstCooldown; + protected boolean canShootOwner; + protected Knockback projectileKnockback; + protected Vector3d targetOffset = new Vector3d(0.0, 0.0, 0.0); + protected boolean doLineOfSightTest = true; + protected String projectileHitWorldSoundEventId; + protected String projectileHitLocalSoundEventId; + protected int projectileHitLocalSoundEventIndex = 0; + protected int projectileHitWorldSoundEventIndex = 0; + protected boolean respectTeams = true; + protected Map projectileSpawnOffsets = new Object2ObjectOpenHashMap<>(); + + public DeployableTurretConfig() { + } + + protected void processConfig() { + if (this.projectileHitWorldSoundEventId != null) { + this.projectileHitWorldSoundEventIndex = this.projectileHitLocalSoundEventIndex = SoundEvent.getAssetMap() + .getIndex(this.projectileHitWorldSoundEventId); + } + + if (this.projectileHitLocalSoundEventId != null) { + this.projectileHitLocalSoundEventIndex = SoundEvent.getAssetMap().getIndex(this.projectileHitLocalSoundEventId); + } + } + + @Override + public void tick( + @Nonnull DeployableComponent deployableComponent, + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Ref entityRef = archetypeChunk.getReferenceTo(index); + switch (deployableComponent.getFlag(DeployableComponent.DeployableFlag.STATE)) { + case 0: + this.tickInitState(entityRef, deployableComponent, store, commandBuffer); + break; + case 1: + this.tickStartDeployState(entityRef, deployableComponent, store); + break; + case 2: + this.tickAwaitDeployState(entityRef, deployableComponent, store); + break; + case 3: + this.tickAttackState(entityRef, deployableComponent, dt, store, commandBuffer); + } + } + + private void tickInitState( + @Nonnull Ref entityRef, + @Nonnull DeployableComponent component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + component.setFlag(DeployableComponent.DeployableFlag.STATE, 1); + commandBuffer.addComponent(entityRef, DeployableProjectileShooterComponent.getComponentType()); + playAnimation(store, entityRef, this, "Deploy"); + } + + private void tickStartDeployState(@Nonnull Ref ref, @Nonnull DeployableComponent component, @Nonnull Store store) { + component.setFlag(DeployableComponent.DeployableFlag.STATE, 2); + playAnimation(store, ref, this, "Deploy"); + } + + private void tickAwaitDeployState(@Nonnull Ref ref, @Nonnull DeployableComponent component, @Nonnull Store store) { + Instant now = store.getResource(TimeResource.getResourceType()).getNow(); + Instant readyTime = component.getSpawnInstant().plus((long)this.deployDelay, ChronoUnit.SECONDS); + if (now.isAfter(readyTime)) { + component.setFlag(DeployableComponent.DeployableFlag.STATE, 3); + playAnimation(store, ref, this, "Loop"); + } + } + + private void tickAttackState( + @Nonnull Ref ref, + @Nonnull DeployableComponent component, + float dt, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + component.setTimeSinceLastAttack(component.getTimeSinceLastAttack() + dt); + World world = commandBuffer.getExternalData().getWorld(); + DeployableProjectileShooterComponent shooterComponent = store.getComponent(ref, DeployableProjectileShooterComponent.getComponentType()); + Vector3d spawnPos = Vector3d.ZERO.clone(); + if (this.projectileSpawnOffsets != null) { + spawnPos.add(this.projectileSpawnOffsets.get(component.getSpawnFace())); + } + + if (shooterComponent == null) { + world.execute(() -> { + if (ref.isValid()) { + DespawnComponent despawn = store.ensureAndGetComponent(ref, DespawnComponent.getComponentType()); + WorldTimeResource timeManager = commandBuffer.getResource(WorldTimeResource.getResourceType()); + despawn.setDespawn(timeManager.getGameTime()); + } + }); + } else { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3d pos = Vector3d.add(spawnPos, transformComponent.getPosition()); + this.updateProjectiles(store, commandBuffer, shooterComponent); + boolean hasTarget = false; + Ref target = shooterComponent.getActiveTarget(); + if (target != null && target.isValid()) { + TransformComponent targetTransformComponent = store.getComponent(target, TransformComponent.getComponentType()); + + assert targetTransformComponent != null; + + Vector3d targetPos = this.calculatedTargetPosition(targetTransformComponent.getPosition()); + Vector3d direction = Vector3d.directionTo(pos, targetPos); + if (targetPos.distanceTo(pos) <= this.trackableRadius && this.testLineOfSight(pos, targetPos, direction, commandBuffer)) { + hasTarget = true; + } + } + + if (!hasTarget) { + Ref closestTarget = null; + Vector3d closestTargetPos = Vector3d.MAX; + + for (Ref potentialTargetRef : TargetUtil.getAllEntitiesInSphere(pos, this.detectionRadius, commandBuffer)) { + if (potentialTargetRef != null && potentialTargetRef.isValid()) { + TransformComponent targetTransformComponentx = store.getComponent(potentialTargetRef, TransformComponent.getComponentType()); + + assert targetTransformComponentx != null; + + Vector3d targetPosition = this.calculatedTargetPosition(targetTransformComponentx.getPosition()); + Vector3d direction = Vector3d.directionTo(pos, targetPosition); + if (this.testLineOfSight(pos, targetPosition, direction, commandBuffer) + && this.isValidTarget(ref, store, potentialTargetRef) + && pos.distanceTo(targetPosition) < pos.distanceTo(closestTargetPos)) { + closestTargetPos = targetPosition; + closestTarget = potentialTargetRef; + } + } + } + + if (closestTarget != null) { + shooterComponent.setActiveTarget(closestTarget); + target = closestTarget; + hasTarget = true; + } + } + + Vector3d targetPos = Vector3d.ZERO; + Vector3f targetLookRotation = Vector3f.ZERO; + Vector3f lookRotation = Vector3f.ZERO; + if (hasTarget) { + TransformComponent targetTransformComponentxx = store.getComponent(target, TransformComponent.getComponentType()); + + assert targetTransformComponentxx != null; + + targetPos = this.calculatedTargetPosition(targetTransformComponentxx.getPosition().clone()); + Vector3d relativeTargetOffset = new Vector3d(pos.x - targetPos.x, pos.y - targetPos.y, pos.z - targetPos.z); + targetLookRotation = Vector3f.lookAt(relativeTargetOffset.negate()); + lookRotation = Vector3f.lerpAngle(headRotationComponent.getRotation(), targetLookRotation, this.rotationSpeed * dt); + } + + headRotationComponent.setRotation(lookRotation); + int shotsFired = component.getFlag(DeployableComponent.DeployableFlag.BURST_SHOTS); + float timeSinceLastAttack = component.getTimeSinceLastAttack(); + boolean canFire = false; + if (shotsFired < this.burstCount && timeSinceLastAttack >= this.shotInterval) { + component.setFlag(DeployableComponent.DeployableFlag.BURST_SHOTS, shotsFired + 1); + canFire = true; + } else if (shotsFired >= this.burstCount && timeSinceLastAttack >= this.burstCooldown) { + component.setFlag(DeployableComponent.DeployableFlag.BURST_SHOTS, 1); + canFire = true; + } + + if (canFire && hasTarget) { + Vector3d fwdDirection = new Vector3d().assign(lookRotation.getYaw(), lookRotation.getPitch()); + Vector3d rootPos = transformComponent.getPosition(); + Vector3d projectileSpawnPos = Vector3d.ZERO.clone(); + if (this.projectileSpawnOffsets != null) { + projectileSpawnPos = this.projectileSpawnOffsets.get(component.getSpawnFace()).clone(); + } + + projectileSpawnPos.add(fwdDirection.clone().normalize()); + projectileSpawnPos.add(rootPos); + UUID uuid = store.getComponent(ref, UUIDComponent.getComponentType()).getUuid(); + shooterComponent.spawnProjectile(ref, commandBuffer, this.projectileConfig, uuid, projectileSpawnPos, fwdDirection.clone()); + playAnimation(store, ref, this, "Shoot"); + component.setTimeSinceLastAttack(0.0F); + } + } + } + + private Vector3d calculatedTargetPosition(@Nonnull Vector3d original) { + return Vector3d.add(original.clone(), this.targetOffset); + } + + private boolean isValidTarget(@Nonnull Ref ref, @Nonnull Store store, @Nonnull Ref targetRef) { + if (targetRef.equals(ref)) { + return false; + } else { + DeployableComponent deployableComponent = store.getComponent(ref, DeployableComponent.getComponentType()); + return deployableComponent == null ? true : this.canShootOwner || !targetRef.equals(deployableComponent.getOwner()); + } + } + + private boolean testLineOfSight( + @Nonnull Vector3d attackerPos, @Nonnull Vector3d targetPos, @Nonnull Vector3d direction, @Nonnull CommandBuffer commandBuffer + ) { + if (!this.doLineOfSightTest) { + return true; + } else { + com.hypixel.hytale.protocol.Vector3f spawnOffset = this.projectileConfig.getSpawnOffset(); + Vector3d testFromPos = attackerPos.clone().add(spawnOffset.x, spawnOffset.y + this.generatedModel.getEyeHeight(), spawnOffset.z); + double distance = testFromPos.distanceTo(targetPos); + World world = commandBuffer.getExternalData().getWorld(); + Vector3f whiteColor = new Vector3f(1.0F, 1.0F, 1.0F); + if (this.getDebugVisuals()) { + Vector3d increment = direction.scale(distance); + + for (int i = 0; i < 10; i++) { + Vector3d pos = testFromPos.clone(); + pos.addScaled(increment, i / 10.0F); + DebugUtils.addSphere(world, pos, whiteColor, 0.1F, 0.5F); + } + } + + Vector3i blockPosition = TargetUtil.getTargetBlock(world, (id, fluid_id) -> { + if (id == 0) { + return false; + } else { + BlockType blockType = BlockType.getAssetMap().getAsset(id); + BlockMaterial material = blockType.getMaterial(); + return material == BlockMaterial.Empty ? false : blockType.getOpacity() != Opacity.Transparent; + } + }, attackerPos.x, attackerPos.y, attackerPos.z, direction.x, direction.y, direction.z, distance); + if (blockPosition == null) { + return true; + } else { + double entityDistance = attackerPos.distanceSquaredTo(targetPos); + double blockDistance = attackerPos.distanceSquaredTo(blockPosition.x + 0.5, blockPosition.y + 0.5, blockPosition.z + 0.5); + return entityDistance < blockDistance; + } + } + } + + private void updateProjectiles( + @Nonnull Store store, @Nonnull CommandBuffer commandBuffer, @Nonnull DeployableProjectileShooterComponent shooterComponent + ) { + List> projectiles = shooterComponent.getProjectiles(); + List> projectilesForRemoval = shooterComponent.getProjectilesForRemoval(); + projectiles.removeAll(Collections.singleton(null)); + + for (Ref projectile : projectiles) { + this.updateProjectile(projectile, shooterComponent, store, commandBuffer); + } + + for (Ref projectile : projectilesForRemoval) { + if (projectile.isValid()) { + commandBuffer.removeEntity(projectile, RemoveReason.REMOVE); + } + + projectiles.remove(projectile); + } + + projectilesForRemoval.clear(); + } + + private void updateProjectile( + @Nonnull Ref projectileRef, + @Nonnull DeployableProjectileShooterComponent shooterComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + if (!projectileRef.isValid()) { + shooterComponent.getProjectilesForRemoval().add(projectileRef); + } else { + TransformComponent projTransformComponent = store.getComponent(projectileRef, TransformComponent.getComponentType()); + + assert projTransformComponent != null; + + Vector3d projPos = projTransformComponent.getPosition(); + AtomicReference hit = new AtomicReference<>(Boolean.FALSE); + DeployableProjectileComponent dProjComponent = store.getComponent(projectileRef, DeployableProjectileComponent.getComponentType()); + + assert dProjComponent != null; + + Vector3d prevPos = dProjComponent.getPreviousTickPosition(); + Vector3d increment = new Vector3d((projPos.x - prevPos.x) * 0.1F, (projPos.y - prevPos.y) * 0.1F, (projPos.z - prevPos.z) * 0.1F); + + for (int j = 0; j < 10; j++) { + if (!hit.get()) { + Vector3d scanPos = dProjComponent.getPreviousTickPosition().clone(); + scanPos.x = scanPos.x + increment.x * j; + scanPos.y = scanPos.y + increment.y * j; + scanPos.z = scanPos.z + increment.z * j; + if (this.getDebugVisuals()) { + DebugUtils.addSphere(store.getExternalData().getWorld(), scanPos, new Vector3f(1.0F, 1.0F, 1.0F), 0.1F, 5.0F); + } + + for (Ref targetEntityRef : TargetUtil.getAllEntitiesInSphere(scanPos, 0.1, store)) { + if (hit.get()) { + return; + } + + this.projectileHit(targetEntityRef, projectileRef, shooterComponent, store, commandBuffer); + hit.set(Boolean.TRUE); + } + } + } + + dProjComponent.setPreviousTickPosition(projPos); + if (!hit.get()) { + StandardPhysicsProvider physics = store.getComponent(projectileRef, StandardPhysicsProvider.getComponentType()); + if (physics != null && physics.getState() != StandardPhysicsProvider.STATE.ACTIVE) { + shooterComponent.getProjectilesForRemoval().add(projectileRef); + } + } + } + } + + private void projectileHit( + @Nonnull Ref ref, + @Nonnull Ref projectileRef, + @Nonnull DeployableProjectileShooterComponent shooterComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Damage damageEntry = new Damage(new Damage.EntitySource(ref), DamageCause.PHYSICAL, this.projectileDamage); + DamageSystems.executeDamage(ref, commandBuffer, damageEntry); + TransformComponent projectileTransformComponent = store.getComponent(projectileRef, TransformComponent.getComponentType()); + + assert projectileTransformComponent != null; + + Vector3d projectilePosition = projectileTransformComponent.getPosition().clone(); + if (this.projectileKnockback != null) { + float projectileRotationYaw = projectileTransformComponent.getRotation().getYaw(); + store.getExternalData().getWorld().execute(() -> { + if (ref.isValid()) { + this.applyKnockback(ref, projectilePosition, projectileRotationYaw, store); + } + }); + } + + DeployablesUtils.playSoundEventsAtEntity( + ref, commandBuffer, this.projectileHitLocalSoundEventIndex, this.projectileHitWorldSoundEventIndex, projectilePosition + ); + shooterComponent.getProjectilesForRemoval().add(projectileRef); + } + + private void applyKnockback(@Nonnull Ref targetRef, @Nonnull Vector3d attackerPos, float attackerYaw, @Nonnull Store store) { + KnockbackComponent knockbackComponent = store.ensureAndGetComponent(targetRef, KnockbackComponent.getComponentType()); + TransformComponent transformComponent = store.getComponent(targetRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + knockbackComponent.setVelocity(this.projectileKnockback.calculateVector(attackerPos, attackerYaw, transformComponent.getPosition())); + knockbackComponent.setVelocityType(this.projectileKnockback.getVelocityType()); + knockbackComponent.setVelocityConfig(this.projectileKnockback.getVelocityConfig()); + knockbackComponent.setDuration(this.projectileKnockback.getDuration()); + } + + @Override + public String toString() { + return "DeployableTurretConfig{}" + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/deployables/interaction/SpawnDeployableAtHitLocationInteraction.java b/src/com/hypixel/hytale/builtin/deployables/interaction/SpawnDeployableAtHitLocationInteraction.java new file mode 100644 index 0000000..e317abf --- /dev/null +++ b/src/com/hypixel/hytale/builtin/deployables/interaction/SpawnDeployableAtHitLocationInteraction.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.builtin.deployables.interaction; + +import com.hypixel.hytale.builtin.deployables.DeployablesUtils; +import com.hypixel.hytale.builtin.deployables.config.DeployableConfig; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.protocol.InteractionChainData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class SpawnDeployableAtHitLocationInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + SpawnDeployableAtHitLocationInteraction.class, SpawnDeployableAtHitLocationInteraction::new, SimpleInstantInteraction.CODEC + ) + .append(new KeyedCodec<>("Config", DeployableConfig.CODEC), (i, s) -> i.config = s, i -> i.config) + .addValidator(Validators.nonNull()) + .add() + .build(); + private DeployableConfig config; + + public SpawnDeployableAtHitLocationInteraction() { + } + + @Override + public boolean needsRemoteSync() { + return false; + } + + @Override + protected void firstRun(@NonNullDecl InteractionType type, @NonNullDecl InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler) { + InteractionChain contextChain = context.getChain(); + + assert contextChain != null; + + InteractionChainData chainData = contextChain.getChainData(); + Vector3f hitLocation = chainData.hitLocation; + if (hitLocation != null) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Store store = commandBuffer.getStore(); + Vector3f hitNormal = chainData.hitNormal; + com.hypixel.hytale.math.vector.Vector3f hitNormalVec = new com.hypixel.hytale.math.vector.Vector3f(hitNormal.x, hitNormal.y, hitNormal.z); + DeployablesUtils.spawnDeployable( + commandBuffer, + store, + this.config, + context.getEntity(), + new com.hypixel.hytale.math.vector.Vector3f(hitLocation.x, hitLocation.y, hitLocation.z), + MathUtil.getRotationForHitNormal(hitNormalVec), + MathUtil.getNameForHitNormal(hitNormalVec) + ); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/deployables/interaction/SpawnDeployableFromRaycastInteraction.java b/src/com/hypixel/hytale/builtin/deployables/interaction/SpawnDeployableFromRaycastInteraction.java new file mode 100644 index 0000000..8491a3f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/deployables/interaction/SpawnDeployableFromRaycastInteraction.java @@ -0,0 +1,171 @@ +package com.hypixel.hytale.builtin.deployables.interaction; + +import com.hypixel.hytale.builtin.deployables.DeployablesUtils; +import com.hypixel.hytale.builtin.deployables.config.DeployableConfig; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.Object2FloatMapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.Position; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import it.unimi.dsi.fastutil.ints.Int2FloatMap.Entry; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class SpawnDeployableFromRaycastInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + SpawnDeployableFromRaycastInteraction.class, SpawnDeployableFromRaycastInteraction::new, SimpleInstantInteraction.CODEC + ) + .append(new KeyedCodec<>("Config", DeployableConfig.CODEC), (i, s) -> i.config = s, i -> i.config) + .addValidator(Validators.nonNull()) + .add() + .>append( + new KeyedCodec<>("PreviewStatConditions", new Object2FloatMapCodec<>(Codec.STRING, Object2FloatOpenHashMap::new)), + (changeStatInteraction, stringObject2DoubleMap) -> changeStatInteraction.unknownEntityStats = stringObject2DoubleMap, + changeStatInteraction -> changeStatInteraction.unknownEntityStats + ) + .addValidator(EntityStatType.VALIDATOR_CACHE.getMapKeyValidator()) + .documentation("Modifiers to apply to EntityStats.") + .add() + .appendInherited( + new KeyedCodec<>("MaxPlacementDistance", Codec.FLOAT), + (o, i) -> o.maxPlacementDistance = i, + o -> o.maxPlacementDistance, + (i, o) -> i.maxPlacementDistance = o.maxPlacementDistance + ) + .documentation("The max distance at which the player can deploy the deployable.") + .add() + .afterDecode(SpawnDeployableFromRaycastInteraction::processConfig) + .build(); + protected Object2FloatMap unknownEntityStats; + protected Int2FloatMap entityStats; + protected float maxPlacementDistance; + private DeployableConfig config; + + public SpawnDeployableFromRaycastInteraction() { + } + + private void processConfig() { + if (this.unknownEntityStats != null) { + this.entityStats = EntityStatsModule.resolveEntityStats(this.unknownEntityStats); + } + } + + private static boolean isSurface(@Nonnull Vector3f normal) { + return normal.x == 0.0F && normal.y - 1.0F < 0.01 && normal.z == 0.0F; + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @NonNullDecl + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Client; + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + Ref entityRef = context.getOwningEntity(); + Store store = entityRef.getStore(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + InteractionSyncData clientState = context.getClientState(); + + assert clientState != null; + + if (!this.canAfford(context.getEntity(), commandBuffer)) { + context.getState().state = InteractionState.Failed; + } else { + Position raycastHit = clientState.raycastHit; + if (raycastHit == null) { + TransformComponent transformComponent = store.getComponent(entityRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + raycastHit = new Position((float)position.x, (float)position.y, (float)position.z); + } + + com.hypixel.hytale.protocol.Vector3f raycastNormal = clientState.raycastNormal; + float correctedRaycastDistance = clientState.raycastDistance; + com.hypixel.hytale.protocol.Vector3f spawnPosition = new com.hypixel.hytale.protocol.Vector3f( + (float)raycastHit.x, (float)raycastHit.y, (float)raycastHit.z + ); + Vector3f norm = new Vector3f(raycastNormal.x, raycastNormal.y, raycastNormal.z); + if (correctedRaycastDistance > 0.0F + && correctedRaycastDistance <= this.maxPlacementDistance + && (this.config.getAllowPlaceOnWalls() || isSurface(norm))) { + Direction attackerRot = clientState.attackerRot; + Vector3f rot = new Vector3f(0.0F, attackerRot.yaw, 0.0F); + DeployablesUtils.spawnDeployable( + commandBuffer, store, this.config, entityRef, new Vector3f(spawnPosition.x, spawnPosition.y, spawnPosition.z), rot, "UP" + ); + } + } + } + + protected boolean canAfford(@Nonnull Ref entityRef, @Nonnull ComponentAccessor componentAccessor) { + if (this.entityStats != null && !this.entityStats.isEmpty()) { + EntityStatMap entityStatMapComponent = componentAccessor.getComponent(entityRef, EntityStatMap.getComponentType()); + if (entityStatMapComponent == null) { + return false; + } else { + for (Entry cost : this.entityStats.int2FloatEntrySet()) { + EntityStatValue stat = entityStatMapComponent.get(cost.getIntKey()); + if (stat == null || stat.get() < cost.getFloatValue()) { + return false; + } + } + + return true; + } + } else { + return true; + } + } + + @NonNullDecl + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.SpawnDeployableFromRaycastInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.SpawnDeployableFromRaycastInteraction p = (com.hypixel.hytale.protocol.SpawnDeployableFromRaycastInteraction)packet; + p.deployableConfig = this.config.toPacket(); + p.maxDistance = this.maxPlacementDistance; + p.costs = this.entityStats; + } +} diff --git a/src/com/hypixel/hytale/builtin/deployables/system/DeployablesSystem.java b/src/com/hypixel/hytale/builtin/deployables/system/DeployablesSystem.java new file mode 100644 index 0000000..aa1d8e4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/deployables/system/DeployablesSystem.java @@ -0,0 +1,172 @@ +package com.hypixel.hytale.builtin.deployables.system; + +import com.hypixel.hytale.builtin.deployables.component.DeployableComponent; +import com.hypixel.hytale.builtin.deployables.component.DeployableOwnerComponent; +import com.hypixel.hytale.builtin.deployables.config.DeployableConfig; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelParticle; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue; +import com.hypixel.hytale.server.core.modules.entitystats.asset.DefaultEntityStatTypes; +import com.hypixel.hytale.server.core.universe.world.ParticleUtil; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import javax.annotation.Nonnull; + +public class DeployablesSystem { + public DeployablesSystem() { + } + + private static void spawnParticleEffect(Ref sourceRef, CommandBuffer commandBuffer, Vector3d position, ModelParticle particle) { + Vector3f particlePositionOffset = particle.getPositionOffset(); + Direction particleRotationOffset = particle.getRotationOffset(); + Vector3d particlePosition = new Vector3d(position.x, position.y, position.z); + Vector3f particleRotation = new Vector3f(0.0F, 0.0F, 0.0F); + if (particlePositionOffset != null) { + particlePosition.add(particlePositionOffset.x, particlePositionOffset.y, particlePositionOffset.z); + } + + if (particleRotationOffset != null) { + particleRotation = new Vector3f(particleRotationOffset.yaw, particleRotationOffset.pitch, particleRotationOffset.roll); + } + + SpatialResource, EntityStore> playerSpatialResource = commandBuffer.getResource(EntityModule.get().getPlayerSpatialResourceType()); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(particlePosition, 75.0, results); + ParticleUtil.spawnParticleEffect( + particle.getSystemId(), + particlePosition.x, + particlePosition.y, + particlePosition.z, + particleRotation.x, + particleRotation.y, + particleRotation.z, + sourceRef, + results, + commandBuffer + ); + } + + public static class DeployableOwnerTicker extends EntityTickingSystem { + public DeployableOwnerTicker() { + } + + @Override + public Query getQuery() { + return Query.and(DeployableOwnerComponent.getComponentType()); + } + + @Override + public void tick(float dt, int index, ArchetypeChunk archetypeChunk, Store store, CommandBuffer commandBuffer) { + DeployableOwnerComponent deployableOwnerComponent = archetypeChunk.getComponent(index, DeployableOwnerComponent.getComponentType()); + deployableOwnerComponent.tick(commandBuffer); + } + } + + public static class DeployableRegisterer extends RefSystem { + public DeployableRegisterer() { + } + + private static void deregisterOwner( + @Nonnull Ref ref, @Nonnull DeployableComponent deployableComponent, @Nonnull DeployableConfig deployableConfig + ) { + Ref ownerRef = deployableComponent.getOwner(); + if (ownerRef != null && ownerRef.isValid()) { + DeployableOwnerComponent deployableOwnerComponent = ownerRef.getStore().getComponent(ownerRef, DeployableOwnerComponent.getComponentType()); + deployableOwnerComponent.deRegisterDeployable(deployableConfig.getId(), ref); + } + } + + @Override + public Query getQuery() { + return Query.and(DeployableComponent.getComponentType()); + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + DeployableComponent deployableComponent = store.getComponent(ref, DeployableComponent.getComponentType()); + DeployableConfig deployableConfig = deployableComponent.getConfig(); + Vector3d position = store.getComponent(ref, TransformComponent.getComponentType()).getPosition(); + Ref ownerRef = deployableComponent.getOwner(); + int soundIndex = deployableConfig.getDeploySoundEventIndex(); + SoundUtil.playSoundEvent3d(null, soundIndex, position, commandBuffer); + ModelParticle[] particles = deployableConfig.getSpawnParticles(); + if (particles != null) { + for (ModelParticle particle : particles) { + DeployablesSystem.spawnParticleEffect(ref, commandBuffer, position, particle); + } + } + + if (ownerRef.isValid()) { + DeployableOwnerComponent deployableOwnerComponent = ownerRef.getStore().getComponent(ownerRef, DeployableOwnerComponent.getComponentType()); + + assert deployableOwnerComponent != null; + + deployableOwnerComponent.registerDeployable(ownerRef, deployableComponent, deployableConfig.getId(), ref, store); + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + DeployableComponent deployableComponent = store.getComponent(ref, DeployableComponent.getComponentType()); + DeployableConfig deployableConfig = deployableComponent.getConfig(); + Vector3d position = store.getComponent(ref, TransformComponent.getComponentType()).getPosition(); + int despawnSoundIndex = deployableConfig.getDespawnSoundEventIndex(); + int dieSoundIndex = deployableConfig.getDieSoundEventIndex(); + if (dieSoundIndex != 0) { + EntityStatMap statMap = store.getComponent(ref, EntityStatMap.getComponentType()); + if (statMap != null) { + EntityStatValue healthStat = statMap.get(DefaultEntityStatTypes.getHealth()); + int removeSound = healthStat != null && healthStat.get() <= 0.0F ? dieSoundIndex : despawnSoundIndex; + SoundUtil.playSoundEvent3d(null, removeSound, position, commandBuffer); + } + } else { + SoundUtil.playSoundEvent3d(null, despawnSoundIndex, position, commandBuffer); + } + + ModelParticle[] particles = deployableConfig.getDespawnParticles(); + if (particles != null) { + for (ModelParticle particle : particles) { + DeployablesSystem.spawnParticleEffect(ref, commandBuffer, position, particle); + } + } + + deregisterOwner(ref, deployableComponent, deployableConfig); + } + } + + public static class DeployableTicker extends EntityTickingSystem { + public DeployableTicker() { + } + + @Override + public Query getQuery() { + return Query.and(DeployableComponent.getComponentType()); + } + + @Override + public void tick(float dt, int index, ArchetypeChunk archetypeChunk, Store store, CommandBuffer commandBuffer) { + DeployableComponent comp = archetypeChunk.getComponent(index, DeployableComponent.getComponentType()); + comp.tick(dt, index, archetypeChunk, store, commandBuffer); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/fluid/FluidCommand.java b/src/com/hypixel/hytale/builtin/fluid/FluidCommand.java new file mode 100644 index 0000000..0d00c3d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/fluid/FluidCommand.java @@ -0,0 +1,251 @@ +package com.hypixel.hytale.builtin.fluid; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.AssetArgumentType; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeIntPosition; +import com.hypixel.hytale.server.core.command.system.arguments.types.SingleArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import javax.annotation.Nonnull; + +public class FluidCommand extends AbstractCommandCollection { + private static final SingleArgumentType FLUID_ARG = new AssetArgumentType("Fluid", Fluid.class, ""); + + public FluidCommand() { + super("fluid", "Fluid debug commands"); + this.addSubCommand(new FluidCommand.SetCommand()); + this.addSubCommand(new FluidCommand.GetCommand()); + this.addSubCommand(new FluidCommand.SetRadiusCommand()); + } + + public static class GetCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_LOOKING_AT_BLOCK = Message.translation("server.commands.errors.playerNotLookingAtBlock"); + @Nonnull + private static final Message MESSAGE_COMMANDS_NO_SECTION_COMPONENT = Message.translation("server.commands.noSectionComponent"); + @Nonnull + private final OptionalArg targetOffset = this.withOptionalArg("offset", "", ArgTypes.RELATIVE_BLOCK_POSITION); + + public GetCommand() { + super("get", "Gets the fluid at the target position"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + RelativeIntPosition offset = this.targetOffset.get(context); + Vector3i blockTarget = TargetUtil.getTargetBlock(ref, 8.0, store); + if (blockTarget == null) { + playerRef.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_LOOKING_AT_BLOCK); + } else { + ChunkStore chunkStore = world.getChunkStore(); + Vector3i pos = offset == null ? blockTarget : offset.getBlockPosition(blockTarget.toVector3d(), chunkStore); + chunkStore.getChunkSectionReferenceAsync(ChunkUtil.chunkCoordinate(pos.x), ChunkUtil.chunkCoordinate(pos.y), ChunkUtil.chunkCoordinate(pos.z)) + .thenAcceptAsync( + section -> { + Store sectionStore = section.getStore(); + FluidSection fluidSection = sectionStore.getComponent((Ref)section, FluidSection.getComponentType()); + if (fluidSection == null) { + playerRef.sendMessage(MESSAGE_COMMANDS_NO_SECTION_COMPONENT); + } else { + int index = ChunkUtil.indexBlock(pos.x, pos.y, pos.z); + Fluid fluid = fluidSection.getFluid(index); + byte level = fluidSection.getFluidLevel(index); + playerRef.sendMessage( + Message.translation("server.commands.get.success") + .param("x", pos.x) + .param("y", pos.y) + .param("z", pos.z) + .param("id", fluid.getId()) + .param("level", (int)level) + ); + } + }, + world + ); + } + } + } + + public static class SetCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_LOOKING_AT_BLOCK = Message.translation("server.commands.errors.playerNotLookingAtBlock"); + @Nonnull + private static final Message MESSAGE_COMMANDS_SET_UNKNOWN_FLUID = Message.translation("server.commands.set.unknownFluid"); + @Nonnull + private static final Message MESSAGE_COMMANDS_NO_SECTION_COMPONENT = Message.translation("server.commands.noSectionComponent"); + @Nonnull + private final RequiredArg fluid = this.withRequiredArg("fluid", "", FluidCommand.FLUID_ARG); + @Nonnull + private final RequiredArg level = this.withRequiredArg("level", "", ArgTypes.INTEGER); + @Nonnull + private final OptionalArg targetOffset = this.withOptionalArg("offset", "", ArgTypes.RELATIVE_BLOCK_POSITION); + + public SetCommand() { + super("set", "Changes the fluid at the target position"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + RelativeIntPosition offset = this.targetOffset.get(context); + Vector3i blockTarget = TargetUtil.getTargetBlock(ref, 8.0, store); + if (blockTarget == null) { + playerRef.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_LOOKING_AT_BLOCK); + } else { + ChunkStore chunkStore = world.getChunkStore(); + Vector3i pos = offset == null ? blockTarget : offset.getBlockPosition(blockTarget.toVector3d(), chunkStore); + Fluid fluid = this.fluid.get(context); + if (fluid == null) { + playerRef.sendMessage(MESSAGE_COMMANDS_SET_UNKNOWN_FLUID); + } else { + Integer level = this.level.get(context); + if (level > fluid.getMaxFluidLevel()) { + level = fluid.getMaxFluidLevel(); + playerRef.sendMessage(Message.translation("server.commands.set.maxFluidLevelClamped").param("level", fluid.getMaxFluidLevel())); + } + + Integer finalLevel = level; + chunkStore.getChunkSectionReferenceAsync(ChunkUtil.chunkCoordinate(pos.x), ChunkUtil.chunkCoordinate(pos.y), ChunkUtil.chunkCoordinate(pos.z)) + .thenAcceptAsync( + section -> { + Store sectionStore = section.getStore(); + FluidSection fluidSection = sectionStore.getComponent((Ref)section, FluidSection.getComponentType()); + if (fluidSection == null) { + playerRef.sendMessage(MESSAGE_COMMANDS_NO_SECTION_COMPONENT); + } else { + int index = ChunkUtil.indexBlock(pos.x, pos.y, pos.z); + fluidSection.setFluid(index, fluid, finalLevel.byteValue()); + playerRef.sendMessage( + Message.translation("server.commands.set.success") + .param("x", pos.x) + .param("y", pos.y) + .param("z", pos.z) + .param("id", fluid.getId()) + .param("level", finalLevel) + ); + ChunkSection chunkSection = sectionStore.getComponent((Ref)section, ChunkSection.getComponentType()); + WorldChunk worldChunk = sectionStore.getComponent(chunkSection.getChunkColumnReference(), WorldChunk.getComponentType()); + worldChunk.markNeedsSaving(); + worldChunk.setTicking(pos.x, pos.y, pos.z, true); + } + }, + world + ); + } + } + } + } + + public static class SetRadiusCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_SET_UNKNOWN_FLUID = Message.translation("server.commands.set.unknownFluid"); + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_LOOKING_AT_BLOCK = Message.translation("server.commands.errors.playerNotLookingAtBlock"); + @Nonnull + private final RequiredArg radius = this.withRequiredArg("radius", "", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg fluid = this.withRequiredArg("fluid", "", FluidCommand.FLUID_ARG); + @Nonnull + private final RequiredArg level = this.withRequiredArg("level", "", ArgTypes.INTEGER); + @Nonnull + private final OptionalArg targetOffset = this.withOptionalArg("offset", "", ArgTypes.RELATIVE_BLOCK_POSITION); + + public SetRadiusCommand() { + super("setradius", "Changes the fluid at the player position in a given radius"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + RelativeIntPosition offset = this.targetOffset.get(context); + Vector3i blockTarget = TargetUtil.getTargetBlock(ref, 8.0, store); + if (blockTarget == null) { + playerRef.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_LOOKING_AT_BLOCK); + } else { + ChunkStore chunkStore = world.getChunkStore(); + Vector3i pos = offset == null ? blockTarget : offset.getBlockPosition(blockTarget.toVector3d(), chunkStore); + Fluid fluid = this.fluid.get(context); + if (fluid == null) { + playerRef.sendMessage(MESSAGE_COMMANDS_SET_UNKNOWN_FLUID); + } else { + Integer level = this.level.get(context); + if (level > fluid.getMaxFluidLevel()) { + level = fluid.getMaxFluidLevel(); + playerRef.sendMessage(Message.translation("server.commands.set.maxFluidLevelClamped").param("level", fluid.getMaxFluidLevel())); + } + + Integer radius = this.radius.get(context); + int minX = pos.x - radius; + int maxX = pos.x + radius; + int minY = pos.y - radius; + int maxY = pos.y + radius; + int minZ = pos.z - radius; + int maxZ = pos.z + radius; + int minCX = ChunkUtil.chunkCoordinate(minX); + int maxCX = ChunkUtil.chunkCoordinate(maxX); + int minCY = ChunkUtil.chunkCoordinate(minY); + int maxCY = ChunkUtil.chunkCoordinate(maxY); + int minCZ = ChunkUtil.chunkCoordinate(minZ); + int maxCZ = ChunkUtil.chunkCoordinate(maxZ); + Integer finalLevel = level; + + for (int cx = minCX; cx <= maxCX; cx++) { + for (int cz = minCZ; cz <= maxCZ; cz++) { + int relMinX = MathUtil.clamp(minX - ChunkUtil.minBlock(cx), 0, 32); + int relMaxX = MathUtil.clamp(maxX - ChunkUtil.minBlock(cx), 0, 32); + int relMinZ = MathUtil.clamp(minZ - ChunkUtil.minBlock(cz), 0, 32); + int relMaxZ = MathUtil.clamp(maxZ - ChunkUtil.minBlock(cz), 0, 32); + + for (int cy = minCY; cy <= maxCY; cy++) { + chunkStore.getChunkSectionReferenceAsync(cx, cy, cz).thenAcceptAsync(section -> { + Store sectionStore = section.getStore(); + FluidSection fluidSection = sectionStore.getComponent((Ref)section, FluidSection.getComponentType()); + if (fluidSection != null) { + int relMinY = MathUtil.clamp(minY - ChunkUtil.minBlock(fluidSection.getY()), 0, 32); + int relMaxY = MathUtil.clamp(maxY - ChunkUtil.minBlock(fluidSection.getY()), 0, 32); + ChunkSection sectionComp = sectionStore.getComponent((Ref)section, ChunkSection.getComponentType()); + WorldChunk worldChunk = sectionStore.getComponent(sectionComp.getChunkColumnReference(), WorldChunk.getComponentType()); + + for (int y = relMinY; y < relMaxY; y++) { + for (int z = relMinZ; z < relMaxZ; z++) { + for (int x = relMinX; x < relMaxX; x++) { + int index = ChunkUtil.indexBlock(x, y, z); + fluidSection.setFluid(index, fluid, finalLevel.byteValue()); + worldChunk.setTicking(pos.x, pos.y, pos.z, true); + } + } + } + + worldChunk.markNeedsSaving(); + } + }, world); + } + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/fluid/FluidPlugin.java b/src/com/hypixel/hytale/builtin/fluid/FluidPlugin.java new file mode 100644 index 0000000..b104016 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/fluid/FluidPlugin.java @@ -0,0 +1,217 @@ +package com.hypixel.hytale.builtin.fluid; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.codec.lookup.Priority; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.event.EventPriority; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.fluid.DefaultFluidTicker; +import com.hypixel.hytale.server.core.asset.type.fluid.FiniteFluidTicker; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.asset.type.fluid.FluidTicker; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.events.ChunkPreLoadProcessEvent; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.time.Instant; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FluidPlugin extends JavaPlugin { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static FluidPlugin instance; + + public static FluidPlugin get() { + return instance; + } + + public FluidPlugin(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + FluidTicker.CODEC.register(Priority.DEFAULT, "Default", DefaultFluidTicker.class, DefaultFluidTicker.CODEC); + FluidTicker.CODEC.register("Finite", FiniteFluidTicker.class, FiniteFluidTicker.CODEC); + this.getChunkStoreRegistry().registerSystem(new FluidSystems.EnsureFluidSection()); + this.getChunkStoreRegistry().registerSystem(new FluidSystems.MigrateFromColumn()); + this.getChunkStoreRegistry().registerSystem(new FluidSystems.SetupSection()); + this.getChunkStoreRegistry().registerSystem(new FluidSystems.LoadPacketGenerator()); + this.getChunkStoreRegistry().registerSystem(new FluidSystems.ReplicateChanges()); + this.getChunkStoreRegistry().registerSystem(new FluidSystems.Ticking()); + this.getEventRegistry().registerGlobal(EventPriority.FIRST, ChunkPreLoadProcessEvent.class, FluidPlugin::onChunkPreProcess); + this.getCommandRegistry().registerCommand(new FluidCommand()); + } + + private static void onChunkPreProcess(@Nonnull ChunkPreLoadProcessEvent event) { + if (event.isNewlyGenerated()) { + WorldChunk wc = event.getChunk(); + Holder holder = event.getHolder(); + ChunkColumn column = holder.getComponent(ChunkColumn.getComponentType()); + if (column != null) { + BlockChunk blockChunk = holder.getComponent(BlockChunk.getComponentType()); + if (blockChunk != null) { + IndexedLookupTableAssetMap fluidMap = Fluid.getAssetMap(); + BlockTypeAssetMap blockMap = BlockType.getAssetMap(); + Holder[] sections = column.getSectionHolders(); + if (sections != null) { + for (int i = 0; i < sections.length && i < 10; i++) { + Holder section = sections[i]; + FluidSection fluid = section.getComponent(FluidSection.getComponentType()); + if (fluid != null && !fluid.isEmpty()) { + BlockSection blockSection = section.ensureAndGetComponent(BlockSection.getComponentType()); + + for (int idx = 0; idx < 32768; idx++) { + int fluidId = fluid.getFluidId(idx); + if (fluidId != 0) { + Fluid fluidType = fluidMap.getAsset(fluidId); + if (fluidType == null) { + LOGGER.at(Level.WARNING) + .log("Invalid fluid found in chunk section: %d, %d %d with id %d", fluid.getX(), fluid.getY(), fluid.getZ(), fluid); + fluid.setFluid(idx, 0, (byte)0); + } else { + FluidTicker ticker = fluidType.getTicker(); + if (FluidTicker.isSolid(blockMap.getAsset(blockSection.get(idx)))) { + fluid.setFluid(idx, 0, (byte)0); + } else { + if (!ticker.canDemote()) { + int x = ChunkUtil.minBlock(fluid.getX()) + ChunkUtil.xFromIndex(idx); + int y = ChunkUtil.minBlock(fluid.getY()) + ChunkUtil.yFromIndex(idx); + int z = ChunkUtil.minBlock(fluid.getZ()) + ChunkUtil.zFromIndex(idx); + boolean canSpread = ChunkUtil.isBorderBlock(x, z) + || fluid.getFluidId(x - 1, y, z) == 0 && !FluidTicker.isSolid(blockMap.getAsset(blockSection.get(x - 1, y, z))) + || fluid.getFluidId(x + 1, y, z) == 0 && !FluidTicker.isSolid(blockMap.getAsset(blockSection.get(x + 1, y, z))) + || fluid.getFluidId(x, y, z - 1) == 0 && !FluidTicker.isSolid(blockMap.getAsset(blockSection.get(x, y, z - 1))) + || fluid.getFluidId(x, y, z + 1) == 0 && !FluidTicker.isSolid(blockMap.getAsset(blockSection.get(x, y, z + 1))); + if (y > 0) { + if (ChunkUtil.chunkCoordinate(y) == ChunkUtil.chunkCoordinate(y - 1)) { + canSpread |= fluid.getFluidId(x, y - 1, z) == 0 + && !FluidTicker.isSolid(blockMap.getAsset(blockSection.get(x, y - 1, z))); + } else { + FluidSection fluidSection2 = sections[i - 1].getComponent(FluidSection.getComponentType()); + canSpread |= fluidSection2.getFluidId(x, y - 1, z) == 0 + && !FluidTicker.isSolid(blockMap.getAsset(blockChunk.getBlock(x, y - 1, z))); + } + } + + if (!canSpread) { + blockSection.setTicking(idx, false); + continue; + } + } + + blockSection.setTicking(idx, true); + } + } + } + } + } + } + + int tickingBlocks = blockChunk.getTickingBlocksCount(); + if (tickingBlocks != 0) { + FluidPlugin.PreprocesorAccessor accessor = new FluidPlugin.PreprocesorAccessor(wc, blockChunk, sections); + + do { + blockChunk.preTick(Instant.MIN); + + for (int ix = 0; ix < sections.length; ix++) { + Holder section = sections[ix]; + FluidSection fluidSection = section.getComponent(FluidSection.getComponentType()); + if (fluidSection != null && !fluidSection.isEmpty()) { + BlockSection blockSection = section.ensureAndGetComponent(BlockSection.getComponentType()); + fluidSection.preload(wc.getX(), ix, wc.getZ()); + accessor.blockSection = blockSection; + blockSection.forEachTicking( + accessor, + fluidSection, + ix, + (preprocesorAccessor, fluidSection1, xx, yx, zx, block) -> { + int fluidId = fluidSection1.getFluidId(xx, yx, zx); + if (fluidId == 0) { + return BlockTickStrategy.IGNORED; + } else { + Fluid fluid = Fluid.getAssetMap().getAsset(fluidId); + int blockX = fluidSection1.getX() << 5 | xx; + int blockZ = fluidSection1.getZ() << 5 | zx; + return fluid.getTicker() + .process( + preprocesorAccessor.worldChunk.getWorld(), + preprocesorAccessor.tick, + preprocesorAccessor, + fluidSection1, + accessor.blockSection, + fluid, + fluidId, + blockX, + yx, + blockZ + ); + } + } + ); + } + } + + tickingBlocks = blockChunk.getTickingBlocksCount(); + accessor.tick++; + } while (tickingBlocks != 0 && accessor.tick <= 100L); + + blockChunk.mergeTickingBlocks(); + } + } + } + } + } + } + + public static class PreprocesorAccessor implements FluidTicker.Accessor { + private final WorldChunk worldChunk; + private final BlockChunk blockChunk; + private final Holder[] sections; + public long tick; + public BlockSection blockSection; + + public PreprocesorAccessor(WorldChunk worldChunk, BlockChunk blockChunk, Holder[] sections) { + this.worldChunk = worldChunk; + this.blockChunk = blockChunk; + this.sections = sections; + } + + @Nullable + @Override + public FluidSection getFluidSection(int cx, int cy, int cz) { + return this.blockChunk.getX() == cx && this.blockChunk.getZ() == cz && cy >= 0 && cy < this.sections.length + ? this.sections[cy].getComponent(FluidSection.getComponentType()) + : null; + } + + @Nullable + @Override + public BlockSection getBlockSection(int cx, int cy, int cz) { + if (cy >= 0 && cy < 10) { + return this.blockChunk.getX() == cx && this.blockChunk.getZ() == cz ? this.blockChunk.getSectionAtIndex(cy) : null; + } else { + return null; + } + } + + @Override + public void setBlock(int x, int y, int z, int blockId) { + if (this.worldChunk.getX() == ChunkUtil.chunkCoordinate(x) || this.worldChunk.getZ() == ChunkUtil.chunkCoordinate(z)) { + this.worldChunk.setBlock(x, y, z, blockId, 157); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/fluid/FluidState.java b/src/com/hypixel/hytale/builtin/fluid/FluidState.java new file mode 100644 index 0000000..8bc2fb6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/fluid/FluidState.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.fluid; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public record FluidState(int fluidLevel, byte verticalFill) { + public static int SOURCE_LEVEL = 0; + public static final int FULL_LEVEL = 8; + public static final FluidState[] FLUID_STATES = generateFluidStates(8); + + public FluidState(int fluidLevel, int verticalFill) { + this(fluidLevel, (byte)verticalFill); + } + + @Nonnull + public static FluidState[] generateFluidStates(int maxLevel) { + List fluidStateList = new ObjectArrayList<>(); + fluidStateList.add(new FluidState(SOURCE_LEVEL, maxLevel)); + + for (int i = 1; i <= maxLevel; i++) { + fluidStateList.add(new FluidState(i, i)); + } + + return fluidStateList.toArray(FluidState[]::new); + } + + @Nonnull + @Override + public String toString() { + return "FluidState{fluidLevel=" + this.fluidLevel + ", verticalFill=" + this.verticalFill + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/fluid/FluidSystems.java b/src/com/hypixel/hytale/builtin/fluid/FluidSystems.java new file mode 100644 index 0000000..11a4a77 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/fluid/FluidSystems.java @@ -0,0 +1,413 @@ +package com.hypixel.hytale.builtin.fluid; + +import com.hypixel.hytale.builtin.blocktick.system.ChunkBlockTickSystem; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.RootDependency; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.component.system.tick.RunWhenPausedSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.packets.world.ServerSetFluid; +import com.hypixel.hytale.protocol.packets.world.ServerSetFluids; +import com.hypixel.hytale.protocol.packets.world.SetFluidCmd; +import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.asset.type.fluid.FluidTicker; +import com.hypixel.hytale.server.core.modules.LegacyModule; +import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; +import com.hypixel.hytale.server.core.modules.migrations.ChunkColumnMigrationSystem; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.IntIterator; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class FluidSystems { + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final int MAX_CHANGES_PER_PACKET = 1024; + + public FluidSystems() { + } + + public static class EnsureFluidSection extends HolderSystem { + @Nonnull + private static final Query QUERY = Query.and(ChunkSection.getComponentType(), Query.not(FluidSection.getComponentType())); + + public EnsureFluidSection() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.addComponent(FluidSection.getComponentType(), new FluidSection()); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.firstSet(); + } + } + + public static class LoadPacketGenerator extends ChunkStore.LoadFuturePacketDataQuerySystem { + public LoadPacketGenerator() { + } + + public void fetch( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + Store store, + @Nonnull CommandBuffer commandBuffer, + PlayerRef query, + @Nonnull List> results + ) { + ChunkColumn chunkColumnComponent = archetypeChunk.getComponent(index, ChunkColumn.getComponentType()); + + assert chunkColumnComponent != null; + + for (Ref sectionRef : chunkColumnComponent.getSections()) { + FluidSection fluidSectionComponent = commandBuffer.getComponent(sectionRef, FluidSection.getComponentType()); + if (fluidSectionComponent != null) { + results.add(fluidSectionComponent.getCachedPacket().exceptionally(throwable -> { + if (throwable != null) { + FluidSystems.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception when compressing chunk fluids:"); + } + + return null; + }).thenApply(Function.identity())); + } + } + } + + @Override + public Query getQuery() { + return ChunkColumn.getComponentType(); + } + } + + public static class MigrateFromColumn extends ChunkColumnMigrationSystem { + @Nonnull + private final Query QUERY = Query.and(ChunkColumn.getComponentType(), BlockChunk.getComponentType()); + @Nonnull + private final Set> DEPENDENCIES = Set.of(new SystemDependency<>(Order.BEFORE, LegacyModule.MigrateLegacySections.class)); + + public MigrateFromColumn() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + ChunkColumn chunkColumnComponent = holder.getComponent(ChunkColumn.getComponentType()); + + assert chunkColumnComponent != null; + + BlockChunk blockChunkComponent = holder.getComponent(BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + Holder[] sections = chunkColumnComponent.getSectionHolders(); + BlockSection[] legacySections = blockChunkComponent.getMigratedSections(); + if (legacySections != null) { + for (int i = 0; i < sections.length; i++) { + Holder section = sections[i]; + BlockSection paletteSection = legacySections[i]; + if (section != null && paletteSection != null) { + FluidSection fluid = paletteSection.takeMigratedFluid(); + if (fluid != null) { + section.putComponent(FluidSection.getComponentType(), fluid); + blockChunkComponent.markNeedsSaving(); + } + } + } + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.DEPENDENCIES; + } + } + + public static class ReplicateChanges extends EntityTickingSystem implements RunWhenPausedSystem { + @Nonnull + private static final Query QUERY = Query.and(ChunkSection.getComponentType(), FluidSection.getComponentType()); + + public ReplicateChanges() { + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + FluidSection fluidSectionComponent = archetypeChunk.getComponent(index, FluidSection.getComponentType()); + + assert fluidSectionComponent != null; + + IntOpenHashSet changes = fluidSectionComponent.getAndClearChangedPositions(); + if (!changes.isEmpty()) { + ChunkSection chunkSectionComponent = archetypeChunk.getComponent(index, ChunkSection.getComponentType()); + + assert chunkSectionComponent != null; + + World world = commandBuffer.getExternalData().getWorld(); + WorldChunk worldChunkComponent = commandBuffer.getComponent(chunkSectionComponent.getChunkColumnReference(), WorldChunk.getComponentType()); + int sectionY = chunkSectionComponent.getY(); + world.execute(() -> { + if (worldChunkComponent != null && worldChunkComponent.getWorld() != null) { + worldChunkComponent.getWorld().getChunkLighting().invalidateLightInChunkSection(worldChunkComponent, sectionY); + } + }); + Collection playerRefs = store.getExternalData().getWorld().getPlayerRefs(); + if (playerRefs.isEmpty()) { + changes.clear(); + } else { + long chunkIndex = ChunkUtil.indexChunk(fluidSectionComponent.getX(), fluidSectionComponent.getZ()); + if (changes.size() >= 1024) { + ObjectArrayList playersCopy = new ObjectArrayList<>(playerRefs); + fluidSectionComponent.getCachedPacket().whenComplete((packetx, throwable) -> { + if (throwable != null) { + FluidSystems.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception when compressing chunk fluids:"); + } else { + for (PlayerRef playerRefx : playersCopy) { + Ref refx = playerRefx.getReference(); + if (refx != null && refx.isValid()) { + ChunkTracker trackerx = playerRefx.getChunkTracker(); + if (trackerx.isLoaded(chunkIndex)) { + playerRefx.getPacketHandler().writeNoCache(packetx); + } + } + } + } + }); + changes.clear(); + } else { + if (changes.size() == 1) { + int change = changes.iterator().nextInt(); + int x = ChunkUtil.minBlock(fluidSectionComponent.getX()) + ChunkUtil.xFromIndex(change); + int y = ChunkUtil.minBlock(fluidSectionComponent.getY()) + ChunkUtil.yFromIndex(change); + int z = ChunkUtil.minBlock(fluidSectionComponent.getZ()) + ChunkUtil.zFromIndex(change); + int fluid = fluidSectionComponent.getFluidId(change); + byte level = fluidSectionComponent.getFluidLevel(change); + ServerSetFluid packet = new ServerSetFluid(x, y, z, fluid, level); + + for (PlayerRef playerRef : playerRefs) { + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + ChunkTracker tracker = playerRef.getChunkTracker(); + if (tracker.isLoaded(chunkIndex)) { + playerRef.getPacketHandler().writeNoCache(packet); + } + } + } + } else { + SetFluidCmd[] cmds = new SetFluidCmd[changes.size()]; + IntIterator iter = changes.intIterator(); + int i = 0; + + while (iter.hasNext()) { + int change = iter.nextInt(); + int fluid = fluidSectionComponent.getFluidId(change); + byte level = fluidSectionComponent.getFluidLevel(change); + cmds[i++] = new SetFluidCmd((short)change, fluid, level); + } + + ServerSetFluids packet = new ServerSetFluids( + fluidSectionComponent.getX(), fluidSectionComponent.getY(), fluidSectionComponent.getZ(), cmds + ); + + for (PlayerRef playerRefx : playerRefs) { + Ref ref = playerRefx.getReference(); + if (ref != null && ref.isValid()) { + ChunkTracker tracker = playerRefx.getChunkTracker(); + if (tracker.isLoaded(chunkIndex)) { + playerRefx.getPacketHandler().writeNoCache(packet); + } + } + } + } + + changes.clear(); + } + } + } + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.lastSet(); + } + } + + public static class SetupSection extends HolderSystem { + @Nonnull + private static final Query QUERY = Query.and(ChunkSection.getComponentType(), FluidSection.getComponentType()); + @Nonnull + private static final Set> DEPENDENCIES = Set.of(new SystemDependency<>(Order.AFTER, FluidSystems.MigrateFromColumn.class)); + + public SetupSection() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + ChunkSection chunkSectionComponent = holder.getComponent(ChunkSection.getComponentType()); + + assert chunkSectionComponent != null; + + FluidSection fluidSectionComponent = holder.getComponent(FluidSection.getComponentType()); + + assert fluidSectionComponent != null; + + fluidSectionComponent.load(chunkSectionComponent.getX(), chunkSectionComponent.getY(), chunkSectionComponent.getZ()); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + } + + public static class Ticking extends EntityTickingSystem { + @Nonnull + private static final Query QUERY = Query.and(FluidSection.getComponentType(), ChunkSection.getComponentType()); + @Nonnull + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, ChunkBlockTickSystem.PreTick.class), new SystemDependency<>(Order.BEFORE, ChunkBlockTickSystem.Ticking.class) + ); + + public Ticking() { + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.useParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + ChunkSection chunkSectionComponent = archetypeChunk.getComponent(index, ChunkSection.getComponentType()); + + assert chunkSectionComponent != null; + + FluidSection fluidSectionComponent = archetypeChunk.getComponent(index, FluidSection.getComponentType()); + + assert fluidSectionComponent != null; + + Ref chunkRef = chunkSectionComponent.getChunkColumnReference(); + BlockChunk blockChunkComponent = commandBuffer.getComponent(chunkRef, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + BlockSection blockSection = blockChunkComponent.getSectionAtIndex(fluidSectionComponent.getY()); + if (blockSection != null) { + if (blockSection.getTickingBlocksCountCopy() != 0) { + FluidTicker.CachedAccessor accessor = FluidTicker.CachedAccessor.of(commandBuffer, fluidSectionComponent, blockSection, 5); + blockSection.forEachTicking(accessor, commandBuffer, fluidSectionComponent.getY(), (accessor1, commandBuffer1, x, y, z, block) -> { + FluidSection fluidSection1 = accessor1.selfFluidSection; + BlockSection blockSection1 = accessor1.selfBlockSection; + int fluidId = fluidSection1.getFluidId(x, y, z); + if (fluidId == 0) { + return BlockTickStrategy.IGNORED; + } else { + Fluid fluid = Fluid.getAssetMap().getAsset(fluidId); + int blockX = fluidSection1.getX() << 5 | x; + int blockZ = fluidSection1.getZ() << 5 | z; + return fluid.getTicker().tick(commandBuffer1, accessor1, fluidSection1, blockSection1, fluid, fluidId, blockX, y, blockZ); + } + }); + } + } + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/ArrayUtil.java b/src/com/hypixel/hytale/builtin/hytalegenerator/ArrayUtil.java new file mode 100644 index 0000000..53d744f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/ArrayUtil.java @@ -0,0 +1,147 @@ +package com.hypixel.hytale.builtin.hytalegenerator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.BiFunction; +import javax.annotation.Nonnull; + +public class ArrayUtil { + public ArrayUtil() { + } + + @Nonnull + public static T[] brokenCopyOf(@Nonnull T[] a) { + T[] copy = (T[])(new Object[a.length]); + System.arraycopy(a, 0, copy, 0, a.length); + return copy; + } + + public static void copy(@Nonnull T[] source, @Nonnull T[] destination) { + if (source.length != destination.length) { + throw new IllegalArgumentException("arrays must have the same size"); + } else { + System.arraycopy(source, 0, destination, 0, source.length); + } + } + + @Nonnull + public static T[] append(@Nonnull T[] a, T e) { + T[] expanded = (T[])(new Object[a.length + 1]); + System.arraycopy(a, 0, expanded, 0, a.length); + expanded[a.length] = e; + return expanded; + } + + @Nonnull + public static List> split(@Nonnull List list, int partCount) { + if (partCount < 1) { + throw new IllegalArgumentException("parts must be greater than 0"); + } else if (partCount == 1) { + return Collections.singletonList(list); + } else { + List> out = new ArrayList<>(partCount); + int listSize = list.size(); + if (listSize <= partCount) { + for (int i = 0; i < listSize; i++) { + out.add(List.of(list.get(i))); + } + + for (int i = listSize; i < partCount; i++) { + out.add(List.of()); + } + + return out; + } else { + int[] partSizes = getPartSizes(listSize, partCount); + int elementIndex = 0; + + for (int partIndex = 0; partIndex < partCount; partIndex++) { + int partSize = partSizes[partIndex]; + List partList = new ArrayList<>(partSize); + + for (int i = 0; i < partSize; i++) { + partList.add(list.get(elementIndex++)); + } + + out.add(partList); + } + + return out; + } + } + } + + public static int[] getPartSizes(int total, int partCount) { + if (total >= 0 && partCount >= 1) { + if (total == 0) { + return new int[]{total}; + } else { + int[] sizes = new int[partCount]; + int baseSize = total / partCount; + int remainder = total % partCount; + + for (int i = 0; i < partCount; i++) { + if (i < remainder) { + sizes[i] = baseSize + 1; + } else { + sizes[i] = baseSize; + } + } + + return sizes; + } + } else { + throw new IllegalArgumentException("total and/or parts must be greater than 0"); + } + } + + public static int sortedSearch(@Nonnull List sortedList, @Nonnull G gauge, @Nonnull BiFunction comparator) { + int BINARY_SIZE_THRESHOLD = 250; + if (sortedList.isEmpty()) { + return -1; + } else if (sortedList.size() == 1) { + return comparator.apply(gauge, sortedList.getFirst()) == 0 ? 0 : -1; + } else if (sortedList.size() <= 250) { + for (int i = 0; i < sortedList.size(); i++) { + if (comparator.apply(gauge, sortedList.get(i)) == 0) { + return i; + } + } + + return -1; + } else { + return binarySearch(sortedList, gauge, comparator); + } + } + + public static int binarySearch(@Nonnull List sortedList, @Nonnull G gauge, @Nonnull BiFunction comparator) { + if (sortedList.isEmpty()) { + return -1; + } else { + int min = 0; + int max = sortedList.size(); + + while (true) { + int index = (max + min) / 2; + T item = sortedList.get(index); + int comparison = comparator.apply(gauge, item); + if (comparison == 0) { + return index; + } + + if (min == max - 1) { + return -1; + } + + if (comparison == -1) { + max = index; + } + + if (comparison == 1) { + min = index; + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/BlockMask.java b/src/com/hypixel/hytale/builtin/hytalegenerator/BlockMask.java new file mode 100644 index 0000000..ff642cd --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/BlockMask.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.hytalegenerator; + +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class BlockMask { + private MaterialSet skippedBlocks = new MaterialSet(); + private MaterialSet defaultMask = new MaterialSet(); + private final List sourceBlocks = new ArrayList<>(0); + private final List destinationBlocks = new ArrayList<>(0); + + public BlockMask() { + } + + public boolean canPlace(@Nonnull Material material) { + return !this.skippedBlocks.test(material); + } + + public boolean canPlace(int materialHash) { + return !this.skippedBlocks.test(materialHash); + } + + public boolean canReplace(Material source, Material destination) { + return this.canReplace(source.hashMaterialIds(), destination.hashMaterialIds()); + } + + public boolean canReplace(int sourceHash, int destinationHash) { + for (int i = 0; i < this.sourceBlocks.size(); i++) { + if (this.sourceBlocks.get(i).test(sourceHash)) { + return this.destinationBlocks.get(i).test(destinationHash); + } + } + + return !this.defaultMask.test(destinationHash); + } + + public void setSkippedBlocks(@Nonnull MaterialSet materialSet) { + this.skippedBlocks = materialSet; + } + + public void putBlockMaskEntry(@Nonnull MaterialSet source, @Nonnull MaterialSet destination) { + this.sourceBlocks.add(source); + this.destinationBlocks.add(destination); + } + + public void setDefaultMask(@Nonnull MaterialSet materialSet) { + this.defaultMask = materialSet; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/FutureUtils.java b/src/com/hypixel/hytale/builtin/hytalegenerator/FutureUtils.java new file mode 100644 index 0000000..e7c9894 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/FutureUtils.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.builtin.hytalegenerator; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class FutureUtils { + public FutureUtils() { + } + + public static CompletableFuture allOf(@Nonnull List> tasks) { + return CompletableFuture.allOf(tasks.toArray(new CompletableFuture[tasks.size()])); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/Indexer.java b/src/com/hypixel/hytale/builtin/hytalegenerator/Indexer.java new file mode 100644 index 0000000..fe66b45 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/Indexer.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.builtin.hytalegenerator; + +import java.util.HashMap; +import java.util.Map; + +public class Indexer { + private Map ids = new HashMap<>(); + + public Indexer() { + } + + public int getIdFor(Object o) { + return this.ids.computeIfAbsent(o, k -> this.ids.size()); + } + + public int size() { + return this.ids.size(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/LoggerUtil.java b/src/com/hypixel/hytale/builtin/hytalegenerator/LoggerUtil.java new file mode 100644 index 0000000..3fbd982 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/LoggerUtil.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.hytalegenerator; + +import com.hypixel.hytale.common.util.ExceptionUtil; +import java.util.logging.Logger; +import javax.annotation.Nonnull; + +public class LoggerUtil { + public static final String HYTALE_GENERATOR_NAME = "HytaleGenerator"; + + public LoggerUtil() { + } + + public static Logger getLogger() { + return Logger.getLogger("HytaleGenerator"); + } + + public static void logException(@Nonnull String contextDescription, @Nonnull Throwable e) { + logException(contextDescription, e, getLogger()); + } + + public static void logException(@Nonnull String contextDescription, @Nonnull Throwable e, @Nonnull Logger logger) { + String msg = "Exception occurred during "; + msg = msg + contextDescription; + msg = msg + " \n"; + msg = msg + ExceptionUtil.toStringWithStack(e); + logger.severe(msg); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/MaterialSet.java b/src/com/hypixel/hytale/builtin/hytalegenerator/MaterialSet.java new file mode 100644 index 0000000..e820d53 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/MaterialSet.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.builtin.hytalegenerator; + +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import it.unimi.dsi.fastutil.ints.IntArraySet; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.IntSets; +import java.util.List; +import java.util.function.Predicate; +import javax.annotation.Nonnull; + +public class MaterialSet implements Predicate { + private final boolean isInclusive; + private final IntSet mask; + + public MaterialSet() { + this.isInclusive = true; + this.mask = IntSet.of(); + } + + public MaterialSet(boolean isInclusive, @Nonnull List elements) { + this.isInclusive = isInclusive; + int size = elements.size(); + if (size == 0) { + this.mask = IntSet.of(); + } else if (size == 1) { + Material first = elements.getFirst(); + if (first == null) { + throw new IllegalArgumentException("element array contains null at index 0"); + } else { + this.mask = IntSet.of(first.hashMaterialIds()); + } + } else { + IntSet innerSet = (IntSet)(size <= 4 ? new IntArraySet(size) : new IntOpenHashSet(size, 0.99F)); + + for (int i = 0; i < size; i++) { + Material element = elements.get(i); + if (element == null) { + throw new IllegalArgumentException("element array contains null at index " + i); + } + + innerSet.add(element.hashMaterialIds()); + } + + this.mask = IntSets.unmodifiable(innerSet); + } + } + + public boolean test(Material value) { + if (value == null) { + return false; + } else { + boolean contains = this.mask.contains(value.hashMaterialIds()); + return contains && this.isInclusive || !contains && !this.isInclusive; + } + } + + public boolean test(int hashMaterialIds) { + boolean contains = this.mask.contains(hashMaterialIds); + return contains && this.isInclusive || !contains && !this.isInclusive; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/PropField.java b/src/com/hypixel/hytale/builtin/hytalegenerator/PropField.java new file mode 100644 index 0000000..7187493 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/PropField.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator; + +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.propdistributions.Assignments; +import javax.annotation.Nonnull; + +public class PropField { + @Nonnull + private final Assignments assignments; + @Nonnull + private final PositionProvider positionProvider; + private final int runtime; + + public PropField(int runtime, @Nonnull Assignments assignments, @Nonnull PositionProvider positionProvider) { + this.runtime = runtime; + this.assignments = assignments; + this.positionProvider = positionProvider; + } + + @Nonnull + public PositionProvider getPositionProvider() { + return this.positionProvider; + } + + @Nonnull + public Assignments getPropDistribution() { + return this.assignments; + } + + public int getRuntime() { + return this.runtime; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/VectorUtil.java b/src/com/hypixel/hytale/builtin/hytalegenerator/VectorUtil.java new file mode 100644 index 0000000..e054009 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/VectorUtil.java @@ -0,0 +1,488 @@ +package com.hypixel.hytale.builtin.hytalegenerator; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.doubles.DoubleObjectPair; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import javax.annotation.Nonnull; + +public class VectorUtil { + public VectorUtil() { + } + + public static boolean areasOverlap(@Nonnull Vector3d minA, @Nonnull Vector3d maxA, @Nonnull Vector3d minB, @Nonnull Vector3d maxB) { + return isAnyGreater(maxA, minB) && isAnySmaller(minA, maxB); + } + + public static double distanceToSegment3d(@Nonnull Vector3d point, @Nonnull Vector3d p0, @Nonnull Vector3d p1) { + Vector3d lineVec = p1.clone().addScaled(p0, -1.0); + Vector3d pointVec = point.clone().addScaled(p0, -1.0); + double lineLength = lineVec.length(); + Vector3d lineUnitVec = lineVec.clone().setLength(1.0); + Vector3d pointVecScaled = pointVec.clone().scale(1.0 / lineLength); + double t = lineUnitVec.dot(pointVecScaled); + t = Calculator.clamp(0.0, t, 1.0); + Vector3d nearestPoint = lineVec.clone().scale(t); + return nearestPoint.distanceTo(pointVec); + } + + public static double distanceToLine3d(@Nonnull Vector3d point, @Nonnull Vector3d p0, @Nonnull Vector3d p1) { + Vector3d lineVec = p1.clone().addScaled(p0, -1.0); + Vector3d pointVec = point.clone().addScaled(p0, -1.0); + double lineLength = lineVec.length(); + Vector3d lineUnitVec = lineVec.clone().setLength(1.0); + Vector3d pointVecScaled = pointVec.clone().scale(1.0 / lineLength); + double t = lineUnitVec.dot(pointVecScaled); + Vector3d nearestPoint = lineVec.clone().scale(t); + return nearestPoint.distanceTo(pointVec); + } + + @Nonnull + public static Vector3d nearestPointOnSegment3d(@Nonnull Vector3d point, @Nonnull Vector3d p0, @Nonnull Vector3d p1) { + Vector3d lineVec = p1.clone().addScaled(p0, -1.0); + Vector3d pointVec = point.clone().addScaled(p0, -1.0); + double lineLength = lineVec.length(); + Vector3d lineUnitVec = lineVec.clone().setLength(1.0); + Vector3d pointVecScaled = pointVec.clone().scale(1.0 / lineLength); + double t = lineUnitVec.dot(pointVecScaled); + t = Calculator.clamp(0.0, t, 1.0); + Vector3d nearestPoint = lineVec.clone().scale(t); + return nearestPoint.add(p0); + } + + @Nonnull + public static Vector3d nearestPointOnLine3d(@Nonnull Vector3d point, @Nonnull Vector3d p0, @Nonnull Vector3d p1) { + Vector3d lineVec = p1.clone().addScaled(p0, -1.0); + Vector3d pointVec = point.clone().addScaled(p0, -1.0); + double lineLength = lineVec.length(); + Vector3d lineUnitVec = lineVec.clone().setLength(1.0); + Vector3d pointVecScaled = pointVec.clone().scale(1.0 / lineLength); + double t = lineUnitVec.dot(pointVecScaled); + Vector3d nearestPoint = lineVec.clone().scale(t); + return nearestPoint.add(p0); + } + + public static boolean[] shortestSegmentBetweenTwoSegments( + @Nonnull Vector3d a0, @Nonnull Vector3d a1, @Nonnull Vector3d b0, @Nonnull Vector3d b1, boolean clamp, @Nonnull Vector3d p0Out, @Nonnull Vector3d p1Out + ) { + boolean[] flags = new boolean[2]; + Vector3d A = a1.clone().addScaled(a0, -1.0); + Vector3d B = b1.clone().addScaled(b0, -1.0); + double magA = A.length(); + double magB = B.length(); + Vector3d _A = A.clone().scale(1.0 / magA); + Vector3d _B = B.clone().scale(1.0 / magB); + Vector3d cross = _A.cross(_B); + double denom = Math.pow(cross.length(), 2.0); + if (denom == 0.0) { + flags[0] = true; + double d0 = _A.dot(b0.clone().addScaled(a0, -1.0)); + if (clamp) { + double d1 = _A.dot(b1.clone().addScaled(a0, -1.0)); + if (d0 <= 0.0 && d1 <= 0.0) { + if (Math.abs(d0) < Math.abs(d1)) { + p0Out.assign(a0); + p1Out.assign(b0); + flags[1] = true; + return flags; + } + + p0Out.assign(a0); + p1Out.assign(b1); + flags[1] = true; + return flags; + } + + if (d0 >= magA && d1 >= magA) { + if (Math.abs(d0) < Math.abs(d1)) { + p0Out.assign(a1); + p1Out.assign(b0); + flags[1] = true; + return flags; + } + + p0Out.assign(a1); + p1Out.assign(b1); + flags[1] = true; + return flags; + } + } + + return flags; + } else { + Vector3d t = b0.clone().addScaled(a0, -1.0); + double detA = determinant(t, _B, cross); + double detB = determinant(t, _A, cross); + double t0 = detA / denom; + double t1 = detB / denom; + Vector3d pA = _A.clone().scale(t0).add(a0); + Vector3d pB = _B.clone().scale(t1).add(b0); + if (clamp) { + if (t0 < 0.0) { + pA = a0.clone(); + } else if (t0 > magA) { + pA = a1.clone(); + } + + if (t1 < 0.0) { + pB = b0.clone(); + } else if (t1 > magB) { + pB = b1.clone(); + } + + if (t0 < 0.0 || t0 > magA) { + double dot = _B.dot(pA.clone().addScaled(b0, -1.0)); + if (dot < 0.0) { + dot = 0.0; + } else if (dot > magB) { + dot = magB; + } + + pB = b0.clone().add(_B.clone().scale(dot)); + } + + if (t1 < 0.0 || t1 > magA) { + double dot = _A.dot(pB.clone().addScaled(a0, -1.0)); + if (dot < 0.0) { + dot = 0.0; + } else if (dot > magA) { + dot = magA; + } + + pA = a0.clone().add(_A.clone().scale(dot)); + } + } + + p0Out.assign(pA); + p1Out.assign(pB); + flags[1] = true; + return flags; + } + } + + public static double shortestDistanceBetweenTwoSegments( + @Nonnull Vector3d a0, @Nonnull Vector3d a1, @Nonnull Vector3d b0, @Nonnull Vector3d b1, boolean clamp + ) { + Vector3d A = a1.clone().addScaled(a0, -1.0); + Vector3d B = b1.clone().addScaled(b0, -1.0); + double magA = A.length(); + double magB = B.length(); + Vector3d _A = A.clone().scale(1.0 / magA); + Vector3d _B = B.clone().scale(1.0 / magB); + Vector3d cross = _A.cross(_B); + double denom = Math.pow(cross.length(), 2.0); + if (denom == 0.0) { + double d0 = _A.dot(b0.clone().addScaled(a0, -1.0)); + if (clamp) { + double d1 = _A.dot(b1.clone().addScaled(a0, -1.0)); + if (d0 <= 0.0 && d1 <= 0.0) { + if (Math.abs(d0) < Math.abs(d1)) { + return a0.distanceTo(b0); + } + + return a0.distanceTo(b1); + } + + if (d0 >= magA && d1 >= magA) { + if (Math.abs(d0) < Math.abs(d1)) { + return a1.distanceTo(b0); + } + + return a1.distanceTo(b1); + } + } + + return distanceToLine3d(a0, b0, b1); + } else { + Vector3d t = b0.clone().addScaled(a0, -1.0); + double detA = determinant(t, _B, cross); + double detB = determinant(t, _A, cross); + double t0 = detA / denom; + double t1 = detB / denom; + Vector3d pA = _A.clone().scale(t0).add(a0); + Vector3d pB = _B.clone().scale(t1).add(b0); + if (clamp) { + if (t0 < 0.0) { + pA = a0.clone(); + } else if (t0 > magA) { + pA = a1.clone(); + } + + if (t1 < 0.0) { + pB = b0.clone(); + } else if (t1 > magB) { + pB = b1.clone(); + } + + if (t0 < 0.0 || t0 > magA) { + double dot = _B.dot(pA.clone().addScaled(b0, -1.0)); + if (dot < 0.0) { + dot = 0.0; + } else if (dot > magB) { + dot = magB; + } + + pB = b0.clone().add(_B.clone().scale(dot)); + } + + if (t1 < 0.0 || t1 > magA) { + double dot = _A.dot(pB.clone().addScaled(a0, -1.0)); + if (dot < 0.0) { + dot = 0.0; + } else if (dot > magA) { + dot = magA; + } + + pA = a0.clone().add(_A.clone().scale(dot)); + } + } + + return pA.distanceTo(pB); + } + } + + public static double determinant(@Nonnull Vector3d v1, @Nonnull Vector3d v2) { + Vector3d crossProduct = v1.cross(v2); + return crossProduct.length(); + } + + public static double determinant(@Nonnull Vector3d a, @Nonnull Vector3d b, @Nonnull Vector3d c) { + double det = a.x * b.y * c.z + b.x * c.y * a.z + c.x * a.y * b.z; + return det - (a.z * b.y * c.x + b.z * c.y * a.x + c.z * a.y * b.x); + } + + @Nonnull + public static DoubleObjectPair distanceAndNearestPointOnSegment3d(@Nonnull Vector3d point, @Nonnull Vector3d p0, @Nonnull Vector3d p1) { + Vector3d lineVec = p1.clone().addScaled(p0, -1.0); + Vector3d pointVec = point.clone().addScaled(p0, -1.0); + double lineLength = lineVec.length(); + Vector3d lineUnitVec = lineVec.clone().setLength(1.0); + Vector3d pointVecScaled = pointVec.clone().scale(1.0 / lineLength); + double t = lineUnitVec.dot(pointVecScaled); + t = Calculator.clamp(0.0, t, 1.0); + Vector3d nearestPoint = lineVec.clone().scale(t); + return DoubleObjectPair.of(nearestPoint.distanceTo(pointVec), nearestPoint.add(p0)); + } + + public static double angle(@Nonnull Vector3d a, @Nonnull Vector3d b) { + double top = a.x * b.x + a.y * b.y + a.z * b.z; + double bottomLeft = Math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z); + double bottomRight = Math.sqrt(b.x * b.x + b.y * b.y + b.z * b.z); + return Math.acos(top / (bottomLeft * bottomRight)); + } + + public static void rotateAroundAxis(@Nonnull Vector3d vec, @Nonnull Vector3d axis, double theta) { + Vector3d unitAxis = new Vector3d(axis); + unitAxis.normalize(); + double xPrime = unitAxis.x * (unitAxis.x * vec.x + unitAxis.y * vec.y + unitAxis.z * vec.z) * (1.0 - Math.cos(theta)) + + vec.x * Math.cos(theta) + + (-unitAxis.z * vec.y + unitAxis.y * vec.z) * Math.sin(theta); + double yPrime = unitAxis.y * (unitAxis.x * vec.x + unitAxis.y * vec.y + unitAxis.z * vec.z) * (1.0 - Math.cos(theta)) + + vec.y * Math.cos(theta) + + (unitAxis.z * vec.x - unitAxis.x * vec.z) * Math.sin(theta); + double zPrime = unitAxis.z * (unitAxis.x * vec.x + unitAxis.y * vec.y + unitAxis.z * vec.z) * (1.0 - Math.cos(theta)) + + vec.z * Math.cos(theta) + + (-unitAxis.y * vec.x + unitAxis.x * vec.y) * Math.sin(theta); + vec.x = xPrime; + vec.y = yPrime; + vec.z = zPrime; + } + + public static void rotateVectorByAxisAngle(@Nonnull Vector3d vec, @Nonnull Vector3d axis, double angle) { + Vector3d crossProd = axis.cross(vec); + double cosAngle = Math.cos(angle); + double sinAngle = Math.sin(angle); + double x = vec.x * cosAngle + crossProd.x * sinAngle + axis.x * axis.dot(vec) * (1.0 - cosAngle); + double y = vec.y * cosAngle + crossProd.y * sinAngle + axis.y * axis.dot(vec) * (1.0 - cosAngle); + double z = vec.z * cosAngle + crossProd.z * sinAngle + axis.z * axis.dot(vec) * (1.0 - cosAngle); + vec.x = x; + vec.y = y; + vec.z = z; + } + + public static boolean isInside(@Nonnull Vector3i point, @Nonnull Vector3i min, @Nonnull Vector3i max) { + return point.x >= min.x && point.x < max.x && point.y >= min.y && point.y < max.y && point.z >= min.z && point.z < max.z; + } + + public static boolean isInside(@Nonnull Vector3d point, @Nonnull Vector3d min, @Nonnull Vector3d max) { + return !isAnySmaller(point, min) && isSmaller(point, max); + } + + public static boolean isAnySmaller(@Nonnull Vector3d point, @Nonnull Vector3d limit) { + return point.x < limit.x || point.y < limit.y || point.z < limit.z; + } + + public static boolean isSmaller(@Nonnull Vector3d point, @Nonnull Vector3d limit) { + return point.x < limit.x && point.y < limit.y && point.z < limit.z; + } + + public static boolean isAnyGreater(@Nonnull Vector3d point, @Nonnull Vector3d limit) { + return point.x > limit.x || point.y > limit.y || point.z > limit.z; + } + + public static boolean isAnySmaller(@Nonnull Vector3i point, @Nonnull Vector3i limit) { + return point.x < limit.x || point.y < limit.y || point.z < limit.z; + } + + public static boolean isAnyGreater(@Nonnull Vector3i point, @Nonnull Vector3i limit) { + return point.x > limit.x || point.y > limit.y || point.z > limit.z; + } + + public static boolean isInside(@Nonnull Vector2d point, @Nonnull Vector2d min, @Nonnull Vector2d max) { + return !isAnySmaller(point, min) && isSmaller(point, max); + } + + public static boolean isAnySmaller(@Nonnull Vector2d point, @Nonnull Vector2d limit) { + return point.x < limit.x || point.y < limit.y; + } + + public static boolean isSmaller(@Nonnull Vector2d point, @Nonnull Vector2d limit) { + return point.x < limit.x && point.y < limit.y; + } + + public static boolean isAnyGreater(@Nonnull Vector2d point, @Nonnull Vector2d limit) { + return point.x > limit.x || point.y > limit.y; + } + + public static boolean isAnySmaller(@Nonnull Vector2i point, @Nonnull Vector2i limit) { + return point.x < limit.x || point.y < limit.y; + } + + public static boolean isSmaller(@Nonnull Vector2i point, @Nonnull Vector2i limit) { + return point.x < limit.x && point.y < limit.y; + } + + public static boolean isAnyGreater(@Nonnull Vector2i point, @Nonnull Vector2i limit) { + return point.x > limit.x || point.y > limit.y; + } + + @Nonnull + public static Vector3i fromOperation(@Nonnull Vector3i v1, @Nonnull Vector3i v2, @Nonnull VectorUtil.BiOperation3i operation) { + return new Vector3i( + operation.run(v1.x, v2.x, VectorUtil.Retriever.ofIndex(0)), + operation.run(v1.y, v2.y, VectorUtil.Retriever.ofIndex(1)), + operation.run(v1.z, v2.z, VectorUtil.Retriever.ofIndex(2)) + ); + } + + @Nonnull + public static Vector3i fromOperation(@Nonnull VectorUtil.NakedOperation3i operation) { + return new Vector3i( + operation.run(VectorUtil.Retriever.ofIndex(0)), operation.run(VectorUtil.Retriever.ofIndex(1)), operation.run(VectorUtil.Retriever.ofIndex(2)) + ); + } + + public static void bitShiftRight(int shift, @Nonnull Vector3i vector) { + if (shift < 0) { + throw new IllegalArgumentException("negative shift"); + } else { + vector.x >>= shift; + vector.y >>= shift; + vector.z >>= shift; + vector.dropHash(); + } + } + + public static void bitShiftLeft(int shift, @Nonnull Vector3i vector) { + if (shift < 0) { + throw new IllegalArgumentException("negative shift"); + } else { + vector.x <<= shift; + vector.y <<= shift; + vector.z <<= shift; + vector.dropHash(); + } + } + + @Nonnull + public static List orderByDistanceFrom(@Nonnull Vector2i origin, @Nonnull List vectors) { + ArrayList> distances = new ArrayList<>(vectors.size()); + + for (int i = 0; i < vectors.size(); i++) { + Vector2i vec = vectors.get(i); + double distance = Calculator.distance(vec.x, vec.y, origin.x, origin.y); + distances.add(Pair.of(distance, vec)); + } + + distances.sort(Comparator.comparingDouble(Pair::first)); + ArrayList sorted = new ArrayList<>(distances.size()); + + for (Pair pair : distances) { + sorted.add(pair.second()); + } + + return sorted; + } + + @FunctionalInterface + public interface BiOperation3i { + int run(int var1, int var2, @Nonnull VectorUtil.Retriever var3); + } + + @FunctionalInterface + public interface NakedOperation3i { + int run(@Nonnull VectorUtil.Retriever var1); + } + + @FunctionalInterface + public interface Operation3i { + int run(int var1, @Nonnull VectorUtil.Retriever var2); + } + + public static class Retriever { + private int index; + + public Retriever(int index) { + this.index = index; + } + + public int getIndex() { + return this.index; + } + + public int from(@Nonnull Vector3i vec) { + return switch (this.index) { + case 0 -> vec.x; + case 1 -> vec.y; + case 2 -> vec.z; + default -> throw new IllegalArgumentException(); + }; + } + + public int from(@Nonnull Vector2i vec) { + return switch (this.index) { + case 0 -> vec.x; + case 1 -> vec.y; + default -> throw new IllegalArgumentException(); + }; + } + + public double from(@Nonnull Vector3d vec) { + return switch (this.index) { + case 0 -> vec.x; + case 1 -> vec.y; + case 2 -> vec.z; + default -> throw new IllegalArgumentException(); + }; + } + + public double from(@Nonnull Vector2d vec) { + return switch (this.index) { + case 0 -> vec.x; + case 1 -> vec.y; + default -> throw new IllegalArgumentException(); + }; + } + + @Nonnull + public static VectorUtil.Retriever ofIndex(int index) { + return new VectorUtil.Retriever(index); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/AssetManager.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/AssetManager.java new file mode 100644 index 0000000..142c4d8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/AssetManager.java @@ -0,0 +1,588 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.assets.biomes.BiomeAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.blockmask.BlockMaskAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.CeilingCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.ClampCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.ConstantCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.CurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.DistanceExponentialCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.DistanceSCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.FloorCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.ImportedCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.InverterCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.MinCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.MultiplierCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.NotCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.SmoothCeilingCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.SmoothClampCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.SmoothFloorCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.SmoothMaxCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.SmoothMinCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.SumCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.manual.ManualCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.AbsDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.AmplitudeConstantAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.AmplitudeDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.AnchorDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.AngleDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.AxisDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.BaseHeightDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.Cache2dDensityAsset_Deprecated; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.CacheDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.CeilingDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.CellNoise2DDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.CellNoise3DDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.CellWallDistanceDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ClampDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ConstantDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.CubeDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.CuboidDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.CurveMapperDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.CylinderDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DistanceDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DistanceToBiomeEdgeDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.EllipsoidDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ExportedDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.FastGradientWarpDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.FloorDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.GradientDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.GradientWarpDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ImportedDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.InverterDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.MaxDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.MinDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.MixDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.MultiMixDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.MultiplierDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.NormalizerDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.OffsetConstantAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.OffsetDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.PipelineDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.PlaneDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.PositionsPinchDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.PositionsTwistDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.PowDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.RotatorDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ScaleDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ShellDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.SimplexNoise2dDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.SimplexNoise3DDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.SliderDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.SmoothCeilingDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.SmoothClampDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.SmoothFloorDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.SmoothMaxDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.SmoothMinDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.SqrtDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.SumDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.SwitchDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.SwitchStateDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.TerrainDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.VectorWarpDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.XOverrideDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.XValueDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.YOverrideDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.YValueDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ZOverrideDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ZValueDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.Positions3DDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.PositionsCellNoiseDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.distancefunctions.DistanceFunctionAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.distancefunctions.EuclideanDistanceFunctionAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.distancefunctions.ManhattanDistanceFunctionAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes.CellValueReturnTypeAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes.CurveReturnTypeAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes.DensityReturnTypeAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes.Distance2AddReturnTypeAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes.Distance2DivReturnTypeAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes.Distance2MulReturnTypeAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes.Distance2ReturnTypeAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes.Distance2SubReturnTypeAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes.DistanceReturnTypeAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes.ImportedReturnTypeAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes.ReturnTypeAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.environmentproviders.ConstantEnvironmentProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.environmentproviders.DensityDelimitedEnvironmentProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.environmentproviders.EnvironmentProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.ConstantMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.DownwardDepthMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.DownwardSpaceMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.FieldFunctionMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.ImportedMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.MaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.QueueMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.SimpleHorizontalMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.SolidityMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.StripedMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.TerrainDensityMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.UpwardDepthMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.UpwardSpaceMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.WeightedMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.SpaceAndDepthMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets.AlwaysTrueConditionAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets.AndConditionAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets.ConditionAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets.EqualsConditionAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets.GreaterThanConditionAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets.NotConditionAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets.OrConditionAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets.SmallerThanConditionAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.layerassets.ConstantThicknessLayerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.layerassets.LayerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.layerassets.NoiseThicknessAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.layerassets.RangeThicknessAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.layerassets.WeightedThicknessLayerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.noisegenerators.CellNoiseAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.noisegenerators.NoiseAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.noisegenerators.SimplexNoiseAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.AndPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.BlockSetPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.CeilingPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.ConstantPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.CuboidPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.DensityPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.FloorPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.GapPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.ImportedPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.MaterialPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.NotPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.OffsetPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.OrPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.PatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.SurfacePatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.WallPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.pointgenerators.MeshPointGeneratorAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.pointgenerators.PointGeneratorAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.AnchorPositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.BaseHeightPositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.CachedPositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.FieldFunctionOccurrencePositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.FieldFunctionPositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.ImportedPositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.ListPositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.Mesh2DPositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.Mesh3DPositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.OffsetPositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.PositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.SpherePositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.UnionPositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.VerticalEliminatorPositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.propassignments.AssignmentsAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.propassignments.ConstantAssignmentsAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.propassignments.FieldFunctionAssignmentsAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.propassignments.ImportedAssignmentsAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.propassignments.SandwichAssignmentsAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.propassignments.WeightedAssignmentsAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.BoxPropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.ClusterPropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.ColumnPropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.DensityPropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.ImportedPropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.PondFillerPropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.PropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.QueuePropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.UnionPropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop.PrefabPropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop.directionality.DirectionalityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop.directionality.ImportedDirectionalityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop.directionality.PatternDirectionalityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop.directionality.RandomDirectionalityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop.directionality.StaticDirectionalityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.AreaScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.ColumnLinearScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.ColumnRandomScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.ImportedScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.OriginScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.ScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.terrains.DensityTerrainAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.terrains.TerrainAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.tintproviders.ConstantTintProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.tintproviders.DensityDelimitedTintProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.tintproviders.TintProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.vectorproviders.CacheVectorProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.vectorproviders.ConstantVectorProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.vectorproviders.DensityGradientVectorProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.vectorproviders.ExportedVectorProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.vectorproviders.ImportedVectorProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.vectorproviders.VectorProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.worldstructures.WorldStructureAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.worldstructures.basic.BasicWorldStructureAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.worldstructures.mapcontentfield.BaseHeightContentFieldAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.worldstructures.mapcontentfield.ContentFieldAsset; +import com.hypixel.hytale.common.util.ExceptionUtil; +import com.hypixel.hytale.event.EventRegistry; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class AssetManager { + @Nonnull + private final HashMap densityAssets; + @Nonnull + private final HashMap assigmentAssets; + @Nonnull + private final HashMap biomeAssets; + @Nonnull + private final HashMap worldStructureAssets; + @Nonnull + private final HashMap blockMaskAssets; + private SettingsAsset settingsAsset; + @Nonnull + private final HytaleLogger logger; + private List reloadListeners; + + public AssetManager(@Nonnull EventRegistry eventRegistry, @Nonnull HytaleLogger logger) { + this.logger = logger; + this.reloadListeners = new ArrayList<>(1); + this.densityAssets = new HashMap<>(1); + this.assigmentAssets = new HashMap<>(1); + this.biomeAssets = new HashMap<>(1); + this.worldStructureAssets = new HashMap<>(1); + this.blockMaskAssets = new HashMap<>(1); + eventRegistry.register(LoadedAssetsEvent.class, DensityAsset.class, this::loadDensityAssets); + eventRegistry.register(LoadedAssetsEvent.class, AssignmentsAsset.class, this::loadAssignmentsAssets); + eventRegistry.register(LoadedAssetsEvent.class, BiomeAsset.class, this::loadBiomeAssets); + eventRegistry.register(LoadedAssetsEvent.class, WorldStructureAsset.class, this::loadWorldStructureAssets); + eventRegistry.register(LoadedAssetsEvent.class, SettingsAsset.class, this::loadSettingsAssets); + eventRegistry.register(LoadedAssetsEvent.class, BlockMaskAsset.class, this::loadBlockMaskAssets); + } + + private void loadBlockMaskAssets(@Nonnull LoadedAssetsEvent> event) { + this.blockMaskAssets.clear(); + + for (BlockMaskAsset value : event.getLoadedAssets().values()) { + this.blockMaskAssets.put(value.getId(), value); + this.logger.at(Level.FINE).log("Loaded BlockMask asset " + value.toString()); + } + + this.triggerReloadListeners(); + } + + private void loadDensityAssets(@Nonnull LoadedAssetsEvent> event) { + this.densityAssets.clear(); + + for (DensityAsset value : event.getLoadedAssets().values()) { + this.densityAssets.put(value.getId(), value); + this.logger.at(Level.FINE).log("Loaded Density asset " + value.toString()); + } + + this.triggerReloadListeners(); + } + + private void loadAssignmentsAssets(@Nonnull LoadedAssetsEvent> event) { + this.assigmentAssets.clear(); + + for (AssignmentsAsset value : event.getLoadedAssets().values()) { + this.assigmentAssets.put(value.getId(), value); + } + + this.triggerReloadListeners(); + } + + private void loadBiomeAssets(@Nonnull LoadedAssetsEvent> event) { + this.biomeAssets.clear(); + + for (BiomeAsset value : event.getLoadedAssets().values()) { + this.biomeAssets.put(value.getId(), value); + } + + this.triggerReloadListeners(); + } + + private void loadWorldStructureAssets(@Nonnull LoadedAssetsEvent> event) { + this.biomeAssets.clear(); + + for (WorldStructureAsset value : event.getLoadedAssets().values()) { + this.worldStructureAssets.put(value.getId(), value); + } + + this.triggerReloadListeners(); + } + + private void loadSettingsAssets(@Nonnull LoadedAssetsEvent> event) { + SettingsAsset asset = event.getLoadedAssets().get("Settings"); + if (asset != null) { + this.settingsAsset = asset; + this.logger.at(Level.INFO).log("Loaded Settings asset."); + this.triggerReloadListeners(); + } + } + + public SettingsAsset getSettingsAsset() { + return this.settingsAsset; + } + + public WorldStructureAsset getWorldStructureAsset(@Nonnull String id) { + return this.worldStructureAssets.get(id); + } + + public void registerReloadListener(@Nonnull Runnable l) { + this.reloadListeners.add(l); + } + + public void unregisterReloadListener(@Nonnull Runnable l) { + this.reloadListeners.remove(l); + } + + private void triggerReloadListeners() { + for (Runnable l : this.reloadListeners) { + try { + l.run(); + } catch (Exception var5) { + String msg = "Exception thrown by HytaleGenerator while executing a reload listener:\n"; + msg = msg + ExceptionUtil.toStringWithStack(var5); + LoggerUtil.getLogger().severe(msg); + } + } + } + + static { + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(BiomeAsset.class, new DefaultAssetMap()) + .setPath("HytaleGenerator/Biomes")) + .setKeyFunction(BiomeAsset::getId)) + .setCodec(BiomeAsset.CODEC)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + WorldStructureAsset.class, new DefaultAssetMap() + ) + .setPath("HytaleGenerator/WorldStructures")) + .setKeyFunction(WorldStructureAsset::getId)) + .setCodec(WorldStructureAsset.CODEC)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(DensityAsset.class, new DefaultAssetMap()) + .setPath("HytaleGenerator/Density")) + .setKeyFunction(DensityAsset::getId)) + .setCodec(DensityAsset.CODEC)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(BlockMaskAsset.class, new DefaultAssetMap()) + .setPath("HytaleGenerator/MaterialMasks")) + .setKeyFunction(BlockMaskAsset::getId)) + .setCodec(BlockMaskAsset.CODEC)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + AssignmentsAsset.class, new DefaultAssetMap() + ) + .setPath("HytaleGenerator/Assignments")) + .setKeyFunction(AssignmentsAsset::getId)) + .setCodec(AssignmentsAsset.CODEC)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(SettingsAsset.class, new DefaultAssetMap()) + .setPath("HytaleGenerator/Settings")) + .setKeyFunction(SettingsAsset::getId)) + .setCodec(SettingsAsset.CODEC)) + .build() + ); + DensityAsset.CODEC.register("SimplexNoise2D", SimplexNoise2dDensityAsset.class, SimplexNoise2dDensityAsset.CODEC); + DensityAsset.CODEC.register("SimplexNoise3D", SimplexNoise3DDensityAsset.class, SimplexNoise3DDensityAsset.CODEC); + DensityAsset.CODEC.register("Offset", OffsetDensityAsset.class, OffsetDensityAsset.CODEC); + DensityAsset.CODEC.register("Sum", SumDensityAsset.class, SumDensityAsset.CODEC); + DensityAsset.CODEC.register("Sqrt", SqrtDensityAsset.class, SqrtDensityAsset.CODEC); + DensityAsset.CODEC.register("Pow", PowDensityAsset.class, PowDensityAsset.CODEC); + DensityAsset.CODEC.register("Multiplier", MultiplierDensityAsset.class, MultiplierDensityAsset.CODEC); + DensityAsset.CODEC.register("Amplitude", AmplitudeDensityAsset.class, AmplitudeDensityAsset.CODEC); + DensityAsset.CODEC.register("Clamp", ClampDensityAsset.class, ClampDensityAsset.CODEC); + DensityAsset.CODEC.register("SmoothClamp", SmoothClampDensityAsset.class, SmoothClampDensityAsset.CODEC); + DensityAsset.CODEC.register("Max", MaxDensityAsset.class, MaxDensityAsset.CODEC); + DensityAsset.CODEC.register("Min", MinDensityAsset.class, MinDensityAsset.CODEC); + DensityAsset.CODEC.register("Floor", FloorDensityAsset.class, FloorDensityAsset.CODEC); + DensityAsset.CODEC.register("Ceiling", CeilingDensityAsset.class, CeilingDensityAsset.CODEC); + DensityAsset.CODEC.register("SmoothMax", SmoothMaxDensityAsset.class, SmoothMaxDensityAsset.CODEC); + DensityAsset.CODEC.register("SmoothMin", SmoothMinDensityAsset.class, SmoothMinDensityAsset.CODEC); + DensityAsset.CODEC.register("SmoothFloor", SmoothFloorDensityAsset.class, SmoothFloorDensityAsset.CODEC); + DensityAsset.CODEC.register("SmoothCeiling", SmoothCeilingDensityAsset.class, SmoothCeilingDensityAsset.CODEC); + DensityAsset.CODEC.register("Constant", ConstantDensityAsset.class, ConstantDensityAsset.CODEC); + DensityAsset.CODEC.register("Abs", AbsDensityAsset.class, AbsDensityAsset.CODEC); + DensityAsset.CODEC.register("Inverter", InverterDensityAsset.class, InverterDensityAsset.CODEC); + DensityAsset.CODEC.register("AmplitudeConstant", AmplitudeConstantAsset.class, AmplitudeConstantAsset.CODEC); + DensityAsset.CODEC.register("OffsetConstant", OffsetConstantAsset.class, OffsetConstantAsset.CODEC); + DensityAsset.CODEC.register("Pipeline", PipelineDensityAsset.class, PipelineDensityAsset.CODEC); + DensityAsset.CODEC.register("Normalizer", NormalizerDensityAsset.class, NormalizerDensityAsset.CODEC); + DensityAsset.CODEC.register("Imported", ImportedDensityAsset.class, ImportedDensityAsset.CODEC); + DensityAsset.CODEC.register("PositionsCellNoise", PositionsCellNoiseDensityAsset.class, PositionsCellNoiseDensityAsset.CODEC); + DensityAsset.CODEC.register("Positions3D", Positions3DDensityAsset.class, Positions3DDensityAsset.CODEC); + DensityAsset.CODEC.register("CellNoise2D", CellNoise2DDensityAsset.class, CellNoise2DDensityAsset.CODEC); + DensityAsset.CODEC.register("CellNoise3D", CellNoise3DDensityAsset.class, CellNoise3DDensityAsset.CODEC); + DensityAsset.CODEC.register("Gradient", GradientDensityAsset.class, GradientDensityAsset.CODEC); + DensityAsset.CODEC.register("Scale", ScaleDensityAsset.class, ScaleDensityAsset.CODEC); + DensityAsset.CODEC.register("Slider", SliderDensityAsset.class, SliderDensityAsset.CODEC); + DensityAsset.CODEC.register("GradientWarp", GradientWarpDensityAsset.class, GradientWarpDensityAsset.CODEC); + DensityAsset.CODEC.register("VectorWarp", VectorWarpDensityAsset.class, VectorWarpDensityAsset.CODEC); + DensityAsset.CODEC.register("Cache2D", Cache2dDensityAsset_Deprecated.class, Cache2dDensityAsset_Deprecated.CODEC); + DensityAsset.CODEC.register("Rotator", RotatorDensityAsset.class, RotatorDensityAsset.CODEC); + DensityAsset.CODEC.register("PositionsPinch", PositionsPinchDensityAsset.class, PositionsPinchDensityAsset.CODEC); + DensityAsset.CODEC.register("PositionsTwist", PositionsTwistDensityAsset.class, PositionsTwistDensityAsset.CODEC); + DensityAsset.CODEC.register("BaseHeight", BaseHeightDensityAsset.class, BaseHeightDensityAsset.CODEC); + DensityAsset.CODEC.register("CurveMapper", CurveMapperDensityAsset.class, CurveMapperDensityAsset.CODEC); + DensityAsset.CODEC.register("Anchor", AnchorDensityAsset.class, AnchorDensityAsset.CODEC); + DensityAsset.CODEC.register("Distance", DistanceDensityAsset.class, DistanceDensityAsset.CODEC); + DensityAsset.CODEC.register("Shell", ShellDensityAsset.class, ShellDensityAsset.CODEC); + DensityAsset.CODEC.register("Axis", AxisDensityAsset.class, AxisDensityAsset.CODEC); + DensityAsset.CODEC.register("Plane", PlaneDensityAsset.class, PlaneDensityAsset.CODEC); + DensityAsset.CODEC.register("Switch", SwitchDensityAsset.class, SwitchDensityAsset.CODEC); + DensityAsset.CODEC.register("SwitchState", SwitchStateDensityAsset.class, SwitchStateDensityAsset.CODEC); + DensityAsset.CODEC.register("Ellipsoid", EllipsoidDensityAsset.class, EllipsoidDensityAsset.CODEC); + DensityAsset.CODEC.register("Cube", CubeDensityAsset.class, CubeDensityAsset.CODEC); + DensityAsset.CODEC.register("Cuboid", CuboidDensityAsset.class, CuboidDensityAsset.CODEC); + DensityAsset.CODEC.register("Cylinder", CylinderDensityAsset.class, CylinderDensityAsset.CODEC); + DensityAsset.CODEC.register("CellWallDistance", CellWallDistanceDensityAsset.class, CellWallDistanceDensityAsset.CODEC); + DensityAsset.CODEC.register("FastGradientWarp", FastGradientWarpDensityAsset.class, FastGradientWarpDensityAsset.CODEC); + DensityAsset.CODEC.register("Mix", MixDensityAsset.class, MixDensityAsset.CODEC); + DensityAsset.CODEC.register("MultiMix", MultiMixDensityAsset.class, MultiMixDensityAsset.CODEC); + DensityAsset.CODEC.register("XValue", XValueDensityAsset.class, XValueDensityAsset.CODEC); + DensityAsset.CODEC.register("YValue", YValueDensityAsset.class, YValueDensityAsset.CODEC); + DensityAsset.CODEC.register("ZValue", ZValueDensityAsset.class, ZValueDensityAsset.CODEC); + DensityAsset.CODEC.register("XOverride", XOverrideDensityAsset.class, XOverrideDensityAsset.CODEC); + DensityAsset.CODEC.register("YOverride", YOverrideDensityAsset.class, YOverrideDensityAsset.CODEC); + DensityAsset.CODEC.register("ZOverride", ZOverrideDensityAsset.class, ZOverrideDensityAsset.CODEC); + DensityAsset.CODEC.register("Cache", CacheDensityAsset.class, CacheDensityAsset.CODEC); + DensityAsset.CODEC.register("Angle", AngleDensityAsset.class, AngleDensityAsset.CODEC); + DensityAsset.CODEC.register("Exported", ExportedDensityAsset.class, ExportedDensityAsset.CODEC); + DensityAsset.CODEC.register("Terrain", TerrainDensityAsset.class, TerrainDensityAsset.CODEC); + DensityAsset.CODEC.register("DistanceToBiomeEdge", DistanceToBiomeEdgeDensityAsset.class, DistanceToBiomeEdgeDensityAsset.CODEC); + ContentFieldAsset.CODEC.register("BaseHeight", BaseHeightContentFieldAsset.class, BaseHeightContentFieldAsset.CODEC); + TerrainAsset.CODEC.register("DAOTerrain", DensityTerrainAsset.class, DensityTerrainAsset.CODEC); + NoiseAsset.CODEC.register("Simplex", SimplexNoiseAsset.class, SimplexNoiseAsset.CODEC); + NoiseAsset.CODEC.register("Cell", CellNoiseAsset.class, CellNoiseAsset.CODEC); + WorldStructureAsset.CODEC.register("NoiseRange", BasicWorldStructureAsset.class, BasicWorldStructureAsset.CODEC); + MaterialProviderAsset.CODEC.register("Constant", ConstantMaterialProviderAsset.class, ConstantMaterialProviderAsset.CODEC); + MaterialProviderAsset.CODEC.register("Solidity", SolidityMaterialProviderAsset.class, SolidityMaterialProviderAsset.CODEC); + MaterialProviderAsset.CODEC.register("DownwardDepth", DownwardDepthMaterialProviderAsset.class, DownwardDepthMaterialProviderAsset.CODEC); + MaterialProviderAsset.CODEC.register("DownwardSpace", DownwardSpaceMaterialProviderAsset.class, DownwardSpaceMaterialProviderAsset.CODEC); + MaterialProviderAsset.CODEC.register("UpwardDepth", UpwardDepthMaterialProviderAsset.class, UpwardDepthMaterialProviderAsset.CODEC); + MaterialProviderAsset.CODEC.register("UpwardSpace", UpwardSpaceMaterialProviderAsset.class, UpwardSpaceMaterialProviderAsset.CODEC); + MaterialProviderAsset.CODEC.register("Queue", QueueMaterialProviderAsset.class, QueueMaterialProviderAsset.CODEC); + MaterialProviderAsset.CODEC.register("SimpleHorizontal", SimpleHorizontalMaterialProviderAsset.class, SimpleHorizontalMaterialProviderAsset.CODEC); + MaterialProviderAsset.CODEC.register("Striped", StripedMaterialProviderAsset.class, StripedMaterialProviderAsset.CODEC); + MaterialProviderAsset.CODEC.register("FieldFunction", FieldFunctionMaterialProviderAsset.class, FieldFunctionMaterialProviderAsset.CODEC); + MaterialProviderAsset.CODEC.register("TerrainDensity", TerrainDensityMaterialProviderAsset.class, TerrainDensityMaterialProviderAsset.CODEC); + MaterialProviderAsset.CODEC.register("Weighted", WeightedMaterialProviderAsset.class, WeightedMaterialProviderAsset.CODEC); + MaterialProviderAsset.CODEC.register("SpaceAndDepth", SpaceAndDepthMaterialProviderAsset.class, SpaceAndDepthMaterialProviderAsset.CODEC); + MaterialProviderAsset.CODEC.register("Imported", ImportedMaterialProviderAsset.class, ImportedMaterialProviderAsset.CODEC); + LayerAsset.CODEC.register("ConstantThickness", ConstantThicknessLayerAsset.class, ConstantThicknessLayerAsset.CODEC); + LayerAsset.CODEC.register("NoiseThickness", NoiseThicknessAsset.class, NoiseThicknessAsset.CODEC); + LayerAsset.CODEC.register("RangeThickness", RangeThicknessAsset.class, RangeThicknessAsset.CODEC); + LayerAsset.CODEC.register("WeightedThickness", WeightedThicknessLayerAsset.class, WeightedThicknessLayerAsset.CODEC); + ConditionAsset.CODEC.register("AndCondition", AndConditionAsset.class, AndConditionAsset.CODEC); + ConditionAsset.CODEC.register("EqualsCondition", EqualsConditionAsset.class, EqualsConditionAsset.CODEC); + ConditionAsset.CODEC.register("GreaterThanCondition", GreaterThanConditionAsset.class, GreaterThanConditionAsset.CODEC); + ConditionAsset.CODEC.register("NotCondition", NotConditionAsset.class, NotConditionAsset.CODEC); + ConditionAsset.CODEC.register("OrCondition", OrConditionAsset.class, OrConditionAsset.CODEC); + ConditionAsset.CODEC.register("SmallerThanCondition", SmallerThanConditionAsset.class, SmallerThanConditionAsset.CODEC); + ConditionAsset.CODEC.register("AlwaysTrueCondition", AlwaysTrueConditionAsset.class, AlwaysTrueConditionAsset.CODEC); + PositionProviderAsset.CODEC.register("List", ListPositionProviderAsset.class, ListPositionProviderAsset.CODEC); + PositionProviderAsset.CODEC.register("Mesh2D", Mesh2DPositionProviderAsset.class, Mesh2DPositionProviderAsset.CODEC); + PositionProviderAsset.CODEC.register("Mesh3D", Mesh3DPositionProviderAsset.class, Mesh3DPositionProviderAsset.CODEC); + PositionProviderAsset.CODEC.register("FieldFunction", FieldFunctionPositionProviderAsset.class, FieldFunctionPositionProviderAsset.CODEC); + PositionProviderAsset.CODEC + .register("Occurrence", FieldFunctionOccurrencePositionProviderAsset.class, FieldFunctionOccurrencePositionProviderAsset.CODEC); + PositionProviderAsset.CODEC.register("Offset", OffsetPositionProviderAsset.class, OffsetPositionProviderAsset.CODEC); + PositionProviderAsset.CODEC.register("Union", UnionPositionProviderAsset.class, UnionPositionProviderAsset.CODEC); + PositionProviderAsset.CODEC.register("VerticalEliminator", VerticalEliminatorPositionProviderAsset.class, VerticalEliminatorPositionProviderAsset.CODEC); + PositionProviderAsset.CODEC.register("Cache", CachedPositionProviderAsset.class, CachedPositionProviderAsset.CODEC); + PositionProviderAsset.CODEC.register("BaseHeight", BaseHeightPositionProviderAsset.class, BaseHeightPositionProviderAsset.CODEC); + PositionProviderAsset.CODEC.register("Imported", ImportedPositionProviderAsset.class, ImportedPositionProviderAsset.CODEC); + PositionProviderAsset.CODEC.register("Anchor", AnchorPositionProviderAsset.class, AnchorPositionProviderAsset.CODEC); + PositionProviderAsset.CODEC.register("Sphere", SpherePositionProviderAsset.class, SpherePositionProviderAsset.CODEC); + PointGeneratorAsset.CODEC.register("Mesh", MeshPointGeneratorAsset.class, MeshPointGeneratorAsset.CODEC); + AssignmentsAsset.CODEC.register("FieldFunction", FieldFunctionAssignmentsAsset.class, FieldFunctionAssignmentsAsset.CODEC); + AssignmentsAsset.CODEC.register("Sandwich", SandwichAssignmentsAsset.class, SandwichAssignmentsAsset.CODEC); + AssignmentsAsset.CODEC.register("Weighted", WeightedAssignmentsAsset.class, WeightedAssignmentsAsset.CODEC); + AssignmentsAsset.CODEC.register("Constant", ConstantAssignmentsAsset.class, ConstantAssignmentsAsset.CODEC); + AssignmentsAsset.CODEC.register("Imported", ImportedAssignmentsAsset.class, ImportedAssignmentsAsset.CODEC); + PropAsset.CODEC.register("Box", BoxPropAsset.class, BoxPropAsset.CODEC); + PropAsset.CODEC.register("Imported", ImportedPropAsset.class, ImportedPropAsset.CODEC); + PropAsset.CODEC.register("Union", UnionPropAsset.class, UnionPropAsset.CODEC); + PropAsset.CODEC.register("Column", ColumnPropAsset.class, ColumnPropAsset.CODEC); + PropAsset.CODEC.register("Cluster", ClusterPropAsset.class, ClusterPropAsset.CODEC); + PropAsset.CODEC.register("Queue", QueuePropAsset.class, QueuePropAsset.CODEC); + PropAsset.CODEC.register("Prefab", PrefabPropAsset.class, PrefabPropAsset.CODEC); + PropAsset.CODEC.register("PondFiller", PondFillerPropAsset.class, PondFillerPropAsset.CODEC); + PropAsset.CODEC.register("Density", DensityPropAsset.class, DensityPropAsset.CODEC); + DirectionalityAsset.CODEC.register("Imported", ImportedDirectionalityAsset.class, ImportedDirectionalityAsset.CODEC); + DirectionalityAsset.CODEC.register("Static", StaticDirectionalityAsset.class, StaticDirectionalityAsset.CODEC); + DirectionalityAsset.CODEC.register("Random", RandomDirectionalityAsset.class, RandomDirectionalityAsset.CODEC); + DirectionalityAsset.CODEC.register("Pattern", PatternDirectionalityAsset.class, PatternDirectionalityAsset.CODEC); + PatternAsset.CODEC.register("BlockType", MaterialPatternAsset.class, MaterialPatternAsset.CODEC); + PatternAsset.CODEC.register("BlockSet", BlockSetPatternAsset.class, BlockSetPatternAsset.CODEC); + PatternAsset.CODEC.register("Offset", OffsetPatternAsset.class, OffsetPatternAsset.CODEC); + PatternAsset.CODEC.register("Floor", FloorPatternAsset.class, FloorPatternAsset.CODEC); + PatternAsset.CODEC.register("Ceiling", CeilingPatternAsset.class, CeilingPatternAsset.CODEC); + PatternAsset.CODEC.register("Wall", WallPatternAsset.class, WallPatternAsset.CODEC); + PatternAsset.CODEC.register("Cuboid", CuboidPatternAsset.class, CuboidPatternAsset.CODEC); + PatternAsset.CODEC.register("And", AndPatternAsset.class, AndPatternAsset.CODEC); + PatternAsset.CODEC.register("Or", OrPatternAsset.class, OrPatternAsset.CODEC); + PatternAsset.CODEC.register("Not", NotPatternAsset.class, NotPatternAsset.CODEC); + PatternAsset.CODEC.register("Surface", SurfacePatternAsset.class, SurfacePatternAsset.CODEC); + PatternAsset.CODEC.register("Gap", GapPatternAsset.class, GapPatternAsset.CODEC); + PatternAsset.CODEC.register("FieldFunction", DensityPatternAsset.class, DensityPatternAsset.CODEC); + PatternAsset.CODEC.register("Imported", ImportedPatternAsset.class, ImportedPatternAsset.CODEC); + PatternAsset.CODEC.register("Constant", ConstantPatternAsset.class, ConstantPatternAsset.CODEC); + ScannerAsset.CODEC.register("ColumnLinear", ColumnLinearScannerAsset.class, ColumnLinearScannerAsset.CODEC); + ScannerAsset.CODEC.register("ColumnRandom", ColumnRandomScannerAsset.class, ColumnRandomScannerAsset.CODEC); + ScannerAsset.CODEC.register("Origin", OriginScannerAsset.class, OriginScannerAsset.CODEC); + ScannerAsset.CODEC.register("Area", AreaScannerAsset.class, AreaScannerAsset.CODEC); + ScannerAsset.CODEC.register("Imported", ImportedScannerAsset.class, ImportedScannerAsset.CODEC); + CurveAsset.CODEC.register("Imported", ImportedCurveAsset.class, ImportedCurveAsset.CODEC); + CurveAsset.CODEC.register("Manual", ManualCurveAsset.class, ManualCurveAsset.CODEC); + CurveAsset.CODEC.register("DistanceExponential", DistanceExponentialCurveAsset.class, DistanceExponentialCurveAsset.CODEC); + CurveAsset.CODEC.register("DistanceS", DistanceSCurveAsset.class, DistanceSCurveAsset.CODEC); + CurveAsset.CODEC.register("Not", NotCurveAsset.class, NotCurveAsset.CODEC); + CurveAsset.CODEC.register("Multiplier", MultiplierCurveAsset.class, MultiplierCurveAsset.CODEC); + CurveAsset.CODEC.register("Sum", SumCurveAsset.class, SumCurveAsset.CODEC); + CurveAsset.CODEC.register("Inverter", InverterCurveAsset.class, InverterCurveAsset.CODEC); + CurveAsset.CODEC.register("Clamp", ClampCurveAsset.class, ClampCurveAsset.CODEC); + CurveAsset.CODEC.register("SmoothClamp", SmoothClampCurveAsset.class, SmoothClampCurveAsset.CODEC); + CurveAsset.CODEC.register("Min", MinCurveAsset.class, MinCurveAsset.CODEC); + CurveAsset.CODEC.register("Max", MinCurveAsset.class, MinCurveAsset.CODEC); + CurveAsset.CODEC.register("SmoothMin", SmoothMinCurveAsset.class, SmoothMinCurveAsset.CODEC); + CurveAsset.CODEC.register("SmoothMax", SmoothMaxCurveAsset.class, SmoothMaxCurveAsset.CODEC); + CurveAsset.CODEC.register("SmoothFloor", SmoothFloorCurveAsset.class, SmoothFloorCurveAsset.CODEC); + CurveAsset.CODEC.register("SmoothCeiling", SmoothCeilingCurveAsset.class, SmoothCeilingCurveAsset.CODEC); + CurveAsset.CODEC.register("Floor", FloorCurveAsset.class, FloorCurveAsset.CODEC); + CurveAsset.CODEC.register("Ceiling", CeilingCurveAsset.class, CeilingCurveAsset.CODEC); + CurveAsset.CODEC.register("Constant", ConstantCurveAsset.class, ConstantCurveAsset.CODEC); + ReturnTypeAsset.CODEC.register("CellValue", CellValueReturnTypeAsset.class, CellValueReturnTypeAsset.CODEC); + ReturnTypeAsset.CODEC.register("Curve", CurveReturnTypeAsset.class, CurveReturnTypeAsset.CODEC); + ReturnTypeAsset.CODEC.register("Distance", DistanceReturnTypeAsset.class, DistanceReturnTypeAsset.CODEC); + ReturnTypeAsset.CODEC.register("Distance2", Distance2ReturnTypeAsset.class, Distance2ReturnTypeAsset.CODEC); + ReturnTypeAsset.CODEC.register("Distance2Add", Distance2AddReturnTypeAsset.class, Distance2AddReturnTypeAsset.CODEC); + ReturnTypeAsset.CODEC.register("Distance2Sub", Distance2SubReturnTypeAsset.class, Distance2SubReturnTypeAsset.CODEC); + ReturnTypeAsset.CODEC.register("Distance2Mul", Distance2MulReturnTypeAsset.class, Distance2MulReturnTypeAsset.CODEC); + ReturnTypeAsset.CODEC.register("Distance2Div", Distance2DivReturnTypeAsset.class, Distance2DivReturnTypeAsset.CODEC); + ReturnTypeAsset.CODEC.register("Imported", ImportedReturnTypeAsset.class, ImportedReturnTypeAsset.CODEC); + ReturnTypeAsset.CODEC.register("Density", DensityReturnTypeAsset.class, DensityReturnTypeAsset.CODEC); + DistanceFunctionAsset.CODEC.register("Euclidean", EuclideanDistanceFunctionAsset.class, EuclideanDistanceFunctionAsset.CODEC); + DistanceFunctionAsset.CODEC.register("Manhattan", ManhattanDistanceFunctionAsset.class, ManhattanDistanceFunctionAsset.CODEC); + EnvironmentProviderAsset.CODEC.register("Constant", ConstantEnvironmentProviderAsset.class, ConstantEnvironmentProviderAsset.CODEC); + EnvironmentProviderAsset.CODEC + .register("DensityDelimited", DensityDelimitedEnvironmentProviderAsset.class, DensityDelimitedEnvironmentProviderAsset.CODEC); + TintProviderAsset.CODEC.register("Constant", ConstantTintProviderAsset.class, ConstantTintProviderAsset.CODEC); + TintProviderAsset.CODEC.register("DensityDelimited", DensityDelimitedTintProviderAsset.class, DensityDelimitedTintProviderAsset.CODEC); + VectorProviderAsset.CODEC.register("Constant", ConstantVectorProviderAsset.class, ConstantVectorProviderAsset.CODEC); + VectorProviderAsset.CODEC.register("DensityGradient", DensityGradientVectorProviderAsset.class, DensityGradientVectorProviderAsset.CODEC); + VectorProviderAsset.CODEC.register("Cache", CacheVectorProviderAsset.class, CacheVectorProviderAsset.CODEC); + VectorProviderAsset.CODEC.register("Exported", ExportedVectorProviderAsset.class, ExportedVectorProviderAsset.CODEC); + VectorProviderAsset.CODEC.register("Imported", ImportedVectorProviderAsset.class, ImportedVectorProviderAsset.CODEC); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/Cleanable.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/Cleanable.java new file mode 100644 index 0000000..f5d5548 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/Cleanable.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets; + +public interface Cleanable { + void cleanUp(); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/SettingsAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/SettingsAsset.java new file mode 100644 index 0000000..637118f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/SettingsAsset.java @@ -0,0 +1,91 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import java.util.List; +import javax.annotation.Nonnull; + +public class SettingsAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + SettingsAsset.class, + SettingsAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("StatsCheckpoints", new ArrayCodec<>(Codec.INTEGER, Integer[]::new), true), (t, k) -> t.checkpoints = k, t -> t.checkpoints) + .add() + .append(new KeyedCodec<>("CustomConcurrency", Codec.INTEGER, true), (t, k) -> t.customConcurrency = k, t -> t.customConcurrency) + .addValidator(Validators.greaterThan(-2)) + .add() + .append( + new KeyedCodec<>("BufferCapacityFactor", Codec.DOUBLE, true), + (asset, value) -> asset.bufferCapacityFactor = value, + asset -> asset.bufferCapacityFactor + ) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append( + new KeyedCodec<>("TargetViewDistance", Codec.DOUBLE, true), (asset, value) -> asset.targetViewDistance = value, asset -> asset.targetViewDistance + ) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append( + new KeyedCodec<>("TargetPlayerCount", Codec.DOUBLE, true), (asset, value) -> asset.targetPlayerCount = value, asset -> asset.targetPlayerCount + ) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private Integer[] checkpoints = new Integer[0]; + private int customConcurrency = -1; + private double bufferCapacityFactor = 0.3; + private double targetViewDistance = 512.0; + private double targetPlayerCount = 3.0; + + private SettingsAsset() { + } + + @Nonnull + public List getStatsCheckpoints() { + return List.of(this.checkpoints); + } + + public int getCustomConcurrency() { + return this.customConcurrency; + } + + public double getBufferCapacityFactor() { + return this.bufferCapacityFactor; + } + + public double getTargetViewDistance() { + return this.targetViewDistance; + } + + public double getTargetPlayerCount() { + return this.targetPlayerCount; + } + + public static int getSampleBits(int v) { + return switch (v) { + case 2 -> 1; + case 4 -> 2; + case 8 -> 3; + default -> 0; + }; + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/ValidatorUtil.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/ValidatorUtil.java new file mode 100644 index 0000000..7b2e35d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/ValidatorUtil.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets; + +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.codec.validation.ValidationResults; +import javax.annotation.Nonnull; + +public class ValidatorUtil { + public ValidatorUtil() { + } + + @Nonnull + public static LegacyValidator validEnumValue(@Nonnull final T[] values) { + return new LegacyValidator() { + public void accept(String providedValue, @Nonnull ValidationResults results) { + for (T value : values) { + if (value.toString().equals(providedValue)) { + return; + } + } + + results.fail("String not a valid enum value: " + providedValue); + } + }; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/biomes/BiomeAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/biomes/BiomeAsset.java new file mode 100644 index 0000000..cf983f1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/biomes/BiomeAsset.java @@ -0,0 +1,163 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.biomes; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.PropField; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.environmentproviders.ConstantEnvironmentProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.environmentproviders.EnvironmentProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.ConstantMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.MaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.propstageiterations.PropRuntimeAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.terrains.DensityTerrainAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.terrains.TerrainAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.tintproviders.ConstantTintProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.tintproviders.TintProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.biome.BiomeType; +import com.hypixel.hytale.builtin.hytalegenerator.biome.SimpleBiomeType; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.environmentproviders.EnvironmentProvider; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.propdistributions.Assignments; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.builtin.hytalegenerator.tintproviders.TintProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import javax.annotation.Nonnull; + +public class BiomeAsset implements JsonAssetWithMap>, Cleanable { + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(BiomeAsset::getAssetStore)); + private static AssetStore> STORE; + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + BiomeAsset.class, + BiomeAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Terrain", TerrainAsset.CODEC, true), (asset, t) -> asset.terrainAsset = t, asset -> asset.terrainAsset) + .add() + .append( + new KeyedCodec<>("FloatingFunctionNodes", new ArrayCodec<>(DensityAsset.CODEC, DensityAsset[]::new), true), + (asset, t) -> asset.floatingFunctionNodeAssets = t, + asset -> asset.floatingFunctionNodeAssets + ) + .add() + .append(new KeyedCodec<>("Name", Codec.STRING, true), (asset, t) -> asset.biomeName = t, asset -> asset.biomeName) + .add() + .append( + new KeyedCodec<>("MaterialProvider", MaterialProviderAsset.CODEC, true), + (asset, materialProvider) -> asset.materialProviderAsset = materialProvider, + asset -> asset.materialProviderAsset + ) + .add() + .append( + new KeyedCodec<>("Props", new ArrayCodec<>(PropRuntimeAsset.CODEC, PropRuntimeAsset[]::new), true), + (asset, materialProvider) -> asset.propRuntimeAssets = materialProvider, + asset -> asset.propRuntimeAssets + ) + .add() + .append( + new KeyedCodec<>("EnvironmentProvider", EnvironmentProviderAsset.CODEC, true), + (asset, environmentProvider) -> asset.environmentProviderAsset = environmentProvider, + asset -> asset.environmentProviderAsset + ) + .add() + .append( + new KeyedCodec<>("TintProvider", TintProviderAsset.CODEC, true), + (asset, tintProvider) -> asset.tintProviderAsset = tintProvider, + asset -> asset.tintProviderAsset + ) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private TerrainAsset terrainAsset = new DensityTerrainAsset(); + private MaterialProviderAsset materialProviderAsset = new ConstantMaterialProviderAsset(); + private PropRuntimeAsset[] propRuntimeAssets = new PropRuntimeAsset[0]; + private EnvironmentProviderAsset environmentProviderAsset = new ConstantEnvironmentProviderAsset(); + private TintProviderAsset tintProviderAsset = new ConstantTintProviderAsset(); + private String biomeName = "DefaultName"; + private DensityAsset[] floatingFunctionNodeAssets = new DensityAsset[0]; + + public static AssetStore> getAssetStore() { + if (STORE == null) { + STORE = AssetRegistry.getAssetStore(BiomeAsset.class); + } + + return STORE; + } + + private BiomeAsset() { + } + + @Override + public void cleanUp() { + this.terrainAsset.cleanUp(); + this.materialProviderAsset.cleanUp(); + + for (PropRuntimeAsset propRuntimeAsset : this.propRuntimeAssets) { + propRuntimeAsset.cleanUp(); + } + + this.environmentProviderAsset.cleanUp(); + this.tintProviderAsset.cleanUp(); + + for (DensityAsset densityAsset : this.floatingFunctionNodeAssets) { + densityAsset.cleanUp(); + } + } + + public BiomeType build( + @Nonnull MaterialCache materialCache, @Nonnull SeedBox parentSeed, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer + ) { + MaterialProvider materialProvider = this.materialProviderAsset + .build(new MaterialProviderAsset.Argument(parentSeed, materialCache, referenceBundle, workerIndexer)); + Density density = this.terrainAsset.buildDensity(parentSeed, referenceBundle, workerIndexer); + EnvironmentProvider environments = EnvironmentProvider.noEnvironmentProvider(); + if (this.environmentProviderAsset != null) { + environments = this.environmentProviderAsset.build(new EnvironmentProviderAsset.Argument(parentSeed, materialCache, referenceBundle, workerIndexer)); + } + + TintProvider tints = TintProvider.noTintProvider(); + if (this.tintProviderAsset != null) { + tints = this.tintProviderAsset.build(new TintProviderAsset.Argument(parentSeed, materialCache, referenceBundle, workerIndexer)); + } + + SimpleBiomeType biome = new SimpleBiomeType(this.biomeName, density, materialProvider, environments, tints); + + for (PropRuntimeAsset fieldAsset : this.propRuntimeAssets) { + if (!fieldAsset.isSkip()) { + PositionProvider positionProvider = fieldAsset.buildPositionProvider(parentSeed, referenceBundle, workerIndexer); + Assignments distribution = fieldAsset.buildPropDistribution(parentSeed, materialCache, fieldAsset.getRuntime(), referenceBundle, workerIndexer); + PropField field = new PropField(fieldAsset.getRuntime(), distribution, positionProvider); + biome.addPropFieldTo(field); + } + } + + return biome; + } + + public String getBiomeName() { + return this.biomeName; + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/blockmask/BlockMaskAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/blockmask/BlockMaskAsset.java new file mode 100644 index 0000000..b420016 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/blockmask/BlockMaskAsset.java @@ -0,0 +1,113 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.blockmask; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.BlockMask; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.MaterialSet; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.assets.blockset.MaterialSetAsset; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class BlockMaskAsset implements JsonAssetWithMap>, Cleanable { + private static final Map exportedNodes = new HashMap<>(); + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + BlockMaskAsset.class, + BlockMaskAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("DontPlace", MaterialSetAsset.CODEC, false), (t, k) -> t.dontPlaceMaterialSetAsset = k, t -> t.dontPlaceMaterialSetAsset) + .add() + .append(new KeyedCodec<>("DontReplace", MaterialSetAsset.CODEC, false), (t, k) -> t.dontReplaceMaterialSetAsset = k, t -> t.dontReplaceMaterialSetAsset) + .add() + .append( + new KeyedCodec<>("Advanced", new ArrayCodec<>(BlockMaskEntryAsset.CODEC, BlockMaskEntryAsset[]::new), false), + (t, k) -> t.blockMaskEntries = k, + t -> t.blockMaskEntries + ) + .add() + .append(new KeyedCodec<>("ExportAs", Codec.STRING, false), (t, k) -> t.exportName = k, t -> t.exportName) + .add() + .append(new KeyedCodec<>("Import", Codec.STRING, false), (t, k) -> t.importName = k, t -> t.importName) + .add() + .afterDecode(asset -> { + if (asset.exportName != null && !asset.exportName.isEmpty()) { + if (exportedNodes.containsKey(asset.exportName)) { + LoggerUtil.getLogger().warning("Duplicate export name for asset: " + asset.exportName); + } + + BlockMaskAsset.Exported exported = new BlockMaskAsset.Exported(); + exported.asset = asset; + exportedNodes.put(asset.exportName, exported); + LoggerUtil.getLogger().fine("Registered imported node asset with name '" + asset.exportName + "' with asset id '" + asset.id); + } + }) + .build(); + private String id; + private AssetExtraInfo.Data data; + protected String exportName = ""; + protected String importName = ""; + private MaterialSetAsset dontPlaceMaterialSetAsset = new MaterialSetAsset(); + private MaterialSetAsset dontReplaceMaterialSetAsset = new MaterialSetAsset(); + private BlockMaskEntryAsset[] blockMaskEntries = new BlockMaskEntryAsset[0]; + + public BlockMaskAsset() { + } + + public BlockMask build(@Nonnull MaterialCache materialCache) { + if (this.importName != null && !this.importName.isEmpty()) { + BlockMaskAsset.Exported importedAssetEntry = exportedNodes.get(this.importName); + if (importedAssetEntry != null && importedAssetEntry.asset != null) { + return importedAssetEntry.asset.build(materialCache); + } else { + LoggerUtil.getLogger().warning("Imported BlockMask asset with name '" + this.importName + "' not found"); + return new BlockMask(); + } + } else { + MaterialSet dontPlaceBlockSet = this.dontPlaceMaterialSetAsset == null ? new MaterialSet() : this.dontPlaceMaterialSetAsset.build(materialCache); + MaterialSet dontReplaceBlockSet = this.dontReplaceMaterialSetAsset == null ? new MaterialSet() : this.dontReplaceMaterialSetAsset.build(materialCache); + BlockMask blockMask = new BlockMask(); + blockMask.setSkippedBlocks(dontPlaceBlockSet); + blockMask.setDefaultMask(dontReplaceBlockSet); + + for (BlockMaskEntryAsset entry : this.blockMaskEntries) { + blockMask.putBlockMaskEntry(entry.getPropBlockSet(materialCache), entry.getReplacesBlockSet(materialCache)); + } + + return blockMask; + } + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + this.dontPlaceMaterialSetAsset.cleanUp(); + this.dontReplaceMaterialSetAsset.cleanUp(); + + for (BlockMaskEntryAsset blockMaskEntryAsset : this.blockMaskEntries) { + blockMaskEntryAsset.cleanUp(); + } + } + + public static class Exported { + public BlockMaskAsset asset; + + public Exported() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/blockmask/BlockMaskEntryAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/blockmask/BlockMaskEntryAsset.java new file mode 100644 index 0000000..f8f4bc0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/blockmask/BlockMaskEntryAsset.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.blockmask; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.MaterialSet; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.assets.blockset.MaterialSetAsset; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import javax.annotation.Nonnull; + +public class BlockMaskEntryAsset implements JsonAssetWithMap>, Cleanable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + BlockMaskEntryAsset.class, + BlockMaskEntryAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Source", MaterialSetAsset.CODEC, true), (t, k) -> t.propBlockSet = k, t -> t.propBlockSet) + .add() + .append(new KeyedCodec<>("CanReplace", MaterialSetAsset.CODEC, true), (t, k) -> t.replacesBlockSet = k, t -> t.replacesBlockSet) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private MaterialSetAsset propBlockSet = new MaterialSetAsset(); + private MaterialSetAsset replacesBlockSet = new MaterialSetAsset(); + + protected BlockMaskEntryAsset() { + } + + public MaterialSet getPropBlockSet(@Nonnull MaterialCache materialCache) { + return this.propBlockSet.build(materialCache); + } + + public MaterialSet getReplacesBlockSet(@Nonnull MaterialCache materialCache) { + return this.replacesBlockSet.build(materialCache); + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + this.propBlockSet.cleanUp(); + this.replacesBlockSet.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/blockset/MaterialSetAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/blockset/MaterialSetAsset.java new file mode 100644 index 0000000..04a63e0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/blockset/MaterialSetAsset.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.blockset; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.MaterialSet; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.assets.material.MaterialAsset; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class MaterialSetAsset implements JsonAssetWithMap>, Cleanable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + MaterialSetAsset.class, + MaterialSetAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Inclusive", Codec.BOOLEAN, false), (t, k) -> t.inclusive = k, t -> t.inclusive) + .add() + .append( + new KeyedCodec<>("Materials", new ArrayCodec<>(MaterialAsset.CODEC, MaterialAsset[]::new), true), + (asset, value) -> asset.materialAssets = value, + asset -> asset.materialAssets + ) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private boolean inclusive = true; + private MaterialAsset[] materialAssets = new MaterialAsset[0]; + + public MaterialSetAsset() { + } + + public MaterialSet build(@Nonnull MaterialCache materialCache) { + ArrayList materials = new ArrayList<>(this.materialAssets.length); + + for (MaterialAsset materialAsset : this.materialAssets) { + if (materialAsset != null) { + materials.add(materialAsset.build(materialCache)); + } + } + + return new MaterialSet(this.inclusive, materials); + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + for (MaterialAsset materialAsset : this.materialAssets) { + materialAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/CeilingCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/CeilingCurveAsset.java new file mode 100644 index 0000000..04eaeb9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/CeilingCurveAsset.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class CeilingCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(CeilingCurveAsset.class, CeilingCurveAsset::new, CurveAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Curve", CurveAsset.CODEC, true), (t, k) -> t.curveAsset = k, k -> k.curveAsset) + .add() + .append(new KeyedCodec<>("Ceiling", Codec.DOUBLE, true), (t, k) -> t.limit = k, k -> k.limit) + .add() + .build(); + private CurveAsset curveAsset = new ConstantCurveAsset(); + private double limit = 0.0; + + public CeilingCurveAsset() { + } + + @Nonnull + @Override + public Double2DoubleFunction build() { + if (this.curveAsset == null) { + return in -> 0.0; + } else { + Double2DoubleFunction curve = this.curveAsset.build(); + return in -> Math.min(this.limit, curve.applyAsDouble(in)); + } + } + + @Override + public void cleanUp() { + this.curveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/ClampCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/ClampCurveAsset.java new file mode 100644 index 0000000..90e844e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/ClampCurveAsset.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class ClampCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(ClampCurveAsset.class, ClampCurveAsset::new, CurveAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Curve", CurveAsset.CODEC, false), (t, k) -> t.curveAsset = k, k -> k.curveAsset) + .add() + .append(new KeyedCodec<>("WallA", Codec.DOUBLE, false), (t, k) -> t.wallA = k, k -> k.wallA) + .add() + .append(new KeyedCodec<>("WallB", Codec.DOUBLE, false), (t, k) -> t.wallB = k, k -> k.wallB) + .add() + .build(); + private CurveAsset curveAsset = new ConstantCurveAsset(); + private double wallA = 1.0; + private double wallB = -1.0; + + public ClampCurveAsset() { + } + + @Nonnull + @Override + public Double2DoubleFunction build() { + double defaultValue = (this.wallA + this.wallB) / 2.0; + if (this.curveAsset == null) { + return in -> defaultValue; + } else { + Double2DoubleFunction inputCurve = this.curveAsset.build(); + return in -> { + double value = inputCurve.applyAsDouble(in); + return Calculator.clamp(this.wallA, value, this.wallB); + }; + } + } + + @Override + public void cleanUp() { + this.curveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/ConstantCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/ConstantCurveAsset.java new file mode 100644 index 0000000..fafeb95 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/ConstantCurveAsset.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class ConstantCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ConstantCurveAsset.class, ConstantCurveAsset::new, CurveAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Value", Codec.DOUBLE, true), (asset, value) -> asset.value = value, asset -> asset.value) + .add() + .build(); + private double value = 0.0; + + public ConstantCurveAsset() { + } + + public ConstantCurveAsset(double value) { + this.value = value; + } + + @Nonnull + @Override + public Double2DoubleFunction build() { + return in -> this.value; + } + + @Override + public void cleanUp() { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/CurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/CurveAsset.java new file mode 100644 index 0000000..c287d03 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/CurveAsset.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; + +public abstract class CurveAsset implements JsonAssetWithMap>, Cleanable { + private static final CurveAsset[] EMPTY_INPUTS = new CurveAsset[0]; + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + private static final Map exportedNodes = new ConcurrentHashMap<>(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(CurveAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(CurveAsset.class) + .append(new KeyedCodec<>("ExportAs", Codec.STRING, false), (t, k) -> t.exportName = k, t -> t.exportName) + .add() + .afterDecode(asset -> { + if (asset.exportName != null && !asset.exportName.isEmpty()) { + if (exportedNodes.containsKey(asset.exportName)) { + LoggerUtil.getLogger().warning("Duplicate export name for asset: " + asset.exportName); + } + + exportedNodes.put(asset.exportName, asset); + LoggerUtil.getLogger().fine("Registered imported node asset with name '" + asset.exportName + "' with asset id '" + asset.id); + } + }) + .build(); + private String id; + private AssetExtraInfo.Data data; + private String exportName = ""; + + protected CurveAsset() { + } + + public abstract Double2DoubleFunction build(); + + public static CurveAsset getExportedAsset(@Nonnull String name) { + return exportedNodes.get(name); + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/DistanceExponentialCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/DistanceExponentialCurveAsset.java new file mode 100644 index 0000000..ae50dbb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/DistanceExponentialCurveAsset.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class DistanceExponentialCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DistanceExponentialCurveAsset.class, DistanceExponentialCurveAsset::new, CurveAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Exponent", Codec.DOUBLE, true), (t, k) -> t.exponent = k, k -> k.exponent) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append(new KeyedCodec<>("Range", Codec.DOUBLE, true), (t, k) -> t.range = k, k -> k.range) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .build(); + private double exponent = 1.0; + private double range = 1.0; + + public DistanceExponentialCurveAsset() { + } + + @Nonnull + @Override + public Double2DoubleFunction build() { + return in -> { + if (in < 0.0) { + return 1.0; + } else if (in > this.range) { + return 0.0; + } else { + in /= this.range; + in *= -1.0; + return Math.pow(++in, this.exponent); + } + }; + } + + @Override + public void cleanUp() { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/DistanceSCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/DistanceSCurveAsset.java new file mode 100644 index 0000000..469dfb9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/DistanceSCurveAsset.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.InterpolatedCurve; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class DistanceSCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DistanceSCurveAsset.class, DistanceSCurveAsset::new, CurveAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("ExponentA", Codec.DOUBLE, true), (t, k) -> t.exponentA = k, k -> k.exponentA) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append(new KeyedCodec<>("ExponentB", Codec.DOUBLE, true), (t, k) -> t.exponentB = k, k -> k.exponentB) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append(new KeyedCodec<>("Transition", Codec.DOUBLE, false), (t, k) -> t.transition = k, k -> k.transition) + .addValidator(Validators.range(0.0, 1.0)) + .add() + .append(new KeyedCodec<>("Range", Codec.DOUBLE, true), (t, k) -> t.range = k, k -> k.range) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append(new KeyedCodec<>("TransitionSmooth", Codec.DOUBLE, false), (t, k) -> t.transitionSmooth = k, k -> k.transitionSmooth) + .addValidator(Validators.range(0.0, 1.0)) + .add() + .build(); + private double exponentA = 1.0; + private double exponentB = 1.0; + private double range = 1.0; + private double transition = 1.0; + private double transitionSmooth = 1.0; + + public DistanceSCurveAsset() { + } + + @Nonnull + @Override + public Double2DoubleFunction build() { + Double2DoubleFunction functionA = in -> { + if (in >= this.range) { + return 0.0; + } else { + in /= this.range; + in *= -1.0; + return Math.pow(++in, this.exponentA); + } + }; + Double2DoubleFunction functionB = in -> { + if (in >= this.range) { + return 0.0; + } else { + in /= this.range; + in *= -1.0; + return Math.pow(++in, this.exponentB); + } + }; + double transitionDistance = this.transition * this.range; + double positionA = this.range / 2.0 - transitionDistance / 2.0; + double positionB = positionA + transitionDistance; + return new InterpolatedCurve(positionA, positionB, this.transitionSmooth, functionA, functionB); + } + + @Override + public void cleanUp() { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/FloorCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/FloorCurveAsset.java new file mode 100644 index 0000000..331a63f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/FloorCurveAsset.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class FloorCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(FloorCurveAsset.class, FloorCurveAsset::new, CurveAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Curve", CurveAsset.CODEC, true), (t, k) -> t.curveAsset = k, k -> k.curveAsset) + .add() + .append(new KeyedCodec<>("Floor", Codec.DOUBLE, true), (t, k) -> t.limit = k, k -> k.limit) + .add() + .build(); + private CurveAsset curveAsset = new ConstantCurveAsset(); + private double limit = 0.0; + + public FloorCurveAsset() { + } + + @Nonnull + @Override + public Double2DoubleFunction build() { + if (this.curveAsset == null) { + return in -> 0.0; + } else { + Double2DoubleFunction curve = this.curveAsset.build(); + return in -> Math.max(this.limit, curve.applyAsDouble(in)); + } + } + + @Override + public void cleanUp() { + this.curveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/ImportedCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/ImportedCurveAsset.java new file mode 100644 index 0000000..b3afd2c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/ImportedCurveAsset.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.logger.HytaleLogger; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; + +public class ImportedCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ImportedCurveAsset.class, ImportedCurveAsset::new, CurveAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Name", Codec.STRING, true), (t, k) -> t.name = k, k -> k.name) + .add() + .build(); + private String name; + + public ImportedCurveAsset() { + } + + @Override + public Double2DoubleFunction build() { + if (this.name != null && !this.name.isEmpty()) { + CurveAsset exportedAsset = CurveAsset.getExportedAsset(this.name); + return exportedAsset == null ? in -> 0.0 : exportedAsset.build(); + } else { + HytaleLogger.getLogger().atWarning().log("An exported Curve with the name does not exist: " + this.name); + return in -> 0.0; + } + } + + @Override + public void cleanUp() { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/InverterCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/InverterCurveAsset.java new file mode 100644 index 0000000..972ecfb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/InverterCurveAsset.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class InverterCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + InverterCurveAsset.class, InverterCurveAsset::new, CurveAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Curve", CurveAsset.CODEC, true), (t, k) -> t.curveAsset = k, k -> k.curveAsset) + .add() + .build(); + private CurveAsset curveAsset = new ConstantCurveAsset(); + + public InverterCurveAsset() { + } + + @Nonnull + @Override + public Double2DoubleFunction build() { + if (this.curveAsset == null) { + return in -> 0.0; + } else { + Double2DoubleFunction inputCurve = this.curveAsset.build(); + return in -> -inputCurve.applyAsDouble(in); + } + } + + @Override + public void cleanUp() { + this.curveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/MaxCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/MaxCurveAsset.java new file mode 100644 index 0000000..50c350f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/MaxCurveAsset.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class MaxCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(MaxCurveAsset.class, MaxCurveAsset::new, CurveAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Curves", new ArrayCodec<>(CurveAsset.CODEC, CurveAsset[]::new), true), (t, k) -> t.curveAssets = k, k -> k.curveAssets) + .add() + .build(); + private CurveAsset[] curveAssets = new CurveAsset[0]; + + public MaxCurveAsset() { + } + + @Nonnull + @Override + public Double2DoubleFunction build() { + if (this.curveAssets.length == 0) { + return in -> 0.0; + } else { + Double2DoubleFunction[] inputCurves = new Double2DoubleFunction[this.curveAssets.length]; + + for (int i = 0; i < this.curveAssets.length; i++) { + inputCurves[i] = this.curveAssets[i].build(); + } + + if (inputCurves.length == 1) { + Double2DoubleFunction curve = inputCurves[0]; + return curve::applyAsDouble; + } else if (inputCurves.length == 2) { + Double2DoubleFunction curveA = inputCurves[0]; + Double2DoubleFunction curveB = inputCurves[1]; + return in -> Math.max(curveA.applyAsDouble(in), curveB.applyAsDouble(in)); + } else if (inputCurves.length == 3) { + Double2DoubleFunction curveA = inputCurves[0]; + Double2DoubleFunction curveB = inputCurves[1]; + Double2DoubleFunction curveC = inputCurves[2]; + return in -> Math.max(Math.max(curveA.applyAsDouble(in), curveB.applyAsDouble(in)), curveC.applyAsDouble(in)); + } else { + return in -> { + double value = inputCurves[0].applyAsDouble(in); + + for (int i = 1; i < inputCurves.length; i++) { + value = Math.max(value, inputCurves[i].applyAsDouble(in)); + } + + return value; + }; + } + } + } + + @Override + public void cleanUp() { + for (CurveAsset curveAsset : this.curveAssets) { + curveAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/MinCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/MinCurveAsset.java new file mode 100644 index 0000000..655ffe3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/MinCurveAsset.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class MinCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(MinCurveAsset.class, MinCurveAsset::new, CurveAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Curves", new ArrayCodec<>(CurveAsset.CODEC, CurveAsset[]::new), true), (t, k) -> t.curveAssets = k, k -> k.curveAssets) + .add() + .build(); + private CurveAsset[] curveAssets = new CurveAsset[0]; + + public MinCurveAsset() { + } + + @Nonnull + @Override + public Double2DoubleFunction build() { + if (this.curveAssets.length == 0) { + return in -> 0.0; + } else { + Double2DoubleFunction[] inputCurves = new Double2DoubleFunction[this.curveAssets.length]; + + for (int i = 0; i < this.curveAssets.length; i++) { + inputCurves[i] = this.curveAssets[i].build(); + } + + if (inputCurves.length == 1) { + Double2DoubleFunction curve = inputCurves[0]; + return curve::applyAsDouble; + } else if (inputCurves.length == 2) { + Double2DoubleFunction curveA = inputCurves[0]; + Double2DoubleFunction curveB = inputCurves[1]; + return in -> Math.min(curveA.applyAsDouble(in), curveB.applyAsDouble(in)); + } else if (inputCurves.length == 3) { + Double2DoubleFunction curveA = inputCurves[0]; + Double2DoubleFunction curveB = inputCurves[1]; + Double2DoubleFunction curveC = inputCurves[2]; + return in -> Math.min(Math.min(curveA.applyAsDouble(in), curveB.applyAsDouble(in)), curveC.applyAsDouble(in)); + } else { + return in -> { + double value = inputCurves[0].applyAsDouble(in); + + for (int i = 1; i < inputCurves.length; i++) { + value = Math.min(value, inputCurves[i].applyAsDouble(in)); + } + + return value; + }; + } + } + } + + @Override + public void cleanUp() { + for (CurveAsset curveAsset : this.curveAssets) { + curveAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/MultiplierCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/MultiplierCurveAsset.java new file mode 100644 index 0000000..d22964e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/MultiplierCurveAsset.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class MultiplierCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + MultiplierCurveAsset.class, MultiplierCurveAsset::new, CurveAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Curves", new ArrayCodec<>(CurveAsset.CODEC, CurveAsset[]::new), true), (t, k) -> t.curveAssets = k, k -> k.curveAssets) + .add() + .build(); + private CurveAsset[] curveAssets = new CurveAsset[0]; + + public MultiplierCurveAsset() { + } + + @Nonnull + @Override + public Double2DoubleFunction build() { + if (this.curveAssets.length == 0) { + return in -> 0.0; + } else { + Double2DoubleFunction[] inputCurves = new Double2DoubleFunction[this.curveAssets.length]; + + for (int i = 0; i < this.curveAssets.length; i++) { + inputCurves[i] = this.curveAssets[i].build(); + } + + if (inputCurves.length == 1) { + Double2DoubleFunction curve = inputCurves[0]; + return curve::applyAsDouble; + } else if (inputCurves.length == 2) { + Double2DoubleFunction curveA = inputCurves[0]; + Double2DoubleFunction curveB = inputCurves[1]; + return in -> curveA.applyAsDouble(in) * curveB.applyAsDouble(in); + } else if (inputCurves.length == 3) { + Double2DoubleFunction curveA = inputCurves[0]; + Double2DoubleFunction curveB = inputCurves[1]; + Double2DoubleFunction curveC = inputCurves[2]; + return in -> curveA.applyAsDouble(in) * curveB.applyAsDouble(in) * curveC.applyAsDouble(in); + } else { + return in -> { + double value = inputCurves[0].applyAsDouble(in); + + for (int i = 1; i < inputCurves.length; i++) { + value *= inputCurves[i].applyAsDouble(in); + } + + return value; + }; + } + } + } + + @Override + public void cleanUp() { + for (CurveAsset curveAsset : this.curveAssets) { + curveAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/NotCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/NotCurveAsset.java new file mode 100644 index 0000000..bd08ba5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/NotCurveAsset.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class NotCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(NotCurveAsset.class, NotCurveAsset::new, CurveAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Curve", CurveAsset.CODEC, true), (t, k) -> t.curveAsset = k, k -> k.curveAsset) + .add() + .build(); + private CurveAsset curveAsset = new ConstantCurveAsset(); + + public NotCurveAsset() { + } + + @Nonnull + @Override + public Double2DoubleFunction build() { + if (this.curveAsset == null) { + return in -> 1.0; + } else { + Double2DoubleFunction inputCurve = this.curveAsset.build(); + return in -> { + double value = inputCurve.applyAsDouble(in); + return --value * -1.0; + }; + } + } + + @Override + public void cleanUp() { + this.curveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SmoothCeilingCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SmoothCeilingCurveAsset.java new file mode 100644 index 0000000..af63bce --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SmoothCeilingCurveAsset.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class SmoothCeilingCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SmoothCeilingCurveAsset.class, SmoothCeilingCurveAsset::new, CurveAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Curve", CurveAsset.CODEC, true), (t, k) -> t.curveAsset = k, k -> k.curveAsset) + .add() + .append(new KeyedCodec<>("Range", Codec.DOUBLE, true), (t, k) -> t.range = k, k -> k.range) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append(new KeyedCodec<>("Ceiling", Codec.DOUBLE, true), (t, k) -> t.limit = k, k -> k.limit) + .add() + .build(); + private CurveAsset curveAsset = new ConstantCurveAsset(); + private double range = 0.0; + private double limit = 0.0; + + public SmoothCeilingCurveAsset() { + } + + @Nonnull + @Override + public Double2DoubleFunction build() { + if (this.curveAsset == null) { + return in -> 0.0; + } else { + Double2DoubleFunction curve = this.curveAsset.build(); + return in -> Calculator.smoothMin(this.range, this.limit, curve.applyAsDouble(in)); + } + } + + @Override + public void cleanUp() { + this.curveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SmoothClampCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SmoothClampCurveAsset.java new file mode 100644 index 0000000..6113c76 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SmoothClampCurveAsset.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class SmoothClampCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SmoothClampCurveAsset.class, SmoothClampCurveAsset::new, CurveAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Curve", CurveAsset.CODEC, false), (t, k) -> t.curveAsset = k, k -> k.curveAsset) + .add() + .append(new KeyedCodec<>("WallA", Codec.DOUBLE, false), (t, k) -> t.wallA = k, k -> k.wallA) + .add() + .append(new KeyedCodec<>("WallB", Codec.DOUBLE, false), (t, k) -> t.wallB = k, k -> k.wallB) + .add() + .append(new KeyedCodec<>("Range", Codec.DOUBLE, false), (t, k) -> t.range = k, k -> k.range) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .build(); + private CurveAsset curveAsset = new ConstantCurveAsset(); + private double wallA = 1.0; + private double wallB = -1.0; + private double range = 0.0; + + public SmoothClampCurveAsset() { + } + + @Nonnull + @Override + public Double2DoubleFunction build() { + double defaultValue = (this.wallA + this.wallB) / 2.0; + if (this.curveAsset == null) { + return in -> defaultValue; + } else { + double min = Math.min(this.wallA, this.wallB); + double max = Math.max(this.wallA, this.wallB); + Double2DoubleFunction inputCurve = this.curveAsset.build(); + return this.range == 0.0 ? in -> Calculator.clamp(this.wallA, inputCurve.applyAsDouble(in), this.wallB) : in -> { + double value = inputCurve.applyAsDouble(in); + double smoothedMax = Calculator.smoothMax(this.range, min, value); + double smoothedMin = Calculator.smoothMin(this.range, max, value); + return (smoothedMin + smoothedMax) / 2.0; + }; + } + } + + @Override + public void cleanUp() { + this.curveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SmoothFloorCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SmoothFloorCurveAsset.java new file mode 100644 index 0000000..0b0d9e2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SmoothFloorCurveAsset.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class SmoothFloorCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SmoothFloorCurveAsset.class, SmoothFloorCurveAsset::new, CurveAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Curve", CurveAsset.CODEC, true), (t, k) -> t.curveAsset = k, k -> k.curveAsset) + .add() + .append(new KeyedCodec<>("Range", Codec.DOUBLE, true), (t, k) -> t.range = k, k -> k.range) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append(new KeyedCodec<>("Floor", Codec.DOUBLE, true), (t, k) -> t.limit = k, k -> k.limit) + .add() + .build(); + private CurveAsset curveAsset = new ConstantCurveAsset(); + private double range = 0.0; + private double limit = 0.0; + + public SmoothFloorCurveAsset() { + } + + @Nonnull + @Override + public Double2DoubleFunction build() { + if (this.curveAsset == null) { + return in -> 0.0; + } else { + Double2DoubleFunction curve = this.curveAsset.build(); + return in -> Calculator.smoothMax(this.range, this.limit, curve.applyAsDouble(in)); + } + } + + @Override + public void cleanUp() { + this.curveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SmoothMaxCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SmoothMaxCurveAsset.java new file mode 100644 index 0000000..02ea892 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SmoothMaxCurveAsset.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class SmoothMaxCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SmoothMaxCurveAsset.class, SmoothMaxCurveAsset::new, CurveAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("CurveA", CurveAsset.CODEC, true), (t, k) -> t.curveAAsset = k, k -> k.curveAAsset) + .add() + .append(new KeyedCodec<>("CurveB", CurveAsset.CODEC, true), (t, k) -> t.curveBAsset = k, k -> k.curveBAsset) + .add() + .append(new KeyedCodec<>("Range", Codec.DOUBLE, true), (t, k) -> t.range = k, k -> k.range) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .build(); + private CurveAsset curveAAsset = new ConstantCurveAsset(); + private CurveAsset curveBAsset = new ConstantCurveAsset(); + private double range = 0.0; + + public SmoothMaxCurveAsset() { + } + + @Nonnull + @Override + public Double2DoubleFunction build() { + if (this.curveAAsset != null && this.curveBAsset != null) { + Double2DoubleFunction curveA = this.curveAAsset.build(); + Double2DoubleFunction curveB = this.curveBAsset.build(); + return in -> Calculator.smoothMax(this.range, curveA.applyAsDouble(in), curveB.applyAsDouble(in)); + } else { + return in -> 0.0; + } + } + + @Override + public void cleanUp() { + this.curveAAsset.cleanUp(); + this.curveBAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SmoothMinCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SmoothMinCurveAsset.java new file mode 100644 index 0000000..3398946 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SmoothMinCurveAsset.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class SmoothMinCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SmoothMinCurveAsset.class, SmoothMinCurveAsset::new, CurveAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("CurveA", CurveAsset.CODEC, true), (t, k) -> t.curveAAsset = k, k -> k.curveAAsset) + .add() + .append(new KeyedCodec<>("CurveB", CurveAsset.CODEC, true), (t, k) -> t.curveBAsset = k, k -> k.curveBAsset) + .add() + .append(new KeyedCodec<>("Range", Codec.DOUBLE, true), (t, k) -> t.range = k, k -> k.range) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .build(); + private CurveAsset curveAAsset = new ConstantCurveAsset(); + private CurveAsset curveBAsset = new ConstantCurveAsset(); + private double range = 0.0; + + public SmoothMinCurveAsset() { + } + + @Nonnull + @Override + public Double2DoubleFunction build() { + if (this.curveAAsset != null && this.curveBAsset != null) { + Double2DoubleFunction curveA = this.curveAAsset.build(); + Double2DoubleFunction curveB = this.curveBAsset.build(); + return in -> Calculator.smoothMin(this.range, curveA.applyAsDouble(in), curveB.applyAsDouble(in)); + } else { + return in -> 0.0; + } + } + + @Override + public void cleanUp() { + this.curveAAsset.cleanUp(); + this.curveBAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SumCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SumCurveAsset.java new file mode 100644 index 0000000..fe2cce8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/SumCurveAsset.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class SumCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(SumCurveAsset.class, SumCurveAsset::new, CurveAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Curves", new ArrayCodec<>(CurveAsset.CODEC, CurveAsset[]::new), true), (t, k) -> t.curveAssets = k, k -> k.curveAssets) + .add() + .build(); + private CurveAsset[] curveAssets = new CurveAsset[0]; + + public SumCurveAsset() { + } + + @Nonnull + @Override + public Double2DoubleFunction build() { + if (this.curveAssets.length == 0) { + return in -> 0.0; + } else { + Double2DoubleFunction[] inputCurves = new Double2DoubleFunction[this.curveAssets.length]; + + for (int i = 0; i < this.curveAssets.length; i++) { + inputCurves[i] = this.curveAssets[i].build(); + } + + if (inputCurves.length == 1) { + Double2DoubleFunction curve = inputCurves[0]; + return curve::applyAsDouble; + } else if (inputCurves.length == 2) { + Double2DoubleFunction curveA = inputCurves[0]; + Double2DoubleFunction curveB = inputCurves[1]; + return in -> curveA.applyAsDouble(in) + curveB.applyAsDouble(in); + } else if (inputCurves.length == 3) { + Double2DoubleFunction curveA = inputCurves[0]; + Double2DoubleFunction curveB = inputCurves[1]; + Double2DoubleFunction curveC = inputCurves[2]; + return in -> curveA.applyAsDouble(in) + curveB.applyAsDouble(in) + curveC.applyAsDouble(in); + } else { + return in -> { + double value = 0.0; + + for (Double2DoubleFunction curve : inputCurves) { + value += curve.applyAsDouble(in); + } + + return value; + }; + } + } + } + + @Override + public void cleanUp() { + for (CurveAsset curveAsset : this.curveAssets) { + curveAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/legacy/NodeFunctionYOutAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/legacy/NodeFunctionYOutAsset.java new file mode 100644 index 0000000..1dcae9f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/legacy/NodeFunctionYOutAsset.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves.legacy; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.NodeFunction; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.math.vector.Vector2d; +import java.util.HashSet; +import javax.annotation.Nonnull; + +public class NodeFunctionYOutAsset implements JsonAssetWithMap>, Cleanable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + NodeFunctionYOutAsset.class, + NodeFunctionYOutAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Points", new ArrayCodec<>(PointYOutAsset.CODEC, PointYOutAsset[]::new), true), (t, k) -> t.nodes = k, t -> t.nodes) + .addValidator((LegacyValidator)((v, r) -> { + HashSet ySet = new HashSet<>(v.length); + + for (PointYOutAsset point : v) { + if (ySet.contains(point.getY())) { + r.fail("More than one point with Y value: " + point.getY()); + return; + } + + ySet.add(point.getY()); + } + })) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private PointYOutAsset[] nodes = new PointYOutAsset[0]; + + public NodeFunctionYOutAsset() { + } + + @Nonnull + public NodeFunction build() { + NodeFunction nodeFunction = new NodeFunction(); + + for (PointYOutAsset node : this.nodes) { + Vector2d point = node.build(); + nodeFunction.addPoint(point.x, point.y); + } + + return nodeFunction; + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/legacy/PointYOutAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/legacy/PointYOutAsset.java new file mode 100644 index 0000000..ad53d43 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/legacy/PointYOutAsset.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves.legacy; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.math.vector.Vector2d; +import javax.annotation.Nonnull; + +public class PointYOutAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + PointYOutAsset.class, + PointYOutAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Y", Codec.DOUBLE, true), (t, y) -> t.y = y, t -> t.y) + .add() + .append(new KeyedCodec<>("Out", Codec.DOUBLE, true), (t, out) -> t.out = out, t -> t.out) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private double y = 0.0; + private double out = 0.0; + + private PointYOutAsset() { + } + + @Nonnull + public Vector2d build() { + return new Vector2d(this.y, this.out); + } + + public double getY() { + return this.y; + } + + public double getOut() { + return this.out; + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/manual/ManualCurveAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/manual/ManualCurveAsset.java new file mode 100644 index 0000000..80c003c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/manual/ManualCurveAsset.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves.manual; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.CurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.NodeFunction; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.math.vector.Vector2d; +import java.util.HashSet; +import javax.annotation.Nonnull; + +public class ManualCurveAsset extends CurveAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(ManualCurveAsset.class, ManualCurveAsset::new, CurveAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Points", new ArrayCodec<>(PointInOutAsset.CODEC, PointInOutAsset[]::new), true), (t, k) -> t.nodes = k, t -> t.nodes) + .addValidator((LegacyValidator)((v, r) -> { + HashSet ySet = new HashSet<>(v.length); + + for (PointInOutAsset point : v) { + if (ySet.contains(point.getY())) { + r.fail("More than one point with Y value: " + point.getY()); + return; + } + + ySet.add(point.getY()); + } + })) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private PointInOutAsset[] nodes = new PointInOutAsset[0]; + + private ManualCurveAsset() { + } + + @Nonnull + public NodeFunction build() { + NodeFunction nodeFunction = new NodeFunction(); + + for (PointInOutAsset node : this.nodes) { + Vector2d point = node.build(); + nodeFunction.addPoint(point.x, point.y); + } + + return nodeFunction; + } + + @Override + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/manual/PointInOutAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/manual/PointInOutAsset.java new file mode 100644 index 0000000..590c765 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/curves/manual/PointInOutAsset.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.curves.manual; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.math.vector.Vector2d; +import javax.annotation.Nonnull; + +public class PointInOutAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + PointInOutAsset.class, + PointInOutAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("In", Codec.DOUBLE, true), (t, y) -> t.y = y, t -> t.y) + .add() + .append(new KeyedCodec<>("Out", Codec.DOUBLE, true), (t, out) -> t.out = out, t -> t.out) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private double y = 0.0; + private double out = 0.0; + + private PointInOutAsset() { + } + + @Nonnull + public Vector2d build() { + return new Vector2d(this.y, this.out); + } + + public double getY() { + return this.y; + } + + public double getOut() { + return this.out; + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/delimiters/RangeDoubleAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/delimiters/RangeDoubleAsset.java new file mode 100644 index 0000000..df98b02 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/delimiters/RangeDoubleAsset.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.delimiters; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.delimiters.RangeDouble; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import javax.annotation.Nonnull; + +public class RangeDoubleAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + RangeDoubleAsset.class, + RangeDoubleAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("MinInclusive", Codec.DOUBLE, true), (t, value) -> t.minInclusive = value, t -> t.minInclusive) + .add() + .append(new KeyedCodec<>("MaxExclusive", Codec.DOUBLE, true), (t, value) -> t.maxExclusive = value, t -> t.maxExclusive) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private double minInclusive = 0.0; + private double maxExclusive = 0.0; + + public RangeDoubleAsset() { + } + + @Nonnull + public RangeDouble build() { + return new RangeDouble(this.minInclusive, this.maxExclusive); + } + + @Nonnull + public String getId() { + return ""; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/delimiters/RangeIntAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/delimiters/RangeIntAsset.java new file mode 100644 index 0000000..cb49f60 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/delimiters/RangeIntAsset.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.delimiters; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.delimiters.RangeInt; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import javax.annotation.Nonnull; + +public class RangeIntAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + RangeIntAsset.class, + RangeIntAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("MinInclusive", Codec.INTEGER, true), (t, value) -> t.minInclusive = value, t -> t.minInclusive) + .add() + .append(new KeyedCodec<>("MaxExclusive", Codec.INTEGER, true), (t, value) -> t.maxExclusive = value, t -> t.maxExclusive) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private int minInclusive = 0; + private int maxExclusive = 0; + + public RangeIntAsset() { + } + + @Nonnull + public RangeInt build() { + return new RangeInt(this.minInclusive, this.maxExclusive); + } + + @Nonnull + public String getId() { + return ""; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AbsDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AbsDensityAsset.java new file mode 100644 index 0000000..f8c96ad --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AbsDensityAsset.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.AbsDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class AbsDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(AbsDensityAsset.class, AbsDensityAsset::new, DensityAsset.ABSTRACT_CODEC) + .build(); + + public AbsDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new AbsDensity(this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AmplitudeConstantAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AmplitudeConstantAsset.java new file mode 100644 index 0000000..bdd7e84 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AmplitudeConstantAsset.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.AmplitudeConstantDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class AmplitudeConstantAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + AmplitudeConstantAsset.class, AmplitudeConstantAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Value", Codec.DOUBLE, true), (t, k) -> t.value = k, t -> t.value) + .add() + .build(); + private double value = 0.0; + + public AmplitudeConstantAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new AmplitudeConstantDensity(this.value, this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AmplitudeDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AmplitudeDensityAsset.java new file mode 100644 index 0000000..5ea0094 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AmplitudeDensityAsset.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.legacy.NodeFunctionYOutAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.AmplitudeDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class AmplitudeDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + AmplitudeDensityAsset.class, AmplitudeDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("FunctionForY", NodeFunctionYOutAsset.CODEC, true), (t, k) -> t.nodeFunctionYOutAsset = k, k -> k.nodeFunctionYOutAsset) + .add() + .build(); + private NodeFunctionYOutAsset nodeFunctionYOutAsset = new NodeFunctionYOutAsset(); + + public AmplitudeDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() + ? new ConstantValueDensity(0.0) + : new AmplitudeDensity(this.nodeFunctionYOutAsset.build(), this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + this.nodeFunctionYOutAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AnchorDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AnchorDensityAsset.java new file mode 100644 index 0000000..ac2c9ea --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AnchorDensityAsset.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.AnchorDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class AnchorDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + AnchorDensityAsset.class, AnchorDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Reversed", Codec.BOOLEAN, false), (t, k) -> t.isReversed = k, k -> k.isReversed) + .add() + .build(); + private boolean isReversed = false; + + public AnchorDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new AnchorDensity(this.buildFirstInput(argument), this.isReversed)); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AngleDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AngleDensityAsset.java new file mode 100644 index 0000000..d846c13 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AngleDensityAsset.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.vectorproviders.ConstantVectorProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.vectorproviders.VectorProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.AngleDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.vectorproviders.VectorProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class AngleDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + AngleDensityAsset.class, AngleDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>("VectorProvider", VectorProviderAsset.CODEC, true), + (asset, value) -> asset.vectorProviderAsset = value, + value -> value.vectorProviderAsset + ) + .add() + .append(new KeyedCodec<>("Vector", Vector3d.CODEC, true), (asset, value) -> asset.vector = value, asset -> asset.vector) + .add() + .append(new KeyedCodec<>("IsAxis", Codec.BOOLEAN, true), (asset, value) -> asset.isAxis = value, asset -> asset.isAxis) + .add() + .build(); + private VectorProviderAsset vectorProviderAsset = new ConstantVectorProviderAsset(); + private Vector3d vector = new Vector3d(); + private boolean isAxis = false; + + public AngleDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else { + VectorProvider vectorProvider = this.vectorProviderAsset.build(new VectorProviderAsset.Argument(argument)); + return new AngleDensity(vectorProvider, this.vector, this.isAxis); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + this.vectorProviderAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AxisDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AxisDensityAsset.java new file mode 100644 index 0000000..df29a24 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/AxisDensityAsset.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.ConstantCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.CurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.AxisDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class AxisDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(AxisDensityAsset.class, AxisDensityAsset::new, DensityAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Curve", CurveAsset.CODEC, true), (t, k) -> t.distanceCurveAsset = k, k -> k.distanceCurveAsset) + .add() + .append(new KeyedCodec<>("IsAnchored", Codec.BOOLEAN, false), (t, k) -> t.isAnchored = k, k -> k.isAnchored) + .add() + .append(new KeyedCodec<>("Axis", Vector3d.CODEC, false), (t, k) -> t.axis = k, k -> k.axis) + .addValidator((LegacyValidator)((v, r) -> { + if (v.length() == 0.0) { + r.fail("Axis can't be a zero vector."); + } + })) + .add() + .build(); + private CurveAsset distanceCurveAsset = new ConstantCurveAsset(); + private Vector3d axis = new Vector3d(0.0, 1.0, 0.0); + private boolean isAnchored = false; + + public AxisDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(!this.isSkipped() && this.distanceCurveAsset != null + ? new AxisDensity(this.distanceCurveAsset.build(), this.axis, this.isAnchored) + : new ConstantValueDensity(0.0)); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + this.distanceCurveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/BaseHeightDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/BaseHeightDensityAsset.java new file mode 100644 index 0000000..a569bd0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/BaseHeightDensityAsset.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.BaseHeightDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.BiDouble2DoubleFunction; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.BaseHeightReference; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.logger.HytaleLogger; +import javax.annotation.Nonnull; + +public class BaseHeightDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + BaseHeightDensityAsset.class, BaseHeightDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("BaseHeightName", Codec.STRING, false), (t, k) -> t.baseHeightName = k, t -> t.baseHeightName) + .add() + .append(new KeyedCodec<>("Distance", Codec.BOOLEAN, false), (t, k) -> t.isDistance = k, t -> t.isDistance) + .add() + .build(); + private String baseHeightName = ""; + private boolean isDistance = false; + + public BaseHeightDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else { + BaseHeightReference heightDataLayer = argument.referenceBundle.getLayerWithName(this.baseHeightName, BaseHeightReference.class); + if (heightDataLayer == null) { + HytaleLogger.getLogger() + .atConfig() + .log("Couldn't find height data layer with name \"" + this.baseHeightName + "\", using a zero-constant Density node."); + return new ConstantValueDensity(0.0); + } else { + BiDouble2DoubleFunction yFunction = heightDataLayer.getHeightFunction(); + return new BaseHeightDensity(yFunction, this.isDistance); + } + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/Cache2dDensityAsset_Deprecated.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/Cache2dDensityAsset_Deprecated.java new file mode 100644 index 0000000..7cab7b3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/Cache2dDensityAsset_Deprecated.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.MultiCacheDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.YOverrideDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class Cache2dDensityAsset_Deprecated extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + Cache2dDensityAsset_Deprecated.class, Cache2dDensityAsset_Deprecated::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Y", Codec.DOUBLE, false), (t, k) -> t.y = k, t -> t.y) + .add() + .build(); + private double y = 0.0; + + public Cache2dDensityAsset_Deprecated() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + DensityAsset inputAsset = this.firstInput(); + if (inputAsset != null && !this.isSkipped() && !inputAsset.isSkipped()) { + Density input = this.buildFirstInput(argument); + if (input == null) { + return new ConstantValueDensity(0.0); + } else { + Density cacheDensity = new MultiCacheDensity(input, argument.workerIndexer.getWorkerCount(), CacheDensityAsset.DEFAULT_CAPACITY); + return new YOverrideDensity(cacheDensity, this.y); + } + } else { + return new ConstantValueDensity(0.0); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CacheDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CacheDensityAsset.java new file mode 100644 index 0000000..c22ff74 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CacheDensityAsset.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.CacheDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.MultiCacheDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class CacheDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CacheDensityAsset.class, CacheDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Capacity", Codec.INTEGER, true), (asset, value) -> asset.capacity = value, asset -> asset.capacity) + .addValidator(Validators.greaterThanOrEqual(0)) + .add() + .build(); + public static int DEFAULT_CAPACITY = 3; + private int capacity = DEFAULT_CAPACITY; + + public CacheDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.capacity <= 0) { + return this.build(argument); + } else { + return (Density)(this.capacity == 1 + ? new CacheDensity(this.buildFirstInput(argument), argument.workerIndexer.getWorkerCount()) + : new MultiCacheDensity(this.buildFirstInput(argument), argument.workerIndexer.getWorkerCount(), this.capacity)); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CeilingDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CeilingDensityAsset.java new file mode 100644 index 0000000..a05faea --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CeilingDensityAsset.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.CeilingDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class CeilingDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CeilingDensityAsset.class, CeilingDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Limit", Codec.DOUBLE, true), (t, k) -> t.limit = k, k -> k.limit) + .add() + .build(); + private double limit = 0.0; + + public CeilingDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new CeilingDensity(this.limit, this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CellNoise2DDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CellNoise2DDensityAsset.java new file mode 100644 index 0000000..745aec7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CellNoise2DDensityAsset.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.MultiCacheDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.Noise2dDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.YOverrideDensity; +import com.hypixel.hytale.builtin.hytalegenerator.fields.FastNoiseLite; +import com.hypixel.hytale.builtin.hytalegenerator.fields.noise.CellNoiseField; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nonnull; + +public class CellNoise2DDensityAsset extends DensityAsset { + private static Set validCellTypes = new HashSet<>(); + public static final BuilderCodec CODEC; + private double scaleX = 1.0; + private double scaleZ = 1.0; + private double jitter = 0.5; + private int octaves = 1; + private String seedKey = "A"; + private FastNoiseLite.CellularReturnType cellType = FastNoiseLite.CellularReturnType.CellValue; + + public CellNoise2DDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else { + SeedBox childSeed = argument.parentSeed.child(this.seedKey); + CellNoiseField noise = new CellNoiseField(childSeed.createSupplier().get(), this.scaleX, 1.0, this.scaleZ, this.jitter, this.octaves, this.cellType); + Noise2dDensity noiseDensity = new Noise2dDensity(noise); + Density cacheDensity = new MultiCacheDensity(noiseDensity, argument.workerIndexer.getWorkerCount(), CacheDensityAsset.DEFAULT_CAPACITY); + return new YOverrideDensity(cacheDensity, 0.0); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } + + static { + for (FastNoiseLite.CellularReturnType e : FastNoiseLite.CellularReturnType.values()) { + validCellTypes.add(e.toString()); + } + + CODEC = BuilderCodec.builder(CellNoise2DDensityAsset.class, CellNoise2DDensityAsset::new, DensityAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("ScaleX", Codec.DOUBLE, true), (asset, scale) -> asset.scaleX = scale, asset -> asset.scaleX) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("ScaleZ", Codec.DOUBLE, true), (asset, scale) -> asset.scaleZ = scale, asset -> asset.scaleZ) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("Jitter", Codec.DOUBLE, true), (asset, v) -> asset.jitter = v, asset -> asset.scaleZ) + .add() + .append(new KeyedCodec<>("Octaves", Codec.INTEGER, true), (asset, octaves) -> asset.octaves = octaves, asset -> asset.octaves) + .addValidator(Validators.greaterThan(0)) + .add() + .append(new KeyedCodec<>("Seed", Codec.STRING, true), (asset, seed) -> asset.seedKey = seed, asset -> asset.seedKey) + .add() + .append( + new KeyedCodec<>("CellType", FastNoiseLite.CellularReturnType.CODEC, true), (asset, cellType) -> asset.cellType = cellType, asset -> asset.cellType + ) + .add() + .build(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CellNoise3DDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CellNoise3DDensityAsset.java new file mode 100644 index 0000000..1482c2c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CellNoise3DDensityAsset.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.Noise3dDensity; +import com.hypixel.hytale.builtin.hytalegenerator.fields.FastNoiseLite; +import com.hypixel.hytale.builtin.hytalegenerator.fields.noise.CellNoiseField; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class CellNoise3DDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CellNoise3DDensityAsset.class, CellNoise3DDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("ScaleX", Codec.DOUBLE, true), (asset, scale) -> asset.scaleX = scale, asset -> asset.scaleX) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("ScaleY", Codec.DOUBLE, true), (asset, scale) -> asset.scaleY = scale, asset -> asset.scaleY) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("ScaleZ", Codec.DOUBLE, true), (asset, scale) -> asset.scaleZ = scale, asset -> asset.scaleZ) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("Jitter", Codec.DOUBLE, true), (asset, v) -> asset.jitter = v, asset -> asset.scaleZ) + .add() + .append(new KeyedCodec<>("Octaves", Codec.INTEGER, true), (asset, octaves) -> asset.octaves = octaves, asset -> asset.octaves) + .addValidator(Validators.greaterThan(0)) + .add() + .append(new KeyedCodec<>("Seed", Codec.STRING, true), (asset, seed) -> asset.seedKey = seed, asset -> asset.seedKey) + .add() + .append( + new KeyedCodec<>("CellType", FastNoiseLite.CellularReturnType.CODEC, true), (asset, cellType) -> asset.cellType = cellType, asset -> asset.cellType + ) + .add() + .build(); + private double scaleX = 1.0; + private double scaleY = 1.0; + private double scaleZ = 1.0; + private double jitter = 0.5; + private int octaves = 1; + private String seedKey = "A"; + private FastNoiseLite.CellularReturnType cellType = FastNoiseLite.CellularReturnType.CellValue; + + public CellNoise3DDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else { + SeedBox childSeed = argument.parentSeed.child(this.seedKey); + CellNoiseField noise = new CellNoiseField( + childSeed.createSupplier().get(), this.scaleX, this.scaleY, this.scaleZ, this.jitter, this.octaves, this.cellType + ); + return new Noise3dDensity(noise); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CellWallDistanceDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CellWallDistanceDensityAsset.java new file mode 100644 index 0000000..89c1abf --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CellWallDistanceDensityAsset.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.CellWallDistanceDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class CellWallDistanceDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CellWallDistanceDensityAsset.class, CellWallDistanceDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .build(); + + public CellWallDistanceDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new CellWallDistanceDensity()); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ClampDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ClampDensityAsset.java new file mode 100644 index 0000000..aabd719 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ClampDensityAsset.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ClampDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class ClampDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ClampDensityAsset.class, ClampDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("WallA", Codec.DOUBLE, true), (t, k) -> t.wallA = k, k -> k.wallA) + .add() + .append(new KeyedCodec<>("WallB", Codec.DOUBLE, true), (t, k) -> t.wallB = k, k -> k.wallB) + .add() + .build(); + private double wallA = 0.0; + private double wallB = 0.0; + + public ClampDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new ClampDensity(this.wallA, this.wallB, this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ConstantDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ConstantDensityAsset.java new file mode 100644 index 0000000..2e59217 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ConstantDensityAsset.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class ConstantDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ConstantDensityAsset.class, ConstantDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Value", Codec.DOUBLE, true), (t, k) -> t.value = k, k -> k.value) + .add() + .build(); + private double value = 0.0; + + public ConstantDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return this.isSkipped() ? new ConstantValueDensity(0.0) : new ConstantValueDensity(this.value); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CubeDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CubeDensityAsset.java new file mode 100644 index 0000000..f830754 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CubeDensityAsset.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.ConstantCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.CurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.CubeDensity; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class CubeDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(CubeDensityAsset.class, CubeDensityAsset::new, DensityAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Curve", CurveAsset.CODEC, false), (t, k) -> t.densityCurveAsset = k, k -> k.densityCurveAsset) + .add() + .build(); + private CurveAsset densityCurveAsset = new ConstantCurveAsset(); + + public CubeDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(!this.isSkipped() && this.densityCurveAsset != null ? new CubeDensity(this.densityCurveAsset.build()) : new ConstantValueDensity(0.0)); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + this.densityCurveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CuboidDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CuboidDensityAsset.java new file mode 100644 index 0000000..d002f89 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CuboidDensityAsset.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.ConstantCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.CurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.CubeDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.RotatorDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ScaleDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class CuboidDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CuboidDensityAsset.class, CuboidDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Curve", CurveAsset.CODEC, true), (t, k) -> t.densityCurveAsset = k, k -> k.densityCurveAsset) + .add() + .append(new KeyedCodec<>("Scale", Vector3d.CODEC, false), (t, k) -> t.scaleVector = k, k -> k.scaleVector) + .addValidator((LegacyValidator)((v, r) -> { + if (v.x == 0.0 || v.y == 0.0 || v.z == 0.0) { + r.fail("scale vector contains 0.0"); + } + })) + .add() + .append(new KeyedCodec<>("NewYAxis", Vector3d.CODEC, false), (t, k) -> { + if (k.length() != 0.0) { + t.newYAxis = k; + } + }, k -> k.newYAxis) + .add() + .append(new KeyedCodec<>("Spin", Codec.DOUBLE, false), (t, k) -> t.spinAngle = k, k -> k.spinAngle) + .add() + .build(); + private CurveAsset densityCurveAsset = new ConstantCurveAsset(); + private Vector3d scaleVector = new Vector3d(1.0, 1.0, 1.0); + @Nonnull + private Vector3d newYAxis = new Vector3d(0.0, 1.0, 0.0); + private double spinAngle = 0.0; + + public CuboidDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (!this.isSkipped() && this.densityCurveAsset != null) { + CubeDensity cube = new CubeDensity(this.densityCurveAsset.build()); + ScaleDensity scale = new ScaleDensity(this.scaleVector.x, this.scaleVector.y, this.scaleVector.z, cube); + return new RotatorDensity(scale, this.newYAxis, this.spinAngle); + } else { + return new ConstantValueDensity(0.0); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + this.densityCurveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CurveMapperDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CurveMapperDensityAsset.java new file mode 100644 index 0000000..11d9d7b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CurveMapperDensityAsset.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.ConstantCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.CurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.CurveMapperDensity; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class CurveMapperDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CurveMapperDensityAsset.class, CurveMapperDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Curve", CurveAsset.CODEC, true), (t, k) -> t.curveAsset = k, k -> k.curveAsset) + .add() + .build(); + private CurveAsset curveAsset = new ConstantCurveAsset(); + + public CurveMapperDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new CurveMapperDensity(this.curveAsset.build(), this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + this.curveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CylinderDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CylinderDensityAsset.java new file mode 100644 index 0000000..2bc46ee --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/CylinderDensityAsset.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.ConstantCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.CurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.CylinderDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.RotatorDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class CylinderDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CylinderDensityAsset.class, CylinderDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("RadialCurve", CurveAsset.CODEC, true), (t, k) -> t.radialCurveAsset = k, k -> k.radialCurveAsset) + .add() + .append(new KeyedCodec<>("AxialCurve", CurveAsset.CODEC, true), (t, k) -> t.axialCurveAsset = k, k -> k.axialCurveAsset) + .add() + .append(new KeyedCodec<>("NewYAxis", Vector3d.CODEC, false), (t, k) -> { + if (k.length() != 0.0) { + t.newYAxis = k; + } + }, k -> k.newYAxis) + .add() + .append(new KeyedCodec<>("Spin", Codec.DOUBLE, false), (t, k) -> t.spinAngle = k, k -> k.spinAngle) + .add() + .build(); + private CurveAsset radialCurveAsset = new ConstantCurveAsset(); + private CurveAsset axialCurveAsset = new ConstantCurveAsset(); + @Nonnull + private Vector3d newYAxis = new Vector3d(0.0, 1.0, 0.0); + private double spinAngle = 0.0; + + public CylinderDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (!this.isSkipped() && this.axialCurveAsset != null && this.radialCurveAsset != null) { + CylinderDensity cylinder = new CylinderDensity(this.radialCurveAsset.build(), this.axialCurveAsset.build()); + return new RotatorDensity(cylinder, this.newYAxis, this.spinAngle); + } else { + return new ConstantValueDensity(0.0); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + this.radialCurveAsset.cleanUp(); + this.axialCurveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/DensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/DensityAsset.java new file mode 100644 index 0000000..a5f2dab --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/DensityAsset.java @@ -0,0 +1,236 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.assets.environmentproviders.EnvironmentProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.MaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.PatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.PositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.propassignments.AssignmentsAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.PropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.tintproviders.TintProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.vectorproviders.VectorProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.worldstructures.WorldStructureAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class DensityAsset implements JsonAssetWithMap>, Cleanable { + private static final DensityAsset[] EMPTY_INPUTS = new DensityAsset[0]; + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + private static final Map exportedNodes = new ConcurrentHashMap<>(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(DensityAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(DensityAsset.class) + .append(new KeyedCodec<>("Inputs", new ArrayCodec<>(CODEC, DensityAsset[]::new), true), (t, k) -> t.inputs = k, t -> t.inputs) + .add() + .append(new KeyedCodec<>("Skip", Codec.BOOLEAN, false), (t, k) -> t.skip = k, t -> t.skip) + .add() + .append(new KeyedCodec<>("ExportAs", Codec.STRING, false), (t, k) -> t.exportName = k, t -> t.exportName) + .add() + .afterDecode(asset -> { + if (asset.exportName != null && !asset.exportName.isEmpty()) { + if (exportedNodes.containsKey(asset.exportName)) { + LoggerUtil.getLogger().warning("Duplicate export name for asset: " + asset.exportName); + } + + DensityAsset.Exported exported = new DensityAsset.Exported(); + exported.asset = asset; + if (asset instanceof ExportedDensityAsset exportedAsset) { + exported.singleInstance = exportedAsset.isSingleInstance(); + } else { + exported.singleInstance = false; + } + + exportedNodes.put(asset.exportName, exported); + LoggerUtil.getLogger().fine("Registered imported node asset with name '" + asset.exportName + "' with asset id '" + asset.id); + } + }) + .build(); + private String id; + private AssetExtraInfo.Data data; + private DensityAsset[] inputs = EMPTY_INPUTS; + private boolean skip = false; + protected String exportName = ""; + + protected DensityAsset() { + } + + @Nonnull + public abstract Density build(@Nonnull DensityAsset.Argument var1); + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } + + protected void cleanUpInputs() { + for (DensityAsset input : this.inputs) { + input.cleanUp(); + } + } + + @Nonnull + public static DensityAsset getFallbackAsset() { + return new ConstantDensityAsset(); + } + + @Nonnull + public Density buildWithInputs(@Nonnull DensityAsset.Argument argument, @Nonnull Density[] inputs) { + Density node = this.build(argument); + node.setInputs(inputs); + return node; + } + + public DensityAsset[] inputs() { + return this.inputs; + } + + @Nonnull + public List buildInputs(@Nonnull DensityAsset.Argument argument, boolean excludeSkipped) { + ArrayList nodes = new ArrayList<>(); + + for (DensityAsset asset : this.inputs) { + if (!excludeSkipped || !asset.isSkipped()) { + nodes.add(asset.build(argument)); + } + } + + return nodes; + } + + @Nonnull + public Density[] buildInputsArray(@Nonnull DensityAsset.Argument argument) { + Density[] nodes = new Density[this.inputs.length]; + int i = 0; + + for (DensityAsset asset : this.inputs) { + nodes[i++] = asset.build(argument); + } + + return nodes; + } + + @Nullable + public DensityAsset firstInput() { + return this.inputs.length > 0 ? this.inputs[0] : null; + } + + @Nullable + public DensityAsset secondInput() { + return this.inputs.length > 1 ? this.inputs[1] : null; + } + + @Nullable + public Density buildFirstInput(@Nonnull DensityAsset.Argument argument) { + return this.firstInput() == null ? null : this.firstInput().build(argument); + } + + @Nullable + public Density buildSecondInput(@Nonnull DensityAsset.Argument argument) { + return this.secondInput() == null ? null : this.secondInput().build(argument); + } + + public boolean isSkipped() { + return this.skip; + } + + public static DensityAsset.Exported getExportedAsset(@Nonnull String name) { + return exportedNodes.get(name); + } + + public String getId() { + return this.id; + } + + @Nonnull + public static DensityAsset.Argument from(@Nonnull VectorProviderAsset.Argument argument) { + return new DensityAsset.Argument(argument.parentSeed, argument.referenceBundle, argument.workerIndexer); + } + + @Nonnull + public static DensityAsset.Argument from(@Nonnull MaterialProviderAsset.Argument argument) { + return new DensityAsset.Argument(argument.parentSeed, argument.referenceBundle, argument.workerIndexer); + } + + @Nonnull + public static DensityAsset.Argument from(@Nonnull PropAsset.Argument argument) { + return new DensityAsset.Argument(argument.parentSeed, argument.referenceBundle, argument.workerIndexer); + } + + @Nonnull + public static DensityAsset.Argument from(@Nonnull PatternAsset.Argument argument) { + return new DensityAsset.Argument(argument.parentSeed, argument.referenceBundle, argument.workerIndexer); + } + + @Nonnull + public static DensityAsset.Argument from(@Nonnull PositionProviderAsset.Argument argument) { + return new DensityAsset.Argument(argument.parentSeed, argument.referenceBundle, argument.workerIndexer); + } + + @Nonnull + public static DensityAsset.Argument from(@Nonnull AssignmentsAsset.Argument argument) { + return new DensityAsset.Argument(argument.parentSeed, argument.referenceBundle, argument.workerIndexer); + } + + @Nonnull + public static DensityAsset.Argument from(@Nonnull WorldStructureAsset.Argument argument, @Nonnull ReferenceBundle referenceBundle) { + return new DensityAsset.Argument(argument.parentSeed, referenceBundle, argument.workerIndexer); + } + + @Nonnull + public static DensityAsset.Argument from(@Nonnull EnvironmentProviderAsset.Argument argument) { + return new DensityAsset.Argument(argument.parentSeed, argument.referenceBundle, argument.workerIndexer); + } + + @Nonnull + public static DensityAsset.Argument from(@Nonnull TintProviderAsset.Argument argument) { + return new DensityAsset.Argument(argument.parentSeed, argument.referenceBundle, argument.workerIndexer); + } + + public static class Argument { + public SeedBox parentSeed; + public ReferenceBundle referenceBundle; + public WorkerIndexer workerIndexer; + + public Argument(@Nonnull SeedBox parentSeed, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer) { + this.parentSeed = parentSeed; + this.referenceBundle = referenceBundle; + this.workerIndexer = workerIndexer; + } + + public Argument(@Nonnull DensityAsset.Argument argument) { + this.parentSeed = argument.parentSeed; + this.referenceBundle = argument.referenceBundle; + this.workerIndexer = argument.workerIndexer; + } + } + + public static class Exported { + public boolean singleInstance; + public DensityAsset asset; + @Nullable + public Density builtInstance; + + public Exported() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/DistanceDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/DistanceDensityAsset.java new file mode 100644 index 0000000..8b79c18 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/DistanceDensityAsset.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.ConstantCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.CurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.DistanceDensity; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class DistanceDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DistanceDensityAsset.class, DistanceDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Curve", CurveAsset.CODEC, false), (t, k) -> t.densityCurveAsset = k, k -> k.densityCurveAsset) + .add() + .build(); + private CurveAsset densityCurveAsset = new ConstantCurveAsset(); + + public DistanceDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(!this.isSkipped() && this.densityCurveAsset != null + ? new DistanceDensity(this.densityCurveAsset.build()) + : new ConstantValueDensity(0.0)); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + this.densityCurveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/DistanceToBiomeEdgeDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/DistanceToBiomeEdgeDensityAsset.java new file mode 100644 index 0000000..0e15f3f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/DistanceToBiomeEdgeDensityAsset.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.DistanceToBiomeEdgeDensity; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class DistanceToBiomeEdgeDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DistanceToBiomeEdgeDensityAsset.class, DistanceToBiomeEdgeDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .build(); + + public DistanceToBiomeEdgeDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new DistanceToBiomeEdgeDensity()); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/EllipsoidDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/EllipsoidDensityAsset.java new file mode 100644 index 0000000..5506eac --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/EllipsoidDensityAsset.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.ConstantCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.CurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.DistanceDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.RotatorDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ScaleDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class EllipsoidDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + EllipsoidDensityAsset.class, EllipsoidDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Curve", CurveAsset.CODEC, true), (t, k) -> t.densityCurveAsset = k, k -> k.densityCurveAsset) + .add() + .append(new KeyedCodec<>("Scale", Vector3d.CODEC, false), (t, k) -> t.scaleVector = k, k -> k.scaleVector) + .addValidator((LegacyValidator)((v, r) -> { + if (v.x == 0.0 || v.y == 0.0 || v.z == 0.0) { + r.fail("scale vector contains 0.0"); + } + })) + .add() + .append(new KeyedCodec<>("NewYAxis", Vector3d.CODEC, false), (t, k) -> { + if (k.length() != 0.0) { + t.newYAxis = k; + } + }, k -> k.newYAxis) + .add() + .append(new KeyedCodec<>("Spin", Codec.DOUBLE, false), (t, k) -> t.spinAngle = k, k -> k.spinAngle) + .add() + .build(); + private CurveAsset densityCurveAsset = new ConstantCurveAsset(); + private Vector3d scaleVector = new Vector3d(1.0, 1.0, 1.0); + @Nonnull + private Vector3d newYAxis = new Vector3d(0.0, 1.0, 0.0); + private double spinAngle = 0.0; + + public EllipsoidDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (!this.isSkipped() && this.densityCurveAsset != null) { + DistanceDensity sphere = new DistanceDensity(this.densityCurveAsset.build()); + ScaleDensity scale = new ScaleDensity(this.scaleVector.x, this.scaleVector.y, this.scaleVector.z, sphere); + return new RotatorDensity(scale, this.newYAxis, this.spinAngle); + } else { + return new ConstantValueDensity(0.0); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + this.densityCurveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ExportedDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ExportedDensityAsset.java new file mode 100644 index 0000000..70b2f36 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ExportedDensityAsset.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class ExportedDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ExportedDensityAsset.class, ExportedDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("SingleInstance", Codec.BOOLEAN, false), (asset, value) -> asset.singleInstance = value, asset -> asset.singleInstance) + .add() + .build(); + private boolean singleInstance = false; + + public ExportedDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (!this.isSkipped() && this.inputs().length != 0) { + DensityAsset.Exported exported = getExportedAsset(this.exportName); + if (exported == null) { + LoggerUtil.getLogger() + .severe("Couldn't find Density asset exported with name: '" + this.exportName + "'. This could indicate a defect in the HytaleGenerator assets."); + return this.firstInput().build(argument); + } else if (exported.singleInstance) { + if (exported.builtInstance == null) { + exported.builtInstance = this.firstInput().build(argument); + } + + return exported.builtInstance; + } else { + return this.firstInput().build(argument); + } + } else { + return new ConstantValueDensity(0.0); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + DensityAsset.Exported exported = getExportedAsset(this.exportName); + if (exported != null) { + exported.builtInstance = null; + + for (DensityAsset input : this.inputs()) { + input.cleanUp(); + } + } + } + + public boolean isSingleInstance() { + return this.singleInstance; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/FastGradientWarpDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/FastGradientWarpDensityAsset.java new file mode 100644 index 0000000..6863043 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/FastGradientWarpDensityAsset.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.FastGradientWarpDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class FastGradientWarpDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + FastGradientWarpDensityAsset.class, FastGradientWarpDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("WarpScale", Codec.FLOAT, false), (t, k) -> t.warpScale = k, t -> t.warpScale) + .addValidator(Validators.greaterThan(0.0F)) + .add() + .append(new KeyedCodec<>("WarpOctaves", Codec.INTEGER, false), (t, k) -> t.warpOctaves = k, t -> t.warpOctaves) + .addValidator(Validators.greaterThan(0)) + .add() + .append(new KeyedCodec<>("WarpLacunarity", Codec.FLOAT, false), (t, k) -> t.warpLacunarity = k, t -> t.warpLacunarity) + .addValidator(Validators.greaterThanOrEqual(0.0F)) + .add() + .append(new KeyedCodec<>("WarpPersistence", Codec.FLOAT, false), (t, k) -> t.warpPersistence = k, t -> t.warpPersistence) + .addValidator(Validators.greaterThanOrEqual(0.0F)) + .add() + .append(new KeyedCodec<>("WarpFactor", Codec.FLOAT, false), (t, k) -> t.warpFactor = k, t -> t.warpFactor) + .addValidator(Validators.greaterThanOrEqual(0.0F)) + .add() + .append(new KeyedCodec<>("Seed", Codec.STRING, false), (t, k) -> t.seed = k, t -> t.seed) + .add() + .build(); + private float warpLacunarity = 2.0F; + private float warpPersistence = 0.5F; + private int warpOctaves = 1; + private float warpScale = 1.0F; + private float warpFactor = 1.0F; + private String seed = "A"; + + public FastGradientWarpDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() + ? new ConstantValueDensity(0.0) + : new FastGradientWarpDensity( + this.buildFirstInput(argument), + this.warpLacunarity, + this.warpPersistence, + this.warpOctaves, + 1.0F / this.warpScale, + this.warpFactor, + argument.parentSeed.child(this.seed).createSupplier().get() + )); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/FloorDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/FloorDensityAsset.java new file mode 100644 index 0000000..ab63bf0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/FloorDensityAsset.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.FloorDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class FloorDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + FloorDensityAsset.class, FloorDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Limit", Codec.DOUBLE, true), (t, k) -> t.limit = k, k -> k.limit) + .add() + .build(); + private double limit = 0.0; + + public FloorDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new FloorDensity(this.limit, this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/GradientDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/GradientDensityAsset.java new file mode 100644 index 0000000..ea347b4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/GradientDensityAsset.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.GradientDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class GradientDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + GradientDensityAsset.class, GradientDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Axis", Vector3d.CODEC, false), (t, k) -> t.axis = k, k -> k.axis) + .addValidator((LegacyValidator)((v, r) -> { + if (v.x == 0.0 && v.y == 0.0 && v.z == 0.0) { + r.fail("Axis can't be zero."); + } + })) + .add() + .append(new KeyedCodec<>("SampleRange", Codec.DOUBLE, false), (t, k) -> t.sampleRange = k, t -> t.sampleRange) + .addValidator(Validators.greaterThan(0.0)) + .add() + .build(); + private Vector3d axis = new Vector3d(0.0, 1.0, 0.0); + private double sampleRange = 1.0; + + public GradientDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() + ? new ConstantValueDensity(0.0) + : new GradientDensity(this.buildFirstInput(argument), this.sampleRange, this.axis.clone())); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/GradientWarpDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/GradientWarpDensityAsset.java new file mode 100644 index 0000000..431db7e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/GradientWarpDensityAsset.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.GradientWarpDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class GradientWarpDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + GradientWarpDensityAsset.class, GradientWarpDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("SampleRange", Codec.DOUBLE, false), (t, k) -> t.sampleRange = k, t -> t.sampleRange) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("WarpFactor", Codec.DOUBLE, false), (t, k) -> t.warpFactor = k, t -> t.warpFactor) + .add() + .append(new KeyedCodec<>("2D", Codec.BOOLEAN, false), (t, k) -> t.is2d = k, t -> t.is2d) + .add() + .append(new KeyedCodec<>("YFor2D", Codec.DOUBLE, false), (t, k) -> t.y2d = k, t -> t.y2d) + .add() + .append(new KeyedCodec<>("CacheSizeFor2D", Codec.INTEGER, false), (t, k) -> t._2dCacheSize = k, t -> t._2dCacheSize) + .add() + .build(); + private double sampleRange = 1.0; + private double warpFactor = 1.0; + private boolean is2d = false; + private double y2d = 0.0; + private int _2dCacheSize = 16; + + public GradientWarpDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() + ? new ConstantValueDensity(0.0) + : new GradientWarpDensity(this.buildFirstInput(argument), this.buildSecondInput(argument), this.sampleRange, this.warpFactor)); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ImportedDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ImportedDensityAsset.java new file mode 100644 index 0000000..ffc2516 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ImportedDensityAsset.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class ImportedDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ImportedDensityAsset.class, ImportedDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Name", Codec.STRING, true), (t, k) -> t.importedNodeName = k, k -> k.importedNodeName) + .add() + .build(); + private String importedNodeName = ""; + + public ImportedDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else { + DensityAsset.Exported asset = getExportedAsset(this.importedNodeName); + if (asset == null) { + LoggerUtil.getLogger().warning("Couldn't find Density asset exported with name: '" + this.importedNodeName + "'. Using empty Node instead."); + return new ConstantValueDensity(0.0); + } else if (asset.singleInstance) { + if (asset.builtInstance == null) { + asset.builtInstance = asset.asset.build(argument); + } + + return asset.builtInstance; + } else { + return asset.asset.build(argument); + } + } + } + + @Override + public DensityAsset[] inputs() { + DensityAsset.Exported asset = getExportedAsset(this.importedNodeName); + if (asset == null) { + LoggerUtil.getLogger().warning("Couldn't find Density asset exported with name: '" + this.importedNodeName + "'. Using empty Node instead."); + return new DensityAsset[0]; + } else { + return asset.asset.inputs(); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + DensityAsset.Exported exported = getExportedAsset(this.importedNodeName); + if (exported != null) { + exported.builtInstance = null; + + for (DensityAsset input : this.inputs()) { + input.cleanUp(); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/InverterDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/InverterDensityAsset.java new file mode 100644 index 0000000..a3a74d8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/InverterDensityAsset.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.InverterDensity; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class InverterDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + InverterDensityAsset.class, InverterDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .build(); + + public InverterDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new InverterDensity(this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/MaxDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/MaxDensityAsset.java new file mode 100644 index 0000000..8ce768c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/MaxDensityAsset.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.MaxDensity; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class MaxDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(MaxDensityAsset.class, MaxDensityAsset::new, DensityAsset.ABSTRACT_CODEC) + .build(); + + public MaxDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new MaxDensity(this.buildInputs(argument, true))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/MinDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/MinDensityAsset.java new file mode 100644 index 0000000..43118d2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/MinDensityAsset.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.MinDensity; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class MinDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(MinDensityAsset.class, MinDensityAsset::new, DensityAsset.ABSTRACT_CODEC) + .build(); + + public MinDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new MinDensity(this.buildInputs(argument, true))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/MixDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/MixDensityAsset.java new file mode 100644 index 0000000..7d9b126 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/MixDensityAsset.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.MixDensity; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import java.util.List; +import javax.annotation.Nonnull; + +public class MixDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(MixDensityAsset.class, MixDensityAsset::new, DensityAsset.ABSTRACT_CODEC) + .build(); + + public MixDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else { + List builtInputs = this.buildInputs(argument, true); + return (Density)(builtInputs.size() != 3 ? new ConstantValueDensity(0.0) : new MixDensity(builtInputs.get(0), builtInputs.get(1), builtInputs.get(2))); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/MultiMixDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/MultiMixDensityAsset.java new file mode 100644 index 0000000..7eb208f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/MultiMixDensityAsset.java @@ -0,0 +1,140 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.MultiMixDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class MultiMixDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + MultiMixDensityAsset.class, MultiMixDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>("Keys", new ArrayCodec<>(MultiMixDensityAsset.KeyAsset.CODEC, MultiMixDensityAsset.KeyAsset[]::new), true), + (asset, v) -> asset.keyAssets = v, + asset -> asset.keyAssets + ) + .add() + .build(); + private MultiMixDensityAsset.KeyAsset[] keyAssets = new MultiMixDensityAsset.KeyAsset[0]; + + public MultiMixDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else { + List densityInputs = this.buildInputs(argument, true); + if (densityInputs.isEmpty()) { + return new ConstantValueDensity(0.0); + } else { + ArrayList keys = new ArrayList<>(this.keyAssets.length); + + for (MultiMixDensityAsset.KeyAsset keyAsset : this.keyAssets) { + if (keyAsset.densityIndex <= 0) { + keys.add(new MultiMixDensity.Key(keyAsset.value, null)); + } else if (keyAsset.densityIndex >= densityInputs.size() - 1) { + LoggerUtil.getLogger() + .warning( + "Density Index out of bounds in MultiMix node " + keyAsset.densityIndex + ", valid range is [0, " + (densityInputs.size() - 1) + "]" + ); + keys.add(new MultiMixDensity.Key(keyAsset.value, null)); + } else { + Density density = densityInputs.get(keyAsset.densityIndex); + keys.add(new MultiMixDensity.Key(keyAsset.value, density)); + } + } + + int i = 1; + + while (i < keys.size()) { + MultiMixDensity.Key previousKey = keys.get(i - 1); + MultiMixDensity.Key currentKey = keys.get(i); + if (previousKey.value() == currentKey.value()) { + keys.remove(i); + } else { + i++; + } + } + + i = 0; + + while (i < keys.size()) { + if (keys.get(i).density() == null) { + keys.remove(i); + } else { + i++; + } + } + + for (int ix = keys.size() - 1; ix >= 0 && keys.get(ix).density() == null; ix--) { + keys.remove(ix); + } + + for (int ix = keys.size() - 2; ix >= 0; ix--) { + if (keys.get(ix).density() == null && keys.get(ix + 1).density() == null) { + keys.remove(ix); + } + } + + if (keys.isEmpty()) { + return new ConstantValueDensity(0.0); + } else if (keys.size() == 1) { + return keys.getFirst().density(); + } else { + keys.trimToSize(); + Density influenceDensity = densityInputs.getLast(); + return new MultiMixDensity(keys, influenceDensity); + } + } + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } + + public static class KeyAsset implements JsonAssetWithMap> { + public static final int NO_DENSITY_INDEX = 0; + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + MultiMixDensityAsset.KeyAsset.class, + MultiMixDensityAsset.KeyAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Value", Codec.DOUBLE, true), (t, value) -> t.value = value, t -> t.value) + .add() + .append(new KeyedCodec<>("DensityIndex", Codec.INTEGER, true), (t, value) -> t.densityIndex = value, t -> t.densityIndex) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private double value = 0.0; + private int densityIndex = 0; + + public KeyAsset() { + } + + public String getId() { + return this.id; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/MultiplierDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/MultiplierDensityAsset.java new file mode 100644 index 0000000..21d0eaa --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/MultiplierDensityAsset.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.MultiplierDensity; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class MultiplierDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + MultiplierDensityAsset.class, MultiplierDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .build(); + + public MultiplierDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new MultiplierDensity(this.buildInputs(argument, true))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/NormalizerDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/NormalizerDensityAsset.java new file mode 100644 index 0000000..f39021d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/NormalizerDensityAsset.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.NormalizerDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class NormalizerDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + NormalizerDensityAsset.class, NormalizerDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("FromMin", Codec.DOUBLE, true), (t, k) -> t.fromMin = k, k -> k.fromMin) + .add() + .append(new KeyedCodec<>("FromMax", Codec.DOUBLE, true), (t, k) -> t.fromMax = k, k -> k.fromMax) + .add() + .append(new KeyedCodec<>("ToMin", Codec.DOUBLE, true), (t, k) -> t.toMin = k, k -> k.toMin) + .add() + .append(new KeyedCodec<>("ToMax", Codec.DOUBLE, true), (t, k) -> t.toMax = k, k -> k.toMax) + .add() + .build(); + private double fromMin = 0.0; + private double fromMax = 1.0; + private double toMin = 0.0; + private double toMax = 1.0; + + public NormalizerDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() + ? new ConstantValueDensity(0.0) + : new NormalizerDensity(this.fromMin, this.fromMax, this.toMin, this.toMax, this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/OffsetConstantAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/OffsetConstantAsset.java new file mode 100644 index 0000000..69b1f95 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/OffsetConstantAsset.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.OffsetConstantDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class OffsetConstantAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + OffsetConstantAsset.class, OffsetConstantAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Value", Codec.DOUBLE, true), (t, k) -> t.value = k, t -> t.value) + .add() + .build(); + private double value = 0.0; + + public OffsetConstantAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new OffsetConstantDensity(this.value, this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/OffsetDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/OffsetDensityAsset.java new file mode 100644 index 0000000..3873446 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/OffsetDensityAsset.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.legacy.NodeFunctionYOutAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.OffsetDensity; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class OffsetDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + OffsetDensityAsset.class, OffsetDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("FunctionForY", NodeFunctionYOutAsset.CODEC, true), (t, k) -> t.nodeFunctionYOutAsset = k, k -> k.nodeFunctionYOutAsset) + .add() + .build(); + private NodeFunctionYOutAsset nodeFunctionYOutAsset = new NodeFunctionYOutAsset(); + + public OffsetDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new OffsetDensity(this.nodeFunctionYOutAsset.build(), this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + this.nodeFunctionYOutAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/PipelineDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/PipelineDensityAsset.java new file mode 100644 index 0000000..1588cdb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/PipelineDensityAsset.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import javax.annotation.Nonnull; + +public class PipelineDensityAsset extends DensityAsset { + private static final DensityAsset[] EMPTY_INPUTS = new DensityAsset[0]; + public static final BuilderCodec CODEC = BuilderCodec.builder( + PipelineDensityAsset.class, PipelineDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Pipeline", new ArrayCodec<>(DensityAsset.CODEC, DensityAsset[]::new)), (t, k) -> t.pipeline = k, t -> t.pipeline) + .add() + .build(); + private DensityAsset[] pipeline = new DensityAsset[0]; + + public PipelineDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else if (this.pipeline.length == 0) { + return this.buildFirstInput(argument); + } else { + Density[] nextInputs = new Density[]{this.pipeline[0].build(argument)}; + nextInputs[0].setInputs(this.buildInputsArray(argument)); + + for (int i = 1; i < this.pipeline.length; i++) { + Density node = this.pipeline[i].buildWithInputs(argument, nextInputs); + nextInputs[0] = node; + } + + return nextInputs[0]; + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + + for (DensityAsset densityAsset : this.pipeline) { + densityAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/PlaneDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/PlaneDensityAsset.java new file mode 100644 index 0000000..4dc4be5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/PlaneDensityAsset.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.ConstantCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.CurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.PlaneDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class PlaneDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + PlaneDensityAsset.class, PlaneDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Curve", CurveAsset.CODEC, true), (t, k) -> t.distanceCurveAsset = k, k -> k.distanceCurveAsset) + .add() + .append(new KeyedCodec<>("IsAnchored", Codec.BOOLEAN, false), (t, k) -> t.isAnchored = k, k -> k.isAnchored) + .add() + .append(new KeyedCodec<>("PlaneNormal", Vector3d.CODEC, false), (t, k) -> t.planeNormal = k, k -> k.planeNormal) + .addValidator((LegacyValidator)((v, r) -> { + if (v.length() == 0.0) { + r.fail("Plane normal can't be a zero vector."); + } + })) + .add() + .build(); + private CurveAsset distanceCurveAsset = new ConstantCurveAsset(); + private Vector3d planeNormal = new Vector3d(0.0, 1.0, 0.0); + private boolean isAnchored = false; + + public PlaneDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(!this.isSkipped() && this.distanceCurveAsset != null + ? new PlaneDensity(this.distanceCurveAsset.build(), this.planeNormal, this.isAnchored) + : new ConstantValueDensity(0.0)); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + this.distanceCurveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/PositionsPinchDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/PositionsPinchDensityAsset.java new file mode 100644 index 0000000..4fdc959 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/PositionsPinchDensityAsset.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.ConstantCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.CurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.ListPositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.PositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.PositionsHorizontalPinchDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.PositionsPinchDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class PositionsPinchDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + PositionsPinchDensityAsset.class, PositionsPinchDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>("Positions", PositionProviderAsset.CODEC, true), (asset, v) -> asset.positionProviderAsset = v, asset -> asset.positionProviderAsset + ) + .add() + .append(new KeyedCodec<>("PinchCurve", CurveAsset.CODEC, true), (asset, v) -> asset.pinchCurveAsset = v, asset -> asset.pinchCurveAsset) + .add() + .append(new KeyedCodec<>("MaxDistance", Codec.DOUBLE, true), (asset, v) -> asset.maxDistance = v, asset -> asset.maxDistance) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append(new KeyedCodec<>("NormalizeDistance", Codec.BOOLEAN, true), (asset, v) -> asset.normalizeDistance = v, asset -> asset.normalizeDistance) + .add() + .append(new KeyedCodec<>("HorizontalPinch", Codec.BOOLEAN, false), (asset, v) -> asset.isHorizontal = v, asset -> asset.isHorizontal) + .add() + .append(new KeyedCodec<>("PositionsMinY", Codec.DOUBLE, false), (asset, v) -> asset.positionsMinY = v, asset -> asset.positionsMinY) + .add() + .append(new KeyedCodec<>("PositionsMaxY", Codec.DOUBLE, false), (asset, v) -> asset.positionsMaxY = v, asset -> asset.positionsMaxY) + .add() + .build(); + private PositionProviderAsset positionProviderAsset = new ListPositionProviderAsset(); + private CurveAsset pinchCurveAsset = new ConstantCurveAsset(); + private double maxDistance = 0.0; + private boolean normalizeDistance = false; + private boolean isHorizontal = false; + private double positionsMinY = 0.0; + private double positionsMaxY = 1.0E-6; + + public PositionsPinchDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else { + return (Density)(this.isHorizontal + ? new PositionsHorizontalPinchDensity( + this.buildFirstInput(argument), + this.positionProviderAsset.build(new PositionProviderAsset.Argument(argument.parentSeed, argument.referenceBundle, argument.workerIndexer)), + this.pinchCurveAsset.build(), + this.maxDistance, + this.normalizeDistance, + this.positionsMinY, + this.positionsMaxY, + argument.workerIndexer.getWorkerCount() + ) + : new PositionsPinchDensity( + this.buildFirstInput(argument), + this.positionProviderAsset.build(new PositionProviderAsset.Argument(argument.parentSeed, argument.referenceBundle, argument.workerIndexer)), + this.pinchCurveAsset.build(), + this.maxDistance, + this.normalizeDistance + )); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + this.positionProviderAsset.cleanUp(); + this.pinchCurveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/PositionsTwistDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/PositionsTwistDensityAsset.java new file mode 100644 index 0000000..48d4143 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/PositionsTwistDensityAsset.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.ConstantCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.CurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.ListPositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.PositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.PositionsTwistDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class PositionsTwistDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + PositionsTwistDensityAsset.class, PositionsTwistDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>("Positions", PositionProviderAsset.CODEC, true), (asset, v) -> asset.positionProviderAsset = v, asset -> asset.positionProviderAsset + ) + .add() + .append(new KeyedCodec<>("TwistCurve", CurveAsset.CODEC, true), (asset, v) -> asset.pinchCurveAsset = v, asset -> asset.pinchCurveAsset) + .add() + .append(new KeyedCodec<>("TwistAxis", Vector3d.CODEC, true), (asset, v) -> asset.twistAxis = v, asset -> asset.twistAxis) + .add() + .append(new KeyedCodec<>("MaxDistance", Codec.DOUBLE, true), (asset, v) -> asset.maxDistance = v, asset -> asset.maxDistance) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append(new KeyedCodec<>("NormalizeDistance", Codec.BOOLEAN, true), (asset, v) -> asset.normalizeDistance = v, asset -> asset.normalizeDistance) + .add() + .append(new KeyedCodec<>("ZeroPositionsY", Codec.BOOLEAN, true), (asset, v) -> asset.zeroPositionsY = v, asset -> asset.zeroPositionsY) + .add() + .build(); + private PositionProviderAsset positionProviderAsset = new ListPositionProviderAsset(); + private CurveAsset pinchCurveAsset = new ConstantCurveAsset(); + private Vector3d twistAxis = new Vector3d(); + private double maxDistance = 0.0; + private boolean normalizeDistance = false; + private boolean zeroPositionsY = false; + + public PositionsTwistDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() + ? new ConstantValueDensity(0.0) + : new PositionsTwistDensity( + this.buildFirstInput(argument), + this.positionProviderAsset.build(new PositionProviderAsset.Argument(argument.parentSeed, argument.referenceBundle, argument.workerIndexer)), + this.pinchCurveAsset.build(), + this.twistAxis, + this.maxDistance, + this.normalizeDistance, + this.zeroPositionsY + )); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + this.positionProviderAsset.cleanUp(); + this.pinchCurveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/PowDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/PowDensityAsset.java new file mode 100644 index 0000000..0a7ea38 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/PowDensityAsset.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.PowDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class PowDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(PowDensityAsset.class, PowDensityAsset::new, DensityAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Exponent", Codec.DOUBLE, true), (t, k) -> t.exponent = k, t -> t.exponent) + .add() + .build(); + private double exponent = 1.0; + + public PowDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new PowDensity(this.exponent, this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/RotatorDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/RotatorDensityAsset.java new file mode 100644 index 0000000..3ef99aa --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/RotatorDensityAsset.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.RotatorDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class RotatorDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + RotatorDensityAsset.class, RotatorDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("NewYAxis", Vector3d.CODEC, true), (t, k) -> t.newYAxis = k, t -> t.newYAxis) + .add() + .append(new KeyedCodec<>("SpinAngle", Codec.DOUBLE, true), (t, k) -> t.spinAngle = k, t -> t.spinAngle) + .add() + .build(); + private Vector3d newYAxis = new Vector3d(); + private double spinAngle = 0.0; + + public RotatorDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new RotatorDensity(this.buildFirstInput(argument), this.newYAxis, this.spinAngle)); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ScaleDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ScaleDensityAsset.java new file mode 100644 index 0000000..3131e3d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ScaleDensityAsset.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ScaleDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class ScaleDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ScaleDensityAsset.class, ScaleDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("ScaleX", Codec.DOUBLE, false), (t, k) -> t.scaleX = k, k -> k.scaleX) + .add() + .append(new KeyedCodec<>("ScaleY", Codec.DOUBLE, false), (t, k) -> t.scaleY = k, k -> k.scaleY) + .add() + .append(new KeyedCodec<>("ScaleZ", Codec.DOUBLE, false), (t, k) -> t.scaleZ = k, k -> k.scaleZ) + .add() + .build(); + private double scaleX = 1.0; + private double scaleY = 1.0; + private double scaleZ = 1.0; + + public ScaleDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() + ? new ConstantValueDensity(0.0) + : new ScaleDensity(this.scaleX, this.scaleY, this.scaleZ, this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SelectorDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SelectorDensityAsset.java new file mode 100644 index 0000000..b8f3f81 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SelectorDensityAsset.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.SelectorDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class SelectorDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SelectorDensityAsset.class, SelectorDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("FromMin", Codec.DOUBLE, true), (t, k) -> t.fromMin = k, k -> k.fromMin) + .add() + .append(new KeyedCodec<>("FromMax", Codec.DOUBLE, true), (t, k) -> t.fromMax = k, k -> k.fromMax) + .add() + .append(new KeyedCodec<>("ToMin", Codec.DOUBLE, true), (t, k) -> t.toMin = k, k -> k.toMin) + .add() + .append(new KeyedCodec<>("ToMax", Codec.DOUBLE, true), (t, k) -> t.toMax = k, k -> k.toMax) + .add() + .append(new KeyedCodec<>("SmoothRange", Codec.DOUBLE, true), (t, k) -> t.smoothRange = k, k -> k.smoothRange) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .build(); + private double fromMin = -1.0; + private double fromMax = 1.0; + private double toMin = -1.0; + private double toMax = 1.0; + private double smoothRange = 0.0; + + public SelectorDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() + ? new ConstantValueDensity(0.0) + : new SelectorDensity(this.fromMin, this.fromMax, this.toMin, this.toMax, this.smoothRange, this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ShellDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ShellDensityAsset.java new file mode 100644 index 0000000..1574fd5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ShellDensityAsset.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.ConstantCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.CurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ShellDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class ShellDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ShellDensityAsset.class, ShellDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Axis", Vector3d.CODEC, true), (t, k) -> t.axis = k, k -> k.axis) + .add() + .append(new KeyedCodec<>("Mirror", Codec.BOOLEAN, false), (t, k) -> t.isMirrored = k, k -> k.isMirrored) + .add() + .append(new KeyedCodec<>("AngleCurve", CurveAsset.CODEC, true), (t, k) -> t.angleCurveAsset = k, k -> k.angleCurveAsset) + .add() + .append(new KeyedCodec<>("DistanceCurve", CurveAsset.CODEC, true), (t, k) -> t.distanceCurveAsset = k, k -> k.distanceCurveAsset) + .add() + .build(); + private Vector3d axis = new Vector3d(0.0, 0.0, 0.0); + private boolean isMirrored = false; + private CurveAsset angleCurveAsset = new ConstantCurveAsset(); + private CurveAsset distanceCurveAsset = new ConstantCurveAsset(); + + public ShellDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(!this.isSkipped() && this.angleCurveAsset != null && this.distanceCurveAsset != null + ? new ShellDensity(this.angleCurveAsset.build(), this.distanceCurveAsset.build(), this.axis, this.isMirrored) + : new ConstantValueDensity(0.0)); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + this.angleCurveAsset.cleanUp(); + this.distanceCurveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SimplexNoise2dDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SimplexNoise2dDensityAsset.java new file mode 100644 index 0000000..40a81cc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SimplexNoise2dDensityAsset.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.MultiCacheDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.Noise2dDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.YOverrideDensity; +import com.hypixel.hytale.builtin.hytalegenerator.fields.noise.SimplexNoiseField; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class SimplexNoise2dDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SimplexNoise2dDensityAsset.class, SimplexNoise2dDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Lacunarity", Codec.DOUBLE, true), (asset, lacunarity) -> asset.lacunarity = lacunarity, asset -> asset.lacunarity) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("Persistence", Codec.DOUBLE, true), (asset, persistence) -> asset.persistence = persistence, asset -> asset.persistence) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("Scale", Codec.DOUBLE, true), (asset, scale) -> asset.scale = scale, asset -> asset.scale) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("Octaves", Codec.INTEGER, true), (asset, octaves) -> asset.octaves = octaves, asset -> asset.octaves) + .addValidator(Validators.greaterThan(0)) + .add() + .append(new KeyedCodec<>("Seed", Codec.STRING, true), (asset, seed) -> asset.seedKey = seed, asset -> asset.seedKey) + .add() + .build(); + private double lacunarity = 1.0; + private double persistence = 1.0; + private double scale = 1.0; + private int octaves = 1; + private String seedKey = "A"; + + public SimplexNoise2dDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else { + SeedBox childSeed = argument.parentSeed.child(this.seedKey); + SimplexNoiseField noise = SimplexNoiseField.builder() + .withAmplitudeMultiplier(this.persistence) + .withFrequencyMultiplier(this.lacunarity) + .withScale(this.scale) + .withSeed(childSeed.createSupplier().get().intValue()) + .withNumberOfOctaves(this.octaves) + .build(); + Noise2dDensity noiseDensity = new Noise2dDensity(noise); + Density cacheDensity = new MultiCacheDensity(noiseDensity, argument.workerIndexer.getWorkerCount(), CacheDensityAsset.DEFAULT_CAPACITY); + return new YOverrideDensity(cacheDensity, 0.0); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SimplexNoise3DDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SimplexNoise3DDensityAsset.java new file mode 100644 index 0000000..665e46c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SimplexNoise3DDensityAsset.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.Noise3dDensity; +import com.hypixel.hytale.builtin.hytalegenerator.fields.noise.SimplexNoiseField; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class SimplexNoise3DDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SimplexNoise3DDensityAsset.class, SimplexNoise3DDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Lacunarity", Codec.DOUBLE, true), (asset, lacunarity) -> asset.lacunarity = lacunarity, asset -> asset.lacunarity) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("Persistence", Codec.DOUBLE, true), (asset, persistence) -> asset.persistence = persistence, asset -> asset.persistence) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("ScaleXZ", Codec.DOUBLE, true), (asset, scale) -> asset.scaleXZ = scale, asset -> asset.scaleXZ) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("ScaleY", Codec.DOUBLE, true), (asset, scale) -> asset.scaleY = scale, asset -> asset.scaleY) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("Octaves", Codec.INTEGER, true), (asset, octaves) -> asset.octaves = octaves, asset -> asset.octaves) + .addValidator(Validators.greaterThan(0)) + .add() + .append(new KeyedCodec<>("Seed", Codec.STRING, true), (asset, seed) -> asset.seedKey = seed, asset -> asset.seedKey) + .add() + .build(); + private double lacunarity = 1.0; + private double persistence = 1.0; + private double scaleXZ = 1.0; + private double scaleY = 1.0; + private int octaves = 1; + private String seedKey = "A"; + + public SimplexNoise3DDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else { + SeedBox childSeed = argument.parentSeed.child(this.seedKey); + SimplexNoiseField noise = SimplexNoiseField.builder() + .withAmplitudeMultiplier(this.persistence) + .withFrequencyMultiplier(this.lacunarity) + .withScale(this.scaleXZ, this.scaleY, this.scaleXZ, this.scaleXZ) + .withSeed(childSeed.createSupplier().get().intValue()) + .withNumberOfOctaves(this.octaves) + .build(); + return new Noise3dDensity(noise); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SliderDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SliderDensityAsset.java new file mode 100644 index 0000000..f81130a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SliderDensityAsset.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.SliderDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class SliderDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SliderDensityAsset.class, SliderDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("SlideX", Codec.DOUBLE, false), (t, k) -> t.slideX = k, k -> k.slideX) + .add() + .append(new KeyedCodec<>("SlideY", Codec.DOUBLE, false), (t, k) -> t.slideY = k, k -> k.slideY) + .add() + .append(new KeyedCodec<>("SlideZ", Codec.DOUBLE, false), (t, k) -> t.slideZ = k, k -> k.slideZ) + .add() + .build(); + private double slideX = 0.0; + private double slideY = 0.0; + private double slideZ = 0.0; + + public SliderDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() + ? new ConstantValueDensity(0.0) + : new SliderDensity(this.slideX, this.slideY, this.slideZ, this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SmoothCeilingDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SmoothCeilingDensityAsset.java new file mode 100644 index 0000000..de43683 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SmoothCeilingDensityAsset.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.SmoothCeilingDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class SmoothCeilingDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SmoothCeilingDensityAsset.class, SmoothCeilingDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Limit", Codec.DOUBLE, true), (t, k) -> t.limit = k, k -> k.limit) + .add() + .append(new KeyedCodec<>("SmoothRange", Codec.DOUBLE, true), (t, k) -> t.smoothRange = k, k -> k.smoothRange) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .build(); + private double smoothRange = 1.0; + private double limit = 0.0; + + public SmoothCeilingDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() + ? new ConstantValueDensity(0.0) + : new SmoothCeilingDensity(this.limit, this.smoothRange, this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SmoothClampDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SmoothClampDensityAsset.java new file mode 100644 index 0000000..de276cb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SmoothClampDensityAsset.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ClampDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.SmoothClampDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class SmoothClampDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SmoothClampDensityAsset.class, SmoothClampDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("WallA", Codec.DOUBLE, true), (t, k) -> t.wallA = k, k -> k.wallA) + .add() + .append(new KeyedCodec<>("WallB", Codec.DOUBLE, true), (t, k) -> t.wallB = k, k -> k.wallB) + .add() + .append(new KeyedCodec<>("Range", Codec.DOUBLE, true), (t, k) -> t.range = k, k -> k.range) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .build(); + private double wallA = -1.0; + private double wallB = 1.0; + private double range = 0.01; + + public SmoothClampDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else if (this.range == 0.0) { + return new ClampDensity(this.wallA, this.wallB, this.buildSecondInput(argument)); + } else { + double min = Math.min(this.wallA, this.wallB); + double max = Math.max(this.wallA, this.wallB); + return new SmoothClampDensity(min, max, this.range, this.buildSecondInput(argument)); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SmoothFloorDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SmoothFloorDensityAsset.java new file mode 100644 index 0000000..1d27d18 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SmoothFloorDensityAsset.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.SmoothFloorDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class SmoothFloorDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SmoothFloorDensityAsset.class, SmoothFloorDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Limit", Codec.DOUBLE, true), (t, k) -> t.limit = k, k -> k.limit) + .add() + .append(new KeyedCodec<>("SmoothRange", Codec.DOUBLE, true), (t, k) -> t.smoothRange = k, k -> k.smoothRange) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .build(); + private double smoothRange = 1.0; + private double limit = 0.0; + + public SmoothFloorDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new SmoothFloorDensity(this.limit, this.smoothRange, this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SmoothMaxDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SmoothMaxDensityAsset.java new file mode 100644 index 0000000..011dee9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SmoothMaxDensityAsset.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.SmoothMaxDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class SmoothMaxDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SmoothMaxDensityAsset.class, SmoothMaxDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Range", Codec.DOUBLE, true), (t, k) -> t.range = k, k -> k.range) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .build(); + private double range = 1.0; + + public SmoothMaxDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() + ? new ConstantValueDensity(0.0) + : new SmoothMaxDensity(this.range, this.buildFirstInput(argument), this.buildSecondInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SmoothMinDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SmoothMinDensityAsset.java new file mode 100644 index 0000000..5d04c3e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SmoothMinDensityAsset.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.SmoothMinDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class SmoothMinDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SmoothMinDensityAsset.class, SmoothMinDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Range", Codec.DOUBLE, true), (t, k) -> t.range = k, k -> k.range) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .build(); + private double range = 1.0; + + public SmoothMinDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() + ? new ConstantValueDensity(0.0) + : new SmoothMinDensity(this.range, this.buildFirstInput(argument), this.buildSecondInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SqrtDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SqrtDensityAsset.java new file mode 100644 index 0000000..ac1c25b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SqrtDensityAsset.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.SqrtDensity; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class SqrtDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(SqrtDensityAsset.class, SqrtDensityAsset::new, DensityAsset.ABSTRACT_CODEC) + .build(); + + public SqrtDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new SqrtDensity(this.buildFirstInput(argument))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SumDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SumDensityAsset.java new file mode 100644 index 0000000..d8a4688 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SumDensityAsset.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.SumDensity; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class SumDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(SumDensityAsset.class, SumDensityAsset::new, DensityAsset.ABSTRACT_CODEC) + .build(); + + public SumDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new SumDensity(this.buildInputs(argument, true))); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SwitchDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SwitchDensityAsset.java new file mode 100644 index 0000000..1b64e13 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SwitchDensityAsset.java @@ -0,0 +1,105 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.SwitchDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SwitchDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SwitchDensityAsset.class, SwitchDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>("SwitchCases", new ArrayCodec<>(SwitchDensityAsset.SwitchCaseAsset.CODEC, SwitchDensityAsset.SwitchCaseAsset[]::new), false), + (t, k) -> t.switchCaseAssets = k, + t -> t.switchCaseAssets + ) + .add() + .build(); + public static final String DEFAULT_STATE = "Default"; + public static final int DEFAULT_STATE_HASH = 0; + private SwitchDensityAsset.SwitchCaseAsset[] switchCaseAssets = new SwitchDensityAsset.SwitchCaseAsset[0]; + + public SwitchDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else { + ArrayList switchStates = new ArrayList<>(); + ArrayList densityNodes = new ArrayList<>(); + + for (int i = 0; i < this.switchCaseAssets.length; i++) { + if (this.switchCaseAssets[i] != null && this.switchCaseAssets[i].densityAsset != null) { + String stringState = this.switchCaseAssets[i].caseState; + int stateHash = getHashFromState(stringState); + Density densityNode = this.switchCaseAssets[i].densityAsset.build(argument); + switchStates.add(stateHash); + densityNodes.add(densityNode); + } + } + + return new SwitchDensity(densityNodes, switchStates); + } + } + + public static int getHashFromState(String state) { + return "Default".equals(state) ? 0 : Objects.hash(state); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + + for (SwitchDensityAsset.SwitchCaseAsset switchCaseAsset : this.switchCaseAssets) { + switchCaseAsset.cleanUp(); + } + } + + public static class SwitchCaseAsset implements Cleanable, JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + SwitchDensityAsset.SwitchCaseAsset.class, + SwitchDensityAsset.SwitchCaseAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("CaseState", Codec.STRING, true), (t, y) -> t.caseState = y, t -> t.caseState) + .add() + .append(new KeyedCodec<>("Density", DensityAsset.CODEC, true), (t, out) -> t.densityAsset = out, t -> t.densityAsset) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private String caseState = ""; + private DensityAsset densityAsset; + + public SwitchCaseAsset() { + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + this.densityAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SwitchStateDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SwitchStateDensityAsset.java new file mode 100644 index 0000000..7be8d83 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/SwitchStateDensityAsset.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.SwitchStateDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SwitchStateDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SwitchStateDensityAsset.class, SwitchStateDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("SwitchState", Codec.STRING, true), (t, k) -> t.switchState = k, t -> t.switchState) + .add() + .build(); + private String switchState = ""; + + public SwitchStateDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else { + int stateHash = Objects.hash(this.switchState); + return new SwitchStateDensity(this.buildFirstInput(argument), stateHash); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/TerrainDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/TerrainDensityAsset.java new file mode 100644 index 0000000..09cfb67 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/TerrainDensityAsset.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.TerrainDensity; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class TerrainDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + TerrainDensityAsset.class, TerrainDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .build(); + + public TerrainDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new TerrainDensity()); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/VectorWarpDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/VectorWarpDensityAsset.java new file mode 100644 index 0000000..99b1aa3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/VectorWarpDensityAsset.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.VectorWarpDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class VectorWarpDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + VectorWarpDensityAsset.class, VectorWarpDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("WarpFactor", Codec.DOUBLE, true), (t, k) -> t.warpFactor = k, t -> t.warpFactor) + .add() + .append(new KeyedCodec<>("WarpVector", Vector3d.CODEC, true), (t, k) -> t.warpVector = k, t -> t.warpVector) + .add() + .build(); + private double warpFactor = 1.0; + private Vector3d warpVector = new Vector3d(); + + public VectorWarpDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() + ? new ConstantValueDensity(0.0) + : new VectorWarpDensity(this.buildFirstInput(argument), this.buildSecondInput(argument), this.warpFactor, this.warpVector)); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/XOverrideDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/XOverrideDensityAsset.java new file mode 100644 index 0000000..b72091a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/XOverrideDensityAsset.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.XOverrideDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class XOverrideDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + XOverrideDensityAsset.class, XOverrideDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Value", Codec.DOUBLE, true), (t, k) -> t.value = k, t -> t.value) + .add() + .build(); + private double value = 0.0; + + public XOverrideDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else { + Density input = this.buildFirstInput(argument); + return (Density)(input == null ? new ConstantValueDensity(0.0) : new XOverrideDensity(input, this.value)); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/XValueDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/XValueDensityAsset.java new file mode 100644 index 0000000..d4da081 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/XValueDensityAsset.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.XValueDensity; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class XValueDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + XValueDensityAsset.class, XValueDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .build(); + + public XValueDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new XValueDensity()); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/YOverrideDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/YOverrideDensityAsset.java new file mode 100644 index 0000000..1249a51 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/YOverrideDensityAsset.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.YOverrideDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class YOverrideDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + YOverrideDensityAsset.class, YOverrideDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Value", Codec.DOUBLE, true), (t, k) -> t.value = k, t -> t.value) + .add() + .build(); + private double value = 0.0; + + public YOverrideDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else { + Density input = this.buildFirstInput(argument); + return (Density)(input == null ? new ConstantValueDensity(0.0) : new YOverrideDensity(input, this.value)); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/YValueDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/YValueDensityAsset.java new file mode 100644 index 0000000..74dbe9b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/YValueDensityAsset.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.YValueDensity; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class YValueDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + YValueDensityAsset.class, YValueDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .build(); + + public YValueDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new YValueDensity()); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ZOverrideDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ZOverrideDensityAsset.java new file mode 100644 index 0000000..d6ea6c9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ZOverrideDensityAsset.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ZOverrideDensity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class ZOverrideDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ZOverrideDensityAsset.class, ZOverrideDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Value", Codec.DOUBLE, true), (t, k) -> t.value = k, t -> t.value) + .add() + .build(); + private double value = 0.0; + + public ZOverrideDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else { + Density input = this.buildFirstInput(argument); + return (Density)(input == null ? new ConstantValueDensity(0.0) : new ZOverrideDensity(input, this.value)); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ZValueDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ZValueDensityAsset.java new file mode 100644 index 0000000..7c00620 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/ZValueDensityAsset.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ZValueDensity; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class ZValueDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ZValueDensityAsset.class, ZValueDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .build(); + + public ZValueDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + return (Density)(this.isSkipped() ? new ConstantValueDensity(0.0) : new ZValueDensity()); + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/Positions3DDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/Positions3DDensityAsset.java new file mode 100644 index 0000000..dec75fc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/Positions3DDensityAsset.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.ConstantCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.CurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.ListPositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.PositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.PositionsDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.distancefunctions.EuclideanDistanceFunction; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.CurveReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class Positions3DDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + Positions3DDensityAsset.class, Positions3DDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>("Positions", PositionProviderAsset.CODEC, true), (asset, v) -> asset.positionProviderAsset = v, asset -> asset.positionProviderAsset + ) + .add() + .append(new KeyedCodec<>("DistanceCurve", CurveAsset.CODEC, true), (asset, v) -> asset.curveAsset = v, asset -> asset.curveAsset) + .add() + .append(new KeyedCodec<>("MaxDistance", Codec.DOUBLE, false), (asset, v) -> asset.maxDistance = v, asset -> asset.maxDistance) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .build(); + private PositionProviderAsset positionProviderAsset = new ListPositionProviderAsset(); + private CurveAsset curveAsset = new ConstantCurveAsset(); + private double maxDistance = 0.0; + + public Positions3DDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else { + PositionProvider positionsField = this.positionProviderAsset + .build(new PositionProviderAsset.Argument(argument.parentSeed, argument.referenceBundle, argument.workerIndexer)); + Double2DoubleFunction curve = this.curveAsset.build(); + CurveReturnType returnType = new CurveReturnType(curve); + return new PositionsDensity(positionsField, returnType, new EuclideanDistanceFunction(), this.maxDistance); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + this.positionProviderAsset.cleanUp(); + this.curveAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/PositionsCellNoiseDensityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/PositionsCellNoiseDensityAsset.java new file mode 100644 index 0000000..100ce80 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/PositionsCellNoiseDensityAsset.java @@ -0,0 +1,71 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.distancefunctions.DistanceFunctionAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.distancefunctions.EuclideanDistanceFunctionAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes.CurveReturnTypeAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes.ReturnTypeAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.ListPositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.PositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.ConstantValueDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.PositionsDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.distancefunctions.DistanceFunction; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.ReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class PositionsCellNoiseDensityAsset extends DensityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + PositionsCellNoiseDensityAsset.class, PositionsCellNoiseDensityAsset::new, DensityAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>("Positions", PositionProviderAsset.CODEC, true), (asset, v) -> asset.positionProviderAsset = v, asset -> asset.positionProviderAsset + ) + .add() + .append(new KeyedCodec<>("ReturnType", ReturnTypeAsset.CODEC, true), (asset, v) -> asset.returnTypeAsset = v, asset -> asset.returnTypeAsset) + .add() + .append( + new KeyedCodec<>("DistanceFunction", DistanceFunctionAsset.CODEC, true), + (asset, v) -> asset.distanceFunctionAsset = v, + asset -> asset.distanceFunctionAsset + ) + .add() + .append(new KeyedCodec<>("MaxDistance", Codec.DOUBLE, true), (asset, v) -> asset.maxDistance = v, asset -> asset.maxDistance) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .build(); + private PositionProviderAsset positionProviderAsset = new ListPositionProviderAsset(); + private ReturnTypeAsset returnTypeAsset = new CurveReturnTypeAsset(); + private DistanceFunctionAsset distanceFunctionAsset = new EuclideanDistanceFunctionAsset(); + private double maxDistance = 0.0; + + public PositionsCellNoiseDensityAsset() { + } + + @Nonnull + @Override + public Density build(@Nonnull DensityAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantValueDensity(0.0); + } else { + PositionProvider positionsField = this.positionProviderAsset + .build(new PositionProviderAsset.Argument(argument.parentSeed, argument.referenceBundle, argument.workerIndexer)); + ReturnType returnType = this.returnTypeAsset.build(argument.parentSeed, argument.referenceBundle, argument.workerIndexer); + returnType.setMaxDistance(this.maxDistance); + DistanceFunction distanceFunction = this.distanceFunctionAsset.build(argument.parentSeed, this.maxDistance); + return new PositionsDensity(positionsField, returnType, distanceFunction, this.maxDistance); + } + } + + @Override + public void cleanUp() { + this.cleanUpInputs(); + this.positionProviderAsset.cleanUp(); + this.returnTypeAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/distancefunctions/DistanceFunctionAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/distancefunctions/DistanceFunctionAsset.java new file mode 100644 index 0000000..cc07360 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/distancefunctions/DistanceFunctionAsset.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.distancefunctions; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.distancefunctions.DistanceFunction; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import javax.annotation.Nonnull; + +public abstract class DistanceFunctionAsset implements JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(DistanceFunctionAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(DistanceFunctionAsset.class).build(); + private String id; + private AssetExtraInfo.Data data; + + protected DistanceFunctionAsset() { + } + + public abstract DistanceFunction build(@Nonnull SeedBox var1, double var2); + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/distancefunctions/EuclideanDistanceFunctionAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/distancefunctions/EuclideanDistanceFunctionAsset.java new file mode 100644 index 0000000..efaab68 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/distancefunctions/EuclideanDistanceFunctionAsset.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.distancefunctions; + +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.distancefunctions.DistanceFunction; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.distancefunctions.EuclideanDistanceFunction; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class EuclideanDistanceFunctionAsset extends DistanceFunctionAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + EuclideanDistanceFunctionAsset.class, EuclideanDistanceFunctionAsset::new, DistanceFunctionAsset.ABSTRACT_CODEC + ) + .build(); + + public EuclideanDistanceFunctionAsset() { + } + + @Nonnull + @Override + public DistanceFunction build(@Nonnull SeedBox parentSeed, double maxDistance) { + return new EuclideanDistanceFunction(); + } + + static { + DistanceFunctionAsset.CODEC.register("Euclidean", EuclideanDistanceFunctionAsset.class, CODEC); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/distancefunctions/ManhattanDistanceFunctionAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/distancefunctions/ManhattanDistanceFunctionAsset.java new file mode 100644 index 0000000..d9e0108 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/distancefunctions/ManhattanDistanceFunctionAsset.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.distancefunctions; + +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.distancefunctions.DistanceFunction; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.distancefunctions.ManhattanDistanceFunction; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class ManhattanDistanceFunctionAsset extends DistanceFunctionAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ManhattanDistanceFunctionAsset.class, ManhattanDistanceFunctionAsset::new, DistanceFunctionAsset.ABSTRACT_CODEC + ) + .build(); + + public ManhattanDistanceFunctionAsset() { + } + + @Nonnull + @Override + public DistanceFunction build(@Nonnull SeedBox parentSeed, double maxDistance) { + return new ManhattanDistanceFunction(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/CellValueReturnTypeAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/CellValueReturnTypeAsset.java new file mode 100644 index 0000000..d285ce9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/CellValueReturnTypeAsset.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.CacheDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ConstantDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.MultiCacheDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.CellValueReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.ReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class CellValueReturnTypeAsset extends ReturnTypeAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CellValueReturnTypeAsset.class, CellValueReturnTypeAsset::new, ReturnTypeAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Density", DensityAsset.CODEC, true), (t, k) -> t.densityAsset = k, t -> t.densityAsset) + .add() + .append(new KeyedCodec<>("DefaultValue", Codec.DOUBLE, false), (t, k) -> t.defaultValue = k, t -> t.defaultValue) + .add() + .build(); + private DensityAsset densityAsset = new ConstantDensityAsset(); + private double defaultValue = 0.0; + + public CellValueReturnTypeAsset() { + } + + @Nonnull + @Override + public ReturnType build(@Nonnull SeedBox parentSeed, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer) { + Density densityNode = this.densityAsset.build(new DensityAsset.Argument(parentSeed, referenceBundle, workerIndexer)); + Density cache = new MultiCacheDensity(densityNode, workerIndexer.getWorkerCount(), CacheDensityAsset.DEFAULT_CAPACITY); + return new CellValueReturnType(cache, this.defaultValue, workerIndexer.getWorkerCount()); + } + + @Override + public void cleanUp() { + this.densityAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/CurveReturnTypeAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/CurveReturnTypeAsset.java new file mode 100644 index 0000000..2645907 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/CurveReturnTypeAsset.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.ConstantCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.CurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.CurveReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.ReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class CurveReturnTypeAsset extends ReturnTypeAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CurveReturnTypeAsset.class, CurveReturnTypeAsset::new, ReturnTypeAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Curve", CurveAsset.CODEC, true), (t, k) -> t.curveAsset = k, t -> t.curveAsset) + .add() + .build(); + private CurveAsset curveAsset = new ConstantCurveAsset(); + + public CurveReturnTypeAsset() { + } + + @Nonnull + @Override + public ReturnType build(@Nonnull SeedBox parentSeed, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer) { + Double2DoubleFunction curve = this.curveAsset.build(); + return new CurveReturnType(curve); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/DensityReturnTypeAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/DensityReturnTypeAsset.java new file mode 100644 index 0000000..da70f44 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/DensityReturnTypeAsset.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.CacheDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ConstantDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.FieldFunctionMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.MultiCacheDensity; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.DensityReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.ReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.math.Range; +import java.util.HashMap; +import javax.annotation.Nonnull; + +public class DensityReturnTypeAsset extends ReturnTypeAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DensityReturnTypeAsset.class, DensityReturnTypeAsset::new, ReturnTypeAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("ChoiceDensity", DensityAsset.CODEC, true), (t, k) -> t.choiceDensityAsset = k, t -> t.choiceDensityAsset) + .add() + .append( + new KeyedCodec<>("Delimiters", new ArrayCodec<>(DensityReturnTypeAsset.DelimiterAsset.CODEC, DensityReturnTypeAsset.DelimiterAsset[]::new), true), + (t, k) -> t.delimiterAssets = k, + t -> t.delimiterAssets + ) + .add() + .append(new KeyedCodec<>("DefaultValue", Codec.DOUBLE, false), (t, k) -> t.defaultValue = k, t -> t.defaultValue) + .add() + .build(); + private DensityAsset choiceDensityAsset = new ConstantDensityAsset(); + private DensityReturnTypeAsset.DelimiterAsset[] delimiterAssets = new DensityReturnTypeAsset.DelimiterAsset[0]; + private double defaultValue = 0.0; + + public DensityReturnTypeAsset() { + } + + @Nonnull + @Override + public ReturnType build(@Nonnull SeedBox parentSeed, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer) { + DensityAsset.Argument densityArgument = new DensityAsset.Argument(parentSeed, referenceBundle, workerIndexer); + Density choiceDensity = this.choiceDensityAsset.build(densityArgument); + HashMap delimiterMap = new HashMap<>(this.delimiterAssets.length); + + for (DensityReturnTypeAsset.DelimiterAsset delimiter : this.delimiterAssets) { + delimiterMap.put(new Range((float)delimiter.from, (float)delimiter.to), delimiter.densityAsset.build(densityArgument)); + } + + Density cache = new MultiCacheDensity(choiceDensity, workerIndexer.getWorkerCount(), CacheDensityAsset.DEFAULT_CAPACITY); + return new DensityReturnType(cache, delimiterMap, true, this.defaultValue, workerIndexer.getWorkerCount()); + } + + public static class DelimiterAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + DensityReturnTypeAsset.DelimiterAsset.class, + DensityReturnTypeAsset.DelimiterAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("From", Codec.DOUBLE, true), (t, y) -> t.from = y, t -> t.from) + .add() + .append(new KeyedCodec<>("To", Codec.DOUBLE, true), (t, out) -> t.to = out, t -> t.to) + .add() + .append(new KeyedCodec<>("Density", DensityAsset.CODEC, true), (t, out) -> t.densityAsset = out, t -> t.densityAsset) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private double from = 0.0; + private double to = 0.0; + private DensityAsset densityAsset = new ConstantDensityAsset(); + + public DelimiterAsset() { + } + + public String getId() { + return this.id; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/Distance2AddReturnTypeAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/Distance2AddReturnTypeAsset.java new file mode 100644 index 0000000..f2ce8c1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/Distance2AddReturnTypeAsset.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.Distance2AddReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.ReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class Distance2AddReturnTypeAsset extends ReturnTypeAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + Distance2AddReturnTypeAsset.class, Distance2AddReturnTypeAsset::new, ReturnTypeAsset.ABSTRACT_CODEC + ) + .build(); + + public Distance2AddReturnTypeAsset() { + } + + @Nonnull + @Override + public ReturnType build(@Nonnull SeedBox parentSeed, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer) { + return new Distance2AddReturnType(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/Distance2DivReturnTypeAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/Distance2DivReturnTypeAsset.java new file mode 100644 index 0000000..85a9b69 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/Distance2DivReturnTypeAsset.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.Distance2DivReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.ReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class Distance2DivReturnTypeAsset extends ReturnTypeAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + Distance2DivReturnTypeAsset.class, Distance2DivReturnTypeAsset::new, ReturnTypeAsset.ABSTRACT_CODEC + ) + .build(); + + public Distance2DivReturnTypeAsset() { + } + + @Nonnull + @Override + public ReturnType build(@Nonnull SeedBox parentSeed, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer) { + return new Distance2DivReturnType(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/Distance2MulReturnTypeAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/Distance2MulReturnTypeAsset.java new file mode 100644 index 0000000..6519b41 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/Distance2MulReturnTypeAsset.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.Distance2MulReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.ReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class Distance2MulReturnTypeAsset extends ReturnTypeAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + Distance2MulReturnTypeAsset.class, Distance2MulReturnTypeAsset::new, ReturnTypeAsset.ABSTRACT_CODEC + ) + .build(); + + public Distance2MulReturnTypeAsset() { + } + + @Nonnull + @Override + public ReturnType build(@Nonnull SeedBox parentSeed, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer) { + return new Distance2MulReturnType(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/Distance2ReturnTypeAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/Distance2ReturnTypeAsset.java new file mode 100644 index 0000000..6acdbfe --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/Distance2ReturnTypeAsset.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.Distance2ReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.ReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class Distance2ReturnTypeAsset extends ReturnTypeAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + Distance2ReturnTypeAsset.class, Distance2ReturnTypeAsset::new, ReturnTypeAsset.ABSTRACT_CODEC + ) + .build(); + + public Distance2ReturnTypeAsset() { + } + + @Nonnull + @Override + public ReturnType build(@Nonnull SeedBox parentSeed, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer) { + return new Distance2ReturnType(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/Distance2SubReturnTypeAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/Distance2SubReturnTypeAsset.java new file mode 100644 index 0000000..83917cd --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/Distance2SubReturnTypeAsset.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.Distance2SubReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.ReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class Distance2SubReturnTypeAsset extends ReturnTypeAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + Distance2SubReturnTypeAsset.class, Distance2SubReturnTypeAsset::new, ReturnTypeAsset.ABSTRACT_CODEC + ) + .build(); + + public Distance2SubReturnTypeAsset() { + } + + @Nonnull + @Override + public ReturnType build(@Nonnull SeedBox parentSeed, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer) { + return new Distance2SubReturnType(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/DistanceReturnTypeAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/DistanceReturnTypeAsset.java new file mode 100644 index 0000000..11d3f16 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/DistanceReturnTypeAsset.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.DistanceReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.ReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class DistanceReturnTypeAsset extends ReturnTypeAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DistanceReturnTypeAsset.class, DistanceReturnTypeAsset::new, ReturnTypeAsset.ABSTRACT_CODEC + ) + .build(); + + public DistanceReturnTypeAsset() { + } + + @Nonnull + @Override + public ReturnType build(@Nonnull SeedBox parentSeed, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer) { + return new DistanceReturnType(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/ImportedReturnTypeAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/ImportedReturnTypeAsset.java new file mode 100644 index 0000000..5c845c0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/ImportedReturnTypeAsset.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.ReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3d; +import java.util.logging.Logger; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ImportedReturnTypeAsset extends ReturnTypeAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ImportedReturnTypeAsset.class, ImportedReturnTypeAsset::new, ReturnTypeAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Name", Codec.STRING, true), (t, k) -> t.importedAssetName = k, k -> k.importedAssetName) + .add() + .build(); + private String importedAssetName = ""; + + public ImportedReturnTypeAsset() { + } + + @Override + public ReturnType build(@Nonnull SeedBox parentSeed, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer) { + ReturnTypeAsset asset = getExportedAsset(this.importedAssetName); + if (asset == null) { + Logger.getLogger("Density") + .warning("Couldn't find ReturnType asset exported with name: '" + this.importedAssetName + "'. Using a return type that only outputs 0 instead."); + return new ReturnType() { + @Override + public double get( + double distance0, + double distance1, + @Nonnull Vector3d samplePoint, + @Nullable Vector3d closestPoint0, + Vector3d closestPoint1, + @Nullable Density.Context context + ) { + return 0.0; + } + }; + } else { + return asset.build(parentSeed, referenceBundle, workerIndexer); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/ReturnTypeAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/ReturnTypeAsset.java new file mode 100644 index 0000000..c481ecc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/density/positions/returntypes/ReturnTypeAsset.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.density.positions.returntypes; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.ReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public abstract class ReturnTypeAsset implements JsonAssetWithMap> { + private static final ReturnTypeAsset[] EMPTY_INPUTS = new ReturnTypeAsset[0]; + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + private static final Map exportedNodes = new HashMap<>(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(ReturnTypeAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(ReturnTypeAsset.class) + .append(new KeyedCodec<>("ExportAs", Codec.STRING, false), (t, k) -> t.exportName = k, t -> t.exportName) + .add() + .afterDecode(asset -> { + if (asset.exportName != null && !asset.exportName.isEmpty()) { + exportedNodes.put(asset.exportName, asset); + LoggerUtil.getLogger().fine("Registered imported node asset with name '" + asset.exportName + "' with asset id '" + asset.id); + } + }) + .build(); + private String id; + private AssetExtraInfo.Data data; + private String exportName = ""; + + protected ReturnTypeAsset() { + } + + public abstract ReturnType build(@Nonnull SeedBox var1, @Nonnull ReferenceBundle var2, @Nonnull WorkerIndexer var3); + + public void cleanUp() { + } + + public static boolean registerExportedNode(@Nonnull String name, @Nonnull ReturnTypeAsset node) { + exportedNodes.put(name, node); + return true; + } + + public static ReturnTypeAsset getExportedAsset(@Nonnull String name) { + return exportedNodes.get(name); + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/environmentproviders/ConstantEnvironmentProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/environmentproviders/ConstantEnvironmentProviderAsset.java new file mode 100644 index 0000000..27b4259 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/environmentproviders/ConstantEnvironmentProviderAsset.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.environmentproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.environmentproviders.ConstantEnvironmentProvider; +import com.hypixel.hytale.builtin.hytalegenerator.environmentproviders.EnvironmentProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import javax.annotation.Nonnull; + +public class ConstantEnvironmentProviderAsset extends EnvironmentProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ConstantEnvironmentProviderAsset.class, ConstantEnvironmentProviderAsset::new, EnvironmentProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Environment", Codec.STRING, true), (t, k) -> t.environment = k, k -> k.environment) + .add() + .build(); + private String environment = "Unknown"; + + public ConstantEnvironmentProviderAsset() { + } + + @Nonnull + @Override + public EnvironmentProvider build(@Nonnull EnvironmentProviderAsset.Argument argument) { + if (super.isSkipped()) { + return EnvironmentProvider.noEnvironmentProvider(); + } else { + int index = Environment.getAssetMap().getIndex(this.environment); + if (index == Integer.MIN_VALUE) { + index = 0; + } + + return new ConstantEnvironmentProvider(index); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/environmentproviders/DensityDelimitedEnvironmentProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/environmentproviders/DensityDelimitedEnvironmentProviderAsset.java new file mode 100644 index 0000000..994a864 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/environmentproviders/DensityDelimitedEnvironmentProviderAsset.java @@ -0,0 +1,117 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.environmentproviders; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.assets.delimiters.RangeDoubleAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.delimiters.DelimiterDouble; +import com.hypixel.hytale.builtin.hytalegenerator.delimiters.RangeDouble; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.environmentproviders.DensityDelimitedEnvironmentProvider; +import com.hypixel.hytale.builtin.hytalegenerator.environmentproviders.EnvironmentProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class DensityDelimitedEnvironmentProviderAsset extends EnvironmentProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DensityDelimitedEnvironmentProviderAsset.class, DensityDelimitedEnvironmentProviderAsset::new, EnvironmentProviderAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>( + "Delimiters", + new ArrayCodec<>(DensityDelimitedEnvironmentProviderAsset.DelimiterAsset.CODEC, DensityDelimitedEnvironmentProviderAsset.DelimiterAsset[]::new), + true + ), + (t, k) -> t.delimiterAssets = k, + k -> k.delimiterAssets + ) + .add() + .append(new KeyedCodec<>("Density", DensityAsset.CODEC, true), (t, value) -> t.densityAsset = value, t -> t.densityAsset) + .add() + .build(); + private DensityDelimitedEnvironmentProviderAsset.DelimiterAsset[] delimiterAssets = new DensityDelimitedEnvironmentProviderAsset.DelimiterAsset[0]; + private DensityAsset densityAsset = DensityAsset.getFallbackAsset(); + + public DensityDelimitedEnvironmentProviderAsset() { + } + + @Nonnull + @Override + public EnvironmentProvider build(@Nonnull EnvironmentProviderAsset.Argument argument) { + if (super.isSkipped()) { + return EnvironmentProvider.noEnvironmentProvider(); + } else { + List> delimiters = new ArrayList<>(this.delimiterAssets.length); + + for (DensityDelimitedEnvironmentProviderAsset.DelimiterAsset delimiterAsset : this.delimiterAssets) { + delimiters.add(delimiterAsset.build(argument)); + } + + Density density = this.densityAsset.build(DensityAsset.from(argument)); + return new DensityDelimitedEnvironmentProvider(delimiters, density); + } + } + + @Override + public void cleanUp() { + this.densityAsset.cleanUp(); + + for (DensityDelimitedEnvironmentProviderAsset.DelimiterAsset delimiterAsset : this.delimiterAssets) { + delimiterAsset.cleanUp(); + } + } + + public static class DelimiterAsset + implements Cleanable, + JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + DensityDelimitedEnvironmentProviderAsset.DelimiterAsset.class, + DensityDelimitedEnvironmentProviderAsset.DelimiterAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Range", RangeDoubleAsset.CODEC, true), (t, value) -> t.rangeAsset = value, t -> t.rangeAsset) + .add() + .append( + new KeyedCodec<>("Environment", EnvironmentProviderAsset.CODEC, true), + (t, value) -> t.environmentProviderAsset = value, + t -> t.environmentProviderAsset + ) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private RangeDoubleAsset rangeAsset = new RangeDoubleAsset(); + private EnvironmentProviderAsset environmentProviderAsset = EnvironmentProviderAsset.getFallbackAsset(); + + public DelimiterAsset() { + } + + @Nonnull + public DelimiterDouble build(@Nonnull EnvironmentProviderAsset.Argument argument) { + RangeDouble range = this.rangeAsset.build(); + EnvironmentProvider environmentProvider = this.environmentProviderAsset.build(argument); + return new DelimiterDouble<>(range, environmentProvider); + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + this.environmentProviderAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/environmentproviders/EnvironmentProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/environmentproviders/EnvironmentProviderAsset.java new file mode 100644 index 0000000..1953967 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/environmentproviders/EnvironmentProviderAsset.java @@ -0,0 +1,99 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.environmentproviders; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.environmentproviders.EnvironmentProvider; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; + +public abstract class EnvironmentProviderAsset implements Cleanable, JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + private static final Map exportedNodes = new ConcurrentHashMap<>(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(EnvironmentProviderAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(EnvironmentProviderAsset.class) + .append(new KeyedCodec<>("Skip", Codec.BOOLEAN, false), (t, k) -> t.skip = k, t -> t.skip) + .add() + .append(new KeyedCodec<>("ExportAs", Codec.STRING, false), (t, k) -> t.exportName = k, t -> t.exportName) + .add() + .afterDecode(asset -> { + if (asset.exportName != null && !asset.exportName.isEmpty()) { + if (exportedNodes.containsKey(asset.exportName)) { + LoggerUtil.getLogger().warning("Duplicate export name for asset: " + asset.exportName); + } + + exportedNodes.put(asset.exportName, asset); + LoggerUtil.getLogger().fine("Registered imported node asset with name '" + asset.exportName + "' with asset id '" + asset.id); + } + }) + .build(); + private String id; + private AssetExtraInfo.Data data; + private boolean skip = false; + private String exportName = ""; + + protected EnvironmentProviderAsset() { + } + + public abstract EnvironmentProvider build(@Nonnull EnvironmentProviderAsset.Argument var1); + + @Nonnull + public static EnvironmentProviderAsset getFallbackAsset() { + return new ConstantEnvironmentProviderAsset(); + } + + public boolean isSkipped() { + return this.skip; + } + + public static EnvironmentProviderAsset getExportedAsset(@Nonnull String name) { + return exportedNodes.get(name); + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + } + + public static class Argument { + public SeedBox parentSeed; + public MaterialCache materialCache; + public ReferenceBundle referenceBundle; + public WorkerIndexer workerIndexer; + + public Argument( + @Nonnull SeedBox parentSeed, @Nonnull MaterialCache materialCache, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer + ) { + this.parentSeed = parentSeed; + this.materialCache = materialCache; + this.referenceBundle = referenceBundle; + this.workerIndexer = workerIndexer; + } + + public Argument(@Nonnull EnvironmentProviderAsset.Argument argument) { + this.parentSeed = argument.parentSeed; + this.materialCache = argument.materialCache; + this.referenceBundle = argument.referenceBundle; + this.workerIndexer = argument.workerIndexer; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/interpolationasset/BiomeFrontierAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/interpolationasset/BiomeFrontierAsset.java new file mode 100644 index 0000000..4ecaf55 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/interpolationasset/BiomeFrontierAsset.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.interpolationasset; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; + +public class BiomeFrontierAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + BiomeFrontierAsset.class, + BiomeFrontierAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("InterpolationRadius", Codec.INTEGER, true), (t, k) -> t.interpolationRadius = k, t -> t.interpolationRadius) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private int interpolationRadius = 1; + + private BiomeFrontierAsset() { + } + + public int getInterpolationRadius() { + return this.interpolationRadius; + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/material/MaterialAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/material/MaterialAsset.java new file mode 100644 index 0000000..aeb5154 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/material/MaterialAsset.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.material; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.material.FluidMaterial; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.material.SolidMaterial; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import javax.annotation.Nonnull; + +public class MaterialAsset implements JsonAssetWithMap>, Cleanable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + MaterialAsset.class, + MaterialAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Solid", Codec.STRING, true), (t, value) -> t.solidName = value, t -> t.solidName) + .add() + .append(new KeyedCodec<>("Fluid", Codec.STRING, true), (t, value) -> t.fluidName = value, t -> t.fluidName) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + @Nonnull + private String solidName = ""; + @Nonnull + private String fluidName = ""; + + public MaterialAsset() { + } + + public MaterialAsset(@Nonnull String solidName, @Nonnull String fluidName) { + this.solidName = solidName; + this.fluidName = fluidName; + } + + public Material build(@Nonnull MaterialCache materialCache) { + SolidMaterial solid = materialCache.EMPTY_AIR; + if (!this.solidName.isEmpty()) { + solid = materialCache.getSolidMaterial(this.solidName); + } + + FluidMaterial fluid = materialCache.EMPTY_FLUID; + if (!this.fluidName.isEmpty()) { + fluid = materialCache.getFluidMaterial(this.fluidName); + } + + return new Material(solid, fluid); + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/ConstantMaterialProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/ConstantMaterialProviderAsset.java new file mode 100644 index 0000000..c1e1eca --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/ConstantMaterialProviderAsset.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.material.MaterialAsset; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.ConstantMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class ConstantMaterialProviderAsset extends MaterialProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ConstantMaterialProviderAsset.class, ConstantMaterialProviderAsset::new, MaterialProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Material", MaterialAsset.CODEC, true), (asset, value) -> asset.materialAsset = value, asset -> asset.materialAsset) + .add() + .build(); + private MaterialAsset materialAsset = new MaterialAsset(); + + public ConstantMaterialProviderAsset() { + } + + @Nonnull + @Override + public MaterialProvider build(@Nonnull MaterialProviderAsset.Argument argument) { + if (super.skip()) { + return MaterialProvider.noMaterialProvider(); + } else if (this.materialAsset == null) { + return new ConstantMaterialProvider<>(null); + } else { + Material material = this.materialAsset.build(argument.materialCache); + return new ConstantMaterialProvider<>(material); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/DownwardDepthMaterialProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/DownwardDepthMaterialProviderAsset.java new file mode 100644 index 0000000..276e766 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/DownwardDepthMaterialProviderAsset.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.DownwardDepthMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class DownwardDepthMaterialProviderAsset extends MaterialProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DownwardDepthMaterialProviderAsset.class, DownwardDepthMaterialProviderAsset::new, MaterialProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Depth", Codec.INTEGER, true), (t, k) -> t.depth = k, k -> k.depth) + .add() + .append(new KeyedCodec<>("Material", MaterialProviderAsset.CODEC, true), (t, k) -> t.materialProviderAsset = k, k -> k.materialProviderAsset) + .add() + .build(); + private int depth = 0; + private MaterialProviderAsset materialProviderAsset = new ConstantMaterialProviderAsset(); + + public DownwardDepthMaterialProviderAsset() { + } + + @Nonnull + @Override + public MaterialProvider build(@Nonnull MaterialProviderAsset.Argument argument) { + return (MaterialProvider)(super.skip() + ? MaterialProvider.noMaterialProvider() + : new DownwardDepthMaterialProvider<>(this.materialProviderAsset.build(argument), this.depth)); + } + + @Override + public void cleanUp() { + this.materialProviderAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/DownwardSpaceMaterialProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/DownwardSpaceMaterialProviderAsset.java new file mode 100644 index 0000000..59b289d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/DownwardSpaceMaterialProviderAsset.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.DownwardSpaceMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class DownwardSpaceMaterialProviderAsset extends MaterialProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DownwardSpaceMaterialProviderAsset.class, DownwardSpaceMaterialProviderAsset::new, MaterialProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Space", Codec.INTEGER, true), (t, k) -> t.space = k, k -> k.space) + .add() + .append(new KeyedCodec<>("Material", MaterialProviderAsset.CODEC, true), (t, k) -> t.materialProviderAsset = k, k -> k.materialProviderAsset) + .add() + .build(); + private int space = 0; + private MaterialProviderAsset materialProviderAsset = new ConstantMaterialProviderAsset(); + + public DownwardSpaceMaterialProviderAsset() { + } + + @Nonnull + @Override + public MaterialProvider build(@Nonnull MaterialProviderAsset.Argument argument) { + return (MaterialProvider)(super.skip() + ? MaterialProvider.noMaterialProvider() + : new DownwardSpaceMaterialProvider<>(this.materialProviderAsset.build(argument), this.space)); + } + + @Override + public void cleanUp() { + this.materialProviderAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/FieldFunctionMaterialProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/FieldFunctionMaterialProviderAsset.java new file mode 100644 index 0000000..43d132a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/FieldFunctionMaterialProviderAsset.java @@ -0,0 +1,111 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ConstantDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.FieldFunctionMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class FieldFunctionMaterialProviderAsset extends MaterialProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + FieldFunctionMaterialProviderAsset.class, FieldFunctionMaterialProviderAsset::new, MaterialProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("FieldFunction", DensityAsset.CODEC, true), (t, k) -> t.densityAsset = k, t -> t.densityAsset) + .add() + .append( + new KeyedCodec<>( + "Delimiters", + new ArrayCodec<>(FieldFunctionMaterialProviderAsset.DelimiterAsset.CODEC, FieldFunctionMaterialProviderAsset.DelimiterAsset[]::new), + true + ), + (t, k) -> t.delimiterAssets = k, + k -> k.delimiterAssets + ) + .add() + .build(); + private DensityAsset densityAsset = new ConstantDensityAsset(); + private FieldFunctionMaterialProviderAsset.DelimiterAsset[] delimiterAssets = new FieldFunctionMaterialProviderAsset.DelimiterAsset[0]; + + public FieldFunctionMaterialProviderAsset() { + } + + @Nonnull + @Override + public MaterialProvider build(@Nonnull MaterialProviderAsset.Argument argument) { + if (super.skip()) { + return MaterialProvider.noMaterialProvider(); + } else { + Density functionTree = this.densityAsset.build(DensityAsset.from(argument)); + ArrayList> delimitersList = new ArrayList<>(this.delimiterAssets.length); + + for (FieldFunctionMaterialProviderAsset.DelimiterAsset delimiterAsset : this.delimiterAssets) { + MaterialProvider materialProvider = delimiterAsset.materialProviderAsset.build(argument); + FieldFunctionMaterialProvider.FieldDelimiter delimiter = new FieldFunctionMaterialProvider.FieldDelimiter<>( + materialProvider, delimiterAsset.from, delimiterAsset.to + ); + delimitersList.add(delimiter); + } + + return new FieldFunctionMaterialProvider<>(functionTree, delimitersList); + } + } + + @Override + public void cleanUp() { + this.densityAsset.cleanUp(); + + for (FieldFunctionMaterialProviderAsset.DelimiterAsset delimiterAsset : this.delimiterAssets) { + delimiterAsset.cleanUp(); + } + } + + public static class DelimiterAsset + implements Cleanable, + JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + FieldFunctionMaterialProviderAsset.DelimiterAsset.class, + FieldFunctionMaterialProviderAsset.DelimiterAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("From", Codec.DOUBLE, true), (t, y) -> t.from = y, t -> t.from) + .add() + .append(new KeyedCodec<>("To", Codec.DOUBLE, true), (t, out) -> t.to = out, t -> t.to) + .add() + .append(new KeyedCodec<>("Material", MaterialProviderAsset.CODEC, true), (t, out) -> t.materialProviderAsset = out, t -> t.materialProviderAsset) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private double from = 0.0; + private double to = 0.0; + private MaterialProviderAsset materialProviderAsset = new ConstantMaterialProviderAsset(); + + public DelimiterAsset() { + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + this.materialProviderAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/ImportedMaterialProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/ImportedMaterialProviderAsset.java new file mode 100644 index 0000000..8481824 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/ImportedMaterialProviderAsset.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.logger.HytaleLogger; +import javax.annotation.Nonnull; + +public class ImportedMaterialProviderAsset extends MaterialProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ImportedMaterialProviderAsset.class, ImportedMaterialProviderAsset::new, MaterialProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Name", Codec.STRING, true), (t, k) -> t.name = k, k -> k.name) + .add() + .build(); + private String name = ""; + + public ImportedMaterialProviderAsset() { + } + + @Override + public MaterialProvider build(@Nonnull MaterialProviderAsset.Argument argument) { + if (super.skip()) { + return MaterialProvider.noMaterialProvider(); + } else if (this.name != null && !this.name.isEmpty()) { + MaterialProviderAsset exportedAsset = MaterialProviderAsset.getExportedAsset(this.name); + return exportedAsset == null ? MaterialProvider.noMaterialProvider() : exportedAsset.build(argument); + } else { + HytaleLogger.getLogger().atWarning().log("An exported Material Provider with the name does not exist: " + this.name); + return MaterialProvider.noMaterialProvider(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/MaterialProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/MaterialProviderAsset.java new file mode 100644 index 0000000..5942f52 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/MaterialProviderAsset.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.PropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; + +public abstract class MaterialProviderAsset implements Cleanable, JsonAssetWithMap> { + private static final MaterialProviderAsset[] EMPTY_INPUTS = new MaterialProviderAsset[0]; + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + private static final Map exportedNodes = new ConcurrentHashMap<>(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(MaterialProviderAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(MaterialProviderAsset.class) + .append(new KeyedCodec<>("Skip", Codec.BOOLEAN, false), (t, k) -> t.skip = k, t -> t.skip) + .add() + .append(new KeyedCodec<>("ExportAs", Codec.STRING, false), (t, k) -> t.exportName = k, t -> t.exportName) + .add() + .afterDecode(asset -> { + if (asset.exportName != null && !asset.exportName.isEmpty()) { + if (exportedNodes.containsKey(asset.exportName)) { + LoggerUtil.getLogger().warning("Duplicate export name for asset: " + asset.exportName); + } + + exportedNodes.put(asset.exportName, asset); + LoggerUtil.getLogger().fine("Registered imported node asset with name '" + asset.exportName + "' with asset id '" + asset.id); + } + }) + .build(); + private String id; + private AssetExtraInfo.Data data; + private boolean skip = false; + private String exportName = ""; + + protected MaterialProviderAsset() { + } + + public abstract MaterialProvider build(@Nonnull MaterialProviderAsset.Argument var1); + + public boolean skip() { + return this.skip; + } + + public static MaterialProviderAsset getExportedAsset(@Nonnull String name) { + return exportedNodes.get(name); + } + + public String getId() { + return this.id; + } + + public static MaterialProviderAsset.Argument argumentFrom(@Nonnull DensityAsset.Argument argument, @Nonnull MaterialCache materialCache) { + return new MaterialProviderAsset.Argument(argument.parentSeed, materialCache, argument.referenceBundle, argument.workerIndexer); + } + + public static MaterialProviderAsset.Argument argumentFrom(@Nonnull PropAsset.Argument argument) { + return new MaterialProviderAsset.Argument(argument.parentSeed, argument.materialCache, argument.referenceBundle, argument.workerIndexer); + } + + @Override + public void cleanUp() { + } + + public static class Argument { + public SeedBox parentSeed; + public MaterialCache materialCache; + public ReferenceBundle referenceBundle; + public WorkerIndexer workerIndexer; + + public Argument( + @Nonnull SeedBox parentSeed, @Nonnull MaterialCache materialCache, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer + ) { + this.parentSeed = parentSeed; + this.materialCache = materialCache; + this.referenceBundle = referenceBundle; + this.workerIndexer = workerIndexer; + } + + public Argument(@Nonnull MaterialProviderAsset.Argument argument) { + this.parentSeed = argument.parentSeed; + this.materialCache = argument.materialCache; + this.referenceBundle = argument.referenceBundle; + this.workerIndexer = argument.workerIndexer; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/QueueMaterialProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/QueueMaterialProviderAsset.java new file mode 100644 index 0000000..b0752b9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/QueueMaterialProviderAsset.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.QueueMaterialProvider; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class QueueMaterialProviderAsset extends MaterialProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + QueueMaterialProviderAsset.class, QueueMaterialProviderAsset::new, MaterialProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Queue", new ArrayCodec<>(MaterialProviderAsset.CODEC, MaterialProviderAsset[]::new), true), (t, k) -> t.queue = k, k -> k.queue) + .add() + .build(); + private MaterialProviderAsset[] queue = new MaterialProviderAsset[0]; + + public QueueMaterialProviderAsset() { + } + + @Nonnull + @Override + public MaterialProvider build(@Nonnull MaterialProviderAsset.Argument argument) { + if (super.skip()) { + return MaterialProvider.noMaterialProvider(); + } else { + ArrayList> queueList = new ArrayList<>(this.queue.length); + + for (MaterialProviderAsset m : this.queue) { + if (m == null) { + LoggerUtil.getLogger().warning("Null element in queue provided."); + } else { + queueList.add(m.build(argument)); + } + } + + return new QueueMaterialProvider<>(queueList); + } + } + + @Override + public void cleanUp() { + for (MaterialProviderAsset materialProviderAsset : this.queue) { + materialProviderAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/SimpleHorizontalMaterialProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/SimpleHorizontalMaterialProviderAsset.java new file mode 100644 index 0000000..4346c14 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/SimpleHorizontalMaterialProviderAsset.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.BiDouble2DoubleFunction; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.HorizontalMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.BaseHeightReference; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.logger.HytaleLogger; +import javax.annotation.Nonnull; + +public class SimpleHorizontalMaterialProviderAsset extends MaterialProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SimpleHorizontalMaterialProviderAsset.class, SimpleHorizontalMaterialProviderAsset::new, MaterialProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("TopY", Codec.INTEGER, true), (t, k) -> t.topY = k, k -> k.topY) + .add() + .append(new KeyedCodec<>("BottomY", Codec.INTEGER, true), (t, k) -> t.bottomY = k, k -> k.bottomY) + .add() + .append(new KeyedCodec<>("Material", MaterialProviderAsset.CODEC, true), (t, k) -> t.materialProviderAsset = k, k -> k.materialProviderAsset) + .add() + .append(new KeyedCodec<>("TopBaseHeight", Codec.STRING, false), (t, k) -> t.topBaseHeightName = k, t -> t.topBaseHeightName) + .add() + .append(new KeyedCodec<>("BottomBaseHeight", Codec.STRING, false), (t, k) -> t.bottomBaseHeightName = k, t -> t.bottomBaseHeightName) + .add() + .build(); + private int topY = 0; + private int bottomY = 0; + private MaterialProviderAsset materialProviderAsset = new ConstantMaterialProviderAsset(); + private String topBaseHeightName = ""; + private String bottomBaseHeightName = ""; + + public SimpleHorizontalMaterialProviderAsset() { + } + + @Nonnull + @Override + public MaterialProvider build(@Nonnull MaterialProviderAsset.Argument argument) { + if (super.skip()) { + return MaterialProvider.noMaterialProvider(); + } else { + BiDouble2DoubleFunction topFunction = (x, z) -> this.topY; + BiDouble2DoubleFunction bottomFunction = (x, z) -> this.bottomY; + if (!this.topBaseHeightName.isEmpty()) { + BaseHeightReference topHeightDataLayer = argument.referenceBundle.getLayerWithName(this.topBaseHeightName, BaseHeightReference.class); + if (topHeightDataLayer != null) { + BiDouble2DoubleFunction baseHeight = topHeightDataLayer.getHeightFunction(); + topFunction = (x, z) -> baseHeight.apply(x, z) + this.topY; + } else { + HytaleLogger.getLogger() + .atConfig() + .log("Couldn't find height data layer with name \"" + this.topBaseHeightName + "\", using a zero-constant Density node."); + } + } + + if (!this.bottomBaseHeightName.isEmpty()) { + BaseHeightReference bottomHeightDataLayer = argument.referenceBundle.getLayerWithName(this.bottomBaseHeightName, BaseHeightReference.class); + if (bottomHeightDataLayer != null) { + BiDouble2DoubleFunction baseHeight = bottomHeightDataLayer.getHeightFunction(); + bottomFunction = (x, z) -> baseHeight.apply(x, z) + this.bottomY; + } else { + HytaleLogger.getLogger() + .atConfig() + .log("Couldn't find height data layer with name \"" + this.bottomBaseHeightName + "\", using a zero-constant Density node."); + } + } + + return new HorizontalMaterialProvider<>(this.materialProviderAsset.build(argument), topFunction::apply, bottomFunction::apply); + } + } + + @Override + public void cleanUp() { + this.materialProviderAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/SolidityMaterialProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/SolidityMaterialProviderAsset.java new file mode 100644 index 0000000..868ac3a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/SolidityMaterialProviderAsset.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.SolidityMaterialProvider; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class SolidityMaterialProviderAsset extends MaterialProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SolidityMaterialProviderAsset.class, SolidityMaterialProviderAsset::new, MaterialProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Solid", MaterialProviderAsset.CODEC, true), (t, k) -> t.solidMaterialProvider = k, k -> k.solidMaterialProvider) + .add() + .append(new KeyedCodec<>("Empty", MaterialProviderAsset.CODEC, true), (t, k) -> t.emptyMaterialProvider = k, k -> k.emptyMaterialProvider) + .add() + .build(); + private MaterialProviderAsset solidMaterialProvider = new ConstantMaterialProviderAsset(); + private MaterialProviderAsset emptyMaterialProvider = new ConstantMaterialProviderAsset(); + + public SolidityMaterialProviderAsset() { + } + + @Nonnull + @Override + public MaterialProvider build(@Nonnull MaterialProviderAsset.Argument argument) { + return (MaterialProvider)(super.skip() + ? MaterialProvider.noMaterialProvider() + : new SolidityMaterialProvider<>(this.solidMaterialProvider.build(argument), this.emptyMaterialProvider.build(argument))); + } + + @Override + public void cleanUp() { + this.solidMaterialProvider.cleanUp(); + this.emptyMaterialProvider.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/StripedMaterialProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/StripedMaterialProviderAsset.java new file mode 100644 index 0000000..238b531 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/StripedMaterialProviderAsset.java @@ -0,0 +1,91 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.StripedMaterialProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class StripedMaterialProviderAsset extends MaterialProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + StripedMaterialProviderAsset.class, StripedMaterialProviderAsset::new, MaterialProviderAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>("Stripes", new ArrayCodec<>(StripedMaterialProviderAsset.StripeAsset.CODEC, StripedMaterialProviderAsset.StripeAsset[]::new), true), + (t, k) -> t.stripeAssets = k, + k -> k.stripeAssets + ) + .add() + .append(new KeyedCodec<>("Material", MaterialProviderAsset.CODEC, true), (t, k) -> t.materialProviderAsset = k, k -> k.materialProviderAsset) + .add() + .build(); + private StripedMaterialProviderAsset.StripeAsset[] stripeAssets = new StripedMaterialProviderAsset.StripeAsset[0]; + private MaterialProviderAsset materialProviderAsset = new ConstantMaterialProviderAsset(); + + public StripedMaterialProviderAsset() { + } + + @Nonnull + @Override + public MaterialProvider build(@Nonnull MaterialProviderAsset.Argument argument) { + if (super.skip()) { + return MaterialProvider.noMaterialProvider(); + } else { + ArrayList stripes = new ArrayList<>(); + + for (StripedMaterialProviderAsset.StripeAsset asset : this.stripeAssets) { + if (asset == null) { + LoggerUtil.getLogger().warning("Couldn't load a strip asset, will skip it."); + } else { + StripedMaterialProvider.Stripe stripe = new StripedMaterialProvider.Stripe(asset.topY, asset.bottomY); + stripes.add(stripe); + } + } + + MaterialProvider materialProvider = this.materialProviderAsset.build(argument); + return new StripedMaterialProvider<>(materialProvider, stripes); + } + } + + @Override + public void cleanUp() { + this.materialProviderAsset.cleanUp(); + } + + public static class StripeAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + StripedMaterialProviderAsset.StripeAsset.class, + StripedMaterialProviderAsset.StripeAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("TopY", Codec.INTEGER, true), (t, y) -> t.topY = y, t -> t.bottomY) + .add() + .append(new KeyedCodec<>("BottomY", Codec.INTEGER, true), (t, y) -> t.bottomY = y, t -> t.bottomY) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private int topY; + private int bottomY; + + public StripeAsset() { + } + + public String getId() { + return this.id; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/TerrainDensityMaterialProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/TerrainDensityMaterialProviderAsset.java new file mode 100644 index 0000000..0ae9cbe --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/TerrainDensityMaterialProviderAsset.java @@ -0,0 +1,102 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.TerrainDensityMaterialProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class TerrainDensityMaterialProviderAsset extends MaterialProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + TerrainDensityMaterialProviderAsset.class, TerrainDensityMaterialProviderAsset::new, MaterialProviderAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>( + "Delimiters", + new ArrayCodec<>(TerrainDensityMaterialProviderAsset.DelimiterAsset.CODEC, TerrainDensityMaterialProviderAsset.DelimiterAsset[]::new), + true + ), + (t, k) -> t.delimiterAssets = k, + k -> k.delimiterAssets + ) + .add() + .build(); + private TerrainDensityMaterialProviderAsset.DelimiterAsset[] delimiterAssets = new TerrainDensityMaterialProviderAsset.DelimiterAsset[0]; + + public TerrainDensityMaterialProviderAsset() { + } + + @Nonnull + @Override + public MaterialProvider build(@Nonnull MaterialProviderAsset.Argument argument) { + if (super.skip()) { + return MaterialProvider.noMaterialProvider(); + } else { + ArrayList> delimitersList = new ArrayList<>(this.delimiterAssets.length); + + for (TerrainDensityMaterialProviderAsset.DelimiterAsset delimiterAsset : this.delimiterAssets) { + MaterialProvider materialProvider = delimiterAsset.materialProviderAsset.build(argument); + TerrainDensityMaterialProvider.FieldDelimiter delimiter = new TerrainDensityMaterialProvider.FieldDelimiter<>( + materialProvider, delimiterAsset.from, delimiterAsset.to + ); + delimitersList.add(delimiter); + } + + return new TerrainDensityMaterialProvider<>(delimitersList); + } + } + + @Override + public void cleanUp() { + for (TerrainDensityMaterialProviderAsset.DelimiterAsset delimiterAsset : this.delimiterAssets) { + delimiterAsset.cleanUp(); + } + } + + public static class DelimiterAsset + implements Cleanable, + JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + TerrainDensityMaterialProviderAsset.DelimiterAsset.class, + TerrainDensityMaterialProviderAsset.DelimiterAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("From", Codec.DOUBLE, true), (t, y) -> t.from = y, t -> t.from) + .add() + .append(new KeyedCodec<>("To", Codec.DOUBLE, true), (t, out) -> t.to = out, t -> t.to) + .add() + .append(new KeyedCodec<>("Material", MaterialProviderAsset.CODEC, true), (t, out) -> t.materialProviderAsset = out, t -> t.materialProviderAsset) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private double from = 0.0; + private double to = 0.0; + private MaterialProviderAsset materialProviderAsset = new ConstantMaterialProviderAsset(); + + public DelimiterAsset() { + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + this.materialProviderAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/UpwardDepthMaterialProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/UpwardDepthMaterialProviderAsset.java new file mode 100644 index 0000000..32f3ac7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/UpwardDepthMaterialProviderAsset.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.UpwardDepthMaterialProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class UpwardDepthMaterialProviderAsset extends MaterialProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + UpwardDepthMaterialProviderAsset.class, UpwardDepthMaterialProviderAsset::new, MaterialProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Depth", Codec.INTEGER, true), (t, k) -> t.depth = k, k -> k.depth) + .add() + .append(new KeyedCodec<>("Material", MaterialProviderAsset.CODEC, true), (t, k) -> t.materialProviderAsset = k, k -> k.materialProviderAsset) + .add() + .build(); + private int depth = 0; + private MaterialProviderAsset materialProviderAsset = new ConstantMaterialProviderAsset(); + + public UpwardDepthMaterialProviderAsset() { + } + + @Nonnull + @Override + public MaterialProvider build(@Nonnull MaterialProviderAsset.Argument argument) { + return (MaterialProvider)(super.skip() + ? MaterialProvider.noMaterialProvider() + : new UpwardDepthMaterialProvider<>(this.materialProviderAsset.build(argument), this.depth)); + } + + @Override + public void cleanUp() { + this.materialProviderAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/UpwardSpaceMaterialProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/UpwardSpaceMaterialProviderAsset.java new file mode 100644 index 0000000..dbdbed5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/UpwardSpaceMaterialProviderAsset.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.UpwardSpaceMaterialProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class UpwardSpaceMaterialProviderAsset extends MaterialProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + UpwardSpaceMaterialProviderAsset.class, UpwardSpaceMaterialProviderAsset::new, MaterialProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Space", Codec.INTEGER, true), (t, k) -> t.space = k, k -> k.space) + .add() + .append(new KeyedCodec<>("Material", MaterialProviderAsset.CODEC, true), (t, k) -> t.materialProviderAsset = k, k -> k.materialProviderAsset) + .add() + .build(); + private int space = 0; + private MaterialProviderAsset materialProviderAsset = new ConstantMaterialProviderAsset(); + + public UpwardSpaceMaterialProviderAsset() { + } + + @Nonnull + @Override + public MaterialProvider build(@Nonnull MaterialProviderAsset.Argument argument) { + return new UpwardSpaceMaterialProvider<>(this.materialProviderAsset.build(argument), this.space); + } + + @Override + public void cleanUp() { + this.materialProviderAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/WeightedMaterialProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/WeightedMaterialProviderAsset.java new file mode 100644 index 0000000..642ec23 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/WeightedMaterialProviderAsset.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.WeightedMap; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.WeightedMaterialProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class WeightedMaterialProviderAsset extends MaterialProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + WeightedMaterialProviderAsset.class, WeightedMaterialProviderAsset::new, MaterialProviderAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>( + "WeightedMaterials", + new ArrayCodec<>(WeightedMaterialProviderAsset.WeightedMaterialAsset.CODEC, WeightedMaterialProviderAsset.WeightedMaterialAsset[]::new), + true + ), + (t, k) -> t.weighedMapEntries = k, + k -> k.weighedMapEntries + ) + .add() + .append(new KeyedCodec<>("SkipChance", Codec.DOUBLE, true), (t, k) -> t.skipChance = k, k -> k.skipChance) + .add() + .append(new KeyedCodec<>("Seed", Codec.STRING, true), (t, k) -> t.seed = k, k -> k.seed) + .add() + .build(); + private WeightedMaterialProviderAsset.WeightedMaterialAsset[] weighedMapEntries = new WeightedMaterialProviderAsset.WeightedMaterialAsset[0]; + private double skipChance = 0.0; + private String seed = ""; + + public WeightedMaterialProviderAsset() { + } + + @Nonnull + @Override + public MaterialProvider build(@Nonnull MaterialProviderAsset.Argument argument) { + if (super.skip()) { + return MaterialProvider.noMaterialProvider(); + } else { + WeightedMap> weightMap = new WeightedMap<>(); + + for (WeightedMaterialProviderAsset.WeightedMaterialAsset entry : this.weighedMapEntries) { + weightMap.add(entry.materialProviderAsset.build(argument), entry.weight); + } + + return new WeightedMaterialProvider<>(weightMap, argument.parentSeed.child(this.seed), this.skipChance); + } + } + + @Override + public void cleanUp() { + for (WeightedMaterialProviderAsset.WeightedMaterialAsset weightedMaterialAsset : this.weighedMapEntries) { + weightedMaterialAsset.cleanUp(); + } + } + + public static class WeightedMaterialAsset + implements Cleanable, + JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + WeightedMaterialProviderAsset.WeightedMaterialAsset.class, + WeightedMaterialProviderAsset.WeightedMaterialAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Weight", Codec.DOUBLE, true), (t, y) -> t.weight = y, t -> t.weight) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append(new KeyedCodec<>("Material", MaterialProviderAsset.CODEC, true), (t, out) -> t.materialProviderAsset = out, t -> t.materialProviderAsset) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private double weight = 1.0; + private MaterialProviderAsset materialProviderAsset; + + public WeightedMaterialAsset() { + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + this.materialProviderAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/SpaceAndDepthMaterialProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/SpaceAndDepthMaterialProviderAsset.java new file mode 100644 index 0000000..4165ae5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/SpaceAndDepthMaterialProviderAsset.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.MaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets.AlwaysTrueConditionAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets.ConditionAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.layerassets.LayerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class SpaceAndDepthMaterialProviderAsset extends MaterialProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SpaceAndDepthMaterialProviderAsset.class, SpaceAndDepthMaterialProviderAsset::new, MaterialProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("LayerContext", SpaceAndDepthMaterialProvider.LayerContextType.CODEC, true), (t, k) -> t.layerContext = k, k -> k.layerContext) + .add() + .append(new KeyedCodec<>("MaxExpectedDepth", Codec.INTEGER, true), (t, k) -> t.maxDistance = k, k -> k.maxDistance) + .addValidator(Validators.greaterThanOrEqual(0)) + .add() + .append(new KeyedCodec<>("Condition", ConditionAsset.CODEC, false), (t, k) -> t.conditionAsset = k, k -> k.conditionAsset) + .add() + .append( + new KeyedCodec<>("Layers", new ArrayCodec<>(LayerAsset.CODEC, LayerAsset[]::new), false), (t, k) -> t.layerAssets = k, k -> k.layerAssets + ) + .addValidator(Validators.nonNullArrayElements()) + .add() + .build(); + private SpaceAndDepthMaterialProvider.LayerContextType layerContext = SpaceAndDepthMaterialProvider.LayerContextType.DEPTH_INTO_FLOOR; + private int maxDistance = 16; + private ConditionAsset conditionAsset = new AlwaysTrueConditionAsset(); + private LayerAsset[] layerAssets = new LayerAsset[0]; + + public SpaceAndDepthMaterialProviderAsset() { + } + + @Nonnull + @Override + public MaterialProvider build(@Nonnull MaterialProviderAsset.Argument argument) { + if (super.skip()) { + return MaterialProvider.noMaterialProvider(); + } else { + SpaceAndDepthMaterialProvider.Condition condition = this.conditionAsset.build(); + ArrayList> layerList = new ArrayList<>(this.layerAssets.length); + + for (LayerAsset asset : this.layerAssets) { + layerList.add(asset.build(argument)); + } + + return new SpaceAndDepthMaterialProvider<>(this.layerContext, layerList, condition, this.maxDistance); + } + } + + @Override + public void cleanUp() { + for (LayerAsset layerAsset : this.layerAssets) { + layerAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/AlwaysTrueConditionAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/AlwaysTrueConditionAsset.java new file mode 100644 index 0000000..2a482bb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/AlwaysTrueConditionAsset.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets; + +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions.AlwaysTrueCondition; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class AlwaysTrueConditionAsset extends ConditionAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + AlwaysTrueConditionAsset.class, AlwaysTrueConditionAsset::new, ConditionAsset.ABSTRACT_CODEC + ) + .build(); + + public AlwaysTrueConditionAsset() { + } + + @Nonnull + @Override + public SpaceAndDepthMaterialProvider.Condition build() { + return AlwaysTrueCondition.INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/AndConditionAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/AndConditionAsset.java new file mode 100644 index 0000000..1266cfa --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/AndConditionAsset.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets; + +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions.AndCondition; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class AndConditionAsset extends ConditionAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + AndConditionAsset.class, AndConditionAsset::new, ConditionAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>("Conditions", new ArrayCodec<>(ConditionAsset.CODEC, ConditionAsset[]::new), true), + (t, k) -> t.conditionAssets = k, + k -> k.conditionAssets + ) + .addValidator(Validators.nonNullArrayElements()) + .add() + .build(); + private ConditionAsset[] conditionAssets = new ConditionAsset[0]; + + public AndConditionAsset() { + } + + @Nonnull + @Override + public SpaceAndDepthMaterialProvider.Condition build() { + ArrayList conditions = new ArrayList<>(this.conditionAssets.length); + + for (ConditionAsset asset : this.conditionAssets) { + if (asset == null) { + LoggerUtil.getLogger().warning("Null condition asset found, skipped."); + } else { + conditions.add(asset.build()); + } + } + + return new AndCondition(conditions); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/ConditionAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/ConditionAsset.java new file mode 100644 index 0000000..4f0209b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/ConditionAsset.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; + +public abstract class ConditionAsset implements JsonAssetWithMap> { + private static final ConditionAsset[] EMPTY_INPUTS = new ConditionAsset[0]; + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(ConditionAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(ConditionAsset.class).build(); + private String id; + private AssetExtraInfo.Data data; + + protected ConditionAsset() { + } + + public abstract SpaceAndDepthMaterialProvider.Condition build(); + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/EqualsConditionAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/EqualsConditionAsset.java new file mode 100644 index 0000000..7a60699 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/EqualsConditionAsset.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets; + +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions.ConditionParameter; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions.EqualsCondition; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class EqualsConditionAsset extends ConditionAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + EqualsConditionAsset.class, EqualsConditionAsset::new, ConditionAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("ContextToCheck", ConditionParameter.CODEC, true), (t, k) -> t.parameter = k, k -> k.parameter) + .add() + .append(new KeyedCodec<>("Value", Codec.INTEGER, true), (t, k) -> t.value = k, k -> k.value) + .add() + .build(); + private ConditionParameter parameter = ConditionParameter.SPACE_ABOVE_FLOOR; + private int value = 0; + + public EqualsConditionAsset() { + } + + @Nonnull + @Override + public SpaceAndDepthMaterialProvider.Condition build() { + return new EqualsCondition(this.value, this.parameter); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/GreaterThanConditionAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/GreaterThanConditionAsset.java new file mode 100644 index 0000000..1485cb2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/GreaterThanConditionAsset.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets; + +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions.ConditionParameter; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions.GreaterThanCondition; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class GreaterThanConditionAsset extends ConditionAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + GreaterThanConditionAsset.class, GreaterThanConditionAsset::new, ConditionAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("ContextToCheck", ConditionParameter.CODEC, true), (t, k) -> t.parameter = k, k -> k.parameter) + .add() + .append(new KeyedCodec<>("Threshold", Codec.INTEGER, true), (t, k) -> t.threshold = k, k -> k.threshold) + .add() + .build(); + private ConditionParameter parameter = ConditionParameter.SPACE_ABOVE_FLOOR; + private int threshold = 0; + + public GreaterThanConditionAsset() { + } + + @Nonnull + @Override + public SpaceAndDepthMaterialProvider.Condition build() { + return new GreaterThanCondition(this.threshold, this.parameter); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/NotConditionAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/NotConditionAsset.java new file mode 100644 index 0000000..0611584 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/NotConditionAsset.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets; + +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions.NotCondition; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class NotConditionAsset extends ConditionAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + NotConditionAsset.class, NotConditionAsset::new, ConditionAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Condition", ConditionAsset.CODEC, true), (t, k) -> t.conditionAsset = k, k -> k.conditionAsset) + .add() + .build(); + private ConditionAsset conditionAsset = new AlwaysTrueConditionAsset(); + + public NotConditionAsset() { + } + + @Nonnull + @Override + public SpaceAndDepthMaterialProvider.Condition build() { + return new NotCondition(this.conditionAsset.build()); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/OrConditionAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/OrConditionAsset.java new file mode 100644 index 0000000..89b37dc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/OrConditionAsset.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets; + +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions.OrCondition; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class OrConditionAsset extends ConditionAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(OrConditionAsset.class, OrConditionAsset::new, ConditionAsset.ABSTRACT_CODEC) + .append( + new KeyedCodec<>("Conditions", new ArrayCodec<>(ConditionAsset.CODEC, ConditionAsset[]::new), true), + (t, k) -> t.conditionAssets = k, + k -> k.conditionAssets + ) + .addValidator(Validators.nonNullArrayElements()) + .add() + .build(); + private ConditionAsset[] conditionAssets = new ConditionAsset[0]; + + public OrConditionAsset() { + } + + @Nonnull + @Override + public SpaceAndDepthMaterialProvider.Condition build() { + ArrayList conditions = new ArrayList<>(this.conditionAssets.length); + + for (ConditionAsset asset : this.conditionAssets) { + if (asset == null) { + LoggerUtil.getLogger().warning("Null condition asset found, skipped."); + } else { + conditions.add(asset.build()); + } + } + + return new OrCondition(conditions); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/SmallerThanConditionAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/SmallerThanConditionAsset.java new file mode 100644 index 0000000..ab95c85 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/conditionassets/SmallerThanConditionAsset.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.conditionassets; + +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions.ConditionParameter; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions.SmallerThanCondition; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class SmallerThanConditionAsset extends ConditionAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SmallerThanConditionAsset.class, SmallerThanConditionAsset::new, ConditionAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("ContextToCheck", ConditionParameter.CODEC, true), (t, k) -> t.parameter = k, k -> k.parameter) + .add() + .append(new KeyedCodec<>("Threshold", Codec.INTEGER, true), (t, k) -> t.threshold = k, k -> k.threshold) + .add() + .build(); + private ConditionParameter parameter = ConditionParameter.SPACE_ABOVE_FLOOR; + private int threshold = 0; + + public SmallerThanConditionAsset() { + } + + @Nonnull + @Override + public SpaceAndDepthMaterialProvider.Condition build() { + return new SmallerThanCondition(this.threshold, this.parameter); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/layerassets/ConstantThicknessLayerAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/layerassets/ConstantThicknessLayerAsset.java new file mode 100644 index 0000000..013a4d5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/layerassets/ConstantThicknessLayerAsset.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.layerassets; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.ConstantMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.MaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.layers.ConstantThicknessLayer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class ConstantThicknessLayerAsset extends LayerAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ConstantThicknessLayerAsset.class, ConstantThicknessLayerAsset::new, LayerAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Thickness", Codec.INTEGER, true), (t, k) -> t.thickness = k, k -> k.thickness) + .addValidator(Validators.greaterThanOrEqual(0)) + .add() + .append(new KeyedCodec<>("Material", MaterialProviderAsset.CODEC, true), (t, k) -> t.materialProviderAsset = k, k -> k.materialProviderAsset) + .add() + .build(); + private int thickness = 0; + private MaterialProviderAsset materialProviderAsset = new ConstantMaterialProviderAsset(); + + public ConstantThicknessLayerAsset() { + } + + @Nonnull + @Override + public SpaceAndDepthMaterialProvider.Layer build(@Nonnull MaterialProviderAsset.Argument argument) { + return new ConstantThicknessLayer<>(this.thickness, this.materialProviderAsset.build(argument)); + } + + @Override + public void cleanUp() { + this.materialProviderAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/layerassets/LayerAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/layerassets/LayerAsset.java new file mode 100644 index 0000000..77f662a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/layerassets/LayerAsset.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.layerassets; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.MaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import javax.annotation.Nonnull; + +public abstract class LayerAsset implements Cleanable, JsonAssetWithMap> { + private static final LayerAsset[] EMPTY_INPUTS = new LayerAsset[0]; + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(LayerAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(LayerAsset.class).build(); + private String id; + private AssetExtraInfo.Data data; + + protected LayerAsset() { + } + + public abstract SpaceAndDepthMaterialProvider.Layer build(@Nonnull MaterialProviderAsset.Argument var1); + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/layerassets/NoiseThicknessAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/layerassets/NoiseThicknessAsset.java new file mode 100644 index 0000000..4356a4d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/layerassets/NoiseThicknessAsset.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.layerassets; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ConstantDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.ConstantMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.MaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.layers.NoiseThickness; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class NoiseThicknessAsset extends LayerAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + NoiseThicknessAsset.class, NoiseThicknessAsset::new, LayerAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("ThicknessFunctionXZ", DensityAsset.CODEC, true), (asset, k) -> asset.densityAsset = k, asset -> asset.densityAsset) + .add() + .append(new KeyedCodec<>("Material", MaterialProviderAsset.CODEC, true), (t, k) -> t.materialProviderAsset = k, k -> k.materialProviderAsset) + .add() + .build(); + private DensityAsset densityAsset = new ConstantDensityAsset(); + private MaterialProviderAsset materialProviderAsset = new ConstantMaterialProviderAsset(); + + public NoiseThicknessAsset() { + } + + @Nonnull + @Override + public SpaceAndDepthMaterialProvider.Layer build(@Nonnull MaterialProviderAsset.Argument argument) { + MaterialProvider materialProvider = this.materialProviderAsset.build(argument); + Density functionTree = this.densityAsset.build(DensityAsset.from(argument)); + return new NoiseThickness<>(functionTree, materialProvider); + } + + @Override + public void cleanUp() { + this.densityAsset.cleanUp(); + this.materialProviderAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/layerassets/RangeThicknessAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/layerassets/RangeThicknessAsset.java new file mode 100644 index 0000000..b1620c3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/layerassets/RangeThicknessAsset.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.layerassets; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.ConstantMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.MaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.layers.RangedThicknessLayer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class RangeThicknessAsset extends LayerAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + RangeThicknessAsset.class, RangeThicknessAsset::new, LayerAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("RangeMin", Codec.INTEGER, true), (t, k) -> t.rangeMin = k, k -> k.rangeMin) + .add() + .append(new KeyedCodec<>("RangeMax", Codec.INTEGER, true), (t, k) -> t.rangeMax = k, k -> k.rangeMax) + .add() + .append(new KeyedCodec<>("Material", MaterialProviderAsset.CODEC, true), (t, k) -> t.materialProviderAsset = k, k -> k.materialProviderAsset) + .add() + .append(new KeyedCodec<>("Seed", Codec.STRING, true), (t, k) -> t.seed = k, k -> k.seed) + .add() + .afterDecode(asset -> asset.rangeMax = Math.max(asset.rangeMin, asset.rangeMax)) + .build(); + private MaterialProviderAsset materialProviderAsset = new ConstantMaterialProviderAsset(); + private String seed = ""; + private int rangeMin = 0; + private int rangeMax = 0; + + public RangeThicknessAsset() { + } + + @Nonnull + @Override + public SpaceAndDepthMaterialProvider.Layer build(@Nonnull MaterialProviderAsset.Argument argument) { + MaterialProvider materialProvider = this.materialProviderAsset.build(argument); + return new RangedThicknessLayer<>(this.rangeMin, this.rangeMax, argument.parentSeed.child(this.seed), materialProvider); + } + + @Override + public void cleanUp() { + this.materialProviderAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/layerassets/WeightedThicknessLayerAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/layerassets/WeightedThicknessLayerAsset.java new file mode 100644 index 0000000..cfe0fe3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/materialproviders/spaceanddepth/layerassets/WeightedThicknessLayerAsset.java @@ -0,0 +1,91 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.spaceanddepth.layerassets; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.ConstantMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.MaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.WeightedMap; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.layers.WeightedThicknessLayer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class WeightedThicknessLayerAsset extends LayerAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + WeightedThicknessLayerAsset.class, WeightedThicknessLayerAsset::new, LayerAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>( + "PossibleThicknesses", + new ArrayCodec<>(WeightedThicknessLayerAsset.WeightedThicknessAsset.CODEC, WeightedThicknessLayerAsset.WeightedThicknessAsset[]::new), + true + ), + (t, k) -> t.possibleThicknessAssets = k, + k -> k.possibleThicknessAssets + ) + .addValidator(Validators.nonNullArrayElements()) + .add() + .append(new KeyedCodec<>("Material", MaterialProviderAsset.CODEC, true), (t, k) -> t.materialProviderAsset = k, k -> k.materialProviderAsset) + .add() + .append(new KeyedCodec<>("Seed", Codec.STRING, true), (t, k) -> t.seed = k, k -> k.seed) + .add() + .build(); + private MaterialProviderAsset materialProviderAsset = new ConstantMaterialProviderAsset(); + private String seed = ""; + private WeightedThicknessLayerAsset.WeightedThicknessAsset[] possibleThicknessAssets = new WeightedThicknessLayerAsset.WeightedThicknessAsset[0]; + + public WeightedThicknessLayerAsset() { + } + + @Nonnull + @Override + public SpaceAndDepthMaterialProvider.Layer build(@Nonnull MaterialProviderAsset.Argument argument) { + WeightedMap pool = new WeightedMap<>(); + + for (WeightedThicknessLayerAsset.WeightedThicknessAsset asset : this.possibleThicknessAssets) { + pool.add(asset.thickness, asset.weight); + } + + return new WeightedThicknessLayer<>(pool, this.materialProviderAsset.build(argument), argument.parentSeed); + } + + @Override + public void cleanUp() { + this.materialProviderAsset.cleanUp(); + } + + public static class WeightedThicknessAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + WeightedThicknessLayerAsset.WeightedThicknessAsset.class, + WeightedThicknessLayerAsset.WeightedThicknessAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Weight", Codec.DOUBLE, true), (t, y) -> t.weight = y, t -> t.weight) + .add() + .append(new KeyedCodec<>("Thickness", Codec.INTEGER, true), (t, out) -> t.thickness = out, t -> t.thickness) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private double weight; + private int thickness; + + public WeightedThicknessAsset() { + } + + public String getId() { + return this.id; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/noisegenerators/CellNoiseAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/noisegenerators/CellNoiseAsset.java new file mode 100644 index 0000000..b9b6c7f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/noisegenerators/CellNoiseAsset.java @@ -0,0 +1,89 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.noisegenerators; + +import com.hypixel.hytale.builtin.hytalegenerator.fields.FastNoiseLite; +import com.hypixel.hytale.builtin.hytalegenerator.fields.noise.CellNoiseField; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.codec.validation.Validators; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nonnull; + +public class CellNoiseAsset extends NoiseAsset { + private static Set validCellTypes = new HashSet<>(); + public static final BuilderCodec CODEC; + private double warpScale = 1.0; + private double warpAmount = 1.0; + private double scale = 1.0; + private double jitter = 0.5; + private int octaves = 1; + private String seedKey = "A"; + @Nonnull + private FastNoiseLite.CellularReturnType cellType = FastNoiseLite.CellularReturnType.CellValue; + + public CellNoiseAsset() { + } + + @Nonnull + public CellNoiseField build(@Nonnull SeedBox parentSeed) { + SeedBox childSeed = parentSeed.child(this.seedKey); + return new CellNoiseField( + childSeed.createSupplier().get(), + this.scale, + this.scale, + this.scale, + this.jitter, + this.octaves, + this.cellType, + FastNoiseLite.DomainWarpType.OpenSimplex2, + this.warpAmount, + this.warpScale + ); + } + + static { + for (FastNoiseLite.CellularReturnType e : FastNoiseLite.CellularReturnType.values()) { + validCellTypes.add(e.toString()); + } + + CODEC = BuilderCodec.builder(CellNoiseAsset.class, CellNoiseAsset::new, NoiseAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("WarpAmount", Codec.DOUBLE, true), (asset, warpAmount) -> asset.warpAmount = warpAmount, asset -> asset.warpAmount) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("WarpScale", Codec.DOUBLE, true), (asset, warpScale) -> asset.warpScale = warpScale, asset -> asset.warpScale) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("Scale", Codec.DOUBLE, true), (asset, scale) -> asset.scale = scale, asset -> asset.scale) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("Octaves", Codec.INTEGER, true), (asset, octaves) -> asset.octaves = octaves, asset -> asset.octaves) + .addValidator(Validators.greaterThan(0)) + .add() + .append(new KeyedCodec<>("Seed", Codec.STRING, true), (asset, seed) -> asset.seedKey = seed, asset -> asset.seedKey) + .add() + .append( + new KeyedCodec<>("CellType", Codec.STRING, true), + (asset, cellType) -> asset.cellType = FastNoiseLite.CellularReturnType.valueOf(cellType), + asset -> asset.cellType.name() + ) + .addValidator((LegacyValidator)((v, r) -> { + try { + FastNoiseLite.CellularReturnType.valueOf(v); + } catch (IllegalArgumentException var6) { + String msg = "Invalid CellType: " + v + ". Valid choices: "; + + for (String t : validCellTypes) { + msg = msg + " "; + msg = msg + t; + } + + r.fail(msg); + } + })) + .add() + .build(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/noisegenerators/NoiseAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/noisegenerators/NoiseAsset.java new file mode 100644 index 0000000..36df09b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/noisegenerators/NoiseAsset.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.noisegenerators; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.fields.noise.NoiseField; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import javax.annotation.Nonnull; + +public abstract class NoiseAsset implements JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(NoiseAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(NoiseAsset.class).build(); + private String id; + private AssetExtraInfo.Data data; + + protected NoiseAsset() { + } + + public abstract NoiseField build(@Nonnull SeedBox var1); + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/noisegenerators/SimplexNoiseAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/noisegenerators/SimplexNoiseAsset.java new file mode 100644 index 0000000..b884f89 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/noisegenerators/SimplexNoiseAsset.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.noisegenerators; + +import com.hypixel.hytale.builtin.hytalegenerator.fields.noise.SimplexNoiseField; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class SimplexNoiseAsset extends NoiseAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(SimplexNoiseAsset.class, SimplexNoiseAsset::new, NoiseAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Lacunarity", Codec.DOUBLE, true), (asset, lacunarity) -> asset.lacunarity = lacunarity, asset -> asset.lacunarity) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("Persistence", Codec.DOUBLE, true), (asset, persistence) -> asset.persistence = persistence, asset -> asset.persistence) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("Scale", Codec.DOUBLE, true), (asset, scale) -> asset.scale = scale, asset -> asset.scale) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("Octaves", Codec.INTEGER, true), (asset, octaves) -> asset.octaves = octaves, asset -> asset.octaves) + .addValidator(Validators.greaterThan(0)) + .add() + .append(new KeyedCodec<>("Seed", Codec.STRING, true), (asset, seed) -> asset.seedKey = seed, asset -> asset.seedKey) + .add() + .build(); + private double lacunarity = 1.0; + private double persistence = 1.0; + private double scale = 1.0; + private int octaves = 1; + private String seedKey = "A"; + + public SimplexNoiseAsset() { + } + + @Nonnull + public SimplexNoiseField build(@Nonnull SeedBox parentSeed) { + SeedBox childSeed = parentSeed.child(this.seedKey); + return SimplexNoiseField.builder() + .withAmplitudeMultiplier(this.persistence) + .withFrequencyMultiplier(this.lacunarity) + .withScale(this.scale) + .withSeed(childSeed.createSupplier().get().intValue()) + .withNumberOfOctaves(this.octaves) + .build(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/AndPatternAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/AndPatternAsset.java new file mode 100644 index 0000000..373603c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/AndPatternAsset.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.patterns.AndPattern; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class AndPatternAsset extends PatternAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(AndPatternAsset.class, AndPatternAsset::new, PatternAsset.ABSTRACT_CODEC) + .append( + new KeyedCodec<>("Patterns", new ArrayCodec<>(PatternAsset.CODEC, PatternAsset[]::new), true), (t, k) -> t.patternAssets = k, k -> k.patternAssets + ) + .add() + .build(); + private PatternAsset[] patternAssets = new PatternAsset[0]; + + public AndPatternAsset() { + } + + @Nonnull + @Override + public Pattern build(@Nonnull PatternAsset.Argument argument) { + if (super.isSkipped()) { + return Pattern.noPattern(); + } else { + ArrayList patterns = new ArrayList<>(this.patternAssets.length); + + for (PatternAsset asset : this.patternAssets) { + if (!asset.isSkipped()) { + patterns.add(asset.build(argument)); + } + } + + return new AndPattern(patterns); + } + } + + @Override + public void cleanUp() { + for (PatternAsset patternAsset : this.patternAssets) { + patternAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/BlockSetPatternAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/BlockSetPatternAsset.java new file mode 100644 index 0000000..3f6b712 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/BlockSetPatternAsset.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.MaterialSet; +import com.hypixel.hytale.builtin.hytalegenerator.assets.blockset.MaterialSetAsset; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.MaterialSetPattern; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class BlockSetPatternAsset extends PatternAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + BlockSetPatternAsset.class, BlockSetPatternAsset::new, PatternAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("BlockSet", MaterialSetAsset.CODEC, true), (t, k) -> t.materialSetAsset = k, k -> k.materialSetAsset) + .add() + .build(); + private MaterialSetAsset materialSetAsset = new MaterialSetAsset(); + + public BlockSetPatternAsset() { + } + + @Nonnull + @Override + public Pattern build(@Nonnull PatternAsset.Argument argument) { + if (super.isSkipped()) { + return Pattern.noPattern(); + } else { + MaterialSet blockSet = this.materialSetAsset.build(argument.materialCache); + return new MaterialSetPattern(blockSet); + } + } + + @Override + public void cleanUp() { + this.materialSetAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/CeilingPatternAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/CeilingPatternAsset.java new file mode 100644 index 0000000..a5f04a8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/CeilingPatternAsset.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.patterns.CeilingPattern; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class CeilingPatternAsset extends PatternAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CeilingPatternAsset.class, CeilingPatternAsset::new, PatternAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Ceiling", PatternAsset.CODEC, true), (t, k) -> t.ceiling = k, k -> k.ceiling) + .add() + .append(new KeyedCodec<>("Origin", PatternAsset.CODEC, true), (t, k) -> t.origin = k, k -> k.origin) + .add() + .build(); + private PatternAsset ceiling = new ConstantPatternAsset(); + private PatternAsset origin = new ConstantPatternAsset(); + + public CeilingPatternAsset() { + } + + @Nonnull + @Override + public Pattern build(@Nonnull PatternAsset.Argument argument) { + if (super.isSkipped()) { + return Pattern.noPattern(); + } else { + Pattern ceilingPattern = this.ceiling.build(argument); + Pattern originPattern = this.origin.build(argument); + return new CeilingPattern(ceilingPattern, originPattern); + } + } + + @Override + public void cleanUp() { + this.ceiling.cleanUp(); + this.origin.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/ConstantPatternAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/ConstantPatternAsset.java new file mode 100644 index 0000000..8ebe7a6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/ConstantPatternAsset.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class ConstantPatternAsset extends PatternAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ConstantPatternAsset.class, ConstantPatternAsset::new, PatternAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Value", Codec.BOOLEAN, true), (asset, value) -> asset.value = value, value -> value.value) + .add() + .build(); + private boolean value = false; + + public ConstantPatternAsset() { + } + + @Override + public Pattern build(@Nonnull PatternAsset.Argument argument) { + return super.isSkipped() ? Pattern.noPattern() : new Pattern() { + @Override + public boolean matches(@Nonnull Pattern.Context context) { + return ConstantPatternAsset.this.value; + } + + @Override + public SpaceSize readSpace() { + return SpaceSize.empty(); + } + }; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/CuboidPatternAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/CuboidPatternAsset.java new file mode 100644 index 0000000..7994ef2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/CuboidPatternAsset.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.patterns.CuboidPattern; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class CuboidPatternAsset extends PatternAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CuboidPatternAsset.class, CuboidPatternAsset::new, PatternAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("SubPattern", PatternAsset.CODEC, true), (t, k) -> t.subPatternAsset = k, k -> k.subPatternAsset) + .add() + .append(new KeyedCodec<>("Min", Vector3i.CODEC, true), (t, k) -> t.min = k, k -> k.min) + .add() + .append(new KeyedCodec<>("Max", Vector3i.CODEC, true), (t, k) -> t.max = k, k -> k.max) + .add() + .build(); + private PatternAsset subPatternAsset = new ConstantPatternAsset(); + private Vector3i min = new Vector3i(); + private Vector3i max = new Vector3i(); + + public CuboidPatternAsset() { + } + + @Nonnull + @Override + public Pattern build(@Nonnull PatternAsset.Argument argument) { + if (super.isSkipped()) { + return Pattern.noPattern(); + } else { + Pattern subPattern = this.subPatternAsset.build(argument); + return new CuboidPattern(subPattern, this.min, this.max); + } + } + + @Override + public void cleanUp() { + this.subPatternAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/DensityPatternAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/DensityPatternAsset.java new file mode 100644 index 0000000..ccd83e9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/DensityPatternAsset.java @@ -0,0 +1,86 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.patterns; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ConstantDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.FieldFunctionPattern; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import javax.annotation.Nonnull; + +public class DensityPatternAsset extends PatternAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DensityPatternAsset.class, DensityPatternAsset::new, PatternAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>("Delimiters", new ArrayCodec<>(DensityPatternAsset.DelimiterAsset.CODEC, DensityPatternAsset.DelimiterAsset[]::new), true), + (t, k) -> t.delimiterAssets = k, + k -> k.delimiterAssets + ) + .add() + .append(new KeyedCodec<>("FieldFunction", DensityAsset.CODEC, true), (t, k) -> t.densityAsset = k, k -> k.densityAsset) + .add() + .build(); + private DensityPatternAsset.DelimiterAsset[] delimiterAssets = new DensityPatternAsset.DelimiterAsset[0]; + private DensityAsset densityAsset = new ConstantDensityAsset(); + + public DensityPatternAsset() { + } + + @Nonnull + @Override + public Pattern build(@Nonnull PatternAsset.Argument argument) { + if (super.isSkipped()) { + return Pattern.noPattern(); + } else { + Density field = this.densityAsset.build(DensityAsset.from(argument)); + FieldFunctionPattern out = new FieldFunctionPattern(field); + + for (DensityPatternAsset.DelimiterAsset asset : this.delimiterAssets) { + out.addDelimiter(asset.min, asset.max); + } + + return out; + } + } + + @Override + public void cleanUp() { + this.densityAsset.cleanUp(); + } + + public static class DelimiterAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + DensityPatternAsset.DelimiterAsset.class, + DensityPatternAsset.DelimiterAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Min", Codec.DOUBLE, true), (t, v) -> t.min = v, t -> t.min) + .add() + .append(new KeyedCodec<>("Max", Codec.DOUBLE, true), (t, v) -> t.max = v, t -> t.max) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private double min; + private double max; + + public DelimiterAsset() { + } + + public String getId() { + return this.id; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/FloorPatternAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/FloorPatternAsset.java new file mode 100644 index 0000000..637f99d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/FloorPatternAsset.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.SurfacePattern; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class FloorPatternAsset extends PatternAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + FloorPatternAsset.class, FloorPatternAsset::new, PatternAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Floor", PatternAsset.CODEC, true), (t, k) -> t.floor = k, k -> k.floor) + .add() + .append(new KeyedCodec<>("Origin", PatternAsset.CODEC, true), (t, k) -> t.origin = k, k -> k.origin) + .add() + .build(); + private PatternAsset floor = new ConstantPatternAsset(); + private PatternAsset origin = new ConstantPatternAsset(); + + public FloorPatternAsset() { + } + + @Nonnull + @Override + public Pattern build(@Nonnull PatternAsset.Argument argument) { + if (super.isSkipped()) { + return Pattern.noPattern(); + } else { + Pattern floorPattern = this.floor.build(argument); + Pattern originPattern = this.origin.build(argument); + return new SurfacePattern(floorPattern, originPattern, 0.0, 0.0, SurfacePattern.Facing.U, 0, 0); + } + } + + @Override + public void cleanUp() { + this.floor.cleanUp(); + this.origin.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/GapPatternAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/GapPatternAsset.java new file mode 100644 index 0000000..b1cb97e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/GapPatternAsset.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.patterns.GapPattern; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class GapPatternAsset extends PatternAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(GapPatternAsset.class, GapPatternAsset::new, PatternAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("GapPattern", PatternAsset.CODEC, true), (t, k) -> t.gapPatternAsset = k, k -> k.gapPatternAsset) + .add() + .append(new KeyedCodec<>("AnchorPattern", PatternAsset.CODEC, true), (t, k) -> t.anchorPatternAsset = k, k -> k.anchorPatternAsset) + .add() + .append(new KeyedCodec<>("GapSize", Codec.DOUBLE, true), (t, k) -> t.gapSize = k, k -> k.gapSize) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append(new KeyedCodec<>("AnchorSize", Codec.DOUBLE, true), (t, k) -> t.anchorSize = k, k -> k.anchorSize) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append(new KeyedCodec<>("AnchorRoughness", Codec.DOUBLE, true), (t, k) -> t.anchorRoughness = k, k -> k.anchorRoughness) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append(new KeyedCodec<>("DepthDown", Codec.INTEGER, true), (t, k) -> t.depthDown = k, k -> k.depthDown) + .addValidator(Validators.greaterThanOrEqual(0)) + .add() + .append(new KeyedCodec<>("DepthUp", Codec.INTEGER, true), (t, k) -> t.depthUp = k, k -> k.depthUp) + .addValidator(Validators.greaterThanOrEqual(0)) + .add() + .append(new KeyedCodec<>("Angles", new ArrayCodec<>(Codec.FLOAT, Float[]::new), true), (t, k) -> t.angles = k, k -> k.angles) + .add() + .build(); + private PatternAsset gapPatternAsset = new ConstantPatternAsset(); + private PatternAsset anchorPatternAsset = new ConstantPatternAsset(); + private double gapSize = 0.0; + private double anchorSize = 0.0; + private double anchorRoughness = 0.0; + private int depthDown = 0; + private int depthUp = 0; + private Float[] angles = new Float[0]; + + public GapPatternAsset() { + } + + @Nonnull + @Override + public Pattern build(@Nonnull PatternAsset.Argument argument) { + if (super.isSkipped()) { + return Pattern.noPattern(); + } else { + Pattern gapPattern = this.gapPatternAsset.build(argument); + Pattern wallPattern = this.anchorPatternAsset.build(argument); + ArrayList angleList = new ArrayList<>(); + + for (Float a : this.angles) { + if (a != null && !Float.isNaN(a)) { + a = a * 180.0F; + angleList.add(a); + } + } + + return new GapPattern(angleList, this.gapSize, this.anchorSize, this.anchorRoughness, this.depthDown, this.depthUp, gapPattern, wallPattern); + } + } + + @Override + public void cleanUp() { + this.gapPatternAsset.cleanUp(); + this.anchorPatternAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/ImportedPatternAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/ImportedPatternAsset.java new file mode 100644 index 0000000..a9159ea --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/ImportedPatternAsset.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.logger.HytaleLogger; +import javax.annotation.Nonnull; + +public class ImportedPatternAsset extends PatternAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ImportedPatternAsset.class, ImportedPatternAsset::new, PatternAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Name", Codec.STRING, true), (t, k) -> t.name = k, k -> k.name) + .add() + .build(); + private String name = ""; + + public ImportedPatternAsset() { + } + + @Override + public Pattern build(@Nonnull PatternAsset.Argument argument) { + if (super.isSkipped()) { + return Pattern.noPattern(); + } else if (this.name != null && !this.name.isEmpty()) { + PatternAsset exportedAsset = PatternAsset.getExportedAsset(this.name); + return exportedAsset == null ? Pattern.noPattern() : exportedAsset.build(argument); + } else { + HytaleLogger.getLogger().atWarning().log("An exported Pattern with the name does not exist: " + this.name); + return Pattern.noPattern(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/MaterialPatternAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/MaterialPatternAsset.java new file mode 100644 index 0000000..f7fbe0f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/MaterialPatternAsset.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.material.MaterialAsset; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.MaterialPattern; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class MaterialPatternAsset extends PatternAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + MaterialPatternAsset.class, MaterialPatternAsset::new, PatternAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Material", MaterialAsset.CODEC, true), (asset, value) -> asset.materialAsset = value, value -> value.materialAsset) + .add() + .build(); + private MaterialAsset materialAsset = new MaterialAsset(); + + public MaterialPatternAsset() { + } + + @Override + public Pattern build(@Nonnull PatternAsset.Argument argument) { + if (super.isSkipped()) { + return Pattern.noPattern(); + } else { + Material material = this.materialAsset.build(argument.materialCache); + return new MaterialPattern(material); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/NotPatternAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/NotPatternAsset.java new file mode 100644 index 0000000..8f6f8a4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/NotPatternAsset.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.patterns.NotPattern; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class NotPatternAsset extends PatternAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(NotPatternAsset.class, NotPatternAsset::new, PatternAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Pattern", PatternAsset.CODEC, true), (t, k) -> t.patternAsset = k, k -> k.patternAsset) + .add() + .build(); + private PatternAsset patternAsset = new ConstantPatternAsset(); + + public NotPatternAsset() { + } + + @Nonnull + @Override + public Pattern build(@Nonnull PatternAsset.Argument argument) { + return (Pattern)(super.isSkipped() ? Pattern.noPattern() : new NotPattern(this.patternAsset.build(argument))); + } + + @Override + public void cleanUp() { + this.patternAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/OffsetPatternAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/OffsetPatternAsset.java new file mode 100644 index 0000000..4cfff31 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/OffsetPatternAsset.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.patterns.OffsetPattern; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class OffsetPatternAsset extends PatternAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + OffsetPatternAsset.class, OffsetPatternAsset::new, PatternAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Pattern", PatternAsset.CODEC, true), (t, k) -> t.patternAsset = k, k -> k.patternAsset) + .add() + .append(new KeyedCodec<>("Offset", Vector3i.CODEC, true), (t, k) -> t.offset = k, k -> k.offset) + .add() + .build(); + private PatternAsset patternAsset = new ConstantPatternAsset(); + private Vector3i offset = new Vector3i(); + + public OffsetPatternAsset() { + } + + @Nonnull + @Override + public Pattern build(@Nonnull PatternAsset.Argument argument) { + if (super.isSkipped()) { + return Pattern.noPattern(); + } else { + Pattern pattern = this.patternAsset.build(argument); + return new OffsetPattern(pattern, this.offset.clone()); + } + } + + @Override + public void cleanUp() { + this.patternAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/OrPatternAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/OrPatternAsset.java new file mode 100644 index 0000000..b7d9f9a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/OrPatternAsset.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.patterns.OrPattern; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class OrPatternAsset extends PatternAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(OrPatternAsset.class, OrPatternAsset::new, PatternAsset.ABSTRACT_CODEC) + .append( + new KeyedCodec<>("Patterns", new ArrayCodec<>(PatternAsset.CODEC, PatternAsset[]::new), true), (t, k) -> t.patternAssets = k, k -> k.patternAssets + ) + .add() + .build(); + private PatternAsset[] patternAssets = new PatternAsset[0]; + + public OrPatternAsset() { + } + + @Nonnull + @Override + public Pattern build(@Nonnull PatternAsset.Argument argument) { + if (super.isSkipped()) { + return Pattern.noPattern(); + } else { + ArrayList patterns = new ArrayList<>(this.patternAssets.length); + + for (PatternAsset asset : this.patternAssets) { + if (!asset.isSkipped()) { + patterns.add(asset.build(argument)); + } + } + + return new OrPattern(patterns); + } + } + + @Override + public void cleanUp() { + for (PatternAsset patternAsset : this.patternAssets) { + patternAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/PatternAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/PatternAsset.java new file mode 100644 index 0000000..d0ba19b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/PatternAsset.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.patterns; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.PropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop.directionality.DirectionalityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; + +public abstract class PatternAsset implements Cleanable, JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + private static final Map exportedNodes = new ConcurrentHashMap<>(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(PatternAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(PatternAsset.class) + .append(new KeyedCodec<>("Skip", Codec.BOOLEAN, false), (t, k) -> t.skip = k, t -> t.skip) + .add() + .append(new KeyedCodec<>("ExportAs", Codec.STRING, false), (t, k) -> t.exportName = k, t -> t.exportName) + .add() + .afterDecode(asset -> { + if (asset.exportName != null && !asset.exportName.isEmpty()) { + if (exportedNodes.containsKey(asset.exportName)) { + LoggerUtil.getLogger().warning("Duplicate export name for asset: " + asset.exportName); + } + + exportedNodes.put(asset.exportName, asset); + LoggerUtil.getLogger().fine("Registered imported node asset with name '" + asset.exportName + "' with asset id '" + asset.id); + } + }) + .build(); + private String id; + private AssetExtraInfo.Data data; + private boolean skip = false; + private String exportName = ""; + + protected PatternAsset() { + } + + public abstract Pattern build(@Nonnull PatternAsset.Argument var1); + + public boolean isSkipped() { + return this.skip; + } + + public static PatternAsset getExportedAsset(@Nonnull String name) { + return exportedNodes.get(name); + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + } + + @Nonnull + public static PatternAsset.Argument argumentFrom(@Nonnull DirectionalityAsset.Argument argument) { + return new PatternAsset.Argument(argument.parentSeed, argument.materialCache, argument.referenceBundle, argument.workerIndexer); + } + + @Nonnull + public static PatternAsset.Argument argumentFrom(@Nonnull PropAsset.Argument argument) { + return new PatternAsset.Argument(argument.parentSeed, argument.materialCache, argument.referenceBundle, argument.workerIndexer); + } + + public static class Argument { + public SeedBox parentSeed; + public MaterialCache materialCache; + public ReferenceBundle referenceBundle; + public WorkerIndexer workerIndexer; + + public Argument( + @Nonnull SeedBox parentSeed, @Nonnull MaterialCache materialCache, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer + ) { + this.parentSeed = parentSeed; + this.materialCache = materialCache; + this.referenceBundle = referenceBundle; + this.workerIndexer = workerIndexer; + } + + public Argument(@Nonnull PatternAsset.Argument argument) { + this.parentSeed = argument.parentSeed; + this.materialCache = argument.materialCache; + this.referenceBundle = argument.referenceBundle; + this.workerIndexer = argument.workerIndexer; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/SurfacePatternAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/SurfacePatternAsset.java new file mode 100644 index 0000000..faa91a9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/SurfacePatternAsset.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.patterns.AndPattern; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.OrPattern; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.SurfacePattern; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class SurfacePatternAsset extends PatternAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SurfacePatternAsset.class, SurfacePatternAsset::new, PatternAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Surface", PatternAsset.CODEC, true), (t, k) -> t.surface = k, k -> k.surface) + .add() + .append(new KeyedCodec<>("Medium", PatternAsset.CODEC, true), (t, k) -> t.origin = k, k -> k.origin) + .add() + .append(new KeyedCodec<>("SurfaceRadius", Codec.DOUBLE, false), (t, k) -> t.surfaceRadius = k, k -> k.surfaceRadius) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append(new KeyedCodec<>("MediumRadius", Codec.DOUBLE, false), (t, k) -> t.originRadius = k, k -> k.originRadius) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append(new KeyedCodec<>("SurfaceGap", Codec.INTEGER, false), (t, k) -> t.surfaceGap = k, k -> k.surfaceGap) + .addValidator(Validators.greaterThanOrEqual(0)) + .add() + .append(new KeyedCodec<>("MediumGap", Codec.INTEGER, false), (t, k) -> t.originGap = k, k -> k.originGap) + .addValidator(Validators.greaterThanOrEqual(0)) + .add() + .append(new KeyedCodec<>("RequireAllFacings", Codec.BOOLEAN, false), (t, k) -> t.requireAllFacings = k, k -> k.requireAllFacings) + .add() + .append( + new KeyedCodec<>("Facings", new ArrayCodec<>(SurfacePattern.Facing.CODEC, SurfacePattern.Facing[]::new), true), + (t, k) -> t.facings = k, + k -> k.facings + ) + .add() + .build(); + private PatternAsset surface = new ConstantPatternAsset(); + private PatternAsset origin = new ConstantPatternAsset(); + private double surfaceRadius = 0.0; + private double originRadius = 0.0; + private int surfaceGap = 0; + private int originGap = 0; + private SurfacePattern.Facing[] facings = new SurfacePattern.Facing[0]; + private boolean requireAllFacings = false; + + public SurfacePatternAsset() { + } + + @Nonnull + @Override + public Pattern build(@Nonnull PatternAsset.Argument argument) { + if (super.isSkipped()) { + return Pattern.noPattern(); + } else { + Pattern floorPattern = this.surface.build(argument); + Pattern originPattern = this.origin.build(argument); + ArrayList patterns = new ArrayList<>(this.facings.length); + + for (SurfacePattern.Facing s : this.facings) { + SurfacePattern pattern = new SurfacePattern(floorPattern, originPattern, this.surfaceRadius, this.originRadius, s, this.surfaceGap, this.originGap); + patterns.add(pattern); + } + + return (Pattern)(this.requireAllFacings ? new AndPattern(patterns) : new OrPattern(patterns)); + } + } + + @Override + public void cleanUp() { + this.surface.cleanUp(); + this.origin.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/WallPatternAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/WallPatternAsset.java new file mode 100644 index 0000000..cdf50f9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/patterns/WallPatternAsset.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.WallPattern; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.List; +import javax.annotation.Nonnull; + +public class WallPatternAsset extends PatternAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(WallPatternAsset.class, WallPatternAsset::new, PatternAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Wall", PatternAsset.CODEC, true), (t, k) -> t.wall = k, k -> k.wall) + .add() + .append(new KeyedCodec<>("Origin", PatternAsset.CODEC, true), (t, k) -> t.origin = k, k -> k.origin) + .add() + .append(new KeyedCodec<>("RequireAllDirections", Codec.BOOLEAN, false), (t, k) -> t.matchAll = k, k -> k.matchAll) + .add() + .append( + new KeyedCodec<>("Directions", new ArrayCodec<>(WallPattern.WallDirection.CODEC, WallPattern.WallDirection[]::new), true), + (t, k) -> t.directions = k, + k -> k.directions + ) + .add() + .build(); + private PatternAsset wall = new ConstantPatternAsset(); + private PatternAsset origin = new ConstantPatternAsset(); + private WallPattern.WallDirection[] directions = new WallPattern.WallDirection[0]; + private boolean matchAll = false; + + public WallPatternAsset() { + } + + @Nonnull + @Override + public Pattern build(@Nonnull PatternAsset.Argument argument) { + if (super.isSkipped()) { + return Pattern.noPattern(); + } else { + Pattern wallPattern = this.wall.build(argument); + Pattern originPattern = this.origin.build(argument); + return new WallPattern(wallPattern, originPattern, List.of(this.directions), this.matchAll); + } + } + + @Override + public void cleanUp() { + this.wall.cleanUp(); + this.origin.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/pointgenerators/MeshPointGeneratorAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/pointgenerators/MeshPointGeneratorAsset.java new file mode 100644 index 0000000..e87a6d9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/pointgenerators/MeshPointGeneratorAsset.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.pointgenerators; + +import com.hypixel.hytale.builtin.hytalegenerator.fields.points.JitterPointField; +import com.hypixel.hytale.builtin.hytalegenerator.fields.points.PointProvider; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class MeshPointGeneratorAsset extends PointGeneratorAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + MeshPointGeneratorAsset.class, MeshPointGeneratorAsset::new, PointGeneratorAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Jitter", Codec.DOUBLE, true), (asset, v) -> asset.jitter = v, asset -> asset.jitter) + .addValidator(Validators.range(0.0, 0.5)) + .add() + .append(new KeyedCodec<>("ScaleX", Codec.DOUBLE, true), (asset, v) -> asset.scaleX = v, asset -> asset.scaleX) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("ScaleY", Codec.DOUBLE, true), (asset, v) -> asset.scaleY = v, asset -> asset.scaleY) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("ScaleZ", Codec.DOUBLE, true), (asset, v) -> asset.scaleZ = v, asset -> asset.scaleZ) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("Seed", Codec.STRING, true), (asset, seed) -> asset.seedKey = seed, asset -> asset.seedKey) + .add() + .build(); + private double jitter = 0.35; + private double scaleX = 1.0; + private double scaleY = 1.0; + private double scaleZ = 1.0; + private String seedKey = "A"; + + public MeshPointGeneratorAsset() { + } + + @Override + public PointProvider build(@Nonnull SeedBox parentSeed) { + SeedBox childSeed = parentSeed.child(this.seedKey); + return new JitterPointField(childSeed.createSupplier().get(), this.jitter).setScale(this.scaleX, this.scaleY, this.scaleZ, 1.0); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/pointgenerators/NoPointGeneratorAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/pointgenerators/NoPointGeneratorAsset.java new file mode 100644 index 0000000..dc4aa6d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/pointgenerators/NoPointGeneratorAsset.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.pointgenerators; + +import com.hypixel.hytale.builtin.hytalegenerator.fields.points.PointProvider; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.List; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +public class NoPointGeneratorAsset extends PointGeneratorAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + NoPointGeneratorAsset.class, NoPointGeneratorAsset::new, PointGeneratorAsset.ABSTRACT_CODEC + ) + .build(); + + public NoPointGeneratorAsset() { + } + + @Override + public PointProvider build(@Nonnull SeedBox parentSeed) { + return new PointProvider() { + @Override + public List points3i(@Nonnull Vector3i min, @Nonnull Vector3i max) { + return List.of(); + } + + @Override + public List points2i(@Nonnull Vector2i min, @Nonnull Vector2i max) { + return List.of(); + } + + @Override + public List points1i(int min, int max) { + return List.of(); + } + + @Override + public void points3i(@Nonnull Vector3i min, @Nonnull Vector3i max, @Nonnull Consumer pointsOut) { + } + + @Override + public void points2i(@Nonnull Vector2i min, @Nonnull Vector2i max, @Nonnull Consumer pointsOut) { + } + + @Override + public void points1i(int min, int max, @Nonnull Consumer pointsOut) { + } + + @Override + public List points3d(@Nonnull Vector3d min, @Nonnull Vector3d max) { + return List.of(); + } + + @Override + public List points2d(@Nonnull Vector2d min, @Nonnull Vector2d max) { + return List.of(); + } + + @Override + public List points1d(double min, double max) { + return List.of(); + } + + @Override + public void points3d(@Nonnull Vector3d min, @Nonnull Vector3d max, @Nonnull Consumer pointsOut) { + } + + @Override + public void points2d(@Nonnull Vector2d min, @Nonnull Vector2d max, @Nonnull Consumer pointsOut) { + } + + @Override + public void points1d(double min, double max, @Nonnull Consumer pointsOut) { + } + }; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/pointgenerators/PointGeneratorAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/pointgenerators/PointGeneratorAsset.java new file mode 100644 index 0000000..e2f7aad --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/pointgenerators/PointGeneratorAsset.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.pointgenerators; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.fields.points.PointProvider; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public abstract class PointGeneratorAsset implements JsonAssetWithMap> { + private static final PointGeneratorAsset[] EMPTY_INPUTS = new PointGeneratorAsset[0]; + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + private static final Map exportedNodes = new HashMap<>(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(PointGeneratorAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(PointGeneratorAsset.class) + .append(new KeyedCodec<>("Skip", Codec.BOOLEAN, false), (t, k) -> t.skip = k, t -> t.skip) + .add() + .append(new KeyedCodec<>("ExportAs", Codec.STRING, false), (t, k) -> t.exportName = k, t -> t.exportName) + .add() + .afterDecode(asset -> { + if (asset.exportName != null && !asset.exportName.isEmpty()) { + exportedNodes.put(asset.exportName, asset); + LoggerUtil.getLogger().fine("Registered imported position provider asset with name '" + asset.exportName + "' with asset id '" + asset.id); + } + }) + .build(); + private String id; + private AssetExtraInfo.Data data; + private PointGeneratorAsset[] inputs = EMPTY_INPUTS; + private boolean skip = false; + private String exportName = ""; + + protected PointGeneratorAsset() { + } + + public abstract PointProvider build(@Nonnull SeedBox var1); + + public PointGeneratorAsset[] inputs() { + return this.inputs; + } + + public boolean skip() { + return this.skip; + } + + public static PointGeneratorAsset getExportedAsset(@Nonnull String name) { + return exportedNodes.get(name); + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/AnchorPositionProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/AnchorPositionProviderAsset.java new file mode 100644 index 0000000..28d09e4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/AnchorPositionProviderAsset.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.AnchorPositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class AnchorPositionProviderAsset extends PositionProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + AnchorPositionProviderAsset.class, AnchorPositionProviderAsset::new, PositionProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Reversed", Codec.BOOLEAN, false), (t, k) -> t.isReversed = k, k -> k.isReversed) + .add() + .append(new KeyedCodec<>("Positions", PositionProviderAsset.CODEC, true), (t, k) -> t.positionProviderAsset = k, k -> k.positionProviderAsset) + .add() + .build(); + private boolean isReversed = false; + private PositionProviderAsset positionProviderAsset = new ListPositionProviderAsset(); + + public AnchorPositionProviderAsset() { + } + + @Nonnull + @Override + public PositionProvider build(@Nonnull PositionProviderAsset.Argument argument) { + if (super.skip()) { + return PositionProvider.noPositionProvider(); + } else { + PositionProvider positionProvider = this.positionProviderAsset == null + ? PositionProvider.noPositionProvider() + : this.positionProviderAsset.build(argument); + return new AnchorPositionProvider(positionProvider, this.isReversed); + } + } + + @Override + public void cleanUp() { + this.positionProviderAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/BaseHeightPositionProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/BaseHeightPositionProviderAsset.java new file mode 100644 index 0000000..1374f34 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/BaseHeightPositionProviderAsset.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.BiDouble2DoubleFunction; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.BaseHeightPositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.BaseHeightReference; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.logger.HytaleLogger; +import javax.annotation.Nonnull; + +public class BaseHeightPositionProviderAsset extends PositionProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + BaseHeightPositionProviderAsset.class, BaseHeightPositionProviderAsset::new, PositionProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("MinYRead", Codec.DOUBLE, false), (asset, v) -> asset.minYRead = v, asset -> asset.minYRead) + .add() + .append(new KeyedCodec<>("MaxYRead", Codec.DOUBLE, false), (asset, v) -> asset.maxYRead = v, asset -> asset.maxYRead) + .add() + .append(new KeyedCodec<>("BedName", Codec.STRING, false), (asset, v) -> asset.bedName = v, asset -> asset.bedName) + .add() + .append( + new KeyedCodec<>("Positions", PositionProviderAsset.CODEC, true), (asset, v) -> asset.positionProviderAsset = v, asset -> asset.positionProviderAsset + ) + .add() + .build(); + private double minYRead = -1.0; + private double maxYRead = 1.0; + private String bedName = ""; + private PositionProviderAsset positionProviderAsset = new ListPositionProviderAsset(); + + public BaseHeightPositionProviderAsset() { + } + + @Nonnull + @Override + public PositionProvider build(@Nonnull PositionProviderAsset.Argument argument) { + if (super.skip()) { + return PositionProvider.noPositionProvider(); + } else { + PositionProvider positionProvider = this.positionProviderAsset.build(argument); + BaseHeightReference heightDataLayer = argument.referenceBundle.getLayerWithName(this.bedName, BaseHeightReference.class); + if (heightDataLayer == null) { + HytaleLogger.getLogger() + .atConfig() + .log("Couldn't height data layer with name \"" + this.bedName + "\", the positions will not be offset by the bed."); + return new BaseHeightPositionProvider((x, z) -> 0.0, positionProvider, this.minYRead, this.maxYRead); + } else { + BiDouble2DoubleFunction heightFunction = heightDataLayer.getHeightFunction(); + return new BaseHeightPositionProvider(heightFunction, positionProvider, this.minYRead, this.maxYRead); + } + } + } + + @Override + public void cleanUp() { + this.positionProviderAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/CachedPositionProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/CachedPositionProviderAsset.java new file mode 100644 index 0000000..69ca574 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/CachedPositionProviderAsset.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.cached.CachedPositionProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class CachedPositionProviderAsset extends PositionProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CachedPositionProviderAsset.class, CachedPositionProviderAsset::new, PositionProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Positions", PositionProviderAsset.CODEC, true), (asset, v) -> asset.childAsset = v, asset -> asset.childAsset) + .add() + .append(new KeyedCodec<>("SectionSize", Codec.INTEGER, true), (asset, v) -> asset.sectionSize = v, asset -> asset.sectionSize) + .addValidator(Validators.greaterThan(0)) + .add() + .append(new KeyedCodec<>("CacheSize", Codec.INTEGER, true), (asset, v) -> asset.cacheSize = v, asset -> asset.cacheSize) + .addValidator(Validators.greaterThan(-1)) + .add() + .build(); + private PositionProviderAsset childAsset = new ListPositionProviderAsset(); + private int sectionSize = 32; + private int cacheSize = 100; + + public CachedPositionProviderAsset() { + } + + @Nonnull + @Override + public PositionProvider build(@Nonnull PositionProviderAsset.Argument argument) { + if (super.skip()) { + return PositionProvider.noPositionProvider(); + } else { + PositionProvider childPositions = this.childAsset.build(argument); + return new CachedPositionProvider(childPositions, this.sectionSize, this.cacheSize, false, argument.workerIndexer.getWorkerCount()); + } + } + + @Override + public void cleanUp() { + this.childAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/FieldFunctionOccurrencePositionProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/FieldFunctionOccurrencePositionProviderAsset.java new file mode 100644 index 0000000..dc6b0b0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/FieldFunctionOccurrencePositionProviderAsset.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ConstantDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.FieldFunctionOccurrencePositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class FieldFunctionOccurrencePositionProviderAsset extends PositionProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + FieldFunctionOccurrencePositionProviderAsset.class, FieldFunctionOccurrencePositionProviderAsset::new, PositionProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Seed", Codec.STRING, true), (asset, v) -> asset.seed = v, asset -> asset.seed) + .add() + .append(new KeyedCodec<>("FieldFunction", DensityAsset.CODEC, true), (asset, v) -> asset.densityAsset = v, asset -> asset.densityAsset) + .add() + .append( + new KeyedCodec<>("Positions", PositionProviderAsset.CODEC, true), (asset, v) -> asset.positionProviderAsset = v, asset -> asset.positionProviderAsset + ) + .add() + .build(); + private String seed = ""; + private DensityAsset densityAsset = new ConstantDensityAsset(); + private PositionProviderAsset positionProviderAsset = new ListPositionProviderAsset(); + + public FieldFunctionOccurrencePositionProviderAsset() { + } + + @Nonnull + @Override + public PositionProvider build(@Nonnull PositionProviderAsset.Argument argument) { + if (super.skip()) { + return PositionProvider.noPositionProvider(); + } else { + Density functionTree = this.densityAsset.build(DensityAsset.from(argument)); + PositionProvider positionProvider = this.positionProviderAsset.build(argument); + int intSeed = argument.parentSeed.child(this.seed).createSupplier().get(); + return new FieldFunctionOccurrencePositionProvider(functionTree, positionProvider, intSeed); + } + } + + @Override + public void cleanUp() { + this.densityAsset.cleanUp(); + this.positionProviderAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/FieldFunctionPositionProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/FieldFunctionPositionProviderAsset.java new file mode 100644 index 0000000..f336a7e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/FieldFunctionPositionProviderAsset.java @@ -0,0 +1,98 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ConstantDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.FieldFunctionPositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import javax.annotation.Nonnull; + +public class FieldFunctionPositionProviderAsset extends PositionProviderAsset { + private static final FieldFunctionPositionProviderAsset.DelimiterAsset[] EMPTY_DELIMITER_ASSETS = new FieldFunctionPositionProviderAsset.DelimiterAsset[0]; + public static final BuilderCodec CODEC = BuilderCodec.builder( + FieldFunctionPositionProviderAsset.class, FieldFunctionPositionProviderAsset::new, PositionProviderAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>( + "Delimiters", + new ArrayCodec<>(FieldFunctionPositionProviderAsset.DelimiterAsset.CODEC, FieldFunctionPositionProviderAsset.DelimiterAsset[]::new), + true + ), + (asset, v) -> asset.delimiterAssets = v, + asset -> asset.delimiterAssets + ) + .add() + .append(new KeyedCodec<>("FieldFunction", DensityAsset.CODEC, true), (asset, v) -> asset.densityAsset = v, asset -> asset.densityAsset) + .add() + .append( + new KeyedCodec<>("Positions", PositionProviderAsset.CODEC, true), (asset, v) -> asset.positionProviderAsset = v, asset -> asset.positionProviderAsset + ) + .add() + .build(); + private FieldFunctionPositionProviderAsset.DelimiterAsset[] delimiterAssets = EMPTY_DELIMITER_ASSETS; + private DensityAsset densityAsset = new ConstantDensityAsset(); + private PositionProviderAsset positionProviderAsset = new ListPositionProviderAsset(); + + public FieldFunctionPositionProviderAsset() { + } + + @Nonnull + @Override + public PositionProvider build(@Nonnull PositionProviderAsset.Argument argument) { + if (super.skip()) { + return PositionProvider.noPositionProvider(); + } else { + Density functionTree = this.densityAsset.build(DensityAsset.from(argument)); + PositionProvider positionProvider = this.positionProviderAsset.build(argument); + FieldFunctionPositionProvider out = new FieldFunctionPositionProvider(functionTree, positionProvider); + + for (FieldFunctionPositionProviderAsset.DelimiterAsset asset : this.delimiterAssets) { + out.addDelimiter(asset.min, asset.max); + } + + return out; + } + } + + @Override + public void cleanUp() { + this.densityAsset.cleanUp(); + this.positionProviderAsset.cleanUp(); + } + + public static class DelimiterAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + FieldFunctionPositionProviderAsset.DelimiterAsset.class, + FieldFunctionPositionProviderAsset.DelimiterAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Min", Codec.DOUBLE, true), (t, v) -> t.min = v, t -> t.min) + .add() + .append(new KeyedCodec<>("Max", Codec.DOUBLE, true), (t, v) -> t.max = v, t -> t.max) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private double min = 0.0; + private double max = 0.0; + + public DelimiterAsset() { + } + + public String getId() { + return this.id; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/ImportedPositionProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/ImportedPositionProviderAsset.java new file mode 100644 index 0000000..b66eb54 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/ImportedPositionProviderAsset.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class ImportedPositionProviderAsset extends PositionProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ImportedPositionProviderAsset.class, ImportedPositionProviderAsset::new, PositionProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Name", Codec.STRING, true), (asset, v) -> asset.name = v, asset -> asset.name) + .add() + .build(); + private String name = ""; + + public ImportedPositionProviderAsset() { + } + + @Override + public PositionProvider build(@Nonnull PositionProviderAsset.Argument argument) { + if (super.skip()) { + return PositionProvider.noPositionProvider(); + } else { + PositionProviderAsset asset = getExportedAsset(this.name); + if (asset == null) { + LoggerUtil.getLogger().warning("Couldn't find Positions asset exported with name: '" + this.name + "'."); + return PositionProvider.noPositionProvider(); + } else { + return asset.build(argument); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/ListPositionProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/ListPositionProviderAsset.java new file mode 100644 index 0000000..41c43f3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/ListPositionProviderAsset.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.ListPositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class ListPositionProviderAsset extends PositionProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ListPositionProviderAsset.class, ListPositionProviderAsset::new, PositionProviderAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>("Positions", new ArrayCodec<>(ListPositionProviderAsset.PositionAsset.CODEC, ListPositionProviderAsset.PositionAsset[]::new), true), + (asset, v) -> asset.positions = v, + asset -> asset.positions + ) + .add() + .build(); + private ListPositionProviderAsset.PositionAsset[] positions = new ListPositionProviderAsset.PositionAsset[0]; + + public ListPositionProviderAsset() { + } + + @Nonnull + @Override + public PositionProvider build(@Nonnull PositionProviderAsset.Argument argument) { + if (super.skip()) { + return PositionProvider.noPositionProvider(); + } else { + ArrayList list = new ArrayList<>(); + + for (ListPositionProviderAsset.PositionAsset asset : this.positions) { + Vector3i position = new Vector3i(asset.x, asset.y, asset.z); + list.add(position); + } + + return ListPositionProvider.from3i(list); + } + } + + public static class PositionAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ListPositionProviderAsset.PositionAsset.class, + ListPositionProviderAsset.PositionAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("X", Codec.INTEGER, true), (t, x) -> t.x = x, t -> t.x) + .add() + .append(new KeyedCodec<>("Y", Codec.INTEGER, true), (t, y) -> t.y = y, t -> t.y) + .add() + .append(new KeyedCodec<>("Z", Codec.INTEGER, true), (t, z) -> t.z = z, t -> t.z) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private int x; + private int y; + private int z; + + public PositionAsset() { + } + + public String getId() { + return this.id; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/Mesh2DPositionProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/Mesh2DPositionProviderAsset.java new file mode 100644 index 0000000..c412e45 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/Mesh2DPositionProviderAsset.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.pointgenerators.NoPointGeneratorAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.pointgenerators.PointGeneratorAsset; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.Mesh2DPositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class Mesh2DPositionProviderAsset extends PositionProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + Mesh2DPositionProviderAsset.class, Mesh2DPositionProviderAsset::new, PositionProviderAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>("PointGenerator", PointGeneratorAsset.CODEC, true), (asset, v) -> asset.pointGeneratorAsset = v, asset -> asset.pointGeneratorAsset + ) + .add() + .append(new KeyedCodec<>("PointsY", Codec.INTEGER, true), (asset, v) -> asset.y = v, asset -> asset.y) + .add() + .build(); + private PointGeneratorAsset pointGeneratorAsset = new NoPointGeneratorAsset(); + private int y = 0; + + public Mesh2DPositionProviderAsset() { + } + + @Nonnull + @Override + public PositionProvider build(@Nonnull PositionProviderAsset.Argument argument) { + return (PositionProvider)(super.skip() + ? PositionProvider.noPositionProvider() + : new Mesh2DPositionProvider(this.pointGeneratorAsset.build(argument.parentSeed), this.y)); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/Mesh3DPositionProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/Mesh3DPositionProviderAsset.java new file mode 100644 index 0000000..ee6ed7b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/Mesh3DPositionProviderAsset.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.pointgenerators.NoPointGeneratorAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.pointgenerators.PointGeneratorAsset; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.Mesh3DPositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class Mesh3DPositionProviderAsset extends PositionProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + Mesh3DPositionProviderAsset.class, Mesh3DPositionProviderAsset::new, PositionProviderAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>("PointGenerator", PointGeneratorAsset.CODEC, true), (asset, v) -> asset.pointGeneratorAsset = v, asset -> asset.pointGeneratorAsset + ) + .add() + .build(); + private PointGeneratorAsset pointGeneratorAsset = new NoPointGeneratorAsset(); + + public Mesh3DPositionProviderAsset() { + } + + @Nonnull + @Override + public PositionProvider build(@Nonnull PositionProviderAsset.Argument argument) { + return (PositionProvider)(super.skip() + ? PositionProvider.noPositionProvider() + : new Mesh3DPositionProvider(this.pointGeneratorAsset.build(argument.parentSeed))); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/OffsetPositionProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/OffsetPositionProviderAsset.java new file mode 100644 index 0000000..56d2a6a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/OffsetPositionProviderAsset.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.OffsetPositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class OffsetPositionProviderAsset extends PositionProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + OffsetPositionProviderAsset.class, OffsetPositionProviderAsset::new, PositionProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("OffsetX", Codec.INTEGER, true), (asset, v) -> asset.offsetX = v, asset -> asset.offsetX) + .add() + .append(new KeyedCodec<>("OffsetY", Codec.INTEGER, true), (asset, v) -> asset.offsetY = v, asset -> asset.offsetY) + .add() + .append(new KeyedCodec<>("OffsetZ", Codec.INTEGER, true), (asset, v) -> asset.offsetZ = v, asset -> asset.offsetZ) + .add() + .append( + new KeyedCodec<>("Positions", PositionProviderAsset.CODEC, true), (asset, v) -> asset.positionProviderAsset = v, asset -> asset.positionProviderAsset + ) + .add() + .build(); + private int offsetX = 0; + private int offsetY = 0; + private int offsetZ = 0; + private PositionProviderAsset positionProviderAsset = new ListPositionProviderAsset(); + + public OffsetPositionProviderAsset() { + } + + @Nonnull + @Override + public PositionProvider build(@Nonnull PositionProviderAsset.Argument argument) { + if (super.skip()) { + return PositionProvider.noPositionProvider(); + } else { + PositionProvider positionProvider = this.positionProviderAsset.build(argument); + return new OffsetPositionProvider(new Vector3i(this.offsetX, this.offsetY, this.offsetZ), positionProvider); + } + } + + @Override + public void cleanUp() { + this.positionProviderAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/PositionProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/PositionProviderAsset.java new file mode 100644 index 0000000..16687a8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/PositionProviderAsset.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; + +public abstract class PositionProviderAsset implements Cleanable, JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + private static final Map exportedNodes = new ConcurrentHashMap<>(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(PositionProviderAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(PositionProviderAsset.class) + .append(new KeyedCodec<>("Skip", Codec.BOOLEAN, false), (t, k) -> t.skip = k, t -> t.skip) + .add() + .append(new KeyedCodec<>("ExportAs", Codec.STRING, false), (t, k) -> t.exportName = k, t -> t.exportName) + .add() + .afterDecode(asset -> { + if (asset.exportName != null && !asset.exportName.isEmpty()) { + if (exportedNodes.containsKey(asset.exportName)) { + LoggerUtil.getLogger().warning("Duplicate export name for asset: " + asset.exportName); + } + + exportedNodes.put(asset.exportName, asset); + LoggerUtil.getLogger().fine("Registered imported position provider asset with name '" + asset.exportName + "' with asset id '" + asset.id); + } + }) + .build(); + private String id; + private AssetExtraInfo.Data data; + private boolean skip = false; + private String exportName = ""; + + protected PositionProviderAsset() { + } + + public abstract PositionProvider build(@Nonnull PositionProviderAsset.Argument var1); + + public boolean skip() { + return this.skip; + } + + public static PositionProviderAsset getExportedAsset(@Nonnull String name) { + return exportedNodes.get(name); + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + } + + public static class Argument { + public SeedBox parentSeed; + public ReferenceBundle referenceBundle; + public WorkerIndexer workerIndexer; + + public Argument(@Nonnull SeedBox parentSeed, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer) { + this.parentSeed = parentSeed; + this.referenceBundle = referenceBundle; + this.workerIndexer = workerIndexer; + } + + public Argument(@Nonnull PositionProviderAsset.Argument argument) { + this.parentSeed = argument.parentSeed; + this.referenceBundle = argument.referenceBundle; + this.workerIndexer = argument.workerIndexer; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/SpherePositionProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/SpherePositionProviderAsset.java new file mode 100644 index 0000000..f8ba70f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/SpherePositionProviderAsset.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.SpherePositionProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class SpherePositionProviderAsset extends PositionProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SpherePositionProviderAsset.class, SpherePositionProviderAsset::new, PositionProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Range", Codec.DOUBLE, false), (t, k) -> t.range = k, k -> k.range) + .add() + .append(new KeyedCodec<>("Positions", PositionProviderAsset.CODEC, true), (t, k) -> t.positionProviderAsset = k, k -> k.positionProviderAsset) + .add() + .build(); + private double range = 0.0; + private PositionProviderAsset positionProviderAsset = new ListPositionProviderAsset(); + + public SpherePositionProviderAsset() { + } + + @Nonnull + @Override + public PositionProvider build(@Nonnull PositionProviderAsset.Argument argument) { + if (super.skip()) { + return PositionProvider.noPositionProvider(); + } else { + PositionProvider positionProvider = this.positionProviderAsset == null + ? PositionProvider.noPositionProvider() + : this.positionProviderAsset.build(argument); + return new SpherePositionProvider(positionProvider, this.range); + } + } + + @Override + public void cleanUp() { + this.positionProviderAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/UnionPositionProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/UnionPositionProviderAsset.java new file mode 100644 index 0000000..802850d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/UnionPositionProviderAsset.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.UnionPositionProvider; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class UnionPositionProviderAsset extends PositionProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + UnionPositionProviderAsset.class, UnionPositionProviderAsset::new, PositionProviderAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>("Positions", new ArrayCodec<>(PositionProviderAsset.CODEC, PositionProviderAsset[]::new), true), + (asset, v) -> asset.positionProviderAssets = v, + asset -> asset.positionProviderAssets + ) + .add() + .build(); + private PositionProviderAsset[] positionProviderAssets = new PositionProviderAsset[0]; + + public UnionPositionProviderAsset() { + } + + @Nonnull + @Override + public PositionProvider build(@Nonnull PositionProviderAsset.Argument argument) { + if (super.skip()) { + return PositionProvider.noPositionProvider(); + } else { + ArrayList list = new ArrayList<>(); + + for (PositionProviderAsset asset : this.positionProviderAssets) { + PositionProvider positionProvider = asset.build(argument); + list.add(positionProvider); + } + + return new UnionPositionProvider(list); + } + } + + @Override + public void cleanUp() { + for (PositionProviderAsset positionProviderAsset : this.positionProviderAssets) { + positionProviderAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/VerticalEliminatorPositionProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/VerticalEliminatorPositionProviderAsset.java new file mode 100644 index 0000000..8e4a3cb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/positionproviders/VerticalEliminatorPositionProviderAsset.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.VerticalEliminatorPositionProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class VerticalEliminatorPositionProviderAsset extends PositionProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + VerticalEliminatorPositionProviderAsset.class, VerticalEliminatorPositionProviderAsset::new, PositionProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("MaxY", Codec.INTEGER, true), (asset, v) -> asset.maxY = v, asset -> asset.maxY) + .add() + .append(new KeyedCodec<>("MinY", Codec.INTEGER, true), (asset, v) -> asset.minY = v, asset -> asset.minY) + .add() + .append( + new KeyedCodec<>("Positions", PositionProviderAsset.CODEC, true), (asset, v) -> asset.positionProviderAsset = v, asset -> asset.positionProviderAsset + ) + .add() + .build(); + private int maxY = 0; + private int minY = 0; + private PositionProviderAsset positionProviderAsset = new ListPositionProviderAsset(); + + public VerticalEliminatorPositionProviderAsset() { + } + + @Nonnull + @Override + public PositionProvider build(@Nonnull PositionProviderAsset.Argument argument) { + if (super.skip()) { + return PositionProvider.noPositionProvider(); + } else { + PositionProvider positionProvider = this.positionProviderAsset.build(argument); + return new VerticalEliminatorPositionProvider(this.minY, this.maxY, positionProvider); + } + } + + @Override + public void cleanUp() { + this.positionProviderAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/AssignmentsAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/AssignmentsAsset.java new file mode 100644 index 0000000..011adbf --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/AssignmentsAsset.java @@ -0,0 +1,101 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.propassignments; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.propdistributions.Assignments; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; + +public abstract class AssignmentsAsset implements Cleanable, JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + private static final Map exportedNodes = new ConcurrentHashMap<>(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(AssignmentsAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(AssignmentsAsset.class) + .append(new KeyedCodec<>("Skip", Codec.BOOLEAN, false), (t, k) -> t.skip = k, t -> t.skip) + .add() + .append(new KeyedCodec<>("ExportAs", Codec.STRING, false), (t, k) -> t.exportName = k, t -> t.exportName) + .add() + .afterDecode(asset -> { + if (asset.exportName != null && !asset.exportName.isEmpty()) { + if (exportedNodes.containsKey(asset.exportName)) { + LoggerUtil.getLogger().warning("Duplicate export name for asset: " + asset.exportName); + } + + exportedNodes.put(asset.exportName, asset); + LoggerUtil.getLogger().fine("Registered imported position provider asset with name '" + asset.exportName + "' with asset id '" + asset.id); + } + }) + .build(); + private String id; + private AssetExtraInfo.Data data; + private boolean skip = false; + private String exportName = ""; + + protected AssignmentsAsset() { + } + + public abstract Assignments build(@Nonnull AssignmentsAsset.Argument var1); + + public boolean skip() { + return this.skip; + } + + public static AssignmentsAsset getExportedAsset(@Nonnull String name) { + return exportedNodes.get(name); + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + } + + public static class Argument { + public SeedBox parentSeed; + public MaterialCache materialCache; + public ReferenceBundle referenceBundle; + public int runtime; + public WorkerIndexer workerIndexer; + + public Argument( + @Nonnull SeedBox parentSeed, + @Nonnull MaterialCache materialCache, + @Nonnull ReferenceBundle referenceBundle, + int runtime, + @Nonnull WorkerIndexer workerIndexer + ) { + this.parentSeed = parentSeed; + this.materialCache = materialCache; + this.referenceBundle = referenceBundle; + this.runtime = runtime; + this.workerIndexer = workerIndexer; + } + + public Argument(@Nonnull AssignmentsAsset.Argument argument) { + this.parentSeed = argument.parentSeed; + this.materialCache = argument.materialCache; + this.referenceBundle = argument.referenceBundle; + this.runtime = argument.runtime; + this.workerIndexer = argument.workerIndexer; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/ConstantAssignmentsAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/ConstantAssignmentsAsset.java new file mode 100644 index 0000000..69bc5d9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/ConstantAssignmentsAsset.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.propassignments; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.NoPropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.PropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.propdistributions.Assignments; +import com.hypixel.hytale.builtin.hytalegenerator.propdistributions.ConstantAssignments; +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class ConstantAssignmentsAsset extends AssignmentsAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ConstantAssignmentsAsset.class, ConstantAssignmentsAsset::new, AssignmentsAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Prop", PropAsset.CODEC, true), (asset, v) -> asset.propAsset = v, asset -> asset.propAsset) + .add() + .build(); + private PropAsset propAsset = new NoPropAsset(); + + public ConstantAssignmentsAsset() { + } + + @Nonnull + @Override + public Assignments build(@Nonnull AssignmentsAsset.Argument argument) { + if (super.skip()) { + return Assignments.noPropDistribution(argument.runtime); + } else { + Prop prop = this.propAsset + .build(new PropAsset.Argument(argument.parentSeed, argument.materialCache, argument.referenceBundle, argument.workerIndexer)); + return new ConstantAssignments(prop, argument.runtime); + } + } + + @Override + public void cleanUp() { + this.propAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/FieldFunctionAssignmentsAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/FieldFunctionAssignmentsAsset.java new file mode 100644 index 0000000..3c0c985 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/FieldFunctionAssignmentsAsset.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.propassignments; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ConstantDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.propdistributions.Assignments; +import com.hypixel.hytale.builtin.hytalegenerator.propdistributions.FieldFunctionAssignments; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class FieldFunctionAssignmentsAsset extends AssignmentsAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + FieldFunctionAssignmentsAsset.class, FieldFunctionAssignmentsAsset::new, AssignmentsAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>( + "Delimiters", new ArrayCodec<>(FieldFunctionAssignmentsAsset.DelimiterAsset.CODEC, FieldFunctionAssignmentsAsset.DelimiterAsset[]::new), true + ), + (asset, v) -> asset.delimiterAssets = v, + asset -> asset.delimiterAssets + ) + .add() + .append(new KeyedCodec<>("FieldFunction", DensityAsset.CODEC, true), (asset, v) -> asset.densityAsset = v, asset -> asset.densityAsset) + .add() + .build(); + private FieldFunctionAssignmentsAsset.DelimiterAsset[] delimiterAssets = new FieldFunctionAssignmentsAsset.DelimiterAsset[0]; + private DensityAsset densityAsset = new ConstantDensityAsset(); + + public FieldFunctionAssignmentsAsset() { + } + + @Nonnull + @Override + public Assignments build(@Nonnull AssignmentsAsset.Argument argument) { + if (super.skip()) { + return Assignments.noPropDistribution(argument.runtime); + } else { + Density functionTree = this.densityAsset.build(DensityAsset.from(argument)); + ArrayList delimiterList = new ArrayList<>(); + + for (FieldFunctionAssignmentsAsset.DelimiterAsset asset : this.delimiterAssets) { + Assignments propDistribution = asset.assignmentsAsset.build(argument); + FieldFunctionAssignments.FieldDelimiter delimiter = new FieldFunctionAssignments.FieldDelimiter(propDistribution, asset.min, asset.max); + delimiterList.add(delimiter); + } + + return new FieldFunctionAssignments(functionTree, delimiterList, argument.runtime); + } + } + + @Override + public void cleanUp() { + this.densityAsset.cleanUp(); + } + + public static class DelimiterAsset implements Cleanable, JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + FieldFunctionAssignmentsAsset.DelimiterAsset.class, + FieldFunctionAssignmentsAsset.DelimiterAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Assignments", AssignmentsAsset.CODEC, true), (t, v) -> t.assignmentsAsset = v, t -> t.assignmentsAsset) + .add() + .append(new KeyedCodec<>("Min", Codec.DOUBLE, true), (t, v) -> t.min = v, t -> t.min) + .add() + .append(new KeyedCodec<>("Max", Codec.DOUBLE, true), (t, v) -> t.max = v, t -> t.max) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private double min; + private double max; + private AssignmentsAsset assignmentsAsset = new ConstantAssignmentsAsset(); + + public DelimiterAsset() { + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + this.assignmentsAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/ImportedAssignmentsAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/ImportedAssignmentsAsset.java new file mode 100644 index 0000000..5c5aa76 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/ImportedAssignmentsAsset.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.propassignments; + +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.propdistributions.Assignments; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class ImportedAssignmentsAsset extends AssignmentsAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ImportedAssignmentsAsset.class, ImportedAssignmentsAsset::new, AssignmentsAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Name", Codec.STRING, true), (asset, v) -> asset.name = v, asset -> asset.name) + .add() + .build(); + private String name = ""; + + public ImportedAssignmentsAsset() { + } + + @Override + public Assignments build(@Nonnull AssignmentsAsset.Argument argument) { + if (super.skip()) { + return Assignments.noPropDistribution(argument.runtime); + } else { + AssignmentsAsset asset = getExportedAsset(this.name); + if (asset == null) { + LoggerUtil.getLogger().warning("Couldn't find Assignments asset exported with name: '" + this.name + "'."); + return Assignments.noPropDistribution(argument.runtime); + } else { + return asset.build(argument); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/SandwichAssignmentsAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/SandwichAssignmentsAsset.java new file mode 100644 index 0000000..600ba67 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/SandwichAssignmentsAsset.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.propassignments; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.propdistributions.Assignments; +import com.hypixel.hytale.builtin.hytalegenerator.propdistributions.SandwichAssignments; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class SandwichAssignmentsAsset extends AssignmentsAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SandwichAssignmentsAsset.class, SandwichAssignmentsAsset::new, AssignmentsAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>("Delimiters", new ArrayCodec<>(SandwichAssignmentsAsset.DelimiterAsset.CODEC, SandwichAssignmentsAsset.DelimiterAsset[]::new), true), + (asset, v) -> asset.delimiterAssets = v, + asset -> asset.delimiterAssets + ) + .add() + .build(); + private SandwichAssignmentsAsset.DelimiterAsset[] delimiterAssets = new SandwichAssignmentsAsset.DelimiterAsset[0]; + + public SandwichAssignmentsAsset() { + } + + @Nonnull + @Override + public Assignments build(@Nonnull AssignmentsAsset.Argument argument) { + if (super.skip()) { + return Assignments.noPropDistribution(argument.runtime); + } else { + ArrayList delimiterList = new ArrayList<>(); + + for (SandwichAssignmentsAsset.DelimiterAsset asset : this.delimiterAssets) { + Assignments propDistribution = asset.assignmentsAsset.build(argument); + SandwichAssignments.VerticalDelimiter delimiter = new SandwichAssignments.VerticalDelimiter(propDistribution, asset.min, asset.max); + delimiterList.add(delimiter); + } + + return new SandwichAssignments(delimiterList, argument.runtime); + } + } + + @Override + public void cleanUp() { + for (SandwichAssignmentsAsset.DelimiterAsset delimiterAsset : this.delimiterAssets) { + delimiterAsset.cleanUp(); + } + } + + public static class DelimiterAsset implements Cleanable, JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + SandwichAssignmentsAsset.DelimiterAsset.class, + SandwichAssignmentsAsset.DelimiterAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Assignments", AssignmentsAsset.CODEC, true), (t, v) -> t.assignmentsAsset = v, t -> t.assignmentsAsset) + .add() + .append(new KeyedCodec<>("MinY", Codec.DOUBLE, true), (t, v) -> t.min = v, t -> t.min) + .add() + .append(new KeyedCodec<>("MaxY", Codec.DOUBLE, true), (t, v) -> t.max = v, t -> t.max) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private double min; + private double max; + private AssignmentsAsset assignmentsAsset = new ConstantAssignmentsAsset(); + + public DelimiterAsset() { + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + this.assignmentsAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/WeightedAssignmentsAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/WeightedAssignmentsAsset.java new file mode 100644 index 0000000..a049535 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propassignments/WeightedAssignmentsAsset.java @@ -0,0 +1,98 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.propassignments; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.WeightedMap; +import com.hypixel.hytale.builtin.hytalegenerator.propdistributions.Assignments; +import com.hypixel.hytale.builtin.hytalegenerator.propdistributions.WeightedAssignments; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import javax.annotation.Nonnull; + +public class WeightedAssignmentsAsset extends AssignmentsAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + WeightedAssignmentsAsset.class, WeightedAssignmentsAsset::new, AssignmentsAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("SkipChance", Codec.DOUBLE, true), (asset, v) -> asset.skipChance = v, asset -> asset.skipChance) + .add() + .append(new KeyedCodec<>("Seed", Codec.STRING, true), (asset, v) -> asset.seed = v, asset -> asset.seed) + .add() + .append( + new KeyedCodec<>( + "WeightedAssignments", new ArrayCodec<>(WeightedAssignmentsAsset.WeightedAssets.CODEC, WeightedAssignmentsAsset.WeightedAssets[]::new), true + ), + (asset, v) -> asset.weightedAssets = v, + asset -> asset.weightedAssets + ) + .add() + .build(); + private WeightedAssignmentsAsset.WeightedAssets[] weightedAssets = new WeightedAssignmentsAsset.WeightedAssets[0]; + private String seed = ""; + private double skipChance = 0.0; + + public WeightedAssignmentsAsset() { + } + + @Nonnull + @Override + public Assignments build(@Nonnull AssignmentsAsset.Argument argument) { + if (super.skip()) { + return Assignments.noPropDistribution(argument.runtime); + } else { + WeightedMap weightMap = new WeightedMap<>(); + + for (WeightedAssignmentsAsset.WeightedAssets asset : this.weightedAssets) { + weightMap.add(asset.assignmentsAsset.build(argument), asset.weight); + } + + SeedBox childSeed = argument.parentSeed.child(this.seed); + return new WeightedAssignments(weightMap, childSeed.createSupplier().get(), this.skipChance, argument.runtime); + } + } + + @Override + public void cleanUp() { + for (WeightedAssignmentsAsset.WeightedAssets weightedAsset : this.weightedAssets) { + weightedAsset.cleanUp(); + } + } + + public static class WeightedAssets implements Cleanable, JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + WeightedAssignmentsAsset.WeightedAssets.class, + WeightedAssignmentsAsset.WeightedAssets::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Weight", Codec.DOUBLE, true), (t, v) -> t.weight = v, t -> t.weight) + .add() + .append(new KeyedCodec<>("Assignments", AssignmentsAsset.CODEC, true), (t, v) -> t.assignmentsAsset = v, t -> t.assignmentsAsset) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private double weight = 1.0; + private AssignmentsAsset assignmentsAsset = new ConstantAssignmentsAsset(); + + public WeightedAssets() { + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + this.assignmentsAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/BoxPropAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/BoxPropAsset.java new file mode 100644 index 0000000..01a5dee --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/BoxPropAsset.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.material.MaterialAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.ConstantPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.PatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.OriginScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.ScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.props.BoxProp; +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class BoxPropAsset extends PropAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(BoxPropAsset.class, BoxPropAsset::new, PropAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Range", Vector3i.CODEC, true), (asset, v) -> asset.range = v, asset -> asset.range) + .add() + .append(new KeyedCodec<>("Material", MaterialAsset.CODEC, true), (asset, value) -> asset.materialAsset = value, asset -> asset.materialAsset) + .add() + .append(new KeyedCodec<>("Pattern", PatternAsset.CODEC, true), (asset, v) -> asset.patternAsset = v, asset -> asset.patternAsset) + .add() + .append(new KeyedCodec<>("Scanner", ScannerAsset.CODEC, true), (asset, v) -> asset.scannerAsset = v, asset -> asset.scannerAsset) + .add() + .build(); + private Vector3i range = new Vector3i(); + private MaterialAsset materialAsset = new MaterialAsset(); + private PatternAsset patternAsset = new ConstantPatternAsset(); + private ScannerAsset scannerAsset = new OriginScannerAsset(); + + public BoxPropAsset() { + } + + @Nonnull + @Override + public Prop build(@Nonnull PropAsset.Argument argument) { + if (super.skip()) { + return Prop.noProp(); + } else { + Material material = this.materialAsset.build(argument.materialCache); + return (Prop)(this.scannerAsset != null && this.patternAsset != null + ? new BoxProp( + this.range, material, this.scannerAsset.build(ScannerAsset.argumentFrom(argument)), this.patternAsset.build(PatternAsset.argumentFrom(argument)) + ) + : Prop.noProp()); + } + } + + @Override + public void cleanUp() { + this.patternAsset.cleanUp(); + this.scannerAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/ClusterPropAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/ClusterPropAsset.java new file mode 100644 index 0000000..a385516 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/ClusterPropAsset.java @@ -0,0 +1,121 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.ConstantCurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.curves.CurveAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.ConstantPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.PatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.OriginScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.ScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.WeightedMap; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.props.ClusterProp; +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.OriginScanner; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class ClusterPropAsset extends PropAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(ClusterPropAsset.class, ClusterPropAsset::new, PropAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Range", Codec.INTEGER, false), (asset, v) -> asset.range = v, asset -> asset.range) + .addValidator(Validators.greaterThanOrEqual(0)) + .add() + .append(new KeyedCodec<>("DistanceCurve", CurveAsset.CODEC, true), (asset, v) -> asset.distanceCurve = v, asset -> asset.distanceCurve) + .add() + .append(new KeyedCodec<>("Seed", Codec.STRING, false), (asset, v) -> asset.seed = v, asset -> asset.seed) + .add() + .append( + new KeyedCodec<>("WeightedProps", new ArrayCodec<>(ClusterPropAsset.WeightedPropAsset.CODEC, ClusterPropAsset.WeightedPropAsset[]::new), true), + (asset, v) -> asset.weightedPropAssets = v, + asset -> asset.weightedPropAssets + ) + .add() + .append(new KeyedCodec<>("Pattern", PatternAsset.CODEC, false), (asset, v) -> asset.patternAsset = v, asset -> asset.patternAsset) + .add() + .append(new KeyedCodec<>("Scanner", ScannerAsset.CODEC, false), (asset, v) -> asset.scannerAsset = v, asset -> asset.scannerAsset) + .add() + .build(); + private int range = 0; + private CurveAsset distanceCurve = new ConstantCurveAsset(); + private String seed = "A"; + private ClusterPropAsset.WeightedPropAsset[] weightedPropAssets = new ClusterPropAsset.WeightedPropAsset[0]; + private PatternAsset patternAsset = new ConstantPatternAsset(); + private ScannerAsset scannerAsset = new OriginScannerAsset(); + + public ClusterPropAsset() { + } + + @Nonnull + @Override + public Prop build(@Nonnull PropAsset.Argument argument) { + if (super.skip()) { + return Prop.noProp(); + } else { + WeightedMap weightedMap = new WeightedMap<>(); + + for (ClusterPropAsset.WeightedPropAsset entry : this.weightedPropAssets) { + weightedMap.add(entry.propAsset.build(argument), entry.weight); + } + + Pattern pattern = this.patternAsset == null ? Pattern.yesPattern() : this.patternAsset.build(PatternAsset.argumentFrom(argument)); + Scanner scanner = (Scanner)(this.scannerAsset == null ? OriginScanner.getInstance() : this.scannerAsset.build(ScannerAsset.argumentFrom(argument))); + int intSeed = argument.parentSeed.child(this.seed).createSupplier().get(); + return new ClusterProp(this.range, this.distanceCurve.build(), intSeed, weightedMap, pattern, scanner); + } + } + + @Override + public void cleanUp() { + this.distanceCurve.cleanUp(); + + for (ClusterPropAsset.WeightedPropAsset weightedPropAsset : this.weightedPropAssets) { + weightedPropAsset.cleanUp(); + } + + this.patternAsset.cleanUp(); + this.scannerAsset.cleanUp(); + } + + public static class WeightedPropAsset implements Cleanable, JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ClusterPropAsset.WeightedPropAsset.class, + ClusterPropAsset.WeightedPropAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Weight", Codec.DOUBLE, true), (t, w) -> t.weight = w, t -> t.weight) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("ColumnProp", PropAsset.CODEC, true), (t, out) -> t.propAsset = out, t -> t.propAsset) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private double weight = 1.0; + private PropAsset propAsset; + + public WeightedPropAsset() { + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + this.propAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/ColumnPropAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/ColumnPropAsset.java new file mode 100644 index 0000000..1a00018 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/ColumnPropAsset.java @@ -0,0 +1,114 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.BlockMask; +import com.hypixel.hytale.builtin.hytalegenerator.assets.blockmask.BlockMaskAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.material.MaterialAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop.directionality.DirectionalityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop.directionality.StaticDirectionalityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.OriginScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.ScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.props.ColumnProp; +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.Directionality; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class ColumnPropAsset extends PropAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(ColumnPropAsset.class, ColumnPropAsset::new, PropAsset.ABSTRACT_CODEC) + .append( + new KeyedCodec<>("ColumnBlocks", new ArrayCodec<>(ColumnPropAsset.ColumnBlock.CODEC, ColumnPropAsset.ColumnBlock[]::new), true), + (asset, v) -> asset.columnBlocks = v, + asset -> asset.columnBlocks + ) + .add() + .append(new KeyedCodec<>("BlockMask", BlockMaskAsset.CODEC, false), (asset, v) -> asset.blockMaskAsset = v, asset -> asset.blockMaskAsset) + .add() + .append( + new KeyedCodec<>("Directionality", DirectionalityAsset.CODEC, true), (asset, v) -> asset.directionalityAsset = v, asset -> asset.directionalityAsset + ) + .add() + .append(new KeyedCodec<>("Scanner", ScannerAsset.CODEC, true), (asset, v) -> asset.scannerAsset = v, asset -> asset.scannerAsset) + .add() + .build(); + private ColumnPropAsset.ColumnBlock[] columnBlocks = new ColumnPropAsset.ColumnBlock[0]; + private BlockMaskAsset blockMaskAsset = new BlockMaskAsset(); + private DirectionalityAsset directionalityAsset = new StaticDirectionalityAsset(); + private ScannerAsset scannerAsset = new OriginScannerAsset(); + + public ColumnPropAsset() { + } + + @Nonnull + @Override + public Prop build(@Nonnull PropAsset.Argument argument) { + if (super.skip()) { + return Prop.noProp(); + } else if (this.directionalityAsset == null) { + return Prop.noProp(); + } else { + ArrayList blockPositions = new ArrayList<>(); + ArrayList blockTypes = new ArrayList<>(); + + for (int i = 0; i < this.columnBlocks.length; i++) { + blockPositions.add(this.columnBlocks[i].y); + blockTypes.add(this.columnBlocks[i].materialAsset.build(argument.materialCache)); + } + + BlockMask blockMask = null; + if (this.blockMaskAsset != null) { + blockMask = this.blockMaskAsset.build(argument.materialCache); + } else { + blockMask = new BlockMask(); + } + + Directionality directionality = this.directionalityAsset.build(DirectionalityAsset.argumentFrom(argument)); + Scanner scanner = this.scannerAsset.build(ScannerAsset.argumentFrom(argument)); + return new ColumnProp(blockPositions, blockTypes, blockMask, scanner, directionality, argument.materialCache); + } + } + + @Override + public void cleanUp() { + this.blockMaskAsset.cleanUp(); + this.directionalityAsset.cleanUp(); + this.scannerAsset.cleanUp(); + } + + public static class ColumnBlock implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ColumnPropAsset.ColumnBlock.class, + ColumnPropAsset.ColumnBlock::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Y", Codec.INTEGER, true), (t, y) -> t.y = y, t -> t.y) + .add() + .append(new KeyedCodec<>("Material", MaterialAsset.CODEC, true), (asset, value) -> asset.materialAsset = value, asset -> asset.materialAsset) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private int y = 1; + private MaterialAsset materialAsset = new MaterialAsset("Empty", "Empty"); + + public ColumnBlock() { + } + + public String getId() { + return this.id; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/DensityPropAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/DensityPropAsset.java new file mode 100644 index 0000000..ecbd19e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/DensityPropAsset.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props; + +import com.hypixel.hytale.builtin.hytalegenerator.BlockMask; +import com.hypixel.hytale.builtin.hytalegenerator.assets.blockmask.BlockMaskAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ConstantDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.ConstantMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.MaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.ConstantPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.PatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.OriginScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.ScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.props.DensityProp; +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class DensityPropAsset extends PropAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(DensityPropAsset.class, DensityPropAsset::new, PropAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Range", Vector3i.CODEC, true), (asset, v) -> asset.range = v, asset -> asset.range) + .addValidator((LegacyValidator)((v, r) -> { + if (v.x < 0 || v.y < 0 || v.z < 0) { + r.fail("Range has a value smaller than 0"); + } + })) + .add() + .append(new KeyedCodec<>("PlacementMask", BlockMaskAsset.CODEC, true), (asset, v) -> asset.placementMaskAsset = v, asset -> asset.placementMaskAsset) + .add() + .append(new KeyedCodec<>("Pattern", PatternAsset.CODEC, true), (asset, v) -> asset.patternAsset = v, asset -> asset.patternAsset) + .add() + .append(new KeyedCodec<>("Scanner", ScannerAsset.CODEC, true), (asset, v) -> asset.scannerAsset = v, asset -> asset.scannerAsset) + .add() + .append(new KeyedCodec<>("Density", DensityAsset.CODEC, true), (asset, v) -> asset.densityAsset = v, asset -> asset.densityAsset) + .add() + .append( + new KeyedCodec<>("Material", MaterialProviderAsset.CODEC, true), (asset, v) -> asset.materialProviderAsset = v, asset -> asset.materialProviderAsset + ) + .add() + .build(); + private Vector3i range = new Vector3i(); + private BlockMaskAsset placementMaskAsset = new BlockMaskAsset(); + private PatternAsset patternAsset = new ConstantPatternAsset(); + private ScannerAsset scannerAsset = new OriginScannerAsset(); + private MaterialProviderAsset materialProviderAsset = new ConstantMaterialProviderAsset(); + private DensityAsset densityAsset = new ConstantDensityAsset(); + + public DensityPropAsset() { + } + + @Nonnull + @Override + public Prop build(@Nonnull PropAsset.Argument argument) { + if (super.skip()) { + return Prop.noProp(); + } else if (this.placementMaskAsset == null) { + return Prop.noProp(); + } else { + BlockMask placementMask = this.placementMaskAsset.build(argument.materialCache); + return (Prop)(this.scannerAsset != null && this.patternAsset != null && this.densityAsset != null && this.materialProviderAsset != null + ? new DensityProp( + this.range, + this.densityAsset.build(DensityAsset.from(argument)), + this.materialProviderAsset.build(MaterialProviderAsset.argumentFrom(argument)), + this.scannerAsset.build(ScannerAsset.argumentFrom(argument)), + this.patternAsset.build(PatternAsset.argumentFrom(argument)), + placementMask, + new Material(argument.materialCache.EMPTY_AIR, argument.materialCache.EMPTY_FLUID) + ) + : Prop.noProp()); + } + } + + @Override + public void cleanUp() { + this.placementMaskAsset.cleanUp(); + this.patternAsset.cleanUp(); + this.scannerAsset.cleanUp(); + this.materialProviderAsset.cleanUp(); + this.densityAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/ImportedPropAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/ImportedPropAsset.java new file mode 100644 index 0000000..7920cec --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/ImportedPropAsset.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props; + +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.logger.HytaleLogger; +import javax.annotation.Nonnull; + +public class ImportedPropAsset extends PropAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(ImportedPropAsset.class, ImportedPropAsset::new, PropAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("Name", Codec.STRING, true), (asset, v) -> asset.name = v, asset -> asset.name) + .add() + .build(); + private String name = ""; + + public ImportedPropAsset() { + } + + @Override + public Prop build(@Nonnull PropAsset.Argument argument) { + if (super.skip()) { + return Prop.noProp(); + } else if (this.name != null && !this.name.isEmpty()) { + PropAsset exportedAsset = PropAsset.getExportedAsset(this.name); + return exportedAsset == null ? Prop.noProp() : exportedAsset.build(argument); + } else { + HytaleLogger.getLogger().atWarning().log("An exported Pattern with the name does not exist: " + this.name); + return Prop.noProp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/NoPropAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/NoPropAsset.java new file mode 100644 index 0000000..bca6987 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/NoPropAsset.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.conveyor.stagedconveyor.ContextDependency; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.builtin.hytalegenerator.props.ScanResult; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class NoPropAsset extends PropAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(NoPropAsset.class, NoPropAsset::new, PropAsset.ABSTRACT_CODEC).build(); + + public NoPropAsset() { + } + + @Nonnull + @Override + public Prop build(@Nonnull PropAsset.Argument argument) { + return new Prop() { + final Bounds3i writeBounds_voxelGrid = new Bounds3i(); + + @Override + public ScanResult scan(@Nonnull Vector3i position, @Nonnull VoxelSpace materialSpace, @Nonnull WorkerIndexer.Id id) { + return ScanResult.noScanResult(); + } + + @Override + public void place(@Nonnull Prop.Context context) { + } + + @Override + public ContextDependency getContextDependency() { + return ContextDependency.EMPTY; + } + + @Nonnull + @Override + public Bounds3i getWriteBounds() { + return this.writeBounds_voxelGrid; + } + }; + } + + @Override + public void cleanUp() { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/PondFillerPropAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/PondFillerPropAsset.java new file mode 100644 index 0000000..7b04cc3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/PondFillerPropAsset.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props; + +import com.hypixel.hytale.builtin.hytalegenerator.MaterialSet; +import com.hypixel.hytale.builtin.hytalegenerator.assets.blockset.MaterialSetAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.ConstantMaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.materialproviders.MaterialProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.ConstantPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.PatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.OriginScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.ScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.builtin.hytalegenerator.props.filler.PondFillerProp; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class PondFillerPropAsset extends PropAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + PondFillerPropAsset.class, PondFillerPropAsset::new, PropAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("BoundingMin", Vector3i.CODEC, true), (asset, v) -> asset.boundingMin = v, asset -> asset.boundingMin) + .add() + .append(new KeyedCodec<>("BoundingMax", Vector3i.CODEC, true), (asset, v) -> asset.boundingMax = v, asset -> asset.boundingMax) + .add() + .append( + new KeyedCodec<>("FillMaterial", MaterialProviderAsset.CODEC, true), + (asset, v) -> asset.fluidMaterialProviderAsset = v, + asset -> asset.fluidMaterialProviderAsset + ) + .add() + .append(new KeyedCodec<>("BarrierBlockSet", MaterialSetAsset.CODEC, true), (asset, v) -> asset.solidSetAsset = v, asset -> asset.solidSetAsset) + .add() + .append(new KeyedCodec<>("Pattern", PatternAsset.CODEC, true), (asset, v) -> asset.patternAsset = v, asset -> asset.patternAsset) + .add() + .append(new KeyedCodec<>("Scanner", ScannerAsset.CODEC, true), (asset, v) -> asset.scannerAsset = v, asset -> asset.scannerAsset) + .add() + .build(); + private Vector3i boundingMin = new Vector3i(-10, -10, -10); + private Vector3i boundingMax = new Vector3i(10, 10, 10); + private MaterialProviderAsset fluidMaterialProviderAsset = new ConstantMaterialProviderAsset(); + private MaterialSetAsset solidSetAsset = new MaterialSetAsset(); + private PatternAsset patternAsset = new ConstantPatternAsset(); + private ScannerAsset scannerAsset = new OriginScannerAsset(); + + public PondFillerPropAsset() { + } + + @Nonnull + @Override + public Prop build(@Nonnull PropAsset.Argument argument) { + if (super.skip()) { + return Prop.noProp(); + } else if (this.scannerAsset != null && this.patternAsset != null && this.fluidMaterialProviderAsset != null && this.solidSetAsset != null) { + MaterialProvider materialProvider = this.fluidMaterialProviderAsset.build(MaterialProviderAsset.argumentFrom(argument)); + MaterialSet solidSet = this.solidSetAsset.build(argument.materialCache); + Pattern pattern = this.patternAsset.build(PatternAsset.argumentFrom(argument)); + Scanner scanner = this.scannerAsset.build(ScannerAsset.argumentFrom(argument)); + return new PondFillerProp(this.boundingMin, this.boundingMax, solidSet, materialProvider, scanner, pattern); + } else { + return Prop.noProp(); + } + } + + @Override + public void cleanUp() { + this.fluidMaterialProviderAsset.cleanUp(); + this.solidSetAsset.cleanUp(); + this.patternAsset.cleanUp(); + this.scannerAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/PropAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/PropAsset.java new file mode 100644 index 0000000..b36d0cb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/PropAsset.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; + +public abstract class PropAsset implements Cleanable, JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + private static final Map exportedNodes = new ConcurrentHashMap<>(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(PropAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(PropAsset.class) + .append(new KeyedCodec<>("Skip", Codec.BOOLEAN, false), (t, k) -> t.skip = k, t -> t.skip) + .add() + .append(new KeyedCodec<>("ExportAs", Codec.STRING, false), (t, k) -> t.exportName = k, t -> t.exportName) + .add() + .afterDecode(asset -> { + if (asset.exportName != null && !asset.exportName.isEmpty()) { + if (exportedNodes.containsKey(asset.exportName)) { + LoggerUtil.getLogger().warning("Duplicate export name for asset: " + asset.exportName); + } + + exportedNodes.put(asset.exportName, asset); + LoggerUtil.getLogger().fine("Registered imported position provider asset with name '" + asset.exportName + "' with asset id '" + asset.id); + } + }) + .build(); + private String id; + private AssetExtraInfo.Data data; + private boolean skip = false; + private String exportName = ""; + + protected PropAsset() { + } + + public abstract Prop build(@Nonnull PropAsset.Argument var1); + + public boolean skip() { + return this.skip; + } + + public static PropAsset getExportedAsset(@Nonnull String name) { + return exportedNodes.get(name); + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + } + + public static class Argument { + public SeedBox parentSeed; + public MaterialCache materialCache; + public ReferenceBundle referenceBundle; + public WorkerIndexer workerIndexer; + + public Argument( + @Nonnull SeedBox parentSeed, @Nonnull MaterialCache materialCache, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer + ) { + this.parentSeed = parentSeed; + this.materialCache = materialCache; + this.referenceBundle = referenceBundle; + this.workerIndexer = workerIndexer; + } + + public Argument(@Nonnull PropAsset.Argument argument) { + this.parentSeed = argument.parentSeed; + this.materialCache = argument.materialCache; + this.referenceBundle = argument.referenceBundle; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/QueuePropAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/QueuePropAsset.java new file mode 100644 index 0000000..0f6b1f0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/QueuePropAsset.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props; + +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.builtin.hytalegenerator.props.QueueProp; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class QueuePropAsset extends PropAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(QueuePropAsset.class, QueuePropAsset::new, PropAsset.ABSTRACT_CODEC) + .append( + new KeyedCodec<>("Queue", new ArrayCodec<>(PropAsset.CODEC, PropAsset[]::new), true), (asset, v) -> asset.propAssets = v, asset -> asset.propAssets + ) + .add() + .build(); + private PropAsset[] propAssets = new PropAsset[0]; + + public QueuePropAsset() { + } + + @Nonnull + @Override + public Prop build(@Nonnull PropAsset.Argument argument) { + if (super.skip()) { + return Prop.noProp(); + } else { + ArrayList propsQueue = new ArrayList<>(this.propAssets.length); + + for (PropAsset asset : this.propAssets) { + propsQueue.add(asset.build(argument)); + } + + return new QueueProp(propsQueue); + } + } + + @Override + public void cleanUp() { + for (PropAsset propAsset : this.propAssets) { + propAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/UnionPropAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/UnionPropAsset.java new file mode 100644 index 0000000..9eed7eb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/UnionPropAsset.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props; + +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.builtin.hytalegenerator.props.UnionProp; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class UnionPropAsset extends PropAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(UnionPropAsset.class, UnionPropAsset::new, PropAsset.ABSTRACT_CODEC) + .append( + new KeyedCodec<>("Props", new ArrayCodec<>(PropAsset.CODEC, PropAsset[]::new), true), (asset, v) -> asset.propAssets = v, asset -> asset.propAssets + ) + .add() + .build(); + private PropAsset[] propAssets = new PropAsset[0]; + + public UnionPropAsset() { + } + + @Nonnull + @Override + public Prop build(@Nonnull PropAsset.Argument argument) { + if (super.skip()) { + return Prop.noProp(); + } else { + ArrayList chainedProps = new ArrayList<>(this.propAssets.length); + + for (PropAsset asset : this.propAssets) { + chainedProps.add(asset.build(argument)); + } + + return new UnionProp(chainedProps); + } + } + + @Override + public void cleanUp() { + for (PropAsset propAsset : this.propAssets) { + propAsset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/PrefabFileVisitor.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/PrefabFileVisitor.java new file mode 100644 index 0000000..06265a3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/PrefabFileVisitor.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop; + +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.List; +import javax.annotation.Nonnull; + +public class PrefabFileVisitor extends SimpleFileVisitor { + @Nonnull + private final List prefabBuffers; + + public PrefabFileVisitor(@Nonnull List prefabBuffers) { + this.prefabBuffers = prefabBuffers; + } + + @Nonnull + public FileVisitResult visitFile(@Nonnull Path file, @Nonnull BasicFileAttributes attrs) throws IOException { + if (!attrs.isRegularFile()) { + return FileVisitResult.CONTINUE; + } else { + PrefabBuffer loadedPrefab = PrefabLoader.loadPrefabBufferAt(file); + if (loadedPrefab == null) { + return FileVisitResult.CONTINUE; + } else { + this.prefabBuffers.add(loadedPrefab); + return FileVisitResult.CONTINUE; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/PrefabLoader.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/PrefabLoader.java new file mode 100644 index 0000000..c1d6e30 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/PrefabLoader.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop; + +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.common.util.ExceptionUtil; +import com.hypixel.hytale.server.core.prefab.selection.buffer.BsonPrefabBufferDeserializer; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer; +import com.hypixel.hytale.server.core.util.BsonUtil; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public class PrefabLoader { + public PrefabLoader() { + } + + @Nonnull + public static void loadAllPrefabBuffersUnder(@Nonnull Path dirPath, List pathPrefabs) { + if (!Files.isDirectory(dirPath)) { + PrefabBuffer prefab = loadPrefabBufferAt(dirPath); + if (prefab != null) { + pathPrefabs.add(prefab); + } + } else { + try { + Files.walkFileTree(dirPath, new PrefabFileVisitor(pathPrefabs)); + } catch (IOException var4) { + String msg = "Exception thrown by HytaleGenerator while loading a Prefab:\n"; + msg = msg + ExceptionUtil.toStringWithStack(var4); + LoggerUtil.getLogger().severe(msg); + } + } + } + + @Nullable + public static PrefabBuffer loadPrefabBufferAt(@Nonnull Path filePath) { + if (!hasJsonExtension(filePath)) { + return null; + } else { + try { + BsonDocument prefabAsBson = BsonUtil.readDocumentNow(filePath); + return prefabAsBson == null ? null : BsonPrefabBufferDeserializer.INSTANCE.deserialize(filePath, prefabAsBson); + } catch (Exception var3) { + String msg = "Exception thrown by HytaleGenerator while loading a PrefabBuffer for " + filePath + ":\n"; + msg = msg + ExceptionUtil.toStringWithStack(var3); + LoggerUtil.getLogger().severe(msg); + return null; + } + } + } + + public static boolean hasJsonExtension(@Nonnull Path path) { + String pathString = path.toString(); + return pathString.toLowerCase().endsWith(".json"); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/PrefabPropAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/PrefabPropAsset.java new file mode 100644 index 0000000..2c9004c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/PrefabPropAsset.java @@ -0,0 +1,212 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.BlockMask; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.assets.blockmask.BlockMaskAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.ConstantPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.PatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.PropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop.directionality.DirectionalityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop.directionality.StaticDirectionalityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.OriginScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.scanners.ScannerAsset; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.WeightedMap; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.Directionality; +import com.hypixel.hytale.builtin.hytalegenerator.props.prefab.MoldingDirection; +import com.hypixel.hytale.builtin.hytalegenerator.props.prefab.PrefabMoldingConfiguration; +import com.hypixel.hytale.builtin.hytalegenerator.props.prefab.PrefabProp; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.ExceptionUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabPropAsset extends PropAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(PrefabPropAsset.class, PrefabPropAsset::new, PropAsset.ABSTRACT_CODEC) + .append( + new KeyedCodec<>("WeightedPrefabPaths", new ArrayCodec<>(PrefabPropAsset.WeightedPathAsset.CODEC, PrefabPropAsset.WeightedPathAsset[]::new), true), + (asset, v) -> asset.weightedPrefabPathAssets = v, + asset -> asset.weightedPrefabPathAssets + ) + .add() + .append(new KeyedCodec<>("LegacyPath", Codec.BOOLEAN, false), (asset, v) -> asset.legacyPath = v, asset -> asset.legacyPath) + .add() + .append( + new KeyedCodec<>("Directionality", DirectionalityAsset.CODEC, true), (asset, v) -> asset.directionalityAsset = v, asset -> asset.directionalityAsset + ) + .add() + .append(new KeyedCodec<>("Scanner", ScannerAsset.CODEC, true), (asset, v) -> asset.scannerAsset = v, asset -> asset.scannerAsset) + .add() + .append(new KeyedCodec<>("BlockMask", BlockMaskAsset.CODEC, false), (asset, v) -> asset.blockMaskAsset = v, asset -> asset.blockMaskAsset) + .add() + .append(new KeyedCodec<>("MoldingDirection", MoldingDirection.CODEC, false), (t, k) -> t.moldingDirectionName = k, k -> k.moldingDirectionName) + .add() + .append(new KeyedCodec<>("MoldingPattern", PatternAsset.CODEC, false), (asset, v) -> asset.moldingPatternAsset = v, asset -> asset.moldingPatternAsset) + .add() + .append(new KeyedCodec<>("MoldingScanner", ScannerAsset.CODEC, false), (asset, v) -> asset.moldingScannerAsset = v, asset -> asset.moldingScannerAsset) + .add() + .append(new KeyedCodec<>("MoldingChildren", Codec.BOOLEAN, false), (asset, v) -> asset.moldChildren = v, asset -> asset.moldChildren) + .add() + .append(new KeyedCodec<>("LoadEntities", Codec.BOOLEAN, false), (asset, v) -> asset.loadEntities = v, asset -> asset.loadEntities) + .add() + .build(); + private PrefabPropAsset.WeightedPathAsset[] weightedPrefabPathAssets = new PrefabPropAsset.WeightedPathAsset[0]; + private boolean legacyPath = false; + private boolean loadEntities = true; + private DirectionalityAsset directionalityAsset = new StaticDirectionalityAsset(); + private ScannerAsset scannerAsset = new OriginScannerAsset(); + private BlockMaskAsset blockMaskAsset = new BlockMaskAsset(); + private MoldingDirection moldingDirectionName = MoldingDirection.NONE; + private ScannerAsset moldingScannerAsset = new OriginScannerAsset(); + private PatternAsset moldingPatternAsset = new ConstantPatternAsset(); + private boolean moldChildren = false; + + public PrefabPropAsset() { + } + + @Override + public void cleanUp() { + this.directionalityAsset.cleanUp(); + this.scannerAsset.cleanUp(); + this.blockMaskAsset.cleanUp(); + this.moldingScannerAsset.cleanUp(); + this.moldingPatternAsset.cleanUp(); + } + + @Nonnull + @Override + public Prop build(@Nonnull PropAsset.Argument argument) { + if (!super.skip() && this.weightedPrefabPathAssets.length != 0) { + WeightedMap> prefabWeightedMap = new WeightedMap<>(); + + for (PrefabPropAsset.WeightedPathAsset pathAsset : this.weightedPrefabPathAssets) { + List pathPrefabs = this.loadPrefabBuffersFrom(pathAsset.path); + if (pathPrefabs != null) { + prefabWeightedMap.add(pathPrefabs, pathAsset.weight); + } + } + + if (prefabWeightedMap.size() == 0) { + return Prop.noProp(); + } else { + MaterialCache voxelCache = argument.materialCache; + BlockMask blockMask; + if (this.blockMaskAsset == null) { + blockMask = new BlockMask(); + } else { + blockMask = this.blockMaskAsset.build(voxelCache); + } + + Scanner scanner = this.scannerAsset.build(ScannerAsset.argumentFrom(argument)); + Directionality directionality = this.directionalityAsset.build(DirectionalityAsset.argumentFrom(argument)); + MoldingDirection moldingDirection = this.moldingDirectionName; + PrefabMoldingConfiguration moldingConfiguration = null; + if (moldingDirection != MoldingDirection.DOWN && moldingDirection != MoldingDirection.UP) { + moldingConfiguration = PrefabMoldingConfiguration.none(); + } else { + Scanner moldingScanner = this.moldingScannerAsset == null + ? Scanner.noScanner() + : this.moldingScannerAsset.build(ScannerAsset.argumentFrom(argument)); + Pattern moldingPattern = this.moldingPatternAsset == null + ? Pattern.noPattern() + : this.moldingPatternAsset.build(PatternAsset.argumentFrom(argument)); + moldingConfiguration = new PrefabMoldingConfiguration(moldingScanner, moldingPattern, moldingDirection, this.moldChildren); + } + + return new PrefabProp( + prefabWeightedMap, + scanner, + directionality, + voxelCache, + blockMask, + moldingConfiguration, + this::loadPrefabBuffersFrom, + argument.parentSeed, + this.loadEntities + ); + } + } else { + return Prop.noProp(); + } + } + + @Nullable + private List loadPrefabBuffersFrom(@Nonnull String path) { + List pathPrefabs = new ArrayList<>(); + + for (AssetPack pack : AssetModule.get().getAssetPacks()) { + Path fullPath = pack.getRoot().resolve("Server"); + if (this.legacyPath) { + fullPath = fullPath.resolve("World").resolve("Default").resolve("Prefabs"); + } else { + fullPath = fullPath.resolve("Prefabs"); + } + + fullPath = fullPath.resolve(path); + + try { + PrefabLoader.loadAllPrefabBuffersUnder(fullPath, pathPrefabs); + } catch (Exception var8) { + String msg = "Couldn't load prefab with path: " + path; + msg = msg + "\n"; + msg = msg + ExceptionUtil.toStringWithStack(var8); + LoggerUtil.getLogger().severe(msg); + return null; + } + } + + if (pathPrefabs.isEmpty()) { + HytaleLogger.getLogger().atWarning().log("This prefab path contains no prefabs: " + path); + return null; + } else { + return pathPrefabs; + } + } + + public static class WeightedPathAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + PrefabPropAsset.WeightedPathAsset.class, + PrefabPropAsset.WeightedPathAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Weight", Codec.DOUBLE, true), (t, y) -> t.weight = y, t -> t.weight) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append(new KeyedCodec<>("Path", Codec.STRING, true), (t, out) -> t.path = out, t -> t.path) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private double weight = 1.0; + private String path; + + public WeightedPathAsset() { + } + + public String getId() { + return this.id; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/directionality/DirectionalityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/directionality/DirectionalityAsset.java new file mode 100644 index 0000000..ede3ed9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/directionality/DirectionalityAsset.java @@ -0,0 +1,89 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop.directionality; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.PropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.Directionality; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public abstract class DirectionalityAsset implements Cleanable, JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + private static final Map exportedNodes = new HashMap<>(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(DirectionalityAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(DirectionalityAsset.class) + .append(new KeyedCodec<>("ExportAs", Codec.STRING, false), (t, k) -> t.exportName = k, t -> t.exportName) + .add() + .afterDecode(asset -> { + if (asset.exportName != null && !asset.exportName.isEmpty()) { + exportedNodes.put(asset.exportName, asset); + LoggerUtil.getLogger().fine("Registered imported position provider asset with name '" + asset.exportName + "' with asset id '" + asset.id); + } + }) + .build(); + private String id; + private AssetExtraInfo.Data data; + private String exportName = ""; + + protected DirectionalityAsset() { + } + + public abstract Directionality build(@Nonnull DirectionalityAsset.Argument var1); + + @Override + public void cleanUp() { + } + + public static DirectionalityAsset getExportedAsset(@Nonnull String name) { + return exportedNodes.get(name); + } + + public String getId() { + return this.id; + } + + @Nonnull + public static DirectionalityAsset.Argument argumentFrom(@Nonnull PropAsset.Argument argument) { + return new DirectionalityAsset.Argument(argument.parentSeed, argument.materialCache, argument.referenceBundle, argument.workerIndexer); + } + + public static class Argument { + public SeedBox parentSeed; + public MaterialCache materialCache; + public ReferenceBundle referenceBundle; + public WorkerIndexer workerIndexer; + + public Argument( + @Nonnull SeedBox parentSeed, @Nonnull MaterialCache materialCache, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer + ) { + this.parentSeed = parentSeed; + this.materialCache = materialCache; + this.referenceBundle = referenceBundle; + this.workerIndexer = workerIndexer; + } + + public Argument(@Nonnull DirectionalityAsset.Argument argument) { + this.parentSeed = argument.parentSeed; + this.materialCache = argument.materialCache; + this.referenceBundle = argument.referenceBundle; + this.workerIndexer = argument.workerIndexer; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/directionality/ImportedDirectionalityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/directionality/ImportedDirectionalityAsset.java new file mode 100644 index 0000000..54c77ac --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/directionality/ImportedDirectionalityAsset.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop.directionality; + +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.Directionality; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.logger.HytaleLogger; +import javax.annotation.Nonnull; + +public class ImportedDirectionalityAsset extends DirectionalityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ImportedDirectionalityAsset.class, ImportedDirectionalityAsset::new, DirectionalityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Name", Codec.STRING, true), (asset, v) -> asset.name = v, asset -> asset.name) + .add() + .build(); + private String name = ""; + + public ImportedDirectionalityAsset() { + } + + @Override + public Directionality build(@Nonnull DirectionalityAsset.Argument argument) { + if (this.name != null && !this.name.isEmpty()) { + DirectionalityAsset exportedAsset = DirectionalityAsset.getExportedAsset(this.name); + return exportedAsset == null ? Directionality.noDirectionality() : exportedAsset.build(argument); + } else { + HytaleLogger.getLogger().atWarning().log("An exported Pattern with the name does not exist: " + this.name); + return Directionality.noDirectionality(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/directionality/PatternDirectionalityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/directionality/PatternDirectionalityAsset.java new file mode 100644 index 0000000..6303b05 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/directionality/PatternDirectionalityAsset.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop.directionality; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.ConstantPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.PatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.Directionality; +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.OrthogonalDirection; +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.PatternDirectionality; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class PatternDirectionalityAsset extends DirectionalityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + PatternDirectionalityAsset.class, PatternDirectionalityAsset::new, DirectionalityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("InitialDirection", OrthogonalDirection.CODEC, true), (asset, v) -> asset.prefabDirection = v, asset -> asset.prefabDirection) + .add() + .append(new KeyedCodec<>("Seed", Codec.STRING, true), (asset, v) -> asset.seed = v, asset -> asset.seed) + .add() + .append(new KeyedCodec<>("NorthPattern", PatternAsset.CODEC, true), (asset, v) -> asset.northPatternAsset = v, asset -> asset.northPatternAsset) + .add() + .append(new KeyedCodec<>("SouthPattern", PatternAsset.CODEC, true), (asset, v) -> asset.southPatternAsset = v, asset -> asset.southPatternAsset) + .add() + .append(new KeyedCodec<>("EastPattern", PatternAsset.CODEC, true), (asset, v) -> asset.eastPatternAsset = v, asset -> asset.eastPatternAsset) + .add() + .append(new KeyedCodec<>("WestPattern", PatternAsset.CODEC, true), (asset, v) -> asset.westPatternAsset = v, asset -> asset.westPatternAsset) + .add() + .build(); + private String seed = "A"; + private OrthogonalDirection prefabDirection = OrthogonalDirection.N; + private PatternAsset northPatternAsset = new ConstantPatternAsset(); + private PatternAsset southPatternAsset = new ConstantPatternAsset(); + private PatternAsset eastPatternAsset = new ConstantPatternAsset(); + private PatternAsset westPatternAsset = new ConstantPatternAsset(); + + public PatternDirectionalityAsset() { + } + + @Nonnull + @Override + public Directionality build(@Nonnull DirectionalityAsset.Argument argument) { + int intSeed = argument.parentSeed.child(this.seed).createSupplier().get(); + OrthogonalDirection direction = this.prefabDirection; + Pattern northPattern = this.northPatternAsset == null ? Pattern.noPattern() : this.northPatternAsset.build(PatternAsset.argumentFrom(argument)); + Pattern southPattern = this.southPatternAsset == null ? Pattern.noPattern() : this.southPatternAsset.build(PatternAsset.argumentFrom(argument)); + Pattern eastPattern = this.eastPatternAsset == null ? Pattern.noPattern() : this.eastPatternAsset.build(PatternAsset.argumentFrom(argument)); + Pattern westPattern = this.westPatternAsset == null ? Pattern.noPattern() : this.westPatternAsset.build(PatternAsset.argumentFrom(argument)); + return new PatternDirectionality(direction, southPattern, northPattern, eastPattern, westPattern, intSeed); + } + + @Override + public void cleanUp() { + this.northPatternAsset.cleanUp(); + this.southPatternAsset.cleanUp(); + this.eastPatternAsset.cleanUp(); + this.westPatternAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/directionality/RandomDirectionalityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/directionality/RandomDirectionalityAsset.java new file mode 100644 index 0000000..1c5518a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/directionality/RandomDirectionalityAsset.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop.directionality; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.ConstantPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.PatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.Directionality; +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.RandomDirectionality; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class RandomDirectionalityAsset extends DirectionalityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + RandomDirectionalityAsset.class, RandomDirectionalityAsset::new, DirectionalityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Seed", Codec.STRING, true), (asset, v) -> asset.seed = v, asset -> asset.seed) + .add() + .append(new KeyedCodec<>("Pattern", PatternAsset.CODEC, true), (asset, v) -> asset.patternAsset = v, asset -> asset.patternAsset) + .add() + .build(); + private String seed = "A"; + private PatternAsset patternAsset = new ConstantPatternAsset(); + + public RandomDirectionalityAsset() { + } + + @Nonnull + @Override + public Directionality build(@Nonnull DirectionalityAsset.Argument argument) { + return new RandomDirectionality(this.patternAsset.build(PatternAsset.argumentFrom(argument)), argument.parentSeed.child(this.seed).createSupplier().get()); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/directionality/StaticDirectionalityAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/directionality/StaticDirectionalityAsset.java new file mode 100644 index 0000000..09dc183 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/props/prefabprop/directionality/StaticDirectionalityAsset.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.props.prefabprop.directionality; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.ConstantPatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.patterns.PatternAsset; +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.Directionality; +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.StaticDirectionality; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import javax.annotation.Nonnull; + +public class StaticDirectionalityAsset extends DirectionalityAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + StaticDirectionalityAsset.class, StaticDirectionalityAsset::new, DirectionalityAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Rotation", Codec.INTEGER, false), (asset, v) -> asset.rotation = v, asset -> asset.rotation) + .addValidator((LegacyValidator)((v, r) -> { + if (v != 0 && v != 90 && v != 180 && v != 270) { + r.fail("Rotation can only have the values: 0, 90, 180, 270"); + } + })) + .add() + .append(new KeyedCodec<>("Pattern", PatternAsset.CODEC, true), (asset, v) -> asset.patternAsset = v, asset -> asset.patternAsset) + .add() + .build(); + private int rotation = 0; + private PatternAsset patternAsset = new ConstantPatternAsset(); + + public StaticDirectionalityAsset() { + } + + @Nonnull + @Override + public Directionality build(@Nonnull DirectionalityAsset.Argument argument) { + PrefabRotation prefabRotation = switch (this.rotation) { + case 90 -> PrefabRotation.ROTATION_90; + case 180 -> PrefabRotation.ROTATION_180; + case 270 -> PrefabRotation.ROTATION_270; + default -> PrefabRotation.ROTATION_0; + }; + return new StaticDirectionality(prefabRotation, this.patternAsset.build(PatternAsset.argumentFrom(argument))); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propstageiterations/PropRuntimeAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propstageiterations/PropRuntimeAsset.java new file mode 100644 index 0000000..95ec5a6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/propstageiterations/PropRuntimeAsset.java @@ -0,0 +1,82 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.propstageiterations; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.ListPositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.positionproviders.PositionProviderAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.propassignments.AssignmentsAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.propassignments.ConstantAssignmentsAsset; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.propdistributions.Assignments; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import javax.annotation.Nonnull; + +public class PropRuntimeAsset implements Cleanable, JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + PropRuntimeAsset.class, + PropRuntimeAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Runtime", Codec.INTEGER, true), (t, k) -> t.runtime = k, t -> t.runtime) + .add() + .append(new KeyedCodec<>("Positions", PositionProviderAsset.CODEC, true), (t, k) -> t.positionProviderAsset = k, t -> t.positionProviderAsset) + .add() + .append(new KeyedCodec<>("Assignments", AssignmentsAsset.CODEC, true), (t, k) -> t.assignmentsAsset = k, t -> t.assignmentsAsset) + .add() + .append(new KeyedCodec<>("Skip", Codec.BOOLEAN, false), (t, k) -> t.skip = k, t -> t.skip) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private boolean skip = false; + private int runtime = 0; + private PositionProviderAsset positionProviderAsset = new ListPositionProviderAsset(); + private AssignmentsAsset assignmentsAsset = new ConstantAssignmentsAsset(); + + protected PropRuntimeAsset() { + } + + public boolean isSkip() { + return this.skip; + } + + @Override + public void cleanUp() { + this.positionProviderAsset.cleanUp(); + this.assignmentsAsset.cleanUp(); + } + + public PositionProvider buildPositionProvider(@Nonnull SeedBox parentSeed, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer) { + return this.positionProviderAsset.build(new PositionProviderAsset.Argument(parentSeed, referenceBundle, workerIndexer)); + } + + public Assignments buildPropDistribution( + @Nonnull SeedBox parentSeed, + @Nonnull MaterialCache materialCache, + int runtime, + @Nonnull ReferenceBundle referenceBundle, + @Nonnull WorkerIndexer workerIndexer + ) { + return this.assignmentsAsset.build(new AssignmentsAsset.Argument(parentSeed, materialCache, referenceBundle, runtime, workerIndexer)); + } + + public int getRuntime() { + return this.runtime; + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/AreaScannerAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/AreaScannerAsset.java new file mode 100644 index 0000000..07efe80 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/AreaScannerAsset.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.scanners; + +import com.hypixel.hytale.builtin.hytalegenerator.scanners.AreaScanner; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class AreaScannerAsset extends ScannerAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(AreaScannerAsset.class, AreaScannerAsset::new, ScannerAsset.ABSTRACT_CODEC) + .append(new KeyedCodec<>("ResultCap", Codec.INTEGER, true), (t, k) -> t.resultCap = k, k -> k.resultCap) + .addValidator(Validators.greaterThanOrEqual(0)) + .add() + .append(new KeyedCodec<>("ScanShape", AreaScanner.ScanShape.CODEC, false), (t, k) -> t.scanShape = k, t -> t.scanShape) + .add() + .append(new KeyedCodec<>("ScanRange", Codec.INTEGER, false), (t, k) -> t.scanRange = k, t -> t.scanRange) + .addValidator(Validators.greaterThan(-1)) + .add() + .append(new KeyedCodec<>("ChildScanner", ScannerAsset.CODEC, false), (t, k) -> t.childScannerAsset = k, t -> t.childScannerAsset) + .add() + .build(); + private int resultCap = 1; + private AreaScanner.ScanShape scanShape = AreaScanner.ScanShape.CIRCLE; + private int scanRange = 0; + private ScannerAsset childScannerAsset = new OriginScannerAsset(); + + public AreaScannerAsset() { + } + + @Nonnull + @Override + public Scanner build(@Nonnull ScannerAsset.Argument argument) { + return (Scanner)(!super.skip() && this.childScannerAsset != null + ? new AreaScanner(this.resultCap, this.scanShape, this.scanRange, this.childScannerAsset.build(argument)) + : Scanner.noScanner()); + } + + @Override + public void cleanUp() { + this.childScannerAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/ColumnLinearScannerAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/ColumnLinearScannerAsset.java new file mode 100644 index 0000000..8da2497 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/ColumnLinearScannerAsset.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.scanners; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.BiDouble2DoubleFunction; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.BaseHeightReference; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.ColumnLinearScanner; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.logger.HytaleLogger; +import javax.annotation.Nonnull; + +public class ColumnLinearScannerAsset extends ScannerAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ColumnLinearScannerAsset.class, ColumnLinearScannerAsset::new, ScannerAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("MinY", Codec.INTEGER, true), (t, k) -> t.minY = k, k -> k.minY) + .add() + .append(new KeyedCodec<>("MaxY", Codec.INTEGER, true), (t, k) -> t.maxY = k, k -> k.maxY) + .add() + .append(new KeyedCodec<>("ResultCap", Codec.INTEGER, true), (t, k) -> t.resultCap = k, k -> k.resultCap) + .addValidator(Validators.greaterThanOrEqual(0)) + .add() + .append(new KeyedCodec<>("TopDownOrder", Codec.BOOLEAN, false), (t, k) -> t.topDownOrder = k, k -> k.topDownOrder) + .add() + .append(new KeyedCodec<>("RelativeToPosition", Codec.BOOLEAN, false), (t, k) -> t.isRelativeToPosition = k, k -> k.isRelativeToPosition) + .add() + .append(new KeyedCodec<>("BaseHeightName", Codec.STRING, false), (t, k) -> t.baseHeight = k, k -> k.baseHeight) + .add() + .build(); + private int minY = 0; + private int maxY = 1; + private int resultCap = 1; + private boolean topDownOrder = true; + private boolean isRelativeToPosition = false; + private String baseHeight = ""; + + public ColumnLinearScannerAsset() { + } + + @Nonnull + @Override + public Scanner build(@Nonnull ScannerAsset.Argument argument) { + if (super.skip()) { + return Scanner.noScanner(); + } else if (this.isRelativeToPosition) { + return new ColumnLinearScanner(this.minY, this.maxY, this.resultCap, this.topDownOrder, true, null); + } else { + BaseHeightReference heightDataLayer = argument.referenceBundle.getLayerWithName(this.baseHeight, BaseHeightReference.class); + if (heightDataLayer == null) { + HytaleLogger.getLogger().atConfig().log("Couldn't find height data layer with name \"" + this.baseHeight + "\", defaulting to not using a bed."); + return new ColumnLinearScanner(this.minY, this.maxY, this.resultCap, this.topDownOrder, false, null); + } else { + BiDouble2DoubleFunction baseHeightFunction = heightDataLayer.getHeightFunction(); + return new ColumnLinearScanner(this.minY, this.maxY, this.resultCap, this.topDownOrder, false, baseHeightFunction); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/ColumnRandomScannerAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/ColumnRandomScannerAsset.java new file mode 100644 index 0000000..6fea444 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/ColumnRandomScannerAsset.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.scanners; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.ValidatorUtil; +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.BiDouble2DoubleFunction; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.BaseHeightReference; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.ColumnRandomScanner; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.logger.HytaleLogger; +import javax.annotation.Nonnull; + +public class ColumnRandomScannerAsset extends ScannerAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ColumnRandomScannerAsset.class, ColumnRandomScannerAsset::new, ScannerAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("MinY", Codec.INTEGER, true), (t, k) -> t.minY = k, k -> k.minY) + .add() + .append(new KeyedCodec<>("MaxY", Codec.INTEGER, true), (t, k) -> t.maxY = k, k -> k.maxY) + .add() + .append(new KeyedCodec<>("ResultCap", Codec.INTEGER, true), (t, k) -> t.resultCap = k, k -> k.resultCap) + .addValidator(Validators.greaterThanOrEqual(0)) + .add() + .append(new KeyedCodec<>("Seed", Codec.STRING, false), (t, k) -> t.seed = k, k -> k.seed) + .add() + .append(new KeyedCodec<>("Strategy", Codec.STRING, false), (t, k) -> t.strategyName = k, k -> k.strategyName) + .addValidator(ValidatorUtil.validEnumValue(ColumnRandomScanner.Strategy.values())) + .add() + .append(new KeyedCodec<>("RelativeToPosition", Codec.BOOLEAN, false), (t, k) -> t.isRelativeToPosition = k, k -> k.isRelativeToPosition) + .add() + .append(new KeyedCodec<>("BaseHeightName", Codec.STRING, false), (t, k) -> t.baseHeight = k, k -> k.baseHeight) + .add() + .build(); + private int minY = 0; + private int maxY = 1; + private int resultCap = 1; + private String seed = "A"; + private String strategyName = "DART_THROW"; + private boolean isRelativeToPosition = false; + private String baseHeight = ""; + + public ColumnRandomScannerAsset() { + } + + @Nonnull + @Override + public Scanner build(@Nonnull ScannerAsset.Argument argument) { + if (super.skip()) { + return Scanner.noScanner(); + } else { + SeedBox childSeed = argument.parentSeed.child(this.seed); + ColumnRandomScanner.Strategy strategy = ColumnRandomScanner.Strategy.valueOf(this.strategyName); + if (this.isRelativeToPosition) { + return new ColumnRandomScanner(this.minY, this.maxY, this.resultCap, childSeed.createSupplier().get(), strategy, true, null); + } else { + BaseHeightReference heightDataLayer = argument.referenceBundle.getLayerWithName(this.baseHeight, BaseHeightReference.class); + if (heightDataLayer == null) { + HytaleLogger.getLogger().atConfig().log("Couldn't find height data layer with name \"" + this.baseHeight + "\", defaulting to not using a bed."); + return new ColumnRandomScanner(this.minY, this.maxY, this.resultCap, childSeed.createSupplier().get(), strategy, false, null); + } else { + BiDouble2DoubleFunction baseHeightFunction = heightDataLayer.getHeightFunction(); + return new ColumnRandomScanner(this.minY, this.maxY, this.resultCap, childSeed.createSupplier().get(), strategy, false, baseHeightFunction); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/ImportedScannerAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/ImportedScannerAsset.java new file mode 100644 index 0000000..09ef135 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/ImportedScannerAsset.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.scanners; + +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.logger.HytaleLogger; +import javax.annotation.Nonnull; + +public class ImportedScannerAsset extends ScannerAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ImportedScannerAsset.class, ImportedScannerAsset::new, ScannerAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Name", Codec.STRING, false), (t, k) -> t.name = k, k -> k.name) + .add() + .build(); + private String name = ""; + + public ImportedScannerAsset() { + } + + @Override + public Scanner build(@Nonnull ScannerAsset.Argument argument) { + if (super.skip()) { + return Scanner.noScanner(); + } else if (this.name != null && !this.name.isEmpty()) { + ScannerAsset exportedAsset = ScannerAsset.getExportedAsset(this.name); + return exportedAsset == null ? Scanner.noScanner() : exportedAsset.build(argument); + } else { + HytaleLogger.getLogger().atWarning().log("An exported Pattern with the name does not exist: " + this.name); + return Scanner.noScanner(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/OriginScannerAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/OriginScannerAsset.java new file mode 100644 index 0000000..3acad31 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/OriginScannerAsset.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.scanners; + +import com.hypixel.hytale.builtin.hytalegenerator.scanners.OriginScanner; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class OriginScannerAsset extends ScannerAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + OriginScannerAsset.class, OriginScannerAsset::new, ScannerAsset.ABSTRACT_CODEC + ) + .build(); + + public OriginScannerAsset() { + } + + @Nonnull + @Override + public Scanner build(@Nonnull ScannerAsset.Argument argument) { + return (Scanner)(super.skip() ? Scanner.noScanner() : OriginScanner.getInstance()); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/ScannerAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/ScannerAsset.java new file mode 100644 index 0000000..2c83934 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/scanners/ScannerAsset.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.scanners; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.assets.props.PropAsset; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; + +public abstract class ScannerAsset implements Cleanable, JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + private static final Map exportedNodes = new ConcurrentHashMap<>(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(ScannerAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(ScannerAsset.class) + .append(new KeyedCodec<>("Skip", Codec.BOOLEAN, false), (t, k) -> t.skip = k, t -> t.skip) + .add() + .append(new KeyedCodec<>("ExportAs", Codec.STRING, false), (t, k) -> t.exportName = k, t -> t.exportName) + .add() + .afterDecode(asset -> { + if (asset.exportName != null && !asset.exportName.isEmpty()) { + if (exportedNodes.containsKey(asset.exportName)) { + LoggerUtil.getLogger().warning("Duplicate export name for asset: " + asset.exportName); + } + + exportedNodes.put(asset.exportName, asset); + LoggerUtil.getLogger().info("Exported Scanner asset with name '" + asset.exportName + "' with asset id '" + asset.id); + } + }) + .build(); + private String id; + private AssetExtraInfo.Data data; + private boolean skip = false; + private String exportName = ""; + + protected ScannerAsset() { + } + + public abstract Scanner build(@Nonnull ScannerAsset.Argument var1); + + public boolean skip() { + return this.skip; + } + + public static ScannerAsset getExportedAsset(@Nonnull String name) { + return exportedNodes.get(name); + } + + public String getId() { + return this.id; + } + + @Nonnull + public static ScannerAsset.Argument argumentFrom(@Nonnull PropAsset.Argument argument) { + return new ScannerAsset.Argument(argument.parentSeed, argument.referenceBundle); + } + + @Override + public void cleanUp() { + } + + public static class Argument { + public SeedBox parentSeed; + public ReferenceBundle referenceBundle; + + public Argument(@Nonnull SeedBox parentSeed, @Nonnull ReferenceBundle referenceBundle) { + this.parentSeed = parentSeed; + this.referenceBundle = referenceBundle; + } + + public Argument(@Nonnull ScannerAsset.Argument argument) { + this.parentSeed = argument.parentSeed; + this.referenceBundle = argument.referenceBundle; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/terrains/DensityTerrainAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/terrains/DensityTerrainAsset.java new file mode 100644 index 0000000..dbcfdcf --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/terrains/DensityTerrainAsset.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.terrains; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ConstantDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class DensityTerrainAsset extends TerrainAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DensityTerrainAsset.class, DensityTerrainAsset::new, TerrainAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Density", DensityAsset.CODEC, true), (t, k) -> t.densityAsset = k, t -> t.densityAsset) + .add() + .build(); + @Nonnull + private DensityAsset densityAsset = new ConstantDensityAsset(); + + public DensityTerrainAsset() { + } + + @Nonnull + @Override + public Density buildDensity(@Nonnull SeedBox parentSeed, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer) { + return this.densityAsset.build(new DensityAsset.Argument(parentSeed, referenceBundle, workerIndexer)); + } + + @Override + public void cleanUp() { + this.densityAsset.cleanUp(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/terrains/TerrainAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/terrains/TerrainAsset.java new file mode 100644 index 0000000..45320dd --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/terrains/TerrainAsset.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.terrains; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import javax.annotation.Nonnull; + +public abstract class TerrainAsset implements Cleanable, JsonAssetWithMap> { + private static final TerrainAsset[] EMPTY_INPUTS = new TerrainAsset[0]; + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(TerrainAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(TerrainAsset.class).build(); + private String id; + private AssetExtraInfo.Data data; + private TerrainAsset[] inputs = EMPTY_INPUTS; + private boolean skip = false; + + protected TerrainAsset() { + } + + public abstract Density buildDensity(@Nonnull SeedBox var1, @Nonnull ReferenceBundle var2, @Nonnull WorkerIndexer var3); + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + for (TerrainAsset asset : this.inputs) { + asset.cleanUp(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/tintproviders/ConstantTintProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/tintproviders/ConstantTintProviderAsset.java new file mode 100644 index 0000000..210c314 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/tintproviders/ConstantTintProviderAsset.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.tintproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.tintproviders.ConstantTintProvider; +import com.hypixel.hytale.builtin.hytalegenerator.tintproviders.TintProvider; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.server.core.asset.util.ColorParseUtil; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import javax.annotation.Nonnull; + +public class ConstantTintProviderAsset extends TintProviderAsset { + public static final Color DEFAULT_COLOR = ColorParseUtil.hexStringToColor("#FF0000"); + public static final BuilderCodec CODEC = BuilderCodec.builder( + ConstantTintProviderAsset.class, ConstantTintProviderAsset::new, TintProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Color", ProtocolCodecs.COLOR, true), (t, k) -> t.color = k, k -> k.color) + .add() + .build(); + private Color color = DEFAULT_COLOR; + + public ConstantTintProviderAsset() { + } + + @Nonnull + @Override + public TintProvider build(@Nonnull TintProviderAsset.Argument argument) { + if (super.isSkipped()) { + return TintProvider.noTintProvider(); + } else { + int colorInt = ColorParseUtil.colorToARGBInt(this.color); + return new ConstantTintProvider(colorInt); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/tintproviders/DensityDelimitedTintProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/tintproviders/DensityDelimitedTintProviderAsset.java new file mode 100644 index 0000000..5a45ef2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/tintproviders/DensityDelimitedTintProviderAsset.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.tintproviders; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.delimiters.RangeDoubleAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.delimiters.DelimiterDouble; +import com.hypixel.hytale.builtin.hytalegenerator.delimiters.RangeDouble; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.tintproviders.DensityDelimitedTintProvider; +import com.hypixel.hytale.builtin.hytalegenerator.tintproviders.TintProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class DensityDelimitedTintProviderAsset extends TintProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DensityDelimitedTintProviderAsset.class, DensityDelimitedTintProviderAsset::new, TintProviderAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>( + "Delimiters", + new ArrayCodec<>(DensityDelimitedTintProviderAsset.DelimiterAsset.CODEC, DensityDelimitedTintProviderAsset.DelimiterAsset[]::new), + true + ), + (t, k) -> t.delimiterAssets = k, + k -> k.delimiterAssets + ) + .add() + .append(new KeyedCodec<>("Density", DensityAsset.CODEC, true), (t, value) -> t.densityAsset = value, t -> t.densityAsset) + .add() + .build(); + private DensityDelimitedTintProviderAsset.DelimiterAsset[] delimiterAssets = new DensityDelimitedTintProviderAsset.DelimiterAsset[0]; + private DensityAsset densityAsset = DensityAsset.getFallbackAsset(); + + public DensityDelimitedTintProviderAsset() { + } + + @Nonnull + @Override + public TintProvider build(@Nonnull TintProviderAsset.Argument argument) { + if (super.isSkipped()) { + return TintProvider.noTintProvider(); + } else { + List> delimiters = new ArrayList<>(this.delimiterAssets.length); + + for (DensityDelimitedTintProviderAsset.DelimiterAsset delimiterAsset : this.delimiterAssets) { + delimiters.add(delimiterAsset.build(argument)); + } + + Density density = this.densityAsset.build(DensityAsset.from(argument)); + return new DensityDelimitedTintProvider(delimiters, density); + } + } + + public static class DelimiterAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + DensityDelimitedTintProviderAsset.DelimiterAsset.class, + DensityDelimitedTintProviderAsset.DelimiterAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Range", RangeDoubleAsset.CODEC, true), (t, value) -> t.rangeAsset = value, t -> t.rangeAsset) + .add() + .append(new KeyedCodec<>("Tint", TintProviderAsset.CODEC, true), (t, value) -> t.tintProviderAsset = value, t -> t.tintProviderAsset) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private RangeDoubleAsset rangeAsset = new RangeDoubleAsset(); + private TintProviderAsset tintProviderAsset = TintProviderAsset.getFallbackAsset(); + + public DelimiterAsset() { + } + + @Nonnull + public DelimiterDouble build(@Nonnull TintProviderAsset.Argument argument) { + RangeDouble range = this.rangeAsset.build(); + TintProvider environmentProvider = this.tintProviderAsset.build(argument); + return new DelimiterDouble<>(range, environmentProvider); + } + + public String getId() { + return this.id; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/tintproviders/TintProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/tintproviders/TintProviderAsset.java new file mode 100644 index 0000000..c9f540d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/tintproviders/TintProviderAsset.java @@ -0,0 +1,99 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.tintproviders; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.builtin.hytalegenerator.tintproviders.TintProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; + +public abstract class TintProviderAsset implements Cleanable, JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + private static final Map exportedNodes = new ConcurrentHashMap<>(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(TintProviderAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(TintProviderAsset.class) + .append(new KeyedCodec<>("Skip", Codec.BOOLEAN, false), (t, k) -> t.skip = k, t -> t.skip) + .add() + .append(new KeyedCodec<>("ExportAs", Codec.STRING, false), (t, k) -> t.exportName = k, t -> t.exportName) + .add() + .afterDecode(asset -> { + if (asset.exportName != null && !asset.exportName.isEmpty()) { + if (exportedNodes.containsKey(asset.exportName)) { + LoggerUtil.getLogger().warning("Duplicate export name for asset: " + asset.exportName); + } + + exportedNodes.put(asset.exportName, asset); + LoggerUtil.getLogger().fine("Registered imported node asset with name '" + asset.exportName + "' with asset id '" + asset.id); + } + }) + .build(); + private String id; + private AssetExtraInfo.Data data; + private boolean skip = false; + private String exportName = ""; + + protected TintProviderAsset() { + } + + public abstract TintProvider build(@Nonnull TintProviderAsset.Argument var1); + + @Nonnull + public static TintProviderAsset getFallbackAsset() { + return new ConstantTintProviderAsset(); + } + + public boolean isSkipped() { + return this.skip; + } + + public static TintProviderAsset getExportedAsset(@Nonnull String name) { + return exportedNodes.get(name); + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + } + + public static class Argument { + public SeedBox parentSeed; + public MaterialCache materialCache; + public ReferenceBundle referenceBundle; + public WorkerIndexer workerIndexer; + + public Argument( + @Nonnull SeedBox parentSeed, @Nonnull MaterialCache materialCache, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer + ) { + this.parentSeed = parentSeed; + this.materialCache = materialCache; + this.referenceBundle = referenceBundle; + this.workerIndexer = workerIndexer; + } + + public Argument(@Nonnull TintProviderAsset.Argument argument) { + this.parentSeed = argument.parentSeed; + this.materialCache = argument.materialCache; + this.referenceBundle = argument.referenceBundle; + this.workerIndexer = argument.workerIndexer; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/CacheVectorProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/CacheVectorProviderAsset.java new file mode 100644 index 0000000..c57c448 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/CacheVectorProviderAsset.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.vectorproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.vectorproviders.CacheVectorProvider; +import com.hypixel.hytale.builtin.hytalegenerator.vectorproviders.ConstantVectorProvider; +import com.hypixel.hytale.builtin.hytalegenerator.vectorproviders.VectorProvider; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class CacheVectorProviderAsset extends VectorProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CacheVectorProviderAsset.class, CacheVectorProviderAsset::new, ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>("VectorProvider", VectorProviderAsset.CODEC, true), + (asset, value) -> asset.vectorProviderAsset = value, + value -> value.vectorProviderAsset + ) + .add() + .build(); + private VectorProviderAsset vectorProviderAsset = new ConstantVectorProviderAsset(); + + private CacheVectorProviderAsset() { + } + + public CacheVectorProviderAsset(@Nonnull VectorProviderAsset vectorProviderAsset) { + this.vectorProviderAsset = vectorProviderAsset; + } + + @Override + public VectorProvider build(@Nonnull VectorProviderAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantVectorProvider(new Vector3d()); + } else { + VectorProvider vectorProvider = this.vectorProviderAsset.build(argument); + return new CacheVectorProvider(vectorProvider, argument.workerIndexer.getWorkerCount()); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/ConstantVectorProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/ConstantVectorProviderAsset.java new file mode 100644 index 0000000..ac8b2f4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/ConstantVectorProviderAsset.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.vectorproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.vectorproviders.ConstantVectorProvider; +import com.hypixel.hytale.builtin.hytalegenerator.vectorproviders.VectorProvider; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class ConstantVectorProviderAsset extends VectorProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ConstantVectorProviderAsset.class, ConstantVectorProviderAsset::new, ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Value", Vector3d.CODEC, true), (asset, value) -> asset.value = value, asset -> asset.value) + .add() + .build(); + private Vector3d value = new Vector3d(); + + public ConstantVectorProviderAsset() { + } + + public ConstantVectorProviderAsset(@Nonnull Vector3d vector) { + this.value.assign(vector); + } + + @Override + public VectorProvider build(@Nonnull VectorProviderAsset.Argument argument) { + return this.isSkipped() ? new ConstantVectorProvider(new Vector3d()) : new ConstantVectorProvider(this.value); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/DensityGradientVectorProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/DensityGradientVectorProviderAsset.java new file mode 100644 index 0000000..eb3ef77 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/DensityGradientVectorProviderAsset.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.vectorproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ConstantDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.vectorproviders.ConstantVectorProvider; +import com.hypixel.hytale.builtin.hytalegenerator.vectorproviders.DensityGradientVectorProvider; +import com.hypixel.hytale.builtin.hytalegenerator.vectorproviders.VectorProvider; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class DensityGradientVectorProviderAsset extends VectorProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DensityGradientVectorProviderAsset.class, DensityGradientVectorProviderAsset::new, VectorProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Density", DensityAsset.CODEC, true), (asset, value) -> asset.densityAsset = value, asset -> asset.densityAsset) + .add() + .append( + new KeyedCodec<>("SampleDistance", BuilderCodec.DOUBLE, true), (asset, value) -> asset.sampleDistance = value, asset -> asset.sampleDistance + ) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .build(); + private DensityAsset densityAsset = new ConstantDensityAsset(); + private double sampleDistance = 1.0; + + public DensityGradientVectorProviderAsset() { + } + + @Override + public VectorProvider build(@Nonnull VectorProviderAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantVectorProvider(new Vector3d()); + } else { + Density density = this.densityAsset.build(DensityAsset.from(argument)); + return new DensityGradientVectorProvider(density, this.sampleDistance); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/ExportedVectorProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/ExportedVectorProviderAsset.java new file mode 100644 index 0000000..cb8ce69 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/ExportedVectorProviderAsset.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.vectorproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.vectorproviders.ConstantVectorProvider; +import com.hypixel.hytale.builtin.hytalegenerator.vectorproviders.VectorProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class ExportedVectorProviderAsset extends VectorProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ExportedVectorProviderAsset.class, ExportedVectorProviderAsset::new, VectorProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("SingleInstance", Codec.BOOLEAN, true), (asset, value) -> asset.singleInstance = value, asset -> asset.singleInstance) + .add() + .append( + new KeyedCodec<>("VectorProvider", VectorProviderAsset.CODEC, true), + (asset, value) -> asset.vectorProviderAsset = value, + value -> value.vectorProviderAsset + ) + .add() + .build(); + private boolean singleInstance = false; + private VectorProviderAsset vectorProviderAsset = new ConstantVectorProviderAsset(); + + public ExportedVectorProviderAsset() { + } + + @Override + public VectorProvider build(@Nonnull VectorProviderAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantVectorProvider(new Vector3d()); + } else { + VectorProviderAsset.Exported exported = getExportedAsset(this.exportName); + if (exported == null) { + LoggerUtil.getLogger() + .severe( + "Couldn't find VectorProvider asset exported with name: '" + + this.exportName + + "'. This could indicate a defect in the HytaleGenerator assets." + ); + return this.vectorProviderAsset.build(argument); + } else if (exported.singleInstance) { + if (exported.builtInstance == null) { + exported.builtInstance = this.vectorProviderAsset.build(argument); + } + + return exported.builtInstance; + } else { + return this.vectorProviderAsset.build(argument); + } + } + } + + @Override + public void cleanUp() { + VectorProviderAsset.Exported exported = getExportedAsset(this.exportName); + if (exported != null) { + exported.builtInstance = null; + this.vectorProviderAsset.cleanUp(); + } + } + + public boolean isSingleInstance() { + return this.singleInstance; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/ImportedVectorProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/ImportedVectorProviderAsset.java new file mode 100644 index 0000000..6e8caf1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/ImportedVectorProviderAsset.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.vectorproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.vectorproviders.ConstantVectorProvider; +import com.hypixel.hytale.builtin.hytalegenerator.vectorproviders.VectorProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class ImportedVectorProviderAsset extends VectorProviderAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ImportedVectorProviderAsset.class, ImportedVectorProviderAsset::new, VectorProviderAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Name", Codec.STRING, true), (t, k) -> t.importedNodeName = k, k -> k.importedNodeName) + .add() + .build(); + private String importedNodeName = ""; + + public ImportedVectorProviderAsset() { + } + + @Override + public VectorProvider build(@Nonnull VectorProviderAsset.Argument argument) { + if (this.isSkipped()) { + return new ConstantVectorProvider(new Vector3d()); + } else { + VectorProviderAsset.Exported exported = getExportedAsset(this.importedNodeName); + if (exported == null) { + LoggerUtil.getLogger().warning("Couldn't find VectorProvider asset exported with name: '" + this.importedNodeName + "'. Using empty Node instead."); + return new ConstantVectorProvider(new Vector3d()); + } else if (exported.singleInstance) { + if (exported.builtInstance == null) { + exported.builtInstance = exported.asset.build(argument); + } + + return exported.builtInstance; + } else { + return exported.asset.build(argument); + } + } + } + + @Override + public void cleanUp() { + VectorProviderAsset.Exported exported = getExportedAsset(this.importedNodeName); + if (exported != null) { + exported.builtInstance = null; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/VectorProviderAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/VectorProviderAsset.java new file mode 100644 index 0000000..c2b9704 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/vectorproviders/VectorProviderAsset.java @@ -0,0 +1,112 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.vectorproviders; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.builtin.hytalegenerator.vectorproviders.VectorProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; + +public abstract class VectorProviderAsset implements Cleanable, JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + private static final Map exportedNodes = new ConcurrentHashMap<>(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(VectorProviderAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(VectorProviderAsset.class) + .append(new KeyedCodec<>("Skip", Codec.BOOLEAN, false), (t, k) -> t.skip = k, t -> t.skip) + .add() + .append(new KeyedCodec<>("ExportAs", Codec.STRING, false), (t, k) -> t.exportName = k, t -> t.exportName) + .add() + .afterDecode(asset -> { + if (asset.exportName != null && !asset.exportName.isEmpty()) { + if (exportedNodes.containsKey(asset.exportName)) { + LoggerUtil.getLogger().warning("Duplicate export name for asset: " + asset.exportName); + } + + VectorProviderAsset.Exported exported = new VectorProviderAsset.Exported(); + exported.asset = asset; + if (asset instanceof ExportedVectorProviderAsset exportedAsset) { + exported.singleInstance = exportedAsset.isSingleInstance(); + } else { + exported.singleInstance = false; + } + + exportedNodes.put(asset.exportName, exported); + LoggerUtil.getLogger().fine("Registered imported node asset with name '" + asset.exportName + "' with asset id '" + asset.id); + } + }) + .build(); + private String id; + private AssetExtraInfo.Data data; + protected boolean skip = false; + protected String exportName = ""; + + protected VectorProviderAsset() { + } + + public abstract VectorProvider build(@Nonnull VectorProviderAsset.Argument var1); + + public boolean isSkipped() { + return this.skip; + } + + public static VectorProviderAsset.Exported getExportedAsset(@Nonnull String name) { + return exportedNodes.get(name); + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + } + + public static class Argument { + public SeedBox parentSeed; + public ReferenceBundle referenceBundle; + public WorkerIndexer workerIndexer; + + public Argument(@Nonnull SeedBox parentSeed, @Nonnull ReferenceBundle referenceBundle, @Nonnull WorkerIndexer workerIndexer) { + this.parentSeed = parentSeed; + this.referenceBundle = referenceBundle; + this.workerIndexer = workerIndexer; + } + + public Argument(@Nonnull VectorProviderAsset.Argument argument) { + this.parentSeed = argument.parentSeed; + this.referenceBundle = argument.referenceBundle; + this.workerIndexer = argument.workerIndexer; + } + + public Argument(@Nonnull DensityAsset.Argument argument) { + this.parentSeed = argument.parentSeed; + this.referenceBundle = argument.referenceBundle; + this.workerIndexer = argument.workerIndexer; + } + } + + public static class Exported { + public boolean singleInstance; + public VectorProviderAsset asset; + public VectorProvider builtInstance; + + public Exported() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/worldstructures/WorldStructureAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/worldstructures/WorldStructureAsset.java new file mode 100644 index 0000000..6119b92 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/worldstructures/WorldStructureAsset.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.worldstructures; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.builtin.hytalegenerator.biomemap.BiomeMap; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.material.SolidMaterial; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import javax.annotation.Nonnull; + +public abstract class WorldStructureAsset implements Cleanable, JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(WorldStructureAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(WorldStructureAsset.class).build(); + private String id; + private AssetExtraInfo.Data data; + + protected WorldStructureAsset() { + } + + public abstract BiomeMap buildBiomeMap(@Nonnull WorldStructureAsset.Argument var1); + + public abstract int getBiomeTransitionDistance(); + + public abstract int getMaxBiomeEdgeDistance(); + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + } + + public static class Argument { + public MaterialCache materialCache; + public SeedBox parentSeed; + public WorkerIndexer workerIndexer; + + public Argument(@Nonnull MaterialCache materialCache, @Nonnull SeedBox parentSeed, @Nonnull WorkerIndexer workerIndexer) { + this.materialCache = materialCache; + this.parentSeed = parentSeed; + this.workerIndexer = workerIndexer; + } + + public Argument(@Nonnull WorldStructureAsset.Argument argument) { + this.materialCache = argument.materialCache; + this.parentSeed = argument.parentSeed; + this.workerIndexer = argument.workerIndexer; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/worldstructures/basic/BasicWorldStructureAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/worldstructures/basic/BasicWorldStructureAsset.java new file mode 100644 index 0000000..fd11c15 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/worldstructures/basic/BasicWorldStructureAsset.java @@ -0,0 +1,153 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.worldstructures.basic; + +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.assets.biomes.BiomeAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.ConstantDensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.density.DensityAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.worldstructures.WorldStructureAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.worldstructures.mapcontentfield.BaseHeightContentFieldAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.worldstructures.mapcontentfield.ContentFieldAsset; +import com.hypixel.hytale.builtin.hytalegenerator.biome.BiomeType; +import com.hypixel.hytale.builtin.hytalegenerator.biomemap.BiomeMap; +import com.hypixel.hytale.builtin.hytalegenerator.biomemap.SimpleBiomeMap; +import com.hypixel.hytale.builtin.hytalegenerator.cartas.SimpleNoiseCarta; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.rangemaps.DoubleRange; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.BaseHeightReference; +import com.hypixel.hytale.builtin.hytalegenerator.referencebundle.ReferenceBundle; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import java.util.HashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BasicWorldStructureAsset extends WorldStructureAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + BasicWorldStructureAsset.class, BasicWorldStructureAsset::new, WorldStructureAsset.ABSTRACT_CODEC + ) + .append( + new KeyedCodec<>("Biomes", new ArrayCodec<>(BiomeRangeAsset.CODEC, BiomeRangeAsset[]::new), true), + (t, k) -> t.biomeRangeAssets = k, + t -> t.biomeRangeAssets + ) + .add() + .append(new KeyedCodec<>("Density", DensityAsset.CODEC, true), (t, k) -> t.densityAsset = k, t -> t.densityAsset) + .add() + .append( + new KeyedCodec<>("DefaultBiome", new ContainedAssetCodec<>(BiomeAsset.class, BiomeAsset.CODEC), true), + (t, k) -> t.defaultBiomeId = k, + t -> t.defaultBiomeId + ) + .addValidatorLate(() -> BiomeAsset.VALIDATOR_CACHE.getValidator().late()) + .add() + .append( + new KeyedCodec<>("DefaultTransitionDistance", Codec.INTEGER, true), (t, k) -> t.biomeTransitionDistance = k, t -> t.biomeTransitionDistance + ) + .addValidator(Validators.greaterThan(0)) + .add() + .append(new KeyedCodec<>("MaxBiomeEdgeDistance", Codec.INTEGER, true), (t, k) -> t.maxBiomeEdgeDistance = k, t -> t.maxBiomeEdgeDistance) + .addValidator(Validators.greaterThanOrEqual(0)) + .add() + .append( + new KeyedCodec<>("ContentFields", new ArrayCodec<>(ContentFieldAsset.CODEC, ContentFieldAsset[]::new), false), + (t, k) -> t.contentFieldAssets = k, + t -> t.contentFieldAssets + ) + .add() + .build(); + private BiomeRangeAsset[] biomeRangeAssets = new BiomeRangeAsset[0]; + private int biomeTransitionDistance = 32; + private int maxBiomeEdgeDistance = 0; + private DensityAsset densityAsset = new ConstantDensityAsset(); + private String defaultBiomeId = ""; + private ContentFieldAsset[] contentFieldAssets = new ContentFieldAsset[0]; + + public BasicWorldStructureAsset() { + } + + @Nullable + @Override + public BiomeMap buildBiomeMap(@Nonnull WorldStructureAsset.Argument argument) { + ReferenceBundle referenceBundle = new ReferenceBundle(); + + for (int i = this.contentFieldAssets.length - 1; i >= 0; i--) { + if (this.contentFieldAssets[i] instanceof BaseHeightContentFieldAsset bedAsset) { + String name = bedAsset.getName(); + double y = bedAsset.getY(); + BaseHeightReference bedLayer = new BaseHeightReference((x, z) -> y); + referenceBundle.put(name, bedLayer, bedLayer.getClass()); + } + } + + HashMap biomeAssetToBiomeType = new HashMap<>(); + BiomeAsset defaultBiomeAsset = (BiomeAsset)((DefaultAssetMap)BiomeAsset.getAssetStore().getAssetMap()).getAsset(this.defaultBiomeId); + if (defaultBiomeAsset == null) { + LoggerUtil.getLogger().warning("Couldn't find Biome asset with id: " + this.defaultBiomeId); + return null; + } else { + BiomeType defaultBiome = defaultBiomeAsset.build(argument.materialCache, argument.parentSeed, referenceBundle, argument.workerIndexer); + biomeAssetToBiomeType.put(defaultBiomeAsset, defaultBiome); + Density noise = this.densityAsset.build(DensityAsset.from(argument, referenceBundle)); + SimpleNoiseCarta carta = new SimpleNoiseCarta<>(noise, defaultBiome); + + for (BiomeRangeAsset asset : this.biomeRangeAssets) { + DoubleRange range = asset.getRange(); + BiomeAsset biomeAsset = asset.getBiomeAsset(); + if (biomeAsset == null) { + LoggerUtil.getLogger().warning("Couldn't find biome asset with name " + asset.getBiomeAssetId()); + } else { + BiomeType biome; + if (biomeAssetToBiomeType.containsKey(biomeAsset)) { + biome = biomeAssetToBiomeType.get(biomeAsset); + } else { + biome = biomeAsset.build(argument.materialCache, argument.parentSeed, referenceBundle, argument.workerIndexer); + biomeAssetToBiomeType.put(biomeAsset, biome); + } + + carta.put(range, biome); + } + } + + SimpleBiomeMap biomeMap = new SimpleBiomeMap<>(carta); + int defaultRadius = Math.max(1, this.biomeTransitionDistance / 2); + biomeMap.setDefaultRadius(defaultRadius); + return biomeMap; + } + } + + @Override + public int getBiomeTransitionDistance() { + return this.biomeTransitionDistance; + } + + @Override + public int getMaxBiomeEdgeDistance() { + return this.maxBiomeEdgeDistance; + } + + @Override + public void cleanUp() { + this.densityAsset.cleanUp(); + + for (ContentFieldAsset contentFieldAsset : this.contentFieldAssets) { + contentFieldAsset.cleanUp(); + } + + BiomeAsset defaultBiomeAsset = (BiomeAsset)((DefaultAssetMap)BiomeAsset.getAssetStore().getAssetMap()).getAsset(this.defaultBiomeId); + if (defaultBiomeAsset != null) { + defaultBiomeAsset.cleanUp(); + } + + for (BiomeRangeAsset asset : this.biomeRangeAssets) { + BiomeAsset biomeAsset = asset.getBiomeAsset(); + if (biomeAsset != null) { + biomeAsset.cleanUp(); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/worldstructures/basic/BiomeRangeAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/worldstructures/basic/BiomeRangeAsset.java new file mode 100644 index 0000000..3715b2a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/worldstructures/basic/BiomeRangeAsset.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.worldstructures.basic; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.biomes.BiomeAsset; +import com.hypixel.hytale.builtin.hytalegenerator.rangemaps.DoubleRange; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BiomeRangeAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + BiomeRangeAsset.class, + BiomeRangeAsset::new, + Codec.STRING, + (asset, id) -> asset.id = id, + config -> config.id, + (config, data) -> config.data = data, + config -> config.data + ) + .append(new KeyedCodec<>("Biome", new ContainedAssetCodec<>(BiomeAsset.class, BiomeAsset.CODEC), true), (t, k) -> t.biomeAssetId = k, t -> t.biomeAssetId) + .addValidatorLate(() -> BiomeAsset.VALIDATOR_CACHE.getValidator().late()) + .add() + .append(new KeyedCodec<>("Min", Codec.DOUBLE, true), (t, k) -> t.min = k, t -> t.min) + .add() + .append(new KeyedCodec<>("Max", Codec.DOUBLE, true), (t, k) -> t.max = k, t -> t.max) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private double min = -1.0; + private double max = 1.0; + private String biomeAssetId = ""; + + private BiomeRangeAsset() { + } + + @Nonnull + public DoubleRange getRange() { + return DoubleRange.inclusive(this.min, this.max); + } + + @Nullable + public BiomeAsset getBiomeAsset() { + return (BiomeAsset)((DefaultAssetMap)BiomeAsset.getAssetStore().getAssetMap()).getAsset(this.biomeAssetId); + } + + public String getBiomeAssetId() { + return this.biomeAssetId; + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/worldstructures/mapcontentfield/BaseHeightContentFieldAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/worldstructures/mapcontentfield/BaseHeightContentFieldAsset.java new file mode 100644 index 0000000..7f3dff6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/worldstructures/mapcontentfield/BaseHeightContentFieldAsset.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.worldstructures.mapcontentfield; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; + +public class BaseHeightContentFieldAsset extends ContentFieldAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + BaseHeightContentFieldAsset.class, BaseHeightContentFieldAsset::new, ContentFieldAsset.ABSTRACT_CODEC + ) + .append(new KeyedCodec<>("Name", Codec.STRING, true), (t, k) -> t.name = k, t -> t.name) + .add() + .append(new KeyedCodec<>("Y", Codec.DOUBLE, false), (t, k) -> t.y = k, t -> t.y) + .add() + .build(); + private String id; + private AssetExtraInfo.Data data; + private String name = ""; + private double y = 0.0; + + private BaseHeightContentFieldAsset() { + } + + @Override + public String getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public double getY() { + return this.y; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/assets/worldstructures/mapcontentfield/ContentFieldAsset.java b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/worldstructures/mapcontentfield/ContentFieldAsset.java new file mode 100644 index 0000000..587b5f0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/assets/worldstructures/mapcontentfield/ContentFieldAsset.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.assets.worldstructures.mapcontentfield; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.hytalegenerator.assets.Cleanable; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; + +public abstract class ContentFieldAsset implements Cleanable, JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(ContentFieldAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(ContentFieldAsset.class).build(); + private String id; + private AssetExtraInfo.Data data; + + protected ContentFieldAsset() { + } + + public String getId() { + return this.id; + } + + @Override + public void cleanUp() { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/biome/BiomeType.java b/src/com/hypixel/hytale/builtin/hytalegenerator/biome/BiomeType.java new file mode 100644 index 0000000..e1cdffd --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/biome/BiomeType.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.builtin.hytalegenerator.biome; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; + +public interface BiomeType extends MaterialSource, PropsSource, EnvironmentSource, TintSource { + String getBiomeName(); + + @Nonnull + Density getTerrainDensity(); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/biome/EnvironmentSource.java b/src/com/hypixel/hytale/builtin/hytalegenerator/biome/EnvironmentSource.java new file mode 100644 index 0000000..6387c90 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/biome/EnvironmentSource.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.builtin.hytalegenerator.biome; + +import com.hypixel.hytale.builtin.hytalegenerator.environmentproviders.EnvironmentProvider; + +public interface EnvironmentSource { + EnvironmentProvider getEnvironmentProvider(); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/biome/MaterialSource.java b/src/com/hypixel/hytale/builtin/hytalegenerator/biome/MaterialSource.java new file mode 100644 index 0000000..2e0fdee --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/biome/MaterialSource.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.builtin.hytalegenerator.biome; + +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; + +public interface MaterialSource { + MaterialProvider getMaterialProvider(); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/biome/PropsSource.java b/src/com/hypixel/hytale/builtin/hytalegenerator/biome/PropsSource.java new file mode 100644 index 0000000..36b2219 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/biome/PropsSource.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.builtin.hytalegenerator.biome; + +import com.hypixel.hytale.builtin.hytalegenerator.PropField; +import com.hypixel.hytale.builtin.hytalegenerator.propdistributions.Assignments; +import java.util.List; + +public interface PropsSource { + List getPropFields(); + + List getAllPropDistributions(); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/biome/SimpleBiomeType.java b/src/com/hypixel/hytale/builtin/hytalegenerator/biome/SimpleBiomeType.java new file mode 100644 index 0000000..b31777e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/biome/SimpleBiomeType.java @@ -0,0 +1,82 @@ +package com.hypixel.hytale.builtin.hytalegenerator.biome; + +import com.hypixel.hytale.builtin.hytalegenerator.PropField; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.environmentproviders.EnvironmentProvider; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.propdistributions.Assignments; +import com.hypixel.hytale.builtin.hytalegenerator.tintproviders.TintProvider; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class SimpleBiomeType implements BiomeType { + private final Density terrainDensity; + private final MaterialProvider materialProvider; + private final List propFields; + private final EnvironmentProvider environmentProvider; + private final TintProvider tintProvider; + private final String biomeName; + + public SimpleBiomeType( + @Nonnull String biomeName, + @Nonnull Density terrainDensity, + @Nonnull MaterialProvider materialProvider, + @Nonnull EnvironmentProvider environmentProvider, + @Nonnull TintProvider tintProvider + ) { + this.terrainDensity = terrainDensity; + this.materialProvider = materialProvider; + this.biomeName = biomeName; + this.propFields = new ArrayList<>(); + this.environmentProvider = environmentProvider; + this.tintProvider = tintProvider; + } + + public void addPropFieldTo(@Nonnull PropField propField) { + this.propFields.add(propField); + } + + @Override + public MaterialProvider getMaterialProvider() { + return this.materialProvider; + } + + @Nonnull + @Override + public Density getTerrainDensity() { + return this.terrainDensity; + } + + @Override + public String getBiomeName() { + return this.biomeName; + } + + @Override + public List getPropFields() { + return this.propFields; + } + + @Override + public EnvironmentProvider getEnvironmentProvider() { + return this.environmentProvider; + } + + @Override + public TintProvider getTintProvider() { + return this.tintProvider; + } + + @Override + public List getAllPropDistributions() { + ArrayList list = new ArrayList<>(); + + for (PropField f : this.propFields) { + list.add(f.getPropDistribution()); + } + + return list; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/biome/TintSource.java b/src/com/hypixel/hytale/builtin/hytalegenerator/biome/TintSource.java new file mode 100644 index 0000000..ce13bd5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/biome/TintSource.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.builtin.hytalegenerator.biome; + +import com.hypixel.hytale.builtin.hytalegenerator.tintproviders.TintProvider; + +public interface TintSource { + TintProvider getTintProvider(); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/biomemap/BiomeMap.java b/src/com/hypixel/hytale/builtin/hytalegenerator/biomemap/BiomeMap.java new file mode 100644 index 0000000..60ed5c4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/biomemap/BiomeMap.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.builtin.hytalegenerator.biomemap; + +import com.hypixel.hytale.builtin.hytalegenerator.biome.BiomeType; +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.BiCarta; + +public abstract class BiomeMap extends BiCarta { + public BiomeMap() { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/biomemap/SimpleBiomeMap.java b/src/com/hypixel/hytale/builtin/hytalegenerator/biomemap/SimpleBiomeMap.java new file mode 100644 index 0000000..8648896 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/biomemap/SimpleBiomeMap.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.builtin.hytalegenerator.biomemap; + +import com.hypixel.hytale.builtin.hytalegenerator.biome.BiomeType; +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.BiCarta; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +public class SimpleBiomeMap extends BiomeMap { + private int defaultTransitionRadius; + private Map pairHashToRadius; + private BiCarta carta; + + public SimpleBiomeMap(@Nonnull BiCarta carta) { + this.carta = carta; + this.defaultTransitionRadius = 1; + this.pairHashToRadius = new HashMap<>(); + } + + public void setDefaultRadius(int defaultRadius) { + if (defaultRadius <= 0) { + throw new IllegalArgumentException(); + } else { + this.defaultTransitionRadius = defaultRadius; + } + } + + public BiomeType apply(int x, int z, @Nonnull WorkerIndexer.Id id) { + return this.carta.apply(x, z, id); + } + + @Override + public List allPossibleValues() { + return this.carta.allPossibleValues(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/bounds/Bounds3d.java b/src/com/hypixel/hytale/builtin/hytalegenerator/bounds/Bounds3d.java new file mode 100644 index 0000000..25592c5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/bounds/Bounds3d.java @@ -0,0 +1,170 @@ +package com.hypixel.hytale.builtin.hytalegenerator.bounds; + +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.performanceinstruments.MemInstrument; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class Bounds3d implements MemInstrument { + public final Vector3d min; + public final Vector3d max; + + public Bounds3d() { + this(Vector3d.ZERO, Vector3d.ZERO); + } + + public Bounds3d(@Nonnull Vector3d min, @Nonnull Vector3d max) { + this.min = min.clone(); + this.max = max.clone(); + this.correct(); + } + + public boolean contains(@Nonnull Vector3d position) { + return position.x >= this.min.x + && position.y >= this.min.y + && position.z >= this.min.z + && position.x < this.max.x + && position.y < this.max.y + && position.z < this.max.z; + } + + public boolean contains(@Nonnull Bounds3d other) { + return other.min.x >= this.min.x + && other.min.y >= this.min.y + && other.min.z >= this.min.z + && other.max.x <= this.max.x + && other.max.y <= this.max.y + && other.max.z <= this.max.z; + } + + public boolean intersects(@Nonnull Bounds3d other) { + return this.min.x < other.max.x + && this.min.y < other.max.y + && this.min.z < other.max.z + && this.max.x > other.min.x + && this.max.y > other.min.y + && this.max.z > other.min.z; + } + + public boolean isZeroVolume() { + return this.min.x >= this.max.x || this.min.y >= this.max.y || this.min.z >= this.max.z; + } + + public Vector3d getSize() { + return this.max.clone().subtract(this.min); + } + + @Nonnull + public Bounds3d assign(@Nonnull Bounds3d other) { + this.min.assign(other.min); + this.max.assign(other.max); + this.correct(); + return this; + } + + @Nonnull + public Bounds3d assign(@Nonnull Vector3d min, @Nonnull Vector3d max) { + this.min.assign(min); + this.max.assign(max); + this.correct(); + return this; + } + + @Nonnull + public Bounds3d offset(@Nonnull Vector3d vector) { + this.min.add(vector); + this.max.add(vector); + return this; + } + + @Nonnull + public Bounds3d intersect(@Nonnull Bounds3d other) { + if (!this.intersects(other)) { + this.min.assign(Vector3d.ZERO); + this.max.assign(Vector3d.ZERO); + } + + this.min.assign(Math.max(this.min.x, other.min.x), Math.max(this.min.y, other.min.y), Math.max(this.min.z, other.min.z)); + this.max.assign(Math.min(this.max.x, other.max.x), Math.min(this.max.y, other.max.y), Math.min(this.max.z, other.max.z)); + return this; + } + + @Nonnull + public Bounds3d encompass(@Nonnull Bounds3d other) { + if (other.isZeroVolume()) { + return this; + } else if (this.isZeroVolume()) { + this.min.assign(other.min); + this.max.assign(other.max); + return this; + } else { + this.min.assign(Math.min(this.min.x, other.min.x), Math.min(this.min.y, other.min.y), Math.min(this.min.z, other.min.z)); + this.max.assign(Math.max(this.max.x, other.max.x), Math.max(this.max.y, other.max.y), Math.max(this.max.z, other.max.z)); + return this; + } + } + + @Nonnull + public Bounds3d encompass(@Nonnull Vector3d position) { + this.min.assign(Math.min(this.min.x, position.x), Math.min(this.min.y, position.y), Math.min(this.min.z, position.z)); + this.max.assign(Math.max(this.max.x, position.x), Math.max(this.max.y, position.y), Math.max(this.max.z, position.z)); + return this; + } + + @Nonnull + public Bounds3d stack(@Nonnull Bounds3d other) { + if (!this.isZeroVolume() && !other.isZeroVolume()) { + Vector3d initialMax = this.max.clone(); + Bounds3d stamp = other.clone(); + stamp.offset(this.min); + this.encompass(stamp); + stamp = other.clone(); + stamp.offset(initialMax); + this.encompass(stamp); + return this; + } else { + return this; + } + } + + @Nonnull + public Bounds3d flipOnOriginPoint() { + Vector3d swap = this.min.clone(); + this.min.assign(this.max); + this.min.scale(-1.0); + this.max.assign(swap); + this.max.scale(-1.0); + return this; + } + + @Nonnull + public Bounds3d flipOnOriginVoxel() { + Vector3d swap = this.min.clone(); + this.min.assign(Vector3d.ALL_ONES); + this.min.subtract(this.max); + this.max.assign(Vector3d.ALL_ONES); + this.max.subtract(swap); + return this; + } + + @Nonnull + public Bounds3d clone() { + return new Bounds3d(this.min.clone(), this.max.clone()); + } + + public boolean isCorrect() { + return this.min.x <= this.max.x && this.min.y <= this.max.y && this.min.z <= this.max.z; + } + + public void correct() { + Vector3d swap = this.min.clone(); + this.min.assign(Math.min(this.max.x, this.min.x), Math.min(this.max.y, this.min.y), Math.min(this.max.z, this.min.z)); + this.max.assign(Math.max(swap.x, this.max.x), Math.max(swap.y, this.max.y), Math.max(swap.z, this.max.z)); + } + + @Nonnull + @Override + public MemInstrument.Report getMemoryUsage() { + long size_byte = 40L; + return new MemInstrument.Report(40L); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/bounds/Bounds3i.java b/src/com/hypixel/hytale/builtin/hytalegenerator/bounds/Bounds3i.java new file mode 100644 index 0000000..aa5f1a1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/bounds/Bounds3i.java @@ -0,0 +1,175 @@ +package com.hypixel.hytale.builtin.hytalegenerator.bounds; + +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.performanceinstruments.MemInstrument; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class Bounds3i implements MemInstrument { + public final Vector3i min; + public final Vector3i max; + + public Bounds3i() { + this(Vector3i.ZERO, Vector3i.ZERO); + } + + public Bounds3i(@Nonnull Vector3i min, @Nonnull Vector3i max) { + this.min = min.clone(); + this.max = max.clone(); + this.correct(); + } + + public boolean contains(@Nonnull Vector3i position) { + return position.x >= this.min.x + && position.y >= this.min.y + && position.z >= this.min.z + && position.x < this.max.x + && position.y < this.max.y + && position.z < this.max.z; + } + + public boolean contains(@Nonnull Bounds3i other) { + return other.min.x >= this.min.x + && other.min.y >= this.min.y + && other.min.z >= this.min.z + && other.max.x <= this.max.x + && other.max.y <= this.max.y + && other.max.z <= this.max.z; + } + + public boolean intersects(@Nonnull Bounds3i other) { + return this.min.x < other.max.x + && this.min.y < other.max.y + && this.min.z < other.max.z + && this.max.x > other.min.x + && this.max.y > other.min.y + && this.max.z > other.min.z; + } + + public boolean isZeroVolume() { + return this.min.x >= this.max.x || this.min.y >= this.max.y || this.min.z >= this.max.z; + } + + public Vector3i getSize() { + return this.max.clone().subtract(this.min); + } + + @Nonnull + public Bounds3i assign(@Nonnull Bounds3i other) { + this.min.assign(other.min); + this.max.assign(other.max); + this.correct(); + return this; + } + + @Nonnull + public Bounds3i assign(@Nonnull Vector3i min, @Nonnull Vector3i max) { + this.min.assign(min); + this.max.assign(max); + this.correct(); + return this; + } + + @Nonnull + public Bounds3i offset(@Nonnull Vector3i vector) { + this.min.add(vector); + this.max.add(vector); + return this; + } + + @Nonnull + public Bounds3i intersect(@Nonnull Bounds3i other) { + if (!this.intersects(other)) { + this.min.assign(Vector3i.ZERO); + this.max.assign(Vector3i.ZERO); + } + + this.min.assign(Math.max(this.min.x, other.min.x), Math.max(this.min.y, other.min.y), Math.max(this.min.z, other.min.z)); + this.max.assign(Math.min(this.max.x, other.max.x), Math.min(this.max.y, other.max.y), Math.min(this.max.z, other.max.z)); + return this; + } + + @Nonnull + public Bounds3i encompass(@Nonnull Bounds3i other) { + if (other.isZeroVolume()) { + return this; + } else if (this.isZeroVolume()) { + this.min.assign(other.min); + this.max.assign(other.max); + return this; + } else { + this.min.assign(Math.min(this.min.x, other.min.x), Math.min(this.min.y, other.min.y), Math.min(this.min.z, other.min.z)); + this.max.assign(Math.max(this.max.x, other.max.x), Math.max(this.max.y, other.max.y), Math.max(this.max.z, other.max.z)); + return this; + } + } + + @Nonnull + public Bounds3i encompass(@Nonnull Vector3i position) { + this.min.assign(Math.min(this.min.x, position.x), Math.min(this.min.y, position.y), Math.min(this.min.z, position.z)); + this.max.assign(Math.max(this.max.x, position.x), Math.max(this.max.y, position.y), Math.max(this.max.z, position.z)); + return this; + } + + @Nonnull + public Bounds3i stack(@Nonnull Bounds3i other) { + if (!this.isZeroVolume() && !other.isZeroVolume()) { + Vector3i initialMax = this.max.clone(); + Bounds3i stamp = other.clone(); + stamp.offset(this.min); + this.encompass(stamp); + stamp = other.clone(); + stamp.offset(initialMax.clone().subtract(Vector3i.ALL_ONES)); + this.encompass(stamp); + return this; + } else { + return this; + } + } + + @Nonnull + public Bounds3i flipOnOriginPoint() { + Vector3i swap = this.min.clone(); + this.min.assign(this.max); + this.min.scale(-1); + this.max.assign(swap); + this.max.scale(-1); + return this; + } + + @Nonnull + public Bounds3i flipOnOriginVoxel() { + Vector3i swap = this.min.clone(); + this.min.assign(Vector3i.ALL_ONES); + this.min.subtract(this.max); + this.max.assign(Vector3i.ALL_ONES); + this.max.subtract(swap); + return this; + } + + @Nonnull + public Bounds3d toBounds3d() { + return new Bounds3d(this.min.toVector3d(), this.max.toVector3d()); + } + + @Nonnull + public Bounds3i clone() { + return new Bounds3i(this.min.clone(), this.max.clone()); + } + + public boolean isCorrect() { + return this.min.x <= this.max.x && this.min.y <= this.max.y && this.min.z <= this.max.z; + } + + public void correct() { + Vector3i swap = this.min.clone(); + this.min.assign(Math.min(this.max.x, this.min.x), Math.min(this.max.y, this.min.y), Math.min(this.max.z, this.min.z)); + this.max.assign(Math.max(swap.x, this.max.x), Math.max(swap.y, this.max.y), Math.max(swap.z, this.max.z)); + } + + @Nonnull + @Override + public MemInstrument.Report getMemoryUsage() { + long size_byte = 28L; + return new MemInstrument.Report(28L); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/bounds/SpaceSize.java b/src/com/hypixel/hytale/builtin/hytalegenerator/bounds/SpaceSize.java new file mode 100644 index 0000000..cb3e1d5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/bounds/SpaceSize.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.builtin.hytalegenerator.bounds; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +@Deprecated +public class SpaceSize { + @Nonnull + private final Vector3i minInclusive; + @Nonnull + private final Vector3i maxExclusive; + @Nonnull + private final Vector3i maxInclusive; + + public SpaceSize(@Nonnull Vector3i minInclusive, @Nonnull Vector3i maxExclusive) { + this.minInclusive = minInclusive.clone(); + this.maxExclusive = maxExclusive.clone(); + this.maxInclusive = maxExclusive.clone().add(-1, -1, -1); + } + + public SpaceSize(@Nonnull Vector3i voxel) { + this(voxel.clone(), voxel.clone().add(1, 1, 1)); + } + + public SpaceSize() { + this(new Vector3i(), new Vector3i()); + } + + @Nonnull + public SpaceSize moveBy(@Nonnull Vector3i delta) { + this.minInclusive.add(delta); + this.maxExclusive.add(delta); + this.maxInclusive.add(delta); + return this; + } + + @Nonnull + public Vector3i getMinInclusive() { + return this.minInclusive.clone(); + } + + @Nonnull + public Vector3i getMaxExclusive() { + return this.maxExclusive.clone(); + } + + @Nonnull + public Vector3i getMaxInclusive() { + return this.maxInclusive.clone(); + } + + @Nonnull + public Vector3i getRange() { + Vector3i absMin = VectorUtil.fromOperation(value -> Math.abs(value.from(this.minInclusive))); + Vector3i absMax = VectorUtil.fromOperation(value -> Math.abs(value.from(this.maxInclusive))); + return Vector3i.max(absMin, absMax); + } + + @Nonnull + public SpaceSize clone() { + return new SpaceSize(this.minInclusive, this.maxExclusive); + } + + @Nonnull + public static SpaceSize merge(@Nonnull SpaceSize a, @Nonnull SpaceSize b) { + return new SpaceSize(Vector3i.min(a.minInclusive, b.minInclusive), Vector3i.max(a.maxExclusive, b.maxExclusive)); + } + + @Nonnull + public static SpaceSize stack(@Nonnull SpaceSize a, @Nonnull SpaceSize b) { + SpaceSize aMovedToMin = a.clone().moveBy(b.minInclusive); + SpaceSize aMovedToMax = a.clone().moveBy(b.maxInclusive); + Vector3i stackedMin = Vector3i.min(aMovedToMin.minInclusive, b.minInclusive); + Vector3i stackedMax = Vector3i.max(aMovedToMax.maxExclusive, b.maxExclusive); + return new SpaceSize(stackedMin, stackedMax); + } + + @Nonnull + public static SpaceSize empty() { + return new SpaceSize(new Vector3i(), new Vector3i()); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/cartas/SimpleNoiseCarta.java b/src/com/hypixel/hytale/builtin/hytalegenerator/cartas/SimpleNoiseCarta.java new file mode 100644 index 0000000..8831fef --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/cartas/SimpleNoiseCarta.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.builtin.hytalegenerator.cartas; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.BiCarta; +import com.hypixel.hytale.builtin.hytalegenerator.rangemaps.DoubleRange; +import com.hypixel.hytale.builtin.hytalegenerator.rangemaps.DoubleRangeMap; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3d; +import java.util.List; +import javax.annotation.Nonnull; + +public class SimpleNoiseCarta extends BiCarta { + @Nonnull + private final Density density; + @Nonnull + private final DoubleRangeMap rangeMap; + private final T defaultValue; + + public SimpleNoiseCarta(@Nonnull Density density, T defaultValue) { + this.density = density; + this.defaultValue = defaultValue; + this.rangeMap = new DoubleRangeMap<>(); + } + + @Nonnull + public SimpleNoiseCarta put(@Nonnull DoubleRange range, T value) { + this.rangeMap.put(range, value); + return this; + } + + @Override + public T apply(int x, int z, @Nonnull WorkerIndexer.Id id) { + Density.Context context = new Density.Context(); + context.position = new Vector3d(x, 0.0, z); + context.workerId = id; + double noiseValue = this.density.process(context); + T value = this.rangeMap.get(noiseValue); + return value == null ? this.defaultValue : value; + } + + @Nonnull + @Override + public List allPossibleValues() { + List list = this.rangeMap.values(); + list.add(this.defaultValue); + return list; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/chunkgenerator/ChunkGenerator.java b/src/com/hypixel/hytale/builtin/hytalegenerator/chunkgenerator/ChunkGenerator.java new file mode 100644 index 0000000..2352ea9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/chunkgenerator/ChunkGenerator.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.builtin.hytalegenerator.chunkgenerator; + +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedChunk; +import javax.annotation.Nonnull; + +public interface ChunkGenerator { + GeneratedChunk generate(@Nonnull ChunkRequest.Arguments var1); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/chunkgenerator/ChunkRequest.java b/src/com/hypixel/hytale/builtin/hytalegenerator/chunkgenerator/ChunkRequest.java new file mode 100644 index 0000000..5dc6050 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/chunkgenerator/ChunkRequest.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.builtin.hytalegenerator.chunkgenerator; + +import com.hypixel.hytale.math.vector.Transform; +import java.util.Objects; +import java.util.function.LongPredicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public record ChunkRequest(@Nonnull ChunkRequest.GeneratorProfile generatorProfile, @Nonnull ChunkRequest.Arguments arguments) { + public record Arguments(int seed, long index, int x, int z, @Nullable LongPredicate stillNeeded) { + } + + public static final class GeneratorProfile { + @Nonnull + private final String worldStructureName; + @Nonnull + private final Transform spawnPosition; + private int seed; + + public GeneratorProfile(@Nonnull String worldStructureName, @Nonnull Transform spawnPosition, int seed) { + this.worldStructureName = worldStructureName; + this.spawnPosition = spawnPosition; + this.seed = seed; + } + + @Nonnull + public String worldStructureName() { + return this.worldStructureName; + } + + @Nonnull + public Transform spawnPosition() { + return this.spawnPosition; + } + + public int seed() { + return this.seed; + } + + public void setSeed(int seed) { + this.seed = seed; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (obj != null && obj.getClass() == this.getClass()) { + ChunkRequest.GeneratorProfile that = (ChunkRequest.GeneratorProfile)obj; + return Objects.equals(this.worldStructureName, that.worldStructureName) + && Objects.equals(this.spawnPosition, that.spawnPosition) + && this.seed == that.seed; + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.worldStructureName, this.spawnPosition, this.seed); + } + + @Override + public String toString() { + return "GeneratorProfile[worldStructureName=" + this.worldStructureName + ", spawnPosition=" + this.spawnPosition + ", seed=" + this.seed + "]"; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/chunkgenerator/FallbackGenerator.java b/src/com/hypixel/hytale/builtin/hytalegenerator/chunkgenerator/FallbackGenerator.java new file mode 100644 index 0000000..7c81d67 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/chunkgenerator/FallbackGenerator.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.builtin.hytalegenerator.chunkgenerator; + +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedBlockChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedBlockStateChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedEntityChunk; +import javax.annotation.Nonnull; + +public class FallbackGenerator implements ChunkGenerator { + public static final FallbackGenerator INSTANCE = new FallbackGenerator(); + + public FallbackGenerator() { + } + + @Override + public GeneratedChunk generate(@Nonnull ChunkRequest.Arguments arguments) { + return new GeneratedChunk( + new GeneratedBlockChunk(arguments.index(), arguments.x(), arguments.z()), + new GeneratedBlockStateChunk(), + new GeneratedEntityChunk(), + GeneratedChunk.makeSections() + ); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/commands/ViewportCommand.java b/src/com/hypixel/hytale/builtin/hytalegenerator/commands/ViewportCommand.java new file mode 100644 index 0000000..f0e6f13 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/commands/ViewportCommand.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.builtin.hytalegenerator.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.assets.AssetManager; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.NViewport; +import com.hypixel.hytale.common.util.ExceptionUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ViewportCommand extends AbstractPlayerCommand { + @Nonnull + private final FlagArg deleteFlag = this.withFlagArg("delete", "Deletes the existing Viewport instance."); + @Nonnull + private final OptionalArg radiusArg = this.withOptionalArg( + "radius", "Creates a viewport with the given radius in chunks around the player.", ArgTypes.INTEGER + ); + @Nonnull + private final AssetManager assetManager; + @Nullable + private Runnable activeTask; + + public ViewportCommand(@Nonnull AssetManager assetManager) { + super("Viewport", "Establishes a worldgen viewport on the selected region."); + this.assetManager = assetManager; + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (this.activeTask != null) { + this.assetManager.unregisterReloadListener(this.activeTask); + this.activeTask = null; + } + + if (context.get(this.deleteFlag)) { + playerRef.sendMessage(Message.translation("server.commands.viewport.removed")); + } else { + Integer radius = context.get(this.radiusArg) << 5; + Bounds3i viewportBounds_voxelGrid; + if (radius != null) { + Vector3d playerPosition_voxelGrid = store.getComponent(ref, TransformComponent.getComponentType()).getPosition(); + Vector3i min_voxelGrid = playerPosition_voxelGrid.clone().subtract(radius.intValue()).toVector3i(); + Vector3i max_voxelGrid = playerPosition_voxelGrid.clone().add(radius.intValue()).toVector3i().add(Vector3i.ALL_ONES); + viewportBounds_voxelGrid = new Bounds3i(min_voxelGrid, max_voxelGrid); + } else { + BuilderToolsPlugin.BuilderState builderState = BuilderToolsPlugin.getState(playerComponent, playerRef); + BlockSelection selection = builderState.getSelection(); + if (selection == null) { + return; + } + + viewportBounds_voxelGrid = new Bounds3i(selection.getSelectionMin(), selection.getSelectionMax()); + } + + NViewport viewport = new NViewport(viewportBounds_voxelGrid, world, context.sender()); + this.activeTask = () -> world.execute(() -> { + try { + viewport.refresh(); + } catch (Exception var3x) { + String msg = "Could not refresh viewport because of the following exception:\n"; + msg = msg + ExceptionUtil.toStringWithStack(var3x); + LoggerUtil.getLogger().severe(msg); + } + }); + this.activeTask.run(); + this.assetManager.registerReloadListener(this.activeTask); + playerRef.sendMessage(Message.translation("server.commands.viewport.created")); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/conveyor/stagedconveyor/ContextDependency.java b/src/com/hypixel/hytale/builtin/hytalegenerator/conveyor/stagedconveyor/ContextDependency.java new file mode 100644 index 0000000..8c2d583 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/conveyor/stagedconveyor/ContextDependency.java @@ -0,0 +1,171 @@ +package com.hypixel.hytale.builtin.hytalegenerator.conveyor.stagedconveyor; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class ContextDependency { + @Nonnull + public static ContextDependency EMPTY = new ContextDependency(new Vector3i(), new Vector3i()); + private final Vector3i readRange; + private final Vector3i writeRange; + private Vector3i trashRange; + private Vector3i externalDependencyRange; + private Vector3i positioningRange; + + public ContextDependency(@Nonnull Vector3i readRange, @Nonnull Vector3i writeRange) { + this.readRange = readRange.clone(); + this.writeRange = writeRange.clone(); + this.update(); + } + + public ContextDependency() { + this(new Vector3i(), new Vector3i()); + } + + @Nonnull + public Bounds3i getTotalPropBounds_voxelGrid() { + Vector3i readMin_voxelGrid = this.getReadRange().scale(-1); + Vector3i readMax_voxelGrid = this.getReadRange().add(Vector3i.ALL_ONES); + Vector3i writeMin_voxelGrid = this.getWriteRange().scale(-1); + Vector3i writeMax_voxelGrid = this.getWriteRange().add(Vector3i.ALL_ONES); + Bounds3i readBounds_voxelGrid = new Bounds3i(readMin_voxelGrid, readMax_voxelGrid); + Bounds3i writeBounds_voxelGrid = new Bounds3i(writeMin_voxelGrid, writeMax_voxelGrid); + writeBounds_voxelGrid.stack(readBounds_voxelGrid); + return writeBounds_voxelGrid; + } + + private void update() { + this.trashRange = VectorUtil.fromOperation(this.readRange, this.writeRange, (r, w, retriever) -> r >= 0 && w >= 0 ? r + w : 0); + this.externalDependencyRange = VectorUtil.fromOperation( + this.readRange, this.writeRange, (r, w, retriever) -> r < 0 ? -w : Math.max(r, retriever.from(this.trashRange)) + ); + this.positioningRange = VectorUtil.fromOperation(this.readRange, this.writeRange, (r, w, retriever) -> r < 0 ? -w : r); + this.trashRange.y = 0; + this.externalDependencyRange.y = 0; + this.positioningRange.y = 0; + this.readRange.y = 0; + this.writeRange.y = 0; + } + + @Nonnull + public ContextDependency stackOver(@Nonnull ContextDependency other) { + new Vector3i(); + new Vector3i(); + Vector3i r1 = this.getReadRange(); + Vector3i w1 = this.getWriteRange(); + Vector3i r2 = other.getReadRange(); + Vector3i w2 = other.getWriteRange(); + Vector3i totalRead = VectorUtil.fromOperation(value -> { + if (value.from(r1) < 0 && value.from(r2) < 0) { + return -1; + } else if (value.from(r1) < 0) { + return value.from(r2); + } else { + return value.from(r2) < 0 ? value.from(r1) : value.from(r1) + value.from(w1) + value.from(r2); + } + }); + Vector3i totalWrite = VectorUtil.fromOperation(value -> { + if (value.from(r1) < 0 && value.from(r2) < 0) { + return -Math.min(value.from(w1), value.from(w2)); + } else if (value.from(r1) < 0) { + return value.from(w2); + } else { + return value.from(r2) < 0 ? value.from(w1) : value.from(w2); + } + }); + return new ContextDependency(totalRead, totalWrite); + } + + @Nonnull + public Vector3i getReadRange() { + return this.readRange.clone(); + } + + @Nonnull + public Vector3i getWriteRange() { + return this.writeRange.clone(); + } + + @Nonnull + public Vector3i getTrashRange() { + return this.trashRange.clone(); + } + + @Nonnull + public Vector3i getExternalDependencyRange() { + return this.externalDependencyRange.clone(); + } + + @Nonnull + public Vector3i getPositioningRange() { + return this.positioningRange.clone(); + } + + @Nonnull + public static Vector3i getRequiredPadOf(@Nonnull List dependencies) { + Vector3i pad = new Vector3i(); + + for (ContextDependency dependency : dependencies) { + pad.add(dependency.getExternalDependencyRange()); + } + + return pad; + } + + @Nonnull + public static Map cloneMap(@Nonnull Map map) { + HashMap out = new HashMap<>(map.size()); + map.forEach((k, v) -> out.put(k, v.clone())); + return out; + } + + @Nonnull + public static Map stackMaps(@Nonnull Map under, @Nonnull Map over) { + Map out = new HashMap<>(); + + for (Entry entry : over.entrySet()) { + if (!under.containsKey(entry.getKey())) { + out.put(entry.getKey(), entry.getValue()); + } else { + out.put(entry.getKey(), entry.getValue().stackOver(under.get(entry.getKey()))); + } + } + + for (Entry entryx : under.entrySet()) { + if (!over.containsKey(entryx.getKey())) { + out.put(entryx.getKey(), entryx.getValue()); + } + } + + return out; + } + + @Nonnull + public static ContextDependency mostOf(@Nonnull List dependencies) { + ContextDependency out = EMPTY; + + for (ContextDependency d : dependencies) { + out = mostOf(out, d); + } + + return out; + } + + @Nonnull + public static ContextDependency mostOf(@Nonnull ContextDependency a, @Nonnull ContextDependency b) { + Vector3i read = Vector3i.max(a.readRange, b.readRange); + Vector3i write = Vector3i.max(a.writeRange, b.writeRange); + return new ContextDependency(read, write); + } + + @Nonnull + public ContextDependency clone() { + return new ContextDependency(this.readRange, this.writeRange); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/CollectionFactory.java b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/CollectionFactory.java new file mode 100644 index 0000000..f03c173 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/CollectionFactory.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.builtin.hytalegenerator.datastructures; + +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nonnull; + +public class CollectionFactory { + public CollectionFactory() { + } + + @Nonnull + public static Set hashSetOf(@Nonnull T... elements) { + Set set = new HashSet<>(elements.length); + + for (T element : elements) { + if (element == null) { + throw new NullPointerException("elements can't be null"); + } + + set.add(element); + } + + return set; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/TieredList.java b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/TieredList.java new file mode 100644 index 0000000..3990b98 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/TieredList.java @@ -0,0 +1,201 @@ +package com.hypixel.hytale.builtin.hytalegenerator.datastructures; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +public class TieredList { + @Nonnull + private final Map> elements; + private final int tiers; + private List sortedTierList; + + public TieredList() { + this(0); + } + + public TieredList(int tiers) { + if (tiers < 0) { + throw new IllegalArgumentException("negative number of tiers"); + } else { + this.tiers = tiers; + this.elements = new HashMap<>(); + + for (int tier = 0; tier < tiers; tier++) { + this.elements.put(tier, new ArrayList<>()); + } + + this.updateSortedTierList(); + } + } + + @Nonnull + public TieredList addTier(int tier) { + if (this.tierExists(tier)) { + throw new IllegalArgumentException("tier already exists " + tier); + } else { + this.elements.put(tier, new ArrayList<>()); + this.updateSortedTierList(); + return this; + } + } + + @Nonnull + public TieredList removeTier(int tier) { + if (!this.tierExists(tier)) { + return this; + } else { + this.elements.remove(tier); + this.updateSortedTierList(); + return this; + } + } + + public void add(@Nonnull E element, int tier) { + if (element == null) { + throw new NullPointerException(); + } else { + if (!this.tierExists(tier)) { + this.addTier(tier); + } + + this.elements.get(tier).add(element); + } + } + + public boolean isEmpty() { + for (List list : this.elements.values()) { + if (!list.isEmpty()) { + return false; + } + } + + return true; + } + + public E peek() { + for (int tier = 0; tier < this.tiers; tier++) { + List tierElements = this.elements.get(tier); + if (!tierElements.isEmpty()) { + return tierElements.getFirst(); + } + } + + throw new IllegalStateException("queue is empty"); + } + + public E remove() { + for (int tier = 0; tier < this.tiers; tier++) { + List tierElements = this.elements.get(tier); + if (!tierElements.isEmpty()) { + return tierElements.removeFirst(); + } + } + + throw new IllegalStateException("queue is empty"); + } + + public int size() { + int size = 0; + + for (List list : this.elements.values()) { + size += list.size(); + } + + return size; + } + + public int size(int tier) { + return !this.tierExists(tier) ? 0 : this.elements.get(tier).size(); + } + + @Nonnull + public TieredList forEach(int tier, @Nonnull Consumer consumer) { + if (!this.tierExists(tier)) { + return this; + } else { + this.elements.get(tier).forEach(consumer); + return this; + } + } + + @Nonnull + public TieredList removeEach(int tier, @Nonnull Consumer consumer) { + if (!this.tierExists(tier)) { + return this; + } else { + for (E e : this.elements.get(tier)) { + consumer.accept(e); + } + + new ArrayList(); + return this; + } + } + + @Nonnull + public TieredList forEach(@Nonnull Consumer consumer) { + ArrayList tiers = new ArrayList<>(this.getTiers()); + tiers.sort(Comparator.naturalOrder()); + + for (int tier : tiers) { + this.forEach(tier, consumer); + } + + return this; + } + + @Nonnull + public TieredList removeEach(@Nonnull Consumer consumer) { + for (int tier : this.getTiers()) { + this.removeEach(tier, consumer); + } + + return this; + } + + @Nonnull + public Iterator iterator(int tier) { + if (!this.tierExists(tier)) { + throw new IllegalArgumentException("tier doesn't exist"); + } else { + return this.elements.get(tier).iterator(); + } + } + + @Nonnull + public List listOf(int tier) { + if (!this.tierExists(tier)) { + throw new IllegalArgumentException("tier doesn't exist"); + } else { + return Collections.unmodifiableList(this.elements.get(tier)); + } + } + + public boolean tierExists(int tier) { + return this.elements.containsKey(tier); + } + + public List getTiers() { + return this.sortedTierList; + } + + private void updateSortedTierList() { + List tierList = new ArrayList<>(this.elements.keySet()); + tierList.sort(Comparator.naturalOrder()); + tierList = Collections.unmodifiableList(tierList); + this.sortedTierList = tierList; + } + + @Nonnull + @Override + public String toString() { + return "TieredList{elements=" + this.elements + ", tiers=" + this.tiers + ", sortedTierList=" + this.sortedTierList + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/WeightedMap.java b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/WeightedMap.java new file mode 100644 index 0000000..4583fef --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/WeightedMap.java @@ -0,0 +1,132 @@ +package com.hypixel.hytale.builtin.hytalegenerator.datastructures; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; + +public class WeightedMap { + @Nonnull + private final Set elementSet; + @Nonnull + private final List elements; + @Nonnull + private final List weights; + @Nonnull + private final Map indices; + private double totalWeight = 0.0; + private boolean immutable = false; + + public WeightedMap(@Nonnull WeightedMap other) { + this.totalWeight = other.totalWeight; + this.elementSet = new HashSet<>(other.elementSet); + this.elements = new ArrayList<>(other.elements); + this.weights = new ArrayList<>(other.weights); + this.indices = new HashMap<>(other.indices); + this.immutable = other.immutable; + } + + public WeightedMap() { + this(2); + } + + public WeightedMap(int initialCapacity) { + this.elementSet = new HashSet<>(initialCapacity); + this.elements = new ArrayList<>(initialCapacity); + this.weights = new ArrayList<>(initialCapacity); + this.indices = new HashMap<>(initialCapacity); + } + + @Nonnull + public WeightedMap add(@Nonnull T element, double weight) { + if (element == null) { + throw new NullPointerException(); + } else if (this.immutable) { + throw new IllegalStateException("method can't be called when object is immutable"); + } else if (weight < 0.0) { + throw new IllegalArgumentException("weight must be positive"); + } else { + this.elements.add(element); + this.weights.add(weight); + this.elementSet.add(element); + this.totalWeight += weight; + this.indices.put(element, this.indices.size()); + return this; + } + } + + public double get(@Nonnull T element) { + if (element == null) { + throw new NullPointerException(); + } else if (this.immutable) { + throw new IllegalStateException("method can't be called when object is immutable"); + } else { + return !this.elementSet.contains(element) ? 0.0 : this.weights.get(this.indices.get(element)); + } + } + + public T pick(@Nonnull Random rand) { + if (rand == null) { + throw new NullPointerException(); + } else if (this.elements.isEmpty()) { + throw new IllegalStateException("can't be empty when calling this method"); + } else { + double pointer = rand.nextDouble() * this.totalWeight; + + for (int i = 0; i < this.elements.size(); i++) { + pointer -= this.weights.get(i); + if (pointer <= 0.0) { + return this.elements.get(i); + } + } + + return this.elements.getLast(); + } + } + + public int size() { + return this.elements.size(); + } + + @Nonnull + public List allElements() { + return new ArrayList<>(this.elements); + } + + public void makeImmutable() { + this.immutable = true; + } + + public boolean isImmutable() { + return this.immutable; + } + + public void forEach(@Nonnull BiConsumer consumer) { + for (int i = 0; i < this.elements.size(); i++) { + consumer.accept(this.elements.get(i), this.weights.get(i)); + } + } + + @Nonnull + @Override + public String toString() { + return "WeighedMap{elementSet=" + + this.elementSet + + ", elements=" + + this.elements + + ", weights=" + + this.weights + + ", indices=" + + this.indices + + ", totalWeight=" + + this.totalWeight + + ", immutable=" + + this.immutable + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/bicoordinatecache/BiCoordinateCache.java b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/bicoordinatecache/BiCoordinateCache.java new file mode 100644 index 0000000..359750d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/bicoordinatecache/BiCoordinateCache.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.builtin.hytalegenerator.datastructures.bicoordinatecache; + +public interface BiCoordinateCache { + T get(int var1, int var2); + + boolean isCached(int var1, int var2); + + T save(int var1, int var2, T var3); + + void flush(int var1, int var2); + + void flush(); + + int size(); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/bicoordinatecache/HashedBiCoordinateCache.java b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/bicoordinatecache/HashedBiCoordinateCache.java new file mode 100644 index 0000000..277b1a0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/bicoordinatecache/HashedBiCoordinateCache.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.builtin.hytalegenerator.datastructures.bicoordinatecache; + +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; + +public class HashedBiCoordinateCache implements BiCoordinateCache { + @Nonnull + private final ConcurrentHashMap values = new ConcurrentHashMap<>(); + + public HashedBiCoordinateCache() { + } + + public static long hash(int x, int z) { + long hash = x; + hash <<= 32; + return hash + z; + } + + @Override + public T get(int x, int z) { + long key = hash(x, z); + if (!this.values.containsKey(key)) { + throw new IllegalStateException("doesn't contain coordinates"); + } else { + return this.values.get(key); + } + } + + @Override + public boolean isCached(int x, int z) { + return this.values.containsKey(hash(x, z)); + } + + @Nonnull + @Override + public T save(int x, int z, @Nonnull T value) { + long key = hash(x, z); + this.values.put(key, value); + return value; + } + + @Override + public void flush(int x, int z) { + long key = hash(x, z); + if (this.values.containsKey(key)) { + this.values.remove(key); + } + } + + @Override + public void flush() { + for (long key : this.values.keySet()) { + this.values.remove(key); + } + } + + @Override + public int size() { + return this.values.size(); + } + + @Nonnull + @Override + public String toString() { + return "HashedBiCoordinateCache{values=" + this.values + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/bicoordinatecache/WrappedBiCoordinateCache.java b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/bicoordinatecache/WrappedBiCoordinateCache.java new file mode 100644 index 0000000..6739491 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/bicoordinatecache/WrappedBiCoordinateCache.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.builtin.hytalegenerator.datastructures.bicoordinatecache; + +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class WrappedBiCoordinateCache implements BiCoordinateCache { + private final int sizeX; + private final int sizeZ; + @Nonnull + private final T[][] values; + @Nonnull + private final boolean[][] populated; + private int size; + + public WrappedBiCoordinateCache(int sizeX, int sizeZ) { + if (sizeX >= 0 && sizeZ >= 0) { + this.sizeX = sizeX; + this.sizeZ = sizeZ; + this.values = (T[][])(new Object[sizeX][sizeZ]); + this.populated = new boolean[sizeX][sizeZ]; + this.size = 0; + } else { + throw new IllegalArgumentException("negative size"); + } + } + + public int localXFrom(int x) { + return x < 0 ? (x % this.sizeX + this.sizeX - 1) % this.sizeX : x % this.sizeX; + } + + public int localZFrom(int z) { + return z < 0 ? (z % this.sizeZ + this.sizeZ - 1) % this.sizeZ : z % this.sizeZ; + } + + @Override + public T get(int x, int z) { + x = this.localXFrom(x); + z = this.localZFrom(z); + if (!this.isCached(x, z)) { + throw new IllegalStateException("accessing coordinates that are not cached: " + x + " " + z); + } else { + return this.values[x][z]; + } + } + + @Override + public boolean isCached(int x, int z) { + return this.populated[this.localXFrom(x)][this.localZFrom(z)]; + } + + @Override + public T save(int x, int z, T value) { + x = this.localXFrom(x); + z = this.localZFrom(z); + this.values[x][z] = value; + this.populated[x][z] = true; + this.size++; + return value; + } + + @Override + public void flush(int x, int z) { + x = this.localXFrom(x); + z = this.localZFrom(z); + if (this.populated[x][z]) { + this.values[x][z] = null; + this.populated[x][z] = false; + this.size--; + } + } + + @Override + public void flush() { + for (int x = 0; x < this.sizeX; x++) { + for (int z = 0; z < this.sizeZ; z++) { + this.values[x][z] = null; + this.populated[x][z] = false; + } + } + + this.size = 0; + } + + @Override + public int size() { + return this.size; + } + + @Nonnull + @Override + public String toString() { + return "WrappedBiCoordinateCache{sizeX=" + + this.sizeX + + ", sizeZ=" + + this.sizeZ + + ", values=" + + Arrays.toString((Object[])this.values) + + ", populated=" + + Arrays.toString((Object[])this.populated) + + ", size=" + + this.size + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/bicoordinatecache/WrappedBiCoordinateDoubleCache.java b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/bicoordinatecache/WrappedBiCoordinateDoubleCache.java new file mode 100644 index 0000000..3f4278f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/bicoordinatecache/WrappedBiCoordinateDoubleCache.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.builtin.hytalegenerator.datastructures.bicoordinatecache; + +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class WrappedBiCoordinateDoubleCache implements BiCoordinateCache { + private final int sizeX; + private final int sizeZ; + @Nonnull + private final double[][] values; + @Nonnull + private final boolean[][] populated; + private int size; + + public WrappedBiCoordinateDoubleCache(int sizeX, int sizeZ) { + if (sizeX >= 0 && sizeZ >= 0) { + this.sizeX = sizeX; + this.sizeZ = sizeZ; + this.values = new double[sizeX][sizeZ]; + this.populated = new boolean[sizeX][sizeZ]; + this.size = 0; + } else { + throw new IllegalArgumentException("negative size"); + } + } + + public int localXFrom(int x) { + return x < 0 ? (x % this.sizeX + this.sizeX - 1) % this.sizeX : x % this.sizeX; + } + + public int localZFrom(int z) { + return z < 0 ? (z % this.sizeZ + this.sizeZ - 1) % this.sizeZ : z % this.sizeZ; + } + + @Nonnull + public Double get(int x, int z) { + x = this.localXFrom(x); + z = this.localZFrom(z); + if (!this.isCached(x, z)) { + throw new IllegalStateException("accessing coordinates that are not cached: " + x + " " + z); + } else { + return this.values[x][z]; + } + } + + @Override + public boolean isCached(int x, int z) { + return this.populated[this.localXFrom(x)][this.localZFrom(z)]; + } + + @Nonnull + public Double save(int x, int z, @Nonnull Double value) { + x = this.localXFrom(x); + z = this.localZFrom(z); + this.values[x][z] = value; + this.populated[x][z] = true; + this.size++; + return value; + } + + @Override + public void flush(int x, int z) { + if (this.populated[this.localXFrom(x)][this.localZFrom(z)]) { + this.populated[this.localXFrom(x)][this.localZFrom(z)] = false; + this.size--; + } + } + + @Override + public void flush() { + for (int x = 0; x < this.sizeX; x++) { + for (int z = 0; z < this.sizeZ; z++) { + this.populated[x][z] = false; + } + } + + this.size = 0; + } + + @Override + public int size() { + return this.size; + } + + @Nonnull + @Override + public String toString() { + return "WrappedBiCoordinateDoubleCache{sizeX=" + + this.sizeX + + ", sizeZ=" + + this.sizeZ + + ", values=" + + Arrays.toString((Object[])this.values) + + ", populated=" + + Arrays.toString((Object[])this.populated) + + ", size=" + + this.size + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/compression/Compressor.java b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/compression/Compressor.java new file mode 100644 index 0000000..9e2e808 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/compression/Compressor.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.builtin.hytalegenerator.datastructures.compression; + +import javax.annotation.Nonnull; + +public class Compressor { + private final int MIN_RUN = 7; + + public Compressor() { + } + + @Nonnull + public Compressor.CompressedArray compressOnReference(@Nonnull T[] in) { + int currentRun = 0; + int resultIndex = 0; + Object runObj = null; + Object[] result = new Object[in.length]; + + for (int i = 0; i < result.length; i++) { + if (in[i] != runObj && currentRun >= 7) { + result[resultIndex] = new Compressor.Run(runObj, currentRun); + currentRun = 0; + resultIndex++; + runObj = in[i]; + } else if (in[i] != runObj && currentRun < 7) { + while (currentRun > 0) { + result[resultIndex] = runObj; + resultIndex++; + currentRun--; + } + + currentRun = 0; + runObj = in[i]; + } else { + currentRun++; + } + } + + if (currentRun >= 7) { + result[resultIndex] = new Compressor.Run(runObj, currentRun); + } else { + while (currentRun > 0) { + result[resultIndex] = runObj; + resultIndex++; + currentRun--; + } + } + + Object[] trimmedResult = new Object[resultIndex]; + System.arraycopy(result, 0, trimmedResult, 0, trimmedResult.length); + return new Compressor.CompressedArray<>(trimmedResult, in.length); + } + + @Nonnull + public T[] decompress(@Nonnull Compressor.CompressedArray compressedArray) { + int caIndex = 0; + int runIndex = 0; + int outIndex = 0; + Object[] ca = compressedArray.data; + + Object[] out; + for (out = new Object[compressedArray.initialLength]; caIndex < ca.length; caIndex++) { + if (ca[caIndex] instanceof Compressor.Run run) { + for (int var8 = 0; var8 < run.length; var8++) { + out[outIndex++] = run.obj; + } + } else { + out[outIndex++] = ca[caIndex]; + } + } + + return (T[])out; + } + + public static class CompressedArray { + private final Object[] data; + private final int initialLength; + + private CompressedArray(Object[] data, int initialLength) { + this.data = data; + this.initialLength = initialLength; + } + } + + public static class Run { + Object obj; + int length; + + private Run(Object obj, int length) { + this.obj = obj; + this.length = length; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/ArrayVoxelSpace.java b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/ArrayVoxelSpace.java new file mode 100644 index 0000000..f3a11a3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/ArrayVoxelSpace.java @@ -0,0 +1,330 @@ +package com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ArrayVoxelSpace implements VoxelSpace { + protected final int sizeX; + protected final int sizeY; + protected final int sizeZ; + @Nonnull + protected final T[] contents; + @Nonnull + protected String name = "schematic"; + @Nullable + protected T[] fastReset = null; + protected VoxelCoordinate origin; + + public ArrayVoxelSpace(@Nonnull Bounds3i bounds) { + int var10005 = -bounds.min.x; + this("", bounds.getSize().x, bounds.getSize().y, bounds.getSize().z, var10005, bounds.min.y, -bounds.min.z); + } + + public ArrayVoxelSpace(@Nonnull String name, int sizeX, int sizeY, int sizeZ, int originX, int originY, int originZ) { + if (name == null) { + throw new NullPointerException(); + } else if (sizeX >= 1 && sizeY >= 1 && sizeZ >= 1) { + this.name = name; + this.sizeX = sizeX; + this.sizeY = sizeY; + this.sizeZ = sizeZ; + this.contents = (T[])(new Object[sizeX * sizeY * sizeZ]); + this.origin = new VoxelCoordinate(originX, originY, originZ); + } else { + throw new IllegalArgumentException("invalid size " + sizeX + " " + sizeY + " " + sizeZ); + } + } + + public ArrayVoxelSpace(int sizeX, int sizeY, int sizeZ) { + if (sizeX >= 1 && sizeY >= 1 && sizeZ >= 1) { + this.name = this.getClass().getName(); + this.sizeX = sizeX; + this.sizeY = sizeY; + this.sizeZ = sizeZ; + this.contents = (T[])(new Object[sizeX * sizeY * sizeZ]); + this.origin = new VoxelCoordinate(0, 0, 0); + } else { + throw new IllegalArgumentException("invalid size " + sizeX + " " + sizeY + " " + sizeZ); + } + } + + public ArrayVoxelSpace(@Nonnull VoxelSpace voxelSpace) { + this( + voxelSpace.getName(), + voxelSpace.sizeX(), + voxelSpace.sizeY(), + voxelSpace.sizeZ(), + voxelSpace.getOriginX(), + voxelSpace.getOriginY(), + voxelSpace.getOriginZ() + ); + voxelSpace.forEach((v, x, y, z) -> this.set((T)v, x, y, z)); + } + + public void setFastResetTo(T e) { + this.fastReset = (T[])(new Object[this.contents.length]); + + for (int i = 0; i < this.fastReset.length; i++) { + this.fastReset[i] = e; + } + } + + public void disableFastReset() { + this.fastReset = null; + } + + public boolean hasFastReset() { + return this.fastReset != null; + } + + public void fastReset() { + if (this.fastReset == null) { + throw new IllegalStateException("no fast-reset"); + } else { + System.arraycopy(this.fastReset, 0, this.contents, 0, this.fastReset.length); + } + } + + @Override + public int sizeX() { + return this.sizeX; + } + + @Override + public int sizeY() { + return this.sizeY; + } + + @Override + public int sizeZ() { + return this.sizeZ; + } + + @Override + public void pasteFrom(@Nonnull VoxelSpace source) { + if (source == null) { + throw new NullPointerException(); + } else { + for (int x = source.minX(); x < source.maxX(); x++) { + for (int y = source.minY(); y < source.maxY(); y++) { + for (int z = source.minZ(); z < source.maxZ(); z++) { + this.set(source.getContent(x, y, z), x, y, z); + } + } + } + } + } + + @Override + public boolean set(T content, int x, int y, int z) { + if (!this.isInsideSpace(x, y, z)) { + return false; + } else { + this.contents[this.arrayIndex(x + this.origin.x, y + this.origin.y, z + this.origin.z)] = content; + return true; + } + } + + @Override + public boolean set(T content, @Nonnull Vector3i position) { + return this.set(content, position.x, position.y, position.z); + } + + @Override + public void set(T content) { + for (int x = this.minX(); x < this.maxX(); x++) { + for (int y = this.minY(); y < this.maxY(); y++) { + for (int z = this.minZ(); z < this.maxZ(); z++) { + this.set(content, x, y, z); + } + } + } + } + + @Override + public void setOrigin(int x, int y, int z) { + this.origin.x = x; + this.origin.y = y; + this.origin.z = z; + } + + @Override + public T getContent(int x, int y, int z) { + if (!this.isInsideSpace(x, y, z)) { + throw new IndexOutOfBoundsException( + "Coordinates outside VoxelSpace: " + + x + + " " + + y + + " " + + z + + " constraints " + + this.minX() + + " -> " + + this.maxX() + + " " + + this.minY() + + " -> " + + this.maxY() + + " " + + this.minZ() + + " -> " + + this.maxZ() + + "\n" + + this.toString() + ); + } else { + return this.contents[this.arrayIndex(x + this.origin.x, y + this.origin.y, z + this.origin.z)]; + } + } + + @Nullable + @Override + public T getContent(@Nonnull Vector3i position) { + return this.getContent(position.x, position.y, position.z); + } + + @Override + public boolean replace(T replacement, int x, int y, int z, @Nonnull Predicate mask) { + if (!this.isInsideSpace(x, y, z)) { + throw new IllegalArgumentException("outside schematic"); + } else if (!mask.test(this.getContent(x, y, z))) { + return false; + } else { + this.set(replacement, x, y, z); + return true; + } + } + + public T[] toArray() { + return (T[])((Object[])this.contents.clone()); + } + + @Nonnull + VoxelCoordinate getOrigin() { + return this.origin.clone(); + } + + @Override + public int getOriginX() { + return this.origin.x; + } + + @Override + public int getOriginY() { + return this.origin.y; + } + + @Override + public int getOriginZ() { + return this.origin.z; + } + + @Nonnull + @Override + public String getName() { + return this.name; + } + + @Override + public boolean isInsideSpace(int x, int y, int z) { + return x + this.origin.x >= 0 + && x + this.origin.x < this.sizeX + && y + this.origin.y >= 0 + && y + this.origin.y < this.sizeY + && z + this.origin.z >= 0 + && z + this.origin.z < this.sizeZ; + } + + @Override + public boolean isInsideSpace(@Nonnull Vector3i position) { + return this.isInsideSpace(position.x, position.y, position.z); + } + + @Override + public void forEach(@Nonnull VoxelConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + for (int x = this.minX(); x < this.maxX(); x++) { + for (int y = this.minY(); y < this.maxY(); y++) { + for (int z = this.minZ(); z < this.maxZ(); z++) { + action.accept(this.getContent(x, y, z), x, y, z); + } + } + } + } + } + + @Override + public int minX() { + return -this.origin.x; + } + + @Override + public int maxX() { + return this.sizeX - this.origin.x; + } + + @Override + public int minY() { + return -this.origin.y; + } + + @Override + public int maxY() { + return this.sizeY - this.origin.y; + } + + @Override + public int minZ() { + return -this.origin.z; + } + + @Override + public int maxZ() { + return this.sizeZ - this.origin.z; + } + + @Nonnull + public ArrayVoxelSpace clone() { + ArrayVoxelSpace clone = new ArrayVoxelSpace<>(this.name, this.sizeX, this.sizeY, this.sizeZ, this.origin.x, this.origin.y, this.origin.z); + this.forEach((v, x, y, z) -> clone.set((T)v, x, y, z)); + return clone; + } + + private int arrayIndex(int x, int y, int z) { + return y + x * this.sizeY + z * this.sizeY * this.sizeX; + } + + @Nonnull + @Override + public String toString() { + return "ArrayVoxelSpace{sizeX=" + + this.sizeX + + ", sizeY=" + + this.sizeY + + ", sizeZ=" + + this.sizeZ + + ", minX=" + + this.minX() + + ", minY=" + + this.minY() + + ", minZ=" + + this.minZ() + + ", maxX=" + + this.maxX() + + ", maxY=" + + this.maxY() + + ", maxZ=" + + this.maxZ() + + ", name='" + + this.name + + "', origin=" + + this.origin + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/BooleanVoxelSpace.java b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/BooleanVoxelSpace.java new file mode 100644 index 0000000..1387d36 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/BooleanVoxelSpace.java @@ -0,0 +1,382 @@ +package com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace; + +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BooleanVoxelSpace implements VoxelSpace { + protected final int sizeX; + protected final int sizeY; + protected final int sizeZ; + @Nonnull + protected final int[][] cells; + protected VoxelCoordinate origin; + private boolean alignedOriginZ; + private int originZOffset; + + public BooleanVoxelSpace(int sizeX, int sizeY, int sizeZ, int originX, int originY, int originZ, boolean alignedOriginZ) { + if (sizeX < 1 || sizeY < 1 || sizeZ < 1) { + throw new IllegalArgumentException("invalid size " + sizeX + " " + sizeY + " " + sizeZ); + } else if (alignedOriginZ && !isAlignedOriginZ(originZ)) { + throw new IllegalArgumentException("unaligned originZ: " + originZ); + } else { + this.sizeX = sizeX; + this.sizeY = sizeY; + this.sizeZ = sizeZ; + this.alignedOriginZ = alignedOriginZ; + int primaryDepth = sizeX * sizeY; + int secondaryDepth = (sizeZ - 1 >> 5) + 1; + if (!alignedOriginZ) { + secondaryDepth++; + } + + this.cells = new int[primaryDepth][secondaryDepth]; + this.origin = new VoxelCoordinate(originX, originY, originZ); + this.setOrigin(originX, originY, originZ); + } + } + + public BooleanVoxelSpace(int sizeX, int sizeY, int sizeZ, int originX, int originY, int originZ) { + this(sizeX, sizeY, sizeZ, originX, originY, originZ, false); + } + + public BooleanVoxelSpace(int sizeX, int sizeY, int sizeZ) { + this(sizeX, sizeY, sizeZ, 0, 0, 0); + } + + public BooleanVoxelSpace(int sizeX, int sizeY, int sizeZ, boolean forceAlignOriginZ) { + this(sizeX, sizeY, sizeZ, 0, 0, 0, forceAlignOriginZ); + } + + @Override + public int sizeX() { + return this.sizeX; + } + + @Override + public int sizeY() { + return this.sizeY; + } + + @Override + public int sizeZ() { + return this.sizeZ; + } + + @Override + public void pasteFrom(@Nonnull VoxelSpace source) { + if (source == null) { + throw new NullPointerException(); + } else { + for (int x = source.minX(); x < source.maxX(); x++) { + for (int y = source.minY(); y < source.maxY(); y++) { + for (int z = source.minZ(); z < source.maxZ(); z++) { + this.set(source.getContent(x, y, z), x, y, z); + } + } + } + } + } + + private int primaryAddressIndex(int x, int y) { + return x * this.sizeY + y; + } + + private int secondaryAddressIndex(int z) { + z += this.originZOffset; + return z >> 5; + } + + private static int setBit(int bits, int index, boolean value) { + int mask = 1 << index; + if (!value) { + bits &= ~mask; + } else { + bits |= mask; + } + + return bits; + } + + private static boolean getBit(int bits, int index) { + return (bits >> index & 1) == 1; + } + + public boolean set(@Nullable Boolean value, int x, int y, int z) { + if (!this.isInsideSpace(x, y, z)) { + return false; + } else { + if (value == null) { + value = false; + } + + int localX = x + this.origin.x; + int localY = y + this.origin.y; + int localZ = z + this.origin.z; + int i = this.primaryAddressIndex(localX, localY); + int j = this.secondaryAddressIndex(localZ); + int bitIndex = localZ - j * 32 + this.originZOffset; + int cell = setBit(this.cells[i][j], bitIndex, value); + this.cells[i][j] = cell; + return true; + } + } + + public boolean set(Boolean content, @Nonnull Vector3i position) { + return this.set(content, position.x, position.y, position.z); + } + + @Nonnull + public Boolean getContent(int x, int y, int z) { + if (!this.isInsideSpace(x, y, z)) { + throw new IndexOutOfBoundsException( + "Coordinates outside VoxelSpace: " + + x + + " " + + y + + " " + + z + + " constraints " + + this.minX() + + " -> " + + this.maxX() + + " " + + this.minY() + + " -> " + + this.maxY() + + " " + + this.minZ() + + " -> " + + this.maxZ() + + "\n" + + this.toString() + ); + } else { + int localX = x + this.origin.x; + int localY = y + this.origin.y; + int localZ = z + this.origin.z; + int i = this.primaryAddressIndex(localX, localY); + int j = this.secondaryAddressIndex(localZ); + int bitIndex = localZ - j * 32 + this.originZOffset; + return getBit(this.cells[i][j], bitIndex); + } + } + + @Nonnull + public Boolean getContent(@Nonnull Vector3i position) { + return this.getContent(position.x, position.y, position.z); + } + + private int globalJ(int globalZ) { + return globalZ >> 5; + } + + private int localJ(int globalJ) { + return globalJ - this.globalJ(-this.origin.z); + } + + public void deepCopyFrom(@Nonnull BooleanVoxelSpace other) { + if (other.cells.length != 0) { + if (other.cells[0].length != 0) { + if (this.cells.length != 0) { + if (this.cells[0].length != 0) { + int thisGlobalJ = this.globalJ(-this.origin.z); + int otherGlobalJ = other.globalJ(-other.origin.z); + int minGlobalJ = Math.max(otherGlobalJ, thisGlobalJ); + int minThisJ = this.localJ(minGlobalJ); + int minOtherJ = other.localJ(minGlobalJ); + int maxIterations = Math.min(other.cells[0].length - minOtherJ, this.cells[0].length - minThisJ); + int minX = Math.max(this.minX(), other.minX()); + int minY = Math.max(this.minY(), other.minY()); + int maxX = Math.min(this.maxX(), other.maxX()); + int maxY = Math.min(this.maxY(), other.maxY()); + + for (int x = minX; x < maxX; x++) { + for (int y = minY; y < maxY; y++) { + int thisLocalX = x + this.origin.x; + int thisLocalY = y + this.origin.y; + int otherLocalX = x + other.origin.x; + int otherLocalY = y + other.origin.y; + int thisI = this.primaryAddressIndex(thisLocalX, thisLocalY); + int otherI = other.primaryAddressIndex(otherLocalX, otherLocalY); + int thisJ = minThisJ; + int otherJ = minOtherJ; + + for (int c = 0; c < maxIterations; c++) { + this.cells[thisI][thisJ] = other.cells[otherI][otherJ]; + otherJ++; + thisJ++; + } + } + } + } + } + } + } + } + + public void set(Boolean content) { + for (int x = this.minX(); x < this.maxX(); x++) { + for (int y = this.minY(); y < this.maxY(); y++) { + for (int z = this.minZ(); z < this.maxZ(); z++) { + this.set(content, x, y, z); + } + } + } + } + + @Override + public void setOrigin(int x, int y, int z) { + if (this.alignedOriginZ && z % 32 != 0) { + throw new IllegalArgumentException("z isn't aligned to 32 bit integer grid: " + z); + } else { + this.origin.x = x; + this.origin.y = y; + this.origin.z = z; + this.originZOffset = -this.origin.z - getAlignedZ(-this.origin.z); + } + } + + public boolean replace(Boolean replacement, int x, int y, int z, @Nonnull Predicate mask) { + if (!this.isInsideSpace(x, y, z)) { + throw new IllegalArgumentException("outside schematic"); + } else if (!mask.test(this.getContent(x, y, z))) { + return false; + } else { + this.set(replacement, x, y, z); + return true; + } + } + + @Nonnull + VoxelCoordinate getOrigin() { + return this.origin.clone(); + } + + @Override + public int getOriginX() { + return this.origin.x; + } + + @Override + public int getOriginY() { + return this.origin.y; + } + + @Override + public int getOriginZ() { + return this.origin.z; + } + + @Nonnull + @Override + public String getName() { + return ""; + } + + @Override + public boolean isInsideSpace(int x, int y, int z) { + return x + this.origin.x >= 0 + && x + this.origin.x < this.sizeX + && y + this.origin.y >= 0 + && y + this.origin.y < this.sizeY + && z + this.origin.z >= 0 + && z + this.origin.z < this.sizeZ; + } + + @Override + public boolean isInsideSpace(@Nonnull Vector3i position) { + return this.isInsideSpace(position.x, position.y, position.z); + } + + @Override + public void forEach(@Nonnull VoxelConsumer action) { + if (action == null) { + throw new NullPointerException(); + } else { + for (int x = this.minX(); x < this.maxX(); x++) { + for (int y = this.minY(); y < this.maxY(); y++) { + for (int z = this.minZ(); z < this.maxZ(); z++) { + action.accept(this.getContent(x, y, z), x, y, z); + } + } + } + } + } + + @Override + public int minX() { + return -this.origin.x; + } + + @Override + public int maxX() { + return this.sizeX - this.origin.x; + } + + @Override + public int minY() { + return -this.origin.y; + } + + @Override + public int maxY() { + return this.sizeY - this.origin.y; + } + + @Override + public int minZ() { + return -this.origin.z; + } + + @Override + public int maxZ() { + return this.sizeZ - this.origin.z; + } + + @Nonnull + public BooleanVoxelSpace clone() { + BooleanVoxelSpace clone = new BooleanVoxelSpace(this.sizeX, this.sizeY, this.sizeZ, this.origin.x, this.origin.y, this.origin.z); + this.forEach(clone::set); + return clone; + } + + private int arrayIndex(int x, int y, int z) { + return y + x * this.sizeY + z * this.sizeY * this.sizeX; + } + + @Nonnull + @Override + public String toString() { + return "ArrayVoxelSpace{sizeX=" + + this.sizeX + + ", sizeY=" + + this.sizeY + + ", sizeZ=" + + this.sizeZ + + ", minX=" + + this.minX() + + ", minY=" + + this.minY() + + ", minZ=" + + this.minZ() + + ", maxX=" + + this.maxX() + + ", maxY=" + + this.maxY() + + ", maxZ=" + + this.maxZ() + + ", origin=" + + this.origin + + "}"; + } + + public static boolean isAlignedOriginZ(int z) { + return z % 32 == 0; + } + + public static int getAlignedZ(int z) { + return z >> 5 << 5; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/NullSpace.java b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/NullSpace.java new file mode 100644 index 0000000..fa676f3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/NullSpace.java @@ -0,0 +1,140 @@ +package com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace; + +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NullSpace implements VoxelSpace { + private static final NullSpace INSTANCE = new NullSpace(); + + public static NullSpace instance() { + return INSTANCE; + } + + public static NullSpace instance(@Nonnull Class clazz) { + return INSTANCE; + } + + private NullSpace() { + } + + @Override + public boolean set(V content, int x, int y, int z) { + return false; + } + + @Override + public boolean set(V content, @Nonnull Vector3i position) { + return this.set(content, position.x, position.y, position.z); + } + + @Override + public void set(V content) { + } + + @Override + public void setOrigin(int x, int y, int z) { + } + + @Nullable + @Override + public V getContent(int x, int y, int z) { + return null; + } + + @Nullable + @Override + public V getContent(@Nonnull Vector3i position) { + return this.getContent(position.x, position.y, position.z); + } + + @Override + public boolean replace(V replacement, int x, int y, int z, @Nonnull Predicate mask) { + return false; + } + + @Override + public void pasteFrom(@Nonnull VoxelSpace source) { + } + + @Override + public int getOriginX() { + return 0; + } + + @Override + public int getOriginY() { + return 0; + } + + @Override + public int getOriginZ() { + return 0; + } + + @Nonnull + @Override + public String getName() { + return "null_space"; + } + + @Override + public boolean isInsideSpace(int x, int y, int z) { + return false; + } + + @Override + public boolean isInsideSpace(@Nonnull Vector3i position) { + return this.isInsideSpace(position.x, position.y, position.z); + } + + @Override + public void forEach(VoxelConsumer action) { + } + + @Override + public int minX() { + return 0; + } + + @Override + public int maxX() { + return 0; + } + + @Override + public int minY() { + return 0; + } + + @Override + public int maxY() { + return 0; + } + + @Override + public int minZ() { + return 0; + } + + @Override + public int maxZ() { + return 0; + } + + @Override + public int sizeX() { + return 0; + } + + @Override + public int sizeY() { + return 0; + } + + @Override + public int sizeZ() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/VoxelConsumer.java b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/VoxelConsumer.java new file mode 100644 index 0000000..62b78a8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/VoxelConsumer.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace; + +@FunctionalInterface +public interface VoxelConsumer { + void accept(V var1, int var2, int var3, int var4); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/VoxelCoordinate.java b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/VoxelCoordinate.java new file mode 100644 index 0000000..6067d64 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/VoxelCoordinate.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace; + +import javax.annotation.Nonnull; + +public class VoxelCoordinate { + int x; + int y; + int z; + + public VoxelCoordinate(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public boolean equals(Object other) { + return !(other instanceof VoxelCoordinate otherVoxelCoordinate) + ? false + : this == otherVoxelCoordinate || this.x == otherVoxelCoordinate.x && this.y == otherVoxelCoordinate.y && this.z == otherVoxelCoordinate.z; + } + + @Nonnull + public VoxelCoordinate clone() { + return new VoxelCoordinate(this.x, this.y, this.z); + } + + @Nonnull + @Override + public String toString() { + return "VoxelCoordinate{x=" + this.x + ", y=" + this.y + ", z=" + this.z + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/VoxelSpace.java b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/VoxelSpace.java new file mode 100644 index 0000000..8a38781 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/VoxelSpace.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface VoxelSpace { + boolean set(T var1, int var2, int var3, int var4); + + boolean set(T var1, @Nonnull Vector3i var2); + + void set(T var1); + + void setOrigin(int var1, int var2, int var3); + + @Nullable + T getContent(int var1, int var2, int var3); + + @Nullable + T getContent(@Nonnull Vector3i var1); + + boolean replace(T var1, int var2, int var3, int var4, @Nonnull Predicate var5); + + void pasteFrom(@Nonnull VoxelSpace var1); + + int getOriginX(); + + int getOriginY(); + + int getOriginZ(); + + String getName(); + + boolean isInsideSpace(int var1, int var2, int var3); + + boolean isInsideSpace(@Nonnull Vector3i var1); + + void forEach(VoxelConsumer var1); + + @Nonnull + default Bounds3i getBounds() { + return new Bounds3i(new Vector3i(this.minX(), this.minY(), this.minZ()), new Vector3i(this.maxX(), this.maxY(), this.maxZ())); + } + + int minX(); + + int maxX(); + + int minY(); + + int maxY(); + + int minZ(); + + int maxZ(); + + int sizeX(); + + int sizeY(); + + int sizeZ(); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/VoxelSpaceUtil.java b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/VoxelSpaceUtil.java new file mode 100644 index 0000000..957d0af --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/VoxelSpaceUtil.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace; + +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.common.util.ExceptionUtil; +import java.util.LinkedList; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import javax.annotation.Nonnull; + +public class VoxelSpaceUtil { + public VoxelSpaceUtil() { + } + + public static void parallelCopy(@Nonnull VoxelSpace source, @Nonnull VoxelSpace destination, int concurrency) { + if (concurrency < 1) { + throw new IllegalArgumentException("negative concurrency"); + } else { + int minX = source.minX(); + int minY = source.minY(); + int minZ = source.minZ(); + int sizeX = source.sizeX(); + int sizeY = source.sizeY(); + int sizeZ = source.sizeZ(); + LinkedList> tasks = new LinkedList<>(); + int bSize = source.sizeX() * source.sizeY() * source.sizeZ() / concurrency; + + for (int b = 0; b < concurrency; b++) { + tasks.add(CompletableFuture.runAsync(() -> { + for (int i = b * bSize; i < (b + 1) * bSize; i++) { + int x = i % sizeX + minX; + int y = i / sizeX % sizeY + minY; + int z = i / (sizeX * sizeY) % sizeZ + minZ; + if (source.isInsideSpace(x, y, z) && destination.isInsideSpace(x, y, z)) { + destination.set(source.getContent(x, y, z), x, y, z); + } + } + }).handle((r, ex) -> { + if (ex == null) { + return (Void)r; + } else { + LoggerUtil.logException("a VoxelSpace async process", ex, LoggerUtil.getLogger()); + return null; + } + })); + } + + try { + while (!tasks.isEmpty()) { + tasks.removeFirst().get(); + } + } catch (ExecutionException | InterruptedException var13) { + Thread.currentThread().interrupt(); + String msg = "Exception thrown by HytaleGenerator while attempting an asynchronous copy of a VoxelSpace:\n"; + msg = msg + ExceptionUtil.toStringWithStack(var13); + LoggerUtil.getLogger().severe(msg); + } + } + } + + private static class BatchTransfer implements Runnable { + private final VoxelSpace source; + private final VoxelSpace destination; + private final int minX; + private final int minY; + private final int minZ; + private final int maxX; + private final int maxY; + private final int maxZ; + + private BatchTransfer(VoxelSpace source, VoxelSpace destination, int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { + this.source = source; + this.destination = destination; + this.minX = minX; + this.minY = minY; + this.minZ = minZ; + this.maxX = maxX; + this.maxY = maxY; + this.maxZ = maxZ; + } + + @Override + public void run() { + try { + for (int x = this.minX; x < this.maxX; x++) { + for (int y = this.minY; y < this.maxY; y++) { + for (int z = this.minZ; z < this.maxZ; z++) { + if (this.destination.isInsideSpace(x, y, z)) { + this.destination.set(this.source.getContent(x, y, z), x, y, z); + } + } + } + } + } catch (Exception var4) { + String msg = "Exception thrown by HytaleGenerator while attempting a BatchTransfer operation:\n"; + msg = msg + ExceptionUtil.toStringWithStack(var4); + LoggerUtil.getLogger().severe(msg); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/WindowVoxelSpace.java b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/WindowVoxelSpace.java new file mode 100644 index 0000000..24ad65a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/datastructures/voxelspace/WindowVoxelSpace.java @@ -0,0 +1,210 @@ +package com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace; + +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WindowVoxelSpace implements VoxelSpace { + @Nonnull + private final VoxelSpace wrappedVoxelSpace; + @Nonnull + private final VoxelCoordinate min; + @Nonnull + private final VoxelCoordinate max; + + public WindowVoxelSpace(@Nonnull VoxelSpace voxelSpace) { + this.wrappedVoxelSpace = voxelSpace; + this.min = new VoxelCoordinate(voxelSpace.minX(), voxelSpace.minY(), voxelSpace.minZ()); + this.max = new VoxelCoordinate(voxelSpace.maxX(), voxelSpace.maxY(), voxelSpace.maxZ()); + } + + @Nonnull + public WindowVoxelSpace setWindow(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { + if (minX >= this.wrappedVoxelSpace.minX() + && minY >= this.wrappedVoxelSpace.minY() + && minZ >= this.wrappedVoxelSpace.minZ() + && maxX >= minX + && maxY >= minY + && maxZ >= minZ + && maxX <= this.wrappedVoxelSpace.maxX() + && maxY <= this.wrappedVoxelSpace.maxY() + && maxZ <= this.wrappedVoxelSpace.maxZ()) { + this.min.x = minX; + this.min.y = minY; + this.min.z = minZ; + this.max.x = maxX; + this.max.y = maxY; + this.max.z = maxZ; + return this; + } else { + throw new IllegalArgumentException("invalid values"); + } + } + + @Nonnull + public VoxelSpace getWrappedSchematic() { + return this.wrappedVoxelSpace; + } + + @Override + public boolean set(T content, int x, int y, int z) { + if (!this.isInsideSpace(x, y, z)) { + return false; + } else { + return !this.wrappedVoxelSpace.isInsideSpace(x, y, z) ? false : this.wrappedVoxelSpace.set(content, x, y, z); + } + } + + @Override + public boolean set(T content, @Nonnull Vector3i position) { + return this.set(content, position.x, position.y, position.z); + } + + @Override + public void set(T content) { + for (int x = this.minX(); x < this.maxX(); x++) { + for (int y = this.minY(); y < this.maxY(); y++) { + for (int z = this.minZ(); z < this.maxZ(); z++) { + this.set(content, x, y, z); + } + } + } + } + + @Override + public void setOrigin(int x, int y, int z) { + throw new UnsupportedOperationException("can't set origin of window"); + } + + @Override + public T getContent(int x, int y, int z) { + if (!this.isInsideSpace(x, y, z)) { + throw new IllegalArgumentException("outside schematic"); + } else { + return this.wrappedVoxelSpace.getContent(x, y, z); + } + } + + @Nullable + @Override + public T getContent(@Nonnull Vector3i position) { + return this.getContent(position.x, position.y, position.z); + } + + @Override + public boolean replace(T replacement, int x, int y, int z, @Nonnull Predicate mask) { + if (!this.isInsideSpace(x, y, z)) { + throw new IllegalArgumentException("outside schematic"); + } else { + return this.wrappedVoxelSpace.replace(replacement, x, y, z, mask); + } + } + + @Override + public void pasteFrom(@Nonnull VoxelSpace source) { + for (int x = source.minX(); x < source.maxX(); x++) { + for (int y = source.minY(); y < source.maxY(); y++) { + for (int z = source.minZ(); z < source.maxZ(); z++) { + this.set(source.getContent(x, y, z), x, y, z); + } + } + } + } + + @Override + public int getOriginX() { + int offset = this.min.x - this.wrappedVoxelSpace.minX(); + return this.wrappedVoxelSpace.getOriginX() - offset; + } + + @Override + public int getOriginY() { + int offset = this.min.y - this.wrappedVoxelSpace.minY(); + return this.wrappedVoxelSpace.getOriginY() - offset; + } + + @Override + public int getOriginZ() { + int offset = this.min.z - this.wrappedVoxelSpace.minZ(); + return this.wrappedVoxelSpace.getOriginZ() - offset; + } + + @Nonnull + @Override + public String getName() { + return "window_to_" + this.wrappedVoxelSpace.getName(); + } + + @Override + public boolean isInsideSpace(int x, int y, int z) { + return x >= this.minX() && x < this.maxX() && y >= this.minY() && y < this.maxY() && z >= this.minZ() && z < this.maxZ(); + } + + @Override + public boolean isInsideSpace(@Nonnull Vector3i position) { + return this.isInsideSpace(position.x, position.y, position.z); + } + + @Override + public void forEach(@Nonnull VoxelConsumer action) { + for (int x = this.minX(); x < this.maxX(); x++) { + for (int y = this.minY(); y < this.maxY(); y++) { + for (int z = this.minZ(); z < this.maxZ(); z++) { + action.accept(this.getContent(x, y, z), x, y, z); + } + } + } + } + + @Override + public int minX() { + return this.min.x; + } + + @Override + public int maxX() { + return this.max.x; + } + + @Override + public int minY() { + return this.min.y; + } + + @Override + public int maxY() { + return this.max.y; + } + + @Override + public int minZ() { + return this.min.z; + } + + @Override + public int maxZ() { + return this.max.z; + } + + @Override + public int sizeX() { + return this.max.x - this.min.x; + } + + @Override + public int sizeY() { + return this.max.y - this.min.y; + } + + @Override + public int sizeZ() { + return this.max.z - this.min.z; + } + + @Nonnull + @Override + public String toString() { + return "WindowVoxelSpace{wrappedVoxelSpace=" + this.wrappedVoxelSpace + ", min=" + this.min + ", max=" + this.max + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/delimiters/DelimiterDouble.java b/src/com/hypixel/hytale/builtin/hytalegenerator/delimiters/DelimiterDouble.java new file mode 100644 index 0000000..ecb1d28 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/delimiters/DelimiterDouble.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.builtin.hytalegenerator.delimiters; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DelimiterDouble { + @Nonnull + private final RangeDouble range; + @Nullable + private final V value; + + public DelimiterDouble(@Nonnull RangeDouble range, @Nullable V value) { + this.range = range; + this.value = value; + } + + @Nonnull + public RangeDouble getRange() { + return this.range; + } + + @Nullable + public V getValue() { + return this.value; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/delimiters/DelimiterInt.java b/src/com/hypixel/hytale/builtin/hytalegenerator/delimiters/DelimiterInt.java new file mode 100644 index 0000000..d48e94e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/delimiters/DelimiterInt.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.builtin.hytalegenerator.delimiters; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DelimiterInt { + @Nonnull + private final RangeInt range; + @Nullable + private final V value; + + public DelimiterInt(@Nonnull RangeInt range, @Nullable V value) { + this.range = range; + this.value = value; + } + + @Nonnull + public RangeInt getRange() { + return this.range; + } + + @Nullable + public V getValue() { + return this.value; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/delimiters/RangeDouble.java b/src/com/hypixel/hytale/builtin/hytalegenerator/delimiters/RangeDouble.java new file mode 100644 index 0000000..af58085 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/delimiters/RangeDouble.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.builtin.hytalegenerator.delimiters; + +public class RangeDouble { + private final double minInclusive; + private final double maxExclusive; + + public RangeDouble(double minInclusive, double maxExclusive) { + this.minInclusive = minInclusive; + this.maxExclusive = maxExclusive; + } + + public boolean contains(double value) { + return this.minInclusive <= value && this.maxExclusive > value; + } + + public double min() { + return this.minInclusive; + } + + public double max() { + return this.maxExclusive; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/delimiters/RangeInt.java b/src/com/hypixel/hytale/builtin/hytalegenerator/delimiters/RangeInt.java new file mode 100644 index 0000000..56af3e7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/delimiters/RangeInt.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.builtin.hytalegenerator.delimiters; + +public class RangeInt { + private final int minInclusive; + private final int maxExclusive; + + public RangeInt(int minInclusive, int maxExclusive) { + this.minInclusive = minInclusive; + this.maxExclusive = maxExclusive; + } + + public boolean contains(int value) { + return this.minInclusive <= value && this.maxExclusive > value; + } + + public int getMinInclusive() { + return this.minInclusive; + } + + public int getMaxExclusive() { + return this.maxExclusive; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/Density.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/Density.java new file mode 100644 index 0000000..c0cb76d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/Density.java @@ -0,0 +1,112 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.environmentproviders.EnvironmentProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.TerrainDensityProvider; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.builtin.hytalegenerator.tintproviders.TintProvider; +import com.hypixel.hytale.builtin.hytalegenerator.vectorproviders.VectorProvider; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class Density { + private static final Bounds3i DEFAULT_READ_BOUNDS = new Bounds3i(); + public static final double DEFAULT_VALUE = Double.MAX_VALUE; + public static final double DEFAULT_DENSITY = 0.0; + + public Density() { + } + + public abstract double process(@Nonnull Density.Context var1); + + public void setInputs(Density[] inputs) { + } + + public static class Context { + @Nonnull + public Vector3d position; + @Nonnull + public WorkerIndexer.Id workerId; + @Nullable + public Vector3d densityAnchor; + @Nullable + public Vector3d positionsAnchor; + public int switchState; + public double distanceFromCellWall; + @Nullable + public TerrainDensityProvider terrainDensityProvider; + public double distanceToBiomeEdge; + + public Context() { + this.position = new Vector3d(); + this.workerId = WorkerIndexer.Id.UNKNOWN; + this.densityAnchor = null; + this.positionsAnchor = null; + this.switchState = 0; + this.distanceFromCellWall = Double.MAX_VALUE; + this.terrainDensityProvider = null; + this.distanceToBiomeEdge = Double.MAX_VALUE; + } + + public Context( + @Nonnull Vector3d position, + @Nonnull WorkerIndexer.Id workerId, + @Nullable Vector3d densityAnchor, + int switchState, + double distanceFromCellWall, + @Nullable TerrainDensityProvider terrainDensityProvider, + double distanceToBiomeEdge + ) { + this.position = position; + this.workerId = workerId; + this.densityAnchor = densityAnchor; + this.switchState = switchState; + this.distanceFromCellWall = distanceFromCellWall; + this.positionsAnchor = null; + this.terrainDensityProvider = terrainDensityProvider; + this.distanceToBiomeEdge = distanceToBiomeEdge; + } + + public Context(@Nonnull Density.Context other) { + this.position = other.position; + this.densityAnchor = other.densityAnchor; + this.switchState = other.switchState; + this.distanceFromCellWall = other.distanceFromCellWall; + this.positionsAnchor = other.positionsAnchor; + this.workerId = other.workerId; + this.terrainDensityProvider = other.terrainDensityProvider; + this.distanceToBiomeEdge = other.distanceToBiomeEdge; + } + + public Context(@Nonnull VectorProvider.Context context) { + this.position = context.position; + this.workerId = context.workerId; + this.terrainDensityProvider = context.terrainDensityProvider; + } + + public Context(@Nonnull TintProvider.Context context) { + this.position = context.position.toVector3d(); + this.workerId = context.workerId; + } + + public Context(@Nonnull EnvironmentProvider.Context context) { + this.position = context.position.toVector3d(); + this.workerId = context.workerId; + } + + public Context(@Nonnull MaterialProvider.Context context) { + this.position = context.position.toVector3d(); + this.workerId = context.workerId; + this.terrainDensityProvider = context.terrainDensityProvider; + this.distanceToBiomeEdge = context.distanceToBiomeEdge; + } + + public Context(@Nonnull Pattern.Context context) { + this.position = context.position.toVector3d(); + this.workerId = context.workerId; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AbsDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AbsDensity.java new file mode 100644 index 0000000..27a6fc6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AbsDensity.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AbsDensity extends Density { + @Nullable + private Density input; + + public AbsDensity(Density input) { + this.input = input; + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.input == null ? 0.0 : Math.abs(this.input.process(context)); + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AmplitudeConstantDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AmplitudeConstantDensity.java new file mode 100644 index 0000000..5a6b17b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AmplitudeConstantDensity.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AmplitudeConstantDensity extends Density { + private final double amplitude; + @Nullable + private Density input; + + public AmplitudeConstantDensity(double amplitude, Density input) { + this.amplitude = amplitude; + this.input = input; + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.input == null ? 0.0 : this.input.process(context) * this.amplitude; + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AmplitudeDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AmplitudeDensity.java new file mode 100644 index 0000000..5ad90af --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AmplitudeDensity.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.NodeFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AmplitudeDensity extends Density { + public static final double ZERO_DELTA = 1.0E-9; + private NodeFunction amplitudeFunc; + @Nullable + private Density input; + + public AmplitudeDensity(@Nonnull NodeFunction offsetFunction, Density input) { + this.amplitudeFunc = offsetFunction; + this.input = input; + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.input != null && !this.skipInputs(context.position.y) ? this.input.process(context) * this.amplitudeFunc.get(context.position.y) : 0.0; + } + + public boolean skipInputs(double y) { + double v = this.amplitudeFunc.get(y); + return v < 1.0E-9 && v > -1.0E-9; + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AnchorDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AnchorDensity.java new file mode 100644 index 0000000..b48dc56 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AnchorDensity.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AnchorDensity extends Density { + @Nullable + private Density input; + private final boolean isReversed; + + public AnchorDensity(Density input, boolean isReversed) { + this.input = input; + this.isReversed = isReversed; + } + + @Override + public double process(@Nonnull Density.Context context) { + Vector3d anchor = context.densityAnchor; + if (anchor == null) { + return this.input.process(context); + } else if (this.isReversed) { + Vector3d childPosition = new Vector3d(context.position.x + anchor.x, context.position.y + anchor.y, context.position.z + anchor.z); + Density.Context childContext = new Density.Context(context); + childContext.position = childPosition; + return this.input.process(childContext); + } else { + Vector3d childPosition = new Vector3d(context.position.x - anchor.x, context.position.y - anchor.y, context.position.z - anchor.z); + Density.Context childContext = new Density.Context(context); + childContext.position = childPosition; + return this.input.process(childContext); + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AngleDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AngleDensity.java new file mode 100644 index 0000000..1228f52 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AngleDensity.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.vectorproviders.VectorProvider; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class AngleDensity extends Density { + private static final double HALF_PI = Math.PI / 2; + @Nonnull + private VectorProvider vectorProvider; + @Nonnull + private final Vector3d vector; + private final boolean toAxis; + + public AngleDensity(@Nonnull VectorProvider vectorProvider, @Nonnull Vector3d vector, boolean toAxis) { + this.vector = vector.clone(); + this.vectorProvider = vectorProvider; + this.toAxis = toAxis; + } + + @Override + public double process(@Nonnull Density.Context context) { + Vector3d otherVector = this.vectorProvider.process(new VectorProvider.Context(context)); + double slopeAngle = VectorUtil.angle(this.vector, otherVector); + if (this.toAxis && slopeAngle > Math.PI / 2) { + slopeAngle = Math.PI - slopeAngle; + } + + slopeAngle /= Math.PI; + return slopeAngle * 180.0; + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AxisDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AxisDensity.java new file mode 100644 index 0000000..a241a1b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/AxisDensity.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class AxisDensity extends Density { + public static final double ZERO_DELTA = 1.0E-9; + private static final Vector3d ZERO_VECTOR = new Vector3d(); + @Nonnull + private final Double2DoubleFunction distanceCurve; + @Nonnull + private final Vector3d axis; + private final boolean isAnchored; + + public AxisDensity(@Nonnull Double2DoubleFunction distanceCurve, @Nonnull Vector3d axis, boolean isAnchored) { + this.distanceCurve = distanceCurve; + this.axis = axis; + this.isAnchored = isAnchored; + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.axis.length() == 0.0) { + return 0.0; + } else if (this.isAnchored) { + return this.processAnchored(context); + } else { + double distance = VectorUtil.distanceToLine3d(context.position, ZERO_VECTOR, this.axis); + return this.distanceCurve.get(distance); + } + } + + private double processAnchored(@Nonnull Density.Context context) { + if (context == null) { + return 0.0; + } else { + Vector3d anchor = context.densityAnchor; + if (anchor == null) { + return 0.0; + } else { + Vector3d position = context.position.clone().subtract(anchor); + double distance = VectorUtil.distanceToLine3d(position, ZERO_VECTOR, this.axis); + return this.distanceCurve.get(distance); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/BaseHeightDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/BaseHeightDensity.java new file mode 100644 index 0000000..bab58a8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/BaseHeightDensity.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.BiDouble2DoubleFunction; +import javax.annotation.Nonnull; + +public class BaseHeightDensity extends Density { + @Nonnull + private final BiDouble2DoubleFunction heightFunction; + private final boolean isDistance; + + public BaseHeightDensity(@Nonnull BiDouble2DoubleFunction heightFunction, boolean isDistance) { + this.heightFunction = heightFunction; + this.isDistance = isDistance; + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.isDistance + ? context.position.y - this.heightFunction.apply(context.position.x, context.position.z) + : this.heightFunction.apply(context.position.x, context.position.z); + } + + public boolean skipInputs(double y) { + return true; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CacheDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CacheDensity.java new file mode 100644 index 0000000..a77bc2d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CacheDensity.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class CacheDensity extends Density { + private final WorkerIndexer.Data threadData; + @Nonnull + private Density input; + + public CacheDensity(@Nonnull Density input, int threadCount) { + this.input = input; + this.threadData = new WorkerIndexer.Data<>(threadCount, CacheDensity.Cache::new); + } + + @Override + public double process(@Nonnull Density.Context context) { + CacheDensity.Cache cache = this.threadData.get(context.workerId); + if (cache.position != null && cache.position.x == context.position.x && cache.position.y == context.position.y && cache.position.z == context.position.z) { + return cache.value; + } else { + if (cache.position == null) { + cache.position = new Vector3d(); + } + + cache.position.assign(context.position); + cache.value = this.input.process(context); + return cache.value; + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + assert inputs.length != 0; + + assert inputs[0] != null; + + this.input = inputs[0]; + } + + private static class Cache { + Vector3d position; + double value; + + private Cache() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CeilingDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CeilingDensity.java new file mode 100644 index 0000000..d949292 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CeilingDensity.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CeilingDensity extends Density { + private double limit; + @Nullable + private Density input; + + public CeilingDensity(double limit, Density input) { + this.limit = limit; + this.input = input; + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.input == null ? 0.0 : Math.min(this.input.process(context), this.limit); + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CellWallDistanceDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CellWallDistanceDensity.java new file mode 100644 index 0000000..380a083 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CellWallDistanceDensity.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; + +public class CellWallDistanceDensity extends Density { + public CellWallDistanceDensity() { + } + + @Override + public double process(@Nonnull Density.Context context) { + return context.distanceFromCellWall; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ClampDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ClampDensity.java new file mode 100644 index 0000000..cf86b3f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ClampDensity.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ClampDensity extends Density { + private final double wallA; + private final double wallB; + @Nullable + private Density input; + + public ClampDensity(double wallA, double wallB, Density input) { + this.wallA = wallA; + this.wallB = wallB; + this.input = input; + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.input == null ? 0.0 : Calculator.clamp(this.wallA, this.input.process(context), this.wallB); + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ConstantValueDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ConstantValueDensity.java new file mode 100644 index 0000000..09ed3ec --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ConstantValueDensity.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; + +public class ConstantValueDensity extends Density { + private final double value; + + public ConstantValueDensity(double value) { + this.value = value; + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.value; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CubeDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CubeDensity.java new file mode 100644 index 0000000..5578832 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CubeDensity.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class CubeDensity extends Density { + @Nonnull + private final Double2DoubleFunction falloffFunction; + + public CubeDensity(@Nonnull Double2DoubleFunction falloffFunction) { + this.falloffFunction = falloffFunction; + } + + @Override + public double process(@Nonnull Density.Context context) { + double distance = Math.max(Math.abs(context.position.x), Math.abs(context.position.y)); + distance = Math.max(distance, Math.abs(context.position.z)); + return this.falloffFunction.get(distance); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CurveMapperDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CurveMapperDensity.java new file mode 100644 index 0000000..24fb5a8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CurveMapperDensity.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CurveMapperDensity extends Density { + @Nonnull + private final Double2DoubleFunction curveFunction; + @Nullable + private Density input; + + public CurveMapperDensity(@Nonnull Double2DoubleFunction curveFunction, Density input) { + this.curveFunction = curveFunction; + this.input = input; + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.input == null ? this.curveFunction.get(0.0) : this.curveFunction.applyAsDouble(this.input.process(context)); + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CylinderDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CylinderDensity.java new file mode 100644 index 0000000..8ccfa9e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/CylinderDensity.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class CylinderDensity extends Density { + @Nonnull + private final Double2DoubleFunction radialCurve; + @Nonnull + private final Double2DoubleFunction axialCurve; + + public CylinderDensity(@Nonnull Double2DoubleFunction radialCurve, @Nonnull Double2DoubleFunction axialCurve) { + this.radialCurve = radialCurve; + this.axialCurve = axialCurve; + } + + @Override + public double process(@Nonnull Density.Context context) { + double radialDistance = Calculator.distance(context.position.x, context.position.z, 0.0, 0.0); + return this.axialCurve.applyAsDouble(context.position.y) * this.radialCurve.applyAsDouble(radialDistance); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/DistanceDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/DistanceDensity.java new file mode 100644 index 0000000..387407b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/DistanceDensity.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class DistanceDensity extends Density { + public static final double ZERO_DELTA = 1.0E-9; + @Nonnull + private final Double2DoubleFunction falloffFunction; + + public DistanceDensity(@Nonnull Double2DoubleFunction falloffFunction) { + this.falloffFunction = falloffFunction; + } + + @Override + public double process(@Nonnull Density.Context context) { + double distance = Calculator.distance(context.position.x, context.position.y, context.position.z, 0.0, 0.0, 0.0); + return this.falloffFunction.get(distance); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/DistanceToBiomeEdgeDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/DistanceToBiomeEdgeDensity.java new file mode 100644 index 0000000..20a6198 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/DistanceToBiomeEdgeDensity.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; + +public class DistanceToBiomeEdgeDensity extends Density { + public DistanceToBiomeEdgeDensity() { + } + + @Override + public double process(@Nonnull Density.Context context) { + return context.distanceToBiomeEdge; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/FastGradientWarpDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/FastGradientWarpDensity.java new file mode 100644 index 0000000..9ba1925 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/FastGradientWarpDensity.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.fields.FastNoiseLite; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FastGradientWarpDensity extends Density { + private static final double HALF_PI = Math.PI / 2; + @Nullable + private Density input; + private final double warpScale; + @Nonnull + private final FastNoiseLite warper; + + public FastGradientWarpDensity( + @Nonnull Density input, float warpLacunarity, float warpPersistence, int warpOctaves, float warpScale, float warpFactor, int seed + ) { + if (warpOctaves < 0.0) { + throw new IllegalArgumentException(); + } else { + this.warpScale = warpScale; + this.input = input; + this.warper = new FastNoiseLite(); + this.warper.setSeed(seed); + this.warper.SetFractalGain(warpPersistence); + this.warper.SetFractalLacunarity(warpLacunarity); + this.warper.setDomainWarpType(FastNoiseLite.DomainWarpType.OpenSimplex2); + this.warper.setFractalOctaves(warpOctaves); + this.warper.setDomainWarpAmp(warpFactor); + this.warper.setDomainWarpFreq(warpScale); + } + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.input == null) { + return 0.0; + } else { + FastNoiseLite.Vector3 warpedPosition = new FastNoiseLite.Vector3(context.position.x, context.position.y, context.position.z); + this.warper.DomainWarpFractalProgressive(warpedPosition); + Density.Context childContext = new Density.Context(context); + childContext.position = new Vector3d(warpedPosition.x, warpedPosition.y, warpedPosition.z); + return this.input.process(childContext); + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 1) { + this.input = inputs[0]; + } else { + this.input = null; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/FloorDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/FloorDensity.java new file mode 100644 index 0000000..bdea0b7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/FloorDensity.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FloorDensity extends Density { + private double limit; + @Nullable + private Density input; + + public FloorDensity(double limit, Density input) { + this.limit = limit; + this.input = input; + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.input == null ? 0.0 : Math.max(this.input.process(context), this.limit); + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/FunctionDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/FunctionDensity.java new file mode 100644 index 0000000..f258948 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/FunctionDensity.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FunctionDensity extends Density { + @Nonnull + private final Double2DoubleFunction function; + @Nullable + private Density input; + + public FunctionDensity(@Nonnull Double2DoubleFunction function, Density input) { + this.function = function; + this.input = input; + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.input == null ? 0.0 : this.function.get(this.input.process(context)); + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/GradientDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/GradientDensity.java new file mode 100644 index 0000000..0042391 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/GradientDensity.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class GradientDensity extends Density { + private static final double HALF_PI = Math.PI / 2; + @Nullable + private Density input; + private final double slopeRange; + @Nonnull + private final Vector3d axis; + + public GradientDensity(@Nonnull Density input, double slopeRange, @Nonnull Vector3d axis) { + if (slopeRange <= 0.0) { + throw new IllegalArgumentException(); + } else { + this.axis = axis.clone(); + this.slopeRange = slopeRange; + this.input = input; + } + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.input == null) { + return 0.0; + } else { + double valueAtOrigin = this.input.process(context); + double maxX = context.position.x + this.slopeRange; + double maxY = context.position.y + this.slopeRange; + double maxZ = context.position.z + this.slopeRange; + Density.Context childContext = new Density.Context(context); + childContext.position = new Vector3d(maxX, context.position.y, context.position.z); + double deltaX = Math.abs(this.input.process(childContext) - valueAtOrigin); + childContext.position = new Vector3d(context.position.x, maxY, context.position.z); + double deltaY = Math.abs(this.input.process(childContext) - valueAtOrigin); + childContext.position = new Vector3d(context.position.x, context.position.y, maxZ); + double deltaZ = Math.abs(this.input.process(childContext) - valueAtOrigin); + Vector3d slopeDirection = new Vector3d(deltaX, deltaY, deltaZ); + double slopeAngle = VectorUtil.angle(this.axis, slopeDirection); + if (slopeAngle > Math.PI / 2) { + slopeAngle = Math.PI - slopeAngle; + } + + slopeAngle /= Math.PI / 2; + return slopeAngle * 90.0; + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/GradientWarpDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/GradientWarpDensity.java new file mode 100644 index 0000000..fd033fe --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/GradientWarpDensity.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class GradientWarpDensity extends Density { + private static final double HALF_PI = Math.PI / 2; + @Nullable + private Density input; + @Nullable + private Density warpInput; + private final double slopeRange; + private final double warpFactor; + + public GradientWarpDensity(@Nonnull Density input, @Nonnull Density warpInput, double slopeRange, double warpFactor) { + if (slopeRange <= 0.0) { + throw new IllegalArgumentException(); + } else { + this.slopeRange = slopeRange; + this.warpFactor = warpFactor; + this.input = input; + this.warpInput = warpInput; + } + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.input == null) { + return 0.0; + } else if (this.warpInput == null) { + return this.input.process(context); + } else { + double valueAtOrigin = this.warpInput.process(context); + double maxX = context.position.x + this.slopeRange; + double maxY = context.position.y + this.slopeRange; + double maxZ = context.position.z + this.slopeRange; + Density.Context childContext = new Density.Context(context); + childContext.position = new Vector3d(maxX, context.position.y, context.position.z); + double deltaX = this.warpInput.process(childContext) - valueAtOrigin; + childContext.position = new Vector3d(context.position.x, maxY, context.position.z); + double deltaY = this.warpInput.process(childContext) - valueAtOrigin; + childContext.position = new Vector3d(context.position.x, context.position.z, maxZ); + double deltaZ = this.warpInput.process(childContext) - valueAtOrigin; + Vector3d gradient = new Vector3d(deltaX, deltaY, deltaZ); + gradient.scale(1.0 / this.slopeRange); + gradient.scale(this.warpFactor); + gradient.add(context.position.x, context.position.y, context.position.z); + childContext.position = gradient; + return this.input.process(childContext); + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + if (inputs.length < 2) { + this.warpInput = null; + } + + this.warpInput = inputs[1]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/InverterDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/InverterDensity.java new file mode 100644 index 0000000..d95b5d5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/InverterDensity.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InverterDensity extends Density { + @Nullable + private Density input; + + public InverterDensity(Density input) { + this.input = input; + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.input == null ? 0.0 : -this.input.process(context); + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MaxDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MaxDensity.java new file mode 100644 index 0000000..7b21554 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MaxDensity.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import java.util.List; +import javax.annotation.Nonnull; + +public class MaxDensity extends Density { + public Density[] inputs; + + public MaxDensity(@Nonnull List inputs) { + this.inputs = new Density[inputs.size()]; + inputs.toArray(this.inputs); + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.inputs.length == 0) { + return 0.0; + } else { + double max = Double.NEGATIVE_INFINITY; + + for (Density input : this.inputs) { + double value = input.process(context); + if (max < value) { + max = value; + } + } + + return max; + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + this.inputs = inputs; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MinDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MinDensity.java new file mode 100644 index 0000000..503982a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MinDensity.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import java.util.List; +import javax.annotation.Nonnull; + +public class MinDensity extends Density { + private Density[] inputs; + + public MinDensity(@Nonnull List inputs) { + this.inputs = new Density[inputs.size()]; + inputs.toArray(this.inputs); + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.inputs.length == 0) { + return 0.0; + } else { + double min = Double.POSITIVE_INFINITY; + + for (Density input : this.inputs) { + double value = input.process(context); + if (min > value) { + min = value; + } + } + + return min; + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + this.inputs = inputs; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MixDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MixDensity.java new file mode 100644 index 0000000..ee64f32 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MixDensity.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; + +public class MixDensity extends Density { + private Density densityA; + private Density densityB; + private Density influenceDensity; + + public MixDensity(@Nonnull Density densityA, @Nonnull Density densityB, @Nonnull Density influenceDensity) { + this.densityA = densityA; + this.densityB = densityB; + this.influenceDensity = influenceDensity; + } + + @Override + public double process(@Nonnull Density.Context context) { + double THRESHOLD_INPUT_A = 0.0; + double THRESHOLD_INPUT_B = 1.0; + double influence = this.influenceDensity.process(context); + if (influence <= 0.0) { + return this.densityA.process(context); + } else if (influence >= 1.0) { + return this.densityB.process(context); + } else { + double valueA = this.densityA.process(context); + double valueB = this.densityB.process(context); + return valueA * (1.0 - influence) + valueB * influence; + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length != 3) { + throw new IllegalArgumentException("inputs.length != 3"); + } else { + this.densityA = inputs[0]; + this.densityB = inputs[1]; + this.influenceDensity = inputs[2]; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MultiCacheDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MultiCacheDensity.java new file mode 100644 index 0000000..7c5d8d8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MultiCacheDensity.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MultiCacheDensity extends Density { + @Nonnull + private final WorkerIndexer.Data threadData; + @Nonnull + private Density input; + + public MultiCacheDensity(@Nonnull Density input, int threadCount, int capacity) { + this.input = input; + this.threadData = new WorkerIndexer.Data<>(threadCount, () -> new MultiCacheDensity.Cache(capacity)); + } + + @Override + public double process(@Nonnull Density.Context context) { + MultiCacheDensity.Cache cache = this.threadData.get(context.workerId); + MultiCacheDensity.Entry matchingEntry = cache.find(context.position); + if (matchingEntry == null) { + matchingEntry = cache.getNext(); + if (matchingEntry.position == null) { + matchingEntry.position = new Vector3d(); + } + + matchingEntry.position.assign(context.position); + matchingEntry.value = this.input.process(context); + } + + return matchingEntry.value; + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + assert inputs.length != 0; + + assert inputs[0] != null; + + this.input = inputs[0]; + } + + private static class Cache { + MultiCacheDensity.Entry[] entries; + int oldestIndex; + + Cache(int size) { + this.entries = new MultiCacheDensity.Entry[size]; + + for (int i = 0; i < size; i++) { + this.entries[i] = new MultiCacheDensity.Entry(); + } + + this.oldestIndex = 0; + } + + MultiCacheDensity.Entry getNext() { + MultiCacheDensity.Entry entry = this.entries[this.oldestIndex]; + this.oldestIndex++; + if (this.oldestIndex >= this.entries.length) { + this.oldestIndex = 0; + } + + return entry; + } + + @Nullable + MultiCacheDensity.Entry find(@Nonnull Vector3d position) { + int startIndex = this.oldestIndex - 1; + if (startIndex < 0) { + startIndex += this.entries.length; + } + + int index = startIndex; + + while (!position.equals(this.entries[index].position)) { + if (++index >= this.entries.length) { + index = 0; + } + + if (index == startIndex) { + return null; + } + } + + return this.entries[index]; + } + } + + private static class Entry { + Vector3d position = null; + double value = 0.0; + + Entry() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MultiMixDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MultiMixDensity.java new file mode 100644 index 0000000..d089303 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MultiMixDensity.java @@ -0,0 +1,141 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.ArrayUtil; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Interpolation; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.BiFunction; +import javax.annotation.Nonnull; + +public class MultiMixDensity extends Density { + @Nonnull + private final List segments; + private final double min; + private final double max; + private final Density firstDensity; + private final Density lastDensity; + private Density influenceDensity; + + public MultiMixDensity(@Nonnull List keys, @Nonnull Density influenceDensity) { + if (keys.size() < 2) { + throw new IllegalArgumentException("must have at least two keys"); + } else { + keys.sort(Comparator.comparingDouble(element -> element.value)); + if (!isKeysUnique(keys)) { + throw new IllegalArgumentException("Duplicate keys provided."); + } else { + this.segments = new ArrayList<>(keys.size() - 1); + + for (int i = 1; i < keys.size(); i++) { + MultiMixDensity.Key key0 = keys.get(i - 1); + MultiMixDensity.Key key1 = keys.get(i); + this.segments.add(new MultiMixDensity.Segment(key0, key1)); + } + + this.min = keys.getFirst().value; + this.max = keys.getLast().value; + this.firstDensity = keys.getFirst().density; + this.lastDensity = keys.getLast().density; + this.influenceDensity = influenceDensity; + } + } + } + + @Override + public double process(@Nonnull Density.Context context) { + double influence = this.influenceDensity.process(context); + if (influence <= this.min) { + return this.firstDensity.process(context); + } else if (influence >= this.max) { + return this.lastDensity.process(context); + } else { + int index = ArrayUtil.sortedSearch(this.segments, influence, MultiMixDensity.Segment.GaugeSegmentComparator.INSTANCE); + if (index == -1) { + assert false : "should never get here"; + + return 0.0; + } else { + return this.segments.get(index).getValue(context, influence); + } + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length != 1) { + throw new IllegalArgumentException("inputs.length != 1"); + } else { + this.influenceDensity = inputs[0]; + } + } + + public static boolean isKeysUnique(@Nonnull List keys) { + for (int i = 1; i < keys.size(); i++) { + if (keys.get(i).value == keys.get(i - 1).value) { + return false; + } + } + + return true; + } + + public record Key(double value, Density density) { + } + + private static class Segment { + @Nonnull + private final MultiMixDensity.Key key0; + @Nonnull + private final MultiMixDensity.Key key1; + private final double magnitude; + + public Segment(@Nonnull MultiMixDensity.Key key0, @Nonnull MultiMixDensity.Key key1) { + assert key0.value < key1.value : "key0 should be smaller than key1"; + + this.key0 = key0; + this.key1 = key1; + this.magnitude = key1.value - key0.value; + } + + public boolean contains(double gauge) { + return gauge >= this.key0.value && gauge <= this.key1.value; + } + + public double getValue(@Nonnull Density.Context context, double gauge) { + assert gauge >= this.key0.value && gauge <= this.key1.value : "mix outside range"; + + double THRESHOLD_INPUT_0 = 0.0; + double THRESHOLD_INPUT_1 = 1.0; + double weight = (gauge - this.key0.value) / this.magnitude; + if (weight == 0.0) { + return this.key0.density.process(context); + } else if (weight == 1.0) { + return this.key1.density.process(context); + } else if (this.key0.density == this.key1.density) { + return this.key0.density.process(context); + } else { + double value0 = this.key0.density.process(context); + double value1 = this.key1.density.process(context); + return Interpolation.linear(value0, value1, weight); + } + } + + public static class GaugeSegmentComparator implements BiFunction { + public static final MultiMixDensity.Segment.GaugeSegmentComparator INSTANCE = new MultiMixDensity.Segment.GaugeSegmentComparator(); + + public GaugeSegmentComparator() { + } + + @Nonnull + public Integer apply(Double gauge, @Nonnull MultiMixDensity.Segment segment) { + if (gauge < segment.key0.value) { + return -1; + } else { + return gauge >= segment.key1.value ? 1 : 0; + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MultiplierDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MultiplierDensity.java new file mode 100644 index 0000000..838891d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/MultiplierDensity.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import java.util.List; +import javax.annotation.Nonnull; + +public class MultiplierDensity extends Density { + private Density[] inputs; + + public MultiplierDensity(@Nonnull List inputs) { + this.inputs = new Density[inputs.size()]; + inputs.toArray(this.inputs); + } + + @Override + public double process(@Nonnull Density.Context context) { + double multiply = this.inputs.length == 0 ? 0.0 : 1.0; + + for (Density input : this.inputs) { + multiply *= input.process(context); + if (multiply == 0.0) { + return 0.0; + } + } + + return multiply; + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + this.inputs = inputs; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/Noise2dDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/Noise2dDensity.java new file mode 100644 index 0000000..f753a4d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/Noise2dDensity.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.fields.noise.NoiseField; +import javax.annotation.Nonnull; + +public class Noise2dDensity extends Density { + private NoiseField noise; + + public Noise2dDensity(@Nonnull NoiseField noise) { + this.noise = noise; + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.noise.valueAt(context.position.x, context.position.z); + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/Noise3dDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/Noise3dDensity.java new file mode 100644 index 0000000..3881e33 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/Noise3dDensity.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.fields.noise.NoiseField; +import javax.annotation.Nonnull; + +public class Noise3dDensity extends Density { + @Nonnull + private final NoiseField noise; + + public Noise3dDensity(@Nonnull NoiseField noise) { + this.noise = noise; + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.noise.valueAt(context.position.x, context.position.y, context.position.z); + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/NormalizerDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/NormalizerDensity.java new file mode 100644 index 0000000..b10b214 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/NormalizerDensity.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Normalizer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NormalizerDensity extends Density { + private final double fromMin; + private final double fromMax; + private final double toMin; + private final double toMax; + @Nullable + private Density input; + + public NormalizerDensity(double fromMin, double fromMax, double toMin, double toMax, Density input) { + if (!(fromMin > fromMax) && !(toMin > toMax)) { + this.fromMin = fromMin; + this.fromMax = fromMax; + this.toMin = toMin; + this.toMax = toMax; + this.input = input; + } else { + throw new IllegalArgumentException("min larger than max"); + } + } + + @Override + public double process(@Nonnull Density.Context context) { + return Normalizer.normalize(this.fromMin, this.fromMax, this.toMin, this.toMax, this.input.process(context)); + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/OffsetConstantDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/OffsetConstantDensity.java new file mode 100644 index 0000000..6d808f6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/OffsetConstantDensity.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class OffsetConstantDensity extends Density { + private final double offset; + @Nullable + private Density input; + + public OffsetConstantDensity(double offset, Density input) { + this.offset = offset; + this.input = input; + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.input == null ? 0.0 : this.input.process(context) + this.offset; + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/OffsetDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/OffsetDensity.java new file mode 100644 index 0000000..896add3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/OffsetDensity.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class OffsetDensity extends Density { + @Nonnull + private final Double2DoubleFunction offsetFunc; + @Nullable + private Density input; + + public OffsetDensity(@Nonnull Double2DoubleFunction offsetFunction, Density input) { + this.offsetFunc = offsetFunction; + this.input = input; + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.input == null ? this.offsetFunc.get(context.position.y) : this.input.process(context) + this.offsetFunc.get(context.position.y); + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/PlaneDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/PlaneDensity.java new file mode 100644 index 0000000..81ec1a1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/PlaneDensity.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlaneDensity extends Density { + public static final double ZERO_DELTA = 1.0E-9; + private static final Vector3d ZERO_VECTOR = new Vector3d(); + @Nonnull + private final Double2DoubleFunction distanceCurve; + @Nonnull + private final Vector3d planeNormal; + private final boolean isPlaneHorizontal; + private final boolean isAnchored; + + public PlaneDensity(@Nonnull Double2DoubleFunction distanceCurve, @Nonnull Vector3d planeNormal, boolean isAnchored) { + this.distanceCurve = distanceCurve; + this.planeNormal = planeNormal; + this.isPlaneHorizontal = planeNormal.x == 0.0 && planeNormal.z == 0.0; + this.isAnchored = isAnchored; + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.planeNormal.length() == 0.0) { + return 0.0; + } else if (this.isAnchored) { + return this.processAnchored(context.position.x, context.position.y, context.position.z, context); + } else { + double distance = 0.0; + if (this.isPlaneHorizontal) { + distance = context.position.y; + } else { + Vector3d nearestPoint = VectorUtil.nearestPointOnLine3d(context.position, ZERO_VECTOR, this.planeNormal); + distance = nearestPoint.length(); + } + + return this.distanceCurve.get(distance); + } + } + + private double processAnchored(double x, double y, double z, @Nullable Density.Context context) { + if (context == null) { + return 0.0; + } else { + Vector3d position = new Vector3d(x, y, z); + Vector3d p0 = context.densityAnchor; + if (p0 == null) { + return 0.0; + } else { + double distance = 0.0; + if (this.isPlaneHorizontal) { + distance = Math.abs(p0.y - position.y); + } + + Vector3d pos = position.clone().addScaled(p0, -1.0); + Vector3d vectorFromPlane = VectorUtil.nearestPointOnLine3d(pos, ZERO_VECTOR, this.planeNormal); + distance = vectorFromPlane.length(); + return this.distanceCurve.get(distance); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/PositionsHorizontalPinchDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/PositionsHorizontalPinchDensity.java new file mode 100644 index 0000000..1c8dedb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/PositionsHorizontalPinchDensity.java @@ -0,0 +1,159 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3d; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import java.util.ArrayList; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +public class PositionsHorizontalPinchDensity extends Density { + @Nonnull + private Density input; + @Nonnull + private final PositionProvider positions; + @Nonnull + private final Double2DoubleFunction pinchCurve; + @Nonnull + private final WorkerIndexer.Data threadData; + private final double maxDistance; + private final boolean distanceNormalized; + private final double positionsMinY; + private final double positionsMaxY; + + public PositionsHorizontalPinchDensity( + @Nonnull Density input, + @Nonnull PositionProvider positions, + @Nonnull Double2DoubleFunction pinchCurve, + double maxDistance, + boolean distanceNormalized, + double positionsMinY, + double positionsMaxY, + int threadCount + ) { + if (maxDistance < 0.0) { + throw new IllegalArgumentException(); + } else { + if (positionsMinY > positionsMaxY) { + positionsMinY = positionsMaxY; + } + + this.input = input; + this.positions = positions; + this.pinchCurve = pinchCurve; + this.maxDistance = maxDistance; + this.distanceNormalized = distanceNormalized; + this.positionsMinY = positionsMinY; + this.positionsMaxY = positionsMaxY; + this.threadData = new WorkerIndexer.Data<>(threadCount, PositionsHorizontalPinchDensity.Cache::new); + } + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.input == null) { + return 0.0; + } else if (this.positions == null) { + return this.input.process(context); + } else { + PositionsHorizontalPinchDensity.Cache cache = this.threadData.get(context.workerId); + Vector3d warpVector; + if (cache.x == context.position.x && cache.z == context.position.z && !cache.hasValue) { + warpVector = cache.warpVector; + } else { + warpVector = this.calculateWarpVector(context); + cache.warpVector = warpVector; + } + + Vector3d position = new Vector3d(warpVector.x + context.position.x, warpVector.y + context.position.y, warpVector.z + context.position.z); + Density.Context childContext = new Density.Context(context); + childContext.position = position; + return this.input.process(childContext); + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = new ConstantValueDensity(0.0); + } + + this.input = inputs[0]; + } + + public Vector3d calculateWarpVector(@Nonnull Density.Context context) { + Vector3d position = context.position; + Vector3d min = new Vector3d(position.x - this.maxDistance, this.positionsMinY, position.z - this.maxDistance); + Vector3d max = new Vector3d(position.x + this.maxDistance, this.positionsMaxY, position.z + this.maxDistance); + Vector3d samplePoint = position.clone(); + ArrayList warpVectors = new ArrayList<>(10); + ArrayList warpDistances = new ArrayList<>(10); + Consumer consumer = iteratedPosition -> { + double distance = Calculator.distance(iteratedPosition.x, iteratedPosition.z, samplePoint.x, samplePoint.z); + if (!(distance > this.maxDistance)) { + double normalizedDistance = distance / this.maxDistance; + Vector3d warpVectorx = iteratedPosition.clone().addScaled(samplePoint, -1.0); + warpVectorx.setY(0.0); + double radialDistance; + if (this.distanceNormalized) { + radialDistance = this.pinchCurve.applyAsDouble(normalizedDistance); + radialDistance *= this.maxDistance; + } else { + radialDistance = this.pinchCurve.applyAsDouble(distance); + } + + if (!(Math.abs(warpVectorx.length()) < 1.0E-9)) { + warpVectorx.setLength(radialDistance); + } + + warpVectors.add(warpVectorx); + warpDistances.add(normalizedDistance); + } + }; + PositionProvider.Context positionsContext = new PositionProvider.Context(); + positionsContext.minInclusive = min; + positionsContext.maxExclusive = max; + positionsContext.consumer = consumer; + this.positions.positionsIn(positionsContext); + if (warpVectors.isEmpty()) { + return new Vector3d(0.0, 0.0, 0.0); + } else if (warpVectors.size() == 1) { + return warpVectors.getFirst(); + } else { + int possiblePointsSize = warpVectors.size(); + ArrayList weights = new ArrayList<>(warpDistances.size()); + double totalWeight = 0.0; + + for (int i = 0; i < possiblePointsSize; i++) { + double distance = warpDistances.get(i); + double weight = 1.0 - distance; + weights.add(weight); + totalWeight += weight; + } + + Vector3d totalWarpVector = new Vector3d(); + + for (int i = 0; i < possiblePointsSize; i++) { + double weight = weights.get(i) / totalWeight; + Vector3d warpVector = warpVectors.get(i); + warpVector.scale(weight); + totalWarpVector.add(warpVector); + } + + return totalWarpVector; + } + } + + private static class Cache { + double x; + double z; + Vector3d warpVector; + boolean hasValue; + + private Cache() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/PositionsPinchDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/PositionsPinchDensity.java new file mode 100644 index 0000000..5d70cc5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/PositionsPinchDensity.java @@ -0,0 +1,114 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.math.vector.Vector3d; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import java.util.ArrayList; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PositionsPinchDensity extends Density { + @Nullable + private Density input; + @Nullable + private PositionProvider positions; + private Double2DoubleFunction pinchCurve; + private double maxDistance; + private boolean distanceNormalized; + + public PositionsPinchDensity( + @Nullable Density input, @Nullable PositionProvider positions, @Nonnull Double2DoubleFunction pinchCurve, double maxDistance, boolean distanceNormalized + ) { + if (maxDistance < 0.0) { + throw new IllegalArgumentException(); + } else { + this.input = input; + this.positions = positions; + this.pinchCurve = pinchCurve; + this.maxDistance = maxDistance; + this.distanceNormalized = distanceNormalized; + } + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.input == null) { + return 0.0; + } else if (this.positions == null) { + return this.input.process(context); + } else { + Vector3d min = new Vector3d(context.position.x - this.maxDistance, context.position.y - this.maxDistance, context.position.z - this.maxDistance); + Vector3d max = new Vector3d(context.position.x + this.maxDistance, context.position.y + this.maxDistance, context.position.z + this.maxDistance); + Vector3d samplePoint = context.position.clone(); + ArrayList warpVectors = new ArrayList<>(10); + ArrayList warpDistances = new ArrayList<>(10); + Consumer consumer = p -> { + double distance = p.distanceTo(samplePoint); + if (!(distance > this.maxDistance)) { + double normalizedDistance = distance / this.maxDistance; + Vector3d warpVectorx = p.clone().addScaled(samplePoint, -1.0); + double radialDistance; + if (this.distanceNormalized) { + radialDistance = this.pinchCurve.applyAsDouble(normalizedDistance); + radialDistance *= this.maxDistance; + } else { + radialDistance = this.pinchCurve.applyAsDouble(distance); + } + + if (!(Math.abs(warpVectorx.length()) < 1.0E-9)) { + warpVectorx.setLength(radialDistance); + } + + warpVectors.add(warpVectorx); + warpDistances.add(normalizedDistance); + } + }; + PositionProvider.Context positionsContext = new PositionProvider.Context(); + positionsContext.minInclusive = min; + positionsContext.maxExclusive = max; + positionsContext.consumer = consumer; + positionsContext.workerId = context.workerId; + this.positions.positionsIn(positionsContext); + if (warpVectors.isEmpty()) { + return this.input.process(context); + } else if (warpVectors.size() == 1) { + Vector3d warpVector = warpVectors.getFirst(); + samplePoint.add(warpVector); + Density.Context childContext = new Density.Context(context); + context.position = samplePoint; + return this.input.process(childContext); + } else { + int possiblePointsSize = warpVectors.size(); + ArrayList weights = new ArrayList<>(warpDistances.size()); + double totalWeight = 0.0; + + for (int i = 0; i < possiblePointsSize; i++) { + double distance = warpDistances.get(i); + double weight = 1.0 - distance; + weights.add(weight); + totalWeight += weight; + } + + for (int i = 0; i < possiblePointsSize; i++) { + double weight = weights.get(i) / totalWeight; + Vector3d warpVector = warpVectors.get(i); + warpVector.scale(weight); + samplePoint.add(warpVector); + } + + return this.input.process(context); + } + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/PositionsTwistDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/PositionsTwistDensity.java new file mode 100644 index 0000000..7df4e00 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/PositionsTwistDensity.java @@ -0,0 +1,139 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.math.vector.Vector3d; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import java.util.ArrayList; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PositionsTwistDensity extends Density { + @Nullable + private Density input; + @Nullable + private PositionProvider positions; + private Double2DoubleFunction twistCurve; + private Vector3d twistAxis; + private double maxDistance; + private boolean distanceNormalized; + private boolean zeroPositionsY; + + public PositionsTwistDensity( + @Nullable Density input, + @Nullable PositionProvider positions, + @Nonnull Double2DoubleFunction twistCurve, + @Nonnull Vector3d twistAxis, + double maxDistance, + boolean distanceNormalized, + boolean zeroPositionsY + ) { + if (maxDistance < 0.0) { + throw new IllegalArgumentException(); + } else { + if (twistAxis.length() < 1.0E-9) { + twistAxis = new Vector3d(0.0, 1.0, 0.0); + } + + this.input = input; + this.positions = positions; + this.twistCurve = twistCurve; + this.twistAxis = twistAxis; + this.maxDistance = maxDistance; + this.distanceNormalized = distanceNormalized; + this.zeroPositionsY = zeroPositionsY; + } + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.input == null) { + return 0.0; + } else if (this.positions == null) { + return this.input.process(context); + } else { + Vector3d min = new Vector3d(context.position.x - this.maxDistance, context.position.y - this.maxDistance, context.position.z - this.maxDistance); + Vector3d max = new Vector3d(context.position.x + this.maxDistance, context.position.y + this.maxDistance, context.position.z + this.maxDistance); + Vector3d samplePoint = context.position.clone(); + Vector3d queryPosition = context.position.clone(); + if (this.zeroPositionsY) { + queryPosition.y = 0.0; + min.y = -1.0; + max.y = 1.0; + } + + ArrayList warpVectors = new ArrayList<>(10); + ArrayList warpDistances = new ArrayList<>(10); + Consumer consumer = p -> { + double distance = p.distanceTo(queryPosition); + if (!(distance > this.maxDistance)) { + double normalizedDistance = distance / this.maxDistance; + Vector3d warpVectorx = samplePoint.clone(); + double twistAngle; + if (this.distanceNormalized) { + twistAngle = this.twistCurve.applyAsDouble(normalizedDistance); + } else { + twistAngle = this.twistCurve.applyAsDouble(distance); + } + + twistAngle /= 180.0; + twistAngle *= Math.PI; + warpVectorx.subtract(p); + VectorUtil.rotateAroundAxis(warpVectorx, this.twistAxis, twistAngle); + warpVectorx.add(p); + warpVectorx.subtract(samplePoint); + warpVectors.add(warpVectorx); + if (this.distanceNormalized) { + warpDistances.add(normalizedDistance); + } else { + warpDistances.add(distance); + } + } + }; + PositionProvider.Context positionsContext = new PositionProvider.Context(min, max, consumer, null, context.workerId); + this.positions.positionsIn(positionsContext); + if (warpVectors.isEmpty()) { + return this.input.process(context); + } else if (warpVectors.size() == 1) { + Vector3d warpVector = warpVectors.getFirst(); + samplePoint.add(warpVector); + Density.Context childContext = new Density.Context(context); + childContext.position = samplePoint; + return this.input.process(childContext); + } else { + int possiblePointsSize = warpVectors.size(); + ArrayList weights = new ArrayList<>(warpDistances.size()); + double totalWeight = 0.0; + + for (int i = 0; i < possiblePointsSize; i++) { + double distance = warpDistances.get(i); + double weight = 1.0 - distance; + weights.add(weight); + totalWeight += weight; + } + + for (int i = 0; i < possiblePointsSize; i++) { + double weight = weights.get(i) / totalWeight; + Vector3d warpVector = warpVectors.get(i); + warpVector.scale(weight); + samplePoint.add(warpVector); + } + + Density.Context childContext = new Density.Context(context); + childContext.position = samplePoint; + return this.input.process(childContext); + } + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/PowDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/PowDensity.java new file mode 100644 index 0000000..91ed66a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/PowDensity.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PowDensity extends Density { + private final double exponent; + @Nullable + private Density input; + + public PowDensity(double exponent, Density input) { + this.exponent = exponent; + this.input = input; + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.input == null) { + return 0.0; + } else { + double value = this.input.process(context); + return value < 0.0 ? -Math.pow(-value, this.exponent) : Math.pow(value, this.exponent); + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/RotatorDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/RotatorDensity.java new file mode 100644 index 0000000..cb10805 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/RotatorDensity.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RotatorDensity extends Density { + private static final Vector3d Y_AXIS = new Vector3d(0.0, 1.0, 0.0); + @Nullable + private Density input; + private Vector3d rotationAxis; + private Vector3d tiltAxis; + private double tiltAngle; + private final double spinAngle; + @Nonnull + private final RotatorDensity.SpecialCase axisSpecialCase; + + public RotatorDensity(@Nonnull Density input, @Nonnull Vector3d newYAxis, double spinAngle) { + this.input = input; + this.spinAngle = spinAngle * Math.PI / 180.0; + Vector3d yAxis = new Vector3d(0.0, 1.0, 0.0); + this.rotationAxis = newYAxis.cross(yAxis); + if (this.rotationAxis.length() < 1.0E-8) { + this.rotationAxis = yAxis; + if (newYAxis.dot(yAxis) < 0.0) { + this.axisSpecialCase = RotatorDensity.SpecialCase.INVERTED_Y_AXIS; + } else { + this.axisSpecialCase = RotatorDensity.SpecialCase.Y_AXIS; + } + } else { + this.axisSpecialCase = RotatorDensity.SpecialCase.NONE; + } + + this.rotationAxis.normalize(); + if (this.axisSpecialCase == RotatorDensity.SpecialCase.INVERTED_Y_AXIS || this.axisSpecialCase == RotatorDensity.SpecialCase.Y_AXIS) { + this.tiltAxis = new Vector3d(); + this.tiltAngle = 0.0; + } + + this.tiltAxis = yAxis.cross(newYAxis); + this.tiltAngle = Math.acos(newYAxis.dot(yAxis) / (newYAxis.length() * yAxis.length())); + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.input == null) { + return 0.0; + } else { + Vector3d childPosition = context.position.clone(); + switch (this.axisSpecialCase) { + case INVERTED_Y_AXIS: + childPosition.scale(-1.0); + case NONE: + VectorUtil.rotateAroundAxis(childPosition, this.tiltAxis, this.tiltAngle); + case Y_AXIS: + default: + VectorUtil.rotateAroundAxis(childPosition, Y_AXIS, this.spinAngle); + Density.Context childContext = new Density.Context(context); + childContext.position = childPosition; + return this.input.process(childContext); + } + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } + + private static enum SpecialCase { + NONE, + Y_AXIS, + INVERTED_Y_AXIS; + + private SpecialCase() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ScaleDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ScaleDensity.java new file mode 100644 index 0000000..9d5392b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ScaleDensity.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ScaleDensity extends Density { + @Nonnull + private final Vector3d scale; + private final boolean isInvalid; + @Nullable + private Density input; + + public ScaleDensity(double scaleX, double scaleY, double scaleZ, Density input) { + this.scale = new Vector3d(1.0 / scaleX, 1.0 / scaleY, 1.0 / scaleZ); + this.isInvalid = scaleX == 0.0 || scaleY == 0.0 || scaleZ == 0.0; + this.input = input; + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.input == null) { + return 0.0; + } else if (this.isInvalid) { + return 0.0; + } else { + Vector3d scaledPosition = context.position.clone(); + scaledPosition.scale(this.scale); + Density.Context childContext = new Density.Context(context); + childContext.position = scaledPosition; + return this.input.process(childContext); + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SelectorDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SelectorDensity.java new file mode 100644 index 0000000..ecbace1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SelectorDensity.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Normalizer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SelectorDensity extends Density { + private final double fromMin; + private final double fromMax; + private final double toMin; + private final double toMax; + private final double smoothRange; + @Nullable + private Density input; + + public SelectorDensity(double fromMin, double fromMax, double toMin, double toMax, double smoothRange, Density input) { + if (!(fromMin > fromMax) && !(toMin > toMax) && !(smoothRange < 0.0)) { + this.fromMin = fromMin; + this.fromMax = fromMax; + this.toMin = toMin; + this.toMax = toMax; + this.smoothRange = smoothRange; + this.input = input; + } else { + throw new IllegalArgumentException("min larger than max"); + } + } + + @Override + public double process(@Nonnull Density.Context context) { + double v = 0.0; + if (this.input != null) { + v = this.input.process(context); + } + + v = Normalizer.normalize(this.fromMin, this.fromMax, this.toMin, this.toMax, v); + if (this.smoothRange == 0.0) { + v = Math.max(this.toMin, v); + return Math.min(this.toMax, v); + } else { + v = Calculator.smoothMax(this.smoothRange, this.toMin, v); + return Calculator.smoothMin(this.smoothRange, v, this.toMax); + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ShellDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ShellDensity.java new file mode 100644 index 0000000..9587774 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ShellDensity.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import com.hypixel.hytale.math.vector.Vector3d; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class ShellDensity extends Density { + public static final double ZERO_DELTA = 1.0E-9; + @Nonnull + private final Double2DoubleFunction angleCurve; + @Nonnull + private final Double2DoubleFunction distanceCurve; + @Nonnull + private final Vector3d axis; + private final boolean isMirrored; + + public ShellDensity(@Nonnull Double2DoubleFunction angleCurve, @Nonnull Double2DoubleFunction distanceCurve, @Nonnull Vector3d axis, boolean isMirrored) { + this.angleCurve = angleCurve; + this.distanceCurve = distanceCurve; + this.axis = axis; + this.isMirrored = isMirrored; + } + + @Override + public double process(@Nonnull Density.Context context) { + double distance = Calculator.distance(context.position, Vector3d.ZERO); + if (this.axis.length() == 0.0) { + return 0.0; + } else { + Vector3d radialVector = context.position.clone(); + double amplitude = this.distanceCurve.applyAsDouble(distance); + if (amplitude == 0.0) { + return 0.0; + } else if (radialVector.length() <= 1.0E-9) { + return amplitude; + } else { + double angle = VectorUtil.angle(radialVector, this.axis); + angle /= Math.PI; + angle *= 180.0; + if (this.isMirrored && angle > 90.0) { + angle = 180.0 - angle; + } + + return amplitude * this.angleCurve.applyAsDouble(angle); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SliderDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SliderDensity.java new file mode 100644 index 0000000..97beb3f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SliderDensity.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SliderDensity extends Density { + private final double slideX; + private final double slideY; + private final double slideZ; + @Nullable + private Density input; + + public SliderDensity(double slideX, double slideY, double slideZ, Density input) { + this.slideX = slideX; + this.slideY = slideY; + this.slideZ = slideZ; + this.input = input; + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.input == null) { + return 0.0; + } else { + Vector3d childPosition = new Vector3d(context.position.x - this.slideX, context.position.y - this.slideY, context.position.z - this.slideZ); + Density.Context childContext = new Density.Context(context); + childContext.position = childPosition; + return this.input.process(childContext); + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SmoothCeilingDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SmoothCeilingDensity.java new file mode 100644 index 0000000..dcdcefd --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SmoothCeilingDensity.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SmoothCeilingDensity extends Density { + private double limit; + private double smoothRange; + @Nullable + private Density input; + + public SmoothCeilingDensity(double limit, double smoothRange, Density input) { + if (smoothRange < 0.0) { + throw new IllegalArgumentException(); + } else { + this.limit = limit; + this.smoothRange = smoothRange; + this.input = input; + } + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.input == null ? 0.0 : Calculator.smoothMin(this.smoothRange, this.input.process(context), this.limit); + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SmoothClampDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SmoothClampDensity.java new file mode 100644 index 0000000..efd3c1c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SmoothClampDensity.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SmoothClampDensity extends Density { + private final double max; + private final double min; + private final double range; + @Nullable + private Density input; + + public SmoothClampDensity(double min, double max, double range, Density input) { + if (range <= 0.0) { + throw new IllegalArgumentException("invalid range + range"); + } else if (max < min) { + throw new IllegalArgumentException("max smaller than min"); + } else { + this.max = max; + this.min = min; + this.range = range; + this.input = input; + } + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.input == null) { + return 0.0; + } else { + double value = this.input.process(context); + value = Calculator.smoothMin(this.range, this.max, value); + return Calculator.smoothMax(this.range, this.min, value); + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SmoothFloorDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SmoothFloorDensity.java new file mode 100644 index 0000000..2cb2085 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SmoothFloorDensity.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SmoothFloorDensity extends Density { + private double limit; + private double smoothRange; + @Nullable + private Density input; + + public SmoothFloorDensity(double limit, double smoothRange, Density input) { + if (smoothRange < 0.0) { + throw new IllegalArgumentException(); + } else { + this.limit = limit; + this.smoothRange = smoothRange; + this.input = input; + } + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.input == null ? 0.0 : Calculator.smoothMax(this.smoothRange, this.input.process(context), this.limit); + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SmoothMaxDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SmoothMaxDensity.java new file mode 100644 index 0000000..45823f0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SmoothMaxDensity.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SmoothMaxDensity extends Density { + private final double range; + @Nullable + private Density inputA; + @Nullable + private Density inputB; + + public SmoothMaxDensity(double range, Density inputA, Density inputB) { + this.range = range; + this.inputA = inputA; + this.inputB = inputB; + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.inputA != null && this.inputB != null ? Calculator.smoothMax(this.range, this.inputA.process(context), this.inputB.process(context)) : 0.0; + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length < 2) { + this.inputA = null; + this.inputB = null; + } + + this.inputA = inputs[0]; + this.inputB = inputs[1]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SmoothMinDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SmoothMinDensity.java new file mode 100644 index 0000000..23348d2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SmoothMinDensity.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SmoothMinDensity extends Density { + private final double range; + @Nullable + private Density inputA; + @Nullable + private Density inputB; + + public SmoothMinDensity(double range, Density inputA, Density inputB) { + this.range = range; + this.inputA = inputA; + this.inputB = inputB; + } + + @Override + public double process(@Nonnull Density.Context context) { + return this.inputA != null && this.inputB != null ? Calculator.smoothMin(this.range, this.inputA.process(context), this.inputB.process(context)) : 0.0; + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length < 2) { + this.inputA = null; + this.inputB = null; + } + + this.inputA = inputs[0]; + this.inputB = inputs[1]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SqrtDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SqrtDensity.java new file mode 100644 index 0000000..458719e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SqrtDensity.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SqrtDensity extends Density { + @Nullable + private Density input; + + public SqrtDensity(Density input) { + this.input = input; + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.input == null) { + return 0.0; + } else { + double v = this.input.process(context); + return v < 0.0 ? -Math.sqrt(-v) : Math.sqrt(v); + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SumDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SumDensity.java new file mode 100644 index 0000000..5b1fd0d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SumDensity.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nonnull; + +public class SumDensity extends Density { + private Density[] inputs; + + public SumDensity(@Nonnull List inputs) { + this.inputs = new Density[inputs.size()]; + inputs.toArray(this.inputs); + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.inputs.length == 0) { + return 0.0; + } else { + double sum = 0.0; + + for (Density input : this.inputs) { + sum += input.process(context); + } + + return sum; + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + this.inputs = Arrays.copyOf(inputs, inputs.length); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SwitchDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SwitchDensity.java new file mode 100644 index 0000000..ad6e672 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SwitchDensity.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import java.util.List; +import javax.annotation.Nonnull; + +public class SwitchDensity extends Density { + private Density[] inputs; + @Nonnull + private final int[] switchStates; + + public SwitchDensity(@Nonnull List inputs, @Nonnull List switchStates) { + if (inputs.size() != switchStates.size()) { + throw new IllegalArgumentException("inputs and switch states have different sizes"); + } else { + this.inputs = new Density[inputs.size()]; + this.switchStates = new int[switchStates.size()]; + inputs.toArray(this.inputs); + + for (int i = 0; i < switchStates.size(); i++) { + this.switchStates[i] = switchStates.get(i); + } + } + } + + @Override + public double process(@Nonnull Density.Context context) { + if (context == null) { + return 0.0; + } else { + int contextSwitchState = context.switchState; + + for (int i = 0; i < this.switchStates.length; i++) { + if (this.switchStates[i] == contextSwitchState) { + Density node = this.inputs[i]; + if (node == null) { + return 0.0; + } + + return node.process(context); + } + } + + return 0.0; + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + for (int i = 0; i < this.switchStates.length; i++) { + this.inputs[i] = null; + } + } else if (inputs.length < this.inputs.length) { + System.arraycopy(inputs, 0, this.inputs, 0, inputs.length); + + for (int i = inputs.length; i < this.inputs.length; i++) { + this.inputs[i] = null; + } + } else { + System.arraycopy(inputs, 0, this.inputs, 0, this.inputs.length); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SwitchStateDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SwitchStateDensity.java new file mode 100644 index 0000000..fe939c9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/SwitchStateDensity.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SwitchStateDensity extends Density { + public static final int DEFAULT_SWITCH_STATE = 0; + @Nullable + private Density input; + private final int switchState; + + public SwitchStateDensity(Density input, int switchState) { + this.input = input; + this.switchState = switchState; + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.input == null) { + return 0.0; + } else { + Density.Context childContext = new Density.Context(context); + childContext.switchState = this.switchState; + return this.input.process(context); + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/TerrainDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/TerrainDensity.java new file mode 100644 index 0000000..1fc4013 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/TerrainDensity.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; + +public class TerrainDensity extends Density { + public TerrainDensity() { + } + + @Override + public double process(@Nonnull Density.Context context) { + return context.terrainDensityProvider == null ? 0.0 : context.terrainDensityProvider.get(context.position.toVector3i(), context.workerId); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/VectorWarpDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/VectorWarpDensity.java new file mode 100644 index 0000000..3f23e30 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/VectorWarpDensity.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class VectorWarpDensity extends Density { + @Nullable + private Density input; + @Nullable + private Density warpInput; + private final double warpFactor; + @Nonnull + private final Vector3d warpVector; + + public VectorWarpDensity(@Nonnull Density input, @Nonnull Density warpInput, double warpFactor, @Nonnull Vector3d warpVector) { + this.input = input; + this.warpInput = warpInput; + this.warpFactor = warpFactor; + this.warpVector = warpVector; + } + + @Override + public double process(@Nonnull Density.Context context) { + if (this.input == null) { + return 0.0; + } else if (this.warpInput == null) { + return this.input.process(context); + } else { + double warp = this.warpInput.process(context); + warp *= this.warpFactor; + Vector3d samplePoint = this.warpVector.clone(); + samplePoint.setLength(1.0); + samplePoint.scale(warp); + samplePoint.add(context.position); + Density.Context childContext = new Density.Context(context); + childContext.position = samplePoint; + return this.input.process(childContext); + } + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + if (inputs.length == 0) { + this.input = null; + } + + this.input = inputs[0]; + if (inputs.length < 2) { + this.warpInput = null; + } + + this.warpInput = inputs[1]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/XOverrideDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/XOverrideDensity.java new file mode 100644 index 0000000..99c952a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/XOverrideDensity.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class XOverrideDensity extends Density { + @Nonnull + private Density input; + private final double value; + + public XOverrideDensity(@Nonnull Density input, double value) { + this.input = input; + this.value = value; + } + + @Override + public double process(@Nonnull Density.Context context) { + Vector3d childPosition = new Vector3d(this.value, context.position.y, context.position.z); + Density.Context childContext = new Density.Context(context); + childContext.position = childPosition; + return this.input.process(childContext); + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + assert inputs.length == 1; + + assert inputs[0] != null; + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/XValueDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/XValueDensity.java new file mode 100644 index 0000000..27621d7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/XValueDensity.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; + +public class XValueDensity extends Density { + public XValueDensity() { + } + + @Override + public double process(@Nonnull Density.Context context) { + return context.position.x; + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/YOverrideDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/YOverrideDensity.java new file mode 100644 index 0000000..0953679 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/YOverrideDensity.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class YOverrideDensity extends Density { + @Nonnull + private Density input; + private final double value; + + public YOverrideDensity(@Nonnull Density input, double value) { + this.input = input; + this.value = value; + } + + @Override + public double process(@Nonnull Density.Context context) { + Vector3d childPosition = new Vector3d(context.position.x, this.value, context.position.z); + Density.Context childContext = new Density.Context(context); + childContext.position = childPosition; + return this.input.process(childContext); + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + assert inputs.length == 1; + + assert inputs[0] != null; + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/YValueDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/YValueDensity.java new file mode 100644 index 0000000..5c5f01a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/YValueDensity.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; + +public class YValueDensity extends Density { + public YValueDensity() { + } + + @Override + public double process(@Nonnull Density.Context context) { + return context.position.y; + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ZOverrideDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ZOverrideDensity.java new file mode 100644 index 0000000..37b54d3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ZOverrideDensity.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class ZOverrideDensity extends Density { + @Nonnull + private Density input; + private final double value; + + public ZOverrideDensity(@Nonnull Density input, double value) { + this.input = input; + this.value = value; + } + + @Override + public double process(@Nonnull Density.Context context) { + Vector3d childPosition = new Vector3d(context.position.x, context.position.y, this.value); + Density.Context childContext = new Density.Context(context); + childContext.position = childPosition; + return this.input.process(childContext); + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + assert inputs.length == 1; + + assert inputs[0] != null; + + this.input = inputs[0]; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ZValueDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ZValueDensity.java new file mode 100644 index 0000000..924d465 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/ZValueDensity.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import javax.annotation.Nonnull; + +public class ZValueDensity extends Density { + public ZValueDensity() { + } + + @Override + public double process(@Nonnull Density.Context context) { + return context.position.z; + } + + @Override + public void setInputs(@Nonnull Density[] inputs) { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/PositionsDensity.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/PositionsDensity.java new file mode 100644 index 0000000..ba6c484 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/PositionsDensity.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.distancefunctions.DistanceFunction; +import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.ReturnType; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.math.vector.Vector3d; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +public class PositionsDensity extends Density { + @Nonnull + private final PositionProvider positionProvider; + private final double maxDistance; + private final double maxDistanceRaw; + @Nonnull + private final ReturnType returnType; + @Nonnull + private final DistanceFunction distanceFunction; + + public PositionsDensity( + @Nonnull PositionProvider positionsField, @Nonnull ReturnType returnType, @Nonnull DistanceFunction distanceFunction, double maxDistance + ) { + if (maxDistance < 0.0) { + throw new IllegalArgumentException("negative distance"); + } else { + this.positionProvider = positionsField; + this.maxDistance = maxDistance; + this.maxDistanceRaw = maxDistance * maxDistance; + this.returnType = returnType; + this.distanceFunction = distanceFunction; + } + } + + @Nonnull + public static Double2DoubleFunction cellNoiseDistanceFunction(double maxDistance) { + return d -> d / maxDistance - 1.0; + } + + @Override + public double process(@Nonnull Density.Context context) { + Vector3d min = context.position.clone().subtract(this.maxDistance); + Vector3d max = context.position.clone().add(this.maxDistance); + double[] distance = new double[]{Double.MAX_VALUE, Double.MAX_VALUE}; + boolean[] hasClosestPoint = new boolean[2]; + Vector3d closestPoint = new Vector3d(); + Vector3d previousClosestPoint = new Vector3d(); + Vector3d localPoint = new Vector3d(); + Consumer positionsConsumer = providedPoint -> { + localPoint.x = providedPoint.x - context.position.x; + localPoint.y = providedPoint.y - context.position.y; + localPoint.z = providedPoint.z - context.position.z; + double newDistance = this.distanceFunction.getDistance(localPoint); + if (!(this.maxDistanceRaw < newDistance)) { + distance[1] = Math.max(Math.min(distance[1], newDistance), distance[0]); + if (newDistance < distance[0]) { + distance[0] = newDistance; + previousClosestPoint.assign(closestPoint); + closestPoint.assign(providedPoint); + hasClosestPoint[1] = hasClosestPoint[0]; + hasClosestPoint[0] = true; + } + } + }; + PositionProvider.Context positionsContext = new PositionProvider.Context(); + positionsContext.minInclusive = min; + positionsContext.maxExclusive = max; + positionsContext.consumer = positionsConsumer; + positionsContext.workerId = context.workerId; + this.positionProvider.positionsIn(positionsContext); + distance[0] = Math.sqrt(distance[0]); + distance[1] = Math.sqrt(distance[1]); + return this.returnType + .get( + distance[0], + distance[1], + context.position.clone(), + hasClosestPoint[0] ? closestPoint : null, + hasClosestPoint[1] ? previousClosestPoint : null, + context + ); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/distancefunctions/DistanceFunction.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/distancefunctions/DistanceFunction.java new file mode 100644 index 0000000..1b01d2c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/distancefunctions/DistanceFunction.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.distancefunctions; + +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public abstract class DistanceFunction { + public DistanceFunction() { + } + + public abstract double getDistance(@Nonnull Vector3d var1); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/distancefunctions/EuclideanDistanceFunction.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/distancefunctions/EuclideanDistanceFunction.java new file mode 100644 index 0000000..559edf3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/distancefunctions/EuclideanDistanceFunction.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.distancefunctions; + +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class EuclideanDistanceFunction extends DistanceFunction { + public EuclideanDistanceFunction() { + } + + @Override + public double getDistance(@Nonnull Vector3d point) { + return point.x * point.x + point.y * point.y + point.z * point.z; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/distancefunctions/ManhattanDistanceFunction.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/distancefunctions/ManhattanDistanceFunction.java new file mode 100644 index 0000000..1c6b6ed --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/distancefunctions/ManhattanDistanceFunction.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.distancefunctions; + +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class ManhattanDistanceFunction extends DistanceFunction { + public ManhattanDistanceFunction() { + } + + @Override + public double getDistance(@Nonnull Vector3d point) { + return Math.abs(point.x) + Math.abs(point.y) + Math.abs(point.z); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/CellValueReturnType.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/CellValueReturnType.java new file mode 100644 index 0000000..15d4561 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/CellValueReturnType.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CellValueReturnType extends ReturnType { + @Nonnull + private final Density sampleField; + private final double defaultValue; + + public CellValueReturnType(@Nonnull Density sampleField, double defaultValue, int threadCount) { + this.sampleField = sampleField; + this.defaultValue = defaultValue; + } + + @Override + public double get( + double distance0, + double distance1, + @Nonnull Vector3d samplePosition, + @Nullable Vector3d closestPoint0, + Vector3d closestPoint1, + @Nonnull Density.Context context + ) { + if (closestPoint0 == null) { + return this.defaultValue; + } else { + Density.Context childContext = new Density.Context(context); + childContext.position = closestPoint0; + return this.sampleField.process(childContext); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/CurveReturnType.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/CurveReturnType.java new file mode 100644 index 0000000..275e2c0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/CurveReturnType.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CurveReturnType extends ReturnType { + @Nonnull + private final Double2DoubleFunction curve; + + public CurveReturnType(@Nonnull Double2DoubleFunction curve) { + this.curve = curve; + } + + @Override + public double get( + double distance0, + double distance1, + @Nonnull Vector3d samplePosition, + @Nullable Vector3d closestPoint0, + Vector3d closestPoint1, + @Nullable Density.Context context + ) { + return this.curve.get(distance0); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/DensityReturnType.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/DensityReturnType.java new file mode 100644 index 0000000..d8983a6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/DensityReturnType.java @@ -0,0 +1,136 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.Range; +import com.hypixel.hytale.math.vector.Vector3d; +import it.unimi.dsi.fastutil.objects.Object2DoubleAVLTreeMap; +import it.unimi.dsi.fastutil.objects.Object2DoubleMap; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DensityReturnType extends ReturnType { + @Nonnull + private final Density choiceDensity; + private final double defaultValue; + @Nonnull + private final double[][] delimiters; + @Nonnull + private final Density[] sampleDensities; + private final boolean calculateDistanceFromWall; + + public DensityReturnType( + @Nonnull Density choiceDensity, @Nonnull Map densityDelimiters, boolean calculateDistanceFromWall, double defaultValue, int threadCount + ) { + this.choiceDensity = choiceDensity; + this.defaultValue = defaultValue; + this.calculateDistanceFromWall = calculateDistanceFromWall; + this.delimiters = new double[densityDelimiters.size()][2]; + this.sampleDensities = new Density[densityDelimiters.size()]; + int i = 0; + + for (Map.Entry entry : densityDelimiters.entrySet()) { + this.delimiters[i][0] = entry.getKey().getMin(); + this.delimiters[i][1] = entry.getKey().getMax(); + this.sampleDensities[i] = entry.getValue(); + i++; + } + } + + @Override + public double get( + double distance0, + double distance1, + @Nonnull Vector3d samplePoint, + @Nullable Vector3d closestPoint0, + @Nullable Vector3d closestPoint1, + @Nullable Density.Context context + ) { + double distanceFromWall = Double.MAX_VALUE; + if (closestPoint0 != null && this.calculateDistanceFromWall) { + distance0 = samplePoint.clone().addScaled(closestPoint0, -1.0).length(); + double fromMaxDistance = Math.abs(super.maxDistance - distance0); + if (closestPoint1 == null) { + distanceFromWall = fromMaxDistance; + } else { + distance1 = samplePoint.clone().addScaled(closestPoint1, -1.0).length(); + double l = distance1 / this.maxDistance; + double fromOtherCell = Math.abs(distance1 - distance0) / 2.0; + distanceFromWall = fromOtherCell; + } + } + + Density.Context childContext = null; + double choiceValue = this.defaultValue; + if (closestPoint0 == null) { + return this.defaultValue; + } else { + choiceValue = this.choiceDensity.process(context); + int i = 0; + + for (double[] delimiter : this.delimiters) { + if (choiceValue >= delimiter[0] && choiceValue < delimiter[1]) { + childContext = new Density.Context(context); + childContext.densityAnchor = closestPoint0.clone(); + childContext.distanceFromCellWall = distanceFromWall; + return this.sampleDensities[i].process(childContext); + } + + i++; + } + + return this.defaultValue; + } + } + + private static class Entry { + @Nonnull + private final Object2DoubleMap map; + @Nonnull + private final LinkedList keyHistory; + private final int size; + + public Entry(int size) { + if (size < 0) { + throw new IllegalArgumentException("negative size"); + } else { + this.map = new Object2DoubleAVLTreeMap<>(new DensityReturnType.Vector3dComparator()); + this.keyHistory = new LinkedList<>(); + this.size = size; + } + } + + public boolean containsKey(Vector3d k) { + return this.map.containsKey(k); + } + + public double get(Vector3d k) { + return this.map.getOrDefault(k, 0.0); + } + + public void put(Vector3d k, double v) { + if (this.keyHistory.size() == this.size) { + Vector3d oldKey = this.keyHistory.removeLast(); + this.map.removeDouble(oldKey); + } + + this.map.put(k, v); + this.keyHistory.addFirst(k); + } + } + + private static class Vector3dComparator implements Comparator { + private Vector3dComparator() { + } + + public int compare(@Nonnull Vector3d o1, @Nonnull Vector3d o2) { + if (o1.y < o2.y || o1.x < o2.x || o1.z < o2.z) { + return -1; + } else { + return !(o1.y > o2.y) && !(o1.x > o2.x) && !(o1.z > o2.z) ? 0 : 1; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/Distance2AddReturnType.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/Distance2AddReturnType.java new file mode 100644 index 0000000..887ed35 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/Distance2AddReturnType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Distance2AddReturnType extends ReturnType { + public Distance2AddReturnType() { + } + + @Override + public double get( + double distance0, + double distance1, + @Nonnull Vector3d samplePosition, + @Nullable Vector3d closestPoint0, + Vector3d closestPoint1, + @Nullable Density.Context context + ) { + if (this.maxDistance <= 0.0) { + return 0.0; + } else { + return closestPoint0 == null ? 1.0 : (distance0 + distance1) / this.maxDistance - 1.0; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/Distance2DivReturnType.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/Distance2DivReturnType.java new file mode 100644 index 0000000..8011180 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/Distance2DivReturnType.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Distance2DivReturnType extends ReturnType { + public Distance2DivReturnType() { + } + + @Override + public double get( + double distance0, + double distance1, + @Nonnull Vector3d samplePosition, + @Nullable Vector3d closestPoint0, + Vector3d closestPoint1, + @Nullable Density.Context context + ) { + if (this.maxDistance <= 0.0) { + return 0.0; + } else if (closestPoint0 == null) { + return 1.0; + } else { + distance0 /= this.maxDistance; + distance1 /= this.maxDistance; + return distance0 / distance1 * 2.0 - 1.0; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/Distance2MulReturnType.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/Distance2MulReturnType.java new file mode 100644 index 0000000..0c73e12 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/Distance2MulReturnType.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Distance2MulReturnType extends ReturnType { + public Distance2MulReturnType() { + } + + @Override + public double get( + double distance0, + double distance1, + @Nonnull Vector3d samplePosition, + @Nullable Vector3d closestPoint0, + Vector3d closestPoint1, + @Nullable Density.Context context + ) { + if (this.maxDistance <= 0.0) { + return 0.0; + } else if (closestPoint0 == null) { + return 1.0; + } else { + distance0 /= this.maxDistance; + distance1 /= this.maxDistance; + return distance1 * distance0 * 2.0 - 1.0; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/Distance2ReturnType.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/Distance2ReturnType.java new file mode 100644 index 0000000..f46bea5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/Distance2ReturnType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Distance2ReturnType extends ReturnType { + public Distance2ReturnType() { + } + + @Override + public double get( + double distance0, + double distance1, + @Nonnull Vector3d samplePosition, + @Nullable Vector3d closestPoint0, + Vector3d closestPoint1, + @Nullable Density.Context context + ) { + if (this.maxDistance <= 0.0) { + return 0.0; + } else { + return closestPoint0 == null ? 1.0 : distance1 / this.maxDistance * 2.0 - 1.0; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/Distance2SubReturnType.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/Distance2SubReturnType.java new file mode 100644 index 0000000..d48e327 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/Distance2SubReturnType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Distance2SubReturnType extends ReturnType { + public Distance2SubReturnType() { + } + + @Override + public double get( + double distance0, + double distance1, + @Nonnull Vector3d samplePosition, + @Nullable Vector3d closestPoint0, + Vector3d closestPoint1, + @Nullable Density.Context context + ) { + if (this.maxDistance <= 0.0) { + return 0.0; + } else { + return closestPoint0 == null ? -1.0 : (distance1 - distance0) / this.maxDistance * 2.0 - 1.0; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/DistanceReturnType.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/DistanceReturnType.java new file mode 100644 index 0000000..8219979 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/DistanceReturnType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DistanceReturnType extends ReturnType { + public DistanceReturnType() { + } + + @Override + public double get( + double distance0, + double distance1, + @Nonnull Vector3d samplePosition, + @Nullable Vector3d closestPoint0, + Vector3d closestPoint1, + @Nullable Density.Context context + ) { + if (this.maxDistance <= 0.0) { + return 0.0; + } else { + return closestPoint0 == null ? 1.0 : distance0 / this.maxDistance * 2.0 - 1.0; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/ReturnType.java b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/ReturnType.java new file mode 100644 index 0000000..384875e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/density/nodes/positions/returntypes/ReturnType.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class ReturnType { + protected double maxDistance = Double.MAX_VALUE; + + public ReturnType() { + } + + public abstract double get( + double var1, double var3, @Nonnull Vector3d var5, @Nullable Vector3d var6, @Nullable Vector3d var7, @Nullable Density.Context var8 + ); + + public void setMaxDistance(double maxDistance) { + if (maxDistance < 0.0) { + throw new IllegalArgumentException("negative distance"); + } else { + this.maxDistance = maxDistance; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/environmentproviders/ConstantEnvironmentProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/environmentproviders/ConstantEnvironmentProvider.java new file mode 100644 index 0000000..ef27461 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/environmentproviders/ConstantEnvironmentProvider.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.builtin.hytalegenerator.environmentproviders; + +import javax.annotation.Nonnull; + +public class ConstantEnvironmentProvider extends EnvironmentProvider { + private final int value; + + public ConstantEnvironmentProvider(int value) { + this.value = value; + } + + @Override + public int getValue(@Nonnull EnvironmentProvider.Context context) { + return this.value; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/environmentproviders/DensityDelimitedEnvironmentProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/environmentproviders/DensityDelimitedEnvironmentProvider.java new file mode 100644 index 0000000..9023a9a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/environmentproviders/DensityDelimitedEnvironmentProvider.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.builtin.hytalegenerator.environmentproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.delimiters.DelimiterDouble; +import com.hypixel.hytale.builtin.hytalegenerator.delimiters.RangeDouble; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class DensityDelimitedEnvironmentProvider extends EnvironmentProvider { + @Nonnull + private final List> delimiters = new ArrayList<>(); + @Nonnull + private final Density density; + + public DensityDelimitedEnvironmentProvider(@Nonnull List> delimiters, @Nonnull Density density) { + for (DelimiterDouble delimiter : delimiters) { + RangeDouble range = delimiter.getRange(); + if (!(range.min() >= range.max())) { + this.delimiters.add(delimiter); + } + } + + this.density = density; + } + + @Override + public int getValue(@Nonnull EnvironmentProvider.Context context) { + double densityValue = this.density.process(new Density.Context(context)); + + for (DelimiterDouble delimiter : this.delimiters) { + if (delimiter.getRange().contains(densityValue)) { + return delimiter.getValue().getValue(context); + } + } + + return 0; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/environmentproviders/EnvironmentProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/environmentproviders/EnvironmentProvider.java new file mode 100644 index 0000000..90484e3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/environmentproviders/EnvironmentProvider.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.hytalegenerator.environmentproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public abstract class EnvironmentProvider { + public EnvironmentProvider() { + } + + public abstract int getValue(@Nonnull EnvironmentProvider.Context var1); + + @Nonnull + public static EnvironmentProvider noEnvironmentProvider() { + return new ConstantEnvironmentProvider(0); + } + + public static class Context { + public Vector3i position; + public WorkerIndexer.Id workerId; + + public Context(@Nonnull Vector3i position, WorkerIndexer.Id workerId) { + this.position = position; + this.workerId = workerId; + } + + public Context(@Nonnull EnvironmentProvider.Context other) { + this.position = other.position; + this.workerId = other.workerId; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/fields/FastNoiseLite.java b/src/com/hypixel/hytale/builtin/hytalegenerator/fields/FastNoiseLite.java new file mode 100644 index 0000000..7fb414d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/fields/FastNoiseLite.java @@ -0,0 +1,3911 @@ +package com.hypixel.hytale.builtin.hytalegenerator.fields; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class FastNoiseLite { + private int mSeed = 1337; + private float mFrequency = 0.01F; + private FastNoiseLite.NoiseType mNoiseType = FastNoiseLite.NoiseType.OpenSimplex2; + private FastNoiseLite.RotationType3D mRotationType3D = FastNoiseLite.RotationType3D.None; + @Nonnull + private FastNoiseLite.TransformType3D mTransformType3D = FastNoiseLite.TransformType3D.DefaultOpenSimplex2; + private FastNoiseLite.FractalType mFractalType = FastNoiseLite.FractalType.None; + private int mOctaves = 3; + private float mLacunarity = 2.0F; + private float mGain = 0.5F; + private float mWeightedStrength = 0.0F; + private float mPingPongStrength = 2.0F; + private float mFractalBounding = 0.5714286F; + private FastNoiseLite.CellularDistanceFunction mCellularDistanceFunction = FastNoiseLite.CellularDistanceFunction.EuclideanSq; + private FastNoiseLite.CellularReturnType mCellularReturnType = FastNoiseLite.CellularReturnType.Distance; + private float mCellularJitterModifier = 1.0F; + private FastNoiseLite.DomainWarpType mDomainWarpType = FastNoiseLite.DomainWarpType.OpenSimplex2; + @Nonnull + private FastNoiseLite.TransformType3D mWarpTransformType3D = FastNoiseLite.TransformType3D.DefaultOpenSimplex2; + private float mDomainWarpAmp = 1.0F; + private float mDomainWarpFreq = 1.0F; + private static final float[] Gradients2D = new float[]{ + 0.13052619F, + 0.9914449F, + 0.38268343F, + 0.9238795F, + 0.6087614F, + 0.7933533F, + 0.7933533F, + 0.6087614F, + 0.9238795F, + 0.38268343F, + 0.9914449F, + 0.13052619F, + 0.9914449F, + -0.13052619F, + 0.9238795F, + -0.38268343F, + 0.7933533F, + -0.6087614F, + 0.6087614F, + -0.7933533F, + 0.38268343F, + -0.9238795F, + 0.13052619F, + -0.9914449F, + -0.13052619F, + -0.9914449F, + -0.38268343F, + -0.9238795F, + -0.6087614F, + -0.7933533F, + -0.7933533F, + -0.6087614F, + -0.9238795F, + -0.38268343F, + -0.9914449F, + -0.13052619F, + -0.9914449F, + 0.13052619F, + -0.9238795F, + 0.38268343F, + -0.7933533F, + 0.6087614F, + -0.6087614F, + 0.7933533F, + -0.38268343F, + 0.9238795F, + -0.13052619F, + 0.9914449F, + 0.13052619F, + 0.9914449F, + 0.38268343F, + 0.9238795F, + 0.6087614F, + 0.7933533F, + 0.7933533F, + 0.6087614F, + 0.9238795F, + 0.38268343F, + 0.9914449F, + 0.13052619F, + 0.9914449F, + -0.13052619F, + 0.9238795F, + -0.38268343F, + 0.7933533F, + -0.6087614F, + 0.6087614F, + -0.7933533F, + 0.38268343F, + -0.9238795F, + 0.13052619F, + -0.9914449F, + -0.13052619F, + -0.9914449F, + -0.38268343F, + -0.9238795F, + -0.6087614F, + -0.7933533F, + -0.7933533F, + -0.6087614F, + -0.9238795F, + -0.38268343F, + -0.9914449F, + -0.13052619F, + -0.9914449F, + 0.13052619F, + -0.9238795F, + 0.38268343F, + -0.7933533F, + 0.6087614F, + -0.6087614F, + 0.7933533F, + -0.38268343F, + 0.9238795F, + -0.13052619F, + 0.9914449F, + 0.13052619F, + 0.9914449F, + 0.38268343F, + 0.9238795F, + 0.6087614F, + 0.7933533F, + 0.7933533F, + 0.6087614F, + 0.9238795F, + 0.38268343F, + 0.9914449F, + 0.13052619F, + 0.9914449F, + -0.13052619F, + 0.9238795F, + -0.38268343F, + 0.7933533F, + -0.6087614F, + 0.6087614F, + -0.7933533F, + 0.38268343F, + -0.9238795F, + 0.13052619F, + -0.9914449F, + -0.13052619F, + -0.9914449F, + -0.38268343F, + -0.9238795F, + -0.6087614F, + -0.7933533F, + -0.7933533F, + -0.6087614F, + -0.9238795F, + -0.38268343F, + -0.9914449F, + -0.13052619F, + -0.9914449F, + 0.13052619F, + -0.9238795F, + 0.38268343F, + -0.7933533F, + 0.6087614F, + -0.6087614F, + 0.7933533F, + -0.38268343F, + 0.9238795F, + -0.13052619F, + 0.9914449F, + 0.13052619F, + 0.9914449F, + 0.38268343F, + 0.9238795F, + 0.6087614F, + 0.7933533F, + 0.7933533F, + 0.6087614F, + 0.9238795F, + 0.38268343F, + 0.9914449F, + 0.13052619F, + 0.9914449F, + -0.13052619F, + 0.9238795F, + -0.38268343F, + 0.7933533F, + -0.6087614F, + 0.6087614F, + -0.7933533F, + 0.38268343F, + -0.9238795F, + 0.13052619F, + -0.9914449F, + -0.13052619F, + -0.9914449F, + -0.38268343F, + -0.9238795F, + -0.6087614F, + -0.7933533F, + -0.7933533F, + -0.6087614F, + -0.9238795F, + -0.38268343F, + -0.9914449F, + -0.13052619F, + -0.9914449F, + 0.13052619F, + -0.9238795F, + 0.38268343F, + -0.7933533F, + 0.6087614F, + -0.6087614F, + 0.7933533F, + -0.38268343F, + 0.9238795F, + -0.13052619F, + 0.9914449F, + 0.13052619F, + 0.9914449F, + 0.38268343F, + 0.9238795F, + 0.6087614F, + 0.7933533F, + 0.7933533F, + 0.6087614F, + 0.9238795F, + 0.38268343F, + 0.9914449F, + 0.13052619F, + 0.9914449F, + -0.13052619F, + 0.9238795F, + -0.38268343F, + 0.7933533F, + -0.6087614F, + 0.6087614F, + -0.7933533F, + 0.38268343F, + -0.9238795F, + 0.13052619F, + -0.9914449F, + -0.13052619F, + -0.9914449F, + -0.38268343F, + -0.9238795F, + -0.6087614F, + -0.7933533F, + -0.7933533F, + -0.6087614F, + -0.9238795F, + -0.38268343F, + -0.9914449F, + -0.13052619F, + -0.9914449F, + 0.13052619F, + -0.9238795F, + 0.38268343F, + -0.7933533F, + 0.6087614F, + -0.6087614F, + 0.7933533F, + -0.38268343F, + 0.9238795F, + -0.13052619F, + 0.9914449F, + 0.38268343F, + 0.9238795F, + 0.9238795F, + 0.38268343F, + 0.9238795F, + -0.38268343F, + 0.38268343F, + -0.9238795F, + -0.38268343F, + -0.9238795F, + -0.9238795F, + -0.38268343F, + -0.9238795F, + 0.38268343F, + -0.38268343F, + 0.9238795F + }; + private static final float[] RandVecs2D = new float[]{ + -0.2700222F, + -0.9628541F, + 0.38630927F, + -0.9223693F, + 0.04444859F, + -0.9990117F, + -0.59925234F, + -0.80056024F, + -0.781928F, + 0.62336874F, + 0.9464672F, + 0.32279992F, + -0.6514147F, + -0.7587219F, + 0.93784726F, + 0.34704837F, + -0.8497876F, + -0.52712524F, + -0.87904257F, + 0.47674325F, + -0.8923003F, + -0.45144236F, + -0.37984443F, + -0.9250504F, + -0.9951651F, + 0.09821638F, + 0.7724398F, + -0.635088F, + 0.75732833F, + -0.6530343F, + -0.9928005F, + -0.119780056F, + -0.05326657F, + 0.99858034F, + 0.97542536F, + -0.22033007F, + -0.76650184F, + 0.64224213F, + 0.9916367F, + 0.12906061F, + -0.99469686F, + 0.10285038F, + -0.53792053F, + -0.8429955F, + 0.50228155F, + -0.86470413F, + 0.45598215F, + -0.8899889F, + -0.8659131F, + -0.50019443F, + 0.08794584F, + -0.9961253F, + -0.5051685F, + 0.8630207F, + 0.7753185F, + -0.6315704F, + -0.69219446F, + 0.72171104F, + -0.51916593F, + -0.85467345F, + 0.8978623F, + -0.4402764F, + -0.17067741F, + 0.98532695F, + -0.935343F, + -0.35374206F, + -0.99924046F, + 0.038967468F, + -0.2882064F, + -0.9575683F, + -0.96638113F, + 0.2571138F, + -0.87597144F, + -0.48236302F, + -0.8303123F, + -0.55729836F, + 0.051101338F, + -0.99869347F, + -0.85583735F, + -0.51724505F, + 0.098870255F, + 0.9951003F, + 0.9189016F, + 0.39448678F, + -0.24393758F, + -0.96979094F, + -0.81214094F, + -0.5834613F, + -0.99104315F, + 0.13354214F, + 0.8492424F, + -0.52800316F, + -0.9717839F, + -0.23587295F, + 0.9949457F, + 0.10041421F, + 0.6241065F, + -0.7813392F, + 0.6629103F, + 0.74869883F, + -0.7197418F, + 0.6942418F, + -0.8143371F, + -0.58039224F, + 0.10452105F, + -0.9945227F, + -0.10659261F, + -0.99430275F, + 0.44579968F, + -0.8951328F, + 0.105547406F, + 0.99441427F, + -0.9927903F, + 0.11986445F, + -0.83343667F, + 0.55261505F, + 0.9115562F, + -0.4111756F, + 0.8285545F, + -0.55990845F, + 0.7217098F, + -0.6921958F, + 0.49404928F, + -0.8694339F, + -0.36523214F, + -0.9309165F, + -0.9696607F, + 0.24445485F, + 0.089255095F, + -0.9960088F, + 0.5354071F, + -0.8445941F, + -0.10535762F, + 0.9944344F, + -0.98902845F, + 0.1477251F, + 0.004856105F, + 0.9999882F, + 0.98855984F, + 0.15082914F, + 0.92861295F, + -0.37104982F, + -0.5832394F, + -0.8123003F, + 0.30152076F, + 0.9534596F, + -0.95751107F, + 0.28839657F, + 0.9715802F, + -0.23671055F, + 0.2299818F, + 0.97319496F, + 0.9557638F, + -0.2941352F, + 0.7409561F, + 0.67155343F, + -0.9971514F, + -0.07542631F, + 0.69057107F, + -0.7232645F, + -0.2907137F, + -0.9568101F, + 0.5912778F, + -0.80646795F, + -0.94545925F, + -0.3257405F, + 0.66644555F, + 0.7455537F, + 0.6236135F, + 0.78173286F, + 0.9126994F, + -0.40863165F, + -0.8191762F, + 0.57354194F, + -0.8812746F, + -0.4726046F, + 0.99533135F, + 0.09651673F, + 0.98556507F, + -0.16929697F, + -0.8495981F, + 0.52743065F, + 0.6174854F, + -0.78658235F, + 0.85081565F, + 0.5254643F, + 0.99850327F, + -0.0546925F, + 0.19713716F, + -0.98037595F, + 0.66078556F, + -0.7505747F, + -0.030974941F, + 0.9995202F, + -0.6731661F, + 0.73949134F, + -0.71950185F, + -0.69449055F, + 0.97275114F, + 0.2318516F, + 0.9997059F, + -0.02425069F, + 0.44217876F, + -0.89692694F, + 0.9981351F, + -0.061043672F, + -0.9173661F, + -0.39804456F, + -0.81500566F, + -0.579453F, + -0.87893313F, + 0.476945F, + 0.015860584F, + 0.99987423F, + -0.8095465F, + 0.5870558F, + -0.9165899F, + -0.39982867F, + -0.8023543F, + 0.5968481F, + -0.5176738F, + 0.85557806F, + -0.8154407F, + -0.57884055F, + 0.40220103F, + -0.91555136F, + -0.9052557F, + -0.4248672F, + 0.7317446F, + 0.681579F, + -0.56476325F, + -0.825253F, + -0.8403276F, + -0.54207885F, + -0.93142813F, + 0.36392525F, + 0.52381986F, + 0.85182905F, + 0.7432804F, + -0.66898F, + -0.9853716F, + -0.17041974F, + 0.46014687F, + 0.88784283F, + 0.8258554F, + 0.56388193F, + 0.6182366F, + 0.785992F, + 0.83315027F, + -0.55304664F, + 0.15003075F, + 0.9886813F, + -0.6623304F, + -0.7492119F, + -0.66859865F, + 0.74362344F, + 0.7025606F, + 0.7116239F, + -0.54193896F, + -0.84041786F, + -0.33886164F, + 0.9408362F, + 0.833153F, + 0.55304253F, + -0.29897207F, + -0.95426184F, + 0.2638523F, + 0.9645631F, + 0.12410874F, + -0.9922686F, + -0.7282649F, + -0.6852957F, + 0.69625F, + 0.71779937F, + -0.91835356F, + 0.395761F, + -0.6326102F, + -0.7744703F, + -0.9331892F, + -0.35938552F, + -0.11537793F, + -0.99332166F, + 0.9514975F, + -0.30765656F, + -0.08987977F, + -0.9959526F, + 0.6678497F, + 0.7442962F, + 0.79524004F, + -0.6062947F, + -0.6462007F, + -0.7631675F, + -0.27335986F, + 0.96191186F, + 0.966959F, + -0.25493184F, + -0.9792895F, + 0.20246519F, + -0.5369503F, + -0.84361386F, + -0.27003646F, + -0.9628501F, + -0.6400277F, + 0.76835185F, + -0.78545374F, + -0.6189204F, + 0.060059056F, + -0.9981948F, + -0.024557704F, + 0.9996984F, + -0.65983623F, + 0.7514095F, + -0.62538946F, + -0.7803128F, + -0.6210409F, + -0.7837782F, + 0.8348889F, + 0.55041856F, + -0.15922752F, + 0.9872419F, + 0.83676225F, + 0.54756635F, + -0.8675754F, + -0.4973057F, + -0.20226626F, + -0.97933054F, + 0.939919F, + 0.34139755F, + 0.98774046F, + -0.1561049F, + -0.90344554F, + 0.42870283F, + 0.12698042F, + -0.9919052F, + -0.3819601F, + 0.92417884F, + 0.9754626F, + 0.22016525F, + -0.32040158F, + -0.94728184F, + -0.9874761F, + 0.15776874F, + 0.025353484F, + -0.99967855F, + 0.4835131F, + -0.8753371F, + -0.28508F, + -0.9585037F, + -0.06805516F, + -0.99768156F, + -0.7885244F, + -0.61500347F, + 0.3185392F, + -0.9479097F, + 0.8880043F, + 0.45983514F, + 0.64769214F, + -0.76190215F, + 0.98202413F, + 0.18875542F, + 0.93572754F, + -0.35272372F, + -0.88948953F, + 0.45695552F, + 0.7922791F, + 0.6101588F, + 0.74838185F, + 0.66326815F, + -0.728893F, + -0.68462765F, + 0.8729033F, + -0.48789328F, + 0.8288346F, + 0.5594937F, + 0.08074567F, + 0.99673474F, + 0.97991484F, + -0.1994165F, + -0.5807307F, + -0.81409574F, + -0.47000498F, + -0.8826638F, + 0.2409493F, + 0.9705377F, + 0.9437817F, + -0.33056942F, + -0.89279985F, + -0.45045355F, + -0.80696225F, + 0.59060305F, + 0.062589735F, + 0.99803936F, + -0.93125975F, + 0.36435598F, + 0.57774496F, + 0.81621736F, + -0.3360096F, + -0.9418586F, + 0.69793206F, + -0.71616393F, + -0.0020081573F, + -0.999998F, + -0.18272944F, + -0.98316324F, + -0.6523912F, + 0.7578824F, + -0.43026268F, + -0.9027037F, + -0.9985126F, + -0.054520912F, + -0.010281022F, + -0.99994713F, + -0.49460712F, + 0.86911666F, + -0.299935F, + 0.95395964F, + 0.8165472F, + 0.5772787F, + 0.26974604F, + 0.9629315F, + -0.7306287F, + -0.68277496F, + -0.7590952F, + -0.65097964F, + -0.9070538F, + 0.4210146F, + -0.5104861F, + -0.859886F, + 0.86133504F, + 0.5080373F, + 0.50078815F, + -0.8655699F, + -0.6541582F, + 0.7563578F, + -0.83827555F, + -0.54524684F, + 0.6940071F, + 0.7199682F, + 0.06950936F, + 0.9975813F, + 0.17029423F, + -0.9853933F, + 0.26959732F, + 0.9629731F, + 0.55196124F, + -0.83386976F, + 0.2256575F, + -0.9742067F, + 0.42152628F, + -0.9068162F, + 0.48818734F, + -0.87273884F, + -0.3683855F, + -0.92967314F, + -0.98253906F, + 0.18605645F, + 0.81256473F, + 0.582871F, + 0.3196461F, + -0.947537F, + 0.9570914F, + 0.28978625F, + -0.6876655F, + -0.7260276F, + -0.9988771F, + -0.04737673F, + -0.1250179F, + 0.9921545F, + -0.82801336F, + 0.56070834F, + 0.93248636F, + -0.36120513F, + 0.63946533F, + 0.7688199F, + -0.016238471F, + -0.99986815F, + -0.99550146F, + -0.094746135F, + -0.8145332F, + 0.580117F, + 0.4037328F, + -0.91487694F, + 0.9944263F, + 0.10543368F, + -0.16247116F, + 0.9867133F, + -0.9949488F, + -0.10038388F, + -0.69953024F, + 0.714603F, + 0.5263415F, + -0.85027325F, + -0.5395222F, + 0.8419714F, + 0.65793705F, + 0.7530729F, + 0.014267588F, + -0.9998982F, + -0.6734384F, + 0.7392433F, + 0.6394121F, + -0.7688642F, + 0.9211571F, + 0.38919085F, + -0.14663722F, + -0.98919034F, + -0.7823181F, + 0.6228791F, + -0.5039611F, + -0.8637264F, + -0.774312F, + -0.632804F + }; + private static final float[] Gradients3D = new float[]{ + 0.0F, + 1.0F, + 1.0F, + 0.0F, + 0.0F, + -1.0F, + 1.0F, + 0.0F, + 0.0F, + 1.0F, + -1.0F, + 0.0F, + 0.0F, + -1.0F, + -1.0F, + 0.0F, + 1.0F, + 0.0F, + 1.0F, + 0.0F, + -1.0F, + 0.0F, + 1.0F, + 0.0F, + 1.0F, + 0.0F, + -1.0F, + 0.0F, + -1.0F, + 0.0F, + -1.0F, + 0.0F, + 1.0F, + 1.0F, + 0.0F, + 0.0F, + -1.0F, + 1.0F, + 0.0F, + 0.0F, + 1.0F, + -1.0F, + 0.0F, + 0.0F, + -1.0F, + -1.0F, + 0.0F, + 0.0F, + 0.0F, + 1.0F, + 1.0F, + 0.0F, + 0.0F, + -1.0F, + 1.0F, + 0.0F, + 0.0F, + 1.0F, + -1.0F, + 0.0F, + 0.0F, + -1.0F, + -1.0F, + 0.0F, + 1.0F, + 0.0F, + 1.0F, + 0.0F, + -1.0F, + 0.0F, + 1.0F, + 0.0F, + 1.0F, + 0.0F, + -1.0F, + 0.0F, + -1.0F, + 0.0F, + -1.0F, + 0.0F, + 1.0F, + 1.0F, + 0.0F, + 0.0F, + -1.0F, + 1.0F, + 0.0F, + 0.0F, + 1.0F, + -1.0F, + 0.0F, + 0.0F, + -1.0F, + -1.0F, + 0.0F, + 0.0F, + 0.0F, + 1.0F, + 1.0F, + 0.0F, + 0.0F, + -1.0F, + 1.0F, + 0.0F, + 0.0F, + 1.0F, + -1.0F, + 0.0F, + 0.0F, + -1.0F, + -1.0F, + 0.0F, + 1.0F, + 0.0F, + 1.0F, + 0.0F, + -1.0F, + 0.0F, + 1.0F, + 0.0F, + 1.0F, + 0.0F, + -1.0F, + 0.0F, + -1.0F, + 0.0F, + -1.0F, + 0.0F, + 1.0F, + 1.0F, + 0.0F, + 0.0F, + -1.0F, + 1.0F, + 0.0F, + 0.0F, + 1.0F, + -1.0F, + 0.0F, + 0.0F, + -1.0F, + -1.0F, + 0.0F, + 0.0F, + 0.0F, + 1.0F, + 1.0F, + 0.0F, + 0.0F, + -1.0F, + 1.0F, + 0.0F, + 0.0F, + 1.0F, + -1.0F, + 0.0F, + 0.0F, + -1.0F, + -1.0F, + 0.0F, + 1.0F, + 0.0F, + 1.0F, + 0.0F, + -1.0F, + 0.0F, + 1.0F, + 0.0F, + 1.0F, + 0.0F, + -1.0F, + 0.0F, + -1.0F, + 0.0F, + -1.0F, + 0.0F, + 1.0F, + 1.0F, + 0.0F, + 0.0F, + -1.0F, + 1.0F, + 0.0F, + 0.0F, + 1.0F, + -1.0F, + 0.0F, + 0.0F, + -1.0F, + -1.0F, + 0.0F, + 0.0F, + 0.0F, + 1.0F, + 1.0F, + 0.0F, + 0.0F, + -1.0F, + 1.0F, + 0.0F, + 0.0F, + 1.0F, + -1.0F, + 0.0F, + 0.0F, + -1.0F, + -1.0F, + 0.0F, + 1.0F, + 0.0F, + 1.0F, + 0.0F, + -1.0F, + 0.0F, + 1.0F, + 0.0F, + 1.0F, + 0.0F, + -1.0F, + 0.0F, + -1.0F, + 0.0F, + -1.0F, + 0.0F, + 1.0F, + 1.0F, + 0.0F, + 0.0F, + -1.0F, + 1.0F, + 0.0F, + 0.0F, + 1.0F, + -1.0F, + 0.0F, + 0.0F, + -1.0F, + -1.0F, + 0.0F, + 0.0F, + 1.0F, + 1.0F, + 0.0F, + 0.0F, + 0.0F, + -1.0F, + 1.0F, + 0.0F, + -1.0F, + 1.0F, + 0.0F, + 0.0F, + 0.0F, + -1.0F, + -1.0F, + 0.0F + }; + private static final float[] randVecs3D = new float[]{ + -0.7292737F, + -0.66184396F, + 0.17355819F, + 0.0F, + 0.7902921F, + -0.5480887F, + -0.2739291F, + 0.0F, + 0.7217579F, + 0.62262124F, + -0.3023381F, + 0.0F, + 0.5656831F, + -0.8208298F, + -0.079000026F, + 0.0F, + 0.76004905F, + -0.55559796F, + -0.33709997F, + 0.0F, + 0.37139457F, + 0.50112647F, + 0.78162545F, + 0.0F, + -0.12770624F, + -0.4254439F, + -0.8959289F, + 0.0F, + -0.2881561F, + -0.5815839F, + 0.7607406F, + 0.0F, + 0.5849561F, + -0.6628202F, + -0.4674352F, + 0.0F, + 0.33071712F, + 0.039165374F, + 0.94291687F, + 0.0F, + 0.8712122F, + -0.41133744F, + -0.26793817F, + 0.0F, + 0.580981F, + 0.7021916F, + 0.41156778F, + 0.0F, + 0.5037569F, + 0.6330057F, + -0.5878204F, + 0.0F, + 0.44937122F, + 0.6013902F, + 0.6606023F, + 0.0F, + -0.6878404F, + 0.090188906F, + -0.7202372F, + 0.0F, + -0.59589565F, + -0.64693505F, + 0.47579765F, + 0.0F, + -0.5127052F, + 0.1946922F, + -0.83619875F, + 0.0F, + -0.99115074F, + -0.054102764F, + -0.12121531F, + 0.0F, + -0.21497211F, + 0.9720882F, + -0.09397608F, + 0.0F, + -0.7518651F, + -0.54280573F, + 0.37424695F, + 0.0F, + 0.5237069F, + 0.8516377F, + -0.021078179F, + 0.0F, + 0.6333505F, + 0.19261672F, + -0.74951047F, + 0.0F, + -0.06788242F, + 0.39983058F, + 0.9140719F, + 0.0F, + -0.55386287F, + -0.47298968F, + -0.6852129F, + 0.0F, + -0.72614557F, + -0.5911991F, + 0.35099334F, + 0.0F, + -0.9229275F, + -0.17828088F, + 0.34120494F, + 0.0F, + -0.6968815F, + 0.65112746F, + 0.30064803F, + 0.0F, + 0.96080446F, + -0.20983632F, + -0.18117249F, + 0.0F, + 0.068171464F, + -0.9743405F, + 0.21450691F, + 0.0F, + -0.3577285F, + -0.6697087F, + -0.65078455F, + 0.0F, + -0.18686211F, + 0.7648617F, + -0.61649746F, + 0.0F, + -0.65416974F, + 0.3967915F, + 0.64390874F, + 0.0F, + 0.699334F, + -0.6164538F, + 0.36182392F, + 0.0F, + -0.15466657F, + 0.6291284F, + 0.7617583F, + 0.0F, + -0.6841613F, + -0.2580482F, + -0.68215424F, + 0.0F, + 0.5383981F, + 0.4258655F, + 0.727163F, + 0.0F, + -0.5026988F, + -0.7939833F, + -0.3418837F, + 0.0F, + 0.32029718F, + 0.28344154F, + 0.9039196F, + 0.0F, + 0.86832273F, + -3.7626564E-4F, + -0.49599952F, + 0.0F, + 0.79112005F, + -0.085110456F, + 0.60571057F, + 0.0F, + -0.04011016F, + -0.43972486F, + 0.8972364F, + 0.0F, + 0.914512F, + 0.35793462F, + -0.18854876F, + 0.0F, + -0.96120393F, + -0.27564842F, + 0.010246669F, + 0.0F, + 0.65103614F, + -0.28777993F, + -0.70237786F, + 0.0F, + -0.20417863F, + 0.73652375F, + 0.6448596F, + 0.0F, + -0.7718264F, + 0.37906268F, + 0.5104856F, + 0.0F, + -0.30600828F, + -0.7692988F, + 0.56083715F, + 0.0F, + 0.45400733F, + -0.5024843F, + 0.73578995F, + 0.0F, + 0.48167956F, + 0.6021208F, + -0.636738F, + 0.0F, + 0.69619805F, + -0.32221973F, + 0.6414692F, + 0.0F, + -0.65321606F, + -0.6781149F, + 0.33685157F, + 0.0F, + 0.50893015F, + -0.61546624F, + -0.60182345F, + 0.0F, + -0.16359198F, + -0.9133605F, + -0.37284088F, + 0.0F, + 0.5240802F, + -0.8437664F, + 0.11575059F, + 0.0F, + 0.5902587F, + 0.4983818F, + -0.63498837F, + 0.0F, + 0.5863228F, + 0.49476475F, + 0.6414308F, + 0.0F, + 0.6779335F, + 0.23413453F, + 0.6968409F, + 0.0F, + 0.7177054F, + -0.68589795F, + 0.12017863F, + 0.0F, + -0.532882F, + -0.5205125F, + 0.6671608F, + 0.0F, + -0.8654874F, + -0.07007271F, + -0.4960054F, + 0.0F, + -0.286181F, + 0.79520893F, + 0.53454953F, + 0.0F, + -0.048495296F, + 0.98108363F, + -0.18741156F, + 0.0F, + -0.63585216F, + 0.60583484F, + 0.47818002F, + 0.0F, + 0.62547946F, + -0.28616196F, + 0.72586966F, + 0.0F, + -0.258526F, + 0.50619495F, + -0.8227582F, + 0.0F, + 0.021363068F, + 0.50640166F, + -0.862033F, + 0.0F, + 0.20011178F, + 0.85992634F, + 0.46955505F, + 0.0F, + 0.47435614F, + 0.6014985F, + -0.6427953F, + 0.0F, + 0.6622994F, + -0.52024746F, + -0.539168F, + 0.0F, + 0.08084973F, + -0.65327203F, + 0.7527941F, + 0.0F, + -0.6893687F, + 0.059286036F, + 0.7219805F, + 0.0F, + -0.11218871F, + -0.96731853F, + 0.22739525F, + 0.0F, + 0.7344116F, + 0.59796685F, + -0.3210533F, + 0.0F, + 0.5789393F, + -0.24888498F, + 0.776457F, + 0.0F, + 0.69881827F, + 0.35571697F, + -0.6205791F, + 0.0F, + -0.86368454F, + -0.27487713F, + -0.4224826F, + 0.0F, + -0.4247028F, + -0.46408808F, + 0.77733505F, + 0.0F, + 0.5257723F, + -0.84270173F, + 0.11583299F, + 0.0F, + 0.93438303F, + 0.31630248F, + -0.16395439F, + 0.0F, + -0.10168364F, + -0.8057303F, + -0.58348876F, + 0.0F, + -0.6529239F, + 0.50602126F, + -0.5635893F, + 0.0F, + -0.24652861F, + -0.9668206F, + -0.06694497F, + 0.0F, + -0.9776897F, + -0.20992506F, + -0.0073688254F, + 0.0F, + 0.7736893F, + 0.57342446F, + 0.2694238F, + 0.0F, + -0.6095088F, + 0.4995679F, + 0.6155737F, + 0.0F, + 0.5794535F, + 0.7434547F, + 0.33392924F, + 0.0F, + -0.8226211F, + 0.081425816F, + 0.56272936F, + 0.0F, + -0.51038545F, + 0.47036678F, + 0.719904F, + 0.0F, + -0.5764972F, + -0.072316565F, + -0.81389266F, + 0.0F, + 0.7250629F, + 0.39499715F, + -0.56414634F, + 0.0F, + -0.1525424F, + 0.48608407F, + -0.8604958F, + 0.0F, + -0.55509764F, + -0.49578208F, + 0.6678823F, + 0.0F, + -0.18836144F, + 0.91458696F, + 0.35784173F, + 0.0F, + 0.76255566F, + -0.54144084F, + -0.35404897F, + 0.0F, + -0.5870232F, + -0.3226498F, + -0.7424964F, + 0.0F, + 0.30511242F, + 0.2262544F, + -0.9250488F, + 0.0F, + 0.63795763F, + 0.57724243F, + -0.50970703F, + 0.0F, + -0.5966776F, + 0.14548524F, + -0.7891831F, + 0.0F, + -0.65833056F, + 0.65554875F, + -0.36994147F, + 0.0F, + 0.74348927F, + 0.23510846F, + 0.6260573F, + 0.0F, + 0.5562114F, + 0.82643604F, + -0.08736329F, + 0.0F, + -0.302894F, + -0.8251527F, + 0.47684193F, + 0.0F, + 0.11293438F, + -0.9858884F, + -0.123571075F, + 0.0F, + 0.5937653F, + -0.5896814F, + 0.5474657F, + 0.0F, + 0.6757964F, + -0.58357584F, + -0.45026484F, + 0.0F, + 0.7242303F, + -0.11527198F, + 0.67985505F, + 0.0F, + -0.9511914F, + 0.0753624F, + -0.29925808F, + 0.0F, + 0.2539471F, + -0.18863393F, + 0.9486454F, + 0.0F, + 0.5714336F, + -0.16794509F, + -0.8032796F, + 0.0F, + -0.06778235F, + 0.39782694F, + 0.9149532F, + 0.0F, + 0.6074973F, + 0.73306F, + -0.30589226F, + 0.0F, + -0.54354787F, + 0.16758224F, + 0.8224791F, + 0.0F, + -0.5876678F, + -0.3380045F, + -0.7351187F, + 0.0F, + -0.79675627F, + 0.040978227F, + -0.60290986F, + 0.0F, + -0.19963509F, + 0.8706295F, + 0.4496111F, + 0.0F, + -0.027876602F, + -0.91062325F, + -0.4122962F, + 0.0F, + -0.7797626F, + -0.6257635F, + 0.019757755F, + 0.0F, + -0.5211233F, + 0.74016446F, + -0.42495546F, + 0.0F, + 0.8575425F, + 0.4053273F, + -0.31675017F, + 0.0F, + 0.10452233F, + 0.8390196F, + -0.53396744F, + 0.0F, + 0.3501823F, + 0.9242524F, + -0.15208502F, + 0.0F, + 0.19878499F, + 0.076476134F, + 0.9770547F, + 0.0F, + 0.78459966F, + 0.6066257F, + -0.12809642F, + 0.0F, + 0.09006737F, + -0.97509897F, + -0.20265691F, + 0.0F, + -0.82743436F, + -0.54229957F, + 0.14582036F, + 0.0F, + -0.34857976F, + -0.41580227F, + 0.8400004F, + 0.0F, + -0.2471779F, + -0.730482F, + -0.6366311F, + 0.0F, + -0.3700155F, + 0.8577948F, + 0.35675845F, + 0.0F, + 0.59133947F, + -0.54831195F, + -0.59133035F, + 0.0F, + 0.120487355F, + -0.7626472F, + -0.6354935F, + 0.0F, + 0.6169593F, + 0.03079648F, + 0.7863923F, + 0.0F, + 0.12581569F, + -0.664083F, + -0.73699677F, + 0.0F, + -0.6477565F, + -0.17401473F, + -0.74170774F, + 0.0F, + 0.6217889F, + -0.7804431F, + -0.06547655F, + 0.0F, + 0.6589943F, + -0.6096988F, + 0.44044736F, + 0.0F, + -0.26898375F, + -0.6732403F, + -0.68876356F, + 0.0F, + -0.38497752F, + 0.56765425F, + 0.7277094F, + 0.0F, + 0.57544446F, + 0.81104714F, + -0.10519635F, + 0.0F, + 0.91415936F, + 0.3832948F, + 0.13190056F, + 0.0F, + -0.10792532F, + 0.9245494F, + 0.36545935F, + 0.0F, + 0.3779771F, + 0.30431488F, + 0.87437165F, + 0.0F, + -0.21428852F, + -0.8259286F, + 0.5214617F, + 0.0F, + 0.58025444F, + 0.41480985F, + -0.7008834F, + 0.0F, + -0.19826609F, + 0.85671616F, + -0.47615966F, + 0.0F, + -0.033815537F, + 0.37731808F, + -0.9254661F, + 0.0F, + -0.68679225F, + -0.6656598F, + 0.29191336F, + 0.0F, + 0.7731743F, + -0.28757936F, + -0.565243F, + 0.0F, + -0.09655942F, + 0.91937083F, + -0.3813575F, + 0.0F, + 0.27157024F, + -0.957791F, + -0.09426606F, + 0.0F, + 0.24510157F, + -0.6917999F, + -0.6792188F, + 0.0F, + 0.97770077F, + -0.17538553F, + 0.115503654F, + 0.0F, + -0.522474F, + 0.8521607F, + 0.029036159F, + 0.0F, + -0.77348804F, + -0.52612925F, + 0.35341796F, + 0.0F, + -0.71344924F, + -0.26954725F, + 0.6467878F, + 0.0F, + 0.16440372F, + 0.5105846F, + -0.84396374F, + 0.0F, + 0.6494636F, + 0.055856112F, + 0.7583384F, + 0.0F, + -0.4711971F, + 0.50172806F, + -0.7254256F, + 0.0F, + -0.63357645F, + -0.23816863F, + -0.7361091F, + 0.0F, + -0.9021533F, + -0.2709478F, + -0.33571818F, + 0.0F, + -0.3793711F, + 0.8722581F, + 0.3086152F, + 0.0F, + -0.68555987F, + -0.32501432F, + 0.6514394F, + 0.0F, + 0.29009423F, + -0.7799058F, + -0.5546101F, + 0.0F, + -0.20983194F, + 0.8503707F, + 0.48253515F, + 0.0F, + -0.45926037F, + 0.6598504F, + -0.5947077F, + 0.0F, + 0.87159455F, + 0.09616365F, + -0.48070312F, + 0.0F, + -0.6776666F, + 0.71185046F, + -0.1844907F, + 0.0F, + 0.7044378F, + 0.3124276F, + 0.637304F, + 0.0F, + -0.7052319F, + -0.24010932F, + -0.6670798F, + 0.0F, + 0.081921004F, + -0.72073364F, + -0.68835455F, + 0.0F, + -0.6993681F, + -0.5875763F, + -0.4069869F, + 0.0F, + -0.12814544F, + 0.6419896F, + 0.75592864F, + 0.0F, + -0.6337388F, + -0.67854714F, + -0.3714147F, + 0.0F, + 0.5565052F, + -0.21688876F, + -0.8020357F, + 0.0F, + -0.57915545F, + 0.7244372F, + -0.3738579F, + 0.0F, + 0.11757791F, + -0.7096451F, + 0.69467926F, + 0.0F, + -0.613462F, + 0.13236311F, + 0.7785528F, + 0.0F, + 0.69846356F, + -0.029805163F, + -0.7150247F, + 0.0F, + 0.83180827F, + -0.3930172F, + 0.39195976F, + 0.0F, + 0.14695764F, + 0.055416517F, + -0.98758924F, + 0.0F, + 0.70886856F, + -0.2690504F, + 0.65201014F, + 0.0F, + 0.27260533F, + 0.67369765F, + -0.68688995F, + 0.0F, + -0.65912956F, + 0.30354586F, + -0.68804663F, + 0.0F, + 0.48151314F, + -0.752827F, + 0.4487723F, + 0.0F, + 0.943001F, + 0.16756473F, + -0.28752613F, + 0.0F, + 0.43480295F, + 0.7695305F, + -0.46772778F, + 0.0F, + 0.39319962F, + 0.5944736F, + 0.70142365F, + 0.0F, + 0.72543365F, + -0.60392565F, + 0.33018148F, + 0.0F, + 0.75902355F, + -0.6506083F, + 0.024333132F, + 0.0F, + -0.8552769F, + -0.3430043F, + 0.38839358F, + 0.0F, + -0.6139747F, + 0.6981725F, + 0.36822575F, + 0.0F, + -0.74659055F, + -0.575201F, + 0.33428493F, + 0.0F, + 0.5730066F, + 0.8105555F, + -0.12109168F, + 0.0F, + -0.92258775F, + -0.3475211F, + -0.16751404F, + 0.0F, + -0.71058166F, + -0.47196922F, + -0.5218417F, + 0.0F, + -0.0856461F, + 0.35830015F, + 0.9296697F, + 0.0F, + -0.8279698F, + -0.2043157F, + 0.5222271F, + 0.0F, + 0.42794403F, + 0.278166F, + 0.8599346F, + 0.0F, + 0.539908F, + -0.78571206F, + -0.3019204F, + 0.0F, + 0.5678404F, + -0.5495414F, + -0.61283076F, + 0.0F, + -0.9896071F, + 0.13656391F, + -0.045034185F, + 0.0F, + -0.6154343F, + -0.64408755F, + 0.45430374F, + 0.0F, + 0.10742044F, + -0.79463404F, + 0.59750944F, + 0.0F, + -0.359545F, + -0.888553F, + 0.28495783F, + 0.0F, + -0.21804053F, + 0.1529889F, + 0.9638738F, + 0.0F, + -0.7277432F, + -0.61640507F, + -0.30072346F, + 0.0F, + 0.7249729F, + -0.0066971947F, + 0.68874484F, + 0.0F, + -0.5553659F, + -0.5336586F, + 0.6377908F, + 0.0F, + 0.5137558F, + 0.79762083F, + -0.316F, + 0.0F, + -0.3794025F, + 0.92456084F, + -0.035227515F, + 0.0F, + 0.82292485F, + 0.27453658F, + -0.49741766F, + 0.0F, + -0.5404114F, + 0.60911417F, + 0.5804614F, + 0.0F, + 0.8036582F, + -0.27030295F, + 0.5301602F, + 0.0F, + 0.60443187F, + 0.68329686F, + 0.40959433F, + 0.0F, + 0.06389989F, + 0.96582085F, + -0.2512108F, + 0.0F, + 0.10871133F, + 0.74024713F, + -0.6634878F, + 0.0F, + -0.7134277F, + -0.6926784F, + 0.10591285F, + 0.0F, + 0.64588976F, + -0.57245487F, + -0.50509584F, + 0.0F, + -0.6553931F, + 0.73814714F, + 0.15999562F, + 0.0F, + 0.39109614F, + 0.91888714F, + -0.05186756F, + 0.0F, + -0.48790225F, + -0.5904377F, + 0.64291114F, + 0.0F, + 0.601479F, + 0.77074414F, + -0.21018201F, + 0.0F, + -0.5677173F, + 0.7511361F, + 0.33688518F, + 0.0F, + 0.7858574F, + 0.22667466F, + 0.5753667F, + 0.0F, + -0.45203456F, + -0.6042227F, + -0.65618575F, + 0.0F, + 0.0022721163F, + 0.4132844F, + -0.9105992F, + 0.0F, + -0.58157516F, + -0.5162926F, + 0.6286591F, + 0.0F, + -0.03703705F, + 0.8273786F, + 0.5604221F, + 0.0F, + -0.51196927F, + 0.79535437F, + -0.324498F, + 0.0F, + -0.26824173F, + -0.957229F, + -0.10843876F, + 0.0F, + -0.23224828F, + -0.9679131F, + -0.09594243F, + 0.0F, + 0.3554329F, + -0.8881506F, + 0.29130062F, + 0.0F, + 0.73465204F, + -0.4371373F, + 0.5188423F, + 0.0F, + 0.998512F, + 0.046590112F, + -0.028339446F, + 0.0F, + -0.37276876F, + -0.9082481F, + 0.19007573F, + 0.0F, + 0.9173738F, + -0.3483642F, + 0.19252984F, + 0.0F, + 0.2714911F, + 0.41475296F, + -0.86848867F, + 0.0F, + 0.5131763F, + -0.71163344F, + 0.4798207F, + 0.0F, + -0.87373537F, + 0.18886992F, + -0.44823506F, + 0.0F, + 0.84600437F, + -0.3725218F, + 0.38145F, + 0.0F, + 0.89787275F, + -0.17802091F, + -0.40265754F, + 0.0F, + 0.21780656F, + -0.9698323F, + -0.10947895F, + 0.0F, + -0.15180314F, + -0.7788918F, + -0.6085091F, + 0.0F, + -0.2600385F, + -0.4755398F, + -0.840382F, + 0.0F, + 0.5723135F, + -0.7474341F, + -0.33734185F, + 0.0F, + -0.7174141F, + 0.16990171F, + -0.67561114F, + 0.0F, + -0.6841808F, + 0.021457076F, + -0.72899675F, + 0.0F, + -0.2007448F, + 0.06555606F, + -0.9774477F, + 0.0F, + -0.11488037F, + -0.8044887F, + 0.5827524F, + 0.0F, + -0.787035F, + 0.03447489F, + 0.6159443F, + 0.0F, + -0.20155965F, + 0.68598723F, + 0.69913894F, + 0.0F, + -0.085810825F, + -0.10920836F, + -0.99030805F, + 0.0F, + 0.5532693F, + 0.73252505F, + -0.39661077F, + 0.0F, + -0.18424894F, + -0.9777375F, + -0.100407675F, + 0.0F, + 0.07754738F, + -0.9111506F, + 0.40471104F, + 0.0F, + 0.13998385F, + 0.7601631F, + -0.63447344F, + 0.0F, + 0.44844192F, + -0.84528923F, + 0.29049253F, + 0.0F + }; + private static final int primeX = 501125321; + private static final int primeY = 1136930381; + private static final int primeZ = 1720413743; + + public FastNoiseLite() { + } + + public FastNoiseLite(int seed) { + this.setSeed(seed); + } + + public void setSeed(int seed) { + this.mSeed = seed; + } + + public void setFrequency(float frequency) { + this.mFrequency = frequency; + } + + public void setNoiseType(FastNoiseLite.NoiseType noiseType) { + this.mNoiseType = noiseType; + this.UpdateTransformType3D(); + } + + public void SetRotationType3D(FastNoiseLite.RotationType3D rotationType3D) { + this.mRotationType3D = rotationType3D; + this.UpdateTransformType3D(); + this.UpdateWarpTransformType3D(); + } + + public void setFractalType(FastNoiseLite.FractalType fractalType) { + this.mFractalType = fractalType; + } + + public void setFractalOctaves(int octaves) { + this.mOctaves = octaves; + this.CalculateFractalBounding(); + } + + public void SetFractalLacunarity(float lacunarity) { + this.mLacunarity = lacunarity; + } + + public void SetFractalGain(float gain) { + this.mGain = gain; + this.CalculateFractalBounding(); + } + + public void SetFractalWeightedStrength(float weightedStrength) { + this.mWeightedStrength = weightedStrength; + } + + public void SetFractalPingPongStrength(float pingPongStrength) { + this.mPingPongStrength = pingPongStrength; + } + + public void setCellularDistanceFunction(FastNoiseLite.CellularDistanceFunction cellularDistanceFunction) { + this.mCellularDistanceFunction = cellularDistanceFunction; + } + + public void setCellularReturnType(FastNoiseLite.CellularReturnType cellularReturnType) { + this.mCellularReturnType = cellularReturnType; + } + + public void setCellularJitter(float cellularJitter) { + this.mCellularJitterModifier = cellularJitter; + } + + public void setDomainWarpType(FastNoiseLite.DomainWarpType domainWarpType) { + this.mDomainWarpType = domainWarpType; + this.UpdateWarpTransformType3D(); + } + + public void setDomainWarpAmp(float domainWarpAmp) { + this.mDomainWarpAmp = domainWarpAmp; + } + + public void setDomainWarpFreq(float domainWarpFreq) { + this.mDomainWarpFreq = domainWarpFreq; + } + + public float getNoise(double x, double y) { + x *= this.mFrequency; + y *= this.mFrequency; + switch (this.mNoiseType) { + case OpenSimplex2: + case OpenSimplex2S: + double SQRT3 = 1.7320508075688772; + double F2 = 0.3660254037844386; + double t = (x + y) * 0.3660254037844386; + x += t; + y += t; + default: + return switch (this.mFractalType) { + case FBm -> this.GenFractalFBm(x, y); + case Ridged -> this.GenFractalRidged(x, y); + case PingPong -> this.GenFractalPingPong(x, y); + default -> this.GenNoiseSingle(this.mSeed, x, y); + }; + } + } + + public float getNoise(double x, double y, double z) { + x *= this.mFrequency; + y *= this.mFrequency; + z *= this.mFrequency; + switch (this.mTransformType3D) { + case ImproveXYPlanes: { + double xy = x + y; + double s2 = xy * -0.211324865405187; + z *= 0.577350269189626; + x += s2 - z; + y = y + s2 - z; + z += xy * 0.577350269189626; + break; + } + case ImproveXZPlanes: { + double xz = x + z; + double s2 = xz * -0.211324865405187; + y *= 0.577350269189626; + x += s2 - y; + z += s2 - y; + y += xz * 0.577350269189626; + break; + } + case DefaultOpenSimplex2: + double R3 = 0.6666666666666666; + double r = (x + y + z) * 0.6666666666666666; + x = r - x; + y = r - y; + z = r - z; + } + return switch (this.mFractalType) { + case FBm -> this.GenFractalFBm(x, y, z); + case Ridged -> this.GenFractalRidged(x, y, z); + case PingPong -> this.GenFractalPingPong(x, y, z); + default -> this.GenNoiseSingle(this.mSeed, x, y, z); + }; + } + + public void DomainWarp(@Nonnull FastNoiseLite.Vector2 coord) { + switch (this.mFractalType) { + case DomainWarpProgressive: + this.DomainWarpFractalProgressive(coord); + break; + case DomainWarpIndependent: + this.DomainWarpFractalIndependent(coord); + break; + default: + this.DomainWarpSingle(coord); + } + } + + public void DomainWarp(@Nonnull FastNoiseLite.Vector3 coord) { + switch (this.mFractalType) { + case DomainWarpProgressive: + this.DomainWarpFractalProgressive(coord); + break; + case DomainWarpIndependent: + this.DomainWarpFractalIndependent(coord); + break; + default: + this.DomainWarpSingle(coord); + } + } + + private static float FastMin(float a, float b) { + return a < b ? a : b; + } + + private static float FastMax(float a, float b) { + return a > b ? a : b; + } + + private static float FastAbs(float f) { + return f < 0.0F ? -f : f; + } + + private static float FastSqrt(float f) { + return (float)Math.sqrt(f); + } + + private static int FastFloor(double f) { + return f >= 0.0 ? (int)f : (int)f - 1; + } + + public static int fastRound(double f) { + return f >= 0.0 ? (int)(f + 0.5) : (int)(f - 0.5); + } + + private static float Lerp(float a, float b, float t) { + return a + t * (b - a); + } + + private static float InterpHermite(float t) { + return t * t * (3.0F - 2.0F * t); + } + + private static float InterpQuintic(float t) { + return t * t * t * (t * (t * 6.0F - 15.0F) + 10.0F); + } + + private static float CubicLerp(float a, float b, float c, float d, float t) { + float p = d - c - (a - b); + return t * t * t * p + t * t * (a - b - p) + t * (c - a) + b; + } + + private static float PingPong(float t) { + t -= (int)(t * 0.5F) * 2; + return t < 1.0F ? t : 2.0F - t; + } + + private void CalculateFractalBounding() { + float gain = FastAbs(this.mGain); + float amp = gain; + float ampFractal = 1.0F; + + for (int i = 1; i < this.mOctaves; i++) { + ampFractal += amp; + amp *= gain; + } + + this.mFractalBounding = 1.0F / ampFractal; + } + + private static int hash(int seed, int xPrimed, int yPrimed) { + int hash = seed ^ xPrimed ^ yPrimed; + return hash * 668265261; + } + + private static int hash(int seed, int xPrimed, int yPrimed, int zPrimed) { + int hash = seed ^ xPrimed ^ yPrimed ^ zPrimed; + return hash * 668265261; + } + + private static float ValCoord(int seed, int xPrimed, int yPrimed) { + int hash = hash(seed, xPrimed, yPrimed); + hash *= hash; + hash ^= hash << 19; + return hash * 4.656613E-10F; + } + + private static float ValCoord(int seed, int xPrimed, int yPrimed, int zPrimed) { + int hash = hash(seed, xPrimed, yPrimed, zPrimed); + hash *= hash; + hash ^= hash << 19; + return hash * 4.656613E-10F; + } + + private static float GradCoord(int seed, int xPrimed, int yPrimed, float xd, float yd) { + int hash = hash(seed, xPrimed, yPrimed); + hash ^= hash >> 15; + hash &= 254; + float xg = Gradients2D[hash]; + float yg = Gradients2D[hash | 1]; + return xd * xg + yd * yg; + } + + private static float GradCoord(int seed, int xPrimed, int yPrimed, int zPrimed, float xd, float yd, float zd) { + int hash = hash(seed, xPrimed, yPrimed, zPrimed); + hash ^= hash >> 15; + hash &= 252; + float xg = Gradients3D[hash]; + float yg = Gradients3D[hash | 1]; + float zg = Gradients3D[hash | 2]; + return xd * xg + yd * yg + zd * zg; + } + + private float GenNoiseSingle(int seed, double x, double y) { + return switch (this.mNoiseType) { + case OpenSimplex2 -> this.SingleSimplex(seed, x, y); + case OpenSimplex2S -> this.SingleOpenSimplex2S(seed, x, y); + case Cellular -> this.SingleCellular(seed, x, y); + case Perlin -> this.SinglePerlin(seed, x, y); + case ValueCubic -> this.SingleValueCubic(seed, x, y); + case Value -> this.SingleValue(seed, x, y); + }; + } + + private float GenNoiseSingle(int seed, double x, double y, double z) { + return switch (this.mNoiseType) { + case OpenSimplex2 -> this.SingleOpenSimplex2(seed, x, y, z); + case OpenSimplex2S -> this.SingleOpenSimplex2S(seed, x, y, z); + case Cellular -> this.SingleCellular(seed, x, y, z); + case Perlin -> this.SinglePerlin(seed, x, y, z); + case ValueCubic -> this.SingleValueCubic(seed, x, y, z); + case Value -> this.SingleValue(seed, x, y, z); + }; + } + + private void UpdateTransformType3D() { + switch (this.mRotationType3D) { + case ImproveXYPlanes: + this.mTransformType3D = FastNoiseLite.TransformType3D.ImproveXYPlanes; + break; + case ImproveXZPlanes: + this.mTransformType3D = FastNoiseLite.TransformType3D.ImproveXZPlanes; + break; + default: + switch (this.mNoiseType) { + case OpenSimplex2: + case OpenSimplex2S: + this.mTransformType3D = FastNoiseLite.TransformType3D.DefaultOpenSimplex2; + break; + default: + this.mTransformType3D = FastNoiseLite.TransformType3D.None; + } + } + } + + private void UpdateWarpTransformType3D() { + switch (this.mRotationType3D) { + case ImproveXYPlanes: + this.mWarpTransformType3D = FastNoiseLite.TransformType3D.ImproveXYPlanes; + break; + case ImproveXZPlanes: + this.mWarpTransformType3D = FastNoiseLite.TransformType3D.ImproveXZPlanes; + break; + default: + switch (this.mDomainWarpType) { + case OpenSimplex2: + case OpenSimplex2Reduced: + this.mWarpTransformType3D = FastNoiseLite.TransformType3D.DefaultOpenSimplex2; + break; + default: + this.mWarpTransformType3D = FastNoiseLite.TransformType3D.None; + } + } + } + + private float GenFractalFBm(double x, double y) { + int seed = this.mSeed; + float sum = 0.0F; + float amp = this.mFractalBounding; + + for (int i = 0; i < this.mOctaves; i++) { + float noise = this.GenNoiseSingle(seed++, x, y); + sum += noise * amp; + amp *= Lerp(1.0F, FastMin(noise + 1.0F, 2.0F) * 0.5F, this.mWeightedStrength); + x *= this.mLacunarity; + y *= this.mLacunarity; + amp *= this.mGain; + } + + return sum; + } + + private float GenFractalFBm(double x, double y, double z) { + int seed = this.mSeed; + float sum = 0.0F; + float amp = this.mFractalBounding; + + for (int i = 0; i < this.mOctaves; i++) { + float noise = this.GenNoiseSingle(seed++, x, y, z); + sum += noise * amp; + amp *= Lerp(1.0F, (noise + 1.0F) * 0.5F, this.mWeightedStrength); + x *= this.mLacunarity; + y *= this.mLacunarity; + z *= this.mLacunarity; + amp *= this.mGain; + } + + return sum; + } + + private float GenFractalRidged(double x, double y) { + int seed = this.mSeed; + float sum = 0.0F; + float amp = this.mFractalBounding; + + for (int i = 0; i < this.mOctaves; i++) { + float noise = FastAbs(this.GenNoiseSingle(seed++, x, y)); + sum += (noise * -2.0F + 1.0F) * amp; + amp *= Lerp(1.0F, 1.0F - noise, this.mWeightedStrength); + x *= this.mLacunarity; + y *= this.mLacunarity; + amp *= this.mGain; + } + + return sum; + } + + private float GenFractalRidged(double x, double y, double z) { + int seed = this.mSeed; + float sum = 0.0F; + float amp = this.mFractalBounding; + + for (int i = 0; i < this.mOctaves; i++) { + float noise = FastAbs(this.GenNoiseSingle(seed++, x, y, z)); + sum += (noise * -2.0F + 1.0F) * amp; + amp *= Lerp(1.0F, 1.0F - noise, this.mWeightedStrength); + x *= this.mLacunarity; + y *= this.mLacunarity; + z *= this.mLacunarity; + amp *= this.mGain; + } + + return sum; + } + + private float GenFractalPingPong(double x, double y) { + int seed = this.mSeed; + float sum = 0.0F; + float amp = this.mFractalBounding; + + for (int i = 0; i < this.mOctaves; i++) { + float noise = PingPong((this.GenNoiseSingle(seed++, x, y) + 1.0F) * this.mPingPongStrength); + sum += (noise - 0.5F) * 2.0F * amp; + amp *= Lerp(1.0F, noise, this.mWeightedStrength); + x *= this.mLacunarity; + y *= this.mLacunarity; + amp *= this.mGain; + } + + return sum; + } + + private float GenFractalPingPong(double x, double y, double z) { + int seed = this.mSeed; + float sum = 0.0F; + float amp = this.mFractalBounding; + + for (int i = 0; i < this.mOctaves; i++) { + float noise = PingPong((this.GenNoiseSingle(seed++, x, y, z) + 1.0F) * this.mPingPongStrength); + sum += (noise - 0.5F) * 2.0F * amp; + amp *= Lerp(1.0F, noise, this.mWeightedStrength); + x *= this.mLacunarity; + y *= this.mLacunarity; + z *= this.mLacunarity; + amp *= this.mGain; + } + + return sum; + } + + private float SingleSimplex(int seed, double x, double y) { + float SQRT3 = 1.7320508F; + float G2 = 0.21132487F; + int i = FastFloor(x); + int j = FastFloor(y); + float xi = (float)(x - i); + float yi = (float)(y - j); + float t = (xi + yi) * 0.21132487F; + float x0 = xi - t; + float y0 = yi - t; + i *= 501125321; + j *= 1136930381; + float a = 0.5F - x0 * x0 - y0 * y0; + float n0; + if (a <= 0.0F) { + n0 = 0.0F; + } else { + n0 = a * a * (a * a) * GradCoord(seed, i, j, x0, y0); + } + + float c = 3.1547005F * t + (-0.6666666F + a); + float n2; + if (c <= 0.0F) { + n2 = 0.0F; + } else { + float x2 = x0 + -0.57735026F; + float y2 = y0 + -0.57735026F; + n2 = c * c * (c * c) * GradCoord(seed, i + 501125321, j + 1136930381, x2, y2); + } + + float n1; + if (y0 > x0) { + float x1 = x0 + 0.21132487F; + float y1 = y0 + -0.7886751F; + float b = 0.5F - x1 * x1 - y1 * y1; + if (b <= 0.0F) { + n1 = 0.0F; + } else { + n1 = b * b * (b * b) * GradCoord(seed, i, j + 1136930381, x1, y1); + } + } else { + float x1 = x0 + -0.7886751F; + float y1 = y0 + 0.21132487F; + float b = 0.5F - x1 * x1 - y1 * y1; + if (b <= 0.0F) { + n1 = 0.0F; + } else { + n1 = b * b * (b * b) * GradCoord(seed, i + 501125321, j, x1, y1); + } + } + + return (n0 + n1 + n2) * 99.83685F; + } + + private float SingleOpenSimplex2(int seed, double x, double y, double z) { + int i = fastRound(x); + int j = fastRound(y); + int k = fastRound(z); + float x0 = (float)(x - i); + float y0 = (float)(y - j); + float z0 = (float)(z - k); + int xNSign = (int)(-1.0F - x0) | 1; + int yNSign = (int)(-1.0F - y0) | 1; + int zNSign = (int)(-1.0F - z0) | 1; + float ax0 = xNSign * -x0; + float ay0 = yNSign * -y0; + float az0 = zNSign * -z0; + i *= 501125321; + j *= 1136930381; + k *= 1720413743; + float value = 0.0F; + float a = 0.6F - x0 * x0 - (y0 * y0 + z0 * z0); + int l = 0; + + while (true) { + if (a > 0.0F) { + value += a * a * (a * a) * GradCoord(seed, i, j, k, x0, y0, z0); + } + + if (ax0 >= ay0 && ax0 >= az0) { + float b = a + ax0 + ax0; + if (b > 1.0F) { + value += --b * b * (b * b) * GradCoord(seed, i - xNSign * 501125321, j, k, x0 + xNSign, y0, z0); + } + } else if (ay0 > ax0 && ay0 >= az0) { + float b = a + ay0 + ay0; + if (b > 1.0F) { + value += --b * b * (b * b) * GradCoord(seed, i, j - yNSign * 1136930381, k, x0, y0 + yNSign, z0); + } + } else { + float b = a + az0 + az0; + if (b > 1.0F) { + value += --b * b * (b * b) * GradCoord(seed, i, j, k - zNSign * 1720413743, x0, y0, z0 + zNSign); + } + } + + if (l == 1) { + return value * 32.694283F; + } + + ax0 = 0.5F - ax0; + ay0 = 0.5F - ay0; + az0 = 0.5F - az0; + x0 = xNSign * ax0; + y0 = yNSign * ay0; + z0 = zNSign * az0; + a += 0.75F - ax0 - (ay0 + az0); + i += xNSign >> 1 & 501125321; + j += yNSign >> 1 & 1136930381; + k += zNSign >> 1 & 1720413743; + xNSign = -xNSign; + yNSign = -yNSign; + zNSign = -zNSign; + seed = ~seed; + l++; + } + } + + private float SingleOpenSimplex2S(int seed, double x, double y) { + double SQRT3 = 1.7320508075688772; + double G2 = 0.21132486540518713; + int i = FastFloor(x); + int j = FastFloor(y); + float xi = (float)(x - i); + float yi = (float)(y - j); + i *= 501125321; + j *= 1136930381; + int i1 = i + 501125321; + int j1 = j + 1136930381; + float t = (xi + yi) * 0.21132487F; + float x0 = xi - t; + float y0 = yi - t; + float a0 = 0.6666667F - x0 * x0 - y0 * y0; + float value = a0 * a0 * (a0 * a0) * GradCoord(seed, i, j, x0, y0); + float a1 = 3.1547005F * t + (-0.6666667F + a0); + float x1 = x0 - 0.57735026F; + float y1 = y0 - 0.57735026F; + value += a1 * a1 * (a1 * a1) * GradCoord(seed, i1, j1, x1, y1); + float xmyi = xi - yi; + if (t > 0.21132486540518713) { + if (xi + xmyi > 1.0F) { + float x2 = x0 + -1.3660254F; + float y2 = y0 + -0.36602542F; + float a2 = 0.6666667F - x2 * x2 - y2 * y2; + if (a2 > 0.0F) { + value += a2 * a2 * (a2 * a2) * GradCoord(seed, i + 1002250642, j + 1136930381, x2, y2); + } + } else { + float x2 = x0 + 0.21132487F; + float y2 = y0 + -0.7886751F; + float a2 = 0.6666667F - x2 * x2 - y2 * y2; + if (a2 > 0.0F) { + value += a2 * a2 * (a2 * a2) * GradCoord(seed, i, j + 1136930381, x2, y2); + } + } + + if (yi - xmyi > 1.0F) { + float x3 = x0 + -0.36602542F; + float y3 = y0 + -1.3660254F; + float a3 = 0.6666667F - x3 * x3 - y3 * y3; + if (a3 > 0.0F) { + value += a3 * a3 * (a3 * a3) * GradCoord(seed, i + 501125321, j + -2021106534, x3, y3); + } + } else { + float x3 = x0 + -0.7886751F; + float y3 = y0 + 0.21132487F; + float a3 = 0.6666667F - x3 * x3 - y3 * y3; + if (a3 > 0.0F) { + value += a3 * a3 * (a3 * a3) * GradCoord(seed, i + 501125321, j, x3, y3); + } + } + } else { + if (xi + xmyi < 0.0F) { + float x2 = x0 + 0.7886751F; + float y2 = y0 - 0.21132487F; + float a2 = 0.6666667F - x2 * x2 - y2 * y2; + if (a2 > 0.0F) { + value += a2 * a2 * (a2 * a2) * GradCoord(seed, i - 501125321, j, x2, y2); + } + } else { + float x2 = x0 + -0.7886751F; + float y2 = y0 + 0.21132487F; + float a2 = 0.6666667F - x2 * x2 - y2 * y2; + if (a2 > 0.0F) { + value += a2 * a2 * (a2 * a2) * GradCoord(seed, i + 501125321, j, x2, y2); + } + } + + if (yi < xmyi) { + float x2x = x0 - 0.21132487F; + float y2x = y0 - -0.7886751F; + float a2x = 0.6666667F - x2x * x2x - y2x * y2x; + if (a2x > 0.0F) { + value += a2x * a2x * (a2x * a2x) * GradCoord(seed, i, j - 1136930381, x2x, y2x); + } + } else { + float x2x = x0 + 0.21132487F; + float y2x = y0 + -0.7886751F; + float a2x = 0.6666667F - x2x * x2x - y2x * y2x; + if (a2x > 0.0F) { + value += a2x * a2x * (a2x * a2x) * GradCoord(seed, i, j + 1136930381, x2x, y2x); + } + } + } + + return value * 18.241962F; + } + + private float SingleOpenSimplex2S(int seed, double x, double y, double z) { + int i = FastFloor(x); + int j = FastFloor(y); + int k = FastFloor(z); + float xi = (float)(x - i); + float yi = (float)(y - j); + float zi = (float)(z - k); + i *= 501125321; + j *= 1136930381; + k *= 1720413743; + int seed2 = seed + 1293373; + int xNMask = (int)(-0.5F - xi); + int yNMask = (int)(-0.5F - yi); + int zNMask = (int)(-0.5F - zi); + float x0 = xi + xNMask; + float y0 = yi + yNMask; + float z0 = zi + zNMask; + float a0 = 0.75F - x0 * x0 - y0 * y0 - z0 * z0; + float value = a0 * a0 * (a0 * a0) * GradCoord(seed, i + (xNMask & 501125321), j + (yNMask & 1136930381), k + (zNMask & 1720413743), x0, y0, z0); + float x1 = xi - 0.5F; + float y1 = yi - 0.5F; + float z1 = zi - 0.5F; + float a1 = 0.75F - x1 * x1 - y1 * y1 - z1 * z1; + value += a1 * a1 * (a1 * a1) * GradCoord(seed2, i + 501125321, j + 1136930381, k + 1720413743, x1, y1, z1); + float xAFlipMask0 = ((xNMask | 1) << 1) * x1; + float yAFlipMask0 = ((yNMask | 1) << 1) * y1; + float zAFlipMask0 = ((zNMask | 1) << 1) * z1; + float xAFlipMask1 = (-2 - (xNMask << 2)) * x1 - 1.0F; + float yAFlipMask1 = (-2 - (yNMask << 2)) * y1 - 1.0F; + float zAFlipMask1 = (-2 - (zNMask << 2)) * z1 - 1.0F; + boolean skip5 = false; + float a2 = xAFlipMask0 + a0; + if (a2 > 0.0F) { + float x2 = x0 - (xNMask | 1); + value += a2 * a2 * (a2 * a2) * GradCoord(seed, i + (~xNMask & 501125321), j + (yNMask & 1136930381), k + (zNMask & 1720413743), x2, y0, z0); + } else { + float a3 = yAFlipMask0 + zAFlipMask0 + a0; + if (a3 > 0.0F) { + float y3 = y0 - (yNMask | 1); + float z3 = z0 - (zNMask | 1); + value += a3 * a3 * (a3 * a3) * GradCoord(seed, i + (xNMask & 501125321), j + (~yNMask & 1136930381), k + (~zNMask & 1720413743), x0, y3, z3); + } + + float a4 = xAFlipMask1 + a1; + if (a4 > 0.0F) { + float x4 = (xNMask | 1) + x1; + value += a4 * a4 * (a4 * a4) * GradCoord(seed2, i + (xNMask & 1002250642), j + 1136930381, k + 1720413743, x4, y1, z1); + skip5 = true; + } + } + + boolean skip9 = false; + float a6 = yAFlipMask0 + a0; + if (a6 > 0.0F) { + float y6 = y0 - (yNMask | 1); + value += a6 * a6 * (a6 * a6) * GradCoord(seed, i + (xNMask & 501125321), j + (~yNMask & 1136930381), k + (zNMask & 1720413743), x0, y6, z0); + } else { + float a7 = xAFlipMask0 + zAFlipMask0 + a0; + if (a7 > 0.0F) { + float x7 = x0 - (xNMask | 1); + float z7 = z0 - (zNMask | 1); + value += a7 * a7 * (a7 * a7) * GradCoord(seed, i + (~xNMask & 501125321), j + (yNMask & 1136930381), k + (~zNMask & 1720413743), x7, y0, z7); + } + + float a8 = yAFlipMask1 + a1; + if (a8 > 0.0F) { + float y8 = (yNMask | 1) + y1; + value += a8 * a8 * (a8 * a8) * GradCoord(seed2, i + 501125321, j + (yNMask & -2021106534), k + 1720413743, x1, y8, z1); + skip9 = true; + } + } + + boolean skipD = false; + float aA = zAFlipMask0 + a0; + if (aA > 0.0F) { + float zA = z0 - (zNMask | 1); + value += aA * aA * (aA * aA) * GradCoord(seed, i + (xNMask & 501125321), j + (yNMask & 1136930381), k + (~zNMask & 1720413743), x0, y0, zA); + } else { + float aB = xAFlipMask0 + yAFlipMask0 + a0; + if (aB > 0.0F) { + float xB = x0 - (xNMask | 1); + float yB = y0 - (yNMask | 1); + value += aB * aB * (aB * aB) * GradCoord(seed, i + (~xNMask & 501125321), j + (~yNMask & 1136930381), k + (zNMask & 1720413743), xB, yB, z0); + } + + float aC = zAFlipMask1 + a1; + if (aC > 0.0F) { + float zC = (zNMask | 1) + z1; + value += aC * aC * (aC * aC) * GradCoord(seed2, i + 501125321, j + 1136930381, k + (zNMask & -854139810), x1, y1, zC); + skipD = true; + } + } + + if (!skip5) { + float a5 = yAFlipMask1 + zAFlipMask1 + a1; + if (a5 > 0.0F) { + float y5 = (yNMask | 1) + y1; + float z5 = (zNMask | 1) + z1; + value += a5 * a5 * (a5 * a5) * GradCoord(seed2, i + 501125321, j + (yNMask & -2021106534), k + (zNMask & -854139810), x1, y5, z5); + } + } + + if (!skip9) { + float a9 = xAFlipMask1 + zAFlipMask1 + a1; + if (a9 > 0.0F) { + float x9 = (xNMask | 1) + x1; + float z9 = (zNMask | 1) + z1; + value += a9 * a9 * (a9 * a9) * GradCoord(seed2, i + (xNMask & 1002250642), j + 1136930381, k + (zNMask & -854139810), x9, y1, z9); + } + } + + if (!skipD) { + float aD = xAFlipMask1 + yAFlipMask1 + a1; + if (aD > 0.0F) { + float xD = (xNMask | 1) + x1; + float yD = (yNMask | 1) + y1; + value += aD * aD * (aD * aD) * GradCoord(seed2, i + (xNMask & 1002250642), j + (yNMask & -2021106534), k + 1720413743, xD, yD, z1); + } + } + + return value * 9.046026F; + } + + @Nonnull + public Vector3d pointFor(int seed, double jitter, double x, double y, double z) { + int xr = fastRound(x); + int yr = fastRound(y); + int zr = fastRound(z); + int hash = hash(seed, xr, yr, zr); + int idx = hash & 1020; + double vecX = randVecs3D[idx] * jitter + xr; + double vecY = randVecs3D[idx | 1] * jitter + yr; + double vecZ = randVecs3D[idx | 2] * jitter + zr; + return new Vector3d(vecX, vecY, vecZ); + } + + @Nonnull + public Vector2d pointFor(int seed, double jitter, double x, double y) { + int xr = fastRound(x); + int yr = fastRound(y); + int hash = hash(seed, xr, yr); + int idx = hash & 1020; + double vecX = randVecs3D[idx] * jitter + xr; + double vecY = randVecs3D[idx | 1] * jitter + yr; + return new Vector2d(vecX, vecY); + } + + public double pointFor(int seed, double jitter, double x) { + int xr = fastRound(x); + int hash = hash(seed, xr, 0); + int idx = hash & 1020; + return randVecs3D[idx] * jitter + xr; + } + + public float SingleCellular(int seed, double x, double y) { + int xr = fastRound(x); + int yr = fastRound(y); + float distance0 = Float.MAX_VALUE; + float distance1 = Float.MAX_VALUE; + int closestHash = 0; + float cellularJitter = 0.5F * this.mCellularJitterModifier; + int xPrimed = (xr - 1) * 501125321; + int yPrimedBase = (yr - 1) * 1136930381; + switch (this.mCellularDistanceFunction) { + case Euclidean: + case EuclideanSq: + default: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int hash = hash(seed, xPrimed, yPrimed); + int idx = hash & 510; + float vecX = (float)(xi - x) + RandVecs2D[idx] * cellularJitter; + float vecY = (float)(yi - y) + RandVecs2D[idx | 1] * cellularJitter; + float newDistance = vecX * vecX + vecY * vecY; + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + + yPrimed += 1136930381; + } + + xPrimed += 501125321; + } + break; + case Manhattan: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int hash = hash(seed, xPrimed, yPrimed); + int idx = hash & 510; + float vecX = (float)(xi - x) + RandVecs2D[idx] * cellularJitter; + float vecY = (float)(yi - y) + RandVecs2D[idx | 1] * cellularJitter; + float newDistance = FastAbs(vecX) + FastAbs(vecY); + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + + yPrimed += 1136930381; + } + + xPrimed += 501125321; + } + break; + case Hybrid: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int hash = hash(seed, xPrimed, yPrimed); + int idx = hash & 510; + float vecX = (float)(xi - x) + RandVecs2D[idx] * cellularJitter; + float vecY = (float)(yi - y) + RandVecs2D[idx | 1] * cellularJitter; + float newDistance = FastAbs(vecX) + FastAbs(vecY) + (vecX * vecX + vecY * vecY); + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + + yPrimed += 1136930381; + } + + xPrimed += 501125321; + } + } + + if (this.mCellularDistanceFunction == FastNoiseLite.CellularDistanceFunction.Euclidean + && this.mCellularReturnType != FastNoiseLite.CellularReturnType.CellValue) { + distance0 = FastSqrt(distance0); + if (this.mCellularReturnType != FastNoiseLite.CellularReturnType.Distance) { + distance1 = FastSqrt(distance1); + } + } + return switch (this.mCellularReturnType) { + case CellValue -> closestHash * 4.656613E-10F; + case Distance -> distance0 - 1.0F; + case Distance2 -> distance1 - 1.0F; + case Distance2Add -> (distance1 + distance0) * 0.5F - 1.0F; + case Distance2Sub -> distance1 - distance0 - 1.0F; + case Distance2Mul -> distance1 * distance0 * 0.5F - 1.0F; + case Distance2Div -> distance0 / distance1 - 1.0F; + }; + } + + private float SingleCellular(int seed, double x, double y, double z) { + int xr = fastRound(x); + int yr = fastRound(y); + int zr = fastRound(z); + float distance0 = Float.MAX_VALUE; + float distance1 = Float.MAX_VALUE; + int closestHash = 0; + float cellularJitter = 0.5F * this.mCellularJitterModifier; + int xPrimed = (xr - 1) * 501125321; + int yPrimedBase = (yr - 1) * 1136930381; + int zPrimedBase = (zr - 1) * 1720413743; + switch (this.mCellularDistanceFunction) { + case Euclidean: + case EuclideanSq: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int zPrimed = zPrimedBase; + + for (int zi = zr - 1; zi <= zr + 1; zi++) { + int hash = hash(seed, xPrimed, yPrimed, zPrimed); + int idx = hash & 1020; + float vecX = (float)(xi - x) + randVecs3D[idx] * cellularJitter; + float vecY = (float)(yi - y) + randVecs3D[idx | 1] * cellularJitter; + float vecZ = (float)(zi - z) + randVecs3D[idx | 2] * cellularJitter; + float newDistance = vecX * vecX + vecY * vecY + vecZ * vecZ; + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + + zPrimed += 1720413743; + } + + yPrimed += 1136930381; + } + + xPrimed += 501125321; + } + break; + case Manhattan: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int zPrimed = zPrimedBase; + + for (int zi = zr - 1; zi <= zr + 1; zi++) { + int hash = hash(seed, xPrimed, yPrimed, zPrimed); + int idx = hash & 1020; + float vecX = (float)(xi - x) + randVecs3D[idx] * cellularJitter; + float vecY = (float)(yi - y) + randVecs3D[idx | 1] * cellularJitter; + float vecZ = (float)(zi - z) + randVecs3D[idx | 2] * cellularJitter; + float newDistance = FastAbs(vecX) + FastAbs(vecY) + FastAbs(vecZ); + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + + zPrimed += 1720413743; + } + + yPrimed += 1136930381; + } + + xPrimed += 501125321; + } + break; + case Hybrid: + for (int xi = xr - 1; xi <= xr + 1; xi++) { + int yPrimed = yPrimedBase; + + for (int yi = yr - 1; yi <= yr + 1; yi++) { + int zPrimed = zPrimedBase; + + for (int zi = zr - 1; zi <= zr + 1; zi++) { + int hash = hash(seed, xPrimed, yPrimed, zPrimed); + int idx = hash & 1020; + float vecX = (float)(xi - x) + randVecs3D[idx] * cellularJitter; + float vecY = (float)(yi - y) + randVecs3D[idx | 1] * cellularJitter; + float vecZ = (float)(zi - z) + randVecs3D[idx | 2] * cellularJitter; + float newDistance = FastAbs(vecX) + FastAbs(vecY) + FastAbs(vecZ) + (vecX * vecX + vecY * vecY + vecZ * vecZ); + distance1 = FastMax(FastMin(distance1, newDistance), distance0); + if (newDistance < distance0) { + distance0 = newDistance; + closestHash = hash; + } + + zPrimed += 1720413743; + } + + yPrimed += 1136930381; + } + + xPrimed += 501125321; + } + } + + if (this.mCellularDistanceFunction == FastNoiseLite.CellularDistanceFunction.Euclidean + && this.mCellularReturnType != FastNoiseLite.CellularReturnType.CellValue) { + distance0 = FastSqrt(distance0); + if (this.mCellularReturnType != FastNoiseLite.CellularReturnType.Distance) { + distance1 = FastSqrt(distance1); + } + } + return switch (this.mCellularReturnType) { + case CellValue -> closestHash * 4.656613E-10F; + case Distance -> distance0 - 1.0F; + case Distance2 -> distance1 - 1.0F; + case Distance2Add -> (distance1 + distance0) * 0.5F - 1.0F; + case Distance2Sub -> distance1 - distance0 - 1.0F; + case Distance2Mul -> distance1 * distance0 * 0.5F - 1.0F; + case Distance2Div -> distance0 / distance1 - 1.0F; + }; + } + + private float SinglePerlin(int seed, double x, double y) { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + float xd0 = (float)(x - x0); + float yd0 = (float)(y - y0); + float xd1 = xd0 - 1.0F; + float yd1 = yd0 - 1.0F; + float xs = InterpQuintic(xd0); + float ys = InterpQuintic(yd0); + x0 *= 501125321; + y0 *= 1136930381; + int x1 = x0 + 501125321; + int y1 = y0 + 1136930381; + float xf0 = Lerp(GradCoord(seed, x0, y0, xd0, yd0), GradCoord(seed, x1, y0, xd1, yd0), xs); + float xf1 = Lerp(GradCoord(seed, x0, y1, xd0, yd1), GradCoord(seed, x1, y1, xd1, yd1), xs); + return Lerp(xf0, xf1, ys) * 1.4247692F; + } + + private float SinglePerlin(int seed, double x, double y, double z) { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + int z0 = FastFloor(z); + float xd0 = (float)(x - x0); + float yd0 = (float)(y - y0); + float zd0 = (float)(z - z0); + float xd1 = xd0 - 1.0F; + float yd1 = yd0 - 1.0F; + float zd1 = zd0 - 1.0F; + float xs = InterpQuintic(xd0); + float ys = InterpQuintic(yd0); + float zs = InterpQuintic(zd0); + x0 *= 501125321; + y0 *= 1136930381; + z0 *= 1720413743; + int x1 = x0 + 501125321; + int y1 = y0 + 1136930381; + int z1 = z0 + 1720413743; + float xf00 = Lerp(GradCoord(seed, x0, y0, z0, xd0, yd0, zd0), GradCoord(seed, x1, y0, z0, xd1, yd0, zd0), xs); + float xf10 = Lerp(GradCoord(seed, x0, y1, z0, xd0, yd1, zd0), GradCoord(seed, x1, y1, z0, xd1, yd1, zd0), xs); + float xf01 = Lerp(GradCoord(seed, x0, y0, z1, xd0, yd0, zd1), GradCoord(seed, x1, y0, z1, xd1, yd0, zd1), xs); + float xf11 = Lerp(GradCoord(seed, x0, y1, z1, xd0, yd1, zd1), GradCoord(seed, x1, y1, z1, xd1, yd1, zd1), xs); + float yf0 = Lerp(xf00, xf10, ys); + float yf1 = Lerp(xf01, xf11, ys); + return Lerp(yf0, yf1, zs) * 0.9649214F; + } + + private float SingleValueCubic(int seed, double x, double y) { + int x1 = FastFloor(x); + int y1 = FastFloor(y); + float xs = (float)(x - x1); + float ys = (float)(y - y1); + x1 *= 501125321; + y1 *= 1136930381; + int x0 = x1 - 501125321; + int y0 = y1 - 1136930381; + int x2 = x1 + 501125321; + int y2 = y1 + 1136930381; + int x3 = x1 + 1002250642; + int y3 = y1 + -2021106534; + return CubicLerp( + CubicLerp(ValCoord(seed, x0, y0), ValCoord(seed, x1, y0), ValCoord(seed, x2, y0), ValCoord(seed, x3, y0), xs), + CubicLerp(ValCoord(seed, x0, y1), ValCoord(seed, x1, y1), ValCoord(seed, x2, y1), ValCoord(seed, x3, y1), xs), + CubicLerp(ValCoord(seed, x0, y2), ValCoord(seed, x1, y2), ValCoord(seed, x2, y2), ValCoord(seed, x3, y2), xs), + CubicLerp(ValCoord(seed, x0, y3), ValCoord(seed, x1, y3), ValCoord(seed, x2, y3), ValCoord(seed, x3, y3), xs), + ys + ) + * 0.44444445F; + } + + private float SingleValueCubic(int seed, double x, double y, double z) { + int x1 = FastFloor(x); + int y1 = FastFloor(y); + int z1 = FastFloor(z); + float xs = (float)(x - x1); + float ys = (float)(y - y1); + float zs = (float)(z - z1); + x1 *= 501125321; + y1 *= 1136930381; + z1 *= 1720413743; + int x0 = x1 - 501125321; + int y0 = y1 - 1136930381; + int z0 = z1 - 1720413743; + int x2 = x1 + 501125321; + int y2 = y1 + 1136930381; + int z2 = z1 + 1720413743; + int x3 = x1 + 1002250642; + int y3 = y1 + -2021106534; + int z3 = z1 + -854139810; + return CubicLerp( + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z0), ValCoord(seed, x1, y0, z0), ValCoord(seed, x2, y0, z0), ValCoord(seed, x3, y0, z0), xs), + CubicLerp(ValCoord(seed, x0, y1, z0), ValCoord(seed, x1, y1, z0), ValCoord(seed, x2, y1, z0), ValCoord(seed, x3, y1, z0), xs), + CubicLerp(ValCoord(seed, x0, y2, z0), ValCoord(seed, x1, y2, z0), ValCoord(seed, x2, y2, z0), ValCoord(seed, x3, y2, z0), xs), + CubicLerp(ValCoord(seed, x0, y3, z0), ValCoord(seed, x1, y3, z0), ValCoord(seed, x2, y3, z0), ValCoord(seed, x3, y3, z0), xs), + ys + ), + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z1), ValCoord(seed, x1, y0, z1), ValCoord(seed, x2, y0, z1), ValCoord(seed, x3, y0, z1), xs), + CubicLerp(ValCoord(seed, x0, y1, z1), ValCoord(seed, x1, y1, z1), ValCoord(seed, x2, y1, z1), ValCoord(seed, x3, y1, z1), xs), + CubicLerp(ValCoord(seed, x0, y2, z1), ValCoord(seed, x1, y2, z1), ValCoord(seed, x2, y2, z1), ValCoord(seed, x3, y2, z1), xs), + CubicLerp(ValCoord(seed, x0, y3, z1), ValCoord(seed, x1, y3, z1), ValCoord(seed, x2, y3, z1), ValCoord(seed, x3, y3, z1), xs), + ys + ), + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z2), ValCoord(seed, x1, y0, z2), ValCoord(seed, x2, y0, z2), ValCoord(seed, x3, y0, z2), xs), + CubicLerp(ValCoord(seed, x0, y1, z2), ValCoord(seed, x1, y1, z2), ValCoord(seed, x2, y1, z2), ValCoord(seed, x3, y1, z2), xs), + CubicLerp(ValCoord(seed, x0, y2, z2), ValCoord(seed, x1, y2, z2), ValCoord(seed, x2, y2, z2), ValCoord(seed, x3, y2, z2), xs), + CubicLerp(ValCoord(seed, x0, y3, z2), ValCoord(seed, x1, y3, z2), ValCoord(seed, x2, y3, z2), ValCoord(seed, x3, y3, z2), xs), + ys + ), + CubicLerp( + CubicLerp(ValCoord(seed, x0, y0, z3), ValCoord(seed, x1, y0, z3), ValCoord(seed, x2, y0, z3), ValCoord(seed, x3, y0, z3), xs), + CubicLerp(ValCoord(seed, x0, y1, z3), ValCoord(seed, x1, y1, z3), ValCoord(seed, x2, y1, z3), ValCoord(seed, x3, y1, z3), xs), + CubicLerp(ValCoord(seed, x0, y2, z3), ValCoord(seed, x1, y2, z3), ValCoord(seed, x2, y2, z3), ValCoord(seed, x3, y2, z3), xs), + CubicLerp(ValCoord(seed, x0, y3, z3), ValCoord(seed, x1, y3, z3), ValCoord(seed, x2, y3, z3), ValCoord(seed, x3, y3, z3), xs), + ys + ), + zs + ) + * 0.2962963F; + } + + private float SingleValue(int seed, double x, double y) { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + float xs = InterpHermite((float)(x - x0)); + float ys = InterpHermite((float)(y - y0)); + x0 *= 501125321; + y0 *= 1136930381; + int x1 = x0 + 501125321; + int y1 = y0 + 1136930381; + float xf0 = Lerp(ValCoord(seed, x0, y0), ValCoord(seed, x1, y0), xs); + float xf1 = Lerp(ValCoord(seed, x0, y1), ValCoord(seed, x1, y1), xs); + return Lerp(xf0, xf1, ys); + } + + private float SingleValue(int seed, double x, double y, double z) { + int x0 = FastFloor(x); + int y0 = FastFloor(y); + int z0 = FastFloor(z); + float xs = InterpHermite((float)(x - x0)); + float ys = InterpHermite((float)(y - y0)); + float zs = InterpHermite((float)(z - z0)); + x0 *= 501125321; + y0 *= 1136930381; + z0 *= 1720413743; + int x1 = x0 + 501125321; + int y1 = y0 + 1136930381; + int z1 = z0 + 1720413743; + float xf00 = Lerp(ValCoord(seed, x0, y0, z0), ValCoord(seed, x1, y0, z0), xs); + float xf10 = Lerp(ValCoord(seed, x0, y1, z0), ValCoord(seed, x1, y1, z0), xs); + float xf01 = Lerp(ValCoord(seed, x0, y0, z1), ValCoord(seed, x1, y0, z1), xs); + float xf11 = Lerp(ValCoord(seed, x0, y1, z1), ValCoord(seed, x1, y1, z1), xs); + float yf0 = Lerp(xf00, xf10, ys); + float yf1 = Lerp(xf01, xf11, ys); + return Lerp(yf0, yf1, zs); + } + + private void DoSingleDomainWarp(int seed, float amp, float freq, double x, double y, @Nonnull FastNoiseLite.Vector2 coord) { + switch (this.mDomainWarpType) { + case OpenSimplex2: + this.SingleDomainWarpSimplexGradient(seed, amp * 38.283688F, freq, x, y, coord, false); + break; + case OpenSimplex2Reduced: + this.SingleDomainWarpSimplexGradient(seed, amp * 16.0F, freq, x, y, coord, true); + break; + case BasicGrid: + this.SingleDomainWarpBasicGrid(seed, amp, freq, x, y, coord); + } + } + + private void DoSingleDomainWarp(int seed, float amp, float freq, double x, double y, double z, @Nonnull FastNoiseLite.Vector3 coord) { + switch (this.mDomainWarpType) { + case OpenSimplex2: + this.SingleDomainWarpOpenSimplex2Gradient(seed, amp * 32.694283F, freq, x, y, z, coord, false); + break; + case OpenSimplex2Reduced: + this.SingleDomainWarpOpenSimplex2Gradient(seed, amp * 7.716049F, freq, x, y, z, coord, true); + break; + case BasicGrid: + this.SingleDomainWarpBasicGrid(seed, amp, freq, x, y, z, coord); + } + } + + private void DomainWarpSingle(@Nonnull FastNoiseLite.Vector2 coord) { + int seed = this.mSeed; + float amp = this.mDomainWarpAmp * this.mFractalBounding; + float freq = this.mDomainWarpFreq; + double xs = coord.x; + double ys = coord.y; + switch (this.mDomainWarpType) { + case OpenSimplex2: + case OpenSimplex2Reduced: + double SQRT3 = 1.7320508075688772; + double F2 = 0.3660254037844386; + double t = (xs + ys) * 0.3660254037844386; + xs += t; + ys += t; + default: + this.DoSingleDomainWarp(seed, amp, freq, xs, ys, coord); + } + } + + public void DomainWarpSingle(@Nonnull FastNoiseLite.Vector3 coord) { + int seed = this.mSeed; + float amp = this.mDomainWarpAmp * this.mFractalBounding; + float freq = this.mDomainWarpFreq; + double xs = coord.x; + double ys = coord.y; + double zs = coord.z; + switch (this.mWarpTransformType3D) { + case ImproveXYPlanes: { + double xy = xs + ys; + double s2 = xy * -0.211324865405187; + zs *= 0.577350269189626; + xs += s2 - zs; + ys = ys + s2 - zs; + zs += xy * 0.577350269189626; + break; + } + case ImproveXZPlanes: { + double xz = xs + zs; + double s2 = xz * -0.211324865405187; + ys *= 0.577350269189626; + xs += s2 - ys; + zs += s2 - ys; + ys += xz * 0.577350269189626; + break; + } + case DefaultOpenSimplex2: + double R3 = 0.6666666666666666; + double r = (xs + ys + zs) * 0.6666666666666666; + xs = r - xs; + ys = r - ys; + zs = r - zs; + } + + this.DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, coord); + } + + public void DomainWarpFractalProgressive(@Nonnull FastNoiseLite.Vector2 coord) { + int seed = this.mSeed; + float amp = this.mDomainWarpAmp * this.mFractalBounding; + float freq = this.mDomainWarpFreq; + + for (int i = 0; i < this.mOctaves; i++) { + double xs = coord.x; + double ys = coord.y; + switch (this.mDomainWarpType) { + case OpenSimplex2: + case OpenSimplex2Reduced: + double SQRT3 = 1.7320508075688772; + double F2 = 0.3660254037844386; + double t = (xs + ys) * 0.3660254037844386; + xs += t; + ys += t; + default: + this.DoSingleDomainWarp(seed, amp, freq, xs, ys, coord); + seed++; + amp *= this.mGain; + freq *= this.mLacunarity; + } + } + } + + public void DomainWarpFractalProgressive(@Nonnull FastNoiseLite.Vector3 coord) { + int seed = this.mSeed; + float amp = this.mDomainWarpAmp * this.mFractalBounding; + float freq = this.mDomainWarpFreq; + + for (int i = 0; i < this.mOctaves; i++) { + double xs = coord.x; + double ys = coord.y; + double zs = coord.z; + switch (this.mWarpTransformType3D) { + case ImproveXYPlanes: { + double xy = xs + ys; + double s2 = xy * -0.211324865405187; + zs *= 0.577350269189626; + xs += s2 - zs; + ys = ys + s2 - zs; + zs += xy * 0.577350269189626; + break; + } + case ImproveXZPlanes: { + double xz = xs + zs; + double s2 = xz * -0.211324865405187; + ys *= 0.577350269189626; + xs += s2 - ys; + zs += s2 - ys; + ys += xz * 0.577350269189626; + break; + } + case DefaultOpenSimplex2: + double R3 = 0.6666666666666666; + double r = (xs + ys + zs) * 0.6666666666666666; + xs = r - xs; + ys = r - ys; + zs = r - zs; + } + + this.DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, coord); + seed++; + amp *= this.mGain; + freq *= this.mLacunarity; + } + } + + private void DomainWarpFractalIndependent(@Nonnull FastNoiseLite.Vector2 coord) { + double xs = coord.x; + double ys = coord.y; + switch (this.mDomainWarpType) { + case OpenSimplex2: + case OpenSimplex2Reduced: + double SQRT3 = 1.7320508075688772; + double F2 = 0.3660254037844386; + double t = (xs + ys) * 0.3660254037844386; + xs += t; + ys += t; + default: + int seed = this.mSeed; + float amp = this.mDomainWarpAmp * this.mFractalBounding; + float freq = this.mFrequency; + + for (int i = 0; i < this.mOctaves; i++) { + this.DoSingleDomainWarp(seed, amp, freq, xs, ys, coord); + seed++; + amp *= this.mGain; + freq *= this.mLacunarity; + } + } + } + + private void DomainWarpFractalIndependent(@Nonnull FastNoiseLite.Vector3 coord) { + double xs = coord.x; + double ys = coord.y; + double zs = coord.z; + switch (this.mWarpTransformType3D) { + case ImproveXYPlanes: { + double xy = xs + ys; + double s2 = xy * -0.211324865405187; + zs *= 0.577350269189626; + xs += s2 - zs; + ys = ys + s2 - zs; + zs += xy * 0.577350269189626; + break; + } + case ImproveXZPlanes: { + double xz = xs + zs; + double s2 = xz * -0.211324865405187; + ys *= 0.577350269189626; + xs += s2 - ys; + zs += s2 - ys; + ys += xz * 0.577350269189626; + break; + } + case DefaultOpenSimplex2: + double R3 = 0.6666666666666666; + double r = (xs + ys + zs) * 0.6666666666666666; + xs = r - xs; + ys = r - ys; + zs = r - zs; + } + + int seed = this.mSeed; + float amp = this.mDomainWarpAmp * this.mFractalBounding; + float freq = this.mFrequency; + + for (int i = 0; i < this.mOctaves; i++) { + this.DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, coord); + seed++; + amp *= this.mGain; + freq *= this.mLacunarity; + } + } + + private void SingleDomainWarpBasicGrid(int seed, float warpAmp, float frequency, double x, double y, @Nonnull FastNoiseLite.Vector2 coord) { + double xf = x * frequency; + double yf = y * frequency; + int x0 = FastFloor(xf); + int y0 = FastFloor(yf); + float xs = InterpHermite((float)(xf - x0)); + float ys = InterpHermite((float)(yf - y0)); + x0 *= 501125321; + y0 *= 1136930381; + int x1 = x0 + 501125321; + int y1 = y0 + 1136930381; + int hash0 = hash(seed, x0, y0) & 510; + int hash1 = hash(seed, x1, y0) & 510; + float lx0x = Lerp(RandVecs2D[hash0], RandVecs2D[hash1], xs); + float ly0x = Lerp(RandVecs2D[hash0 | 1], RandVecs2D[hash1 | 1], xs); + hash0 = hash(seed, x0, y1) & 510; + hash1 = hash(seed, x1, y1) & 510; + float lx1x = Lerp(RandVecs2D[hash0], RandVecs2D[hash1], xs); + float ly1x = Lerp(RandVecs2D[hash0 | 1], RandVecs2D[hash1 | 1], xs); + coord.x = coord.x + Lerp(lx0x, lx1x, ys) * warpAmp; + coord.y = coord.y + Lerp(ly0x, ly1x, ys) * warpAmp; + } + + private void SingleDomainWarpBasicGrid(int seed, float warpAmp, float frequency, double x, double y, double z, @Nonnull FastNoiseLite.Vector3 coord) { + double xf = x * frequency; + double yf = y * frequency; + double zf = z * frequency; + int x0 = FastFloor(xf); + int y0 = FastFloor(yf); + int z0 = FastFloor(zf); + float xs = InterpHermite((float)(xf - x0)); + float ys = InterpHermite((float)(yf - y0)); + float zs = InterpHermite((float)(zf - z0)); + x0 *= 501125321; + y0 *= 1136930381; + z0 *= 1720413743; + int x1 = x0 + 501125321; + int y1 = y0 + 1136930381; + int z1 = z0 + 1720413743; + int hash0 = hash(seed, x0, y0, z0) & 1020; + int hash1 = hash(seed, x1, y0, z0) & 1020; + float lx0x = Lerp(randVecs3D[hash0], randVecs3D[hash1], xs); + float ly0x = Lerp(randVecs3D[hash0 | 1], randVecs3D[hash1 | 1], xs); + float lz0x = Lerp(randVecs3D[hash0 | 2], randVecs3D[hash1 | 2], xs); + hash0 = hash(seed, x0, y1, z0) & 1020; + hash1 = hash(seed, x1, y1, z0) & 1020; + float lx1x = Lerp(randVecs3D[hash0], randVecs3D[hash1], xs); + float ly1x = Lerp(randVecs3D[hash0 | 1], randVecs3D[hash1 | 1], xs); + float lz1x = Lerp(randVecs3D[hash0 | 2], randVecs3D[hash1 | 2], xs); + float lx0y = Lerp(lx0x, lx1x, ys); + float ly0y = Lerp(ly0x, ly1x, ys); + float lz0y = Lerp(lz0x, lz1x, ys); + hash0 = hash(seed, x0, y0, z1) & 1020; + hash1 = hash(seed, x1, y0, z1) & 1020; + lx0x = Lerp(randVecs3D[hash0], randVecs3D[hash1], xs); + ly0x = Lerp(randVecs3D[hash0 | 1], randVecs3D[hash1 | 1], xs); + lz0x = Lerp(randVecs3D[hash0 | 2], randVecs3D[hash1 | 2], xs); + hash0 = hash(seed, x0, y1, z1) & 1020; + hash1 = hash(seed, x1, y1, z1) & 1020; + lx1x = Lerp(randVecs3D[hash0], randVecs3D[hash1], xs); + ly1x = Lerp(randVecs3D[hash0 | 1], randVecs3D[hash1 | 1], xs); + lz1x = Lerp(randVecs3D[hash0 | 2], randVecs3D[hash1 | 2], xs); + coord.x = coord.x + Lerp(lx0y, Lerp(lx0x, lx1x, ys), zs) * warpAmp; + coord.y = coord.y + Lerp(ly0y, Lerp(ly0x, ly1x, ys), zs) * warpAmp; + coord.z = coord.z + Lerp(lz0y, Lerp(lz0x, lz1x, ys), zs) * warpAmp; + } + + private void SingleDomainWarpSimplexGradient( + int seed, float warpAmp, float frequency, double x, double y, @Nonnull FastNoiseLite.Vector2 coord, boolean outGradOnly + ) { + float SQRT3 = 1.7320508F; + float G2 = 0.21132487F; + x *= frequency; + y *= frequency; + int i = FastFloor(x); + int j = FastFloor(y); + float xi = (float)(x - i); + float yi = (float)(y - j); + float t = (xi + yi) * 0.21132487F; + float x0 = xi - t; + float y0 = yi - t; + i *= 501125321; + j *= 1136930381; + float vy = 0.0F; + float vx = 0.0F; + float a = 0.5F - x0 * x0 - y0 * y0; + if (a > 0.0F) { + float aaaa = a * a * (a * a); + float xo; + float yo; + if (outGradOnly) { + int hash = hash(seed, i, j) & 510; + xo = RandVecs2D[hash]; + yo = RandVecs2D[hash | 1]; + } else { + int hash = hash(seed, i, j); + int index1 = hash & 254; + int index2 = hash >> 7 & 510; + float xg = Gradients2D[index1]; + float yg = Gradients2D[index1 | 1]; + float value = x0 * xg + y0 * yg; + float xgo = RandVecs2D[index2]; + float ygo = RandVecs2D[index2 | 1]; + xo = value * xgo; + yo = value * ygo; + } + + vx += aaaa * xo; + vy += aaaa * yo; + } + + float c = 3.1547005F * t + (-0.6666666F + a); + if (c > 0.0F) { + float x2 = x0 + -0.57735026F; + float y2 = y0 + -0.57735026F; + float cccc = c * c * (c * c); + float xo; + float yo; + if (outGradOnly) { + int hash = hash(seed, i + 501125321, j + 1136930381) & 510; + xo = RandVecs2D[hash]; + yo = RandVecs2D[hash | 1]; + } else { + int hash = hash(seed, i + 501125321, j + 1136930381); + int index1 = hash & 254; + int index2 = hash >> 7 & 510; + float xg = Gradients2D[index1]; + float yg = Gradients2D[index1 | 1]; + float value = x2 * xg + y2 * yg; + float xgo = RandVecs2D[index2]; + float ygo = RandVecs2D[index2 | 1]; + xo = value * xgo; + yo = value * ygo; + } + + vx += cccc * xo; + vy += cccc * yo; + } + + if (y0 > x0) { + float x1 = x0 + 0.21132487F; + float y1 = y0 + -0.7886751F; + float b = 0.5F - x1 * x1 - y1 * y1; + if (b > 0.0F) { + float bbbb = b * b * (b * b); + float xo; + float yo; + if (outGradOnly) { + int hash = hash(seed, i, j + 1136930381) & 510; + xo = RandVecs2D[hash]; + yo = RandVecs2D[hash | 1]; + } else { + int hash = hash(seed, i, j + 1136930381); + int index1 = hash & 254; + int index2 = hash >> 7 & 510; + float xg = Gradients2D[index1]; + float yg = Gradients2D[index1 | 1]; + float value = x1 * xg + y1 * yg; + float xgo = RandVecs2D[index2]; + float ygo = RandVecs2D[index2 | 1]; + xo = value * xgo; + yo = value * ygo; + } + + vx += bbbb * xo; + vy += bbbb * yo; + } + } else { + float x1 = x0 + -0.7886751F; + float y1 = y0 + 0.21132487F; + float b = 0.5F - x1 * x1 - y1 * y1; + if (b > 0.0F) { + float bbbb = b * b * (b * b); + float xo; + float yo; + if (outGradOnly) { + int hash = hash(seed, i + 501125321, j) & 510; + xo = RandVecs2D[hash]; + yo = RandVecs2D[hash | 1]; + } else { + int hash = hash(seed, i + 501125321, j); + int index1 = hash & 254; + int index2 = hash >> 7 & 510; + float xg = Gradients2D[index1]; + float yg = Gradients2D[index1 | 1]; + float value = x1 * xg + y1 * yg; + float xgo = RandVecs2D[index2]; + float ygo = RandVecs2D[index2 | 1]; + xo = value * xgo; + yo = value * ygo; + } + + vx += bbbb * xo; + vy += bbbb * yo; + } + } + + coord.x += vx * warpAmp; + coord.y += vy * warpAmp; + } + + private void SingleDomainWarpOpenSimplex2Gradient( + int seed, float warpAmp, float frequency, double x, double y, double z, @Nonnull FastNoiseLite.Vector3 coord, boolean outGradOnly + ) { + x *= frequency; + y *= frequency; + z *= frequency; + int i = fastRound(x); + int j = fastRound(y); + int k = fastRound(z); + float x0 = (float)x - i; + float y0 = (float)y - j; + float z0 = (float)z - k; + int xNSign = (int)(-x0 - 1.0F) | 1; + int yNSign = (int)(-y0 - 1.0F) | 1; + int zNSign = (int)(-z0 - 1.0F) | 1; + float ax0 = xNSign * -x0; + float ay0 = yNSign * -y0; + float az0 = zNSign * -z0; + i *= 501125321; + j *= 1136930381; + k *= 1720413743; + float vz = 0.0F; + float vy = 0.0F; + float vx = 0.0F; + float a = 0.6F - x0 * x0 - (y0 * y0 + z0 * z0); + int l = 0; + + while (true) { + if (a > 0.0F) { + float aaaa = a * a * (a * a); + float xo; + float yo; + float zo; + if (outGradOnly) { + int hash = hash(seed, i, j, k) & 1020; + xo = randVecs3D[hash]; + yo = randVecs3D[hash | 1]; + zo = randVecs3D[hash | 2]; + } else { + int hash = hash(seed, i, j, k); + int index1 = hash & 252; + int index2 = hash >> 6 & 1020; + float xg = Gradients3D[index1]; + float yg = Gradients3D[index1 | 1]; + float zg = Gradients3D[index1 | 2]; + float value = x0 * xg + y0 * yg + z0 * zg; + float xgo = randVecs3D[index2]; + float ygo = randVecs3D[index2 | 1]; + float zgo = randVecs3D[index2 | 2]; + xo = value * xgo; + yo = value * ygo; + zo = value * zgo; + } + + vx += aaaa * xo; + vy += aaaa * yo; + vz += aaaa * zo; + } + + int i1 = i; + int j1 = j; + int k1 = k; + float x1 = x0; + float y1 = y0; + float z1 = z0; + float var56; + if (ax0 >= ay0 && ax0 >= az0) { + x1 = x0 + xNSign; + var56 = a + ax0 + ax0; + i1 = i - xNSign * 501125321; + } else if (ay0 > ax0 && ay0 >= az0) { + y1 = y0 + yNSign; + var56 = a + ay0 + ay0; + j1 = j - yNSign * 1136930381; + } else { + z1 = z0 + zNSign; + var56 = a + az0 + az0; + k1 = k - zNSign * 1720413743; + } + + if (var56 > 1.0F) { + float bbbb = --var56 * var56 * (var56 * var56); + float xo; + float yo; + float zo; + if (outGradOnly) { + int hash = hash(seed, i1, j1, k1) & 1020; + xo = randVecs3D[hash]; + yo = randVecs3D[hash | 1]; + zo = randVecs3D[hash | 2]; + } else { + int hash = hash(seed, i1, j1, k1); + int index1 = hash & 252; + int index2 = hash >> 6 & 1020; + float xg = Gradients3D[index1]; + float yg = Gradients3D[index1 | 1]; + float zg = Gradients3D[index1 | 2]; + float value = x1 * xg + y1 * yg + z1 * zg; + float xgo = randVecs3D[index2]; + float ygo = randVecs3D[index2 | 1]; + float zgo = randVecs3D[index2 | 2]; + xo = value * xgo; + yo = value * ygo; + zo = value * zgo; + } + + vx += bbbb * xo; + vy += bbbb * yo; + vz += bbbb * zo; + } + + if (l == 1) { + coord.x += vx * warpAmp; + coord.y += vy * warpAmp; + coord.z += vz * warpAmp; + return; + } + + ax0 = 0.5F - ax0; + ay0 = 0.5F - ay0; + az0 = 0.5F - az0; + x0 = xNSign * ax0; + y0 = yNSign * ay0; + z0 = zNSign * az0; + a += 0.75F - ax0 - (ay0 + az0); + i += xNSign >> 1 & 501125321; + j += yNSign >> 1 & 1136930381; + k += zNSign >> 1 & 1720413743; + xNSign = -xNSign; + yNSign = -yNSign; + zNSign = -zNSign; + seed += 1293373; + l++; + } + } + + public static enum CellularDistanceFunction { + Euclidean, + EuclideanSq, + Manhattan, + Hybrid; + + private CellularDistanceFunction() { + } + } + + public static enum CellularReturnType { + CellValue, + Distance, + Distance2, + Distance2Add, + Distance2Sub, + Distance2Mul, + Distance2Div; + + public static final Codec CODEC = new EnumCodec<>(FastNoiseLite.CellularReturnType.class); + + private CellularReturnType() { + } + } + + public static enum DomainWarpType { + OpenSimplex2, + OpenSimplex2Reduced, + BasicGrid; + + private DomainWarpType() { + } + } + + public static enum FractalType { + None, + FBm, + Ridged, + PingPong, + DomainWarpProgressive, + DomainWarpIndependent; + + private FractalType() { + } + } + + public static enum NoiseType { + OpenSimplex2, + OpenSimplex2S, + Cellular, + Perlin, + ValueCubic, + Value; + + private NoiseType() { + } + } + + public static enum RotationType3D { + None, + ImproveXYPlanes, + ImproveXZPlanes; + + private RotationType3D() { + } + } + + private static enum TransformType3D { + None, + ImproveXYPlanes, + ImproveXZPlanes, + DefaultOpenSimplex2; + + private TransformType3D() { + } + } + + public static class Vector2 { + public double x; + public double y; + + public Vector2(double x, double y) { + this.x = x; + this.y = y; + } + } + + public static class Vector3 { + public double x; + public double y; + public double z; + + public Vector3(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/fields/noise/CellNoiseField.java b/src/com/hypixel/hytale/builtin/hytalegenerator/fields/noise/CellNoiseField.java new file mode 100644 index 0000000..c6afecf --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/fields/noise/CellNoiseField.java @@ -0,0 +1,121 @@ +package com.hypixel.hytale.builtin.hytalegenerator.fields.noise; + +import com.hypixel.hytale.builtin.hytalegenerator.fields.FastNoiseLite; +import javax.annotation.Nonnull; + +public class CellNoiseField extends NoiseField { + private FastNoiseLite cellNoise; + private int seed; + private boolean doDomainWarp; + private double scaleX; + private double scaleY; + private double scaleZ; + + public CellNoiseField( + int seed, + double scaleX, + double scaleY, + double scaleZ, + double jitter, + int octaves, + @Nonnull FastNoiseLite.CellularReturnType cellType, + @Nonnull FastNoiseLite.DomainWarpType domainWarpType, + double warpAmount, + double warpScale + ) { + if (octaves >= 1 && !(warpAmount <= 0.0) && !(warpScale <= 0.0)) { + this.seed = seed; + this.scaleX = scaleX; + this.scaleY = scaleY; + this.scaleZ = scaleZ; + this.cellNoise = new FastNoiseLite(); + float frequency = 1.0F; + float warpFrequency = 1.0F / (float)warpScale; + this.doDomainWarp = true; + jitter *= 2.0; + this.cellNoise.setNoiseType(FastNoiseLite.NoiseType.Cellular); + this.cellNoise.setCellularReturnType(cellType); + this.cellNoise.setFractalOctaves(octaves); + this.cellNoise.setFractalType(FastNoiseLite.FractalType.FBm); + this.cellNoise.setCellularDistanceFunction(FastNoiseLite.CellularDistanceFunction.Euclidean); + this.cellNoise.setSeed(seed); + this.cellNoise.setFrequency(frequency); + this.cellNoise.setDomainWarpType(FastNoiseLite.DomainWarpType.OpenSimplex2); + this.cellNoise.setDomainWarpAmp((float)warpAmount); + this.cellNoise.setDomainWarpFreq(warpFrequency); + this.cellNoise.setCellularJitter((float)jitter); + } else { + throw new IllegalArgumentException(); + } + } + + public CellNoiseField(int seed, double scaleX, double scaleY, double scaleZ, double jitter, int octaves, @Nonnull FastNoiseLite.CellularReturnType cellType) { + if (octaves < 1) { + throw new IllegalArgumentException(); + } else { + this.seed = seed; + this.scaleX = scaleX; + this.scaleY = scaleY; + this.scaleZ = scaleZ; + this.cellNoise = new FastNoiseLite(); + float frequency = 1.0F; + this.doDomainWarp = false; + jitter *= 2.0; + this.cellNoise.setNoiseType(FastNoiseLite.NoiseType.Cellular); + this.cellNoise.setCellularReturnType(cellType); + this.cellNoise.setFractalOctaves(octaves); + this.cellNoise.setFractalType(FastNoiseLite.FractalType.FBm); + this.cellNoise.setCellularDistanceFunction(FastNoiseLite.CellularDistanceFunction.Euclidean); + this.cellNoise.setSeed(seed); + this.cellNoise.setFrequency(frequency); + this.cellNoise.setCellularJitter((float)jitter); + } + } + + @Override + public double valueAt(double x, double y, double z, double w) { + x /= this.scaleX; + y /= this.scaleY; + z /= this.scaleZ; + if (this.doDomainWarp) { + FastNoiseLite.Vector3 point = new FastNoiseLite.Vector3((float)x, (float)y, (float)z); + this.cellNoise.DomainWarp(point); + return this.cellNoise.getNoise(point.x, point.y, point.z); + } else { + return this.cellNoise.getNoise(x, y, z); + } + } + + @Override + public double valueAt(double x, double y, double z) { + x /= this.scaleX; + y /= this.scaleY; + z /= this.scaleZ; + if (this.doDomainWarp) { + FastNoiseLite.Vector3 point = new FastNoiseLite.Vector3((float)x, (float)y, (float)z); + this.cellNoise.DomainWarp(point); + return this.cellNoise.getNoise(point.x, point.y, point.z); + } else { + return this.cellNoise.getNoise(x, y, z); + } + } + + @Override + public double valueAt(double x, double z) { + x /= this.scaleX; + z /= this.scaleZ; + if (this.doDomainWarp) { + FastNoiseLite.Vector2 point = new FastNoiseLite.Vector2((float)x, (float)z); + this.cellNoise.DomainWarp(point); + return this.cellNoise.getNoise(point.x, point.y); + } else { + return this.cellNoise.getNoise(x, z); + } + } + + @Override + public double valueAt(double x) { + x /= this.scaleX; + return this.cellNoise.getNoise((float)x, 0.0); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/fields/noise/NoiseField.java b/src/com/hypixel/hytale/builtin/hytalegenerator/fields/noise/NoiseField.java new file mode 100644 index 0000000..b5dd575 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/fields/noise/NoiseField.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.builtin.hytalegenerator.fields.noise; + +import javax.annotation.Nonnull; + +public abstract class NoiseField { + protected double scaleX = 1.0; + protected double scaleY = 1.0; + protected double scaleZ = 1.0; + protected double scaleW = 1.0; + + public NoiseField() { + } + + public abstract double valueAt(double var1, double var3, double var5, double var7); + + public abstract double valueAt(double var1, double var3, double var5); + + public abstract double valueAt(double var1, double var3); + + public abstract double valueAt(double var1); + + @Nonnull + public NoiseField setScale(double scaleX, double scaleY, double scaleZ, double scaleW) { + this.scaleX = scaleX; + this.scaleY = scaleY; + this.scaleZ = scaleZ; + this.scaleW = scaleW; + return this; + } + + @Nonnull + public NoiseField setScale(double scale) { + this.scaleX = scale; + this.scaleY = scale; + this.scaleZ = scale; + this.scaleW = scale; + return this; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/fields/noise/Simplex.java b/src/com/hypixel/hytale/builtin/hytalegenerator/fields/noise/Simplex.java new file mode 100644 index 0000000..abb91b1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/fields/noise/Simplex.java @@ -0,0 +1,684 @@ +package com.hypixel.hytale.builtin.hytalegenerator.fields.noise; + +import javax.annotation.Nonnull; + +class Simplex { + private static final double F2 = 0.5 * (Math.sqrt(3.0) - 1.0); + private static final double G2 = (3.0 - Math.sqrt(3.0)) / 6.0; + private static final double F3 = 0.3333333333333333; + private static final double G3 = 0.16666666666666666; + private static final double F4 = (Math.sqrt(5.0) - 1.0) / 4.0; + private static final double G4 = (5.0 - Math.sqrt(5.0)) / 20.0; + private static final Simplex.Grad[] grad3 = new Simplex.Grad[]{ + new Simplex.Grad(1.0, 1.0, 0.0), + new Simplex.Grad(-1.0, 1.0, 0.0), + new Simplex.Grad(1.0, -1.0, 0.0), + new Simplex.Grad(-1.0, -1.0, 0.0), + new Simplex.Grad(1.0, 0.0, 1.0), + new Simplex.Grad(-1.0, 0.0, 1.0), + new Simplex.Grad(1.0, 0.0, -1.0), + new Simplex.Grad(-1.0, 0.0, -1.0), + new Simplex.Grad(0.0, 1.0, 1.0), + new Simplex.Grad(0.0, -1.0, 1.0), + new Simplex.Grad(0.0, 1.0, -1.0), + new Simplex.Grad(0.0, -1.0, -1.0) + }; + private static final Simplex.Grad[] grad4 = new Simplex.Grad[]{ + new Simplex.Grad(0.0, 1.0, 1.0, 1.0), + new Simplex.Grad(0.0, 1.0, 1.0, -1.0), + new Simplex.Grad(0.0, 1.0, -1.0, 1.0), + new Simplex.Grad(0.0, 1.0, -1.0, -1.0), + new Simplex.Grad(0.0, -1.0, 1.0, 1.0), + new Simplex.Grad(0.0, -1.0, 1.0, -1.0), + new Simplex.Grad(0.0, -1.0, -1.0, 1.0), + new Simplex.Grad(0.0, -1.0, -1.0, -1.0), + new Simplex.Grad(1.0, 0.0, 1.0, 1.0), + new Simplex.Grad(1.0, 0.0, 1.0, -1.0), + new Simplex.Grad(1.0, 0.0, -1.0, 1.0), + new Simplex.Grad(1.0, 0.0, -1.0, -1.0), + new Simplex.Grad(-1.0, 0.0, 1.0, 1.0), + new Simplex.Grad(-1.0, 0.0, 1.0, -1.0), + new Simplex.Grad(-1.0, 0.0, -1.0, 1.0), + new Simplex.Grad(-1.0, 0.0, -1.0, -1.0), + new Simplex.Grad(1.0, 1.0, 0.0, 1.0), + new Simplex.Grad(1.0, 1.0, 0.0, -1.0), + new Simplex.Grad(1.0, -1.0, 0.0, 1.0), + new Simplex.Grad(1.0, -1.0, 0.0, -1.0), + new Simplex.Grad(-1.0, 1.0, 0.0, 1.0), + new Simplex.Grad(-1.0, 1.0, 0.0, -1.0), + new Simplex.Grad(-1.0, -1.0, 0.0, 1.0), + new Simplex.Grad(-1.0, -1.0, 0.0, -1.0), + new Simplex.Grad(1.0, 1.0, 1.0, 0.0), + new Simplex.Grad(1.0, 1.0, -1.0, 0.0), + new Simplex.Grad(1.0, -1.0, 1.0, 0.0), + new Simplex.Grad(1.0, -1.0, -1.0, 0.0), + new Simplex.Grad(-1.0, 1.0, 1.0, 0.0), + new Simplex.Grad(-1.0, 1.0, -1.0, 0.0), + new Simplex.Grad(-1.0, -1.0, 1.0, 0.0), + new Simplex.Grad(-1.0, -1.0, -1.0, 0.0) + }; + private static final short[] p = new short[]{ + 151, + 160, + 137, + 91, + 90, + 15, + 131, + 13, + 201, + 95, + 96, + 53, + 194, + 233, + 7, + 225, + 140, + 36, + 103, + 30, + 69, + 142, + 8, + 99, + 37, + 240, + 21, + 10, + 23, + 190, + 6, + 148, + 247, + 120, + 234, + 75, + 0, + 26, + 197, + 62, + 94, + 252, + 219, + 203, + 117, + 35, + 11, + 32, + 57, + 177, + 33, + 88, + 237, + 149, + 56, + 87, + 174, + 20, + 125, + 136, + 171, + 168, + 68, + 175, + 74, + 165, + 71, + 134, + 139, + 48, + 27, + 166, + 77, + 146, + 158, + 231, + 83, + 111, + 229, + 122, + 60, + 211, + 133, + 230, + 220, + 105, + 92, + 41, + 55, + 46, + 245, + 40, + 244, + 102, + 143, + 54, + 65, + 25, + 63, + 161, + 1, + 216, + 80, + 73, + 209, + 76, + 132, + 187, + 208, + 89, + 18, + 169, + 200, + 196, + 135, + 130, + 116, + 188, + 159, + 86, + 164, + 100, + 109, + 198, + 173, + 186, + 3, + 64, + 52, + 217, + 226, + 250, + 124, + 123, + 5, + 202, + 38, + 147, + 118, + 126, + 255, + 82, + 85, + 212, + 207, + 206, + 59, + 227, + 47, + 16, + 58, + 17, + 182, + 189, + 28, + 42, + 223, + 183, + 170, + 213, + 119, + 248, + 152, + 2, + 44, + 154, + 163, + 70, + 221, + 153, + 101, + 155, + 167, + 43, + 172, + 9, + 129, + 22, + 39, + 253, + 19, + 98, + 108, + 110, + 79, + 113, + 224, + 232, + 178, + 185, + 112, + 104, + 218, + 246, + 97, + 228, + 251, + 34, + 242, + 193, + 238, + 210, + 144, + 12, + 191, + 179, + 162, + 241, + 81, + 51, + 145, + 235, + 249, + 14, + 239, + 107, + 49, + 192, + 214, + 31, + 181, + 199, + 106, + 157, + 184, + 84, + 204, + 176, + 115, + 121, + 50, + 45, + 127, + 4, + 150, + 254, + 138, + 236, + 205, + 93, + 222, + 114, + 67, + 29, + 24, + 72, + 243, + 141, + 128, + 195, + 78, + 66, + 215, + 61, + 156, + 180 + }; + private static final short[] perm = new short[512]; + private static final short[] permMod12 = new short[512]; + + Simplex() { + } + + private static int fastfloor(double x) { + int xi = (int)x; + return x < xi ? xi - 1 : xi; + } + + private static double dot(@Nonnull Simplex.Grad g, double x, double y) { + return g.x * x + g.y * y; + } + + private static double dot(@Nonnull Simplex.Grad g, double x, double y, double z) { + return g.x * x + g.y * y + g.z * z; + } + + private static double dot(@Nonnull Simplex.Grad g, double x, double y, double z, double w) { + return g.x * x + g.y * y + g.z * z + g.w * w; + } + + public static double noise(double xin, double yin) { + double s = (xin + yin) * F2; + int i = fastfloor(xin + s); + int j = fastfloor(yin + s); + double t = (i + j) * G2; + double X0 = i - t; + double Y0 = j - t; + double x0 = xin - X0; + double y0 = yin - Y0; + int i1; + int j1; + if (x0 > y0) { + i1 = 1; + j1 = 0; + } else { + i1 = 0; + j1 = 1; + } + + double x1 = x0 - i1 + G2; + double y1 = y0 - j1 + G2; + double x2 = x0 - 1.0 + 2.0 * G2; + double y2 = y0 - 1.0 + 2.0 * G2; + int ii = i & 0xFF; + int jj = j & 0xFF; + int gi0 = permMod12[ii + perm[jj]]; + int gi1 = permMod12[ii + i1 + perm[jj + j1]]; + int gi2 = permMod12[ii + 1 + perm[jj + 1]]; + double t0 = 0.5 - x0 * x0 - y0 * y0; + double n0; + if (t0 < 0.0) { + n0 = 0.0; + } else { + t0 *= t0; + n0 = t0 * t0 * dot(grad3[gi0], x0, y0); + } + + double t1 = 0.5 - x1 * x1 - y1 * y1; + double n1; + if (t1 < 0.0) { + n1 = 0.0; + } else { + t1 *= t1; + n1 = t1 * t1 * dot(grad3[gi1], x1, y1); + } + + double t2 = 0.5 - x2 * x2 - y2 * y2; + double n2; + if (t2 < 0.0) { + n2 = 0.0; + } else { + t2 *= t2; + n2 = t2 * t2 * dot(grad3[gi2], x2, y2); + } + + return 70.0 * (n0 + n1 + n2); + } + + public static double noise(double xin, double yin, double zin) { + double s = (xin + yin + zin) * 0.3333333333333333; + int i = fastfloor(xin + s); + int j = fastfloor(yin + s); + int k = fastfloor(zin + s); + double t = (i + j + k) * 0.16666666666666666; + double X0 = i - t; + double Y0 = j - t; + double Z0 = k - t; + double x0 = xin - X0; + double y0 = yin - Y0; + double z0 = zin - Z0; + int i1; + int j1; + int k1; + int i2; + int j2; + int k2; + if (x0 >= y0) { + if (y0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 1; + k2 = 0; + } else if (x0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 0; + k2 = 1; + } else { + i1 = 0; + j1 = 0; + k1 = 1; + i2 = 1; + j2 = 0; + k2 = 1; + } + } else if (y0 < z0) { + i1 = 0; + j1 = 0; + k1 = 1; + i2 = 0; + j2 = 1; + k2 = 1; + } else if (x0 < z0) { + i1 = 0; + j1 = 1; + k1 = 0; + i2 = 0; + j2 = 1; + k2 = 1; + } else { + i1 = 0; + j1 = 1; + k1 = 0; + i2 = 1; + j2 = 1; + k2 = 0; + } + + double x1 = x0 - i1 + 0.16666666666666666; + double y1 = y0 - j1 + 0.16666666666666666; + double z1 = z0 - k1 + 0.16666666666666666; + double x2 = x0 - i2 + 0.3333333333333333; + double y2 = y0 - j2 + 0.3333333333333333; + double z2 = z0 - k2 + 0.3333333333333333; + double x3 = x0 - 1.0 + 0.5; + double y3 = y0 - 1.0 + 0.5; + double z3 = z0 - 1.0 + 0.5; + int ii = i & 0xFF; + int jj = j & 0xFF; + int kk = k & 0xFF; + int gi0 = permMod12[ii + perm[jj + perm[kk]]]; + int gi1 = permMod12[ii + i1 + perm[jj + j1 + perm[kk + k1]]]; + int gi2 = permMod12[ii + i2 + perm[jj + j2 + perm[kk + k2]]]; + int gi3 = permMod12[ii + 1 + perm[jj + 1 + perm[kk + 1]]]; + double t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0; + double n0; + if (t0 < 0.0) { + n0 = 0.0; + } else { + t0 *= t0; + n0 = t0 * t0 * dot(grad3[gi0], x0, y0, z0); + } + + double t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1; + double n1; + if (t1 < 0.0) { + n1 = 0.0; + } else { + t1 *= t1; + n1 = t1 * t1 * dot(grad3[gi1], x1, y1, z1); + } + + double t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2; + double n2; + if (t2 < 0.0) { + n2 = 0.0; + } else { + t2 *= t2; + n2 = t2 * t2 * dot(grad3[gi2], x2, y2, z2); + } + + double t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3; + double n3; + if (t3 < 0.0) { + n3 = 0.0; + } else { + t3 *= t3; + n3 = t3 * t3 * dot(grad3[gi3], x3, y3, z3); + } + + return 32.0 * (n0 + n1 + n2 + n3); + } + + public static double noise(double x, double y, double z, double w) { + double s = (x + y + z + w) * F4; + int i = fastfloor(x + s); + int j = fastfloor(y + s); + int k = fastfloor(z + s); + int l = fastfloor(w + s); + double t = (i + j + k + l) * G4; + double X0 = i - t; + double Y0 = j - t; + double Z0 = k - t; + double W0 = l - t; + double x0 = x - X0; + double y0 = y - Y0; + double z0 = z - Z0; + double w0 = w - W0; + int rankx = 0; + int ranky = 0; + int rankz = 0; + int rankw = 0; + if (x0 > y0) { + rankx++; + } else { + ranky++; + } + + if (x0 > z0) { + rankx++; + } else { + rankz++; + } + + if (x0 > w0) { + rankx++; + } else { + rankw++; + } + + if (y0 > z0) { + ranky++; + } else { + rankz++; + } + + if (y0 > w0) { + ranky++; + } else { + rankw++; + } + + if (z0 > w0) { + rankz++; + } else { + rankw++; + } + + int i1 = rankx >= 3 ? 1 : 0; + int j1 = ranky >= 3 ? 1 : 0; + int k1 = rankz >= 3 ? 1 : 0; + int l1 = rankw >= 3 ? 1 : 0; + int i2 = rankx >= 2 ? 1 : 0; + int j2 = ranky >= 2 ? 1 : 0; + int k2 = rankz >= 2 ? 1 : 0; + int l2 = rankw >= 2 ? 1 : 0; + int i3 = rankx >= 1 ? 1 : 0; + int j3 = ranky >= 1 ? 1 : 0; + int k3 = rankz >= 1 ? 1 : 0; + int l3 = rankw >= 1 ? 1 : 0; + double x1 = x0 - i1 + G4; + double y1 = y0 - j1 + G4; + double z1 = z0 - k1 + G4; + double w1 = w0 - l1 + G4; + double x2 = x0 - i2 + 2.0 * G4; + double y2 = y0 - j2 + 2.0 * G4; + double z2 = z0 - k2 + 2.0 * G4; + double w2 = w0 - l2 + 2.0 * G4; + double x3 = x0 - i3 + 3.0 * G4; + double y3 = y0 - j3 + 3.0 * G4; + double z3 = z0 - k3 + 3.0 * G4; + double w3 = w0 - l3 + 3.0 * G4; + double x4 = x0 - 1.0 + 4.0 * G4; + double y4 = y0 - 1.0 + 4.0 * G4; + double z4 = z0 - 1.0 + 4.0 * G4; + double w4 = w0 - 1.0 + 4.0 * G4; + int ii = i & 0xFF; + int jj = j & 0xFF; + int kk = k & 0xFF; + int ll = l & 0xFF; + int gi0 = perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32; + int gi1 = perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32; + int gi2 = perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32; + int gi3 = perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32; + int gi4 = perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32; + double t0 = 0.5 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0; + double n0; + if (t0 < 0.0) { + n0 = 0.0; + } else { + t0 *= t0; + n0 = t0 * t0 * dot(grad4[gi0], x0, y0, z0, w0); + } + + double t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1; + double n1; + if (t1 < 0.0) { + n1 = 0.0; + } else { + t1 *= t1; + n1 = t1 * t1 * dot(grad4[gi1], x1, y1, z1, w1); + } + + double t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2; + double n2; + if (t2 < 0.0) { + n2 = 0.0; + } else { + t2 *= t2; + n2 = t2 * t2 * dot(grad4[gi2], x2, y2, z2, w2); + } + + double t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3; + double n3; + if (t3 < 0.0) { + n3 = 0.0; + } else { + t3 *= t3; + n3 = t3 * t3 * dot(grad4[gi3], x3, y3, z3, w3); + } + + double t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4; + double n4; + if (t4 < 0.0) { + n4 = 0.0; + } else { + t4 *= t4; + n4 = t4 * t4 * dot(grad4[gi4], x4, y4, z4, w4); + } + + return 27.0 * (n0 + n1 + n2 + n3 + n4); + } + + static { + for (int i = 0; i < 512; i++) { + perm[i] = p[i & 0xFF]; + permMod12[i] = (short)(perm[i] % 12); + } + } + + private static class Grad { + double x; + double y; + double z; + double w; + + Grad(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + Grad(double x, double y, double z, double w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/fields/noise/SimplexNoiseField.java b/src/com/hypixel/hytale/builtin/hytalegenerator/fields/noise/SimplexNoiseField.java new file mode 100644 index 0000000..15ed476 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/fields/noise/SimplexNoiseField.java @@ -0,0 +1,213 @@ +package com.hypixel.hytale.builtin.hytalegenerator.fields.noise; + +import java.util.Random; +import javax.annotation.Nonnull; + +public class SimplexNoiseField extends NoiseField { + private final long seed; + @Nonnull + private final double[] offsetX; + @Nonnull + private final double[] offsetY; + @Nonnull + private final double[] offsetZ; + @Nonnull + private final double[] offsetW; + private final int numberOfOctaves; + @Nonnull + private final double[] octaveFrequency; + @Nonnull + private final double[] octaveAmplitude; + private final double normalizer; + + public SimplexNoiseField(long seed, double octaveAmplitudeMultiplier, double octaveFrequencyMultiplier, int numberOfOctaves) { + if (numberOfOctaves <= 0) { + throw new IllegalArgumentException("octaves can't be smaller than 1"); + } else { + this.seed = seed; + this.numberOfOctaves = numberOfOctaves; + Random rand = new Random(seed); + this.offsetX = new double[numberOfOctaves]; + this.offsetY = new double[numberOfOctaves]; + this.offsetZ = new double[numberOfOctaves]; + this.offsetW = new double[numberOfOctaves]; + + for (int i = 0; i < numberOfOctaves; i++) { + this.offsetX[i] = rand.nextDouble() * 256.0; + this.offsetY[i] = rand.nextDouble() * 256.0; + this.offsetZ[i] = rand.nextDouble() * 256.0; + this.offsetW[i] = rand.nextDouble() * 256.0; + } + + this.octaveAmplitude = new double[numberOfOctaves]; + this.octaveFrequency = new double[numberOfOctaves]; + double frequency = 1.0; + double amplitude = 1.0; + double maxAmplitude = 0.0; + + for (int i = 0; i < numberOfOctaves; i++) { + this.octaveAmplitude[i] = amplitude; + this.octaveFrequency[i] = frequency; + maxAmplitude += amplitude; + amplitude *= octaveAmplitudeMultiplier; + frequency *= octaveFrequencyMultiplier; + } + + this.normalizer = 1.0 / maxAmplitude; + } + } + + @Nonnull + public static SimplexNoiseField.Builder builder() { + return new SimplexNoiseField.Builder(); + } + + @Override + public double valueAt(double x, double y, double z, double w) { + x /= this.scaleX; + y /= this.scaleY; + z /= this.scaleZ; + w /= this.scaleW; + double octaveX = 0.0; + double octaveY = 0.0; + double octaveZ = 0.0; + double octaveW = 0.0; + double value = 0.0; + + for (int i = 0; i < this.numberOfOctaves; i++) { + octaveX = x + this.offsetX[i]; + octaveY = y + this.offsetY[i]; + octaveZ = z + this.offsetZ[i]; + octaveW = w + this.offsetW[i]; + value += Simplex.noise( + octaveX * this.octaveFrequency[i], octaveY * this.octaveFrequency[i], octaveZ * this.octaveFrequency[i], octaveW * this.octaveFrequency[i] + ) + * this.octaveAmplitude[i]; + } + + return value * this.normalizer; + } + + @Override + public double valueAt(double x, double y, double z) { + x /= this.scaleX; + y /= this.scaleY; + z /= this.scaleZ; + double octaveX = 0.0; + double octaveY = 0.0; + double octaveZ = 0.0; + double value = 0.0; + + for (int i = 0; i < this.numberOfOctaves; i++) { + octaveX = x + this.offsetX[i]; + octaveY = y + this.offsetY[i]; + octaveZ = z + this.offsetZ[i]; + value += Simplex.noise(octaveX * this.octaveFrequency[i], octaveY * this.octaveFrequency[i], octaveZ * this.octaveFrequency[i]) + * this.octaveAmplitude[i]; + } + + return value * this.normalizer; + } + + @Override + public double valueAt(double x, double y) { + x /= this.scaleX; + y /= this.scaleY; + double octaveX = 0.0; + double octaveY = 0.0; + double value = 0.0; + + for (int i = 0; i < this.numberOfOctaves; i++) { + octaveX = x + this.offsetX[i]; + octaveY = y + this.offsetY[i]; + value += Simplex.noise(octaveX * this.octaveFrequency[i], octaveY * this.octaveFrequency[i]) * this.octaveAmplitude[i]; + } + + return value * this.normalizer; + } + + @Override + public double valueAt(double x) { + x /= this.scaleX; + double octaveX = 0.0; + double value = 0.0; + + for (int i = 0; i < this.numberOfOctaves; i++) { + octaveX = x + this.offsetX[i]; + value += Simplex.noise(octaveX * this.octaveFrequency[i], 0.0) * this.octaveAmplitude[i]; + } + + return value * this.normalizer; + } + + public long getSeed() { + return this.seed; + } + + public static class Builder { + private long seed = 1L; + private double octaveAmplitudeMultiplier = 0.5; + private double octaveFrequencyMultiplier = 2.0; + private int numberOfOctaves = 4; + private double scaleX; + private double scaleY; + private double scaleZ; + private double scaleW; + + private Builder() { + } + + @Nonnull + public SimplexNoiseField build() { + SimplexNoiseField g = new SimplexNoiseField(this.seed, this.octaveAmplitudeMultiplier, this.octaveFrequencyMultiplier, this.numberOfOctaves); + g.setScale(this.scaleX, this.scaleY, this.scaleZ, this.scaleW); + return g; + } + + @Nonnull + public SimplexNoiseField.Builder withScale(double s) { + this.scaleX = s; + this.scaleY = s; + this.scaleZ = s; + this.scaleW = s; + return this; + } + + @Nonnull + public SimplexNoiseField.Builder withScale(double x, double y, double z, double w) { + this.scaleX = x; + this.scaleY = y; + this.scaleZ = z; + this.scaleW = w; + return this; + } + + @Nonnull + public SimplexNoiseField.Builder withNumberOfOctaves(int n) { + if (n <= 0) { + throw new IllegalArgumentException("invalid number"); + } else { + this.numberOfOctaves = n; + return this; + } + } + + @Nonnull + public SimplexNoiseField.Builder withFrequencyMultiplier(double f) { + this.octaveFrequencyMultiplier = f; + return this; + } + + @Nonnull + public SimplexNoiseField.Builder withAmplitudeMultiplier(double a) { + this.octaveAmplitudeMultiplier = a; + return this; + } + + @Nonnull + public SimplexNoiseField.Builder withSeed(long s) { + this.seed = s; + return this; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/fields/points/JitterPointField.java b/src/com/hypixel/hytale/builtin/hytalegenerator/fields/points/JitterPointField.java new file mode 100644 index 0000000..f1d9a6b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/fields/points/JitterPointField.java @@ -0,0 +1,119 @@ +package com.hypixel.hytale.builtin.hytalegenerator.fields.points; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import com.hypixel.hytale.builtin.hytalegenerator.fields.FastNoiseLite; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +public class JitterPointField extends PointField { + @Nonnull + private final FastNoiseLite noise; + private final int seed; + private final double jitter; + @Nonnull + private final Vector3d scaleDown3d; + @Nonnull + private final Vector3d scaleUp3d; + @Nonnull + private final Vector2d scaleDown2d; + @Nonnull + private final Vector2d scaleUp2d; + + public JitterPointField(int seed, double jitter) { + this.jitter = jitter; + this.seed = seed; + this.noise = new FastNoiseLite(); + this.scaleDown3d = new Vector3d(1.0, 1.0, 1.0); + this.scaleUp3d = new Vector3d(1.0, 1.0, 1.0); + this.scaleDown2d = new Vector2d(1.0, 1.0); + this.scaleUp2d = new Vector2d(1.0, 1.0); + } + + @Override + public PointField setScale(double scaleX, double scaleY, double scaleZ, double scaleW) { + this.scaleDown3d.x = 1.0 / scaleX; + this.scaleDown3d.y = 1.0 / scaleY; + this.scaleDown3d.z = 1.0 / scaleZ; + this.scaleUp3d.x = scaleX; + this.scaleUp3d.y = scaleY; + this.scaleUp3d.z = scaleZ; + this.scaleDown2d.x = 1.0 / scaleX; + this.scaleDown2d.y = 1.0 / scaleZ; + this.scaleUp2d.x = scaleX; + this.scaleUp2d.y = scaleZ; + return super.setScale(scaleX, scaleY, scaleZ, scaleW); + } + + @Override + public void points3i(@Nonnull Vector3i min, @Nonnull Vector3i max, @Nonnull Consumer pointsOut) { + this.points3d(min.toVector3d(), max.toVector3d(), p -> pointsOut.accept(p.toVector3i())); + } + + @Override + public void points2i(@Nonnull Vector2i min, @Nonnull Vector2i max, @Nonnull Consumer pointsOut) { + this.points2d(new Vector2d(min.x, min.y), new Vector2d(max.x, max.y), p -> pointsOut.accept(new Vector2i((int)p.x, (int)p.y))); + } + + @Override + public void points1i(int min, int max, @Nonnull Consumer pointsOut) { + this.points1d(min, max, p -> pointsOut.accept(FastNoiseLite.fastRound(p))); + } + + @Override + public void points3d(@Nonnull Vector3d min, @Nonnull Vector3d max, @Nonnull Consumer pointsOut) { + Vector3d cellMin = min.clone().scale(this.scaleDown3d); + Vector3d cellMax = max.clone().scale(this.scaleDown3d); + cellMin.x = FastNoiseLite.fastRound(cellMin.x); + cellMin.y = FastNoiseLite.fastRound(cellMin.y); + cellMin.z = FastNoiseLite.fastRound(cellMin.z); + cellMax.x = FastNoiseLite.fastRound(cellMax.x); + cellMax.y = FastNoiseLite.fastRound(cellMax.y); + cellMax.z = FastNoiseLite.fastRound(cellMax.z); + + for (double x = cellMin.x; x <= cellMax.x + 0.25; x++) { + for (double y = cellMin.y; y <= cellMax.y + 0.25; y++) { + for (double z = cellMin.z; z <= cellMax.z + 0.25; z++) { + Vector3d point = this.noise.pointFor(this.seed, this.jitter, x, y, z); + point.scale(this.scaleUp3d); + if (VectorUtil.isInside(point, min, max)) { + pointsOut.accept(point); + } + } + } + } + } + + @Override + public void points2d(@Nonnull Vector2d min, @Nonnull Vector2d max, @Nonnull Consumer pointsOut) { + Vector2d cellMin = min.clone().scale(this.scaleDown2d); + Vector2d cellMax = max.clone().scale(this.scaleDown2d); + cellMin.x = FastNoiseLite.fastRound(cellMin.x); + cellMin.y = FastNoiseLite.fastRound(cellMin.y); + cellMax.x = FastNoiseLite.fastRound(cellMax.x); + cellMax.y = FastNoiseLite.fastRound(cellMax.y); + + for (double x = cellMin.x; x <= cellMax.x + 0.25; x++) { + for (double z = cellMin.y; z <= cellMax.y + 0.25; z++) { + Vector2d point = this.noise.pointFor(this.seed, this.jitter, x, z); + point.scale(this.scaleUp2d); + if (VectorUtil.isInside(point, min, max)) { + pointsOut.accept(point); + } + } + } + } + + @Override + public void points1d(double min, double max, @Nonnull Consumer pointsOut) { + for (double x = min - this.scaleX; x < max + this.scaleX; x += this.scaleX) { + double point = this.noise.pointFor(this.seed, this.jitter, x); + if (!(point < min) || point < max) { + pointsOut.accept(point); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/fields/points/PointField.java b/src/com/hypixel/hytale/builtin/hytalegenerator/fields/points/PointField.java new file mode 100644 index 0000000..bf320b7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/fields/points/PointField.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.builtin.hytalegenerator.fields.points; + +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public abstract class PointField implements PointProvider { + protected double scaleX = 1.0; + protected double scaleY = 1.0; + protected double scaleZ = 1.0; + protected double scaleW = 1.0; + + public PointField() { + } + + @Nonnull + @Override + public List points3i(@Nonnull Vector3i min, @Nonnull Vector3i max) { + ArrayList list = new ArrayList<>(); + this.points3i(min, max, list::add); + return list; + } + + @Nonnull + @Override + public List points2i(@Nonnull Vector2i min, @Nonnull Vector2i max) { + ArrayList list = new ArrayList<>(); + this.points2i(min, max, list::add); + return list; + } + + @Nonnull + @Override + public List points1i(int min, int max) { + ArrayList list = new ArrayList<>(); + this.points1i(min, max, list::add); + return list; + } + + @Nonnull + @Override + public List points3d(@Nonnull Vector3d min, @Nonnull Vector3d max) { + ArrayList list = new ArrayList<>(); + this.points3d(min, max, list::add); + return list; + } + + @Nonnull + @Override + public List points2d(@Nonnull Vector2d min, @Nonnull Vector2d max) { + ArrayList list = new ArrayList<>(); + this.points2d(min, max, list::add); + return list; + } + + @Nonnull + @Override + public List points1d(double min, double max) { + ArrayList list = new ArrayList<>(); + this.points1d(min, max, list::add); + return list; + } + + public PointField setScale(double scaleX, double scaleY, double scaleZ, double scaleW) { + this.scaleX = scaleX; + this.scaleY = scaleY; + this.scaleZ = scaleZ; + this.scaleW = scaleW; + return this; + } + + @Nonnull + public PointField setScale(double scale) { + this.setScale(scale, scale, scale, scale); + return this; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/fields/points/PointProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/fields/points/PointProvider.java new file mode 100644 index 0000000..0f4af45 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/fields/points/PointProvider.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.hytalegenerator.fields.points; + +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.List; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +public interface PointProvider { + List points3i(@Nonnull Vector3i var1, @Nonnull Vector3i var2); + + List points2i(@Nonnull Vector2i var1, @Nonnull Vector2i var2); + + List points1i(int var1, int var2); + + void points3i(@Nonnull Vector3i var1, @Nonnull Vector3i var2, @Nonnull Consumer var3); + + void points2i(@Nonnull Vector2i var1, @Nonnull Vector2i var2, @Nonnull Consumer var3); + + void points1i(int var1, int var2, @Nonnull Consumer var3); + + List points3d(@Nonnull Vector3d var1, @Nonnull Vector3d var2); + + List points2d(@Nonnull Vector2d var1, @Nonnull Vector2d var2); + + List points1d(double var1, double var3); + + void points3d(@Nonnull Vector3d var1, @Nonnull Vector3d var2, @Nonnull Consumer var3); + + void points2d(@Nonnull Vector2d var1, @Nonnull Vector2d var2, @Nonnull Consumer var3); + + void points1d(double var1, double var3, @Nonnull Consumer var5); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/cartas/ImageCarta.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/cartas/ImageCarta.java new file mode 100644 index 0000000..07d6479 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/cartas/ImageCarta.java @@ -0,0 +1,147 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.cartas; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.TriCarta; +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.TriDoubleFunction; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ImageCarta extends TriCarta { + private int[] rgbArray; + private int width; + private int height; + private TriDoubleFunction functionX; + private TriDoubleFunction functionY; + private Map rgbToTerrainMap; + private List allPossibleValues; + + private ImageCarta() { + } + + @Nullable + @Override + public R apply(int x, int y, int z, @Nonnull WorkerIndexer.Id tHreadId) { + Objects.requireNonNull(x); + Objects.requireNonNull(y); + Objects.requireNonNull(z); + int sampleX = Calculator.toNearestInt(this.functionX.apply(x, y, z) * this.width); + sampleX = sampleX < 0 ? 0 : Math.min(sampleX, this.width - 1); + int sampleY = Calculator.toNearestInt(this.functionY.apply(x, y, z) * this.height); + sampleY = sampleY < 0 ? 0 : Math.min(sampleY, this.height - 1); + int rgb = this.rgbArray[sampleX + sampleY * this.width]; + return !this.rgbToTerrainMap.containsKey(rgb) ? null : this.rgbToTerrainMap.get(rgb); + } + + @Override + public List allPossibleValues() { + return this.allPossibleValues; + } + + public static int greenFromRgb(int rgb) { + return (rgb & 0xFF00) >> 8; + } + + public static int redFromRgb(int rgb) { + return (rgb & 0xFF0000) >> 16; + } + + public static int blueFromRgb(int rgb) { + return rgb & 0xFF; + } + + public static int coloursToRgb(int red, int green, int blue) { + int rgb = red << 16; + rgb += green << 8; + return rgb + blue; + } + + @Nonnull + @Override + public String toString() { + return "ImageCarta{rgbArray=" + + Arrays.toString(this.rgbArray) + + ", width=" + + this.width + + ", height=" + + this.height + + ", functionX=" + + this.functionX + + ", functionY=" + + this.functionY + + ", rgbToTerrainMap=" + + this.rgbToTerrainMap + + ", allPossibleValues=" + + this.allPossibleValues + + "}"; + } + + public static class Builder { + @Nonnull + private final Map rgbToTerrainMap = new HashMap<>(); + private BufferedImage bufferedImage; + private boolean bufferedImageCheck; + private TriDoubleFunction noiseX; + private TriDoubleFunction noiseY; + private boolean noiseCheck; + + public Builder() { + } + + @Nonnull + public ImageCarta build() { + if (this.bufferedImageCheck && this.noiseCheck) { + ImageCarta instance = new ImageCarta<>(); + instance.rgbToTerrainMap = this.rgbToTerrainMap; + instance.functionX = this.noiseX; + instance.functionY = this.noiseY; + instance.allPossibleValues = new ArrayList<>(1); + instance.allPossibleValues.addAll(this.rgbToTerrainMap.values()); + instance.width = this.bufferedImage.getWidth(); + instance.height = this.bufferedImage.getHeight(); + instance.rgbArray = new int[instance.width * instance.height]; + + for (int x = 0; x < instance.width; x++) { + for (int y = 0; y < instance.height; y++) { + instance.rgbArray[x + y * instance.width] = 16777215 & this.bufferedImage.getRGB(x, y); + } + } + + return instance; + } else { + throw new IllegalStateException("incomplete builder"); + } + } + + @Nonnull + public ImageCarta.Builder withImage(BufferedImage image) { + Objects.requireNonNull(image); + this.bufferedImage = image; + this.bufferedImageCheck = true; + return this; + } + + @Nonnull + public ImageCarta.Builder withNoiseFunctions(TriDoubleFunction noiseX, TriDoubleFunction noiseY) { + Objects.requireNonNull(noiseX); + Objects.requireNonNull(noiseY); + this.noiseX = noiseX; + this.noiseY = noiseY; + this.noiseCheck = true; + return this; + } + + @Nonnull + public ImageCarta.Builder addTerrainRgb(int rgb, @Nonnull R terrain) { + this.rgbToTerrainMap.put(rgb, terrain); + return this; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/cartas/LayeredCarta.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/cartas/LayeredCarta.java new file mode 100644 index 0000000..191f72f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/cartas/LayeredCarta.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.cartas; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.TriCarta; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class LayeredCarta extends TriCarta { + @Nonnull + private final List> layers; + @Nonnull + private final List allValues; + @Nonnull + private final R defaultValue; + + public LayeredCarta(@Nonnull R defaultValue) { + Objects.requireNonNull(defaultValue); + this.layers = new ArrayList<>(1); + this.allValues = new ArrayList<>(1); + this.defaultValue = defaultValue; + this.allValues.add(defaultValue); + } + + @Override + public R apply(int x, int y, int z, @Nonnull WorkerIndexer.Id id) { + R result = this.defaultValue; + + for (TriCarta layer : this.layers) { + R value = layer.apply(x, y, z, id); + if (value != null) { + result = value; + } + } + + return result; + } + + @Nonnull + @Override + public List allPossibleValues() { + return Collections.unmodifiableList(this.allValues); + } + + @Nonnull + public LayeredCarta addLayer(@Nonnull TriCarta layer) { + Objects.requireNonNull(layer); + this.layers.add(layer); + this.allValues.addAll(layer.allPossibleValues()); + return this; + } + + @Nonnull + @Override + public String toString() { + return "LayeredCarta{layers=" + this.layers + ", allValues=" + this.allValues + ", defaultValue=" + this.defaultValue + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/cartas/SingleElementCarta.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/cartas/SingleElementCarta.java new file mode 100644 index 0000000..d735684 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/cartas/SingleElementCarta.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.cartas; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.BiCarta; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; + +public class SingleElementCarta extends BiCarta { + private R element; + + private SingleElementCarta() { + } + + @Nonnull + public static SingleElementCarta of(@Nonnull R element) { + SingleElementCarta c = new SingleElementCarta<>(); + c.element = element; + return c; + } + + @Override + public R apply(int x, int z, @Nonnull WorkerIndexer.Id id) { + return this.element; + } + + @Nonnull + @Override + public List allPossibleValues() { + return Collections.singletonList(this.element); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/BiCarta.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/BiCarta.java new file mode 100644 index 0000000..1086d87 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/BiCarta.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions; + +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import java.util.List; +import javax.annotation.Nonnull; + +public abstract class BiCarta { + public BiCarta() { + } + + public abstract R apply(int var1, int var2, @Nonnull WorkerIndexer.Id var3); + + public abstract List allPossibleValues(); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/BiDouble2DoubleFunction.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/BiDouble2DoubleFunction.java new file mode 100644 index 0000000..02c9aed --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/BiDouble2DoubleFunction.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions; + +@FunctionalInterface +public interface BiDouble2DoubleFunction { + double apply(double var1, double var3); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/QuadDoubleFunction.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/QuadDoubleFunction.java new file mode 100644 index 0000000..dbdd129 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/QuadDoubleFunction.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions; + +@FunctionalInterface +public interface QuadDoubleFunction { + R apply(double var1, double var3, double var5, double var7); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/QuintoDoubleFunction.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/QuintoDoubleFunction.java new file mode 100644 index 0000000..9e0fbbc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/QuintoDoubleFunction.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions; + +@FunctionalInterface +public interface QuintoDoubleFunction { + R apply(double var1, double var3, double var5, double var7, double var9); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/TriCarta.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/TriCarta.java new file mode 100644 index 0000000..7581980 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/TriCarta.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions; + +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class TriCarta { + public TriCarta() { + } + + @Nullable + public abstract R apply(int var1, int var2, int var3, @Nonnull WorkerIndexer.Id var4); + + public abstract List allPossibleValues(); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/TriDoubleFunction.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/TriDoubleFunction.java new file mode 100644 index 0000000..0f97a5b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/TriDoubleFunction.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions; + +@FunctionalInterface +public interface TriDoubleFunction { + R apply(double var1, double var3, double var5); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/TriFunction.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/TriFunction.java new file mode 100644 index 0000000..ae644d3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/interfaces/functions/TriFunction.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions; + +@FunctionalInterface +public interface TriFunction { + R apply(A var1, B var2, C var3); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/BitConverter.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/BitConverter.java new file mode 100644 index 0000000..677f8b9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/BitConverter.java @@ -0,0 +1,155 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.math; + +import javax.annotation.Nonnull; + +public class BitConverter { + public BitConverter() { + } + + public static void main(String[] args) { + System.out.println("LONG TEST:"); + + for (int i = -4; i < 10; i++) { + System.out.println(); + System.out.print("INPUT [" + i + "] -> BINARY -> ["); + boolean[] output = toBitArray((long)i); + + for (boolean bit : output) { + System.out.print(bit ? "1" : "0"); + } + + System.out.print("] -> DECIMAL -> [" + toLong(output) + "]"); + } + + System.out.println(); + System.out.println("INT TEST:"); + + for (int i = -4; i < 10; i++) { + System.out.println(); + System.out.print("INPUT [" + i + "] -> BINARY -> ["); + boolean[] output = toBitArray(i); + + for (boolean bit : output) { + System.out.print(bit ? "1" : "0"); + } + + System.out.print("] -> DECIMAL -> [" + toInt(output) + "]"); + } + + System.out.println(); + System.out.println("BYTE TEST:"); + + for (int i = -4; i < 10; i++) { + System.out.println(); + System.out.print("INPUT [" + i + "] -> BINARY -> ["); + boolean[] output = toBitArray((byte)i); + + for (boolean bit : output) { + System.out.print(bit ? "1" : "0"); + } + + System.out.print("] -> DECIMAL -> [" + toByte(output) + "]"); + } + + System.out.println(); + } + + public static boolean[] toBitArray(long number) { + byte PRECISION = 64; + boolean[] bits = new boolean[64]; + long position = 1L; + + for (byte i = 63; i >= 0; i--) { + bits[i] = (number & position) != 0L; + position <<= 1; + } + + return bits; + } + + public static boolean[] toBitArray(int number) { + byte PRECISION = 32; + boolean[] bits = new boolean[32]; + int position = 1; + + for (byte i = 31; i >= 0; i--) { + bits[i] = (number & position) != 0; + position <<= 1; + } + + return bits; + } + + public static boolean[] toBitArray(byte number) { + byte PRECISION = 8; + boolean[] bits = new boolean[8]; + byte position = 1; + + for (byte i = 7; i >= 0; i--) { + bits[i] = (number & position) != 0; + position = (byte)(position << 1); + } + + return bits; + } + + public static long toLong(@Nonnull boolean[] bits) { + byte PRECISION = 64; + if (bits.length != 64) { + throw new IllegalArgumentException("array must have length 64"); + } else { + long position = 1L; + long number = 0L; + + for (byte i = 63; i >= 0; i--) { + if (bits[i]) { + number += position; + } + + position <<= 1; + } + + return number; + } + } + + public static int toInt(@Nonnull boolean[] bits) { + byte PRECISION = 32; + if (bits.length != 32) { + throw new IllegalArgumentException("array must have length 32"); + } else { + int position = 1; + int number = 0; + + for (byte i = 31; i >= 0; i--) { + if (bits[i]) { + number += position; + } + + position <<= 1; + } + + return number; + } + } + + public static int toByte(@Nonnull boolean[] bits) { + byte PRECISION = 8; + if (bits.length != 8) { + throw new IllegalArgumentException("array must have length 8"); + } else { + byte position = 1; + byte number = 0; + + for (byte i = 7; i >= 0; i--) { + if (bits[i]) { + number += position; + } + + position = (byte)(position << 1); + } + + return number; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Calculator.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Calculator.java new file mode 100644 index 0000000..3351a68 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Calculator.java @@ -0,0 +1,224 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.math; + +import com.hypixel.hytale.math.vector.Vector3d; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class Calculator { + public Calculator() { + } + + public static int toIntFloored(double d) { + d = Math.floor(d); + return (int)d; + } + + public static boolean perfectDiv(int x, int divisor) { + return x % divisor == 0; + } + + public static double max(@Nonnull double... n) { + Objects.requireNonNull(n); + if (n.length <= 0) { + throw new IllegalArgumentException("array can't be empty"); + } else { + double max = Double.NEGATIVE_INFINITY; + + for (double value : n) { + if (max < value) { + max = value; + } + } + + return max; + } + } + + public static double min(@Nonnull double... n) { + Objects.requireNonNull(n); + if (n.length <= 0) { + throw new IllegalArgumentException("array can't be empty"); + } else { + double min = Double.POSITIVE_INFINITY; + + for (double value : n) { + if (min > value) { + min = value; + } + } + + return min; + } + } + + public static int max(@Nonnull int... n) { + Objects.requireNonNull(n); + if (n.length <= 0) { + throw new IllegalArgumentException("array can't be empty"); + } else { + int max = Integer.MIN_VALUE; + + for (int value : n) { + if (max < value) { + max = value; + } + } + + return max; + } + } + + public static int min(@Nonnull int... n) { + Objects.requireNonNull(n); + if (n.length <= 0) { + throw new IllegalArgumentException("array can't be empty"); + } else { + int min = Integer.MAX_VALUE; + + for (int value : n) { + if (min > value) { + min = value; + } + } + + return min; + } + } + + public static int limit(int value, int floor, int ceil) { + if (floor >= ceil) { + throw new IllegalArgumentException("floor must be smaller than ceil"); + } else { + return value < floor ? floor : Math.min(value, ceil); + } + } + + public static double limit(double value, double floor, double ceil) { + if (floor >= ceil) { + throw new IllegalArgumentException("floor must be smaller than ceil"); + } else if (value < floor) { + return floor; + } else { + return value > ceil ? ceil : value; + } + } + + public static double distance(double x1, double y1, double z1, double x2, double y2, double z2) { + return Math.sqrt(Math.pow(x2 - x1, 2.0) + Math.pow(y2 - y1, 2.0) + Math.pow(z2 - z1, 2.0)); + } + + public static double distance(@Nonnull Vector3d a, @Nonnull Vector3d b) { + return Math.sqrt(Math.pow(b.x - a.x, 2.0) + Math.pow(b.y - a.y, 2.0) + Math.pow(b.z - a.z, 2.0)); + } + + public static double distance(double x1, double y1, double x2, double y2) { + return Math.sqrt(Math.pow(x2 - x1, 2.0) + Math.pow(y2 - y1, 2.0)); + } + + public static boolean isDivisibleBy(int number, int divisor) { + if (number == 0) { + return false; + } else { + while (number != 1) { + if (number % 4 != 0) { + return false; + } + + number >>= 2; + } + + return true; + } + } + + public static double clamp(double wallA, double value, double wallB) { + double floor; + double ceil; + if (wallA > wallB) { + ceil = wallA; + floor = wallB; + } else { + if (!(wallA < wallB)) { + return wallA; + } + + ceil = wallB; + floor = wallA; + } + + if (value < floor) { + value = floor; + } else if (value > ceil || Double.isInfinite(value)) { + value = ceil; + } + + return value; + } + + public static int clamp(int wallA, int value, int wallB) { + int floor; + int ceil; + if (wallA > wallB) { + ceil = wallA; + floor = wallB; + } else { + if (wallA >= wallB) { + return wallA; + } + + ceil = wallB; + floor = wallA; + } + + if (value < floor) { + value = floor; + } else if (value > ceil) { + value = ceil; + } + + return value; + } + + public static int toNearestInt(double input) { + return (int)Math.round(input); + } + + public static double smoothMin(double range, double a, double b) { + if (range < 0.0) { + throw new IllegalArgumentException("negative range"); + } else if (range == 0.0) { + return Math.min(a, b); + } else if (Math.abs(a - b) >= range) { + return Math.min(a, b); + } else { + double weight = clamp(0.0, 0.5 + 0.5 * (b - a) / range, 1.0); + return Interpolation.linear(b, a, weight) - range * weight * (1.0 - weight); + } + } + + public static double smoothMax(double range, double a, double b) { + if (range < 0.0) { + throw new IllegalArgumentException("negative range"); + } else if (range == 0.0) { + return Math.max(a, b); + } else if (Math.abs(a - b) > range) { + return Math.max(a, b); + } else { + double weight = clamp(0.0, 0.5 + 0.5 * (b - a) / range, 1.0); + return Interpolation.linear(a, b, weight) + range * weight * (1.0 - weight); + } + } + + public static int wrap(int value, int max) { + value %= max; + return value < 0 ? value + max : value; + } + + public static int floor(int value, int gridSize) { + return value < 0 ? value / gridSize * gridSize - (value % gridSize != 0 ? gridSize : 0) : value / gridSize * gridSize; + } + + public static int ceil(int value, int gridSize) { + return value >= 0 ? (value + gridSize - 1) / gridSize * gridSize : value / gridSize * gridSize; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/CoPrimeGenerator.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/CoPrimeGenerator.java new file mode 100644 index 0000000..b3616f6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/CoPrimeGenerator.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.math; + +import java.util.Random; +import java.util.stream.IntStream; +import javax.annotation.Nonnull; + +public class CoPrimeGenerator { + public CoPrimeGenerator() { + } + + public static long[] generateCoPrimes(long seed, int bucketSize, int numberOfBuckets, long floor) { + if (bucketSize >= 1 && numberOfBuckets >= 1) { + Random rand = new Random(seed); + int[] primes = new int[bucketSize * numberOfBuckets]; + fillWithPrimes(primes); + int[][] buckets = new int[numberOfBuckets][bucketSize]; + long[] output = new long[numberOfBuckets]; + IntStream.range(0, output.length).forEach(ix -> output[ix] = 1L); + int indexOfBucket = 0; + int indexOfPrime = 0; + + for (int indexInsideBucket = 0; indexOfPrime < primes.length; indexOfPrime++) { + buckets[indexOfBucket][indexInsideBucket] = primes[indexOfPrime]; + if (indexOfBucket == numberOfBuckets - 1) { + indexInsideBucket++; + } + + indexOfBucket = (indexOfBucket + 1) % numberOfBuckets; + } + + for (int i = 0; i < numberOfBuckets; i++) { + while (output[i] < floor) { + output[i] *= buckets[i][rand.nextInt(bucketSize)]; + } + } + + return output; + } else { + throw new IllegalArgumentException("invalid sizes"); + } + } + + public static void fillWithPrimes(@Nonnull int[] bucket) { + int number = 2; + + for (int index = 0; index < bucket.length; number++) { + if (isPrime(number)) { + bucket[index] = number; + index++; + } + } + } + + public static boolean isPrime(int number) { + for (int i = 2; i < number; i++) { + if (number % i == 0) { + return false; + } + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Combiner.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Combiner.java new file mode 100644 index 0000000..02797f0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Combiner.java @@ -0,0 +1,130 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.math; + +import javax.annotation.Nonnull; + +public class Combiner { + private final double y; + private double value; + + public Combiner(double background, double y) { + this.value = background; + this.y = y; + } + + @Nonnull + public Combiner.Layer addLayer(double density) { + return new Combiner.Layer(this, density); + } + + public double getValue() { + return this.value; + } + + public static enum IntersectionPolicy { + MAX_POLICY, + MIN_POLICY; + + private IntersectionPolicy() { + } + } + + public class Layer { + @Nonnull + private final Combiner parent; + private double value; + private double floor; + private double ceiling; + private double paddingFloor; + private double paddingCeiling; + private Combiner.IntersectionPolicy intersectionPolicy; + private double intersectionSmoothingRange; + private boolean withLimitsCheck; + private boolean withPaddingCheck; + private boolean withIntersectionPolicyCheck; + private boolean isFinished = false; + + private Layer(@Nonnull Combiner combiner, double value) { + if (combiner == null) { + throw new NullPointerException(); + } else { + this.parent = combiner; + this.value = value; + } + } + + @Nonnull + public Combiner finishLayer() { + if (!this.withPaddingCheck || !this.withIntersectionPolicyCheck || !this.withLimitsCheck) { + throw new IllegalStateException("incomplete"); + } else if (this.isFinished) { + throw new IllegalStateException("method was already called"); + } else { + this.isFinished = true; + if (this.intersectionPolicy == Combiner.IntersectionPolicy.MAX_POLICY) { + this.ceiling = Calculator.smoothMax(this.intersectionSmoothingRange, this.floor, this.ceiling); + } else if (this.intersectionPolicy == Combiner.IntersectionPolicy.MIN_POLICY) { + this.floor = Calculator.smoothMin(this.intersectionSmoothingRange, this.floor, this.ceiling); + } else { + this.ceiling = this.floor; + } + + if (!(Combiner.this.y < this.floor) && !(Combiner.this.y >= this.ceiling)) { + double floorPaddingMultiplier; + if (this.paddingFloor == 0.0) { + floorPaddingMultiplier = 1.0; + } else { + floorPaddingMultiplier = (Combiner.this.y - this.floor) / this.paddingFloor; + floorPaddingMultiplier = Calculator.clamp(0.0, floorPaddingMultiplier, 1.0); + } + + double ceilingPaddingMultiplier; + if (this.paddingCeiling == 0.0) { + ceilingPaddingMultiplier = 1.0; + } else { + ceilingPaddingMultiplier = (this.ceiling - Combiner.this.y) / this.paddingCeiling; + ceilingPaddingMultiplier = Calculator.clamp(0.0, ceilingPaddingMultiplier, 1.0); + } + + double paddingMultiplier = Calculator.smoothMin(0.2, floorPaddingMultiplier, ceilingPaddingMultiplier); + this.value *= paddingMultiplier; + this.parent.value = this.parent.value + this.value; + return this.parent; + } else { + return this.parent; + } + } + } + + @Nonnull + public Combiner.Layer withLimits(double floor, double ceiling) { + this.withLimitsCheck = true; + this.floor = floor; + this.ceiling = ceiling; + return this; + } + + @Nonnull + public Combiner.Layer withPadding(double paddingFloor, double paddingCeiling) { + if (!(paddingFloor < 0.0) && !(paddingCeiling < 0.0)) { + this.withPaddingCheck = true; + this.paddingFloor = paddingFloor; + this.paddingCeiling = paddingCeiling; + return this; + } else { + throw new IllegalArgumentException("negative padding values"); + } + } + + @Nonnull + public Combiner.Layer withIntersectionPolicy(@Nonnull Combiner.IntersectionPolicy policy, double smoothRange) { + if (policy == null) { + throw new NullPointerException(); + } else { + this.withIntersectionPolicyCheck = true; + this.intersectionPolicy = policy; + this.intersectionSmoothingRange = smoothRange; + return this; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/InterpolatedCurve.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/InterpolatedCurve.java new file mode 100644 index 0000000..a6e74c4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/InterpolatedCurve.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.math; + +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import javax.annotation.Nonnull; + +public class InterpolatedCurve implements Double2DoubleFunction { + @Nonnull + private final Double2DoubleFunction functionA; + @Nonnull + private final Double2DoubleFunction functionB; + private final double positionA; + private final double positionB; + private final double distance; + private final double smoothTransition; + + public InterpolatedCurve( + double positionA, double positionB, double smoothTransition, @Nonnull Double2DoubleFunction functionA, @Nonnull Double2DoubleFunction functionB + ) { + if (!(smoothTransition < 0.0) && !(smoothTransition > 1.0)) { + this.smoothTransition = smoothTransition; + this.positionA = Math.min(positionA, positionB); + this.positionB = Math.max(positionA, positionB); + this.distance = positionB - positionA; + this.functionA = functionA; + this.functionB = functionB; + } else { + throw new IllegalArgumentException(); + } + } + + @Override + public double get(double x) { + if (x < this.positionA) { + return this.functionA.get(x); + } else if (x > this.positionB) { + return this.functionB.get(x); + } else if (this.distance == 0.0) { + return (this.functionA.get(x) + this.functionB.get(x)) * 0.5; + } else { + double bRatio = this.transitionCurve((x - this.positionA) / this.distance); + double aRatio = 1.0 - bRatio; + return aRatio * this.functionA.get(x) + bRatio * this.functionB.get(x); + } + } + + public double transitionCurve(double ratio) { + double a = ratio * Math.PI; + double v = Math.cos(a); + v++; + v /= 2.0; + v = 1.0 - v; + return v * this.smoothTransition + ratio * (1.0 - this.smoothTransition); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Interpolation.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Interpolation.java new file mode 100644 index 0000000..1321a41 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Interpolation.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.math; + +public class Interpolation { + public Interpolation() { + } + + public static double linear(double valueA, double valueB, double weight) { + if (!(weight < 0.0) && !(weight > 1.0)) { + return valueA * (1.0 - weight) + valueB * weight; + } else { + throw new IllegalArgumentException("weight outside range"); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/MultipliedIteration.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/MultipliedIteration.java new file mode 100644 index 0000000..91c56f1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/MultipliedIteration.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.math; + +public class MultipliedIteration { + public MultipliedIteration() { + } + + public static double calculateMultiplier(double startValue, double endValue, int numberOfIterations, double precision) { + if (startValue < endValue) { + throw new IllegalArgumentException("start smaller than end"); + } else if (numberOfIterations <= 0) { + throw new IllegalArgumentException("number of iterations must be greater than 0"); + } else if (precision <= 0.0) { + throw new IllegalArgumentException("precision must be greater than 0"); + } else { + double candidate = 0.0; + + for (int result = 0; candidate < 1.0; candidate += precision) { + result = calculateIterations(candidate, startValue, endValue); + if (result >= numberOfIterations) { + break; + } + } + + return Math.min(candidate, 0.99999); + } + } + + public static int calculateIterations(double multiplier, double startValue, double endValue) { + double currentSize = startValue; + + int iterations; + for (iterations = 0; currentSize > endValue; iterations++) { + currentSize *= multiplier; + } + + return iterations; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/NodeFunction.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/NodeFunction.java new file mode 100644 index 0000000..e1532fd --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/NodeFunction.java @@ -0,0 +1,86 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.math; + +import com.hypixel.hytale.builtin.hytalegenerator.ArrayUtil; +import com.hypixel.hytale.builtin.hytalegenerator.delimiters.RangeDouble; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public class NodeFunction implements Function, Double2DoubleFunction { + private static final double FALLBACK_VALUE = 0.0; + @Nonnull + private final List points = new ArrayList<>(2); + @Nonnull + private final List ranges = new ArrayList<>(2); + + public NodeFunction() { + } + + public Double apply(@Nonnull Double input) { + return this.get(input); + } + + @Override + public double get(double input) { + if (Double.isNaN(input)) { + return 0.0; + } else if (this.points.isEmpty()) { + return 0.0; + } else if (this.points.size() == 1 || input <= this.points.getFirst()[0]) { + return this.points.getFirst()[1]; + } else if (input >= this.points.getLast()[0]) { + return this.points.getLast()[1]; + } else { + int indexBefore = this.indexBefore(input); + double[] before = this.points.get(indexBefore); + double[] after = this.points.get(indexBefore + 1); + double differenceY = after[1] - before[1]; + double ratio = (input - before[0]) / (after[0] - before[0]); + return before[1] + differenceY * ratio; + } + } + + @Nonnull + public NodeFunction addPoint(double in, double out) { + for (double[] point : this.points) { + if (point[0] == in) { + return this; + } + } + + this.points.add(new double[]{in, out}); + this.points.sort((a, b) -> { + if (a[0] < b[0]) { + return -1; + } else { + return a[0] == b[0] ? 0 : 1; + } + }); + this.initializeRanges(); + return this; + } + + public boolean contains(double x) { + return this.points.parallelStream().anyMatch(point -> point[0] == x); + } + + private void initializeRanges() { + this.ranges.clear(); + + for (int i = 0; i < this.points.size() - 1; i++) { + this.ranges.add(new RangeDouble(this.points.get(i)[0], this.points.get(i + 1)[0])); + } + } + + private int indexBefore(double input) { + return ArrayUtil.sortedSearch(this.ranges, input, (gauge, range) -> { + if (gauge < range.min()) { + return -1; + } else { + return gauge >= range.max() ? 1 : 0; + } + }); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Normalizer.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Normalizer.java new file mode 100644 index 0000000..0e9a466 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Normalizer.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.math; + +public class Normalizer { + public Normalizer() { + } + + public static double normalizeNoise(double input) { + return normalize(-1.0, 1.0, 0.0, 1.0, input); + } + + public static double normalize(double fromMin, double fromMax, double toMin, double toMax, double input) { + if (!(fromMin > fromMax) && !(toMin > toMax)) { + input -= fromMin; + input /= fromMax - fromMin; + input *= toMax - toMin; + return input + toMin; + } else { + throw new IllegalArgumentException("min larger than max"); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Probability.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Probability.java new file mode 100644 index 0000000..18fab11 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Probability.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.math; + +import java.util.Random; + +public class Probability { + public Probability() { + } + + public static boolean of(double chance, long seed) { + Random rand = new Random(seed); + return rand.nextDouble() < chance; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/RegionGrid.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/RegionGrid.java new file mode 100644 index 0000000..e33b560 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/RegionGrid.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.math; + +public class RegionGrid { + private int regionSizeX; + private int regionSizeZ; + + public RegionGrid(int regionSizeX, int regionSizeZ) { + this.regionSizeX = regionSizeX; + this.regionSizeZ = regionSizeZ; + } + + public int regionMinX(int chunkX) { + return chunkX >= 0 ? chunkX / this.regionSizeX * this.regionSizeX : (chunkX - (this.regionSizeZ - 1)) / this.regionSizeX * this.regionSizeX; + } + + public int regionMinZ(int chunkZ) { + return chunkZ >= 0 ? chunkZ / this.regionSizeZ * this.regionSizeZ : (chunkZ - (this.regionSizeX - 1)) / this.regionSizeZ * this.regionSizeZ; + } + + public int regionMaxX(int chunkX) { + return this.regionMinX(chunkX) + this.regionSizeX; + } + + public int regionMaxZ(int chunkZ) { + return this.regionMinZ(chunkZ) + this.regionSizeZ; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/SeedGenerator.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/SeedGenerator.java new file mode 100644 index 0000000..cc91da5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/SeedGenerator.java @@ -0,0 +1,82 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.math; + +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class SeedGenerator { + @Nonnull + private final long[] coPrimes; + private static final long FLOOR = 10000000L; + + public SeedGenerator(long seed) { + this.coPrimes = CoPrimeGenerator.generateCoPrimes(seed, 100, 7, 10000000L); + } + + public long seedAt(long x, long y, long z, long w, long k, long t) { + return (x * this.coPrimes[0] + y * this.coPrimes[1] + z * this.coPrimes[2] + w * this.coPrimes[3] + k * this.coPrimes[4] + t * this.coPrimes[5]) + % this.coPrimes[6]; + } + + public long seedAt(long x, long y, long z, long w, long k) { + return (x * this.coPrimes[0] + y * this.coPrimes[1] + z * this.coPrimes[2] + w * this.coPrimes[3] + k * this.coPrimes[4]) % this.coPrimes[6]; + } + + public long seedAt(long x, long y, long z, long w) { + return (x * this.coPrimes[0] + y * this.coPrimes[1] + z * this.coPrimes[2] + w * this.coPrimes[3]) % this.coPrimes[6]; + } + + public long seedAt(long x, long y, long z) { + return (x * this.coPrimes[0] + y * this.coPrimes[1] + z * this.coPrimes[2]) % this.coPrimes[6]; + } + + public long seedAt(long x, long y) { + return (x * this.coPrimes[0] + y * this.coPrimes[1]) % this.coPrimes[6]; + } + + public long seedAt(double xd, double yd, double zd, double wd, double kd, double td, double resolution) { + int x = (int)(xd * resolution); + int y = (int)(yd * resolution); + int z = (int)(zd * resolution); + int w = (int)(wd * resolution); + int k = (int)(kd * resolution); + int t = (int)(td * resolution); + return (x * this.coPrimes[0] + y * this.coPrimes[1] + z * this.coPrimes[2] + w * this.coPrimes[3] + k * this.coPrimes[4] + t * this.coPrimes[5]) + % this.coPrimes[6]; + } + + public long seedAt(double xd, double yd, double zd, double wd, double kd, double resolution) { + int x = (int)(xd * resolution); + int y = (int)(yd * resolution); + int z = (int)(zd * resolution); + int w = (int)(wd * resolution); + int k = (int)(kd * resolution); + return (x * this.coPrimes[0] + y * this.coPrimes[1] + z * this.coPrimes[2] + w * this.coPrimes[3] + k * this.coPrimes[4]) % this.coPrimes[6]; + } + + public long seedAt(double xd, double yd, double zd, double wd, double resolution) { + int x = (int)(xd * resolution); + int y = (int)(yd * resolution); + int z = (int)(zd * resolution); + int w = (int)(wd * resolution); + return (x * this.coPrimes[0] + y * this.coPrimes[1] + z * this.coPrimes[2] + w * this.coPrimes[3]) % this.coPrimes[6]; + } + + public long seedAt(double xd, double yd, double zd, double resolution) { + int x = (int)(xd * resolution); + int y = (int)(yd * resolution); + int z = (int)(zd * resolution); + return (x * this.coPrimes[0] + y * this.coPrimes[1] + z * this.coPrimes[2]) % this.coPrimes[6]; + } + + public long seedAt(double xd, double yd, double resolution) { + int x = (int)(xd * resolution); + int y = (int)(yd * resolution); + return (x * this.coPrimes[0] + y * this.coPrimes[1]) % this.coPrimes[6]; + } + + @Nonnull + @Override + public String toString() { + return "SeedGenerator{coPrimes=" + Arrays.toString(this.coPrimes) + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Splitter.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Splitter.java new file mode 100644 index 0000000..05d7f07 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Splitter.java @@ -0,0 +1,139 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.math; + +import javax.annotation.Nonnull; + +public class Splitter { + public Splitter() { + } + + @Nonnull + public static Splitter.Range[] split(@Nonnull Splitter.Range range, int pieces) { + if (pieces < 0) { + throw new IllegalArgumentException("negative number of pieces"); + } else { + int size = range.max - range.min; + int pieceSize = size / pieces; + if (size % pieces > 0) { + pieceSize++; + } + + Splitter.Range[] output = new Splitter.Range[pieces]; + + for (int i = 0; i < output.length; i++) { + int min = Math.min(i * pieceSize + range.min, range.max); + int max = Math.min(min + pieceSize, range.max); + output[i] = new Splitter.Range(min, max); + } + + return output; + } + } + + @Nonnull + public static Splitter.Area[] split(@Nonnull Splitter.Area area, int pieces) { + if (pieces < 1) { + throw new IllegalArgumentException("negative number of pieces"); + } else if (pieces == 1) { + return new Splitter.Area[]{area}; + } else { + int sizeX = area.maxX - area.minX; + int sizeZ = area.maxZ - area.minZ; + if (pieces > sizeX) { + pieces = sizeX; + } + + Splitter.Area[] output = new Splitter.Area[pieces]; + if (pieces % 3 == 0) { + Splitter.Range[] rangesX = split(new Splitter.Range(area.minX, area.maxX), 3); + Splitter.Range[] rangesZ = split(new Splitter.Range(area.minZ, area.maxZ), pieces / 3); + int o = 0; + + for (Splitter.Range x : rangesX) { + for (Splitter.Range range : rangesZ) { + output[o++] = new Splitter.Area(x.min, range.min, x.max, range.max); + } + } + } else if (pieces % 2 == 0) { + Splitter.Range[] rangesX = split(new Splitter.Range(area.minX, area.maxX), 2); + Splitter.Range[] rangesZ = split(new Splitter.Range(area.minZ, area.maxZ), pieces / 2); + int o = 0; + + for (Splitter.Range x : rangesX) { + for (Splitter.Range range : rangesZ) { + output[o++] = new Splitter.Area(x.min, range.min, x.max, range.max); + } + } + } else { + Splitter.Range[] ranges = split(new Splitter.Range(area.minX, area.maxX), pieces); + + for (int i = 0; i < ranges.length; i++) { + output[i] = new Splitter.Area(ranges[i].min, area.minZ, ranges[i].max, area.maxZ); + } + } + + return output; + } + } + + @Nonnull + public static Splitter.Area[] splitX(@Nonnull Splitter.Area area, int pieces) { + if (pieces < 1) { + throw new IllegalArgumentException("negative number of pieces"); + } else if (pieces == 1) { + return new Splitter.Area[]{area}; + } else { + int sizeX = area.maxX - area.minX; + int sizeZ = area.maxZ - area.minZ; + if (pieces > sizeX) { + pieces = sizeX; + } + + Splitter.Area[] output = new Splitter.Area[pieces]; + Splitter.Range[] ranges = split(new Splitter.Range(area.minX, area.maxX), pieces); + + for (int i = 0; i < ranges.length; i++) { + output[i] = new Splitter.Area(ranges[i].min, area.minZ, ranges[i].max, area.maxZ); + } + + return output; + } + } + + public static class Area { + public final int minX; + public final int minZ; + public final int maxX; + public final int maxZ; + + public Area(int minX, int minZ, int maxX, int maxZ) { + if (maxX >= minX && maxZ >= minZ) { + this.minX = minX; + this.minZ = minZ; + this.maxX = maxX; + this.maxZ = maxZ; + } else { + throw new IllegalArgumentException("max smaller than min"); + } + } + + @Nonnull + @Override + public String toString() { + return "Area{minX=" + this.minX + ", minZ=" + this.minZ + ", maxX=" + this.maxX + ", maxZ=" + this.maxZ + "}"; + } + } + + public static class Range { + public final int min; + public final int max; + + public Range(int min, int max) { + if (max < min) { + throw new IllegalArgumentException("max smaller than min"); + } else { + this.min = min; + this.max = max; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Stepinizer.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Stepinizer.java new file mode 100644 index 0000000..06612d0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/math/Stepinizer.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.math; + +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public class Stepinizer implements Function, Double2DoubleFunction { + private double stepSize; + private double stepSizeHalf; + private double slope; + private double topSmooth; + private double bottomSmooth; + + public Stepinizer() { + this.setStep(1.0); + this.setEdgeSlope(1.0); + this.setSmooth(1.0, 1.0); + } + + @Nonnull + public Stepinizer setSmooth(double top, double bottom) { + if (!(top <= 0.0) && !(bottom <= 0.0)) { + this.topSmooth = top; + this.bottomSmooth = bottom; + return this; + } else { + throw new IllegalArgumentException("invalid values provided"); + } + } + + @Nonnull + public Stepinizer setEdgeSlope(double slope) { + if (slope < 0.0) { + throw new IllegalArgumentException("negative slope"); + } else { + this.slope = slope; + return this; + } + } + + @Nonnull + public Stepinizer setStep(double size) { + if (size < 0.0) { + throw new IllegalArgumentException("negative size"); + } else { + this.stepSize = size; + this.stepSizeHalf = size / 2.0; + return this; + } + } + + public double apply(double x) { + return this.get(x); + } + + @Override + public double get(double x) { + double polarity = this.polarity(x); + double steepness = this.steepness(polarity); + double bottomStep = this.bottomStep(x); + double topStep = this.topStep(x); + double result; + if (polarity < 0.0) { + result = Calculator.smoothMax(this.bottomSmooth, steepness, -1.0); + } else { + result = Calculator.smoothMin(this.topSmooth, steepness, 1.0); + } + + return Normalizer.normalize(-1.0, 1.0, bottomStep, topStep, result); + } + + private double closestStep(double x) { + double remainder = x % this.stepSize; + return remainder < this.stepSizeHalf ? x - remainder : x - remainder + this.stepSize; + } + + private double topStep(double x) { + return x - x % this.stepSize + this.stepSize; + } + + private double bottomStep(double x) { + return x - x % this.stepSize; + } + + private double polarity(double x) { + double midPoint = this.bottomStep(x) + this.stepSizeHalf; + return (x - midPoint) / this.stepSizeHalf; + } + + private double steepness(double x) { + return this.slope * x; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/shaders/MaskShader.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/shaders/MaskShader.java new file mode 100644 index 0000000..27fb8cf --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/shaders/MaskShader.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.shaders; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.SeedGenerator; +import java.util.function.Predicate; +import javax.annotation.Nonnull; + +public class MaskShader implements Shader { + private final Shader childShader; + private final Predicate mask; + private SeedGenerator seedGenerator; + + private MaskShader(Predicate mask, Shader childShader, long seed) { + this.mask = mask; + this.childShader = childShader; + this.seedGenerator = new SeedGenerator(seed); + } + + @Nonnull + public static MaskShader.Builder builder(@Nonnull Class dataType) { + return new MaskShader.Builder<>(); + } + + @Override + public T shade(T current, long seed) { + return !this.mask.test(current) ? current : this.childShader.shade(current, seed); + } + + @Override + public T shade(T current, long seedA, long seedB) { + return this.shade(current, 0L); + } + + @Override + public T shade(T current, long seedA, long seedB, long seedC) { + return this.shade(current, 0L); + } + + @Nonnull + @Override + public String toString() { + return "MaskShader{childShader=" + this.childShader + ", mask=" + this.mask + ", seedGenerator=" + this.seedGenerator + "}"; + } + + public static class Builder { + private Shader childShader; + private Predicate mask; + private long seed = System.nanoTime(); + + private Builder() { + } + + @Nonnull + public MaskShader build() { + if (this.childShader != null && this.mask != null) { + return new MaskShader<>(this.mask, this.childShader, this.seed); + } else { + throw new IllegalStateException("incomplete builder"); + } + } + + @Nonnull + public MaskShader.Builder withSeed(long seed) { + this.seed = seed; + return this; + } + + @Nonnull + public MaskShader.Builder withMask(@Nonnull Predicate mask) { + this.mask = mask; + return this; + } + + @Nonnull + public MaskShader.Builder withChildShader(@Nonnull Shader shader) { + this.childShader = shader; + return this; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/shaders/RelationalShader.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/shaders/RelationalShader.java new file mode 100644 index 0000000..aa91b7f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/shaders/RelationalShader.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.shaders; + +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class RelationalShader implements Shader { + @Nonnull + private final Map> relations; + @Nonnull + private final Shader onMissingKey; + + public RelationalShader(@Nonnull Shader onMissingKey) { + this.onMissingKey = onMissingKey; + this.relations = new HashMap<>(1); + } + + @Nonnull + public RelationalShader addRelation(@Nonnull T key, @Nonnull Shader value) { + this.relations.put(key, value); + return this; + } + + @Override + public T shade(T current, long seed) { + return !this.relations.containsKey(current) ? this.onMissingKey.shade(current, seed) : this.relations.get(current).shade(current, seed); + } + + @Override + public T shade(T current, long seedA, long seedB) { + return !this.relations.containsKey(current) ? this.onMissingKey.shade(current, seedA, seedB) : this.relations.get(current).shade(current, seedA, seedB); + } + + @Override + public T shade(T current, long seedA, long seedB, long seedC) { + return !this.relations.containsKey(current) + ? this.onMissingKey.shade(current, seedA, seedB, seedC) + : this.relations.get(current).shade(current, seedA, seedB, seedC); + } + + @Nonnull + @Override + public String toString() { + return "RelationalShader{relations=" + this.relations + ", onMissingKey=" + this.onMissingKey + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/shaders/Shader.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/shaders/Shader.java new file mode 100644 index 0000000..aca7ab5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/shaders/Shader.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.shaders; + +public interface Shader { + T shade(T var1, long var2); + + T shade(T var1, long var2, long var4); + + T shade(T var1, long var2, long var4, long var6); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/shaders/SimpleShader.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/shaders/SimpleShader.java new file mode 100644 index 0000000..aaa1d26 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/shaders/SimpleShader.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.shaders; + +import javax.annotation.Nonnull; + +public class SimpleShader implements Shader { + @Nonnull + private final T value; + + private SimpleShader(@Nonnull T value) { + this.value = value; + } + + @Nonnull + public static SimpleShader of(@Nonnull T value) { + return new SimpleShader<>(value); + } + + @Nonnull + @Override + public T shade(T current, long seed) { + return this.value; + } + + @Nonnull + @Override + public T shade(T current, long seedA, long seedB) { + return this.value; + } + + @Nonnull + @Override + public T shade(T current, long seedA, long seedB, long seedC) { + return this.value; + } + + @Nonnull + @Override + public String toString() { + return "SimpleShader{value=" + this.value + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/framework/shaders/WeighedShader.java b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/shaders/WeighedShader.java new file mode 100644 index 0000000..cdfd190 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/framework/shaders/WeighedShader.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.hytalegenerator.framework.shaders; + +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.WeightedMap; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.SeedGenerator; +import java.util.Random; +import javax.annotation.Nonnull; + +public class WeighedShader implements Shader { + @Nonnull + private final WeightedMap> childrenWeightedMap = new WeightedMap<>(1); + private SeedGenerator seedGenerator = new SeedGenerator(System.nanoTime()); + + public WeighedShader(@Nonnull Shader initialChild, double weight) { + this.add(initialChild, weight); + } + + @Nonnull + public WeighedShader add(@Nonnull Shader child, double weight) { + if (weight <= 0.0) { + throw new IllegalArgumentException("invalid weight"); + } else { + this.childrenWeightedMap.add(child, weight); + return this; + } + } + + @Nonnull + public WeighedShader setSeed(long seed) { + this.seedGenerator = new SeedGenerator(seed); + return this; + } + + @Override + public T shade(T current, long seed) { + Random r = new Random(seed); + return this.childrenWeightedMap.pick(r).shade(current, seed); + } + + @Override + public T shade(T current, long seedA, long seedB) { + return this.shade(current, this.seedGenerator.seedAt(seedA, seedB)); + } + + @Override + public T shade(T current, long seedA, long seedB, long seedC) { + return this.shade(current, this.seedGenerator.seedAt(seedA, seedB, seedC)); + } + + @Nonnull + @Override + public String toString() { + return "WeighedShader{childrenWeighedMap=" + this.childrenWeightedMap + ", seedGenerator=" + this.seedGenerator + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/iterators/BackwardIntIterator.java b/src/com/hypixel/hytale/builtin/hytalegenerator/iterators/BackwardIntIterator.java new file mode 100644 index 0000000..b03c0a2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/iterators/BackwardIntIterator.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.hytalegenerator.iterators; + +import it.unimi.dsi.fastutil.ints.IntIterator; +import java.util.Iterator; +import javax.annotation.Nonnull; + +public class BackwardIntIterator implements IntIterator, Iterator { + private int min; + private int current; + + public BackwardIntIterator(int min, int maxExclusive) { + if (min > maxExclusive) { + throw new IllegalArgumentException("Start greater than end."); + } else { + this.min = min; + this.current = maxExclusive; + } + } + + private BackwardIntIterator() { + } + + @Override + public boolean hasNext() { + return this.current > this.min; + } + + @Override + public int nextInt() { + return --this.current; + } + + @Nonnull + @Override + public Integer next() { + return --this.current; + } + + @Nonnull + public Integer getCurrent() { + return this.current; + } + + @Nonnull + public BackwardIntIterator clone() { + BackwardIntIterator clone = new BackwardIntIterator(); + clone.current = this.current; + clone.min = this.min; + return clone; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/iterators/ForwardIntIterator.java b/src/com/hypixel/hytale/builtin/hytalegenerator/iterators/ForwardIntIterator.java new file mode 100644 index 0000000..c27efe5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/iterators/ForwardIntIterator.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.hytalegenerator.iterators; + +import it.unimi.dsi.fastutil.ints.IntIterator; +import java.util.Iterator; +import javax.annotation.Nonnull; + +public class ForwardIntIterator implements IntIterator, Iterator { + private int max; + private int current; + + public ForwardIntIterator(int min, int maxExclusive) { + if (min > maxExclusive) { + throw new IllegalArgumentException("Start greater than end."); + } else { + this.max = maxExclusive - 1; + this.current = min - 1; + } + } + + private ForwardIntIterator() { + } + + @Override + public boolean hasNext() { + return this.current < this.max; + } + + @Override + public int nextInt() { + return ++this.current; + } + + @Nonnull + @Override + public Integer next() { + return ++this.current; + } + + @Nonnull + public Integer getCurrent() { + return this.current; + } + + @Nonnull + public ForwardIntIterator clone() { + ForwardIntIterator clone = new ForwardIntIterator(); + clone.current = this.current; + clone.max = this.max; + return clone; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/iterators/IntIterators.java b/src/com/hypixel/hytale/builtin/hytalegenerator/iterators/IntIterators.java new file mode 100644 index 0000000..7c6b372 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/iterators/IntIterators.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.builtin.hytalegenerator.iterators; + +import it.unimi.dsi.fastutil.ints.IntIterator; +import javax.annotation.Nonnull; + +public class IntIterators { + public IntIterators() { + } + + @Nonnull + public static IntIterator range(int start, int end) { + return (IntIterator)(start <= end ? new ForwardIntIterator(start, end) : new BackwardIntIterator(end, start)); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/material/FluidMaterial.java b/src/com/hypixel/hytale/builtin/hytalegenerator/material/FluidMaterial.java new file mode 100644 index 0000000..bf10113 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/material/FluidMaterial.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.hytalegenerator.material; + +import java.util.Objects; +import javax.annotation.Nonnull; + +public class FluidMaterial { + private final MaterialCache materialCache; + public final int fluidId; + public final byte fluidLevel; + + FluidMaterial(@Nonnull MaterialCache materialCache, int fluidId, byte fluidLevel) { + this.materialCache = materialCache; + this.fluidId = fluidId; + this.fluidLevel = fluidLevel; + } + + public MaterialCache getVoxelCache() { + return this.materialCache; + } + + @Override + public final boolean equals(Object o) { + return !(o instanceof FluidMaterial that) + ? false + : this.fluidId == that.fluidId && this.fluidLevel == that.fluidLevel && this.materialCache.equals(that.materialCache); + } + + @Override + public int hashCode() { + return contentHash(this.fluidId, this.fluidLevel); + } + + public static int contentHash(int blockId, byte fluidLevel) { + return Objects.hash(blockId, fluidLevel); + } + + @Override + public String toString() { + return "FluidMaterial{materialCache=" + this.materialCache + ", fluidId=" + this.fluidId + ", fluidLevel=" + this.fluidLevel + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/material/Material.java b/src/com/hypixel/hytale/builtin/hytalegenerator/material/Material.java new file mode 100644 index 0000000..57bda72 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/material/Material.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.builtin.hytalegenerator.material; + +import java.util.Objects; +import javax.annotation.Nonnull; + +public final class Material { + @Nonnull + private final SolidMaterial solid; + @Nonnull + private final FluidMaterial fluid; + private Material.Hash hashCode; + private Material.Hash materialIdsHash; + + public Material(@Nonnull SolidMaterial solid, @Nonnull FluidMaterial fluid) { + this.solid = solid; + this.fluid = fluid; + this.hashCode = new Material.Hash(); + this.materialIdsHash = new Material.Hash(); + } + + @Override + public boolean equals(Object o) { + return !(o instanceof Material material) ? false : Objects.equals(this.solid, material.solid) && Objects.equals(this.fluid, material.fluid); + } + + @Override + public int hashCode() { + if (this.hashCode.isCalculated) { + return this.hashCode.value; + } else { + this.hashCode.value = hashCode(this.solid, this.fluid); + this.hashCode.isCalculated = true; + return this.hashCode.value; + } + } + + public int hashMaterialIds() { + if (this.materialIdsHash.isCalculated) { + return this.materialIdsHash.value; + } else { + this.materialIdsHash.value = hashMaterialIds(this.solid, this.fluid); + this.materialIdsHash.isCalculated = true; + return this.materialIdsHash.value; + } + } + + public static int hashCode(@Nonnull SolidMaterial solid, @Nonnull FluidMaterial fluid) { + int result = solid.hashCode(); + return 31 * result + fluid.hashCode(); + } + + public static int hashMaterialIds(@Nonnull SolidMaterial solid, @Nonnull FluidMaterial fluid) { + return Objects.hash(solid.blockId, fluid.fluidId); + } + + public SolidMaterial solid() { + return this.solid; + } + + public FluidMaterial fluid() { + return this.fluid; + } + + @Override + public String toString() { + return "Material[solid=" + this.solid + ", fluid=" + this.fluid + "]"; + } + + private class Hash { + int value = 0; + boolean isCalculated = false; + + private Hash() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/material/MaterialCache.java b/src/com/hypixel/hytale/builtin/hytalegenerator/material/MaterialCache.java new file mode 100644 index 0000000..a7b291c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/material/MaterialCache.java @@ -0,0 +1,140 @@ +package com.hypixel.hytale.builtin.hytalegenerator.material; + +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MaterialCache { + private final Map hashToSolidMap = new HashMap<>(); + private final Map hashToFluidMap = new HashMap<>(); + private final Map hashToMaterialMap = new HashMap<>(); + public final SolidMaterial EMPTY_AIR = this.getSolidMaterial("Empty_Air"); + public final SolidMaterial ROCK_STONE = this.getSolidMaterial("Rock_Stone"); + public final SolidMaterial SOIL_GRASS = this.getSolidMaterial("Soil_Grass"); + public final SolidMaterial SOIL_DIRT = this.getSolidMaterial("Soil_Dirt"); + public final SolidMaterial SOIL_MUD = this.getSolidMaterial("Soil_Mud"); + public final SolidMaterial SOIL_NEEDLES = this.getSolidMaterial("Soil_Needles"); + public final SolidMaterial SOIL_GRAVEL = this.getSolidMaterial("Soil_Gravel"); + public final SolidMaterial ROCK_QUARTZITE = this.getSolidMaterial("Rock_Quartzite"); + public final SolidMaterial ROCK_MARBLE = this.getSolidMaterial("Rock_Marble"); + public final SolidMaterial ROCK_SHALE = this.getSolidMaterial("Rock_Shale"); + public final SolidMaterial FLUID_WATER = this.getSolidMaterial("Fluid_Water"); + public final SolidMaterial BEDROCK = this.getSolidMaterial("Rock_Volcanic"); + public final FluidMaterial UNKNOWN_FLUID = this.getFluidMaterial(Fluid.UNKNOWN.getId()); + public final FluidMaterial EMPTY_FLUID = this.getFluidMaterial(Fluid.EMPTY.getId()); + public final Material EMPTY = this.getMaterial(this.EMPTY_AIR, this.EMPTY_FLUID); + + public MaterialCache() { + } + + @Nonnull + public Material getMaterial(@Nonnull SolidMaterial solidMaterial, @Nonnull FluidMaterial fluidMaterial) { + int hash = Material.hashCode(solidMaterial, fluidMaterial); + Material material = this.hashToMaterialMap.get(hash); + if (material == null) { + material = new Material(solidMaterial, fluidMaterial); + this.hashToMaterialMap.put(hash, material); + return material; + } else { + return material; + } + } + + public FluidMaterial getFluidMaterial(@Nonnull String fluidString) { + int fluidId = 0; + Fluid key = Fluid.getAssetMap().getAsset(fluidString); + if (key != null) { + fluidId = Fluid.getAssetMap().getIndex(fluidString); + byte level = fluidId == 0 ? 0 : (byte)key.getMaxFluidLevel(); + return this.getOrRegisterFluid(fluidId, level); + } else { + LoggerUtil.getLogger().warning("Attempted to register an invalid Fluid " + fluidString + ", using Unknown instead."); + return this.UNKNOWN_FLUID; + } + } + + public FluidMaterial getFluidMaterial(int fluidId, byte level) { + Fluid key = Fluid.getAssetMap().getAsset(fluidId); + if (key == null) { + LoggerUtil.getLogger().warning("Attempted to register an invalid Fluid " + fluidId + ", using Unknown instead."); + return this.UNKNOWN_FLUID; + } else { + return this.getOrRegisterFluid(fluidId, level); + } + } + + private FluidMaterial getOrRegisterFluid(int fluidId, byte level) { + int hash = FluidMaterial.contentHash(fluidId, level); + FluidMaterial fluidMaterial = this.hashToFluidMap.get(hash); + if (fluidMaterial != null) { + return fluidMaterial; + } else { + fluidMaterial = new FluidMaterial(this, fluidId, level); + this.hashToFluidMap.put(hash, fluidMaterial); + return fluidMaterial; + } + } + + public SolidMaterial getSolidMaterial(@Nonnull String solidString) { + int blockId = 0; + BlockType key = BlockType.fromString(solidString); + if (key != null) { + blockId = BlockType.getAssetMap().getIndex(key.getId()); + } + + if (BlockType.getAssetMap().getAsset(blockId) == null) { + System.out.println("Attempted to register an invalid block ID " + blockId + ": using Empty_Air instead."); + return this.EMPTY_AIR; + } else { + int hash = SolidMaterial.contentHash(blockId, 0, 0, 0, null); + SolidMaterial solidMaterial = this.hashToSolidMap.get(hash); + if (solidMaterial != null) { + return solidMaterial; + } else { + solidMaterial = new SolidMaterial(this, blockId, 0, 0, 0, null); + this.hashToSolidMap.put(blockId, solidMaterial); + return solidMaterial; + } + } + } + + public SolidMaterial getSolidMaterialRotatedY(@Nonnull SolidMaterial solidMaterial, Rotation rotation) { + PrefabRotation prefabRotation = PrefabRotation.fromRotation(rotation); + int rotatedRotation = prefabRotation.getRotation(solidMaterial.rotation); + int rotatedFiller = prefabRotation.getFiller(solidMaterial.filler); + int hash = SolidMaterial.contentHash(solidMaterial.blockId, solidMaterial.support, rotatedRotation, rotatedFiller, solidMaterial.holder); + SolidMaterial rotatedSolidMaterial = this.hashToSolidMap.get(hash); + if (rotatedSolidMaterial != null) { + return rotatedSolidMaterial; + } else { + rotatedSolidMaterial = new SolidMaterial(this, solidMaterial.blockId, solidMaterial.support, rotatedRotation, rotatedFiller, solidMaterial.holder); + this.hashToSolidMap.put(hash, rotatedSolidMaterial); + return rotatedSolidMaterial; + } + } + + public SolidMaterial getSolidMaterial(int blockId, int support, int rotation, int filler, @Nullable Holder holder) { + if (BlockType.getAssetMap().getAsset(blockId) == null) { + System.out.println("Attempted to register an invalid block ID " + blockId + ": using Empty_Air instead."); + return this.EMPTY_AIR; + } else { + int hash = SolidMaterial.contentHash(blockId, support, rotation, filler, holder); + SolidMaterial solidMaterial = this.hashToSolidMap.get(hash); + if (solidMaterial != null) { + return solidMaterial; + } else { + solidMaterial = new SolidMaterial(this, blockId, support, rotation, filler, holder); + this.hashToSolidMap.put(hash, solidMaterial); + return solidMaterial; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/material/SolidMaterial.java b/src/com/hypixel/hytale/builtin/hytalegenerator/material/SolidMaterial.java new file mode 100644 index 0000000..e8c15bc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/material/SolidMaterial.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.builtin.hytalegenerator.material; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SolidMaterial { + private final MaterialCache materialCache; + public final int blockId; + public final int support; + public final int rotation; + public final int filler; + @Nullable + public final Holder holder; + + SolidMaterial(@Nonnull MaterialCache materialCache, int blockId, int support, int rotation, int filler, @Nullable Holder holder) { + this.materialCache = materialCache; + this.blockId = blockId; + this.support = support; + this.rotation = rotation; + this.filler = filler; + this.holder = holder; + } + + @Override + public boolean equals(Object o) { + return !(o instanceof SolidMaterial that) + ? false + : this.blockId == that.blockId + && this.support == that.support + && this.rotation == that.rotation + && this.filler == that.filler + && Objects.equals(this.materialCache, that.materialCache) + && Objects.equals(this.holder, that.holder); + } + + @Override + public int hashCode() { + return contentHash(this.blockId, this.support, this.rotation, this.filler, this.holder); + } + + public static int contentHash(int blockId, int support, int rotation, int filler, @Nullable Holder holder) { + return Objects.hash(blockId, support, rotation, filler, holder); + } + + @Override + public String toString() { + return "SolidMaterial{materialCache=" + + this.materialCache + + ", blockId=" + + this.blockId + + ", support=" + + this.support + + ", rotation=" + + this.rotation + + ", filler=" + + this.filler + + ", holder=" + + this.holder + + "}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/AllStoneMaterialProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/AllStoneMaterialProvider.java new file mode 100644 index 0000000..44eaf2d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/AllStoneMaterialProvider.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.material.SolidMaterial; +import javax.annotation.Nonnull; + +public class AllStoneMaterialProvider extends MaterialProvider { + private final MaterialCache materialCache; + + public AllStoneMaterialProvider(@Nonnull MaterialCache materialCache) { + this.materialCache = materialCache; + } + + public SolidMaterial getVoxelTypeAt(@Nonnull MaterialProvider.Context context) { + return context.depthIntoFloor > 0 ? this.materialCache.ROCK_STONE : this.materialCache.EMPTY_AIR; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/ConstantMaterialProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/ConstantMaterialProvider.java new file mode 100644 index 0000000..671afa0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/ConstantMaterialProvider.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ConstantMaterialProvider extends MaterialProvider { + @Nullable + private final V material; + + public ConstantMaterialProvider(@Nullable V material) { + this.material = material; + } + + @Nullable + @Override + public V getVoxelTypeAt(@Nonnull MaterialProvider.Context context) { + return this.material; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/DownwardDepthMaterialProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/DownwardDepthMaterialProvider.java new file mode 100644 index 0000000..318f52d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/DownwardDepthMaterialProvider.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DownwardDepthMaterialProvider extends MaterialProvider { + @Nonnull + private final MaterialProvider materialProvider; + private final int depth; + + public DownwardDepthMaterialProvider(@Nonnull MaterialProvider materialProvider, int depth) { + this.materialProvider = materialProvider; + this.depth = depth; + } + + @Nullable + @Override + public V getVoxelTypeAt(@Nonnull MaterialProvider.Context context) { + return this.depth != context.depthIntoFloor ? null : this.materialProvider.getVoxelTypeAt(context); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/DownwardSpaceMaterialProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/DownwardSpaceMaterialProvider.java new file mode 100644 index 0000000..40aa69c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/DownwardSpaceMaterialProvider.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DownwardSpaceMaterialProvider extends MaterialProvider { + @Nonnull + private final MaterialProvider materialProvider; + private final int space; + + public DownwardSpaceMaterialProvider(@Nonnull MaterialProvider materialProvider, int space) { + this.materialProvider = materialProvider; + this.space = space; + } + + @Nullable + @Override + public V getVoxelTypeAt(@Nonnull MaterialProvider.Context context) { + return this.space != context.spaceBelowCeiling ? null : this.materialProvider.getVoxelTypeAt(context); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/FieldFunctionMaterialProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/FieldFunctionMaterialProvider.java new file mode 100644 index 0000000..234b470 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/FieldFunctionMaterialProvider.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FieldFunctionMaterialProvider extends MaterialProvider { + @Nonnull + private final Density density; + @Nonnull + private final FieldFunctionMaterialProvider.FieldDelimiter[] fieldDelimiters; + + public FieldFunctionMaterialProvider(@Nonnull Density density, @Nonnull List> delimiters) { + this.density = density; + this.fieldDelimiters = new FieldFunctionMaterialProvider.FieldDelimiter[delimiters.size()]; + + for (FieldFunctionMaterialProvider.FieldDelimiter field : delimiters) { + if (field == null) { + throw new IllegalArgumentException("delimiters contain null value"); + } + } + + for (int i = 0; i < delimiters.size(); i++) { + this.fieldDelimiters[i] = delimiters.get(i); + } + } + + @Nullable + @Override + public V getVoxelTypeAt(@Nonnull MaterialProvider.Context context) { + Density.Context childContext = new Density.Context(context); + double densityValue = this.density.process(childContext); + + for (FieldFunctionMaterialProvider.FieldDelimiter delimiter : this.fieldDelimiters) { + if (delimiter.isInside(densityValue)) { + return delimiter.materialProvider.getVoxelTypeAt(context); + } + } + + return null; + } + + public static class FieldDelimiter { + double top; + double bottom; + MaterialProvider materialProvider; + + public FieldDelimiter(@Nonnull MaterialProvider materialProvider, double bottom, double top) { + this.bottom = bottom; + this.top = top; + this.materialProvider = materialProvider; + } + + boolean isInside(double fieldValue) { + return fieldValue < this.top && fieldValue >= this.bottom; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/GrassTopMaterialProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/GrassTopMaterialProvider.java new file mode 100644 index 0000000..315f9c7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/GrassTopMaterialProvider.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.material.SolidMaterial; +import javax.annotation.Nonnull; + +public class GrassTopMaterialProvider extends MaterialProvider { + private final SolidMaterial grass; + private final SolidMaterial dirt; + private final SolidMaterial stone; + private final SolidMaterial empty; + + public GrassTopMaterialProvider(@Nonnull SolidMaterial grass, @Nonnull SolidMaterial dirt, @Nonnull SolidMaterial stone, @Nonnull SolidMaterial empty) { + this.grass = grass; + this.dirt = dirt; + this.stone = stone; + this.empty = empty; + } + + public SolidMaterial getVoxelTypeAt(@Nonnull MaterialProvider.Context context) { + if (context.depthIntoFloor == 1) { + return this.grass; + } else if (context.depthIntoFloor > 1 && context.depthIntoFloor <= 3) { + return this.dirt; + } else { + return context.depthIntoFloor > 3 ? this.stone : this.empty; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/HorizontalMaterialProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/HorizontalMaterialProvider.java new file mode 100644 index 0000000..7f3ef81 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/HorizontalMaterialProvider.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.functions.DoubleFunctionXZ; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HorizontalMaterialProvider extends MaterialProvider { + @Nonnull + private final MaterialProvider materialProvider; + @Nonnull + private final DoubleFunctionXZ topY; + @Nonnull + private final DoubleFunctionXZ bottomY; + + public HorizontalMaterialProvider(@Nonnull MaterialProvider materialProvider, @Nonnull DoubleFunctionXZ topY, @Nonnull DoubleFunctionXZ bottomY) { + this.materialProvider = materialProvider; + this.topY = topY; + this.bottomY = bottomY; + } + + @Nullable + @Override + public V getVoxelTypeAt(@Nonnull MaterialProvider.Context context) { + double topY = this.topY.apply(context.position.x, context.position.z); + double bottomY = this.bottomY.apply(context.position.x, context.position.z); + return !(context.position.y >= topY) && !(context.position.y < bottomY) ? this.materialProvider.getVoxelTypeAt(context) : null; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/MaterialProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/MaterialProvider.java new file mode 100644 index 0000000..bb63402 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/MaterialProvider.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.TerrainDensityProvider; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class MaterialProvider { + public MaterialProvider() { + } + + @Nullable + public abstract V getVoxelTypeAt(@Nonnull MaterialProvider.Context var1); + + @Nonnull + public static MaterialProvider noMaterialProvider() { + return new ConstantMaterialProvider<>(null); + } + + public static class Context { + @Nonnull + public Vector3i position; + public double density; + public int depthIntoFloor; + public int depthIntoCeiling; + public int spaceAboveFloor; + public int spaceBelowCeiling; + @Nonnull + public WorkerIndexer.Id workerId; + @Nullable + public TerrainDensityProvider terrainDensityProvider; + public double distanceToBiomeEdge; + + public Context( + @Nonnull Vector3i position, + double density, + int depthIntoFloor, + int depthIntoCeiling, + int spaceAboveFloor, + int spaceBelowCeiling, + @Nonnull WorkerIndexer.Id workerId, + @Nullable TerrainDensityProvider terrainDensityProvider, + double distanceToBiomeEdge + ) { + this.position = position; + this.density = density; + this.depthIntoFloor = depthIntoFloor; + this.depthIntoCeiling = depthIntoCeiling; + this.spaceAboveFloor = spaceAboveFloor; + this.spaceBelowCeiling = spaceBelowCeiling; + this.workerId = workerId; + this.terrainDensityProvider = terrainDensityProvider; + this.distanceToBiomeEdge = distanceToBiomeEdge; + } + + public Context(@Nonnull MaterialProvider.Context other) { + this.position = other.position; + this.density = other.density; + this.depthIntoFloor = other.depthIntoFloor; + this.depthIntoCeiling = other.depthIntoCeiling; + this.spaceAboveFloor = other.spaceAboveFloor; + this.spaceBelowCeiling = other.spaceBelowCeiling; + this.workerId = other.workerId; + this.terrainDensityProvider = other.terrainDensityProvider; + this.distanceToBiomeEdge = other.distanceToBiomeEdge; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/QueueMaterialProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/QueueMaterialProvider.java new file mode 100644 index 0000000..6a99c95 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/QueueMaterialProvider.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders; + +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class QueueMaterialProvider extends MaterialProvider { + @Nonnull + private final MaterialProvider[] queue; + + public QueueMaterialProvider(@Nonnull List> queue) { + this.queue = new MaterialProvider[queue.size()]; + + for (int i = 0; i < queue.size(); i++) { + MaterialProvider l = queue.get(i); + if (l == null) { + throw new IllegalArgumentException("null element in layers"); + } + + this.queue[i] = l; + } + } + + @Nullable + @Override + public V getVoxelTypeAt(@Nonnull MaterialProvider.Context context) { + for (MaterialProvider layer : this.queue) { + V material = layer.getVoxelTypeAt(context); + if (material != null) { + return material; + } + } + + return null; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/SolidityMaterialProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/SolidityMaterialProvider.java new file mode 100644 index 0000000..ddc536e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/SolidityMaterialProvider.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders; + +import javax.annotation.Nonnull; + +public class SolidityMaterialProvider extends MaterialProvider { + @Nonnull + private final MaterialProvider solidMaterialProvider; + @Nonnull + private final MaterialProvider emptyMaterialProvider; + + public SolidityMaterialProvider(@Nonnull MaterialProvider solidMaterialProvider, @Nonnull MaterialProvider emptyMaterialProvider) { + this.solidMaterialProvider = solidMaterialProvider; + this.emptyMaterialProvider = emptyMaterialProvider; + } + + @Override + public V getVoxelTypeAt(@Nonnull MaterialProvider.Context context) { + return context.depthIntoFloor <= 0 ? this.emptyMaterialProvider.getVoxelTypeAt(context) : this.solidMaterialProvider.getVoxelTypeAt(context); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/StripedMaterialProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/StripedMaterialProvider.java new file mode 100644 index 0000000..b1fa75f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/StripedMaterialProvider.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders; + +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StripedMaterialProvider extends MaterialProvider { + @Nonnull + private final MaterialProvider materialProvider; + @Nonnull + private final StripedMaterialProvider.Stripe[] stripes; + + public StripedMaterialProvider(@Nonnull MaterialProvider materialProvider, @Nonnull List stripes) { + this.materialProvider = materialProvider; + this.stripes = new StripedMaterialProvider.Stripe[stripes.size()]; + + for (int i = 0; i < stripes.size(); i++) { + StripedMaterialProvider.Stripe s = stripes.get(i); + this.stripes[i] = s; + } + } + + @Nullable + @Override + public V getVoxelTypeAt(@Nonnull MaterialProvider.Context context) { + for (StripedMaterialProvider.Stripe s : this.stripes) { + if (s.contains(context.position.y)) { + return this.materialProvider.getVoxelTypeAt(context); + } + } + + return null; + } + + public static class Stripe { + private final int topY; + private final int bottomY; + + public Stripe(int topY, int bottomY) { + this.topY = topY; + this.bottomY = bottomY; + } + + public boolean contains(int y) { + return y >= this.bottomY && y <= this.topY; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/TerrainDensityMaterialProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/TerrainDensityMaterialProvider.java new file mode 100644 index 0000000..46eec5a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/TerrainDensityMaterialProvider.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders; + +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TerrainDensityMaterialProvider extends MaterialProvider { + @Nonnull + private final TerrainDensityMaterialProvider.FieldDelimiter[] fieldDelimiters; + + public TerrainDensityMaterialProvider(@Nonnull List> delimiters) { + this.fieldDelimiters = new TerrainDensityMaterialProvider.FieldDelimiter[delimiters.size()]; + + for (TerrainDensityMaterialProvider.FieldDelimiter field : delimiters) { + if (field == null) { + throw new IllegalArgumentException("delimiters contain null value"); + } + } + + for (int i = 0; i < delimiters.size(); i++) { + this.fieldDelimiters[i] = delimiters.get(i); + } + } + + @Nullable + @Override + public V getVoxelTypeAt(@Nonnull MaterialProvider.Context context) { + for (TerrainDensityMaterialProvider.FieldDelimiter delimiter : this.fieldDelimiters) { + if (delimiter.isInside(context.density)) { + return delimiter.materialProvider.getVoxelTypeAt(context); + } + } + + return null; + } + + public static class FieldDelimiter { + double top; + double bottom; + MaterialProvider materialProvider; + + public FieldDelimiter(@Nonnull MaterialProvider materialProvider, double bottom, double top) { + this.bottom = bottom; + this.top = top; + this.materialProvider = materialProvider; + } + + boolean isInside(double fieldValue) { + return fieldValue < this.top && fieldValue >= this.bottom; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/UpwardDepthMaterialProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/UpwardDepthMaterialProvider.java new file mode 100644 index 0000000..5bdf538 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/UpwardDepthMaterialProvider.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpwardDepthMaterialProvider extends MaterialProvider { + @Nonnull + private final MaterialProvider materialProvider; + private final int depth; + + public UpwardDepthMaterialProvider(@Nonnull MaterialProvider materialProvider, int depth) { + this.materialProvider = materialProvider; + this.depth = depth; + } + + @Nullable + @Override + public V getVoxelTypeAt(@Nonnull MaterialProvider.Context context) { + return this.depth != context.depthIntoCeiling ? null : this.materialProvider.getVoxelTypeAt(context); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/UpwardSpaceMaterialProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/UpwardSpaceMaterialProvider.java new file mode 100644 index 0000000..daefdb9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/UpwardSpaceMaterialProvider.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpwardSpaceMaterialProvider extends MaterialProvider { + @Nonnull + private final MaterialProvider materialProvider; + private final int space; + + public UpwardSpaceMaterialProvider(@Nonnull MaterialProvider materialProvider, int space) { + this.materialProvider = materialProvider; + this.space = space; + } + + @Nullable + @Override + public V getVoxelTypeAt(@Nonnull MaterialProvider.Context context) { + return this.space != context.spaceAboveFloor ? null : this.materialProvider.getVoxelTypeAt(context); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/WeightedMaterialProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/WeightedMaterialProvider.java new file mode 100644 index 0000000..2205cdf --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/WeightedMaterialProvider.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.WeightedMap; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.SeedGenerator; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.math.util.FastRandom; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WeightedMaterialProvider extends MaterialProvider { + @Nonnull + private final WeightedMap> weightedMap; + @Nonnull + private final SeedGenerator seedGenerator; + private final double noneProbability; + + public WeightedMaterialProvider(@Nonnull WeightedMap> weightedMap, @Nonnull SeedBox seedBox, double noneProbability) { + this.weightedMap = weightedMap; + this.seedGenerator = new SeedGenerator(seedBox.createSupplier().get().intValue()); + this.noneProbability = noneProbability; + } + + @Nullable + @Override + public V getVoxelTypeAt(@Nonnull MaterialProvider.Context context) { + long seed = this.seedGenerator.seedAt((long)context.position.x, (long)context.position.y, (long)context.position.z); + FastRandom random = new FastRandom(seed); + if (this.weightedMap.size() != 0 && !(random.nextDouble() < this.noneProbability)) { + MaterialProvider pick = this.weightedMap.pick(random); + return pick.getVoxelTypeAt(context); + } else { + return null; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/functions/DoubleFunctionXZ.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/functions/DoubleFunctionXZ.java new file mode 100644 index 0000000..fb962c3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/functions/DoubleFunctionXZ.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders.functions; + +@FunctionalInterface +public interface DoubleFunctionXZ { + double apply(double var1, double var3); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/SpaceAndDepthMaterialProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/SpaceAndDepthMaterialProvider.java new file mode 100644 index 0000000..4c66a24 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/SpaceAndDepthMaterialProvider.java @@ -0,0 +1,115 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth; + +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SpaceAndDepthMaterialProvider extends MaterialProvider { + @Nonnull + private final SpaceAndDepthMaterialProvider.LayerContextType layerContextType; + @Nonnull + private final SpaceAndDepthMaterialProvider.Layer[] layers; + @Nonnull + private final SpaceAndDepthMaterialProvider.Condition condition; + private final int maxDistance; + + public SpaceAndDepthMaterialProvider( + @Nonnull SpaceAndDepthMaterialProvider.LayerContextType layerContextType, + @Nonnull List> layers, + @Nonnull SpaceAndDepthMaterialProvider.Condition condition, + int maxDistance + ) { + this.layerContextType = layerContextType; + this.maxDistance = maxDistance; + this.layers = new SpaceAndDepthMaterialProvider.Layer[layers.size()]; + + for (int i = 0; i < layers.size(); i++) { + SpaceAndDepthMaterialProvider.Layer l = layers.get(i); + if (l == null) { + LoggerUtil.getLogger().warning("Couldn't retrieve layer with index " + i); + } else { + this.layers[i] = l; + } + } + + this.condition = condition; + } + + @Nullable + @Override + public V getVoxelTypeAt(@Nonnull MaterialProvider.Context context) { + int distance = switch (this.layerContextType) { + case DEPTH_INTO_FLOOR -> context.depthIntoFloor; + case DEPTH_INTO_CEILING -> context.depthIntoCeiling; + }; + if (distance > this.maxDistance) { + return null; + } else if (!this.condition + .qualifies( + context.position.x, + context.position.y, + context.position.z, + context.depthIntoFloor, + context.depthIntoCeiling, + context.spaceAboveFloor, + context.spaceBelowCeiling + )) { + return null; + } else { + MaterialProvider material = null; + int depthAccumulator = 0; + + for (SpaceAndDepthMaterialProvider.Layer l : this.layers) { + int layerDepth = l.getThicknessAt( + context.position.x, + context.position.y, + context.position.z, + context.depthIntoFloor, + context.depthIntoCeiling, + context.spaceAboveFloor, + context.spaceBelowCeiling, + context.distanceToBiomeEdge + ); + int nextDepthAccumulator = depthAccumulator + layerDepth; + if (distance > depthAccumulator && distance <= nextDepthAccumulator) { + material = l.getMaterialProvider(); + break; + } + + depthAccumulator = nextDepthAccumulator; + } + + return material == null ? null : material.getVoxelTypeAt(context); + } + } + + public interface Condition { + boolean qualifies(int var1, int var2, int var3, int var4, int var5, int var6, int var7); + } + + public abstract static class Layer { + public Layer() { + } + + public abstract int getThicknessAt(int var1, int var2, int var3, int var4, int var5, int var6, int var7, double var8); + + @Nullable + public abstract MaterialProvider getMaterialProvider(); + } + + public static enum LayerContextType { + DEPTH_INTO_FLOOR, + DEPTH_INTO_CEILING; + + public static final Codec CODEC = new EnumCodec<>( + SpaceAndDepthMaterialProvider.LayerContextType.class, EnumCodec.EnumStyle.LEGACY + ); + + private LayerContextType() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/AlwaysTrueCondition.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/AlwaysTrueCondition.java new file mode 100644 index 0000000..986648d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/AlwaysTrueCondition.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions; + +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; + +public class AlwaysTrueCondition implements SpaceAndDepthMaterialProvider.Condition { + public static final AlwaysTrueCondition INSTANCE = new AlwaysTrueCondition(); + + private AlwaysTrueCondition() { + } + + @Override + public boolean qualifies(int x, int y, int z, int depthIntoFloor, int depthIntoCeiling, int spaceAboveFloor, int spaceBelowCeiling) { + return true; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/AndCondition.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/AndCondition.java new file mode 100644 index 0000000..c3778ba --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/AndCondition.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions; + +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import java.util.List; +import javax.annotation.Nonnull; + +public class AndCondition implements SpaceAndDepthMaterialProvider.Condition { + @Nonnull + private final SpaceAndDepthMaterialProvider.Condition[] conditions; + + public AndCondition(@Nonnull List conditions) { + this.conditions = new SpaceAndDepthMaterialProvider.Condition[conditions.size()]; + + for (int i = 0; i < conditions.size(); i++) { + this.conditions[i] = conditions.get(i); + if (this.conditions[i] == null) { + throw new IllegalArgumentException("conditions contains null element"); + } + } + } + + @Override + public boolean qualifies(int x, int y, int z, int depthIntoFloor, int depthIntoCeiling, int spaceAboveFloor, int spaceBelowCeiling) { + for (SpaceAndDepthMaterialProvider.Condition c : this.conditions) { + if (!c.qualifies(x, y, z, depthIntoFloor, depthIntoCeiling, spaceAboveFloor, spaceBelowCeiling)) { + return false; + } + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/ConditionParameter.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/ConditionParameter.java new file mode 100644 index 0000000..198c129 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/ConditionParameter.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.codecs.EnumCodec; + +public enum ConditionParameter { + SPACE_ABOVE_FLOOR, + SPACE_BELOW_CEILING; + + public static final Codec CODEC = new EnumCodec<>(ConditionParameter.class, EnumCodec.EnumStyle.LEGACY); + + private ConditionParameter() { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/EqualsCondition.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/EqualsCondition.java new file mode 100644 index 0000000..2255303 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/EqualsCondition.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions; + +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import javax.annotation.Nonnull; + +public class EqualsCondition implements SpaceAndDepthMaterialProvider.Condition { + private final int value; + @Nonnull + private final ConditionParameter parameter; + + public EqualsCondition(int value, @Nonnull ConditionParameter parameter) { + this.value = value; + this.parameter = parameter; + } + + @Override + public boolean qualifies(int x, int y, int z, int depthIntoFloor, int depthIntoCeiling, int spaceAboveFloor, int spaceBelowCeiling) { + int contextValue = switch (this.parameter) { + case SPACE_ABOVE_FLOOR -> spaceAboveFloor; + case SPACE_BELOW_CEILING -> spaceBelowCeiling; + }; + return contextValue == this.value; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/GreaterThanCondition.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/GreaterThanCondition.java new file mode 100644 index 0000000..f4cbb7d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/GreaterThanCondition.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions; + +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import javax.annotation.Nonnull; + +public class GreaterThanCondition implements SpaceAndDepthMaterialProvider.Condition { + private final int threshold; + @Nonnull + private final ConditionParameter parameter; + + public GreaterThanCondition(int threshold, @Nonnull ConditionParameter parameter) { + this.threshold = threshold; + this.parameter = parameter; + } + + @Override + public boolean qualifies(int x, int y, int z, int depthIntoFloor, int depthIntoCeiling, int spaceAboveFloor, int spaceBelowCeiling) { + int contextValue = switch (this.parameter) { + case SPACE_ABOVE_FLOOR -> spaceAboveFloor; + case SPACE_BELOW_CEILING -> spaceBelowCeiling; + }; + return contextValue > this.threshold; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/NotCondition.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/NotCondition.java new file mode 100644 index 0000000..8b6c8c0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/NotCondition.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions; + +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import javax.annotation.Nonnull; + +public class NotCondition implements SpaceAndDepthMaterialProvider.Condition { + @Nonnull + private final SpaceAndDepthMaterialProvider.Condition condition; + + public NotCondition(@Nonnull SpaceAndDepthMaterialProvider.Condition condition) { + this.condition = condition; + } + + @Override + public boolean qualifies(int x, int y, int z, int depthIntoFloor, int depthIntoCeiling, int spaceAboveFloor, int spaceBelowCeiling) { + return !this.condition.qualifies(x, y, z, depthIntoFloor, depthIntoCeiling, spaceAboveFloor, spaceBelowCeiling); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/OrCondition.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/OrCondition.java new file mode 100644 index 0000000..5291276 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/OrCondition.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions; + +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import java.util.List; +import javax.annotation.Nonnull; + +public class OrCondition implements SpaceAndDepthMaterialProvider.Condition { + @Nonnull + private final SpaceAndDepthMaterialProvider.Condition[] conditions; + + public OrCondition(@Nonnull List conditions) { + this.conditions = new SpaceAndDepthMaterialProvider.Condition[conditions.size()]; + + for (int i = 0; i < conditions.size(); i++) { + this.conditions[i] = conditions.get(i); + if (this.conditions[i] == null) { + throw new IllegalArgumentException("conditions contains null element"); + } + } + } + + @Override + public boolean qualifies(int x, int y, int z, int depthIntoFloor, int depthIntoCeiling, int spaceAboveFloor, int spaceBelowCeiling) { + for (SpaceAndDepthMaterialProvider.Condition c : this.conditions) { + if (c.qualifies(x, y, z, depthIntoFloor, depthIntoCeiling, spaceAboveFloor, spaceBelowCeiling)) { + return true; + } + } + + return false; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/SmallerThanCondition.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/SmallerThanCondition.java new file mode 100644 index 0000000..152d1b6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/conditions/SmallerThanCondition.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.conditions; + +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import javax.annotation.Nonnull; + +public class SmallerThanCondition implements SpaceAndDepthMaterialProvider.Condition { + private final int threshold; + @Nonnull + private final ConditionParameter parameter; + + public SmallerThanCondition(int threshold, @Nonnull ConditionParameter parameter) { + this.threshold = threshold; + this.parameter = parameter; + } + + @Override + public boolean qualifies(int x, int y, int z, int depthIntoFloor, int depthIntoCeiling, int spaceAboveFloor, int spaceBelowCeiling) { + int contextValue = switch (this.parameter) { + case SPACE_ABOVE_FLOOR -> spaceAboveFloor; + case SPACE_BELOW_CEILING -> spaceBelowCeiling; + }; + return contextValue < this.threshold; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/layers/ConstantThicknessLayer.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/layers/ConstantThicknessLayer.java new file mode 100644 index 0000000..444bc22 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/layers/ConstantThicknessLayer.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.layers; + +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import javax.annotation.Nullable; + +public class ConstantThicknessLayer extends SpaceAndDepthMaterialProvider.Layer { + private final int thickness; + @Nullable + private final MaterialProvider materialProvider; + + public ConstantThicknessLayer(int thickness, @Nullable MaterialProvider materialProvider) { + this.thickness = thickness; + this.materialProvider = materialProvider; + } + + @Override + public int getThicknessAt( + int x, int y, int z, int depthIntoFloor, int depthIntoCeiling, int spaceAboveFloor, int spaceBelowCeiling, double distanceTOBiomeEdge + ) { + return this.thickness; + } + + @Nullable + @Override + public MaterialProvider getMaterialProvider() { + return this.materialProvider; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/layers/NoiseThickness.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/layers/NoiseThickness.java new file mode 100644 index 0000000..c0e1ce9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/layers/NoiseThickness.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.layers; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NoiseThickness extends SpaceAndDepthMaterialProvider.Layer { + @Nonnull + private final Density density; + @Nullable + private final MaterialProvider materialProvider; + + public NoiseThickness(@Nonnull Density density, @Nullable MaterialProvider materialProvider) { + this.density = density; + this.materialProvider = materialProvider; + } + + @Override + public int getThicknessAt( + int x, int y, int z, int depthIntoFloor, int depthIntoCeiling, int spaceAboveFloor, int spaceBelowCeiling, double distanceToBiomeEdge + ) { + Density.Context childContext = new Density.Context(); + childContext.position = new Vector3d(x, y, z); + childContext.distanceToBiomeEdge = distanceToBiomeEdge; + return (int)this.density.process(childContext); + } + + @Nullable + @Override + public MaterialProvider getMaterialProvider() { + return this.materialProvider; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/layers/RangedThicknessLayer.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/layers/RangedThicknessLayer.java new file mode 100644 index 0000000..2b44fb4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/layers/RangedThicknessLayer.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.layers; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.SeedGenerator; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.math.util.FastRandom; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RangedThicknessLayer extends SpaceAndDepthMaterialProvider.Layer { + private final int min; + private final int max; + private final int delta; + @Nonnull + private final SeedGenerator seedGenerator; + @Nullable + private final MaterialProvider materialProvider; + + public RangedThicknessLayer(int minInclusive, int maxInclusive, @Nonnull SeedBox seedBox, @Nullable MaterialProvider materialProvider) { + this.min = minInclusive; + this.max = maxInclusive; + this.delta = this.max - this.min; + if (this.delta < 0) { + throw new IllegalArgumentException("min greater than max"); + } else { + this.seedGenerator = new SeedGenerator(seedBox.createSupplier().get().intValue()); + this.materialProvider = materialProvider; + } + } + + @Override + public int getThicknessAt( + int x, int y, int z, int depthIntoFloor, int depthIntoCeiling, int spaceAboveFloor, int spaceBelowCeiling, double distanceTOBiomeEdge + ) { + if (this.delta <= 0) { + return this.min; + } else { + FastRandom random = new FastRandom(this.seedGenerator.seedAt(x, z)); + return random.nextInt(this.delta + 1) + this.min; + } + } + + @Override + public MaterialProvider getMaterialProvider() { + return this.materialProvider; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/layers/WeightedThicknessLayer.java b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/layers/WeightedThicknessLayer.java new file mode 100644 index 0000000..939e98b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/materialproviders/spaceanddepth/layers/WeightedThicknessLayer.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.layers; + +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.WeightedMap; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.SeedGenerator; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.spaceanddepth.SpaceAndDepthMaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.math.util.FastRandom; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WeightedThicknessLayer extends SpaceAndDepthMaterialProvider.Layer { + @Nonnull + private final WeightedMap thicknessPool; + @Nonnull + private final SeedGenerator seedGenerator; + @Nullable + private final MaterialProvider materialProvider; + + public WeightedThicknessLayer(@Nonnull WeightedMap thicknessPool, @Nullable MaterialProvider materialProvider, @Nonnull SeedBox seedBox) { + this.seedGenerator = new SeedGenerator(seedBox.createSupplier().get().intValue()); + this.materialProvider = materialProvider; + this.thicknessPool = thicknessPool; + } + + @Override + public int getThicknessAt( + int x, int y, int z, int depthIntoFloor, int depthIntoCeiling, int spaceAboveFloor, int spaceBelowCeiling, double distanceTOBiomeEdge + ) { + if (this.thicknessPool.size() == 0) { + return 0; + } else { + FastRandom random = new FastRandom(this.seedGenerator.seedAt(x, z)); + return this.thicknessPool.pick(random); + } + } + + @Override + public MaterialProvider getMaterialProvider() { + return this.materialProvider; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/GridUtils.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/GridUtils.java new file mode 100644 index 0000000..c15ce2c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/GridUtils.java @@ -0,0 +1,203 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NVoxelBuffer; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class GridUtils { + public static final int BUFFER_COUNT_IN_CHUNK_Y = 320 / NVoxelBuffer.SIZE.y; + + public GridUtils() { + } + + public static void toBufferGrid_fromVoxelGridOverlap(@Nonnull Bounds3i bounds_voxelGrid) { + assert bounds_voxelGrid.isCorrect(); + + VectorUtil.bitShiftRight(3, bounds_voxelGrid.min); + if (bounds_voxelGrid.max.x % NVoxelBuffer.SIZE.x == 0) { + bounds_voxelGrid.max.x--; + } + + if (bounds_voxelGrid.max.y % NVoxelBuffer.SIZE.y == 0) { + bounds_voxelGrid.max.y--; + } + + if (bounds_voxelGrid.max.z % NVoxelBuffer.SIZE.z == 0) { + bounds_voxelGrid.max.z--; + } + + bounds_voxelGrid.max.x >>= 3; + bounds_voxelGrid.max.y >>= 3; + bounds_voxelGrid.max.z >>= 3; + } + + @Nonnull + public static Bounds3i createColumnBounds_voxelGrid(@Nonnull Vector3i position_bufferGrid, int minY_voxelSpace, int maxY_voxelSpace) { + assert minY_voxelSpace <= maxY_voxelSpace; + + Vector3i min = position_bufferGrid.clone(); + VectorUtil.bitShiftLeft(3, min); + Vector3i max = min.clone().add(NVoxelBuffer.SIZE); + min.y = minY_voxelSpace; + max.y = maxY_voxelSpace; + return new Bounds3i(min, max); + } + + @Nonnull + public static Bounds3i createBufferBoundsInclusive_fromVoxelBounds(@Nonnull Bounds3i bounds_voxelGrid) { + assert bounds_voxelGrid.isCorrect(); + + Vector3i min = bounds_voxelGrid.min.clone(); + Vector3i max = bounds_voxelGrid.max.clone(); + min.x = Calculator.floor(min.x, NVoxelBuffer.SIZE.x); + min.x >>= 3; + min.y = Calculator.floor(min.y, NVoxelBuffer.SIZE.y); + min.y >>= 3; + min.z = Calculator.floor(min.z, NVoxelBuffer.SIZE.z); + min.z >>= 3; + int mod = Calculator.wrap(max.x, NVoxelBuffer.SIZE.x); + max.x = Calculator.ceil(max.x, NVoxelBuffer.SIZE.x); + max.x >>= 3; + if (mod == 0) { + max.x += 2; + } else { + max.x++; + } + + mod = Calculator.wrap(max.y, NVoxelBuffer.SIZE.y); + max.y = Calculator.ceil(max.y, NVoxelBuffer.SIZE.y); + max.y >>= 3; + if (mod == 0) { + max.y += 2; + } else { + max.y++; + } + + mod = Calculator.wrap(max.z, NVoxelBuffer.SIZE.z); + max.z = Calculator.ceil(max.z, NVoxelBuffer.SIZE.z); + max.z >>= 3; + if (mod == 0) { + max.z += 2; + } else { + max.z++; + } + + min.dropHash(); + max.dropHash(); + return new Bounds3i(min, max); + } + + @Nonnull + public static Bounds3i createColumnBounds_bufferGrid(@Nonnull Vector3i position_bufferGrid, int minY_bufferGrid, int maxY_bufferGrid) { + assert minY_bufferGrid <= maxY_bufferGrid; + + Vector3i min = position_bufferGrid.clone(); + Vector3i max = min.clone().add(Vector3i.ALL_ONES); + min.y = minY_bufferGrid; + max.y = maxY_bufferGrid; + return new Bounds3i(min, max); + } + + @Nonnull + public static Bounds3i createChunkBounds_voxelGrid(int x_chunkGrid, int z_chunkGrid) { + Vector3i min = new Vector3i(x_chunkGrid << 5, 0, z_chunkGrid << 5); + Vector3i max = min.clone().add(32, 320, 32); + return new Bounds3i(min, max); + } + + @Nonnull + public static Bounds3i createUnitBounds3i(@Nonnull Vector3i position) { + return new Bounds3i(position, position.clone().add(Vector3i.ALL_ONES)); + } + + @Nonnull + public static Bounds3i createBounds_fromRadius_originVoxelInclusive(int radius) { + Vector3i min = new Vector3i(-radius, -radius, -radius); + Vector3i max = new Vector3i(radius + 1, radius + 1, radius + 1); + return new Bounds3i(min, max); + } + + @Nonnull + public static Bounds3i createBounds_fromVector_originVoxelInclusive(@Nonnull Vector3i range) { + Vector3i min = new Vector3i(range).scale(-1); + Vector3i max = new Vector3i(range).add(Vector3i.ALL_ONES); + return new Bounds3i(min, max); + } + + @Nonnull + public static Bounds3i createChunkBounds_bufferGrid(int x_chunkGrid, int z_chunkGrid) { + int bits = 2; + Vector3i min = new Vector3i(x_chunkGrid << 2, 0, z_chunkGrid << 2); + Vector3i max = new Vector3i(x_chunkGrid + 1 << 2, 40, z_chunkGrid + 1 << 2); + return new Bounds3i(min, max); + } + + public static void toVoxelGrid_fromBufferGrid(@Nonnull Bounds3i bounds_bufferGrid) { + assert bounds_bufferGrid.isCorrect(); + + VectorUtil.bitShiftLeft(3, bounds_bufferGrid.min); + VectorUtil.bitShiftLeft(3, bounds_bufferGrid.max); + } + + public static void toVoxelGrid_fromBufferGrid(@Nonnull Vector3i position_voxelGrid) { + VectorUtil.bitShiftLeft(3, position_voxelGrid); + } + + public static void toBufferGrid_fromVoxelGrid(@Nonnull Vector3i worldPosition_voxelGrid) { + VectorUtil.bitShiftRight(3, worldPosition_voxelGrid); + } + + public static int toBufferDistanceInclusive_fromVoxelDistance(int distance_voxelGrid) { + int distance_bufferGrid = distance_voxelGrid >> 3; + return Calculator.wrap(distance_voxelGrid, NVoxelBuffer.SIZE.x) == 0 ? distance_bufferGrid : distance_bufferGrid + 1; + } + + @Nonnull + public static Vector3i toIntegerGrid_fromDecimalGrid(@Nonnull Vector3d worldPosition_decimalGrid) { + Vector3i position = new Vector3i(); + position.x = Calculator.toIntFloored(worldPosition_decimalGrid.x); + position.y = Calculator.toIntFloored(worldPosition_decimalGrid.y); + position.z = Calculator.toIntFloored(worldPosition_decimalGrid.z); + return position; + } + + public static void toVoxelGridInsideBuffer_fromWorldGrid(@Nonnull Vector3i worldPosition_voxelGrid) { + worldPosition_voxelGrid.x = Calculator.wrap(worldPosition_voxelGrid.x, NVoxelBuffer.SIZE.x); + worldPosition_voxelGrid.y = Calculator.wrap(worldPosition_voxelGrid.y, NVoxelBuffer.SIZE.y); + worldPosition_voxelGrid.z = Calculator.wrap(worldPosition_voxelGrid.z, NVoxelBuffer.SIZE.z); + } + + public static int toIndexFromPositionYXZ(@Nonnull Vector3i position, @Nonnull Bounds3i bounds) { + assert bounds.contains(position); + + int x = position.x - bounds.min.x; + int y = position.y - bounds.min.y; + int z = position.z - bounds.min.z; + int sizeX = bounds.max.x - bounds.min.x; + int sizeY = bounds.max.y - bounds.min.y; + return y + x * sizeY + z * sizeY * sizeX; + } + + public static void setBoundsYToWorldHeight_bufferGrid(@Nonnull Bounds3i bounds_bufferGrid) { + assert bounds_bufferGrid.isCorrect(); + + bounds_bufferGrid.min.setY(0); + bounds_bufferGrid.max.setY(40); + } + + public static void setBoundsYToWorldHeight_voxelGrid(@Nonnull Bounds3i bounds_voxelGrid) { + assert bounds_voxelGrid.isCorrect(); + + bounds_voxelGrid.min.setY(0); + bounds_voxelGrid.max.setY(320); + } + + public static void toVoxelPosition_fromChunkPosition(@Nonnull Vector3i chunkPosition_voxelGrid) { + chunkPosition_voxelGrid.x <<= 5; + chunkPosition_voxelGrid.z <<= 5; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/NStagedChunkGenerator.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/NStagedChunkGenerator.java new file mode 100644 index 0000000..aaeda7d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/NStagedChunkGenerator.java @@ -0,0 +1,830 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem; + +import com.hypixel.hytale.builtin.hytalegenerator.ArrayUtil; +import com.hypixel.hytale.builtin.hytalegenerator.FutureUtils; +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.chunkgenerator.ChunkGenerator; +import com.hypixel.hytale.builtin.hytalegenerator.chunkgenerator.ChunkRequest; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.material.SolidMaterial; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.NBufferBundle; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NEntityBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NSimplePixelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NVoxelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NParametrizedBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.performanceinstruments.TimeInstrument; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NStage; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.NEntityBufferView; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.NPixelBufferView; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.NVoxelBufferView; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedBlockChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedBlockStateChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedEntityChunk; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import javax.annotation.Nonnull; + +public class NStagedChunkGenerator implements ChunkGenerator { + public static final int WORLD_MIN_Y_BUFFER_GRID = 0; + public static final int WORLD_MAX_Y_BUFFER_GRID = 40; + public static final int WORLD_HEIGHT_BUFFER_GRID = 40; + public static final Bounds3i CHUNK_BOUNDS_BUFFER_GRID = new Bounds3i(Vector3i.ZERO, new Vector3i(4, 40, 4)); + public static final Bounds3i SINGLE_BUFFER_TILE_BOUNDS_BUFFER_GRID = new Bounds3i( + new Vector3i(0, 0, 0), new Vector3i(NVoxelBuffer.SIZE.x, 320, NVoxelBuffer.SIZE.x) + ); + private NBufferType materialOutput_bufferType; + private NBufferType tintOutput_bufferType; + private NBufferType environmentOutput_bufferType; + private NBufferType entityOutput_bufferType; + private NStage[] stages; + private Bounds3i[] stagesOutputBounds_bufferGrid; + private NBufferBundle bufferBundle; + private ExecutorService concurrentExecutor; + private MaterialCache materialCache; + private WorkerIndexer workerIndexer; + private TimeInstrument timeInstrument; + private Set statsCheckpoints; + private int generatedChunkCount; + private long totalCacheBufferRequests; + private long missedCacheBufferRequests; + + private NStagedChunkGenerator() { + } + + @Override + public GeneratedChunk generate(@Nonnull ChunkRequest.Arguments arguments) { + if (arguments.stillNeeded() != null && !arguments.stillNeeded().test(arguments.index())) { + return null; + } else { + this.generatedChunkCount++; + TimeInstrument.Probe total_timeProbe = new TimeInstrument.Probe("Total").start(); + TimeInstrument.Probe contentGeneration_timeProbe = total_timeProbe.createProbe("Content Generation").start(); + TimeInstrument.Probe accessInit_timeProbe = contentGeneration_timeProbe.createProbe("Access Initialization").start(); + Bounds3i localChunkBounds_bufferGrid = GridUtils.createChunkBounds_bufferGrid(arguments.x(), arguments.z()); + Map accessMap = this.createAccesses(localChunkBounds_bufferGrid); + accessInit_timeProbe.stop(); + + for (int stageIndex = 0; stageIndex < this.stages.length; stageIndex++) { + TimeInstrument.Probe stage_timeProbe = contentGeneration_timeProbe.createProbe(this.stages[stageIndex].getName() + " (Stage " + stageIndex + ")") + .start(); + TimeInstrument.Probe stagePrep_timeProbe = stage_timeProbe.createProbe("Preparation").start(); + int stageIndexConst = stageIndex; + NStage stage = this.stages[stageIndex]; + List outputTypes = stage.getOutputTypes(); + List outputGrids = new ArrayList<>(outputTypes.size()); + + for (NBufferType type : outputTypes) { + NBufferBundle.Grid grid = this.bufferBundle.getGrid(type); + outputGrids.add(grid); + } + + Bounds3i stageChunkOutputBounds_bufferGrid = this.stagesOutputBounds_bufferGrid[stageIndex].clone(); + stageChunkOutputBounds_bufferGrid.stack(CHUNK_BOUNDS_BUFFER_GRID); + stageChunkOutputBounds_bufferGrid.offset(localChunkBounds_bufferGrid.min); + List positions_bufferGrid = new ArrayList<>(); + Vector3i tilePos_bufferGrid = new Vector3i(0, 0, 0); + + for (tilePos_bufferGrid.x = stageChunkOutputBounds_bufferGrid.min.x; + tilePos_bufferGrid.x < stageChunkOutputBounds_bufferGrid.max.x; + tilePos_bufferGrid.x++ + ) { + for (tilePos_bufferGrid.z = stageChunkOutputBounds_bufferGrid.min.z; + tilePos_bufferGrid.z < stageChunkOutputBounds_bufferGrid.max.z; + tilePos_bufferGrid.z++ + ) { + tilePos_bufferGrid.dropHash(); + this.totalCacheBufferRequests++; + boolean isOutputCached = true; + + for (NBufferBundle.Grid grid : outputGrids) { + NBufferBundle.Access access = accessMap.get(grid.getBufferType()); + if (!isColumnCached(access, tilePos_bufferGrid, stageIndex)) { + isOutputCached = false; + break; + } + } + + if (!isOutputCached) { + this.missedCacheBufferRequests++; + positions_bufferGrid.add(tilePos_bufferGrid.clone()); + } + } + } + + List> splitPositions_bufferGrid = ArrayUtil.split(positions_bufferGrid, this.workerIndexer.getWorkerCount()); + WorkerIndexer.Session workerSession = this.workerIndexer.createSession(); + List> allTasks = new ArrayList<>(); + + for (int i = 0; i < splitPositions_bufferGrid.size(); i++) { + List workerPositions_bufferGrid = splitPositions_bufferGrid.get(i); + WorkerIndexer.Id workerId = workerSession.next(); + List workerTasks = new ArrayList<>(workerPositions_bufferGrid.size()); + + for (int j = 0; j < workerPositions_bufferGrid.size(); j++) { + Vector3i position_bufferGrid = workerPositions_bufferGrid.get(j); + Runnable tileTask = this.createTileTask(stageIndexConst, position_bufferGrid, workerId, accessMap); + workerTasks.add(tileTask); + } + + allTasks.add(workerTasks); + } + + stagePrep_timeProbe.stop(); + TimeInstrument.Probe stageExecution_timeProbe = stage_timeProbe.createProbe("Execution").start(); + TimeInstrument.Probe taskStart_timeProbe = stageExecution_timeProbe.createProbe("Async Processes Start").start(); + List> futures = new ArrayList<>(); + + for (List workerTasks : allTasks) { + CompletableFuture future = CompletableFuture.runAsync(() -> { + for (Runnable task : workerTasks) { + task.run(); + } + }, this.concurrentExecutor).handle((r, e) -> { + if (e == null) { + return (Void)r; + } else { + LoggerUtil.logException("during async execution of stage " + stage, e); + return null; + } + }); + futures.add(future); + } + + taskStart_timeProbe.stop(); + FutureUtils.allOf(futures).join(); + stageExecution_timeProbe.stop(); + stage_timeProbe.stop(); + } + + contentGeneration_timeProbe.stop(); + TimeInstrument.Probe transfer_timeProbe = total_timeProbe.createProbe("Data Transfer").start(); + GeneratedChunk outputChunk = new GeneratedChunk( + new GeneratedBlockChunk(arguments.index(), arguments.x(), arguments.z()), + new GeneratedBlockStateChunk(), + new GeneratedEntityChunk(), + GeneratedChunk.makeSections() + ); + List> futures = new ArrayList<>(); + futures.add(this.transferMaterials(arguments, outputChunk, transfer_timeProbe)); + futures.add(this.transferEnvironments(arguments, outputChunk, transfer_timeProbe)); + futures.add(this.transferTints(arguments, outputChunk, transfer_timeProbe)); + futures.add(this.transferEntities(arguments, outputChunk, transfer_timeProbe)); + futures.add(this.transferBlockStates(arguments, outputChunk.getBlockStateChunk(), transfer_timeProbe)); + FutureUtils.allOf(futures).join(); + transfer_timeProbe.stop(); + total_timeProbe.stop(); + this.timeInstrument.takeSample(total_timeProbe); + if (this.statsCheckpoints.contains(this.generatedChunkCount)) { + NBufferBundle.MemoryReport bufferMemoryReport = this.bufferBundle.createMemoryReport(); + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(this.timeInstrument.toString()) + .append(bufferMemoryReport.toString()) + .append(this.createContextDependencyReport(0)) + .append(this.createBufferRequestCacheReport()); + LoggerUtil.getLogger().info(stringBuilder.toString()); + } + + this.bufferBundle.closeALlAccesses(); + return outputChunk; + } + } + + @Nonnull + private Map createAccesses(@Nonnull Bounds3i localChunkBounds_bufferGrid) { + Map accessMap = new HashMap<>(); + + for (int stageIndex = 0; stageIndex < this.stages.length; stageIndex++) { + NStage stage = this.stages[stageIndex]; + List outputTypes = stage.getOutputTypes(); + Bounds3i bounds_bufferGrid = this.stagesOutputBounds_bufferGrid[stageIndex].clone(); + bounds_bufferGrid.stack(CHUNK_BOUNDS_BUFFER_GRID); + bounds_bufferGrid.offset(localChunkBounds_bufferGrid.min); + + for (NBufferType bufferType : outputTypes) { + NBufferBundle.Access access = this.bufferBundle.createBufferAccess(bufferType, bounds_bufferGrid); + accessMap.put(bufferType, access); + } + } + + return accessMap; + } + + @Nonnull + private Runnable createTileTask( + int stageIndex, @Nonnull Vector3i position_bufferTileGrid, @Nonnull WorkerIndexer.Id workerId, @Nonnull Map accessMap + ) { + NStage stage = this.stages[stageIndex]; + Map inputTypesAndBounds_tileGrid = stage.getInputTypesAndBounds_bufferGrid(); + List outputTypes = stage.getOutputTypes(); + int bufferAccessCount = inputTypesAndBounds_tileGrid.size() + outputTypes.size(); + NStage.Context context = new NStage.Context(new HashMap<>(bufferAccessCount), workerId); + + for (Entry entry : inputTypesAndBounds_tileGrid.entrySet()) { + NBufferType bufferType = entry.getKey(); + Bounds3i localInputBounds_bufferGrid = entry.getValue().clone().offset(position_bufferTileGrid); + NBufferBundle.Access.View bufferAccess = accessMap.get(bufferType).createView(localInputBounds_bufferGrid); + context.bufferAccess.put(bufferType, bufferAccess); + } + + for (NBufferType bufferType : stage.getOutputTypes()) { + assert !context.bufferAccess.containsKey(bufferType); + + Bounds3i columnBounds_bufferGrid = GridUtils.createColumnBounds_bufferGrid(position_bufferTileGrid, 0, 40); + NBufferBundle.Access.View bufferAccess = accessMap.get(bufferType).createView(columnBounds_bufferGrid); + context.bufferAccess.put(bufferType, bufferAccess); + } + + Vector3i bufferPositionClone_bufferTileGrid = position_bufferTileGrid.clone(); + return () -> { + stage.run(context); + + for (NBufferType outputType : stage.getOutputTypes()) { + updateTrackersForColumn(stageIndex, accessMap.get(outputType).createView(), bufferPositionClone_bufferTileGrid); + } + }; + } + + @Nonnull + private CompletableFuture transferBlockStates( + @Nonnull ChunkRequest.Arguments arguments, @Nonnull GeneratedBlockStateChunk blockStateChunk, @Nonnull TimeInstrument.Probe transfer_timeProbe + ) { + Bounds3i chunkBounds_voxelGrid = GridUtils.createChunkBounds_voxelGrid(arguments.x(), arguments.z()); + Bounds3i chunkBounds_bufferGrid = GridUtils.createChunkBounds_bufferGrid(arguments.x(), arguments.z()); + NBufferBundle.Access materialBufferAccess = this.bufferBundle.createBufferAccess(this.materialOutput_bufferType, chunkBounds_bufferGrid); + VoxelSpace materialVoxelSpace = new NVoxelBufferView<>(materialBufferAccess.createView(), Material.class); + TimeInstrument.Probe timeProbe = transfer_timeProbe.createProbe("Block States"); + return CompletableFuture.runAsync(() -> { + timeProbe.start(); + Vector3i position_voxelGrid = new Vector3i(); + + for (position_voxelGrid.x = chunkBounds_voxelGrid.min.x; position_voxelGrid.x < chunkBounds_voxelGrid.max.x; position_voxelGrid.x++) { + for (position_voxelGrid.z = chunkBounds_voxelGrid.min.z; position_voxelGrid.z < chunkBounds_voxelGrid.max.z; position_voxelGrid.z++) { + for (position_voxelGrid.y = chunkBounds_voxelGrid.min.y; position_voxelGrid.y < chunkBounds_voxelGrid.max.y; position_voxelGrid.y++) { + SolidMaterial solidMaterial = materialVoxelSpace.getContent(position_voxelGrid).solid(); + if (solidMaterial != null && solidMaterial.holder != null) { + blockStateChunk.setState(position_voxelGrid.x, position_voxelGrid.y, position_voxelGrid.z, solidMaterial.holder); + } + } + } + } + + timeProbe.stop(); + }, this.concurrentExecutor).handle((r, e) -> { + if (e == null) { + return (Void)r; + } else { + LoggerUtil.logException("a HytaleGenerator async process", e, LoggerUtil.getLogger()); + return null; + } + }); + } + + @Nonnull + private CompletableFuture transferMaterials( + @Nonnull ChunkRequest.Arguments arguments, @Nonnull GeneratedChunk generatedChunk, @Nonnull TimeInstrument.Probe transfer_timeProbe + ) { + Bounds3i chunkBounds_voxelGrid = GridUtils.createChunkBounds_voxelGrid(arguments.x(), arguments.z()); + Bounds3i chunkBounds_bufferGrid = GridUtils.createChunkBounds_bufferGrid(arguments.x(), arguments.z()); + NBufferBundle.Access materialBufferAccess = this.bufferBundle.createBufferAccess(this.materialOutput_bufferType, chunkBounds_bufferGrid); + VoxelSpace materialVoxelSpace = new NVoxelBufferView<>(materialBufferAccess.createView(), Material.class); + GeneratedBlockChunk blockChunk = generatedChunk.getBlockChunk(); + Holder[] sections = generatedChunk.getSections(); + FluidSection[] fluidSections = new FluidSection[sections.length]; + + for (int sectionIndex = 0; sectionIndex < 10; sectionIndex++) { + Holder section = sections[sectionIndex]; + FluidSection fluidSection = section.ensureAndGetComponent(FluidSection.getComponentType()); + fluidSections[sectionIndex] = fluidSection; + } + + CompletableFuture[] futures = new CompletableFuture[10]; + + for (int sectionIndex = 0; sectionIndex < 10; sectionIndex++) { + int sectionIndexFinal = sectionIndex; + TimeInstrument.Probe section_timeProbe = transfer_timeProbe.createProbe("Materials Section " + sectionIndexFinal); + CompletableFuture task = CompletableFuture.runAsync( + () -> { + section_timeProbe.start(); + Holder section = sections[sectionIndexFinal]; + FluidSection fluidSection = fluidSections[sectionIndexFinal]; + + for (int x_voxelGrid = 0; x_voxelGrid < 32; x_voxelGrid++) { + for (int z_voxelGrid = 0; z_voxelGrid < 32; z_voxelGrid++) { + int minY_voxelGrid = sectionIndexFinal * 32; + int maxY_voxelGrid = minY_voxelGrid + 32; + + for (int y_voxelGrid = minY_voxelGrid; y_voxelGrid < maxY_voxelGrid; y_voxelGrid++) { + int sectionY = y_voxelGrid - minY_voxelGrid; + int worldX_voxelGrid = x_voxelGrid + chunkBounds_voxelGrid.min.x; + int worldY_voxelGrid = y_voxelGrid + chunkBounds_voxelGrid.min.y; + int worldZ_voxelGrid = z_voxelGrid + chunkBounds_voxelGrid.min.z; + Material material = materialVoxelSpace.getContent(worldX_voxelGrid, worldY_voxelGrid, worldZ_voxelGrid); + if (material == null) { + blockChunk.setBlock(x_voxelGrid, y_voxelGrid, z_voxelGrid, 0, 0, 0); + fluidSection.setFluid( + x_voxelGrid, sectionY, z_voxelGrid, this.materialCache.EMPTY_FLUID.fluidId, this.materialCache.EMPTY_FLUID.fluidLevel + ); + } else { + blockChunk.setBlock( + x_voxelGrid, y_voxelGrid, z_voxelGrid, material.solid().blockId, material.solid().rotation, material.solid().filler + ); + setSupport(generatedChunk, x_voxelGrid, y_voxelGrid, z_voxelGrid, material.solid().blockId, material.solid().support); + fluidSection.setFluid(x_voxelGrid, sectionY, z_voxelGrid, material.fluid().fluidId, material.fluid().fluidLevel); + } + } + } + } + + section_timeProbe.stop(); + }, + this.concurrentExecutor + ) + .handle((r, e) -> { + if (e == null) { + return (Void)r; + } else { + LoggerUtil.logException("a HytaleGenerator async process", e, LoggerUtil.getLogger()); + return null; + } + }); + futures[sectionIndex] = task; + } + + return CompletableFuture.allOf(futures).thenRun(blockChunk::generateHeight); + } + + @Nonnull + private CompletableFuture transferTints( + @Nonnull ChunkRequest.Arguments arguments, @Nonnull GeneratedChunk generatedChunk, @Nonnull TimeInstrument.Probe transfer_timeProbe + ) { + Bounds3i chunkBounds_voxelGrid = GridUtils.createChunkBounds_voxelGrid(arguments.x(), arguments.z()); + Bounds3i chunkBounds_bufferGrid = GridUtils.createChunkBounds_bufferGrid(arguments.x(), arguments.z()); + NBufferBundle.Access tintBufferAccess = this.bufferBundle.createBufferAccess(this.tintOutput_bufferType, chunkBounds_bufferGrid); + VoxelSpace tintVoxelSpace = new NPixelBufferView<>(tintBufferAccess.createView(), Integer.class); + GeneratedBlockChunk blockChunk = generatedChunk.getBlockChunk(); + int worldY_voxelGrid = 0; + TimeInstrument.Probe tintsTransfer_timeProbe = transfer_timeProbe.createProbe("Tints"); + return CompletableFuture.runAsync(() -> { + tintsTransfer_timeProbe.start(); + + for (int x_voxelGrid = 0; x_voxelGrid < 32; x_voxelGrid++) { + for (int z_voxelGrid = 0; z_voxelGrid < 32; z_voxelGrid++) { + int worldX_voxelGrid = x_voxelGrid + chunkBounds_voxelGrid.min.x; + int worldZ_voxelGrid = z_voxelGrid + chunkBounds_voxelGrid.min.z; + Integer tint = tintVoxelSpace.getContent(worldX_voxelGrid, 0, worldZ_voxelGrid); + if (tint == null) { + blockChunk.setTint(x_voxelGrid, z_voxelGrid, 0); + } else { + blockChunk.setTint(x_voxelGrid, z_voxelGrid, tint); + } + } + } + + tintsTransfer_timeProbe.stop(); + }, this.concurrentExecutor).handle((r, e) -> { + if (e == null) { + return (Void)r; + } else { + LoggerUtil.logException("a HytaleGenerator async process", e, LoggerUtil.getLogger()); + return null; + } + }); + } + + @Nonnull + private CompletableFuture transferEnvironments( + @Nonnull ChunkRequest.Arguments arguments, @Nonnull GeneratedChunk generatedChunk, @Nonnull TimeInstrument.Probe transfer_timeProbe + ) { + Bounds3i chunkBounds_voxelGrid = GridUtils.createChunkBounds_voxelGrid(arguments.x(), arguments.z()); + Bounds3i chunkBounds_bufferGrid = GridUtils.createChunkBounds_bufferGrid(arguments.x(), arguments.z()); + NBufferBundle.Access environmentBufferAccess = this.bufferBundle.createBufferAccess(this.environmentOutput_bufferType, chunkBounds_bufferGrid); + VoxelSpace environmentVoxelSpace = new NVoxelBufferView<>(environmentBufferAccess.createView(), Integer.class); + GeneratedBlockChunk blockChunk = generatedChunk.getBlockChunk(); + TimeInstrument.Probe timeProbe = transfer_timeProbe.createProbe("Environment"); + return CompletableFuture.runAsync(() -> { + timeProbe.start(); + + for (int x_voxelGrid = 0; x_voxelGrid < 32; x_voxelGrid++) { + for (int z_voxelGrid = 0; z_voxelGrid < 32; z_voxelGrid++) { + int minY_voxelGrid = 0; + int maxY_voxelGrid = 320; + + for (int y_voxelGrid = 0; y_voxelGrid < 320; y_voxelGrid++) { + int worldX_voxelGrid = x_voxelGrid + chunkBounds_voxelGrid.min.x; + int worldY_voxelGrid = y_voxelGrid + chunkBounds_voxelGrid.min.y; + int worldZ_voxelGrid = z_voxelGrid + chunkBounds_voxelGrid.min.z; + Integer environment = environmentVoxelSpace.getContent(worldX_voxelGrid, worldY_voxelGrid, worldZ_voxelGrid); + blockChunk.setEnvironment(x_voxelGrid, y_voxelGrid, z_voxelGrid, Objects.requireNonNullElse(environment, 0)); + } + } + } + + timeProbe.stop(); + }, this.concurrentExecutor).handle((r, e) -> { + if (e == null) { + return (Void)r; + } else { + LoggerUtil.logException("a HytaleGenerator async process", e, LoggerUtil.getLogger()); + return null; + } + }); + } + + @Nonnull + private CompletableFuture transferEntities( + @Nonnull ChunkRequest.Arguments arguments, @Nonnull GeneratedChunk generatedChunk, @Nonnull TimeInstrument.Probe transfer_timeProbe + ) { + Bounds3i chunkBounds_bufferGrid = GridUtils.createChunkBounds_bufferGrid(arguments.x(), arguments.z()); + NBufferBundle.Access entityBufferAccess = this.bufferBundle.createBufferAccess(this.entityOutput_bufferType, chunkBounds_bufferGrid); + NEntityBufferView entityView = new NEntityBufferView(entityBufferAccess.createView()); + GeneratedEntityChunk entityChunk = generatedChunk.getEntityChunk(); + TimeInstrument.Probe entitesTransfer_timeProbe = transfer_timeProbe.createProbe("Entities"); + return CompletableFuture.runAsync(() -> { + entitesTransfer_timeProbe.start(); + entityView.forEach(e -> entityChunk.addEntities(e.getOffset(), e.getRotation(), new Holder[]{e.getEntityHolder()}, arguments.seed())); + entitesTransfer_timeProbe.stop(); + }, this.concurrentExecutor).handle((r, e) -> { + if (e == null) { + return (Void)r; + } else { + LoggerUtil.logException("a HytaleGenerator async process", e, LoggerUtil.getLogger()); + return null; + } + }); + } + + @Nonnull + private String createBufferRequestCacheReport() { + StringBuilder builder = new StringBuilder(); + builder.append("Buffer Cache Report\n"); + builder.append("Total Cache Buffer Requests: ").append(this.totalCacheBufferRequests).append("\n"); + builder.append("Missed Cache Buffer Requests: ").append(this.missedCacheBufferRequests).append("\n"); + double ratio = (double)this.missedCacheBufferRequests / this.totalCacheBufferRequests * 100.0; + builder.append("Missed/Total Ratio: ").append(ratio).append("%\n"); + return builder.toString(); + } + + @Nonnull + private String createContextDependencyReport(int indentation) { + StringBuilder builder = new StringBuilder(); + builder.append("Context Dependency Report\n"); + + for (int stageIndex = 0; stageIndex < this.stages.length; stageIndex++) { + Bounds3i bounds_bufferGrid = this.stagesOutputBounds_bufferGrid[stageIndex]; + Vector3i size_bufferGrid = bounds_bufferGrid.getSize().add(3, 0, 3); + Vector3d size_chunkGrid = new Vector3d(size_bufferGrid); + size_chunkGrid.scale(0.25); + builder.append("\t".repeat(indentation)).append(this.stages[stageIndex].getName()).append(" (Stage ").append(stageIndex).append("):\n"); + builder.append("\t".repeat(indentation + 1)) + .append("Output Size (Buffer Column): {x=") + .append(size_bufferGrid.x) + .append(", z=") + .append(size_bufferGrid.z) + .append("}\n"); + builder.append("\t".repeat(indentation + 1)) + .append("Output Size (Chunk Column): {x=") + .append(size_chunkGrid.x) + .append(", z=") + .append(size_chunkGrid.z) + .append("}\n"); + } + + return builder.toString(); + } + + private static void setSupport(@Nonnull GeneratedChunk chunk, int x, int y, int z, int blockId, int supportValue) { + Holder[] sections = chunk.getSections(); + Holder section = sections[ChunkUtil.chunkCoordinate(y)]; + if (supportValue >= 0) { + BlockPhysics.setSupportValue(section, x, y, z, supportValue); + } else { + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + if (blockType != null && blockType.hasSupport()) { + BlockPhysics.reset(section, x, y, z); + } else { + BlockPhysics.clear(section, x, y, z); + } + } + } + + private static void setBoundsToWorldHeight_bufferGrid(@Nonnull Bounds3i bounds_bufferGrid) { + bounds_bufferGrid.min.setY(0); + bounds_bufferGrid.max.setY(40); + } + + private static boolean isColumnCached(@Nonnull NBufferBundle.Access access, @Nonnull Vector3i position_bufferGrid, int stageIndex) { + assert position_bufferGrid.y == 0; + + NBufferBundle.Tracker tracker = access.getBuffer(position_bufferGrid).tracker(); + return tracker.stageIndex == stageIndex; + } + + private static void updateTrackersForColumn(int stageIndex, @Nonnull NBufferBundle.Access.View access, @Nonnull Vector3i position_bufferGrid) { + for (position_bufferGrid.y = 0; position_bufferGrid.y < 40; position_bufferGrid.y++) { + position_bufferGrid.dropHash(); + NBufferBundle.Tracker tracker = access.getBuffer(position_bufferGrid).tracker(); + tracker.stageIndex = stageIndex; + } + } + + public static class Builder { + public final NParametrizedBufferType MATERIAL_OUTPUT_BUFFER_TYPE = new NParametrizedBufferType( + "MaterialResult", -1, NVoxelBuffer.class, Material.class, () -> new NVoxelBuffer<>(Material.class) + ); + public final NParametrizedBufferType TINT_OUTPUT_BUFFER_TYPE = new NParametrizedBufferType( + "TintResult", -3, NSimplePixelBuffer.class, Integer.class, () -> new NSimplePixelBuffer<>(Integer.class) + ); + public final NParametrizedBufferType ENVIRONMENT_OUTPUT_BUFFER_TYPE = new NParametrizedBufferType( + "EnvironmentResult", -4, NVoxelBuffer.class, Integer.class, () -> new NVoxelBuffer<>(Integer.class) + ); + public final NBufferType ENTITY_OUTPUT_BUFFER_TYPE = new NBufferType("EntityResult", -5, NEntityBuffer.class, NEntityBuffer::new); + private List stages = new ArrayList<>(); + private ExecutorService concurrentExecutor; + private MaterialCache materialCache; + private WorkerIndexer workerIndexer; + private String statsHeader; + private Set statsCheckpoints; + private double bufferCapacityFactor; + private double targetViewDistance; + private double targetPlayerCount; + + public Builder() { + } + + @Nonnull + public NStagedChunkGenerator build() { + assert this.concurrentExecutor != null; + + assert this.materialCache != null; + + assert this.workerIndexer != null; + + assert this.statsHeader != null; + + assert this.statsCheckpoints != null; + + NStagedChunkGenerator instance = new NStagedChunkGenerator(); + instance.materialOutput_bufferType = this.MATERIAL_OUTPUT_BUFFER_TYPE; + instance.tintOutput_bufferType = this.TINT_OUTPUT_BUFFER_TYPE; + instance.environmentOutput_bufferType = this.ENVIRONMENT_OUTPUT_BUFFER_TYPE; + instance.entityOutput_bufferType = this.ENTITY_OUTPUT_BUFFER_TYPE; + instance.stages = new NStage[this.stages.size()]; + this.stages.toArray(instance.stages); + Set allUsedBufferTypes = this.createListOfAllBufferTypes(); + Map> laterToEalierStageMap = this.createStageDependencyMap(); + instance.stagesOutputBounds_bufferGrid = this.createTotalOutputBoundsArray(laterToEalierStageMap); + instance.bufferBundle = new NBufferBundle(); + instance.bufferBundle + .createGrid(this.MATERIAL_OUTPUT_BUFFER_TYPE, this.resolveBufferCapacity(this.MATERIAL_OUTPUT_BUFFER_TYPE, instance.stagesOutputBounds_bufferGrid)); + instance.bufferBundle + .createGrid(this.TINT_OUTPUT_BUFFER_TYPE, this.resolveBufferCapacity(this.TINT_OUTPUT_BUFFER_TYPE, instance.stagesOutputBounds_bufferGrid)); + instance.bufferBundle + .createGrid( + this.ENVIRONMENT_OUTPUT_BUFFER_TYPE, this.resolveBufferCapacity(this.ENVIRONMENT_OUTPUT_BUFFER_TYPE, instance.stagesOutputBounds_bufferGrid) + ); + instance.bufferBundle + .createGrid(this.ENTITY_OUTPUT_BUFFER_TYPE, this.resolveBufferCapacity(this.ENTITY_OUTPUT_BUFFER_TYPE, instance.stagesOutputBounds_bufferGrid)); + + for (NBufferType bufferType : allUsedBufferTypes) { + if (!this.isGeneratorOutputBufferType(bufferType)) { + instance.bufferBundle.createGrid(bufferType, this.resolveBufferCapacity(bufferType, instance.stagesOutputBounds_bufferGrid)); + } + } + + instance.concurrentExecutor = this.concurrentExecutor; + instance.materialCache = this.materialCache; + instance.workerIndexer = this.workerIndexer; + instance.timeInstrument = new TimeInstrument(this.statsHeader); + instance.statsCheckpoints = new HashSet<>(this.statsCheckpoints); + instance.generatedChunkCount = 0; + return instance; + } + + @Nonnull + public NStagedChunkGenerator.Builder withStats(@Nonnull String statsHeader, @Nonnull Set statsCheckpoints) { + this.statsHeader = statsHeader; + this.statsCheckpoints = new HashSet<>(statsCheckpoints); + return this; + } + + @Nonnull + public NStagedChunkGenerator.Builder withConcurrentExecutor(@Nonnull ExecutorService executor, @Nonnull WorkerIndexer workerIndexer) { + this.concurrentExecutor = executor; + this.workerIndexer = workerIndexer; + return this; + } + + @Nonnull + public NStagedChunkGenerator.Builder withMaterialCache(@Nonnull MaterialCache materialCache) { + this.materialCache = materialCache; + return this; + } + + @Nonnull + public NStagedChunkGenerator.Builder withBufferCapacity(double factor, double targetViewDistance, double targetPlayerCount) { + assert factor >= 0.0; + + assert targetViewDistance >= 0.0; + + assert targetPlayerCount >= 0.0; + + this.bufferCapacityFactor = factor; + this.targetViewDistance = targetViewDistance; + this.targetPlayerCount = targetPlayerCount; + return this; + } + + @Nonnull + public NStagedChunkGenerator.Builder appendStage(@Nonnull NStage stage) { + this.stages.add(stage); + return this; + } + + @Nonnull + private List createStagesThatReadFrom(int stageIndex) { + NStage stage = this.stages.get(stageIndex); + List stagesThatReadFromThis = new ArrayList<>(); + List outputTypes = stage.getOutputTypes(); + + for (int i = 0; i < outputTypes.size(); i++) { + NBufferType outputType = outputTypes.get(i); + + for (int j = 0; j < this.stages.size(); j++) { + NStage dependentStage = this.stages.get(j); + if (dependentStage.getInputTypesAndBounds_bufferGrid().containsKey(outputType)) { + stagesThatReadFromThis.add(j); + } + } + } + + return stagesThatReadFromThis; + } + + @Nonnull + private Map> createStageDependencyMap() { + Map> dependencyMap = new HashMap<>(); + + for (int stageIndex = 0; stageIndex < this.stages.size(); stageIndex++) { + dependencyMap.put(stageIndex, new HashSet<>(1)); + } + + for (int stageIndex = 0; stageIndex < this.stages.size(); stageIndex++) { + for (Integer dependentStage : this.createStagesThatReadFrom(stageIndex)) { + dependencyMap.get(dependentStage).add(stageIndex); + } + } + + return dependencyMap; + } + + private int resolveBufferCapacity(@Nonnull NBufferType bufferType, @Nonnull Bounds3i[] stagesOutputBounds) { + int stageIndex = 0; + + while (stageIndex < stagesOutputBounds.length && !this.stages.get(stageIndex).getOutputTypes().contains(bufferType)) { + stageIndex++; + } + + if (stageIndex >= stagesOutputBounds.length) { + return 0; + } else { + Bounds3i outputBounds = stagesOutputBounds[stageIndex]; + return calculateCapacityFromBounds(outputBounds, this.bufferCapacityFactor, this.targetViewDistance, this.targetPlayerCount); + } + } + + private static int calculateCapacityFromBounds(@Nonnull Bounds3i bounds, double factor, double viewDistance_voxelGrid, double playerCount) { + assert factor >= 0.0; + + assert viewDistance_voxelGrid >= 0.0; + + assert playerCount >= 0.0; + + Vector3i size = bounds.getSize(); + if (size.x == 1 && size.z == 1) { + return 0; + } else { + double viewDistance_bufferGrid = viewDistance_voxelGrid / NVoxelBuffer.SIZE.x; + double entireArea = size.x + viewDistance_bufferGrid * 2.0; + entireArea *= size.z + viewDistance_bufferGrid * 2.0; + double holeArea; + if (!(size.x > viewDistance_bufferGrid) && !(size.z > viewDistance_bufferGrid)) { + holeArea = (viewDistance_bufferGrid - size.x / 2.0) * (viewDistance_bufferGrid - size.z / 2.0); + } else { + holeArea = 0.0; + } + + double ringArea = entireArea - holeArea; + double totalPlayersArea = ringArea * playerCount; + double factoredArea = totalPlayersArea * factor; + double totalVolume = factoredArea * 40.0; + + assert totalVolume >= 0.0; + + return Math.max(0, (int)totalVolume); + } + } + + private void createTotalOutputBoundsForStage( + int stageIndex, @Nonnull Map> stageDependencyMap, @Nonnull Bounds3i[] totalOutputBoundsPerStage_bufferGrid + ) { + Bounds3i initialOutputBounds_bufferGrid = new Bounds3i(Vector3i.ZERO, Vector3i.ALL_ONES); + NStage stage = this.stages.get(stageIndex); + List allOutputBounds = new ArrayList<>(); + + for (int dependentStageIndex = this.stages.size() - 1; dependentStageIndex >= stageIndex + 1; dependentStageIndex--) { + if (stageDependencyMap.get(dependentStageIndex).contains(stageIndex)) { + NStage dependentStage = this.stages.get(dependentStageIndex); + Map dependentInputTypesAndBounds_bufferGrid = dependentStage.getInputTypesAndBounds_bufferGrid(); + + for (NBufferType thisStageOutputTypes : stage.getOutputTypes()) { + Bounds3i dependentStageInputBounds_bufferGrid = dependentInputTypesAndBounds_bufferGrid.get(thisStageOutputTypes); + if (dependentStageInputBounds_bufferGrid != null) { + Bounds3i totalDependentStageOutputBounds_bufferGrid = totalOutputBoundsPerStage_bufferGrid[dependentStageIndex]; + Bounds3i totalThisStageOutputBounds_bufferGrid = totalDependentStageOutputBounds_bufferGrid.clone() + .stack(dependentStageInputBounds_bufferGrid); + allOutputBounds.add(totalThisStageOutputBounds_bufferGrid); + } + } + } + } + + if (allOutputBounds.isEmpty()) { + NStagedChunkGenerator.setBoundsToWorldHeight_bufferGrid(initialOutputBounds_bufferGrid); + totalOutputBoundsPerStage_bufferGrid[stageIndex] = initialOutputBounds_bufferGrid; + } else { + Bounds3i totalOutputBounds_bufferGrid = allOutputBounds.getFirst().clone(); + + for (int i = 1; i < allOutputBounds.size(); i++) { + totalOutputBounds_bufferGrid.encompass(allOutputBounds.get(i)); + } + + NStagedChunkGenerator.setBoundsToWorldHeight_bufferGrid(totalOutputBounds_bufferGrid); + totalOutputBoundsPerStage_bufferGrid[stageIndex] = totalOutputBounds_bufferGrid; + } + } + + @Nonnull + private Bounds3i[] createTotalOutputBoundsArray(@Nonnull Map> stageDependencyMap) { + Bounds3i[] totalOutputBounds_bufferGrid = new Bounds3i[this.stages.size()]; + + for (int stageIndex = this.stages.size() - 1; stageIndex >= 0; stageIndex--) { + this.createTotalOutputBoundsForStage(stageIndex, stageDependencyMap, totalOutputBounds_bufferGrid); + } + + return totalOutputBounds_bufferGrid; + } + + @Nonnull + private Set createListOfAllBufferTypes() { + Set allBufferTypes = new HashSet<>(); + + for (int stageIndex = 0; stageIndex < this.stages.size(); stageIndex++) { + NStage stage = this.stages.get(stageIndex); + allBufferTypes.addAll(stage.getInputTypesAndBounds_bufferGrid().keySet()); + allBufferTypes.addAll(stage.getOutputTypes()); + } + + return allBufferTypes; + } + + private static Bounds3i getEncompassingBounds(@Nonnull Collection set) { + Bounds3i out = new Bounds3i(); + + for (Bounds3i bounds : set) { + out.encompass(bounds); + } + + return out; + } + + private boolean isGeneratorOutputBufferType(@Nonnull NBufferType bufferType) { + return bufferType.equals(this.MATERIAL_OUTPUT_BUFFER_TYPE) + || bufferType.equals(this.TINT_OUTPUT_BUFFER_TYPE) + || bufferType.equals(this.ENVIRONMENT_OUTPUT_BUFFER_TYPE) + || bufferType.equals(this.ENTITY_OUTPUT_BUFFER_TYPE); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/NViewport.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/NViewport.java new file mode 100644 index 0000000..a060a2f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/NViewport.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem; + +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import it.unimi.dsi.fastutil.longs.LongArraySet; +import it.unimi.dsi.fastutil.longs.LongSet; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class NViewport { + @Nonnull + private final World world; + @Nonnull + private final CommandSender sender; + @Nonnull + private final LongSet affectedChunkIndices; + + public NViewport(@Nonnull Bounds3i viewportBounds_voxelGrid, @Nonnull World world, @Nonnull CommandSender sender) { + this.world = world; + this.sender = sender; + int minCX = ChunkUtil.chunkCoordinate(viewportBounds_voxelGrid.min.x); + int minCZ = ChunkUtil.chunkCoordinate(viewportBounds_voxelGrid.min.z); + int maxCX = ChunkUtil.chunkCoordinate(viewportBounds_voxelGrid.max.x); + int maxCZ = ChunkUtil.chunkCoordinate(viewportBounds_voxelGrid.max.z); + this.affectedChunkIndices = new LongArraySet(); + + for (int x = minCX; x <= maxCX; x++) { + for (int z = minCZ; z <= maxCZ; z++) { + long chunkIndex = ChunkUtil.indexChunk(x, z); + this.affectedChunkIndices.add(chunkIndex); + } + } + } + + public void refresh() { + LoggerUtil.getLogger().info("Refreshing viewport..."); + CompletableFuture[] futures = new CompletableFuture[this.affectedChunkIndices.size()]; + int i = 0; + + for (long chunkIndex : this.affectedChunkIndices) { + ChunkStore chunkStore = this.world.getChunkStore(); + CompletableFuture future = chunkStore.getChunkReferenceAsync(chunkIndex, 9); + futures[i++] = future; + } + + CompletableFuture.allOf(futures).handle((r, e) -> { + if (e == null) { + return (Void)r; + } else { + LoggerUtil.logException("viewport refresh", e); + return null; + } + }).thenRun(() -> LoggerUtil.getLogger().info("Viewport refresh complete.")); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/TerrainDensityProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/TerrainDensityProvider.java new file mode 100644 index 0000000..73c6521 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/TerrainDensityProvider.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem; + +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +@FunctionalInterface +public interface TerrainDensityProvider { + double get(@Nonnull Vector3i var1, @Nonnull WorkerIndexer.Id var2); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/NBufferBundle.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/NBufferBundle.java new file mode 100644 index 0000000..ffc469c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/NBufferBundle.java @@ -0,0 +1,426 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.GridUtils; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.performanceinstruments.MemInstrument; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class NBufferBundle implements MemInstrument { + private final Map grids = new HashMap<>(); + + public NBufferBundle() { + } + + @Nonnull + public NBufferBundle.Grid createGrid(@Nonnull NBufferType bufferType, int capacity) { + assert capacity >= 0; + + assert !this.grids.containsKey(bufferType); + + assert !this.existingGridHasBufferTypeIndex(bufferType.index); + + NBufferBundle.Grid grid = new NBufferBundle.Grid(bufferType, capacity); + this.grids.put(bufferType, grid); + return grid; + } + + @Nonnull + public NBufferBundle.Access createBufferAccess(@Nonnull NBufferType bufferType, @Nonnull Bounds3i bounds_bufferGrid) { + assert bounds_bufferGrid.isCorrect(); + + return this.getGrid(bufferType).openAccess(bounds_bufferGrid); + } + + public void closeALlAccesses() { + for (NBufferBundle.Grid grid : this.grids.values()) { + grid.closeAllAccesses(); + } + } + + @Nonnull + public NBufferBundle.Grid getGrid(@Nonnull NBufferType contentType) { + NBufferBundle.Grid grid = this.grids.get(contentType); + + assert grid != null; + + return grid; + } + + @Nonnull + @Override + public MemInstrument.Report getMemoryUsage() { + long size_bytes = 16L; + + for (Entry entry : this.grids.entrySet()) { + size_bytes += entry.getValue().getMemoryUsage().size_bytes(); + } + + return new MemInstrument.Report(size_bytes); + } + + private boolean existingGridHasBufferTypeIndex(int bufferTypeIndex) { + for (NBufferBundle.Grid grid : this.grids.values()) { + if (grid.bufferType.index == bufferTypeIndex) { + return true; + } + } + + return false; + } + + @Nonnull + public NBufferBundle.MemoryReport createMemoryReport() { + NBufferBundle.MemoryReport memoryReport = new NBufferBundle.MemoryReport(); + + for (NBufferBundle.Grid grid : this.grids.values()) { + MemInstrument.Report gridUsage = grid.getMemoryUsage(); + int gridBufferCount = grid.buffers.size(); + memoryReport.gridEntries.add(new NBufferBundle.MemoryReport.GridEntry(gridUsage, gridBufferCount, grid.bufferType)); + } + + return memoryReport; + } + + public static class Access implements MemInstrument { + private final NBufferBundle.Grid grid; + private final Bounds3i bounds_bufferGrid; + private final NBufferBundle.Grid.TrackedBuffer[] buffers; + private boolean isClosed; + + private Access(@Nonnull NBufferBundle.Grid grid, @Nonnull Bounds3i bounds_bufferGrid) { + assert bounds_bufferGrid.isCorrect(); + + this.grid = grid; + this.bounds_bufferGrid = bounds_bufferGrid.clone(); + this.bounds_bufferGrid.min.y = 0; + this.bounds_bufferGrid.max.y = 40; + Vector3i boundsSize_bufferGrid = this.bounds_bufferGrid.getSize(); + int bufferCount = boundsSize_bufferGrid.x * boundsSize_bufferGrid.y * boundsSize_bufferGrid.z; + this.buffers = new NBufferBundle.Grid.TrackedBuffer[bufferCount]; + this.isClosed = false; + } + + @Nonnull + public NBufferBundle.Access.View createView(@Nonnull Bounds3i viewBounds_bufferGrid) { + assert this.bounds_bufferGrid.contains(viewBounds_bufferGrid); + + return new NBufferBundle.Access.View(this, viewBounds_bufferGrid); + } + + @Nonnull + public NBufferBundle.Access.View createView() { + return new NBufferBundle.Access.View(this, this.bounds_bufferGrid); + } + + @Nonnull + public NBufferBundle.Grid.TrackedBuffer getBuffer(@Nonnull Vector3i position_bufferGrid) { + assert !this.isClosed; + + assert this.bounds_bufferGrid.contains(position_bufferGrid); + + int index = GridUtils.toIndexFromPositionYXZ(position_bufferGrid, this.bounds_bufferGrid); + return this.buffers[index]; + } + + @Nonnull + public Bounds3i getBounds_bufferGrid() { + return this.bounds_bufferGrid.clone(); + } + + public void close() { + this.grid.accessors.remove(this); + this.isClosed = true; + Arrays.fill(this.buffers, null); + } + + @Nonnull + @Override + public MemInstrument.Report getMemoryUsage() { + long size_bytes = 8L + this.bounds_bufferGrid.getMemoryUsage().size_bytes(); + return new MemInstrument.Report(size_bytes); + } + + private void loadGrid() { + assert !this.isClosed; + + assert this.bounds_bufferGrid.min.y == 0 && this.bounds_bufferGrid.max.y == 40; + + Vector3i position_bufferGrid = this.bounds_bufferGrid.min.clone(); + position_bufferGrid.setY(0); + NBufferBundle.Grid.TrackedBuffer[] trackedBuffersOutput = new NBufferBundle.Grid.TrackedBuffer[40]; + + for (position_bufferGrid.z = this.bounds_bufferGrid.min.z; position_bufferGrid.z < this.bounds_bufferGrid.max.z; position_bufferGrid.z++) { + for (position_bufferGrid.x = this.bounds_bufferGrid.min.x; position_bufferGrid.x < this.bounds_bufferGrid.max.x; position_bufferGrid.x++) { + position_bufferGrid.setY(0); + this.grid.ensureBufferColumnExists(position_bufferGrid, trackedBuffersOutput); + int i = 0; + + for (position_bufferGrid.y = 0; position_bufferGrid.y < 40; position_bufferGrid.y++) { + position_bufferGrid.dropHash(); + int index = GridUtils.toIndexFromPositionYXZ(position_bufferGrid, this.bounds_bufferGrid); + this.buffers[index] = trackedBuffersOutput[i]; + i++; + } + } + } + } + + public static class View { + private final NBufferBundle.Access access; + private final Bounds3i bounds_bufferGrid; + + private View(@Nonnull NBufferBundle.Access access, @Nonnull Bounds3i bounds_bufferGrid) { + assert access.bounds_bufferGrid.contains(bounds_bufferGrid); + + this.access = access; + this.bounds_bufferGrid = bounds_bufferGrid; + } + + @Nonnull + public NBufferBundle.Grid.TrackedBuffer getBuffer(@Nonnull Vector3i position_bufferGrid) { + assert !this.access.isClosed; + + assert this.bounds_bufferGrid.contains(position_bufferGrid); + + return this.access.getBuffer(position_bufferGrid); + } + + @Nonnull + public Bounds3i getBounds_bufferGrid() { + return this.bounds_bufferGrid.clone(); + } + } + } + + public static class Grid implements MemInstrument { + private final NBufferType bufferType; + private final Map buffers; + private final Deque oldestColumnEntryDeque_bufferGrid; + private final int capacity; + private final List accessors; + + private Grid(@Nonnull NBufferType bufferType, int capacity) { + this.bufferType = bufferType; + this.buffers = new HashMap<>(); + this.oldestColumnEntryDeque_bufferGrid = new ArrayDeque<>(); + this.capacity = Math.max(capacity, 0); + this.accessors = new ArrayList<>(); + } + + @Nonnull + public NBufferType getBufferType() { + return this.bufferType; + } + + @Nonnull + public NBufferBundle.Access openAccess(@Nonnull Bounds3i bounds_bufferGrid) { + NBufferBundle.Access access = new NBufferBundle.Access(this, bounds_bufferGrid); + this.accessors.add(access); + access.loadGrid(); + return access; + } + + public void closeAllAccesses() { + for (int i = this.accessors.size() - 1; i >= 0; i--) { + NBufferBundle.Access access = this.accessors.get(i); + access.close(); + } + } + + @Nonnull + @Override + public MemInstrument.Report getMemoryUsage() { + long size_bytes = 68L; + size_bytes += 28L * this.buffers.size(); + size_bytes += 4L * this.buffers.size(); + size_bytes += 32L * this.buffers.size(); + + for (NBufferBundle.Grid.TrackedBuffer buffer : this.buffers.values()) { + size_bytes += buffer.getMemoryUsage().size_bytes(); + } + + size_bytes += 8L * this.accessors.size(); + + for (NBufferBundle.Access access : this.accessors) { + size_bytes += access.getMemoryUsage().size_bytes(); + } + + return new MemInstrument.Report(size_bytes); + } + + private void ensureBufferColumnExists(@Nonnull Vector3i position_bufferGrid, @Nonnull NBufferBundle.Grid.TrackedBuffer[] trackedBuffersOut) { + assert position_bufferGrid.y == 0; + + assert trackedBuffersOut.length == 40; + + NBufferBundle.Grid.TrackedBuffer buffer = this.buffers.get(position_bufferGrid); + if (buffer == null) { + this.createBufferColumn(position_bufferGrid, trackedBuffersOut); + } else { + Vector3i positionClone_bufferGrid = new Vector3i(position_bufferGrid); + + for (int i = 0; i < trackedBuffersOut.length; i++) { + positionClone_bufferGrid.setY(i + 0); + trackedBuffersOut[i] = this.buffers.get(positionClone_bufferGrid); + + assert trackedBuffersOut[i] != null; + } + } + } + + private void createBufferColumn(@Nonnull Vector3i position_bufferGrid, @Nonnull NBufferBundle.Grid.TrackedBuffer[] trackedBuffersOut) { + assert !this.buffers.containsKey(position_bufferGrid); + + assert trackedBuffersOut.length == 40; + + this.tryTrimSurplus(40); + int i = 0; + + for (int y = 0; y < 40; y++) { + Vector3i finalPosition_bufferGrid = new Vector3i(position_bufferGrid.x, y, position_bufferGrid.z); + NBufferBundle.Tracker tracker = new NBufferBundle.Tracker(); + NBuffer buffer = this.bufferType.bufferSupplier.get(); + + assert this.bufferType.isValid(buffer); + + trackedBuffersOut[i] = new NBufferBundle.Grid.TrackedBuffer(tracker, buffer); + this.buffers.put(finalPosition_bufferGrid, trackedBuffersOut[i]); + i++; + } + + Vector3i tilePosition_bufferGrid = new Vector3i(position_bufferGrid.x, 0, position_bufferGrid.z); + this.oldestColumnEntryDeque_bufferGrid.addLast(tilePosition_bufferGrid); + } + + private void tryTrimSurplus(int extraRoom) { + int surplusCount = Math.max(0, this.buffers.size() - this.capacity - extraRoom); + int surplusColumnsCount = surplusCount == 0 ? 0 : surplusCount / 40 + 1; + + for (int i = 0; i < surplusColumnsCount; i++) { + if (!this.destroyOldestBufferColumn()) { + return; + } + } + } + + private boolean destroyOldestBufferColumn() { + assert !this.oldestColumnEntryDeque_bufferGrid.isEmpty(); + + for (int i = 0; i < this.oldestColumnEntryDeque_bufferGrid.size(); i++) { + Vector3i oldest_bufferGrid = this.oldestColumnEntryDeque_bufferGrid.removeFirst(); + if (!this.isBufferColumnInAccess(oldest_bufferGrid)) { + this.removeBufferColumn(oldest_bufferGrid); + return true; + } + + this.oldestColumnEntryDeque_bufferGrid.addLast(oldest_bufferGrid); + } + + return false; + } + + private void removeBufferColumn(@Nonnull Vector3i position_bufferGrid) { + assert position_bufferGrid.y == 0; + + Vector3i removalPosition_bufferGrid = new Vector3i(position_bufferGrid); + + for (int y = 0; y < 40; y++) { + removalPosition_bufferGrid.setY(y); + this.buffers.remove(removalPosition_bufferGrid); + } + } + + private boolean isBufferColumnInAccess(@Nonnull Vector3i position_bufferGrid) { + assert position_bufferGrid.y == 0; + + for (NBufferBundle.Access access : this.accessors) { + if (access.bounds_bufferGrid.contains(position_bufferGrid)) { + return true; + } + } + + return false; + } + + public record TrackedBuffer(@Nonnull NBufferBundle.Tracker tracker, @Nonnull NBuffer buffer) implements MemInstrument { + @Nonnull + @Override + public MemInstrument.Report getMemoryUsage() { + long size_bytes = 16L + this.tracker.getMemoryUsage().size_bytes() + this.buffer.getMemoryUsage().size_bytes(); + return new MemInstrument.Report(size_bytes); + } + } + } + + public static class MemoryReport { + public final List gridEntries = new ArrayList<>(); + + public MemoryReport() { + } + + @Nonnull + @Override + public String toString() { + this.gridEntries.sort((o1, o2) -> { + if (o1.bufferType().index > o2.bufferType().index) { + return 1; + } else { + return o1.bufferType().index < o2.bufferType().index ? -1 : 0; + } + }); + StringBuilder builder = new StringBuilder(); + long total_mb = 0L; + + for (NBufferBundle.MemoryReport.GridEntry entry : this.gridEntries) { + total_mb += entry.report.size_bytes(); + } + + total_mb /= 1000000L; + builder.append("Memory Usage Report\n"); + builder.append("Buffers Memory Usage: ").append(total_mb).append(" mb\n"); + + for (NBufferBundle.MemoryReport.GridEntry entry : this.gridEntries) { + builder.append(entry.toString(1)); + } + + return builder.toString(); + } + + public record GridEntry(MemInstrument.Report report, int bufferCount, @Nonnull NBufferType bufferType) { + @Nonnull + public String toString(int indentation) { + long size_mb = this.report.size_bytes() / 1000000L; + StringBuilder builder = new StringBuilder(); + builder.append("\t".repeat(indentation)).append(this.bufferType.name + " Grid (Index ").append(this.bufferType().index).append("):\n"); + builder.append("\t".repeat(indentation + 1)).append("Memory Footprint: ").append(size_mb).append(" mb\n"); + builder.append("\t".repeat(indentation + 1)).append("Buffer Count: ").append(this.bufferCount).append("\n"); + return builder.toString(); + } + } + } + + public static class Tracker implements MemInstrument { + public final int INITIAL_STAGE_INDEX = -1; + public int stageIndex = -1; + + public Tracker() { + } + + @Nonnull + @Override + public MemInstrument.Report getMemoryUsage() { + return new MemInstrument.Report(4L); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NBuffer.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NBuffer.java new file mode 100644 index 0000000..f0c8290 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NBuffer.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers; + +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.performanceinstruments.MemInstrument; + +public abstract class NBuffer implements MemInstrument { + public NBuffer() { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NCountedPixelBuffer.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NCountedPixelBuffer.java new file mode 100644 index 0000000..87c1df9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NCountedPixelBuffer.java @@ -0,0 +1,163 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers; + +import com.hypixel.hytale.builtin.hytalegenerator.ArrayUtil; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.performanceinstruments.MemInstrument; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NCountedPixelBuffer extends NPixelBuffer { + public static final int BUFFER_SIZE_BITS = 3; + public static final Vector3i SIZE_VOXEL_GRID = new Vector3i(8, 1, 8); + public static final Bounds3i BOUNDS_VOXEL_GRID = new Bounds3i(Vector3i.ZERO, SIZE_VOXEL_GRID); + @Nonnull + private final Class pixelType; + @Nonnull + private NCountedPixelBuffer.State state; + @Nullable + private NCountedPixelBuffer.CountedArrayContents countedArrayContents; + @Nullable + private T singleValue; + + public NCountedPixelBuffer(@Nonnull Class voxelType) { + this.pixelType = voxelType; + this.state = NCountedPixelBuffer.State.EMPTY; + this.countedArrayContents = null; + this.singleValue = null; + } + + @Nullable + @Override + public T getPixelContent(@Nonnull Vector3i position) { + assert BOUNDS_VOXEL_GRID.contains(position); + + return (T)(switch (this.state) { + case SINGLE_VALUE -> this.singleValue; + case ARRAY -> this.countedArrayContents.array[index(position)]; + default -> null; + }); + } + + @Override + public void setPixelContent(@Nonnull Vector3i position, @Nullable T value) { + assert BOUNDS_VOXEL_GRID.contains(position); + + switch (this.state) { + case SINGLE_VALUE: + if (this.singleValue == value) { + return; + } + + this.switchFromSingleValueToArray(); + this.setPixelContent(position, value); + break; + case ARRAY: + this.countedArrayContents.array[index(position)] = value; + if (!this.countedArrayContents.allBiomes.contains(value)) { + this.countedArrayContents.allBiomes.add(value); + } + break; + default: + this.state = NCountedPixelBuffer.State.SINGLE_VALUE; + this.singleValue = value; + } + } + + @Nonnull + @Override + public Class getPixelType() { + return this.pixelType; + } + + @Nonnull + public List getUniqueEntries() { + switch (this.state) { + case SINGLE_VALUE: + return List.of(this.singleValue); + case ARRAY: + assert this.countedArrayContents != null; + + return this.countedArrayContents.allBiomes; + default: + return List.of(); + } + } + + public void copyFrom(@Nonnull NCountedPixelBuffer sourceBuffer) { + this.state = sourceBuffer.state; + switch (this.state) { + case SINGLE_VALUE: + this.singleValue = sourceBuffer.singleValue; + break; + case ARRAY: + this.countedArrayContents = new NCountedPixelBuffer.CountedArrayContents<>(); + this.countedArrayContents.copyFrom(sourceBuffer.countedArrayContents); + break; + default: + return; + } + } + + @Nonnull + @Override + public MemInstrument.Report getMemoryUsage() { + long size_bytes = 128L; + if (this.countedArrayContents != null) { + size_bytes += this.countedArrayContents.getMemoryUsage().size_bytes(); + } + + return new MemInstrument.Report(size_bytes); + } + + private void switchFromSingleValueToArray() { + assert this.state == NCountedPixelBuffer.State.SINGLE_VALUE; + + this.state = NCountedPixelBuffer.State.ARRAY; + this.countedArrayContents = new NCountedPixelBuffer.CountedArrayContents<>(); + Arrays.fill(this.countedArrayContents.array, this.singleValue); + this.countedArrayContents.allBiomes.add(this.singleValue); + this.singleValue = null; + } + + private static int index(@Nonnull Vector3i position) { + return position.y + position.x * SIZE_VOXEL_GRID.y + position.z * SIZE_VOXEL_GRID.y * SIZE_VOXEL_GRID.x; + } + + public static class CountedArrayContents implements MemInstrument { + private final T[] array = (T[])(new Object[NCountedPixelBuffer.SIZE_VOXEL_GRID.x + * NCountedPixelBuffer.SIZE_VOXEL_GRID.y + * NCountedPixelBuffer.SIZE_VOXEL_GRID.z]); + private final List allBiomes = new ArrayList<>(1); + + public CountedArrayContents() { + } + + public void copyFrom(@Nonnull NCountedPixelBuffer.CountedArrayContents countedArrayContents) { + ArrayUtil.copy(countedArrayContents.array, this.array); + this.allBiomes.clear(); + this.allBiomes.addAll(countedArrayContents.allBiomes); + } + + @Nonnull + @Override + public MemInstrument.Report getMemoryUsage() { + long size_bytes = 16L + 8L * this.array.length; + size_bytes += 32L; + size_bytes += 8L * this.allBiomes.size(); + return new MemInstrument.Report(size_bytes); + } + } + + private static enum State { + EMPTY, + SINGLE_VALUE, + ARRAY; + + private State() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NEntityBuffer.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NEntityBuffer.java new file mode 100644 index 0000000..f94dfe8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NEntityBuffer.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers; + +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.performanceinstruments.MemInstrument; +import com.hypixel.hytale.builtin.hytalegenerator.props.entity.EntityPlacementData; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +public class NEntityBuffer extends NBuffer { + private List entities = null; + private boolean isReference = false; + + public NEntityBuffer() { + } + + public void forEach(@Nonnull Consumer consumer) { + if (this.entities != null) { + for (EntityPlacementData entity : this.entities) { + consumer.accept(entity); + } + } + } + + public void addEntity(@Nonnull EntityPlacementData entityPlacementData) { + if (this.entities == null) { + this.entities = new ArrayList<>(); + } + + this.entities.add(entityPlacementData); + } + + @Nonnull + @Override + public MemInstrument.Report getMemoryUsage() { + long size_bytes = 1L; + if (this.entities != null) { + size_bytes += 24L + 8L * this.entities.size(); + + for (EntityPlacementData entity : this.entities) { + size_bytes += entity.getMemoryUsage().size_bytes(); + } + } + + return new MemInstrument.Report(size_bytes); + } + + public void copyFrom(@Nonnull NEntityBuffer sourceBuffer) { + this.entities = sourceBuffer.entities; + this.isReference = true; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NPixelBuffer.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NPixelBuffer.java new file mode 100644 index 0000000..50eb535 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NPixelBuffer.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers; + +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class NPixelBuffer extends NBuffer { + public static final int BUFFER_SIZE_BITS = 3; + public static final Vector3i SIZE = new Vector3i(8, 1, 8); + + public NPixelBuffer() { + } + + @Nullable + public abstract T getPixelContent(@Nonnull Vector3i var1); + + public abstract void setPixelContent(@Nonnull Vector3i var1, @Nullable T var2); + + @Nonnull + public abstract Class getPixelType(); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NSimplePixelBuffer.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NSimplePixelBuffer.java new file mode 100644 index 0000000..afdbd5f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NSimplePixelBuffer.java @@ -0,0 +1,136 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers; + +import com.hypixel.hytale.builtin.hytalegenerator.ArrayUtil; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.performanceinstruments.MemInstrument; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NSimplePixelBuffer extends NPixelBuffer { + private static final Bounds3i bounds = new Bounds3i(Vector3i.ZERO, SIZE); + @Nonnull + private final Class pixelType; + @Nonnull + private NSimplePixelBuffer.State state; + @Nullable + private NSimplePixelBuffer.ArrayContents arrayContents; + @Nullable + private T singleValue; + + public NSimplePixelBuffer(@Nonnull Class pixelType) { + this.pixelType = pixelType; + this.state = NSimplePixelBuffer.State.EMPTY; + this.arrayContents = null; + this.singleValue = null; + } + + @Nullable + @Override + public T getPixelContent(@Nonnull Vector3i position) { + assert bounds.contains(position); + + return (T)(switch (this.state) { + case SINGLE_VALUE -> this.singleValue; + case ARRAY -> this.arrayContents.array[index(position)]; + default -> null; + }); + } + + @Override + public void setPixelContent(@Nonnull Vector3i position, @Nullable T value) { + assert bounds.contains(position); + + switch (this.state) { + case SINGLE_VALUE: + if (this.singleValue == value) { + return; + } + + this.switchFromSingleValueToArray(); + this.setPixelContent(position, value); + break; + case ARRAY: + this.arrayContents.array[index(position)] = value; + break; + default: + this.state = NSimplePixelBuffer.State.SINGLE_VALUE; + this.singleValue = value; + } + } + + @Nonnull + @Override + public Class getPixelType() { + return this.pixelType; + } + + public void copyFrom(@Nonnull NSimplePixelBuffer sourceBuffer) { + this.state = sourceBuffer.state; + switch (this.state) { + case SINGLE_VALUE: + this.singleValue = sourceBuffer.singleValue; + break; + case ARRAY: + this.arrayContents = new NSimplePixelBuffer.ArrayContents<>(); + ArrayUtil.copy(sourceBuffer.arrayContents.array, this.arrayContents.array); + break; + default: + return; + } + } + + @Nonnull + @Override + public MemInstrument.Report getMemoryUsage() { + long size_bytes = 128L; + if (this.arrayContents != null) { + size_bytes += this.arrayContents.getMemoryUsage().size_bytes(); + } + + return new MemInstrument.Report(size_bytes); + } + + private void ensureContents() { + if (this.arrayContents == null) { + this.arrayContents = new NSimplePixelBuffer.ArrayContents<>(); + } + } + + private void switchFromSingleValueToArray() { + assert this.state == NSimplePixelBuffer.State.SINGLE_VALUE; + + this.state = NSimplePixelBuffer.State.ARRAY; + this.arrayContents = new NSimplePixelBuffer.ArrayContents<>(); + Arrays.fill(this.arrayContents.array, this.singleValue); + this.singleValue = null; + } + + private static int index(@Nonnull Vector3i position) { + return position.y + position.x * SIZE.y + position.z * SIZE.y * SIZE.x; + } + + public static class ArrayContents implements MemInstrument { + private final T[] array = (T[])(new Object[NPixelBuffer.SIZE.x * NPixelBuffer.SIZE.y * NPixelBuffer.SIZE.z]); + + public ArrayContents() { + } + + @Nonnull + @Override + public MemInstrument.Report getMemoryUsage() { + long size_bytes = 16L + 8L * this.array.length; + return new MemInstrument.Report(size_bytes); + } + } + + private static enum State { + EMPTY, + SINGLE_VALUE, + ARRAY; + + private State() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NVoxelBuffer.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NVoxelBuffer.java new file mode 100644 index 0000000..5134cc5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/NVoxelBuffer.java @@ -0,0 +1,160 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers; + +import com.hypixel.hytale.builtin.hytalegenerator.ArrayUtil; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.performanceinstruments.MemInstrument; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NVoxelBuffer extends NBuffer { + public static final int BUFFER_SIZE_BITS = 3; + public static final Vector3i SIZE = new Vector3i(8, 8, 8); + private static final Bounds3i bounds = new Bounds3i(Vector3i.ZERO, SIZE); + @Nonnull + private final Class voxelType; + @Nonnull + private NVoxelBuffer.State state; + @Nullable + private NVoxelBuffer.ArrayContents arrayContents; + @Nullable + private T singleValue; + @Nullable + private NVoxelBuffer referenceBuffer; + + public NVoxelBuffer(@Nonnull Class voxelType) { + this.voxelType = voxelType; + this.state = NVoxelBuffer.State.EMPTY; + this.arrayContents = null; + this.singleValue = null; + this.referenceBuffer = null; + } + + @Nullable + public T getVoxelContent(@Nonnull Vector3i position) { + assert bounds.contains(position); + + return (T)(switch (this.state) { + case SINGLE_VALUE -> this.singleValue; + case ARRAY -> this.arrayContents.array[index(position)]; + case REFERENCE -> this.referenceBuffer.getVoxelContent(position); + default -> null; + }); + } + + @Nonnull + public Class getVoxelType() { + return this.voxelType; + } + + public void setVoxelContent(@Nonnull Vector3i position, @Nullable T value) { + assert bounds.contains(position); + + switch (this.state) { + case SINGLE_VALUE: + if (this.singleValue == value) { + return; + } + + this.switchFromSingleValueToArray(); + this.setVoxelContent(position, value); + break; + case ARRAY: + this.arrayContents.array[index(position)] = value; + break; + case REFERENCE: + this.dereference(); + this.setVoxelContent(position, value); + break; + default: + this.state = NVoxelBuffer.State.SINGLE_VALUE; + this.singleValue = value; + } + } + + public void reference(@Nonnull NVoxelBuffer sourceBuffer) { + this.state = NVoxelBuffer.State.REFERENCE; + this.referenceBuffer = this.lastReference(sourceBuffer); + this.singleValue = null; + this.arrayContents = null; + } + + @Nonnull + private NVoxelBuffer lastReference(@Nonnull NVoxelBuffer sourceBuffer) { + while (sourceBuffer.state == NVoxelBuffer.State.REFERENCE) { + sourceBuffer = sourceBuffer.referenceBuffer; + } + + return sourceBuffer; + } + + @Nonnull + @Override + public MemInstrument.Report getMemoryUsage() { + long size_bytes = 128L; + size_bytes += 40L; + if (this.state == NVoxelBuffer.State.ARRAY) { + size_bytes += this.arrayContents.getMemoryUsage().size_bytes(); + } + + return new MemInstrument.Report(size_bytes); + } + + private void switchFromSingleValueToArray() { + assert this.state == NVoxelBuffer.State.SINGLE_VALUE; + + this.state = NVoxelBuffer.State.ARRAY; + this.arrayContents = new NVoxelBuffer.ArrayContents<>(); + Arrays.fill(this.arrayContents.array, this.singleValue); + this.singleValue = null; + } + + private void dereference() { + assert this.state == NVoxelBuffer.State.REFERENCE; + + this.state = this.referenceBuffer.state; + switch (this.state) { + case SINGLE_VALUE: + this.singleValue = this.referenceBuffer.singleValue; + break; + case ARRAY: + this.arrayContents = new NVoxelBuffer.ArrayContents<>(); + ArrayUtil.copy(this.referenceBuffer.arrayContents.array, this.arrayContents.array); + break; + case REFERENCE: + this.referenceBuffer = this.referenceBuffer.referenceBuffer; + break; + default: + return; + } + } + + private static int index(@Nonnull Vector3i position) { + return position.y + position.x * SIZE.y + position.z * SIZE.y * SIZE.x; + } + + public static class ArrayContents implements MemInstrument { + private final T[] array = (T[])(new Object[NVoxelBuffer.SIZE.x * NVoxelBuffer.SIZE.y * NVoxelBuffer.SIZE.z]); + + public ArrayContents() { + } + + @Nonnull + @Override + public MemInstrument.Report getMemoryUsage() { + long size_bytes = 16L + 8L * this.array.length; + return new MemInstrument.Report(size_bytes); + } + } + + private static enum State { + EMPTY, + SINGLE_VALUE, + ARRAY, + REFERENCE; + + private State() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/type/NBufferType.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/type/NBufferType.java new file mode 100644 index 0000000..23946ac --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/type/NBufferType.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type; + +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NBuffer; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class NBufferType { + public final Class bufferClass; + public final int index; + public final Supplier bufferSupplier; + public final String name; + + public NBufferType(@Nonnull String name, int index, @Nonnull Class bufferClass, @Nonnull Supplier bufferSupplier) { + this.name = name; + this.index = index; + this.bufferClass = bufferClass; + this.bufferSupplier = bufferSupplier; + } + + @Override + public boolean equals(Object o) { + return !(o instanceof NBufferType that) + ? false + : this.index == that.index && this.bufferClass.equals(that.bufferClass) && this.bufferSupplier.equals(that.bufferSupplier); + } + + public boolean isValidType(@Nonnull Class bufferClass) { + return this.bufferClass.equals(bufferClass); + } + + public boolean isValid(@Nonnull NBuffer buffer) { + return this.bufferClass.isInstance(buffer); + } + + @Override + public int hashCode() { + int result = this.bufferClass.hashCode(); + result = 31 * result + this.index; + return 31 * result + this.bufferSupplier.hashCode(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/type/NParametrizedBufferType.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/type/NParametrizedBufferType.java new file mode 100644 index 0000000..61e4350 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/bufferbundle/buffers/type/NParametrizedBufferType.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type; + +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NBuffer; +import java.util.Objects; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class NParametrizedBufferType extends NBufferType { + @Nonnull + public final Class parameterClass; + + public NParametrizedBufferType( + @Nonnull String name, int index, @Nonnull Class bufferClass, @Nonnull Class parameterClass, @Nonnull Supplier bufferSupplier + ) { + super(name, index, bufferClass, bufferSupplier); + this.parameterClass = parameterClass; + } + + public boolean isValidType(@Nonnull Class bufferClass, @Nonnull Class parameterClass) { + return this.bufferClass.equals(bufferClass) && this.parameterClass.equals(parameterClass); + } + + @Override + public boolean isValid(@Nonnull NBuffer buffer) { + return this.bufferClass.isInstance(buffer); + } + + @Override + public boolean equals(Object o) { + if (o instanceof NParametrizedBufferType that) { + return !super.equals(o) ? false : Objects.equals(this.parameterClass, that.parameterClass); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), this.parameterClass); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/containers/FloatContainer3d.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/containers/FloatContainer3d.java new file mode 100644 index 0000000..bdfff46 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/containers/FloatContainer3d.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.containers; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.GridUtils; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class FloatContainer3d { + private final Bounds3i bounds_voxelGrid; + private final Vector3i size_voxelGrid; + private final float[] data; + private final float outOfBoundsValue; + + public FloatContainer3d(@Nonnull Bounds3i bounds_voxelGrid, float outOfBoundsValue) { + this.bounds_voxelGrid = bounds_voxelGrid.clone(); + this.size_voxelGrid = bounds_voxelGrid.getSize(); + this.data = new float[this.size_voxelGrid.x * this.size_voxelGrid.y * this.size_voxelGrid.z]; + this.outOfBoundsValue = outOfBoundsValue; + } + + public float get(@Nonnull Vector3i position_voxelGrid) { + if (!this.bounds_voxelGrid.contains(position_voxelGrid)) { + return this.outOfBoundsValue; + } else { + int index = GridUtils.toIndexFromPositionYXZ(position_voxelGrid, this.bounds_voxelGrid); + return this.data[index]; + } + } + + @Nonnull + public Bounds3i getBounds_voxelGrid() { + return this.bounds_voxelGrid; + } + + public void set(@Nonnull Vector3i position_voxelGrid, float value) { + assert this.bounds_voxelGrid.contains(position_voxelGrid); + + int index = GridUtils.toIndexFromPositionYXZ(position_voxelGrid, this.bounds_voxelGrid); + this.data[index] = value; + } + + public void moveMinTo(@Nonnull Vector3i min_voxelGrid) { + Vector3i oldMin_voxelGrid = this.bounds_voxelGrid.min.clone().scale(-1); + this.bounds_voxelGrid.offset(oldMin_voxelGrid); + this.bounds_voxelGrid.offset(min_voxelGrid); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/performanceinstruments/MemInstrument.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/performanceinstruments/MemInstrument.java new file mode 100644 index 0000000..07cde36 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/performanceinstruments/MemInstrument.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.performanceinstruments; + +import javax.annotation.Nonnull; + +public interface MemInstrument { + long BYTES_IN_MEGABYTES = 1000000L; + long INT_SIZE = 4L; + long DOUBLE_SIZE = 8L; + long BOOLEAN_SIZE = 1L; + long OBJECT_REFERENCE_SIZE = 8L; + long OBJECT_HEADER_SIZE = 16L; + long ARRAY_HEADER_SIZE = 16L; + long CLASS_OBJECT_SIZE = 128L; + long ARRAYLIST_OBJECT_SIZE = 24L; + long VECTOR3I_SIZE = 28L; + long VECTOR3D_SIZE = 40L; + long HASHMAP_ENTRY_SIZE = 32L; + + @Nonnull + MemInstrument.Report getMemoryUsage(); + + public record Report(long size_bytes) { + public Report(long size_bytes) { + assert size_bytes >= 0L; + + this.size_bytes = size_bytes; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/performanceinstruments/TimeInstrument.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/performanceinstruments/TimeInstrument.java new file mode 100644 index 0000000..34f461b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/performanceinstruments/TimeInstrument.java @@ -0,0 +1,150 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.performanceinstruments; + +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class TimeInstrument { + private int sampleCount; + @Nonnull + private TimeInstrument.Probe totalProbe; + private String header; + + public TimeInstrument(@Nonnull String header) { + this.header = header; + this.sampleCount = 0; + this.totalProbe = null; + } + + public void takeSample(@Nonnull TimeInstrument.Probe probe) { + if (this.totalProbe == null) { + this.totalProbe = probe; + this.sampleCount++; + } else if (!probe.isCompatibleForAddition(this.totalProbe)) { + LoggerUtil.getLogger().warning("Discarded a probe because of mismatched structures."); + + assert false; + } else { + this.sampleCount++; + this.totalProbe.add(probe); + } + } + + @Nonnull + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("Performance Report\n"); + s.append("Sample Count: ").append(this.sampleCount).append("\n"); + s.append(this.header).append("\n"); + s.append(this.toString(0, this.totalProbe)); + return s.toString(); + } + + @Nonnull + private String toString(int indentation, @Nonnull TimeInstrument.Probe probe) { + long ms = probe.getTotalTime() / this.sampleCount; + ms /= 1000000L; + StringBuilder s = new StringBuilder(); + s.append("\t".repeat(indentation)); + s.append(probe.getName()).append(": "); + s.append(Long.toString(ms)).append(" ms"); + s.append("\n"); + List childProbes = probe.getProbes(); + + for (int i = 0; i < childProbes.size(); i++) { + s.append(this.toString(indentation + 1, childProbes.get(i))); + } + + return s.toString(); + } + + public static class Probe { + private final String name; + private long startTime; + private long totalTime; + private TimeInstrument.Probe.State state; + private List probes; + + public Probe(@Nonnull String name) { + this.name = name; + this.state = TimeInstrument.Probe.State.NOT_STARTED; + this.probes = new ArrayList<>(1); + } + + @Nonnull + public TimeInstrument.Probe start() { + this.startTime = System.nanoTime(); + this.state = TimeInstrument.Probe.State.STARTED; + return this; + } + + @Nonnull + public TimeInstrument.Probe stop() { + assert this.state == TimeInstrument.Probe.State.STARTED; + + this.state = TimeInstrument.Probe.State.COMPLETED; + this.totalTime = System.nanoTime() - this.startTime; + return this; + } + + public long getTotalTime() { + assert this.state == TimeInstrument.Probe.State.COMPLETED; + + return this.totalTime; + } + + @Nonnull + public String getName() { + return this.name; + } + + @Nonnull + public List getProbes() { + return this.probes; + } + + @Nonnull + public TimeInstrument.Probe createProbe(@Nonnull String name) { + TimeInstrument.Probe probe = new TimeInstrument.Probe(name); + this.probes.add(probe); + return probe; + } + + public boolean isCompatibleForAddition(@Nonnull TimeInstrument.Probe other) { + if (this.probes.size() == other.probes.size() && this.name.equals(other.name)) { + for (int i = 0; i < this.probes.size(); i++) { + if (!this.probes.get(i).isCompatibleForAddition(other.probes.get(i))) { + return false; + } + } + + return true; + } else { + return false; + } + } + + public void add(@Nonnull TimeInstrument.Probe probe) { + assert this.state == TimeInstrument.Probe.State.COMPLETED; + + assert this.isCompatibleForAddition(probe); + + this.totalTime = this.totalTime + probe.getTotalTime(); + + for (int i = 0; i < this.probes.size(); i++) { + this.probes.get(i).add(probe.probes.get(i)); + } + } + + private static enum State { + NOT_STARTED, + STARTED, + COMPLETED; + + private State() { + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NBiomeDistanceStage.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NBiomeDistanceStage.java new file mode 100644 index 0000000..bf206f8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NBiomeDistanceStage.java @@ -0,0 +1,270 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages; + +import com.hypixel.hytale.builtin.hytalegenerator.biome.BiomeType; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.GridUtils; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.NBufferBundle; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NCountedPixelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NPixelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NSimplePixelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NParametrizedBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.NPixelBufferView; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NBiomeDistanceStage implements NStage { + private static final double ORIGIN_REACH = 1.0; + private static final double BUFFER_DIAGONAL_VOXEL_GRID = Math.sqrt(NPixelBuffer.SIZE.x * NPixelBuffer.SIZE.x + NPixelBuffer.SIZE.z * NPixelBuffer.SIZE.z); + public static final double DEFAULT_DISTANCE_TO_BIOME_EDGE = Double.MAX_VALUE; + public static final Class biomeBufferClass = NCountedPixelBuffer.class; + public static final Class biomeTypeClass = BiomeType.class; + public static final Class biomeDistanceBufferClass = NSimplePixelBuffer.class; + public static final Class biomeDistanceClass = NBiomeDistanceStage.BiomeDistanceEntries.class; + private final NParametrizedBufferType biomeInputBufferType; + private final NParametrizedBufferType biomeDistanceOutputBufferType; + private final String stageName; + private final double maxDistance_voxelGrid; + private final int maxDistance_bufferGrid; + private final Bounds3i inputBounds_bufferGrid; + + public NBiomeDistanceStage( + @Nonnull String stageName, + @Nonnull NParametrizedBufferType biomeInputBufferType, + @Nonnull NParametrizedBufferType biomeDistanceOutputBufferType, + double maxDistance_voxelGrid + ) { + assert maxDistance_voxelGrid >= 0.0; + + this.stageName = stageName; + this.biomeInputBufferType = biomeInputBufferType; + this.biomeDistanceOutputBufferType = biomeDistanceOutputBufferType; + this.maxDistance_voxelGrid = maxDistance_voxelGrid; + this.maxDistance_bufferGrid = GridUtils.toBufferDistanceInclusive_fromVoxelDistance((int)Math.ceil(maxDistance_voxelGrid)); + Bounds3i inputBounds_voxelGrid = GridUtils.createBounds_fromRadius_originVoxelInclusive((int)Math.ceil(maxDistance_voxelGrid)); + Bounds3i bufferColumnBounds_voxelGrid = GridUtils.createColumnBounds_voxelGrid(new Vector3i(), 0, 1); + inputBounds_voxelGrid.stack(bufferColumnBounds_voxelGrid); + this.inputBounds_bufferGrid = GridUtils.createBufferBoundsInclusive_fromVoxelBounds(inputBounds_voxelGrid); + GridUtils.setBoundsYToWorldHeight_bufferGrid(this.inputBounds_bufferGrid); + } + + @Override + public void run(@Nonnull NStage.Context context) { + NBufferBundle.Access.View biomeAccess = context.bufferAccess.get(this.biomeInputBufferType); + NPixelBufferView biomeSpace = new NPixelBufferView<>(biomeAccess, biomeTypeClass); + NBufferBundle.Access.View biomeDistanceAccess = context.bufferAccess.get(this.biomeDistanceOutputBufferType); + NPixelBufferView biomeDistanceSpace = new NPixelBufferView<>(biomeDistanceAccess, biomeDistanceClass); + Vector3i position_voxelGrid = new Vector3i(); + + for (position_voxelGrid.x = biomeDistanceSpace.minX(); position_voxelGrid.x < biomeDistanceSpace.maxX(); position_voxelGrid.x++) { + for (position_voxelGrid.z = biomeDistanceSpace.minZ(); position_voxelGrid.z < biomeDistanceSpace.maxZ(); position_voxelGrid.z++) { + NBiomeDistanceStage.BiomeDistanceEntries distanceEntries = this.createDistanceTracker(biomeAccess, biomeSpace, position_voxelGrid); + biomeDistanceSpace.set(distanceEntries, position_voxelGrid); + } + } + } + + @Nonnull + private NBiomeDistanceStage.BiomeDistanceEntries createDistanceTracker( + @Nonnull NBufferBundle.Access.View biomeAccess, @Nonnull NPixelBufferView biomeSpace, @Nonnull Vector3i targetPosition_voxelGrid + ) { + NBiomeDistanceStage.BiomeDistanceCounter counter = new NBiomeDistanceStage.BiomeDistanceCounter(); + Vector3i position_bufferGrid = new Vector3i(); + Bounds3i scanBounds_voxelGrid = GridUtils.createBounds_fromRadius_originVoxelInclusive((int)Math.ceil(this.maxDistance_voxelGrid)); + scanBounds_voxelGrid.offset(targetPosition_voxelGrid); + Bounds3i scanBounds_bufferGrid = GridUtils.createBufferBoundsInclusive_fromVoxelBounds(scanBounds_voxelGrid); + + for (position_bufferGrid.x = scanBounds_bufferGrid.min.x; position_bufferGrid.x < scanBounds_bufferGrid.max.x; position_bufferGrid.x++) { + for (position_bufferGrid.z = scanBounds_bufferGrid.min.z; position_bufferGrid.z < scanBounds_bufferGrid.max.z; position_bufferGrid.z++) { + double distanceToBuffer_voxelGrid = distanceToBuffer_voxelGrid(targetPosition_voxelGrid, position_bufferGrid); + distanceToBuffer_voxelGrid = Math.max(distanceToBuffer_voxelGrid - 1.0, 0.0); + if (!(distanceToBuffer_voxelGrid > this.maxDistance_voxelGrid)) { + NCountedPixelBuffer biomeBuffer = (NCountedPixelBuffer)biomeAccess.getBuffer(position_bufferGrid).buffer(); + List uniqueBiomeTypes = biomeBuffer.getUniqueEntries(); + + assert !uniqueBiomeTypes.isEmpty(); + + if (!allBiomesAreCountedAndFarther(counter, uniqueBiomeTypes, distanceToBuffer_voxelGrid)) { + if (uniqueBiomeTypes.size() == 1) { + if (!(distanceToBuffer_voxelGrid > this.maxDistance_voxelGrid)) { + counter.accountFor(uniqueBiomeTypes.getFirst(), distanceToBuffer_voxelGrid); + } + } else { + Bounds3i bufferBounds_voxelGrid = GridUtils.createColumnBounds_voxelGrid(position_bufferGrid, 0, 1); + Vector3i columnPosition_voxelGrid = new Vector3i(); + + for (columnPosition_voxelGrid.x = bufferBounds_voxelGrid.min.x; + columnPosition_voxelGrid.x < bufferBounds_voxelGrid.max.x; + columnPosition_voxelGrid.x++ + ) { + for (columnPosition_voxelGrid.z = bufferBounds_voxelGrid.min.z; + columnPosition_voxelGrid.z < bufferBounds_voxelGrid.max.z; + columnPosition_voxelGrid.z++ + ) { + double distanceToColumn_voxelGrid = Calculator.distance( + columnPosition_voxelGrid.x, columnPosition_voxelGrid.z, targetPosition_voxelGrid.x, targetPosition_voxelGrid.z + ); + distanceToColumn_voxelGrid = Math.max(distanceToColumn_voxelGrid - 1.0, 0.0); + if (!(distanceToColumn_voxelGrid > this.maxDistance_voxelGrid)) { + BiomeType biomeType = biomeSpace.getContent(columnPosition_voxelGrid); + + assert biomeType != null; + + counter.accountFor(biomeType, distanceToColumn_voxelGrid); + } + } + } + } + } + } + } + } + + return new NBiomeDistanceStage.BiomeDistanceEntries(counter.entries); + } + + @Nonnull + @Override + public Map getInputTypesAndBounds_bufferGrid() { + return Map.of(this.biomeInputBufferType, this.inputBounds_bufferGrid); + } + + @Nonnull + @Override + public List getOutputTypes() { + return List.of(this.biomeDistanceOutputBufferType); + } + + @Nonnull + @Override + public String getName() { + return this.stageName; + } + + public static double distanceToBuffer_voxelGrid(@Nonnull Vector3i position_voxelGrid, @Nonnull Vector3i position_bufferGrid) { + assert (double)position_voxelGrid.y == 0.0; + + assert (double)position_bufferGrid.y == 0.0; + + Vector3i bufferAtPosition_bufferGrid = position_voxelGrid.clone(); + GridUtils.toBufferGrid_fromVoxelGrid(bufferAtPosition_bufferGrid); + if (bufferAtPosition_bufferGrid.x == position_bufferGrid.x && bufferAtPosition_bufferGrid.z == position_bufferGrid.z) { + return 0.0; + } else { + int cornerShift = NCountedPixelBuffer.SIZE_VOXEL_GRID.x - 1; + Vector3i corner00 = position_bufferGrid.clone(); + GridUtils.toVoxelGrid_fromBufferGrid(corner00); + Vector3i corner01 = new Vector3i(corner00); + corner01.z += cornerShift; + if (position_voxelGrid.x >= corner00.x && position_voxelGrid.x <= corner00.x + cornerShift) { + return Math.min(Math.abs(position_voxelGrid.z - corner00.z), Math.abs(position_voxelGrid.z - corner01.z)); + } else { + Vector3i corner10 = new Vector3i(corner00); + corner10.x += cornerShift; + if (position_voxelGrid.z >= corner00.z && position_voxelGrid.z <= corner00.z + cornerShift) { + return Math.min(Math.abs(position_voxelGrid.x - corner00.x), Math.abs(position_voxelGrid.x - corner10.x)); + } else if (position_voxelGrid.x < corner00.x && position_voxelGrid.z < corner00.z) { + return position_voxelGrid.distanceTo(corner00); + } else if (position_voxelGrid.x < corner01.x && position_voxelGrid.z > corner01.z) { + return position_voxelGrid.distanceTo(corner01); + } else if (position_voxelGrid.x > corner10.x && position_voxelGrid.z < corner10.z) { + return position_voxelGrid.distanceTo(corner10); + } else { + Vector3i corner11 = new Vector3i(corner10.x, 0, corner01.z); + return position_voxelGrid.distanceTo(corner11); + } + } + } + } + + private static boolean allBiomesAreCountedAndFarther( + @Nonnull NBiomeDistanceStage.BiomeDistanceCounter counter, @Nonnull List uniqueBiomes, double distanceToBuffer_voxelGrid + ) { + for (BiomeType biomeType : uniqueBiomes) { + if (counter.isCloserThanCounted(biomeType, distanceToBuffer_voxelGrid)) { + return false; + } + } + + return true; + } + + private static class BiomeDistanceCounter { + @Nonnull + final List entries = new ArrayList<>(3); + @Nullable + NBiomeDistanceStage.BiomeDistanceEntry cachedEntry = null; + + BiomeDistanceCounter() { + } + + boolean isCloserThanCounted(@Nonnull BiomeType biomeType, double distance_voxelGrid) { + for (NBiomeDistanceStage.BiomeDistanceEntry entry : this.entries) { + if (entry.biomeType == biomeType) { + return distance_voxelGrid < entry.distance_voxelGrid; + } + } + + return true; + } + + void accountFor(@Nonnull BiomeType biomeType, double distance_voxelGrid) { + if (this.cachedEntry != null && this.cachedEntry.biomeType == biomeType) { + if (!(this.cachedEntry.distance_voxelGrid <= distance_voxelGrid)) { + this.cachedEntry.distance_voxelGrid = distance_voxelGrid; + } + } else { + for (NBiomeDistanceStage.BiomeDistanceEntry entry : this.entries) { + if (entry.biomeType == biomeType) { + this.cachedEntry = entry; + if (entry.distance_voxelGrid <= distance_voxelGrid) { + return; + } + + entry.distance_voxelGrid = distance_voxelGrid; + return; + } + } + + NBiomeDistanceStage.BiomeDistanceEntry entryx = new NBiomeDistanceStage.BiomeDistanceEntry(); + entryx.biomeType = biomeType; + entryx.distance_voxelGrid = distance_voxelGrid; + this.entries.add(entryx); + this.cachedEntry = entryx; + } + } + } + + public static class BiomeDistanceEntries { + public final List entries; + + public BiomeDistanceEntries(@Nonnull List entries) { + this.entries = entries; + } + + public double distanceToClosestOtherBiome(@Nonnull BiomeType thisBiome) { + double smallestDistance = Double.MAX_VALUE; + + for (NBiomeDistanceStage.BiomeDistanceEntry entry : this.entries) { + if (entry.biomeType != thisBiome) { + smallestDistance = Math.min(smallestDistance, entry.distance_voxelGrid); + } + } + + return smallestDistance; + } + } + + public static class BiomeDistanceEntry { + public BiomeType biomeType; + public double distance_voxelGrid; + + public BiomeDistanceEntry() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NBiomeStage.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NBiomeStage.java new file mode 100644 index 0000000..4261004 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NBiomeStage.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages; + +import com.hypixel.hytale.builtin.hytalegenerator.biome.BiomeType; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.BiCarta; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.NBufferBundle; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NCountedPixelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NParametrizedBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.NPixelBufferView; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +public class NBiomeStage implements NStage { + public static final Class bufferClass = NCountedPixelBuffer.class; + public static final Class biomeTypeClass = BiomeType.class; + private final NParametrizedBufferType biomeOutputBufferType; + private final String stageName; + private BiCarta biomeCarta; + + public NBiomeStage(@Nonnull String stageName, @Nonnull NParametrizedBufferType biomeOutputBufferType, @Nonnull BiCarta biomeCarta) { + this.stageName = stageName; + this.biomeOutputBufferType = biomeOutputBufferType; + this.biomeCarta = biomeCarta; + } + + @Override + public void run(@Nonnull NStage.Context context) { + NBufferBundle.Access.View biomeAccess = context.bufferAccess.get(this.biomeOutputBufferType); + NPixelBufferView biomeSpace = new NPixelBufferView<>(biomeAccess, biomeTypeClass); + + for (int x = biomeSpace.minX(); x < biomeSpace.maxX(); x++) { + for (int z = biomeSpace.minZ(); z < biomeSpace.maxZ(); z++) { + BiomeType biome = this.biomeCarta.apply(x, z, context.workerId); + biomeSpace.set(biome, x, 0, z); + } + } + } + + @Nonnull + @Override + public Map getInputTypesAndBounds_bufferGrid() { + return Map.of(); + } + + @Nonnull + @Override + public List getOutputTypes() { + return List.of(this.biomeOutputBufferType); + } + + @Nonnull + @Override + public String getName() { + return this.stageName; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NEnvironmentStage.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NEnvironmentStage.java new file mode 100644 index 0000000..203c9bb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NEnvironmentStage.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages; + +import com.hypixel.hytale.builtin.hytalegenerator.biome.BiomeType; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.environmentproviders.EnvironmentProvider; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.GridUtils; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.NBufferBundle; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NCountedPixelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NVoxelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NParametrizedBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.NPixelBufferView; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.NVoxelBufferView; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +public class NEnvironmentStage implements NStage { + public static final Class biomeBufferClass = NCountedPixelBuffer.class; + public static final Class biomeTypeClass = BiomeType.class; + public static final Class environmentBufferClass = NVoxelBuffer.class; + public static final Class environmentClass = Integer.class; + private final NParametrizedBufferType biomeInputBufferType; + private final NParametrizedBufferType environmentOutputBufferType; + private final Bounds3i inputBounds_bufferGrid; + private final String stageName; + + public NEnvironmentStage( + @Nonnull String stageName, @Nonnull NParametrizedBufferType biomeInputBufferType, @Nonnull NParametrizedBufferType environmentOutputBufferType + ) { + assert biomeInputBufferType.isValidType(biomeBufferClass, biomeTypeClass); + + assert environmentOutputBufferType.isValidType(environmentBufferClass, environmentClass); + + this.biomeInputBufferType = biomeInputBufferType; + this.environmentOutputBufferType = environmentOutputBufferType; + this.stageName = stageName; + this.inputBounds_bufferGrid = GridUtils.createUnitBounds3i(Vector3i.ZERO); + } + + @Override + public void run(@Nonnull NStage.Context context) { + NBufferBundle.Access.View biomeAccess = context.bufferAccess.get(this.biomeInputBufferType); + NPixelBufferView biomeSpace = new NPixelBufferView<>(biomeAccess, biomeTypeClass); + NBufferBundle.Access.View environmentAccess = context.bufferAccess.get(this.environmentOutputBufferType); + NVoxelBufferView environmentSpace = new NVoxelBufferView<>(environmentAccess, environmentClass); + Bounds3i outputBounds_voxelGrid = environmentSpace.getBounds(); + Vector3i position_voxelGrid = new Vector3i(outputBounds_voxelGrid.min); + EnvironmentProvider.Context tintContext = new EnvironmentProvider.Context(position_voxelGrid, context.workerId); + + for (position_voxelGrid.x = outputBounds_voxelGrid.min.x; position_voxelGrid.x < outputBounds_voxelGrid.max.x; position_voxelGrid.x++) { + for (position_voxelGrid.z = outputBounds_voxelGrid.min.z; position_voxelGrid.z < outputBounds_voxelGrid.max.z; position_voxelGrid.z++) { + BiomeType biome = biomeSpace.getContent(position_voxelGrid.x, 0, position_voxelGrid.z); + + assert biome != null; + + EnvironmentProvider environmentProvider = biome.getEnvironmentProvider(); + + for (position_voxelGrid.y = outputBounds_voxelGrid.min.y; position_voxelGrid.y < outputBounds_voxelGrid.max.y; position_voxelGrid.y++) { + int environment = environmentProvider.getValue(tintContext); + environmentSpace.set(environment, position_voxelGrid); + } + } + } + } + + @Nonnull + @Override + public Map getInputTypesAndBounds_bufferGrid() { + return Map.of(this.biomeInputBufferType, this.inputBounds_bufferGrid); + } + + @Nonnull + @Override + public List getOutputTypes() { + return List.of(this.environmentOutputBufferType); + } + + @Nonnull + @Override + public String getName() { + return this.stageName; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NPropStage.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NPropStage.java new file mode 100644 index 0000000..6dd1c23 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NPropStage.java @@ -0,0 +1,215 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages; + +import com.hypixel.hytale.builtin.hytalegenerator.PropField; +import com.hypixel.hytale.builtin.hytalegenerator.biome.BiomeType; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3d; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.GridUtils; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.NBufferBundle; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NCountedPixelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NEntityBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NSimplePixelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NVoxelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NParametrizedBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.NEntityBufferView; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.NPixelBufferView; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.NVoxelBufferView; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.builtin.hytalegenerator.props.ScanResult; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NPropStage implements NStage { + public static final double DEFAULT_BACKGROUND_DENSITY = 0.0; + public static final Class biomeBufferClass = NCountedPixelBuffer.class; + public static final Class biomeTypeClass = BiomeType.class; + public static final Class biomeDistanceBufferClass = NSimplePixelBuffer.class; + public static final Class biomeDistanceClass = NBiomeDistanceStage.BiomeDistanceEntries.class; + public static final Class materialBufferClass = NVoxelBuffer.class; + public static final Class materialClass = Material.class; + public static final Class entityBufferClass = NEntityBuffer.class; + private final NParametrizedBufferType biomeInputBufferType; + private final NParametrizedBufferType biomeDistanceInputBufferType; + private final NParametrizedBufferType materialInputBufferType; + private final NBufferType entityInputBufferType; + private final NParametrizedBufferType materialOutputBufferType; + private final NBufferType entityOutputBufferType; + private final Bounds3i inputBounds_bufferGrid; + private final Bounds3i inputBounds_voxelGrid; + private final String stageName; + private final MaterialCache materialCache; + private final int runtimeIndex; + + public NPropStage( + @Nonnull String stageName, + @Nonnull NParametrizedBufferType biomeInputBufferType, + @Nonnull NParametrizedBufferType biomeDistanceInputBufferType, + @Nonnull NParametrizedBufferType materialInputBufferType, + @Nullable NBufferType entityInputBufferType, + @Nonnull NParametrizedBufferType materialOutputBufferType, + @Nonnull NBufferType entityOutputBufferType, + @Nonnull MaterialCache materialCache, + @Nonnull List expectedBiomes, + int runtimeIndex + ) { + assert biomeInputBufferType.isValidType(biomeBufferClass, biomeTypeClass); + + assert biomeDistanceInputBufferType.isValidType(biomeDistanceBufferClass, biomeDistanceClass); + + assert materialInputBufferType.isValidType(materialBufferClass, materialClass); + + assert entityInputBufferType == null || entityInputBufferType.isValidType(entityBufferClass); + + assert materialOutputBufferType.isValidType(materialBufferClass, materialClass); + + assert entityOutputBufferType.isValidType(entityBufferClass); + + this.biomeInputBufferType = biomeInputBufferType; + this.biomeDistanceInputBufferType = biomeDistanceInputBufferType; + this.materialInputBufferType = materialInputBufferType; + this.entityInputBufferType = entityInputBufferType; + this.materialOutputBufferType = materialOutputBufferType; + this.entityOutputBufferType = entityOutputBufferType; + this.stageName = stageName; + this.materialCache = materialCache; + this.runtimeIndex = runtimeIndex; + this.inputBounds_voxelGrid = new Bounds3i(); + Vector3i range = new Vector3i(); + + for (BiomeType biome : expectedBiomes) { + for (PropField propField : biome.getPropFields()) { + if (propField.getRuntime() == this.runtimeIndex) { + for (Prop prop : propField.getPropDistribution().getAllPossibleProps()) { + Vector3i readRange_voxelGrid = prop.getContextDependency().getReadRange(); + Vector3i writeRange_voxelGrid = prop.getContextDependency().getWriteRange(); + range.x = readRange_voxelGrid.x + writeRange_voxelGrid.x; + range.y = readRange_voxelGrid.y + writeRange_voxelGrid.y; + range.z = readRange_voxelGrid.z + writeRange_voxelGrid.z; + this.inputBounds_voxelGrid.encompass(range.clone().add(Vector3i.ALL_ONES)); + range.scale(-1); + this.inputBounds_voxelGrid.encompass(range); + } + } + } + } + + this.inputBounds_voxelGrid.min.y = 0; + this.inputBounds_voxelGrid.max.y = 320; + this.inputBounds_bufferGrid = GridUtils.createBufferBoundsInclusive_fromVoxelBounds(this.inputBounds_voxelGrid); + GridUtils.setBoundsYToWorldHeight_bufferGrid(this.inputBounds_bufferGrid); + } + + @Override + public void run(@Nonnull NStage.Context context) { + NBufferBundle.Access.View biomeAccess = context.bufferAccess.get(this.biomeInputBufferType); + NPixelBufferView biomeInputSpace = new NPixelBufferView<>(biomeAccess, biomeTypeClass); + NBufferBundle.Access.View biomeDistanceAccess = context.bufferAccess.get(this.biomeDistanceInputBufferType); + NPixelBufferView biomeDistanceSpace = new NPixelBufferView<>(biomeDistanceAccess, biomeDistanceClass); + NBufferBundle.Access.View materialInputAccess = context.bufferAccess.get(this.materialInputBufferType); + NVoxelBufferView materialInputSpace = new NVoxelBufferView<>(materialInputAccess, materialClass); + NBufferBundle.Access.View materialOutputAccess = context.bufferAccess.get(this.materialOutputBufferType); + NVoxelBufferView materialOutputSpace = new NVoxelBufferView<>(materialOutputAccess, materialClass); + NBufferBundle.Access.View entityOutputAccess = context.bufferAccess.get(this.entityOutputBufferType); + NEntityBufferView entityOutputSpace = new NEntityBufferView(entityOutputAccess); + Bounds3i localOutputBounds_voxelGrid = materialOutputSpace.getBounds(); + Bounds3i localInputBounds_voxelGrid = this.inputBounds_voxelGrid.clone(); + Bounds3i absoluteOutputBounds_voxelGrid = localOutputBounds_voxelGrid.clone(); + absoluteOutputBounds_voxelGrid.offset(localOutputBounds_voxelGrid.min.clone().scale(-1)); + localInputBounds_voxelGrid.stack(absoluteOutputBounds_voxelGrid); + localInputBounds_voxelGrid.offset(localOutputBounds_voxelGrid.min); + localInputBounds_voxelGrid.min.y = 0; + localInputBounds_voxelGrid.max.y = 320; + Bounds3d localInputBoundsDouble_voxelGrid = localInputBounds_voxelGrid.toBounds3d(); + materialOutputSpace.copyFrom(materialInputSpace); + if (this.entityInputBufferType != null) { + NBufferBundle.Access.View entityInputAccess = context.bufferAccess.get(this.entityInputBufferType); + NEntityBufferView entityInputSpace = new NEntityBufferView(entityInputAccess); + entityOutputSpace.copyFrom(entityInputSpace); + } + + HashSet biomesInBuffer = new HashSet<>(); + + for (int x = localInputBounds_voxelGrid.min.x; x < localInputBounds_voxelGrid.max.x; x++) { + for (int z = localInputBounds_voxelGrid.min.z; z < localInputBounds_voxelGrid.max.z; z++) { + biomesInBuffer.add(biomeInputSpace.getContent(x, 0, z)); + } + } + + Map propFieldBiomeMap = new HashMap<>(); + + for (BiomeType biome : biomesInBuffer) { + for (PropField propField : biome.getPropFields()) { + if (propField.getRuntime() == this.runtimeIndex) { + propFieldBiomeMap.put(propField, biome); + } + } + } + + for (Entry entry : propFieldBiomeMap.entrySet()) { + PropField propFieldx = entry.getKey(); + BiomeType biome = entry.getValue(); + PositionProvider positionProvider = propFieldx.getPositionProvider(); + Consumer positionsConsumer = position -> { + if (localInputBoundsDouble_voxelGrid.contains(position)) { + Vector3i positionInt_voxelGrid = position.toVector3i(); + BiomeType biomeAtPosition = biomeInputSpace.getContent(positionInt_voxelGrid.x, 0, positionInt_voxelGrid.z); + if (biomeAtPosition == biome) { + Vector3i position2d_voxelGrid = positionInt_voxelGrid.clone(); + position2d_voxelGrid.setY(0); + double distanceToBiomeEdge = biomeDistanceSpace.getContent(position2d_voxelGrid).distanceToClosestOtherBiome(biomeAtPosition); + Prop prop = propField.getPropDistribution().propAt(position, context.workerId, distanceToBiomeEdge); + Bounds3i propWriteBounds = prop.getWriteBounds().clone(); + propWriteBounds.offset(positionInt_voxelGrid); + if (propWriteBounds.intersects(localOutputBounds_voxelGrid)) { + ScanResult scanResult = prop.scan(positionInt_voxelGrid, materialInputSpace, context.workerId); + Prop.Context propContext = new Prop.Context(scanResult, materialOutputSpace, entityOutputSpace, context.workerId, distanceToBiomeEdge); + prop.place(propContext); + } + } + } + }; + PositionProvider.Context positionsContext = new PositionProvider.Context( + localInputBoundsDouble_voxelGrid.min, localInputBoundsDouble_voxelGrid.max, positionsConsumer, null, context.workerId + ); + positionProvider.positionsIn(positionsContext); + } + } + + @Nonnull + @Override + public Map getInputTypesAndBounds_bufferGrid() { + Map map = new HashMap<>(); + map.put(this.biomeInputBufferType, this.inputBounds_bufferGrid); + map.put(this.biomeDistanceInputBufferType, this.inputBounds_bufferGrid); + map.put(this.materialInputBufferType, this.inputBounds_bufferGrid); + if (this.entityInputBufferType != null) { + map.put(this.entityInputBufferType, this.inputBounds_bufferGrid); + } + + return map; + } + + @Nonnull + @Override + public List getOutputTypes() { + return List.of(this.materialOutputBufferType, this.entityOutputBufferType); + } + + @Nonnull + @Override + public String getName() { + return this.stageName; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NStage.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NStage.java new file mode 100644 index 0000000..172bf81 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NStage.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.NBufferBundle; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +public interface NStage { + void run(@Nonnull NStage.Context var1); + + @Nonnull + Map getInputTypesAndBounds_bufferGrid(); + + @Nonnull + List getOutputTypes(); + + @Nonnull + String getName(); + + public static final class Context { + @Nonnull + public Map bufferAccess; + @Nonnull + public WorkerIndexer.Id workerId; + + public Context(@Nonnull Map bufferAccess, @Nonnull WorkerIndexer.Id workerId) { + this.bufferAccess = bufferAccess; + this.workerId = workerId; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NTerrainStage.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NTerrainStage.java new file mode 100644 index 0000000..f1f7235 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NTerrainStage.java @@ -0,0 +1,416 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages; + +import com.hypixel.hytale.builtin.hytalegenerator.biome.BiomeType; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.GridUtils; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.NStagedChunkGenerator; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.NBufferBundle; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NCountedPixelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NSimplePixelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NVoxelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NParametrizedBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.containers.FloatContainer3d; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.NPixelBufferView; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.NVoxelBufferView; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +public class NTerrainStage implements NStage { + public static final double DEFAULT_BACKGROUND_DENSITY = 0.0; + public static final double ORIGIN_REACH = 1.0; + public static final double ORIGIN_REACH_HALF = 0.5; + public static final double QUARTER_PI = Math.PI / 4; + public static final Class biomeBufferClass = NCountedPixelBuffer.class; + public static final Class biomeClass = BiomeType.class; + public static final Class biomeDistanceBufferClass = NSimplePixelBuffer.class; + public static final Class biomeDistanceClass = NBiomeDistanceStage.BiomeDistanceEntries.class; + public static final Class materialBufferClass = NVoxelBuffer.class; + public static final Class materialClass = Material.class; + private final NParametrizedBufferType biomeInputBufferType; + private final NParametrizedBufferType biomeDistanceInputBufferType; + private final NParametrizedBufferType materialOutputBufferType; + private final Bounds3i inputBounds_bufferGrid; + private final String stageName; + private final int maxInterpolationRadius_voxelGrid; + private final MaterialCache materialCache; + private final WorkerIndexer.Data densityContainers; + + public NTerrainStage( + @Nonnull String stageName, + @Nonnull NParametrizedBufferType biomeInputBufferType, + @Nonnull NParametrizedBufferType biomeDistanceInputBufferType, + @Nonnull NParametrizedBufferType materialOutputBufferType, + int maxInterpolationRadius_voxelGrid, + @Nonnull MaterialCache materialCache, + @Nonnull WorkerIndexer workerIndexer + ) { + assert biomeInputBufferType.isValidType(biomeBufferClass, biomeClass); + + assert biomeDistanceInputBufferType.isValidType(biomeDistanceBufferClass, biomeDistanceClass); + + assert materialOutputBufferType.isValidType(materialBufferClass, materialClass); + + assert maxInterpolationRadius_voxelGrid >= 0; + + this.biomeInputBufferType = biomeInputBufferType; + this.biomeDistanceInputBufferType = biomeDistanceInputBufferType; + this.materialOutputBufferType = materialOutputBufferType; + this.stageName = stageName; + this.maxInterpolationRadius_voxelGrid = maxInterpolationRadius_voxelGrid; + this.materialCache = materialCache; + this.densityContainers = new WorkerIndexer.Data<>( + workerIndexer.getWorkerCount(), () -> new FloatContainer3d(NStagedChunkGenerator.SINGLE_BUFFER_TILE_BOUNDS_BUFFER_GRID, 0.0F) + ); + this.inputBounds_bufferGrid = GridUtils.createColumnBounds_bufferGrid(new Vector3i(), 0, 40); + this.inputBounds_bufferGrid.min.subtract(Vector3i.ALL_ONES); + this.inputBounds_bufferGrid.max.add(Vector3i.ALL_ONES); + GridUtils.setBoundsYToWorldHeight_bufferGrid(this.inputBounds_bufferGrid); + } + + @Override + public void run(@Nonnull NStage.Context context) { + NBufferBundle.Access.View biomeAccess = context.bufferAccess.get(this.biomeInputBufferType); + NPixelBufferView biomeSpace = new NPixelBufferView<>(biomeAccess, biomeClass); + NBufferBundle.Access.View biomeDistanceAccess = context.bufferAccess.get(this.biomeDistanceInputBufferType); + NPixelBufferView biomeDistanceSpace = new NPixelBufferView<>(biomeDistanceAccess, biomeDistanceClass); + NBufferBundle.Access.View materialAccess = context.bufferAccess.get(this.materialOutputBufferType); + NVoxelBufferView materialSpace = new NVoxelBufferView<>(materialAccess, materialClass); + Bounds3i outputBounds_voxelGrid = materialSpace.getBounds(); + FloatContainer3d densityContainer = this.densityContainers.get(context.workerId); + densityContainer.moveMinTo(outputBounds_voxelGrid.min); + this.generateDensity(densityContainer, biomeSpace, biomeDistanceSpace, context.workerId); + this.generateMaterials(biomeSpace, biomeDistanceSpace, densityContainer, materialSpace, context.workerId); + } + + @Nonnull + @Override + public Map getInputTypesAndBounds_bufferGrid() { + Map map = new HashMap<>(); + map.put(this.biomeInputBufferType, this.inputBounds_bufferGrid); + map.put(this.biomeDistanceInputBufferType, this.inputBounds_bufferGrid); + return map; + } + + @Nonnull + @Override + public List getOutputTypes() { + return List.of(this.materialOutputBufferType); + } + + @Nonnull + @Override + public String getName() { + return this.stageName; + } + + private void generateDensity( + @Nonnull FloatContainer3d densityBuffer, + @Nonnull NPixelBufferView biomeSpace, + @Nonnull NPixelBufferView distanceSpace, + @Nonnull WorkerIndexer.Id workerId + ) { + Bounds3i bounds_voxelGrid = densityBuffer.getBounds_voxelGrid(); + Vector3i position_voxelGrid = new Vector3i(bounds_voxelGrid.min); + Density.Context densityContext = new Density.Context(); + densityContext.position = position_voxelGrid.toVector3d(); + densityContext.workerId = workerId; + + for (position_voxelGrid.x = bounds_voxelGrid.min.x; position_voxelGrid.x < bounds_voxelGrid.max.x; position_voxelGrid.x++) { + densityContext.position.x = position_voxelGrid.x; + + for (position_voxelGrid.z = bounds_voxelGrid.min.z; position_voxelGrid.z < bounds_voxelGrid.max.z; position_voxelGrid.z++) { + densityContext.position.z = position_voxelGrid.z; + position_voxelGrid.y = 0; + position_voxelGrid.dropHash(); + BiomeType biomeAtOrigin = biomeSpace.getContent(position_voxelGrid); + NBiomeDistanceStage.BiomeDistanceEntries biomeDistances = distanceSpace.getContent(position_voxelGrid); + NTerrainStage.BiomeWeights biomeWeights = createWeights(biomeDistances, biomeAtOrigin, this.maxInterpolationRadius_voxelGrid); + densityContext.distanceToBiomeEdge = biomeDistances.distanceToClosestOtherBiome(biomeAtOrigin); + boolean isFirstBiome = true; + + for (NTerrainStage.BiomeWeights.Entry biomeWeight : biomeWeights.entries) { + Density density = biomeWeight.biomeType.getTerrainDensity(); + if (isFirstBiome) { + for (position_voxelGrid.y = bounds_voxelGrid.min.y; position_voxelGrid.y < bounds_voxelGrid.max.y; position_voxelGrid.y++) { + position_voxelGrid.dropHash(); + densityContext.position.y = position_voxelGrid.y; + float densityValue = (float)density.process(densityContext); + float scaledDensityValue = densityValue * biomeWeight.weight; + densityBuffer.set(position_voxelGrid, scaledDensityValue); + } + } + + if (!isFirstBiome) { + for (position_voxelGrid.y = bounds_voxelGrid.min.y; position_voxelGrid.y < bounds_voxelGrid.max.y; position_voxelGrid.y++) { + position_voxelGrid.dropHash(); + densityContext.position.y = position_voxelGrid.y; + float bufferDensityValue = densityBuffer.get(position_voxelGrid); + float densityValue = (float)density.process(densityContext); + float scaledDensityValue = densityValue * biomeWeight.weight; + densityBuffer.set(position_voxelGrid, bufferDensityValue + scaledDensityValue); + } + } + + isFirstBiome = false; + } + } + } + } + + private float getOrGenerateDensity( + @Nonnull Vector3i position_voxelGrid, + @Nonnull FloatContainer3d densityBuffer, + @Nonnull NPixelBufferView biomeSpace, + @Nonnull NPixelBufferView distanceSpace, + @Nonnull WorkerIndexer.Id workerId + ) { + return densityBuffer.getBounds_voxelGrid().contains(position_voxelGrid) + ? densityBuffer.get(position_voxelGrid) + : this.generateDensity(position_voxelGrid, biomeSpace, distanceSpace, workerId); + } + + private float generateDensity( + @Nonnull Vector3i position_voxelGrid, + @Nonnull NPixelBufferView biomeSpace, + @Nonnull NPixelBufferView distanceSpace, + @Nonnull WorkerIndexer.Id workerId + ) { + if (!distanceSpace.isInsideSpace(position_voxelGrid.x, 0, position_voxelGrid.z)) { + return 0.0F; + } else { + Density.Context densityContext = new Density.Context(); + densityContext.position = position_voxelGrid.toVector3d(); + densityContext.workerId = workerId; + BiomeType biomeAtOrigin = biomeSpace.getContent(position_voxelGrid.x, 0, position_voxelGrid.z); + NBiomeDistanceStage.BiomeDistanceEntries biomeDistances = distanceSpace.getContent(position_voxelGrid.x, 0, position_voxelGrid.z); + NTerrainStage.BiomeWeights biomeWeights = createWeights(biomeDistances, biomeAtOrigin, this.maxInterpolationRadius_voxelGrid); + float densityResult = 0.0F; + + for (NTerrainStage.BiomeWeights.Entry biomeWeight : biomeWeights.entries) { + Density density = biomeWeight.biomeType.getTerrainDensity(); + float densityValue = (float)density.process(densityContext); + float scaledDensityValue = densityValue * biomeWeight.weight; + densityResult += scaledDensityValue; + } + + return densityResult; + } + } + + private void generateMaterials( + @Nonnull NPixelBufferView biomeSpace, + @Nonnull NPixelBufferView distanceSpace, + @Nonnull FloatContainer3d densityBuffer, + @Nonnull NVoxelBufferView materialSpace, + @Nonnull WorkerIndexer.Id workerId + ) { + Bounds3i bounds_voxelGrid = materialSpace.getBounds(); + Vector3i position_voxelGrid = new Vector3i(); + + for (position_voxelGrid.x = bounds_voxelGrid.min.x; position_voxelGrid.x < bounds_voxelGrid.max.x; position_voxelGrid.x++) { + for (position_voxelGrid.z = bounds_voxelGrid.min.z; position_voxelGrid.z < bounds_voxelGrid.max.z; position_voxelGrid.z++) { + position_voxelGrid.y = bounds_voxelGrid.min.y; + BiomeType biome = biomeSpace.getContent(position_voxelGrid.x, 0, position_voxelGrid.z); + MaterialProvider materialProvider = biome.getMaterialProvider(); + NTerrainStage.ColumnData columnData = new NTerrainStage.ColumnData( + bounds_voxelGrid.min.y, bounds_voxelGrid.max.y, position_voxelGrid.x, position_voxelGrid.z, densityBuffer, materialProvider + ); + double distanceToOtherBiome_voxelGrid = distanceSpace.getContent(position_voxelGrid).distanceToClosestOtherBiome(biome); + + for (position_voxelGrid.y = bounds_voxelGrid.min.y; position_voxelGrid.y < bounds_voxelGrid.max.y; position_voxelGrid.y++) { + int i = position_voxelGrid.y - bounds_voxelGrid.min.y; + MaterialProvider.Context context = new MaterialProvider.Context( + position_voxelGrid, + 0.0, + columnData.depthIntoFloor[i], + columnData.depthIntoCeiling[i], + columnData.spaceAboveFloor[i], + columnData.spaceBelowCeiling[i], + workerId, + (position, id) -> this.getOrGenerateDensity(position, densityBuffer, biomeSpace, distanceSpace, workerId), + distanceToOtherBiome_voxelGrid + ); + Material material = columnData.materialProvider.getVoxelTypeAt(context); + if (material != null) { + materialSpace.set(material, position_voxelGrid.x, position_voxelGrid.y, position_voxelGrid.z); + } else { + materialSpace.set(this.materialCache.EMPTY, position_voxelGrid.x, position_voxelGrid.y, position_voxelGrid.z); + } + } + } + } + } + + @Nonnull + private static NTerrainStage.BiomeWeights createWeights( + @Nonnull NBiomeDistanceStage.BiomeDistanceEntries distances, @Nonnull BiomeType biomeAtOrigin, double interpolationRange + ) { + double circleRadius = interpolationRange + 0.5; + NTerrainStage.BiomeWeights biomeWeights = new NTerrainStage.BiomeWeights(); + int originIndex = 0; + double smallestNonOriginDistance = Double.MAX_VALUE; + double total = 0.0; + + for (int i = 0; i < distances.entries.size(); i++) { + NBiomeDistanceStage.BiomeDistanceEntry distanceEntry = distances.entries.get(i); + NTerrainStage.BiomeWeights.Entry weightEntry = new NTerrainStage.BiomeWeights.Entry(); + if (!(distanceEntry.distance_voxelGrid >= interpolationRange)) { + if (distanceEntry.biomeType == biomeAtOrigin) { + originIndex = biomeWeights.entries.size(); + } else if (distanceEntry.distance_voxelGrid < smallestNonOriginDistance) { + smallestNonOriginDistance = distanceEntry.distance_voxelGrid; + } + + weightEntry.biomeType = distanceEntry.biomeType; + weightEntry.weight = (float)areaUnderCircleCurve(distanceEntry.distance_voxelGrid, circleRadius, circleRadius); + biomeWeights.entries.add(weightEntry); + total += weightEntry.weight; + } + } + + if (biomeWeights.entries.size() > 0) { + NTerrainStage.BiomeWeights.Entry originWeightEntry = biomeWeights.entries.get(originIndex); + double maxX = 0.5 + smallestNonOriginDistance; + double originExtraWeight = areaUnderCircleCurve(0.0, maxX, circleRadius); + originWeightEntry.weight = (float)(originWeightEntry.weight + originExtraWeight); + total += originExtraWeight; + } + + for (NTerrainStage.BiomeWeights.Entry entry : biomeWeights.entries) { + entry.weight /= (float)total; + } + + return biomeWeights; + } + + private static double areaUnderCircleCurve(double maxX) { + if (maxX < 0.0) { + return 0.0; + } else { + return maxX > 1.0 ? Math.PI / 4 : 0.5 * (maxX * Math.sqrt(1.0 - maxX * maxX) + Math.asin(maxX)); + } + } + + private static double areaUnderCircleCurve(double minX, double maxX, double circleRadius) { + assert circleRadius >= 0.0; + + assert minX <= maxX; + + minX /= circleRadius; + maxX /= circleRadius; + return circleRadius * circleRadius * (areaUnderCircleCurve(maxX) - areaUnderCircleCurve(minX)); + } + + private static class BiomeWeights { + List entries = new ArrayList<>(3); + + BiomeWeights() { + } + + static class Entry { + BiomeType biomeType; + float weight; + + Entry() { + } + } + } + + private class ColumnData { + int worldX; + int worldZ; + MaterialProvider materialProvider; + int topExclusive; + int bottom; + int arrayLength; + int[] depthIntoFloor; + int[] spaceBelowCeiling; + int[] depthIntoCeiling; + int[] spaceAboveFloor; + int top; + FloatContainer3d densityBuffer; + + private ColumnData( + int bottom, int topExclusive, int worldX, int worldZ, @Nonnull FloatContainer3d densityBuffer, @Nonnull MaterialProvider materialProvider + ) { + this.topExclusive = topExclusive; + this.bottom = bottom; + this.worldX = worldX; + this.worldZ = worldZ; + this.arrayLength = topExclusive - bottom; + this.depthIntoFloor = new int[this.arrayLength]; + this.spaceBelowCeiling = new int[this.arrayLength]; + this.depthIntoCeiling = new int[this.arrayLength]; + this.spaceAboveFloor = new int[this.arrayLength]; + this.top = topExclusive - 1; + this.densityBuffer = densityBuffer; + this.materialProvider = materialProvider; + Vector3i position = new Vector3i(worldX, 0, worldZ); + Vector3i positionAbove = new Vector3i(worldX, 0, worldZ); + Vector3i positionBelow = new Vector3i(worldX, 0, worldZ); + + for (int y = this.top; y >= bottom; y--) { + position.y = y; + positionAbove.y = y + 1; + positionBelow.y = y - 1; + int i = y - bottom; + float density = densityBuffer.get(position); + boolean solidity = density > 0.0; + if (y == this.top) { + if (solidity) { + this.depthIntoFloor[i] = 1; + } else { + this.depthIntoFloor[i] = 0; + } + + this.spaceAboveFloor[i] = 1073741823; + } else if (solidity) { + this.depthIntoFloor[i] = this.depthIntoFloor[i + 1] + 1; + this.spaceAboveFloor[i] = this.spaceAboveFloor[i + 1]; + } else { + this.depthIntoFloor[i] = 0; + if (densityBuffer.get(positionAbove) > 0.0) { + this.spaceAboveFloor[i] = 0; + } else { + this.spaceAboveFloor[i] = this.spaceAboveFloor[i + 1] + 1; + } + } + } + + for (int yx = bottom; yx <= this.top; yx++) { + int i = yx - bottom; + double density = densityBuffer.get(position); + boolean solidity = density > 0.0; + if (yx == bottom) { + if (solidity) { + this.depthIntoCeiling[i] = 1; + } else { + this.depthIntoCeiling[i] = 0; + } + + this.spaceBelowCeiling[i] = Integer.MAX_VALUE; + } else if (solidity) { + this.depthIntoCeiling[i] = this.depthIntoCeiling[i - 1] + 1; + this.spaceBelowCeiling[i] = this.spaceBelowCeiling[i - 1]; + } else { + this.depthIntoCeiling[i] = 0; + if (densityBuffer.get(positionBelow) > 0.0) { + this.spaceBelowCeiling[i] = 0; + } else { + this.spaceBelowCeiling[i] = this.spaceBelowCeiling[i - 1] + 1; + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NTestPropStage.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NTestPropStage.java new file mode 100644 index 0000000..9d99d43 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NTestPropStage.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.material.SolidMaterial; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.NBufferBundle; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NVoxelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NParametrizedBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.NVoxelBufferView; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import javax.annotation.Nonnull; + +public class NTestPropStage implements NStage { + private static final Class bufferClass = NVoxelBuffer.class; + private static final Class solidMaterialClass = SolidMaterial.class; + private final int CONTEXT_DEPENDENCY_RANGE_BUFFER_GRID = 0; + private final Bounds3i inputBounds_bufferGrid = new Bounds3i(new Vector3i(0, 0, 0), new Vector3i(1, 40, 1)); + private final NParametrizedBufferType inputBufferType; + private final NParametrizedBufferType outputBufferType; + private final SolidMaterial floorMaterial; + private final SolidMaterial anchorMaterial; + private final SolidMaterial propMaterial; + + public NTestPropStage( + @Nonnull NBufferType inputBufferType, + @Nonnull NBufferType outputBufferType, + @Nonnull SolidMaterial floorMaterial, + @Nonnull SolidMaterial anchorMaterial, + @Nonnull SolidMaterial propMaterial + ) { + assert inputBufferType instanceof NParametrizedBufferType; + + assert outputBufferType instanceof NParametrizedBufferType; + + this.inputBufferType = (NParametrizedBufferType)inputBufferType; + this.outputBufferType = (NParametrizedBufferType)outputBufferType; + + assert this.outputBufferType.isValidType(bufferClass, solidMaterialClass); + + this.floorMaterial = floorMaterial; + this.anchorMaterial = anchorMaterial; + this.propMaterial = propMaterial; + } + + @Override + public void run(@Nonnull NStage.Context context) { + NBufferBundle.Access.View inputAccess = context.bufferAccess.get(this.inputBufferType); + NVoxelBufferView inputView = new NVoxelBufferView<>(inputAccess, solidMaterialClass); + NBufferBundle.Access.View outputAccess = context.bufferAccess.get(this.outputBufferType); + NVoxelBufferView outputView = new NVoxelBufferView<>(outputAccess, solidMaterialClass); + outputView.copyFrom(inputView); + Vector3i scanPosition = new Vector3i(0, 316, 0); + Random rand = new Random(Objects.hash(outputView.minX() * 1000, outputView.minZ())); + scanPosition.setX(rand.nextInt(NVoxelBuffer.SIZE.x) + outputView.minX()); + scanPosition.setZ(rand.nextInt(NVoxelBuffer.SIZE.z) + outputView.minZ()); + + for (; scanPosition.y >= 10; scanPosition.setY(scanPosition.y - 1)) { + SolidMaterial floor = inputView.getContent(scanPosition.clone().add(0, -1, 0)); + SolidMaterial anchor = inputView.getContent(scanPosition); + if (this.floorMaterial.equals(floor) && this.anchorMaterial.equals(anchor)) { + this.placeProp(scanPosition, outputView); + } + } + } + + private void placeProp(@Nonnull Vector3i position, @Nonnull NVoxelBufferView view) { + int height = 5; + Vector3i placePosition = position.clone(); + + for (int i = 0; i < 5; i++) { + view.set(this.propMaterial, placePosition); + placePosition.setY(placePosition.getY() + 1); + } + } + + @Nonnull + @Override + public Map getInputTypesAndBounds_bufferGrid() { + return Map.of(this.inputBufferType, this.inputBounds_bufferGrid.clone()); + } + + @Nonnull + @Override + public List getOutputTypes() { + return List.of(this.outputBufferType); + } + + @Nonnull + @Override + public String getName() { + return "TestPropStage"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NTestTerrainStage.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NTestTerrainStage.java new file mode 100644 index 0000000..c641592 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NTestTerrainStage.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.material.SolidMaterial; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.NBufferBundle; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NVoxelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NParametrizedBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.NVoxelBufferView; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.procedurallib.logic.SimplexNoise; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +public class NTestTerrainStage implements NStage { + private static final Class bufferClass = NVoxelBuffer.class; + private static final Class solidMaterialClass = SolidMaterial.class; + private final NParametrizedBufferType outputBufferType; + private final SolidMaterial ground; + private final SolidMaterial empty; + + public NTestTerrainStage(@Nonnull NBufferType outputBufferType, @Nonnull SolidMaterial groundMaterial, @Nonnull SolidMaterial emptyMaterial) { + assert outputBufferType instanceof NParametrizedBufferType; + + this.outputBufferType = (NParametrizedBufferType)outputBufferType; + + assert this.outputBufferType.isValidType(bufferClass, solidMaterialClass); + + this.ground = groundMaterial; + this.empty = emptyMaterial; + } + + @Override + public void run(@Nonnull NStage.Context context) { + NBufferBundle.Access.View access = context.bufferAccess.get(this.outputBufferType); + NVoxelBufferView materialBuffer = new NVoxelBufferView<>(access, solidMaterialClass); + SimplexNoise noise = SimplexNoise.INSTANCE; + Vector3i position = new Vector3i(); + + for (position.x = materialBuffer.minX(); position.x < materialBuffer.maxX(); position.x++) { + for (position.z = materialBuffer.minZ(); position.z < materialBuffer.maxZ(); position.z++) { + for (position.y = materialBuffer.minY(); position.y < materialBuffer.maxY(); position.y++) { + Vector3d noisePosition = position.toVector3d(); + noisePosition.scale(0.05); + double noiseValue = noise.get(1, 0, noisePosition.x, noisePosition.y, noisePosition.z); + if (position.y >= 130 && (!(noiseValue > 0.0) || position.y >= 150)) { + materialBuffer.set(this.empty, position); + } else { + materialBuffer.set(this.ground, position); + } + } + } + } + } + + @Nonnull + @Override + public Map getInputTypesAndBounds_bufferGrid() { + return Map.of(); + } + + @Nonnull + @Override + public List getOutputTypes() { + return List.of(this.outputBufferType); + } + + @Nonnull + @Override + public String getName() { + return "TestTerrainStage"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NTintStage.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NTintStage.java new file mode 100644 index 0000000..57688de --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/stages/NTintStage.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages; + +import com.hypixel.hytale.builtin.hytalegenerator.biome.BiomeType; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.GridUtils; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.NBufferBundle; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NCountedPixelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NSimplePixelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NParametrizedBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.NPixelBufferView; +import com.hypixel.hytale.builtin.hytalegenerator.tintproviders.TintProvider; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +public class NTintStage implements NStage { + public static final Class biomeBufferClass = NCountedPixelBuffer.class; + public static final Class biomeTypeClass = BiomeType.class; + public static final Class tintBufferClass = NSimplePixelBuffer.class; + public static final Class tintClass = Integer.class; + private final NParametrizedBufferType biomeInputBufferType; + private final NParametrizedBufferType tintOutputBufferType; + private final Bounds3i inputBounds_bufferGrid; + private final String stageName; + + public NTintStage(@Nonnull String stageName, @Nonnull NParametrizedBufferType biomeInputBufferType, @Nonnull NParametrizedBufferType tintOutputBufferType) { + assert biomeInputBufferType.isValidType(biomeBufferClass, biomeTypeClass); + + assert tintOutputBufferType.isValidType(tintBufferClass, tintClass); + + this.biomeInputBufferType = biomeInputBufferType; + this.tintOutputBufferType = tintOutputBufferType; + this.stageName = stageName; + this.inputBounds_bufferGrid = GridUtils.createUnitBounds3i(Vector3i.ZERO); + } + + @Override + public void run(@Nonnull NStage.Context context) { + NBufferBundle.Access.View biomeAccess = context.bufferAccess.get(this.biomeInputBufferType); + NPixelBufferView biomeSpace = new NPixelBufferView<>(biomeAccess, biomeTypeClass); + NBufferBundle.Access.View tintAccess = context.bufferAccess.get(this.tintOutputBufferType); + NPixelBufferView tintSpace = new NPixelBufferView<>(tintAccess, tintClass); + Bounds3i outputBounds_voxelGrid = tintSpace.getBounds(); + Vector3i position_voxelGrid = new Vector3i(outputBounds_voxelGrid.min); + position_voxelGrid.setY(0); + TintProvider.Context tintContext = new TintProvider.Context(position_voxelGrid, context.workerId); + + for (position_voxelGrid.x = outputBounds_voxelGrid.min.x; position_voxelGrid.x < outputBounds_voxelGrid.max.x; position_voxelGrid.x++) { + for (position_voxelGrid.z = outputBounds_voxelGrid.min.z; position_voxelGrid.z < outputBounds_voxelGrid.max.z; position_voxelGrid.z++) { + BiomeType biome = biomeSpace.getContent(position_voxelGrid.x, 0, position_voxelGrid.z); + + assert biome != null; + + TintProvider tintProvider = biome.getTintProvider(); + TintProvider.Result tintResult = tintProvider.getValue(tintContext); + if (!tintResult.hasValue) { + tintSpace.set(TintProvider.DEFAULT_TINT, position_voxelGrid); + } else { + tintSpace.set(tintResult.tint, position_voxelGrid); + } + } + } + } + + @Nonnull + @Override + public Map getInputTypesAndBounds_bufferGrid() { + return Map.of(this.biomeInputBufferType, this.inputBounds_bufferGrid); + } + + @Nonnull + @Override + public List getOutputTypes() { + return List.of(this.tintOutputBufferType); + } + + @Nonnull + @Override + public String getName() { + return this.stageName; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/views/EntityContainer.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/views/EntityContainer.java new file mode 100644 index 0000000..e5af4a2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/views/EntityContainer.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.views; + +import com.hypixel.hytale.builtin.hytalegenerator.props.entity.EntityPlacementData; +import javax.annotation.Nonnull; + +public interface EntityContainer { + void addEntity(@Nonnull EntityPlacementData var1); + + boolean isInsideBuffer(int var1, int var2, int var3); +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/views/NEntityBufferView.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/views/NEntityBufferView.java new file mode 100644 index 0000000..e64a2bb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/views/NEntityBufferView.java @@ -0,0 +1,102 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.views; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.GridUtils; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.NBufferBundle; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NEntityBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.props.entity.EntityPlacementData; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +public class NEntityBufferView implements EntityContainer { + private final NBufferBundle.Access.View access; + private final Bounds3i bounds_voxelGrid; + private final Bounds3i bounds_bufferGrid; + + public NEntityBufferView(@Nonnull NBufferBundle.Access.View bufferAccess) { + this.access = bufferAccess; + this.bounds_bufferGrid = bufferAccess.getBounds_bufferGrid(); + this.bounds_voxelGrid = bufferAccess.getBounds_bufferGrid(); + GridUtils.toVoxelGrid_fromBufferGrid(this.bounds_voxelGrid); + } + + public void forEach(@Nonnull Consumer consumer) { + Vector3i position_bufferGrid = this.bounds_voxelGrid.min.clone(); + position_bufferGrid.setX(this.bounds_bufferGrid.min.x); + + while (position_bufferGrid.x < this.bounds_bufferGrid.max.x) { + position_bufferGrid.setZ(this.bounds_bufferGrid.min.z); + + while (position_bufferGrid.z < this.bounds_bufferGrid.max.z) { + position_bufferGrid.setY(this.bounds_bufferGrid.min.y); + + while (position_bufferGrid.y < this.bounds_bufferGrid.max.y) { + NEntityBuffer buffer = this.getBuffer_fromBufferGrid(position_bufferGrid); + buffer.forEach(consumer); + position_bufferGrid.setY(position_bufferGrid.y + 1); + } + + position_bufferGrid.setZ(position_bufferGrid.z + 1); + } + + position_bufferGrid.setX(position_bufferGrid.x + 1); + } + } + + private NEntityBuffer getBuffer_fromBufferGrid(@Nonnull Vector3i position_bufferGrid) { + return (NEntityBuffer)this.access.getBuffer(position_bufferGrid).buffer(); + } + + public void copyFrom(@Nonnull NEntityBufferView source) { + assert source.bounds_voxelGrid.contains(this.bounds_voxelGrid); + + Bounds3i thisBounds_bufferGrid = this.access.getBounds_bufferGrid(); + Vector3i pos_bufferGrid = new Vector3i(); + pos_bufferGrid.setX(thisBounds_bufferGrid.min.x); + + while (pos_bufferGrid.x < thisBounds_bufferGrid.max.x) { + pos_bufferGrid.setY(thisBounds_bufferGrid.min.y); + + while (pos_bufferGrid.y < thisBounds_bufferGrid.max.y) { + pos_bufferGrid.setZ(thisBounds_bufferGrid.min.z); + + while (pos_bufferGrid.z < thisBounds_bufferGrid.max.z) { + NEntityBuffer sourceBuffer = source.getBuffer_fromBufferGrid(pos_bufferGrid); + NEntityBuffer destinationBuffer = this.getBuffer_fromBufferGrid(pos_bufferGrid); + destinationBuffer.copyFrom(sourceBuffer); + pos_bufferGrid.setZ(pos_bufferGrid.z + 1); + } + + pos_bufferGrid.setY(pos_bufferGrid.y + 1); + } + + pos_bufferGrid.setX(pos_bufferGrid.x + 1); + } + } + + @Override + public void addEntity(@Nonnull EntityPlacementData entityPlacementData) { + Vector3d entityPosition_voxelGrid = entityPlacementData.getOffset().toVector3d(); + TransformComponent transform = entityPlacementData.getEntityHolder().getComponent(TransformComponent.getComponentType()); + + assert transform != null; + + Vector3d holderPosition_voxelGrid = transform.getPosition(); + entityPosition_voxelGrid.add(holderPosition_voxelGrid); + Vector3i position_bufferGrid = GridUtils.toIntegerGrid_fromDecimalGrid(entityPosition_voxelGrid); + + assert this.bounds_voxelGrid.contains(position_bufferGrid); + + GridUtils.toBufferGrid_fromVoxelGrid(position_bufferGrid); + NEntityBuffer buffer = (NEntityBuffer)this.access.getBuffer(position_bufferGrid).buffer(); + buffer.addEntity(entityPlacementData); + } + + @Override + public boolean isInsideBuffer(int x, int y, int z) { + return this.bounds_voxelGrid.contains(new Vector3i(x, y, z)); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/views/NPixelBufferView.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/views/NPixelBufferView.java new file mode 100644 index 0000000..11d3c68 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/views/NPixelBufferView.java @@ -0,0 +1,174 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.views; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelConsumer; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.GridUtils; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.NBufferBundle; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NPixelBuffer; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NPixelBufferView implements VoxelSpace { + public static final int Y_LEVEL_BUFFER_GRID = 0; + public static final int Y_LEVEL_VOXEL_GRID = 0; + private final Class voxelType; + private final NBufferBundle.Access.View bufferAccess; + private final Bounds3i bounds_voxelGrid; + private final Vector3i size_voxelGrid; + + public NPixelBufferView(@Nonnull NBufferBundle.Access.View bufferAccess, @Nonnull Class pixelType) { + assert bufferAccess.getBounds_bufferGrid().min.y <= 0 && bufferAccess.getBounds_bufferGrid().max.y > 0; + + this.bufferAccess = bufferAccess; + this.voxelType = pixelType; + this.bounds_voxelGrid = bufferAccess.getBounds_bufferGrid(); + GridUtils.toVoxelGrid_fromBufferGrid(this.bounds_voxelGrid); + this.bounds_voxelGrid.min.y = 0; + this.bounds_voxelGrid.max.y = 1; + this.size_voxelGrid = this.bounds_voxelGrid.getSize(); + } + + @Override + public boolean set(T content, int x, int y, int z) { + return this.set(content, new Vector3i(x, y, z)); + } + + @Override + public boolean set(T value, @Nonnull Vector3i position_voxelGrid) { + assert this.bounds_voxelGrid.contains(position_voxelGrid); + + NPixelBuffer buffer = this.getBuffer(position_voxelGrid); + Vector3i positionInBuffer_voxelGrid = position_voxelGrid.clone(); + GridUtils.toVoxelGridInsideBuffer_fromWorldGrid(positionInBuffer_voxelGrid); + buffer.setPixelContent(positionInBuffer_voxelGrid, value); + return true; + } + + @Override + public void set(T content) { + throw new UnsupportedOperationException(); + } + + @Override + public void setOrigin(int x, int y, int z) { + throw new UnsupportedOperationException(); + } + + @Nullable + @Override + public T getContent(int x, int y, int z) { + return this.getContent(new Vector3i(x, y, z)); + } + + @Nullable + @Override + public T getContent(@Nonnull Vector3i position_voxelGrid) { + assert this.bounds_voxelGrid.contains(position_voxelGrid); + + NPixelBuffer buffer = this.getBuffer(position_voxelGrid); + Vector3i positionInBuffer_voxelGrid = position_voxelGrid.clone(); + GridUtils.toVoxelGridInsideBuffer_fromWorldGrid(positionInBuffer_voxelGrid); + return buffer.getPixelContent(positionInBuffer_voxelGrid); + } + + private NPixelBuffer getBuffer(@Nonnull Vector3i position_voxelGrid) { + assert this.bounds_voxelGrid.contains(position_voxelGrid); + + Vector3i localBufferPosition_bufferGrid = position_voxelGrid.clone(); + GridUtils.toBufferGrid_fromVoxelGrid(localBufferPosition_bufferGrid); + return (NPixelBuffer)this.bufferAccess.getBuffer(localBufferPosition_bufferGrid).buffer(); + } + + @Override + public boolean replace(T replacement, int x, int y, int z, @Nonnull Predicate mask) { + return false; + } + + @Override + public void pasteFrom(@Nonnull VoxelSpace source) { + throw new UnsupportedOperationException(); + } + + @Override + public int getOriginX() { + return 0; + } + + @Override + public int getOriginY() { + return 0; + } + + @Override + public int getOriginZ() { + return 0; + } + + @Override + public String getName() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isInsideSpace(int x, int y, int z) { + return this.isInsideSpace(new Vector3i(x, y, z)); + } + + @Override + public boolean isInsideSpace(@Nonnull Vector3i position) { + return this.bounds_voxelGrid.contains(position); + } + + @Override + public void forEach(VoxelConsumer action) { + throw new UnsupportedOperationException(); + } + + @Override + public int minX() { + return this.bounds_voxelGrid.min.x; + } + + @Override + public int maxX() { + return this.bounds_voxelGrid.max.x; + } + + @Override + public int minY() { + return this.bounds_voxelGrid.min.y; + } + + @Override + public int maxY() { + return this.bounds_voxelGrid.max.y; + } + + @Override + public int minZ() { + return this.bounds_voxelGrid.min.z; + } + + @Override + public int maxZ() { + return this.bounds_voxelGrid.max.z; + } + + @Override + public int sizeX() { + return this.size_voxelGrid.x; + } + + @Override + public int sizeY() { + return this.size_voxelGrid.y; + } + + @Override + public int sizeZ() { + return this.size_voxelGrid.z; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/views/NVoxelBufferView.java b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/views/NVoxelBufferView.java new file mode 100644 index 0000000..b76cb16 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/newsystem/views/NVoxelBufferView.java @@ -0,0 +1,199 @@ +package com.hypixel.hytale.builtin.hytalegenerator.newsystem.views; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelConsumer; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.GridUtils; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.NBufferBundle; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NVoxelBuffer; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NVoxelBufferView implements VoxelSpace { + private final Class voxelType; + private final NBufferBundle.Access.View bufferAccess; + private final Bounds3i bounds_voxelGrid; + private final Vector3i size_voxelGrid; + + public NVoxelBufferView(@Nonnull NBufferBundle.Access.View bufferAccess, @Nonnull Class voxelType) { + this.bufferAccess = bufferAccess; + this.voxelType = voxelType; + this.bounds_voxelGrid = bufferAccess.getBounds_bufferGrid(); + GridUtils.toVoxelGrid_fromBufferGrid(this.bounds_voxelGrid); + this.size_voxelGrid = this.bounds_voxelGrid.getSize(); + } + + public void copyFrom(@Nonnull NVoxelBufferView source) { + assert source.bounds_voxelGrid.contains(this.bounds_voxelGrid); + + Bounds3i thisBounds_bufferGrid = this.bufferAccess.getBounds_bufferGrid(); + Vector3i pos_bufferGrid = new Vector3i(); + pos_bufferGrid.setX(thisBounds_bufferGrid.min.x); + + while (pos_bufferGrid.x < thisBounds_bufferGrid.max.x) { + pos_bufferGrid.setY(thisBounds_bufferGrid.min.y); + + while (pos_bufferGrid.y < thisBounds_bufferGrid.max.y) { + pos_bufferGrid.setZ(thisBounds_bufferGrid.min.z); + + while (pos_bufferGrid.z < thisBounds_bufferGrid.max.z) { + NVoxelBuffer sourceBuffer = source.getBuffer_fromBufferGrid(pos_bufferGrid); + NVoxelBuffer destinationBuffer = this.getBuffer_fromBufferGrid(pos_bufferGrid); + destinationBuffer.reference(sourceBuffer); + pos_bufferGrid.setZ(pos_bufferGrid.z + 1); + } + + pos_bufferGrid.setY(pos_bufferGrid.y + 1); + } + + pos_bufferGrid.setX(pos_bufferGrid.x + 1); + } + } + + @Override + public boolean set(T content, int x, int y, int z) { + return this.set(content, new Vector3i(x, y, z)); + } + + @Override + public boolean set(T content, @Nonnull Vector3i position_voxelGrid) { + assert this.bounds_voxelGrid.contains(position_voxelGrid); + + NVoxelBuffer buffer = this.getBuffer_fromVoxelGrid(position_voxelGrid); + Vector3i positionInBuffer_voxelGrid = position_voxelGrid.clone(); + GridUtils.toVoxelGridInsideBuffer_fromWorldGrid(positionInBuffer_voxelGrid); + buffer.setVoxelContent(positionInBuffer_voxelGrid, content); + return true; + } + + @Override + public void set(T content) { + throw new UnsupportedOperationException(); + } + + @Override + public void setOrigin(int x, int y, int z) { + throw new UnsupportedOperationException(); + } + + @Nullable + @Override + public T getContent(int x, int y, int z) { + return this.getContent(new Vector3i(x, y, z)); + } + + @Nullable + @Override + public T getContent(@Nonnull Vector3i position_voxelGrid) { + assert this.bounds_voxelGrid.contains(position_voxelGrid); + + NVoxelBuffer buffer = this.getBuffer_fromVoxelGrid(position_voxelGrid); + Vector3i positionInBuffer_voxelGrid = position_voxelGrid.clone(); + GridUtils.toVoxelGridInsideBuffer_fromWorldGrid(positionInBuffer_voxelGrid); + return buffer.getVoxelContent(positionInBuffer_voxelGrid); + } + + @Override + public boolean replace(T replacement, int x, int y, int z, @Nonnull Predicate mask) { + return false; + } + + @Override + public void pasteFrom(@Nonnull VoxelSpace source) { + throw new UnsupportedOperationException(); + } + + @Override + public int getOriginX() { + return 0; + } + + @Override + public int getOriginY() { + return 0; + } + + @Override + public int getOriginZ() { + return 0; + } + + @Override + public String getName() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isInsideSpace(int x, int y, int z) { + return this.isInsideSpace(new Vector3i(x, y, z)); + } + + @Override + public boolean isInsideSpace(@Nonnull Vector3i position) { + return this.bounds_voxelGrid.contains(position); + } + + @Override + public void forEach(VoxelConsumer action) { + throw new UnsupportedOperationException(); + } + + @Override + public int minX() { + return this.bounds_voxelGrid.min.x; + } + + @Override + public int maxX() { + return this.bounds_voxelGrid.max.x; + } + + @Override + public int minY() { + return this.bounds_voxelGrid.min.y; + } + + @Override + public int maxY() { + return this.bounds_voxelGrid.max.y; + } + + @Override + public int minZ() { + return this.bounds_voxelGrid.min.z; + } + + @Override + public int maxZ() { + return this.bounds_voxelGrid.max.z; + } + + @Override + public int sizeX() { + return this.size_voxelGrid.x; + } + + @Override + public int sizeY() { + return this.size_voxelGrid.y; + } + + @Override + public int sizeZ() { + return this.size_voxelGrid.z; + } + + @Nonnull + private NVoxelBuffer getBuffer_fromVoxelGrid(@Nonnull Vector3i position_voxelGrid) { + Vector3i localBufferPosition_bufferGrid = position_voxelGrid.clone(); + GridUtils.toBufferGrid_fromVoxelGrid(localBufferPosition_bufferGrid); + return this.getBuffer_fromBufferGrid(localBufferPosition_bufferGrid); + } + + @Nonnull + private NVoxelBuffer getBuffer_fromBufferGrid(@Nonnull Vector3i position_bufferGrid) { + return (NVoxelBuffer)this.bufferAccess.getBuffer(position_bufferGrid).buffer(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/AndPattern.java b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/AndPattern.java new file mode 100644 index 0000000..1150556 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/AndPattern.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.hytalegenerator.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import java.util.List; +import javax.annotation.Nonnull; + +public class AndPattern extends Pattern { + @Nonnull + private final Pattern[] patterns; + private final SpaceSize readSpaceSize; + + public AndPattern(@Nonnull List patterns) { + if (patterns.isEmpty()) { + this.patterns = new Pattern[0]; + this.readSpaceSize = SpaceSize.empty(); + } else { + this.patterns = new Pattern[patterns.size()]; + SpaceSize spaceAcc = patterns.getFirst().readSpace(); + + for (int i = 0; i < patterns.size(); i++) { + Pattern pattern = patterns.get(i); + this.patterns[i] = pattern; + spaceAcc = SpaceSize.merge(spaceAcc, pattern.readSpace()); + } + + this.readSpaceSize = spaceAcc; + } + } + + @Override + public boolean matches(@Nonnull Pattern.Context context) { + for (Pattern pattern : this.patterns) { + if (!pattern.matches(context)) { + return false; + } + } + + return true; + } + + @Nonnull + @Override + public SpaceSize readSpace() { + return this.readSpaceSize.clone(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/CeilingPattern.java b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/CeilingPattern.java new file mode 100644 index 0000000..7d36b58 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/CeilingPattern.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.builtin.hytalegenerator.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class CeilingPattern extends Pattern { + @Nonnull + private final Pattern ceilingPattern; + @Nonnull + private final Pattern airPattern; + @Nonnull + private final SpaceSize readSpaceSize; + + public CeilingPattern(@Nonnull Pattern ceilingPattern, @Nonnull Pattern airPattern) { + this.ceilingPattern = ceilingPattern; + this.airPattern = airPattern; + SpaceSize ceilingSpace = ceilingPattern.readSpace(); + ceilingSpace.moveBy(new Vector3i(0, 1, 0)); + this.readSpaceSize = SpaceSize.merge(ceilingSpace, airPattern.readSpace()); + } + + @Override + public boolean matches(@Nonnull Pattern.Context context) { + Vector3i ceilingPosition = new Vector3i(context.position.x, context.position.y + 1, context.position.z); + if (context.materialSpace.isInsideSpace(context.position) && context.materialSpace.isInsideSpace(ceilingPosition)) { + Pattern.Context ceilingContext = new Pattern.Context(context); + ceilingContext.position = ceilingPosition; + return this.airPattern.matches(context) && this.ceilingPattern.matches(ceilingContext); + } else { + return false; + } + } + + @Nonnull + @Override + public SpaceSize readSpace() { + return this.readSpaceSize.clone(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/CuboidPattern.java b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/CuboidPattern.java new file mode 100644 index 0000000..f6cc81f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/CuboidPattern.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.hytalegenerator.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class CuboidPattern extends Pattern { + @Nonnull + private final Pattern subPattern; + @Nonnull + private final Vector3i min; + @Nonnull + private final Vector3i max; + @Nonnull + private final SpaceSize readSpaceSize; + + public CuboidPattern(@Nonnull Pattern subPattern, @Nonnull Vector3i min, @Nonnull Vector3i max) { + this.subPattern = subPattern; + this.min = min; + this.max = max; + this.readSpaceSize = new SpaceSize(min, max.clone().add(1, 1, 1)); + } + + @Override + public boolean matches(@Nonnull Pattern.Context context) { + Vector3i scanMin = this.min.clone().add(context.position); + Vector3i scanMax = this.max.clone().add(context.position); + Vector3i childPosition = context.position.clone(); + Pattern.Context childContext = new Pattern.Context(context); + childContext.position = childPosition; + + for (childPosition.x = scanMin.x; childPosition.x <= scanMax.x; childPosition.x++) { + for (childPosition.z = scanMin.z; childPosition.z <= scanMax.z; childPosition.z++) { + for (childPosition.y = scanMin.y; childPosition.y <= scanMax.y; childPosition.y++) { + if (!context.materialSpace.isInsideSpace(childPosition)) { + return false; + } + + if (!this.subPattern.matches(childContext)) { + return false; + } + } + } + } + + return true; + } + + @Nonnull + @Override + public SpaceSize readSpace() { + return this.readSpaceSize.clone(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/FieldFunctionPattern.java b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/FieldFunctionPattern.java new file mode 100644 index 0000000..254d745 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/FieldFunctionPattern.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.builtin.hytalegenerator.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class FieldFunctionPattern extends Pattern { + @Nonnull + private final Density field; + @Nonnull + private final SpaceSize readSpaceSize; + @Nonnull + private final List delimiters; + + public FieldFunctionPattern(@Nonnull Density field) { + this.field = field; + this.readSpaceSize = SpaceSize.empty(); + this.delimiters = new ArrayList<>(1); + } + + @Override + public boolean matches(@Nonnull Pattern.Context context) { + Density.Context densityContext = new Density.Context(context); + double density = this.field.process(densityContext); + + for (FieldFunctionPattern.Delimiter d : this.delimiters) { + if (d.isInside(density)) { + return true; + } + } + + return false; + } + + @Nonnull + @Override + public SpaceSize readSpace() { + return this.readSpaceSize.clone(); + } + + public void addDelimiter(double min, double max) { + FieldFunctionPattern.Delimiter d = new FieldFunctionPattern.Delimiter(); + d.min = min; + d.max = max; + this.delimiters.add(d); + } + + private static class Delimiter { + double min; + double max; + + private Delimiter() { + } + + boolean isInside(double v) { + return v >= this.min && v < this.max; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/GapPattern.java b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/GapPattern.java new file mode 100644 index 0000000..6226265 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/GapPattern.java @@ -0,0 +1,232 @@ +package com.hypixel.hytale.builtin.hytalegenerator.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import javax.annotation.Nonnull; + +public class GapPattern extends Pattern { + private List> axisPositionedPatterns; + private List depthPositionedPatterns; + private double gapSize; + private double anchorSize; + private double anchorRoughness; + private int depthDown; + private int depthUp; + private Pattern gapPattern; + private Pattern anchorPattern; + private SpaceSize readSpaceSize; + + public GapPattern( + @Nonnull List angles, + double gapSize, + double anchorSize, + double anchorRoughness, + int depthDown, + int depthUp, + @Nonnull Pattern gapPattern, + @Nonnull Pattern anchorPattern + ) { + if (!(gapSize < 0.0) && !(anchorSize < 0.0) && !(anchorRoughness < 0.0) && depthDown >= 0 && depthUp >= 0) { + this.gapSize = gapSize; + this.anchorSize = anchorSize; + this.gapPattern = gapPattern; + this.anchorPattern = anchorPattern; + this.anchorRoughness = anchorRoughness; + this.depthDown = depthDown; + this.depthUp = depthUp; + this.depthPositionedPatterns = this.renderDepths(); + this.axisPositionedPatterns = new ArrayList<>(angles.size()); + + for (float angle : angles) { + List positions = this.renderPositions(angle); + this.axisPositionedPatterns.add(positions); + } + + Vector3i min = null; + Vector3i max = null; + + for (List direction : this.axisPositionedPatterns) { + for (GapPattern.PositionedPattern pos : direction) { + if (min == null) { + min = pos.position.clone(); + max = pos.position.clone(); + } else { + min = Vector3i.min(min, pos.position); + max = Vector3i.max(max, pos.position); + } + } + } + + if (max == null) { + this.readSpaceSize = new SpaceSize(new Vector3i(), new Vector3i()); + } else { + max.add(1, 1, 1); + this.readSpaceSize = new SpaceSize(min, max); + } + } else { + throw new IllegalArgumentException("negative sizes"); + } + } + + @Nonnull + private List renderDepths() { + ArrayList positions = new ArrayList<>(); + Vector3i pointer = new Vector3i(); + int stepsDown = this.depthDown - 1; + + for (int i = 0; i < this.depthDown; i++) { + pointer.add(0, -1, 0); + positions.add(new GapPattern.PositionedPattern(this.gapPattern, pointer.clone())); + } + + pointer = new Vector3i(); + int stepsUp = this.depthUp - 1; + + for (int i = 0; i < this.depthUp; i++) { + pointer.add(0, 1, 0); + positions.add(new GapPattern.PositionedPattern(this.gapPattern, pointer.clone())); + } + + return positions; + } + + @Nonnull + private List renderPositions(float angle) { + ArrayList positions = new ArrayList<>(); + positions.addAll(this.renderHalfPositions(angle)); + positions.addAll(this.renderHalfPositions((float) Math.PI + angle)); + ArrayList uniquePositions = new ArrayList<>(positions.size()); + HashSet positionsSet = new HashSet<>(); + + for (GapPattern.PositionedPattern e : positions) { + if (!positionsSet.contains(e.position)) { + uniquePositions.add(e); + positionsSet.add(e.position); + } + } + + return uniquePositions; + } + + @Nonnull + private List renderHalfPositions(float angle) { + ArrayList positions = new ArrayList<>(); + double halfGap = this.gapSize / 2.0 - 1.0 - this.anchorRoughness; + halfGap = Math.max(0.0, halfGap); + double halfWall = this.anchorSize / 2.0; + Vector3d pointer = new Vector3d(0.5, 0.5, 0.5); + Vector3d mov = new Vector3d(0.0, 0.0, -1.0); + mov.rotateY(angle); + double stepSize = 0.5; + mov.setLength(stepSize); + int steps = (int)(halfGap / stepSize); + + for (int s = 0; s < steps; s++) { + pointer.add(mov); + positions.add(new GapPattern.PositionedPattern(this.gapPattern, pointer.toVector3i())); + } + + positions.add(new GapPattern.PositionedPattern(this.gapPattern, new Vector3i())); + pointer = mov.clone().setLength(halfGap).add(0.5, 0.5, 0.5); + positions.add(new GapPattern.PositionedPattern(this.gapPattern, pointer.toVector3i())); + Vector3d anchor = mov.clone().setLength(this.gapSize / 2.0); + pointer = anchor.clone().add(0.5, 0.5, 0.5); + positions.add(new GapPattern.PositionedPattern(this.anchorPattern, anchor.toVector3i())); + mov.rotateY((float) (Math.PI / 2)); + steps = (int)(halfWall / stepSize); + + for (int s = 0; s < steps; s++) { + pointer.add(mov); + positions.add(new GapPattern.PositionedPattern(this.anchorPattern, pointer.toVector3i())); + } + + Vector3d wallTip = anchor.clone().add(0.5, 0.5, 0.5); + wallTip.add(mov.clone().setLength(halfWall)); + positions.add(new GapPattern.PositionedPattern(this.anchorPattern, wallTip.toVector3i())); + mov.scale(-1.0); + pointer = anchor.clone().add(0.5, 0.5, 0.5); + + for (int s = 0; s < steps; s++) { + pointer.add(mov); + positions.add(new GapPattern.PositionedPattern(this.anchorPattern, pointer.toVector3i())); + } + + wallTip = anchor.clone().add(0.5, 0.5, 0.5); + wallTip.add(mov.clone().setLength(halfWall)); + positions.add(new GapPattern.PositionedPattern(this.anchorPattern, wallTip.toVector3i())); + return positions; + } + + @Override + public boolean matches(@Nonnull Pattern.Context context) { + Vector3i childPosition = new Vector3i(); + Pattern.Context childContext = new Pattern.Context(context); + childContext.position = childPosition; + + for (GapPattern.PositionedPattern entry : this.depthPositionedPatterns) { + childPosition.assign(entry.position).add(context.position); + if (!entry.pattern.matches(childContext)) { + return false; + } + } + + for (List patternsInDirection : this.axisPositionedPatterns) { + boolean matchesDirection = true; + + for (GapPattern.PositionedPattern entryx : patternsInDirection) { + childPosition.assign(entryx.position).add(context.position); + if (!entryx.pattern.matches(context)) { + matchesDirection = false; + break; + } + } + + if (matchesDirection) { + return true; + } + } + + return false; + } + + @Nonnull + @Override + public SpaceSize readSpace() { + return this.readSpaceSize.clone(); + } + + public static class PositionedPattern { + private Vector3i position; + private Pattern pattern; + + public PositionedPattern(@Nonnull Pattern pattern, @Nonnull Vector3i position) { + this.pattern = pattern; + this.position = position.clone(); + } + + public int getX() { + return this.position.x; + } + + public int getY() { + return this.position.y; + } + + public int getZ() { + return this.position.z; + } + + public Pattern getPattern() { + return this.pattern; + } + + @Nonnull + protected GapPattern.PositionedPattern clone() { + return new GapPattern.PositionedPattern(this.pattern, this.position.clone()); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/MaterialPattern.java b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/MaterialPattern.java new file mode 100644 index 0000000..f11956b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/MaterialPattern.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.builtin.hytalegenerator.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class MaterialPattern extends Pattern { + private static final SpaceSize READ_SPACE_SIZE = new SpaceSize(new Vector3i(0, 0, 0), new Vector3i(1, 0, 1)); + private final Material material; + + public MaterialPattern(@Nonnull Material material) { + this.material = material; + } + + @Override + public boolean matches(@Nonnull Pattern.Context context) { + if (!context.materialSpace.isInsideSpace(context.position)) { + return false; + } else { + Material material = context.materialSpace.getContent(context.position); + return this.material.solid().blockId == material.solid().blockId && this.material.fluid().fluidId == material.fluid().fluidId; + } + } + + @Override + public SpaceSize readSpace() { + return READ_SPACE_SIZE.clone(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/MaterialSetPattern.java b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/MaterialSetPattern.java new file mode 100644 index 0000000..7692c2f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/MaterialSetPattern.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.builtin.hytalegenerator.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.MaterialSet; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class MaterialSetPattern extends Pattern { + private static final SpaceSize READ_SPACE_SIZE = new SpaceSize(new Vector3i(0, 0, 0), new Vector3i(1, 0, 1)); + private final MaterialSet materialSet; + + public MaterialSetPattern(@Nonnull MaterialSet materialSet) { + this.materialSet = materialSet; + } + + @Override + public boolean matches(@Nonnull Pattern.Context context) { + if (!context.materialSpace.isInsideSpace(context.position)) { + return false; + } else { + Material material = context.materialSpace.getContent(context.position); + int hash = material.hashMaterialIds(); + return this.materialSet.test(hash); + } + } + + @Override + public SpaceSize readSpace() { + return READ_SPACE_SIZE.clone(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/NotPattern.java b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/NotPattern.java new file mode 100644 index 0000000..76f7ec9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/NotPattern.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.builtin.hytalegenerator.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import javax.annotation.Nonnull; + +public class NotPattern extends Pattern { + @Nonnull + private final Pattern pattern; + private final SpaceSize readSpaceSize; + + public NotPattern(@Nonnull Pattern pattern) { + this.pattern = pattern; + this.readSpaceSize = pattern.readSpace(); + } + + @Override + public boolean matches(@Nonnull Pattern.Context context) { + return !this.pattern.matches(context); + } + + @Nonnull + @Override + public SpaceSize readSpace() { + return this.readSpaceSize.clone(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/OffsetPattern.java b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/OffsetPattern.java new file mode 100644 index 0000000..752edae --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/OffsetPattern.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class OffsetPattern extends Pattern { + @Nonnull + private final Pattern pattern; + @Nonnull + private final Vector3i offset; + @Nonnull + private final SpaceSize readSpaceSize; + + public OffsetPattern(@Nonnull Pattern pattern, @Nonnull Vector3i offset) { + this.pattern = pattern; + this.offset = offset; + this.readSpaceSize = pattern.readSpace().moveBy(offset); + } + + @Override + public boolean matches(@Nonnull Pattern.Context context) { + Pattern.Context childContext = new Pattern.Context(context); + childContext.position = context.position.clone().add(this.offset); + return this.pattern.matches(childContext); + } + + @Nonnull + @Override + public SpaceSize readSpace() { + return this.readSpaceSize.clone(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/OrPattern.java b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/OrPattern.java new file mode 100644 index 0000000..dca7e84 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/OrPattern.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.hytalegenerator.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import java.util.List; +import javax.annotation.Nonnull; + +public class OrPattern extends Pattern { + @Nonnull + private final Pattern[] patterns; + private final SpaceSize readSpaceSize; + + public OrPattern(@Nonnull List patterns) { + if (patterns.isEmpty()) { + this.patterns = new Pattern[0]; + this.readSpaceSize = SpaceSize.empty(); + } else { + this.patterns = new Pattern[patterns.size()]; + SpaceSize spaceAcc = patterns.getFirst().readSpace(); + + for (int i = 0; i < patterns.size(); i++) { + Pattern pattern = patterns.get(i); + this.patterns[i] = pattern; + spaceAcc = SpaceSize.merge(spaceAcc, pattern.readSpace()); + } + + this.readSpaceSize = spaceAcc; + } + } + + @Override + public boolean matches(@Nonnull Pattern.Context context) { + for (Pattern pattern : this.patterns) { + if (pattern.matches(context)) { + return true; + } + } + + return false; + } + + @Nonnull + @Override + public SpaceSize readSpace() { + return this.readSpaceSize.clone(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/Pattern.java b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/Pattern.java new file mode 100644 index 0000000..18623a8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/Pattern.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.builtin.hytalegenerator.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public abstract class Pattern { + public Pattern() { + } + + public abstract boolean matches(@Nonnull Pattern.Context var1); + + public abstract SpaceSize readSpace(); + + @Nonnull + public static Pattern noPattern() { + final SpaceSize space = new SpaceSize(new Vector3i(0, 0, 0), new Vector3i(0, 0, 0)); + return new Pattern() { + @Override + public boolean matches(@Nonnull Pattern.Context context) { + return false; + } + + @Nonnull + @Override + public SpaceSize readSpace() { + return space; + } + }; + } + + @Nonnull + public static Pattern yesPattern() { + final SpaceSize space = new SpaceSize(new Vector3i(0, 0, 0), new Vector3i(0, 0, 0)); + return new Pattern() { + @Override + public boolean matches(@Nonnull Pattern.Context context) { + return true; + } + + @Nonnull + @Override + public SpaceSize readSpace() { + return space; + } + }; + } + + public static class Context { + public Vector3i position; + public VoxelSpace materialSpace; + public WorkerIndexer.Id workerId; + + public Context(@Nonnull Vector3i position, @Nonnull VoxelSpace materialSpace, WorkerIndexer.Id workerId) { + this.position = position; + this.materialSpace = materialSpace; + this.workerId = workerId; + } + + public Context(@Nonnull Pattern.Context other) { + this.position = other.position; + this.materialSpace = other.materialSpace; + this.workerId = other.workerId; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/SurfacePattern.java b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/SurfacePattern.java new file mode 100644 index 0000000..687c7f5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/SurfacePattern.java @@ -0,0 +1,172 @@ +package com.hypixel.hytale.builtin.hytalegenerator.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class SurfacePattern extends Pattern { + @Nonnull + private final Pattern wallPattern; + @Nonnull + private final Pattern originPattern; + @Nonnull + private final SpaceSize readSpaceSize; + @Nonnull + private final List surfacePositions; + @Nonnull + private final List originPositions; + + public SurfacePattern( + @Nonnull Pattern surfacePattern, + @Nonnull Pattern originPattern, + double surfaceRadius, + double originRadius, + @Nonnull SurfacePattern.Facing facing, + int surfaceGap, + int originGap + ) { + this.wallPattern = surfacePattern; + this.originPattern = originPattern; + int surfaceY = -1 - surfaceGap; + this.surfacePositions = new ArrayList<>(1); + + for (int x = -((int)surfaceRadius) - 1; x <= (int)surfaceRadius + 1; x++) { + for (int z = -((int)surfaceRadius) - 1; z <= (int)surfaceRadius + 1; z++) { + if (!(Calculator.distance(x, z, 0.0, 0.0) > surfaceRadius)) { + Vector3i position = new Vector3i(x, surfaceY, z); + this.surfacePositions.add(position); + } + } + } + + int originY = originGap; + this.originPositions = new ArrayList<>(1); + + for (int x = -((int)originRadius) - 1; x <= (int)originRadius + 1; x++) { + for (int zx = -((int)originRadius) - 1; zx <= (int)originRadius + 1; zx++) { + if (!(Calculator.distance(x, zx, 0.0, 0.0) > originRadius)) { + Vector3i position = new Vector3i(x, originY, zx); + this.originPositions.add(position); + } + } + } + + for (Vector3i pos : this.surfacePositions) { + this.applyFacing(pos, facing); + } + + for (Vector3i pos : this.originPositions) { + this.applyFacing(pos, facing); + } + + SpaceSize floorSpace = surfacePattern.readSpace(); + + for (Vector3i pos : this.surfacePositions) { + floorSpace = SpaceSize.merge(floorSpace, new SpaceSize(pos)); + } + + floorSpace = SpaceSize.stack(floorSpace, surfacePattern.readSpace()); + SpaceSize originSpace = originPattern.readSpace(); + + for (Vector3i pos : this.originPositions) { + originSpace = SpaceSize.merge(originSpace, new SpaceSize(pos)); + } + + originSpace = SpaceSize.stack(originSpace, originPattern.readSpace()); + this.readSpaceSize = SpaceSize.merge(floorSpace, originSpace); + } + + private void applyFacing(@Nonnull Vector3i pos, @Nonnull SurfacePattern.Facing facing) { + switch (facing) { + case D: + this.toD(pos); + break; + case E: + this.toE(pos); + break; + case W: + this.toW(pos); + break; + case S: + this.toS(pos); + break; + case N: + this.toN(pos); + } + } + + private void toD(@Nonnull Vector3i pos) { + pos.y = -pos.y; + } + + private void toN(@Nonnull Vector3i pos) { + int y = pos.y; + pos.y = pos.z; + pos.z = y; + } + + private void toS(@Nonnull Vector3i pos) { + this.toN(pos); + pos.z = -pos.z; + } + + private void toW(@Nonnull Vector3i pos) { + int y = pos.y; + pos.y = -pos.x; + pos.x = y; + } + + private void toE(@Nonnull Vector3i pos) { + this.toW(pos); + pos.x = -pos.x; + } + + @Override + public boolean matches(@Nonnull Pattern.Context context) { + Vector3i childPosition = context.position.clone(); + Pattern.Context childContext = new Pattern.Context(context); + childContext.position = childPosition; + + for (Vector3i pos : this.originPositions) { + childPosition.assign(pos).add(context.position); + if (!this.originPattern.matches(childContext)) { + return false; + } + } + + for (Vector3i posx : this.surfacePositions) { + childPosition.assign(posx).add(context.position); + if (!this.wallPattern.matches(childContext)) { + return false; + } + } + + return true; + } + + @Nonnull + @Override + public SpaceSize readSpace() { + return this.readSpaceSize.clone(); + } + + public static enum Facing { + U, + D, + E, + W, + S, + N; + + @Nonnull + public static Codec CODEC = new EnumCodec<>(SurfacePattern.Facing.class, EnumCodec.EnumStyle.LEGACY); + + private Facing() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/WallPattern.java b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/WallPattern.java new file mode 100644 index 0000000..0e4f7a3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/patterns/WallPattern.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.builtin.hytalegenerator.patterns; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class WallPattern extends Pattern { + @Nonnull + private final Pattern wallPattern; + @Nonnull + private final Pattern originPattern; + @Nonnull + private final List directions; + private final boolean matchAll; + private final SpaceSize readSpaceSize; + + public WallPattern(@Nonnull Pattern wallPattern, @Nonnull Pattern originPattern, @Nonnull List wallDirections, boolean matchAll) { + this.wallPattern = wallPattern; + this.originPattern = originPattern; + this.directions = new ArrayList<>(wallDirections); + this.matchAll = matchAll; + SpaceSize originSpace = originPattern.readSpace(); + SpaceSize wallSpace = wallPattern.readSpace(); + SpaceSize totalSpace = originSpace; + + for (WallPattern.WallDirection d : this.directions) { + SpaceSize directionedWallSpace = switch (d) { + case N -> wallSpace.clone().moveBy(new Vector3i(0, 0, -1)); + case S -> wallSpace.clone().moveBy(new Vector3i(0, 0, 1)); + case E -> wallSpace.clone().moveBy(new Vector3i(1, 0, 0)); + case W -> wallSpace.clone().moveBy(new Vector3i(-1, 0, 0)); + }; + totalSpace = SpaceSize.merge(totalSpace, directionedWallSpace); + } + + this.readSpaceSize = totalSpace; + } + + @Override + public boolean matches(@Nonnull Pattern.Context context) { + for (WallPattern.WallDirection direction : this.directions) { + boolean matches = this.matches(context, direction); + if (this.matchAll && !matches) { + return false; + } + + if (matches) { + return true; + } + } + + return false; + } + + private boolean matches(@Nonnull Pattern.Context context, @Nonnull WallPattern.WallDirection direction) { + Vector3i wallPosition = context.position.clone(); + switch (direction) { + case N: + wallPosition.z--; + break; + case S: + wallPosition.z++; + break; + case E: + wallPosition.x++; + break; + case W: + wallPosition.x--; + } + + Pattern.Context wallContext = new Pattern.Context(context); + wallContext.position = wallPosition; + return this.originPattern.matches(context) && this.wallPattern.matches(wallContext); + } + + @Nonnull + @Override + public SpaceSize readSpace() { + return this.readSpaceSize.clone(); + } + + public static enum WallDirection { + N, + S, + E, + W; + + public static final Codec CODEC = new EnumCodec<>(WallPattern.WallDirection.class, EnumCodec.EnumStyle.LEGACY); + + private WallDirection() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/plugin/Handle.java b/src/com/hypixel/hytale/builtin/hytalegenerator/plugin/Handle.java new file mode 100644 index 0000000..fb78e64 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/plugin/Handle.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.builtin.hytalegenerator.plugin; + +import com.hypixel.hytale.builtin.hytalegenerator.chunkgenerator.ChunkRequest; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen; +import com.hypixel.hytale.server.core.universe.world.worldgen.WorldGenTimingsCollector; +import java.util.concurrent.CompletableFuture; +import java.util.function.LongPredicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Handle implements IWorldGen { + @Nonnull + private final HytaleGenerator plugin; + @Nonnull + private final ChunkRequest.GeneratorProfile profile; + + public Handle(@Nonnull HytaleGenerator plugin, @Nonnull ChunkRequest.GeneratorProfile profile) { + this.plugin = plugin; + this.profile = profile; + } + + @Override + public CompletableFuture generate(int seed, long index, int x, int z, LongPredicate stillNeeded) { + ChunkRequest.Arguments arguments = new ChunkRequest.Arguments(seed, index, x, z, stillNeeded); + this.profile.setSeed(seed); + ChunkRequest request = new ChunkRequest(this.profile, arguments); + return this.plugin.submitChunkRequest(request); + } + + @Nonnull + public ChunkRequest.GeneratorProfile getProfile() { + return this.profile; + } + + @Override + public Transform[] getSpawnPoints(int seed) { + return new Transform[]{this.profile.spawnPosition().clone()}; + } + + @Nonnull + @Override + public ISpawnProvider getDefaultSpawnProvider(int seed) { + return IWorldGen.super.getDefaultSpawnProvider(seed); + } + + @Nullable + @Override + public WorldGenTimingsCollector getTimings() { + return null; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/plugin/HandleProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/plugin/HandleProvider.java new file mode 100644 index 0000000..4171a1c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/plugin/HandleProvider.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.builtin.hytalegenerator.plugin; + +import com.hypixel.hytale.builtin.hytalegenerator.chunkgenerator.ChunkRequest; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen; +import com.hypixel.hytale.server.core.universe.world.worldgen.WorldGenLoadException; +import com.hypixel.hytale.server.core.universe.world.worldgen.provider.IWorldGenProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HandleProvider implements IWorldGenProvider { + public static final String ID = "HytaleGenerator"; + public static final String DEFAULT_WORLD_STRUCTURE_NAME = "Default"; + public static final Transform DEFAULT_PLAYER_SPAWN = new Transform(0.0, 140.0, 0.0); + @Nonnull + private final HytaleGenerator plugin; + @Nonnull + private String worldStructureName = "Default"; + @Nonnull + private Transform playerSpawn = DEFAULT_PLAYER_SPAWN; + + public HandleProvider(@Nonnull HytaleGenerator plugin) { + this.plugin = plugin; + } + + public void setWorldStructureName(@Nullable String worldStructureName) { + this.worldStructureName = worldStructureName; + } + + public void setPlayerSpawn(@Nullable Transform worldSpawn) { + if (worldSpawn == null) { + this.playerSpawn = DEFAULT_PLAYER_SPAWN; + } else { + this.playerSpawn = worldSpawn.clone(); + } + } + + @Nonnull + public String getWorldStructureName() { + return this.worldStructureName; + } + + @Nonnull + public Transform getPlayerSpawn() { + return this.playerSpawn; + } + + @Override + public IWorldGen getGenerator() throws WorldGenLoadException { + return new Handle(this.plugin, new ChunkRequest.GeneratorProfile(this.worldStructureName, this.playerSpawn.clone(), 0)); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/plugin/HytaleGenerator.java b/src/com/hypixel/hytale/builtin/hytalegenerator/plugin/HytaleGenerator.java new file mode 100644 index 0000000..74fdc1c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/plugin/HytaleGenerator.java @@ -0,0 +1,335 @@ +package com.hypixel.hytale.builtin.hytalegenerator.plugin; + +import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil; +import com.hypixel.hytale.builtin.hytalegenerator.PropField; +import com.hypixel.hytale.builtin.hytalegenerator.assets.AssetManager; +import com.hypixel.hytale.builtin.hytalegenerator.assets.SettingsAsset; +import com.hypixel.hytale.builtin.hytalegenerator.assets.worldstructures.WorldStructureAsset; +import com.hypixel.hytale.builtin.hytalegenerator.biome.BiomeType; +import com.hypixel.hytale.builtin.hytalegenerator.biomemap.BiomeMap; +import com.hypixel.hytale.builtin.hytalegenerator.chunkgenerator.ChunkGenerator; +import com.hypixel.hytale.builtin.hytalegenerator.chunkgenerator.ChunkRequest; +import com.hypixel.hytale.builtin.hytalegenerator.chunkgenerator.FallbackGenerator; +import com.hypixel.hytale.builtin.hytalegenerator.commands.ViewportCommand; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.material.SolidMaterial; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.NStagedChunkGenerator; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NCountedPixelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NEntityBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NSimplePixelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NVoxelBuffer; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NParametrizedBufferType; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NBiomeDistanceStage; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NBiomeStage; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NEnvironmentStage; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NPropStage; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NStage; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NTerrainStage; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NTintStage; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.provider.IWorldGenProvider; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.Semaphore; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + +public class HytaleGenerator extends JavaPlugin { + private AssetManager assetManager; + private Runnable assetReloadListener; + private final Map generators = new HashMap<>(); + private final Semaphore chunkGenerationSemaphore = new Semaphore(1); + private int concurrency; + private ExecutorService mainExecutor; + private ThreadPoolExecutor concurrentExecutor; + + @Override + protected void start() { + super.start(); + if (this.mainExecutor == null) { + this.loadExecutors(this.assetManager.getSettingsAsset()); + } + + if (this.assetReloadListener == null) { + this.assetReloadListener = () -> this.reloadGenerators(); + this.assetManager.registerReloadListener(this.assetReloadListener); + } + } + + @Nonnull + public CompletableFuture submitChunkRequest(@Nonnull ChunkRequest request) { + return CompletableFuture.supplyAsync(() -> { + GeneratedChunk var3; + try { + this.chunkGenerationSemaphore.acquireUninterruptibly(); + ChunkGenerator generator = this.getGenerator(request.generatorProfile()); + var3 = generator.generate(request.arguments()); + } finally { + this.chunkGenerationSemaphore.release(); + } + + return var3; + }, this.mainExecutor).handle((r, e) -> { + if (e == null) { + return (GeneratedChunk)r; + } else { + LoggerUtil.logException("generation of the chunk with request " + request, e, LoggerUtil.getLogger()); + return FallbackGenerator.INSTANCE.generate(request.arguments()); + } + }); + } + + @Override + protected void setup() { + this.assetManager = new AssetManager(this.getEventRegistry(), this.getLogger()); + BuilderCodec generatorProvider = BuilderCodec.builder(HandleProvider.class, () -> new HandleProvider(this)) + .documentation("The standard generator for Hytale.") + .append(new KeyedCodec<>("WorldStructure", Codec.STRING), HandleProvider::setWorldStructureName, HandleProvider::getWorldStructureName) + .documentation("The world structure to be used for this world.") + .add() + .append(new KeyedCodec<>("PlayerSpawn", Transform.CODEC), HandleProvider::setPlayerSpawn, HandleProvider::getPlayerSpawn) + .add() + .build(); + IWorldGenProvider.CODEC.register("HytaleGenerator", HandleProvider.class, generatorProvider); + this.getCommandRegistry().registerCommand(new ViewportCommand(this.assetManager)); + } + + @Nonnull + public NStagedChunkGenerator createStagedChunkGenerator( + @Nonnull ChunkRequest.GeneratorProfile generatorProfile, @Nonnull WorldStructureAsset worldStructureAsset, @Nonnull SettingsAsset settingsAsset + ) { + WorkerIndexer workerIndexer = new WorkerIndexer(this.concurrency); + SeedBox seed = new SeedBox(generatorProfile.seed()); + MaterialCache materialCache = new MaterialCache(); + BiomeMap biomeMap = worldStructureAsset.buildBiomeMap(new WorldStructureAsset.Argument(materialCache, seed, workerIndexer)); + worldStructureAsset.cleanUp(); + NStagedChunkGenerator.Builder generatorBuilder = new NStagedChunkGenerator.Builder(); + List allBiomes = biomeMap.allPossibleValues(); + List allRuntimes = new ArrayList<>(getAllPossibleRuntimeIndices(allBiomes)); + allRuntimes.sort(Comparator.naturalOrder()); + int bufferTypeIndexCounter = 0; + NParametrizedBufferType biome_bufferType = new NParametrizedBufferType( + "Biome", bufferTypeIndexCounter++, NBiomeStage.bufferClass, NBiomeStage.biomeTypeClass, () -> new NCountedPixelBuffer<>(NBiomeStage.biomeTypeClass) + ); + NStage biomeStage = new NBiomeStage("BiomeStage", biome_bufferType, biomeMap); + generatorBuilder.appendStage(biomeStage); + NParametrizedBufferType biomeDistance_bufferType = new NParametrizedBufferType( + "BiomeDistance", + bufferTypeIndexCounter++, + NBiomeDistanceStage.biomeDistanceBufferClass, + NBiomeDistanceStage.biomeDistanceClass, + () -> new NSimplePixelBuffer<>(NBiomeDistanceStage.biomeDistanceClass) + ); + int MAX_BIOME_DISTANCE_RADIUS = 512; + int interpolationRadius = Math.clamp((long)(worldStructureAsset.getBiomeTransitionDistance() / 2), 0, 512); + int biomeEdgeRadius = Math.clamp((long)worldStructureAsset.getMaxBiomeEdgeDistance(), 0, 512); + int maxDistance = Math.max(interpolationRadius, biomeEdgeRadius); + NStage biomeDistanceStage = new NBiomeDistanceStage("BiomeDistanceStage", biome_bufferType, biomeDistance_bufferType, maxDistance); + generatorBuilder.appendStage(biomeDistanceStage); + int materialBufferIndexCounter = 0; + NParametrizedBufferType material0_bufferType = generatorBuilder.MATERIAL_OUTPUT_BUFFER_TYPE; + if (!allRuntimes.isEmpty()) { + material0_bufferType = new NParametrizedBufferType( + "Material" + materialBufferIndexCounter, + bufferTypeIndexCounter++, + NTerrainStage.materialBufferClass, + NTerrainStage.materialClass, + () -> new NVoxelBuffer<>(NTerrainStage.materialClass) + ); + materialBufferIndexCounter++; + } + + NStage terrainStage = new NTerrainStage( + "TerrainStage", biome_bufferType, biomeDistance_bufferType, material0_bufferType, interpolationRadius, materialCache, workerIndexer + ); + generatorBuilder.appendStage(terrainStage); + NParametrizedBufferType materialInput_bufferType = material0_bufferType; + NBufferType entityInput_bufferType = null; + + for (int i = 0; i < allRuntimes.size() - 1; i++) { + int runtime = allRuntimes.get(i); + String runtimeString = Integer.toString(runtime); + NParametrizedBufferType materialOutput_bufferType = new NParametrizedBufferType( + "Material" + materialBufferIndexCounter, + bufferTypeIndexCounter++, + NTerrainStage.materialBufferClass, + NTerrainStage.materialClass, + () -> new NVoxelBuffer<>(NTerrainStage.materialClass) + ); + NBufferType entityOutput_bufferType = new NBufferType( + "Entity" + materialBufferIndexCounter, bufferTypeIndexCounter++, NEntityBuffer.class, NEntityBuffer::new + ); + NStage propStage = new NPropStage( + "PropStage" + runtimeString, + biome_bufferType, + biomeDistance_bufferType, + materialInput_bufferType, + entityInput_bufferType, + materialOutput_bufferType, + entityOutput_bufferType, + materialCache, + allBiomes, + runtime + ); + generatorBuilder.appendStage(propStage); + materialInput_bufferType = materialOutput_bufferType; + entityInput_bufferType = entityOutput_bufferType; + materialBufferIndexCounter++; + } + + if (!allRuntimes.isEmpty()) { + int runtime = allRuntimes.getLast(); + String runtimeString = Integer.toString(runtime); + NStage propStage = new NPropStage( + "PropStage" + runtimeString, + biome_bufferType, + biomeDistance_bufferType, + materialInput_bufferType, + entityInput_bufferType, + generatorBuilder.MATERIAL_OUTPUT_BUFFER_TYPE, + generatorBuilder.ENTITY_OUTPUT_BUFFER_TYPE, + materialCache, + allBiomes, + runtime + ); + generatorBuilder.appendStage(propStage); + } + + NStage tintStage = new NTintStage("TintStage", biome_bufferType, generatorBuilder.TINT_OUTPUT_BUFFER_TYPE); + generatorBuilder.appendStage(tintStage); + NStage environmentStage = new NEnvironmentStage("EnvironmentStage", biome_bufferType, generatorBuilder.ENVIRONMENT_OUTPUT_BUFFER_TYPE); + generatorBuilder.appendStage(environmentStage); + double bufferCapacityFactor = Math.max(0.0, settingsAsset.getBufferCapacityFactor()); + double targetViewDistance = Math.max(0.0, settingsAsset.getTargetViewDistance()); + double targetPlayerCount = Math.max(0.0, settingsAsset.getTargetPlayerCount()); + Set statsCheckpoints = new HashSet<>(settingsAsset.getStatsCheckpoints()); + return generatorBuilder.withStats("WorldStructure Name: " + generatorProfile.worldStructureName(), statsCheckpoints) + .withMaterialCache(materialCache) + .withConcurrentExecutor(this.concurrentExecutor, workerIndexer) + .withBufferCapacity(bufferCapacityFactor, targetViewDistance, targetPlayerCount) + .build(); + } + + @Nonnull + private static Set getAllPossibleRuntimeIndices(@Nonnull List biomes) { + Set allRuntimes = new HashSet<>(); + + for (BiomeType biome : biomes) { + for (PropField propField : biome.getPropFields()) { + allRuntimes.add(propField.getRuntime()); + } + } + + return allRuntimes; + } + + @Nonnull + private ChunkGenerator getGenerator(@Nonnull ChunkRequest.GeneratorProfile profile) { + ChunkGenerator generator = this.generators.get(profile); + if (generator == null) { + if (profile.worldStructureName() == null) { + LoggerUtil.getLogger().warning("World Structure asset not loaded."); + return FallbackGenerator.INSTANCE; + } + + WorldStructureAsset worldStructureAsset = this.assetManager.getWorldStructureAsset(profile.worldStructureName()); + if (worldStructureAsset == null) { + LoggerUtil.getLogger().warning("World Structure asset not found: " + profile.worldStructureName()); + return FallbackGenerator.INSTANCE; + } + + SettingsAsset settingsAsset = this.assetManager.getSettingsAsset(); + if (settingsAsset == null) { + LoggerUtil.getLogger().warning("Settings asset not found."); + return FallbackGenerator.INSTANCE; + } + + generator = this.createStagedChunkGenerator(profile, worldStructureAsset, settingsAsset); + this.generators.put(profile, generator); + } + + return generator; + } + + private void loadExecutors(@Nonnull SettingsAsset settingsAsset) { + int newConcurrency = getConcurrency(settingsAsset); + if (newConcurrency != this.concurrency || this.mainExecutor == null || this.concurrentExecutor == null) { + this.concurrency = newConcurrency; + if (this.mainExecutor == null) { + this.mainExecutor = Executors.newSingleThreadExecutor(); + } + + if (this.concurrentExecutor != null && !this.concurrentExecutor.isShutdown()) { + try { + this.concurrentExecutor.shutdown(); + if (!this.concurrentExecutor.awaitTermination(1L, TimeUnit.MINUTES)) { + } + } catch (InterruptedException var4) { + throw new RuntimeException(var4); + } + } + + this.concurrentExecutor = new ThreadPoolExecutor(this.concurrency, this.concurrency, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), r -> { + Thread t = new Thread(r, "HytaleGenerator-Worker"); + t.setPriority(1); + t.setDaemon(true); + return t; + }); + if (this.mainExecutor == null || this.mainExecutor.isShutdown()) { + this.mainExecutor = Executors.newSingleThreadExecutor(); + } + } + } + + private static int getConcurrency(@Nonnull SettingsAsset settingsAsset) { + int concurrencySetting = settingsAsset.getCustomConcurrency(); + int availableProcessors = Runtime.getRuntime().availableProcessors(); + int value = 1; + if (concurrencySetting < 1) { + value = Math.max(availableProcessors, 1); + } else { + if (concurrencySetting > availableProcessors) { + LoggerUtil.getLogger().warning("Concurrency setting " + concurrencySetting + " exceeds available processors " + availableProcessors); + } + + value = concurrencySetting; + } + + return value; + } + + private void reloadGenerators() { + try { + this.chunkGenerationSemaphore.acquireUninterruptibly(); + this.loadExecutors(this.assetManager.getSettingsAsset()); + this.generators.clear(); + } finally { + this.chunkGenerationSemaphore.release(); + } + + LoggerUtil.getLogger().info("Reloaded HytaleGenerator."); + } + + public HytaleGenerator(@Nonnull JavaPluginInit init) { + super(init); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/AnchorPositionProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/AnchorPositionProvider.java new file mode 100644 index 0000000..5fd2ce9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/AnchorPositionProvider.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.builtin.hytalegenerator.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class AnchorPositionProvider extends PositionProvider { + @Nonnull + private final PositionProvider positionProvider; + private final boolean isReversed; + + public AnchorPositionProvider(@Nonnull PositionProvider positionProvider, boolean isReversed) { + this.positionProvider = positionProvider; + this.isReversed = isReversed; + } + + @Override + public void positionsIn(@Nonnull PositionProvider.Context context) { + if (context != null) { + Vector3d anchor = context.anchor; + if (anchor != null) { + Vector3d offsetMin = this.isReversed ? context.minInclusive.clone().add(anchor) : context.minInclusive.clone().addScaled(anchor, -1.0); + Vector3d offsetMax = this.isReversed ? context.maxExclusive.clone().add(anchor) : context.maxExclusive.clone().addScaled(anchor, -1.0); + PositionProvider.Context childContext = new PositionProvider.Context(offsetMin, offsetMax, p -> { + Vector3d newPoint = p.clone(); + if (this.isReversed) { + newPoint.addScaled(anchor, -1.0); + } else { + newPoint.add(anchor); + } + + if (VectorUtil.isInside(newPoint, context.minInclusive, context.maxExclusive)) { + context.consumer.accept(newPoint); + } + }, context.anchor, context.workerId); + this.positionProvider.positionsIn(childContext); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/BaseHeightPositionProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/BaseHeightPositionProvider.java new file mode 100644 index 0000000..73b7c1a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/BaseHeightPositionProvider.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.builtin.hytalegenerator.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.BiDouble2DoubleFunction; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class BaseHeightPositionProvider extends PositionProvider { + @Nonnull + private final BiDouble2DoubleFunction baseHeightFunction; + private final double maxYInput; + private final double minYInput; + @Nonnull + private final PositionProvider positionProvider; + + public BaseHeightPositionProvider( + @Nonnull BiDouble2DoubleFunction baseHeightFunction, @Nonnull PositionProvider positionProvider, double minYInput, double maxYInput + ) { + maxYInput = Math.max(minYInput, maxYInput); + this.baseHeightFunction = baseHeightFunction; + this.positionProvider = positionProvider; + this.maxYInput = maxYInput; + this.minYInput = minYInput; + } + + @Override + public void positionsIn(@Nonnull PositionProvider.Context context) { + PositionProvider.Context childContext = new PositionProvider.Context(context); + childContext.consumer = position -> { + Vector3d offsetP = position.clone(); + offsetP.y = offsetP.y + this.baseHeightFunction.apply(position.x, position.z); + if (VectorUtil.isInside(offsetP, context.minInclusive, context.maxExclusive)) { + context.consumer.accept(offsetP); + } + }; + this.positionProvider.positionsIn(childContext); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/FieldFunctionOccurrencePositionProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/FieldFunctionOccurrencePositionProvider.java new file mode 100644 index 0000000..387de4f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/FieldFunctionOccurrencePositionProvider.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.builtin.hytalegenerator.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.SeedGenerator; +import com.hypixel.hytale.math.util.FastRandom; +import javax.annotation.Nonnull; + +public class FieldFunctionOccurrencePositionProvider extends PositionProvider { + public static final double FP_RESOLUTION = 100.0; + @Nonnull + private final Density field; + @Nonnull + private final PositionProvider positionProvider; + @Nonnull + private final SeedGenerator seedGenerator; + + public FieldFunctionOccurrencePositionProvider(@Nonnull Density field, @Nonnull PositionProvider positionProvider, int seed) { + this.field = field; + this.positionProvider = positionProvider; + this.seedGenerator = new SeedGenerator(seed); + } + + @Override + public void positionsIn(@Nonnull PositionProvider.Context context) { + PositionProvider.Context childContext = new PositionProvider.Context(context); + childContext.consumer = position -> { + Density.Context densityContext = new Density.Context(); + densityContext.position = position; + densityContext.positionsAnchor = context.anchor; + densityContext.workerId = context.workerId; + double discardChance = 1.0 - this.field.process(densityContext); + FastRandom random = new FastRandom(this.seedGenerator.seedAt(position.x, position.y, position.z, 100.0)); + if (!(discardChance > random.nextDouble())) { + context.consumer.accept(position); + } + }; + this.positionProvider.positionsIn(childContext); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/FieldFunctionPositionProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/FieldFunctionPositionProvider.java new file mode 100644 index 0000000..cd097df --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/FieldFunctionPositionProvider.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.hytalegenerator.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class FieldFunctionPositionProvider extends PositionProvider { + @Nonnull + private final Density field; + @Nonnull + private final List delimiters; + @Nonnull + private final PositionProvider positionProvider; + + public FieldFunctionPositionProvider(@Nonnull Density field, @Nonnull PositionProvider positionProvider) { + this.field = field; + this.positionProvider = positionProvider; + this.delimiters = new ArrayList<>(); + } + + public void addDelimiter(double min, double max) { + FieldFunctionPositionProvider.Delimiter d = new FieldFunctionPositionProvider.Delimiter(); + d.min = min; + d.max = max; + this.delimiters.add(d); + } + + @Override + public void positionsIn(@Nonnull PositionProvider.Context context) { + PositionProvider.Context childContext = new PositionProvider.Context(context); + childContext.consumer = p -> { + Density.Context densityContext = new Density.Context(); + densityContext.position = p; + densityContext.positionsAnchor = context.anchor; + densityContext.workerId = context.workerId; + double value = this.field.process(densityContext); + + for (FieldFunctionPositionProvider.Delimiter d : this.delimiters) { + if (d.isInside(value)) { + context.consumer.accept(p); + } + } + }; + this.positionProvider.positionsIn(childContext); + } + + private static class Delimiter { + double min; + double max; + + private Delimiter() { + } + + boolean isInside(double v) { + return v >= this.min && v < this.max; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/ListPositionProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/ListPositionProvider.java new file mode 100644 index 0000000..4f2ed34 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/ListPositionProvider.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.hytalegenerator.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class ListPositionProvider extends PositionProvider { + private List positions3i; + private List positions3d; + + private ListPositionProvider() { + } + + @Nonnull + public static ListPositionProvider from3i(@Nonnull List positions3i) { + ListPositionProvider instance = new ListPositionProvider(); + instance.positions3i = new ArrayList<>(); + instance.positions3i.addAll(positions3i); + instance.positions3d = new ArrayList<>(positions3i.size()); + instance.positions3i.forEach(p -> instance.positions3d.add(p.toVector3d())); + return instance; + } + + @Nonnull + public static ListPositionProvider from3d(@Nonnull List positions3d) { + ListPositionProvider instance = new ListPositionProvider(); + instance.positions3d = new ArrayList<>(); + instance.positions3d.addAll(positions3d); + instance.positions3i = new ArrayList<>(positions3d.size()); + instance.positions3d.forEach(p -> instance.positions3i.add(p.toVector3i())); + return instance; + } + + @Override + public void positionsIn(@Nonnull PositionProvider.Context context) { + for (Vector3d p : this.positions3d) { + if (VectorUtil.isInside(p, context.minInclusive, context.maxExclusive)) { + } + + context.consumer.accept(p); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/Mesh2DPositionProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/Mesh2DPositionProvider.java new file mode 100644 index 0000000..e138277 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/Mesh2DPositionProvider.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.builtin.hytalegenerator.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.fields.points.PointProvider; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class Mesh2DPositionProvider extends PositionProvider { + @Nonnull + private final PointProvider pointGenerator; + private final int y; + + public Mesh2DPositionProvider(@Nonnull PointProvider positionProvider, int y) { + this.pointGenerator = positionProvider; + this.y = y; + } + + @Override + public void positionsIn(@Nonnull PositionProvider.Context context) { + if (!(context.minInclusive.y > this.y) && !(context.maxExclusive.y <= this.y)) { + Vector2d min2d = new Vector2d(context.minInclusive.x, context.minInclusive.z); + Vector2d max2d = new Vector2d(context.maxExclusive.x, context.maxExclusive.z); + this.pointGenerator.points2d(min2d, max2d, point -> context.consumer.accept(new Vector3d(point.x, this.y, point.y))); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/Mesh3DPositionProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/Mesh3DPositionProvider.java new file mode 100644 index 0000000..a9a98d4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/Mesh3DPositionProvider.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.builtin.hytalegenerator.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.fields.points.PointProvider; +import javax.annotation.Nonnull; + +public class Mesh3DPositionProvider extends PositionProvider { + @Nonnull + private final PointProvider pointGenerator; + + public Mesh3DPositionProvider(@Nonnull PointProvider positionProvider) { + this.pointGenerator = positionProvider; + } + + @Override + public void positionsIn(@Nonnull PositionProvider.Context context) { + this.pointGenerator.points3d(context.minInclusive, context.maxExclusive, context.consumer); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/OffsetPositionProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/OffsetPositionProvider.java new file mode 100644 index 0000000..d93743c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/OffsetPositionProvider.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.builtin.hytalegenerator.positionproviders; + +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class OffsetPositionProvider extends PositionProvider { + @Nonnull + private final Vector3i offset3i; + @Nonnull + private final Vector3d offset3d; + @Nonnull + private final PositionProvider positionProvider; + + public OffsetPositionProvider(@Nonnull Vector3i offset, @Nonnull PositionProvider positionProvider) { + this.offset3i = offset.clone(); + this.positionProvider = positionProvider; + this.offset3d = this.offset3i.toVector3d(); + } + + public OffsetPositionProvider(@Nonnull Vector3d offset, @Nonnull PositionProvider positionProvider) { + this.offset3d = offset.clone(); + this.positionProvider = positionProvider; + this.offset3i = this.offset3d.toVector3i(); + } + + @Override + public void positionsIn(@Nonnull PositionProvider.Context context) { + Vector3d windowMin = context.minInclusive.clone(); + Vector3d windowMax = context.maxExclusive.clone(); + windowMin.subtract(this.offset3d); + windowMax.subtract(this.offset3d); + PositionProvider.Context childContext = new PositionProvider.Context(); + childContext.minInclusive = windowMin; + childContext.maxExclusive = windowMax; + childContext.consumer = p -> { + Vector3d offsetP = p.clone(); + offsetP.add(this.offset3d); + context.consumer.accept(offsetP); + }; + childContext.workerId = context.workerId; + this.positionProvider.positionsIn(childContext); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/PositionProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/PositionProvider.java new file mode 100644 index 0000000..bb86338 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/PositionProvider.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.builtin.hytalegenerator.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3d; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class PositionProvider { + public PositionProvider() { + } + + public abstract void positionsIn(@Nonnull PositionProvider.Context var1); + + @Nonnull + public static PositionProvider noPositionProvider() { + return new PositionProvider() { + @Override + public void positionsIn(@Nonnull PositionProvider.Context context) { + } + }; + } + + public static class Context { + public static final Consumer EMPTY_CONSUMER = p -> {}; + public Vector3d minInclusive; + public Vector3d maxExclusive; + public Consumer consumer; + @Nullable + public Vector3d anchor; + public WorkerIndexer.Id workerId; + + public Context() { + this.minInclusive = Vector3d.ZERO; + this.maxExclusive = Vector3d.ZERO; + this.consumer = EMPTY_CONSUMER; + this.anchor = null; + this.workerId = WorkerIndexer.Id.UNKNOWN; + } + + public Context( + @Nonnull Vector3d minInclusive, + @Nonnull Vector3d maxExclusive, + @Nonnull Consumer consumer, + @Nullable Vector3d anchor, + WorkerIndexer.Id workerId + ) { + this.minInclusive = minInclusive; + this.maxExclusive = maxExclusive; + this.consumer = consumer; + this.anchor = anchor; + this.workerId = workerId; + } + + public Context(@Nonnull PositionProvider.Context other) { + this.minInclusive = other.minInclusive; + this.maxExclusive = other.maxExclusive; + this.consumer = other.consumer; + this.anchor = other.anchor; + this.workerId = other.workerId; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/SpherePositionProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/SpherePositionProvider.java new file mode 100644 index 0000000..f16b33c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/SpherePositionProvider.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.builtin.hytalegenerator.positionproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import javax.annotation.Nonnull; + +public class SpherePositionProvider extends PositionProvider { + @Nonnull + private final PositionProvider positionProvider; + private final double range; + + public SpherePositionProvider(@Nonnull PositionProvider positionProvider, double range) { + this.positionProvider = positionProvider; + this.range = range; + } + + @Override + public void positionsIn(@Nonnull PositionProvider.Context context) { + PositionProvider.Context childContext = new PositionProvider.Context(context); + childContext.consumer = position -> { + double distance = position.length(); + if (VectorUtil.isInside(position, context.minInclusive, context.maxExclusive) && distance <= this.range) { + context.consumer.accept(position); + } + }; + this.positionProvider.positionsIn(childContext); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/UnionPositionProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/UnionPositionProvider.java new file mode 100644 index 0000000..1c9f3fd --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/UnionPositionProvider.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.builtin.hytalegenerator.positionproviders; + +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class UnionPositionProvider extends PositionProvider { + @Nonnull + private final List positionProviders = new ArrayList<>(); + + public UnionPositionProvider(@Nonnull List positionProviders) { + this.positionProviders.addAll(positionProviders); + } + + @Override + public void positionsIn(@Nonnull PositionProvider.Context context) { + for (PositionProvider position : this.positionProviders) { + position.positionsIn(context); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/VerticalEliminatorPositionProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/VerticalEliminatorPositionProvider.java new file mode 100644 index 0000000..c203c9e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/VerticalEliminatorPositionProvider.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.builtin.hytalegenerator.positionproviders; + +import javax.annotation.Nonnull; + +public class VerticalEliminatorPositionProvider extends PositionProvider { + private final int maxY; + private final int minY; + @Nonnull + private final PositionProvider positionProvider; + + public VerticalEliminatorPositionProvider(int minY, int maxY, @Nonnull PositionProvider positionProvider) { + this.minY = minY; + this.maxY = maxY; + this.positionProvider = positionProvider; + } + + @Override + public void positionsIn(@Nonnull PositionProvider.Context context) { + PositionProvider.Context childContext = new PositionProvider.Context(context); + childContext.consumer = positions -> { + if (!(positions.y < this.minY) && !(positions.y >= this.maxY)) { + context.consumer.accept(positions); + } + }; + this.positionProvider.positionsIn(childContext); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/cached/CacheThreadMemory.java b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/cached/CacheThreadMemory.java new file mode 100644 index 0000000..a46f461 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/cached/CacheThreadMemory.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.builtin.hytalegenerator.positionproviders.cached; + +import com.hypixel.hytale.math.vector.Vector3d; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +public class CacheThreadMemory { + Map sections; + LinkedList expirationList; + int size; + + public CacheThreadMemory(int size) { + if (size < 0) { + throw new IllegalArgumentException(); + } else { + this.sections = new HashMap<>(size); + this.expirationList = new LinkedList<>(); + this.size = size; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/cached/CachedPositionProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/cached/CachedPositionProvider.java new file mode 100644 index 0000000..ffd2544 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/positionproviders/cached/CachedPositionProvider.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.builtin.hytalegenerator.positionproviders.cached; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.ArrayList; +import javax.annotation.Nonnull; + +public class CachedPositionProvider extends PositionProvider { + @Nonnull + private final PositionProvider positionProvider; + private final int sectionSize; + private WorkerIndexer.Data threadData; + + public CachedPositionProvider(@Nonnull PositionProvider positionProvider, int sectionSize, int cacheSize, boolean useInternalThreadData, int threadCount) { + if (sectionSize > 0 && cacheSize >= 0 && threadCount > 0) { + this.positionProvider = positionProvider; + this.sectionSize = sectionSize; + this.threadData = new WorkerIndexer.Data<>(threadCount, () -> new CacheThreadMemory(cacheSize)); + } else { + throw new IllegalArgumentException(); + } + } + + @Override + public void positionsIn(@Nonnull PositionProvider.Context context) { + this.get(context); + } + + public void get(@Nonnull PositionProvider.Context context) { + CacheThreadMemory cachedData = this.threadData.get(context.workerId); + Vector3i minSection = this.sectionAddress(context.minInclusive); + Vector3i maxSection = this.sectionAddress(context.maxExclusive); + Vector3i sectionAddress = minSection.clone(); + + for (sectionAddress.x = minSection.x; sectionAddress.x <= maxSection.x; sectionAddress.x++) { + for (sectionAddress.z = minSection.z; sectionAddress.z <= maxSection.z; sectionAddress.z++) { + for (sectionAddress.y = minSection.y; sectionAddress.y <= maxSection.y; sectionAddress.y++) { + long key = HashUtil.hash(sectionAddress.x, sectionAddress.y, sectionAddress.z); + Vector3d[] section = cachedData.sections.get(key); + if (section == null) { + Vector3d sectionMin = this.sectionMin(sectionAddress); + Vector3d sectionMax = sectionMin.clone().add(this.sectionSize, this.sectionSize, this.sectionSize); + ArrayList generatedPositions = new ArrayList<>(); + PositionProvider.Context childContext = new PositionProvider.Context(sectionMin, sectionMax, generatedPositions::add, null, context.workerId); + this.positionProvider.positionsIn(childContext); + section = new Vector3d[generatedPositions.size()]; + generatedPositions.toArray(section); + cachedData.sections.put(key, section); + cachedData.expirationList.addFirst(key); + if (cachedData.expirationList.size() > cachedData.size) { + long removedKey = cachedData.expirationList.removeLast(); + cachedData.sections.remove(removedKey); + } + } + + for (Vector3d position : section) { + if (VectorUtil.isInside(position, context.minInclusive, context.maxExclusive)) { + context.consumer.accept(position.clone()); + } + } + } + } + } + } + + @Nonnull + private Vector3i sectionAddress(@Nonnull Vector3d pointer) { + Vector3i address = pointer.toVector3i(); + address.x = this.sectionFloor(address.x) / this.sectionSize; + address.y = this.sectionFloor(address.y) / this.sectionSize; + address.z = this.sectionFloor(address.z) / this.sectionSize; + return address; + } + + @Nonnull + private Vector3d sectionMin(@Nonnull Vector3i sectionAddress) { + Vector3d min = sectionAddress.toVector3d(); + min.x = min.x * this.sectionSize; + min.y = min.y * this.sectionSize; + min.z = min.z * this.sectionSize; + return min; + } + + private int toSectionAddress(double position) { + int positionAddress = (int)position; + positionAddress = this.sectionFloor(positionAddress); + return positionAddress / this.sectionSize; + } + + public int sectionFloor(int voxelAddress) { + return voxelAddress < 0 ? voxelAddress - voxelAddress % this.sectionSize - this.sectionSize : voxelAddress - voxelAddress % this.sectionSize; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/propdistributions/Assignments.java b/src/com/hypixel/hytale/builtin/hytalegenerator/propdistributions/Assignments.java new file mode 100644 index 0000000..11af0ac --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/propdistributions/Assignments.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.hytalegenerator.propdistributions; + +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3d; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; + +public abstract class Assignments { + public Assignments() { + } + + public abstract Prop propAt(@Nonnull Vector3d var1, @Nonnull WorkerIndexer.Id var2, double var3); + + public abstract int getRuntime(); + + public abstract List getAllPossibleProps(); + + @Nonnull + public static Assignments noPropDistribution(final int runtime) { + return new Assignments() { + @Nonnull + @Override + public Prop propAt(@Nonnull Vector3d position, @Nonnull WorkerIndexer.Id id, double distanceTOBiomeEdge) { + return Prop.noProp(); + } + + @Override + public int getRuntime() { + return runtime; + } + + @Nonnull + @Override + public List getAllPossibleProps() { + return Collections.singletonList(Prop.noProp()); + } + }; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/propdistributions/ConstantAssignments.java b/src/com/hypixel/hytale/builtin/hytalegenerator/propdistributions/ConstantAssignments.java new file mode 100644 index 0000000..852da5e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/propdistributions/ConstantAssignments.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.hytalegenerator.propdistributions; + +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3d; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; + +public class ConstantAssignments extends Assignments { + @Nonnull + private final Prop prop; + private final int runtime; + + public ConstantAssignments(@Nonnull Prop prop, int runtime) { + this.prop = prop; + this.runtime = runtime; + } + + @Nonnull + @Override + public Prop propAt(@Nonnull Vector3d position, @Nonnull WorkerIndexer.Id id, double distanceTOBiomeEdge) { + return this.prop; + } + + @Override + public int getRuntime() { + return this.runtime; + } + + @Nonnull + @Override + public List getAllPossibleProps() { + return Collections.singletonList(this.prop); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/propdistributions/FieldFunctionAssignments.java b/src/com/hypixel/hytale/builtin/hytalegenerator/propdistributions/FieldFunctionAssignments.java new file mode 100644 index 0000000..f4743e0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/propdistributions/FieldFunctionAssignments.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.builtin.hytalegenerator.propdistributions; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3d; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class FieldFunctionAssignments extends Assignments { + @Nonnull + private final Density density; + @Nonnull + private final List fieldDelimiters; + private final int runtime; + + public FieldFunctionAssignments(@Nonnull Density functionTree, @Nonnull List fieldDelimiters, int runtime) { + this.runtime = runtime; + this.density = functionTree; + this.fieldDelimiters = new ArrayList<>(fieldDelimiters); + } + + @Override + public Prop propAt(@Nonnull Vector3d position, @Nonnull WorkerIndexer.Id id, double distanceTOBiomeEdge) { + if (this.fieldDelimiters.isEmpty()) { + return Prop.noProp(); + } else { + Density.Context context = new Density.Context(); + context.position = position; + context.workerId = id; + context.distanceToBiomeEdge = distanceTOBiomeEdge; + double fieldValue = this.density.process(context); + + for (FieldFunctionAssignments.FieldDelimiter fd : this.fieldDelimiters) { + if (fd.isInside(fieldValue)) { + return fd.assignments.propAt(position, id, distanceTOBiomeEdge); + } + } + + return Prop.noProp(); + } + } + + @Override + public int getRuntime() { + return this.runtime; + } + + @Nonnull + @Override + public List getAllPossibleProps() { + ArrayList list = new ArrayList<>(); + + for (FieldFunctionAssignments.FieldDelimiter f : this.fieldDelimiters) { + list.addAll(f.assignments.getAllPossibleProps()); + } + + return list; + } + + public static class FieldDelimiter { + double top; + double bottom; + Assignments assignments; + + public FieldDelimiter(@Nonnull Assignments propDistributions, double bottom, double top) { + this.bottom = bottom; + this.top = top; + this.assignments = propDistributions; + } + + boolean isInside(double fieldValue) { + return fieldValue < this.top && fieldValue >= this.bottom; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/propdistributions/SandwichAssignments.java b/src/com/hypixel/hytale/builtin/hytalegenerator/propdistributions/SandwichAssignments.java new file mode 100644 index 0000000..58e2825 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/propdistributions/SandwichAssignments.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.builtin.hytalegenerator.propdistributions; + +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3d; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class SandwichAssignments extends Assignments { + @Nonnull + private final List verticalDelimiters; + private final int runtime; + + public SandwichAssignments(@Nonnull List verticalDelimiters, int runtime) { + this.runtime = runtime; + this.verticalDelimiters = new ArrayList<>(verticalDelimiters); + } + + @Override + public Prop propAt(@Nonnull Vector3d position, @Nonnull WorkerIndexer.Id id, double distanceTOBiomeEdge) { + if (this.verticalDelimiters.isEmpty()) { + return Prop.noProp(); + } else { + for (SandwichAssignments.VerticalDelimiter fd : this.verticalDelimiters) { + if (fd.isInside(position.y)) { + return fd.assignments.propAt(position, id, distanceTOBiomeEdge); + } + } + + return Prop.noProp(); + } + } + + @Override + public int getRuntime() { + return this.runtime; + } + + @Nonnull + @Override + public List getAllPossibleProps() { + ArrayList list = new ArrayList<>(); + + for (SandwichAssignments.VerticalDelimiter f : this.verticalDelimiters) { + list.addAll(f.assignments.getAllPossibleProps()); + } + + return list; + } + + public static class VerticalDelimiter { + double maxY; + double minY; + Assignments assignments; + + public VerticalDelimiter(@Nonnull Assignments propDistributions, double minY, double maxY) { + this.minY = minY; + this.maxY = maxY; + this.assignments = propDistributions; + } + + boolean isInside(double fieldValue) { + return fieldValue < this.maxY && fieldValue >= this.minY; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/propdistributions/WeightedAssignments.java b/src/com/hypixel/hytale/builtin/hytalegenerator/propdistributions/WeightedAssignments.java new file mode 100644 index 0000000..93ccac8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/propdistributions/WeightedAssignments.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.builtin.hytalegenerator.propdistributions; + +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.WeightedMap; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.SeedGenerator; +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.util.FastRandom; +import com.hypixel.hytale.math.vector.Vector3d; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class WeightedAssignments extends Assignments { + @Nonnull + private final WeightedMap weightedDistributions; + @Nonnull + private final SeedGenerator seedGenerator; + private final int runtime; + private final double noneProbability; + + public WeightedAssignments(@Nonnull WeightedMap props, int seed, double noneProbability, int runtime) { + this.weightedDistributions = new WeightedMap<>(props); + this.runtime = runtime; + this.seedGenerator = new SeedGenerator(seed); + this.noneProbability = noneProbability; + } + + @Override + public Prop propAt(@Nonnull Vector3d position, @Nonnull WorkerIndexer.Id id, double distanceTOBiomeEdge) { + if (this.weightedDistributions.size() == 0) { + return Prop.noProp(); + } else { + long x = (long)(position.x * 10000.0); + long y = (long)(position.y * 10000.0); + long z = (long)(position.z * 10000.0); + FastRandom rand = new FastRandom(this.seedGenerator.seedAt(x, y, z)); + return rand.nextDouble() < this.noneProbability ? Prop.noProp() : this.weightedDistributions.pick(rand).propAt(position, id, distanceTOBiomeEdge); + } + } + + @Override + public int getRuntime() { + return this.runtime; + } + + @Nonnull + @Override + public List getAllPossibleProps() { + ArrayList list = new ArrayList<>(); + + for (Assignments d : this.weightedDistributions.allElements()) { + list.addAll(d.getAllPossibleProps()); + } + + return list; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/BoxProp.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/BoxProp.java new file mode 100644 index 0000000..1d905f7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/BoxProp.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.builtin.hytalegenerator.conveyor.stagedconveyor.ContextDependency; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.GridUtils; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.List; +import javax.annotation.Nonnull; + +public class BoxProp extends Prop { + private final Vector3i range; + private final Material material; + private final Scanner scanner; + private final Pattern pattern; + private final ContextDependency contextDependency; + private final Bounds3i writeBounds_voxelGrid; + private final Bounds3i boxBounds_voxelGrid; + + public BoxProp(Vector3i range, @Nonnull Material material, @Nonnull Scanner scanner, @Nonnull Pattern pattern) { + if (VectorUtil.isAnySmaller(range, new Vector3i())) { + throw new IllegalArgumentException("negative range"); + } else { + this.range = range.clone(); + this.material = material; + this.scanner = scanner; + this.pattern = pattern; + SpaceSize writeSpace = new SpaceSize(new Vector3i(-range.x - 1, 0, -range.z - 1), new Vector3i(range.x + 2, 0, range.z + 2)); + writeSpace = SpaceSize.stack(writeSpace, scanner.readSpaceWith(pattern)); + Vector3i writeRange = writeSpace.getRange(); + Vector3i readRange = scanner.readSpaceWith(pattern).getRange(); + this.contextDependency = new ContextDependency(readRange, writeRange); + this.writeBounds_voxelGrid = this.contextDependency.getTotalPropBounds_voxelGrid(); + this.boxBounds_voxelGrid = GridUtils.createBounds_fromVector_originVoxelInclusive(range); + } + } + + public PositionListScanResult scan(@Nonnull Vector3i position, @Nonnull VoxelSpace materialSpace, @Nonnull WorkerIndexer.Id id) { + Scanner.Context scannerContext = new Scanner.Context(position, this.pattern, materialSpace, id); + List validPositions = this.scanner.scan(scannerContext); + return new PositionListScanResult(validPositions); + } + + @Override + public void place(@Nonnull Prop.Context context) { + List positions = PositionListScanResult.cast(context.scanResult).getPositions(); + if (positions != null) { + Bounds3i writeSpaceBounds_voxelGrid = context.materialSpace.getBounds(); + + for (Vector3i position : positions) { + Bounds3i localBoxBounds_voxelGrid = this.boxBounds_voxelGrid.clone().offset(position); + if (localBoxBounds_voxelGrid.intersects(writeSpaceBounds_voxelGrid)) { + this.place(position, context.materialSpace); + } + } + } + } + + private void place(@Nonnull Vector3i position, @Nonnull VoxelSpace materialSpace) { + Vector3i min = position.clone().add(-this.range.x, 0, -this.range.z); + Vector3i max = position.clone().add(this.range.x, this.range.y + this.range.y, this.range.z); + + for (int x = min.x; x <= max.x; x++) { + for (int y = min.y; y <= max.y; y++) { + for (int z = min.z; z <= max.z; z++) { + if (materialSpace.isInsideSpace(x, y, z)) { + materialSpace.set(this.material, x, y, z); + } + } + } + } + } + + @Override + public ContextDependency getContextDependency() { + return this.contextDependency.clone(); + } + + @Nonnull + @Override + public Bounds3i getWriteBounds() { + return this.writeBounds_voxelGrid; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/ClusterProp.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/ClusterProp.java new file mode 100644 index 0000000..7ce52ee --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/ClusterProp.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.conveyor.stagedconveyor.ContextDependency; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.WeightedMap; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.WindowVoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.SeedGenerator; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.EntityContainer; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.util.FastRandom; +import com.hypixel.hytale.math.vector.Vector3i; +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import java.util.List; +import javax.annotation.Nonnull; + +public class ClusterProp extends Prop { + private final Double2DoubleFunction weightCurve; + private final SeedGenerator seedGenerator; + private final WeightedMap propWeightedMap; + private final int range; + private final ContextDependency contextDependency; + private final Pattern pattern; + private final Scanner scanner; + private final Bounds3i writeBounds_voxelGrid; + + public ClusterProp( + int range, + @Nonnull Double2DoubleFunction weightCurve, + int seed, + @Nonnull WeightedMap propWeightedMap, + @Nonnull Pattern pattern, + @Nonnull Scanner scanner + ) { + if (range < 0) { + throw new IllegalArgumentException("negative range"); + } else { + this.range = range; + this.seedGenerator = new SeedGenerator(seed); + this.weightCurve = weightCurve; + this.pattern = pattern; + this.scanner = scanner; + this.propWeightedMap = new WeightedMap<>(); + propWeightedMap.forEach((prop, weight) -> { + ContextDependency contextDependency = prop.getContextDependency(); + Vector3i readRangex = contextDependency.getReadRange(); + Vector3i writeRangex = contextDependency.getWriteRange(); + if (readRangex.x <= 0 && readRangex.z <= 0 && writeRangex.x <= 0 && writeRangex.z <= 0) { + this.propWeightedMap.add(prop, propWeightedMap.get(prop)); + } + }); + Vector3i readRange = scanner.readSpaceWith(pattern).getRange(); + Vector3i writeRange = new Vector3i(range + readRange.x, 0, range + readRange.z); + this.contextDependency = new ContextDependency(readRange, writeRange); + this.writeBounds_voxelGrid = this.contextDependency.getTotalPropBounds_voxelGrid(); + } + } + + public PositionListScanResult scan(@Nonnull Vector3i position, @Nonnull VoxelSpace materialSpace, @Nonnull WorkerIndexer.Id id) { + Scanner.Context scannerContext = new Scanner.Context(position, this.pattern, materialSpace, id); + List validPositions = this.scanner.scan(scannerContext); + return new PositionListScanResult(validPositions); + } + + @Override + public void place(@Nonnull Prop.Context context) { + List positions = PositionListScanResult.cast(context.scanResult).getPositions(); + if (positions != null) { + for (Vector3i position : positions) { + this.place(position, context.materialSpace, context.entityBuffer, context.workerId, context.distanceFromBiomeEdge); + } + } + } + + private void place( + @Nonnull Vector3i position, + @Nonnull VoxelSpace materialSpace, + @Nonnull EntityContainer entityBuffer, + @Nonnull WorkerIndexer.Id id, + double distanceFromBiomeEdge + ) { + WindowVoxelSpace columnSpace = new WindowVoxelSpace<>(materialSpace); + FastRandom random = new FastRandom(this.seedGenerator.seedAt(position.x, position.z)); + + for (int x = position.x - this.range; x < position.x + this.range; x++) { + for (int z = position.z - this.range; z < position.z + this.range; z++) { + double distance = Calculator.distance(x, z, position.x, position.z); + double density = this.weightCurve.get(distance); + if (!(random.nextDouble() > density)) { + Prop pickedProp = this.propWeightedMap.pick(random); + if (materialSpace.isInsideSpace(x, materialSpace.minY(), z)) { + columnSpace.setWindow(x, materialSpace.minY(), z, x + 1, materialSpace.maxY(), z + 1); + ScanResult propScanResult = pickedProp.scan(new Vector3i(x, position.y, z), columnSpace, id); + Prop.Context childContext = new Prop.Context(propScanResult, columnSpace, entityBuffer, id, distanceFromBiomeEdge); + pickedProp.place(childContext); + } + } + } + } + } + + @Override + public ContextDependency getContextDependency() { + return this.contextDependency.clone(); + } + + @Nonnull + @Override + public Bounds3i getWriteBounds() { + return this.writeBounds_voxelGrid; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/ColumnProp.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/ColumnProp.java new file mode 100644 index 0000000..0ddb254 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/ColumnProp.java @@ -0,0 +1,134 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props; + +import com.hypixel.hytale.builtin.hytalegenerator.BlockMask; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.builtin.hytalegenerator.conveyor.stagedconveyor.ContextDependency; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.Directionality; +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.RotatedPosition; +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.RotatedPositionsScanResult; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class ColumnProp extends Prop { + private final int[] yPositions; + private final Material[] blocks0; + private final Material[] blocks90; + private final Material[] blocks180; + private final Material[] blocks270; + private final BlockMask blockMask; + private final Scanner scanner; + private final ContextDependency contextDependency; + private final Directionality directionality; + private final Bounds3i writeBounds_voxelGrid; + + public ColumnProp( + @Nonnull List propYPositions, + @Nonnull List blocks, + @Nonnull BlockMask blockMask, + @Nonnull Scanner scanner, + @Nonnull Directionality directionality, + @Nonnull MaterialCache materialCache + ) { + if (propYPositions.size() != blocks.size()) { + throw new IllegalArgumentException("blocks and positions sizes don't match"); + } else { + this.blockMask = blockMask; + this.yPositions = new int[propYPositions.size()]; + this.blocks0 = new Material[blocks.size()]; + this.blocks90 = new Material[blocks.size()]; + this.blocks180 = new Material[blocks.size()]; + this.blocks270 = new Material[blocks.size()]; + + for (int i = 0; i < this.yPositions.length; i++) { + this.yPositions[i] = propYPositions.get(i); + this.blocks0[i] = blocks.get(i); + this.blocks90[i] = new Material(materialCache.getSolidMaterialRotatedY(blocks.get(i).solid(), Rotation.Ninety), blocks.get(i).fluid()); + this.blocks180[i] = new Material(materialCache.getSolidMaterialRotatedY(blocks.get(i).solid(), Rotation.OneEighty), blocks.get(i).fluid()); + this.blocks270[i] = new Material(materialCache.getSolidMaterialRotatedY(blocks.get(i).solid(), Rotation.TwoSeventy), blocks.get(i).fluid()); + } + + this.scanner = scanner; + this.directionality = directionality; + SpaceSize writeSpace = new SpaceSize(new Vector3i(0, 0, 0), new Vector3i(1, 0, 1)); + writeSpace = SpaceSize.stack(writeSpace, scanner.readSpaceWith(directionality.getGeneralPattern())); + Vector3i writeRange = writeSpace.getRange(); + Vector3i readRange = directionality.getReadRangeWith(scanner); + this.contextDependency = new ContextDependency(readRange, writeRange); + this.writeBounds_voxelGrid = this.contextDependency.getTotalPropBounds_voxelGrid(); + } + } + + @Override + public ScanResult scan(@Nonnull Vector3i position, @Nonnull VoxelSpace materialSpace, @Nonnull WorkerIndexer.Id id) { + Scanner.Context scannerContext = new Scanner.Context(position, this.directionality.getGeneralPattern(), materialSpace, id); + List validPositions = this.scanner.scan(scannerContext); + Vector3i patternPosition = new Vector3i(); + Pattern.Context patternContext = new Pattern.Context(patternPosition, materialSpace, id); + RotatedPositionsScanResult scanResult = new RotatedPositionsScanResult(new ArrayList<>()); + + for (Vector3i validPosition : validPositions) { + patternPosition.assign(validPosition); + PrefabRotation rotation = this.directionality.getRotationAt(patternContext); + if (rotation != null) { + scanResult.positions.add(new RotatedPosition(validPosition.x, validPosition.y, validPosition.z, rotation)); + } + } + + return scanResult; + } + + @Override + public void place(@Nonnull Prop.Context context) { + for (RotatedPosition position : RotatedPositionsScanResult.cast(context.scanResult).positions) { + this.place(position, context.materialSpace); + } + } + + private void place(@Nonnull RotatedPosition position, @Nonnull VoxelSpace materialSpace) { + PrefabRotation rotation = position.rotation; + + Material[] blocks = switch (rotation) { + case ROTATION_0 -> this.blocks0; + case ROTATION_90 -> this.blocks90; + case ROTATION_180 -> this.blocks180; + case ROTATION_270 -> this.blocks270; + }; + + for (int i = 0; i < this.yPositions.length; i++) { + int y = this.yPositions[i] + position.y; + Material propBlock = blocks[i]; + if (materialSpace.isInsideSpace(position.x, y, position.z) && this.blockMask.canPlace(propBlock)) { + Material worldMaterial = materialSpace.getContent(position.x, y, position.z); + + assert worldMaterial != null; + + int worldMaterialHash = worldMaterial.hashMaterialIds(); + if (this.blockMask.canReplace(propBlock.hashMaterialIds(), worldMaterialHash)) { + materialSpace.set(propBlock, position.x, y, position.z); + } + } + } + } + + @Override + public ContextDependency getContextDependency() { + return this.contextDependency.clone(); + } + + @Nonnull + @Override + public Bounds3i getWriteBounds() { + return this.writeBounds_voxelGrid; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/DensityProp.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/DensityProp.java new file mode 100644 index 0000000..6730c9e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/DensityProp.java @@ -0,0 +1,196 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props; + +import com.hypixel.hytale.builtin.hytalegenerator.BlockMask; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.builtin.hytalegenerator.conveyor.stagedconveyor.ContextDependency; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.ArrayVoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.List; +import javax.annotation.Nonnull; + +public class DensityProp extends Prop { + private final Vector3i range; + private final Density density; + private final MaterialProvider materialProvider; + private final Scanner scanner; + private final Pattern pattern; + private final ContextDependency contextDependency; + private final BlockMask placementMask; + private final Material defaultMaterial; + private final Bounds3i writeBounds_voxelGrid; + + public DensityProp( + @Nonnull Vector3i range, + @Nonnull Density density, + @Nonnull MaterialProvider materialProvider, + @Nonnull Scanner scanner, + @Nonnull Pattern pattern, + @Nonnull BlockMask placementMask, + @Nonnull Material defaultMaterial + ) { + this.range = range.clone(); + this.density = density; + this.materialProvider = materialProvider; + this.scanner = scanner; + this.pattern = pattern; + this.placementMask = placementMask; + this.defaultMaterial = defaultMaterial; + SpaceSize writeSpace = new SpaceSize(new Vector3i(-range.x - 1, 0, -range.z - 1), new Vector3i(range.x + 2, 0, range.z + 2)); + writeSpace = SpaceSize.stack(writeSpace, scanner.readSpaceWith(pattern)); + Vector3i writeRange = writeSpace.getRange(); + Vector3i readRange = scanner.readSpaceWith(pattern).getRange(); + this.contextDependency = new ContextDependency(readRange, writeRange); + this.writeBounds_voxelGrid = this.contextDependency.getTotalPropBounds_voxelGrid(); + } + + public PositionListScanResult scan(@Nonnull Vector3i position, @Nonnull VoxelSpace materialSpace, @Nonnull WorkerIndexer.Id id) { + Scanner.Context scannerContext = new Scanner.Context(position, this.pattern, materialSpace, id); + List validPositions = this.scanner.scan(scannerContext); + return new PositionListScanResult(validPositions); + } + + @Override + public void place(@Nonnull Prop.Context context) { + List positions = PositionListScanResult.cast(context.scanResult).getPositions(); + if (positions != null) { + for (Vector3i position : positions) { + this.place(position, context.materialSpace, context.workerId); + } + } + } + + private void place(Vector3i position, @Nonnull VoxelSpace materialSpace, @Nonnull WorkerIndexer.Id id) { + Vector3i min = position.clone().add(-this.range.x, -this.range.y, -this.range.z); + Vector3i max = position.clone().add(this.range.x, this.range.y, this.range.z); + Vector3i writeMin = Vector3i.max(min, new Vector3i(materialSpace.minX(), materialSpace.minY(), materialSpace.minZ())); + Vector3i writeMax = Vector3i.min(max, new Vector3i(materialSpace.maxX(), materialSpace.maxY(), materialSpace.maxZ())); + int bottom = min.y; + int top = max.y; + int height = top - bottom; + ArrayVoxelSpace densitySpace = new ArrayVoxelSpace<>(max.x - min.x + 1, max.y - min.y + 1, max.z - min.z + 1); + densitySpace.setOrigin(-min.x, -min.y, -min.z); + Density.Context childContext = new Density.Context(); + childContext.densityAnchor = position.toVector3d(); + childContext.workerId = id; + Vector3i itPosition = new Vector3i(position); + + for (itPosition.x = min.x; itPosition.x <= max.x; itPosition.x++) { + for (itPosition.z = min.z; itPosition.z <= max.z; itPosition.z++) { + for (itPosition.y = min.y; itPosition.y <= max.y; itPosition.y++) { + if (densitySpace.isInsideSpace(itPosition.x, itPosition.y, itPosition.z)) { + childContext.position.x = itPosition.x; + childContext.position.y = itPosition.y; + childContext.position.z = itPosition.z; + double densityValue = this.density.process(childContext); + densitySpace.set(densityValue > 0.0, itPosition.x, itPosition.y, itPosition.z); + } + } + } + } + + for (itPosition.x = min.x; itPosition.x <= max.x; itPosition.x++) { + for (itPosition.z = min.z; itPosition.z <= max.z; itPosition.z++) { + int[] depthIntoCeiling = new int[height + 1]; + int[] depthIntoFloor = new int[height + 1]; + int[] spaceBelowCeiling = new int[height + 1]; + int[] spaceAboveFloor = new int[height + 1]; + + for (itPosition.y = top; itPosition.y >= bottom; itPosition.y--) { + int i = itPosition.y - bottom; + boolean density = densitySpace.getContent(itPosition.x, itPosition.y, itPosition.z); + if (itPosition.y == top) { + if (density) { + depthIntoFloor[i] = 1; + } else { + depthIntoFloor[i] = 0; + } + + spaceAboveFloor[i] = 1073741823; + } else if (density) { + depthIntoFloor[i] = depthIntoFloor[i + 1] + 1; + spaceAboveFloor[i] = spaceAboveFloor[i + 1]; + } else { + depthIntoFloor[i] = 0; + if (densitySpace.getContent(itPosition.x, itPosition.y + 1, itPosition.z)) { + spaceAboveFloor[i] = 0; + } else { + spaceAboveFloor[i] = spaceAboveFloor[i + 1] + 1; + } + } + } + + for (itPosition.y = bottom; itPosition.y < top; itPosition.y++) { + int i = itPosition.y - bottom; + boolean density = densitySpace.getContent(itPosition.x, itPosition.y, itPosition.z); + if (itPosition.y == bottom) { + if (density) { + depthIntoCeiling[i] = 1; + } else { + depthIntoCeiling[i] = 0; + } + + spaceBelowCeiling[i] = Integer.MAX_VALUE; + } else if (density) { + depthIntoCeiling[i] = depthIntoCeiling[i - 1] + 1; + spaceBelowCeiling[i] = spaceBelowCeiling[i - 1]; + } else { + depthIntoCeiling[i] = 0; + if (densitySpace.getContent(itPosition.x, itPosition.y - 1, itPosition.z)) { + spaceBelowCeiling[i] = 0; + } else { + spaceBelowCeiling[i] = spaceBelowCeiling[i - 1] + 1; + } + } + } + + for (itPosition.y = top; itPosition.y >= bottom; itPosition.y--) { + if (itPosition.x >= writeMin.x + && itPosition.y >= writeMin.y + && itPosition.z >= writeMin.z + && itPosition.x < writeMax.x + && itPosition.y < writeMax.y + && itPosition.z < writeMax.z) { + int i = itPosition.y - bottom; + MaterialProvider.Context materialContext = new MaterialProvider.Context( + position, 0.0, depthIntoFloor[i], depthIntoCeiling[i], spaceAboveFloor[i], spaceBelowCeiling[i], id, (functionPosition, workerId) -> { + childContext.position = functionPosition.toVector3d(); + return this.density.process(childContext); + }, childContext.distanceToBiomeEdge + ); + Material material = this.materialProvider.getVoxelTypeAt(materialContext); + if (material == null) { + material = this.defaultMaterial; + } + + if (this.placementMask.canPlace(material)) { + Material worldMaterial = materialSpace.getContent(itPosition.x, itPosition.y, itPosition.z); + int worldMaterialHash = worldMaterial.hashMaterialIds(); + if (this.placementMask.canReplace(material.hashCode(), worldMaterialHash)) { + materialSpace.set(material, itPosition.x, itPosition.y, itPosition.z); + } + } + } + } + } + } + } + + @Override + public ContextDependency getContextDependency() { + return this.contextDependency.clone(); + } + + @Nonnull + @Override + public Bounds3i getWriteBounds() { + return this.writeBounds_voxelGrid; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/PositionListScanResult.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/PositionListScanResult.java new file mode 100644 index 0000000..1ff3a7e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/PositionListScanResult.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props; + +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PositionListScanResult implements ScanResult { + private List positions; + + public PositionListScanResult(@Nullable List positions) { + if (positions != null) { + this.positions = positions; + } + } + + @Nullable + public List getPositions() { + return this.positions; + } + + @Nonnull + public static PositionListScanResult cast(ScanResult scanResult) { + if (!(scanResult instanceof PositionListScanResult)) { + throw new IllegalArgumentException("The provided ScanResult isn't compatible with this prop."); + } else { + return (PositionListScanResult)scanResult; + } + } + + @Override + public boolean isNegative() { + return this.positions == null || this.positions.isEmpty(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/PositionScanResult.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/PositionScanResult.java new file mode 100644 index 0000000..a6bef1a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/PositionScanResult.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props; + +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PositionScanResult implements ScanResult { + private Vector3i position; + + public PositionScanResult(@Nullable Vector3i position) { + if (position != null) { + this.position = position.clone(); + } + } + + @Nullable + public Vector3i getPosition() { + return this.position == null ? null : this.position.clone(); + } + + @Nonnull + public static PositionScanResult cast(ScanResult scanResult) { + if (!(scanResult instanceof PositionScanResult)) { + throw new IllegalArgumentException("The provided ScanResult isn't compatible with this prop."); + } else { + return (PositionScanResult)scanResult; + } + } + + @Override + public boolean isNegative() { + return this.position == null; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/Prop.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/Prop.java new file mode 100644 index 0000000..26cd215 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/Prop.java @@ -0,0 +1,89 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.conveyor.stagedconveyor.ContextDependency; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.EntityContainer; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public abstract class Prop { + public Prop() { + } + + public abstract ScanResult scan(@Nonnull Vector3i var1, @Nonnull VoxelSpace var2, @Nonnull WorkerIndexer.Id var3); + + public abstract void place(@Nonnull Prop.Context var1); + + public abstract ContextDependency getContextDependency(); + + @Nonnull + public abstract Bounds3i getWriteBounds(); + + @Nonnull + public static Prop noProp() { + final ScanResult scanResult = new ScanResult() { + @Override + public boolean isNegative() { + return true; + } + }; + final ContextDependency contextDependency = new ContextDependency(new Vector3i(), new Vector3i()); + final Bounds3i writeBounds_voxelGrid = new Bounds3i(); + return new Prop() { + @Nonnull + @Override + public ScanResult scan(@Nonnull Vector3i position, @Nonnull VoxelSpace materialSpace, @Nonnull WorkerIndexer.Id id) { + return scanResult; + } + + @Override + public void place(@Nonnull Prop.Context context) { + } + + @Nonnull + @Override + public ContextDependency getContextDependency() { + return contextDependency; + } + + @Nonnull + @Override + public Bounds3i getWriteBounds() { + return writeBounds_voxelGrid; + } + }; + } + + public static class Context { + public ScanResult scanResult; + public VoxelSpace materialSpace; + public EntityContainer entityBuffer; + public WorkerIndexer.Id workerId; + public double distanceFromBiomeEdge; + + public Context( + @Nonnull ScanResult scanResult, + @Nonnull VoxelSpace materialSpace, + @Nonnull EntityContainer entityBuffer, + WorkerIndexer.Id workerId, + double distanceFromBiomeEdge + ) { + this.scanResult = scanResult; + this.materialSpace = materialSpace; + this.entityBuffer = entityBuffer; + this.workerId = workerId; + this.distanceFromBiomeEdge = distanceFromBiomeEdge; + } + + public Context(@Nonnull Prop.Context other) { + this.scanResult = other.scanResult; + this.materialSpace = other.materialSpace; + this.entityBuffer = other.entityBuffer; + this.workerId = other.workerId; + this.distanceFromBiomeEdge = other.distanceFromBiomeEdge; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/QueueProp.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/QueueProp.java new file mode 100644 index 0000000..df1c05f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/QueueProp.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.conveyor.stagedconveyor.ContextDependency; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class QueueProp extends Prop { + @Nonnull + private final List props; + @Nonnull + private final ContextDependency contextDependency; + @Nonnull + private final Bounds3i writeBounds_voxelGrid; + + public QueueProp(@Nonnull List propsQueue) { + this.props = new ArrayList<>(propsQueue); + Vector3i writeRange = new Vector3i(); + Vector3i readRange = new Vector3i(); + + for (Prop prop : propsQueue) { + writeRange = Vector3i.max(writeRange, prop.getContextDependency().getWriteRange()); + readRange = Vector3i.max(readRange, prop.getContextDependency().getReadRange()); + } + + this.contextDependency = new ContextDependency(readRange, writeRange); + this.writeBounds_voxelGrid = this.contextDependency.getTotalPropBounds_voxelGrid(); + } + + @Nonnull + @Override + public ScanResult scan(@Nonnull Vector3i position, @Nonnull VoxelSpace materialSpace, @Nonnull WorkerIndexer.Id id) { + QueueProp.QueueScanResult queueScanResult = new QueueProp.QueueScanResult(); + + for (Prop prop : this.props) { + ScanResult propScanResult = prop.scan(position, materialSpace, id); + if (!propScanResult.isNegative()) { + queueScanResult.propScanResult = propScanResult; + queueScanResult.prop = prop; + return queueScanResult; + } + } + + return queueScanResult; + } + + @Override + public void place(@Nonnull Prop.Context context) { + QueueProp.QueueScanResult conditionalScanResult = QueueProp.QueueScanResult.cast(context.scanResult); + if (!conditionalScanResult.isNegative()) { + conditionalScanResult.prop.place(context); + } + } + + @Nonnull + @Override + public ContextDependency getContextDependency() { + return this.contextDependency.clone(); + } + + @Nonnull + @Override + public Bounds3i getWriteBounds() { + return this.writeBounds_voxelGrid; + } + + private static class QueueScanResult implements ScanResult { + ScanResult propScanResult; + Prop prop; + + private QueueScanResult() { + } + + @Nonnull + public static QueueProp.QueueScanResult cast(ScanResult scanResult) { + if (!(scanResult instanceof QueueProp.QueueScanResult)) { + throw new IllegalArgumentException("The provided ScanResult isn't compatible with this prop."); + } else { + return (QueueProp.QueueScanResult)scanResult; + } + } + + @Override + public boolean isNegative() { + return this.propScanResult == null || this.propScanResult.isNegative(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/ScanResult.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/ScanResult.java new file mode 100644 index 0000000..97a6346 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/ScanResult.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props; + +import javax.annotation.Nonnull; + +public interface ScanResult { + ScanResult NONE = new ScanResult() { + @Override + public boolean isNegative() { + return true; + } + }; + + boolean isNegative(); + + @Nonnull + static ScanResult noScanResult() { + return NONE; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/UnionProp.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/UnionProp.java new file mode 100644 index 0000000..4e4aef0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/UnionProp.java @@ -0,0 +1,92 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.conveyor.stagedconveyor.ContextDependency; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class UnionProp extends Prop { + @Nonnull + private final List props; + @Nonnull + private final ContextDependency contextDependency; + @Nonnull + private final Bounds3i writeBounds_voxelGrid; + + public UnionProp(@Nonnull List propChain) { + this.props = new ArrayList<>(propChain); + Vector3i writeRange = new Vector3i(); + Vector3i readRange = new Vector3i(); + + for (Prop prop : propChain) { + writeRange = Vector3i.max(writeRange, prop.getContextDependency().getWriteRange()); + readRange = Vector3i.max(readRange, prop.getContextDependency().getReadRange()); + } + + this.contextDependency = new ContextDependency(readRange, writeRange); + this.writeBounds_voxelGrid = this.contextDependency.getTotalPropBounds_voxelGrid(); + } + + @Nonnull + @Override + public ScanResult scan(@Nonnull Vector3i position, @Nonnull VoxelSpace materialSpace, @Nonnull WorkerIndexer.Id id) { + UnionProp.ChainedScanResult scanResult = new UnionProp.ChainedScanResult(); + scanResult.scanResults = new ArrayList<>(this.props.size()); + + for (Prop prop : this.props) { + scanResult.scanResults.add(prop.scan(position, materialSpace, id)); + } + + return scanResult; + } + + @Override + public void place(@Nonnull Prop.Context context) { + List scanResults = UnionProp.ChainedScanResult.cast(context.scanResult).scanResults; + + for (int i = 0; i < this.props.size(); i++) { + Prop prop = this.props.get(i); + Prop.Context childContext = new Prop.Context(context); + childContext.scanResult = scanResults.get(i); + prop.place(childContext); + } + } + + @Nonnull + @Override + public ContextDependency getContextDependency() { + return this.contextDependency.clone(); + } + + @Nonnull + @Override + public Bounds3i getWriteBounds() { + return this.writeBounds_voxelGrid; + } + + private static class ChainedScanResult implements ScanResult { + List scanResults; + + private ChainedScanResult() { + } + + @Nonnull + public static UnionProp.ChainedScanResult cast(ScanResult scanResult) { + if (!(scanResult instanceof UnionProp.ChainedScanResult)) { + throw new IllegalArgumentException("The provided ScanResult isn't compatible with this prop."); + } else { + return (UnionProp.ChainedScanResult)scanResult; + } + } + + @Override + public boolean isNegative() { + return this.scanResults == null || this.scanResults.isEmpty(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/Directionality.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/Directionality.java new file mode 100644 index 0000000..26b4c6f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/Directionality.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props.directionality; + +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class Directionality { + public Directionality() { + } + + @Nullable + public abstract PrefabRotation getRotationAt(@Nonnull Pattern.Context var1); + + public abstract Pattern getGeneralPattern(); + + public abstract Vector3i getReadRangeWith(@Nonnull Scanner var1); + + public abstract List getPossibleRotations(); + + @Nonnull + public static Directionality noDirectionality() { + return new Directionality() { + @Override + public PrefabRotation getRotationAt(@Nonnull Pattern.Context context) { + return null; + } + + @Nonnull + @Override + public Pattern getGeneralPattern() { + return Pattern.noPattern(); + } + + @Nonnull + @Override + public Vector3i getReadRangeWith(@Nonnull Scanner scanner) { + return new Vector3i(); + } + + @Nonnull + @Override + public List getPossibleRotations() { + return Collections.emptyList(); + } + }; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/OrthogonalDirection.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/OrthogonalDirection.java new file mode 100644 index 0000000..35ffe86 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/OrthogonalDirection.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props.directionality; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.codecs.EnumCodec; + +public enum OrthogonalDirection { + N, + S, + E, + W, + U, + D; + + public static final Codec CODEC = new EnumCodec<>(OrthogonalDirection.class, EnumCodec.EnumStyle.LEGACY); + + private OrthogonalDirection() { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/PatternDirectionality.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/PatternDirectionality.java new file mode 100644 index 0000000..6e3a1f9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/PatternDirectionality.java @@ -0,0 +1,126 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props.directionality; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.SeedGenerator; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.OrPattern; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.math.util.FastRandom; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; + +public class PatternDirectionality extends Directionality { + @Nonnull + private final List rotations; + @Nonnull + private final PrefabRotation south; + @Nonnull + private final PrefabRotation north; + @Nonnull + private final PrefabRotation east; + @Nonnull + private final PrefabRotation west; + @Nonnull + private final Pattern southPattern; + @Nonnull + private final Pattern northPattern; + @Nonnull + private final Pattern eastPattern; + @Nonnull + private final Pattern westPattern; + @Nonnull + private final Pattern generalPattern; + @Nonnull + private final SeedGenerator seedGenerator; + + public PatternDirectionality( + @Nonnull OrthogonalDirection startingDirection, + @Nonnull Pattern southPattern, + @Nonnull Pattern northPattern, + @Nonnull Pattern eastPattern, + @Nonnull Pattern westPattern, + int seed + ) { + this.southPattern = southPattern; + this.northPattern = northPattern; + this.eastPattern = eastPattern; + this.westPattern = westPattern; + this.generalPattern = new OrPattern(List.of(northPattern, southPattern, eastPattern, westPattern)); + this.seedGenerator = new SeedGenerator(seed); + switch (startingDirection) { + case S: + this.south = PrefabRotation.ROTATION_0; + this.west = PrefabRotation.ROTATION_270; + this.north = PrefabRotation.ROTATION_180; + this.east = PrefabRotation.ROTATION_90; + break; + case E: + this.east = PrefabRotation.ROTATION_180; + this.south = PrefabRotation.ROTATION_90; + this.west = PrefabRotation.ROTATION_0; + this.north = PrefabRotation.ROTATION_270; + break; + case W: + this.west = PrefabRotation.ROTATION_180; + this.north = PrefabRotation.ROTATION_90; + this.east = PrefabRotation.ROTATION_0; + this.south = PrefabRotation.ROTATION_270; + break; + default: + this.north = PrefabRotation.ROTATION_0; + this.east = PrefabRotation.ROTATION_270; + this.south = PrefabRotation.ROTATION_180; + this.west = PrefabRotation.ROTATION_90; + } + + this.rotations = Collections.unmodifiableList(List.of(this.north, this.south, this.east, this.west)); + } + + @Nonnull + @Override + public Pattern getGeneralPattern() { + return this.generalPattern; + } + + @Nonnull + @Override + public Vector3i getReadRangeWith(@Nonnull Scanner scanner) { + return scanner.readSpaceWith(this.generalPattern).getRange(); + } + + @Nonnull + @Override + public List getPossibleRotations() { + return this.rotations; + } + + @Override + public PrefabRotation getRotationAt(@Nonnull Pattern.Context context) { + ArrayList successful = new ArrayList<>(4); + if (this.northPattern.matches(context)) { + successful.add(this.north); + } + + if (this.southPattern.matches(context)) { + successful.add(this.south); + } + + if (this.eastPattern.matches(context)) { + successful.add(this.east); + } + + if (this.westPattern.matches(context)) { + successful.add(this.west); + } + + if (successful.isEmpty()) { + return null; + } else { + FastRandom random = new FastRandom(this.seedGenerator.seedAt((long)context.position.x, (long)context.position.y, (long)context.position.z)); + return successful.get(random.nextInt(successful.size())); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/RandomDirectionality.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/RandomDirectionality.java new file mode 100644 index 0000000..accae28 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/RandomDirectionality.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props.directionality; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.SeedGenerator; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.math.util.FastRandom; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; + +public class RandomDirectionality extends Directionality { + @Nonnull + private final List rotations; + @Nonnull + private final Pattern pattern; + @Nonnull + private final SeedGenerator seedGenerator; + + public RandomDirectionality(@Nonnull Pattern pattern, int seed) { + this.pattern = pattern; + this.seedGenerator = new SeedGenerator(seed); + this.rotations = Collections.unmodifiableList( + List.of(PrefabRotation.ROTATION_0, PrefabRotation.ROTATION_90, PrefabRotation.ROTATION_180, PrefabRotation.ROTATION_270) + ); + } + + @Nonnull + @Override + public Pattern getGeneralPattern() { + return this.pattern; + } + + @Nonnull + @Override + public Vector3i getReadRangeWith(@Nonnull Scanner scanner) { + return scanner.readSpaceWith(this.pattern).getRange(); + } + + @Nonnull + @Override + public List getPossibleRotations() { + return this.rotations; + } + + @Override + public PrefabRotation getRotationAt(@Nonnull Pattern.Context context) { + FastRandom random = new FastRandom(this.seedGenerator.seedAt((long)context.position.x, (long)context.position.y, (long)context.position.z)); + return this.rotations.get(random.nextInt(this.rotations.size())); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/RotatedPosition.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/RotatedPosition.java new file mode 100644 index 0000000..b4657bc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/RotatedPosition.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props.directionality; + +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import javax.annotation.Nonnull; + +public class RotatedPosition { + public final int x; + public final int y; + public final int z; + @Nonnull + public final PrefabRotation rotation; + + public RotatedPosition(int x, int y, int z, @Nonnull PrefabRotation rotation) { + this.x = x; + this.y = y; + this.z = z; + this.rotation = rotation; + } + + @Nonnull + public RotatedPosition getRelativeTo(@Nonnull RotatedPosition other) { + Vector3i vec = new Vector3i(this.x, this.y, this.z); + other.rotation.rotate(vec); + int x = vec.x + other.x; + int y = vec.y + other.y; + int z = vec.z + other.z; + PrefabRotation rotation = this.rotation.add(other.rotation); + return new RotatedPosition(x, y, z, rotation); + } + + @Nonnull + public Vector3i toVector3i() { + return new Vector3i(this.x, this.y, this.z); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/RotatedPositionsScanResult.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/RotatedPositionsScanResult.java new file mode 100644 index 0000000..721d0e8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/RotatedPositionsScanResult.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props.directionality; + +import com.hypixel.hytale.builtin.hytalegenerator.props.ScanResult; +import java.util.List; +import javax.annotation.Nonnull; + +public class RotatedPositionsScanResult implements ScanResult { + @Nonnull + public final List positions; + + public RotatedPositionsScanResult(@Nonnull List positions) { + this.positions = positions; + } + + @Nonnull + public static RotatedPositionsScanResult cast(ScanResult scanResult) { + if (!(scanResult instanceof RotatedPositionsScanResult)) { + throw new IllegalArgumentException("The provided ScanResult isn't compatible with this type."); + } else { + return (RotatedPositionsScanResult)scanResult; + } + } + + @Override + public boolean isNegative() { + return this.positions == null || this.positions.isEmpty(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/StaticDirectionality.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/StaticDirectionality.java new file mode 100644 index 0000000..4342c20 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/directionality/StaticDirectionality.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props.directionality; + +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; + +public class StaticDirectionality extends Directionality { + @Nonnull + private final List possibleRotations; + @Nonnull + private final PrefabRotation rotation; + @Nonnull + private final Pattern pattern; + + public StaticDirectionality(@Nonnull PrefabRotation rotation, @Nonnull Pattern pattern) { + this.rotation = rotation; + this.pattern = pattern; + this.possibleRotations = Collections.unmodifiableList(List.of(rotation)); + } + + @Override + public PrefabRotation getRotationAt(@Nonnull Pattern.Context context) { + return this.rotation; + } + + @Nonnull + @Override + public Pattern getGeneralPattern() { + return this.pattern; + } + + @Nonnull + @Override + public Vector3i getReadRangeWith(@Nonnull Scanner scanner) { + return scanner.readSpaceWith(this.pattern).getRange(); + } + + @Nonnull + @Override + public List getPossibleRotations() { + return this.possibleRotations; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/entity/EntityPlacementData.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/entity/EntityPlacementData.java new file mode 100644 index 0000000..52d2bd5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/entity/EntityPlacementData.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props.entity; + +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.performanceinstruments.MemInstrument; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EntityPlacementData implements MemInstrument { + private final Vector3i offset; + private final PrefabRotation rotation; + private final Holder entityHolder; + private final int objectId; + + public EntityPlacementData(Vector3i offset, PrefabRotation rotation, Holder entityHolder, int objectId) { + this.offset = offset; + this.rotation = rotation; + this.entityHolder = entityHolder; + this.objectId = objectId; + } + + public Vector3i getOffset() { + return this.offset; + } + + public PrefabRotation getRotation() { + return this.rotation; + } + + public Holder getEntityHolder() { + return this.entityHolder; + } + + public int getObjectId() { + return this.objectId; + } + + @Nonnull + @Override + public MemInstrument.Report getMemoryUsage() { + long size_bytes = 48L; + return new MemInstrument.Report(48L); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/filler/FillerPropScanResult.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/filler/FillerPropScanResult.java new file mode 100644 index 0000000..28f24fe --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/filler/FillerPropScanResult.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props.filler; + +import com.hypixel.hytale.builtin.hytalegenerator.props.ScanResult; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FillerPropScanResult implements ScanResult { + private List positions; + + public FillerPropScanResult(@Nullable List positions) { + if (positions != null) { + this.positions = positions; + } + } + + @Nullable + public List getFluidBlocks() { + return this.positions; + } + + @Nonnull + public static FillerPropScanResult cast(ScanResult scanResult) { + if (!(scanResult instanceof FillerPropScanResult)) { + throw new IllegalArgumentException("The provided ScanResult isn't compatible with this prop."); + } else { + return (FillerPropScanResult)scanResult; + } + } + + @Override + public boolean isNegative() { + return this.positions == null || this.positions.isEmpty(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/filler/PondFillerProp.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/filler/PondFillerProp.java new file mode 100644 index 0000000..148d3c8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/filler/PondFillerProp.java @@ -0,0 +1,245 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props.filler; + +import com.hypixel.hytale.builtin.hytalegenerator.MaterialSet; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.builtin.hytalegenerator.conveyor.stagedconveyor.ContextDependency; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.ArrayVoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.materialproviders.MaterialProvider; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class PondFillerProp extends Prop { + private static final int TRAVERSED = 1; + private static final int LEAKS = 16; + private static final int SOLID = 256; + private static final int STACKED = 4096; + private final Vector3i boundingMin; + private final Vector3i boundingMax; + private final MaterialProvider filledMaterialProvider; + private final MaterialSet solidSet; + private final Scanner scanner; + private final Pattern pattern; + private final ContextDependency contextDependency; + private final Bounds3i writeBounds_voxelGrid; + + public PondFillerProp( + @Nonnull Vector3i boundingMin, + @Nonnull Vector3i boundingMax, + @Nonnull MaterialSet solidSet, + @Nonnull MaterialProvider filledMaterialProvider, + @Nonnull Scanner scanner, + @Nonnull Pattern pattern + ) { + this.boundingMin = boundingMin.clone(); + this.boundingMax = boundingMax.clone(); + this.solidSet = solidSet; + this.filledMaterialProvider = filledMaterialProvider; + this.scanner = scanner; + this.pattern = pattern; + SpaceSize boundingSpace = new SpaceSize(boundingMin, boundingMax); + boundingSpace = SpaceSize.stack(boundingSpace, scanner.readSpaceWith(pattern)); + SpaceSize.stack(scanner.readSpaceWith(pattern), boundingSpace); + Vector3i range = boundingSpace.getRange(); + this.contextDependency = new ContextDependency(range, range); + this.writeBounds_voxelGrid = this.contextDependency.getTotalPropBounds_voxelGrid(); + } + + public FillerPropScanResult scan(@Nonnull Vector3i position, @Nonnull VoxelSpace materialSpace, @Nonnull WorkerIndexer.Id id) { + Scanner.Context scannerContext = new Scanner.Context(position, this.pattern, materialSpace, id); + List scanResults = this.scanner.scan(scannerContext); + if (scanResults.size() == 1) { + List resultList = this.renderFluidBlocks(scanResults.getFirst(), materialSpace); + return new FillerPropScanResult(resultList); + } else { + ArrayList resultList = new ArrayList<>(); + + for (Vector3i scanPosition : scanResults) { + List renderResult = this.renderFluidBlocks(scanPosition, materialSpace); + resultList.addAll(renderResult); + } + + return new FillerPropScanResult(resultList); + } + } + + private List renderFluidBlocks(@Nonnull Vector3i origin, @Nonnull VoxelSpace materialSpace) { + Vector3i min = this.boundingMin.clone().add(origin); + Vector3i max = this.boundingMax.clone().add(origin); + min = Vector3i.max(min, new Vector3i(materialSpace.minX(), materialSpace.minY(), materialSpace.minZ())); + max = Vector3i.min(max, new Vector3i(materialSpace.maxX(), materialSpace.maxY(), materialSpace.maxZ())); + ArrayVoxelSpace mask = new ArrayVoxelSpace<>(max.x - min.x, max.y - min.y, max.z - min.z); + mask.setOrigin(-min.x, -min.y, -min.z); + mask.set(0); + int y = min.y; + + for (int x = min.x; x < max.x; x++) { + for (int z = min.z; z < max.z; z++) { + Material material = materialSpace.getContent(x, y, z); + int contextMaterialHash = material.hashMaterialIds(); + int maskValue = 1; + if (this.solidSet.test(contextMaterialHash)) { + maskValue |= 256; + mask.set(maskValue, x, y, z); + } else { + maskValue |= 16; + mask.set(maskValue, x, y, z); + } + } + } + + for (int var29 = min.y + 1; var29 < max.y; var29++) { + int underY = var29 - 1; + + for (int x = min.x; x < max.x; x++) { + for (int zx = min.z; zx < max.z; zx++) { + if (!isTraversed(mask.getContent(x, var29, zx))) { + int maskValueUnder = mask.getContent(x, underY, zx); + Material material = materialSpace.getContent(x, var29, zx); + int contextMaterialHash = material.hashMaterialIds(); + if (this.solidSet.test(contextMaterialHash)) { + int maskValue = 0; + maskValue |= 1; + maskValue |= 256; + mask.set(maskValue, x, var29, zx); + } else if (isLeaks(maskValueUnder) || x == min.x || x == max.x - 1 || zx == min.z || zx == max.z - 1) { + ArrayDeque stack = new ArrayDeque<>(); + stack.push(new Vector3i(x, var29, zx)); + mask.set(4096, x, var29, zx); + + while (!stack.isEmpty()) { + Vector3i poppedPos = stack.pop(); + int maskValue = mask.getContent(poppedPos.x, poppedPos.y, poppedPos.z); + maskValue |= 16; + mask.set(maskValue, poppedPos.x, poppedPos.y, poppedPos.z); + poppedPos.x--; + if (mask.isInsideSpace(poppedPos.x, poppedPos.y, poppedPos.z)) { + int poppedMaskValue = mask.getContent(poppedPos.x, poppedPos.y, poppedPos.z); + if (!isStacked(poppedMaskValue)) { + material = materialSpace.getContent(poppedPos.x, poppedPos.y, poppedPos.z); + contextMaterialHash = material.hashMaterialIds(); + if (!this.solidSet.test(contextMaterialHash)) { + stack.push(poppedPos.clone()); + mask.set(4096 | poppedMaskValue, poppedPos.x, poppedPos.y, poppedPos.z); + } + } + } + + poppedPos.x += 2; + if (mask.isInsideSpace(poppedPos.x, poppedPos.y, poppedPos.z)) { + int poppedMaskValue = mask.getContent(poppedPos.x, poppedPos.y, poppedPos.z); + if (!isStacked(poppedMaskValue)) { + material = materialSpace.getContent(poppedPos.x, poppedPos.y, poppedPos.z); + contextMaterialHash = material.hashMaterialIds(); + if (!this.solidSet.test(contextMaterialHash)) { + stack.push(poppedPos.clone()); + mask.set(4096 | poppedMaskValue, poppedPos.x, poppedPos.y, poppedPos.z); + } + } + } + + poppedPos.x--; + poppedPos.z--; + if (mask.isInsideSpace(poppedPos.x, poppedPos.y, poppedPos.z)) { + int poppedMaskValue = mask.getContent(poppedPos.x, poppedPos.y, poppedPos.z); + if (!isStacked(poppedMaskValue)) { + material = materialSpace.getContent(poppedPos.x, var29, poppedPos.z); + contextMaterialHash = material.hashMaterialIds(); + if (!this.solidSet.test(contextMaterialHash)) { + stack.push(poppedPos.clone()); + mask.set(4096 | poppedMaskValue, poppedPos.x, poppedPos.y, poppedPos.z); + } + } + } + + poppedPos.z += 2; + if (mask.isInsideSpace(poppedPos.x, poppedPos.y, poppedPos.z)) { + int poppedMaskValue = mask.getContent(poppedPos.x, poppedPos.y, poppedPos.z); + if (!isStacked(poppedMaskValue)) { + material = materialSpace.getContent(poppedPos.x, poppedPos.y, poppedPos.z); + contextMaterialHash = material.hashMaterialIds(); + if (!this.solidSet.test(contextMaterialHash)) { + stack.push(poppedPos.clone()); + mask.set(4096 | poppedMaskValue, poppedPos.x, poppedPos.y, poppedPos.z); + } + } + } + + poppedPos.z--; + } + } + } + } + } + } + + ArrayList fluidBlocks = new ArrayList<>(); + + for (int var30 = mask.minY() + 1; var30 < mask.maxY(); var30++) { + for (int x = mask.minX() + 1; x < mask.maxX() - 1; x++) { + for (int zxx = mask.minZ() + 1; zxx < mask.maxZ() - 1; zxx++) { + int maskValuex = mask.getContent(x, var30, zxx); + if (!isSolid(maskValuex) && !isLeaks(maskValuex)) { + fluidBlocks.add(new Vector3i(x, var30, zxx)); + } + } + } + } + + return fluidBlocks; + } + + @Override + public void place(@Nonnull Prop.Context context) { + List fluidBlocks = FillerPropScanResult.cast(context.scanResult).getFluidBlocks(); + if (fluidBlocks != null) { + for (Vector3i position : fluidBlocks) { + if (context.materialSpace.isInsideSpace(position.x, position.y, position.z)) { + MaterialProvider.Context materialsContext = new MaterialProvider.Context( + position, 0.0, 0, 0, 0, 0, context.workerId, null, context.distanceFromBiomeEdge + ); + Material material = this.filledMaterialProvider.getVoxelTypeAt(materialsContext); + if (material != null) { + context.materialSpace.set(material, position.x, position.y, position.z); + } + } + } + } + } + + @Override + public ContextDependency getContextDependency() { + return this.contextDependency.clone(); + } + + @Nonnull + @Override + public Bounds3i getWriteBounds() { + return this.writeBounds_voxelGrid; + } + + private static boolean isTraversed(int maskValue) { + return (maskValue & 1) == 1; + } + + private static boolean isLeaks(int maskValue) { + return (maskValue & 16) == 16; + } + + private static boolean isSolid(int maskValue) { + return (maskValue & 256) == 256; + } + + private static boolean isStacked(int maskValue) { + return (maskValue & 4096) == 4096; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/prefab/MoldingDirection.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/prefab/MoldingDirection.java new file mode 100644 index 0000000..9359db7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/prefab/MoldingDirection.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props.prefab; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.codecs.EnumCodec; + +public enum MoldingDirection { + NONE, + UP, + DOWN, + NORTH, + SOUTH, + EAST, + WEST; + + public static final Codec CODEC = new EnumCodec<>(MoldingDirection.class, EnumCodec.EnumStyle.LEGACY); + + private MoldingDirection() { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/prefab/PrefabMoldingConfiguration.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/prefab/PrefabMoldingConfiguration.java new file mode 100644 index 0000000..3317159 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/prefab/PrefabMoldingConfiguration.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props.prefab; + +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import javax.annotation.Nonnull; + +public class PrefabMoldingConfiguration { + public final Scanner moldingScanner; + public final Pattern moldingPattern; + public final MoldingDirection moldingDirection; + public final boolean moldChildren; + + public PrefabMoldingConfiguration(Scanner moldingScanner, Pattern moldingPattern, MoldingDirection moldingDirection, boolean moldChildren) { + this.moldingScanner = moldingScanner; + this.moldingPattern = moldingPattern; + this.moldingDirection = moldingDirection; + this.moldChildren = moldChildren; + } + + @Nonnull + public static PrefabMoldingConfiguration none() { + return new PrefabMoldingConfiguration(null, null, MoldingDirection.NONE, false); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/prefab/PrefabProp.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/prefab/PrefabProp.java new file mode 100644 index 0000000..a1c1d9f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/prefab/PrefabProp.java @@ -0,0 +1,359 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props.prefab; + +import com.hypixel.hytale.builtin.hytalegenerator.BlockMask; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.builtin.hytalegenerator.conveyor.stagedconveyor.ContextDependency; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.WeightedMap; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.ArrayVoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.SeedGenerator; +import com.hypixel.hytale.builtin.hytalegenerator.material.FluidMaterial; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache; +import com.hypixel.hytale.builtin.hytalegenerator.material.SolidMaterial; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.EntityContainer; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.props.Prop; +import com.hypixel.hytale.builtin.hytalegenerator.props.ScanResult; +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.Directionality; +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.RotatedPosition; +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.RotatedPositionsScanResult; +import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.StaticDirectionality; +import com.hypixel.hytale.builtin.hytalegenerator.props.entity.EntityPlacementData; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.OriginScanner; +import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner; +import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.common.util.ExceptionUtil; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabBufferCall; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.IPrefabBuffer; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabProp extends Prop { + private final WeightedMap> prefabPool; + private final Scanner scanner; + private ContextDependency contextDependency; + private final MaterialCache materialCache; + private final SeedGenerator seedGenerator; + private final BlockMask materialMask; + private final Directionality directionality; + private final Bounds3i writeBounds_voxelGrid; + private final Bounds3i prefabBounds_voxelGrid; + private final List childProps; + private final List childPositions; + private final Function> childPrefabLoader; + private final Scanner moldingScanner; + private final Pattern moldingPattern; + private final MoldingDirection moldingDirection; + private final boolean moldChildren; + private final int prefabId = this.hashCode(); + private boolean loadEntities; + + public PrefabProp( + @Nonnull WeightedMap> prefabPool, + @Nonnull Scanner scanner, + @Nonnull Directionality directionality, + @Nonnull MaterialCache materialCache, + @Nonnull BlockMask materialMask, + @Nonnull PrefabMoldingConfiguration prefabMoldingConfiguration, + @Nullable Function> childPrefabLoader, + @Nonnull SeedBox seedBox, + boolean loadEntities + ) { + this.prefabPool = prefabPool; + this.scanner = scanner; + this.directionality = directionality; + this.materialCache = materialCache; + this.seedGenerator = new SeedGenerator(seedBox.createSupplier().get().intValue()); + this.materialMask = materialMask; + this.loadEntities = loadEntities; + this.childProps = new ArrayList<>(); + this.childPositions = new ArrayList<>(); + this.childPrefabLoader = childPrefabLoader == null ? s -> null : childPrefabLoader; + this.moldingScanner = prefabMoldingConfiguration.moldingScanner; + this.moldingPattern = prefabMoldingConfiguration.moldingPattern; + this.moldingDirection = prefabMoldingConfiguration.moldingDirection; + this.moldChildren = prefabMoldingConfiguration.moldChildren; + this.contextDependency = new ContextDependency(); + Vector3i readRange = directionality.getReadRangeWith(scanner); + + for (List prefabList : prefabPool.allElements()) { + if (prefabList.isEmpty()) { + throw new IllegalArgumentException("prefab pool contains empty list"); + } + + for (PrefabBuffer prefab : prefabList) { + if (prefab == null) { + throw new IllegalArgumentException("prefab pool contains list with null element"); + } + + PrefabBuffer.PrefabBufferAccessor prefabAccess = prefab.newAccess(); + PrefabBuffer.ChildPrefab[] childPrefabs = prefabAccess.getChildPrefabs(); + int childId = 0; + + for (PrefabBuffer.ChildPrefab child : childPrefabs) { + RotatedPosition childPosition = new RotatedPosition(child.getX(), child.getY(), child.getZ(), child.getRotation()); + String childPath = child.getPath().replace('.', '/'); + childPath = childPath.replace("*", ""); + List childPrefabBuffers = this.childPrefabLoader.apply(childPath); + WeightedMap> weightedChildPrefabs = new WeightedMap<>(); + weightedChildPrefabs.add(childPrefabBuffers, 1.0); + StaticDirectionality childDirectionality = new StaticDirectionality(child.getRotation(), Pattern.yesPattern()); + PrefabProp childProp = new PrefabProp( + weightedChildPrefabs, + OriginScanner.getInstance(), + childDirectionality, + materialCache, + materialMask, + this.moldChildren ? prefabMoldingConfiguration : PrefabMoldingConfiguration.none(), + childPrefabLoader, + seedBox.child(String.valueOf(childId++)), + loadEntities + ); + this.childProps.add(childProp); + this.childPositions.add(childPosition); + } + + Vector3i writeRange = this.getWriteRange(prefabAccess); + + for (int i = 0; i < this.childPositions.size(); i++) { + PrefabProp child = this.childProps.get(i); + Vector3i position = this.childPositions.get(i).toVector3i(); + Vector3i childWriteRange = child.getContextDependency().getWriteRange(); + int maxRange = Calculator.max(position.x, position.y, position.z); + maxRange += Calculator.max(childWriteRange.x, childWriteRange.y, childWriteRange.z); + writeRange.x = Math.max(writeRange.x, maxRange); + writeRange.y = Math.max(writeRange.y, maxRange); + writeRange.z = Math.max(writeRange.z, maxRange); + } + + ContextDependency contextDependency = new ContextDependency(readRange, writeRange); + this.contextDependency = ContextDependency.mostOf(this.contextDependency, contextDependency); + prefabAccess.release(); + } + } + + this.writeBounds_voxelGrid = this.contextDependency.getTotalPropBounds_voxelGrid(); + this.prefabBounds_voxelGrid = new Bounds3i(); + this.prefabBounds_voxelGrid.min.assign(this.contextDependency.getWriteRange()).scale(-1); + this.prefabBounds_voxelGrid.max.assign(this.contextDependency.getWriteRange()).add(Vector3i.ALL_ONES); + } + + private Vector3i getWriteRange(PrefabBuffer.PrefabBufferAccessor prefabAccess) { + SpaceSize space = new SpaceSize(); + + for (PrefabRotation rotation : this.directionality.getPossibleRotations()) { + Vector3i max = PropPrefabUtil.getMax(prefabAccess, rotation); + max.add(1, 1, 1); + Vector3i min = PropPrefabUtil.getMin(prefabAccess, rotation); + space = SpaceSize.merge(space, new SpaceSize(min, max)); + } + + space = SpaceSize.stack(space, this.scanner.readSpaceWith(this.directionality.getGeneralPattern())); + return space.getRange(); + } + + @Override + public ScanResult scan(@Nonnull Vector3i position, @Nonnull VoxelSpace materialSpace, @Nonnull WorkerIndexer.Id id) { + Scanner.Context scannerContext = new Scanner.Context(position, this.directionality.getGeneralPattern(), materialSpace, id); + List validPositions = this.scanner.scan(scannerContext); + Vector3i patternPosition = new Vector3i(); + Pattern.Context patternContext = new Pattern.Context(patternPosition, materialSpace, id); + RotatedPositionsScanResult scanResult = new RotatedPositionsScanResult(new ArrayList<>()); + + for (Vector3i validPosition : validPositions) { + patternPosition.assign(validPosition); + PrefabRotation rotation = this.directionality.getRotationAt(patternContext); + if (rotation != null) { + scanResult.positions.add(new RotatedPosition(validPosition.x, validPosition.y, validPosition.z, rotation)); + } + } + + return scanResult; + } + + @Override + public void place(@Nonnull Prop.Context context) { + if (this.prefabPool.size() != 0) { + List positions = RotatedPositionsScanResult.cast(context.scanResult).positions; + if (positions != null) { + Bounds3i writeSpaceBounds_voxelGrid = context.materialSpace.getBounds(); + + for (RotatedPosition position : positions) { + Bounds3i localPrefabWriteBounds_voxelGrid = this.prefabBounds_voxelGrid.clone().offset(position.toVector3i()); + if (localPrefabWriteBounds_voxelGrid.intersects(writeSpaceBounds_voxelGrid)) { + this.place(position, context.materialSpace, context.entityBuffer, context.workerId); + } + } + } + } + } + + private PrefabBuffer pickPrefab(Random rand) { + List list = this.prefabPool.pick(rand); + int randomIndex = rand.nextInt(list.size()); + return list.get(randomIndex); + } + + private void place( + RotatedPosition position, @Nonnull VoxelSpace materialSpace, @Nonnull EntityContainer entityBuffer, @Nonnull WorkerIndexer.Id id + ) { + Random random = new Random(this.seedGenerator.seedAt((long)position.x, (long)position.y, (long)position.z)); + PrefabBufferCall callInstance = new PrefabBufferCall(random, position.rotation); + PrefabBuffer prefab = this.pickPrefab(random); + PrefabBuffer.PrefabBufferAccessor prefabAccess = prefab.newAccess(); + VoxelSpace moldingOffsets = null; + if (this.moldingDirection != MoldingDirection.NONE) { + int prefabMinX = prefabAccess.getMinX(position.rotation); + int prefabMinZ = prefabAccess.getMinZ(position.rotation); + int prefabMaxX = prefabAccess.getMaxX(position.rotation); + int prefabMaxZ = prefabAccess.getMaxZ(position.rotation); + int prefabSizeX = prefabMaxX - prefabMinX; + int prefabSizeZ = prefabMaxZ - prefabMinZ; + moldingOffsets = new ArrayVoxelSpace<>(prefabSizeX, 1, prefabSizeZ); + moldingOffsets.setOrigin(-position.x - prefabMinX, 0, -position.z - prefabMinZ); + if (this.moldingDirection == MoldingDirection.DOWN || this.moldingDirection == MoldingDirection.UP) { + Vector3i pointer = new Vector3i(0, position.y, 0); + Scanner.Context scannerContext = new Scanner.Context(pointer, this.moldingPattern, materialSpace, id); + + for (pointer.x = moldingOffsets.minX(); pointer.x < moldingOffsets.maxX(); pointer.x++) { + for (pointer.z = moldingOffsets.minZ(); pointer.z < moldingOffsets.maxZ(); pointer.z++) { + List scanResult = this.moldingScanner.scan(scannerContext); + Integer offset = scanResult.isEmpty() ? null : scanResult.getFirst().y - position.y; + if (offset != null && this.moldingDirection == MoldingDirection.UP) { + offset = offset - 1; + } + + moldingOffsets.set(offset, pointer.x, 0, pointer.z); + } + } + } + } + + try { + Vector3i prefabPositionVector = position.toVector3i(); + VoxelSpace moldingOffsetsFinal = moldingOffsets; + prefabAccess.forEach( + IPrefabBuffer.iterateAllColumns(), + (x, yx, z, blockId, holder, support, rotation, filler, call, fluidId, fluidLevel) -> { + int worldX = position.x + x; + int worldY = position.y + yx; + int worldZ = position.z + z; + if (materialSpace.isInsideSpace(worldX, worldY, worldZ)) { + SolidMaterial solid = this.materialCache.getSolidMaterial(blockId, support, rotation, filler, holder != null ? holder.clone() : null); + FluidMaterial fluid = this.materialCache.getFluidMaterial(fluidId, (byte)fluidLevel); + Material material = this.materialCache.getMaterial(solid, fluid); + int materialHash = material.hashMaterialIds(); + if (this.materialMask.canPlace(materialHash)) { + if (this.moldingDirection == MoldingDirection.DOWN || this.moldingDirection == MoldingDirection.UP) { + Integer offsetx = null; + if (moldingOffsetsFinal.isInsideSpace(worldX, 0, worldZ)) { + offsetx = moldingOffsetsFinal.getContent(worldX, 0, worldZ); + } + + if (offsetx == null) { + return; + } + + worldY += offsetx; + } + + Material worldMaterial = materialSpace.getContent(worldX, worldY, worldZ); + int worldMaterialHash = worldMaterial.hashMaterialIds(); + if (this.materialMask.canReplace(materialHash, worldMaterialHash)) { + materialSpace.set(material, worldX, worldY, worldZ); + } + } + } + }, + (cx, cz, entityWrappers, buffer) -> { + if (this.loadEntities) { + if (entityWrappers != null) { + for (int ix = 0; ix < entityWrappers.length; ix++) { + TransformComponent transformComp = entityWrappers[ix].getComponent(TransformComponent.getComponentType()); + if (transformComp != null) { + Vector3d entityPosition = transformComp.getPosition().clone(); + buffer.rotation.rotate(entityPosition); + Vector3d entityWorldPosition = entityPosition.add(prefabPositionVector); + if (entityBuffer.isInsideBuffer((int)entityWorldPosition.x, (int)entityWorldPosition.y, (int)entityWorldPosition.z)) { + Holder entityClone = entityWrappers[ix].clone(); + transformComp = entityClone.getComponent(TransformComponent.getComponentType()); + if (transformComp != null) { + entityPosition = transformComp.getPosition(); + entityPosition.x = entityWorldPosition.x; + entityPosition.y = entityWorldPosition.y; + entityPosition.z = entityWorldPosition.z; + if (!materialSpace.isInsideSpace( + (int)Math.floor(entityPosition.x), (int)Math.floor(entityPosition.y), (int)Math.floor(entityPosition.z) + )) { + return; + } + + EntityPlacementData placementData = new EntityPlacementData( + new Vector3i(), PrefabRotation.ROTATION_0, entityClone, this.prefabId + ); + entityBuffer.addEntity(placementData); + } + } + } + } + } + } + }, + (x, yx, z, path, fitHeightmap, inheritSeed, inheritHeightCondition, weights, rotation, t) -> {}, + callInstance + ); + } catch (Exception var23) { + String msg = "Couldn't place prefab prop."; + msg = msg + "\n"; + msg = msg + ExceptionUtil.toStringWithStack(var23); + HytaleLogger.getLogger().atWarning().log(msg); + } finally { + prefabAccess.release(); + } + + for (int i = 0; i < this.childProps.size(); i++) { + PrefabProp prop = this.childProps.get(i); + RotatedPosition childPosition = this.childPositions.get(i).getRelativeTo(position); + Vector3i rotatedChildPositionVec = new Vector3i(childPosition.x, childPosition.y, childPosition.z); + position.rotation.rotate(rotatedChildPositionVec); + if (moldingOffsets != null && moldingOffsets.isInsideSpace(childPosition.x, 0, childPosition.z)) { + Integer offset = moldingOffsets.getContent(childPosition.x, 0, childPosition.z); + if (offset == null) { + continue; + } + + int y = childPosition.y + offset; + childPosition = new RotatedPosition(childPosition.x, y, childPosition.z, childPosition.rotation); + } + + prop.place(childPosition, materialSpace, entityBuffer, id); + } + } + + @Override + public ContextDependency getContextDependency() { + return this.contextDependency.clone(); + } + + @Nonnull + @Override + public Bounds3i getWriteBounds() { + return this.writeBounds_voxelGrid; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/props/prefab/PropPrefabUtil.java b/src/com/hypixel/hytale/builtin/hytalegenerator/props/prefab/PropPrefabUtil.java new file mode 100644 index 0000000..83872ec --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/props/prefab/PropPrefabUtil.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.builtin.hytalegenerator.props.prefab; + +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer; +import javax.annotation.Nonnull; + +public class PropPrefabUtil { + public PropPrefabUtil() { + } + + @Nonnull + public static Vector3i getMin(@Nonnull PrefabBuffer.PrefabBufferAccessor prefab) { + int minX = prefab.getMinX(PrefabRotation.ROTATION_0); + int minY = prefab.getMinY(); + int minZ = prefab.getMinZ(PrefabRotation.ROTATION_0); + return new Vector3i(minX, minY, minZ); + } + + @Nonnull + public static Vector3i getMax(@Nonnull PrefabBuffer.PrefabBufferAccessor prefab) { + int maxX = prefab.getMaxX(PrefabRotation.ROTATION_0); + int maxY = prefab.getMaxY(); + int maxZ = prefab.getMaxZ(PrefabRotation.ROTATION_0); + return new Vector3i(maxX, maxY, maxZ); + } + + @Nonnull + public static Vector3i getMin(@Nonnull PrefabBuffer.PrefabBufferAccessor prefab, @Nonnull PrefabRotation rotation) { + int minX = prefab.getMinX(rotation); + int minY = prefab.getMinY(); + int minZ = prefab.getMinZ(rotation); + return new Vector3i(minX, minY, minZ); + } + + @Nonnull + public static Vector3i getMax(@Nonnull PrefabBuffer.PrefabBufferAccessor prefab, @Nonnull PrefabRotation rotation) { + int maxX = prefab.getMaxX(rotation); + int maxY = prefab.getMaxY(); + int maxZ = prefab.getMaxZ(rotation); + return new Vector3i(maxX, maxY, maxZ); + } + + @Nonnull + public static Vector3i getSize(@Nonnull PrefabBuffer.PrefabBufferAccessor prefab) { + Vector3i min = getMin(prefab); + Vector3i max = getMax(prefab); + return max.addScaled(min, -1); + } + + @Nonnull + public static Vector3i getAnchor(@Nonnull PrefabBuffer.PrefabBufferAccessor prefab) { + int x = prefab.getAnchorX(); + int y = prefab.getAnchorY(); + int z = prefab.getAnchorZ(); + return new Vector3i(x, y, z); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/rangemaps/DoubleRange.java b/src/com/hypixel/hytale/builtin/hytalegenerator/rangemaps/DoubleRange.java new file mode 100644 index 0000000..6780fbd --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/rangemaps/DoubleRange.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.hytalegenerator.rangemaps; + +import javax.annotation.Nonnull; + +public class DoubleRange { + private double min; + private double max; + private boolean inclusiveMin; + private boolean inclusiveMax; + + public DoubleRange(double min, boolean inclusiveMin, double max, boolean inclusiveMax) { + if (min > max) { + throw new IllegalArgumentException("min greater than max"); + } else { + this.min = min; + this.max = max; + this.inclusiveMin = inclusiveMin; + this.inclusiveMax = inclusiveMax; + } + } + + public double getMin() { + return this.min; + } + + public boolean isInclusiveMin() { + return this.inclusiveMin; + } + + public double getMax() { + return this.max; + } + + public boolean isInclusiveMax() { + return this.inclusiveMax; + } + + public boolean includes(double v) { + return (this.inclusiveMin ? v >= this.min : v > this.min) && (this.inclusiveMax ? v <= this.max : v < this.max); + } + + @Nonnull + public static DoubleRange inclusive(double min, double max) { + return new DoubleRange(min, true, max, true); + } + + @Nonnull + public static DoubleRange exclusive(double min, double max) { + return new DoubleRange(min, false, max, false); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/rangemaps/DoubleRangeMap.java b/src/com/hypixel/hytale/builtin/hytalegenerator/rangemaps/DoubleRangeMap.java new file mode 100644 index 0000000..855808f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/rangemaps/DoubleRangeMap.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.builtin.hytalegenerator.rangemaps; + +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DoubleRangeMap { + private ArrayList ranges = new ArrayList<>(1); + private ArrayList values = new ArrayList<>(1); + + public DoubleRangeMap() { + } + + @Nullable + public T get(double k) { + for (int i = 0; i < this.ranges.size(); i++) { + if (this.ranges.get(i).includes(k)) { + return this.values.get(i); + } + } + + return null; + } + + @Nonnull + public List ranges() { + return new ArrayList<>(this.ranges); + } + + @Nonnull + public List values() { + return new ArrayList<>(this.values); + } + + public void put(@Nonnull DoubleRange range, T value) { + this.ranges.add(range); + this.values.add(value); + } + + public int size() { + return this.ranges.size(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/referencebundle/BaseHeightReference.java b/src/com/hypixel/hytale/builtin/hytalegenerator/referencebundle/BaseHeightReference.java new file mode 100644 index 0000000..0b85131 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/referencebundle/BaseHeightReference.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.builtin.hytalegenerator.referencebundle; + +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.BiDouble2DoubleFunction; +import javax.annotation.Nonnull; + +public class BaseHeightReference extends Reference { + @Nonnull + private final BiDouble2DoubleFunction heightFunction; + + public BaseHeightReference(@Nonnull BiDouble2DoubleFunction heightFunction) { + this.heightFunction = heightFunction; + } + + @Nonnull + public BiDouble2DoubleFunction getHeightFunction() { + return this.heightFunction; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/referencebundle/Reference.java b/src/com/hypixel/hytale/builtin/hytalegenerator/referencebundle/Reference.java new file mode 100644 index 0000000..809ac32 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/referencebundle/Reference.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.builtin.hytalegenerator.referencebundle; + +public class Reference { + public Reference() { + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/referencebundle/ReferenceBundle.java b/src/com/hypixel/hytale/builtin/hytalegenerator/referencebundle/ReferenceBundle.java new file mode 100644 index 0000000..57e1652 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/referencebundle/ReferenceBundle.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.hytalegenerator.referencebundle; + +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ReferenceBundle { + @Nonnull + private final Map dataLayerMap = new HashMap<>(); + @Nonnull + private final Map layerTypeMap = new HashMap<>(); + + public ReferenceBundle() { + } + + public void put(@Nonnull String name, @Nonnull Reference reference, @Nonnull Class type) { + this.dataLayerMap.put(name, reference); + this.layerTypeMap.put(name, type.getName()); + } + + @Nullable + public Reference getLayerWithName(@Nonnull String name) { + return this.dataLayerMap.get(name); + } + + @Nullable + public T getLayerWithName(@Nonnull String name, @Nonnull Class type) { + String storedType = this.layerTypeMap.get(name); + if (storedType == null) { + return null; + } else { + return (T)(!storedType.equals(type.getName()) ? null : this.dataLayerMap.get(name)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/scanners/AreaScanner.java b/src/com/hypixel/hytale/builtin/hytalegenerator/scanners/AreaScanner.java new file mode 100644 index 0000000..38527c0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/scanners/AreaScanner.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.builtin.hytalegenerator.scanners; + +import com.hypixel.hytale.builtin.hytalegenerator.VectorUtil; +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; + +public class AreaScanner extends Scanner { + @Nonnull + private final AreaScanner.ScanShape scanShape; + private final int range; + private final int resultCap; + @Nonnull + private final Scanner childScanner; + @Nonnull + private final List scanOrder; + @Nonnull + private final SpaceSize scanSpaceSize; + + public AreaScanner(int resultCap, @Nonnull AreaScanner.ScanShape scanShape, int range, @Nonnull Scanner childScanner) { + if (resultCap >= 0 && range >= 0) { + this.resultCap = resultCap; + this.childScanner = childScanner; + this.scanShape = scanShape; + this.range = range; + ArrayList scanOrder = new ArrayList<>(); + + for (int x = -range; x <= range; x++) { + for (int z = -range; z <= range; z++) { + if (scanShape != AreaScanner.ScanShape.CIRCLE || !(Calculator.distance(x, z, 0.0, 0.0) > range)) { + scanOrder.add(new Vector2i(x, z)); + } + } + } + + this.scanOrder = VectorUtil.orderByDistanceFrom(new Vector2i(), scanOrder); + this.scanSpaceSize = new SpaceSize(new Vector3i(-range, 0, -range), new Vector3i(1 + range, 0, 1 + range)); + } else { + throw new IllegalArgumentException(); + } + } + + @Nonnull + @Override + public List scan(@Nonnull Scanner.Context context) { + if (this.resultCap == 0) { + return Collections.emptyList(); + } else { + ArrayList validPositions = new ArrayList<>(this.resultCap); + + for (Vector2i column : this.scanOrder) { + Vector3i columnOrigin = new Vector3i(context.position.x + column.x, context.position.y, context.position.z + column.y); + Scanner.Context childContext = new Scanner.Context(columnOrigin, context.pattern, context.materialSpace, context.workerId); + + for (Vector3i result : this.childScanner.scan(childContext)) { + validPositions.add(result); + if (validPositions.size() == this.resultCap) { + return validPositions; + } + } + } + + return validPositions; + } + } + + @Nonnull + @Override + public SpaceSize scanSpace() { + return this.scanSpaceSize.clone(); + } + + public static enum ScanShape { + CIRCLE, + SQUARE; + + public static final Codec CODEC = new EnumCodec<>(AreaScanner.ScanShape.class, EnumCodec.EnumStyle.LEGACY); + + private ScanShape() { + } + } + + public static enum Verticality { + GLOBAL, + LOCAL; + + private Verticality() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/scanners/ColumnLinearScanner.java b/src/com/hypixel/hytale/builtin/hytalegenerator/scanners/ColumnLinearScanner.java new file mode 100644 index 0000000..b3037a5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/scanners/ColumnLinearScanner.java @@ -0,0 +1,87 @@ +package com.hypixel.hytale.builtin.hytalegenerator.scanners; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.BiDouble2DoubleFunction; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ColumnLinearScanner extends Scanner { + private final int minY; + private final int maxY; + private final boolean isRelativeToPosition; + @Nullable + private final BiDouble2DoubleFunction baseHeightFunction; + private final int resultsCap; + private final boolean topDownOrder; + @Nonnull + private final SpaceSize scanSpaceSize; + + public ColumnLinearScanner( + int minY, int maxY, int resultsCap, boolean topDownOrder, boolean isRelativeToPosition, @Nullable BiDouble2DoubleFunction baseHeightFunction + ) { + if (resultsCap < 0) { + throw new IllegalArgumentException(); + } else { + this.baseHeightFunction = baseHeightFunction; + this.minY = minY; + this.maxY = maxY; + this.isRelativeToPosition = isRelativeToPosition; + this.resultsCap = resultsCap; + this.topDownOrder = topDownOrder; + this.scanSpaceSize = new SpaceSize(new Vector3i(0, 0, 0), new Vector3i(1, 0, 1)); + } + } + + @Nonnull + @Override + public List scan(@Nonnull Scanner.Context context) { + ArrayList validPositions = new ArrayList<>(this.resultsCap); + int scanMinY; + int scanMaxY; + if (this.isRelativeToPosition) { + scanMinY = Math.max(context.position.y + this.minY, context.materialSpace.minY()); + scanMaxY = Math.min(context.position.y + this.maxY, context.materialSpace.maxY()); + } else if (this.baseHeightFunction != null) { + int bedY = (int)this.baseHeightFunction.apply(context.position.x, context.position.z); + scanMinY = Math.max(bedY + this.minY, context.materialSpace.minY()); + scanMaxY = Math.min(bedY + this.maxY, context.materialSpace.maxY()); + } else { + scanMinY = Math.max(this.minY, context.materialSpace.minY()); + scanMaxY = Math.min(this.maxY, context.materialSpace.maxY()); + } + + Vector3i patternPosition = context.position.clone(); + Pattern.Context patternContext = new Pattern.Context(patternPosition, context.materialSpace, context.workerId); + if (this.topDownOrder) { + for (patternPosition.y = scanMaxY - 1; patternPosition.y >= scanMinY; patternPosition.y--) { + if (context.pattern.matches(patternContext)) { + validPositions.add(patternPosition.clone()); + if (validPositions.size() >= this.resultsCap) { + return validPositions; + } + } + } + } else { + for (patternPosition.y = scanMinY; patternPosition.y < scanMaxY; patternPosition.y++) { + if (context.pattern.matches(patternContext)) { + validPositions.add(patternPosition.clone()); + if (validPositions.size() >= this.resultsCap) { + return validPositions; + } + } + } + } + + return validPositions; + } + + @Nonnull + @Override + public SpaceSize scanSpace() { + return this.scanSpaceSize.clone(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/scanners/ColumnRandomScanner.java b/src/com/hypixel/hytale/builtin/hytalegenerator/scanners/ColumnRandomScanner.java new file mode 100644 index 0000000..702c095 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/scanners/ColumnRandomScanner.java @@ -0,0 +1,169 @@ +package com.hypixel.hytale.builtin.hytalegenerator.scanners; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.BiDouble2DoubleFunction; +import com.hypixel.hytale.builtin.hytalegenerator.framework.math.SeedGenerator; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.math.util.FastRandom; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ColumnRandomScanner extends Scanner { + private final int minY; + private final int maxY; + private final boolean isRelativeToPosition; + @Nullable + private final BiDouble2DoubleFunction bedFunction; + private final int resultsCap; + @Nonnull + private final SeedGenerator seedGenerator; + @Nonnull + private final ColumnRandomScanner.Strategy strategy; + @Nonnull + private final SpaceSize scanSpaceSize; + + public ColumnRandomScanner( + int minY, + int maxY, + int resultsCap, + int seed, + @Nonnull ColumnRandomScanner.Strategy strategy, + boolean isRelativeToPosition, + @Nullable BiDouble2DoubleFunction bedFunction + ) { + if (resultsCap < 0) { + throw new IllegalArgumentException(); + } else { + this.bedFunction = bedFunction; + this.minY = minY; + this.maxY = maxY; + this.isRelativeToPosition = isRelativeToPosition; + this.resultsCap = resultsCap; + this.seedGenerator = new SeedGenerator(seed); + this.strategy = strategy; + this.scanSpaceSize = new SpaceSize(new Vector3i(0, 0, 0), new Vector3i(1, 0, 1)); + } + } + + @Nonnull + @Override + public List scan(@Nonnull Scanner.Context context) { + return switch (this.strategy) { + case DART_THROW -> this.scanDartThrow(context); + case PICK_VALID -> this.scanPickValid(context); + }; + } + + @Nonnull + private List scanPickValid(@Nonnull Scanner.Context context) { + if (this.resultsCap == 0) { + return Collections.emptyList(); + } else { + int scanMinY; + int scanMaxY; + if (this.isRelativeToPosition) { + scanMinY = Math.max(context.position.y + this.minY, context.materialSpace.minY()); + scanMaxY = Math.min(context.position.y + this.maxY, context.materialSpace.maxY()); + } else if (this.bedFunction != null) { + int bedY = (int)this.bedFunction.apply(context.position.x, context.position.z); + scanMinY = Math.max(bedY + this.minY, context.materialSpace.minY()); + scanMaxY = Math.min(bedY + this.maxY, context.materialSpace.maxY()); + } else { + scanMinY = Math.max(this.minY, context.materialSpace.minY()); + scanMaxY = Math.min(this.maxY, context.materialSpace.maxY()); + } + + int numberOfPossiblePositions = Math.max(0, scanMaxY - scanMinY); + ArrayList validPositions = new ArrayList<>(numberOfPossiblePositions); + Vector3i patternPosition = context.position.clone(); + Pattern.Context patternContext = new Pattern.Context(patternPosition, context.materialSpace, context.workerId); + + for (int y = scanMinY; y < scanMaxY; y++) { + patternPosition.y = y; + if (context.pattern.matches(patternContext)) { + Vector3i position = context.position.clone(); + position.setY(y); + validPositions.add(position); + } + } + + if (validPositions.isEmpty()) { + return validPositions; + } else if (validPositions.size() <= this.resultsCap) { + return validPositions; + } else { + ArrayList usedIndices = new ArrayList<>(this.resultsCap); + ArrayList outPositions = new ArrayList<>(this.resultsCap); + FastRandom random = new FastRandom(this.seedGenerator.seedAt((long)context.position.x, (long)context.position.y, (long)context.position.z)); + + for (int i = 0; i < this.resultsCap; i++) { + int pickedIndex = random.nextInt(validPositions.size()); + if (!usedIndices.contains(pickedIndex)) { + usedIndices.add(pickedIndex); + outPositions.add(validPositions.get(pickedIndex)); + } + } + + return outPositions; + } + } + } + + @Nonnull + private List scanDartThrow(@Nonnull Scanner.Context context) { + if (this.resultsCap == 0) { + return Collections.emptyList(); + } else { + int scanMinY = this.isRelativeToPosition + ? Math.max(context.position.y + this.minY, context.materialSpace.minY()) + : Math.max(this.minY, context.materialSpace.minY()); + int scanMaxY = this.isRelativeToPosition + ? Math.min(context.position.y + this.maxY, context.materialSpace.maxY()) + : Math.min(this.maxY, context.materialSpace.maxY()); + int range = scanMaxY - scanMinY; + if (range == 0) { + return Collections.emptyList(); + } else { + int TRY_MULTIPLIER = 1; + int numberOfTries = range * 1; + ArrayList validPositions = new ArrayList<>(this.resultsCap); + FastRandom random = new FastRandom(this.seedGenerator.seedAt((long)context.position.x, (long)context.position.y, (long)context.position.z)); + ArrayList usedYs = new ArrayList<>(this.resultsCap); + Vector3i patternPosition = context.position.clone(); + Pattern.Context patternContext = new Pattern.Context(patternPosition, context.materialSpace, context.workerId); + + for (int i = 0; i < numberOfTries; i++) { + patternPosition.y = random.nextInt(range) + scanMinY; + if (context.pattern.matches(patternContext) && !usedYs.contains(patternPosition.y)) { + usedYs.add(patternPosition.y); + Vector3i position = patternPosition.clone(); + validPositions.add(position); + if (validPositions.size() == this.resultsCap) { + break; + } + } + } + + return validPositions; + } + } + } + + @Nonnull + @Override + public SpaceSize scanSpace() { + return this.scanSpaceSize.clone(); + } + + public static enum Strategy { + DART_THROW, + PICK_VALID; + + private Strategy() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/scanners/OriginScanner.java b/src/com/hypixel/hytale/builtin/hytalegenerator/scanners/OriginScanner.java new file mode 100644 index 0000000..330d033 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/scanners/OriginScanner.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.hytalegenerator.scanners; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; + +public class OriginScanner extends Scanner { + private static final OriginScanner instance = new OriginScanner(); + private static final SpaceSize SCAN_SPACE_SIZE = new SpaceSize(new Vector3i(0, 0, 0), new Vector3i(1, 0, 1)); + + private OriginScanner() { + } + + @Nonnull + @Override + public List scan(@Nonnull Scanner.Context context) { + Pattern.Context patternContext = new Pattern.Context(context.position, context.materialSpace, context.workerId); + return context.pattern.matches(patternContext) ? Collections.singletonList(context.position.clone()) : Collections.emptyList(); + } + + @Nonnull + @Override + public SpaceSize scanSpace() { + return SCAN_SPACE_SIZE.clone(); + } + + public static OriginScanner getInstance() { + return instance; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/scanners/Scanner.java b/src/com/hypixel/hytale/builtin/hytalegenerator/scanners/Scanner.java new file mode 100644 index 0000000..846d970 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/scanners/Scanner.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.builtin.hytalegenerator.scanners; + +import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize; +import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelSpace; +import com.hypixel.hytale.builtin.hytalegenerator.material.Material; +import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; + +public abstract class Scanner { + public Scanner() { + } + + public abstract List scan(@Nonnull Scanner.Context var1); + + public abstract SpaceSize scanSpace(); + + @Nonnull + public SpaceSize readSpaceWith(@Nonnull Pattern pattern) { + return SpaceSize.stack(pattern.readSpace(), this.scanSpace()); + } + + @Nonnull + public static Scanner noScanner() { + final SpaceSize space = new SpaceSize(new Vector3i(0, 0, 0), new Vector3i(0, 0, 0)); + return new Scanner() { + @Nonnull + @Override + public List scan(@Nonnull Scanner.Context context) { + return Collections.emptyList(); + } + + @Nonnull + @Override + public SpaceSize scanSpace() { + return space; + } + }; + } + + public static class Context { + public Vector3i position; + public Pattern pattern; + public VoxelSpace materialSpace; + public WorkerIndexer.Id workerId; + + public Context(@Nonnull Vector3i position, @Nonnull Pattern pattern, @Nonnull VoxelSpace materialSpace, @Nonnull WorkerIndexer.Id workerId) { + this.position = position; + this.pattern = pattern; + this.materialSpace = materialSpace; + this.workerId = workerId; + } + + public Context(@Nonnull Scanner.Context other) { + this.position = other.position; + this.pattern = other.pattern; + this.materialSpace = other.materialSpace; + this.workerId = other.workerId; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/seed/SeedBox.java b/src/com/hypixel/hytale/builtin/hytalegenerator/seed/SeedBox.java new file mode 100644 index 0000000..66bb40d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/seed/SeedBox.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.hytalegenerator.seed; + +import java.util.Random; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class SeedBox { + @Nonnull + private final String key; + + public SeedBox(@Nonnull String key) { + this.key = key; + } + + public SeedBox(int key) { + this.key = Integer.toString(key); + } + + @Nonnull + public SeedBox child(@Nonnull String childKey) { + return new SeedBox(this.key + childKey); + } + + @Nonnull + public Supplier createSupplier() { + Random rand = new Random(this.key.hashCode()); + return () -> rand.nextInt(); + } + + @Nonnull + @Override + public String toString() { + return "SeedBox{value='" + this.key + "'}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/threadindexer/WorkerIndexer.java b/src/com/hypixel/hytale/builtin/hytalegenerator/threadindexer/WorkerIndexer.java new file mode 100644 index 0000000..6e6a5d2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/threadindexer/WorkerIndexer.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.builtin.hytalegenerator.threadindexer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class WorkerIndexer { + private final int workerCount; + @Nonnull + private final List ids; + + public WorkerIndexer(int workerCount) { + if (workerCount <= 0) { + throw new IllegalArgumentException("workerCount must be > 0"); + } else { + this.workerCount = workerCount; + List tempIds = new ArrayList<>(workerCount); + + for (int i = 0; i < workerCount; i++) { + tempIds.add(new WorkerIndexer.Id(i)); + } + + this.ids = Collections.unmodifiableList(tempIds); + } + } + + public int getWorkerCount() { + return this.workerCount; + } + + @Nonnull + public List getWorkedIds() { + return this.ids; + } + + @Nonnull + public WorkerIndexer.Session createSession() { + return new WorkerIndexer.Session(); + } + + public static class Data { + private T[] data; + private Supplier initialize; + + public Data(int size, @Nonnull Supplier initialize) { + this.data = (T[])(new Object[size]); + this.initialize = initialize; + } + + public boolean isValid(@Nonnull WorkerIndexer.Id id) { + return id != null && id.id < this.data.length && id.id >= 0; + } + + @Nonnull + public T get(@Nonnull WorkerIndexer.Id id) { + if (!this.isValid(id)) { + throw new IllegalArgumentException("Invalid thread id " + id); + } else { + if (this.data[id.id] == null) { + this.data[id.id] = this.initialize.get(); + + assert this.data[id.id] != null; + } + + return this.data[id.id]; + } + } + } + + public static class Id { + public static final int UNKNOWN_THREAD_ID = -1; + public static final WorkerIndexer.Id UNKNOWN = new WorkerIndexer.Id(-1); + public final int id; + + private Id(int id) { + this.id = id; + } + + @Nonnull + @Override + public String toString() { + return String.valueOf(this.id); + } + } + + public class Session { + private int index = 0; + + public Session() { + } + + public WorkerIndexer.Id next() { + if (this.index >= WorkerIndexer.this.workerCount) { + throw new IllegalStateException("worker count exceeded"); + } else { + return WorkerIndexer.this.ids.get(this.index++); + } + } + + public boolean hasNext() { + return this.index < WorkerIndexer.this.workerCount; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/tintproviders/ConstantTintProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/tintproviders/ConstantTintProvider.java new file mode 100644 index 0000000..a5145a4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/tintproviders/ConstantTintProvider.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.builtin.hytalegenerator.tintproviders; + +import javax.annotation.Nonnull; + +public class ConstantTintProvider extends TintProvider { + @Nonnull + private final TintProvider.Result result; + + public ConstantTintProvider(int value) { + this.result = new TintProvider.Result(value); + } + + @Nonnull + @Override + public TintProvider.Result getValue(@Nonnull TintProvider.Context context) { + return this.result; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/tintproviders/DensityDelimitedTintProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/tintproviders/DensityDelimitedTintProvider.java new file mode 100644 index 0000000..1316611 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/tintproviders/DensityDelimitedTintProvider.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.builtin.hytalegenerator.tintproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.delimiters.DelimiterDouble; +import com.hypixel.hytale.builtin.hytalegenerator.delimiters.RangeDouble; +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class DensityDelimitedTintProvider extends TintProvider { + @Nonnull + private final List> delimiters = new ArrayList<>(); + @Nonnull + private final Density density; + + public DensityDelimitedTintProvider(@Nonnull List> delimiters, @Nonnull Density density) { + for (DelimiterDouble delimiter : delimiters) { + RangeDouble range = delimiter.getRange(); + if (!(range.min() >= range.max())) { + this.delimiters.add(delimiter); + } + } + + this.density = density; + } + + @Override + public TintProvider.Result getValue(@Nonnull TintProvider.Context context) { + double densityValue = this.density.process(new Density.Context(context)); + + for (DelimiterDouble delimiter : this.delimiters) { + if (delimiter.getRange().contains(densityValue)) { + return delimiter.getValue().getValue(context); + } + } + + return TintProvider.Result.WITHOUT_VALUE; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/tintproviders/NoTintProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/tintproviders/NoTintProvider.java new file mode 100644 index 0000000..0e22e4c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/tintproviders/NoTintProvider.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.builtin.hytalegenerator.tintproviders; + +import javax.annotation.Nonnull; + +public class NoTintProvider extends TintProvider { + public NoTintProvider() { + } + + @Nonnull + @Override + public TintProvider.Result getValue(@Nonnull TintProvider.Context context) { + return TintProvider.Result.WITHOUT_VALUE; + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/tintproviders/TintProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/tintproviders/TintProvider.java new file mode 100644 index 0000000..e3269d6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/tintproviders/TintProvider.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.builtin.hytalegenerator.tintproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.server.core.asset.util.ColorParseUtil; +import javax.annotation.Nonnull; + +public abstract class TintProvider { + public static final int DEFAULT_TINT = ColorParseUtil.colorToARGBInt(new Color((byte)91, (byte)-98, (byte)40)); + + public TintProvider() { + } + + public abstract TintProvider.Result getValue(@Nonnull TintProvider.Context var1); + + @Nonnull + public static TintProvider noTintProvider() { + return new ConstantTintProvider(DEFAULT_TINT); + } + + public static class Context { + public Vector3i position; + public WorkerIndexer.Id workerId; + + public Context(@Nonnull Vector3i position, WorkerIndexer.Id workerId) { + this.position = position; + this.workerId = workerId; + } + + public Context(@Nonnull TintProvider.Context other) { + this.position = other.position; + this.workerId = other.workerId; + } + } + + public static class Result { + public static final TintProvider.Result WITHOUT_VALUE = new TintProvider.Result(); + public final int tint; + public final boolean hasValue; + + public Result(int tint) { + this.tint = tint; + this.hasValue = true; + } + + public Result() { + this.tint = TintProvider.DEFAULT_TINT; + this.hasValue = false; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/vectorproviders/CacheVectorProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/vectorproviders/CacheVectorProvider.java new file mode 100644 index 0000000..bd6053b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/vectorproviders/CacheVectorProvider.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.hytalegenerator.vectorproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class CacheVectorProvider extends VectorProvider { + @Nonnull + private final VectorProvider vectorProvider; + @Nonnull + private final WorkerIndexer.Data threadData; + + public CacheVectorProvider(@Nonnull VectorProvider vectorProvider, int threadCount) { + if (threadCount <= 0) { + throw new IllegalArgumentException("threadCount must be greater than 0"); + } else { + this.vectorProvider = vectorProvider; + this.threadData = new WorkerIndexer.Data<>(threadCount, CacheVectorProvider.Cache::new); + } + } + + @Nonnull + @Override + public Vector3d process(@Nonnull VectorProvider.Context context) { + CacheVectorProvider.Cache cache = this.threadData.get(context.workerId); + if (cache.position != null && cache.position.equals(context.position)) { + return cache.value; + } else { + if (cache.position == null) { + cache.position = new Vector3d(); + } + + cache.position.assign(context.position); + cache.value = this.vectorProvider.process(context); + return cache.value; + } + } + + public static class Cache { + Vector3d position; + Vector3d value; + + public Cache() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/vectorproviders/ConstantVectorProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/vectorproviders/ConstantVectorProvider.java new file mode 100644 index 0000000..5d18230 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/vectorproviders/ConstantVectorProvider.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.builtin.hytalegenerator.vectorproviders; + +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class ConstantVectorProvider extends VectorProvider { + @Nonnull + private final Vector3d value; + + public ConstantVectorProvider(@Nonnull Vector3d value) { + this.value = value.clone(); + } + + @Nonnull + @Override + public Vector3d process(@Nonnull VectorProvider.Context context) { + return this.value.clone(); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/vectorproviders/DensityGradientVectorProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/vectorproviders/DensityGradientVectorProvider.java new file mode 100644 index 0000000..f37a481 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/vectorproviders/DensityGradientVectorProvider.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.hytalegenerator.vectorproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class DensityGradientVectorProvider extends VectorProvider { + @Nonnull + private final Density density; + private final double sampleDistance; + + public DensityGradientVectorProvider(@Nonnull Density density, double sampleDistance) { + assert sampleDistance >= 0.0; + + this.density = density; + this.sampleDistance = Math.max(0.0, sampleDistance); + } + + @Nonnull + @Override + public Vector3d process(@Nonnull VectorProvider.Context context) { + double valueAtOrigin = this.density.process(new Density.Context(context)); + double maxX = context.position.x + this.sampleDistance; + double maxY = context.position.y + this.sampleDistance; + double maxZ = context.position.z + this.sampleDistance; + Density.Context childContext = new Density.Context(context); + childContext.position = new Vector3d(maxX, context.position.y, context.position.z); + double deltaX = this.density.process(childContext) - valueAtOrigin; + childContext.position = new Vector3d(context.position.x, maxY, context.position.z); + double deltaY = this.density.process(childContext) - valueAtOrigin; + childContext.position = new Vector3d(context.position.x, context.position.y, maxZ); + double deltaZ = this.density.process(childContext) - valueAtOrigin; + return new Vector3d(deltaX, deltaY, deltaZ); + } +} diff --git a/src/com/hypixel/hytale/builtin/hytalegenerator/vectorproviders/VectorProvider.java b/src/com/hypixel/hytale/builtin/hytalegenerator/vectorproviders/VectorProvider.java new file mode 100644 index 0000000..9d93c5d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/hytalegenerator/vectorproviders/VectorProvider.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.builtin.hytalegenerator.vectorproviders; + +import com.hypixel.hytale.builtin.hytalegenerator.density.Density; +import com.hypixel.hytale.builtin.hytalegenerator.newsystem.TerrainDensityProvider; +import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class VectorProvider { + public VectorProvider() { + } + + @Nonnull + public abstract Vector3d process(@Nonnull VectorProvider.Context var1); + + public static class Context { + @Nonnull + public Vector3d position; + @Nonnull + public WorkerIndexer.Id workerId; + @Nullable + public TerrainDensityProvider terrainDensityProvider; + + public Context(@Nonnull Vector3d position, @Nonnull WorkerIndexer.Id workerId, @Nullable TerrainDensityProvider terrainDensityProvider) { + this.position = position; + this.workerId = workerId; + this.terrainDensityProvider = terrainDensityProvider; + } + + public Context(@Nonnull VectorProvider.Context other) { + this.position = other.position; + this.workerId = other.workerId; + this.terrainDensityProvider = other.terrainDensityProvider; + } + + public Context(@Nonnull Density.Context other) { + this.position = other.position; + this.workerId = other.workerId; + this.terrainDensityProvider = other.terrainDensityProvider; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/InstanceValidator.java b/src/com/hypixel/hytale/builtin/instances/InstanceValidator.java new file mode 100644 index 0000000..52017d5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/InstanceValidator.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.builtin.instances; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import javax.annotation.Nonnull; + +public class InstanceValidator implements Validator { + public static final InstanceValidator INSTANCE = new InstanceValidator(); + + public InstanceValidator() { + } + + public void accept(@Nonnull String s, @Nonnull ValidationResults results) { + if (!InstancesPlugin.doesInstanceAssetExist(s)) { + results.fail("Instance asset with name '" + s + "' does not exist"); + } + } + + @Override + public void updateSchema(SchemaContext context, @Nonnull Schema target) { + target.setHytaleCustomAssetRef("Instance"); + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/InstancesPlugin.java b/src/com/hypixel/hytale/builtin/instances/InstancesPlugin.java new file mode 100644 index 0000000..a8ce0b7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/InstancesPlugin.java @@ -0,0 +1,638 @@ +package com.hypixel.hytale.builtin.instances; + +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.builtin.blockphysics.WorldValidationUtil; +import com.hypixel.hytale.builtin.instances.blocks.ConfigurableInstanceBlock; +import com.hypixel.hytale.builtin.instances.blocks.InstanceBlock; +import com.hypixel.hytale.builtin.instances.command.InstancesCommand; +import com.hypixel.hytale.builtin.instances.config.ExitInstance; +import com.hypixel.hytale.builtin.instances.config.InstanceDiscoveryConfig; +import com.hypixel.hytale.builtin.instances.config.InstanceEntityConfig; +import com.hypixel.hytale.builtin.instances.config.InstanceWorldConfig; +import com.hypixel.hytale.builtin.instances.config.WorldReturnPoint; +import com.hypixel.hytale.builtin.instances.event.DiscoverInstanceEvent; +import com.hypixel.hytale.builtin.instances.interactions.ExitInstanceInteraction; +import com.hypixel.hytale.builtin.instances.interactions.TeleportConfigInstanceInteraction; +import com.hypixel.hytale.builtin.instances.interactions.TeleportInstanceInteraction; +import com.hypixel.hytale.builtin.instances.page.ConfigureInstanceBlockPage; +import com.hypixel.hytale.builtin.instances.removal.IdleTimeoutCondition; +import com.hypixel.hytale.builtin.instances.removal.InstanceDataResource; +import com.hypixel.hytale.builtin.instances.removal.RemovalCondition; +import com.hypixel.hytale.builtin.instances.removal.RemovalSystem; +import com.hypixel.hytale.builtin.instances.removal.TimeoutCondition; +import com.hypixel.hytale.builtin.instances.removal.WorldEmptyCondition; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.EventRegistry; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.asset.GenerateSchemaEvent; +import com.hypixel.hytale.server.core.asset.LoadAssetEvent; +import com.hypixel.hytale.server.core.asset.type.gameplay.respawn.RespawnController; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData; +import com.hypixel.hytale.server.core.event.events.player.AddPlayerToWorldEvent; +import com.hypixel.hytale.server.core.event.events.player.DrainPlayerFromWorldEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerReadyEvent; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenCustomUIInteraction; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.ValidationOption; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.provider.EmptyChunkStorageProvider; +import com.hypixel.hytale.server.core.universe.world.storage.provider.IChunkStorageProvider; +import com.hypixel.hytale.server.core.universe.world.storage.provider.MigrationChunkStorageProvider; +import com.hypixel.hytale.server.core.universe.world.storage.resources.EmptyResourceStorageProvider; +import com.hypixel.hytale.server.core.util.EventTitleUtil; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InstancesPlugin extends JavaPlugin { + private static InstancesPlugin instance; + @Nonnull + public static final String INSTANCE_PREFIX = "instance-"; + @Nonnull + public static final String CONFIG_FILENAME = "instance.bson"; + private ResourceType instanceDataResourceType; + private ComponentType instanceEntityConfigComponentType; + private ComponentType instanceBlockComponentType; + private ComponentType configurableInstanceBlockComponentType; + + public static InstancesPlugin get() { + return instance; + } + + public InstancesPlugin(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + EventRegistry eventRegistry = this.getEventRegistry(); + ComponentRegistryProxy chunkStoreRegistry = this.getChunkStoreRegistry(); + this.getCommandRegistry().registerCommand(new InstancesCommand()); + eventRegistry.register((short)64, LoadAssetEvent.class, this::validateInstanceAssets); + eventRegistry.register(GenerateSchemaEvent.class, InstancesPlugin::generateSchema); + eventRegistry.registerGlobal(AddPlayerToWorldEvent.class, InstancesPlugin::onPlayerAddToWorld); + eventRegistry.registerGlobal(DrainPlayerFromWorldEvent.class, InstancesPlugin::onPlayerDrainFromWorld); + eventRegistry.register(PlayerConnectEvent.class, InstancesPlugin::onPlayerConnect); + eventRegistry.registerGlobal(PlayerReadyEvent.class, InstancesPlugin::onPlayerReady); + this.instanceBlockComponentType = chunkStoreRegistry.registerComponent(InstanceBlock.class, "Instance", InstanceBlock.CODEC); + chunkStoreRegistry.registerSystem(new InstanceBlock.OnRemove()); + this.configurableInstanceBlockComponentType = chunkStoreRegistry.registerComponent( + ConfigurableInstanceBlock.class, "InstanceConfig", ConfigurableInstanceBlock.CODEC + ); + chunkStoreRegistry.registerSystem(new ConfigurableInstanceBlock.OnRemove()); + this.instanceDataResourceType = chunkStoreRegistry.registerResource(InstanceDataResource.class, "InstanceData", InstanceDataResource.CODEC); + chunkStoreRegistry.registerSystem(new RemovalSystem()); + this.instanceEntityConfigComponentType = this.getEntityStoreRegistry() + .registerComponent(InstanceEntityConfig.class, "Instance", InstanceEntityConfig.CODEC); + this.getCodecRegistry(RemovalCondition.CODEC) + .register("WorldEmpty", WorldEmptyCondition.class, WorldEmptyCondition.CODEC) + .register("IdleTimeout", IdleTimeoutCondition.class, IdleTimeoutCondition.CODEC) + .register("Timeout", TimeoutCondition.class, TimeoutCondition.CODEC); + this.getCodecRegistry(Interaction.CODEC) + .register("TeleportInstance", TeleportInstanceInteraction.class, TeleportInstanceInteraction.CODEC) + .register("TeleportConfigInstance", TeleportConfigInstanceInteraction.class, TeleportConfigInstanceInteraction.CODEC) + .register("ExitInstance", ExitInstanceInteraction.class, ExitInstanceInteraction.CODEC); + this.getCodecRegistry(RespawnController.CODEC).register("ExitInstance", ExitInstance.class, ExitInstance.CODEC); + OpenCustomUIInteraction.registerBlockEntityCustomPage( + this, ConfigureInstanceBlockPage.class, "ConfigInstanceBlock", ConfigureInstanceBlockPage::new, () -> { + Holder holder = ChunkStore.REGISTRY.newHolder(); + holder.ensureComponent(ConfigurableInstanceBlock.getComponentType()); + return holder; + } + ); + this.getCodecRegistry(WorldConfig.PLUGIN_CODEC).register(InstanceWorldConfig.class, "Instance", InstanceWorldConfig.CODEC); + } + + @Nonnull + public CompletableFuture spawnInstance(@Nonnull String name, @Nonnull World forWorld, @Nonnull Transform returnPoint) { + return this.spawnInstance(name, null, forWorld, returnPoint); + } + + @Nonnull + public CompletableFuture spawnInstance(@Nonnull String name, @Nullable String worldName, @Nonnull World forWorld, @Nonnull Transform returnPoint) { + Universe universe = Universe.get(); + Path path = universe.getPath(); + Path assetPath = getInstanceAssetPath(name); + UUID uuid = UUID.randomUUID(); + String worldKey = worldName; + if (worldName == null) { + worldKey = "instance-" + safeName(name) + "-" + uuid; + } + + Path worldPath = path.resolve("worlds").resolve(worldKey); + String finalWorldKey = worldKey; + return WorldConfig.load(assetPath.resolve("instance.bson")) + .thenApplyAsync( + SneakyThrow.sneakyFunction( + config -> { + config.setUuid(uuid); + config.setDisplayName(WorldConfig.formatDisplayName(name)); + InstanceWorldConfig instanceConfig = InstanceWorldConfig.ensureAndGet(config); + instanceConfig.setReturnPoint( + new WorldReturnPoint(forWorld.getWorldConfig().getUuid(), returnPoint, instanceConfig.shouldPreventReconnection()) + ); + config.markChanged(); + long start = System.nanoTime(); + this.getLogger().at(Level.INFO).log("Copying instance files for %s to world %s", name, finalWorldKey); + + try (Stream files = Files.walk(assetPath, FileUtil.DEFAULT_WALK_TREE_OPTIONS_ARRAY)) { + files.forEach(SneakyThrow.sneakyConsumer(filePath -> { + Path rel = assetPath.relativize(filePath); + Path toPath = worldPath.resolve(rel.toString()); + if (Files.isDirectory(filePath)) { + Files.createDirectories(toPath); + } else { + if (Files.isRegularFile(filePath)) { + Files.copy(filePath, toPath); + } + } + })); + } + + this.getLogger() + .at(Level.INFO) + .log("Completed instance files for %s to world %s in %s", name, finalWorldKey, FormatUtil.nanosToString(System.nanoTime() - start)); + return config; + } + ) + ) + .thenCompose(config -> universe.makeWorld(finalWorldKey, worldPath, config)); + } + + public static void teleportPlayerToLoadingInstance( + @Nonnull Ref entityRef, + @Nonnull ComponentAccessor componentAccessor, + @Nonnull CompletableFuture worldFuture, + @Nullable Transform overrideReturn + ) { + World originalWorld = componentAccessor.getExternalData().getWorld(); + TransformComponent transformComponent = componentAccessor.getComponent(entityRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Transform originalPosition = transformComponent.getTransform().clone(); + InstanceEntityConfig instanceEntityConfigComponent = componentAccessor.getComponent(entityRef, InstanceEntityConfig.getComponentType()); + if (instanceEntityConfigComponent == null) { + instanceEntityConfigComponent = componentAccessor.addComponent(entityRef, InstanceEntityConfig.getComponentType()); + } + + if (overrideReturn != null) { + instanceEntityConfigComponent.setReturnPointOverride(new WorldReturnPoint(originalWorld.getWorldConfig().getUuid(), overrideReturn, false)); + } else { + instanceEntityConfigComponent.setReturnPointOverride(null); + } + + PlayerRef playerRefComponent = componentAccessor.getComponent(entityRef, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + UUIDComponent uuidComponent = componentAccessor.getComponent(entityRef, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID playerUUID = uuidComponent.getUuid(); + InstanceEntityConfig finalPlayerConfig = instanceEntityConfigComponent; + CompletableFuture.runAsync(playerRefComponent::removeFromStore, originalWorld) + .thenCombine(worldFuture.orTimeout(1L, TimeUnit.MINUTES), (ignored, world) -> (World)world) + .thenCompose(world -> { + ISpawnProvider spawnProvider = world.getWorldConfig().getSpawnProvider(); + Transform spawnPoint = spawnProvider != null ? spawnProvider.getSpawnPoint(world, playerUUID) : null; + return world.addPlayer(playerRefComponent, spawnPoint, Boolean.TRUE, Boolean.FALSE); + }) + .whenComplete((ret, ex) -> { + if (ex != null) { + get().getLogger().at(Level.SEVERE).withCause(ex).log("Failed to send %s to instance world", playerRefComponent.getUsername()); + finalPlayerConfig.setReturnPointOverride(null); + } + + if (ret == null) { + if (originalWorld.isAlive()) { + originalWorld.addPlayer(playerRefComponent, originalPosition, Boolean.TRUE, Boolean.FALSE); + } else { + World defaultWorld = Universe.get().getDefaultWorld(); + if (defaultWorld != null) { + defaultWorld.addPlayer(playerRefComponent, null, Boolean.TRUE, Boolean.FALSE); + } else { + get().getLogger().at(Level.SEVERE).log("No fallback world for %s, disconnecting", playerRefComponent.getUsername()); + playerRefComponent.getPacketHandler().disconnect("Failed to teleport - no world available"); + } + } + } + }); + } + + public static void teleportPlayerToInstance( + @Nonnull Ref playerRef, + @Nonnull ComponentAccessor componentAccessor, + @Nonnull World targetWorld, + @Nullable Transform overrideReturn + ) { + World originalWorld = componentAccessor.getExternalData().getWorld(); + WorldConfig originalWorldConfig = originalWorld.getWorldConfig(); + if (overrideReturn != null) { + InstanceEntityConfig instanceConfig = componentAccessor.ensureAndGetComponent(playerRef, InstanceEntityConfig.getComponentType()); + instanceConfig.setReturnPointOverride(new WorldReturnPoint(originalWorldConfig.getUuid(), overrideReturn, false)); + } + + UUIDComponent uuidComponent = componentAccessor.getComponent(playerRef, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID playerUUID = uuidComponent.getUuid(); + WorldConfig targetWorldConfig = targetWorld.getWorldConfig(); + ISpawnProvider spawnProvider = targetWorldConfig.getSpawnProvider(); + if (spawnProvider == null) { + throw new IllegalStateException("Spawn provider cannot be null when teleporting player to instance!"); + } else { + HeadRotation headRotationComponent = componentAccessor.getComponent(playerRef, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3f headRotation = headRotationComponent.getRotation(); + Transform spawnPoint = spawnProvider.getSpawnPoint(targetWorld, playerUUID); + Vector3f spawnHeadRotation = headRotation.clone(); + spawnHeadRotation.setYaw(spawnPoint.getRotation().getYaw()); + componentAccessor.addComponent(playerRef, Teleport.getComponentType(), new Teleport(targetWorld, spawnPoint).withHeadRotation(spawnHeadRotation)); + } + } + + public static void exitInstance(@Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor) { + World world = componentAccessor.getExternalData().getWorld(); + InstanceEntityConfig entityConfig = componentAccessor.getComponent(targetRef, InstanceEntityConfig.getComponentType()); + WorldReturnPoint returnPoint = entityConfig != null ? entityConfig.getReturnPoint() : null; + if (returnPoint == null) { + WorldConfig config = world.getWorldConfig(); + InstanceWorldConfig instanceConfig = InstanceWorldConfig.get(config); + returnPoint = instanceConfig != null ? instanceConfig.getReturnPoint() : null; + if (returnPoint == null) { + throw new IllegalArgumentException("Player is not in an instance"); + } + } + + Universe universe = Universe.get(); + World targetWorld = universe.getWorld(returnPoint.getWorld()); + if (targetWorld == null) { + throw new IllegalArgumentException("Missing return world"); + } else { + Teleport teleportComponent = new Teleport(targetWorld, returnPoint.getReturnPoint()); + componentAccessor.addComponent(targetRef, Teleport.getComponentType(), teleportComponent); + } + } + + public static void safeRemoveInstance(@Nonnull String worldName) { + safeRemoveInstance(Universe.get().getWorld(worldName)); + } + + public static void safeRemoveInstance(@Nonnull UUID worldUUID) { + safeRemoveInstance(Universe.get().getWorld(worldUUID)); + } + + public static void safeRemoveInstance(@Nullable World instanceWorld) { + if (instanceWorld != null) { + Store chunkStore = instanceWorld.getChunkStore().getStore(); + chunkStore.getResource(InstanceDataResource.getResourceType()).setHadPlayer(true); + WorldConfig config = instanceWorld.getWorldConfig(); + InstanceWorldConfig instanceConfig = InstanceWorldConfig.get(config); + if (instanceConfig != null) { + instanceConfig.setRemovalConditions(WorldEmptyCondition.REMOVE_WHEN_EMPTY); + } + + config.markChanged(); + } + } + + @Nonnull + public static Path getInstanceAssetPath(@Nonnull String name) { + for (AssetPack pack : AssetModule.get().getAssetPacks()) { + Path path = pack.getRoot().resolve("Server").resolve("Instances").resolve(name); + if (Files.exists(path)) { + return path; + } + } + + return AssetModule.get().getBaseAssetPack().getRoot().resolve("Server").resolve("Instances").resolve(name); + } + + public static boolean doesInstanceAssetExist(@Nonnull String name) { + return Files.exists(getInstanceAssetPath(name).resolve("instance.bson")); + } + + @Nonnull + public static CompletableFuture loadInstanceAssetForEdit(@Nonnull String name) { + Path path = getInstanceAssetPath(name); + Universe universe = Universe.get(); + return WorldConfig.load(path.resolve("instance.bson")).thenCompose(config -> { + config.setUuid(UUID.randomUUID()); + config.setSavingPlayers(false); + config.setIsAllNPCFrozen(true); + config.setTicking(false); + config.setGameMode(GameMode.Creative); + config.setDeleteOnRemove(false); + InstanceWorldConfig.ensureAndGet(config).setRemovalConditions(RemovalCondition.EMPTY); + config.markChanged(); + String worldName = "instance-edit-" + safeName(name); + return universe.makeWorld(worldName, path, config); + }); + } + + @Nonnull + public List getInstanceAssets() { + final List instances = new ObjectArrayList<>(); + + for (AssetPack pack : AssetModule.get().getAssetPacks()) { + final Path path = pack.getRoot().resolve("Server").resolve("Instances"); + if (Files.isDirectory(path)) { + try { + Files.walkFileTree(path, FileUtil.DEFAULT_WALK_TREE_OPTIONS_SET, Integer.MAX_VALUE, new SimpleFileVisitor() { + @Nonnull + public FileVisitResult preVisitDirectory(@Nonnull Path dir, @Nonnull BasicFileAttributes attrs) { + if (Files.exists(dir.resolve("instance.bson"))) { + Path relative = path.relativize(dir); + String name = relative.toString(); + instances.add(name); + return FileVisitResult.SKIP_SUBTREE; + } else { + return FileVisitResult.CONTINUE; + } + } + }); + } catch (IOException var6) { + throw SneakyThrow.sneakyThrow(var6); + } + } + } + + return instances; + } + + private static void onPlayerConnect(@Nonnull PlayerConnectEvent event) { + Holder holder = event.getHolder(); + Player playerComponent = holder.getComponent(Player.getComponentType()); + + assert playerComponent != null; + + PlayerConfigData playerConfig = playerComponent.getPlayerConfigData(); + InstanceEntityConfig config = InstanceEntityConfig.ensureAndGet(holder); + String lastWorldName = playerConfig.getWorld(); + World lastWorld = Universe.get().getWorld(lastWorldName); + WorldReturnPoint fallbackWorld = config.getReturnPoint(); + if (fallbackWorld != null && (lastWorld == null || fallbackWorld.isReturnOnReconnect())) { + lastWorld = Universe.get().getWorld(fallbackWorld.getWorld()); + if (lastWorld != null) { + Transform transform = fallbackWorld.getReturnPoint(); + TransformComponent transformComponent = holder.ensureAndGetComponent(TransformComponent.getComponentType()); + transformComponent.setPosition(transform.getPosition()); + Vector3f rotationClone = transformComponent.getRotation().clone(); + rotationClone.setYaw(transform.getRotation().getYaw()); + transformComponent.setRotation(rotationClone); + HeadRotation headRotationComponent = holder.ensureAndGetComponent(HeadRotation.getComponentType()); + headRotationComponent.teleportRotation(transform.getRotation()); + } + } else if (lastWorld != null) { + config.setReturnPointOverride(config.getReturnPoint()); + } + } + + private static void onPlayerAddToWorld(@Nonnull AddPlayerToWorldEvent event) { + Holder holder = event.getHolder(); + InstanceWorldConfig worldConfig = InstanceWorldConfig.get(event.getWorld().getWorldConfig()); + if (worldConfig == null) { + InstanceEntityConfig entityConfig = holder.getComponent(InstanceEntityConfig.getComponentType()); + if (entityConfig != null && entityConfig.getReturnPoint() != null) { + entityConfig.setReturnPoint(null); + } + } else { + InstanceEntityConfig entityConfig = InstanceEntityConfig.ensureAndGet(holder); + if (entityConfig.getReturnPointOverride() == null) { + entityConfig.setReturnPoint(worldConfig.getReturnPoint()); + } else { + WorldReturnPoint override = entityConfig.getReturnPointOverride(); + override.setReturnOnReconnect(worldConfig.shouldPreventReconnection()); + entityConfig.setReturnPoint(override); + entityConfig.setReturnPointOverride(null); + } + } + } + + private static void onPlayerReady(@Nonnull PlayerReadyEvent event) { + Player player = event.getPlayer(); + World world = player.getWorld(); + if (world != null) { + WorldConfig worldConfig = world.getWorldConfig(); + InstanceWorldConfig instanceWorldConfig = InstanceWorldConfig.get(worldConfig); + if (instanceWorldConfig != null) { + InstanceDiscoveryConfig discoveryConfig = instanceWorldConfig.getDiscovery(); + if (discoveryConfig != null) { + PlayerConfigData playerConfigData = player.getPlayerConfigData(); + UUID instanceUuid = worldConfig.getUuid(); + if (discoveryConfig.alwaysDisplay() || !playerConfigData.getDiscoveredInstances().contains(instanceUuid)) { + Set discoveredInstances = new HashSet<>(playerConfigData.getDiscoveredInstances()); + discoveredInstances.add(instanceUuid); + playerConfigData.setDiscoveredInstances(discoveredInstances); + Ref playerRef = event.getPlayerRef(); + if (playerRef.isValid()) { + world.execute(() -> { + Store store = world.getEntityStore().getStore(); + showInstanceDiscovery(playerRef, store, instanceUuid, discoveryConfig); + }); + } + } + } + } + } + } + + private static void showInstanceDiscovery( + @Nonnull Ref ref, @Nonnull Store store, @Nonnull UUID instanceUuid, @Nonnull InstanceDiscoveryConfig discoveryConfig + ) { + DiscoverInstanceEvent.Display discoverInstanceEvent = new DiscoverInstanceEvent.Display(instanceUuid, discoveryConfig.clone()); + store.invoke(ref, discoverInstanceEvent); + discoveryConfig = discoverInstanceEvent.getDiscoveryConfig(); + if (!discoverInstanceEvent.isCancelled() && discoverInstanceEvent.shouldDisplay()) { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + if (playerRefComponent != null) { + String subtitleKey = discoveryConfig.getSubtitleKey(); + Message subtitle = subtitleKey != null ? Message.translation(subtitleKey) : Message.empty(); + EventTitleUtil.showEventTitleToPlayer( + playerRefComponent, + Message.translation(discoveryConfig.getTitleKey()), + subtitle, + discoveryConfig.isMajor(), + discoveryConfig.getIcon(), + discoveryConfig.getDuration(), + discoveryConfig.getFadeInDuration(), + discoveryConfig.getFadeOutDuration() + ); + String discoverySoundEventId = discoveryConfig.getDiscoverySoundEventId(); + if (discoverySoundEventId != null) { + int assetIndex = SoundEvent.getAssetMap().getIndex(discoverySoundEventId); + if (assetIndex != Integer.MIN_VALUE) { + SoundUtil.playSoundEvent2d(ref, assetIndex, SoundCategory.UI, store); + } + } + } + } + } + + private static void onPlayerDrainFromWorld(@Nonnull DrainPlayerFromWorldEvent event) { + InstanceEntityConfig config = InstanceEntityConfig.removeAndGet(event.getHolder()); + if (config != null) { + WorldReturnPoint returnPoint = config.getReturnPoint(); + if (returnPoint != null) { + World returnWorld = Universe.get().getWorld(returnPoint.getWorld()); + if (returnWorld != null) { + event.setWorld(returnWorld); + event.setTransform(returnPoint.getReturnPoint()); + } + } + } + } + + private static void generateSchema(@Nonnull GenerateSchemaEvent event) { + ObjectSchema worldConfig = WorldConfig.CODEC.toSchema(event.getContext()); + Map props = worldConfig.getProperties(); + props.put("UUID", Schema.anyOf(new StringSchema(), new ObjectSchema())); + worldConfig.setTitle("Instance Configuration"); + worldConfig.setId("InstanceConfig.json"); + Schema.HytaleMetadata hytaleMetadata = worldConfig.getHytale(); + if (hytaleMetadata != null) { + hytaleMetadata.setPath("Instances"); + hytaleMetadata.setExtension("instance.bson"); + hytaleMetadata.setUiEditorIgnore(Boolean.TRUE); + } + + event.addSchema("InstanceConfig.json", worldConfig); + event.addSchemaLink("InstanceConfig", List.of("Instances/**/instance.bson"), ".bson"); + } + + private void validateInstanceAssets(@Nonnull LoadAssetEvent event) { + Path path = AssetModule.get().getBaseAssetPack().getRoot().resolve("Server").resolve("Instances"); + if (Options.getOptionSet().has(Options.VALIDATE_ASSETS) && Files.isDirectory(path) && !event.isShouldShutdown()) { + StringBuilder errors = new StringBuilder(); + + for (String name : this.getInstanceAssets()) { + StringBuilder sb = new StringBuilder(); + Path instancePath = getInstanceAssetPath(name); + Universe universe = Universe.get(); + WorldConfig config = WorldConfig.load(instancePath.resolve("instance.bson")).join(); + IChunkStorageProvider storage = config.getChunkStorageProvider(); + config.setChunkStorageProvider(new MigrationChunkStorageProvider(new IChunkStorageProvider[]{storage}, EmptyChunkStorageProvider.INSTANCE)); + config.setResourceStorageProvider(EmptyResourceStorageProvider.INSTANCE); + config.setUuid(UUID.randomUUID()); + config.setSavingPlayers(false); + config.setIsAllNPCFrozen(true); + config.setSavingConfig(false); + config.setTicking(false); + config.setGameMode(GameMode.Creative); + config.setDeleteOnRemove(false); + config.setCompassUpdating(false); + InstanceWorldConfig.ensureAndGet(config).setRemovalConditions(RemovalCondition.EMPTY); + config.markChanged(); + String worldName = "instance-validate-" + safeName(name); + + try { + World world = universe.makeWorld(worldName, instancePath, config, false).join(); + EnumSet options = EnumSet.of(ValidationOption.BLOCK_STATES, ValidationOption.BLOCKS); + world.validate(sb, WorldValidationUtil.blockValidator(errors, options), options); + } catch (Exception var18) { + sb.append("\t").append(var18.getMessage()); + this.getLogger().at(Level.SEVERE).withCause(var18).log("Failed to validate: " + name); + } finally { + if (!sb.isEmpty()) { + errors.append("Instance: ").append(name).append('\n').append((CharSequence)sb).append('\n'); + } + } + + if (universe.getWorld(worldName) != null) { + universe.removeWorld(worldName); + } + } + + if (!errors.isEmpty()) { + this.getLogger().at(Level.SEVERE).log("Failed to validate instances:\n" + errors); + event.failed(true, "failed to validate instances"); + } + + HytaleLogger.getLogger() + .at(Level.INFO) + .log("Loading Instance assets phase completed! Boot time %s", FormatUtil.nanosToString(System.nanoTime() - event.getBootStart())); + } + } + + @Nonnull + public static String safeName(@Nonnull String name) { + return name.replace('/', '-'); + } + + @Nonnull + public ResourceType getInstanceDataResourceType() { + return this.instanceDataResourceType; + } + + @Nonnull + public ComponentType getInstanceEntityConfigComponentType() { + return this.instanceEntityConfigComponentType; + } + + @Nonnull + public ComponentType getInstanceBlockComponentType() { + return this.instanceBlockComponentType; + } + + @Nonnull + public ComponentType getConfigurableInstanceBlockComponentType() { + return this.configurableInstanceBlockComponentType; + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/blocks/ConfigurableInstanceBlock.java b/src/com/hypixel/hytale/builtin/instances/blocks/ConfigurableInstanceBlock.java new file mode 100644 index 0000000..11d42d5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/blocks/ConfigurableInstanceBlock.java @@ -0,0 +1,232 @@ +package com.hypixel.hytale.builtin.instances.blocks; + +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ConfigurableInstanceBlock implements Component { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(ConfigurableInstanceBlock.class, ConfigurableInstanceBlock::new) + .appendInherited(new KeyedCodec<>("WorldName", Codec.UUID_BINARY), (o, i) -> o.worldUUID = i, o -> o.worldUUID, (o, p) -> o.worldUUID = p.worldUUID) + .add() + .appendInherited( + new KeyedCodec<>("CloseOnBlockRemove", Codec.BOOLEAN), + (o, i) -> o.closeOnRemove = i, + o -> o.closeOnRemove, + (o, p) -> o.closeOnRemove = p.closeOnRemove + ) + .add() + .appendInherited( + new KeyedCodec<>("InstanceName", Codec.STRING), (o, i) -> o.instanceName = i, o -> o.instanceName, (o, p) -> o.instanceName = p.instanceName + ) + .add() + .appendInherited(new KeyedCodec<>("InstanceKey", Codec.STRING), (o, i) -> o.instanceKey = i, o -> o.instanceKey, (o, p) -> o.instanceKey = p.instanceKey) + .add() + .appendInherited( + new KeyedCodec<>("PositionOffset", Vector3d.CODEC), + (o, i) -> o.positionOffset = i, + o -> o.positionOffset, + (o, p) -> o.positionOffset = p.positionOffset + ) + .add() + .appendInherited(new KeyedCodec<>("Rotation", Vector3f.ROTATION), (o, i) -> o.rotation = i, o -> o.rotation, (o, p) -> o.rotation = p.rotation) + .add() + .appendInherited( + new KeyedCodec<>("PersonalReturnPoint", Codec.BOOLEAN), + (o, i) -> o.personalReturnPoint = i, + o -> o.personalReturnPoint, + (o, p) -> o.personalReturnPoint = p.personalReturnPoint + ) + .add() + .appendInherited( + new KeyedCodec<>("RemoveBlockAfter", Codec.DOUBLE), + (o, i) -> o.removeBlockAfter = i, + o -> o.removeBlockAfter, + (o, p) -> o.removeBlockAfter = p.removeBlockAfter + ) + .add() + .build(); + protected UUID worldUUID; + protected CompletableFuture worldFuture; + protected boolean closeOnRemove = true; + private String instanceName; + private String instanceKey; + @Nullable + private Vector3d positionOffset; + @Nullable + private Vector3f rotation; + private boolean personalReturnPoint = false; + private double removeBlockAfter = -1.0; + + public static ComponentType getComponentType() { + return InstancesPlugin.get().getConfigurableInstanceBlockComponentType(); + } + + public ConfigurableInstanceBlock() { + } + + public ConfigurableInstanceBlock( + UUID worldUUID, + boolean closeOnRemove, + String instanceName, + String instanceKey, + @Nullable Vector3d positionOffset, + @Nullable Vector3f rotation, + boolean personalReturnPoint, + double removeBlockAfter + ) { + this.worldUUID = worldUUID; + this.closeOnRemove = closeOnRemove; + this.instanceName = instanceName; + this.instanceKey = instanceKey; + this.positionOffset = positionOffset; + this.rotation = rotation; + this.personalReturnPoint = personalReturnPoint; + this.removeBlockAfter = removeBlockAfter; + } + + public UUID getWorldUUID() { + return this.worldUUID; + } + + public void setWorldUUID(UUID worldUUID) { + this.worldUUID = worldUUID; + } + + public CompletableFuture getWorldFuture() { + return this.worldFuture; + } + + public void setWorldFuture(CompletableFuture worldFuture) { + this.worldFuture = worldFuture; + } + + public boolean isCloseOnRemove() { + return this.closeOnRemove; + } + + public void setCloseOnRemove(boolean closeOnRemove) { + this.closeOnRemove = closeOnRemove; + } + + public String getInstanceName() { + return this.instanceName; + } + + public void setInstanceName(@Nonnull String instanceName) { + this.instanceName = instanceName; + } + + public String getInstanceKey() { + return this.instanceKey; + } + + public void setInstanceKey(@Nonnull String instanceKey) { + this.instanceKey = instanceKey; + } + + @Nullable + public Vector3d getPositionOffset() { + return this.positionOffset; + } + + public void setPositionOffset(@Nullable Vector3d positionOffset) { + this.positionOffset = positionOffset; + } + + @Nullable + public Vector3f getRotation() { + return this.rotation; + } + + public void setRotation(@Nullable Vector3f rotation) { + this.rotation = rotation; + } + + public boolean isPersonalReturnPoint() { + return this.personalReturnPoint; + } + + public void setPersonalReturnPoint(boolean personalReturnPoint) { + this.personalReturnPoint = personalReturnPoint; + } + + public double getRemoveBlockAfter() { + return this.removeBlockAfter; + } + + public void setRemoveBlockAfter(double removeBlockAfter) { + this.removeBlockAfter = removeBlockAfter; + } + + @Nullable + @Override + public Component clone() { + return new ConfigurableInstanceBlock( + this.worldUUID, + this.closeOnRemove, + this.instanceName, + this.instanceKey, + this.positionOffset, + this.rotation, + this.personalReturnPoint, + this.removeBlockAfter + ); + } + + public static class OnRemove extends RefSystem { + public OnRemove() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + ConfigurableInstanceBlock instance = commandBuffer.getComponent(ref, ConfigurableInstanceBlock.getComponentType()); + + assert instance != null; + + if (instance.closeOnRemove) { + if (instance.worldUUID != null) { + InstancesPlugin.get(); + InstancesPlugin.safeRemoveInstance(instance.worldUUID); + } else if (instance.worldFuture != null) { + instance.worldFuture.thenAccept(world -> { + InstancesPlugin.get(); + InstancesPlugin.safeRemoveInstance(world.getName()); + }); + } + } + } + + @Nullable + @Override + public Query getQuery() { + return ConfigurableInstanceBlock.getComponentType(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/blocks/InstanceBlock.java b/src/com/hypixel/hytale/builtin/instances/blocks/InstanceBlock.java new file mode 100644 index 0000000..9e68507 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/blocks/InstanceBlock.java @@ -0,0 +1,118 @@ +package com.hypixel.hytale.builtin.instances.blocks; + +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InstanceBlock implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(InstanceBlock.class, InstanceBlock::new) + .appendInherited(new KeyedCodec<>("WorldName", Codec.UUID_BINARY), (o, i) -> o.worldUUID = i, o -> o.worldUUID, (o, p) -> o.worldUUID = p.worldUUID) + .add() + .appendInherited( + new KeyedCodec<>("CloseOnBlockRemove", Codec.BOOLEAN), + (o, i) -> o.closeOnRemove = i, + o -> o.closeOnRemove, + (o, p) -> o.closeOnRemove = p.closeOnRemove + ) + .add() + .build(); + protected UUID worldUUID; + protected CompletableFuture worldFuture; + protected boolean closeOnRemove = true; + + public static ComponentType getComponentType() { + return InstancesPlugin.get().getInstanceBlockComponentType(); + } + + public InstanceBlock() { + } + + public InstanceBlock(UUID worldUUID, boolean closeOnRemove) { + this.worldUUID = worldUUID; + this.closeOnRemove = closeOnRemove; + } + + public UUID getWorldUUID() { + return this.worldUUID; + } + + public void setWorldUUID(UUID worldUUID) { + this.worldUUID = worldUUID; + } + + public CompletableFuture getWorldFuture() { + return this.worldFuture; + } + + public void setWorldFuture(CompletableFuture worldFuture) { + this.worldFuture = worldFuture; + } + + public boolean isCloseOnRemove() { + return this.closeOnRemove; + } + + public void setCloseOnRemove(boolean closeOnRemove) { + this.closeOnRemove = closeOnRemove; + } + + @Nullable + @Override + public Component clone() { + return new InstanceBlock(this.worldUUID, this.closeOnRemove); + } + + public static class OnRemove extends RefSystem { + public OnRemove() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + InstanceBlock instance = commandBuffer.getComponent(ref, InstanceBlock.getComponentType()); + + assert instance != null; + + if (instance.closeOnRemove) { + if (instance.worldUUID != null) { + InstancesPlugin.get(); + InstancesPlugin.safeRemoveInstance(instance.worldUUID); + } else if (instance.worldFuture != null) { + instance.worldFuture.thenAccept(world -> { + InstancesPlugin.get(); + InstancesPlugin.safeRemoveInstance(world.getName()); + }); + } + } + } + + @Nullable + @Override + public Query getQuery() { + return InstanceBlock.getComponentType(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/command/InstanceEditCopyCommand.java b/src/com/hypixel/hytale/builtin/instances/command/InstanceEditCopyCommand.java new file mode 100644 index 0000000..a118f85 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/command/InstanceEditCopyCommand.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.builtin.instances.command; + +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class InstanceEditCopyCommand extends AbstractAsyncCommand { + private final RequiredArg originNameArg = this.withRequiredArg("instanceToCopy", "server.commands.instances.editcopy.origin.name", ArgTypes.STRING); + private final RequiredArg destinationNameArg = this.withRequiredArg( + "newInstanceName", "server.commands.instances.editcopy.destination.name", ArgTypes.STRING + ); + + public InstanceEditCopyCommand() { + super("copy", "server.commands.instances.edit.copy.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + if (AssetModule.get().getBaseAssetPack().isImmutable()) { + context.sendMessage(Message.translation("server.commands.instances.edit.assetsImmutable")); + return CompletableFuture.completedFuture(null); + } else { + String instanceToCopy = this.originNameArg.get(context); + Path originPath = InstancesPlugin.getInstanceAssetPath(instanceToCopy); + if (!Files.exists(originPath)) { + context.sendMessage(Message.translation("server.commands.instances.edit.copy.originNotFound").param("path", originPath.toAbsolutePath().toString())); + return CompletableFuture.completedFuture(null); + } else { + String destinationName = this.destinationNameArg.get(context); + Path destinationPath = originPath.getParent().resolve(destinationName); + if (Files.exists(destinationPath)) { + context.sendMessage( + Message.translation("server.commands.instances.edit.copy.destinationExists").param("path", destinationPath.toAbsolutePath().toString()) + ); + return CompletableFuture.completedFuture(null); + } else { + WorldConfig worldConfig; + try { + worldConfig = WorldConfig.load(originPath.resolve("instance.bson")).join(); + } catch (Throwable var10) { + context.sendMessage(Message.translation("server.commands.instances.edit.copy.errorLoading")); + InstancesPlugin.get().getLogger().at(Level.SEVERE).log("Error loading origin instance config for copy", var10); + return CompletableFuture.completedFuture(null); + } + + worldConfig.setUuid(UUID.randomUUID()); + Path destinationConfigFile = destinationPath.resolve("instance.bson"); + + try { + FileUtil.copyDirectory(originPath, destinationPath); + Files.deleteIfExists(destinationConfigFile); + } catch (Throwable var9) { + context.sendMessage(Message.translation("server.commands.instances.edit.copy.errorCopying")); + InstancesPlugin.get().getLogger().at(Level.SEVERE).log("Error copying instance folder for copy", var9); + return CompletableFuture.completedFuture(null); + } + + return WorldConfig.save(destinationConfigFile, worldConfig) + .thenRun( + () -> context.sendMessage( + Message.translation("server.commands.instances.copiedInstanceAssetConfig") + .param("origin", instanceToCopy) + .param("destination", destinationName) + ) + ); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/command/InstanceEditListCommand.java b/src/com/hypixel/hytale/builtin/instances/command/InstanceEditListCommand.java new file mode 100644 index 0000000..afba1ab --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/command/InstanceEditListCommand.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.builtin.instances.command; + +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class InstanceEditListCommand extends AbstractAsyncCommand { + public InstanceEditListCommand() { + super("list", "server.commands.instances.edit.list.desc"); + } + + @Nonnull + @Override + public CompletableFuture executeAsync(@Nonnull CommandContext context) { + List instanceAssets = InstancesPlugin.get().getInstanceAssets(); + context.sendMessage( + MessageFormat.list(Message.translation("server.commands.instances.edit.list.header"), instanceAssets.stream().map(Message::raw).toList()) + ); + return CompletableFuture.completedFuture(null); + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/command/InstanceEditLoadCommand.java b/src/com/hypixel/hytale/builtin/instances/command/InstanceEditLoadCommand.java new file mode 100644 index 0000000..9502f7f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/command/InstanceEditLoadCommand.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.builtin.instances.command; + +import com.hypixel.hytale.builtin.instances.InstanceValidator; +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class InstanceEditLoadCommand extends AbstractAsyncCommand { + private final RequiredArg instanceNameArg = this.withRequiredArg("instanceName", "server.commands.instances.edit.arg.name", ArgTypes.STRING) + .addValidator(new InstanceValidator()); + + public InstanceEditLoadCommand() { + super("load", "server.commands.instances.edit.load.desc"); + } + + @Nonnull + @Override + public CompletableFuture executeAsync(@Nonnull CommandContext context) { + if (AssetModule.get().getBaseAssetPack().isImmutable()) { + context.sendMessage(Message.translation("server.commands.instances.edit.assetsImmutable")); + return CompletableFuture.completedFuture(null); + } else { + String name = this.instanceNameArg.get(context); + context.sendMessage(Message.translation("server.commands.instance.beginLoading").param("name", name)); + InstancesPlugin.get(); + return InstancesPlugin.loadInstanceAssetForEdit(name).thenAccept(world -> { + context.sendMessage(Message.translation("server.commands.instance.doneLoading").param("world", world.getName())); + if (context.isPlayer()) { + Ref ref = context.senderAsPlayerRef(); + if (ref == null || !ref.isValid()) { + return; + } + + Store playerStore = ref.getStore(); + World playerWorld = playerStore.getExternalData().getWorld(); + playerWorld.execute(() -> { + Transform spawn = world.getWorldConfig().getSpawnProvider().getSpawnPoint(ref, playerStore); + playerStore.addComponent(ref, Teleport.getComponentType(), new Teleport(world, spawn)); + }); + } + }); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/command/InstanceEditNewCommand.java b/src/com/hypixel/hytale/builtin/instances/command/InstanceEditNewCommand.java new file mode 100644 index 0000000..a107f81 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/command/InstanceEditNewCommand.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.instances.command; + +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class InstanceEditNewCommand extends AbstractAsyncCommand { + private final RequiredArg instanceNameArg = this.withRequiredArg("instanceName", "server.commands.instances.edit.arg.name", ArgTypes.STRING); + private final OptionalArg packName = this.withOptionalArg("pack", "server.commands.instances.edit.arg.packName", ArgTypes.STRING); + + public InstanceEditNewCommand() { + super("new", "server.commands.instances.edit.new.desc"); + } + + @Nonnull + @Override + public CompletableFuture executeAsync(@Nonnull CommandContext context) { + if (AssetModule.get().getBaseAssetPack().isImmutable()) { + context.sendMessage(Message.translation("server.commands.instances.edit.assetsImmutable")); + return CompletableFuture.completedFuture(null); + } else { + String packId = this.packName.get(context); + AssetPack pack; + if (packId != null) { + pack = AssetModule.get().getAssetPack(packId); + if (pack == null) { + throw new IllegalArgumentException("Unknown asset pack: " + packId); + } + } else { + pack = AssetModule.get().getBaseAssetPack(); + } + + String name = this.instanceNameArg.get(context); + Path path = pack.getRoot().resolve("Server").resolve("Instances").resolve(name); + WorldConfig defaultConfig = new WorldConfig(); + + try { + Files.createDirectories(path); + } catch (IOException var8) { + context.sendMessage(Message.translation("server.commands.instances.createDirectory.failed").param("errormsg", var8.getMessage())); + return CompletableFuture.completedFuture(null); + } + + return WorldConfig.save(path.resolve("instance.bson"), defaultConfig) + .thenRun(() -> context.sendMessage(Message.translation("server.commands.instances.createdInstanceAssetConfig").param("name", name))); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/command/InstanceExitCommand.java b/src/com/hypixel/hytale/builtin/instances/command/InstanceExitCommand.java new file mode 100644 index 0000000..ae5f8be --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/command/InstanceExitCommand.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.builtin.instances.command; + +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class InstanceExitCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_INSTANCES_EXIT_FAIL = Message.translation("server.commands.instances.exit.fail"); + @Nonnull + private static final Message MESSAGE_COMMANDS_INSTANCES_EXIT_SUCCESS_SELF = Message.translation("server.commands.instances.exit.success.self"); + + public InstanceExitCommand() { + super("exit", "server.commands.instances.exit.desc"); + this.addAliases("leave"); + this.addUsageVariant(new InstanceExitCommand.InstanceExitOtherCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + try { + InstancesPlugin.exitInstance(ref, store); + context.sendMessage(MESSAGE_COMMANDS_INSTANCES_EXIT_SUCCESS_SELF); + } catch (IllegalArgumentException var7) { + context.sendMessage(MESSAGE_COMMANDS_INSTANCES_EXIT_FAIL); + } + } + + private static class InstanceExitOtherCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private static final Message MESSAGE_COMMANDS_INSTANCES_EXIT_FAIL = Message.translation("server.commands.instances.exit.fail"); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + + InstanceExitOtherCommand() { + super("server.commands.instances.exit.other.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef playerRef = this.playerArg.get(context); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + try { + InstancesPlugin.exitInstance(ref, store); + context.sendMessage(Message.translation("server.commands.instances.exit.success.other").param("username", playerRef.getUsername())); + } catch (IllegalArgumentException var6) { + context.sendMessage(MESSAGE_COMMANDS_INSTANCES_EXIT_FAIL); + } + } + }); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/command/InstanceMigrateCommand.java b/src/com/hypixel/hytale/builtin/instances/command/InstanceMigrateCommand.java new file mode 100644 index 0000000..667f23d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/command/InstanceMigrateCommand.java @@ -0,0 +1,226 @@ +package com.hypixel.hytale.builtin.instances.command; + +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.builtin.instances.config.InstanceWorldConfig; +import com.hypixel.hytale.builtin.instances.removal.RemovalCondition; +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemType; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.migrations.ChunkColumnMigrationSystem; +import com.hypixel.hytale.server.core.modules.migrations.ChunkSectionMigrationSystem; +import com.hypixel.hytale.server.core.modules.migrations.MigrationModule; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.EntityChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.IChunkLoader; +import com.hypixel.hytale.server.core.universe.world.storage.IChunkSaver; +import com.hypixel.hytale.server.core.universe.world.storage.component.ChunkSavingSystems; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.nio.file.Path; +import java.util.BitSet; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicLong; +import javax.annotation.Nonnull; + +public class InstanceMigrateCommand extends AbstractAsyncCommand { + private static final long CHUNK_UPDATE_INTERVAL = 100L; + + public InstanceMigrateCommand() { + super("migrate", ""); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + InstancesPlugin instancePlugin = InstancesPlugin.get(); + List instancesToMigrate = instancePlugin.getInstanceAssets(); + CompletableFuture[] futures = new CompletableFuture[instancesToMigrate.size()]; + AtomicLong chunkCount = new AtomicLong(); + AtomicLong chunksMigrated = new AtomicLong(); + + for (int i = 0; i < instancesToMigrate.size(); i++) { + String asset = instancesToMigrate.get(i); + Path instancePath = InstancesPlugin.getInstanceAssetPath(asset); + CompletableFuture configFuture = WorldConfig.load(instancePath.resolve("instance.bson")); + futures[i] = CompletableFutureUtil._catch(configFuture.thenCompose(config -> migrateInstance(context, asset, config, chunkCount, chunksMigrated))); + futures[i].join(); + } + + return CompletableFuture.allOf(futures) + .whenComplete( + (result, throwable) -> { + if (throwable != null) { + context.sendMessage(Message.translation("server.commands.instances.migrate.failed").param("error", throwable.getMessage())); + } else { + context.sendMessage( + Message.translation("server.commands.instances.migrate.complete").param("worlds", futures.length).param("chunks", chunkCount.get()) + ); + } + } + ); + } + + @Nonnull + private static CompletableFuture migrateInstance( + @Nonnull CommandContext context, @Nonnull String asset, @Nonnull WorldConfig config, @Nonnull AtomicLong chunkCount, @Nonnull AtomicLong chunksMigrated + ) { + Path instancePath = InstancesPlugin.getInstanceAssetPath(asset); + Universe universe = Universe.get(); + config.setUuid(UUID.randomUUID()); + config.setSavingPlayers(false); + config.setIsAllNPCFrozen(true); + config.setSavingConfig(false); + config.setTicking(false); + config.setGameMode(GameMode.Creative); + config.setDeleteOnRemove(false); + config.setCompassUpdating(false); + InstanceWorldConfig.ensureAndGet(config).setRemovalConditions(RemovalCondition.EMPTY); + config.markChanged(); + String worldName = "instance-migrate-" + InstancesPlugin.safeName(asset); + return universe.makeWorld(worldName, instancePath, config, true).thenCompose(world -> { + IChunkLoader loader = world.getChunkStore().getLoader(); + IChunkSaver saver = world.getChunkStore().getSaver(); + return CompletableFuture.>supplyAsync(() -> { + ChunkStore chunkStore = world.getChunkStore(); + ChunkSavingSystems.Data data = chunkStore.getStore().getResource(ChunkStore.SAVE_RESOURCE); + data.isSaving = false; + return data.waitForSavingChunks(); + }, world).thenCompose(val -> (CompletionStage)val).thenComposeAsync(SneakyThrow.sneakyFunction(_void -> { + LongSet chunks = loader.getIndexes(); + ObjectArrayList> futures = new ObjectArrayList<>(chunks.size()); + LongIterator iterator = chunks.iterator(); + + while (iterator.hasNext()) { + long chunkIndex = iterator.nextLong(); + chunkCount.incrementAndGet(); + int chunkX = ChunkUtil.xOfChunkIndex(chunkIndex); + int chunkZ = ChunkUtil.zOfChunkIndex(chunkIndex); + futures.add(CompletableFutureUtil._catch(loader.loadHolder(chunkX, chunkZ).thenComposeAsync(holder -> { + ComponentRegistry.Data data = ChunkStore.REGISTRY.getData(); + ChunkStore chunkStore = world.getChunkStore(); + Store store = chunkStore.getStore(); + boolean shouldSave = false; + SystemType systemType = MigrationModule.get().getChunkColumnMigrationSystem(); + BitSet systemIndexes = data.getSystemIndexesForType(systemType); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + ChunkColumnMigrationSystem system = data.getSystem(systemIndex, systemType); + if (system.test(ChunkStore.REGISTRY, holder.getArchetype())) { + system.onEntityAdd((Holder)holder, AddReason.LOAD, store); + shouldSave = true; + } + } + + systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + ChunkColumnMigrationSystem system = data.getSystem(systemIndex, systemType); + if (system.test(ChunkStore.REGISTRY, holder.getArchetype())) { + system.onEntityRemoved((Holder)holder, RemoveReason.REMOVE, store); + } + } + + EntityChunk entityChunk = holder.getComponent(EntityChunk.getComponentType()); + if (entityChunk != null && !entityChunk.getEntityHolders().isEmpty()) { + Store entityStore = world.getEntityStore().getStore(); + ComponentRegistry.Data entityData = EntityStore.REGISTRY.getData(); + List> entities = entityChunk.getEntityHolders(); + SystemType systemTypex = EntityModule.get().getMigrationSystemType(); + BitSet systemIndexesx = entityData.getSystemIndexesForType(systemTypex); + int systemIndexx = -1; + + while ((systemIndexx = systemIndexesx.nextSetBit(systemIndexx + 1)) >= 0) { + EntityModule.MigrationSystem system = entityData.getSystem(systemIndexx, systemTypex); + + for (int i = 0; i < entities.size(); i++) { + Holder section = entities.get(i); + if (system.test(EntityStore.REGISTRY, section.getArchetype())) { + system.onEntityAdd(section, AddReason.LOAD, entityStore); + shouldSave = true; + } + } + } + + systemIndexx = -1; + + while ((systemIndexx = systemIndexesx.nextSetBit(systemIndexx + 1)) >= 0) { + EntityModule.MigrationSystem system = entityData.getSystem(systemIndexx, systemTypex); + + for (int ix = 0; ix < entities.size(); ix++) { + Holder section = entities.get(ix); + if (system.test(EntityStore.REGISTRY, section.getArchetype())) { + system.onEntityRemoved(section, RemoveReason.REMOVE, entityStore); + } + } + } + } + + ChunkColumn chunkColumn = holder.getComponent(ChunkColumn.getComponentType()); + if (chunkColumn != null && chunkColumn.getSectionHolders() != null) { + Holder[] sections = chunkColumn.getSectionHolders(); + SystemType systemTypex = MigrationModule.get().getChunkSectionMigrationSystem(); + BitSet systemIndexesx = data.getSystemIndexesForType(systemTypex); + int systemIndexx = -1; + + while ((systemIndexx = systemIndexesx.nextSetBit(systemIndexx + 1)) >= 0) { + ChunkSectionMigrationSystem system = data.getSystem(systemIndexx, systemTypex); + + for (Holder section : sections) { + if (system.test(ChunkStore.REGISTRY, section.getArchetype())) { + system.onEntityAdd(section, AddReason.LOAD, store); + shouldSave = true; + } + } + } + + systemIndexx = -1; + + while ((systemIndexx = systemIndexesx.nextSetBit(systemIndexx + 1)) >= 0) { + ChunkSectionMigrationSystem system = data.getSystem(systemIndexx, systemTypex); + + for (Holder sectionx : sections) { + if (system.test(ChunkStore.REGISTRY, sectionx.getArchetype())) { + system.onEntityRemoved(sectionx, RemoveReason.REMOVE, store); + } + } + } + } + + return shouldSave ? saver.saveHolder(chunkX, chunkZ, (Holder)holder) : CompletableFuture.completedFuture(null); + }, world).whenComplete((v, throwable) -> { + long migratedChunks = chunksMigrated.incrementAndGet(); + long max = chunkCount.get(); + if (migratedChunks % 100L == 0L || migratedChunks == max) { + context.sendMessage(Message.translation("server.commands.instances.migrate.update").param("chunks", migratedChunks).param("max", max)); + } + }))); + } + + return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).whenCompleteAsync((result, throwable) -> { + context.sendMessage(Message.translation("server.commands.instances.migrate.worldDone").param("asset", asset)); + Universe.get().removeWorld(worldName); + }); + })); + }); + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/command/InstanceSpawnCommand.java b/src/com/hypixel/hytale/builtin/instances/command/InstanceSpawnCommand.java new file mode 100644 index 0000000..68bc6c8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/command/InstanceSpawnCommand.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.builtin.instances.command; + +import com.hypixel.hytale.builtin.instances.InstanceValidator; +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeDoublePosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class InstanceSpawnCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg instanceNameArg = this.withRequiredArg("instanceName", "server.commands.instances.spawn.arg.name", ArgTypes.STRING) + .addValidator(new InstanceValidator()); + @Nonnull + private final OptionalArg positionArg = this.withOptionalArg( + "position", "server.commands.instances.spawn.arg.position", ArgTypes.RELATIVE_POSITION + ); + @Nonnull + private final DefaultArg rotationArg = this.withDefaultArg( + "rotation", "server.commands.instances.spawn.arg.rotation", ArgTypes.ROTATION, Vector3f.FORWARD, "server.commands.instances.spawn.arg.rotation.default" + ); + + public InstanceSpawnCommand() { + super("spawn", "server.commands.instances.spawn.desc"); + this.addAliases("sp"); + } + + protected Vector3f getSpawnRotation( + @Nonnull Ref ref, + @Nonnull CommandContext context, + @Nonnull DefaultArg rotationArg, + @Nonnull ComponentAccessor componentAccessor + ) { + if (!rotationArg.provided(context) && context.isPlayer()) { + TransformComponent headRotationComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert headRotationComponent != null; + + return headRotationComponent.getRotation().clone(); + } else { + return rotationArg.get(context); + } + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Vector3d position; + if (!this.positionArg.provided(context)) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + position = transformComponent.getPosition(); + } else { + position = this.positionArg.get(context).getRelativePosition(context, world, store); + } + + Transform returnLocation = new Transform(position.clone(), this.getSpawnRotation(ref, context, this.rotationArg, store).clone()); + String instanceName = this.instanceNameArg.get(context); + CompletableFuture instanceWorld = InstancesPlugin.get().spawnInstance(instanceName, world, returnLocation); + InstancesPlugin.teleportPlayerToLoadingInstance(ref, store, instanceWorld, null); + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/command/InstancesCommand.java b/src/com/hypixel/hytale/builtin/instances/command/InstancesCommand.java new file mode 100644 index 0000000..200f58c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/command/InstancesCommand.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.instances.command; + +import com.hypixel.hytale.builtin.instances.page.InstanceListPage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class InstancesCommand extends AbstractPlayerCommand { + public InstancesCommand() { + super("instances", "server.commands.instances.desc"); + this.addAliases("instance", "inst"); + this.addSubCommand(new InstancesCommand.InstancesEditCommand()); + this.addSubCommand(new InstanceSpawnCommand()); + this.addSubCommand(new InstanceExitCommand()); + this.addSubCommand(new InstanceMigrateCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getPageManager().openCustomPage(ref, store, new InstanceListPage(playerRef)); + } + + public static class InstancesEditCommand extends AbstractCommandCollection { + public InstancesEditCommand() { + super("edit", "server.commands.instances.edit.desc"); + this.addAliases("modify"); + this.addSubCommand(new InstanceEditNewCommand()); + this.addSubCommand(new InstanceEditCopyCommand()); + this.addSubCommand(new InstanceEditLoadCommand()); + this.addSubCommand(new InstanceEditListCommand()); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/config/ExitInstance.java b/src/com/hypixel/hytale/builtin/instances/config/ExitInstance.java new file mode 100644 index 0000000..f4a9f83 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/config/ExitInstance.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.builtin.instances.config; + +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.asset.type.gameplay.respawn.HomeOrSpawnPoint; +import com.hypixel.hytale.server.core.asset.type.gameplay.respawn.RespawnController; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class ExitInstance implements RespawnController { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(ExitInstance.class, ExitInstance::new) + .append(new KeyedCodec<>("Fallback", RespawnController.CODEC), (o, i) -> o.fallback = i, o -> o.fallback) + .addValidator(Validators.nonNull()) + .add() + .build(); + @Nonnull + private RespawnController fallback = HomeOrSpawnPoint.INSTANCE; + + public ExitInstance() { + } + + @Override + public void respawnPlayer(@Nonnull World world, @Nonnull Ref playerReference, @Nonnull CommandBuffer commandBuffer) { + try { + InstancesPlugin.exitInstance(playerReference, commandBuffer); + } catch (Exception var6) { + PlayerRef playerRefComponent = commandBuffer.getComponent(playerReference, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + InstancesPlugin.get().getLogger().at(Level.WARNING).withCause(var6).log(playerRefComponent.getUsername() + " failed to leave an instance"); + this.fallback.respawnPlayer(world, playerReference, commandBuffer); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/config/InstanceDiscoveryConfig.java b/src/com/hypixel/hytale/builtin/instances/config/InstanceDiscoveryConfig.java new file mode 100644 index 0000000..d5db053 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/config/InstanceDiscoveryConfig.java @@ -0,0 +1,163 @@ +package com.hypixel.hytale.builtin.instances.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InstanceDiscoveryConfig { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(InstanceDiscoveryConfig.class, InstanceDiscoveryConfig::new) + .documentation("Configuration for displaying an event title when a player discovers an instance.") + .append(new KeyedCodec<>("TitleKey", Codec.STRING), (o, i) -> o.titleKey = i, o -> o.titleKey) + .documentation("The translation key for the primary title (e.g., \"server.instances.gaia_temple.title\").") + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("SubtitleKey", Codec.STRING), (o, i) -> o.subtitleKey = i, o -> o.subtitleKey) + .documentation("The translation key for the subtitle (e.g., \"server.instances.gaia_temple.subtitle\").") + .add() + .append(new KeyedCodec<>("Display", Codec.BOOLEAN), (o, i) -> o.display = i, o -> o.display) + .documentation("Whether to display the discovery title and play the discovery sound.") + .add() + .append(new KeyedCodec<>("AlwaysDisplay", Codec.BOOLEAN), (o, i) -> o.alwaysDisplay = i, o -> o.alwaysDisplay) + .documentation("Whether to always display the discovery title, even if already discovered.") + .add() + .append(new KeyedCodec<>("DiscoverySoundEventId", Codec.STRING), (o, i) -> o.discoverySoundEventId = i, o -> o.discoverySoundEventId) + .documentation("The sound event ID to play when discovering this instance.") + .add() + .append(new KeyedCodec<>("Icon", Codec.STRING), (o, i) -> o.icon = i, o -> o.icon) + .documentation("The icon to display with the event title.") + .add() + .append(new KeyedCodec<>("Major", Codec.BOOLEAN), (o, i) -> o.major = i, o -> o.major) + .documentation("Whether this is a major discovery (affects visual presentation).") + .add() + .append(new KeyedCodec<>("Duration", Codec.FLOAT), (o, i) -> o.duration = i, o -> o.duration) + .documentation("The duration to display the event title for, in seconds.") + .add() + .append(new KeyedCodec<>("FadeInDuration", Codec.FLOAT), (o, i) -> o.fadeInDuration = i, o -> o.fadeInDuration) + .documentation("The fade-in duration for the event title, in seconds.") + .add() + .append(new KeyedCodec<>("FadeOutDuration", Codec.FLOAT), (o, i) -> o.fadeOutDuration = i, o -> o.fadeOutDuration) + .documentation("The fade-out duration for the event title, in seconds.") + .add() + .build(); + @Nullable + private String titleKey; + @Nullable + private String subtitleKey; + private boolean display = true; + private boolean alwaysDisplay = false; + @Nullable + private String discoverySoundEventId; + @Nullable + private String icon; + private boolean major = false; + private float duration = 4.0F; + private float fadeInDuration = 1.5F; + private float fadeOutDuration = 1.5F; + + public InstanceDiscoveryConfig() { + } + + @Nullable + public String getTitleKey() { + return this.titleKey; + } + + public void setTitleKey(@Nonnull String titleKey) { + this.titleKey = titleKey; + } + + @Nullable + public String getSubtitleKey() { + return this.subtitleKey; + } + + public void setSubtitleKey(@Nullable String subtitleKey) { + this.subtitleKey = subtitleKey; + } + + public boolean isDisplay() { + return this.display; + } + + public void setDisplay(boolean display) { + this.display = display; + } + + public boolean alwaysDisplay() { + return this.alwaysDisplay; + } + + public void setAlwaysDisplay(boolean alwaysDisplay) { + this.alwaysDisplay = alwaysDisplay; + } + + @Nullable + public String getDiscoverySoundEventId() { + return this.discoverySoundEventId; + } + + public void setDiscoverySoundEventId(@Nullable String discoverySoundEventId) { + this.discoverySoundEventId = discoverySoundEventId; + } + + @Nullable + public String getIcon() { + return this.icon; + } + + public void setIcon(@Nullable String icon) { + this.icon = icon; + } + + public boolean isMajor() { + return this.major; + } + + public void setMajor(boolean major) { + this.major = major; + } + + public float getDuration() { + return this.duration; + } + + public void setDuration(float duration) { + this.duration = duration; + } + + public float getFadeInDuration() { + return this.fadeInDuration; + } + + public void setFadeInDuration(float fadeInDuration) { + this.fadeInDuration = fadeInDuration; + } + + public float getFadeOutDuration() { + return this.fadeOutDuration; + } + + public void setFadeOutDuration(float fadeOutDuration) { + this.fadeOutDuration = fadeOutDuration; + } + + @Nonnull + public InstanceDiscoveryConfig clone() { + InstanceDiscoveryConfig clone = new InstanceDiscoveryConfig(); + clone.titleKey = this.titleKey; + clone.subtitleKey = this.subtitleKey; + clone.display = this.display; + clone.alwaysDisplay = this.alwaysDisplay; + clone.discoverySoundEventId = this.discoverySoundEventId; + clone.icon = this.icon; + clone.major = this.major; + clone.duration = this.duration; + clone.fadeInDuration = this.fadeInDuration; + clone.fadeOutDuration = this.fadeOutDuration; + return clone; + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/config/InstanceEntityConfig.java b/src/com/hypixel/hytale/builtin/instances/config/InstanceEntityConfig.java new file mode 100644 index 0000000..41211d0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/config/InstanceEntityConfig.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.builtin.instances.config; + +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InstanceEntityConfig implements Component { + public static final String ID = "Instance"; + public static final BuilderCodec CODEC = BuilderCodec.builder(InstanceEntityConfig.class, InstanceEntityConfig::new) + .appendInherited( + new KeyedCodec<>("ReturnPoint", WorldReturnPoint.CODEC), (o, i) -> o.returnPoint = i, o -> o.returnPoint, (o, p) -> o.returnPoint = p.returnPoint + ) + .add() + .build(); + private WorldReturnPoint returnPoint; + private transient WorldReturnPoint returnPointOverride; + + public InstanceEntityConfig() { + } + + @Nonnull + public static ComponentType getComponentType() { + return InstancesPlugin.get().getInstanceEntityConfigComponentType(); + } + + @Nonnull + public static InstanceEntityConfig ensureAndGet(@Nonnull Holder holder) { + ComponentType type = getComponentType(); + return holder.ensureAndGetComponent(type); + } + + @Nullable + public static InstanceEntityConfig removeAndGet(@Nonnull Holder holder) { + ComponentType type = getComponentType(); + InstanceEntityConfig component = holder.getComponent(type); + holder.removeComponent(type); + return component; + } + + public WorldReturnPoint getReturnPoint() { + return this.returnPoint; + } + + public void setReturnPoint(WorldReturnPoint returnPoint) { + this.returnPoint = returnPoint; + } + + public WorldReturnPoint getReturnPointOverride() { + return this.returnPointOverride; + } + + public void setReturnPointOverride(WorldReturnPoint returnPointOverride) { + this.returnPointOverride = returnPointOverride; + } + + @Nonnull + public InstanceEntityConfig clone() { + InstanceEntityConfig v = new InstanceEntityConfig(); + v.returnPoint = v.returnPoint.clone(); + v.returnPointOverride = v.returnPointOverride.clone(); + return v; + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/config/InstanceWorldConfig.java b/src/com/hypixel/hytale/builtin/instances/config/InstanceWorldConfig.java new file mode 100644 index 0000000..9cebcf1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/config/InstanceWorldConfig.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.builtin.instances.config; + +import com.hypixel.hytale.builtin.instances.removal.RemovalCondition; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InstanceWorldConfig { + public static final String ID = "Instance"; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(InstanceWorldConfig.class, InstanceWorldConfig::new) + .append( + new KeyedCodec<>("RemovalConditions", new ArrayCodec<>(RemovalCondition.CODEC, RemovalCondition[]::new)), + (o, i) -> o.removalConditions = i, + o -> o.removalConditions + ) + .documentation( + "The set of conditions that have to be met to remove the world.\n\nIf no conditions are provided (e.g. a empty list) then the world will not be removed automatically" + ) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("ReturnPoint", WorldReturnPoint.CODEC), (o, i) -> o.returnPoint = i, o -> o.returnPoint) + .documentation("The location to send players to when leaving this world.") + .add() + .append(new KeyedCodec<>("PreventReconnection", Codec.BOOLEAN), (o, i) -> o.preventReconnection = i, o -> o.preventReconnection) + .documentation( + "Whether to prevent reconnecting into this world.\n\nPlayers that reconnect back into the world will forced out of the world to the return point (or their own)." + ) + .add() + .append(new KeyedCodec<>("Discovery", InstanceDiscoveryConfig.CODEC), (o, i) -> o.discovery = i, o -> o.discovery) + .documentation("Optional discovery configuration for displaying an event title when a player enters this instance for the first time.") + .add() + .build(); + @Nonnull + private RemovalCondition[] removalConditions = RemovalCondition.EMPTY; + @Nullable + private WorldReturnPoint returnPoint; + private boolean preventReconnection = false; + @Nullable + private InstanceDiscoveryConfig discovery; + + public InstanceWorldConfig() { + } + + @Nullable + public static InstanceWorldConfig get(@Nonnull WorldConfig config) { + return config.getPluginConfig().get(InstanceWorldConfig.class); + } + + @Nonnull + public static InstanceWorldConfig ensureAndGet(@Nonnull WorldConfig config) { + return config.getPluginConfig().computeIfAbsent(InstanceWorldConfig.class, v -> new InstanceWorldConfig()); + } + + public boolean shouldPreventReconnection() { + return this.preventReconnection; + } + + @Nonnull + public RemovalCondition[] getRemovalConditions() { + return this.removalConditions; + } + + public void setRemovalConditions(@Nonnull RemovalCondition... removalConditions) { + this.removalConditions = removalConditions; + } + + @Nullable + public WorldReturnPoint getReturnPoint() { + return this.returnPoint; + } + + public void setReturnPoint(@Nullable WorldReturnPoint returnPoint) { + this.returnPoint = returnPoint; + } + + @Nullable + public InstanceDiscoveryConfig getDiscovery() { + return this.discovery; + } + + public void setDiscovery(@Nullable InstanceDiscoveryConfig discovery) { + this.discovery = discovery; + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/config/WorldReturnPoint.java b/src/com/hypixel/hytale/builtin/instances/config/WorldReturnPoint.java new file mode 100644 index 0000000..93fe807 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/config/WorldReturnPoint.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.builtin.instances.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Transform; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class WorldReturnPoint { + public static final BuilderCodec CODEC = BuilderCodec.builder(WorldReturnPoint.class, WorldReturnPoint::new) + .documentation("A world/location pair that is used as a place to return players to.") + .append(new KeyedCodec<>("World", Codec.UUID_BINARY), (o, i) -> o.world = i, o -> o.world) + .documentation("The UUID of the world to return the player to.") + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("ReturnPoint", Transform.CODEC), (o, i) -> o.returnPoint = i, o -> o.returnPoint) + .documentation("The location to send the player to.") + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("ReturnOnReconnect", Codec.BOOLEAN), (o, i) -> o.returnOnReconnect = i, o -> o.returnOnReconnect) + .documentation("Whether this point should be triggered when a player reconnects into a world.") + .add() + .build(); + private UUID world; + private Transform returnPoint; + private boolean returnOnReconnect = false; + + public WorldReturnPoint() { + } + + public WorldReturnPoint(UUID world, Transform returnPoint, boolean returnOnReconnect) { + this.world = world; + this.returnPoint = returnPoint; + this.returnOnReconnect = returnOnReconnect; + } + + public UUID getWorld() { + return this.world; + } + + public void setWorld(UUID world) { + this.world = world; + } + + public Transform getReturnPoint() { + return this.returnPoint; + } + + public void setReturnPoint(Transform returnPoint) { + this.returnPoint = returnPoint; + } + + public boolean isReturnOnReconnect() { + return this.returnOnReconnect; + } + + public void setReturnOnReconnect(boolean returnOnReconnect) { + this.returnOnReconnect = returnOnReconnect; + } + + @Nonnull + public WorldReturnPoint clone() { + return new WorldReturnPoint(this.world, this.returnPoint, this.returnOnReconnect); + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/event/DiscoverInstanceEvent.java b/src/com/hypixel/hytale/builtin/instances/event/DiscoverInstanceEvent.java new file mode 100644 index 0000000..5d9e003 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/event/DiscoverInstanceEvent.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.builtin.instances.event; + +import com.hypixel.hytale.builtin.instances.config.InstanceDiscoveryConfig; +import com.hypixel.hytale.component.system.EcsEvent; +import com.hypixel.hytale.component.system.ICancellableEcsEvent; +import java.util.UUID; +import javax.annotation.Nonnull; + +public abstract class DiscoverInstanceEvent extends EcsEvent { + @Nonnull + private final UUID instanceWorldUuid; + @Nonnull + private final InstanceDiscoveryConfig discoveryConfig; + + public DiscoverInstanceEvent(@Nonnull UUID instanceWorldUuid, @Nonnull InstanceDiscoveryConfig discoveryConfig) { + this.instanceWorldUuid = instanceWorldUuid; + this.discoveryConfig = discoveryConfig; + } + + @Nonnull + public UUID getInstanceWorldUuid() { + return this.instanceWorldUuid; + } + + @Nonnull + public InstanceDiscoveryConfig getDiscoveryConfig() { + return this.discoveryConfig; + } + + public static class Display extends DiscoverInstanceEvent implements ICancellableEcsEvent { + private boolean cancelled = false; + private boolean display; + + public Display(@Nonnull UUID instanceWorldUuid, @Nonnull InstanceDiscoveryConfig discoveryConfig) { + super(instanceWorldUuid, discoveryConfig); + this.display = discoveryConfig.isDisplay(); + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + public boolean shouldDisplay() { + return this.display; + } + + public void setDisplay(boolean display) { + this.display = display; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/interactions/ExitInstanceInteraction.java b/src/com/hypixel/hytale/builtin/instances/interactions/ExitInstanceInteraction.java new file mode 100644 index 0000000..b789646 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/interactions/ExitInstanceInteraction.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.instances.interactions; + +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.teleport.PendingTeleport; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ExitInstanceInteraction extends SimpleInstantInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ExitInstanceInteraction.class, ExitInstanceInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Teleports the **Entity** out of the current **Instance** and places them at their set return point.") + .build(); + + public ExitInstanceInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + Ref ref = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent == null || !playerComponent.isWaitingForClientReady()) { + Archetype archetype = commandBuffer.getArchetype(ref); + if (!archetype.contains(Teleport.getComponentType()) && !archetype.contains(PendingTeleport.getComponentType())) { + InstancesPlugin.exitInstance(ref, commandBuffer); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/interactions/TeleportConfigInstanceInteraction.java b/src/com/hypixel/hytale/builtin/instances/interactions/TeleportConfigInstanceInteraction.java new file mode 100644 index 0000000..e0712ee --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/interactions/TeleportConfigInstanceInteraction.java @@ -0,0 +1,234 @@ +package com.hypixel.hytale.builtin.instances.interactions; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.builtin.instances.blocks.ConfigurableInstanceBlock; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.teleport.PendingTeleport; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TeleportConfigInstanceInteraction extends SimpleBlockInteraction { + @Nonnull + private static final Message MESSAGE_GENERAL_INTERACTION_CONFIGURE_INSTANCE_NO_INSTANCE_NAME = Message.translation( + "server.general.interaction.configureInstance.noInstanceName" + ); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + TeleportConfigInstanceInteraction.class, TeleportConfigInstanceInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation( + "Teleports the **Player** to the named instance, creating it if required.\n\nThis is configured via a UI instead of inside the interaction. This interaction just executes that set configuration." + ) + .build(); + private static final int SET_BLOCK_SETTINGS = 256; + + public TeleportConfigInstanceInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + Ref ref = context.getEntity(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent != null && !playerComponent.isWaitingForClientReady()) { + Archetype archetype = commandBuffer.getArchetype(ref); + if (!archetype.contains(Teleport.getComponentType()) && !archetype.contains(PendingTeleport.getComponentType())) { + InstancesPlugin module = InstancesPlugin.get(); + Universe universe = Universe.get(); + ChunkStore chunkStore = world.getChunkStore(); + Ref chunkRef = chunkStore.getChunkReference(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)); + if (chunkRef != null && chunkRef.isValid()) { + BlockComponentChunk blockComponentChunk = chunkStore.getStore().getComponent(chunkRef, BlockComponentChunk.getComponentType()); + + assert blockComponentChunk != null; + + Ref blockRef = blockComponentChunk.getEntityReference(ChunkUtil.indexBlockInColumn(targetBlock.x, targetBlock.y, targetBlock.z)); + if (blockRef != null && blockRef.isValid()) { + ConfigurableInstanceBlock configurableInstanceBlock = chunkStore.getStore() + .getComponent(blockRef, ConfigurableInstanceBlock.getComponentType()); + if (configurableInstanceBlock != null) { + if (configurableInstanceBlock.getInstanceName() == null) { + playerComponent.sendMessage(MESSAGE_GENERAL_INTERACTION_CONFIGURE_INSTANCE_NO_INSTANCE_NAME); + } else { + CompletableFuture targetWorldFuture = null; + Transform returnPoint = null; + World targetWorld; + if (configurableInstanceBlock.getInstanceKey() != null) { + targetWorld = universe.getWorld(configurableInstanceBlock.getInstanceKey()); + if (targetWorld == null) { + returnPoint = makeReturnPoint(configurableInstanceBlock, context, commandBuffer); + targetWorldFuture = module.spawnInstance( + configurableInstanceBlock.getInstanceName(), configurableInstanceBlock.getInstanceKey(), world, returnPoint + ); + } + } else { + UUID worldUuid = configurableInstanceBlock.getWorldUUID(); + targetWorldFuture = configurableInstanceBlock.getWorldFuture(); + targetWorld = worldUuid != null ? universe.getWorld(worldUuid) : null; + if (targetWorld == null && targetWorldFuture == null) { + returnPoint = makeReturnPoint(configurableInstanceBlock, context, commandBuffer); + targetWorldFuture = module.spawnInstance(configurableInstanceBlock.getInstanceName(), world, returnPoint); + configurableInstanceBlock.setWorldFuture(targetWorldFuture); + targetWorldFuture.thenAccept(instanceWorld -> { + if (blockRef.isValid()) { + configurableInstanceBlock.setWorldFuture(null); + configurableInstanceBlock.setWorldUUID(instanceWorld.getWorldConfig().getUuid()); + blockComponentChunk.markNeedsSaving(); + } + }); + } + } + + if (targetWorldFuture != null) { + Transform personalReturnPoint = getPersonalReturnPoint(configurableInstanceBlock, context, returnPoint, commandBuffer); + InstancesPlugin.teleportPlayerToLoadingInstance(ref, commandBuffer, targetWorldFuture, personalReturnPoint); + } else if (targetWorld != null) { + Transform personalReturnPoint = getPersonalReturnPoint(configurableInstanceBlock, context, returnPoint, commandBuffer); + InstancesPlugin.teleportPlayerToInstance(ref, commandBuffer, targetWorld, personalReturnPoint); + } + + double removeBlockAfter = configurableInstanceBlock.getRemoveBlockAfter(); + if (removeBlockAfter >= 0.0) { + if (removeBlockAfter == 0.0) { + long chunkIndex = ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z); + WorldChunk worldChunk = world.getChunk(chunkIndex); + worldChunk.setBlock(targetBlock.x, targetBlock.y, targetBlock.z, 0, 256); + } else { + int block = world.getBlock(targetBlock); + new CompletableFuture().completeOnTimeout(null, (long)(removeBlockAfter * 1.0E9), TimeUnit.NANOSECONDS).thenRunAsync(() -> { + if (world.getBlock(targetBlock) == block) { + long chunkIndex = ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z); + WorldChunk worldChunkx = world.getChunk(chunkIndex); + worldChunkx.setBlock(targetBlock.x, targetBlock.y, targetBlock.z, 0, 256); + } + }, world); + } + } + } + } + } + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } + + @Nullable + private static Transform getPersonalReturnPoint( + @Nonnull ConfigurableInstanceBlock state, + @Nonnull InteractionContext context, + @Nullable Transform returnPoint, + @Nonnull ComponentAccessor componentAccessor + ) { + if (!state.isPersonalReturnPoint()) { + return null; + } else { + return returnPoint == null ? makeReturnPoint(state, context, componentAccessor) : returnPoint; + } + } + + @Nonnull + private static Transform makeReturnPoint( + @Nonnull ConfigurableInstanceBlock state, @Nonnull InteractionContext context, @Nonnull ComponentAccessor componentAccessor + ) { + BlockPosition targetBlock = context.getTargetBlock(); + if (targetBlock == null) { + throw new IllegalArgumentException("Can't use OriginSource.BLOCK without a target block"); + } else { + World world = componentAccessor.getExternalData().getWorld(); + ChunkStore chunkStore = world.getChunkStore(); + Store chunkComponentStore = chunkStore.getStore(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z); + Ref chunkRef = chunkStore.getChunkReference(chunkIndex); + if (chunkRef != null && chunkRef.isValid()) { + BlockChunk blockChunkComponent = chunkComponentStore.getComponent(chunkRef, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + WorldChunk worldChunkComponent = chunkComponentStore.getComponent(chunkRef, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + BlockType blockType = worldChunkComponent.getBlockType(targetBlock.x, targetBlock.y, targetBlock.z); + if (blockType == null) { + throw new IllegalArgumentException("Block type not found"); + } else { + IndexedLookupTableAssetMap hitboxAssetMap = BlockBoundingBoxes.getAssetMap(); + BlockSection section = blockChunkComponent.getSectionAtBlockY(targetBlock.y); + int rotationIndex = section.getRotationIndex(targetBlock.x, targetBlock.y, targetBlock.z); + RotationTuple rotation = RotationTuple.get(rotationIndex); + Box hitbox = hitboxAssetMap.getAsset(blockType.getHitboxTypeIndex()).get(rotationIndex).getBoundingBox(); + Vector3d position = state.getPositionOffset() != null ? rotation.rotate(state.getPositionOffset()) : new Vector3d(); + position.x = position.x + (hitbox.middleX() + targetBlock.x); + position.y = position.y + (hitbox.middleY() + targetBlock.y); + position.z = position.z + (hitbox.middleZ() + targetBlock.z); + Vector3f rotationOutput = Vector3f.NaN; + if (state.getRotation() != null) { + rotationOutput = state.getRotation().clone(); + rotationOutput.addRotationOnAxis(Axis.Y, rotation.yaw().getDegrees()); + rotationOutput.addRotationOnAxis(Axis.X, rotation.pitch().getDegrees()); + } + + return new Transform(position, rotationOutput); + } + } else { + throw new IllegalArgumentException("Chunk not loaded"); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/interactions/TeleportInstanceInteraction.java b/src/com/hypixel/hytale/builtin/instances/interactions/TeleportInstanceInteraction.java new file mode 100644 index 0000000..42752c8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/interactions/TeleportInstanceInteraction.java @@ -0,0 +1,327 @@ +package com.hypixel.hytale.builtin.instances.interactions; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.builtin.instances.InstanceValidator; +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.builtin.instances.blocks.InstanceBlock; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.PendingTeleport; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TeleportInstanceInteraction extends SimpleInstantInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + TeleportInstanceInteraction.class, TeleportInstanceInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Teleports the **Player** to the named instance, creating it if required.") + .appendInherited( + new KeyedCodec<>("InstanceName", Codec.STRING), (o, i) -> o.instanceName = i, o -> o.instanceName, (o, p) -> o.instanceName = p.instanceName + ) + .documentation("The name of the **instance** to teleport to.") + .addValidator(Validators.nonNull()) + .addValidator(InstanceValidator.INSTANCE) + .add() + .appendInherited( + new KeyedCodec<>("InstanceKey", Codec.STRING), (o, i) -> o.instanceKey = i, o -> o.instanceKey, (o, p) -> o.instanceKey = p.instanceKey + ) + .documentation("The key to name the world. Random if not provided") + .add() + .appendInherited( + new KeyedCodec<>("PositionOffset", Vector3d.CODEC), + (o, i) -> o.positionOffset = i, + o -> o.positionOffset, + (o, p) -> o.positionOffset = p.positionOffset + ) + .documentation("The offset to apply to the return point.\n\nUsed to prevent repeated interactions when returning from the instance.") + .add() + .appendInherited(new KeyedCodec<>("Rotation", Vector3f.ROTATION), (o, i) -> o.rotation = i, o -> o.rotation, (o, p) -> o.rotation = p.rotation) + .documentation("The rotation to set the player to when returning from an instance.") + .add() + .appendInherited( + new KeyedCodec<>("OriginSource", TeleportInstanceInteraction.OriginSource.CODEC), + (o, i) -> o.originSource = i, + o -> o.originSource, + (o, p) -> o.originSource = p.originSource + ) + .documentation("The source to use for the return position.\n\nDefaults to the player's position.") + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("PersonalReturnPoint", Codec.BOOLEAN), + (o, i) -> o.personalReturnPoint = i, + o -> o.personalReturnPoint, + (o, p) -> o.personalReturnPoint = p.personalReturnPoint + ) + .documentation( + "Whether the player entering the instance will have their own return point\nset to the current location. Overriding the world's return point." + ) + .add() + .appendInherited( + new KeyedCodec<>("CloseOnBlockRemove", Codec.BOOLEAN), + (o, i) -> o.closeOnBlockRemove = i, + o -> o.closeOnBlockRemove, + (o, p) -> o.closeOnBlockRemove = p.closeOnBlockRemove + ) + .documentation("Whether to delete the instance when the portal block is removed.") + .add() + .appendInherited( + new KeyedCodec<>("RemoveBlockAfter", Codec.DOUBLE), + (o, i) -> o.removeBlockAfter = i, + o -> o.removeBlockAfter, + (o, p) -> o.removeBlockAfter = p.removeBlockAfter + ) + .documentation( + "The number of seconds to wait before removing the block that triggered\nthe interaction. A negative value disables this.\n\nThis is needed instead of using another interaction due to all interactions\nbeing stopped once teleporting to another world." + ) + .add() + .afterDecode(i -> { + if (i.rotation != null) { + i.rotation.scale((float) (Math.PI / 180.0)); + } + }) + .build(); + private static final int SET_BLOCK_SETTINGS = 256; + private String instanceName; + private String instanceKey; + private Vector3d positionOffset; + private Vector3f rotation; + @Nonnull + private TeleportInstanceInteraction.OriginSource originSource = TeleportInstanceInteraction.OriginSource.PLAYER; + private boolean personalReturnPoint = false; + private boolean closeOnBlockRemove = true; + private double removeBlockAfter = -1.0; + + public TeleportInstanceInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + Ref ref = context.getEntity(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent != null && !playerComponent.isWaitingForClientReady()) { + Archetype archetype = commandBuffer.getArchetype(ref); + if (!archetype.contains(Teleport.getComponentType()) && !archetype.contains(PendingTeleport.getComponentType())) { + World world = commandBuffer.getExternalData().getWorld(); + InstancesPlugin module = InstancesPlugin.get(); + Universe universe = Universe.get(); + CompletableFuture targetWorldFuture = null; + Transform returnPoint = null; + World targetWorld; + if (this.instanceKey != null) { + targetWorld = universe.getWorld(this.instanceKey); + if (targetWorld == null) { + returnPoint = this.makeReturnPoint(ref, context, commandBuffer); + targetWorldFuture = module.spawnInstance(this.instanceName, this.instanceKey, world, returnPoint); + } + } else { + BlockPosition targetBlock = context.getTargetBlock(); + if (targetBlock == null) { + return; + } + + ChunkStore chunkStore = world.getChunkStore(); + Ref chunkRef = chunkStore.getChunkReference(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)); + if (chunkRef == null || !chunkRef.isValid()) { + return; + } + + BlockComponentChunk blockComponentChunk = chunkStore.getStore().getComponent(chunkRef, BlockComponentChunk.getComponentType()); + + assert blockComponentChunk != null; + + int index = ChunkUtil.indexBlockInColumn(targetBlock.x, targetBlock.y, targetBlock.z); + Ref blockRef = blockComponentChunk.getEntityReference(index); + InstanceBlock instanceState; + if (blockRef == null) { + Holder holder = ChunkStore.REGISTRY.newHolder(); + instanceState = holder.ensureAndGetComponent(InstanceBlock.getComponentType()); + holder.addComponent(BlockModule.BlockStateInfo.getComponentType(), new BlockModule.BlockStateInfo(index, chunkRef)); + blockRef = chunkStore.getStore().addEntity(holder, AddReason.SPAWN); + instanceState.setCloseOnRemove(this.closeOnBlockRemove); + } else { + instanceState = chunkStore.getStore().getComponent(chunkRef, InstanceBlock.getComponentType()); + } + + if (blockRef == null) { + return; + } + + if (instanceState == null) { + instanceState = chunkStore.getStore().ensureAndGetComponent(blockRef, InstanceBlock.getComponentType()); + instanceState.setCloseOnRemove(this.closeOnBlockRemove); + } + + UUID worldName = instanceState.getWorldUUID(); + targetWorldFuture = instanceState.getWorldFuture(); + targetWorld = worldName != null ? universe.getWorld(worldName) : null; + if (targetWorld == null && targetWorldFuture == null) { + returnPoint = this.makeReturnPoint(ref, context, commandBuffer); + targetWorldFuture = module.spawnInstance(this.instanceName, world, returnPoint); + instanceState.setWorldFuture(targetWorldFuture); + Ref finalBlockRef = blockRef; + InstanceBlock finalInstanceState = instanceState; + targetWorldFuture.thenAccept(instanceWorld -> { + if (finalBlockRef.isValid()) { + finalInstanceState.setWorldFuture(null); + finalInstanceState.setWorldUUID(instanceWorld.getWorldConfig().getUuid()); + blockComponentChunk.markNeedsSaving(); + } + }); + } + } + + if (targetWorldFuture != null) { + Transform personalReturnPoint = this.getPersonalReturnPoint(ref, context, returnPoint, commandBuffer); + InstancesPlugin.teleportPlayerToLoadingInstance(ref, commandBuffer, targetWorldFuture, personalReturnPoint); + } else if (targetWorld != null) { + Transform personalReturnPoint = this.getPersonalReturnPoint(ref, context, returnPoint, commandBuffer); + InstancesPlugin.teleportPlayerToInstance(ref, commandBuffer, targetWorld, personalReturnPoint); + } + + if (this.removeBlockAfter >= 0.0) { + BlockPosition targetBlockx = context.getTargetBlock(); + if (targetBlockx != null) { + if (this.removeBlockAfter == 0.0) { + world.getChunk(ChunkUtil.indexChunkFromBlock(targetBlockx.x, targetBlockx.z)) + .setBlock(targetBlockx.x, targetBlockx.y, targetBlockx.z, 0, 256); + } else { + int block = world.getBlock(targetBlockx.x, targetBlockx.y, targetBlockx.z); + new CompletableFuture() + .completeOnTimeout(null, (long)(this.removeBlockAfter * 1.0E9), TimeUnit.NANOSECONDS) + .thenRunAsync( + () -> { + if (world.getBlock(targetBlock.x, targetBlock.y, targetBlock.z) == block) { + world.getChunk(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)) + .setBlock(targetBlock.x, targetBlock.y, targetBlock.z, 0, 256); + } + }, + world + ); + } + } + } + } + } + } + + @Nullable + private Transform getPersonalReturnPoint( + @Nonnull Ref playerRef, + @Nonnull InteractionContext context, + @Nullable Transform returnPoint, + @Nonnull ComponentAccessor componentAccessor + ) { + if (!this.personalReturnPoint) { + return null; + } else { + return returnPoint == null ? this.makeReturnPoint(playerRef, context, componentAccessor) : returnPoint; + } + } + + @Nonnull + private Transform makeReturnPoint( + @Nonnull Ref playerRef, @Nonnull InteractionContext context, @Nonnull ComponentAccessor componentAccessor + ) { + Transform transform = null; + switch (this.originSource) { + case PLAYER: + TransformComponent transformComponent = componentAccessor.getComponent(playerRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + transform = transformComponent.getTransform().clone(); + transform.getPosition().add(this.positionOffset); + transform.setRotation(this.rotation != null ? this.rotation : Vector3f.NaN); + break; + case BLOCK: + BlockPosition targetBlock = context.getTargetBlock(); + if (targetBlock == null) { + throw new IllegalArgumentException("Can't use OriginSource.BLOCK without a target block"); + } + + World world = componentAccessor.getExternalData().getWorld(); + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)); + if (chunk == null) { + throw new IllegalArgumentException("Missing chunk"); + } + + BlockType blockType = chunk.getBlockType(targetBlock.x, targetBlock.y, targetBlock.z); + int rotationIndex = chunk.getRotationIndex(targetBlock.x, targetBlock.y, targetBlock.z); + RotationTuple rotationTuple = RotationTuple.get(rotationIndex); + IndexedLookupTableAssetMap hitboxAssetMap = BlockBoundingBoxes.getAssetMap(); + Box hitbox = hitboxAssetMap.getAsset(blockType.getHitboxTypeIndex()).get(rotationIndex).getBoundingBox(); + Vector3d position = this.positionOffset != null ? rotationTuple.rotate(this.positionOffset) : new Vector3d(); + position.x = position.x + (hitbox.middleX() + targetBlock.x); + position.y = position.y + (hitbox.middleY() + targetBlock.y); + position.z = position.z + (hitbox.middleZ() + targetBlock.z); + Vector3f rotation = Vector3f.NaN; + if (this.rotation != null) { + rotation = this.rotation.clone(); + rotation.addRotationOnAxis(Axis.Y, rotationTuple.yaw().getDegrees()); + rotation.addRotationOnAxis(Axis.X, rotationTuple.pitch().getDegrees()); + } + + transform = new Transform(position, rotation); + } + + return transform; + } + + private static enum OriginSource { + PLAYER, + BLOCK; + + @Nonnull + public static EnumCodec CODEC = new EnumCodec<>(TeleportInstanceInteraction.OriginSource.class) + .documentKey(PLAYER, "The origin of operations will be based on the player's current position.") + .documentKey(BLOCK, "The origin of operations will be based on the middle of the block's hitbox."); + + private OriginSource() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/page/ConfigureInstanceBlockPage.java b/src/com/hypixel/hytale/builtin/instances/page/ConfigureInstanceBlockPage.java new file mode 100644 index 0000000..d2fd1aa --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/page/ConfigureInstanceBlockPage.java @@ -0,0 +1,259 @@ +package com.hypixel.hytale.builtin.instances.page; + +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.builtin.instances.blocks.ConfigurableInstanceBlock; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.ui.DropdownEntryInfo; +import com.hypixel.hytale.server.core.ui.LocalizableString; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ConfigureInstanceBlockPage extends InteractiveCustomUIPage { + @Nonnull + private final ConfigurableInstanceBlock instanceBlock; + @Nonnull + private final Ref ref; + @Nullable + private Vector3d positionOffset; + @Nullable + private Vector3f rotation; + + public ConfigureInstanceBlockPage(@Nonnull PlayerRef playerRef, @Nonnull Ref ref) { + super(playerRef, CustomPageLifetime.CanDismissOrCloseThroughInteraction, ConfigureInstanceBlockPage.PageData.CODEC); + this.instanceBlock = ref.getStore().getComponent(ref, ConfigurableInstanceBlock.getComponentType()); + this.ref = ref; + this.positionOffset = this.instanceBlock.getPositionOffset() != null ? this.instanceBlock.getPositionOffset().clone() : null; + this.rotation = this.instanceBlock.getRotation() != null ? this.instanceBlock.getRotation().clone() : null; + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/ConfigureInstanceBlockPage.ui"); + ObjectArrayList worlds = new ObjectArrayList<>(); + worlds.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.configureInstanceBlockPage.noInstances"), "")); + List instances = InstancesPlugin.get().getInstanceAssets(); + instances.sort(String::compareToIgnoreCase); + + for (String instance : instances) { + worlds.add(new DropdownEntryInfo(LocalizableString.fromString(instance), instance)); + } + + commandBuilder.set("#Instance #Input.Entries", worlds); + commandBuilder.set("#Instance #Input.Value", this.instanceBlock.getInstanceName() == null ? "" : this.instanceBlock.getInstanceName()); + this.buildPositionOffset(commandBuilder); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, + "#PositionOffset #Use #CheckBox", + new EventData() + .append("Action", ConfigureInstanceBlockPage.Action.PositionOffset.name()) + .append("@PositionOffset", "#PositionOffset #Use #CheckBox.Value"), + false + ); + this.buildRotation(commandBuilder); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, + "#Rotation #Use #CheckBox", + new EventData().append("Action", ConfigureInstanceBlockPage.Action.Rotation.name()).append("@Rotation", "#Rotation #Use #CheckBox.Value"), + false + ); + commandBuilder.set("#InstanceKey #Input.Value", this.instanceBlock.getInstanceKey() == null ? "" : this.instanceBlock.getInstanceKey()); + commandBuilder.set("#PersonalReturnPoint #CheckBox.Value", this.instanceBlock.isPersonalReturnPoint()); + commandBuilder.set("#CloseOnBlockRemove #CheckBox.Value", this.instanceBlock.isCloseOnRemove()); + commandBuilder.set("#RemoveBlockAfter #Input.Value", this.instanceBlock.getRemoveBlockAfter()); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#SaveButton", + new EventData() + .append("Action", ConfigureInstanceBlockPage.Action.Save.name()) + .append("@Instance", "#Instance #Input.Value") + .append("@InstanceKey", "#InstanceKey #Input.Value") + .append("@PositionOffset", "#PositionOffset #Use #CheckBox.Value") + .append("@PositionOffsetX", "#PositionOffset #X #Input.Value") + .append("@PositionOffsetY", "#PositionOffset #Y #Input.Value") + .append("@PositionOffsetZ", "#PositionOffset #Z #Input.Value") + .append("@Rotation", "#Rotation #Use #CheckBox.Value") + .append("@RotationPitch", "#Rotation #Pitch #Input.Value") + .append("@RotationYaw", "#Rotation #Yaw #Input.Value") + .append("@RotationRoll", "#Rotation #Roll #Input.Value") + .append("@PersonalReturnPoint", "#PersonalReturnPoint #CheckBox.Value") + .append("@CloseOnBlockRemove", "#CloseOnBlockRemove #CheckBox.Value") + .append("@RemoveBlockAfter", "#RemoveBlockAfter #Input.Value") + ); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull ConfigureInstanceBlockPage.PageData data) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + BlockModule.BlockStateInfo info = this.ref.getStore().getComponent(this.ref, BlockModule.BlockStateInfo.getComponentType()); + BlockComponentChunk blockComponentChunk = this.ref.getStore().getComponent(info.getChunkRef(), BlockComponentChunk.getComponentType()); + + assert blockComponentChunk != null; + + switch (data.action) { + case Save: + this.instanceBlock.setInstanceName(this.emptyToNull(data.instance)); + this.instanceBlock.setInstanceKey(this.emptyToNull(data.instanceKey)); + this.instanceBlock.setPersonalReturnPoint(data.personalReturnPoint); + this.instanceBlock.setCloseOnRemove(data.closeOnBlockRemove); + this.instanceBlock.setRemoveBlockAfter(data.removeBlockAfter); + if (data.positionOffset) { + this.instanceBlock.setPositionOffset(new Vector3d(data.positionX, data.positionY, data.positionZ)); + } else { + this.instanceBlock.setPositionOffset(null); + } + + if (data.rotation) { + this.instanceBlock + .setRotation( + new Vector3f( + data.rotationPitch * (float) (Math.PI / 180.0), + data.rotationYaw * (float) (Math.PI / 180.0), + data.rotationRoll * (float) (Math.PI / 180.0) + ) + ); + } else { + this.instanceBlock.setRotation(null); + } + + blockComponentChunk.markNeedsSaving(); + playerComponent.getPageManager().setPage(ref, store, Page.None); + break; + case PositionOffset: + if (data.positionOffset) { + this.positionOffset = new Vector3d(); + } else { + this.positionOffset = null; + } + + blockComponentChunk.markNeedsSaving(); + UICommandBuilder commandBuilder = new UICommandBuilder(); + this.buildPositionOffset(commandBuilder); + this.sendUpdate(commandBuilder); + break; + case Rotation: + if (data.rotation) { + this.rotation = new Vector3f(); + } else { + this.rotation = null; + } + + blockComponentChunk.markNeedsSaving(); + UICommandBuilder commandBuilder = new UICommandBuilder(); + this.buildRotation(commandBuilder); + this.sendUpdate(commandBuilder); + } + } + + private String emptyToNull(@Nullable String s) { + return s != null && !s.isEmpty() ? s : null; + } + + private void buildPositionOffset(@Nonnull UICommandBuilder commandBuilder) { + boolean hasPosition = this.positionOffset != null; + commandBuilder.set("#PositionOffset #Use #CheckBox.Value", hasPosition); + commandBuilder.set("#PositionOffset #X.Visible", hasPosition); + commandBuilder.set("#PositionOffset #Y.Visible", hasPosition); + commandBuilder.set("#PositionOffset #Z.Visible", hasPosition); + if (hasPosition) { + commandBuilder.set("#PositionOffset #X #Input.Value", this.positionOffset.x); + commandBuilder.set("#PositionOffset #Y #Input.Value", this.positionOffset.y); + commandBuilder.set("#PositionOffset #Z #Input.Value", this.positionOffset.z); + } + } + + private void buildRotation(@Nonnull UICommandBuilder commandBuilder) { + boolean hasRotation = this.rotation != null; + commandBuilder.set("#Rotation #Use #CheckBox.Value", hasRotation); + commandBuilder.set("#Rotation #Pitch.Visible", hasRotation); + commandBuilder.set("#Rotation #Yaw.Visible", hasRotation); + commandBuilder.set("#Rotation #Roll.Visible", hasRotation); + if (hasRotation) { + commandBuilder.set("#Rotation #Pitch #Input.Value", this.rotation.x * (180.0F / (float)Math.PI)); + commandBuilder.set("#Rotation #Yaw #Input.Value", this.rotation.y * (180.0F / (float)Math.PI)); + commandBuilder.set("#Rotation #Roll #Input.Value", this.rotation.z * (180.0F / (float)Math.PI)); + } + } + + public static enum Action { + Save, + PositionOffset, + Rotation; + + private Action() { + } + } + + public static class PageData { + public static final String INSTANCE = "@Instance"; + public static final String INSTANCE_KEY = "@InstanceKey"; + public static final String POSITION_OFFSET = "@PositionOffset"; + public static final String POSITION_OFFSET_X = "@PositionOffsetX"; + public static final String POSITION_OFFSET_Y = "@PositionOffsetY"; + public static final String POSITION_OFFSET_Z = "@PositionOffsetZ"; + public static final String ROTATION = "@Rotation"; + public static final String ROTATION_PITCH = "@RotationPitch"; + public static final String ROTATION_YAW = "@RotationYaw"; + public static final String ROTATION_ROLL = "@RotationRoll"; + public static final String PERSONAL_RETURN_POINT = "@PersonalReturnPoint"; + public static final String CLOSE_ON_BLOCK_REMOVE = "@CloseOnBlockRemove"; + public static final String REMOVE_BLOCK_AFTER = "@RemoveBlockAfter"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + ConfigureInstanceBlockPage.PageData.class, ConfigureInstanceBlockPage.PageData::new + ) + .addField(new KeyedCodec<>("Action", new EnumCodec<>(ConfigureInstanceBlockPage.Action.class)), (o, i) -> o.action = i, o -> o.action) + .addField(new KeyedCodec<>("@Instance", Codec.STRING), (o, i) -> o.instance = i, o -> o.instance) + .addField(new KeyedCodec<>("@InstanceKey", Codec.STRING), (o, i) -> o.instanceKey = i, o -> o.instanceKey) + .addField(new KeyedCodec<>("@PositionOffset", Codec.BOOLEAN), (o, i) -> o.positionOffset = i, o -> o.positionOffset) + .addField(new KeyedCodec<>("@PositionOffsetX", Codec.DOUBLE), (o, i) -> o.positionX = i, o -> o.positionX) + .addField(new KeyedCodec<>("@PositionOffsetY", Codec.DOUBLE), (o, i) -> o.positionY = i, o -> o.positionY) + .addField(new KeyedCodec<>("@PositionOffsetZ", Codec.DOUBLE), (o, i) -> o.positionZ = i, o -> o.positionZ) + .addField(new KeyedCodec<>("@Rotation", Codec.BOOLEAN), (o, i) -> o.rotation = i, o -> o.rotation) + .addField(new KeyedCodec<>("@RotationPitch", Codec.FLOAT), (o, i) -> o.rotationPitch = i, o -> o.rotationPitch) + .addField(new KeyedCodec<>("@RotationYaw", Codec.FLOAT), (o, i) -> o.rotationYaw = i, o -> o.rotationYaw) + .addField(new KeyedCodec<>("@RotationRoll", Codec.FLOAT), (o, i) -> o.rotationRoll = i, o -> o.rotationRoll) + .addField(new KeyedCodec<>("@PersonalReturnPoint", Codec.BOOLEAN), (o, i) -> o.personalReturnPoint = i, o -> o.personalReturnPoint) + .addField(new KeyedCodec<>("@CloseOnBlockRemove", Codec.BOOLEAN), (o, i) -> o.closeOnBlockRemove = i, o -> o.closeOnBlockRemove) + .addField(new KeyedCodec<>("@RemoveBlockAfter", Codec.DOUBLE), (o, i) -> o.removeBlockAfter = i, o -> o.removeBlockAfter) + .build(); + public ConfigureInstanceBlockPage.Action action; + public String instance; + public String instanceKey; + public boolean positionOffset; + public double positionX; + public double positionY; + public double positionZ; + public boolean rotation; + public float rotationPitch; + public float rotationYaw; + public float rotationRoll; + public boolean personalReturnPoint; + public boolean closeOnBlockRemove; + public double removeBlockAfter; + + public PageData() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/page/InstanceListPage.java b/src/com/hypixel/hytale/builtin/instances/page/InstanceListPage.java new file mode 100644 index 0000000..8643148 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/page/InstanceListPage.java @@ -0,0 +1,169 @@ +package com.hypixel.hytale.builtin.instances.page; + +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.ui.Value; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InstanceListPage extends InteractiveCustomUIPage { + private static final String COMMON_TEXT_BUTTON_DOCUMENT = "Pages/BasicTextButton.ui"; + private static final Value BUTTON_LABEL_STYLE = Value.ref("Pages/BasicTextButton.ui", "LabelStyle"); + private static final Value BUTTON_LABEL_STYLE_SELECTED = Value.ref("Pages/BasicTextButton.ui", "SelectedLabelStyle"); + @Nullable + private String selectedInstance; + private List instances = new ObjectArrayList<>(); + + public InstanceListPage(@Nonnull PlayerRef playerRef) { + super(playerRef, CustomPageLifetime.CanDismiss, InstanceListPage.PageData.CODEC); + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/InstanceListPage.ui"); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#Spawn", EventData.of("Action", InstanceListPage.Action.Spawn.toString())); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#Load", EventData.of("Action", InstanceListPage.Action.Load.toString())); + commandBuilder.set("#Load.Visible", !AssetModule.get().getBaseAssetPack().isImmutable()); + int buttonIndex = 0; + + for (String instance : InstancesPlugin.get().getInstanceAssets()) { + commandBuilder.append("#List", "Pages/BasicTextButton.ui"); + commandBuilder.set("#List[" + buttonIndex + "].Text", instance); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#List[" + buttonIndex + "]", EventData.of("Instance", instance)); + this.instances.add(instance); + buttonIndex++; + } + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull InstanceListPage.PageData data) { + if (data.getInstance() != null) { + this.updateSelection(data.getInstance()); + } + + if (data.getAction() != null) { + switch (data.getAction()) { + case Load: + if (this.selectedInstance != null) { + this.load(ref, store); + this.close(); + } + break; + case Spawn: + if (this.selectedInstance != null) { + this.spawn(ref, store); + this.close(); + } + } + } + } + + private void load(@Nonnull Ref ref, @Nonnull Store store) { + Player player = store.getComponent(ref, Player.getComponentType()); + InstancesPlugin.get(); + InstancesPlugin.loadInstanceAssetForEdit(this.selectedInstance).thenAccept(world -> { + Store playerStore = ref.getStore(); + World playerWorld = playerStore.getExternalData().getWorld(); + playerWorld.execute(() -> { + Transform spawn = world.getWorldConfig().getSpawnProvider().getSpawnPoint(ref, playerStore); + playerStore.addComponent(ref, Teleport.getComponentType(), new Teleport(world, spawn)); + }); + }).exceptionally(ex -> { + ex.printStackTrace(); + return null; + }); + } + + private void spawn(@Nonnull Ref ref, @Nonnull Store store) { + World world = store.getExternalData().getWorld(); + world.execute(() -> { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + Transform returnLocation = new Transform(position.clone(), headRotationComponent.getRotation().clone()); + CompletableFuture instanceWorld = InstancesPlugin.get().spawnInstance(this.selectedInstance, world, returnLocation); + InstancesPlugin.teleportPlayerToLoadingInstance(ref, store, instanceWorld, null); + }); + } + + private void updateSelection(String instance) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + if (this.selectedInstance != null) { + commandBuilder.set("#List[" + this.instances.indexOf(this.selectedInstance) + "].Style", BUTTON_LABEL_STYLE); + } + + if (Objects.equals(instance, this.selectedInstance)) { + this.selectedInstance = null; + } else { + this.selectedInstance = instance; + } + + if (this.selectedInstance != null) { + commandBuilder.set("#List[" + this.instances.indexOf(this.selectedInstance) + "].Style", BUTTON_LABEL_STYLE_SELECTED); + } + + commandBuilder.set("#Name.Text", this.selectedInstance != null ? this.selectedInstance : ""); + commandBuilder.set("#Spawn.Disabled", this.selectedInstance == null); + commandBuilder.set("#Load.Disabled", this.selectedInstance == null); + this.sendUpdate(commandBuilder, false); + } + + public static enum Action { + Select, + Load, + Spawn; + + private Action() { + } + } + + public static class PageData { + public static final String KEY_INSTANCE = "Instance"; + public static final String KEY_ACTION = "Action"; + public static final BuilderCodec CODEC = BuilderCodec.builder(InstanceListPage.PageData.class, InstanceListPage.PageData::new) + .addField(new KeyedCodec<>("Instance", BuilderCodec.STRING), (o, i) -> o.instance = i, o -> o.instance) + .addField(new KeyedCodec<>("Action", new EnumCodec<>(InstanceListPage.Action.class)), (o, i) -> o.action = i, o -> o.action) + .build(); + private String instance; + private InstanceListPage.Action action; + + public PageData() { + } + + public String getInstance() { + return this.instance; + } + + public InstanceListPage.Action getAction() { + return this.action; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/removal/IdleTimeoutCondition.java b/src/com/hypixel/hytale/builtin/instances/removal/IdleTimeoutCondition.java new file mode 100644 index 0000000..030d1a8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/removal/IdleTimeoutCondition.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.builtin.instances.removal; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + +public class IdleTimeoutCondition implements RemovalCondition { + public static final BuilderCodec CODEC = BuilderCodec.builder(IdleTimeoutCondition.class, IdleTimeoutCondition::new) + .documentation("A condition that triggers after the world has be idle (without players) for a set time.") + .append(new KeyedCodec<>("TimeoutSeconds", Codec.DOUBLE), (o, i) -> o.timeoutSeconds = i, o -> o.timeoutSeconds) + .documentation("How long (in seconds) the world has to be idle (without players) for before triggering.") + .add() + .build(); + private double timeoutSeconds = TimeUnit.MINUTES.toSeconds(5L); + + public IdleTimeoutCondition() { + } + + @Override + public boolean shouldRemoveWorld(@Nonnull Store store) { + InstanceDataResource instanceDataResource = store.getResource(InstanceDataResource.getResourceType()); + World world = store.getExternalData().getWorld(); + Store entityStore = world.getEntityStore().getStore(); + TimeResource timeResource = entityStore.getResource(TimeResource.getResourceType()); + boolean hasPlayer = world.getPlayerCount() > 0; + if (!hasPlayer) { + if (instanceDataResource.getIdleTimeoutTimer() == null) { + instanceDataResource.setIdleTimeoutTimer(timeResource.getNow().plusNanos((long)(this.timeoutSeconds * 1.0E9))); + } + + return timeResource.getNow().isAfter(instanceDataResource.getIdleTimeoutTimer()); + } else { + instanceDataResource.setIdleTimeoutTimer(null); + return false; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/removal/InstanceDataResource.java b/src/com/hypixel/hytale/builtin/instances/removal/InstanceDataResource.java new file mode 100644 index 0000000..2f07494 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/removal/InstanceDataResource.java @@ -0,0 +1,82 @@ +package com.hypixel.hytale.builtin.instances.removal; + +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class InstanceDataResource implements Resource { + public static final BuilderCodec CODEC = BuilderCodec.builder(InstanceDataResource.class, InstanceDataResource::new) + .append(new KeyedCodec<>("TimeoutTimer", Codec.INSTANT), (o, i) -> o.timeoutTimer = i, o -> o.timeoutTimer) + .add() + .append(new KeyedCodec<>("IdleTimeoutTimer", Codec.INSTANT), (o, i) -> o.idleTimeoutTimer = i, o -> o.idleTimeoutTimer) + .add() + .append(new KeyedCodec<>("HadPlayer", Codec.BOOLEAN), (o, i) -> o.hadPlayer = i, o -> o.hadPlayer) + .add() + .append(new KeyedCodec<>("WorldTimeoutTimer", Codec.INSTANT), (o, i) -> o.worldTimeoutTimer = i, o -> o.worldTimeoutTimer) + .add() + .build(); + private boolean isRemoving; + private Instant timeoutTimer; + private Instant idleTimeoutTimer; + private boolean hadPlayer; + private Instant worldTimeoutTimer; + + public InstanceDataResource() { + } + + @Nonnull + public static ResourceType getResourceType() { + return InstancesPlugin.get().getInstanceDataResourceType(); + } + + public boolean isRemoving() { + return this.isRemoving; + } + + public void setRemoving(boolean removing) { + this.isRemoving = removing; + } + + public Instant getTimeoutTimer() { + return this.timeoutTimer; + } + + public void setTimeoutTimer(Instant timeoutTimer) { + this.timeoutTimer = timeoutTimer; + } + + public Instant getIdleTimeoutTimer() { + return this.idleTimeoutTimer; + } + + public void setIdleTimeoutTimer(Instant idleTimeoutTimer) { + this.idleTimeoutTimer = idleTimeoutTimer; + } + + public boolean hadPlayer() { + return this.hadPlayer; + } + + public void setHadPlayer(boolean hadPlayer) { + this.hadPlayer = hadPlayer; + } + + public Instant getWorldTimeoutTimer() { + return this.worldTimeoutTimer; + } + + public void setWorldTimeoutTimer(Instant worldTimeoutTimer) { + this.worldTimeoutTimer = worldTimeoutTimer; + } + + @Nonnull + public InstanceDataResource clone() { + return new InstanceDataResource(); + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/removal/RemovalCondition.java b/src/com/hypixel/hytale/builtin/instances/removal/RemovalCondition.java new file mode 100644 index 0000000..caffb3e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/removal/RemovalCondition.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.builtin.instances.removal; + +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; + +public interface RemovalCondition { + @Nonnull + CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + RemovalCondition[] EMPTY = new RemovalCondition[0]; + + boolean shouldRemoveWorld(@Nonnull Store var1); +} diff --git a/src/com/hypixel/hytale/builtin/instances/removal/RemovalSystem.java b/src/com/hypixel/hytale/builtin/instances/removal/RemovalSystem.java new file mode 100644 index 0000000..74dd77c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/removal/RemovalSystem.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.instances.removal; + +import com.hypixel.hytale.builtin.instances.config.InstanceWorldConfig; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.tick.RunWhenPausedSystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class RemovalSystem extends TickingSystem implements RunWhenPausedSystem { + public RemovalSystem() { + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + InstanceDataResource data = store.getResource(InstanceDataResource.getResourceType()); + if (!data.isRemoving() && shouldRemoveWorld(store)) { + data.setRemoving(true); + CompletableFuture.runAsync(() -> Universe.get().removeWorld(store.getExternalData().getWorld().getName())); + } + } + + public static boolean shouldRemoveWorld(@Nonnull Store store) { + World world = store.getExternalData().getWorld(); + InstanceWorldConfig config = InstanceWorldConfig.get(world.getWorldConfig()); + if (config == null) { + return false; + } else { + RemovalCondition[] removalConditions = config.getRemovalConditions(); + if (removalConditions.length == 0) { + return false; + } else { + boolean shouldRemove = true; + + for (RemovalCondition cond : removalConditions) { + shouldRemove &= cond.shouldRemoveWorld(store); + } + + return shouldRemove; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/removal/TimeoutCondition.java b/src/com/hypixel/hytale/builtin/instances/removal/TimeoutCondition.java new file mode 100644 index 0000000..01a96db --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/removal/TimeoutCondition.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.builtin.instances.removal; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + +public class TimeoutCondition implements RemovalCondition { + public static final BuilderCodec CODEC = BuilderCodec.builder(TimeoutCondition.class, TimeoutCondition::new) + .documentation("A condition that triggers after a set time limit.") + .append(new KeyedCodec<>("TimeoutSeconds", Codec.DOUBLE), (o, i) -> o.timeoutSeconds = i, o -> o.timeoutSeconds) + .documentation("How long to wait (in seconds) before closing the world.") + .add() + .build(); + private double timeoutSeconds = TimeUnit.MINUTES.toSeconds(5L); + + public TimeoutCondition() { + } + + public TimeoutCondition(double timeoutSeconds) { + this.timeoutSeconds = timeoutSeconds; + } + + public double getTimeoutSeconds() { + return this.timeoutSeconds; + } + + @Override + public boolean shouldRemoveWorld(@Nonnull Store store) { + InstanceDataResource data = store.getResource(InstanceDataResource.getResourceType()); + World world = store.getExternalData().getWorld(); + TimeResource timeResource = world.getEntityStore().getStore().getResource(TimeResource.getResourceType()); + if (data.getTimeoutTimer() == null) { + data.setTimeoutTimer(timeResource.getNow().plusNanos((long)(this.timeoutSeconds * 1.0E9))); + } + + return timeResource.getNow().isAfter(data.getTimeoutTimer()); + } +} diff --git a/src/com/hypixel/hytale/builtin/instances/removal/WorldEmptyCondition.java b/src/com/hypixel/hytale/builtin/instances/removal/WorldEmptyCondition.java new file mode 100644 index 0000000..7bf9bd7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/instances/removal/WorldEmptyCondition.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.instances.removal; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + +public class WorldEmptyCondition implements RemovalCondition { + public static final WorldEmptyCondition INSTANCE = new WorldEmptyCondition(); + public static final RemovalCondition[] REMOVE_WHEN_EMPTY = new RemovalCondition[]{INSTANCE}; + public static final BuilderCodec CODEC = BuilderCodec.builder(WorldEmptyCondition.class, WorldEmptyCondition::new) + .documentation( + "A condition that triggers when the world is empty.\n\nIt will only trigger after at least one player has joined. As a safety measure it provides a timeout for waiting for a player to join in case the player disconnected before entering the world." + ) + .append(new KeyedCodec<>("TimeoutSeconds", Codec.DOUBLE), (o, i) -> o.timeoutSeconds = i, o -> o.timeoutSeconds) + .documentation("How long to wait (in seconds) for a player to join before closing the world.") + .add() + .build(); + private double timeoutSeconds = TimeUnit.MINUTES.toSeconds(5L); + + public WorldEmptyCondition() { + } + + public WorldEmptyCondition(double timeoutSeconds) { + this.timeoutSeconds = timeoutSeconds; + } + + @Override + public boolean shouldRemoveWorld(@Nonnull Store store) { + InstanceDataResource data = store.getResource(InstanceDataResource.getResourceType()); + World world = store.getExternalData().getWorld(); + TimeResource timeResource = world.getEntityStore().getStore().getResource(TimeResource.getResourceType()); + boolean hasPlayer = world.getPlayerCount() > 0; + boolean hadPlayer = data.hadPlayer(); + if (!hasPlayer && hadPlayer) { + return true; + } else { + if (hasPlayer && !hadPlayer) { + data.setHadPlayer(true); + data.setWorldTimeoutTimer(null); + } + + if (!hadPlayer && !hasPlayer) { + if (data.getWorldTimeoutTimer() == null) { + data.setWorldTimeoutTimer(timeResource.getNow().plusNanos((long)(this.timeoutSeconds * 1.0E9))); + } + + return timeResource.getNow().isAfter(data.getWorldTimeoutTimer()); + } else { + return false; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/landiscovery/LANDiscoveryCommand.java b/src/com/hypixel/hytale/builtin/landiscovery/LANDiscoveryCommand.java new file mode 100644 index 0000000..fc89d3a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/landiscovery/LANDiscoveryCommand.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.builtin.landiscovery; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import javax.annotation.Nonnull; + +public class LANDiscoveryCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_IO_LAN_DISCOVERY_DISABLED = Message.translation("server.io.landiscovery.disabled"); + @Nonnull + private static final Message MESSAGE_IO_LAN_DISCOVERY_ENABLED = Message.translation("server.io.landiscovery.enabled"); + @Nonnull + private final OptionalArg enabledArg = this.withOptionalArg("enabled", "server.commands.landiscovery.enabled.desc", ArgTypes.BOOLEAN); + + public LANDiscoveryCommand() { + super("landiscovery", "server.commands.landiscovery.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + if (!this.enabledArg.provided(context)) { + LANDiscoveryPlugin plugin = LANDiscoveryPlugin.get(); + if (plugin.getLanDiscoveryThread() == null) { + plugin.setLANDiscoveryEnabled(true); + context.sendMessage(MESSAGE_IO_LAN_DISCOVERY_ENABLED); + } else { + plugin.setLANDiscoveryEnabled(false); + context.sendMessage(MESSAGE_IO_LAN_DISCOVERY_DISABLED); + } + } else { + boolean enabled = this.enabledArg.get(context); + LANDiscoveryPlugin plugin = LANDiscoveryPlugin.get(); + if (!enabled && plugin.getLanDiscoveryThread() != null) { + plugin.setLANDiscoveryEnabled(false); + context.sendMessage(MESSAGE_IO_LAN_DISCOVERY_DISABLED); + } else if (enabled && plugin.getLanDiscoveryThread() == null) { + plugin.setLANDiscoveryEnabled(true); + context.sendMessage(MESSAGE_IO_LAN_DISCOVERY_ENABLED); + } else { + context.sendMessage(Message.translation("server.io.landiscovery.alreadyToggled").param("status", MessageFormat.enabled(enabled))); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/landiscovery/LANDiscoveryPlugin.java b/src/com/hypixel/hytale/builtin/landiscovery/LANDiscoveryPlugin.java new file mode 100644 index 0000000..b408f2f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/landiscovery/LANDiscoveryPlugin.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.builtin.landiscovery; + +import com.hypixel.hytale.protocol.packets.serveraccess.Access; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerRequestAccessEvent; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class LANDiscoveryPlugin extends JavaPlugin { + @Nullable + private LANDiscoveryThread lanDiscoveryThread; + private static LANDiscoveryPlugin instance; + + public static LANDiscoveryPlugin get() { + return instance; + } + + public LANDiscoveryPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + instance = this; + this.getCommandRegistry().registerCommand(new LANDiscoveryCommand()); + this.getEventRegistry().registerGlobal(SingleplayerRequestAccessEvent.class, event -> this.setLANDiscoveryEnabled(event.getAccess() != Access.Private)); + } + + @Override + protected void start() { + if (this.lanDiscoveryThread != null) { + throw new IllegalArgumentException("Listener thread already exists!"); + } + } + + @Override + protected void shutdown() { + if (this.lanDiscoveryThread != null) { + this.setLANDiscoveryEnabled(false); + } + } + + public void setLANDiscoveryEnabled(boolean enabled) { + if (!enabled && this.lanDiscoveryThread != null) { + this.lanDiscoveryThread.interrupt(); + this.lanDiscoveryThread.getSocket().close(); + this.lanDiscoveryThread = null; + } else if (enabled && this.lanDiscoveryThread == null) { + this.lanDiscoveryThread = new LANDiscoveryThread(); + this.lanDiscoveryThread.start(); + } + } + + public boolean isLANDiscoveryEnabled() { + return this.lanDiscoveryThread != null; + } + + @Nullable + public LANDiscoveryThread getLanDiscoveryThread() { + return this.lanDiscoveryThread; + } +} diff --git a/src/com/hypixel/hytale/builtin/landiscovery/LANDiscoveryThread.java b/src/com/hypixel/hytale/builtin/landiscovery/LANDiscoveryThread.java new file mode 100644 index 0000000..c4dae14 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/landiscovery/LANDiscoveryThread.java @@ -0,0 +1,107 @@ +package com.hypixel.hytale.builtin.landiscovery; + +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.io.ServerManager; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.util.io.ByteBufUtil; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import java.net.DatagramPacket; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.MulticastSocket; +import java.net.SocketException; +import java.nio.charset.StandardCharsets; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +class LANDiscoveryThread extends Thread { + private static final byte[] REPLY_HEADER = "HYTALE_DISCOVER_REPLY".getBytes(StandardCharsets.US_ASCII); + private static final byte[] REQUEST_HEADER = "HYTALE_DISCOVER_REQUEST".getBytes(StandardCharsets.US_ASCII); + public static final int LAN_DISCOVERY_PORT = 5510; + @Nonnull + private final HytaleLogger LOGGER; + private MulticastSocket socket; + + public LANDiscoveryThread() { + super("LAN Discovery Listener"); + this.setDaemon(true); + this.LOGGER = LANDiscoveryPlugin.get().getLogger(); + } + + @Override + public void run() { + try { + this.socket = new MulticastSocket(5510); + this.socket.setBroadcast(true); + this.LOGGER.at(Level.INFO).log("Bound to UDP 0.0.0.0:5510 for LAN discovery"); + String name = HytaleServer.get().getServerName(); + if (name.length() > 16377) { + name = name.substring(0, 16377) + "..."; + } + + byte[] serverName = name.getBytes(StandardCharsets.UTF_8); + byte[] receiveBuf = new byte[15000]; + DatagramPacket packet = new DatagramPacket(receiveBuf, receiveBuf.length); + + while (!this.isInterrupted()) { + this.socket.receive(packet); + if (ArrayUtil.startsWith(packet.getData(), REQUEST_HEADER)) { + InetSocketAddress publicAddress = ServerManager.get().getNonLoopbackAddress(); + if (publicAddress != null) { + ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(); + buf.writeBytes(REPLY_HEADER); + buf.writeByte(0); + InetAddress address = publicAddress.getAddress(); + if (address != null && !address.isLoopbackAddress()) { + if (address instanceof Inet4Address) { + buf.writeByte(4); + } else { + if (!(address instanceof Inet6Address)) { + this.LOGGER.at(Level.WARNING).log("Unrecognized target address class %s: %s", address.getClass(), address); + continue; + } + + buf.writeByte(16); + } + + buf.writeBytes(address.getAddress()); + buf.writeShortLE(publicAddress.getPort()); + buf.writeShortLE(serverName.length); + buf.writeBytes(serverName); + buf.writeIntLE(Universe.get().getPlayerCount()); + int maxPlayers = HytaleServer.get().getConfig().getMaxPlayers(); + buf.writeIntLE(Math.max(maxPlayers, 0)); + byte[] sendData = ByteBufUtil.getBytesRelease(buf); + DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, packet.getAddress(), packet.getPort()); + this.socket.send(sendPacket); + this.LOGGER.at(Level.FINE).log("Was discovered by %s:%d", packet.getAddress(), packet.getPort()); + } else { + this.LOGGER.at(Level.WARNING).log("No public address to send as response!"); + } + } + } + } + } catch (SocketException var14) { + if (!"Socket closed".equalsIgnoreCase(var14.getMessage()) && !"Socket is closed".equalsIgnoreCase(var14.getMessage())) { + this.LOGGER.at(Level.SEVERE).withCause(var14).log("Exception in lan discovery listener:"); + } + } catch (Throwable var15) { + this.LOGGER.at(Level.SEVERE).withCause(var15).log("Exception in lan discovery listener:"); + } finally { + if (this.socket != null) { + this.socket.close(); + } + } + + this.LOGGER.at(Level.INFO).log("Stopped listing on UDP 0.0.0.0:5510 for LAN discovery"); + } + + public MulticastSocket getSocket() { + return this.socket; + } +} diff --git a/src/com/hypixel/hytale/builtin/mantling/MantlingPlugin.java b/src/com/hypixel/hytale/builtin/mantling/MantlingPlugin.java new file mode 100644 index 0000000..afa2800 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mantling/MantlingPlugin.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.builtin.mantling; + +import com.hypixel.hytale.protocol.packets.setup.ClientFeature; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import javax.annotation.Nonnull; + +public class MantlingPlugin extends JavaPlugin { + public MantlingPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + this.getClientFeatureRegistry().registerClientTag("Allows=Movement"); + this.getClientFeatureRegistry().register(ClientFeature.Mantling); + } +} diff --git a/src/com/hypixel/hytale/builtin/model/ModelPlugin.java b/src/com/hypixel/hytale/builtin/model/ModelPlugin.java new file mode 100644 index 0000000..e1b4630 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/model/ModelPlugin.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.model; + +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.builtin.model.commands.ModelCommand; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import javax.annotation.Nonnull; + +public class ModelPlugin extends JavaPlugin { + public ModelPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + this.getCommandRegistry().registerCommand(new ModelCommand()); + this.getEventRegistry().register(LoadedAssetsEvent.class, ModelAsset.class, this::updateModelAssets); + } + + private void checkForModelUpdate( + @Nonnull Map reloadedModelAssets, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull CommandBuffer commandBuffer + ) { + ModelComponent modelComponent = archetypeChunk.getComponent(index, ModelComponent.getComponentType()); + Model oldModel = modelComponent.getModel(); + ModelAsset newModel = reloadedModelAssets.get(oldModel.getModelAssetId()); + if (newModel != null) { + Model model = Model.createScaledModel(newModel, oldModel.getScale(), oldModel.getRandomAttachmentIds()); + commandBuffer.putComponent(archetypeChunk.getReferenceTo(index), ModelComponent.getComponentType(), new ModelComponent(model)); + } + } + + private void updateModelAssets(@Nonnull LoadedAssetsEvent> event) { + Map map = event.getLoadedAssets(); + Universe.get() + .getWorlds() + .forEach( + (name, world) -> world.execute( + () -> world.getEntityStore() + .getStore() + .forEachEntityParallel( + ModelComponent.getComponentType(), + (index, archetypeChunk, commandBuffer) -> this.checkForModelUpdate(map, index, archetypeChunk, commandBuffer) + ) + ) + ); + } +} diff --git a/src/com/hypixel/hytale/builtin/model/commands/ModelCommand.java b/src/com/hypixel/hytale/builtin/model/commands/ModelCommand.java new file mode 100644 index 0000000..b6c7e14 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/model/commands/ModelCommand.java @@ -0,0 +1,244 @@ +package com.hypixel.hytale.builtin.model.commands; + +import com.hypixel.hytale.builtin.model.pages.ChangeModelPage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSkinComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ModelCommand extends AbstractPlayerCommand { + public ModelCommand() { + super("model", "server.commands.model.desc"); + this.addUsageVariant(new ModelCommand.ModelOtherCommand()); + this.addSubCommand(new ModelCommand.ModelSetCommand()); + this.addSubCommand(new ModelCommand.ModelResetCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getPageManager().openCustomPage(ref, store, new ChangeModelPage(playerRef)); + } + + private static class ModelOtherCommand extends CommandBase { + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + + ModelOtherCommand() { + super("server.commands.model.other.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef targetPlayerRef = this.playerArg.get(context); + Ref ref = targetPlayerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + } else { + playerComponent.getPageManager().openCustomPage(ref, store, new ChangeModelPage(targetPlayerRef)); + } + }); + } else { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + } + } + } + + static class ModelResetCommand extends AbstractPlayerCommand { + @Nonnull + private final OptionalArg scaleArg = this.withOptionalArg("scale", "server.commands.model.reset.scale.desc", ArgTypes.FLOAT); + + ModelResetCommand() { + super("reset", "server.commands.model.reset.desc"); + this.addAliases("clear"); + this.addUsageVariant(new ModelCommand.ModelResetCommand.ModelResetOtherCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + PlayerSkinComponent skinComponent = store.getComponent(ref, PlayerSkinComponent.getComponentType()); + if (skinComponent == null) { + context.sendMessage(Message.translation("server.commands.model.noAuthSkinForPlayer").param("model", "Player")); + } else { + PlayerSkinComponent playerSkinComponent = store.getComponent(ref, PlayerSkinComponent.getComponentType()); + + assert playerSkinComponent != null; + + CosmeticsModule cosmeticsModule = CosmeticsModule.get(); + if (this.scaleArg.provided(context)) { + Float scale = this.scaleArg.get(context); + Model newModel = cosmeticsModule.createModel(playerSkinComponent.getPlayerSkin(), scale); + store.putComponent(ref, ModelComponent.getComponentType(), new ModelComponent(newModel)); + } else { + Model newModel = cosmeticsModule.createModel(playerSkinComponent.getPlayerSkin()); + store.putComponent(ref, ModelComponent.getComponentType(), new ModelComponent(newModel)); + } + + playerSkinComponent.setNetworkOutdated(); + context.sendMessage(Message.translation("server.commands.model.modelResetForPlayer")); + } + } + + private static class ModelResetOtherCommand extends CommandBase { + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + @Nonnull + private final OptionalArg scaleArg = this.withOptionalArg("scale", "server.commands.model.reset.scale.desc", ArgTypes.FLOAT); + + ModelResetOtherCommand() { + super("server.commands.model.reset.other.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef targetPlayerRef = this.playerArg.get(context); + Ref ref = targetPlayerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + } else { + PlayerSkinComponent skinComponent = store.getComponent(ref, PlayerSkinComponent.getComponentType()); + if (skinComponent == null) { + context.sendMessage( + Message.translation("server.commands.model.noAuthSkin").param("name", targetPlayerRef.getUsername()).param("model", "Player") + ); + } else { + PlayerSkinComponent playerSkinComponent = store.getComponent(ref, PlayerSkinComponent.getComponentType()); + + assert playerSkinComponent != null; + + CosmeticsModule cosmeticsModule = CosmeticsModule.get(); + if (this.scaleArg.provided(context)) { + Float scale = this.scaleArg.get(context); + Model newModel = cosmeticsModule.createModel(playerSkinComponent.getPlayerSkin(), scale); + store.putComponent(ref, ModelComponent.getComponentType(), new ModelComponent(newModel)); + } else { + Model newModel = cosmeticsModule.createModel(playerSkinComponent.getPlayerSkin()); + store.putComponent(ref, ModelComponent.getComponentType(), new ModelComponent(newModel)); + } + + playerSkinComponent.setNetworkOutdated(); + context.sendMessage(Message.translation("server.commands.model.modelReset").param("name", targetPlayerRef.getUsername())); + } + } + } + ); + } else { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + } + } + } + } + + static class ModelSetCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg modelAssetArg = this.withRequiredArg("model", "server.commands.model.set.model.desc", ArgTypes.MODEL_ASSET); + @Nonnull + private final OptionalArg scaleArg = this.withOptionalArg("scale", "server.commands.model.set.scale.desc", ArgTypes.FLOAT); + @Nonnull + private final FlagArg bypassScaleLimitsFlag = this.withFlagArg("bypassScaleLimits", "server.commands.model.set.bypassScaleLimits.desc"); + + ModelSetCommand() { + super("set", "server.commands.model.set.desc"); + this.addUsageVariant(new ModelCommand.ModelSetCommand.ModelSetOtherCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + ModelAsset modelAsset = this.modelAssetArg.get(context); + float scale = this.scaleArg.provided(context) ? this.scaleArg.get(context) : modelAsset.generateRandomScale(); + if (!this.bypassScaleLimitsFlag.provided(context)) { + scale = MathUtil.clamp(scale, modelAsset.getMinScale(), modelAsset.getMaxScale()); + } + + Model model = Model.createScaledModel(modelAsset, scale); + store.putComponent(ref, ModelComponent.getComponentType(), new ModelComponent(model)); + context.sendMessage(Message.translation("server.commands.model.modelSetForPlayer").param("modelName", modelAsset.getId())); + } + + private static class ModelSetOtherCommand extends CommandBase { + @Nonnull + private final RequiredArg modelAssetArg = this.withRequiredArg("model", "server.commands.model.set.model.desc", ArgTypes.MODEL_ASSET); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + @Nonnull + private final OptionalArg scaleArg = this.withOptionalArg("scale", "server.commands.model.set.scale.desc", ArgTypes.FLOAT); + @Nonnull + private final FlagArg bypassScaleLimitsFlag = this.withFlagArg("bypassScaleLimits", "server.commands.model.set.bypassScaleLimits.desc"); + + ModelSetOtherCommand() { + super("server.commands.model.set.other.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef targetPlayerRef = this.playerArg.get(context); + Ref ref = targetPlayerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + } else { + ModelAsset modelAsset = this.modelAssetArg.get(context); + float scale = this.scaleArg.provided(context) ? this.scaleArg.get(context) : modelAsset.generateRandomScale(); + if (!this.bypassScaleLimitsFlag.provided(context)) { + scale = MathUtil.clamp(scale, modelAsset.getMinScale(), modelAsset.getMaxScale()); + } + + Model model = Model.createScaledModel(modelAsset, scale); + store.putComponent(ref, ModelComponent.getComponentType(), new ModelComponent(model)); + context.sendMessage( + Message.translation("server.commands.model.modelSet") + .param("playerName", targetPlayerRef.getUsername()) + .param("modelName", modelAsset.getId()) + ); + } + } + ); + } else { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/model/pages/ChangeModelPage.java b/src/com/hypixel/hytale/builtin/model/pages/ChangeModelPage.java new file mode 100644 index 0000000..829e649 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/model/pages/ChangeModelPage.java @@ -0,0 +1,254 @@ +package com.hypixel.hytale.builtin.model.pages; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.StringCompareUtil; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.NonSerialized; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSkinComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.ui.Value; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChangeModelPage extends InteractiveCustomUIPage { + private static final String COMMON_TEXT_BUTTON_DOCUMENT = "Common/TextButton.ui"; + private static final Value BUTTON_LABEL_STYLE = Value.ref("Common/TextButton.ui", "LabelStyle"); + private static final Value BUTTON_LABEL_STYLE_SELECTED = Value.ref("Common/TextButton.ui", "SelectedLabelStyle"); + @Nonnull + private String searchQuery = ""; + private List models; + @Nullable + private String selectedModel; + @Nullable + private Ref modelPreview; + private Vector3d position; + private Vector3f rotation; + private float scale = 1.0F; + + public ChangeModelPage(@Nonnull PlayerRef playerRef) { + super(playerRef, CustomPageLifetime.CanDismiss, ChangeModelPage.PageEventData.CODEC); + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/ChangeModelPage.ui"); + eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#SearchInput", EventData.of("@SearchQuery", "#SearchInput.Value"), false); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, "#Scale", new EventData().append("Type", "UpdateScale").append("@Scale", "#Scale.Value"), false + ); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ChangeModel", new EventData().append("Type", "ChangeModel"), false); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ResetModel", new EventData().append("Type", "ResetModel"), false); + this.buildModelList(ref, store, commandBuilder, eventBuilder); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull ChangeModelPage.PageEventData data) { + if (data.searchQuery != null) { + this.searchQuery = data.searchQuery.trim().toLowerCase(); + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.buildModelList(ref, store, commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + } else { + String var9 = data.type; + switch (var9) { + case "Select": + if (data.model != null) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + this.selectModel(ref, store, data.model, commandBuilder); + this.sendUpdate(commandBuilder, null, false); + } + break; + case "UpdateScale": + this.scale = data.scale; + if (this.modelPreview.isValid()) { + store.putComponent(this.modelPreview, ModelComponent.getComponentType(), new ModelComponent(this.getModel(this.scale))); + } + break; + case "ChangeModel": + if (this.selectedModel != null) { + if (this.modelPreview.isValid()) { + store.removeEntity(this.modelPreview, RemoveReason.REMOVE); + } + + Model model = this.getModel(this.scale); + store.putComponent(ref, ModelComponent.getComponentType(), new ModelComponent(model)); + store.getComponent(ref, Player.getComponentType()).getPageManager().setPage(ref, store, Page.None); + } + break; + case "ResetModel": + PlayerSkinComponent skinComponent = store.getComponent(ref, PlayerSkinComponent.getComponentType()); + if (skinComponent == null) { + return; + } + + PlayerSkinComponent playerSkinComponent = store.getComponent(ref, PlayerSkinComponent.getComponentType()); + Model newModel = CosmeticsModule.get().createModel(playerSkinComponent.getPlayerSkin()); + store.putComponent(ref, ModelComponent.getComponentType(), new ModelComponent(newModel)); + playerSkinComponent.setNetworkOutdated(); + } + } + } + + @Override + public void onDismiss(@Nonnull Ref ref, @Nonnull Store store) { + if (this.modelPreview != null && this.modelPreview.isValid()) { + store.removeEntity(this.modelPreview, RemoveReason.REMOVE); + } + } + + private void buildModelList( + @Nonnull Ref ref, @Nonnull Store store, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder + ) { + commandBuilder.clear("#ModelList"); + Set roleTemplateNames = ModelAsset.getAssetMap().getAssetMap().keySet(); + if (!this.searchQuery.isEmpty()) { + Object2IntMap map = new Object2IntOpenHashMap<>(roleTemplateNames.size()); + + for (String value : roleTemplateNames) { + int fuzzyDistance = StringCompareUtil.getFuzzyDistance(value, this.searchQuery, Locale.ENGLISH); + if (fuzzyDistance > 0) { + map.put(value, fuzzyDistance); + } + } + + this.models = map.keySet().stream().sorted().sorted(Comparator.comparingInt(map::getInt).reversed()).limit(20L).collect(Collectors.toList()); + } else { + this.models = roleTemplateNames.stream().sorted().sorted(String::compareTo).collect(Collectors.toList()); + } + + int i = 0; + + for (int bound = this.models.size(); i < bound; i++) { + String id = this.models.get(i); + String selector = "#ModelList[" + i + "]"; + commandBuilder.append("#ModelList", "Common/TextButton.ui"); + commandBuilder.set(selector + " #Button.Text", id); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, selector + " #Button", new EventData().append("Type", "Select").append("Model", id), false + ); + } + + if (!this.models.isEmpty()) { + if (!this.models.contains(this.selectedModel)) { + this.selectModel(ref, store, this.models.getFirst(), commandBuilder); + } else if (this.selectedModel != null) { + this.selectModel(ref, store, this.selectedModel, commandBuilder); + } + } + } + + private void selectModel(@Nonnull Ref ref, @Nonnull Store store, @Nonnull String modelId, @Nonnull UICommandBuilder commandBuilder) { + if (this.selectedModel != null && this.models.contains(this.selectedModel)) { + commandBuilder.set("#ModelList[" + this.models.indexOf(this.selectedModel) + "] #Button.Style", BUTTON_LABEL_STYLE); + } + + commandBuilder.set("#ModelList[" + this.models.indexOf(modelId) + "] #Button.Style", BUTTON_LABEL_STYLE_SELECTED); + commandBuilder.set("#ModelName.Text", modelId); + this.selectedModel = modelId; + if (this.modelPreview == null || !this.modelPreview.isValid()) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3d playerPosition = transformComponent.getPosition(); + Vector3f headRotation = headRotationComponent.getRotation(); + Vector3d previewPosition = TargetUtil.getTargetLocation(ref, 8.0, store); + if (previewPosition == null) { + previewPosition = playerPosition.clone().add(Transform.getDirection(headRotation.getPitch(), headRotation.getYaw()).scale(4.0)); + } + + Vector3d targetGround = TargetUtil.getTargetLocation( + store.getExternalData().getWorld(), blockId -> blockId != 0, previewPosition.x, previewPosition.y, previewPosition.z, 0.0, -1.0, 0.0, 8.0 + ); + if (targetGround != null) { + previewPosition = targetGround; + } + + Vector3d relativePos = playerPosition.clone().subtract(previewPosition); + relativePos.setY(0.0); + Vector3f previewRotation = Vector3f.lookAt(relativePos); + this.position = previewPosition; + this.rotation = previewRotation; + Holder holder = store.getRegistry().newHolder(); + holder.addComponent(NetworkId.getComponentType(), new NetworkId(store.getExternalData().takeNextNetworkId())); + holder.addComponent(EntityStore.REGISTRY.getNonSerializedComponentType(), NonSerialized.get()); + holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(previewPosition, previewRotation)); + holder.addComponent(ModelComponent.getComponentType(), new ModelComponent(this.getModel(this.scale))); + holder.addComponent(HeadRotation.getComponentType(), new HeadRotation(previewRotation)); + this.modelPreview = store.addEntity(holder, AddReason.SPAWN); + } else if (this.modelPreview != null && this.modelPreview.isValid()) { + store.putComponent(this.modelPreview, ModelComponent.getComponentType(), new ModelComponent(this.getModel(1.0F))); + } + } + + @Nullable + private Model getModel(float scale) { + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(this.selectedModel); + return Model.createScaledModel(modelAsset, scale); + } + + public static class PageEventData { + static final String KEY_MODEL = "Model"; + static final String KEY_TYPE = "Type"; + static final String KEY_SEARCH_QUERY = "@SearchQuery"; + static final String KEY_SCALE = "@Scale"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + ChangeModelPage.PageEventData.class, ChangeModelPage.PageEventData::new + ) + .append(new KeyedCodec<>("Model", Codec.STRING), (entry, s) -> entry.model = s, entry -> entry.model) + .add() + .append(new KeyedCodec<>("Type", Codec.STRING), (entry, s) -> entry.type = s, entry -> entry.type) + .add() + .append(new KeyedCodec<>("@SearchQuery", Codec.STRING), (entry, s) -> entry.searchQuery = s, entry -> entry.searchQuery) + .add() + .append(new KeyedCodec<>("@Scale", Codec.FLOAT), (entry, s) -> entry.scale = s, entry -> entry.scale) + .add() + .build(); + private String model; + private String type; + private String searchQuery; + private float scale; + + public PageEventData() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/BlockMountAPI.java b/src/com/hypixel/hytale/builtin/mounts/BlockMountAPI.java new file mode 100644 index 0000000..5eaa314 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/BlockMountAPI.java @@ -0,0 +1,128 @@ +package com.hypixel.hytale.builtin.mounts; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockMountType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.mountpoints.BlockMountPoint; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public final class BlockMountAPI { + private BlockMountAPI() { + } + + public static BlockMountAPI.BlockMountResult mountOnBlock( + Ref entity, CommandBuffer commandBuffer, Vector3i targetBlock, Vector3f interactPos + ) { + MountedComponent existingMounted = commandBuffer.getComponent(entity, MountedComponent.getComponentType()); + if (existingMounted != null) { + return BlockMountAPI.DidNotMount.ALREADY_MOUNTED; + } else { + World world = entity.getStore().getExternalData().getWorld(); + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)); + if (chunk == null) { + return BlockMountAPI.DidNotMount.CHUNK_NOT_FOUND; + } else { + Ref chunkRef = chunk.getReference(); + if (chunkRef == null) { + return BlockMountAPI.DidNotMount.CHUNK_REF_NOT_FOUND; + } else { + ChunkStore chunkStore = world.getChunkStore(); + BlockComponentChunk blockComponentChunk = chunkStore.getStore().getComponent(chunkRef, BlockComponentChunk.getComponentType()); + if (blockComponentChunk == null) { + return BlockMountAPI.DidNotMount.CHUNK_REF_NOT_FOUND; + } else { + BlockType blockType = world.getBlockType(targetBlock); + if (blockType == null) { + return BlockMountAPI.DidNotMount.INVALID_BLOCK; + } else { + int rotationIndex = chunk.getRotationIndex(targetBlock.x, targetBlock.y, targetBlock.z); + int blockIndex = ChunkUtil.indexBlockInColumn(targetBlock.x, targetBlock.y, targetBlock.z); + Ref blockRef = blockComponentChunk.getEntityReference(blockIndex); + if (blockRef == null || !blockRef.isValid()) { + Holder blockHolder = ChunkStore.REGISTRY.newHolder(); + blockHolder.putComponent(BlockModule.BlockStateInfo.getComponentType(), new BlockModule.BlockStateInfo(blockIndex, chunkRef)); + blockRef = world.getChunkStore().getStore().addEntity(blockHolder, AddReason.SPAWN); + if (blockRef == null || !blockRef.isValid()) { + return BlockMountAPI.DidNotMount.BLOCK_REF_NOT_FOUND; + } + } + + BlockMountType blockMountType = null; + BlockMountPoint[] mountPointsForBlock = null; + if (blockType.getSeats() != null) { + blockMountType = BlockMountType.Seat; + mountPointsForBlock = blockType.getSeats().getRotated(rotationIndex); + } else { + if (blockType.getBeds() == null) { + return BlockMountAPI.DidNotMount.UNKNOWN_BLOCKMOUNT_TYPE; + } + + blockMountType = BlockMountType.Bed; + mountPointsForBlock = blockType.getBeds().getRotated(rotationIndex); + } + + BlockMountComponent blockMountComponent = world.getChunkStore().getStore().getComponent(blockRef, BlockMountComponent.getComponentType()); + if (blockMountComponent == null) { + blockMountComponent = new BlockMountComponent(blockMountType, targetBlock, blockType, rotationIndex); + world.getChunkStore().getStore().addComponent(blockRef, BlockMountComponent.getComponentType(), blockMountComponent); + } + + if (mountPointsForBlock != null && mountPointsForBlock.length != 0) { + BlockMountPoint pickedMountPoint = blockMountComponent.findAvailableSeat(targetBlock, mountPointsForBlock, interactPos); + if (pickedMountPoint == null) { + return BlockMountAPI.DidNotMount.NO_MOUNT_POINT_FOUND; + } else { + TransformComponent transformComponent = commandBuffer.getComponent(entity, TransformComponent.getComponentType()); + if (transformComponent != null) { + Vector3f position = pickedMountPoint.computeWorldSpacePosition(blockMountComponent.getBlockPos()); + Vector3f rotationEuler = pickedMountPoint.computeRotationEuler(blockMountComponent.getExpectedRotation()); + transformComponent.setPosition(position.toVector3d()); + transformComponent.setRotation(rotationEuler); + } + + MountedComponent mountedComponent = new MountedComponent(blockRef, new Vector3f(0.0F, 0.0F, 0.0F), blockMountType); + commandBuffer.addComponent(entity, MountedComponent.getComponentType(), mountedComponent); + blockMountComponent.putSeatedEntity(pickedMountPoint, entity); + return new BlockMountAPI.Mounted(blockType, mountedComponent); + } + } else { + return BlockMountAPI.DidNotMount.NO_MOUNT_POINT_FOUND; + } + } + } + } + } + } + } + + public sealed interface BlockMountResult permits BlockMountAPI.Mounted, BlockMountAPI.DidNotMount { + } + + public static enum DidNotMount implements BlockMountAPI.BlockMountResult { + CHUNK_NOT_FOUND, + CHUNK_REF_NOT_FOUND, + BLOCK_REF_NOT_FOUND, + INVALID_BLOCK, + ALREADY_MOUNTED, + UNKNOWN_BLOCKMOUNT_TYPE, + NO_MOUNT_POINT_FOUND; + + private DidNotMount() { + } + } + + public record Mounted(BlockType blockType, MountedComponent component) implements BlockMountAPI.BlockMountResult { + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/BlockMountComponent.java b/src/com/hypixel/hytale/builtin/mounts/BlockMountComponent.java new file mode 100644 index 0000000..a8175b2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/BlockMountComponent.java @@ -0,0 +1,122 @@ +package com.hypixel.hytale.builtin.mounts; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockMountType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.mountpoints.BlockMountPoint; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Collection; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockMountComponent implements Component { + private BlockMountType type; + private Vector3i blockPos; + private BlockType expectedBlockType; + private int expectedRotation; + @Nonnull + private Map> entitiesByMountPoint = new Object2ObjectOpenHashMap<>(); + @Nonnull + private Map, BlockMountPoint> mountPointByEntity = new Object2ObjectOpenHashMap<>(); + + public static ComponentType getComponentType() { + return MountPlugin.getInstance().getBlockMountComponentType(); + } + + public BlockMountComponent() { + } + + public BlockMountComponent(BlockMountType type, Vector3i blockPos, BlockType expectedBlockType, int expectedRotation) { + this.type = type; + this.blockPos = blockPos; + this.expectedBlockType = expectedBlockType; + this.expectedRotation = expectedRotation; + } + + public BlockMountType getType() { + return this.type; + } + + public Vector3i getBlockPos() { + return this.blockPos; + } + + public BlockType getExpectedBlockType() { + return this.expectedBlockType; + } + + public int getExpectedRotation() { + return this.expectedRotation; + } + + public boolean isDead() { + this.clean(); + return this.entitiesByMountPoint.isEmpty(); + } + + private void clean() { + this.entitiesByMountPoint.values().removeIf(ref -> !ref.isValid()); + this.mountPointByEntity.keySet().removeIf(ref -> !ref.isValid()); + } + + public void putSeatedEntity(@Nonnull BlockMountPoint mountPoint, @Nonnull Ref seatedEntity) { + this.entitiesByMountPoint.put(mountPoint, seatedEntity); + this.mountPointByEntity.put(seatedEntity, mountPoint); + } + + public void removeSeatedEntity(@Nonnull Ref seatedEntity) { + BlockMountPoint seat = this.mountPointByEntity.remove(seatedEntity); + if (seat != null) { + this.entitiesByMountPoint.remove(seat); + } + } + + @Nullable + public BlockMountPoint getSeatBlockBySeatedEntity(Ref seatedEntity) { + return this.mountPointByEntity.get(seatedEntity); + } + + @Nonnull + public Collection> getSeatedEntities() { + return this.entitiesByMountPoint.values(); + } + + @Nullable + public BlockMountPoint findAvailableSeat(@Nonnull Vector3i targetBlock, @Nonnull BlockMountPoint[] choices, @Nonnull Vector3f whereWasClicked) { + this.clean(); + double minDistSq = Double.MAX_VALUE; + BlockMountPoint closestSeat = null; + + for (BlockMountPoint choice : choices) { + if (!this.entitiesByMountPoint.containsKey(choice)) { + Vector3f seatInWorldSpace = choice.computeWorldSpacePosition(targetBlock); + double distSq = whereWasClicked.distanceSquaredTo(seatInWorldSpace); + if (distSq < minDistSq) { + minDistSq = distSq; + closestSeat = choice; + } + } + } + + return closestSeat; + } + + @Nonnull + @Override + public Component clone() { + BlockMountComponent seat = new BlockMountComponent(); + seat.type = this.type; + seat.blockPos = this.blockPos; + seat.expectedBlockType = this.expectedBlockType; + seat.entitiesByMountPoint = new Object2ObjectOpenHashMap<>(this.entitiesByMountPoint); + seat.mountPointByEntity = new Object2ObjectOpenHashMap<>(this.mountPointByEntity); + return seat; + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/MountGamePacketHandler.java b/src/com/hypixel/hytale/builtin/mounts/MountGamePacketHandler.java new file mode 100644 index 0000000..7c6ddb6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/MountGamePacketHandler.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.builtin.mounts; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.MountController; +import com.hypixel.hytale.protocol.packets.interaction.DismountNPC; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.io.handlers.IPacketHandler; +import com.hypixel.hytale.server.core.io.handlers.SubPacketHandler; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public class MountGamePacketHandler implements SubPacketHandler { + private final IPacketHandler packetHandler; + + public MountGamePacketHandler(IPacketHandler packetHandler) { + this.packetHandler = packetHandler; + } + + @Override + public void registerHandlers() { + this.packetHandler.registerHandler(294, protoPacket -> this.handle((DismountNPC)protoPacket)); + } + + public void handle(DismountNPC packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + EntityStore entityStore = store.getExternalData(); + World world = entityStore.getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + MountedComponent mounted = store.getComponent(ref, MountedComponent.getComponentType()); + if (mounted == null) { + int mountEntityId = playerComponent.getMountEntityId(); + playerComponent.setMountEntityId(0); + MountPlugin.dismountNpc(store, mountEntityId); + } else { + if (mounted.getControllerType() == MountController.BlockMount) { + store.tryRemoveComponent(ref, MountedComponent.getComponentType()); + } + } + }); + } else { + throw new RuntimeException("Unable to process DismountNPC packet. Player ref is invalid!"); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/MountPlugin.java b/src/com/hypixel/hytale/builtin/mounts/MountPlugin.java new file mode 100644 index 0000000..7131700 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/MountPlugin.java @@ -0,0 +1,161 @@ +package com.hypixel.hytale.builtin.mounts; + +import com.hypixel.hytale.builtin.mounts.commands.MountCommand; +import com.hypixel.hytale.builtin.mounts.interactions.MountInteraction; +import com.hypixel.hytale.builtin.mounts.interactions.SeatingInteraction; +import com.hypixel.hytale.builtin.mounts.interactions.SpawnMinecartInteraction; +import com.hypixel.hytale.builtin.mounts.minecart.MinecartComponent; +import com.hypixel.hytale.builtin.mounts.npc.builders.BuilderActionMount; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interaction.DismountNPC; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementManager; +import com.hypixel.hytale.server.core.event.events.player.PlayerDisconnectEvent; +import com.hypixel.hytale.server.core.io.ServerManager; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.systems.RoleChangeSystem; +import javax.annotation.Nonnull; + +public class MountPlugin extends JavaPlugin { + private static MountPlugin instance; + private ComponentType blockMountComponentType; + private ComponentType mountComponentType; + private ComponentType mountedComponentType; + private ComponentType mountedByComponentType; + private ComponentType minecartComponentType; + + public static MountPlugin getInstance() { + return instance; + } + + public MountPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + public ComponentType getMountComponentType() { + return this.mountComponentType; + } + + public ComponentType getMountedComponentType() { + return this.mountedComponentType; + } + + public ComponentType getMountedByComponentType() { + return this.mountedByComponentType; + } + + public ComponentType getMinecartComponentType() { + return this.minecartComponentType; + } + + @Override + protected void setup() { + instance = this; + this.blockMountComponentType = this.getChunkStoreRegistry().registerComponent(BlockMountComponent.class, BlockMountComponent::new); + NPCPlugin.get().registerCoreComponentType("Mount", BuilderActionMount::new); + this.mountComponentType = this.getEntityStoreRegistry().registerComponent(NPCMountComponent.class, "Mount", NPCMountComponent.CODEC); + this.mountedComponentType = this.getEntityStoreRegistry().registerComponent(MountedComponent.class, () -> { + throw new UnsupportedOperationException("Mounted component cannot be default constructed"); + }); + this.mountedByComponentType = this.getEntityStoreRegistry().registerComponent(MountedByComponent.class, MountedByComponent::new); + this.minecartComponentType = this.getEntityStoreRegistry().registerComponent(MinecartComponent.class, "Minecart", MinecartComponent.CODEC); + this.getEntityStoreRegistry().registerSystem(new NPCMountSystems.OnAdd(this.mountComponentType)); + this.getEntityStoreRegistry().registerSystem(new NPCMountSystems.DismountOnPlayerDeath()); + this.getEntityStoreRegistry().registerSystem(new NPCMountSystems.DismountOnMountDeath()); + this.getEntityStoreRegistry().registerSystem(new MountSystems.TrackerUpdate()); + this.getEntityStoreRegistry().registerSystem(new MountSystems.TrackerRemove()); + this.getEntityStoreRegistry().registerSystem(new MountSystems.RemoveMountedBy()); + this.getEntityStoreRegistry().registerSystem(new MountSystems.RemoveMounted()); + this.getEntityStoreRegistry().registerSystem(new MountSystems.TeleportMountedEntity()); + this.getEntityStoreRegistry().registerSystem(new MountSystems.MountedEntityDeath()); + this.getEntityStoreRegistry().registerSystem(new MountSystems.PlayerMount()); + this.getEntityStoreRegistry().registerSystem(new MountSystems.HandleMountInput()); + this.getEntityStoreRegistry().registerSystem(new MountSystems.TrackedMounted()); + this.getEntityStoreRegistry().registerSystem(new MountSystems.EnsureMinecartComponents()); + this.getEntityStoreRegistry().registerSystem(new MountSystems.OnMinecartHit()); + this.getChunkStoreRegistry().registerSystem(new MountSystems.RemoveBlockSeat()); + ServerManager.get().registerSubPacketHandlers(MountGamePacketHandler::new); + this.getEventRegistry().register(PlayerDisconnectEvent.class, MountPlugin::onPlayerDisconnect); + this.getCommandRegistry().registerCommand(new MountCommand()); + Interaction.CODEC.register("SpawnMinecart", SpawnMinecartInteraction.class, SpawnMinecartInteraction.CODEC); + Interaction.CODEC.register("Mount", MountInteraction.class, MountInteraction.CODEC); + Interaction.CODEC.register("Seating", SeatingInteraction.class, SeatingInteraction.CODEC); + } + + public ComponentType getBlockMountComponentType() { + return this.blockMountComponentType; + } + + private static void onPlayerDisconnect(@Nonnull PlayerDisconnectEvent event) { + PlayerRef playerRef = event.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + if (ref.isValid()) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + checkDismountNpc(store, playerComponent); + } + } + }); + } + } + + public static void checkDismountNpc(@Nonnull ComponentAccessor store, @Nonnull Player playerComponent) { + int mountEntityId = playerComponent.getMountEntityId(); + if (mountEntityId != 0) { + dismountNpc(store, mountEntityId); + } + } + + public static void dismountNpc(@Nonnull ComponentAccessor store, int mountEntityId) { + Ref entityReference = store.getExternalData().getRefFromNetworkId(mountEntityId); + if (entityReference != null && entityReference.isValid()) { + NPCMountComponent mountComponent = store.getComponent(entityReference, NPCMountComponent.getComponentType()); + + assert mountComponent != null; + + resetOriginalMountRole(entityReference, store, mountComponent); + PlayerRef ownerPlayerRef = mountComponent.getOwnerPlayerRef(); + if (ownerPlayerRef != null) { + resetOriginalPlayerMovementSettings(ownerPlayerRef, store); + } + } + } + + private static void resetOriginalMountRole( + @Nonnull Ref entityReference, @Nonnull ComponentAccessor store, @Nonnull NPCMountComponent mountComponent + ) { + NPCEntity npcComponent = store.getComponent(entityReference, NPCEntity.getComponentType()); + + assert npcComponent != null; + + RoleChangeSystem.requestRoleChange(entityReference, npcComponent.getRole(), mountComponent.getOriginalRoleIndex(), false, "Idle", null, store); + store.removeComponent(entityReference, NPCMountComponent.getComponentType()); + } + + public static void resetOriginalPlayerMovementSettings(@Nonnull PlayerRef playerRef, @Nonnull ComponentAccessor store) { + Ref reference = playerRef.getReference(); + if (reference != null) { + playerRef.getPacketHandler().write(new DismountNPC()); + MovementManager movementManagerComponent = store.getComponent(reference, MovementManager.getComponentType()); + + assert movementManagerComponent != null; + + movementManagerComponent.resetDefaultsAndUpdate(reference, store); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/MountSystems.java b/src/com/hypixel/hytale/builtin/mounts/MountSystems.java new file mode 100644 index 0000000..fdefbd9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/MountSystems.java @@ -0,0 +1,709 @@ +package com.hypixel.hytale.builtin.mounts; + +import com.hypixel.hytale.builtin.mounts.minecart.MinecartComponent; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.OrderPriority; +import com.hypixel.hytale.component.dependency.RootDependency; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.dependency.SystemGroupDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.AnimationSlot; +import com.hypixel.hytale.protocol.BlockMount; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.MountController; +import com.hypixel.hytale.protocol.MountedUpdate; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.mountpoints.BlockMountPoint; +import com.hypixel.hytale.server.core.entity.AnimationUtils; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.component.Interactable; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageEventSystem; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageModule; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerInput; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSystems; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.modules.entity.teleport.TeleportSystems; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.prefab.PrefabCopyableComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MountSystems { + public MountSystems() { + } + + private static void handleMountedRemoval(Ref ref, @Nonnull CommandBuffer commandBuffer, @Nonnull MountedComponent component) { + Ref mountedToEntity = component.getMountedToEntity(); + if (mountedToEntity != null && mountedToEntity.isValid()) { + MountedByComponent mountedBy = commandBuffer.getComponent(mountedToEntity, MountedByComponent.getComponentType()); + if (mountedBy != null) { + mountedBy.removePassenger(ref); + } + } + + Ref mountedToBlock = component.getMountedToBlock(); + if (mountedToBlock != null && mountedToBlock.isValid()) { + Store chunkStore = mountedToBlock.getStore(); + BlockMountComponent seatComponent = chunkStore.getComponent(mountedToBlock, BlockMountComponent.getComponentType()); + if (seatComponent != null) { + seatComponent.removeSeatedEntity(ref); + if (seatComponent.isDead()) { + chunkStore.removeComponent(mountedToBlock, BlockMountComponent.getComponentType()); + } + } + } + } + + public static class EnsureMinecartComponents extends HolderSystem { + public EnsureMinecartComponents() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(Interactable.getComponentType()); + holder.putComponent(NetworkId.getComponentType(), new NetworkId(store.getExternalData().takeNextNetworkId())); + holder.ensureComponent(PrefabCopyableComponent.getComponentType()); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Override + public Query getQuery() { + return MinecartComponent.getComponentType(); + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.firstSet(); + } + } + + public static class HandleMountInput extends EntityTickingSystem { + private final Query query = Query.and(MountedComponent.getComponentType(), PlayerInput.getComponentType()); + private final Set> deps = Set.of(new SystemDependency<>(Order.BEFORE, PlayerSystems.ProcessPlayerInput.class)); + + public HandleMountInput() { + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + MountedComponent mounted = archetypeChunk.getComponent(index, MountedComponent.getComponentType()); + + assert mounted != null; + + PlayerInput input = archetypeChunk.getComponent(index, PlayerInput.getComponentType()); + + assert input != null; + + MountController controller = mounted.getControllerType(); + Ref targetRef = controller == MountController.BlockMount ? archetypeChunk.getReferenceTo(index) : mounted.getMountedToEntity(); + List queue = input.getMovementUpdateQueue(); + + for (int i = 0; i < queue.size(); i++) { + PlayerInput.InputUpdate q = queue.get(i); + if (controller == MountController.BlockMount && (q instanceof PlayerInput.RelativeMovement || q instanceof PlayerInput.AbsoluteMovement)) { + if (mounted.getMountedDurationMs() < 600L) { + continue; + } + + Ref ref = archetypeChunk.getReferenceTo(index); + commandBuffer.removeComponent(ref, MountedComponent.getComponentType()); + } + + if (q instanceof PlayerInput.SetRiderMovementStates s) { + MovementStates states = s.movementStates(); + MovementStatesComponent movementStatesComponent = archetypeChunk.getComponent(index, MovementStatesComponent.getComponentType()); + if (movementStatesComponent != null) { + movementStatesComponent.setMovementStates(states); + } + } else if (!(q instanceof PlayerInput.WishMovement)) { + if (q instanceof PlayerInput.RelativeMovement relative) { + relative.apply(commandBuffer, archetypeChunk, index); + TransformComponent transform = commandBuffer.getComponent(targetRef, TransformComponent.getComponentType()); + transform.getPosition().add(relative.getX(), relative.getY(), relative.getZ()); + } else if (q instanceof PlayerInput.AbsoluteMovement absolute) { + absolute.apply(commandBuffer, archetypeChunk, index); + TransformComponent transform = commandBuffer.getComponent(targetRef, TransformComponent.getComponentType()); + transform.getPosition().assign(absolute.getX(), absolute.getY(), absolute.getZ()); + } else if (q instanceof PlayerInput.SetMovementStates sx) { + MovementStates states = sx.movementStates(); + MovementStatesComponent movementStatesComponent = commandBuffer.getComponent(targetRef, MovementStatesComponent.getComponentType()); + if (movementStatesComponent != null) { + movementStatesComponent.setMovementStates(states); + } + } else if (q instanceof PlayerInput.SetBody body) { + body.apply(commandBuffer, archetypeChunk, index); + TransformComponent transform = commandBuffer.getComponent(targetRef, TransformComponent.getComponentType()); + transform.getRotation().assign(body.direction().pitch, body.direction().yaw, body.direction().roll); + } else if (q instanceof PlayerInput.SetHead head) { + head.apply(commandBuffer, archetypeChunk, index); + } + } + } + + queue.clear(); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.deps; + } + } + + public static class MountedEntityDeath extends RefChangeSystem { + public MountedEntityDeath() { + } + + @Override + public Query getQuery() { + return MountedComponent.getComponentType(); + } + + @Nonnull + @Override + public ComponentType componentType() { + return DeathComponent.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + commandBuffer.removeComponent(ref, MountedComponent.getComponentType()); + } + + public void onComponentSet( + @Nonnull Ref ref, + @Nullable DeathComponent oldComponent, + @Nonnull DeathComponent newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + } + + public static class OnMinecartHit extends DamageEventSystem { + private static final Duration HIT_RESET_TIME = Duration.ofSeconds(10L); + private static final int NUMBER_OF_HITS = 3; + @Nonnull + private static final Query QUERY = Archetype.of(MinecartComponent.getComponentType(), TransformComponent.getComponentType()); + @Nonnull + private static final Set> DEPENDENCIES = Set.of( + new SystemGroupDependency<>(Order.AFTER, DamageModule.get().getGatherDamageGroup()), + new SystemGroupDependency<>(Order.AFTER, DamageModule.get().getFilterDamageGroup()), + new SystemGroupDependency<>(Order.BEFORE, DamageModule.get().getInspectDamageGroup()) + ); + + public OnMinecartHit() { + } + + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + MinecartComponent minecartComponent = archetypeChunk.getComponent(index, MinecartComponent.getComponentType()); + + assert minecartComponent != null; + + Instant currentTime = commandBuffer.getResource(TimeResource.getResourceType()).getNow(); + if (minecartComponent.getLastHit() != null && currentTime.isAfter(minecartComponent.getLastHit().plus(HIT_RESET_TIME))) { + minecartComponent.setLastHit(null); + minecartComponent.setNumberOfHits(0); + } + + if (!(damage.getAmount() <= 0.0F)) { + minecartComponent.setNumberOfHits(minecartComponent.getNumberOfHits() + 1); + minecartComponent.setLastHit(currentTime); + if (minecartComponent.getNumberOfHits() == 3) { + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + boolean shouldDropItem = true; + if (damage.getSource() instanceof Damage.EntitySource source) { + Player playerComponent = source.getRef().isValid() ? commandBuffer.getComponent(source.getRef(), Player.getComponentType()) : null; + if (playerComponent != null) { + shouldDropItem = playerComponent.getGameMode() != GameMode.Creative; + } + } + + if (shouldDropItem && minecartComponent.getSourceItem() != null) { + TransformComponent transform = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transform != null; + + Holder drop = ItemComponent.generateItemDrop( + commandBuffer, new ItemStack(minecartComponent.getSourceItem()), transform.getPosition(), transform.getRotation(), 0.0F, 1.0F, 0.0F + ); + if (drop != null) { + commandBuffer.addEntity(drop, AddReason.SPAWN); + } + } + } + } + } + } + + public static class PlayerMount extends RefChangeSystem { + private final Query query = PlayerInput.getComponentType(); + + public PlayerMount() { + } + + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public ComponentType componentType() { + return MountedComponent.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, + @Nonnull MountedComponent component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + MountedComponent mounted = commandBuffer.getComponent(ref, MountedComponent.getComponentType()); + + assert mounted != null; + + PlayerInput input = commandBuffer.getComponent(ref, PlayerInput.getComponentType()); + + assert input != null; + + Ref mountRef = mounted.getMountedToEntity(); + if (mountRef != null && mountRef.isValid()) { + int mountNetworkId = commandBuffer.getComponent(mountRef, NetworkId.getComponentType()).getId(); + input.setMountId(mountNetworkId); + input.getMovementUpdateQueue().clear(); + } + } + + public void onComponentSet( + @Nonnull Ref ref, + @Nullable MountedComponent oldComponent, + @Nonnull MountedComponent newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, + @Nonnull MountedComponent component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + PlayerInput input = commandBuffer.getComponent(ref, PlayerInput.getComponentType()); + + assert input != null; + + input.setMountId(0); + } + } + + public static class RemoveBlockSeat extends RefSystem { + public RemoveBlockSeat() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + BlockMountComponent blockSeatComponent = commandBuffer.getComponent(ref, BlockMountComponent.getComponentType()); + + assert blockSeatComponent != null; + + ObjectArrayList> dismounting = new ObjectArrayList<>(blockSeatComponent.getSeatedEntities()); + World world = ref.getStore().getExternalData().getWorld(); + + for (Ref seated : dismounting) { + blockSeatComponent.removeSeatedEntity(seated); + world.execute(() -> { + if (seated.isValid()) { + seated.getStore().tryRemoveComponent(seated, MountedComponent.getComponentType()); + } + }); + } + } + + @Override + public Query getQuery() { + return BlockMountComponent.getComponentType(); + } + } + + public static class RemoveMounted extends RefSystem { + public RemoveMounted() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + MountedComponent mounted = commandBuffer.getComponent(ref, MountedComponent.getComponentType()); + commandBuffer.removeComponent(ref, MountedComponent.getComponentType()); + MountSystems.handleMountedRemoval(ref, commandBuffer, mounted); + } + + @Override + public Query getQuery() { + return MountedComponent.getComponentType(); + } + } + + public static class RemoveMountedBy extends RefSystem { + public RemoveMountedBy() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + MountedByComponent by = commandBuffer.getComponent(ref, MountedByComponent.getComponentType()); + + for (Ref p : by.getPassengers()) { + if (p.isValid()) { + MountedComponent mounted = commandBuffer.getComponent(p, MountedComponent.getComponentType()); + if (mounted != null) { + Ref target = mounted.getMountedToEntity(); + if (!target.isValid() || target.equals(ref)) { + commandBuffer.removeComponent(p, MountedComponent.getComponentType()); + } + } + } + } + } + + @Override + public Query getQuery() { + return MountedByComponent.getComponentType(); + } + } + + public static class TeleportMountedEntity extends RefChangeSystem { + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.BEFORE, TeleportSystems.MoveSystem.class, OrderPriority.CLOSEST), + new SystemDependency<>(Order.BEFORE, TeleportSystems.PlayerMoveSystem.class, OrderPriority.CLOSEST) + ); + + public TeleportMountedEntity() { + } + + @Override + public Query getQuery() { + return MountedComponent.getComponentType(); + } + + @Nonnull + @Override + public ComponentType componentType() { + return Teleport.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull Teleport component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + commandBuffer.removeComponent(ref, MountedComponent.getComponentType()); + } + + public void onComponentSet( + @Nonnull Ref ref, + @Nullable Teleport oldComponent, + @Nonnull Teleport newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull Teleport component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + } + + public static class TrackedMounted extends RefChangeSystem { + public TrackedMounted() { + } + + @Override + public Query getQuery() { + return MountedComponent.getComponentType(); + } + + @Nonnull + @Override + public ComponentType componentType() { + return MountedComponent.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, + @Nonnull MountedComponent component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Ref target = component.getMountedToEntity(); + if (target != null && target.isValid()) { + MountedByComponent mountedBy = commandBuffer.ensureAndGetComponent(target, MountedByComponent.getComponentType()); + mountedBy.addPassenger(ref); + } + } + + public void onComponentSet( + @Nonnull Ref ref, + MountedComponent oldComponent, + @Nonnull MountedComponent newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, + @Nonnull MountedComponent component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + MountSystems.handleMountedRemoval(ref, commandBuffer, component); + } + } + + public static class TrackerRemove extends RefChangeSystem { + public TrackerRemove() { + } + + @Override + public Query getQuery() { + return EntityTrackerSystems.Visible.getComponentType(); + } + + @Nonnull + @Override + public ComponentType componentType() { + return MountedComponent.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, + @Nonnull MountedComponent component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentSet( + @Nonnull Ref ref, + MountedComponent oldComponent, + @Nonnull MountedComponent newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, + @Nonnull MountedComponent component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + if (component.getControllerType() == MountController.BlockMount) { + AnimationUtils.stopAnimation(ref, AnimationSlot.Movement, true, commandBuffer); + } + + EntityTrackerSystems.Visible visibleComponent = store.getComponent(ref, EntityTrackerSystems.Visible.getComponentType()); + + assert visibleComponent != null; + + for (EntityTrackerSystems.EntityViewer viewer : visibleComponent.visibleTo.values()) { + viewer.queueRemove(ref, ComponentUpdateType.Mounted); + } + } + } + + public static class TrackerUpdate extends EntityTickingSystem { + private final ComponentType componentType = EntityTrackerSystems.Visible.getComponentType(); + @Nonnull + private final Query query = Query.and(this.componentType, MountedComponent.getComponentType()); + + public TrackerUpdate() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visible = archetypeChunk.getComponent(index, this.componentType); + MountedComponent mounted = archetypeChunk.getComponent(index, MountedComponent.getComponentType()); + Ref ref = archetypeChunk.getReferenceTo(index); + if (mounted.consumeNetworkOutdated()) { + queueUpdatesFor(ref, visible.visibleTo, mounted); + } else if (!visible.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(ref, visible.newlyVisibleTo, mounted); + } + } + + private static void queueUpdatesFor( + @Nonnull Ref ref, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo, @Nonnull MountedComponent component + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Mounted; + Ref mountedToEntity = component.getMountedToEntity(); + Ref mountedToBlock = component.getMountedToBlock(); + Vector3f offset = component.getAttachmentOffset(); + com.hypixel.hytale.protocol.Vector3f netOffset = new com.hypixel.hytale.protocol.Vector3f(offset.x, offset.y, offset.z); + MountedUpdate mountedUpdate; + if (mountedToEntity != null) { + int mountedToNetworkId = ref.getStore().getComponent(mountedToEntity, NetworkId.getComponentType()).getId(); + mountedUpdate = new MountedUpdate(mountedToNetworkId, netOffset, component.getControllerType(), null); + } else { + if (mountedToBlock == null) { + throw new UnsupportedOperationException("Couldn't create MountedUpdate packet for MountedComponent"); + } + + BlockMountComponent blockMountComponent = mountedToBlock.getStore().getComponent(mountedToBlock, BlockMountComponent.getComponentType()); + if (blockMountComponent == null) { + return; + } + + BlockMountPoint occupiedSeat = blockMountComponent.getSeatBlockBySeatedEntity(ref); + if (occupiedSeat == null) { + return; + } + + BlockType blockType = blockMountComponent.getExpectedBlockType(); + Vector3f position = occupiedSeat.computeWorldSpacePosition(blockMountComponent.getBlockPos()); + Vector3f rotationEuler = occupiedSeat.computeRotationEuler(blockMountComponent.getExpectedRotation()); + BlockMount blockMount = new BlockMount( + blockMountComponent.getType(), + new com.hypixel.hytale.protocol.Vector3f(position.x, position.y, position.z), + new com.hypixel.hytale.protocol.Vector3f(rotationEuler.x, rotationEuler.y, rotationEuler.z), + BlockType.getAssetMap().getIndex(blockType.getId()) + ); + mountedUpdate = new MountedUpdate(0, netOffset, component.getControllerType(), blockMount); + } + + update.mounted = mountedUpdate; + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, update); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/MountedByComponent.java b/src/com/hypixel/hytale/builtin/mounts/MountedByComponent.java new file mode 100644 index 0000000..ad8a951 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/MountedByComponent.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.mounts; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class MountedByComponent implements Component { + @Nonnull + private final List> passengers = new ObjectArrayList<>(); + + public MountedByComponent() { + } + + public static ComponentType getComponentType() { + return MountPlugin.getInstance().getMountedByComponentType(); + } + + public void removeInvalid() { + this.passengers.removeIf(v -> !v.isValid()); + } + + @Nonnull + public List> getPassengers() { + this.removeInvalid(); + return this.passengers; + } + + public void addPassenger(Ref passenger) { + this.passengers.add(passenger); + } + + public void removePassenger(Ref ref) { + this.passengers.remove(ref); + } + + @Nonnull + public MountedByComponent withPassenger(Ref passenger) { + this.passengers.add(passenger); + return this; + } + + @Nonnull + @Override + public Component clone() { + return new MountedByComponent(); + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/MountedComponent.java b/src/com/hypixel/hytale/builtin/mounts/MountedComponent.java new file mode 100644 index 0000000..2859868 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/MountedComponent.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.builtin.mounts; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.BlockMountType; +import com.hypixel.hytale.protocol.MountController; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MountedComponent implements Component { + private Ref mountedToEntity; + private Ref mountedToBlock; + private MountController controller; + private BlockMountType blockMountType; + private Vector3f attachmentOffset = new Vector3f(0.0F, 0.0F, 0.0F); + private long mountStartMs; + private boolean isNetworkOutdated = true; + + public static ComponentType getComponentType() { + return MountPlugin.getInstance().getMountedComponentType(); + } + + public MountedComponent(Ref mountedToEntity, Vector3f attachmentOffset, MountController controller) { + this.mountedToEntity = mountedToEntity; + this.attachmentOffset = attachmentOffset; + this.controller = controller; + this.mountStartMs = System.currentTimeMillis(); + } + + public MountedComponent(Ref mountedToBlock, Vector3f attachmentOffset, BlockMountType blockMountType) { + this.mountedToBlock = mountedToBlock; + this.attachmentOffset = attachmentOffset; + this.controller = MountController.BlockMount; + this.blockMountType = blockMountType; + this.mountStartMs = System.currentTimeMillis(); + } + + @Nullable + public Ref getMountedToEntity() { + return this.mountedToEntity; + } + + @Nullable + public Ref getMountedToBlock() { + return this.mountedToBlock; + } + + public Vector3f getAttachmentOffset() { + return this.attachmentOffset; + } + + public MountController getControllerType() { + return this.controller; + } + + public BlockMountType getBlockMountType() { + return this.blockMountType; + } + + public long getMountedDurationMs() { + return System.currentTimeMillis() - this.mountStartMs; + } + + public boolean consumeNetworkOutdated() { + boolean tmp = this.isNetworkOutdated; + this.isNetworkOutdated = false; + return tmp; + } + + @Nonnull + @Override + public Component clone() { + return new MountedComponent(this.mountedToEntity, this.attachmentOffset, this.controller); + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/NPCMountComponent.java b/src/com/hypixel/hytale/builtin/mounts/NPCMountComponent.java new file mode 100644 index 0000000..64c6441 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/NPCMountComponent.java @@ -0,0 +1,82 @@ +package com.hypixel.hytale.builtin.mounts; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NPCMountComponent implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(NPCMountComponent.class, NPCMountComponent::new) + .append( + new KeyedCodec<>("OriginalRoleIndex", Codec.INTEGER), + (mountComponent, integer) -> mountComponent.originalRoleIndex = integer, + mountComponent -> mountComponent.originalRoleIndex + ) + .add() + .build(); + private int originalRoleIndex; + @Nullable + private PlayerRef ownerPlayerRef; + private float anchorX; + private float anchorY; + private float anchorZ; + + public NPCMountComponent() { + } + + public static ComponentType getComponentType() { + return MountPlugin.getInstance().getMountComponentType(); + } + + public int getOriginalRoleIndex() { + return this.originalRoleIndex; + } + + public void setOriginalRoleIndex(int originalRoleIndex) { + this.originalRoleIndex = originalRoleIndex; + } + + @Nullable + public PlayerRef getOwnerPlayerRef() { + return this.ownerPlayerRef; + } + + public void setOwnerPlayerRef(PlayerRef ownerPlayerRef) { + this.ownerPlayerRef = ownerPlayerRef; + } + + public float getAnchorX() { + return this.anchorX; + } + + public float getAnchorY() { + return this.anchorY; + } + + public float getAnchorZ() { + return this.anchorZ; + } + + public void setAnchor(float x, float y, float z) { + this.anchorX = x; + this.anchorY = y; + this.anchorZ = z; + } + + @Nonnull + @Override + public Component clone() { + NPCMountComponent component = new NPCMountComponent(); + component.originalRoleIndex = this.originalRoleIndex; + component.ownerPlayerRef = this.ownerPlayerRef; + component.anchorX = this.anchorX; + component.anchorY = this.anchorY; + component.anchorZ = this.anchorZ; + return component; + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/NPCMountSystems.java b/src/com/hypixel/hytale/builtin/mounts/NPCMountSystems.java new file mode 100644 index 0000000..3e849ea --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/NPCMountSystems.java @@ -0,0 +1,134 @@ +package com.hypixel.hytale.builtin.mounts; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.protocol.packets.interaction.MountNPC; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathSystems; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.systems.RoleChangeSystem; +import javax.annotation.Nonnull; + +public class NPCMountSystems { + public NPCMountSystems() { + } + + public static class DismountOnMountDeath extends DeathSystems.OnDeathSystem { + public DismountOnMountDeath() { + } + + @Override + public Query getQuery() { + return NPCMountComponent.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + NPCMountComponent mountComponent = store.getComponent(ref, NPCMountComponent.getComponentType()); + + assert mountComponent != null; + + PlayerRef playerRef = mountComponent.getOwnerPlayerRef(); + if (playerRef != null) { + MountPlugin.resetOriginalPlayerMovementSettings(playerRef, store); + } + } + } + + public static class DismountOnPlayerDeath extends DeathSystems.OnDeathSystem { + public DismountOnPlayerDeath() { + } + + @Nonnull + @Override + public Query getQuery() { + return Player.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + MountPlugin.checkDismountNpc(commandBuffer, playerComponent); + } + } + + public static class OnAdd extends RefSystem { + @Nonnull + private final ComponentType mountComponentType; + + public OnAdd(@Nonnull ComponentType mountRoleChangeComponentType) { + this.mountComponentType = mountRoleChangeComponentType; + } + + @Override + public Query getQuery() { + return this.mountComponentType; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + NPCMountComponent mountComponent = store.getComponent(ref, this.mountComponentType); + + assert mountComponent != null; + + PlayerRef playerRef = mountComponent.getOwnerPlayerRef(); + if (playerRef == null) { + resetOriginalRoleMount(ref, store, commandBuffer, mountComponent); + } else { + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + NetworkId networkIdComponent = store.getComponent(ref, NetworkId.getComponentType()); + + assert networkIdComponent != null; + + int networkId = networkIdComponent.getId(); + MountNPC packet = new MountNPC(mountComponent.getAnchorX(), mountComponent.getAnchorY(), mountComponent.getAnchorZ(), networkId); + Player playerComponent = playerRef.getComponent(Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.setMountEntityId(networkId); + playerRef.getPacketHandler().write(packet); + } + } + + private static void resetOriginalRoleMount( + @Nonnull Ref ref, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull NPCMountComponent mountComponent + ) { + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + RoleChangeSystem.requestRoleChange(ref, npcComponent.getRole(), mountComponent.getOriginalRoleIndex(), false, "Idle", null, store); + commandBuffer.removeComponent(ref, NPCMountComponent.getComponentType()); + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/commands/DismountCommand.java b/src/com/hypixel/hytale/builtin/mounts/commands/DismountCommand.java new file mode 100644 index 0000000..1173a84 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/commands/DismountCommand.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.builtin.mounts.commands; + +import com.hypixel.hytale.builtin.mounts.MountedComponent; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class DismountCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_DISMOUNT_DISMOUNT_ATTEMPTED = Message.translation("server.commands.dismount.dismountAttempted"); + + public DismountCommand() { + super("dismount", "server.commands.dismount.desc"); + this.addUsageVariant(new DismountCommand.DismountOtherCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + store.tryRemoveComponent(ref, MountedComponent.getComponentType()); + context.sendMessage(MESSAGE_COMMANDS_DISMOUNT_DISMOUNT_ATTEMPTED); + } + + private static class DismountOtherCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + + DismountOtherCommand() { + super("server.commands.dismount.other.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef playerRef = this.playerArg.get(context); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + store.tryRemoveComponent(ref, MountedComponent.getComponentType()); + context.sendMessage(Message.translation("server.commands.dismount.dismountOther").param("username", playerRef.getUsername())); + } + }); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/commands/MountCheckCommand.java b/src/com/hypixel/hytale/builtin/mounts/commands/MountCheckCommand.java new file mode 100644 index 0000000..44321ab --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/commands/MountCheckCommand.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.mounts.commands; + +import com.hypixel.hytale.builtin.mounts.MountedComponent; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MountCheckCommand extends AbstractTargetPlayerCommand { + private static final Message MESSAGE_COMMANDS_CHECK_NO_COMPONENT = Message.translation("server.commands.check.noComponent"); + private static final Message MESSAGE_COMMANDS_CHECK_MOUNTED_TO_ENTITY = Message.translation("server.commands.check.mountedToEntity"); + private static final Message MESSAGE_COMMANDS_CHECK_MOUNTED_TO_BLOCK = Message.translation("server.commands.check.mountedToBlock"); + private static final Message MESSAGE_COMMANDS_CHECK_UNKNOWN_STATUS = Message.translation("server.commands.check.unknownStatus"); + + public MountCheckCommand() { + super("check", "server.commands.check.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + MountedComponent mountedComponent = store.getComponent(ref, MountedComponent.getComponentType()); + if (mountedComponent == null) { + playerRef.sendMessage(MESSAGE_COMMANDS_CHECK_NO_COMPONENT); + } else { + if (mountedComponent.getMountedToEntity() != null) { + playerRef.sendMessage(MESSAGE_COMMANDS_CHECK_MOUNTED_TO_ENTITY); + } else if (mountedComponent.getMountedToBlock() != null) { + playerRef.sendMessage(MESSAGE_COMMANDS_CHECK_MOUNTED_TO_BLOCK); + } else { + playerRef.sendMessage(MESSAGE_COMMANDS_CHECK_UNKNOWN_STATUS); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/commands/MountCommand.java b/src/com/hypixel/hytale/builtin/mounts/commands/MountCommand.java new file mode 100644 index 0000000..b074a81 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/commands/MountCommand.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.builtin.mounts.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class MountCommand extends AbstractCommandCollection { + public MountCommand() { + super("mount", "server.commands.mount"); + this.addSubCommand(new DismountCommand()); + this.addSubCommand(new MountCheckCommand()); + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/interactions/MountInteraction.java b/src/com/hypixel/hytale/builtin/mounts/interactions/MountInteraction.java new file mode 100644 index 0000000..96e3b95 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/interactions/MountInteraction.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.builtin.mounts.interactions; + +import com.hypixel.hytale.builtin.mounts.MountedByComponent; +import com.hypixel.hytale.builtin.mounts.MountedComponent; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.MountController; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class MountInteraction extends SimpleInstantInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + MountInteraction.class, MountInteraction::new, SimpleInstantInteraction.CODEC + ) + .appendInherited( + new KeyedCodec<>("AttachmentOffset", ProtocolCodecs.VECTOR3F), + (o, v) -> o.attachmentOffset.assign(v.x, v.y, v.z), + o -> new Vector3f(o.attachmentOffset.x, o.attachmentOffset.y, o.attachmentOffset.z), + (o, p) -> o.attachmentOffset = p.attachmentOffset + ) + .add() + .appendInherited( + new KeyedCodec<>("Controller", new EnumCodec<>(MountController.class)), + (o, v) -> o.controller = v, + o -> o.controller, + (o, p) -> o.controller = p.controller + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + private com.hypixel.hytale.math.vector.Vector3f attachmentOffset = new com.hypixel.hytale.math.vector.Vector3f(0.0F, 0.0F, 0.0F); + private MountController controller; + + public MountInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + Ref target = context.getTargetEntity(); + if (target == null) { + context.getState().state = InteractionState.Failed; + } else { + Ref self = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + MountedComponent mounted = commandBuffer.getComponent(self, MountedComponent.getComponentType()); + if (mounted != null) { + commandBuffer.removeComponent(self, MountedComponent.getComponentType()); + context.getState().state = InteractionState.Failed; + } else { + MountedByComponent mountedBy = commandBuffer.getComponent(target, MountedByComponent.getComponentType()); + if (mountedBy != null && !mountedBy.getPassengers().isEmpty()) { + context.getState().state = InteractionState.Failed; + } else { + commandBuffer.addComponent(self, MountedComponent.getComponentType(), new MountedComponent(target, this.attachmentOffset, this.controller)); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/interactions/SeatingInteraction.java b/src/com/hypixel/hytale/builtin/mounts/interactions/SeatingInteraction.java new file mode 100644 index 0000000..1e926c9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/interactions/SeatingInteraction.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.builtin.mounts.interactions; + +import com.hypixel.hytale.builtin.mounts.BlockMountAPI; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.BlockSoundEvent; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocksound.config.BlockSoundSet; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SeatingInteraction extends SimpleBlockInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + SeatingInteraction.class, SeatingInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Arranges perfect seating accommodations") + .build(); + + public SeatingInteraction() { + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + Ref ref = context.getEntity(); + Player player = commandBuffer.getComponent(ref, Player.getComponentType()); + if (player != null) { + BlockPosition rawTarget = context.getMetaStore().getMetaObject(TARGET_BLOCK_RAW); + Vector3f whereWasHit = new Vector3f(rawTarget.x + 0.5F, rawTarget.y + 0.5F, rawTarget.z + 0.5F); + BlockMountAPI.BlockMountResult result = BlockMountAPI.mountOnBlock(ref, commandBuffer, targetBlock, whereWasHit); + if (result == BlockMountAPI.DidNotMount.ALREADY_MOUNTED) { + int soundEventIndex = SoundEvent.getAssetMap().getIndex("Creative_Play_Add_Mask"); + SoundUtil.playSoundEvent2d(ref, soundEventIndex, SoundCategory.SFX, commandBuffer); + } else if (result instanceof BlockMountAPI.Mounted mounted) { + BlockSoundSet soundSet = BlockSoundSet.getAssetMap().getAsset(mounted.blockType().getBlockSoundSetIndex()); + String seatSoundId = soundSet == null ? null : soundSet.getSoundEventIds().getOrDefault(BlockSoundEvent.Walk, null); + if (seatSoundId != null) { + int soundEventIndex = SoundEvent.getAssetMap().getIndex(seatSoundId); + SoundUtil.playSoundEvent3dToPlayer(ref, soundEventIndex, SoundCategory.SFX, targetBlock.toVector3d(), commandBuffer); + } + } else { + player.sendMessage(Message.translation("server.interactions.didNotMount").param("state", result.toString())); + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/interactions/SpawnMinecartInteraction.java b/src/com/hypixel/hytale/builtin/mounts/interactions/SpawnMinecartInteraction.java new file mode 100644 index 0000000..2ead381 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/interactions/SpawnMinecartInteraction.java @@ -0,0 +1,167 @@ +package com.hypixel.hytale.builtin.mounts.interactions; + +import com.hypixel.hytale.builtin.mounts.minecart.MinecartComponent; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.RailConfig; +import com.hypixel.hytale.protocol.RailPoint; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.Interactable; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.PersistentModel; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.Interactions; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Collections; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SpawnMinecartInteraction extends SimpleBlockInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SpawnMinecartInteraction.class, SpawnMinecartInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Spawns a minecart at the target block") + .appendInherited(new KeyedCodec<>("Model", Codec.STRING), (o, v) -> o.modelId = v, o -> o.modelId, (o, p) -> o.modelId = p.modelId) + .addValidator(ModelAsset.VALIDATOR_CACHE.getValidator()) + .add() + .>appendInherited( + new KeyedCodec<>("CartInteractions", new EnumMapCodec<>(InteractionType.class, RootInteraction.CHILD_ASSET_CODEC)), + (o, v) -> o.cartInteractions = v, + o -> o.cartInteractions, + (o, p) -> o.cartInteractions = p.cartInteractions + ) + .addValidatorLate(() -> RootInteraction.VALIDATOR_CACHE.getMapValueValidator().late()) + .add() + .build(); + private String modelId; + private Map cartInteractions = Collections.emptyMap(); + + public SpawnMinecartInteraction() { + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + Ref ref = context.getEntity(); + Holder holder = EntityStore.REGISTRY.newHolder(); + Vector3d targetPosition = targetBlock.toVector3d(); + targetPosition.add(0.5, 0.5, 0.5); + Vector3f rotation = new Vector3f(); + HeadRotation headRotation = commandBuffer.getComponent(ref, HeadRotation.getComponentType()); + if (headRotation != null) { + rotation.setYaw(headRotation.getRotation().getYaw()); + } + + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)); + if (chunk != null) { + BlockType block = chunk.getBlockType(targetBlock); + int blockRotation = chunk.getRotationIndex(targetBlock.x, targetBlock.y, targetBlock.z); + RailConfig railConfig = block.getRailConfig(blockRotation); + if (railConfig != null) { + alignToRail(targetBlock, targetPosition, rotation, rotation.getYaw(), railConfig); + } else { + BlockBoundingBoxes.RotatedVariantBoxes bounding = BlockBoundingBoxes.getAssetMap().getAsset(block.getHitboxTypeIndex()).get(blockRotation); + targetPosition.add(0.0, bounding.getBoundingBox().max.y - 0.5, 0.0); + } + + holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(targetPosition, rotation)); + holder.ensureComponent(UUIDComponent.getComponentType()); + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(this.modelId); + if (modelAsset == null) { + modelAsset = ModelAsset.DEBUG; + } + + Model model = Model.createRandomScaleModel(modelAsset); + holder.addComponent(PersistentModel.getComponentType(), new PersistentModel(model.toReference())); + holder.addComponent(ModelComponent.getComponentType(), new ModelComponent(model)); + holder.addComponent(BoundingBox.getComponentType(), new BoundingBox(model.getBoundingBox())); + holder.ensureComponent(Interactable.getComponentType()); + holder.addComponent(Interactions.getComponentType(), new Interactions(this.cartInteractions)); + holder.putComponent( + MinecartComponent.getComponentType(), new MinecartComponent(context.getHeldItem() != null ? context.getHeldItem().getItemId() : null) + ); + commandBuffer.addEntity(holder, AddReason.SPAWN); + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } + + private static void alignToRail(@Nonnull Vector3i targetBlock, @Nonnull Vector3d target, @Nonnull Vector3f rotation, float yaw, @Nonnull RailConfig config) { + RailPoint[] points = config.points; + double smallestDistance = Double.MAX_VALUE; + double ox = target.x; + double oy = target.y; + double oz = target.z; + Vector3d facingDir = new Vector3d(); + facingDir.assign(yaw, 0.0); + + for (int index = 0; index < points.length - 1; index++) { + RailPoint p = points[index]; + RailPoint p2 = points[index + 1]; + Vector3d point = new Vector3d(targetBlock.x + p.point.x, targetBlock.y + p.point.y, targetBlock.z + p.point.z); + Vector3d point2 = new Vector3d(targetBlock.x + p2.point.x, targetBlock.y + p2.point.y, targetBlock.z + p2.point.z); + Vector3d dir = point2.clone().subtract(point); + double maxLength = dir.length(); + dir.normalize(); + Vector3d toPoint = target.clone().subtract(point); + double distance = dir.dot(toPoint); + Vector3d pointOnLine = point.clone(); + pointOnLine.addScaled(dir, Math.min(maxLength, Math.max(0.0, distance))); + double pointDist = pointOnLine.distanceSquaredTo(target); + if (pointDist >= 0.0 && pointDist <= 0.8F && pointDist < smallestDistance) { + ox = pointOnLine.x; + oy = pointOnLine.y; + oz = pointOnLine.z; + smallestDistance = pointDist; + if (facingDir.dot(dir) < 0.0) { + dir.scale(-1.0); + } + + float newYaw = (float)(Math.atan2(dir.x, dir.z) + Math.PI); + float newPitch = (float)Math.asin(dir.y); + rotation.setYaw(newYaw); + rotation.setPitch(newPitch); + } + } + + if (!(smallestDistance >= Double.MAX_VALUE)) { + target.assign(ox, oy, oz); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/minecart/MinecartComponent.java b/src/com/hypixel/hytale/builtin/mounts/minecart/MinecartComponent.java new file mode 100644 index 0000000..818f55d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/minecart/MinecartComponent.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.builtin.mounts.minecart; + +import com.hypixel.hytale.builtin.mounts.MountPlugin; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; + +public class MinecartComponent implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(MinecartComponent.class, MinecartComponent::new) + .append(new KeyedCodec<>("SourceItem", Codec.STRING), (o, v) -> o.sourceItem = v, o -> o.sourceItem) + .add() + .build(); + private int numberOfHits = 0; + private Instant lastHit; + private String sourceItem = "Rail_Kart"; + + public static ComponentType getComponentType() { + return MountPlugin.getInstance().getMinecartComponentType(); + } + + private MinecartComponent() { + } + + public MinecartComponent(String sourceItem) { + this.sourceItem = sourceItem; + } + + public int getNumberOfHits() { + return this.numberOfHits; + } + + public void setNumberOfHits(int numberOfHits) { + this.numberOfHits = numberOfHits; + } + + public Instant getLastHit() { + return this.lastHit; + } + + public void setLastHit(Instant lastHit) { + this.lastHit = lastHit; + } + + public String getSourceItem() { + return this.sourceItem; + } + + public void setSourceItem(String sourceItem) { + this.sourceItem = sourceItem; + } + + @Override + public Component clone() { + return new MinecartComponent(this.sourceItem); + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/npc/ActionMount.java b/src/com/hypixel/hytale/builtin/mounts/npc/ActionMount.java new file mode 100644 index 0000000..cb05d04 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/npc/ActionMount.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.builtin.mounts.npc; + +import com.hypixel.hytale.builtin.mounts.NPCMountComponent; +import com.hypixel.hytale.builtin.mounts.npc.builders.BuilderActionMount; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementConfig; +import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementManager; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.physics.component.PhysicsValues; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.systems.RoleChangeSystem; +import javax.annotation.Nonnull; + +public class ActionMount extends ActionBase { + public static final String EMPTY_ROLE_ID = "Empty_Role"; + protected final float anchorX; + protected final float anchorY; + protected final float anchorZ; + protected final String movementConfigId; + protected final int emptyRoleIndex; + + public ActionMount(@Nonnull BuilderActionMount builderActionMount, @Nonnull BuilderSupport builderSupport) { + super(builderActionMount); + this.anchorX = builderActionMount.getAnchorX(builderSupport); + this.anchorY = builderActionMount.getAnchorY(builderSupport); + this.anchorZ = builderActionMount.getAnchorZ(builderSupport); + this.movementConfigId = builderActionMount.getMovementConfig(builderSupport); + this.emptyRoleIndex = NPCPlugin.get().getIndex("Empty_Role"); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + Ref target = role.getStateSupport().getInteractionIterationTarget(); + boolean targetExists = target != null && !store.getArchetype(target).contains(DeathComponent.getComponentType()); + return super.canExecute(ref, role, sensorInfo, dt, store) && targetExists; + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + ComponentType mountComponentType = NPCMountComponent.getComponentType(); + NPCMountComponent mountComponent = store.getComponent(ref, mountComponentType); + if (mountComponent != null) { + return false; + } else { + mountComponent = store.ensureAndGetComponent(ref, mountComponentType); + mountComponent.setOriginalRoleIndex(NPCPlugin.get().getIndex(role.getRoleName())); + Ref playerReference = role.getStateSupport().getInteractionIterationTarget(); + if (playerReference == null) { + return false; + } else { + PlayerRef playerRefComponent = store.getComponent(playerReference, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + mountComponent.setOwnerPlayerRef(playerRefComponent); + mountComponent.setAnchor(this.anchorX, this.anchorY, this.anchorZ); + Player playerComponent = store.getComponent(playerReference, Player.getComponentType()); + + assert playerComponent != null; + + PhysicsValues playerPhysicsValues = store.getComponent(playerReference, PhysicsValues.getComponentType()); + RoleChangeSystem.requestRoleChange(ref, role, this.emptyRoleIndex, false, null, null, store); + MovementConfig movementConfig = MovementConfig.getAssetMap().getAsset(this.movementConfigId); + if (movementConfig != null) { + MovementManager movementManagerComponent = store.getComponent(playerReference, MovementManager.getComponentType()); + + assert movementManagerComponent != null; + + movementManagerComponent.setDefaultSettings(movementConfig.toPacket(), playerPhysicsValues, playerComponent.getGameMode()); + movementManagerComponent.applyDefaultSettings(); + movementManagerComponent.update(playerRefComponent.getPacketHandler()); + } + + return true; + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/mounts/npc/builders/BuilderActionMount.java b/src/com/hypixel/hytale/builtin/mounts/npc/builders/BuilderActionMount.java new file mode 100644 index 0000000..dac1592 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/mounts/npc/builders/BuilderActionMount.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.builtin.mounts.npc.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.builtin.mounts.npc.ActionMount; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.FloatHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public class BuilderActionMount extends BuilderActionBase { + protected final FloatHolder anchorX = new FloatHolder(); + protected final FloatHolder anchorY = new FloatHolder(); + protected final FloatHolder anchorZ = new FloatHolder(); + protected final StringHolder movementConfig = new StringHolder(); + + public BuilderActionMount() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Enable the player to mount the entity"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + public float getAnchorX(@Nonnull BuilderSupport support) { + return this.anchorX.get(support.getExecutionContext()); + } + + public float getAnchorY(@Nonnull BuilderSupport support) { + return this.anchorY.get(support.getExecutionContext()); + } + + public float getAnchorZ(@Nonnull BuilderSupport support) { + return this.anchorZ.get(support.getExecutionContext()); + } + + public String getMovementConfig(@Nonnull BuilderSupport support) { + return this.movementConfig.get(support.getExecutionContext()); + } + + @Nonnull + public ActionMount build(@Nonnull BuilderSupport builderSupport) { + return new ActionMount(this, builderSupport); + } + + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireFloat(data, "AnchorX", this.anchorX, null, BuilderDescriptorState.Stable, "The X anchor pos", null); + this.requireFloat(data, "AnchorY", this.anchorY, null, BuilderDescriptorState.Stable, "The Y anchor pos", null); + this.requireFloat(data, "AnchorZ", this.anchorZ, null, BuilderDescriptorState.Stable, "The Z anchor pos", null); + this.requireString(data, "MovementConfig", this.movementConfig, null, BuilderDescriptorState.Stable, "The MovementConfig to use for this mount", null); + return super.readConfig(data); + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/CombatActionEvaluatorSystems.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/CombatActionEvaluatorSystems.java new file mode 100644 index 0000000..fd02036 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/CombatActionEvaluatorSystems.java @@ -0,0 +1,418 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.config.CombatBalanceAsset; +import com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.CombatActionEvaluator; +import com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.CombatActionEvaluatorConfig; +import com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.combatactions.CombatActionOption; +import com.hypixel.hytale.builtin.npccombatactionevaluator.memory.DamageMemory; +import com.hypixel.hytale.builtin.npccombatactionevaluator.memory.TargetMemory; +import com.hypixel.hytale.builtin.npccombatactionevaluator.memory.TargetMemorySystems; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.config.balancing.BalanceAsset; +import com.hypixel.hytale.server.npc.decisionmaker.core.EvaluationContext; +import com.hypixel.hytale.server.npc.decisionmaker.core.Evaluator; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.StateSupport; +import com.hypixel.hytale.server.npc.systems.BalancingInitialisationSystem; +import com.hypixel.hytale.server.npc.systems.RoleSystems; +import com.hypixel.hytale.server.npc.valuestore.ValueStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CombatActionEvaluatorSystems { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + + public CombatActionEvaluatorSystems() { + } + + public static class CombatConstructionData implements Component { + protected String combatState; + protected int markedTargetSlot; + protected int minRangeSlot; + protected int maxRangeSlot; + protected int positioningAngleSlot; + + public CombatConstructionData() { + } + + public static ComponentType getComponentType() { + return NPCCombatActionEvaluatorPlugin.get().getCombatConstructionDataComponentType(); + } + + public String getCombatState() { + return this.combatState; + } + + public void setCombatState(String state) { + if (this.combatState != null && !this.combatState.equals(state)) { + throw new IllegalStateException("Cannot have more than one combat state in an NPC!"); + } else { + this.combatState = state; + } + } + + public int getMarkedTargetSlot() { + return this.markedTargetSlot; + } + + public void setMarkedTargetSlot(int markedTargetSlot) { + this.markedTargetSlot = markedTargetSlot; + } + + public int getMinRangeSlot() { + return this.minRangeSlot; + } + + public void setMinRangeSlot(int minRangeSlot) { + this.minRangeSlot = minRangeSlot; + } + + public int getMaxRangeSlot() { + return this.maxRangeSlot; + } + + public void setMaxRangeSlot(int maxRangeSlot) { + this.maxRangeSlot = maxRangeSlot; + } + + public int getPositioningAngleSlot() { + return this.positioningAngleSlot; + } + + public void setPositioningAngleSlot(int positioningAngleSlot) { + this.positioningAngleSlot = positioningAngleSlot; + } + + @Nonnull + @Override + public Component clone() { + CombatActionEvaluatorSystems.CombatConstructionData data = new CombatActionEvaluatorSystems.CombatConstructionData(); + data.combatState = this.combatState; + data.markedTargetSlot = this.markedTargetSlot; + data.minRangeSlot = this.minRangeSlot; + data.maxRangeSlot = this.maxRangeSlot; + data.positioningAngleSlot = this.positioningAngleSlot; + return data; + } + } + + public static class EvaluatorTick extends EntityTickingSystem { + private final ComponentType componentType; + private final ComponentType targetMemoryComponentType; + private final ComponentType damageMemoryComponentType; + @Nullable + private final ComponentType npcComponentType; + @Nonnull + private final ComponentType playerComponentType; + private final ComponentType valueStoreComponentType; + private final ComponentType transformComponentType; + private final Query query; + @Nonnull + private final Set> dependencies; + + public EvaluatorTick( + ComponentType componentType, + ComponentType targetMemoryComponentType, + ComponentType damageMemoryComponentType + ) { + this.componentType = componentType; + this.targetMemoryComponentType = targetMemoryComponentType; + this.damageMemoryComponentType = damageMemoryComponentType; + this.npcComponentType = NPCEntity.getComponentType(); + this.playerComponentType = Player.getComponentType(); + this.valueStoreComponentType = ValueStore.getComponentType(); + this.transformComponentType = TransformComponent.getComponentType(); + this.query = Archetype.of(componentType, targetMemoryComponentType, this.npcComponentType, this.valueStoreComponentType, this.transformComponentType); + this.dependencies = Set.of( + new SystemDependency<>(Order.BEFORE, RoleSystems.PreBehaviourSupportTickSystem.class), + new SystemDependency<>(Order.AFTER, TargetMemorySystems.Ticking.class) + ); + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return false; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + NPCEntity npcComponent = archetypeChunk.getComponent(index, this.npcComponentType); + + assert npcComponent != null; + + Role role = npcComponent.getRole(); + if (!archetypeChunk.getArchetype().contains(DeathComponent.getComponentType())) { + CombatActionEvaluator evaluatorComponent = archetypeChunk.getComponent(index, this.componentType); + + assert evaluatorComponent != null; + + evaluatorComponent.tickBasicAttackCoolDown(dt); + StateSupport stateSupport = role.getStateSupport(); + int currentState = stateSupport.getStateIndex(); + if (currentState != evaluatorComponent.getRunInState()) { + if (evaluatorComponent.getCurrentAction() != null) { + evaluatorComponent.completeCurrentAction(true, true); + evaluatorComponent.clearPrimaryTarget(); + role.getMarkedEntitySupport().clearMarkedEntity(evaluatorComponent.getMarkedTargetSlot()); + HytaleLogger.Api context = CombatActionEvaluatorSystems.LOGGER.at(Level.FINEST); + if (context.isEnabled()) { + context.log("%s: Leaving combat", archetypeChunk.getReferenceTo(index)); + } + } + + DamageMemory damageMemory = archetypeChunk.getComponent(index, this.damageMemoryComponentType); + if (damageMemory != null) { + damageMemory.clearTotalDamage(); + } + } else if (!role.getCombatSupport().isExecutingAttack()) { + ValueStore valueStoreComponent = archetypeChunk.getComponent(index, this.valueStoreComponentType); + + assert valueStoreComponent != null; + + double[] postExecutionDistanceRange = evaluatorComponent.consumePostExecutionDistanceRange(); + if (postExecutionDistanceRange != null) { + valueStoreComponent.storeDouble(evaluatorComponent.getMinRangeSlot(), postExecutionDistanceRange[0]); + valueStoreComponent.storeDouble(evaluatorComponent.getMaxRangeSlot(), postExecutionDistanceRange[1]); + } + + int currentSubState = stateSupport.getSubStateIndex(); + CombatActionEvaluatorConfig.BasicAttacks basicAttacks = evaluatorComponent.getBasicAttacks(currentSubState); + if (basicAttacks != null) { + evaluatorComponent.setCurrentBasicAttackSet(currentSubState, basicAttacks); + String currentBasicAttack = evaluatorComponent.getCurrentBasicAttack(); + if (currentBasicAttack != null) { + if (!evaluatorComponent.tickBasicAttackTimeout(dt)) { + role.getMarkedEntitySupport().setMarkedEntity(evaluatorComponent.getMarkedTargetSlot(), evaluatorComponent.getBasicAttackTarget()); + return; + } + + evaluatorComponent.clearCurrentBasicAttack(); + HytaleLogger.Api context = CombatActionEvaluatorSystems.LOGGER.at(Level.FINEST); + if (context.isEnabled()) { + context.log("%s: Basic attack timed out", archetypeChunk.getReferenceTo(index)); + } + } + + if (evaluatorComponent.canUseBasicAttack(index, archetypeChunk, commandBuffer)) { + MotionController activeMotionController = role.getActiveMotionController(); + TransformComponent transformComponent = archetypeChunk.getComponent(index, this.transformComponentType); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + Ref targetRef = null; + CombatActionEvaluator.CombatOptionHolder currentAction = evaluatorComponent.getCurrentAction(); + if (currentAction == null || ((CombatActionOption)currentAction.getOption()).getActionTarget() != CombatActionOption.Target.Friendly) { + Ref primaryTargetRef = evaluatorComponent.getPrimaryTarget(); + if (primaryTargetRef != null && primaryTargetRef.isValid()) { + TransformComponent targetTransformComponent = commandBuffer.getComponent(primaryTargetRef, this.transformComponentType); + + assert targetTransformComponent != null; + + Vector3d targetPosition = targetTransformComponent.getPosition(); + if (activeMotionController.getSquaredDistance(position, targetPosition, basicAttacks.shouldUseProjectedDistance()) + < basicAttacks.getMaxRangeSquared()) { + targetRef = primaryTargetRef; + } + } + } + + if (targetRef == null) { + TargetMemory targetMemoryComponent = archetypeChunk.getComponent(index, this.targetMemoryComponentType); + + assert targetMemoryComponent != null; + + targetRef = targetMemoryComponent.getClosestHostile(); + } + + if (targetRef != null) { + TransformComponent targetTransformComponentx = commandBuffer.getComponent(targetRef, this.transformComponentType); + + assert targetTransformComponentx != null; + + Vector3d targetPosition = targetTransformComponentx.getPosition(); + if (activeMotionController.getSquaredDistance(position, targetPosition, basicAttacks.shouldUseProjectedDistance()) + < basicAttacks.getMaxRangeSquared()) { + evaluatorComponent.setBasicAttackTarget(targetRef); + role.getMarkedEntitySupport().setMarkedEntity(evaluatorComponent.getMarkedTargetSlot(), targetRef); + String[] basicAttackOptions = basicAttacks.getAttacks(); + String attack; + if (basicAttacks.isRandom()) { + attack = basicAttackOptions[RandomExtra.randomRange(basicAttackOptions.length)]; + } else { + int nextAttackIndex = evaluatorComponent.getNextBasicAttackIndex(); + attack = basicAttackOptions[nextAttackIndex]; + if (++nextAttackIndex >= basicAttackOptions.length) { + nextAttackIndex = 0; + } + + evaluatorComponent.setNextBasicAttackIndex(nextAttackIndex); + } + + evaluatorComponent.setCurrentBasicAttack(attack, basicAttacks.isDamageFriendlies(), basicAttacks::getInteractionVars); + evaluatorComponent.setBasicAttackTimeout(basicAttacks.getTimeout()); + HytaleLogger.Api context = CombatActionEvaluatorSystems.LOGGER.at(Level.FINEST); + if (context.isEnabled()) { + context.log("%s: Started basic attack %s", archetypeChunk.getReferenceTo(index), attack); + } + } + } + } + } else { + evaluatorComponent.setCurrentBasicAttackSet(currentSubState, null); + } + + CombatActionEvaluator.CombatOptionHolder currentActionx = evaluatorComponent.getCurrentAction(); + if (currentActionx != null) { + if (((CombatActionOption)currentActionx.getOption()).getActionTarget() == CombatActionOption.Target.Self) { + return; + } + + if (!evaluatorComponent.hasTimedOut(dt)) { + Ref targetRefx = evaluatorComponent.getPrimaryTarget(); + if (targetRefx != null && targetRefx.isValid() && !commandBuffer.getArchetype(targetRefx).contains(DeathComponent.getComponentType())) { + Player targetPlayerComponent = commandBuffer.getComponent(targetRefx, this.playerComponentType); + if (targetPlayerComponent == null || targetPlayerComponent.getGameMode() == GameMode.Adventure) { + role.getMarkedEntitySupport().setMarkedEntity(evaluatorComponent.getMarkedTargetSlot(), targetRefx); + return; + } + } + } + + evaluatorComponent.terminateCurrentAction(); + evaluatorComponent.clearPrimaryTarget(); + role.getMarkedEntitySupport().clearMarkedEntity(evaluatorComponent.getMarkedTargetSlot()); + HytaleLogger.Api context = CombatActionEvaluatorSystems.LOGGER.at(Level.FINEST); + if (context.isEnabled()) { + context.log("%s: Lost current action target or timed out", archetypeChunk.getReferenceTo(index)); + } + } + + if (evaluatorComponent.getOptionsBySubState().containsKey(currentSubState)) { + EvaluationContext evaluationContext = evaluatorComponent.getEvaluationContext(); + double minRunUtility = evaluatorComponent.getMinRunUtility(); + evaluationContext.setMinimumUtility(minRunUtility); + evaluationContext.setMinimumWeightCoefficient(0.0); + evaluationContext.setLastUsedNanos(evaluatorComponent.getLastRunNanos()); + CombatActionEvaluator.RunOption runOption = evaluatorComponent.getRunOption(); + double utility = runOption.calculateUtility(index, archetypeChunk, evaluatorComponent.getPrimaryTarget(), commandBuffer, evaluationContext); + evaluationContext.reset(); + if (!(utility < minRunUtility)) { + Int2ObjectMap.OptionHolder>> optionLists = evaluatorComponent.getOptionsBySubState(); + List.OptionHolder> currentStateOptions = optionLists.get(currentSubState); + evaluatorComponent.setActiveOptions(currentStateOptions); + evaluatorComponent.selectNextCombatAction(index, archetypeChunk, commandBuffer, role, valueStoreComponent); + evaluatorComponent.setLastRunNanos(System.nanoTime()); + DamageMemory damageMemory = archetypeChunk.getComponent(index, this.damageMemoryComponentType); + if (damageMemory != null) { + damageMemory.clearRecentDamage(); + } + + HytaleLogger.Api context = CombatActionEvaluatorSystems.LOGGER.at(Level.FINEST); + if (context.isEnabled()) { + context.log("%s: Has run the combat action evaluator", archetypeChunk.getReferenceTo(index)); + } + } + } + } + } + } + } + + public static class OnAdded extends HolderSystem { + @Nullable + private final ComponentType componentType = NPCEntity.getComponentType(); + private final ComponentType combatConstructionDataComponentType; + @Nonnull + private final Set> dependencies; + @Nonnull + private final Query query; + + public OnAdded(ComponentType combatConstructionDataComponentType) { + this.combatConstructionDataComponentType = combatConstructionDataComponentType; + this.dependencies = Set.of(new SystemDependency<>(Order.AFTER, BalancingInitialisationSystem.class)); + this.query = Query.and(combatConstructionDataComponentType, combatConstructionDataComponentType); + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + Role role = holder.getComponent(this.componentType).getRole(); + if (role.getBalanceAsset() != null) { + BalanceAsset balancingAsset = BalanceAsset.getAssetMap().getAsset(role.getBalanceAsset()); + if (balancingAsset instanceof CombatBalanceAsset combatBalance) { + CombatActionEvaluatorSystems.CombatConstructionData constructionData = holder.getComponent(this.combatConstructionDataComponentType); + CombatActionEvaluator evaluator = new CombatActionEvaluator(role, combatBalance.getEvaluatorConfig(), constructionData); + evaluator.setupNPC(holder); + role.getPositionCache().addExternalPositionCacheRegistration(evaluator::setupNPC); + holder.putComponent(TargetMemory.getComponentType(), new TargetMemory(combatBalance.getTargetMemoryDuration())); + holder.putComponent(CombatActionEvaluator.getComponentType(), evaluator); + holder.ensureComponent(InteractionModule.get().getChainingDataComponent()); + holder.removeComponent(this.combatConstructionDataComponentType); + } + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/NPCCombatActionEvaluatorPlugin.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/NPCCombatActionEvaluatorPlugin.java new file mode 100644 index 0000000..bda41a7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/NPCCombatActionEvaluatorPlugin.java @@ -0,0 +1,111 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.builtin.npccombatactionevaluator.conditions.RecentSustainedDamageCondition; +import com.hypixel.hytale.builtin.npccombatactionevaluator.conditions.TargetMemoryCountCondition; +import com.hypixel.hytale.builtin.npccombatactionevaluator.conditions.TotalSustainedDamageCondition; +import com.hypixel.hytale.builtin.npccombatactionevaluator.config.CombatBalanceAsset; +import com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.builders.BuilderActionAddToTargetMemory; +import com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.builders.BuilderActionCombatAbility; +import com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.builders.BuilderCombatTargetCollector; +import com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.builders.BuilderSensorCombatActionEvaluator; +import com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.builders.BuilderSensorHasHostileTargetMemory; +import com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.CombatActionEvaluator; +import com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.combatactions.CombatActionOption; +import com.hypixel.hytale.builtin.npccombatactionevaluator.memory.DamageMemory; +import com.hypixel.hytale.builtin.npccombatactionevaluator.memory.DamageMemorySystems; +import com.hypixel.hytale.builtin.npccombatactionevaluator.memory.TargetMemory; +import com.hypixel.hytale.builtin.npccombatactionevaluator.memory.TargetMemorySystems; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.config.balancing.BalanceAsset; +import com.hypixel.hytale.server.npc.decisionmaker.core.conditions.base.Condition; +import javax.annotation.Nonnull; + +public class NPCCombatActionEvaluatorPlugin extends JavaPlugin { + public static final String CAE_MARKED_TARGET_SLOT = "CAETargetSlot"; + public static final String CAE_MIN_RANGE_PARAMETER = "CAEMinRange"; + public static final String CAE_MAX_RANGE_PARAMETER = "CAEMaxRange"; + public static final String CAE_POSITIONING_ANGLE_PARAMETER = "CAEPositioningAngle"; + private static NPCCombatActionEvaluatorPlugin instance; + private ComponentType targetMemoryComponentType; + private ComponentType combatActionEvaluatorComponentType; + private ComponentType combatConstructionDataComponentType; + private ComponentType damageMemoryComponentType; + + public static NPCCombatActionEvaluatorPlugin get() { + return instance; + } + + public NPCCombatActionEvaluatorPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + instance = this; + BalanceAsset.CODEC.register("CombatActionEvaluator", CombatBalanceAsset.class, CombatBalanceAsset.CODEC); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + CombatActionOption.class, new IndexedLookupTableAssetMap<>(CombatActionOption[]::new) + ) + .setPath("NPC/DecisionMaking/CombatActions")) + .setCodec(CombatActionOption.CODEC)) + .setKeyFunction(CombatActionOption::getId)) + .setReplaceOnRemove(CombatActionOption::getNothingFor)) + .loadsAfter(Item.class, Condition.class, RootInteraction.class)) + .loadsBefore(BalanceAsset.class)) + .build() + ); + NPCPlugin.get() + .registerCoreComponentType("CombatActionEvaluator", BuilderSensorCombatActionEvaluator::new) + .registerCoreComponentType("CombatTargets", BuilderCombatTargetCollector::new) + .registerCoreComponentType("HasHostileTargetMemory", BuilderSensorHasHostileTargetMemory::new) + .registerCoreComponentType("CombatAbility", BuilderActionCombatAbility::new) + .registerCoreComponentType("AddToHostileTargetMemory", BuilderActionAddToTargetMemory::new); + this.targetMemoryComponentType = this.getEntityStoreRegistry().registerComponent(TargetMemory.class, () -> { + throw new UnsupportedOperationException("Not implemented"); + }); + this.combatActionEvaluatorComponentType = this.getEntityStoreRegistry().registerComponent(CombatActionEvaluator.class, () -> { + throw new UnsupportedOperationException("Not implemented"); + }); + this.combatConstructionDataComponentType = this.getEntityStoreRegistry() + .registerComponent(CombatActionEvaluatorSystems.CombatConstructionData.class, CombatActionEvaluatorSystems.CombatConstructionData::new); + this.damageMemoryComponentType = this.getEntityStoreRegistry().registerComponent(DamageMemory.class, DamageMemory::new); + this.getEntityStoreRegistry().registerSystem(new TargetMemorySystems.Ticking(this.targetMemoryComponentType)); + this.getEntityStoreRegistry() + .registerSystem( + new CombatActionEvaluatorSystems.EvaluatorTick( + this.combatActionEvaluatorComponentType, this.targetMemoryComponentType, this.damageMemoryComponentType + ) + ); + this.getEntityStoreRegistry().registerSystem(new DamageMemorySystems.CollectDamage(this.damageMemoryComponentType)); + this.getEntityStoreRegistry().registerSystem(new CombatActionEvaluatorSystems.OnAdded(this.combatConstructionDataComponentType)); + Condition.CODEC.register("RecentSustainedDamage", RecentSustainedDamageCondition.class, RecentSustainedDamageCondition.CODEC); + Condition.CODEC.register("TotalSustainedDamage", TotalSustainedDamageCondition.class, TotalSustainedDamageCondition.CODEC); + Condition.CODEC.register("KnownTargetCount", TargetMemoryCountCondition.class, TargetMemoryCountCondition.CODEC); + } + + public ComponentType getTargetMemoryComponentType() { + return this.targetMemoryComponentType; + } + + public ComponentType getCombatActionEvaluatorComponentType() { + return this.combatActionEvaluatorComponentType; + } + + public ComponentType getCombatConstructionDataComponentType() { + return this.combatConstructionDataComponentType; + } + + public ComponentType getDamageMemoryComponentType() { + return this.damageMemoryComponentType; + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/Positioning.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/Positioning.java new file mode 100644 index 0000000..6270aec --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/Positioning.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator; + +public enum Positioning { + Any, + Front, + Behind, + Flank; + + private Positioning() { + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/conditions/RecentSustainedDamageCondition.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/conditions/RecentSustainedDamageCondition.java new file mode 100644 index 0000000..54af585 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/conditions/RecentSustainedDamageCondition.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.conditions; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.memory.DamageMemory; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.decisionmaker.core.EvaluationContext; +import com.hypixel.hytale.server.npc.decisionmaker.core.conditions.base.ScaledCurveCondition; +import javax.annotation.Nonnull; + +public class RecentSustainedDamageCondition extends ScaledCurveCondition { + public static final BuilderCodec CODEC = BuilderCodec.builder( + RecentSustainedDamageCondition.class, RecentSustainedDamageCondition::new, ScaledCurveCondition.ABSTRACT_CODEC + ) + .documentation("A scaled curve condition that returns a utility value based on damage taken since the combat action evaluator was last run.") + .build(); + protected static final ComponentType DAMAGE_MEMORY_COMPONENT_TYPE = DamageMemory.getComponentType(); + + public RecentSustainedDamageCondition() { + } + + @Override + protected double getInput( + int selfIndex, + @Nonnull ArchetypeChunk archetypeChunk, + Ref target, + CommandBuffer commandBuffer, + EvaluationContext context + ) { + DamageMemory memory = archetypeChunk.getComponent(selfIndex, DAMAGE_MEMORY_COMPONENT_TYPE); + return memory.getRecentDamage(); + } + + @Override + public void setupNPC(@Nonnull Holder holder) { + holder.ensureComponent(DAMAGE_MEMORY_COMPONENT_TYPE); + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/conditions/TargetMemoryCountCondition.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/conditions/TargetMemoryCountCondition.java new file mode 100644 index 0000000..4dfc1da --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/conditions/TargetMemoryCountCondition.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.conditions; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.memory.TargetMemory; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.decisionmaker.core.EvaluationContext; +import com.hypixel.hytale.server.npc.decisionmaker.core.conditions.base.ScaledCurveCondition; +import javax.annotation.Nonnull; + +public class TargetMemoryCountCondition extends ScaledCurveCondition { + public static final EnumCodec TARGET_TYPE_CODEC = new EnumCodec<>(TargetMemoryCountCondition.TargetType.class) + .documentKey(TargetMemoryCountCondition.TargetType.All, "All known targets.") + .documentKey(TargetMemoryCountCondition.TargetType.Friendly, "Known friendly targets.") + .documentKey(TargetMemoryCountCondition.TargetType.Hostile, "Known hostile targets."); + public static final BuilderCodec CODEC = BuilderCodec.builder( + TargetMemoryCountCondition.class, TargetMemoryCountCondition::new, ScaledCurveCondition.ABSTRACT_CODEC + ) + .documentation("A scaled curve condition that returns a utility value based on the number of known targets in the memory.") + .appendInherited( + new KeyedCodec<>("TargetType", TARGET_TYPE_CODEC), + (condition, e) -> condition.targetType = e, + condition -> condition.targetType, + (condition, parent) -> condition.targetType = parent.targetType + ) + .documentation("The type of targets to count.") + .add() + .build(); + protected static final ComponentType TARGET_MEMORY_COMPONENT_TYPE = TargetMemory.getComponentType(); + protected TargetMemoryCountCondition.TargetType targetType = TargetMemoryCountCondition.TargetType.Hostile; + + public TargetMemoryCountCondition() { + } + + @Override + protected double getInput( + int selfIndex, + @Nonnull ArchetypeChunk archetypeChunk, + Ref target, + CommandBuffer commandBuffer, + EvaluationContext context + ) { + TargetMemory memory = archetypeChunk.getComponent(selfIndex, TARGET_MEMORY_COMPONENT_TYPE); + + return switch (this.targetType) { + case Hostile -> memory.getKnownHostiles().size(); + case Friendly -> memory.getKnownFriendlies().size(); + case All -> memory.getKnownFriendlies().size() + memory.getKnownHostiles().size(); + }; + } + + private static enum TargetType { + Hostile, + Friendly, + All; + + private TargetType() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/conditions/TotalSustainedDamageCondition.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/conditions/TotalSustainedDamageCondition.java new file mode 100644 index 0000000..a970dab --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/conditions/TotalSustainedDamageCondition.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.conditions; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.memory.DamageMemory; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.decisionmaker.core.EvaluationContext; +import com.hypixel.hytale.server.npc.decisionmaker.core.conditions.base.ScaledCurveCondition; +import javax.annotation.Nonnull; + +public class TotalSustainedDamageCondition extends ScaledCurveCondition { + public static final BuilderCodec CODEC = BuilderCodec.builder( + TotalSustainedDamageCondition.class, TotalSustainedDamageCondition::new, ScaledCurveCondition.ABSTRACT_CODEC + ) + .documentation("A scaled curve condition that returns a utility value based on total damage taken during this bout of combat.") + .build(); + protected static final ComponentType DAMAGE_MEMORY_COMPONENT_TYPE = DamageMemory.getComponentType(); + + public TotalSustainedDamageCondition() { + } + + @Override + protected double getInput( + int selfIndex, + @Nonnull ArchetypeChunk archetypeChunk, + Ref target, + CommandBuffer commandBuffer, + EvaluationContext context + ) { + DamageMemory memory = archetypeChunk.getComponent(selfIndex, DAMAGE_MEMORY_COMPONENT_TYPE); + return memory.getTotalCombatDamage(); + } + + @Override + public void setupNPC(@Nonnull Holder holder) { + holder.ensureComponent(DAMAGE_MEMORY_COMPONENT_TYPE); + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/config/CombatBalanceAsset.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/config/CombatBalanceAsset.java new file mode 100644 index 0000000..d96696b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/config/CombatBalanceAsset.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.config; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.CombatActionEvaluatorConfig; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.npc.config.balancing.BalanceAsset; +import javax.annotation.Nonnull; + +public class CombatBalanceAsset extends BalanceAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(CombatBalanceAsset.class, CombatBalanceAsset::new, ABSTRACT_CODEC) + .documentation("A balance asset which also configures a combat action evaluator.") + .appendInherited( + new KeyedCodec<>("TargetMemoryDuration", Codec.FLOAT), + (balanceAsset, f) -> balanceAsset.targetMemoryDuration = f, + balanceAsset -> balanceAsset.targetMemoryDuration, + (balanceAsset, parent) -> balanceAsset.targetMemoryDuration = parent.targetMemoryDuration + ) + .documentation("How long the target should remain in the NPCs list of potential targets after last being spotted") + .addValidator(Validators.greaterThan(0.0F)) + .add() + .appendInherited( + new KeyedCodec<>("CombatActionEvaluator", CombatActionEvaluatorConfig.CODEC), + (balanceAsset, o) -> balanceAsset.evaluatorConfig = o, + balanceAsset -> balanceAsset.evaluatorConfig, + (balanceAsset, parent) -> balanceAsset.targetMemoryDuration = parent.targetMemoryDuration + ) + .addValidator(Validators.nonNull()) + .documentation("The combat action evaluator complete with combat action definitions and conditions.") + .add() + .build(); + protected float targetMemoryDuration = 15.0F; + protected CombatActionEvaluatorConfig evaluatorConfig; + + public CombatBalanceAsset() { + } + + public float getTargetMemoryDuration() { + return this.targetMemoryDuration; + } + + public CombatActionEvaluatorConfig getEvaluatorConfig() { + return this.evaluatorConfig; + } + + @Nonnull + @Override + public String toString() { + return "CombatBalanceAsset{TargetMemoryDuration='" + this.targetMemoryDuration + "'}"; + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/ActionAddToTargetMemory.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/ActionAddToTargetMemory.java new file mode 100644 index 0000000..a1c6503 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/ActionAddToTargetMemory.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.builders.BuilderActionAddToTargetMemory; +import com.hypixel.hytale.builtin.npccombatactionevaluator.memory.TargetMemory; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionAddToTargetMemory extends ActionBase { + private static final ComponentType TARGET_MEMORY = TargetMemory.getComponentType(); + + public ActionAddToTargetMemory(@Nonnull BuilderActionAddToTargetMemory builder) { + super(builder); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && sensorInfo != null && sensorInfo.hasPosition(); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, @Nonnull InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + TargetMemory targetMemory = ref.getStore().getComponent(ref, TARGET_MEMORY); + if (targetMemory == null) { + return true; + } else { + Ref target = sensorInfo.getPositionProvider().getTarget(); + Int2FloatOpenHashMap hostiles = targetMemory.getKnownHostiles(); + if (hostiles.put(target.getIndex(), targetMemory.getRememberFor()) <= 0.0F) { + targetMemory.getKnownHostilesList().add(target); + } + + return true; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/ActionCombatAbility.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/ActionCombatAbility.java new file mode 100644 index 0000000..65319fc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/ActionCombatAbility.java @@ -0,0 +1,206 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.builders.BuilderActionCombatAbility; +import com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.CombatActionEvaluator; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.SingleCollector; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.modules.projectile.config.BallisticData; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.combat.ActionAttack; +import com.hypixel.hytale.server.npc.interactions.NPCInteractionSimulationHandler; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.sensorinfo.parameterproviders.DoubleParameterProvider; +import com.hypixel.hytale.server.npc.sensorinfo.parameterproviders.ParameterProvider; +import com.hypixel.hytale.server.npc.util.AimingData; +import com.hypixel.hytale.server.npc.util.NPCPhysicsMath; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionCombatAbility extends ActionBase { + protected static final ComponentType COMPONENT_TYPE = CombatActionEvaluator.getComponentType(); + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected static final float POSITIONING_ANGLE_THRESHOLD = 0.08726646F; + protected final int id; + protected final int positioningAngleProviderSlot; + protected final double meleeConeAngle = (float) (Math.PI / 12); + @Nullable + protected String attack; + protected DoubleParameterProvider cachedPositioningAngleProvider; + protected boolean initialised; + + public ActionCombatAbility(@Nonnull BuilderActionCombatAbility builder, @Nonnull BuilderSupport builderSupport) { + super(builder); + this.id = builderSupport.getNextAttackIndex(); + this.positioningAngleProviderSlot = builderSupport.getParameterSlot("PositioningAngle"); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, double dt, @Nonnull Store store) { + if (!this.initialised) { + if (sensorInfo != null) { + ParameterProvider parameterProvider = sensorInfo.getParameterProvider(this.positioningAngleProviderSlot); + if (parameterProvider instanceof DoubleParameterProvider) { + this.cachedPositioningAngleProvider = (DoubleParameterProvider)parameterProvider; + } + } + + this.initialised = true; + } + + if (!super.canExecute(ref, role, sensorInfo, dt, store)) { + return false; + } else { + CombatActionEvaluator combatActionEvaluator = ref.getStore().getComponent(ref, COMPONENT_TYPE); + return combatActionEvaluator == null ? false : combatActionEvaluator.getCurrentAttack() != null; + } + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, double dt, @Nonnull Store store) { + CombatActionEvaluator combatActionEvaluatorComponent = store.getComponent(ref, COMPONENT_TYPE); + + assert combatActionEvaluatorComponent != null; + + InteractionManager interactionManagerComponent = store.getComponent(ref, InteractionModule.get().getInteractionManagerComponent()); + + assert interactionManagerComponent != null; + + AimingData aimingDataInfo = sensorInfo != null ? sensorInfo.getPassedExtraInfo(AimingData.class) : null; + AimingData aimingData = aimingDataInfo != null && aimingDataInfo.isClaimedBy(this.id) ? aimingDataInfo : null; + boolean requireAiming = combatActionEvaluatorComponent.requiresAiming(); + String nextAttack = combatActionEvaluatorComponent.getCurrentAttack(); + if (!nextAttack.equals(this.attack)) { + this.attack = nextAttack; + if (requireAiming && aimingData != null) { + SingleCollector collector = ActionAttack.THREAD_LOCAL_COLLECTOR.get(); + interactionManagerComponent.walkChain(ref, collector, InteractionType.Primary, RootInteraction.getAssetMap().getAsset(this.attack), store); + BallisticData ballisticData = collector.getResult(); + if (ballisticData != null) { + aimingData.requireBallistic(ballisticData); + aimingData.setUseFlatTrajectory(true); + } else { + double chargeDistance = combatActionEvaluatorComponent.getChargeDistance(); + if (chargeDistance > 0.0) { + aimingData.setChargeDistance(chargeDistance); + aimingData.setDesiredHitAngle((float) (Math.PI / 12)); + } + + aimingData.requireCloseCombat(); + } + + return false; + } + } + + TransformComponent transformComponent = store.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3f rotation = aimingData != null && aimingData.getChargeDistance() > 0.0 ? transformComponent.getRotation() : headRotationComponent.getRotation(); + if (aimingData != null && !aimingData.isOnTarget(rotation.getYaw(), rotation.getPitch(), (float) (Math.PI / 12))) { + aimingData.clearSolution(); + return false; + } else { + Ref target = aimingData != null ? aimingData.getTarget() : null; + if (!requireAiming || target != null && role.getPositionCache().hasLineOfSight(ref, target, store)) { + if (combatActionEvaluatorComponent.shouldPositionFirst()) { + double positioningAngle = Double.MAX_VALUE; + if (this.cachedPositioningAngleProvider != null) { + positioningAngle = this.cachedPositioningAngleProvider.getDoubleParameter(); + } + + if (positioningAngle != Double.MAX_VALUE) { + if (target == null) { + return false; + } + + TransformComponent targetTransformComponent = store.getComponent(target, TRANSFORM_COMPONENT_TYPE); + + assert targetTransformComponent != null; + + Vector3d targetPosition = targetTransformComponent.getPosition(); + float selfYaw = NPCPhysicsMath.lookatHeading(transformComponent.getPosition(), targetPosition, transformComponent.getRotation().getYaw()); + float difference = PhysicsMath.normalizeTurnAngle(targetTransformComponent.getRotation().getYaw() - selfYaw - (float)positioningAngle); + if (Math.abs(difference) > 0.08726646F) { + return false; + } + } + } + + boolean damageFriendlies = combatActionEvaluatorComponent.shouldDamageFriendlies(); + if (!damageFriendlies && target != null && role.getPositionCache().isFriendlyBlockingLineOfSight(ref, target, store)) { + aimingData.clearSolution(); + return false; + } else { + if (interactionManagerComponent.getInteractionSimulationHandler() instanceof NPCInteractionSimulationHandler npcInteractionSimulationHandler) { + npcInteractionSimulationHandler.requestChargeTime(combatActionEvaluatorComponent.getChargeFor()); + } + + InteractionType interactionType = combatActionEvaluatorComponent.getCurrentInteractionType(); + InteractionContext context = InteractionContext.forInteraction(interactionManagerComponent, ref, interactionType, store); + context.setInteractionVarsGetter(combatActionEvaluatorComponent.getCurrentInteractionVarsGetter()); + InteractionChain chain = interactionManagerComponent.initChain( + interactionType, context, RootInteraction.getRootInteractionOrUnknown(this.attack), false + ); + interactionManagerComponent.queueExecuteChain(chain); + role.getCombatSupport().setExecutingAttack(chain, damageFriendlies, 0.0); + if (aimingData != null) { + aimingData.setHaveAttacked(true); + } + + combatActionEvaluatorComponent.completeCurrentAction(false, true); + this.attack = null; + return true; + } + } else { + if (aimingData != null) { + aimingData.clearSolution(); + } + + return false; + } + } + } + + @Override + public void activate(Role role, @Nullable InfoProvider infoProvider) { + super.activate(role, infoProvider); + if (infoProvider != null) { + AimingData aimingData = infoProvider.getPassedExtraInfo(AimingData.class); + if (aimingData != null) { + aimingData.tryClaim(this.id); + } + } + } + + @Override + public void deactivate(Role role, @Nullable InfoProvider infoProvider) { + super.deactivate(role, infoProvider); + if (infoProvider != null) { + AimingData aimingData = infoProvider.getPassedExtraInfo(AimingData.class); + if (aimingData != null) { + aimingData.release(); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/CombatTargetCollector.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/CombatTargetCollector.java new file mode 100644 index 0000000..e323408 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/CombatTargetCollector.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.memory.TargetMemory; +import com.hypixel.hytale.builtin.npccombatactionevaluator.memory.TargetMemorySystems; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.ISensorEntityCollector; +import com.hypixel.hytale.server.npc.role.Role; +import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CombatTargetCollector implements ISensorEntityCollector { + private static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + @Nullable + private Role role; + @Nullable + private TargetMemory targetMemory; + private double closestHostileDistanceSquared = Double.MAX_VALUE; + + public CombatTargetCollector() { + } + + @Override + public void registerWithSupport(@Nonnull Role role) { + role.getWorldSupport().requireAttitudeCache(); + } + + @Override + public void init(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + this.targetMemory = componentAccessor.getComponent(ref, TargetMemory.getComponentType()); + this.role = role; + } + + @Override + public void collectMatching(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor) { + if (this.targetMemory != null) { + Attitude attitude = this.role.getWorldSupport().getAttitude(ref, targetRef, componentAccessor); + switch (attitude) { + case IGNORE: + case NEUTRAL: + default: + break; + case HOSTILE: + Int2FloatOpenHashMap hostiles = this.targetMemory.getKnownHostiles(); + if (hostiles.put(targetRef.getIndex(), this.targetMemory.getRememberFor()) <= 0.0F) { + this.targetMemory.getKnownHostilesList().add(targetRef); + HytaleLogger.Api context = TargetMemorySystems.LOGGER.at(Level.FINE); + if (context.isEnabled()) { + context.log("%s: Registered new hostile %s", ref, targetRef); + } + } + + TransformComponent transformComponent = componentAccessor.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + Vector3d selfPos = transformComponent.getPosition(); + TransformComponent targetTransformComponent = componentAccessor.getComponent(targetRef, TRANSFORM_COMPONENT_TYPE); + + assert targetTransformComponent != null; + + Vector3d targetPos = targetTransformComponent.getPosition(); + double distanceSquared = selfPos.distanceSquaredTo(targetPos); + if (distanceSquared < this.closestHostileDistanceSquared) { + this.targetMemory.setClosestHostile(targetRef); + this.closestHostileDistanceSquared = distanceSquared; + } + break; + case FRIENDLY: + case REVERED: + Int2FloatOpenHashMap friendlies = this.targetMemory.getKnownFriendlies(); + if (friendlies.put(targetRef.getIndex(), this.targetMemory.getRememberFor()) <= 0.0F) { + this.targetMemory.getKnownFriendliesList().add(targetRef); + HytaleLogger.Api context = TargetMemorySystems.LOGGER.at(Level.FINE); + if (context.isEnabled()) { + context.log("%s: Registered new friendly %s", ref, targetRef); + } + } + } + } + } + + @Override + public void collectNonMatching(@Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor) { + } + + @Override + public boolean terminateOnFirstMatch() { + return this.targetMemory == null; + } + + @Override + public void cleanup() { + this.role = null; + this.targetMemory = null; + this.closestHostileDistanceSquared = Double.MAX_VALUE; + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/SensorCombatActionEvaluator.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/SensorCombatActionEvaluator.java new file mode 100644 index 0000000..809687b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/SensorCombatActionEvaluator.java @@ -0,0 +1,102 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.CombatActionEvaluatorSystems; +import com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.builders.BuilderSensorCombatActionEvaluator; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.EntityPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.sensorinfo.parameterproviders.MultipleParameterProvider; +import com.hypixel.hytale.server.npc.sensorinfo.parameterproviders.SingleDoubleParameterProvider; +import com.hypixel.hytale.server.npc.valuestore.ValueStore; +import javax.annotation.Nonnull; + +public class SensorCombatActionEvaluator extends SensorBase { + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected final boolean targetInRange; + protected final double allowableDeviation; + protected final int minRangeStoreSlot; + protected final int maxRangeStoreSlot; + protected final int positioningAngleStoreSlot; + protected final int targetSlot; + @Nonnull + protected final SingleDoubleParameterProvider minRangeParameterProvider; + @Nonnull + protected final SingleDoubleParameterProvider maxRangeParameterProvider; + @Nonnull + protected final SingleDoubleParameterProvider positioningAngleParameterProvider; + protected final MultipleParameterProvider parameterProvider = new MultipleParameterProvider(); + protected final EntityPositionProvider positionProvider = new EntityPositionProvider(this.parameterProvider); + protected final ComponentType valueStoreComponentType; + + public SensorCombatActionEvaluator(@Nonnull BuilderSensorCombatActionEvaluator builder, @Nonnull BuilderSupport support) { + super(builder); + this.targetInRange = builder.isTargetInRange(support); + this.allowableDeviation = builder.getAllowableDeviation(support); + this.targetSlot = builder.getTargetSlot(support); + int minRangeParameter = support.getParameterSlot("MinRange"); + int maxRangeParameter = support.getParameterSlot("MaxRange"); + int positioningAngleParameter = support.getParameterSlot("PositioningAngle"); + this.minRangeParameterProvider = new SingleDoubleParameterProvider(minRangeParameter); + this.maxRangeParameterProvider = new SingleDoubleParameterProvider(maxRangeParameter); + this.positioningAngleParameterProvider = new SingleDoubleParameterProvider(positioningAngleParameter); + this.parameterProvider.addParameterProvider(minRangeParameter, this.minRangeParameterProvider); + this.parameterProvider.addParameterProvider(maxRangeParameter, this.maxRangeParameterProvider); + this.parameterProvider.addParameterProvider(positioningAngleParameter, this.positioningAngleParameterProvider); + this.minRangeStoreSlot = builder.getMinRangeStoreSlot(support); + this.maxRangeStoreSlot = builder.getMaxRangeStoreSlot(support); + this.positioningAngleStoreSlot = builder.getPositioningAngleStoreSlot(support); + this.valueStoreComponentType = ValueStore.getComponentType(); + Holder holder = support.getHolder(); + CombatActionEvaluatorSystems.CombatConstructionData constructionData = holder.ensureAndGetComponent( + CombatActionEvaluatorSystems.CombatConstructionData.getComponentType() + ); + constructionData.setCombatState(support.getCurrentStateName()); + constructionData.setMarkedTargetSlot(support.getTargetSlot("CAETargetSlot")); + constructionData.setMinRangeSlot(this.minRangeStoreSlot); + constructionData.setMaxRangeSlot(this.maxRangeStoreSlot); + constructionData.setPositioningAngleSlot(this.positioningAngleStoreSlot); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + this.positionProvider.clear(); + this.parameterProvider.clear(); + return false; + } else { + Ref target = role.getMarkedEntitySupport().getMarkedEntityRef(this.targetSlot); + if (target == null) { + this.positionProvider.clear(); + this.parameterProvider.clear(); + return false; + } else { + this.positionProvider.setTarget(target, store); + ValueStore valueStore = store.getComponent(ref, this.valueStoreComponentType); + double minRange = valueStore.readDouble(this.minRangeStoreSlot); + double maxRange = valueStore.readDouble(this.maxRangeStoreSlot); + this.minRangeParameterProvider.overrideDouble(minRange); + this.maxRangeParameterProvider.overrideDouble(maxRange); + double positioningAngle = valueStore.readDouble(this.positioningAngleStoreSlot); + this.positioningAngleParameterProvider.overrideDouble(positioningAngle); + Vector3d selfPosition = store.getComponent(ref, TRANSFORM_COMPONENT_TYPE).getPosition(); + Vector3d targetPosition = store.getComponent(target, TRANSFORM_COMPONENT_TYPE).getPosition(); + double distance = targetPosition.distanceTo(selfPosition); + return this.targetInRange == distance <= maxRange + this.allowableDeviation; + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/SensorHasHostileTargetMemory.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/SensorHasHostileTargetMemory.java new file mode 100644 index 0000000..86ca542 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/SensorHasHostileTargetMemory.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.builders.BuilderSensorHasHostileTargetMemory; +import com.hypixel.hytale.builtin.npccombatactionevaluator.memory.TargetMemory; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorHasHostileTargetMemory extends SensorBase { + private static final ComponentType TARGET_MEMORY = TargetMemory.getComponentType(); + + public SensorHasHostileTargetMemory(@Nonnull BuilderSensorHasHostileTargetMemory builder) { + super(builder); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + return false; + } else { + TargetMemory targetMemory = ref.getStore().getComponent(ref, TARGET_MEMORY); + return targetMemory != null && !targetMemory.getKnownHostiles().isEmpty(); + } + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/builders/BuilderActionAddToTargetMemory.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/builders/BuilderActionAddToTargetMemory.java new file mode 100644 index 0000000..ca1ba0b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/builders/BuilderActionAddToTargetMemory.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.ActionAddToTargetMemory; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public class BuilderActionAddToTargetMemory extends BuilderActionBase { + public BuilderActionAddToTargetMemory() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Adds the passed target from the sensor to the hostile target memory"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Action build(BuilderSupport builderSupport) { + return new ActionAddToTargetMemory(this); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(JsonElement data) { + this.requireFeature(Feature.LiveEntity); + return this; + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/builders/BuilderActionCombatAbility.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/builders/BuilderActionCombatAbility.java new file mode 100644 index 0000000..477d679 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/builders/BuilderActionCombatAbility.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.builders; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.ActionCombatAbility; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import javax.annotation.Nonnull; + +public class BuilderActionCombatAbility extends BuilderActionBase { + public BuilderActionCombatAbility() { + } + + @Nonnull + public ActionCombatAbility build(@Nonnull BuilderSupport builderSupport) { + return new ActionCombatAbility(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Starts the combat ability selected by the combat action evaluator."; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/builders/BuilderCombatTargetCollector.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/builders/BuilderCombatTargetCollector.java new file mode 100644 index 0000000..a7e072f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/builders/BuilderCombatTargetCollector.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.builders; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.CombatTargetCollector; +import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ISensorEntityCollector; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import javax.annotation.Nonnull; + +public class BuilderCombatTargetCollector extends BuilderBase { + public BuilderCombatTargetCollector() { + } + + @Nonnull + public ISensorEntityCollector build(BuilderSupport builderSupport) { + return new CombatTargetCollector(); + } + + @Nonnull + @Override + public Class category() { + return ISensorEntityCollector.class; + } + + @Override + public boolean isEnabled(ExecutionContext context) { + return true; + } + + @Nonnull + @Override + public String getShortDescription() { + return "A collector which processes matched friendly and hostile targets and adds them to the NPC's short-term combat memory."; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/builders/BuilderSensorCombatActionEvaluator.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/builders/BuilderSensorCombatActionEvaluator.java new file mode 100644 index 0000000..84e12a4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/builders/BuilderSensorCombatActionEvaluator.java @@ -0,0 +1,113 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.SensorCombatActionEvaluator; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import com.hypixel.hytale.server.npc.valuestore.ValueStoreValidator; +import java.util.List; +import java.util.function.ToIntFunction; +import javax.annotation.Nonnull; + +public class BuilderSensorCombatActionEvaluator extends BuilderSensorBase { + protected final BooleanHolder targetInRange = new BooleanHolder(); + protected final DoubleHolder allowableDeviation = new DoubleHolder(); + protected ToIntFunction minRangeStoreSlot; + protected ToIntFunction maxRangeStoreSlot; + protected ToIntFunction positioningAngleStoreSlot; + + public BuilderSensorCombatActionEvaluator() { + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorCombatActionEvaluator(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "A sensor which handles funnelling information to actions and motions from the combat action evaluator."; + } + + @Nonnull + @Override + public String getLongDescription() { + return "A sensor which handles funnelling information to actions and motions from the combat action evaluator. Delivers the current attack target and desired range for supported direct child motions."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + public BuilderSensorCombatActionEvaluator readConfig(@Nonnull JsonElement data) { + this.requireBoolean( + data, "TargetInRange", this.targetInRange, BuilderDescriptorState.Stable, "Whether to match on target being in or out of range.", null + ); + this.getDouble( + data, + "AllowableDeviation", + this.allowableDeviation, + 0.5, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "The allowable deviation from the desired attack range", + null + ); + this.minRangeStoreSlot = this.requireDoubleValueStoreParameter("CAEMinRange", ValueStoreValidator.UseType.READ); + this.maxRangeStoreSlot = this.requireDoubleValueStoreParameter("CAEMaxRange", ValueStoreValidator.UseType.READ); + this.positioningAngleStoreSlot = this.requireDoubleValueStoreParameter("CAEPositioningAngle", ValueStoreValidator.UseType.READ); + this.provideFeature(Feature.LiveEntity); + return this; + } + + @Override + public boolean validate( + String configName, @Nonnull NPCLoadTimeValidationHelper validationHelper, ExecutionContext context, Scope globalScope, @Nonnull List errors + ) { + boolean valid = true; + String state = validationHelper.getCurrentStateName(); + if (state == null) { + errors.add(String.format("%s: CombatActionEvaluator sensors must belong to a state! At %s", configName, this.getBreadCrumbs())); + valid = false; + } + + return super.validate(configName, validationHelper, context, globalScope, errors) & valid; + } + + public boolean isTargetInRange(@Nonnull BuilderSupport support) { + return this.targetInRange.get(support.getExecutionContext()); + } + + public int getMinRangeStoreSlot(BuilderSupport support) { + return this.minRangeStoreSlot.applyAsInt(support); + } + + public int getMaxRangeStoreSlot(BuilderSupport support) { + return this.maxRangeStoreSlot.applyAsInt(support); + } + + public int getPositioningAngleStoreSlot(BuilderSupport support) { + return this.positioningAngleStoreSlot.applyAsInt(support); + } + + public double getAllowableDeviation(@Nonnull BuilderSupport support) { + return this.allowableDeviation.get(support.getExecutionContext()); + } + + public int getTargetSlot(@Nonnull BuilderSupport support) { + return support.getTargetSlot("CAETargetSlot"); + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/builders/BuilderSensorHasHostileTargetMemory.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/builders/BuilderSensorHasHostileTargetMemory.java new file mode 100644 index 0000000..4d8bcac --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/corecomponents/builders/BuilderSensorHasHostileTargetMemory.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.builders; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.corecomponents.SensorHasHostileTargetMemory; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorHasHostileTargetMemory extends BuilderSensorBase { + public BuilderSensorHasHostileTargetMemory() { + } + + @Nonnull + public Sensor build(BuilderSupport builderSupport) { + return new SensorHasHostileTargetMemory(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Checks if there is currently a hostile target in the target memory."; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/CombatActionEvaluator.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/CombatActionEvaluator.java new file mode 100644 index 0000000..e80a9a1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/CombatActionEvaluator.java @@ -0,0 +1,581 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.CombatActionEvaluatorSystems; +import com.hypixel.hytale.builtin.npccombatactionevaluator.NPCCombatActionEvaluatorPlugin; +import com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.combatactions.CombatActionOption; +import com.hypixel.hytale.builtin.npccombatactionevaluator.memory.TargetMemory; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.StateMappingHelper; +import com.hypixel.hytale.server.npc.decisionmaker.core.EvaluationContext; +import com.hypixel.hytale.server.npc.decisionmaker.core.Evaluator; +import com.hypixel.hytale.server.npc.decisionmaker.core.Option; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.valuestore.ValueStore; +import it.unimi.dsi.fastutil.doubles.DoubleArrayList; +import it.unimi.dsi.fastutil.doubles.DoubleList; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CombatActionEvaluator extends Evaluator implements Component { + protected static final float NO_TIMEOUT = Float.MAX_VALUE; + protected CombatActionEvaluator.RunOption runOption; + protected double minRunUtility; + protected long lastRunNanos = NOT_USED; + protected int runInState; + protected float predictability; + protected double minActionUtility; + protected final Int2ObjectMap.OptionHolder>> optionsBySubState = new Int2ObjectOpenHashMap<>(); + protected final Int2ObjectMap basicAttacksBySubState = new Int2ObjectOpenHashMap<>(); + protected int currentBasicAttackSubState = Integer.MIN_VALUE; + protected CombatActionEvaluatorConfig.BasicAttacks currentBasicAttackSet; + @Nullable + protected String currentBasicAttack; + protected Function> currentBasicAttacksInteractionVarsGetter; + protected boolean currentBasicAttackDamageFriendlies; + protected int nextBasicAttackIndex; + protected double basicAttackCooldown; + @Nullable + protected Ref basicAttackTarget; + protected double basicAttackTimeout; + @Nullable + protected Ref primaryTarget; + @Nullable + protected Ref previousTarget; + @Nullable + protected CombatActionEvaluator.CombatOptionHolder currentAction; + @Nullable + protected double[] postExecutionDistanceRange; + protected int markedTargetSlot; + protected int minRangeSlot; + protected int maxRangeSlot; + protected int positioningAngleSlot; + @Nullable + protected String currentInteraction; + protected Function> currentInteractionVarsGetter; + protected InteractionType currentInteractionType; + protected float chargeFor; + protected boolean currentDamageFriendlies; + protected boolean requireAiming; + protected boolean positionFirst; + protected double chargeDistance; + protected float timeout; + protected final EvaluationContext evaluationContext = new EvaluationContext(); + + public static ComponentType getComponentType() { + return NPCCombatActionEvaluatorPlugin.get().getCombatActionEvaluatorComponentType(); + } + + public CombatActionEvaluator( + @Nonnull Role role, @Nonnull CombatActionEvaluatorConfig config, @Nonnull CombatActionEvaluatorSystems.CombatConstructionData data + ) { + this.runOption = new CombatActionEvaluator.RunOption(config.getRunConditions()); + this.runOption.sortConditions(); + this.minRunUtility = config.getMinRunUtility(); + this.minActionUtility = config.getMinActionUtility(); + this.predictability = (float)RandomExtra.randomRange(config.getPredictabilityRange()); + StateMappingHelper stateHelper = role.getStateSupport().getStateHelper(); + String activeState = data.getCombatState(); + this.runInState = stateHelper.getStateIndex(activeState); + this.markedTargetSlot = data.getMarkedTargetSlot(); + this.minRangeSlot = data.getMinRangeSlot(); + this.maxRangeSlot = data.getMaxRangeSlot(); + this.positioningAngleSlot = data.getPositioningAngleSlot(); + Map availableActions = config.getAvailableActions(); + Map.OptionHolder> wrappedAvailableActions = new Object2ObjectOpenHashMap<>(); + + for (Entry action : availableActions.entrySet()) { + CombatActionOption option = CombatActionOption.getAssetMap().getAsset(action.getValue()); + if (option == null) { + throw new IllegalStateException(String.format("Option %s does not exist!", action.getValue())); + } + + option.sortConditions(); + + CombatActionEvaluator.CombatOptionHolder holder = (CombatActionEvaluator.CombatOptionHolder)(switch (option.getActionTarget()) { + case Self -> new CombatActionEvaluator.SelfCombatOptionHolder(option); + case Hostile, Friendly -> new CombatActionEvaluator.MultipleTargetCombatOptionHolder(option); + }); + wrappedAvailableActions.put(action.getKey(), holder); + } + + Map actionSets = config.getActionSets(); + + for (Entry subState : actionSets.entrySet()) { + int subStateIndex = stateHelper.getSubStateIndex(this.runInState, subState.getKey()); + if (subStateIndex == Integer.MIN_VALUE) { + throw new IllegalStateException(String.format("No such state for combat evaluator: %s.%s", activeState, subState.getKey())); + } + + CombatActionEvaluatorConfig.ActionSet actionSet = subState.getValue(); + this.basicAttacksBySubState.put(subStateIndex, actionSet.getBasicAttacks()); + List.OptionHolder> optionList = this.optionsBySubState.computeIfAbsent(subStateIndex, k -> new ObjectArrayList<>()); + String[] combatActions = actionSet.getCombatActions(); + + for (String action : combatActions) { + Evaluator.OptionHolder wrappedAction = wrappedAvailableActions.get(action); + if (wrappedAction == null) { + throw new IllegalStateException(String.format("No action with name '%s' defined in AvailableActions!", action)); + } + + optionList.add(wrappedAction); + } + } + + for (List.OptionHolder> optionList : this.optionsBySubState.values()) { + optionList.sort(Comparator.comparingDouble(Evaluator.OptionHolder::getWeightCoefficient).reversed()); + } + } + + protected CombatActionEvaluator() { + } + + public CombatActionEvaluator.RunOption getRunOption() { + return this.runOption; + } + + public double getMinRunUtility() { + return this.minRunUtility; + } + + @Nonnull + public EvaluationContext getEvaluationContext() { + return this.evaluationContext; + } + + public long getLastRunNanos() { + return this.lastRunNanos; + } + + public void setLastRunNanos(long lastRunNanos) { + this.lastRunNanos = lastRunNanos; + } + + public int getRunInState() { + return this.runInState; + } + + @Nonnull + public Int2ObjectMap.OptionHolder>> getOptionsBySubState() { + return this.optionsBySubState; + } + + public CombatActionEvaluatorConfig.BasicAttacks getBasicAttacks(int subState) { + return this.basicAttacksBySubState.get(subState); + } + + public void setCurrentBasicAttackSet(int subState, CombatActionEvaluatorConfig.BasicAttacks attacks) { + if (subState != this.currentBasicAttackSubState) { + this.nextBasicAttackIndex = 0; + this.currentBasicAttackSubState = subState; + this.currentBasicAttackSet = attacks; + } + } + + @Nullable + public String getCurrentBasicAttack() { + return this.currentBasicAttack; + } + + public CombatActionEvaluatorConfig.BasicAttacks getCurrentBasicAttackSet() { + return this.currentBasicAttackSet; + } + + public void setCurrentBasicAttack(String attack, boolean damageFriendlies, Function> interactionVarsGetter) { + this.currentBasicAttack = attack; + this.currentBasicAttacksInteractionVarsGetter = interactionVarsGetter; + this.currentBasicAttackDamageFriendlies = damageFriendlies; + } + + public int getNextBasicAttackIndex() { + return this.nextBasicAttackIndex; + } + + public void setNextBasicAttackIndex(int next) { + this.nextBasicAttackIndex = next; + } + + public boolean canUseBasicAttack(int selfIndex, ArchetypeChunk archetypeChunk, CommandBuffer commandBuffer) { + return this.basicAttackCooldown > 0.0 + ? false + : this.currentAction == null + || ((CombatActionOption)this.currentAction.getOption()).isBasicAttackAllowed(selfIndex, archetypeChunk, commandBuffer, this); + } + + public void tickBasicAttackCoolDown(float dt) { + if (this.basicAttackCooldown > 0.0) { + this.basicAttackCooldown -= dt; + } + } + + @Nullable + public Ref getBasicAttackTarget() { + return this.basicAttackTarget; + } + + public void setBasicAttackTarget(Ref target) { + this.basicAttackTarget = target; + } + + public boolean tickBasicAttackTimeout(float dt) { + return (this.basicAttackTimeout -= dt) <= 0.0; + } + + public void setBasicAttackTimeout(double timeout) { + this.basicAttackTimeout = timeout; + } + + @Nullable + public Ref getPrimaryTarget() { + return this.primaryTarget; + } + + public void clearPrimaryTarget() { + this.primaryTarget = null; + } + + public void setActiveOptions(List.OptionHolder> options) { + this.options = options; + } + + public int getMarkedTargetSlot() { + return this.markedTargetSlot; + } + + public int getMaxRangeSlot() { + return this.maxRangeSlot; + } + + public int getMinRangeSlot() { + return this.minRangeSlot; + } + + public int getPositioningAngleSlot() { + return this.positioningAngleSlot; + } + + @Nullable + public String getCurrentAttack() { + return this.currentBasicAttack != null ? this.currentBasicAttack : this.currentInteraction; + } + + public float getChargeFor() { + return this.currentBasicAttack != null ? 0.0F : this.chargeFor; + } + + public InteractionType getCurrentInteractionType() { + return this.currentBasicAttack != null ? InteractionType.Primary : this.currentInteractionType; + } + + public Function> getCurrentInteractionVarsGetter() { + return this.currentBasicAttack != null ? this.currentBasicAttacksInteractionVarsGetter : this.currentInteractionVarsGetter; + } + + public boolean shouldDamageFriendlies() { + return this.currentBasicAttack != null ? this.currentBasicAttackDamageFriendlies : this.currentDamageFriendlies; + } + + public boolean requiresAiming() { + return this.currentBasicAttack != null ? true : this.requireAiming; + } + + public boolean shouldPositionFirst() { + return this.currentBasicAttack != null ? false : this.positionFirst; + } + + public double getChargeDistance() { + return this.currentBasicAttack != null ? 0.0 : this.chargeDistance; + } + + public void setCurrentInteraction( + String currentInteraction, + InteractionType interactionType, + float chargeFor, + boolean damageFriendlies, + boolean requireAiming, + boolean positionFirst, + double chargeDistance, + Function> interactionVarsGetter + ) { + this.currentInteraction = currentInteraction; + this.currentInteractionType = interactionType; + this.chargeFor = chargeFor; + this.currentDamageFriendlies = damageFriendlies; + this.requireAiming = requireAiming; + this.positionFirst = positionFirst; + this.currentInteractionVarsGetter = interactionVarsGetter; + this.chargeDistance = chargeDistance; + } + + @Nullable + public CombatActionEvaluator.CombatOptionHolder getCurrentAction() { + return this.currentAction; + } + + public double[] consumePostExecutionDistanceRange() { + double[] distance = this.postExecutionDistanceRange; + this.postExecutionDistanceRange = null; + return distance; + } + + public void setTimeout(float timeout) { + this.timeout = timeout; + } + + public void clearTimeout() { + this.timeout = Float.MAX_VALUE; + } + + public boolean hasTimedOut(float dt) { + return this.timeout != Float.MAX_VALUE && (this.timeout -= dt) <= 0.0F; + } + + public void selectNextCombatAction( + int index, @Nonnull ArchetypeChunk archetypeChunk, CommandBuffer commandBuffer, @Nonnull Role role, ValueStore valueStore + ) { + this.evaluationContext.setPredictability(this.predictability); + this.evaluationContext.setMinimumUtility(this.minActionUtility); + CombatActionEvaluator.CombatOptionHolder option = (CombatActionEvaluator.CombatOptionHolder)this.evaluate( + index, archetypeChunk, commandBuffer, this.evaluationContext + ); + if (option != null) { + Ref target = option.getOptionTarget(); + if (target != null) { + if (((CombatActionOption)option.getOption()).getActionTarget() == CombatActionOption.Target.Friendly) { + this.previousTarget = this.primaryTarget; + } + + this.primaryTarget = target; + role.getMarkedEntitySupport().setMarkedEntity(this.markedTargetSlot, this.primaryTarget); + } + + this.currentAction = option; + ((CombatActionOption)this.currentAction.getOption()).execute(index, archetypeChunk, commandBuffer, role, this, valueStore); + if (((CombatActionOption)option.getOption()).cancelBasicAttackOnSelect()) { + this.clearCurrentBasicAttack(); + } + } + } + + public void completeCurrentAction(boolean forceClearAbility, boolean clearBasicAttack) { + if (forceClearAbility || this.currentBasicAttack == null) { + this.terminateCurrentAction(); + this.setLastRunNanos(System.nanoTime()); + } + + if (clearBasicAttack) { + this.clearCurrentBasicAttack(); + } + } + + public void terminateCurrentAction() { + this.currentInteraction = null; + this.chargeFor = 0.0F; + if (this.currentAction != null) { + this.currentAction.setLastUsedNanos(System.nanoTime()); + CombatActionOption option = (CombatActionOption)this.currentAction.getOption(); + if (option.getActionTarget() == CombatActionOption.Target.Friendly) { + this.primaryTarget = this.previousTarget; + this.previousTarget = null; + } + + this.postExecutionDistanceRange = option.getPostExecuteDistanceRange(); + this.currentAction = null; + } + } + + public void clearCurrentBasicAttack() { + if (this.currentBasicAttackSet != null) { + this.basicAttackCooldown = RandomExtra.randomRange(this.currentBasicAttackSet.getCooldownRange()); + } + + this.currentBasicAttack = null; + this.basicAttackTarget = null; + } + + @Override + public void setupNPC(Role role) { + for (List.OptionHolder> optionList : this.optionsBySubState.values()) { + for (Evaluator.OptionHolder option : optionList) { + CombatActionOption opt = (CombatActionOption)option.getOption(); + opt.setupNPC(role); + } + } + } + + @Override + public void setupNPC(Holder holder) { + for (List.OptionHolder> optionList : this.optionsBySubState.values()) { + for (Evaluator.OptionHolder option : optionList) { + CombatActionOption opt = (CombatActionOption)option.getOption(); + opt.setupNPC(holder); + } + } + } + + @Nonnull + @Override + public Component clone() { + CombatActionEvaluator evaluator = new CombatActionEvaluator(); + evaluator.options = this.options; + evaluator.runOption = this.runOption; + evaluator.minRunUtility = this.minRunUtility; + evaluator.minActionUtility = this.minActionUtility; + evaluator.predictability = this.predictability; + evaluator.runInState = this.runInState; + evaluator.optionsBySubState.putAll(this.optionsBySubState); + evaluator.lastRunNanos = this.lastRunNanos; + evaluator.markedTargetSlot = this.markedTargetSlot; + evaluator.minRangeSlot = this.minRangeSlot; + evaluator.maxRangeSlot = this.maxRangeSlot; + evaluator.positioningAngleSlot = this.positioningAngleSlot; + evaluator.primaryTarget = this.primaryTarget; + evaluator.previousTarget = this.previousTarget; + evaluator.currentAction = this.currentAction; + evaluator.currentInteraction = this.currentInteraction; + evaluator.chargeFor = this.chargeFor; + evaluator.timeout = this.timeout; + evaluator.basicAttacksBySubState.putAll(this.basicAttacksBySubState); + evaluator.nextBasicAttackIndex = this.nextBasicAttackIndex; + evaluator.basicAttackCooldown = this.basicAttackCooldown; + evaluator.currentBasicAttackSet = this.currentBasicAttackSet; + evaluator.currentBasicAttack = this.currentBasicAttack; + evaluator.basicAttackTimeout = this.basicAttackTimeout; + evaluator.basicAttackTarget = this.basicAttackTarget; + evaluator.currentBasicAttackSubState = this.currentBasicAttackSubState; + evaluator.currentInteractionType = this.currentInteractionType; + evaluator.currentBasicAttackDamageFriendlies = this.currentBasicAttackDamageFriendlies; + evaluator.currentDamageFriendlies = this.currentDamageFriendlies; + evaluator.requireAiming = this.requireAiming; + return evaluator; + } + + public abstract class CombatOptionHolder extends Evaluator.OptionHolder { + protected long lastUsedNanos = Evaluator.NOT_USED; + + protected CombatOptionHolder(CombatActionOption option) { + super(option); + } + + public void setLastUsedNanos(long lastUsedNanos) { + this.lastUsedNanos = lastUsedNanos; + } + + @Nullable + public Ref getOptionTarget() { + return null; + } + } + + public class MultipleTargetCombatOptionHolder extends CombatActionEvaluator.CombatOptionHolder { + protected List> targets; + protected final DoubleList targetUtilities = new DoubleArrayList(); + @Nullable + protected Ref pickedTarget; + + protected MultipleTargetCombatOptionHolder(CombatActionOption option) { + super(option); + } + + @Override + public double calculateUtility( + int index, @Nonnull ArchetypeChunk archetypeChunk, CommandBuffer commandBuffer, @Nonnull EvaluationContext context + ) { + context.setLastUsedNanos(this.lastUsedNanos); + TargetMemory targetMemory = archetypeChunk.getComponent(index, TargetMemory.getComponentType()); + + this.targets = switch (((CombatActionOption)this.option).getActionTarget()) { + case Self -> throw new IllegalStateException("Self option should not be wrapped in a MultipleTargetCombatOptionHolder!"); + case Hostile -> targetMemory.getKnownHostilesList(); + case Friendly -> targetMemory.getKnownFriendliesList(); + }; + this.targetUtilities.clear(); + this.pickedTarget = null; + this.utility = 0.0; + + for (int i = 0; i < this.targets.size(); i++) { + double targetUtility = ((CombatActionOption)this.option).calculateUtility(index, archetypeChunk, this.targets.get(i), commandBuffer, context); + this.targetUtilities.add(i, targetUtility); + if (targetUtility > this.utility) { + this.utility = targetUtility; + this.pickedTarget = this.targets.get(i); + } + } + + return this.utility; + } + + @Override + public double getTotalUtility(double threshold) { + double utility = 0.0; + + for (int i = 0; i < this.targets.size(); i++) { + double targetUtility = this.targetUtilities.getDouble(i); + if (targetUtility >= threshold) { + utility += targetUtility; + } + } + + return utility; + } + + @Override + public double tryPick(double currentWeight, double threshold) { + for (int i = 0; i < this.targets.size(); i++) { + double targetUtility = this.targetUtilities.getDouble(i); + if (!(targetUtility < threshold)) { + currentWeight -= targetUtility; + if (currentWeight <= 0.0) { + this.pickedTarget = this.targets.get(i); + break; + } + } + } + + return currentWeight; + } + + @Override + public Ref getOptionTarget() { + return this.pickedTarget; + } + } + + public static class RunOption extends Option { + protected RunOption(String[] conditions) { + this.conditions = conditions; + } + } + + public class SelfCombatOptionHolder extends CombatActionEvaluator.CombatOptionHolder { + protected SelfCombatOptionHolder(CombatActionOption option) { + super(option); + } + + @Override + public double calculateUtility( + int index, @Nonnull ArchetypeChunk archetypeChunk, CommandBuffer commandBuffer, @Nonnull EvaluationContext context + ) { + context.setLastUsedNanos(this.lastUsedNanos); + return this.utility = ((CombatActionOption)this.option) + .calculateUtility(index, archetypeChunk, CombatActionEvaluator.this.primaryTarget, commandBuffer, context); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/CombatActionEvaluatorConfig.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/CombatActionEvaluatorConfig.java new file mode 100644 index 0000000..291dc68 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/CombatActionEvaluatorConfig.java @@ -0,0 +1,313 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.combatactions.CombatActionOption; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.MapUtil; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.npc.decisionmaker.core.conditions.base.Condition; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import javax.annotation.Nonnull; + +public class CombatActionEvaluatorConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CombatActionEvaluatorConfig.class, CombatActionEvaluatorConfig::new + ) + .appendInherited( + new KeyedCodec<>("AvailableActions", new MapCodec<>(CombatActionOption.CHILD_ASSET_CODEC, Object2ObjectOpenHashMap::new)), + (option, o) -> option.availableActions = MapUtil.combineUnmodifiable(option.availableActions, o), + option -> option.availableActions, + (option, parent) -> option.availableActions = parent.availableActions + ) + .addValidator(Validators.nonNull()) + .addValidator(CombatActionOption.VALIDATOR_CACHE.getMapValueValidator()) + .documentation("A map of all available combat actions this NPC can take.") + .add() + .append( + new KeyedCodec<>("ActionSets", new MapCodec<>(CombatActionEvaluatorConfig.ActionSet.CODEC, Object2ObjectOpenHashMap::new)), + (option, o) -> option.actionSets = o, + option -> option.actionSets + ) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyMap()) + .documentation("A mapping of all combat substate names to the basic attacks and abilities that should be used in them.") + .add() + .appendInherited( + new KeyedCodec<>("RunConditions", Condition.CHILD_ASSET_CODEC_ARRAY), + (option, s) -> option.runConditions = s, + option -> option.runConditions, + (option, parent) -> option.runConditions = parent.runConditions + ) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyArray()) + .addValidator(Condition.VALIDATOR_CACHE.getArrayValidator()) + .documentation("The list of conditions that determine whether or not the combat action evaluator should run.") + .add() + .appendInherited( + new KeyedCodec<>("MinRunUtility", Codec.DOUBLE), + (option, d) -> option.minRunUtility = d, + option -> option.minRunUtility, + (option, parent) -> option.minRunUtility = parent.minRunUtility + ) + .addValidator(Validators.range(0.5, 1.0)) + .documentation("The minimum utility score required to be returned from the RunConditions to trigger a new run of the combat action evaluator.") + .add() + .appendInherited( + new KeyedCodec<>("MinActionUtility", Codec.DOUBLE), + (option, d) -> option.minActionUtility = d, + option -> option.minActionUtility, + (option, parent) -> option.minActionUtility = parent.minActionUtility + ) + .addValidator(Validators.range(0.0, 1.0)) + .documentation("The minimum utility score required for any individual combat action to be run.") + .add() + .appendInherited( + new KeyedCodec<>("PredictabilityRange", Codec.DOUBLE_ARRAY), + (option, o) -> option.predictabilityRange = o, + option -> option.predictabilityRange, + (option, parent) -> option.predictabilityRange = parent.predictabilityRange + ) + .addValidator(Validators.doubleArraySize(2)) + .addValidator(Validators.weaklyMonotonicSequentialDoubleArrayValidator()) + .documentation("A random range from which to pick the NPC's predictability factor.") + .add() + .build(); + private static final double[] DEFAULT_PREDICTABILITY_RANGE = new double[]{1.0, 1.0}; + protected Map availableActions = Collections.emptyMap(); + protected Map actionSets; + protected String[] runConditions; + protected double minRunUtility = 0.8; + protected double minActionUtility = 0.1; + protected double[] predictabilityRange = DEFAULT_PREDICTABILITY_RANGE; + + public CombatActionEvaluatorConfig() { + } + + public Map getAvailableActions() { + return this.availableActions; + } + + public Map getActionSets() { + return this.actionSets; + } + + public String[] getRunConditions() { + return this.runConditions; + } + + public double getMinRunUtility() { + return this.minRunUtility; + } + + public double getMinActionUtility() { + return this.minActionUtility; + } + + public double[] getPredictabilityRange() { + return this.predictabilityRange; + } + + @Nonnull + @Override + public String toString() { + return "CombatActionEvaluatorConfig{availableActions=" + + this.availableActions + + ", actionSets=" + + this.actionSets + + ", runConditions=" + + Arrays.toString((Object[])this.runConditions) + + ", minRunUtility=" + + this.minRunUtility + + ", minActionUtility=" + + this.minActionUtility + + ", predictabilityRange=" + + Arrays.toString(this.predictabilityRange) + + "}"; + } + + public static class ActionSet { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CombatActionEvaluatorConfig.ActionSet.class, CombatActionEvaluatorConfig.ActionSet::new + ) + .append( + new KeyedCodec<>("BasicAttacks", CombatActionEvaluatorConfig.BasicAttacks.CODEC), + (actionSet, o) -> actionSet.basicAttacks = o, + actionSet -> actionSet.basicAttacks + ) + .documentation("The basic attacks to be used in this combat substate.") + .add() + .append( + new KeyedCodec<>("Actions", Codec.STRING_ARRAY), (actionSet, o) -> actionSet.combatActions = o, actionsSet -> actionsSet.combatActions + ) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyArray()) + .documentation("A list of available actions that should be used in this combat substate, mapped from AvailableActions.") + .add() + .build(); + protected CombatActionEvaluatorConfig.BasicAttacks basicAttacks; + protected String[] combatActions; + + protected ActionSet() { + } + + public CombatActionEvaluatorConfig.BasicAttacks getBasicAttacks() { + return this.basicAttacks; + } + + public String[] getCombatActions() { + return this.combatActions; + } + + @Nonnull + @Override + public String toString() { + return "ActionSet{basicAttacks=" + this.basicAttacks + ", combatActions=" + Arrays.toString((Object[])this.combatActions) + "}"; + } + } + + public static class BasicAttacks { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CombatActionEvaluatorConfig.BasicAttacks.class, CombatActionEvaluatorConfig.BasicAttacks::new + ) + .append( + new KeyedCodec<>("Attacks", RootInteraction.CHILD_ASSET_CODEC_ARRAY), + (basicAttacks, o) -> basicAttacks.attacks = o, + basicAttacks -> basicAttacks.attacks + ) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyArray()) + .addValidator(RootInteraction.VALIDATOR_CACHE.getArrayValidator()) + .documentation("The sequence of basic attacks to be used.") + .add() + .append(new KeyedCodec<>("Randomise", Codec.BOOLEAN), (basicAttacks, b) -> basicAttacks.randomise = b, basicAttacks -> basicAttacks.randomise) + .documentation("Whether or not the basic attacks should be executed randomly, or run in the order they were defined in.") + .add() + .append(new KeyedCodec<>("MaxRange", Codec.DOUBLE), (basicAttacks, d) -> { + basicAttacks.maxRange = d; + basicAttacks.maxRangeSquared = d * d; + }, basicAttacks -> basicAttacks.maxRange) + .addValidator(Validators.nonNull()) + .addValidator(Validators.greaterThan(0.0)) + .documentation("How close a target needs to be to use a basic attack against them.") + .add() + .append(new KeyedCodec<>("Timeout", Codec.FLOAT), (basicAttacks, f) -> basicAttacks.timeout = f, basicAttacks -> basicAttacks.timeout) + .addValidator(Validators.greaterThan(0.0F)) + .documentation("How long before giving up if a target moves out of range while preparing to execute a basic attack.") + .add() + .append( + new KeyedCodec<>("CooldownRange", Codec.DOUBLE_ARRAY), + (basicAttacks, o) -> basicAttacks.cooldownRange = o, + basicAttacks -> basicAttacks.cooldownRange + ) + .addValidator(Validators.nonNull()) + .addValidator(Validators.doubleArraySize(2)) + .addValidator(Validators.weaklyMonotonicSequentialDoubleArrayValidator()) + .documentation("A random range to pick a cooldown between basic attacks from.") + .add() + .appendInherited( + new KeyedCodec<>("InteractionVars", new MapCodec<>(RootInteraction.CHILD_ASSET_CODEC, Object2ObjectOpenHashMap::new)), + (basicAttacks, v) -> basicAttacks.interactionVars = v, + basicAttacks -> basicAttacks.interactionVars, + (basicAttacks, parent) -> basicAttacks.interactionVars = parent.interactionVars + ) + .addValidator(RootInteraction.VALIDATOR_CACHE.getMapValueValidator()) + .documentation("Interaction vars to modify the values in the interaction itself.") + .add() + .appendInherited( + new KeyedCodec<>("DamageFriendlies", Codec.BOOLEAN), + (basicAttacks, b) -> basicAttacks.damageFriendlies = b, + basicAttacks -> basicAttacks.damageFriendlies, + (basicAttacks, parent) -> basicAttacks.damageFriendlies = parent.damageFriendlies + ) + .documentation("Whether or not basic attacks should be able to damage friendly targets.") + .add() + .appendInherited( + new KeyedCodec<>("UseProjectedDistance", Codec.BOOLEAN), + (basicAttacks, b) -> basicAttacks.useProjectedDistance = b, + basicAttacks -> basicAttacks.useProjectedDistance, + (basicAttacks, parent) -> basicAttacks.useProjectedDistance = parent.useProjectedDistance + ) + .documentation("Whether to use projected distance instead of 3D distance for checking if in range of basic attacks.") + .add() + .build(); + protected String[] attacks; + protected boolean randomise; + protected double maxRange; + protected double maxRangeSquared; + protected float timeout = 2.0F; + protected double[] cooldownRange; + protected Map interactionVars = Collections.emptyMap(); + protected boolean damageFriendlies; + protected boolean useProjectedDistance; + + protected BasicAttacks() { + } + + public String[] getAttacks() { + return this.attacks; + } + + public boolean isRandom() { + return this.randomise; + } + + public double getMaxRange() { + return this.maxRange; + } + + public double getMaxRangeSquared() { + return this.maxRangeSquared; + } + + public float getTimeout() { + return this.timeout; + } + + public double[] getCooldownRange() { + return this.cooldownRange; + } + + public Map getInteractionVars(InteractionContext c) { + return this.interactionVars; + } + + public boolean isDamageFriendlies() { + return this.damageFriendlies; + } + + public boolean shouldUseProjectedDistance() { + return this.useProjectedDistance; + } + + @Nonnull + @Override + public String toString() { + return "BasicAttacks{attacks=" + + Arrays.toString((Object[])this.attacks) + + ", randomise=" + + this.randomise + + ", maxRange=" + + this.maxRange + + ", maxRangeSquared=" + + this.maxRangeSquared + + ", timeout=" + + this.timeout + + ", cooldownRange=" + + Arrays.toString(this.cooldownRange) + + ", interactionVars=" + + this.interactionVars + + ", damageFriendlies=" + + this.damageFriendlies + + ", useProjectedDistance=" + + this.useProjectedDistance + + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/combatactions/AbilityCombatAction.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/combatactions/AbilityCombatAction.java new file mode 100644 index 0000000..782dcf0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/combatactions/AbilityCombatAction.java @@ -0,0 +1,385 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.combatactions; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.Positioning; +import com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.CombatActionEvaluator; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.interactions.NPCInteractionSimulationHandler; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.util.InventoryHelper; +import com.hypixel.hytale.server.npc.valuestore.ValueStore; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class AbilityCombatAction extends CombatActionOption { + public static final EnumCodec MODE_CODEC = new EnumCodec<>(AbilityCombatAction.AbilityType.class) + .documentKey(AbilityCombatAction.AbilityType.Primary, "Use primary attack.") + .documentKey(AbilityCombatAction.AbilityType.Secondary, "Use secondary attack."); + public static final EnumCodec POSITIONING_CODEC = new EnumCodec<>(Positioning.class) + .documentKey(Positioning.Any, "Don't care about positioning.") + .documentKey(Positioning.Front, "Try to be in front of the target.") + .documentKey(Positioning.Behind, "Try to be behind the target.") + .documentKey(Positioning.Flank, "Try to be on the target's flank."); + public static final BuilderCodec CODEC = BuilderCodec.builder( + AbilityCombatAction.class, AbilityCombatAction::new, CombatActionOption.BASE_CODEC + ) + .documentation("A combat action which executes an attack or ability by triggering an Interaction.") + .appendInherited( + new KeyedCodec<>("Ability", RootInteraction.CHILD_ASSET_CODEC), + (option, s) -> option.ability = s, + option -> option.ability, + (option, parent) -> option.ability = parent.ability + ) + .addValidator(Validators.nonNull()) + .addValidator(RootInteraction.VALIDATOR_CACHE.getValidator()) + .documentation("The interaction (ability) to use.") + .add() + .appendInherited( + new KeyedCodec<>("AbilityType", MODE_CODEC), + (option, e) -> option.abilityType = e, + option -> option.abilityType, + (option, parent) -> option.abilityType = parent.abilityType + ) + .documentation("The ability type.") + .add() + .appendInherited( + new KeyedCodec<>("ChargeFor", Codec.FLOAT), + (option, f) -> option.chargeFor = f, + option -> option.chargeFor, + (option, parent) -> option.chargeFor = parent.chargeFor + ) + .addValidator(Validators.greaterThanOrEqual(0.0F)) + .documentation("How long to charge the ability for before using it.") + .add() + .appendInherited(new KeyedCodec<>("AttackDistanceRange", Codec.DOUBLE_ARRAY), (option, o) -> { + option.attackRange = o; + option.maxRangeSquared = o[1] * o[1]; + }, option -> option.attackRange, (option, parent) -> option.attackRange = parent.attackRange) + .addValidator(Validators.nonNull()) + .addValidator(Validators.doubleArraySize(2)) + .addValidator(Validators.weaklyMonotonicSequentialDoubleArrayValidator()) + .documentation("The range at which the NPC needs to be from the target to execute the attack.") + .add() + .appendInherited( + new KeyedCodec<>("WeaponSlot", Codec.INTEGER), + (option, i) -> option.weaponSlot = i, + option -> option.weaponSlot, + (option, parent) -> option.weaponSlot = parent.weaponSlot + ) + .documentation("The weapon (hotbar) slot to switch to for this attack.") + .add() + .appendInherited( + new KeyedCodec<>("OffhandSlot", Codec.INTEGER), + (option, i) -> option.offhandSlot = i, + option -> option.offhandSlot, + (option, parent) -> option.offhandSlot = parent.offhandSlot + ) + .documentation("The off-hand slot to switch to for this attack. -1 set to no off-hand equipped.") + .add() + .appendInherited( + new KeyedCodec<>("FailureTimeout", Codec.FLOAT), + (option, f) -> option.failureTimeout = f, + option -> option.failureTimeout, + (option, parent) -> option.failureTimeout = parent.failureTimeout + ) + .addValidator(Validators.greaterThan(0.0F)) + .documentation("How long to try and run the action before giving up if it can't be completed in time.") + .add() + .appendInherited( + new KeyedCodec<>("SubState", Codec.STRING), + (option, s) -> option.subState = s, + option -> option.subState, + (option, parent) -> option.subState = parent.subState + ) + .addValidator(Validators.nonEmptyString()) + .documentation("An optional substate to switch to when selecting this combat action to modify motion or other available actions.") + .add() + .appendInherited( + new KeyedCodec<>("InteractionVars", new MapCodec<>(RootInteraction.CHILD_ASSET_CODEC, Object2ObjectOpenHashMap::new)), + (option, v) -> option.interactionVars = v, + option -> option.interactionVars, + (option, parent) -> option.interactionVars = parent.interactionVars + ) + .addValidator(RootInteraction.VALIDATOR_CACHE.getMapValueValidator()) + .documentation("Interaction vars to modify the values in the interaction itself.") + .add() + .appendInherited( + new KeyedCodec<>("DamageFriendlies", Codec.BOOLEAN), + (option, b) -> option.damageFriendlies = b, + option -> option.damageFriendlies, + (option, parent) -> option.damageFriendlies = parent.damageFriendlies + ) + .documentation("Whether or not this ability should be able to damage friendly targets.") + .add() + .appendInherited( + new KeyedCodec<>("RequireAiming", Codec.BOOLEAN), + (option, b) -> option.requireAiming = b, + option -> option.requireAiming, + (option, parent) -> option.requireAiming = parent.requireAiming + ) + .documentation("Whether or not this ability needs to be aimed at the target.") + .add() + .appendInherited( + new KeyedCodec<>("Positioning", POSITIONING_CODEC), + (option, e) -> option.positioning = e, + option -> option.positioning, + (option, parent) -> option.positioning = parent.positioning + ) + .documentation("Where the NPC should try to position itself relative to the target's facing direction.") + .add() + .appendInherited( + new KeyedCodec<>("PositionFirst", Codec.BOOLEAN), + (option, b) -> option.positionFirst = b, + option -> option.positionFirst, + (option, parent) -> option.positionFirst = parent.positionFirst + ) + .documentation("Whether the NPC should try to reach the correct positioning before executing the ability.") + .add() + .appendInherited( + new KeyedCodec<>("ChargeDistance", Codec.DOUBLE), + (option, d) -> option.chargeDistance = d, + option -> option.chargeDistance, + (option, parent) -> option.chargeDistance = parent.chargeDistance + ) + .documentation("If this is a charge attack, the distance the charge will cover.") + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .build(); + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected String ability; + protected AbilityCombatAction.AbilityType abilityType = AbilityCombatAction.AbilityType.Primary; + protected float chargeFor; + protected double[] attackRange; + protected double maxRangeSquared; + protected int weaponSlot; + protected int offhandSlot = -1; + protected float failureTimeout = 10.0F; + protected String subState; + protected Map interactionVars = Collections.emptyMap(); + protected boolean damageFriendlies; + protected boolean requireAiming = true; + protected Positioning positioning = Positioning.Any; + protected boolean positionFirst; + protected double chargeDistance; + + public AbilityCombatAction() { + } + + public String getAbility() { + return this.ability; + } + + public float getChargeFor() { + return this.chargeFor; + } + + public double[] getAttackRange() { + return this.attackRange; + } + + public int getWeaponSlot() { + return this.weaponSlot; + } + + public int getOffhandSlot() { + return this.offhandSlot; + } + + public float getFailureTimeout() { + return this.failureTimeout; + } + + public boolean isDamageFriendlies() { + return this.damageFriendlies; + } + + public boolean isPositionFirst() { + return this.positionFirst; + } + + @Override + public void execute( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Role role, + @Nonnull CombatActionEvaluator evaluator, + @Nonnull ValueStore valueStore + ) { + NPCEntity npcComponent = archetypeChunk.getComponent(index, NPCEntity.getComponentType()); + + assert npcComponent != null; + + HytaleLogger.Api ctx = CombatActionEvaluator.LOGGER.at(Level.FINEST); + if (ctx.isEnabled()) { + ctx.log("%s: Executing option %s", archetypeChunk.getReferenceTo(index), this.getId()); + } + + InventoryHelper.setHotbarSlot(npcComponent.getInventory(), (byte)this.weaponSlot); + InventoryHelper.setOffHandSlot(npcComponent.getInventory(), (byte)this.offhandSlot); + if (this.subState != null) { + role.getStateSupport().setSubState(this.subState); + ctx = CombatActionEvaluator.LOGGER.at(Level.FINEST); + if (ctx.isEnabled()) { + ctx.log("%s: Set substate to %s", archetypeChunk.getReferenceTo(index), this.subState); + } + } + + if (this.actionTarget == CombatActionOption.Target.Self) { + Ref ref = archetypeChunk.getReferenceTo(index); + RootInteraction interaction = RootInteraction.getAssetMap().getAsset(this.ability); + if (interaction == null) { + throw new IllegalStateException("No such interaction: " + this.ability); + } else { + InteractionManager interactionManagerComponent = archetypeChunk.getComponent(index, InteractionModule.get().getInteractionManagerComponent()); + + assert interactionManagerComponent != null; + + if (interactionManagerComponent.getInteractionSimulationHandler() instanceof NPCInteractionSimulationHandler npcInteractionSimulationHandler) { + npcInteractionSimulationHandler.requestChargeTime(this.chargeFor); + } + + InteractionContext context = InteractionContext.forInteraction(interactionManagerComponent, ref, this.abilityType.interactionType, commandBuffer); + context.setInteractionVarsGetter(this::getInteractionVars); + InteractionChain chain = interactionManagerComponent.initChain(this.abilityType.interactionType, context, interaction, false); + interactionManagerComponent.queueExecuteChain(chain); + role.getCombatSupport().setExecutingAttack(chain, false, 2.0); + ctx = CombatActionEvaluator.LOGGER.at(Level.INFO); + if (ctx.isEnabled()) { + ctx.log("%s: Executed self-targeted ability %s", archetypeChunk.getReferenceTo(index), this.ability); + } + + evaluator.completeCurrentAction(true, true); + } + } else { + valueStore.storeDouble(evaluator.getMinRangeSlot(), this.attackRange[0]); + valueStore.storeDouble(evaluator.getMaxRangeSlot(), this.attackRange[1]); + if (this.positioning == Positioning.Any) { + valueStore.storeDouble(evaluator.getPositioningAngleSlot(), Double.MAX_VALUE); + } else { + float randomAngle = RandomExtra.randomRange(0.0F, (float) (Math.PI / 2)); + + float chosenAngle = PhysicsMath.normalizeTurnAngle(switch (this.positioning) { + case Front -> randomAngle + (float) (Math.PI / 2) + (float) (Math.PI / 4); + case Behind -> randomAngle - (float) (Math.PI / 4); + case Flank -> randomAngle + (RandomExtra.randomBoolean() ? (float) (Math.PI / 4) : (float) (-Math.PI * 3.0 / 4.0)); + default -> throw new IllegalStateException("Unexpected value: " + this.positioning); + }); + valueStore.storeDouble(evaluator.getPositioningAngleSlot(), chosenAngle); + } + + evaluator.setCurrentInteraction( + this.ability, + this.abilityType.interactionType, + this.chargeFor, + this.damageFriendlies, + this.requireAiming, + this.positionFirst, + this.chargeDistance, + this::getInteractionVars + ); + evaluator.setTimeout(this.failureTimeout); + ctx = CombatActionEvaluator.LOGGER.at(Level.FINEST); + if (ctx.isEnabled()) { + ctx.log("%s: Began executing ability %s", archetypeChunk.getReferenceTo(index), this.ability); + } + } + } + + @Override + public boolean isBasicAttackAllowed( + int selfIndex, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull CommandBuffer commandBuffer, + @Nonnull CombatActionEvaluator evaluator + ) { + Ref primaryTarget = evaluator.getPrimaryTarget(); + if (primaryTarget != null && primaryTarget.isValid()) { + TransformComponent primaryTargetTransformComponent = commandBuffer.getComponent(primaryTarget, TRANSFORM_COMPONENT_TYPE); + + assert primaryTargetTransformComponent != null; + + Vector3d targetPos = primaryTargetTransformComponent.getPosition(); + TransformComponent selfTransformComponent = archetypeChunk.getComponent(selfIndex, TRANSFORM_COMPONENT_TYPE); + + assert selfTransformComponent != null; + + Vector3d selfPos = selfTransformComponent.getPosition(); + double distance = selfPos.distanceSquaredTo(targetPos); + return distance > this.maxRangeSquared; + } else { + return true; + } + } + + private Map getInteractionVars(InteractionContext c) { + return this.interactionVars; + } + + @Nonnull + @Override + public String toString() { + return "AbilityCombatAction{ability='" + + this.ability + + "', abilityType=" + + this.abilityType + + ", chargeFor=" + + this.chargeFor + + ", attackRange=" + + Arrays.toString(this.attackRange) + + ", maxRangeSquared=" + + this.maxRangeSquared + + ", weaponSlot=" + + this.weaponSlot + + ", offhandSlot=" + + this.offhandSlot + + ", failureTimeout=" + + this.failureTimeout + + ", subState='" + + this.subState + + "', interactionVars=" + + this.interactionVars + + ", damageFriendlies=" + + this.damageFriendlies + + ", requireAiming=" + + this.requireAiming + + "}" + + super.toString(); + } + + private static enum AbilityType { + Primary(InteractionType.Primary), + Secondary(InteractionType.Secondary); + + private final InteractionType interactionType; + + private AbilityType(InteractionType interactionType) { + this.interactionType = interactionType; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/combatactions/BasicAttackTargetCombatAction.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/combatactions/BasicAttackTargetCombatAction.java new file mode 100644 index 0000000..a600f68 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/combatactions/BasicAttackTargetCombatAction.java @@ -0,0 +1,92 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.combatactions; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.CombatActionEvaluator; +import com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.CombatActionEvaluatorConfig; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.decisionmaker.core.Option; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.util.InventoryHelper; +import com.hypixel.hytale.server.npc.valuestore.ValueStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class BasicAttackTargetCombatAction extends CombatActionOption { + private static final double BASIC_ATTACK_DISTANCE_OFFSET = 0.1; + public static final BuilderCodec CODEC = BuilderCodec.builder( + BasicAttackTargetCombatAction.class, BasicAttackTargetCombatAction::new, Option.ABSTRACT_CODEC + ) + .documentation("A combat action which simply selects a target and sets up distances for use with substates that only contain basic attacks.") + .appendInherited( + new KeyedCodec<>("WeaponSlot", Codec.INTEGER), + (option, i) -> option.weaponSlot = i, + option -> option.weaponSlot, + (option, parent) -> option.weaponSlot = parent.weaponSlot + ) + .documentation("The weapon (hotbar) slot to switch to for basic attacks.") + .add() + .appendInherited( + new KeyedCodec<>("OffhandSlot", Codec.INTEGER), + (option, i) -> option.offhandSlot = i, + option -> option.offhandSlot, + (option, parent) -> option.offhandSlot = parent.offhandSlot + ) + .documentation("The off-hand slot to switch to for basic attacks. -1 set to no off-hand equipped.") + .add() + .afterDecode(option -> option.actionTarget = CombatActionOption.Target.Hostile) + .build(); + protected int weaponSlot; + protected int offhandSlot; + + public BasicAttackTargetCombatAction() { + } + + @Override + public void execute( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + CommandBuffer commandBuffer, + Role role, + @Nonnull CombatActionEvaluator evaluator, + @Nonnull ValueStore valueStore + ) { + NPCEntity npcComponent = archetypeChunk.getComponent(index, NPCEntity.getComponentType()); + + assert npcComponent != null; + + HytaleLogger.Api ctx = CombatActionEvaluator.LOGGER.at(Level.FINEST); + if (ctx.isEnabled()) { + ctx.log("%s: Executing option %s", archetypeChunk.getReferenceTo(index), this.getId()); + } + + InventoryHelper.setHotbarSlot(npcComponent.getInventory(), (byte)this.weaponSlot); + InventoryHelper.setOffHandSlot(npcComponent.getInventory(), (byte)this.offhandSlot); + CombatActionEvaluatorConfig.BasicAttacks basicAttacks = evaluator.getCurrentBasicAttackSet(); + if (basicAttacks != null) { + double range = basicAttacks.getMaxRange() - 0.1; + valueStore.storeDouble(evaluator.getMinRangeSlot(), range); + valueStore.storeDouble(evaluator.getMaxRangeSlot(), range); + } + + evaluator.completeCurrentAction(true, false); + evaluator.clearTimeout(); + } + + @Override + public boolean isBasicAttackAllowed( + int selfIndex, ArchetypeChunk archetypeChunk, CommandBuffer commandBuffer, CombatActionEvaluator evaluator + ) { + return true; + } + + @Override + public boolean cancelBasicAttackOnSelect() { + return false; + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/combatactions/CombatActionOption.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/combatactions/CombatActionOption.java new file mode 100644 index 0000000..8c9b8ed --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/combatactions/CombatActionOption.java @@ -0,0 +1,172 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.combatactions; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.CombatActionEvaluator; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.decisionmaker.core.Option; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.valuestore.ValueStore; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class CombatActionOption extends Option implements JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.extraData = data, t -> t.extraData + ); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(CombatActionOption.class, Option.ABSTRACT_CODEC) + .appendInherited( + new KeyedCodec<>("Target", CombatActionOption.Target.CODEC), + (option, e) -> option.actionTarget = e, + option -> option.actionTarget, + (option, parent) -> option.actionTarget = parent.actionTarget + ) + .addValidator(Validators.nonNull()) + .documentation("The target type this action applies to.") + .add() + .appendInherited( + new KeyedCodec<>("PostExecuteDistanceRange", Codec.DOUBLE_ARRAY), + (option, o) -> option.postExecuteDistanceRange = o, + option -> option.postExecuteDistanceRange, + (option, parent) -> option.postExecuteDistanceRange = parent.postExecuteDistanceRange + ) + .addValidator(Validators.doubleArraySize(2)) + .addValidator(Validators.weaklyMonotonicSequentialDoubleArrayValidator()) + .documentation("An optional range the NPC will try to maintain from the target after executing the combat action.") + .add() + .build(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(CombatActionOption.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(CombatActionOption::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data extraData; + protected String id; + protected CombatActionOption.Target actionTarget; + protected double[] postExecuteDistanceRange; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(CombatActionOption.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + protected CombatActionOption() { + } + + public String getId() { + return this.id; + } + + public CombatActionOption.Target getActionTarget() { + return this.actionTarget; + } + + @Nullable + public double[] getPostExecuteDistanceRange() { + return this.postExecuteDistanceRange; + } + + public abstract void execute( + int var1, ArchetypeChunk var2, CommandBuffer var3, Role var4, CombatActionEvaluator var5, ValueStore var6 + ); + + public abstract boolean isBasicAttackAllowed(int var1, ArchetypeChunk var2, CommandBuffer var3, CombatActionEvaluator var4); + + public boolean cancelBasicAttackOnSelect() { + return true; + } + + @Nonnull + @Override + public String toString() { + return "CombatActionOption{extraData=" + + this.extraData + + ", id='" + + this.id + + "', actionTarget=" + + this.actionTarget + + ", postExecuteDistanceRange=" + + Arrays.toString(this.postExecuteDistanceRange) + + "}" + + super.toString(); + } + + @Nonnull + public static CombatActionOption getNothingFor(String id) { + return new CombatActionOption.Nothing(id); + } + + static { + CODEC.register("State", StateCombatAction.class, StateCombatAction.CODEC); + CODEC.register("Ability", AbilityCombatAction.class, AbilityCombatAction.CODEC); + CODEC.register("SelectBasicAttackTarget", BasicAttackTargetCombatAction.class, BasicAttackTargetCombatAction.CODEC); + } + + private static class Nothing extends CombatActionOption { + private final String id; + + public Nothing(String id) { + this.id = id; + this.actionTarget = CombatActionOption.Target.Self; + } + + @Override + public String getId() { + return this.id; + } + + @Override + public void execute( + int index, + ArchetypeChunk archetypeChunk, + CommandBuffer commandBuffer, + Role role, + CombatActionEvaluator evaluator, + ValueStore valueStore + ) { + } + + @Override + public boolean isBasicAttackAllowed( + int selfIndex, ArchetypeChunk archetypeChunk, CommandBuffer commandBuffer, CombatActionEvaluator evaluator + ) { + return true; + } + } + + public static enum Target { + Self, + Hostile, + Friendly; + + public static final EnumCodec CODEC = new EnumCodec<>(CombatActionOption.Target.class) + .documentKey(Self, "Action targets self.") + .documentKey(Hostile, "Action targets any hostile target.") + .documentKey(Friendly, "Action targets any friendly target."); + + private Target() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/combatactions/StateCombatAction.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/combatactions/StateCombatAction.java new file mode 100644 index 0000000..6d0a291 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/evaluator/combatactions/StateCombatAction.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.combatactions; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.evaluator.CombatActionEvaluator; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.valuestore.ValueStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class StateCombatAction extends CombatActionOption { + public static final BuilderCodec CODEC = BuilderCodec.builder( + StateCombatAction.class, StateCombatAction::new, CombatActionOption.BASE_CODEC + ) + .documentation( + "A combat action which switches the NPCs state. Using substate only will switch between combat substates, whereas including the main state can be used to transition out of combat." + ) + .append(new KeyedCodec<>("State", Codec.STRING), (option, s) -> option.state = s, option -> option.state) + .documentation("The main state name.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyString()) + .add() + .append(new KeyedCodec<>("SubState", Codec.STRING), (option, s) -> option.subState = s, option -> option.subState) + .documentation("The substate name.") + .add() + .build(); + protected String state; + protected String subState; + + public StateCombatAction() { + } + + public String getState() { + return this.state; + } + + public String getSubState() { + return this.subState; + } + + @Override + public void execute( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + CommandBuffer commandBuffer, + @Nonnull Role role, + @Nonnull CombatActionEvaluator evaluator, + ValueStore valueStore + ) { + Ref ref = archetypeChunk.getReferenceTo(index); + role.getStateSupport().setState(ref, this.state, this.subState, commandBuffer); + evaluator.completeCurrentAction(true, true); + evaluator.clearTimeout(); + HytaleLogger.Api ctx = CombatActionEvaluator.LOGGER.at(Level.FINEST); + if (ctx.isEnabled()) { + ctx.log("%s: Set state to %s.%s", archetypeChunk.getReferenceTo(index), this.state, this.subState == null ? "Default" : this.subState); + } + } + + @Override + public boolean isBasicAttackAllowed( + int selfIndex, ArchetypeChunk archetypeChunk, CommandBuffer commandBuffer, CombatActionEvaluator evaluator + ) { + return false; + } + + @Nonnull + @Override + public String toString() { + return "StateCombatAction{state='" + this.state + "', subState='" + this.subState + "'}" + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/memory/DamageMemory.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/memory/DamageMemory.java new file mode 100644 index 0000000..5567272 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/memory/DamageMemory.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.memory; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.NPCCombatActionEvaluatorPlugin; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class DamageMemory implements Component { + private float recentDamage; + private float totalCombatDamage; + + public DamageMemory() { + } + + public static ComponentType getComponentType() { + return NPCCombatActionEvaluatorPlugin.get().getDamageMemoryComponentType(); + } + + public float getRecentDamage() { + return this.recentDamage; + } + + public float getTotalCombatDamage() { + return this.totalCombatDamage; + } + + public void addDamage(float damage) { + this.totalCombatDamage += damage; + this.recentDamage += damage; + } + + public void clearRecentDamage() { + this.recentDamage = 0.0F; + } + + public void clearTotalDamage() { + this.totalCombatDamage = 0.0F; + this.clearRecentDamage(); + } + + @Nonnull + @Override + public Component clone() { + DamageMemory damageMemory = new DamageMemory(); + damageMemory.recentDamage = this.recentDamage; + damageMemory.totalCombatDamage = this.totalCombatDamage; + return damageMemory; + } + + @Nonnull + @Override + public String toString() { + return "DamageMemory{recentDamage=" + this.recentDamage + ", totalCombatDamage=" + this.totalCombatDamage + "}" + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/memory/DamageMemorySystems.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/memory/DamageMemorySystems.java new file mode 100644 index 0000000..3e85431 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/memory/DamageMemorySystems.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.memory; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageEventSystem; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DamageMemorySystems { + public DamageMemorySystems() { + } + + public static class CollectDamage extends DamageEventSystem { + private final ComponentType damageMemoryComponentType; + @Nonnull + private final Query query; + + public CollectDamage(ComponentType damageMemoryComponentType) { + this.damageMemoryComponentType = damageMemoryComponentType; + this.query = damageMemoryComponentType; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + DamageMemory memory = archetypeChunk.getComponent(index, this.damageMemoryComponentType); + memory.addDamage(damage.getAmount()); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getInspectDamageGroup(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/memory/TargetMemory.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/memory/TargetMemory.java new file mode 100644 index 0000000..1ec7f71 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/memory/TargetMemory.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.memory; + +import com.hypixel.hytale.builtin.npccombatactionevaluator.NPCCombatActionEvaluatorPlugin; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class TargetMemory implements Component { + private final Int2FloatOpenHashMap knownFriendlies = new Int2FloatOpenHashMap(); + private final List> knownFriendliesList = new ObjectArrayList<>(); + private final Int2FloatOpenHashMap knownHostiles = new Int2FloatOpenHashMap(); + private final List> knownHostilesList = new ObjectArrayList<>(); + private final float rememberFor; + private Ref closestHostile; + + public static ComponentType getComponentType() { + return NPCCombatActionEvaluatorPlugin.get().getTargetMemoryComponentType(); + } + + public TargetMemory(float rememberFor) { + this.rememberFor = rememberFor; + this.knownFriendlies.defaultReturnValue(-1.0F); + this.knownHostiles.defaultReturnValue(-1.0F); + } + + @Nonnull + public Int2FloatOpenHashMap getKnownFriendlies() { + return this.knownFriendlies; + } + + @Nonnull + public List> getKnownFriendliesList() { + return this.knownFriendliesList; + } + + @Nonnull + public Int2FloatOpenHashMap getKnownHostiles() { + return this.knownHostiles; + } + + @Nonnull + public List> getKnownHostilesList() { + return this.knownHostilesList; + } + + public float getRememberFor() { + return this.rememberFor; + } + + public Ref getClosestHostile() { + return this.closestHostile; + } + + public void setClosestHostile(Ref ref) { + this.closestHostile = ref; + } + + @Nonnull + @Override + public Component clone() { + return new TargetMemory(this.rememberFor); + } +} diff --git a/src/com/hypixel/hytale/builtin/npccombatactionevaluator/memory/TargetMemorySystems.java b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/memory/TargetMemorySystems.java new file mode 100644 index 0000000..6dc0fc1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npccombatactionevaluator/memory/TargetMemorySystems.java @@ -0,0 +1,138 @@ +package com.hypixel.hytale.builtin.npccombatactionevaluator.memory; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.systems.RoleSystems; +import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class TargetMemorySystems { + @Nonnull + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + + public TargetMemorySystems() { + } + + public static class Ticking extends EntityTickingSystem { + @Nonnull + private static final String HOSTILE = "hostile"; + @Nonnull + private static final String FRIENDLY = "friendly"; + @Nonnull + private final ComponentType targetMemoryComponentType; + @Nonnull + private final Set> dependencies; + + public Ticking(@Nonnull ComponentType targetMemoryComponentType) { + this.targetMemoryComponentType = targetMemoryComponentType; + this.dependencies = Set.of(new SystemDependency<>(Order.BEFORE, RoleSystems.BehaviourTickSystem.class)); + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Nonnull + @Override + public Query getQuery() { + return this.targetMemoryComponentType; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + TargetMemory targetMemoryComponent = archetypeChunk.getComponent(index, this.targetMemoryComponentType); + + assert targetMemoryComponent != null; + + Int2FloatOpenHashMap hostileMap = targetMemoryComponent.getKnownHostiles(); + List> hostileList = targetMemoryComponent.getKnownHostilesList(); + iterateMemory(dt, index, archetypeChunk, commandBuffer, hostileList, hostileMap, "hostile"); + Int2FloatOpenHashMap friendlyMap = targetMemoryComponent.getKnownFriendlies(); + List> friendlyList = targetMemoryComponent.getKnownFriendliesList(); + iterateMemory(dt, index, archetypeChunk, commandBuffer, friendlyList, friendlyMap, "friendly"); + Ref closestHostileRef = targetMemoryComponent.getClosestHostile(); + if (closestHostileRef != null && !isValidTarget(closestHostileRef, commandBuffer)) { + targetMemoryComponent.setClosestHostile(null); + } + } + + private static void iterateMemory( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull CommandBuffer commandBuffer, + @Nonnull List> targetsList, + @Nonnull Int2FloatOpenHashMap targetsMap, + @Nonnull String type + ) { + for (int i = targetsList.size() - 1; i >= 0; i--) { + Ref ref = targetsList.get(i); + if (!isValidTarget(ref, commandBuffer)) { + removeEntry(index, archetypeChunk, i, ref, targetsList, targetsMap, type); + } else { + float timeRemaining = targetsMap.mergeFloat(ref.getIndex(), -dt, Float::sum); + if (timeRemaining <= 0.0F) { + removeEntry(index, archetypeChunk, i, ref, targetsList, targetsMap, type); + } + } + } + } + + private static boolean isValidTarget(@Nonnull Ref targetRef, @Nonnull CommandBuffer commandBuffer) { + if (!targetRef.isValid()) { + return false; + } else if (commandBuffer.getArchetype(targetRef).contains(DeathComponent.getComponentType())) { + return false; + } else { + Player targetPlayerComponent = commandBuffer.getComponent(targetRef, Player.getComponentType()); + return targetPlayerComponent == null || targetPlayerComponent.getGameMode() == GameMode.Adventure; + } + } + + private static void removeEntry( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + int targetIndex, + @Nonnull Ref targetRef, + @Nonnull List> targetsList, + @Nonnull Int2FloatOpenHashMap targetsMap, + @Nonnull String type + ) { + targetsMap.remove(targetRef.getIndex()); + targetsList.remove(targetIndex); + HytaleLogger.Api context = TargetMemorySystems.LOGGER.at(Level.FINEST); + if (context.isEnabled()) { + context.log("%s: Removed lost %s target %s", archetypeChunk.getReferenceTo(index), type, targetRef); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/npceditor/NPCEditorPlugin.java b/src/com/hypixel/hytale/builtin/npceditor/NPCEditorPlugin.java new file mode 100644 index 0000000..03db9d1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npceditor/NPCEditorPlugin.java @@ -0,0 +1,82 @@ +package com.hypixel.hytale.builtin.npceditor; + +import com.hypixel.hytale.builtin.asseteditor.AssetEditorPlugin; +import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorSelectAssetEvent; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorPreviewCameraSettings; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUpdateModelPreview; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderInfo; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.spawning.ISpawnableWithModel; +import com.hypixel.hytale.server.spawning.SpawningContext; +import javax.annotation.Nonnull; + +public class NPCEditorPlugin extends JavaPlugin { + private static final AssetEditorPreviewCameraSettings DEFAULT_PREVIEW_CAMERA_SETTINGS = new AssetEditorPreviewCameraSettings( + 0.25F, new Vector3f(0.0F, 75.0F, 0.0F), new Vector3f(0.0F, 0.7853F, 0.0F) + ); + + public NPCEditorPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + AssetEditorPlugin.get().getAssetTypeRegistry().registerAssetType(new NPCRoleAssetTypeHandler()); + this.getEventRegistry().register(AssetEditorSelectAssetEvent.class, NPCEditorPlugin::onSelectAsset); + } + + private static void onSelectAsset(@Nonnull AssetEditorSelectAssetEvent event) { + String assetType = event.getAssetType(); + if ("NPCRole".equals(assetType)) { + NPCPlugin npcPlugin = NPCPlugin.get(); + String key = ModelAsset.getAssetStore().decodeFilePathKey(event.getAssetFilePath().path()); + int roleIndex = npcPlugin.getIndex(key); + npcPlugin.forceValidation(roleIndex); + BuilderInfo roleBuilderInfo = npcPlugin.getRoleBuilderInfo(roleIndex); + if (roleBuilderInfo == null) { + throw new IllegalStateException("Can't find a matching role builder"); + } + + if (!npcPlugin.testAndValidateRole(roleBuilderInfo)) { + throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.validation_failed")); + } + + Builder roleBuilder = npcPlugin.tryGetCachedValidRole(roleIndex); + if (roleBuilder == null) { + throw new IllegalArgumentException("Can't find a matching role builder"); + } + + if (!(roleBuilder instanceof ISpawnableWithModel spawnable)) { + return; + } + + if (!roleBuilder.isSpawnable()) { + return; + } + + SpawningContext spawningContext = new SpawningContext(); + if (!spawningContext.setSpawnable(spawnable)) { + return; + } + + Model model = spawningContext.getModel(); + if (model == null) { + return; + } + + com.hypixel.hytale.protocol.Model modelPacket = model.toPacket(); + event.getEditorClient() + .getPacketHandler() + .write(new AssetEditorUpdateModelPreview(event.getAssetFilePath().toPacket(), modelPacket, null, DEFAULT_PREVIEW_CAMERA_SETTINGS)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/npceditor/NPCRoleAssetTypeHandler.java b/src/com/hypixel/hytale/builtin/npceditor/NPCRoleAssetTypeHandler.java new file mode 100644 index 0000000..72eca3e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/npceditor/NPCRoleAssetTypeHandler.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.builtin.npceditor; + +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.builtin.asseteditor.AssetPath; +import com.hypixel.hytale.builtin.asseteditor.EditorClient; +import com.hypixel.hytale.builtin.asseteditor.assettypehandler.AssetTypeHandler; +import com.hypixel.hytale.builtin.asseteditor.assettypehandler.JsonTypeHandler; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetType; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorEditorType; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.npc.NPCPlugin; +import java.nio.file.Path; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; + +public class NPCRoleAssetTypeHandler extends JsonTypeHandler { + public static final String TYPE_ID = "NPCRole"; + + public NPCRoleAssetTypeHandler() { + super(new AssetEditorAssetType("NPCRole", null, false, NPCPlugin.ROLE_ASSETS_PATH, ".json", AssetEditorEditorType.JsonSource)); + } + + @Nonnull + @Override + public AssetTypeHandler.AssetLoadResult loadAssetFromDocument( + AssetPath assetPath, Path dataPath, BsonDocument document, AssetUpdateQuery updateQuery, EditorClient editorClient + ) { + NPCPlugin.get().getBuilderManager().assetEditorLoadFile(dataPath); + return AssetTypeHandler.AssetLoadResult.ASSETS_CHANGED; + } + + @Nonnull + @Override + public AssetTypeHandler.AssetLoadResult unloadAsset(@Nonnull AssetPath path, AssetUpdateQuery updateQuery) { + Path rootPath = AssetModule.get().getAssetPack(path.packId()).getRoot(); + NPCPlugin.get().getBuilderManager().assetEditorRemoveFile(rootPath.resolve(path.path()).toAbsolutePath()); + return AssetTypeHandler.AssetLoadResult.ASSETS_CHANGED; + } + + @Nonnull + @Override + public AssetTypeHandler.AssetLoadResult restoreOriginalAsset(@Nonnull AssetPath originalAssetPath, AssetUpdateQuery updateQuery) { + Path rootPath = AssetModule.get().getAssetPack(originalAssetPath.packId()).getRoot(); + NPCPlugin.get().getBuilderManager().assetEditorLoadFile(rootPath.resolve(originalAssetPath.path()).toAbsolutePath()); + return AssetTypeHandler.AssetLoadResult.ASSETS_CHANGED; + } + + @Nonnull + @Override + public AssetUpdateQuery getDefaultUpdateQuery() { + return AssetUpdateQuery.DEFAULT_NO_REBUILD; + } +} diff --git a/src/com/hypixel/hytale/builtin/parkour/ParkourCheckpoint.java b/src/com/hypixel/hytale/builtin/parkour/ParkourCheckpoint.java new file mode 100644 index 0000000..36c65cb --- /dev/null +++ b/src/com/hypixel/hytale/builtin/parkour/ParkourCheckpoint.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.builtin.parkour; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ParkourCheckpoint implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(ParkourCheckpoint.class, ParkourCheckpoint::new) + .append( + new KeyedCodec<>("CheckpointIndex", Codec.INTEGER), + (parkourCheckpoint, integer) -> parkourCheckpoint.index = integer, + parkourCheckpoint -> parkourCheckpoint.index + ) + .add() + .build(); + protected int index; + + public static ComponentType getComponentType() { + return ParkourPlugin.get().getParkourCheckpointComponentType(); + } + + public ParkourCheckpoint(int index) { + this.index = index; + } + + protected ParkourCheckpoint() { + } + + public int getIndex() { + return this.index; + } + + @Nonnull + @Override + public Component clone() { + ParkourCheckpoint parkourCheckpoint = new ParkourCheckpoint(); + parkourCheckpoint.index = this.index; + return parkourCheckpoint; + } +} diff --git a/src/com/hypixel/hytale/builtin/parkour/ParkourCheckpointSystems.java b/src/com/hypixel/hytale/builtin/parkour/ParkourCheckpointSystems.java new file mode 100644 index 0000000..ea59efe --- /dev/null +++ b/src/com/hypixel/hytale/builtin/parkour/ParkourCheckpointSystems.java @@ -0,0 +1,203 @@ +package com.hypixel.hytale.builtin.parkour; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.OrderPriority; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.system.PlayerSpatialSystem; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + +public class ParkourCheckpointSystems { + public ParkourCheckpointSystems() { + } + + public static class EnsureNetworkSendable extends HolderSystem { + private final Query query = Query.and(ParkourCheckpoint.getComponentType(), Query.not(NetworkId.getComponentType())); + + public EnsureNetworkSendable() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.addComponent(NetworkId.getComponentType(), new NetworkId(store.getExternalData().takeNextNetworkId())); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + } + + public static class Init extends RefSystem { + private final ComponentType parkourCheckpointComponentType; + @Nonnull + private final ComponentType uuidComponentComponentType; + @Nonnull + private final Query query; + + public Init(ComponentType parkourCheckpointComponentType) { + this.parkourCheckpointComponentType = parkourCheckpointComponentType; + this.uuidComponentComponentType = UUIDComponent.getComponentType(); + ComponentType transformComponentType = TransformComponent.getComponentType(); + this.query = Query.and(parkourCheckpointComponentType, transformComponentType); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + ParkourCheckpoint entity = store.getComponent(ref, this.parkourCheckpointComponentType); + ParkourPlugin.get().updateLastIndex(entity.getIndex()); + ParkourPlugin.get().getCheckpointUUIDMap().put(entity.getIndex(), store.getComponent(ref, this.uuidComponentComponentType).getUuid()); + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + } + + public static class Ticking extends EntityTickingSystem { + private final ComponentType parkourCheckpointComponentType; + private final ComponentType playerComponentType; + private final ResourceType, EntityStore>> playerSpatialComponent; + private final ComponentType transformComponentType; + @Nonnull + private final ComponentType uuidComponentType; + @Nonnull + private final Query query; + @Nonnull + private final Set> dependencies; + + public Ticking( + ComponentType parkourCheckpointComponentType, + ComponentType playerComponentType, + ResourceType, EntityStore>> playerSpatialComponent + ) { + this.parkourCheckpointComponentType = parkourCheckpointComponentType; + this.playerComponentType = playerComponentType; + this.playerSpatialComponent = playerSpatialComponent; + this.transformComponentType = TransformComponent.getComponentType(); + this.uuidComponentType = UUIDComponent.getComponentType(); + this.query = Query.and(parkourCheckpointComponentType, this.transformComponentType); + this.dependencies = Set.of(new SystemDependency<>(Order.AFTER, PlayerSpatialSystem.class, OrderPriority.CLOSEST)); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + int lastIndex = ParkourPlugin.get().getLastIndex(); + if (lastIndex != 0) { + int parkourCheckpointIndex = archetypeChunk.getComponent(index, this.parkourCheckpointComponentType).getIndex(); + SpatialResource, EntityStore> spatialResource = store.getResource(this.playerSpatialComponent); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + Vector3d position = archetypeChunk.getComponent(index, this.transformComponentType).getPosition(); + spatialResource.getSpatialStructure().ordered(position, 1.0, results); + ParkourPlugin parkourPlugin = ParkourPlugin.get(); + Object2IntMap currentCheckpointByPlayerMap = parkourPlugin.getCurrentCheckpointByPlayerMap(); + Object2LongMap startTimeByPlayerMap = parkourPlugin.getStartTimeByPlayerMap(); + + for (int i = 0; i < results.size(); i++) { + Ref otherReference = results.get(i); + UUIDComponent uuidComponent = commandBuffer.getComponent(otherReference, this.uuidComponentType); + UUID playerUuid = uuidComponent.getUuid(); + Player player = commandBuffer.getComponent(otherReference, this.playerComponentType); + handleCheckpointUpdate(currentCheckpointByPlayerMap, startTimeByPlayerMap, player, playerUuid, parkourCheckpointIndex, lastIndex); + } + } + } + + private static void handleCheckpointUpdate( + @Nonnull Object2IntMap currentCheckpointByPlayerMap, + @Nonnull Object2LongMap startTimeByPlayerMap, + @Nonnull Player player, + UUID playerUuid, + int checkpointIndex, + int lastIndex + ) { + int currentCheckpoint = currentCheckpointByPlayerMap.getOrDefault(playerUuid, -1); + if (currentCheckpoint == -1) { + if (checkpointIndex != 0) { + return; + } + + currentCheckpointByPlayerMap.put(playerUuid, 0); + startTimeByPlayerMap.put(playerUuid, System.nanoTime()); + player.sendMessage(Message.translation("server.general.parkourRun.started")); + } else { + if (currentCheckpoint + 1 != checkpointIndex) { + return; + } + + if (lastIndex == checkpointIndex) { + long completionTimeNano = System.nanoTime() - startTimeByPlayerMap.getLong(playerUuid); + long completionTimeMillis = TimeUnit.NANOSECONDS.toMillis(completionTimeNano); + player.sendMessage(Message.translation("server.general.parkourRun.completed").param("seconds", completionTimeMillis / 1000.0)); + currentCheckpointByPlayerMap.remove(playerUuid, currentCheckpoint); + return; + } + + currentCheckpointByPlayerMap.put(playerUuid, checkpointIndex); + player.sendMessage( + Message.translation("server.general.parkourRun.checkpointReached").param("checkpoint", checkpointIndex).param("checkpoints", lastIndex) + ); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/parkour/ParkourCommand.java b/src/com/hypixel/hytale/builtin/parkour/ParkourCommand.java new file mode 100644 index 0000000..1941c9c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/parkour/ParkourCommand.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.builtin.parkour; + +import com.hypixel.hytale.builtin.parkour.commands.CheckpointAddCommand; +import com.hypixel.hytale.builtin.parkour.commands.CheckpointRemoveCommand; +import com.hypixel.hytale.builtin.parkour.commands.CheckpointResetCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class ParkourCommand extends AbstractCommandCollection { + public ParkourCommand() { + super("checkpoint", "server.commands.checkpoint.desc"); + this.addSubCommand(new CheckpointAddCommand()); + this.addSubCommand(new CheckpointRemoveCommand()); + this.addSubCommand(new CheckpointResetCommand()); + } +} diff --git a/src/com/hypixel/hytale/builtin/parkour/ParkourPlugin.java b/src/com/hypixel/hytale/builtin/parkour/ParkourPlugin.java new file mode 100644 index 0000000..b15caa1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/parkour/ParkourPlugin.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.builtin.parkour; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; +import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; +import java.util.Collections; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class ParkourPlugin extends JavaPlugin { + protected static ParkourPlugin instance; + public static final String PARKOUR_CHECKPOINT_MODEL_ID = "Objective_Location_Marker"; + private final Object2IntMap currentCheckpointByPlayerMap = new Object2IntOpenHashMap<>(); + private final Object2LongMap startTimeByPlayerMap = new Object2LongOpenHashMap<>(); + private final Int2ObjectMap checkpointUUIDMap = new Int2ObjectOpenHashMap<>(); + private ComponentType parkourCheckpointComponentType; + private Model parkourCheckpointModel; + private int lastIndex; + + public static ParkourPlugin get() { + return instance; + } + + public ParkourPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + public ComponentType getParkourCheckpointComponentType() { + return this.parkourCheckpointComponentType; + } + + public Model getParkourCheckpointModel() { + return this.parkourCheckpointModel; + } + + public Object2IntMap getCurrentCheckpointByPlayerMap() { + return this.currentCheckpointByPlayerMap; + } + + public Object2LongMap getStartTimeByPlayerMap() { + return this.startTimeByPlayerMap; + } + + public Int2ObjectMap getCheckpointUUIDMap() { + return this.checkpointUUIDMap; + } + + public int getLastIndex() { + return this.lastIndex; + } + + @Override + protected void setup() { + instance = this; + this.parkourCheckpointComponentType = this.getEntityStoreRegistry() + .registerComponent(ParkourCheckpoint.class, "ParkourCheckpoint", ParkourCheckpoint.CODEC); + EntityModule entityModule = EntityModule.get(); + ComponentType playerComponentType = entityModule.getPlayerComponentType(); + ResourceType, EntityStore>> playerSpatialComponent = entityModule.getPlayerSpatialResourceType(); + this.getEntityStoreRegistry().registerSystem(new ParkourCheckpointSystems.EnsureNetworkSendable()); + this.getEntityStoreRegistry().registerSystem(new ParkourCheckpointSystems.Init(this.parkourCheckpointComponentType)); + this.getEntityStoreRegistry() + .registerSystem(new ParkourCheckpointSystems.Ticking(this.parkourCheckpointComponentType, playerComponentType, playerSpatialComponent)); + this.getCommandRegistry().registerCommand(new ParkourCommand()); + } + + @Override + protected void start() { + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset("Objective_Location_Marker"); + if (modelAsset == null) { + throw new IllegalStateException(String.format("Default parkour checkpoint model '%s' not found", "Objective_Location_Marker")); + } else { + this.parkourCheckpointModel = Model.createUnitScaleModel(modelAsset); + } + } + + public void updateLastIndex(int index) { + if (index > this.lastIndex) { + this.lastIndex = index; + } + } + + public void updateLastIndex() { + this.lastIndex = Collections.max(this.checkpointUUIDMap.keySet()); + } + + public void resetPlayer(UUID playerUuid) { + this.currentCheckpointByPlayerMap.replace(playerUuid, -1); + } +} diff --git a/src/com/hypixel/hytale/builtin/parkour/commands/CheckpointAddCommand.java b/src/com/hypixel/hytale/builtin/parkour/commands/CheckpointAddCommand.java new file mode 100644 index 0000000..91c93a6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/parkour/commands/CheckpointAddCommand.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.builtin.parkour.commands; + +import com.hypixel.hytale.builtin.parkour.ParkourCheckpoint; +import com.hypixel.hytale.builtin.parkour.ParkourPlugin; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.nameplate.Nameplate; +import com.hypixel.hytale.server.core.modules.entity.component.HiddenFromAdventurePlayers; +import com.hypixel.hytale.server.core.modules.entity.component.Intangible; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.PersistentModel; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class CheckpointAddCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CHECKPOINT_ADD_FAILED = Message.translation("server.commands.checkpoint.add.failed"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHECKPOINT_ADD_SUCCESS = Message.translation("server.commands.checkpoint.add.success"); + @Nonnull + private final RequiredArg indexArg = this.withRequiredArg("index", "server.commands.checkpoint.add.index.desc", ArgTypes.INTEGER); + + public CheckpointAddCommand() { + super("add", "server.commands.checkpoint.add.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Integer index = this.indexArg.get(context); + Int2ObjectMap checkpointUUIDMap = ParkourPlugin.get().getCheckpointUUIDMap(); + if (checkpointUUIDMap.containsKey(index)) { + context.sendMessage(MESSAGE_COMMANDS_CHECKPOINT_ADD_FAILED); + } else { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + Vector3f rotation = transformComponent.getRotation(); + Holder holder = EntityStore.REGISTRY.newHolder(); + holder.addComponent(ParkourCheckpoint.getComponentType(), new ParkourCheckpoint(index)); + Model model = ParkourPlugin.get().getParkourCheckpointModel(); + holder.addComponent(ModelComponent.getComponentType(), new ModelComponent(model)); + holder.addComponent(PersistentModel.getComponentType(), new PersistentModel(model.toReference())); + holder.addComponent(Nameplate.getComponentType(), new Nameplate(Integer.toString(index))); + TransformComponent transform = new TransformComponent(position, rotation); + holder.addComponent(TransformComponent.getComponentType(), transform); + holder.ensureComponent(UUIDComponent.getComponentType()); + holder.ensureComponent(Intangible.getComponentType()); + holder.ensureComponent(HiddenFromAdventurePlayers.getComponentType()); + store.addEntity(holder, AddReason.SPAWN); + context.sendMessage(MESSAGE_COMMANDS_CHECKPOINT_ADD_SUCCESS); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/parkour/commands/CheckpointRemoveCommand.java b/src/com/hypixel/hytale/builtin/parkour/commands/CheckpointRemoveCommand.java new file mode 100644 index 0000000..9a361aa --- /dev/null +++ b/src/com/hypixel/hytale/builtin/parkour/commands/CheckpointRemoveCommand.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.builtin.parkour.commands; + +import com.hypixel.hytale.builtin.parkour.ParkourPlugin; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class CheckpointRemoveCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CHECKPOINT_REMOVE_FAILED = Message.translation("server.commands.checkpoint.remove.failed"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHECKPOINT_REMOVE_SUCCESS = Message.translation("server.commands.checkpoint.remove.success"); + @Nonnull + private final RequiredArg indexArg = this.withRequiredArg("index", "server.commands.checkpoint.remove.index.desc", ArgTypes.INTEGER); + + public CheckpointRemoveCommand() { + super("remove", "server.commands.checkpoint.remove.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + int index = this.indexArg.get(context); + Int2ObjectMap checkpointUUIDMap = ParkourPlugin.get().getCheckpointUUIDMap(); + UUID uuid = checkpointUUIDMap.get(index); + if (uuid == null) { + context.sendMessage(MESSAGE_COMMANDS_CHECKPOINT_REMOVE_FAILED); + } else { + Ref ref = store.getExternalData().getRefFromUUID(uuid); + if (ref != null && ref.isValid()) { + store.removeEntity(ref, RemoveReason.REMOVE); + checkpointUUIDMap.remove(index); + ParkourPlugin.get().updateLastIndex(); + context.sendMessage(MESSAGE_COMMANDS_CHECKPOINT_REMOVE_SUCCESS); + } else { + context.sendMessage(MESSAGE_COMMANDS_CHECKPOINT_REMOVE_FAILED); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/parkour/commands/CheckpointResetCommand.java b/src/com/hypixel/hytale/builtin/parkour/commands/CheckpointResetCommand.java new file mode 100644 index 0000000..5648f64 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/parkour/commands/CheckpointResetCommand.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.builtin.parkour.commands; + +import com.hypixel.hytale.builtin.parkour.ParkourPlugin; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class CheckpointResetCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CHECKPOINT_RESET_SUCCESS = Message.translation("server.commands.checkpoint.reset.success"); + + public CheckpointResetCommand() { + super("reset", "server.commands.checkpoint.reset.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + ParkourPlugin.get().resetPlayer(uuidComponent.getUuid()); + context.sendMessage(MESSAGE_COMMANDS_CHECKPOINT_RESET_SUCCESS); + } +} diff --git a/src/com/hypixel/hytale/builtin/path/PathPlugin.java b/src/com/hypixel/hytale/builtin/path/PathPlugin.java new file mode 100644 index 0000000..1620be6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/PathPlugin.java @@ -0,0 +1,156 @@ +package com.hypixel.hytale.builtin.path; + +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.path.commands.PrefabPathCommand; +import com.hypixel.hytale.builtin.path.commands.WorldPathCommand; +import com.hypixel.hytale.builtin.path.entities.PatrolPathMarkerEntity; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.OrderPriority; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.spatial.KDTree; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.system.WorldEventSystem; +import com.hypixel.hytale.event.EventRegistry; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.HytaleServerConfig; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.command.system.CommandRegistry; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.prefab.event.PrefabPasteEvent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class PathPlugin extends JavaPlugin { + public static final KeyedCodec PATH_MARKER_MODEL = new KeyedCodec<>("PathMarkerModel", Codec.STRING); + public static final String DEFAULT_PATH_MARKER_MODEL = "NPC_Path_Marker"; + private static PathPlugin instance; + private ResourceType worldPathDataResourceType; + private ResourceType, EntityStore>> prefabPathSpatialResource; + private ComponentType worldPathBuilderComponentType; + private Model pathMarkerModel; + + public static PathPlugin get() { + return instance; + } + + public PathPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + instance = this; + CommandRegistry commandRegistry = this.getCommandRegistry(); + EventRegistry eventRegistry = this.getEventRegistry(); + ComponentRegistryProxy entityStoreRegistry = this.getEntityStoreRegistry(); + if (BuilderToolsPlugin.get().isEnabled()) { + commandRegistry.registerCommand(new PrefabPathCommand()); + } + + commandRegistry.registerCommand(new WorldPathCommand()); + eventRegistry.register(LoadedAssetsEvent.class, ModelAsset.class, this::onModelsChanged); + entityStoreRegistry.registerSystem(new PathPlugin.PrefabPasteEventSystem()); + this.getEntityRegistry().registerEntity("PatrolPathMarker", PatrolPathMarkerEntity.class, PatrolPathMarkerEntity::new, PatrolPathMarkerEntity.CODEC); + this.worldPathDataResourceType = entityStoreRegistry.registerResource(WorldPathData.class, WorldPathData::new); + this.prefabPathSpatialResource = entityStoreRegistry.registerSpatialResource(() -> new KDTree<>(Ref::isValid)); + this.worldPathBuilderComponentType = entityStoreRegistry.registerComponent(WorldPathBuilder.class, () -> { + throw new UnsupportedOperationException("Not implemented"); + }); + entityStoreRegistry.registerSystem(new PrefabPathSystems.AddOrRemove()); + entityStoreRegistry.registerSystem(new PrefabPathSystems.AddedFromWorldGen()); + entityStoreRegistry.registerSystem(new PathSpatialSystem(this.prefabPathSpatialResource)); + entityStoreRegistry.registerSystem(new PrefabPathSystems.NameplateHolderSystem()); + entityStoreRegistry.registerSystem(new PrefabPathSystems.NameplateRefChangeSystem()); + entityStoreRegistry.registerSystem(new PrefabPathSystems.WorldGenChangeSystem()); + entityStoreRegistry.registerSystem(new PrefabPathSystems.PrefabPlaceEntityEventSystem()); + } + + @Override + protected void start() { + HytaleServerConfig.Module config = HytaleServer.get().getConfig().getModule("PathPlugin"); + String pathMarkerModelId = config.getData(PATH_MARKER_MODEL).orElse("NPC_Path_Marker"); + DefaultAssetMap modelAssetMap = ModelAsset.getAssetMap(); + ModelAsset modelAsset = modelAssetMap.getAsset(pathMarkerModelId); + if (modelAsset == null) { + this.getLogger().at(Level.SEVERE).log("Path marker model %s does not exist"); + modelAsset = modelAssetMap.getAsset("NPC_Path_Marker"); + if (modelAsset == null) { + throw new IllegalStateException(String.format("Default path marker '%s' not found", "NPC_Path_Marker")); + } + } + + this.pathMarkerModel = Model.createUnitScaleModel(modelAsset); + } + + public ResourceType getWorldPathDataResourceType() { + return this.worldPathDataResourceType; + } + + public ResourceType, EntityStore>> getPrefabPathSpatialResource() { + return this.prefabPathSpatialResource; + } + + public ComponentType getWorldPathBuilderComponentType() { + return this.worldPathBuilderComponentType; + } + + public Model getPathMarkerModel() { + return this.pathMarkerModel; + } + + protected void onModelsChanged(@Nonnull LoadedAssetsEvent> event) { + if (this.pathMarkerModel != null) { + ModelAsset modelAsset = event.getLoadedAssets().get(this.pathMarkerModel.getModelAssetId()); + if (modelAsset != null) { + this.pathMarkerModel = Model.createUnitScaleModel(modelAsset); + } + } + } + + private static class PrefabPasteEventSystem extends WorldEventSystem { + @Nonnull + private final Set> dependencies = Set.of( + new SystemDependency<>(Order.AFTER, BuilderToolsPlugin.PrefabPasteEventSystem.class, OrderPriority.CLOSEST) + ); + + protected PrefabPasteEventSystem() { + super(PrefabPasteEvent.class); + } + + public void handle(@Nonnull Store store, @Nonnull CommandBuffer commandBuffer, @Nonnull PrefabPasteEvent event) { + if (!event.isPasteStart()) { + ConcurrentHashMap pastedPrefabPathUUIDMap = BuilderToolsPlugin.get().getPastedPrefabPathUUIDMap().get(event.getPrefabId()); + if (pastedPrefabPathUUIDMap != null) { + WorldPathData worldPathDataResource = store.getResource(WorldPathData.getResourceType()); + + for (UUID value : pastedPrefabPathUUIDMap.values()) { + worldPathDataResource.compactPrefabPath(0, value); + } + } + } + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/path/PathSpatialSystem.java b/src/com/hypixel/hytale/builtin/path/PathSpatialSystem.java new file mode 100644 index 0000000..8e6f199 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/PathSpatialSystem.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.builtin.path; + +import com.hypixel.hytale.builtin.path.entities.PatrolPathMarkerEntity; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.spatial.SpatialSystem; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PathSpatialSystem extends SpatialSystem { + private static final Archetype QUERY = Archetype.of(PatrolPathMarkerEntity.getComponentType(), TransformComponent.getComponentType()); + + public PathSpatialSystem(ResourceType, EntityStore>> resourceType) { + super(resourceType); + } + + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + super.tick(dt, systemIndex, store); + } + + @Nonnull + @Override + public Vector3d getPosition(@Nonnull ArchetypeChunk archetypeChunk, int index) { + return archetypeChunk.getComponent(index, TransformComponent.getComponentType()).getPosition(); + } +} diff --git a/src/com/hypixel/hytale/builtin/path/PrefabPathCollection.java b/src/com/hypixel/hytale/builtin/path/PrefabPathCollection.java new file mode 100644 index 0000000..ebc3987 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/PrefabPathCollection.java @@ -0,0 +1,152 @@ +package com.hypixel.hytale.builtin.path; + +import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.builtin.path.path.IPrefabPath; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabPathCollection { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final int worldgenId; + private final Map paths = new Object2ObjectOpenHashMap<>(); + private final Int2ObjectMap pathsByFriendlyName = new Int2ObjectOpenHashMap<>(); + + public PrefabPathCollection(int id) { + this.worldgenId = id; + } + + @Nullable + public IPrefabPath getNearestPrefabPath( + int nameIndex, @Nonnull Vector3d position, Set disallowedPaths, @Nonnull ComponentAccessor componentAccessor + ) { + PrefabPathCollection.PathSet set = this.pathsByFriendlyName.getOrDefault(nameIndex, null); + return set == null ? null : set.getNearestPath(position, disallowedPaths, componentAccessor); + } + + public IPrefabPath getPath(UUID id) { + return this.paths.getOrDefault(id, null); + } + + public IPrefabPath getOrConstructPath( + @Nonnull UUID id, @Nonnull String name, @Nonnull Int2ObjectConcurrentHashMap.IntBiObjFunction pathGenerator + ) { + IPrefabPath path = this.paths.computeIfAbsent(id, k -> { + LOGGER.at(Level.FINER).log("Adding path %s.%s", this.worldgenId, k); + return pathGenerator.apply(this.worldgenId, k, name); + }); + int nameIndex = AssetRegistry.getOrCreateTagIndex(name); + PrefabPathCollection.PathSet set = this.pathsByFriendlyName.computeIfAbsent(nameIndex, s -> new PrefabPathCollection.PathSet()); + set.add(path); + return path; + } + + @Nullable + public IPrefabPath getNearestPrefabPath( + @Nonnull Vector3d position, @Nullable Set disallowedPaths, @Nonnull ComponentAccessor componentAccessor + ) { + IPrefabPath nearest = null; + double minDist2 = Double.MAX_VALUE; + + for (IPrefabPath path : this.paths.values()) { + if (disallowedPaths == null || !disallowedPaths.contains(path.getId())) { + double dist2 = position.distanceSquaredTo(path.getNearestWaypointPosition(position, componentAccessor)); + if (dist2 < minDist2) { + nearest = path; + minDist2 = dist2; + } + } + } + + return nearest; + } + + public void removePathWaypoint(UUID id, int index) { + this.removePathWaypoint(id, index, false); + } + + public void unloadPathWaypoint(UUID id, int index) { + this.removePathWaypoint(id, index, true); + } + + private void removePathWaypoint(UUID id, int index, boolean unload) { + IPrefabPath path = this.getPath(id); + LOGGER.at(Level.FINER).log("%s waypoint %s from path %s.%s", unload ? "Unloading" : "Removing", index, this.worldgenId, id); + if (path == null) { + LOGGER.at(Level.SEVERE).log("Path %s.%s not found", this.worldgenId, id); + } else { + if (unload) { + path.unloadWaypoint(index); + } else { + path.removeWaypoint(index, this.worldgenId); + } + + if (path.length() == 0 || !path.hasLoadedWaypoints()) { + LOGGER.at(Level.FINER).log("%s path %s.%s", unload ? "Unloading" : "Removing", this.worldgenId, id); + this.removePath(id); + } + } + } + + public void removePath(UUID id) { + IPrefabPath removed = this.paths.remove(id); + this.pathsByFriendlyName.get(AssetRegistry.getTagIndex(removed.getName())).remove(removed); + } + + public boolean isEmpty() { + return this.paths.isEmpty(); + } + + public void forEach(BiConsumer consumer) { + this.paths.forEach(consumer); + } + + private static class PathSet { + private final List paths = new ObjectArrayList<>(); + + private PathSet() { + } + + public void add(IPrefabPath path) { + this.paths.add(path); + } + + public void remove(IPrefabPath path) { + this.paths.remove(path); + } + + @Nullable + public IPrefabPath getNearestPath(@Nonnull Vector3d position, @Nullable Set disallowedPaths, ComponentAccessor componentAccessor) { + IPrefabPath nearest = null; + double minDist2 = Double.MAX_VALUE; + + for (int i = 0; i < this.paths.size(); i++) { + IPrefabPath path = this.paths.get(i); + if (disallowedPaths == null || !disallowedPaths.contains(path.getId())) { + Vector3d nearestWp = path.getNearestWaypointPosition(position, componentAccessor); + double dist2 = position.distanceSquaredTo(nearestWp); + if (dist2 < minDist2) { + nearest = path; + minDist2 = dist2; + } + } + } + + return nearest; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/path/PrefabPathSystems.java b/src/com/hypixel/hytale/builtin/path/PrefabPathSystems.java new file mode 100644 index 0000000..8176da8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/PrefabPathSystems.java @@ -0,0 +1,326 @@ +package com.hypixel.hytale.builtin.path; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.path.entities.PatrolPathMarkerEntity; +import com.hypixel.hytale.builtin.path.path.IPrefabPath; +import com.hypixel.hytale.builtin.path.path.PatrolPath; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.WorldEventSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.nameplate.Nameplate; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.DisplayNameComponent; +import com.hypixel.hytale.server.core.modules.entity.component.FromWorldGen; +import com.hypixel.hytale.server.core.modules.entity.component.HiddenFromAdventurePlayers; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.WorldGenId; +import com.hypixel.hytale.server.core.modules.entity.system.ModelSystems; +import com.hypixel.hytale.server.core.prefab.PrefabCopyableComponent; +import com.hypixel.hytale.server.core.prefab.event.PrefabPlaceEntityEvent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.nio.charset.StandardCharsets; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class PrefabPathSystems { + public PrefabPathSystems() { + } + + public static class AddOrRemove extends HolderSystem { + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nullable + private static final ComponentType PATH_MARKER_ENTITY_COMPONENT_TYPE = PatrolPathMarkerEntity.getComponentType(); + private static final ComponentType MODEL_COMPONENT_TYPE = ModelComponent.getComponentType(); + private static final ResourceType STORE_WORLD_PATH_DATA_RESOURCE_TYPE = WorldPathData.getResourceType(); + private static final ComponentType WORLD_GEN_ID_COMPONENT_TYPE = WorldGenId.getComponentType(); + private static final Set> DEPENDENCIES = Set.of(new SystemDependency<>(Order.BEFORE, ModelSystems.ModelSpawned.class)); + + public AddOrRemove() { + } + + @Nullable + @Override + public Query getQuery() { + return PATH_MARKER_ENTITY_COMPONENT_TYPE; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + PatrolPathMarkerEntity pathMarker = holder.getComponent(PATH_MARKER_ENTITY_COMPONENT_TYPE); + WorldPathData worldPathData = store.getResource(STORE_WORLD_PATH_DATA_RESOURCE_TYPE); + WorldGenId worldGenIdComponent = holder.getComponent(WORLD_GEN_ID_COMPONENT_TYPE); + int worldgenId = worldGenIdComponent != null ? worldGenIdComponent.getWorldGenId() : 0; + String pathName = pathMarker.getPathName(); + UUID pathId = pathMarker.getPathId(); + if (pathId == null) { + pathId = UUID.nameUUIDFromBytes((pathName + worldgenId).getBytes(StandardCharsets.UTF_8)); + pathMarker.setPathId(pathId); + int lastIndex = pathName.lastIndexOf(126); + if (lastIndex != -1) { + pathMarker.setPathName(pathName.substring(0, lastIndex)); + pathMarker.markNeedsSave(); + LOGGER.at(Level.INFO).log("Migrating path marker from path %s to use new UUID %s", pathName, pathId); + } + } + + IPrefabPath path = worldPathData.getOrConstructPrefabPath(worldgenId, pathId, pathName, PatrolPath::new); + path.addLoadedWaypoint(pathMarker, pathMarker.getTempPathLength(), pathMarker.getOrder(), worldgenId); + pathMarker.setParentPath(path); + holder.putComponent(MODEL_COMPONENT_TYPE, new ModelComponent(PathPlugin.get().getPathMarkerModel())); + pathMarker.markNeedsSave(); + holder.ensureComponent(HiddenFromAdventurePlayers.getComponentType()); + holder.ensureComponent(PrefabCopyableComponent.getComponentType()); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + PatrolPathMarkerEntity pathMarker = holder.getComponent(PatrolPathMarkerEntity.getComponentType()); + WorldPathData worldPathData = store.getResource(WorldPathData.getResourceType()); + WorldGenId worldGenIdComponent = holder.getComponent(WORLD_GEN_ID_COMPONENT_TYPE); + int worldgenId = worldGenIdComponent != null ? worldGenIdComponent.getWorldGenId() : 0; + switch (reason) { + case UNLOAD: + worldPathData.unloadPrefabPathWaypoint(worldgenId, pathMarker.getPathId(), pathMarker.getOrder()); + break; + case REMOVE: + UUID path = pathMarker.getPathId(); + if (path != null) { + worldPathData.removePrefabPathWaypoint(worldgenId, path, pathMarker.getOrder()); + } + } + } + } + + public static class AddedFromWorldGen extends HolderSystem { + @Nullable + private static final ComponentType PATH_MARKER_ENTITY_COMPONENT_TYPE = PatrolPathMarkerEntity.getComponentType(); + private static final ComponentType WORLD_GEN_ID_COMPONENT_TYPE = WorldGenId.getComponentType(); + private static final ComponentType FROM_WORLD_GEN_COMPONENT_TYPE = FromWorldGen.getComponentType(); + private static final Query QUERY = Query.and(PATH_MARKER_ENTITY_COMPONENT_TYPE, FROM_WORLD_GEN_COMPONENT_TYPE); + private static final Set> DEPENDENCIES = Set.of(new SystemDependency<>(Order.BEFORE, PrefabPathSystems.AddOrRemove.class)); + + public AddedFromWorldGen() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityModule.get().getPreClearMarkersGroup(); + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.putComponent(WORLD_GEN_ID_COMPONENT_TYPE, new WorldGenId(holder.getComponent(FROM_WORLD_GEN_COMPONENT_TYPE).getWorldGenId())); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + } + + public static class NameplateHolderSystem extends HolderSystem { + public NameplateHolderSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return Archetype.of(PatrolPathMarkerEntity.getComponentType()); + } + + @Override + public void onEntityAdd(@NonNullDecl Holder holder, @NonNullDecl AddReason reason, @NonNullDecl Store store) { + PatrolPathMarkerEntity patrolPathMarkerComponent = holder.getComponent(PatrolPathMarkerEntity.getComponentType()); + + assert patrolPathMarkerComponent != null; + + DisplayNameComponent displayNameComponent = holder.getComponent(DisplayNameComponent.getComponentType()); + String displayName = ""; + if (displayNameComponent == null) { + String legacyDisplayName = patrolPathMarkerComponent.getLegacyDisplayName(); + displayName = legacyDisplayName != null ? legacyDisplayName : "Path Marker"; + Message legacyDisplayNameMessage = Message.raw(displayName); + displayNameComponent = new DisplayNameComponent(legacyDisplayNameMessage); + holder.putComponent(DisplayNameComponent.getComponentType(), displayNameComponent); + } + + Nameplate nameplateComponent = holder.getComponent(Nameplate.getComponentType()); + if (nameplateComponent == null) { + holder.putComponent(Nameplate.getComponentType(), new Nameplate(displayName)); + } + } + + @Override + public void onEntityRemoved(@NonNullDecl Holder holder, @NonNullDecl RemoveReason reason, @NonNullDecl Store store) { + } + } + + public static class NameplateRefChangeSystem extends RefChangeSystem { + public NameplateRefChangeSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return PatrolPathMarkerEntity.getComponentType(); + } + + @Nonnull + @Override + public ComponentType componentType() { + return DisplayNameComponent.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, + @Nonnull DisplayNameComponent component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Nameplate nameplateComponent = commandBuffer.ensureAndGetComponent(ref, Nameplate.getComponentType()); + nameplateComponent.setText(component.getDisplayName() != null ? component.getDisplayName().getAnsiMessage() : ""); + } + + public void onComponentSet( + @Nonnull Ref ref, + DisplayNameComponent oldComponent, + @Nonnull DisplayNameComponent newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Nameplate nameplateComponent = commandBuffer.ensureAndGetComponent(ref, Nameplate.getComponentType()); + nameplateComponent.setText(newComponent.getDisplayName() != null ? newComponent.getDisplayName().getAnsiMessage() : ""); + } + + public void onComponentRemoved( + @Nonnull Ref ref, + @Nonnull DisplayNameComponent component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Nameplate nameplateComponent = commandBuffer.ensureAndGetComponent(ref, Nameplate.getComponentType()); + nameplateComponent.setText(""); + } + } + + public static class PrefabPlaceEntityEventSystem extends WorldEventSystem { + public PrefabPlaceEntityEventSystem() { + super(PrefabPlaceEntityEvent.class); + } + + public void handle(@Nonnull Store store, @Nonnull CommandBuffer commandBuffer, @Nonnull PrefabPlaceEntityEvent event) { + Holder holder = event.getHolder(); + PatrolPathMarkerEntity patrolPathMarkerComponent = holder.getComponent(PatrolPathMarkerEntity.getComponentType()); + if (patrolPathMarkerComponent != null) { + String pathName = patrolPathMarkerComponent.getPathName(); + UUID pathId = patrolPathMarkerComponent.getPathId(); + if (pathId == null) { + String newPathName = pathName.substring(0, pathName.lastIndexOf(126)); + patrolPathMarkerComponent.setPathName(newPathName); + } + + UUID newPathId = BuilderToolsPlugin.get().getNewPathIdOnPrefabPasted(pathId, patrolPathMarkerComponent.getPathName(), event.getPrefabId()); + patrolPathMarkerComponent.setPathId(newPathId); + } + } + } + + public static class WorldGenChangeSystem extends RefChangeSystem { + @Nonnull + private static final Message MESSAGE_PREFABS_UNKNOWN = Message.translation("server.prefabs.unknown"); + + public WorldGenChangeSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return PatrolPathMarkerEntity.getComponentType(); + } + + @Nonnull + @Override + public ComponentType componentType() { + return WorldGenId.getComponentType(); + } + + public void onComponentAdded( + @NonNullDecl Ref ref, + @NonNullDecl WorldGenId component, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + PatrolPathMarkerEntity patrolPathMarkerComponent = commandBuffer.getComponent(ref, PatrolPathMarkerEntity.getComponentType()); + + assert patrolPathMarkerComponent != null; + + String displayName = PatrolPathMarkerEntity.generateDisplayName(component.getWorldGenId(), patrolPathMarkerComponent); + Message displayNameMessage = Message.raw(displayName); + commandBuffer.putComponent(ref, DisplayNameComponent.getComponentType(), new DisplayNameComponent(displayNameMessage)); + } + + public void onComponentSet( + @NonNullDecl Ref ref, + @NullableDecl WorldGenId oldComponent, + @NonNullDecl WorldGenId newComponent, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + PatrolPathMarkerEntity patrolPathMarkerComponent = commandBuffer.getComponent(ref, PatrolPathMarkerEntity.getComponentType()); + + assert patrolPathMarkerComponent != null; + + String displayName = PatrolPathMarkerEntity.generateDisplayName(newComponent.getWorldGenId(), patrolPathMarkerComponent); + Message displayNameMessage = Message.raw(displayName); + commandBuffer.putComponent(ref, DisplayNameComponent.getComponentType(), new DisplayNameComponent(displayNameMessage)); + } + + public void onComponentRemoved( + @NonNullDecl Ref ref, + @NonNullDecl WorldGenId component, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + commandBuffer.putComponent(ref, DisplayNameComponent.getComponentType(), new DisplayNameComponent(MESSAGE_PREFABS_UNKNOWN)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/path/WorldPathBuilder.java b/src/com/hypixel/hytale/builtin/path/WorldPathBuilder.java new file mode 100644 index 0000000..a1554e3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/WorldPathBuilder.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.path; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.path.WorldPath; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.List; +import javax.annotation.Nonnull; + +public class WorldPathBuilder implements Component { + private WorldPath path; + + public WorldPathBuilder() { + } + + public static ComponentType getComponentType() { + return PathPlugin.get().getWorldPathBuilderComponentType(); + } + + public WorldPath getPath() { + return this.path; + } + + public void setPath(WorldPath path) { + this.path = path; + } + + @Nonnull + @Override + public Component clone() { + WorldPathBuilder builder = new WorldPathBuilder(); + builder.path = new WorldPath(this.path.getName(), List.copyOf(this.path.getWaypoints())); + return builder; + } +} diff --git a/src/com/hypixel/hytale/builtin/path/WorldPathData.java b/src/com/hypixel/hytale/builtin/path/WorldPathData.java new file mode 100644 index 0000000..753f55b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/WorldPathData.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.builtin.path; + +import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap; +import com.hypixel.hytale.builtin.path.path.IPrefabPath; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WorldPathData implements Resource { + private final Int2ObjectMap prefabPaths = new Int2ObjectOpenHashMap<>(); + + public WorldPathData() { + } + + public static ResourceType getResourceType() { + return PathPlugin.get().getWorldPathDataResourceType(); + } + + @Nullable + public IPrefabPath getNearestPrefabPath( + int worldgenId, int nameIndex, @Nonnull Vector3d position, Set disallowedPaths, @Nonnull ComponentAccessor componentAccessor + ) { + PrefabPathCollection entry = this.getPrefabPathCollection(worldgenId); + return entry.getNearestPrefabPath(nameIndex, position, disallowedPaths, componentAccessor); + } + + @Nullable + public IPrefabPath getNearestPrefabPath( + int worldgenId, @Nonnull Vector3d position, Set disallowedPaths, @Nonnull ComponentAccessor componentAccessor + ) { + return this.getPrefabPathCollection(worldgenId).getNearestPrefabPath(position, disallowedPaths, componentAccessor); + } + + public IPrefabPath getOrConstructPrefabPath( + int worldgenId, @Nonnull UUID id, @Nonnull String name, @Nonnull Int2ObjectConcurrentHashMap.IntBiObjFunction pathGenerator + ) { + PrefabPathCollection entry = this.getPrefabPathCollection(worldgenId); + return entry.getOrConstructPath(id, name, pathGenerator); + } + + public void removePrefabPathWaypoint(int worldgenId, UUID id, int index) { + PrefabPathCollection entry = this.getPrefabPathCollection(worldgenId); + entry.removePathWaypoint(id, index); + if (entry.isEmpty()) { + this.prefabPaths.remove(worldgenId); + } + } + + public void unloadPrefabPathWaypoint(int worldgenId, UUID id, int index) { + PrefabPathCollection entry = this.getPrefabPathCollection(worldgenId); + entry.unloadPathWaypoint(id, index); + if (entry.isEmpty()) { + this.prefabPaths.remove(worldgenId); + } + } + + public void removePrefabPath(int worldgenId, UUID id) { + PrefabPathCollection entry = this.getPrefabPathCollection(worldgenId); + entry.removePath(id); + if (entry.isEmpty()) { + this.prefabPaths.remove(worldgenId); + } + } + + @Nullable + public IPrefabPath getPrefabPath(int worldgenId, UUID id, boolean ignoreLoadState) { + PrefabPathCollection collection = this.getPrefabPathCollection(worldgenId); + IPrefabPath path = collection.getPath(id); + return ignoreLoadState || path != null && path.isFullyLoaded() ? path : null; + } + + public void compactPrefabPath(int worldgenId, UUID id) { + IPrefabPath path = this.getPrefabPath(worldgenId, id, true); + if (path != null) { + path.compact(worldgenId); + } + } + + @Nonnull + public List getAllPrefabPaths() { + ObjectArrayList list = new ObjectArrayList<>(); + this.prefabPaths.forEach((id, entry) -> entry.forEach((key, path) -> list.add(path))); + return Collections.unmodifiableList(list); + } + + @Nonnull + public PrefabPathCollection getPrefabPathCollection(int worldgenId) { + return this.prefabPaths.computeIfAbsent(worldgenId, PrefabPathCollection::new); + } + + @Override + public Resource clone() { + throw new UnsupportedOperationException("Not implemented!"); + } +} diff --git a/src/com/hypixel/hytale/builtin/path/commands/PrefabPathAddCommand.java b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathAddCommand.java new file mode 100644 index 0000000..164ec68 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathAddCommand.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.builtin.path.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.path.WorldPathData; +import com.hypixel.hytale.builtin.path.path.IPrefabPath; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class PrefabPathAddCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_NPC_PATH_ADD_NO_ACTIVE_PATH = Message.translation("server.commands.npcpath.add.noActivePath"); + @Nonnull + private final DefaultArg pauseTimeArg = this.withDefaultArg("pauseTime", "server.commands.npcpath.add.pauseTime.desc", ArgTypes.DOUBLE, 0.0, "0.0"); + @Nonnull + private final DefaultArg observationAngleArg = this.withDefaultArg( + "observationAngleDegrees", "server.commands.npcpath.add.observationAngleDegrees.desc", ArgTypes.FLOAT, 0.0F, "0.0" + ); + @Nonnull + private final DefaultArg indexArg = this.withDefaultArg("index", "server.commands.npcpath.add.index.desc", ArgTypes.INTEGER, -1, "-1"); + + public PrefabPathAddCommand() { + super("add", "server.commands.npcpath.add.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + UUID path = BuilderToolsPlugin.getState(playerComponent, playerRef).getActivePrefabPath(); + if (path == null) { + throw new GeneralCommandException(MESSAGE_COMMANDS_NPC_PATH_ADD_NO_ACTIVE_PATH); + } else { + Double pauseTime = this.pauseTimeArg.get(context); + Float obsvAngle = this.observationAngleArg.get(context); + short targetIndex = this.indexArg.get(context).shortValue(); + WorldPathData worldPathData = store.getResource(WorldPathData.getResourceType()); + IPrefabPath parentPath = worldPathData.getPrefabPath(0, path, false); + if (parentPath != null && parentPath.isFullyLoaded()) { + PrefabPathHelper.addMarker(store, ref, path, parentPath.getName(), pauseTime, obsvAngle, targetIndex, parentPath.getWorldGenId()); + } else { + context.sendMessage(Message.translation("server.npc.npcpath.pathMustBeLoaded").param("path", path.toString())); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/path/commands/PrefabPathCommand.java b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathCommand.java new file mode 100644 index 0000000..ee1fd82 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathCommand.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.builtin.path.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class PrefabPathCommand extends AbstractCommandCollection { + public PrefabPathCommand() { + super("path", "server.commands.npcpath.desc"); + this.addSubCommand(new PrefabPathListCommand()); + this.addSubCommand(new PrefabPathNodesCommand()); + this.addSubCommand(new PrefabPathNewCommand()); + this.addSubCommand(new PrefabPathEditCommand()); + this.addSubCommand(new PrefabPathAddCommand()); + this.addSubCommand(new PrefabPathMergeCommand()); + this.addSubCommand(new PrefabPathUpdateCommand()); + } +} diff --git a/src/com/hypixel/hytale/builtin/path/commands/PrefabPathEditCommand.java b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathEditCommand.java new file mode 100644 index 0000000..17dbd83 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathEditCommand.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.builtin.path.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.path.WorldPathData; +import com.hypixel.hytale.builtin.path.entities.PatrolPathMarkerEntity; +import com.hypixel.hytale.builtin.path.path.IPrefabPath; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class PrefabPathEditCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_NPC_PATH_EDIT_NO_ENTITY_IN_VIEW = Message.translation("server.commands.npcpath.edit.noEntityInView"); + @Nonnull + private final OptionalArg pathIdArg = this.withOptionalArg("pathId", "server.commands.npcpath.edit.pathId.desc", ArgTypes.UUID); + + public PrefabPathEditCommand() { + super("edit", "server.commands.npcpath.edit.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + UUID pathId; + if (this.pathIdArg.provided(context)) { + pathId = this.pathIdArg.get(context); + } else { + Ref entityRef = TargetUtil.getTargetEntity(ref, store); + if (entityRef == null || !entityRef.isValid()) { + return; + } + + if (!(EntityUtils.getEntity(ref, store) instanceof PatrolPathMarkerEntity pathMarkerEntity)) { + context.sendMessage(MESSAGE_COMMANDS_NPC_PATH_EDIT_NO_ENTITY_IN_VIEW); + return; + } + + pathId = pathMarkerEntity.getPathId(); + } + + WorldPathData worldPathData = store.getResource(WorldPathData.getResourceType()); + IPrefabPath path = worldPathData.getPrefabPath(0, pathId, false); + if (path == null) { + context.sendMessage(Message.translation("server.npc.npcpath.pathMustBeLoaded").param("path", pathId.toString())); + } else { + BuilderToolsPlugin.getState(playerComponent, playerRef).setActivePrefabPath(pathId); + context.sendMessage(Message.translation("server.npc.npcpath.editingPath").param("path", pathId.toString())); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/path/commands/PrefabPathHelper.java b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathHelper.java new file mode 100644 index 0000000..b15dc26 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathHelper.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.path.commands; + +import com.hypixel.hytale.builtin.path.PathPlugin; +import com.hypixel.hytale.builtin.path.entities.PatrolPathMarkerEntity; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.entity.nameplate.Nameplate; +import com.hypixel.hytale.server.core.modules.entity.component.DisplayNameComponent; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; + +public final class PrefabPathHelper { + private PrefabPathHelper() { + } + + public static void addMarker( + @Nonnull Store store, + @Nonnull Ref playerRef, + @Nonnull UUID pathId, + @Nonnull String pathName, + double pauseTime, + float obsvAngleDegrees, + short targetIndex, + int worldgenId + ) { + World world = store.getExternalData().getWorld(); + PatrolPathMarkerEntity waypoint = new PatrolPathMarkerEntity(world); + waypoint.initialise(pathId, pathName, targetIndex, pauseTime, obsvAngleDegrees * (float) (Math.PI / 180.0), worldgenId, store); + TransformComponent transformComponent = store.getComponent(playerRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d playerPosition = transformComponent.getPosition().clone(); + Vector3f playerBodyRotation = transformComponent.getRotation().clone(); + PatrolPathMarkerEntity waypointEntity = world.spawnEntity(waypoint, playerPosition, playerBodyRotation); + if (waypointEntity != null) { + Ref waypointRef = waypointEntity.getReference(); + if (waypointRef != null && waypointRef.isValid()) { + TransformComponent waypointTransformComponent = store.getComponent(waypointRef, TransformComponent.getComponentType()); + Vector3f waypointRotation = waypointTransformComponent.getRotation(); + waypointRotation.assign(playerBodyRotation); + Model model = PathPlugin.get().getPathMarkerModel(); + store.putComponent(waypointRef, ModelComponent.getComponentType(), new ModelComponent(model)); + String displayName = PatrolPathMarkerEntity.generateDisplayName(worldgenId, waypointEntity); + Message displayNameMessage = Message.raw(displayName); + store.putComponent(waypointRef, DisplayNameComponent.getComponentType(), new DisplayNameComponent(displayNameMessage)); + store.putComponent(waypointRef, Nameplate.getComponentType(), new Nameplate(displayName)); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/path/commands/PrefabPathListCommand.java b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathListCommand.java new file mode 100644 index 0000000..4bc941e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathListCommand.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.path.commands; + +import com.hypixel.hytale.builtin.path.PathPlugin; +import com.hypixel.hytale.builtin.path.WorldPathData; +import com.hypixel.hytale.builtin.path.path.IPrefabPath; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.List; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class PrefabPathListCommand extends AbstractWorldCommand { + public PrefabPathListCommand() { + super("list", "server.commands.npcpath.list.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + WorldPathData worldPathDataResource = store.getResource(WorldPathData.getResourceType()); + List paths = worldPathDataResource.getAllPrefabPaths(); + StringBuilder sb = new StringBuilder("Active prefab paths:\n"); + Message msg = Message.translation("server.npc.npcpath.list.prefabPaths"); + + for (IPrefabPath path : paths) { + sb.append(' ').append(path.getWorldGenId()).append('.').append(path.getId()); + sb.append(" (").append(path.getName()).append(')'); + sb.append(" [ Length: ").append(path.length()); + sb.append(", Loaded nodes: ").append(path.loadedWaypointCount()).append(" ]\n"); + msg.insert( + Message.translation("server.npc.npcpath.list.details") + .param("worldGenId", path.getWorldGenId()) + .param("pathId", path.getId().toString()) + .param("pathName", path.getName()) + .param("length", path.length()) + .param("count", path.loadedWaypointCount()) + ); + } + + PathPlugin.get().getLogger().at(Level.INFO).log(sb.toString()); + context.sendMessage(msg); + } +} diff --git a/src/com/hypixel/hytale/builtin/path/commands/PrefabPathMergeCommand.java b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathMergeCommand.java new file mode 100644 index 0000000..797e02d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathMergeCommand.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.builtin.path.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.builtin.path.WorldPathData; +import com.hypixel.hytale.builtin.path.path.IPrefabPath; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class PrefabPathMergeCommand extends AbstractPlayerCommand { + public static final Message MESSAGE_COMMANDS_NPC_PATH_MERGE_NO_ACTIVE_PATH = Message.translation("server.commands.npcpath.merge.noActivePath"); + @Nonnull + private final RequiredArg targetPathIdArg = this.withRequiredArg("pathName", "server.commands.npcpath.merge.pathName.desc", ArgTypes.UUID); + + public PrefabPathMergeCommand() { + super("merge", "server.commands.npcpath.merge.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + UUID activePathId = BuilderToolsPlugin.getState(playerComponent, playerRef).getActivePrefabPath(); + if (activePathId == null) { + throw new GeneralCommandException(MESSAGE_COMMANDS_NPC_PATH_MERGE_NO_ACTIVE_PATH); + } else { + UUID targetPathId = this.targetPathIdArg.get(context); + WorldPathData worldPathData = store.getResource(WorldPathData.getResourceType()); + IPrefabPath activePath = worldPathData.getPrefabPath(0, activePathId, false); + if (activePath != null && activePath.isFullyLoaded()) { + IPrefabPath targetPath = worldPathData.getPrefabPath(0, targetPathId, false); + if (targetPath != null && targetPath.isFullyLoaded()) { + targetPath.mergeInto(activePath, targetPath.getWorldGenId(), store); + worldPathData.removePrefabPath(0, targetPathId); + playerRef.sendMessage( + Message.translation("server.npc.npcpath.pathMergedInto") + .param("targetPathName", targetPathId.toString()) + .param("activePathName", activePathId.toString()) + ); + } else { + playerRef.sendMessage(Message.translation("server.npc.npcpath.pathMustBeLoaded").param("path", targetPathId.toString())); + } + } else { + playerRef.sendMessage(Message.translation("server.npc.npcpath.pathMustBeLoaded").param("path", activePathId.toString())); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/path/commands/PrefabPathNewCommand.java b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathNewCommand.java new file mode 100644 index 0000000..82d9d2d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathNewCommand.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.builtin.path.commands; + +import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class PrefabPathNewCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg pathNameArg = this.withRequiredArg("pathName", "server.commands.npcpath.new.pathName.desc", ArgTypes.STRING); + @Nonnull + private final DefaultArg pauseTimeArg = this.withDefaultArg("pauseTime", "server.commands.npcpath.new.pauseTime.desc", ArgTypes.DOUBLE, 0.0, "0.0"); + @Nonnull + private final DefaultArg observationAngleArg = this.withDefaultArg( + "observationAngleDegrees", "server.commands.npcpath.new.observationAngleDegrees.desc", ArgTypes.FLOAT, 0.0F, "0.0" + ); + + public PrefabPathNewCommand() { + super("new", "server.commands.npcpath.new.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + String pathName = this.pathNameArg.get(context); + Double pauseTime = this.pauseTimeArg.get(context); + Float obsvAngle = this.observationAngleArg.get(context); + UUID uuid = UUID.randomUUID(); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PrefabPathHelper.addMarker(store, ref, uuid, pathName, pauseTime, obsvAngle, (short)-1, 0); + BuilderToolsPlugin.getState(playerComponent, playerRef).setActivePrefabPath(uuid); + context.sendMessage(Message.translation("server.npc.npcpath.editingPath").param("path", pathName)); + } +} diff --git a/src/com/hypixel/hytale/builtin/path/commands/PrefabPathNodesCommand.java b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathNodesCommand.java new file mode 100644 index 0000000..094b6a5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathNodesCommand.java @@ -0,0 +1,86 @@ +package com.hypixel.hytale.builtin.path.commands; + +import com.hypixel.hytale.builtin.path.PathPlugin; +import com.hypixel.hytale.builtin.path.WorldPathData; +import com.hypixel.hytale.builtin.path.path.IPrefabPath; +import com.hypixel.hytale.builtin.path.waypoint.IPrefabPathWaypoint; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class PrefabPathNodesCommand extends AbstractWorldCommand { + @Nonnull + private final RequiredArg worldgenIdArg = this.withRequiredArg("worldgenId", "server.commands.npcpath.nodes.worldgenId.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg pathArg = this.withRequiredArg("path", "server.commands.npcpath.nodes.path.desc", ArgTypes.UUID); + + public PrefabPathNodesCommand() { + super("nodes", "server.commands.npcpath.nodes.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Integer worldgenId = this.worldgenIdArg.get(context); + UUID uuid = this.pathArg.get(context); + WorldPathData worldPathData = store.getResource(WorldPathData.getResourceType()); + IPrefabPath path = worldPathData.getPrefabPath(worldgenId, uuid, true); + if (path == null) { + context.sendMessage(Message.translation("server.npc.npcpath.noSuchPath").param("path", uuid.toString()).param("worldgenId", worldgenId)); + } else { + StringBuilder sb = new StringBuilder("Path [ "); + sb.append(path.getName()).append(" ]:"); + sb.append("\n Length: ").append(path.length()); + sb.append("\n Fully loaded: ").append(path.isFullyLoaded()); + sb.append("\n Waypoints: "); + Message msg = Message.translation("server.npc.npcpath.nodes.pathDesc") + .param("name", path.getName()) + .param("length", path.length()) + .param("isLoaded", path.isFullyLoaded()); + List waypoints = path.getPathWaypoints(); + int[] order = new int[]{0}; + waypoints.forEach( + waypoint -> { + if (waypoint == null) { + sb.append("\n ").append('#').append(order[0]).append(" (Not loaded)"); + msg.insert(Message.translation("server.npc.npcpath.nodes.waypointNotLoaded").param("index", order[0])); + order[0]++; + } else { + Vector3d pos = waypoint.getWaypointPosition(store); + Vector3f rotation = waypoint.getWaypointRotation(store); + sb.append("\n ").append('#').append(waypoint.getOrder()); + sb.append(" (").append(pos.x).append(", ").append(pos.y).append(", ").append(pos.z).append(')'); + sb.append("\n ").append("Rotation: (").append(rotation.x).append(", ").append(rotation.y).append(", ").append(rotation.z).append(')'); + sb.append("\n ").append("Pause time: ").append(waypoint.getPauseTime()).append('s'); + sb.append("\n ").append(String.format("Observation angle: %.2f", waypoint.getObservationAngle() * (180.0F / (float)Math.PI))); + msg.insert( + Message.translation("server.npc.npcpath.nodes.node") + .param("index", waypoint.getOrder()) + .param("posX", pos.x) + .param("posY", pos.y) + .param("posZ", pos.z) + .param("rotX", rotation.x) + .param("rotY", rotation.y) + .param("rotZ", rotation.z) + .param("time", waypoint.getPauseTime()) + .param("angle", String.format("%.2f", waypoint.getObservationAngle() * (180.0F / (float)Math.PI))) + ); + order[0]++; + } + } + ); + PathPlugin.get().getLogger().at(Level.INFO).log(sb.toString()); + context.sendMessage(msg); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/path/commands/PrefabPathUpdateCommand.java b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathUpdateCommand.java new file mode 100644 index 0000000..dfffa64 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathUpdateCommand.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.builtin.path.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class PrefabPathUpdateCommand extends AbstractCommandCollection { + public PrefabPathUpdateCommand() { + super("update", "server.commands.npcpath.update.desc"); + this.addSubCommand(new PrefabPathUpdatePauseCommand()); + this.addSubCommand(new PrefabPathUpdateObservationAngleCommand()); + } +} diff --git a/src/com/hypixel/hytale/builtin/path/commands/PrefabPathUpdateObservationAngleCommand.java b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathUpdateObservationAngleCommand.java new file mode 100644 index 0000000..2eecbb2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathUpdateObservationAngleCommand.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.builtin.path.commands; + +import com.hypixel.hytale.builtin.path.entities.PatrolPathMarkerEntity; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import javax.annotation.Nonnull; + +public class PrefabPathUpdateObservationAngleCommand extends AbstractWorldCommand { + @Nonnull + private final EntityWrappedArg entityIdArg = this.withOptionalArg( + "entityId", "server.commands.npcpath.update.observationAngle.entityId.desc", ArgTypes.ENTITY_ID + ); + @Nonnull + private final RequiredArg angleArg = this.withRequiredArg("angle", "server.commands.npcpath.update.observationAngle.angle.desc", ArgTypes.FLOAT); + + public PrefabPathUpdateObservationAngleCommand() { + super("observationAngle", "server.commands.npcpath.update.observationAngle.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Float angleDegrees = this.angleArg.get(context); + Ref ref; + if (this.entityIdArg.provided(context)) { + ref = this.entityIdArg.get(store, context); + } else { + Ref playerRef = context.senderAsPlayerRef(); + if (playerRef == null) { + throw new GeneralCommandException(Message.translation("server.commands.errors.playerOrArg").param("option", "entity")); + } + + if (!playerRef.isValid()) { + throw new GeneralCommandException(Message.translation("server.commands.errors.playerNotInWorld").param("option", "entity")); + } + + Ref entityRef = TargetUtil.getTargetEntity(playerRef, store); + if (entityRef == null) { + throw new GeneralCommandException(Message.translation("server.commands.errors.no_entity_in_view").param("option", "entity")); + } + + ref = entityRef; + } + + PatrolPathMarkerEntity marker = store.getComponent(ref, PatrolPathMarkerEntity.getComponentType()); + if (marker == null) { + context.sendMessage(Message.translation("server.general.entityNotFound").param("id", ref.getIndex())); + } else { + marker.setObservationAngle(angleDegrees * (float) (Math.PI / 180.0)); + marker.markNeedsSave(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/path/commands/PrefabPathUpdatePauseCommand.java b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathUpdatePauseCommand.java new file mode 100644 index 0000000..a32c711 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/commands/PrefabPathUpdatePauseCommand.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.builtin.path.commands; + +import com.hypixel.hytale.builtin.path.entities.PatrolPathMarkerEntity; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import javax.annotation.Nonnull; + +public class PrefabPathUpdatePauseCommand extends AbstractWorldCommand { + @Nonnull + private final EntityWrappedArg entityIdArg = this.withOptionalArg("entityId", "server.commands.npcpath.update.pause.entityId.desc", ArgTypes.ENTITY_ID); + @Nonnull + private final RequiredArg pauseTimeArg = this.withRequiredArg("pauseTime", "server.commands.npcpath.update.pause.pauseTime.desc", ArgTypes.DOUBLE); + + public PrefabPathUpdatePauseCommand() { + super("pause", "server.commands.npcpath.update.pause.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Double pauseTime = this.pauseTimeArg.get(context); + Ref ref; + if (this.entityIdArg.provided(context)) { + ref = this.entityIdArg.get(store, context); + } else { + Ref playerRef = context.senderAsPlayerRef(); + if (playerRef == null) { + throw new GeneralCommandException(Message.translation("server.commands.errors.playerOrArg").param("option", "entity")); + } + + if (!playerRef.isValid()) { + throw new GeneralCommandException(Message.translation("server.commands.errors.playerNotInWorld").param("option", "entity")); + } + + Ref entityRef = TargetUtil.getTargetEntity(playerRef, store); + if (entityRef == null) { + throw new GeneralCommandException(Message.translation("server.commands.errors.no_entity_in_view").param("option", "entity")); + } + + ref = entityRef; + } + + PatrolPathMarkerEntity patrolPathMarkerComponent = store.getComponent(ref, PatrolPathMarkerEntity.getComponentType()); + if (patrolPathMarkerComponent == null) { + context.sendMessage(Message.translation("server.general.entityNotFound").param("id", ref.getIndex())); + } else { + patrolPathMarkerComponent.setPauseTime(pauseTime); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + transformComponent.markChunkDirty(store); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/path/commands/WorldPathBuilderCommand.java b/src/com/hypixel/hytale/builtin/path/commands/WorldPathBuilderCommand.java new file mode 100644 index 0000000..a1374a2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/commands/WorldPathBuilderCommand.java @@ -0,0 +1,293 @@ +package com.hypixel.hytale.builtin.path.commands; + +import com.hypixel.hytale.builtin.path.WorldPathBuilder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.path.WorldPath; +import com.hypixel.hytale.server.core.universe.world.path.WorldPathConfig; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WorldPathBuilderCommand extends AbstractCommandCollection { + public WorldPathBuilderCommand() { + super("builder", "server.commands.worldpath.builder.desc"); + this.addSubCommand(new WorldPathBuilderCommand.WorldPathBuilderStopCommand()); + this.addSubCommand(new WorldPathBuilderCommand.WorldPathBuilderLoadCommand()); + this.addSubCommand(new WorldPathBuilderCommand.WorldPathBuilderSimulateCommand()); + this.addSubCommand(new WorldPathBuilderCommand.WorldPathBuilderClearCommand()); + this.addSubCommand(new WorldPathBuilderCommand.WorldPathBuilderAddCommand()); + this.addSubCommand(new WorldPathBuilderCommand.WorldPathBuilderSetCommand()); + this.addSubCommand(new WorldPathBuilderCommand.WorldPathBuilderGotoCommand()); + this.addSubCommand(new WorldPathBuilderCommand.WorldPathBuilderRemoveCommand()); + this.addSubCommand(new WorldPathBuilderCommand.WorldPathBuilderSaveCommand()); + } + + @Nonnull + private static WorldPathBuilder createBuilder(@Nonnull Ref ref, @Nonnull Store store, @Nullable WorldPath existing) { + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + String name = "Builder-" + uuidComponent.getUuid(); + WorldPathBuilder builder = new WorldPathBuilder(); + if (existing == null) { + builder.setPath(new WorldPath(name, new ObjectArrayList<>())); + } else { + builder.setPath(new WorldPath(name, new ObjectArrayList<>(existing.getWaypoints()))); + } + + return builder; + } + + @Nullable + private static WorldPathBuilder getBuilder(@Nonnull Ref ref, @Nonnull Store store) { + return store.getComponent(ref, WorldPathBuilder.getComponentType()); + } + + @Nonnull + private static WorldPathBuilder getOrCreateBuilder(@Nonnull Ref ref, @Nonnull Store store) { + WorldPathBuilder builder = store.getComponent(ref, WorldPathBuilder.getComponentType()); + return builder != null ? builder : putBuilder(ref, store, createBuilder(ref, store, null)); + } + + @Nullable + private static WorldPath removeBuilder(@Nonnull Ref ref, @Nonnull Store store) { + WorldPathBuilder worldPath = store.getComponent(ref, WorldPathBuilder.getComponentType()); + if (worldPath != null) { + store.removeComponent(ref, WorldPathBuilder.getComponentType()); + return worldPath.getPath(); + } else { + return null; + } + } + + @Nonnull + private static WorldPathBuilder putBuilder(@Nonnull Ref ref, @Nonnull Store store, @Nonnull WorldPathBuilder builder) { + store.putComponent(ref, WorldPathBuilder.getComponentType(), builder); + return builder; + } + + private static class WorldPathBuilderAddCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_UNIVERSE_WORLD_PATH_POINT_ADDED = Message.translation("server.universe.worldpath.pointAdded"); + + public WorldPathBuilderAddCommand() { + super("add", "server.commands.worldpath.builder.add.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Transform transform = transformComponent.getTransform().clone(); + WorldPathBuilderCommand.getOrCreateBuilder(ref, store).getPath().getWaypoints().add(transform); + context.sendMessage(MESSAGE_UNIVERSE_WORLD_PATH_POINT_ADDED); + } + } + + private static class WorldPathBuilderClearCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_UNIVERSE_WORLD_PATH_POINTS_CLEARED = Message.translation("server.universe.worldpath.pointsCleared"); + + public WorldPathBuilderClearCommand() { + super("clear", "server.commands.worldpath.builder.clear.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + WorldPathBuilder builder = WorldPathBuilderCommand.getBuilder(ref, store); + if (builder != null) { + builder.getPath().getWaypoints().clear(); + context.sendMessage(MESSAGE_UNIVERSE_WORLD_PATH_POINTS_CLEARED); + } + } + } + + private static class WorldPathBuilderGotoCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg indexArg = this.withRequiredArg("index", "server.commands.worldpath.builder.goto.index.desc", ArgTypes.INTEGER); + + public WorldPathBuilderGotoCommand() { + super("goto", "server.commands.worldpath.builder.goto.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + WorldPathBuilder builder = WorldPathBuilderCommand.getBuilder(ref, store); + if (builder != null) { + Integer index = this.indexArg.get(context); + WorldPath worldPath = builder.getPath(); + store.addComponent(ref, Teleport.getComponentType(), new Teleport(null, worldPath.getWaypoints().get(index))); + context.sendMessage(Message.translation("server.universe.worldpath.teleportedToPoint").param("index", index)); + } + } + } + + private static class WorldPathBuilderLoadCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg nameArg = this.withRequiredArg("name", "server.commands.worldpath.builder.load.name.desc", ArgTypes.STRING); + + public WorldPathBuilderLoadCommand() { + super("load", "server.commands.worldpath.builder.load.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + String name = this.nameArg.get(context); + WorldPath worldPath = world.getWorldPathConfig().getPath(name); + if (worldPath == null) { + context.sendMessage(Message.translation("server.universe.worldpath.noPathFound").param("path", name)); + } else { + WorldPathBuilderCommand.putBuilder(ref, store, WorldPathBuilderCommand.createBuilder(ref, store, worldPath)); + } + } + } + + private static class WorldPathBuilderRemoveCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg indexArg = this.withRequiredArg("index", "server.commands.worldpath.builder.remove.index.desc", ArgTypes.INTEGER); + + public WorldPathBuilderRemoveCommand() { + super("remove", "server.commands.worldpath.builder.remove.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + WorldPathBuilder builder = WorldPathBuilderCommand.getBuilder(ref, store); + if (builder != null) { + int index = this.indexArg.get(context); + builder.getPath().getWaypoints().remove(index); + context.sendMessage(Message.translation("server.universe.worldpath.removedIndex").param("index", index)); + } + } + } + + private static class WorldPathBuilderSaveCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_UNIVERSE_WORLD_PATH_NO_POINTS_DEFINED = Message.translation("server.universe.worldpath.noPointsDefined"); + @Nonnull + private static final Message MESSAGE_UNIVERSE_WORLD_PATH_SAVED = Message.translation("server.universe.worldpath.saved"); + @Nonnull + private final RequiredArg nameArg = this.withRequiredArg("name", "server.commands.worldpath.builder.save.name.desc", ArgTypes.STRING); + + public WorldPathBuilderSaveCommand() { + super("save", "server.commands.worldpath.builder.save.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + String name = this.nameArg.get(context); + WorldPath path = WorldPathBuilderCommand.removeBuilder(ref, store); + if (path != null && !path.getWaypoints().isEmpty()) { + WorldPathConfig worldPathConfig = world.getWorldPathConfig(); + WorldPath worldPath = new WorldPath(name, path.getWaypoints()); + worldPathConfig.putPath(worldPath); + worldPathConfig.save(world); + context.sendMessage(MESSAGE_UNIVERSE_WORLD_PATH_SAVED); + } else { + context.sendMessage(MESSAGE_UNIVERSE_WORLD_PATH_NO_POINTS_DEFINED); + } + } + } + + private static class WorldPathBuilderSetCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_UNIVERSE_WORLD_PATH_POINT_SET = Message.translation("server.universe.worldpath.pointSet"); + @Nonnull + private final OptionalArg indexArg = this.withOptionalArg("index", "server.commands.worldpath.builder.set.index.desc", ArgTypes.INTEGER); + + public WorldPathBuilderSetCommand() { + super("set", "server.commands.worldpath.builder.set.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + WorldPathBuilder builder = WorldPathBuilderCommand.getBuilder(ref, store); + if (builder != null) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + WorldPath worldPath = builder.getPath(); + int index = this.indexArg.provided(context) ? this.indexArg.get(context) : worldPath.getWaypoints().size() - 1; + worldPath.getWaypoints().set(index, transformComponent.getTransform().clone()); + context.sendMessage(MESSAGE_UNIVERSE_WORLD_PATH_POINT_SET); + } + } + } + + private static class WorldPathBuilderSimulateCommand extends AbstractPlayerCommand { + public WorldPathBuilderSimulateCommand() { + super("simulate", "server.commands.worldpath.builder.simulate.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + WorldPathBuilder builder = WorldPathBuilderCommand.getBuilder(ref, store); + if (builder != null) { + ObjectArrayList waypoints = new ObjectArrayList<>(builder.getPath().getWaypoints()); + CompletableFuture future = new CompletableFuture<>(); + ScheduledFuture[] scheduledFuture = new ScheduledFuture[1]; + scheduledFuture[0] = HytaleServer.SCHEDULED_EXECUTOR.scheduleWithFixedDelay(() -> { + Transform transform = waypoints.removeFirst(); + if (transform == null) { + future.complete(null); + scheduledFuture[0].cancel(false); + } else { + world.execute(() -> store.addComponent(ref, Teleport.getComponentType(), new Teleport(null, transform))); + } + }, 1L, 1L, TimeUnit.SECONDS); + } + } + } + + private static class WorldPathBuilderStopCommand extends AbstractPlayerCommand { + public WorldPathBuilderStopCommand() { + super("stop", "server.commands.worldpath.builder.stop.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + WorldPathBuilderCommand.removeBuilder(ref, store); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/path/commands/WorldPathCommand.java b/src/com/hypixel/hytale/builtin/path/commands/WorldPathCommand.java new file mode 100644 index 0000000..f749e3f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/commands/WorldPathCommand.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.builtin.path.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class WorldPathCommand extends AbstractCommandCollection { + public WorldPathCommand() { + super("worldpath", "server.commands.worldpath.desc"); + this.addSubCommand(new WorldPathListCommand()); + this.addSubCommand(new WorldPathRemoveCommand()); + this.addSubCommand(new WorldPathSaveCommand()); + this.addSubCommand(new WorldPathBuilderCommand()); + } +} diff --git a/src/com/hypixel/hytale/builtin/path/commands/WorldPathListCommand.java b/src/com/hypixel/hytale/builtin/path/commands/WorldPathListCommand.java new file mode 100644 index 0000000..809cc02 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/commands/WorldPathListCommand.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.builtin.path.commands; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.path.WorldPath; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Comparator; +import javax.annotation.Nonnull; + +public class WorldPathListCommand extends AbstractWorldCommand { + public WorldPathListCommand() { + super("list", "server.commands.worldpath.list.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + world.getWorldPathConfig() + .getPaths() + .values() + .stream() + .sorted(Comparator.comparing(WorldPath::getName)) + .map(WorldPath::getName) + .map(Message::raw) + .forEach(context::sendMessage); + } +} diff --git a/src/com/hypixel/hytale/builtin/path/commands/WorldPathRemoveCommand.java b/src/com/hypixel/hytale/builtin/path/commands/WorldPathRemoveCommand.java new file mode 100644 index 0000000..7319685 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/commands/WorldPathRemoveCommand.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.builtin.path.commands; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WorldPathRemoveCommand extends AbstractWorldCommand { + @Nonnull + private final RequiredArg nameArg = this.withRequiredArg("name", "server.commands.worldpath.remove.name.desc", ArgTypes.STRING); + + public WorldPathRemoveCommand() { + super("remove", "server.commands.worldpath.remove.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + String name = this.nameArg.get(context); + if (world.getWorldPathConfig().removePath(name) == null) { + context.sendMessage(Message.translation("server.universe.worldpath.noPathFound").param("path", name)); + } else { + context.sendMessage(Message.translation("server.universe.worldpath.removed").param("path", name)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/path/commands/WorldPathSaveCommand.java b/src/com/hypixel/hytale/builtin/path/commands/WorldPathSaveCommand.java new file mode 100644 index 0000000..a6e1466 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/commands/WorldPathSaveCommand.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.builtin.path.commands; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WorldPathSaveCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_UNIVERSE_WORLD_PATH_CONFIG_SAVED = Message.translation("server.universe.worldpath.configSaved"); + + public WorldPathSaveCommand() { + super("save", "server.commands.worldpath.save.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + world.getWorldPathConfig().save(world); + context.sendMessage(MESSAGE_UNIVERSE_WORLD_PATH_CONFIG_SAVED); + } +} diff --git a/src/com/hypixel/hytale/builtin/path/entities/PatrolPathMarkerEntity.java b/src/com/hypixel/hytale/builtin/path/entities/PatrolPathMarkerEntity.java new file mode 100644 index 0000000..4e55578 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/entities/PatrolPathMarkerEntity.java @@ -0,0 +1,262 @@ +package com.hypixel.hytale.builtin.path.entities; + +import com.hypixel.hytale.builtin.path.WorldPathData; +import com.hypixel.hytale.builtin.path.path.IPrefabPath; +import com.hypixel.hytale.builtin.path.path.PatrolPath; +import com.hypixel.hytale.builtin.path.waypoint.IPrefabPathWaypoint; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.path.IPath; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PatrolPathMarkerEntity extends Entity implements IPrefabPathWaypoint { + public static final BuilderCodec CODEC = BuilderCodec.builder( + PatrolPathMarkerEntity.class, PatrolPathMarkerEntity::new, Entity.CODEC + ) + .append( + new KeyedCodec<>("PathId", Codec.UUID_BINARY), + (patrolPathMarkerEntity, uuid) -> patrolPathMarkerEntity.pathId = uuid, + patrolPathMarkerEntity -> patrolPathMarkerEntity.pathId + ) + .setVersionRange(5, 5) + .add() + .append( + new KeyedCodec<>("PathName", Codec.STRING), + (patrolPathMarkerEntity, s) -> patrolPathMarkerEntity.pathName = s, + patrolPathMarkerEntity -> patrolPathMarkerEntity.pathName + ) + .setVersionRange(5, 5) + .add() + .append( + new KeyedCodec<>("Path", Codec.STRING), + (patrolPathMarkerEntity, s) -> patrolPathMarkerEntity.pathName = s, + patrolPathMarkerEntity -> patrolPathMarkerEntity.pathName + ) + .setVersionRange(0, 4) + .add() + .addField( + new KeyedCodec<>("PathLength", Codec.INTEGER), + (patrolPathMarkerEntity, i) -> patrolPathMarkerEntity.tempPathLength = i.shortValue(), + patrolPathMarkerEntity -> patrolPathMarkerEntity.parentPath != null + ? patrolPathMarkerEntity.parentPath.length() + : patrolPathMarkerEntity.tempPathLength + ) + .addField( + new KeyedCodec<>("Order", Codec.INTEGER), + (patrolPathMarkerEntity, i) -> patrolPathMarkerEntity.order = i, + patrolPathMarkerEntity -> patrolPathMarkerEntity.order + ) + .addField( + new KeyedCodec<>("PauseTime", Codec.DOUBLE), + (patrolPathMarkerEntity, d) -> patrolPathMarkerEntity.pauseTime = d, + patrolPathMarkerEntity -> patrolPathMarkerEntity.pauseTime + ) + .addField( + new KeyedCodec<>("ObsvAngle", Codec.DOUBLE), + (patrolPathMarkerEntity, d) -> patrolPathMarkerEntity.observationAngle = d.floatValue(), + patrolPathMarkerEntity -> (double)patrolPathMarkerEntity.observationAngle + ) + .build(); + @Nullable + private UUID pathId; + private String pathName; + private int order; + private double pauseTime; + private float observationAngle; + private short tempPathLength; + private IPrefabPath parentPath; + + @Nullable + public static ComponentType getComponentType() { + return EntityModule.get().getComponentType(PatrolPathMarkerEntity.class); + } + + public PatrolPathMarkerEntity() { + } + + public PatrolPathMarkerEntity(World world) { + super(world); + } + + public void setParentPath(IPrefabPath parentPath) { + this.parentPath = parentPath; + } + + @Nullable + public UUID getPathId() { + return this.pathId; + } + + public void setPathId(UUID pathId) { + this.pathId = pathId; + } + + public String getPathName() { + return this.pathName; + } + + public void setPathName(String pathName) { + this.pathName = pathName; + } + + @Nonnull + public static String generateDisplayName(int worldgenId, PatrolPathMarkerEntity patrolPathMarkerEntity) { + return String.format( + "%s.%s (%s) #%s [Wait %ss] ", + worldgenId, + patrolPathMarkerEntity.pathId, + patrolPathMarkerEntity.pathName, + patrolPathMarkerEntity.order, + patrolPathMarkerEntity.pauseTime, + patrolPathMarkerEntity.observationAngle * (180.0F / (float)Math.PI) + ); + } + + public short getTempPathLength() { + return this.tempPathLength; + } + + @Override + public void initialise( + @Nonnull UUID id, + @Nonnull String pathName, + int index, + double pauseTime, + float observationAngle, + int worldGenId, + @Nonnull ComponentAccessor componentAccessor + ) { + this.pathId = id; + this.pathName = pathName; + WorldPathData worldPathData = componentAccessor.getResource(WorldPathData.getResourceType()); + this.parentPath = worldPathData.getOrConstructPrefabPath(worldGenId, this.pathId, pathName, PatrolPath::new); + this.pauseTime = pauseTime; + this.observationAngle = observationAngle; + if (index < 0) { + this.order = this.parentPath.registerNewWaypoint(this, worldGenId); + } else { + this.order = index; + this.parentPath.registerNewWaypointAt(index, this, worldGenId); + } + + this.tempPathLength = (short)this.parentPath.length(); + } + + @Override + public IPath getParentPath() { + return this.parentPath; + } + + @Override + public boolean isCollidable() { + return false; + } + + @Override + public boolean isHiddenFromLivingEntity( + @Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor + ) { + Player playerComponent = componentAccessor.getComponent(targetRef, Player.getComponentType()); + return playerComponent == null || playerComponent.getGameMode() != GameMode.Creative; + } + + @Override + public int getOrder() { + return this.order; + } + + public void setOrder(int order) { + this.order = order; + this.markNeedsSave(); + } + + @Override + public double getPauseTime() { + return this.pauseTime; + } + + public void setPauseTime(double pauseTime) { + this.pauseTime = pauseTime; + this.markNeedsSave(); + } + + @Override + public float getObservationAngle() { + return this.observationAngle; + } + + @Override + public void onReplaced() { + this.pathId = null; + this.remove(); + } + + public void setObservationAngle(float observationAngle) { + this.observationAngle = observationAngle; + this.markNeedsSave(); + } + + @Nonnull + @Override + public Vector3d getWaypointPosition(@Nonnull ComponentAccessor componentAccessor) { + Ref ref = this.getReference(); + + assert ref != null && ref.isValid() : "Entity reference is null or invalid"; + + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + return transformComponent.getPosition(); + } + + @Nonnull + @Override + public Vector3f getWaypointRotation(@Nonnull ComponentAccessor componentAccessor) { + Ref ref = this.getReference(); + + assert ref != null && ref.isValid() : "Entity reference is null or invalid"; + + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + return transformComponent.getRotation(); + } + + @Nonnull + @Override + public String toString() { + return "PatrolPathMarkerEntity{pathId=" + + this.pathId + + ", path='" + + this.pathName + + "', order=" + + this.order + + ", pauseTime=" + + this.pauseTime + + ", observationAngle=" + + this.observationAngle + + ", tempPathLength=" + + this.tempPathLength + + ", parentPath=" + + this.parentPath + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/path/path/IPrefabPath.java b/src/com/hypixel/hytale/builtin/path/path/IPrefabPath.java new file mode 100644 index 0000000..5100484 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/path/IPrefabPath.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.builtin.path.path; + +import com.hypixel.hytale.builtin.path.waypoint.IPrefabPathWaypoint; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.path.IPath; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public interface IPrefabPath extends IPath { + short registerNewWaypoint(@Nonnull IPrefabPathWaypoint var1, int var2); + + void registerNewWaypointAt(int var1, @Nonnull IPrefabPathWaypoint var2, int var3); + + void addLoadedWaypoint(@Nonnull IPrefabPathWaypoint var1, int var2, int var3, int var4); + + void removeWaypoint(int var1, int var2); + + void unloadWaypoint(int var1); + + boolean hasLoadedWaypoints(); + + boolean isFullyLoaded(); + + int loadedWaypointCount(); + + int getWorldGenId(); + + Vector3d getNearestWaypointPosition(@Nonnull Vector3d var1, @Nonnull ComponentAccessor var2); + + void mergeInto(@Nonnull IPrefabPath var1, int var2, @Nonnull ComponentAccessor var3); + + void compact(int var1); +} diff --git a/src/com/hypixel/hytale/builtin/path/path/PatrolPath.java b/src/com/hypixel/hytale/builtin/path/path/PatrolPath.java new file mode 100644 index 0000000..4106906 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/path/PatrolPath.java @@ -0,0 +1,234 @@ +package com.hypixel.hytale.builtin.path.path; + +import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap; +import com.hypixel.hytale.builtin.path.PathPlugin; +import com.hypixel.hytale.builtin.path.entities.PatrolPathMarkerEntity; +import com.hypixel.hytale.builtin.path.waypoint.IPrefabPathWaypoint; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class PatrolPath implements IPrefabPath { + private final UUID id; + private final String name; + private final int worldgenId; + private final Int2ObjectConcurrentHashMap waypoints = new Int2ObjectConcurrentHashMap<>(); + private final AtomicInteger length = new AtomicInteger(0); + private final AtomicInteger loadedCount = new AtomicInteger(0); + private final AtomicBoolean pathChanged = new AtomicBoolean(false); + private final ReentrantReadWriteLock listLock = new ReentrantReadWriteLock(); + private List waypointList; + + public PatrolPath(int worldgenId, UUID id, String name) { + this.id = id; + this.worldgenId = worldgenId; + this.name = name; + } + + @Override + public UUID getId() { + return this.id; + } + + @Override + public String getName() { + return this.name; + } + + @Nonnull + @Override + public List getPathWaypoints() { + if (this.pathChanged.get()) { + this.listLock.writeLock().lock(); + + try { + this.waypointList = new ObjectArrayList<>(); + int size = this.length.get(); + + for (int i = 0; i < size; i++) { + this.waypointList.add(this.waypoints.get(i)); + } + + this.pathChanged.set(false); + } finally { + this.listLock.writeLock().unlock(); + } + } + + this.listLock.readLock().lock(); + + List var11; + try { + var11 = Collections.unmodifiableList(this.waypointList); + } finally { + this.listLock.readLock().unlock(); + } + + return var11; + } + + @Override + public short registerNewWaypoint(@Nonnull IPrefabPathWaypoint waypoint, int worldGenId) { + short index = (short)this.length.getAndIncrement(); + PathPlugin.get().getLogger().at(Level.FINER).log("Adding waypoint %s to path %s.%s", index, worldGenId, this.name); + + for (int i = 0; i < index; i++) { + PatrolPathMarkerEntity wp = (PatrolPathMarkerEntity)this.waypoints.get(i); + wp.markNeedsSave(); + } + + this.pathChanged.set(true); + return index; + } + + @Override + public void registerNewWaypointAt(int index, @Nonnull IPrefabPathWaypoint waypoint, int worldGenId) { + for (int i = 0; i < index; i++) { + PatrolPathMarkerEntity wp = (PatrolPathMarkerEntity)this.waypoints.get(i); + wp.markNeedsSave(); + } + + for (int i = this.waypoints.size() - 1; i >= index; i--) { + PatrolPathMarkerEntity wp = (PatrolPathMarkerEntity)this.waypoints.remove(i); + wp.setOrder((short)(i + 1)); + this.waypoints.put(i + 1, wp); + } + + this.length.getAndIncrement(); + this.pathChanged.set(true); + } + + @Override + public void addLoadedWaypoint(@Nonnull IPrefabPathWaypoint waypoint, int pathLength, int index, int worldGenId) { + PathPlugin.get().getLogger().at(Level.FINER).log("Loading waypoint %s to path %s.%s", index, worldGenId, this.name); + IPrefabPathWaypoint old = this.waypoints.put(index, waypoint); + if (old != null) { + old.onReplaced(); + PathPlugin.get().getLogger().at(Level.WARNING).log("Waypoint %s replaced in path %s.%s", index, worldGenId, this.name); + } else { + this.loadedCount.getAndIncrement(); + } + + this.length.set(pathLength); + this.pathChanged.set(true); + } + + @Override + public void removeWaypoint(int index, int worldGenId) { + this.waypoints.remove(index); + this.length.getAndDecrement(); + this.loadedCount.getAndDecrement(); + + for (int i = 0; i < index; i++) { + PatrolPathMarkerEntity wp = (PatrolPathMarkerEntity)this.waypoints.get(i); + wp.markNeedsSave(); + } + + for (int i = index; i < this.waypoints.size(); i++) { + PatrolPathMarkerEntity wp = (PatrolPathMarkerEntity)this.waypoints.remove(i + 1); + wp.setOrder(i); + this.waypoints.put(i, wp); + } + + this.pathChanged.set(true); + } + + @Override + public void unloadWaypoint(int index) { + this.waypoints.remove(index); + this.loadedCount.getAndDecrement(); + } + + @Override + public boolean hasLoadedWaypoints() { + return this.loadedCount.get() > 0; + } + + @Override + public boolean isFullyLoaded() { + return this.loadedCount.get() == this.length.get(); + } + + @Override + public int loadedWaypointCount() { + return this.loadedCount.get(); + } + + @Override + public int getWorldGenId() { + return this.worldgenId; + } + + @Override + public Vector3d getNearestWaypointPosition(@Nonnull Vector3d origin, @Nonnull ComponentAccessor componentAccessor) { + Vector3d nearest = Vector3d.MAX; + double minDist2 = Double.MAX_VALUE; + + for (int i = 0; i < this.length.get(); i++) { + IPrefabPathWaypoint wp = this.waypoints.get(i); + if (wp != null) { + double dist2 = origin.distanceSquaredTo(wp.getWaypointPosition(componentAccessor)); + if (dist2 < minDist2) { + nearest = wp.getWaypointPosition(componentAccessor); + minDist2 = dist2; + } + } + } + + return nearest; + } + + @Override + public void mergeInto(@Nonnull IPrefabPath target, int worldGenId, @Nonnull ComponentAccessor componentAccessor) { + for (int i = 0; i < this.length.get(); i++) { + IPrefabPathWaypoint waypoint = this.waypoints.get(i); + waypoint.initialise(target.getId(), target.getName(), -1, waypoint.getPauseTime(), waypoint.getObservationAngle(), worldGenId, componentAccessor); + target.addLoadedWaypoint(waypoint, target.length(), waypoint.getOrder(), worldGenId); + } + + this.waypoints.clear(); + this.loadedCount.set(0); + this.length.set(0); + this.pathChanged.set(true); + } + + @Override + public void compact(int worldGenId) { + short length = 0; + + for (int i = 0; i < this.length.get(); i++) { + PatrolPathMarkerEntity wp = (PatrolPathMarkerEntity)this.waypoints.remove(i); + if (wp != null) { + wp.setOrder(length); + this.waypoints.put(length++, wp); + } + } + + PathPlugin.get().getLogger().at(Level.WARNING).log("Compacted path %s.%s from length %s to %s", worldGenId, this.name, this.length.get(), length); + this.loadedCount.set(length); + this.length.set(length); + this.pathChanged.set(true); + } + + @Override + public int length() { + return this.length.get(); + } + + public IPrefabPathWaypoint get(int index) { + if (index >= 0 && index < this.length.get()) { + return this.waypoints.get(index); + } else { + throw new IndexOutOfBoundsException(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/path/path/TransientPath.java b/src/com/hypixel/hytale/builtin/path/path/TransientPath.java new file mode 100644 index 0000000..3e73ee4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/path/TransientPath.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.builtin.path.path; + +import com.hypixel.hytale.builtin.path.waypoint.RelativeWaypointDefinition; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.universe.world.path.IPath; +import com.hypixel.hytale.server.core.universe.world.path.SimplePathWaypoint; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Queue; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TransientPath implements IPath { + protected final List waypoints = new ObjectArrayList<>(); + + public TransientPath() { + } + + public void addWaypoint(@Nonnull Vector3d position, @Nonnull Vector3f rotation) { + this.waypoints + .add( + new SimplePathWaypoint( + (short)this.waypoints.size(), new Transform(position.x, position.y, position.z, rotation.getPitch(), rotation.getYaw(), rotation.getRoll()) + ) + ); + } + + @Nullable + @Override + public UUID getId() { + return null; + } + + @Nullable + @Override + public String getName() { + return null; + } + + @Nonnull + @Override + public List getPathWaypoints() { + return Collections.unmodifiableList(this.waypoints); + } + + @Override + public int length() { + return this.waypoints.size(); + } + + public SimplePathWaypoint get(int index) { + return this.waypoints.get(index); + } + + @Nonnull + public static IPath buildPath( + @Nonnull Vector3d origin, @Nonnull Vector3f rotation, @Nonnull Queue instructions, double scale + ) { + TransientPath path = new TransientPath(); + path.addWaypoint(origin, rotation); + Vector3d position = new Vector3d(origin); + Vector3d directionVector = new Vector3d(); + Vector3f rotationVector = new Vector3f(rotation); + + while (!instructions.isEmpty()) { + RelativeWaypointDefinition instruction = instructions.poll(); + rotationVector.addYaw(instruction.getRotation()); + directionVector.assign(PhysicsMath.headingX(rotationVector.getYaw()), 0.0, PhysicsMath.headingZ(rotationVector.getYaw())); + directionVector.setLength(instruction.getDistance() * scale); + position.add(directionVector); + path.addWaypoint(position, rotationVector); + } + + return path; + } +} diff --git a/src/com/hypixel/hytale/builtin/path/path/TransientPathDefinition.java b/src/com/hypixel/hytale/builtin/path/path/TransientPathDefinition.java new file mode 100644 index 0000000..1337c33 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/path/TransientPathDefinition.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.builtin.path.path; + +import com.hypixel.hytale.builtin.path.waypoint.RelativeWaypointDefinition; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.universe.world.path.IPath; +import com.hypixel.hytale.server.core.universe.world.path.SimplePathWaypoint; +import java.util.ArrayDeque; +import java.util.List; +import javax.annotation.Nonnull; + +public class TransientPathDefinition { + protected final List waypointDefinitions; + protected final double scale; + + public TransientPathDefinition(List waypointDefinitions, double scale) { + this.waypointDefinitions = waypointDefinitions; + this.scale = scale; + } + + @Nonnull + public IPath buildPath(@Nonnull Vector3d position, @Nonnull Vector3f rotation) { + ArrayDeque queue = new ArrayDeque<>(this.waypointDefinitions); + return TransientPath.buildPath(position, rotation, queue, this.scale); + } +} diff --git a/src/com/hypixel/hytale/builtin/path/waypoint/IPrefabPathWaypoint.java b/src/com/hypixel/hytale/builtin/path/waypoint/IPrefabPathWaypoint.java new file mode 100644 index 0000000..feee152 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/waypoint/IPrefabPathWaypoint.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.builtin.path.waypoint; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.server.core.universe.world.path.IPath; +import com.hypixel.hytale.server.core.universe.world.path.IPathWaypoint; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; + +public interface IPrefabPathWaypoint extends IPathWaypoint { + void onReplaced(); + + void initialise(@Nonnull UUID var1, @Nonnull String var2, int var3, double var4, float var6, int var7, @Nonnull ComponentAccessor var8); + + IPath getParentPath(); +} diff --git a/src/com/hypixel/hytale/builtin/path/waypoint/RelativeWaypointDefinition.java b/src/com/hypixel/hytale/builtin/path/waypoint/RelativeWaypointDefinition.java new file mode 100644 index 0000000..f5d36b4 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/path/waypoint/RelativeWaypointDefinition.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.builtin.path.waypoint; + +public class RelativeWaypointDefinition { + protected final float rotation; + protected final double distance; + + public RelativeWaypointDefinition(float rotation, double distance) { + this.rotation = rotation; + this.distance = distance; + } + + public float getRotation() { + return this.rotation; + } + + public double getDistance() { + return this.distance; + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/PortalsPlugin.java b/src/com/hypixel/hytale/builtin/portals/PortalsPlugin.java new file mode 100644 index 0000000..5baefc5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/PortalsPlugin.java @@ -0,0 +1,133 @@ +package com.hypixel.hytale.builtin.portals; + +import com.hypixel.hytale.builtin.instances.removal.RemovalCondition; +import com.hypixel.hytale.builtin.portals.commands.FragmentCommands; +import com.hypixel.hytale.builtin.portals.commands.player.LeaveCommand; +import com.hypixel.hytale.builtin.portals.commands.utils.CursedHeldItemCommand; +import com.hypixel.hytale.builtin.portals.commands.voidevent.VoidEventCommands; +import com.hypixel.hytale.builtin.portals.components.PortalDevice; +import com.hypixel.hytale.builtin.portals.components.voidevent.VoidEvent; +import com.hypixel.hytale.builtin.portals.components.voidevent.VoidSpawner; +import com.hypixel.hytale.builtin.portals.integrations.PortalGameplayConfig; +import com.hypixel.hytale.builtin.portals.integrations.PortalMarkerProvider; +import com.hypixel.hytale.builtin.portals.integrations.PortalRemovalCondition; +import com.hypixel.hytale.builtin.portals.interactions.EnterPortalInteraction; +import com.hypixel.hytale.builtin.portals.interactions.ReturnPortalInteraction; +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.builtin.portals.systems.CloseWorldWhenBreakingDeviceSystems; +import com.hypixel.hytale.builtin.portals.systems.PortalInvalidDestinationSystem; +import com.hypixel.hytale.builtin.portals.systems.PortalTrackerSystems; +import com.hypixel.hytale.builtin.portals.systems.curse.CurseItemDropsSystem; +import com.hypixel.hytale.builtin.portals.systems.curse.DeleteCursedItemsOnSpawnSystem; +import com.hypixel.hytale.builtin.portals.systems.curse.DiedInPortalSystem; +import com.hypixel.hytale.builtin.portals.systems.voidevent.StartVoidEventInFragmentSystem; +import com.hypixel.hytale.builtin.portals.systems.voidevent.VoidEventRefSystem; +import com.hypixel.hytale.builtin.portals.systems.voidevent.VoidEventStagesSystem; +import com.hypixel.hytale.builtin.portals.systems.voidevent.VoidInvasionPortalsSpawnSystem; +import com.hypixel.hytale.builtin.portals.systems.voidevent.VoidSpawnerSystems; +import com.hypixel.hytale.builtin.portals.ui.PortalDevicePageSupplier; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenCustomUIInteraction; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.events.AddWorldEvent; +import com.hypixel.hytale.server.core.universe.world.events.RemoveWorldEvent; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import javax.annotation.Nonnull; + +public class PortalsPlugin extends JavaPlugin { + private static PortalsPlugin instance; + private ResourceType portalResourceType; + private ComponentType portalDeviceComponentType; + private ComponentType voidEventComponentType; + private ComponentType voidPortalComponentType; + public static final int MAX_CONCURRENT_FRAGMENTS = 4; + + public static PortalsPlugin getInstance() { + return instance; + } + + public PortalsPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + instance = this; + this.portalResourceType = this.getEntityStoreRegistry().registerResource(PortalWorld.class, PortalWorld::new); + this.portalDeviceComponentType = this.getChunkStoreRegistry().registerComponent(PortalDevice.class, "Portal", PortalDevice.CODEC); + this.voidEventComponentType = this.getEntityStoreRegistry().registerComponent(VoidEvent.class, VoidEvent::new); + this.voidPortalComponentType = this.getEntityStoreRegistry().registerComponent(VoidSpawner.class, VoidSpawner::new); + this.getCodecRegistry(OpenCustomUIInteraction.PAGE_CODEC).register("PortalDevice", PortalDevicePageSupplier.class, PortalDevicePageSupplier.CODEC); + this.getCodecRegistry(Interaction.CODEC) + .register("Portal", EnterPortalInteraction.class, EnterPortalInteraction.CODEC) + .register("PortalReturn", ReturnPortalInteraction.class, ReturnPortalInteraction.CODEC); + this.getEventRegistry().registerGlobal(RemoveWorldEvent.class, this::turnOffPortalWhenWorldRemoved); + this.getEventRegistry() + .registerGlobal(AddWorldEvent.class, event -> event.getWorld().getWorldMapManager().addMarkerProvider("portals", PortalMarkerProvider.INSTANCE)); + this.getChunkStoreRegistry().registerSystem(new PortalInvalidDestinationSystem()); + this.getChunkStoreRegistry().registerSystem(new CloseWorldWhenBreakingDeviceSystems.ComponentRemoved()); + this.getChunkStoreRegistry().registerSystem(new CloseWorldWhenBreakingDeviceSystems.EntityRemoved()); + this.getEntityStoreRegistry().registerSystem(new PortalTrackerSystems.TrackerSystem()); + this.getEntityStoreRegistry().registerSystem(new PortalTrackerSystems.UiTickingSystem()); + this.getEntityStoreRegistry().registerSystem(new DiedInPortalSystem()); + this.getEntityStoreRegistry().registerSystem(new CurseItemDropsSystem()); + this.getEntityStoreRegistry().registerSystem(new DeleteCursedItemsOnSpawnSystem()); + this.getEntityStoreRegistry().registerSystem(new VoidEventRefSystem()); + this.getEntityStoreRegistry().registerSystem(new VoidInvasionPortalsSpawnSystem()); + this.getEntityStoreRegistry().registerSystem(new VoidSpawnerSystems.Instantiate()); + this.getEntityStoreRegistry().registerSystem(new StartVoidEventInFragmentSystem()); + this.getEntityStoreRegistry().registerSystem(new VoidEventStagesSystem()); + this.getCommandRegistry().registerCommand(new LeaveCommand()); + this.getCommandRegistry().registerCommand(new CursedHeldItemCommand()); + this.getCommandRegistry().registerCommand(new VoidEventCommands()); + this.getCommandRegistry().registerCommand(new FragmentCommands()); + this.getCodecRegistry(RemovalCondition.CODEC).register("Portal", PortalRemovalCondition.class, PortalRemovalCondition.CODEC); + this.getCodecRegistry(GameplayConfig.PLUGIN_CODEC).register(PortalGameplayConfig.class, "Portal", PortalGameplayConfig.CODEC); + } + + private void turnOffPortalWhenWorldRemoved(RemoveWorldEvent event) { + for (World world : Universe.get().getWorlds().values()) { + if (world != event.getWorld()) { + world.execute(() -> PortalInvalidDestinationSystem.turnOffPortalsInWorld(world, event.getWorld())); + } + } + } + + public int countActiveFragments() { + Map worlds = Universe.get().getWorlds(); + int count = 0; + + for (World world : worlds.values()) { + PortalGameplayConfig portalConfig = world.getGameplayConfig().getPluginConfig().get(PortalGameplayConfig.class); + if (portalConfig != null) { + count++; + } + } + + return count; + } + + public ResourceType getPortalResourceType() { + return this.portalResourceType; + } + + public ComponentType getPortalDeviceComponentType() { + return this.portalDeviceComponentType; + } + + public ComponentType getVoidEventComponentType() { + return this.voidEventComponentType; + } + + public ComponentType getVoidPortalComponentType() { + return this.voidPortalComponentType; + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/commands/FragmentCommands.java b/src/com/hypixel/hytale/builtin/portals/commands/FragmentCommands.java new file mode 100644 index 0000000..9aedbcd --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/commands/FragmentCommands.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.builtin.portals.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class FragmentCommands extends AbstractCommandCollection { + public FragmentCommands() { + super("fragment", "server.commands.fragment.desc"); + this.addSubCommand(new TimerFragmentCommand()); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/commands/PortalWorldCommandBase.java b/src/com/hypixel/hytale/builtin/portals/commands/PortalWorldCommandBase.java new file mode 100644 index 0000000..918efc9 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/commands/PortalWorldCommandBase.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.portals.commands; + +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public abstract class PortalWorldCommandBase extends AbstractWorldCommand { + public PortalWorldCommandBase(String name, String description) { + super(name, description); + } + + @Override + protected final void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + PortalWorld portalWorld = store.getResource(PortalWorld.getResourceType()); + if (!portalWorld.exists()) { + context.sendMessage(Message.translation("server.commands.portals.notInPortal")); + } else { + this.execute(context, world, portalWorld, store); + } + } + + protected abstract void execute(@Nonnull CommandContext var1, @Nonnull World var2, @Nonnull PortalWorld var3, @Nonnull Store var4); +} diff --git a/src/com/hypixel/hytale/builtin/portals/commands/TimerFragmentCommand.java b/src/com/hypixel/hytale/builtin/portals/commands/TimerFragmentCommand.java new file mode 100644 index 0000000..6d195a6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/commands/TimerFragmentCommand.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.builtin.portals.commands; + +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class TimerFragmentCommand extends PortalWorldCommandBase { + private final RequiredArg remainingSecondsArg = this.withRequiredArg("seconds", "server.commands.fragment.timer.arg.seconds.desc", ArgTypes.INTEGER); + + public TimerFragmentCommand() { + super("timer", "server.commands.fragment.timer.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull PortalWorld portalWorld, @Nonnull Store store) { + int before = (int)portalWorld.getRemainingSeconds(world); + int desired = this.remainingSecondsArg.get(context); + portalWorld.setRemainingSeconds(world, desired); + context.sendMessage(Message.translation("server.commands.fragment.timer.success").param("before", before).param("after", desired)); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/commands/player/LeaveCommand.java b/src/com/hypixel/hytale/builtin/portals/commands/player/LeaveCommand.java new file mode 100644 index 0000000..9272e5e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/commands/player/LeaveCommand.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.portals.commands.player; + +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.builtin.portals.utils.CursedItems; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class LeaveCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_LEAVE_NOT_IN_PORTAL = Message.translation("server.commands.leave.notInPortal"); + @Nonnull + private static final Message MESSAGE_COMMANDS_LEAVE_UNCURSED_TEMP = Message.translation("server.commands.leave.uncursedTemp").color("#d955ef"); + + public LeaveCommand() { + super("leave", "server.commands.leave.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PortalWorld portalWorldResource = store.getResource(PortalWorld.getResourceType()); + if (!portalWorldResource.exists()) { + playerRef.sendMessage(MESSAGE_COMMANDS_LEAVE_NOT_IN_PORTAL); + } else { + boolean uncursedAny = CursedItems.uncurseAll(playerComponent.getInventory().getCombinedEverything()); + if (uncursedAny) { + playerRef.sendMessage(MESSAGE_COMMANDS_LEAVE_UNCURSED_TEMP); + } + + InstancesPlugin.exitInstance(ref, store); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/commands/utils/CursedHeldItemCommand.java b/src/com/hypixel/hytale/builtin/portals/commands/utils/CursedHeldItemCommand.java new file mode 100644 index 0000000..a81b11a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/commands/utils/CursedHeldItemCommand.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.portals.commands.utils; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.item.config.metadata.AdventureMetadata; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class CursedHeldItemCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CURSE_THIS_NOT_HOLDING_ITEM = Message.translation("server.commands.cursethis.notHoldingItem"); + + public CursedHeldItemCommand() { + super("cursethis", "server.commands.cursethis.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Inventory inventory = playerComponent.getInventory(); + if (!inventory.usingToolsItem()) { + ItemStack inHandItemStack = inventory.getActiveHotbarItem(); + if (inHandItemStack != null && !inHandItemStack.isEmpty()) { + AdventureMetadata adventureMeta = inHandItemStack.getFromMetadataOrDefault("Adventure", AdventureMetadata.CODEC); + adventureMeta.setCursed(!adventureMeta.isCursed()); + ItemStack edited = inHandItemStack.withMetadata(AdventureMetadata.KEYED_CODEC, adventureMeta); + inventory.getHotbar().replaceItemStackInSlot(inventory.getActiveHotbarSlot(), inHandItemStack, edited); + playerRef.sendMessage(Message.translation("server.commands.cursethis.done").param("state", adventureMeta.isCursed())); + } else { + playerRef.sendMessage(MESSAGE_COMMANDS_CURSE_THIS_NOT_HOLDING_ITEM); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/commands/voidevent/StartVoidEventCommand.java b/src/com/hypixel/hytale/builtin/portals/commands/voidevent/StartVoidEventCommand.java new file mode 100644 index 0000000..d719316 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/commands/voidevent/StartVoidEventCommand.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.builtin.portals.commands.voidevent; + +import com.hypixel.hytale.builtin.portals.integrations.PortalGameplayConfig; +import com.hypixel.hytale.builtin.portals.integrations.PortalRemovalCondition; +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.portalworld.PortalType; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class StartVoidEventCommand extends AbstractWorldCommand { + private final FlagArg overrideWorld = this.withFlagArg("override", "server.commands.voidevent.start.overrideArg"); + private static final String HARDCODED_GAMEPLAY_CONFIG = "Portal"; + private static final String HARDCODED_PORTAL_TYPE = "Hederas_Lair"; + + public StartVoidEventCommand() { + super("start", "server.commands.voidevent.start.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + PortalWorld portalWorld = store.getResource(PortalWorld.getResourceType()); + if (portalWorld.exists() && portalWorld.isVoidEventActive()) { + context.sendMessage(Message.translation("server.commands.voidevent.start.alreadyRunning")); + } else { + if (!portalWorld.exists()) { + if (!this.overrideWorld.get(context)) { + context.sendMessage(Message.translation("server.commands.portals.notInPortal")); + return; + } + + GameplayConfig gameplayConfig = GameplayConfig.getAssetMap().getAsset("Portal"); + PortalGameplayConfig portalGameplayConfig = gameplayConfig == null ? null : gameplayConfig.getPluginConfig().get(PortalGameplayConfig.class); + if (portalGameplayConfig == null) { + context.sendMessage(Message.translation("server.commands.voidevent.start.botchedConfig").param("config", "Portal")); + return; + } + + world.getWorldConfig().setGameplayConfig("Portal"); + portalWorld.init(PortalType.getAssetMap().getAsset("Hederas_Lair"), 10000, new PortalRemovalCondition(10000.0), portalGameplayConfig); + portalWorld.setSpawnPoint(new Transform(0.0, 100.0, 0.0)); + context.sendMessage(Message.translation("server.commands.voidevent.start.overrode")); + } + + portalWorld.setRemainingSeconds(world, 1.0); + context.sendMessage(Message.translation("server.commands.voidevent.start.success")); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/commands/voidevent/VoidEventCommands.java b/src/com/hypixel/hytale/builtin/portals/commands/voidevent/VoidEventCommands.java new file mode 100644 index 0000000..a8a845e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/commands/voidevent/VoidEventCommands.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.builtin.portals.commands.voidevent; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class VoidEventCommands extends AbstractCommandCollection { + public VoidEventCommands() { + super("voidevent", "server.commands.voidevent.desc"); + this.addSubCommand(new StartVoidEventCommand()); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/components/PortalDevice.java b/src/com/hypixel/hytale/builtin/portals/components/PortalDevice.java new file mode 100644 index 0000000..933e331 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/components/PortalDevice.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.builtin.portals.components; + +import com.hypixel.hytale.builtin.portals.PortalsPlugin; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.UUID; +import javax.annotation.Nullable; + +public class PortalDevice implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(PortalDevice.class, PortalDevice::new) + .append(new KeyedCodec<>("Config", PortalDeviceConfig.CODEC), (portal, o) -> portal.config = o, portal -> portal.config) + .add() + .append(new KeyedCodec<>("BaseBlockType", Codec.STRING), (portal, o) -> portal.baseBlockTypeKey = o, portal -> portal.baseBlockTypeKey) + .add() + .append(new KeyedCodec<>("DestinationWorld", Codec.UUID_BINARY), (portal, o) -> portal.destinationWorldUuid = o, portal -> portal.destinationWorldUuid) + .add() + .build(); + private PortalDeviceConfig config; + private String baseBlockTypeKey; + private UUID destinationWorldUuid; + + public static ComponentType getComponentType() { + return PortalsPlugin.getInstance().getPortalDeviceComponentType(); + } + + private PortalDevice() { + } + + public PortalDevice(PortalDeviceConfig config, String baseBlockTypeKey) { + this.config = config; + this.baseBlockTypeKey = baseBlockTypeKey; + } + + public PortalDeviceConfig getConfig() { + return this.config; + } + + public String getBaseBlockTypeKey() { + return this.baseBlockTypeKey; + } + + public BlockType getBaseBlockType() { + return BlockType.getAssetMap().getAsset(this.baseBlockTypeKey); + } + + @Nullable + public UUID getDestinationWorldUuid() { + return this.destinationWorldUuid; + } + + @Nullable + public World getDestinationWorld() { + if (this.destinationWorldUuid == null) { + return null; + } else { + World world = Universe.get().getWorld(this.destinationWorldUuid); + return world != null && world.isAlive() ? world : null; + } + } + + public void setDestinationWorld(World world) { + this.destinationWorldUuid = world.getWorldConfig().getUuid(); + } + + @Override + public Component clone() { + PortalDevice portal = new PortalDevice(); + portal.config = this.config; + portal.baseBlockTypeKey = this.baseBlockTypeKey; + portal.destinationWorldUuid = this.destinationWorldUuid; + return portal; + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/components/PortalDeviceConfig.java b/src/com/hypixel/hytale/builtin/portals/components/PortalDeviceConfig.java new file mode 100644 index 0000000..404ddb7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/components/PortalDeviceConfig.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.builtin.portals.components; + +import com.hypixel.hytale.builtin.portals.utils.BlockTypeUtils; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import javax.annotation.Nullable; + +public class PortalDeviceConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder(PortalDeviceConfig.class, PortalDeviceConfig::new) + .appendInherited( + new KeyedCodec<>("SpawningState", Codec.STRING), + (config, o) -> config.spawningState = o, + config -> config.spawningState, + (config, parent) -> config.spawningState = parent.spawningState + ) + .documentation("The StateData for the short transition from off to on, when the instance is being created") + .add() + .appendInherited( + new KeyedCodec<>("OnState", Codec.STRING), + (config, o) -> config.onState = o, + config -> config.onState, + (config, parent) -> config.onState = parent.onState + ) + .documentation("The StateData when the portal is summoned and active.") + .add() + .appendInherited( + new KeyedCodec<>("OffState", Codec.STRING), + (config, o) -> config.offState = o, + config -> config.offState, + (config, parent) -> config.offState = parent.offState + ) + .documentation("The StateData when there is no portal and the device is inactive.") + .add() + .appendInherited( + new KeyedCodec<>("ReturnBlockType", Codec.STRING), + (config, o) -> config.returnBlock = o, + config -> config.returnBlock, + (config, parent) -> config.returnBlock = parent.returnBlock + ) + .documentation("This block type will be placed (once) on the spawn point of the portal world.") + .add() + .build(); + private String onState = "Active"; + private String spawningState = "Spawning"; + private String offState = "default"; + private String returnBlock; + + public PortalDeviceConfig() { + } + + public String getOnState() { + return this.onState; + } + + public String getSpawningState() { + return this.spawningState; + } + + public String getOffState() { + return this.offState; + } + + @Nullable + public String getReturnBlock() { + return this.returnBlock; + } + + public String[] getBlockStates() { + return new String[]{this.onState, this.spawningState, this.offState}; + } + + public boolean areBlockStatesValid(BlockType baseBlockType) { + for (String stateKey : this.getBlockStates()) { + BlockType state = BlockTypeUtils.getBlockForState(baseBlockType, stateKey); + if (state == null) { + return false; + } + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/components/voidevent/VoidEvent.java b/src/com/hypixel/hytale/builtin/portals/components/voidevent/VoidEvent.java new file mode 100644 index 0000000..ec5b26f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/components/voidevent/VoidEvent.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.builtin.portals.components.voidevent; + +import com.hypixel.hytale.builtin.portals.PortalsPlugin; +import com.hypixel.hytale.builtin.portals.components.voidevent.config.VoidEventConfig; +import com.hypixel.hytale.builtin.portals.components.voidevent.config.VoidEventStage; +import com.hypixel.hytale.builtin.portals.integrations.PortalGameplayConfig; +import com.hypixel.hytale.builtin.portals.utils.spatial.SpatialHashGrid; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nullable; + +public class VoidEvent implements Component { + public static final double MIN_BLOCKS_BETWEEN_SPAWNERS = 62.0; + private SpatialHashGrid> voidSpawners = new SpatialHashGrid<>(62.0); + private VoidEventStage activeStage; + + public VoidEvent() { + } + + public static ComponentType getComponentType() { + return PortalsPlugin.getInstance().getVoidEventComponentType(); + } + + public VoidEventConfig getConfig(World world) { + PortalGameplayConfig portalConfig = world.getGameplayConfig().getPluginConfig().get(PortalGameplayConfig.class); + return portalConfig.getVoidEvent(); + } + + public SpatialHashGrid> getVoidSpawners() { + return this.voidSpawners; + } + + @Nullable + public VoidEventStage getActiveStage() { + return this.activeStage; + } + + public void setActiveStage(@Nullable VoidEventStage activeStage) { + this.activeStage = activeStage; + } + + @Nullable + @Override + public Component clone() { + VoidEvent clone = new VoidEvent(); + clone.voidSpawners = this.voidSpawners; + clone.activeStage = this.activeStage; + return clone; + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/components/voidevent/VoidSpawner.java b/src/com/hypixel/hytale/builtin/portals/components/voidevent/VoidSpawner.java new file mode 100644 index 0000000..26fcbaa --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/components/voidevent/VoidSpawner.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.portals.components.voidevent; + +import com.hypixel.hytale.builtin.portals.PortalsPlugin; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nullable; + +public class VoidSpawner implements Component { + private List spawnBeaconUuids = new ObjectArrayList<>(); + + public VoidSpawner() { + } + + public static ComponentType getComponentType() { + return PortalsPlugin.getInstance().getVoidPortalComponentType(); + } + + public List getSpawnBeaconUuids() { + return this.spawnBeaconUuids; + } + + @Nullable + @Override + public Component clone() { + VoidSpawner clone = new VoidSpawner(); + clone.spawnBeaconUuids = this.spawnBeaconUuids; + return clone; + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/components/voidevent/config/InvasionPortalConfig.java b/src/com/hypixel/hytale/builtin/portals/components/voidevent/config/InvasionPortalConfig.java new file mode 100644 index 0000000..da6b887 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/components/voidevent/config/InvasionPortalConfig.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.builtin.portals.components.voidevent.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; + +public class InvasionPortalConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder(InvasionPortalConfig.class, InvasionPortalConfig::new) + .append(new KeyedCodec<>("BlockKey", Codec.STRING), (config, o) -> config.blockKey = o, config -> config.blockKey) + .documentation("The block used for evil portals that spawn around the world during the event") + .add() + .append(new KeyedCodec<>("SpawnBeacons", Codec.STRING_ARRAY), (config, o) -> config.spawnBeacons = o, config -> config.spawnBeacons) + .add() + .documentation("An array of SpawnBeacon IDs, which will make mobs spawn around the evil portals") + .append(new KeyedCodec<>("OnSpawnParticles", Codec.STRING), (config, o) -> config.onSpawnParticles = o, config -> config.onSpawnParticles) + .documentation("A particle system ID to spawn when the portal spawns, should be a temporary one.") + .add() + .build(); + private String blockKey; + private String[] spawnBeacons; + private String onSpawnParticles; + + public InvasionPortalConfig() { + } + + public String getBlockKey() { + return this.blockKey; + } + + public BlockType getBlockType() { + return BlockType.getAssetMap().getAsset(this.blockKey); + } + + @Nullable + public String getOnSpawnParticles() { + return this.onSpawnParticles; + } + + @Nullable + public String[] getSpawnBeacons() { + return this.spawnBeacons; + } + + public List getSpawnBeaconsList() { + return this.spawnBeacons == null ? Collections.emptyList() : Arrays.asList(this.spawnBeacons); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/components/voidevent/config/VoidEventConfig.java b/src/com/hypixel/hytale/builtin/portals/components/voidevent/config/VoidEventConfig.java new file mode 100644 index 0000000..a9683f6 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/components/voidevent/config/VoidEventConfig.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.builtin.portals.components.voidevent.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.server.core.asset.type.ambiencefx.config.AmbienceFX; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import javax.annotation.Nullable; + +public class VoidEventConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder(VoidEventConfig.class, VoidEventConfig::new) + .append(new KeyedCodec<>("DurationSeconds", Codec.INTEGER), (config, o) -> config.durationSeconds = o, config -> config.durationSeconds) + .documentation( + "How long the void event lasts in seconds. The void event starts at the end of the instance. If your fragment is 10 minutes and this is 180 seconds, it will start 7 minutes in." + ) + .add() + .append( + new KeyedCodec<>("InvasionPortals", InvasionPortalConfig.CODEC), (config, o) -> config.portalConfig = o, config -> config.portalConfig + ) + .documentation("Configuration regarding the enemy portals that spawn around the players during the event") + .add() + .append( + new KeyedCodec<>("Stages", new ArrayCodec<>(VoidEventStage.CODEC, VoidEventStage[]::new)), (config, o) -> config.stages = o, config -> config.stages + ) + .documentation( + "Certain event characteristics happen over stages that can be defined here. Stages are spread in time. Only one stage is \"active\" at a time." + ) + .add() + .append(new KeyedCodec<>("MusicAmbienceFX", Codec.STRING), (config, o) -> config.musicAmbienceFX = o, config -> config.musicAmbienceFX) + .documentation("The ID of an AmbienceFX which will be used for the music during the event") + .addValidator(AmbienceFX.VALIDATOR_CACHE.getValidator()) + .add() + .afterDecode(VoidEventConfig::processConfig) + .build(); + private int durationSeconds = 180; + private InvasionPortalConfig portalConfig; + private VoidEventStage[] stages; + private List stagesSortedByStartTime; + private String musicAmbienceFX; + + public VoidEventConfig() { + } + + public int getDurationSeconds() { + return this.durationSeconds; + } + + public int getShouldStartAfterSeconds(int portalTimeLimitSeconds) { + return Math.max(10, portalTimeLimitSeconds - this.durationSeconds); + } + + public InvasionPortalConfig getInvasionPortalConfig() { + return this.portalConfig; + } + + public VoidEventStage[] getStages() { + return this.stages; + } + + public List getStagesSortedByStartTime() { + return this.stagesSortedByStartTime; + } + + @Nullable + public String getMusicAmbienceFX() { + return this.musicAmbienceFX; + } + + private void processConfig() { + this.stagesSortedByStartTime = new ObjectArrayList<>(); + if (this.stages != null) { + Collections.addAll(this.stagesSortedByStartTime, this.stages); + this.stagesSortedByStartTime.sort(Comparator.comparingInt(VoidEventStage::getSecondsInto)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/components/voidevent/config/VoidEventStage.java b/src/com/hypixel/hytale/builtin/portals/components/voidevent/config/VoidEventStage.java new file mode 100644 index 0000000..0991bf5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/components/voidevent/config/VoidEventStage.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.portals.components.voidevent.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import javax.annotation.Nullable; + +public class VoidEventStage { + public static final BuilderCodec CODEC = BuilderCodec.builder(VoidEventStage.class, VoidEventStage::new) + .append(new KeyedCodec<>("SecondsInto", Codec.INTEGER), (stage, o) -> stage.secondsInto = o, stage -> stage.secondsInto) + .documentation("How many seconds into the void event does this stage becomes the active stage.") + .add() + .append(new KeyedCodec<>("ForcedWeather", Codec.STRING), (stage, o) -> stage.forcedWeatherId = o, stage -> stage.forcedWeatherId) + .documentation("What weather to force during this stage.") + .addValidator(Weather.VALIDATOR_CACHE.getValidator()) + .add() + .build(); + private int secondsInto; + private String forcedWeatherId; + + public VoidEventStage() { + } + + public int getSecondsInto() { + return this.secondsInto; + } + + @Nullable + public String getForcedWeatherId() { + return this.forcedWeatherId; + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/integrations/PortalGameplayConfig.java b/src/com/hypixel/hytale/builtin/portals/integrations/PortalGameplayConfig.java new file mode 100644 index 0000000..53b46bc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/integrations/PortalGameplayConfig.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.builtin.portals.integrations; + +import com.hypixel.hytale.builtin.portals.components.voidevent.config.VoidEventConfig; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nullable; + +public class PortalGameplayConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder(PortalGameplayConfig.class, PortalGameplayConfig::new) + .append(new KeyedCodec<>("VoidEvent", VoidEventConfig.CODEC), (config, o) -> config.voidEvent = o, config -> config.voidEvent) + .add() + .build(); + private VoidEventConfig voidEvent; + + public PortalGameplayConfig() { + } + + @Nullable + public VoidEventConfig getVoidEvent() { + return this.voidEvent; + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/integrations/PortalMarkerProvider.java b/src/com/hypixel/hytale/builtin/portals/integrations/PortalMarkerProvider.java new file mode 100644 index 0000000..5d80385 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/integrations/PortalMarkerProvider.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.portals.integrations; + +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import com.hypixel.hytale.server.core.universe.world.spawn.IndividualSpawnProvider; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; +import com.hypixel.hytale.server.core.util.PositionUtil; + +public class PortalMarkerProvider implements WorldMapManager.MarkerProvider { + public static final PortalMarkerProvider INSTANCE = new PortalMarkerProvider(); + + public PortalMarkerProvider() { + } + + @Override + public void update(World world, GameplayConfig gameplayConfig, WorldMapTracker tracker, int chunkViewRadius, int playerChunkX, int playerChunkZ) { + addSpawn(world, tracker, chunkViewRadius, playerChunkX, playerChunkZ); + } + + private static void addSpawn(World world, WorldMapTracker tracker, int chunkViewRadius, int playerChunkX, int playerChunkZ) { + if (world.getWorldConfig().getSpawnProvider() instanceof IndividualSpawnProvider individualSpawnProvider) { + Transform spawnPoint = individualSpawnProvider.getFirstSpawnPoint(); + if (spawnPoint != null) { + tracker.trySendMarker( + chunkViewRadius, + playerChunkX, + playerChunkZ, + spawnPoint.getPosition(), + spawnPoint.getRotation().getYaw(), + "Portal", + "Fragment Exit", + spawnPoint, + (id, name, sp) -> new MapMarker(id, name, "Portal.png", PositionUtil.toTransformPacket(sp), null) + ); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/integrations/PortalRemovalCondition.java b/src/com/hypixel/hytale/builtin/portals/integrations/PortalRemovalCondition.java new file mode 100644 index 0000000..1e1f32a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/integrations/PortalRemovalCondition.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.builtin.portals.integrations; + +import com.hypixel.hytale.builtin.instances.removal.InstanceDataResource; +import com.hypixel.hytale.builtin.instances.removal.RemovalCondition; +import com.hypixel.hytale.builtin.instances.removal.TimeoutCondition; +import com.hypixel.hytale.builtin.instances.removal.WorldEmptyCondition; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Duration; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class PortalRemovalCondition implements RemovalCondition { + public static final BuilderCodec CODEC = BuilderCodec.builder(PortalRemovalCondition.class, PortalRemovalCondition::new) + .documentation("A condition for temporary portal worlds.") + .append(new KeyedCodec<>("TimeoutSeconds", Codec.DOUBLE), (o, i) -> o.setTimeLimitSeconds(i), o -> o.getTimeLimitSeconds()) + .documentation("How long the portal world will stay open (in seconds) after being joined.") + .add() + .build(); + private final WorldEmptyCondition worldEmptyCondition = new WorldEmptyCondition(90.0); + private TimeoutCondition timeLimitCondition; + + public PortalRemovalCondition() { + this(60.0); + } + + public PortalRemovalCondition(double timeLimitSeconds) { + this.timeLimitCondition = new TimeoutCondition(timeLimitSeconds); + } + + private double getTimeLimitSeconds() { + return this.timeLimitCondition.getTimeoutSeconds(); + } + + private void setTimeLimitSeconds(double timeLimitSeconds) { + this.timeLimitCondition = new TimeoutCondition(timeLimitSeconds); + } + + public double getElapsedSeconds(World world) { + double timeLimitSeconds = this.timeLimitCondition.getTimeoutSeconds(); + double remainingSeconds = this.getRemainingSeconds(world); + return Math.max(0.0, timeLimitSeconds - remainingSeconds); + } + + public double getRemainingSeconds(World world) { + Store chunkStore = world.getChunkStore().getStore(); + Store entityStore = world.getEntityStore().getStore(); + InstanceDataResource instanceData = chunkStore.getResource(InstanceDataResource.getResourceType()); + TimeResource timeResource = entityStore.getResource(TimeResource.getResourceType()); + Instant timeoutInstant = instanceData.getTimeoutTimer(); + if (timeoutInstant == null) { + return this.timeLimitCondition.getTimeoutSeconds(); + } else { + double remainingSeconds = Duration.between(timeResource.getNow(), timeoutInstant).toNanos() / 1.0E9; + return Math.max(0.0, remainingSeconds); + } + } + + public void setRemainingSeconds(World world, double seconds) { + seconds = Math.max(0.0, seconds); + Store chunkStore = world.getChunkStore().getStore(); + Store entityStore = world.getEntityStore().getStore(); + InstanceDataResource instanceData = chunkStore.getResource(InstanceDataResource.getResourceType()); + TimeResource timeResource = entityStore.getResource(TimeResource.getResourceType()); + instanceData.setTimeoutTimer(timeResource.getNow().plusMillis((long)(seconds * 1000.0))); + } + + @Override + public boolean shouldRemoveWorld(@Nonnull Store store) { + InstanceDataResource instanceData = store.getResource(InstanceDataResource.getResourceType()); + return instanceData.hadPlayer() && this.timeLimitCondition.shouldRemoveWorld(store) ? true : this.worldEmptyCondition.shouldRemoveWorld(store); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/interactions/EnterPortalInteraction.java b/src/com/hypixel/hytale/builtin/portals/interactions/EnterPortalInteraction.java new file mode 100644 index 0000000..5d4b60e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/interactions/EnterPortalInteraction.java @@ -0,0 +1,176 @@ +package com.hypixel.hytale.builtin.portals.interactions; + +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.builtin.portals.components.PortalDevice; +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.builtin.portals.ui.PortalDeviceActivePage; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Duration; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EnterPortalInteraction extends SimpleBlockInteraction { + @Nonnull + public static final Duration MINIMUM_TIME_IN_WORLD = Duration.ofMillis(3000L); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + EnterPortalInteraction.class, EnterPortalInteraction::new, SimpleBlockInteraction.CODEC + ) + .build(); + private static final Message MESSAGE_PORTALS_DEVICE_REF_INVALID = Message.translation("server.portals.device.refInvalid"); + private static final Message MESSAGE_PORTALS_DEVICE_WORLD_IS_DEAD = Message.translation("server.portals.device.worldIsDead"); + private static final Message MESSAGE_PORTALS_DEVICE_NO_SPAWN = Message.translation("server.portals.device.worldNoSpawn"); + private static final Message MESSAGE_PORTALS_DEVICE_BLOCK_ENTITY_REF_INVALID = Message.translation("server.portals.device.blockEntityRefInvalid"); + + public EnterPortalInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + Ref ref = context.getEntity(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.getState().state = InteractionState.Failed; + } else if (playerComponent.getSinceLastSpawnNanos() < MINIMUM_TIME_IN_WORLD.toNanos()) { + context.getState().state = InteractionState.Failed; + } else { + PortalDevice portalDevice = BlockModule.get().getComponent(PortalDevice.getComponentType(), world, targetBlock.x, targetBlock.y, targetBlock.z); + if (portalDevice == null) { + context.getState().state = InteractionState.Failed; + } else { + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)); + if (chunk == null) { + context.getState().state = InteractionState.Failed; + } else { + BlockType blockType = chunk.getBlockType(targetBlock); + if (blockType == null) { + context.getState().state = InteractionState.Failed; + } else { + RotationTuple rotation = chunk.getRotation(targetBlock.x, targetBlock.y, targetBlock.z); + double yaw = rotation.yaw().getRadians() + Math.PI; + Transform returnTransform = new Transform(targetBlock.x + 0.5, targetBlock.y + 0.5, targetBlock.z + 0.5, 0.0F, (float)yaw, 0.0F); + World targetWorld = portalDevice.getDestinationWorld(); + if (targetWorld == null) { + playerComponent.sendMessage(MESSAGE_PORTALS_DEVICE_WORLD_IS_DEAD); + context.getState().state = InteractionState.Failed; + } else { + UUIDComponent uuidComponent = commandBuffer.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID playerUuid = uuidComponent.getUuid(); + fetchTargetWorldState(targetWorld, playerUuid).thenAcceptAsync(state -> { + if (!ref.isValid()) { + playerComponent.sendMessage(MESSAGE_PORTALS_DEVICE_REF_INVALID); + context.getState().state = InteractionState.Failed; + } else { + switch (state) { + case OKAY: + InstancesPlugin.teleportPlayerToInstance(ref, commandBuffer, targetWorld, returnTransform); + break; + case WORLD_DEAD: + playerComponent.sendMessage(MESSAGE_PORTALS_DEVICE_WORLD_IS_DEAD); + context.getState().state = InteractionState.Failed; + break; + case DIED_IN_WORLD: + PlayerRef playerRefComponent = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + Ref blockEntityRef = BlockModule.getBlockEntity(world, targetBlock.x, targetBlock.y, targetBlock.z); + if (blockEntityRef == null || !blockEntityRef.isValid()) { + playerComponent.sendMessage(MESSAGE_PORTALS_DEVICE_BLOCK_ENTITY_REF_INVALID); + context.getState().state = InteractionState.Failed; + return; + } + + PortalDeviceActivePage activePage = new PortalDeviceActivePage(playerRefComponent, portalDevice.getConfig(), blockEntityRef); + playerComponent.getPageManager().openCustomPage(ref, world.getEntityStore().getStore(), activePage); + break; + case NO_SPAWN_AVAILABLE: + playerComponent.sendMessage(MESSAGE_PORTALS_DEVICE_NO_SPAWN); + context.getState().state = InteractionState.Failed; + } + } + }, world); + } + } + } + } + } + } + + @Nonnull + private static CompletableFuture fetchTargetWorldState(@Nonnull World world, @Nonnull UUID playerId) { + return CompletableFuture.supplyAsync( + () -> { + PortalWorld portalWorld = world.getEntityStore().getStore().getResource(PortalWorld.getResourceType()); + if (!portalWorld.exists()) { + return EnterPortalInteraction.TargetWorldState.WORLD_DEAD; + } else if (portalWorld.getSpawnPoint() == null) { + return EnterPortalInteraction.TargetWorldState.NO_SPAWN_AVAILABLE; + } else { + return portalWorld.getDiedInWorld().contains(playerId) + ? EnterPortalInteraction.TargetWorldState.DIED_IN_WORLD + : EnterPortalInteraction.TargetWorldState.OKAY; + } + }, + world + ); + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } + + private static enum TargetWorldState { + OKAY, + WORLD_DEAD, + DIED_IN_WORLD, + NO_SPAWN_AVAILABLE; + + private TargetWorldState() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/interactions/ReturnPortalInteraction.java b/src/com/hypixel/hytale/builtin/portals/interactions/ReturnPortalInteraction.java new file mode 100644 index 0000000..31e5096 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/interactions/ReturnPortalInteraction.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.builtin.portals.interactions; + +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.builtin.portals.utils.CursedItems; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Duration; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ReturnPortalInteraction extends SimpleBlockInteraction { + @Nonnull + public static final Duration MINIMUM_TIME_IN_WORLD = Duration.ofSeconds(15L); + @Nonnull + public static final Duration WARNING_TIME = Duration.ofSeconds(4L); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ReturnPortalInteraction.class, ReturnPortalInteraction::new, SimpleBlockInteraction.CODEC + ) + .build(); + private static final Message MESSAGE_PORTALS_ATTUNING_TO_WORLD = Message.translation("server.portals.attuningToWorld"); + private static final Message MESSAGE_PORTALS_DEVICE_NOT_IN_PORTAL_WORLD = Message.translation("server.portals.device.notInPortalWorld"); + + public ReturnPortalInteraction() { + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + Ref ref = context.getEntity(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.getState().state = InteractionState.Failed; + } else { + long elapsedNanosInWorld = playerComponent.getSinceLastSpawnNanos(); + if (elapsedNanosInWorld < MINIMUM_TIME_IN_WORLD.toNanos()) { + if (elapsedNanosInWorld > WARNING_TIME.toNanos()) { + playerComponent.sendMessage(MESSAGE_PORTALS_ATTUNING_TO_WORLD); + } + + context.getState().state = InteractionState.Failed; + } else { + PortalWorld portalWorld = commandBuffer.getResource(PortalWorld.getResourceType()); + if (!portalWorld.exists()) { + playerComponent.sendMessage(MESSAGE_PORTALS_DEVICE_NOT_IN_PORTAL_WORLD); + context.getState().state = InteractionState.Failed; + } else { + CursedItems.uncurseAll(playerComponent.getInventory().getCombinedEverything()); + InstancesPlugin.exitInstance(ref, commandBuffer); + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + context.getState().state = InteractionState.Failed; + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/resources/PortalWorld.java b/src/com/hypixel/hytale/builtin/portals/resources/PortalWorld.java new file mode 100644 index 0000000..8a8dc76 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/resources/PortalWorld.java @@ -0,0 +1,156 @@ +package com.hypixel.hytale.builtin.portals.resources; + +import com.hypixel.hytale.builtin.portals.PortalsPlugin; +import com.hypixel.hytale.builtin.portals.components.voidevent.config.VoidEventConfig; +import com.hypixel.hytale.builtin.portals.integrations.PortalGameplayConfig; +import com.hypixel.hytale.builtin.portals.integrations.PortalRemovalCondition; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.protocol.packets.interface_.PortalDef; +import com.hypixel.hytale.protocol.packets.interface_.PortalState; +import com.hypixel.hytale.protocol.packets.interface_.UpdatePortal; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.portalworld.PortalType; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Collections; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; + +public class PortalWorld implements Resource { + private String portalTypeId; + private int timeLimitSeconds; + private PortalRemovalCondition worldRemovalCondition; + private PortalGameplayConfig storedGameplayConfig; + private Set diedInWorld; + private Set seesUi; + private Transform spawnPoint; + private Ref voidEventRef; + + public PortalWorld() { + } + + public static ResourceType getResourceType() { + return PortalsPlugin.getInstance().getPortalResourceType(); + } + + public void init(PortalType portalType, int timeLimitSeconds, PortalRemovalCondition removalCondition, PortalGameplayConfig gameplayConfig) { + this.portalTypeId = portalType.getId(); + this.timeLimitSeconds = timeLimitSeconds; + this.worldRemovalCondition = removalCondition; + this.storedGameplayConfig = gameplayConfig; + this.diedInWorld = Collections.newSetFromMap(new ConcurrentHashMap<>()); + this.seesUi = Collections.newSetFromMap(new ConcurrentHashMap<>()); + } + + public PortalType getPortalType() { + return this.portalTypeId == null ? null : PortalType.getAssetMap().getAsset(this.portalTypeId); + } + + public boolean exists() { + return this.getPortalType() != null; + } + + public int getTimeLimitSeconds() { + return this.timeLimitSeconds; + } + + public double getElapsedSeconds(World world) { + return this.worldRemovalCondition.getElapsedSeconds(world); + } + + public double getRemainingSeconds(World world) { + return this.worldRemovalCondition.getRemainingSeconds(world); + } + + public void setRemainingSeconds(World world, double seconds) { + this.worldRemovalCondition.setRemainingSeconds(world, seconds); + } + + public Set getDiedInWorld() { + return this.diedInWorld; + } + + public Set getSeesUi() { + return this.seesUi; + } + + public PortalGameplayConfig getGameplayConfig() { + GameplayConfig gameplayConfig = this.getPortalType().getGameplayConfig(); + PortalGameplayConfig portalGameplayConfig = gameplayConfig == null ? null : gameplayConfig.getPluginConfig().get(PortalGameplayConfig.class); + return portalGameplayConfig != null ? portalGameplayConfig : this.storedGameplayConfig; + } + + public VoidEventConfig getVoidEventConfig() { + return this.getGameplayConfig().getVoidEvent(); + } + + @Nullable + public Transform getSpawnPoint() { + return this.spawnPoint; + } + + public void setSpawnPoint(Transform spawnPoint) { + this.spawnPoint = spawnPoint; + } + + @Nullable + public Ref getVoidEventRef() { + if (this.voidEventRef != null && !this.voidEventRef.isValid()) { + this.voidEventRef = null; + } + + return this.voidEventRef; + } + + public boolean isVoidEventActive() { + return this.getVoidEventRef() != null; + } + + public void setVoidEventRef(Ref voidEventRef) { + this.voidEventRef = voidEventRef; + } + + public UpdatePortal createFullPacket(World world) { + boolean hasBreach = this.getPortalType().isVoidInvasionEnabled(); + int explorationSeconds; + int breachSeconds; + if (hasBreach) { + breachSeconds = this.getGameplayConfig().getVoidEvent().getDurationSeconds(); + explorationSeconds = this.timeLimitSeconds - breachSeconds; + } else { + explorationSeconds = this.timeLimitSeconds; + breachSeconds = 0; + } + + PortalDef portalDef = new PortalDef(this.getPortalType().getDescription().getDisplayNameKey(), explorationSeconds, breachSeconds); + return new UpdatePortal(this.createStateForPacket(world), portalDef); + } + + public UpdatePortal createUpdatePacket(World world) { + return new UpdatePortal(this.createStateForPacket(world), null); + } + + private PortalState createStateForPacket(World world) { + double remainingSeconds = this.worldRemovalCondition.getRemainingSeconds(world); + int breachSeconds = this.getGameplayConfig().getVoidEvent().getDurationSeconds(); + if (this.getPortalType().isVoidInvasionEnabled() && remainingSeconds > breachSeconds) { + remainingSeconds -= breachSeconds; + } + + return new PortalState((int)Math.ceil(remainingSeconds), this.isVoidEventActive()); + } + + @Override + public Resource clone() { + PortalWorld clone = new PortalWorld(); + clone.portalTypeId = this.portalTypeId; + clone.timeLimitSeconds = this.timeLimitSeconds; + clone.worldRemovalCondition = this.worldRemovalCondition; + return clone; + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/systems/CloseWorldWhenBreakingDeviceSystems.java b/src/com/hypixel/hytale/builtin/portals/systems/CloseWorldWhenBreakingDeviceSystems.java new file mode 100644 index 0000000..977cf71 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/systems/CloseWorldWhenBreakingDeviceSystems.java @@ -0,0 +1,105 @@ +package com.hypixel.hytale.builtin.portals.systems; + +import com.hypixel.hytale.builtin.portals.components.PortalDevice; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public final class CloseWorldWhenBreakingDeviceSystems { + private CloseWorldWhenBreakingDeviceSystems() { + } + + private static void maybeCloseFragmentWorld(@Nullable PortalDevice device) { + if (device != null) { + World world = device.getDestinationWorld(); + if (world != null) { + if (world.getPlayerCount() <= 0) { + Universe.get().removeWorld(world.getName()); + } + } + } + } + + public static class ComponentRemoved extends RefChangeSystem { + public ComponentRemoved() { + } + + @Override + public ComponentType componentType() { + return PortalDevice.getComponentType(); + } + + public void onComponentAdded( + @NonNullDecl Ref ref, + @NonNullDecl PortalDevice component, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + } + + public void onComponentSet( + @NonNullDecl Ref ref, + @NullableDecl PortalDevice oldComponent, + @NonNullDecl PortalDevice newComponent, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @NonNullDecl Ref ref, + @NonNullDecl PortalDevice component, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + CloseWorldWhenBreakingDeviceSystems.maybeCloseFragmentWorld(component); + } + + @Override + public Query getQuery() { + return this.componentType(); + } + } + + public static class EntityRemoved extends RefSystem { + public EntityRemoved() { + } + + @Override + public void onEntityAdded( + @NonNullDecl Ref ref, + @NonNullDecl AddReason reason, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + } + + @Override + public void onEntityRemove( + @NonNullDecl Ref ref, + @NonNullDecl RemoveReason reason, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + PortalDevice device = store.getComponent(ref, PortalDevice.getComponentType()); + CloseWorldWhenBreakingDeviceSystems.maybeCloseFragmentWorld(device); + } + + @Override + public Query getQuery() { + return PortalDevice.getComponentType(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/systems/PortalInvalidDestinationSystem.java b/src/com/hypixel/hytale/builtin/portals/systems/PortalInvalidDestinationSystem.java new file mode 100644 index 0000000..2722661 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/systems/PortalInvalidDestinationSystem.java @@ -0,0 +1,91 @@ +package com.hypixel.hytale.builtin.portals.systems; + +import com.hypixel.hytale.builtin.portals.components.PortalDevice; +import com.hypixel.hytale.builtin.portals.components.PortalDeviceConfig; +import com.hypixel.hytale.builtin.portals.utils.BlockTypeUtils; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.AndQuery; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class PortalInvalidDestinationSystem extends RefSystem { + public PortalInvalidDestinationSystem() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + if (reason == AddReason.LOAD) { + World originWorld = store.getExternalData().getWorld(); + PortalDevice portalDevice = commandBuffer.getComponent(ref, PortalDevice.getComponentType()); + BlockModule.BlockStateInfo blockStateInfo = commandBuffer.getComponent(ref, BlockModule.BlockStateInfo.getComponentType()); + World destinationWorld = portalDevice.getDestinationWorld(); + if (destinationWorld == null) { + originWorld.execute(() -> turnOffPortalBlock(originWorld, portalDevice, blockStateInfo)); + } + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Override + public Query getQuery() { + return Query.and(PortalDevice.getComponentType(), BlockModule.BlockStateInfo.getComponentType()); + } + + public static void turnOffPortalsInWorld(World originWorld, World destinationWorld) { + UUID destinationWorldUuid = destinationWorld.getWorldConfig().getUuid(); + Store store = originWorld.getChunkStore().getStore(); + AndQuery entityQuery = Query.and(PortalDevice.getComponentType(), BlockModule.BlockStateInfo.getComponentType()); + store.forEachEntityParallel(entityQuery, (id, archetypeChunk, commandBuffer) -> { + PortalDevice portalDevice = archetypeChunk.getComponent(id, PortalDevice.getComponentType()); + if (destinationWorldUuid.equals(portalDevice.getDestinationWorldUuid())) { + BlockModule.BlockStateInfo blockStateInfo = archetypeChunk.getComponent(id, BlockModule.BlockStateInfo.getComponentType()); + originWorld.execute(() -> turnOffPortalBlock(originWorld, portalDevice, blockStateInfo)); + } + }); + } + + private static void turnOffPortalBlock(World world, PortalDevice portalDevice, BlockModule.BlockStateInfo blockStateInfo) { + Ref chunkRef = blockStateInfo.getChunkRef(); + if (chunkRef != null && chunkRef.isValid()) { + Store store = world.getChunkStore().getStore(); + WorldChunk worldChunk = store.getComponent(chunkRef, WorldChunk.getComponentType()); + if (worldChunk != null) { + int index = blockStateInfo.getIndex(); + int x = ChunkUtil.xFromBlockInColumn(index); + int y = ChunkUtil.yFromBlockInColumn(index); + int z = ChunkUtil.zFromBlockInColumn(index); + PortalDeviceConfig config = portalDevice.getConfig(); + BlockType blockType = worldChunk.getBlockType(x, y, z); + BlockType offState = BlockTypeUtils.getBlockForState(blockType, config.getOffState()); + if (offState == null) { + HytaleLogger.getLogger() + .at(Level.WARNING) + .log("Couldn't find/set off set for portal block, either " + blockType.getId() + " is misconfigured or the block changed unexpectedly"); + } else { + worldChunk.setBlockInteractionState(x, y, z, blockType, config.getOffState(), false); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/systems/PortalTrackerSystems.java b/src/com/hypixel/hytale/builtin/portals/systems/PortalTrackerSystems.java new file mode 100644 index 0000000..6eb78ef --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/systems/PortalTrackerSystems.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.builtin.portals.systems; + +import com.hypixel.hytale.builtin.instances.removal.InstanceDataResource; +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.DelayedEntitySystem; +import com.hypixel.hytale.protocol.packets.interface_.UpdatePortal; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class PortalTrackerSystems { + private PortalTrackerSystems() { + } + + public static class TrackerSystem extends RefSystem { + public TrackerSystem() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + PortalWorld portalWorld = store.getResource(PortalWorld.getResourceType()); + if (portalWorld.exists()) { + World world = store.getExternalData().getWorld(); + PlayerRef playerRef = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); + if (portalWorld.getSeesUi().add(playerRef.getUuid())) { + UpdatePortal packet = portalWorld.createFullPacket(world); + playerRef.getPacketHandler().write(packet); + } + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + PortalWorld portalWorld = store.getResource(PortalWorld.getResourceType()); + if (portalWorld.exists()) { + PlayerRef playerRef = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); + playerRef.getPacketHandler().write(new UpdatePortal(null, null)); + portalWorld.getSeesUi().remove(playerRef.getUuid()); + } + } + + @Override + public Query getQuery() { + return Query.and(Player.getComponentType(), PlayerRef.getComponentType()); + } + } + + public static class UiTickingSystem extends DelayedEntitySystem { + public UiTickingSystem() { + super(1.0F); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + PortalWorld portalWorld = store.getResource(PortalWorld.getResourceType()); + if (portalWorld.exists()) { + World world = store.getExternalData().getWorld(); + Store chunkStore = world.getChunkStore().getStore(); + InstanceDataResource instanceData = chunkStore.getResource(InstanceDataResource.getResourceType()); + Instant timeout = instanceData.getTimeoutTimer(); + if (timeout != null) { + PlayerRef playerRef = archetypeChunk.getComponent(index, PlayerRef.getComponentType()); + UpdatePortal packet = portalWorld.getSeesUi().add(playerRef.getUuid()) + ? portalWorld.createFullPacket(world) + : portalWorld.createUpdatePacket(world); + playerRef.getPacketHandler().write(packet); + } + } + } + + @Nullable + @Override + public Query getQuery() { + return Query.and(Player.getComponentType(), PlayerRef.getComponentType()); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/systems/curse/CurseItemDropsSystem.java b/src/com/hypixel/hytale/builtin/portals/systems/curse/CurseItemDropsSystem.java new file mode 100644 index 0000000..a0f4866 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/systems/curse/CurseItemDropsSystem.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.builtin.portals.systems.curse; + +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.server.core.asset.type.item.config.metadata.AdventureMetadata; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CurseItemDropsSystem extends RefSystem { + public CurseItemDropsSystem() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + PortalWorld portalWorld = store.getResource(PortalWorld.getResourceType()); + if (portalWorld.exists()) { + Set cursedItemsInWorld = portalWorld.getPortalType().getCursedItems(); + if (!cursedItemsInWorld.isEmpty()) { + ItemComponent itemComponent = store.getComponent(ref, ItemComponent.getComponentType()); + ItemStack itemStack = itemComponent.getItemStack(); + if (itemStack != null) { + String itemId = itemStack.getItemId().toString(); + if (cursedItemsInWorld.contains(itemId)) { + AdventureMetadata adventureMeta = itemStack.getFromMetadataOrDefault("Adventure", AdventureMetadata.CODEC); + adventureMeta.setCursed(true); + ItemStack cursed = itemStack.withMetadata(AdventureMetadata.KEYED_CODEC, adventureMeta); + itemComponent.setItemStack(cursed); + } + } + } + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Nullable + @Override + public Query getQuery() { + return ItemComponent.getComponentType(); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/systems/curse/DeleteCursedItemsOnSpawnSystem.java b/src/com/hypixel/hytale/builtin/portals/systems/curse/DeleteCursedItemsOnSpawnSystem.java new file mode 100644 index 0000000..bf7b113 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/systems/curse/DeleteCursedItemsOnSpawnSystem.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.builtin.portals.systems.curse; + +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.builtin.portals.utils.CursedItems; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DeleteCursedItemsOnSpawnSystem extends RefSystem { + public DeleteCursedItemsOnSpawnSystem() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + PortalWorld portalWorld = store.getResource(PortalWorld.getResourceType()); + if (!portalWorld.exists()) { + Player player = store.getComponent(ref, Player.getComponentType()); + CursedItems.deleteAll(player); + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Nullable + @Override + public Query getQuery() { + return Player.getComponentType(); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/systems/curse/DiedInPortalSystem.java b/src/com/hypixel/hytale/builtin/portals/systems/curse/DiedInPortalSystem.java new file mode 100644 index 0000000..010c20e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/systems/curse/DiedInPortalSystem.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.builtin.portals.systems.curse; + +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.builtin.portals.utils.CursedItems; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathSystems; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DiedInPortalSystem extends DeathSystems.OnDeathSystem { + public DiedInPortalSystem() { + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + PortalWorld portalWorld = commandBuffer.getResource(PortalWorld.getResourceType()); + if (portalWorld.exists()) { + UUID playerId = commandBuffer.getComponent(ref, UUIDComponent.getComponentType()).getUuid(); + portalWorld.getDiedInWorld().add(playerId); + Player player = store.getComponent(ref, Player.getComponentType()); + CursedItems.deleteAll(player); + } + } + + @Nullable + @Override + public Query getQuery() { + return Query.and(Player.getComponentType(), UUIDComponent.getComponentType()); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/systems/voidevent/StartVoidEventInFragmentSystem.java b/src/com/hypixel/hytale/builtin/portals/systems/voidevent/StartVoidEventInFragmentSystem.java new file mode 100644 index 0000000..72cd249 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/systems/voidevent/StartVoidEventInFragmentSystem.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.builtin.portals.systems.voidevent; + +import com.hypixel.hytale.builtin.portals.components.voidevent.VoidEvent; +import com.hypixel.hytale.builtin.portals.components.voidevent.config.VoidEventConfig; +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.DelayedSystem; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class StartVoidEventInFragmentSystem extends DelayedSystem { + public StartVoidEventInFragmentSystem() { + super(1.0F); + } + + @Override + public void delayedTick(float dt, int systemIndex, @Nonnull Store store) { + PortalWorld portalWorld = store.getResource(PortalWorld.getResourceType()); + if (portalWorld.exists()) { + if (portalWorld.getPortalType().isVoidInvasionEnabled()) { + World world = store.getExternalData().getWorld(); + VoidEventConfig voidEventConfig = portalWorld.getVoidEventConfig(); + int timeLimitSeconds = portalWorld.getTimeLimitSeconds(); + int shouldStartAfter = voidEventConfig.getShouldStartAfterSeconds(timeLimitSeconds); + int elapsedSeconds = (int)Math.ceil(portalWorld.getElapsedSeconds(world)); + Ref voidEventRef = portalWorld.getVoidEventRef(); + boolean exists = voidEventRef != null; + boolean shouldExist = elapsedSeconds >= shouldStartAfter; + if (exists && !shouldExist) { + store.removeEntity(voidEventRef, RemoveReason.REMOVE); + } + + if (shouldExist && !exists) { + Holder holder = EntityStore.REGISTRY.newHolder(); + holder.addComponent(VoidEvent.getComponentType(), new VoidEvent()); + Ref spawnedEventRef = store.addEntity(holder, AddReason.SPAWN); + portalWorld.setVoidEventRef(spawnedEventRef); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/systems/voidevent/VoidEventRefSystem.java b/src/com/hypixel/hytale/builtin/portals/systems/voidevent/VoidEventRefSystem.java new file mode 100644 index 0000000..0822397 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/systems/voidevent/VoidEventRefSystem.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.builtin.portals.systems.voidevent; + +import com.hypixel.hytale.builtin.ambience.resources.AmbienceResource; +import com.hypixel.hytale.builtin.portals.components.voidevent.VoidEvent; +import com.hypixel.hytale.builtin.portals.components.voidevent.config.VoidEventConfig; +import com.hypixel.hytale.builtin.portals.components.voidevent.config.VoidEventStage; +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class VoidEventRefSystem extends RefSystem { + public VoidEventRefSystem() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + PortalWorld portalWorld = store.getResource(PortalWorld.getResourceType()); + if (portalWorld.exists()) { + VoidEventConfig voidEventConfig = portalWorld.getVoidEventConfig(); + String forcedMusic = voidEventConfig.getMusicAmbienceFX(); + if (forcedMusic != null) { + AmbienceResource ambienceResource = store.getResource(AmbienceResource.getResourceType()); + ambienceResource.setForcedMusicAmbience(forcedMusic); + } + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + PortalWorld portalWorld = store.getResource(PortalWorld.getResourceType()); + if (portalWorld.exists()) { + World world = store.getExternalData().getWorld(); + VoidEventConfig voidEventConfig = portalWorld.getVoidEventConfig(); + String forcedMusic = voidEventConfig.getMusicAmbienceFX(); + if (forcedMusic != null) { + AmbienceResource ambienceResource = store.getResource(AmbienceResource.getResourceType()); + ambienceResource.setForcedMusicAmbience(null); + } + + VoidEvent voidEvent = commandBuffer.getComponent(ref, VoidEvent.getComponentType()); + VoidEventStage activeStage = voidEvent.getActiveStage(); + if (activeStage != null) { + VoidEventStagesSystem.stopStage(activeStage, world, store, commandBuffer); + voidEvent.setActiveStage(null); + } + } + } + + @Nullable + @Override + public Query getQuery() { + return VoidEvent.getComponentType(); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/systems/voidevent/VoidEventStagesSystem.java b/src/com/hypixel/hytale/builtin/portals/systems/voidevent/VoidEventStagesSystem.java new file mode 100644 index 0000000..1dcf281 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/systems/voidevent/VoidEventStagesSystem.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.builtin.portals.systems.voidevent; + +import com.hypixel.hytale.builtin.portals.components.voidevent.VoidEvent; +import com.hypixel.hytale.builtin.portals.components.voidevent.config.VoidEventConfig; +import com.hypixel.hytale.builtin.portals.components.voidevent.config.VoidEventStage; +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.builtin.weather.resources.WeatherResource; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.DelayedEntitySystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.List; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class VoidEventStagesSystem extends DelayedEntitySystem { + public VoidEventStagesSystem() { + super(1.5F); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + PortalWorld portalWorld = store.getResource(PortalWorld.getResourceType()); + if (portalWorld.exists()) { + Ref eventRef = portalWorld.getVoidEventRef(); + VoidEvent voidEvent = eventRef == null ? null : commandBuffer.getComponent(eventRef, VoidEvent.getComponentType()); + if (voidEvent != null) { + World world = store.getExternalData().getWorld(); + VoidEventConfig voidEventConfig = portalWorld.getVoidEventConfig(); + double elapsedSecondsInPortal = portalWorld.getElapsedSeconds(world); + int timeLimitSeconds = portalWorld.getTimeLimitSeconds(); + int shouldStartAfter = voidEventConfig.getShouldStartAfterSeconds(timeLimitSeconds); + int elapsedSecondsInEvent = (int)Math.max(0.0, elapsedSecondsInPortal - shouldStartAfter); + VoidEventStage currentStage = voidEvent.getActiveStage(); + VoidEventStage desiredStage = this.computeAppropriateStage(voidEventConfig, elapsedSecondsInEvent); + if (currentStage != desiredStage) { + if (currentStage != null) { + stopStage(currentStage, world, store, commandBuffer); + } + + if (desiredStage != null) { + startStage(desiredStage, world, store, commandBuffer); + } + + voidEvent.setActiveStage(desiredStage); + } + } + } + } + + @Nullable + private VoidEventStage computeAppropriateStage(VoidEventConfig config, int elapsedSeconds) { + List stages = config.getStagesSortedByStartTime(); + + for (int i = stages.size() - 1; i >= 0; i--) { + VoidEventStage stage = stages.get(i); + if (elapsedSeconds > stage.getSecondsInto()) { + return stage; + } + } + + return null; + } + + public static void startStage(VoidEventStage stage, World world, Store store, CommandBuffer commandBuffer) { + HytaleLogger.getLogger().at(Level.INFO).log("Starting stage SecondsInto=" + stage.getSecondsInto() + " in portal void event"); + String forcedWeatherId = stage.getForcedWeatherId(); + if (forcedWeatherId != null) { + WeatherResource weatherResource = store.getResource(WeatherResource.getResourceType()); + weatherResource.setForcedWeather(forcedWeatherId); + } + } + + public static void stopStage(VoidEventStage stage, World world, Store store, CommandBuffer commandBuffer) { + HytaleLogger.getLogger().at(Level.INFO).log("Stopping stage SecondsInto=" + stage.getSecondsInto() + " in portal void event"); + String forcedWeatherId = stage.getForcedWeatherId(); + if (forcedWeatherId != null) { + WeatherResource weatherResource = store.getResource(WeatherResource.getResourceType()); + weatherResource.setForcedWeather(null); + } + } + + @Nullable + @Override + public Query getQuery() { + return VoidEvent.getComponentType(); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/systems/voidevent/VoidInvasionPortalsSpawnSystem.java b/src/com/hypixel/hytale/builtin/portals/systems/voidevent/VoidInvasionPortalsSpawnSystem.java new file mode 100644 index 0000000..693e2b8 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/systems/voidevent/VoidInvasionPortalsSpawnSystem.java @@ -0,0 +1,156 @@ +package com.hypixel.hytale.builtin.portals.systems.voidevent; + +import com.hypixel.hytale.builtin.portals.components.voidevent.VoidEvent; +import com.hypixel.hytale.builtin.portals.components.voidevent.VoidSpawner; +import com.hypixel.hytale.builtin.portals.components.voidevent.config.InvasionPortalConfig; +import com.hypixel.hytale.builtin.portals.components.voidevent.config.VoidEventConfig; +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.builtin.portals.utils.posqueries.generators.SearchBelow; +import com.hypixel.hytale.builtin.portals.utils.posqueries.generators.SearchCone; +import com.hypixel.hytale.builtin.portals.utils.posqueries.predicates.FitsAPortal; +import com.hypixel.hytale.builtin.portals.utils.posqueries.predicates.NotNearAnyInHashGrid; +import com.hypixel.hytale.builtin.portals.utils.posqueries.predicates.NotNearPointXZ; +import com.hypixel.hytale.builtin.portals.utils.spatial.SpatialHashGrid; +import com.hypixel.hytale.common.util.RandomUtil; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.DelayedEntitySystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class VoidInvasionPortalsSpawnSystem extends DelayedEntitySystem { + private static final int MAX_PORTALS = 24; + private CompletableFuture findPortalSpawnPos; + + public VoidInvasionPortalsSpawnSystem() { + super(2.0F); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + VoidEvent voidEvent = archetypeChunk.getComponent(index, VoidEvent.getComponentType()); + World world = store.getExternalData().getWorld(); + if (this.findPortalSpawnPos == null) { + SpatialHashGrid> spawners = this.cleanupAndGetSpawners(voidEvent); + if (spawners.size() < 24) { + this.findPortalSpawnPos = this.findPortalSpawnPosition(world, voidEvent, commandBuffer); + } + } else if (this.findPortalSpawnPos.isDone()) { + Vector3d portalPos; + try { + portalPos = this.findPortalSpawnPos.join(); + this.findPortalSpawnPos = null; + } catch (Throwable var14) { + HytaleLogger.getLogger().at(Level.SEVERE).withCause(var14).log("Error trying to find a void event spawn position"); + return; + } + + if (portalPos != null) { + Holder voidSpawnerHolder = EntityStore.REGISTRY.newHolder(); + voidSpawnerHolder.addComponent(VoidSpawner.getComponentType(), new VoidSpawner()); + voidSpawnerHolder.addComponent(TransformComponent.getComponentType(), new TransformComponent(portalPos, new Vector3f())); + Ref voidSpawner = commandBuffer.addEntity(voidSpawnerHolder, AddReason.SPAWN); + voidEvent.getVoidSpawners().add(portalPos, voidSpawner); + VoidEventConfig eventConfig = voidEvent.getConfig(world); + if (eventConfig == null) { + HytaleLogger.getLogger().at(Level.WARNING).log("There's a Void Event entity but no void event config in the gameplay config"); + } else { + InvasionPortalConfig invasionPortalConfig = eventConfig.getInvasionPortalConfig(); + Vector3i portalBlockPos = portalPos.toVector3i(); + world.getChunkAsync(ChunkUtil.indexChunkFromBlock(portalBlockPos.x, portalBlockPos.z)).thenAcceptAsync(chunk -> { + BlockType blockType = invasionPortalConfig.getBlockType(); + chunk.setBlock(portalBlockPos.x, portalBlockPos.y, portalBlockPos.z, blockType, 4); + }, world); + } + } + } + } + + private CompletableFuture findPortalSpawnPosition(World world, VoidEvent voidEvent, CommandBuffer commandBuffer) { + PortalWorld portalWorld = commandBuffer.getResource(PortalWorld.getResourceType()); + if (!portalWorld.exists()) { + return null; + } else { + Vector3d spawnPos = portalWorld.getSpawnPoint().getPosition(); + Transform playerTransform = this.findRandomPlayerTransform(world, commandBuffer); + if (playerTransform == null) { + return null; + } else { + Vector3d origin = playerTransform.getPosition().clone().add(0.0, 5.0, 0.0); + Vector3d direction = playerTransform.getDirection(); + SpatialHashGrid> existingSpawners = voidEvent.getVoidSpawners(); + NotNearAnyInHashGrid noNearbySpawners = new NotNearAnyInHashGrid(existingSpawners, 62.0); + return CompletableFuture.supplyAsync( + () -> new SearchCone(direction, 48.0, 64.0, 90.0, 8) + .filter(noNearbySpawners) + .filter(new NotNearPointXZ(spawnPos, 18.0)) + .then(new SearchBelow(12)) + .filter(new FitsAPortal()) + .execute(world, origin) + .orElse(null), + world + ); + } + } + } + + @Nullable + private Transform findRandomPlayerTransform(World world, CommandBuffer commandBuffer) { + Collection playerRefs = world.getPlayerRefs(); + if (playerRefs.isEmpty()) { + return null; + } else { + List> players = new ObjectArrayList<>(playerRefs.size()); + + for (PlayerRef playerRef : playerRefs) { + players.add(playerRef.getReference()); + } + + Ref randomPlayer = RandomUtil.selectRandom(players); + TransformComponent transformComponent = commandBuffer.getComponent(randomPlayer, TransformComponent.getComponentType()); + + assert transformComponent != null; + + return transformComponent.getTransform(); + } + } + + private SpatialHashGrid> cleanupAndGetSpawners(VoidEvent voidEvent) { + SpatialHashGrid> spawners = voidEvent.getVoidSpawners(); + spawners.removeIf(ref -> !ref.isValid()); + return spawners; + } + + @Nullable + @Override + public Query getQuery() { + return VoidEvent.getComponentType(); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/systems/voidevent/VoidSpawnerSystems.java b/src/com/hypixel/hytale/builtin/portals/systems/voidevent/VoidSpawnerSystems.java new file mode 100644 index 0000000..e76ffaf --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/systems/voidevent/VoidSpawnerSystems.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.builtin.portals.systems.voidevent; + +import com.hypixel.hytale.builtin.portals.components.voidevent.VoidSpawner; +import com.hypixel.hytale.builtin.portals.components.voidevent.config.InvasionPortalConfig; +import com.hypixel.hytale.builtin.portals.components.voidevent.config.VoidEventConfig; +import com.hypixel.hytale.builtin.portals.integrations.PortalGameplayConfig; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.ParticleUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.spawning.SpawningPlugin; +import com.hypixel.hytale.server.spawning.assets.spawns.config.BeaconNPCSpawn; +import com.hypixel.hytale.server.spawning.beacons.LegacySpawnBeaconEntity; +import com.hypixel.hytale.server.spawning.wrappers.BeaconSpawnWrapper; +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class VoidSpawnerSystems { + private static final Query QUERY = Query.and(VoidSpawner.getComponentType(), TransformComponent.getComponentType()); + + public VoidSpawnerSystems() { + } + + public static class Instantiate extends RefSystem { + public Instantiate() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + HytaleLogger.getLogger().at(Level.INFO).log("Adding void spawner..."); + World world = store.getExternalData().getWorld(); + PortalGameplayConfig gameplayConfig = world.getGameplayConfig().getPluginConfig().get(PortalGameplayConfig.class); + VoidEventConfig voidEventConfig = gameplayConfig.getVoidEvent(); + InvasionPortalConfig invasionPortalConfig = voidEventConfig.getInvasionPortalConfig(); + List spawnBeacons = invasionPortalConfig.getSpawnBeaconsList(); + if (spawnBeacons.isEmpty()) { + HytaleLogger.getLogger() + .at(Level.WARNING) + .log("No spawn beacons configured for void spawn in GameplayConfig for portal world (no mobs will spawn during void event)"); + } else { + VoidSpawner voidSpawner = commandBuffer.getComponent(ref, VoidSpawner.getComponentType()); + TransformComponent transform = commandBuffer.getComponent(ref, TransformComponent.getComponentType()); + Vector3d position = transform.getPosition(); + + for (int i = 0; i < spawnBeacons.size(); i++) { + String spawnBeacon = spawnBeacons.get(i); + Vector3d beaconPos = position.clone().add(0.0, 0.5 + 0.1 * i, 0.0); + int beaconAssetId = BeaconNPCSpawn.getAssetMap().getIndexOrDefault(spawnBeacon, -1); + if (beaconAssetId == -1) { + HytaleLogger.getLogger().at(Level.WARNING).log("No asset found for spawn beacon \"" + spawnBeacon + "\" in GameplayConfig for portal world"); + } else { + BeaconSpawnWrapper beaconSpawnWrapper = SpawningPlugin.get().getBeaconSpawnWrapper(beaconAssetId); + Holder spawnBeaconRef = LegacySpawnBeaconEntity.createHolder(beaconSpawnWrapper, beaconPos, transform.getRotation()); + commandBuffer.addEntity(spawnBeaconRef, AddReason.SPAWN); + UUID beaconUuid = spawnBeaconRef.getComponent(UUIDComponent.getComponentType()).getUuid(); + voidSpawner.getSpawnBeaconUuids().add(beaconUuid); + } + } + + String onSpawnParticles = invasionPortalConfig.getOnSpawnParticles(); + if (onSpawnParticles != null) { + ParticleUtil.spawnParticleEffect(onSpawnParticles, position, commandBuffer); + } + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + World world = store.getExternalData().getWorld(); + VoidSpawner voidSpawnerComponent = commandBuffer.getComponent(ref, VoidSpawner.getComponentType()); + + assert voidSpawnerComponent != null; + + for (UUID spawnBeaconUuid : voidSpawnerComponent.getSpawnBeaconUuids()) { + Ref spawnBeaconRef = world.getEntityStore().getRefFromUUID(spawnBeaconUuid); + if (spawnBeaconRef != null) { + commandBuffer.removeEntity(spawnBeaconRef, RemoveReason.REMOVE); + } + } + } + + @Nullable + @Override + public Query getQuery() { + return VoidSpawnerSystems.QUERY; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/ui/PortalDeviceActivePage.java b/src/com/hypixel/hytale/builtin/portals/ui/PortalDeviceActivePage.java new file mode 100644 index 0000000..1d20bda --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/ui/PortalDeviceActivePage.java @@ -0,0 +1,157 @@ +package com.hypixel.hytale.builtin.portals.ui; + +import com.hypixel.hytale.builtin.portals.components.PortalDevice; +import com.hypixel.hytale.builtin.portals.components.PortalDeviceConfig; +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.portalworld.PortalType; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class PortalDeviceActivePage extends InteractiveCustomUIPage { + @Nonnull + private final PortalDeviceConfig config; + @Nonnull + private final Ref blockRef; + + public PortalDeviceActivePage(@Nonnull PlayerRef playerRef, @Nonnull PortalDeviceConfig config, @Nonnull Ref blockRef) { + super(playerRef, CustomPageLifetime.CanDismissOrCloseThroughInteraction, PortalDeviceActivePage.Data.CODEC); + this.config = config; + this.blockRef = blockRef; + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + PortalDeviceActivePage.State state = this.computeState(ref, store); + if (state != PortalDeviceActivePage.Error.INVALID_BLOCK) { + commandBuilder.append("Pages/PortalDeviceActive.ui"); + if (state instanceof PortalDeviceActivePage.PortalIsOpen(World playerCountMsg, PortalWorld var19, boolean var26)) { + boolean var20 = var26; + if (true) { + PortalType var18 = var19.getPortalType(); + commandBuilder.set("#PortalPanel.Visible", true); + if (var20) { + commandBuilder.set("#Died.Visible", true); + } + + commandBuilder.set( + "#PortalTitle.TextSpans", Message.translation("server.customUI.portalDevice.portalTitle").param("name", var18.getDisplayName()) + ); + commandBuilder.set("#PortalDescription.TextSpans", PortalDeviceSummonPage.createDescription(var18, var19.getTimeLimitSeconds())); + Message playerCountMsgx = createPlayerCountMsg(playerCountMsg); + commandBuilder.set("#PlayersInside.TextSpans", playerCountMsgx); + double remainingSeconds = var19.getRemainingSeconds(playerCountMsg); + if (remainingSeconds < var19.getTimeLimitSeconds()) { + int remainingMinutes = (int)Math.round(remainingSeconds / 60.0); + Message remainingTimeMsg = remainingMinutes <= 1 + ? Message.translation("server.customUI.portalDevice.lessThanAMinute") + : Message.translation("server.customUI.portalDevice.remainingMinutes").param("time", remainingMinutes); + commandBuilder.set( + "#RemainingDuration.TextSpans", + Message.translation("server.customUI.portalDevice.remainingDuration").param("remaining", remainingTimeMsg.color("#ea4fa46b")) + ); + } else { + commandBuilder.set("#PortalIsOpen.Visible", true); + commandBuilder.set("#RemainingDuration.TextSpans", Message.translation("server.customUI.portalDevice.beTheFirst").color("#ea4fa46b")); + } + + return; + } + } + + commandBuilder.set("#Error.Visible", true); + commandBuilder.set("#ErrorLabel.Text", Message.translation("server.customUI.portalDevice.unknownError").param("state", state.toString())); + } + } + + private static Message createPlayerCountMsg(World world) { + int playerCount = world.getPlayerCount(); + String pinkEnoughColor = "#ea4fa46b"; + if (playerCount == 0) { + return Message.translation("server.customUI.portalDevice.playersInside") + .param("count", Message.translation("server.customUI.portalDevice.playersInsideNone").color(pinkEnoughColor)); + } else if (playerCount > 4) { + return Message.translation("server.customUI.portalDevice.playersInside").param("count", Message.raw(playerCount + "!").color(pinkEnoughColor)); + } else { + Message msg = Message.translation("server.customUI.portalDevice.playersInside").param("count", Message.raw(playerCount + "!").color(pinkEnoughColor)); + + for (PlayerRef ref : world.getPlayerRefs()) { + msg.insert(Message.raw("- ").color("#6b6b6b6b")).insert(ref.getUsername()); + } + + return msg; + } + } + + private PortalDeviceActivePage.State computeState(Ref ref, @Nonnull ComponentAccessor componentAccessor) { + if (!this.blockRef.isValid()) { + return PortalDeviceActivePage.Error.INVALID_BLOCK; + } else { + Store chunkStore = this.blockRef.getStore(); + PortalDevice portalDevice = chunkStore.getComponent(this.blockRef, PortalDevice.getComponentType()); + if (portalDevice == null) { + return PortalDeviceActivePage.Error.INVALID_BLOCK; + } else { + World destinationWorld = portalDevice.getDestinationWorld(); + if (destinationWorld == null) { + return PortalDeviceActivePage.Error.INVALID_WORLD; + } else { + Store destinationStore = destinationWorld.getEntityStore().getStore(); + PortalWorld portalWorld = destinationStore.getResource(PortalWorld.getResourceType()); + if (!portalWorld.exists()) { + return PortalDeviceActivePage.Error.DESTINATION_NOT_FRAGMENT; + } else { + UUIDComponent uuidComponent = componentAccessor.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID playerUUID = uuidComponent.getUuid(); + boolean diedInside = portalWorld.getDiedInWorld().contains(playerUUID); + return new PortalDeviceActivePage.PortalIsOpen(destinationWorld, portalWorld, diedInside); + } + } + } + } + } + + protected static class Data { + public static final BuilderCodec CODEC = BuilderCodec.builder( + PortalDeviceActivePage.Data.class, PortalDeviceActivePage.Data::new + ) + .build(); + + protected Data() { + } + } + + private static enum Error implements PortalDeviceActivePage.State { + INVALID_BLOCK, + INVALID_WORLD, + DESTINATION_NOT_FRAGMENT, + INACTIVE_PORTAL; + + private Error() { + } + } + + private record PortalIsOpen(World world, PortalWorld portalWorld, boolean diedInside) implements PortalDeviceActivePage.State { + } + + private sealed interface State permits PortalDeviceActivePage.PortalIsOpen, PortalDeviceActivePage.Error { + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/ui/PortalDevicePageSupplier.java b/src/com/hypixel/hytale/builtin/portals/ui/PortalDevicePageSupplier.java new file mode 100644 index 0000000..a9d4d70 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/ui/PortalDevicePageSupplier.java @@ -0,0 +1,98 @@ +package com.hypixel.hytale.builtin.portals.ui; + +import com.hypixel.hytale.builtin.portals.components.PortalDevice; +import com.hypixel.hytale.builtin.portals.components.PortalDeviceConfig; +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.builtin.portals.utils.BlockTypeUtils; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.CustomUIPage; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenCustomUIInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nullable; + +public class PortalDevicePageSupplier implements OpenCustomUIInteraction.CustomPageSupplier { + public static final BuilderCodec CODEC = BuilderCodec.builder(PortalDevicePageSupplier.class, PortalDevicePageSupplier::new) + .appendInherited( + new KeyedCodec<>("Config", PortalDeviceConfig.CODEC), + (supplier, o) -> supplier.config = o, + supplier -> supplier.config, + (supplier, parent) -> supplier.config = parent.config + ) + .documentation("The portal device's config.") + .add() + .build(); + private PortalDeviceConfig config; + + public PortalDevicePageSupplier() { + } + + @Override + public CustomUIPage tryCreate(Ref ref, ComponentAccessor store, PlayerRef playerRef, InteractionContext context) { + BlockPosition targetBlock = context.getTargetBlock(); + if (targetBlock == null) { + return null; + } else { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + ItemStack inHand = playerComponent.getInventory().getItemInHand(); + World world = store.getExternalData().getWorld(); + BlockType blockType = world.getBlockType(targetBlock.x, targetBlock.y, targetBlock.z); + + for (String blockStateKey : this.config.getBlockStates()) { + BlockType blockState = BlockTypeUtils.getBlockForState(blockType, blockStateKey); + if (blockState == null) { + playerRef.sendMessage(Message.translation("server.portals.device.blockStateMisconfigured").param("state", blockStateKey)); + return null; + } + } + + BlockType onBlock = BlockTypeUtils.getBlockForState(blockType, this.config.getOnState()); + ChunkStore chunkStore = world.getChunkStore(); + Ref blockRef = BlockModule.getBlockEntity(world, targetBlock.x, targetBlock.y, targetBlock.z); + if (blockRef == null) { + playerRef.sendMessage(Message.translation("server.portals.device.blockEntityMisconfigured")); + return null; + } else { + PortalDevice existingDevice = chunkStore.getStore().getComponent(blockRef, PortalDevice.getComponentType()); + World destinationWorld = existingDevice == null ? null : existingDevice.getDestinationWorld(); + if (existingDevice != null && blockType == onBlock && !isPortalWorldValid(destinationWorld)) { + world.setBlockInteractionState(new Vector3i(targetBlock.x, targetBlock.y, targetBlock.z), blockType, this.config.getOffState()); + playerRef.sendMessage(Message.translation("server.portals.device.adjusted").color("#ff0000")); + return null; + } else if (existingDevice != null && destinationWorld != null) { + return new PortalDeviceActivePage(playerRef, this.config, blockRef); + } else { + chunkStore.getStore().putComponent(blockRef, PortalDevice.getComponentType(), new PortalDevice(this.config, blockType.getId())); + return new PortalDeviceSummonPage(playerRef, this.config, blockRef, inHand); + } + } + } + } + + private static boolean isPortalWorldValid(@Nullable World world) { + if (world == null) { + return false; + } else { + Store store = world.getEntityStore().getStore(); + PortalWorld portalWorld = store.getResource(PortalWorld.getResourceType()); + return portalWorld.exists(); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/ui/PortalDeviceSummonPage.java b/src/com/hypixel/hytale/builtin/portals/ui/PortalDeviceSummonPage.java new file mode 100644 index 0000000..f3f9bfa --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/ui/PortalDeviceSummonPage.java @@ -0,0 +1,460 @@ +package com.hypixel.hytale.builtin.portals.ui; + +import com.hypixel.hytale.builtin.instances.InstancesPlugin; +import com.hypixel.hytale.builtin.instances.config.InstanceDiscoveryConfig; +import com.hypixel.hytale.builtin.instances.config.InstanceWorldConfig; +import com.hypixel.hytale.builtin.portals.PortalsPlugin; +import com.hypixel.hytale.builtin.portals.components.PortalDevice; +import com.hypixel.hytale.builtin.portals.components.PortalDeviceConfig; +import com.hypixel.hytale.builtin.portals.integrations.PortalGameplayConfig; +import com.hypixel.hytale.builtin.portals.integrations.PortalRemovalCondition; +import com.hypixel.hytale.builtin.portals.resources.PortalWorld; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.PortalKey; +import com.hypixel.hytale.server.core.asset.type.portalworld.PillTag; +import com.hypixel.hytale.server.core.asset.type.portalworld.PortalDescription; +import com.hypixel.hytale.server.core.asset.type.portalworld.PortalSpawn; +import com.hypixel.hytale.server.core.asset.type.portalworld.PortalType; +import com.hypixel.hytale.server.core.asset.util.ColorParseUtil; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider; +import com.hypixel.hytale.server.core.universe.world.spawn.IndividualSpawnProvider; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PortalDeviceSummonPage extends InteractiveCustomUIPage { + private final PortalDeviceConfig config; + private final Ref blockRef; + private final ItemStack offeredItemStack; + private static final Transform DEFAULT_WORLDGEN_SPAWN = new Transform(0.0, 140.0, 0.0); + + public PortalDeviceSummonPage(@Nonnull PlayerRef playerRef, PortalDeviceConfig config, Ref blockRef, ItemStack offeredItemStack) { + super(playerRef, CustomPageLifetime.CanDismissOrCloseThroughInteraction, PortalDeviceSummonPage.Data.CODEC); + this.config = config; + this.blockRef = blockRef; + this.offeredItemStack = offeredItemStack; + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PortalDeviceSummonPage.State state = this.computeState(playerComponent, store); + if (state != PortalDeviceSummonPage.Error.INVALID_BLOCK) { + if (state instanceof PortalDeviceSummonPage.CanSpawnPortal canSpawn) { + commandBuilder.append("Pages/PortalDeviceSummon.ui"); + PortalKey var22 = canSpawn.portalKey(); + PortalType portalType = canSpawn.portalType(); + PortalDescription var23 = portalType.getDescription(); + commandBuilder.set("#Artwork.Background", "Pages/Portals/" + var23.getSplashImageFilename()); + commandBuilder.set("#Title0.TextSpans", var23.getDisplayName()); + commandBuilder.set("#FlavorLabel.TextSpans", var23.getFlavorText()); + updateCustomPills(commandBuilder, portalType); + String[] objectivesKeys = var23.getObjectivesKeys(); + String[] var25 = var23.getWisdomKeys(); + commandBuilder.set("#Objectives.Visible", objectivesKeys.length > 0); + commandBuilder.set("#Tips.Visible", var25.length > 0); + updateBulletList(commandBuilder, "#ObjectivesList", objectivesKeys); + updateBulletList(commandBuilder, "#TipsList", var25); + PortalGameplayConfig gameplayConfig = portalType.getGameplayConfig().getPluginConfig().get(PortalGameplayConfig.class); + long totalTimeLimit = TimeUnit.SECONDS.toMinutes(var22.getTimeLimitSeconds()); + if (portalType.isVoidInvasionEnabled()) { + long minutesBreach = TimeUnit.SECONDS.toMinutes(gameplayConfig.getVoidEvent().getDurationSeconds()); + long exploMinutes = totalTimeLimit - minutesBreach; + commandBuilder.set( + "#ExplorationTimeText.TextSpans", Message.translation("server.customUI.portalDevice.minutesToExplore").param("time", exploMinutes) + ); + commandBuilder.set("#BreachTimeBullet.Visible", true); + commandBuilder.set( + "#BreachTimeText.TextSpans", Message.translation("server.customUI.portalDevice.minutesVoidInvasion").param("time", minutesBreach) + ); + } else { + commandBuilder.set( + "#ExplorationTimeText.TextSpans", Message.translation("server.customUI.portalDevice.durationMins").param("time", totalTimeLimit) + ); + } + + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#SummonButton", EventData.of("Action", "SummonActivated"), false); + eventBuilder.addEventBinding(CustomUIEventBindingType.MouseEntered, "#SummonButton", EventData.of("Action", "SummonMouseEntered"), false); + eventBuilder.addEventBinding(CustomUIEventBindingType.MouseExited, "#SummonButton", EventData.of("Action", "SummonMouseExited"), false); + } else { + commandBuilder.append("Pages/PortalDeviceError.ui"); + if (state == PortalDeviceSummonPage.Error.NOTHING_OFFERED || state == PortalDeviceSummonPage.Error.NOT_A_PORTAL_KEY) { + commandBuilder.set("#UsageErrorTitle.Text", Message.translation("server.customUI.portalDevice.needPortalKey")); + commandBuilder.set("#UsageErrorLabel.Text", Message.translation("server.customUI.portalDevice.nothingHeld")); + } else if (state == PortalDeviceSummonPage.Error.PORTAL_INSIDE_PORTAL) { + commandBuilder.set("#UsageErrorLabel.Text", Message.translation("server.customUI.portalDevice.portalInsidePortal")); + } else if (state == PortalDeviceSummonPage.Error.MAX_ACTIVE_PORTALS) { + commandBuilder.set("#UsageErrorLabel.Text", Message.translation("server.customUI.portalDevice.maxFragments").param("max", 4)); + } else if (state instanceof PortalDeviceSummonPage.InstanceKeyNotFound(String wisdomKeys)) { + commandBuilder.set( + "#UsageErrorLabel.Text", "The instance id '" + wisdomKeys + "' does not exist, this is a developer error with the portaltype." + ); + } else if (state instanceof PortalDeviceSummonPage.PortalTypeNotFound(String var24)) { + commandBuilder.set("#UsageErrorLabel.Text", "The portaltype id '" + var24 + "' does not exist, this is a developer error with the portal key."); + } else if (state == PortalDeviceSummonPage.Error.BOTCHED_GAMEPLAY_CONFIG) { + commandBuilder.set( + "#UsageErrorLabel.Text", + "The gameplay config set on the PortalType set in the key does not have a Portal plugin configuration, this is a developer error." + ); + } else { + commandBuilder.set("#UsageErrorLabel.Text", Message.translation("server.customUI.portalDevice.unknownError").param("state", state.toString())); + } + } + } + } + + private static void updateCustomPills(UICommandBuilder commandBuilder, PortalType portalType) { + List pills = portalType.getDescription().getPillTags(); + + for (int i = 0; i < pills.size(); i++) { + PillTag pillTag = pills.get(i); + String child = "#Pills[" + i + "]"; + commandBuilder.append("#Pills", "Pages/Portals/Pill.ui"); + commandBuilder.set(child + ".Background.Color", ColorParseUtil.colorToHexString(pillTag.getColor())); + commandBuilder.set(child + " #Label.TextSpans", pillTag.getMessage()); + } + } + + private static void updateBulletList(UICommandBuilder commandBuilder, String selector, String[] messageKeys) { + for (int i = 0; i < messageKeys.length; i++) { + String messageKey = messageKeys[i]; + String child = selector + "[" + i + "]"; + commandBuilder.append(selector, "Pages/Portals/BulletPoint.ui"); + commandBuilder.set(child + " #Label.TextSpans", Message.translation(messageKey)); + } + } + + public static Message createDescription(PortalType portalType, int timeLimitSeconds) { + Message msg = Message.empty(); + Message durationMsg = formatDurationCrudely(timeLimitSeconds); + msg.insert(Message.translation("server.customUI.portalDevice.timeLimit").param("limit", durationMsg.color("#f9cb13"))); + return msg; + } + + private static Message formatDurationCrudely(int seconds) { + if (seconds < 0) { + return Message.translation("server.customUI.portalDevice.durationUnlimited"); + } else if (seconds >= 120) { + int minutes = seconds / 60; + return Message.translation("server.customUI.portalDevice.durationMinutes").param("duration", minutes); + } else { + return Message.translation("server.customUI.portalDevice.durationSeconds").param("duration", seconds); + } + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull PortalDeviceSummonPage.Data data) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (this.computeState(playerComponent, store) instanceof PortalDeviceSummonPage.CanSpawnPortal canSpawn) { + if ("SummonMouseEntered".equals(data.action)) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#Vignette.Visible", true); + this.sendUpdate(commandBuilder, null, false); + } else if ("SummonMouseExited".equals(data.action)) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#Vignette.Visible", false); + this.sendUpdate(commandBuilder, null, false); + } else { + playerComponent.getPageManager().setPage(ref, store, Page.None); + World originWorld = store.getExternalData().getWorld(); + int index = canSpawn.blockState().getIndex(); + int x = ChunkUtil.xFromBlockInColumn(index); + int y = ChunkUtil.yFromBlockInColumn(index); + int z = ChunkUtil.zFromBlockInColumn(index); + WorldChunk worldChunk = canSpawn.worldChunk(); + PortalKey portalKey = canSpawn.portalKey(); + PortalDevice portalDevice = canSpawn.portalDevice(); + BlockType blockType = worldChunk.getBlockType(x, y, z); + if (blockType == portalDevice.getBaseBlockType()) { + if (this.config.areBlockStatesValid(blockType)) { + int rotation = worldChunk.getRotationIndex(x, y, z); + BlockType spawningType = blockType.getBlockForState(this.config.getSpawningState()); + BlockType onType = blockType.getBlockForState(this.config.getOnState()); + BlockType offType = blockType.getBlockForState(this.config.getOffState()); + int setting = 6; + worldChunk.setBlock(x, y, z, BlockType.getAssetMap().getIndex(spawningType.getId()), spawningType, rotation, 0, 6); + double worldX = ChunkUtil.worldCoordFromLocalCoord(worldChunk.getX(), x) + 0.5; + double worldY = y + 0.5; + double worldZ = ChunkUtil.worldCoordFromLocalCoord(worldChunk.getZ(), z) + 0.5; + if (spawningType.getInteractionSoundEventIndex() != 0) { + SoundUtil.playSoundEvent3d(spawningType.getInteractionSoundEventIndex(), SoundCategory.SFX, worldX, worldY, worldZ, store); + } + + decrementItemInHand(playerComponent.getInventory(), 1); + Transform transform = new Transform(x + 0.5, y + 1.0, z + 0.5); + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + PortalType portalType = canSpawn.portalType; + UUID playerUUID = uuidComponent.getUuid(); + PortalGameplayConfig gameplayConfig = canSpawn.portalGameplayConfig; + InstancesPlugin.get().spawnInstance(portalType.getInstanceId(), originWorld, transform).thenCompose(spawnedWorld -> { + WorldConfig worldConfig = spawnedWorld.getWorldConfig(); + worldConfig.setDeleteOnUniverseStart(true); + worldConfig.setDeleteOnRemove(true); + worldConfig.setGameplayConfig(portalType.getGameplayConfigId()); + InstanceWorldConfig instanceConfig = InstanceWorldConfig.ensureAndGet(worldConfig); + if (instanceConfig.getDiscovery() == null) { + InstanceDiscoveryConfig discoveryConfig = new InstanceDiscoveryConfig(); + discoveryConfig.setTitleKey(portalType.getDescription().getDisplayNameKey()); + discoveryConfig.setSubtitleKey("server.portals.discoverySubtitle"); + discoveryConfig.setDisplay(true); + discoveryConfig.setAlwaysDisplay(true); + instanceConfig.setDiscovery(discoveryConfig); + } + + PortalRemovalCondition portalRemoval = new PortalRemovalCondition(portalKey.getTimeLimitSeconds()); + instanceConfig.setRemovalConditions(portalRemoval); + PortalWorld portalWorld = spawnedWorld.getEntityStore().getStore().getResource(PortalWorld.getResourceType()); + portalWorld.init(portalType, portalKey.getTimeLimitSeconds(), portalRemoval, gameplayConfig); + String returnBlockType = portalDevice.getConfig().getReturnBlock(); + if (returnBlockType == null) { + throw new RuntimeException("Return block type on PortalDevice is misconfigured"); + } else { + return spawnReturnPortal(spawnedWorld, portalWorld, playerUUID, returnBlockType); + } + }).thenAcceptAsync(spawnedWorld -> { + portalDevice.setDestinationWorld(spawnedWorld); + worldChunk.setBlock(x, y, z, BlockType.getAssetMap().getIndex(onType.getId()), onType, rotation, 0, 6); + }, originWorld).exceptionallyAsync(t -> { + playerComponent.sendMessage(Message.translation("server.portals.device.internalErrorSpawning")); + HytaleLogger.getLogger().at(Level.SEVERE).withCause(t).log("Error creating instance for Portal Device " + portalKey, t); + worldChunk.setBlock(x, y, z, BlockType.getAssetMap().getIndex(offType.getId()), offType, rotation, 0, 6); + return null; + }, originWorld); + } + } + } + } + } + + private static CompletableFuture spawnReturnPortal(World world, PortalWorld portalWorld, UUID sampleUuid, String portalBlockType) { + PortalSpawn portalSpawn = portalWorld.getPortalType().getPortalSpawn(); + return getSpawnTransform(world, sampleUuid, portalSpawn) + .thenCompose( + spawnTransform -> { + Vector3d spawnPoint = spawnTransform.getPosition(); + return world.getChunkAsync(ChunkUtil.indexChunkFromBlock((int)spawnPoint.x, (int)spawnPoint.z)) + .thenAccept( + chunk -> { + for (int dy = 0; dy < 3; dy++) { + for (int dx = -1; dx <= 1; dx++) { + for (int dz = -1; dz <= 1; dz++) { + chunk.setBlock((int)spawnPoint.x + dx, (int)spawnPoint.y + dy, (int)spawnPoint.z + dz, BlockType.EMPTY); + } + } + } + + chunk.setBlock((int)spawnPoint.x, (int)spawnPoint.y, (int)spawnPoint.z, portalBlockType); + portalWorld.setSpawnPoint(spawnTransform); + world.getWorldConfig().setSpawnProvider(new IndividualSpawnProvider(spawnTransform)); + HytaleLogger.getLogger() + .at(Level.INFO) + .log( + "Spawned return portal for " + world.getName() + " at " + (int)spawnPoint.x + ", " + (int)spawnPoint.y + ", " + (int)spawnPoint.z + ); + } + ) + .thenApply(nothing -> world); + } + ); + } + + private static CompletableFuture getSpawnTransform(World world, UUID sampleUuid, @Nullable PortalSpawn portalSpawn) { + ISpawnProvider spawnProvider = world.getWorldConfig().getSpawnProvider(); + if (spawnProvider == null) { + return CompletableFuture.completedFuture(null); + } else { + Transform worldSpawnPoint = spawnProvider.getSpawnPoint(world, sampleUuid); + return DEFAULT_WORLDGEN_SPAWN.equals(worldSpawnPoint) && portalSpawn != null ? CompletableFuture.supplyAsync(() -> { + Transform computedSpawn = PortalSpawnFinder.computeSpawnTransform(world, portalSpawn); + return computedSpawn == null ? worldSpawnPoint : computedSpawn; + }, world) : CompletableFuture.completedFuture(worldSpawnPoint); + } + } + + private PortalDeviceSummonPage.State computeState(@Nonnull Player player, @Nonnull ComponentAccessor componentAccessor) { + if (!this.blockRef.isValid()) { + return PortalDeviceSummonPage.Error.INVALID_BLOCK; + } else { + int activeFragments = PortalsPlugin.getInstance().countActiveFragments(); + if (activeFragments >= 4) { + return PortalDeviceSummonPage.Error.MAX_ACTIVE_PORTALS; + } else { + Store chunkStore = this.blockRef.getStore(); + BlockModule.BlockStateInfo blockStateInfo = chunkStore.getComponent(this.blockRef, BlockModule.BlockStateInfo.getComponentType()); + PortalDevice portalDevice = chunkStore.getComponent(this.blockRef, PortalDevice.getComponentType()); + if (blockStateInfo != null && portalDevice != null) { + Ref chunkRef = blockStateInfo.getChunkRef(); + if (chunkRef != null && chunkRef.isValid()) { + WorldChunk worldChunk = chunkStore.getComponent(chunkRef, WorldChunk.getComponentType()); + if (worldChunk == null) { + return PortalDeviceSummonPage.Error.INVALID_BLOCK; + } else { + World existingDestinationWorld = portalDevice.getDestinationWorld(); + if (existingDestinationWorld != null) { + return PortalDeviceSummonPage.Error.INVALID_DESTINATION; + } else if (this.offeredItemStack == null) { + return PortalDeviceSummonPage.Error.NOTHING_OFFERED; + } else { + ItemStack inHand = player.getInventory().getItemInHand(); + if (!this.offeredItemStack.equals(inHand)) { + return PortalDeviceSummonPage.Error.OFFERED_IS_NOT_HELD; + } else { + Item offeredItem = this.offeredItemStack.getItem(); + PortalKey portalKey = offeredItem.getPortalKey(); + if (portalKey == null) { + return PortalDeviceSummonPage.Error.NOT_A_PORTAL_KEY; + } else { + String portalTypeId = portalKey.getPortalTypeId(); + PortalType portalType = PortalType.getAssetMap().getAsset(portalTypeId); + if (portalType == null) { + return new PortalDeviceSummonPage.PortalTypeNotFound(portalTypeId); + } else { + String instanceId = portalType.getInstanceId(); + InstancesPlugin.get(); + boolean instanceExists = InstancesPlugin.doesInstanceAssetExist(instanceId); + if (!instanceExists) { + return new PortalDeviceSummonPage.InstanceKeyNotFound(instanceId); + } else { + PortalWorld insidePortalWorld = componentAccessor.getResource(PortalWorld.getResourceType()); + if (insidePortalWorld.exists()) { + return PortalDeviceSummonPage.Error.PORTAL_INSIDE_PORTAL; + } else { + String gameplayConfigId = portalType.getGameplayConfigId(); + GameplayConfig gameplayConfig = GameplayConfig.getAssetMap().getAsset(gameplayConfigId); + PortalGameplayConfig portalGameplayConfig = gameplayConfig == null + ? null + : gameplayConfig.getPluginConfig().get(PortalGameplayConfig.class); + return (PortalDeviceSummonPage.State)(portalGameplayConfig == null + ? PortalDeviceSummonPage.Error.BOTCHED_GAMEPLAY_CONFIG + : new PortalDeviceSummonPage.CanSpawnPortal( + portalKey, portalType, worldChunk, blockStateInfo, portalDevice, portalGameplayConfig + )); + } + } + } + } + } + } + } + } else { + return PortalDeviceSummonPage.Error.INVALID_BLOCK; + } + } else { + return PortalDeviceSummonPage.Error.INVALID_BLOCK; + } + } + } + } + + private static void decrementItemInHand(Inventory inventory, int amount) { + if (!inventory.usingToolsItem()) { + byte hotbarSlot = inventory.getActiveHotbarSlot(); + if (hotbarSlot != -1) { + ItemContainer hotbar = inventory.getHotbar(); + ItemStack inHand = hotbar.getItemStack(hotbarSlot); + if (inHand != null) { + hotbar.removeItemStackFromSlot(hotbarSlot, inHand, amount, false, true); + } + } + } + } + + private record CanSpawnPortal( + PortalKey portalKey, + PortalType portalType, + WorldChunk worldChunk, + BlockModule.BlockStateInfo blockState, + PortalDevice portalDevice, + PortalGameplayConfig portalGameplayConfig + ) implements PortalDeviceSummonPage.State { + } + + protected static class Data { + private static final String KEY_ACTION = "Action"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + PortalDeviceSummonPage.Data.class, PortalDeviceSummonPage.Data::new + ) + .append(new KeyedCodec<>("Action", Codec.STRING), (entry, s) -> entry.action = s, entry -> entry.action) + .add() + .build(); + private String action; + + protected Data() { + } + } + + private static enum Error implements PortalDeviceSummonPage.State { + NOTHING_OFFERED, + OFFERED_IS_NOT_HELD, + NOT_A_PORTAL_KEY, + INVALID_BLOCK, + INVALID_DESTINATION, + PORTAL_INSIDE_PORTAL, + BOTCHED_GAMEPLAY_CONFIG, + MAX_ACTIVE_PORTALS; + + private Error() { + } + } + + private record InstanceKeyNotFound(String instanceId) implements PortalDeviceSummonPage.State { + } + + private record PortalTypeNotFound(String portalTypeId) implements PortalDeviceSummonPage.State { + } + + private sealed interface State + permits PortalDeviceSummonPage.CanSpawnPortal, + PortalDeviceSummonPage.Error, + PortalDeviceSummonPage.InstanceKeyNotFound, + PortalDeviceSummonPage.PortalTypeNotFound { + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/ui/PortalSpawnFinder.java b/src/com/hypixel/hytale/builtin/portals/ui/PortalSpawnFinder.java new file mode 100644 index 0000000..6204fec --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/ui/PortalSpawnFinder.java @@ -0,0 +1,169 @@ +package com.hypixel.hytale.builtin.portals.ui; + +import com.hypixel.hytale.builtin.portals.utils.posqueries.generators.SearchCircular; +import com.hypixel.hytale.builtin.portals.utils.posqueries.predicates.FitsAPortal; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.portalworld.PortalSpawn; +import com.hypixel.hytale.server.core.modules.collision.WorldUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class PortalSpawnFinder { + public PortalSpawnFinder() { + } + + @Nullable + public static Transform computeSpawnTransform(World world, PortalSpawn config) { + Vector3d spawn = findSpawnByThrowingDarts(world, config); + if (spawn == null) { + spawn = findFallbackPositionOnGround(world, config); + HytaleLogger.getLogger().at(Level.INFO).log("Had to use fallback spawn for portal spawn"); + } + + if (spawn == null) { + HytaleLogger.getLogger().at(Level.INFO).log("Both dart and fallback spawn finder failed for portal spawn"); + return null; + } else { + Vector3f direction = Vector3f.lookAt(spawn).scale(-1.0F); + direction.setPitch(0.0F); + direction.setRoll(0.0F); + return new Transform(spawn.clone().add(0.0, 0.5, 0.0), direction); + } + } + + @Nullable + private static Vector3d findSpawnByThrowingDarts(World world, PortalSpawn config) { + Vector3d center = config.getCenter().toVector3d(); + center.setY(config.getCheckSpawnY()); + int halfwayThrows = config.getChunkDartThrows() / 2; + + for (int chunkDart = 0; chunkDart < config.getChunkDartThrows(); chunkDart++) { + Vector3d pointd = new SearchCircular(config.getMinRadius(), config.getMaxRadius(), 1).execute(world, center).orElse(null); + if (pointd != null) { + Vector3i point = pointd.toVector3i(); + WorldChunk chunk = world.getChunk(ChunkUtil.indexChunkFromBlock(point.x, point.z)); + BlockType firstBlock = chunk.getBlockType(point.x, point.y, point.z); + if (firstBlock != null) { + BlockMaterial firstBlockMat = firstBlock.getMaterial(); + if (firstBlockMat != BlockMaterial.Solid) { + boolean checkIfPortalFitsNice = chunkDart < halfwayThrows; + Vector3d spawn = findGroundWithinChunk(chunk, config, checkIfPortalFitsNice); + if (spawn != null) { + HytaleLogger.getLogger().at(Level.INFO).log("Found fragment spawn at " + spawn + " after " + (chunkDart + 1) + " chunk scan(s)"); + return spawn; + } + } + } + } + } + + return null; + } + + @Nullable + private static Vector3d findGroundWithinChunk(WorldChunk chunk, PortalSpawn config, boolean checkIfPortalFitsNice) { + int chunkBlockX = ChunkUtil.minBlock(chunk.getX()); + int chunkBlockZ = ChunkUtil.minBlock(chunk.getZ()); + ThreadLocalRandom rand = ThreadLocalRandom.current(); + + for (int i = 0; i < config.getChecksPerChunk(); i++) { + int x = chunkBlockX + rand.nextInt(2, 14); + int z = chunkBlockZ + rand.nextInt(2, 14); + Vector3d point = findWithGroundBelow(chunk, x, config.getCheckSpawnY(), z, config.getScanHeight(), false); + if (point != null && (!checkIfPortalFitsNice || FitsAPortal.check(chunk.getWorld(), point))) { + return point; + } + } + + return null; + } + + @Nullable + private static Vector3d findWithGroundBelow(WorldChunk chunk, int x, int y, int z, int scanHeight, boolean fluidsAreAcceptable) { + World world = chunk.getWorld(); + ChunkStore chunkStore = world.getChunkStore(); + Ref chunkRef = chunk.getReference(); + Store chunkStoreAccessor = chunkStore.getStore(); + ChunkColumn chunkColumnComponent = chunkStoreAccessor.getComponent(chunkRef, ChunkColumn.getComponentType()); + BlockChunk blockChunkComponent = chunkStoreAccessor.getComponent(chunkRef, BlockChunk.getComponentType()); + + for (int dy = 0; dy < scanHeight; dy++) { + PortalSpawnFinder.Material selfMat = getMaterial(chunkStoreAccessor, chunkColumnComponent, blockChunkComponent, x, y - dy, z); + PortalSpawnFinder.Material belowMat = getMaterial(chunkStoreAccessor, chunkColumnComponent, blockChunkComponent, x, y - dy - 1, z); + boolean selfValid = selfMat == PortalSpawnFinder.Material.AIR || fluidsAreAcceptable && selfMat == PortalSpawnFinder.Material.FLUID; + if (!selfValid) { + break; + } + + if (belowMat == PortalSpawnFinder.Material.SOLID) { + return new Vector3d(x, y - dy, z); + } + } + + return null; + } + + private static PortalSpawnFinder.Material getMaterial( + @Nonnull ComponentAccessor chunkStore, + @Nonnull ChunkColumn chunkColumnComponent, + @Nonnull BlockChunk blockChunkComponent, + double x, + double y, + double z + ) { + int blockX = (int)x; + int blockY = (int)y; + int blockZ = (int)z; + int fluidId = WorldUtil.getFluidIdAtPosition(chunkStore, chunkColumnComponent, blockX, blockY, blockZ); + if (fluidId != 0) { + return PortalSpawnFinder.Material.FLUID; + } else { + BlockSection blockSection = blockChunkComponent.getSectionAtBlockY(blockY); + int blockId = blockSection.get(blockX, blockY, blockZ); + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + if (blockType == null) { + return PortalSpawnFinder.Material.UNKNOWN; + } else { + return switch (blockType.getMaterial()) { + case Solid -> PortalSpawnFinder.Material.SOLID; + case Empty -> PortalSpawnFinder.Material.AIR; + }; + } + } + } + + @Nullable + private static Vector3d findFallbackPositionOnGround(World world, PortalSpawn config) { + Vector3i center = config.getCenter(); + WorldChunk centerChunk = world.getChunk(ChunkUtil.indexChunkFromBlock(center.x, center.z)); + return findWithGroundBelow(centerChunk, 0, 319, 0, 319, true); + } + + private static enum Material { + SOLID, + FLUID, + AIR, + UNKNOWN; + + private Material() { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/utils/BlockTypeUtils.java b/src/com/hypixel/hytale/builtin/portals/utils/BlockTypeUtils.java new file mode 100644 index 0000000..5e2a5b7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/utils/BlockTypeUtils.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.builtin.portals.utils; + +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; + +public final class BlockTypeUtils { + private BlockTypeUtils() { + } + + public static BlockType getBlockForState(BlockType blockType, String state) { + String baseKey = blockType.getDefaultStateKey(); + BlockType baseBlock = baseKey == null ? blockType : BlockType.getAssetMap().getAsset(baseKey); + return "default".equals(state) ? baseBlock : baseBlock.getBlockForState(state); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/utils/CursedItems.java b/src/com/hypixel/hytale/builtin/portals/utils/CursedItems.java new file mode 100644 index 0000000..1f67335 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/utils/CursedItems.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.portals.utils; + +import com.hypixel.hytale.server.core.asset.type.item.config.metadata.AdventureMetadata; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import java.util.concurrent.atomic.AtomicBoolean; + +public final class CursedItems { + private CursedItems() { + } + + public static boolean uncurseAll(ItemContainer itemContainer) { + AtomicBoolean uncursedAny = new AtomicBoolean(false); + itemContainer.replaceAll((slot, existing) -> { + AdventureMetadata adventureMeta = existing.getFromMetadataOrNull("Adventure", AdventureMetadata.CODEC); + if (adventureMeta == null) { + return existing; + } else if (!adventureMeta.isCursed()) { + return existing; + } else { + adventureMeta.setCursed(false); + uncursedAny.setPlain(true); + return existing.withMetadata("Adventure", AdventureMetadata.CODEC, adventureMeta); + } + }); + return uncursedAny.get(); + } + + public static void deleteAll(Player player) { + deleteAll(player.getInventory().getCombinedEverything()); + } + + public static void deleteAll(ItemContainer itemContainer) { + itemContainer.replaceAll((slot, existing) -> { + AdventureMetadata adventureMeta = existing.getFromMetadataOrNull(AdventureMetadata.KEYED_CODEC); + boolean cursed = adventureMeta != null && adventureMeta.isCursed(); + return cursed ? ItemStack.EMPTY : existing; + }); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/utils/posqueries/PositionPredicate.java b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/PositionPredicate.java new file mode 100644 index 0000000..4fc5d4b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/PositionPredicate.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.builtin.portals.utils.posqueries; + +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.World; + +public interface PositionPredicate { + boolean test(World var1, Vector3d var2); +} diff --git a/src/com/hypixel/hytale/builtin/portals/utils/posqueries/SpatialQuery.java b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/SpatialQuery.java new file mode 100644 index 0000000..1b4de6b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/SpatialQuery.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.builtin.portals.utils.posqueries; + +import com.hypixel.hytale.builtin.portals.utils.posqueries.predicates.generic.FilterQuery; +import com.hypixel.hytale.builtin.portals.utils.posqueries.predicates.generic.FlatMapQuery; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.Optional; +import java.util.logging.Level; +import java.util.stream.Stream; +import javax.annotation.Nullable; + +public interface SpatialQuery { + Stream createCandidates(World var1, Vector3d var2, @Nullable SpatialQueryDebug var3); + + default SpatialQuery then(SpatialQuery expand) { + return new FlatMapQuery(this, expand); + } + + default SpatialQuery filter(PositionPredicate predicate) { + return new FilterQuery(this, predicate); + } + + default Optional execute(World world, Vector3d origin) { + return this.createCandidates(world, origin, null).findFirst(); + } + + default Optional debug(World world, Vector3d origin) { + try { + SpatialQueryDebug debug = new SpatialQueryDebug(); + Optional output = this.createCandidates(world, origin, debug).findFirst(); + debug.appendLine("-> OUTPUT: " + output.map(debug::fmt).orElse("")); + HytaleLogger.getLogger().at(Level.INFO).log(debug.toString()); + return output; + } catch (Throwable var5) { + HytaleLogger.getLogger().at(Level.SEVERE).withCause(var5).log("Error in SpatialQuery"); + throw new RuntimeException("Error in SpatialQuery", var5); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/utils/posqueries/SpatialQueryDebug.java b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/SpatialQueryDebug.java new file mode 100644 index 0000000..19010fa --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/SpatialQueryDebug.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.builtin.portals.utils.posqueries; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3d; +import java.util.Stack; +import java.util.logging.Level; + +public class SpatialQueryDebug { + private final StringBuilder builder = new StringBuilder(); + private String indent = ""; + private Stack scope = new Stack<>(); + + public SpatialQueryDebug() { + this.appendLine("SPATIAL QUERY DEBUG"); + } + + public SpatialQueryDebug appendLine(String string) { + HytaleLogger.getLogger().at(Level.INFO).log(this.indent + "| " + string); + return this; + } + + public SpatialQueryDebug indent(String scopeReason) { + HytaleLogger.getLogger().at(Level.INFO).log(this.indent + "\u2b91 " + scopeReason); + this.indent = this.indent + " "; + this.scope.add(scopeReason); + return this; + } + + public SpatialQueryDebug unindent() { + if (this.indent.length() >= 2) { + this.indent = this.indent.substring(0, this.indent.length() - 2); + } + + if (!this.scope.isEmpty()) { + String scopeReason = this.scope.pop(); + HytaleLogger.getLogger().at(Level.INFO).log(this.indent + "\u2b90 (DONE) " + scopeReason); + } + + return this; + } + + public String fmt(Vector3d point) { + return "(" + String.format("%.1f", point.x) + ", " + String.format("%.1f", point.y) + ", " + String.format("%.1f", point.z) + ")"; + } + + @Override + public String toString() { + return this.builder.toString(); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/utils/posqueries/generators/SearchBelow.java b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/generators/SearchBelow.java new file mode 100644 index 0000000..a09705e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/generators/SearchBelow.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.builtin.portals.utils.posqueries.generators; + +import com.hypixel.hytale.builtin.portals.utils.posqueries.SpatialQuery; +import com.hypixel.hytale.builtin.portals.utils.posqueries.SpatialQueryDebug; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import javax.annotation.Nullable; + +public class SearchBelow implements SpatialQuery { + private final int height; + + public SearchBelow(int height) { + this.height = height; + } + + @Override + public Stream createCandidates(World world, Vector3d origin, @Nullable SpatialQueryDebug debug) { + if (debug != null) { + debug.appendLine("Searching up to " + this.height + " blocks below " + debug.fmt(origin) + ":"); + } + + return IntStream.rangeClosed(0, this.height).mapToObj(dy -> origin.clone().add(0.0, -dy, 0.0)); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/utils/posqueries/generators/SearchCircular.java b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/generators/SearchCircular.java new file mode 100644 index 0000000..d48a717 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/generators/SearchCircular.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.builtin.portals.utils.posqueries.generators; + +import com.hypixel.hytale.builtin.portals.utils.posqueries.SpatialQuery; +import com.hypixel.hytale.builtin.portals.utils.posqueries.SpatialQueryDebug; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Stream; +import javax.annotation.Nullable; + +public class SearchCircular implements SpatialQuery { + private final double minRadius; + private final double maxRadius; + private final int attempts; + + public SearchCircular(double radius, int attempts) { + this(radius, radius, attempts); + } + + public SearchCircular(double minRadius, double maxRadius, int attempts) { + this.minRadius = minRadius; + this.maxRadius = maxRadius; + this.attempts = attempts; + } + + @Override + public Stream createCandidates(World world, Vector3d origin, @Nullable SpatialQueryDebug debug) { + if (debug != null) { + String radiusFmt = this.minRadius == this.maxRadius + ? String.format("%.1f", this.minRadius) + : String.format("%.1f", this.minRadius) + "-" + String.format("%.1f", this.maxRadius); + debug.appendLine("Searching in a " + radiusFmt + " radius circle around " + debug.fmt(origin) + ":"); + } + + return Stream.generate(() -> { + ThreadLocalRandom rand = ThreadLocalRandom.current(); + double rad = rand.nextDouble() * Math.PI * 2.0; + double radius = this.minRadius + rand.nextDouble() * (this.maxRadius - this.minRadius); + return origin.clone().add(Math.cos(rad) * radius, 0.0, Math.sin(rad) * radius); + }).limit(this.attempts); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/utils/posqueries/generators/SearchCone.java b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/generators/SearchCone.java new file mode 100644 index 0000000..fbc144d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/generators/SearchCone.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.builtin.portals.utils.posqueries.generators; + +import com.hypixel.hytale.builtin.portals.utils.posqueries.SpatialQuery; +import com.hypixel.hytale.builtin.portals.utils.posqueries.SpatialQueryDebug; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Stream; +import javax.annotation.Nullable; + +public class SearchCone implements SpatialQuery { + private final Vector3d direction; + private final double minRadius; + private final double maxRadius; + private final double maxDegrees; + private final int attempts; + + public SearchCone(Vector3d direction, double radius, double maxDegrees, int attempts) { + this(direction, radius, radius, maxDegrees, attempts); + } + + public SearchCone(Vector3d direction, double minRadius, double maxRadius, double maxDegrees, int attempts) { + this.direction = direction; + this.minRadius = minRadius; + this.maxRadius = maxRadius; + this.maxDegrees = maxDegrees; + this.attempts = attempts; + } + + @Override + public Stream createCandidates(World world, Vector3d origin, @Nullable SpatialQueryDebug debug) { + if (debug != null) { + String radiusFmt = this.minRadius == this.maxRadius + ? String.format("%.1f", this.minRadius) + : String.format("%.1f", this.minRadius) + "-" + String.format("%.1f", this.maxRadius); + debug.appendLine( + "Searching in a " + + radiusFmt + + " radius cone (max " + + String.format("%.1f", this.maxDegrees) + + "\u00b0) in direction " + + debug.fmt(this.direction) + + " from " + + debug.fmt(origin) + + ":" + ); + } + + double maxRadians = Math.toRadians(this.maxDegrees); + return Stream.generate(() -> { + ThreadLocalRandom rand = ThreadLocalRandom.current(); + double distance = this.minRadius + rand.nextDouble() * (this.maxRadius - this.minRadius); + double yawOffset = (rand.nextDouble() - 0.5) * maxRadians; + Vector3d dir = this.direction.clone().rotateY((float)yawOffset).setLength(distance); + return dir.add(origin); + }).limit(this.attempts); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/FitsAPortal.java b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/FitsAPortal.java new file mode 100644 index 0000000..e2f2132 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/FitsAPortal.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.builtin.portals.utils.posqueries.predicates; + +import com.hypixel.hytale.builtin.portals.utils.posqueries.PositionPredicate; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.modules.collision.WorldUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; + +public class FitsAPortal implements PositionPredicate { + private static final int[] THREES = new int[]{-1, 0, 1}; + + public FitsAPortal() { + } + + @Override + public boolean test(World world, Vector3d point) { + return check(world, point); + } + + public static boolean check(World world, Vector3d point) { + ChunkStore chunkStore = world.getChunkStore(); + + for (int x : THREES) { + for (int z : THREES) { + for (int y = -1; y <= 3; y++) { + Vector3i rel = point.toVector3i().add(x, y, z); + WorldChunk chunk = world.getChunk(ChunkUtil.indexChunkFromBlock(rel.x, rel.z)); + Ref chunkRef = chunk.getReference(); + Store chunkStoreAccessor = chunkStore.getStore(); + ChunkColumn chunkColumnComponent = chunkStoreAccessor.getComponent(chunkRef, ChunkColumn.getComponentType()); + BlockChunk blockChunkComponent = chunkStoreAccessor.getComponent(chunkRef, BlockChunk.getComponentType()); + int fluidId = WorldUtil.getFluidIdAtPosition(chunkStoreAccessor, chunkColumnComponent, rel.x, rel.y, rel.z); + if (fluidId != 0) { + return false; + } + + BlockSection blockSection = blockChunkComponent.getSectionAtBlockY(rel.y); + int blockId = blockSection.get(rel.x, rel.y, rel.z); + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + if (blockType == null) { + return false; + } + + BlockMaterial wanted = y < 0 ? BlockMaterial.Solid : BlockMaterial.Empty; + if (blockType.getMaterial() != wanted) { + return false; + } + } + } + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/NotNearAnyInHashGrid.java b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/NotNearAnyInHashGrid.java new file mode 100644 index 0000000..b084645 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/NotNearAnyInHashGrid.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.builtin.portals.utils.posqueries.predicates; + +import com.hypixel.hytale.builtin.portals.utils.posqueries.PositionPredicate; +import com.hypixel.hytale.builtin.portals.utils.spatial.SpatialHashGrid; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.World; + +public record NotNearAnyInHashGrid(SpatialHashGrid hashGrid, double radius) implements PositionPredicate { + @Override + public boolean test(World world, Vector3d point) { + return !this.hashGrid.hasAnyWithin(point, this.radius); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/NotNearPoint.java b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/NotNearPoint.java new file mode 100644 index 0000000..e53700c --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/NotNearPoint.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.builtin.portals.utils.posqueries.predicates; + +import com.hypixel.hytale.builtin.portals.utils.posqueries.PositionPredicate; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.World; + +public final class NotNearPoint implements PositionPredicate { + private final Vector3d point; + private final double radiusSq; + + public NotNearPoint(Vector3d point, double radius) { + this.point = point; + this.radiusSq = radius * radius; + } + + @Override + public boolean test(World world, Vector3d origin) { + return origin.distanceSquaredTo(this.point) >= this.radiusSq; + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/NotNearPointXZ.java b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/NotNearPointXZ.java new file mode 100644 index 0000000..9f474ba --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/NotNearPointXZ.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.builtin.portals.utils.posqueries.predicates; + +import com.hypixel.hytale.builtin.portals.utils.posqueries.PositionPredicate; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.World; + +public final class NotNearPointXZ implements PositionPredicate { + private final Vector3d point; + private final double radiusSq; + + public NotNearPointXZ(Vector3d point, double radius) { + this.point = point; + this.radiusSq = radius * radius; + } + + @Override + public boolean test(World world, Vector3d origin) { + Vector3d pointAtHeight = this.point.clone(); + pointAtHeight.y = origin.y; + return origin.distanceSquaredTo(pointAtHeight) >= this.radiusSq; + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/generic/FilterQuery.java b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/generic/FilterQuery.java new file mode 100644 index 0000000..62216a5 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/generic/FilterQuery.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.builtin.portals.utils.posqueries.predicates.generic; + +import com.hypixel.hytale.builtin.portals.utils.posqueries.PositionPredicate; +import com.hypixel.hytale.builtin.portals.utils.posqueries.SpatialQuery; +import com.hypixel.hytale.builtin.portals.utils.posqueries.SpatialQueryDebug; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; +import javax.annotation.Nullable; + +public class FilterQuery implements SpatialQuery { + private final SpatialQuery query; + private final PositionPredicate predicate; + private final boolean failFast; + + public FilterQuery(SpatialQuery query, PositionPredicate predicate) { + this(query, predicate, false); + } + + public FilterQuery(SpatialQuery query, PositionPredicate predicate, boolean failFast) { + this.query = query; + this.predicate = predicate; + this.failFast = failFast; + } + + @Override + public Stream createCandidates(World world, Vector3d origin, @Nullable SpatialQueryDebug debug) { + Stream stream = this.query.createCandidates(world, origin, debug); + AtomicBoolean failed = new AtomicBoolean(); + if (this.failFast) { + stream = stream.takeWhile(candidate -> !failed.get()); + } + + return stream.filter(candidate -> { + boolean accepted = this.predicate.test(world, candidate); + if (debug != null) { + debug.appendLine(this.predicate.getClass().getSimpleName() + " on " + debug.fmt(candidate) + " = " + accepted); + } + + if (!accepted) { + failed.set(true); + } + + return accepted; + }); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/generic/FlatMapQuery.java b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/generic/FlatMapQuery.java new file mode 100644 index 0000000..309ed1a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/utils/posqueries/predicates/generic/FlatMapQuery.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.builtin.portals.utils.posqueries.predicates.generic; + +import com.hypixel.hytale.builtin.portals.utils.posqueries.SpatialQuery; +import com.hypixel.hytale.builtin.portals.utils.posqueries.SpatialQueryDebug; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.stream.Stream; +import javax.annotation.Nullable; + +public class FlatMapQuery implements SpatialQuery { + private final SpatialQuery generator; + private final SpatialQuery expand; + + public FlatMapQuery(SpatialQuery generator, SpatialQuery expand) { + this.generator = generator; + this.expand = expand; + } + + @Override + public Stream createCandidates(World world, Vector3d origin, @Nullable SpatialQueryDebug debug) { + return this.generator.createCandidates(world, origin, debug).flatMap(candidate -> { + Stream candidates = this.expand.createCandidates(world, candidate, debug); + if (debug != null) { + debug.indent("Flat-map expand from " + debug.fmt(candidate) + ":"); + return Stream.concat(candidates, Stream.of((Vector3d)null).peek(x -> debug.unindent()).flatMap(x -> Stream.empty())); + } else { + return candidates; + } + }); + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/utils/spatial/OctTree.java b/src/com/hypixel/hytale/builtin/portals/utils/spatial/OctTree.java new file mode 100644 index 0000000..567a704 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/utils/spatial/OctTree.java @@ -0,0 +1,118 @@ +package com.hypixel.hytale.builtin.portals.utils.spatial; + +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector3d; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Map; + +public class OctTree { + private static final int SIZE = 8; + private static final int DEFAULT_NODE_CAPACITY = 4; + private final OctTree.Node root; + private final int nodeCapacity; + + public OctTree(double inradius) { + this(new Box(-inradius, -inradius, -inradius, inradius, inradius, inradius), 4); + } + + public OctTree(Box boundary) { + this(boundary, 4); + } + + public OctTree(Box boundary, int nodeCapacity) { + this.root = new OctTree.Node(boundary); + this.nodeCapacity = nodeCapacity; + } + + public void add(Vector3d pos, T value) { + this.add(this.root, pos, value); + } + + private boolean add(OctTree.Node node, Vector3d pos, T value) { + if (node != null && node.boundary.containsPosition(pos)) { + if (node.size() < this.nodeCapacity) { + node.addPoint(pos, value); + return true; + } else { + if (node.dirs.isEmpty()) { + this.subdivide(node); + } + + for (int i = 0; i < 8; i++) { + if (this.add(node.dirs.get(i), pos, value)) { + return true; + } + } + + return false; + } + } else { + return false; + } + } + + private void subdivide(OctTree.Node node) { + Vector3d min = node.boundary.min; + double side = node.boundary.width() / 2.0; + + for (int i = 0; i < 8; i++) { + Vector3d subMin = new Vector3d(min.x + ((i & 1) > 0 ? side : 0.0), min.y + ((i & 2) > 0 ? side : 0.0), min.z + ((i & 4) > 0 ? side : 0.0)); + Box sub = Box.cube(subMin, side); + node.dirs.add(new OctTree.Node(sub)); + } + } + + public Map getAllPoints() { + return this.queryRange(this.root.boundary); + } + + public Map queryRange(Vector3d position, double inradius) { + Box range = Box.centeredCube(position, inradius); + return this.queryRange(range); + } + + public Map queryRange(Box range) { + Map out = new Object2ObjectOpenHashMap<>(); + this.queryRange(this.root, range, out); + return out; + } + + private void queryRange(OctTree.Node node, Box range, Map out) { + if (node != null && node.boundary.isIntersecting(range)) { + for (int i = 0; i < node.size(); i++) { + Vector3d point = node.points[i]; + if (range.containsPosition(point)) { + T value = node.values.get(i); + out.put(value, point); + } + } + + for (OctTree.Node dir : node.dirs) { + this.queryRange(dir, range, out); + } + } + } + + private class Node { + private final Box boundary; + private final List.Node> dirs = new ObjectArrayList<>(8); + private final Vector3d[] points = new Vector3d[OctTree.this.nodeCapacity]; + private final List values = new ObjectArrayList<>(OctTree.this.nodeCapacity); + private int count; + + public Node(Box boundary) { + this.boundary = boundary; + } + + public int size() { + return this.count; + } + + public void addPoint(Vector3d pos, T value) { + this.points[this.count++] = pos; + this.values.add(value); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/portals/utils/spatial/SpatialHashGrid.java b/src/com/hypixel/hytale/builtin/portals/utils/spatial/SpatialHashGrid.java new file mode 100644 index 0000000..5c7ee0a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/portals/utils/spatial/SpatialHashGrid.java @@ -0,0 +1,199 @@ +package com.hypixel.hytale.builtin.portals.utils.spatial; + +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; +import javax.annotation.Nullable; + +public class SpatialHashGrid { + private final double cellSize; + private final Map>> grid = new Object2ObjectOpenHashMap<>(); + private final Map> index = new Object2ObjectOpenHashMap<>(); + + public SpatialHashGrid(double cellSize) { + this.cellSize = cellSize; + } + + private Vector3i cellFor(Vector3d p) { + return new Vector3i(MathUtil.floor(p.x / this.cellSize), MathUtil.floor(p.y / this.cellSize), MathUtil.floor(p.z / this.cellSize)); + } + + public Collection getAll() { + return Collections.unmodifiableSet(this.index.keySet()); + } + + public int size() { + return this.index.size(); + } + + public boolean isEmpty() { + return this.index.isEmpty(); + } + + public void add(Vector3d pos, T value) { + Vector3i cell = this.cellFor(pos); + SpatialHashGrid.Entry entry = new SpatialHashGrid.Entry<>(pos.clone(), cell, value); + this.index.put(value, entry); + this.grid.computeIfAbsent(cell, x -> new ObjectArrayList<>()).add(entry); + } + + public boolean remove(T value) { + SpatialHashGrid.Entry entry = this.index.remove(value); + if (entry == null) { + return false; + } else { + List> bucket = this.grid.get(entry.cell); + if (bucket != null) { + bucket.remove(entry); + if (bucket.isEmpty()) { + this.grid.remove(entry.cell); + } + } + + return true; + } + } + + public void removeIf(Predicate predicate) { + Set toRemove = new HashSet<>(); + + for (T elem : this.index.keySet()) { + if (predicate.test(elem)) { + toRemove.add(elem); + } + } + + for (T elemx : toRemove) { + this.remove(elemx); + } + } + + public void move(T value, Vector3d newPos) { + SpatialHashGrid.Entry entry = this.index.get(value); + if (entry != null) { + Vector3i oldCell = entry.cell; + Vector3i newCell = this.cellFor(newPos); + entry.pos.assign(newPos); + if (!oldCell.equals(newCell)) { + List> oldBucket = this.grid.get(oldCell); + oldBucket.remove(entry); + if (oldBucket.isEmpty()) { + this.grid.remove(oldCell); + } + + entry.cell = newCell; + this.grid.computeIfAbsent(newCell, x -> new ObjectArrayList<>()).add(entry); + } + } + } + + public Map queryRange(Vector3d center, double radius) { + Map out = new Object2ObjectOpenHashMap<>(); + double radiusSq = radius * radius; + this.query(center, radius, bucket -> { + for (SpatialHashGrid.Entry entry : bucket) { + if (entry.pos.distanceSquaredTo(center) <= radiusSq) { + out.put(entry.value, entry.pos); + } + } + + return true; + }); + return out; + } + + @Nullable + public T findClosest(final Vector3d center, double searchRadius) { + var closestVisitor = new SpatialHashGrid.CellVisitor() { + double closestDist = Double.MAX_VALUE; + T closest = (T)null; + + @Override + public boolean visit(List> bucket) { + for (SpatialHashGrid.Entry entry : bucket) { + double dist = entry.pos.distanceSquaredTo(center); + if (dist <= this.closestDist) { + this.closestDist = dist; + this.closest = entry.value; + } + } + + return true; + } + }; + this.query(center, searchRadius, closestVisitor); + return closestVisitor.closest; + } + + public boolean hasAnyWithin(final Vector3d center, final double radius) { + var withinVisitor = new SpatialHashGrid.CellVisitor() { + final double radiusSq = radius * radius; + boolean hasWithin = false; + + @Override + public boolean visit(List> bucket) { + for (SpatialHashGrid.Entry entry : bucket) { + double dist = entry.pos.distanceSquaredTo(center); + if (dist <= this.radiusSq) { + this.hasWithin = true; + return false; + } + } + + return true; + } + }; + this.query(center, radius, withinVisitor); + return withinVisitor.hasWithin; + } + + private void query(Vector3d center, double radius, SpatialHashGrid.CellVisitor visitor) { + int minX = MathUtil.floor((center.x - radius) / this.cellSize); + int minY = MathUtil.floor((center.y - radius) / this.cellSize); + int minZ = MathUtil.floor((center.z - radius) / this.cellSize); + int maxX = MathUtil.floor((center.x + radius) / this.cellSize); + int maxY = MathUtil.floor((center.y + radius) / this.cellSize); + int maxZ = MathUtil.floor((center.z + radius) / this.cellSize); + Vector3i lookup = new Vector3i(); + + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + for (int z = minZ; z <= maxZ; z++) { + lookup.assign(x, y, z); + List> bucket = this.grid.get(lookup); + if (bucket != null) { + boolean keepGoing = visitor.visit(bucket); + if (!keepGoing) { + return; + } + } + } + } + } + } + + private interface CellVisitor { + boolean visit(List> var1); + } + + private static final class Entry { + private final Vector3d pos; + private Vector3i cell; + private final T value; + + private Entry(Vector3d pos, Vector3i cell, T value) { + this.pos = pos; + this.cell = cell; + this.value = value; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/safetyroll/SafetyRollPlugin.java b/src/com/hypixel/hytale/builtin/safetyroll/SafetyRollPlugin.java new file mode 100644 index 0000000..36a9acd --- /dev/null +++ b/src/com/hypixel/hytale/builtin/safetyroll/SafetyRollPlugin.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.builtin.safetyroll; + +import com.hypixel.hytale.protocol.packets.setup.ClientFeature; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import javax.annotation.Nonnull; + +public class SafetyRollPlugin extends JavaPlugin { + public SafetyRollPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + this.getClientFeatureRegistry().registerClientTag("Allows=Movement"); + this.getClientFeatureRegistry().register(ClientFeature.SafetyRoll); + } +} diff --git a/src/com/hypixel/hytale/builtin/sprintforce/SprintForcePlugin.java b/src/com/hypixel/hytale/builtin/sprintforce/SprintForcePlugin.java new file mode 100644 index 0000000..6dea026 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/sprintforce/SprintForcePlugin.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.builtin.sprintforce; + +import com.hypixel.hytale.protocol.packets.setup.ClientFeature; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import javax.annotation.Nonnull; + +public class SprintForcePlugin extends JavaPlugin { + public SprintForcePlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + this.getClientFeatureRegistry().register(ClientFeature.SprintForce); + } +} diff --git a/src/com/hypixel/hytale/builtin/tagset/TagSet.java b/src/com/hypixel/hytale/builtin/tagset/TagSet.java new file mode 100644 index 0000000..ef780f0 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/tagset/TagSet.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.builtin.tagset; + +import com.hypixel.hytale.assetstore.JsonAsset; + +public interface TagSet extends JsonAsset { + String[] getIncludedTagSets(); + + String[] getExcludedTagSets(); + + String[] getIncludedTags(); + + String[] getExcludedTags(); +} diff --git a/src/com/hypixel/hytale/builtin/tagset/TagSetLookupTable.java b/src/com/hypixel/hytale/builtin/tagset/TagSetLookupTable.java new file mode 100644 index 0000000..cde1f27 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/tagset/TagSetLookupTable.java @@ -0,0 +1,167 @@ +package com.hypixel.hytale.builtin.tagset; + +import com.hypixel.hytale.common.util.StringUtil; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntMaps; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.IntConsumer; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TagSetLookupTable { + @Nonnull + private Int2ObjectMap tagMatcher = new Int2ObjectOpenHashMap<>(); + + public TagSetLookupTable(@Nonnull Map tagSetMap, @Nonnull Object2IntMap tagSetIndexMap, @Nonnull Object2IntMap tagIndexMap) { + this.createTagMap(tagSetMap, tagSetIndexMap, tagIndexMap); + } + + private void createTagMap(@Nonnull Map tagSetMap, @Nonnull Object2IntMap tagSetIndexMap, @Nonnull Object2IntMap tagIndexMap) { + IntArrayList path = new IntArrayList(); + tagSetMap.forEach((key, entry) -> { + int id = tagSetIndexMap.getOrDefault(key, -1); + if (id < 0 || !this.tagMatcher.containsKey(id)) { + try { + this.createTagSet((T)entry, tagSetMap, tagSetIndexMap, tagIndexMap, path); + } catch (IllegalStateException var9) { + throw new IllegalStateException(key + ": ", var9); + } + + path.clear(); + } + }); + } + + @Nonnull + private IntSet createTagSet( + @Nonnull T tagSet, + @Nonnull Map tagSetMap, + @Nonnull Object2IntMap tagSetIndexMap, + @Nonnull Object2IntMap tagIndexMap, + @Nonnull IntArrayList path + ) { + IntOpenHashSet set = new IntOpenHashSet(); + int index = tagSetIndexMap.getInt(tagSet.getId()); + if (path.contains(index)) { + throw new IllegalStateException("Cyclic reference to set detected: " + tagSet.getId()); + } else { + path.add(index); + this.tagMatcher.put(index, set); + if (!tagIndexMap.isEmpty()) { + String[] includedTagSets = tagSet.getIncludedTagSets(); + if (includedTagSets != null) { + for (String tag : includedTagSets) { + this.consumeSet(tag, tagSetMap, tagSetIndexMap, tagIndexMap, path, set::addAll); + } + } + + String[] excludedTagSets = tagSet.getExcludedTagSets(); + if (excludedTagSets != null) { + for (String tag : excludedTagSets) { + this.consumeSet(tag, tagSetMap, tagSetIndexMap, tagIndexMap, path, set::removeAll); + } + } + + String[] includedTags = tagSet.getIncludedTags(); + if (includedTags != null) { + for (String tag : includedTags) { + this.consumeTag(tag, tagSet, tagIndexMap, set::add); + } + } + + String[] excludedTags = tagSet.getExcludedTags(); + if (excludedTags != null) { + for (String tag : excludedTags) { + this.consumeTag(tag, tagSet, tagIndexMap, set::remove); + } + } + } + + return set; + } + } + + private void consumeSet( + String tag, + @Nonnull Map tagSetMap, + @Nonnull Object2IntMap tagSetIndexMap, + @Nonnull Object2IntMap tagIndexMap, + @Nonnull IntArrayList path, + @Nonnull Consumer predicate + ) { + IntSet s = this.getOrCreateTagSet(tag, tagSetMap, tagSetIndexMap, tagIndexMap, path); + if (s != null) { + predicate.accept(s); + } + } + + private void consumeTag(@Nonnull String tag, @Nonnull T tagSet, @Nonnull Object2IntMap tagIndexMap, @Nonnull IntConsumer predicate) { + if (StringUtil.isGlobPattern(tag)) { + ObjectIterator> it = Object2IntMaps.fastIterator(tagIndexMap); + + while (it.hasNext()) { + Entry entry = it.next(); + if (StringUtil.isGlobMatching(tag, entry.getKey())) { + predicate.accept(entry.getIntValue()); + } + } + } else { + int index = tagIndexMap.getOrDefault(tag, -1); + if (index >= 0) { + predicate.accept(index); + } else { + TagSetPlugin.get() + .getLogger() + .at(Level.WARNING) + .log("Tag Set '%s' references '%s' which is not a pattern and does not otherwise exist", tagSet.getId(), tag); + } + } + } + + @Nullable + private IntSet getOrCreateTagSet( + String identifier, + @Nonnull Map tagSetMap, + @Nonnull Object2IntMap tagSetIndexMap, + @Nonnull Object2IntMap tagIndexMap, + @Nonnull IntArrayList path + ) { + int tagSetIndex = tagSetIndexMap.getOrDefault(identifier, -1); + IntSet intSet = null; + if (tagSetIndex >= 0 && this.tagMatcher.containsKey(tagSetIndex)) { + if (path.contains(tagSetIndex)) { + throw new IllegalStateException("Cyclic reference to set detected: " + identifier); + } + + path.add(tagSetIndex); + intSet = this.tagMatcher.get(tagSetIndex); + } else { + T set = tagSetMap.get(identifier); + if (set != null) { + intSet = this.createTagSet(set, tagSetMap, tagSetIndexMap, tagIndexMap, path); + } else { + TagSetPlugin.get() + .getLogger() + .at(Level.WARNING) + .log("Creating tag sets: Tag Set '%s' does not exist, but is being referenced as a tag", identifier); + } + } + + path.removeInt(path.size() - 1); + return intSet; + } + + @Nonnull + public Int2ObjectMap getFlattenedSet() { + return this.tagMatcher; + } +} diff --git a/src/com/hypixel/hytale/builtin/tagset/TagSetPlugin.java b/src/com/hypixel/hytale/builtin/tagset/TagSetPlugin.java new file mode 100644 index 0000000..1313014 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/tagset/TagSetPlugin.java @@ -0,0 +1,89 @@ +package com.hypixel.hytale.builtin.tagset; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.builtin.tagset.config.NPCGroup; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TagSetPlugin extends JavaPlugin { + private static TagSetPlugin instance; + private final Map, TagSetPlugin.TagSetLookup> lookups = new ConcurrentHashMap<>(); + + public static TagSetPlugin get() { + return instance; + } + + public TagSetPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + instance = this; + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + NPCGroup.class, new IndexedLookupTableAssetMap<>(NPCGroup[]::new) + ) + .setPath("NPC/Groups")) + .setCodec(NPCGroup.CODEC)) + .setKeyFunction(NPCGroup::getId)) + .setReplaceOnRemove(NPCGroup::new)) + .loadsBefore(Interaction.class)) + .build() + ); + this.registerTagSetType(NPCGroup.class); + } + + public void registerTagSetType(Class clazz) { + if (!this.isDisabled()) { + this.lookups.computeIfAbsent(clazz, c -> new TagSetPlugin.TagSetLookup()); + } + } + + @Nonnull + public static TagSetPlugin.TagSetLookup get(Class clazz) { + return Objects.requireNonNull(instance.lookups.get(clazz), "Class is not registered with the TagSet module!"); + } + + public static class TagSetLookup { + @Nonnull + private Int2ObjectMap flattenedSets = Int2ObjectMaps.unmodifiable(new Int2ObjectOpenHashMap<>()); + + public TagSetLookup() { + } + + public void putAssetSets( + @Nonnull Map tagSetAssets, @Nonnull Object2IntMap tagSetIndexMap, @Nonnull Object2IntMap tagIndexMap + ) { + TagSetLookupTable lookupTable = new TagSetLookupTable<>(tagSetAssets, tagSetIndexMap, tagIndexMap); + this.flattenedSets = Int2ObjectMaps.unmodifiable(lookupTable.getFlattenedSet()); + } + + public boolean tagInSet(int tagSet, int tagIndex) { + IntSet set = this.flattenedSets.get(tagSet); + if (set == null) { + throw new IllegalArgumentException("Attempting to access a tagset which does not exist!"); + } else { + return set.contains(tagIndex); + } + } + + @Nullable + public IntSet getSet(int tagSet) { + return this.flattenedSets.get(tagSet); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/tagset/config/NPCGroup.java b/src/com/hypixel/hytale/builtin/tagset/config/NPCGroup.java new file mode 100644 index 0000000..658b6ea --- /dev/null +++ b/src/com/hypixel/hytale/builtin/tagset/config/NPCGroup.java @@ -0,0 +1,102 @@ +package com.hypixel.hytale.builtin.tagset.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.builtin.tagset.TagSet; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import javax.annotation.Nonnull; + +public class NPCGroup implements JsonAssetWithMap>, TagSet { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + NPCGroup.class, NPCGroup::new, Codec.STRING, (t, k) -> t.id = k, t -> t.id, (asset, data) -> asset.data = data, asset -> asset.data + ) + .documentation("Defines a group or collection of NPC types.") + .append( + new KeyedCodec<>("IncludeRoles", Codec.STRING_ARRAY), (npcGroup, strings) -> npcGroup.includedRoles = strings, npcGroup -> npcGroup.includedRoles + ) + .documentation("A list of individual types to include.") + .add() + .append( + new KeyedCodec<>("ExcludeRoles", Codec.STRING_ARRAY), (npcGroup, strings) -> npcGroup.excludedRoles = strings, npcGroup -> npcGroup.excludedRoles + ) + .documentation("A list of individual types to exclude.") + .add() + .append( + new KeyedCodec<>("IncludeGroups", Codec.STRING_ARRAY), + (npcGroup, strings) -> npcGroup.includedGroupTags = strings, + npcGroup -> npcGroup.includedGroupTags + ) + .documentation("A list of other groups to include.") + .add() + .append( + new KeyedCodec<>("ExcludeGroups", Codec.STRING_ARRAY), + (npcGroup, strings) -> npcGroup.excludedGroupTags = strings, + npcGroup -> npcGroup.excludedGroupTags + ) + .documentation("A list of other groups to exclude.") + .add() + .build(); + @Nonnull + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(NPCGroup.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(NPCGroup::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected String[] includedGroupTags; + protected String[] excludedGroupTags; + protected String[] includedRoles; + protected String[] excludedRoles; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(NPCGroup.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public NPCGroup(String id) { + this.id = id; + } + + protected NPCGroup() { + } + + public String getId() { + return this.id; + } + + @Override + public String[] getIncludedTagSets() { + return this.includedGroupTags; + } + + @Override + public String[] getExcludedTagSets() { + return this.excludedGroupTags; + } + + @Override + public String[] getIncludedTags() { + return this.includedRoles; + } + + @Override + public String[] getExcludedTags() { + return this.excludedRoles; + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/TeleportPlugin.java b/src/com/hypixel/hytale/builtin/teleport/TeleportPlugin.java new file mode 100644 index 0000000..674f2f3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/TeleportPlugin.java @@ -0,0 +1,285 @@ +package com.hypixel.hytale.builtin.teleport; + +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.builtin.teleport.commands.teleport.SpawnCommand; +import com.hypixel.hytale.builtin.teleport.commands.teleport.TeleportCommand; +import com.hypixel.hytale.builtin.teleport.commands.warp.WarpCommand; +import com.hypixel.hytale.builtin.teleport.components.TeleportHistory; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.EventRegistry; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.command.system.CommandRegistry; +import com.hypixel.hytale.server.core.entity.nameplate.Nameplate; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.HiddenFromAdventurePlayers; +import com.hypixel.hytale.server.core.modules.entity.component.Intangible; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.events.AddWorldEvent; +import com.hypixel.hytale.server.core.universe.world.events.AllWorldsLoadedEvent; +import com.hypixel.hytale.server.core.universe.world.events.ChunkPreLoadProcessEvent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; +import com.hypixel.hytale.server.core.util.BsonUtil; +import com.hypixel.hytale.server.core.util.PositionUtil; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonDocument; + +public class TeleportPlugin extends JavaPlugin { + private static TeleportPlugin instance; + public static final String WARP_MODEL_ID = "Warp"; + private ComponentType teleportHistoryComponentType; + private ComponentType warpComponentType; + @Nonnull + private final AtomicBoolean loaded = new AtomicBoolean(); + @Nonnull + private final ReentrantLock saveLock = new ReentrantLock(); + @Nonnull + private final AtomicBoolean postSaveRedo = new AtomicBoolean(false); + @Nonnull + private final Map warps = new ConcurrentHashMap<>(); + private Model warpModel; + + @Nonnull + public static TeleportPlugin get() { + return instance; + } + + public TeleportPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Nonnull + public ComponentType getTeleportHistoryComponentType() { + return this.teleportHistoryComponentType; + } + + public boolean isWarpsLoaded() { + return this.loaded.get(); + } + + @Override + protected void setup() { + instance = this; + CommandRegistry commandRegistry = this.getCommandRegistry(); + EventRegistry eventRegistry = this.getEventRegistry(); + commandRegistry.registerCommand(new TeleportCommand()); + commandRegistry.registerCommand(new WarpCommand()); + commandRegistry.registerCommand(new SpawnCommand()); + eventRegistry.register(LoadedAssetsEvent.class, ModelAsset.class, this::onModelAssetChange); + eventRegistry.registerGlobal(ChunkPreLoadProcessEvent.class, this::onChunkPreLoadProcess); + eventRegistry.registerGlobal( + AddWorldEvent.class, event -> event.getWorld().getWorldMapManager().addMarkerProvider("warps", TeleportPlugin.WarpMarkerProvider.INSTANCE) + ); + eventRegistry.registerGlobal(AllWorldsLoadedEvent.class, event -> this.loadWarps()); + this.teleportHistoryComponentType = EntityStore.REGISTRY.registerComponent(TeleportHistory.class, TeleportHistory::new); + this.warpComponentType = EntityStore.REGISTRY.registerComponent(TeleportPlugin.WarpComponent.class, () -> { + throw new UnsupportedOperationException("WarpComponent must be created manually"); + }); + } + + @Override + protected void start() { + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset("Warp"); + if (modelAsset == null) { + throw new IllegalStateException(String.format("Default warp model '%s' not found", "Warp")); + } else { + this.warpModel = Model.createUnitScaleModel(modelAsset); + } + } + + @Override + protected void shutdown() { + } + + public void loadWarps() { + BsonDocument document = null; + Path universePath = Universe.get().getPath(); + Path oldPath = universePath.resolve("warps.bson"); + Path path = universePath.resolve("warps.json"); + if (Files.exists(oldPath) && !Files.exists(path)) { + try { + Files.move(oldPath, path); + } catch (IOException var10) { + } + } + + if (Files.exists(path)) { + document = BsonUtil.readDocument(path).join(); + } + + if (document != null) { + BsonArray bsonWarps = document.containsKey("Warps") ? document.getArray("Warps") : document.getArray("warps"); + this.warps.clear(); + + for (Warp warp : Warp.ARRAY_CODEC.decode(bsonWarps)) { + this.warps.put(warp.getId().toLowerCase(), warp); + } + + this.getLogger().at(Level.INFO).log("Loaded %d warps", bsonWarps.size()); + } else { + this.getLogger().at(Level.INFO).log("Loaded 0 warps (No warps.json found)"); + } + + this.loaded.set(true); + } + + private void saveWarps0() { + Warp[] array = this.warps.values().toArray(Warp[]::new); + BsonDocument document = new BsonDocument("Warps", Warp.ARRAY_CODEC.encode(array)); + Path path = Universe.get().getPath().resolve("warps.json"); + BsonUtil.writeDocument(path, document).join(); + this.getLogger().at(Level.INFO).log("Saved %d warps to warps.json", array.length); + } + + public void saveWarps() { + if (this.saveLock.tryLock()) { + try { + this.saveWarps0(); + } catch (Throwable var5) { + this.getLogger().at(Level.SEVERE).withCause(var5).log("Failed to save warps:"); + } finally { + this.saveLock.unlock(); + } + + if (this.postSaveRedo.getAndSet(false)) { + this.saveWarps(); + } + } else { + this.postSaveRedo.set(true); + } + } + + public Map getWarps() { + return this.warps; + } + + private void onModelAssetChange(@Nonnull LoadedAssetsEvent> event) { + Map modelMap = event.getLoadedAssets(); + ModelAsset modelAsset = modelMap.get("Warp"); + if (modelAsset != null) { + this.warpModel = Model.createUnitScaleModel(modelAsset); + } + } + + private void onChunkPreLoadProcess(@Nonnull ChunkPreLoadProcessEvent event) { + WorldChunk chunk = event.getChunk(); + BlockChunk blockChunk = chunk.getBlockChunk(); + if (blockChunk != null) { + int chunkX = blockChunk.getX(); + int chunkZ = blockChunk.getZ(); + World world = chunk.getWorld(); + String worldName = world.getName(); + + for (Entry warpEntry : this.warps.entrySet()) { + Warp warp = warpEntry.getValue(); + Transform transform = warp.getTransform(); + if (transform != null) { + Vector3d position = transform.getPosition(); + if (ChunkUtil.isInsideChunk(chunkX, chunkZ, MathUtil.floor(position.x), MathUtil.floor(position.z)) && warp.getWorld().equals(worldName)) { + world.execute(() -> { + Store store = world.getEntityStore().getStore(); + store.addEntity(this.createWarp(warp, store), AddReason.LOAD); + }); + } + } + } + } + } + + @Nonnull + public Holder createWarp(@Nonnull Warp warp, @Nonnull Store store) { + Transform transform = warp.getTransform(); + Holder holder = EntityStore.REGISTRY.newHolder(); + holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(transform.getPosition(), transform.getRotation())); + holder.addComponent(NetworkId.getComponentType(), new NetworkId(store.getExternalData().takeNextNetworkId())); + holder.ensureComponent(Intangible.getComponentType()); + holder.addComponent(BoundingBox.getComponentType(), new BoundingBox(this.warpModel.getBoundingBox())); + holder.addComponent(ModelComponent.getComponentType(), new ModelComponent(this.warpModel)); + holder.addComponent(Nameplate.getComponentType(), new Nameplate(warp.getId())); + holder.ensureComponent(HiddenFromAdventurePlayers.getComponentType()); + holder.ensureComponent(EntityStore.REGISTRY.getNonSerializedComponentType()); + holder.addComponent(this.warpComponentType, new TeleportPlugin.WarpComponent(warp)); + return holder; + } + + public record WarpComponent(Warp warp) implements Component { + public static ComponentType getComponentType() { + return TeleportPlugin.get().warpComponentType; + } + + @Nonnull + @Override + public Component clone() { + return new TeleportPlugin.WarpComponent(this.warp); + } + } + + public static class WarpMarkerProvider implements WorldMapManager.MarkerProvider { + public static final TeleportPlugin.WarpMarkerProvider INSTANCE = new TeleportPlugin.WarpMarkerProvider(); + + public WarpMarkerProvider() { + } + + @Override + public void update( + @Nonnull World world, + @Nonnull GameplayConfig gameplayConfig, + @Nonnull WorldMapTracker tracker, + int chunkViewRadius, + int playerChunkX, + int playerChunkZ + ) { + Map warps = TeleportPlugin.get().getWarps(); + if (!warps.isEmpty()) { + if (gameplayConfig.getWorldMapConfig().isDisplayWarps()) { + for (Warp warp : warps.values()) { + if (warp.getWorld().equals(world.getName())) { + tracker.trySendMarker( + chunkViewRadius, + playerChunkX, + playerChunkZ, + warp.getTransform().getPosition(), + warp.getTransform().getRotation().getYaw(), + "Warp-" + warp.getId(), + "Warp: " + warp.getId(), + warp, + (id, name, w) -> new MapMarker(id, name, "Warp.png", PositionUtil.toTransformPacket(w.getTransform()), null) + ); + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/Warp.java b/src/com/hypixel/hytale/builtin/teleport/Warp.java new file mode 100644 index 0000000..bbfb05e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/Warp.java @@ -0,0 +1,120 @@ +package com.hypixel.hytale.builtin.teleport; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.function.BsonFunctionCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import java.time.Instant; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@Deprecated +public class Warp { + public static final Codec CODEC = new BsonFunctionCodec<>( + BuilderCodec.builder(Warp.class, Warp::new) + .addField(new KeyedCodec<>("Id", Codec.STRING), (warp, s) -> warp.id = s, warp -> warp.id) + .addField(new KeyedCodec<>("World", Codec.STRING), (warp, s) -> warp.world = s, warp -> warp.world) + .addField(new KeyedCodec<>("Creator", Codec.STRING), (warp, s) -> warp.creator = s, warp -> warp.creator) + .append(new KeyedCodec<>("Date", Codec.LONG), (warp, s) -> warp.creationDate = Instant.ofEpochMilli(s), warp -> null) + .addValidator(Validators.deprecated()) + .add() + .append(new KeyedCodec<>("CreationDate", Codec.INSTANT), (o, i) -> o.creationDate = i, o -> o.creationDate) + .add() + .build(), + (warp, bsonValue) -> { + warp.transform = Transform.CODEC.decode(bsonValue); + return warp; + }, + (bsonValue, warp) -> { + bsonValue.asDocument().putAll(Transform.CODEC.encode(warp.transform).asDocument()); + return bsonValue; + } + ); + public static final ArrayCodec ARRAY_CODEC = new ArrayCodec<>(CODEC, Warp[]::new); + private String id; + private String world; + @Nullable + private Transform transform; + private String creator; + private Instant creationDate; + + public Warp() { + } + + public Warp(double locX, double locY, double locZ, float yaw, float pitch, float roll, String id, @Nonnull World world, String creator, Instant creationDate) { + this.id = id; + this.world = world.getName(); + this.transform = new Transform(locX, locY, locZ, pitch, yaw, roll); + this.creator = creator; + this.creationDate = creationDate; + } + + public String getId() { + return this.id; + } + + public String getWorld() { + return this.world; + } + + @Nullable + public Transform getTransform() { + return this.transform; + } + + public String getCreator() { + return this.creator; + } + + public Instant getCreationDate() { + return this.creationDate; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Warp warp = (Warp)o; + if (!Objects.equals(this.id, warp.id)) { + return false; + } else if (!Objects.equals(this.world, warp.world)) { + return false; + } else if (!Objects.equals(this.transform, warp.transform)) { + return false; + } else { + return !Objects.equals(this.creator, warp.creator) ? false : Objects.equals(this.creationDate, warp.creationDate); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.id != null ? this.id.hashCode() : 0; + result = 31 * result + (this.world != null ? this.world.hashCode() : 0); + result = 31 * result + (this.transform != null ? this.transform.hashCode() : 0); + result = 31 * result + (this.creator != null ? this.creator.hashCode() : 0); + return 31 * result + (this.creationDate != null ? this.creationDate.hashCode() : 0); + } + + @Nonnull + @Override + public String toString() { + return "Warp{id='" + this.id + "', transform=" + this.transform + ", creator='" + this.creator + "', creationDate=" + this.creationDate + "}"; + } + + @Nullable + public Teleport toTeleport() { + World worldInstance = Universe.get().getWorld(this.world); + return worldInstance == null ? null : new Teleport(worldInstance, this.transform); + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/WarpListPage.java b/src/com/hypixel/hytale/builtin/teleport/WarpListPage.java new file mode 100644 index 0000000..bec61a7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/WarpListPage.java @@ -0,0 +1,114 @@ +package com.hypixel.hytale.builtin.teleport; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.Map; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +public class WarpListPage extends InteractiveCustomUIPage { + @Nonnull + private static final String PAGE_UI_FILE = "Pages/WarpEntryButton.ui"; + private final Consumer callback; + private final Map warps; + @Nonnull + private String searchQuery = ""; + + public WarpListPage(@Nonnull PlayerRef playerRef, Map warps, Consumer callback) { + super(playerRef, CustomPageLifetime.CanDismiss, WarpListPage.WarpListPageEventData.CODEC); + this.warps = warps; + this.callback = callback; + } + + private void buildWarpList(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) { + commandBuilder.clear("#WarpList"); + ObjectArrayList warps = new ObjectArrayList<>(this.warps.keySet()); + if (warps.isEmpty()) { + commandBuilder.appendInline("#WarpList", "Label { Text: %server.customUI.warpListPage.noWarps; Style: (Alignment: Center); }"); + } else { + if (!this.searchQuery.isEmpty()) { + warps.removeIf(w -> !w.toLowerCase().contains(this.searchQuery)); + } + + Collections.sort(warps); + int i = 0; + + for (int bound = warps.size(); i < bound; i++) { + String selector = "#WarpList[" + i + "]"; + String warp = warps.get(i); + commandBuilder.append("#WarpList", "Pages/WarpEntryButton.ui"); + commandBuilder.set(selector + " #Name.Text", warp); + commandBuilder.set(selector + " #World.Text", this.warps.get(warp).getWorld()); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, selector, EventData.of("Warp", warp), false); + } + } + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/WarpListPage.ui"); + eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#SearchInput", EventData.of("@SearchQuery", "#SearchInput.Value")); + this.buildWarpList(commandBuilder, eventBuilder); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull WarpListPage.WarpListPageEventData eventData) { + if (eventData.getWarp() != null) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getPageManager().setPage(ref, store, Page.None); + this.callback.accept(eventData.getWarp()); + } else if (eventData.getSearchQuery() != null) { + this.searchQuery = eventData.getSearchQuery().trim().toLowerCase(); + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.buildWarpList(commandBuilder, eventBuilder); + this.sendUpdate(commandBuilder, eventBuilder, false); + } + } + + public static class WarpListPageEventData { + static final String KEY_WARP = "Warp"; + static final String KEY_SEARCH_QUERY = "@SearchQuery"; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + WarpListPage.WarpListPageEventData.class, WarpListPage.WarpListPageEventData::new + ) + .append(new KeyedCodec<>("Warp", Codec.STRING), (entry, s) -> entry.warp = s, entry -> entry.warp) + .add() + .append(new KeyedCodec<>("@SearchQuery", Codec.STRING), (entry, s) -> entry.searchQuery = s, entry -> entry.searchQuery) + .add() + .build(); + private String warp; + private String searchQuery; + + public WarpListPageEventData() { + } + + public String getWarp() { + return this.warp; + } + + public String getSearchQuery() { + return this.searchQuery; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/teleport/SpawnCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/SpawnCommand.java new file mode 100644 index 0000000..02ef919 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/SpawnCommand.java @@ -0,0 +1,152 @@ +package com.hypixel.hytale.builtin.teleport.commands.teleport; + +import com.hypixel.hytale.builtin.teleport.components.TeleportHistory; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SpawnCommand extends AbstractPlayerCommand { + @Nonnull + private final OptionalArg spawnIndexArg = this.withOptionalArg("spawnIndex", "server.commands.spawn.index.desc", ArgTypes.INTEGER); + + public SpawnCommand() { + super("spawn", "server.commands.spawn.desc"); + this.requirePermission(HytalePermissions.fromCommand("spawn.self")); + this.addUsageVariant(new SpawnCommand.SpawnOtherCommand()); + this.addSubCommand(new SpawnSetCommand()); + this.addSubCommand(new SpawnSetDefaultCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Transform spawn = resolveSpawn(context, world, playerRef, this.spawnIndexArg); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3f previousBodyRotation = transformComponent.getRotation().clone(); + Vector3d previousPos = transformComponent.getPosition().clone(); + Vector3f previousRotation = headRotationComponent.getRotation().clone(); + TeleportHistory teleportHistoryComponent = store.ensureAndGetComponent(ref, TeleportHistory.getComponentType()); + teleportHistoryComponent.append(world, previousPos, previousRotation, "World " + world.getName() + "'s spawn"); + Vector3f spawnRotation = spawn.getRotation().clone(); + spawn.setRotation(new Vector3f(previousBodyRotation.getPitch(), spawnRotation.getYaw(), previousBodyRotation.getRoll())); + Teleport teleport = new Teleport(world, spawn).withHeadRotation(spawnRotation); + store.addComponent(ref, Teleport.getComponentType(), teleport); + Vector3d position = spawn.getPosition(); + context.sendMessage( + Message.translation("server.commands.spawn.teleported").param("x", position.getX()).param("y", position.getY()).param("z", position.getZ()) + ); + } + + private static Transform resolveSpawn( + @Nonnull CommandContext context, @Nonnull World world, @Nonnull PlayerRef playerRef, @Nonnull OptionalArg spawnIndexArg + ) { + ISpawnProvider spawnProvider = world.getWorldConfig().getSpawnProvider(); + if (spawnIndexArg.provided(context)) { + int spawnIndex = spawnIndexArg.get(context); + Transform[] spawnPoints = spawnProvider.getSpawnPoints(); + if (spawnIndex >= 0 && spawnIndex < spawnPoints.length) { + return spawnPoints[spawnIndex]; + } else { + int maxIndex = spawnPoints.length - 1; + context.sendMessage(Message.translation("server.commands.spawn.indexNotFound").param("maxIndex", maxIndex)); + throw new GeneralCommandException( + Message.translation("server.commands.errors.spawnIndexOutOfRange").param("index", spawnIndex).param("maxIndex", maxIndex) + ); + } + } else { + return spawnProvider.getSpawnPoint(world, playerRef.getUuid()); + } + } + + private static class SpawnOtherCommand extends CommandBase { + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + @Nonnull + private final OptionalArg spawnIndexArg = this.withOptionalArg("spawnIndex", "server.commands.spawn.index.desc", ArgTypes.INTEGER); + + SpawnOtherCommand() { + super("server.commands.spawn.other.desc"); + this.requirePermission(HytalePermissions.fromCommand("spawn.other")); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef targetPlayerRef = this.playerArg.get(context); + Ref ref = targetPlayerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + Transform spawn = SpawnCommand.resolveSpawn(context, world, targetPlayerRef, this.spawnIndexArg); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3f previousBodyRotation = transformComponent.getRotation().clone(); + Vector3d previousPos = transformComponent.getPosition().clone(); + Vector3f previousRotation = headRotationComponent.getRotation().clone(); + TeleportHistory teleportHistoryComponent = store.ensureAndGetComponent(ref, TeleportHistory.getComponentType()); + teleportHistoryComponent.append(world, previousPos, previousRotation, "World " + world.getName() + "'s spawn"); + Vector3f spawnRotation = spawn.getRotation().clone(); + spawn.setRotation(new Vector3f(previousBodyRotation.getPitch(), spawnRotation.getYaw(), previousBodyRotation.getRoll())); + Teleport teleport = new Teleport(world, spawn).withHeadRotation(spawnRotation); + store.addComponent(ref, Teleport.getComponentType(), teleport); + Vector3d position = spawn.getPosition(); + context.sendMessage( + Message.translation("server.commands.spawn.teleportedOther") + .param("username", targetPlayerRef.getUsername()) + .param("x", position.getX()) + .param("y", position.getY()) + .param("z", position.getZ()) + ); + } + } + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/teleport/SpawnSetCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/SpawnSetCommand.java new file mode 100644 index 0000000..32f8857 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/SpawnSetCommand.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.builtin.teleport.commands.teleport; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeDoublePosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.spawn.GlobalSpawnProvider; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.text.DecimalFormat; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class SpawnSetCommand extends AbstractWorldCommand { + @Nonnull + private static final DecimalFormat DECIMAL = new DecimalFormat("#.###"); + @Nonnull + private static final Message MESSAGE_COMMANDS_ERROR_PROVIDE_POSITION = Message.translation("server.commands.errors.providePosition"); + @Nonnull + private final OptionalArg positionArg = this.withOptionalArg( + "position", "server.commands.spawn.set.position.desc", ArgTypes.RELATIVE_POSITION + ); + @Nonnull + private final DefaultArg rotationArg = this.withDefaultArg( + "rotation", "server.commands.spawn.set.rotation.desc", ArgTypes.ROTATION, Vector3f.FORWARD, "server.commands.spawn.set.rotation.default.desc" + ); + + public SpawnSetCommand() { + super("set", "server.commands.spawn.set.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Vector3d position; + if (this.positionArg.provided(context)) { + RelativeDoublePosition relativePosition = this.positionArg.get(context); + position = relativePosition.getRelativePosition(context, world, store); + } else { + if (!context.isPlayer()) { + throw new GeneralCommandException(MESSAGE_COMMANDS_ERROR_PROVIDE_POSITION); + } + + Ref playerRef = context.senderAsPlayerRef(); + if (playerRef == null || !playerRef.isValid()) { + throw new GeneralCommandException(MESSAGE_COMMANDS_ERROR_PROVIDE_POSITION); + } + + TransformComponent transformComponent = store.getComponent(playerRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + position = transformComponent.getPosition().clone(); + } + + Vector3f rotation; + if (this.rotationArg.provided(context)) { + rotation = this.rotationArg.get(context); + } else if (context.isPlayer()) { + Ref playerRefx = context.senderAsPlayerRef(); + if (playerRefx != null && playerRefx.isValid()) { + HeadRotation headRotationComponent = store.getComponent(playerRefx, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + rotation = headRotationComponent.getRotation(); + } else { + rotation = this.rotationArg.get(context); + } + } else { + rotation = this.rotationArg.get(context); + } + + Transform transform = new Transform(position, rotation); + WorldConfig worldConfig = world.getWorldConfig(); + worldConfig.setSpawnProvider(new GlobalSpawnProvider(transform)); + worldConfig.markChanged(); + world.getLogger().at(Level.INFO).log("Set spawn provider to: %s", worldConfig.getSpawnProvider()); + context.sendMessage( + Message.translation("server.universe.setspawn.info") + .param("posX", DECIMAL.format(position.getX())) + .param("posY", DECIMAL.format(position.getY())) + .param("posZ", DECIMAL.format(position.getZ())) + .param("rotX", DECIMAL.format(rotation.getX())) + .param("rotY", DECIMAL.format(rotation.getY())) + .param("rotZ", DECIMAL.format(rotation.getZ())) + ); + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/teleport/SpawnSetDefaultCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/SpawnSetDefaultCommand.java new file mode 100644 index 0000000..8dc2a21 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/SpawnSetDefaultCommand.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.builtin.teleport.commands.teleport; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class SpawnSetDefaultCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_UNIVERSE_SET_SPAWN_DEFAULT = Message.translation("server.universe.setspawn.default"); + + public SpawnSetDefaultCommand() { + super("default", "server.commands.spawn.set.default.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + world.getWorldConfig().setSpawnProvider(null); + world.getLogger().at(Level.INFO).log("Set spawn provider to: %s", world.getWorldConfig().getSpawnProvider()); + context.sendMessage(MESSAGE_UNIVERSE_SET_SPAWN_DEFAULT); + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportAllCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportAllCommand.java new file mode 100644 index 0000000..471733e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportAllCommand.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.builtin.teleport.commands.teleport; + +import com.hypixel.hytale.builtin.teleport.components.TeleportHistory; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.Coord; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeFloat; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.NotificationUtil; +import javax.annotation.Nonnull; + +public class TeleportAllCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_OR_ARG = Message.translation("server.commands.errors.playerOrArg"); + @Nonnull + private static final Message MESSAGE_COMMANDS_TELEPORT_TELEPORTED_WITH_LOOK_NOTIFICATION = Message.translation( + "server.commands.teleport.teleportedWithLookNotification" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_TELEPORT_TELEPORTED_TO_COORDINATES_NOTIFICATION = Message.translation( + "server.commands.teleport.teleportedToCoordinatesNotification" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_TELEPORT_TELEPORT_EVERYONE_WITH_LOOK = Message.translation("server.commands.teleport.teleportEveryoneWithLook"); + @Nonnull + private static final Message MESSAGE_COMMANDS_TELEPORT_TELEPORT_EVERYONE = Message.translation("server.commands.teleport.teleportEveryone"); + @Nonnull + private final RequiredArg xArg = this.withRequiredArg("x", "server.commands.teleport.x.desc", ArgTypes.RELATIVE_DOUBLE_COORD); + @Nonnull + private final RequiredArg yArg = this.withRequiredArg("y", "server.commands.teleport.y.desc", ArgTypes.RELATIVE_DOUBLE_COORD); + @Nonnull + private final RequiredArg zArg = this.withRequiredArg("z", "server.commands.teleport.z.desc", ArgTypes.RELATIVE_DOUBLE_COORD); + @Nonnull + private final OptionalArg yawArg = this.withOptionalArg("yaw", "server.commands.teleport.yaw.desc", ArgTypes.RELATIVE_FLOAT); + @Nonnull + private final OptionalArg pitchArg = this.withOptionalArg("pitch", "server.commands.teleport.pitch.desc", ArgTypes.RELATIVE_FLOAT); + @Nonnull + private final OptionalArg rollArg = this.withOptionalArg("roll", "server.commands.teleport.roll.desc", ArgTypes.RELATIVE_FLOAT); + @Nonnull + private final OptionalArg worldArg = this.withOptionalArg("world", "server.commands.worldthread.arg.desc", ArgTypes.WORLD); + + public TeleportAllCommand() { + super("all", "server.commands.tpall.desc"); + this.setPermissionGroup(null); + this.requirePermission(HytalePermissions.fromCommand("teleport.all")); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + Coord relX = this.xArg.get(context); + Coord relY = this.yArg.get(context); + Coord relZ = this.zArg.get(context); + World targetWorld; + if (this.worldArg.provided(context)) { + targetWorld = this.worldArg.get(context); + } else { + if (!context.isPlayer()) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_OR_ARG.param("option", "world")); + return; + } + + Ref senderRef = context.senderAsPlayerRef(); + if (senderRef == null || !senderRef.isValid()) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + return; + } + + targetWorld = senderRef.getStore().getExternalData().getWorld(); + } + + targetWorld.execute( + () -> { + Store store = targetWorld.getEntityStore().getStore(); + double baseX = 0.0; + double baseY = 0.0; + double baseZ = 0.0; + if (context.isPlayer()) { + Ref senderRefx = context.senderAsPlayerRef(); + if (senderRefx != null && senderRefx.isValid()) { + Store senderStore = senderRefx.getStore(); + World senderWorld = senderStore.getExternalData().getWorld(); + if (senderWorld == targetWorld) { + TransformComponent transformComponent = senderStore.getComponent(senderRefx, TransformComponent.getComponentType()); + if (transformComponent != null) { + Vector3d pos = transformComponent.getPosition(); + baseX = pos.getX(); + baseY = pos.getY(); + baseZ = pos.getZ(); + } + } + } + } + + double x = relX.resolveXZ(baseX); + double z = relZ.resolveXZ(baseZ); + double y = relY.resolveYAtWorldCoords(baseY, targetWorld, x, z); + boolean hasRotation = this.yawArg.provided(context) || this.pitchArg.provided(context) || this.rollArg.provided(context); + + for (PlayerRef playerRef : targetWorld.getPlayerRefs()) { + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + if (transformComponent != null && headRotationComponent != null) { + Vector3d previousPos = transformComponent.getPosition().clone(); + Vector3f previousHeadRotation = headRotationComponent.getRotation().clone(); + Vector3f previousBodyRotation = transformComponent.getRotation().clone(); + float yaw = this.yawArg.provided(context) + ? this.yawArg.get(context).resolve(previousHeadRotation.getYaw() * (180.0F / (float)Math.PI)) * (float) (Math.PI / 180.0) + : Float.NaN; + float pitch = this.pitchArg.provided(context) + ? this.pitchArg.get(context).resolve(previousHeadRotation.getPitch() * (180.0F / (float)Math.PI)) * (float) (Math.PI / 180.0) + : Float.NaN; + float roll = this.rollArg.provided(context) + ? this.rollArg.get(context).resolve(previousHeadRotation.getRoll() * (180.0F / (float)Math.PI)) * (float) (Math.PI / 180.0) + : Float.NaN; + Teleport teleport = new Teleport(new Vector3d(x, y, z), new Vector3f(previousBodyRotation.getPitch(), yaw, previousBodyRotation.getRoll())) + .withHeadRotation(new Vector3f(pitch, yaw, roll)); + store.addComponent(ref, Teleport.getComponentType(), teleport); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + TeleportHistory teleportHistoryComponent = store.ensureAndGetComponent(ref, TeleportHistory.getComponentType()); + teleportHistoryComponent.append( + targetWorld, + previousPos, + previousHeadRotation, + String.format("Teleport to (%s, %s, %s) by %s", x, y, z, context.sender().getDisplayName()) + ); + if (hasRotation) { + float displayYaw = Float.isNaN(yaw) ? previousHeadRotation.getYaw() * (180.0F / (float)Math.PI) : yaw * (180.0F / (float)Math.PI); + float displayPitch = Float.isNaN(pitch) + ? previousHeadRotation.getPitch() * (180.0F / (float)Math.PI) + : pitch * (180.0F / (float)Math.PI); + float displayRoll = Float.isNaN(roll) + ? previousHeadRotation.getRoll() * (180.0F / (float)Math.PI) + : roll * (180.0F / (float)Math.PI); + NotificationUtil.sendNotification( + playerRefComponent.getPacketHandler(), + MESSAGE_COMMANDS_TELEPORT_TELEPORTED_WITH_LOOK_NOTIFICATION.param("x", x) + .param("y", y) + .param("z", z) + .param("yaw", displayYaw) + .param("pitch", displayPitch) + .param("roll", displayRoll) + .param("sender", context.sender().getDisplayName()), + null, + "teleportation" + ); + } else { + NotificationUtil.sendNotification( + playerRefComponent.getPacketHandler(), + MESSAGE_COMMANDS_TELEPORT_TELEPORTED_TO_COORDINATES_NOTIFICATION.param("x", x) + .param("y", y) + .param("z", z) + .param("sender", context.sender().getDisplayName()), + null, + "teleportation" + ); + } + } + } + } + } + + if (hasRotation) { + float displayYaw = this.yawArg.provided(context) ? this.yawArg.get(context).getRawValue() : 0.0F; + float displayPitch = this.pitchArg.provided(context) ? this.pitchArg.get(context).getRawValue() : 0.0F; + float displayRoll = this.rollArg.provided(context) ? this.rollArg.get(context).getRawValue() : 0.0F; + context.sendMessage( + MESSAGE_COMMANDS_TELEPORT_TELEPORT_EVERYONE_WITH_LOOK.param("world", targetWorld.getName()) + .param("x", x) + .param("y", y) + .param("z", z) + .param("yaw", displayYaw) + .param("pitch", displayPitch) + .param("roll", displayRoll) + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_TELEPORT_EVERYONE.param("world", targetWorld.getName()).param("x", x).param("y", y).param("z", z)); + } + } + ); + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportBackCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportBackCommand.java new file mode 100644 index 0000000..f2d5049 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportBackCommand.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.teleport.commands.teleport; + +import com.hypixel.hytale.builtin.teleport.components.TeleportHistory; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class TeleportBackCommand extends AbstractPlayerCommand { + @Nonnull + private final OptionalArg countArg = this.withOptionalArg("count", "server.commands.teleport.back.count.desc", ArgTypes.INTEGER); + + public TeleportBackCommand() { + super("back", "server.commands.teleport.recent.desc"); + this.requirePermission(HytalePermissions.fromCommand("teleport.back")); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + int counter = this.countArg.provided(context) ? this.countArg.get(context) : 1; + TeleportHistory history = store.ensureAndGetComponent(ref, TeleportHistory.getComponentType()); + history.back(ref, counter); + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportCommand.java new file mode 100644 index 0000000..ca19b86 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportCommand.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.builtin.teleport.commands.teleport; + +import com.hypixel.hytale.builtin.teleport.commands.teleport.variant.TeleportOtherToPlayerCommand; +import com.hypixel.hytale.builtin.teleport.commands.teleport.variant.TeleportPlayerToCoordinatesCommand; +import com.hypixel.hytale.builtin.teleport.commands.teleport.variant.TeleportToCoordinatesCommand; +import com.hypixel.hytale.builtin.teleport.commands.teleport.variant.TeleportToPlayerCommand; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class TeleportCommand extends AbstractCommandCollection { + public TeleportCommand() { + super("tp", "server.commands.tp.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addAliases("teleport"); + this.addUsageVariant(new TeleportToCoordinatesCommand()); + this.addUsageVariant(new TeleportPlayerToCoordinatesCommand()); + this.addUsageVariant(new TeleportToPlayerCommand()); + this.addUsageVariant(new TeleportOtherToPlayerCommand()); + this.addSubCommand(new TeleportAllCommand()); + this.addSubCommand(new TeleportHomeCommand()); + this.addSubCommand(new TeleportTopCommand()); + this.addSubCommand(new TeleportBackCommand()); + this.addSubCommand(new TeleportForwardCommand()); + this.addSubCommand(new TeleportHistoryCommand()); + this.addSubCommand(new TeleportWorldCommand()); + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportForwardCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportForwardCommand.java new file mode 100644 index 0000000..052ca5d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportForwardCommand.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.builtin.teleport.commands.teleport; + +import com.hypixel.hytale.builtin.teleport.components.TeleportHistory; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class TeleportForwardCommand extends AbstractPlayerCommand { + @Nonnull + private final OptionalArg countArg = this.withOptionalArg("count", "server.commands.teleport.forward.count.desc", ArgTypes.INTEGER); + + public TeleportForwardCommand() { + super("forward", "server.commands.teleport.next.desc"); + this.requirePermission(HytalePermissions.fromCommand("teleport.forward")); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + int counter = this.countArg.provided(context) ? this.countArg.get(context) : 1; + TeleportHistory history = store.ensureAndGetComponent(ref, TeleportHistory.getComponentType()); + history.forward(ref, counter); + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportHistoryCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportHistoryCommand.java new file mode 100644 index 0000000..d918423 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportHistoryCommand.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.builtin.teleport.commands.teleport; + +import com.hypixel.hytale.builtin.teleport.TeleportPlugin; +import com.hypixel.hytale.builtin.teleport.components.TeleportHistory; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class TeleportHistoryCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_TELEPORT_HISTORY_EMPTY = Message.translation("server.commands.teleport.history.empty"); + @Nonnull + private static final Message MESSAGE_COMMANDS_TELEPORT_HISTORY_INFO = Message.translation("server.commands.teleport.history.info"); + + public TeleportHistoryCommand() { + super("history", "server.commands.teleport.dump.desc"); + this.requirePermission(HytalePermissions.fromCommand("teleport.history")); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + TeleportHistory history = store.ensureAndGetComponent(ref, TeleportHistory.getComponentType()); + TeleportPlugin.get().getLogger().at(Level.INFO).log("Got history for player %s: %s", playerRefComponent.getUsername(), history); + int backSize = history.getBackSize(); + int forwardSize = history.getForwardSize(); + if (backSize == 0 && forwardSize == 0) { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_HISTORY_EMPTY); + } else { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_HISTORY_INFO.param("backCount", backSize).param("forwardCount", forwardSize)); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportHomeCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportHomeCommand.java new file mode 100644 index 0000000..02d5224 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportHomeCommand.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.teleport.commands.teleport; + +import com.hypixel.hytale.builtin.teleport.components.TeleportHistory; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class TeleportHomeCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_TELEPORT_TELEPORTED_SELF_HOME = Message.translation("server.commands.teleport.teleportedSelfHome"); + + public TeleportHomeCommand() { + super("home", "server.commands.home.desc"); + this.requirePermission(HytalePermissions.fromCommand("teleport.home")); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3d position = transformComponent.getPosition().clone(); + Vector3f headRotation = headRotationComponent.getRotation().clone(); + TeleportHistory teleportHistoryComponent = store.ensureAndGetComponent(ref, TeleportHistory.getComponentType()); + teleportHistoryComponent.append(world, position, headRotation, "Home"); + Transform homeTransform = Player.getRespawnPosition(ref, world.getName(), store); + store.addComponent(ref, Teleport.getComponentType(), new Teleport(null, homeTransform)); + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_TELEPORTED_SELF_HOME); + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportTopCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportTopCommand.java new file mode 100644 index 0000000..e5801cc --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportTopCommand.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.teleport.commands.teleport; + +import com.hypixel.hytale.builtin.teleport.components.TeleportHistory; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class TeleportTopCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_TELEPORT_TOP_CHUNK_NOT_LOADED_AT_POS = Message.translation("server.commands.teleport.top.chunkNotLoadedAtPos"); + @Nonnull + private static final Message MESSAGE_COMMANDS_TELEPORT_TELEPORTED_TO_TOP = Message.translation("server.commands.teleport.teleportedToTop"); + private static final String TELEPORT_HISTORY_KEY = "Underground"; + + public TeleportTopCommand() { + super("top", "server.commands.top.desc"); + this.requirePermission(HytalePermissions.fromCommand("teleport.top")); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + WorldChunk worldChunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(position.getX(), position.getZ())); + if (worldChunk == null) { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_TOP_CHUNK_NOT_LOADED_AT_POS); + } else { + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3f headRotation = headRotationComponent.getRotation().clone(); + int height = worldChunk.getHeight(MathUtil.floor(position.getX()), MathUtil.floor(position.getZ())); + store.ensureAndGetComponent(ref, TeleportHistory.getComponentType()).append(world, position.clone(), headRotation.clone(), "Underground"); + store.addComponent(ref, Teleport.getComponentType(), new Teleport(new Vector3d(position.getX(), height + 2, position.getZ()), Vector3f.NaN)); + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_TELEPORTED_TO_TOP); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportWorldCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportWorldCommand.java new file mode 100644 index 0000000..a7e5b39 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/TeleportWorldCommand.java @@ -0,0 +1,71 @@ +package com.hypixel.hytale.builtin.teleport.commands.teleport; + +import com.hypixel.hytale.builtin.teleport.components.TeleportHistory; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class TeleportWorldCommand extends AbstractPlayerCommand { + private static final Message MESSAGE_WORLD_NOT_FOUND = Message.translation("server.world.notFound"); + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + private static final Message MESSAGE_WORLD_SPAWN_NOT_SET = Message.translation("server.world.spawn.notSet"); + private static final Message MESSAGE_COMMANDS_TELEPORT_TELEPORTED_TO_WORLD = Message.translation("server.commands.teleport.teleportedToWorld"); + @Nonnull + private final RequiredArg worldNameArg = this.withRequiredArg("worldName", "server.commands.worldport.worldName.desc", ArgTypes.STRING); + + public TeleportWorldCommand() { + super("world", "server.commands.worldport.desc"); + this.setPermissionGroup(null); + this.requirePermission(HytalePermissions.fromCommand("teleport.world")); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + String worldName = this.worldNameArg.get(context); + World targetWorld = Universe.get().getWorld(worldName); + if (targetWorld == null) { + context.sendMessage(MESSAGE_WORLD_NOT_FOUND.param("worldName", worldName)); + } else { + Transform spawnPoint = targetWorld.getWorldConfig().getSpawnProvider().getSpawnPoint(ref, store); + if (spawnPoint == null) { + context.sendMessage(MESSAGE_WORLD_SPAWN_NOT_SET.param("worldName", worldName)); + } else { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + if (transformComponent != null && headRotationComponent != null) { + Vector3d previousPos = transformComponent.getPosition().clone(); + Vector3f previousRotation = headRotationComponent.getRotation().clone(); + TeleportHistory teleportHistoryComponent = store.ensureAndGetComponent(ref, TeleportHistory.getComponentType()); + teleportHistoryComponent.append(world, previousPos, previousRotation, "World " + targetWorld.getName()); + } + + store.addComponent(ref, Teleport.getComponentType(), new Teleport(targetWorld, spawnPoint)); + Vector3d spawnPos = spawnPoint.getPosition(); + context.sendMessage( + MESSAGE_COMMANDS_TELEPORT_TELEPORTED_TO_WORLD.param("worldName", worldName) + .param("x", spawnPos.getX()) + .param("y", spawnPos.getY()) + .param("z", spawnPos.getZ()) + ); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/teleport/variant/TeleportOtherToPlayerCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/variant/TeleportOtherToPlayerCommand.java new file mode 100644 index 0000000..fc8d1cf --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/variant/TeleportOtherToPlayerCommand.java @@ -0,0 +1,113 @@ +package com.hypixel.hytale.builtin.teleport.commands.teleport.variant; + +import com.hypixel.hytale.builtin.teleport.components.TeleportHistory; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class TeleportOtherToPlayerCommand extends CommandBase { + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + private static final Message MESSAGE_COMMANDS_ERRORS_TARGET_NOT_IN_WORLD = Message.translation("server.commands.errors.targetNotInWorld"); + private static final Message MESSAGE_COMMANDS_TELEPORT_TELEPORTED_OTHER_TO_PLAYER = Message.translation("server.commands.teleport.teleportedOtherToPlayer"); + @Nonnull + private final RequiredArg targetPlayerArg = this.withRequiredArg( + "targetPlayer", "server.commands.teleport.targetPlayer.desc", ArgTypes.PLAYER_REF + ); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + + public TeleportOtherToPlayerCommand() { + super("server.commands.teleport.otherToPlayer.desc"); + this.requirePermission(HytalePermissions.fromCommand("teleport.other")); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef playerToTpRef = this.playerArg.get(context); + Ref sourceRef = playerToTpRef.getReference(); + if (sourceRef != null && sourceRef.isValid()) { + PlayerRef targetPlayerRef = this.targetPlayerArg.get(context); + Ref targetRef = targetPlayerRef.getReference(); + if (targetRef != null && targetRef.isValid()) { + Store sourceStore = sourceRef.getStore(); + World sourceWorld = sourceStore.getExternalData().getWorld(); + Store targetStore = targetRef.getStore(); + World targetWorld = targetStore.getExternalData().getWorld(); + sourceWorld.execute( + () -> { + TransformComponent transformComponent = sourceStore.getComponent(sourceRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = sourceStore.getComponent(sourceRef, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3d pos = transformComponent.getPosition().clone(); + Vector3f rotation = headRotationComponent.getRotation().clone(); + targetWorld.execute( + () -> { + TransformComponent targetTransformComponent = targetStore.getComponent(targetRef, TransformComponent.getComponentType()); + + assert targetTransformComponent != null; + + HeadRotation targetHeadRotationComponent = targetStore.getComponent(targetRef, HeadRotation.getComponentType()); + + assert targetHeadRotationComponent != null; + + Vector3d targetPosition = targetTransformComponent.getPosition().clone(); + Vector3f targetRotation = targetTransformComponent.getRotation().clone(); + Vector3f targetHeadRotation = targetHeadRotationComponent.getRotation().clone(); + sourceWorld.execute( + () -> { + Teleport teleport = new Teleport(targetWorld, targetPosition, targetRotation) + .withHeadRotation(targetHeadRotation) + .withResetRoll(); + sourceStore.addComponent(sourceRef, Teleport.getComponentType(), teleport); + PlayerRef sourcePlayerRefComponent = sourceStore.getComponent(sourceRef, PlayerRef.getComponentType()); + + assert sourcePlayerRefComponent != null; + + PlayerRef targetPlayerRefComponent = targetStore.getComponent(targetRef, PlayerRef.getComponentType()); + + assert targetPlayerRefComponent != null; + + context.sendMessage( + MESSAGE_COMMANDS_TELEPORT_TELEPORTED_OTHER_TO_PLAYER.param("targetName", sourcePlayerRefComponent.getUsername()) + .param("toName", targetPlayerRefComponent.getUsername()) + ); + sourceStore.ensureAndGetComponent(sourceRef, TeleportHistory.getComponentType()) + .append( + sourceWorld, + pos, + rotation, + "Teleport to " + targetPlayerRefComponent.getUsername() + " by " + context.sender().getDisplayName() + ); + } + ); + } + ); + } + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_TARGET_NOT_IN_WORLD); + } + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/teleport/variant/TeleportPlayerToCoordinatesCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/variant/TeleportPlayerToCoordinatesCommand.java new file mode 100644 index 0000000..fc39c22 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/variant/TeleportPlayerToCoordinatesCommand.java @@ -0,0 +1,121 @@ +package com.hypixel.hytale.builtin.teleport.commands.teleport.variant; + +import com.hypixel.hytale.builtin.teleport.components.TeleportHistory; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.Coord; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeFloat; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class TeleportPlayerToCoordinatesCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private static final Message MESSAGE_COMMANDS_TELEPORT_TELEPORTED_TO_COORDINATES_WITH_LOOK = Message.translation( + "server.commands.teleport.teleportedToCoordinatesWithLook" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_TELEPORT_TELEPORTED_TO_COORDINATES = Message.translation("server.commands.teleport.teleportedToCoordinates"); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.teleport.targetPlayer.desc", ArgTypes.PLAYER_REF); + @Nonnull + private final RequiredArg xArg = this.withRequiredArg("x", "server.commands.teleport.x.desc", ArgTypes.RELATIVE_DOUBLE_COORD); + @Nonnull + private final RequiredArg yArg = this.withRequiredArg("y", "server.commands.teleport.y.desc", ArgTypes.RELATIVE_DOUBLE_COORD); + @Nonnull + private final RequiredArg zArg = this.withRequiredArg("z", "server.commands.teleport.z.desc", ArgTypes.RELATIVE_DOUBLE_COORD); + @Nonnull + private final OptionalArg yawArg = this.withOptionalArg("yaw", "server.commands.teleport.yaw.desc", ArgTypes.RELATIVE_FLOAT); + @Nonnull + private final OptionalArg pitchArg = this.withOptionalArg("pitch", "server.commands.teleport.pitch.desc", ArgTypes.RELATIVE_FLOAT); + @Nonnull + private final OptionalArg rollArg = this.withOptionalArg("roll", "server.commands.teleport.roll.desc", ArgTypes.RELATIVE_FLOAT); + + public TeleportPlayerToCoordinatesCommand() { + super("server.commands.teleport.toCoordinates.desc"); + this.requirePermission(HytalePermissions.fromCommand("teleport.other")); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef targetPlayerRef = this.playerArg.get(context); + Ref ref = targetPlayerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World targetWorld = store.getExternalData().getWorld(); + targetWorld.execute( + () -> { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3d previousPos = transformComponent.getPosition().clone(); + Vector3f previousHeadRotation = headRotationComponent.getRotation().clone(); + Vector3f previousBodyRotation = transformComponent.getRotation().clone(); + Coord relX = this.xArg.get(context); + Coord relY = this.yArg.get(context); + Coord relZ = this.zArg.get(context); + double x = relX.resolveXZ(previousPos.getX()); + double z = relZ.resolveXZ(previousPos.getZ()); + double y = relY.resolveYAtWorldCoords(previousPos.getY(), targetWorld, x, z); + float yaw = this.yawArg.provided(context) + ? this.yawArg.get(context).resolve(previousHeadRotation.getYaw() * (180.0F / (float)Math.PI)) * (float) (Math.PI / 180.0) + : Float.NaN; + float pitch = this.pitchArg.provided(context) + ? this.pitchArg.get(context).resolve(previousHeadRotation.getPitch() * (180.0F / (float)Math.PI)) * (float) (Math.PI / 180.0) + : Float.NaN; + float roll = this.rollArg.provided(context) + ? this.rollArg.get(context).resolve(previousHeadRotation.getRoll() * (180.0F / (float)Math.PI)) * (float) (Math.PI / 180.0) + : Float.NaN; + Teleport teleport = new Teleport(new Vector3d(x, y, z), new Vector3f(previousBodyRotation.getPitch(), yaw, previousBodyRotation.getRoll())) + .withHeadRotation(new Vector3f(pitch, yaw, roll)); + store.addComponent(ref, Teleport.getComponentType(), teleport); + Player player = store.getComponent(ref, Player.getComponentType()); + if (player != null) { + store.ensureAndGetComponent(ref, TeleportHistory.getComponentType()) + .append(targetWorld, previousPos, previousHeadRotation, String.format("Teleport to (%s, %s, %s)", x, y, z)); + } + + boolean hasRotation = this.yawArg.provided(context) || this.pitchArg.provided(context) || this.rollArg.provided(context); + if (hasRotation) { + float displayYaw = Float.isNaN(yaw) ? previousHeadRotation.getYaw() * (180.0F / (float)Math.PI) : yaw * (180.0F / (float)Math.PI); + float displayPitch = Float.isNaN(pitch) ? previousHeadRotation.getPitch() * (180.0F / (float)Math.PI) : pitch * (180.0F / (float)Math.PI); + float displayRoll = Float.isNaN(roll) ? previousHeadRotation.getRoll() * (180.0F / (float)Math.PI) : roll * (180.0F / (float)Math.PI); + context.sendMessage( + MESSAGE_COMMANDS_TELEPORT_TELEPORTED_TO_COORDINATES_WITH_LOOK.param("x", x) + .param("y", y) + .param("z", z) + .param("yaw", displayYaw) + .param("pitch", displayPitch) + .param("roll", displayRoll) + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_TELEPORTED_TO_COORDINATES.param("x", x).param("y", y).param("z", z)); + } + } + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/teleport/variant/TeleportToCoordinatesCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/variant/TeleportToCoordinatesCommand.java new file mode 100644 index 0000000..34153e7 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/variant/TeleportToCoordinatesCommand.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.builtin.teleport.commands.teleport.variant; + +import com.hypixel.hytale.builtin.teleport.components.TeleportHistory; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.Coord; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeFloat; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class TeleportToCoordinatesCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_TELEPORT_TELEPORTED_TO_COORDINATES_WITH_LOOK = Message.translation( + "server.commands.teleport.teleportedToCoordinatesWithLook" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_TELEPORT_TELEPORTED_TO_COORDINATES = Message.translation("server.commands.teleport.teleportedToCoordinates"); + @Nonnull + private final RequiredArg xArg = this.withRequiredArg("x", "server.commands.teleport.x.desc", ArgTypes.RELATIVE_DOUBLE_COORD); + @Nonnull + private final RequiredArg yArg = this.withRequiredArg("y", "server.commands.teleport.y.desc", ArgTypes.RELATIVE_DOUBLE_COORD); + @Nonnull + private final RequiredArg zArg = this.withRequiredArg("z", "server.commands.teleport.z.desc", ArgTypes.RELATIVE_DOUBLE_COORD); + @Nonnull + private final OptionalArg yawArg = this.withOptionalArg("yaw", "server.commands.teleport.yaw.desc", ArgTypes.RELATIVE_FLOAT); + @Nonnull + private final OptionalArg pitchArg = this.withOptionalArg("pitch", "server.commands.teleport.pitch.desc", ArgTypes.RELATIVE_FLOAT); + @Nonnull + private final OptionalArg rollArg = this.withOptionalArg("roll", "server.commands.teleport.roll.desc", ArgTypes.RELATIVE_FLOAT); + + public TeleportToCoordinatesCommand() { + super("server.commands.teleport.toCoordinates.desc"); + this.requirePermission(HytalePermissions.fromCommand("teleport.self")); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3d previousPos = transformComponent.getPosition().clone(); + Vector3f previousHeadRotation = headRotationComponent.getRotation().clone(); + Vector3f previousBodyRotation = transformComponent.getRotation().clone(); + Coord relX = this.xArg.get(context); + Coord relY = this.yArg.get(context); + Coord relZ = this.zArg.get(context); + double x = relX.resolveXZ(previousPos.getX()); + double z = relZ.resolveXZ(previousPos.getZ()); + double y = relY.resolveYAtWorldCoords(previousPos.getY(), world, x, z); + float yaw = this.yawArg.provided(context) + ? this.yawArg.get(context).resolve(previousHeadRotation.getYaw() * (180.0F / (float)Math.PI)) * (float) (Math.PI / 180.0) + : Float.NaN; + float pitch = this.pitchArg.provided(context) + ? this.pitchArg.get(context).resolve(previousHeadRotation.getPitch() * (180.0F / (float)Math.PI)) * (float) (Math.PI / 180.0) + : Float.NaN; + float roll = this.rollArg.provided(context) + ? this.rollArg.get(context).resolve(previousHeadRotation.getRoll() * (180.0F / (float)Math.PI)) * (float) (Math.PI / 180.0) + : Float.NaN; + Teleport teleport = new Teleport(new Vector3d(x, y, z), new Vector3f(previousBodyRotation.getPitch(), yaw, previousBodyRotation.getRoll())) + .withHeadRotation(new Vector3f(pitch, yaw, roll)); + store.addComponent(ref, Teleport.getComponentType(), teleport); + boolean hasRotation = this.yawArg.provided(context) || this.pitchArg.provided(context) || this.rollArg.provided(context); + if (hasRotation) { + float displayYaw = Float.isNaN(yaw) ? previousHeadRotation.getYaw() * (180.0F / (float)Math.PI) : yaw * (180.0F / (float)Math.PI); + float displayPitch = Float.isNaN(pitch) ? previousHeadRotation.getPitch() * (180.0F / (float)Math.PI) : pitch * (180.0F / (float)Math.PI); + float displayRoll = Float.isNaN(roll) ? previousHeadRotation.getRoll() * (180.0F / (float)Math.PI) : roll * (180.0F / (float)Math.PI); + context.sendMessage( + MESSAGE_COMMANDS_TELEPORT_TELEPORTED_TO_COORDINATES_WITH_LOOK.param("x", x) + .param("y", y) + .param("z", z) + .param("yaw", displayYaw) + .param("pitch", displayPitch) + .param("roll", displayRoll) + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_TELEPORTED_TO_COORDINATES.param("x", x).param("y", y).param("z", z)); + } + + store.ensureAndGetComponent(ref, TeleportHistory.getComponentType()) + .append(world, previousPos, previousHeadRotation, String.format("Teleport to (%s, %s, %s)", x, y, z)); + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/teleport/variant/TeleportToPlayerCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/variant/TeleportToPlayerCommand.java new file mode 100644 index 0000000..6194a23 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/teleport/variant/TeleportToPlayerCommand.java @@ -0,0 +1,86 @@ +package com.hypixel.hytale.builtin.teleport.commands.teleport.variant; + +import com.hypixel.hytale.builtin.teleport.components.TeleportHistory; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class TeleportToPlayerCommand extends AbstractPlayerCommand { + private static final Message MESSAGE_COMMANDS_ERRORS_TARGET_NOT_IN_WORLD = Message.translation("server.commands.errors.targetNotInWorld"); + private static final Message MESSAGE_COMMANDS_TELEPORT_TELEPORTED_TO_PLAYER = Message.translation("server.commands.teleport.teleportedToPlayer"); + @Nonnull + private final RequiredArg targetPlayerArg = this.withRequiredArg( + "targetPlayer", "server.commands.teleport.targetPlayer.desc", ArgTypes.PLAYER_REF + ); + + public TeleportToPlayerCommand() { + super("server.commands.teleport.toPlayer.desc"); + this.requirePermission(HytalePermissions.fromCommand("teleport.self")); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + PlayerRef targetPlayerRef = this.targetPlayerArg.get(context); + Ref targetRef = targetPlayerRef.getReference(); + if (targetRef != null && targetRef.isValid()) { + Store targetStore = targetRef.getStore(); + World targetWorld = targetStore.getExternalData().getWorld(); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3d pos = transformComponent.getPosition().clone(); + Vector3f rotation = headRotationComponent.getRotation().clone(); + targetWorld.execute( + () -> { + TransformComponent targetTransformComponent = targetStore.getComponent(targetRef, TransformComponent.getComponentType()); + + assert targetTransformComponent != null; + + HeadRotation targetHeadRotationComponent = targetStore.getComponent(targetRef, HeadRotation.getComponentType()); + + assert targetHeadRotationComponent != null; + + Vector3d targetPosition = targetTransformComponent.getPosition().clone(); + Vector3f targetRotation = targetTransformComponent.getRotation().clone(); + Vector3f targetHeadRotation = targetHeadRotationComponent.getRotation().clone(); + world.execute( + () -> { + Teleport teleport = new Teleport(targetWorld, targetPosition, targetRotation).withHeadRotation(targetHeadRotation).withResetRoll(); + store.addComponent(ref, Teleport.getComponentType(), teleport); + PlayerRef targetPlayerRefComponent = targetStore.getComponent(targetRef, PlayerRef.getComponentType()); + + assert targetPlayerRefComponent != null; + + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_TELEPORTED_TO_PLAYER.param("toName", targetPlayerRefComponent.getUsername())); + store.ensureAndGetComponent(ref, TeleportHistory.getComponentType()) + .append(world, pos, rotation, "Teleport to " + targetPlayerRefComponent.getUsername()); + } + ); + } + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_TARGET_NOT_IN_WORLD); + } + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpCommand.java new file mode 100644 index 0000000..31a3ff2 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpCommand.java @@ -0,0 +1,72 @@ +package com.hypixel.hytale.builtin.teleport.commands.warp; + +import com.hypixel.hytale.builtin.teleport.TeleportPlugin; +import com.hypixel.hytale.builtin.teleport.Warp; +import com.hypixel.hytale.builtin.teleport.components.TeleportHistory; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WarpCommand extends AbstractCommandCollection { + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_NOT_LOADED = Message.translation("server.commands.teleport.warp.notLoaded"); + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_UNKNOWN_WARP = Message.translation("server.commands.teleport.warp.unknownWarp"); + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_WORLD_NAME_FOR_WARP_NOT_FOUND = Message.translation( + "server.commands.teleport.warp.worldNameForWarpNotFound" + ); + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_WARPED_TO = Message.translation("server.commands.teleport.warp.warpedTo"); + + public WarpCommand() { + super("warp", "server.commands.warp.desc"); + this.addUsageVariant(new WarpGoVariantCommand()); + this.addSubCommand(new WarpGoCommand()); + this.addSubCommand(new WarpSetCommand()); + this.addSubCommand(new WarpListCommand()); + this.addSubCommand(new WarpRemoveCommand()); + this.addSubCommand(new WarpReloadCommand()); + } + + static void tryGo(@Nonnull CommandContext context, @Nonnull String warp, @Nonnull Ref ref, @Nonnull Store store) { + if (!TeleportPlugin.get().isWarpsLoaded()) { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_NOT_LOADED); + } else { + Warp targetWarp = TeleportPlugin.get().getWarps().get(warp.toLowerCase()); + if (targetWarp == null) { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_UNKNOWN_WARP.param("name", warp)); + } else { + String worldName = targetWarp.getWorld(); + World world = Universe.get().getWorld(worldName); + Teleport teleport = targetWarp.toTeleport(); + if (world != null && teleport != null) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3d playerPosition = transformComponent.getPosition(); + Vector3f playerHeadRotation = headRotationComponent.getRotation(); + store.ensureAndGetComponent(ref, TeleportHistory.getComponentType()) + .append(world, playerPosition.clone(), playerHeadRotation.clone(), "Warp '" + warp + "'"); + store.addComponent(ref, Teleport.getComponentType(), teleport); + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_WARPED_TO.param("name", warp)); + } else { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_WORLD_NAME_FOR_WARP_NOT_FOUND.param("worldName", worldName).param("warp", warp)); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpGoCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpGoCommand.java new file mode 100644 index 0000000..e84fae3 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpGoCommand.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.builtin.teleport.commands.warp; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WarpGoCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg warpNameArg = this.withRequiredArg("warpName", "server.commands.warp.warpName.desc", ArgTypes.STRING); + + public WarpGoCommand() { + super("go", "server.commands.warp.go.desc"); + this.requirePermission(HytalePermissions.fromCommand("warp.go")); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + String warpName = this.warpNameArg.get(context); + WarpCommand.tryGo(context, warpName, ref, store); + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpGoVariantCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpGoVariantCommand.java new file mode 100644 index 0000000..fdf478a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpGoVariantCommand.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.builtin.teleport.commands.warp; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WarpGoVariantCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg warpNameArg = this.withRequiredArg("warpName", "server.commands.warp.warpName.desc", ArgTypes.STRING); + + public WarpGoVariantCommand() { + super("server.commands.warp.go.desc"); + this.requirePermission(HytalePermissions.fromCommand("warp.go")); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + String warpName = this.warpNameArg.get(context); + WarpCommand.tryGo(context, warpName, ref, store); + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpListCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpListCommand.java new file mode 100644 index 0000000..e2db919 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpListCommand.java @@ -0,0 +1,102 @@ +package com.hypixel.hytale.builtin.teleport.commands.warp; + +import com.hypixel.hytale.builtin.teleport.TeleportPlugin; +import com.hypixel.hytale.builtin.teleport.Warp; +import com.hypixel.hytale.builtin.teleport.WarpListPage; +import com.hypixel.hytale.common.util.ListUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +public class WarpListCommand extends CommandBase { + private static final int WARPS_PER_LIST_PAGE = 8; + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_NOT_LOADED = Message.translation("server.commands.teleport.warp.notLoaded"); + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_NO_WARPS = Message.translation("server.commands.teleport.warp.noWarps"); + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_PAGE_NUM_TOO_LOW = Message.translation("server.commands.teleport.warp.pageNumTooLow"); + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_PAGE_NUM_TOO_HIGH = Message.translation("server.commands.teleport.warp.pageNumTooHigh"); + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_LIST_HEADER = Message.translation("server.commands.teleport.warp.listHeader"); + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_LIST_ENTRY = Message.translation("server.commands.teleport.warp.listEntry"); + @Nonnull + private final OptionalArg pageArg = this.withOptionalArg("page", "server.commands.warp.list.page.desc", ArgTypes.INTEGER); + + public WarpListCommand() { + super("list", "server.commands.warp.list.desc"); + this.requirePermission(HytalePermissions.fromCommand("warp.list")); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + if (!TeleportPlugin.get().isWarpsLoaded()) { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_NOT_LOADED); + } else { + Map warps = TeleportPlugin.get().getWarps(); + if (context.isPlayer() && !this.pageArg.provided(context)) { + Ref ref = context.senderAsPlayerRef(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World playerWorld = store.getExternalData().getWorld(); + playerWorld.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerComponent.getPageManager().openCustomPage(ref, store, new WarpListPage(playerRefComponent, warps, warp -> { + try { + WarpCommand.tryGo(context, warp, ref, store); + } catch (Exception var5x) { + throw SneakyThrow.sneakyThrow(var5x); + } + })); + }); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } else if (warps.isEmpty()) { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_NO_WARPS); + } else { + int pageNumber = this.pageArg.provided(context) ? this.pageArg.get(context) : 1; + if (pageNumber < 1) { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_PAGE_NUM_TOO_LOW.param("page", pageNumber)); + } else { + List innerWarps = new ObjectArrayList<>(warps.values()); + innerWarps.sort((o1, o2) -> o2.getCreationDate().compareTo(o1.getCreationDate())); + List> paginated = ListUtil.partition(innerWarps, 8); + if (paginated.size() < pageNumber) { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_PAGE_NUM_TOO_HIGH); + } else { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_LIST_HEADER.param("page", pageNumber).param("pages", paginated.size())); + int startIndex = (pageNumber - 1) * 8; + List page = paginated.get(pageNumber - 1); + int i = 1; + + for (Warp w : page) { + context.sendMessage( + MESSAGE_COMMANDS_TELEPORT_WARP_LIST_ENTRY.param("index", startIndex + i).param("name", w.getId()).param("creator", w.getCreator()) + ); + i++; + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpReloadCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpReloadCommand.java new file mode 100644 index 0000000..f791175 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpReloadCommand.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.builtin.teleport.commands.warp; + +import com.hypixel.hytale.builtin.teleport.TeleportPlugin; +import com.hypixel.hytale.builtin.teleport.Warp; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import java.util.Map; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class WarpReloadCommand extends CommandBase { + private static final HytaleLogger logger = HytaleLogger.forEnclosingClass(); + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_NOT_LOADED = Message.translation("server.commands.teleport.warp.notLoaded"); + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_RELOADED = Message.translation("server.commands.teleport.warp.reloaded"); + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_FAILED_TO_RELOAD = Message.translation("server.commands.teleport.warp.failedToReload"); + + public WarpReloadCommand() { + super("reload", "server.commands.warp.reload.desc"); + this.requirePermission(HytalePermissions.fromCommand("warp.reload")); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + if (!TeleportPlugin.get().isWarpsLoaded()) { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_NOT_LOADED); + } else { + Map warps = TeleportPlugin.get().getWarps(); + + try { + TeleportPlugin.get().loadWarps(); + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_RELOADED.param("count", warps.size())); + } catch (Throwable var4) { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_FAILED_TO_RELOAD); + logger.at(Level.SEVERE).withCause(var4).log("Failed to reload warps:"); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpRemoveCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpRemoveCommand.java new file mode 100644 index 0000000..10d02ae --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpRemoveCommand.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.builtin.teleport.commands.warp; + +import com.hypixel.hytale.builtin.teleport.TeleportPlugin; +import com.hypixel.hytale.builtin.teleport.Warp; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import javax.annotation.Nonnull; + +public class WarpRemoveCommand extends CommandBase { + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_NOT_LOADED = Message.translation("server.commands.teleport.warp.notLoaded"); + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_UNKNOWN_WARP = Message.translation("server.commands.teleport.warp.unknownWarp"); + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_REMOVED_WARP = Message.translation("server.commands.teleport.warp.removedWarp"); + @Nonnull + private final RequiredArg nameArg = this.withRequiredArg("name", "server.commands.warp.remove.name.desc", ArgTypes.STRING); + + public WarpRemoveCommand() { + super("remove", "server.commands.warp.remove.desc"); + this.requirePermission(HytalePermissions.fromCommand("warp.remove")); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + if (!TeleportPlugin.get().isWarpsLoaded()) { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_NOT_LOADED); + } else { + Map warps = TeleportPlugin.get().getWarps(); + String warpName = this.nameArg.get(context).toLowerCase(); + Warp old = warps.remove(warpName); + if (old == null) { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_UNKNOWN_WARP.param("name", warpName)); + } else { + TeleportPlugin.get().saveWarps(); + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_REMOVED_WARP.param("name", warpName)); + World targetWorld = Universe.get().getWorld(old.getWorld()); + if (targetWorld != null) { + ComponentType warpComponentType = TeleportPlugin.WarpComponent.getComponentType(); + Store store = targetWorld.getEntityStore().getStore(); + targetWorld.execute(() -> store.forEachEntityParallel(warpComponentType, (index, archetypeChunk, commandBuffer) -> { + TeleportPlugin.WarpComponent warpComponent = archetypeChunk.getComponent(index, warpComponentType); + if (warpComponent != null && warpComponent.warp().getId().equals(old.getId())) { + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + } + })); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpSetCommand.java b/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpSetCommand.java new file mode 100644 index 0000000..57dad8e --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/commands/warp/WarpSetCommand.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.builtin.teleport.commands.warp; + +import com.hypixel.hytale.builtin.teleport.TeleportPlugin; +import com.hypixel.hytale.builtin.teleport.Warp; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import java.util.Map; +import javax.annotation.Nonnull; + +public class WarpSetCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_NOT_LOADED = Message.translation("server.commands.teleport.warp.notLoaded"); + @Nonnull + private static final Message MESSAGE_COMMANDS_TELEPORT_WARP_RESERVED_KEYWORD = Message.translation("server.commands.teleport.warp.reservedKeyword"); + @Nonnull + private final RequiredArg nameArg = this.withRequiredArg("name", "server.commands.warp.set.name.desc", ArgTypes.STRING); + + public WarpSetCommand() { + super("set", "server.commands.warp.set.desc"); + this.requirePermission(HytalePermissions.fromCommand("warp.set")); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + if (!TeleportPlugin.get().isWarpsLoaded()) { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_NOT_LOADED); + } else { + Map warps = TeleportPlugin.get().getWarps(); + String newId = this.nameArg.get(context).toLowerCase(); + if (!"reload".equals(newId) && !"remove".equals(newId) && !"set".equals(newId) && !"list".equals(newId) && !"go".equals(newId)) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3d position = transformComponent.getPosition(); + Vector3f rotation = transformComponent.getRotation(); + Vector3f headRotation = headRotationComponent.getRotation(); + Warp newWarp = new Warp( + position.getX(), + position.getY(), + position.getZ(), + headRotation.getYaw(), + rotation.getPitch(), + rotation.getRoll(), + newId, + world, + playerRef.getUsername(), + Instant.now() + ); + warps.put(newWarp.getId().toLowerCase(), newWarp); + TeleportPlugin plugin = TeleportPlugin.get(); + plugin.saveWarps(); + store.addEntity(plugin.createWarp(newWarp, store), AddReason.LOAD); + context.sendMessage(Message.translation("server.commands.teleport.warp.setWarp").param("name", newWarp.getId())); + } else { + context.sendMessage(MESSAGE_COMMANDS_TELEPORT_WARP_RESERVED_KEYWORD); + } + } + } +} diff --git a/src/com/hypixel/hytale/builtin/teleport/components/TeleportHistory.java b/src/com/hypixel/hytale/builtin/teleport/components/TeleportHistory.java new file mode 100644 index 0000000..9b5fb60 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/teleport/components/TeleportHistory.java @@ -0,0 +1,177 @@ +package com.hypixel.hytale.builtin.teleport.components; + +import com.hypixel.hytale.builtin.teleport.TeleportPlugin; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.ArrayDeque; +import java.util.Deque; +import javax.annotation.Nonnull; + +public class TeleportHistory implements Component { + private static final int MAX_TELEPORT_HISTORY = 100; + private static final Message MESSAGE_COMMANDS_TELEPORT_NOT_FURTHER = Message.translation("server.commands.teleport.notFurther"); + private static final Message MESSAGE_COMMANDS_TELEPORT_WORLD_NOT_LOADED = Message.translation("server.commands.teleport.worldNotLoaded"); + private static final Message MESSAGE_COMMANDS_TELEPORT_TELEPORTED_FORWARD_TO_WAYPOINT = Message.translation( + "server.commands.teleport.teleportedForwardToWaypoint" + ); + private static final Message MESSAGE_COMMANDS_TELEPORT_TELEPORTED_BACK_TO_WAYPOINT = Message.translation("server.commands.teleport.teleportedBackToWaypoint"); + private static final Message MESSAGE_COMMANDS_TELEPORT_TELEPORTED_FORWARD_TO_COORDINATES = Message.translation( + "server.commands.teleport.teleportedForwardToCoordinates" + ); + private static final Message MESSAGE_COMMANDS_TELEPORT_TELEPORTED_BACK_TO_COORDINATES = Message.translation( + "server.commands.teleport.teleportedBackToCoordinates" + ); + @Nonnull + private final Deque back = new ArrayDeque<>(); + @Nonnull + private final Deque forward = new ArrayDeque<>(); + + @Nonnull + public static ComponentType getComponentType() { + return TeleportPlugin.get().getTeleportHistoryComponentType(); + } + + public TeleportHistory() { + } + + public void forward(@Nonnull Ref ref, int count) { + Store store = ref.getStore(); + go(store, ref, this.forward, this.back, count, true); + } + + public void back(@Nonnull Ref ref, int count) { + Store store = ref.getStore(); + go(store, ref, this.back, this.forward, count, false); + } + + public int getForwardSize() { + return this.forward.size(); + } + + public int getBackSize() { + return this.back.size(); + } + + private static void go( + @Nonnull Store store, + @Nonnull Ref ref, + @Nonnull Deque from, + @Nonnull Deque to, + int count, + boolean isForward + ) { + if (count <= 0) { + throw new IllegalArgumentException(String.valueOf(count)); + } else { + PlayerRef playerRef = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRef != null; + + TeleportHistory.Waypoint point = null; + + for (int i = 0; i < count; i++) { + if (from.isEmpty()) { + if (point == null) { + playerRef.sendMessage(MESSAGE_COMMANDS_TELEPORT_NOT_FURTHER); + return; + } + break; + } + + point = from.pop(); + to.push(point); + } + + if (point == null) { + throw new NullPointerException(to.toString()); + } else { + World targetWorld = Universe.get().getWorld(point.world); + if (targetWorld == null) { + playerRef.sendMessage(MESSAGE_COMMANDS_TELEPORT_WORLD_NOT_LOADED); + } else { + to.push(point); + store.addComponent(ref, Teleport.getComponentType(), new Teleport(targetWorld, point.position, point.rotation)); + Vector3d pos = point.position; + int remainingInDirection = from.size(); + int totalInOtherDirection = to.size() - 1; + if (point.message != null && !point.message.isEmpty()) { + playerRef.sendMessage( + isForward + ? MESSAGE_COMMANDS_TELEPORT_TELEPORTED_FORWARD_TO_WAYPOINT + : MESSAGE_COMMANDS_TELEPORT_TELEPORTED_BACK_TO_WAYPOINT.param("name", point.message) + .param("x", pos.getX()) + .param("y", pos.getY()) + .param("z", pos.getZ()) + .param("remaining", remainingInDirection) + .param("otherDirection", totalInOtherDirection) + ); + } else { + playerRef.sendMessage( + isForward + ? MESSAGE_COMMANDS_TELEPORT_TELEPORTED_FORWARD_TO_COORDINATES + : MESSAGE_COMMANDS_TELEPORT_TELEPORTED_BACK_TO_COORDINATES.param("x", pos.getX()) + .param("y", pos.getY()) + .param("z", pos.getZ()) + .param("remaining", remainingInDirection) + .param("otherDirection", totalInOtherDirection) + ); + } + } + } + } + } + + public void append(@Nonnull World world, @Nonnull Vector3d pos, @Nonnull Vector3f rotation, @Nonnull String key) { + this.back.push(new TeleportHistory.Waypoint(world.getName(), pos, rotation, key)); + this.forward.clear(); + + while (this.back.size() > 100) { + this.back.removeLast(); + } + } + + @Nonnull + @Override + public String toString() { + return "TeleportHistory{back=" + this.back + ", forward=" + this.forward + "}"; + } + + @Nonnull + @Override + public Component clone() { + TeleportHistory cloned = new TeleportHistory(); + cloned.back.addAll(this.back); + cloned.forward.addAll(this.forward); + return cloned; + } + + public static class Waypoint { + private final String world; + private final Vector3d position; + private final Vector3f rotation; + private final String message; + + public Waypoint(@Nonnull String world, @Nonnull Vector3d position, @Nonnull Vector3f rotation, @Nonnull String message) { + this.world = world; + this.position = position; + this.rotation = rotation; + this.message = message; + } + + @Nonnull + @Override + public String toString() { + return "Waypoint{world='" + this.world + "', position=" + this.position + ", rotation=" + this.rotation + ", message='" + this.message + "'}"; + } + } +} diff --git a/src/com/hypixel/hytale/builtin/weather/WeatherPlugin.java b/src/com/hypixel/hytale/builtin/weather/WeatherPlugin.java new file mode 100644 index 0000000..76d1355 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/weather/WeatherPlugin.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.builtin.weather; + +import com.hypixel.hytale.builtin.weather.commands.WeatherCommand; +import com.hypixel.hytale.builtin.weather.components.WeatherTracker; +import com.hypixel.hytale.builtin.weather.resources.WeatherResource; +import com.hypixel.hytale.builtin.weather.systems.WeatherSystem; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.command.system.CommandManager; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WeatherPlugin extends JavaPlugin { + private static WeatherPlugin instance; + private ComponentType weatherTrackerComponentType; + private ResourceType weatherResourceType; + + public static WeatherPlugin get() { + return instance; + } + + public WeatherPlugin(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + ComponentRegistryProxy entityStoreRegistry = this.getEntityStoreRegistry(); + this.weatherResourceType = EntityStore.REGISTRY.registerResource(WeatherResource.class, WeatherResource::new); + this.weatherTrackerComponentType = EntityStore.REGISTRY.registerComponent(WeatherTracker.class, WeatherTracker::new); + entityStoreRegistry.registerSystem(new WeatherSystem.WorldAddedSystem()); + entityStoreRegistry.registerSystem(new WeatherSystem.PlayerAddedSystem()); + entityStoreRegistry.registerSystem(new WeatherSystem.TickingSystem()); + entityStoreRegistry.registerSystem(new WeatherSystem.InvalidateWeatherAfterTeleport()); + CommandManager.get().registerSystemCommand(new WeatherCommand()); + } + + @Nonnull + public ComponentType getWeatherTrackerComponentType() { + return this.weatherTrackerComponentType; + } + + @Nonnull + public ResourceType getWeatherResourceType() { + return this.weatherResourceType; + } +} diff --git a/src/com/hypixel/hytale/builtin/weather/commands/WeatherCommand.java b/src/com/hypixel/hytale/builtin/weather/commands/WeatherCommand.java new file mode 100644 index 0000000..631efab --- /dev/null +++ b/src/com/hypixel/hytale/builtin/weather/commands/WeatherCommand.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.builtin.weather.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class WeatherCommand extends AbstractCommandCollection { + public WeatherCommand() { + super("weather", "server.commands.weather.desc"); + this.addSubCommand(new WeatherSetCommand()); + this.addSubCommand(new WeatherGetCommand()); + this.addSubCommand(new WeatherResetCommand()); + } +} diff --git a/src/com/hypixel/hytale/builtin/weather/commands/WeatherGetCommand.java b/src/com/hypixel/hytale/builtin/weather/commands/WeatherGetCommand.java new file mode 100644 index 0000000..7e6dc2b --- /dev/null +++ b/src/com/hypixel/hytale/builtin/weather/commands/WeatherGetCommand.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.builtin.weather.commands; + +import com.hypixel.hytale.builtin.weather.resources.WeatherResource; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WeatherGetCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_WEATHER_GET_FORCED_WEATHER = Message.translation("server.commands.weather.get.getForcedWeather"); + + public WeatherGetCommand() { + super("get", "server.commands.weather.get.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + WeatherResource weatherResource = store.getResource(WeatherResource.getResourceType()); + int forcedWeatherIndex = weatherResource.getForcedWeatherIndex(); + String weatherId; + if (forcedWeatherIndex != 0) { + Weather weatherAsset = Weather.getAssetMap().getAsset(forcedWeatherIndex); + weatherId = weatherAsset.getId(); + } else { + weatherId = "not locked"; + } + + context.sendMessage(MESSAGE_COMMANDS_WEATHER_GET_FORCED_WEATHER.param("worldName", world.getName()).param("weather", weatherId)); + } +} diff --git a/src/com/hypixel/hytale/builtin/weather/commands/WeatherResetCommand.java b/src/com/hypixel/hytale/builtin/weather/commands/WeatherResetCommand.java new file mode 100644 index 0000000..66a096d --- /dev/null +++ b/src/com/hypixel/hytale/builtin/weather/commands/WeatherResetCommand.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.builtin.weather.commands; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WeatherResetCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_WEATHER_RESET_FORCED_WEATHER_RESET = Message.translation("server.commands.weather.reset.forcedWeatherReset"); + + public WeatherResetCommand() { + super("reset", "server.commands.weather.reset.desc"); + this.addAliases("clear"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + WeatherSetCommand.setForcedWeather(world, null, store); + context.sendMessage(MESSAGE_COMMANDS_WEATHER_RESET_FORCED_WEATHER_RESET.param("worldName", world.getName())); + } +} diff --git a/src/com/hypixel/hytale/builtin/weather/commands/WeatherSetCommand.java b/src/com/hypixel/hytale/builtin/weather/commands/WeatherSetCommand.java new file mode 100644 index 0000000..34bcbe1 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/weather/commands/WeatherSetCommand.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.builtin.weather.commands; + +import com.hypixel.hytale.builtin.weather.resources.WeatherResource; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WeatherSetCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_WEATHER_SET_FORCED_WEATHER_SET = Message.translation("server.commands.weather.set.forcedWeatherSet"); + @Nonnull + private final RequiredArg weatherArg = this.withRequiredArg("weather", "server.commands.weather.set.weather.desc", ArgTypes.WEATHER_ASSET); + + public WeatherSetCommand() { + super("set", "server.commands.weather.set.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Weather weather = this.weatherArg.get(context); + String weatherName = weather.getId(); + setForcedWeather(world, weatherName, store); + context.sendMessage(MESSAGE_COMMANDS_WEATHER_SET_FORCED_WEATHER_SET.param("worldName", world.getName()).param("weather", weatherName)); + } + + protected static void setForcedWeather(@Nonnull World world, @Nullable String forcedWeather, ComponentAccessor componentAccessor) { + WeatherResource weatherResource = componentAccessor.getResource(WeatherResource.getResourceType()); + weatherResource.setForcedWeather(forcedWeather); + WorldConfig config = world.getWorldConfig(); + config.setForcedWeather(forcedWeather); + config.markChanged(); + } +} diff --git a/src/com/hypixel/hytale/builtin/weather/components/WeatherTracker.java b/src/com/hypixel/hytale/builtin/weather/components/WeatherTracker.java new file mode 100644 index 0000000..e87c599 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/weather/components/WeatherTracker.java @@ -0,0 +1,128 @@ +package com.hypixel.hytale.builtin.weather.components; + +import com.hypixel.hytale.builtin.weather.WeatherPlugin; +import com.hypixel.hytale.builtin.weather.resources.WeatherResource; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.packets.world.UpdateWeather; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WeatherTracker implements Component { + private final UpdateWeather updateWeather = new UpdateWeather(0, 10.0F); + private final Vector3i previousBlockPosition = new Vector3i(); + private int environmentId; + private boolean firstSendForWorld = true; + + public static ComponentType getComponentType() { + return WeatherPlugin.get().getWeatherTrackerComponentType(); + } + + public WeatherTracker() { + } + + private WeatherTracker(@Nonnull WeatherTracker other) { + this.environmentId = other.environmentId; + this.updateWeather.weatherIndex = other.updateWeather.weatherIndex; + this.previousBlockPosition.assign(other.previousBlockPosition); + } + + public void updateWeather( + @Nonnull PlayerRef playerRef, + @Nonnull WeatherResource weatherComponent, + @Nonnull TransformComponent transformComponent, + float transitionSeconds, + @Nonnull ComponentAccessor componentAccessor + ) { + int forcedWeatherIndex = weatherComponent.getForcedWeatherIndex(); + if (forcedWeatherIndex != 0) { + this.sendWeatherIndex(playerRef, forcedWeatherIndex, transitionSeconds); + } else { + this.updateEnvironment(transformComponent, componentAccessor); + int weatherIndexForEnvironment = weatherComponent.getWeatherIndexForEnvironment(this.environmentId); + this.sendWeatherIndex(playerRef, weatherIndexForEnvironment, transitionSeconds); + } + } + + public void sendWeatherIndex(@Nonnull PlayerRef playerRef, int weatherIndex, float transitionSeconds) { + if (weatherIndex == Integer.MIN_VALUE) { + weatherIndex = 0; + } + + if (this.updateWeather.weatherIndex != weatherIndex) { + this.updateWeather.weatherIndex = weatherIndex; + this.updateWeather.transitionSeconds = transitionSeconds; + playerRef.getPacketHandler().write(this.updateWeather); + } + } + + public boolean consumeFirstSendForWorld() { + if (this.firstSendForWorld) { + this.firstSendForWorld = false; + return true; + } else { + return false; + } + } + + public void clear() { + this.updateWeather.weatherIndex = 0; + this.firstSendForWorld = true; + } + + public void updateEnvironment(@Nonnull TransformComponent transformComponent, @Nonnull ComponentAccessor componentAccessor) { + Vector3d vector = transformComponent.getPosition(); + int blockX = MathUtil.floor(vector.getX()); + int blockY = MathUtil.floor(vector.getY()); + int blockZ = MathUtil.floor(vector.getZ()); + if (this.previousBlockPosition.getX() != blockX || this.previousBlockPosition.getY() != blockY || this.previousBlockPosition.getZ() != blockZ) { + Ref chunkRef = transformComponent.getChunkRef(); + if (chunkRef == null || !chunkRef.isValid()) { + return; + } + + World world = componentAccessor.getExternalData().getWorld(); + Store chunkStore = world.getChunkStore().getStore(); + BlockChunk blockChunkComponent = chunkStore.getComponent(chunkRef, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + int y = MathUtil.clamp(blockY, 0, 319); + this.environmentId = blockChunkComponent.getEnvironment(blockX, y, blockZ); + } + + this.previousBlockPosition.x = blockX; + this.previousBlockPosition.y = blockY; + this.previousBlockPosition.z = blockZ; + } + + public int getEnvironmentId() { + return this.environmentId; + } + + public int getWeatherIndex() { + return this.updateWeather.weatherIndex; + } + + public void setWeatherIndex(@Nonnull PlayerRef playerRef, int weatherIndex) { + this.updateWeather.weatherIndex = weatherIndex; + playerRef.getPacketHandler().write(this.updateWeather); + } + + @Nonnull + @Override + public Component clone() { + return new WeatherTracker(this); + } +} diff --git a/src/com/hypixel/hytale/builtin/weather/resources/WeatherResource.java b/src/com/hypixel/hytale/builtin/weather/resources/WeatherResource.java new file mode 100644 index 0000000..c678f36 --- /dev/null +++ b/src/com/hypixel/hytale/builtin/weather/resources/WeatherResource.java @@ -0,0 +1,71 @@ +package com.hypixel.hytale.builtin.weather.resources; + +import com.hypixel.hytale.builtin.weather.WeatherPlugin; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WeatherResource implements Resource { + public static final float WEATHER_UPDATE_RATE_S = 1.0F; + private int forcedWeatherIndex; + private int previousForcedWeatherIndex; + @Nonnull + private final Int2IntMap environmentWeather = new Int2IntOpenHashMap(); + private int previousHour = -1; + public float playerUpdateDelay; + + public static ResourceType getResourceType() { + return WeatherPlugin.get().getWeatherResourceType(); + } + + public WeatherResource() { + this.environmentWeather.defaultReturnValue(Integer.MIN_VALUE); + } + + @Nonnull + public Int2IntMap getEnvironmentWeather() { + return this.environmentWeather; + } + + public int getWeatherIndexForEnvironment(int environmentId) { + return this.environmentWeather.get(environmentId); + } + + public int getForcedWeatherIndex() { + return this.forcedWeatherIndex; + } + + public void setForcedWeather(@Nullable String forcedWeather) { + this.previousForcedWeatherIndex = this.forcedWeatherIndex; + this.forcedWeatherIndex = forcedWeather == null ? 0 : Weather.getAssetMap().getIndex(forcedWeather); + } + + public boolean consumeForcedWeatherChange() { + if (this.previousForcedWeatherIndex == this.forcedWeatherIndex) { + return false; + } else { + this.previousForcedWeatherIndex = this.forcedWeatherIndex; + return true; + } + } + + public boolean compareAndSwapHour(int currentHour) { + if (currentHour == this.previousHour) { + return false; + } else { + this.previousHour = currentHour; + return true; + } + } + + @Nonnull + @Override + public Resource clone() { + return new WeatherResource(); + } +} diff --git a/src/com/hypixel/hytale/builtin/weather/systems/WeatherSystem.java b/src/com/hypixel/hytale/builtin/weather/systems/WeatherSystem.java new file mode 100644 index 0000000..052f30a --- /dev/null +++ b/src/com/hypixel/hytale/builtin/weather/systems/WeatherSystem.java @@ -0,0 +1,226 @@ +package com.hypixel.hytale.builtin.weather.systems; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.builtin.weather.components.WeatherTracker; +import com.hypixel.hytale.builtin.weather.resources.WeatherResource; +import com.hypixel.hytale.common.map.IWeightedMap; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.StoreSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.core.asset.type.environment.config.WeatherForecast; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.modules.entity.teleport.TeleportSystems; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class WeatherSystem { + private static final float JOIN_TRANSITION_SECONDS = 0.5F; + private static final float WEATHERCHANGE_TRANSITION_SECONDS = 10.0F; + + public WeatherSystem() { + } + + public static class InvalidateWeatherAfterTeleport extends RefChangeSystem { + private static final Query QUERY = WeatherTracker.getComponentType(); + private static final Set> DEPENDENCIES = Set.of(new SystemDependency<>(Order.AFTER, TeleportSystems.PlayerMoveSystem.class)); + + public InvalidateWeatherAfterTeleport() { + } + + @NonNullDecl + @Override + public ComponentType componentType() { + return Teleport.getComponentType(); + } + + public void onComponentAdded( + @NonNullDecl Ref ref, + @NonNullDecl Teleport component, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + commandBuffer.getComponent(ref, WeatherTracker.getComponentType()).clear(); + } + + public void onComponentSet( + @NonNullDecl Ref ref, + @NullableDecl Teleport oldComponent, + @NonNullDecl Teleport newComponent, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @NonNullDecl Ref ref, + @NonNullDecl Teleport component, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + } + + @NullableDecl + @Override + public Query getQuery() { + return QUERY; + } + + @NonNullDecl + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + } + + public static class PlayerAddedSystem extends HolderSystem { + private static final ComponentType PLAYER_REF_COMPONENT_TYPE = PlayerRef.getComponentType(); + private static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + private static final ComponentType WEATHER_TRACKER_COMPONENT_TYPE = WeatherTracker.getComponentType(); + private static final Query QUERY = Archetype.of(PLAYER_REF_COMPONENT_TYPE, TRANSFORM_COMPONENT_TYPE); + + public PlayerAddedSystem() { + } + + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(WEATHER_TRACKER_COMPONENT_TYPE); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + WeatherTracker weatherTrackerComponent = holder.ensureAndGetComponent(WEATHER_TRACKER_COMPONENT_TYPE); + weatherTrackerComponent.clear(); + } + } + + public static class TickingSystem extends EntityTickingSystem { + private static final ComponentType PLAYER_REF_COMPONENT_TYPE = PlayerRef.getComponentType(); + private static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + private static final ComponentType WEATHER_TRACKER_COMPONENT_TYPE = WeatherTracker.getComponentType(); + private static final ResourceType WEATHER_RESOURCE_TYPE = WeatherResource.getResourceType(); + private static final Query QUERY = Archetype.of(PLAYER_REF_COMPONENT_TYPE, TRANSFORM_COMPONENT_TYPE, WEATHER_TRACKER_COMPONENT_TYPE); + + public TickingSystem() { + } + + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + WeatherResource weatherResource = store.getResource(WEATHER_RESOURCE_TYPE); + if (weatherResource.consumeForcedWeatherChange()) { + weatherResource.playerUpdateDelay = 1.0F; + store.tick(this, dt, systemIndex); + } else { + if (weatherResource.getForcedWeatherIndex() == 0) { + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + int currentHour = worldTimeResource.getCurrentHour(); + if (weatherResource.compareAndSwapHour(currentHour)) { + Int2IntMap environmentWeather = weatherResource.getEnvironmentWeather(); + ThreadLocalRandom random = ThreadLocalRandom.current(); + IndexedLookupTableAssetMap assetMap = Environment.getAssetMap(); + + for (Entry entry : assetMap.getAssetMap().entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + IWeightedMap weatherForecast = entry.getValue().getWeatherForecast(currentHour); + int selectedWeatherIndex = weatherForecast.get(random).getWeatherIndex(); + environmentWeather.put(index, selectedWeatherIndex); + } + } + } + + weatherResource.playerUpdateDelay -= dt; + if (weatherResource.playerUpdateDelay <= 0.0F) { + weatherResource.playerUpdateDelay = 1.0F; + store.tick(this, dt, systemIndex); + } + } + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return false; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + WeatherResource weatherResource = store.getResource(WEATHER_RESOURCE_TYPE); + PlayerRef playerRefComponent = archetypeChunk.getComponent(index, PLAYER_REF_COMPONENT_TYPE); + + assert playerRefComponent != null; + + TransformComponent transformComponent = archetypeChunk.getComponent(index, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + WeatherTracker weatherTrackerComponent = archetypeChunk.getComponent(index, WEATHER_TRACKER_COMPONENT_TYPE); + + assert weatherTrackerComponent != null; + + float transitionSeconds = weatherTrackerComponent.consumeFirstSendForWorld() ? 0.5F : 10.0F; + weatherTrackerComponent.updateWeather(playerRefComponent, weatherResource, transformComponent, transitionSeconds, commandBuffer); + } + } + + public static class WorldAddedSystem extends StoreSystem { + @Nonnull + private final ResourceType weatherResourceType = WeatherResource.getResourceType(); + + public WorldAddedSystem() { + } + + @Override + public void onSystemAddedToStore(@Nonnull Store store) { + String forcedWeather = store.getExternalData().getWorld().getWorldConfig().getForcedWeather(); + store.getResource(this.weatherResourceType).setForcedWeather(forcedWeather); + } + + @Override + public void onSystemRemovedFromStore(@Nonnull Store store) { + } + } +} diff --git a/src/com/hypixel/hytale/builtin/worldgen/WorldGenPlugin.java b/src/com/hypixel/hytale/builtin/worldgen/WorldGenPlugin.java new file mode 100644 index 0000000..956507f --- /dev/null +++ b/src/com/hypixel/hytale/builtin/worldgen/WorldGenPlugin.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.builtin.worldgen; + +import com.hypixel.hytale.codec.lookup.Priority; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.worldgen.provider.IWorldGenProvider; +import com.hypixel.hytale.server.worldgen.BiomeDataSystem; +import com.hypixel.hytale.server.worldgen.HytaleWorldGenProvider; +import javax.annotation.Nonnull; + +public class WorldGenPlugin extends JavaPlugin { + private static WorldGenPlugin instance; + + public static WorldGenPlugin get() { + return instance; + } + + public WorldGenPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + instance = this; + this.getEntityStoreRegistry().registerSystem(new BiomeDataSystem()); + IWorldGenProvider.CODEC.register(Priority.DEFAULT.before(1), "Hytale", HytaleWorldGenProvider.class, HytaleWorldGenProvider.CODEC); + } +} diff --git a/src/com/hypixel/hytale/codec/Codec.java b/src/com/hypixel/hytale/codec/Codec.java new file mode 100644 index 0000000..914eb6e --- /dev/null +++ b/src/com/hypixel/hytale/codec/Codec.java @@ -0,0 +1,154 @@ +package com.hypixel.hytale.codec; + +import com.hypixel.hytale.codec.codecs.BsonDocumentCodec; +import com.hypixel.hytale.codec.codecs.UUIDBinaryCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.array.DoubleArrayCodec; +import com.hypixel.hytale.codec.codecs.array.FloatArrayCodec; +import com.hypixel.hytale.codec.codecs.array.IntArrayCodec; +import com.hypixel.hytale.codec.codecs.array.LongArrayCodec; +import com.hypixel.hytale.codec.codecs.simple.BooleanCodec; +import com.hypixel.hytale.codec.codecs.simple.ByteCodec; +import com.hypixel.hytale.codec.codecs.simple.DoubleCodec; +import com.hypixel.hytale.codec.codecs.simple.FloatCodec; +import com.hypixel.hytale.codec.codecs.simple.IntegerCodec; +import com.hypixel.hytale.codec.codecs.simple.LongCodec; +import com.hypixel.hytale.codec.codecs.simple.ShortCodec; +import com.hypixel.hytale.codec.codecs.simple.StringCodec; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.function.FunctionCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.SchemaConvertable; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonBinary; +import org.bson.BsonValue; + +public interface Codec extends RawJsonCodec, SchemaConvertable { + @Deprecated + BsonDocumentCodec BSON_DOCUMENT = new BsonDocumentCodec(); + StringCodec STRING = new StringCodec(); + BooleanCodec BOOLEAN = new BooleanCodec(); + DoubleCodec DOUBLE = new DoubleCodec(); + FloatCodec FLOAT = new FloatCodec(); + ByteCodec BYTE = new ByteCodec(); + ShortCodec SHORT = new ShortCodec(); + IntegerCodec INTEGER = new IntegerCodec(); + LongCodec LONG = new LongCodec(); + Pattern BASE64_PATTERN = Pattern.compile("^[0-9a-zA-Z+/]+$"); + @Deprecated + Codec BYTE_ARRAY = new Codec() { + public byte[] decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + return bsonValue.asBinary().getData(); + } + + @Nonnull + public BsonValue encode(@Nonnull byte[] bytes, ExtraInfo extraInfo) { + return new BsonBinary(bytes); + } + + @Nullable + public byte[] decodeJson(RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + reader.expect('['); + reader.consumeWhiteSpace(); + if (reader.tryConsume(']')) { + return new byte[0]; + } else { + int i = 0; + byte[] arr = new byte[10]; + + while (true) { + if (i == arr.length) { + arr = Arrays.copyOf(arr, i + 1 + (i >> 1)); + } + + extraInfo.pushIntKey(i, reader); + + try { + arr[i] = reader.readByteValue(); + i++; + } catch (Exception var9) { + throw new CodecException("Failed to decode", reader, extraInfo, var9); + } finally { + extraInfo.popKey(); + } + + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect(']', ',')) { + if (arr.length == i) { + return arr; + } + + return Arrays.copyOf(arr, i); + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + StringSchema base64 = new StringSchema(); + base64.setPattern(BASE64_PATTERN); + base64.setTitle("Binary"); + return base64; + } + }; + DoubleArrayCodec DOUBLE_ARRAY = new DoubleArrayCodec(); + FloatArrayCodec FLOAT_ARRAY = new FloatArrayCodec(); + IntArrayCodec INT_ARRAY = new IntArrayCodec(); + LongArrayCodec LONG_ARRAY = new LongArrayCodec(); + ArrayCodec STRING_ARRAY = new ArrayCodec<>(STRING, String[]::new); + FunctionCodec PATH = new FunctionCodec<>(STRING, x$0 -> Paths.get(x$0), Path::toString); + FunctionCodec INSTANT = new FunctionCodec<>(STRING, Instant::parse, Instant::toString); + FunctionCodec DURATION = new FunctionCodec<>(STRING, Duration::parse, Duration::toString); + FunctionCodec DURATION_SECONDS = new FunctionCodec<>( + DOUBLE, v -> Duration.ofNanos((long)(v * TimeUnit.SECONDS.toNanos(1L))), v -> v == null ? null : (double)v.toNanos() / TimeUnit.SECONDS.toNanos(1L) + ); + FunctionCodec LOG_LEVEL = new FunctionCodec<>(STRING, Level::parse, Level::toString); + UUIDBinaryCodec UUID_BINARY = new UUIDBinaryCodec(); + FunctionCodec UUID_STRING = new FunctionCodec<>(STRING, UUID::fromString, UUID::toString); + + @Nullable + @Deprecated + default T decode(BsonValue bsonValue) { + return this.decode(bsonValue, EmptyExtraInfo.EMPTY); + } + + @Nullable + T decode(BsonValue var1, ExtraInfo var2); + + @Deprecated + default BsonValue encode(T t) { + return this.encode(t, EmptyExtraInfo.EMPTY); + } + + BsonValue encode(T var1, ExtraInfo var2); + + @Nullable + @Override + default T decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + System.err.println("decodeJson: " + this.getClass()); + BsonValue bsonValue = RawJsonReader.readBsonValue(reader); + return this.decode(bsonValue, extraInfo); + } + + static boolean isNullBsonValue(@Nullable BsonValue bsonValue) { + return bsonValue == null || bsonValue.isNull(); + } +} diff --git a/src/com/hypixel/hytale/codec/DirectDecodeCodec.java b/src/com/hypixel/hytale/codec/DirectDecodeCodec.java new file mode 100644 index 0000000..d3fc448 --- /dev/null +++ b/src/com/hypixel/hytale/codec/DirectDecodeCodec.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.codec; + +import org.bson.BsonValue; + +public interface DirectDecodeCodec extends Codec { + void decode(BsonValue var1, T var2, ExtraInfo var3); +} diff --git a/src/com/hypixel/hytale/codec/DocumentContainingCodec.java b/src/com/hypixel/hytale/codec/DocumentContainingCodec.java new file mode 100644 index 0000000..fe4915f --- /dev/null +++ b/src/com/hypixel/hytale/codec/DocumentContainingCodec.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.codec; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.builder.BuilderField; +import com.hypixel.hytale.codec.function.BsonFunctionCodec; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Function; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; + +@Deprecated +public class DocumentContainingCodec extends BsonFunctionCodec { + public DocumentContainingCodec(@Nonnull BuilderCodec codec, @Nonnull BiConsumer setter, @Nonnull Function getter) { + super(codec, (value, bsonValue) -> { + BsonDocument document = bsonValue.asDocument().clone(); + + for (List> entry : codec.getEntries().values()) { + for (BuilderField field : entry) { + document.remove(field.getCodec().getKey()); + } + } + + setter.accept(value, document); + return value; + }, (bsonValue, value) -> { + BsonDocument document = getter.apply(value); + if (document != null) { + bsonValue.asDocument().putAll(document); + } + + return bsonValue; + }); + } +} diff --git a/src/com/hypixel/hytale/codec/EmptyExtraInfo.java b/src/com/hypixel/hytale/codec/EmptyExtraInfo.java new file mode 100644 index 0000000..b19fe3d --- /dev/null +++ b/src/com/hypixel/hytale/codec/EmptyExtraInfo.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.codec; + +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.codec.validation.ThrowingValidationResults; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; + +@Deprecated +public class EmptyExtraInfo extends ExtraInfo { + public static final EmptyExtraInfo EMPTY = new EmptyExtraInfo(); + + private EmptyExtraInfo() { + super(Integer.MAX_VALUE, ThrowingValidationResults::new); + } + + @Override + public void pushKey(String key) { + } + + @Override + public void pushIntKey(int i) { + } + + @Override + public void pushKey(String key, RawJsonReader reader) { + } + + @Override + public void pushIntKey(int key, RawJsonReader reader) { + } + + @Override + public void popKey() { + } + + @Override + public void addUnknownKey(@Nonnull String key) { + } + + @Override + public void ignoreUnusedKey(String key) { + } + + @Override + public void popIgnoredUnusedKey() { + } + + @Nonnull + @Override + public String peekKey() { + return ""; + } + + @Nonnull + @Override + public String peekKey(char separator) { + return ""; + } + + @Nonnull + @Override + public List getUnknownKeys() { + return Collections.emptyList(); + } + + @Override + public void appendDetailsTo(@Nonnull StringBuilder sb) { + sb.append("EmptyExtraInfo\n"); + } + + @Nonnull + @Override + public String toString() { + return "EmptyExtraInfo{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/codec/ExtraInfo.java b/src/com/hypixel/hytale/codec/ExtraInfo.java new file mode 100644 index 0000000..43dab66 --- /dev/null +++ b/src/com/hypixel/hytale/codec/ExtraInfo.java @@ -0,0 +1,293 @@ +package com.hypixel.hytale.codec; + +import com.hypixel.hytale.codec.store.CodecStore; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.codec.validation.ThrowingValidationResults; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.logger.util.GithubMessageUtil; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public class ExtraInfo { + public static final ThreadLocal THREAD_LOCAL = ThreadLocal.withInitial(ExtraInfo::new); + public static final String GENERATED_ID_PREFIX = "*"; + public static final int UNSET_VERSION = Integer.MAX_VALUE; + private final int legacyVersion; + private final int keysInitialSize = this instanceof EmptyExtraInfo ? 0 : 128; + @Nonnull + private String[] stringKeys = new String[this.keysInitialSize]; + @Nonnull + private int[] intKeys = new int[this.keysInitialSize]; + private int[] lineNumbers = GithubMessageUtil.isGithub() ? new int[this.keysInitialSize] : null; + private int[] columnNumbers = GithubMessageUtil.isGithub() ? new int[this.keysInitialSize] : null; + private int keysSize; + @Nonnull + private String[] ignoredUnknownKeys = new String[this.keysInitialSize]; + private int ignoredUnknownSize; + private final List unknownKeys = new ObjectArrayList<>(); + private final ValidationResults validationResults; + private final CodecStore codecStore; + @Deprecated + private final Map metadata = new Object2ObjectOpenHashMap<>(); + + public ExtraInfo() { + this.legacyVersion = Integer.MAX_VALUE; + this.validationResults = new ThrowingValidationResults(this); + this.codecStore = CodecStore.STATIC; + } + + @Deprecated + public ExtraInfo(int version) { + this.legacyVersion = version; + this.validationResults = new ThrowingValidationResults(this); + this.codecStore = CodecStore.STATIC; + } + + @Deprecated + public ExtraInfo(int version, @Nonnull Function validationResultsSupplier) { + this.legacyVersion = version; + this.validationResults = validationResultsSupplier.apply(this); + this.codecStore = CodecStore.STATIC; + } + + public int getVersion() { + return Integer.MAX_VALUE; + } + + @Deprecated + public int getLegacyVersion() { + return this.legacyVersion; + } + + public int getKeysSize() { + return this.keysSize; + } + + public CodecStore getCodecStore() { + return this.codecStore; + } + + private int nextKeyIndex() { + int index = this.keysSize++; + if (this.stringKeys.length <= index) { + int newLength = grow(index); + this.stringKeys = Arrays.copyOf(this.stringKeys, newLength); + this.intKeys = Arrays.copyOf(this.intKeys, newLength); + if (GithubMessageUtil.isGithub()) { + this.lineNumbers = Arrays.copyOf(this.lineNumbers, newLength); + this.columnNumbers = Arrays.copyOf(this.columnNumbers, newLength); + } + } + + return index; + } + + public void pushKey(String key) { + int index = this.nextKeyIndex(); + this.stringKeys[index] = key; + } + + public void pushIntKey(int key) { + int index = this.nextKeyIndex(); + this.intKeys[index] = key; + } + + public void pushKey(String key, RawJsonReader reader) { + int index = this.nextKeyIndex(); + this.stringKeys[index] = key; + if (GithubMessageUtil.isGithub()) { + this.lineNumbers[index] = reader.getLine(); + this.columnNumbers[index] = reader.getColumn(); + } + } + + public void pushIntKey(int key, RawJsonReader reader) { + int index = this.nextKeyIndex(); + this.intKeys[index] = key; + if (GithubMessageUtil.isGithub()) { + this.lineNumbers[index] = reader.getLine(); + this.columnNumbers[index] = reader.getColumn(); + } + } + + public void popKey() { + this.stringKeys[this.keysSize] = null; + this.keysSize--; + } + + private int nextIgnoredUnknownIndex() { + int index = this.ignoredUnknownSize++; + if (this.ignoredUnknownKeys.length <= index) { + this.ignoredUnknownKeys = Arrays.copyOf(this.ignoredUnknownKeys, grow(index)); + } + + return index; + } + + public void ignoreUnusedKey(String key) { + int index = this.nextIgnoredUnknownIndex(); + this.ignoredUnknownKeys[index] = key; + } + + public void popIgnoredUnusedKey() { + this.ignoredUnknownKeys[this.ignoredUnknownSize] = null; + this.ignoredUnknownSize--; + } + + public boolean consumeIgnoredUnknownKey(@Nonnull RawJsonReader reader) throws IOException { + if (this.ignoredUnknownSize <= 0) { + return false; + } else { + int lastIndex = this.ignoredUnknownSize - 1; + String ignoredUnknownKey = this.ignoredUnknownKeys[lastIndex]; + if (ignoredUnknownKey == null) { + return false; + } else if (!reader.tryConsumeString(ignoredUnknownKey)) { + return false; + } else { + this.ignoredUnknownKeys[lastIndex] = null; + return true; + } + } + } + + public boolean consumeIgnoredUnknownKey(@Nonnull String key) { + if (this.ignoredUnknownSize <= 0) { + return false; + } else { + int lastIndex = this.ignoredUnknownSize - 1; + if (!key.equals(this.ignoredUnknownKeys[lastIndex])) { + return false; + } else { + this.ignoredUnknownKeys[lastIndex] = null; + return true; + } + } + } + + public void readUnknownKey(@Nonnull RawJsonReader reader) throws IOException { + if (!this.consumeIgnoredUnknownKey(reader)) { + String key = reader.readString(); + if (this.keysSize == 0) { + this.unknownKeys.add(key); + } else { + this.unknownKeys.add(this.peekKey() + "." + key); + } + } + } + + public void addUnknownKey(@Nonnull String key) { + switch (key) { + case "$Title": + case "$Comment": + case "$TODO": + case "$Author": + case "$Position": + case "$FloatingFunctionNodes": + case "$Groups": + case "$WorkspaceID": + case "$NodeEditorMetadata": + case "$NodeId": + return; + default: + if (!this.consumeIgnoredUnknownKey(key)) { + if (this.keysSize == 0) { + if ("Parent".equals(key)) { + return; + } + + this.unknownKeys.add(key); + } else { + this.unknownKeys.add(this.peekKey() + "." + key); + } + } + } + } + + public String peekKey() { + return this.peekKey('.'); + } + + public String peekKey(char separator) { + if (this.keysSize == 0) { + return ""; + } else if (this.keysSize == 1) { + String str = this.stringKeys[0]; + return str != null ? str : String.valueOf(this.intKeys[0]); + } else { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < this.keysSize; i++) { + if (i > 0) { + sb.append(separator); + } + + String str = this.stringKeys[i]; + if (str != null) { + sb.append(str); + } else { + sb.append(this.intKeys[i]); + } + } + + return sb.toString(); + } + } + + public int peekLine() { + return GithubMessageUtil.isGithub() && this.keysSize > 0 ? this.lineNumbers[this.keysSize - 1] : -1; + } + + public int peekColumn() { + return GithubMessageUtil.isGithub() && this.keysSize > 0 ? this.columnNumbers[this.keysSize - 1] : -1; + } + + public List getUnknownKeys() { + return this.unknownKeys; + } + + public ValidationResults getValidationResults() { + return this.validationResults; + } + + @Deprecated + public Map getMetadata() { + return this.metadata; + } + + public void appendDetailsTo(@Nonnull StringBuilder sb) { + sb.append("ExtraInfo\n"); + } + + @Nonnull + @Override + public String toString() { + return "ExtraInfo{version=" + + this.legacyVersion + + ", stringKeys=" + + Arrays.toString((Object[])this.stringKeys) + + ", intKeys=" + + Arrays.toString(this.intKeys) + + ", keysSize=" + + this.keysSize + + ", ignoredUnknownKeys=" + + Arrays.toString((Object[])this.ignoredUnknownKeys) + + ", ignoredUnknownSize=" + + this.ignoredUnknownSize + + ", unknownKeys=" + + this.unknownKeys + + ", validationResults=" + + this.validationResults + + "}"; + } + + private static int grow(int oldSize) { + return oldSize + (oldSize >> 1); + } +} diff --git a/src/com/hypixel/hytale/codec/InheritCodec.java b/src/com/hypixel/hytale/codec/InheritCodec.java new file mode 100644 index 0000000..a0d330b --- /dev/null +++ b/src/com/hypixel/hytale/codec/InheritCodec.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.codec; + +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public interface InheritCodec extends Codec, RawJsonInheritCodec { + @Nullable + T decodeAndInherit(BsonDocument var1, T var2, ExtraInfo var3); + + void decodeAndInherit(BsonDocument var1, T var2, T var3, ExtraInfo var4); +} diff --git a/src/com/hypixel/hytale/codec/KeyedCodec.java b/src/com/hypixel/hytale/codec/KeyedCodec.java new file mode 100644 index 0000000..317532d --- /dev/null +++ b/src/com/hypixel/hytale/codec/KeyedCodec.java @@ -0,0 +1,219 @@ +package com.hypixel.hytale.codec; + +import com.hypixel.hytale.codec.exception.CodecException; +import java.util.Objects; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonSerializationException; +import org.bson.BsonValue; + +public class KeyedCodec { + @Nonnull + private final String key; + @Nonnull + private final Codec codec; + private final boolean required; + + public KeyedCodec(@Nonnull String key, Codec codec) { + this(key, codec, false); + } + + public KeyedCodec(@Nonnull String key, Codec codec, boolean required) { + this.key = Objects.requireNonNull(key, "key parameter can't be null"); + this.codec = Objects.requireNonNull(codec, "codec parameter can't be null"); + this.required = required; + if (key.isEmpty()) { + throw new IllegalArgumentException("Key must not be empty! Key: '" + key + "'"); + } else { + char firstCharFromKey = key.charAt(0); + if (Character.isLetter(firstCharFromKey) && !Character.isUpperCase(firstCharFromKey)) { + throw new IllegalArgumentException("Key must start with an upper case character! Key: '" + key + "'"); + } + } + } + + @Deprecated + public KeyedCodec(@Nonnull String key, Codec codec, boolean required, boolean bypassCaseCheck) { + this.key = Objects.requireNonNull(key, "key parameter can't be null"); + this.codec = Objects.requireNonNull(codec, "codec parameter can't be null"); + this.required = required; + if (key.isEmpty()) { + throw new IllegalArgumentException("Key must not be empty! Key: '" + key + "'"); + } else { + char firstCharFromKey = key.charAt(0); + if (!bypassCaseCheck && Character.isLetter(firstCharFromKey) && !Character.isUpperCase(firstCharFromKey)) { + throw new IllegalArgumentException("Key must start with an upper case character! Key: '" + key + "'"); + } + } + } + + @Nonnull + public String getKey() { + return this.key; + } + + @Deprecated + public T getNow(BsonDocument document) { + return this.getNow(document, EmptyExtraInfo.EMPTY); + } + + public T getNow(BsonDocument document, @Nonnull ExtraInfo extraInfo) { + return this.get(document, extraInfo).orElseThrow(() -> new BsonSerializationException(this.key + " does not exist in document!")); + } + + @Nullable + @Deprecated + public T getOrNull(BsonDocument document) { + return this.getOrNull(document, EmptyExtraInfo.EMPTY); + } + + @Nullable + public T getOrNull(BsonDocument document, @Nonnull ExtraInfo extraInfo) { + return this.get(document, extraInfo).orElse(null); + } + + @Nonnull + @Deprecated + public Optional get(BsonDocument document) { + return this.get(document, EmptyExtraInfo.EMPTY); + } + + @Nonnull + public Optional get(@Nullable BsonDocument document, @Nonnull ExtraInfo extraInfo) { + extraInfo.pushKey(this.key); + + Optional var4; + try { + if (document == null) { + return Optional.empty(); + } + + BsonValue bsonValue = document.get(this.key); + if (!Codec.isNullBsonValue(bsonValue)) { + return Optional.ofNullable(this.decode(bsonValue, extraInfo)); + } + + var4 = Optional.empty(); + } catch (Exception var8) { + throw new CodecException("Failed decode", (BsonValue)document, extraInfo, var8); + } finally { + extraInfo.popKey(); + } + + return var4; + } + + @Nullable + public T getOrDefault(@Nullable BsonDocument document, @Nonnull ExtraInfo extraInfo, T def) { + extraInfo.pushKey(this.key); + + Object var5; + try { + if (document == null) { + return def; + } + + BsonValue bsonValue = document.get(this.key); + if (!Codec.isNullBsonValue(bsonValue)) { + return this.codec.decode(bsonValue, extraInfo); + } + + var5 = def; + } catch (Exception var9) { + throw new CodecException("Failed decode", (BsonValue)document, extraInfo, var9); + } finally { + extraInfo.popKey(); + } + + return (T)var5; + } + + @Nonnull + public Optional getAndInherit(@Nullable BsonDocument document, T parent, @Nonnull ExtraInfo extraInfo) { + extraInfo.pushKey(this.key); + + Optional var5; + try { + if (document == null) { + return Optional.ofNullable(this.decodeAndInherit(null, parent, extraInfo)); + } + + BsonValue bsonValue = document.get(this.key); + if (!Codec.isNullBsonValue(bsonValue)) { + return Optional.ofNullable(this.decodeAndInherit(bsonValue, parent, extraInfo)); + } + + var5 = Optional.ofNullable(this.decodeAndInherit(null, parent, extraInfo)); + } catch (Exception var9) { + throw new CodecException("Failed decode", (BsonValue)document, extraInfo, var9); + } finally { + extraInfo.popKey(); + } + + return var5; + } + + @Deprecated + public void put(@Nonnull BsonDocument document, T t) { + this.put(document, t, EmptyExtraInfo.EMPTY); + } + + public void put(@Nonnull BsonDocument document, @Nullable T t, @Nonnull ExtraInfo extraInfo) { + if (t != null) { + try { + document.put(this.key, this.encode(t, extraInfo)); + } catch (Exception var5) { + throw new CodecException("Failed encode", t, extraInfo, var5); + } + } + } + + @Nullable + protected T decode(BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { + if (!this.required && Codec.isNullBsonValue(bsonValue)) { + return null; + } else { + try { + return this.codec.decode(bsonValue, extraInfo); + } catch (Exception var4) { + throw new CodecException("Failed to decode", bsonValue, extraInfo, var4); + } + } + } + + @Nullable + protected T decodeAndInherit(@Nullable BsonValue bsonValue, T parent, @Nonnull ExtraInfo extraInfo) { + if (!this.required && Codec.isNullBsonValue(bsonValue)) { + return null; + } else { + try { + return bsonValue != null && bsonValue.isDocument() && this.codec instanceof InheritCodec + ? ((InheritCodec)this.codec).decodeAndInherit(bsonValue.asDocument(), parent, extraInfo) + : this.codec.decode(bsonValue, extraInfo); + } catch (Exception var5) { + throw new CodecException("Failed to decode", bsonValue, extraInfo, var5); + } + } + } + + protected BsonValue encode(T t, ExtraInfo extraInfo) { + return this.codec.encode(t, extraInfo); + } + + @Nonnull + public Codec getChildCodec() { + return this.codec; + } + + public boolean isRequired() { + return this.required; + } + + @Nonnull + @Override + public String toString() { + return "KeyedCodec{key='" + this.key + "', codec=" + this.codec + "}"; + } +} diff --git a/src/com/hypixel/hytale/codec/PrimitiveCodec.java b/src/com/hypixel/hytale/codec/PrimitiveCodec.java new file mode 100644 index 0000000..558eae9 --- /dev/null +++ b/src/com/hypixel/hytale/codec/PrimitiveCodec.java @@ -0,0 +1,4 @@ +package com.hypixel.hytale.codec; + +public interface PrimitiveCodec { +} diff --git a/src/com/hypixel/hytale/codec/RawJsonCodec.java b/src/com/hypixel/hytale/codec/RawJsonCodec.java new file mode 100644 index 0000000..8a08491 --- /dev/null +++ b/src/com/hypixel/hytale/codec/RawJsonCodec.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.codec; + +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import javax.annotation.Nullable; + +public interface RawJsonCodec { + @Nullable + @Deprecated + default T decodeJson(RawJsonReader reader) throws IOException { + return this.decodeJson(reader, EmptyExtraInfo.EMPTY); + } + + @Nullable + T decodeJson(RawJsonReader var1, ExtraInfo var2) throws IOException; +} diff --git a/src/com/hypixel/hytale/codec/RawJsonInheritCodec.java b/src/com/hypixel/hytale/codec/RawJsonInheritCodec.java new file mode 100644 index 0000000..2fbd3f5 --- /dev/null +++ b/src/com/hypixel/hytale/codec/RawJsonInheritCodec.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.codec; + +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import javax.annotation.Nullable; + +public interface RawJsonInheritCodec extends RawJsonCodec { + @Nullable + T decodeAndInheritJson(RawJsonReader var1, T var2, ExtraInfo var3) throws IOException; + + void decodeAndInheritJson(RawJsonReader var1, T var2, T var3, ExtraInfo var4) throws IOException; +} diff --git a/src/com/hypixel/hytale/codec/VersionedExtraInfo.java b/src/com/hypixel/hytale/codec/VersionedExtraInfo.java new file mode 100644 index 0000000..350130c --- /dev/null +++ b/src/com/hypixel/hytale/codec/VersionedExtraInfo.java @@ -0,0 +1,124 @@ +package com.hypixel.hytale.codec; + +import com.hypixel.hytale.codec.store.CodecStore; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.codec.validation.ValidationResults; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +public class VersionedExtraInfo extends ExtraInfo { + private final int version; + private final ExtraInfo delegate; + + public VersionedExtraInfo(int version, ExtraInfo delegate) { + this.version = version; + this.delegate = delegate; + } + + @Override + public int getVersion() { + return this.version; + } + + @Override + public int getKeysSize() { + return this.delegate.getKeysSize(); + } + + @Override + public CodecStore getCodecStore() { + return this.delegate.getCodecStore(); + } + + @Override + public void pushKey(String key) { + this.delegate.pushKey(key); + } + + @Override + public void pushIntKey(int key) { + this.delegate.pushIntKey(key); + } + + @Override + public void pushKey(String key, RawJsonReader reader) { + this.delegate.pushKey(key, reader); + } + + @Override + public void pushIntKey(int key, RawJsonReader reader) { + this.delegate.pushIntKey(key, reader); + } + + @Override + public void popKey() { + this.delegate.popKey(); + } + + @Override + public void ignoreUnusedKey(String key) { + this.delegate.ignoreUnusedKey(key); + } + + @Override + public void popIgnoredUnusedKey() { + this.delegate.popIgnoredUnusedKey(); + } + + @Override + public boolean consumeIgnoredUnknownKey(@Nonnull RawJsonReader reader) throws IOException { + return this.delegate.consumeIgnoredUnknownKey(reader); + } + + @Override + public boolean consumeIgnoredUnknownKey(@Nonnull String key) { + return this.delegate.consumeIgnoredUnknownKey(key); + } + + @Override + public void readUnknownKey(@Nonnull RawJsonReader reader) throws IOException { + this.delegate.readUnknownKey(reader); + } + + @Override + public void addUnknownKey(@Nonnull String key) { + this.delegate.addUnknownKey(key); + } + + @Override + public String peekKey() { + return this.delegate.peekKey(); + } + + @Override + public String peekKey(char separator) { + return this.delegate.peekKey(separator); + } + + @Override + public List getUnknownKeys() { + return this.delegate.getUnknownKeys(); + } + + @Override + public ValidationResults getValidationResults() { + return this.delegate.getValidationResults(); + } + + @Override + public Map getMetadata() { + return this.delegate.getMetadata(); + } + + @Override + public void appendDetailsTo(@Nonnull StringBuilder sb) { + this.delegate.appendDetailsTo(sb); + } + + @Override + public int getLegacyVersion() { + return this.delegate.getLegacyVersion(); + } +} diff --git a/src/com/hypixel/hytale/codec/WrappedCodec.java b/src/com/hypixel/hytale/codec/WrappedCodec.java new file mode 100644 index 0000000..214cf4f --- /dev/null +++ b/src/com/hypixel/hytale/codec/WrappedCodec.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.codec; + +public interface WrappedCodec { + Codec getChildCodec(); +} diff --git a/src/com/hypixel/hytale/codec/builder/BuilderCodec.java b/src/com/hypixel/hytale/codec/builder/BuilderCodec.java new file mode 100644 index 0000000..c8e7c30 --- /dev/null +++ b/src/com/hypixel/hytale/codec/builder/BuilderCodec.java @@ -0,0 +1,1069 @@ +package com.hypixel.hytale.codec.builder; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.DirectDecodeCodec; +import com.hypixel.hytale.codec.EmptyExtraInfo; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.InheritCodec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.RawJsonCodec; +import com.hypixel.hytale.codec.VersionedExtraInfo; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.NullSchema; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.metadata.Metadata; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDisplayMode; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.codec.validation.ValidatableCodec; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.function.consumer.TriConsumer; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.Map.Entry; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class BuilderCodec implements Codec, DirectDecodeCodec, RawJsonCodec, InheritCodec, ValidatableCodec { + public static final int UNSET_VERSION = Integer.MIN_VALUE; + public static final int UNSET_MAX_VERSION = Integer.MAX_VALUE; + public static final int INITIAL_VERSION = 0; + public static final BuilderCodec[] EMPTY_ARRAY = new BuilderCodec[0]; + private static final KeyedCodec VERSION = new KeyedCodec<>("Version", INTEGER); + protected final Class tClass; + protected final Supplier supplier; + @Nullable + protected final BuilderCodec parentCodec; + @Nonnull + protected final Map>> entries; + @Nonnull + protected final Map>> unmodifiableEntries; + protected final BiConsumer validator; + protected final BiConsumer afterDecode; + protected final boolean hasNonNullValidator; + protected final String documentation; + protected final List metadata; + protected final int codecVersion; + protected final int minCodecVersion; + protected final boolean versioned; + /** @deprecated */ + protected final boolean useLegacyVersion; + @Nonnull + protected final StringTreeMap> stringTreeMap; + + protected BuilderCodec(@Nonnull BuilderCodec.BuilderBase builder) { + this.tClass = builder.tClass; + this.supplier = builder.supplier; + this.parentCodec = builder.parentCodec; + this.entries = Objects.requireNonNull(builder.entries, "entries parameter can't be null"); + this.unmodifiableEntries = Collections.unmodifiableMap(builder.entries); + this.stringTreeMap = builder.stringTreeMap; + this.validator = builder.validator; + this.afterDecode = builder.afterDecode; + this.documentation = builder.documentation; + this.metadata = builder.metadata; + boolean hasNonNullValidator = false; + + for (List> fields : this.entries.values()) { + fields.sort(Comparator.comparingInt(BuilderField::getMinVersion)); + + for (BuilderField field : fields) { + hasNonNullValidator |= field.hasNonNullValidator(); + } + } + + this.hasNonNullValidator = hasNonNullValidator; + int codecVersion; + if (builder.codecVersion != Integer.MIN_VALUE) { + codecVersion = builder.codecVersion; + } else { + int highestFieldVersion = Integer.MIN_VALUE; + + for (List> fields : this.entries.values()) { + for (BuilderField field : fields) { + highestFieldVersion = Math.max(highestFieldVersion, field.getHighestSupportedVersion()); + } + } + + codecVersion = highestFieldVersion; + } + + int minCodecVersion; + if (builder.minCodecVersion != Integer.MAX_VALUE) { + minCodecVersion = builder.minCodecVersion; + } else { + int lowestFieldVersion = Integer.MAX_VALUE; + + for (List> fields : this.entries.values()) { + for (BuilderField field : fields) { + int min = field.getMinVersion(); + if (min != Integer.MIN_VALUE) { + lowestFieldVersion = Math.min(lowestFieldVersion, min); + } + } + } + + minCodecVersion = lowestFieldVersion; + } + + if (this.parentCodec != null) { + codecVersion = Math.max(codecVersion, this.parentCodec.codecVersion); + minCodecVersion = Math.min(minCodecVersion, this.parentCodec.minCodecVersion); + this.versioned = builder.versioned || this.parentCodec.versioned; + this.useLegacyVersion = builder.useLegacyVersion || this.parentCodec.useLegacyVersion; + } else { + this.versioned = builder.versioned; + this.useLegacyVersion = builder.useLegacyVersion; + } + + this.codecVersion = codecVersion; + this.minCodecVersion = minCodecVersion; + } + + public Class getInnerClass() { + return this.tClass; + } + + public Supplier getSupplier() { + return this.supplier; + } + + public T getDefaultValue() { + return this.getDefaultValue(ExtraInfo.THREAD_LOCAL.get()); + } + + public T getDefaultValue(ExtraInfo extraInfo) { + T t = this.supplier.get(); + this.afterDecode(t, extraInfo); + return t; + } + + @Nonnull + public Map>> getEntries() { + return this.unmodifiableEntries; + } + + public BiConsumer getAfterDecode() { + return this.afterDecode; + } + + @Nullable + public BuilderCodec getParent() { + return this.parentCodec; + } + + public String getDocumentation() { + return this.documentation; + } + + public int getCodecVersion() { + return this.codecVersion; + } + + public void inherit(T t, @Nonnull T parent, @Nonnull ExtraInfo extraInfo) { + if (this.parentCodec != null) { + this.parentCodec.inherit(t, parent, extraInfo); + } + + if (this.getInnerClass().isAssignableFrom(parent.getClass())) { + for (List> entry : this.entries.values()) { + BuilderField field = findField(entry, extraInfo); + if (field != null) { + field.inherit(t, parent, extraInfo); + } + } + } + } + + public void afterDecode(T t, ExtraInfo extraInfo) { + if (this.parentCodec != null) { + this.parentCodec.afterDecode(t, extraInfo); + } + + if (this.afterDecode != null) { + this.afterDecode.accept(t, extraInfo); + } + } + + public void afterDecodeAndValidate(T t, @Nonnull ExtraInfo extraInfo) { + if (this.parentCodec != null) { + this.parentCodec.afterDecodeAndValidate(t, extraInfo); + } + + if (this.afterDecode != null) { + this.afterDecode.accept(t, extraInfo); + } + + ValidationResults results = extraInfo.getValidationResults(); + if (this.hasNonNullValidator) { + for (List> entry : this.entries.values()) { + BuilderField field = findField(entry, extraInfo); + if (field != null) { + extraInfo.pushKey(field.codec.getKey()); + + try { + field.nullValidate(t, results, extraInfo); + } finally { + extraInfo.popKey(); + } + } + } + + if (this.validator != null) { + this.validator.accept(t, results); + } + + results._processValidationResults(); + } else if (this.validator != null) { + this.validator.accept(t, results); + results._processValidationResults(); + } + } + + @Override + public T decode(@Nonnull BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { + if (this.supplier == null) { + throw new CodecException("This BuilderCodec is for an abstract or direct codec. To use this codec you must specify an existing object to decode into."); + } else { + T t = this.supplier.get(); + this.decode(bsonValue.asDocument(), t, extraInfo); + return t; + } + } + + @Nonnull + public BsonDocument encode(T t, @Nonnull ExtraInfo extraInfo) { + BsonDocument document = new BsonDocument(); + if (this.versioned) { + if (this.codecVersion != 0) { + VERSION.put(document, this.codecVersion, extraInfo); + } + + extraInfo = new VersionedExtraInfo(this.codecVersion, extraInfo); + } + + return this.encode0(t, document, extraInfo); + } + + @Override + public void decode(@Nonnull BsonValue bsonValue, T t, @Nonnull ExtraInfo extraInfo) { + this.decode0(bsonValue.asDocument(), t, extraInfo); + this.afterDecodeAndValidate(t, extraInfo); + } + + protected void decode0(@Nonnull BsonDocument document, T t, ExtraInfo extraInfo) { + if (this.versioned) { + extraInfo = this.decodeVersion(document, extraInfo); + } + + for (Entry entry : document.entrySet()) { + String key = entry.getKey(); + BuilderField field = findEntry(this, key, extraInfo); + if (field != null) { + field.decode(document, t, extraInfo); + } else { + extraInfo.addUnknownKey(key); + } + } + } + + @Nonnull + protected BsonDocument encode0(T t, @Nonnull BsonDocument document, @Nonnull ExtraInfo extraInfo) { + if (this.parentCodec != null) { + this.parentCodec.encode0(t, document, extraInfo); + } + + for (List> entry : this.entries.values()) { + BuilderField field = findField(entry, extraInfo); + if (field != null) { + field.encode(document, t, extraInfo); + } + } + + return document; + } + + @Override + public T decodeJson(@Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + if (this.supplier == null) { + throw new CodecException("This BuilderCodec is for an abstract or direct codec. To use this codec you must specify an existing object to decode into."); + } else { + T t = this.supplier.get(); + this.decodeJson0(reader, t, extraInfo); + this.afterDecodeAndValidate(t, extraInfo); + return t; + } + } + + public void decodeJson0(@Nonnull RawJsonReader reader, T t, ExtraInfo extraInfo) throws IOException { + if (this.versioned) { + extraInfo = this.decodeVersion(reader, extraInfo); + } + + reader.expect('{'); + reader.consumeWhiteSpace(); + if (!reader.tryConsume('}')) { + while (true) { + this.readEntry(reader, t, extraInfo); + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + return; + } + + reader.consumeWhiteSpace(); + } + } + } + + protected void readEntry(@Nonnull RawJsonReader reader, T t, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.mark(); + StringTreeMap> treeMapEntry = this.stringTreeMap.findEntry(reader); + BuilderCodec.KeyEntry keyEntry; + if (treeMapEntry != null && (keyEntry = treeMapEntry.getValue()) != null) { + switch (keyEntry.getType()) { + case FIELD: + String key = treeMapEntry.getKey(); + List> fields = keyEntry.getFields(); + this.readField(reader, t, extraInfo, key, fields); + break; + case IGNORE: + reader.unmark(); + this.skipField(reader); + break; + case IGNORE_IN_BASE_OBJECT: + if (extraInfo.getKeysSize() == 0) { + reader.unmark(); + this.skipField(reader); + } else { + reader.reset(); + this.readUnknownField(reader, extraInfo); + } + break; + default: + throw new IllegalArgumentException("Unknown field entry type: " + keyEntry.getType()); + } + } else { + reader.reset(); + this.readUnknownField(reader, extraInfo); + } + } + + private void skipField(@Nonnull RawJsonReader reader) throws IOException { + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + reader.skipValue(); + } + + private void readField(@Nonnull RawJsonReader reader, T t, @Nonnull ExtraInfo extraInfo, String key, @Nonnull List> fields) throws IOException { + BuilderField entry = null; + + for (BuilderField field : fields) { + if (field.supportsVersion(extraInfo.getVersion())) { + entry = field; + break; + } + } + + if (entry == null) { + reader.reset(); + this.readUnknownField(reader, extraInfo); + } else { + reader.unmark(); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + extraInfo.pushKey(key, reader); + + try { + entry.decodeJson(reader, t, extraInfo); + } catch (Exception var12) { + throw new CodecException("Failed to decode", reader, extraInfo, var12); + } finally { + extraInfo.popKey(); + } + } + } + + private void readUnknownField(@Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + extraInfo.readUnknownKey(reader); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + reader.skipValue(); + } + + public void decodeJson(@Nonnull RawJsonReader reader, T t, @Nonnull ExtraInfo extraInfo) throws IOException { + this.decodeJson0(reader, t, extraInfo); + this.afterDecodeAndValidate(t, extraInfo); + } + + @Override + public T decodeAndInherit(@Nonnull BsonDocument document, T parent, ExtraInfo extraInfo) { + T t = this.supplier.get(); + this.decodeAndInherit(document, t, parent, extraInfo); + return t; + } + + @Override + public void decodeAndInherit(@Nonnull BsonDocument document, T t, @Nullable T parent, ExtraInfo extraInfo) { + if (this.versioned) { + extraInfo = this.decodeVersion(document, extraInfo); + } + + if (parent != null) { + this.inherit(t, parent, extraInfo); + } + + this.decodeAndInherit0(document, t, parent, extraInfo); + this.afterDecodeAndValidate(t, extraInfo); + } + + protected void decodeAndInherit0(@Nonnull BsonDocument document, T t, T parent, @Nonnull ExtraInfo extraInfo) { + for (Entry entry : document.entrySet()) { + String key = entry.getKey(); + BuilderField field = findEntry(this, key, extraInfo); + if (field != null) { + if (field.codec.getChildCodec() instanceof BuilderCodec) { + decodeAndInherit(field, document, t, parent, extraInfo); + } else { + field.decodeAndInherit(document, t, parent, extraInfo); + } + } else { + extraInfo.addUnknownKey(key); + } + } + } + + private static void decodeAndInherit( + @Nonnull BuilderField entry, @Nonnull BsonDocument document, Type t, @Nullable Type parent, @Nonnull ExtraInfo extraInfo + ) { + KeyedCodec codec = entry.codec; + String key = codec.getKey(); + BsonValue bsonValue = document.get(key); + if (Codec.isNullBsonValue(bsonValue)) { + if (bsonValue != null && bsonValue.isNull()) { + entry.setValue(t, null, extraInfo); + } + } else { + extraInfo.pushKey(key); + + try { + BuilderCodec inheritCodec = (BuilderCodec)codec.getChildCodec(); + FieldType value = inheritCodec.getSupplier().get(); + FieldType parentValue = parent != null ? entry.getter.apply(parent, extraInfo) : null; + inheritCodec.decodeAndInherit(bsonValue.asDocument(), value, parentValue, extraInfo); + entry.setValue(t, value, extraInfo); + } catch (Exception var14) { + throw new CodecException("Failed to decode", bsonValue, extraInfo, var14); + } finally { + extraInfo.popKey(); + } + } + } + + @Override + public T decodeAndInheritJson(@Nonnull RawJsonReader reader, T parent, ExtraInfo extraInfo) throws IOException { + T t = this.supplier.get(); + this.decodeAndInheritJson(reader, t, parent, extraInfo); + return t; + } + + @Override + public void decodeAndInheritJson(@Nonnull RawJsonReader reader, T t, @Nullable T parent, ExtraInfo extraInfo) throws IOException { + if (this.versioned) { + extraInfo = this.decodeVersion(reader, extraInfo); + } + + if (parent != null) { + this.inherit(t, parent, extraInfo); + } + + this.decodeAndInheritJson0(reader, t, parent, extraInfo); + this.afterDecodeAndValidate(t, extraInfo); + } + + public void decodeAndInheritJson0(@Nonnull RawJsonReader reader, T t, T parent, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.expect('{'); + reader.consumeWhiteSpace(); + if (!reader.tryConsume('}')) { + while (true) { + this.readAndInheritEntry(reader, t, parent, extraInfo); + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + return; + } + + reader.consumeWhiteSpace(); + } + } + } + + protected void readAndInheritEntry(@Nonnull RawJsonReader reader, T t, T parent, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.mark(); + StringTreeMap> treeMapEntry = this.stringTreeMap.findEntry(reader); + BuilderCodec.KeyEntry keyEntry; + if (treeMapEntry != null && (keyEntry = treeMapEntry.getValue()) != null) { + switch (keyEntry.getType()) { + case FIELD: + String key = treeMapEntry.getKey(); + List> fields = keyEntry.getFields(); + this.readAndInheritField(reader, t, parent, extraInfo, key, fields); + break; + case IGNORE: + reader.unmark(); + this.skipField(reader); + break; + case IGNORE_IN_BASE_OBJECT: + if (extraInfo.getKeysSize() == 0) { + reader.unmark(); + this.skipField(reader); + } else { + reader.reset(); + this.readUnknownField(reader, extraInfo); + } + } + } else { + reader.reset(); + this.readUnknownField(reader, extraInfo); + } + } + + private void readAndInheritField( + @Nonnull RawJsonReader reader, T t, T parent, @Nonnull ExtraInfo extraInfo, String key, @Nonnull List> fields + ) throws IOException { + BuilderField entry = null; + + for (BuilderField field : fields) { + if (field.supportsVersion(extraInfo.getVersion())) { + entry = field; + break; + } + } + + if (entry == null) { + reader.reset(); + this.readUnknownField(reader, extraInfo); + } else { + reader.unmark(); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + extraInfo.pushKey(key, reader); + + try { + if (entry.codec.getChildCodec() instanceof BuilderCodec) { + decodeAndInheritJson(entry, reader, t, parent, extraInfo); + } else { + entry.decodeAndInheritJson(reader, t, parent, extraInfo); + } + } catch (Exception var13) { + throw new CodecException("Failed to decode", reader, extraInfo, var13); + } finally { + extraInfo.popKey(); + } + } + } + + private static void decodeAndInheritJson( + @Nonnull BuilderField entry, @Nonnull RawJsonReader reader, Type t, @Nullable Type parent, @Nonnull ExtraInfo extraInfo + ) throws IOException { + int read = reader.peek(); + if (read == -1) { + throw new IOException("Unexpected EOF!"); + } else { + switch (read) { + case 78: + case 110: + reader.readNullValue(); + entry.setValue(t, null, extraInfo); + return; + default: + BuilderCodec inheritCodec = (BuilderCodec)entry.codec.getChildCodec(); + FieldType value = inheritCodec.getSupplier().get(); + FieldType parentValue = parent != null ? entry.getter.apply(parent, extraInfo) : null; + inheritCodec.decodeAndInheritJson(reader, value, parentValue, extraInfo); + entry.setValue(t, value, extraInfo); + } + } + } + + @Nonnull + protected ExtraInfo decodeVersion(BsonDocument document, @Nonnull ExtraInfo extraInfo) { + if (this.useLegacyVersion && extraInfo.getLegacyVersion() != Integer.MAX_VALUE) { + return new VersionedExtraInfo(extraInfo.getLegacyVersion(), extraInfo); + } else { + int version = VERSION.get(document, extraInfo).orElse(0); + if (version > this.codecVersion) { + throw new IllegalArgumentException("Version " + version + " is newer than expected version " + this.codecVersion); + } else if (this.minCodecVersion != Integer.MAX_VALUE && version < this.minCodecVersion) { + throw new IllegalArgumentException("Version " + version + " is older than min supported version " + this.minCodecVersion); + } else { + return new VersionedExtraInfo(version, extraInfo); + } + } + } + + @Nonnull + protected ExtraInfo decodeVersion(@Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + if (this.useLegacyVersion && extraInfo.getLegacyVersion() != Integer.MAX_VALUE) { + return new VersionedExtraInfo(extraInfo.getLegacyVersion(), extraInfo); + } else { + reader.mark(); + int version = 0; + if (RawJsonReader.seekToKey(reader, VERSION.getKey())) { + version = reader.readIntValue(); + } + + if (version > this.codecVersion) { + throw new IllegalArgumentException("Version " + version + " is newer than expected version " + this.codecVersion); + } else if (this.minCodecVersion != Integer.MAX_VALUE && version < this.minCodecVersion) { + throw new IllegalArgumentException("Version " + version + " is older than min supported version " + this.minCodecVersion); + } else { + reader.reset(); + extraInfo.ignoreUnusedKey(VERSION.getKey()); + return new VersionedExtraInfo(version, extraInfo); + } + } + } + + @Override + public void validate(T t, @Nonnull ExtraInfo extraInfo) { + if (this.parentCodec != null) { + this.parentCodec.validate(t, extraInfo); + } + + for (List> entry : this.entries.values()) { + BuilderField field = findField(entry, extraInfo); + if (field != null) { + extraInfo.pushKey(field.codec.getKey()); + + try { + field.validate(t, extraInfo); + } finally { + extraInfo.popKey(); + } + } + } + } + + @Override + public void validateDefaults(@Nonnull ExtraInfo extraInfo, @Nonnull Set> tested) { + if (tested.add(this)) { + T t = this.supplier.get(); + this.afterDecode(t, extraInfo); + + for (BuilderCodec codec = this; codec != null; codec = (BuilderCodec)codec.parentCodec) { + for (List> entry : codec.entries.values()) { + BuilderField field = findField(entry, extraInfo); + if (field != null) { + extraInfo.pushKey(field.codec.getKey()); + + try { + field.validateDefaults(t, extraInfo, tested); + } finally { + extraInfo.popKey(); + } + } + } + } + } + } + + @Nonnull + public ObjectSchema toSchema(@Nonnull SchemaContext context) { + T t = this.getDefaultValue(); + return this.toSchema(context, t); + } + + @Nonnull + public ObjectSchema toSchema(@Nonnull SchemaContext context, @Nullable T def) { + ObjectSchema schema = new ObjectSchema(); + schema.setAdditionalProperties(false); + schema.setTitle(this.tClass.getSimpleName()); + schema.setMarkdownDescription(this.documentation); + schema.getHytale().setMergesProperties(true); + Map properties = new Object2ObjectLinkedOpenHashMap<>(); + if (this.versioned) { + properties.put(VERSION.getKey(), VERSION.getChildCodec().toSchema(context)); + } + + Schema comment = new Schema(); + comment.getHytale().setUiPropertyTitle("Comment"); + comment.setDoNotSuggest(true); + comment.setDescription("Comments don't have any function other than allowing users to add certain internal comments or notes to an asset"); + UIDisplayMode.HIDDEN.modify(comment); + properties.put("$Title", comment); + properties.put("$Comment", comment); + properties.put("$Author", comment); + properties.put("$TODO", comment); + properties.put("$Position", comment); + properties.put("$FloatingFunctionNodes", comment); + properties.put("$Groups", comment); + properties.put("$WorkspaceID", comment); + properties.put("$NodeId", comment); + properties.put("$NodeEditorMetadata", comment); + schema.setProperties(properties); + createSchemaFields(context, def, this, properties); + if (this.metadata != null) { + for (int i = 0; i < this.metadata.size(); i++) { + Metadata meta = this.metadata.get(i); + meta.modify(schema); + } + } + + return schema; + } + + private static void createSchemaFields( + @Nonnull SchemaContext context, @Nullable T def, @Nonnull BuilderCodec codec, @Nonnull Map properties + ) { + if (codec.parentCodec != null) { + createSchemaFields(context, def, codec.parentCodec, properties); + } + + for (Entry>> entry : codec.getEntries().entrySet()) { + String key = entry.getKey(); + List> fields = entry.getValue(); + BuilderField field = fields.getLast(); + Codec c = field.getCodec().getChildCodec(); + + Object defC; + try { + defC = field.getter.apply(def, EmptyExtraInfo.EMPTY); + } catch (UnsupportedOperationException var14) { + continue; + } + + Schema fieldSchema = context.refDefinition(c, (T)defC); + field.updateSchema(context, fieldSchema); + Schema finalSchema = fieldSchema; + String type = Schema.CODEC.getIdFor((Class)fieldSchema.getClass()); + if (!type.isEmpty()) { + if (!field.hasNonNullValidator() && !field.isPrimitive) { + fieldSchema.setTypes(new String[]{type, "null"}); + } + + properties.put(key, fieldSchema); + } else if (field.hasNonNullValidator()) { + properties.put(key, fieldSchema); + } else { + properties.put(key, finalSchema = Schema.anyOf(fieldSchema, NullSchema.INSTANCE)); + } + + finalSchema.setMarkdownDescription(field.getDocumentation()); + } + } + + @Nonnull + @Override + public String toString() { + return "BuilderCodec{supplier=" + + this.supplier + + ", parentCodec=" + + this.parentCodec + + ", entries=" + + this.entries + + ", afterDecodeAndValidate=" + + this.afterDecode + + "}"; + } + + @Nullable + protected static BuilderField findEntry(@Nonnull BuilderCodec current, String key, @Nonnull ExtraInfo extraInfo) { + List> fields = current.entries.get(key); + if (fields != null && fields.size() == 1) { + BuilderField field = (BuilderField)fields.getFirst(); + if (field.supportsVersion(extraInfo.getVersion())) { + return field; + } + } + + BuilderField entry; + for (entry = null; current != null; current = current.parentCodec) { + entry = findField(current.entries.get(key), extraInfo); + if (entry != null) { + return entry; + } + } + + return entry; + } + + @Nullable + protected static > F findField(@Nullable List entry, @Nonnull ExtraInfo extraInfo) { + if (entry == null) { + return null; + } else { + int i = 0; + + for (int size = entry.size(); i < size; i++) { + F field = (F)entry.get(i); + if (field.supportsVersion(extraInfo.getVersion())) { + return field; + } + } + + return null; + } + } + + @Nonnull + public static BuilderCodec.Builder builder(Class tClass, Supplier supplier) { + return new BuilderCodec.Builder<>(tClass, supplier); + } + + @Nonnull + public static BuilderCodec.Builder builder(Class tClass, Supplier supplier, BuilderCodec parentCodec) { + return new BuilderCodec.Builder<>(tClass, supplier, parentCodec); + } + + @Nonnull + public static BuilderCodec.Builder abstractBuilder(Class tClass) { + return new BuilderCodec.Builder<>(tClass, null); + } + + @Nonnull + public static BuilderCodec.Builder abstractBuilder(Class tClass, BuilderCodec parentCodec) { + return new BuilderCodec.Builder<>(tClass, null, parentCodec); + } + + public static class Builder extends BuilderCodec.BuilderBase> { + protected Builder(Class tClass, Supplier supplier) { + super(tClass, supplier); + } + + protected Builder(Class tClass, Supplier supplier, @Nullable BuilderCodec parentCodec) { + super(tClass, supplier, parentCodec); + } + } + + public abstract static class BuilderBase> { + protected final Class tClass; + protected final Supplier supplier; + @Nullable + protected final BuilderCodec parentCodec; + protected final Map>> entries = new Object2ObjectLinkedOpenHashMap<>(); + @Nonnull + protected final StringTreeMap> stringTreeMap; + protected BiConsumer validator; + protected BiConsumer afterDecode; + protected String documentation; + protected List metadata; + protected int codecVersion = Integer.MIN_VALUE; + protected int minCodecVersion = Integer.MAX_VALUE; + protected boolean versioned = false; + protected boolean useLegacyVersion = false; + + protected BuilderBase(Class tClass, Supplier supplier) { + this(tClass, supplier, null); + } + + protected BuilderBase(Class tClass, Supplier supplier, @Nullable BuilderCodec parentCodec) { + this.tClass = tClass; + this.supplier = supplier; + this.parentCodec = parentCodec; + if (parentCodec != null) { + this.stringTreeMap = new StringTreeMap<>((StringTreeMap>)parentCodec.stringTreeMap); + } else { + this.stringTreeMap = new StringTreeMap<>( + Map.ofEntries( + Map.entry("$Title", new BuilderCodec.KeyEntry(BuilderCodec.EntryType.IGNORE)), + Map.entry("$Comment", new BuilderCodec.KeyEntry(BuilderCodec.EntryType.IGNORE)), + Map.entry("$TODO", new BuilderCodec.KeyEntry(BuilderCodec.EntryType.IGNORE)), + Map.entry("$Author", new BuilderCodec.KeyEntry(BuilderCodec.EntryType.IGNORE)), + Map.entry("$Position", new BuilderCodec.KeyEntry(BuilderCodec.EntryType.IGNORE)), + Map.entry("$FloatingFunctionNodes", new BuilderCodec.KeyEntry(BuilderCodec.EntryType.IGNORE)), + Map.entry("$Groups", new BuilderCodec.KeyEntry(BuilderCodec.EntryType.IGNORE)), + Map.entry("$WorkspaceID", new BuilderCodec.KeyEntry(BuilderCodec.EntryType.IGNORE)), + Map.entry("$NodeEditorMetadata", new BuilderCodec.KeyEntry(BuilderCodec.EntryType.IGNORE)), + Map.entry("$NodeId", new BuilderCodec.KeyEntry(BuilderCodec.EntryType.IGNORE)), + Map.entry("Parent", new BuilderCodec.KeyEntry(BuilderCodec.EntryType.IGNORE_IN_BASE_OBJECT)) + ) + ); + } + } + + private S self() { + return (S)this; + } + + @Nonnull + public S documentation(String doc) { + this.documentation = doc; + return this.self(); + } + + @Nonnull + public S versioned() { + this.versioned = true; + return this.self(); + } + + @Nonnull + @Deprecated + public S legacyVersioned() { + this.versioned = true; + this.useLegacyVersion = true; + return this.self(); + } + + @Nonnull + @Deprecated + public S addField(@Nonnull KeyedCodec codec, @Nonnull BiConsumer setter, @Nonnull Function getter) { + return this.addField(new BuilderField<>(codec, (t, fieldType, extraInfo) -> setter.accept(t, fieldType), (t1, extraInfo1) -> getter.apply(t1), null)); + } + + @Nonnull + public BuilderField.FieldBuilder append( + KeyedCodec codec, @Nonnull BiConsumer setter, @Nonnull Function getter + ) { + return this.append(codec, (t, fieldType, extraInfo) -> setter.accept(t, fieldType), (t, extraInfo) -> getter.apply(t)); + } + + @Nonnull + public BuilderField.FieldBuilder append( + KeyedCodec codec, TriConsumer setter, BiFunction getter + ) { + return new BuilderField.FieldBuilder<>(this.self(), codec, setter, getter, null); + } + + @Nonnull + public BuilderField.FieldBuilder appendInherited( + KeyedCodec codec, @Nonnull BiConsumer setter, @Nonnull Function getter, @Nonnull BiConsumer inherit + ) { + return this.appendInherited( + codec, + (t, fieldType, extraInfo) -> setter.accept(t, fieldType), + (t, extraInfo) -> getter.apply(t), + (t, parent, extraInfo) -> inherit.accept(t, parent) + ); + } + + @Nonnull + public BuilderField.FieldBuilder appendInherited( + KeyedCodec codec, + TriConsumer setter, + BiFunction getter, + TriConsumer inherit + ) { + return new BuilderField.FieldBuilder<>(this.self(), codec, setter, getter, inherit); + } + + @Nonnull + public S addField(@Nonnull BuilderField entry) { + if (entry.getMinVersion() > entry.getMaxVersion()) { + throw new IllegalArgumentException("Min version must be less than the max version: " + entry); + } else { + List> fields = this.entries.computeIfAbsent(entry.getCodec().getKey(), k -> new ObjectArrayList<>()); + + for (BuilderField field : fields) { + if (entry.getMaxVersion() >= field.getMinVersion() && entry.getMinVersion() <= field.getMaxVersion()) { + throw new IllegalArgumentException("Field already defined for this version range!"); + } + } + + fields.add(entry); + this.stringTreeMap.put(entry.getCodec().getKey(), new BuilderCodec.KeyEntry<>(fields)); + return this.self(); + } + } + + @Nonnull + public S afterDecode(@Nonnull Consumer afterDecode) { + Objects.requireNonNull(afterDecode, "afterDecodeAndValidate can't be null!"); + return this.afterDecode((t, extraInfo) -> afterDecode.accept(t)); + } + + @Nonnull + public S afterDecode(BiConsumer afterDecode) { + this.afterDecode = Objects.requireNonNull(afterDecode, "afterDecodeAndValidate can't be null!"); + return this.self(); + } + + @Nonnull + @Deprecated + public S validator(BiConsumer validator) { + this.validator = Objects.requireNonNull(validator, "validator can't be null!"); + return this.self(); + } + + @Nonnull + public S metadata(Metadata metadata) { + if (this.metadata == null) { + this.metadata = new ObjectArrayList<>(); + } + + this.metadata.add(metadata); + return this.self(); + } + + @Nonnull + public S codecVersion(int minCodecVersion, int codecVersion) { + this.minCodecVersion = minCodecVersion; + this.codecVersion = codecVersion; + return this.self(); + } + + @Nonnull + public S codecVersion(int codecVersion) { + this.minCodecVersion = 0; + this.codecVersion = codecVersion; + return this.self(); + } + + @Nonnull + public BuilderCodec build() { + return new BuilderCodec<>(this); + } + } + + protected static enum EntryType { + FIELD, + IGNORE, + IGNORE_IN_BASE_OBJECT; + + private EntryType() { + } + } + + protected static class KeyEntry { + private final BuilderCodec.EntryType type; + @Nullable + private final List> fields; + + public KeyEntry(BuilderCodec.EntryType type) { + this.type = type; + this.fields = null; + } + + public KeyEntry(List> fields) { + this.type = BuilderCodec.EntryType.FIELD; + this.fields = fields; + } + + public BuilderCodec.EntryType getType() { + return this.type; + } + + @Nullable + public List> getFields() { + return this.fields; + } + } +} diff --git a/src/com/hypixel/hytale/codec/builder/BuilderField.java b/src/com/hypixel/hytale/codec/builder/BuilderField.java new file mode 100644 index 0000000..b4c182c --- /dev/null +++ b/src/com/hypixel/hytale/codec/builder/BuilderField.java @@ -0,0 +1,411 @@ +package com.hypixel.hytale.codec.builder; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.InheritCodec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.PrimitiveCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.metadata.Metadata; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.codec.validation.LateValidator; +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.codec.validation.ValidatableCodec; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.codec.validation.validator.DeprecatedValidator; +import com.hypixel.hytale.codec.validation.validator.NonNullValidator; +import com.hypixel.hytale.function.consumer.TriConsumer; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Predicate; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public class BuilderField { + @Nonnull + protected final KeyedCodec codec; + @Nonnull + protected final TriConsumer setter; + @Nonnull + protected final BiFunction getter; + protected final TriConsumer inherit; + @Nullable + protected final List> validators; + @Nullable + protected final List metadata; + protected final int minVersion; + protected final int maxVersion; + @Nullable + protected final String documentation; + @Nullable + protected final NonNullValidator nonNullValidator; + protected final boolean isPrimitive; + + protected BuilderField(@Nonnull BuilderField.FieldBuilder builder) { + this.codec = builder.codec; + this.setter = builder.setter; + this.getter = builder.getter; + this.inherit = builder.inherit; + this.validators = builder.validators; + this.metadata = builder.metadata; + this.minVersion = builder.minVersion; + this.maxVersion = builder.maxVersion; + this.documentation = builder.documentation; + if (builder.validators == null) { + this.nonNullValidator = null; + } else { + NonNullValidator found = null; + + for (Validator validator : builder.validators) { + if (validator instanceof NonNullValidator) { + found = (NonNullValidator)validator; + break; + } + } + + this.nonNullValidator = found; + } + + this.isPrimitive = this.codec.getChildCodec() instanceof PrimitiveCodec; + } + + protected BuilderField( + @Nonnull KeyedCodec codec, + TriConsumer setter, + BiFunction getter, + TriConsumer inherit + ) { + this.codec = Objects.requireNonNull(codec, "codec parameter can't be null"); + this.setter = Objects.requireNonNull(setter, "setter parameter can't be null"); + this.getter = Objects.requireNonNull(getter, "getter parameter can't be null"); + this.inherit = inherit; + this.validators = null; + this.metadata = null; + this.minVersion = Integer.MIN_VALUE; + this.maxVersion = Integer.MAX_VALUE; + this.documentation = null; + this.nonNullValidator = null; + this.isPrimitive = codec.getChildCodec() instanceof PrimitiveCodec; + } + + @Nonnull + public KeyedCodec getCodec() { + return this.codec; + } + + public int getMinVersion() { + return this.minVersion; + } + + public int getMaxVersion() { + return this.maxVersion; + } + + public int getHighestSupportedVersion() { + return this.maxVersion != Integer.MAX_VALUE ? this.maxVersion : this.minVersion; + } + + public boolean supportsVersion(int version) { + return version == Integer.MIN_VALUE || version >= this.minVersion && version <= this.maxVersion; + } + + @Nullable + public List> getValidators() { + return this.validators; + } + + public boolean hasNonNullValidator() { + return this.nonNullValidator != null; + } + + @Nullable + public String getDocumentation() { + return this.documentation; + } + + public void decode(BsonDocument document, Type t, @Nonnull ExtraInfo extraInfo) { + FieldType value = this.codec.getOrNull(document, extraInfo); + this.setValue(t, value, extraInfo); + } + + public void decodeAndInherit(BsonDocument document, Type t, @Nullable Type parent, @Nonnull ExtraInfo extraInfo) { + FieldType parentValue = parent != null ? this.getter.apply(parent, extraInfo) : null; + FieldType value = this.codec.getAndInherit(document, parentValue, extraInfo).orElse(null); + this.setValue(t, value, extraInfo); + } + + public void encode(@Nonnull BsonDocument document, Type t, @Nonnull ExtraInfo extraInfo) { + FieldType value = this.getter.apply(t, extraInfo); + if (value != null) { + this.codec.put(document, value, extraInfo); + } + } + + public void inherit(Type t, Type parent, ExtraInfo extraInfo) { + if (this.inherit != null) { + this.inherit.accept(t, parent, extraInfo); + } + } + + public void decodeJson(@Nonnull RawJsonReader reader, Type t, @Nonnull ExtraInfo extraInfo) throws IOException { + int read = reader.peek(); + if (read == -1) { + throw new IOException("Unexpected EOF!"); + } else { + switch (read) { + case 78: + case 110: + reader.readNullValue(); + this.setValue(t, null, extraInfo); + return; + default: + FieldType value = this.codec.getChildCodec().decodeJson(reader, extraInfo); + this.setValue(t, value, extraInfo); + } + } + } + + public void decodeAndInheritJson(@Nonnull RawJsonReader reader, Type t, @Nullable Type parent, @Nonnull ExtraInfo extraInfo) throws IOException { + int read = reader.peek(); + if (read == -1) { + throw new IOException("Unexpected EOF!"); + } else { + switch (read) { + case 78: + case 110: + reader.readNullValue(); + this.setValue(t, null, extraInfo); + return; + default: + Codec child = this.codec.getChildCodec(); + if (child instanceof InheritCodec) { + FieldType parentValue = parent != null ? this.getter.apply(parent, extraInfo) : null; + FieldType value = ((InheritCodec)child).decodeAndInheritJson(reader, parentValue, extraInfo); + this.setValue(t, value, extraInfo); + } else { + FieldType value = child.decodeJson(reader, extraInfo); + this.setValue(t, value, extraInfo); + } + } + } + } + + public void setValue(Type t, @Nullable FieldType value, @Nonnull ExtraInfo extraInfo) { + if (this.validators != null) { + ValidationResults results = extraInfo.getValidationResults(); + + for (int i = 0; i < this.validators.size(); i++) { + this.validators.get(i).accept(value, results); + } + + results._processValidationResults(); + } + + if (this.isPrimitive && value == null) { + ValidationResults results = extraInfo.getValidationResults(); + Validators.nonNull().accept(null, results); + results._processValidationResults(); + } else { + this.setter.accept(t, value, extraInfo); + } + } + + public void validate(Type t, @Nonnull ExtraInfo extraInfo) { + FieldType value = this.getter.apply(t, extraInfo); + this.validateValue(value, extraInfo, null); + } + + public void validateDefaults(Type t, @Nonnull ExtraInfo extraInfo, Set> tested) { + FieldType defaultValue = this.getter.apply(t, extraInfo); + if (defaultValue != null && !this.codec.isRequired() && this.nonNullValidator == null) { + this.validateValue(defaultValue, extraInfo, v -> v instanceof DeprecatedValidator); + } + + Codec childCodec = this.codec.getChildCodec(); + ValidatableCodec.validateDefaults(childCodec, extraInfo, tested); + } + + private void validateValue(FieldType value, @Nonnull ExtraInfo extraInfo, @Nullable Predicate> filter) { + if (this.codec instanceof ValidatableCodec) { + ((ValidatableCodec)this.codec).validate(value, extraInfo); + } + + if (this.validators != null) { + ValidationResults results = extraInfo.getValidationResults(); + + for (int i = 0; i < this.validators.size(); i++) { + Validator validator = this.validators.get(i); + if ((filter == null || !filter.test(validator)) && !(validator instanceof LateValidator)) { + validator.accept(value, results); + } + } + + results._processValidationResults(); + } + } + + public void nullValidate(Type t, @Nonnull ValidationResults results, ExtraInfo extraInfo) { + if (this.nonNullValidator != null) { + FieldType apply = this.getter.apply(t, extraInfo); + if (apply == null) { + this.nonNullValidator.accept(null, results); + results._processValidationResults(); + } + } + } + + public void updateSchema(SchemaContext context, @Nonnull Schema target) { + if (this.validators != null) { + for (int i = 0; i < this.validators.size(); i++) { + Validator validator = this.validators.get(i); + validator.updateSchema(context, target); + } + } + + if (this.metadata != null) { + for (int i = 0; i < this.metadata.size(); i++) { + Metadata meta = this.metadata.get(i); + meta.modify(target); + } + } + + if (this.inherit != null) { + target.getHytale().setInheritsProperty(true); + } + } + + @Nonnull + @Override + public String toString() { + return "BuilderField{codec=" + this.codec + ", setter=" + this.setter + ", getter=" + this.getter + "}"; + } + + public static class FieldBuilder> { + @Nonnull + protected final Builder parentBuilder; + @Nonnull + protected final KeyedCodec codec; + @Nonnull + protected final TriConsumer setter; + @Nonnull + protected final BiFunction getter; + protected final TriConsumer inherit; + protected List> validators; + protected List metadata; + protected int minVersion = Integer.MIN_VALUE; + protected int maxVersion = Integer.MAX_VALUE; + protected String documentation; + + public FieldBuilder( + Builder parentBuilder, + KeyedCodec codec, + TriConsumer setter, + BiFunction getter, + TriConsumer inherit + ) { + this.parentBuilder = Objects.requireNonNull(parentBuilder, "parentBuilder parameter can't be null"); + this.codec = Objects.requireNonNull(codec, "codec parameter can't be null"); + this.setter = Objects.requireNonNull(setter, "setter parameter can't be null"); + this.getter = Objects.requireNonNull(getter, "getter parameter can't be null"); + this.inherit = inherit; + } + + @Nonnull + public BuilderField.FieldBuilder addValidator(Validator validator) { + if (this.validators == null) { + this.validators = new ObjectArrayList<>(); + } + + this.validators.add(validator); + return this; + } + + @Nonnull + @Deprecated(forRemoval = true) + public BuilderField.FieldBuilder addValidator(LegacyValidator validator) { + if (this.validators == null) { + this.validators = new ObjectArrayList<>(); + } + + this.validators.add(validator); + return this; + } + + @Nonnull + public BuilderField.FieldBuilder addValidatorLate(@Nonnull final Supplier> validatorSupplier) { + if (this.validators == null) { + this.validators = new ObjectArrayList<>(); + } + + this.validators.add(new LateValidator() { + private LateValidator validator; + + @Override + public void accept(FieldType fieldType, ValidationResults results) { + if (this.validator == null) { + this.validator = validatorSupplier.get(); + } + + this.validator.accept(fieldType, results); + } + + @Override + public void acceptLate(FieldType fieldType, ValidationResults results, ExtraInfo extraInfo) { + if (this.validator == null) { + this.validator = validatorSupplier.get(); + } + + this.validator.acceptLate(fieldType, results, extraInfo); + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + if (this.validator == null) { + this.validator = validatorSupplier.get(); + } + + this.validator.updateSchema(context, target); + } + }); + return this; + } + + @Nonnull + public BuilderField.FieldBuilder setVersionRange(int minVersion, int maxVersion) { + this.minVersion = minVersion; + this.maxVersion = maxVersion; + return this; + } + + @Nonnull + public BuilderField.FieldBuilder documentation(String doc) { + this.documentation = doc; + return this; + } + + @Nonnull + public BuilderField.FieldBuilder metadata(Metadata metadata) { + if (this.metadata == null) { + this.metadata = new ObjectArrayList<>(); + } + + this.metadata.add(metadata); + return this; + } + + @Nonnull + public Builder add() { + this.parentBuilder.addField(new BuilderField<>(this)); + return this.parentBuilder; + } + } +} diff --git a/src/com/hypixel/hytale/codec/builder/StringTreeMap.java b/src/com/hypixel/hytale/codec/builder/StringTreeMap.java new file mode 100644 index 0000000..2dbc017 --- /dev/null +++ b/src/com/hypixel/hytale/codec/builder/StringTreeMap.java @@ -0,0 +1,227 @@ +package com.hypixel.hytale.codec.builder; + +import com.hypixel.hytale.codec.util.RawJsonReader; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; +import java.io.IOException; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StringTreeMap { + public static final int STRING_PART_SIZE = 4; + private Long2ObjectMap> map; + @Nullable + private String key; + @Nullable + private V value; + + public StringTreeMap() { + } + + public StringTreeMap(@Nonnull StringTreeMap parent) { + if (parent.map != null) { + this.map = new Long2ObjectOpenHashMap<>(parent.map.size()); + + for (Entry> entry : parent.map.long2ObjectEntrySet()) { + this.map.put(entry.getLongKey(), new StringTreeMap<>(entry.getValue())); + } + } + + this.key = parent.key; + this.value = parent.value; + } + + public StringTreeMap(@Nonnull Map entries) { + this.putAll(entries); + } + + @Nullable + public String getKey() { + return this.key; + } + + @Nullable + public V getValue() { + return this.value; + } + + public void putAll(@Nonnull Map entries) { + for (java.util.Map.Entry entry : entries.entrySet()) { + this.put(entry.getKey(), entry.getValue()); + } + } + + public void put(@Nonnull String key, V values) { + this.put0(key, values, 0, key.length()); + } + + private void put0(@Nonnull String key, V fields, int start, int end) { + if (this.map != null || this.key != null && !this.key.equals(key)) { + if (start < end) { + long part = readStringPartAsLong(key, start, end); + if (this.map == null) { + this.map = new Long2ObjectOpenHashMap<>(); + } + + this.map.computeIfAbsent(part, k -> new StringTreeMap<>()).put0(key, fields, start + 4, end); + if (this.key != null && this.key.length() > start) { + String oldKey = this.key; + V oldFields = this.value; + this.key = null; + this.value = null; + this.put0(oldKey, oldFields, start, oldKey.length()); + } + } else { + if (this.key != null && this.key.length() > start) { + String oldKey = this.key; + V oldFields = this.value; + this.key = key; + this.value = fields; + this.put0(oldKey, oldFields, start, oldKey.length()); + } else { + if (this.key != null && !this.key.equals(key)) { + throw new IllegalStateException("Keys don't match: " + this.key + " != " + key); + } + + this.key = key; + this.value = fields; + } + } + } else { + this.key = key; + this.value = fields; + } + } + + public void remove(@Nonnull String key) { + if (this.map == null) { + if (this.key != null) { + if (this.key.equals(key)) { + this.key = null; + this.value = null; + } + } + } else { + this.remove0(key, 0, key.length()); + } + } + + protected void remove0(@Nonnull String key, int start, int end) { + long part = readStringPartAsLong(key, start, end); + StringTreeMap entry = this.map.get(part); + if (entry != null) { + int newStart = start + 4; + if (newStart >= end) { + this.map.remove(part); + } else { + if (entry.map == null) { + if (entry.key == null) { + throw new IllegalStateException("Incorrectly built tree!"); + } + + if (entry.key.equals(key)) { + this.map.remove(part); + return; + } + } + + entry.remove0(key, newStart, end); + } + } + } + + @Nullable + public StringTreeMap findEntry(@Nonnull RawJsonReader reader) throws IOException { + reader.expect('"'); + int end = reader.findOffset('"'); + return this.findEntry0(reader, null, end); + } + + public StringTreeMap findEntryOrDefault(@Nonnull RawJsonReader reader, StringTreeMap def) throws IOException { + reader.expect('"'); + int end = reader.findOffset('"'); + return this.findEntry0(reader, def, end); + } + + protected StringTreeMap findEntry0(@Nonnull RawJsonReader reader, StringTreeMap def, int end) throws IOException { + if (this.map == null) { + if (this.key == null) { + reader.skipRemainingString(); + return def; + } else { + return this.consumeEntryKey(reader, def, end, this); + } + } else { + long part = reader.readStringPartAsLong(Math.min(end, 4)); + int newEnd = Math.max(end - 4, 0); + StringTreeMap entry = this.map.get(part); + if (entry == null) { + if (newEnd != 0) { + reader.skipRemainingString(); + } else { + reader.expect('"'); + } + + return def; + } else if (newEnd == 0) { + reader.expect('"'); + return entry; + } else if (entry.map == null) { + if (entry.key == null) { + throw new IllegalStateException("Incorrectly built tree!"); + } else { + return this.consumeEntryKey(reader, def, newEnd, entry); + } + } else { + return entry.findEntry0(reader, def, newEnd); + } + } + } + + private StringTreeMap consumeEntryKey(@Nonnull RawJsonReader reader, StringTreeMap def, int end, @Nonnull StringTreeMap entry) throws IOException { + int keyLength = entry.key.length(); + if (keyLength < end) { + reader.skipRemainingString(); + return def; + } else if (!reader.tryConsume(entry.key, keyLength - end)) { + reader.skipRemainingString(); + return def; + } else if (!reader.tryConsume('"')) { + reader.skipRemainingString(); + return def; + } else { + return entry; + } + } + + @Nonnull + @Override + public String toString() { + return "StringTreeMap{map=" + this.map + ", key='" + this.key + "', value=" + this.value + "}"; + } + + public static long readStringPartAsLong(@Nonnull String key, int start, int end) { + int length = end - start; + char c1 = key.charAt(start); + if (length == 1) { + return c1; + } else { + char c2 = key.charAt(start + 1); + long value = c1 | (long)c2 << 16; + if (length == 2) { + return value; + } else { + char c3 = key.charAt(start + 2); + value |= (long)c3 << 32; + if (length == 3) { + return value; + } else { + char c4 = key.charAt(start + 3); + return value | (long)c4 << 48; + } + } + } + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/BsonDocumentCodec.java b/src/com/hypixel/hytale/codec/codecs/BsonDocumentCodec.java new file mode 100644 index 0000000..e3ca431 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/BsonDocumentCodec.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.codec.codecs; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +@Deprecated +public class BsonDocumentCodec implements Codec { + public BsonDocumentCodec() { + } + + public BsonDocument decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + return bsonValue.asDocument(); + } + + public BsonValue encode(BsonDocument document, ExtraInfo extraInfo) { + return document; + } + + public BsonDocument decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + return RawJsonReader.readBsonValue(reader).asDocument(); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return new ObjectSchema(); + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/EnumCodec.java b/src/com/hypixel/hytale/codec/codecs/EnumCodec.java new file mode 100644 index 0000000..a61f820 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/EnumCodec.java @@ -0,0 +1,188 @@ +package com.hypixel.hytale.codec.codecs; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import java.util.EnumMap; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonValue; + +public class EnumCodec> implements Codec { + @Nonnull + private final Class clazz; + @Nonnull + private final T[] enumConstants; + @Nonnull + private final String[] enumKeys; + private final EnumCodec.EnumStyle enumStyle; + @Nonnull + private final EnumMap documentation; + + public EnumCodec(@Nonnull Class clazz) { + this(clazz, EnumCodec.EnumStyle.CAMEL_CASE); + } + + public EnumCodec(@Nonnull Class clazz, EnumCodec.EnumStyle enumStyle) { + this.clazz = clazz; + this.enumConstants = clazz.getEnumConstants(); + this.enumStyle = enumStyle; + this.documentation = new EnumMap<>(clazz); + EnumCodec.EnumStyle currentStyle = EnumCodec.EnumStyle.detect(this.enumConstants); + this.enumKeys = new String[this.enumConstants.length]; + + for (int i = 0; i < this.enumConstants.length; i++) { + T e = this.enumConstants[i]; + this.enumKeys[i] = currentStyle.formatCamelCase(e.name()); + } + } + + @Nonnull + public EnumCodec documentKey(T key, String doc) { + this.documentation.put(key, doc); + return this; + } + + @Nonnull + public T decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + String decode = STRING.decode(bsonValue, extraInfo); + T value = this.getEnum(decode); + if (value == null) { + throw new IllegalArgumentException("Failed to apply function to '" + decode + "' decoded from '" + bsonValue + "'!"); + } else { + return value; + } + } + + @Nonnull + public BsonValue encode(@Nonnull T r, ExtraInfo extraInfo) { + return switch (this.enumStyle) { + case LEGACY -> STRING.encode(r.name(), extraInfo); + case CAMEL_CASE -> STRING.encode(this.enumKeys[r.ordinal()], extraInfo); + }; + } + + @Nonnull + public T decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + String decode = STRING.decodeJson(reader, extraInfo); + T value = this.getEnum(decode); + if (value == null) { + throw new IllegalArgumentException("Failed to apply function to '" + decode + "'!"); + } else { + return value; + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return this.toSchema(context, null); + } + + @Nonnull + public Schema toSchema(@Nonnull SchemaContext context, @Nullable T def) { + StringSchema enumSchema = new StringSchema(); + enumSchema.setTitle(this.clazz.getSimpleName()); + enumSchema.setEnum(this.enumKeys); + enumSchema.getHytale().setType("Enum"); + String[] documentation = new String[this.enumKeys.length]; + + for (int i = 0; i < this.enumKeys.length; i++) { + String desc = this.documentation.get(this.enumConstants[i]); + documentation[i] = Objects.requireNonNullElse(desc, ""); + } + + enumSchema.setMarkdownEnumDescriptions(documentation); + if (def != null) { + enumSchema.setDefault(this.enumKeys[def.ordinal()]); + } + + return enumSchema; + } + + @Nullable + private T getEnum(String value) { + return this.enumStyle.match(this.enumConstants, this.enumKeys, value); + } + + public static enum EnumStyle { + @Deprecated + LEGACY, + CAMEL_CASE; + + private EnumStyle() { + } + + @Nullable + public > T match(@Nonnull T[] enumConstants, @Nonnull String[] enumKeys, String value) { + return this.match(enumConstants, enumKeys, value, false); + } + + @Nullable + public > T match(@Nonnull T[] enumConstants, @Nonnull String[] enumKeys, String value, boolean allowInvalid) { + switch (this) { + case LEGACY: + for (int ix = 0; ix < enumConstants.length; ix++) { + T e = enumConstants[ix]; + if (e.name().equalsIgnoreCase(value)) { + return e; + } + } + case CAMEL_CASE: + for (int i = 0; i < enumKeys.length; i++) { + String key = enumKeys[i]; + if (key.equals(value)) { + return enumConstants[i]; + } + } + } + + if (allowInvalid) { + return null; + } else { + throw new CodecException("Failed to find enum value for " + value); + } + } + + @Nonnull + public String formatCamelCase(@Nonnull String name) { + return switch (this) { + case LEGACY -> { + StringBuilder nameParts = new StringBuilder(); + + for (String part : name.split("_")) { + nameParts.append(Character.toUpperCase(part.charAt(0))).append(part.substring(1).toLowerCase()); + } + + yield nameParts.toString(); + } + case CAMEL_CASE -> name; + }; + } + + @Nonnull + public static > EnumCodec.EnumStyle detect(@Nonnull T[] enumConstants) { + for (T e : enumConstants) { + String name = e.name(); + if (name.length() <= 1 || !Character.isUpperCase(name.charAt(1))) { + return CAMEL_CASE; + } + + for (int i = 1; i < name.length(); i++) { + char c = name.charAt(i); + if (Character.isLetter(c) && Character.isLowerCase(c)) { + return CAMEL_CASE; + } + } + } + + return LEGACY; + } + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/InetSocketAddressCodec.java b/src/com/hypixel/hytale/codec/codecs/InetSocketAddressCodec.java new file mode 100644 index 0000000..da0b531 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/InetSocketAddressCodec.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.codec.codecs; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import org.bson.BsonValue; + +public class InetSocketAddressCodec implements Codec { + private static final Pattern ADDRESS_PATTERN = Pattern.compile("(.*?:)?[0-9]+"); + private final int defaultPort; + + public InetSocketAddressCodec(int defaultPort) { + this.defaultPort = defaultPort; + } + + @Nonnull + public InetSocketAddress decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + String decode = STRING.decode(bsonValue, extraInfo); + return decodeString(decode, this.defaultPort); + } + + @Nonnull + public BsonValue encode(@Nonnull InetSocketAddress r, ExtraInfo extraInfo) { + return STRING.encode(r.getHostString() + ":" + r.getPort(), extraInfo); + } + + @Nonnull + public InetSocketAddress decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + String decode = STRING.decodeJson(reader, extraInfo); + return decodeString(decode, this.defaultPort); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + StringSchema s = new StringSchema(); + s.setPattern(ADDRESS_PATTERN); + return s; + } + + @Nonnull + private static InetSocketAddress decodeString(@Nonnull String value, int defaultPort) { + if (value.contains(":")) { + String[] split = value.split(":", 2); + return new InetSocketAddress(split[0], Integer.parseInt(split[1])); + } else { + try { + return new InetSocketAddress(Integer.parseInt(value)); + } catch (NumberFormatException var3) { + return new InetSocketAddress(value, defaultPort); + } + } + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/StringIntegerCodec.java b/src/com/hypixel/hytale/codec/codecs/StringIntegerCodec.java new file mode 100644 index 0000000..6c9480b --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/StringIntegerCodec.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.codec.codecs; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import org.bson.BsonInt32; +import org.bson.BsonValue; + +@Deprecated +public class StringIntegerCodec implements Codec { + public static final StringIntegerCodec INSTANCE = new StringIntegerCodec(); + private static final Pattern INTEGER_PATTERN = Pattern.compile("^[0-9]+$"); + + public StringIntegerCodec() { + } + + @Nonnull + public Integer decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + return bsonValue.isString() ? Integer.parseInt(bsonValue.asString().getValue()) : bsonValue.asNumber().intValue(); + } + + @Nonnull + public BsonValue encode(Integer t, ExtraInfo extraInfo) { + return new BsonInt32(t); + } + + @Nonnull + public Integer decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + int read = reader.peek(); + if (read == -1) { + throw new IOException("Unexpected EOF!"); + } else { + return read == 34 ? Integer.parseInt(reader.readString()) : reader.readIntValue(); + } + } + + @Nonnull + public StringSchema toSchema(@Nonnull SchemaContext context) { + StringSchema s = new StringSchema(); + s.setPattern(INTEGER_PATTERN); + s.setMarkdownDescription("A string that contains any integer"); + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/UUIDBinaryCodec.java b/src/com/hypixel/hytale/codec/codecs/UUIDBinaryCodec.java new file mode 100644 index 0000000..1c1600f --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/UUIDBinaryCodec.java @@ -0,0 +1,131 @@ +package com.hypixel.hytale.codec.codecs; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import java.util.Base64; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.bson.BsonBinary; +import org.bson.BsonBinarySubType; +import org.bson.BsonValue; + +public class UUIDBinaryCodec implements Codec { + public UUIDBinaryCodec() { + } + + @Nonnull + public UUID decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + BsonBinary bsonBinary = bsonValue.asBinary(); + byte subType = bsonBinary.getType(); + if (subType != BsonBinarySubType.UUID_STANDARD.getValue()) { + throw new CodecException("Unexpected BsonBinarySubType"); + } else { + byte[] bytes = bsonBinary.getData(); + return uuidFromBytes(bytes); + } + } + + @Nonnull + public BsonValue encode(@Nonnull UUID uuid, ExtraInfo extraInfo) { + byte[] binaryData = new byte[16]; + writeLongToArrayBigEndian(binaryData, 0, uuid.getMostSignificantBits()); + writeLongToArrayBigEndian(binaryData, 8, uuid.getLeastSignificantBits()); + return new BsonBinary(BsonBinarySubType.UUID_STANDARD, binaryData); + } + + @Nonnull + public UUID decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + if (reader.peekFor('"')) { + return uuidFromHex(reader.readString()); + } else { + reader.expect('{'); + reader.consumeWhiteSpace(); + UUID uuid = null; + + while (true) { + String key = reader.readString(); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + switch (key) { + case "$binary": + uuid = uuidFromHex(reader.readString()); + break; + case "$type": + reader.expect('"'); + reader.expect('0'); + reader.expect('4'); + reader.expect('"'); + break; + default: + throw new IOException("Unknown field '" + key + "' when decoding UUID!"); + } + + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + if (uuid == null) { + throw new IOException("Expected to find '$binary' field when decoding UUID!"); + } + + return uuid; + } + + reader.consumeWhiteSpace(); + } + } + } + + public static void writeLongToArrayBigEndian(@Nonnull byte[] bytes, int offset, long x) { + bytes[offset + 7] = (byte)(255L & x); + bytes[offset + 6] = (byte)(255L & x >> 8); + bytes[offset + 5] = (byte)(255L & x >> 16); + bytes[offset + 4] = (byte)(255L & x >> 24); + bytes[offset + 3] = (byte)(255L & x >> 32); + bytes[offset + 2] = (byte)(255L & x >> 40); + bytes[offset + 1] = (byte)(255L & x >> 48); + bytes[offset] = (byte)(255L & x >> 56); + } + + public static long readLongFromArrayBigEndian(@Nonnull byte[] bytes, int offset) { + long x = 0L; + x |= 255L & bytes[offset + 7]; + x |= (255L & bytes[offset + 6]) << 8; + x |= (255L & bytes[offset + 5]) << 16; + x |= (255L & bytes[offset + 4]) << 24; + x |= (255L & bytes[offset + 3]) << 32; + x |= (255L & bytes[offset + 2]) << 40; + x |= (255L & bytes[offset + 1]) << 48; + return x | (255L & bytes[offset]) << 56; + } + + @Nonnull + public static UUID uuidFromBytes(@Nonnull byte[] bytes) { + if (bytes.length != 16) { + throw new CodecException(String.format("Expected length to be 16, not %d.", bytes.length)); + } else { + return new UUID(readLongFromArrayBigEndian(bytes, 0), readLongFromArrayBigEndian(bytes, 8)); + } + } + + @Nonnull + public static UUID uuidFromHex(String src) { + return uuidFromBytes(Base64.getDecoder().decode(src)); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + StringSchema hexUUID = new StringSchema(); + hexUUID.setMinLength(24); + hexUUID.setMaxLength(24); + hexUUID.setPattern(Codec.BASE64_PATTERN); + hexUUID.setTitle("UUID Binary"); + return hexUUID; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/array/ArrayCodec.java b/src/com/hypixel/hytale/codec/codecs/array/ArrayCodec.java new file mode 100644 index 0000000..f945505 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/array/ArrayCodec.java @@ -0,0 +1,181 @@ +package com.hypixel.hytale.codec.codecs.array; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.RawJsonCodec; +import com.hypixel.hytale.codec.WrappedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.metadata.Metadata; +import com.hypixel.hytale.codec.util.RawJsonReader; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.function.IntFunction; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonArray; +import org.bson.BsonNull; +import org.bson.BsonValue; + +public class ArrayCodec implements Codec, RawJsonCodec, WrappedCodec { + private final Codec codec; + private final IntFunction arrayConstructor; + @Nullable + private final Supplier defaultValue; + private List metadata; + private T[] emptyArray; + + public ArrayCodec(Codec codec, IntFunction arrayConstructor) { + this(codec, arrayConstructor, null); + } + + public ArrayCodec(Codec codec, IntFunction arrayConstructor, @Nullable Supplier defaultValue) { + this.codec = codec; + this.arrayConstructor = arrayConstructor; + this.defaultValue = defaultValue; + } + + @Override + public Codec getChildCodec() { + return this.codec; + } + + public T[] decode(@Nonnull BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { + BsonArray bsonArray = bsonValue.asArray(); + T[] array = (T[])((Object[])this.arrayConstructor.apply(bsonArray.size())); + int i = 0; + + for (int size = bsonArray.size(); i < size; i++) { + BsonValue value = bsonArray.get(i); + extraInfo.pushIntKey(i); + + try { + array[i] = this.decodeElement(value, extraInfo); + } catch (Exception var12) { + throw new CodecException("Failed to decode", value, extraInfo, var12); + } finally { + extraInfo.popKey(); + } + } + + return array; + } + + @Nonnull + public BsonValue encode(@Nonnull T[] array, ExtraInfo extraInfo) { + BsonArray bsonArray = new BsonArray(); + + for (T t : array) { + if (t == null) { + bsonArray.add((BsonValue)(new BsonNull())); + } else { + bsonArray.add(this.codec.encode(t, extraInfo)); + } + } + + return bsonArray; + } + + public T[] decodeJson(@Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.expect('['); + reader.consumeWhiteSpace(); + if (reader.tryConsume(']')) { + if (this.emptyArray == null) { + this.emptyArray = (T[])((Object[])this.arrayConstructor.apply(0)); + } + + return this.emptyArray; + } else { + int i = 0; + T[] arr = (T[])((Object[])this.arrayConstructor.apply(10)); + + while (true) { + if (i == arr.length) { + arr = (T[])Arrays.copyOf(arr, i + 1 + (i >> 1)); + } + + extraInfo.pushIntKey(i, reader); + + try { + arr[i] = this.decodeJsonElement(reader, extraInfo); + i++; + } catch (Exception var9) { + throw new CodecException("Failed to decode", reader, extraInfo, var9); + } finally { + extraInfo.popKey(); + } + + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect(']', ',')) { + if (arr.length == i) { + return arr; + } + + return (T[])Arrays.copyOf(arr, i); + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + public ArrayCodec metadata(Metadata metadata) { + if (this.metadata == null) { + this.metadata = new ObjectArrayList<>(); + } + + this.metadata.add(metadata); + return this; + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ArraySchema arraySchema = new ArraySchema(); + Schema childSchema = context.refDefinition(this.codec); + if (this.metadata != null) { + for (int i = 0; i < this.metadata.size(); i++) { + Metadata meta = this.metadata.get(i); + meta.modify(childSchema); + } + } + + arraySchema.setItem(childSchema); + return arraySchema; + } + + @Nullable + public Supplier getDefaultSupplier() { + return this.defaultValue; + } + + @Nullable + protected T decodeElement(@Nonnull BsonValue value, ExtraInfo extraInfo) { + if (!value.isNull()) { + return this.codec.decode(value, extraInfo); + } else { + return this.defaultValue == null ? null : this.defaultValue.get(); + } + } + + @Nullable + protected T decodeJsonElement(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + if (!reader.tryConsume("null")) { + return this.codec.decodeJson(reader, extraInfo); + } else { + return this.defaultValue == null ? null : this.defaultValue.get(); + } + } + + @Nonnull + public static ArrayCodec ofBuilderCodec(@Nonnull BuilderCodec codec, IntFunction arrayConstructor) { + return new ArrayCodec<>(codec, arrayConstructor, codec.getSupplier()); + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/array/DoubleArrayCodec.java b/src/com/hypixel/hytale/codec/codecs/array/DoubleArrayCodec.java new file mode 100644 index 0000000..c856e87 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/array/DoubleArrayCodec.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.codec.codecs.array; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.RawJsonCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import java.util.Arrays; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonDouble; +import org.bson.BsonValue; + +public class DoubleArrayCodec implements Codec, RawJsonCodec { + public static final double[] EMPTY_DOUBLE_ARRAY = new double[0]; + + public DoubleArrayCodec() { + } + + public double[] decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + BsonArray array = bsonValue.asArray(); + double[] doubles = new double[array.size()]; + + for (int i = 0; i < doubles.length; i++) { + doubles[i] = array.get(i).asNumber().doubleValue(); + } + + return doubles; + } + + @Nonnull + public BsonValue encode(@Nonnull double[] doubles, ExtraInfo extraInfo) { + BsonArray array = new BsonArray(); + + for (int i = 0; i < doubles.length; i++) { + array.add((BsonValue)(new BsonDouble(doubles[i]))); + } + + return array; + } + + public double[] decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + reader.expect('['); + reader.consumeWhiteSpace(); + if (reader.tryConsume(']')) { + return EMPTY_DOUBLE_ARRAY; + } else { + int i = 0; + double[] arr = new double[10]; + + while (true) { + if (i == arr.length) { + double[] temp = new double[i + 1 + (i >> 1)]; + System.arraycopy(arr, 0, temp, 0, i); + arr = temp; + } + + arr[i++] = reader.readDoubleValue(); + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect(']', ',')) { + if (arr.length == i) { + return arr; + } + + return Arrays.copyOf(arr, i); + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ArraySchema s = new ArraySchema(); + s.setItem(new NumberSchema()); + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/array/FloatArrayCodec.java b/src/com/hypixel/hytale/codec/codecs/array/FloatArrayCodec.java new file mode 100644 index 0000000..8cb3d5c --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/array/FloatArrayCodec.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.codec.codecs.array; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.RawJsonCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import java.util.Arrays; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonDouble; +import org.bson.BsonValue; + +public class FloatArrayCodec implements Codec, RawJsonCodec { + public static final float[] EMPTY_FLOAT_ARRAY = new float[0]; + + public FloatArrayCodec() { + } + + public float[] decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + BsonArray array = bsonValue.asArray(); + float[] floats = new float[array.size()]; + + for (int i = 0; i < floats.length; i++) { + floats[i] = (float)array.get(i).asNumber().doubleValue(); + } + + return floats; + } + + @Nonnull + public BsonValue encode(@Nonnull float[] floats, ExtraInfo extraInfo) { + BsonArray array = new BsonArray(); + + for (int i = 0; i < floats.length; i++) { + array.add((BsonValue)(new BsonDouble(floats[i]))); + } + + return array; + } + + public float[] decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + reader.expect('['); + reader.consumeWhiteSpace(); + if (reader.tryConsume(']')) { + return EMPTY_FLOAT_ARRAY; + } else { + int i = 0; + float[] arr = new float[10]; + + while (true) { + if (i == arr.length) { + float[] temp = new float[i + 1 + (i >> 1)]; + System.arraycopy(arr, 0, temp, 0, i); + arr = temp; + } + + arr[i++] = reader.readFloatValue(); + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect(']', ',')) { + if (arr.length == i) { + return arr; + } + + return Arrays.copyOf(arr, i); + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ArraySchema s = new ArraySchema(); + s.setItem(new NumberSchema()); + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/array/IntArrayCodec.java b/src/com/hypixel/hytale/codec/codecs/array/IntArrayCodec.java new file mode 100644 index 0000000..a981021 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/array/IntArrayCodec.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.codec.codecs.array; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.RawJsonCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.IntegerSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import java.util.Arrays; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonInt32; +import org.bson.BsonValue; + +public class IntArrayCodec implements Codec, RawJsonCodec { + public static final int[] EMPTY_INT_ARRAY = new int[0]; + + public IntArrayCodec() { + } + + public int[] decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + BsonArray array = bsonValue.asArray(); + int[] ints = new int[array.size()]; + + for (int i = 0; i < ints.length; i++) { + ints[i] = array.get(i).asInt32().getValue(); + } + + return ints; + } + + @Nonnull + public BsonValue encode(@Nonnull int[] ints, ExtraInfo extraInfo) { + BsonArray array = new BsonArray(); + + for (int i = 0; i < ints.length; i++) { + array.add((BsonValue)(new BsonInt32(ints[i]))); + } + + return array; + } + + public int[] decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + reader.expect('['); + reader.consumeWhiteSpace(); + if (reader.tryConsume(']')) { + return EMPTY_INT_ARRAY; + } else { + int i = 0; + int[] arr = new int[10]; + + while (true) { + if (i == arr.length) { + int[] temp = new int[i + 1 + (i >> 1)]; + System.arraycopy(arr, 0, temp, 0, i); + arr = temp; + } + + arr[i++] = reader.readIntValue(); + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect(']', ',')) { + if (arr.length == i) { + return arr; + } + + return Arrays.copyOf(arr, i); + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ArraySchema s = new ArraySchema(); + s.setItem(new IntegerSchema()); + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/array/LongArrayCodec.java b/src/com/hypixel/hytale/codec/codecs/array/LongArrayCodec.java new file mode 100644 index 0000000..827e112 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/array/LongArrayCodec.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.codec.codecs.array; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.RawJsonCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.IntegerSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import java.util.Arrays; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonInt64; +import org.bson.BsonValue; + +public class LongArrayCodec implements Codec, RawJsonCodec { + public static final long[] EMPTY_LONG_ARRAY = new long[0]; + + public LongArrayCodec() { + } + + public long[] decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + BsonArray array = bsonValue.asArray(); + long[] longs = new long[array.size()]; + + for (int i = 0; i < longs.length; i++) { + longs[i] = array.get(i).asInt64().getValue(); + } + + return longs; + } + + @Nonnull + public BsonValue encode(@Nonnull long[] longs, ExtraInfo extraInfo) { + BsonArray array = new BsonArray(); + + for (int i = 0; i < longs.length; i++) { + array.add((BsonValue)(new BsonInt64(longs[i]))); + } + + return array; + } + + public long[] decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + reader.expect('['); + reader.consumeWhiteSpace(); + if (reader.tryConsume(']')) { + return EMPTY_LONG_ARRAY; + } else { + int i = 0; + long[] arr = new long[10]; + + while (true) { + if (i == arr.length) { + long[] temp = new long[i + 1 + (i >> 1)]; + System.arraycopy(arr, 0, temp, 0, i); + arr = temp; + } + + arr[i++] = reader.readLongValue(); + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect(']', ',')) { + if (arr.length == i) { + return arr; + } + + return Arrays.copyOf(arr, i); + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ArraySchema s = new ArraySchema(); + s.setItem(new IntegerSchema()); + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/map/EnumMapCodec.java b/src/com/hypixel/hytale/codec/codecs/map/EnumMapCodec.java new file mode 100644 index 0000000..1fd55f1 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/map/EnumMapCodec.java @@ -0,0 +1,191 @@ +package com.hypixel.hytale.codec.codecs.map; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.WrappedCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import java.io.IOException; +import java.util.Collections; +import java.util.EnumMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class EnumMapCodec, V> implements Codec>, WrappedCodec { + @Nonnull + private final Class clazz; + private final K[] enumConstants; + @Nonnull + private final String[] enumKeys; + private final EnumCodec.EnumStyle enumStyle; + private final Codec codec; + private final Supplier> supplier; + private final boolean unmodifiable; + @Nonnull + private final EnumMap keyDocumentation; + + public EnumMapCodec(@Nonnull Class clazz, Codec codec) { + this(clazz, codec, true); + } + + public EnumMapCodec(@Nonnull Class clazz, Codec codec, boolean unmodifiable) { + this(clazz, EnumCodec.EnumStyle.CAMEL_CASE, codec, () -> new EnumMap<>(clazz), unmodifiable); + } + + public EnumMapCodec(@Nonnull Class clazz, Codec codec, Supplier> supplier) { + this(clazz, EnumCodec.EnumStyle.CAMEL_CASE, codec, supplier, true); + } + + public EnumMapCodec(@Nonnull Class clazz, Codec codec, Supplier> supplier, boolean unmodifiable) { + this(clazz, EnumCodec.EnumStyle.CAMEL_CASE, codec, supplier, unmodifiable); + } + + public EnumMapCodec(@Nonnull Class clazz, EnumCodec.EnumStyle enumStyle, Codec codec, Supplier> supplier, boolean unmodifiable) { + this.clazz = clazz; + this.enumConstants = clazz.getEnumConstants(); + this.enumStyle = enumStyle; + this.codec = codec; + this.supplier = supplier; + this.unmodifiable = unmodifiable; + this.keyDocumentation = new EnumMap<>(clazz); + EnumCodec.EnumStyle currentStyle = EnumCodec.EnumStyle.detect(this.enumConstants); + this.enumKeys = new String[this.enumConstants.length]; + + for (int i = 0; i < this.enumConstants.length; i++) { + K e = this.enumConstants[i]; + this.enumKeys[i] = currentStyle.formatCamelCase(e.name()); + } + } + + @Nonnull + public EnumMapCodec documentKey(K key, String doc) { + this.keyDocumentation.put(key, doc); + return this; + } + + @Override + public Codec getChildCodec() { + return this.codec; + } + + public Map decode(@Nonnull BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { + BsonDocument bsonDocument = bsonValue.asDocument(); + Map map = this.supplier.get(); + + for (Entry entry : bsonDocument.entrySet()) { + String key = entry.getKey(); + BsonValue value = entry.getValue(); + K enumKey = this.getEnum(key); + extraInfo.pushKey(key); + + try { + map.put(enumKey, this.codec.decode(value, extraInfo)); + } catch (Exception var14) { + throw new CodecException("Failed to decode", value, extraInfo, var14); + } finally { + extraInfo.popKey(); + } + } + + if (this.unmodifiable) { + map = Collections.unmodifiableMap(map); + } + + return map; + } + + @Nonnull + public BsonValue encode(@Nonnull Map map, ExtraInfo extraInfo) { + BsonDocument bsonDocument = new BsonDocument(); + + for (Entry entry : map.entrySet()) { + BsonValue value = this.codec.encode(entry.getValue(), extraInfo); + if (value != null && !value.isNull() && (!value.isDocument() || !value.asDocument().isEmpty()) && (!value.isArray() || !value.asArray().isEmpty())) { + String key = switch (this.enumStyle) { + case CAMEL_CASE -> this.enumKeys[entry.getKey().ordinal()]; + case LEGACY -> entry.getKey().name(); + }; + bsonDocument.put(key, value); + } + } + + return bsonDocument; + } + + public Map decodeJson(@Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.expect('{'); + reader.consumeWhiteSpace(); + if (reader.tryConsume('}')) { + return this.unmodifiable ? Collections.emptyMap() : this.supplier.get(); + } else { + Map map = this.supplier.get(); + + while (true) { + String key = reader.readString(); + K enumKey = this.getEnum(key); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + extraInfo.pushKey(key, reader); + + try { + map.put(enumKey, this.codec.decodeJson(reader, extraInfo)); + } catch (Exception var10) { + throw new CodecException("Failed to decode", reader, extraInfo, var10); + } finally { + extraInfo.popKey(); + } + + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + if (this.unmodifiable) { + map = Collections.unmodifiableMap(map); + } + + return map; + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ObjectSchema schema = new ObjectSchema(); + schema.getHytale().setType("EnumMap"); + schema.setTitle("Map of " + this.clazz.getSimpleName()); + StringSchema values = new StringSchema(); + schema.setPropertyNames(values); + Map properties = new Object2ObjectLinkedOpenHashMap<>(); + schema.setProperties(properties); + Schema childSchema = context.refDefinition(this.codec); + schema.setAdditionalProperties(childSchema); + + for (int i = 0; i < this.enumConstants.length; i++) { + Schema subSchema = context.refDefinition(this.codec); + subSchema.setMarkdownDescription(this.keyDocumentation.get(this.enumConstants[i])); + properties.put(this.enumKeys[i], subSchema); + } + + values.setEnum(this.enumKeys); + return schema; + } + + @Nullable + protected K getEnum(String value) { + return this.enumStyle.match(this.enumConstants, this.enumKeys, value); + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/map/Float2ObjectMapCodec.java b/src/com/hypixel/hytale/codec/codecs/map/Float2ObjectMapCodec.java new file mode 100644 index 0000000..7eefb71 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/map/Float2ObjectMapCodec.java @@ -0,0 +1,132 @@ +package com.hypixel.hytale.codec.codecs.map; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.WrappedCodec; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import it.unimi.dsi.fastutil.floats.Float2ObjectMap; +import it.unimi.dsi.fastutil.floats.Float2ObjectMaps; +import java.io.IOException; +import java.util.Map.Entry; +import java.util.function.Supplier; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class Float2ObjectMapCodec implements Codec>, WrappedCodec { + public static final Pattern FLOAT_PATTERN = Pattern.compile("^[-+]?[0-9]+(.[0-9]+)?$"); + private final Codec valueCodec; + private final Supplier> supplier; + private final boolean unmodifiable; + + public Float2ObjectMapCodec(Codec valueCodec, Supplier> supplier, boolean unmodifiable) { + this.valueCodec = valueCodec; + this.supplier = supplier; + this.unmodifiable = unmodifiable; + } + + public Float2ObjectMapCodec(Codec valueCodec, Supplier> supplier) { + this(valueCodec, supplier, true); + } + + @Override + public Codec getChildCodec() { + return this.valueCodec; + } + + public Float2ObjectMap decode(@Nonnull BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { + BsonDocument bsonDocument = bsonValue.asDocument(); + Float2ObjectMap map = this.supplier.get(); + + for (Entry entry : bsonDocument.entrySet()) { + String key = entry.getKey(); + BsonValue value = entry.getValue(); + extraInfo.pushKey(key); + + try { + float decodedKey = Float.parseFloat(key); + map.put(decodedKey, this.valueCodec.decode(value, extraInfo)); + } catch (Exception var13) { + throw new CodecException("Failed to decode", value, extraInfo, var13); + } finally { + extraInfo.popKey(); + } + } + + if (this.unmodifiable) { + map = Float2ObjectMaps.unmodifiable(map); + } + + return map; + } + + @Nonnull + public BsonValue encode(@Nonnull Float2ObjectMap map, ExtraInfo extraInfo) { + BsonDocument bsonDocument = new BsonDocument(); + + for (it.unimi.dsi.fastutil.floats.Float2ObjectMap.Entry entry : map.float2ObjectEntrySet()) { + bsonDocument.put(Float.toString(entry.getFloatKey()), this.valueCodec.encode(entry.getValue(), extraInfo)); + } + + return bsonDocument; + } + + public Float2ObjectMap decodeJson(@Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.expect('{'); + reader.consumeWhiteSpace(); + Float2ObjectMap map = this.supplier.get(); + if (reader.tryConsume('}')) { + if (this.unmodifiable) { + map = Float2ObjectMaps.unmodifiable(map); + } + + return map; + } else { + while (true) { + String key = reader.readString(); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + extraInfo.pushKey(key, reader); + + try { + float decodedKey = Float.parseFloat(key); + map.put(decodedKey, this.valueCodec.decodeJson(reader, extraInfo)); + } catch (Exception var9) { + throw new CodecException("Failed to decode", reader, extraInfo, var9); + } finally { + extraInfo.popKey(); + } + + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + if (this.unmodifiable) { + map = Float2ObjectMaps.unmodifiable(map); + } + + return map; + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ObjectSchema s = new ObjectSchema(); + StringSchema name = new StringSchema(); + name.setPattern(FLOAT_PATTERN); + name.setMarkdownDescription("A string that contains any floating point number"); + s.setPropertyNames(name); + s.setAdditionalProperties(context.refDefinition(this.valueCodec)); + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/map/Int2ObjectMapCodec.java b/src/com/hypixel/hytale/codec/codecs/map/Int2ObjectMapCodec.java new file mode 100644 index 0000000..3c8712c --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/map/Int2ObjectMapCodec.java @@ -0,0 +1,130 @@ +package com.hypixel.hytale.codec.codecs.map; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.WrappedCodec; +import com.hypixel.hytale.codec.codecs.StringIntegerCodec; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import java.io.IOException; +import java.util.Map.Entry; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class Int2ObjectMapCodec implements Codec>, WrappedCodec { + private final Codec valueCodec; + private final Supplier> supplier; + private final boolean unmodifiable; + + public Int2ObjectMapCodec(Codec valueCodec, Supplier> supplier, boolean unmodifiable) { + this.valueCodec = valueCodec; + this.supplier = supplier; + this.unmodifiable = unmodifiable; + } + + public Int2ObjectMapCodec(Codec valueCodec, Supplier> supplier) { + this(valueCodec, supplier, true); + } + + @Override + public Codec getChildCodec() { + return this.valueCodec; + } + + public Int2ObjectMap decode(@Nonnull BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { + BsonDocument bsonDocument = bsonValue.asDocument(); + Int2ObjectMap map = this.supplier.get(); + + for (Entry entry : bsonDocument.entrySet()) { + String key = entry.getKey(); + BsonValue value = entry.getValue(); + extraInfo.pushKey(key); + + try { + int decodedKey = Integer.parseInt(key); + map.put(decodedKey, this.valueCodec.decode(value, extraInfo)); + } catch (Exception var13) { + throw new CodecException("Failed to decode", value, extraInfo, var13); + } finally { + extraInfo.popKey(); + } + } + + if (this.unmodifiable) { + map = Int2ObjectMaps.unmodifiable(map); + } + + return map; + } + + @Nonnull + public BsonValue encode(@Nonnull Int2ObjectMap map, ExtraInfo extraInfo) { + BsonDocument bsonDocument = new BsonDocument(); + + for (it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry entry : map.int2ObjectEntrySet()) { + bsonDocument.put(Integer.toString(entry.getIntKey()), this.valueCodec.encode(entry.getValue(), extraInfo)); + } + + return bsonDocument; + } + + public Int2ObjectMap decodeJson(@Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.expect('{'); + reader.consumeWhiteSpace(); + Int2ObjectMap map = this.supplier.get(); + if (reader.tryConsume('}')) { + if (this.unmodifiable) { + map = Int2ObjectMaps.unmodifiable(map); + } + + return map; + } else { + while (true) { + reader.expect('"'); + int decodedKey = reader.readIntValue(); + reader.expect('"'); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + extraInfo.pushIntKey(decodedKey, reader); + + try { + map.put(decodedKey, this.valueCodec.decodeJson(reader, extraInfo)); + } catch (Exception var9) { + throw new CodecException("Failed to decode", reader, extraInfo, var9); + } finally { + extraInfo.popKey(); + } + + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + if (this.unmodifiable) { + map = Int2ObjectMaps.unmodifiable(map); + } + + return map; + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ObjectSchema s = new ObjectSchema(); + StringSchema name = StringIntegerCodec.INSTANCE.toSchema(context); + s.setPropertyNames(name); + s.setAdditionalProperties(context.refDefinition(this.valueCodec)); + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/map/MapCodec.java b/src/com/hypixel/hytale/codec/codecs/map/MapCodec.java new file mode 100644 index 0000000..8342bc1 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/map/MapCodec.java @@ -0,0 +1,131 @@ +package com.hypixel.hytale.codec.codecs.map; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.WrappedCodec; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class MapCodec> implements Codec>, WrappedCodec { + public static final MapCodec> STRING_HASH_MAP_CODEC = new MapCodec<>(Codec.STRING, Object2ObjectOpenHashMap::new); + private final Codec codec; + private final Supplier supplier; + private final boolean unmodifiable; + + public MapCodec(Codec codec, Supplier supplier) { + this(codec, supplier, true); + } + + public MapCodec(Codec codec, Supplier supplier, boolean unmodifiable) { + this.codec = codec; + this.supplier = supplier; + this.unmodifiable = unmodifiable; + } + + @Override + public Codec getChildCodec() { + return this.codec; + } + + public Map decode(@Nonnull BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { + BsonDocument bsonDocument = bsonValue.asDocument(); + if (bsonDocument.isEmpty()) { + return this.unmodifiable ? Collections.emptyMap() : this.supplier.get(); + } else { + Map map = this.supplier.get(); + + for (Entry entry : bsonDocument.entrySet()) { + String key = entry.getKey(); + BsonValue value = entry.getValue(); + extraInfo.pushKey(key); + + try { + map.put(key, this.codec.decode(value, extraInfo)); + } catch (Exception var13) { + throw new CodecException("Failed to decode", value, extraInfo, var13); + } finally { + extraInfo.popKey(); + } + } + + if (this.unmodifiable) { + map = Collections.unmodifiableMap(map); + } + + return map; + } + } + + @Nonnull + public BsonValue encode(@Nonnull Map map, ExtraInfo extraInfo) { + BsonDocument bsonDocument = new BsonDocument(); + + for (Entry entry : map.entrySet()) { + BsonValue value = this.codec.encode(entry.getValue(), extraInfo); + if (value != null && !value.isNull() && (!value.isDocument() || !value.asDocument().isEmpty()) && (!value.isArray() || !value.asArray().isEmpty())) { + bsonDocument.put(entry.getKey(), value); + } + } + + return bsonDocument; + } + + public Map decodeJson(@Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.expect('{'); + reader.consumeWhiteSpace(); + if (reader.tryConsume('}')) { + return this.unmodifiable ? Collections.emptyMap() : this.supplier.get(); + } else { + Map map = this.supplier.get(); + + while (true) { + String key = reader.readString(); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + extraInfo.pushKey(key, reader); + + try { + map.put(key, this.codec.decodeJson(reader, extraInfo)); + } catch (Exception var9) { + throw new CodecException("Failed to decode", reader, extraInfo, var9); + } finally { + extraInfo.popKey(); + } + + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + if (this.unmodifiable) { + map = Collections.unmodifiableMap(map); + } + + return map; + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ObjectSchema schema = new ObjectSchema(); + schema.setTitle("Map"); + Schema childSchema = context.refDefinition(this.codec); + schema.setAdditionalProperties(childSchema); + return schema; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/map/MergedEnumMapCodec.java b/src/com/hypixel/hytale/codec/codecs/map/MergedEnumMapCodec.java new file mode 100644 index 0000000..2197360 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/map/MergedEnumMapCodec.java @@ -0,0 +1,261 @@ +package com.hypixel.hytale.codec.codecs.map; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.WrappedCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import java.io.IOException; +import java.util.Collections; +import java.util.EnumMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class MergedEnumMapCodec, V, M extends Enum> implements Codec>, WrappedCodec { + @Nonnull + private final Class clazz; + private final K[] enumConstants; + @Nonnull + private final String[] enumKeys; + @Nonnull + private final Class mergeClazz; + private final M[] mergeEnumConstants; + @Nonnull + private final String[] mergeEnumKeys; + private final Function unmergeFunction; + private final BiFunction mergeResultFunction; + private final EnumCodec.EnumStyle enumStyle; + private final Codec codec; + private final Supplier> supplier; + private final boolean unmodifiable; + + public MergedEnumMapCodec( + @Nonnull Class clazz, @Nonnull Class mergeClass, Function unmergeFunction, BiFunction mergeResultFunction, Codec codec + ) { + this(clazz, EnumCodec.EnumStyle.CAMEL_CASE, mergeClass, unmergeFunction, mergeResultFunction, codec, () -> new EnumMap<>(clazz), true); + } + + public MergedEnumMapCodec( + @Nonnull Class clazz, + @Nonnull Class mergeClass, + Function unmergeFunction, + BiFunction mergeResultFunction, + Codec codec, + Supplier> supplier + ) { + this(clazz, EnumCodec.EnumStyle.CAMEL_CASE, mergeClass, unmergeFunction, mergeResultFunction, codec, supplier, true); + } + + public MergedEnumMapCodec( + @Nonnull Class clazz, + @Nonnull Class mergeClass, + Function unmergeFunction, + BiFunction mergeResultFunction, + Codec codec, + Supplier> supplier, + boolean unmodifiable + ) { + this(clazz, EnumCodec.EnumStyle.CAMEL_CASE, mergeClass, unmergeFunction, mergeResultFunction, codec, supplier, unmodifiable); + } + + public MergedEnumMapCodec( + @Nonnull Class clazz, + EnumCodec.EnumStyle enumStyle, + @Nonnull Class mergeClass, + Function unmergeFunction, + BiFunction mergeResultFunction, + Codec codec, + Supplier> supplier, + boolean unmodifiable + ) { + this.clazz = clazz; + this.enumConstants = clazz.getEnumConstants(); + this.mergeClazz = mergeClass; + this.mergeEnumConstants = mergeClass.getEnumConstants(); + this.unmergeFunction = unmergeFunction; + this.enumStyle = enumStyle; + this.mergeResultFunction = mergeResultFunction; + this.codec = codec; + this.supplier = supplier; + this.unmodifiable = unmodifiable; + EnumCodec.EnumStyle currentStyle = EnumCodec.EnumStyle.detect(this.enumConstants); + this.enumKeys = new String[this.enumConstants.length]; + + for (int i = 0; i < this.enumConstants.length; i++) { + K e = this.enumConstants[i]; + this.enumKeys[i] = currentStyle.formatCamelCase(e.name()); + } + + EnumCodec.EnumStyle currentMergeStyle = EnumCodec.EnumStyle.detect(this.mergeEnumConstants); + this.mergeEnumKeys = new String[this.mergeEnumConstants.length]; + + for (int i = 0; i < this.mergeEnumConstants.length; i++) { + M e = this.mergeEnumConstants[i]; + this.mergeEnumKeys[i] = currentMergeStyle.formatCamelCase(e.name()); + } + } + + @Override + public Codec getChildCodec() { + return this.codec; + } + + public Map decode(@Nonnull BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { + BsonDocument bsonDocument = bsonValue.asDocument(); + Map map = this.supplier.get(); + + for (Entry entry : bsonDocument.entrySet()) { + String key = entry.getKey(); + BsonValue value = entry.getValue(); + extraInfo.pushKey(key); + + try { + V decode = this.codec.decode(value, extraInfo); + this.put0(map, key, decode); + } catch (Exception var13) { + throw new CodecException("Failed to decode", value, extraInfo, var13); + } finally { + extraInfo.popKey(); + } + } + + if (this.unmodifiable) { + map = Collections.unmodifiableMap(map); + } + + return map; + } + + private void put0(@Nonnull Map map, String key, V decode) { + K k = this.getEnum(key); + if (k != null) { + V v = map.get(k); + if (v == null) { + map.put(k, decode); + } else { + map.put(k, this.mergeResultFunction.apply(v, decode)); + } + } else { + K[] mergedEnum = this.getMergedEnum(key); + if (mergedEnum != null) { + for (K merged : mergedEnum) { + V v = map.get(merged); + if (v == null) { + map.put(merged, decode); + } else { + map.put(merged, this.mergeResultFunction.apply(v, decode)); + } + } + } + } + } + + @Nonnull + public BsonValue encode(@Nonnull Map map, ExtraInfo extraInfo) { + BsonDocument bsonDocument = new BsonDocument(); + + for (Entry entry : map.entrySet()) { + BsonValue value = this.codec.encode(entry.getValue(), extraInfo); + if (value != null && !value.isNull() && (!value.isDocument() || !value.asDocument().isEmpty()) && (!value.isArray() || !value.asArray().isEmpty())) { + String key = switch (this.enumStyle) { + case CAMEL_CASE -> this.enumKeys[entry.getKey().ordinal()]; + case LEGACY -> entry.getKey().name(); + }; + bsonDocument.put(key, value); + } + } + + return bsonDocument; + } + + public Map decodeJson(@Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.expect('{'); + reader.consumeWhiteSpace(); + if (reader.tryConsume('}')) { + return (Map)(this.unmodifiable ? Collections.emptyMap() : this.supplier.get()); + } else { + Map map = this.supplier.get(); + + while (true) { + String key = reader.readString(); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + extraInfo.pushKey(key, reader); + + try { + V decode = this.codec.decodeJson(reader, extraInfo); + this.put0(map, key, decode); + } catch (Exception var9) { + throw new CodecException("Failed to decode", reader, extraInfo, var9); + } finally { + extraInfo.popKey(); + } + + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + if (this.unmodifiable) { + map = Collections.unmodifiableMap(map); + } + + return map; + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ObjectSchema schema = new ObjectSchema(); + schema.getHytale().setType("EnumMap"); + schema.setTitle("Merged map of " + this.clazz.getSimpleName() + " and " + this.mergeClazz.getSimpleName()); + StringSchema values = new StringSchema(); + schema.setPropertyNames(values); + Schema childSchema = context.refDefinition(this.codec); + Map properties = new Object2ObjectLinkedOpenHashMap<>(); + schema.setProperties(properties); + schema.setAdditionalProperties(childSchema); + String[] enum_ = new String[this.enumKeys.length + this.mergeEnumKeys.length]; + + for (int i = 0; i < this.enumKeys.length; i++) { + String entry = this.enumKeys[i]; + enum_[i] = entry; + properties.put(entry, childSchema); + } + + for (int i = 0; i < this.mergeEnumKeys.length; i++) { + String entry = this.mergeEnumKeys[i]; + enum_[this.enumConstants.length + i] = entry; + properties.put(entry, childSchema); + } + + values.setEnum(enum_); + return schema; + } + + @Nullable + protected K getEnum(String value) { + return this.enumStyle.match(this.enumConstants, this.enumKeys, value, true); + } + + protected K[] getMergedEnum(String value) { + M m = this.enumStyle.match(this.mergeEnumConstants, this.mergeEnumKeys, value, true); + return (K[])((Enum[])this.unmergeFunction.apply(m)); + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/map/Object2DoubleMapCodec.java b/src/com/hypixel/hytale/codec/codecs/map/Object2DoubleMapCodec.java new file mode 100644 index 0000000..bd67e14 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/map/Object2DoubleMapCodec.java @@ -0,0 +1,117 @@ +package com.hypixel.hytale.codec.codecs.map; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.WrappedCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import it.unimi.dsi.fastutil.objects.Object2DoubleMap; +import it.unimi.dsi.fastutil.objects.Object2DoubleMaps; +import java.io.IOException; +import java.util.Map.Entry; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; +import org.bson.BsonDouble; +import org.bson.BsonString; +import org.bson.BsonValue; + +public class Object2DoubleMapCodec implements Codec>, WrappedCodec { + private final Codec keyCodec; + private final Supplier> supplier; + private final boolean unmodifiable; + + public Object2DoubleMapCodec(Codec keyCodec, Supplier> supplier, boolean unmodifiable) { + this.keyCodec = keyCodec; + this.supplier = supplier; + this.unmodifiable = unmodifiable; + } + + public Object2DoubleMapCodec(Codec keyCodec, Supplier> supplier) { + this(keyCodec, supplier, true); + } + + @Override + public Codec getChildCodec() { + return this.keyCodec; + } + + public Object2DoubleMap decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + BsonDocument bsonDocument = bsonValue.asDocument(); + Object2DoubleMap map = this.supplier.get(); + + for (Entry stringBsonValueEntry : bsonDocument.entrySet()) { + T decodedKey = this.keyCodec.decode(new BsonString(stringBsonValueEntry.getKey()), extraInfo); + map.put(decodedKey, stringBsonValueEntry.getValue().asNumber().doubleValue()); + } + + if (this.unmodifiable) { + map = Object2DoubleMaps.unmodifiable(map); + } + + return map; + } + + @Nonnull + public BsonValue encode(@Nonnull Object2DoubleMap map, ExtraInfo extraInfo) { + BsonDocument bsonDocument = new BsonDocument(); + + for (T key : map.keySet()) { + String encodedKey = this.keyCodec.encode(key, extraInfo).asString().getValue(); + bsonDocument.put(encodedKey, new BsonDouble(map.getDouble(key))); + } + + return bsonDocument; + } + + public Object2DoubleMap decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + reader.expect('{'); + reader.consumeWhiteSpace(); + Object2DoubleMap map = this.supplier.get(); + if (reader.tryConsume('}')) { + if (this.unmodifiable) { + map = Object2DoubleMaps.unmodifiable(map); + } + + return map; + } else { + while (true) { + T key = this.keyCodec.decodeJson(reader, extraInfo); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + map.put(key, reader.readDoubleValue()); + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + if (this.unmodifiable) { + map = Object2DoubleMaps.unmodifiable(map); + } + + return map; + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ObjectSchema s = new ObjectSchema(); + StringSchema key = (StringSchema)this.keyCodec.toSchema(context); + String title = key.getTitle(); + if (title == null) { + title = key.getHytale().getType(); + } + + s.setTitle("Map of " + title + " to double"); + s.setPropertyNames(key); + s.setAdditionalProperties(new NumberSchema()); + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/map/Object2FloatMapCodec.java b/src/com/hypixel/hytale/codec/codecs/map/Object2FloatMapCodec.java new file mode 100644 index 0000000..7939f96 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/map/Object2FloatMapCodec.java @@ -0,0 +1,117 @@ +package com.hypixel.hytale.codec.codecs.map; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.WrappedCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatMaps; +import java.io.IOException; +import java.util.Map.Entry; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; +import org.bson.BsonDouble; +import org.bson.BsonString; +import org.bson.BsonValue; + +public class Object2FloatMapCodec implements Codec>, WrappedCodec { + private final Codec keyCodec; + private final Supplier> supplier; + private final boolean unmodifiable; + + public Object2FloatMapCodec(Codec keyCodec, Supplier> supplier, boolean unmodifiable) { + this.keyCodec = keyCodec; + this.supplier = supplier; + this.unmodifiable = unmodifiable; + } + + public Object2FloatMapCodec(Codec keyCodec, Supplier> supplier) { + this(keyCodec, supplier, true); + } + + @Override + public Codec getChildCodec() { + return this.keyCodec; + } + + public Object2FloatMap decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + BsonDocument bsonDocument = bsonValue.asDocument(); + Object2FloatMap map = this.supplier.get(); + + for (Entry stringBsonValueEntry : bsonDocument.entrySet()) { + T decodedKey = this.keyCodec.decode(new BsonString(stringBsonValueEntry.getKey()), extraInfo); + map.put(decodedKey, (float)stringBsonValueEntry.getValue().asNumber().doubleValue()); + } + + if (this.unmodifiable) { + map = Object2FloatMaps.unmodifiable(map); + } + + return map; + } + + @Nonnull + public BsonValue encode(@Nonnull Object2FloatMap map, ExtraInfo extraInfo) { + BsonDocument bsonDocument = new BsonDocument(); + + for (T key : map.keySet()) { + String encodedKey = this.keyCodec.encode(key, extraInfo).asString().getValue(); + bsonDocument.put(encodedKey, new BsonDouble(map.getFloat(key))); + } + + return bsonDocument; + } + + public Object2FloatMap decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + reader.expect('{'); + reader.consumeWhiteSpace(); + Object2FloatMap map = this.supplier.get(); + if (reader.tryConsume('}')) { + if (this.unmodifiable) { + map = Object2FloatMaps.unmodifiable(map); + } + + return map; + } else { + while (true) { + T key = this.keyCodec.decodeJson(reader, extraInfo); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + map.put(key, reader.readFloatValue()); + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + if (this.unmodifiable) { + map = Object2FloatMaps.unmodifiable(map); + } + + return map; + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ObjectSchema s = new ObjectSchema(); + StringSchema key = (StringSchema)this.keyCodec.toSchema(context); + String title = key.getTitle(); + if (title == null) { + title = key.getHytale().getType(); + } + + s.setTitle("Map of " + title + " to float"); + s.setPropertyNames(key); + s.setAdditionalProperties(new NumberSchema()); + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/map/Object2IntMapCodec.java b/src/com/hypixel/hytale/codec/codecs/map/Object2IntMapCodec.java new file mode 100644 index 0000000..1e75e8b --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/map/Object2IntMapCodec.java @@ -0,0 +1,117 @@ +package com.hypixel.hytale.codec.codecs.map; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.WrappedCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.IntegerSchema; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntMaps; +import java.io.IOException; +import java.util.Map.Entry; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; +import org.bson.BsonInt32; +import org.bson.BsonString; +import org.bson.BsonValue; + +public class Object2IntMapCodec implements Codec>, WrappedCodec { + private final Codec keyCodec; + private final Supplier> supplier; + private final boolean unmodifiable; + + public Object2IntMapCodec(Codec keyCodec, Supplier> supplier, boolean unmodifiable) { + this.keyCodec = keyCodec; + this.supplier = supplier; + this.unmodifiable = unmodifiable; + } + + public Object2IntMapCodec(Codec keyCodec, Supplier> supplier) { + this(keyCodec, supplier, true); + } + + @Override + public Codec getChildCodec() { + return this.keyCodec; + } + + public Object2IntMap decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + BsonDocument bsonDocument = bsonValue.asDocument(); + Object2IntMap map = this.supplier.get(); + + for (Entry entry : bsonDocument.entrySet()) { + T decodedKey = this.keyCodec.decode(new BsonString(entry.getKey()), extraInfo); + map.put(decodedKey, entry.getValue().asInt32().intValue()); + } + + if (this.unmodifiable) { + map = Object2IntMaps.unmodifiable(map); + } + + return map; + } + + @Nonnull + public BsonValue encode(@Nonnull Object2IntMap map, ExtraInfo extraInfo) { + BsonDocument bsonDocument = new BsonDocument(); + + for (T key : map.keySet()) { + String encodedKey = this.keyCodec.encode(key, extraInfo).asString().getValue(); + bsonDocument.put(encodedKey, new BsonInt32(map.getInt(key))); + } + + return bsonDocument; + } + + public Object2IntMap decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + reader.expect('{'); + reader.consumeWhiteSpace(); + Object2IntMap map = this.supplier.get(); + if (reader.tryConsume('}')) { + if (this.unmodifiable) { + map = Object2IntMaps.unmodifiable(map); + } + + return map; + } else { + while (true) { + T key = this.keyCodec.decodeJson(reader, extraInfo); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + map.put(key, reader.readIntValue()); + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + if (this.unmodifiable) { + map = Object2IntMaps.unmodifiable(map); + } + + return map; + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ObjectSchema s = new ObjectSchema(); + StringSchema key = (StringSchema)this.keyCodec.toSchema(context); + String title = key.getTitle(); + if (title == null) { + title = key.getHytale().getType(); + } + + s.setTitle("Map of " + title + " to integer"); + s.setPropertyNames(key); + s.setAdditionalProperties(new IntegerSchema()); + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/map/ObjectMapCodec.java b/src/com/hypixel/hytale/codec/codecs/map/ObjectMapCodec.java new file mode 100644 index 0000000..804f9b2 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/map/ObjectMapCodec.java @@ -0,0 +1,133 @@ +package com.hypixel.hytale.codec.codecs.map; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.WrappedCodec; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +@Deprecated +public class ObjectMapCodec> implements Codec>, WrappedCodec { + private final Codec codec; + private final Supplier supplier; + private final Function keyToString; + private final Function stringToKey; + private final boolean unmodifiable; + + public ObjectMapCodec(Codec codec, Supplier supplier, Function keyToString, Function stringToKey) { + this(codec, supplier, keyToString, stringToKey, true); + } + + public ObjectMapCodec(Codec codec, Supplier supplier, Function keyToString, Function stringToKey, boolean unmodifiable) { + this.codec = codec; + this.supplier = supplier; + this.keyToString = keyToString; + this.stringToKey = stringToKey; + this.unmodifiable = unmodifiable; + } + + @Override + public Codec getChildCodec() { + return this.codec; + } + + public Map decode(@Nonnull BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { + BsonDocument bsonDocument = bsonValue.asDocument(); + Map map = this.supplier.get(); + + for (Entry entry : bsonDocument.entrySet()) { + String key = entry.getKey(); + BsonValue value = entry.getValue(); + K decodedKey = this.stringToKey.apply(key); + extraInfo.pushKey(key); + + try { + map.put(decodedKey, this.codec.decode(value, extraInfo)); + } catch (Exception var14) { + throw new CodecException("Failed to decode", value, extraInfo, var14); + } finally { + extraInfo.popKey(); + } + } + + if (this.unmodifiable) { + map = Collections.unmodifiableMap(map); + } + + return map; + } + + @Nonnull + public BsonValue encode(@Nonnull Map map, ExtraInfo extraInfo) { + BsonDocument bsonDocument = new BsonDocument(); + + for (Entry entry : map.entrySet()) { + BsonValue value = this.codec.encode(entry.getValue(), extraInfo); + if (value != null && !value.isNull() && (!value.isDocument() || !value.asDocument().isEmpty()) && (!value.isArray() || !value.asArray().isEmpty())) { + bsonDocument.put(this.keyToString.apply(entry.getKey()), value); + } + } + + return bsonDocument; + } + + public Map decodeJson(@Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.expect('{'); + reader.consumeWhiteSpace(); + if (reader.tryConsume('}')) { + return this.unmodifiable ? Collections.emptyMap() : this.supplier.get(); + } else { + Map map = this.supplier.get(); + + while (true) { + String key = reader.readString(); + K decodedKey = this.stringToKey.apply(key); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + extraInfo.pushKey(key, reader); + + try { + map.put(decodedKey, this.codec.decodeJson(reader, extraInfo)); + } catch (Exception var10) { + throw new CodecException("Failed to decode", reader, extraInfo, var10); + } finally { + extraInfo.popKey(); + } + + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + if (this.unmodifiable) { + map = Collections.unmodifiableMap(map); + } + + return map; + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ObjectSchema s = new ObjectSchema(); + s.setPropertyNames(new StringSchema()); + s.setAdditionalProperties(context.refDefinition(this.codec)); + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/map/Short2ObjectMapCodec.java b/src/com/hypixel/hytale/codec/codecs/map/Short2ObjectMapCodec.java new file mode 100644 index 0000000..8ba2d7b --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/map/Short2ObjectMapCodec.java @@ -0,0 +1,129 @@ +package com.hypixel.hytale.codec.codecs.map; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.WrappedCodec; +import com.hypixel.hytale.codec.codecs.StringIntegerCodec; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import it.unimi.dsi.fastutil.shorts.Short2ObjectMaps; +import java.io.IOException; +import java.util.Map.Entry; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class Short2ObjectMapCodec implements Codec>, WrappedCodec { + private final Codec valueCodec; + private final Supplier> supplier; + private final boolean unmodifiable; + + public Short2ObjectMapCodec(Codec valueCodec, Supplier> supplier, boolean unmodifiable) { + this.valueCodec = valueCodec; + this.supplier = supplier; + this.unmodifiable = unmodifiable; + } + + public Short2ObjectMapCodec(Codec valueCodec, Supplier> supplier) { + this(valueCodec, supplier, true); + } + + @Override + public Codec getChildCodec() { + return this.valueCodec; + } + + public Short2ObjectMap decode(@Nonnull BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { + BsonDocument bsonDocument = bsonValue.asDocument(); + Short2ObjectMap map = this.supplier.get(); + + for (Entry entry : bsonDocument.entrySet()) { + String key = entry.getKey(); + BsonValue value = entry.getValue(); + extraInfo.pushKey(key); + + try { + short decodedKey = Short.valueOf(key); + map.put(decodedKey, this.valueCodec.decode(value, extraInfo)); + } catch (Exception var13) { + throw new CodecException("Failed to decode", value, extraInfo, var13); + } finally { + extraInfo.popKey(); + } + } + + if (this.unmodifiable) { + map = Short2ObjectMaps.unmodifiable(map); + } + + return map; + } + + @Nonnull + public BsonValue encode(@Nonnull Short2ObjectMap map, ExtraInfo extraInfo) { + BsonDocument bsonDocument = new BsonDocument(); + + for (it.unimi.dsi.fastutil.shorts.Short2ObjectMap.Entry entry : map.short2ObjectEntrySet()) { + bsonDocument.put(Short.toString(entry.getShortKey()), this.valueCodec.encode(entry.getValue(), extraInfo)); + } + + return bsonDocument; + } + + public Short2ObjectMap decodeJson(@Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.expect('{'); + reader.consumeWhiteSpace(); + Short2ObjectMap map = this.supplier.get(); + if (reader.tryConsume('}')) { + if (this.unmodifiable) { + map = Short2ObjectMaps.unmodifiable(map); + } + + return map; + } else { + while (true) { + String key = reader.readString(); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + extraInfo.pushKey(key, reader); + + try { + short decodedKey = Short.valueOf(key); + map.put(decodedKey, this.valueCodec.decodeJson(reader, extraInfo)); + } catch (Exception var9) { + throw new CodecException("Failed to decode", reader, extraInfo, var9); + } finally { + extraInfo.popKey(); + } + + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + if (this.unmodifiable) { + map = Short2ObjectMaps.unmodifiable(map); + } + + return map; + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ObjectSchema s = new ObjectSchema(); + StringSchema name = StringIntegerCodec.INSTANCE.toSchema(context); + s.setPropertyNames(name); + s.setAdditionalProperties(context.refDefinition(this.valueCodec)); + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/set/SetCodec.java b/src/com/hypixel/hytale/codec/codecs/set/SetCodec.java new file mode 100644 index 0000000..2b28cc9 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/set/SetCodec.java @@ -0,0 +1,124 @@ +package com.hypixel.hytale.codec.codecs.set; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.WrappedCodec; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import java.util.Collections; +import java.util.Set; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonValue; + +public class SetCodec> implements Codec>, WrappedCodec { + private final Codec codec; + private final Supplier supplier; + private final boolean unmodifiable; + + public SetCodec(Codec codec, Supplier supplier, boolean unmodifiable) { + this.codec = codec; + this.supplier = supplier; + this.unmodifiable = unmodifiable; + } + + public Set decode(@Nonnull BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { + BsonArray list = bsonValue.asArray(); + if (list.isEmpty()) { + return this.unmodifiable ? Collections.emptySet() : this.supplier.get(); + } else { + S out = this.supplier.get(); + + for (int i = 0; i < list.size(); i++) { + BsonValue value = list.get(i); + extraInfo.pushIntKey(i); + + try { + V decoded = this.codec.decode(value, extraInfo); + if (!out.add(decoded)) { + throw new CodecException("The value is already in the set:" + decoded); + } + } catch (Exception var11) { + throw new CodecException("Failed to decode", value, extraInfo, var11); + } finally { + extraInfo.popKey(); + } + } + + return this.unmodifiable ? Collections.unmodifiableSet(out) : out; + } + } + + public Set decodeJson(@Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.expect('['); + reader.consumeWhiteSpace(); + if (reader.tryConsume(']')) { + return this.unmodifiable ? Collections.emptySet() : this.supplier.get(); + } else { + int i = 0; + S out = this.supplier.get(); + + while (true) { + extraInfo.pushIntKey(i, reader); + + try { + V decoded = this.codec.decodeJson(reader, extraInfo); + if (!out.add(decoded)) { + throw new CodecException("The value is already in the set:" + decoded); + } + + i++; + } catch (Exception var9) { + throw new CodecException("Failed to decode", reader, extraInfo, var9); + } finally { + extraInfo.popKey(); + } + + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect(']', ',')) { + return this.unmodifiable ? Collections.unmodifiableSet(out) : out; + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + public BsonValue encode(@Nonnull Set vs, @Nonnull ExtraInfo extraInfo) { + BsonArray out = new BsonArray(); + int key = 0; + + for (V v : vs) { + extraInfo.pushIntKey(key++); + + try { + out.add(this.codec.encode(v, extraInfo)); + } finally { + extraInfo.popKey(); + } + } + + return out; + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ArraySchema schema = new ArraySchema(); + schema.setTitle("Set"); + schema.setItem(context.refDefinition(this.codec)); + schema.setUniqueItems(true); + return schema; + } + + @Override + public Codec getChildCodec() { + return this.codec; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/simple/BooleanCodec.java b/src/com/hypixel/hytale/codec/codecs/simple/BooleanCodec.java new file mode 100644 index 0000000..1ad4fec --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/simple/BooleanCodec.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.codec.codecs.simple; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.PrimitiveCodec; +import com.hypixel.hytale.codec.RawJsonCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.BooleanSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonBoolean; +import org.bson.BsonValue; + +public class BooleanCodec implements Codec, RawJsonCodec, PrimitiveCodec { + public BooleanCodec() { + } + + @Nonnull + public Boolean decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + return bsonValue.asBoolean().getValue(); + } + + @Nonnull + public BsonValue encode(Boolean t, ExtraInfo extraInfo) { + return new BsonBoolean(t); + } + + @Nonnull + public Boolean decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + return reader.readBooleanValue(); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return new BooleanSchema(); + } + + @Nonnull + public Schema toSchema(@Nonnull SchemaContext context, @Nullable Boolean def) { + BooleanSchema s = new BooleanSchema(); + if (def != null) { + s.setDefault(def); + } + + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/simple/ByteCodec.java b/src/com/hypixel/hytale/codec/codecs/simple/ByteCodec.java new file mode 100644 index 0000000..1b929f5 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/simple/ByteCodec.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.codec.codecs.simple; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.PrimitiveCodec; +import com.hypixel.hytale.codec.RawJsonCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.IntegerSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonInt32; +import org.bson.BsonValue; + +public class ByteCodec implements Codec, RawJsonCodec, PrimitiveCodec { + public ByteCodec() { + } + + @Nonnull + public Byte decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + int intValue = bsonValue.asNumber().intValue(); + if (intValue >= -128 && intValue <= 127) { + return (byte)intValue; + } else { + throw new IllegalArgumentException("Expected a value between -128 and 127"); + } + } + + @Nonnull + public BsonValue encode(Byte t, ExtraInfo extraInfo) { + return new BsonInt32(t); + } + + @Nonnull + public Byte decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + int intValue = reader.readIntValue(); + if (intValue >= -128 && intValue <= 127) { + return (byte)intValue; + } else { + throw new IllegalArgumentException("Expected a value between -128 and 127"); + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return new IntegerSchema(); + } + + @Nonnull + public Schema toSchema(@Nonnull SchemaContext context, @Nullable Byte def) { + IntegerSchema s = new IntegerSchema(); + if (def != null) { + s.setDefault(def.intValue()); + } + + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/simple/DoubleCodec.java b/src/com/hypixel/hytale/codec/codecs/simple/DoubleCodec.java new file mode 100644 index 0000000..12e7ebd --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/simple/DoubleCodec.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.codec.codecs.simple; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.PrimitiveCodec; +import com.hypixel.hytale.codec.RawJsonCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDouble; +import org.bson.BsonValue; + +public class DoubleCodec implements Codec, RawJsonCodec, PrimitiveCodec { + public DoubleCodec() { + } + + @Nonnull + public Double decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + if (bsonValue.isString()) { + String var3 = bsonValue.asString().getValue(); + switch (var3) { + case "NaN": + return Double.NaN; + case "Infinity": + return Double.POSITIVE_INFINITY; + case "-Infinity": + return Double.NEGATIVE_INFINITY; + } + } + + return bsonValue.asNumber().doubleValue(); + } + + @Nonnull + public BsonValue encode(Double t, ExtraInfo extraInfo) { + return new BsonDouble(t); + } + + @Nonnull + public Double decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + return reader.readDoubleValue(); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return new NumberSchema(); + } + + @Nonnull + public Schema toSchema(@Nonnull SchemaContext context, @Nullable Double def) { + NumberSchema s = new NumberSchema(); + if (def != null && !def.isNaN() && !def.isInfinite()) { + s.setDefault(def); + } + + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/simple/FloatCodec.java b/src/com/hypixel/hytale/codec/codecs/simple/FloatCodec.java new file mode 100644 index 0000000..fcfb728 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/simple/FloatCodec.java @@ -0,0 +1,95 @@ +package com.hypixel.hytale.codec.codecs.simple; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.PrimitiveCodec; +import com.hypixel.hytale.codec.RawJsonCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDouble; +import org.bson.BsonValue; + +public class FloatCodec implements Codec, RawJsonCodec, PrimitiveCodec { + public static final String STRING_SCHEMA_PATTERN = "^(-?Infinity|NaN)$"; + + public FloatCodec() { + } + + @Nonnull + public Float decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + return decodeFloat(bsonValue); + } + + @Nonnull + public BsonValue encode(Float t, ExtraInfo extraInfo) { + return new BsonDouble(t.floatValue()); + } + + @Nonnull + public Float decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + return readFloat(reader); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + StringSchema stringSchema = new StringSchema(); + stringSchema.setPattern("^(-?Infinity|NaN)$"); + return Schema.anyOf(new NumberSchema(), stringSchema); + } + + @Nonnull + public Schema toSchema(@Nonnull SchemaContext context, @Nullable Float def) { + StringSchema stringSchema = new StringSchema(); + stringSchema.setPattern("^(-?Infinity|NaN)$"); + NumberSchema numberSchema = new NumberSchema(); + if (def != null) { + if (!def.isNaN() && !def.isInfinite()) { + numberSchema.setDefault(def.doubleValue()); + } else { + stringSchema.setDefault(def.toString()); + } + } + + Schema schema = Schema.anyOf(numberSchema, stringSchema); + schema.getHytale().setType("Number"); + return schema; + } + + public static float decodeFloat(@Nonnull BsonValue value) { + if (value.isString()) { + String var1 = value.asString().getValue(); + switch (var1) { + case "NaN": + return Float.NaN; + case "Infinity": + return Float.POSITIVE_INFINITY; + case "-Infinity": + return Float.NEGATIVE_INFINITY; + } + } + + return (float)value.asNumber().doubleValue(); + } + + public static float readFloat(@Nonnull RawJsonReader reader) throws IOException { + if (reader.peekFor('"')) { + String str = reader.readString(); + + return switch (str) { + case "NaN" -> Float.NaN; + case "Infinity" -> Float.POSITIVE_INFINITY; + case "-Infinity" -> Float.NEGATIVE_INFINITY; + default -> throw new IOException("Unexpected string: \"" + str + "\", expected NaN, Infinity, -Infinity"); + }; + } else { + return (float)reader.readDoubleValue(); + } + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/simple/IntegerCodec.java b/src/com/hypixel/hytale/codec/codecs/simple/IntegerCodec.java new file mode 100644 index 0000000..6aa1219 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/simple/IntegerCodec.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.codec.codecs.simple; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.PrimitiveCodec; +import com.hypixel.hytale.codec.RawJsonCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.IntegerSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonInt32; +import org.bson.BsonValue; + +public class IntegerCodec implements Codec, RawJsonCodec, PrimitiveCodec { + public IntegerCodec() { + } + + @Nonnull + public Integer decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + int intValue = bsonValue.asNumber().intValue(); + if (intValue != bsonValue.asNumber().doubleValue()) { + throw new IllegalArgumentException("Expected an int but got a decimal!"); + } else { + return intValue; + } + } + + @Nonnull + public BsonValue encode(Integer t, ExtraInfo extraInfo) { + return new BsonInt32(t); + } + + @Nonnull + public Integer decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + return reader.readIntValue(); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return new IntegerSchema(); + } + + @Nonnull + public Schema toSchema(@Nonnull SchemaContext context, @Nullable Integer def) { + IntegerSchema s = new IntegerSchema(); + if (def != null) { + s.setDefault(def); + } + + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/simple/LongCodec.java b/src/com/hypixel/hytale/codec/codecs/simple/LongCodec.java new file mode 100644 index 0000000..3662a06 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/simple/LongCodec.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.codec.codecs.simple; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.PrimitiveCodec; +import com.hypixel.hytale.codec.RawJsonCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.IntegerSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonInt64; +import org.bson.BsonValue; + +public class LongCodec implements Codec, RawJsonCodec, PrimitiveCodec { + public LongCodec() { + } + + @Nonnull + public Long decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + long longValue = bsonValue.asNumber().longValue(); + if (longValue != bsonValue.asNumber().doubleValue()) { + throw new IllegalArgumentException("Expected an long but got a decimal!"); + } else { + return longValue; + } + } + + @Nonnull + public BsonValue encode(Long t, ExtraInfo extraInfo) { + return new BsonInt64(t); + } + + @Nonnull + public Long decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + return reader.readLongValue(); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return new IntegerSchema(); + } + + @Nonnull + public Schema toSchema(@Nonnull SchemaContext context, @Nullable Long def) { + IntegerSchema s = new IntegerSchema(); + if (def != null) { + s.setDefault(def.intValue()); + } + + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/simple/ShortCodec.java b/src/com/hypixel/hytale/codec/codecs/simple/ShortCodec.java new file mode 100644 index 0000000..a957042 --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/simple/ShortCodec.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.codec.codecs.simple; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.PrimitiveCodec; +import com.hypixel.hytale.codec.RawJsonCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.IntegerSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import javax.annotation.Nonnull; +import org.bson.BsonInt32; +import org.bson.BsonValue; + +public class ShortCodec implements Codec, RawJsonCodec, PrimitiveCodec { + public ShortCodec() { + } + + @Nonnull + public Short decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + int intValue = bsonValue.asNumber().intValue(); + if (intValue >= -32768 && intValue <= 32767) { + return (short)intValue; + } else { + throw new IllegalArgumentException("Expected a value between -32768 and 32767"); + } + } + + @Nonnull + public BsonValue encode(Short t, ExtraInfo extraInfo) { + return new BsonInt32(t); + } + + @Nonnull + public Short decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + int intValue = reader.readIntValue(); + if (intValue >= -32768 && intValue <= 32767) { + return (short)intValue; + } else { + throw new IllegalArgumentException("Expected a value between -32768 and 32767"); + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return new IntegerSchema(); + } +} diff --git a/src/com/hypixel/hytale/codec/codecs/simple/StringCodec.java b/src/com/hypixel/hytale/codec/codecs/simple/StringCodec.java new file mode 100644 index 0000000..72e9b9e --- /dev/null +++ b/src/com/hypixel/hytale/codec/codecs/simple/StringCodec.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.codec.codecs.simple; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.RawJsonCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonString; +import org.bson.BsonValue; + +public class StringCodec implements Codec, RawJsonCodec { + public StringCodec() { + } + + public String decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + return bsonValue.asString().getValue(); + } + + @Nonnull + public BsonValue encode(@Nonnull String t, ExtraInfo extraInfo) { + return new BsonString(t); + } + + public String decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + return reader.readString(); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return new StringSchema(); + } + + @Nonnull + public Schema toSchema(@Nonnull SchemaContext context, @Nullable String def) { + StringSchema s = new StringSchema(); + if (def != null) { + s.setDefault(def); + } + + return s; + } +} diff --git a/src/com/hypixel/hytale/codec/exception/CodecException.java b/src/com/hypixel/hytale/codec/exception/CodecException.java new file mode 100644 index 0000000..dba697c --- /dev/null +++ b/src/com/hypixel/hytale/codec/exception/CodecException.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.codec.exception; + +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.util.RawJsonReader; +import javax.annotation.Nonnull; +import org.bson.BsonValue; + +public class CodecException extends RuntimeException { + private final String message; + + public CodecException(String message) { + super(message); + this.message = message; + } + + public CodecException(String message, Throwable cause) { + super(message, cause); + this.message = message; + } + + public CodecException(String message, BsonValue bsonValue, @Nonnull ExtraInfo extraInfo, Throwable cause) { + super(message + " '" + extraInfo.peekKey() + "' " + (cause instanceof CodecException ? "" : "\nFrom: '" + bsonValue + "'"), cause); + this.message = message; + } + + public CodecException(String message, RawJsonReader reader, @Nonnull ExtraInfo extraInfo, Throwable cause) { + super(message + " '" + extraInfo.peekKey() + "' " + (cause instanceof CodecException ? "" : "\nFrom: " + reader + "'"), cause); + this.message = message; + } + + public CodecException(String message, Object obj, @Nonnull ExtraInfo extraInfo, Throwable cause) { + super(message + " '" + extraInfo.peekKey() + "' " + (cause instanceof CodecException ? "" : "\nFor: '" + obj + "'"), cause); + this.message = message; + } + + @Override + public String getMessage() { + return this.message; + } +} diff --git a/src/com/hypixel/hytale/codec/exception/CodecValidationException.java b/src/com/hypixel/hytale/codec/exception/CodecValidationException.java new file mode 100644 index 0000000..4f7dea0 --- /dev/null +++ b/src/com/hypixel/hytale/codec/exception/CodecValidationException.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.codec.exception; + +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.util.RawJsonReader; +import javax.annotation.Nonnull; +import org.bson.BsonValue; + +public class CodecValidationException extends CodecException { + public CodecValidationException(String message) { + super(message); + } + + public CodecValidationException(String message, Throwable cause) { + super(message, cause); + } + + public CodecValidationException(String message, BsonValue bsonValue, @Nonnull ExtraInfo extraInfo, Throwable cause) { + super(message, bsonValue, extraInfo, cause); + } + + public CodecValidationException(String message, RawJsonReader reader, @Nonnull ExtraInfo extraInfo, Throwable cause) { + super(message, reader, extraInfo, cause); + } + + public CodecValidationException(String message, Object obj, @Nonnull ExtraInfo extraInfo, Throwable cause) { + super(message, obj, extraInfo, cause); + } +} diff --git a/src/com/hypixel/hytale/codec/function/BsonFunctionCodec.java b/src/com/hypixel/hytale/codec/function/BsonFunctionCodec.java new file mode 100644 index 0000000..83d7f69 --- /dev/null +++ b/src/com/hypixel/hytale/codec/function/BsonFunctionCodec.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.codec.function; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.WrappedCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import java.util.Objects; +import java.util.function.BiFunction; +import javax.annotation.Nonnull; +import org.bson.BsonValue; + +@Deprecated +public class BsonFunctionCodec implements Codec, WrappedCodec { + @Nonnull + private final Codec codec; + @Nonnull + private final BiFunction decode; + @Nonnull + private final BiFunction encode; + + public BsonFunctionCodec(Codec codec, BiFunction decode, BiFunction encode) { + this.codec = Objects.requireNonNull(codec, "codec parameter can't be null"); + this.decode = Objects.requireNonNull(decode, "decode parameter can't be null"); + this.encode = Objects.requireNonNull(encode, "encode parameter can't be null"); + } + + @Override + public T decode(BsonValue bsonValue, ExtraInfo extraInfo) { + return this.decode.apply(this.codec.decode(bsonValue, extraInfo), bsonValue); + } + + @Override + public BsonValue encode(T r, ExtraInfo extraInfo) { + return this.encode.apply(this.codec.encode(r, extraInfo), r); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return this.codec.toSchema(context); + } + + @Nonnull + @Override + public Codec getChildCodec() { + return this.codec; + } +} diff --git a/src/com/hypixel/hytale/codec/function/FunctionCodec.java b/src/com/hypixel/hytale/codec/function/FunctionCodec.java new file mode 100644 index 0000000..dbba300 --- /dev/null +++ b/src/com/hypixel/hytale/codec/function/FunctionCodec.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.codec.function; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import java.util.Objects; +import java.util.function.Function; +import javax.annotation.Nonnull; +import org.bson.BsonValue; + +@Deprecated +public class FunctionCodec implements Codec { + @Nonnull + private final Codec codec; + @Nonnull + private final Function decode; + @Nonnull + private final Function encode; + + public FunctionCodec(Codec codec, Function decode, Function encode) { + this.codec = Objects.requireNonNull(codec, "codec parameter can't be null"); + this.decode = Objects.requireNonNull(decode, "decode parameter can't be null"); + this.encode = Objects.requireNonNull(encode, "encode parameter can't be null"); + } + + @Nonnull + @Override + public R decode(BsonValue bsonValue, ExtraInfo extraInfo) { + T decode = this.codec.decode(bsonValue, extraInfo); + R value = this.decode.apply(decode); + if (value == null) { + throw new IllegalArgumentException("Failed to apply function to '" + decode + "' decoded from '" + bsonValue + "'!"); + } else { + return value; + } + } + + @Override + public BsonValue encode(R r, ExtraInfo extraInfo) { + return this.codec.encode(this.encode.apply(r), extraInfo); + } + + @Nonnull + @Override + public R decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + T decode = this.codec.decodeJson(reader, extraInfo); + R value = this.decode.apply(decode); + if (value == null) { + throw new IllegalArgumentException("Failed to apply function to '" + decode + "'!"); + } else { + return value; + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return this.codec.toSchema(context); + } +} diff --git a/src/com/hypixel/hytale/codec/lookup/ACodecMapCodec.java b/src/com/hypixel/hytale/codec/lookup/ACodecMapCodec.java new file mode 100644 index 0000000..69326ab --- /dev/null +++ b/src/com/hypixel/hytale/codec/lookup/ACodecMapCodec.java @@ -0,0 +1,461 @@ +package com.hypixel.hytale.codec.lookup; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.InheritCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.codec.validation.ValidatableCodec; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public abstract class ACodecMapCodec> implements Codec, ValidatableCodec, InheritCodec { + protected final String key; + protected final Codec keyCodec; + protected final Map idToCodec = new ConcurrentHashMap<>(); + protected final Map, K> classToId = new ConcurrentHashMap<>(); + protected final Map> idToClass = new ConcurrentHashMap<>(); + @Nonnull + protected AtomicReference[]> codecs = new AtomicReference<>(new ACodecMapCodec.CodecPriority[0]); + protected final boolean allowDefault; + protected final boolean encodeDefaultKey; + + public ACodecMapCodec(Codec keyCodec) { + this(keyCodec, false); + } + + public ACodecMapCodec(Codec keyCodec, boolean allowDefault) { + this("Id", keyCodec, allowDefault); + } + + public ACodecMapCodec(String id, Codec keyCodec) { + this(id, keyCodec, false); + } + + public ACodecMapCodec(String key, Codec keyCodec, boolean allowDefault) { + this(key, keyCodec, allowDefault, true); + } + + public ACodecMapCodec(String key, Codec keyCodec, boolean allowDefault, boolean encodeDefaultKey) { + this.key = key; + this.allowDefault = allowDefault; + this.encodeDefaultKey = encodeDefaultKey; + this.keyCodec = keyCodec; + } + + @Nonnull + public ACodecMapCodec register(K id, Class aClass, C codec) { + this.register(Priority.NORMAL, id, aClass, codec); + return this; + } + + public ACodecMapCodec register(@Nonnull Priority priority, K id, Class aClass, C codec) { + this.idToCodec.put(id, codec); + this.classToId.put(aClass, id); + this.idToClass.put(id, aClass); + if (codec instanceof ValidatableCodec) { + ((ValidatableCodec)codec).validateDefaults(new ExtraInfo(), new HashSet<>()); + } + + if (!this.allowDefault && !priority.equals(Priority.NORMAL)) { + throw new IllegalStateException("Defaults disallowed but non-normal priority provided"); + } else if (!this.allowDefault) { + return this; + } else { + ACodecMapCodec.CodecPriority codecPriority = new ACodecMapCodec.CodecPriority<>(codec, priority); + + ACodecMapCodec.CodecPriority[] current; + ACodecMapCodec.CodecPriority[] newCodecs; + do { + current = this.codecs.get(); + int index = Arrays.binarySearch(current, codecPriority, Comparator.comparingInt(a -> a.priority().getLevel())); + int insertionPoint; + if (index >= 0) { + insertionPoint = index + 1; + } else { + insertionPoint = -(index + 1); + } + + newCodecs = new ACodecMapCodec.CodecPriority[current.length + 1]; + System.arraycopy(current, 0, newCodecs, 0, insertionPoint); + newCodecs[insertionPoint] = codecPriority; + System.arraycopy(current, insertionPoint, newCodecs, insertionPoint + 1, current.length - insertionPoint); + } while (!this.codecs.compareAndSet(current, newCodecs)); + + return this; + } + } + + public void remove(Class aClass) { + K id = this.classToId.remove(aClass); + C codec = this.idToCodec.remove(id); + this.idToClass.remove(id); + if (this.allowDefault) { + ACodecMapCodec.CodecPriority[] current; + ACodecMapCodec.CodecPriority[] newCodecs; + do { + current = this.codecs.get(); + int index = -1; + + for (int i = 0; i < current.length; i++) { + ACodecMapCodec.CodecPriority c = current[i]; + if (c.codec() == codec) { + index = i; + break; + } + } + + if (index == -1) { + return; + } + + newCodecs = new ACodecMapCodec.CodecPriority[current.length - 1]; + System.arraycopy(current, 0, newCodecs, 0, index); + System.arraycopy(current, index + 1, newCodecs, index, current.length - index - 1); + } while (!this.codecs.compareAndSet(current, newCodecs)); + } + } + + @Nullable + public C getDefaultCodec() { + ACodecMapCodec.CodecPriority[] c = this.codecs.get(); + return c.length == 0 ? null : c[0].codec(); + } + + public C getCodecFor(K key) { + return this.idToCodec.get(key); + } + + public C getCodecFor(Class key) { + return this.idToCodec.get(this.classToId.get(key)); + } + + public Class getClassFor(K key) { + return this.idToClass.get(key); + } + + public K getIdFor(Class key) { + return this.classToId.get(key); + } + + public Set getRegisteredIds() { + return Collections.unmodifiableSet(this.idToCodec.keySet()); + } + + @Override + public T decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + BsonDocument document = bsonValue.asDocument(); + BsonValue id = document.get(this.key); + C codec = id == null ? null : this.idToCodec.get(this.keyCodec.decode(id, extraInfo)); + if (codec == null) { + C defaultCodec = this.getDefaultCodec(); + if (defaultCodec == null) { + throw new ACodecMapCodec.UnknownIdException("No codec registered with for '" + this.key + "': " + id); + } else { + return (T)defaultCodec.decode(document, extraInfo); + } + } else { + return (T)codec.decode(document, extraInfo); + } + } + + @Nullable + @Override + public T decodeAndInherit(@Nonnull BsonDocument document, T parent, ExtraInfo extraInfo) { + BsonValue id = document.get(this.key); + C codec = this.idToCodec.get(id == null ? null : id.asString().getValue()); + if (codec == null) { + C defaultCodec = this.getDefaultCodec(); + if (defaultCodec == null) { + throw new ACodecMapCodec.UnknownIdException("No codec registered with for '" + this.key + "': " + id); + } else { + return (T)(defaultCodec instanceof InheritCodec + ? ((InheritCodec)defaultCodec).decodeAndInherit(document, parent, extraInfo) + : defaultCodec.decode(document, extraInfo)); + } + } else { + return (T)(codec instanceof InheritCodec ? ((InheritCodec)codec).decodeAndInherit(document, parent, extraInfo) : codec.decode(document, extraInfo)); + } + } + + @Override + public void decodeAndInherit(@Nonnull BsonDocument document, T t, T parent, ExtraInfo extraInfo) { + BsonValue id = document.get(this.key); + C codec = this.idToCodec.get(id == null ? null : id.asString().getValue()); + if (codec == null) { + C defaultCodec = this.getDefaultCodec(); + if (defaultCodec == null) { + throw new ACodecMapCodec.UnknownIdException("No codec registered with for '" + this.key + "': " + id); + } else if (defaultCodec instanceof InheritCodec) { + ((InheritCodec)defaultCodec).decodeAndInherit(document, t, parent, extraInfo); + } else { + throw new UnsupportedOperationException(); + } + } else if (codec instanceof InheritCodec) { + ((InheritCodec)codec).decodeAndInherit(document, t, parent, extraInfo); + } else { + throw new UnsupportedOperationException(); + } + } + + @Override + public BsonValue encode(@Nonnull T t, ExtraInfo extraInfo) { + Class aClass = (Class)t.getClass(); + K id = this.classToId.get(aClass); + C defaultCodec = this.getDefaultCodec(); + if (id == null && defaultCodec == null) { + throw new ACodecMapCodec.UnknownIdException("No id registered with for '" + aClass + "': " + t); + } else { + C codec = this.idToCodec.get(id); + if (codec == null) { + if (defaultCodec == null) { + throw new ACodecMapCodec.UnknownIdException("No codec registered with for '" + aClass + "': " + t); + } + + codec = defaultCodec; + } + + BsonValue encode = codec.encode(t, extraInfo); + if (id == null) { + return encode; + } else { + BsonDocument document = new BsonDocument(); + if (this.encodeDefaultKey || codec != defaultCodec) { + document.put(this.key, this.keyCodec.encode(id, extraInfo)); + } + + document.putAll(encode.asDocument()); + return document; + } + } + } + + @Nullable + @Override + public T decodeJson(@Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.mark(); + K id = null; + if (RawJsonReader.seekToKey(reader, this.key)) { + id = this.keyCodec.decodeJson(reader, extraInfo); + } + + reader.reset(); + extraInfo.ignoreUnusedKey(this.key); + + Object var6; + try { + C codec = id == null ? null : this.idToCodec.get(id); + if (codec != null) { + return (T)codec.decodeJson(reader, extraInfo); + } + + C defaultCodec = this.getDefaultCodec(); + if (defaultCodec == null) { + throw new ACodecMapCodec.UnknownIdException("No codec registered with for '" + this.key + "': " + id); + } + + var6 = defaultCodec.decodeJson(reader, extraInfo); + } finally { + extraInfo.popIgnoredUnusedKey(); + } + + return (T)var6; + } + + @Nullable + @Override + public T decodeAndInheritJson(@Nonnull RawJsonReader reader, @Nullable T parent, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.mark(); + K id = null; + if (RawJsonReader.seekToKey(reader, this.key)) { + id = this.keyCodec.decodeJson(reader, extraInfo); + } else if (parent != null) { + id = this.getIdFor((Class)parent.getClass()); + } + + reader.reset(); + extraInfo.ignoreUnusedKey(this.key); + + Object var7; + try { + C codec = id == null ? null : this.idToCodec.get(id); + if (codec != null) { + if (!(codec instanceof InheritCodec)) { + return (T)codec.decodeJson(reader, extraInfo); + } + + return (T)((InheritCodec)codec).decodeAndInheritJson(reader, parent, extraInfo); + } + + C defaultCodec = this.getDefaultCodec(); + if (defaultCodec == null) { + throw new ACodecMapCodec.UnknownIdException("No codec registered with for '" + this.key + "': " + id); + } + + if (!(defaultCodec instanceof InheritCodec)) { + return (T)defaultCodec.decodeJson(reader, extraInfo); + } + + var7 = ((InheritCodec)defaultCodec).decodeAndInheritJson(reader, parent, extraInfo); + } finally { + extraInfo.popIgnoredUnusedKey(); + } + + return (T)var7; + } + + @Override + public void decodeAndInheritJson(@Nonnull RawJsonReader reader, T t, @Nullable T parent, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.mark(); + K id = null; + if (RawJsonReader.seekToKey(reader, this.key)) { + id = this.keyCodec.decodeJson(reader, extraInfo); + } else if (parent != null) { + id = this.getIdFor((Class)parent.getClass()); + } + + reader.reset(); + extraInfo.ignoreUnusedKey(this.key); + + try { + C codec = id == null ? null : this.idToCodec.get(id); + if (codec != null) { + if (!(codec instanceof InheritCodec)) { + throw new UnsupportedOperationException(); + } + + ((InheritCodec)codec).decodeAndInheritJson(reader, t, parent, extraInfo); + return; + } + + C defaultCodec = this.getDefaultCodec(); + if (defaultCodec == null) { + throw new ACodecMapCodec.UnknownIdException("No codec registered with for '" + this.key + "': " + id); + } + + if (!(defaultCodec instanceof InheritCodec)) { + throw new UnsupportedOperationException(); + } + + ((InheritCodec)defaultCodec).decodeAndInheritJson(reader, t, parent, extraInfo); + } finally { + extraInfo.popIgnoredUnusedKey(); + } + } + + @Override + public void validate(@Nonnull T t, ExtraInfo extraInfo) { + K id = this.getIdFor((Class)t.getClass()); + C codec = this.getCodecFor(id); + if (this.keyCodec instanceof ValidatableCodec) { + ((ValidatableCodec)this.keyCodec).validate(id, extraInfo); + } + + if (codec instanceof ValidatableCodec) { + ((ValidatableCodec)codec).validate(t, extraInfo); + } + } + + @Override + public void validateDefaults(ExtraInfo extraInfo, @Nonnull Set> tested) { + if (tested.add(this)) { + ValidatableCodec.validateDefaults(this.keyCodec, extraInfo, tested); + + for (C codec : this.idToCodec.values()) { + ValidatableCodec.validateDefaults(codec, extraInfo, tested); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + List options = new ObjectArrayList<>(); + Entry[] entries = this.idToCodec.entrySet().toArray(Entry[]::new); + Arrays.sort(entries, Comparator.comparing(e -> (Comparable)(e.getKey() instanceof Comparable ? (Comparable)e.getKey() : e.getKey().toString()))); + C def = this.allowDefault ? this.getDefaultCodec() : null; + String defKey = null; + + for (Entry entry : entries) { + C c = entry.getValue(); + if (c == def) { + defKey = entry.getKey().toString(); + } + + Schema schema = context.refDefinition(c); + if (schema.getRef() != null && c instanceof BuilderCodec bc && context.getRawDefinition(bc) instanceof ObjectSchema objectSchema) { + this.mutateChildSchema(entry.getKey().toString(), context, bc, objectSchema); + } + + options.add(schema); + } + + if (options.isEmpty()) { + ObjectSchema s = new ObjectSchema(); + s.setAdditionalProperties(false); + return s; + } else { + Schema s = Schema.anyOf(options.toArray(Schema[]::new)); + s.getHytale().setMergesProperties(true); + s.setTitle("Type Selector"); + s.setHytaleSchemaTypeField(new Schema.SchemaTypeField(this.key, defKey, Arrays.stream(entries).map(e -> e.getKey().toString()).toArray(String[]::new))); + return s; + } + } + + protected void mutateChildSchema(String key, @Nonnull SchemaContext context, BuilderCodec c, @Nonnull ObjectSchema objectSchema) { + C def = null; + if (this.allowDefault) { + def = this.getDefaultCodec(); + } + + Schema keySchema = this.keyCodec.toSchema(context); + if (def == c) { + keySchema.setTypes(new String[]{"null", "string"}); + Schema origKey = keySchema; + keySchema = new Schema(); + StringSchema enum_ = new StringSchema(); + enum_.setEnum(this.idToCodec.entrySet().stream().filter(v -> v.getValue() != c).map(Entry::getKey).map(Object::toString).toArray(String[]::new)); + keySchema.setAllOf(origKey, Schema.not(enum_)); + } else { + ((StringSchema)keySchema).setConst(key); + } + + keySchema.setMarkdownDescription("This field controls the type, it must be set to the constant value \"" + key + "\" to function as this type."); + LinkedHashMap props = new LinkedHashMap<>(); + props.put(this.key, keySchema); + Map otherProps = objectSchema.getProperties(); + otherProps.remove(this.key); + props.putAll(otherProps); + objectSchema.setProperties(props); + } + + private record CodecPriority(C codec, Priority priority) { + } + + public static class UnknownIdException extends CodecException { + public UnknownIdException(String message) { + super(message); + } + } +} diff --git a/src/com/hypixel/hytale/codec/lookup/AMapProvidedMapCodec.java b/src/com/hypixel/hytale/codec/lookup/AMapProvidedMapCodec.java new file mode 100644 index 0000000..2c74c9b --- /dev/null +++ b/src/com/hypixel/hytale/codec/lookup/AMapProvidedMapCodec.java @@ -0,0 +1,177 @@ +package com.hypixel.hytale.codec.lookup; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.codec.validation.ValidatableCodec; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public abstract class AMapProvidedMapCodec> implements Codec, ValidatableCodec { + protected final Map codecProvider; + protected final Function> mapper; + protected final boolean unmodifiable; + + public AMapProvidedMapCodec(Map codecProvider, Function> mapper) { + this(codecProvider, mapper, true); + } + + public AMapProvidedMapCodec(Map codecProvider, Function> mapper, boolean unmodifiable) { + this.codecProvider = codecProvider; + this.mapper = mapper; + this.unmodifiable = unmodifiable; + } + + public abstract M createMap(); + + public void handleUnknown(M map, @Nonnull String key, BsonValue value, @Nonnull ExtraInfo extraInfo) { + extraInfo.addUnknownKey(key); + } + + public void handleUnknown(M map, @Nonnull String key, @Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + extraInfo.addUnknownKey(key); + reader.skipValue(); + } + + public M decode(@Nonnull BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { + BsonDocument bsonDocument = bsonValue.asDocument(); + M map = this.createMap(); + + for (Entry entry : bsonDocument.entrySet()) { + extraInfo.pushKey(entry.getKey()); + + try { + K key = this.getKeyForId(entry.getKey()); + if (key == null) { + this.handleUnknown(map, entry.getKey(), entry.getValue(), extraInfo); + } else { + Codec codecFor = this.getCodecFor(key); + map.put(key, codecFor.decode(entry.getValue(), extraInfo)); + } + } finally { + extraInfo.popKey(); + } + } + + if (this.unmodifiable) { + map = this.unmodifiableMap(map); + } + + return map; + } + + @Nonnull + public BsonValue encode(@Nonnull M map, ExtraInfo extraInfo) { + BsonDocument document = new BsonDocument(); + + for (Entry entry : map.entrySet()) { + Codec codecFor = this.getCodecFor(entry.getKey()); + document.put(this.getIdForKey(entry.getKey()), codecFor.encode(entry.getValue(), extraInfo)); + } + + this.encodeExtra(document, map, extraInfo); + return document; + } + + protected void encodeExtra(BsonDocument document, M map, ExtraInfo extraInfo) { + } + + public M decodeJson(@Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.expect('{'); + reader.consumeWhiteSpace(); + if (reader.tryConsume('}')) { + return this.unmodifiable ? this.emptyMap() : this.createMap(); + } else { + M map = this.createMap(); + + while (true) { + String id = reader.readString(); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + extraInfo.pushKey(id, reader); + + try { + K key = this.getKeyForId(id); + if (key == null) { + this.handleUnknown(map, id, reader, extraInfo); + } else { + Codec codec = this.getCodecFor(key); + map.put(key, codec.decodeJson(reader, extraInfo)); + } + } catch (Exception var10) { + throw new CodecException("Failed to decode", reader, extraInfo, var10); + } finally { + extraInfo.popKey(); + } + + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + if (this.unmodifiable) { + map = this.unmodifiableMap(map); + } + + return map; + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ObjectSchema obj = new ObjectSchema(); + obj.setAdditionalProperties(false); + LinkedHashMap props = this.codecProvider.keySet().stream().map(key -> { + Codec codec = this.getCodecFor((K)key); + return Map.entry(this.getIdForKey((K)key), codec.toSchema(context)); + }).sorted(Entry.comparingByKey()).collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> a, LinkedHashMap::new)); + obj.setProperties(props); + return obj; + } + + public void validate(@Nonnull M map, ExtraInfo extraInfo) { + for (Entry entry : map.entrySet()) { + Codec codec = this.getCodecFor(entry.getKey()); + if (codec instanceof ValidatableCodec) { + ((ValidatableCodec)codec).validate(entry.getValue(), extraInfo); + } + } + } + + @Override + public void validateDefaults(ExtraInfo extraInfo, @Nonnull Set> tested) { + if (tested.add(this)) { + for (P value : this.codecProvider.values()) { + Codec codec = this.mapper.apply(value); + ValidatableCodec.validateDefaults(codec, extraInfo, tested); + } + } + } + + private Codec getCodecFor(K key) { + return this.mapper.apply(this.codecProvider.get(key)); + } + + protected abstract String getIdForKey(K var1); + + protected abstract K getKeyForId(String var1); + + protected abstract M emptyMap(); + + protected abstract M unmodifiableMap(M var1); +} diff --git a/src/com/hypixel/hytale/codec/lookup/BuilderCodecMapCodec.java b/src/com/hypixel/hytale/codec/lookup/BuilderCodecMapCodec.java new file mode 100644 index 0000000..b15b46e --- /dev/null +++ b/src/com/hypixel/hytale/codec/lookup/BuilderCodecMapCodec.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.codec.lookup; + +import com.hypixel.hytale.codec.builder.BuilderCodec; + +public class BuilderCodecMapCodec extends StringCodecMapCodec> { + public BuilderCodecMapCodec() { + } + + public BuilderCodecMapCodec(boolean allowDefault) { + super(allowDefault); + } + + public BuilderCodecMapCodec(String id) { + super(id); + } + + public BuilderCodecMapCodec(String key, boolean allowDefault) { + super(key, allowDefault); + } + + public T getDefault() { + return (T)this.getDefaultCodec().getDefaultValue(); + } +} diff --git a/src/com/hypixel/hytale/codec/lookup/CodecMapCodec.java b/src/com/hypixel/hytale/codec/lookup/CodecMapCodec.java new file mode 100644 index 0000000..db35866 --- /dev/null +++ b/src/com/hypixel/hytale/codec/lookup/CodecMapCodec.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.codec.lookup; + +import com.hypixel.hytale.codec.Codec; +import javax.annotation.Nonnull; + +public class CodecMapCodec extends StringCodecMapCodec> { + public CodecMapCodec() { + } + + public CodecMapCodec(String id) { + super(id); + } + + public CodecMapCodec(boolean allowDefault) { + super(allowDefault); + } + + public CodecMapCodec(String key, boolean allowDefault) { + super(key, allowDefault); + } + + public CodecMapCodec(String key, boolean allowDefault, boolean encodeDefaultKey) { + super(key, allowDefault, encodeDefaultKey); + } + + @Nonnull + public CodecMapCodec register(String id, Class aClass, Codec codec) { + super.register(id, aClass, codec); + return this; + } + + @Nonnull + public CodecMapCodec register(@Nonnull Priority priority, @Nonnull String id, Class aClass, Codec codec) { + super.register(priority, id, aClass, codec); + return this; + } +} diff --git a/src/com/hypixel/hytale/codec/lookup/MapKeyMapCodec.java b/src/com/hypixel/hytale/codec/lookup/MapKeyMapCodec.java new file mode 100644 index 0000000..16a5b6e --- /dev/null +++ b/src/com/hypixel/hytale/codec/lookup/MapKeyMapCodec.java @@ -0,0 +1,331 @@ +package com.hypixel.hytale.codec.lookup; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.EmptyExtraInfo; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.logger.HytaleLogger; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.StampedLock; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class MapKeyMapCodec extends AMapProvidedMapCodec, V, Codec, MapKeyMapCodec.TypeMap> { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final Set>> ACTIVE_MAPS = ConcurrentHashMap.newKeySet(); + private static final ReferenceQueue> MAP_REFERENCE_QUEUE = new ReferenceQueue<>(); + private static final StampedLock DATA_LOCK = new StampedLock(); + protected final Map> idToClass = new ConcurrentHashMap<>(); + protected final Map, String> classToId = new ConcurrentHashMap<>(); + + public MapKeyMapCodec() { + this(true); + } + + public MapKeyMapCodec(boolean unmodifiable) { + super(new ConcurrentHashMap<>(), Function.identity(), unmodifiable); + } + + public void register(@Nonnull Class tClass, @Nonnull String id, @Nonnull Codec codec) { + long lock = DATA_LOCK.writeLock(); + + try { + if (this.codecProvider.put(tClass, codec) != null) { + throw new IllegalArgumentException("Id already registered"); + } + + if (this.idToClass.put(id, tClass) != null) { + throw new IllegalArgumentException("Id already registered"); + } + + if (this.classToId.put(tClass, id) != null) { + throw new IllegalArgumentException("Class already registered"); + } + + for (Reference> mapRef : ACTIVE_MAPS) { + MapKeyMapCodec.TypeMap map = mapRef.get(); + if (map != null && map.codec == this) { + map.tryUpgrade(tClass, id, codec); + } + } + } finally { + DATA_LOCK.unlockWrite(lock); + } + } + + public void unregister(@Nonnull Class tClass) { + long lock = DATA_LOCK.writeLock(); + + try { + Codec codec = this.codecProvider.get(tClass); + if (codec == null) { + throw new IllegalStateException(tClass + " not registered"); + } + + String id = this.classToId.get(tClass); + + for (Reference> mapRef : ACTIVE_MAPS) { + MapKeyMapCodec.TypeMap map = mapRef.get(); + if (map != null && map.codec == this) { + map.tryDowngrade(tClass, id, codec); + } + } + + this.codecProvider.remove(tClass); + this.classToId.remove(tClass); + this.idToClass.remove(id); + } finally { + DATA_LOCK.unlockWrite(lock); + } + } + + @Nullable + @Deprecated(forRemoval = true) + public V decodeById(@Nonnull String id, BsonValue value, ExtraInfo extraInfo) { + Codec codec = this.codecProvider.get(this.getKeyForId(id)); + return codec.decode(value, extraInfo); + } + + protected String getIdForKey(Class key) { + return this.classToId.get(key); + } + + @Nonnull + public MapKeyMapCodec.TypeMap createMap() { + return new MapKeyMapCodec.TypeMap<>(this); + } + + public void handleUnknown(@Nonnull MapKeyMapCodec.TypeMap map, @Nonnull String key, BsonValue value, @Nonnull ExtraInfo extraInfo) { + extraInfo.addUnknownKey(key); + map.unknownValues.put(key, value); + } + + public void handleUnknown(@Nonnull MapKeyMapCodec.TypeMap map, @Nonnull String key, @Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + extraInfo.addUnknownKey(key); + map.unknownValues.put(key, RawJsonReader.readBsonValue(reader)); + } + + protected void encodeExtra(@Nonnull BsonDocument document, @Nonnull MapKeyMapCodec.TypeMap map, ExtraInfo extraInfo) { + document.putAll(map.unknownValues); + } + + public Class getKeyForId(String id) { + return this.idToClass.get(id); + } + + @Nonnull + protected MapKeyMapCodec.TypeMap emptyMap() { + return MapKeyMapCodec.TypeMap.EMPTY; + } + + @Nonnull + protected MapKeyMapCodec.TypeMap unmodifiableMap(@Nonnull MapKeyMapCodec.TypeMap m) { + return new MapKeyMapCodec.TypeMap<>(this, Collections.unmodifiableMap(m.map), m.map, m.unknownValues); + } + + static { + Thread thread = new Thread(() -> { + while (!Thread.interrupted()) { + try { + ACTIVE_MAPS.remove(MAP_REFERENCE_QUEUE.remove()); + } catch (InterruptedException var1) { + Thread.currentThread().interrupt(); + return; + } + } + }, "MapKeyMapCodec"); + thread.setDaemon(true); + thread.start(); + } + + public static class TypeMap implements Map, V> { + private static final MapKeyMapCodec.TypeMap EMPTY = new MapKeyMapCodec.TypeMap(null, Collections.emptyMap(), Collections.emptyMap()); + private final MapKeyMapCodec codec; + @Nonnull + private final Map, V> map; + @Nonnull + private final Map, V> internalMap; + @Nonnull + private final Map unknownValues; + + public TypeMap(MapKeyMapCodec codec) { + this(codec, new Object2ObjectOpenHashMap<>(), new Object2ObjectOpenHashMap<>()); + } + + public TypeMap(MapKeyMapCodec codec, @Nonnull Map, V> map, @Nonnull Map unknownValues) { + this(codec, map, map, unknownValues); + } + + public TypeMap( + MapKeyMapCodec codec, + @Nonnull Map, V> map, + @Nonnull Map, V> internalMap, + @Nonnull Map unknownValues + ) { + this.codec = codec; + this.map = map; + this.internalMap = internalMap; + this.unknownValues = unknownValues; + MapKeyMapCodec.ACTIVE_MAPS.add(new WeakReference<>(this, MapKeyMapCodec.MAP_REFERENCE_QUEUE)); + } + + public void tryUpgrade(@Nonnull Class tClass, @Nonnull String id, @Nonnull Codec codec) { + BsonValue unknownValue = this.unknownValues.remove(id); + if (unknownValue != null) { + T value = codec.decode(unknownValue, EmptyExtraInfo.EMPTY); + this.internalMap.put(tClass, (V)value); + MapKeyMapCodec.LOGGER.atInfo().log("Upgrade " + id + " from unknown value"); + } + } + + public void tryDowngrade(@Nonnull Class tClass, @Nonnull String id, @Nonnull Codec codec) { + V value = this.internalMap.remove(tClass); + if (value != null) { + BsonValue encoded = codec.encode((T)value, EmptyExtraInfo.EMPTY); + this.unknownValues.put(id, encoded); + MapKeyMapCodec.LOGGER.atInfo().log("Downgraded " + id + " to unknown value"); + } + } + + @Override + public int size() { + return this.map.size(); + } + + @Override + public boolean isEmpty() { + return this.map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return this.map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return this.map.containsValue(value); + } + + @Override + public V get(Object key) { + return this.map.get(key); + } + + @Nullable + public T get(Class key) { + long lock = MapKeyMapCodec.DATA_LOCK.readLock(); + + Object var4; + try { + var4 = this.map.get(key); + } finally { + MapKeyMapCodec.DATA_LOCK.unlockRead(lock); + } + + return (T)var4; + } + + public V put(@Nonnull Class key, V value) { + long lock = MapKeyMapCodec.DATA_LOCK.readLock(); + + Object var5; + try { + if (!key.isInstance(value)) { + throw new IllegalArgumentException("Passed value '" + value + "' isn't of type: " + key); + } + + var5 = this.map.put(key, value); + } finally { + MapKeyMapCodec.DATA_LOCK.unlockRead(lock); + } + + return (V)var5; + } + + @Override + public V remove(Object key) { + return this.map.remove(key); + } + + @Override + public void putAll(@Nonnull Map, ? extends V> m) { + for (Entry, ? extends V> e : m.entrySet()) { + this.put((Class)e.getKey(), (V)e.getValue()); + } + } + + @Override + public void clear() { + this.map.clear(); + } + + @Nonnull + @Override + public Set> keySet() { + return this.map.keySet(); + } + + @Nonnull + @Override + public Collection values() { + return this.map.values(); + } + + @Nonnull + @Override + public Set, V>> entrySet() { + return this.map.entrySet(); + } + + public T computeIfAbsent(Class key, @Nonnull Function, T> mappingFunction) { + long lock = MapKeyMapCodec.DATA_LOCK.readLock(); + + Object var5; + try { + var5 = this.map.computeIfAbsent(key, mappingFunction); + } finally { + MapKeyMapCodec.DATA_LOCK.unlockRead(lock); + } + + return (T)var5; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else { + return !(o instanceof Map) ? false : this.entrySet().equals(((Map)o).entrySet()); + } + } + + @Override + public int hashCode() { + return this.map.hashCode(); + } + + @Nonnull + @Override + public String toString() { + return "TypeMap{map=" + this.map + "}"; + } + + public static MapKeyMapCodec.TypeMap empty() { + return EMPTY; + } + } +} diff --git a/src/com/hypixel/hytale/codec/lookup/MapProvidedMapCodec.java b/src/com/hypixel/hytale/codec/lookup/MapProvidedMapCodec.java new file mode 100644 index 0000000..3f184c1 --- /dev/null +++ b/src/com/hypixel/hytale/codec/lookup/MapProvidedMapCodec.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.codec.lookup; + +import com.hypixel.hytale.codec.Codec; +import java.util.Collections; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class MapProvidedMapCodec extends AMapProvidedMapCodec> { + private final Supplier> supplier; + + public MapProvidedMapCodec(Map codecProvider, Function> mapper, Supplier> supplier) { + this(codecProvider, mapper, supplier, true); + } + + public MapProvidedMapCodec(Map codecProvider, Function> mapper, Supplier> supplier, boolean unmodifiable) { + super(codecProvider, mapper, unmodifiable); + this.supplier = supplier; + } + + @Override + public Map createMap() { + return this.supplier.get(); + } + + protected String getIdForKey(String key) { + return key; + } + + protected String getKeyForId(String id) { + return id; + } + + @Nonnull + @Override + protected Map emptyMap() { + return Collections.emptyMap(); + } + + @Nonnull + @Override + protected Map unmodifiableMap(@Nonnull Map m) { + return Collections.unmodifiableMap(m); + } +} diff --git a/src/com/hypixel/hytale/codec/lookup/ObjectCodecMapCodec.java b/src/com/hypixel/hytale/codec/lookup/ObjectCodecMapCodec.java new file mode 100644 index 0000000..a6ba3f6 --- /dev/null +++ b/src/com/hypixel/hytale/codec/lookup/ObjectCodecMapCodec.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.codec.lookup; + +import com.hypixel.hytale.codec.Codec; +import javax.annotation.Nonnull; + +public class ObjectCodecMapCodec extends ACodecMapCodec> { + public ObjectCodecMapCodec(Codec keyCodec) { + super(keyCodec); + } + + public ObjectCodecMapCodec(Codec keyCodec, boolean allowDefault) { + super(keyCodec, allowDefault); + } + + public ObjectCodecMapCodec(String id, Codec keyCodec) { + super(id, keyCodec); + } + + public ObjectCodecMapCodec(String key, Codec keyCodec, boolean allowDefault) { + super(key, keyCodec, allowDefault); + } + + public ObjectCodecMapCodec(String key, Codec keyCodec, boolean allowDefault, boolean encodeDefaultKey) { + super(key, keyCodec, allowDefault, encodeDefaultKey); + } + + @Nonnull + public ObjectCodecMapCodec register(K id, Class aClass, Codec codec) { + super.register(id, aClass, codec); + return this; + } + + @Nonnull + public ObjectCodecMapCodec register(@Nonnull Priority priority, K id, Class aClass, Codec codec) { + super.register(priority, id, aClass, codec); + return this; + } +} diff --git a/src/com/hypixel/hytale/codec/lookup/Priority.java b/src/com/hypixel/hytale/codec/lookup/Priority.java new file mode 100644 index 0000000..e6adc2e --- /dev/null +++ b/src/com/hypixel/hytale/codec/lookup/Priority.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.codec.lookup; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Priority { + @Nonnull + public static Priority DEFAULT = new Priority(-1000); + @Nonnull + public static Priority NORMAL = new Priority(0); + private int level; + + public Priority(int level) { + this.level = level; + } + + public int getLevel() { + return this.level; + } + + @Nonnull + public Priority before() { + return this.before(1); + } + + @Nonnull + public Priority before(int by) { + return new Priority(this.level - by); + } + + @Nonnull + public Priority after() { + return this.after(1); + } + + @Nonnull + public Priority after(int by) { + return new Priority(this.level - by); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Priority priority = (Priority)o; + return this.level == priority.level; + } else { + return false; + } + } + + @Override + public int hashCode() { + return this.level; + } +} diff --git a/src/com/hypixel/hytale/codec/lookup/StringCodecMapCodec.java b/src/com/hypixel/hytale/codec/lookup/StringCodecMapCodec.java new file mode 100644 index 0000000..02ddbde --- /dev/null +++ b/src/com/hypixel/hytale/codec/lookup/StringCodecMapCodec.java @@ -0,0 +1,108 @@ +package com.hypixel.hytale.codec.lookup; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.builder.StringTreeMap; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import java.util.concurrent.locks.StampedLock; +import javax.annotation.Nonnull; + +public abstract class StringCodecMapCodec> extends ACodecMapCodec { + protected final StampedLock stampedLock = new StampedLock(); + protected final StringTreeMap stringTreeMap = new StringTreeMap<>(); + + public StringCodecMapCodec() { + super(Codec.STRING); + } + + public StringCodecMapCodec(boolean allowDefault) { + super(Codec.STRING, allowDefault); + } + + public StringCodecMapCodec(String id) { + super(id, Codec.STRING); + } + + public StringCodecMapCodec(String key, boolean allowDefault) { + super(key, Codec.STRING, allowDefault); + } + + public StringCodecMapCodec(String key, boolean allowDefault, boolean encodeDefaultKey) { + super(key, Codec.STRING, allowDefault, encodeDefaultKey); + } + + public StringCodecMapCodec register(@Nonnull Priority priority, @Nonnull String id, Class aClass, C codec) { + long lock = this.stampedLock.readLock(); + + try { + this.stringTreeMap.put(id, codec); + } finally { + this.stampedLock.unlockRead(lock); + } + + return (StringCodecMapCodec)super.register(priority, id, aClass, codec); + } + + @Override + public void remove(Class aClass) { + String id = this.classToId.get(aClass); + if (id != null) { + long lock = this.stampedLock.readLock(); + + try { + this.stringTreeMap.remove(id); + } finally { + this.stampedLock.unlockRead(lock); + } + + super.remove(aClass); + } + } + + @Override + public T decodeJson(@Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.mark(); + C codec = null; + int distance = 0; + if (RawJsonReader.seekToKey(reader, this.key)) { + distance = reader.getMarkDistance(); + long lock = this.stampedLock.readLock(); + + try { + StringTreeMap entry = this.stringTreeMap.findEntry(reader); + codec = entry == null ? null : entry.getValue(); + } finally { + this.stampedLock.unlockRead(lock); + } + } + + extraInfo.ignoreUnusedKey(this.key); + + String id; + try { + if (codec != null) { + reader.reset(); + return (T)codec.decodeJson(reader, extraInfo); + } + + C defaultCodec = this.getDefaultCodec(); + if (defaultCodec == null) { + if (distance == 0) { + throw new ACodecMapCodec.UnknownIdException("No codec registered with for '" + this.key + "': null"); + } + + reader.skip(distance - reader.getMarkDistance()); + id = reader.readString(); + throw new ACodecMapCodec.UnknownIdException("No codec registered with for '" + this.key + "': " + id); + } + + reader.reset(); + id = defaultCodec.decodeJson(reader, extraInfo); + } finally { + extraInfo.popIgnoredUnusedKey(); + } + + return (T)id; + } +} diff --git a/src/com/hypixel/hytale/codec/schema/NamedSchema.java b/src/com/hypixel/hytale/codec/schema/NamedSchema.java new file mode 100644 index 0000000..df1e2e0 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/NamedSchema.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.codec.schema; + +import javax.annotation.Nonnull; + +public interface NamedSchema { + @Nonnull + String getSchemaName(); +} diff --git a/src/com/hypixel/hytale/codec/schema/SchemaContext.java b/src/com/hypixel/hytale/codec/schema/SchemaContext.java new file mode 100644 index 0000000..5132e25 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/SchemaContext.java @@ -0,0 +1,119 @@ +package com.hypixel.hytale.codec.schema; + +import com.hypixel.hytale.codec.EmptyExtraInfo; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.schema.config.NullSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SchemaContext { + @Nonnull + private final Map definitions = new Object2ObjectLinkedOpenHashMap<>(); + @Nonnull + private final Map otherDefinitions = new Object2ObjectLinkedOpenHashMap<>(); + @Nonnull + private final Map nameMap = new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Object2IntMap nameCollisionCount = new Object2IntOpenHashMap<>(); + @Nonnull + private final Map, String> fileReferences = new Object2ObjectOpenHashMap<>(); + + public SchemaContext() { + } + + public void addFileReference(@Nonnull String fileName, @Nonnull SchemaConvertable codec) { + this.fileReferences.put(codec, fileName + "#"); + } + + @Nullable + public Schema getFileReference(@Nonnull SchemaConvertable codec) { + String file = this.fileReferences.get(codec); + return file != null ? Schema.ref(file) : null; + } + + @Nonnull + public Schema refDefinition(@Nonnull SchemaConvertable codec) { + return this.refDefinition(codec, null); + } + + @Nonnull + public Schema refDefinition(@Nonnull SchemaConvertable convertable, @Nullable T def) { + Schema ref = this.getFileReference(convertable); + if (ref != null) { + return ref; + } else if (convertable instanceof BuilderCodec builderCodec) { + String name = this.resolveName(builderCodec); + if (!this.definitions.containsKey(name)) { + this.definitions.put(name, NullSchema.INSTANCE); + this.definitions.put(name, convertable.toSchema(this)); + } + + Schema c = Schema.ref("common.json#/definitions/" + name); + if (def != null) { + c.setDefaultRaw(builderCodec.encode(def, EmptyExtraInfo.EMPTY)); + } + + return c; + } else if (convertable instanceof NamedSchema namedSchema) { + String namex = this.resolveName(namedSchema); + if (!this.otherDefinitions.containsKey(namex)) { + this.otherDefinitions.put(namex, NullSchema.INSTANCE); + this.otherDefinitions.put(namex, convertable.toSchema(this)); + } + + return Schema.ref("other.json#/definitions/" + namex); + } else { + return convertable.toSchema(this, def); + } + } + + @Nullable + public Schema getRawDefinition(@Nonnull BuilderCodec codec) { + String name = this.resolveName(codec); + return this.definitions.get(name); + } + + @Nullable + public Schema getRawDefinition(@Nonnull NamedSchema namedSchema) { + return this.otherDefinitions.get(this.resolveName(namedSchema)); + } + + @Nonnull + public Map getDefinitions() { + return this.definitions; + } + + @Nonnull + public Map getOtherDefinitions() { + return this.otherDefinitions; + } + + private String resolveName(@Nonnull NamedSchema namedSchema) { + return this.nameMap.computeIfAbsent(namedSchema, key -> { + String n = ((NamedSchema)key).getSchemaName(); + int count = this.nameCollisionCount.getInt(n); + this.nameCollisionCount.put(n, count + 1); + return count > 0 ? n + "@" + count : n; + }); + } + + @Nonnull + private String resolveName(@Nonnull BuilderCodec codec) { + return this.nameMap.computeIfAbsent(codec.getInnerClass(), key -> { + String n = ((Class)key).getSimpleName(); + int count = this.nameCollisionCount.getInt(n); + this.nameCollisionCount.put(n, count + 1); + return count > 0 ? n + "@" + count : n; + }); + } + + static { + Schema.init(); + } +} diff --git a/src/com/hypixel/hytale/codec/schema/SchemaConvertable.java b/src/com/hypixel/hytale/codec/schema/SchemaConvertable.java new file mode 100644 index 0000000..d85c1e9 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/SchemaConvertable.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.codec.schema; + +import com.hypixel.hytale.codec.schema.config.Schema; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface SchemaConvertable { + @Nonnull + Schema toSchema(@Nonnull SchemaContext var1); + + @Nonnull + default Schema toSchema(@Nonnull SchemaContext context, @Nullable T def) { + return this.toSchema(context); + } +} diff --git a/src/com/hypixel/hytale/codec/schema/config/ArraySchema.java b/src/com/hypixel/hytale/codec/schema/config/ArraySchema.java new file mode 100644 index 0000000..9dd5c32 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/config/ArraySchema.java @@ -0,0 +1,130 @@ +package com.hypixel.hytale.codec.schema.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonValue; + +public class ArraySchema extends Schema { + public static final BuilderCodec CODEC = BuilderCodec.builder(ArraySchema.class, ArraySchema::new, Schema.BASE_CODEC) + .addField(new KeyedCodec<>("items", new ArraySchema.ItemOrItems(), false, true), (o, i) -> o.items = i, o -> o.items) + .addField(new KeyedCodec<>("minItems", Codec.INTEGER, false, true), (o, i) -> o.minItems = i, o -> o.minItems) + .addField(new KeyedCodec<>("maxItems", Codec.INTEGER, false, true), (o, i) -> o.maxItems = i, o -> o.maxItems) + .addField(new KeyedCodec<>("uniqueItems", Codec.BOOLEAN, false, true), (o, i) -> o.uniqueItems = i, o -> o.uniqueItems) + .build(); + private Object items; + private Integer minItems; + private Integer maxItems; + private Boolean uniqueItems; + + public ArraySchema() { + } + + public ArraySchema(Schema item) { + this.setItem(item); + } + + @Nullable + public Object getItems() { + return this.items; + } + + public void setItem(Schema items) { + this.items = items; + } + + public void setItems(Schema... items) { + this.items = items; + } + + @Nullable + public Integer getMinItems() { + return this.minItems; + } + + public void setMinItems(Integer minItems) { + this.minItems = minItems; + } + + @Nullable + public Integer getMaxItems() { + return this.maxItems; + } + + public void setMaxItems(Integer maxItems) { + this.maxItems = maxItems; + } + + public boolean getUniqueItems() { + return this.uniqueItems; + } + + public void setUniqueItems(boolean uniqueItems) { + this.uniqueItems = uniqueItems; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o == null || this.getClass() != o.getClass()) { + return false; + } else if (!super.equals(o)) { + return false; + } else { + ArraySchema that = (ArraySchema)o; + if (this.items != null ? this.items.equals(that.items) : that.items == null) { + if (this.minItems != null ? this.minItems.equals(that.minItems) : that.minItems == null) { + if (this.maxItems != null ? this.maxItems.equals(that.maxItems) : that.maxItems == null) { + return this.uniqueItems != null ? this.uniqueItems.equals(that.uniqueItems) : that.uniqueItems == null; + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (this.items != null ? this.items.hashCode() : 0); + result = 31 * result + (this.minItems != null ? this.minItems.hashCode() : 0); + result = 31 * result + (this.maxItems != null ? this.maxItems.hashCode() : 0); + return 31 * result + (this.uniqueItems != null ? this.uniqueItems.hashCode() : 0); + } + + @Deprecated + private static class ItemOrItems implements Codec { + @Nonnull + private ArrayCodec array = new ArrayCodec<>(Schema.CODEC, Schema[]::new); + + private ItemOrItems() { + } + + @Override + public Object decode(@Nonnull BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { + return bsonValue.isArray() ? this.array.decode(bsonValue, extraInfo) : Schema.CODEC.decode(bsonValue, extraInfo); + } + + @Override + public BsonValue encode(Object o, ExtraInfo extraInfo) { + return o instanceof Schema[] ? this.array.encode((Schema[])o, extraInfo) : Schema.CODEC.encode((Schema)o, extraInfo); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return Schema.anyOf(Schema.CODEC.toSchema(context), this.array.toSchema(context)); + } + } +} diff --git a/src/com/hypixel/hytale/codec/schema/config/BooleanSchema.java b/src/com/hypixel/hytale/codec/schema/config/BooleanSchema.java new file mode 100644 index 0000000..1fc7a98 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/config/BooleanSchema.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.codec.schema.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nullable; + +public class BooleanSchema extends Schema { + public static final BuilderCodec CODEC = BuilderCodec.builder(BooleanSchema.class, BooleanSchema::new, Schema.BASE_CODEC) + .addField(new KeyedCodec<>("default", Codec.BOOLEAN, false, true), (o, i) -> o.default_ = i, o -> o.default_) + .build(); + private Boolean default_; + + public BooleanSchema() { + } + + public Boolean getDefault() { + return this.default_; + } + + public void setDefault(Boolean default_) { + this.default_ = default_; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o == null || this.getClass() != o.getClass()) { + return false; + } else if (!super.equals(o)) { + return false; + } else { + BooleanSchema that = (BooleanSchema)o; + return this.default_ != null ? this.default_.equals(that.default_) : that.default_ == null; + } + } + + @Override + public int hashCode() { + int result = super.hashCode(); + return 31 * result + (this.default_ != null ? this.default_.hashCode() : 0); + } +} diff --git a/src/com/hypixel/hytale/codec/schema/config/IntegerSchema.java b/src/com/hypixel/hytale/codec/schema/config/IntegerSchema.java new file mode 100644 index 0000000..f5acced --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/config/IntegerSchema.java @@ -0,0 +1,193 @@ +package com.hypixel.hytale.codec.schema.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonValue; + +public class IntegerSchema extends Schema { + public static final BuilderCodec CODEC = BuilderCodec.builder(IntegerSchema.class, IntegerSchema::new, Schema.BASE_CODEC) + .addField(new KeyedCodec<>("minimum", IntegerSchema.IntegerOrSchema.INSTANCE, false, true), (o, i) -> o.minimum = i, o -> o.minimum) + .addField( + new KeyedCodec<>("exclusiveMinimum", IntegerSchema.IntegerOrSchema.INSTANCE, false, true), (o, i) -> o.exclusiveMinimum = i, o -> o.exclusiveMinimum + ) + .addField(new KeyedCodec<>("maximum", IntegerSchema.IntegerOrSchema.INSTANCE, false, true), (o, i) -> o.maximum = i, o -> o.maximum) + .addField( + new KeyedCodec<>("exclusiveMaximum", IntegerSchema.IntegerOrSchema.INSTANCE, false, true), (o, i) -> o.exclusiveMaximum = i, o -> o.exclusiveMaximum + ) + .addField(new KeyedCodec<>("enum", Codec.INT_ARRAY, false, true), (o, i) -> o.enum_ = i, o -> o.enum_) + .addField(new KeyedCodec<>("const", Codec.INTEGER, false, true), (o, i) -> o.const_ = i, o -> o.const_) + .addField(new KeyedCodec<>("default", Codec.INTEGER, false, true), (o, i) -> o.default_ = i, o -> o.default_) + .build(); + private Object minimum; + private Object exclusiveMinimum; + private Object maximum; + private Object exclusiveMaximum; + private int[] enum_; + private Integer const_; + private Integer default_; + + public IntegerSchema() { + } + + @Nullable + public Object getMinimum() { + return this.minimum; + } + + public void setMinimum(int minimum) { + this.minimum = minimum; + } + + @Nullable + public Object getExclusiveMinimum() { + return this.exclusiveMinimum; + } + + public void setExclusiveMinimum(int exclusiveMinimum) { + this.exclusiveMinimum = exclusiveMinimum; + } + + @Nullable + public Object getMaximum() { + return this.maximum; + } + + public void setMaximum(int maximum) { + this.maximum = maximum; + } + + @Nullable + public Object getExclusiveMaximum() { + return this.exclusiveMaximum; + } + + public void setExclusiveMaximum(int exclusiveMaximum) { + this.exclusiveMaximum = exclusiveMaximum; + } + + public void setMinimum(Schema minimum) { + this.minimum = minimum; + } + + public void setExclusiveMinimum(Schema exclusiveMinimum) { + this.exclusiveMinimum = exclusiveMinimum; + } + + public void setMaximum(Schema maximum) { + this.maximum = maximum; + } + + public void setExclusiveMaximum(Schema exclusiveMaximum) { + this.exclusiveMaximum = exclusiveMaximum; + } + + public int[] getEnum() { + return this.enum_; + } + + public void setEnum(int[] enum_) { + this.enum_ = enum_; + } + + @Nullable + public Integer getConst() { + return this.const_; + } + + public void setConst(Integer const_) { + this.const_ = const_; + } + + public Integer getDefault() { + return this.default_; + } + + public void setDefault(Integer default_) { + this.default_ = default_; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o == null || this.getClass() != o.getClass()) { + return false; + } else if (!super.equals(o)) { + return false; + } else { + IntegerSchema that = (IntegerSchema)o; + if (this.minimum != null ? this.minimum.equals(that.minimum) : that.minimum == null) { + if (this.exclusiveMinimum != null ? this.exclusiveMinimum.equals(that.exclusiveMinimum) : that.exclusiveMinimum == null) { + if (this.maximum != null ? this.maximum.equals(that.maximum) : that.maximum == null) { + if (this.exclusiveMaximum != null ? this.exclusiveMaximum.equals(that.exclusiveMaximum) : that.exclusiveMaximum == null) { + if (!Arrays.equals(this.enum_, that.enum_)) { + return false; + } else if (this.const_ != null ? this.const_.equals(that.const_) : that.const_ == null) { + return this.default_ != null ? this.default_.equals(that.default_) : that.default_ == null; + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (this.minimum != null ? this.minimum.hashCode() : 0); + result = 31 * result + (this.exclusiveMinimum != null ? this.exclusiveMinimum.hashCode() : 0); + result = 31 * result + (this.maximum != null ? this.maximum.hashCode() : 0); + result = 31 * result + (this.exclusiveMaximum != null ? this.exclusiveMaximum.hashCode() : 0); + result = 31 * result + Arrays.hashCode(this.enum_); + result = 31 * result + (this.const_ != null ? this.const_.hashCode() : 0); + return 31 * result + (this.default_ != null ? this.default_.hashCode() : 0); + } + + @Nonnull + public static Schema constant(int c) { + IntegerSchema s = new IntegerSchema(); + s.setConst(c); + return s; + } + + @Deprecated + private static class IntegerOrSchema implements Codec { + private static final IntegerSchema.IntegerOrSchema INSTANCE = new IntegerSchema.IntegerOrSchema(); + + private IntegerOrSchema() { + } + + @Override + public Object decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + return bsonValue.isNumber() ? Codec.INTEGER.decode(bsonValue, extraInfo) : Schema.CODEC.decode(bsonValue, extraInfo); + } + + @Override + public BsonValue encode(Object o, ExtraInfo extraInfo) { + return o instanceof Integer ? Codec.INTEGER.encode((Integer)o, extraInfo) : Schema.CODEC.encode((Schema)o, extraInfo); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return Schema.anyOf(new IntegerSchema(), Schema.CODEC.toSchema(context)); + } + } +} diff --git a/src/com/hypixel/hytale/codec/schema/config/NullSchema.java b/src/com/hypixel/hytale/codec/schema/config/NullSchema.java new file mode 100644 index 0000000..da35525 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/config/NullSchema.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.codec.schema.config; + +import com.hypixel.hytale.codec.builder.BuilderCodec; + +public class NullSchema extends Schema { + public static final BuilderCodec CODEC = BuilderCodec.builder(NullSchema.class, NullSchema::new, Schema.BASE_CODEC).build(); + public static final NullSchema INSTANCE = new NullSchema(); + + public NullSchema() { + } +} diff --git a/src/com/hypixel/hytale/codec/schema/config/NumberSchema.java b/src/com/hypixel/hytale/codec/schema/config/NumberSchema.java new file mode 100644 index 0000000..c65cc73 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/config/NumberSchema.java @@ -0,0 +1,193 @@ +package com.hypixel.hytale.codec.schema.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonValue; + +public class NumberSchema extends Schema { + public static final BuilderCodec CODEC = BuilderCodec.builder(NumberSchema.class, NumberSchema::new, Schema.BASE_CODEC) + .addField(new KeyedCodec<>("minimum", NumberSchema.DoubleOrSchema.INSTANCE, false, true), (o, i) -> o.minimum = i, o -> o.minimum) + .addField( + new KeyedCodec<>("exclusiveMinimum", NumberSchema.DoubleOrSchema.INSTANCE, false, true), (o, i) -> o.exclusiveMinimum = i, o -> o.exclusiveMinimum + ) + .addField(new KeyedCodec<>("maximum", NumberSchema.DoubleOrSchema.INSTANCE, false, true), (o, i) -> o.maximum = i, o -> o.maximum) + .addField( + new KeyedCodec<>("exclusiveMaximum", NumberSchema.DoubleOrSchema.INSTANCE, false, true), (o, i) -> o.exclusiveMaximum = i, o -> o.exclusiveMaximum + ) + .addField(new KeyedCodec<>("enum", Codec.DOUBLE_ARRAY, false, true), (o, i) -> o.enum_ = i, o -> o.enum_) + .addField(new KeyedCodec<>("const", Codec.DOUBLE, false, true), (o, i) -> o.const_ = i, o -> o.const_) + .addField(new KeyedCodec<>("default", Codec.DOUBLE, false, true), (o, i) -> o.default_ = i, o -> o.default_) + .build(); + private Object minimum; + private Object exclusiveMinimum; + private Object maximum; + private Object exclusiveMaximum; + private double[] enum_; + private Double const_; + private Double default_; + + public NumberSchema() { + } + + @Nullable + public Object getMinimum() { + return this.minimum; + } + + public void setMinimum(double minimum) { + this.minimum = minimum; + } + + @Nullable + public Object getExclusiveMinimum() { + return this.exclusiveMinimum; + } + + public void setExclusiveMinimum(double exclusiveMinimum) { + this.exclusiveMinimum = exclusiveMinimum; + } + + @Nullable + public Object getMaximum() { + return this.maximum; + } + + public void setMaximum(double maximum) { + this.maximum = maximum; + } + + @Nullable + public Object getExclusiveMaximum() { + return this.exclusiveMaximum; + } + + public void setExclusiveMaximum(double exclusiveMaximum) { + this.exclusiveMaximum = exclusiveMaximum; + } + + public void setMinimum(Schema minimum) { + this.minimum = minimum; + } + + public void setExclusiveMinimum(Schema exclusiveMinimum) { + this.exclusiveMinimum = exclusiveMinimum; + } + + public void setMaximum(Schema maximum) { + this.maximum = maximum; + } + + public void setExclusiveMaximum(Schema exclusiveMaximum) { + this.exclusiveMaximum = exclusiveMaximum; + } + + public double[] getEnum() { + return this.enum_; + } + + public void setEnum(double[] enum_) { + this.enum_ = enum_; + } + + @Nullable + public Double getConst() { + return this.const_; + } + + public void setConst(Double const_) { + this.const_ = const_; + } + + public Double getDefault() { + return this.default_; + } + + public void setDefault(Double default_) { + this.default_ = default_; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o == null || this.getClass() != o.getClass()) { + return false; + } else if (!super.equals(o)) { + return false; + } else { + NumberSchema that = (NumberSchema)o; + if (this.minimum != null ? this.minimum.equals(that.minimum) : that.minimum == null) { + if (this.exclusiveMinimum != null ? this.exclusiveMinimum.equals(that.exclusiveMinimum) : that.exclusiveMinimum == null) { + if (this.maximum != null ? this.maximum.equals(that.maximum) : that.maximum == null) { + if (this.exclusiveMaximum != null ? this.exclusiveMaximum.equals(that.exclusiveMaximum) : that.exclusiveMaximum == null) { + if (!Arrays.equals(this.enum_, that.enum_)) { + return false; + } else if (this.const_ != null ? this.const_.equals(that.const_) : that.const_ == null) { + return this.default_ != null ? this.default_.equals(that.default_) : that.default_ == null; + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (this.minimum != null ? this.minimum.hashCode() : 0); + result = 31 * result + (this.exclusiveMinimum != null ? this.exclusiveMinimum.hashCode() : 0); + result = 31 * result + (this.maximum != null ? this.maximum.hashCode() : 0); + result = 31 * result + (this.exclusiveMaximum != null ? this.exclusiveMaximum.hashCode() : 0); + result = 31 * result + Arrays.hashCode(this.enum_); + result = 31 * result + (this.const_ != null ? this.const_.hashCode() : 0); + return 31 * result + (this.default_ != null ? this.default_.hashCode() : 0); + } + + @Nonnull + public static Schema constant(double c) { + NumberSchema s = new NumberSchema(); + s.setConst(c); + return s; + } + + @Deprecated + private static class DoubleOrSchema implements Codec { + private static final NumberSchema.DoubleOrSchema INSTANCE = new NumberSchema.DoubleOrSchema(); + + private DoubleOrSchema() { + } + + @Override + public Object decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + return bsonValue.isNumber() ? Codec.DOUBLE.decode(bsonValue, extraInfo) : Schema.CODEC.decode(bsonValue, extraInfo); + } + + @Override + public BsonValue encode(Object o, ExtraInfo extraInfo) { + return o instanceof Double ? Codec.DOUBLE.encode((Double)o, extraInfo) : Schema.CODEC.encode((Schema)o, extraInfo); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return Schema.anyOf(new NumberSchema(), Schema.CODEC.toSchema(context)); + } + } +} diff --git a/src/com/hypixel/hytale/codec/schema/config/ObjectSchema.java b/src/com/hypixel/hytale/codec/schema/config/ObjectSchema.java new file mode 100644 index 0000000..111f661 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/config/ObjectSchema.java @@ -0,0 +1,99 @@ +package com.hypixel.hytale.codec.schema.config; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ObjectSchema extends Schema { + public static final BuilderCodec CODEC = BuilderCodec.builder(ObjectSchema.class, ObjectSchema::new, Schema.BASE_CODEC) + .addField(new KeyedCodec<>("properties", new MapCodec<>(Schema.CODEC, LinkedHashMap::new), false, true), (o, i) -> o.properties = i, o -> o.properties) + .addField( + new KeyedCodec<>("additionalProperties", new Schema.BooleanOrSchema(), false, true), (o, i) -> o.additionalProperties = i, o -> o.additionalProperties + ) + .addField(new KeyedCodec<>("propertyNames", StringSchema.CODEC, false, true), (o, i) -> o.propertyNames = i, o -> o.propertyNames) + .build(); + private Map properties; + @Nullable + private Object additionalProperties; + private StringSchema propertyNames; + private Schema unevaluatedProperties; + + public ObjectSchema() { + } + + public Map getProperties() { + return this.properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + @Nullable + public Object getAdditionalProperties() { + return this.additionalProperties; + } + + public void setAdditionalProperties(boolean additionalProperties) { + this.additionalProperties = additionalProperties; + } + + public void setAdditionalProperties(Schema additionalProperties) { + this.additionalProperties = additionalProperties; + } + + public StringSchema getPropertyNames() { + return this.propertyNames; + } + + public void setPropertyNames(StringSchema propertyNames) { + this.propertyNames = propertyNames; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o == null || this.getClass() != o.getClass()) { + return false; + } else if (!super.equals(o)) { + return false; + } else { + ObjectSchema that = (ObjectSchema)o; + if (this.properties != null ? this.properties.equals(that.properties) : that.properties == null) { + if (this.additionalProperties != null ? this.additionalProperties.equals(that.additionalProperties) : that.additionalProperties == null) { + if (this.propertyNames != null ? this.propertyNames.equals(that.propertyNames) : that.propertyNames == null) { + return this.unevaluatedProperties != null + ? this.unevaluatedProperties.equals(that.unevaluatedProperties) + : that.unevaluatedProperties == null; + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (this.properties != null ? this.properties.hashCode() : 0); + result = 31 * result + (this.additionalProperties != null ? this.additionalProperties.hashCode() : 0); + result = 31 * result + (this.propertyNames != null ? this.propertyNames.hashCode() : 0); + return 31 * result + (this.unevaluatedProperties != null ? this.unevaluatedProperties.hashCode() : 0); + } + + @Nonnull + @Override + public String toString() { + return "ObjectSchema{properties=" + this.properties + ", additionalProperties=" + this.additionalProperties + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/codec/schema/config/Schema.java b/src/com/hypixel/hytale/codec/schema/config/Schema.java new file mode 100644 index 0000000..b480533 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/config/Schema.java @@ -0,0 +1,1069 @@ +package com.hypixel.hytale.codec.schema.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.lookup.ObjectCodecMapCodec; +import com.hypixel.hytale.codec.lookup.Priority; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.metadata.ui.UIButton; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDisplayMode; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorFeatures; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorPreview; +import com.hypixel.hytale.codec.schema.metadata.ui.UIRebuildCaches; +import com.hypixel.hytale.codec.util.Documentation; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonArray; +import org.bson.BsonDocument; +import org.bson.BsonNull; +import org.bson.BsonValue; + +public class Schema { + public static final ObjectCodecMapCodec CODEC = new ObjectCodecMapCodec<>("type", new Schema.StringOrBlank(), true, false); + public static final ArrayCodec ARRAY_CODEC = new ArrayCodec<>(CODEC, Schema[]::new); + public static final BuilderCodec BASE_CODEC = BuilderCodec.builder(Schema.class, Schema::new) + .addField(new KeyedCodec<>("$id", Codec.STRING, false, true), (o, i) -> o.id = i, o -> o.id) + .addField(new KeyedCodec<>("type", new Schema.ArrayOrNull(), false, true), (o, i) -> o.types = i, o -> o.types) + .addField(new KeyedCodec<>("title", Codec.STRING, false, true), (o, i) -> o.title = i, o -> o.title) + .addField(new KeyedCodec<>("description", Codec.STRING, false, true), (o, i) -> o.description = i, o -> o.description) + .addField(new KeyedCodec<>("markdownDescription", Codec.STRING, false, true), (o, i) -> o.markdownDescription = i, o -> o.markdownDescription) + .addField(new KeyedCodec<>("enumDescriptions", Codec.STRING_ARRAY, false, true), (o, i) -> o.enumDescriptions = i, o -> { + if (o.enumDescriptions == null && o.markdownEnumDescriptions != null) { + String[] enumDescriptions = new String[o.markdownEnumDescriptions.length]; + + for (int i = 0; i < enumDescriptions.length; i++) { + enumDescriptions[i] = Documentation.stripMarkdown(o.markdownEnumDescriptions[i]); + } + + return enumDescriptions; + } else { + return o.enumDescriptions; + } + }) + .addField( + new KeyedCodec<>("markdownEnumDescriptions", Codec.STRING_ARRAY, false, true), + (o, i) -> o.markdownEnumDescriptions = i, + o -> o.markdownEnumDescriptions + ) + .addField(new KeyedCodec<>("anyOf", ARRAY_CODEC, false, true), (o, i) -> o.anyOf = i, o -> o.anyOf) + .addField(new KeyedCodec<>("oneOf", ARRAY_CODEC, false, true), (o, i) -> o.oneOf = i, o -> o.oneOf) + .addField(new KeyedCodec<>("allOf", ARRAY_CODEC, false, true), (o, i) -> o.allOf = i, o -> o.allOf) + .addField(new KeyedCodec<>("not", CODEC, false, true), (o, i) -> o.not = i, o -> o.not) + .addField(new KeyedCodec<>("if", CODEC, false, true), (o, i) -> o.if_ = i, o -> o.if_) + .addField(new KeyedCodec<>("then", CODEC, false, true), (o, i) -> o.then = i, o -> o.then) + .addField(new KeyedCodec<>("else", new Schema.BooleanOrSchema(), false, true), (o, i) -> o.else_ = i, o -> o.else_) + .addField(new KeyedCodec<>("required", Codec.STRING_ARRAY, false, true), (o, i) -> o.required = i, o -> o.required) + .addField(new KeyedCodec<>("default", Codec.BSON_DOCUMENT, false, true), (o, i) -> o.default_ = i, o -> o.default_) + .addField(new KeyedCodec<>("definitions", new MapCodec<>(CODEC, HashMap::new), false, true), (o, i) -> o.definitions = i, o -> o.definitions) + .addField(new KeyedCodec<>("$ref", Codec.STRING, false, true), (o, i) -> o.ref = i, o -> o.ref) + .addField(new KeyedCodec<>("$data", Codec.STRING, false, true), (o, i) -> o.data = i, o -> o.data) + .addField(new KeyedCodec<>("doNotSuggest", Codec.BOOLEAN, false, true), (o, i) -> o.doNotSuggest = i, o -> o.doNotSuggest) + .addField(new KeyedCodec<>("hytaleAssetRef", Codec.STRING, false, true), (o, i) -> o.hytaleAssetRef = i, o -> o.hytaleAssetRef) + .addField(new KeyedCodec<>("hytaleCustomAssetRef", Codec.STRING, false, true), (o, i) -> o.hytaleCustomAssetRef = i, o -> o.hytaleCustomAssetRef) + .addField(new KeyedCodec<>("hytaleParent", Schema.InheritSettings.CODEC, false, true), (o, i) -> o.hytaleParent = i, o -> o.hytaleParent) + .addField( + new KeyedCodec<>("hytaleSchemaTypeField", Schema.SchemaTypeField.CODEC, false, true), + (o, i) -> o.hytaleSchemaTypeField = i, + o -> o.hytaleSchemaTypeField + ) + .addField(new KeyedCodec<>("hytale", Schema.HytaleMetadata.CODEC, false, true), (o, i) -> { + if (i.type == null) { + i.type = CODEC.getIdFor((Class)o.getClass()); + } + + o.hytale = i; + }, o -> o.hytale) + .build(); + private String id; + private String[] types; + private String title; + private String description; + private String markdownDescription; + private Schema[] anyOf; + private Schema[] oneOf; + private Schema[] allOf; + private Schema not; + private String[] required; + private String[] enumDescriptions; + private String[] markdownEnumDescriptions; + private Map definitions; + private String ref; + private String data; + private BsonDocument default_; + private Schema if_; + private Schema then; + private Object else_; + private Schema.HytaleMetadata hytale; + private Schema.InheritSettings hytaleParent; + private Schema.SchemaTypeField hytaleSchemaTypeField; + private String hytaleAssetRef; + private String hytaleCustomAssetRef; + private Boolean doNotSuggest; + + public Schema() { + String id = CODEC.getIdFor((Class)this.getClass()); + if (id != null && !id.isBlank()) { + this.hytale = new Schema.HytaleMetadata(id); + } + } + + public String getId() { + return this.id; + } + + public void setId(String id) { + this.id = id; + } + + public String[] getTypes() { + return this.types; + } + + public void setTypes(String[] types) { + this.types = types; + } + + public String getTitle() { + return this.title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getMarkdownDescription() { + return this.markdownDescription; + } + + public void setMarkdownDescription(String markdownDescription) { + this.markdownDescription = markdownDescription; + } + + public String[] getEnumDescriptions() { + return this.enumDescriptions; + } + + public void setEnumDescriptions(String[] enumDescriptions) { + this.enumDescriptions = enumDescriptions; + } + + public String[] getMarkdownEnumDescriptions() { + return this.markdownEnumDescriptions; + } + + public void setMarkdownEnumDescriptions(String[] markdownEnumDescriptions) { + this.markdownEnumDescriptions = markdownEnumDescriptions; + } + + public Schema[] getAnyOf() { + return this.anyOf; + } + + public void setAnyOf(Schema... anyOf) { + this.anyOf = anyOf; + } + + public Schema[] getOneOf() { + return this.oneOf; + } + + public void setOneOf(Schema... oneOf) { + this.oneOf = oneOf; + } + + public Schema[] getAllOf() { + return this.allOf; + } + + public void setAllOf(Schema... allOf) { + this.allOf = allOf; + } + + public String[] getRequired() { + return this.required; + } + + public void setRequired(String... required) { + this.required = required; + } + + public BsonDocument getDefaultRaw() { + return this.default_; + } + + public void setDefaultRaw(BsonDocument default_) { + this.default_ = default_; + } + + public Map getDefinitions() { + return this.definitions; + } + + public void setDefinitions(Map definitions) { + this.definitions = definitions; + } + + public String getRef() { + return this.ref; + } + + public void setRef(String ref) { + this.ref = ref; + } + + public String getData() { + return this.data; + } + + public void setData(String data) { + this.data = data; + } + + public Schema getIf() { + return this.if_; + } + + public void setIf(Schema if_) { + this.if_ = if_; + } + + public Schema getThen() { + return this.then; + } + + public void setThen(Schema then) { + this.then = then; + } + + public Schema getElse() { + return (Schema)this.else_; + } + + public void setElse(Schema else_) { + this.else_ = else_; + } + + public void setElse(boolean else_) { + this.else_ = else_; + } + + public Boolean isDoNotSuggest() { + return this.doNotSuggest; + } + + public void setDoNotSuggest(boolean doNotSuggest) { + this.doNotSuggest = doNotSuggest; + } + + @Nullable + public Schema.HytaleMetadata getHytale() { + return this.getHytale(true); + } + + @Nullable + public Schema.HytaleMetadata getHytale(boolean createInstance) { + if (createInstance && this.hytale == null) { + this.hytale = new Schema.HytaleMetadata(); + this.hytale.type = CODEC.getIdFor((Class)this.getClass()); + } + + return this.hytale; + } + + public String getHytaleAssetRef() { + return this.hytaleAssetRef; + } + + public void setHytaleAssetRef(String hytaleAssetRef) { + this.hytaleAssetRef = hytaleAssetRef; + } + + public Schema.InheritSettings getHytaleParent() { + return this.hytaleParent; + } + + public void setHytaleParent(Schema.InheritSettings hytaleParent) { + this.hytaleParent = hytaleParent; + } + + public Schema.SchemaTypeField getHytaleSchemaTypeField() { + return this.hytaleSchemaTypeField; + } + + public void setHytaleSchemaTypeField(Schema.SchemaTypeField hytaleSchemaTypeField) { + this.hytaleSchemaTypeField = hytaleSchemaTypeField; + } + + public String getHytaleCustomAssetRef() { + return this.hytaleCustomAssetRef; + } + + public void setHytaleCustomAssetRef(String hytaleCustomAssetRef) { + this.hytaleCustomAssetRef = hytaleCustomAssetRef; + } + + @Nonnull + public static Schema ref(String file) { + Schema s = new Schema(); + s.setRef(file); + return s; + } + + @Nonnull + public static Schema data(String file) { + Schema s = new Schema(); + s.setData(file); + return s; + } + + @Nonnull + public static Schema anyOf(Schema... anyOf) { + Schema s = new Schema(); + s.anyOf = anyOf; + return s; + } + + @Nonnull + public static Schema not(Schema not) { + Schema s = new Schema(); + s.not = not; + return s; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Schema schema = (Schema)o; + if (this.id != null ? this.id.equals(schema.id) : schema.id == null) { + if (!Arrays.equals((Object[])this.types, (Object[])schema.types)) { + return false; + } else if (this.title != null ? this.title.equals(schema.title) : schema.title == null) { + if (this.description != null ? this.description.equals(schema.description) : schema.description == null) { + if (this.markdownDescription != null ? this.markdownDescription.equals(schema.markdownDescription) : schema.markdownDescription == null) { + if (!Arrays.equals((Object[])this.anyOf, (Object[])schema.anyOf)) { + return false; + } else if (!Arrays.equals((Object[])this.oneOf, (Object[])schema.oneOf)) { + return false; + } else if (!Arrays.equals((Object[])this.allOf, (Object[])schema.allOf)) { + return false; + } else if (this.not != null ? this.not.equals(schema.not) : schema.not == null) { + if (!Arrays.equals((Object[])this.required, (Object[])schema.required)) { + return false; + } else if (!Arrays.equals((Object[])this.enumDescriptions, (Object[])schema.enumDescriptions)) { + return false; + } else if (!Arrays.equals((Object[])this.markdownEnumDescriptions, (Object[])schema.markdownEnumDescriptions)) { + return false; + } else if (this.definitions != null ? this.definitions.equals(schema.definitions) : schema.definitions == null) { + if (this.ref != null ? this.ref.equals(schema.ref) : schema.ref == null) { + if (this.data != null ? this.data.equals(schema.data) : schema.data == null) { + if (this.default_ != null ? this.default_.equals(schema.default_) : schema.default_ == null) { + if (this.hytale != null ? this.hytale.equals(schema.hytale) : schema.hytale == null) { + if (this.hytaleParent != null ? this.hytaleParent.equals(schema.hytaleParent) : schema.hytaleParent == null) { + if (this.hytaleSchemaTypeField != null + ? this.hytaleSchemaTypeField.equals(schema.hytaleSchemaTypeField) + : schema.hytaleSchemaTypeField == null) { + if (this.hytaleAssetRef != null + ? this.hytaleAssetRef.equals(schema.hytaleAssetRef) + : schema.hytaleAssetRef == null) { + return this.hytaleCustomAssetRef != null + ? this.hytaleCustomAssetRef.equals(schema.hytaleCustomAssetRef) + : schema.hytaleCustomAssetRef == null; + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.id != null ? this.id.hashCode() : 0; + result = 31 * result + Arrays.hashCode((Object[])this.types); + result = 31 * result + (this.title != null ? this.title.hashCode() : 0); + result = 31 * result + (this.description != null ? this.description.hashCode() : 0); + result = 31 * result + (this.markdownDescription != null ? this.markdownDescription.hashCode() : 0); + result = 31 * result + Arrays.hashCode((Object[])this.anyOf); + result = 31 * result + Arrays.hashCode((Object[])this.oneOf); + result = 31 * result + Arrays.hashCode((Object[])this.allOf); + result = 31 * result + (this.not != null ? this.not.hashCode() : 0); + result = 31 * result + Arrays.hashCode((Object[])this.required); + result = 31 * result + Arrays.hashCode((Object[])this.enumDescriptions); + result = 31 * result + Arrays.hashCode((Object[])this.markdownEnumDescriptions); + result = 31 * result + (this.definitions != null ? this.definitions.hashCode() : 0); + result = 31 * result + (this.ref != null ? this.ref.hashCode() : 0); + result = 31 * result + (this.data != null ? this.data.hashCode() : 0); + result = 31 * result + (this.default_ != null ? this.default_.hashCode() : 0); + result = 31 * result + (this.hytale != null ? this.hytale.hashCode() : 0); + result = 31 * result + (this.hytaleParent != null ? this.hytaleParent.hashCode() : 0); + result = 31 * result + (this.hytaleSchemaTypeField != null ? this.hytaleSchemaTypeField.hashCode() : 0); + result = 31 * result + (this.hytaleAssetRef != null ? this.hytaleAssetRef.hashCode() : 0); + return 31 * result + (this.hytaleCustomAssetRef != null ? this.hytaleCustomAssetRef.hashCode() : 0); + } + + public static void init() { + CODEC.register(Priority.DEFAULT, "", Schema.class, BASE_CODEC); + CODEC.register("null", NullSchema.class, NullSchema.CODEC); + CODEC.register("string", StringSchema.class, StringSchema.CODEC); + CODEC.register("number", NumberSchema.class, NumberSchema.CODEC); + CODEC.register("integer", IntegerSchema.class, IntegerSchema.CODEC); + CODEC.register("array", ArraySchema.class, ArraySchema.CODEC); + CODEC.register("boolean", BooleanSchema.class, BooleanSchema.CODEC); + CODEC.register("object", ObjectSchema.class, ObjectSchema.CODEC); + UIEditor.init(); + } + + @Deprecated + private static class ArrayOrNull implements Codec { + private ArrayOrNull() { + } + + @Nullable + public String[] decode(@Nonnull BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { + return bsonValue.isArray() ? Codec.STRING_ARRAY.decode(bsonValue, extraInfo) : null; + } + + @Nonnull + public BsonValue encode(@Nullable String[] o, ExtraInfo extraInfo) { + return (BsonValue)(o != null ? Codec.STRING_ARRAY.encode(o, extraInfo) : new BsonNull()); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return Schema.anyOf(new ArraySchema(), new NullSchema()); + } + } + + @Deprecated + protected static class BooleanOrSchema implements Codec { + protected BooleanOrSchema() { + } + + @Override + public Object decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + return bsonValue.isBoolean() ? Codec.BOOLEAN.decode(bsonValue, extraInfo) : Schema.CODEC.decode(bsonValue, extraInfo); + } + + @Override + public BsonValue encode(Object o, ExtraInfo extraInfo) { + return o instanceof Boolean ? Codec.BOOLEAN.encode((Boolean)o, extraInfo) : Schema.CODEC.encode((Schema)o, extraInfo); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return Schema.anyOf(new BooleanSchema(), Schema.CODEC.toSchema(context)); + } + } + + public static class HytaleMetadata { + public static final BuilderCodec CODEC = BuilderCodec.builder(Schema.HytaleMetadata.class, Schema.HytaleMetadata::new) + .addField(new KeyedCodec<>("type", Codec.STRING, false, true), (o, i) -> o.type = i, o -> o.type != null && o.type.isEmpty() ? null : o.type) + .addField(new KeyedCodec<>("internalKeys", Codec.STRING_ARRAY, false, true), (o, i) -> o.internalKeys = i, o -> o.internalKeys) + .addField(new KeyedCodec<>("path", Codec.STRING, false, true), (o, i) -> o.path = i, o -> o.path) + .addField(new KeyedCodec<>("virtualPath", Codec.STRING, false, true), (o, i) -> o.virtualPath = i, o -> o.virtualPath) + .addField(new KeyedCodec<>("extension", Codec.STRING, false, true), (o, i) -> o.extension = i, o -> o.extension) + .addField(new KeyedCodec<>("idProvider", Codec.STRING, false, true), (o, i) -> o.idProvider = i, o -> o.idProvider) + .addField(new KeyedCodec<>("inheritsProperty", Codec.BOOLEAN, false, true), (o, i) -> o.inheritsProperty = i, o -> o.inheritsProperty) + .addField(new KeyedCodec<>("mergesProperties", Codec.BOOLEAN, false, true), (o, i) -> o.mergesProperties = i, o -> o.mergesProperties) + .addField( + new KeyedCodec<>("uiDisplayMode", new EnumCodec<>(UIDisplayMode.DisplayMode.class), false, true), + (o, i) -> o.uiDisplayMode = i, + o -> o.uiDisplayMode + ) + .addField(new KeyedCodec<>("uiEditorComponent", UIEditor.CODEC, false, true), (o, i) -> o.uiEditorComponent = i, o -> o.uiEditorComponent) + .addField(new KeyedCodec<>("allowEmptyObject", Codec.BOOLEAN, false, true), (o, i) -> o.allowEmptyObject = i, o -> o.allowEmptyObject) + .addField(new KeyedCodec<>("uiEditorIgnore", Codec.BOOLEAN, false, true), (o, i) -> o.uiEditorIgnore = i, o -> o.uiEditorIgnore) + .addField( + new KeyedCodec<>( + "uiEditorFeatures", new ArrayCodec<>(new EnumCodec<>(UIEditorFeatures.EditorFeature.class), UIEditorFeatures.EditorFeature[]::new), false, true + ), + (o, i) -> o.uiEditorFeatures = i, + o -> o.uiEditorFeatures + ) + .addField( + new KeyedCodec<>("uiEditorPreview", new EnumCodec<>(UIEditorPreview.PreviewType.class), false, true), + (o, i) -> o.uiEditorPreview = i, + o -> o.uiEditorPreview + ) + .addField(new KeyedCodec<>("uiTypeIcon", Codec.STRING, false, true), (o, i) -> o.uiTypeIcon = i, o -> o.uiTypeIcon) + .addField(new KeyedCodec<>("uiPropertyTitle", Codec.STRING, false, true), (o, i) -> o.uiPropertyTitle = i, o -> o.uiPropertyTitle) + .addField(new KeyedCodec<>("uiSectionStart", Codec.STRING, false, true), (o, i) -> o.uiSectionStart = i, o -> o.uiSectionStart) + .addField( + new KeyedCodec<>( + "uiRebuildCaches", new ArrayCodec<>(new EnumCodec<>(UIRebuildCaches.ClientCache.class), UIRebuildCaches.ClientCache[]::new), false, true + ), + (o, i) -> o.uiRebuildCaches = i, + o -> o.uiRebuildCaches + ) + .addField( + new KeyedCodec<>("uiSidebarButtons", new ArrayCodec<>(UIButton.CODEC, UIButton[]::new), false, true), + (o, i) -> o.uiSidebarButtons = i, + o -> o.uiSidebarButtons + ) + .addField( + new KeyedCodec<>("uiRebuildCachesForChildProperties", Codec.BOOLEAN, false, true), + (o, i) -> o.uiRebuildCachesForChildProperties = i, + o -> o.uiRebuildCachesForChildProperties + ) + .addField(new KeyedCodec<>("uiCollapsedByDefault", Codec.BOOLEAN, false, true), (o, i) -> o.uiCollapsedByDefault = i, o -> o.uiCollapsedByDefault) + .addField( + new KeyedCodec<>("uiCreateButtons", new ArrayCodec<>(UIButton.CODEC, UIButton[]::new), false, true), + (o, i) -> o.uiCreateButtons = i, + o -> o.uiCreateButtons + ) + .build(); + private String type; + private String path; + private String virtualPath; + private String extension; + private String idProvider; + private String[] internalKeys; + private Boolean inheritsProperty; + private Boolean mergesProperties; + private UIEditorFeatures.EditorFeature[] uiEditorFeatures; + private UIEditorPreview.PreviewType uiEditorPreview; + private String uiTypeIcon; + private Boolean uiEditorIgnore; + private Boolean allowEmptyObject; + private UIDisplayMode.DisplayMode uiDisplayMode; + private UIEditor.EditorComponent uiEditorComponent; + private String uiPropertyTitle; + private String uiSectionStart; + private UIRebuildCaches.ClientCache[] uiRebuildCaches; + private Boolean uiRebuildCachesForChildProperties; + private UIButton[] uiSidebarButtons; + private Boolean uiCollapsedByDefault; + private UIButton[] uiCreateButtons; + + public HytaleMetadata(String type) { + this.type = type; + } + + public HytaleMetadata() { + } + + public String getType() { + return this.type; + } + + public void setType(String type) { + this.type = type; + } + + public String getPath() { + return this.path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getVirtualPath() { + return this.virtualPath; + } + + public void setVirtualPath(String virtualPath) { + this.virtualPath = virtualPath; + } + + public String getExtension() { + return this.extension; + } + + public void setExtension(String extension) { + this.extension = extension; + } + + public String getIdProvider() { + return this.idProvider; + } + + public void setIdProvider(String idProvider) { + this.idProvider = idProvider; + } + + public String[] getInternalKeys() { + return this.internalKeys; + } + + public void setInternalKeys(String[] internalKeys) { + this.internalKeys = internalKeys; + } + + public UIDisplayMode.DisplayMode getUiDisplayMode() { + return this.uiDisplayMode; + } + + public void setUiDisplayMode(UIDisplayMode.DisplayMode uiDisplayMode) { + this.uiDisplayMode = uiDisplayMode; + } + + public UIEditor.EditorComponent getUiEditorComponent() { + return this.uiEditorComponent; + } + + public void setUiEditorComponent(UIEditor.EditorComponent uiEditorComponent) { + this.uiEditorComponent = uiEditorComponent; + } + + public UIEditorFeatures.EditorFeature[] getUiEditorFeatures() { + return this.uiEditorFeatures; + } + + public void setUiEditorFeatures(UIEditorFeatures.EditorFeature[] uiEditorFeatures) { + this.uiEditorFeatures = uiEditorFeatures; + } + + public UIEditorPreview.PreviewType getUiEditorPreview() { + return this.uiEditorPreview; + } + + public void setUiEditorPreview(UIEditorPreview.PreviewType uiEditorPreview) { + this.uiEditorPreview = uiEditorPreview; + } + + public String getUiTypeIcon() { + return this.uiTypeIcon; + } + + public void setUiTypeIcon(String uiTypeIcon) { + this.uiTypeIcon = uiTypeIcon; + } + + public Boolean getUiEditorIgnore() { + return this.uiEditorIgnore; + } + + public void setUiEditorIgnore(Boolean uiEditorIgnore) { + this.uiEditorIgnore = uiEditorIgnore; + } + + public Boolean getAllowEmptyObject() { + return this.allowEmptyObject; + } + + public void setAllowEmptyObject(Boolean allowEmptyObject) { + this.allowEmptyObject = allowEmptyObject; + } + + public String getUiPropertyTitle() { + return this.uiPropertyTitle; + } + + public void setUiPropertyTitle(String uiPropertyTitle) { + this.uiPropertyTitle = uiPropertyTitle; + } + + public String getUiSectionStart() { + return this.uiSectionStart; + } + + public void setUiSectionStart(String uiSectionStart) { + this.uiSectionStart = uiSectionStart; + } + + public boolean isInheritsProperty() { + return this.inheritsProperty; + } + + public void setInheritsProperty(boolean inheritsProperty) { + this.inheritsProperty = inheritsProperty; + } + + public boolean getMergesProperties() { + return this.mergesProperties; + } + + public void setMergesProperties(boolean mergesProperties) { + this.mergesProperties = mergesProperties; + } + + public UIRebuildCaches.ClientCache[] getUiRebuildCaches() { + return this.uiRebuildCaches; + } + + public void setUiRebuildCaches(UIRebuildCaches.ClientCache[] uiRebuildCaches) { + this.uiRebuildCaches = uiRebuildCaches; + } + + public Boolean getUiRebuildCachesForChildProperties() { + return this.uiRebuildCachesForChildProperties; + } + + public void setUiRebuildCachesForChildProperties(Boolean uiRebuildCachesForChildProperties) { + this.uiRebuildCachesForChildProperties = uiRebuildCachesForChildProperties; + } + + public UIButton[] getUiSidebarButtons() { + return this.uiSidebarButtons; + } + + public void setUiSidebarButtons(UIButton[] uiSidebarButtons) { + this.uiSidebarButtons = uiSidebarButtons; + } + + public Boolean getUiCollapsedByDefault() { + return this.uiCollapsedByDefault; + } + + public void setUiCollapsedByDefault(Boolean uiCollapsedByDefault) { + this.uiCollapsedByDefault = uiCollapsedByDefault; + } + + public UIButton[] getUiCreateButtons() { + return this.uiCreateButtons; + } + + public void setUiCreateButtons(UIButton[] uiCreateButtons) { + this.uiCreateButtons = uiCreateButtons; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Schema.HytaleMetadata that = (Schema.HytaleMetadata)o; + if (this.type != null ? this.type.equals(that.type) : that.type == null) { + if (this.path != null ? this.path.equals(that.path) : that.path == null) { + if (this.virtualPath != null ? this.virtualPath.equals(that.virtualPath) : that.virtualPath == null) { + if (this.extension != null ? this.extension.equals(that.extension) : that.extension == null) { + if (this.idProvider != null ? this.idProvider.equals(that.idProvider) : that.idProvider == null) { + if (!Arrays.equals((Object[])this.internalKeys, (Object[])that.internalKeys)) { + return false; + } else if (this.inheritsProperty != null ? this.inheritsProperty.equals(that.inheritsProperty) : that.inheritsProperty == null) { + if (this.mergesProperties != null ? this.mergesProperties.equals(that.mergesProperties) : that.mergesProperties == null) { + if (!Arrays.equals((Object[])this.uiEditorFeatures, (Object[])that.uiEditorFeatures)) { + return false; + } else if (this.uiEditorPreview != that.uiEditorPreview) { + return false; + } else if (this.uiTypeIcon != null ? this.uiTypeIcon.equals(that.uiTypeIcon) : that.uiTypeIcon == null) { + if (this.uiEditorIgnore != null ? this.uiEditorIgnore.equals(that.uiEditorIgnore) : that.uiEditorIgnore == null) { + if (this.allowEmptyObject != null ? this.allowEmptyObject.equals(that.allowEmptyObject) : that.allowEmptyObject == null) { + if (this.uiDisplayMode != that.uiDisplayMode) { + return false; + } else if (this.uiEditorComponent != null + ? this.uiEditorComponent.equals(that.uiEditorComponent) + : that.uiEditorComponent == null) { + if (this.uiPropertyTitle != null + ? this.uiPropertyTitle.equals(that.uiPropertyTitle) + : that.uiPropertyTitle == null) { + if (this.uiSectionStart != null ? this.uiSectionStart.equals(that.uiSectionStart) : that.uiSectionStart == null + ) + { + if (!Arrays.equals((Object[])this.uiRebuildCaches, (Object[])that.uiRebuildCaches)) { + return false; + } else if (this.uiRebuildCachesForChildProperties != null + ? this.uiRebuildCachesForChildProperties.equals(that.uiRebuildCachesForChildProperties) + : that.uiRebuildCachesForChildProperties == null) { + if (!Arrays.equals((Object[])this.uiSidebarButtons, (Object[])that.uiSidebarButtons)) { + return false; + } else { + return ( + this.uiCollapsedByDefault != null + ? this.uiCollapsedByDefault.equals(that.uiCollapsedByDefault) + : that.uiCollapsedByDefault == null + ) + ? Arrays.equals((Object[])this.uiCreateButtons, (Object[])that.uiCreateButtons) + : false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.type != null ? this.type.hashCode() : 0; + result = 31 * result + (this.path != null ? this.path.hashCode() : 0); + result = 31 * result + (this.virtualPath != null ? this.virtualPath.hashCode() : 0); + result = 31 * result + (this.extension != null ? this.extension.hashCode() : 0); + result = 31 * result + (this.idProvider != null ? this.idProvider.hashCode() : 0); + result = 31 * result + Arrays.hashCode((Object[])this.internalKeys); + result = 31 * result + (this.inheritsProperty != null ? this.inheritsProperty.hashCode() : 0); + result = 31 * result + (this.mergesProperties != null ? this.mergesProperties.hashCode() : 0); + result = 31 * result + Arrays.hashCode((Object[])this.uiEditorFeatures); + result = 31 * result + (this.uiEditorPreview != null ? this.uiEditorPreview.hashCode() : 0); + result = 31 * result + (this.uiTypeIcon != null ? this.uiTypeIcon.hashCode() : 0); + result = 31 * result + (this.uiEditorIgnore != null ? this.uiEditorIgnore.hashCode() : 0); + result = 31 * result + (this.allowEmptyObject != null ? this.allowEmptyObject.hashCode() : 0); + result = 31 * result + (this.uiDisplayMode != null ? this.uiDisplayMode.hashCode() : 0); + result = 31 * result + (this.uiEditorComponent != null ? this.uiEditorComponent.hashCode() : 0); + result = 31 * result + (this.uiPropertyTitle != null ? this.uiPropertyTitle.hashCode() : 0); + result = 31 * result + (this.uiSectionStart != null ? this.uiSectionStart.hashCode() : 0); + result = 31 * result + Arrays.hashCode((Object[])this.uiRebuildCaches); + result = 31 * result + (this.uiRebuildCachesForChildProperties != null ? this.uiRebuildCachesForChildProperties.hashCode() : 0); + result = 31 * result + Arrays.hashCode((Object[])this.uiSidebarButtons); + result = 31 * result + (this.uiCollapsedByDefault != null ? this.uiCollapsedByDefault.hashCode() : 0); + return 31 * result + Arrays.hashCode((Object[])this.uiCreateButtons); + } + } + + public static class InheritSettings { + public static final BuilderCodec CODEC = BuilderCodec.builder(Schema.InheritSettings.class, Schema.InheritSettings::new) + .addField(new KeyedCodec<>("type", Codec.STRING, false, true), (o, i) -> o.type = i, o -> o.type) + .addField(new KeyedCodec<>("mapKey", Codec.STRING, false, true), (o, i) -> o.mapKey = i, o -> o.mapKey) + .addField(new KeyedCodec<>("mapKeyValue", Codec.STRING, false, true), (o, i) -> o.mapKeyValue = i, o -> o.mapKeyValue) + .build(); + private String type; + private String mapKey; + private String mapKeyValue; + + public InheritSettings(String type) { + this.type = type; + } + + protected InheritSettings() { + } + + public String getType() { + return this.type; + } + + public void setType(String type) { + this.type = type; + } + + public String getMapKey() { + return this.mapKey; + } + + public void setMapKey(String mapKey) { + this.mapKey = mapKey; + } + + public String getMapKeyValue() { + return this.mapKeyValue; + } + + public void setMapKeyValue(String mapKeyValue) { + this.mapKeyValue = mapKeyValue; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Schema.InheritSettings that = (Schema.InheritSettings)o; + if (this.type != null ? this.type.equals(that.type) : that.type == null) { + if (this.mapKey != null ? this.mapKey.equals(that.mapKey) : that.mapKey == null) { + return this.mapKeyValue != null ? this.mapKeyValue.equals(that.mapKeyValue) : that.mapKeyValue == null; + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.type != null ? this.type.hashCode() : 0; + result = 31 * result + (this.mapKey != null ? this.mapKey.hashCode() : 0); + return 31 * result + (this.mapKeyValue != null ? this.mapKeyValue.hashCode() : 0); + } + } + + public static class SchemaTypeField { + public static final BuilderCodec CODEC = BuilderCodec.builder(Schema.SchemaTypeField.class, Schema.SchemaTypeField::new) + .addField(new KeyedCodec<>("property", Codec.STRING, false, true), (o, i) -> o.property = i, o -> o.property) + .addField(new KeyedCodec<>("defaultValue", Codec.STRING, false, true), (o, i) -> o.defaultValue = i, o -> o.defaultValue) + .addField(new KeyedCodec<>("values", Codec.STRING_ARRAY, false, true), (o, i) -> o.values = i, o -> o.values) + .addField(new KeyedCodec<>("parentPropertyKey", Codec.STRING, false, true), (o, i) -> o.parentPropertyKey = i, o -> o.parentPropertyKey) + .build(); + private String property; + private String defaultValue; + private String[] values; + private String parentPropertyKey; + + public SchemaTypeField(String property, String defaultValue, String... values) { + this.property = property; + this.defaultValue = defaultValue; + this.values = values; + } + + protected SchemaTypeField() { + } + + public String getProperty() { + return this.property; + } + + public String getDefaultValue() { + return this.defaultValue; + } + + public String[] getValues() { + return this.values; + } + + public String getParentPropertyKey() { + return this.parentPropertyKey; + } + + public void setParentPropertyKey(String parentPropertyKey) { + this.parentPropertyKey = parentPropertyKey; + } + + @Override + public boolean equals(Object o) { + if (o != null && this.getClass() == o.getClass()) { + Schema.SchemaTypeField that = (Schema.SchemaTypeField)o; + return Objects.equals(this.property, that.property) + && Objects.equals(this.defaultValue, that.defaultValue) + && Arrays.deepEquals(this.values, that.values) + && Objects.equals(this.parentPropertyKey, that.parentPropertyKey); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = Objects.hashCode(this.property); + result = 31 * result + Objects.hashCode(this.defaultValue); + result = 31 * result + Arrays.hashCode((Object[])this.values); + return 31 * result + Objects.hashCode(this.parentPropertyKey); + } + } + + @Deprecated + private static class StringOrBlank implements Codec { + private StringOrBlank() { + } + + public String decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + if (bsonValue.isString()) { + return Codec.STRING.decode(bsonValue, extraInfo); + } else if (bsonValue.isArray()) { + BsonArray arr = bsonValue.asArray(); + + for (int i = 0; i < arr.size(); i++) { + BsonValue val = arr.get(i); + if (!val.asString().getValue().equals("null")) { + return Codec.STRING.decode(val, extraInfo); + } + } + + throw new IllegalArgumentException("Unknown type (in array)"); + } else { + return ""; + } + } + + @Nonnull + public BsonValue encode(@Nonnull String o, ExtraInfo extraInfo) { + return Codec.STRING.encode(o, extraInfo); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return Schema.anyOf(new ArraySchema(), new StringSchema()); + } + } +} diff --git a/src/com/hypixel/hytale/codec/schema/config/StringSchema.java b/src/com/hypixel/hytale/codec/schema/config/StringSchema.java new file mode 100644 index 0000000..79aca10 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/config/StringSchema.java @@ -0,0 +1,199 @@ +package com.hypixel.hytale.codec.schema.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import java.util.Arrays; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StringSchema extends Schema { + public static final BuilderCodec CODEC = BuilderCodec.builder(StringSchema.class, StringSchema::new, Schema.BASE_CODEC) + .addField(new KeyedCodec<>("pattern", Codec.STRING, false, true), (o, i) -> o.pattern = i, o -> o.pattern) + .addField(new KeyedCodec<>("enum", Codec.STRING_ARRAY, false, true), (o, i) -> o.enum_ = i, o -> o.enum_) + .addField(new KeyedCodec<>("const", Codec.STRING, false, true), (o, i) -> o.const_ = i, o -> o.const_) + .addField(new KeyedCodec<>("default", Codec.STRING, false, true), (o, i) -> o.default_ = i, o -> o.default_) + .addField(new KeyedCodec<>("minLength", Codec.INTEGER, false, true), (o, i) -> o.minLength = i, o -> o.minLength) + .addField(new KeyedCodec<>("maxLength", Codec.INTEGER, false, true), (o, i) -> o.maxLength = i, o -> o.maxLength) + .addField(new KeyedCodec<>("hytaleCommonAsset", StringSchema.CommonAsset.CODEC, false, true), (o, i) -> o.hytaleCommonAsset = i, o -> o.hytaleCommonAsset) + .addField(new KeyedCodec<>("hytaleCosmeticAsset", Codec.STRING, false, true), (o, i) -> o.hytaleCosmeticAsset = i, o -> o.hytaleCosmeticAsset) + .build(); + private String pattern; + private String[] enum_; + private String const_; + private String default_; + private Integer minLength; + private Integer maxLength; + private StringSchema.CommonAsset hytaleCommonAsset; + private String hytaleCosmeticAsset; + + public StringSchema() { + } + + public String getPattern() { + return this.pattern; + } + + public void setPattern(String pattern) { + this.pattern = pattern; + } + + public void setPattern(@Nonnull Pattern pattern) { + if (pattern.flags() != 0) { + throw new IllegalArgumentException("Pattern has flags set. Flags are not supported in schema."); + } else { + this.pattern = pattern.pattern(); + } + } + + public Integer getMinLength() { + return this.minLength; + } + + public void setMinLength(int minLength) { + this.minLength = minLength; + } + + public Integer getMaxLength() { + return this.maxLength; + } + + public void setMaxLength(int maxLength) { + this.maxLength = maxLength; + } + + public String[] getEnum() { + return this.enum_; + } + + public void setEnum(String[] enum_) { + this.enum_ = enum_; + } + + public String getConst() { + return this.const_; + } + + public void setConst(String const_) { + this.const_ = const_; + } + + public String getDefault() { + return this.default_; + } + + public void setDefault(String default_) { + this.default_ = default_; + } + + public StringSchema.CommonAsset getHytaleCommonAsset() { + return this.hytaleCommonAsset; + } + + public void setHytaleCommonAsset(StringSchema.CommonAsset hytaleCommonAsset) { + this.hytaleCommonAsset = hytaleCommonAsset; + } + + public String getHytaleCosmeticAsset() { + return this.hytaleCosmeticAsset; + } + + public void setHytaleCosmeticAsset(String hytaleCosmeticAsset) { + this.hytaleCosmeticAsset = hytaleCosmeticAsset; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o == null || this.getClass() != o.getClass()) { + return false; + } else if (!super.equals(o)) { + return false; + } else { + StringSchema that = (StringSchema)o; + if (this.pattern != null ? this.pattern.equals(that.pattern) : that.pattern == null) { + if (!Arrays.equals((Object[])this.enum_, (Object[])that.enum_)) { + return false; + } else if (this.const_ != null ? this.const_.equals(that.const_) : that.const_ == null) { + if (this.default_ != null ? this.default_.equals(that.default_) : that.default_ == null) { + if (this.minLength != null ? this.minLength.equals(that.minLength) : that.minLength == null) { + if (this.maxLength != null ? this.maxLength.equals(that.maxLength) : that.maxLength == null) { + if (this.hytaleCommonAsset != null ? this.hytaleCommonAsset.equals(that.hytaleCommonAsset) : that.hytaleCommonAsset == null) { + return this.hytaleCosmeticAsset != null + ? this.hytaleCosmeticAsset.equals(that.hytaleCosmeticAsset) + : that.hytaleCosmeticAsset == null; + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (this.pattern != null ? this.pattern.hashCode() : 0); + result = 31 * result + Arrays.hashCode((Object[])this.enum_); + result = 31 * result + (this.const_ != null ? this.const_.hashCode() : 0); + result = 31 * result + (this.default_ != null ? this.default_.hashCode() : 0); + result = 31 * result + (this.minLength != null ? this.minLength.hashCode() : 0); + result = 31 * result + (this.maxLength != null ? this.maxLength.hashCode() : 0); + result = 31 * result + (this.hytaleCommonAsset != null ? this.hytaleCommonAsset.hashCode() : 0); + return 31 * result + (this.hytaleCosmeticAsset != null ? this.hytaleCosmeticAsset.hashCode() : 0); + } + + @Nonnull + public static Schema constant(String c) { + StringSchema s = new StringSchema(); + s.setConst(c); + return s; + } + + public static class CommonAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(StringSchema.CommonAsset.class, StringSchema.CommonAsset::new) + .addField(new KeyedCodec<>("requiredRoots", Codec.STRING_ARRAY, false, true), (o, i) -> o.requiredRoots = i, o -> o.requiredRoots) + .addField(new KeyedCodec<>("requiredExtension", Codec.STRING, false, true), (o, i) -> o.requiredExtension = i, o -> o.requiredExtension) + .addField(new KeyedCodec<>("isUIAsset", Codec.BOOLEAN, false, true), (o, i) -> o.isUIAsset = i, o -> o.isUIAsset) + .build(); + private String[] requiredRoots; + private String requiredExtension; + private boolean isUIAsset; + + public CommonAsset(String requiredExtension, boolean isUIAsset, String... requiredRoots) { + this.requiredRoots = requiredRoots; + this.requiredExtension = requiredExtension; + this.isUIAsset = isUIAsset; + } + + protected CommonAsset() { + } + + public String[] getRequiredRoots() { + return this.requiredRoots; + } + + public String getRequiredExtension() { + return this.requiredExtension; + } + + public boolean isUIAsset() { + return this.isUIAsset; + } + } +} diff --git a/src/com/hypixel/hytale/codec/schema/metadata/AllowEmptyObject.java b/src/com/hypixel/hytale/codec/schema/metadata/AllowEmptyObject.java new file mode 100644 index 0000000..5e7acbc --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/metadata/AllowEmptyObject.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.codec.schema.metadata; + +import com.hypixel.hytale.codec.schema.config.Schema; +import javax.annotation.Nonnull; + +public class AllowEmptyObject implements Metadata { + public static final AllowEmptyObject INSTANCE = new AllowEmptyObject(true); + private final boolean allowEmptyObject; + + public AllowEmptyObject(boolean allowEmptyObject) { + this.allowEmptyObject = allowEmptyObject; + } + + @Override + public void modify(@Nonnull Schema schema) { + schema.getHytale().setAllowEmptyObject(this.allowEmptyObject); + } +} diff --git a/src/com/hypixel/hytale/codec/schema/metadata/HytaleType.java b/src/com/hypixel/hytale/codec/schema/metadata/HytaleType.java new file mode 100644 index 0000000..2d3bdcf --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/metadata/HytaleType.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.codec.schema.metadata; + +import com.hypixel.hytale.codec.schema.config.Schema; +import javax.annotation.Nonnull; + +public class HytaleType implements Metadata { + private final String type; + + public HytaleType(String type) { + this.type = type; + } + + @Override + public void modify(@Nonnull Schema schema) { + schema.getHytale().setType(this.type); + } +} diff --git a/src/com/hypixel/hytale/codec/schema/metadata/Metadata.java b/src/com/hypixel/hytale/codec/schema/metadata/Metadata.java new file mode 100644 index 0000000..fcd4980 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/metadata/Metadata.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.codec.schema.metadata; + +import com.hypixel.hytale.codec.schema.config.Schema; + +public interface Metadata { + void modify(Schema var1); +} diff --git a/src/com/hypixel/hytale/codec/schema/metadata/NoDefaultValue.java b/src/com/hypixel/hytale/codec/schema/metadata/NoDefaultValue.java new file mode 100644 index 0000000..8e7d038 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/metadata/NoDefaultValue.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.codec.schema.metadata; + +import com.hypixel.hytale.codec.schema.config.BooleanSchema; +import com.hypixel.hytale.codec.schema.config.IntegerSchema; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; + +public class NoDefaultValue implements Metadata { + public static final NoDefaultValue INSTANCE = new NoDefaultValue(); + + private NoDefaultValue() { + } + + @Override + public void modify(Schema schema) { + if (schema instanceof StringSchema) { + ((StringSchema)schema).setDefault(null); + } else if (schema instanceof IntegerSchema) { + ((IntegerSchema)schema).setDefault(null); + } else if (schema instanceof NumberSchema) { + ((NumberSchema)schema).setDefault(null); + } else if (schema instanceof BooleanSchema) { + ((BooleanSchema)schema).setDefault(null); + } + } +} diff --git a/src/com/hypixel/hytale/codec/schema/metadata/VirtualPath.java b/src/com/hypixel/hytale/codec/schema/metadata/VirtualPath.java new file mode 100644 index 0000000..0979bc9 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/metadata/VirtualPath.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.codec.schema.metadata; + +import com.hypixel.hytale.codec.schema.config.Schema; +import javax.annotation.Nonnull; + +public class VirtualPath implements Metadata { + private final String path; + + public VirtualPath(String path) { + this.path = path; + } + + @Override + public void modify(@Nonnull Schema schema) { + schema.getHytale().setVirtualPath(this.path); + } +} diff --git a/src/com/hypixel/hytale/codec/schema/metadata/ui/UIButton.java b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIButton.java new file mode 100644 index 0000000..17cb1f7 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIButton.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.codec.schema.metadata.ui; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; + +public class UIButton { + public static final BuilderCodec CODEC = BuilderCodec.builder(UIButton.class, UIButton::new) + .append(new KeyedCodec<>("textId", Codec.STRING, false, true), (o, i) -> o.textId = i, o -> o.textId) + .add() + .append(new KeyedCodec<>("buttonId", Codec.STRING, false, true), (o, i) -> o.buttonId = i, o -> o.buttonId) + .add() + .build(); + private String buttonId; + private String textId; + + public UIButton(String textId, String buttonId) { + this.textId = textId; + this.buttonId = buttonId; + } + + protected UIButton() { + } +} diff --git a/src/com/hypixel/hytale/codec/schema/metadata/ui/UICreateButtons.java b/src/com/hypixel/hytale/codec/schema/metadata/ui/UICreateButtons.java new file mode 100644 index 0000000..de83cba --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/metadata/ui/UICreateButtons.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.codec.schema.metadata.ui; + +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.metadata.Metadata; +import javax.annotation.Nonnull; + +public class UICreateButtons implements Metadata { + private final UIButton[] buttons; + + public UICreateButtons(UIButton... buttons) { + this.buttons = buttons; + } + + @Override + public void modify(@Nonnull Schema schema) { + schema.getHytale().setUiCreateButtons(this.buttons); + } +} diff --git a/src/com/hypixel/hytale/codec/schema/metadata/ui/UIDefaultCollapsedState.java b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIDefaultCollapsedState.java new file mode 100644 index 0000000..bf870e1 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIDefaultCollapsedState.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.codec.schema.metadata.ui; + +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.metadata.Metadata; +import javax.annotation.Nonnull; + +public class UIDefaultCollapsedState implements Metadata { + public static final UIDefaultCollapsedState UNCOLLAPSED = new UIDefaultCollapsedState(false); + private final boolean collapsedByDefault; + + private UIDefaultCollapsedState(boolean collapsedByDefault) { + this.collapsedByDefault = collapsedByDefault; + } + + @Override + public void modify(@Nonnull Schema schema) { + schema.getHytale().setUiCollapsedByDefault(this.collapsedByDefault); + } +} diff --git a/src/com/hypixel/hytale/codec/schema/metadata/ui/UIDisplayMode.java b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIDisplayMode.java new file mode 100644 index 0000000..c5cae22 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIDisplayMode.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.codec.schema.metadata.ui; + +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.metadata.Metadata; +import javax.annotation.Nonnull; + +public class UIDisplayMode implements Metadata { + public static final UIDisplayMode NORMAL = new UIDisplayMode(UIDisplayMode.DisplayMode.NORMAL); + public static final UIDisplayMode COMPACT = new UIDisplayMode(UIDisplayMode.DisplayMode.COMPACT); + public static final UIDisplayMode HIDDEN = new UIDisplayMode(UIDisplayMode.DisplayMode.HIDDEN); + private final UIDisplayMode.DisplayMode mode; + + private UIDisplayMode(UIDisplayMode.DisplayMode mode) { + this.mode = mode; + } + + @Override + public void modify(@Nonnull Schema schema) { + schema.getHytale().setUiDisplayMode(this.mode); + } + + public static enum DisplayMode { + NORMAL, + COMPACT, + HIDDEN; + + private DisplayMode() { + } + } +} diff --git a/src/com/hypixel/hytale/codec/schema/metadata/ui/UIEditor.java b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIEditor.java new file mode 100644 index 0000000..76e118e --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIEditor.java @@ -0,0 +1,173 @@ +package com.hypixel.hytale.codec.schema.metadata.ui; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.metadata.Metadata; +import javax.annotation.Nonnull; + +public class UIEditor implements Metadata { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("component"); + public static final UIEditor.Timeline TIMELINE = new UIEditor.Timeline(); + public static final UIEditor.WeightedTimeline WEIGHTED_TIMELINE = new UIEditor.WeightedTimeline(); + private final UIEditor.EditorComponent component; + + public UIEditor(UIEditor.EditorComponent component) { + this.component = component; + } + + @Override + public void modify(@Nonnull Schema schema) { + schema.getHytale().setUiEditorComponent(this.component); + } + + public static void init() { + CODEC.register("Timeline", UIEditor.Timeline.class, UIEditor.Timeline.CODEC); + CODEC.register("WeightedTimeline", UIEditor.WeightedTimeline.class, UIEditor.WeightedTimeline.CODEC); + CODEC.register("Number", UIEditor.FormattedNumber.class, UIEditor.FormattedNumber.CODEC); + CODEC.register("Text", UIEditor.TextField.class, UIEditor.TextField.CODEC); + CODEC.register("MultilineText", UIEditor.MultilineTextField.class, UIEditor.MultilineTextField.CODEC); + CODEC.register("Dropdown", UIEditor.Dropdown.class, UIEditor.Dropdown.CODEC); + CODEC.register("Icon", UIEditor.Icon.class, UIEditor.Icon.CODEC); + CODEC.register("LocalizationKey", UIEditor.LocalizationKeyField.class, UIEditor.LocalizationKeyField.CODEC); + } + + public static class Dropdown implements UIEditor.EditorComponent { + public static final BuilderCodec CODEC = BuilderCodec.builder(UIEditor.Dropdown.class, UIEditor.Dropdown::new) + .addField(new KeyedCodec<>("dataSet", Codec.STRING, false, true), (o, i) -> o.dataSet = i, o -> o.dataSet) + .build(); + private String dataSet; + + public Dropdown(String dataSet) { + this.dataSet = dataSet; + } + + protected Dropdown() { + } + } + + public interface EditorComponent { + } + + public static class FormattedNumber implements UIEditor.EditorComponent { + public static final BuilderCodec CODEC = BuilderCodec.builder(UIEditor.FormattedNumber.class, UIEditor.FormattedNumber::new) + .addField(new KeyedCodec<>("step", Codec.DOUBLE, false, true), (o, i) -> o.step = i, o -> o.step) + .addField(new KeyedCodec<>("suffix", Codec.STRING, false, true), (o, i) -> o.suffix = i, o -> o.suffix) + .addField(new KeyedCodec<>("maxDecimalPlaces", Codec.INTEGER, false, true), (o, i) -> o.maxDecimalPlaces = i, o -> o.maxDecimalPlaces) + .build(); + private Double step; + private String suffix; + private Integer maxDecimalPlaces; + + public FormattedNumber(Double step, String suffix, Integer maxDecimalPlaces) { + this.step = step; + this.suffix = suffix; + this.maxDecimalPlaces = maxDecimalPlaces; + } + + public FormattedNumber() { + } + + @Nonnull + public UIEditor.FormattedNumber setStep(Double step) { + this.step = step; + return this; + } + + @Nonnull + public UIEditor.FormattedNumber setSuffix(String suffix) { + this.suffix = suffix; + return this; + } + + @Nonnull + public UIEditor.FormattedNumber setMaxDecimalPlaces(Integer maxDecimalPlaces) { + this.maxDecimalPlaces = maxDecimalPlaces; + return this; + } + } + + public static class Icon implements UIEditor.EditorComponent { + public static final BuilderCodec CODEC = BuilderCodec.builder(UIEditor.Icon.class, UIEditor.Icon::new) + .addField(new KeyedCodec<>("defaultPathTemplate", Codec.STRING, true, true), (o, i) -> o.defaultPathTemplate = i, o -> o.defaultPathTemplate) + .addField(new KeyedCodec<>("width", Codec.INTEGER, true, true), (o, i) -> o.width = i, o -> o.width) + .addField(new KeyedCodec<>("height", Codec.INTEGER, true, true), (o, i) -> o.height = i, o -> o.height) + .build(); + private String defaultPathTemplate; + private int width; + private int height; + + public Icon(String defaultPathTemplate, int width, int height) { + this.defaultPathTemplate = defaultPathTemplate; + this.width = width; + this.height = height; + } + + public Icon() { + } + } + + public static class LocalizationKeyField implements UIEditor.EditorComponent { + public static final BuilderCodec CODEC = BuilderCodec.builder( + UIEditor.LocalizationKeyField.class, UIEditor.LocalizationKeyField::new + ) + .addField(new KeyedCodec<>("keyTemplate", Codec.STRING, false, true), (o, i) -> o.keyTemplate = i, o -> o.keyTemplate) + .addField(new KeyedCodec<>("generateDefaultKey", Codec.BOOLEAN, false, true), (o, i) -> o.generateDefaultKey = i, o -> o.generateDefaultKey) + .build(); + private String keyTemplate; + private boolean generateDefaultKey; + + public LocalizationKeyField(String keyTemplate) { + this(keyTemplate, false); + } + + public LocalizationKeyField(String keyTemplate, boolean generateDefaultKey) { + this.keyTemplate = keyTemplate; + this.generateDefaultKey = generateDefaultKey; + } + + public LocalizationKeyField() { + } + } + + public static class MultilineTextField implements UIEditor.EditorComponent { + public static final BuilderCodec CODEC = BuilderCodec.builder( + UIEditor.MultilineTextField.class, UIEditor.MultilineTextField::new + ) + .build(); + + public MultilineTextField() { + } + } + + public static class TextField implements UIEditor.EditorComponent { + public static final BuilderCodec CODEC = BuilderCodec.builder(UIEditor.TextField.class, UIEditor.TextField::new) + .addField(new KeyedCodec<>("dataSet", Codec.STRING, false, true), (o, i) -> o.dataSet = i, o -> o.dataSet) + .build(); + private String dataSet; + + public TextField(String dataSet) { + this.dataSet = dataSet; + } + + protected TextField() { + } + } + + public static class Timeline implements UIEditor.EditorComponent { + public static final BuilderCodec CODEC = BuilderCodec.builder(UIEditor.Timeline.class, UIEditor.Timeline::new).build(); + + public Timeline() { + } + } + + public static class WeightedTimeline implements UIEditor.EditorComponent { + public static final BuilderCodec CODEC = BuilderCodec.builder(UIEditor.WeightedTimeline.class, UIEditor.WeightedTimeline::new) + .build(); + + public WeightedTimeline() { + } + } +} diff --git a/src/com/hypixel/hytale/codec/schema/metadata/ui/UIEditorFeatures.java b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIEditorFeatures.java new file mode 100644 index 0000000..66ac236 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIEditorFeatures.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.codec.schema.metadata.ui; + +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.metadata.Metadata; +import javax.annotation.Nonnull; + +public class UIEditorFeatures implements Metadata { + private final UIEditorFeatures.EditorFeature[] features; + + public UIEditorFeatures(UIEditorFeatures.EditorFeature... features) { + this.features = features; + } + + @Override + public void modify(@Nonnull Schema schema) { + schema.getHytale().setUiEditorFeatures(this.features); + } + + public static enum EditorFeature { + WEATHER_DAYTIME_BAR, + WEATHER_PREVIEW_LOCAL; + + private EditorFeature() { + } + } +} diff --git a/src/com/hypixel/hytale/codec/schema/metadata/ui/UIEditorPreview.java b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIEditorPreview.java new file mode 100644 index 0000000..b32c26e --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIEditorPreview.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.codec.schema.metadata.ui; + +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.metadata.Metadata; +import javax.annotation.Nonnull; + +public class UIEditorPreview implements Metadata { + private final UIEditorPreview.PreviewType previewType; + + public UIEditorPreview(UIEditorPreview.PreviewType type) { + this.previewType = type; + } + + @Override + public void modify(@Nonnull Schema schema) { + schema.getHytale().setUiEditorPreview(this.previewType); + } + + public static enum PreviewType { + ITEM, + MODEL, + REVERB_EFFECT, + EQUALIZER_EFFECT; + + private PreviewType() { + } + } +} diff --git a/src/com/hypixel/hytale/codec/schema/metadata/ui/UIEditorSectionStart.java b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIEditorSectionStart.java new file mode 100644 index 0000000..a3b4ef2 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIEditorSectionStart.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.codec.schema.metadata.ui; + +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.metadata.Metadata; +import javax.annotation.Nonnull; + +public class UIEditorSectionStart implements Metadata { + private final String title; + + public UIEditorSectionStart(String title) { + this.title = title; + } + + @Override + public void modify(@Nonnull Schema schema) { + schema.getHytale().setUiSectionStart(this.title); + } +} diff --git a/src/com/hypixel/hytale/codec/schema/metadata/ui/UIPropertyTitle.java b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIPropertyTitle.java new file mode 100644 index 0000000..1af18de --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIPropertyTitle.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.codec.schema.metadata.ui; + +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.metadata.Metadata; +import javax.annotation.Nonnull; + +public class UIPropertyTitle implements Metadata { + private final String title; + + public UIPropertyTitle(String title) { + this.title = title; + } + + @Override + public void modify(@Nonnull Schema schema) { + schema.getHytale().setUiPropertyTitle(this.title); + } +} diff --git a/src/com/hypixel/hytale/codec/schema/metadata/ui/UIRebuildCaches.java b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIRebuildCaches.java new file mode 100644 index 0000000..b1d71a7 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/metadata/ui/UIRebuildCaches.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.codec.schema.metadata.ui; + +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.metadata.Metadata; +import javax.annotation.Nonnull; + +public class UIRebuildCaches implements Metadata { + private final UIRebuildCaches.ClientCache[] caches; + private final boolean appliesToChildProperties; + + public UIRebuildCaches(UIRebuildCaches.ClientCache... caches) { + this(true, caches); + } + + public UIRebuildCaches(boolean appliesToChildProperties, UIRebuildCaches.ClientCache... caches) { + this.caches = caches; + this.appliesToChildProperties = appliesToChildProperties; + } + + @Override + public void modify(@Nonnull Schema schema) { + schema.getHytale().setUiRebuildCaches(this.caches); + schema.getHytale().setUiRebuildCachesForChildProperties(this.appliesToChildProperties); + } + + public static enum ClientCache { + BLOCK_TEXTURES, + MODELS, + MODEL_TEXTURES, + MAP_GEOMETRY, + ITEM_ICONS; + + private ClientCache() { + } + } +} diff --git a/src/com/hypixel/hytale/codec/schema/metadata/ui/UISidebarButtons.java b/src/com/hypixel/hytale/codec/schema/metadata/ui/UISidebarButtons.java new file mode 100644 index 0000000..b1b67f1 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/metadata/ui/UISidebarButtons.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.codec.schema.metadata.ui; + +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.metadata.Metadata; +import javax.annotation.Nonnull; + +public class UISidebarButtons implements Metadata { + private final UIButton[] buttons; + + public UISidebarButtons(UIButton... buttons) { + this.buttons = buttons; + } + + @Override + public void modify(@Nonnull Schema schema) { + schema.getHytale().setUiSidebarButtons(this.buttons); + } +} diff --git a/src/com/hypixel/hytale/codec/schema/metadata/ui/UITypeIcon.java b/src/com/hypixel/hytale/codec/schema/metadata/ui/UITypeIcon.java new file mode 100644 index 0000000..a15a606 --- /dev/null +++ b/src/com/hypixel/hytale/codec/schema/metadata/ui/UITypeIcon.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.codec.schema.metadata.ui; + +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.metadata.Metadata; +import javax.annotation.Nonnull; + +public class UITypeIcon implements Metadata { + private final String icon; + + public UITypeIcon(String icon) { + this.icon = icon; + } + + @Override + public void modify(@Nonnull Schema schema) { + schema.getHytale().setUiTypeIcon(this.icon); + } +} diff --git a/src/com/hypixel/hytale/codec/store/CodecKey.java b/src/com/hypixel/hytale/codec/store/CodecKey.java new file mode 100644 index 0000000..12c4f63 --- /dev/null +++ b/src/com/hypixel/hytale/codec/store/CodecKey.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.codec.store; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CodecKey { + private final String id; + + public CodecKey(String id) { + this.id = id; + } + + public String getId() { + return this.id; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + CodecKey codecKey = (CodecKey)o; + return this.id != null ? this.id.equals(codecKey.id) : codecKey.id == null; + } else { + return false; + } + } + + @Override + public int hashCode() { + return this.id != null ? this.id.hashCode() : 0; + } + + @Nonnull + @Override + public String toString() { + return "CodecKey{id='" + this.id + "'}"; + } +} diff --git a/src/com/hypixel/hytale/codec/store/CodecStore.java b/src/com/hypixel/hytale/codec/store/CodecStore.java new file mode 100644 index 0000000..758e6c8 --- /dev/null +++ b/src/com/hypixel/hytale/codec/store/CodecStore.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.codec.store; + +import com.hypixel.hytale.codec.Codec; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; +import javax.annotation.Nullable; + +public class CodecStore { + public static final CodecStore STATIC = new CodecStore(); + private final CodecStore parent; + private final Map, Codec> codecs = new ConcurrentHashMap<>(); + private final Map, Supplier>> codecSuppliers = new ConcurrentHashMap<>(); + + public CodecStore() { + this.parent = STATIC; + } + + public CodecStore(CodecStore parent) { + this.parent = parent; + } + + @Nullable + public Codec getCodec(CodecKey key) { + Codec codec = (Codec)this.codecs.get(key); + if (codec != null) { + return codec; + } else { + Supplier> supplier = this.codecSuppliers.get(key); + if (supplier != null) { + codec = (Codec)supplier.get(); + } + + if (codec != null) { + return codec; + } else { + return this.parent != null ? this.parent.getCodec(key) : null; + } + } + } + + public void putCodec(CodecKey key, Codec codec) { + this.codecs.put(key, codec); + } + + public Codec removeCodec(CodecKey key) { + return this.codecs.remove(key); + } + + public void putCodecSupplier(CodecKey key, Supplier> supplier) { + this.codecSuppliers.put(key, supplier); + } + + public Supplier> removeCodecSupplier(CodecKey key) { + return this.codecSuppliers.remove(key); + } +} diff --git a/src/com/hypixel/hytale/codec/store/StoredCodec.java b/src/com/hypixel/hytale/codec/store/StoredCodec.java new file mode 100644 index 0000000..23b5b8f --- /dev/null +++ b/src/com/hypixel/hytale/codec/store/StoredCodec.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.codec.store; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonValue; + +public class StoredCodec implements Codec { + private final CodecKey key; + + public StoredCodec(CodecKey key) { + this.key = key; + } + + @Override + public T decode(BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { + Codec codec = extraInfo.getCodecStore().getCodec(this.key); + if (codec == null) { + throw new IllegalArgumentException("Failed to find codec for " + this.key); + } else { + return codec.decode(bsonValue, extraInfo); + } + } + + @Override + public BsonValue encode(T t, @Nonnull ExtraInfo extraInfo) { + Codec codec = extraInfo.getCodecStore().getCodec(this.key); + if (codec == null) { + throw new IllegalArgumentException("Failed to find codec for " + this.key); + } else { + return codec.encode(t, extraInfo); + } + } + + @Nullable + @Override + public T decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + Codec codec = extraInfo.getCodecStore().getCodec(this.key); + if (codec == null) { + throw new IllegalArgumentException("Failed to find codec for " + this.key); + } else { + return codec.decodeJson(reader, extraInfo); + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + Codec codec = CodecStore.STATIC.getCodec(this.key); + if (codec == null) { + throw new IllegalArgumentException("Failed to find codec for " + this.key); + } else { + return context.refDefinition(codec); + } + } +} diff --git a/src/com/hypixel/hytale/codec/util/Documentation.java b/src/com/hypixel/hytale/codec/util/Documentation.java new file mode 100644 index 0000000..ad4f703 --- /dev/null +++ b/src/com/hypixel/hytale/codec/util/Documentation.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.codec.util; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import javax.annotation.Nullable; + +public class Documentation { + public Documentation() { + } + + public static String stripMarkdown(@Nullable String markdown) { + if (markdown == null) { + return null; + } else { + StringBuilder output = new StringBuilder(); + IntArrayList counts = new IntArrayList(); + + for (int i = 0; i < markdown.length(); i++) { + char c = markdown.charAt(i); + switch (c) { + case '*': + case '_': + int start = i; + boolean isEnding = i >= 1 && !Character.isWhitespace(markdown.charAt(i - 1)); + int targetCount = !counts.isEmpty() && isEnding ? counts.getInt(counts.size() - 1) : -1; + + while (i < markdown.length() && markdown.charAt(i) == c && i - start != targetCount) { + i++; + } + + int matchingCount = i - start; + if (!counts.isEmpty() && counts.getInt(counts.size() - 1) == matchingCount) { + if (!isEnding) { + output.append(String.valueOf(c).repeat(matchingCount)); + break; + } + + counts.removeInt(counts.size() - 1); + } else { + if (i < markdown.length() && Character.isWhitespace(markdown.charAt(i))) { + output.append(String.valueOf(c).repeat(matchingCount)); + output.append(markdown.charAt(i)); + break; + } + + counts.add(matchingCount); + } + + i--; + break; + default: + output.append(c); + } + } + + if (!counts.isEmpty()) { + throw new IllegalArgumentException("Unbalanced markdown formatting"); + } else { + return output.toString(); + } + } + } +} diff --git a/src/com/hypixel/hytale/codec/util/RawJsonReader.java b/src/com/hypixel/hytale/codec/util/RawJsonReader.java new file mode 100644 index 0000000..58360da --- /dev/null +++ b/src/com/hypixel/hytale/codec/util/RawJsonReader.java @@ -0,0 +1,1475 @@ +package com.hypixel.hytale.codec.util; + +import ch.randelshofer.fastdoubleparser.JavaDoubleParser; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.unsafe.UnsafeUtil; +import io.sentry.Sentry; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonArray; +import org.bson.BsonBoolean; +import org.bson.BsonDocument; +import org.bson.BsonDouble; +import org.bson.BsonNull; +import org.bson.BsonString; +import org.bson.BsonValue; +import sun.misc.Unsafe; + +public class RawJsonReader implements AutoCloseable { + public static final ThreadLocal READ_BUFFER = ThreadLocal.withInitial(() -> new char[131072]); + public static final int DEFAULT_CHAR_BUFFER_SIZE = 32768; + public static final int MIN_CHAR_BUFFER_READ = 16384; + public static final int BUFFER_GROWTH = 1048576; + private static final int UNMARKED = -1; + private int streamIndex; + @Nullable + private Reader in; + @Nullable + private char[] buffer; + private int bufferIndex; + private int bufferSize; + private int markIndex = -1; + private int markLine = -1; + private int markLineStart = -1; + private StringBuilder tempSb; + private int line; + private int lineStart; + public static final int ERROR_LINES_BUFFER = 10; + + public RawJsonReader(@Nonnull char[] preFilledBuffer) { + if (preFilledBuffer == null) { + throw new IllegalArgumentException("buffer can't be null!"); + } else { + this.in = null; + this.buffer = preFilledBuffer; + this.bufferIndex = 0; + this.streamIndex = 0; + this.bufferSize = preFilledBuffer.length; + } + } + + public RawJsonReader(Reader in, @Nonnull char[] buffer) { + if (buffer == null) { + throw new IllegalArgumentException("buffer can't be null!"); + } else { + this.in = in; + this.buffer = buffer; + this.bufferIndex = 0; + this.streamIndex = 0; + this.bufferSize = 0; + } + } + + public char[] getBuffer() { + return this.buffer; + } + + public int getBufferIndex() { + return this.bufferIndex; + } + + public int getBufferSize() { + return this.bufferSize; + } + + public int getLine() { + return this.line + 1; + } + + public int getColumn() { + return this.bufferIndex - this.lineStart + 1; + } + + private boolean ensure() throws IOException { + return this.ensure(1); + } + + private boolean ensure(int n) throws IOException { + boolean filled; + for (filled = false; this.bufferIndex + n > this.bufferSize; filled = true) { + if (!this.fill()) { + throw this.unexpectedEOF(); + } + } + + return filled; + } + + private boolean fill() throws IOException { + int dst; + int len; + if (this.markIndex <= -1) { + this.streamIndex = this.streamIndex + this.bufferIndex; + dst = 0; + len = this.buffer.length; + } else { + int spaceInBuffer = this.buffer.length - this.bufferIndex; + if (spaceInBuffer > 16384) { + dst = this.bufferIndex; + len = spaceInBuffer; + } else { + int delta = this.bufferIndex - this.markIndex; + if (this.markIndex > 16384) { + System.arraycopy(this.buffer, this.markIndex, this.buffer, 0, delta); + } else { + int newSize = this.bufferIndex + 1048576; + System.err.println("Reallocate: " + this.buffer.length + " to " + newSize); + char[] ncb = new char[newSize]; + System.arraycopy(this.buffer, this.markIndex, ncb, 0, delta); + this.buffer = ncb; + } + + this.streamIndex = this.streamIndex + this.markIndex; + this.markIndex = 0; + dst = delta; + this.bufferIndex = this.bufferSize = delta; + len = this.buffer.length - delta; + } + } + + if (this.in == null) { + return false; + } else { + int n = this.in.read(this.buffer, dst, len); + if (n > 0) { + this.bufferSize = dst + n; + this.bufferIndex = dst; + return true; + } else { + return false; + } + } + } + + public int peek() throws IOException { + return this.peek(0); + } + + public int peek(int n) throws IOException { + if (this.bufferIndex + n >= this.bufferSize) { + this.fill(); + if (this.bufferIndex + n >= this.bufferSize) { + return -1; + } + } + + return this.buffer[this.bufferIndex + n]; + } + + public int read() throws IOException { + if (this.bufferIndex >= this.bufferSize) { + this.fill(); + if (this.bufferIndex >= this.bufferSize) { + return -1; + } + } + + char c = this.buffer[this.bufferIndex++]; + if (c == '\n') { + this.line++; + this.lineStart = this.bufferIndex; + } + + return c; + } + + public long skip(long skip) throws IOException { + if (skip < 0L) { + int negativeBufferIndex = -this.bufferIndex; + if (skip < negativeBufferIndex) { + this.bufferIndex = 0; + return negativeBufferIndex; + } else { + this.bufferIndex = (int)(this.bufferIndex + skip); + return skip; + } + } else { + long haveSkipped = 0L; + + while (haveSkipped < skip) { + long charsInBuffer = this.bufferSize - this.bufferIndex; + long charsToSkip = skip - haveSkipped; + if (charsToSkip <= charsInBuffer) { + this.bufferIndex = (int)(this.bufferIndex + charsToSkip); + return skip; + } + + haveSkipped += charsInBuffer; + this.bufferIndex = this.bufferSize; + this.fill(); + if (this.bufferIndex >= this.bufferSize) { + break; + } + } + + return haveSkipped; + } + } + + public int findOffset(char value) throws IOException { + return this.findOffset(0, value); + } + + public int findOffset(int start, char value) throws IOException { + while (true) { + this.ensure(); + char c = this.buffer[this.bufferIndex + start]; + if (c == value) { + return start; + } + + start++; + } + } + + public void skipOrThrow(long n) throws IOException { + long skipped = this.skip(n); + if (skipped != n) { + throw new IOException("Failed to skip " + n + " char's!"); + } + } + + public boolean ready() throws IOException { + return this.buffer != null && this.bufferIndex < this.bufferSize || this.in.ready(); + } + + public boolean markSupported() { + return true; + } + + public void mark(int readAheadLimit) throws IOException { + this.mark(); + } + + public boolean isMarked() { + return this.markIndex >= 0; + } + + public void mark() throws IOException { + if (this.markIndex >= 0) { + throw new IOException("mark can't be used while already marked!"); + } else { + this.markIndex = this.bufferIndex; + this.markLine = this.line; + this.markLineStart = this.lineStart; + } + } + + public void unmark() { + this.markIndex = -1; + this.markLine = -1; + this.markLineStart = -1; + } + + public int getMarkDistance() { + return this.bufferIndex - this.markIndex; + } + + public char[] cloneMark() { + return Arrays.copyOfRange(this.buffer, this.markIndex, this.bufferIndex); + } + + public void reset() throws IOException { + if (this.markIndex < 0) { + throw new IOException("Stream not marked"); + } else { + this.bufferIndex = this.markIndex; + this.markIndex = -1; + this.line = this.markLine; + this.lineStart = this.markLineStart; + this.markLine = -1; + } + } + + @Override + public void close() throws IOException { + if (this.buffer != null) { + try { + if (this.in != null) { + this.in.close(); + } + } finally { + this.in = null; + this.buffer = null; + } + } + } + + public char[] closeAndTakeBuffer() throws IOException { + char[] buffer = this.buffer; + this.close(); + return buffer; + } + + public boolean peekFor(char consume) throws IOException { + this.ensure(); + return this.buffer[this.bufferIndex] == consume; + } + + public boolean tryConsume(char consume) throws IOException { + this.ensure(); + if (this.buffer[this.bufferIndex] == consume) { + this.bufferIndex++; + if (consume == '\n') { + this.line++; + this.lineStart = this.bufferIndex; + } + + return true; + } else { + return false; + } + } + + public boolean tryConsumeString(@Nonnull String str) throws IOException { + this.mark(); + if (this.tryConsume('"') && this.tryConsume(str) && this.tryConsume('"')) { + this.unmark(); + return true; + } else { + this.reset(); + return false; + } + } + + public boolean tryConsume(@Nonnull String str) throws IOException { + return this.tryConsume(str, 0); + } + + public boolean tryConsume(@Nonnull String str, int start) throws IOException { + while (start < str.length()) { + this.ensure(); + + while (start < str.length() && this.bufferIndex < this.bufferSize) { + char c = this.buffer[this.bufferIndex]; + if (c != str.charAt(start++)) { + return false; + } + + this.bufferIndex++; + if (c == '\n') { + this.line++; + this.lineStart = this.bufferIndex; + } + } + } + + return true; + } + + public int tryConsumeSome(@Nonnull String str, int start) throws IOException { + while (start < str.length()) { + this.ensure(); + + while (start < str.length() && this.bufferIndex < this.bufferSize) { + char c = this.buffer[this.bufferIndex]; + if (c != str.charAt(start)) { + return start; + } + + start++; + this.bufferIndex++; + if (c == '\n') { + this.line++; + this.lineStart = this.bufferIndex; + } + } + } + + return start; + } + + public void expect(char expect) throws IOException { + this.ensure(); + char read = this.buffer[this.bufferIndex++]; + if (read != expect) { + throw this.expecting(read, expect); + } else { + if (expect == '\n') { + this.line++; + this.lineStart = this.bufferIndex; + } + } + } + + public void expect(@Nonnull String str, int start) throws IOException { + this.ensure(str.length() - start); + + while (start < str.length()) { + char c = this.buffer[this.bufferIndex]; + if (c != str.charAt(start++)) { + throw this.expecting(c, str, start); + } + + this.bufferIndex++; + if (c == '\n') { + this.line++; + this.lineStart = this.bufferIndex; + } + } + } + + public boolean tryConsumeOrExpect(char consume, char expect) throws IOException { + this.ensure(); + char read = this.buffer[this.bufferIndex]; + if (read == consume) { + this.bufferIndex++; + if (consume == '\n') { + this.line++; + this.lineStart = this.bufferIndex; + } + + return true; + } else if (read == expect) { + this.bufferIndex++; + if (expect == '\n') { + this.line++; + this.lineStart = this.bufferIndex; + } + + return false; + } else { + throw this.expecting(read, expect); + } + } + + public void consumeWhiteSpace() throws IOException { + while (true) { + if (this.bufferIndex >= this.bufferSize) { + this.fill(); + if (this.bufferIndex >= this.bufferSize) { + return; + } + } + + while (this.bufferIndex < this.bufferSize) { + char ch = this.buffer[this.bufferIndex]; + switch (ch) { + case '\t': + case '\r': + case ' ': + this.bufferIndex++; + break; + case '\n': + this.bufferIndex++; + this.line++; + this.lineStart = this.bufferIndex; + break; + default: + if (!Character.isWhitespace(ch)) { + return; + } + + this.bufferIndex++; + } + } + } + } + + public void consumeIgnoreCase(@Nonnull String str, int start) throws IOException { + this.ensure(str.length() - start); + + while (start < str.length()) { + char c = this.buffer[this.bufferIndex]; + if (!equalsIgnoreCase(c, str.charAt(start++))) { + throw this.expecting(c, str, start); + } + + this.bufferIndex++; + if (c == '\n') { + this.line++; + this.lineStart = this.bufferIndex; + } + } + } + + @Nonnull + public String readString() throws IOException { + this.expect('"'); + return this.readRemainingString(); + } + + @Nonnull + public String readRemainingString() throws IOException { + if (this.tempSb == null) { + this.tempSb = new StringBuilder(1024); + } + + while (true) { + this.ensure(); + + while (this.bufferIndex < this.bufferSize) { + char read = this.buffer[this.bufferIndex++]; + switch (read) { + case '"': + String string = this.tempSb.toString(); + this.tempSb.setLength(0); + return string; + case '\\': + this.ensure(); + read = this.buffer[this.bufferIndex++]; + switch (read) { + case '"': + case '/': + case '\\': + this.tempSb.append(read); + continue; + case 'b': + this.tempSb.append('\b'); + continue; + case 'f': + this.tempSb.append('\f'); + continue; + case 'n': + this.tempSb.append('\n'); + continue; + case 'r': + this.tempSb.append('\r'); + continue; + case 't': + this.tempSb.append('\t'); + continue; + case 'u': + this.ensure(4); + read = this.buffer[this.bufferIndex++]; + int digit = Character.digit(read, 16); + if (digit == -1) { + throw this.expectingWhile(read, "HEX Digit 0-F", "reading string"); + } + + int hex = digit << 12; + read = this.buffer[this.bufferIndex++]; + digit = Character.digit(read, 16); + if (digit == -1) { + throw this.expectingWhile(read, "HEX Digit 0-F", "reading string"); + } + + hex |= digit << 8; + read = this.buffer[this.bufferIndex++]; + digit = Character.digit(read, 16); + if (digit == -1) { + throw this.expectingWhile(read, "HEX Digit 0-F", "reading string"); + } + + hex |= digit << 4; + read = this.buffer[this.bufferIndex++]; + digit = Character.digit(read, 16); + if (digit == -1) { + throw this.expectingWhile(read, "HEX Digit 0-F", "reading string"); + } + + hex |= digit; + this.tempSb.appendCodePoint(hex); + continue; + default: + throw this.expecting(read, "escape char"); + } + default: + if (Character.isISOControl(read)) { + throw this.unexpectedChar(read); + } + + this.tempSb.append(read); + } + } + } + } + + public void skipString() throws IOException { + this.expect('"'); + this.skipRemainingString(); + } + + public void skipRemainingString() throws IOException { + while (true) { + this.ensure(); + + while (this.bufferIndex < this.bufferSize) { + char read = this.buffer[this.bufferIndex++]; + switch (read) { + case '"': + return; + case '\\': + this.ensure(); + read = this.buffer[this.bufferIndex++]; + switch (read) { + case '"': + case '/': + case '\\': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + continue; + case 'u': + this.ensure(4); + read = this.buffer[this.bufferIndex++]; + int digit = Character.digit(read, 16); + if (digit == -1) { + throw this.expectingWhile(read, "HEX Digit 0-F", "skipping string"); + } + + read = this.buffer[this.bufferIndex++]; + digit = Character.digit(read, 16); + if (digit == -1) { + throw this.expectingWhile(read, "HEX Digit 0-F", "skipping string"); + } + + read = this.buffer[this.bufferIndex++]; + digit = Character.digit(read, 16); + if (digit == -1) { + throw this.expectingWhile(read, "HEX Digit 0-F", "skipping string"); + } + + read = this.buffer[this.bufferIndex++]; + digit = Character.digit(read, 16); + if (digit == -1) { + throw this.expectingWhile(read, "HEX Digit 0-F", "skipping string"); + } + continue; + default: + throw this.expecting(read, "escape char"); + } + default: + if (Character.isISOControl(read)) { + throw this.unexpectedChar(read); + } + } + } + } + } + + public long readStringPartAsLong(int count) throws IOException { + assert count > 0 && count <= 4; + + return UnsafeUtil.UNSAFE != null && this.bufferIndex + count <= this.bufferSize + ? this.readStringPartAsLongUnsafe(count) + : this.readStringPartAsLongSlow(count); + } + + protected long readStringPartAsLongSlow(int count) throws IOException { + this.ensure(count); + char c1 = this.buffer[this.bufferIndex++]; + if (count == 1) { + return c1; + } else { + char c2 = this.buffer[this.bufferIndex++]; + long value = c1 | (long)c2 << 16; + if (count == 2) { + return value; + } else { + char c3 = this.buffer[this.bufferIndex++]; + value |= (long)c3 << 32; + if (count == 3) { + return value; + } else { + char c4 = this.buffer[this.bufferIndex++]; + return value | (long)c4 << 48; + } + } + } + } + + protected long readStringPartAsLongUnsafe(int count) throws IOException { + this.ensure(count); + int offset = Unsafe.ARRAY_CHAR_BASE_OFFSET + Unsafe.ARRAY_CHAR_INDEX_SCALE * this.bufferIndex; + long value = UnsafeUtil.UNSAFE.getLong(this.buffer, offset); + this.bufferIndex += count; + long mask = count == 4 ? -1L : (1L << count * 16) - 1L; + return value & mask; + } + + public boolean readBooleanValue() throws IOException { + this.ensure(4); + char read = this.buffer[this.bufferIndex++]; + + return switch (read) { + case 'F', 'f' -> { + this.consumeIgnoreCase("false", 1); + yield false; + } + case 'T', 't' -> { + this.consumeIgnoreCase("true", 1); + yield true; + } + default -> throw this.expecting(read, "true' or 'false"); + }; + } + + public void skipBooleanValue() throws IOException { + this.readBooleanValue(); + } + + @Nullable + public Void readNullValue() throws IOException { + this.consumeIgnoreCase("null", 0); + return null; + } + + public void skipNullValue() throws IOException { + this.consumeIgnoreCase("null", 0); + } + + public double readDoubleValue() throws IOException { + int start = this.bufferIndex; + + while (true) { + if (this.bufferIndex >= this.bufferSize) { + this.fill(); + if (this.bufferIndex >= this.bufferSize) { + return JavaDoubleParser.parseDouble(this.buffer, start, this.bufferIndex - start); + } + } + + while (this.bufferIndex < this.bufferSize) { + char read = this.buffer[this.bufferIndex]; + switch (read) { + case '+': + case '-': + case '.': + case 'E': + case 'e': + this.bufferIndex++; + break; + default: + if (!Character.isDigit(read)) { + return JavaDoubleParser.parseDouble(this.buffer, start, this.bufferIndex - start); + } + + this.bufferIndex++; + } + } + } + } + + public void skipDoubleValue() throws IOException { + while (true) { + if (this.bufferIndex >= this.bufferSize) { + this.fill(); + if (this.bufferIndex >= this.bufferSize) { + return; + } + } + + while (this.bufferIndex < this.bufferSize) { + char read = this.buffer[this.bufferIndex]; + switch (read) { + case '+': + case '-': + case '.': + case 'E': + case 'e': + this.bufferIndex++; + break; + default: + if (!Character.isDigit(read)) { + return; + } + + this.bufferIndex++; + } + } + } + } + + public float readFloatValue() throws IOException { + return (float)this.readDoubleValue(); + } + + public void skipFloatValue() throws IOException { + this.skipDoubleValue(); + } + + public long readLongValue() throws IOException { + return this.readLongValue(10); + } + + public long readLongValue(int radix) throws IOException { + if (this.tempSb == null) { + this.tempSb = new StringBuilder(1024); + } + + while (true) { + if (this.bufferIndex >= this.bufferSize) { + this.fill(); + if (this.bufferIndex >= this.bufferSize) { + long value = Long.parseLong(this.tempSb, 0, this.tempSb.length(), radix); + this.tempSb.setLength(0); + return value; + } + } + + while (this.bufferIndex < this.bufferSize) { + char read = this.buffer[this.bufferIndex]; + switch (read) { + case '+': + case '-': + case '.': + case 'E': + case 'e': + this.tempSb.append(read); + this.bufferIndex++; + break; + default: + if (Character.digit(read, radix) < 0) { + long value = Long.parseLong(this.tempSb, 0, this.tempSb.length(), radix); + this.tempSb.setLength(0); + return value; + } + + this.tempSb.append(read); + this.bufferIndex++; + } + } + } + } + + public void skipLongValue() throws IOException { + this.skipLongValue(10); + } + + public void skipLongValue(int radix) throws IOException { + while (true) { + if (this.bufferIndex >= this.bufferSize) { + this.fill(); + if (this.bufferIndex >= this.bufferSize) { + return; + } + } + + while (this.bufferIndex < this.bufferSize) { + char read = this.buffer[this.bufferIndex]; + switch (read) { + case '+': + case '-': + case '.': + case 'E': + case 'e': + this.bufferIndex++; + break; + default: + if (Character.digit(read, radix) < 0) { + return; + } + + this.bufferIndex++; + } + } + } + } + + public int readIntValue() throws IOException { + return this.readIntValue(10); + } + + public int readIntValue(int radix) throws IOException { + return (int)this.readLongValue(radix); + } + + public byte readByteValue() throws IOException { + return this.readByteValue(10); + } + + public byte readByteValue(int radix) throws IOException { + return (byte)this.readLongValue(radix); + } + + public void skipIntValue() throws IOException { + this.skipLongValue(); + } + + public void skipIntValue(int radix) throws IOException { + this.skipLongValue(radix); + } + + public void skipObject() throws IOException { + this.expect('{'); + this.skipObjectContinued(); + } + + public void skipObjectContinued() throws IOException { + int count = 1; + + while (true) { + this.ensure(); + + while (this.bufferIndex < this.bufferSize) { + char read = this.buffer[this.bufferIndex++]; + switch (read) { + case '\n': + this.line++; + this.lineStart = this.bufferIndex; + break; + case '{': + count++; + break; + case '}': + if (--count == 0) { + return; + } + } + } + } + } + + public void skipArray() throws IOException { + this.expect('['); + this.skipArrayContinued(); + } + + public void skipArrayContinued() throws IOException { + int count = 1; + + while (true) { + this.ensure(); + + while (this.bufferIndex < this.bufferSize) { + char read = this.buffer[this.bufferIndex++]; + switch (read) { + case '\n': + this.line++; + this.lineStart = this.bufferIndex; + break; + case '[': + count++; + break; + case ']': + if (--count == 0) { + return; + } + } + } + } + } + + public void skipValue() throws IOException { + this.ensure(); + char read = this.buffer[this.bufferIndex]; + switch (read) { + case '"': + this.skipString(); + break; + case '+': + case '-': + this.skipDoubleValue(); + break; + case 'F': + case 'f': + this.consumeIgnoreCase("false", 0); + break; + case 'N': + case 'n': + this.skipNullValue(); + break; + case 'T': + case 't': + this.consumeIgnoreCase("true", 0); + break; + case '[': + this.skipArray(); + break; + case '{': + this.skipObject(); + break; + default: + if (!Character.isDigit(read)) { + throw this.unexpectedChar(read); + } + + this.skipDoubleValue(); + } + } + + @Nonnull + private IOException unexpectedEOF() { + return new IOException("Unexpected EOF!"); + } + + @Nonnull + private IOException unexpectedChar(char read) { + return new IOException("Unexpected character: " + Integer.toHexString(read) + ", '" + read + "'!"); + } + + @Nonnull + private IOException expecting(char read, char expect) { + return new IOException("Unexpected character: " + Integer.toHexString(read) + ", '" + read + "' expected '" + expect + "'!"); + } + + @Nonnull + private IOException expecting(char read, String expected) { + return new IOException("Unexpected character: " + Integer.toHexString(read) + ", '" + read + "' expected '" + expected + "'!"); + } + + @Nonnull + private IOException expectingWhile(char read, String expected, String reason) { + return new IOException("Unexpected character: " + Integer.toHexString(read) + ", '" + read + "' expected '" + expected + "' while " + reason + "!"); + } + + @Nonnull + private IOException expecting(char read, @Nonnull String expected, int index) { + return new IOException( + "Unexpected character: " + + Integer.toHexString(read) + + ", '" + + read + + "' when consuming string '" + + expected + + "' expected '" + + expected.substring(index - 1) + + "'!" + ); + } + + @Nonnull + @Override + public String toString() { + if (this.buffer == null) { + return "Closed RawJsonReader"; + } else { + StringBuilder s = new StringBuilder("Index: ") + .append(this.streamIndex + this.bufferIndex) + .append(", StreamIndex: ") + .append(this.streamIndex) + .append(", BufferIndex: ") + .append(this.bufferIndex) + .append(", BufferSize: ") + .append(this.bufferSize) + .append(", Line: ") + .append(this.line) + .append(", MarkIndex: ") + .append(this.markIndex) + .append(", MarkLine: ") + .append(this.markLine) + .append('\n'); + int lineStart = this.findLineStart(this.bufferIndex); + + int lineNumber; + for (lineNumber = this.line; lineStart > 0 && lineNumber > this.line - 10; lineNumber--) { + lineStart = this.findLineStart(lineStart); + } + + while (lineNumber < this.line) { + lineStart = this.appendLine(s, lineStart, lineNumber); + lineNumber++; + } + + for (int var4 = this.appendProblemLine(s, lineStart, this.line); var4 < this.bufferSize && lineNumber < this.line + 10; lineNumber++) { + var4 = this.appendLine(s, var4, lineNumber); + } + + return this.in == null ? "Buffer RawJsonReader: " + s : "Streamed RawJsonReader: " + s; + } + } + + private int findLineStart(int index) { + index--; + + while (index > 0 && this.buffer[index] != '\n') { + index--; + } + + return index; + } + + private int appendLine(@Nonnull StringBuilder sb, int index, int lineNumber) { + int lineStart = index + 1; + index++; + + while (index < this.bufferSize && this.buffer[index] != '\n') { + index++; + } + + sb.append("L").append(String.format("%3s", lineNumber)).append('|').append(this.buffer, lineStart, index - lineStart).append('\n'); + return index; + } + + private int appendProblemLine(@Nonnull StringBuilder sb, int index, int lineNumber) { + int lineStart = ++index; + + while (index < this.bufferSize && this.buffer[index] != '\n') { + index++; + } + + sb.append("L").append(String.format("%3s", lineNumber)).append('>').append(this.buffer, lineStart, index - lineStart).append('\n'); + sb.append(" |"); + sb.append("-".repeat(Math.max(0, this.bufferIndex - lineStart - 1))); + sb.append('^').append('\n'); + return index; + } + + @Nonnull + public static RawJsonReader fromRawString(String str) { + return fromJsonString("\"" + str + "\""); + } + + @Nonnull + public static RawJsonReader fromJsonString(@Nonnull String str) { + return fromBuffer(str.toCharArray()); + } + + @Nonnull + public static RawJsonReader fromPath(@Nonnull Path path, @Nonnull char[] buffer) throws IOException { + return new RawJsonReader(new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8), buffer); + } + + @Nonnull + public static RawJsonReader fromBuffer(@Nonnull char[] buffer) { + return new RawJsonReader(buffer); + } + + public static boolean equalsIgnoreCase(char c1, char c2) { + if (c1 == c2) { + return true; + } else { + char u1 = Character.toUpperCase(c1); + char u2 = Character.toUpperCase(c2); + return u1 == u2 || Character.toLowerCase(u1) == Character.toLowerCase(u2); + } + } + + @Deprecated + public static BsonDocument readBsonDocument(@Nonnull RawJsonReader reader) throws IOException { + reader.expect('{'); + StringBuilder sb = new StringBuilder("{"); + readBsonDocument0(reader, sb); + return BsonDocument.parse(sb.toString()); + } + + private static void readBsonDocument0(@Nonnull RawJsonReader reader, @Nonnull StringBuilder sb) throws IOException { + int count = 1; + + int read; + while ((read = reader.read()) != -1) { + sb.append((char)read); + switch (read) { + case 10: + reader.line++; + reader.lineStart = reader.bufferIndex; + break; + case 91: + readBsonArray0(reader, sb); + break; + case 123: + count++; + break; + case 125: + if (--count == 0) { + return; + } + } + } + + throw reader.unexpectedEOF(); + } + + @Deprecated + public static BsonArray readBsonArray(@Nonnull RawJsonReader reader) throws IOException { + reader.expect('['); + StringBuilder sb = new StringBuilder("["); + readBsonArray0(reader, sb); + return BsonArray.parse(sb.toString()); + } + + private static void readBsonArray0(@Nonnull RawJsonReader reader, @Nonnull StringBuilder sb) throws IOException { + int count = 1; + + int read; + while ((read = reader.read()) != -1) { + sb.append((char)read); + switch (read) { + case 10: + reader.line++; + reader.lineStart = reader.bufferIndex; + break; + case 91: + count++; + break; + case 93: + if (--count == 0) { + return; + } + break; + case 123: + readBsonDocument0(reader, sb); + } + } + + throw reader.unexpectedEOF(); + } + + @Deprecated + public static BsonValue readBsonValue(@Nonnull RawJsonReader reader) throws IOException { + int read = reader.peek(); + if (read == -1) { + throw reader.unexpectedEOF(); + } else { + return (BsonValue)(switch (read) { + case 34 -> new BsonString(reader.readString()); + case 43, 45 -> new BsonDouble(reader.readDoubleValue()); + case 70, 84, 102, 116 -> reader.readBooleanValue() ? BsonBoolean.TRUE : BsonBoolean.FALSE; + case 78, 110 -> { + reader.skipNullValue(); + yield BsonNull.VALUE; + } + case 91 -> readBsonArray(reader); + case 123 -> readBsonDocument(reader); + default -> { + if (!Character.isDigit(read)) { + throw reader.unexpectedChar((char)read); + } + + yield new BsonDouble(reader.readDoubleValue()); + } + }); + } + } + + public static boolean seekToKey(@Nonnull RawJsonReader reader, @Nonnull String search) throws IOException { + reader.consumeWhiteSpace(); + reader.expect('{'); + reader.consumeWhiteSpace(); + if (reader.tryConsume('}')) { + return false; + } else { + while (true) { + reader.expect('"'); + if (reader.tryConsume(search) && reader.tryConsume('"')) { + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + return true; + } + + reader.skipRemainingString(); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + reader.skipValue(); + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + return false; + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nullable + public static String seekToKeyFromObjectStart(@Nonnull RawJsonReader reader, @Nonnull String search1, @Nonnull String search2) throws IOException { + reader.consumeWhiteSpace(); + reader.expect('{'); + reader.consumeWhiteSpace(); + if (reader.tryConsume('}')) { + return null; + } else { + while (true) { + reader.expect('"'); + int search1Index = reader.tryConsumeSome(search1, 0); + if (search1Index == search1.length() && reader.tryConsume('"')) { + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + return search1; + } + + if (reader.tryConsume(search2, search1Index) && reader.tryConsume('"')) { + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + return search2; + } + + reader.skipRemainingString(); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + reader.skipValue(); + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + return null; + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nullable + public static String seekToKeyFromObjectContinued(@Nonnull RawJsonReader reader, @Nonnull String search1, @Nonnull String search2) throws IOException { + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + return null; + } else { + reader.consumeWhiteSpace(); + + while (true) { + reader.expect('"'); + int search1Index = reader.tryConsumeSome(search1, 0); + if (search1Index == search1.length() && reader.tryConsume('"')) { + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + return search1; + } + + if (reader.tryConsume(search2, search1Index) && reader.tryConsume('"')) { + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + return search2; + } + + reader.skipRemainingString(); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + reader.skipValue(); + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + return null; + } + + reader.consumeWhiteSpace(); + } + } + } + + public static void validateBsonDocument(@Nonnull RawJsonReader reader) throws IOException { + reader.expect('{'); + reader.consumeWhiteSpace(); + if (!reader.tryConsume('}')) { + while (true) { + reader.skipString(); + reader.consumeWhiteSpace(); + reader.expect(':'); + reader.consumeWhiteSpace(); + validateBsonValue(reader); + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect('}', ',')) { + return; + } + + reader.consumeWhiteSpace(); + } + } + } + + public static void validateBsonArray(@Nonnull RawJsonReader reader) throws IOException { + reader.expect('['); + reader.consumeWhiteSpace(); + if (!reader.tryConsume(']')) { + while (true) { + validateBsonValue(reader); + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect(']', ',')) { + return; + } + + reader.consumeWhiteSpace(); + } + } + } + + public static void validateBsonValue(@Nonnull RawJsonReader reader) throws IOException { + int read = reader.peek(); + if (read == -1) { + throw reader.unexpectedEOF(); + } else { + switch (read) { + case 34: + reader.skipString(); + break; + case 43: + case 45: + reader.readDoubleValue(); + break; + case 70: + case 84: + case 102: + case 116: + reader.readBooleanValue(); + break; + case 78: + case 110: + reader.skipNullValue(); + break; + case 91: + validateBsonArray(reader); + break; + case 123: + validateBsonDocument(reader); + break; + default: + if (Character.isDigit(read)) { + reader.readDoubleValue(); + return; + } + + throw reader.unexpectedChar((char)read); + } + } + } + + @Nullable + public static T readSync(@Nonnull Path path, @Nonnull Codec codec, @Nonnull HytaleLogger logger) throws IOException { + char[] buffer = READ_BUFFER.get(); + RawJsonReader reader = fromPath(path, buffer); + + Object var7; + try { + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + T value = codec.decodeJson(reader, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(logger); + var7 = value; + } finally { + char[] newBuffer = reader.closeAndTakeBuffer(); + if (newBuffer.length > buffer.length) { + READ_BUFFER.set(newBuffer); + } + } + + return (T)var7; + } + + @Nullable + public static T readSyncWithBak(@Nonnull Path path, @Nonnull Codec codec, @Nonnull HytaleLogger logger) { + try { + return readSync(path, codec, logger); + } catch (IOException var8) { + Path backupPath = path.resolveSibling(path.getFileName() + ".bak"); + if (var8 instanceof NoSuchFileException && !Files.exists(backupPath)) { + return null; + } else { + if (Sentry.isEnabled()) { + Sentry.captureException(var8); + } + + logger.at(Level.SEVERE).withCause(var8).log("Failed to load from primary file %s, trying backup file", path); + + try { + T value = readSync(backupPath, codec, logger); + logger.at(Level.WARNING).log("Loaded from backup file %s after primary file %s failed to load", backupPath, path); + return value; + } catch (NoSuchFileException var6) { + return null; + } catch (IOException var7) { + logger.at(Level.WARNING).withCause(var8).log("Failed to load from both %s and backup file %s", path, backupPath); + return null; + } + } + } + } +} diff --git a/src/com/hypixel/hytale/codec/validation/LateValidator.java b/src/com/hypixel/hytale/codec/validation/LateValidator.java new file mode 100644 index 0000000..c825581 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/LateValidator.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.codec.validation; + +import com.hypixel.hytale.codec.ExtraInfo; + +public interface LateValidator extends Validator { + void acceptLate(T var1, ValidationResults var2, ExtraInfo var3); +} diff --git a/src/com/hypixel/hytale/codec/validation/LegacyValidator.java b/src/com/hypixel/hytale/codec/validation/LegacyValidator.java new file mode 100644 index 0000000..14fcd37 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/LegacyValidator.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.codec.validation; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; + +@Deprecated(forRemoval = true) +public interface LegacyValidator extends Validator { + @Override + void accept(T var1, ValidationResults var2); + + @Override + default void updateSchema(SchemaContext context, Schema target) { + System.err.println("updateSchema: " + this.getClass().getSimpleName()); + } +} diff --git a/src/com/hypixel/hytale/codec/validation/ThrowingValidationResults.java b/src/com/hypixel/hytale/codec/validation/ThrowingValidationResults.java new file mode 100644 index 0000000..b9052c4 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/ThrowingValidationResults.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.codec.validation; + +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.exception.CodecValidationException; +import com.hypixel.hytale.logger.HytaleLogger; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class ThrowingValidationResults extends ValidationResults { + public ThrowingValidationResults(ExtraInfo extraInfo) { + super(extraInfo); + } + + @Override + public void add(@Nonnull ValidationResults.ValidationResult result) { + StringBuilder sb = new StringBuilder("Failed to validate asset!\n"); + this.extraInfo.appendDetailsTo(sb); + sb.append("Key: ").append(this.extraInfo.peekKey()).append("\n"); + sb.append("Results:\n"); + boolean failed = result.appendResult(sb); + if (failed) { + throw new CodecValidationException(sb.toString()); + } else { + HytaleLogger.getLogger().at(Level.WARNING).log(sb.toString()); + } + } + + @Nonnull + @Override + public String toString() { + return "ThrowingValidationResults{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/codec/validation/ValidatableCodec.java b/src/com/hypixel/hytale/codec/validation/ValidatableCodec.java new file mode 100644 index 0000000..67baa52 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/ValidatableCodec.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.codec.validation; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.WrappedCodec; +import java.util.Set; + +public interface ValidatableCodec extends Codec { + void validate(T var1, ExtraInfo var2); + + void validateDefaults(ExtraInfo var1, Set> var2); + + static void validateDefaults(Codec codec, ExtraInfo extraInfo, Set> tested) { + while (true) { + if (codec instanceof WrappedCodec wrappedCodec) { + codec = wrappedCodec.getChildCodec(); + if (codec != null) { + continue; + } + } else if (codec instanceof ValidatableCodec validatableCodec) { + validatableCodec.validateDefaults(extraInfo, tested); + } + + return; + } + } +} diff --git a/src/com/hypixel/hytale/codec/validation/ValidationResults.java b/src/com/hypixel/hytale/codec/validation/ValidationResults.java new file mode 100644 index 0000000..6f74bf2 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/ValidationResults.java @@ -0,0 +1,154 @@ +package com.hypixel.hytale.codec.validation; + +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.exception.CodecValidationException; +import com.hypixel.hytale.logger.HytaleLogger; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ValidationResults { + protected final ExtraInfo extraInfo; + @Nullable + protected List validatorExceptions; + @Nullable + protected List results; + + public ValidationResults(ExtraInfo extraInfo) { + this.extraInfo = extraInfo; + } + + public ExtraInfo getExtraInfo() { + return this.extraInfo; + } + + public void fail(String reason) { + this.add(ValidationResults.ValidationResult.fail(reason)); + } + + public void warn(String reason) { + this.add(ValidationResults.ValidationResult.warn(reason)); + } + + public void add(ValidationResults.ValidationResult result) { + if (this.results == null) { + this.results = new ObjectArrayList<>(); + } + + this.results.add(result); + } + + public void _processValidationResults() { + if (this.results != null && !this.results.isEmpty()) { + for (ValidationResults.ValidationResult validationResult : this.results) { + ValidationResults.Result result = validationResult.result; + if (result == ValidationResults.Result.WARNING || result == ValidationResults.Result.FAIL) { + if (this.validatorExceptions == null) { + this.validatorExceptions = new ObjectArrayList<>(); + } + + this.validatorExceptions + .add( + new ValidationResults.ValidatorResultsHolder( + this.extraInfo.peekKey(), this.extraInfo.peekLine(), this.extraInfo.peekColumn(), new ObjectArrayList<>(this.results) + ) + ); + break; + } + } + + this.results.clear(); + } + } + + public void logOrThrowValidatorExceptions(@Nonnull HytaleLogger logger) { + this.logOrThrowValidatorExceptions(logger, "Failed to validate asset!\n"); + } + + public void logOrThrowValidatorExceptions(@Nonnull HytaleLogger logger, @Nonnull String msg) { + if (this.validatorExceptions != null && !this.validatorExceptions.isEmpty()) { + StringBuilder sb = new StringBuilder(msg); + this.extraInfo.appendDetailsTo(sb); + boolean failed = false; + + for (ValidationResults.ValidatorResultsHolder holder : this.validatorExceptions) { + if (holder.key != null && !holder.key.isEmpty()) { + sb.append("Key: ").append(holder.key).append("\n"); + } + + sb.append("Results:\n"); + + for (ValidationResults.ValidationResult result : holder.results) { + failed |= result.appendResult(sb); + } + } + + if (failed) { + throw new CodecValidationException(sb.toString()); + } else { + logger.at(Level.WARNING).log(sb.toString()); + this.validatorExceptions.clear(); + } + } + } + + public boolean hasFailed() { + if (this.results == null) { + return false; + } else { + for (ValidationResults.ValidationResult res : this.results) { + if (res.result() == ValidationResults.Result.FAIL) { + return true; + } + } + + return false; + } + } + + @Nullable + public List getResults() { + return this.results == null ? null : this.results; + } + + public void setResults(@Nullable List results) { + this.results = results; + } + + @Nonnull + @Override + public String toString() { + return "ValidationResults{results=" + this.results + "}"; + } + + public static enum Result { + SUCCESS, + WARNING, + FAIL; + + private Result() { + } + } + + public record ValidationResult(ValidationResults.Result result, String reason) { + public boolean appendResult(@Nonnull StringBuilder sb) { + sb.append("\t").append(this.result).append(": ").append(this.reason).append("\n"); + return this.result == ValidationResults.Result.FAIL; + } + + @Nonnull + public static ValidationResults.ValidationResult fail(String reason) { + return new ValidationResults.ValidationResult(ValidationResults.Result.FAIL, reason); + } + + @Nonnull + public static ValidationResults.ValidationResult warn(String reason) { + return new ValidationResults.ValidationResult(ValidationResults.Result.WARNING, reason); + } + } + + protected record ValidatorResultsHolder(String key, int line, int column, List results) { + } +} diff --git a/src/com/hypixel/hytale/codec/validation/Validator.java b/src/com/hypixel/hytale/codec/validation/Validator.java new file mode 100644 index 0000000..b071e6c --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/Validator.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.codec.validation; + +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; + +public interface Validator extends BiConsumer { + void accept(T var1, ValidationResults var2); + + void updateSchema(SchemaContext var1, Schema var2); + + @Nonnull + default LateValidator late() { + final Validator current = this; + return new LateValidator() { + @Override + public void accept(T t, ValidationResults results) { + } + + @Override + public void acceptLate(T t, ValidationResults results, ExtraInfo extraInfo) { + current.accept(t, results); + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + current.updateSchema(context, target); + } + }; + } +} diff --git a/src/com/hypixel/hytale/codec/validation/ValidatorCache.java b/src/com/hypixel/hytale/codec/validation/ValidatorCache.java new file mode 100644 index 0000000..f4d8e33 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/ValidatorCache.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.codec.validation; + +import com.hypixel.hytale.codec.validation.validator.ArrayValidator; +import com.hypixel.hytale.codec.validation.validator.MapKeyValidator; +import com.hypixel.hytale.codec.validation.validator.MapValueValidator; +import javax.annotation.Nonnull; + +public class ValidatorCache { + private final Validator validator; + private ArrayValidator arrayValidator; + private ArrayValidator arrayofArrayValidator; + private MapKeyValidator mapKeyValidator; + private MapKeyValidator mapArrayKeyValidator; + private MapValueValidator mapValueValidator; + private MapValueValidator mapArrayValueValidator; + + public ValidatorCache(Validator validator) { + this.validator = validator; + } + + public Validator getValidator() { + return this.validator; + } + + @Nonnull + public ArrayValidator getArrayValidator() { + if (this.arrayValidator == null) { + this.arrayValidator = new ArrayValidator<>(this.getValidator()); + } + + return this.arrayValidator; + } + + @Nonnull + public ArrayValidator getArrayOfArrayValidator() { + if (this.arrayofArrayValidator == null) { + this.arrayofArrayValidator = new ArrayValidator(this.getArrayValidator()); + } + + return this.arrayofArrayValidator; + } + + @Nonnull + public MapKeyValidator getMapKeyValidator() { + if (this.mapKeyValidator == null) { + this.mapKeyValidator = new MapKeyValidator<>(this.getValidator()); + } + + return this.mapKeyValidator; + } + + @Nonnull + public MapKeyValidator getMapArrayKeyValidator() { + if (this.mapArrayKeyValidator == null) { + this.mapArrayKeyValidator = new MapKeyValidator(this.getArrayValidator()); + } + + return this.mapArrayKeyValidator; + } + + @Nonnull + public MapValueValidator getMapValueValidator() { + if (this.mapValueValidator == null) { + this.mapValueValidator = new MapValueValidator<>(this.getValidator()); + } + + return this.mapValueValidator; + } + + @Nonnull + public MapValueValidator getMapArrayValueValidator() { + if (this.mapArrayValueValidator == null) { + this.mapArrayValueValidator = new MapValueValidator(this.getArrayValidator()); + } + + return this.mapArrayValueValidator; + } +} diff --git a/src/com/hypixel/hytale/codec/validation/Validators.java b/src/com/hypixel/hytale/codec/validation/Validators.java new file mode 100644 index 0000000..d7101a9 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/Validators.java @@ -0,0 +1,165 @@ +package com.hypixel.hytale.codec.validation; + +import com.hypixel.hytale.codec.validation.validator.ArraySizeRangeValidator; +import com.hypixel.hytale.codec.validation.validator.ArraySizeValidator; +import com.hypixel.hytale.codec.validation.validator.ArrayValidator; +import com.hypixel.hytale.codec.validation.validator.DeprecatedValidator; +import com.hypixel.hytale.codec.validation.validator.DoubleArraySizeValidator; +import com.hypixel.hytale.codec.validation.validator.EqualValidator; +import com.hypixel.hytale.codec.validation.validator.IntArraySizeValidator; +import com.hypixel.hytale.codec.validation.validator.ListValidator; +import com.hypixel.hytale.codec.validation.validator.NonEmptyArrayValidator; +import com.hypixel.hytale.codec.validation.validator.NonEmptyDoubleArrayValidator; +import com.hypixel.hytale.codec.validation.validator.NonEmptyFloatArrayValidator; +import com.hypixel.hytale.codec.validation.validator.NonEmptyMapValidator; +import com.hypixel.hytale.codec.validation.validator.NonEmptyStringValidator; +import com.hypixel.hytale.codec.validation.validator.NonNullValidator; +import com.hypixel.hytale.codec.validation.validator.NotEqualValidator; +import com.hypixel.hytale.codec.validation.validator.OrValidator; +import com.hypixel.hytale.codec.validation.validator.RangeValidator; +import com.hypixel.hytale.codec.validation.validator.RequiredMapKeysValidator; +import com.hypixel.hytale.codec.validation.validator.SequentialDoubleArrayValidator; +import com.hypixel.hytale.codec.validation.validator.UniqueInArrayValidator; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +public class Validators { + public Validators() { + } + + @Nonnull + public static DeprecatedValidator deprecated() { + return (DeprecatedValidator)DeprecatedValidator.INSTANCE; + } + + @Nonnull + public static Validator nonNull() { + return (Validator)NonNullValidator.INSTANCE; + } + + @Nonnull + public static ArrayValidator nonNullArrayElements() { + return new ArrayValidator<>(nonNull()); + } + + @Nonnull + public static Validator nonEmptyString() { + return NonEmptyStringValidator.INSTANCE; + } + + @Nonnull + public static Validator nonEmptyArray() { + return (Validator)NonEmptyArrayValidator.INSTANCE; + } + + @Nonnull + public static Validator> nonEmptyMap() { + return NonEmptyMapValidator.INSTANCE; + } + + @Nonnull + public static Validator uniqueInArray() { + return (Validator)UniqueInArrayValidator.INSTANCE; + } + + @Nonnull + public static Validator> requiredMapKeysValidator(T[] array) { + return new RequiredMapKeysValidator<>(array); + } + + @Nonnull + public static > Validator greaterThan(T greaterThan) { + return new RangeValidator<>(greaterThan, null, false); + } + + @Nonnull + public static > Validator greaterThanOrEqual(T greaterThan) { + return new RangeValidator<>(greaterThan, null, true); + } + + @Nonnull + public static > Validator lessThan(T lessThan) { + return new RangeValidator<>(null, lessThan, false); + } + + @Nonnull + public static > Validator insideRange(T greaterthan, T lessThan) { + return new RangeValidator<>(greaterthan, lessThan, false); + } + + @Nonnull + public static > Validator min(T min) { + return new RangeValidator<>(min, null, true); + } + + @Nonnull + public static > Validator max(T max) { + return new RangeValidator<>(null, max, true); + } + + @Nonnull + public static > Validator range(T min, T max) { + return new RangeValidator<>(min, max, true); + } + + @Nonnull + public static Validator arraySizeRange(int min, int max) { + return new ArraySizeRangeValidator<>(min, max); + } + + @Nonnull + public static Validator arraySize(int size) { + return new ArraySizeValidator<>(size); + } + + @Nonnull + public static Validator intArraySize(int size) { + return new IntArraySizeValidator(size); + } + + @Nonnull + public static Validator doubleArraySize(int size) { + return new DoubleArraySizeValidator(size); + } + + @Nonnull + public static > Validator equal(@Nonnull T value) { + return new EqualValidator<>(value); + } + + @Nonnull + public static > Validator notEqual(@Nonnull T value) { + return new NotEqualValidator<>(value); + } + + @Nonnull + public static Validator nonEmptyDoubleArray() { + return NonEmptyDoubleArrayValidator.INSTANCE; + } + + @Nonnull + public static Validator nonEmptyFloatArray() { + return NonEmptyFloatArrayValidator.INSTANCE; + } + + @Nonnull + public static Validator monotonicSequentialDoubleArrayValidator() { + return SequentialDoubleArrayValidator.NEQ_INSTANCE; + } + + @Nonnull + public static Validator weaklyMonotonicSequentialDoubleArrayValidator() { + return SequentialDoubleArrayValidator.ALLOW_EQ_INSTANCE; + } + + @Nonnull + public static Validator or(Validator... validators) { + return new OrValidator<>(validators); + } + + @Nonnull + public static Validator> listItem(Validator validator) { + return new ListValidator<>(validator); + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/ArraySizeRangeValidator.java b/src/com/hypixel/hytale/codec/validation/validator/ArraySizeRangeValidator.java new file mode 100644 index 0000000..63618f6 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/ArraySizeRangeValidator.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import javax.annotation.Nonnull; + +public class ArraySizeRangeValidator implements Validator { + private int min; + private int max; + + public ArraySizeRangeValidator(int min, int max) { + this.min = min; + this.max = max; + } + + public void accept(@Nonnull T[] array, @Nonnull ValidationResults results) { + if (array.length < this.min || array.length > this.max) { + results.fail(String.format("Array size is invalid! Was %s, expected between %s and %s", array.length, this.min, this.max)); + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + ArraySchema arr = (ArraySchema)target; + arr.setMinItems(this.min); + arr.setMaxItems(this.max); + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/ArraySizeValidator.java b/src/com/hypixel/hytale/codec/validation/validator/ArraySizeValidator.java new file mode 100644 index 0000000..3056126 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/ArraySizeValidator.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import javax.annotation.Nonnull; + +public class ArraySizeValidator implements Validator { + private final int size; + + public ArraySizeValidator(int size) { + this.size = size; + } + + public void accept(@Nonnull T[] array, @Nonnull ValidationResults results) { + if (array.length != this.size) { + results.fail(String.format("Array size is invalid! Was %s, expected %s", array.length, this.size)); + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + ArraySchema arr = (ArraySchema)target; + arr.setMinItems(this.size); + arr.setMaxItems(this.size); + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/ArrayValidator.java b/src/com/hypixel/hytale/codec/validation/validator/ArrayValidator.java new file mode 100644 index 0000000..751a7da --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/ArrayValidator.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import javax.annotation.Nullable; + +public class ArrayValidator implements Validator { + private Validator validator; + + public ArrayValidator(Validator validator) { + this.validator = validator; + } + + @Deprecated(forRemoval = true) + public ArrayValidator(LegacyValidator validator) { + this.validator = validator; + } + + public Validator getValidator() { + return this.validator; + } + + public void accept(@Nullable T[] ts, ValidationResults results) { + if (ts != null) { + for (T t : ts) { + this.validator.accept(t, results); + } + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + if (!(target instanceof ArraySchema)) { + throw new IllegalArgumentException(); + } else { + Schema item = (Schema)((ArraySchema)target).getItems(); + this.validator.updateSchema(context, item); + } + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/DeprecatedValidator.java b/src/com/hypixel/hytale/codec/validation/validator/DeprecatedValidator.java new file mode 100644 index 0000000..49c7dea --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/DeprecatedValidator.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.codec.validation.ValidationResults; +import javax.annotation.Nonnull; + +public class DeprecatedValidator implements LegacyValidator { + public static final DeprecatedValidator INSTANCE = new DeprecatedValidator(); + + private DeprecatedValidator() { + } + + @Override + public void accept(T t, @Nonnull ValidationResults results) { + results.warn("This field is deprecated and will be removed in the future!"); + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/DoubleArraySizeValidator.java b/src/com/hypixel/hytale/codec/validation/validator/DoubleArraySizeValidator.java new file mode 100644 index 0000000..42799d2 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/DoubleArraySizeValidator.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import javax.annotation.Nonnull; + +public class DoubleArraySizeValidator implements Validator { + private final int size; + + public DoubleArraySizeValidator(int size) { + this.size = size; + } + + public void accept(@Nonnull double[] array, @Nonnull ValidationResults results) { + if (array.length != this.size) { + results.fail(String.format("Array size is invalid! Was %s, expected %s", array.length, this.size)); + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + ArraySchema arr = (ArraySchema)target; + arr.setMinItems(this.size); + arr.setMaxItems(this.size); + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/DoubleArrayValidator.java b/src/com/hypixel/hytale/codec/validation/validator/DoubleArrayValidator.java new file mode 100644 index 0000000..fb766fa --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/DoubleArrayValidator.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import javax.annotation.Nonnull; + +public class DoubleArrayValidator implements Validator { + private Validator validator; + + public DoubleArrayValidator(Validator validator) { + this.validator = validator; + } + + public void accept(@Nonnull double[] ds, ValidationResults results) { + for (double d : ds) { + this.validator.accept(d, results); + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + if (!(target instanceof ArraySchema)) { + throw new IllegalArgumentException(); + } else { + Schema item = (Schema)((ArraySchema)target).getItems(); + this.validator.updateSchema(context, item); + } + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/EqualValidator.java b/src/com/hypixel/hytale/codec/validation/validator/EqualValidator.java new file mode 100644 index 0000000..0d20494 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/EqualValidator.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.IntegerSchema; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import com.hypixel.hytale.logger.HytaleLogger; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EqualValidator> implements Validator { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private final T value; + + public EqualValidator(@Nonnull T value) { + this.value = value; + } + + public void accept(@Nullable T o, @Nonnull ValidationResults results) { + if (o != null && this.value.compareTo(o) != 0) { + results.fail("Provided value must be equal to " + this.value); + } + } + + @Override + public void updateSchema(SchemaContext context, @Nonnull Schema target) { + if (target.getAllOf() != null) { + throw new IllegalArgumentException(); + } else { + if (target instanceof StringSchema && this.value instanceof String) { + ((StringSchema)target).setConst((String)this.value); + } else if (target instanceof IntegerSchema && this.value instanceof Number) { + ((IntegerSchema)target).setConst(((Number)this.value).intValue()); + } else if (target instanceof NumberSchema && this.value instanceof Number) { + ((NumberSchema)target).setConst(((Number)this.value).doubleValue()); + } else { + LOGGER.at(Level.WARNING).log("Cannot compare " + this.value.getClass() + " with " + target.getClass()); + } + } + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/FloatArrayValidator.java b/src/com/hypixel/hytale/codec/validation/validator/FloatArrayValidator.java new file mode 100644 index 0000000..e515c4a --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/FloatArrayValidator.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import javax.annotation.Nullable; + +public class FloatArrayValidator implements Validator { + private final Validator validator; + + public FloatArrayValidator(Validator validator) { + this.validator = validator; + } + + public void accept(@Nullable float[] floats, ValidationResults results) { + if (floats != null) { + for (float t : floats) { + this.validator.accept(t, results); + } + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + if (!(target instanceof ArraySchema)) { + throw new IllegalArgumentException(); + } else { + Schema item = (Schema)((ArraySchema)target).getItems(); + this.validator.updateSchema(context, item); + } + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/IntArraySizeValidator.java b/src/com/hypixel/hytale/codec/validation/validator/IntArraySizeValidator.java new file mode 100644 index 0000000..1314a9b --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/IntArraySizeValidator.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import javax.annotation.Nonnull; + +public class IntArraySizeValidator implements Validator { + private int size; + + public IntArraySizeValidator(int size) { + this.size = size; + } + + public void accept(@Nonnull int[] array, @Nonnull ValidationResults results) { + if (array.length != this.size) { + results.fail(String.format("Array size is invalid! Was %s, expected %s", array.length, this.size)); + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + ArraySchema arr = (ArraySchema)target; + arr.setMinItems(this.size); + arr.setMaxItems(this.size); + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/IntArrayValidator.java b/src/com/hypixel/hytale/codec/validation/validator/IntArrayValidator.java new file mode 100644 index 0000000..820056d --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/IntArrayValidator.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import javax.annotation.Nonnull; + +public class IntArrayValidator implements Validator { + private Validator validator; + + public IntArrayValidator(Validator validator) { + this.validator = validator; + } + + public void accept(@Nonnull int[] is, ValidationResults results) { + for (int i : is) { + this.validator.accept(i, results); + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + if (!(target instanceof ArraySchema)) { + throw new IllegalArgumentException(); + } else { + Schema item = (Schema)((ArraySchema)target).getItems(); + this.validator.updateSchema(context, item); + } + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/ListValidator.java b/src/com/hypixel/hytale/codec/validation/validator/ListValidator.java new file mode 100644 index 0000000..1599103 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/ListValidator.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import java.util.List; +import javax.annotation.Nonnull; + +public class ListValidator implements LegacyValidator> { + private Validator validator; + + public ListValidator(Validator validator) { + this.validator = validator; + } + + public void accept(@Nonnull List ts, ValidationResults results) { + for (T t : ts) { + this.validator.accept(t, results); + } + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/MapKeyValidator.java b/src/com/hypixel/hytale/codec/validation/validator/MapKeyValidator.java new file mode 100644 index 0000000..c3870d3 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/MapKeyValidator.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import java.util.Map; +import javax.annotation.Nonnull; + +public class MapKeyValidator implements Validator> { + private Validator key; + + public MapKeyValidator(Validator key) { + this.key = key; + } + + public Validator getKeyValidator() { + return this.key; + } + + public void accept(@Nonnull Map map, ValidationResults results) { + for (K k : map.keySet()) { + this.key.accept(k, results); + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + if (target instanceof ObjectSchema obj) { + StringSchema names = obj.getPropertyNames(); + if (names == null) { + names = new StringSchema(); + obj.setPropertyNames(names); + } + + this.key.updateSchema(context, names); + } else { + throw new IllegalArgumentException(); + } + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/MapValidator.java b/src/com/hypixel/hytale/codec/validation/validator/MapValidator.java new file mode 100644 index 0000000..550761b --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/MapValidator.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class MapValidator implements Validator> { + private Validator key; + private Validator value; + + public MapValidator(Validator key, Validator value) { + this.key = key; + this.value = value; + } + + public void accept(@Nonnull Map map, ValidationResults results) { + for (Entry entry : map.entrySet()) { + this.key.accept(entry.getKey(), results); + this.value.accept(entry.getValue(), results); + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + if (!(target instanceof ObjectSchema obj)) { + throw new IllegalArgumentException(); + } else { + if (obj.getProperties() != null) { + for (Schema val : obj.getProperties().values()) { + this.value.updateSchema(context, val); + } + } + + if (obj.getAdditionalProperties() instanceof Schema) { + this.value.updateSchema(context, (Schema)obj.getAdditionalProperties()); + } + + StringSchema names = obj.getPropertyNames(); + if (names == null) { + names = new StringSchema(); + obj.setPropertyNames(names); + } + + this.key.updateSchema(context, names); + } + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/MapValueValidator.java b/src/com/hypixel/hytale/codec/validation/validator/MapValueValidator.java new file mode 100644 index 0000000..0fa3b41 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/MapValueValidator.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import java.util.Map; +import javax.annotation.Nonnull; + +public class MapValueValidator implements Validator> { + private Validator value; + + public MapValueValidator(Validator value) { + this.value = value; + } + + public Validator getValueValidator() { + return this.value; + } + + public void accept(@Nonnull Map map, ValidationResults results) { + for (V v : map.values()) { + this.value.accept(v, results); + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + if (!(target instanceof ObjectSchema obj)) { + throw new IllegalArgumentException(); + } else { + if (obj.getProperties() != null) { + for (Schema val : obj.getProperties().values()) { + this.value.updateSchema(context, val); + } + } + + if (obj.getAdditionalProperties() instanceof Schema) { + this.value.updateSchema(context, (Schema)obj.getAdditionalProperties()); + } + } + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/NonEmptyArrayValidator.java b/src/com/hypixel/hytale/codec/validation/validator/NonEmptyArrayValidator.java new file mode 100644 index 0000000..e8eaf8d --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/NonEmptyArrayValidator.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NonEmptyArrayValidator extends NonNullValidator { + public static final NonEmptyArrayValidator INSTANCE = new NonEmptyArrayValidator(); + + private NonEmptyArrayValidator() { + } + + public void accept(@Nullable T[] t, @Nonnull ValidationResults results) { + if (t == null || t.length == 0) { + results.fail("Array can't be empty!"); + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + ArraySchema arr = (ArraySchema)target; + arr.setMinItems(1); + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/NonEmptyDoubleArrayValidator.java b/src/com/hypixel/hytale/codec/validation/validator/NonEmptyDoubleArrayValidator.java new file mode 100644 index 0000000..30179df --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/NonEmptyDoubleArrayValidator.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NonEmptyDoubleArrayValidator implements Validator { + public static final NonEmptyDoubleArrayValidator INSTANCE = new NonEmptyDoubleArrayValidator(); + + private NonEmptyDoubleArrayValidator() { + } + + public void accept(@Nullable double[] doubles, @Nonnull ValidationResults results) { + if (doubles == null || doubles.length == 0) { + results.fail("Array can't be empty!"); + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + ArraySchema arr = (ArraySchema)target; + arr.setMinItems(1); + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/NonEmptyFloatArrayValidator.java b/src/com/hypixel/hytale/codec/validation/validator/NonEmptyFloatArrayValidator.java new file mode 100644 index 0000000..39d8a95 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/NonEmptyFloatArrayValidator.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NonEmptyFloatArrayValidator implements Validator { + public static final NonEmptyFloatArrayValidator INSTANCE = new NonEmptyFloatArrayValidator(); + + private NonEmptyFloatArrayValidator() { + } + + public void accept(@Nullable float[] floats, @Nonnull ValidationResults results) { + if (floats == null || floats.length == 0) { + results.fail("Array can't be empty!"); + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + ArraySchema arr = (ArraySchema)target; + arr.setMinItems(1); + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/NonEmptyMapValidator.java b/src/com/hypixel/hytale/codec/validation/validator/NonEmptyMapValidator.java new file mode 100644 index 0000000..287bb28 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/NonEmptyMapValidator.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.validation.ValidationResults; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NonEmptyMapValidator extends NonNullValidator> { + public static final NonEmptyMapValidator INSTANCE = new NonEmptyMapValidator(); + + private NonEmptyMapValidator() { + } + + public void accept(@Nullable Map t, @Nonnull ValidationResults results) { + if (t == null || t.isEmpty()) { + results.fail("Map can't be empty!"); + } + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/NonEmptyStringValidator.java b/src/com/hypixel/hytale/codec/validation/validator/NonEmptyStringValidator.java new file mode 100644 index 0000000..9b3a49b --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/NonEmptyStringValidator.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; + +public class NonEmptyStringValidator implements Validator { + public static final NonEmptyStringValidator INSTANCE = new NonEmptyStringValidator(); + private static final Pattern NON_WHITESPACE_PATTERN = Pattern.compile("[^\\s]"); + + protected NonEmptyStringValidator() { + } + + public void accept(@Nonnull String string, @Nonnull ValidationResults results) { + if (string.isBlank()) { + results.fail("String can't be empty!"); + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + StringSchema s = (StringSchema)target; + s.setMinLength(1); + s.setPattern(NON_WHITESPACE_PATTERN); + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/NonNullValidator.java b/src/com/hypixel/hytale/codec/validation/validator/NonNullValidator.java new file mode 100644 index 0000000..1726e71 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/NonNullValidator.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NonNullValidator implements Validator { + public static final NonNullValidator INSTANCE = new NonNullValidator(); + + protected NonNullValidator() { + } + + @Override + public void accept(@Nullable T t, @Nonnull ValidationResults results) { + if (t == null) { + results.fail("Can't be null!"); + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/NotEqualValidator.java b/src/com/hypixel/hytale/codec/validation/validator/NotEqualValidator.java new file mode 100644 index 0000000..cae5d22 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/NotEqualValidator.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.IntegerSchema; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NotEqualValidator> implements Validator { + @Nonnull + private final T value; + + public NotEqualValidator(@Nonnull T value) { + this.value = value; + } + + public void accept(@Nullable T o, @Nonnull ValidationResults results) { + if (o != null && this.value.compareTo(o) == 0) { + results.fail("Provided value can't be equal to " + this.value); + } + } + + @Override + public void updateSchema(SchemaContext context, @Nonnull Schema target) { + if (target.getAllOf() != null) { + throw new IllegalArgumentException(); + } else { + if (target instanceof StringSchema) { + target.setAllOf(Schema.not(StringSchema.constant((String)this.value))); + } else if (target instanceof IntegerSchema) { + target.setAllOf(Schema.not(IntegerSchema.constant(((Number)this.value).intValue()))); + } else { + if (!(target instanceof NumberSchema)) { + throw new IllegalArgumentException(); + } + + target.setAllOf(Schema.not(NumberSchema.constant(((Number)this.value).doubleValue()))); + } + } + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/OrValidator.java b/src/com/hypixel/hytale/codec/validation/validator/OrValidator.java new file mode 100644 index 0000000..5ca96f2 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/OrValidator.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nonnull; + +public class OrValidator implements Validator { + private final Validator[] validators; + + public OrValidator(Validator[] validators) { + this.validators = validators; + } + + @Override + public void accept(T t, @Nonnull ValidationResults results) { + ObjectArrayList possibleResults = new ObjectArrayList<>(); + List oldResults = results.getResults(); + + for (Validator validator : this.validators) { + results.setResults(null); + validator.accept(t, results); + if (!results.hasFailed()) { + results.setResults(oldResults); + return; + } + + possibleResults.addAll(results.getResults()); + } + + results.setResults(oldResults); + + for (ValidationResults.ValidationResult p : possibleResults) { + results.add(p); + } + } + + @Override + public void updateSchema(SchemaContext context, @Nonnull Schema target) { + if (target.getAnyOf() == null) { + BuilderCodec subCodec = (BuilderCodec)Schema.CODEC.getCodecFor((Class)target.getClass()); + Schema[] anyOf = new Schema[this.validators.length]; + int index = 0; + Schema def = subCodec.getSupplier().get(); + + for (Validator val : this.validators) { + Schema base = subCodec.getSupplier().get(); + val.updateSchema(context, base); + if (!base.equals(def)) { + anyOf[index++] = base; + } + } + + if (index != 0) { + target.setAnyOf(Arrays.copyOf(anyOf, index)); + } + } else { + for (Schema c : target.getAnyOf()) { + this.updateSchema(context, c); + } + } + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/RangeRefValidator.java b/src/com/hypixel/hytale/codec/validation/validator/RangeRefValidator.java new file mode 100644 index 0000000..5fe6da8 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/RangeRefValidator.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.IntegerSchema; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import com.hypixel.hytale.logger.HytaleLogger; +import java.util.logging.Level; + +public class RangeRefValidator> implements Validator { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final String minPointer; + private final String maxPointer; + private final boolean inclusive; + + public RangeRefValidator(String minPointer, String maxPointer, boolean inclusive) { + this.minPointer = minPointer; + this.maxPointer = maxPointer; + this.inclusive = inclusive; + } + + public void accept(T t, ValidationResults results) { + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + if (!(target instanceof NumberSchema) && !(target instanceof IntegerSchema)) { + LOGGER.at(Level.WARNING).log("Can't handle: " + target.getHytale().getType() + " as a range"); + } else { + if (target instanceof IntegerSchema i) { + if (this.minPointer != null) { + if (this.inclusive) { + i.setMinimum(Schema.data(this.minPointer)); + } else { + i.setExclusiveMinimum(Schema.data(this.minPointer)); + } + } + + if (this.maxPointer != null) { + if (this.inclusive) { + i.setMaximum(Schema.data(this.maxPointer)); + } else { + i.setExclusiveMaximum(Schema.data(this.maxPointer)); + } + } + } else { + NumberSchema i = (NumberSchema)target; + if (this.minPointer != null) { + if (this.inclusive) { + i.setMinimum(Schema.data(this.minPointer)); + } else { + i.setExclusiveMinimum(Schema.data(this.minPointer)); + } + } + + if (this.maxPointer != null) { + if (this.inclusive) { + i.setMaximum(Schema.data(this.maxPointer)); + } else { + i.setExclusiveMaximum(Schema.data(this.maxPointer)); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/RangeValidator.java b/src/com/hypixel/hytale/codec/validation/validator/RangeValidator.java new file mode 100644 index 0000000..441c023 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/RangeValidator.java @@ -0,0 +1,117 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.IntegerSchema; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import com.hypixel.hytale.logger.HytaleLogger; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RangeValidator> implements Validator { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final T min; + private final T max; + private final boolean inclusive; + + public RangeValidator(T min, T max, boolean inclusive) { + this.min = min; + this.max = max; + this.inclusive = inclusive; + } + + public void accept(@Nullable T t, @Nonnull ValidationResults results) { + if (t != null) { + if (this.min != null) { + int compare = t.compareTo(this.min); + if (this.inclusive) { + if (compare < 0) { + results.fail("Must be greater than or equal to " + this.min); + } + } else if (compare < 0 || compare == 0) { + results.fail("Must be greater than " + this.min); + } + } + + if (this.max != null) { + int compare = t.compareTo(this.max); + if (this.inclusive) { + if (compare > 0) { + results.fail("Must be less than or equal to " + this.max); + } + } else if (compare > 0 || compare == 0) { + results.fail("Must be less than " + this.max); + } + } + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + if (this.min != null && !(this.min instanceof Number)) { + LOGGER.at(Level.WARNING).log("Can't handle: min is not a Number: %s, %s", this.min, this.min.getClass()); + } else if (this.max != null && !(this.max instanceof Number)) { + LOGGER.at(Level.WARNING).log("Can't handle: max is not a Number: %s, %s", this.max, this.max.getClass()); + } else if (!(target instanceof NumberSchema) && !(target instanceof IntegerSchema)) { + boolean failed = true; + if (target.getAnyOf() != null) { + for (Schema schema : target.getAnyOf()) { + if (schema instanceof NumberSchema || schema instanceof IntegerSchema) { + this.updateSchema(schema); + failed = false; + } + } + } + + if (failed) { + LOGGER.at(Level.WARNING).log("Can't handle: %s as a range: %s", target.getHytale().getType(), target); + } + } else { + this.updateSchema(target); + } + } + + private void updateSchema(Schema target) { + if (target instanceof IntegerSchema i) { + if (this.min != null) { + Number v = (Number)this.min; + if (this.inclusive) { + i.setMinimum(v.intValue()); + } else { + i.setExclusiveMinimum(v.intValue()); + } + } + + if (this.max != null) { + Number v = (Number)this.max; + if (this.inclusive) { + i.setMaximum(v.intValue()); + } else { + i.setExclusiveMaximum(v.intValue()); + } + } + } else { + NumberSchema i = (NumberSchema)target; + if (this.min != null) { + Number v = (Number)this.min; + if (this.inclusive) { + i.setMinimum(v.doubleValue()); + } else { + i.setExclusiveMinimum(v.doubleValue()); + } + } + + if (this.max != null) { + Number v = (Number)this.max; + if (this.inclusive) { + i.setMaximum(v.doubleValue()); + } else { + i.setExclusiveMaximum(v.doubleValue()); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/RequiredMapKeysValidator.java b/src/com/hypixel/hytale/codec/validation/validator/RequiredMapKeysValidator.java new file mode 100644 index 0000000..d2bb479 --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/RequiredMapKeysValidator.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import java.util.Map; +import javax.annotation.Nonnull; + +public class RequiredMapKeysValidator implements Validator> { + private final T[] array; + + public RequiredMapKeysValidator(T[] array) { + this.array = array; + } + + public void accept(@Nonnull Map map, @Nonnull ValidationResults results) { + for (int i = 0; i < this.array.length; i++) { + T obj = this.array[i]; + if (!map.containsKey(obj)) { + results.fail(String.format("Key not found! %s", obj)); + } + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + ObjectSchema obj = (ObjectSchema)target; + StringSchema keys = obj.getPropertyNames() != null ? obj.getPropertyNames() : new StringSchema(); + String[] keyValues = new String[this.array.length]; + + for (int i = 0; i < this.array.length; i++) { + keyValues[i] = this.array[i].toString(); + } + + keys.setEnum(keyValues); + obj.setPropertyNames(keys); + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/SequentialDoubleArrayValidator.java b/src/com/hypixel/hytale/codec/validation/validator/SequentialDoubleArrayValidator.java new file mode 100644 index 0000000..1507aff --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/SequentialDoubleArrayValidator.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import javax.annotation.Nonnull; + +public class SequentialDoubleArrayValidator implements Validator { + public static final SequentialDoubleArrayValidator NEQ_INSTANCE = new SequentialDoubleArrayValidator(false); + public static final SequentialDoubleArrayValidator ALLOW_EQ_INSTANCE = new SequentialDoubleArrayValidator(true); + private final boolean allowEquals; + + public SequentialDoubleArrayValidator(boolean allowEquals) { + this.allowEquals = allowEquals; + } + + public void accept(@Nonnull double[] doubles, @Nonnull ValidationResults results) { + if (doubles.length > 1) { + double last = doubles[0]; + + for (int i = 1; i < doubles.length; i++) { + double val = doubles[i]; + if (!this.allowEquals && last >= val || this.allowEquals && last > val) { + results.fail( + String.format( + "Values must be sequential. %f at index %d is larger than %s %f at index %d", last, i - 1, this.allowEquals ? "" : "or equal to", val, i + ) + ); + } + + last = val; + } + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + } +} diff --git a/src/com/hypixel/hytale/codec/validation/validator/UniqueInArrayValidator.java b/src/com/hypixel/hytale/codec/validation/validator/UniqueInArrayValidator.java new file mode 100644 index 0000000..7b2e59e --- /dev/null +++ b/src/com/hypixel/hytale/codec/validation/validator/UniqueInArrayValidator.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.codec.validation.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class UniqueInArrayValidator implements Validator { + public static final UniqueInArrayValidator INSTANCE = new UniqueInArrayValidator(); + + private UniqueInArrayValidator() { + } + + public void accept(@Nonnull T[] arr, @Nonnull ValidationResults results) { + for (int i = 0; i < arr.length; i++) { + T obj = arr[i]; + + for (int j = i + 1; j < arr.length; j++) { + T other = arr[j]; + if (Objects.equals(obj, other)) { + results.fail(String.format("The two objects at index %s and %s are the same but must be unique! %s == %s", i, j, obj, other)); + } + } + } + } + + @Override + public void updateSchema(SchemaContext context, @Nonnull Schema target) { + ((ArraySchema)target).setUniqueItems(true); + } +} diff --git a/src/com/hypixel/hytale/common/benchmark/ContinuousValueRecorder.java b/src/com/hypixel/hytale/common/benchmark/ContinuousValueRecorder.java new file mode 100644 index 0000000..8b728c3 --- /dev/null +++ b/src/com/hypixel/hytale/common/benchmark/ContinuousValueRecorder.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.common.benchmark; + +public class ContinuousValueRecorder { + protected double minValue = Double.MAX_VALUE; + protected double maxValue = -Double.MAX_VALUE; + protected double sumValues = 0.0; + protected long count = 0L; + + public ContinuousValueRecorder() { + } + + public void reset() { + this.minValue = Double.MAX_VALUE; + this.maxValue = -Double.MAX_VALUE; + this.sumValues = 0.0; + this.count = 0L; + } + + public double getMinValue(double def) { + return this.count > 0L ? this.minValue : def; + } + + public double getMinValue() { + return this.getMinValue(0.0); + } + + public double getMaxValue(double def) { + return this.count > 0L ? this.maxValue : def; + } + + public double getMaxValue() { + return this.getMaxValue(0.0); + } + + public long getCount() { + return this.count; + } + + public double getAverage(double def) { + return this.count > 0L ? this.sumValues / this.count : def; + } + + public double getAverage() { + return this.getAverage(0.0); + } + + public double record(double value) { + if (this.minValue > value) { + this.minValue = value; + } + + if (this.maxValue < value) { + this.maxValue = value; + } + + this.count++; + this.sumValues += value; + return value; + } +} diff --git a/src/com/hypixel/hytale/common/benchmark/DiscreteValueRecorder.java b/src/com/hypixel/hytale/common/benchmark/DiscreteValueRecorder.java new file mode 100644 index 0000000..95fde49 --- /dev/null +++ b/src/com/hypixel/hytale/common/benchmark/DiscreteValueRecorder.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.common.benchmark; + +import com.hypixel.hytale.common.util.FormatUtil; +import java.util.Formatter; +import javax.annotation.Nonnull; + +public class DiscreteValueRecorder { + public static final String DEFAULT_COLUMN_SEPARATOR = "|"; + public static final String DEFAULT_COLUMN_FORMAT_HEADER = "|%-6.6s"; + public static final String DEFAULT_COLUMN_FORMAT_VALUE = "|%6.6s"; + public static final String[] DEFAULT_COLUMNS = new String[]{"AVG", "MIN", "MAX", "COUNT"}; + protected long minValue; + protected long maxValue; + protected long sumValues; + protected long count; + + public DiscreteValueRecorder() { + this.reset(); + } + + public void reset() { + this.minValue = Long.MAX_VALUE; + this.maxValue = Long.MIN_VALUE; + this.sumValues = 0L; + this.count = 0L; + } + + public long getMinValue(long def) { + return this.count > 0L ? this.minValue : def; + } + + public long getMinValue() { + return this.getMinValue(0L); + } + + public long getMaxValue(long def) { + return this.count > 0L ? this.maxValue : def; + } + + public long getMaxValue() { + return this.getMaxValue(0L); + } + + public long getCount() { + return this.count; + } + + public long getAverage(long def) { + return this.count > 0L ? (2L * this.sumValues + this.count) / (2L * this.count) : def; + } + + public long getAverage() { + return this.getAverage(0L); + } + + public void record(long value) { + if (this.minValue > value) { + this.minValue = value; + } + + if (this.maxValue < value) { + this.maxValue = value; + } + + this.count++; + this.sumValues += value; + } + + @Nonnull + @Override + public String toString() { + return String.format("Avg=%s Min=%s Max=%s", this.getAverage(), this.getMinValue(), this.getMaxValue()); + } + + public void formatHeader(@Nonnull Formatter formatter) { + this.formatHeader(formatter, "|%-6.6s"); + } + + public void formatHeader(@Nonnull Formatter formatter, @Nonnull String columnFormatHeader) { + FormatUtil.formatArray(formatter, columnFormatHeader, DEFAULT_COLUMNS); + } + + public void formatValues(@Nonnull Formatter formatter) { + this.formatValues(formatter, "|%6.6s"); + } + + public void formatValues(@Nonnull Formatter formatter, @Nonnull String columnFormatValue) { + FormatUtil.formatArgs(formatter, columnFormatValue, this.getAverage(), this.getMinValue(), this.getMaxValue(), this.count); + } +} diff --git a/src/com/hypixel/hytale/common/benchmark/TimeDistributionRecorder.java b/src/com/hypixel/hytale/common/benchmark/TimeDistributionRecorder.java new file mode 100644 index 0000000..c5f078b --- /dev/null +++ b/src/com/hypixel/hytale/common/benchmark/TimeDistributionRecorder.java @@ -0,0 +1,135 @@ +package com.hypixel.hytale.common.benchmark; + +import com.hypixel.hytale.math.util.MathUtil; +import java.util.Formatter; +import javax.annotation.Nonnull; + +public class TimeDistributionRecorder extends TimeRecorder { + protected int minLogRange; + protected int maxLogRange; + protected int logSteps; + protected long[] valueBins; + + public TimeDistributionRecorder(double maxSecs, double minSecs, int logSteps) { + if (maxSecs < 1.0E-6 || maxSecs > 0.1) { + throw new IllegalArgumentException("Max seconds must be between 100 milli secs and 1 micro sec"); + } else if (minSecs < 1.0E-6 || minSecs > 0.1) { + throw new IllegalArgumentException("Min seconds must be between 100 milli secs and 1 micro sec"); + } else if (maxSecs <= minSecs) { + throw new IllegalArgumentException("Max seconds must be larger than min seconds"); + } else if (logSteps >= 2 && logSteps <= 10) { + this.maxLogRange = MathUtil.ceil(Math.log10(maxSecs)); + this.minLogRange = MathUtil.floor(Math.log10(minSecs)); + this.logSteps = MathUtil.clamp(logSteps, 2, 10); + this.valueBins = new long[(this.maxLogRange - this.minLogRange) * this.logSteps + 2]; + int i = 0; + + for (int length = this.valueBins.length; i < length; i++) { + this.valueBins[i] = 0L; + } + } else { + throw new IllegalArgumentException("LogSteps must be between 2 and 10"); + } + } + + public TimeDistributionRecorder(double maxSecs, double minSecs) { + this(maxSecs, minSecs, 5); + } + + public TimeDistributionRecorder() { + this(0.1, 1.0E-5); + } + + @Override + public void reset() { + super.reset(); + int i = 0; + + for (int length = this.valueBins.length; i < length; i++) { + this.valueBins[i] = 0L; + } + } + + @Override + public double recordNanos(long nanos) { + double secs = super.recordNanos(nanos); + this.valueBins[this.timeToIndex(secs)]++; + return secs; + } + + public int timeToIndex(double secs) { + double logSecs = Math.log10(secs); + double indexDbl = (this.maxLogRange - logSecs) * this.logSteps; + int index = MathUtil.ceil(indexDbl); + if (index < 0) { + index = 0; + } else if (index >= this.valueBins.length) { + index = this.valueBins.length - 1; + } + + return index; + } + + public double indexToTime(int index) { + if (index < 0) { + index = 0; + } else if (index >= this.valueBins.length) { + index = this.valueBins.length - 1; + } + + if (index == this.valueBins.length - 1) { + return 0.0; + } else { + double exp = this.maxLogRange - (double)index / this.logSteps; + return Math.pow(10.0, exp); + } + } + + public int size() { + return this.valueBins.length; + } + + public long get(int index) { + return this.valueBins[index]; + } + + @Nonnull + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(12 * this.size()); + stringBuilder.append("Cnt=").append(this.getCount()); + + for (int i = 0; i < this.size(); i++) { + stringBuilder.append(' ').append(formatTime(this.indexToTime(i))).append('=').append(this.get(i)); + } + + return super.toString() + " " + stringBuilder; + } + + @Override + public void formatHeader(@Nonnull Formatter formatter, @Nonnull String columnFormatHeader) { + super.formatHeader(formatter, columnFormatHeader); + + for (int i = 0; i < this.size(); i++) { + formatter.format(columnFormatHeader, formatTime(this.indexToTime(i))); + } + } + + @Override + public void formatValues(@Nonnull Formatter formatter, @Nonnull String columnFormatValue) { + this.formatValues(formatter, 0L, columnFormatValue); + } + + public void formatValues(@Nonnull Formatter formatter, long normalValue) { + this.formatValues(formatter, normalValue, "|%6.6s"); + } + + public void formatValues(@Nonnull Formatter formatter, long normalValue, @Nonnull String columnFormatValue) { + super.formatValues(formatter, columnFormatValue); + double norm = this.count > 0L && normalValue > 1L ? (double)normalValue / this.count : 1.0; + + for (int i = 0; i < this.size(); i++) { + formatter.format(columnFormatValue, (int)Math.round(this.get(i) * norm)); + } + } +} diff --git a/src/com/hypixel/hytale/common/benchmark/TimeRecorder.java b/src/com/hypixel/hytale/common/benchmark/TimeRecorder.java new file mode 100644 index 0000000..ae3ba0f --- /dev/null +++ b/src/com/hypixel/hytale/common/benchmark/TimeRecorder.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.common.benchmark; + +import com.hypixel.hytale.common.util.FormatUtil; +import java.util.Formatter; +import javax.annotation.Nonnull; + +public class TimeRecorder extends ContinuousValueRecorder { + public static final String DEFAULT_COLUMN_SEPARATOR = "|"; + public static final String DEFAULT_COLUMN_FORMAT_HEADER = "|%-6.6s"; + public static final String DEFAULT_COLUMN_FORMAT_VALUE = "|%6.6s"; + public static final String[] DEFAULT_COLUMNS = DiscreteValueRecorder.DEFAULT_COLUMNS; + public static final double NANOS_TO_SECONDS = 1.0E-9; + + public TimeRecorder() { + } + + public long start() { + return System.nanoTime(); + } + + public double end(long start) { + return this.recordNanos(System.nanoTime() - start); + } + + public double recordNanos(long nanos) { + return super.record(nanos * 1.0E-9); + } + + @Nonnull + @Override + public String toString() { + return String.format("Avg=%s Min=%s Max=%s", formatTime(this.getAverage(0.0)), formatTime(this.getMinValue(0.0)), formatTime(this.getMaxValue(0.0))); + } + + @Nonnull + public static String formatTime(double secs) { + if (secs <= 0.0) { + return "0s"; + } else if (secs >= 10.0) { + return format(secs, "s"); + } else { + secs *= 1000.0; + if (secs >= 10.0) { + return format(secs, "ms"); + } else { + secs *= 1000.0; + if (secs >= 10.0) { + return format(secs, "us"); + } else { + secs *= 1000.0; + return format(secs, "ns"); + } + } + } + } + + @Nonnull + protected static String format(double val, String suffix) { + return (int)Math.round(val) + suffix; + } + + public void formatHeader(@Nonnull Formatter formatter) { + this.formatHeader(formatter, "|%-6.6s"); + } + + public void formatHeader(@Nonnull Formatter formatter, @Nonnull String columnFormatHeader) { + FormatUtil.formatArray(formatter, columnFormatHeader, DEFAULT_COLUMNS); + } + + public void formatValues(@Nonnull Formatter formatter) { + this.formatValues(formatter, "|%6.6s"); + } + + public void formatValues(@Nonnull Formatter formatter, @Nonnull String columnFormatValue) { + FormatUtil.formatArgs( + formatter, columnFormatValue, formatTime(this.getAverage()), formatTime(this.getMinValue()), formatTime(this.getMaxValue()), this.count + ); + } +} diff --git a/src/com/hypixel/hytale/common/collection/BucketItem.java b/src/com/hypixel/hytale/common/collection/BucketItem.java new file mode 100644 index 0000000..0e9355c --- /dev/null +++ b/src/com/hypixel/hytale/common/collection/BucketItem.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.common.collection; + +import javax.annotation.Nonnull; + +public class BucketItem { + public E item; + public double squaredDistance; + + public BucketItem() { + } + + @Nonnull + public BucketItem set(E reference, double squaredDistance) { + this.item = reference; + this.squaredDistance = squaredDistance; + return this; + } +} diff --git a/src/com/hypixel/hytale/common/collection/BucketItemPool.java b/src/com/hypixel/hytale/common/collection/BucketItemPool.java new file mode 100644 index 0000000..e8dc48f --- /dev/null +++ b/src/com/hypixel/hytale/common/collection/BucketItemPool.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.common.collection; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nonnull; + +public class BucketItemPool { + @Nonnull + protected final List> pool = new ObjectArrayList<>(); + + public BucketItemPool() { + } + + public void deallocate(BucketItem[] entityHolders, int count) { + this.pool.addAll(Arrays.asList(entityHolders).subList(0, count)); + } + + public BucketItem allocate(E reference, double squaredDistance) { + int l = this.pool.size(); + BucketItem holder = l == 0 ? new BucketItem<>() : this.pool.remove(l - 1); + return holder.set(reference, squaredDistance); + } +} diff --git a/src/com/hypixel/hytale/common/collection/BucketList.java b/src/com/hypixel/hytale/common/collection/BucketList.java new file mode 100644 index 0000000..b3ed0b0 --- /dev/null +++ b/src/com/hypixel/hytale/common/collection/BucketList.java @@ -0,0 +1,300 @@ +package com.hypixel.hytale.common.collection; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntArrays; +import it.unimi.dsi.fastutil.objects.ObjectArrays; +import java.util.Comparator; +import java.util.function.IntFunction; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BucketList { + public static final int INITIAL_BUCKET_ITEM_ARRAY_SIZE = 4; + public static final Comparator> CLOSER_TO_SELF = Comparator.comparingDouble(bucketItem -> bucketItem.squaredDistance); + protected static final byte[] EMPTY_INDICES = new byte[]{-1}; + protected BucketItemPool bucketItemPool; + @Nullable + protected BucketList.Bucket[] buckets; + protected byte[] bucketIndices = EMPTY_INDICES; + protected int bucketCount; + protected int squaredMaxDistance; + + public BucketList(BucketItemPool bucketItemPool) { + this.bucketItemPool = bucketItemPool; + } + + public void setBucketItemPool(@Nonnull BucketItemPool bucketItemPool) { + this.clear(); + this.bucketItemPool = bucketItemPool; + } + + public void clear() { + if (this.buckets != null && this.bucketItemPool != null) { + for (BucketList.Bucket bucket : this.buckets) { + bucket.clear(this.bucketItemPool); + } + } + } + + public void reset() { + this.clear(); + this.buckets = null; + this.bucketCount = 0; + this.bucketIndices = EMPTY_INDICES; + } + + public void configure(@Nonnull int[] bucketRanges) { + this.configure(bucketRanges, 4); + } + + public void configure(@Nonnull int[] bucketRanges, int initialBucketItemArraySize) { + if (bucketRanges == null) { + throw new IllegalArgumentException("bucketRanges can't be null"); + } else if (bucketRanges.length <= 0) { + throw new IllegalArgumentException("bucketRanges can't beempty"); + } else { + int[] copyRanges = (int[])bucketRanges.clone(); + IntArrays.quickSort(copyRanges); + if (copyRanges[0] <= 0) { + throw new IllegalArgumentException("bucketRanges entries must be >0"); + } else { + this.configureWithPreSortedArray(copyRanges, initialBucketItemArraySize); + } + } + } + + public void configureWithPreSortedArray(@Nonnull int[] bucketRanges) { + this.configureWithPreSortedArray(bucketRanges, 4); + } + + public void configureWithPreSortedArray(@Nonnull int[] bucketRanges, int initialBucketItemArraySize) { + this.clear(); + this.bucketCount = bucketRanges.length; + this.squaredMaxDistance = bucketRanges[this.bucketCount - 1]; + this.squaredMaxDistance = this.squaredMaxDistance * this.squaredMaxDistance; + this.buckets = new BucketList.Bucket[this.bucketCount]; + this.bucketIndices = new byte[this.squaredMaxDistance + 1]; + int inner = 0; + + for (int i = 0; i < this.bucketCount; i++) { + int outer = bucketRanges[i] * bucketRanges[i]; + this.buckets[i] = new BucketList.Bucket<>(initialBucketItemArraySize); + + for (int j = inner; j < outer; j++) { + this.bucketIndices[j] = (byte)i; + } + + inner = outer; + } + + this.bucketIndices[this.bucketIndices.length - 1] = -1; + } + + public void configureWithPresortedArray(@Nonnull IntArrayList bucketRanges, int initialBucketItemArraySize) { + this.configureWithPreSortedArray(bucketRanges.toIntArray(), initialBucketItemArraySize); + } + + public boolean add(@Nonnull E item, double squaredDistance) { + int bucketIndex = this.getFirstBucketIndex((int)squaredDistance); + if (bucketIndex < 0) { + return false; + } else { + BucketItem bucketItem = this.bucketItemPool.allocate(item, squaredDistance); + this.buckets[bucketIndex].add(bucketItem); + return true; + } + } + + public int getBucketCount() { + return this.buckets != null ? this.buckets.length : 0; + } + + @Nullable + public BucketList.Bucket getBucket(int index) { + return index >= 0 && index < this.getBucketCount() ? this.buckets[index] : null; + } + + public int getFirstBucketIndex(int distanceSquared) { + if (distanceSquared == 0) { + return this.bucketIndices[0]; + } else { + distanceSquared = Math.min(distanceSquared, this.squaredMaxDistance); + return distanceSquared <= 0 ? -1 : this.bucketIndices[distanceSquared]; + } + } + + public int getLastBucketIndex(int distanceSquared) { + int d = Math.min(distanceSquared, this.squaredMaxDistance) - 1; + return d < 0 ? -1 : this.bucketIndices[d]; + } + + @Nullable + public E getClosestInRange(int minRange, int maxRange, @Nonnull Predicate filter, @Nonnull BucketList.SortBufferProvider sortBufferProvider) { + int minRangeSquared = minRange * minRange; + int startBucket = this.getFirstBucketIndex(minRangeSquared); + if (startBucket < 0) { + return null; + } else { + int maxRangeSquared = maxRange * maxRange; + int endBucket = this.getLastBucketIndex(maxRangeSquared); + + for (int i = startBucket; i <= endBucket; i++) { + BucketList.Bucket bucket = this.buckets[i]; + if (!bucket.isEmpty) { + if (bucket.isUnsorted) { + bucket.sort(sortBufferProvider); + } + + BucketItem[] entityHolders = bucket.bucketItems; + int i1 = 0; + + for (int entityHoldersSize = bucket.size; i1 < entityHoldersSize; i1++) { + BucketItem holder = entityHolders[i1]; + double squaredDistance = holder.squaredDistance; + if (!(squaredDistance < minRangeSquared)) { + if (squaredDistance >= maxRangeSquared) { + return null; + } + + E item = holder.item; + if (item != null && filter.test(item)) { + return item; + } + } + } + } + } + + return null; + } + } + + public static void addBucketDistance(@Nonnull IntArrayList bucketRanges, int maxBucketCount, int distance) { + addBucketDistance(bucketRanges, maxBucketCount, distance, -1); + } + + public static void addBucketDistance(@Nonnull IntArrayList bucketRanges, int maxBucketCount, int distance, int keepDistance) { + if (distance >= 1) { + int i = 0; + + int length; + for (length = bucketRanges.size(); i < length; i++) { + int v = bucketRanges.getInt(i); + if (v == distance) { + return; + } + + if (v > distance) { + break; + } + } + + bucketRanges.add(i, distance); + if (++length > maxBucketCount) { + int middle = bucketRanges.getInt(0); + int innerArea = area(0, middle); + int area = Integer.MAX_VALUE; + int pos = -1; + + for (int var13 = 1; var13 < length; var13++) { + int outer = bucketRanges.getInt(var13); + int outerArea = area(middle, outer); + int sumAreas = innerArea + outerArea; + if (sumAreas <= area && middle != keepDistance) { + pos = var13 - 1; + area = sumAreas; + } + + middle = outer; + innerArea = outerArea; + } + + bucketRanges.removeInt(pos); + } + } + } + + protected static int area(int inner, int outer) { + return outer * outer - inner * inner; + } + + public static class Bucket { + protected BucketItem[] bucketItems; + protected int size; + protected boolean isUnsorted; + protected boolean isEmpty; + + public Bucket(int initialBucketArraySize) { + this.bucketItems = new BucketItem[initialBucketArraySize]; + this.size = 0; + this.isUnsorted = false; + this.isEmpty = true; + } + + public BucketItem[] getItems() { + return this.bucketItems; + } + + public int size() { + return this.size; + } + + public boolean isUnsorted() { + return this.isUnsorted; + } + + public boolean isEmpty() { + return this.isEmpty; + } + + public void clear(@Nonnull BucketItemPool pool) { + if (!this.isEmpty) { + pool.deallocate(this.bucketItems, this.size); + + for (int i = 0; i < this.size; i++) { + this.bucketItems[i] = null; + } + + this.size = 0; + this.isUnsorted = false; + this.isEmpty = true; + } + } + + public void add(@Nonnull BucketItem item) { + this.isEmpty = false; + if (this.size == this.bucketItems.length) { + this.bucketItems = ObjectArrays.grow(this.bucketItems, this.size + 1); + } + + this.bucketItems[this.size++] = item; + this.isUnsorted = true; + } + + public void sort(@Nonnull BucketList.SortBufferProvider sortBufferProvider) { + this.isUnsorted = false; + if (this.size > 1) { + BucketItem[] sortBuffer = sortBufferProvider.apply(this.size); + System.arraycopy(this.bucketItems, 0, sortBuffer, 0, this.size); + ObjectArrays.mergeSort(this.bucketItems, 0, this.size, (Comparator>)BucketList.CLOSER_TO_SELF, sortBuffer); + } + } + } + + public static class SortBufferProvider implements IntFunction { + protected BucketItem[] buffer = new BucketItem[4]; + + public SortBufferProvider() { + } + + public BucketItem[] apply(int size) { + if (size <= this.buffer.length) { + return this.buffer; + } else { + this.buffer = ObjectArrays.grow(this.buffer, size); + return this.buffer; + } + } + } +} diff --git a/src/com/hypixel/hytale/common/collection/Flag.java b/src/com/hypixel/hytale/common/collection/Flag.java new file mode 100644 index 0000000..f98e6dc --- /dev/null +++ b/src/com/hypixel/hytale/common/collection/Flag.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.common.collection; + +public interface Flag { + String name(); + + int mask(); +} diff --git a/src/com/hypixel/hytale/common/collection/Flags.java b/src/com/hypixel/hytale/common/collection/Flags.java new file mode 100644 index 0000000..f9b1d82 --- /dev/null +++ b/src/com/hypixel/hytale/common/collection/Flags.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.common.collection; + +import com.hypixel.hytale.common.util.StringUtil; +import javax.annotation.Nonnull; + +public class Flags { + private int flags; + + public Flags(@Nonnull T flag) { + this.set(flag, true); + } + + @SafeVarargs + public Flags(@Nonnull T... flags) { + for (T flag : flags) { + this.set(flag, true); + } + } + + public Flags(int flags) { + this.flags = flags; + } + + public int getFlags() { + return this.flags; + } + + public boolean is(@Nonnull T flag) { + return (this.flags & flag.mask()) != 0; + } + + public boolean not(@Nonnull T flag) { + return (this.flags & flag.mask()) == 0; + } + + public boolean set(@Nonnull T flag, boolean value) { + return value ? this.flags != (this.flags = this.flags | flag.mask()) : this.flags != (this.flags = this.flags & ~flag.mask()); + } + + public boolean toggle(@Nonnull T flag) { + return ((this.flags = this.flags ^ flag.mask()) & flag.mask()) != 0; + } + + @Nonnull + @Override + public String toString() { + return StringUtil.toPaddedBinaryString(this.flags); + } +} diff --git a/src/com/hypixel/hytale/common/fastutil/HLongOpenHashSet.java b/src/com/hypixel/hytale/common/fastutil/HLongOpenHashSet.java new file mode 100644 index 0000000..0dc8cd2 --- /dev/null +++ b/src/com/hypixel/hytale/common/fastutil/HLongOpenHashSet.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.common.fastutil; + +import com.hypixel.hytale.function.predicate.LongTriIntBiObjPredicate; +import it.unimi.dsi.fastutil.HashCommon; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import javax.annotation.Nonnull; + +public class HLongOpenHashSet extends LongOpenHashSet implements HLongSet { + public HLongOpenHashSet() { + } + + @Override + public void removeIf(@Nonnull LongTriIntBiObjPredicate predicate, int ia, int ib, int ic, T obj1, V obj2) { + int pos = super.n; + int last = -1; + int c = super.size; + boolean mustReturnNull = super.containsNull; + LongArrayList wrapped = null; + + while (c != 0) { + long value = 0L; + c--; + if (mustReturnNull) { + mustReturnNull = false; + last = super.n; + value = super.key[super.n]; + } else { + long[] key1 = super.key; + + while (--pos >= 0) { + if (key1[pos] != 0L) { + last = pos; + value = key1[pos]; + break; + } + } + + if (pos < 0) { + last = Integer.MIN_VALUE; + value = wrapped.getLong(-pos - 1); + } + } + + if (predicate.test(value, ia, ib, ic, obj1, obj2)) { + if (last == super.n) { + super.containsNull = false; + super.key[super.n] = 0L; + super.size--; + last = -1; + } else if (pos < 0) { + super.remove(wrapped.getLong(-pos - 1)); + last = -1; + } else { + int pos1 = last; + long[] key1 = super.key; + + label80: + while (true) { + int last1 = pos1; + + long curr; + for (pos1 = pos1 + 1 & super.mask; (curr = key1[pos1]) != 0L; pos1 = pos1 + 1 & super.mask) { + int slot = (int)HashCommon.mix(curr) & super.mask; + if (last1 <= pos1 ? last1 >= slot || slot > pos1 : last1 >= slot && slot > pos1) { + if (pos1 < last1) { + if (wrapped == null) { + wrapped = new LongArrayList(2); + } + + wrapped.add(key1[pos1]); + } + + key1[last1] = curr; + continue label80; + } + } + + key1[last1] = 0L; + super.size--; + last = -1; + break; + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/common/fastutil/HLongSet.java b/src/com/hypixel/hytale/common/fastutil/HLongSet.java new file mode 100644 index 0000000..aa56ed0 --- /dev/null +++ b/src/com/hypixel/hytale/common/fastutil/HLongSet.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.common.fastutil; + +import com.hypixel.hytale.function.predicate.LongTriIntBiObjPredicate; +import it.unimi.dsi.fastutil.longs.LongSet; + +public interface HLongSet extends LongSet { + void removeIf(LongTriIntBiObjPredicate var1, int var2, int var3, int var4, T var5, V var6); +} diff --git a/src/com/hypixel/hytale/common/fastutil/HObjectOpenHashSet.java b/src/com/hypixel/hytale/common/fastutil/HObjectOpenHashSet.java new file mode 100644 index 0000000..8a5e932 --- /dev/null +++ b/src/com/hypixel/hytale/common/fastutil/HObjectOpenHashSet.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.common.fastutil; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.Collection; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HObjectOpenHashSet extends ObjectOpenHashSet { + public HObjectOpenHashSet() { + } + + @Nullable + public K first() { + if (this.containsNull) { + return this.key[this.n]; + } else { + K[] key = this.key; + int pos = this.n; + + while (pos-- != 0) { + if (key[pos] != null) { + return key[pos]; + } + } + + return null; + } + } + + public void pushInto(@Nonnull Collection c) { + if (this.containsNull) { + c.add(this.key[this.n]); + } + + K[] key = this.key; + int pos = this.n; + + while (pos-- != 0) { + if (key[pos] != null) { + c.add(key[pos]); + } + } + } +} diff --git a/src/com/hypixel/hytale/common/map/DefaultMap.java b/src/com/hypixel/hytale/common/map/DefaultMap.java new file mode 100644 index 0000000..2b00395 --- /dev/null +++ b/src/com/hypixel/hytale/common/map/DefaultMap.java @@ -0,0 +1,217 @@ +package com.hypixel.hytale.common.map; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DefaultMap implements Map { + private final Map delegate; + private final boolean allowReplacing; + private final boolean replaceNullWithDefault; + private V defaultValue; + + public DefaultMap(V defaultValue) { + this(defaultValue, new HashMap<>()); + } + + public DefaultMap(V defaultValue, Map delegate) { + this(defaultValue, delegate, true); + } + + public DefaultMap(V defaultValue, Map delegate, boolean allowReplacing) { + this(defaultValue, delegate, allowReplacing, true); + } + + public DefaultMap(V defaultValue, Map delegate, boolean allowReplacing, boolean replaceNullWithDefault) { + this.defaultValue = defaultValue; + this.delegate = delegate; + this.allowReplacing = allowReplacing; + this.replaceNullWithDefault = replaceNullWithDefault; + } + + public V getDefaultValue() { + return this.defaultValue; + } + + public void setDefaultValue(V defaultValue) { + this.defaultValue = defaultValue; + } + + public Map getDelegate() { + return this.delegate; + } + + @Override + public int size() { + return this.delegate.size(); + } + + @Override + public boolean isEmpty() { + return this.delegate.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return this.delegate.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return this.delegate.containsValue(value); + } + + @Override + public V get(@Nullable Object key) { + if (this.replaceNullWithDefault && key == null) { + return this.defaultValue; + } else { + V value = this.delegate.get(key); + return value != null ? value : this.defaultValue; + } + } + + @Override + public V put(K key, V value) { + if (this.allowReplacing) { + return this.delegate.put(key, value); + } else { + V oldValue = this.delegate.putIfAbsent(key, value); + if (oldValue == null) { + return null; + } else { + throw new IllegalArgumentException("Attachment (" + key + ") is already registered!"); + } + } + } + + @Override + public V remove(Object key) { + return this.delegate.remove(key); + } + + @Override + public void putAll(@Nonnull Map m) { + this.delegate.putAll(m); + } + + @Override + public void clear() { + this.delegate.clear(); + } + + @Nonnull + @Override + public Set keySet() { + return this.delegate.keySet(); + } + + @Nonnull + @Override + public Collection values() { + return this.delegate.values(); + } + + @Nonnull + @Override + public Set> entrySet() { + return this.delegate.entrySet(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DefaultMap that = (DefaultMap)o; + if (this.allowReplacing != that.allowReplacing) { + return false; + } else if (this.replaceNullWithDefault != that.replaceNullWithDefault) { + return false; + } else if (this.delegate != null ? this.delegate.equals(that.delegate) : that.delegate == null) { + return this.defaultValue != null ? this.defaultValue.equals(that.defaultValue) : that.defaultValue == null; + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.delegate != null ? this.delegate.hashCode() : 0; + result = 31 * result + (this.allowReplacing ? 1 : 0); + result = 31 * result + (this.replaceNullWithDefault ? 1 : 0); + return 31 * result + (this.defaultValue != null ? this.defaultValue.hashCode() : 0); + } + + @Override + public V getOrDefault(Object key, V defaultValue) { + return this.delegate.getOrDefault(key, defaultValue); + } + + @Override + public void forEach(BiConsumer action) { + this.delegate.forEach(action); + } + + @Override + public void replaceAll(BiFunction function) { + this.delegate.replaceAll(function); + } + + @Override + public V putIfAbsent(K key, V value) { + return this.delegate.putIfAbsent(key, value); + } + + @Override + public boolean remove(Object key, Object value) { + return this.delegate.remove(key, value); + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + return this.delegate.replace(key, oldValue, newValue); + } + + @Override + public V replace(K key, V value) { + return this.delegate.replace(key, value); + } + + @Override + public V computeIfAbsent(K key, @Nonnull Function mappingFunction) { + return this.delegate.computeIfAbsent(key, mappingFunction); + } + + @Nullable + @Override + public V computeIfPresent(K key, @Nonnull BiFunction remappingFunction) { + return this.delegate.computeIfPresent(key, remappingFunction); + } + + @Override + public V compute(K key, @Nonnull BiFunction remappingFunction) { + return this.delegate.compute(key, remappingFunction); + } + + @Override + public V merge(K key, @Nonnull V value, @Nonnull BiFunction remappingFunction) { + return this.delegate.merge(key, value, remappingFunction); + } + + @Nonnull + @Override + public String toString() { + return "DefaultMap{defaultValue=" + this.defaultValue + ", delegate=" + this.delegate + "}"; + } +} diff --git a/src/com/hypixel/hytale/common/map/IWeightedElement.java b/src/com/hypixel/hytale/common/map/IWeightedElement.java new file mode 100644 index 0000000..9d26053 --- /dev/null +++ b/src/com/hypixel/hytale/common/map/IWeightedElement.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.common.map; + +public interface IWeightedElement { + double getWeight(); +} diff --git a/src/com/hypixel/hytale/common/map/IWeightedMap.java b/src/com/hypixel/hytale/common/map/IWeightedMap.java new file mode 100644 index 0000000..b46fc8a --- /dev/null +++ b/src/com/hypixel/hytale/common/map/IWeightedMap.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.common.map; + +import com.hypixel.hytale.function.function.BiDoubleToDoubleFunction; +import com.hypixel.hytale.function.function.BiIntToDoubleFunction; +import com.hypixel.hytale.function.function.BiLongToDoubleFunction; +import java.util.Random; +import java.util.function.Consumer; +import java.util.function.DoubleSupplier; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.ObjDoubleConsumer; +import javax.annotation.Nullable; + +public interface IWeightedMap { + @Nullable + T get(double var1); + + @Nullable + T get(DoubleSupplier var1); + + @Nullable + T get(Random var1); + + @Nullable + T get(int var1, int var2, BiIntToDoubleFunction var3); + + @Nullable + T get(long var1, long var3, BiLongToDoubleFunction var5); + + @Nullable + T get(double var1, double var3, BiDoubleToDoubleFunction var5); + + @Nullable + T get(int var1, int var2, int var3, IWeightedMap.SeedCoordinateFunction var4, K var5); + + int size(); + + boolean contains(T var1); + + void forEach(Consumer var1); + + void forEachEntry(ObjDoubleConsumer var1); + + T[] internalKeys(); + + T[] toArray(); + + IWeightedMap resolveKeys(Function var1, IntFunction var2); + + @FunctionalInterface + public interface SeedCoordinateFunction { + double apply(int var1, int var2, int var3, T var4); + } +} diff --git a/src/com/hypixel/hytale/common/map/WeightedMap.java b/src/com/hypixel/hytale/common/map/WeightedMap.java new file mode 100644 index 0000000..1ed1591 --- /dev/null +++ b/src/com/hypixel/hytale/common/map/WeightedMap.java @@ -0,0 +1,348 @@ +package com.hypixel.hytale.common.map; + +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.function.function.BiDoubleToDoubleFunction; +import com.hypixel.hytale.function.function.BiIntToDoubleFunction; +import com.hypixel.hytale.function.function.BiLongToDoubleFunction; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.DoubleSupplier; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.ObjDoubleConsumer; +import java.util.function.ToDoubleFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WeightedMap implements IWeightedMap { + public static final double EPSILON = 0.99999; + public static final double ONE_MINUS_EPSILON = 9.99999999995449E-6; + @Nonnull + private final Set keySet = new HashSet<>(); + @Nonnull + private final T[] keys; + private final double[] values; + private final double sum; + + @Nonnull + public static WeightedMap.Builder builder(T[] emptyKeys) { + return new WeightedMap.Builder<>(emptyKeys); + } + + private WeightedMap(@Nonnull T[] keys, double[] values, double sum) { + Collections.addAll(this.keySet, keys); + this.keys = keys; + this.values = values; + this.sum = sum; + } + + @Nullable + @Override + public T get(double value) { + double weightPercentSum = Math.min(value, 0.99999) * this.sum; + + for (int i = 0; i < this.keys.length; i++) { + if ((weightPercentSum -= this.values[i]) <= 9.99999999995449E-6) { + return this.keys[i]; + } + } + + return null; + } + + @Nullable + @Override + public T get(@Nonnull DoubleSupplier supplier) { + return this.get(supplier.getAsDouble()); + } + + @Nullable + @Override + public T get(@Nonnull Random random) { + return this.get(random.nextDouble()); + } + + @Nullable + @Override + public T get(int x, int z, @Nonnull BiIntToDoubleFunction supplier) { + return this.get(supplier.apply(x, z)); + } + + @Nullable + @Override + public T get(long x, long z, @Nonnull BiLongToDoubleFunction supplier) { + return this.get(supplier.apply(x, z)); + } + + @Nullable + @Override + public T get(double x, double z, @Nonnull BiDoubleToDoubleFunction supplier) { + return this.get(supplier.apply(x, z)); + } + + @Nullable + @Override + public T get(int seed, int x, int z, @Nonnull IWeightedMap.SeedCoordinateFunction supplier, K k) { + return this.get(supplier.apply(seed, x, z, k)); + } + + @Override + public int size() { + return this.keys.length; + } + + @Override + public boolean contains(T obj) { + return this.keySet.contains(obj); + } + + @Override + public void forEach(@Nonnull Consumer consumer) { + for (T o : this.keys) { + consumer.accept(o); + } + } + + @Override + public void forEachEntry(@Nonnull ObjDoubleConsumer consumer) { + for (int i = 0; i < this.keys.length; i++) { + consumer.accept(this.keys[i], this.values[i]); + } + } + + @Nonnull + @Override + public T[] internalKeys() { + return this.keys; + } + + @Nonnull + @Override + public T[] toArray() { + return (T[])Arrays.copyOf(this.keys, this.keys.length); + } + + @Nonnull + @Override + public IWeightedMap resolveKeys(@Nonnull Function mapper, @Nonnull IntFunction arraySupplier) { + K[] array = (K[])arraySupplier.apply(this.keys.length); + + for (int i = 0; i < this.keys.length; i++) { + array[i] = mapper.apply(this.keys[i]); + } + + return new WeightedMap<>(array, this.values, this.sum); + } + + @Nonnull + @Override + public String toString() { + return "WeightedMap{keySet=" + + this.keySet + + ", sum=" + + this.sum + + ", keys=" + + Arrays.toString(this.keys) + + ", values=" + + Arrays.toString(this.values) + + "}"; + } + + public static class Builder { + private final T[] emptyKeys; + private T[] keys; + private double[] values; + private int size; + + private Builder(T[] emptyKeys) { + this.emptyKeys = emptyKeys; + this.keys = emptyKeys; + this.values = ArrayUtil.EMPTY_DOUBLE_ARRAY; + } + + @Nonnull + public WeightedMap.Builder putAll(@Nullable IWeightedMap map) { + if (map != null) { + this.ensureCapacity(map.size()); + map.forEachEntry(this::insert); + } + + return this; + } + + @Nonnull + public WeightedMap.Builder putAll(@Nullable T[] arr, @Nonnull ToDoubleFunction weight) { + if (arr != null && arr.length != 0) { + this.ensureCapacity(arr.length); + + for (T t : arr) { + this.insert(t, weight.applyAsDouble(t)); + } + + return this; + } else { + return this; + } + } + + @Nonnull + public WeightedMap.Builder put(T obj, double weight) { + this.ensureCapacity(1); + this.insert(obj, weight); + return this; + } + + public void ensureCapacity(int toAdd) { + int minCapacity = this.size + toAdd; + int allocated = this.allocated(); + if (minCapacity > allocated) { + int newLength = Math.max(allocated + (allocated >> 1), minCapacity); + this.resize(newLength); + } + } + + private void resize(int newLength) { + this.keys = (T[])Arrays.copyOf(this.keys, newLength); + this.values = Arrays.copyOf(this.values, newLength); + } + + private void insert(T key, double value) { + this.keys[this.size] = key; + this.values[this.size] = value; + this.size++; + } + + public int size() { + return this.size; + } + + private int allocated() { + return this.keys.length; + } + + public void clear() { + this.keys = this.emptyKeys; + this.values = ArrayUtil.EMPTY_DOUBLE_ARRAY; + this.size = 0; + } + + @Nonnull + public IWeightedMap build() { + if (this.size < this.allocated()) { + this.resize(this.size); + } + + if (this.keys.length != 0 && this.keys.length != 1) { + double sum = 0.0; + + for (double value : this.values) { + sum += value; + } + + return new WeightedMap<>(this.keys, this.values, sum); + } else { + return new WeightedMap.SingletonWeightedMap<>(this.keys); + } + } + } + + private static class SingletonWeightedMap implements IWeightedMap { + @Nonnull + protected final T[] keys; + protected final T key; + + private SingletonWeightedMap(@Nonnull T[] keys) { + this.keys = keys; + this.key = keys.length > 0 ? keys[0] : null; + } + + @Override + public T get(double value) { + return this.key; + } + + @Override + public T get(DoubleSupplier supplier) { + return this.key; + } + + @Override + public T get(Random random) { + return this.key; + } + + @Override + public T get(int x, int z, BiIntToDoubleFunction supplier) { + return this.key; + } + + @Override + public T get(long x, long z, BiLongToDoubleFunction supplier) { + return this.key; + } + + @Override + public T get(double x, double z, BiDoubleToDoubleFunction supplier) { + return this.key; + } + + @Override + public T get(int seed, int x, int z, IWeightedMap.SeedCoordinateFunction supplier, K k) { + return this.key; + } + + @Override + public int size() { + return this.keys.length; + } + + @Override + public boolean contains(@Nullable T obj) { + return obj != null && (obj == this.key || obj.equals(this.key)); + } + + @Override + public void forEach(@Nonnull Consumer consumer) { + if (this.key != null) { + consumer.accept(this.key); + } + } + + @Override + public void forEachEntry(@Nonnull ObjDoubleConsumer consumer) { + if (this.key != null) { + consumer.accept(this.key, 1.0); + } + } + + @Nonnull + @Override + public T[] internalKeys() { + return this.keys; + } + + @Nonnull + @Override + public T[] toArray() { + return (T[])Arrays.copyOf(this.keys, this.keys.length); + } + + @Nonnull + @Override + public IWeightedMap resolveKeys(@Nonnull Function mapper, @Nonnull IntFunction arraySupplier) { + K[] array = (K[])arraySupplier.apply(1); + array[0] = mapper.apply(this.key); + return new WeightedMap.SingletonWeightedMap<>(array); + } + + @Nonnull + @Override + public String toString() { + return "SingletonWeightedMap{key=" + this.key + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/common/plugin/AuthorInfo.java b/src/com/hypixel/hytale/common/plugin/AuthorInfo.java new file mode 100644 index 0000000..ff109b6 --- /dev/null +++ b/src/com/hypixel/hytale/common/plugin/AuthorInfo.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.common.plugin; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AuthorInfo { + @Nonnull + public static final Codec CODEC = BuilderCodec.builder(AuthorInfo.class, AuthorInfo::new) + .append(new KeyedCodec<>("Name", Codec.STRING), (authorInfo, s) -> authorInfo.name = s, authorInfo -> authorInfo.name) + .add() + .append(new KeyedCodec<>("Email", Codec.STRING), (authorInfo, s) -> authorInfo.email = s, authorInfo -> authorInfo.email) + .add() + .append(new KeyedCodec<>("Url", Codec.STRING), (authorInfo, s) -> authorInfo.url = s, authorInfo -> authorInfo.url) + .add() + .build(); + @Nullable + private String name; + @Nullable + private String email; + @Nullable + private String url; + + public AuthorInfo() { + } + + @Nullable + public String getName() { + return this.name; + } + + @Nullable + public String getEmail() { + return this.email; + } + + @Nullable + public String getUrl() { + return this.url; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + public void setEmail(@Nullable String email) { + this.email = email; + } + + public void setUrl(@Nullable String url) { + this.url = url; + } + + @Nonnull + @Override + public String toString() { + return "AuthorInfo{name='" + this.name + "', email='" + this.email + "', url='" + this.url + "'}"; + } +} diff --git a/src/com/hypixel/hytale/common/plugin/PluginIdentifier.java b/src/com/hypixel/hytale/common/plugin/PluginIdentifier.java new file mode 100644 index 0000000..3b1fbbd --- /dev/null +++ b/src/com/hypixel/hytale/common/plugin/PluginIdentifier.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.common.plugin; + +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PluginIdentifier { + @Nonnull + private final String group; + @Nonnull + private final String name; + + public PluginIdentifier(@Nonnull String group, @Nonnull String name) { + this.group = group; + this.name = name; + } + + public PluginIdentifier(@Nonnull PluginManifest manifest) { + this(manifest.getGroup(), manifest.getName()); + } + + @Nonnull + public String getGroup() { + return this.group; + } + + @Nonnull + public String getName() { + return this.name; + } + + @Override + public int hashCode() { + int result = this.group.hashCode(); + return 31 * result + this.name.hashCode(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + PluginIdentifier that = (PluginIdentifier)o; + return !Objects.equals(this.group, that.group) ? false : Objects.equals(this.name, that.name); + } else { + return false; + } + } + + @Nonnull + @Override + public String toString() { + return this.group + ":" + this.name; + } + + @Nonnull + public static PluginIdentifier fromString(@Nonnull String str) { + String[] split = str.split(":"); + if (split.length != 2) { + throw new IllegalArgumentException("String does not match :"); + } else { + return new PluginIdentifier(split[0], split[1]); + } + } +} diff --git a/src/com/hypixel/hytale/common/plugin/PluginManifest.java b/src/com/hypixel/hytale/common/plugin/PluginManifest.java new file mode 100644 index 0000000..b65d9c2 --- /dev/null +++ b/src/com/hypixel/hytale/common/plugin/PluginManifest.java @@ -0,0 +1,389 @@ +package com.hypixel.hytale.common.plugin; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.ObjectMapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.semver.Semver; +import com.hypixel.hytale.common.semver.SemverRange; +import com.hypixel.hytale.common.util.java.ManifestUtil; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PluginManifest { + @Nonnull + private static final BuilderCodec.Builder BUILDER = BuilderCodec.builder(PluginManifest.class, PluginManifest::new); + @Nonnull + public static final Codec CODEC = BUILDER.append( + new KeyedCodec<>("Group", Codec.STRING), (manifest, o) -> manifest.group = o, manifest -> manifest.group + ) + .add() + .append(new KeyedCodec<>("Name", Codec.STRING), (manifest, o) -> manifest.name = o, manifest -> manifest.name) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Version", Semver.CODEC), (manifest, o) -> manifest.version = o, manifest -> manifest.version) + .add() + .append(new KeyedCodec<>("Description", Codec.STRING), (manifest, o) -> manifest.description = o, manifest -> manifest.description) + .add() + .append( + new KeyedCodec<>("Authors", new ArrayCodec<>(AuthorInfo.CODEC, AuthorInfo[]::new)), + (manifest, o) -> manifest.authors = List.of(o), + manifest -> manifest.authors.toArray(AuthorInfo[]::new) + ) + .add() + .append(new KeyedCodec<>("Website", Codec.STRING), (manifest, o) -> manifest.website = o, manifest -> manifest.website) + .add() + .append(new KeyedCodec<>("Main", Codec.STRING), (manifest, o) -> manifest.main = o, manifest -> manifest.main) + .add() + .append(new KeyedCodec<>("ServerVersion", SemverRange.CODEC), (manifest, o) -> manifest.serverVersion = o, manifest -> manifest.serverVersion) + .add() + .append( + new KeyedCodec<>( + "Dependencies", + new ObjectMapCodec<>(SemverRange.CODEC, Object2ObjectLinkedOpenHashMap::new, PluginIdentifier::toString, PluginIdentifier::fromString) + ), + (manifest, o) -> manifest.dependencies = o, + manifest -> manifest.dependencies + ) + .add() + .append( + new KeyedCodec<>( + "OptionalDependencies", + new ObjectMapCodec<>(SemverRange.CODEC, Object2ObjectLinkedOpenHashMap::new, PluginIdentifier::toString, PluginIdentifier::fromString) + ), + (manifest, o) -> manifest.optionalDependencies = o, + manifest -> manifest.optionalDependencies + ) + .add() + .append( + new KeyedCodec<>( + "LoadBefore", + new ObjectMapCodec<>(SemverRange.CODEC, Object2ObjectLinkedOpenHashMap::new, PluginIdentifier::toString, PluginIdentifier::fromString) + ), + (manifest, o) -> manifest.loadBefore = o, + manifest -> manifest.loadBefore + ) + .add() + .append(new KeyedCodec<>("DisabledByDefault", Codec.BOOLEAN), (manifest, o) -> manifest.disabledByDefault = o, manifest -> manifest.disabledByDefault) + .add() + .append(new KeyedCodec<>("IncludesAssetPack", Codec.BOOLEAN), (manifest, o) -> manifest.includesAssetPack = o, o -> o.includesAssetPack) + .add() + .build(); + @Nonnull + public static final Codec ARRAY_CODEC = new ArrayCodec<>(CODEC, PluginManifest[]::new); + private static final String CORE_GROUP = "Hytale"; + private static final Semver CORE_VERSION = ManifestUtil.getVersion() == null ? Semver.fromString("0.0.0-dev") : ManifestUtil.getVersion(); + private String group; + private String name; + private Semver version; + @Nullable + private String description; + @Nonnull + private List authors = new ObjectArrayList<>(); + @Nullable + private String website; + @Nullable + private String main; + private SemverRange serverVersion; + @Nonnull + private Map dependencies = new Object2ObjectLinkedOpenHashMap<>(); + @Nonnull + private Map optionalDependencies = new Object2ObjectLinkedOpenHashMap<>(); + @Nonnull + private Map loadBefore = new Object2ObjectLinkedOpenHashMap<>(); + @Nonnull + private List subPlugins = new ArrayList<>(); + private boolean disabledByDefault = false; + private boolean includesAssetPack = false; + + public PluginManifest() { + } + + public PluginManifest( + @Nonnull String group, + @Nonnull String name, + @Nonnull Semver version, + @Nullable String description, + @Nonnull List authors, + @Nullable String website, + @Nullable String main, + @Nullable SemverRange serverVersion, + @Nonnull Map dependencies, + @Nonnull Map optionalDependencies, + @Nonnull Map loadBefore, + @Nonnull List subPlugins, + boolean disabledByDefault + ) { + this.group = group; + this.name = name; + this.version = version; + this.description = description; + this.authors = authors; + this.website = website; + this.main = main; + this.serverVersion = serverVersion; + this.dependencies = dependencies; + this.optionalDependencies = optionalDependencies; + this.loadBefore = loadBefore; + this.subPlugins = subPlugins; + this.disabledByDefault = disabledByDefault; + } + + public String getGroup() { + return this.group; + } + + public String getName() { + return this.name; + } + + public Semver getVersion() { + return this.version; + } + + @Nullable + public String getDescription() { + return this.description; + } + + @Nonnull + public List getAuthors() { + return Collections.unmodifiableList(this.authors); + } + + @Nullable + public String getWebsite() { + return this.website; + } + + public void setGroup(@Nonnull String group) { + this.group = group; + } + + public void setName(@Nonnull String name) { + this.name = name; + } + + public void setVersion(@Nullable Semver version) { + this.version = version; + } + + public void setDescription(@Nullable String description) { + this.description = description; + } + + public void setAuthors(@Nonnull List authors) { + this.authors = authors; + } + + public void setWebsite(@Nullable String website) { + this.website = website; + } + + @Nullable + public String getMain() { + return this.main; + } + + public SemverRange getServerVersion() { + return this.serverVersion; + } + + @Nonnull + public Map getDependencies() { + return Collections.unmodifiableMap(this.dependencies); + } + + public void injectDependency(PluginIdentifier identifier, SemverRange range) { + this.dependencies.put(identifier, range); + } + + @Nonnull + public Map getOptionalDependencies() { + return Collections.unmodifiableMap(this.optionalDependencies); + } + + @Nonnull + public Map getLoadBefore() { + return Collections.unmodifiableMap(this.loadBefore); + } + + public boolean isDisabledByDefault() { + return this.disabledByDefault; + } + + public boolean includesAssetPack() { + return this.includesAssetPack; + } + + @Nonnull + public List getSubPlugins() { + return Collections.unmodifiableList(this.subPlugins); + } + + public void inherit(@Nonnull PluginManifest manifest) { + if (this.group == null) { + this.group = manifest.group; + } + + if (this.version == null) { + this.version = manifest.version; + } + + if (this.description == null) { + this.description = manifest.description; + } + + if (this.authors.isEmpty()) { + this.authors = manifest.authors; + } + + if (this.website == null) { + this.website = manifest.website; + } + + if (!this.disabledByDefault) { + this.disabledByDefault = manifest.disabledByDefault; + } + + this.dependencies.put(new PluginIdentifier(manifest), SemverRange.fromString(manifest.version.toString())); + } + + @Nonnull + @Override + public String toString() { + return "PluginManifest{group='" + + this.group + + "', name='" + + this.name + + "', version='" + + this.version + + "', description='" + + this.description + + "', authors=" + + this.authors + + ", website='" + + this.website + + "', main='" + + this.main + + "', serverVersion=" + + this.serverVersion + + ", dependencies=" + + this.dependencies + + ", optionalDependencies=" + + this.optionalDependencies + + ", disabledByDefault=" + + this.disabledByDefault + + "}"; + } + + @Nonnull + public static PluginManifest.CoreBuilder corePlugin(@Nonnull Class pluginClass) { + return new PluginManifest.CoreBuilder("Hytale", pluginClass.getSimpleName(), CORE_VERSION, pluginClass.getName()); + } + + static { + BUILDER.append( + new KeyedCodec<>("SubPlugins", new ArrayCodec<>(CODEC, PluginManifest[]::new)), + (manifest, o) -> manifest.subPlugins = List.of(o), + manifest -> manifest.subPlugins.toArray(PluginManifest[]::new) + ) + .add(); + } + + public static class CoreBuilder { + private static final String CORE_GROUP = "Hytale"; + private static final Semver CORE_VERSION = ManifestUtil.getVersion() == null ? Semver.fromString("0.0.0-dev") : ManifestUtil.getVersion(); + @Nonnull + private final String group; + @Nonnull + private final String name; + @Nonnull + private final Semver version; + @Nullable + private String description; + @Nonnull + private final String main; + @Nonnull + private final Map dependencies = new Object2ObjectLinkedOpenHashMap<>(); + @Nonnull + private final Map optionalDependencies = new Object2ObjectLinkedOpenHashMap<>(); + @Nonnull + private final Map loadBefore = new Object2ObjectLinkedOpenHashMap<>(); + + @Nonnull + public static PluginManifest.CoreBuilder corePlugin(@Nonnull Class pluginClass) { + return new PluginManifest.CoreBuilder("Hytale", pluginClass.getSimpleName(), CORE_VERSION, pluginClass.getName()); + } + + private CoreBuilder(@Nonnull String group, @Nonnull String name, @Nonnull Semver version, @Nonnull String main) { + this.group = group; + this.name = name; + this.version = version; + this.main = main; + } + + @Nonnull + public PluginManifest.CoreBuilder description(@Nonnull String description) { + this.description = description; + return this; + } + + @Nonnull + @SafeVarargs + public final PluginManifest.CoreBuilder depends(@Nonnull Class... dependencies) { + for (Class dependency : dependencies) { + this.dependencies.put(new PluginIdentifier("Hytale", dependency.getSimpleName()), SemverRange.WILDCARD); + } + + return this; + } + + @Nonnull + @SafeVarargs + public final PluginManifest.CoreBuilder optDepends(@Nonnull Class... dependencies) { + for (Class optionalDependency : dependencies) { + this.optionalDependencies.put(new PluginIdentifier("Hytale", optionalDependency.getSimpleName()), SemverRange.WILDCARD); + } + + return this; + } + + @Nonnull + @SafeVarargs + public final PluginManifest.CoreBuilder loadsBefore(@Nonnull Class... plugins) { + for (Class plugin : plugins) { + this.loadBefore.put(new PluginIdentifier("Hytale", plugin.getSimpleName()), SemverRange.WILDCARD); + } + + return this; + } + + @Nonnull + public PluginManifest build() { + return new PluginManifest( + this.group, + this.name, + this.version, + this.description, + Collections.emptyList(), + null, + this.main, + null, + this.dependencies, + this.optionalDependencies, + this.loadBefore, + Collections.emptyList(), + false + ); + } + } +} diff --git a/src/com/hypixel/hytale/common/semver/Semver.java b/src/com/hypixel/hytale/common/semver/Semver.java new file mode 100644 index 0000000..b293a4f --- /dev/null +++ b/src/com/hypixel/hytale/common/semver/Semver.java @@ -0,0 +1,237 @@ +package com.hypixel.hytale.common.semver; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.function.FunctionCodec; +import com.hypixel.hytale.common.util.StringUtil; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Semver implements Comparable { + public static final Codec CODEC = new FunctionCodec<>(Codec.STRING, Semver::fromString, Semver::toString); + private final long major; + private final long minor; + private final long patch; + private final String[] preRelease; + private final String build; + + public Semver(long major, long minor, long patch) { + this(major, minor, patch, null, null); + } + + public Semver(long major, long minor, long patch, String[] preRelease, String build) { + if (major < 0L) { + throw new IllegalArgumentException("Major must be a non-negative integers"); + } else if (minor < 0L) { + throw new IllegalArgumentException("Major must be a non-negative integers"); + } else if (patch < 0L) { + throw new IllegalArgumentException("Major must be a non-negative integers"); + } else { + validatePreRelease(preRelease); + validateBuild(build); + this.major = major; + this.minor = minor; + this.patch = patch; + this.preRelease = preRelease; + this.build = build; + } + } + + public long getMajor() { + return this.major; + } + + public long getMinor() { + return this.minor; + } + + public long getPatch() { + return this.patch; + } + + public String[] getPreRelease() { + return (String[])this.preRelease.clone(); + } + + public String getBuild() { + return this.build; + } + + public boolean satisfies(@Nonnull SemverRange range) { + return range.satisfies(this); + } + + public int compareTo(@Nonnull Semver other) { + if (this.major != other.major) { + return Long.compare(this.major, other.major); + } else if (this.minor != other.minor) { + return Long.compare(this.minor, other.minor); + } else if (this.patch != other.patch) { + return Long.compare(this.patch, other.patch); + } else if (this.preRelease == null || other.preRelease != null && other.preRelease.length != 0) { + if ((this.preRelease == null || this.preRelease.length == 0) && other.preRelease != null) { + return 1; + } else if (this.preRelease == null) { + return 0; + } else { + int i; + for (i = 0; i < this.preRelease.length && i < other.preRelease.length; i++) { + String pre = this.preRelease[i]; + String otherPre = other.preRelease[i]; + if (StringUtil.isNumericString(pre) && StringUtil.isNumericString(otherPre)) { + int compare = Integer.compare(Integer.parseInt(pre), Integer.parseInt(otherPre)); + if (compare != 0) { + return compare; + } + } else { + int compare = pre.compareTo(otherPre); + if (compare != 0) { + return compare; + } + } + } + + if (this.preRelease.length > i) { + return 1; + } else { + return other.preRelease.length > i ? -1 : 0; + } + } + } else { + return -1; + } + } + + @Nonnull + @Override + public String toString() { + StringBuilder ver = new StringBuilder().append(this.major).append('.').append(this.minor).append('.').append(this.patch); + if (this.preRelease != null && this.preRelease.length > 0) { + ver.append('-').append(String.join(".", this.preRelease)); + } + + if (this.build != null && !this.build.isEmpty()) { + ver.append('+').append(this.build); + } + + return ver.toString(); + } + + @Nonnull + public static Semver fromString(String str) { + return fromString(str, false); + } + + @Nonnull + public static Semver fromString(String str, boolean strict) { + Objects.requireNonNull(str, "String can't be null!"); + str = str.trim(); + if (str.isEmpty()) { + throw new IllegalArgumentException("String is empty!"); + } else { + if (str.charAt(0) == '=' || str.charAt(0) == 'v') { + str = str.substring(1); + } + + if (str.charAt(0) == '=' || str.charAt(0) == 'v') { + str = str.substring(1); + } + + str = str.trim(); + if (str.isEmpty()) { + throw new IllegalArgumentException("String is empty!"); + } else { + String build = null; + if (str.contains("+")) { + String[] buildSplit = str.split("\\+", 2); + str = buildSplit[0]; + build = buildSplit[1]; + validateBuild(build); + } + + String[] preRelease = null; + if (str.contains("-")) { + String[] preReleaseSplit = str.split("-", 2); + str = preReleaseSplit[0]; + preRelease = preReleaseSplit[1].split("\\."); + validatePreRelease(preRelease); + } + + if (str.isEmpty() || str.charAt(0) != '.' && str.charAt(str.length() - 1) != '.') { + String[] split = str.split("\\."); + if (split.length < 1) { + throw new IllegalArgumentException("String doesn't match .. (" + str + ")"); + } else { + long major = Long.parseLong(split[0]); + if (major < 0L) { + throw new IllegalArgumentException("Major must be a non-negative integers (" + str + ")"); + } else if (!strict && split.length == 1) { + return new Semver(major, 0L, 0L, preRelease, build); + } else if (split.length < 2) { + throw new IllegalArgumentException("String doesn't match .. (" + str + ")"); + } else { + long minor = Long.parseLong(split[1]); + if (minor < 0L) { + throw new IllegalArgumentException("Minor must be a non-negative integers (" + str + ")"); + } else if (!strict && split.length == 2) { + return new Semver(major, minor, 0L, preRelease, build); + } else if (split.length != 3) { + throw new IllegalArgumentException("String doesn't match .. (" + str + ")"); + } else { + String patchStr = split[2]; + if (!strict && preRelease == null) { + String pre = ""; + StringBuilder s = new StringBuilder(); + + for (int i = 0; i < patchStr.length(); i++) { + char c = patchStr.charAt(i); + if (!Character.isDigit(c)) { + pre = patchStr.substring(i); + patchStr = s.toString(); + break; + } + + s.append(c); + } + + if (!pre.trim().isEmpty()) { + preRelease = pre.split("\\."); + validatePreRelease(preRelease); + } + } + + long patch = Long.parseLong(patchStr); + if (patch < 0L) { + throw new IllegalArgumentException("Patch must be a non-negative integers (" + str + ")"); + } else { + return new Semver(major, minor, patch, preRelease, build); + } + } + } + } + } else { + throw new IllegalArgumentException("Failed to parse digits (" + str + ")"); + } + } + } + } + + private static void validateBuild(@Nullable String build) { + if (build != null) { + if (build.isEmpty() || !StringUtil.isAlphaNumericHyphenString(build)) { + throw new IllegalArgumentException("Build must only be alphanumeric (" + build + ")"); + } + } + } + + private static void validatePreRelease(@Nullable String[] preRelease) { + if (preRelease != null) { + for (String preReleasePart : preRelease) { + if (preReleasePart.isEmpty() || !StringUtil.isAlphaNumericHyphenString(preReleasePart)) { + throw new IllegalArgumentException("Pre-release must only be alphanumeric (" + Arrays.toString((Object[])preRelease) + ")"); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/common/semver/SemverComparator.java b/src/com/hypixel/hytale/common/semver/SemverComparator.java new file mode 100644 index 0000000..c523ef1 --- /dev/null +++ b/src/com/hypixel/hytale/common/semver/SemverComparator.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.common.semver; + +import java.util.Objects; +import java.util.function.BiPredicate; +import javax.annotation.Nonnull; + +public class SemverComparator implements SemverSatisfies { + private final SemverComparator.ComparisonType comparisonType; + private final Semver compareTo; + + public SemverComparator(SemverComparator.ComparisonType comparisonType, Semver compareTo) { + this.comparisonType = comparisonType; + this.compareTo = compareTo; + } + + @Override + public boolean satisfies(Semver semver) { + return this.comparisonType.satisfies(this.compareTo, semver); + } + + @Nonnull + @Override + public String toString() { + return this.comparisonType.getPrefix() + this.compareTo; + } + + @Nonnull + public static SemverComparator fromString(String str) { + Objects.requireNonNull(str, "String can't be null!"); + str = str.trim(); + if (str.isEmpty()) { + throw new IllegalArgumentException("String is empty!"); + } else { + for (SemverComparator.ComparisonType comparisonType : SemverComparator.ComparisonType.values()) { + if (str.startsWith(comparisonType.getPrefix())) { + Semver semver = Semver.fromString(str.substring(comparisonType.getPrefix().length())); + return new SemverComparator(comparisonType, semver); + } + } + + throw new IllegalArgumentException("Invalid comparator type! " + str); + } + } + + public static enum ComparisonType { + GTE(">=", (ct, s) -> ct.compareTo(s) <= 0), + GT(">", (ct, s) -> ct.compareTo(s) < 0), + LTE("<=", (ct, s) -> ct.compareTo(s) >= 0), + LT("<", (ct, s) -> ct.compareTo(s) > 0), + EQUAL("=", (ct, s) -> ct.compareTo(s) == 0); + + private final String prefix; + private final BiPredicate satisfies; + + private ComparisonType(String prefix, BiPredicate satisfies) { + this.prefix = prefix; + this.satisfies = satisfies; + } + + public String getPrefix() { + return this.prefix; + } + + public boolean satisfies(Semver compareTo, Semver semver) { + return this.satisfies.test(compareTo, semver); + } + + public static boolean hasAPrefix(@Nonnull String range) { + for (SemverComparator.ComparisonType comparisonType : values()) { + if (range.startsWith(comparisonType.prefix)) { + return true; + } + } + + return false; + } + } +} diff --git a/src/com/hypixel/hytale/common/semver/SemverRange.java b/src/com/hypixel/hytale/common/semver/SemverRange.java new file mode 100644 index 0000000..0bfd784 --- /dev/null +++ b/src/com/hypixel/hytale/common/semver/SemverRange.java @@ -0,0 +1,170 @@ +package com.hypixel.hytale.common.semver; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.function.FunctionCodec; +import java.util.Objects; +import java.util.StringJoiner; +import javax.annotation.Nonnull; + +public class SemverRange implements SemverSatisfies { + public static final Codec CODEC = new FunctionCodec<>(Codec.STRING, SemverRange::fromString, SemverRange::toString); + public static final SemverRange WILDCARD = new SemverRange(new SemverSatisfies[0], true); + private final SemverSatisfies[] comparators; + private final boolean and; + + public SemverRange(SemverSatisfies[] comparators, boolean and) { + this.comparators = comparators; + this.and = and; + } + + @Override + public boolean satisfies(Semver semver) { + if (this.and) { + for (SemverSatisfies comparator : this.comparators) { + if (!comparator.satisfies(semver)) { + return false; + } + } + + return true; + } else { + for (SemverSatisfies comparatorx : this.comparators) { + if (comparatorx.satisfies(semver)) { + return true; + } + } + + return false; + } + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(" || "); + + for (SemverSatisfies comparator : this.comparators) { + joiner.add(comparator.toString()); + } + + return joiner.toString(); + } + + @Nonnull + public static SemverRange fromString(String str) { + return fromString(str, false); + } + + @Nonnull + public static SemverRange fromString(String str, boolean strict) { + Objects.requireNonNull(str, "String can't be null!"); + str = str.trim(); + if (!str.isBlank() && !"*".equals(str)) { + String[] split = str.split("\\|\\|"); + SemverSatisfies[] comparators = new SemverSatisfies[split.length]; + + for (int i = 0; i < split.length; i++) { + String subRange = split[i].trim(); + if (subRange.contains(" - ")) { + String[] range = subRange.split(" - "); + if (range.length != 2) { + throw new IllegalArgumentException("Range has an invalid number of arguments!"); + } + + comparators[i] = new SemverRange( + new SemverSatisfies[]{ + new SemverComparator(SemverComparator.ComparisonType.GTE, Semver.fromString(range[0], strict)), + new SemverComparator(SemverComparator.ComparisonType.LTE, Semver.fromString(range[1], strict)) + }, + true + ); + } else if (subRange.charAt(0) == '~') { + Semver semver = Semver.fromString(subRange.substring(1), strict); + if (semver.getMinor() > 0L) { + comparators[i] = new SemverRange( + new SemverSatisfies[]{ + new SemverComparator(SemverComparator.ComparisonType.GTE, semver), + new SemverComparator(SemverComparator.ComparisonType.LT, new Semver(semver.getMajor(), semver.getMinor() + 1L, 0L, null, null)) + }, + true + ); + } else { + comparators[i] = new SemverRange( + new SemverSatisfies[]{ + new SemverComparator(SemverComparator.ComparisonType.GTE, semver), + new SemverComparator(SemverComparator.ComparisonType.LT, new Semver(semver.getMajor() + 1L, 0L, 0L, null, null)) + }, + true + ); + } + } else if (subRange.charAt(0) == '^') { + Semver semver = Semver.fromString(subRange.substring(1), strict); + if (semver.getMajor() > 0L) { + comparators[i] = new SemverRange( + new SemverSatisfies[]{ + new SemverComparator(SemverComparator.ComparisonType.GTE, semver), + new SemverComparator(SemverComparator.ComparisonType.LT, new Semver(semver.getMajor() + 1L, 0L, 0L, null, null)) + }, + true + ); + } else if (semver.getMinor() > 0L) { + comparators[i] = new SemverRange( + new SemverSatisfies[]{ + new SemverComparator(SemverComparator.ComparisonType.GTE, semver), + new SemverComparator(SemverComparator.ComparisonType.LT, new Semver(0L, semver.getMinor() + 1L, 0L, null, null)) + }, + true + ); + } else { + comparators[i] = new SemverRange( + new SemverSatisfies[]{ + new SemverComparator(SemverComparator.ComparisonType.GTE, semver), + new SemverComparator(SemverComparator.ComparisonType.LT, new Semver(0L, 0L, semver.getPatch() + 1L, null, null)) + }, + true + ); + } + } else if (SemverComparator.ComparisonType.hasAPrefix(subRange)) { + comparators[i] = SemverComparator.fromString(subRange); + } else if (!subRange.contains(" ")) { + Semver semver = Semver.fromString(subRange.replace("x", "0").replace("*", "0"), strict); + if (semver.getPatch() == 0L && semver.getMinor() == 0L && semver.getMajor() == 0L) { + comparators[i] = new SemverComparator(SemverComparator.ComparisonType.GTE, new Semver(0L, 0L, 0L)); + } else if (semver.getPatch() == 0L && semver.getMinor() == 0L) { + comparators[i] = new SemverRange( + new SemverSatisfies[]{ + new SemverComparator(SemverComparator.ComparisonType.GTE, semver), + new SemverComparator(SemverComparator.ComparisonType.LT, new Semver(semver.getMajor() + 1L, 0L, 0L, null, null)) + }, + true + ); + } else { + if (semver.getPatch() != 0L) { + throw new IllegalArgumentException("Invalid X-Range! " + subRange); + } + + comparators[i] = new SemverRange( + new SemverSatisfies[]{ + new SemverComparator(SemverComparator.ComparisonType.GTE, semver), + new SemverComparator(SemverComparator.ComparisonType.LT, new Semver(semver.getMajor(), semver.getMinor() + 1L, 0L, null, null)) + }, + true + ); + } + } else { + String[] comparatorStrings = subRange.split(" "); + SemverSatisfies[] comparatorsAnd = new SemverSatisfies[comparatorStrings.length]; + + for (int y = 0; y < comparatorStrings.length; y++) { + comparatorsAnd[i] = SemverComparator.fromString(comparatorStrings[i]); + } + + comparators[i] = new SemverRange(comparatorsAnd, true); + } + } + + return new SemverRange(comparators, false); + } else { + return WILDCARD; + } + } +} diff --git a/src/com/hypixel/hytale/common/semver/SemverSatisfies.java b/src/com/hypixel/hytale/common/semver/SemverSatisfies.java new file mode 100644 index 0000000..f99a834 --- /dev/null +++ b/src/com/hypixel/hytale/common/semver/SemverSatisfies.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.common.semver; + +public interface SemverSatisfies { + boolean satisfies(Semver var1); +} diff --git a/src/com/hypixel/hytale/common/thread/HytaleForkJoinThreadFactory.java b/src/com/hypixel/hytale/common/thread/HytaleForkJoinThreadFactory.java new file mode 100644 index 0000000..c899867 --- /dev/null +++ b/src/com/hypixel/hytale/common/thread/HytaleForkJoinThreadFactory.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.common.thread; + +import com.hypixel.hytale.metrics.InitStackThread; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; +import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; +import javax.annotation.Nonnull; + +public class HytaleForkJoinThreadFactory implements ForkJoinWorkerThreadFactory { + public HytaleForkJoinThreadFactory() { + } + + @Nonnull + @Override + public ForkJoinWorkerThread newThread(@Nonnull ForkJoinPool pool) { + return new HytaleForkJoinThreadFactory.WorkerThread(pool); + } + + public static class WorkerThread extends ForkJoinWorkerThread implements InitStackThread { + @Nonnull + private final StackTraceElement[] initStack = Thread.currentThread().getStackTrace(); + + protected WorkerThread(@Nonnull ForkJoinPool pool) { + super(pool); + } + + @Nonnull + @Override + public StackTraceElement[] getInitStack() { + return this.initStack; + } + } +} diff --git a/src/com/hypixel/hytale/common/thread/ticking/Tickable.java b/src/com/hypixel/hytale/common/thread/ticking/Tickable.java new file mode 100644 index 0000000..2d7bba4 --- /dev/null +++ b/src/com/hypixel/hytale/common/thread/ticking/Tickable.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.common.thread.ticking; + +public interface Tickable { + void tick(float var1); +} diff --git a/src/com/hypixel/hytale/common/tuple/BoolDoublePair.java b/src/com/hypixel/hytale/common/tuple/BoolDoublePair.java new file mode 100644 index 0000000..d888e7b --- /dev/null +++ b/src/com/hypixel/hytale/common/tuple/BoolDoublePair.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.common.tuple; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BoolDoublePair implements Comparable { + private final boolean left; + private final double right; + + public BoolDoublePair(boolean left, double right) { + this.left = left; + this.right = right; + } + + public final boolean getKey() { + return this.getLeft(); + } + + public boolean getLeft() { + return this.left; + } + + public final double getValue() { + return this.getRight(); + } + + public double getRight() { + return this.right; + } + + public int compareTo(@Nonnull BoolDoublePair other) { + int compare = Boolean.compare(this.left, other.left); + return compare != 0 ? compare : Double.compare(this.right, other.right); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + BoolDoublePair that = (BoolDoublePair)o; + return this.left != that.left ? false : Double.compare(that.right, this.right) == 0; + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.left ? 1 : 0; + long temp = Double.doubleToLongBits(this.right); + return 31 * result + (int)(temp ^ temp >>> 32); + } + + @Nonnull + @Override + public String toString() { + return "(" + this.getLeft() + "," + this.getRight() + ")"; + } + + @Nonnull + public String toString(@Nonnull String format) { + return String.format(format, this.getLeft(), this.getRight()); + } + + @Nonnull + public static BoolDoublePair of(boolean left, double right) { + return new BoolDoublePair(left, right); + } +} diff --git a/src/com/hypixel/hytale/common/tuple/BoolIntPair.java b/src/com/hypixel/hytale/common/tuple/BoolIntPair.java new file mode 100644 index 0000000..b89745f --- /dev/null +++ b/src/com/hypixel/hytale/common/tuple/BoolIntPair.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.common.tuple; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BoolIntPair implements Comparable { + private final boolean left; + private final int right; + + public BoolIntPair(boolean left, int right) { + this.left = left; + this.right = right; + } + + public final boolean getKey() { + return this.getLeft(); + } + + public boolean getLeft() { + return this.left; + } + + public final int getValue() { + return this.getRight(); + } + + public int getRight() { + return this.right; + } + + public int compareTo(@Nonnull BoolIntPair other) { + int compare = Boolean.compare(this.left, other.left); + return compare != 0 ? compare : Integer.compare(this.right, other.right); + } + + @Override + public int hashCode() { + int result = this.left ? 1 : 0; + return 31 * result + this.right; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + BoolIntPair that = (BoolIntPair)o; + return this.left != that.left ? false : this.right == that.right; + } else { + return false; + } + } + + @Nonnull + @Override + public String toString() { + return "(" + this.getLeft() + "," + this.getRight() + ")"; + } + + @Nonnull + public String toString(@Nonnull String format) { + return String.format(format, this.getLeft(), this.getRight()); + } + + @Nonnull + public static BoolIntPair of(boolean left, int right) { + return new BoolIntPair(left, right); + } +} diff --git a/src/com/hypixel/hytale/common/util/ArrayUtil.java b/src/com/hypixel/hytale/common/util/ArrayUtil.java new file mode 100644 index 0000000..29ef6f9 --- /dev/null +++ b/src/com/hypixel/hytale/common/util/ArrayUtil.java @@ -0,0 +1,221 @@ +package com.hypixel.hytale.common.util; + +import com.hypixel.hytale.function.predicate.UnaryBiPredicate; +import com.hypixel.hytale.math.util.MathUtil; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Objects; +import java.util.Random; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ArrayUtil { + public static final String[] EMPTY_STRING_ARRAY = new String[0]; + public static final double[] EMPTY_DOUBLE_ARRAY = new double[0]; + public static final int[] EMPTY_INT_ARRAY = new int[0]; + public static final long[] EMPTY_LONG_ARRAY = new long[0]; + public static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0]; + public static final Integer[] EMPTY_INTEGER_ARRAY = new Integer[0]; + public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + public static final BitSet[] EMPTY_BITSET_ARRAY = new BitSet[0]; + public static final float[] EMPTY_FLOAT_ARRAY = new float[0]; + private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + private static final Supplier[] EMPTY_SUPPLIER_ARRAY = new Supplier[0]; + private static final Entry[] EMPTY_ENTRY_ARRAY = new Entry[0]; + + public ArrayUtil() { + } + + @Nonnull + public static T[] emptyArray() { + return (T[])EMPTY_OBJECT_ARRAY; + } + + @Nonnull + public static Supplier[] emptySupplierArray() { + return EMPTY_SUPPLIER_ARRAY; + } + + @Nonnull + public static Entry[] emptyEntryArray() { + return EMPTY_ENTRY_ARRAY; + } + + public static int grow(int oldSize) { + return (int)MathUtil.clamp((long)oldSize + (oldSize >> 1), 2L, 2147483639L); + } + + public static EndType[] copyAndMutate( + @Nullable StartType[] array, @Nonnull Function adapter, @Nonnull IntFunction arrayProvider + ) { + if (array == null) { + return null; + } else { + EndType[] endArray = (EndType[])arrayProvider.apply(array.length); + + for (int i = 0; i < endArray.length; i++) { + endArray[i] = adapter.apply(array[i]); + } + + return endArray; + } + } + + @Nullable + public static T[] combine(@Nullable T[] a1, @Nullable T[] a2) { + if (a1 == null || a1.length == 0) { + return a2; + } else if (a2 != null && a2.length != 0) { + T[] newArray = (T[])Arrays.copyOf(a1, a1.length + a2.length); + System.arraycopy(a2, 0, newArray, a1.length, a2.length); + return newArray; + } else { + return a1; + } + } + + @Nonnull + public static T[] append(@Nullable T[] arr, @Nonnull T t) { + if (arr == null) { + T[] newArray = (T[])Array.newInstance(t.getClass(), 1); + newArray[0] = t; + return newArray; + } else { + T[] newArray = (T[])Arrays.copyOf(arr, arr.length + 1); + newArray[arr.length] = t; + return newArray; + } + } + + @Nonnull + public static T[] remove(@Nonnull T[] arr, int index) { + int newLength = arr.length - 1; + T[] newArray = (T[])Array.newInstance(arr.getClass().getComponentType(), newLength); + System.arraycopy(arr, 0, newArray, 0, index); + if (index < newLength) { + System.arraycopy(arr, index + 1, newArray, index, newLength - index); + } + + return newArray; + } + + public static boolean startsWith(@Nonnull byte[] array, @Nonnull byte[] start) { + if (start.length > array.length) { + return false; + } else { + for (int i = 0; i < start.length; i++) { + if (array[i] != start[i]) { + return false; + } + } + + return true; + } + } + + public static boolean equals(@Nullable T[] a, @Nullable T[] a2, @Nonnull UnaryBiPredicate predicate) { + if (a == a2) { + return true; + } else if (a != null && a2 != null) { + int length = a.length; + if (a2.length != length) { + return false; + } else { + for (int i = 0; i < length; i++) { + T o1 = a[i]; + T o2 = a2[i]; + if (o1 == null ? o2 != null : !predicate.test(o1, o2)) { + return false; + } + } + + return true; + } + } else { + return false; + } + } + + @Nonnull + public static T[][] split(@Nonnull T[] data, int size) { + Class aClass = (Class)data.getClass(); + T[][] ret = (T[][])Array.newInstance(aClass.getComponentType(), MathUtil.ceil((double)data.length / size), 0); + int start = 0; + + for (int i = 0; i < ret.length; i++) { + ret[i] = (T[])Arrays.copyOfRange(data, start, Math.min(start + size, data.length)); + start += size; + } + + return ret; + } + + public static byte[][] split(@Nonnull byte[] data, int size) { + byte[][] ret = new byte[MathUtil.ceil((double)data.length / size)][]; + int start = 0; + + for (int i = 0; i < ret.length; i++) { + ret[i] = Arrays.copyOfRange(data, start, Math.min(start + size, data.length)); + start += size; + } + + return ret; + } + + public static void shuffleArray(@Nonnull int[] ar, int from, int to, @Nonnull Random rnd) { + Objects.checkFromToIndex(from, to, ar.length); + + for (int i = to - 1; i > from; i--) { + int index = rnd.nextInt(i + 1 - from) + from; + int a = ar[index]; + ar[index] = ar[i]; + ar[i] = a; + } + } + + public static void shuffleArray(@Nonnull byte[] ar, int from, int to, @Nonnull Random rnd) { + Objects.checkFromToIndex(from, to, ar.length); + + for (int i = to - 1; i > from; i--) { + int index = rnd.nextInt(i + 1 - from) + from; + byte a = ar[index]; + ar[index] = ar[i]; + ar[i] = a; + } + } + + public static boolean contains(@Nonnull T[] array, @Nullable T obj) { + return indexOf(array, obj) >= 0; + } + + public static boolean contains(@Nonnull T[] array, @Nullable T obj, int start, int end) { + return indexOf(array, obj, start, end) >= 0; + } + + public static int indexOf(@Nonnull T[] array, @Nullable T obj) { + return indexOf(array, obj, 0, array.length); + } + + public static int indexOf(@Nonnull T[] array, @Nullable T obj, int start, int end) { + if (obj == null) { + for (int i = start; i < end; i++) { + if (array[i] == null) { + return i; + } + } + } else { + for (int ix = start; ix < end; ix++) { + if (obj.equals(array[ix])) { + return ix; + } + } + } + + return -1; + } +} diff --git a/src/com/hypixel/hytale/common/util/AudioUtil.java b/src/com/hypixel/hytale/common/util/AudioUtil.java new file mode 100644 index 0000000..9dff136 --- /dev/null +++ b/src/com/hypixel/hytale/common/util/AudioUtil.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.common.util; + +public class AudioUtil { + public static final float MIN_DECIBEL_VOLUME = -100.0F; + public static final float MAX_DECIBEL_VOLUME = 10.0F; + public static final float MIN_SEMITONE_PITCH = -12.0F; + public static final float MAX_SEMITONE_PITCH = 12.0F; + + public AudioUtil() { + } + + public static float decibelsToLinearGain(float decibels) { + return decibels <= -100.0F ? 0.0F : (float)Math.pow(10.0, decibels / 20.0F); + } + + public static float linearGainToDecibels(float linearGain) { + return linearGain <= 0.0F ? -100.0F : (float)(Math.log(linearGain) / Math.log(10.0) * 20.0); + } + + public static float semitonesToLinearPitch(float semitones) { + return (float)(1.0 / Math.pow(2.0, -semitones / 12.0F)); + } + + public static float linearPitchToSemitones(float linearPitch) { + return (float)(Math.log(linearPitch) / Math.log(2.0) * 12.0); + } +} diff --git a/src/com/hypixel/hytale/common/util/BitSetUtil.java b/src/com/hypixel/hytale/common/util/BitSetUtil.java new file mode 100644 index 0000000..81db201 --- /dev/null +++ b/src/com/hypixel/hytale/common/util/BitSetUtil.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.common.util; + +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import com.hypixel.hytale.unsafe.UnsafeUtil; +import java.util.BitSet; +import javax.annotation.Nonnull; + +public class BitSetUtil { + public static final long WORDS_OFFSET; + public static final long WORDS_IN_USE_OFFSET; + + public BitSetUtil() { + } + + public static void copyValues(@Nonnull BitSet from, @Nonnull BitSet to) { + if (UnsafeUtil.UNSAFE == null) { + copyValuesSlow(from, to); + } else { + int wordsInUse = UnsafeUtil.UNSAFE.getInt(from, WORDS_IN_USE_OFFSET); + UnsafeUtil.UNSAFE.putInt(to, WORDS_IN_USE_OFFSET, wordsInUse); + long[] fromWords = (long[])UnsafeUtil.UNSAFE.getObject(from, WORDS_OFFSET); + long[] toWords = (long[])UnsafeUtil.UNSAFE.getObject(to, WORDS_OFFSET); + if (wordsInUse > toWords.length) { + toWords = new long[wordsInUse]; + UnsafeUtil.UNSAFE.putObject(to, WORDS_OFFSET, toWords); + } + + System.arraycopy(fromWords, 0, toWords, 0, wordsInUse); + } + } + + public static void copyValuesSlow(@Nonnull BitSet from, @Nonnull BitSet to) { + to.clear(); + to.or(from); + } + + static { + try { + if (UnsafeUtil.UNSAFE != null) { + WORDS_OFFSET = UnsafeUtil.UNSAFE.objectFieldOffset(BitSet.class.getDeclaredField("words")); + WORDS_IN_USE_OFFSET = UnsafeUtil.UNSAFE.objectFieldOffset(BitSet.class.getDeclaredField("wordsInUse")); + } else { + WORDS_OFFSET = 0L; + WORDS_IN_USE_OFFSET = 0L; + } + } catch (NoSuchFieldException var1) { + throw SneakyThrow.sneakyThrow(var1); + } + } +} diff --git a/src/com/hypixel/hytale/common/util/BitUtil.java b/src/com/hypixel/hytale/common/util/BitUtil.java new file mode 100644 index 0000000..1341c15 --- /dev/null +++ b/src/com/hypixel/hytale/common/util/BitUtil.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.common.util; + +import javax.annotation.Nonnull; + +public class BitUtil { + public BitUtil() { + } + + public static void setNibble(@Nonnull byte[] data, int idx, byte b) { + int fieldIdx = idx >> 1; + byte val = data[fieldIdx]; + b = (byte)(b & 15); + int i = idx & 1; + b = (byte)(b << ((i ^ 1) << 2)); + val = (byte)(val & 15 << (i << 2)); + val = (byte)(val | b); + data[fieldIdx] = val; + } + + public static byte getNibble(@Nonnull byte[] data, int idx) { + int fieldIdx = idx >> 1; + byte val = data[fieldIdx]; + int i = idx & 1; + val = (byte)(val >> ((i ^ 1) << 2)); + return (byte)(val & 15); + } + + public static byte getAndSetNibble(@Nonnull byte[] data, int idx, byte b) { + int fieldIdx = idx >> 1; + byte val = data[fieldIdx]; + int i = idx & 1; + byte oldVal = (byte)(val >> ((i ^ 1) << 2)); + oldVal = (byte)(oldVal & 15); + b = (byte)(b & 15); + b = (byte)(b << ((i ^ 1) << 2)); + val = (byte)(val & 15 << (i << 2)); + val = (byte)(val | b); + data[fieldIdx] = val; + return oldVal; + } +} diff --git a/src/com/hypixel/hytale/common/util/CompletableFutureUtil.java b/src/com/hypixel/hytale/common/util/CompletableFutureUtil.java new file mode 100644 index 0000000..150c22b --- /dev/null +++ b/src/com/hypixel/hytale/common/util/CompletableFutureUtil.java @@ -0,0 +1,94 @@ +package com.hypixel.hytale.common.util; + +import com.hypixel.hytale.logger.HytaleLogger; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class CompletableFutureUtil { + public static final Function fn = throwable -> { + if (!(throwable instanceof CompletableFutureUtil.TailedRuntimeException)) { + HytaleLogger.getLogger().at(Level.SEVERE).withCause(throwable).log("Unhandled exception! " + Thread.currentThread()); + } + + throw new CompletableFutureUtil.TailedRuntimeException(throwable); + }; + + public CompletableFutureUtil() { + } + + @Nonnull + public static CompletableFuture whenComplete(@Nonnull CompletableFuture future, @Nonnull CompletableFuture callee) { + return future.whenComplete((result, throwable) -> { + if (throwable != null) { + callee.completeExceptionally(throwable); + } else { + callee.complete((T)result); + } + }); + } + + public static boolean isCanceled(Throwable throwable) { + return throwable instanceof CancellationException + || throwable instanceof CompletionException && throwable.getCause() != null && throwable.getCause() != throwable && isCanceled(throwable.getCause()); + } + + @Nonnull + public static CompletableFuture _catch(@Nonnull CompletableFuture future) { + return future.exceptionally((Function)fn); + } + + @Nonnull + public static CompletableFuture completionCanceled() { + CompletableFuture out = new CompletableFuture<>(); + out.cancel(false); + return out; + } + + public static void joinWithProgress( + @Nonnull List> list, @Nonnull CompletableFutureUtil.ProgressConsumer callback, int millisSleep, int millisProgress + ) throws InterruptedException { + CompletableFuture all = CompletableFuture.allOf(list.toArray(CompletableFuture[]::new)); + long last = System.nanoTime(); + long nanosProgress = TimeUnit.MILLISECONDS.toNanos(millisProgress); + int listSize = list.size(); + + while (!all.isDone()) { + Thread.sleep(millisSleep); + long now; + if (last + nanosProgress < (now = System.nanoTime())) { + last = now; + int done = 0; + + for (CompletableFuture c : list) { + if (c.isDone()) { + done++; + } + } + + if (done < listSize) { + callback.accept((double)done / listSize, done, listSize); + } + } + } + + callback.accept(1.0, listSize, listSize); + all.join(); + } + + @FunctionalInterface + public interface ProgressConsumer { + void accept(double var1, int var3, int var4); + } + + static class TailedRuntimeException extends RuntimeException { + public TailedRuntimeException(Throwable cause) { + super(cause); + } + } +} diff --git a/src/com/hypixel/hytale/common/util/ExceptionUtil.java b/src/com/hypixel/hytale/common/util/ExceptionUtil.java new file mode 100644 index 0000000..2d6f381 --- /dev/null +++ b/src/com/hypixel/hytale/common/util/ExceptionUtil.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.common.util; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.annotation.Nonnull; + +public class ExceptionUtil { + public ExceptionUtil() { + } + + @Nonnull + public static String combineMessages(Throwable thrown, @Nonnull String joiner) { + StringBuilder sb = new StringBuilder(); + + for (Throwable throwable = thrown; throwable != null; throwable = throwable.getCause()) { + if (throwable.getCause() == throwable) { + return sb.toString(); + } + + if (throwable.getMessage() != null) { + sb.append(throwable.getMessage()).append(joiner); + } + } + + sb.setLength(sb.length() - joiner.length()); + return sb.toString(); + } + + public static String toStringWithStack(@Nonnull Throwable t) { + try { + String var2; + try (StringWriter out = new StringWriter()) { + t.printStackTrace(new PrintWriter(out)); + var2 = out.toString(); + } + + return var2; + } catch (IOException var6) { + return t.toString(); + } + } +} diff --git a/src/com/hypixel/hytale/common/util/FormatUtil.java b/src/com/hypixel/hytale/common/util/FormatUtil.java new file mode 100644 index 0000000..0f8ff94 --- /dev/null +++ b/src/com/hypixel/hytale/common/util/FormatUtil.java @@ -0,0 +1,214 @@ +package com.hypixel.hytale.common.util; + +import com.hypixel.hytale.metrics.metric.Metric; +import java.util.EnumMap; +import java.util.Formatter; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.DoubleUnaryOperator; +import javax.annotation.Nonnull; + +public class FormatUtil { + private static final String[] NUMBER_SUFFIXES = new String[]{"th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"}; + private static final EnumMap timeUnitToShortString = new EnumMap(TimeUnit.class) { + { + this.put(TimeUnit.DAYS, "days"); + this.put(TimeUnit.HOURS, "hours"); + this.put(TimeUnit.MINUTES, "min"); + this.put(TimeUnit.SECONDS, "s"); + this.put(TimeUnit.MILLISECONDS, "ms"); + this.put(TimeUnit.MICROSECONDS, "us"); + this.put(TimeUnit.NANOSECONDS, "ns"); + } + }; + public static final long DAY_AS_NANOS = TimeUnit.DAYS.toNanos(1L); + public static final long HOUR_AS_NANOS = TimeUnit.HOURS.toNanos(1L); + public static final long MINUTE_AS_NANOS = TimeUnit.MINUTES.toNanos(1L); + public static final long SECOND_AS_NANOS = TimeUnit.SECONDS.toNanos(1L); + public static final long MILLISECOND_AS_NANOS = TimeUnit.MILLISECONDS.toNanos(1L); + public static final long MICOSECOND_AS_NANOS = TimeUnit.MICROSECONDS.toNanos(1L); + + public FormatUtil() { + } + + @Nonnull + public static TimeUnit largestUnit(long value, @Nonnull TimeUnit unit) { + long nanos = unit.toNanos(value); + if (nanos > DAY_AS_NANOS) { + return TimeUnit.DAYS; + } else if (nanos > HOUR_AS_NANOS) { + return TimeUnit.HOURS; + } else if (nanos > MINUTE_AS_NANOS) { + return TimeUnit.MINUTES; + } else if (nanos > SECOND_AS_NANOS) { + return TimeUnit.SECONDS; + } else if (nanos > MILLISECOND_AS_NANOS) { + return TimeUnit.MILLISECONDS; + } else { + return nanos > MICOSECOND_AS_NANOS ? TimeUnit.MICROSECONDS : TimeUnit.NANOSECONDS; + } + } + + @Nonnull + public static String simpleTimeUnitFormat(@Nonnull Metric metric, @Nonnull TimeUnit timeUnit, int rounding) { + TimeUnit largestUnit = largestUnit(Math.round(metric.getAverage()), timeUnit); + return simpleTimeUnitFormat(metric, timeUnit, largestUnit, rounding); + } + + @Nonnull + public static String simpleTimeUnitFormat(@Nonnull Metric metric, TimeUnit timeUnit, @Nonnull TimeUnit largestUnit, int rounding) { + long min = metric.getMin(); + double average = metric.getAverage(); + long max = metric.getMax(); + return simpleTimeUnitFormat(min, average, max, timeUnit, largestUnit, rounding); + } + + @Nonnull + public static String simpleTimeUnitFormat(long min, double average, long max, TimeUnit timeUnit, @Nonnull TimeUnit largestUnit, int rounding) { + int roundValue = (int)Math.pow(10.0, rounding); + long range = Math.round(Math.max(Math.abs(average - min), Math.abs(max - average))); + long averageNanos = largestUnit.convert(Math.round(average * roundValue), timeUnit); + long rangeNanos = largestUnit.convert(range * roundValue, timeUnit); + String unitStr = timeUnitToShortString.get(largestUnit); + return (double)averageNanos / roundValue + unitStr + " +/-" + (double)rangeNanos / roundValue + unitStr; + } + + @Nonnull + public static String simpleTimeUnitFormat(long value, @Nonnull TimeUnit timeUnit, int rounding) { + int roundValue = (int)Math.pow(10.0, rounding); + TimeUnit largestUnit = largestUnit(value, timeUnit); + long averageNanos = largestUnit.convert(value * roundValue, timeUnit); + String unitStr = timeUnitToShortString.get(largestUnit); + return (double)averageNanos / roundValue + unitStr; + } + + @Nonnull + public static String simpleFormat(long min1, double average1, long max1, @Nonnull DoubleUnaryOperator doubleFunction, int rounding) { + double average = doubleFunction.applyAsDouble(average1); + double min = Math.abs(average - doubleFunction.applyAsDouble(min1)); + double max = Math.abs(doubleFunction.applyAsDouble(max1) - average); + double range = Math.max(min, max); + return simpleFormat(rounding, average, range); + } + + @Nonnull + public static String simpleFormat(@Nonnull Metric metric) { + return simpleFormat(metric, 2); + } + + @Nonnull + public static String simpleFormat(@Nonnull Metric metric, int rounding) { + double average = metric.getAverage(); + double min = Math.abs(average - metric.getMin()); + double max = Math.abs(metric.getMax() - average); + double range = Math.max(min, max); + return simpleFormat(rounding, average, range); + } + + @Nonnull + public static String simpleFormat(int rounding, double average, double range) { + int roundValue = (int)Math.pow(10.0, rounding); + return (double)((long)(average * roundValue)) / roundValue + " +/-" + (double)((long)(range * roundValue)) / roundValue; + } + + @Nonnull + public static String timeUnitToString(@Nonnull Metric metric, @Nonnull TimeUnit timeUnit) { + double min = Math.abs(metric.getAverage() - metric.getMin()); + double max = Math.abs(metric.getMax() - metric.getAverage()); + long range = Math.round(Math.max(min, max)); + return timeUnitToString(Math.round(metric.getAverage()), timeUnit) + " +/-" + timeUnitToString(range, timeUnit); + } + + @Nonnull + public static String timeUnitToString(long value, @Nonnull TimeUnit timeUnit) { + return timeUnitToString(value, timeUnit, false); + } + + @Nonnull + public static String timeUnitToString(long value, @Nonnull TimeUnit timeUnit, boolean paddingBetween) { + boolean p = false; + StringBuilder sb = new StringBuilder(); + AtomicLong time = new AtomicLong(value); + p |= timeToStringPart(time, sb, p, timeUnit, TimeUnit.DAYS, "days", false, paddingBetween); + boolean hasHours = timeToStringPart(time, sb, p, timeUnit, TimeUnit.HOURS, ":", true, paddingBetween); + p |= hasHours; + p |= timeToStringPart(time, sb, p, timeUnit, TimeUnit.MINUTES, hasHours ? ":" : "min", false, paddingBetween); + p |= timeToStringPart(time, sb, p, timeUnit, TimeUnit.SECONDS, hasHours ? "" : "sec", !hasHours, paddingBetween); + p |= timeToStringPart(time, sb, p, timeUnit, TimeUnit.MILLISECONDS, "ms", true, paddingBetween); + p |= timeToStringPart(time, sb, p, timeUnit, TimeUnit.MICROSECONDS, "us", true, paddingBetween); + p |= timeToStringPart(time, sb, p, timeUnit, TimeUnit.NANOSECONDS, "ns", true, paddingBetween); + return sb.toString(); + } + + @Nonnull + public static String nanosToString(long nanos) { + return timeUnitToString(nanos, TimeUnit.NANOSECONDS); + } + + private static boolean timeToStringPart( + @Nonnull AtomicLong time, + @Nonnull StringBuilder sb, + boolean previous, + @Nonnull TimeUnit timeUnitFrom, + @Nonnull TimeUnit timeUnitTo, + String after, + boolean paddingBefore, + boolean paddingBetween + ) { + if (timeUnitFrom.ordinal() > timeUnitTo.ordinal()) { + return false; + } else { + long timeInUnitTo = timeUnitTo.convert(time.get(), timeUnitFrom); + time.getAndAdd(-timeUnitFrom.convert(timeInUnitTo, timeUnitTo)); + if (timeInUnitTo > 0L || previous && time.get() > 0L || !previous && timeUnitFrom == timeUnitTo) { + if (paddingBefore && previous) { + sb.append(' '); + } + + sb.append(timeInUnitTo); + if (paddingBetween) { + sb.append(' '); + } + + sb.append(after); + return true; + } else { + return false; + } + } + } + + @Nonnull + public static String bytesToString(long bytes) { + return bytesToString(bytes, false); + } + + @Nonnull + public static String bytesToString(long bytes, boolean si) { + int unit = si ? 1000 : 1024; + if (bytes < unit) { + return bytes + " B"; + } else { + int exp = (int)(Math.log(bytes) / Math.log(unit)); + return String.format("%.1f %sB", bytes / Math.pow(unit, exp), (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i")); + } + } + + @Nonnull + public static String addNumberSuffix(int i) { + return switch (i % 100) { + case 11, 12, 13 -> i + "th"; + default -> i + NUMBER_SUFFIXES[i % 10]; + }; + } + + public static void formatArray(@Nonnull Formatter formatter, @Nonnull String format, @Nonnull Object[] args) { + for (Object arg : args) { + formatter.format(format, arg); + } + } + + public static void formatArgs(@Nonnull Formatter formatter, @Nonnull String format, @Nonnull Object... args) { + formatArray(formatter, format, args); + } +} diff --git a/src/com/hypixel/hytale/common/util/GCUtil.java b/src/com/hypixel/hytale/common/util/GCUtil.java new file mode 100644 index 0000000..368c80d --- /dev/null +++ b/src/com/hypixel/hytale/common/util/GCUtil.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.common.util; + +import com.sun.management.GarbageCollectionNotificationInfo; +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.management.NotificationEmitter; +import javax.management.openmbean.CompositeData; + +public class GCUtil { + public GCUtil() { + } + + public static void register(@Nonnull Consumer consumer) { + for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) { + NotificationEmitter emitter = (NotificationEmitter)gcBean; + emitter.addNotificationListener((notification, handback) -> { + if (notification.getType().equals("com.sun.management.gc.notification")) { + GarbageCollectionNotificationInfo info = GarbageCollectionNotificationInfo.from((CompositeData)notification.getUserData()); + consumer.accept(info); + } + }, null, null); + } + } +} diff --git a/src/com/hypixel/hytale/common/util/HardwareUtil.java b/src/com/hypixel/hytale/common/util/HardwareUtil.java new file mode 100644 index 0000000..97a8b03 --- /dev/null +++ b/src/com/hypixel/hytale/common/util/HardwareUtil.java @@ -0,0 +1,175 @@ +package com.hypixel.hytale.common.util; + +import com.hypixel.hytale.function.supplier.SupplierUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nullable; + +public class HardwareUtil { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final int PROCESS_TIMEOUT_SECONDS = 2; + private static final Pattern UUID_PATTERN = Pattern.compile("([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"); + private static final Supplier WINDOWS = SupplierUtil.cache(() -> { + String output = runCommand("reg", "query", "HKLM\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid"); + if (output != null) { + for (String line : output.split("\r?\n")) { + if (line.contains("MachineGuid")) { + Matcher matcher = UUID_PATTERN.matcher(line); + if (matcher.find()) { + return UUID.fromString(matcher.group(1)); + } + } + } + } + + output = runCommand("powershell", "-NoProfile", "-Command", "(Get-CimInstance -Class Win32_ComputerSystemProduct).UUID"); + if (output != null) { + UUID uuid = parseUuidFromOutput(output); + if (uuid != null) { + return uuid; + } + } + + output = runCommand("wmic", "csproduct", "get", "UUID"); + if (output != null) { + UUID uuid = parseUuidFromOutput(output); + if (uuid != null) { + return uuid; + } + } + + throw new RuntimeException("Failed to get hardware UUID for Windows - registry, PowerShell, and wmic all failed"); + }); + private static final Supplier MAC = SupplierUtil.cache(() -> { + String output = runCommand("/usr/sbin/ioreg", "-rd1", "-c", "IOPlatformExpertDevice"); + if (output != null) { + for (String line : output.split("\r?\n")) { + if (line.contains("IOPlatformUUID")) { + Matcher matcher = UUID_PATTERN.matcher(line); + if (matcher.find()) { + return UUID.fromString(matcher.group(1)); + } + } + } + } + + output = runCommand("/usr/sbin/system_profiler", "SPHardwareDataType"); + if (output != null) { + for (String linex : output.split("\r?\n")) { + if (linex.contains("Hardware UUID")) { + Matcher matcher = UUID_PATTERN.matcher(linex); + if (matcher.find()) { + return UUID.fromString(matcher.group(1)); + } + } + } + } + + throw new RuntimeException("Failed to get hardware UUID for macOS - ioreg and system_profiler both failed"); + }); + private static final Supplier LINUX = SupplierUtil.cache(() -> { + UUID machineId = readMachineIdFile(Path.of("/etc/machine-id")); + if (machineId != null) { + return machineId; + } else { + machineId = readMachineIdFile(Path.of("/var/lib/dbus/machine-id")); + if (machineId != null) { + return machineId; + } else { + try { + Path path = Path.of("/sys/class/dmi/id/product_uuid"); + if (Files.isReadable(path)) { + String content = Files.readString(path, StandardCharsets.UTF_8).trim(); + if (!content.isEmpty()) { + return UUID.fromString(content); + } + } + } catch (Exception var3) { + } + + String output = runCommand("dmidecode", "-s", "system-uuid"); + if (output != null) { + UUID uuid = parseUuidFromOutput(output); + if (uuid != null) { + return uuid; + } + } + + throw new RuntimeException("Failed to get hardware UUID for Linux - all methods failed"); + } + } + }); + + public HardwareUtil() { + } + + @Nullable + private static String runCommand(String... command) { + try { + Process process = new ProcessBuilder(command).start(); + if (process.waitFor(2L, TimeUnit.SECONDS)) { + return new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8).trim(); + } + + process.destroyForcibly(); + } catch (Exception var2) { + } + + return null; + } + + @Nullable + private static UUID parseUuidFromOutput(String output) { + Matcher matcher = UUID_PATTERN.matcher(output); + return matcher.find() ? UUID.fromString(matcher.group(1)) : null; + } + + @Nullable + private static UUID readMachineIdFile(Path path) { + try { + if (!Files.isReadable(path)) { + return null; + } else { + String content = Files.readString(path, StandardCharsets.UTF_8).trim(); + return !content.isEmpty() && content.length() == 32 + ? UUID.fromString( + content.substring(0, 8) + + "-" + + content.substring(8, 12) + + "-" + + content.substring(12, 16) + + "-" + + content.substring(16, 20) + + "-" + + content.substring(20, 32) + ) + : null; + } + } catch (Exception var2) { + return null; + } + } + + @Nullable + public static UUID getUUID() { + try { + return switch (SystemUtil.TYPE) { + case WINDOWS -> (UUID)WINDOWS.get(); + case LINUX -> (UUID)LINUX.get(); + case MACOS -> (UUID)MAC.get(); + case OTHER -> throw new RuntimeException("Unknown OS!"); + }; + } catch (Exception var1) { + LOGGER.at(Level.WARNING).withCause(var1).log("Failed to get Hardware UUID"); + return null; + } + } +} diff --git a/src/com/hypixel/hytale/common/util/ListUtil.java b/src/com/hypixel/hytale/common/util/ListUtil.java new file mode 100644 index 0000000..44771f3 --- /dev/null +++ b/src/com/hypixel/hytale/common/util/ListUtil.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.common.util; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Predicate; +import javax.annotation.Nonnull; + +public class ListUtil { + public ListUtil() { + } + + @Nonnull + public static List> partition(@Nonnull List list, int sectionSize) { + List> sections = new ObjectArrayList<>(); + int i = 0; + + while (i < list.size()) { + int endIndex = Math.min(list.size(), i + sectionSize); + sections.add(list.subList(i, endIndex)); + i += sectionSize; + } + + return sections; + } + + public static void removeIf(@Nonnull List list, @Nonnull Predicate predicate) { + for (int i = list.size() - 1; i >= 0; i--) { + if (predicate.test(list.get(i))) { + list.remove(i); + } + } + } + + public static void removeIf(@Nonnull List list, @Nonnull BiPredicate predicate, U obj) { + for (int i = list.size() - 1; i >= 0; i--) { + if (predicate.test(list.get(i), obj)) { + list.remove(i); + } + } + } + + public static boolean emptyOrAllNull(@Nonnull List list) { + for (int i = 0; i < list.size(); i++) { + T e = list.get(i); + if (e != null) { + return false; + } + } + + return true; + } + + public static int binarySearch(@Nonnull List l, @Nonnull Function func, V key, @Nonnull Comparator c) { + int low = 0; + int high = l.size() - 1; + + while (low <= high) { + int mid = low + high >>> 1; + T midVal = (T)l.get(mid); + int cmp = c.compare(func.apply(midVal), key); + if (cmp < 0) { + low = mid + 1; + } else { + if (cmp <= 0) { + return mid; + } + + high = mid - 1; + } + } + + return -(low + 1); + } +} diff --git a/src/com/hypixel/hytale/common/util/MapUtil.java b/src/com/hypixel/hytale/common/util/MapUtil.java new file mode 100644 index 0000000..5d34217 --- /dev/null +++ b/src/com/hypixel/hytale/common/util/MapUtil.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.common.util; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Collections; +import java.util.Map; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class MapUtil { + public MapUtil() { + } + + @Nonnull + public static Map combineUnmodifiable(@Nonnull Map one, @Nonnull Map two) { + Map map = new Object2ObjectOpenHashMap<>(); + map.putAll(one); + map.putAll(two); + return Collections.unmodifiableMap(map); + } + + @Nonnull + public static > Map combineUnmodifiable(@Nonnull Map one, @Nonnull Map two, @Nonnull Supplier supplier) { + Map map = supplier.get(); + map.putAll(one); + map.putAll(two); + return Collections.unmodifiableMap(map); + } + + @Nonnull + public static > M combine(@Nonnull Map one, @Nonnull Map two, @Nonnull Supplier supplier) { + M map = (M)supplier.get(); + map.putAll(one); + map.putAll(two); + return map; + } +} diff --git a/src/com/hypixel/hytale/common/util/NetworkUtil.java b/src/com/hypixel/hytale/common/util/NetworkUtil.java new file mode 100644 index 0000000..1f966cc --- /dev/null +++ b/src/com/hypixel/hytale/common/util/NetworkUtil.java @@ -0,0 +1,358 @@ +package com.hypixel.hytale.common.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Enumeration; +import java.util.Locale; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NetworkUtil { + public static Inet6Address ANY_IPV6_ADDRESS; + public static Inet4Address ANY_IPV4_ADDRESS; + public static Inet6Address LOOPBACK_IPV6_ADDRESS; + public static Inet4Address LOOPBACK_IPV4_ADDRESS; + + public NetworkUtil() { + } + + @Nullable + public static InetAddress getFirstNonLoopbackAddress() throws SocketException { + InetAddress firstInet6Address = null; + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + + while (networkInterfaces.hasMoreElements()) { + NetworkInterface networkInterface = networkInterfaces.nextElement(); + if (!networkInterface.isLoopback() && networkInterface.isUp()) { + Enumeration inetAddresses = networkInterface.getInetAddresses(); + + while (inetAddresses.hasMoreElements()) { + InetAddress inetAddress = inetAddresses.nextElement(); + if (!inetAddress.isLoopbackAddress() && !inetAddress.isAnyLocalAddress() && !inetAddress.isLinkLocalAddress()) { + if (inetAddress instanceof Inet4Address) { + return inetAddress; + } + + if (inetAddress instanceof Inet6Address && firstInet6Address == null) { + firstInet6Address = inetAddress; + } + } + } + } + } + + return firstInet6Address; + } + + @Nullable + public static InetAddress getFirstAddressWith(NetworkUtil.AddressType... include) throws SocketException { + return getFirstAddressWith(include, null); + } + + @Nullable + public static InetAddress getFirstAddressWithout(NetworkUtil.AddressType... include) throws SocketException { + return getFirstAddressWith(null, include); + } + + @Nullable + public static InetAddress getFirstAddressWith(@Nullable NetworkUtil.AddressType[] include, @Nullable NetworkUtil.AddressType[] exclude) throws SocketException { + InetAddress firstInet6Address = null; + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + + while (networkInterfaces.hasMoreElements()) { + NetworkInterface networkInterface = networkInterfaces.nextElement(); + if (!networkInterface.isLoopback() && networkInterface.isUp()) { + Enumeration inetAddresses = networkInterface.getInetAddresses(); + + label70: + while (inetAddresses.hasMoreElements()) { + InetAddress inetAddress = inetAddresses.nextElement(); + if (include != null) { + for (NetworkUtil.AddressType addressType : include) { + if (!addressType.predicate.test(inetAddress)) { + continue label70; + } + } + } + + if (exclude != null) { + for (NetworkUtil.AddressType addressTypex : exclude) { + if (addressTypex.predicate.test(inetAddress)) { + continue label70; + } + } + } + + if (inetAddress instanceof Inet4Address) { + return inetAddress; + } + + if (inetAddress instanceof Inet6Address && firstInet6Address == null) { + firstInet6Address = inetAddress; + } + } + } + } + + return firstInet6Address; + } + + public static boolean addressMatchesAll(InetAddress address, @Nonnull NetworkUtil.AddressType... types) { + for (NetworkUtil.AddressType type : types) { + if (!type.predicate.test(address)) { + return false; + } + } + + return true; + } + + public static boolean addressMatchesAny(InetAddress address) { + return addressMatchesAny(address, NetworkUtil.AddressType.values()); + } + + public static boolean addressMatchesAny(InetAddress address, @Nonnull NetworkUtil.AddressType... types) { + for (NetworkUtil.AddressType type : types) { + if (type.predicate.test(address)) { + return true; + } + } + + return false; + } + + @Nonnull + public static String toSocketString(@Nonnull InetSocketAddress address) { + String str; + if (address.getAddress() instanceof Inet6Address) { + String host = address.getHostString(); + if (host.indexOf(58) >= 0) { + str = "[" + host + "]"; + } else { + str = host; + } + + str = str + ":" + address.getPort(); + } else { + str = address.getHostString() + ":" + address.getPort(); + } + + return str; + } + + @Nullable + public static String getHostName() { + String localhost = null; + + try { + InetAddress localHost = InetAddress.getLocalHost(); + localhost = localHost.getHostName(); + if (isAcceptableHostName(localhost)) { + return localhost; + } + + String hostName = localHost.getCanonicalHostName(); + if (isAcceptableHostName(hostName)) { + return hostName; + } + } catch (UnknownHostException var10) { + } + + String hostName = System.getenv("HOSTNAME"); + if (isAcceptableHostName(hostName)) { + return hostName; + } else { + hostName = System.getenv("COMPUTERNAME"); + if (isAcceptableHostName(hostName)) { + return hostName; + } else { + hostName = firstLineIfExists("/etc/hostname"); + if (isAcceptableHostName(hostName)) { + return hostName; + } else { + hostName = firstLineIfExists("/proc/sys/kernel/hostname"); + if (isAcceptableHostName(hostName)) { + return hostName; + } else { + try { + Enumeration en = NetworkInterface.getNetworkInterfaces(); + + while (en.hasMoreElements()) { + NetworkInterface ni = en.nextElement(); + if (ni.isUp() && !ni.isLoopback() && !ni.isPointToPoint()) { + String name = ni.getName().toLowerCase(Locale.ROOT); + if (!name.startsWith("lo") + && !name.startsWith("docker") + && !name.startsWith("br-") + && !name.startsWith("veth") + && !name.startsWith("virbr") + && !name.startsWith("utun") + && !name.startsWith("wg") + && !name.startsWith("zt")) { + Enumeration e = ni.getInetAddresses(); + + while (e.hasMoreElements()) { + InetAddress a = e.nextElement(); + if (!a.isLoopbackAddress() && !a.isLinkLocalAddress() && !a.isAnyLocalAddress()) { + String hostAddress = a.getHostAddress(); + String addressHostName = a.getHostName(); + if (addressHostName != null && !addressHostName.equals(hostAddress) && isAcceptableHostName(addressHostName)) { + return addressHostName; + } + + String canonicalHostName = a.getCanonicalHostName(); + if (canonicalHostName != null && !canonicalHostName.equals(hostAddress) && isAcceptableHostName(canonicalHostName)) { + return canonicalHostName; + } + } + } + } + } + } + } catch (SocketException var11) { + } + + return null; + } + } + } + } + } + + @Nullable + private static String firstLineIfExists(String path) { + try { + Path p = Path.of(path); + if (!Files.isRegularFile(p)) { + return null; + } else { + String var4; + try (BufferedReader reader = Files.newBufferedReader(p, StandardCharsets.UTF_8)) { + String line = reader.readLine(); + var4 = line == null ? null : line.trim(); + } + + return var4; + } + } catch (IOException var7) { + return null; + } + } + + private static boolean isAcceptableHostName(@Nullable String name) { + if (name == null) { + return false; + } else { + name = name.trim(); + if (name.isEmpty()) { + return false; + } else { + String lower = name.toLowerCase(Locale.ROOT); + if (isIPv4Literal(lower) || isLikelyIPv6Literal(lower)) { + return false; + } else if ("localhost".equals(lower) || "ip6-localhost".equals(lower) || "ip6-loopback".equals(lower) || "docker-desktop".equals(lower)) { + return false; + } else { + return !lower.contains("docker") && !lower.contains("wsl") && !lower.endsWith(".internal") && !lower.endsWith(".localdomain") + ? !lower.endsWith(".local") + : false; + } + } + } + } + + private static boolean isIPv4Literal(@Nonnull String name) { + int dots = 0; + int octet = -1; + int val = 0; + + for (int i = 0; i < name.length(); i++) { + char ch = name.charAt(i); + if (ch >= '0' && ch <= '9') { + if (octet == -1) { + octet = 0; + } + + val = val * 10 + (ch - '0'); + if (val > 255) { + return false; + } + + if (++octet > 3) { + return false; + } + } else { + if (ch != '.') { + return false; + } + + if (octet <= 0) { + return false; + } + + dots++; + octet = -1; + val = 0; + if (dots > 3) { + return false; + } + } + } + + return dots == 3 && octet > 0; + } + + private static boolean isLikelyIPv6Literal(@Nonnull String name) { + boolean colon = false; + + for (int i = 0; i < name.length(); i++) { + char ch = name.charAt(i); + if (ch == ':') { + colon = true; + } else if ((ch < '0' || ch > '9') && (ch < 'a' || ch > 'f') && (ch < 'A' || ch > 'F')) { + return false; + } + } + + return colon; + } + + static { + try { + ANY_IPV6_ADDRESS = Inet6Address.getByAddress("::", new byte[16], null); + ANY_IPV4_ADDRESS = (Inet4Address)Inet4Address.getByAddress("0.0.0.0", new byte[4]); + LOOPBACK_IPV6_ADDRESS = Inet6Address.getByAddress("::1", new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, null); + LOOPBACK_IPV4_ADDRESS = (Inet4Address)Inet4Address.getByAddress("127.0.0.1", new byte[]{127, 0, 0, 1}); + } catch (UnknownHostException var1) { + throw new RuntimeException(var1); + } + } + + public static enum AddressType { + MULTICAST(InetAddress::isMulticastAddress), + ANY_LOCAL(InetAddress::isAnyLocalAddress), + LOOPBACK(InetAddress::isLoopbackAddress), + LINK_LOCAL(InetAddress::isLinkLocalAddress), + SITE_LOCAL(InetAddress::isSiteLocalAddress), + MC_GLOBAL(InetAddress::isMCGlobal), + MC_SITE_LOCAL(InetAddress::isMCSiteLocal), + MC_ORG_LOCAL(InetAddress::isMCOrgLocal); + + private final Predicate predicate; + + private AddressType(Predicate predicate) { + this.predicate = predicate; + } + } +} diff --git a/src/com/hypixel/hytale/common/util/PathUtil.java b/src/com/hypixel/hytale/common/util/PathUtil.java new file mode 100644 index 0000000..6e8a1e3 --- /dev/null +++ b/src/com/hypixel/hytale/common/util/PathUtil.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.common.util; + +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PathUtil { + private static final Pattern PATH_PATTERN = Pattern.compile("[\\\\/]"); + + public PathUtil() { + } + + @Nonnull + public static Path getParent(@Nonnull Path path) { + if (path.isAbsolute()) { + return path.getParent().normalize(); + } else { + Path parentAbsolute = path.toAbsolutePath().getParent(); + Path parent = path.resolve(relativize(path, parentAbsolute)); + return parent.normalize(); + } + } + + @Nonnull + public static Path relativize(@Nonnull Path pathA, @Nonnull Path pathB) { + Path absolutePathA = pathA.toAbsolutePath(); + Path absolutePathB = pathB.toAbsolutePath(); + return Objects.equals(absolutePathA.getRoot(), absolutePathB.getRoot()) + ? absolutePathA.normalize().relativize(absolutePathB.normalize()).normalize() + : absolutePathB.normalize(); + } + + @Nonnull + public static Path relativizePretty(@Nonnull Path pathA, @Nonnull Path pathB) { + Path absolutePathA = pathA.toAbsolutePath().normalize(); + Path absolutePathB = pathB.toAbsolutePath().normalize(); + Path absoluteUserHome = getUserHome().toAbsolutePath(); + if (Objects.equals(absoluteUserHome.getRoot(), absolutePathB.getRoot())) { + Path relativizedHome = absoluteUserHome.relativize(absolutePathB).normalize(); + if (Objects.equals(absolutePathA.getRoot(), absolutePathB.getRoot())) { + Path relativized = absolutePathA.relativize(absolutePathB).normalize(); + return relativizedHome.getNameCount() < relativized.getNameCount() ? Paths.get("~").resolve(relativizedHome) : relativized; + } else { + return relativizedHome.getNameCount() < absolutePathB.getNameCount() ? Paths.get("~").resolve(relativizedHome) : absolutePathB; + } + } else { + return Objects.equals(absolutePathA.getRoot(), absolutePathB.getRoot()) ? absolutePathA.relativize(absolutePathB).normalize() : absolutePathB; + } + } + + @Nonnull + public static Path get(@Nonnull String path) { + return get(Paths.get(path)); + } + + @Nonnull + public static Path get(@Nonnull Path path) { + return path.toString().charAt(0) == '~' ? getUserHome().resolve(path.subpath(1, path.getNameCount())).normalize() : path.normalize(); + } + + @Nonnull + public static Path getUserHome() { + return Paths.get(System.getProperty("user.home")); + } + + public static String getFileName(@Nonnull URL extUrl) { + String[] pathContents = PATH_PATTERN.split(extUrl.getPath()); + String fileName = pathContents[pathContents.length - 1]; + return fileName.isEmpty() && pathContents.length > 1 ? pathContents[pathContents.length - 2] : fileName; + } + + public static boolean isChildOf(@Nonnull Path parent, @Nonnull Path child) { + return child.toAbsolutePath().normalize().startsWith(parent.toAbsolutePath().normalize()); + } + + public static void forEachParent(@Nonnull Path path, @Nullable Path limit, @Nonnull Consumer consumer) { + Path parent = path.toAbsolutePath(); + if (Files.isRegularFile(parent)) { + parent = parent.getParent(); + } + + if (parent != null) { + do { + consumer.accept(parent); + } while ((parent = parent.getParent()) != null && (limit == null || isChildOf(limit, parent))); + } + } + + @Nonnull + public static String getFileExtension(@Nonnull Path path) { + String fileName = path.getFileName().toString(); + int index = fileName.lastIndexOf(46); + return index == -1 ? "" : fileName.substring(index); + } + + @Nonnull + public static String toUnixPathString(@Nonnull Path path) { + return "\\".equals(path.getFileSystem().getSeparator()) ? path.toString().replace("\\", "/") : path.toString(); + } +} diff --git a/src/com/hypixel/hytale/common/util/PatternUtil.java b/src/com/hypixel/hytale/common/util/PatternUtil.java new file mode 100644 index 0000000..78b445f --- /dev/null +++ b/src/com/hypixel/hytale/common/util/PatternUtil.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.common.util; + +import javax.annotation.Nonnull; + +public class PatternUtil { + public PatternUtil() { + } + + @Nonnull + public static String replaceBackslashWithForwardSlash(@Nonnull String name) { + return name.replace("\\", "/"); + } +} diff --git a/src/com/hypixel/hytale/common/util/RandomUtil.java b/src/com/hypixel/hytale/common/util/RandomUtil.java new file mode 100644 index 0000000..d1a6097 --- /dev/null +++ b/src/com/hypixel/hytale/common/util/RandomUtil.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.common.util; + +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RandomUtil { + public static final ThreadLocal SECURE_RANDOM = ThreadLocal.withInitial(SecureRandom::new); + + public RandomUtil() { + } + + public static T roll(int roll, T[] data, @Nonnull int[] chances) { + roll++; + int lower = 0; + int upper = 0; + + for (int i = 0; i < chances.length; i++) { + int thisOne = chances[i]; + upper += thisOne; + if (roll > lower && roll <= upper) { + return data[i]; + } + + lower += thisOne; + } + + throw new AssertionError("Reached end of roll(" + roll + ", " + Arrays.toString(data) + ", " + Arrays.toString(chances) + ")!"); + } + + public static int rollInt(int roll, int[] data, @Nonnull int[] chances) { + roll++; + int lower = 0; + int upper = 0; + + for (int i = 0; i < chances.length; i++) { + int thisOne = chances[i]; + upper += thisOne; + if (roll > lower && roll <= upper) { + return data[i]; + } + + lower += thisOne; + } + + throw new AssertionError("Reached end of roll(" + roll + ", " + Arrays.toString(data) + ", " + Arrays.toString(chances) + ")!"); + } + + public static SecureRandom getSecureRandom() { + return SECURE_RANDOM.get(); + } + + public static T selectRandom(@Nonnull T[] arr, @Nonnull Random random) { + return arr[random.nextInt(arr.length)]; + } + + @Nullable + public static T selectRandomOrNull(@Nonnull T[] arr, @Nonnull Random random) { + int index = random.nextInt(arr.length + 1); + return index == arr.length ? null : arr[index]; + } + + public static T selectRandom(@Nonnull List list) { + return selectRandom(list, ThreadLocalRandom.current()); + } + + public static T selectRandom(@Nonnull List list, @Nonnull Random random) { + return (T)list.get(random.nextInt(list.size())); + } +} diff --git a/src/com/hypixel/hytale/common/util/StringCompareUtil.java b/src/com/hypixel/hytale/common/util/StringCompareUtil.java new file mode 100644 index 0000000..fc5e596 --- /dev/null +++ b/src/com/hypixel/hytale/common/util/StringCompareUtil.java @@ -0,0 +1,105 @@ +package com.hypixel.hytale.common.util; + +import java.util.Locale; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StringCompareUtil { + public StringCompareUtil() { + } + + public static int indexOfDifference(@Nullable CharSequence cs1, @Nullable CharSequence cs2) { + if (cs1 == cs2) { + return -1; + } else if (cs1 != null && cs2 != null) { + int i = 0; + + while (i < cs1.length() && i < cs2.length() && cs1.charAt(i) == cs2.charAt(i)) { + i++; + } + + return i >= cs2.length() && i >= cs1.length() ? -1 : i; + } else { + return 0; + } + } + + public static int getFuzzyDistance(@Nonnull CharSequence term, @Nonnull CharSequence query, @Nonnull Locale locale) { + if (term != null && query != null) { + if (locale == null) { + throw new IllegalArgumentException("Locale must not be null"); + } else { + String termLowerCase = term.toString().toLowerCase(locale); + String queryLowerCase = query.toString().toLowerCase(locale); + int score = 0; + int termIndex = 0; + int previousMatchingCharacterIndex = Integer.MIN_VALUE; + + for (int queryIndex = 0; queryIndex < queryLowerCase.length(); queryIndex++) { + char queryChar = queryLowerCase.charAt(queryIndex); + + for (boolean termCharacterMatchFound = false; termIndex < termLowerCase.length() && !termCharacterMatchFound; termIndex++) { + char termChar = termLowerCase.charAt(termIndex); + if (queryChar == termChar) { + score++; + if (previousMatchingCharacterIndex + 1 == termIndex) { + score += 2; + } + + previousMatchingCharacterIndex = termIndex; + termCharacterMatchFound = true; + } + } + } + + return score; + } + } else { + throw new IllegalArgumentException("Strings must not be null"); + } + } + + public static int getLevenshteinDistance(@Nonnull CharSequence s, @Nonnull CharSequence t) { + if (s != null && t != null) { + int n = s.length(); + int m = t.length(); + if (n == 0) { + return m; + } else if (m == 0) { + return n; + } else { + if (n > m) { + CharSequence tmp = s; + s = t; + t = tmp; + n = m; + m = tmp.length(); + } + + int[] p = new int[n + 1]; + int i = 0; + + while (i <= n) { + p[i] = i++; + } + + for (int j = 1; j <= m; j++) { + int upper_left = p[0]; + char t_j = t.charAt(j - 1); + p[0] = j; + + for (int var12 = 1; var12 <= n; var12++) { + int upper = p[var12]; + int cost = s.charAt(var12 - 1) == t_j ? 0 : 1; + p[var12] = Math.min(Math.min(p[var12 - 1] + 1, p[var12] + 1), upper_left + cost); + upper_left = upper; + } + } + + return p[n]; + } + } else { + throw new IllegalArgumentException("Strings must not be null"); + } + } +} diff --git a/src/com/hypixel/hytale/common/util/StringUtil.java b/src/com/hypixel/hytale/common/util/StringUtil.java new file mode 100644 index 0000000..ca1b2bd --- /dev/null +++ b/src/com/hypixel/hytale/common/util/StringUtil.java @@ -0,0 +1,544 @@ +package com.hypixel.hytale.common.util; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.time.Duration; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.function.DoubleFunction; +import java.util.function.IntToDoubleFunction; +import java.util.function.IntToLongFunction; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StringUtil { + public static final Pattern RAW_ARGS_PATTERN = Pattern.compile(" -- "); + @Nonnull + private static final char[] GRAPH_CHARS = new char[]{'_', '\u2584', '\u2500', '\u2580', '\u00af'}; + + public StringUtil() { + } + + public static boolean isNumericString(@Nonnull String str) { + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (c < '0' || c > '9') { + return false; + } + } + + return true; + } + + public static boolean isAlphaNumericHyphenString(@Nonnull String str) { + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if ((c < '0' || c > 'z' || c > '9' && c < 'A' || c > 'Z' && c < 'a') && c != '-') { + return false; + } + } + + return true; + } + + public static boolean isAlphaNumericHyphenUnderscoreString(@Nonnull String str) { + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if ((c < '0' || c > 'z' || c > '9' && c < 'A' || c > 'Z' && c < 'a') && c != '-' && c != '_') { + return false; + } + } + + return true; + } + + public static boolean isCapitalized(@Nonnull String keyStr, char delim) { + boolean wasDelimOrFirst = true; + + for (int i = 0; i < keyStr.length(); i++) { + char c = keyStr.charAt(i); + if (wasDelimOrFirst && c != Character.toUpperCase(c)) { + return false; + } + + wasDelimOrFirst = c == delim; + } + + return true; + } + + @Nonnull + public static String capitalize(@Nonnull String keyStr, char delim) { + boolean wasDelimOrFirst = true; + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < keyStr.length(); i++) { + char c = keyStr.charAt(i); + sb.append(wasDelimOrFirst ? Character.toUpperCase(c) : c); + wasDelimOrFirst = c == delim; + } + + return sb.toString(); + } + + @Nullable + public static > V parseEnum(@Nonnull V[] enumConstants, String str) { + return parseEnum(enumConstants, str, StringUtil.MatchType.EQUALS); + } + + @Nullable + public static > V parseEnum(@Nonnull V[] enumConstants, String str, StringUtil.MatchType matchType) { + if (matchType == StringUtil.MatchType.EQUALS) { + for (V enumValue : enumConstants) { + if (enumValue.name().equals(str)) { + return enumValue; + } + } + } else if (matchType == StringUtil.MatchType.CASE_INSENSITIVE) { + for (V enumValuex : enumConstants) { + if (enumValuex.name().equalsIgnoreCase(str)) { + return enumValuex; + } + } + } else { + str = str.toLowerCase(); + V closest = null; + int diff = -2; + + for (V enumValuexx : enumConstants) { + int index = StringCompareUtil.indexOfDifference(str, enumValuexx.name().toLowerCase()); + if (index > diff && diff != -1 || index == -1 || diff == -2) { + closest = enumValuexx; + diff = index; + } + } + + if (diff > -2) { + return closest; + } + } + + return null; + } + + @Nonnull + @Deprecated(forRemoval = true) + public static String[] parseArgs(String rawString, @Nonnull Map argOptions) { + String[] rawSplit = RAW_ARGS_PATTERN.split(rawString, 2); + String argsStr = rawSplit[0]; + boolean hasRaw = rawSplit.length > 1; + char quote = 0; + int start = 0; + List argsList = new ObjectArrayList<>(); + + for (int i = 0; i < argsStr.length(); i++) { + char c = argsStr.charAt(i); + switch (c) { + case ' ': + if (quote == 0) { + if (start != i) { + argsList.add(argsStr.substring(start, i)); + } + + start = i + 1; + } + break; + case '"': + switch (quote) { + case '\u0000': + quote = '"'; + continue; + case '"': + quote = 0; + argsList.add(argsStr.substring(start, i + 1)); + start = i + 1; + default: + continue; + } + case '\'': + switch (quote) { + case '\u0000': + quote = '\''; + continue; + case '\'': + quote = 0; + argsList.add(argsStr.substring(start, i + 1)); + start = i + 1; + default: + continue; + } + case '\\': + if (i + 1 >= argsStr.length()) { + throw new IllegalStateException("Invalid escape at end of string, index " + (i + 1) + " in: " + rawString); + } + + char c1 = argsStr.charAt(i + 1); + switch (c1) { + case '"': + case '\'': + case '\\': + argsStr = argsStr.substring(0, i) + argsStr.substring(i + 1); + i++; + break; + default: + throw new IllegalStateException("Invalid escape for char " + c1 + " at index " + (i + 1) + " in: " + rawString); + } + } + } + + if (quote != 0) { + throw new IllegalStateException("Unbalanced quotes!"); + } else { + if (start != argsStr.length()) { + argsList.add(argsStr.substring(start)); + } + + argsList.removeIf(arg -> { + if (arg.startsWith("--")) { + String[] split = arg.substring(2).split("=", 2); + String value = ""; + if (split.length > 1) { + value = split[1]; + value = removeQuotes(value); + } + + argOptions.put(split[0], value); + return true; + } else { + return false; + } + }); + argsList.replaceAll(value -> removeQuotes(value.trim())); + if (hasRaw) { + String[] strings = new String[argsList.size() + 1]; + argsList.toArray(strings); + strings[argsList.size()] = rawSplit[1].trim(); + return strings; + } else { + return argsList.toArray(String[]::new); + } + } + } + + @Nonnull + public static String[] parseArgs(String rawString) { + String[] rawSplit = RAW_ARGS_PATTERN.split(rawString, 2); + String argsStr = rawSplit[0]; + boolean hasRaw = rawSplit.length > 1; + char quote = 0; + int start = 0; + List argsList = new ObjectArrayList<>(); + + for (int i = 0; i < argsStr.length(); i++) { + char c = argsStr.charAt(i); + switch (c) { + case ' ': + if (quote == 0) { + if (start != i) { + argsList.add(argsStr.substring(start, i)); + } + + start = i + 1; + } + break; + case '"': + switch (quote) { + case '\u0000': + quote = '"'; + continue; + case '"': + quote = 0; + argsList.add(argsStr.substring(start, i + 1)); + start = i + 1; + default: + continue; + } + case '\'': + switch (quote) { + case '\u0000': + quote = '\''; + continue; + case '\'': + quote = 0; + argsList.add(argsStr.substring(start, i + 1)); + start = i + 1; + default: + continue; + } + case '\\': + if (i + 1 >= argsStr.length()) { + throw new IllegalStateException("Invalid escape at end of string, index " + (i + 1) + " in: " + rawString); + } + + char c1 = argsStr.charAt(i + 1); + switch (c1) { + case '"': + case '\'': + case '\\': + argsStr = argsStr.substring(0, i) + argsStr.substring(i + 1); + i++; + break; + default: + throw new IllegalStateException("Invalid escape for char " + c1 + " at index " + (i + 1) + " in: " + rawString); + } + } + } + + if (quote != 0) { + throw new IllegalStateException("Unbalanced quotes!"); + } else { + if (start != argsStr.length()) { + argsList.add(argsStr.substring(start)); + } + + argsList.replaceAll(value -> removeQuotes(value.trim())); + if (hasRaw) { + String[] strings = new String[argsList.size() + 1]; + argsList.toArray(strings); + strings[argsList.size()] = rawSplit[1].trim(); + return strings; + } else { + return argsList.toArray(String[]::new); + } + } + } + + @Nonnull + public static String removeQuotes(@Nonnull String value) { + switch (value.charAt(0)) { + case '"': + case '\'': + value = value.substring(1, value.length() - 1); + default: + return value; + } + } + + @Nonnull + public static String stripQuotes(@Nonnull String s) { + if (s.length() >= 2) { + char first = s.charAt(0); + char last = s.charAt(s.length() - 1); + if (first == '"' && last == '"' || first == '\'' && last == '\'') { + return s.substring(1, s.length() - 1); + } + } + + return s; + } + + public static boolean isGlobMatching(@Nonnull String pattern, @Nonnull String text) { + return pattern.equals(text) || isGlobMatching(pattern, 0, text, 0); + } + + public static boolean isGlobMatching(@Nonnull String pattern, int patternPos, @Nonnull String text, int textPos) { + while (patternPos < pattern.length()) { + char charAt = pattern.charAt(patternPos); + if (charAt == '*') { + patternPos++; + + while (patternPos < pattern.length() && pattern.charAt(patternPos) == '*') { + patternPos++; + } + + if (patternPos == pattern.length()) { + return true; + } + + for (char matchChar = pattern.charAt(patternPos); textPos < text.length(); textPos++) { + if (matchChar == text.charAt(textPos) && isGlobMatching(pattern, patternPos + 1, text, textPos + 1)) { + return true; + } + } + + return false; + } + + if (textPos == text.length()) { + return false; + } + + if (charAt != '?' && charAt != text.charAt(textPos)) { + return false; + } + + patternPos++; + textPos++; + } + + return textPos == text.length(); + } + + public static boolean isGlobPattern(@Nonnull String text) { + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c == '?' || c == '*') { + return true; + } + } + + return false; + } + + @Nonnull + public static String humanizeTime(@Nonnull Duration duration, boolean useSeconds) { + long length = duration.toMillis(); + long days = length / 86400000L; + long hours = (length - days * 86400000L) / 3600000L; + long minutes = (length - (days * 86400000L + hours * 3600000L)) / 60000L; + String base = days + "d " + hours + "h " + minutes + "m"; + if (useSeconds) { + long seconds = (length - (days * 86400000L + hours * 3600000L + minutes * 60000L)) / 1000L; + base = base + " " + seconds + "s"; + } + + return base; + } + + @Nonnull + public static String humanizeTime(@Nonnull Duration length) { + return humanizeTime(length, false); + } + + @Nonnull + public static List sortByFuzzyDistance(@Nonnull String str, @Nonnull Collection collection, int length) { + List list = sortByFuzzyDistance(str, collection); + return list.size() > length ? list.subList(0, length) : list; + } + + @Nonnull + public static List sortByFuzzyDistance(@Nonnull String str, @Nonnull Collection collection) { + Object2IntMap map = new Object2IntOpenHashMap<>(collection.size()); + + for (T value : collection) { + map.put(value, StringCompareUtil.getFuzzyDistance(value.toString(), str, Locale.ENGLISH)); + } + + List list = new ObjectArrayList<>(collection); + list.sort(Comparator.comparingInt(map::getInt).reversed()); + return list; + } + + @Nonnull + public static String toPaddedBinaryString(int val) { + byte[] buf = new byte[]{48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48}; + int leadingZeros = Integer.numberOfLeadingZeros(val); + int mag = 32 - leadingZeros; + int pos = Math.max(mag, 1); + + do { + buf[leadingZeros + --pos] = (byte)(48 + (val & 1)); + val >>>= 1; + } while (pos > 0); + + return new String(buf, 0); + } + + @Nonnull + public static String trimEnd(@Nonnull String str, @Nonnull String end) { + return !str.endsWith(end) ? str : str.substring(0, str.length() - end.length()); + } + + public static void generateGraph( + @Nonnull StringBuilder sb, + int width, + int height, + long minX, + long maxX, + double minY, + double maxY, + @Nonnull DoubleFunction labelFormatFunc, + int historyLength, + @Nonnull IntToLongFunction timestampFunc, + @Nonnull IntToDoubleFunction valueFunc + ) { + double lengthY = maxY - minY; + long lengthX = maxX - minX; + double rowAggLength = lengthY / height; + double colAggLength = (double)lengthX / width; + double[] values = new double[width]; + Arrays.fill(values, -1.0); + int historyIndex = 0; + + for (int i = 0; i < width; i++) { + double total = 0.0; + int count = 0; + + for (long nextAggTimestamp = maxX - (lengthX - (long)(colAggLength * i)); + historyIndex < historyLength && timestampFunc.applyAsLong(historyIndex) < nextAggTimestamp; + historyIndex++ + ) { + total += valueFunc.applyAsDouble(historyIndex); + count++; + } + + if (count != 0) { + values[i] = total / count; + } else if (i > 0) { + values[i] = values[i - 1]; + } + } + + double last = -1.0; + + for (int i = values.length - 1; i >= 0; i--) { + if (values[i] != -1.0) { + last = values[i]; + } else if (last != -1.0) { + values[i] = last; + } + } + + int yLabelWidth = 0; + String[] labels = new String[height]; + + for (int row = 0; row < height; row++) { + double rowMaxValue = minY + lengthY - rowAggLength * row; + String label = labelFormatFunc.apply(rowMaxValue); + labels[row] = label; + int length = label.length(); + if (length > yLabelWidth) { + yLabelWidth = length; + } + } + + String bar = " ".repeat(yLabelWidth) + " " + "#".repeat(width + 2); + sb.append(bar).append('\n'); + + for (int rowx = 0; rowx < height; rowx++) { + sb.append(" ".repeat(Math.max(0, yLabelWidth - labels[rowx].length()))).append(labels[rowx]).append(" #"); + double rowMinValue = minY + lengthY - rowAggLength * (rowx + 1); + + for (int col = 0; col < width; col++) { + double colRowValue = values[col] - rowMinValue; + if (!(colRowValue <= 0.0) && !(colRowValue > rowAggLength)) { + double valuePercent = colRowValue / rowAggLength; + int charIndex = (int)Math.round(valuePercent * (GRAPH_CHARS.length - 1)); + sb.append(GRAPH_CHARS[Math.max(0, charIndex)]); + } else { + sb.append(' '); + } + } + + sb.append("#\n"); + } + + sb.append(bar).append('\n'); + sb.append('\n'); + } + + public static enum MatchType { + INDEX_DIFFERENCE, + EQUALS, + CASE_INSENSITIVE; + + private MatchType() { + } + } +} diff --git a/src/com/hypixel/hytale/common/util/SystemUtil.java b/src/com/hypixel/hytale/common/util/SystemUtil.java new file mode 100644 index 0000000..73d8cf6 --- /dev/null +++ b/src/com/hypixel/hytale/common/util/SystemUtil.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.common.util; + +import javax.annotation.Nonnull; + +public class SystemUtil { + public static final SystemUtil.SystemType TYPE = getSystemType(); + + public SystemUtil() { + } + + @Nonnull + private static SystemUtil.SystemType getSystemType() { + String osName = System.getProperty("os.name"); + if (osName.startsWith("Windows")) { + return SystemUtil.SystemType.WINDOWS; + } else if (osName.startsWith("Mac OS X")) { + return SystemUtil.SystemType.MACOS; + } else if (osName.startsWith("Linux")) { + return SystemUtil.SystemType.LINUX; + } else { + return osName.startsWith("LINUX") ? SystemUtil.SystemType.LINUX : SystemUtil.SystemType.OTHER; + } + } + + public static enum SystemType { + WINDOWS, + MACOS, + LINUX, + OTHER; + + private SystemType() { + } + } +} diff --git a/src/com/hypixel/hytale/common/util/TimeUtil.java b/src/com/hypixel/hytale/common/util/TimeUtil.java new file mode 100644 index 0000000..ebdba76 --- /dev/null +++ b/src/com/hypixel/hytale/common/util/TimeUtil.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.common.util; + +import java.time.DateTimeException; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import javax.annotation.Nonnull; + +public class TimeUtil { + public TimeUtil() { + } + + public static int compareDifference(@Nonnull Instant from, @Nonnull Instant to, @Nonnull Duration duration) { + if (from.equals(Instant.MIN) && !to.equals(Instant.MIN) && !duration.isZero()) { + return 1; + } else { + try { + long diff = from.until(to, ChronoUnit.NANOS); + return Long.compare(diff, duration.toNanos()); + } catch (ArithmeticException | DateTimeException var13) { + long seconds = from.until(to, ChronoUnit.SECONDS); + + long nanos; + try { + nanos = to.getLong(ChronoField.NANO_OF_SECOND) - from.getLong(ChronoField.NANO_OF_SECOND); + if (seconds > 0L && nanos < 0L) { + seconds++; + } else if (seconds < 0L && nanos > 0L) { + seconds--; + } + } catch (DateTimeException var12) { + nanos = 0L; + } + + long durSeconds = duration.getSeconds(); + int durNanos = duration.getNano(); + int res = Long.compare(seconds, durSeconds); + if (res == 0) { + res = Integer.compare((int)nanos, durNanos); + } + + return res; + } + } + } +} diff --git a/src/com/hypixel/hytale/common/util/java/ManifestUtil.java b/src/com/hypixel/hytale/common/util/java/ManifestUtil.java new file mode 100644 index 0000000..f8989b0 --- /dev/null +++ b/src/com/hypixel/hytale/common/util/java/ManifestUtil.java @@ -0,0 +1,125 @@ +package com.hypixel.hytale.common.util.java; + +import com.hypixel.hytale.common.semver.Semver; +import com.hypixel.hytale.function.supplier.CachedSupplier; +import com.hypixel.hytale.function.supplier.SupplierUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.Objects; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.logging.Level; +import javax.annotation.Nullable; + +public class ManifestUtil { + public static final String VENDOR_ID_PROPERTY = "Implementation-Vendor-Id"; + public static final String VERSION_PROPERTY = "Implementation-Version"; + public static final String REVISION_ID_PROPERTY = "Implementation-Revision-Id"; + public static final String PATCHLINE_PROPERTY = "Implementation-Patchline"; + private static final CachedSupplier MANIFEST = SupplierUtil.cache(() -> { + try { + ClassLoader cl = ManifestUtil.class.getClassLoader(); + Enumeration enumeration = cl.getResources("META-INF/MANIFEST.MF"); + Manifest theManifest = null; + + while (enumeration.hasMoreElements()) { + URL url = enumeration.nextElement(); + + Manifest possible; + try (InputStream is = url.openStream()) { + possible = new Manifest(is); + } + + Attributes mainAttributes = possible.getMainAttributes(); + String vendorId = mainAttributes.getValue("Implementation-Vendor-Id"); + if (vendorId != null && vendorId.equals("com.hypixel.hytale")) { + theManifest = possible; + break; + } + } + + return theManifest; + } catch (Throwable var10) { + HytaleLogger.getLogger().at(Level.WARNING).log("Exception was thrown getting manifest!", var10); + return null; + } + }); + private static final CachedSupplier IMPLEMENTATION_VERSION = SupplierUtil.cache( + () -> { + try { + Manifest localManifest = MANIFEST.get(); + return localManifest == null + ? "NoJar" + : Objects.requireNonNull(localManifest.getMainAttributes().getValue("Implementation-Version"), "Null implementation version!"); + } catch (Throwable var1) { + HytaleLogger.getLogger().at(Level.WARNING).log("Exception was thrown getting implementation version!", var1); + return "UNKNOWN"; + } + } + ); + private static final CachedSupplier IMPLEMENTATION_REVISION_ID = SupplierUtil.cache( + () -> { + try { + Manifest localManifest = MANIFEST.get(); + return localManifest == null + ? "NoJar" + : Objects.requireNonNull(localManifest.getMainAttributes().getValue("Implementation-Revision-Id"), "Null implementation revision id!"); + } catch (Throwable var1) { + HytaleLogger.getLogger().at(Level.WARNING).log("Exception was thrown getting implementation revision id!", var1); + return "UNKNOWN"; + } + } + ); + private static final CachedSupplier IMPLEMENTATION_PATCHLINE = SupplierUtil.cache(() -> { + try { + Manifest localManifest = MANIFEST.get(); + if (localManifest == null) { + return "dev"; + } else { + String value = localManifest.getMainAttributes().getValue("Implementation-Patchline"); + return value != null && !value.isEmpty() ? value : "dev"; + } + } catch (Throwable var2) { + HytaleLogger.getLogger().at(Level.WARNING).log("Exception was thrown getting implementation patchline!", var2); + return "dev"; + } + }); + private static final CachedSupplier VERSION = SupplierUtil.cache(() -> { + String version = IMPLEMENTATION_VERSION.get(); + return "NoJar".equals(version) ? null : Semver.fromString(version); + }); + + public ManifestUtil() { + } + + public static boolean isJar() { + return MANIFEST.get() != null; + } + + @Nullable + public static Manifest getManifest() { + return MANIFEST.get(); + } + + @Nullable + public static String getImplementationVersion() { + return IMPLEMENTATION_VERSION.get(); + } + + @Nullable + public static Semver getVersion() { + return VERSION.get(); + } + + @Nullable + public static String getImplementationRevisionId() { + return IMPLEMENTATION_REVISION_ID.get(); + } + + @Nullable + public static String getPatchline() { + return IMPLEMENTATION_PATCHLINE.get(); + } +} diff --git a/src/com/hypixel/hytale/component/AddReason.java b/src/com/hypixel/hytale/component/AddReason.java new file mode 100644 index 0000000..fac7bb7 --- /dev/null +++ b/src/com/hypixel/hytale/component/AddReason.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.component; + +public enum AddReason { + SPAWN, + LOAD; + + private AddReason() { + } +} diff --git a/src/com/hypixel/hytale/component/Archetype.java b/src/com/hypixel/hytale/component/Archetype.java new file mode 100644 index 0000000..1bd5d13 --- /dev/null +++ b/src/com/hypixel/hytale/component/Archetype.java @@ -0,0 +1,314 @@ +package com.hypixel.hytale.component; + +import com.hypixel.hytale.component.data.unknown.UnknownComponents; +import com.hypixel.hytale.component.query.ExactArchetypeQuery; +import com.hypixel.hytale.component.query.Query; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Archetype implements Query { + @Nonnull + private static final Archetype EMPTY = new Archetype(0, 0, ComponentType.EMPTY_ARRAY); + private final int minIndex; + private final int count; + @Nonnull + private final ComponentType[] componentTypes; + @Nonnull + private final ExactArchetypeQuery exactQuery = new ExactArchetypeQuery<>(this); + + public static Archetype empty() { + return EMPTY; + } + + private Archetype(int minIndex, int count, @Nonnull ComponentType[] componentTypes) { + this.minIndex = minIndex; + this.count = count; + this.componentTypes = componentTypes; + } + + public int getMinIndex() { + return this.minIndex; + } + + public int count() { + return this.count; + } + + public int length() { + return this.componentTypes.length; + } + + @Nullable + public ComponentType get(int index) { + return this.componentTypes[index]; + } + + public boolean isEmpty() { + return this.componentTypes.length == 0; + } + + public boolean contains(@Nonnull ComponentType componentType) { + int index = componentType.getIndex(); + return index < this.componentTypes.length && this.componentTypes[index] == componentType; + } + + public boolean contains(@Nonnull Archetype archetype) { + if (this != archetype && !archetype.isEmpty()) { + for (int i = archetype.minIndex; i < archetype.componentTypes.length; i++) { + ComponentType componentType = archetype.componentTypes[i]; + if (componentType != null && !this.contains(componentType)) { + return false; + } + } + + return true; + } else { + return true; + } + } + + public void validateComponentType(@Nonnull ComponentType componentType) { + if (!this.contains(componentType)) { + throw new IllegalArgumentException("ComponentType is not in archetype: " + componentType + ", " + this); + } + } + + public void validateComponents(@Nonnull Component[] components, @Nullable ComponentType> ignore) { + int len = Math.max(this.componentTypes.length, components.length); + + for (int index = 0; index < len; index++) { + ComponentType componentType = index >= this.componentTypes.length ? null : this.componentTypes[index]; + Component component = index >= components.length ? null : components[index]; + if (componentType == null) { + if (component != null && (ignore == null || index != ignore.getIndex())) { + throw new IllegalStateException("Invalid component at index " + index + " expected null but found " + component.getClass()); + } + } else { + Class typeClass = componentType.getTypeClass(); + if (component == null) { + throw new IllegalStateException("Invalid component at index " + index + " expected " + typeClass + " but found null"); + } + + Class aClass = (Class)component.getClass(); + if (!aClass.equals(typeClass)) { + throw new IllegalStateException("Invalid component at index " + index + " expected " + typeClass + " but found " + aClass); + } + } + } + } + + public boolean hasSerializableComponents(@Nonnull ComponentRegistry.Data data) { + if (this.isEmpty()) { + return false; + } else if (this.contains(data.getRegistry().getNonSerializedComponentType())) { + return false; + } else { + for (int index = this.minIndex; index < this.componentTypes.length; index++) { + ComponentType componentType = this.componentTypes[index]; + if (componentType != null && data.getComponentCodec(componentType) != null) { + return true; + } + } + + return false; + } + } + + public Archetype getSerializableArchetype(@Nonnull ComponentRegistry.Data data) { + if (this.isEmpty()) { + return EMPTY; + } else if (this.contains(data.getRegistry().getNonSerializedComponentType())) { + return EMPTY; + } else { + int lastSerializableIndex = this.componentTypes.length - 1; + + for (int index = this.componentTypes.length - 1; index >= this.minIndex; index--) { + ComponentType componentType = this.componentTypes[index]; + if (componentType != null && data.getComponentCodec(componentType) != null) { + lastSerializableIndex = index; + break; + } + } + + if (lastSerializableIndex < this.minIndex) { + return EMPTY; + } else { + ComponentType[] serializableComponentTypes = new ComponentType[lastSerializableIndex + 1]; + int serializableMinIndex = this.minIndex; + + for (int indexx = serializableMinIndex; indexx < serializableComponentTypes.length; indexx++) { + ComponentType componentType = this.componentTypes[indexx]; + if (componentType != null && data.getComponentCodec(componentType) != null) { + serializableMinIndex = Math.min(serializableMinIndex, indexx); + serializableComponentTypes[indexx] = componentType; + } + } + + return new Archetype<>(this.minIndex, serializableComponentTypes.length, serializableComponentTypes); + } + } + } + + @Nonnull + public ExactArchetypeQuery asExactQuery() { + return this.exactQuery; + } + + @Nonnull + public static Archetype of(@Nonnull ComponentType componentTypes) { + int index = componentTypes.getIndex(); + ComponentType[] arr = new ComponentType[index + 1]; + arr[index] = componentTypes; + return new Archetype<>(index, 1, arr); + } + + @SafeVarargs + public static Archetype of(@Nonnull ComponentType... componentTypes) { + if (componentTypes.length == 0) { + return EMPTY; + } else { + ComponentRegistry registry = componentTypes[0].getRegistry(); + int minIndex = Integer.MAX_VALUE; + int maxIndex = Integer.MIN_VALUE; + + for (int i = 0; i < componentTypes.length; i++) { + componentTypes[i].validateRegistry(registry); + int index = componentTypes[i].getIndex(); + if (index < minIndex) { + minIndex = index; + } + + if (index > maxIndex) { + maxIndex = index; + } + + for (int n = i + 1; n < componentTypes.length; n++) { + if (componentTypes[i] == componentTypes[n]) { + throw new IllegalArgumentException("ComponentType provided multiple times! " + Arrays.toString((Object[])componentTypes)); + } + } + } + + ComponentType[] arr = new ComponentType[maxIndex + 1]; + + for (ComponentType componentType : componentTypes) { + arr[componentType.getIndex()] = componentType; + } + + return new Archetype<>(minIndex, componentTypes.length, arr); + } + } + + @Nonnull + public static > Archetype add( + @Nonnull Archetype archetype, @Nonnull ComponentType componentType + ) { + if (archetype.isEmpty()) { + return of(componentType); + } else if (archetype.contains(componentType)) { + throw new IllegalArgumentException("ComponentType is already in Archetype! " + archetype + ", " + componentType); + } else { + archetype.validateRegistry(componentType.getRegistry()); + int index = componentType.getIndex(); + int minIndex = Math.min(index, archetype.minIndex); + int newLength = Math.max(index + 1, archetype.componentTypes.length); + ComponentType[] arr = Arrays.copyOf(archetype.componentTypes, newLength); + arr[index] = componentType; + return new Archetype<>(minIndex, archetype.count + 1, arr); + } + } + + public static > Archetype remove( + @Nonnull Archetype archetype, @Nonnull ComponentType componentType + ) { + if (archetype.isEmpty()) { + throw new IllegalArgumentException("Archetype is already empty!"); + } else if (!archetype.contains(componentType)) { + throw new IllegalArgumentException("Archetype doesn't contain ComponentType! " + archetype + ", " + componentType); + } else { + int oldLength = archetype.componentTypes.length; + int oldMinIndex = archetype.minIndex; + int oldMaxIndex = oldLength - 1; + if (oldMinIndex == oldMaxIndex) { + return EMPTY; + } else { + int newCount = archetype.count - 1; + int index = componentType.getIndex(); + if (index == oldMaxIndex) { + int maxIndex = index - 1; + + while (maxIndex > oldMinIndex && archetype.componentTypes[maxIndex] == null) { + maxIndex--; + } + + return new Archetype<>(oldMinIndex, newCount, Arrays.copyOf(archetype.componentTypes, maxIndex + 1)); + } else { + ComponentType[] arr = Arrays.copyOf(archetype.componentTypes, oldLength); + arr[index] = null; + if (index != oldMinIndex) { + return new Archetype<>(oldMinIndex, newCount, arr); + } else { + int minIndex = index + 1; + + while (minIndex < oldLength && arr[minIndex] == null) { + minIndex++; + } + + return new Archetype<>(minIndex, newCount, arr); + } + } + } + } + } + + @Override + public boolean test(@Nonnull Archetype archetype) { + return archetype.contains(this); + } + + @Override + public boolean requiresComponentType(@Nonnull ComponentType componentType) { + return this.contains(componentType); + } + + @Override + public void validateRegistry(ComponentRegistry registry) { + if (!this.isEmpty()) { + this.componentTypes[this.minIndex].validateRegistry(registry); + } + } + + @Override + public void validate() { + for (int i = this.minIndex; i < this.componentTypes.length; i++) { + ComponentType componentType = this.componentTypes[i]; + if (componentType != null) { + componentType.validate(); + } + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Archetype archetype = (Archetype)o; + return Arrays.equals((Object[])this.componentTypes, (Object[])archetype.componentTypes); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Arrays.hashCode((Object[])this.componentTypes); + } + + @Nonnull + @Override + public String toString() { + return "Archetype{componentTypes=" + Arrays.toString((Object[])this.componentTypes) + "}"; + } +} diff --git a/src/com/hypixel/hytale/component/ArchetypeChunk.java b/src/com/hypixel/hytale/component/ArchetypeChunk.java new file mode 100644 index 0000000..f394ec1 --- /dev/null +++ b/src/com/hypixel/hytale/component/ArchetypeChunk.java @@ -0,0 +1,343 @@ +package com.hypixel.hytale.component; + +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.function.consumer.IntObjectConsumer; +import java.util.Arrays; +import java.util.function.Consumer; +import java.util.function.IntPredicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ArchetypeChunk { + @Nonnull + private static final ArchetypeChunk[] EMPTY_ARRAY = new ArchetypeChunk[0]; + @Nonnull + protected final Store store; + @Nonnull + protected final Archetype archetype; + protected int entitiesSize; + @Nonnull + protected Ref[] refs = new Ref[16]; + protected Component[][] components; + + public static ArchetypeChunk[] emptyArray() { + return EMPTY_ARRAY; + } + + public ArchetypeChunk(@Nonnull Store store, @Nonnull Archetype archetype) { + this.store = store; + this.archetype = archetype; + this.components = new Component[archetype.length()][]; + + for (int i = archetype.getMinIndex(); i < archetype.length(); i++) { + ComponentType> componentType = (ComponentType>)archetype.get(i); + if (componentType != null) { + this.components[componentType.getIndex()] = new Component[16]; + } + } + } + + @Nonnull + public Archetype getArchetype() { + return this.archetype; + } + + public int size() { + return this.entitiesSize; + } + + @Nonnull + public Ref getReferenceTo(int index) { + if (index >= 0 && index < this.entitiesSize) { + return this.refs[index]; + } else { + throw new IndexOutOfBoundsException(index); + } + } + + public > void setComponent(int index, @Nonnull ComponentType componentType, @Nonnull T component) { + componentType.validateRegistry(this.store.getRegistry()); + if (index < 0 || index >= this.entitiesSize) { + throw new IndexOutOfBoundsException(index); + } else if (!this.archetype.contains(componentType)) { + throw new IllegalArgumentException("Entity doesn't have component type " + componentType); + } else { + this.components[componentType.getIndex()][index] = component; + } + } + + @Nullable + public > T getComponent(int index, @Nonnull ComponentType componentType) { + componentType.validateRegistry(this.store.getRegistry()); + if (index < 0 || index >= this.entitiesSize) { + throw new IndexOutOfBoundsException(index); + } else { + return (T)(!this.archetype.contains(componentType) ? null : this.components[componentType.getIndex()][index]); + } + } + + public int addEntity(@Nonnull Ref ref, @Nonnull Holder holder) { + if (!this.archetype.equals(holder.getArchetype())) { + throw new IllegalArgumentException("EntityHolder is not for this archetype chunk!"); + } else { + int entityIndex = this.entitiesSize++; + int oldLength = this.refs.length; + if (oldLength <= entityIndex) { + int newLength = ArrayUtil.grow(entityIndex); + this.refs = Arrays.copyOf(this.refs, newLength); + + for (int i = this.archetype.getMinIndex(); i < this.archetype.length(); i++) { + ComponentType> componentType = (ComponentType>)this.archetype + .get(i); + if (componentType != null) { + int componentTypeIndex = componentType.getIndex(); + this.components[componentTypeIndex] = Arrays.copyOf(this.components[componentTypeIndex], newLength); + } + } + } + + this.refs[entityIndex] = ref; + + for (int ix = this.archetype.getMinIndex(); ix < this.archetype.length(); ix++) { + ComponentType> componentType = (ComponentType>)this.archetype + .get(ix); + if (componentType != null) { + this.components[componentType.getIndex()][entityIndex] = holder.getComponent((ComponentType>)componentType); + } + } + + return entityIndex; + } + } + + @Nonnull + public Holder copyEntity(int entityIndex, @Nonnull Holder target) { + if (entityIndex >= this.entitiesSize) { + throw new IndexOutOfBoundsException(entityIndex); + } else { + Component[] entityComponents = target.ensureComponentsSize(this.archetype.length()); + + for (int i = this.archetype.getMinIndex(); i < this.archetype.length(); i++) { + ComponentType> componentType = (ComponentType>)this.archetype + .get(i); + if (componentType != null) { + int componentTypeIndex = componentType.getIndex(); + Component component = this.components[componentTypeIndex][entityIndex]; + entityComponents[componentTypeIndex] = component.clone(); + } + } + + target.init(this.archetype, entityComponents); + return target; + } + } + + @Nonnull + public Holder copySerializableEntity(@Nonnull ComponentRegistry.Data data, int entityIndex, @Nonnull Holder target) { + if (entityIndex >= this.entitiesSize) { + throw new IndexOutOfBoundsException(entityIndex); + } else { + Archetype serializableArchetype = this.archetype.getSerializableArchetype(data); + Component[] entityComponents = target.ensureComponentsSize(serializableArchetype.length()); + + for (int i = serializableArchetype.getMinIndex(); i < serializableArchetype.length(); i++) { + ComponentType> componentType = (ComponentType>)serializableArchetype.get( + i + ); + if (componentType != null) { + int componentTypeIndex = componentType.getIndex(); + Component component = this.components[componentTypeIndex][entityIndex]; + entityComponents[componentTypeIndex] = component.cloneSerializable(); + } + } + + target.init(serializableArchetype, entityComponents); + return target; + } + } + + @Nonnull + public Holder removeEntity(int entityIndex, @Nonnull Holder target) { + if (entityIndex >= this.entitiesSize) { + throw new IndexOutOfBoundsException(entityIndex); + } else { + Component[] entityComponents = target.ensureComponentsSize(this.archetype.length()); + + for (int i = this.archetype.getMinIndex(); i < this.archetype.length(); i++) { + ComponentType> componentType = (ComponentType>)this.archetype + .get(i); + if (componentType != null) { + int componentTypeIndex = componentType.getIndex(); + entityComponents[componentTypeIndex] = this.components[componentTypeIndex][entityIndex]; + } + } + + int lastIndex = this.entitiesSize - 1; + if (entityIndex != lastIndex) { + this.fillEmptyIndex(entityIndex, lastIndex); + } + + this.refs[lastIndex] = null; + + for (int ix = this.archetype.getMinIndex(); ix < this.archetype.length(); ix++) { + ComponentType> componentType = (ComponentType>)this.archetype + .get(ix); + if (componentType != null) { + this.components[componentType.getIndex()][lastIndex] = null; + } + } + + this.entitiesSize = lastIndex; + target.init(this.archetype, entityComponents); + return target; + } + } + + public void transferTo( + @Nonnull Holder tempInternalEntityHolder, + @Nonnull ArchetypeChunk chunk, + @Nonnull Consumer> modification, + @Nonnull IntObjectConsumer> referenceConsumer + ) { + Component[] entityComponents = new Component[this.archetype.length()]; + + for (int entityIndex = 0; entityIndex < this.entitiesSize; entityIndex++) { + Ref ref = this.refs[entityIndex]; + this.refs[entityIndex] = null; + + for (int i = this.archetype.getMinIndex(); i < this.archetype.length(); i++) { + ComponentType> componentType = (ComponentType>)this.archetype + .get(i); + if (componentType != null) { + int componentTypeIndex = componentType.getIndex(); + entityComponents[componentTypeIndex] = this.components[componentTypeIndex][entityIndex]; + this.components[componentTypeIndex][entityIndex] = null; + } + } + + tempInternalEntityHolder._internal_init(this.archetype, entityComponents, this.store.getRegistry().getUnknownComponentType()); + modification.accept(tempInternalEntityHolder); + int newEntityIndex = chunk.addEntity(ref, tempInternalEntityHolder); + referenceConsumer.accept(newEntityIndex, ref); + } + + this.entitiesSize = 0; + } + + public void transferSomeTo( + @Nonnull Holder tempInternalEntityHolder, + @Nonnull ArchetypeChunk chunk, + @Nonnull IntPredicate shouldTransfer, + @Nonnull Consumer> modification, + @Nonnull IntObjectConsumer> referenceConsumer + ) { + int firstTransfered = Integer.MIN_VALUE; + int lastTransfered = Integer.MIN_VALUE; + Component[] entityComponents = new Component[this.archetype.length()]; + + for (int entityIndex = 0; entityIndex < this.entitiesSize; entityIndex++) { + if (shouldTransfer.test(entityIndex)) { + if (firstTransfered == Integer.MIN_VALUE) { + firstTransfered = entityIndex; + } + + lastTransfered = entityIndex; + Ref ref = this.refs[entityIndex]; + this.refs[entityIndex] = null; + + for (int i = this.archetype.getMinIndex(); i < this.archetype.length(); i++) { + ComponentType> componentType = (ComponentType>)this.archetype + .get(i); + if (componentType != null) { + int componentTypeIndex = componentType.getIndex(); + entityComponents[componentTypeIndex] = this.components[componentTypeIndex][entityIndex]; + this.components[componentTypeIndex][entityIndex] = null; + } + } + + tempInternalEntityHolder.init(this.archetype, entityComponents); + modification.accept(tempInternalEntityHolder); + int newEntityIndex = chunk.addEntity(ref, tempInternalEntityHolder); + referenceConsumer.accept(newEntityIndex, ref); + } + } + + if (firstTransfered != Integer.MIN_VALUE) { + if (lastTransfered == this.entitiesSize - 1) { + this.entitiesSize = firstTransfered; + return; + } + + int newSize = this.entitiesSize - (lastTransfered - firstTransfered + 1); + + for (int entityIndexx = firstTransfered; entityIndexx <= lastTransfered; entityIndexx++) { + if (this.refs[entityIndexx] == null) { + int lastIndex = this.entitiesSize - 1; + if (lastIndex == lastTransfered) { + break; + } + + if (entityIndexx != lastIndex) { + this.fillEmptyIndex(entityIndexx, lastIndex); + } + + this.entitiesSize--; + } + } + + this.entitiesSize = newSize; + } + } + + protected void fillEmptyIndex(int entityIndex, int lastIndex) { + Ref ref = this.refs[lastIndex]; + this.store.setEntityChunkIndex(ref, entityIndex); + + for (int i = this.archetype.getMinIndex(); i < this.archetype.length(); i++) { + ComponentType> componentType = (ComponentType>)this.archetype.get(i); + if (componentType != null) { + Component[] componentArr = this.components[componentType.getIndex()]; + componentArr[entityIndex] = componentArr[lastIndex]; + } + } + + this.refs[entityIndex] = ref; + } + + public void appendDump(@Nonnull String prefix, @Nonnull StringBuilder sb) { + sb.append(prefix).append("archetype=").append(this.archetype).append("\n"); + sb.append(prefix).append("entitiesSize=").append(this.entitiesSize).append("\n"); + + for (int i = 0; i < this.entitiesSize; i++) { + sb.append(prefix).append("\t- ").append(this.refs[i]).append("\n"); + sb.append(prefix).append("\t").append("components=").append("\n"); + + for (int x = this.archetype.getMinIndex(); x < this.archetype.length(); x++) { + ComponentType> componentType = (ComponentType>)this.archetype + .get(x); + if (componentType != null) { + sb.append(prefix) + .append("\t\t- ") + .append(componentType.getIndex()) + .append("\t") + .append(this.components[componentType.getIndex()][x]) + .append("\n"); + } + } + } + } + + @Nonnull + @Override + public String toString() { + return "ArchetypeChunk{archetype=" + + this.archetype + + ", entitiesSize=" + + this.entitiesSize + + ", entityReferences=" + + Arrays.toString((Object[])this.refs) + + ", components=" + + Arrays.toString((Object[])this.components) + + "}"; + } +} diff --git a/src/com/hypixel/hytale/component/CommandBuffer.java b/src/com/hypixel/hytale/component/CommandBuffer.java new file mode 100644 index 0000000..a4fa1f3 --- /dev/null +++ b/src/com/hypixel/hytale/component/CommandBuffer.java @@ -0,0 +1,385 @@ +package com.hypixel.hytale.component; + +import com.hypixel.hytale.component.event.EntityEventType; +import com.hypixel.hytale.component.event.WorldEventType; +import com.hypixel.hytale.component.system.EcsEvent; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CommandBuffer implements ComponentAccessor { + @Nonnull + private final Store store; + @Nonnull + private final Deque>> queue = new ArrayDeque<>(); + @Nullable + private Ref trackedRef; + private boolean trackedRefRemoved; + @Nullable + private CommandBuffer parentBuffer; + @Nullable + private Thread thread; + + protected CommandBuffer(@Nonnull Store store) { + this.store = store; + + assert this.setThread(); + } + + @Nonnull + public Store getStore() { + return this.store; + } + + public void run(@Nonnull Consumer> consumer) { + assert Thread.currentThread() == this.thread; + + this.queue.add(consumer); + } + + @Override + public > T getComponent(@Nonnull Ref ref, @Nonnull ComponentType componentType) { + assert Thread.currentThread() == this.thread; + + return this.store.__internal_getComponent(ref, componentType); + } + + @Nonnull + @Override + public Archetype getArchetype(@Nonnull Ref ref) { + assert Thread.currentThread() == this.thread; + + return this.store.__internal_getArchetype(ref); + } + + @Nonnull + @Override + public > T getResource(@Nonnull ResourceType resourceType) { + assert Thread.currentThread() == this.thread; + + return this.store.__internal_getResource(resourceType); + } + + @Nonnull + @Override + public ECS_TYPE getExternalData() { + return this.store.getExternalData(); + } + + @Nonnull + @Override + public Ref[] addEntities(@Nonnull Holder[] holders, @Nonnull AddReason reason) { + assert Thread.currentThread() == this.thread; + + Ref[] refs = new Ref[holders.length]; + + for (int i = 0; i < holders.length; i++) { + refs[i] = new Ref<>(this.store); + } + + this.queue.add(chunk -> chunk.addEntities(holders, refs, reason)); + return refs; + } + + @Nonnull + @Override + public Ref addEntity(@Nonnull Holder holder, @Nonnull AddReason reason) { + assert Thread.currentThread() == this.thread; + + Ref ref = new Ref<>(this.store); + this.queue.add(chunk -> chunk.addEntity(holder, ref, reason)); + return ref; + } + + public void addEntities( + @Nonnull Holder[] holders, int holderStart, @Nonnull Ref[] refs, int refStart, int length, @Nonnull AddReason reason + ) { + assert Thread.currentThread() == this.thread; + + for (int i = refStart; i < refStart + length; i++) { + refs[i] = new Ref<>(this.store); + } + + this.queue.add(chunk -> chunk.addEntities(holders, holderStart, refs, refStart, length, reason)); + } + + @Nonnull + public Ref addEntity(@Nonnull Holder holder, @Nonnull Ref ref, @Nonnull AddReason reason) { + if (ref.isValid()) { + throw new IllegalArgumentException("EntityReference is already in use!"); + } else if (ref.getStore() != this.store) { + throw new IllegalArgumentException("EntityReference is not for the correct store!"); + } else { + assert Thread.currentThread() == this.thread; + + this.queue.add(chunk -> chunk.addEntity(holder, ref, reason)); + return ref; + } + } + + @Nonnull + public Holder copyEntity(@Nonnull Ref ref, @Nonnull Holder target) { + assert Thread.currentThread() == this.thread; + + this.queue.add(chunk -> chunk.copyEntity(ref, target)); + return target; + } + + public void tryRemoveEntity(@Nonnull Ref ref, @Nonnull RemoveReason reason) { + assert Thread.currentThread() == this.thread; + + Throwable source = new Throwable(); + this.queue.add(chunk -> { + if (ref.isValid()) { + chunk.removeEntity(ref, chunk.getRegistry().newHolder(), reason, source); + } + }); + if (ref.equals(this.trackedRef)) { + this.trackedRefRemoved = true; + } + + if (this.parentBuffer != null) { + this.parentBuffer.testRemovedTracked(ref); + } + } + + public void removeEntity(@Nonnull Ref ref, @Nonnull RemoveReason reason) { + assert Thread.currentThread() == this.thread; + + Throwable source = new Throwable(); + this.queue.add(chunk -> chunk.removeEntity(ref, chunk.getRegistry().newHolder(), reason, source)); + if (ref.equals(this.trackedRef)) { + this.trackedRefRemoved = true; + } + + if (this.parentBuffer != null) { + this.parentBuffer.testRemovedTracked(ref); + } + } + + @Nonnull + @Override + public Holder removeEntity(@Nonnull Ref ref, @Nonnull Holder target, @Nonnull RemoveReason reason) { + assert Thread.currentThread() == this.thread; + + Throwable source = new Throwable(); + this.queue.add(chunk -> chunk.removeEntity(ref, target, reason, source)); + if (ref.equals(this.trackedRef)) { + this.trackedRefRemoved = true; + } + + if (this.parentBuffer != null) { + this.parentBuffer.testRemovedTracked(ref); + } + + return target; + } + + public > void ensureComponent(@Nonnull Ref ref, @Nonnull ComponentType componentType) { + assert Thread.currentThread() == this.thread; + + this.queue.add(chunk -> { + if (ref.isValid()) { + chunk.ensureComponent(ref, componentType); + } + }); + } + + @Nonnull + @Override + public > T ensureAndGetComponent(@Nonnull Ref ref, @Nonnull ComponentType componentType) { + assert Thread.currentThread() == this.thread; + + T component = this.store.__internal_getComponent(ref, componentType); + if (component != null) { + return component; + } else { + T newComponent = this.store.getRegistry()._internal_getData().createComponent(componentType); + this.queue.add(chunk -> { + if (ref.isValid()) { + chunk.addComponent(ref, componentType, newComponent); + } + }); + return newComponent; + } + } + + @Nonnull + @Override + public > T addComponent(@Nonnull Ref ref, @Nonnull ComponentType componentType) { + assert Thread.currentThread() == this.thread; + + T component = this.store.getRegistry()._internal_getData().createComponent(componentType); + this.queue.add(chunk -> { + if (ref.isValid()) { + chunk.addComponent(ref, componentType, component); + } + }); + return component; + } + + @Override + public > void addComponent(@Nonnull Ref ref, @Nonnull ComponentType componentType, @Nonnull T component) { + assert Thread.currentThread() == this.thread; + + this.queue.add(chunk -> { + if (ref.isValid()) { + chunk.addComponent(ref, componentType, component); + } + }); + } + + public > void replaceComponent( + @Nonnull Ref ref, @Nonnull ComponentType componentType, @Nonnull T component + ) { + assert Thread.currentThread() == this.thread; + + this.queue.add(chunk -> { + if (ref.isValid()) { + chunk.replaceComponent(ref, componentType, component); + } + }); + } + + @Override + public > void removeComponent(@Nonnull Ref ref, @Nonnull ComponentType componentType) { + assert Thread.currentThread() == this.thread; + + this.queue.add(chunk -> { + if (ref.isValid()) { + chunk.removeComponent(ref, componentType); + } + }); + } + + @Override + public > void tryRemoveComponent(@Nonnull Ref ref, @Nonnull ComponentType componentType) { + assert Thread.currentThread() == this.thread; + + this.queue.add(chunk -> { + if (ref.isValid()) { + chunk.tryRemoveComponent(ref, componentType); + } + }); + } + + @Override + public > void putComponent(@Nonnull Ref ref, @Nonnull ComponentType componentType, @Nonnull T component) { + assert Thread.currentThread() == this.thread; + + this.queue.add(chunk -> { + if (ref.isValid()) { + chunk.putComponent(ref, componentType, component); + } + }); + } + + @Override + public void invoke(@Nonnull Ref ref, @Nonnull Event param) { + assert Thread.currentThread() == this.thread; + + this.store.internal_invoke(this, ref, param); + } + + @Override + public void invoke(@Nonnull EntityEventType systemType, @Nonnull Ref ref, @Nonnull Event param) { + assert Thread.currentThread() == this.thread; + + this.store.internal_invoke(this, systemType, ref, param); + } + + @Override + public void invoke(@Nonnull Event param) { + assert Thread.currentThread() == this.thread; + + this.store.internal_invoke(this, param); + } + + @Override + public void invoke(@Nonnull WorldEventType systemType, @Nonnull Event param) { + assert Thread.currentThread() == this.thread; + + this.store.internal_invoke(this, systemType, param); + } + + void track(@Nonnull Ref ref) { + this.trackedRef = ref; + } + + private void testRemovedTracked(@Nonnull Ref ref) { + if (ref.equals(this.trackedRef)) { + this.trackedRefRemoved = true; + } + + if (this.parentBuffer != null) { + this.parentBuffer.testRemovedTracked(ref); + } + } + + boolean consumeWasTrackedRefRemoved() { + if (this.trackedRef == null) { + throw new IllegalStateException("Not tracking any ref!"); + } else { + boolean wasRemoved = this.trackedRefRemoved; + this.trackedRefRemoved = false; + return wasRemoved; + } + } + + void consume() { + this.trackedRef = null; + this.trackedRefRemoved = false; + + assert Thread.currentThread() == this.thread; + + while (!this.queue.isEmpty()) { + this.queue.pop().accept(this.store); + } + + this.store.storeCommandBuffer(this); + } + + @Nonnull + public CommandBuffer fork() { + CommandBuffer forkedBuffer = this.store.takeCommandBuffer(); + forkedBuffer.parentBuffer = this; + return forkedBuffer; + } + + public void mergeParallel(@Nonnull CommandBuffer commandBuffer) { + this.trackedRef = null; + this.trackedRefRemoved = false; + this.parentBuffer = null; + + while (!this.queue.isEmpty()) { + commandBuffer.queue.add(this.queue.pop()); + } + + this.store.storeCommandBuffer(this); + } + + public boolean setThread() { + boolean areAssertionsEnabled = false; + if (!$assertionsDisabled) { + areAssertionsEnabled = true; + if (false) { + throw new AssertionError(); + } + } + + if (!areAssertionsEnabled) { + throw new AssertionError("setThread should only be called when assertions are enabled!"); + } else { + this.thread = Thread.currentThread(); + return true; + } + } + + public void validateEmpty() { + if (!this.queue.isEmpty()) { + throw new AssertionError("CommandBuffer must be empty when returned to store!"); + } + } +} diff --git a/src/com/hypixel/hytale/component/Component.java b/src/com/hypixel/hytale/component/Component.java new file mode 100644 index 0000000..f85c40c --- /dev/null +++ b/src/com/hypixel/hytale/component/Component.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.component; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface Component extends Cloneable { + @Nonnull + Component[] EMPTY_ARRAY = new Component[0]; + + @Nullable + Component clone(); + + @Nullable + default Component cloneSerializable() { + return this.clone(); + } +} diff --git a/src/com/hypixel/hytale/component/ComponentAccessor.java b/src/com/hypixel/hytale/component/ComponentAccessor.java new file mode 100644 index 0000000..20edb4f --- /dev/null +++ b/src/com/hypixel/hytale/component/ComponentAccessor.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.component; + +import com.hypixel.hytale.component.event.EntityEventType; +import com.hypixel.hytale.component.event.WorldEventType; +import com.hypixel.hytale.component.system.EcsEvent; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface ComponentAccessor { + @Nullable + > T getComponent(@Nonnull Ref var1, @Nonnull ComponentType var2); + + @Nonnull + > T ensureAndGetComponent(@Nonnull Ref var1, @Nonnull ComponentType var2); + + @Nonnull + Archetype getArchetype(@Nonnull Ref var1); + + @Nonnull + > T getResource(@Nonnull ResourceType var1); + + @Nonnull + ECS_TYPE getExternalData(); + + > void putComponent(@Nonnull Ref var1, @Nonnull ComponentType var2, @Nonnull T var3); + + > void addComponent(@Nonnull Ref var1, @Nonnull ComponentType var2, @Nonnull T var3); + + > T addComponent(@Nonnull Ref var1, @Nonnull ComponentType var2); + + Ref[] addEntities(@Nonnull Holder[] var1, @Nonnull AddReason var2); + + @Nullable + Ref addEntity(@Nonnull Holder var1, @Nonnull AddReason var2); + + @Nonnull + Holder removeEntity(@Nonnull Ref var1, @Nonnull Holder var2, @Nonnull RemoveReason var3); + + > void removeComponent(@Nonnull Ref var1, @Nonnull ComponentType var2); + + > void tryRemoveComponent(@Nonnull Ref var1, @Nonnull ComponentType var2); + + void invoke(@Nonnull Ref var1, @Nonnull Event var2); + + void invoke(@Nonnull EntityEventType var1, @Nonnull Ref var2, @Nonnull Event var3); + + void invoke(@Nonnull Event var1); + + void invoke(@Nonnull WorldEventType var1, @Nonnull Event var2); +} diff --git a/src/com/hypixel/hytale/component/ComponentRegistration.java b/src/com/hypixel/hytale/component/ComponentRegistration.java new file mode 100644 index 0000000..747c0b4 --- /dev/null +++ b/src/com/hypixel/hytale/component/ComponentRegistration.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.component; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public record ComponentRegistration>( + @Nonnull Class typeClass, + @Nullable String id, + @Nullable BuilderCodec codec, + @Nonnull Supplier supplier, + @Nonnull ComponentType componentType +) { +} diff --git a/src/com/hypixel/hytale/component/ComponentRegistry.java b/src/com/hypixel/hytale/component/ComponentRegistry.java new file mode 100644 index 0000000..2e8e7a4 --- /dev/null +++ b/src/com/hypixel/hytale/component/ComponentRegistry.java @@ -0,0 +1,1661 @@ +package com.hypixel.hytale.component; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.MapProvidedMapCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.component.data.change.ChangeType; +import com.hypixel.hytale.component.data.change.ComponentChange; +import com.hypixel.hytale.component.data.change.DataChange; +import com.hypixel.hytale.component.data.change.ResourceChange; +import com.hypixel.hytale.component.data.change.SystemChange; +import com.hypixel.hytale.component.data.change.SystemGroupChange; +import com.hypixel.hytale.component.data.change.SystemTypeChange; +import com.hypixel.hytale.component.data.unknown.TempUnknownComponent; +import com.hypixel.hytale.component.data.unknown.UnknownComponents; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.event.EntityEventType; +import com.hypixel.hytale.component.event.WorldEventType; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.query.ReadWriteArchetypeQuery; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.spatial.SpatialStructure; +import com.hypixel.hytale.component.system.EcsEvent; +import com.hypixel.hytale.component.system.EntityEventSystem; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.ISystem; +import com.hypixel.hytale.component.system.QuerySystem; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.System; +import com.hypixel.hytale.component.system.WorldEventSystem; +import com.hypixel.hytale.component.system.tick.ArchetypeTickingSystem; +import com.hypixel.hytale.component.system.tick.RunWhenPausedSystem; +import com.hypixel.hytale.component.system.tick.TickableSystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import it.unimi.dsi.fastutil.objects.Object2BooleanMap; +import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntMaps; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.StampedLock; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public class ComponentRegistry implements IComponentRegistry { + public static final int UNASSIGNED_INDEX = Integer.MIN_VALUE; + public static final int DEFAULT_INITIAL_SIZE = 16; + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Deprecated + private static final KeyedCodec VERSION = new KeyedCodec<>("Version", Codec.INTEGER); + private static final AtomicInteger REFERENCE_THREAD_COUNTER = new AtomicInteger(); + private boolean shutdown; + private final StampedLock dataLock = new StampedLock(); + @Nonnull + private final Object2IntMap componentIdToIndex = new Object2IntOpenHashMap<>(16); + private final BitSet componentIndexReuse = new BitSet(); + private int componentSize; + @Nonnull + private String[] componentIds = new String[16]; + @Nonnull + private BuilderCodec>[] componentCodecs = new BuilderCodec[16]; + @Nonnull + private Supplier>[] componentSuppliers = new Supplier[16]; + @Nonnull + private ComponentType>[] componentTypes = new ComponentType[16]; + @Nonnull + private final Object2IntMap resourceIdToIndex = new Object2IntOpenHashMap<>(16); + private final BitSet resourceIndexReuse = new BitSet(); + private int resourceSize; + @Nonnull + private String[] resourceIds = new String[16]; + @Nonnull + private BuilderCodec>[] resourceCodecs = new BuilderCodec[16]; + @Nonnull + private Supplier>[] resourceSuppliers = new Supplier[16]; + @Nonnull + private ResourceType>[] resourceTypes = new ResourceType[16]; + @Nonnull + private final Object2IntMap>> systemTypeClassToIndex = new Object2IntOpenHashMap<>(16); + @Nonnull + private final Object2IntMap> entityEventTypeClassToIndex = new Object2IntOpenHashMap<>(16); + @Nonnull + private final Object2IntMap> worldEventTypeClassToIndex = new Object2IntOpenHashMap<>(16); + private final BitSet systemTypeIndexReuse = new BitSet(); + private int systemTypeSize; + @Nonnull + private SystemType>[] systemTypes = new SystemType[16]; + private BitSet[] systemTypeToSystemIndex = new BitSet[16]; + private final BitSet systemGroupIndexReuse = new BitSet(); + private int systemGroupSize; + @Nonnull + private SystemGroup[] systemGroups = new SystemGroup[16]; + private int systemSize; + @Nonnull + private ISystem[] systems = new ISystem[16]; + @Nonnull + private ISystem[] sortedSystems = new ISystem[16]; + @Nonnull + private final Object2IntMap>> systemClasses = new Object2IntOpenHashMap<>(16); + @Nonnull + private final Object2BooleanMap>> systemBypassClassCheck = new Object2BooleanOpenHashMap<>(16); + @Nonnull + private final StampedLock storeLock = new StampedLock(); + private int storeSize; + @Nonnull + private Store[] stores = new Store[16]; + private final ReadWriteLock dataUpdateLock = new ReentrantReadWriteLock(); + private ComponentRegistry.Data data; + private final Set>> holders = ConcurrentHashMap.newKeySet(); + private final ReferenceQueue> holderReferenceQueue = new ReferenceQueue<>(); + @Nonnull + private final Thread holderReferenceThread; + @Nonnull + private final ComponentType> unknownComponentType; + @Nonnull + private final ComponentType> nonTickingComponentType; + @Nonnull + private final ComponentType> nonSerializedComponentType; + @Nonnull + private final SystemType> holderSystemType; + @Nonnull + private final SystemType> refSystemType; + @Nonnull + private final SystemType> refChangeSystemType; + @Nonnull + private final SystemType> querySystemType; + @Nonnull + private final SystemType> tickingSystemType; + @Nonnull + private final SystemType> tickableSystemType; + @Nonnull + private final SystemType> runWhenPausedSystemType; + @Nonnull + private final SystemType> archetypeTickingSystemType; + + public ComponentRegistry() { + this.componentIdToIndex.defaultReturnValue(Integer.MIN_VALUE); + this.resourceIdToIndex.defaultReturnValue(Integer.MIN_VALUE); + this.systemTypeClassToIndex.defaultReturnValue(Integer.MIN_VALUE); + this.entityEventTypeClassToIndex.defaultReturnValue(Integer.MIN_VALUE); + this.worldEventTypeClassToIndex.defaultReturnValue(Integer.MIN_VALUE); + + for (int i = 0; i < 16; i++) { + this.systemTypeToSystemIndex[i] = new BitSet(); + } + + this.data = new ComponentRegistry.Data<>(this); + this.unknownComponentType = this.registerComponent(UnknownComponents.class, "Unknown", UnknownComponents.CODEC); + this.nonTickingComponentType = this.registerComponent(NonTicking.class, NonTicking::get); + this.nonSerializedComponentType = this.registerComponent(NonSerialized.class, NonSerialized::get); + this.holderSystemType = this.registerSystemType(HolderSystem.class); + this.refSystemType = this.registerSystemType(RefSystem.class); + this.refChangeSystemType = this.registerSystemType(RefChangeSystem.class); + this.querySystemType = this.registerSystemType(QuerySystem.class); + this.tickingSystemType = this.registerSystemType(TickingSystem.class); + this.tickableSystemType = this.registerSystemType(TickableSystem.class); + this.runWhenPausedSystemType = this.registerSystemType(RunWhenPausedSystem.class); + this.archetypeTickingSystemType = this.registerSystemType(ArchetypeTickingSystem.class); + this.holderReferenceThread = new Thread(() -> { + try { + while (!Thread.interrupted()) { + this.holders.remove(this.holderReferenceQueue.remove()); + } + } catch (InterruptedException var2) { + Thread.currentThread().interrupt(); + } + }, "EntityHolderReferenceThread-" + REFERENCE_THREAD_COUNTER.getAndIncrement()); + this.holderReferenceThread.setDaemon(true); + this.holderReferenceThread.start(); + } + + public boolean isShutdown() { + return this.shutdown; + } + + public void shutdown() { + this.shutdown0(); + } + + void shutdown0() { + this.shutdown = true; + this.holderReferenceThread.interrupt(); + long lock = this.storeLock.writeLock(); + + try { + for (int storeIndex = this.storeSize - 1; storeIndex >= 0; storeIndex--) { + Store store = this.stores[storeIndex]; + if (store != null) { + store.shutdown0(this.data); + } + } + + this.stores = Store.EMPTY_ARRAY; + } finally { + this.storeLock.unlockWrite(lock); + } + } + + @Nonnull + public ReadWriteLock getDataUpdateLock() { + return this.dataUpdateLock; + } + + @Nonnull + public ComponentType> getUnknownComponentType() { + return this.unknownComponentType; + } + + @Nonnull + public ComponentType> getNonTickingComponentType() { + return this.nonTickingComponentType; + } + + @Nonnull + public ComponentType> getNonSerializedComponentType() { + return this.nonSerializedComponentType; + } + + @Nonnull + public SystemType> getHolderSystemType() { + return this.holderSystemType; + } + + @Nonnull + public SystemType> getRefSystemType() { + return this.refSystemType; + } + + @Nonnull + public SystemType> getRefChangeSystemType() { + return this.refChangeSystemType; + } + + @Nonnull + public SystemType> getQuerySystemType() { + return this.querySystemType; + } + + @Nonnull + public SystemType> getTickingSystemType() { + return this.tickingSystemType; + } + + @Nonnull + public SystemType> getTickableSystemType() { + return this.tickableSystemType; + } + + @Nonnull + public SystemType> getRunWhenPausedSystemType() { + return this.runWhenPausedSystemType; + } + + @Nonnull + public SystemType> getArchetypeTickingSystemType() { + return this.archetypeTickingSystemType; + } + + @Nonnull + @Override + public > ComponentType registerComponent(@Nonnull Class tClass, @Nonnull Supplier supplier) { + return this.registerComponent(tClass, null, null, supplier, false); + } + + @Nonnull + @Override + public > ComponentType registerComponent( + @Nonnull Class tClass, @Nonnull String id, @Nonnull BuilderCodec codec + ) { + return this.registerComponent(tClass, id, codec, codec::getDefaultValue, false); + } + + @Deprecated + @Nonnull + public > ComponentType registerComponent( + @Nonnull Class tClass, @Nonnull String id, @Nonnull BuilderCodec codec, boolean skipValidation + ) { + return this.registerComponent(tClass, id, codec, codec::getDefaultValue, skipValidation); + } + + @Nonnull + private > ComponentType registerComponent( + @Nonnull Class tClass, @Nullable String id, @Nullable BuilderCodec codec, @Nonnull Supplier supplier, boolean skipValidation + ) { + if (this.shutdown) { + throw new IllegalStateException("Registry has been shutdown"); + } else { + if (codec != null && !skipValidation) { + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + codec.validateDefaults(extraInfo, new HashSet<>()); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER, "Default Asset Validation Failed!\n"); + } + + long lock = this.dataLock.writeLock(); + + ComponentType var9; + try { + ComponentType componentType = this.registerComponent0(tClass, id, codec, supplier, new ComponentType<>()); + lock = this.dataLock.tryConvertToReadLock(lock); + this.updateData0(new ComponentChange<>(ChangeType.REGISTERED, componentType)); + var9 = componentType; + } finally { + this.dataLock.unlock(lock); + } + + return var9; + } + } + + public > void unregisterComponent(@Nonnull ComponentType componentType) { + if (this.shutdown) { + throw new IllegalStateException("Registry has been shutdown"); + } else { + componentType.validateRegistry(this); + componentType.validate(); + if (componentType.equals(this.unknownComponentType)) { + throw new IllegalArgumentException("UnknownComponentType can not be unregistered!"); + } else { + long lock = this.dataLock.writeLock(); + + try { + this.unregisterComponent0(componentType); + List changes = new ObjectArrayList<>(); + changes.add(new ComponentChange<>(ChangeType.UNREGISTERED, componentType)); + + for (int unsortedSystemIndex = this.systemSize - 1; unsortedSystemIndex >= 0; unsortedSystemIndex--) { + ISystem system = this.systems[unsortedSystemIndex]; + if (system instanceof QuerySystem archetypeSystem) { + Query query = archetypeSystem.getQuery(); + if (query != null && query.requiresComponentType(componentType)) { + this.unregisterSystem0(unsortedSystemIndex, system); + changes.add(new SystemChange<>(ChangeType.UNREGISTERED, system)); + } + } + } + + lock = this.dataLock.tryConvertToReadLock(lock); + this.updateData0(changes.toArray(DataChange[]::new)); + componentType.invalidate(); + } finally { + this.dataLock.unlock(lock); + } + } + } + } + + @Nonnull + @Override + public > ResourceType registerResource(@Nonnull Class tClass, @Nonnull Supplier supplier) { + return this.registerResource(tClass, null, null, supplier); + } + + @Nonnull + @Override + public > ResourceType registerResource( + @Nonnull Class tClass, @Nonnull String id, @Nonnull BuilderCodec codec + ) { + return this.registerResource(tClass, id, codec, codec::getDefaultValue); + } + + @Nonnull + private > ResourceType registerResource( + @Nonnull Class tClass, @Nullable String id, @Nullable BuilderCodec codec, @Nonnull Supplier supplier + ) { + if (this.shutdown) { + throw new IllegalStateException("Registry has been shutdown"); + } else { + if (codec != null) { + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + codec.validateDefaults(extraInfo, new HashSet<>()); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER, "Default Asset Validation Failed!\n"); + } + + long lock = this.dataLock.writeLock(); + + ResourceType var8; + try { + ResourceType resourceType = this.registerResource0(tClass, id, codec, supplier, new ResourceType<>()); + lock = this.dataLock.tryConvertToReadLock(lock); + this.updateData0(new ResourceChange<>(ChangeType.REGISTERED, resourceType)); + var8 = resourceType; + } finally { + this.dataLock.unlock(lock); + } + + return var8; + } + } + + public > void unregisterResource(@Nonnull ResourceType resourceType) { + if (this.shutdown) { + throw new IllegalStateException("Registry has been shutdown"); + } else { + resourceType.validateRegistry(this); + resourceType.validate(); + long lock = this.dataLock.writeLock(); + + try { + this.unregisterResource0(resourceType); + lock = this.dataLock.tryConvertToReadLock(lock); + this.updateData0(new ResourceChange<>(ChangeType.UNREGISTERED, resourceType)); + resourceType.invalidate(); + } finally { + this.dataLock.unlock(lock); + } + } + } + + @Nonnull + @Override + public > SystemType registerSystemType(@Nonnull Class systemTypeClass) { + if (this.shutdown) { + throw new IllegalStateException("Registry has been shutdown"); + } else if (!ISystem.class.isAssignableFrom(systemTypeClass)) { + throw new IllegalArgumentException("systemTypeClass must extend ComponentSystem! " + systemTypeClass); + } else { + long lock = this.dataLock.writeLock(); + + SystemType var5; + try { + SystemType systemType = this.registerSystemType0(systemTypeClass); + lock = this.dataLock.tryConvertToReadLock(lock); + this.updateData0(new SystemTypeChange<>(ChangeType.REGISTERED, systemType)); + var5 = systemType; + } finally { + this.dataLock.unlock(lock); + } + + return var5; + } + } + + public > void unregisterSystemType(@Nonnull SystemType systemType) { + if (this.shutdown) { + throw new IllegalStateException("Registry has been shutdown"); + } else { + systemType.validate(); + long lock = this.dataLock.writeLock(); + + try { + this.unregisterSystemType0(systemType); + lock = this.dataLock.tryConvertToReadLock(lock); + this.updateData0(new SystemTypeChange<>(ChangeType.UNREGISTERED, systemType)); + systemType.invalidate(); + } finally { + this.dataLock.unlock(lock); + } + } + } + + @Nonnull + @Override + public EntityEventType registerEntityEventType(@Nonnull Class eventTypeClass) { + if (this.shutdown) { + throw new IllegalStateException("Registry has been shutdown"); + } else if (!EcsEvent.class.isAssignableFrom(eventTypeClass)) { + throw new IllegalArgumentException("eventTypeClass must extend EcsEvent! " + eventTypeClass); + } else { + long lock = this.dataLock.writeLock(); + + EntityEventType var5; + try { + EntityEventType systemType = this.registerEntityEventType0(eventTypeClass); + lock = this.dataLock.tryConvertToReadLock(lock); + this.updateData0(new SystemTypeChange<>(ChangeType.REGISTERED, systemType)); + var5 = systemType; + } finally { + this.dataLock.unlock(lock); + } + + return var5; + } + } + + @Nonnull + @Override + public WorldEventType registerWorldEventType(@Nonnull Class eventTypeClass) { + if (this.shutdown) { + throw new IllegalStateException("Registry has been shutdown"); + } else if (!EcsEvent.class.isAssignableFrom(eventTypeClass)) { + throw new IllegalArgumentException("eventTypeClass must extend EcsEvent! " + eventTypeClass); + } else { + long lock = this.dataLock.writeLock(); + + WorldEventType var5; + try { + WorldEventType systemType = this.registerWorldEventType0(eventTypeClass); + lock = this.dataLock.tryConvertToReadLock(lock); + this.updateData0(new SystemTypeChange<>(ChangeType.REGISTERED, systemType)); + var5 = systemType; + } finally { + this.dataLock.unlock(lock); + } + + return var5; + } + } + + public void unregisterEntityEventType(@Nonnull EntityEventType eventType) { + if (this.shutdown) { + throw new IllegalStateException("Registry has been shutdown"); + } else { + eventType.validate(); + long lock = this.dataLock.writeLock(); + + try { + this.unregisterEntityEventType0(eventType); + lock = this.dataLock.tryConvertToReadLock(lock); + this.updateData0(new SystemTypeChange<>(ChangeType.UNREGISTERED, eventType)); + eventType.invalidate(); + } finally { + this.dataLock.unlock(lock); + } + } + } + + public void unregisterWorldEventType(@Nonnull WorldEventType eventType) { + if (this.shutdown) { + throw new IllegalStateException("Registry has been shutdown"); + } else { + eventType.validate(); + long lock = this.dataLock.writeLock(); + + try { + this.unregisterWorldEventType0(eventType); + lock = this.dataLock.tryConvertToReadLock(lock); + this.updateData0(new SystemTypeChange<>(ChangeType.UNREGISTERED, eventType)); + eventType.invalidate(); + } finally { + this.dataLock.unlock(lock); + } + } + } + + @Nonnull + @Override + public SystemGroup registerSystemGroup() { + return this.registerSystemGroup(Collections.emptySet()); + } + + @Nonnull + public SystemGroup registerSystemGroup(Set> dependencies) { + if (this.shutdown) { + throw new IllegalStateException("Registry has been shutdown"); + } else { + long lock = this.dataLock.writeLock(); + + SystemGroup var5; + try { + SystemGroup systemGroup = this.registerSystemGroup0(dependencies); + lock = this.dataLock.tryConvertToReadLock(lock); + this.updateData0(new SystemGroupChange<>(ChangeType.REGISTERED, systemGroup)); + var5 = systemGroup; + } finally { + this.dataLock.unlock(lock); + } + + return var5; + } + } + + public void unregisterSystemGroup(@Nonnull SystemGroup systemGroup) { + if (this.shutdown) { + throw new IllegalStateException("Registry has been shutdown"); + } else { + systemGroup.validate(); + long lock = this.dataLock.writeLock(); + + try { + this.unregisterSystemGroup0(systemGroup); + lock = this.dataLock.tryConvertToReadLock(lock); + this.updateData0(new SystemGroupChange<>(ChangeType.UNREGISTERED, systemGroup)); + systemGroup.invalidate(); + } finally { + this.dataLock.unlock(lock); + } + } + } + + @Override + public void registerSystem(@Nonnull ISystem system) { + this.registerSystem(system, false); + } + + @Deprecated(forRemoval = true) + public void registerSystem(@Nonnull ISystem system, boolean bypassClassCheck) { + if (this.shutdown) { + throw new IllegalStateException("Registry has been shutdown"); + } else { + Class systemClass = (Class)system.getClass(); + if (system instanceof QuerySystem archetypeSystem) { + Query query = archetypeSystem.getQuery(); + query.validateRegistry(this); + query.validate(); + if (query instanceof ReadWriteArchetypeQuery readWriteQuery + && readWriteQuery.getReadArchetype().equals(readWriteQuery.getWriteArchetype())) { + LOGGER.at(Level.WARNING) + .log( + "%s.getQuery() is using ReadWriteArchetypeEntityQuery with the same `Read` and `Modified` Archetype! This can be simplified by using the Archetype directly as the EntityQuery.", + systemClass.getName() + ); + } + } + + long lock = this.dataLock.writeLock(); + + try { + if (!bypassClassCheck) { + if (this.systemClasses.containsKey(systemClass)) { + throw new IllegalArgumentException("System of type " + systemClass.getName() + " is already registered!"); + } + } else { + this.systemBypassClassCheck.put(systemClass, true); + } + + if (ArrayUtil.indexOf(this.systems, system) != -1) { + throw new IllegalArgumentException("System is already registered!"); + } + + for (Dependency dependency : system.getDependencies()) { + dependency.validate(this); + } + + this.registerSystem0(system); + List changes = new ObjectArrayList<>(); + changes.add(new SystemChange<>(ChangeType.REGISTERED, system)); + if (system instanceof System theSystem) { + for (ComponentRegistration componentRegistration : theSystem.getComponentRegistrations()) { + ComponentType> componentType = this.registerComponent0( + (ComponentRegistration>)componentRegistration + ); + changes.add(new ComponentChange<>(ChangeType.REGISTERED, componentType)); + } + + for (ResourceRegistration resourceRegistration : theSystem.getResourceRegistrations()) { + ResourceType> resourceType = this.registerResource0( + (ResourceRegistration>)resourceRegistration + ); + changes.add(new ResourceChange<>(ChangeType.REGISTERED, resourceType)); + } + } + + if (system instanceof EntityEventSystem eventSystem && !this.entityEventTypeClassToIndex.containsKey(eventSystem.getEventType())) { + EntityEventType eventType = this.registerEntityEventType0(eventSystem.getEventType()); + changes.add(new SystemTypeChange<>(ChangeType.REGISTERED, (SystemType>)eventType)); + } + + if (system instanceof WorldEventSystem eventSystem && !this.worldEventTypeClassToIndex.containsKey(eventSystem.getEventType())) { + WorldEventType eventType = this.registerWorldEventType0(eventSystem.getEventType()); + changes.add(new SystemTypeChange<>(ChangeType.REGISTERED, (SystemType>)eventType)); + } + + lock = this.dataLock.tryConvertToReadLock(lock); + this.updateData0(changes.toArray(DataChange[]::new)); + } finally { + this.dataLock.unlock(lock); + } + } + } + + public void unregisterSystem(@Nonnull Class> systemClass) { + if (this.shutdown) { + throw new IllegalStateException("Registry has been shutdown"); + } else { + long lock = this.dataLock.writeLock(); + + try { + int systemIndex = this.systemClasses.getInt(systemClass); + ISystem system = this.systems[systemIndex]; + if (system != null) { + if (system instanceof QuerySystem archetypeSystem) { + Query query = archetypeSystem.getQuery(); + query.validateRegistry(this); + query.validate(); + } + + int unsortedSystemIndex = ArrayUtil.indexOf(this.systems, system); + if (unsortedSystemIndex == -1) { + throw new IllegalArgumentException("System is not registered!"); + } + + this.unregisterSystem0(unsortedSystemIndex, system); + List changes = new ObjectArrayList<>(); + changes.add(new SystemChange<>(ChangeType.UNREGISTERED, system)); + if (system instanceof System theSystem) { + for (ComponentRegistration systemComponent : theSystem.getComponentRegistrations()) { + this.unregisterComponent0(systemComponent.componentType()); + changes.add(new ComponentChange<>(ChangeType.UNREGISTERED, systemComponent.componentType())); + } + + for (ResourceRegistration systemResource : theSystem.getResourceRegistrations()) { + this.unregisterResource0(systemResource.resourceType()); + changes.add(new ResourceChange<>(ChangeType.UNREGISTERED, systemResource.resourceType())); + } + } + + lock = this.dataLock.tryConvertToReadLock(lock); + this.updateData0(changes.toArray(DataChange[]::new)); + return; + } + } finally { + this.dataLock.unlock(lock); + } + } + } + + @Nonnull + @Override + public ResourceType, ECS_TYPE>> registerSpatialResource(@Nonnull Supplier>> supplier) { + return this.registerResource(SpatialResource.class, () -> new SpatialResource<>(supplier.get())); + } + + @Nonnull + public Store addStore(@Nonnull ECS_TYPE externalData, @Nonnull IResourceStorage resourceStorage) { + if (this.shutdown) { + throw new IllegalStateException("Registry has been shutdown"); + } else { + return this.addStore0(externalData, resourceStorage, _store -> {}); + } + } + + @Nonnull + public Store addStore(@Nonnull ECS_TYPE externalData, @Nonnull IResourceStorage resourceStorage, Consumer> consumer) { + if (this.shutdown) { + throw new IllegalStateException("Registry has been shutdown"); + } else { + return this.addStore0(externalData, resourceStorage, consumer); + } + } + + public void removeStore(@Nonnull Store store) { + if (this.shutdown) { + throw new IllegalStateException("Registry has been shutdown"); + } else if (store.isShutdown()) { + throw new IllegalStateException("Store is already shutdown!"); + } else { + this.removeStore0(store); + } + } + + @Nonnull + public Holder newHolder() { + Holder holder = new Holder<>(this); + this.holders.add(new WeakReference<>(holder, this.holderReferenceQueue)); + return holder; + } + + @Nonnull + public Holder newHolder(@Nonnull Archetype archetype, @Nonnull Component[] components) { + Holder holder = new Holder<>(this, archetype, components); + this.holders.add(new WeakReference<>(holder, this.holderReferenceQueue)); + return holder; + } + + @Nonnull + protected Holder _internal_newEntityHolder() { + return new Holder<>(); + } + + protected ComponentRegistry.Data _internal_getData() { + return this.data; + } + + public ComponentRegistry.Data getData() { + this.assertInStoreThread(); + return this.data; + } + + @Nonnull + public BuilderCodec> getEntityCodec() { + return this.data.getEntityCodec(); + } + + public void assertInStoreThread() { + long lock = this.storeLock.readLock(); + + try { + for (int i = 0; i < this.storeSize; i++) { + if (this.stores[i].isInThread()) { + return; + } + } + + throw new AssertionError("Data can only be accessed from a store thread!"); + } finally { + this.storeLock.unlockRead(lock); + } + } + + @Nullable + public Holder deserialize(@Nonnull BsonDocument entityDocument) { + Optional version = VERSION.get(entityDocument); + if (version.isPresent()) { + return this.deserialize(entityDocument, version.get()); + } else { + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + Holder holder = this.data.getEntityCodec().decode(entityDocument, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER); + return holder; + } + } + + @Nullable + @Deprecated + public Holder deserialize(@Nonnull BsonDocument entityDocument, int version) { + ExtraInfo extraInfo = new ExtraInfo(version); + Holder holder = this.data.getEntityCodec().decode(entityDocument, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER); + return holder; + } + + public BsonDocument serialize(@Nonnull Holder holder) { + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + BsonDocument document = this.data.getEntityCodec().encode(holder, extraInfo).asDocument(); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER); + return document; + } + + public boolean hasSystem(@Nonnull ISystem system) { + return ArrayUtil.indexOf(this.systems, system, 0, this.systemSize) != -1; + } + + public > boolean hasSystemClass(@Nonnull Class systemClass) { + return this.systemClasses.containsKey(systemClass); + } + + public > boolean hasSystemType(@Nonnull SystemType systemType) { + return this.systemTypeClassToIndex.containsKey(systemType.getTypeClass()); + } + + public boolean hasSystemGroup(@Nonnull SystemGroup group) { + return ArrayUtil.indexOf(this.systemGroups, group) != -1; + } + + @Nonnull + private > ComponentType registerComponent0(@Nonnull ComponentRegistration registration) { + return this.registerComponent0(registration.typeClass(), registration.id(), registration.codec(), registration.supplier(), registration.componentType()); + } + + @Nonnull + private > ComponentType registerComponent0( + @Nonnull Class tClass, + @Nullable String id, + @Nullable BuilderCodec codec, + @Nonnull Supplier supplier, + @Nonnull ComponentType componentType + ) { + if (id != null && this.componentIdToIndex.containsKey(id)) { + throw new IllegalArgumentException("id '" + id + "' already exists!"); + } else { + int index; + if (this.componentIndexReuse.isEmpty()) { + index = this.componentSize++; + } else { + index = this.componentIndexReuse.nextSetBit(0); + this.componentIndexReuse.clear(index); + } + + if (this.componentIds.length <= index) { + int newLength = ArrayUtil.grow(index); + this.componentIds = Arrays.copyOf(this.componentIds, newLength); + this.componentCodecs = Arrays.copyOf(this.componentCodecs, newLength); + this.componentSuppliers = Arrays.copyOf(this.componentSuppliers, newLength); + this.componentTypes = Arrays.copyOf(this.componentTypes, newLength); + } + + componentType.init(this, tClass, index); + this.componentIdToIndex.put(id, index); + this.componentIds[index] = id; + this.componentCodecs[index] = codec; + this.componentSuppliers[index] = supplier; + this.componentTypes[index] = componentType; + return componentType; + } + } + + private > void unregisterComponent0(@Nonnull ComponentType componentType) { + int componentIndex = componentType.getIndex(); + if (componentIndex == this.componentSize - 1) { + int highestUsedIndex = this.componentIndexReuse.previousClearBit(componentIndex - 1); + this.componentSize = highestUsedIndex + 1; + this.componentIndexReuse.clear(this.componentSize, componentIndex); + } else { + this.componentIndexReuse.set(componentIndex); + } + + this.componentIdToIndex.removeInt(this.componentIds[componentIndex]); + this.componentIds[componentIndex] = null; + this.componentCodecs[componentIndex] = null; + this.componentSuppliers[componentIndex] = null; + this.componentTypes[componentIndex] = null; + } + + @Nonnull + private > ResourceType registerResource0(@Nonnull ResourceRegistration registration) { + return this.registerResource0(registration.typeClass(), registration.id(), registration.codec(), registration.supplier(), registration.resourceType()); + } + + @Nonnull + private > ResourceType registerResource0( + @Nonnull Class tClass, + @Nullable String id, + @Nullable BuilderCodec codec, + @Nonnull Supplier supplier, + @Nonnull ResourceType resourceType + ) { + if (id != null && this.resourceIdToIndex.containsKey(id)) { + throw new IllegalArgumentException("id '" + id + "' already exists!"); + } else { + int index; + if (this.resourceIndexReuse.isEmpty()) { + index = this.resourceSize++; + } else { + index = this.resourceIndexReuse.nextSetBit(0); + this.resourceIndexReuse.clear(index); + } + + if (this.resourceIds.length <= index) { + int newLength = ArrayUtil.grow(index); + this.resourceIds = Arrays.copyOf(this.resourceIds, newLength); + this.resourceCodecs = Arrays.copyOf(this.resourceCodecs, newLength); + this.resourceSuppliers = Arrays.copyOf(this.resourceSuppliers, newLength); + this.resourceTypes = Arrays.copyOf(this.resourceTypes, newLength); + } + + resourceType.init(this, tClass, index); + this.resourceIdToIndex.put(id, index); + this.resourceIds[index] = id; + this.resourceCodecs[index] = codec; + this.resourceSuppliers[index] = supplier; + this.resourceTypes[index] = resourceType; + return resourceType; + } + } + + private > void unregisterResource0(@Nonnull ResourceType resourceType) { + int resourceIndex = resourceType.getIndex(); + if (resourceIndex == this.resourceSize - 1) { + int highestUsedIndex = this.resourceIndexReuse.previousClearBit(resourceIndex - 1); + this.resourceSize = highestUsedIndex + 1; + this.resourceIndexReuse.clear(this.resourceSize, resourceIndex); + } else { + this.resourceIndexReuse.set(resourceIndex); + } + + this.resourceIdToIndex.removeInt(this.resourceIds[resourceIndex]); + this.resourceIds[resourceIndex] = null; + this.resourceCodecs[resourceIndex] = null; + this.resourceSuppliers[resourceIndex] = null; + this.resourceTypes[resourceIndex] = null; + } + + @Nonnull + private > SystemType registerSystemType0(@Nonnull Class systemTypeClass) { + if (this.systemTypeClassToIndex.containsKey(systemTypeClass)) { + throw new IllegalArgumentException("system type '" + systemTypeClass + "' already exists!"); + } else { + int systemTypeIndex; + if (this.systemTypeIndexReuse.isEmpty()) { + systemTypeIndex = this.systemTypeSize++; + } else { + systemTypeIndex = this.systemTypeIndexReuse.nextSetBit(0); + this.systemTypeIndexReuse.clear(systemTypeIndex); + } + + if (this.systemTypes.length <= systemTypeIndex) { + this.systemTypes = Arrays.copyOf(this.systemTypes, ArrayUtil.grow(systemTypeIndex)); + this.systemTypeToSystemIndex = Arrays.copyOf(this.systemTypeToSystemIndex, ArrayUtil.grow(systemTypeIndex)); + } + + SystemType systemType = new SystemType<>(this, systemTypeClass, systemTypeIndex); + this.systemTypeClassToIndex.put((Class>)systemTypeClass, systemTypeIndex); + this.systemTypes[systemTypeIndex] = systemType; + return systemType; + } + } + + private > void unregisterSystemType0(@Nonnull SystemType systemType) { + int systemTypeIndex = systemType.getIndex(); + if (systemTypeIndex == this.systemTypeSize - 1) { + int highestUsedIndex = this.systemTypeIndexReuse.previousClearBit(systemTypeIndex - 1); + this.systemTypeSize = highestUsedIndex + 1; + this.systemTypeIndexReuse.clear(this.systemTypeSize, systemTypeIndex); + } else { + this.systemTypeIndexReuse.set(systemTypeIndex); + } + + this.systemTypeClassToIndex.removeInt(systemType.getTypeClass()); + this.systemTypes[systemTypeIndex] = null; + this.systemTypeToSystemIndex[systemTypeIndex].clear(); + } + + @Nonnull + private EntityEventType registerEntityEventType0(@Nonnull Class eventTypeClass) { + if (this.entityEventTypeClassToIndex.containsKey(eventTypeClass)) { + throw new IllegalArgumentException("event type '" + eventTypeClass + "' already exists!"); + } else { + int systemTypeIndex; + if (this.systemTypeIndexReuse.isEmpty()) { + systemTypeIndex = this.systemTypeSize++; + } else { + systemTypeIndex = this.systemTypeIndexReuse.nextSetBit(0); + this.systemTypeIndexReuse.clear(systemTypeIndex); + } + + if (this.systemTypes.length <= systemTypeIndex) { + this.systemTypes = Arrays.copyOf(this.systemTypes, ArrayUtil.grow(systemTypeIndex)); + this.systemTypeToSystemIndex = Arrays.copyOf(this.systemTypeToSystemIndex, ArrayUtil.grow(systemTypeIndex)); + } + + EntityEventType systemType = new EntityEventType<>(this, EntityEventSystem.class, (Class)eventTypeClass, systemTypeIndex); + this.entityEventTypeClassToIndex.put((Class)eventTypeClass, systemTypeIndex); + this.systemTypes[systemTypeIndex] = systemType; + return systemType; + } + } + + private void unregisterEntityEventType0(@Nonnull EntityEventType eventType) { + int systemTypeIndex = eventType.getIndex(); + if (systemTypeIndex == this.systemTypeSize - 1) { + int highestUsedIndex = this.systemTypeIndexReuse.previousClearBit(systemTypeIndex - 1); + this.systemTypeSize = highestUsedIndex + 1; + this.systemTypeIndexReuse.clear(this.systemTypeSize, systemTypeIndex); + } else { + this.systemTypeIndexReuse.set(systemTypeIndex); + } + + this.entityEventTypeClassToIndex.removeInt(eventType.getEventClass()); + this.systemTypes[systemTypeIndex] = null; + this.systemTypeToSystemIndex[systemTypeIndex].clear(); + } + + @Nullable + public EntityEventType getEntityEventTypeForClass(Class eClass) { + int index = this.entityEventTypeClassToIndex.getInt(eClass); + return index == Integer.MIN_VALUE ? null : (EntityEventType)this.systemTypes[index]; + } + + @Nonnull + private WorldEventType registerWorldEventType0(@Nonnull Class eventTypeClass) { + if (this.worldEventTypeClassToIndex.containsKey(eventTypeClass)) { + throw new IllegalArgumentException("event type '" + eventTypeClass + "' already exists!"); + } else { + int systemTypeIndex; + if (this.systemTypeIndexReuse.isEmpty()) { + systemTypeIndex = this.systemTypeSize++; + } else { + systemTypeIndex = this.systemTypeIndexReuse.nextSetBit(0); + this.systemTypeIndexReuse.clear(systemTypeIndex); + } + + if (this.systemTypes.length <= systemTypeIndex) { + this.systemTypes = Arrays.copyOf(this.systemTypes, ArrayUtil.grow(systemTypeIndex)); + this.systemTypeToSystemIndex = Arrays.copyOf(this.systemTypeToSystemIndex, ArrayUtil.grow(systemTypeIndex)); + } + + WorldEventType systemType = new WorldEventType<>(this, WorldEventSystem.class, (Class)eventTypeClass, systemTypeIndex); + this.worldEventTypeClassToIndex.put((Class)eventTypeClass, systemTypeIndex); + this.systemTypes[systemTypeIndex] = systemType; + return systemType; + } + } + + private void unregisterWorldEventType0(@Nonnull WorldEventType eventType) { + int systemTypeIndex = eventType.getIndex(); + if (systemTypeIndex == this.systemTypeSize - 1) { + int highestUsedIndex = this.systemTypeIndexReuse.previousClearBit(systemTypeIndex - 1); + this.systemTypeSize = highestUsedIndex + 1; + this.systemTypeIndexReuse.clear(this.systemTypeSize, systemTypeIndex); + } else { + this.systemTypeIndexReuse.set(systemTypeIndex); + } + + this.worldEventTypeClassToIndex.removeInt(eventType.getEventClass()); + this.systemTypes[systemTypeIndex] = null; + this.systemTypeToSystemIndex[systemTypeIndex].clear(); + } + + @Nullable + public WorldEventType getWorldEventTypeForClass(Class eClass) { + int index = this.worldEventTypeClassToIndex.getInt(eClass); + return index == Integer.MIN_VALUE ? null : (WorldEventType)this.systemTypes[index]; + } + + @Nonnull + private SystemGroup registerSystemGroup0(@Nonnull Set> dependencies) { + int systemGroupIndex; + if (this.systemGroupIndexReuse.isEmpty()) { + systemGroupIndex = this.systemGroupSize++; + } else { + systemGroupIndex = this.systemGroupIndexReuse.nextSetBit(0); + this.systemGroupIndexReuse.clear(systemGroupIndex); + } + + if (this.systemGroups.length <= systemGroupIndex) { + this.systemGroups = Arrays.copyOf(this.systemGroups, ArrayUtil.grow(systemGroupIndex)); + } + + SystemGroup systemGroup = new SystemGroup<>(this, systemGroupIndex, dependencies); + this.systemGroups[systemGroupIndex] = systemGroup; + return systemGroup; + } + + private void unregisterSystemGroup0(@Nonnull SystemGroup systemType) { + int systemGroupIndex = systemType.getIndex(); + if (systemGroupIndex == this.systemGroupSize - 1) { + int highestUsedIndex = this.systemGroupIndexReuse.previousClearBit(systemGroupIndex - 1); + this.systemGroupSize = highestUsedIndex + 1; + this.systemGroupIndexReuse.clear(this.systemGroupSize, systemGroupIndex); + } else { + this.systemGroupIndexReuse.set(systemGroupIndex); + } + + this.systemGroups[systemGroupIndex] = null; + } + + private void registerSystem0(@Nonnull ISystem system) { + int systemIndex = this.systemSize++; + if (this.systems.length <= systemIndex) { + this.systems = Arrays.copyOf(this.systems, ArrayUtil.grow(systemIndex)); + } + + this.systems[systemIndex] = system; + this.systemClasses.put((Class>)system.getClass(), systemIndex); + system.onSystemRegistered(); + } + + private void unregisterSystem0(int systemIndex, @Nonnull ISystem system) { + int lastIndex = this.systemSize - 1; + if (systemIndex != lastIndex) { + ISystem lastSystem = this.systems[lastIndex]; + this.systems[systemIndex] = lastSystem; + this.systemClasses.put((Class>)lastSystem.getClass(), systemIndex); + } + + this.systems[lastIndex] = null; + this.systemSize = lastIndex; + Class systemClass = (Class)system.getClass(); + boolean bypassClassCheck = this.systemBypassClassCheck.getBoolean(systemClass); + if (!bypassClassCheck && !this.systemClasses.remove(systemClass, systemIndex)) { + throw new IllegalArgumentException("Failed to remove system " + systemClass.getName() + ", " + systemIndex); + } else { + system.onSystemUnregistered(); + } + } + + @Nonnull + private Store addStore0(@Nonnull ECS_TYPE externalData, @Nonnull IResourceStorage resourceStorage, Consumer> consumer) { + long lock = this.storeLock.writeLock(); + + Store var8; + try { + int storeIndex = this.storeSize++; + if (this.stores.length <= storeIndex) { + this.stores = Arrays.copyOf(this.stores, ArrayUtil.grow(storeIndex)); + } + + Store store = new Store<>(this, storeIndex, externalData, resourceStorage); + this.stores[storeIndex] = store; + consumer.accept(store); + store.onAdd(this.data); + var8 = store; + } finally { + this.storeLock.unlockWrite(lock); + } + + return var8; + } + + private void removeStore0(@Nonnull Store store) { + store.shutdown0(this.data); + + long lock; + do { + Thread.onSpinWait(); + this.doDataUpdate(); + lock = this.storeLock.tryWriteLock(); + } while (!this.storeLock.validate(lock)); + + try { + int storeIndex = store.storeIndex; + int lastIndex = this.storeSize - 1; + if (storeIndex != lastIndex) { + Store lastStore = this.stores[lastIndex]; + lastStore.storeIndex = storeIndex; + this.stores[storeIndex] = lastStore; + } + + this.stores[lastIndex] = null; + this.storeSize = lastIndex; + } finally { + this.storeLock.unlockWrite(lock); + } + } + + ComponentRegistry.Data doDataUpdate() { + return this.data; + } + + private void updateData0(@Nonnull DataChange... dataChanges) { + boolean systemChanged = false; + boolean systemTypeChanged = false; + + for (DataChange dataChange : dataChanges) { + if (dataChange instanceof SystemChange) { + systemChanged = true; + } + + if (dataChange instanceof SystemTypeChange) { + systemTypeChanged = true; + } + } + + if (systemChanged) { + if (this.sortedSystems.length < this.systems.length) { + this.sortedSystems = Arrays.copyOf(this.systems, this.systems.length); + } else { + java.lang.System.arraycopy(this.systems, 0, this.sortedSystems, 0, this.systems.length); + } + + ISystem.calculateOrder(this, this.sortedSystems, this.systemSize); + } + + if (systemChanged || systemTypeChanged) { + for (int systemTypeIndex = 0; systemTypeIndex < this.systemTypeSize; systemTypeIndex++) { + SystemType> systemType = this.systemTypes[systemTypeIndex]; + if (systemType != null) { + BitSet bitSet = this.systemTypeToSystemIndex[systemTypeIndex] = new BitSet(); + + for (int systemIndex = 0; systemIndex < this.systemSize; systemIndex++) { + if (systemType.isType(this.sortedSystems[systemIndex])) { + bitSet.set(systemIndex); + } + } + } + } + } + + long lock = this.storeLock.readLock(); + this.dataUpdateLock.writeLock().lock(); + + try { + ComponentRegistry.Data oldData = this.data; + this.data = new ComponentRegistry.Data<>(oldData.getVersion() + 1, this, dataChanges); + + for (int i = 0; i < this.storeSize; i++) { + this.stores[i].updateData(oldData, this.data); + } + + for (Reference> holderReference : this.holders) { + Holder holder = holderReference.get(); + if (holder != null) { + holder.updateData(oldData, this.data); + } + } + } finally { + this.dataUpdateLock.writeLock().unlock(); + this.storeLock.unlockRead(lock); + } + } + + @Nonnull + @Override + public String toString() { + return "ComponentRegistry{super()=" + + this.getClass() + + "@" + + this.hashCode() + + ", shutdown=" + + this.shutdown + + ", dataLock=" + + this.dataLock + + ", idToIndex=" + + this.componentIdToIndex + + ", componentIndexReuse=" + + this.componentIndexReuse + + ", componentSize=" + + this.componentSize + + ", componentIds=" + + Arrays.toString((Object[])this.componentIds) + + ", componentCodecs=" + + Arrays.toString((Object[])this.componentCodecs) + + ", componentSuppliers=" + + Arrays.toString((Object[])this.componentSuppliers) + + ", componentTypes=" + + Arrays.toString((Object[])this.componentTypes) + + ", resourceIndexReuse=" + + this.resourceIndexReuse + + ", resourceSize=" + + this.resourceSize + + ", resourceIds=" + + Arrays.toString((Object[])this.resourceIds) + + ", resourceCodecs=" + + Arrays.toString((Object[])this.resourceCodecs) + + ", resourceSuppliers=" + + Arrays.toString((Object[])this.resourceSuppliers) + + ", resourceTypes=" + + Arrays.toString((Object[])this.resourceTypes) + + ", systemSize=" + + this.systemSize + + ", systems=" + + Arrays.toString((Object[])this.systems) + + ", sortedSystems=" + + Arrays.toString((Object[])this.sortedSystems) + + ", storeLock=" + + this.storeLock + + ", storeSize=" + + this.storeSize + + ", stores=" + + Arrays.toString((Object[])this.stores) + + ", data=" + + this.data + + "}"; + } + + public > T createComponent(@Nonnull ComponentType componentType) { + long lock = this.dataLock.readLock(); + + Component var4; + try { + var4 = this.data.createComponent(componentType); + } finally { + this.dataLock.unlockRead(lock); + } + + return (T)var4; + } + + public static class Data { + private final int version; + @Nonnull + private final ComponentRegistry registry; + private final Object2IntMap componentIdToIndex; + private final int componentSize; + @Nonnull + private final String[] componentIds; + @Nonnull + private final BuilderCodec>[] componentCodecs; + @Nonnull + private final Supplier>[] componentSuppliers; + @Nonnull + private final ComponentType>[] componentTypes; + private final Object2IntMap resourceIdToIndex; + private final int resourceSize; + @Nonnull + private final String[] resourceIds; + @Nonnull + private final BuilderCodec>[] resourceCodecs; + @Nonnull + private final Supplier>[] resourceSuppliers; + @Nonnull + private final ResourceType>[] resourceTypes; + private final Object2IntMap>> systemTypeClassToIndex; + private final int systemTypeSize; + @Nonnull + private final SystemType>[] systemTypes; + @Nonnull + private final BitSet[] systemTypeToSystemIndex; + private final int systemSize; + @Nonnull + private final ISystem[] sortedSystems; + @Nonnull + private final Map>> codecMap; + @Nonnull + private final BuilderCodec> entityCodec; + @Nullable + private final DataChange[] dataChanges; + + private Data(@Nonnull ComponentRegistry registry) { + this.version = 0; + this.registry = registry; + this.componentIdToIndex = Object2IntMaps.emptyMap(); + this.componentSize = 0; + this.componentIds = ArrayUtil.EMPTY_STRING_ARRAY; + this.componentCodecs = (BuilderCodec>[])BuilderCodec.EMPTY_ARRAY; + this.componentSuppliers = ArrayUtil.emptySupplierArray(); + this.componentTypes = ComponentType.EMPTY_ARRAY; + this.resourceIdToIndex = Object2IntMaps.emptyMap(); + this.resourceSize = 0; + this.resourceIds = ArrayUtil.EMPTY_STRING_ARRAY; + this.resourceCodecs = (BuilderCodec>[])BuilderCodec.EMPTY_ARRAY; + this.resourceSuppliers = ArrayUtil.emptySupplierArray(); + this.resourceTypes = ResourceType.EMPTY_ARRAY; + this.systemTypeClassToIndex = Object2IntMaps.emptyMap(); + this.systemTypeSize = 0; + this.systemTypes = SystemType.EMPTY_ARRAY; + this.systemTypeToSystemIndex = ArrayUtil.EMPTY_BITSET_ARRAY; + this.systemSize = 0; + this.sortedSystems = ISystem.EMPTY_ARRAY; + this.codecMap = Collections.emptyMap(); + this.entityCodec = this.createCodec(); + this.dataChanges = null; + } + + private Data(int version, @Nonnull ComponentRegistry registry, DataChange... dataChanges) { + this.version = version; + this.registry = registry; + this.componentIdToIndex = new Object2IntOpenHashMap<>(registry.componentIdToIndex); + this.componentIdToIndex.defaultReturnValue(Integer.MIN_VALUE); + this.componentSize = registry.componentSize; + this.componentIds = Arrays.copyOf(registry.componentIds, this.componentSize); + this.componentCodecs = Arrays.copyOf(registry.componentCodecs, this.componentSize); + this.componentSuppliers = Arrays.copyOf(registry.componentSuppliers, this.componentSize); + this.componentTypes = Arrays.copyOf(registry.componentTypes, this.componentSize); + this.resourceIdToIndex = new Object2IntOpenHashMap<>(registry.resourceIdToIndex); + this.resourceIdToIndex.defaultReturnValue(Integer.MIN_VALUE); + this.resourceSize = registry.resourceSize; + this.resourceIds = Arrays.copyOf(registry.resourceIds, this.resourceSize); + this.resourceCodecs = Arrays.copyOf(registry.resourceCodecs, this.resourceSize); + this.resourceSuppliers = Arrays.copyOf(registry.resourceSuppliers, this.resourceSize); + this.resourceTypes = Arrays.copyOf(registry.resourceTypes, this.resourceSize); + this.systemTypeClassToIndex = new Object2IntOpenHashMap<>(registry.systemTypeClassToIndex); + this.systemTypeClassToIndex.defaultReturnValue(Integer.MIN_VALUE); + this.systemTypeSize = registry.systemTypeSize; + this.systemTypes = Arrays.copyOf(registry.systemTypes, this.systemTypeSize); + this.systemTypeToSystemIndex = Arrays.copyOf(registry.systemTypeToSystemIndex, this.systemTypeSize); + this.systemSize = registry.systemSize; + this.sortedSystems = Arrays.copyOf(registry.sortedSystems, this.systemSize); + Object2ObjectOpenHashMap>> codecMap = new Object2ObjectOpenHashMap<>(this.componentSize); + + for (int i = 0; i < this.componentSize; i++) { + if (this.componentCodecs[i] != null) { + codecMap.put(this.componentIds[i], (Codec>)this.componentCodecs[i]); + } + } + + this.codecMap = codecMap; + this.entityCodec = this.createCodec(); + this.dataChanges = dataChanges; + } + + @Nonnull + private BuilderCodec> createCodec() { + Function>, Codec>> function = componentCodec -> componentCodec != null + ? componentCodec + : TempUnknownComponent.COMPONENT_CODEC; + return BuilderCodec.builder(Holder.class, this.registry::newHolder) + .addField( + new KeyedCodec<>("Components", new MapProvidedMapCodec<>(this.codecMap, function, LinkedHashMap::new, false)), + (holder, map) -> holder.loadComponentsMap(this, map), + holder -> holder.createComponentsMap(this) + ) + .build(); + } + + public int getVersion() { + return this.version; + } + + @Nonnull + public ComponentRegistry getRegistry() { + return this.registry; + } + + @Nullable + public ComponentType getComponentType(String id) { + int index = this.componentIdToIndex.getInt(id); + return index == Integer.MIN_VALUE ? null : this.componentTypes[index]; + } + + public int getComponentSize() { + return this.componentSize; + } + + @Nullable + public String getComponentId(@Nonnull ComponentType componentType) { + return this.componentIds[componentType.getIndex()]; + } + + @Nullable + public > Codec getComponentCodec(@Nonnull ComponentType componentType) { + return (Codec)this.componentCodecs[componentType.getIndex()]; + } + + public > T createComponent(@Nonnull ComponentType componentType) { + componentType.validateRegistry(this.registry); + componentType.validate(); + return (T)this.componentSuppliers[componentType.getIndex()].get(); + } + + public ResourceType getResourceType(int index) { + return this.resourceTypes[index]; + } + + @Nullable + public ResourceType getResourceType(String id) { + int index = this.resourceIdToIndex.getInt(id); + return index == Integer.MIN_VALUE ? null : this.resourceTypes[index]; + } + + public int getResourceSize() { + return this.resourceSize; + } + + @Nullable + public String getResourceId(@Nonnull ResourceType resourceType) { + return this.resourceIds[resourceType.getIndex()]; + } + + @Nullable + public > BuilderCodec getResourceCodec(@Nonnull ResourceType resourceType) { + return (BuilderCodec)this.resourceCodecs[resourceType.getIndex()]; + } + + public > T createResource(@Nonnull ResourceType resourceType) { + resourceType.validateRegistry(this.registry); + resourceType.validate(); + return (T)this.resourceSuppliers[resourceType.getIndex()].get(); + } + + public int getSystemTypeSize() { + return this.systemTypeSize; + } + + @Nullable + public > SystemType getSystemType(Class systemTypeClass) { + int systemTypeClassToIndexInt = this.systemTypeClassToIndex.getInt(systemTypeClass); + return (SystemType)(systemTypeClassToIndexInt == Integer.MIN_VALUE ? null : this.systemTypes[systemTypeClassToIndexInt]); + } + + public SystemType> getSystemType(int systemTypeIndex) { + return this.systemTypes[systemTypeIndex]; + } + + public > BitSet getSystemIndexesForType(@Nonnull SystemType systemType) { + return this.systemTypeToSystemIndex[systemType.getIndex()]; + } + + public int getSystemSize() { + return this.systemSize; + } + + public ISystem getSystem(int systemIndex) { + return this.sortedSystems[systemIndex]; + } + + public > T getSystem(int systemIndex, SystemType systemType) { + return (T)this.sortedSystems[systemIndex]; + } + + public int indexOf(ISystem system) { + int systemIndex = -1; + + for (int i = 0; i < this.sortedSystems.length; i++) { + if (this.sortedSystems[i] == system) { + systemIndex = i; + break; + } + } + + return systemIndex; + } + + @Nonnull + public BuilderCodec> getEntityCodec() { + return this.entityCodec; + } + + public int getDataChangeCount() { + return this.dataChanges.length; + } + + public DataChange getDataChange(int index) { + return this.dataChanges[index]; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + ComponentRegistry.Data data = (ComponentRegistry.Data)o; + return this.version == data.version; + } else { + return false; + } + } + + @Override + public int hashCode() { + return this.version; + } + + @Nonnull + @Override + public String toString() { + return "Data{version=" + + this.version + + ", componentSize=" + + this.componentSize + + ", componentSuppliers=" + + Arrays.toString((Object[])this.componentSuppliers) + + ", resourceSize=" + + this.resourceSize + + ", resourceSuppliers=" + + Arrays.toString((Object[])this.resourceSuppliers) + + ", systemSize=" + + this.systemSize + + ", sortedSystems=" + + Arrays.toString((Object[])this.sortedSystems) + + ", dataChanges=" + + Arrays.toString((Object[])this.dataChanges) + + "}"; + } + + public void appendDump(@Nonnull String prefix, @Nonnull StringBuilder sb) { + sb.append(prefix).append("version=").append(this.version).append("\n"); + sb.append(prefix).append("componentSize=").append(this.componentSize).append("\n"); + sb.append(prefix).append("componentSuppliers=").append("\n"); + + for (int i = 0; i < this.componentSize; i++) { + sb.append(prefix).append("\t- ").append(i).append("\t").append(this.componentSuppliers[i]).append("\n"); + } + + sb.append(prefix).append("resourceSuppliers=").append("\n"); + + for (int i = 0; i < this.resourceSize; i++) { + sb.append(prefix).append("\t- ").append(i).append("\t").append(this.resourceSuppliers[i]).append("\n"); + } + + sb.append(prefix).append("systemSize=").append(this.systemSize).append("\n"); + sb.append(prefix).append("sortedSystems=").append("\n"); + + for (int i = 0; i < this.systemSize; i++) { + sb.append(prefix).append("\t- ").append(i).append("\t").append(this.sortedSystems[i]).append("\n"); + } + + sb.append(prefix).append("dataChanges=").append("\n"); + + for (int i = 0; i < this.dataChanges.length; i++) { + sb.append(prefix).append("\t- ").append(i).append("\t").append(this.dataChanges[i]).append("\n"); + } + } + } +} diff --git a/src/com/hypixel/hytale/component/ComponentRegistryProxy.java b/src/com/hypixel/hytale/component/ComponentRegistryProxy.java new file mode 100644 index 0000000..6e77785 --- /dev/null +++ b/src/com/hypixel/hytale/component/ComponentRegistryProxy.java @@ -0,0 +1,190 @@ +package com.hypixel.hytale.component; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.event.EntityEventType; +import com.hypixel.hytale.component.event.WorldEventType; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.spatial.SpatialStructure; +import com.hypixel.hytale.component.system.EcsEvent; +import com.hypixel.hytale.component.system.ISystem; +import com.hypixel.hytale.function.consumer.BooleanConsumer; +import java.util.List; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class ComponentRegistryProxy implements IComponentRegistry { + private final ComponentRegistry registry; + private final List unregister; + + public ComponentRegistryProxy(List registrations, ComponentRegistry registry) { + this.unregister = registrations; + this.registry = registry; + } + + public void shutdown() { + } + + @Nonnull + @Override + public > ComponentType registerComponent(@Nonnull Class tClass, @Nonnull Supplier supplier) { + return this.registerComponentType(this.registry.registerComponent(tClass, supplier)); + } + + @Nonnull + @Override + public > ComponentType registerComponent( + @Nonnull Class tClass, @Nonnull String id, @Nonnull BuilderCodec codec + ) { + return this.registerComponentType(this.registry.registerComponent(tClass, id, codec)); + } + + @Deprecated(forRemoval = true) + @Nonnull + public > ComponentType registerComponent( + @Nonnull Class tClass, @Nonnull String id, @Nonnull BuilderCodec codec, boolean skipValidation + ) { + return this.registerComponentType(this.registry.registerComponent(tClass, id, codec, skipValidation)); + } + + @Nonnull + @Override + public > ResourceType registerResource(@Nonnull Class tClass, @Nonnull Supplier supplier) { + return this.registerResourceType(this.registry.registerResource(tClass, supplier)); + } + + @Nonnull + @Override + public > ResourceType registerResource( + @Nonnull Class tClass, @Nonnull String id, @Nonnull BuilderCodec codec + ) { + return this.registerResourceType(this.registry.registerResource(tClass, id, codec)); + } + + @Nonnull + @Override + public ResourceType, ECS_TYPE>> registerSpatialResource(@Nonnull Supplier>> supplier) { + return this.registerResourceType(this.registry.registerSpatialResource(supplier)); + } + + @Nonnull + @Override + public > SystemType registerSystemType(@Nonnull Class systemTypeClass) { + return this.registerSystemType(this.registry.registerSystemType(systemTypeClass)); + } + + @Nonnull + @Override + public EntityEventType registerEntityEventType(@Nonnull Class eventTypeClass) { + return this.registerEntityEventType(this.registry.registerEntityEventType(eventTypeClass)); + } + + @Nonnull + @Override + public WorldEventType registerWorldEventType(@Nonnull Class eventTypeClass) { + return this.registerWorldEventType(this.registry.registerWorldEventType(eventTypeClass)); + } + + @Nonnull + @Override + public SystemGroup registerSystemGroup() { + return this.registerSystemGroup(this.registry.registerSystemGroup()); + } + + @Override + public void registerSystem(@Nonnull ISystem system) { + Class> systemClass = (Class>)system.getClass(); + this.registry.registerSystem(system); + this.unregister.add(shutdown -> { + if (!shutdown) { + if (this.registry.hasSystemClass(systemClass)) { + this.registry.unregisterSystem(systemClass); + } + } + }); + } + + @Deprecated(forRemoval = true) + public void registerSystem(@Nonnull ISystem system, boolean bypassClassCheck) { + Class> systemClass = (Class>)system.getClass(); + this.registry.registerSystem(system, bypassClassCheck); + this.unregister.add(shutdown -> { + if (!shutdown) { + if (this.registry.hasSystemClass(systemClass)) { + this.registry.unregisterSystem(systemClass); + } + } + }); + } + + @Nonnull + private > ComponentType registerComponentType(@Nonnull ComponentType componentType) { + this.unregister.add(shutdown -> { + if (!shutdown) { + if (componentType.isValid()) { + this.registry.unregisterComponent(componentType); + } + } + }); + return componentType; + } + + @Nonnull + private > ResourceType registerResourceType(@Nonnull ResourceType componentType) { + this.unregister.add(shutdown -> { + if (!shutdown) { + if (componentType.isValid()) { + this.registry.unregisterResource(componentType); + } + } + }); + return componentType; + } + + @Nonnull + private > SystemType registerSystemType(@Nonnull SystemType systemType) { + this.unregister.add(shutdown -> { + if (!shutdown) { + if (systemType.isValid()) { + this.registry.unregisterSystemType(systemType); + } + } + }); + return systemType; + } + + @Nonnull + private EntityEventType registerEntityEventType(@Nonnull EntityEventType eventType) { + this.unregister.add(shutdown -> { + if (!shutdown) { + if (eventType.isValid()) { + this.registry.unregisterEntityEventType(eventType); + } + } + }); + return eventType; + } + + @Nonnull + private WorldEventType registerWorldEventType(@Nonnull WorldEventType eventType) { + this.unregister.add(shutdown -> { + if (!shutdown) { + if (eventType.isValid()) { + this.registry.unregisterWorldEventType(eventType); + } + } + }); + return eventType; + } + + @Nonnull + private SystemGroup registerSystemGroup(@Nonnull SystemGroup systemGroup) { + this.unregister.add(shutdown -> { + if (!shutdown) { + if (systemGroup.isValid()) { + this.registry.unregisterSystemGroup(systemGroup); + } + } + }); + return systemGroup; + } +} diff --git a/src/com/hypixel/hytale/component/ComponentType.java b/src/com/hypixel/hytale/component/ComponentType.java new file mode 100644 index 0000000..46a8584 --- /dev/null +++ b/src/com/hypixel/hytale/component/ComponentType.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.component; + +import com.hypixel.hytale.component.query.Query; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ComponentType> implements Comparable>, Query { + @Nonnull + public static final ComponentType[] EMPTY_ARRAY = new ComponentType[0]; + private ComponentRegistry registry; + private Class tClass; + private int index; + private boolean invalid = true; + + public ComponentType() { + } + + void init(@Nonnull ComponentRegistry registry, @Nonnull Class tClass, int index) { + this.registry = registry; + this.tClass = tClass; + this.index = index; + this.invalid = false; + } + + @Nonnull + public ComponentRegistry getRegistry() { + return this.registry; + } + + @Nonnull + public Class getTypeClass() { + return this.tClass; + } + + public int getIndex() { + return this.index; + } + + void invalidate() { + this.invalid = true; + } + + boolean isValid() { + return !this.invalid; + } + + @Override + public boolean test(@Nonnull Archetype archetype) { + return archetype.contains(this); + } + + @Override + public boolean requiresComponentType(ComponentType componentType) { + return this.equals(componentType); + } + + @Override + public void validateRegistry(ComponentRegistry registry) { + if (!this.registry.equals(registry)) { + throw new IllegalArgumentException("ComponentType is for a different registry! " + this); + } + } + + @Override + public void validate() { + if (this.invalid) { + throw new IllegalStateException("ComponentType is invalid!"); + } + } + + public int compareTo(@Nonnull ComponentType o) { + return Integer.compare(this.index, o.index); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + ComponentType that = (ComponentType)o; + return this.index != that.index ? false : this.registry.equals(that.registry); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.registry.hashCode(); + return 31 * result + this.index; + } + + @Nonnull + @Override + public String toString() { + return "ComponentType{registry=" + + this.registry.getClass() + + "@" + + this.registry.hashCode() + + ", typeClass=" + + this.tClass + + ", index=" + + this.index + + "}"; + } +} diff --git a/src/com/hypixel/hytale/component/DisableProcessingAssert.java b/src/com/hypixel/hytale/component/DisableProcessingAssert.java new file mode 100644 index 0000000..218e397 --- /dev/null +++ b/src/com/hypixel/hytale/component/DisableProcessingAssert.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.component; + +@Deprecated(forRemoval = true) +public interface DisableProcessingAssert { +} diff --git a/src/com/hypixel/hytale/component/EmptyResourceStorage.java b/src/com/hypixel/hytale/component/EmptyResourceStorage.java new file mode 100644 index 0000000..143cc8f --- /dev/null +++ b/src/com/hypixel/hytale/component/EmptyResourceStorage.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.component; + +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class EmptyResourceStorage implements IResourceStorage { + private static final EmptyResourceStorage INSTANCE = new EmptyResourceStorage(); + + public EmptyResourceStorage() { + } + + public static EmptyResourceStorage get() { + return INSTANCE; + } + + @Nonnull + @Override + public , ECS_TYPE> CompletableFuture load( + @Nonnull Store store, @Nonnull ComponentRegistry.Data data, @Nonnull ResourceType resourceType + ) { + return CompletableFuture.completedFuture(data.createResource(resourceType)); + } + + @Nonnull + @Override + public , ECS_TYPE> CompletableFuture save( + @Nonnull Store store, @Nonnull ComponentRegistry.Data data, @Nonnull ResourceType resourceType, T resource + ) { + return CompletableFuture.completedFuture(null); + } + + @Nonnull + @Override + public , ECS_TYPE> CompletableFuture remove( + @Nonnull Store store, @Nonnull ComponentRegistry.Data data, @Nonnull ResourceType resourceType + ) { + return CompletableFuture.completedFuture(null); + } +} diff --git a/src/com/hypixel/hytale/component/Holder.java b/src/com/hypixel/hytale/component/Holder.java new file mode 100644 index 0000000..47b7009 --- /dev/null +++ b/src/com/hypixel/hytale/component/Holder.java @@ -0,0 +1,546 @@ +package com.hypixel.hytale.component; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.component.data.change.ComponentChange; +import com.hypixel.hytale.component.data.unknown.TempUnknownComponent; +import com.hypixel.hytale.component.data.unknown.UnknownComponents; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import java.util.concurrent.locks.StampedLock; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public class Holder { + private static final Holder[] EMPTY_ARRAY = new Holder[0]; + @Nullable + private final ComponentRegistry registry; + private final StampedLock lock = new StampedLock(); + @Nullable + private Archetype archetype; + @Nullable + private Component[] components; + private boolean ensureValidComponents = true; + + public static Holder[] emptyArray() { + return (Holder[])EMPTY_ARRAY; + } + + Holder() { + this.registry = null; + } + + Holder(@Nonnull ComponentRegistry registry) { + this.registry = registry; + this.archetype = Archetype.empty(); + this.components = Component.EMPTY_ARRAY; + } + + Holder(@Nonnull ComponentRegistry registry, @Nonnull Archetype archetype, @Nonnull Component[] components) { + this.registry = registry; + this.init(archetype, components); + } + + @Nonnull + public Component[] ensureComponentsSize(int size) { + long stamp = this.lock.writeLock(); + + Component[] var4; + try { + if (this.components != null) { + if (this.components.length < size) { + this.components = Arrays.copyOf(this.components, size); + } + + return this.components; + } + + this.components = new Component[size]; + var4 = this.components; + } finally { + this.lock.unlockWrite(stamp); + } + + return var4; + } + + public void init(@Nonnull Archetype archetype, @Nonnull Component[] components) { + archetype.validate(); + archetype.validateComponents(components, null); + long stamp = this.lock.writeLock(); + + try { + this.archetype = archetype; + this.components = components; + this.ensureValidComponents = true; + } finally { + this.lock.unlockWrite(stamp); + } + } + + public void _internal_init( + @Nonnull Archetype archetype, + @Nonnull Component[] components, + @Nonnull ComponentType> unknownComponentType + ) { + archetype.validateComponents(components, unknownComponentType); + long stamp = this.lock.writeLock(); + + try { + this.archetype = archetype; + this.components = components; + this.ensureValidComponents = false; + } finally { + this.lock.unlockWrite(stamp); + } + } + + @Nullable + public Archetype getArchetype() { + return this.archetype; + } + + public > void ensureComponent(@Nonnull ComponentType componentType) { + assert this.archetype != null; + + assert this.registry != null; + + if (this.ensureValidComponents) { + componentType.validate(); + } + + long stamp = this.lock.writeLock(); + + try { + if (!this.archetype.contains(componentType)) { + T component = this.registry.createComponent(componentType); + this.addComponent0(componentType, component); + return; + } + } finally { + this.lock.unlockWrite(stamp); + } + } + + @Nonnull + public > T ensureAndGetComponent(@Nonnull ComponentType componentType) { + this.ensureComponent(componentType); + return this.getComponent(componentType); + } + + public > void addComponent(@Nonnull ComponentType componentType, @Nonnull T component) { + assert this.archetype != null; + + long stamp = this.lock.writeLock(); + + try { + if (this.ensureValidComponents) { + componentType.validate(); + } + + if (this.archetype.contains(componentType)) { + throw new IllegalArgumentException("Entity contains component type: " + componentType); + } + + this.addComponent0(componentType, component); + } finally { + this.lock.unlockWrite(stamp); + } + } + + private > void addComponent0(@Nonnull ComponentType componentType, @Nonnull T component) { + assert this.archetype != null; + + assert this.components != null; + + this.archetype = Archetype.add(this.archetype, componentType); + int newLength = this.archetype.length(); + if (this.components.length < newLength) { + this.components = Arrays.copyOf(this.components, newLength); + } + + this.components[componentType.getIndex()] = component; + } + + public > void replaceComponent(@Nonnull ComponentType componentType, @Nonnull T component) { + assert this.archetype != null; + + assert this.components != null; + + long stamp = this.lock.writeLock(); + + try { + if (this.ensureValidComponents) { + componentType.validate(); + } + + this.archetype.validateComponentType(componentType); + this.components[componentType.getIndex()] = component; + } finally { + this.lock.unlockWrite(stamp); + } + } + + public > void putComponent(@Nonnull ComponentType componentType, @Nonnull T component) { + if (this.getComponent(componentType) != null) { + this.replaceComponent(componentType, component); + } else { + this.addComponent(componentType, component); + } + } + + @Nullable + public > T getComponent(@Nonnull ComponentType componentType) { + assert this.archetype != null; + + assert this.components != null; + + long stamp = this.lock.readLock(); + + Object var4; + try { + if (this.ensureValidComponents) { + componentType.validate(); + } + + if (this.archetype.contains(componentType)) { + return (T)this.components[componentType.getIndex()]; + } + + var4 = null; + } finally { + this.lock.unlockRead(stamp); + } + + return (T)var4; + } + + public > void removeComponent(@Nonnull ComponentType componentType) { + assert this.archetype != null; + + assert this.components != null; + + long stamp = this.lock.writeLock(); + + try { + if (this.ensureValidComponents) { + componentType.validate(); + } + + this.archetype.validateComponentType(componentType); + this.archetype = Archetype.remove(this.archetype, componentType); + this.components[componentType.getIndex()] = null; + } finally { + this.lock.unlockWrite(stamp); + } + } + + public > boolean tryRemoveComponent(@Nonnull ComponentType componentType) { + if (this.getComponent(componentType) == null) { + return false; + } else { + this.removeComponent(componentType); + return true; + } + } + + public boolean hasSerializableComponents(@Nonnull ComponentRegistry.Data data) { + assert this.archetype != null; + + return this.archetype.hasSerializableComponents(data); + } + + public void updateData(@Nonnull ComponentRegistry.Data oldData, @Nonnull ComponentRegistry.Data newData) { + assert this.archetype != null; + + assert this.components != null; + + assert this.registry != null; + + long stamp = this.lock.writeLock(); + + try { + if (this.archetype.isEmpty()) { + return; + } + + ComponentType> unknownComponentType = this.registry.getUnknownComponentType(); + + for (int i = 0; i < newData.getDataChangeCount(); i++) { + if (newData.getDataChange(i) instanceof ComponentChange> componentChange) { + ComponentType> componentType = componentChange.getComponentType(); + switch (componentChange.getType()) { + case REGISTERED: + assert this.archetype != null; + + if (!this.archetype.contains(componentType) && this.archetype.contains(unknownComponentType)) { + String componentId = newData.getComponentId(componentType); + Codec> componentCodec = newData.getComponentCodec((ComponentType>)componentType); + if (componentCodec != null) { + UnknownComponents unknownComponents = (UnknownComponents)this.components[unknownComponentType.getIndex()]; + + assert unknownComponents != null; + + Component component = unknownComponents.removeComponent(componentId, componentCodec); + if (component != null) { + this.addComponent0(componentType, component); + } + } + } + break; + case UNREGISTERED: + assert this.archetype != null; + + if (this.archetype.contains(componentType)) { + String componentId = oldData.getComponentId(componentType); + Codec> componentCodec = oldData.getComponentCodec((ComponentType>)componentType); + if (componentCodec != null) { + UnknownComponents unknownComponents; + if (this.archetype.contains(unknownComponentType)) { + unknownComponents = (UnknownComponents)this.components[unknownComponentType.getIndex()]; + + assert unknownComponents != null; + } else { + unknownComponents = new UnknownComponents<>(); + this.addComponent0(unknownComponentType, unknownComponents); + } + + Component component = this.components[componentType.getIndex()]; + unknownComponents.addComponent(componentId, component, componentCodec); + } + + this.archetype = Archetype.remove(this.archetype, componentType); + this.components[componentType.getIndex()] = null; + } + } + } + } + } finally { + this.lock.unlockWrite(stamp); + } + } + + @Nonnull + public Holder clone() { + assert this.archetype != null; + + assert this.components != null; + + assert this.registry != null; + + long stamp = this.lock.readLock(); + + Holder var9; + try { + Component[] componentsClone = new Component[this.components.length]; + + for (int i = 0; i < this.components.length; i++) { + Component component = this.components[i]; + if (component != null) { + componentsClone[i] = component.clone(); + } + } + + var9 = this.registry.newHolder(this.archetype, componentsClone); + } finally { + this.lock.unlockRead(stamp); + } + + return var9; + } + + void loadComponentsMap(@Nonnull ComponentRegistry.Data data, @Nonnull Map> map) { + assert this.components != null; + + long stamp = this.lock.writeLock(); + + try { + ComponentType[] componentTypes = new ComponentType[map.size()]; + int i = 0; + ComponentType> unknownComponentType = data.getRegistry().getUnknownComponentType(); + UnknownComponents unknownComponents = (UnknownComponents)map.remove("Unknown"); + if (unknownComponents != null) { + for (Entry e : unknownComponents.getUnknownComponents().entrySet()) { + ComponentType> type = (ComponentType>)data.getComponentType( + e.getKey() + ); + if (type != null && !map.containsKey(e.getKey())) { + Codec> codec = data.getComponentCodec((ComponentType>)type); + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + Component decodedComponent = codec.decode(e.getValue(), extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(UnknownComponents.LOGGER); + if (componentTypes.length <= i) { + componentTypes = Arrays.copyOf(componentTypes, i + 1); + } + + componentTypes[i++] = type; + int index = type.getIndex(); + if (this.components.length <= index) { + this.components = Arrays.copyOf(this.components, index + 1); + } + + this.components[index] = decodedComponent; + } + } + + if (componentTypes.length <= i) { + componentTypes = Arrays.copyOf(componentTypes, i + 1); + } + + componentTypes[i++] = unknownComponentType; + int index = unknownComponentType.getIndex(); + if (this.components.length <= index) { + this.components = Arrays.copyOf(this.components, index + 1); + } + + this.components[index] = unknownComponents; + } + + for (Entry> entry : map.entrySet()) { + Component component = entry.getValue(); + if (component instanceof TempUnknownComponent tempUnknownComponent) { + if (unknownComponents == null) { + unknownComponents = new UnknownComponents<>(); + if (componentTypes.length <= i) { + componentTypes = Arrays.copyOf(componentTypes, i + 1); + } + + componentTypes[i++] = unknownComponentType; + int index = unknownComponentType.getIndex(); + if (this.components.length <= index) { + this.components = Arrays.copyOf(this.components, index + 1); + } + + this.components[index] = unknownComponents; + } + + unknownComponents.addComponent(entry.getKey(), tempUnknownComponent); + } else { + ComponentType> componentType = (ComponentType>)data.getComponentType( + entry.getKey() + ); + if (componentTypes.length <= i) { + componentTypes = Arrays.copyOf(componentTypes, i + 1); + } + + componentTypes[i++] = componentType; + int index = componentType.getIndex(); + if (this.components.length <= index) { + this.components = Arrays.copyOf(this.components, index + 1); + } + + this.components[index] = component; + } + } + + this.archetype = Archetype.of(componentTypes.length == i ? componentTypes : Arrays.copyOf(componentTypes, i)); + } finally { + this.lock.unlockWrite(stamp); + } + } + + @Nonnull + Map> createComponentsMap(@Nonnull ComponentRegistry.Data data) { + assert this.archetype != null; + + assert this.components != null; + + long stamp = this.lock.readLock(); + + Map registry; + try { + if (!this.archetype.isEmpty()) { + ComponentRegistry registryx = data.getRegistry(); + ComponentType> unknownComponentType = registryx.getUnknownComponentType(); + Map> map = new Object2ObjectOpenHashMap<>(this.archetype.length()); + + for (int i = this.archetype.getMinIndex(); i < this.archetype.length(); i++) { + ComponentType> componentType = (ComponentType>)this.archetype + .get(i); + if (componentType != null && data.getComponentCodec(componentType) != null) { + if (componentType == unknownComponentType) { + UnknownComponents unknownComponents = (UnknownComponents)this.components[componentType.getIndex()]; + + for (Entry entry : unknownComponents.getUnknownComponents().entrySet()) { + map.putIfAbsent(entry.getKey(), new TempUnknownComponent<>(entry.getValue())); + } + } else { + map.put(data.getComponentId(componentType), this.components[componentType.getIndex()]); + } + } + } + + return map; + } + + registry = Collections.emptyMap(); + } finally { + this.lock.unlockRead(stamp); + } + + return registry; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Holder that = (Holder)o; + long stamp = this.lock.readLock(); + long thatStamp = that.lock.readLock(); + + boolean var7; + try { + if (Objects.equals(this.archetype, that.archetype)) { + return Arrays.equals((Object[])this.components, (Object[])that.components); + } + + var7 = false; + } finally { + that.lock.unlockRead(thatStamp); + this.lock.unlockRead(stamp); + } + + return var7; + } else { + return false; + } + } + + @Override + public int hashCode() { + long stamp = this.lock.readLock(); + + int var4; + try { + int result = this.archetype != null ? this.archetype.hashCode() : 0; + result = 31 * result + Arrays.hashCode((Object[])this.components); + var4 = result; + } finally { + this.lock.unlockRead(stamp); + } + + return var4; + } + + @Nonnull + @Override + public String toString() { + long stamp = this.lock.readLock(); + + String var3; + try { + var3 = "EntityHolder{archetype=" + this.archetype + ", components=" + Arrays.toString((Object[])this.components) + "}"; + } finally { + this.lock.unlockRead(stamp); + } + + return var3; + } +} diff --git a/src/com/hypixel/hytale/component/IComponentRegistry.java b/src/com/hypixel/hytale/component/IComponentRegistry.java new file mode 100644 index 0000000..6607b51 --- /dev/null +++ b/src/com/hypixel/hytale/component/IComponentRegistry.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.component; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.event.EntityEventType; +import com.hypixel.hytale.component.event.WorldEventType; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.spatial.SpatialStructure; +import com.hypixel.hytale.component.system.EcsEvent; +import com.hypixel.hytale.component.system.ISystem; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public interface IComponentRegistry { + @Nonnull + > ComponentType registerComponent(@Nonnull Class var1, @Nonnull Supplier var2); + + @Nonnull + > ComponentType registerComponent( + @Nonnull Class var1, @Nonnull String var2, @Nonnull BuilderCodec var3 + ); + + @Nonnull + > ResourceType registerResource(@Nonnull Class var1, @Nonnull Supplier var2); + + @Nonnull + > ResourceType registerResource( + @Nonnull Class var1, @Nonnull String var2, @Nonnull BuilderCodec var3 + ); + + > SystemType registerSystemType(@Nonnull Class var1); + + @Nonnull + EntityEventType registerEntityEventType(@Nonnull Class var1); + + @Nonnull + WorldEventType registerWorldEventType(@Nonnull Class var1); + + @Nonnull + SystemGroup registerSystemGroup(); + + void registerSystem(@Nonnull ISystem var1); + + ResourceType, ECS_TYPE>> registerSpatialResource(@Nonnull Supplier>> var1); +} diff --git a/src/com/hypixel/hytale/component/IResourceStorage.java b/src/com/hypixel/hytale/component/IResourceStorage.java new file mode 100644 index 0000000..aa0d152 --- /dev/null +++ b/src/com/hypixel/hytale/component/IResourceStorage.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.component; + +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public interface IResourceStorage { + @Nonnull + , ECS_TYPE> CompletableFuture load( + @Nonnull Store var1, @Nonnull ComponentRegistry.Data var2, @Nonnull ResourceType var3 + ); + + @Nonnull + , ECS_TYPE> CompletableFuture save( + @Nonnull Store var1, @Nonnull ComponentRegistry.Data var2, @Nonnull ResourceType var3, T var4 + ); + + @Nonnull + , ECS_TYPE> CompletableFuture remove( + @Nonnull Store var1, @Nonnull ComponentRegistry.Data var2, @Nonnull ResourceType var3 + ); +} diff --git a/src/com/hypixel/hytale/component/NonSerialized.java b/src/com/hypixel/hytale/component/NonSerialized.java new file mode 100644 index 0000000..cfd086e --- /dev/null +++ b/src/com/hypixel/hytale/component/NonSerialized.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.component; + +public class NonSerialized implements Component { + private static final NonSerialized INSTANCE = new NonSerialized(); + + public NonSerialized() { + } + + public static NonSerialized get() { + return (NonSerialized)INSTANCE; + } + + @Override + public Component clone() { + return get(); + } +} diff --git a/src/com/hypixel/hytale/component/NonTicking.java b/src/com/hypixel/hytale/component/NonTicking.java new file mode 100644 index 0000000..e81be47 --- /dev/null +++ b/src/com/hypixel/hytale/component/NonTicking.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.component; + +public class NonTicking implements Component { + private static final NonTicking INSTANCE = new NonTicking(); + + public NonTicking() { + } + + public static NonTicking get() { + return (NonTicking)INSTANCE; + } + + @Override + public Component clone() { + return get(); + } +} diff --git a/src/com/hypixel/hytale/component/ReadWriteQuery.java b/src/com/hypixel/hytale/component/ReadWriteQuery.java new file mode 100644 index 0000000..d4b3ad2 --- /dev/null +++ b/src/com/hypixel/hytale/component/ReadWriteQuery.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.component; + +import com.hypixel.hytale.component.query.ReadWriteArchetypeQuery; + +public class ReadWriteQuery implements ReadWriteArchetypeQuery { + private final Archetype read; + private final Archetype write; + + public ReadWriteQuery(Archetype read, Archetype write) { + this.read = read; + this.write = write; + } + + @Override + public Archetype getReadArchetype() { + return this.read; + } + + @Override + public Archetype getWriteArchetype() { + return this.write; + } +} diff --git a/src/com/hypixel/hytale/component/Ref.java b/src/com/hypixel/hytale/component/Ref.java new file mode 100644 index 0000000..1463cf8 --- /dev/null +++ b/src/com/hypixel/hytale/component/Ref.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.component; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Ref { + public static final Ref[] EMPTY_ARRAY = new Ref[0]; + @Nonnull + private final Store store; + private volatile int index; + private transient volatile int hashCode; + private volatile Throwable invalidatedBy; + + public Ref(@Nonnull Store store) { + this(store, Integer.MIN_VALUE); + } + + public Ref(@Nonnull Store store, int index) { + this.store = store; + this.index = index; + this.hashCode = this.hashCode0(); + } + + @Nonnull + public Store getStore() { + return this.store; + } + + public int getIndex() { + return this.index; + } + + void setIndex(int index) { + this.index = index; + } + + void invalidate() { + this.index = Integer.MIN_VALUE; + this.hashCode = this.hashCode0(); + this.invalidatedBy = new Throwable(); + } + + void invalidate(@Nullable Throwable invalidatedBy) { + this.index = Integer.MIN_VALUE; + this.hashCode = this.hashCode0(); + this.invalidatedBy = invalidatedBy != null ? invalidatedBy : new Throwable(); + } + + public void validate() { + if (this.index == Integer.MIN_VALUE) { + throw new IllegalStateException("Invalid entity reference!", this.invalidatedBy); + } + } + + public boolean isValid() { + return this.index != Integer.MIN_VALUE; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Ref ref = (Ref)o; + return this.index != ref.index ? false : this.store.equals(ref.store); + } else { + return false; + } + } + + public boolean equals(@Nullable Ref o) { + return this == o || o != null && this.index == o.index && this.store.equals(o.store); + } + + @Override + public int hashCode() { + return this.hashCode; + } + + public int hashCode0() { + int result = this.store.hashCode(); + return 31 * result + this.index; + } + + @Nonnull + @Override + public String toString() { + return "Ref{store=" + this.store.getClass() + "@" + this.store.hashCode() + ", index=" + this.index + "}"; + } +} diff --git a/src/com/hypixel/hytale/component/RemoveReason.java b/src/com/hypixel/hytale/component/RemoveReason.java new file mode 100644 index 0000000..f52d244 --- /dev/null +++ b/src/com/hypixel/hytale/component/RemoveReason.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.component; + +public enum RemoveReason { + REMOVE, + UNLOAD; + + private RemoveReason() { + } +} diff --git a/src/com/hypixel/hytale/component/Resource.java b/src/com/hypixel/hytale/component/Resource.java new file mode 100644 index 0000000..25a8679 --- /dev/null +++ b/src/com/hypixel/hytale/component/Resource.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.component; + +import javax.annotation.Nullable; + +public interface Resource extends Cloneable { + Resource[] EMPTY_ARRAY = new Resource[0]; + + @Nullable + Resource clone(); +} diff --git a/src/com/hypixel/hytale/component/ResourceRegistration.java b/src/com/hypixel/hytale/component/ResourceRegistration.java new file mode 100644 index 0000000..0d8f708 --- /dev/null +++ b/src/com/hypixel/hytale/component/ResourceRegistration.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.component; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public record ResourceRegistration>( + @Nonnull Class typeClass, + @Nullable String id, + @Nullable BuilderCodec codec, + @Nonnull Supplier supplier, + @Nonnull ResourceType resourceType +) { +} diff --git a/src/com/hypixel/hytale/component/ResourceType.java b/src/com/hypixel/hytale/component/ResourceType.java new file mode 100644 index 0000000..7a45a28 --- /dev/null +++ b/src/com/hypixel/hytale/component/ResourceType.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.component; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ResourceType> implements Comparable> { + @Nonnull + public static final ResourceType[] EMPTY_ARRAY = new ResourceType[0]; + private ComponentRegistry registry; + private Class tClass; + private int index; + private boolean invalid = true; + + public ResourceType() { + } + + void init(@Nonnull ComponentRegistry registry, @Nonnull Class tClass, int index) { + this.registry = registry; + this.tClass = tClass; + this.index = index; + this.invalid = false; + } + + @Nonnull + public ComponentRegistry getRegistry() { + return this.registry; + } + + @Nonnull + public Class getTypeClass() { + return this.tClass; + } + + public int getIndex() { + return this.index; + } + + public void validateRegistry(@Nonnull ComponentRegistry registry) { + if (!this.registry.equals(registry)) { + throw new IllegalArgumentException("ResourceType is for a different registry! " + this); + } + } + + public void validate() { + if (this.invalid) { + throw new IllegalStateException("ResourceType is invalid!"); + } + } + + void invalidate() { + this.invalid = true; + } + + boolean isValid() { + return !this.invalid; + } + + public int compareTo(@Nonnull ResourceType o) { + return Integer.compare(this.index, o.getIndex()); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + ResourceType that = (ResourceType)o; + return this.index != that.index ? false : this.registry.equals(that.registry); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.registry.hashCode(); + return 31 * result + this.index; + } + + @Nonnull + @Override + public String toString() { + return "ResourceType{registry=" + + this.registry.getClass() + + "@" + + this.registry.hashCode() + + ", typeClass=" + + this.tClass + + ", index=" + + this.index + + "}"; + } +} diff --git a/src/com/hypixel/hytale/component/Store.java b/src/com/hypixel/hytale/component/Store.java new file mode 100644 index 0000000..c76050b --- /dev/null +++ b/src/com/hypixel/hytale/component/Store.java @@ -0,0 +1,2159 @@ +package com.hypixel.hytale.component; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.component.data.ForEachTaskData; +import com.hypixel.hytale.component.data.change.ChangeType; +import com.hypixel.hytale.component.data.change.ComponentChange; +import com.hypixel.hytale.component.data.change.DataChange; +import com.hypixel.hytale.component.data.change.SystemChange; +import com.hypixel.hytale.component.data.unknown.UnknownComponents; +import com.hypixel.hytale.component.event.EntityEventType; +import com.hypixel.hytale.component.event.WorldEventType; +import com.hypixel.hytale.component.metric.ArchetypeChunkData; +import com.hypixel.hytale.component.metric.SystemMetricData; +import com.hypixel.hytale.component.query.ExactArchetypeQuery; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.ArchetypeChunkSystem; +import com.hypixel.hytale.component.system.EcsEvent; +import com.hypixel.hytale.component.system.EntityEventSystem; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.ISystem; +import com.hypixel.hytale.component.system.MetricSystem; +import com.hypixel.hytale.component.system.QuerySystem; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.StoreSystem; +import com.hypixel.hytale.component.system.WorldEventSystem; +import com.hypixel.hytale.component.system.data.ArchetypeDataSystem; +import com.hypixel.hytale.component.system.data.EntityDataSystem; +import com.hypixel.hytale.component.system.tick.ArchetypeTickingSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.component.system.tick.TickableSystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.component.task.ParallelRangeTask; +import com.hypixel.hytale.component.task.ParallelTask; +import com.hypixel.hytale.function.consumer.IntBiObjectConsumer; +import com.hypixel.hytale.metrics.MetricResults; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.metrics.metric.HistoricMetric; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collection; +import java.util.Deque; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Store implements ComponentAccessor { + public static final Store[] EMPTY_ARRAY = new Store[0]; + @Nonnull + public static final MetricsRegistry> METRICS_REGISTRY = new MetricsRegistry>() + .register("ArchetypeChunkCount", Store::getArchetypeChunkCount, Codec.INTEGER) + .register("EntityCount", Store::getEntityCount, Codec.INTEGER) + .register( + "Systems", + componentStore -> { + ComponentRegistry.Data data = componentStore.getRegistry().getData(); + HistoricMetric[] systemMetrics = componentStore.getSystemMetrics(); + SystemMetricData[] systemMetricData = new SystemMetricData[data.getSystemSize()]; + + for (int systemIndex = 0; systemIndex < data.getSystemSize(); systemIndex++) { + ISystem system = data.getSystem(systemIndex); + MetricResults metrics = null; + if (system instanceof MetricSystem metricSystem) { + metrics = metricSystem.toMetricResults(componentStore); + } + + systemMetricData[systemIndex] = new SystemMetricData( + system.getClass().getName(), + componentStore.getArchetypeChunkCountFor(systemIndex), + componentStore.getEntityCountFor(systemIndex), + system instanceof TickingSystem ? systemMetrics[systemIndex] : null, + metrics + ); + } + + return systemMetricData; + }, + new ArrayCodec<>(SystemMetricData.CODEC, SystemMetricData[]::new) + ) + .register("ArchetypeChunks", Store::collectArchetypeChunkData, new ArrayCodec<>(ArchetypeChunkData.CODEC, ArchetypeChunkData[]::new)); + @Nonnull + private final ComponentRegistry registry; + @Nonnull + private final ECS_TYPE externalData; + @Nonnull + private final IResourceStorage resourceStorage; + private final Deque> commandBuffers = new ArrayDeque<>(); + private final Thread thread = Thread.currentThread(); + @Nonnull + private final ParallelTask> parallelTask = new ParallelTask<>(EntityTickingSystem.SystemTaskData::new); + @Nonnull + private final ParallelTask> forEachTask = new ParallelTask<>(ForEachTaskData::new); + @Nonnull + private final ParallelTask> fetchTask = new ParallelTask<>(EntityDataSystem.SystemTaskData::new); + @Nonnull + private final Store.ProcessingCounter processing = new Store.ProcessingCounter(); + private boolean shutdown; + int storeIndex; + private int entitiesSize; + @Nonnull + private Ref[] refs = new Ref[16]; + @Nonnull + private int[] entityToArchetypeChunk = new int[16]; + @Nonnull + private int[] entityChunkIndex = new int[16]; + @Nonnull + private BitSet[] systemIndexToArchetypeChunkIndexes = ArrayUtil.EMPTY_BITSET_ARRAY; + @Nonnull + private BitSet[] archetypeChunkIndexesToSystemIndex = ArrayUtil.EMPTY_BITSET_ARRAY; + @Nonnull + private final Object2IntMap> archetypeToIndexMap = new Object2IntOpenHashMap<>(); + private int archetypeSize; + @Nonnull + private final BitSet archetypeChunkReuse = new BitSet(); + @Nonnull + private ArchetypeChunk[] archetypeChunks = ArchetypeChunk.emptyArray(); + @Nonnull + private Resource[] resources = Resource.EMPTY_ARRAY; + @Nonnull + private HistoricMetric[] systemMetrics = HistoricMetric.EMPTY_ARRAY; + @Deprecated(forRemoval = true) + private boolean disableProcessingAssert = false; + + Store(@Nonnull ComponentRegistry registry, int storeIndex, @Nonnull ECS_TYPE externalData, @Nonnull IResourceStorage resourceStorage) { + this.registry = registry; + this.storeIndex = storeIndex; + this.externalData = externalData; + this.resourceStorage = resourceStorage; + this.archetypeToIndexMap.defaultReturnValue(Integer.MIN_VALUE); + Arrays.fill(this.entityToArchetypeChunk, Integer.MIN_VALUE); + Arrays.fill(this.entityChunkIndex, Integer.MIN_VALUE); + } + + @Nonnull + CommandBuffer takeCommandBuffer() { + this.assertThread(); + if (this.commandBuffers.isEmpty()) { + return new CommandBuffer<>(this); + } else { + CommandBuffer buffer = this.commandBuffers.pop(); + + assert buffer.setThread(); + + return buffer; + } + } + + void storeCommandBuffer(@Nonnull CommandBuffer commandBuffer) { + this.assertThread(); + commandBuffer.validateEmpty(); + this.commandBuffers.add(commandBuffer); + } + + public int getStoreIndex() { + return this.storeIndex; + } + + @Nonnull + public ComponentRegistry getRegistry() { + return this.registry; + } + + @Nonnull + @Override + public ECS_TYPE getExternalData() { + return this.externalData; + } + + @Nonnull + public IResourceStorage getResourceStorage() { + return this.resourceStorage; + } + + @Nonnull + public ParallelTask> getParallelTask() { + return this.parallelTask; + } + + @Nonnull + public ParallelTask> getFetchTask() { + return this.fetchTask; + } + + @Nonnull + public HistoricMetric[] getSystemMetrics() { + this.assertThread(); + return this.systemMetrics; + } + + public boolean isShutdown() { + return this.shutdown; + } + + void onAdd(@Nonnull ComponentRegistry.Data data) { + this.updateArchetypeIndexes(data); + int resourceSize = data.getResourceSize(); + this.resources = Arrays.copyOf(this.resources, resourceSize); + + for (int index = 0; index < resourceSize; index++) { + ResourceType> resourceType = (ResourceType>)data.getResourceType(index); + if (resourceType != null) { + this.resources[index] = (Resource)this.resourceStorage.load(this, data, resourceType).join(); + } + } + + for (int systemIndex = 0; systemIndex < data.getSystemSize(); systemIndex++) { + this.updateData(data, data, new SystemChange<>(ChangeType.REGISTERED, data.getSystem(systemIndex))); + } + + this.systemMetrics = Arrays.copyOf(this.systemMetrics, data.getSystemSize()); + SystemType> tickingSystemType = this.registry.getTickableSystemType(); + BitSet systemIndexes = data.getSystemIndexesForType(tickingSystemType); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + this.systemMetrics[systemIndex] = HistoricMetric.builder(33333333L, TimeUnit.NANOSECONDS) + .addPeriod(1L, TimeUnit.SECONDS) + .addPeriod(1L, TimeUnit.MINUTES) + .addPeriod(5L, TimeUnit.MINUTES) + .build(); + } + } + + public void shutdown() { + if (this.shutdown) { + throw new IllegalStateException("Store is already shutdown!"); + } else { + this.registry.removeStore(this); + } + } + + void shutdown0(@Nonnull ComponentRegistry.Data data) { + if (this.thread.isAlive() && !this.thread.equals(Thread.currentThread())) { + throw new IllegalArgumentException("Unable to shutdown store while thread is still running!"); + } else { + for (int systemIndex = data.getSystemSize() - 1; systemIndex >= 0; systemIndex--) { + this.updateData(data, data, new SystemChange<>(ChangeType.UNREGISTERED, data.getSystem(systemIndex))); + } + + this.saveAllResources0(data).join(); + this.processing.lock(); + + try { + for (int i = 0; i < this.entitiesSize; i++) { + this.refs[i].invalidate(); + this.refs[i] = null; + } + } finally { + this.processing.unlock(); + } + + this.shutdown = true; + } + } + + @Nonnull + public CompletableFuture saveAllResources() { + return this.saveAllResources0(this.registry.getData()); + } + + @Nonnull + private CompletableFuture saveAllResources0(@Nonnull ComponentRegistry.Data data) { + int resourceSize = data.getResourceSize(); + CompletableFuture[] futures = new CompletableFuture[resourceSize]; + int idx = 0; + + for (int index = 0; index < resourceSize; index++) { + ResourceType> resourceType = (ResourceType>)data.getResourceType(index); + if (resourceType != null) { + futures[idx++] = this.resourceStorage.save(this, data, resourceType, this.resources[index]); + } + } + + return CompletableFuture.allOf(Arrays.copyOf(futures, idx)); + } + + public int getEntityCount() { + return this.entitiesSize; + } + + public int getEntityCountFor(@Nonnull Query query) { + this.assertThread(); + if (query instanceof ExactArchetypeQuery exactQuery) { + int archetypeIndex = this.archetypeToIndexMap.getInt(exactQuery.getArchetype()); + return archetypeIndex != Integer.MIN_VALUE ? this.archetypeChunks[archetypeIndex].size() : 0; + } else { + int count = 0; + + for (int archetypeIndex = 0; archetypeIndex < this.archetypeSize; archetypeIndex++) { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + if (archetypeChunk != null && query.test(archetypeChunk.getArchetype())) { + count += archetypeChunk.size(); + } + } + + return count; + } + } + + public int getEntityCountFor(int systemIndex) { + this.assertThread(); + int count = 0; + BitSet indexes = this.systemIndexToArchetypeChunkIndexes[systemIndex]; + int index = -1; + + while ((index = indexes.nextSetBit(index + 1)) >= 0) { + count += this.archetypeChunks[index].size(); + } + + return count; + } + + public int getArchetypeChunkCount() { + this.assertThread(); + return this.archetypeSize; + } + + @Nonnull + public ArchetypeChunkData[] collectArchetypeChunkData() { + this.assertThread(); + ObjectArrayList result = new ObjectArrayList<>(this.archetypeSize); + + for (int i = 0; i < this.archetypeSize; i++) { + ArchetypeChunk chunk = this.archetypeChunks[i]; + if (chunk != null) { + Archetype archetype = chunk.getArchetype(); + String[] componentTypeNames = new String[archetype.count()]; + int nameIndex = 0; + + for (int j = archetype.getMinIndex(); j < archetype.length(); j++) { + ComponentType> componentType = (ComponentType>)archetype.get(j); + if (componentType != null) { + componentTypeNames[nameIndex++] = componentType.getTypeClass().getName(); + } + } + + result.add(new ArchetypeChunkData(componentTypeNames, chunk.size())); + } + } + + return result.toArray(ArchetypeChunkData[]::new); + } + + public int getArchetypeChunkCountFor(int systemIndex) { + this.assertThread(); + return this.systemIndexToArchetypeChunkIndexes[systemIndex].cardinality(); + } + + protected void setEntityChunkIndex(@Nonnull Ref ref, int newEntityChunkIndex) { + if (ref.isValid()) { + this.entityChunkIndex[ref.getIndex()] = newEntityChunkIndex; + } + } + + @Nullable + public Ref addEntity(@Nonnull Archetype archetype, @Nonnull AddReason reason) { + this.assertThread(); + this.assertWriteProcessing(); + Component[] entityComponents; + if (archetype.isEmpty()) { + entityComponents = Component.EMPTY_ARRAY; + } else { + ComponentRegistry.Data data = this.registry._internal_getData(); + entityComponents = new Component[archetype.length()]; + + for (int i = archetype.getMinIndex(); i < archetype.length(); i++) { + ComponentType> componentType = (ComponentType>)archetype.get(i); + if (componentType != null) { + entityComponents[componentType.getIndex()] = data.createComponent((ComponentType>)componentType); + } + } + } + + return this.addEntity(this.registry.newHolder(archetype, entityComponents), new Ref<>(this), reason); + } + + @Nullable + @Override + public Ref addEntity(@Nonnull Holder holder, @Nonnull AddReason reason) { + return this.addEntity(holder, new Ref<>(this), reason); + } + + @Nullable + public Ref addEntity(@Nonnull Holder holder, @Nonnull Ref ref, @Nonnull AddReason reason) { + if (ref.isValid()) { + throw new IllegalArgumentException("EntityReference is already in use!"); + } else if (ref.getStore() != this) { + throw new IllegalArgumentException("EntityReference is not for this store!"); + } else { + this.assertThread(); + this.assertWriteProcessing(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + ComponentRegistry.Data data = this.registry._internal_getData(); + this.processing.lock(); + + try { + SystemType> systemType = this.registry.getHolderSystemType(); + BitSet systemIndexes = data.getSystemIndexesForType(systemType); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + HolderSystem system = data.getSystem(systemIndex, systemType); + if (system.test(this.registry, holder.getArchetype())) { + system.onEntityAdd(holder, reason, this); + } + } + + int entityIndex = this.entitiesSize++; + int oldLength = this.refs.length; + if (oldLength <= entityIndex) { + systemIndex = ArrayUtil.grow(entityIndex); + this.refs = Arrays.copyOf(this.refs, systemIndex); + this.entityToArchetypeChunk = Arrays.copyOf(this.entityToArchetypeChunk, systemIndex); + this.entityChunkIndex = Arrays.copyOf(this.entityChunkIndex, systemIndex); + Arrays.fill(this.entityToArchetypeChunk, oldLength, systemIndex, Integer.MIN_VALUE); + Arrays.fill(this.entityChunkIndex, oldLength, systemIndex, Integer.MIN_VALUE); + } + + this.refs[entityIndex] = ref; + ref.setIndex(entityIndex); + systemIndex = this.findOrCreateArchetypeChunk(holder.getArchetype()); + ArchetypeChunk archetypeChunk = this.archetypeChunks[systemIndex]; + int chunkEntityRef = archetypeChunk.addEntity(ref, holder); + this.entityToArchetypeChunk[entityIndex] = systemIndex; + this.entityChunkIndex[entityIndex] = chunkEntityRef; + SystemType> systemTypex = this.registry.getRefSystemType(); + BitSet systemIndexesx = data.getSystemIndexesForType(systemTypex); + BitSet entityProcessedBySystemIndexes = this.archetypeChunkIndexesToSystemIndex[systemIndex]; + commandBuffer.track(ref); + int systemIndexx = -1; + + while ((systemIndexx = systemIndexesx.nextSetBit(systemIndexx + 1)) >= 0) { + if (entityProcessedBySystemIndexes.get(systemIndexx)) { + RefSystem system = data.getSystem(systemIndexx, systemTypex); + boolean oldDisableProcessingAssert = this.disableProcessingAssert; + this.disableProcessingAssert = system instanceof DisableProcessingAssert; + system.onEntityAdded(ref, reason, this, commandBuffer); + this.disableProcessingAssert = oldDisableProcessingAssert; + if (commandBuffer.consumeWasTrackedRefRemoved()) { + break; + } + } + } + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + return ref.isValid() ? ref : null; + } + } + + @Nonnull + @Override + public Ref[] addEntities(@Nonnull Holder[] holders, @Nonnull AddReason reason) { + return this.addEntities(holders, 0, holders.length, reason); + } + + @Nonnull + public Ref[] addEntities(@Nonnull Holder[] holders, int start, int length, @Nonnull AddReason reason) { + Ref[] refs = new Ref[length]; + + for (int i = 0; i < length; i++) { + refs[i] = new Ref<>(this); + } + + this.addEntities(holders, start, refs, 0, length, reason); + return refs; + } + + public void addEntities(@Nonnull Holder[] holders, @Nonnull Ref[] refs, @Nonnull AddReason reason) { + if (holders.length != refs.length) { + throw new IllegalArgumentException("EntityHolder and EntityReference array length doesn't match!"); + } else { + this.addEntities(holders, 0, refs, 0, holders.length, reason); + } + } + + public void addEntities( + @Nonnull Holder[] holders, int holderStart, @Nonnull Ref[] refs, int refStart, int length, @Nonnull AddReason reason + ) { + int holderEnd = holderStart + length; + int refEnd = refStart + length; + if (holders.length < holderEnd) { + throw new IllegalArgumentException("EntityHolder start and length exceed array length!"); + } else if (refs.length < refEnd) { + throw new IllegalArgumentException("EntityReference start and length exceed array length!"); + } else { + for (int i = refStart; i < refEnd; i++) { + Ref ref = refs[i]; + if (ref.isValid()) { + throw new IllegalArgumentException("EntityReference is already in use!"); + } + + if (ref.getStore() != this) { + throw new IllegalArgumentException("EntityReference is not for this store!"); + } + } + + this.assertThread(); + this.assertWriteProcessing(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + ComponentRegistry.Data data = this.registry._internal_getData(); + this.processing.lock(); + + try { + SystemType> systemType = this.registry.getHolderSystemType(); + BitSet systemIndexes = data.getSystemIndexesForType(systemType); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + HolderSystem system = data.getSystem(systemIndex, systemType); + + for (int i = holderStart; i < holderEnd; i++) { + if (system.test(this.registry, holders[i].getArchetype())) { + system.onEntityAdd(holders[i], reason, this); + } + } + } + + int firstIndex = this.entitiesSize; + this.entitiesSize += length; + int oldLength = this.refs.length; + if (oldLength <= this.entitiesSize) { + systemIndex = ArrayUtil.grow(this.entitiesSize); + this.refs = Arrays.copyOf(this.refs, systemIndex); + this.entityToArchetypeChunk = Arrays.copyOf(this.entityToArchetypeChunk, systemIndex); + this.entityChunkIndex = Arrays.copyOf(this.entityChunkIndex, systemIndex); + Arrays.fill(this.entityToArchetypeChunk, oldLength, systemIndex, Integer.MIN_VALUE); + Arrays.fill(this.entityChunkIndex, oldLength, systemIndex, Integer.MIN_VALUE); + } + + System.arraycopy(refs, refStart, this.refs, firstIndex, length); + systemIndex = refStart; + + for (int entityIndex = firstIndex; systemIndex < refEnd; entityIndex++) { + refs[systemIndex].setIndex(entityIndex); + systemIndex++; + } + + systemIndex = 0; + + for (int entityIndex = firstIndex; systemIndex < length; entityIndex++) { + Ref refx = refs[refStart + systemIndex]; + Holder holder = holders[holderStart + systemIndex]; + int archetypeIndex = this.findOrCreateArchetypeChunk(holder.getArchetype()); + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + int chunkEntityRef = archetypeChunk.addEntity(refx, holder); + this.entityToArchetypeChunk[entityIndex] = archetypeIndex; + this.entityChunkIndex[entityIndex] = chunkEntityRef; + systemIndex++; + } + + SystemType> systemTypex = this.registry.getRefSystemType(); + BitSet systemIndexesx = data.getSystemIndexesForType(systemTypex); + int systemIndexx = -1; + + while ((systemIndexx = systemIndexesx.nextSetBit(systemIndexx + 1)) >= 0) { + for (int ix = refStart; ix < refEnd; ix++) { + Ref refx = refs[ix]; + int archetypeIndex = this.entityToArchetypeChunk[refx.getIndex()]; + BitSet entityProcessedBySystemIndexes = this.archetypeChunkIndexesToSystemIndex[archetypeIndex]; + if (entityProcessedBySystemIndexes.get(systemIndexx)) { + RefSystem system = data.getSystem(systemIndexx, systemTypex); + boolean oldDisableProcessingAssert = this.disableProcessingAssert; + this.disableProcessingAssert = system instanceof DisableProcessingAssert; + commandBuffer.track(refx); + system.onEntityAdded(refx, reason, this, commandBuffer); + if (commandBuffer.consumeWasTrackedRefRemoved()) { + int remaining = refEnd - ix; + if (remaining > 1) { + System.arraycopy(refs, ix + 1, refs, ix, remaining - 1); + refs[refEnd - 1] = refx; + ix--; + } + + refEnd--; + length--; + } + + this.disableProcessingAssert = oldDisableProcessingAssert; + } + } + } + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + } + } + + @Nonnull + public Holder copyEntity(@Nonnull Ref ref) { + return this.copyEntity(ref, this.registry.newHolder()); + } + + @Nonnull + public Holder copyEntity(@Nonnull Ref ref, @Nonnull Holder holder) { + this.assertThread(); + ref.validate(); + int refIndex = ref.getIndex(); + int archetypeIndex = this.entityToArchetypeChunk[refIndex]; + return this.archetypeChunks[archetypeIndex].copyEntity(this.entityChunkIndex[refIndex], holder); + } + + @Nonnull + public Holder copySerializableEntity(@Nonnull Ref ref) { + return this.copySerializableEntity(ref, this.registry.newHolder()); + } + + @Nonnull + public Holder copySerializableEntity(@Nonnull Ref ref, @Nonnull Holder holder) { + this.assertThread(); + ref.validate(); + int refIndex = ref.getIndex(); + int archetypeIndex = this.entityToArchetypeChunk[refIndex]; + return this.archetypeChunks[archetypeIndex].copySerializableEntity(this.registry.getData(), this.entityChunkIndex[refIndex], holder); + } + + @Nonnull + @Override + public Archetype getArchetype(@Nonnull Ref ref) { + this.assertThread(); + ref.validate(); + int archetypeIndex = this.entityToArchetypeChunk[ref.getIndex()]; + return this.archetypeChunks[archetypeIndex].getArchetype(); + } + + @Nonnull + protected Archetype __internal_getArchetype(@Nonnull Ref ref) { + ref.validate(); + int archetypeIndex = this.entityToArchetypeChunk[ref.getIndex()]; + return this.archetypeChunks[archetypeIndex].getArchetype(); + } + + @Nonnull + public Holder removeEntity(@Nonnull Ref ref, @Nonnull RemoveReason reason) { + return this.removeEntity(ref, this.registry.newHolder(), reason); + } + + @Nonnull + @Override + public Holder removeEntity(@Nonnull Ref ref, @Nonnull Holder holder, @Nonnull RemoveReason reason) { + return this.removeEntity(ref, holder, reason, null); + } + + @Nonnull + Holder removeEntity(@Nonnull Ref ref, @Nonnull Holder holder, @Nonnull RemoveReason reason, @Nullable Throwable proxyReason) { + this.assertThread(); + this.assertWriteProcessing(); + ref.validate(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + int entityIndex = ref.getIndex(); + int archetypeIndex = this.entityToArchetypeChunk[entityIndex]; + int chunkEntityRef = this.entityChunkIndex[entityIndex]; + ComponentRegistry.Data data = this.registry._internal_getData(); + this.processing.lock(); + + try { + SystemType> systemType = this.registry.getRefSystemType(); + BitSet systemIndexes = data.getSystemIndexesForType(systemType); + BitSet entityProcessedBySystemIndexes = this.archetypeChunkIndexesToSystemIndex[archetypeIndex]; + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + if (entityProcessedBySystemIndexes.get(systemIndex)) { + data.getSystem(systemIndex, systemType).onEntityRemove(ref, reason, this, commandBuffer); + } + } + + int lastIndex = this.entitiesSize - 1; + if (entityIndex != lastIndex) { + Ref lastEntityRef = this.refs[lastIndex]; + int lastSelfEntityRef = this.entityToArchetypeChunk[lastIndex]; + systemIndex = this.entityChunkIndex[lastIndex]; + lastEntityRef.setIndex(entityIndex); + this.refs[entityIndex] = lastEntityRef; + this.entityToArchetypeChunk[entityIndex] = lastSelfEntityRef; + this.entityChunkIndex[entityIndex] = systemIndex; + } + + this.refs[lastIndex] = null; + this.entityToArchetypeChunk[lastIndex] = Integer.MIN_VALUE; + this.entityChunkIndex[lastIndex] = Integer.MIN_VALUE; + this.entitiesSize = lastIndex; + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + archetypeChunk.removeEntity(chunkEntityRef, holder); + if (archetypeChunk.size() == 0) { + this.removeArchetypeChunk(archetypeIndex); + } + + ref.invalidate(proxyReason); + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + this.processing.lock(); + + try { + SystemType> systemType = this.registry.getHolderSystemType(); + BitSet systemIndexes = data.getSystemIndexesForType(systemType); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + HolderSystem system = data.getSystem(systemIndex, systemType); + if (system.test(this.registry, holder.getArchetype())) { + system.onEntityRemoved(holder, reason, this); + } + } + } finally { + this.processing.unlock(); + } + + return holder; + } + + @Nonnull + public Holder[] removeEntities(@Nonnull Ref[] refs, @Nonnull RemoveReason reason) { + return this.removeEntities(refs, 0, refs.length, reason); + } + + @Nonnull + public Holder[] removeEntities(@Nonnull Ref[] refs, int start, int length, @Nonnull RemoveReason reason) { + Holder[] holders = new Holder[length]; + + for (int i = 0; i < length; i++) { + holders[i] = this.registry.newHolder(); + } + + return this.removeEntities(refs, start, holders, 0, length, reason); + } + + @Nonnull + public Holder[] removeEntities(@Nonnull Ref[] refs, @Nonnull Holder[] holders, @Nonnull RemoveReason reason) { + if (refs.length != holders.length) { + throw new IllegalArgumentException("EntityHolder and EntityReference array length doesn't match!"); + } else { + return this.removeEntities(refs, 0, holders, 0, refs.length, reason); + } + } + + @Nonnull + public Holder[] removeEntities( + @Nonnull Ref[] refArr, int refStart, @Nonnull Holder[] holders, int holderStart, int length, @Nonnull RemoveReason reason + ) { + int refEnd = refStart + length; + int holderEnd = holderStart + length; + if (refArr.length < refEnd) { + throw new IllegalArgumentException("EntityReference start and length exceed array length!"); + } else if (holders.length < holderEnd) { + throw new IllegalArgumentException("EntityHolder start and length exceed array length!"); + } else { + for (int i = refStart; i < refEnd; i++) { + refArr[i].validate(); + } + + this.assertThread(); + this.assertWriteProcessing(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + ComponentRegistry.Data data = this.registry._internal_getData(); + this.processing.lock(); + + try { + SystemType> systemType = this.registry.getRefSystemType(); + BitSet systemIndexes = data.getSystemIndexesForType(systemType); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + for (int i = refStart; i < refEnd; i++) { + Ref ref = refArr[i]; + int archetypeIndex = this.entityToArchetypeChunk[ref.getIndex()]; + BitSet entityProcessedBySystemIndexes = this.archetypeChunkIndexesToSystemIndex[archetypeIndex]; + if (entityProcessedBySystemIndexes.get(systemIndex)) { + data.getSystem(systemIndex, systemType).onEntityRemove(refArr[i], reason, this, commandBuffer); + } + } + } + + for (int ix = 0; ix < length; ix++) { + int entityIndex = refArr[refStart + ix].getIndex(); + systemIndex = this.entityToArchetypeChunk[entityIndex]; + int chunkEntityRef = this.entityChunkIndex[entityIndex]; + int lastIndex = this.entitiesSize - 1; + if (entityIndex != lastIndex) { + Ref lastEntityRef = this.refs[lastIndex]; + int lastSelfEntityRef = this.entityToArchetypeChunk[lastIndex]; + int lastEntityChunkIndex = this.entityChunkIndex[lastIndex]; + lastEntityRef.setIndex(entityIndex); + this.refs[entityIndex] = lastEntityRef; + this.entityToArchetypeChunk[entityIndex] = lastSelfEntityRef; + this.entityChunkIndex[entityIndex] = lastEntityChunkIndex; + } + + this.refs[lastIndex] = null; + this.entityToArchetypeChunk[lastIndex] = Integer.MIN_VALUE; + this.entityChunkIndex[lastIndex] = Integer.MIN_VALUE; + this.entitiesSize = lastIndex; + Holder holder = holders[holderStart + ix]; + ArchetypeChunk archetypeChunk = this.archetypeChunks[systemIndex]; + archetypeChunk.removeEntity(chunkEntityRef, holder); + if (archetypeChunk.size() == 0) { + this.removeArchetypeChunk(systemIndex); + } + } + + for (int ix = refStart; ix < refEnd; ix++) { + refArr[ix].invalidate(); + } + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + this.processing.lock(); + + try { + SystemType> systemType = this.registry.getHolderSystemType(); + BitSet systemIndexes = data.getSystemIndexesForType(systemType); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + HolderSystem system = data.getSystem(systemIndex, systemType); + + for (int ix = holderStart; ix < holderEnd; ix++) { + if (system.test(this.registry, holders[ix].getArchetype())) { + system.onEntityRemoved(holders[ix], reason, this); + } + } + } + } finally { + this.processing.unlock(); + } + + return holders; + } + } + + public > void ensureComponent(@Nonnull Ref ref, @Nonnull ComponentType componentType) { + this.assertThread(); + this.assertWriteProcessing(); + componentType.validateRegistry(this.registry); + componentType.validate(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + int archetypeIndex = this.entityToArchetypeChunk[ref.getIndex()]; + this.processing.lock(); + + try { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + if (!archetypeChunk.getArchetype().contains(componentType)) { + T component = this.registry._internal_getData().createComponent(componentType); + this.datachunk_addComponent(ref, archetypeIndex, componentType, component, commandBuffer); + } + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + } + + @Nonnull + @Override + public > T ensureAndGetComponent(@Nonnull Ref ref, @Nonnull ComponentType componentType) { + this.assertThread(); + this.assertWriteProcessing(); + int refIndex = ref.getIndex(); + componentType.validateRegistry(this.registry); + componentType.validate(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + int archetypeIndex = this.entityToArchetypeChunk[refIndex]; + this.processing.lock(); + + T component; + try { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + component = archetypeChunk.getComponent(this.entityChunkIndex[refIndex], componentType); + if (component == null) { + component = this.registry._internal_getData().createComponent(componentType); + this.datachunk_addComponent(ref, archetypeIndex, componentType, component, commandBuffer); + } + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + return component; + } + + @Nonnull + @Override + public > T addComponent(@Nonnull Ref ref, @Nonnull ComponentType componentType) { + this.assertThread(); + this.assertWriteProcessing(); + T component = this.registry._internal_getData().createComponent(componentType); + this.addComponent(ref, componentType, component); + return component; + } + + @Override + public > void addComponent(@Nonnull Ref ref, @Nonnull ComponentType componentType, @Nonnull T component) { + this.assertThread(); + this.assertWriteProcessing(); + ref.validate(); + componentType.validateRegistry(this.registry); + componentType.validate(); + Objects.requireNonNull(component); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + int archetypeIndex = this.entityToArchetypeChunk[ref.getIndex()]; + this.processing.lock(); + + try { + this.datachunk_addComponent(ref, archetypeIndex, componentType, component, commandBuffer); + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + } + + public > void replaceComponent( + @Nonnull Ref ref, @Nonnull ComponentType componentType, @Nonnull T component + ) { + this.assertThread(); + this.assertWriteProcessing(); + ref.validate(); + componentType.validateRegistry(this.registry); + componentType.validate(); + Objects.requireNonNull(component); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + int archetypeIndex = this.entityToArchetypeChunk[ref.getIndex()]; + this.processing.lock(); + + try { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + int chunkEntityRef = this.entityChunkIndex[ref.getIndex()]; + T oldComponent = archetypeChunk.getComponent(chunkEntityRef, componentType); + archetypeChunk.setComponent(chunkEntityRef, componentType, component); + BitSet entityProcessedBySystemIndexes = this.archetypeChunkIndexesToSystemIndex[archetypeIndex]; + ComponentRegistry.Data data = this.registry._internal_getData(); + BitSet systemIndexes = data.getSystemIndexesForType(this.registry.getRefChangeSystemType()); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + if (entityProcessedBySystemIndexes.get(systemIndex)) { + RefChangeSystem system = (RefChangeSystem)data.getSystem(systemIndex); + if (system.componentType().getIndex() == componentType.getIndex()) { + system.onComponentSet(ref, oldComponent, component, this, commandBuffer); + } + } + } + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + } + + @Override + public > void putComponent(@Nonnull Ref ref, @Nonnull ComponentType componentType, @Nonnull T component) { + this.assertThread(); + this.assertWriteProcessing(); + ref.validate(); + componentType.validateRegistry(this.registry); + componentType.validate(); + Objects.requireNonNull(component); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + int archetypeIndex = this.entityToArchetypeChunk[ref.getIndex()]; + this.processing.lock(); + + try { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + if (archetypeChunk.getArchetype().contains(componentType)) { + int chunkEntityRef = this.entityChunkIndex[ref.getIndex()]; + T oldComponent = archetypeChunk.getComponent(chunkEntityRef, componentType); + archetypeChunk.setComponent(chunkEntityRef, componentType, component); + BitSet entityProcessedBySystemIndexes = this.archetypeChunkIndexesToSystemIndex[archetypeIndex]; + ComponentRegistry.Data data = this.registry._internal_getData(); + BitSet systemIndexes = data.getSystemIndexesForType(this.registry.getRefChangeSystemType()); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + if (entityProcessedBySystemIndexes.get(systemIndex)) { + RefChangeSystem system = (RefChangeSystem)data.getSystem(systemIndex); + if (system.componentType().getIndex() == componentType.getIndex()) { + system.onComponentSet(ref, oldComponent, component, this, commandBuffer); + } + } + } + } else { + this.datachunk_addComponent(ref, archetypeIndex, componentType, component, commandBuffer); + } + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + } + + @Override + public > T getComponent(@Nonnull Ref ref, @Nonnull ComponentType componentType) { + this.assertThread(); + return this.__internal_getComponent(ref, componentType); + } + + @Nullable + protected > T __internal_getComponent(@Nonnull Ref ref, @Nonnull ComponentType componentType) { + ref.validate(); + componentType.validateRegistry(this.registry); + componentType.validate(); + int archetypeIndex = this.entityToArchetypeChunk[ref.getIndex()]; + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + return archetypeChunk.getComponent(this.entityChunkIndex[ref.getIndex()], componentType); + } + + @Override + public > void removeComponent(@Nonnull Ref ref, @Nonnull ComponentType componentType) { + this.assertThread(); + this.assertWriteProcessing(); + ref.validate(); + componentType.validateRegistry(this.registry); + componentType.validate(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + int entityIndex = ref.getIndex(); + int fromArchetypeIndex = this.entityToArchetypeChunk[entityIndex]; + this.processing.lock(); + + try { + ArchetypeChunk fromArchetypeChunk = this.archetypeChunks[fromArchetypeIndex]; + Holder holder = this.registry._internal_newEntityHolder(); + fromArchetypeChunk.removeEntity(this.entityChunkIndex[entityIndex], holder); + T component = holder.getComponent(componentType); + + assert component != null; + + holder.removeComponent(componentType); + int toArchetypeIndex = this.findOrCreateArchetypeChunk(holder.getArchetype()); + ArchetypeChunk toArchetypeChunk = this.archetypeChunks[toArchetypeIndex]; + int chunkEntityRef = toArchetypeChunk.addEntity(ref, holder); + this.entityToArchetypeChunk[entityIndex] = toArchetypeIndex; + this.entityChunkIndex[entityIndex] = chunkEntityRef; + BitSet entityProcessedBySystemIndexes = this.archetypeChunkIndexesToSystemIndex[fromArchetypeIndex]; + ComponentRegistry.Data data = this.registry._internal_getData(); + BitSet systemIndexes = data.getSystemIndexesForType(this.registry.getRefChangeSystemType()); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + if (entityProcessedBySystemIndexes.get(systemIndex)) { + RefChangeSystem system = (RefChangeSystem)data.getSystem(systemIndex); + if (system.componentType().getIndex() == componentType.getIndex()) { + system.onComponentRemoved(ref, component, this, commandBuffer); + } + } + } + + if (fromArchetypeChunk.size() == 0) { + this.removeArchetypeChunk(fromArchetypeIndex); + } + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + } + + @Override + public > void tryRemoveComponent(@Nonnull Ref ref, @Nonnull ComponentType componentType) { + this.removeComponentIfExists(ref, componentType); + } + + public > boolean removeComponentIfExists(@Nonnull Ref ref, @Nonnull ComponentType componentType) { + this.assertThread(); + this.assertWriteProcessing(); + ref.validate(); + componentType.validateRegistry(this.registry); + componentType.validate(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + int entityIndex = ref.getIndex(); + int fromArchetypeIndex = this.entityToArchetypeChunk[entityIndex]; + this.processing.lock(); + + boolean result; + try { + ArchetypeChunk fromArchetypeChunk = this.archetypeChunks[fromArchetypeIndex]; + if (!fromArchetypeChunk.getArchetype().contains(componentType)) { + result = false; + } else { + Holder holder = this.registry._internal_newEntityHolder(); + fromArchetypeChunk.removeEntity(this.entityChunkIndex[entityIndex], holder); + T component = holder.getComponent(componentType); + + assert component != null; + + holder.removeComponent(componentType); + int toArchetypeIndex = this.findOrCreateArchetypeChunk(holder.getArchetype()); + ArchetypeChunk toArchetypeChunk = this.archetypeChunks[toArchetypeIndex]; + int chunkEntityRef = toArchetypeChunk.addEntity(ref, holder); + this.entityToArchetypeChunk[entityIndex] = toArchetypeIndex; + this.entityChunkIndex[entityIndex] = chunkEntityRef; + BitSet entityProcessedBySystemIndexes = this.archetypeChunkIndexesToSystemIndex[fromArchetypeIndex]; + ComponentRegistry.Data data = this.registry._internal_getData(); + BitSet systemIndexes = data.getSystemIndexesForType(this.registry.getRefChangeSystemType()); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + if (entityProcessedBySystemIndexes.get(systemIndex)) { + RefChangeSystem system = (RefChangeSystem)data.getSystem(systemIndex); + if (system.componentType().getIndex() == componentType.getIndex()) { + system.onComponentRemoved(ref, component, this, commandBuffer); + } + } + } + + if (fromArchetypeChunk.size() == 0) { + this.removeArchetypeChunk(fromArchetypeIndex); + } + + result = true; + } + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + return result; + } + + public > void replaceResource(@Nonnull ResourceType resourceType, @Nonnull T resource) { + this.assertThread(); + resourceType.validateRegistry(this.registry); + Objects.requireNonNull(resource); + this.resources[resourceType.getIndex()] = resource; + } + + @Nonnull + @Override + public > T getResource(@Nonnull ResourceType resourceType) { + resourceType.validateRegistry(this.registry); + return (T)this.resources[resourceType.getIndex()]; + } + + @Nonnull + protected > T __internal_getResource(@Nonnull ResourceType resourceType) { + resourceType.validateRegistry(this.registry); + return (T)this.resources[resourceType.getIndex()]; + } + + public void forEachChunk(@Nonnull BiConsumer, CommandBuffer> consumer) { + if (this.shutdown) { + throw new IllegalStateException("Store is shutdown!"); + } else { + this.assertThread(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + this.processing.lock(); + + try { + for (int archetypeIndex = 0; archetypeIndex < this.archetypeSize; archetypeIndex++) { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + if (archetypeChunk != null) { + consumer.accept(archetypeChunk, commandBuffer); + } + } + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + } + } + + public boolean forEachChunk(@Nonnull BiPredicate, CommandBuffer> predicate) { + if (this.shutdown) { + throw new IllegalStateException("Store is shutdown!"); + } else { + this.assertThread(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + boolean result = false; + this.processing.lock(); + + try { + for (int archetypeIndex = 0; archetypeIndex < this.archetypeSize; archetypeIndex++) { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + if (archetypeChunk != null && predicate.test(archetypeChunk, commandBuffer)) { + result = true; + break; + } + } + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + return result; + } + } + + public void forEachChunk(Query query, @Nonnull BiConsumer, CommandBuffer> consumer) { + if (this.shutdown) { + throw new IllegalStateException("Store is shutdown!"); + } else { + this.assertThread(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + this.processing.lock(); + + try { + if (query instanceof ExactArchetypeQuery exactQuery) { + int archetypeIndex = this.archetypeToIndexMap.getInt(exactQuery.getArchetype()); + if (archetypeIndex != Integer.MIN_VALUE) { + consumer.accept(this.archetypeChunks[archetypeIndex], commandBuffer); + } + } else { + for (int archetypeIndex = 0; archetypeIndex < this.archetypeSize; archetypeIndex++) { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + if (archetypeChunk != null && query.test(archetypeChunk.getArchetype())) { + consumer.accept(this.archetypeChunks[archetypeIndex], commandBuffer); + } + } + } + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + } + } + + public boolean forEachChunk(Query query, @Nonnull BiPredicate, CommandBuffer> predicate) { + if (this.shutdown) { + throw new IllegalStateException("Store is shutdown!"); + } else { + this.assertThread(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + boolean result = false; + this.processing.lock(); + + try { + if (query instanceof ExactArchetypeQuery exactQuery) { + int archetypeIndex = this.archetypeToIndexMap.getInt(exactQuery.getArchetype()); + if (archetypeIndex != Integer.MIN_VALUE) { + result = predicate.test(this.archetypeChunks[archetypeIndex], commandBuffer); + } + } else { + for (int archetypeIndex = 0; archetypeIndex < this.archetypeSize; archetypeIndex++) { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + if (archetypeChunk != null + && query.test(archetypeChunk.getArchetype()) + && predicate.test(this.archetypeChunks[archetypeIndex], commandBuffer)) { + result = true; + break; + } + } + } + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + return result; + } + } + + public void forEachChunk(int systemIndex, @Nonnull BiConsumer, CommandBuffer> consumer) { + if (this.shutdown) { + throw new IllegalStateException("Store is shutdown!"); + } else { + this.assertThread(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + this.processing.lock(); + + try { + BitSet indexes = this.systemIndexToArchetypeChunkIndexes[systemIndex]; + int index = -1; + + while ((index = indexes.nextSetBit(index + 1)) >= 0) { + consumer.accept(this.archetypeChunks[index], commandBuffer); + } + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + } + } + + public boolean forEachChunk(int systemIndex, @Nonnull BiPredicate, CommandBuffer> predicate) { + if (this.shutdown) { + throw new IllegalStateException("Store is shutdown!"); + } else { + this.assertThread(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + boolean result = false; + this.processing.lock(); + + try { + BitSet indexes = this.systemIndexToArchetypeChunkIndexes[systemIndex]; + int index = -1; + + while ((index = indexes.nextSetBit(index + 1)) >= 0) { + if (predicate.test(this.archetypeChunks[index], commandBuffer)) { + result = true; + break; + } + } + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + return result; + } + } + + public void forEachEntityParallel(IntBiObjectConsumer, CommandBuffer> consumer) { + if (this.shutdown) { + throw new IllegalStateException("Store is shutdown!"); + } else { + this.assertThread(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + this.forEachTask.init(); + this.processing.lock(); + + try { + for (int archetypeIndex = 0; archetypeIndex < this.archetypeSize; archetypeIndex++) { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + if (archetypeChunk != null) { + int size = archetypeChunk.size(); + if (size != 0) { + ParallelRangeTask> systemTask = this.forEachTask.appendTask(); + systemTask.init(0, size); + int i = 0; + + for (int systemTaskSize = systemTask.size(); i < systemTaskSize; i++) { + systemTask.get(i).init(consumer, archetypeChunk, commandBuffer.fork()); + } + } + } + } + + ForEachTaskData.invokeParallelTask(this.forEachTask, commandBuffer); + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + } + } + + public void forEachEntityParallel(Query query, IntBiObjectConsumer, CommandBuffer> consumer) { + if (this.shutdown) { + throw new IllegalStateException("Store is shutdown!"); + } else { + this.assertThread(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + this.forEachTask.init(); + this.processing.lock(); + + try { + if (query instanceof ExactArchetypeQuery exactQuery) { + int archetypeIndex = this.archetypeToIndexMap.getInt(exactQuery.getArchetype()); + if (archetypeIndex != Integer.MIN_VALUE) { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + int archetypeChunkSize = archetypeChunk.size(); + if (archetypeChunkSize != 0) { + ParallelRangeTask> systemTask = this.forEachTask.appendTask(); + systemTask.init(0, archetypeChunkSize); + int i = 0; + + for (int systemSize = systemTask.size(); i < systemSize; i++) { + systemTask.get(i).init(consumer, archetypeChunk, commandBuffer.fork()); + } + } + } + } else { + for (int archetypeIndex = 0; archetypeIndex < this.archetypeSize; archetypeIndex++) { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + if (archetypeChunk != null && query.test(archetypeChunk.getArchetype())) { + int archetypeChunkSize = archetypeChunk.size(); + if (archetypeChunkSize != 0) { + ParallelRangeTask> systemTask = this.forEachTask.appendTask(); + systemTask.init(0, archetypeChunkSize); + int i = 0; + + for (int systemTaskSize = systemTask.size(); i < systemTaskSize; i++) { + systemTask.get(i).init(consumer, archetypeChunk, commandBuffer.fork()); + } + } + } + } + } + + ForEachTaskData.invokeParallelTask(this.forEachTask, commandBuffer); + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + } + } + + public , Q, R> void fetch(@Nonnull SystemType systemType, Q query, @Nonnull List results) { + if (this.shutdown) { + throw new IllegalStateException("Store is shutdown!"); + } else { + this.assertThread(); + ComponentRegistry.Data data = this.registry._internal_getData(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + this.fetchTask.init(); + BitSet systemIndexes = data.getSystemIndexesForType(systemType); + this.processing.lock(); + + try { + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + T system = (T)data.getSystem(systemIndex, systemType); + BitSet indexes = this.systemIndexToArchetypeChunkIndexes[systemIndex]; + int index = -1; + + while ((index = indexes.nextSetBit(index + 1)) >= 0) { + system.fetch(this.archetypeChunks[index], this, commandBuffer, query, results); + } + } + + EntityDataSystem.SystemTaskData.invokeParallelTask(this.fetchTask, commandBuffer, results); + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + } + } + + public , Q, R> void fetch( + @Nonnull Collection> refs, @Nonnull SystemType systemType, Q query, @Nonnull List results + ) { + if (this.shutdown) { + throw new IllegalStateException("Store is shutdown!"); + } else { + this.assertThread(); + ComponentRegistry.Data data = this.registry._internal_getData(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + this.fetchTask.init(); + BitSet systemIndexes = data.getSystemIndexesForType(systemType); + this.processing.lock(); + + try { + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + T system = (T)data.getSystem(systemIndex, systemType); + + for (Ref ref : refs) { + int entityIndex = ref.getIndex(); + int archetypeIndex = this.entityToArchetypeChunk[entityIndex]; + BitSet entityProcessedBySystemIndexes = this.archetypeChunkIndexesToSystemIndex[archetypeIndex]; + if (entityProcessedBySystemIndexes.get(systemIndex)) { + int index = this.entityChunkIndex[entityIndex]; + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + system.fetch(index, archetypeChunk, this, commandBuffer, query, results); + } + } + } + + EntityDataSystem.SystemTaskData.invokeParallelTask(this.fetchTask, commandBuffer, results); + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + } + } + + @Override + public void invoke(@Nonnull Ref ref, @Nonnull Event param) { + EntityEventType eventType = this.registry.getEntityEventTypeForClass(param.getClass()); + if (eventType != null) { + ((Store)this).invoke(eventType, ref, param); + } + } + + @Override + public void invoke(@Nonnull EntityEventType systemType, @Nonnull Ref ref, @Nonnull Event param) { + if (this.shutdown) { + throw new IllegalStateException("Store is shutdown!"); + } else { + this.assertThread(); + ComponentRegistry.Data data = this.registry._internal_getData(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + commandBuffer.track(ref); + BitSet systemIndexes = data.getSystemIndexesForType(systemType); + this.processing.lock(); + + try { + int entityIndex = ref.getIndex(); + int archetypeIndex = this.entityToArchetypeChunk[entityIndex]; + BitSet entityProcessedBySystemIndexes = this.archetypeChunkIndexesToSystemIndex[archetypeIndex]; + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + int index = this.entityChunkIndex[entityIndex]; + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + systemIndex = entityProcessedBySystemIndexes.nextSetBit(systemIndex); + if (systemIndex < 0) { + break; + } + + if (systemIndexes.get(systemIndex)) { + EntityEventSystem system = data.getSystem(systemIndex, systemType); + system.handleInternal(index, archetypeChunk, this, commandBuffer, param); + if (commandBuffer.consumeWasTrackedRefRemoved()) { + break; + } + } + } + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + } + } + + @Override + public void invoke(@Nonnull Event param) { + WorldEventType eventType = this.registry.getWorldEventTypeForClass(param.getClass()); + if (eventType != null) { + ((Store)this).invoke(eventType, param); + } + } + + @Override + public void invoke(@Nonnull WorldEventType systemType, @Nonnull Event param) { + if (this.shutdown) { + throw new IllegalStateException("Store is shutdown!"); + } else { + this.assertThread(); + ComponentRegistry.Data data = this.registry._internal_getData(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + BitSet systemIndexes = data.getSystemIndexesForType(systemType); + this.processing.lock(); + + try { + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + WorldEventSystem system = data.getSystem(systemIndex, systemType); + system.handleInternal(this, commandBuffer, param); + } + } finally { + this.processing.unlock(); + } + + commandBuffer.consume(); + } + } + + protected void internal_invoke(CommandBuffer sourceCommandBuffer, Ref ref, Event param) { + EntityEventType eventType = this.registry.getEntityEventTypeForClass(param.getClass()); + if (eventType != null) { + ((Store)this).internal_invoke(sourceCommandBuffer, eventType, ref, param); + } + } + + protected void internal_invoke( + CommandBuffer sourceCommandBuffer, @Nonnull EntityEventType systemType, Ref ref, Event param + ) { + ComponentRegistry.Data data = this.registry._internal_getData(); + CommandBuffer commandBuffer = sourceCommandBuffer.fork(); + commandBuffer.track(ref); + BitSet systemIndexes = data.getSystemIndexesForType(systemType); + int entityIndex = ref.getIndex(); + int archetypeIndex = this.entityToArchetypeChunk[entityIndex]; + BitSet entityProcessedBySystemIndexes = this.archetypeChunkIndexesToSystemIndex[archetypeIndex]; + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + int index = this.entityChunkIndex[entityIndex]; + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + systemIndex = entityProcessedBySystemIndexes.nextSetBit(systemIndex); + if (systemIndex < 0) { + break; + } + + if (systemIndexes.get(systemIndex)) { + EntityEventSystem system = data.getSystem(systemIndex, systemType); + system.handleInternal(index, archetypeChunk, this, commandBuffer, param); + if (commandBuffer.consumeWasTrackedRefRemoved()) { + break; + } + } + } + + commandBuffer.mergeParallel(sourceCommandBuffer); + } + + protected void internal_invoke(CommandBuffer sourceCommandBuffer, Event param) { + WorldEventType eventType = this.registry.getWorldEventTypeForClass(param.getClass()); + if (eventType != null) { + ((Store)this).internal_invoke(sourceCommandBuffer, eventType, param); + } + } + + protected void internal_invoke( + CommandBuffer sourceCommandBuffer, @Nonnull WorldEventType systemType, Event param + ) { + ComponentRegistry.Data data = this.registry._internal_getData(); + BitSet systemIndexes = data.getSystemIndexesForType(systemType); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + WorldEventSystem system = data.getSystem(systemIndex, systemType); + system.handleInternal(this, sourceCommandBuffer, param); + } + } + + public void tick(float dt) { + this.tickInternal(dt, this.registry.getTickingSystemType()); + } + + public void pausedTick(float dt) { + if (this.shutdown) { + throw new IllegalStateException("Store is shutdown!"); + } else { + this.assertThread(); + this.tickInternal(dt, this.registry.getRunWhenPausedSystemType()); + } + } + + private > void tickInternal(float dt, SystemType tickingSystemType) { + if (this.shutdown) { + throw new IllegalStateException("Store is shutdown!"); + } else { + this.assertThread(); + this.registry.getDataUpdateLock().readLock().lock(); + + try { + ComponentRegistry.Data data = this.registry.doDataUpdate(); + BitSet systemIndexes = data.getSystemIndexesForType(tickingSystemType); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + Tickable tickingSystem = (Tickable)data.getSystem(systemIndex, tickingSystemType); + long start = System.nanoTime(); + tickingSystem.tick(dt, systemIndex, this); + long end = System.nanoTime(); + this.systemMetrics[systemIndex].add(end, end - start); + } + } finally { + this.registry.getDataUpdateLock().readLock().unlock(); + } + } + } + + public void tick(ArchetypeTickingSystem system, float dt, int systemIndex) { + if (this.shutdown) { + throw new IllegalStateException("Store is shutdown!"); + } else { + this.assertThread(); + CommandBuffer commandBuffer = this.takeCommandBuffer(); + this.parallelTask.init(); + boolean oldDisableProcessingAssert = this.disableProcessingAssert; + this.disableProcessingAssert = system instanceof DisableProcessingAssert; + this.processing.lock(); + + try { + BitSet indexes = this.systemIndexToArchetypeChunkIndexes[systemIndex]; + int index = -1; + + while ((index = indexes.nextSetBit(index + 1)) >= 0) { + system.tick(dt, this.archetypeChunks[index], this, commandBuffer); + } + + EntityTickingSystem.SystemTaskData.invokeParallelTask(this.parallelTask, commandBuffer); + } finally { + this.processing.unlock(); + this.disableProcessingAssert = oldDisableProcessingAssert; + } + + commandBuffer.consume(); + } + } + + void updateData(@Nonnull ComponentRegistry.Data oldData, @Nonnull ComponentRegistry.Data data) { + if (this.shutdown) { + throw new IllegalStateException("Store is shutdown!"); + } else { + int resourceSize = data.getResourceSize(); + this.resources = Arrays.copyOf(this.resources, resourceSize); + + for (int index = 0; index < this.resources.length; index++) { + ResourceType> resourceType = (ResourceType>)data.getResourceType( + index + ); + if (this.resources[index] == null && resourceType != null) { + this.resources[index] = (Resource)this.resourceStorage.load(this, data, resourceType).join(); + } else if (this.resources[index] != null && resourceType == null) { + this.resources[index] = null; + } + } + + boolean systemChanged = false; + + for (int i = 0; i < data.getDataChangeCount(); i++) { + DataChange dataChange = data.getDataChange(i); + systemChanged |= dataChange instanceof SystemChange; + this.updateData(oldData, data, dataChange); + } + + HistoricMetric[] oldSystemMetrics = this.systemMetrics; + this.systemMetrics = new HistoricMetric[data.getSystemSize()]; + SystemType> tickingSystemType = this.registry.getTickableSystemType(); + BitSet systemIndexes = data.getSystemIndexesForType(tickingSystemType); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + ISystem system = data.getSystem(systemIndex); + int oldSystemIndex = oldData.indexOf(system); + if (oldSystemIndex >= 0) { + this.systemMetrics[systemIndex] = oldSystemMetrics[oldSystemIndex]; + } else { + this.systemMetrics[systemIndex] = HistoricMetric.builder(33333333L, TimeUnit.NANOSECONDS) + .addPeriod(1L, TimeUnit.SECONDS) + .addPeriod(1L, TimeUnit.MINUTES) + .addPeriod(5L, TimeUnit.MINUTES) + .build(); + } + } + + if (systemChanged) { + this.updateArchetypeIndexes(data); + } + } + } + + private void updateData(@Nonnull ComponentRegistry.Data oldData, @Nonnull ComponentRegistry.Data newData, DataChange dataChange) { + this.processing.lock(); + + try { + this.updateData0(oldData, newData, dataChange); + } finally { + this.processing.unlock(); + } + + if (dataChange instanceof SystemChange systemChange) { + ISystem system = systemChange.getSystem(); + switch (systemChange.getType()) { + case REGISTERED: + if (system instanceof StoreSystem) { + ((StoreSystem)system).onSystemAddedToStore(this); + } + break; + case UNREGISTERED: + if (system instanceof StoreSystem) { + ((StoreSystem)system).onSystemRemovedFromStore(this); + } + } + } + } + + private void updateData0(@Nonnull ComponentRegistry.Data oldData, @Nonnull ComponentRegistry.Data newData, DataChange dataChange) { + if (dataChange instanceof ComponentChange> componentChange) { + ComponentType> componentType = componentChange.getComponentType(); + ComponentType> unknownComponentType = this.registry.getUnknownComponentType(); + switch (componentChange.getType()) { + case REGISTERED: + String componentId = newData.getComponentId(componentType); + Codec> componentCodec = newData.getComponentCodec((ComponentType>)componentType); + if (componentCodec != null) { + Holder tempInternalEntityHolder = this.registry._internal_newEntityHolder(); + int oldArchetypeSize = this.archetypeSize; + + for (int archetypeIndexx = 0; archetypeIndexx < oldArchetypeSize; archetypeIndexx++) { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndexx]; + if (archetypeChunk != null) { + Archetype archetype = archetypeChunk.getArchetype(); + if (!archetype.contains(componentType) && archetype.contains(unknownComponentType)) { + Archetype newArchetype = Archetype.add(archetype, componentType); + int toArchetypeIndex = this.findOrCreateArchetypeChunk(newArchetype); + ArchetypeChunk toArchetypeChunk = this.archetypeChunks[toArchetypeIndex]; + archetypeChunk.transferSomeTo(tempInternalEntityHolder, toArchetypeChunk, index -> { + UnknownComponents unknownComponents = archetypeChunk.getComponent(index, unknownComponentType); + + assert unknownComponents != null; + + return unknownComponents.contains(componentId); + }, entity -> { + UnknownComponents unknownComponents = entity.getComponent(unknownComponentType); + + assert unknownComponents != null; + + Component component = unknownComponents.removeComponent(componentId, componentCodec); + entity.addComponent(componentType, component); + }, (newChunkEntityRef, ref) -> { + this.entityToArchetypeChunk[ref.getIndex()] = toArchetypeIndex; + this.entityChunkIndex[ref.getIndex()] = newChunkEntityRef; + }); + if (archetypeChunk.size() == 0) { + this.archetypeToIndexMap.removeInt(this.archetypeChunks[archetypeIndexx].getArchetype()); + this.archetypeChunks[archetypeIndexx] = null; + + for (int systemIndex = 0; systemIndex < oldData.getSystemSize(); systemIndex++) { + this.systemIndexToArchetypeChunkIndexes[systemIndex].clear(archetypeIndexx); + } + + this.archetypeChunkIndexesToSystemIndex[archetypeIndexx].clear(); + this.archetypeChunkReuse.set(archetypeIndexx); + } + + if (toArchetypeChunk.size() == 0) { + this.archetypeToIndexMap.removeInt(this.archetypeChunks[toArchetypeIndex].getArchetype()); + this.archetypeChunks[toArchetypeIndex] = null; + + for (int systemIndex = 0; systemIndex < oldData.getSystemSize(); systemIndex++) { + this.systemIndexToArchetypeChunkIndexes[systemIndex].clear(toArchetypeIndex); + } + + this.archetypeChunkIndexesToSystemIndex[toArchetypeIndex].clear(); + this.archetypeChunkReuse.set(toArchetypeIndex); + } + } + } + } + } + break; + case UNREGISTERED: + Holder tempInternalEntityHolder = this.registry._internal_newEntityHolder(); + String componentId = oldData.getComponentId(componentType); + Codec> componentCodec = oldData.getComponentCodec((ComponentType>)componentType); + int oldArchetypeSize = this.archetypeSize; + + for (int archetypeIndex = 0; archetypeIndex < oldArchetypeSize; archetypeIndex++) { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + if (archetypeChunk != null) { + Archetype archetype = archetypeChunk.getArchetype(); + if (archetype.contains(componentType)) { + this.archetypeToIndexMap.removeInt(this.archetypeChunks[archetypeIndex].getArchetype()); + this.archetypeChunks[archetypeIndex] = null; + + for (int systemIndex = 0; systemIndex < oldData.getSystemSize(); systemIndex++) { + this.systemIndexToArchetypeChunkIndexes[systemIndex].clear(archetypeIndex); + } + + this.archetypeChunkIndexesToSystemIndex[archetypeIndex].clear(); + this.archetypeChunkReuse.set(archetypeIndex); + Archetype newArchetype = Archetype.remove(archetype, componentType); + if (componentCodec != null && !newArchetype.contains(unknownComponentType)) { + newArchetype = Archetype.add(newArchetype, unknownComponentType); + } + + int toArchetypeIndex = this.findOrCreateArchetypeChunk(newArchetype); + ArchetypeChunk toArchetypeChunk = this.archetypeChunks[toArchetypeIndex]; + archetypeChunk.transferTo(tempInternalEntityHolder, toArchetypeChunk, entity -> { + if (componentCodec != null) { + UnknownComponents unknownComponents; + if (entity.getArchetype().contains(unknownComponentType)) { + unknownComponents = entity.getComponent(unknownComponentType); + + assert unknownComponents != null; + } else { + unknownComponents = new UnknownComponents<>(); + entity.addComponent(unknownComponentType, unknownComponents); + } + + Component component = entity.getComponent((ComponentType>)componentType); + unknownComponents.addComponent(componentId, component, componentCodec); + } + + entity.removeComponent(componentType); + }, (newChunkEntityRef, ref) -> { + this.entityToArchetypeChunk[ref.getIndex()] = toArchetypeIndex; + this.entityChunkIndex[ref.getIndex()] = newChunkEntityRef; + }); + } + } + } + + int highestUsedIndex = this.archetypeChunkReuse.previousClearBit(oldArchetypeSize - 1); + this.archetypeSize = highestUsedIndex + 1; + this.archetypeChunkReuse.clear(this.archetypeSize, oldArchetypeSize); + } + } else if (dataChange instanceof SystemChange systemChange) { + ISystem system = systemChange.getSystem(); + switch (systemChange.getType()) { + case REGISTERED: + if (system instanceof ArchetypeChunkSystem archetypeChunkSystem) { + for (int archetypeIndexxxx = 0; archetypeIndexxxx < this.archetypeSize; archetypeIndexxxx++) { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndexxxx]; + if (archetypeChunk != null && archetypeChunkSystem.test(this.registry, archetypeChunk.getArchetype())) { + archetypeChunkSystem.onSystemAddedToArchetypeChunk(archetypeChunk); + } + } + } + break; + case UNREGISTERED: + if (system instanceof ArchetypeChunkSystem archetypeChunkSystem) { + for (int archetypeIndexxx = 0; archetypeIndexxx < this.archetypeSize; archetypeIndexxx++) { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndexxx]; + if (archetypeChunk != null && archetypeChunkSystem.test(this.registry, archetypeChunk.getArchetype())) { + archetypeChunkSystem.onSystemRemovedFromArchetypeChunk(archetypeChunk); + } + } + } + } + } + } + + private void updateArchetypeIndexes(@Nonnull ComponentRegistry.Data data) { + int systemSize = data.getSystemSize(); + int oldLength = this.systemIndexToArchetypeChunkIndexes.length; + if (oldLength < systemSize) { + this.systemIndexToArchetypeChunkIndexes = Arrays.copyOf(this.systemIndexToArchetypeChunkIndexes, systemSize); + + for (int i = oldLength; i < systemSize; i++) { + this.systemIndexToArchetypeChunkIndexes[i] = new BitSet(this.archetypeSize); + } + } + + for (int systemIndex = 0; systemIndex < oldLength; systemIndex++) { + this.systemIndexToArchetypeChunkIndexes[systemIndex].clear(); + } + + for (int archetypeIndex = 0; archetypeIndex < this.archetypeSize; archetypeIndex++) { + this.archetypeChunkIndexesToSystemIndex[archetypeIndex].clear(); + } + + SystemType> entityQuerySystemType = this.registry.getQuerySystemType(); + BitSet systemIndexes = data.getSystemIndexesForType(entityQuerySystemType); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + QuerySystem system = data.getSystem(systemIndex, entityQuerySystemType); + BitSet archetypeChunkIndexes = this.systemIndexToArchetypeChunkIndexes[systemIndex]; + + for (int archetypeIndex = 0; archetypeIndex < this.archetypeSize; archetypeIndex++) { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + if (archetypeChunk != null && system.test(this.registry, archetypeChunk.getArchetype())) { + archetypeChunkIndexes.set(archetypeIndex); + this.archetypeChunkIndexesToSystemIndex[archetypeIndex].set(systemIndex); + } + } + } + } + + public void assertWriteProcessing() { + if (this.processing.isHeld() && !this.disableProcessingAssert) { + throw new IllegalStateException("Store is currently processing! Ensure you aren't calling a store method from a system."); + } + } + + @Deprecated + public boolean isProcessing() { + return this.processing.isHeld(); + } + + public void assertThread() { + Thread currentThread = Thread.currentThread(); + if (!currentThread.equals(this.thread) && this.thread.isAlive()) { + throw new IllegalStateException("Assert not in thread! " + this.thread + " but was in " + currentThread); + } + } + + public boolean isInThread() { + return Thread.currentThread().equals(this.thread); + } + + public boolean isAliveInDifferentThread() { + return this.thread.isAlive() && !Thread.currentThread().equals(this.thread); + } + + @Nonnull + @Override + public String toString() { + return "Store{super()=" + + this.getClass() + + "@" + + this.hashCode() + + ", registry=" + + this.registry.getClass() + + "@" + + this.registry.hashCode() + + ", shutdown=" + + this.shutdown + + ", storeIndex=" + + this.storeIndex + + ", systemIndexToArchetypeChunkIndexes=" + + Arrays.toString((Object[])this.systemIndexToArchetypeChunkIndexes) + + ", archetypeSize=" + + this.archetypeSize + + ", archetypeChunks=" + + Arrays.toString((Object[])this.archetypeChunks) + + "}"; + } + + private > void datachunk_addComponent( + @Nonnull Ref ref, + int fromArchetypeIndex, + @Nonnull ComponentType componentType, + @Nonnull T component, + @Nonnull CommandBuffer commandBuffer + ) { + int entityIndex = ref.getIndex(); + ArchetypeChunk fromArchetypeChunk = this.archetypeChunks[fromArchetypeIndex]; + int oldChunkEntityRef = this.entityChunkIndex[entityIndex]; + Holder holder = this.registry._internal_newEntityHolder(); + fromArchetypeChunk.removeEntity(oldChunkEntityRef, holder); + holder.addComponent(componentType, component); + int toArchetypeIndex = this.findOrCreateArchetypeChunk(holder.getArchetype()); + ArchetypeChunk toArchetypeChunk = this.archetypeChunks[toArchetypeIndex]; + int chunkEntityRef = toArchetypeChunk.addEntity(ref, holder); + this.entityToArchetypeChunk[entityIndex] = toArchetypeIndex; + this.entityChunkIndex[entityIndex] = chunkEntityRef; + BitSet entityProcessedBySystemIndexes = this.archetypeChunkIndexesToSystemIndex[toArchetypeIndex]; + ComponentRegistry.Data data = this.registry._internal_getData(); + BitSet systemIndexes = data.getSystemIndexesForType(this.registry.getRefChangeSystemType()); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + if (entityProcessedBySystemIndexes.get(systemIndex)) { + RefChangeSystem system = (RefChangeSystem)data.getSystem(systemIndex); + if (system.componentType().getIndex() == componentType.getIndex()) { + system.onComponentAdded(ref, component, this, commandBuffer); + } + } + } + + if (fromArchetypeChunk.size() == 0) { + this.removeArchetypeChunk(fromArchetypeIndex); + } + } + + private int findOrCreateArchetypeChunk(@Nonnull Archetype archetype) { + int archetypeIndex = this.archetypeToIndexMap.getInt(archetype); + if (archetypeIndex != Integer.MIN_VALUE) { + return archetypeIndex; + } else { + ComponentRegistry.Data data = this.registry._internal_getData(); + if (this.archetypeChunkReuse.isEmpty()) { + archetypeIndex = this.archetypeSize++; + } else { + archetypeIndex = this.archetypeChunkReuse.nextSetBit(0); + this.archetypeChunkReuse.clear(archetypeIndex); + } + + int oldLength = this.archetypeChunks.length; + if (oldLength <= archetypeIndex) { + int newLength = ArrayUtil.grow(archetypeIndex); + this.archetypeChunks = Arrays.copyOf(this.archetypeChunks, newLength); + this.archetypeChunkIndexesToSystemIndex = Arrays.copyOf(this.archetypeChunkIndexesToSystemIndex, newLength); + int systemSize = data.getSystemSize(); + + for (int i = oldLength; i < newLength; i++) { + this.archetypeChunkIndexesToSystemIndex[i] = new BitSet(systemSize); + } + } + + ArchetypeChunk archetypeChunk = new ArchetypeChunk<>(this, archetype); + this.archetypeChunks[archetypeIndex] = archetypeChunk; + this.archetypeToIndexMap.put(archetype, archetypeIndex); + BitSet archetypeChunkToSystemIndex = this.archetypeChunkIndexesToSystemIndex[archetypeIndex]; + SystemType> entityQuerySystemType = this.registry.getQuerySystemType(); + BitSet systemIndexes = data.getSystemIndexesForType(entityQuerySystemType); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + QuerySystem system = data.getSystem(systemIndex, entityQuerySystemType); + if (system.test(this.registry, archetype)) { + this.systemIndexToArchetypeChunkIndexes[systemIndex].set(archetypeIndex); + archetypeChunkToSystemIndex.set(systemIndex); + if (system instanceof ArchetypeChunkSystem) { + ((ArchetypeChunkSystem)system).onSystemAddedToArchetypeChunk(archetypeChunk); + } + } + } + + return archetypeIndex; + } + } + + private void removeArchetypeChunk(int archetypeIndex) { + ArchetypeChunk archetypeChunk = this.archetypeChunks[archetypeIndex]; + Archetype archetype = archetypeChunk.getArchetype(); + this.archetypeToIndexMap.removeInt(archetype); + this.archetypeChunks[archetypeIndex] = null; + this.archetypeChunkIndexesToSystemIndex[archetypeIndex].clear(); + ComponentRegistry.Data data = this.registry._internal_getData(); + BitSet systemIndexes = data.getSystemIndexesForType(this.registry.getQuerySystemType()); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + this.systemIndexToArchetypeChunkIndexes[systemIndex].clear(archetypeIndex); + if (data.getSystem(systemIndex) instanceof ArchetypeChunkSystem archetypeChunkSystem && archetypeChunkSystem.test(this.registry, archetype)) { + archetypeChunkSystem.onSystemRemovedFromArchetypeChunk(archetypeChunk); + } + } + + if (archetypeIndex == this.archetypeSize - 1) { + int highestUsedIndex = this.archetypeChunkReuse.previousClearBit(archetypeIndex - 1); + this.archetypeSize = highestUsedIndex + 1; + this.archetypeChunkReuse.clear(this.archetypeSize, archetypeIndex); + } else { + this.archetypeChunkReuse.set(archetypeIndex); + } + } + + private static class ProcessingCounter implements Lock { + private int count = 0; + + private ProcessingCounter() { + } + + public boolean isHeld() { + return this.count > 0; + } + + @Override + public void lock() { + this.count++; + } + + @Override + public void lockInterruptibly() { + throw new UnsupportedOperationException("lockInterruptibly() is not supported"); + } + + @Override + public boolean tryLock() { + throw new UnsupportedOperationException("tryLock() is not supported"); + } + + @Override + public boolean tryLock(long time, @Nonnull TimeUnit unit) { + throw new UnsupportedOperationException("tryLock() is not supported"); + } + + @Override + public void unlock() { + this.count--; + } + + @Nonnull + @Override + public Condition newCondition() { + throw new UnsupportedOperationException("Conditions are not supported"); + } + } +} diff --git a/src/com/hypixel/hytale/component/SystemGroup.java b/src/com/hypixel/hytale/component/SystemGroup.java new file mode 100644 index 0000000..d23aff1 --- /dev/null +++ b/src/com/hypixel/hytale/component/SystemGroup.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.component; + +import com.hypixel.hytale.component.dependency.Dependency; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SystemGroup implements Comparable> { + @Nonnull + private final ComponentRegistry registry; + private final int index; + @Nonnull + private final Set> dependencies; + private boolean invalidated; + + SystemGroup(@Nonnull ComponentRegistry registry, int index, @Nonnull Set> dependencies) { + this.registry = registry; + this.index = index; + this.dependencies = dependencies; + } + + @Nonnull + public ComponentRegistry getRegistry() { + return this.registry; + } + + @Nonnull + public Set> getDependencies() { + return this.dependencies; + } + + public int getIndex() { + return this.index; + } + + public void validateRegistry(@Nonnull ComponentRegistry registry) { + if (!this.registry.equals(registry)) { + throw new IllegalArgumentException("SystemGroup is for a different registry! " + this); + } + } + + public void validate() { + if (this.invalidated) { + throw new IllegalStateException("SystemGroup is invalid!"); + } + } + + void invalidate() { + this.invalidated = true; + } + + boolean isValid() { + return !this.invalidated; + } + + public int compareTo(@Nonnull SystemGroup o) { + return Integer.compare(this.index, o.getIndex()); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + SystemGroup that = (SystemGroup)o; + return this.index != that.index ? false : this.registry.equals(that.registry); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.registry.hashCode(); + return 31 * result + this.index; + } + + @Nonnull + @Override + public String toString() { + return "SystemGroup{registry=" + this.registry.getClass() + "@" + this.registry.hashCode() + ", index=" + this.index + "}"; + } +} diff --git a/src/com/hypixel/hytale/component/SystemType.java b/src/com/hypixel/hytale/component/SystemType.java new file mode 100644 index 0000000..117d1e4 --- /dev/null +++ b/src/com/hypixel/hytale/component/SystemType.java @@ -0,0 +1,87 @@ +package com.hypixel.hytale.component; + +import com.hypixel.hytale.component.system.ISystem; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SystemType> implements Comparable> { + @Nonnull + public static final SystemType[] EMPTY_ARRAY = new SystemType[0]; + @Nonnull + private final ComponentRegistry registry; + @Nonnull + private final Class tClass; + private final int index; + private boolean invalidated; + + protected SystemType(@Nonnull ComponentRegistry registry, @Nonnull Class tClass, int index) { + this.registry = registry; + this.tClass = tClass; + this.index = index; + } + + @Nonnull + public ComponentRegistry getRegistry() { + return this.registry; + } + + public Class getTypeClass() { + return this.tClass; + } + + public boolean isType(@Nonnull ISystem system) { + return this.tClass.isAssignableFrom(system.getClass()); + } + + public int getIndex() { + return this.index; + } + + public void validateRegistry(@Nonnull ComponentRegistry registry) { + if (!this.registry.equals(registry)) { + throw new IllegalArgumentException("SystemType is for a different registry! " + this); + } + } + + public void validate() { + if (this.invalidated) { + throw new IllegalStateException("SystemType is invalid!"); + } + } + + protected void invalidate() { + this.invalidated = true; + } + + protected boolean isValid() { + return !this.invalidated; + } + + public int compareTo(@Nonnull SystemType o) { + return Integer.compare(this.index, o.getIndex()); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + SystemType that = (SystemType)o; + return this.index != that.index ? false : this.registry.equals(that.registry); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.registry.hashCode(); + return 31 * result + this.index; + } + + @Nonnull + @Override + public String toString() { + return "SystemType{registry=" + this.registry.getClass() + "@" + this.registry.hashCode() + ", typeClass=" + this.tClass + ", index=" + this.index + "}"; + } +} diff --git a/src/com/hypixel/hytale/component/WeakComponentReference.java b/src/com/hypixel/hytale/component/WeakComponentReference.java new file mode 100644 index 0000000..08d6d52 --- /dev/null +++ b/src/com/hypixel/hytale/component/WeakComponentReference.java @@ -0,0 +1,89 @@ +package com.hypixel.hytale.component; + +import java.lang.ref.WeakReference; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WeakComponentReference> { + @Nonnull + private final Store store; + @Nonnull + private final ComponentType type; + @Nullable + private Ref ref; + private WeakReference reference; + + WeakComponentReference(@Nonnull Store store, @Nonnull ComponentType type, @Nonnull Ref ref, @Nonnull T data) { + this.store = store; + this.type = type; + this.ref = ref; + this.reference = new WeakReference<>(data); + } + + @Nullable + public T get() { + T data = this.reference.get(); + if (data != null) { + return data; + } else if (this.ref == null) { + return null; + } else { + data = this.store.getComponent(this.ref, this.type); + if (data != null) { + this.reference = new WeakReference<>(data); + } + + return data; + } + } + + @Nonnull + public Store getStore() { + return this.store; + } + + @Nonnull + public ComponentType getType() { + return this.type; + } + + @Nullable + public Ref getEntityReference() { + return this.ref; + } + + void invalidate() { + this.ref = null; + this.reference.clear(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + WeakComponentReference that = (WeakComponentReference)o; + if (!this.store.equals(that.store)) { + return false; + } else { + return !this.type.equals(that.type) ? false : Objects.equals(this.ref, that.ref); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.store.hashCode(); + result = 31 * result + this.type.hashCode(); + return 31 * result + (this.ref != null ? this.ref.hashCode() : 0); + } + + @Nonnull + @Override + public String toString() { + return "WeakComponentReference{store=" + this.store + ", type=" + this.type + ", entity=" + this.ref + ", reference=" + this.reference + "}"; + } +} diff --git a/src/com/hypixel/hytale/component/data/ForEachTaskData.java b/src/com/hypixel/hytale/component/data/ForEachTaskData.java new file mode 100644 index 0000000..c00a667 --- /dev/null +++ b/src/com/hypixel/hytale/component/data/ForEachTaskData.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.component.data; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.task.ParallelRangeTask; +import com.hypixel.hytale.component.task.ParallelTask; +import com.hypixel.hytale.function.consumer.IntBiObjectConsumer; +import java.util.function.IntConsumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ForEachTaskData implements IntConsumer { + @Nullable + private IntBiObjectConsumer, CommandBuffer> consumer; + @Nullable + private ArchetypeChunk archetypeChunk; + @Nullable + private CommandBuffer commandBuffer; + + public ForEachTaskData() { + } + + public void init( + IntBiObjectConsumer, CommandBuffer> consumer, + ArchetypeChunk archetypeChunk, + CommandBuffer commandBuffer + ) { + this.consumer = consumer; + this.archetypeChunk = archetypeChunk; + this.commandBuffer = commandBuffer; + } + + @Override + public void accept(int index) { + assert this.commandBuffer.setThread(); + + this.consumer.accept(index, this.archetypeChunk, this.commandBuffer); + } + + public void clear() { + this.consumer = null; + this.archetypeChunk = null; + this.commandBuffer = null; + } + + public static void invokeParallelTask( + @Nonnull ParallelTask> parallelTask, @Nonnull CommandBuffer commandBuffer + ) { + int parallelTaskSize = parallelTask.size(); + if (parallelTaskSize > 0) { + parallelTask.doInvoke(); + + for (int x = 0; x < parallelTaskSize; x++) { + ParallelRangeTask> systemTask = parallelTask.get(x); + int i = 0; + + for (int systemTaskSize = systemTask.size(); i < systemTaskSize; i++) { + ForEachTaskData data = systemTask.get(i); + data.commandBuffer.mergeParallel(commandBuffer); + data.clear(); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/component/data/change/ChangeType.java b/src/com/hypixel/hytale/component/data/change/ChangeType.java new file mode 100644 index 0000000..8d67941 --- /dev/null +++ b/src/com/hypixel/hytale/component/data/change/ChangeType.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.component.data.change; + +public enum ChangeType { + REGISTERED, + UNREGISTERED; + + private ChangeType() { + } +} diff --git a/src/com/hypixel/hytale/component/data/change/ComponentChange.java b/src/com/hypixel/hytale/component/data/change/ComponentChange.java new file mode 100644 index 0000000..0cd5912 --- /dev/null +++ b/src/com/hypixel/hytale/component/data/change/ComponentChange.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.component.data.change; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import javax.annotation.Nonnull; + +public class ComponentChange> implements DataChange { + private final ChangeType type; + private final ComponentType componentType; + + public ComponentChange(ChangeType type, ComponentType componentType) { + this.type = type; + this.componentType = componentType; + } + + public ChangeType getType() { + return this.type; + } + + public ComponentType getComponentType() { + return this.componentType; + } + + @Nonnull + @Override + public String toString() { + return "ComponentChange{type=" + this.type + ", componentType=" + this.componentType + "}"; + } +} diff --git a/src/com/hypixel/hytale/component/data/change/DataChange.java b/src/com/hypixel/hytale/component/data/change/DataChange.java new file mode 100644 index 0000000..7207425 --- /dev/null +++ b/src/com/hypixel/hytale/component/data/change/DataChange.java @@ -0,0 +1,4 @@ +package com.hypixel.hytale.component.data.change; + +public interface DataChange { +} diff --git a/src/com/hypixel/hytale/component/data/change/ResourceChange.java b/src/com/hypixel/hytale/component/data/change/ResourceChange.java new file mode 100644 index 0000000..cabbeb9 --- /dev/null +++ b/src/com/hypixel/hytale/component/data/change/ResourceChange.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.component.data.change; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import javax.annotation.Nonnull; + +public class ResourceChange> implements DataChange { + private final ChangeType type; + private final ResourceType resourceType; + + public ResourceChange(ChangeType type, ResourceType resourceType) { + this.type = type; + this.resourceType = resourceType; + } + + public ChangeType getType() { + return this.type; + } + + public ResourceType getResourceType() { + return this.resourceType; + } + + @Nonnull + @Override + public String toString() { + return "ResourceChange{type=" + this.type + ", resourceChange=" + this.resourceType + "}"; + } +} diff --git a/src/com/hypixel/hytale/component/data/change/SystemChange.java b/src/com/hypixel/hytale/component/data/change/SystemChange.java new file mode 100644 index 0000000..2dd6a68 --- /dev/null +++ b/src/com/hypixel/hytale/component/data/change/SystemChange.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.component.data.change; + +import com.hypixel.hytale.component.system.ISystem; +import javax.annotation.Nonnull; + +public class SystemChange implements DataChange { + private final ChangeType type; + private final ISystem system; + + public SystemChange(ChangeType type, ISystem system) { + this.type = type; + this.system = system; + } + + public ChangeType getType() { + return this.type; + } + + public ISystem getSystem() { + return this.system; + } + + @Nonnull + @Override + public String toString() { + return "SystemChange{type=" + this.type + ", system=" + this.system + "}"; + } +} diff --git a/src/com/hypixel/hytale/component/data/change/SystemGroupChange.java b/src/com/hypixel/hytale/component/data/change/SystemGroupChange.java new file mode 100644 index 0000000..2ae2cea --- /dev/null +++ b/src/com/hypixel/hytale/component/data/change/SystemGroupChange.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.component.data.change; + +import com.hypixel.hytale.component.SystemGroup; +import javax.annotation.Nonnull; + +public class SystemGroupChange implements DataChange { + private final ChangeType type; + private final SystemGroup systemGroup; + + public SystemGroupChange(ChangeType type, SystemGroup systemGroup) { + this.type = type; + this.systemGroup = systemGroup; + } + + public ChangeType getType() { + return this.type; + } + + public SystemGroup getSystemGroup() { + return this.systemGroup; + } + + @Nonnull + @Override + public String toString() { + return "SystemGroupChange{type=" + this.type + ", systemGroup=" + this.systemGroup + "}"; + } +} diff --git a/src/com/hypixel/hytale/component/data/change/SystemTypeChange.java b/src/com/hypixel/hytale/component/data/change/SystemTypeChange.java new file mode 100644 index 0000000..f986627 --- /dev/null +++ b/src/com/hypixel/hytale/component/data/change/SystemTypeChange.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.component.data.change; + +import com.hypixel.hytale.component.SystemType; +import com.hypixel.hytale.component.system.ISystem; +import javax.annotation.Nonnull; + +public class SystemTypeChange> implements DataChange { + private final ChangeType type; + private final SystemType systemType; + + public SystemTypeChange(ChangeType type, SystemType systemType) { + this.type = type; + this.systemType = systemType; + } + + public ChangeType getType() { + return this.type; + } + + public SystemType getSystemType() { + return this.systemType; + } + + @Nonnull + @Override + public String toString() { + return "SystemTypeChange{type=" + this.type + ", systemType=" + this.systemType + "}"; + } +} diff --git a/src/com/hypixel/hytale/component/data/unknown/TempUnknownComponent.java b/src/com/hypixel/hytale/component/data/unknown/TempUnknownComponent.java new file mode 100644 index 0000000..0880dd3 --- /dev/null +++ b/src/com/hypixel/hytale/component/data/unknown/TempUnknownComponent.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.component.data.unknown; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.function.FunctionCodec; +import com.hypixel.hytale.component.Component; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; + +public class TempUnknownComponent implements Component { + public static final Codec COMPONENT_CODEC = new FunctionCodec<>( + Codec.BSON_DOCUMENT, TempUnknownComponent::new, component -> ((TempUnknownComponent)component).document + ); + private BsonDocument document; + + public TempUnknownComponent(BsonDocument document) { + this.document = document; + } + + public BsonDocument getDocument() { + return this.document; + } + + @Nonnull + @Override + public Component clone() { + return new TempUnknownComponent<>(this.document.clone()); + } +} diff --git a/src/com/hypixel/hytale/component/data/unknown/UnknownComponents.java b/src/com/hypixel/hytale/component/data/unknown/UnknownComponents.java new file mode 100644 index 0000000..e6530e5 --- /dev/null +++ b/src/com/hypixel/hytale/component/data/unknown/UnknownComponents.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.component.data.unknown; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.logger.HytaleLogger; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class UnknownComponents implements Component { + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final String ID = "Unknown"; + public static final BuilderCodec CODEC = BuilderCodec.builder(UnknownComponents.class, UnknownComponents::new) + .addField( + new KeyedCodec<>("Components", new MapCodec<>(Codec.BSON_DOCUMENT, Object2ObjectOpenHashMap::new, false)), + (o, map) -> o.unknownComponents = map, + o -> o.unknownComponents + ) + .build(); + private Map unknownComponents; + + public UnknownComponents() { + this.unknownComponents = new Object2ObjectOpenHashMap<>(); + } + + public UnknownComponents(Map unknownComponents) { + this.unknownComponents = unknownComponents; + } + + public void addComponent(String componentId, Component component, @Nonnull Codec> codec) { + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + BsonValue bsonValue = codec.encode(component, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER); + this.unknownComponents.put(componentId, bsonValue.asDocument()); + } + + public void addComponent(String componentId, @Nonnull TempUnknownComponent component) { + this.unknownComponents.put(componentId, component.getDocument()); + } + + public boolean contains(String componentId) { + return this.unknownComponents.containsKey(componentId); + } + + @Nullable + public > T removeComponent(String componentId, @Nonnull Codec codec) { + BsonDocument bsonDocument = this.unknownComponents.remove(componentId); + if (bsonDocument == null) { + return null; + } else { + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + T component = (T)codec.decode(bsonDocument, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER); + return component; + } + } + + public Map getUnknownComponents() { + return this.unknownComponents; + } + + @Nonnull + @Override + public Component clone() { + return new UnknownComponents<>(new Object2ObjectOpenHashMap<>(this.unknownComponents)); + } +} diff --git a/src/com/hypixel/hytale/component/dependency/Dependency.java b/src/com/hypixel/hytale/component/dependency/Dependency.java new file mode 100644 index 0000000..465b816 --- /dev/null +++ b/src/com/hypixel/hytale/component/dependency/Dependency.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.component.dependency; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.system.ISystem; +import javax.annotation.Nonnull; + +public abstract class Dependency { + @Nonnull + protected final Order order; + protected final int priority; + + public Dependency(@Nonnull Order order, int priority) { + this.order = order; + this.priority = priority; + } + + public Dependency(@Nonnull Order order, @Nonnull OrderPriority priority) { + this.order = order; + this.priority = priority.getValue(); + } + + @Nonnull + public Order getOrder() { + return this.order; + } + + public int getPriority() { + return this.priority; + } + + public abstract void validate(@Nonnull ComponentRegistry var1); + + public abstract void resolveGraphEdge(@Nonnull ComponentRegistry var1, @Nonnull ISystem var2, @Nonnull DependencyGraph var3); + + @Nonnull + @Override + public String toString() { + return "Dependency{order=" + this.order + "}"; + } +} diff --git a/src/com/hypixel/hytale/component/dependency/DependencyGraph.java b/src/com/hypixel/hytale/component/dependency/DependencyGraph.java new file mode 100644 index 0000000..94b7c48 --- /dev/null +++ b/src/com/hypixel/hytale/component/dependency/DependencyGraph.java @@ -0,0 +1,207 @@ +package com.hypixel.hytale.component.dependency; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.system.ISystem; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DependencyGraph { + @Nonnull + private final ISystem[] systems; + private final Map, List>> beforeSystemEdges = new Object2ObjectOpenHashMap<>(); + private final Map, List>> afterSystemEdges = new Object2ObjectOpenHashMap<>(); + private final Map, Set>> afterSystemUnfulfilledEdges = new Object2ObjectOpenHashMap<>(); + private DependencyGraph.Edge[] edges = DependencyGraph.Edge.emptyArray(); + + public DependencyGraph(@Nonnull ISystem[] systems) { + this.systems = systems; + + for (int i = 0; i < systems.length; i++) { + ISystem system = systems[i]; + this.beforeSystemEdges.put(system, new ObjectArrayList<>()); + this.afterSystemEdges.put(system, new ObjectArrayList<>()); + this.afterSystemUnfulfilledEdges.put(system, new HashSet<>()); + } + } + + @Nonnull + public ISystem[] getSystems() { + return this.systems; + } + + public void resolveEdges(@Nonnull ComponentRegistry registry) { + for (ISystem system : this.systems) { + for (Dependency dependency : system.getDependencies()) { + dependency.resolveGraphEdge(registry, system, this); + } + + if (system.getGroup() != null) { + for (Dependency dependency : system.getGroup().getDependencies()) { + dependency.resolveGraphEdge(registry, system, this); + } + } + } + + for (ISystem system : this.systems) { + if (this.afterSystemEdges.get(system).isEmpty()) { + int priority = 0; + List> edges = this.beforeSystemEdges.get(system); + + for (DependencyGraph.Edge edge : edges) { + priority += edge.priority / edges.size(); + } + + this.addEdgeFromRoot(system, priority); + } + } + } + + public void addEdgeFromRoot(@Nonnull ISystem afterSystem, int priority) { + this.addEdge(new DependencyGraph.Edge<>(null, afterSystem, priority)); + } + + public void addEdge(@Nonnull ISystem beforeSystem, @Nonnull ISystem afterSystem, int priority) { + this.addEdge(new DependencyGraph.Edge<>(beforeSystem, afterSystem, priority)); + } + + public void addEdge(@Nonnull DependencyGraph.Edge edge) { + int index = Arrays.binarySearch(this.edges, edge); + int insertionPoint; + if (index >= 0) { + insertionPoint = index; + + while (insertionPoint < this.edges.length && this.edges[insertionPoint].priority == edge.priority) { + insertionPoint++; + } + } else { + insertionPoint = -(index + 1); + } + + int oldLength = this.edges.length; + int newLength = oldLength + 1; + if (oldLength < newLength) { + this.edges = Arrays.copyOf(this.edges, newLength); + } + + System.arraycopy(this.edges, insertionPoint, this.edges, insertionPoint + 1, oldLength - insertionPoint); + this.edges[insertionPoint] = edge; + if (edge.beforeSystem != null) { + this.beforeSystemEdges.get(edge.beforeSystem).add(edge); + } + + this.afterSystemEdges.get(edge.afterSystem).add(edge); + if (!edge.fulfilled) { + this.afterSystemUnfulfilledEdges.get(edge.afterSystem).add(edge); + } + } + + public void sort(ISystem[] sortedSystems) { + int index = 0; + + label52: + while (index < this.systems.length) { + for (DependencyGraph.Edge edge : this.edges) { + if (!edge.resolved && edge.fulfilled) { + ISystem system = edge.afterSystem; + if (this.afterSystemUnfulfilledEdges.get(system).isEmpty() && !this.hasEdgeOfLaterPriority(system, edge.priority)) { + sortedSystems[index++] = system; + this.resolveEdgesFor(system); + this.fulfillEdgesFor(system); + continue label52; + } + } + } + + for (DependencyGraph.Edge edgex : this.edges) { + if (!edgex.resolved && edgex.fulfilled) { + ISystem system = edgex.afterSystem; + if (this.afterSystemUnfulfilledEdges.get(system).isEmpty()) { + sortedSystems[index++] = system; + this.resolveEdgesFor(system); + this.fulfillEdgesFor(system); + continue label52; + } + } + } + + throw new IllegalArgumentException("Found a cyclic dependency!" + this); + } + } + + private boolean hasEdgeOfLaterPriority(ISystem system, int priority) { + for (DependencyGraph.Edge edge : this.afterSystemEdges.get(system)) { + if (!edge.resolved && edge.priority > priority) { + return true; + } + } + + return false; + } + + private void resolveEdgesFor(ISystem system) { + for (DependencyGraph.Edge edge : this.afterSystemEdges.get(system)) { + edge.resolved = true; + } + } + + private void fulfillEdgesFor(ISystem system) { + for (DependencyGraph.Edge edge : this.beforeSystemEdges.get(system)) { + edge.fulfilled = true; + this.afterSystemUnfulfilledEdges.get(edge.afterSystem).remove(edge); + } + } + + @Nonnull + @Override + public String toString() { + return "DependencyGraph{systems=" + Arrays.toString((Object[])this.systems) + ", edges=" + Arrays.toString((Object[])this.edges) + "}"; + } + + private static class Edge implements Comparable> { + private static final DependencyGraph.Edge[] EMPTY_ARRAY = new DependencyGraph.Edge[0]; + @Nullable + private final ISystem beforeSystem; + private final ISystem afterSystem; + private final int priority; + private boolean fulfilled; + private boolean resolved; + + public static DependencyGraph.Edge[] emptyArray() { + return (DependencyGraph.Edge[])EMPTY_ARRAY; + } + + public Edge(@Nullable ISystem beforeSystem, ISystem afterSystem, int priority) { + this.beforeSystem = beforeSystem; + this.afterSystem = afterSystem; + this.priority = priority; + this.fulfilled = beforeSystem == null; + } + + public int compareTo(@Nonnull DependencyGraph.Edge o) { + return Integer.compare(this.priority, o.priority); + } + + @Nonnull + @Override + public String toString() { + return "Edge{beforeSystem=" + + this.beforeSystem + + ", afterSystem=" + + this.afterSystem + + ", priority=" + + this.priority + + ", fulfilled=" + + this.fulfilled + + ", resolved=" + + this.resolved + + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/component/dependency/Order.java b/src/com/hypixel/hytale/component/dependency/Order.java new file mode 100644 index 0000000..bee3fe6 --- /dev/null +++ b/src/com/hypixel/hytale/component/dependency/Order.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.component.dependency; + +public enum Order { + BEFORE, + AFTER; + + private Order() { + } +} diff --git a/src/com/hypixel/hytale/component/dependency/OrderPriority.java b/src/com/hypixel/hytale/component/dependency/OrderPriority.java new file mode 100644 index 0000000..b1b553b --- /dev/null +++ b/src/com/hypixel/hytale/component/dependency/OrderPriority.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.component.dependency; + +public enum OrderPriority { + CLOSEST(-1431655764), + CLOSE(-715827882), + NORMAL(0), + FURTHER(715827882), + FURTHEST(1431655764); + + private final int value; + + private OrderPriority(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } +} diff --git a/src/com/hypixel/hytale/component/dependency/RootDependency.java b/src/com/hypixel/hytale/component/dependency/RootDependency.java new file mode 100644 index 0000000..87e1478 --- /dev/null +++ b/src/com/hypixel/hytale/component/dependency/RootDependency.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.component.dependency; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.system.ISystem; +import java.util.Set; +import javax.annotation.Nonnull; + +public class RootDependency extends Dependency { + private static final RootDependency FIRST = new RootDependency(OrderPriority.CLOSEST); + private static final RootDependency LAST = new RootDependency(OrderPriority.FURTHEST); + private static final Set> FIRST_SET = Set.of(FIRST); + private static final Set> LAST_SET = Set.of(LAST); + + public static RootDependency first() { + return (RootDependency)FIRST; + } + + public static RootDependency last() { + return (RootDependency)LAST; + } + + public static Set> firstSet() { + return (Set>)FIRST_SET; + } + + public static Set> lastSet() { + return (Set>)LAST_SET; + } + + public RootDependency(int priority) { + super(Order.AFTER, priority); + } + + public RootDependency(@Nonnull OrderPriority priority) { + super(Order.AFTER, priority); + } + + @Override + public void validate(@Nonnull ComponentRegistry registry) { + } + + @Override + public void resolveGraphEdge(@Nonnull ComponentRegistry registry, @Nonnull ISystem thisSystem, @Nonnull DependencyGraph graph) { + if (this.order == Order.BEFORE) { + throw new UnsupportedOperationException("RootDependency can't have Order.BEFORE!"); + } else { + graph.addEdgeFromRoot(thisSystem, this.priority); + } + } + + @Nonnull + @Override + public String toString() { + return "SystemDependency{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/component/dependency/SystemDependency.java b/src/com/hypixel/hytale/component/dependency/SystemDependency.java new file mode 100644 index 0000000..65cae75 --- /dev/null +++ b/src/com/hypixel/hytale/component/dependency/SystemDependency.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.component.dependency; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.system.ISystem; +import javax.annotation.Nonnull; + +public class SystemDependency> extends Dependency { + @Nonnull + private final Class systemClass; + + public SystemDependency(@Nonnull Order order, @Nonnull Class systemClass) { + this(order, systemClass, OrderPriority.NORMAL); + } + + public SystemDependency(@Nonnull Order order, @Nonnull Class systemClass, int priority) { + super(order, priority); + this.systemClass = systemClass; + } + + public SystemDependency(@Nonnull Order order, @Nonnull Class systemClass, @Nonnull OrderPriority priority) { + super(order, priority); + this.systemClass = systemClass; + } + + @Nonnull + public Class getSystemClass() { + return this.systemClass; + } + + @Override + public void validate(@Nonnull ComponentRegistry registry) { + if (!registry.hasSystemClass(this.systemClass)) { + throw new IllegalArgumentException("SystemType dependency isn't registered: " + this.systemClass); + } + } + + @Override + public void resolveGraphEdge(@Nonnull ComponentRegistry registry, @Nonnull ISystem thisSystem, @Nonnull DependencyGraph graph) { + switch (this.order) { + case BEFORE: + for (ISystem systemx : graph.getSystems()) { + if (this.systemClass.equals(systemx.getClass())) { + graph.addEdge(thisSystem, systemx, -this.priority); + } + } + break; + case AFTER: + for (ISystem system : graph.getSystems()) { + if (this.systemClass.equals(system.getClass())) { + graph.addEdge(system, thisSystem, this.priority); + } + } + } + } + + @Nonnull + @Override + public String toString() { + return "SystemDependency{systemClass=" + this.systemClass + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/component/dependency/SystemGroupDependency.java b/src/com/hypixel/hytale/component/dependency/SystemGroupDependency.java new file mode 100644 index 0000000..9dbbad6 --- /dev/null +++ b/src/com/hypixel/hytale/component/dependency/SystemGroupDependency.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.component.dependency; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.system.ISystem; +import javax.annotation.Nonnull; + +public class SystemGroupDependency extends Dependency { + @Nonnull + private final SystemGroup group; + + public SystemGroupDependency(@Nonnull Order order, @Nonnull SystemGroup group) { + this(order, group, OrderPriority.NORMAL); + } + + public SystemGroupDependency(@Nonnull Order order, @Nonnull SystemGroup group, int priority) { + super(order, priority); + this.group = group; + } + + public SystemGroupDependency(@Nonnull Order order, @Nonnull SystemGroup group, @Nonnull OrderPriority priority) { + super(order, priority); + this.group = group; + } + + @Nonnull + public SystemGroup getGroup() { + return this.group; + } + + @Override + public void validate(@Nonnull ComponentRegistry registry) { + if (!registry.hasSystemGroup(this.group)) { + throw new IllegalArgumentException("System dependency isn't registered: " + this.group); + } + } + + @Override + public void resolveGraphEdge(@Nonnull ComponentRegistry registry, @Nonnull ISystem thisSystem, @Nonnull DependencyGraph graph) { + switch (this.order) { + case BEFORE: + for (ISystem systemx : graph.getSystems()) { + if (this.group.equals(systemx.getGroup())) { + graph.addEdge(thisSystem, systemx, -this.priority); + } + } + break; + case AFTER: + for (ISystem system : graph.getSystems()) { + if (this.group.equals(system.getGroup())) { + graph.addEdge(system, thisSystem, this.priority); + } + } + } + } + + @Nonnull + @Override + public String toString() { + return "SystemGroupDependency{group=" + this.group + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/component/dependency/SystemTypeDependency.java b/src/com/hypixel/hytale/component/dependency/SystemTypeDependency.java new file mode 100644 index 0000000..e2e821b --- /dev/null +++ b/src/com/hypixel/hytale/component/dependency/SystemTypeDependency.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.component.dependency; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.SystemType; +import com.hypixel.hytale.component.system.ISystem; +import javax.annotation.Nonnull; + +public class SystemTypeDependency> extends Dependency { + @Nonnull + private final SystemType systemType; + + public SystemTypeDependency(@Nonnull Order order, @Nonnull SystemType systemType) { + this(order, systemType, OrderPriority.NORMAL); + } + + public SystemTypeDependency(@Nonnull Order order, @Nonnull SystemType systemType, int priority) { + super(order, priority); + this.systemType = systemType; + } + + public SystemTypeDependency(@Nonnull Order order, @Nonnull SystemType systemType, @Nonnull OrderPriority priority) { + super(order, priority); + this.systemType = systemType; + } + + @Nonnull + public SystemType getSystemType() { + return this.systemType; + } + + @Override + public void validate(@Nonnull ComponentRegistry registry) { + if (!registry.hasSystemType(this.systemType)) { + throw new IllegalArgumentException("SystemType dependency isn't registered: " + this.systemType); + } + } + + @Override + public void resolveGraphEdge(@Nonnull ComponentRegistry registry, @Nonnull ISystem thisSystem, @Nonnull DependencyGraph graph) { + switch (this.order) { + case BEFORE: + for (ISystem systemx : graph.getSystems()) { + if (this.systemType.isType(systemx)) { + graph.addEdge(thisSystem, systemx, -this.priority); + } + } + break; + case AFTER: + for (ISystem system : graph.getSystems()) { + if (this.systemType.isType(system)) { + graph.addEdge(system, thisSystem, this.priority); + } + } + } + } + + @Nonnull + @Override + public String toString() { + return "SystemTypeDependency{systemType=" + this.systemType + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/component/event/EntityEventType.java b/src/com/hypixel/hytale/component/event/EntityEventType.java new file mode 100644 index 0000000..48f21e5 --- /dev/null +++ b/src/com/hypixel/hytale/component/event/EntityEventType.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.component.event; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.system.EcsEvent; +import com.hypixel.hytale.component.system.EntityEventSystem; +import javax.annotation.Nonnull; + +public class EntityEventType extends EventSystemType> { + public EntityEventType( + @Nonnull ComponentRegistry registry, @Nonnull Class> tClass, @Nonnull Class eClass, int index + ) { + super(registry, tClass, eClass, index); + } +} diff --git a/src/com/hypixel/hytale/component/event/EventSystemType.java b/src/com/hypixel/hytale/component/event/EventSystemType.java new file mode 100644 index 0000000..023a550 --- /dev/null +++ b/src/com/hypixel/hytale/component/event/EventSystemType.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.component.event; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.SystemType; +import com.hypixel.hytale.component.system.EcsEvent; +import com.hypixel.hytale.component.system.EventSystem; +import com.hypixel.hytale.component.system.ISystem; +import javax.annotation.Nonnull; + +public abstract class EventSystemType & ISystem> + extends SystemType { + @Nonnull + private final Class eClass; + + protected EventSystemType(@Nonnull ComponentRegistry registry, @Nonnull Class tClass, @Nonnull Class eClass, int index) { + super(registry, tClass, index); + this.eClass = eClass; + } + + @Nonnull + public Class getEventClass() { + return this.eClass; + } + + @Override + public boolean isType(@Nonnull ISystem system) { + if (!super.isType(system)) { + return false; + } else { + return system instanceof EventSystem eventSystem ? this.eClass.equals(eventSystem.getEventType()) : false; + } + } +} diff --git a/src/com/hypixel/hytale/component/event/WorldEventType.java b/src/com/hypixel/hytale/component/event/WorldEventType.java new file mode 100644 index 0000000..f00d525 --- /dev/null +++ b/src/com/hypixel/hytale/component/event/WorldEventType.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.component.event; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.system.EcsEvent; +import com.hypixel.hytale.component.system.WorldEventSystem; +import javax.annotation.Nonnull; + +public class WorldEventType extends EventSystemType> { + public WorldEventType( + @Nonnull ComponentRegistry registry, @Nonnull Class> tClass, @Nonnull Class eClass, int index + ) { + super(registry, tClass, eClass, index); + } +} diff --git a/src/com/hypixel/hytale/component/metric/ArchetypeChunkData.java b/src/com/hypixel/hytale/component/metric/ArchetypeChunkData.java new file mode 100644 index 0000000..f101d28 --- /dev/null +++ b/src/com/hypixel/hytale/component/metric/ArchetypeChunkData.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.component.metric; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class ArchetypeChunkData { + @Nonnull + public static final Codec CODEC = BuilderCodec.builder(ArchetypeChunkData.class, ArchetypeChunkData::new) + .append(new KeyedCodec<>("ComponentTypes", Codec.STRING_ARRAY), (data, o) -> data.componentTypes = o, data -> data.componentTypes) + .add() + .append(new KeyedCodec<>("EntityCount", Codec.INTEGER), (data, o) -> data.entityCount = o, data -> data.entityCount) + .add() + .build(); + @Nonnull + private String[] componentTypes = new String[0]; + private int entityCount; + + public ArchetypeChunkData() { + } + + public ArchetypeChunkData(@Nonnull String[] componentTypes, int entityCount) { + this.componentTypes = componentTypes; + this.entityCount = entityCount; + } + + @Nonnull + public String[] getComponentTypes() { + return this.componentTypes; + } + + public int getEntityCount() { + return this.entityCount; + } +} diff --git a/src/com/hypixel/hytale/component/metric/SystemMetricData.java b/src/com/hypixel/hytale/component/metric/SystemMetricData.java new file mode 100644 index 0000000..f6975b2 --- /dev/null +++ b/src/com/hypixel/hytale/component/metric/SystemMetricData.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.component.metric; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.metrics.MetricResults; +import com.hypixel.hytale.metrics.metric.HistoricMetric; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SystemMetricData { + @Nonnull + public static final Codec CODEC = BuilderCodec.builder(SystemMetricData.class, SystemMetricData::new) + .append(new KeyedCodec<>("Name", Codec.STRING), (systemMetricData, o) -> systemMetricData.name = o, systemMetricData -> systemMetricData.name) + .add() + .append( + new KeyedCodec<>("ArchetypeChunkCount", Codec.INTEGER), + (systemMetricData, o) -> systemMetricData.archetypeChunkCount = o, + systemMetricData -> systemMetricData.archetypeChunkCount + ) + .add() + .append( + new KeyedCodec<>("EntityCount", Codec.INTEGER), + (systemMetricData, o) -> systemMetricData.entityCount = o, + systemMetricData -> systemMetricData.entityCount + ) + .add() + .append( + new KeyedCodec<>("HistoricMetric", HistoricMetric.METRICS_CODEC), + (systemMetricData, o) -> systemMetricData.historicMetric = o, + systemMetricData -> systemMetricData.historicMetric + ) + .add() + .append( + new KeyedCodec<>("Metrics", MetricResults.CODEC), (systemMetricData, o) -> systemMetricData.metrics = o, systemMetricData -> systemMetricData.metrics + ) + .add() + .build(); + private String name; + private int archetypeChunkCount; + private int entityCount; + @Nullable + private HistoricMetric historicMetric; + private MetricResults metrics; + + public SystemMetricData() { + } + + public SystemMetricData( + @Nonnull String name, int archetypeChunkCount, int entityCount, @Nullable HistoricMetric historicMetric, @Nonnull MetricResults metrics + ) { + this.name = name; + this.archetypeChunkCount = archetypeChunkCount; + this.entityCount = entityCount; + this.historicMetric = historicMetric; + this.metrics = metrics; + } +} diff --git a/src/com/hypixel/hytale/component/package-info.java b/src/com/hypixel/hytale/component/package-info.java new file mode 100644 index 0000000..325501a --- /dev/null +++ b/src/com/hypixel/hytale/component/package-info.java @@ -0,0 +1,2 @@ +package com.hypixel.hytale.component; + diff --git a/src/com/hypixel/hytale/component/query/AndQuery.java b/src/com/hypixel/hytale/component/query/AndQuery.java new file mode 100644 index 0000000..698befa --- /dev/null +++ b/src/com/hypixel/hytale/component/query/AndQuery.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.component.query; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentType; + +public class AndQuery implements Query { + private final Query[] queries; + + @SafeVarargs + public AndQuery(Query... queries) { + this.queries = queries; + } + + @Override + public boolean test(Archetype archetype) { + for (Query query : this.queries) { + if (!query.test(archetype)) { + return false; + } + } + + return true; + } + + @Override + public boolean requiresComponentType(ComponentType componentType) { + for (Query query : this.queries) { + if (query.requiresComponentType(componentType)) { + return true; + } + } + + return false; + } + + @Override + public void validateRegistry(ComponentRegistry registry) { + for (Query query : this.queries) { + query.validateRegistry(registry); + } + } + + @Override + public void validate() { + for (Query query : this.queries) { + query.validate(); + } + } +} diff --git a/src/com/hypixel/hytale/component/query/AnyQuery.java b/src/com/hypixel/hytale/component/query/AnyQuery.java new file mode 100644 index 0000000..55e04a2 --- /dev/null +++ b/src/com/hypixel/hytale/component/query/AnyQuery.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.component.query; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentType; + +class AnyQuery implements Query { + static final AnyQuery INSTANCE = new AnyQuery(); + + AnyQuery() { + } + + @Override + public boolean test(Archetype archetype) { + return true; + } + + @Override + public boolean requiresComponentType(ComponentType componentType) { + return false; + } + + @Override + public void validateRegistry(ComponentRegistry registry) { + } + + @Override + public void validate() { + } +} diff --git a/src/com/hypixel/hytale/component/query/ExactArchetypeQuery.java b/src/com/hypixel/hytale/component/query/ExactArchetypeQuery.java new file mode 100644 index 0000000..b5af335 --- /dev/null +++ b/src/com/hypixel/hytale/component/query/ExactArchetypeQuery.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.component.query; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentType; +import javax.annotation.Nonnull; + +public class ExactArchetypeQuery implements Query { + private final Archetype archetype; + + public ExactArchetypeQuery(Archetype archetype) { + this.archetype = archetype; + } + + public Archetype getArchetype() { + return this.archetype; + } + + @Override + public boolean test(@Nonnull Archetype archetype) { + return archetype.equals(this.archetype); + } + + @Override + public boolean requiresComponentType(@Nonnull ComponentType componentType) { + return this.archetype.requiresComponentType(componentType); + } + + @Override + public void validateRegistry(ComponentRegistry registry) { + this.archetype.validateRegistry(registry); + } + + @Override + public void validate() { + this.archetype.validate(); + } +} diff --git a/src/com/hypixel/hytale/component/query/NotQuery.java b/src/com/hypixel/hytale/component/query/NotQuery.java new file mode 100644 index 0000000..f9ed16d --- /dev/null +++ b/src/com/hypixel/hytale/component/query/NotQuery.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.component.query; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentType; + +public class NotQuery implements Query { + private final Query query; + + public NotQuery(Query query) { + this.query = query; + } + + @Override + public boolean test(Archetype archetype) { + return !this.query.test(archetype); + } + + @Override + public boolean requiresComponentType(ComponentType componentType) { + return this.query.requiresComponentType(componentType); + } + + @Override + public void validateRegistry(ComponentRegistry registry) { + this.query.validateRegistry(registry); + } + + @Override + public void validate() { + this.query.validate(); + } +} diff --git a/src/com/hypixel/hytale/component/query/OrQuery.java b/src/com/hypixel/hytale/component/query/OrQuery.java new file mode 100644 index 0000000..8d7946e --- /dev/null +++ b/src/com/hypixel/hytale/component/query/OrQuery.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.component.query; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentType; + +public class OrQuery implements Query { + private final Query[] queries; + + public OrQuery(Query... queries) { + this.queries = queries; + } + + @Override + public boolean test(Archetype archetype) { + for (Query query : this.queries) { + if (query.test(archetype)) { + return true; + } + } + + return false; + } + + @Override + public boolean requiresComponentType(ComponentType componentType) { + for (Query query : this.queries) { + if (query.requiresComponentType(componentType)) { + return true; + } + } + + return false; + } + + @Override + public void validateRegistry(ComponentRegistry registry) { + for (Query query : this.queries) { + query.validateRegistry(registry); + } + } + + @Override + public void validate() { + for (Query query : this.queries) { + query.validate(); + } + } +} diff --git a/src/com/hypixel/hytale/component/query/Query.java b/src/com/hypixel/hytale/component/query/Query.java new file mode 100644 index 0000000..bd90a4e --- /dev/null +++ b/src/com/hypixel/hytale/component/query/Query.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.component.query; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentType; +import javax.annotation.Nonnull; + +public interface Query { + @Nonnull + static AnyQuery any() { + return (AnyQuery)AnyQuery.INSTANCE; + } + + @Nonnull + static NotQuery not(Query query) { + return new NotQuery<>(query); + } + + @Nonnull + @SafeVarargs + static AndQuery and(Query... queries) { + return new AndQuery<>(queries); + } + + @Nonnull + @SafeVarargs + static OrQuery or(Query... queries) { + return new OrQuery<>(queries); + } + + boolean test(Archetype var1); + + boolean requiresComponentType(ComponentType var1); + + void validateRegistry(ComponentRegistry var1); + + void validate(); +} diff --git a/src/com/hypixel/hytale/component/query/ReadWriteArchetypeQuery.java b/src/com/hypixel/hytale/component/query/ReadWriteArchetypeQuery.java new file mode 100644 index 0000000..f568672 --- /dev/null +++ b/src/com/hypixel/hytale/component/query/ReadWriteArchetypeQuery.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.component.query; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentType; +import javax.annotation.Nonnull; + +public interface ReadWriteArchetypeQuery extends Query { + Archetype getReadArchetype(); + + Archetype getWriteArchetype(); + + @Override + default boolean test(@Nonnull Archetype archetype) { + return archetype.contains(this.getReadArchetype()) && archetype.contains(this.getWriteArchetype()); + } + + @Override + default boolean requiresComponentType(@Nonnull ComponentType componentType) { + return this.getReadArchetype().contains(componentType) || this.getWriteArchetype().contains(componentType); + } + + @Override + default void validateRegistry(ComponentRegistry registry) { + this.getReadArchetype().validateRegistry(registry); + this.getWriteArchetype().validateRegistry(registry); + } + + @Override + default void validate() { + this.getReadArchetype().validate(); + this.getWriteArchetype().validate(); + } +} diff --git a/src/com/hypixel/hytale/component/spatial/KDTree.java b/src/com/hypixel/hytale/component/spatial/KDTree.java new file mode 100644 index 0000000..006d6cd --- /dev/null +++ b/src/com/hypixel/hytale/component/spatial/KDTree.java @@ -0,0 +1,527 @@ +package com.hypixel.hytale.component.spatial; + +import com.hypixel.hytale.math.vector.Vector3d; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class KDTree implements SpatialStructure { + @Nonnull + private final List> nodePool = new ObjectArrayList<>(); + private int nodePoolIndex = 0; + @Nonnull + private final List> dataListPool = new ObjectArrayList<>(); + private int dataListPoolIndex = 0; + private int size; + @Nonnull + private final Predicate collectionFilter; + @Nullable + private KDTree.Node root; + + public KDTree(@Nonnull Predicate collectionFilter) { + this.collectionFilter = collectionFilter; + } + + @Override + public int size() { + return this.size; + } + + @Override + public void rebuild(@Nonnull SpatialData spatialData) { + this.root = null; + this.size = 0; + int spatialDataSize = spatialData.size(); + if (spatialDataSize != 0) { + for (int i = 0; i < this.dataListPoolIndex; i++) { + this.dataListPool.get(i).clear(); + } + + this.nodePoolIndex = 0; + this.dataListPoolIndex = 0; + spatialData.sortMorton(); + int mid = spatialDataSize / 2; + int sortedIndex = spatialData.getSortedIndex(mid); + Vector3d vector = spatialData.getVector(sortedIndex); + T data = spatialData.getData(sortedIndex); + List list = this.getPooledDataList(); + list.add(data); + + int left; + for (left = mid - 1; left >= 0; left--) { + int leftSortedIndex = spatialData.getSortedIndex(left); + Vector3d leftVector = spatialData.getVector(leftSortedIndex); + if (!leftVector.equals(vector)) { + break; + } + + T leftData = spatialData.getData(leftSortedIndex); + list.add(leftData); + } + + int right; + for (right = mid + 1; right < spatialDataSize; right++) { + int rightSortedIndex = spatialData.getSortedIndex(right); + Vector3d rightVector = spatialData.getVector(rightSortedIndex); + if (!rightVector.equals(vector)) { + break; + } + + T rightData = spatialData.getData(rightSortedIndex); + list.add(rightData); + } + + this.root = this.getPooledNode(vector, list); + if (0 < left + 1) { + this.build0(spatialData, 0, left + 1); + } + + if (right < spatialDataSize) { + this.build0(spatialData, right, spatialDataSize); + } + + this.size = spatialDataSize; + } + } + + @Nullable + @Override + public T closest(@Nonnull Vector3d point) { + KDTree.ClosestState closestState = new KDTree.ClosestState<>(null, Double.MAX_VALUE); + this.closest0(closestState, this.root, point, 0); + return closestState.node == null ? null : closestState.node.data.getFirst(); + } + + @Override + public void collect(@Nonnull Vector3d center, double radius, @Nonnull List results) { + double distanceSq = radius * radius; + this.collect0(results, this.root, center, distanceSq, 0); + } + + @Override + public void collectCylinder(@Nonnull Vector3d center, double radius, double height, @Nonnull List results) { + double radiusSq = radius * radius; + double halfHeight = height / 2.0; + this.collectCylinder0(results, this.root, center, radiusSq, halfHeight, radius, 0); + } + + @Override + public void collectBox(@Nonnull Vector3d min, @Nonnull Vector3d max, @Nonnull List results) { + this.collectBox0(results, this.root, min, max, 0); + } + + @Override + public void ordered(@Nonnull Vector3d center, double radius, @Nonnull List results) { + double distanceSq = radius * radius; + ObjectArrayList> entryResults = new ObjectArrayList<>(); + this.ordered0(entryResults, this.root, center, distanceSq, 0); + entryResults.sort(Comparator.comparingDouble(o -> o.distanceSq)); + + for (KDTree.OrderedEntry entry : entryResults) { + int i = 0; + + for (int bound = entry.values.size(); i < bound; i++) { + T data = entry.values.get(i); + if (this.collectionFilter.test(data)) { + results.add(data); + } + } + } + } + + @Override + public void ordered3DAxis(@Nonnull Vector3d center, double xSearchRadius, double YSearchRadius, double zSearchRadius, @Nonnull List results) { + ObjectArrayList> entryResults = new ObjectArrayList<>(); + this._internal_ordered3DAxis(entryResults, this.root, center, xSearchRadius, YSearchRadius, zSearchRadius, 0); + entryResults.sort(Comparator.comparingDouble(o -> o.distanceSq)); + + for (KDTree.OrderedEntry entry : entryResults) { + int i = 0; + + for (int bound = entry.values.size(); i < bound; i++) { + T data = entry.values.get(i); + if (this.collectionFilter.test(data)) { + results.add(data); + } + } + } + } + + @Nonnull + @Override + public String dump() { + return "KDTree(size=" + this.size + ")\n" + (this.root == null ? null : this.root.dump(0)); + } + + @Nonnull + private KDTree.Node getPooledNode(Vector3d vector, List data) { + if (this.nodePoolIndex < this.nodePool.size()) { + KDTree.Node node = this.nodePool.get(this.nodePoolIndex++); + node.reset(vector, data); + return node; + } else { + KDTree.Node node = new KDTree.Node<>(vector, data); + this.nodePool.add(node); + this.nodePoolIndex++; + return node; + } + } + + private List getPooledDataList() { + if (this.dataListPoolIndex < this.dataListPool.size()) { + return this.dataListPool.get(this.dataListPoolIndex++); + } else { + ObjectArrayList set = new ObjectArrayList<>(1); + this.dataListPool.add(set); + this.dataListPoolIndex++; + return set; + } + } + + private void build0(@Nonnull SpatialData spatialData, int start, int end) { + int mid = (start + end) / 2; + int sortedIndex = spatialData.getSortedIndex(mid); + Vector3d vector = spatialData.getVector(sortedIndex); + T data = spatialData.getData(sortedIndex); + List list = this.getPooledDataList(); + list.add(data); + + int left; + for (left = mid - 1; left >= start; left--) { + int leftSortedIndex = spatialData.getSortedIndex(left); + Vector3d leftVector = spatialData.getVector(leftSortedIndex); + if (!leftVector.equals(vector)) { + break; + } + + T leftData = spatialData.getData(leftSortedIndex); + list.add(leftData); + } + + int right; + for (right = mid + 1; right < end; right++) { + int rightSortedIndex = spatialData.getSortedIndex(right); + Vector3d rightVector = spatialData.getVector(rightSortedIndex); + if (!rightVector.equals(vector)) { + break; + } + + T rightData = spatialData.getData(rightSortedIndex); + list.add(rightData); + } + + this.put0(this.root, vector, list, 0); + if (start < left + 1) { + this.build0(spatialData, start, left + 1); + } + + if (right < end) { + this.build0(spatialData, right, end); + } + } + + private void put0(@Nonnull KDTree.Node node, @Nonnull Vector3d vector, @Nonnull List list, int axis) { + if (compare(node.vector, vector, axis) < 0) { + if (node.one == null) { + node.one = this.getPooledNode(vector, list); + } else { + this.put0(node.one, vector, list, (axis + 1) % 3); + } + } else if (node.two == null) { + node.two = this.getPooledNode(vector, list); + } else { + this.put0(node.two, vector, list, (axis + 1) % 3); + } + } + + private void closest0(@Nonnull KDTree.ClosestState closestState, @Nullable KDTree.Node node, @Nonnull Vector3d vector, int depth) { + if (node != null) { + if (vector.equals(node.vector)) { + closestState.distanceSq = 0.0; + closestState.node = node; + } else { + int axis = depth % 3; + int compare = compare(node.vector, vector, axis); + double distanceSq = node.vector.distanceSquaredTo(vector); + if (distanceSq < closestState.distanceSq) { + closestState.node = node; + closestState.distanceSq = distanceSq; + } + + int newDepth = depth + 1; + if (compare < 0) { + this.closest0(closestState, node.one, vector, newDepth); + } else { + this.closest0(closestState, node.two, vector, newDepth); + } + + double plane = get(node.vector, axis); + double component = get(closestState.node.vector, axis); + double planeDistance = Math.abs(component - plane); + if (planeDistance * planeDistance < closestState.distanceSq) { + if (compare < 0) { + this.closest0(closestState, node.two, vector, newDepth); + } else { + this.closest0(closestState, node.one, vector, newDepth); + } + } + } + } + } + + private void collect0(@Nonnull List results, @Nullable KDTree.Node node, @Nonnull Vector3d vector, double distanceSq, int depth) { + if (node != null) { + int axis = depth % 3; + int compare = compare(node.vector, vector, axis); + double nodeDistanceSq = node.vector.distanceSquaredTo(vector); + if (nodeDistanceSq < distanceSq) { + int i = 0; + + for (int bound = node.data.size(); i < bound; i++) { + T data = node.data.get(i); + if (this.collectionFilter.test(data)) { + results.add(data); + } + } + } + + int newDepth = depth + 1; + if (compare < 0) { + this.collect0(results, node.one, vector, distanceSq, newDepth); + } else { + this.collect0(results, node.two, vector, distanceSq, newDepth); + } + + double plane = get(node.vector, axis); + double component = get(vector, axis); + double planeDistance = Math.abs(component - plane); + if (planeDistance * planeDistance < distanceSq) { + if (compare < 0) { + this.collect0(results, node.two, vector, distanceSq, newDepth); + } else { + this.collect0(results, node.one, vector, distanceSq, newDepth); + } + } + } + } + + private void collectCylinder0( + @Nonnull List results, @Nullable KDTree.Node node, @Nonnull Vector3d center, double radiusSq, double halfHeight, double radius, int depth + ) { + if (node != null) { + int axis = depth % 3; + int compare = compare(node.vector, center, axis); + double dy = node.vector.y - center.y; + if (Math.abs(dy) <= halfHeight) { + double dx = node.vector.x - center.x; + double dz = node.vector.z - center.z; + double xzDistanceSq = dx * dx + dz * dz; + if (xzDistanceSq <= radiusSq) { + int i = 0; + + for (int bound = node.data.size(); i < bound; i++) { + T data = node.data.get(i); + if (this.collectionFilter.test(data)) { + results.add(data); + } + } + } + } + + int newDepth = depth + 1; + if (compare < 0) { + this.collectCylinder0(results, node.one, center, radiusSq, halfHeight, radius, newDepth); + } else { + this.collectCylinder0(results, node.two, center, radiusSq, halfHeight, radius, newDepth); + } + + double plane = get(node.vector, axis); + double component = get(center, axis); + double axisRadius = axis == 2 ? halfHeight : radius; + if (Math.abs(component - plane) <= axisRadius) { + if (compare < 0) { + this.collectCylinder0(results, node.two, center, radiusSq, halfHeight, radius, newDepth); + } else { + this.collectCylinder0(results, node.one, center, radiusSq, halfHeight, radius, newDepth); + } + } + } + } + + private void collectBox0(@Nonnull List results, @Nullable KDTree.Node node, @Nonnull Vector3d min, @Nonnull Vector3d max, int depth) { + if (node != null) { + int axis = depth % 3; + if (node.vector.x >= min.x + && node.vector.x <= max.x + && node.vector.y >= min.y + && node.vector.y <= max.y + && node.vector.z >= min.z + && node.vector.z <= max.z) { + int i = 0; + + for (int bound = node.data.size(); i < bound; i++) { + T data = node.data.get(i); + if (this.collectionFilter.test(data)) { + results.add(data); + } + } + } + + int newDepth = depth + 1; + double plane = get(node.vector, axis); + double minComponent = get(min, axis); + double maxComponent = get(max, axis); + if (maxComponent >= plane) { + this.collectBox0(results, node.one, min, max, newDepth); + } + + if (minComponent <= plane) { + this.collectBox0(results, node.two, min, max, newDepth); + } + } + } + + private void ordered0(@Nonnull List> results, @Nullable KDTree.Node node, @Nonnull Vector3d vector, double distanceSq, int depth) { + if (node != null) { + int axis = depth % 3; + int compare = compare(node.vector, vector, axis); + double nodeDistanceSq = node.vector.distanceSquaredTo(vector); + if (nodeDistanceSq < distanceSq) { + results.add(new KDTree.OrderedEntry<>(nodeDistanceSq, node.data)); + } + + int newDepth = depth + 1; + if (compare < 0) { + this.ordered0(results, node.one, vector, distanceSq, newDepth); + } else { + this.ordered0(results, node.two, vector, distanceSq, newDepth); + } + + double plane = get(node.vector, axis); + double component = get(vector, axis); + double planeDistance = Math.abs(component - plane); + if (planeDistance * planeDistance < distanceSq) { + if (compare < 0) { + this.ordered0(results, node.two, vector, distanceSq, newDepth); + } else { + this.ordered0(results, node.one, vector, distanceSq, newDepth); + } + } + } + } + + private void _internal_ordered3DAxis( + @Nonnull List> results, + @Nullable KDTree.Node node, + @Nonnull Vector3d center, + double xSearchRadius, + double ySearchRadius, + double zSearchRadius, + int depth + ) { + if (node != null) { + int axis = depth % 3; + boolean inCuboid = node.vector.x >= center.x - xSearchRadius + && node.vector.x <= center.x + xSearchRadius + && node.vector.y >= center.y - ySearchRadius + && node.vector.y <= center.y + ySearchRadius + && node.vector.z >= center.z - zSearchRadius + && node.vector.z <= center.z + zSearchRadius; + if (inCuboid) { + double nodeDistanceSq = node.vector.distanceSquaredTo(center); + results.add(new KDTree.OrderedEntry<>(nodeDistanceSq, node.data)); + } + + int newDepth = depth + 1; + int compare = compare(node.vector, center, axis); + KDTree.Node primary = compare < 0 ? node.one : node.two; + KDTree.Node secondary = compare < 0 ? node.two : node.one; + this._internal_ordered3DAxis(results, primary, center, xSearchRadius, ySearchRadius, zSearchRadius, newDepth); + double plane = get(node.vector, axis); + double component = get(center, axis); + double radius = axis == 0 ? xSearchRadius : (axis == 1 ? ySearchRadius : zSearchRadius); + if (Math.abs(component - plane) <= radius) { + this._internal_ordered3DAxis(results, secondary, center, xSearchRadius, ySearchRadius, zSearchRadius, newDepth); + } + } + } + + private static int compare(@Nonnull Vector3d v1, @Nonnull Vector3d v2, int axis) { + return switch (axis) { + case 0 -> Double.compare(v1.x, v2.x); + case 1 -> Double.compare(v1.z, v2.z); + case 2 -> Double.compare(v1.y, v2.y); + default -> throw new IllegalArgumentException("Invalid axis: " + axis); + }; + } + + private static double get(@Nonnull Vector3d v, int axis) { + return switch (axis) { + case 0 -> v.x; + case 1 -> v.z; + case 2 -> v.y; + default -> throw new IllegalArgumentException("Invalid axis: " + axis); + }; + } + + private static class ClosestState { + private KDTree.Node node; + private double distanceSq; + + public ClosestState(KDTree.Node node, double distanceSq) { + this.node = node; + this.distanceSq = distanceSq; + } + } + + private static class Node { + private Vector3d vector; + private List data; + @Nullable + private KDTree.Node one; + @Nullable + private KDTree.Node two; + + public Node(Vector3d vector, List data) { + this.vector = vector; + this.data = data; + } + + public void reset(Vector3d vector, List data) { + this.vector = vector; + this.data = data; + this.one = null; + this.two = null; + } + + @Nonnull + public String dump(int depth) { + int nextDepth = depth + 1; + return "vector=" + + this.vector + + ", data=" + + this.data + + ",\n" + + " ".repeat(depth) + + "one=" + + (this.one == null ? null : this.one.dump(nextDepth)) + + ",\n" + + " ".repeat(depth) + + "two=" + + (this.two == null ? null : this.two.dump(nextDepth)); + } + } + + private static class OrderedEntry { + private final double distanceSq; + private final List values; + + public OrderedEntry(double distanceSq, List values) { + this.distanceSq = distanceSq; + this.values = values; + } + } +} diff --git a/src/com/hypixel/hytale/component/spatial/MortonCode.java b/src/com/hypixel/hytale/component/spatial/MortonCode.java new file mode 100644 index 0000000..ec3426a --- /dev/null +++ b/src/com/hypixel/hytale/component/spatial/MortonCode.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.component.spatial; + +public class MortonCode { + private static final int BITS_PER_AXIS = 21; + private static final long MAX_COORD = 2097151L; + + public MortonCode() { + } + + public static long encode(double x, double y, double z, double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { + double nx = (x - minX) / (maxX - minX); + double ny = (y - minY) / (maxY - minY); + double nz = (z - minZ) / (maxZ - minZ); + long ix = Math.min(Math.max((long)(nx * 2097151.0), 0L), 2097151L); + long iy = Math.min(Math.max((long)(ny * 2097151.0), 0L), 2097151L); + long iz = Math.min(Math.max((long)(nz * 2097151.0), 0L), 2097151L); + return interleaveBits(ix, iy, iz); + } + + private static long interleaveBits(long x, long y, long z) { + x = expandBits(x); + y = expandBits(y); + z = expandBits(z); + return x | y << 1 | z << 2; + } + + private static long expandBits(long value) { + value &= 2097151L; + value = (value | value << 32) & 8725724278095871L; + value = (value | value << 16) & 8725728556220671L; + value = (value | value << 8) & 1157144660301377551L; + value = (value | value << 4) & 75488908039734028L; + return (value | value << 2) & 1317624576693539401L; + } +} diff --git a/src/com/hypixel/hytale/component/spatial/SpatialData.java b/src/com/hypixel/hytale/component/spatial/SpatialData.java new file mode 100644 index 0000000..a23678f --- /dev/null +++ b/src/com/hypixel/hytale/component/spatial/SpatialData.java @@ -0,0 +1,146 @@ +package com.hypixel.hytale.component.spatial; + +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import it.unimi.dsi.fastutil.ints.IntArrays; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SpatialData { + public static final Vector3d[] EMPTY_VECTOR_ARRAY = new Vector3d[0]; + @Nonnull + private int[] indexes = ArrayUtil.EMPTY_INT_ARRAY; + @Nonnull + private long[] moroton = ArrayUtil.EMPTY_LONG_ARRAY; + private Vector3d[] vectors = EMPTY_VECTOR_ARRAY; + @Nonnull + private T[] data = (T[])ArrayUtil.emptyArray(); + private int size; + + public SpatialData() { + } + + public int size() { + return this.size; + } + + public int getSortedIndex(int i) { + return this.indexes[i]; + } + + @Nonnull + public Vector3d getVector(int i) { + return this.vectors[i]; + } + + @Nonnull + public T getData(int i) { + return this.data[i]; + } + + public void add(@Nonnull Vector3d vector, @Nonnull T value) { + Objects.requireNonNull(value); + if (this.vectors.length < this.size + 1) { + int newLength = ArrayUtil.grow(this.size); + this.indexes = Arrays.copyOf(this.indexes, newLength); + this.vectors = Arrays.copyOf(this.vectors, newLength); + this.data = (T[])Arrays.copyOf(this.data, newLength); + + for (int i = this.size; i < newLength; i++) { + this.vectors[i] = new Vector3d(); + } + } + + int index = this.size++; + this.indexes[index] = index; + this.vectors[index].assign(vector); + this.data[index] = value; + } + + public void addCapacity(int additionalSize) { + int newSize = this.size + additionalSize; + if (this.vectors.length < newSize) { + int newLength = ArrayUtil.grow(newSize); + this.indexes = Arrays.copyOf(this.indexes, newLength); + this.vectors = Arrays.copyOf(this.vectors, newLength); + this.data = (T[])Arrays.copyOf(this.data, newLength); + + for (int i = this.size; i < newLength; i++) { + this.vectors[i] = new Vector3d(); + } + } + } + + public void append(@Nonnull Vector3d vector, @Nonnull T value) { + Objects.requireNonNull(value); + int index = this.size++; + this.indexes[index] = index; + this.vectors[index].assign(vector); + this.data[index] = value; + } + + public void sort() { + IntArrays.quickSort(this.indexes, 0, this.size, (i1, i2) -> { + Vector3d v1 = this.vectors[i1]; + Vector3d v2 = this.vectors[i2]; + int xComp = Double.compare(v1.x, v2.x); + if (xComp != 0) { + return xComp; + } else { + int zComp = Double.compare(v1.z, v2.z); + return zComp != 0 ? zComp : Double.compare(v1.y, v2.y); + } + }); + } + + public void sortMorton() { + double minX = Double.POSITIVE_INFINITY; + double minY = Double.POSITIVE_INFINITY; + double minZ = Double.POSITIVE_INFINITY; + double maxX = Double.NEGATIVE_INFINITY; + double maxY = Double.NEGATIVE_INFINITY; + double maxZ = Double.NEGATIVE_INFINITY; + + for (int i = 0; i < this.size; i++) { + Vector3d v = this.vectors[i]; + if (v.x < minX) { + minX = v.x; + } + + if (v.y < minY) { + minY = v.y; + } + + if (v.z < minZ) { + minZ = v.z; + } + + if (v.x > maxX) { + maxX = v.x; + } + + if (v.y > maxY) { + maxY = v.y; + } + + if (v.z > maxZ) { + maxZ = v.z; + } + } + + this.moroton = this.moroton.length < this.size ? Arrays.copyOf(this.moroton, this.size) : this.moroton; + + for (int i = 0; i < this.size; i++) { + Vector3d vx = this.vectors[i]; + this.moroton[i] = Long.reverse(MortonCode.encode(vx.x, vx.y, vx.z, minX, minY, minZ, maxX, maxY, maxZ)); + } + + IntArrays.quickSort(this.indexes, 0, this.size, (i1, i2) -> Long.compare(this.moroton[i1], this.moroton[i2])); + } + + public void clear() { + Arrays.fill(this.data, 0, this.size, null); + this.size = 0; + } +} diff --git a/src/com/hypixel/hytale/component/spatial/SpatialResource.java b/src/com/hypixel/hytale/component/spatial/SpatialResource.java new file mode 100644 index 0000000..1c8b66d --- /dev/null +++ b/src/com/hypixel/hytale/component/spatial/SpatialResource.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.component.spatial; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Resource; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import javax.annotation.Nonnull; + +public class SpatialResource implements Resource { + @Nonnull + private static final ThreadLocal>> THREAD_LOCAL_REFERENCE_LIST = ThreadLocal.withInitial(ObjectArrayList::new); + @Nonnull + private final SpatialData> spatialData = new SpatialData<>(); + @Nonnull + private final SpatialStructure spatialStructure; + + @Nonnull + public static ObjectList> getThreadLocalReferenceList() { + ObjectList list = THREAD_LOCAL_REFERENCE_LIST.get(); + list.clear(); + return list; + } + + public SpatialResource(@Nonnull SpatialStructure spatialStructure) { + this.spatialStructure = spatialStructure; + } + + @Nonnull + public SpatialData> getSpatialData() { + return this.spatialData; + } + + @Nonnull + public SpatialStructure getSpatialStructure() { + return this.spatialStructure; + } + + @Override + public Resource clone() { + throw new UnsupportedOperationException("Not supported yet."); + } +} diff --git a/src/com/hypixel/hytale/component/spatial/SpatialStructure.java b/src/com/hypixel/hytale/component/spatial/SpatialStructure.java new file mode 100644 index 0000000..8c6a06a --- /dev/null +++ b/src/com/hypixel/hytale/component/spatial/SpatialStructure.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.component.spatial; + +import com.hypixel.hytale.math.vector.Vector3d; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface SpatialStructure { + int size(); + + void rebuild(@Nonnull SpatialData var1); + + @Nullable + T closest(@Nonnull Vector3d var1); + + void collect(@Nonnull Vector3d var1, double var2, @Nonnull List var4); + + void collectCylinder(@Nonnull Vector3d var1, double var2, double var4, @Nonnull List var6); + + void collectBox(@Nonnull Vector3d var1, @Nonnull Vector3d var2, @Nonnull List var3); + + void ordered(@Nonnull Vector3d var1, double var2, @Nonnull List var4); + + void ordered3DAxis(@Nonnull Vector3d var1, double var2, double var4, double var6, @Nonnull List var8); + + @Nonnull + String dump(); +} diff --git a/src/com/hypixel/hytale/component/spatial/SpatialSystem.java b/src/com/hypixel/hytale/component/spatial/SpatialSystem.java new file mode 100644 index 0000000..099fe57 --- /dev/null +++ b/src/com/hypixel/hytale/component/spatial/SpatialSystem.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.component.spatial; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.QuerySystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class SpatialSystem extends TickingSystem implements QuerySystem { + @Nonnull + private final ResourceType, ECS_TYPE>> resourceType; + + public SpatialSystem(@Nonnull ResourceType, ECS_TYPE>> resourceType) { + this.resourceType = resourceType; + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + SpatialResource, ECS_TYPE> spatialResource = store.getResource(this.resourceType); + SpatialData> spatialData = spatialResource.getSpatialData(); + spatialData.clear(); + store.forEachChunk(systemIndex, (archetypeChunk, commandBuffer) -> { + int size = archetypeChunk.size(); + spatialData.addCapacity(size); + + for (int index = 0; index < size; index++) { + Vector3d position = this.getPosition(archetypeChunk, index); + if (position != null) { + Ref ref = archetypeChunk.getReferenceTo(index); + spatialData.append(position, ref); + } + } + }); + spatialResource.getSpatialStructure().rebuild(spatialData); + } + + @Nullable + public abstract Vector3d getPosition(@Nonnull ArchetypeChunk var1, int var2); +} diff --git a/src/com/hypixel/hytale/component/system/ArchetypeChunkSystem.java b/src/com/hypixel/hytale/component/system/ArchetypeChunkSystem.java new file mode 100644 index 0000000..85343b0 --- /dev/null +++ b/src/com/hypixel/hytale/component/system/ArchetypeChunkSystem.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.component.system; + +import com.hypixel.hytale.component.ArchetypeChunk; + +public abstract class ArchetypeChunkSystem extends System implements QuerySystem { + public ArchetypeChunkSystem() { + } + + public abstract void onSystemAddedToArchetypeChunk(ArchetypeChunk var1); + + public abstract void onSystemRemovedFromArchetypeChunk(ArchetypeChunk var1); +} diff --git a/src/com/hypixel/hytale/component/system/CancellableEcsEvent.java b/src/com/hypixel/hytale/component/system/CancellableEcsEvent.java new file mode 100644 index 0000000..2119a92 --- /dev/null +++ b/src/com/hypixel/hytale/component/system/CancellableEcsEvent.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.component.system; + +public abstract class CancellableEcsEvent extends EcsEvent implements ICancellableEcsEvent { + private boolean cancelled = false; + + public CancellableEcsEvent() { + } + + @Override + public final boolean isCancelled() { + return this.cancelled; + } + + @Override + public final void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } +} diff --git a/src/com/hypixel/hytale/component/system/DelayedSystem.java b/src/com/hypixel/hytale/component/system/DelayedSystem.java new file mode 100644 index 0000000..dcff3d3 --- /dev/null +++ b/src/com/hypixel/hytale/component/system/DelayedSystem.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.component.system; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import javax.annotation.Nonnull; + +public abstract class DelayedSystem extends TickingSystem { + @Nonnull + private final ResourceType> resourceType = this.registerResource(DelayedSystem.Data.class, DelayedSystem.Data::new); + private final float intervalSec; + + public DelayedSystem(float intervalSec) { + this.intervalSec = intervalSec; + } + + @Nonnull + public ResourceType> getResourceType() { + return this.resourceType; + } + + public float getIntervalSec() { + return this.intervalSec; + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + DelayedSystem.Data data = store.getResource(this.resourceType); + data.dt += dt; + if (data.dt >= this.intervalSec) { + float fullDeltaTime = data.dt; + data.dt = 0.0F; + this.delayedTick(fullDeltaTime, systemIndex, store); + } + } + + public abstract void delayedTick(float var1, int var2, @Nonnull Store var3); + + private static class Data implements Resource { + private float dt; + + private Data() { + } + + @Nonnull + @Override + public Resource clone() { + DelayedSystem.Data data = new DelayedSystem.Data<>(); + data.dt = this.dt; + return data; + } + } +} diff --git a/src/com/hypixel/hytale/component/system/EcsEvent.java b/src/com/hypixel/hytale/component/system/EcsEvent.java new file mode 100644 index 0000000..4c63683 --- /dev/null +++ b/src/com/hypixel/hytale/component/system/EcsEvent.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.component.system; + +public abstract class EcsEvent { + public EcsEvent() { + } +} diff --git a/src/com/hypixel/hytale/component/system/EntityEventSystem.java b/src/com/hypixel/hytale/component/system/EntityEventSystem.java new file mode 100644 index 0000000..0b9ecdc --- /dev/null +++ b/src/com/hypixel/hytale/component/system/EntityEventSystem.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.component.system; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Store; +import javax.annotation.Nonnull; + +public abstract class EntityEventSystem extends EventSystem implements QuerySystem { + protected EntityEventSystem(@Nonnull Class eventType) { + super(eventType); + } + + public abstract void handle( + int var1, @Nonnull ArchetypeChunk var2, @Nonnull Store var3, @Nonnull CommandBuffer var4, @Nonnull EventType var5 + ); + + public void handleInternal( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull EventType event + ) { + if (this.shouldProcessEvent(event)) { + this.handle(index, archetypeChunk, store, commandBuffer, event); + } + } +} diff --git a/src/com/hypixel/hytale/component/system/EventSystem.java b/src/com/hypixel/hytale/component/system/EventSystem.java new file mode 100644 index 0000000..00a1250 --- /dev/null +++ b/src/com/hypixel/hytale/component/system/EventSystem.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.component.system; + +import javax.annotation.Nonnull; + +public abstract class EventSystem { + @Nonnull + private final Class eventType; + + protected EventSystem(@Nonnull Class eventType) { + this.eventType = eventType; + } + + protected boolean shouldProcessEvent(@Nonnull EventType event) { + return !(event instanceof ICancellableEcsEvent cancellable && cancellable.isCancelled()); + } + + @Nonnull + public Class getEventType() { + return this.eventType; + } +} diff --git a/src/com/hypixel/hytale/component/system/HolderSystem.java b/src/com/hypixel/hytale/component/system/HolderSystem.java new file mode 100644 index 0000000..20f5f24 --- /dev/null +++ b/src/com/hypixel/hytale/component/system/HolderSystem.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.component.system; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import javax.annotation.Nonnull; + +public abstract class HolderSystem extends System implements QuerySystem { + public HolderSystem() { + } + + public abstract void onEntityAdd(@Nonnull Holder var1, @Nonnull AddReason var2, @Nonnull Store var3); + + public abstract void onEntityRemoved(@Nonnull Holder var1, @Nonnull RemoveReason var2, @Nonnull Store var3); +} diff --git a/src/com/hypixel/hytale/component/system/ICancellableEcsEvent.java b/src/com/hypixel/hytale/component/system/ICancellableEcsEvent.java new file mode 100644 index 0000000..0232e15 --- /dev/null +++ b/src/com/hypixel/hytale/component/system/ICancellableEcsEvent.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.component.system; + +public interface ICancellableEcsEvent { + boolean isCancelled(); + + void setCancelled(boolean var1); +} diff --git a/src/com/hypixel/hytale/component/system/ISystem.java b/src/com/hypixel/hytale/component/system/ISystem.java new file mode 100644 index 0000000..bf6e814 --- /dev/null +++ b/src/com/hypixel/hytale/component/system/ISystem.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.component.system; + +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.DependencyGraph; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface ISystem { + ISystem[] EMPTY_ARRAY = new ISystem[0]; + + default void onSystemRegistered() { + } + + default void onSystemUnregistered() { + } + + @Nullable + default SystemGroup getGroup() { + return null; + } + + @Nonnull + default Set> getDependencies() { + return Collections.emptySet(); + } + + static void calculateOrder(@Nonnull ComponentRegistry registry, @Nonnull ISystem[] sortedSystems, int systemSize) { + DependencyGraph graph = new DependencyGraph<>(Arrays.copyOf(sortedSystems, systemSize)); + graph.resolveEdges(registry); + graph.sort(sortedSystems); + } +} diff --git a/src/com/hypixel/hytale/component/system/MetricSystem.java b/src/com/hypixel/hytale/component/system/MetricSystem.java new file mode 100644 index 0000000..57c42b2 --- /dev/null +++ b/src/com/hypixel/hytale/component/system/MetricSystem.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.component.system; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.metrics.MetricResults; + +public interface MetricSystem { + MetricResults toMetricResults(Store var1); +} diff --git a/src/com/hypixel/hytale/component/system/QuerySystem.java b/src/com/hypixel/hytale/component/system/QuerySystem.java new file mode 100644 index 0000000..1582566 --- /dev/null +++ b/src/com/hypixel/hytale/component/system/QuerySystem.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.component.system; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.query.Query; +import javax.annotation.Nullable; + +public interface QuerySystem extends ISystem { + default boolean test(ComponentRegistry componentRegistry, Archetype archetype) { + return this.getQuery().test(archetype); + } + + @Nullable + Query getQuery(); +} diff --git a/src/com/hypixel/hytale/component/system/RefChangeSystem.java b/src/com/hypixel/hytale/component/system/RefChangeSystem.java new file mode 100644 index 0000000..cd214d3 --- /dev/null +++ b/src/com/hypixel/hytale/component/system/RefChangeSystem.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.component.system; + +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class RefChangeSystem> extends System implements QuerySystem { + public RefChangeSystem() { + } + + @Nonnull + public abstract ComponentType componentType(); + + public abstract void onComponentAdded(@Nonnull Ref var1, @Nonnull T var2, @Nonnull Store var3, @Nonnull CommandBuffer var4); + + public abstract void onComponentSet( + @Nonnull Ref var1, @Nullable T var2, @Nonnull T var3, @Nonnull Store var4, @Nonnull CommandBuffer var5 + ); + + public abstract void onComponentRemoved(@Nonnull Ref var1, @Nonnull T var2, @Nonnull Store var3, @Nonnull CommandBuffer var4); +} diff --git a/src/com/hypixel/hytale/component/system/RefSystem.java b/src/com/hypixel/hytale/component/system/RefSystem.java new file mode 100644 index 0000000..58fa34f --- /dev/null +++ b/src/com/hypixel/hytale/component/system/RefSystem.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.component.system; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import javax.annotation.Nonnull; + +public abstract class RefSystem extends System implements QuerySystem { + public RefSystem() { + } + + public abstract void onEntityAdded( + @Nonnull Ref var1, @Nonnull AddReason var2, @Nonnull Store var3, @Nonnull CommandBuffer var4 + ); + + public abstract void onEntityRemove( + @Nonnull Ref var1, @Nonnull RemoveReason var2, @Nonnull Store var3, @Nonnull CommandBuffer var4 + ); +} diff --git a/src/com/hypixel/hytale/component/system/StoreSystem.java b/src/com/hypixel/hytale/component/system/StoreSystem.java new file mode 100644 index 0000000..681e96c --- /dev/null +++ b/src/com/hypixel/hytale/component/system/StoreSystem.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.component.system; + +import com.hypixel.hytale.component.Store; +import javax.annotation.Nonnull; + +public abstract class StoreSystem extends System { + public StoreSystem() { + } + + public abstract void onSystemAddedToStore(@Nonnull Store var1); + + public abstract void onSystemRemovedFromStore(@Nonnull Store var1); +} diff --git a/src/com/hypixel/hytale/component/system/System.java b/src/com/hypixel/hytale/component/system/System.java new file mode 100644 index 0000000..1af1842 --- /dev/null +++ b/src/com/hypixel/hytale/component/system/System.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.component.system; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentRegistration; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceRegistration; +import com.hypixel.hytale.component.ResourceType; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.List; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class System implements ISystem { + @Nonnull + private final ObjectList> componentRegistrations = new ObjectArrayList<>(); + @Nonnull + private final ObjectList> resourceRegistrations = new ObjectArrayList<>(); + + public System() { + } + + @Nonnull + protected > ComponentType registerComponent(@Nonnull Class tClass, @Nonnull Supplier supplier) { + return this.registerComponent(tClass, null, null, supplier); + } + + @Nonnull + protected > ComponentType registerComponent( + @Nonnull Class tClass, @Nonnull String id, @Nonnull BuilderCodec codec + ) { + return this.registerComponent(tClass, id, codec, codec::getDefaultValue); + } + + @Nonnull + protected > ComponentType registerComponent( + @Nonnull Class tClass, @Nullable String id, @Nullable BuilderCodec codec, @Nonnull Supplier supplier + ) { + ComponentType componentType = new ComponentType<>(); + this.componentRegistrations.add(new ComponentRegistration<>(tClass, id, codec, supplier, componentType)); + return componentType; + } + + @Nonnull + public > ResourceType registerResource(@Nonnull Class tClass, @Nonnull Supplier supplier) { + return this.registerResource(tClass, null, null, supplier); + } + + @Nonnull + public > ResourceType registerResource( + @Nonnull Class tClass, @Nonnull String id, @Nonnull BuilderCodec codec + ) { + return this.registerResource(tClass, id, codec, codec::getDefaultValue); + } + + @Nonnull + private > ResourceType registerResource( + @Nonnull Class tClass, @Nullable String id, @Nullable BuilderCodec codec, @Nonnull Supplier supplier + ) { + ResourceType componentType = new ResourceType<>(); + this.resourceRegistrations.add(new ResourceRegistration<>(tClass, id, codec, supplier, componentType)); + return componentType; + } + + @Nonnull + public List> getComponentRegistrations() { + return this.componentRegistrations; + } + + @Nonnull + public List> getResourceRegistrations() { + return this.resourceRegistrations; + } +} diff --git a/src/com/hypixel/hytale/component/system/WorldEventSystem.java b/src/com/hypixel/hytale/component/system/WorldEventSystem.java new file mode 100644 index 0000000..22269db --- /dev/null +++ b/src/com/hypixel/hytale/component/system/WorldEventSystem.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.component.system; + +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Store; +import javax.annotation.Nonnull; + +public abstract class WorldEventSystem extends EventSystem implements ISystem { + protected WorldEventSystem(@Nonnull Class eventType) { + super(eventType); + } + + public abstract void handle(@Nonnull Store var1, @Nonnull CommandBuffer var2, @Nonnull EventType var3); + + public void handleInternal(@Nonnull Store store, @Nonnull CommandBuffer commandBuffer, @Nonnull EventType event) { + if (this.shouldProcessEvent(event)) { + this.handle(store, commandBuffer, event); + } + } +} diff --git a/src/com/hypixel/hytale/component/system/data/ArchetypeDataSystem.java b/src/com/hypixel/hytale/component/system/data/ArchetypeDataSystem.java new file mode 100644 index 0000000..526d75e --- /dev/null +++ b/src/com/hypixel/hytale/component/system/data/ArchetypeDataSystem.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.component.system.data; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.QuerySystem; +import com.hypixel.hytale.component.system.System; +import java.util.List; + +public abstract class ArchetypeDataSystem extends System implements QuerySystem { + public ArchetypeDataSystem() { + } + + public abstract void fetch(ArchetypeChunk var1, Store var2, CommandBuffer var3, Q var4, List var5); +} diff --git a/src/com/hypixel/hytale/component/system/data/EntityDataSystem.java b/src/com/hypixel/hytale/component/system/data/EntityDataSystem.java new file mode 100644 index 0000000..a211b49 --- /dev/null +++ b/src/com/hypixel/hytale/component/system/data/EntityDataSystem.java @@ -0,0 +1,135 @@ +package com.hypixel.hytale.component.system.data; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.task.ParallelRangeTask; +import com.hypixel.hytale.component.task.ParallelTask; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.function.IntConsumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class EntityDataSystem extends ArchetypeDataSystem { + public EntityDataSystem() { + } + + public boolean isParallel() { + return false; + } + + @Override + public void fetch( + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + Q query, + List results + ) { + doFetch(this, archetypeChunk, store, commandBuffer, query, results); + } + + public abstract void fetch(int var1, ArchetypeChunk var2, Store var3, CommandBuffer var4, Q var5, List var6); + + public static void doFetch( + @Nonnull EntityDataSystem system, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + Q query, + List results + ) { + if (system.isParallel()) { + int size = archetypeChunk.size(); + if (size == 0) { + return; + } + + ParallelTask> task = store.getFetchTask(); + ParallelRangeTask> systemTask = task.appendTask(); + systemTask.init(0, size); + int i = 0; + + for (int systemTaskSize = systemTask.size(); i < systemTaskSize; i++) { + ((EntityDataSystem.SystemTaskData)systemTask.get(i)).init(system, archetypeChunk, store, commandBuffer.fork(), query); + } + } else { + int index = 0; + + for (int archetypeChunkSize = archetypeChunk.size(); index < archetypeChunkSize; index++) { + system.fetch(index, archetypeChunk, store, commandBuffer, query, results); + } + } + } + + public static class SystemTaskData implements IntConsumer { + private final List results = new ObjectArrayList<>(); + @Nullable + private EntityDataSystem system; + @Nullable + private ArchetypeChunk archetypeChunk; + @Nullable + private Store store; + @Nullable + private CommandBuffer commandBuffer; + @Nullable + private Q query; + + public SystemTaskData() { + } + + public void init( + EntityDataSystem system, + ArchetypeChunk archetypeChunk, + Store store, + CommandBuffer commandBuffer, + Q query + ) { + this.system = system; + this.archetypeChunk = archetypeChunk; + this.store = store; + this.commandBuffer = commandBuffer; + this.query = query; + } + + @Override + public void accept(int index) { + assert this.commandBuffer.setThread(); + + this.system.fetch(index, this.archetypeChunk, this.store, this.commandBuffer, this.query, this.results); + } + + public void clear() { + this.system = null; + this.archetypeChunk = null; + this.store = null; + this.commandBuffer = null; + this.query = null; + this.results.clear(); + } + + public static void invokeParallelTask( + @Nonnull ParallelTask> parallelTask, + @Nonnull CommandBuffer commandBuffer, + @Nonnull List results + ) { + int parallelTaskSize = parallelTask.size(); + if (parallelTaskSize > 0) { + parallelTask.doInvoke(); + + for (int x = 0; x < parallelTaskSize; x++) { + ParallelRangeTask> systemTask = parallelTask.get(x); + int i = 0; + + for (int systemTaskSize = systemTask.size(); i < systemTaskSize; i++) { + EntityDataSystem.SystemTaskData taskData = systemTask.get(i); + results.addAll(taskData.results); + taskData.commandBuffer.mergeParallel(commandBuffer); + taskData.clear(); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/component/system/tick/ArchetypeTickingSystem.java b/src/com/hypixel/hytale/component/system/tick/ArchetypeTickingSystem.java new file mode 100644 index 0000000..46a989d --- /dev/null +++ b/src/com/hypixel/hytale/component/system/tick/ArchetypeTickingSystem.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.component.system.tick; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.QuerySystem; +import javax.annotation.Nonnull; + +public abstract class ArchetypeTickingSystem extends TickingSystem implements QuerySystem { + public ArchetypeTickingSystem() { + } + + @Override + public boolean test(@Nonnull ComponentRegistry componentRegistry, @Nonnull Archetype archetype) { + return !this.isExplicitQuery() && componentRegistry.getNonTickingComponentType().test(archetype) ? false : this.getQuery().test(archetype); + } + + public boolean isExplicitQuery() { + return false; + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + store.tick(this, dt, systemIndex); + } + + public abstract void tick(float var1, @Nonnull ArchetypeChunk var2, @Nonnull Store var3, @Nonnull CommandBuffer var4); +} diff --git a/src/com/hypixel/hytale/component/system/tick/DelayedEntitySystem.java b/src/com/hypixel/hytale/component/system/tick/DelayedEntitySystem.java new file mode 100644 index 0000000..fbd4c61 --- /dev/null +++ b/src/com/hypixel/hytale/component/system/tick/DelayedEntitySystem.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.component.system.tick; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import javax.annotation.Nonnull; + +public abstract class DelayedEntitySystem extends EntityTickingSystem { + private final ResourceType> resourceType = this.registerResource( + DelayedEntitySystem.Data.class, DelayedEntitySystem.Data::new + ); + private final float intervalSec; + + public DelayedEntitySystem(float intervalSec) { + this.intervalSec = intervalSec; + } + + @Nonnull + public ResourceType> getResourceType() { + return this.resourceType; + } + + public float getIntervalSec() { + return this.intervalSec; + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + DelayedEntitySystem.Data data = store.getResource(this.resourceType); + data.dt += dt; + if (data.dt >= this.intervalSec) { + float fullDt = data.dt; + data.dt = 0.0F; + super.tick(fullDt, systemIndex, store); + } + } + + private static class Data implements Resource { + private float dt; + + private Data() { + } + + @Nonnull + @Override + public Resource clone() { + DelayedEntitySystem.Data data = new DelayedEntitySystem.Data<>(); + data.dt = this.dt; + return data; + } + } +} diff --git a/src/com/hypixel/hytale/component/system/tick/EntityTickingSystem.java b/src/com/hypixel/hytale/component/system/tick/EntityTickingSystem.java new file mode 100644 index 0000000..d485637 --- /dev/null +++ b/src/com/hypixel/hytale/component/system/tick/EntityTickingSystem.java @@ -0,0 +1,119 @@ +package com.hypixel.hytale.component.system.tick; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.task.ParallelRangeTask; +import com.hypixel.hytale.component.task.ParallelTask; +import java.util.function.IntConsumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class EntityTickingSystem extends ArchetypeTickingSystem { + public EntityTickingSystem() { + } + + protected static boolean maybeUseParallel(int archetypeChunkSize, int taskCount) { + return false; + } + + protected static boolean useParallel(int archetypeChunkSize, int taskCount) { + return taskCount > 0 || archetypeChunkSize > ParallelRangeTask.PARALLELISM; + } + + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return false; + } + + @Override + public void tick(float dt, @Nonnull ArchetypeChunk archetypeChunk, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer) { + doTick(this, dt, archetypeChunk, store, commandBuffer); + } + + public abstract void tick(float var1, int var2, @Nonnull ArchetypeChunk var3, @Nonnull Store var4, @Nonnull CommandBuffer var5); + + public static void doTick( + @Nonnull EntityTickingSystem system, + float dt, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + int archetypeChunkSize = archetypeChunk.size(); + if (archetypeChunkSize != 0) { + ParallelTask> task = store.getParallelTask(); + if (system.isParallel(archetypeChunkSize, task.size())) { + ParallelRangeTask> systemTask = task.appendTask(); + systemTask.init(0, archetypeChunkSize); + int i = 0; + + for (int systemTaskSize = systemTask.size(); i < systemTaskSize; i++) { + systemTask.get(i).init(system, dt, archetypeChunk, store, commandBuffer.fork()); + } + } else { + for (int index = 0; index < archetypeChunkSize; index++) { + system.tick(dt, index, archetypeChunk, store, commandBuffer); + } + } + } + } + + public static class SystemTaskData implements IntConsumer { + @Nullable + private EntityTickingSystem system; + private float dt; + @Nullable + private ArchetypeChunk archetypeChunk; + @Nullable + private Store store; + @Nullable + private CommandBuffer commandBuffer; + + public SystemTaskData() { + } + + public void init( + EntityTickingSystem system, float dt, ArchetypeChunk archetypeChunk, Store store, CommandBuffer commandBuffer + ) { + this.system = system; + this.dt = dt; + this.archetypeChunk = archetypeChunk; + this.store = store; + this.commandBuffer = commandBuffer; + } + + @Override + public void accept(int index) { + assert this.commandBuffer.setThread(); + + this.system.tick(this.dt, index, this.archetypeChunk, this.store, this.commandBuffer); + } + + public void clear() { + this.system = null; + this.archetypeChunk = null; + this.store = null; + this.commandBuffer = null; + } + + public static void invokeParallelTask( + @Nonnull ParallelTask> parallelTask, @Nonnull CommandBuffer commandBuffer + ) { + int parallelTaskSize = parallelTask.size(); + if (parallelTaskSize > 0) { + parallelTask.doInvoke(); + + for (int x = 0; x < parallelTaskSize; x++) { + ParallelRangeTask> systemTask = parallelTask.get(x); + int i = 0; + + for (int systemTaskSize = systemTask.size(); i < systemTaskSize; i++) { + EntityTickingSystem.SystemTaskData taskData = systemTask.get(i); + taskData.commandBuffer.mergeParallel(commandBuffer); + taskData.clear(); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/component/system/tick/RunWhenPausedSystem.java b/src/com/hypixel/hytale/component/system/tick/RunWhenPausedSystem.java new file mode 100644 index 0000000..120a108 --- /dev/null +++ b/src/com/hypixel/hytale/component/system/tick/RunWhenPausedSystem.java @@ -0,0 +1,4 @@ +package com.hypixel.hytale.component.system.tick; + +public interface RunWhenPausedSystem extends TickableSystem { +} diff --git a/src/com/hypixel/hytale/component/system/tick/TickableSystem.java b/src/com/hypixel/hytale/component/system/tick/TickableSystem.java new file mode 100644 index 0000000..4ff3ae3 --- /dev/null +++ b/src/com/hypixel/hytale/component/system/tick/TickableSystem.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.component.system.tick; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.ISystem; +import javax.annotation.Nonnull; + +public interface TickableSystem extends ISystem { + void tick(float var1, int var2, @Nonnull Store var3); +} diff --git a/src/com/hypixel/hytale/component/system/tick/TickingSystem.java b/src/com/hypixel/hytale/component/system/tick/TickingSystem.java new file mode 100644 index 0000000..0013b7a --- /dev/null +++ b/src/com/hypixel/hytale/component/system/tick/TickingSystem.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.component.system.tick; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.System; +import javax.annotation.Nonnull; + +public abstract class TickingSystem extends System implements TickableSystem { + public TickingSystem() { + } + + @Override + public abstract void tick(float var1, int var2, @Nonnull Store var3); +} diff --git a/src/com/hypixel/hytale/component/task/ParallelRangeTask.java b/src/com/hypixel/hytale/component/task/ParallelRangeTask.java new file mode 100644 index 0000000..6382c11 --- /dev/null +++ b/src/com/hypixel/hytale/component/task/ParallelRangeTask.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.component.task; + +import java.util.concurrent.CountedCompleter; +import java.util.concurrent.ForkJoinPool; +import java.util.function.IntConsumer; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class ParallelRangeTask extends CountedCompleter { + public static final int PARALLELISM = Math.max(ForkJoinPool.getCommonPoolParallelism(), 1); + public static final int TASK_COUNT = Math.max(ForkJoinPool.getCommonPoolParallelism() << 2, 1); + @Nonnull + private final ParallelRangeTask.SubTask[] subTasks = new ParallelRangeTask.SubTask[TASK_COUNT]; + private int size; + public volatile boolean running; + + public ParallelRangeTask(@Nonnull Supplier supplier) { + this(null, supplier); + } + + public ParallelRangeTask(CountedCompleter completer, @Nonnull Supplier supplier) { + super(completer); + + for (int i = 0; i < this.subTasks.length; i++) { + this.subTasks[i] = new ParallelRangeTask.SubTask<>(this, supplier.get()); + } + } + + @Override + public void reinitialize() { + if (this.running) { + throw new IllegalStateException("ParallelRangeTask has already been started"); + } else { + super.reinitialize(); + } + } + + @Nonnull + public ParallelRangeTask init(int from, int to) { + this.reinitialize(); + int perTask = Math.max((to - from + (this.subTasks.length - 1)) / this.subTasks.length, 1); + + for (this.size = 0; this.size < this.subTasks.length && from < to; this.size++) { + int next = Math.min(from + perTask, to); + this.subTasks[this.size].init(from, next); + from = next; + } + + if (from < to) { + throw new IllegalStateException("Failed to distribute the whole range to tasks!"); + } else { + return this; + } + } + + public int size() { + return this.size; + } + + public D get(int i) { + return this.subTasks[i].getData(); + } + + public void set(int i, D data) { + if (this.running) { + throw new IllegalStateException("ParallelRangeTask has already been started"); + } else { + this.subTasks[i].setData(data); + } + } + + @Override + public void compute() { + this.setPendingCount(this.size - 1); + + for (int i = 0; i < this.size - 1; i++) { + this.subTasks[i].fork(); + } + + this.subTasks[this.size - 1].compute(); + } + + static class SubTask extends CountedCompleter { + private int from; + private int to; + private D data; + + SubTask(ParallelRangeTask parent, D data) { + super(parent); + this.data = data; + } + + void init(int from, int to) { + this.reinitialize(); + this.from = from; + this.to = to; + } + + D getData() { + return this.data; + } + + void setData(D data) { + this.data = data; + } + + @Override + public void compute() { + for (int i = this.from; i < this.to; i++) { + this.data.accept(i); + } + + this.propagateCompletion(); + } + } +} diff --git a/src/com/hypixel/hytale/component/task/ParallelTask.java b/src/com/hypixel/hytale/component/task/ParallelTask.java new file mode 100644 index 0000000..f0020a8 --- /dev/null +++ b/src/com/hypixel/hytale/component/task/ParallelTask.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.component.task; + +import com.hypixel.hytale.common.util.ArrayUtil; +import java.util.Arrays; +import java.util.concurrent.CountedCompleter; +import java.util.function.IntConsumer; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class ParallelTask extends CountedCompleter { + private final Supplier supplier; + @Nonnull + private ParallelRangeTask[] subTasks = new ParallelRangeTask[0]; + private int size; + private volatile boolean running; + + public ParallelTask(Supplier supplier) { + this(null, supplier); + } + + public ParallelTask(CountedCompleter completer, Supplier supplier) { + super(completer); + this.supplier = supplier; + } + + @Override + public void reinitialize() { + if (this.running) { + throw new IllegalStateException("Parallel task has already been started"); + } else { + super.reinitialize(); + } + } + + public void init() { + this.reinitialize(); + this.size = 0; + } + + public ParallelRangeTask appendTask() { + if (this.running) { + throw new IllegalStateException("Parallel task has already been started"); + } else { + if (this.subTasks.length <= this.size) { + this.subTasks = Arrays.copyOf(this.subTasks, ArrayUtil.grow(this.size)); + + for (int i = this.size; i < this.subTasks.length; i++) { + this.subTasks[i] = new ParallelRangeTask<>(this, this.supplier); + } + } + + return this.subTasks[this.size++]; + } + } + + public int size() { + return this.size; + } + + public ParallelRangeTask get(int i) { + return this.subTasks[i]; + } + + @Override + public void compute() { + this.setPendingCount(this.size - 1); + + for (int i = 0; i < this.size - 1; i++) { + this.subTasks[i].fork(); + } + + this.subTasks[this.size - 1].compute(); + } + + public void doInvoke() { + this.running = true; + + for (int i = 0; i < this.size; i++) { + this.subTasks[i].running = true; + } + + this.invoke(); + + for (int i = 0; i < this.size; i++) { + this.subTasks[i].running = false; + } + + this.running = false; + } +} diff --git a/src/com/hypixel/hytale/event/AsyncEventBusRegistry.java b/src/com/hypixel/hytale/event/AsyncEventBusRegistry.java new file mode 100644 index 0000000..29818ce --- /dev/null +++ b/src/com/hypixel/hytale/event/AsyncEventBusRegistry.java @@ -0,0 +1,282 @@ +package com.hypixel.hytale.event; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AsyncEventBusRegistry> + extends EventBusRegistry> { + @Nonnull + public static final IEventDispatcher NO_OP = new IEventDispatcher>() { + @Override + public boolean hasListener() { + return false; + } + + @Nonnull + public CompletableFuture dispatch(IAsyncEvent event) { + return CompletableFuture.completedFuture(event); + } + }; + @Nonnull + private final IEventDispatcher> globalDispatcher = event -> { + CompletableFuture future = CompletableFuture.completedFuture(event); + future = this.dispatchGlobal(future); + if (future == future) { + future = this.dispatchUnhandled(future); + } + + return future; + }; + + public AsyncEventBusRegistry(@Nonnull HytaleLogger logger, @Nonnull Class eventClass) { + super(logger, eventClass, new AsyncEventBusRegistry.AsyncEventConsumerMap<>(null), new AsyncEventBusRegistry.AsyncEventConsumerMap<>(null)); + this.global.registry = this.unhandled.registry = this; + } + + @Nonnull + public EventRegistration registerAsync( + short priority, @Nonnull KeyType key, @Nonnull Function, CompletableFuture> function + ) { + return this.registerAsync0(priority, key, function, function.toString()); + } + + @Nonnull + private EventRegistration registerAsync0( + short priority, + @Nullable KeyType key, + @Nonnull Function, CompletableFuture> function, + @Nonnull String consumerString + ) { + if (this.shutdown) { + throw new IllegalArgumentException("EventRegistry is shutdown!"); + } else { + KeyType k = (KeyType)(key != null ? key : NULL); + AsyncEventBusRegistry.AsyncEventConsumerMap eventMap = this.map + .computeIfAbsent(k, o -> new AsyncEventBusRegistry.AsyncEventConsumerMap<>(this)); + AsyncEventBusRegistry.AsyncEventConsumer eventConsumer = new AsyncEventBusRegistry.AsyncEventConsumer<>(priority, consumerString, function); + eventMap.add(eventConsumer); + return new EventRegistration<>(this.eventClass, this::isAlive, () -> this.unregister(key, eventConsumer)); + } + } + + private void unregister(@Nullable KeyType key, @Nonnull AsyncEventBusRegistry.AsyncEventConsumer consumer) { + if (this.shutdown) { + throw new IllegalArgumentException("EventRegistry is shutdown!"); + } else { + KeyType k = (KeyType)(key != null ? key : NULL); + AsyncEventBusRegistry.AsyncEventConsumerMap eventMap = this.map.get(k); + if (eventMap != null && !eventMap.remove(consumer)) { + throw new IllegalArgumentException(String.valueOf(consumer)); + } + } + } + + @Nonnull + public EventRegistration registerAsyncGlobal( + short priority, @Nonnull Function, CompletableFuture> function + ) { + return this.registerAsyncGlobal0(priority, function, function.toString()); + } + + @Nonnull + private EventRegistration registerAsyncGlobal0( + short priority, @Nonnull Function, CompletableFuture> function, @Nonnull String consumerString + ) { + if (this.shutdown) { + throw new IllegalArgumentException("EventRegistry is shutdown!"); + } else { + AsyncEventBusRegistry.AsyncEventConsumer eventConsumer = new AsyncEventBusRegistry.AsyncEventConsumer<>(priority, consumerString, function); + this.global.add(eventConsumer); + return new EventRegistration<>(this.eventClass, this::isAlive, () -> this.unregisterGlobal(eventConsumer)); + } + } + + private void unregisterGlobal(@Nonnull AsyncEventBusRegistry.AsyncEventConsumer consumer) { + if (this.shutdown) { + throw new IllegalArgumentException("EventRegistry is shutdown!"); + } else if (!this.global.remove(consumer)) { + throw new IllegalArgumentException(String.valueOf(consumer)); + } + } + + @Nonnull + public EventRegistration registerAsyncUnhandled( + short priority, @Nonnull Function, CompletableFuture> function + ) { + return this.registerAsyncUnhandled0(priority, function, function.toString()); + } + + @Nonnull + private EventRegistration registerAsyncUnhandled0( + short priority, @Nonnull Function, CompletableFuture> function, @Nonnull String consumerString + ) { + if (this.shutdown) { + throw new IllegalArgumentException("EventRegistry is shutdown!"); + } else { + AsyncEventBusRegistry.AsyncEventConsumer eventConsumer = new AsyncEventBusRegistry.AsyncEventConsumer<>(priority, consumerString, function); + this.unhandled.add(eventConsumer); + return new EventRegistration<>(this.eventClass, this::isAlive, () -> this.unregisterUnhandled(eventConsumer)); + } + } + + private void unregisterUnhandled(@Nonnull AsyncEventBusRegistry.AsyncEventConsumer consumer) { + if (this.shutdown) { + throw new IllegalArgumentException("EventRegistry is shutdown!"); + } else if (!this.unhandled.remove(consumer)) { + throw new IllegalArgumentException(String.valueOf(consumer)); + } + } + + private CompletableFuture dispatchGlobal(@Nonnull CompletableFuture future) { + return this.dispatchEventMap(future, this.global, "Failed to dispatch event (global)"); + } + + private CompletableFuture dispatchUnhandled(@Nonnull CompletableFuture future) { + return this.dispatchEventMap(future, this.unhandled, "Failed to dispatch event (unhandled)"); + } + + private CompletableFuture dispatchEventMap( + @Nonnull CompletableFuture future, @Nonnull AsyncEventBusRegistry.AsyncEventConsumerMap eventMap, @Nonnull String s + ) { + for (short priority : eventMap.getPriorities()) { + List> consumers = eventMap.get(priority); + if (consumers != null) { + for (AsyncEventBusRegistry.AsyncEventConsumer consumer : consumers) { + try { + Function, CompletableFuture> theConsumer = this.timeEvents + ? consumer.getTimedFunction() + : consumer.getFunction(); + future = theConsumer.apply(future).whenComplete((event, throwable) -> { + if (event instanceof IProcessedEvent processedEvent) { + processedEvent.processEvent(consumer.getConsumerString()); + } + + if (throwable != null) { + this.logger.at(Level.SEVERE).withCause(throwable).log("%s %s to %s", s, event, consumer); + } + }); + } catch (Throwable var12) { + this.logger.at(Level.SEVERE).withCause(var12).log("%s %s to %s", s, future, consumer); + } + } + } + } + + return future; + } + + @Nonnull + @Override + public EventRegistration register(short priority, KeyType key, @Nonnull Consumer consumer) { + return this.registerAsync0(priority, key, f -> f.thenApply(e -> { + consumer.accept((EventType)e); + return (EventType)e; + }), consumer.toString()); + } + + @Nonnull + @Override + public EventRegistration registerGlobal(short priority, @Nonnull Consumer consumer) { + return this.registerAsyncGlobal0(priority, f -> f.thenApply(e -> { + consumer.accept((EventType)e); + return (EventType)e; + }), consumer.toString()); + } + + @Nonnull + @Override + public EventRegistration registerUnhandled(short priority, @Nonnull Consumer consumer) { + return this.registerAsyncUnhandled0(priority, f -> f.thenApply(e -> { + consumer.accept((EventType)e); + return (EventType)e; + }), consumer.toString()); + } + + @Nonnull + @Override + public IEventDispatcher> dispatchFor(@Nullable KeyType key) { + if (this.shutdown) { + throw new IllegalArgumentException("EventRegistry is shutdown!"); + } else { + KeyType k = (KeyType)(key != null ? key : NULL); + AsyncEventBusRegistry.AsyncEventConsumerMap eventMap = this.map.get(k); + if (eventMap != null && !eventMap.isEmpty()) { + return eventMap; + } else { + return this.global.isEmpty() && this.unhandled.isEmpty() ? NO_OP : this.globalDispatcher; + } + } + } + + protected static class AsyncEventConsumer extends EventBusRegistry.EventConsumer { + @Nonnull + private final Function, CompletableFuture> function; + @Nonnull + private final Function, CompletableFuture> timedFunction; + + public AsyncEventConsumer( + short priority, @Nonnull String consumerString, @Nonnull Function, CompletableFuture> function + ) { + super(priority, consumerString); + this.function = function; + this.timedFunction = f -> { + long before = System.nanoTime(); + return function.apply(f).whenComplete((eventType, throwable) -> { + long after = System.nanoTime(); + this.timer.add(after - before); + if (throwable != null) { + throw SneakyThrow.sneakyThrow(throwable); + } + }); + }; + } + + @Nonnull + public Function, CompletableFuture> getFunction() { + return this.function; + } + + @Nonnull + public Function, CompletableFuture> getTimedFunction() { + return this.timedFunction; + } + + @Nonnull + @Override + public String toString() { + return "AsyncEventConsumer{function=" + this.function + ", timedFunction=" + this.timedFunction + "} " + super.toString(); + } + } + + protected static class AsyncEventConsumerMap + extends EventBusRegistry.EventConsumerMap, CompletableFuture> { + protected AsyncEventBusRegistry registry; + + public AsyncEventConsumerMap(AsyncEventBusRegistry registry) { + this.registry = registry; + } + + @Nonnull + public CompletableFuture dispatch(EventType event) { + return CompletableFuture.completedFuture(event).thenComposeAsync(this::dispatch0); + } + + private CompletableFuture dispatch0(EventType event) { + CompletableFuture future = CompletableFuture.completedFuture(event); + future = this.registry.dispatchEventMap(future, this, "Failed to dispatch event"); + future = this.registry.dispatchGlobal(future); + if (future == future && future == future) { + future = this.registry.dispatchUnhandled(future); + } + + return future; + } + } +} diff --git a/src/com/hypixel/hytale/event/EventBus.java b/src/com/hypixel/hytale/event/EventBus.java new file mode 100644 index 0000000..162b0f7 --- /dev/null +++ b/src/com/hypixel/hytale/event/EventBus.java @@ -0,0 +1,288 @@ +package com.hypixel.hytale.event; + +import com.hypixel.hytale.logger.HytaleLogger; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EventBus implements IEventBus { + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private final Map>, EventBusRegistry> registryMap = new ConcurrentHashMap<>(); + private final boolean timeEvents; + + public EventBus(boolean timeEvents) { + this.timeEvents = timeEvents; + } + + public void shutdown() { + this.registryMap.values().forEach(EventBusRegistry::shutdown); + } + + @Nonnull + public Set>> getRegisteredEventClasses() { + return new HashSet<>(this.registryMap.keySet()); + } + + @Nonnull + public Set getRegisteredEventClassNames() { + Set classNames = new HashSet<>(); + + for (Class aClass : this.registryMap.keySet()) { + classNames.add(aClass.getSimpleName()); + } + + return classNames; + } + + @Nullable + public EventBusRegistry getRegistry(@Nonnull String eventName) { + Class eventClass = null; + + for (Class> aClass : this.registryMap.keySet()) { + if (aClass.getSimpleName().equalsIgnoreCase(eventName) || aClass.getName().equalsIgnoreCase(eventName)) { + eventClass = aClass; + } + } + + return eventClass == null ? null : this.getRegistry(eventClass); + } + + @Nonnull + public > EventBusRegistry getRegistry(@Nonnull Class eventClass) { + return (EventBusRegistry)(IAsyncEvent.class.isAssignableFrom(eventClass) + ? this.getAsyncRegistry(eventClass) + : this.getSyncRegistry(eventClass)); + } + + @Nonnull + public > EventBusRegistry getSyncRegistry(@Nonnull Class eventClass) { + EventBusRegistry, ? extends EventBusRegistry.EventConsumerMap, ?, ?>> registry = (EventBusRegistry, ? extends EventBusRegistry.EventConsumerMap, ?, ?>>)this.registryMap + .computeIfAbsent((Class>)eventClass, aClass -> new SyncEventBusRegistry(LOGGER, (Class)aClass)); + if (this.timeEvents) { + registry.setTimeEvents(true); + } + + return (EventBusRegistry)registry; + } + + @Nonnull + private > AsyncEventBusRegistry getAsyncRegistry( + @Nonnull Class eventClass + ) { + EventBusRegistry, ? extends EventBusRegistry.EventConsumerMap, ?, ?>> registry = (EventBusRegistry, ? extends EventBusRegistry.EventConsumerMap, ?, ?>>)this.registryMap + .computeIfAbsent((Class>)eventClass, aClass -> new AsyncEventBusRegistry(LOGGER, (Class)aClass)); + if (this.timeEvents) { + registry.setTimeEvents(true); + } + + return (AsyncEventBusRegistry)registry; + } + + @Override + public > EventRegistration register( + @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + return this.register((short)0, eventClass, null, consumer); + } + + @Override + public > EventRegistration register( + @Nonnull EventPriority priority, @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + return this.register(priority.getValue(), eventClass, null, consumer); + } + + @Override + public > EventRegistration register( + short priority, @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + return this.register(priority, eventClass, null, consumer); + } + + @Override + public > EventRegistration register( + @Nonnull Class eventClass, @Nonnull KeyType key, @Nonnull Consumer consumer + ) { + return this.register((short)0, eventClass, key, consumer); + } + + @Override + public > EventRegistration register( + @Nonnull EventPriority priority, @Nonnull Class eventClass, @Nonnull KeyType key, @Nonnull Consumer consumer + ) { + return this.register(priority.getValue(), eventClass, key, consumer); + } + + @Override + public > EventRegistration register( + short priority, @Nonnull Class eventClass, @Nullable KeyType key, @Nonnull Consumer consumer + ) { + return this.getRegistry(eventClass).register(priority, key, consumer); + } + + @Override + public > EventRegistration registerAsync( + @Nonnull Class eventClass, @Nonnull Function, CompletableFuture> function + ) { + return this.registerAsync((short)0, eventClass, null, function); + } + + @Override + public > EventRegistration registerAsync( + @Nonnull EventPriority priority, + @Nonnull Class eventClass, + @Nonnull Function, CompletableFuture> function + ) { + return this.registerAsync(priority.getValue(), eventClass, null, function); + } + + @Override + public > EventRegistration registerAsync( + short priority, Class eventClass, @Nonnull Function, CompletableFuture> function + ) { + return this.registerAsync(priority, eventClass, null, function); + } + + @Override + public > EventRegistration registerAsync( + @Nonnull Class eventClass, + @Nonnull KeyType key, + @Nonnull Function, CompletableFuture> function + ) { + return this.registerAsync((short)0, eventClass, key, function); + } + + @Override + public > EventRegistration registerAsync( + @Nonnull EventPriority priority, + Class eventClass, + @Nonnull KeyType key, + @Nonnull Function, CompletableFuture> function + ) { + return this.registerAsync(priority.getValue(), eventClass, key, function); + } + + @Override + public > EventRegistration registerAsync( + short priority, + @Nonnull Class eventClass, + @Nullable KeyType key, + @Nonnull Function, CompletableFuture> function + ) { + return this.getAsyncRegistry(eventClass).registerAsync(priority, key, function); + } + + @Override + public > EventRegistration registerGlobal( + @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + return this.registerGlobal((short)0, eventClass, consumer); + } + + @Override + public > EventRegistration registerGlobal( + @Nonnull EventPriority priority, @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + return this.registerGlobal(priority.getValue(), eventClass, consumer); + } + + @Override + public > EventRegistration registerGlobal( + short priority, @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + return this.getRegistry(eventClass).registerGlobal(priority, consumer); + } + + @Override + public > EventRegistration registerAsyncGlobal( + @Nonnull Class eventClass, @Nonnull Function, CompletableFuture> function + ) { + return this.registerAsyncGlobal((short)0, eventClass, function); + } + + @Override + public > EventRegistration registerAsyncGlobal( + @Nonnull EventPriority priority, + @Nonnull Class eventClass, + @Nonnull Function, CompletableFuture> function + ) { + return this.registerAsyncGlobal(priority.getValue(), eventClass, function); + } + + @Override + public > EventRegistration registerAsyncGlobal( + short priority, @Nonnull Class eventClass, @Nonnull Function, CompletableFuture> function + ) { + return this.getAsyncRegistry(eventClass).registerAsyncGlobal(priority, function); + } + + @Override + public > EventRegistration registerUnhandled( + @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + return this.registerUnhandled((short)0, eventClass, consumer); + } + + @Override + public > EventRegistration registerUnhandled( + @Nonnull EventPriority priority, @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + return this.registerUnhandled(priority.getValue(), eventClass, consumer); + } + + @Override + public > EventRegistration registerUnhandled( + short priority, @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + return this.getRegistry(eventClass).registerUnhandled(priority, consumer); + } + + @Override + public > EventRegistration registerAsyncUnhandled( + @Nonnull Class eventClass, @Nonnull Function, CompletableFuture> function + ) { + return this.registerAsyncUnhandled((short)0, eventClass, function); + } + + @Override + public > EventRegistration registerAsyncUnhandled( + @Nonnull EventPriority priority, + @Nonnull Class eventClass, + @Nonnull Function, CompletableFuture> function + ) { + return this.registerAsyncUnhandled(priority.getValue(), eventClass, function); + } + + @Override + public > EventRegistration registerAsyncUnhandled( + short priority, @Nonnull Class eventClass, @Nonnull Function, CompletableFuture> function + ) { + return this.getAsyncRegistry(eventClass).registerAsyncUnhandled(priority, function); + } + + @Nonnull + @Override + public > IEventDispatcher dispatchFor( + @Nonnull Class eventClass, KeyType key + ) { + SyncEventBusRegistry registry = (SyncEventBusRegistry)this.registryMap.get(eventClass); + return registry == null ? SyncEventBusRegistry.NO_OP : registry.dispatchFor(key); + } + + @Nonnull + @Override + public > IEventDispatcher> dispatchForAsync( + @Nonnull Class eventClass, KeyType key + ) { + AsyncEventBusRegistry registry = (AsyncEventBusRegistry)this.registryMap.get(eventClass); + return registry == null ? AsyncEventBusRegistry.NO_OP : registry.dispatchFor(key); + } +} diff --git a/src/com/hypixel/hytale/event/EventBusRegistry.java b/src/com/hypixel/hytale/event/EventBusRegistry.java new file mode 100644 index 0000000..e3c37bb --- /dev/null +++ b/src/com/hypixel/hytale/event/EventBusRegistry.java @@ -0,0 +1,213 @@ +package com.hypixel.hytale.event; + +import com.hypixel.fastutil.shorts.Short2ObjectConcurrentHashMap; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.metrics.metric.Metric; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class EventBusRegistry, ConsumerMapType extends EventBusRegistry.EventConsumerMap> { + @Nonnull + protected static final Object NULL = new Object(); + @Nonnull + protected final HytaleLogger logger; + @Nonnull + protected final Class eventClass; + @Nonnull + protected final Map map = new ConcurrentHashMap<>(); + @Nonnull + protected final ConsumerMapType global; + @Nonnull + protected final ConsumerMapType unhandled; + protected boolean timeEvents; + protected boolean shutdown; + + public EventBusRegistry( + @Nonnull HytaleLogger logger, @Nonnull Class eventClass, @Nonnull ConsumerMapType global, @Nonnull ConsumerMapType unhandled + ) { + this.logger = logger; + this.eventClass = eventClass; + this.global = global; + this.unhandled = unhandled; + } + + @Nonnull + public Class getEventClass() { + return this.eventClass; + } + + public boolean isTimeEvents() { + return this.timeEvents; + } + + public void setTimeEvents(boolean timeEvents) { + this.timeEvents = timeEvents; + } + + public void shutdown() { + this.shutdown = true; + this.map.clear(); + } + + public boolean isAlive() { + return !this.shutdown; + } + + public abstract EventRegistration register(short var1, @Nullable KeyType var2, @Nonnull Consumer var3); + + public abstract EventRegistration registerGlobal(short var1, @Nonnull Consumer var2); + + public abstract EventRegistration registerUnhandled(short var1, @Nonnull Consumer var2); + + public abstract IEventDispatcher dispatchFor(KeyType var1); + + public abstract static class EventConsumer { + @Nonnull + protected static final AtomicInteger consumerIndex = new AtomicInteger(); + protected final int index; + protected final short priority; + @Nonnull + protected final String consumerString; + @Nonnull + protected final Metric timer = new Metric(); + + public EventConsumer(short priority, @Nonnull String consumerString) { + this.priority = priority; + this.consumerString = consumerString; + this.index = consumerIndex.getAndIncrement(); + } + + public int getIndex() { + return this.index; + } + + public short getPriority() { + return this.priority; + } + + @Nonnull + public String getConsumerString() { + return this.consumerString; + } + + @Nonnull + public Metric getTimer() { + return this.timer; + } + + @Nonnull + @Override + public String toString() { + return "EventConsumer{index=" + + this.index + + ", priority=" + + this.priority + + ", consumerString='" + + this.consumerString + + "', timer=" + + this.timer + + "}"; + } + } + + public abstract static class EventConsumerMap + implements IEventDispatcher { + private static final short[] EMPTY_SHORT_ARRAY = new short[0]; + private final AtomicReference prioritiesRef = new AtomicReference<>(EMPTY_SHORT_ARRAY); + @Nonnull + private final Short2ObjectConcurrentHashMap> map = new Short2ObjectConcurrentHashMap<>(true, (short)-32768); + + public EventConsumerMap() { + } + + public boolean isEmpty() { + return this.map.isEmpty(); + } + + public void add(@Nonnull ConsumerType eventConsumer) { + short priority = eventConsumer.getPriority(); + boolean[] wasPriorityAdded = new boolean[]{false}; + this.map.computeIfAbsent(priority, s -> { + wasPriorityAdded[0] = true; + return new CopyOnWriteArrayList<>(); + }).add(eventConsumer); + if (wasPriorityAdded[0]) { + this.addPriority(priority); + } + } + + public boolean remove(@Nonnull ConsumerType consumer) { + short priority = consumer.getPriority(); + boolean[] wasRemoved = new boolean[]{false, false}; + this.map.computeIfPresent(priority, (key, obj) -> { + wasRemoved[0] = obj.remove(consumer); + if (!obj.isEmpty()) { + return obj; + } else { + wasRemoved[1] = true; + return null; + } + }); + if (wasRemoved[1]) { + this.removePriority(priority); + } + + return wasRemoved[0]; + } + + public short[] getPriorities() { + return this.prioritiesRef.get(); + } + + @Nullable + public List get(short priority) { + return this.map.get(priority); + } + + private void addPriority(short priority) { + while (this.map.containsKey(priority)) { + short[] currentPriorities = this.prioritiesRef.get(); + int index = Arrays.binarySearch(currentPriorities, priority); + if (index >= 0) { + return; + } + + int insertionPoint = -(index + 1); + int newLength = currentPriorities.length + 1; + short[] newPriorities = new short[newLength]; + System.arraycopy(currentPriorities, 0, newPriorities, 0, insertionPoint); + newPriorities[insertionPoint] = priority; + System.arraycopy(currentPriorities, insertionPoint, newPriorities, insertionPoint + 1, currentPriorities.length - insertionPoint); + if (this.prioritiesRef.compareAndSet(currentPriorities, newPriorities)) { + return; + } + } + } + + private void removePriority(short priority) { + while (!this.map.containsKey(priority)) { + short[] currentPriorities = this.prioritiesRef.get(); + int index = Arrays.binarySearch(currentPriorities, priority); + if (index < 0) { + return; + } + + int newLength = currentPriorities.length - 1; + short[] newPriorities = new short[newLength]; + System.arraycopy(currentPriorities, 0, newPriorities, 0, index); + System.arraycopy(currentPriorities, index + 1, newPriorities, index, newLength - index); + if (this.prioritiesRef.compareAndSet(currentPriorities, newPriorities)) { + return; + } + } + } + } +} diff --git a/src/com/hypixel/hytale/event/EventPriority.java b/src/com/hypixel/hytale/event/EventPriority.java new file mode 100644 index 0000000..8012ed3 --- /dev/null +++ b/src/com/hypixel/hytale/event/EventPriority.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.event; + +public enum EventPriority { + FIRST((short)-21844), + EARLY((short)-10922), + NORMAL((short)0), + LATE((short)10922), + LAST((short)21844); + + private final short value; + + private EventPriority(final short value) { + this.value = value; + } + + public short getValue() { + return this.value; + } +} diff --git a/src/com/hypixel/hytale/event/EventRegistration.java b/src/com/hypixel/hytale/event/EventRegistration.java new file mode 100644 index 0000000..6b1e0cc --- /dev/null +++ b/src/com/hypixel/hytale/event/EventRegistration.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.event; + +import com.hypixel.hytale.registry.Registration; +import java.util.function.BooleanSupplier; +import javax.annotation.Nonnull; + +public class EventRegistration> extends Registration { + @Nonnull + protected final Class eventClass; + + public EventRegistration(@Nonnull Class eventClass, @Nonnull BooleanSupplier isEnabled, @Nonnull Runnable unregister) { + super(isEnabled, unregister); + this.eventClass = eventClass; + } + + public EventRegistration(@Nonnull EventRegistration registration, @Nonnull BooleanSupplier isEnabled, @Nonnull Runnable unregister) { + super(isEnabled, unregister); + this.eventClass = registration.eventClass; + } + + @Nonnull + public Class getEventClass() { + return this.eventClass; + } + + @Nonnull + @Override + public String toString() { + return "EventRegistration{eventClass=" + this.eventClass + ", " + super.toString() + "}"; + } + + @Nonnull + @SafeVarargs + public static > EventRegistration combine( + @Nonnull EventRegistration thisRegistration, @Nonnull EventRegistration... containerRegistrations + ) { + return new EventRegistration<>(thisRegistration.eventClass, () -> { + if (!thisRegistration.isEnabled.getAsBoolean()) { + return false; + } else { + for (EventRegistration containerRegistration : containerRegistrations) { + if (!containerRegistration.isEnabled.getAsBoolean()) { + return false; + } + } + + return true; + } + }, () -> { + thisRegistration.unregister(); + + for (EventRegistration containerRegistration : containerRegistrations) { + containerRegistration.unregister(); + } + }); + } +} diff --git a/src/com/hypixel/hytale/event/EventRegistry.java b/src/com/hypixel/hytale/event/EventRegistry.java new file mode 100644 index 0000000..5cc2f52 --- /dev/null +++ b/src/com/hypixel/hytale/event/EventRegistry.java @@ -0,0 +1,237 @@ +package com.hypixel.hytale.event; + +import com.hypixel.hytale.function.consumer.BooleanConsumer; +import com.hypixel.hytale.registry.Registry; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public class EventRegistry extends Registry> implements IEventRegistry { + @Nonnull + private final IEventRegistry parent; + + public EventRegistry( + @Nonnull List registrations, @Nonnull BooleanSupplier precondition, String preconditionMessage, @Nonnull IEventRegistry parent + ) { + super(registrations, precondition, preconditionMessage, EventRegistration::new); + this.parent = parent; + } + + @Nonnull + private IEventRegistry getParent() { + return this.parent; + } + + public > EventRegistration register(@Nonnull EventRegistration evt) { + return super.register(evt); + } + + @Override + public > EventRegistration register( + @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + this.checkPrecondition(); + return this.register(this.getParent().register(eventClass, consumer)); + } + + @Override + public > EventRegistration register( + @Nonnull EventPriority priority, @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + this.checkPrecondition(); + return this.register(this.getParent().register(priority, eventClass, consumer)); + } + + @Override + public > EventRegistration register( + short priority, @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + this.checkPrecondition(); + return this.register(this.getParent().register(priority, eventClass, consumer)); + } + + @Override + public > EventRegistration register( + @Nonnull Class eventClass, @Nonnull KeyType key, @Nonnull Consumer consumer + ) { + this.checkPrecondition(); + return this.register(this.getParent().register(eventClass, key, consumer)); + } + + @Override + public > EventRegistration register( + @Nonnull EventPriority priority, @Nonnull Class eventClass, @Nonnull KeyType key, @Nonnull Consumer consumer + ) { + this.checkPrecondition(); + return this.register(this.getParent().register(priority, eventClass, key, consumer)); + } + + @Override + public > EventRegistration register( + short priority, @Nonnull Class eventClass, @Nonnull KeyType key, @Nonnull Consumer consumer + ) { + this.checkPrecondition(); + return this.register(this.getParent().register(priority, eventClass, key, consumer)); + } + + @Override + public > EventRegistration registerAsync( + @Nonnull Class eventClass, @Nonnull Function, CompletableFuture> function + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerAsync(eventClass, function)); + } + + @Override + public > EventRegistration registerAsync( + @Nonnull EventPriority priority, + @Nonnull Class eventClass, + @Nonnull Function, CompletableFuture> function + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerAsync(priority, eventClass, function)); + } + + @Override + public > EventRegistration registerAsync( + short priority, @Nonnull Class eventClass, @Nonnull Function, CompletableFuture> function + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerAsync(priority, eventClass, function)); + } + + @Override + public > EventRegistration registerAsync( + @Nonnull Class eventClass, + @Nonnull KeyType key, + @Nonnull Function, CompletableFuture> function + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerAsync(eventClass, key, function)); + } + + @Override + public > EventRegistration registerAsync( + @Nonnull EventPriority priority, + Class eventClass, + @Nonnull KeyType key, + @Nonnull Function, CompletableFuture> function + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerAsync(priority, eventClass, key, function)); + } + + @Override + public > EventRegistration registerAsync( + short priority, + @Nonnull Class eventClass, + @Nonnull KeyType key, + @Nonnull Function, CompletableFuture> function + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerAsync(priority, eventClass, key, function)); + } + + @Override + public > EventRegistration registerGlobal( + @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerGlobal(eventClass, consumer)); + } + + @Override + public > EventRegistration registerGlobal( + @Nonnull EventPriority priority, @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerGlobal(priority, eventClass, consumer)); + } + + @Override + public > EventRegistration registerGlobal( + short priority, @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerGlobal(priority, eventClass, consumer)); + } + + @Override + public > EventRegistration registerAsyncGlobal( + @Nonnull Class eventClass, @Nonnull Function, CompletableFuture> function + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerAsyncGlobal(eventClass, function)); + } + + @Override + public > EventRegistration registerAsyncGlobal( + @Nonnull EventPriority priority, + @Nonnull Class eventClass, + @Nonnull Function, CompletableFuture> function + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerAsyncGlobal(priority, eventClass, function)); + } + + @Override + public > EventRegistration registerAsyncGlobal( + short priority, @Nonnull Class eventClass, @Nonnull Function, CompletableFuture> function + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerAsyncGlobal(priority, eventClass, function)); + } + + @Override + public > EventRegistration registerUnhandled( + @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerUnhandled(eventClass, consumer)); + } + + @Override + public > EventRegistration registerUnhandled( + @Nonnull EventPriority priority, @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerUnhandled(priority, eventClass, consumer)); + } + + @Override + public > EventRegistration registerUnhandled( + short priority, @Nonnull Class eventClass, @Nonnull Consumer consumer + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerUnhandled(priority, eventClass, consumer)); + } + + @Override + public > EventRegistration registerAsyncUnhandled( + @Nonnull Class eventClass, @Nonnull Function, CompletableFuture> function + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerAsyncUnhandled(eventClass, function)); + } + + @Override + public > EventRegistration registerAsyncUnhandled( + @Nonnull EventPriority priority, + @Nonnull Class eventClass, + @Nonnull Function, CompletableFuture> function + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerAsyncUnhandled(priority, eventClass, function)); + } + + @Override + public > EventRegistration registerAsyncUnhandled( + short priority, @Nonnull Class eventClass, @Nonnull Function, CompletableFuture> function + ) { + this.checkPrecondition(); + return this.register(this.getParent().registerAsyncUnhandled(priority, eventClass, function)); + } +} diff --git a/src/com/hypixel/hytale/event/IAsyncEvent.java b/src/com/hypixel/hytale/event/IAsyncEvent.java new file mode 100644 index 0000000..5a2da67 --- /dev/null +++ b/src/com/hypixel/hytale/event/IAsyncEvent.java @@ -0,0 +1,4 @@ +package com.hypixel.hytale.event; + +public interface IAsyncEvent extends IBaseEvent { +} diff --git a/src/com/hypixel/hytale/event/IBaseEvent.java b/src/com/hypixel/hytale/event/IBaseEvent.java new file mode 100644 index 0000000..8864152 --- /dev/null +++ b/src/com/hypixel/hytale/event/IBaseEvent.java @@ -0,0 +1,4 @@ +package com.hypixel.hytale.event; + +public interface IBaseEvent { +} diff --git a/src/com/hypixel/hytale/event/ICancellable.java b/src/com/hypixel/hytale/event/ICancellable.java new file mode 100644 index 0000000..b8bc37c --- /dev/null +++ b/src/com/hypixel/hytale/event/ICancellable.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.event; + +public interface ICancellable { + boolean isCancelled(); + + void setCancelled(boolean var1); +} diff --git a/src/com/hypixel/hytale/event/IEvent.java b/src/com/hypixel/hytale/event/IEvent.java new file mode 100644 index 0000000..c48f3d3 --- /dev/null +++ b/src/com/hypixel/hytale/event/IEvent.java @@ -0,0 +1,4 @@ +package com.hypixel.hytale.event; + +public interface IEvent extends IBaseEvent { +} diff --git a/src/com/hypixel/hytale/event/IEventBus.java b/src/com/hypixel/hytale/event/IEventBus.java new file mode 100644 index 0000000..136a2da --- /dev/null +++ b/src/com/hypixel/hytale/event/IEventBus.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.event; + +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface IEventBus extends IEventRegistry { + default > EventType dispatch(@Nonnull Class eventClass) { + return this.dispatchFor(eventClass, null).dispatch(null); + } + + default > CompletableFuture dispatchAsync(@Nonnull Class eventClass) { + return this.dispatchForAsync(eventClass).dispatch(null); + } + + default > IEventDispatcher dispatchFor(@Nonnull Class eventClass) { + return this.dispatchFor(eventClass, null); + } + + > IEventDispatcher dispatchFor( + @Nonnull Class var1, @Nullable KeyType var2 + ); + + default > IEventDispatcher> dispatchForAsync( + @Nonnull Class eventClass + ) { + return this.dispatchForAsync(eventClass, null); + } + + > IEventDispatcher> dispatchForAsync( + @Nonnull Class var1, @Nullable KeyType var2 + ); +} diff --git a/src/com/hypixel/hytale/event/IEventDispatcher.java b/src/com/hypixel/hytale/event/IEventDispatcher.java new file mode 100644 index 0000000..479ea86 --- /dev/null +++ b/src/com/hypixel/hytale/event/IEventDispatcher.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.event; + +import javax.annotation.Nullable; + +public interface IEventDispatcher { + default boolean hasListener() { + return true; + } + + ReturnType dispatch(@Nullable EventType var1); +} diff --git a/src/com/hypixel/hytale/event/IEventRegistry.java b/src/com/hypixel/hytale/event/IEventRegistry.java new file mode 100644 index 0000000..0a4ff86 --- /dev/null +++ b/src/com/hypixel/hytale/event/IEventRegistry.java @@ -0,0 +1,133 @@ +package com.hypixel.hytale.event; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface IEventRegistry { + @Nullable + > EventRegistration register(@Nonnull Class var1, @Nonnull Consumer var2); + + @Nullable + > EventRegistration register( + @Nonnull EventPriority var1, @Nonnull Class var2, @Nonnull Consumer var3 + ); + + @Nullable + > EventRegistration register( + short var1, @Nonnull Class var2, @Nonnull Consumer var3 + ); + + @Nullable + > EventRegistration register( + @Nonnull Class var1, @Nonnull KeyType var2, @Nonnull Consumer var3 + ); + + @Nullable + > EventRegistration register( + @Nonnull EventPriority var1, @Nonnull Class var2, @Nonnull KeyType var3, @Nonnull Consumer var4 + ); + + @Nullable + > EventRegistration register( + short var1, @Nonnull Class var2, @Nonnull KeyType var3, @Nonnull Consumer var4 + ); + + @Nullable + > EventRegistration registerAsync( + @Nonnull Class var1, @Nonnull Function, CompletableFuture> var2 + ); + + @Nullable + > EventRegistration registerAsync( + @Nonnull EventPriority var1, @Nonnull Class var2, @Nonnull Function, CompletableFuture> var3 + ); + + @Nullable + > EventRegistration registerAsync( + short var1, Class var2, @Nonnull Function, CompletableFuture> var3 + ); + + @Nullable + > EventRegistration registerAsync( + @Nonnull Class var1, @Nonnull KeyType var2, @Nonnull Function, CompletableFuture> var3 + ); + + @Nullable + > EventRegistration registerAsync( + @Nonnull EventPriority var1, + Class var2, + @Nonnull KeyType var3, + @Nonnull Function, CompletableFuture> var4 + ); + + @Nullable + > EventRegistration registerAsync( + short var1, + @Nonnull Class var2, + @Nonnull KeyType var3, + @Nonnull Function, CompletableFuture> var4 + ); + + @Nullable + > EventRegistration registerGlobal( + @Nonnull Class var1, @Nonnull Consumer var2 + ); + + @Nullable + > EventRegistration registerGlobal( + @Nonnull EventPriority var1, @Nonnull Class var2, @Nonnull Consumer var3 + ); + + @Nullable + > EventRegistration registerGlobal( + short var1, @Nonnull Class var2, @Nonnull Consumer var3 + ); + + @Nullable + > EventRegistration registerAsyncGlobal( + @Nonnull Class var1, @Nonnull Function, CompletableFuture> var2 + ); + + @Nullable + > EventRegistration registerAsyncGlobal( + @Nonnull EventPriority var1, @Nonnull Class var2, @Nonnull Function, CompletableFuture> var3 + ); + + @Nullable + > EventRegistration registerAsyncGlobal( + short var1, @Nonnull Class var2, @Nonnull Function, CompletableFuture> var3 + ); + + @Nullable + > EventRegistration registerUnhandled( + @Nonnull Class var1, @Nonnull Consumer var2 + ); + + @Nullable + > EventRegistration registerUnhandled( + @Nonnull EventPriority var1, @Nonnull Class var2, @Nonnull Consumer var3 + ); + + @Nullable + > EventRegistration registerUnhandled( + short var1, @Nonnull Class var2, @Nonnull Consumer var3 + ); + + @Nullable + > EventRegistration registerAsyncUnhandled( + @Nonnull Class var1, @Nonnull Function, CompletableFuture> var2 + ); + + @Nullable + > EventRegistration registerAsyncUnhandled( + @Nonnull EventPriority var1, @Nonnull Class var2, @Nonnull Function, CompletableFuture> var3 + ); + + @Nullable + > EventRegistration registerAsyncUnhandled( + short var1, @Nonnull Class var2, @Nonnull Function, CompletableFuture> var3 + ); +} diff --git a/src/com/hypixel/hytale/event/IProcessedEvent.java b/src/com/hypixel/hytale/event/IProcessedEvent.java new file mode 100644 index 0000000..acd57fa --- /dev/null +++ b/src/com/hypixel/hytale/event/IProcessedEvent.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.event; + +import javax.annotation.Nonnull; + +public interface IProcessedEvent { + void processEvent(@Nonnull String var1); +} diff --git a/src/com/hypixel/hytale/event/SyncEventBusRegistry.java b/src/com/hypixel/hytale/event/SyncEventBusRegistry.java new file mode 100644 index 0000000..067ad7f --- /dev/null +++ b/src/com/hypixel/hytale/event/SyncEventBusRegistry.java @@ -0,0 +1,202 @@ +package com.hypixel.hytale.event; + +import com.hypixel.hytale.logger.HytaleLogger; +import java.util.List; +import java.util.function.Consumer; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SyncEventBusRegistry> + extends EventBusRegistry> { + public static final IEventDispatcher NO_OP = new IEventDispatcher() { + @Override + public boolean hasListener() { + return false; + } + + public IBaseEvent dispatch(IBaseEvent event) { + return event; + } + }; + private final IEventDispatcher globalDispatcher = event -> { + if (!this.dispatchGlobal(event)) { + this.dispatchUnhandled(event); + } + + return event; + }; + + public SyncEventBusRegistry(HytaleLogger logger, Class eventClass) { + super(logger, eventClass, new SyncEventBusRegistry.SyncEventConsumerMap<>(null), new SyncEventBusRegistry.SyncEventConsumerMap<>(null)); + this.global.registry = this.unhandled.registry = this; + } + + @Nonnull + @Override + public EventRegistration register(short priority, @Nullable KeyType key, @Nonnull Consumer consumer) { + if (this.shutdown) { + throw new IllegalArgumentException("EventRegistry is shutdown!"); + } else { + KeyType k = (KeyType)(key != null ? key : NULL); + SyncEventBusRegistry.SyncEventConsumerMap eventMap = this.map + .computeIfAbsent(k, o -> new SyncEventBusRegistry.SyncEventConsumerMap<>(this)); + SyncEventBusRegistry.SyncEventConsumer eventConsumer = new SyncEventBusRegistry.SyncEventConsumer<>(priority, consumer); + eventMap.add(eventConsumer); + return new EventRegistration<>(this.eventClass, this::isAlive, () -> this.unregister(key, eventConsumer)); + } + } + + private void unregister(@Nullable KeyType key, @Nonnull SyncEventBusRegistry.SyncEventConsumer consumer) { + if (this.shutdown) { + throw new IllegalArgumentException("EventRegistry is shutdown!"); + } else { + KeyType k = (KeyType)(key != null ? key : NULL); + SyncEventBusRegistry.SyncEventConsumerMap eventMap = this.map.get(k); + if (eventMap != null && !eventMap.remove(consumer)) { + throw new IllegalArgumentException(String.valueOf(consumer)); + } + } + } + + @Nonnull + @Override + public EventRegistration registerGlobal(short priority, @Nonnull Consumer consumer) { + if (this.shutdown) { + throw new IllegalArgumentException("EventRegistry is shutdown!"); + } else { + SyncEventBusRegistry.SyncEventConsumer eventConsumer = new SyncEventBusRegistry.SyncEventConsumer<>(priority, consumer); + this.global.add(eventConsumer); + return new EventRegistration<>(this.eventClass, this::isAlive, () -> this.unregisterGlobal(eventConsumer)); + } + } + + private void unregisterGlobal(@Nonnull SyncEventBusRegistry.SyncEventConsumer consumer) { + if (this.shutdown) { + throw new IllegalArgumentException("EventRegistry is shutdown!"); + } else if (!this.global.remove(consumer)) { + throw new IllegalArgumentException(String.valueOf(consumer)); + } + } + + @Nonnull + @Override + public EventRegistration registerUnhandled(short priority, @Nonnull Consumer consumer) { + if (this.shutdown) { + throw new IllegalArgumentException("EventRegistry is shutdown!"); + } else { + SyncEventBusRegistry.SyncEventConsumer eventConsumer = new SyncEventBusRegistry.SyncEventConsumer<>(priority, consumer); + this.unhandled.add(eventConsumer); + return new EventRegistration<>(this.eventClass, this::isAlive, () -> this.unregisterUnhandled(eventConsumer)); + } + } + + private void unregisterUnhandled(@Nonnull SyncEventBusRegistry.SyncEventConsumer consumer) { + if (this.shutdown) { + throw new IllegalArgumentException("EventRegistry is shutdown!"); + } else if (!this.unhandled.remove(consumer)) { + throw new IllegalArgumentException(String.valueOf(consumer)); + } + } + + @Nonnull + @Override + public IEventDispatcher dispatchFor(@Nullable KeyType key) { + if (this.shutdown) { + throw new IllegalArgumentException("EventRegistry is shutdown!"); + } else { + KeyType k = (KeyType)(key != null ? key : NULL); + SyncEventBusRegistry.SyncEventConsumerMap eventMap = this.map.get(k); + if (eventMap != null && !eventMap.isEmpty()) { + return eventMap; + } else { + return this.global.isEmpty() && this.unhandled.isEmpty() ? NO_OP : this.globalDispatcher; + } + } + } + + private boolean dispatchGlobal(EventType event) { + return this.dispatchEventMap(event, this.global, "Failed to dispatch event (global)"); + } + + private boolean dispatchUnhandled(EventType event) { + return this.dispatchEventMap(event, this.unhandled, "Failed to dispatch event (unhandled)"); + } + + private boolean dispatchEventMap(EventType event, @Nonnull SyncEventBusRegistry.SyncEventConsumerMap eventMap, String s) { + boolean handled = false; + + for (short priority : eventMap.getPriorities()) { + List> consumers = eventMap.get(priority); + if (consumers != null) { + for (SyncEventBusRegistry.SyncEventConsumer consumer : consumers) { + try { + Consumer theConsumer = this.timeEvents ? consumer.getTimedConsumer() : consumer.getConsumer(); + theConsumer.accept(event); + if (event instanceof IProcessedEvent processedEvent) { + processedEvent.processEvent(consumer.getConsumerString()); + } + + handled = true; + } catch (Throwable var14) { + this.logger.at(Level.SEVERE).withCause(var14).log("%s %s to %s", s, event, consumer); + } + } + } + } + + return handled; + } + + protected static class SyncEventConsumer extends EventBusRegistry.EventConsumer { + @Nonnull + private final Consumer consumer; + @Nonnull + private final Consumer timedConsumer; + + public SyncEventConsumer(short priority, @Nonnull Consumer consumer) { + super(priority, consumer.toString()); + this.consumer = consumer; + this.timedConsumer = t -> { + long before = System.nanoTime(); + consumer.accept(t); + long after = System.nanoTime(); + this.timer.add(after - before); + }; + } + + @Nonnull + protected Consumer getConsumer() { + return this.consumer; + } + + @Nonnull + public Consumer getTimedConsumer() { + return this.timedConsumer; + } + + @Nonnull + @Override + public String toString() { + return "SyncEventConsumer{consumer=" + this.consumer + ", timedConsumer=" + this.timedConsumer + "} " + super.toString(); + } + } + + protected static class SyncEventConsumerMap + extends EventBusRegistry.EventConsumerMap, EventType> { + protected SyncEventBusRegistry registry; + + public SyncEventConsumerMap(SyncEventBusRegistry registry) { + this.registry = registry; + } + + public EventType dispatch(EventType event) { + boolean handled = this.registry.dispatchEventMap(event, this, "Failed to dispatch event"); + if (!this.registry.dispatchGlobal(event) && !handled) { + this.registry.dispatchUnhandled(event); + } + + return event; + } + } +} diff --git a/src/com/hypixel/hytale/function/consumer/BooleanConsumer.java b/src/com/hypixel/hytale/function/consumer/BooleanConsumer.java new file mode 100644 index 0000000..01a9413 --- /dev/null +++ b/src/com/hypixel/hytale/function/consumer/BooleanConsumer.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.consumer; + +@FunctionalInterface +public interface BooleanConsumer { + void accept(boolean var1); +} diff --git a/src/com/hypixel/hytale/function/consumer/DoubleQuadObjectConsumer.java b/src/com/hypixel/hytale/function/consumer/DoubleQuadObjectConsumer.java new file mode 100644 index 0000000..7500542 --- /dev/null +++ b/src/com/hypixel/hytale/function/consumer/DoubleQuadObjectConsumer.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.consumer; + +@FunctionalInterface +public interface DoubleQuadObjectConsumer { + void accept(double var1, T var3, U var4, R var5, V var6); +} diff --git a/src/com/hypixel/hytale/function/consumer/FloatConsumer.java b/src/com/hypixel/hytale/function/consumer/FloatConsumer.java new file mode 100644 index 0000000..974a722 --- /dev/null +++ b/src/com/hypixel/hytale/function/consumer/FloatConsumer.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.consumer; + +@FunctionalInterface +public interface FloatConsumer { + void accept(float var1); +} diff --git a/src/com/hypixel/hytale/function/consumer/IntBiObjectConsumer.java b/src/com/hypixel/hytale/function/consumer/IntBiObjectConsumer.java new file mode 100644 index 0000000..7df815f --- /dev/null +++ b/src/com/hypixel/hytale/function/consumer/IntBiObjectConsumer.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.consumer; + +@FunctionalInterface +public interface IntBiObjectConsumer { + void accept(int var1, T var2, J var3); +} diff --git a/src/com/hypixel/hytale/function/consumer/IntObjectConsumer.java b/src/com/hypixel/hytale/function/consumer/IntObjectConsumer.java new file mode 100644 index 0000000..cac35db --- /dev/null +++ b/src/com/hypixel/hytale/function/consumer/IntObjectConsumer.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.consumer; + +@FunctionalInterface +public interface IntObjectConsumer { + void accept(int var1, T var2); +} diff --git a/src/com/hypixel/hytale/function/consumer/IntTriObjectConsumer.java b/src/com/hypixel/hytale/function/consumer/IntTriObjectConsumer.java new file mode 100644 index 0000000..4fb49a1 --- /dev/null +++ b/src/com/hypixel/hytale/function/consumer/IntTriObjectConsumer.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.consumer; + +@FunctionalInterface +public interface IntTriObjectConsumer { + void accept(int var1, T var2, J var3, K var4); +} diff --git a/src/com/hypixel/hytale/function/consumer/QuadConsumer.java b/src/com/hypixel/hytale/function/consumer/QuadConsumer.java new file mode 100644 index 0000000..ee27a2b --- /dev/null +++ b/src/com/hypixel/hytale/function/consumer/QuadConsumer.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.consumer; + +@FunctionalInterface +public interface QuadConsumer { + void accept(T var1, U var2, R var3, V var4); +} diff --git a/src/com/hypixel/hytale/function/consumer/ShortObjectConsumer.java b/src/com/hypixel/hytale/function/consumer/ShortObjectConsumer.java new file mode 100644 index 0000000..d6e55fe --- /dev/null +++ b/src/com/hypixel/hytale/function/consumer/ShortObjectConsumer.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.consumer; + +@FunctionalInterface +public interface ShortObjectConsumer { + void accept(short var1, T var2); +} diff --git a/src/com/hypixel/hytale/function/consumer/TriConsumer.java b/src/com/hypixel/hytale/function/consumer/TriConsumer.java new file mode 100644 index 0000000..b25bd3b --- /dev/null +++ b/src/com/hypixel/hytale/function/consumer/TriConsumer.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.consumer; + +@FunctionalInterface +public interface TriConsumer { + void accept(T var1, U var2, R var3); +} diff --git a/src/com/hypixel/hytale/function/consumer/TriIntConsumer.java b/src/com/hypixel/hytale/function/consumer/TriIntConsumer.java new file mode 100644 index 0000000..b22ad7f --- /dev/null +++ b/src/com/hypixel/hytale/function/consumer/TriIntConsumer.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.consumer; + +@FunctionalInterface +public interface TriIntConsumer { + void accept(int var1, int var2, int var3); +} diff --git a/src/com/hypixel/hytale/function/function/BiDoubleToDoubleFunction.java b/src/com/hypixel/hytale/function/function/BiDoubleToDoubleFunction.java new file mode 100644 index 0000000..c8537a9 --- /dev/null +++ b/src/com/hypixel/hytale/function/function/BiDoubleToDoubleFunction.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.function; + +@FunctionalInterface +public interface BiDoubleToDoubleFunction { + double apply(double var1, double var3); +} diff --git a/src/com/hypixel/hytale/function/function/BiIntToDoubleFunction.java b/src/com/hypixel/hytale/function/function/BiIntToDoubleFunction.java new file mode 100644 index 0000000..d235c76 --- /dev/null +++ b/src/com/hypixel/hytale/function/function/BiIntToDoubleFunction.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.function; + +@FunctionalInterface +public interface BiIntToDoubleFunction { + double apply(int var1, int var2); +} diff --git a/src/com/hypixel/hytale/function/function/BiLongToDoubleFunction.java b/src/com/hypixel/hytale/function/function/BiLongToDoubleFunction.java new file mode 100644 index 0000000..3527e53 --- /dev/null +++ b/src/com/hypixel/hytale/function/function/BiLongToDoubleFunction.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.function; + +@FunctionalInterface +public interface BiLongToDoubleFunction { + double apply(long var1, long var3); +} diff --git a/src/com/hypixel/hytale/function/function/BiToFloatFunction.java b/src/com/hypixel/hytale/function/function/BiToFloatFunction.java new file mode 100644 index 0000000..09c668a --- /dev/null +++ b/src/com/hypixel/hytale/function/function/BiToFloatFunction.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.function; + +@FunctionalInterface +public interface BiToFloatFunction { + float applyAsFloat(T var1, V var2); +} diff --git a/src/com/hypixel/hytale/function/function/QuadBoolFunction.java b/src/com/hypixel/hytale/function/function/QuadBoolFunction.java new file mode 100644 index 0000000..0dc7489 --- /dev/null +++ b/src/com/hypixel/hytale/function/function/QuadBoolFunction.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.function; + +@FunctionalInterface +public interface QuadBoolFunction { + R apply(T var1, U var2, V var3, W var4, boolean var5); +} diff --git a/src/com/hypixel/hytale/function/function/ToFloatFunction.java b/src/com/hypixel/hytale/function/function/ToFloatFunction.java new file mode 100644 index 0000000..e7eb0ba --- /dev/null +++ b/src/com/hypixel/hytale/function/function/ToFloatFunction.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.function; + +@FunctionalInterface +public interface ToFloatFunction { + float applyAsFloat(T var1); +} diff --git a/src/com/hypixel/hytale/function/function/TriBoolFunction.java b/src/com/hypixel/hytale/function/function/TriBoolFunction.java new file mode 100644 index 0000000..ef6ea30 --- /dev/null +++ b/src/com/hypixel/hytale/function/function/TriBoolFunction.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.function; + +@FunctionalInterface +public interface TriBoolFunction { + R apply(T var1, U var2, V var3, boolean var4); +} diff --git a/src/com/hypixel/hytale/function/function/TriFunction.java b/src/com/hypixel/hytale/function/function/TriFunction.java new file mode 100644 index 0000000..f065b23 --- /dev/null +++ b/src/com/hypixel/hytale/function/function/TriFunction.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.function; + +@FunctionalInterface +public interface TriFunction { + R apply(T var1, U var2, V var3); +} diff --git a/src/com/hypixel/hytale/function/function/TriIntObjectDoubleToByteFunction.java b/src/com/hypixel/hytale/function/function/TriIntObjectDoubleToByteFunction.java new file mode 100644 index 0000000..818f8ff --- /dev/null +++ b/src/com/hypixel/hytale/function/function/TriIntObjectDoubleToByteFunction.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.function; + +@FunctionalInterface +public interface TriIntObjectDoubleToByteFunction { + byte apply(int var1, int var2, int var3, T var4, double var5); +} diff --git a/src/com/hypixel/hytale/function/function/TriToIntFunction.java b/src/com/hypixel/hytale/function/function/TriToIntFunction.java new file mode 100644 index 0000000..111a395 --- /dev/null +++ b/src/com/hypixel/hytale/function/function/TriToIntFunction.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.function; + +@FunctionalInterface +public interface TriToIntFunction { + int apply(T var1, U var2, V var3); +} diff --git a/src/com/hypixel/hytale/function/package-info.java b/src/com/hypixel/hytale/function/package-info.java new file mode 100644 index 0000000..ee0f7c0 --- /dev/null +++ b/src/com/hypixel/hytale/function/package-info.java @@ -0,0 +1,2 @@ +package com.hypixel.hytale.function; + diff --git a/src/com/hypixel/hytale/function/predicate/BiFloatPredicate.java b/src/com/hypixel/hytale/function/predicate/BiFloatPredicate.java new file mode 100644 index 0000000..6e003c3 --- /dev/null +++ b/src/com/hypixel/hytale/function/predicate/BiFloatPredicate.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.predicate; + +@FunctionalInterface +public interface BiFloatPredicate { + boolean test(float var1, float var2); +} diff --git a/src/com/hypixel/hytale/function/predicate/BiIntPredicate.java b/src/com/hypixel/hytale/function/predicate/BiIntPredicate.java new file mode 100644 index 0000000..4c0861d --- /dev/null +++ b/src/com/hypixel/hytale/function/predicate/BiIntPredicate.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.predicate; + +@FunctionalInterface +public interface BiIntPredicate { + boolean test(int var1, int var2); +} diff --git a/src/com/hypixel/hytale/function/predicate/LongTriIntBiObjPredicate.java b/src/com/hypixel/hytale/function/predicate/LongTriIntBiObjPredicate.java new file mode 100644 index 0000000..7f8c97b --- /dev/null +++ b/src/com/hypixel/hytale/function/predicate/LongTriIntBiObjPredicate.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.predicate; + +@FunctionalInterface +public interface LongTriIntBiObjPredicate { + boolean test(long var1, int var3, int var4, int var5, T var6, V var7); +} diff --git a/src/com/hypixel/hytale/function/predicate/ObjectPositionBlockFunction.java b/src/com/hypixel/hytale/function/predicate/ObjectPositionBlockFunction.java new file mode 100644 index 0000000..38a0c4e --- /dev/null +++ b/src/com/hypixel/hytale/function/predicate/ObjectPositionBlockFunction.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.predicate; + +@FunctionalInterface +public interface ObjectPositionBlockFunction { + K accept(T var1, V var2, int var3, int var4, int var5, int var6); +} diff --git a/src/com/hypixel/hytale/function/predicate/QuadObjectDoublePredicate.java b/src/com/hypixel/hytale/function/predicate/QuadObjectDoublePredicate.java new file mode 100644 index 0000000..a97a68e --- /dev/null +++ b/src/com/hypixel/hytale/function/predicate/QuadObjectDoublePredicate.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.predicate; + +@FunctionalInterface +public interface QuadObjectDoublePredicate { + boolean test(T var1, U var2, V var3, R var4, double var5); +} diff --git a/src/com/hypixel/hytale/function/predicate/QuadPredicate.java b/src/com/hypixel/hytale/function/predicate/QuadPredicate.java new file mode 100644 index 0000000..5c25d3b --- /dev/null +++ b/src/com/hypixel/hytale/function/predicate/QuadPredicate.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.function.predicate; + +public interface QuadPredicate { + boolean test(T var1, R var2, S var3, U var4); +} diff --git a/src/com/hypixel/hytale/function/predicate/TriIntObjPredicate.java b/src/com/hypixel/hytale/function/predicate/TriIntObjPredicate.java new file mode 100644 index 0000000..fe1d490 --- /dev/null +++ b/src/com/hypixel/hytale/function/predicate/TriIntObjPredicate.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.predicate; + +@FunctionalInterface +public interface TriIntObjPredicate { + boolean test(int var1, int var2, int var3, T var4); +} diff --git a/src/com/hypixel/hytale/function/predicate/TriIntPredicate.java b/src/com/hypixel/hytale/function/predicate/TriIntPredicate.java new file mode 100644 index 0000000..15b6212 --- /dev/null +++ b/src/com/hypixel/hytale/function/predicate/TriIntPredicate.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.predicate; + +@FunctionalInterface +public interface TriIntPredicate { + boolean test(int var1, int var2, int var3); +} diff --git a/src/com/hypixel/hytale/function/predicate/TriObjectDoublePredicate.java b/src/com/hypixel/hytale/function/predicate/TriObjectDoublePredicate.java new file mode 100644 index 0000000..84a75d6 --- /dev/null +++ b/src/com/hypixel/hytale/function/predicate/TriObjectDoublePredicate.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.function.predicate; + +@FunctionalInterface +public interface TriObjectDoublePredicate { + boolean test(T var1, U var2, V var3, double var4); +} diff --git a/src/com/hypixel/hytale/function/predicate/TriPredicate.java b/src/com/hypixel/hytale/function/predicate/TriPredicate.java new file mode 100644 index 0000000..ffa044d --- /dev/null +++ b/src/com/hypixel/hytale/function/predicate/TriPredicate.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.function.predicate; + +public interface TriPredicate { + boolean test(T var1, R var2, S var3); +} diff --git a/src/com/hypixel/hytale/function/predicate/UnaryBiPredicate.java b/src/com/hypixel/hytale/function/predicate/UnaryBiPredicate.java new file mode 100644 index 0000000..cfe846e --- /dev/null +++ b/src/com/hypixel/hytale/function/predicate/UnaryBiPredicate.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.function.predicate; + +import java.util.function.BiPredicate; + +@FunctionalInterface +public interface UnaryBiPredicate extends BiPredicate { +} diff --git a/src/com/hypixel/hytale/function/supplier/CachedSupplier.java b/src/com/hypixel/hytale/function/supplier/CachedSupplier.java new file mode 100644 index 0000000..2cf8eb3 --- /dev/null +++ b/src/com/hypixel/hytale/function/supplier/CachedSupplier.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.function.supplier; + +import java.util.function.Supplier; +import javax.annotation.Nullable; + +public class CachedSupplier implements Supplier { + private final Supplier delegate; + private transient volatile boolean initialized; + @Nullable + private transient T value; + + public CachedSupplier(Supplier delegate) { + this.delegate = delegate; + } + + @Nullable + @Override + public T get() { + if (!this.initialized) { + synchronized (this) { + if (!this.initialized) { + T t = this.delegate.get(); + this.value = t; + this.initialized = true; + return t; + } + } + } + + return this.value; + } + + @Nullable + public T getValue() { + return this.value; + } + + public void invalidate() { + if (this.initialized) { + synchronized (this) { + if (this.initialized) { + this.value = null; + this.initialized = false; + } + } + } + } +} diff --git a/src/com/hypixel/hytale/function/supplier/SupplierUtil.java b/src/com/hypixel/hytale/function/supplier/SupplierUtil.java new file mode 100644 index 0000000..3ccfed8 --- /dev/null +++ b/src/com/hypixel/hytale/function/supplier/SupplierUtil.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.function.supplier; + +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class SupplierUtil { + public SupplierUtil() { + } + + @Nonnull + public static CachedSupplier cache(Supplier delegate) { + return new CachedSupplier<>(delegate); + } +} diff --git a/src/com/hypixel/hytale/logger/HytaleLogger.java b/src/com/hypixel/hytale/logger/HytaleLogger.java new file mode 100644 index 0000000..1c7f979 --- /dev/null +++ b/src/com/hypixel/hytale/logger/HytaleLogger.java @@ -0,0 +1,162 @@ +package com.hypixel.hytale.logger; + +import com.google.common.flogger.AbstractLogger; +import com.google.common.flogger.LogContext; +import com.google.common.flogger.LoggingApi; +import com.google.common.flogger.backend.Platform; +import com.google.common.flogger.parser.DefaultPrintfMessageParser; +import com.google.common.flogger.parser.MessageParser; +import com.hypixel.hytale.logger.backend.HytaleConsole; +import com.hypixel.hytale.logger.backend.HytaleFileHandler; +import com.hypixel.hytale.logger.backend.HytaleLogManager; +import com.hypixel.hytale.logger.backend.HytaleLoggerBackend; +import com.hypixel.hytale.logger.backend.HytaleUncaughtExceptionHandler; +import com.hypixel.hytale.logger.util.LoggerPrintStream; +import io.sentry.IScopes; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.LogManager; +import javax.annotation.Nonnull; + +public class HytaleLogger extends AbstractLogger { + private static final Map CACHE; + private static final HytaleLogger LOGGER; + static final HytaleLogger.NoOp NO_OP; + @Nonnull + private final HytaleLoggerBackend backend; + + public static void init() { + HytaleFileHandler fileHandler = HytaleFileHandler.INSTANCE; + HytaleConsole console = HytaleConsole.INSTANCE; + LOGGER.at(Level.INFO).log("Logger Initialized"); + } + + public static void replaceStd() { + if (!HytaleLoggerBackend.isJunitTest()) { + System.setOut(new LoggerPrintStream(get("SOUT"), Level.INFO)); + System.setErr(new LoggerPrintStream(get("SERR"), Level.SEVERE)); + } + } + + public static HytaleLogger getLogger() { + return LOGGER; + } + + @Nonnull + public static HytaleLogger forEnclosingClass() { + String className = Platform.getCallerFinder().findLoggingClass(HytaleLogger.class); + String loggerName = classToLoggerName(className); + return get(loggerName); + } + + @Nonnull + public static HytaleLogger forEnclosingClassFull() { + String loggingClass = Platform.getCallerFinder().findLoggingClass(HytaleLogger.class); + return get(loggingClass); + } + + @Nonnull + public static HytaleLogger get(String loggerName) { + return CACHE.computeIfAbsent(loggerName, key -> new HytaleLogger(HytaleLoggerBackend.getLogger(key))); + } + + private HytaleLogger(@Nonnull HytaleLoggerBackend backend) { + super(backend); + this.backend = backend; + } + + public HytaleLogger.Api at(@Nonnull Level level) { + return (HytaleLogger.Api)(this.isLoggable(level) ? new HytaleLogger.Context(level) : NO_OP); + } + + @Override + public String getName() { + return super.getName(); + } + + @Nonnull + public Level getLevel() { + return this.backend.getLevel(); + } + + public void setLevel(@Nonnull Level level) { + this.backend.setLevel(level); + } + + @Nonnull + public HytaleLogger getSubLogger(String name) { + return new HytaleLogger(this.backend.getSubLogger(name)); + } + + public void setSentryClient(@Nonnull IScopes scope) { + this.backend.setSentryClient(scope); + } + + public void setPropagatesSentryToParent(boolean propagate) { + this.backend.setPropagatesSentryToParent(propagate); + } + + @Nonnull + private static String classToLoggerName(@Nonnull String className) { + int lastIndexOf = className.lastIndexOf(46); + String loggerName; + if (lastIndexOf >= 0 && className.length() > lastIndexOf + 1) { + loggerName = className.substring(lastIndexOf + 1); + } else { + loggerName = className; + } + + return loggerName; + } + + static { + System.setProperty("java.util.logging.manager", HytaleLogManager.class.getName()); + HytaleUncaughtExceptionHandler.setup(); + LogManager logManager = LogManager.getLogManager(); + if (!logManager.getClass().getName().equals(HytaleLogManager.class.getName())) { + throw new IllegalStateException( + "Log manager wasn't set! Please ensure HytaleLogger is the first logger to be initialized or\nuse `System.setProperty(\"java.util.logging.manager\", HytaleLogManager.class.getName());` at the start of your application.\nLog manager is: " + + logManager + ); + } else { + CACHE = new ConcurrentHashMap<>(); + LOGGER = new HytaleLogger(HytaleLoggerBackend.getLogger()); + NO_OP = new HytaleLogger.NoOp(); + } + } + + public interface Api extends LoggingApi { + } + + final class Context extends LogContext implements HytaleLogger.Api { + private Context(@Nonnull Level level) { + super(level, false); + } + + @Nonnull + protected HytaleLogger getLogger() { + return HytaleLogger.this; + } + + @Nonnull + protected HytaleLogger.Api api() { + return this; + } + + @Nonnull + protected HytaleLogger.Api noOp() { + return HytaleLogger.NO_OP; + } + + @Override + protected MessageParser getMessageParser() { + return DefaultPrintfMessageParser.getInstance(); + } + } + + private static final class NoOp extends com.google.common.flogger.LoggingApi.NoOp implements HytaleLogger.Api { + private NoOp() { + } + } +} diff --git a/src/com/hypixel/hytale/logger/backend/HytaleConsole.java b/src/com/hypixel/hytale/logger/backend/HytaleConsole.java new file mode 100644 index 0000000..f91414a --- /dev/null +++ b/src/com/hypixel/hytale/logger/backend/HytaleConsole.java @@ -0,0 +1,133 @@ +package com.hypixel.hytale.logger.backend; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HytaleConsole extends Thread { + public static final String TYPE_DUMB = "dumb"; + public static final HytaleConsole INSTANCE = new HytaleConsole(); + private final BlockingQueue logRecords = new LinkedBlockingQueue<>(); + private final HytaleLogFormatter formatter = new HytaleLogFormatter(this::shouldPrintAnsi); + @Nullable + private OutputStreamWriter soutwriter = new OutputStreamWriter(HytaleLoggerBackend.REAL_SOUT, StandardCharsets.UTF_8); + @Nullable + private OutputStreamWriter serrwriter = new OutputStreamWriter(HytaleLoggerBackend.REAL_SERR, StandardCharsets.UTF_8); + private String terminalType = "dumb"; + + private HytaleConsole() { + super("HytaleConsole"); + this.setDaemon(true); + this.start(); + } + + public void publish(@Nonnull LogRecord logRecord) { + if (!this.isAlive()) { + this.publish0(logRecord); + } else { + this.logRecords.offer(logRecord); + } + } + + @Override + public void run() { + try { + while (!this.isInterrupted()) { + this.publish0(this.logRecords.take()); + } + } catch (InterruptedException var2) { + Thread.currentThread().interrupt(); + } + } + + public void shutdown() { + this.interrupt(); + + try { + this.join(); + } catch (InterruptedException var5) { + Thread.currentThread().interrupt(); + } + + List list = new ObjectArrayList<>(); + this.logRecords.drainTo(list); + list.forEach(this::publish0); + if (this.soutwriter != null) { + try { + this.soutwriter.flush(); + } catch (Exception var4) { + } + + this.soutwriter = null; + } + + if (this.serrwriter != null) { + try { + this.serrwriter.flush(); + } catch (Exception var3) { + } + + this.serrwriter = null; + } + } + + private void publish0(@Nonnull LogRecord record) { + String msg; + try { + msg = this.formatter.format(record); + } catch (Exception var7) { + if (this.serrwriter != null) { + var7.printStackTrace(new PrintWriter(this.serrwriter)); + } else { + var7.printStackTrace(System.err); + } + + return; + } + + try { + if (record.getLevel().intValue() >= Level.SEVERE.intValue()) { + if (this.serrwriter != null) { + this.serrwriter.write(msg); + + try { + this.serrwriter.flush(); + } catch (Exception var5) { + } + } else { + HytaleLoggerBackend.REAL_SERR.print(msg); + } + } else if (this.soutwriter != null) { + this.soutwriter.write(msg); + + try { + this.soutwriter.flush(); + } catch (Exception var4) { + } + } else { + HytaleLoggerBackend.REAL_SOUT.print(msg); + } + } catch (Exception var6) { + } + } + + public void setTerminal(String type) { + this.terminalType = type; + } + + private boolean shouldPrintAnsi() { + return !"dumb".equals(this.terminalType); + } + + public HytaleLogFormatter getFormatter() { + return this.formatter; + } +} diff --git a/src/com/hypixel/hytale/logger/backend/HytaleFileHandler.java b/src/com/hypixel/hytale/logger/backend/HytaleFileHandler.java new file mode 100644 index 0000000..814f85d --- /dev/null +++ b/src/com/hypixel/hytale/logger/backend/HytaleFileHandler.java @@ -0,0 +1,107 @@ +package com.hypixel.hytale.logger.backend; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HytaleFileHandler extends Thread { + public static final DateTimeFormatter LOG_FILE_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"); + public static final HytaleFileHandler INSTANCE = new HytaleFileHandler(); + private final BlockingQueue logRecords = new LinkedBlockingQueue<>(); + @Nullable + private FileHandler fileHandler; + + public HytaleFileHandler() { + super("HytaleLogger"); + this.setDaemon(true); + } + + @Override + public void run() { + if (this.fileHandler == null) { + throw new IllegalStateException("Thread should not be started when no file handler exists!"); + } else { + try { + while (!this.isInterrupted()) { + this.fileHandler.publish(this.logRecords.take()); + } + } catch (InterruptedException var2) { + Thread.currentThread().interrupt(); + } + } + } + + @Nullable + public FileHandler getFileHandler() { + return this.fileHandler; + } + + public void enable() { + if (this.fileHandler != null) { + throw new IllegalStateException("Already enabled!"); + } else { + try { + Path logsDirectory = Paths.get("logs/"); + if (!Files.isDirectory(logsDirectory)) { + Files.createDirectory(logsDirectory); + } + + String fileNamePart = "logs/" + LOG_FILE_DATE_FORMAT.format(LocalDateTime.now()); + String fileName = fileNamePart + "_server.log"; + if (Files.exists(Paths.get(fileName))) { + fileName = fileNamePart + "%u_server.log"; + } + + this.fileHandler = new FileHandler(fileName); + this.fileHandler.setEncoding("UTF-8"); + this.fileHandler.setLevel(Level.ALL); + this.fileHandler.setFormatter(new HytaleLogFormatter(() -> false)); + } catch (IOException var4) { + throw new RuntimeException("Failed to create file handler!", var4); + } + + this.start(); + } + } + + public void log(@Nonnull LogRecord logRecord) { + if (!this.isAlive()) { + if (this.fileHandler != null) { + this.fileHandler.publish(logRecord); + } + } else { + this.logRecords.add(logRecord); + } + } + + public void shutdown() { + if (this.fileHandler != null) { + this.interrupt(); + + try { + this.join(); + } catch (InterruptedException var2) { + Thread.currentThread().interrupt(); + } + + List list = new ObjectArrayList<>(); + this.logRecords.drainTo(list); + list.forEach(this.fileHandler::publish); + this.fileHandler.flush(); + this.fileHandler.close(); + this.fileHandler = null; + } + } +} diff --git a/src/com/hypixel/hytale/logger/backend/HytaleLogFormatter.java b/src/com/hypixel/hytale/logger/backend/HytaleLogFormatter.java new file mode 100644 index 0000000..3f8a74e --- /dev/null +++ b/src/com/hypixel/hytale/logger/backend/HytaleLogFormatter.java @@ -0,0 +1,122 @@ +package com.hypixel.hytale.logger.backend; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.function.BooleanSupplier; +import java.util.logging.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; + +public class HytaleLogFormatter extends Formatter { + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); + private static final Pattern ANSI_CONTROL_CODES = Pattern.compile("\u001b\\[[;\\d]*m"); + private BooleanSupplier ansi; + public int maxModuleName; + private int shorterCount; + + public HytaleLogFormatter(BooleanSupplier ansi) { + this.ansi = ansi; + } + + @Nonnull + @Override + public String format(@Nonnull LogRecord record) { + String message = record.getMessage(); + if (message == null) { + message = "null"; + } + + if (record.getParameters() != null && record.getParameters().length > 0) { + try { + message = String.format(message, record.getParameters()); + } catch (RuntimeException var9) { + throw new IllegalArgumentException("Error logging using format string: " + record.getMessage(), var9); + } + } + + if (record.getThrown() != null) { + StringWriter writer = new StringWriter(); + record.getThrown().printStackTrace(new PrintWriter(writer)); + message = message + "\n" + writer.toString(); + } + + boolean ansi = this.ansi.getAsBoolean(); + if (ansi) { + message = message + "\u001b[m"; + } + + if (record instanceof HytaleLoggerBackend.RawLogRecord) { + return !ansi ? stripAnsi(message) + "\n" : message + "\n"; + } else { + String loggerName = record.getLoggerName(); + int moduleNameTextSize = loggerName.length() + 3; + if ((moduleNameTextSize <= this.maxModuleName || moduleNameTextSize >= 35) && this.shorterCount <= 500) { + if (moduleNameTextSize < this.maxModuleName) { + this.shorterCount++; + } + } else { + this.maxModuleName = moduleNameTextSize; + this.shorterCount = 0; + } + + StringBuilder sb = new StringBuilder(33 + this.maxModuleName + message.length()); + if (ansi) { + String color = null; + int level = record.getLevel().intValue(); + if (level <= Level.ALL.intValue()) { + color = "\u001b[37m"; + } else if (level <= Level.FINEST.intValue()) { + color = "\u001b[36m"; + } else if (level <= Level.FINER.intValue()) { + color = "\u001b[34m"; + } else if (level <= Level.FINE.intValue()) { + color = "\u001b[35m"; + } else if (level <= Level.CONFIG.intValue()) { + color = "\u001b[32m"; + } else if (level <= Level.INFO.intValue()) { + color = "\u001b[m"; + } else if (level <= Level.WARNING.intValue()) { + color = "\u001b[33m"; + } else if (level <= Level.SEVERE.intValue()) { + color = "\u001b[31m"; + } + + if (color != null) { + sb.append(color); + } + } + + sb.append('['); + DATE_FORMATTER.formatTo(LocalDateTime.ofInstant(record.getInstant(), ZoneOffset.UTC), sb); + sb.append(' '); + String levelx; + if (Level.WARNING.equals(record.getLevel())) { + levelx = "WARN"; + } else { + levelx = record.getLevel().getName(); + } + + int levelLength = levelx.length(); + if (levelLength < 6) { + sb.append(" ".repeat(6 - levelLength)); + sb.append(levelx); + } else { + sb.append(levelx, 0, 6); + } + + sb.append("] "); + sb.append(" ".repeat(Math.max(0, this.maxModuleName - moduleNameTextSize))); + sb.append('[').append(loggerName).append("] ").append(ansi ? message : stripAnsi(message)).append('\n'); + return sb.toString(); + } + } + + public static String stripAnsi(@Nonnull String message) { + return ANSI_CONTROL_CODES.matcher(message).replaceAll(""); + } +} diff --git a/src/com/hypixel/hytale/logger/backend/HytaleLogManager.java b/src/com/hypixel/hytale/logger/backend/HytaleLogManager.java new file mode 100644 index 0000000..1e82fe5 --- /dev/null +++ b/src/com/hypixel/hytale/logger/backend/HytaleLogManager.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.logger.backend; + +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import javax.annotation.Nonnull; + +public class HytaleLogManager extends LogManager { + public static HytaleLogManager instance; + + public HytaleLogManager() { + instance = this; + this.getLogger("Hytale"); + } + + @Override + public void reset() { + } + + private void reset0() { + super.reset(); + } + + @Nonnull + @Override + public Logger getLogger(@Nonnull String name) { + Logger logger = super.getLogger(name); + return (Logger)(logger != null ? logger : new HytaleLogManager.HytaleJdkLogger(HytaleLoggerBackend.getLogger(name))); + } + + public static void resetFinally() { + HytaleConsole.INSTANCE.shutdown(); + HytaleFileHandler.INSTANCE.shutdown(); + if (instance != null) { + instance.reset0(); + } + } + + private static class HytaleJdkLogger extends Logger { + @Nonnull + private final HytaleLoggerBackend backend; + + public HytaleJdkLogger(@Nonnull HytaleLoggerBackend backend) { + super(backend.getLoggerName(), null); + this.backend = backend; + } + + @Override + public String getName() { + return this.backend.getLoggerName(); + } + + @Nonnull + @Override + public Level getLevel() { + return this.backend.getLevel(); + } + + @Override + public boolean isLoggable(@Nonnull Level level) { + return this.backend.isLoggable(level); + } + + @Override + public void log(@Nonnull LogRecord record) { + this.backend.log(record); + } + + @Override + public void setLevel(@Nonnull Level newLevel) { + this.backend.setLevel(newLevel); + } + } +} diff --git a/src/com/hypixel/hytale/logger/backend/HytaleLoggerBackend.java b/src/com/hypixel/hytale/logger/backend/HytaleLoggerBackend.java new file mode 100644 index 0000000..bdba31e --- /dev/null +++ b/src/com/hypixel/hytale/logger/backend/HytaleLoggerBackend.java @@ -0,0 +1,216 @@ +package com.hypixel.hytale.logger.backend; + +import com.google.common.flogger.backend.LogData; +import com.google.common.flogger.backend.LoggerBackend; +import com.google.common.flogger.backend.system.SimpleLogRecord; +import com.hypixel.hytale.logger.sentry.HytaleSentryHandler; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import io.sentry.IScopes; +import java.io.PrintStream; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HytaleLoggerBackend extends LoggerBackend { + public static Function LOG_LEVEL_LOADER; + public static final PrintStream REAL_SOUT = System.out; + public static final PrintStream REAL_SERR = System.err; + private static final Map CACHE = new ConcurrentHashMap<>(); + private static final HytaleLoggerBackend ROOT_LOGGER = new HytaleLoggerBackend("Hytale", null); + private static final int OFF_VALUE = Level.OFF.intValue(); + private final String name; + private final HytaleLoggerBackend parent; + @Nonnull + private Level level = Level.INFO; + private BiConsumer onLevelChange; + @Nullable + private HytaleSentryHandler sentryHandler; + private boolean propagateSentryToParent = true; + @Nonnull + private CopyOnWriteArrayList> subscribers = new CopyOnWriteArrayList<>(); + + protected HytaleLoggerBackend(String name) { + this.name = name; + this.parent = ROOT_LOGGER; + } + + protected HytaleLoggerBackend(String name, HytaleLoggerBackend parent) { + this.name = name; + this.parent = parent; + } + + @Override + public String getLoggerName() { + return this.name; + } + + @Nonnull + public Level getLevel() { + return this.level; + } + + @Override + public boolean isLoggable(@Nonnull Level lvl) { + int levelValue = this.level.intValue(); + return lvl.intValue() >= levelValue && levelValue != OFF_VALUE; + } + + @Override + public void log(@Nonnull LogData data) { + this.log(SimpleLogRecord.create(data)); + } + + @Override + public void handleError(@Nonnull RuntimeException error, @Nonnull LogData badData) { + this.log(SimpleLogRecord.error(error, badData)); + } + + public void log(@Nonnull LogRecord logRecord) { + this.log(logRecord, false); + } + + public void log(@Nonnull LogRecord logRecord, boolean sentryHandled) { + if (this.sentryHandler != null && !sentryHandled && logRecord.getThrown() != null && !SkipSentryException.hasSkipSentry(logRecord.getThrown())) { + this.sentryHandler.publish(logRecord); + sentryHandled = true; + } + + if (!this.propagateSentryToParent && !sentryHandled && logRecord.getThrown() != null) { + sentryHandled = true; + } + + if (this.parent != null) { + this.parent.log(logRecord, sentryHandled); + } else { + HytaleFileHandler.INSTANCE.log(logRecord); + HytaleConsole.INSTANCE.publish(logRecord); + + for (int i = 0; i < this.subscribers.size(); i++) { + this.subscribers.get(i).add(logRecord); + } + } + } + + public static void subscribe(CopyOnWriteArrayList subscriber) { + if (!ROOT_LOGGER.subscribers.contains(subscriber)) { + ROOT_LOGGER.subscribers.add(subscriber); + } + } + + public static void unsubscribe(CopyOnWriteArrayList subscriber) { + if (ROOT_LOGGER.subscribers.contains(subscriber)) { + ROOT_LOGGER.subscribers.remove(subscriber); + } + } + + @Nonnull + public HytaleLoggerBackend getSubLogger(String name) { + HytaleLoggerBackend hytaleLoggerBackend = new HytaleLoggerBackend(this.name + "][" + name, this); + hytaleLoggerBackend.loadLogLevel(); + return hytaleLoggerBackend; + } + + public void setSentryClient(@Nullable IScopes scope) { + if (scope != null) { + this.sentryHandler = new HytaleSentryHandler(scope); + this.sentryHandler.setLevel(Level.ALL); + } else { + this.sentryHandler = null; + } + } + + public void setPropagatesSentryToParent(boolean propagate) { + this.propagateSentryToParent = propagate; + } + + public void setOnLevelChange(BiConsumer onLevelChange) { + this.onLevelChange = onLevelChange; + } + + public void setLevel(@Nonnull Level newLevel) { + Level old = this.level; + this.level = newLevel; + if (this.onLevelChange != null && !Objects.equals(old, newLevel)) { + this.onLevelChange.accept(old, newLevel); + } + } + + public void loadLogLevel() { + if (this.name != null && LOG_LEVEL_LOADER != null) { + Level level = LOG_LEVEL_LOADER.apply(this.name); + if (level != null) { + this.setLevel(level); + } + } + } + + public static void loadLevels(@Nonnull List> list) { + for (Entry e : list) { + getLogger(e.getKey()).setLevel(e.getValue()); + } + } + + public static void reloadLogLevels() { + CACHE.values().forEach(HytaleLoggerBackend::loadLogLevel); + } + + public static HytaleLoggerBackend getLogger() { + return ROOT_LOGGER; + } + + public static HytaleLoggerBackend getLogger(@Nonnull String name) { + if (name.isEmpty()) { + return getLogger(); + } else { + HytaleLoggerBackend logger = CACHE.computeIfAbsent(name, HytaleLoggerBackend::new); + logger.loadLogLevel(); + return logger; + } + } + + @Nonnull + public static HytaleLoggerBackend getLogger(String name, BiConsumer onLevelChange) { + HytaleLoggerBackend logger = CACHE.computeIfAbsent(name, HytaleLoggerBackend::new); + logger.setOnLevelChange(onLevelChange); + logger.loadLogLevel(); + return logger; + } + + public static void setIndent(int indent) { + HytaleConsole.INSTANCE.getFormatter().maxModuleName = indent; + FileHandler fileHandler = HytaleFileHandler.INSTANCE.getFileHandler(); + if (fileHandler != null) { + ((HytaleLogFormatter)fileHandler.getFormatter()).maxModuleName = indent; + } + } + + public static boolean isJunitTest() { + for (StackTraceElement element : Thread.currentThread().getStackTrace()) { + if (element.getClassName().startsWith("org.junit.")) { + return true; + } + } + + return false; + } + + public static void rawLog(String message) { + ROOT_LOGGER.log(new HytaleLoggerBackend.RawLogRecord(Level.ALL, message)); + } + + public static class RawLogRecord extends LogRecord { + public RawLogRecord(@Nonnull Level level, String msg) { + super(level, msg); + } + } +} diff --git a/src/com/hypixel/hytale/logger/backend/HytaleUncaughtExceptionHandler.java b/src/com/hypixel/hytale/logger/backend/HytaleUncaughtExceptionHandler.java new file mode 100644 index 0000000..21a75c5 --- /dev/null +++ b/src/com/hypixel/hytale/logger/backend/HytaleUncaughtExceptionHandler.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.logger.backend; + +import com.hypixel.hytale.logger.HytaleLogger; +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.logging.Level; + +public class HytaleUncaughtExceptionHandler implements UncaughtExceptionHandler { + public static final HytaleUncaughtExceptionHandler INSTANCE = new HytaleUncaughtExceptionHandler(); + + public HytaleUncaughtExceptionHandler() { + } + + public static void setup() { + Thread.setDefaultUncaughtExceptionHandler(INSTANCE); + System.setProperty("java.util.concurrent.ForkJoinPool.common.exceptionHandler", HytaleUncaughtExceptionHandler.class.getName()); + } + + @Override + public void uncaughtException(Thread t, Throwable e) { + HytaleLogger.getLogger().at(Level.SEVERE).withCause(e).log("Exception in thread: %s", t); + } +} diff --git a/src/com/hypixel/hytale/logger/sentry/HytaleSentryHandler.java b/src/com/hypixel/hytale/logger/sentry/HytaleSentryHandler.java new file mode 100644 index 0000000..c5738c4 --- /dev/null +++ b/src/com/hypixel/hytale/logger/sentry/HytaleSentryHandler.java @@ -0,0 +1,312 @@ +package com.hypixel.hytale.logger.sentry; + +import io.sentry.Breadcrumb; +import io.sentry.Hint; +import io.sentry.IScopes; +import io.sentry.ScopesAdapter; +import io.sentry.Sentry; +import io.sentry.SentryAttribute; +import io.sentry.SentryAttributes; +import io.sentry.SentryEvent; +import io.sentry.SentryIntegrationPackageStorage; +import io.sentry.SentryLevel; +import io.sentry.SentryLogLevel; +import io.sentry.exception.ExceptionMechanismException; +import io.sentry.logger.SentryLogParameters; +import io.sentry.protocol.Mechanism; +import io.sentry.protocol.Message; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.logging.Filter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HytaleSentryHandler extends Handler { + public static final String MECHANISM_TYPE = "JulSentryHandler"; + public static final String THREAD_ID = "thread_id"; + private final IScopes scope; + private boolean printfStyle; + @Nonnull + private Level minimumBreadcrumbLevel = Level.INFO; + @Nonnull + private Level minimumEventLevel = Level.SEVERE; + @Nonnull + private Level minimumLevel = Level.INFO; + + public HytaleSentryHandler(@Nonnull IScopes scope) { + this(scope, true); + } + + HytaleSentryHandler(@Nonnull IScopes scope, boolean configureFromLogManager) { + this.setFilter(new HytaleSentryHandler.DropSentryFilter()); + if (configureFromLogManager) { + this.retrieveProperties(); + } + + this.scope = scope; + } + + @Override + public void publish(@Nonnull LogRecord record) { + if (this.isLoggable(record)) { + try { + if (ScopesAdapter.getInstance().getOptions().getLogs().isEnabled() && record.getLevel().intValue() >= this.minimumLevel.intValue()) { + this.captureLog(record); + } + + if (record.getLevel().intValue() >= this.minimumEventLevel.intValue()) { + Hint hint = new Hint(); + hint.set("syntheticException", record); + this.scope.captureEvent(this.createEvent(record), hint); + } + + if (record.getLevel().intValue() >= this.minimumBreadcrumbLevel.intValue()) { + Hint hint = new Hint(); + hint.set("jul:logRecord", record); + Sentry.addBreadcrumb(this.createBreadcrumb(record), hint); + } + } catch (RuntimeException var3) { + this.reportError("An exception occurred while creating a new event in Sentry", var3, 1); + } + } + } + + protected void captureLog(@Nonnull LogRecord loggingEvent) { + SentryLogLevel sentryLevel = toSentryLogLevel(loggingEvent.getLevel()); + Object[] arguments = loggingEvent.getParameters(); + SentryAttributes attributes = SentryAttributes.of(); + String message = loggingEvent.getMessage(); + if (loggingEvent.getResourceBundle() != null && loggingEvent.getResourceBundle().containsKey(loggingEvent.getMessage())) { + message = loggingEvent.getResourceBundle().getString(loggingEvent.getMessage()); + } + + String formattedMessage = this.maybeFormatted(arguments, message); + if (!formattedMessage.equals(message)) { + attributes.add(SentryAttribute.stringAttribute("sentry.message.template", message)); + } + + SentryLogParameters params = SentryLogParameters.create(attributes); + params.setOrigin("auto.log.jul.hytale"); + Sentry.logger().log(sentryLevel, params, formattedMessage, arguments); + } + + @Nonnull + private String maybeFormatted(@Nonnull Object[] arguments, @Nonnull String message) { + if (arguments != null) { + try { + return this.formatMessage(message, arguments); + } catch (RuntimeException var4) { + } + } + + return message; + } + + private void retrieveProperties() { + LogManager manager = LogManager.getLogManager(); + String className = HytaleSentryHandler.class.getName(); + this.setPrintfStyle(Boolean.parseBoolean(manager.getProperty(className + ".printfStyle"))); + this.setLevel(this.parseLevelOrDefault(manager.getProperty(className + ".level"))); + String minimumBreadCrumbLevel = manager.getProperty(className + ".minimumBreadcrumbLevel"); + if (minimumBreadCrumbLevel != null) { + this.setMinimumBreadcrumbLevel(this.parseLevelOrDefault(minimumBreadCrumbLevel)); + } + + String minimumEventLevel = manager.getProperty(className + ".minimumEventLevel"); + if (minimumEventLevel != null) { + this.setMinimumEventLevel(this.parseLevelOrDefault(minimumEventLevel)); + } + + String minimumLevel = manager.getProperty(className + ".minimumLevel"); + if (minimumLevel != null) { + this.setMinimumLevel(this.parseLevelOrDefault(minimumLevel)); + } + } + + @Nullable + private static SentryLevel formatLevel(@Nonnull Level level) { + if (level.intValue() >= Level.SEVERE.intValue()) { + return SentryLevel.ERROR; + } else if (level.intValue() >= Level.WARNING.intValue()) { + return SentryLevel.WARNING; + } else if (level.intValue() >= Level.INFO.intValue()) { + return SentryLevel.INFO; + } else { + return level.intValue() >= Level.ALL.intValue() ? SentryLevel.DEBUG : null; + } + } + + @Nonnull + private static SentryLogLevel toSentryLogLevel(@Nonnull Level level) { + if (level.intValue() >= Level.SEVERE.intValue()) { + return SentryLogLevel.ERROR; + } else if (level.intValue() >= Level.WARNING.intValue()) { + return SentryLogLevel.WARN; + } else if (level.intValue() >= Level.INFO.intValue()) { + return SentryLogLevel.INFO; + } else { + return level.intValue() >= Level.FINE.intValue() ? SentryLogLevel.DEBUG : SentryLogLevel.TRACE; + } + } + + @Nonnull + private Level parseLevelOrDefault(@Nonnull String levelName) { + try { + return Level.parse(levelName.trim()); + } catch (RuntimeException var3) { + return Level.WARNING; + } + } + + @Nonnull + private Breadcrumb createBreadcrumb(@Nonnull LogRecord record) { + Breadcrumb breadcrumb = new Breadcrumb(); + breadcrumb.setLevel(formatLevel(record.getLevel())); + breadcrumb.setCategory(record.getLoggerName()); + if (record.getParameters() != null) { + try { + breadcrumb.setMessage(this.formatMessage(record.getMessage(), record.getParameters())); + } catch (RuntimeException var4) { + breadcrumb.setMessage(record.getMessage()); + } + } else { + breadcrumb.setMessage(record.getMessage()); + } + + return breadcrumb; + } + + @Nonnull + SentryEvent createEvent(@Nonnull LogRecord record) { + SentryEvent event = new SentryEvent(new Date(record.getMillis())); + event.setLevel(formatLevel(record.getLevel())); + event.setLogger(record.getLoggerName()); + Message sentryMessage = new Message(); + sentryMessage.setParams(this.toParams(record.getParameters())); + String message = record.getMessage(); + if (record.getResourceBundle() != null && record.getResourceBundle().containsKey(record.getMessage())) { + message = record.getResourceBundle().getString(record.getMessage()); + } + + sentryMessage.setMessage(message); + if (record.getParameters() != null) { + try { + sentryMessage.setFormatted(this.formatMessage(message, record.getParameters())); + } catch (RuntimeException var8) { + } + } + + event.setMessage(sentryMessage); + Throwable throwable = record.getThrown(); + if (throwable != null) { + Mechanism mechanism = new Mechanism(); + mechanism.setType("JulSentryHandler"); + Throwable mechanismException = new ExceptionMechanismException(mechanism, throwable, Thread.currentThread()); + event.setThrowable(mechanismException); + } + + event.setExtra("thread_id", String.valueOf(record.getLongThreadID())); + return event; + } + + @Nonnull + private List toParams(@Nullable Object[] arguments) { + List result = new ArrayList<>(); + if (arguments != null) { + for (Object argument : arguments) { + if (argument != null) { + result.add(argument.toString()); + } + } + } + + return result; + } + + @Nonnull + private String formatMessage(@Nonnull String message, @Nullable Object[] parameters) { + String formatted; + if (this.printfStyle) { + formatted = String.format(message, parameters); + } else { + formatted = MessageFormat.format(message, parameters); + } + + return formatted; + } + + @Override + public void flush() { + } + + @Override + public void close() throws SecurityException { + try { + Sentry.close(); + } catch (RuntimeException var2) { + this.reportError("An exception occurred while closing the Sentry connection", var2, 3); + } + } + + public void setPrintfStyle(boolean printfStyle) { + this.printfStyle = printfStyle; + } + + public void setMinimumBreadcrumbLevel(@Nullable Level minimumBreadcrumbLevel) { + if (minimumBreadcrumbLevel != null) { + this.minimumBreadcrumbLevel = minimumBreadcrumbLevel; + } + } + + @Nonnull + public Level getMinimumBreadcrumbLevel() { + return this.minimumBreadcrumbLevel; + } + + public void setMinimumEventLevel(@Nullable Level minimumEventLevel) { + if (minimumEventLevel != null) { + this.minimumEventLevel = minimumEventLevel; + } + } + + @Nonnull + public Level getMinimumEventLevel() { + return this.minimumEventLevel; + } + + public void setMinimumLevel(@Nullable Level minimumLevel) { + if (minimumLevel != null) { + this.minimumLevel = minimumLevel; + } + } + + @Nonnull + public Level getMinimumLevel() { + return this.minimumLevel; + } + + public boolean isPrintfStyle() { + return this.printfStyle; + } + + static { + SentryIntegrationPackageStorage.getInstance().addPackage("maven:io.sentry:sentry-jul", "8.29.0"); + } + + private static final class DropSentryFilter implements Filter { + private DropSentryFilter() { + } + + @Override + public boolean isLoggable(@Nonnull LogRecord record) { + String loggerName = record.getLoggerName(); + return loggerName == null || !loggerName.startsWith("server.io.sentry") || loggerName.startsWith("server.io.sentry.samples"); + } + } +} diff --git a/src/com/hypixel/hytale/logger/sentry/SkipSentryException.java b/src/com/hypixel/hytale/logger/sentry/SkipSentryException.java new file mode 100644 index 0000000..b3821dd --- /dev/null +++ b/src/com/hypixel/hytale/logger/sentry/SkipSentryException.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.logger.sentry; + +public class SkipSentryException extends RuntimeException { + public SkipSentryException() { + } + + public SkipSentryException(Throwable cause) { + super(cause); + } + + public SkipSentryException(String message, Throwable cause) { + super(message, cause); + } + + public static boolean hasSkipSentry(Throwable thrown) { + for (Throwable throwable = thrown; throwable != null; throwable = throwable.getCause()) { + if (throwable instanceof SkipSentryException) { + return true; + } + + if (throwable.getCause() == throwable) { + return false; + } + } + + return false; + } +} diff --git a/src/com/hypixel/hytale/logger/util/GithubMessageUtil.java b/src/com/hypixel/hytale/logger/util/GithubMessageUtil.java new file mode 100644 index 0000000..34d8cbb --- /dev/null +++ b/src/com/hypixel/hytale/logger/util/GithubMessageUtil.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.logger.util; + +import javax.annotation.Nonnull; + +public class GithubMessageUtil { + private static final String CI = System.getenv("CI"); + + public GithubMessageUtil() { + } + + public static boolean isGithub() { + return CI != null; + } + + @Nonnull + public static String messageError(@Nonnull String file, int line, int column, @Nonnull String message) { + return "::error file=%s,line=%d,col=%d::%s\n".formatted(file, line, column, message.replace("\n", "%0A")); + } + + @Nonnull + public static String messageError(@Nonnull String file, @Nonnull String message) { + return "::error file=%s::%s\n".formatted(file, message.replace("\n", "%0A")); + } + + @Nonnull + public static String messageWarning(@Nonnull String file, int line, int column, @Nonnull String message) { + return "::warning file=%s,line=%d,col=%d::%s\n".formatted(file, line, column, message.replace("\n", "%0A")); + } + + @Nonnull + public static String messageWarning(@Nonnull String file, @Nonnull String message) { + return "::warning file=%s::%s\n".formatted(file, message.replace("\n", "%0A")); + } +} diff --git a/src/com/hypixel/hytale/logger/util/LoggerPrintStream.java b/src/com/hypixel/hytale/logger/util/LoggerPrintStream.java new file mode 100644 index 0000000..d41c43d --- /dev/null +++ b/src/com/hypixel/hytale/logger/util/LoggerPrintStream.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.logger.util; + +import com.hypixel.hytale.logger.HytaleLogger; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.logging.Level; + +public class LoggerPrintStream extends PrintStream { + private final HytaleLogger logger; + private final Level level; + private final ByteArrayOutputStream bufferedOutput; + private int last; + + public LoggerPrintStream(HytaleLogger logger, Level level) { + super(new ByteArrayOutputStream()); + this.logger = logger; + this.level = level; + this.bufferedOutput = (ByteArrayOutputStream)super.out; + this.last = -1; + } + + @Override + public void write(int b) { + if (this.last == 13 && b == 10) { + this.last = -1; + } else { + if (b != 10 && b != 13) { + super.write(b); + } else { + try { + this.logger.at(this.level).log(this.bufferedOutput.toString()); + } finally { + this.bufferedOutput.reset(); + } + } + + this.last = b; + } + } + + @Override + public void write(byte[] buf, int off, int len) { + if (len < 0) { + throw new ArrayIndexOutOfBoundsException(len); + } else { + for (int i = 0; i < len; i++) { + this.write(buf[off + i]); + } + } + } + + public HytaleLogger getLogger() { + return this.logger; + } + + public Level getLevel() { + return this.level; + } +} diff --git a/src/com/hypixel/hytale/math/Axis.java b/src/com/hypixel/hytale/math/Axis.java new file mode 100644 index 0000000..57c5f70 --- /dev/null +++ b/src/com/hypixel/hytale/math/Axis.java @@ -0,0 +1,108 @@ +package com.hypixel.hytale.math; + +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public enum Axis { + X(new Vector3i(1, 0, 0)), + Y(new Vector3i(0, 1, 0)), + Z(new Vector3i(0, 0, 1)); + + private final Vector3i direction; + + private Axis(@Nonnull final Vector3i direction) { + this.direction = direction; + } + + @Nonnull + public Vector3i getDirection() { + return this.direction.clone(); + } + + public void rotate(@Nonnull Vector3i vector, int angle) { + if (angle < 0) { + angle = Math.floorMod(angle, 360); + } + + for (int i = angle; i > 0; i -= 90) { + this.rotate(vector); + } + } + + public void rotate(@Nonnull Vector3d vector, int angle) { + if (angle < 0) { + angle = Math.floorMod(angle, 360); + } + + for (int i = angle; i > 0; i -= 90) { + this.rotate(vector); + } + } + + public void rotate(@Nonnull Vector3i vector) { + switch (this) { + case X: + vector.assign(vector.getX(), -vector.getZ(), vector.getY()); + break; + case Y: + vector.assign(vector.getZ(), vector.getY(), -vector.getX()); + break; + case Z: + vector.assign(-vector.getY(), vector.getX(), vector.getZ()); + } + } + + public void rotate(@Nonnull Vector3d vector) { + switch (this) { + case X: + vector.assign(vector.getX(), -vector.getZ(), vector.getY()); + break; + case Y: + vector.assign(vector.getZ(), vector.getY(), -vector.getX()); + break; + case Z: + vector.assign(-vector.getY(), vector.getX(), vector.getZ()); + } + } + + public void flip(@Nonnull Vector3i vector) { + switch (this) { + case X: + vector.assign(-vector.getX(), vector.getY(), vector.getZ()); + break; + case Y: + vector.assign(vector.getX(), -vector.getY(), vector.getZ()); + break; + case Z: + vector.assign(vector.getX(), vector.getY(), -vector.getZ()); + } + } + + public void flip(@Nonnull Vector3d vector) { + switch (this) { + case X: + vector.assign(-vector.getX(), vector.getY(), vector.getZ()); + break; + case Y: + vector.assign(vector.getX(), -vector.getY(), vector.getZ()); + break; + case Z: + vector.assign(vector.getX(), vector.getY(), -vector.getZ()); + } + } + + public void flipRotation(@Nonnull Vector3f rotation) { + switch (this) { + case X: + rotation.setYaw(-rotation.getYaw()); + break; + case Y: + rotation.setPitch(-rotation.getPitch()); + break; + case Z: + rotation.setYaw((float) Math.PI - rotation.getYaw()); + } + } +} diff --git a/src/com/hypixel/hytale/math/Mat4f.java b/src/com/hypixel/hytale/math/Mat4f.java new file mode 100644 index 0000000..9b54ecc --- /dev/null +++ b/src/com/hypixel/hytale/math/Mat4f.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.math; + +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class Mat4f { + public static final int SIZE = 64; + public final float m11; + public final float m12; + public final float m13; + public final float m14; + public final float m21; + public final float m22; + public final float m23; + public final float m24; + public final float m31; + public final float m32; + public final float m33; + public final float m34; + public final float m41; + public final float m42; + public final float m43; + public final float m44; + + public Mat4f( + float m11, + float m12, + float m13, + float m14, + float m21, + float m22, + float m23, + float m24, + float m31, + float m32, + float m33, + float m34, + float m41, + float m42, + float m43, + float m44 + ) { + this.m11 = m11; + this.m12 = m12; + this.m13 = m13; + this.m14 = m14; + this.m21 = m21; + this.m22 = m22; + this.m23 = m23; + this.m24 = m24; + this.m31 = m31; + this.m32 = m32; + this.m33 = m33; + this.m34 = m34; + this.m41 = m41; + this.m42 = m42; + this.m43 = m43; + this.m44 = m44; + } + + @Nonnull + public static Mat4f identity() { + return new Mat4f(1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F); + } + + @Nonnull + public static Mat4f deserialize(@Nonnull ByteBuf buf, int offset) { + return new Mat4f( + Float.intBitsToFloat(buf.getIntLE(offset)), + Float.intBitsToFloat(buf.getIntLE(offset + 4)), + Float.intBitsToFloat(buf.getIntLE(offset + 8)), + Float.intBitsToFloat(buf.getIntLE(offset + 12)), + Float.intBitsToFloat(buf.getIntLE(offset + 16)), + Float.intBitsToFloat(buf.getIntLE(offset + 20)), + Float.intBitsToFloat(buf.getIntLE(offset + 24)), + Float.intBitsToFloat(buf.getIntLE(offset + 28)), + Float.intBitsToFloat(buf.getIntLE(offset + 32)), + Float.intBitsToFloat(buf.getIntLE(offset + 36)), + Float.intBitsToFloat(buf.getIntLE(offset + 40)), + Float.intBitsToFloat(buf.getIntLE(offset + 44)), + Float.intBitsToFloat(buf.getIntLE(offset + 48)), + Float.intBitsToFloat(buf.getIntLE(offset + 52)), + Float.intBitsToFloat(buf.getIntLE(offset + 56)), + Float.intBitsToFloat(buf.getIntLE(offset + 60)) + ); + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(Float.floatToRawIntBits(this.m11)); + buf.writeIntLE(Float.floatToRawIntBits(this.m12)); + buf.writeIntLE(Float.floatToRawIntBits(this.m13)); + buf.writeIntLE(Float.floatToRawIntBits(this.m14)); + buf.writeIntLE(Float.floatToRawIntBits(this.m21)); + buf.writeIntLE(Float.floatToRawIntBits(this.m22)); + buf.writeIntLE(Float.floatToRawIntBits(this.m23)); + buf.writeIntLE(Float.floatToRawIntBits(this.m24)); + buf.writeIntLE(Float.floatToRawIntBits(this.m31)); + buf.writeIntLE(Float.floatToRawIntBits(this.m32)); + buf.writeIntLE(Float.floatToRawIntBits(this.m33)); + buf.writeIntLE(Float.floatToRawIntBits(this.m34)); + buf.writeIntLE(Float.floatToRawIntBits(this.m41)); + buf.writeIntLE(Float.floatToRawIntBits(this.m42)); + buf.writeIntLE(Float.floatToRawIntBits(this.m43)); + buf.writeIntLE(Float.floatToRawIntBits(this.m44)); + } +} diff --git a/src/com/hypixel/hytale/math/Quatf.java b/src/com/hypixel/hytale/math/Quatf.java new file mode 100644 index 0000000..f83161c --- /dev/null +++ b/src/com/hypixel/hytale/math/Quatf.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.math; + +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class Quatf { + public static final int SIZE = 16; + public final float x; + public final float y; + public final float z; + public final float w; + + public Quatf(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + @Nonnull + public static Quatf deserialize(@Nonnull ByteBuf buf, int offset) { + return new Quatf( + Float.intBitsToFloat(buf.getIntLE(offset)), + Float.intBitsToFloat(buf.getIntLE(offset + 4)), + Float.intBitsToFloat(buf.getIntLE(offset + 8)), + Float.intBitsToFloat(buf.getIntLE(offset + 12)) + ); + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(Float.floatToRawIntBits(this.x)); + buf.writeIntLE(Float.floatToRawIntBits(this.y)); + buf.writeIntLE(Float.floatToRawIntBits(this.z)); + buf.writeIntLE(Float.floatToRawIntBits(this.w)); + } +} diff --git a/src/com/hypixel/hytale/math/Range.java b/src/com/hypixel/hytale/math/Range.java new file mode 100644 index 0000000..2c97ffe --- /dev/null +++ b/src/com/hypixel/hytale/math/Range.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.math; + +import javax.annotation.Nonnull; + +public class Range { + private float min; + private float max; + + public Range() { + } + + public Range(float min, float max) { + this.min = min; + this.max = max; + } + + public float getMin() { + return this.min; + } + + public float getMax() { + return this.max; + } + + @Nonnull + @Override + public String toString() { + return "Range{min=" + this.min + ", max='" + this.max + "'}"; + } +} diff --git a/src/com/hypixel/hytale/math/Vec2f.java b/src/com/hypixel/hytale/math/Vec2f.java new file mode 100644 index 0000000..6da0ad5 --- /dev/null +++ b/src/com/hypixel/hytale/math/Vec2f.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.math; + +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public final class Vec2f { + public static final int SIZE = 8; + public float x; + public float y; + + public Vec2f(float x, float y) { + this.x = x; + this.y = y; + } + + public Vec2f() { + } + + @Nonnull + public static Vec2f deserialize(@Nonnull ByteBuf buf, int offset) { + return new Vec2f(Float.intBitsToFloat(buf.getIntLE(offset)), Float.intBitsToFloat(buf.getIntLE(offset + 4))); + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(Float.floatToRawIntBits(this.x)); + buf.writeIntLE(Float.floatToRawIntBits(this.y)); + } + + @Override + public String toString() { + return "Vec2f(" + this.x + ", " + this.y + ")"; + } +} diff --git a/src/com/hypixel/hytale/math/Vec3f.java b/src/com/hypixel/hytale/math/Vec3f.java new file mode 100644 index 0000000..cb7d933 --- /dev/null +++ b/src/com/hypixel/hytale/math/Vec3f.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.math; + +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public final class Vec3f { + public static final int SIZE = 12; + public float x; + public float y; + public float z; + + public Vec3f(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public Vec3f() { + } + + @Nonnull + public static Vec3f deserialize(@Nonnull ByteBuf buf, int offset) { + return new Vec3f( + Float.intBitsToFloat(buf.getIntLE(offset)), Float.intBitsToFloat(buf.getIntLE(offset + 4)), Float.intBitsToFloat(buf.getIntLE(offset + 8)) + ); + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(Float.floatToRawIntBits(this.x)); + buf.writeIntLE(Float.floatToRawIntBits(this.y)); + buf.writeIntLE(Float.floatToRawIntBits(this.z)); + } + + @Override + public String toString() { + return "Vec3f(" + this.x + ", " + this.y + ", " + this.z + ")"; + } +} diff --git a/src/com/hypixel/hytale/math/Vec4f.java b/src/com/hypixel/hytale/math/Vec4f.java new file mode 100644 index 0000000..47bac9b --- /dev/null +++ b/src/com/hypixel/hytale/math/Vec4f.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.math; + +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class Vec4f { + public static final int SIZE = 16; + public final float x; + public final float y; + public final float z; + public final float w; + + public Vec4f(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + @Nonnull + public static Vec4f deserialize(@Nonnull ByteBuf buf, int offset) { + return new Vec4f( + Float.intBitsToFloat(buf.getIntLE(offset)), + Float.intBitsToFloat(buf.getIntLE(offset + 4)), + Float.intBitsToFloat(buf.getIntLE(offset + 8)), + Float.intBitsToFloat(buf.getIntLE(offset + 12)) + ); + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(Float.floatToRawIntBits(this.x)); + buf.writeIntLE(Float.floatToRawIntBits(this.y)); + buf.writeIntLE(Float.floatToRawIntBits(this.z)); + buf.writeIntLE(Float.floatToRawIntBits(this.w)); + } +} diff --git a/src/com/hypixel/hytale/math/block/BlockConeUtil.java b/src/com/hypixel/hytale/math/block/BlockConeUtil.java new file mode 100644 index 0000000..55c33e9 --- /dev/null +++ b/src/com/hypixel/hytale/math/block/BlockConeUtil.java @@ -0,0 +1,173 @@ +package com.hypixel.hytale.math.block; + +import com.hypixel.hytale.function.predicate.TriIntObjPredicate; +import javax.annotation.Nonnull; + +public class BlockConeUtil { + public BlockConeUtil() { + } + + public static void forEachBlock( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, T t, @Nonnull TriIntObjPredicate consumer + ) { + if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (height <= 0) { + throw new IllegalArgumentException(String.valueOf(height)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + float radiusXAdjusted = radiusX + 0.41F; + float radiusZAdjusted = radiusZ + 0.41F; + + for (int y = height - 1; y >= 0; y--) { + double rf = 1.0 - (double)y / height; + double dx = radiusXAdjusted * rf; + int maxX; + int minX = -(maxX = (int)dx); + + for (int x = minX; x <= maxX; x++) { + double qx = 1.0 - x * x / (dx * dx); + double dz = Math.sqrt(qx) * radiusZAdjusted * rf; + int maxZ; + int minZ = -(maxZ = (int)dz); + + for (int z = minZ; z <= maxZ; z++) { + if (!consumer.test(originX + x, originY + y, originZ + z, t)) { + return; + } + } + } + } + } + } + + public static void forEachBlock( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, int thickness, T t, @Nonnull TriIntObjPredicate consumer + ) { + forEachBlock(originX, originY, originZ, radiusX, height, radiusZ, thickness, false, t, consumer); + } + + public static void forEachBlock( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, int thickness, boolean capped, T t, @Nonnull TriIntObjPredicate consumer + ) { + if (thickness < 1) { + forEachBlock(originX, originY, originZ, radiusX, height, radiusZ, t, consumer); + } else if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (height <= 0) { + throw new IllegalArgumentException(String.valueOf(height)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + float radiusXAdjusted = radiusX + 0.41F; + + for (int y = height - 1; y >= 0; y--) { + boolean cap = capped && y < thickness; + double rf = 1.0 - (double)y / height; + double dx = radiusXAdjusted * rf; + double dxInvSqr = 1.0 / (dx * dx); + double innerDx = dx > thickness ? dx - thickness : 0.0; + double innerDxInvSqr = innerDx > 0.0 ? 1.0 / (innerDx * innerDx) : 0.0; + int maxX; + int minX = -(maxX = (int)dx); + + for (int x = minX; x <= maxX; x++) { + double dz = Math.sqrt(1.0 - x * x * dxInvSqr) * dx; + int maxZ; + int minZ = -(maxZ = (int)dz); + double innerMaxZ = cap ? 0.0 : Math.sqrt(1.0 - x * x * innerDxInvSqr) * innerDx; + double innerMinZ = cap ? 0.0 : -innerMaxZ; + + for (int z = minZ; z <= maxZ; z++) { + if ((!(z > innerMinZ) || !(z < innerMaxZ)) && !consumer.test(originX + x, originY + y, originZ + z, t)) { + return; + } + } + } + } + } + } + + public static void forEachBlockInverted( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, T t, @Nonnull TriIntObjPredicate consumer + ) { + if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (height <= 0) { + throw new IllegalArgumentException(String.valueOf(height)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + float radiusXAdjusted = radiusX + 0.41F; + float radiusZAdjusted = radiusZ + 0.41F; + + for (int y = height - 1; y >= 0; y--) { + double rf = 1.0 - (double)y / height; + double dx = radiusXAdjusted * rf; + int maxX; + int minX = -(maxX = (int)dx); + + for (int x = minX; x <= maxX; x++) { + double qx = 1.0 - x * x / (dx * dx); + double dz = Math.sqrt(qx) * radiusZAdjusted * rf; + int maxZ; + int minZ = -(maxZ = (int)dz); + + for (int z = minZ; z <= maxZ; z++) { + if (!consumer.test(originX + x, originY + height - 1 - y, originZ + z, t)) { + return; + } + } + } + } + } + } + + public static void forEachBlockInverted( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, int thickness, T t, @Nonnull TriIntObjPredicate consumer + ) { + forEachBlock(originX, originY, originZ, radiusX, height, radiusZ, thickness, false, t, consumer); + } + + public static void forEachBlockInverted( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, int thickness, boolean capped, T t, @Nonnull TriIntObjPredicate consumer + ) { + if (thickness < 1) { + forEachBlockInverted(originX, originY, originZ, radiusX, height, radiusZ, t, consumer); + } else if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (height <= 0) { + throw new IllegalArgumentException(String.valueOf(height)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + float radiusXAdjusted = radiusX + 0.41F; + + for (int y = height - 1; y >= 0; y--) { + boolean cap = capped && y < thickness; + double rf = 1.0 - (double)y / height; + double dx = radiusXAdjusted * rf; + double dxInvSqr = 1.0 / (dx * dx); + double innerDx = dx > thickness ? dx - thickness : 0.0; + double innerDxInvSqr = innerDx > 0.0 ? 1.0 / (innerDx * innerDx) : 0.0; + int maxX; + int minX = -(maxX = (int)dx); + + for (int x = minX; x <= maxX; x++) { + double dz = Math.sqrt(1.0 - x * x * dxInvSqr) * dx; + int maxZ; + int minZ = -(maxZ = (int)dz); + double innerMaxZ = cap ? 0.0 : Math.sqrt(1.0 - x * x * innerDxInvSqr) * innerDx; + double innerMinZ = cap ? 0.0 : -innerMaxZ; + + for (int z = minZ; z <= maxZ; z++) { + if ((!(z > innerMinZ) || !(z < innerMaxZ)) && !consumer.test(originX + x, originY + height - 1 - y, originZ + z, t)) { + return; + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/math/block/BlockCubeUtil.java b/src/com/hypixel/hytale/math/block/BlockCubeUtil.java new file mode 100644 index 0000000..00847d3 --- /dev/null +++ b/src/com/hypixel/hytale/math/block/BlockCubeUtil.java @@ -0,0 +1,144 @@ +package com.hypixel.hytale.math.block; + +import com.hypixel.hytale.function.predicate.TriIntObjPredicate; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class BlockCubeUtil { + public BlockCubeUtil() { + } + + public static boolean forEachBlock( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, T t, @Nonnull TriIntObjPredicate consumer + ) { + int radiusY = height / 2; + + for (int dx = -radiusX; dx <= radiusX; dx++) { + for (int dz = -radiusZ; dz <= radiusZ; dz++) { + for (int dy = -radiusY; dy <= radiusY; dy++) { + if (!consumer.test(originX + dx, originY + dy, originZ + dz, t)) { + return false; + } + } + } + } + + return true; + } + + public static boolean forEachBlock( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, int thickness, T t, @Nonnull TriIntObjPredicate consumer + ) { + return forEachBlock(originX, originY, originZ, radiusX, height, radiusZ, thickness, false, t, consumer); + } + + public static boolean forEachBlock( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, int thickness, boolean capped, T t, @Nonnull TriIntObjPredicate consumer + ) { + return forEachBlock(originX, originY, originZ, radiusX, height, radiusZ, thickness, capped, capped, false, t, consumer); + } + + public static boolean forEachBlock( + int originX, + int originY, + int originZ, + int radiusX, + int height, + int radiusZ, + int thickness, + boolean cappedTop, + boolean cappedBottom, + boolean hollow, + T t, + @Nonnull TriIntObjPredicate consumer + ) { + if (thickness < 1) { + return forEachBlock(originX, originY, originZ, radiusX, height, radiusZ, t, consumer); + } else { + int radiusY = height / 2; + int innerMinX = -radiusX + thickness; + int innerMaxX = radiusX - thickness; + int innerMinZ = -radiusZ + thickness; + int innerMaxZ = radiusZ - thickness; + int innerMinY = cappedBottom ? -radiusY + thickness : -height; + int innerMaxY = cappedTop ? radiusY - thickness : height; + + for (int dx = -radiusX; dx <= radiusX; dx++) { + for (int dz = -radiusZ; dz <= radiusZ; dz++) { + for (int dy = -radiusY; dy <= radiusY; dy++) { + if (dy < innerMinY || dy > innerMaxY || dx < innerMinX || dx > innerMaxX || dz < innerMinZ || dz > innerMaxZ) { + if (!consumer.test(originX + dx, originY + dy, originZ + dz, t)) { + return false; + } + } else if (hollow && !consumer.test(originX + dx, originY + dy, originZ + dz, t)) { + return false; + } + } + } + } + + return true; + } + } + + public static boolean forEachBlock(@Nonnull Vector3i pointOne, @Nonnull Vector3i pointTwo, T t, @Nonnull TriIntObjPredicate consumer) { + Vector3i min = Vector3i.min(pointOne, pointTwo); + Vector3i max = Vector3i.max(pointOne, pointTwo); + + for (int x = min.x; x <= max.x; x++) { + for (int z = min.z; z <= max.z; z++) { + for (int y = min.y; y <= max.y; y++) { + if (!consumer.test(x, y, z, t)) { + return false; + } + } + } + } + + return true; + } + + public static boolean forEachBlock( + @Nonnull Vector3i pointOne, + @Nonnull Vector3i pointTwo, + int thickness, + boolean cappedTop, + boolean cappedBottom, + boolean hollow, + T t, + @Nonnull TriIntObjPredicate consumer + ) { + if (thickness < 1) { + return forEachBlock(pointOne, pointTwo, t, consumer); + } else { + Vector3i min = Vector3i.min(pointOne, pointTwo); + Vector3i max = Vector3i.max(pointOne, pointTwo); + int innerMinX = min.x + thickness; + int innerMaxX = max.x - thickness; + int innerMinZ = min.z + thickness; + int innerMaxZ = max.z - thickness; + int innerMinY = cappedBottom ? min.y + thickness : min.y; + int innerMaxY = cappedTop ? max.y - thickness : max.y; + + for (int x = min.x; x <= max.x; x++) { + for (int z = min.z; z <= max.z; z++) { + for (int y = min.y; y <= max.y; y++) { + if (hollow) { + if (y >= innerMinY && y <= innerMaxY && x >= innerMinX && x <= innerMaxX && z >= innerMinZ && z <= innerMaxZ && !consumer.test(x, y, z, t) + ) + { + return false; + } + } else if ((y < innerMinY || y > innerMaxY || x < innerMinX || x > innerMaxX || z < innerMinZ || z > innerMaxZ) && !consumer.test(x, y, z, t) + ) + { + return false; + } + } + } + } + + return true; + } + } +} diff --git a/src/com/hypixel/hytale/math/block/BlockCylinderUtil.java b/src/com/hypixel/hytale/math/block/BlockCylinderUtil.java new file mode 100644 index 0000000..746cba0 --- /dev/null +++ b/src/com/hypixel/hytale/math/block/BlockCylinderUtil.java @@ -0,0 +1,111 @@ +package com.hypixel.hytale.math.block; + +import com.hypixel.hytale.function.predicate.TriIntObjPredicate; +import com.hypixel.hytale.math.util.MathUtil; +import javax.annotation.Nonnull; + +public class BlockCylinderUtil { + public BlockCylinderUtil() { + } + + public static boolean forEachBlock( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, T t, @Nonnull TriIntObjPredicate consumer + ) { + if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (height <= 0) { + throw new IllegalArgumentException(String.valueOf(height)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + float radiusXAdjusted = radiusX + 0.41F; + float radiusZAdjusted = radiusZ + 0.41F; + double invRadiusXSqr = 1.0 / (radiusXAdjusted * radiusXAdjusted); + + for (int x = -radiusX; x <= radiusX; x++) { + double qx = 1.0 - x * x * invRadiusXSqr; + double dz = Math.sqrt(qx) * radiusZAdjusted; + int maxZ; + int minZ = -(maxZ = (int)dz); + + for (int z = minZ; z <= maxZ; z++) { + for (int y = height - 1; y >= 0; y--) { + if (!consumer.test(originX + x, originY + y, originZ + z, t)) { + return false; + } + } + } + } + + return true; + } + } + + public static boolean forEachBlock( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, int thickness, T t, @Nonnull TriIntObjPredicate consumer + ) { + return forEachBlock(originX, originY, originZ, radiusX, height, radiusZ, thickness, false, t, consumer); + } + + public static boolean forEachBlock( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, int thickness, boolean capped, T t, @Nonnull TriIntObjPredicate consumer + ) { + if (thickness < 1) { + return forEachBlock(originX, originY, originZ, radiusX, height, radiusZ, t, consumer); + } else if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (height <= 0) { + throw new IllegalArgumentException(String.valueOf(height)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + float radiusXAdjusted = radiusX + 0.41F; + float radiusZAdjusted = radiusZ + 0.41F; + float innerRadiusXAdjusted = radiusXAdjusted - thickness; + float innerRadiusZAdjusted = radiusZAdjusted - thickness; + if (!(innerRadiusXAdjusted <= 0.0F) && !(innerRadiusZAdjusted <= 0.0F)) { + double invRadiusXSqr = 1.0 / (radiusXAdjusted * radiusXAdjusted); + double invInnerRadiusXSqr = 1.0 / (innerRadiusXAdjusted * innerRadiusXAdjusted); + int innerMinY = thickness; + int innerMaxY = height - thickness; + + for (int y = height - 1; y >= 0; y--) { + boolean cap = capped && (y < innerMinY || y > innerMaxY); + + for (int x = -radiusX; x <= radiusX; x++) { + double qx = 1.0 - x * x * invRadiusXSqr; + double dz = Math.sqrt(qx) * radiusZAdjusted; + int maxZ = (int)dz; + double innerQx = x < innerRadiusXAdjusted ? 1.0 - x * x * invInnerRadiusXSqr : 0.0; + double innerDZ = innerQx > 0.0 ? Math.sqrt(innerQx) * innerRadiusZAdjusted : 0.0; + int minZ = cap ? 0 : MathUtil.ceil(innerDZ); + int z = minZ; + if (minZ == 0) { + if (!consumer.test(originX + x, originY + y, originZ, t)) { + return false; + } + + z = minZ + 1; + } + + while (z <= maxZ) { + if (!consumer.test(originX + x, originY + y, originZ + z, t)) { + return false; + } + + if (!consumer.test(originX + x, originY + y, originZ - z, t)) { + return false; + } + + z++; + } + } + } + + return true; + } else { + return forEachBlock(originX, originY, originZ, radiusX, height, radiusZ, t, consumer); + } + } + } +} diff --git a/src/com/hypixel/hytale/math/block/BlockDiamondUtil.java b/src/com/hypixel/hytale/math/block/BlockDiamondUtil.java new file mode 100644 index 0000000..9d7fdb0 --- /dev/null +++ b/src/com/hypixel/hytale/math/block/BlockDiamondUtil.java @@ -0,0 +1,131 @@ +package com.hypixel.hytale.math.block; + +import com.hypixel.hytale.function.predicate.TriIntObjPredicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockDiamondUtil { + public BlockDiamondUtil() { + } + + public static boolean forEachBlock( + int originX, int originY, int originZ, int radiusX, int radiusY, int radiusZ, @Nullable T t, @Nonnull TriIntObjPredicate consumer + ) { + if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (radiusY <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusY)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + float radiusXAdjusted = radiusX + 0.41F; + float radiusZAdjusted = radiusZ + 0.41F; + + for (int y = 0; y <= radiusY; y++) { + float normalizedY = (float)y / radiusY; + float currentRadiusX = radiusXAdjusted * (1.0F - normalizedY); + float currentRadiusZ = radiusZAdjusted * (1.0F - normalizedY); + int maxX = (int)currentRadiusX; + int maxZ = (int)currentRadiusZ; + + for (int x = 0; x <= maxX; x++) { + for (int z = 0; z <= maxZ; z++) { + if (Math.abs(x) <= currentRadiusX && Math.abs(z) <= currentRadiusZ && !test(originX, originY, originZ, x, y, z, t, consumer)) { + return false; + } + } + } + } + + return true; + } + } + + public static boolean forEachBlock( + int originX, + int originY, + int originZ, + int radiusX, + int radiusY, + int radiusZ, + int thickness, + boolean capped, + @Nullable T t, + @Nonnull TriIntObjPredicate consumer + ) { + if (thickness < 1) { + return forEachBlock(originX, originY, originZ, radiusX, radiusY, radiusZ, t, consumer); + } else if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (radiusY <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusY)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + float radiusXAdjusted = radiusX + 0.41F; + float radiusZAdjusted = radiusZ + 0.41F; + + for (int y = 0; y <= radiusY; y++) { + float normalizedY = (float)y / radiusY; + float currentRadiusX = radiusXAdjusted * (1.0F - normalizedY); + float currentRadiusZ = radiusZAdjusted * (1.0F - normalizedY); + float innerRadiusX = Math.max(0.0F, currentRadiusX - thickness); + float innerRadiusZ = Math.max(0.0F, currentRadiusZ - thickness); + int maxX = (int)currentRadiusX; + int maxZ = (int)currentRadiusZ; + + for (int x = 0; x <= maxX; x++) { + for (int z = 0; z <= maxZ; z++) { + boolean inOuter = Math.abs(x) <= currentRadiusX && Math.abs(z) <= currentRadiusZ; + if (inOuter) { + boolean inInner = Math.abs(x) < innerRadiusX && Math.abs(z) < innerRadiusZ; + if (!inInner && !test(originX, originY, originZ, x, y, z, t, consumer)) { + return false; + } + } + } + } + } + + return true; + } + } + + private static boolean test(int originX, int originY, int originZ, int x, int y, int z, T context, @Nonnull TriIntObjPredicate consumer) { + if (!consumer.test(originX + x, originY + y, originZ + z, context)) { + return false; + } else if (y > 0 && !consumer.test(originX + x, originY - y, originZ + z, context)) { + return false; + } else { + if (x > 0) { + if (!consumer.test(originX - x, originY + y, originZ + z, context)) { + return false; + } + + if (y > 0 && !consumer.test(originX - x, originY - y, originZ + z, context)) { + return false; + } + + if (z > 0 && !consumer.test(originX - x, originY + y, originZ - z, context)) { + return false; + } + + if (y > 0 && z > 0 && !consumer.test(originX - x, originY - y, originZ - z, context)) { + return false; + } + } + + if (z > 0) { + if (!consumer.test(originX + x, originY + y, originZ - z, context)) { + return false; + } + + if (y > 0 && !consumer.test(originX + x, originY - y, originZ - z, context)) { + return false; + } + } + + return true; + } + } +} diff --git a/src/com/hypixel/hytale/math/block/BlockDomeUtil.java b/src/com/hypixel/hytale/math/block/BlockDomeUtil.java new file mode 100644 index 0000000..a1a8293 --- /dev/null +++ b/src/com/hypixel/hytale/math/block/BlockDomeUtil.java @@ -0,0 +1,151 @@ +package com.hypixel.hytale.math.block; + +import com.hypixel.hytale.function.predicate.TriIntObjPredicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockDomeUtil { + public BlockDomeUtil() { + } + + public static boolean forEachBlock( + int originX, int originY, int originZ, int radiusX, int radiusY, int radiusZ, @Nullable T t, @Nonnull TriIntObjPredicate consumer + ) { + if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (radiusY <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusY)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + float radiusXAdjusted = radiusX + 0.41F; + float radiusYAdjusted = radiusY + 0.41F; + float radiusZAdjusted = radiusZ + 0.41F; + float invRadiusXSqr = 1.0F / (radiusXAdjusted * radiusXAdjusted); + float invRadiusYSqr = 1.0F / (radiusYAdjusted * radiusYAdjusted); + + for (int x = 0; x <= radiusX; x++) { + float qx = 1.0F - x * x * invRadiusXSqr; + double dy = Math.sqrt(qx) * radiusYAdjusted; + int maxY = (int)dy; + + for (int y = 0; y <= maxY; y++) { + double dz = Math.sqrt(qx - y * y * invRadiusYSqr) * radiusZAdjusted; + int maxZ = (int)dz; + + for (int z = 0; z <= maxZ; z++) { + if (!test(originX, originY, originZ, x, y, z, t, consumer)) { + return false; + } + } + } + } + + return true; + } + } + + public static boolean forEachBlock( + int originX, + int originY, + int originZ, + int radiusX, + int radiusY, + int radiusZ, + int thickness, + boolean capped, + @Nullable T t, + @Nonnull TriIntObjPredicate consumer + ) { + if (thickness < 1) { + return forEachBlock(originX, originY, originZ, radiusX, radiusY, radiusZ, t, consumer); + } else if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (radiusY <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusY)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + float radiusXAdjusted = radiusX + 0.41F; + float radiusYAdjusted = radiusY + 0.41F; + float radiusZAdjusted = radiusZ + 0.41F; + float innerRadiusXAdjusted = radiusXAdjusted - thickness; + float innerRadiusYAdjusted = radiusYAdjusted - thickness; + float innerRadiusZAdjusted = radiusZAdjusted - thickness; + float invRadiusX2 = 1.0F / (radiusXAdjusted * radiusXAdjusted); + float invRadiusY2 = 1.0F / (radiusYAdjusted * radiusYAdjusted); + float invRadiusZ2 = 1.0F / (radiusZAdjusted * radiusZAdjusted); + float invInnerRadiusX2 = 1.0F / (innerRadiusXAdjusted * innerRadiusXAdjusted); + float invInnerRadiusY2 = 1.0F / (innerRadiusYAdjusted * innerRadiusYAdjusted); + float invInnerRadiusZ2 = 1.0F / (innerRadiusZAdjusted * innerRadiusZAdjusted); + int y = 0; + + for (int y1 = 1; y <= radiusY; y1++) { + float qy = y * y * invRadiusY2; + double dx = Math.sqrt(1.0F - qy) * radiusXAdjusted; + int maxX = (int)dx; + float innerQy = y * y * invInnerRadiusY2; + float outerQy = y1 * y1 * invRadiusY2; + boolean isAtBase = y == 0 && capped; + int x = 0; + + for (int x1 = 1; x <= maxX; x1++) { + float qx = x * x * invRadiusX2; + double dz = Math.sqrt(1.0F - qx - qy) * radiusZAdjusted; + int maxZ = (int)dz; + float innerQx = x * x * invInnerRadiusX2; + float outerQx = x1 * x1 * invRadiusX2; + int z = 0; + + for (int z1 = 1; z <= maxZ; z1++) { + float innerQz = z * z * invInnerRadiusZ2; + if (isAtBase) { + if (!test(originX, originY, originZ, x, y, z, t, consumer)) { + return false; + } + } else { + label60: { + if (innerQx + innerQy + innerQz < 1.0F) { + float outerQz = z1 * z1 * invRadiusZ2; + if (outerQx + outerQy + outerQz < 1.0F) { + break label60; + } + } + + if (!test(originX, originY, originZ, x, y, z, t, consumer)) { + return false; + } + } + } + + z++; + } + + x++; + } + + y++; + } + + return true; + } + } + + private static boolean test(int originX, int originY, int originZ, int x, int y, int z, T context, @Nonnull TriIntObjPredicate consumer) { + if (!consumer.test(originX + x, originY + y, originZ + z, context)) { + return false; + } else { + if (x > 0) { + if (!consumer.test(originX - x, originY + y, originZ + z, context)) { + return false; + } + + if (z > 0 && !consumer.test(originX - x, originY + y, originZ - z, context)) { + return false; + } + } + + return z > 0 ? consumer.test(originX + x, originY + y, originZ - z, context) : true; + } + } +} diff --git a/src/com/hypixel/hytale/math/block/BlockInvertedDomeUtil.java b/src/com/hypixel/hytale/math/block/BlockInvertedDomeUtil.java new file mode 100644 index 0000000..64066f3 --- /dev/null +++ b/src/com/hypixel/hytale/math/block/BlockInvertedDomeUtil.java @@ -0,0 +1,151 @@ +package com.hypixel.hytale.math.block; + +import com.hypixel.hytale.function.predicate.TriIntObjPredicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockInvertedDomeUtil { + public BlockInvertedDomeUtil() { + } + + public static boolean forEachBlock( + int originX, int originY, int originZ, int radiusX, int radiusY, int radiusZ, @Nullable T t, @Nonnull TriIntObjPredicate consumer + ) { + if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (radiusY <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusY)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + float radiusXAdjusted = radiusX + 0.41F; + float radiusYAdjusted = radiusY + 0.41F; + float radiusZAdjusted = radiusZ + 0.41F; + float invRadiusXSqr = 1.0F / (radiusXAdjusted * radiusXAdjusted); + float invRadiusYSqr = 1.0F / (radiusYAdjusted * radiusYAdjusted); + + for (int x = 0; x <= radiusX; x++) { + float qx = 1.0F - x * x * invRadiusXSqr; + double dy = Math.sqrt(qx) * radiusYAdjusted; + int maxY = (int)dy; + + for (int y = 0; y <= maxY; y++) { + double dz = Math.sqrt(qx - y * y * invRadiusYSqr) * radiusZAdjusted; + int maxZ = (int)dz; + + for (int z = 0; z <= maxZ; z++) { + if (!test(originX, originY, originZ, x, -y, z, t, consumer)) { + return false; + } + } + } + } + + return true; + } + } + + public static boolean forEachBlock( + int originX, + int originY, + int originZ, + int radiusX, + int radiusY, + int radiusZ, + int thickness, + boolean capped, + @Nullable T t, + @Nonnull TriIntObjPredicate consumer + ) { + if (thickness < 1) { + return forEachBlock(originX, originY, originZ, radiusX, radiusY, radiusZ, t, consumer); + } else if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (radiusY <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusY)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + float radiusXAdjusted = radiusX + 0.41F; + float radiusYAdjusted = radiusY + 0.41F; + float radiusZAdjusted = radiusZ + 0.41F; + float innerRadiusXAdjusted = radiusXAdjusted - thickness; + float innerRadiusYAdjusted = radiusYAdjusted - thickness; + float innerRadiusZAdjusted = radiusZAdjusted - thickness; + float invRadiusX2 = 1.0F / (radiusXAdjusted * radiusXAdjusted); + float invRadiusY2 = 1.0F / (radiusYAdjusted * radiusYAdjusted); + float invRadiusZ2 = 1.0F / (radiusZAdjusted * radiusZAdjusted); + float invInnerRadiusX2 = 1.0F / (innerRadiusXAdjusted * innerRadiusXAdjusted); + float invInnerRadiusY2 = 1.0F / (innerRadiusYAdjusted * innerRadiusYAdjusted); + float invInnerRadiusZ2 = 1.0F / (innerRadiusZAdjusted * innerRadiusZAdjusted); + int y = 0; + + for (int y1 = 1; y <= radiusY; y1++) { + float qy = y * y * invRadiusY2; + double dx = Math.sqrt(1.0F - qy) * radiusXAdjusted; + int maxX = (int)dx; + float innerQy = y * y * invInnerRadiusY2; + float outerQy = y1 * y1 * invRadiusY2; + boolean isAtTop = y == 0 && capped; + int x = 0; + + for (int x1 = 1; x <= maxX; x1++) { + float qx = x * x * invRadiusX2; + double dz = Math.sqrt(1.0F - qx - qy) * radiusZAdjusted; + int maxZ = (int)dz; + float innerQx = x * x * invInnerRadiusX2; + float outerQx = x1 * x1 * invRadiusX2; + int z = 0; + + for (int z1 = 1; z <= maxZ; z1++) { + float innerQz = z * z * invInnerRadiusZ2; + if (isAtTop) { + if (!test(originX, originY, originZ, x, -y, z, t, consumer)) { + return false; + } + } else { + label60: { + if (innerQx + innerQy + innerQz < 1.0F) { + float outerQz = z1 * z1 * invRadiusZ2; + if (outerQx + outerQy + outerQz < 1.0F) { + break label60; + } + } + + if (!test(originX, originY, originZ, x, -y, z, t, consumer)) { + return false; + } + } + } + + z++; + } + + x++; + } + + y++; + } + + return true; + } + } + + private static boolean test(int originX, int originY, int originZ, int x, int y, int z, T context, @Nonnull TriIntObjPredicate consumer) { + if (!consumer.test(originX + x, originY + y, originZ + z, context)) { + return false; + } else { + if (x > 0) { + if (!consumer.test(originX - x, originY + y, originZ + z, context)) { + return false; + } + + if (z > 0 && !consumer.test(originX - x, originY + y, originZ - z, context)) { + return false; + } + } + + return z > 0 ? consumer.test(originX + x, originY + y, originZ - z, context) : true; + } + } +} diff --git a/src/com/hypixel/hytale/math/block/BlockPyramidUtil.java b/src/com/hypixel/hytale/math/block/BlockPyramidUtil.java new file mode 100644 index 0000000..e827ddb --- /dev/null +++ b/src/com/hypixel/hytale/math/block/BlockPyramidUtil.java @@ -0,0 +1,168 @@ +package com.hypixel.hytale.math.block; + +import com.hypixel.hytale.function.predicate.TriIntObjPredicate; +import javax.annotation.Nonnull; + +public class BlockPyramidUtil { + public BlockPyramidUtil() { + } + + public static void forEachBlock( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, T t, @Nonnull TriIntObjPredicate consumer + ) { + if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (height <= 0) { + throw new IllegalArgumentException(String.valueOf(height)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + for (int y = height - 1; y >= 0; y--) { + double rf = 1.0 - (double)y / height; + double dx = radiusX * rf; + int maxX; + int minX = -(maxX = (int)dx); + + for (int x = minX; x <= maxX; x++) { + double dz = radiusZ * rf; + int maxZ; + int minZ = -(maxZ = (int)dz); + + for (int z = minZ; z <= maxZ; z++) { + if (!consumer.test(originX + x, originY + y, originZ + z, t)) { + return; + } + } + } + } + } + } + + public static void forEachBlock( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, int thickness, T t, @Nonnull TriIntObjPredicate consumer + ) { + forEachBlock(originX, originY, originZ, radiusX, height, radiusZ, thickness, false, t, consumer); + } + + public static void forEachBlock( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, int thickness, boolean capped, T t, @Nonnull TriIntObjPredicate consumer + ) { + if (thickness < 1) { + forEachBlock(originX, originY, originZ, radiusX, height, radiusZ, t, consumer); + } else if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (height <= 0) { + throw new IllegalArgumentException(String.valueOf(height)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + double df = 1.0 / height; + + for (int y = height - 1; y >= 0; y--) { + boolean cap = capped && y < thickness; + double rf = 1.0 - y * df; + double dx = rf * radiusX; + double dz = rf * radiusZ; + int maxX; + int minX = -(maxX = (int)dx); + int maxZ; + int minZ = -(maxZ = (int)dz); + double innerRf = rf - df; + double innerDx = innerRf * radiusX; + double innerDz = innerRf * radiusZ; + int innerMinX = cap ? 1 : -((int)innerDx) + thickness; + int innerMaxX = cap ? 0 : (int)innerDx - thickness; + int innerMinZ = cap ? 1 : -((int)innerDz) + thickness; + int innerMaxZ = cap ? 0 : (int)innerDz - thickness; + + for (int x = minX; x <= maxX; x++) { + for (int z = minZ; z <= maxZ; z++) { + if ((x < innerMinX || x > innerMaxX || z < innerMinZ || z > innerMaxZ) && !consumer.test(originX + x, originY + y, originZ + z, t)) { + return; + } + } + } + } + } + } + + public static void forEachBlockInverted( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, T t, @Nonnull TriIntObjPredicate consumer + ) { + if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (height <= 0) { + throw new IllegalArgumentException(String.valueOf(height)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + for (int y = height - 1; y >= 0; y--) { + double rf = 1.0 - (double)y / height; + double dx = radiusX * rf; + int maxX; + int minX = -(maxX = (int)dx); + + for (int x = minX; x <= maxX; x++) { + double dz = radiusZ * rf; + int maxZ; + int minZ = -(maxZ = (int)dz); + + for (int z = minZ; z <= maxZ; z++) { + if (!consumer.test(originX + x, originY + height - 1 - y, originZ + z, t)) { + return; + } + } + } + } + } + } + + public static void forEachBlockInverted( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, int thickness, T t, @Nonnull TriIntObjPredicate consumer + ) { + forEachBlockInverted(originX, originY, originZ, radiusX, height, radiusZ, thickness, false, t, consumer); + } + + public static void forEachBlockInverted( + int originX, int originY, int originZ, int radiusX, int height, int radiusZ, int thickness, boolean capped, T t, @Nonnull TriIntObjPredicate consumer + ) { + if (thickness < 1) { + forEachBlockInverted(originX, originY, originZ, radiusX, height, radiusZ, t, consumer); + } else if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (height <= 0) { + throw new IllegalArgumentException(String.valueOf(height)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + double df = 1.0 / height; + + for (int y = height - 1; y >= 0; y--) { + boolean cap = capped && y < thickness; + double rf = 1.0 - y * df; + double dx = rf * radiusX; + double dz = rf * radiusZ; + int maxX; + int minX = -(maxX = (int)dx); + int maxZ; + int minZ = -(maxZ = (int)dz); + double innerRf = rf - df; + double innerDx = innerRf * radiusX; + double innerDz = innerRf * radiusZ; + int innerMinX = cap ? 1 : -((int)innerDx) + thickness; + int innerMaxX = cap ? 0 : (int)innerDx - thickness; + int innerMinZ = cap ? 1 : -((int)innerDz) + thickness; + int innerMaxZ = cap ? 0 : (int)innerDz - thickness; + + for (int x = minX; x <= maxX; x++) { + for (int z = minZ; z <= maxZ; z++) { + if ((x < innerMinX || x > innerMaxX || z < innerMinZ || z > innerMaxZ) + && !consumer.test(originX + x, originY + height - 1 - y, originZ + z, t)) { + return; + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/math/block/BlockSphereUtil.java b/src/com/hypixel/hytale/math/block/BlockSphereUtil.java new file mode 100644 index 0000000..3440446 --- /dev/null +++ b/src/com/hypixel/hytale/math/block/BlockSphereUtil.java @@ -0,0 +1,191 @@ +package com.hypixel.hytale.math.block; + +import com.hypixel.hytale.function.predicate.TriIntObjPredicate; +import com.hypixel.hytale.math.util.MathUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockSphereUtil { + public BlockSphereUtil() { + } + + public static void forEachBlockExact(int originX, int originY, int originZ, double radius, @Nullable T t, @Nonnull TriIntObjPredicate consumer) { + if (radius <= 0.0) { + throw new IllegalArgumentException(String.valueOf(radius)); + } else { + int ceiledRadius = MathUtil.ceil(radius); + double invRadiusXSqr = 1.0 / (ceiledRadius * ceiledRadius); + double invRadiusYSqr = 1.0 / (ceiledRadius * ceiledRadius); + + for (int x = -ceiledRadius; x <= ceiledRadius; x++) { + double qx = 1.0 - x * x * invRadiusXSqr; + double dy = Math.sqrt(qx) * ceiledRadius; + int maxY; + int minY = -(maxY = (int)dy); + + for (int y = maxY; y >= minY; y--) { + double dz = Math.sqrt(qx - y * y * invRadiusYSqr) * ceiledRadius; + int maxZ; + int minZ = -(maxZ = (int)dz); + + for (int z = minZ; z <= maxZ; z++) { + if (!consumer.test(originX + x, originY + y, originZ + z, t)) { + return; + } + } + } + } + } + } + + public static void forEachBlock(int originX, int originY, int originZ, int radius, @Nullable T t, @Nonnull TriIntObjPredicate consumer) { + forEachBlock(originX, originY, originZ, radius, radius, radius, t, consumer); + } + + public static boolean forEachBlock( + int originX, int originY, int originZ, int radiusX, int radiusY, int radiusZ, @Nullable T t, @Nonnull TriIntObjPredicate consumer + ) { + if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (radiusY <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusY)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + float radiusXAdjusted = radiusX + 0.41F; + float radiusYAdjusted = radiusY + 0.41F; + float radiusZAdjusted = radiusZ + 0.41F; + float invRadiusXSqr = 1.0F / (radiusXAdjusted * radiusXAdjusted); + float invRadiusYSqr = 1.0F / (radiusYAdjusted * radiusYAdjusted); + + for (int x = 0; x <= radiusX; x++) { + float qx = 1.0F - x * x * invRadiusXSqr; + double dy = Math.sqrt(qx) * radiusYAdjusted; + int maxY = (int)dy; + + for (int y = 0; y <= maxY; y++) { + double dz = Math.sqrt(qx - y * y * invRadiusYSqr) * radiusZAdjusted; + int maxZ = (int)dz; + + for (int z = 0; z <= maxZ; z++) { + if (!test(originX, originY, originZ, x, y, z, t, consumer)) { + return false; + } + } + } + } + + return true; + } + } + + public static void forEachBlock(int originX, int originY, int originZ, int radius, int thickness, @Nullable T t, @Nonnull TriIntObjPredicate consumer) { + forEachBlock(originX, originY, originZ, radius, radius, radius, thickness, t, consumer); + } + + public static boolean forEachBlock( + int originX, int originY, int originZ, int radiusX, int radiusY, int radiusZ, int thickness, @Nullable T t, @Nonnull TriIntObjPredicate consumer + ) { + if (thickness < 1) { + return forEachBlock(originX, originY, originZ, radiusX, radiusY, radiusZ, t, consumer); + } else if (radiusX <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusX)); + } else if (radiusY <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusY)); + } else if (radiusZ <= 0) { + throw new IllegalArgumentException(String.valueOf(radiusZ)); + } else { + float radiusXAdjusted = radiusX + 0.41F; + float radiusYAdjusted = radiusY + 0.41F; + float radiusZAdjusted = radiusZ + 0.41F; + float innerRadiusXAdjusted = radiusXAdjusted - thickness; + float innerRadiusYAdjusted = radiusYAdjusted - thickness; + float innerRadiusZAdjusted = radiusZAdjusted - thickness; + float invRadiusX2 = 1.0F / (radiusXAdjusted * radiusXAdjusted); + float invRadiusY2 = 1.0F / (radiusYAdjusted * radiusYAdjusted); + float invRadiusZ2 = 1.0F / (radiusZAdjusted * radiusZAdjusted); + float invInnerRadiusX2 = 1.0F / (innerRadiusXAdjusted * innerRadiusXAdjusted); + float invInnerRadiusY2 = 1.0F / (innerRadiusYAdjusted * innerRadiusYAdjusted); + float invInnerRadiusZ2 = 1.0F / (innerRadiusZAdjusted * innerRadiusZAdjusted); + int y = 0; + + for (int y1 = 1; y <= radiusY; y1++) { + float qy = y * y * invRadiusY2; + double dx = Math.sqrt(1.0F - qy) * radiusXAdjusted; + int maxX = (int)dx; + float innerQy = y * y * invInnerRadiusY2; + float outerQy = y1 * y1 * invRadiusY2; + int x = 0; + + for (int x1 = 1; x <= maxX; x1++) { + float qx = x * x * invRadiusX2; + double dz = Math.sqrt(1.0F - qx - qy) * radiusZAdjusted; + int maxZ = (int)dz; + float innerQx = x * x * invInnerRadiusX2; + float outerQx = x1 * x1 * invRadiusX2; + int z = 0; + + for (int z1 = 1; z <= maxZ; z1++) { + label47: { + float innerQz = z * z * invInnerRadiusZ2; + if (innerQx + innerQy + innerQz < 1.0F) { + float outerQz = z1 * z1 * invRadiusZ2; + if (outerQx + outerQy + outerQz < 1.0F) { + break label47; + } + } + + if (!test(originX, originY, originZ, x, y, z, t, consumer)) { + return false; + } + } + + z++; + } + + x++; + } + + y++; + } + + return true; + } + } + + private static boolean test(int originX, int originY, int originZ, int x, int y, int z, T context, @Nonnull TriIntObjPredicate consumer) { + if (!consumer.test(originX + x, originY + y, originZ + z, context)) { + return false; + } else { + if (x > 0) { + if (!consumer.test(originX - x, originY + y, originZ + z, context)) { + return false; + } + + if (y > 0 && !consumer.test(originX - x, originY - y, originZ + z, context)) { + return false; + } + + if (z > 0 && !consumer.test(originX - x, originY + y, originZ - z, context)) { + return false; + } + + if (y > 0 && z > 0 && !consumer.test(originX - x, originY - y, originZ - z, context)) { + return false; + } + } + + if (y > 0) { + if (!consumer.test(originX + x, originY - y, originZ + z, context)) { + return false; + } + + if (z > 0 && !consumer.test(originX + x, originY - y, originZ - z, context)) { + return false; + } + } + + return z > 0 ? consumer.test(originX + x, originY + y, originZ - z, context) : true; + } + } +} diff --git a/src/com/hypixel/hytale/math/block/BlockTorusUtil.java b/src/com/hypixel/hytale/math/block/BlockTorusUtil.java new file mode 100644 index 0000000..e13619e --- /dev/null +++ b/src/com/hypixel/hytale/math/block/BlockTorusUtil.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.math.block; + +import com.hypixel.hytale.function.predicate.TriIntObjPredicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockTorusUtil { + public BlockTorusUtil() { + } + + public static boolean forEachBlock( + int originX, int originY, int originZ, int outerRadius, int minorRadius, @Nullable T t, @Nonnull TriIntObjPredicate consumer + ) { + if (outerRadius <= 0) { + throw new IllegalArgumentException(String.valueOf(outerRadius)); + } else if (minorRadius <= 0) { + throw new IllegalArgumentException(String.valueOf(minorRadius)); + } else { + int majorRadius = Math.max(1, outerRadius - minorRadius); + int sizeXZ = majorRadius + minorRadius; + float minorRadiusAdjusted = minorRadius + 0.41F; + + for (int x = -sizeXZ; x <= sizeXZ; x++) { + for (int z = -sizeXZ; z <= sizeXZ; z++) { + double distFromCenter = Math.sqrt(x * x + z * z); + double distFromRing = distFromCenter - majorRadius; + + for (int y = -minorRadius; y <= minorRadius; y++) { + double distFromTube = Math.sqrt(distFromRing * distFromRing + y * y); + if (distFromTube <= minorRadiusAdjusted && !consumer.test(originX + x, originY + y, originZ + z, t)) { + return false; + } + } + } + } + + return true; + } + } + + public static boolean forEachBlock( + int originX, + int originY, + int originZ, + int outerRadius, + int minorRadius, + int thickness, + boolean capped, + @Nullable T t, + @Nonnull TriIntObjPredicate consumer + ) { + if (thickness < 1) { + return forEachBlock(originX, originY, originZ, outerRadius, minorRadius, t, consumer); + } else if (outerRadius <= 0) { + throw new IllegalArgumentException(String.valueOf(outerRadius)); + } else if (minorRadius <= 0) { + throw new IllegalArgumentException(String.valueOf(minorRadius)); + } else { + int majorRadius = Math.max(1, outerRadius - minorRadius); + int sizeXZ = majorRadius + minorRadius; + float minorRadiusAdjusted = minorRadius + 0.41F; + float innerMinorRadius = Math.max(0.0F, minorRadiusAdjusted - thickness); + + for (int x = -sizeXZ; x <= sizeXZ; x++) { + for (int z = -sizeXZ; z <= sizeXZ; z++) { + double distFromCenter = Math.sqrt(x * x + z * z); + double distFromRing = distFromCenter - majorRadius; + + for (int y = -minorRadius; y <= minorRadius; y++) { + double distFromTube = Math.sqrt(distFromRing * distFromRing + y * y); + boolean inOuter = distFromTube <= minorRadiusAdjusted; + if (inOuter) { + boolean inInner = distFromTube < innerMinorRadius; + if (!inInner && !consumer.test(originX + x, originY + y, originZ + z, t)) { + return false; + } + } + } + } + } + + return true; + } + } +} diff --git a/src/com/hypixel/hytale/math/block/BlockUtil.java b/src/com/hypixel/hytale/math/block/BlockUtil.java new file mode 100644 index 0000000..5c2f610 --- /dev/null +++ b/src/com/hypixel/hytale/math/block/BlockUtil.java @@ -0,0 +1,82 @@ +package com.hypixel.hytale.math.block; + +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class BlockUtil { + public static final float RADIUS_ADJUST = 0.41F; + public static final long BITS_Y = 9L; + public static final long MAX_Y = 512L; + public static final long MIN_Y = -513L; + public static final long Y_INVERT = -512L; + public static final long Y_MASK = 511L; + public static final long BITS_PER_DIRECTION = 26L; + public static final long MAX = 67108864L; + public static final long MIN = -67108865L; + public static final long DIRECTION_INVERT = -67108864L; + public static final long DIRECTION_MASK = 67108863L; + + public BlockUtil() { + } + + public static long pack(@Nonnull Vector3i val) { + return pack(val.x, val.y, val.z); + } + + public static long pack(int x, int y, int z) { + if (y <= -513L || y >= 512L) { + throw new IllegalArgumentException(String.valueOf(y)); + } else if (x <= -67108865L || x >= 67108864L) { + throw new IllegalArgumentException(String.valueOf(x)); + } else if (z > -67108865L && z < 67108864L) { + long l = (y & 511L) << 54 | (z & 67108863L) << 27 | x & 67108863L; + if (y < 0) { + l |= Long.MIN_VALUE; + } + + if (z < 0) { + l |= 9007199254740992L; + } + + if (x < 0) { + l |= 67108864L; + } + + return l; + } else { + throw new IllegalArgumentException(String.valueOf(z)); + } + } + + public static int unpackX(long packed) { + int i = (int)(packed & 67108863L); + if ((packed & 67108864L) != 0L) { + i = (int)(i | -67108864L); + } + + return i; + } + + public static int unpackY(long packed) { + int i = (int)(packed >> 54 & 511L); + if ((packed & Long.MIN_VALUE) != 0L) { + i = (int)(i | -512L); + } + + return i; + } + + public static int unpackZ(long packed) { + int i = (int)(packed >> 27 & 67108863L); + if ((packed & 9007199254740992L) != 0L) { + i = (int)(i | -67108864L); + } + + return i; + } + + @Nonnull + public static Vector3i unpack(long packed) { + return new Vector3i(unpackX(packed), unpackY(packed), unpackZ(packed)); + } +} diff --git a/src/com/hypixel/hytale/math/codec/FloatRangeArrayCodec.java b/src/com/hypixel/hytale/math/codec/FloatRangeArrayCodec.java new file mode 100644 index 0000000..c9b336b --- /dev/null +++ b/src/com/hypixel/hytale/math/codec/FloatRangeArrayCodec.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.math.codec; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.codecs.simple.FloatCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.codec.validation.ValidatableCodec; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.math.range.FloatRange; +import java.io.IOException; +import java.util.Set; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonDouble; +import org.bson.BsonValue; + +public class FloatRangeArrayCodec implements Codec, ValidatableCodec { + public FloatRangeArrayCodec() { + } + + @Nonnull + public FloatRange decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + BsonArray document = bsonValue.asArray(); + return new FloatRange(FloatCodec.decodeFloat(document.get(0)), FloatCodec.decodeFloat(document.get(1))); + } + + @Nonnull + public BsonValue encode(@Nonnull FloatRange floatRange, ExtraInfo extraInfo) { + BsonArray array = new BsonArray(); + array.add((BsonValue)(new BsonDouble(floatRange.getInclusiveMin()))); + array.add((BsonValue)(new BsonDouble(floatRange.getInclusiveMax()))); + return array; + } + + @Nonnull + public FloatRange decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + reader.expect('['); + reader.consumeWhiteSpace(); + float inclusiveMin = FloatCodec.readFloat(reader); + reader.consumeWhiteSpace(); + reader.expect(','); + reader.consumeWhiteSpace(); + float inclusiveMax = FloatCodec.readFloat(reader); + reader.consumeWhiteSpace(); + reader.expect(']'); + reader.consumeWhiteSpace(); + return new FloatRange(inclusiveMin, inclusiveMax); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + StringSchema stringSchema = new StringSchema(); + stringSchema.setPattern("^(-?Infinity|NaN)$"); + Schema choiceSchema = Schema.anyOf(new NumberSchema(), stringSchema); + choiceSchema.getHytale().setType("Number"); + ArraySchema s = new ArraySchema(); + s.setTitle("FloatRange"); + s.setItems(choiceSchema, choiceSchema); + s.setMinItems(2); + s.setMaxItems(2); + return s; + } + + public void validate(@Nonnull FloatRange floatRange, @Nonnull ExtraInfo extraInfo) { + if (floatRange.getInclusiveMin() > floatRange.getInclusiveMax()) { + ValidationResults results = extraInfo.getValidationResults(); + results.fail(String.format("Min (%f) > Max (%f)", floatRange.getInclusiveMin(), floatRange.getInclusiveMax())); + results._processValidationResults(); + } + } + + @Override + public void validateDefaults(ExtraInfo extraInfo, Set> tested) { + } +} diff --git a/src/com/hypixel/hytale/math/codec/IntRangeArrayCodec.java b/src/com/hypixel/hytale/math/codec/IntRangeArrayCodec.java new file mode 100644 index 0000000..114c1ab --- /dev/null +++ b/src/com/hypixel/hytale/math/codec/IntRangeArrayCodec.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.math.codec; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.IntegerSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.codec.validation.ValidatableCodec; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.math.range.IntRange; +import java.io.IOException; +import java.util.Set; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonDouble; +import org.bson.BsonValue; + +public class IntRangeArrayCodec implements Codec, ValidatableCodec { + public IntRangeArrayCodec() { + } + + @Nonnull + public IntRange decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + BsonArray document = bsonValue.asArray(); + return new IntRange(document.get(0).asNumber().intValue(), document.get(1).asNumber().intValue()); + } + + @Nonnull + public BsonValue encode(@Nonnull IntRange t, ExtraInfo extraInfo) { + BsonArray array = new BsonArray(); + array.add((BsonValue)(new BsonDouble(t.getInclusiveMin()))); + array.add((BsonValue)(new BsonDouble(t.getInclusiveMax()))); + return array; + } + + @Nonnull + public IntRange decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + reader.expect('['); + reader.consumeWhiteSpace(); + int inclusiveMin = reader.readIntValue(); + reader.consumeWhiteSpace(); + reader.expect(','); + reader.consumeWhiteSpace(); + int inclusiveMax = reader.readIntValue(); + reader.consumeWhiteSpace(); + reader.expect(']'); + reader.consumeWhiteSpace(); + return new IntRange(inclusiveMin, inclusiveMax); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ArraySchema s = new ArraySchema(); + s.setTitle("IntRange"); + s.setItems(new IntegerSchema(), new IntegerSchema()); + s.setMinItems(2); + s.setMaxItems(2); + return s; + } + + public void validate(@Nonnull IntRange range, @Nonnull ExtraInfo extraInfo) { + if (range.getInclusiveMin() > range.getInclusiveMax()) { + ValidationResults results = extraInfo.getValidationResults(); + results.fail(String.format("Min (%d) > Max (%d)", range.getInclusiveMin(), range.getInclusiveMax())); + results._processValidationResults(); + } + } + + @Override + public void validateDefaults(ExtraInfo extraInfo, Set> tested) { + } +} diff --git a/src/com/hypixel/hytale/math/codec/Vector2dArrayCodec.java b/src/com/hypixel/hytale/math/codec/Vector2dArrayCodec.java new file mode 100644 index 0000000..c997b4a --- /dev/null +++ b/src/com/hypixel/hytale/math/codec/Vector2dArrayCodec.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.math.codec; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.math.vector.Vector2d; +import java.io.IOException; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonDouble; +import org.bson.BsonValue; + +@Deprecated +public class Vector2dArrayCodec implements Codec { + public Vector2dArrayCodec() { + } + + @Nonnull + public Vector2d decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + BsonArray document = bsonValue.asArray(); + return new Vector2d(document.get(0).asNumber().doubleValue(), document.get(1).asNumber().doubleValue()); + } + + @Nonnull + public BsonValue encode(@Nonnull Vector2d t, ExtraInfo extraInfo) { + BsonArray array = new BsonArray(); + array.add((BsonValue)(new BsonDouble(t.getX()))); + array.add((BsonValue)(new BsonDouble(t.getY()))); + return array; + } + + @Nonnull + public Vector2d decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + reader.expect('['); + reader.consumeWhiteSpace(); + double x = reader.readDoubleValue(); + reader.consumeWhiteSpace(); + reader.expect(','); + reader.consumeWhiteSpace(); + double y = reader.readDoubleValue(); + reader.consumeWhiteSpace(); + reader.expect(']'); + reader.consumeWhiteSpace(); + return new Vector2d(x, y); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ArraySchema s = new ArraySchema(); + s.setTitle("Vector2d"); + s.setItems(new NumberSchema(), new NumberSchema()); + s.setMinItems(2); + s.setMaxItems(2); + return s; + } +} diff --git a/src/com/hypixel/hytale/math/codec/Vector3dArrayCodec.java b/src/com/hypixel/hytale/math/codec/Vector3dArrayCodec.java new file mode 100644 index 0000000..d7757e3 --- /dev/null +++ b/src/com/hypixel/hytale/math/codec/Vector3dArrayCodec.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.math.codec; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.math.vector.Vector3d; +import java.io.IOException; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonDouble; +import org.bson.BsonValue; + +@Deprecated +public class Vector3dArrayCodec implements Codec { + public Vector3dArrayCodec() { + } + + @Nonnull + public Vector3d decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + BsonArray document = bsonValue.asArray(); + return new Vector3d(document.get(0).asNumber().doubleValue(), document.get(1).asNumber().doubleValue(), document.get(2).asNumber().doubleValue()); + } + + @Nonnull + public BsonValue encode(@Nonnull Vector3d t, ExtraInfo extraInfo) { + BsonArray array = new BsonArray(); + array.add((BsonValue)(new BsonDouble(t.getX()))); + array.add((BsonValue)(new BsonDouble(t.getY()))); + array.add((BsonValue)(new BsonDouble(t.getZ()))); + return array; + } + + @Nonnull + public Vector3d decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + reader.expect('['); + reader.consumeWhiteSpace(); + double x = reader.readDoubleValue(); + reader.consumeWhiteSpace(); + reader.expect(','); + reader.consumeWhiteSpace(); + double y = reader.readDoubleValue(); + reader.consumeWhiteSpace(); + reader.expect(','); + reader.consumeWhiteSpace(); + double z = reader.readDoubleValue(); + reader.consumeWhiteSpace(); + reader.expect(']'); + reader.consumeWhiteSpace(); + return new Vector3d(x, y, z); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ArraySchema s = new ArraySchema(); + s.setTitle("Vector3d"); + s.setItems(new NumberSchema(), new NumberSchema(), new NumberSchema()); + s.setMinItems(3); + s.setMaxItems(3); + return s; + } +} diff --git a/src/com/hypixel/hytale/math/codec/Vector3iArrayCodec.java b/src/com/hypixel/hytale/math/codec/Vector3iArrayCodec.java new file mode 100644 index 0000000..7d9b4f7 --- /dev/null +++ b/src/com/hypixel/hytale/math/codec/Vector3iArrayCodec.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.math.codec; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.math.vector.Vector3i; +import java.io.IOException; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonInt32; +import org.bson.BsonValue; + +@Deprecated +public class Vector3iArrayCodec implements Codec { + public Vector3iArrayCodec() { + } + + @Nonnull + public Vector3i decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + BsonArray document = bsonValue.asArray(); + return new Vector3i(document.get(0).asNumber().intValue(), document.get(1).asNumber().intValue(), document.get(2).asNumber().intValue()); + } + + @Nonnull + public BsonValue encode(@Nonnull Vector3i t, ExtraInfo extraInfo) { + BsonArray array = new BsonArray(); + array.add((BsonValue)(new BsonInt32(t.getX()))); + array.add((BsonValue)(new BsonInt32(t.getY()))); + array.add((BsonValue)(new BsonInt32(t.getZ()))); + return array; + } + + @Nonnull + public Vector3i decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + reader.expect('['); + reader.consumeWhiteSpace(); + int x = reader.readIntValue(); + reader.consumeWhiteSpace(); + reader.expect(','); + reader.consumeWhiteSpace(); + int y = reader.readIntValue(); + reader.consumeWhiteSpace(); + reader.expect(','); + reader.consumeWhiteSpace(); + int z = reader.readIntValue(); + reader.consumeWhiteSpace(); + reader.expect(']'); + reader.consumeWhiteSpace(); + return new Vector3i(x, y, z); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ArraySchema s = new ArraySchema(); + s.setTitle("Vector3i"); + s.setItems(new NumberSchema(), new NumberSchema(), new NumberSchema()); + s.setMinItems(3); + s.setMaxItems(3); + return s; + } +} diff --git a/src/com/hypixel/hytale/math/data/VarInt.java b/src/com/hypixel/hytale/math/data/VarInt.java new file mode 100644 index 0000000..d98ca72 --- /dev/null +++ b/src/com/hypixel/hytale/math/data/VarInt.java @@ -0,0 +1,132 @@ +package com.hypixel.hytale.math.data; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import javax.annotation.Nonnull; + +public final class VarInt { + private VarInt() { + throw new UnsupportedOperationException("Do not instantiate."); + } + + public static void writeSignedVarLong(long value, @Nonnull DataOutput out) throws IOException { + writeUnsignedVarLong(value << 1 ^ value >> 63, out); + } + + public static void writeUnsignedVarLong(long value, @Nonnull DataOutput out) throws IOException { + while ((value & -128L) != 0L) { + out.writeByte((int)value & 127 | 128); + value >>>= 7; + } + + out.writeByte((int)value & 127); + } + + public static void writeSignedVarInt(int value, @Nonnull DataOutput out) throws IOException { + writeUnsignedVarInt(value << 1 ^ value >> 31, out); + } + + public static void writeUnsignedVarInt(int value, @Nonnull DataOutput out) throws IOException { + while ((value & -128) != 0L) { + out.writeByte(value & 127 | 128); + value >>>= 7; + } + + out.writeByte(value & 127); + } + + public static byte[] writeSignedVarInt(int value) { + return writeUnsignedVarInt(value << 1 ^ value >> 31); + } + + public static byte[] writeUnsignedVarInt(int value) { + byte[] byteArrayList = new byte[10]; + int i = 0; + + while ((value & -128) != 0L) { + byteArrayList[i++] = (byte)(value & 127 | 128); + value >>>= 7; + } + + byteArrayList[i] = (byte)(value & 127); + + byte[] out; + for (out = new byte[i + 1]; i >= 0; i--) { + out[i] = byteArrayList[i]; + } + + return out; + } + + public static long readSignedVarLong(@Nonnull DataInput in) throws IOException { + long raw = readUnsignedVarLong(in); + long temp = (raw << 63 >> 63 ^ raw) >> 1; + return temp ^ raw & Long.MIN_VALUE; + } + + public static long readUnsignedVarLong(@Nonnull DataInput in) throws IOException { + long value = 0L; + int i = 0; + + long b; + while (((b = in.readByte()) & 128L) != 0L) { + value |= (b & 127L) << i; + i += 7; + if (i > 63) { + throw new IllegalArgumentException("Variable length quantity is too long"); + } + } + + return value | b << i; + } + + public static int readSignedVarInt(@Nonnull DataInput in) throws IOException { + int raw = readUnsignedVarInt(in); + int temp = (raw << 31 >> 31 ^ raw) >> 1; + return temp ^ raw & -2147483648; + } + + public static int readUnsignedVarInt(@Nonnull DataInput in) throws IOException { + int value = 0; + int i = 0; + + int b; + while (((b = in.readByte()) & 128) != 0) { + value |= (b & 127) << i; + i += 7; + if (i > 35) { + throw new IllegalArgumentException("Variable length quantity is too long"); + } + } + + return value | b << i; + } + + public static int readSignedVarInt(@Nonnull byte[] bytes) { + int raw = readUnsignedVarInt(bytes); + int temp = (raw << 31 >> 31 ^ raw) >> 1; + return temp ^ raw & -2147483648; + } + + public static int readUnsignedVarInt(@Nonnull byte[] bytes) { + int value = 0; + int i = 0; + byte rb = -128; + + for (byte b : bytes) { + rb = b; + if ((b & 128) == 0) { + break; + } + + value |= (b & 127) << i; + i += 7; + if (i > 35) { + throw new IllegalArgumentException("Variable length quantity is too long"); + } + } + + return value | rb << i; + } +} diff --git a/src/com/hypixel/hytale/math/hitdetection/HitDetectionBuffer.java b/src/com/hypixel/hytale/math/hitdetection/HitDetectionBuffer.java new file mode 100644 index 0000000..320cf53 --- /dev/null +++ b/src/com/hypixel/hytale/math/hitdetection/HitDetectionBuffer.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.math.hitdetection; + +import com.hypixel.hytale.math.shape.Quad4d; +import com.hypixel.hytale.math.shape.Triangle4d; +import com.hypixel.hytale.math.util.FastRandom; +import com.hypixel.hytale.math.vector.Vector4d; +import java.util.Random; + +public class HitDetectionBuffer { + private static final int VECTOR_BUFFER_SIZE = 16; + public Random random = new FastRandom(); + public Vector4d hitPosition = new Vector4d(); + public Vector4d tempHitPosition = new Vector4d(); + public Quad4d transformedQuad; + public Vector4d transformedPoint = new Vector4d(); + public Triangle4d visibleTriangle; + public Vector4dBufferList vertexList1; + public Vector4dBufferList vertexList2; + public boolean containsFully; + + public HitDetectionBuffer() { + this.transformedQuad = new Quad4d(); + this.visibleTriangle = new Triangle4d(); + this.vertexList1 = new Vector4dBufferList(16); + this.vertexList2 = new Vector4dBufferList(16); + this.containsFully = false; + } +} diff --git a/src/com/hypixel/hytale/math/hitdetection/HitDetectionExecutor.java b/src/com/hypixel/hytale/math/hitdetection/HitDetectionExecutor.java new file mode 100644 index 0000000..1c4b340 --- /dev/null +++ b/src/com/hypixel/hytale/math/hitdetection/HitDetectionExecutor.java @@ -0,0 +1,243 @@ +package com.hypixel.hytale.math.hitdetection; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.matrix.Matrix4d; +import com.hypixel.hytale.math.shape.Quad4d; +import com.hypixel.hytale.math.shape.Triangle4d; +import com.hypixel.hytale.math.vector.Vector4d; +import java.util.Arrays; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class HitDetectionExecutor { + public static final HytaleLogger log = HytaleLogger.forEnclosingClass(); + private static final Vector4d[] VERTEX_POINTS = new Vector4d[]{ + Vector4d.newPosition(0.0, 1.0, 1.0), + Vector4d.newPosition(0.0, 1.0, 0.0), + Vector4d.newPosition(1.0, 1.0, 1.0), + Vector4d.newPosition(1.0, 1.0, 0.0), + Vector4d.newPosition(0.0, 0.0, 1.0), + Vector4d.newPosition(0.0, 0.0, 0.0), + Vector4d.newPosition(1.0, 0.0, 1.0), + Vector4d.newPosition(1.0, 0.0, 0.0) + }; + public static final Quad4d[] CUBE_QUADS = new Quad4d[]{ + new Quad4d(VERTEX_POINTS, 0, 1, 3, 2), + new Quad4d(VERTEX_POINTS, 0, 4, 5, 1), + new Quad4d(VERTEX_POINTS, 4, 5, 7, 6), + new Quad4d(VERTEX_POINTS, 2, 3, 7, 6), + new Quad4d(VERTEX_POINTS, 1, 3, 7, 5), + new Quad4d(VERTEX_POINTS, 0, 2, 6, 4) + }; + @Nonnull + private final Matrix4d pvmMatrix = new Matrix4d(); + @Nonnull + private final Matrix4d invPvMatrix = new Matrix4d(); + @Nonnull + private final Vector4d origin = new Vector4d(); + @Nonnull + private final HitDetectionBuffer buffer = new HitDetectionBuffer(); + private MatrixProvider projectionProvider; + private MatrixProvider viewProvider; + private LineOfSightProvider losProvider = LineOfSightProvider.DEFAULT_TRUE; + private int maxRayTests = 10; + + public HitDetectionExecutor() { + } + + public Vector4d getHitLocation() { + return this.buffer.hitPosition; + } + + @Nonnull + public HitDetectionExecutor setProjectionProvider(MatrixProvider provider) { + this.projectionProvider = provider; + return this; + } + + @Nonnull + public HitDetectionExecutor setViewProvider(MatrixProvider provider) { + this.viewProvider = provider; + return this; + } + + @Nonnull + public HitDetectionExecutor setLineOfSightProvider(LineOfSightProvider losProvider) { + this.losProvider = losProvider; + return this; + } + + @Nonnull + public HitDetectionExecutor setMaxRayTests(int maxRayTests) { + this.maxRayTests = maxRayTests; + return this; + } + + @Nonnull + public HitDetectionExecutor setOrigin(double x, double y, double z) { + this.origin.assign(x, y, z, 1.0); + return this; + } + + private void setupMatrices(@Nonnull Matrix4d modelMatrix) { + Matrix4d projectionMatrix = this.projectionProvider.getMatrix(); + Matrix4d viewMatrix = this.viewProvider.getMatrix(); + this.pvmMatrix.assign(projectionMatrix).multiply(viewMatrix); + this.invPvMatrix.assign(this.pvmMatrix).invert(); + this.pvmMatrix.multiply(modelMatrix); + } + + public boolean test(@Nonnull Vector4d point, @Nonnull Matrix4d modelMatrix) { + this.setupMatrices(modelMatrix); + return this.testPoint(point); + } + + public boolean test(@Nonnull Quad4d[] model, @Nonnull Matrix4d modelMatrix) { + try { + this.setupMatrices(modelMatrix); + return this.testModel(model); + } catch (Throwable var4) { + log.at(Level.SEVERE).withCause(var4).log("Error occured during Hit Detection execution. Dumping parameters!"); + log.at(Level.SEVERE).log("this = %s", this); + log.at(Level.SEVERE).log("model = %s", Arrays.toString((Object[])model)); + log.at(Level.SEVERE).log("modelMatrix = %s", modelMatrix); + log.at(Level.SEVERE).log("thread = %s", Thread.currentThread().getName()); + throw var4; + } + } + + private boolean testPoint(@Nonnull Vector4d point) { + this.pvmMatrix.multiply(point, this.buffer.transformedPoint); + if (!this.buffer.transformedPoint.isInsideFrustum()) { + return false; + } else { + Vector4d hit = this.buffer.transformedPoint; + this.invPvMatrix.multiply(hit); + hit.perspectiveTransform(); + return this.losProvider.test(this.origin.x, this.origin.y, this.origin.z, hit.x, hit.y, hit.z); + } + } + + private boolean testModel(@Nonnull Quad4d[] model) { + int testsDone = 0; + double minDistanceSquared = Double.POSITIVE_INFINITY; + + for (Quad4d quad : model) { + if (testsDone++ == this.maxRayTests) { + return false; + } + + quad.multiply(this.pvmMatrix, this.buffer.transformedQuad); + if (this.insideFrustum()) { + Vector4d hit = this.buffer.tempHitPosition; + if (this.buffer.containsFully) { + this.buffer.transformedQuad.getRandom(this.buffer.random, hit); + } else { + this.buffer.visibleTriangle.getRandom(this.buffer.random, hit); + } + + this.invPvMatrix.multiply(hit); + hit.perspectiveTransform(); + double dx = this.origin.x - hit.x; + double dy = this.origin.y - hit.y; + double dz = this.origin.z - hit.z; + double distanceSquared = dx * dx + dy * dy + dz * dz; + if (!(distanceSquared >= minDistanceSquared) && this.losProvider.test(this.origin.x, this.origin.y, this.origin.z, hit.x, hit.y, hit.z)) { + minDistanceSquared = distanceSquared; + this.buffer.hitPosition.assign(hit); + } + } + } + + return minDistanceSquared != Double.POSITIVE_INFINITY; + } + + protected boolean insideFrustum() { + Quad4d quad = this.buffer.transformedQuad; + if (quad.isFullyInsideFrustum()) { + this.buffer.containsFully = true; + return true; + } else { + this.buffer.containsFully = false; + Vector4dBufferList vertices = this.buffer.vertexList1; + Vector4dBufferList auxillaryList = this.buffer.vertexList2; + vertices.clear(); + auxillaryList.clear(); + vertices.next().assign(quad.getA()); + vertices.next().assign(quad.getB()); + vertices.next().assign(quad.getC()); + vertices.next().assign(quad.getD()); + if (this.clipPolygonAxis(0) && this.clipPolygonAxis(1) && this.clipPolygonAxis(2)) { + Vector4d initialVertex = vertices.get(0); + int i = 1; + if (i < vertices.size() - 1) { + Triangle4d triangle = this.buffer.visibleTriangle; + triangle.assign(initialVertex, vertices.get(i), vertices.get(i + 1)); + return true; + } + } + + return false; + } + } + + private boolean clipPolygonAxis(int componentIndex) { + clipPolygonComponent(this.buffer.vertexList1, componentIndex, 1.0, this.buffer.vertexList2); + this.buffer.vertexList1.clear(); + if (this.buffer.vertexList2.isEmpty()) { + return false; + } else { + clipPolygonComponent(this.buffer.vertexList2, componentIndex, -1.0, this.buffer.vertexList1); + this.buffer.vertexList2.clear(); + return !this.buffer.vertexList1.isEmpty(); + } + } + + private static void clipPolygonComponent( + @Nonnull Vector4dBufferList vertices, int componentIndex, double componentFactor, @Nonnull Vector4dBufferList result + ) { + Vector4d previousVertex = vertices.get(vertices.size() - 1); + double previousComponent = previousVertex.get(componentIndex) * componentFactor; + boolean previousInside = previousComponent <= previousVertex.w; + + for (int i = 0; i < vertices.size(); i++) { + Vector4d vertex = vertices.get(i); + double component = vertex.get(componentIndex) * componentFactor; + boolean inside = component <= vertex.w; + if (inside ^ previousInside) { + double lerp = (previousVertex.w - previousComponent) / (previousVertex.w - previousComponent - (vertex.w - component)); + previousVertex.lerp(vertex, lerp, result.next()); + } + + if (inside) { + result.next().assign(vertex); + } + + previousVertex = vertex; + previousComponent = component; + previousInside = inside; + } + } + + @Nonnull + @Override + public String toString() { + return "HitDetectionExecutor{pvmMatrix=" + + this.pvmMatrix + + ", invPvMatrix=" + + this.invPvMatrix + + ", origin=" + + this.origin + + ", buffer=" + + this.buffer + + ", projectionProvider=" + + this.projectionProvider + + ", viewProvider=" + + this.viewProvider + + ", losProvider=" + + this.losProvider + + ", maxRayTests=" + + this.maxRayTests + + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/hitdetection/LineOfSightProvider.java b/src/com/hypixel/hytale/math/hitdetection/LineOfSightProvider.java new file mode 100644 index 0000000..696e7c9 --- /dev/null +++ b/src/com/hypixel/hytale/math/hitdetection/LineOfSightProvider.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.math.hitdetection; + +public interface LineOfSightProvider { + LineOfSightProvider DEFAULT_TRUE = (fromX, fromY, fromZ, toX, toY, toZ) -> true; + + boolean test(double var1, double var3, double var5, double var7, double var9, double var11); +} diff --git a/src/com/hypixel/hytale/math/hitdetection/MatrixProvider.java b/src/com/hypixel/hytale/math/hitdetection/MatrixProvider.java new file mode 100644 index 0000000..f4482ec --- /dev/null +++ b/src/com/hypixel/hytale/math/hitdetection/MatrixProvider.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.math.hitdetection; + +import com.hypixel.hytale.math.matrix.Matrix4d; + +public interface MatrixProvider { + Matrix4d getMatrix(); +} diff --git a/src/com/hypixel/hytale/math/hitdetection/Vector4dBufferList.java b/src/com/hypixel/hytale/math/hitdetection/Vector4dBufferList.java new file mode 100644 index 0000000..14a4186 --- /dev/null +++ b/src/com/hypixel/hytale/math/hitdetection/Vector4dBufferList.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.math.hitdetection; + +import com.hypixel.hytale.math.vector.Vector4d; +import javax.annotation.Nonnull; + +public class Vector4dBufferList { + private Vector4d[] vectors; + private int size; + + public Vector4dBufferList(int size) { + this.vectors = new Vector4d[size]; + + for (int i = 0; i < size; i++) { + this.vectors[i] = new Vector4d(); + } + + this.size = 0; + } + + public Vector4d next() { + return this.vectors[this.size++]; + } + + public void clear() { + this.size = 0; + } + + public int size() { + return this.size; + } + + public Vector4d get(int i) { + return this.vectors[i]; + } + + public boolean isEmpty() { + return this.size == 0; + } + + @Nonnull + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Vector4dBufferList{vectors=[\n"); + + for (int i = 0; i < this.size; i++) { + sb.append(this.vectors[i]).append(",\n"); + } + + sb.append("], size=").append(this.size).append('}'); + return sb.toString(); + } +} diff --git a/src/com/hypixel/hytale/math/hitdetection/projection/FrustumProjectionProvider.java b/src/com/hypixel/hytale/math/hitdetection/projection/FrustumProjectionProvider.java new file mode 100644 index 0000000..d919a05 --- /dev/null +++ b/src/com/hypixel/hytale/math/hitdetection/projection/FrustumProjectionProvider.java @@ -0,0 +1,113 @@ +package com.hypixel.hytale.math.hitdetection.projection; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.hitdetection.MatrixProvider; +import com.hypixel.hytale.math.matrix.Matrix4d; +import javax.annotation.Nonnull; + +public class FrustumProjectionProvider implements MatrixProvider { + public static final BuilderCodec CODEC = BuilderCodec.builder(FrustumProjectionProvider.class, FrustumProjectionProvider::new) + .addField(new KeyedCodec<>("Left", Codec.DOUBLE), (projectionProvider, d) -> projectionProvider.left = d, projectionProvider -> projectionProvider.left) + .addField( + new KeyedCodec<>("Right", Codec.DOUBLE), (projectionProvider, d) -> projectionProvider.right = d, projectionProvider -> projectionProvider.right + ) + .addField(new KeyedCodec<>("Top", Codec.DOUBLE), (projectionProvider, d) -> projectionProvider.top = d, projectionProvider -> projectionProvider.top) + .addField( + new KeyedCodec<>("Bottom", Codec.DOUBLE), (projectionProvider, d) -> projectionProvider.bottom = d, projectionProvider -> projectionProvider.bottom + ) + .addField(new KeyedCodec<>("Near", Codec.DOUBLE), (projectionProvider, d) -> projectionProvider.near = d, projectionProvider -> projectionProvider.near) + .addField(new KeyedCodec<>("Far", Codec.DOUBLE), (projectionProvider, d) -> projectionProvider.far = d, projectionProvider -> projectionProvider.far) + .build(); + protected final Matrix4d matrix; + protected final Matrix4d rotMatrix = new Matrix4d(); + protected boolean invalid; + protected double left; + protected double right; + protected double bottom; + protected double top; + protected double near; + protected double far; + protected double yaw; + protected double pitch; + protected double roll; + + public FrustumProjectionProvider() { + this(new Matrix4d(), 0.1, 0.1, 0.1, 0.1, 1.0, 5.0); + } + + public FrustumProjectionProvider(Matrix4d matrix, double left, double right, double bottom, double top, double near, double far) { + this.matrix = matrix; + this.left = left; + this.right = right; + this.bottom = bottom; + this.top = top; + this.near = near; + this.far = far; + this.invalid = true; + } + + @Nonnull + public FrustumProjectionProvider setLeft(double left) { + this.left = left; + this.invalid = true; + return this; + } + + @Nonnull + public FrustumProjectionProvider setRight(double right) { + this.right = right; + this.invalid = true; + return this; + } + + @Nonnull + public FrustumProjectionProvider setBottom(double bottom) { + this.bottom = bottom; + this.invalid = true; + return this; + } + + @Nonnull + public FrustumProjectionProvider setTop(double top) { + this.top = top; + this.invalid = true; + return this; + } + + @Nonnull + public FrustumProjectionProvider setNear(double near) { + this.near = near; + this.invalid = true; + return this; + } + + @Nonnull + public FrustumProjectionProvider setFar(double far) { + this.far = far; + this.invalid = true; + return this; + } + + @Nonnull + public FrustumProjectionProvider setRotation(double yaw, double pitch, double roll) { + this.yaw = yaw; + this.pitch = pitch; + this.roll = roll; + return this; + } + + @Override + public Matrix4d getMatrix() { + if (this.invalid) { + this.matrix.projectionFrustum(this.left, this.right, this.bottom, this.top, this.near, this.far); + this.matrix.rotateAxis(this.pitch, 1.0, 0.0, 0.0, this.rotMatrix); + this.matrix.rotateAxis(this.yaw, 0.0, 1.0, 0.0, this.rotMatrix); + this.matrix.rotateAxis(this.roll, 0.0, 0.0, 1.0, this.rotMatrix); + this.invalid = false; + } + + return this.matrix; + } +} diff --git a/src/com/hypixel/hytale/math/hitdetection/projection/OrthogonalProjectionProvider.java b/src/com/hypixel/hytale/math/hitdetection/projection/OrthogonalProjectionProvider.java new file mode 100644 index 0000000..c626ce2 --- /dev/null +++ b/src/com/hypixel/hytale/math/hitdetection/projection/OrthogonalProjectionProvider.java @@ -0,0 +1,142 @@ +package com.hypixel.hytale.math.hitdetection.projection; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.hitdetection.MatrixProvider; +import com.hypixel.hytale.math.matrix.Matrix4d; +import javax.annotation.Nonnull; + +public class OrthogonalProjectionProvider implements MatrixProvider { + public static final BuilderCodec CODEC = BuilderCodec.builder( + OrthogonalProjectionProvider.class, OrthogonalProjectionProvider::new + ) + .addField(new KeyedCodec<>("Left", Codec.DOUBLE), (projectionProvider, d) -> projectionProvider.left = d, projectionProvider -> projectionProvider.left) + .addField( + new KeyedCodec<>("Right", Codec.DOUBLE), (projectionProvider, d) -> projectionProvider.right = d, projectionProvider -> projectionProvider.right + ) + .addField(new KeyedCodec<>("Top", Codec.DOUBLE), (projectionProvider, d) -> projectionProvider.top = d, projectionProvider -> projectionProvider.top) + .addField( + new KeyedCodec<>("Bottom", Codec.DOUBLE), (projectionProvider, d) -> projectionProvider.bottom = d, projectionProvider -> projectionProvider.bottom + ) + .addField(new KeyedCodec<>("Near", Codec.DOUBLE), (projectionProvider, d) -> projectionProvider.near = d, projectionProvider -> projectionProvider.near) + .addField(new KeyedCodec<>("Far", Codec.DOUBLE), (projectionProvider, d) -> projectionProvider.far = d, projectionProvider -> projectionProvider.far) + .build(); + protected final Matrix4d matrix; + protected final Matrix4d rotMatrix = new Matrix4d(); + protected boolean invalid; + protected double left; + protected double right; + protected double bottom; + protected double top; + protected double near; + protected double far; + protected double yaw; + protected double pitch; + protected double roll; + + public OrthogonalProjectionProvider() { + this(new Matrix4d(), 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0); + } + + public OrthogonalProjectionProvider( + Matrix4d matrix, double left, double right, double bottom, double top, double near, double far, double yaw, double pitch, double roll + ) { + this.matrix = matrix; + this.left = left; + this.right = right; + this.bottom = bottom; + this.top = top; + this.near = near; + this.far = far; + this.yaw = yaw; + this.pitch = pitch; + this.roll = roll; + this.invalid = true; + } + + @Nonnull + public OrthogonalProjectionProvider setLeft(double left) { + this.left = left; + this.invalid = true; + return this; + } + + @Nonnull + public OrthogonalProjectionProvider setRight(double right) { + this.right = right; + this.invalid = true; + return this; + } + + @Nonnull + public OrthogonalProjectionProvider setBottom(double bottom) { + this.bottom = bottom; + this.invalid = true; + return this; + } + + @Nonnull + public OrthogonalProjectionProvider setTop(double top) { + this.top = top; + this.invalid = true; + return this; + } + + @Nonnull + public OrthogonalProjectionProvider setNear(double near) { + this.near = near; + this.invalid = true; + return this; + } + + @Nonnull + public OrthogonalProjectionProvider setFar(double far) { + this.far = far; + this.invalid = true; + return this; + } + + public double getRange() { + return this.far; + } + + @Nonnull + public OrthogonalProjectionProvider setRotation(double yaw, double pitch, double roll) { + this.yaw = yaw; + this.pitch = pitch; + this.roll = roll; + return this; + } + + @Override + public Matrix4d getMatrix() { + if (this.invalid) { + this.matrix.projectionOrtho(this.left, this.right, this.bottom, this.top, this.near, this.far); + this.matrix.rotateAxis(this.roll, 0.0, 0.0, 1.0, this.rotMatrix); + this.matrix.rotateAxis(this.pitch, 1.0, 0.0, 0.0, this.rotMatrix); + this.matrix.rotateAxis(this.yaw, 0.0, 1.0, 0.0, this.rotMatrix); + this.invalid = false; + } + + return this.matrix; + } + + @Nonnull + @Override + public String toString() { + return "OrthogonalProjectionProvider{left=" + + this.left + + ", right=" + + this.right + + ", bottom=" + + this.bottom + + ", top=" + + this.top + + ", near=" + + this.near + + ", far=" + + this.far + + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/hitdetection/view/DirectionViewProvider.java b/src/com/hypixel/hytale/math/hitdetection/view/DirectionViewProvider.java new file mode 100644 index 0000000..a4c7a1e --- /dev/null +++ b/src/com/hypixel/hytale/math/hitdetection/view/DirectionViewProvider.java @@ -0,0 +1,139 @@ +package com.hypixel.hytale.math.hitdetection.view; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.hitdetection.MatrixProvider; +import com.hypixel.hytale.math.matrix.Matrix4d; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class DirectionViewProvider implements MatrixProvider { + public static final BuilderCodec CODEC = BuilderCodec.builder(DirectionViewProvider.class, DirectionViewProvider::new) + .append( + new KeyedCodec<>("YawOffset", Codec.DOUBLE), + (projectionProvider, d) -> projectionProvider.yawOffset = d, + projectionProvider -> projectionProvider.yawOffset + ) + .add() + .append( + new KeyedCodec<>("PitchOffset", Codec.DOUBLE), + (projectionProvider, d) -> projectionProvider.pitchOffset = d, + projectionProvider -> projectionProvider.pitchOffset + ) + .add() + .append( + new KeyedCodec<>("Up", Vector3d.CODEC), (projectionProvider, vec) -> projectionProvider.up.assign(vec), projectionProvider -> projectionProvider.up + ) + .add() + .build(); + public static final Vector3d DEFAULT_UP = new Vector3d(0.0, 1.0, 0.0); + protected final Matrix4d matrix; + protected final Vector3d position; + protected final Vector3d direction; + protected final Vector3d up; + protected double yaw; + protected double pitch; + protected double yawOffset; + protected double pitchOffset; + protected boolean invalid; + + public DirectionViewProvider() { + this(new Matrix4d(), new Vector3d(), new Vector3d(), new Vector3d(DEFAULT_UP)); + } + + public DirectionViewProvider(Matrix4d matrix, Vector3d position, Vector3d direction, Vector3d up) { + this.matrix = matrix; + this.position = position; + this.direction = direction; + this.up = up; + this.invalid = true; + } + + public Vector3d getPosition() { + return this.position; + } + + @Nonnull + public DirectionViewProvider setPosition(@Nonnull Vector3d vec) { + return this.setPosition(vec, 0.0, 0.0, 0.0); + } + + @Nonnull + public DirectionViewProvider setPosition(@Nonnull Vector3d vec, double offsetX, double offsetY, double offsetZ) { + return this.setPosition(vec.x, vec.y, vec.z, offsetX, offsetY, offsetZ); + } + + @Nonnull + public DirectionViewProvider setPosition(double x, double y, double z) { + this.position.assign(x, y, z); + this.invalid = true; + return this; + } + + @Nonnull + public DirectionViewProvider setPosition(double x, double y, double z, double offsetX, double offsetY, double offsetZ) { + return this.setPosition(x + offsetX, y + offsetY, z + offsetZ); + } + + public Vector3d getDirection() { + return this.direction; + } + + @Nonnull + public DirectionViewProvider setDirection(@Nonnull Vector3d vec) { + return this.setDirection(vec.x, vec.y, vec.z); + } + + @Nonnull + public DirectionViewProvider setDirection(double yaw, double pitch) { + yaw += this.yawOffset; + pitch += this.pitchOffset; + this.direction.assign(yaw, pitch); + this.invalid = true; + return this; + } + + @Nonnull + public DirectionViewProvider setDirection(double x, double y, double z) { + this.direction.assign(x, y, z); + this.invalid = true; + return this; + } + + @Nonnull + public DirectionViewProvider setUp(double x, double y, double z) { + this.up.assign(x, y, z); + this.invalid = true; + return this; + } + + @Override + public Matrix4d getMatrix() { + if (this.invalid) { + this.matrix + .viewDirection( + this.position.x, this.position.y, this.position.z, this.direction.x, this.direction.y, this.direction.z, this.up.x, this.up.y, this.up.z + ); + this.invalid = false; + } + + return this.matrix; + } + + @Nonnull + @Override + public String toString() { + return "DirectionViewProvider{up=" + + this.up + + ", yaw=" + + this.yaw + + ", pitch=" + + this.pitch + + ", yawOffset=" + + this.yawOffset + + ", pitchOffset=" + + this.pitchOffset + + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/iterator/BlockIterator.java b/src/com/hypixel/hytale/math/iterator/BlockIterator.java new file mode 100644 index 0000000..7b76fa9 --- /dev/null +++ b/src/com/hypixel/hytale/math/iterator/BlockIterator.java @@ -0,0 +1,333 @@ +package com.hypixel.hytale.math.iterator; + +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public final class BlockIterator { + private BlockIterator() { + throw new UnsupportedOperationException("This is a utilitiy class. Do not instantiate."); + } + + public static boolean iterateFromTo(@Nonnull Vector3d origin, @Nonnull Vector3d target, @Nonnull BlockIterator.BlockIteratorProcedure procedure) { + return iterateFromTo(origin.x, origin.y, origin.z, target.x, target.y, target.z, procedure); + } + + public static boolean iterateFromTo(@Nonnull Vector3i origin, @Nonnull Vector3i target, @Nonnull BlockIterator.BlockIteratorProcedure procedure) { + return iterateFromTo(origin.x, origin.y, origin.z, target.x, target.y, target.z, procedure); + } + + public static boolean iterateFromTo( + double sx, double sy, double sz, double tx, double ty, double tz, @Nonnull BlockIterator.BlockIteratorProcedure procedure + ) { + double dx = tx - sx; + double dy = ty - sy; + double dz = tz - sz; + double maxDistance = Math.sqrt(dx * dx + dy * dy + dz * dz); + return iterate(sx, sy, sz, dx, dy, dz, maxDistance, procedure); + } + + public static boolean iterateFromTo( + double sx, double sy, double sz, double tx, double ty, double tz, @Nonnull BlockIterator.BlockIteratorProcedurePlus1 procedure, T t + ) { + double dx = tx - sx; + double dy = ty - sy; + double dz = tz - sz; + double maxDistance = Math.sqrt(dx * dx + dy * dy + dz * dz); + return iterate(sx, sy, sz, dx, dy, dz, maxDistance, procedure, t); + } + + public static boolean iterate( + @Nonnull Vector3d origin, @Nonnull Vector3d direction, double maxDistance, @Nonnull BlockIterator.BlockIteratorProcedure procedure + ) { + return iterate(origin.x, origin.y, origin.z, direction.x, direction.y, direction.z, maxDistance, procedure); + } + + public static boolean iterate( + double sx, double sy, double sz, double dx, double dy, double dz, double maxDistance, @Nonnull BlockIterator.BlockIteratorProcedure procedure + ) { + checkParameters(sx, sy, sz, dx, dy, dz); + return iterate0(sx, sy, sz, dx, dy, dz, maxDistance, procedure); + } + + private static boolean iterate0( + double sx, double sy, double sz, double dx, double dy, double dz, double maxDistance, @Nonnull BlockIterator.BlockIteratorProcedure procedure + ) { + maxDistance /= Math.sqrt(dx * dx + dy * dy + dz * dz); + int bx = (int)BlockIterator.FastMath.fastFloor(sx); + int by = (int)BlockIterator.FastMath.fastFloor(sy); + int bz = (int)BlockIterator.FastMath.fastFloor(sz); + double px = sx - bx; + double py = sy - by; + double pz = sz - bz; + double pt = 0.0; + + while (pt <= maxDistance) { + double t = intersection(px, py, pz, dx, dy, dz); + double qx = px + t * dx; + double qy = py + t * dy; + double qz = pz + t * dz; + if (!procedure.accept(bx, by, bz, px, py, pz, qx, qy, qz)) { + return false; + } + + if (dx < 0.0 && BlockIterator.FastMath.sEq(qx, 0.0)) { + qx++; + bx--; + } else if (dx > 0.0 && BlockIterator.FastMath.gEq(qx, 1.0)) { + qx--; + bx++; + } + + if (dy < 0.0 && BlockIterator.FastMath.sEq(qy, 0.0)) { + qy++; + by--; + } else if (dy > 0.0 && BlockIterator.FastMath.gEq(qy, 1.0)) { + qy--; + by++; + } + + if (dz < 0.0 && BlockIterator.FastMath.sEq(qz, 0.0)) { + qz++; + bz--; + } else if (dz > 0.0 && BlockIterator.FastMath.gEq(qz, 1.0)) { + qz--; + bz++; + } + + pt += t; + px = qx; + py = qy; + pz = qz; + } + + return true; + } + + public static boolean iterate( + double sx, + double sy, + double sz, + double dx, + double dy, + double dz, + double maxDistance, + @Nonnull BlockIterator.BlockIteratorProcedurePlus1 procedure, + T obj1 + ) { + checkParameters(sx, sy, sz, dx, dy, dz); + return iterate0(sx, sy, sz, dx, dy, dz, maxDistance, procedure, obj1); + } + + private static boolean iterate0( + double sx, + double sy, + double sz, + double dx, + double dy, + double dz, + double maxDistance, + @Nonnull BlockIterator.BlockIteratorProcedurePlus1 procedure, + T obj1 + ) { + maxDistance /= Math.sqrt(dx * dx + dy * dy + dz * dz); + int bx = (int)BlockIterator.FastMath.fastFloor(sx); + int by = (int)BlockIterator.FastMath.fastFloor(sy); + int bz = (int)BlockIterator.FastMath.fastFloor(sz); + double px = sx - bx; + double py = sy - by; + double pz = sz - bz; + double pt = 0.0; + + while (pt <= maxDistance) { + double t = intersection(px, py, pz, dx, dy, dz); + double qx = px + t * dx; + double qy = py + t * dy; + double qz = pz + t * dz; + if (!procedure.accept(bx, by, bz, px, py, pz, qx, qy, qz, obj1)) { + return false; + } + + if (dx < 0.0 && BlockIterator.FastMath.sEq(qx, 0.0)) { + qx++; + bx--; + } else if (dx > 0.0 && BlockIterator.FastMath.gEq(qx, 1.0)) { + qx--; + bx++; + } + + if (dy < 0.0 && BlockIterator.FastMath.sEq(qy, 0.0)) { + qy++; + by--; + } else if (dy > 0.0 && BlockIterator.FastMath.gEq(qy, 1.0)) { + qy--; + by++; + } + + if (dz < 0.0 && BlockIterator.FastMath.sEq(qz, 0.0)) { + qz++; + bz--; + } else if (dz > 0.0 && BlockIterator.FastMath.gEq(qz, 1.0)) { + qz--; + bz++; + } + + pt += t; + px = qx; + py = qy; + pz = qz; + } + + return true; + } + + private static void checkParameters(double sx, double sy, double sz, double dx, double dy, double dz) { + if (isNonValidNumber(sx)) { + throw new IllegalArgumentException("sx is a non-valid number! Given: " + sx); + } else if (isNonValidNumber(sy)) { + throw new IllegalArgumentException("sy is a non-valid number! Given: " + sy); + } else if (isNonValidNumber(sz)) { + throw new IllegalArgumentException("sz is a non-valid number! Given: " + sz); + } else if (isNonValidNumber(dx)) { + throw new IllegalArgumentException("dx is a non-valid number! Given: " + dx); + } else if (isNonValidNumber(dy)) { + throw new IllegalArgumentException("dy is a non-valid number! Given: " + dy); + } else if (isNonValidNumber(dz)) { + throw new IllegalArgumentException("dz is a non-valid number! Given: " + dz); + } else if (isZeroDirection(dx, dy, dz)) { + throw new IllegalArgumentException("Direction is ZERO! Given: (" + dx + ", " + dy + ", " + dz + ")"); + } + } + + public static boolean isNonValidNumber(double d) { + return Double.isNaN(d) || Double.isInfinite(d); + } + + public static boolean isZeroDirection(double dx, double dy, double dz) { + return BlockIterator.FastMath.eq(dx, 0.0) && BlockIterator.FastMath.eq(dy, 0.0) && BlockIterator.FastMath.eq(dz, 0.0); + } + + private static double intersection(double px, double py, double pz, double dx, double dy, double dz) { + double tFar = 0.0; + if (dx < 0.0) { + double t = -px / dx; + double u = pz + dz * t; + double v = py + dy * t; + if (t > tFar + && BlockIterator.FastMath.gEq(u, 0.0) + && BlockIterator.FastMath.sEq(u, 1.0) + && BlockIterator.FastMath.gEq(v, 0.0) + && BlockIterator.FastMath.sEq(v, 1.0)) { + tFar = t; + } + } else if (dx > 0.0) { + double t = (1.0 - px) / dx; + double u = pz + dz * t; + double v = py + dy * t; + if (t > tFar + && BlockIterator.FastMath.gEq(u, 0.0) + && BlockIterator.FastMath.sEq(u, 1.0) + && BlockIterator.FastMath.gEq(v, 0.0) + && BlockIterator.FastMath.sEq(v, 1.0)) { + tFar = t; + } + } + + if (dy < 0.0) { + double t = -py / dy; + double u = px + dx * t; + double v = pz + dz * t; + if (t > tFar + && BlockIterator.FastMath.gEq(u, 0.0) + && BlockIterator.FastMath.sEq(u, 1.0) + && BlockIterator.FastMath.gEq(v, 0.0) + && BlockIterator.FastMath.sEq(v, 1.0)) { + tFar = t; + } + } else if (dy > 0.0) { + double t = (1.0 - py) / dy; + double u = px + dx * t; + double v = pz + dz * t; + if (t > tFar + && BlockIterator.FastMath.gEq(u, 0.0) + && BlockIterator.FastMath.sEq(u, 1.0) + && BlockIterator.FastMath.gEq(v, 0.0) + && BlockIterator.FastMath.sEq(v, 1.0)) { + tFar = t; + } + } + + if (dz < 0.0) { + double t = -pz / dz; + double u = px + dx * t; + double v = py + dy * t; + if (t > tFar + && BlockIterator.FastMath.gEq(u, 0.0) + && BlockIterator.FastMath.sEq(u, 1.0) + && BlockIterator.FastMath.gEq(v, 0.0) + && BlockIterator.FastMath.sEq(v, 1.0)) { + tFar = t; + } + } else if (dz > 0.0) { + double t = (1.0 - pz) / dz; + double u = px + dx * t; + double v = py + dy * t; + if (t > tFar + && BlockIterator.FastMath.gEq(u, 0.0) + && BlockIterator.FastMath.sEq(u, 1.0) + && BlockIterator.FastMath.gEq(v, 0.0) + && BlockIterator.FastMath.sEq(v, 1.0)) { + tFar = t; + } + } + + return tFar; + } + + @FunctionalInterface + public interface BlockIteratorProcedure { + boolean accept(int var1, int var2, int var3, double var4, double var6, double var8, double var10, double var12, double var14); + } + + @FunctionalInterface + public interface BlockIteratorProcedurePlus1 { + boolean accept(int var1, int var2, int var3, double var4, double var6, double var8, double var10, double var12, double var14, T var16); + } + + static class FastMath { + static final double TWO_POWER_52 = 4.5035996E15F; + static final double ROUNDING_ERROR = 1.0E-15; + + FastMath() { + } + + static boolean eq(double a, double b) { + return abs(a - b) < 1.0E-15; + } + + static boolean sEq(double a, double b) { + return a <= b + 1.0E-15; + } + + static boolean gEq(double a, double b) { + return a >= b - 1.0E-15; + } + + static double abs(double x) { + return x < 0.0 ? -x : x; + } + + static long fastFloor(double x) { + if (!(x >= 4.5035996E15F) && !(x <= -4.5035996E15F)) { + long y = (long)x; + if (x < 0.0 && y != x) { + y--; + } + + return y == 0L ? (long)(x * y) : y; + } else { + return (long)x; + } + } + } +} diff --git a/src/com/hypixel/hytale/math/iterator/BoxBlockIterator.java b/src/com/hypixel/hytale/math/iterator/BoxBlockIterator.java new file mode 100644 index 0000000..a82fec1 --- /dev/null +++ b/src/com/hypixel/hytale/math/iterator/BoxBlockIterator.java @@ -0,0 +1,266 @@ +package com.hypixel.hytale.math.iterator; + +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public final class BoxBlockIterator { + @Nonnull + private static ThreadLocal THREAD_LOCAL_BUFFER = ThreadLocal.withInitial(BoxBlockIterator.BoxIterationBuffer::new); + + private BoxBlockIterator() { + throw new UnsupportedOperationException("This is a utility class. Do not instantiate."); + } + + public static BoxBlockIterator.BoxIterationBuffer getBuffer() { + return THREAD_LOCAL_BUFFER.get(); + } + + public static boolean iterate( + @Nonnull Box box, @Nonnull Vector3d position, @Nonnull Vector3d d, double maxDistance, @Nonnull BoxBlockIterator.BoxIterationConsumer consumer + ) { + return iterate(box, position, d, maxDistance, consumer, getBuffer()); + } + + public static boolean iterate( + @Nonnull Box box, + @Nonnull Vector3d pos, + @Nonnull Vector3d d, + double maxDistance, + @Nonnull BoxBlockIterator.BoxIterationConsumer consumer, + @Nonnull BoxBlockIterator.BoxIterationBuffer buffer + ) { + return iterate(box.min, box.max, pos, d, maxDistance, consumer, buffer); + } + + public static boolean iterate( + @Nonnull Box box, + double px, + double py, + double pz, + double dx, + double dy, + double dz, + double maxDistance, + @Nonnull BoxBlockIterator.BoxIterationConsumer consumer + ) { + return iterate(box, px, py, pz, dx, dy, dz, maxDistance, consumer, getBuffer()); + } + + public static boolean iterate( + @Nonnull Box box, + double px, + double py, + double pz, + double dx, + double dy, + double dz, + double maxDistance, + @Nonnull BoxBlockIterator.BoxIterationConsumer consumer, + @Nonnull BoxBlockIterator.BoxIterationBuffer buffer + ) { + return iterate(box.min, box.max, px, py, pz, dx, dy, dz, maxDistance, consumer, buffer); + } + + public static boolean iterate( + @Nonnull Vector3d min, + @Nonnull Vector3d max, + double px, + double py, + double pz, + double dx, + double dy, + double dz, + double maxDistance, + @Nonnull BoxBlockIterator.BoxIterationConsumer consumer + ) { + return iterate(min, max, px, py, pz, dx, dy, dz, maxDistance, consumer, getBuffer()); + } + + public static boolean iterate( + @Nonnull Vector3d min, + @Nonnull Vector3d max, + double px, + double py, + double pz, + double dx, + double dy, + double dz, + double maxDistance, + @Nonnull BoxBlockIterator.BoxIterationConsumer consumer, + @Nonnull BoxBlockIterator.BoxIterationBuffer buffer + ) { + return iterate(min.x, min.y, min.z, max.x, max.y, max.z, px, py, pz, dx, dy, dz, maxDistance, consumer, buffer); + } + + public static boolean iterate( + @Nonnull Vector3d min, + @Nonnull Vector3d max, + @Nonnull Vector3d pos, + @Nonnull Vector3d d, + double maxDistance, + @Nonnull BoxBlockIterator.BoxIterationConsumer consumer + ) { + return iterate(min, max, pos, d, maxDistance, consumer, getBuffer()); + } + + public static boolean iterate( + @Nonnull Vector3d min, + @Nonnull Vector3d max, + @Nonnull Vector3d pos, + @Nonnull Vector3d d, + double maxDistance, + @Nonnull BoxBlockIterator.BoxIterationConsumer consumer, + @Nonnull BoxBlockIterator.BoxIterationBuffer buffer + ) { + return iterate(min.x, min.y, min.z, max.x, max.y, max.z, pos.x, pos.y, pos.z, d.x, d.y, d.z, maxDistance, consumer, buffer); + } + + public static boolean iterate( + double minX, + double minY, + double minZ, + double maxX, + double maxY, + double maxZ, + double px, + double py, + double pz, + double dx, + double dy, + double dz, + double maxDistance, + @Nonnull BoxBlockIterator.BoxIterationConsumer consumer + ) { + return iterate(minX, minY, minZ, maxX, maxY, maxZ, px, py, pz, dx, dy, dz, maxDistance, consumer, getBuffer()); + } + + public static boolean iterate( + double minX, + double minY, + double minZ, + double maxX, + double maxY, + double maxZ, + double px, + double py, + double pz, + double dx, + double dy, + double dz, + double maxDistance, + @Nonnull BoxBlockIterator.BoxIterationConsumer consumer, + @Nonnull BoxBlockIterator.BoxIterationBuffer buffer + ) { + if (minX > maxX) { + throw new IllegalArgumentException("minX is larger than maxX! Given: " + minX + " > " + maxX); + } else if (minY > maxY) { + throw new IllegalArgumentException("minY is larger than maxY! Given: " + minY + " > " + maxY); + } else if (minZ > maxZ) { + throw new IllegalArgumentException("minZ is larger than maxZ! Given: " + minZ + " > " + maxZ); + } else if (consumer == null) { + throw new NullPointerException("consumer is null!"); + } else if (buffer == null) { + throw new NullPointerException("buffer is null!"); + } else { + return iterate0(minX, minY, minZ, maxX, maxY, maxZ, px, py, pz, dx, dy, dz, maxDistance, consumer, buffer); + } + } + + private static boolean iterate0( + double minX, + double minY, + double minZ, + double maxX, + double maxY, + double maxZ, + double posX, + double posY, + double posZ, + double dx, + double dy, + double dz, + double maxDistance, + BoxBlockIterator.BoxIterationConsumer consumer, + @Nonnull BoxBlockIterator.BoxIterationBuffer buffer + ) { + buffer.consumer = consumer; + buffer.mx = maxX - minX; + buffer.my = maxY - minY; + buffer.mz = maxZ - minZ; + buffer.signX = dx > 0.0 ? -1 : 1; + buffer.signY = dy > 0.0 ? -1 : 1; + buffer.signZ = dz > 0.0 ? -1 : 1; + double bx = posX + (dx > 0.0 ? maxX : minX); + double by = posY + (dy > 0.0 ? maxY : minY); + double bz = posZ + (dz > 0.0 ? maxZ : minZ); + buffer.posX = (long)bx; + buffer.posY = (long)by; + buffer.posZ = (long)bz; + return BlockIterator.iterate(bx, by, bz, dx, dy, dz, maxDistance, (x, y, z, px, py, pz, qx, qy, qz, buf) -> { + int tx = (int)MathUtil.fastCeil((buf.signX < 0 ? 1.0 - px : px) + buf.mx); + int ty = (int)MathUtil.fastCeil((buf.signY < 0 ? 1.0 - py : py) + buf.my); + int tz = (int)MathUtil.fastCeil((buf.signZ < 0 ? 1.0 - pz : pz) + buf.mz); + if (x != buf.posX) { + for (int iy = 0; iy < ty; iy++) { + for (int iz = 0; iz < tz; iz++) { + if (!buf.consumer.accept(x, y + (long)iy * buf.signY, z + (long)iz * buf.signZ)) { + return false; + } + } + } + + buf.posX = x; + } + + if (y != buf.posY) { + for (int izx = 0; izx < tz; izx++) { + for (int ix = 0; ix < tx; ix++) { + if (!buf.consumer.accept(x + (long)ix * buf.signX, y, z + (long)izx * buf.signZ)) { + return false; + } + } + } + + buf.posY = y; + } + + if (z != buf.posZ) { + for (int ixx = 0; ixx < tx; ixx++) { + for (int iy = 0; iy < ty; iy++) { + if (!buf.consumer.accept(x + (long)ixx * buf.signX, y + (long)iy * buf.signY, z)) { + return false; + } + } + } + + buf.posZ = z; + } + + return buf.consumer.next(); + }, buffer); + } + + public static class BoxIterationBuffer { + BoxBlockIterator.BoxIterationConsumer consumer; + double mx; + double my; + double mz; + int signX; + int signY; + int signZ; + long posX; + long posY; + long posZ; + + public BoxIterationBuffer() { + } + } + + public interface BoxIterationConsumer { + boolean next(); + + boolean accept(long var1, long var3, long var5); + } +} diff --git a/src/com/hypixel/hytale/math/iterator/CircleIterator.java b/src/com/hypixel/hytale/math/iterator/CircleIterator.java new file mode 100644 index 0000000..e6cb75e --- /dev/null +++ b/src/com/hypixel/hytale/math/iterator/CircleIterator.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.math.iterator; + +import com.hypixel.hytale.math.util.TrigMathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import java.util.Iterator; +import javax.annotation.Nonnull; + +public class CircleIterator implements Iterator { + private final Vector3d origin; + private final int pointTotal; + private final double radius; + private final float angleOffset; + private int pointIndex; + + public CircleIterator(Vector3d origin, double radius, int pointTotal) { + this(origin, radius, pointTotal, 0.0F); + } + + public CircleIterator(Vector3d origin, double radius, int pointTotal, float angleOffset) { + this.origin = origin; + this.pointTotal = pointTotal; + this.angleOffset = angleOffset; + this.pointIndex = 0; + this.radius = radius; + } + + @Override + public boolean hasNext() { + return this.pointIndex < this.pointTotal; + } + + @Nonnull + public Vector3d next() { + this.pointIndex++; + float angle = (float)this.pointIndex / this.pointTotal * (float) (Math.PI * 2) + this.angleOffset; + return new Vector3d( + TrigMathUtil.cos(angle) * this.radius + this.origin.getX(), this.origin.getY(), TrigMathUtil.sin(angle) * this.radius + this.origin.getZ() + ); + } +} diff --git a/src/com/hypixel/hytale/math/iterator/CircleSpiralIterator.java b/src/com/hypixel/hytale/math/iterator/CircleSpiralIterator.java new file mode 100644 index 0000000..d1f6493 --- /dev/null +++ b/src/com/hypixel/hytale/math/iterator/CircleSpiralIterator.java @@ -0,0 +1,134 @@ +package com.hypixel.hytale.math.iterator; + +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import java.util.NoSuchElementException; + +public class CircleSpiralIterator { + public static final long MAX_RADIUS_LONG = (long)Math.sqrt(9.223372E18F) / 2L - 1L; + public static final int MAX_RADIUS = (int)MAX_RADIUS_LONG; + private boolean setup; + private int chunkX; + private int chunkZ; + private long maxI; + private long i; + private int x; + private int z; + private int dx; + private int dz; + private long radiusFromSq; + private long radiusToSq; + private boolean hasNext; + private long nextChunk; + + public CircleSpiralIterator() { + } + + public void init(int chunkX, int chunkZ, int radiusTo) { + this.init(chunkX, chunkZ, 0, radiusTo); + } + + public void init(int chunkX, int chunkZ, int radiusFrom, int radiusTo) { + if (radiusFrom < 0) { + throw new IllegalArgumentException("radiusFrom must be >= 0: " + radiusFrom); + } else if (radiusTo <= 0) { + throw new IllegalArgumentException("radiusTo must be > 0: " + radiusTo); + } else if (radiusTo > MAX_RADIUS) { + throw new IllegalArgumentException("radiusTo must be < MAX_RADIUS " + MAX_RADIUS + ": " + radiusTo); + } else if (radiusFrom >= radiusTo) { + throw new IllegalArgumentException("radiusFrom must be < radiusTo: " + radiusFrom + " -> " + radiusTo); + } else { + this.chunkX = chunkX; + this.chunkZ = chunkZ; + this.radiusFromSq = (long)radiusFrom * radiusFrom; + this.radiusToSq = (long)radiusTo * radiusTo; + long widthTo = 1L + radiusTo * 2L; + this.maxI = widthTo * widthTo; + if (radiusFrom != 0) { + float halfFrom = radiusFrom / 2.0F; + float sq = halfFrom * halfFrom; + int diagRadius = (int)Math.sqrt(sq + sq); + long widthFrom = 1L + diagRadius * 2L; + this.i = widthFrom * widthFrom; + long pos = SpiralIterator.getPosFromIndex((int)this.i); + this.x = ChunkUtil.xOfChunkIndex(pos); + this.z = ChunkUtil.zOfChunkIndex(pos); + this.dx = 1; + this.dz = 0; + } else { + this.i = 0L; + this.x = this.z = 0; + this.dx = 0; + this.dz = -1; + } + + this.hasNext = false; + this.prepareNext(); + this.setup = true; + } + } + + public void reset() { + this.setup = false; + this.hasNext = false; + } + + public long next() { + if (!this.setup) { + throw new IllegalStateException("SpiralIterator is not setup!"); + } else { + if (!this.hasNext) { + this.prepareNext(); + } + + if (!this.hasNext) { + throw new NoSuchElementException("No more positions inside the circle!"); + } else { + this.hasNext = false; + return this.nextChunk; + } + } + } + + public boolean hasNext() { + if (!this.setup) { + throw new IllegalStateException("SpiralIterator is not setup!"); + } else { + if (!this.hasNext) { + this.prepareNext(); + } + + return this.hasNext; + } + } + + public int getCurrentRadius() { + return MathUtil.ceil((Math.sqrt(this.i) - 1.0) / 2.0); + } + + public int getCompletedRadius() { + return (int)((Math.sqrt(this.i) - 1.0) / 2.0); + } + + private void prepareNext() { + while (!this.hasNext && this.i < this.maxI) { + long rx = this.x; + long rz = this.z; + long radiusSq = rx * rx + rz * rz; + if (radiusSq >= this.radiusFromSq && radiusSq <= this.radiusToSq) { + this.nextChunk = ChunkUtil.indexChunk(this.chunkX + this.x, this.chunkZ + this.z); + this.hasNext = true; + } + + if (this.x == this.z || this.x < 0 && this.x == -this.z || this.x > 0 && this.x == 1 - this.z) { + int tempDx = this.dx; + this.dx = -this.dz; + this.dz = tempDx; + } + + this.x = this.x + this.dx; + this.z = this.z + this.dz; + this.i++; + } + } +} diff --git a/src/com/hypixel/hytale/math/iterator/LineIterator.java b/src/com/hypixel/hytale/math/iterator/LineIterator.java new file mode 100644 index 0000000..794d78b --- /dev/null +++ b/src/com/hypixel/hytale/math/iterator/LineIterator.java @@ -0,0 +1,136 @@ +package com.hypixel.hytale.math.iterator; + +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.Iterator; +import java.util.NoSuchElementException; +import javax.annotation.Nonnull; + +public class LineIterator implements Iterator { + private final int x_inc; + private final int y_inc; + private final int z_inc; + private final int l; + private final int m; + private final int n; + private final int dx2; + private final int dy2; + private final int dz2; + private int i; + private int err1; + private int err2; + private int pointX; + private int pointY; + private int pointZ; + + public LineIterator(int x1, int y1, int z1, int x2, int y2, int z2) { + this.pointX = x1; + this.pointY = y1; + this.pointZ = z1; + int dx = x2 - x1; + int dy = y2 - y1; + int dz = z2 - z1; + this.x_inc = dx < 0 ? -1 : 1; + this.y_inc = dy < 0 ? -1 : 1; + this.z_inc = dz < 0 ? -1 : 1; + this.l = Math.abs(dx); + this.m = Math.abs(dy); + this.n = Math.abs(dz); + this.dx2 = this.l << 1; + this.dy2 = this.m << 1; + this.dz2 = this.n << 1; + if (this.l >= this.m && this.l >= this.n) { + this.err1 = this.dy2 - this.l; + this.err2 = this.dz2 - this.l; + } else if (this.m >= this.l && this.m >= this.n) { + this.err1 = this.dx2 - this.m; + this.err2 = this.dz2 - this.m; + } else { + this.err1 = this.dx2 - this.n; + this.err2 = this.dy2 - this.n; + } + } + + @Override + public boolean hasNext() { + if (this.l >= this.m && this.l >= this.n) { + return this.i <= this.l; + } else { + return this.m >= this.l && this.m >= this.n ? this.i <= this.m : this.i <= this.n; + } + } + + @Nonnull + public Vector3i next() { + if (this.l >= this.m && this.l >= this.n) { + if (this.i == this.l) { + this.i++; + return new Vector3i(this.pointX, this.pointY, this.pointZ); + } else if (this.i > this.l) { + throw new NoSuchElementException(); + } else { + Vector3i vector3i = new Vector3i(this.pointX, this.pointY, this.pointZ); + this.pointX = this.pointX + this.x_inc; + if (this.err1 > 0) { + this.pointY = this.pointY + this.y_inc; + this.err1 = this.err1 - this.dx2; + } + + this.err1 = this.err1 + this.dy2; + if (this.err2 > 0) { + this.pointZ = this.pointZ + this.z_inc; + this.err2 = this.err2 - this.dx2; + } + + this.err2 = this.err2 + this.dz2; + this.i++; + return vector3i; + } + } else if (this.m >= this.l && this.m >= this.n) { + if (this.i == this.m) { + this.i++; + return new Vector3i(this.pointX, this.pointY, this.pointZ); + } else if (this.i > this.m) { + throw new NoSuchElementException(); + } else { + Vector3i vector3ix = new Vector3i(this.pointX, this.pointY, this.pointZ); + if (this.err1 > 0) { + this.pointX = this.pointX + this.x_inc; + this.err1 = this.err1 - this.dy2; + } + + this.err1 = this.err1 + this.dx2; + this.pointY = this.pointY + this.y_inc; + if (this.err2 > 0) { + this.pointZ = this.pointZ + this.z_inc; + this.err2 = this.err2 - this.dy2; + } + + this.err2 = this.err2 + this.dz2; + this.i++; + return vector3ix; + } + } else if (this.i == this.n) { + this.i++; + return new Vector3i(this.pointX, this.pointY, this.pointZ); + } else if (this.i > this.n) { + throw new NoSuchElementException(); + } else { + Vector3i vector3ixx = new Vector3i(this.pointX, this.pointY, this.pointZ); + if (this.err1 > 0) { + this.pointX = this.pointX + this.x_inc; + this.err1 = this.err1 - this.dz2; + } + + this.err1 = this.err1 + this.dx2; + if (this.err2 > 0) { + this.pointY = this.pointY + this.y_inc; + this.err2 = this.err2 - this.dz2; + } + + this.err2 = this.err2 + this.dy2; + this.pointZ = this.pointZ + this.z_inc; + this.i++; + return vector3ixx; + } + } +} diff --git a/src/com/hypixel/hytale/math/iterator/SpiralIterator.java b/src/com/hypixel/hytale/math/iterator/SpiralIterator.java new file mode 100644 index 0000000..3ea91b3 --- /dev/null +++ b/src/com/hypixel/hytale/math/iterator/SpiralIterator.java @@ -0,0 +1,158 @@ +package com.hypixel.hytale.math.iterator; + +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; + +public class SpiralIterator { + public static final long MAX_RADIUS_LONG = (long)Math.sqrt(9.223372E18F) / 2L - 1L; + public static final int MAX_RADIUS = (int)MAX_RADIUS_LONG; + private boolean setup; + private int chunkX; + private int chunkZ; + private long maxI; + private long i; + private int x; + private int z; + private int dx; + private int dz; + + public SpiralIterator() { + } + + public SpiralIterator(int chunkX, int chunkZ, int radius) { + this.init(chunkX, chunkZ, radius); + } + + public SpiralIterator(int chunkX, int chunkZ, int radiusFrom, int radiusTo) { + this.init(chunkX, chunkZ, radiusFrom, radiusTo); + } + + public void init(int chunkX, int chunkZ, int radiusTo) { + this.init(chunkX, chunkZ, 0, radiusTo); + } + + public void init(int chunkX, int chunkZ, int radiusFrom, int radiusTo) { + if (radiusFrom < 0) { + throw new IllegalArgumentException("radiusFrom must be >= 0: " + radiusFrom); + } else if (radiusTo <= 0) { + throw new IllegalArgumentException("radiusTo must be > 0: " + radiusTo); + } else if (radiusTo > MAX_RADIUS) { + throw new IllegalArgumentException("radiusTo must be < MAX_RADIUS " + MAX_RADIUS + ": " + radiusTo); + } else if (radiusFrom >= radiusTo) { + throw new IllegalArgumentException("radiusFrom must be < radiusTo: " + radiusFrom + " -> " + radiusTo); + } else { + this.chunkX = chunkX; + this.chunkZ = chunkZ; + long widthTo = 1L + radiusTo * 2L; + this.maxI = widthTo * widthTo; + if (radiusFrom != 0) { + long widthFrom = 1L + radiusFrom * 2L; + this.i = widthFrom * widthFrom; + long pos = getPosFromIndex((int)this.i); + this.x = ChunkUtil.xOfChunkIndex(pos); + this.z = ChunkUtil.zOfChunkIndex(pos); + this.dx = 1; + this.dz = 0; + } else { + this.i = 0L; + this.x = this.z = 0; + this.dx = 0; + this.dz = -1; + } + + this.setup = true; + } + } + + public void reset() { + this.setup = false; + } + + public long next() { + if (!this.setup) { + throw new IllegalStateException("SpiralIterator is not setup!"); + } else { + long chunkCoordinates = ChunkUtil.indexChunk(this.chunkX + this.x, this.chunkZ + this.z); + if (this.x == this.z || this.x < 0 && this.x == -this.z || this.x > 0 && this.x == 1 - this.z) { + int tempDx = this.dx; + this.dx = -this.dz; + this.dz = tempDx; + } + + this.x = this.x + this.dx; + this.z = this.z + this.dz; + this.i++; + return chunkCoordinates; + } + } + + public boolean hasNext() { + return this.i < this.maxI; + } + + public boolean isSetup() { + return this.setup; + } + + public long getIndex() { + return this.i; + } + + public long getMaxIndex() { + return this.maxI; + } + + public int getChunkX() { + return this.chunkX; + } + + public int getChunkZ() { + return this.chunkZ; + } + + public int getX() { + return this.x; + } + + public int getZ() { + return this.z; + } + + public int getDx() { + return this.dx; + } + + public int getDz() { + return this.dz; + } + + public int getCurrentRadius() { + return MathUtil.ceil((Math.sqrt(this.i) - 1.0) / 2.0); + } + + public int getCompletedRadius() { + return (int)((Math.sqrt(this.i) - 1.0) / 2.0); + } + + public static long getPosFromIndex(int index) { + if (index < 0) { + throw new IllegalArgumentException("Index mus be >= 0"); + } else { + index++; + int k = MathUtil.ceil((Math.sqrt(index) - 1.0) / 2.0); + int t = 2 * k; + int m = (int)Math.pow(1 + t, 2.0); + int m1 = m - t; + if (index < m1) { + int m2 = m1 - t; + if (index < m2) { + return index >= m2 - t ? ChunkUtil.indexChunk(-k + (m2 - index), k) : ChunkUtil.indexChunk(k, k - (m2 - index - t)); + } else { + return ChunkUtil.indexChunk(-k, -k + (m1 - index)); + } + } else { + return ChunkUtil.indexChunk(k - (m - index), -k); + } + } + } +} diff --git a/src/com/hypixel/hytale/math/matrix/Matrix4d.java b/src/com/hypixel/hytale/math/matrix/Matrix4d.java new file mode 100644 index 0000000..ced4be4 --- /dev/null +++ b/src/com/hypixel/hytale/math/matrix/Matrix4d.java @@ -0,0 +1,542 @@ +package com.hypixel.hytale.math.matrix; + +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector4d; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class Matrix4d { + public static final int M00 = 0; + public static final int M10 = 4; + public static final int M20 = 8; + public static final int M30 = 12; + public static final int M01 = 1; + public static final int M11 = 5; + public static final int M21 = 9; + public static final int M31 = 13; + public static final int M02 = 2; + public static final int M12 = 6; + public static final int M22 = 10; + public static final int M32 = 14; + public static final int M03 = 3; + public static final int M13 = 7; + public static final int M23 = 11; + public static final int M33 = 15; + public static final int COLUMNS = 4; + public static final int ROWS = 4; + public static final int FIELDS = 16; + private final double[] m; + + public Matrix4d() { + this(new double[16]); + } + + public Matrix4d(@Nonnull Matrix4d other) { + this(); + this.assign(other); + } + + public Matrix4d(double[] m) { + this.m = m; + } + + public double get(int idx) { + return this.m[idx]; + } + + public double get(int col, int row) { + return this.get(idx(col, row)); + } + + @Nonnull + public Matrix4d set(int idx, double val) { + this.m[idx] = val; + return this; + } + + @Nonnull + public Matrix4d set(int col, int row, double val) { + return this.set(idx(col, row), val); + } + + @Nonnull + public Matrix4d add(int idx, double val) { + this.m[idx] = this.m[idx] + val; + return this; + } + + @Nonnull + public Matrix4d add(int col, int row, double val) { + return this.set(idx(col, row), val); + } + + @Nonnull + public Matrix4d identity() { + Arrays.fill(this.m, 0.0); + + for (int i = 0; i < 16; i += 5) { + this.m[i] = 1.0; + } + + return this; + } + + @Nonnull + public Matrix4d assign(@Nonnull Matrix4d other) { + System.arraycopy(other.m, 0, this.m, 0, 16); + return this; + } + + @Nonnull + public Matrix4d assign( + double m00, + double m10, + double m20, + double m30, + double m01, + double m11, + double m21, + double m31, + double m02, + double m12, + double m22, + double m32, + double m03, + double m13, + double m23, + double m33 + ) { + this.m[0] = m00; + this.m[1] = m01; + this.m[2] = m02; + this.m[3] = m03; + this.m[4] = m10; + this.m[5] = m11; + this.m[6] = m12; + this.m[7] = m13; + this.m[8] = m20; + this.m[9] = m21; + this.m[10] = m22; + this.m[11] = m23; + this.m[12] = m30; + this.m[13] = m31; + this.m[14] = m32; + this.m[15] = m33; + return this; + } + + @Nonnull + public Matrix4d translate(@Nonnull Vector3d vec) { + return this.translate(vec.x, vec.y, vec.z); + } + + @Nonnull + public Matrix4d translate(double x, double y, double z) { + for (int i = 0; i < 4; i++) { + this.m[i + 12] = this.m[i + 12] + (this.m[i] * x + this.m[i + 4] * y + this.m[i + 8] * z); + } + + return this; + } + + @Nonnull + public Matrix4d scale(double x, double y, double z) { + for (int i = 0; i < 4; i++) { + this.m[i] = this.m[i] * x; + this.m[i + 4] = this.m[i + 4] * y; + this.m[i + 8] = this.m[i + 8] * z; + } + + return this; + } + + @Nonnull + public Vector3d multiplyPosition(@Nonnull Vector3d vec) { + return this.multiplyPosition(vec, vec); + } + + @Nonnull + public Vector3d multiplyPosition(@Nonnull Vector3d vec, @Nonnull Vector3d result) { + double x = this.m[0] * vec.x + this.m[4] * vec.y + this.m[8] * vec.z + this.m[12]; + double y = this.m[1] * vec.x + this.m[5] * vec.y + this.m[9] * vec.z + this.m[13]; + double z = this.m[2] * vec.x + this.m[6] * vec.y + this.m[10] * vec.z + this.m[14]; + double w = this.m[3] * vec.x + this.m[7] * vec.y + this.m[11] * vec.z + this.m[15]; + double invW = 1.0 / w; + result.assign(x * invW, y * invW, z * invW); + return result; + } + + @Nonnull + public Vector3d multiplyDirection(@Nonnull Vector3d vec) { + double x = this.m[0] * vec.x + this.m[4] * vec.y + this.m[8] * vec.z; + double y = this.m[1] * vec.x + this.m[5] * vec.y + this.m[9] * vec.z; + double z = this.m[2] * vec.x + this.m[6] * vec.y + this.m[10] * vec.z; + vec.assign(x, y, z); + return vec; + } + + @Nonnull + public Vector4d multiply(@Nonnull Vector4d vec) { + return this.multiply(vec, vec); + } + + @Nonnull + public Vector4d multiply(@Nonnull Vector4d vec, @Nonnull Vector4d result) { + double x = this.m[0] * vec.x + this.m[4] * vec.y + this.m[8] * vec.z + this.m[12] * vec.w; + double y = this.m[1] * vec.x + this.m[5] * vec.y + this.m[9] * vec.z + this.m[13] * vec.w; + double z = this.m[2] * vec.x + this.m[6] * vec.y + this.m[10] * vec.z + this.m[14] * vec.w; + double w = this.m[3] * vec.x + this.m[7] * vec.y + this.m[11] * vec.z + this.m[15] * vec.w; + result.assign(x, y, z, w); + return result; + } + + @Nonnull + public Matrix4d multiply(@Nonnull Matrix4d other) { + double a00 = this.m[0]; + double a01 = this.m[1]; + double a02 = this.m[2]; + double a03 = this.m[3]; + double a10 = this.m[4]; + double a11 = this.m[5]; + double a12 = this.m[6]; + double a13 = this.m[7]; + double a20 = this.m[8]; + double a21 = this.m[9]; + double a22 = this.m[10]; + double a23 = this.m[11]; + double a30 = this.m[12]; + double a31 = this.m[13]; + double a32 = this.m[14]; + double a33 = this.m[15]; + double b00 = other.m[0]; + double b01 = other.m[1]; + double b02 = other.m[2]; + double b03 = other.m[3]; + double b10 = other.m[4]; + double b11 = other.m[5]; + double b12 = other.m[6]; + double b13 = other.m[7]; + double b20 = other.m[8]; + double b21 = other.m[9]; + double b22 = other.m[10]; + double b23 = other.m[11]; + double b30 = other.m[12]; + double b31 = other.m[13]; + double b32 = other.m[14]; + double b33 = other.m[15]; + this.m[0] = a00 * b00 + a10 * b01 + a20 * b02 + a30 * b03; + this.m[1] = a01 * b00 + a11 * b01 + a21 * b02 + a31 * b03; + this.m[2] = a02 * b00 + a12 * b01 + a22 * b02 + a32 * b03; + this.m[3] = a03 * b00 + a13 * b01 + a23 * b02 + a33 * b03; + this.m[4] = a00 * b10 + a10 * b11 + a20 * b12 + a30 * b13; + this.m[5] = a01 * b10 + a11 * b11 + a21 * b12 + a31 * b13; + this.m[6] = a02 * b10 + a12 * b11 + a22 * b12 + a32 * b13; + this.m[7] = a03 * b10 + a13 * b11 + a23 * b12 + a33 * b13; + this.m[8] = a00 * b20 + a10 * b21 + a20 * b22 + a30 * b23; + this.m[9] = a01 * b20 + a11 * b21 + a21 * b22 + a31 * b23; + this.m[10] = a02 * b20 + a12 * b21 + a22 * b22 + a32 * b23; + this.m[11] = a03 * b20 + a13 * b21 + a23 * b22 + a33 * b23; + this.m[12] = a00 * b30 + a10 * b31 + a20 * b32 + a30 * b33; + this.m[13] = a01 * b30 + a11 * b31 + a21 * b32 + a31 * b33; + this.m[14] = a02 * b30 + a12 * b31 + a22 * b32 + a32 * b33; + this.m[15] = a03 * b30 + a13 * b31 + a23 * b32 + a33 * b33; + return this; + } + + public boolean invert() { + double src0 = this.m[0]; + double src4 = this.m[1]; + double src8 = this.m[2]; + double src12 = this.m[3]; + double src1 = this.m[4]; + double src5 = this.m[5]; + double src9 = this.m[6]; + double src13 = this.m[7]; + double src2 = this.m[8]; + double src6 = this.m[9]; + double src10 = this.m[10]; + double src14 = this.m[11]; + double src3 = this.m[12]; + double src7 = this.m[13]; + double src11 = this.m[14]; + double src15 = this.m[15]; + double atmp0 = src10 * src15; + double atmp1 = src11 * src14; + double atmp2 = src9 * src15; + double atmp3 = src11 * src13; + double atmp4 = src9 * src14; + double atmp5 = src10 * src13; + double atmp6 = src8 * src15; + double atmp7 = src11 * src12; + double atmp8 = src8 * src14; + double atmp9 = src10 * src12; + double atmp10 = src8 * src13; + double atmp11 = src9 * src12; + double dst0 = atmp0 * src5 + atmp3 * src6 + atmp4 * src7 - (atmp1 * src5 + atmp2 * src6 + atmp5 * src7); + double dst1 = atmp1 * src4 + atmp6 * src6 + atmp9 * src7 - (atmp0 * src4 + atmp7 * src6 + atmp8 * src7); + double dst2 = atmp2 * src4 + atmp7 * src5 + atmp10 * src7 - (atmp3 * src4 + atmp6 * src5 + atmp11 * src7); + double dst3 = atmp5 * src4 + atmp8 * src5 + atmp11 * src6 - (atmp4 * src4 + atmp9 * src5 + atmp10 * src6); + double dst4 = atmp1 * src1 + atmp2 * src2 + atmp5 * src3 - (atmp0 * src1 + atmp3 * src2 + atmp4 * src3); + double dst5 = atmp0 * src0 + atmp7 * src2 + atmp8 * src3 - (atmp1 * src0 + atmp6 * src2 + atmp9 * src3); + double dst6 = atmp3 * src0 + atmp6 * src1 + atmp11 * src3 - (atmp2 * src0 + atmp7 * src1 + atmp10 * src3); + double dst7 = atmp4 * src0 + atmp9 * src1 + atmp10 * src2 - (atmp5 * src0 + atmp8 * src1 + atmp11 * src2); + double btmp0 = src2 * src7; + double btmp1 = src3 * src6; + double btmp2 = src1 * src7; + double btmp3 = src3 * src5; + double btmp4 = src1 * src6; + double btmp5 = src2 * src5; + double btmp6 = src0 * src7; + double btmp7 = src3 * src4; + double btmp8 = src0 * src6; + double btmp9 = src2 * src4; + double btmp10 = src0 * src5; + double btmp11 = src1 * src4; + double dst8 = btmp0 * src13 + btmp3 * src14 + btmp4 * src15 - (btmp1 * src13 + btmp2 * src14 + btmp5 * src15); + double dst9 = btmp1 * src12 + btmp6 * src14 + btmp9 * src15 - (btmp0 * src12 + btmp7 * src14 + btmp8 * src15); + double dst10 = btmp2 * src12 + btmp7 * src13 + btmp10 * src15 - (btmp3 * src12 + btmp6 * src13 + btmp11 * src15); + double dst11 = btmp5 * src12 + btmp8 * src13 + btmp11 * src14 - (btmp4 * src12 + btmp9 * src13 + btmp10 * src14); + double dst12 = btmp2 * src10 + btmp5 * src11 + btmp1 * src9 - (btmp4 * src11 + btmp0 * src9 + btmp3 * src10); + double dst13 = btmp8 * src11 + btmp0 * src8 + btmp7 * src10 - (btmp6 * src10 + btmp9 * src11 + btmp1 * src8); + double dst14 = btmp6 * src9 + btmp11 * src11 + btmp3 * src8 - (btmp10 * src11 + btmp2 * src8 + btmp7 * src9); + double dst15 = btmp10 * src10 + btmp4 * src8 + btmp9 * src9 - (btmp8 * src9 + btmp11 * src10 + btmp5 * src8); + double det = src0 * dst0 + src1 * dst1 + src2 * dst2 + src3 * dst3; + if (det == 0.0) { + return false; + } else { + double invdet = 1.0 / det; + this.m[0] = dst0 * invdet; + this.m[1] = dst1 * invdet; + this.m[2] = dst2 * invdet; + this.m[3] = dst3 * invdet; + this.m[4] = dst4 * invdet; + this.m[5] = dst5 * invdet; + this.m[6] = dst6 * invdet; + this.m[7] = dst7 * invdet; + this.m[8] = dst8 * invdet; + this.m[9] = dst9 * invdet; + this.m[10] = dst10 * invdet; + this.m[11] = dst11 * invdet; + this.m[12] = dst12 * invdet; + this.m[13] = dst13 * invdet; + this.m[14] = dst14 * invdet; + this.m[15] = dst15 * invdet; + return true; + } + } + + @Nonnull + public Matrix4d projectionOrtho(double left, double right, double bottom, double top, double near, double far) { + double r_width = 1.0 / (right + left); + double r_height = 1.0 / (top + bottom); + double r_depth = -1.0 / (far - near); + double x = 2.0 * r_width; + double y = 2.0 * r_height; + double z = 2.0 * r_depth; + this.m[1] = this.m[2] = this.m[3] = 0.0; + this.m[4] = this.m[6] = this.m[7] = 0.0; + this.m[8] = this.m[9] = this.m[11] = 0.0; + this.m[15] = 1.0; + this.m[0] = x; + this.m[5] = y; + this.m[10] = z; + this.m[12] = -(right - left) * r_width; + this.m[13] = -(top - bottom) * r_height; + this.m[14] = (far + near) * r_depth; + return this; + } + + @Nonnull + public Matrix4d projectionFrustum(double left, double right, double bottom, double top, double near, double far) { + double r_width = 1.0 / (right + left); + double r_height = 1.0 / (top + bottom); + double r_depth = 1.0 / (near - far); + this.m[1] = this.m[2] = this.m[3] = 0.0; + this.m[4] = this.m[6] = this.m[7] = 0.0; + this.m[12] = this.m[13] = this.m[15] = 0.0; + this.m[11] = -1.0; + this.m[0] = 2.0 * (near * r_width); + this.m[5] = 2.0 * (near * r_height); + this.m[14] = 2.0 * (far * near * r_depth); + this.m[8] = 2.0 * (right - left) * r_width; + this.m[9] = (top - bottom) * r_height; + this.m[10] = (far + near) * r_depth; + return this; + } + + @Nonnull + public Matrix4d projectionCone(double fov, double aspect, double near, double far) { + double f = 1.0 / Math.tan(fov * 0.5); + double r = 1.0 / (near - far); + this.m[0] = f / aspect; + this.m[1] = this.m[2] = this.m[3] = 0.0; + this.m[5] = f; + this.m[4] = this.m[6] = this.m[7] = 0.0; + this.m[8] = this.m[9] = 0.0; + this.m[10] = (far + near) * r; + this.m[11] = -1.0; + this.m[12] = this.m[13] = this.m[15] = 0.0; + this.m[14] = 2.0 * far * near * r; + return this; + } + + @Nonnull + public Matrix4d viewTarget(double eyeX, double eyeY, double eyeZ, double centerX, double centerY, double centerZ, double upX, double upY, double upZ) { + double dirX = centerX - eyeX; + double dirY = centerY - eyeY; + double dirZ = centerZ - eyeZ; + return this.viewDirection(eyeX, eyeY, eyeZ, dirX, dirY, dirZ, upX, upY, upZ); + } + + @Nonnull + public Matrix4d viewDirection(double eyeX, double eyeY, double eyeZ, double dirX, double dirY, double dirZ, double upX, double upY, double upZ) { + double rlf = 1.0 / MathUtil.length(dirX, dirY, dirZ); + dirX *= rlf; + dirY *= rlf; + dirZ *= rlf; + double sx = dirY * upZ - dirZ * upY; + double sy = dirZ * upX - dirX * upZ; + double sz = dirX * upY - dirY * upX; + double rls = 1.0 / MathUtil.length(sx, sy, sz); + sx *= rls; + sy *= rls; + sz *= rls; + double ux = sy * dirZ - sz * dirY; + double uy = sz * dirX - sx * dirZ; + double uz = sx * dirY - sy * dirX; + this.m[0] = sx; + this.m[1] = ux; + this.m[2] = -dirX; + this.m[3] = 0.0; + this.m[4] = sy; + this.m[5] = uy; + this.m[6] = -dirY; + this.m[7] = 0.0; + this.m[8] = sz; + this.m[9] = uz; + this.m[10] = -dirZ; + this.m[11] = 0.0; + this.m[12] = 0.0; + this.m[13] = 0.0; + this.m[14] = 0.0; + this.m[15] = 1.0; + this.translate(-eyeX, -eyeY, -eyeZ); + return this; + } + + @Nonnull + public Matrix4d rotateAxis(double a, double x, double y, double z, @Nonnull Matrix4d tmp) { + return this.multiply(tmp.setRotateAxis(a, x, y, z)); + } + + @Nonnull + public Matrix4d setRotateAxis(double a, double x, double y, double z) { + double sin = TrigMathUtil.sin(a); + double cos = TrigMathUtil.cos(a); + this.m[0] = cos + x * x * (1.0 - cos); + this.m[1] = x * y * (1.0 - cos) - z * sin; + this.m[2] = x * z * (1.0 - cos) + y * sin; + this.m[3] = 0.0; + this.m[4] = y * x * (1.0 - cos) + z * sin; + this.m[5] = cos + y * y * (1.0 - cos); + this.m[6] = y * z * (1.0 - cos) - x * sin; + this.m[7] = 0.0; + this.m[8] = z * x * (1.0 - cos) - y * sin; + this.m[9] = z * y * (1.0 - cos) + x * sin; + this.m[10] = cos + z * z * (1.0 - cos); + this.m[11] = 0.0; + this.m[12] = 0.0; + this.m[13] = 0.0; + this.m[14] = 0.0; + this.m[15] = 1.0; + return this; + } + + @Nonnull + public Matrix4d rotateEuler(double x, double y, double z, @Nonnull Matrix4d tmp) { + return this.multiply(tmp.setRotateEuler(x, y, z)); + } + + @Nonnull + public Matrix4d setRotateEuler(double x, double y, double z) { + double cx = TrigMathUtil.cos(x); + double sx = TrigMathUtil.sin(x); + double cy = TrigMathUtil.cos(y); + double sy = TrigMathUtil.sin(y); + double cz = TrigMathUtil.cos(z); + double sz = TrigMathUtil.sin(z); + double cxsy = cx * sy; + double sxsy = sx * sy; + this.m[0] = cy * cz; + this.m[1] = -cy * sz; + this.m[2] = sy; + this.m[4] = sxsy * cz + cx * sz; + this.m[5] = -sxsy * sz + cx * cz; + this.m[6] = -sx * cy; + this.m[8] = -cxsy * cz + sx * sz; + this.m[9] = cxsy * sz + sx * cz; + this.m[10] = cx * cy; + this.m[3] = this.m[7] = this.m[11] = 0.0; + this.m[12] = this.m[13] = this.m[14] = 0.0; + this.m[15] = 1.0; + return this; + } + + public double[] getData() { + return this.m; + } + + public float[] asFloatData() { + float[] data = new float[16]; + + for (int i = 0; i < 16; i++) { + data[i] = (float)this.m[i]; + } + + return data; + } + + @Nonnull + @Override + public String toString() { + return "Matrix4d{\n " + + this.m[0] + + " " + + this.m[4] + + " " + + this.m[8] + + " " + + this.m[12] + + "\n " + + this.m[1] + + " " + + this.m[5] + + " " + + this.m[9] + + " " + + this.m[13] + + "\n " + + this.m[2] + + " " + + this.m[6] + + " " + + this.m[10] + + " " + + this.m[14] + + "\n " + + this.m[3] + + " " + + this.m[7] + + " " + + this.m[11] + + " " + + this.m[15] + + "\n}"; + } + + public static int idx(int col, int row) { + return col << 2 | row; + } +} diff --git a/src/com/hypixel/hytale/math/random/RandomExtra.java b/src/com/hypixel/hytale/math/random/RandomExtra.java new file mode 100644 index 0000000..d1d4e27 --- /dev/null +++ b/src/com/hypixel/hytale/math/random/RandomExtra.java @@ -0,0 +1,324 @@ +package com.hypixel.hytale.math.random; + +import com.hypixel.hytale.function.function.TriFunction; +import com.hypixel.hytale.math.vector.Vector3d; +import java.time.Duration; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import java.util.function.ToDoubleBiFunction; +import java.util.function.ToDoubleFunction; +import java.util.function.ToIntFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class RandomExtra { + private RandomExtra() { + } + + public static double randomBinomial() { + ThreadLocalRandom random = ThreadLocalRandom.current(); + double a = random.nextDouble(); + double b = random.nextDouble(); + return a - b; + } + + public static double randomRange(@Nonnull double[] range) { + return randomRange(range[0], range[1]); + } + + public static double randomRange(double from, double to) { + return from + ThreadLocalRandom.current().nextDouble() * (to - from); + } + + public static float randomRange(@Nonnull float[] range) { + return randomRange(range[0], range[1]); + } + + public static float randomRange(float from, float to) { + return from + ThreadLocalRandom.current().nextFloat() * (to - from); + } + + public static int randomRange(int bound) { + return ThreadLocalRandom.current().nextInt(bound); + } + + public static int randomRange(@Nonnull int[] range) { + return randomRange(range[0], range[1]); + } + + public static int randomRange(int from, int to) { + return ThreadLocalRandom.current().nextInt(to - from + 1) + from; + } + + public static long randomRange(long from, long to) { + return ThreadLocalRandom.current().nextLong(to - from + 1L) + from; + } + + public static Duration randomDuration(@Nonnull Duration from, @Nonnull Duration to) { + return Duration.ofNanos(randomRange(from.toNanos(), to.toNanos())); + } + + public static boolean randomBoolean() { + return ThreadLocalRandom.current().nextBoolean(); + } + + public static T randomElement(@Nonnull List collection) { + return collection.get(randomRange(collection.size())); + } + + @Nonnull + public static Vector3d jitter(@Nonnull Vector3d vec, double maxRange) { + ThreadLocalRandom current = ThreadLocalRandom.current(); + vec.x = vec.x + current.nextDouble() * maxRange; + vec.y = vec.y + current.nextDouble() * maxRange; + vec.z = vec.z + current.nextDouble() * maxRange; + return vec; + } + + @Nullable + public static T randomWeightedElement(@Nonnull Collection elements, @Nonnull ToDoubleFunction weight) { + Iterator i = elements.iterator(); + double sumWeights = 0.0; + + while (i.hasNext()) { + sumWeights += weight.applyAsDouble((T)i.next()); + } + + return randomWeightedElement(elements, weight, sumWeights); + } + + @Nullable + public static T randomWeightedElement(@Nonnull Collection elements, @Nonnull ToDoubleFunction weight, double sumWeights) { + if (sumWeights == 0.0) { + return null; + } else { + Iterator i = elements.iterator(); + T result = null; + sumWeights *= ThreadLocalRandom.current().nextDouble(); + + while (i.hasNext()) { + result = (T)i.next(); + sumWeights -= weight.applyAsDouble(result); + if (sumWeights < 0.0) { + break; + } + } + + return result; + } + } + + @Nullable + public static T randomIntWeightedElement(@Nonnull Collection elements, @Nonnull ToIntFunction weight) { + Iterator i = elements.iterator(); + int sumWeights = 0; + + while (i.hasNext()) { + sumWeights += weight.applyAsInt((T)i.next()); + } + + return randomIntWeightedElement(elements, weight, sumWeights); + } + + @Nullable + public static T randomIntWeightedElement(@Nonnull Collection elements, @Nonnull ToIntFunction weight, int sumWeights) { + if (sumWeights == 0) { + return null; + } else { + Iterator i = elements.iterator(); + T result = null; + sumWeights = randomRange(sumWeights); + + while (i.hasNext()) { + result = (T)i.next(); + sumWeights -= weight.applyAsInt(result); + if (sumWeights < 0) { + break; + } + } + + return result; + } + } + + @Nullable + public static T randomWeightedElementFiltered(@Nonnull Collection elements, @Nonnull Predicate filter, @Nonnull ToIntFunction weight) { + Iterator i = elements.iterator(); + int sumWeights = 0; + + while (i.hasNext()) { + T t = (T)i.next(); + if (filter.test(t)) { + sumWeights += weight.applyAsInt(t); + } + } + + return randomWeightedElementFiltered(elements, filter, weight, sumWeights); + } + + @Nullable + public static T randomWeightedElementFiltered( + @Nonnull Collection elements, @Nonnull Predicate filter, @Nonnull ToIntFunction weight, int sumWeights + ) { + if (sumWeights == 0) { + return null; + } else { + Iterator i = elements.iterator(); + T result = null; + sumWeights = randomRange(sumWeights); + + while (i.hasNext()) { + result = (T)i.next(); + if (filter.test(result)) { + sumWeights -= weight.applyAsInt(result); + if (sumWeights < 0) { + break; + } + } + } + + return result; + } + } + + @Nullable + public static T randomWeightedElement(@Nonnull Collection elements, @Nonnull Predicate filter, @Nonnull ToDoubleFunction weight) { + Iterator i = elements.iterator(); + double sumWeights = 0.0; + + while (i.hasNext()) { + T t = (T)i.next(); + if (filter.test(t)) { + sumWeights += weight.applyAsDouble(t); + } + } + + return randomWeightedElement(elements, filter, weight, sumWeights); + } + + @Nullable + public static T randomWeightedElement( + @Nonnull Collection elements, @Nonnull Predicate filter, @Nonnull ToDoubleFunction weight, double sumWeights + ) { + if (sumWeights == 0.0) { + return null; + } else { + Iterator i = elements.iterator(); + T result = null; + sumWeights *= ThreadLocalRandom.current().nextDouble(); + + while (i.hasNext()) { + result = (T)i.next(); + if (filter.test(result)) { + sumWeights -= weight.applyAsDouble(result); + if (sumWeights < 0.0) { + break; + } + } + } + + return result; + } + } + + @Nullable + public static T randomWeightedElement( + @Nonnull Collection elements, @Nonnull BiPredicate filter, @Nonnull ToDoubleBiFunction weight, double sumWeights, U meta + ) { + if (sumWeights == 0.0) { + return null; + } else { + Iterator i = elements.iterator(); + T result = null; + sumWeights *= ThreadLocalRandom.current().nextDouble(); + + while (i.hasNext()) { + result = (T)i.next(); + if (filter.test(result, meta)) { + sumWeights -= weight.applyAsDouble(result, meta); + if (sumWeights < 0.0) { + break; + } + } + } + + return result; + } + } + + public static void reservoirSample(@Nonnull List input, @Nonnull Predicate matcher, int count, @Nonnull List picked) { + int selected = 0; + + for (int i = 0; i < input.size(); i++) { + T element = input.get(i); + if (matcher.test(element)) { + if (selected < count) { + picked.add(element); + } else { + int j = randomRange(selected + 1); + if (j < count) { + picked.set(j, element); + } + } + + selected++; + } + } + } + + public static , F, T extends List, G, H> void reservoirSample( + @Nonnull S input, @Nonnull TriFunction filter, int count, @Nonnull T picked, G g, H h + ) { + int selected = 0; + + for (int i = 0; i < input.size(); i++) { + F f = filter.apply(input.get(i), g, h); + if (f != null) { + if (selected < count) { + picked.add(f); + } else { + int j = randomRange(selected + 1); + if (j < count) { + picked.set(j, f); + } + } + + selected++; + } + } + } + + public static > void reservoirSample(E element, int count, @Nonnull T picked) { + if (picked.size() < count) { + picked.add(element); + } else { + int i = randomRange(count + 1); + if (i < count) { + picked.set(i, element); + } + } + } + + public static int pickWeightedIndex(@Nonnull double[] weights) { + double sum = 0.0; + + for (double weight : weights) { + sum += weight; + } + + double randomWeight = ThreadLocalRandom.current().nextDouble(sum); + + for (int i = 0; i < weights.length - 1; i++) { + randomWeight -= weights[i]; + if (randomWeight <= 0.0) { + return i; + } + } + + return weights.length - 1; + } +} diff --git a/src/com/hypixel/hytale/math/range/FloatRange.java b/src/com/hypixel/hytale/math/range/FloatRange.java new file mode 100644 index 0000000..1eb0019 --- /dev/null +++ b/src/com/hypixel/hytale/math/range/FloatRange.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.math.range; + +import com.hypixel.hytale.math.codec.FloatRangeArrayCodec; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FloatRange { + public static final FloatRangeArrayCodec CODEC = new FloatRangeArrayCodec(); + private float inclusiveMin; + private float inclusiveMax; + private float range; + + public FloatRange() { + this(0.0F, 0.0F); + } + + public FloatRange(float inclusiveMin, float inclusiveMax) { + this.inclusiveMin = inclusiveMin; + this.inclusiveMax = inclusiveMax; + this.range = inclusiveMax - inclusiveMin + 1.0F; + } + + public float getInclusiveMin() { + return this.inclusiveMin; + } + + public float getInclusiveMax() { + return this.inclusiveMax; + } + + public void setInclusiveMin(float inclusiveMin) { + this.inclusiveMin = inclusiveMin; + this.range = this.inclusiveMax - inclusiveMin + 1.0F; + } + + public void setInclusiveMax(float inclusiveMax) { + this.inclusiveMax = inclusiveMax; + this.range = inclusiveMax - this.inclusiveMin + 1.0F; + } + + public float getFloat(float factor) { + float value = this.inclusiveMin + this.range * factor; + return Float.min(this.inclusiveMax, value); + } + + public float getFloat(double factor) { + float value = this.inclusiveMin + (float)(this.range * factor); + return Float.min(this.inclusiveMax, value); + } + + public boolean includes(float value) { + return value >= this.inclusiveMin && value <= this.inclusiveMax; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + FloatRange that = (FloatRange)o; + if (Float.compare(that.inclusiveMin, this.inclusiveMin) != 0) { + return false; + } else { + return Float.compare(that.inclusiveMax, this.inclusiveMax) != 0 ? false : Float.compare(that.range, this.range) == 0; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.inclusiveMin != 0.0F ? Float.floatToIntBits(this.inclusiveMin) : 0; + result = 31 * result + (this.inclusiveMax != 0.0F ? Float.floatToIntBits(this.inclusiveMax) : 0); + return 31 * result + (this.range != 0.0F ? Float.floatToIntBits(this.range) : 0); + } + + @Nonnull + @Override + public String toString() { + return "FloatRange{inclusiveMin=" + this.inclusiveMin + ", inclusiveMax=" + this.inclusiveMax + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/range/IntRange.java b/src/com/hypixel/hytale/math/range/IntRange.java new file mode 100644 index 0000000..5e85354 --- /dev/null +++ b/src/com/hypixel/hytale/math/range/IntRange.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.math.range; + +import com.hypixel.hytale.math.codec.IntRangeArrayCodec; +import com.hypixel.hytale.math.util.MathUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class IntRange { + public static final IntRangeArrayCodec CODEC = new IntRangeArrayCodec(); + private int inclusiveMin; + private int inclusiveMax; + private int range; + + public IntRange() { + this(0, 0); + } + + public IntRange(int inclusiveMin, int inclusiveMax) { + this.inclusiveMin = inclusiveMin; + this.inclusiveMax = inclusiveMax; + this.range = inclusiveMax - inclusiveMin + 1; + } + + public int getInclusiveMin() { + return this.inclusiveMin; + } + + public int getInclusiveMax() { + return this.inclusiveMax; + } + + public void setInclusiveMin(int inclusiveMin) { + this.inclusiveMin = inclusiveMin; + this.range = this.inclusiveMax - inclusiveMin + 1; + } + + public void setInclusiveMax(int inclusiveMax) { + this.inclusiveMax = inclusiveMax; + this.range = inclusiveMax - this.inclusiveMin + 1; + } + + public int getInt(float factor) { + int value = this.inclusiveMin + MathUtil.fastFloor(this.range * factor); + return Integer.min(this.inclusiveMax, value); + } + + public int getInt(double factor) { + int value = this.inclusiveMin + MathUtil.floor(this.range * factor); + return Integer.min(this.inclusiveMax, value); + } + + public boolean includes(int value) { + return value >= this.inclusiveMin && value <= this.inclusiveMax; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + IntRange intRange = (IntRange)o; + return this.inclusiveMin != intRange.inclusiveMin ? false : this.inclusiveMax == intRange.inclusiveMax; + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.inclusiveMin; + return 31 * result + this.inclusiveMax; + } + + @Nonnull + @Override + public String toString() { + return "IntRange{inclusiveMin=" + this.inclusiveMin + ", inclusiveMax=" + this.inclusiveMax + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/range/IntRangeBoundValidator.java b/src/com/hypixel/hytale/math/range/IntRangeBoundValidator.java new file mode 100644 index 0000000..1272114 --- /dev/null +++ b/src/com/hypixel/hytale/math/range/IntRangeBoundValidator.java @@ -0,0 +1,110 @@ +package com.hypixel.hytale.math.range; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.IntegerSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class IntRangeBoundValidator implements Validator { + private final Integer min; + private final Integer max; + private final boolean inclusive; + private final boolean lowerBound; + + @Nonnull + public static IntRangeBoundValidator lowerBound(Integer min, Integer max, boolean inclusive) { + return new IntRangeBoundValidator(min, max, inclusive, true); + } + + @Nonnull + public static IntRangeBoundValidator upperBound(Integer min, Integer max, boolean inclusive) { + return new IntRangeBoundValidator(min, max, inclusive, false); + } + + private IntRangeBoundValidator(Integer min, Integer max, boolean inclusive, boolean lowerBound) { + this.min = min; + this.max = max; + this.inclusive = inclusive; + this.lowerBound = lowerBound; + } + + public void accept(@Nullable IntRange intRange, @Nonnull ValidationResults results) { + if (intRange != null) { + if (this.lowerBound) { + this.validateBound(intRange.getInclusiveMin(), "Min bound", results); + } else { + this.validateBound(intRange.getInclusiveMax(), "Max bound", results); + } + } + } + + private void validateBound(int value, String boundName, @Nonnull ValidationResults results) { + if (this.min != null) { + if (this.inclusive) { + if (value < this.min) { + results.fail(boundName + " must be greater than or equal to " + this.min); + } + } else if (value <= this.min) { + results.fail(boundName + " must be greater than " + this.min); + } + } + + if (this.max != null) { + if (this.inclusive) { + if (value > this.max) { + results.fail(boundName + " must be less than or equal to " + this.max); + } + } else if (value >= this.max) { + results.fail(boundName + " must be less than " + this.max); + } + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + if (target instanceof ArraySchema arraySchema) { + Schema[] items = (Schema[])arraySchema.getItems(); + if (items == null) { + throw new IllegalArgumentException(); + } else { + if (this.lowerBound) { + if (!(items[0] instanceof IntegerSchema)) { + throw new IllegalArgumentException(); + } + + this.updateSchemaBound((IntegerSchema)items[0]); + } else { + if (!(items[1] instanceof IntegerSchema)) { + throw new IllegalArgumentException(); + } + + this.updateSchemaBound((IntegerSchema)items[1]); + } + } + } else { + throw new IllegalArgumentException(); + } + } + + private void updateSchemaBound(@Nonnull IntegerSchema integerSchema) { + if (this.min != null) { + if (this.inclusive) { + integerSchema.setMinimum(this.min); + } else { + integerSchema.setExclusiveMinimum(this.min); + } + } + + if (this.max != null) { + if (this.inclusive) { + integerSchema.setMaximum(this.max); + } else { + integerSchema.setExclusiveMaximum(this.max); + } + } + } +} diff --git a/src/com/hypixel/hytale/math/raycast/RaycastAABB.java b/src/com/hypixel/hytale/math/raycast/RaycastAABB.java new file mode 100644 index 0000000..047269d --- /dev/null +++ b/src/com/hypixel/hytale/math/raycast/RaycastAABB.java @@ -0,0 +1,469 @@ +package com.hypixel.hytale.math.raycast; + +import javax.annotation.Nonnull; + +public class RaycastAABB { + public static final double EPSILON = -1.0E-8; + + public RaycastAABB() { + } + + public static double intersect( + double minX, double minY, double minZ, double maxX, double maxY, double maxZ, double ox, double oy, double oz, double dx, double dy, double dz + ) { + double tNear = Double.POSITIVE_INFINITY; + double t = (minX - ox) / dx; + if (t < tNear && t > -1.0E-8) { + double u = oz + dz * t; + double v = oy + dy * t; + if (u >= minZ && u <= maxZ && v >= minY && v <= maxY) { + tNear = t; + } + } + + t = (maxX - ox) / dx; + if (t < tNear && t > -1.0E-8) { + double u = oz + dz * t; + double v = oy + dy * t; + if (u >= minZ && u <= maxZ && v >= minY && v <= maxY) { + tNear = t; + } + } + + t = (minY - oy) / dy; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oz + dz * t; + if (u >= minX && u <= maxX && v >= minZ && v <= maxZ) { + tNear = t; + } + } + + t = (maxY - oy) / dy; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oz + dz * t; + if (u >= minX && u <= maxX && v >= minZ && v <= maxZ) { + tNear = t; + } + } + + t = (minZ - oz) / dz; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oy + dy * t; + if (u >= minX && u <= maxX && v >= minY && v <= maxY) { + tNear = t; + } + } + + t = (maxZ - oz) / dz; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oy + dy * t; + if (u >= minX && u <= maxX && v >= minY && v <= maxY) { + tNear = t; + } + } + + return tNear; + } + + public static void intersect( + double minX, + double minY, + double minZ, + double maxX, + double maxY, + double maxZ, + double ox, + double oy, + double oz, + double dx, + double dy, + double dz, + @Nonnull RaycastAABB.RaycastConsumer consumer + ) { + double tNear = Double.POSITIVE_INFINITY; + double nx = 0.0; + double ny = 0.0; + double nz = 0.0; + double t = (minX - ox) / dx; + if (t < tNear && t > -1.0E-8) { + double u = oz + dz * t; + double v = oy + dy * t; + if (u >= minZ && u <= maxZ && v >= minY && v <= maxY) { + tNear = t; + nx = -1.0; + } + } + + t = (maxX - ox) / dx; + if (t < tNear && t > -1.0E-8) { + double u = oz + dz * t; + double v = oy + dy * t; + if (u >= minZ && u <= maxZ && v >= minY && v <= maxY) { + tNear = t; + nx = 1.0; + } + } + + t = (minY - oy) / dy; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oz + dz * t; + if (u >= minX && u <= maxX && v >= minZ && v <= maxZ) { + tNear = t; + ny = -1.0; + } + } + + t = (maxY - oy) / dy; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oz + dz * t; + if (u >= minX && u <= maxX && v >= minZ && v <= maxZ) { + tNear = t; + ny = 1.0; + } + } + + t = (minZ - oz) / dz; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oy + dy * t; + if (u >= minX && u <= maxX && v >= minY && v <= maxY) { + tNear = t; + nz = -1.0; + } + } + + t = (maxZ - oz) / dz; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oy + dy * t; + if (u >= minX && u <= maxX && v >= minY && v <= maxY) { + tNear = t; + nz = 1.0; + } + } + + consumer.accept(tNear != Double.POSITIVE_INFINITY, ox, oy, oz, dx, dy, dz, tNear, nx, ny, nz); + } + + public static void intersect( + double minX, + double minY, + double minZ, + double maxX, + double maxY, + double maxZ, + double ox, + double oy, + double oz, + double dx, + double dy, + double dz, + @Nonnull RaycastAABB.RaycastConsumerPlus1 consumer, + T obj1 + ) { + double tNear = Double.POSITIVE_INFINITY; + double nx = 0.0; + double ny = 0.0; + double nz = 0.0; + double t = (minX - ox) / dx; + if (t < tNear && t > -1.0E-8) { + double u = oz + dz * t; + double v = oy + dy * t; + if (u >= minZ && u <= maxZ && v >= minY && v <= maxY) { + tNear = t; + nx = -1.0; + } + } + + t = (maxX - ox) / dx; + if (t < tNear && t > -1.0E-8) { + double u = oz + dz * t; + double v = oy + dy * t; + if (u >= minZ && u <= maxZ && v >= minY && v <= maxY) { + tNear = t; + nx = 1.0; + } + } + + t = (minY - oy) / dy; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oz + dz * t; + if (u >= minX && u <= maxX && v >= minZ && v <= maxZ) { + tNear = t; + ny = -1.0; + } + } + + t = (maxY - oy) / dy; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oz + dz * t; + if (u >= minX && u <= maxX && v >= minZ && v <= maxZ) { + tNear = t; + ny = 1.0; + } + } + + t = (minZ - oz) / dz; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oy + dy * t; + if (u >= minX && u <= maxX && v >= minY && v <= maxY) { + tNear = t; + nz = -1.0; + } + } + + t = (maxZ - oz) / dz; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oy + dy * t; + if (u >= minX && u <= maxX && v >= minY && v <= maxY) { + tNear = t; + nz = 1.0; + } + } + + consumer.accept(tNear != Double.POSITIVE_INFINITY, ox, oy, oz, dx, dy, dz, tNear, nx, ny, nz, obj1); + } + + public static void intersect( + double minX, + double minY, + double minZ, + double maxX, + double maxY, + double maxZ, + double ox, + double oy, + double oz, + double dx, + double dy, + double dz, + @Nonnull RaycastAABB.RaycastConsumerPlus2 consumer, + T obj1, + K obj2 + ) { + double tNear = Double.POSITIVE_INFINITY; + double nx = 0.0; + double ny = 0.0; + double nz = 0.0; + double t = (minX - ox) / dx; + if (t < tNear && t > -1.0E-8) { + double u = oz + dz * t; + double v = oy + dy * t; + if (u >= minZ && u <= maxZ && v >= minY && v <= maxY) { + tNear = t; + nx = -1.0; + } + } + + t = (maxX - ox) / dx; + if (t < tNear && t > -1.0E-8) { + double u = oz + dz * t; + double v = oy + dy * t; + if (u >= minZ && u <= maxZ && v >= minY && v <= maxY) { + tNear = t; + nx = 1.0; + } + } + + t = (minY - oy) / dy; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oz + dz * t; + if (u >= minX && u <= maxX && v >= minZ && v <= maxZ) { + tNear = t; + ny = -1.0; + } + } + + t = (maxY - oy) / dy; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oz + dz * t; + if (u >= minX && u <= maxX && v >= minZ && v <= maxZ) { + tNear = t; + ny = 1.0; + } + } + + t = (minZ - oz) / dz; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oy + dy * t; + if (u >= minX && u <= maxX && v >= minY && v <= maxY) { + tNear = t; + nz = -1.0; + } + } + + t = (maxZ - oz) / dz; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oy + dy * t; + if (u >= minX && u <= maxX && v >= minY && v <= maxY) { + tNear = t; + nz = 1.0; + } + } + + consumer.accept(tNear != Double.POSITIVE_INFINITY, ox, oy, oz, dx, dy, dz, tNear, nx, ny, nz, obj1, obj2); + } + + public static void intersect( + double minX, + double minY, + double minZ, + double maxX, + double maxY, + double maxZ, + double ox, + double oy, + double oz, + double dx, + double dy, + double dz, + @Nonnull RaycastAABB.RaycastConsumerPlus3 consumer, + T obj1, + K obj2, + L obj3 + ) { + double tNear = Double.POSITIVE_INFINITY; + double nx = 0.0; + double ny = 0.0; + double nz = 0.0; + double t = (minX - ox) / dx; + if (t < tNear && t > -1.0E-8) { + double u = oz + dz * t; + double v = oy + dy * t; + if (u >= minZ && u <= maxZ && v >= minY && v <= maxY) { + tNear = t; + nx = -1.0; + } + } + + t = (maxX - ox) / dx; + if (t < tNear && t > -1.0E-8) { + double u = oz + dz * t; + double v = oy + dy * t; + if (u >= minZ && u <= maxZ && v >= minY && v <= maxY) { + tNear = t; + nx = 1.0; + } + } + + t = (minY - oy) / dy; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oz + dz * t; + if (u >= minX && u <= maxX && v >= minZ && v <= maxZ) { + tNear = t; + ny = -1.0; + } + } + + t = (maxY - oy) / dy; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oz + dz * t; + if (u >= minX && u <= maxX && v >= minZ && v <= maxZ) { + tNear = t; + ny = 1.0; + } + } + + t = (minZ - oz) / dz; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oy + dy * t; + if (u >= minX && u <= maxX && v >= minY && v <= maxY) { + tNear = t; + nz = -1.0; + } + } + + t = (maxZ - oz) / dz; + if (t < tNear && t > -1.0E-8) { + double u = ox + dx * t; + double v = oy + dy * t; + if (u >= minX && u <= maxX && v >= minY && v <= maxY) { + tNear = t; + nz = 1.0; + } + } + + consumer.accept(tNear != Double.POSITIVE_INFINITY, ox, oy, oz, dx, dy, dz, tNear, nx, ny, nz, obj1, obj2, obj3); + } + + @FunctionalInterface + public interface RaycastConsumer { + void accept( + boolean var1, double var2, double var4, double var6, double var8, double var10, double var12, double var14, double var16, double var18, double var20 + ); + } + + @FunctionalInterface + public interface RaycastConsumerPlus1 { + void accept( + boolean var1, + double var2, + double var4, + double var6, + double var8, + double var10, + double var12, + double var14, + double var16, + double var18, + double var20, + T var22 + ); + } + + @FunctionalInterface + public interface RaycastConsumerPlus2 { + void accept( + boolean var1, + double var2, + double var4, + double var6, + double var8, + double var10, + double var12, + double var14, + double var16, + double var18, + double var20, + T var22, + K var23 + ); + } + + @FunctionalInterface + public interface RaycastConsumerPlus3 { + void accept( + boolean var1, + double var2, + double var4, + double var6, + double var8, + double var10, + double var12, + double var14, + double var16, + double var18, + double var20, + T var22, + K var23, + L var24 + ); + } +} diff --git a/src/com/hypixel/hytale/math/shape/Box.java b/src/com/hypixel/hytale/math/shape/Box.java new file mode 100644 index 0000000..f68909e --- /dev/null +++ b/src/com/hypixel/hytale/math/shape/Box.java @@ -0,0 +1,511 @@ +package com.hypixel.hytale.math.shape; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.function.predicate.TriIntObjPredicate; +import com.hypixel.hytale.function.predicate.TriIntPredicate; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class Box implements Shape { + public static final Codec CODEC = BuilderCodec.builder(Box.class, Box::new) + .append(new KeyedCodec<>("Min", Vector3d.CODEC), (box, v) -> box.min.assign(v), box -> box.min) + .add() + .append(new KeyedCodec<>("Max", Vector3d.CODEC), (box, v) -> box.max.assign(v), box -> box.max) + .add() + .validator((box, results) -> { + if (box.width() <= 0.0) { + results.fail("Width is <= 0! Given: " + box.width()); + } + + if (box.height() <= 0.0) { + results.fail("Height is <= 0! Given: " + box.height()); + } + + if (box.depth() <= 0.0) { + results.fail("Depth is <= 0! Given: " + box.depth()); + } + }) + .build(); + public static final Box UNIT = new Box(Vector3d.ZERO, Vector3d.ALL_ONES); + @Nonnull + public final Vector3d min = new Vector3d(); + @Nonnull + public final Vector3d max = new Vector3d(); + + @Nonnull + public static Box horizontallyCentered(double width, double height, double depth) { + return new Box(-width / 2.0, 0.0, -depth / 2.0, width / 2.0, height, depth / 2.0); + } + + public Box() { + } + + public Box(@Nonnull Box box) { + this(); + this.min.assign(box.min); + this.max.assign(box.max); + } + + public Box(@Nonnull Vector3d min, @Nonnull Vector3d max) { + this(); + this.min.assign(min); + this.max.assign(max); + } + + public Box(double xMin, double yMin, double zMin, double xMax, double yMax, double zMax) { + this(); + this.min.assign(xMin, yMin, zMin); + this.max.assign(xMax, yMax, zMax); + } + + public static Box cube(@Nonnull Vector3d min, double side) { + return new Box(min.x, min.y, min.z, min.x + side, min.y + side, min.z + side); + } + + public static Box centeredCube(@Nonnull Vector3d center, double inradius) { + return new Box(center.x - inradius, center.y - inradius, center.z - inradius, center.x + inradius, center.y + inradius, center.z + inradius); + } + + @Nonnull + public Box setMinMax(@Nonnull Vector3d min, @Nonnull Vector3d max) { + this.min.assign(min); + this.max.assign(max); + return this; + } + + @Nonnull + public Box setMinMax(@Nonnull double[] min, @Nonnull double[] max) { + this.min.assign(min); + this.max.assign(max); + return this; + } + + @Nonnull + public Box setMinMax(@Nonnull float[] min, @Nonnull float[] max) { + this.min.assign(min); + this.max.assign(max); + return this; + } + + @Nonnull + public Box setEmpty() { + this.setMinMax(Double.MAX_VALUE, -Double.MAX_VALUE); + return this; + } + + @Nonnull + public Box setMinMax(double min, double max) { + this.min.assign(min); + this.max.assign(max); + return this; + } + + @Nonnull + public Box union(@Nonnull Box bb) { + if (this.min.x > bb.min.x) { + this.min.x = bb.min.x; + } + + if (this.min.y > bb.min.y) { + this.min.y = bb.min.y; + } + + if (this.min.z > bb.min.z) { + this.min.z = bb.min.z; + } + + if (this.max.x < bb.max.x) { + this.max.x = bb.max.x; + } + + if (this.max.y < bb.max.y) { + this.max.y = bb.max.y; + } + + if (this.max.z < bb.max.z) { + this.max.z = bb.max.z; + } + + return this; + } + + @Nonnull + public Box assign(@Nonnull Box other) { + this.min.assign(other.min); + this.max.assign(other.max); + return this; + } + + @Nonnull + public Box assign(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { + this.min.assign(minX, minY, minZ); + this.max.assign(maxX, maxY, maxZ); + return this; + } + + @Nonnull + public Box minkowskiSum(@Nonnull Box bb) { + this.min.subtract(bb.max); + this.max.subtract(bb.min); + return this; + } + + @Nonnull + public Box scale(float scale) { + this.min.scale(scale); + this.max.scale(scale); + return this; + } + + @Nonnull + public Box normalize() { + if (this.min.x > this.max.x) { + double t = this.min.x; + this.min.x = this.max.x; + this.max.x = t; + } + + if (this.min.y > this.max.y) { + double t = this.min.y; + this.min.y = this.max.y; + this.max.y = t; + } + + if (this.min.z > this.max.z) { + double t = this.min.z; + this.min.z = this.max.z; + this.max.z = t; + } + + return this; + } + + @Nonnull + public Box rotateX(float angleInRadians) { + this.min.rotateX(angleInRadians); + this.max.rotateX(angleInRadians); + return this; + } + + @Nonnull + public Box rotateY(float angleInRadians) { + this.min.rotateY(angleInRadians); + this.max.rotateY(angleInRadians); + return this; + } + + @Nonnull + public Box rotateZ(float angleInRadians) { + this.min.rotateZ(angleInRadians); + this.max.rotateZ(angleInRadians); + return this; + } + + @Nonnull + public Box offset(double x, double y, double z) { + this.min.add(x, y, z); + this.max.add(x, y, z); + return this; + } + + @Nonnull + public Box offset(@Nonnull Vector3d pos) { + this.min.add(pos); + this.max.add(pos); + return this; + } + + @Nonnull + public Box sweep(@Nonnull Vector3d v) { + if (v.x < 0.0) { + this.min.x = this.min.x + v.x; + } else if (v.x > 0.0) { + this.max.x = this.max.x + v.x; + } + + if (v.y < 0.0) { + this.min.y = this.min.y + v.y; + } else if (v.y > 0.0) { + this.max.y = this.max.y + v.y; + } + + if (v.z < 0.0) { + this.min.z = this.min.z + v.z; + } else if (v.z > 0.0) { + this.max.z = this.max.z + v.z; + } + + return this; + } + + @Nonnull + public Box extend(double extentX, double extentY, double extentZ) { + this.min.subtract(extentX, extentY, extentZ); + this.max.add(extentX, extentY, extentZ); + return this; + } + + public double width() { + return this.max.x - this.min.x; + } + + public double height() { + return this.max.y - this.min.y; + } + + public double depth() { + return this.max.z - this.min.z; + } + + public double dimension(@Nonnull Axis axis) { + return switch (axis) { + case X -> this.width(); + case Y -> this.height(); + case Z -> this.depth(); + }; + } + + public double getThickness() { + return MathUtil.minValue(this.width(), this.height(), this.depth()); + } + + public double getMaximumThickness() { + return MathUtil.maxValue(this.width(), this.height(), this.depth()); + } + + public double getVolume() { + double w = this.width(); + if (w <= 0.0) { + return 0.0; + } else { + double h = this.height(); + if (h <= 0.0) { + return 0.0; + } else { + double d = this.depth(); + return d <= 0.0 ? 0.0 : w * h * d; + } + } + } + + public boolean hasVolume() { + return this.min.x <= this.max.x && this.min.y <= this.max.y && this.min.z <= this.max.z; + } + + public boolean isIntersecting(@Nonnull Box other) { + return !(this.min.x > other.max.x) + && !(other.min.x > this.max.x) + && !(this.min.y > other.max.y) + && !(other.min.y > this.max.y) + && !(this.min.z > other.max.z) + && !(other.min.z > this.max.z); + } + + public boolean isUnitBox() { + return this.min.equals(Vector3d.ZERO) && this.max.equals(Vector3d.ALL_ONES); + } + + public double middleX() { + return (this.min.x + this.max.x) / 2.0; + } + + public double middleY() { + return (this.min.y + this.max.y) / 2.0; + } + + public double middleZ() { + return (this.min.z + this.max.z) / 2.0; + } + + @Nonnull + public Box clone() { + Box box = new Box(); + box.assign(this); + return box; + } + + @Nonnull + public Vector3d getMin() { + return this.min; + } + + @Nonnull + public Vector3d getMax() { + return this.max; + } + + @Nonnull + @Override + public Box getBox(double x, double y, double z) { + return new Box(this.min.getX() + x, this.min.getY() + y, this.min.getZ() + z, this.max.getX() + x, this.max.getY() + y, this.max.getZ() + z); + } + + @Override + public boolean containsPosition(double x, double y, double z) { + return x >= this.min.getX() && x <= this.max.getX() && y >= this.min.getY() && y <= this.max.getY() && z >= this.min.getZ() && z <= this.max.getZ(); + } + + @Override + public void expand(double radius) { + this.extend(radius, radius, radius); + } + + public boolean containsBlock(int x, int y, int z) { + int minX = MathUtil.floor(this.min.getX()); + int minY = MathUtil.floor(this.min.getY()); + int minZ = MathUtil.floor(this.min.getZ()); + int maxX = MathUtil.ceil(this.max.getX()); + int maxY = MathUtil.ceil(this.max.getY()); + int maxZ = MathUtil.ceil(this.max.getZ()); + return x >= minX && x < maxX && y >= minY && y < maxY && z >= minZ && z < maxZ; + } + + public boolean containsBlock(@Nonnull Vector3i origin, int x, int y, int z) { + return this.containsBlock(x - origin.getX(), y - origin.getY(), z - origin.getZ()); + } + + @Override + public boolean forEachBlock(double x, double y, double z, double epsilon, @Nonnull TriIntPredicate consumer) { + int minX = MathUtil.floor(x + this.min.getX() - epsilon); + int minY = MathUtil.floor(y + this.min.getY() - epsilon); + int minZ = MathUtil.floor(z + this.min.getZ() - epsilon); + int maxX = MathUtil.floor(x + this.max.getX() + epsilon); + int maxY = MathUtil.floor(y + this.max.getY() + epsilon); + int maxZ = MathUtil.floor(z + this.max.getZ() + epsilon); + + for (int _x = minX; _x <= maxX; _x++) { + for (int _y = minY; _y <= maxY; _y++) { + for (int _z = minZ; _z <= maxZ; _z++) { + if (!consumer.test(_x, _y, _z)) { + return false; + } + } + } + } + + return true; + } + + @Override + public boolean forEachBlock(double x, double y, double z, double epsilon, T t, @Nonnull TriIntObjPredicate consumer) { + int minX = MathUtil.floor(x + this.min.getX() - epsilon); + int minY = MathUtil.floor(y + this.min.getY() - epsilon); + int minZ = MathUtil.floor(z + this.min.getZ() - epsilon); + int maxX = MathUtil.floor(x + this.max.getX() + epsilon); + int maxY = MathUtil.floor(y + this.max.getY() + epsilon); + int maxZ = MathUtil.floor(z + this.max.getZ() + epsilon); + + for (int _x = minX; _x <= maxX; _x++) { + for (int _y = minY; _y <= maxY; _y++) { + for (int _z = minZ; _z <= maxZ; _z++) { + if (!consumer.test(_x, _y, _z, t)) { + return false; + } + } + } + } + + return true; + } + + public double getMaximumExtent() { + double maximumExtent = 0.0; + if (-this.min.x > maximumExtent) { + maximumExtent = -this.min.x; + } + + if (-this.min.y > maximumExtent) { + maximumExtent = -this.min.y; + } + + if (-this.min.z > maximumExtent) { + maximumExtent = -this.min.z; + } + + if (this.max.x - 1.0 > maximumExtent) { + maximumExtent = this.max.x - 1.0; + } + + if (this.max.y - 1.0 > maximumExtent) { + maximumExtent = this.max.y - 1.0; + } + + if (this.max.z - 1.0 > maximumExtent) { + maximumExtent = this.max.z - 1.0; + } + + return maximumExtent; + } + + public boolean intersectsLine(@Nonnull Vector3d start, @Nonnull Vector3d end) { + Vector3d direction = end.clone().subtract(start); + double tmin = 0.0; + double tmax = 1.0; + if (Math.abs(direction.x) < 1.0E-10) { + if (start.x < this.min.x || start.x > this.max.x) { + return false; + } + } else { + double t1 = (this.min.x - start.x) / direction.x; + double t2 = (this.max.x - start.x) / direction.x; + if (t1 > t2) { + double temp = t1; + t1 = t2; + t2 = temp; + } + + tmin = Math.max(tmin, t1); + tmax = Math.min(tmax, t2); + if (tmin > tmax) { + return false; + } + } + + if (Math.abs(direction.y) < 1.0E-10) { + if (start.y < this.min.y || start.y > this.max.y) { + return false; + } + } else { + double t1x = (this.min.y - start.y) / direction.y; + double t2x = (this.max.y - start.y) / direction.y; + if (t1x > t2x) { + double temp = t1x; + t1x = t2x; + t2x = temp; + } + + tmin = Math.max(tmin, t1x); + tmax = Math.min(tmax, t2x); + if (tmin > tmax) { + return false; + } + } + + if (!(Math.abs(direction.z) < 1.0E-10)) { + double t1xx = (this.min.z - start.z) / direction.z; + double t2xx = (this.max.z - start.z) / direction.z; + if (t1xx > t2xx) { + double temp = t1xx; + t1xx = t2xx; + t2xx = temp; + } + + tmin = Math.max(tmin, t1xx); + tmax = Math.min(tmax, t2xx); + return !(tmin > tmax); + } else { + return !(start.z < this.min.z) && !(start.z > this.max.z); + } + } + + @Nonnull + @Override + public String toString() { + return "Box{min=" + this.min + ", max=" + this.max + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/shape/Box2D.java b/src/com/hypixel/hytale/math/shape/Box2D.java new file mode 100644 index 0000000..a4faa01 --- /dev/null +++ b/src/com/hypixel/hytale/math/shape/Box2D.java @@ -0,0 +1,202 @@ +package com.hypixel.hytale.math.shape; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector2d; +import javax.annotation.Nonnull; + +public class Box2D implements Shape2D { + public static final BuilderCodec CODEC = BuilderCodec.builder(Box2D.class, Box2D::new) + .append(new KeyedCodec<>("Min", Vector2d.CODEC), (shape, min) -> shape.min.assign(min), shape -> shape.min) + .add() + .append(new KeyedCodec<>("Max", Vector2d.CODEC), (shape, max) -> shape.max.assign(max), shape -> shape.max) + .add() + .build(); + @Nonnull + public final Vector2d min = new Vector2d(); + @Nonnull + public final Vector2d max = new Vector2d(); + + public Box2D() { + } + + public Box2D(@Nonnull Box2D box) { + this(); + this.min.assign(box.min); + this.max.assign(box.max); + } + + public Box2D(@Nonnull Vector2d min, @Nonnull Vector2d max) { + this(); + this.min.assign(min); + this.max.assign(max); + } + + public Box2D(double xMin, double yMin, double xMax, double yMax) { + this(); + this.min.assign(xMin, yMin); + this.max.assign(xMax, yMax); + } + + @Nonnull + public Box2D setMinMax(@Nonnull Vector2d min, @Nonnull Vector2d max) { + this.min.assign(min); + this.max.assign(max); + return this; + } + + @Nonnull + public Box2D setMinMax(@Nonnull double[] min, @Nonnull double[] max) { + this.min.assign(min); + this.max.assign(max); + return this; + } + + @Nonnull + public Box2D setMinMax(@Nonnull float[] min, @Nonnull float[] max) { + this.min.assign(min); + this.max.assign(max); + return this; + } + + @Nonnull + public Box2D setEmpty() { + this.setMinMax(Double.MAX_VALUE, -Double.MAX_VALUE); + return this; + } + + @Nonnull + public Box2D setMinMax(double min, double max) { + this.min.assign(min); + this.max.assign(max); + return this; + } + + @Nonnull + public Box2D union(@Nonnull Box2D bb) { + if (this.min.x > bb.min.x) { + this.min.x = bb.min.x; + } + + if (this.min.y > bb.min.y) { + this.min.y = bb.min.y; + } + + if (this.max.x < bb.max.x) { + this.max.x = bb.max.x; + } + + if (this.max.y < bb.max.y) { + this.max.y = bb.max.y; + } + + return this; + } + + @Nonnull + public Box2D assign(@Nonnull Box2D other) { + this.min.assign(other.min); + this.max.assign(other.max); + return this; + } + + @Nonnull + public Box2D minkowskiSum(@Nonnull Box2D bb) { + this.min.subtract(bb.max); + this.max.subtract(bb.min); + return this; + } + + @Nonnull + public Box2D normalize() { + if (this.min.x > this.max.x) { + double t = this.min.x; + this.min.x = this.max.x; + this.max.x = t; + } + + if (this.min.y > this.max.y) { + double t = this.min.y; + this.min.y = this.max.y; + this.max.y = t; + } + + return this; + } + + @Nonnull + public Box2D offset(@Nonnull Vector2d pos) { + this.min.add(pos); + this.max.add(pos); + return this; + } + + @Nonnull + public Box2D sweep(@Nonnull Vector2d v) { + if (v.x < 0.0) { + this.min.x = this.min.x + v.x; + } else if (v.x > 0.0) { + this.max.x = this.max.x + v.x; + } + + if (v.y < 0.0) { + this.min.y = this.min.y + v.y; + } else if (v.y > 0.0) { + this.max.y = this.max.y + v.y; + } + + return this; + } + + @Nonnull + public Box2D extendToInt() { + this.min.floor(); + this.max.ceil(); + return this; + } + + @Nonnull + public Box2D extend(double extentX, double extentY) { + this.min.subtract(extentX, extentY); + this.max.add(extentX, extentY); + return this; + } + + public double width() { + return this.max.x - this.min.x; + } + + public double height() { + return this.max.y - this.min.y; + } + + public boolean isIntersecting(@Nonnull Box2D other) { + return !(this.min.x > other.max.x) && !(other.min.x > this.max.x) && !(this.min.y > other.max.y) && !(other.min.y > this.max.y); + } + + @Nonnull + @Override + public Box2D getBox(double x, double y) { + return new Box2D(this.min.getX() + x, this.min.getY() + y, this.max.getX() + x, this.max.getY() + y); + } + + @Override + public boolean containsPosition(@Nonnull Vector2d origin, @Nonnull Vector2d position) { + double x = position.getX() - origin.getX(); + double y = position.getY() - origin.getY(); + return x >= this.min.getX() && x <= this.max.getX() && y >= this.min.getY() && y <= this.max.getY(); + } + + @Override + public boolean containsPosition(@Nonnull Vector2d origin, double xx, double yy) { + double x = xx - origin.getX(); + double y = yy - origin.getY(); + return x >= this.min.getX() && x <= this.max.getX() && y >= this.min.getY() && y <= this.max.getY(); + } + + @Nonnull + @Override + public String toString() { + return "Box2D{min=" + this.min + ", max=" + this.max + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/shape/Cylinder.java b/src/com/hypixel/hytale/math/shape/Cylinder.java new file mode 100644 index 0000000..d77ea5a --- /dev/null +++ b/src/com/hypixel/hytale/math/shape/Cylinder.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.math.shape; + +import com.hypixel.hytale.function.predicate.TriIntObjPredicate; +import com.hypixel.hytale.function.predicate.TriIntPredicate; +import com.hypixel.hytale.math.block.BlockCylinderUtil; +import com.hypixel.hytale.math.util.MathUtil; +import javax.annotation.Nonnull; + +public class Cylinder implements Shape { + public double height; + public double radiusX; + public double radiusZ; + + public Cylinder() { + } + + public Cylinder(double height, double radiusX, double radiusZ) { + this.height = height; + this.radiusX = radiusX; + this.radiusZ = radiusZ; + } + + public double getRadiusX() { + return this.radiusX; + } + + public double getRadiusZ() { + return this.radiusZ; + } + + public double getHeight() { + return this.height; + } + + @Nonnull + public Cylinder assign(double radius) { + this.radiusX = radius; + this.radiusZ = radius; + return this; + } + + @Override + public boolean containsPosition(double x, double y, double z) { + if (!(y > this.height) && !(y < 0.0)) { + double result = x * x / (this.radiusX * this.radiusX) + z * z / (this.radiusZ * this.radiusZ); + return result <= 1.0; + } else { + return false; + } + } + + @Override + public boolean forEachBlock(double x, double y, double z, double epsilon, @Nonnull TriIntPredicate consumer) { + return BlockCylinderUtil.forEachBlock( + MathUtil.floor(x), + MathUtil.floor(y), + MathUtil.floor(z), + MathUtil.floor(this.radiusX + epsilon), + MathUtil.floor(this.height + epsilon), + MathUtil.floor(this.radiusZ + epsilon), + null, + (_x, _y, _z, aVoid) -> consumer.test(_x, _y, _z) + ); + } + + @Override + public boolean forEachBlock(double x, double y, double z, double epsilon, T t, @Nonnull TriIntObjPredicate consumer) { + return BlockCylinderUtil.forEachBlock( + MathUtil.floor(x), + MathUtil.floor(y), + MathUtil.floor(z), + MathUtil.floor(this.radiusX + epsilon), + MathUtil.floor(this.height + epsilon), + MathUtil.floor(this.radiusZ + epsilon), + t, + consumer + ); + } + + @Override + public void expand(double radius) { + this.radiusX += radius; + this.radiusZ += radius; + } + + @Nonnull + @Override + public Box getBox(double x, double y, double z) { + double biggestRadius = Math.max(this.radiusX, this.radiusZ); + Box boundingBox = new Box(); + boundingBox.min.assign(x - biggestRadius, y, z - biggestRadius); + boundingBox.max.assign(x + biggestRadius, y + this.height, z + biggestRadius); + return boundingBox; + } + + @Nonnull + protected Cylinder clone() { + return new Cylinder(this.height, this.radiusX, this.radiusZ); + } + + @Nonnull + @Override + public String toString() { + return "Cylinder{height=" + this.height + ", radiusX=" + this.radiusX + ", radiusZ=" + this.radiusZ + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/shape/Ellipsoid.java b/src/com/hypixel/hytale/math/shape/Ellipsoid.java new file mode 100644 index 0000000..e289d63 --- /dev/null +++ b/src/com/hypixel/hytale/math/shape/Ellipsoid.java @@ -0,0 +1,92 @@ +package com.hypixel.hytale.math.shape; + +import com.hypixel.hytale.function.predicate.TriIntObjPredicate; +import com.hypixel.hytale.function.predicate.TriIntPredicate; +import com.hypixel.hytale.math.block.BlockSphereUtil; +import com.hypixel.hytale.math.util.MathUtil; +import javax.annotation.Nonnull; + +public class Ellipsoid implements Shape { + public double radiusX; + public double radiusY; + public double radiusZ; + + public Ellipsoid() { + } + + public Ellipsoid(double radius) { + this(radius, radius, radius); + } + + public Ellipsoid(double radiusX, double radiusY, double radiusZ) { + this.radiusX = radiusX; + this.radiusY = radiusY; + this.radiusZ = radiusZ; + } + + @Nonnull + public Ellipsoid assign(double radius) { + this.radiusX = radius; + this.radiusY = radius; + this.radiusZ = radius; + return this; + } + + @Nonnull + @Override + public Box getBox(double x, double y, double z) { + Box boundingBox = new Box(); + boundingBox.min.assign(x - this.radiusX, y - this.radiusY, z - this.radiusZ); + boundingBox.max.assign(x + this.radiusX, y + this.radiusY, z + this.radiusZ); + return boundingBox; + } + + @Override + public boolean containsPosition(double x, double y, double z) { + double xRatio = x / this.radiusX; + double yRatio = y / this.radiusY; + double zRatio = z / this.radiusZ; + return xRatio * xRatio + yRatio * yRatio + zRatio * zRatio <= 1.0; + } + + @Override + public void expand(double radius) { + this.radiusX += radius; + this.radiusY += radius; + this.radiusZ += radius; + } + + @Override + public boolean forEachBlock(double x, double y, double z, double epsilon, @Nonnull TriIntPredicate consumer) { + return BlockSphereUtil.forEachBlock( + MathUtil.floor(x), + MathUtil.floor(y), + MathUtil.floor(z), + MathUtil.floor(this.radiusX + epsilon), + MathUtil.floor(this.radiusY + epsilon), + MathUtil.floor(this.radiusZ + epsilon), + null, + (_x, _y, _z, aVoid) -> consumer.test(_x, _y, _z) + ); + } + + @Override + public boolean forEachBlock(double x, double y, double z, double epsilon, T t, @Nonnull TriIntObjPredicate consumer) { + return BlockSphereUtil.forEachBlock( + MathUtil.floor(x), + MathUtil.floor(y), + MathUtil.floor(z), + MathUtil.floor(this.radiusX + epsilon), + MathUtil.floor(this.radiusY + epsilon), + MathUtil.floor(this.radiusZ + epsilon), + t, + consumer + ); + } + + @Nonnull + @Override + public String toString() { + return "Ellipsoid{radiusX=" + this.radiusX + ", radiusY=" + this.radiusY + ", radiusZ=" + this.radiusZ + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/shape/OriginShape.java b/src/com/hypixel/hytale/math/shape/OriginShape.java new file mode 100644 index 0000000..676bd8a --- /dev/null +++ b/src/com/hypixel/hytale/math/shape/OriginShape.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.math.shape; + +import com.hypixel.hytale.function.predicate.TriIntObjPredicate; +import com.hypixel.hytale.function.predicate.TriIntPredicate; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class OriginShape implements Shape { + public final Vector3d origin; + public S shape; + + public OriginShape() { + this.origin = new Vector3d(); + } + + public OriginShape(Vector3d origin, S shape) { + this.origin = origin; + this.shape = shape; + } + + public Vector3d getOrigin() { + return this.origin; + } + + public S getShape() { + return this.shape; + } + + @Override + public Box getBox(double x, double y, double z) { + return this.shape.getBox(x + this.origin.getX(), y + this.origin.getY(), z + this.origin.getZ()); + } + + @Override + public boolean containsPosition(double x, double y, double z) { + return this.shape.containsPosition(x - this.origin.getX(), y - this.origin.getY(), z - this.origin.getZ()); + } + + @Override + public void expand(double radius) { + this.shape.expand(radius); + } + + @Override + public boolean forEachBlock(double x, double y, double z, double epsilon, TriIntPredicate consumer) { + return this.shape.forEachBlock(x + this.origin.getX(), y + this.origin.getY(), z + this.origin.getZ(), epsilon, consumer); + } + + @Override + public boolean forEachBlock(double x, double y, double z, double epsilon, T t, TriIntObjPredicate consumer) { + return this.shape.forEachBlock(x + this.origin.getX(), y + this.origin.getY(), z + this.origin.getZ(), epsilon, t, consumer); + } + + @Nonnull + @Override + public String toString() { + return "OriginShape{origin=" + this.origin + ", shape=" + this.shape + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/shape/Quad2d.java b/src/com/hypixel/hytale/math/shape/Quad2d.java new file mode 100644 index 0000000..f0acaf6 --- /dev/null +++ b/src/com/hypixel/hytale/math/shape/Quad2d.java @@ -0,0 +1,155 @@ +package com.hypixel.hytale.math.shape; + +import com.hypixel.hytale.math.vector.Vector2d; +import java.util.Random; +import javax.annotation.Nonnull; + +public class Quad2d { + private Vector2d a; + private Vector2d b; + private Vector2d c; + private Vector2d d; + + public Quad2d(Vector2d a, Vector2d b, Vector2d c, Vector2d d) { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + } + + public Quad2d() { + this(new Vector2d(), new Vector2d(), new Vector2d(), new Vector2d()); + } + + public Quad2d(@Nonnull Vector2d[] points) { + this(points, 0, 1, 2, 3); + } + + public Quad2d(@Nonnull Vector2d[] points, int a, int b, int c, int d) { + this(points[a], points[b], points[c], points[d]); + } + + public Vector2d getA() { + return this.a; + } + + public Vector2d getB() { + return this.b; + } + + public Vector2d getC() { + return this.c; + } + + public Vector2d getD() { + return this.d; + } + + public double getMinX() { + double min = this.a.x; + if (min > this.b.x) { + min = this.b.x; + } + + if (min > this.c.x) { + min = this.c.x; + } + + if (min > this.d.x) { + min = this.d.x; + } + + return min; + } + + public double getMinY() { + double min = this.a.y; + if (min > this.b.y) { + min = this.b.y; + } + + if (min > this.c.y) { + min = this.c.y; + } + + if (min > this.d.y) { + min = this.d.y; + } + + return min; + } + + public double getMaxX() { + double max = this.a.x; + if (max < this.b.x) { + max = this.b.x; + } + + if (max < this.c.x) { + max = this.c.x; + } + + if (max < this.d.x) { + max = this.d.x; + } + + return max; + } + + public double getMaxY() { + double max = this.a.y; + if (max < this.b.y) { + max = this.b.y; + } + + if (max < this.c.y) { + max = this.c.y; + } + + if (max < this.d.y) { + max = this.d.y; + } + + return max; + } + + @Nonnull + public Vector2d getCenter() { + return this.getCenter(new Vector2d()); + } + + @Nonnull + public Vector2d getCenter(@Nonnull Vector2d target) { + return target.assign((this.a.x + this.c.x) * 0.5, (this.a.y + this.c.y) * 0.5); + } + + @Nonnull + public Vector2d getRandom(@Nonnull Random random) { + return this.getRandom(random, new Vector2d()); + } + + @Nonnull + public Vector2d getRandom(@Nonnull Random random, @Nonnull Vector2d vec) { + double p = random.nextDouble(); + double q = random.nextDouble(); + if (p + q > 1.0) { + p = 1.0 - p; + q = 1.0 - q; + } + + double pq = 1.0 - p - q; + if (random.nextBoolean()) { + vec.assign(-this.a.x * pq + this.b.x * p + this.c.x * q, -this.a.y * pq + this.b.y * p + this.c.y * q); + } else { + vec.assign(-this.a.x * pq + this.c.x * p + this.d.x * q, -this.a.y * pq + this.c.y * p + this.d.y * q); + } + + return vec; + } + + @Nonnull + @Override + public String toString() { + return "Quad2d{a=" + this.a + ", b=" + this.b + ", c=" + this.c + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/shape/Quad4d.java b/src/com/hypixel/hytale/math/shape/Quad4d.java new file mode 100644 index 0000000..7b63be2 --- /dev/null +++ b/src/com/hypixel/hytale/math/shape/Quad4d.java @@ -0,0 +1,176 @@ +package com.hypixel.hytale.math.shape; + +import com.hypixel.hytale.math.matrix.Matrix4d; +import com.hypixel.hytale.math.vector.Vector4d; +import java.util.Random; +import javax.annotation.Nonnull; + +public class Quad4d { + private Vector4d a; + private Vector4d b; + private Vector4d c; + private Vector4d d; + + public Quad4d(Vector4d a, Vector4d b, Vector4d c, Vector4d d) { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + } + + public Quad4d() { + this(new Vector4d(), new Vector4d(), new Vector4d(), new Vector4d()); + } + + public Quad4d(@Nonnull Vector4d[] points) { + this(points, 0, 1, 2, 3); + } + + public Quad4d(@Nonnull Vector4d[] points, int a, int b, int c, int d) { + this(points[a], points[b], points[c], points[d]); + } + + public boolean isFullyInsideFrustum() { + return this.a.isInsideFrustum() && this.b.isInsideFrustum() && this.c.isInsideFrustum() && this.d.isInsideFrustum(); + } + + public Vector4d getA() { + return this.a; + } + + public Vector4d getB() { + return this.b; + } + + public Vector4d getC() { + return this.c; + } + + public Vector4d getD() { + return this.d; + } + + public Vector4d get(int idx) { + return switch (idx) { + case 0 -> this.a; + case 1 -> this.b; + case 2 -> this.c; + case 3 -> this.d; + default -> throw new IllegalArgumentException("Index must be in range of 0 to 3. Given: " + idx); + }; + } + + public double getMin(int component) { + double min = this.a.get(component); + if (min > this.b.get(component)) { + min = this.b.get(component); + } + + if (min > this.c.get(component)) { + min = this.c.get(component); + } + + if (min > this.d.get(component)) { + min = this.d.get(component); + } + + return min; + } + + public double getMax(int component) { + double max = this.a.get(component); + if (max < this.b.get(component)) { + max = this.b.get(component); + } + + if (max < this.c.get(component)) { + max = this.c.get(component); + } + + if (max < this.d.get(component)) { + max = this.d.get(component); + } + + return max; + } + + @Nonnull + public Quad4d multiply(@Nonnull Matrix4d matrix) { + return this.multiply(matrix, this); + } + + @Nonnull + public Quad4d multiply(@Nonnull Matrix4d matrix, @Nonnull Quad4d target) { + matrix.multiply(this.a, target.a); + matrix.multiply(this.b, target.b); + matrix.multiply(this.c, target.c); + matrix.multiply(this.d, target.d); + return target; + } + + @Nonnull + public Quad2d to2d(@Nonnull Quad2d target) { + target.getA().assign(this.a.x, this.a.y); + target.getB().assign(this.b.x, this.b.y); + target.getC().assign(this.c.x, this.c.y); + target.getD().assign(this.d.x, this.d.y); + return target; + } + + @Nonnull + public Vector4d getCenter() { + return this.getCenter(new Vector4d()); + } + + @Nonnull + public Vector4d getCenter(@Nonnull Vector4d target) { + return target.assign( + this.a.x + (this.c.x - this.a.x) * 0.5, + this.b.x + (this.b.x - this.b.x) * 0.5, + this.c.x + (this.c.x - this.c.x) * 0.5, + this.d.x + (this.d.x - this.d.x) * 0.5 + ); + } + + public void perspectiveTransform() { + this.a.perspectiveTransform(); + this.b.perspectiveTransform(); + this.c.perspectiveTransform(); + this.d.perspectiveTransform(); + } + + @Nonnull + public Vector4d getRandom(@Nonnull Random random) { + return this.getRandom(random, new Vector4d()); + } + + @Nonnull + public Vector4d getRandom(@Nonnull Random random, @Nonnull Vector4d target) { + double p = random.nextDouble(); + double q = random.nextDouble() * (1.0 - p); + double pq = 1.0 - p - q; + if (random.nextBoolean()) { + target.assign( + this.a.x * pq + this.b.x * p + this.c.x * q, + this.a.y * pq + this.b.y * p + this.c.y * q, + this.a.z * pq + this.b.z * p + this.c.z * q, + this.a.w * pq + this.b.w * p + this.c.w * q + ); + } else { + target.assign( + this.a.x * pq + this.c.x * p + this.d.x * q, + this.a.y * pq + this.c.y * p + this.d.y * q, + this.a.z * pq + this.c.z * p + this.d.z * q, + this.a.w * pq + this.c.w * p + this.d.w * q + ); + } + + return target; + } + + @Nonnull + @Override + public String toString() { + return "Quad4d{\na=" + this.a + ",\nb=" + this.b + ",\nc=" + this.c + ",\nd=" + this.d + "\n}"; + } +} diff --git a/src/com/hypixel/hytale/math/shape/Rectangle.java b/src/com/hypixel/hytale/math/shape/Rectangle.java new file mode 100644 index 0000000..778cf2c --- /dev/null +++ b/src/com/hypixel/hytale/math/shape/Rectangle.java @@ -0,0 +1,92 @@ +package com.hypixel.hytale.math.shape; + +import com.hypixel.hytale.math.vector.Vector2d; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Rectangle { + private Vector2d min; + private Vector2d max; + + public Rectangle() { + this(new Vector2d(), new Vector2d()); + } + + public Rectangle(double minX, double minY, double maxX, double maxY) { + this(new Vector2d(minX, minY), new Vector2d(maxX, maxY)); + } + + public Rectangle(Vector2d min, Vector2d max) { + this.min = min; + this.max = max; + } + + public Rectangle(@Nonnull Rectangle other) { + this(other.getMinX(), other.getMinY(), other.getMaxX(), other.getMaxY()); + } + + public Vector2d getMin() { + return this.min; + } + + public Vector2d getMax() { + return this.max; + } + + public double getMinX() { + return this.min.x; + } + + public double getMinY() { + return this.min.y; + } + + public double getMaxX() { + return this.max.x; + } + + public double getMaxY() { + return this.max.y; + } + + @Nonnull + public Rectangle assign(double minX, double minY, double maxX, double maxY) { + this.min.x = minX; + this.min.y = minY; + this.max.x = maxX; + this.max.y = maxY; + return this; + } + + public boolean hasArea() { + return this.min.x < this.max.x && this.min.y < this.max.y; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Rectangle that = (Rectangle)o; + if (this.min != null ? this.min.equals(that.min) : that.min == null) { + return this.max != null ? this.max.equals(that.max) : that.max == null; + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.min != null ? this.min.hashCode() : 0; + return 31 * result + (this.max != null ? this.max.hashCode() : 0); + } + + @Nonnull + @Override + public String toString() { + return "Rectangle2d{min=" + this.min + ", max=" + this.max + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/shape/Shape.java b/src/com/hypixel/hytale/math/shape/Shape.java new file mode 100644 index 0000000..e4948d1 --- /dev/null +++ b/src/com/hypixel/hytale/math/shape/Shape.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.math.shape; + +import com.hypixel.hytale.function.predicate.TriIntObjPredicate; +import com.hypixel.hytale.function.predicate.TriIntPredicate; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public interface Shape { + default Box getBox(@Nonnull Vector3d position) { + return this.getBox(position.getX(), position.getY(), position.getZ()); + } + + Box getBox(double var1, double var3, double var5); + + default boolean containsPosition(@Nonnull Vector3d origin, @Nonnull Vector3d position) { + return this.containsPosition(position.getX() - origin.getX(), position.getY() - origin.getY(), position.getZ() - origin.getZ()); + } + + default boolean containsPosition(@Nonnull Vector3d position) { + return this.containsPosition(position.getX(), position.getY(), position.getZ()); + } + + boolean containsPosition(double var1, double var3, double var5); + + void expand(double var1); + + default boolean forEachBlock(@Nonnull Vector3d origin, TriIntPredicate consumer) { + return this.forEachBlock(origin.getX(), origin.getY(), origin.getZ(), consumer); + } + + default boolean forEachBlock(@Nonnull Vector3d origin, double epsilon, TriIntPredicate consumer) { + return this.forEachBlock(origin.getX(), origin.getY(), origin.getZ(), epsilon, consumer); + } + + default boolean forEachBlock(double x, double y, double z, TriIntPredicate consumer) { + return this.forEachBlock(x, y, z, 0.0, consumer); + } + + boolean forEachBlock(double var1, double var3, double var5, double var7, TriIntPredicate var9); + + default boolean forEachBlock(@Nonnull Vector3d origin, T t, TriIntObjPredicate consumer) { + return this.forEachBlock(origin.getX(), origin.getY(), origin.getZ(), t, consumer); + } + + default boolean forEachBlock(@Nonnull Vector3d origin, double epsilon, T t, TriIntObjPredicate consumer) { + return this.forEachBlock(origin.getX(), origin.getY(), origin.getZ(), epsilon, t, consumer); + } + + default boolean forEachBlock(double x, double y, double z, T t, TriIntObjPredicate consumer) { + return this.forEachBlock(x, y, z, 0.0, t, consumer); + } + + boolean forEachBlock(double var1, double var3, double var5, double var7, T var9, TriIntObjPredicate var10); +} diff --git a/src/com/hypixel/hytale/math/shape/Shape2D.java b/src/com/hypixel/hytale/math/shape/Shape2D.java new file mode 100644 index 0000000..279a7f5 --- /dev/null +++ b/src/com/hypixel/hytale/math/shape/Shape2D.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.math.shape; + +import com.hypixel.hytale.math.vector.Vector2d; +import javax.annotation.Nonnull; + +public interface Shape2D { + default Box2D getBox(@Nonnull Vector2d position) { + return this.getBox(position.getX(), position.getY()); + } + + Box2D getBox(double var1, double var3); + + boolean containsPosition(Vector2d var1, Vector2d var2); + + boolean containsPosition(Vector2d var1, double var2, double var4); +} diff --git a/src/com/hypixel/hytale/math/shape/Triangle2d.java b/src/com/hypixel/hytale/math/shape/Triangle2d.java new file mode 100644 index 0000000..02d6501 --- /dev/null +++ b/src/com/hypixel/hytale/math/shape/Triangle2d.java @@ -0,0 +1,123 @@ +package com.hypixel.hytale.math.shape; + +import com.hypixel.hytale.math.vector.Vector2d; +import java.util.Random; +import javax.annotation.Nonnull; + +public class Triangle2d { + private Vector2d a; + private Vector2d b; + private Vector2d c; + + public Triangle2d(Vector2d a, Vector2d b, Vector2d c) { + this.a = a; + this.b = b; + this.c = c; + } + + public Triangle2d() { + this(new Vector2d(), new Vector2d(), new Vector2d()); + } + + public Triangle2d(@Nonnull Vector2d[] points) { + this(points, 0, 1, 2); + } + + public Triangle2d(@Nonnull Vector2d[] points, int a, int b, int c) { + this(points[a], points[b], points[c]); + } + + public Vector2d getA() { + return this.a; + } + + public void setA(Vector2d a) { + this.a = a; + } + + public Vector2d getB() { + return this.b; + } + + public void setB(Vector2d b) { + this.b = b; + } + + public Vector2d getC() { + return this.c; + } + + public void setC(Vector2d c) { + this.c = c; + } + + public double getMinX() { + double min = this.a.x; + if (min > this.b.x) { + min = this.b.x; + } + + if (min > this.c.x) { + min = this.c.x; + } + + return min; + } + + public double getMinY() { + double min = this.a.y; + if (min > this.b.y) { + min = this.b.y; + } + + if (min > this.c.y) { + min = this.c.y; + } + + return min; + } + + public double getMaxX() { + double max = this.a.x; + if (max < this.b.x) { + max = this.b.x; + } + + if (max < this.c.x) { + max = this.c.x; + } + + return max; + } + + public double getMaxY() { + double max = this.a.y; + if (max < this.b.y) { + max = this.b.y; + } + + if (max < this.c.y) { + max = this.c.y; + } + + return max; + } + + @Nonnull + public Vector2d getRandom(@Nonnull Random random) { + return this.getRandom(random, new Vector2d()); + } + + @Nonnull + public Vector2d getRandom(@Nonnull Random random, @Nonnull Vector2d vec) { + double p = random.nextDouble(); + double q = random.nextDouble(); + if (p + q > 1.0) { + p = 1.0 - p; + q = 1.0 - q; + } + + vec.assign(-this.a.x * (1.0 - p - q) + this.b.x * p + this.c.x * q, -this.a.y * (1.0 - p - q) + this.b.y * p + this.c.y * q); + return vec; + } +} diff --git a/src/com/hypixel/hytale/math/shape/Triangle4d.java b/src/com/hypixel/hytale/math/shape/Triangle4d.java new file mode 100644 index 0000000..10a13c7 --- /dev/null +++ b/src/com/hypixel/hytale/math/shape/Triangle4d.java @@ -0,0 +1,130 @@ +package com.hypixel.hytale.math.shape; + +import com.hypixel.hytale.math.matrix.Matrix4d; +import com.hypixel.hytale.math.vector.Vector4d; +import java.util.Random; +import javax.annotation.Nonnull; + +public class Triangle4d { + private Vector4d a; + private Vector4d b; + private Vector4d c; + + public Triangle4d(Vector4d a, Vector4d b, Vector4d c) { + this.a = a; + this.b = b; + this.c = c; + } + + public Triangle4d() { + this(new Vector4d(), new Vector4d(), new Vector4d()); + } + + public Triangle4d(@Nonnull Vector4d[] points) { + this(points, 0, 1, 2); + } + + public Triangle4d(@Nonnull Vector4d[] points, int a, int b, int c) { + this(points[a], points[b], points[c]); + } + + public Vector4d getA() { + return this.a; + } + + public Vector4d getB() { + return this.b; + } + + public Vector4d getC() { + return this.c; + } + + public double getMin(int component) { + double min = this.a.get(component); + if (min > this.b.get(component)) { + min = this.b.get(component); + } + + if (min > this.c.get(component)) { + min = this.c.get(component); + } + + return min; + } + + public double getMax(int component) { + double max = this.a.get(component); + if (max < this.b.get(component)) { + max = this.b.get(component); + } + + if (max < this.c.get(component)) { + max = this.c.get(component); + } + + return max; + } + + @Nonnull + public Triangle4d assign(@Nonnull Vector4d v1, @Nonnull Vector4d v2, @Nonnull Vector4d v3) { + this.a.assign(v1); + this.b.assign(v2); + this.c.assign(v3); + return this; + } + + @Nonnull + public Vector4d getRandom(@Nonnull Random random) { + return this.getRandom(random, new Vector4d()); + } + + @Nonnull + public Vector4d getRandom(@Nonnull Random random, @Nonnull Vector4d vec) { + double p = random.nextDouble(); + double q = random.nextDouble() * (1.0 - p); + double pq = 1.0 - p - q; + vec.assign( + this.a.x * pq + this.b.x * p + this.c.x * q, + this.a.y * pq + this.b.y * p + this.c.y * q, + this.a.z * pq + this.b.z * p + this.c.z * q, + this.a.w * pq + this.b.w * p + this.c.w * q + ); + return vec; + } + + @Nonnull + public Triangle4d multiply(@Nonnull Matrix4d matrix) { + return this.multiply(matrix, this); + } + + @Nonnull + public Triangle4d multiply(@Nonnull Matrix4d matrix, @Nonnull Triangle4d target) { + matrix.multiply(this.a, target.a); + matrix.multiply(this.b, target.b); + matrix.multiply(this.c, target.c); + return target; + } + + @Nonnull + public Triangle2d to2d(@Nonnull Triangle2d target) { + target.getA().assign(this.a.x, this.a.y); + target.getB().assign(this.b.x, this.b.y); + target.getC().assign(this.c.x, this.c.y); + return target; + } + + @Nonnull + public Triangle4d perspectiveTransform() { + this.a.perspectiveTransform(); + this.b.perspectiveTransform(); + this.c.perspectiveTransform(); + return this; + } + + @Nonnull + @Override + public String toString() { + return "Triangle4d{a=" + this.a + ", b=" + this.b + ", c=" + this.c + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/shape/ViewUtil.java b/src/com/hypixel/hytale/math/shape/ViewUtil.java new file mode 100644 index 0000000..ddb4cc3 --- /dev/null +++ b/src/com/hypixel/hytale/math/shape/ViewUtil.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.math.shape; + +import com.hypixel.hytale.math.vector.Vector2d; +import java.awt.Graphics2D; + +public class ViewUtil { + public static final int INSIDE = 0; + public static final int LEFT = 1; + public static final int RIGHT = 2; + public static final int BOTTOM = 4; + public static final int TOP = 8; + + private ViewUtil() { + throw new UnsupportedOperationException(); + } + + private static int computeOutCode(double x, double y) { + int code = 0; + if (x < -1.0) { + code |= 1; + } else if (x > 1.0) { + code |= 2; + } + + if (y < -1.0) { + code |= 4; + } else if (y > 1.0) { + code |= 8; + } + + return code; + } + + private static void CohenSutherlandLineClipAndDraw(double x0, double y0, double x1, double y1, Graphics2D graphics2D, double width, double height) { + int outcode0 = computeOutCode(x0, y0); + int outcode1 = computeOutCode(x1, y1); + boolean accept = false; + + while (true) { + if ((outcode0 | outcode1) == 0) { + accept = true; + break; + } + + if ((outcode0 & outcode1) != 0) { + break; + } + + int outcodeOut = outcode0 != 0 ? outcode0 : outcode1; + double x; + double y; + if ((outcodeOut & 8) != 0) { + x = x0 + (x1 - x0) * (1.0 - y0) / (y1 - y0); + y = 1.0; + } else if ((outcodeOut & 4) != 0) { + x = x0 + (x1 - x0) * (-1.0 - y0) / (y1 - y0); + y = -1.0; + } else if ((outcodeOut & 2) != 0) { + y = y0 + (y1 - y0) * (1.0 - x0) / (x1 - x0); + x = 1.0; + } else if ((outcodeOut & 1) != 0) { + y = y0 + (y1 - y0) * (-1.0 - x0) / (x1 - x0); + x = -1.0; + } else { + x = 0.0; + y = 0.0; + } + + if (outcodeOut == outcode0) { + x0 = x; + y0 = y; + outcode0 = computeOutCode(x, y); + } else { + x1 = x; + y1 = y; + outcode1 = computeOutCode(x, y); + } + } + + if (accept) { + new Vector2d(x0, y0); + new Vector2d(x1, y1); + } + } +} diff --git a/src/com/hypixel/hytale/math/util/ChunkUtil.java b/src/com/hypixel/hytale/math/util/ChunkUtil.java new file mode 100644 index 0000000..457f6fa --- /dev/null +++ b/src/com/hypixel/hytale/math/util/ChunkUtil.java @@ -0,0 +1,176 @@ +package com.hypixel.hytale.math.util; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import javax.annotation.Nonnull; + +public class ChunkUtil { + public static final int BITS = 5; + public static final int SIZE = 32; + public static final int SIZE_2 = 1024; + public static final int SIZE_MINUS_1 = 31; + public static final int SIZE_MASK = 31; + public static final int SIZE_COLUMNS = 1024; + public static final int SIZE_COLUMNS_MASK = 1023; + public static final int SIZE_BLOCKS = 32768; + public static final int BITS2 = 10; + public static final int NON_CHUNK_MASK = -32; + public static final int HEIGHT_SECTIONS = 10; + public static final int HEIGHT = 320; + public static final int HEIGHT_MINUS_1 = 319; + public static final int HEIGHT_MASK = (Integer.highestOneBit(320) << 1) - 1; + public static final int SIZE_BLOCKS_COLUMN = 327680; + public static final long NOT_FOUND = indexChunk(Integer.MIN_VALUE, Integer.MIN_VALUE); + public static final int MIN_Y = 0; + public static final int MIN_ENTITY_Y = -32; + public static final int MIN_SECTION = 0; + + private ChunkUtil() { + } + + public static byte[] shortToByteArray(@Nonnull short[] data) { + ByteBuffer byteBuffer = ByteBuffer.allocate(data.length * 2).order(ByteOrder.LITTLE_ENDIAN); + byteBuffer.asShortBuffer().put(data); + return byteBuffer.array(); + } + + public static byte[] intToByteArray(@Nonnull int[] data) { + ByteBuffer byteBuffer = ByteBuffer.allocate(data.length * 4).order(ByteOrder.LITTLE_ENDIAN); + byteBuffer.asIntBuffer().put(data); + return byteBuffer.array(); + } + + public static int indexColumn(int x, int z) { + return (z & 31) << 5 | x & 31; + } + + public static int xFromColumn(int index) { + return index & 31; + } + + public static int zFromColumn(int index) { + return index >> 5 & 31; + } + + public static int indexSection(int y) { + return y >> 5; + } + + public static int indexBlockFromColumn(int column, int y) { + return (y & 31) << 10 | column & 1023; + } + + public static int indexBlock(int x, int y, int z) { + return (y & 31) << 10 | (z & 31) << 5 | x & 31; + } + + public static int xFromIndex(int index) { + return index & 31; + } + + public static int yFromIndex(int index) { + return index >> 10 & 31; + } + + public static int zFromIndex(int index) { + return index >> 5 & 31; + } + + public static int indexBlockInColumn(int x, int y, int z) { + return (y & HEIGHT_MASK) << 10 | (z & 31) << 5 | x & 31; + } + + public static int indexBlockInColumnFromColumn(int column, int y) { + return (y & HEIGHT_MASK) << 10 | column & 1023; + } + + public static int xFromBlockInColumn(int index) { + return index & 31; + } + + public static int yFromBlockInColumn(int index) { + return index >> 10 & HEIGHT_MASK; + } + + public static int zFromBlockInColumn(int index) { + return index >> 5 & 31; + } + + public static int localCoordinate(long v) { + return (int)(v & 31L); + } + + public static int chunkCoordinate(double block) { + return MathUtil.floor(block) >> 5; + } + + public static int chunkCoordinate(int block) { + return block >> 5; + } + + public static int chunkCoordinate(long block) { + return (int)(block >> 5); + } + + public static int minBlock(int index) { + return index << 5; + } + + public static int maxBlock(int index) { + return (index << 5) + 31; + } + + public static boolean isWithinLocalChunk(int x, int z) { + return x >= 0 && z >= 0 && x < 32 && z < 32; + } + + public static boolean isBorderBlock(int x, int z) { + return x == 0 || z == 0 || x == 31 || z == 31; + } + + public static boolean isBorderBlockGlobal(int x, int z) { + x &= 31; + z &= 31; + return isBorderBlock(x, z); + } + + public static boolean isInsideChunk(int chunkX, int chunkZ, int x, int z) { + return chunkCoordinate(x) == chunkX && chunkCoordinate(z) == chunkZ; + } + + public static boolean isSameChunk(int x0, int z0, int x1, int z1) { + return chunkCoordinate(x0) == chunkCoordinate(x1) && chunkCoordinate(z0) == chunkCoordinate(z1); + } + + public static boolean isSameChunkSection(int x0, int y0, int z0, int x1, int y1, int z1) { + return chunkCoordinate(x0) == chunkCoordinate(x1) && chunkCoordinate(y0) == chunkCoordinate(y1) && chunkCoordinate(z0) == chunkCoordinate(z1); + } + + public static boolean isInsideChunkRelative(int x, int z) { + return (x & 31) == x && (z & 31) == z; + } + + public static int xOfChunkIndex(long index) { + return (int)(index >> 32); + } + + public static int zOfChunkIndex(long index) { + return (int)index; + } + + public static long indexChunk(int x, int z) { + return (long)x << 32 | z & 4294967295L; + } + + public static long indexChunkFromBlock(int blockX, int blockZ) { + return indexChunk(chunkCoordinate(blockX), chunkCoordinate(blockZ)); + } + + public static long indexChunkFromBlock(double blockX, double blockZ) { + return indexChunkFromBlock(MathUtil.floor(blockX), MathUtil.floor(blockZ)); + } + + public static int worldCoordFromLocalCoord(int chunkCoord, int localCoord) { + return chunkCoord << 5 | localCoord; + } +} diff --git a/src/com/hypixel/hytale/math/util/FastRandom.java b/src/com/hypixel/hytale/math/util/FastRandom.java new file mode 100644 index 0000000..6b4e94a --- /dev/null +++ b/src/com/hypixel/hytale/math/util/FastRandom.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.math.util; + +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +public class FastRandom extends Random { + private static final long multiplier = 25214903917L; + private static final long addend = 11L; + private static final long mask = 281474976710655L; + private long seed; + + public FastRandom() { + this.seed = initialScramble(ThreadLocalRandom.current().nextLong()); + } + + public FastRandom(long seed) { + this.seed = initialScramble(seed); + } + + @Override + public void setSeed(long seed) { + this.seed = initialScramble(seed); + } + + private static long initialScramble(long seed) { + return (seed ^ 25214903917L) & 281474976710655L; + } + + @Override + protected int next(int bits) { + long seed = this.seed; + seed = seed * 25214903917L + 11L & 281474976710655L; + this.seed = seed; + return (int)(seed >>> 48 - bits); + } + + @Override + public synchronized double nextGaussian() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/com/hypixel/hytale/math/util/HashUtil.java b/src/com/hypixel/hytale/math/util/HashUtil.java new file mode 100644 index 0000000..73b7e67 --- /dev/null +++ b/src/com/hypixel/hytale/math/util/HashUtil.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.math.util; + +import java.util.UUID; +import javax.annotation.Nonnull; + +public class HashUtil { + public static long hash(long v) { + v = (v >>> 30 ^ v) * -4658895280553007687L; + v = (v >>> 27 ^ v) * -7723592293110705685L; + return v >>> 31 ^ v; + } + + public static long hash(long l1, long l2) { + l1 = (hash(l1) >>> 30 ^ l1) * -4658895280553007687L; + return hash(l2) >>> 31 ^ l1; + } + + public static long hash(long l1, long l2, long l3) { + l1 = (hash(l1) >>> 30 ^ l1) * -4658895280553007687L; + l1 = (hash(l2) >>> 27 ^ l1) * -7723592293110705685L; + return hash(l3) >>> 31 ^ l1; + } + + public static long hash(long l1, long l2, long l3, long l4) { + l1 = (hash(l1) >>> 30 ^ l1) * -4658895280553007687L; + l1 = (hash(l2) >>> 27 ^ l1) * -7723592293110705685L; + l1 = (hash(l3) >>> 30 ^ l1) * -6389720478792763523L; + return hash(l4) >>> 31 ^ l1; + } + + public static long rehash(long l1) { + return hash(hash(l1)); + } + + public static long rehash(long l1, long l2) { + return hash(hash(l1, l2)); + } + + public static long rehash(long l1, long l2, long l3) { + return hash(hash(l1, l2, l3)); + } + + public static long rehash(long l1, long l2, long l3, long l4) { + return hash(hash(l1, l2, l3, l4)); + } + + public static double random(long l1) { + return hashToRandomDouble(rehash(l1)); + } + + public static double random(long l1, long l2) { + return hashToRandomDouble(rehash(l1, l2)); + } + + public static double random(long l1, long l2, long l3) { + return hashToRandomDouble(rehash(l1, l2, l3)); + } + + public static double random(long l1, long l2, long l3, long l4) { + return hashToRandomDouble(rehash(l1, l2, l3, l4)); + } + + public static int randomInt(long l1, long l2, long l3, int bound) { + long hash = rehash(l1, l2, l3); + hash &= Long.MAX_VALUE; + return (int)(hash % bound); + } + + private static double hashToRandomDouble(long hash) { + hash &= 4294967295L; + return hash / 4.294967295E9; + } + + public static long hashUuid(@Nonnull UUID uuid) { + return hash(uuid.getLeastSignificantBits(), uuid.getMostSignificantBits()); + } + + private HashUtil() { + } +} diff --git a/src/com/hypixel/hytale/math/util/MathUtil.java b/src/com/hypixel/hytale/math/util/MathUtil.java new file mode 100644 index 0000000..313bbb9 --- /dev/null +++ b/src/com/hypixel/hytale/math/util/MathUtil.java @@ -0,0 +1,569 @@ +package com.hypixel.hytale.math.util; + +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; + +public class MathUtil { + public static final double EPSILON_DOUBLE = Math.ulp(1.0); + public static final float EPSILON_FLOAT = Math.ulp(1.0F); + public static float PITCH_EDGE_PADDING = 0.01F; + + public static int abs(int i) { + int mask = i >> 31; + return i + mask ^ mask; + } + + public static int floor(double d) { + int i = (int)d; + if (i <= d) { + return i; + } else { + return d < -2.1474836E9F ? Integer.MIN_VALUE : i - 1; + } + } + + public static int ceil(double d) { + int i = (int)d; + if (!(d > 0.0) || d == i) { + return i; + } else { + return d > 2.147483647E9 ? Integer.MAX_VALUE : i + 1; + } + } + + public static int randomInt(int min, int max) { + return ThreadLocalRandom.current().nextInt(min, max); + } + + public static double randomDouble(double min, double max) { + return min + Math.random() * (max - min); + } + + public static float randomFloat(float min, float max) { + return min + (float)Math.random() * (max - min); + } + + public static double round(double d, int p) { + double pow = Math.pow(10.0, p); + return Math.round(d * pow) / pow; + } + + public static boolean within(double val, double min, double max) { + return val >= min && val <= max; + } + + public static double minValue(double v, double a, double c) { + if (a < v) { + v = a; + } + + if (c < v) { + v = c; + } + + return v; + } + + public static int minValue(int v, int a, int c) { + if (a < v) { + v = a; + } + + if (c < v) { + v = c; + } + + return v; + } + + public static double maxValue(double v, double a, double b, double c) { + if (a > v) { + v = a; + } + + if (b > v) { + v = b; + } + + if (c > v) { + v = c; + } + + return v; + } + + public static double maxValue(double v, double a, double b) { + if (a > v) { + v = a; + } + + if (b > v) { + v = b; + } + + return v; + } + + public static byte maxValue(byte v, byte a, byte b) { + if (a > v) { + v = a; + } + + if (b > v) { + v = b; + } + + return v; + } + + public static byte maxValue(byte v, byte a, byte b, byte c) { + if (a > v) { + v = a; + } + + if (b > v) { + v = b; + } + + if (c > v) { + v = c; + } + + return v; + } + + public static int maxValue(int v, int a, int b) { + if (a > v) { + v = a; + } + + if (b > v) { + v = b; + } + + return v; + } + + public static double lengthSquared(double x, double y) { + return x * x + y * y; + } + + public static double length(double x, double y) { + return Math.sqrt(lengthSquared(x, y)); + } + + public static double lengthSquared(double x, double y, double z) { + return x * x + y * y + z * z; + } + + public static double length(double x, double y, double z) { + return Math.sqrt(lengthSquared(x, y, z)); + } + + public static double maxValue(double v, double a) { + return a > v ? a : v; + } + + public static double clipToZero(double v) { + return clipToZero(v, EPSILON_DOUBLE); + } + + public static double clipToZero(double v, double epsilon) { + return v >= -epsilon && v <= epsilon ? 0.0 : v; + } + + public static float clipToZero(float v) { + return clipToZero(v, EPSILON_FLOAT); + } + + public static float clipToZero(float v, float epsilon) { + return v >= -epsilon && v <= epsilon ? 0.0F : v; + } + + public static boolean closeToZero(double v) { + return closeToZero(v, EPSILON_DOUBLE); + } + + public static boolean closeToZero(double v, double epsilon) { + return v >= -epsilon && v <= epsilon; + } + + public static boolean closeToZero(float v) { + return closeToZero(v, EPSILON_FLOAT); + } + + public static boolean closeToZero(float v, float epsilon) { + return v >= -epsilon && v <= epsilon; + } + + public static double clamp(double v, double min, double max) { + if (v > max) { + return v < min ? min : max; + } else { + return v < min ? min : v; + } + } + + public static float clamp(float v, float min, float max) { + if (v > max) { + return v < min ? min : max; + } else { + return v < min ? min : v; + } + } + + public static int clamp(int v, int min, int max) { + if (v > max) { + return v < min ? min : max; + } else { + return v < min ? min : v; + } + } + + public static long clamp(long v, long min, long max) { + if (v > max) { + return v < min ? min : max; + } else { + return v < min ? min : v; + } + } + + public static int getPercentageOf(int index, int max) { + return (int)(index / (max - 1.0) * 100.0); + } + + public static double percent(int v, int total) { + return total == 0 ? 0.0 : v * 100.0 / total; + } + + public static int fastRound(float f) { + return fastFloor(f + 0.5F); + } + + public static long fastRound(double d) { + return fastFloor(d + 0.5); + } + + public static int fastFloor(float f) { + int i = (int)f; + if (i <= f) { + return i; + } else { + return f < -2.1474836E9F ? Integer.MIN_VALUE : i - 1; + } + } + + public static long fastFloor(double d) { + long i = (long)d; + if (i <= d) { + return i; + } else { + return d < -9.223372E18F ? Long.MIN_VALUE : i - 1L; + } + } + + public static int fastCeil(float f) { + int i = (int)f; + if (!(f > 0.0F) || f == i) { + return i; + } else { + return f > 2.1474836E9F ? Integer.MAX_VALUE : i + 1; + } + } + + public static long fastCeil(double d) { + long i = (long)d; + if (!(d > 0.0) || d == i) { + return i; + } else { + return d > 9.223372E18F ? Long.MAX_VALUE : i + 1L; + } + } + + private MathUtil() { + } + + public static float halfFloatToFloat(int hbits) { + int mant = hbits & 1023; + int exp = hbits & 31744; + if (exp == 31744) { + exp = 261120; + } else if (exp != 0) { + exp += 114688; + if (mant == 0 && exp > 115712) { + return Float.intBitsToFloat((hbits & 32768) << 16 | exp << 13 | 1023); + } + } else if (mant != 0) { + exp = 115712; + + do { + mant <<= 1; + exp -= 1024; + } while ((mant & 1024) == 0); + + mant &= 1023; + } + + return Float.intBitsToFloat((hbits & 32768) << 16 | (exp | mant) << 13); + } + + public static int halfFloatFromFloat(float fval) { + int fbits = Float.floatToIntBits(fval); + int sign = fbits >>> 16 & 32768; + int val = (fbits & 2147483647) + 4096; + if (val >= 1199570944) { + if ((fbits & 2147483647) >= 1199570944) { + return val < 2139095040 ? sign | 31744 : sign | 31744 | (fbits & 8388607) >>> 13; + } else { + return sign | 31743; + } + } else if (val >= 947912704) { + return sign | val - 939524096 >>> 13; + } else if (val < 855638016) { + return sign; + } else { + val = (fbits & 2147483647) >>> 23; + return sign | (fbits & 8388607 | 8388608) + (8388608 >>> val - 102) >>> 126 - val; + } + } + + public static int byteCount(int i) { + if (i > 65535) { + return 4; + } else if (i > 255) { + return 2; + } else { + return i > 0 ? 1 : 0; + } + } + + public static int packInt(int x, int z) { + return x << 16 | z & 65535; + } + + public static int unpackLeft(int packed) { + int i = packed >> 16 & 65535; + if ((i & 32768) != 0) { + i |= -65536; + } + + return i; + } + + public static int unpackRight(int packed) { + int i = packed & 65535; + if ((i & 32768) != 0) { + i |= -65536; + } + + return i; + } + + public static long packLong(int left, int right) { + return (long)left << 32 | right & 4294967295L; + } + + public static int unpackLeft(long packed) { + return (int)(packed >> 32); + } + + public static int unpackRight(long packed) { + return (int)packed; + } + + @Nonnull + public static Vector3i rotateVectorYAxis(@Nonnull Vector3i vector, int angle, boolean clockwise) { + float radAngle = (float) (Math.PI / 180.0) * angle; + int x1; + int z1; + if (clockwise) { + x1 = (int)(vector.x * TrigMathUtil.cos(radAngle) - vector.z * TrigMathUtil.sin(radAngle)); + z1 = (int)(vector.x * TrigMathUtil.sin(radAngle) + vector.z * TrigMathUtil.cos(radAngle)); + } else { + x1 = (int)(vector.x * TrigMathUtil.cos(radAngle) + vector.z * TrigMathUtil.sin(radAngle)); + z1 = (int)(-vector.x * TrigMathUtil.sin(radAngle) + vector.z * TrigMathUtil.cos(radAngle)); + } + + return new Vector3i(x1, vector.y, z1); + } + + @Nonnull + public static Vector3d rotateVectorYAxis(@Nonnull Vector3d vector, int angle, boolean clockwise) { + float radAngle = (float) (Math.PI / 180.0) * angle; + double x1; + double z1; + if (clockwise) { + x1 = vector.x * TrigMathUtil.cos(radAngle) - vector.z * TrigMathUtil.sin(radAngle); + z1 = vector.x * TrigMathUtil.sin(radAngle) + vector.z * TrigMathUtil.cos(radAngle); + } else { + x1 = vector.x * TrigMathUtil.cos(radAngle) + vector.z * TrigMathUtil.sin(radAngle); + z1 = -vector.x * TrigMathUtil.sin(radAngle) + vector.z * TrigMathUtil.cos(radAngle); + } + + return new Vector3d(x1, vector.y, z1); + } + + public static float wrapAngle(float angle) { + angle %= (float) (Math.PI * 2); + if (angle <= (float) -Math.PI) { + angle += (float) (Math.PI * 2); + } else if (angle > (float) Math.PI) { + angle -= (float) (Math.PI * 2); + } + + return angle; + } + + public static float lerp(float a, float b, float t) { + return lerpUnclamped(a, b, clamp(t, 0.0F, 1.0F)); + } + + public static float lerpUnclamped(float a, float b, float t) { + return a + t * (b - a); + } + + public static double lerp(double a, double b, double t) { + return lerpUnclamped(a, b, clamp(t, 0.0, 1.0)); + } + + public static double lerpUnclamped(double a, double b, double t) { + return a + t * (b - a); + } + + public static float shortAngleDistance(float a, float b) { + float distance = (b - a) % (float) (Math.PI * 2); + return 2.0F * distance % (float) (Math.PI * 2) - distance; + } + + public static float lerpAngle(float a, float b, float t) { + return a + shortAngleDistance(a, b) * t; + } + + public static double floorMod(double x, double y) { + return x - Math.floor(x / y) * y; + } + + public static double compareAngle(double a, double b) { + double diff = b - a; + return floorMod(diff + Math.PI, Math.PI * 2) - Math.PI; + } + + public static double percentile(@Nonnull long[] sortedData, double percentile) { + if (sortedData.length == 1) { + return sortedData[0]; + } else if (percentile >= 1.0) { + return sortedData[sortedData.length - 1]; + } else { + double position = (sortedData.length + 1) * percentile; + double n = percentile * (sortedData.length - 1) + 1.0; + long left; + long right; + if (position >= 1.0) { + left = sortedData[floor(n) - 1]; + right = sortedData[floor(n)]; + } else { + left = sortedData[0]; + right = sortedData[1]; + } + + if (left == right) { + return left; + } else { + double part = n - floor(n); + return left + part * (right - left); + } + } + } + + public static double distanceToLineSq(double x, double y, double ax, double ay, double bx, double by) { + double dx0 = x - ax; + double dy0 = y - ay; + double dx1 = bx - ax; + double dy1 = by - ay; + return distanceToLineSq(x, y, ax, ay, bx, by, dx0, dy0, dx1, dy1); + } + + public static double distanceToLineSq(double x, double y, double ax, double ay, double bx, double by, double dxAx, double dyAy, double dBxAx, double dByAy) { + double t = dxAx * dBxAx + dyAy * dByAy; + t /= dBxAx * dBxAx + dByAy * dByAy; + double px = ax; + double py = ay; + if (t > 1.0) { + px = bx; + py = by; + } else if (t > 0.0) { + px = ax + t * dBxAx; + py = ay + t * dByAy; + } + + dBxAx = x - px; + dByAy = y - py; + return dBxAx * dBxAx + dByAy * dByAy; + } + + public static double distanceToInfLineSq(double x, double y, double ax, double ay, double bx, double by) { + double dx0 = x - ax; + double dy0 = y - ay; + double dx1 = bx - ax; + double dy1 = by - ay; + return distanceToInfLineSq(x, y, ax, ay, dx0, dy0, dx1, dy1); + } + + public static double distanceToInfLineSq(double x, double y, double ax, double ay, double dxAx, double dyAy, double dBxAx, double dByAy) { + double t = dxAx * dBxAx + dyAy * dByAy; + t /= dBxAx * dBxAx + dByAy * dByAy; + double px = ax + t * dBxAx; + double py = ay + t * dByAy; + dBxAx = x - px; + dByAy = y - py; + return dBxAx * dBxAx + dByAy * dByAy; + } + + public static int sideOfLine(double x, double y, double ax, double ay, double bx, double by) { + return (ax - x) * (by - y) - (ay - y) * (bx - x) >= 0.0 ? 1 : -1; + } + + public static Vector3f getRotationForHitNormal(Vector3f normal) { + if (normal == null) { + return Vector3f.ZERO; + } else if (normal.y == 1.0F) { + return Vector3f.ZERO; + } else if (normal.y == -1.0F) { + return new Vector3f(0.0F, 0.0F, (float) Math.PI); + } else if (normal.x == 1.0F) { + return new Vector3f(0.0F, 0.0F, (float) (-Math.PI / 2)); + } else if (normal.x == -1.0F) { + return new Vector3f(0.0F, 0.0F, (float) (Math.PI / 2)); + } else if (normal.z == 1.0F) { + return new Vector3f((float) (Math.PI / 2), 0.0F, 0.0F); + } else { + return normal.z == -1.0F ? new Vector3f((float) (-Math.PI / 2), 0.0F, 0.0F) : Vector3f.ZERO; + } + } + + public static String getNameForHitNormal(Vector3f normal) { + if (normal == null) { + return "UP"; + } else if (normal.y == 1.0F) { + return "UP"; + } else if (normal.y == -1.0F) { + return "DOWN"; + } else if (normal.x == 1.0F) { + return "WEST"; + } else if (normal.x == -1.0F) { + return "EAST"; + } else if (normal.z == 1.0F) { + return "NORTH"; + } else { + return normal.z == -1.0F ? "SOUTH" : "UP"; + } + } + + public static float mapToRange(float value, float valueMin, float valueMax, float rangeMin, float rangeMax) { + float alpha = (value - valueMin) / (valueMax - valueMin); + return rangeMin + alpha * (rangeMax - rangeMin); + } +} diff --git a/src/com/hypixel/hytale/math/util/NearestBlockUtil.java b/src/com/hypixel/hytale/math/util/NearestBlockUtil.java new file mode 100644 index 0000000..e693b3f --- /dev/null +++ b/src/com/hypixel/hytale/math/util/NearestBlockUtil.java @@ -0,0 +1,115 @@ +package com.hypixel.hytale.math.util; + +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.function.BiPredicate; +import java.util.function.DoubleUnaryOperator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class NearestBlockUtil { + public static final NearestBlockUtil.IterationElement[] DEFAULT_ELEMENTS = new NearestBlockUtil.IterationElement[]{ + new NearestBlockUtil.IterationElement(-1, 0, 0, x -> 0.0, y -> y, z -> z), + new NearestBlockUtil.IterationElement(1, 0, 0, x -> 1.0, y -> y, z -> z), + new NearestBlockUtil.IterationElement(0, -1, 0, x -> x, y -> 0.0, z -> z), + new NearestBlockUtil.IterationElement(0, 1, 0, x -> x, y -> 1.0, z -> z), + new NearestBlockUtil.IterationElement(0, 0, -1, x -> x, y -> y, z -> 0.0), + new NearestBlockUtil.IterationElement(0, 0, 1, x -> x, y -> y, z -> 1.0) + }; + + private NearestBlockUtil() { + throw new UnsupportedOperationException(); + } + + @Nullable + public static Vector3i findNearestBlock(@Nonnull Vector3d position, @Nonnull BiPredicate validBlock, T t) { + return findNearestBlock(DEFAULT_ELEMENTS, position.getX(), position.getY(), position.getZ(), validBlock, t); + } + + @Nullable + public static Vector3i findNearestBlock( + @Nonnull NearestBlockUtil.IterationElement[] elements, @Nonnull Vector3d position, @Nonnull BiPredicate validBlock, T t + ) { + return findNearestBlock(elements, position.getX(), position.getY(), position.getZ(), validBlock, t); + } + + @Nullable + public static Vector3i findNearestBlock(double x, double y, double z, @Nonnull BiPredicate validBlock, T t) { + return findNearestBlock(DEFAULT_ELEMENTS, x, y, z, validBlock, t); + } + + @Nullable + public static Vector3i findNearestBlock( + @Nonnull NearestBlockUtil.IterationElement[] elements, double x, double y, double z, @Nonnull BiPredicate validBlock, T t + ) { + int blockX = MathUtil.floor(x); + int blockY = MathUtil.floor(y); + int blockZ = MathUtil.floor(z); + double rx = x % 1.0; + double ry = y % 1.0; + double rz = z % 1.0; + Vector3i nearest = null; + Vector3i tmp = new Vector3i(); + double nearestDist = Double.POSITIVE_INFINITY; + + for (NearestBlockUtil.IterationElement element : elements) { + double dx = rx - element.getX().applyAsDouble(rx); + double dy = ry - element.getY().applyAsDouble(ry); + double dz = rz - element.getZ().applyAsDouble(rz); + double dist = dx * dx + dy * dy + dz * dz; + tmp.assign(blockX + element.getOffsetX(), blockY + element.getOffsetY(), blockZ + element.getOffsetZ()); + if (dist < nearestDist && validBlock.test(tmp, t)) { + nearestDist = dist; + if (nearest == null) { + nearest = new Vector3i(); + } + + nearest.assign(tmp); + } + } + + return nearest; + } + + public static class IterationElement { + private final int ox; + private final int oy; + private final int oz; + private final DoubleUnaryOperator x; + private final DoubleUnaryOperator y; + private final DoubleUnaryOperator z; + + public IterationElement(int ox, int oy, int oz, DoubleUnaryOperator x, DoubleUnaryOperator y, DoubleUnaryOperator z) { + this.ox = ox; + this.oy = oy; + this.oz = oz; + this.x = x; + this.y = y; + this.z = z; + } + + public int getOffsetX() { + return this.ox; + } + + public int getOffsetY() { + return this.oy; + } + + public int getOffsetZ() { + return this.oz; + } + + public DoubleUnaryOperator getX() { + return this.x; + } + + public DoubleUnaryOperator getY() { + return this.y; + } + + public DoubleUnaryOperator getZ() { + return this.z; + } + } +} diff --git a/src/com/hypixel/hytale/math/util/NumberUtil.java b/src/com/hypixel/hytale/math/util/NumberUtil.java new file mode 100644 index 0000000..32c2d9b --- /dev/null +++ b/src/com/hypixel/hytale/math/util/NumberUtil.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.math.util; + +public class NumberUtil { + public NumberUtil() { + } + + public static short sum(short a, short b) { + return (short)(a + b); + } + + public static short subtract(short a, short b) { + return (short)(a - b); + } +} diff --git a/src/com/hypixel/hytale/math/util/TrigMathUtil.java b/src/com/hypixel/hytale/math/util/TrigMathUtil.java new file mode 100644 index 0000000..da59f46 --- /dev/null +++ b/src/com/hypixel/hytale/math/util/TrigMathUtil.java @@ -0,0 +1,120 @@ +package com.hypixel.hytale.math.util; + +import javax.annotation.Nonnull; + +public class TrigMathUtil { + public static final float PI = (float) Math.PI; + public static final float PI_HALF = (float) (Math.PI / 2); + public static final float PI_QUARTER = (float) (Math.PI / 4); + public static final float PI2 = (float) (Math.PI * 2); + public static final float PI4 = (float) (Math.PI * 4); + public static final float radToDeg = 180.0F / (float)Math.PI; + public static final float degToRad = (float) (Math.PI / 180.0); + + public static float sin(float radians) { + return TrigMathUtil.Riven.sin(radians); + } + + public static float cos(float radians) { + return TrigMathUtil.Riven.cos(radians); + } + + public static float sin(double radians) { + return TrigMathUtil.Riven.sin((float)radians); + } + + public static float cos(double radians) { + return TrigMathUtil.Riven.cos((float)radians); + } + + public static float atan2(float y, float x) { + return TrigMathUtil.Icecore.atan2(y, x); + } + + public static float atan2(double y, double x) { + return TrigMathUtil.Icecore.atan2((float)y, (float)x); + } + + public static float atan(double d) { + return (float)Math.atan(d); + } + + public static float asin(double d) { + return (float)Math.asin(d); + } + + private TrigMathUtil() { + } + + private static final class Icecore { + private static final int SIZE_AC = 100000; + private static final int SIZE_AR = 100001; + private static final float[] ATAN2 = new float[100001]; + + private Icecore() { + } + + public static float atan2(float y, float x) { + if (y < 0.0F) { + if (x < 0.0F) { + return y < x ? -ATAN2[(int)(x / y * 100000.0F)] - (float) (Math.PI / 2) : ATAN2[(int)(y / x * 100000.0F)] - (float) Math.PI; + } else { + y = -y; + return y > x ? ATAN2[(int)(x / y * 100000.0F)] - (float) (Math.PI / 2) : -ATAN2[(int)(y / x * 100000.0F)]; + } + } else if (x < 0.0F) { + x = -x; + return y > x ? ATAN2[(int)(x / y * 100000.0F)] + (float) (Math.PI / 2) : -ATAN2[(int)(y / x * 100000.0F)] + (float) Math.PI; + } else { + return y > x ? -ATAN2[(int)(x / y * 100000.0F)] + (float) (Math.PI / 2) : ATAN2[(int)(y / x * 100000.0F)]; + } + } + + static { + for (int i = 0; i <= 100000; i++) { + double d = i / 100000.0; + double x = 1.0; + double y = x * d; + float v = (float)Math.atan2(y, x); + ATAN2[i] = v; + } + } + } + + private static final class Riven { + private static final int SIN_BITS = 12; + private static final int SIN_MASK = ~(-1 << SIN_BITS); + private static final int SIN_COUNT = SIN_MASK + 1; + private static final float radFull = (float) (Math.PI * 2); + private static final float radToIndex = SIN_COUNT / radFull; + private static final float degFull = 360.0F; + private static final float degToIndex = SIN_COUNT / degFull; + @Nonnull + private static final float[] SIN = new float[SIN_COUNT]; + @Nonnull + private static final float[] COS = new float[SIN_COUNT]; + + private Riven() { + } + + public static float sin(float rad) { + return SIN[(int)(rad * radToIndex) & SIN_MASK]; + } + + public static float cos(float rad) { + return COS[(int)(rad * radToIndex) & SIN_MASK]; + } + + static { + for (int i = 0; i < SIN_COUNT; i++) { + SIN[i] = (float)Math.sin((i + 0.5F) / SIN_COUNT * radFull); + COS[i] = (float)Math.cos((i + 0.5F) / SIN_COUNT * radFull); + } + + for (int i = 0; i < 360; i += 90) { + SIN[(int)(i * degToIndex) & SIN_MASK] = (float)Math.sin(i * Math.PI / 180.0); + COS[(int)(i * degToIndex) & SIN_MASK] = (float)Math.cos(i * Math.PI / 180.0); + } + } + } +} diff --git a/src/com/hypixel/hytale/math/vector/Location.java b/src/com/hypixel/hytale/math/vector/Location.java new file mode 100644 index 0000000..424b2dd --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/Location.java @@ -0,0 +1,167 @@ +package com.hypixel.hytale.math.vector; + +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Location { + @Nullable + protected String world; + @Nonnull + protected Vector3d position; + @Nonnull + protected Vector3f rotation; + + public Location() { + this(null, new Vector3d(), new Vector3f(Float.NaN, Float.NaN, Float.NaN)); + } + + public Location(@Nonnull Vector3i position) { + this(null, new Vector3d(position), new Vector3f(Float.NaN, Float.NaN, Float.NaN)); + } + + public Location(@Nullable String world, @Nonnull Vector3i position) { + this(world, new Vector3d(position), new Vector3f(Float.NaN, Float.NaN, Float.NaN)); + } + + public Location(@Nonnull Vector3d position) { + this(null, new Vector3d(position), new Vector3f(Float.NaN, Float.NaN, Float.NaN)); + } + + public Location(@Nullable String world, @Nonnull Vector3d position) { + this(world, new Vector3d(position), new Vector3f(Float.NaN, Float.NaN, Float.NaN)); + } + + public Location(double x, double y, double z) { + this(null, new Vector3d(x, y, z), new Vector3f(Float.NaN, Float.NaN, Float.NaN)); + } + + public Location(@Nullable String world, double x, double y, double z) { + this(world, new Vector3d(x, y, z), new Vector3f(Float.NaN, Float.NaN, Float.NaN)); + } + + public Location(double x, double y, double z, float pitch, float yaw, float roll) { + this(null, new Vector3d(x, y, z), new Vector3f(pitch, yaw, roll)); + } + + public Location(@Nullable String world, double x, double y, double z, float pitch, float yaw, float roll) { + this(world, new Vector3d(x, y, z), new Vector3f(pitch, yaw, roll)); + } + + public Location(@Nonnull Vector3d position, @Nonnull Vector3f rotation) { + this(null, position, rotation); + } + + public Location(@Nonnull Transform transform) { + this(null, transform.position, transform.rotation); + } + + public Location(@Nullable String world, @Nonnull Transform transform) { + this(world, transform.position, transform.rotation); + } + + public Location(@Nullable String world, @Nonnull Vector3d position, @Nonnull Vector3f rotation) { + this.world = world; + this.position = position; + this.rotation = rotation; + } + + @Nullable + public String getWorld() { + return this.world; + } + + public void setWorld(@Nullable String world) { + this.world = world; + } + + @Nonnull + public Vector3d getPosition() { + return this.position; + } + + public void setPosition(@Nonnull Vector3d position) { + this.position = position; + } + + @Nonnull + public Vector3f getRotation() { + return this.rotation; + } + + public void setRotation(@Nonnull Vector3f rotation) { + this.rotation = rotation; + } + + @Nonnull + public Vector3d getDirection() { + return Transform.getDirection(this.rotation.getPitch(), this.rotation.getYaw()); + } + + @Nonnull + public Vector3i getAxisDirection() { + return this.getAxisDirection(this.rotation.getPitch(), this.rotation.getYaw()); + } + + @Nonnull + public Vector3i getAxisDirection(float pitch, float yaw) { + if (Float.isNaN(pitch)) { + throw new IllegalStateException("Pitch can't be NaN"); + } else if (Float.isNaN(yaw)) { + throw new IllegalStateException("Yaw can't be NaN"); + } else { + float len = TrigMathUtil.cos(pitch); + float x = len * -TrigMathUtil.sin(yaw); + float y = TrigMathUtil.sin(pitch); + float z = len * -TrigMathUtil.cos(yaw); + return new Vector3i(MathUtil.fastRound(x), MathUtil.fastRound(y), MathUtil.fastRound(z)); + } + } + + @Nonnull + public Axis getAxis() { + Vector3i axisDirection = this.getAxisDirection(); + if (axisDirection.getX() != 0) { + return Axis.X; + } else { + return axisDirection.getY() != 0 ? Axis.Y : Axis.Z; + } + } + + @Nonnull + public Transform toTransform() { + return new Transform(this.position, this.rotation); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Location location = (Location)o; + if (!Objects.equals(this.world, location.world)) { + return false; + } else { + return !Objects.equals(this.position, location.position) ? false : Objects.equals(this.rotation, location.rotation); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.world != null ? this.world.hashCode() : 0; + result = 31 * result + this.position.hashCode(); + return 31 * result + this.rotation.hashCode(); + } + + @Nonnull + @Override + public String toString() { + return "Location{world='" + this.world + "', position=" + this.position + ", rotation=" + this.rotation + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/vector/Transform.java b/src/com/hypixel/hytale/math/vector/Transform.java new file mode 100644 index 0000000..b294b3c --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/Transform.java @@ -0,0 +1,275 @@ +package com.hypixel.hytale.math.vector; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Transform { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(Transform.class, Transform::new) + .appendInherited( + new KeyedCodec<>("X", Codec.DOUBLE), (o, i) -> o.getPosition().x = i, o -> o.getPosition().x, (o, p) -> o.getPosition().x = p.getPosition().x + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Y", Codec.DOUBLE), (o, i) -> o.getPosition().y = i, o -> o.getPosition().y, (o, p) -> o.getPosition().y = p.getPosition().y + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Z", Codec.DOUBLE), (o, i) -> o.getPosition().z = i, o -> o.getPosition().z, (o, p) -> o.getPosition().z = p.getPosition().z + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Pitch", Codec.FLOAT), + (o, i) -> o.getRotation().x = i, + o -> Float.isNaN(o.getRotation().x) ? null : o.getRotation().x, + (o, p) -> o.getRotation().x = p.getRotation().x + ) + .add() + .appendInherited( + new KeyedCodec<>("Yaw", Codec.FLOAT), + (o, i) -> o.getRotation().y = i, + o -> Float.isNaN(o.getRotation().y) ? null : o.getRotation().y, + (o, p) -> o.getRotation().y = p.getRotation().y + ) + .add() + .appendInherited( + new KeyedCodec<>("Roll", Codec.FLOAT), + (o, i) -> o.getRotation().z = i, + o -> Float.isNaN(o.getRotation().z) ? null : o.getRotation().z, + (o, p) -> o.getRotation().z = p.getRotation().z + ) + .add() + .build(); + @Nonnull + public static final BuilderCodec CODEC_DEGREES = BuilderCodec.builder(Transform.class, Transform::new) + .appendInherited( + new KeyedCodec<>("X", Codec.DOUBLE), (o, i) -> o.getPosition().x = i, o -> o.getPosition().x, (o, p) -> o.getPosition().x = p.getPosition().x + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Y", Codec.DOUBLE), (o, i) -> o.getPosition().y = i, o -> o.getPosition().y, (o, p) -> o.getPosition().y = p.getPosition().y + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Z", Codec.DOUBLE), (o, i) -> o.getPosition().z = i, o -> o.getPosition().z, (o, p) -> o.getPosition().z = p.getPosition().z + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Pitch", Codec.FLOAT), + (o, i) -> o.getRotation().x = (float)Math.toRadians(i.floatValue()), + o -> Float.isNaN(o.getRotation().x) ? null : (float)Math.toDegrees(o.getRotation().x), + (o, p) -> o.getRotation().x = p.getRotation().x + ) + .add() + .appendInherited( + new KeyedCodec<>("Yaw", Codec.FLOAT), + (o, i) -> o.getRotation().y = (float)Math.toRadians(i.floatValue()), + o -> Float.isNaN(o.getRotation().y) ? null : (float)Math.toDegrees(o.getRotation().y), + (o, p) -> o.getRotation().y = p.getRotation().y + ) + .add() + .appendInherited( + new KeyedCodec<>("Roll", Codec.FLOAT), + (o, i) -> o.getRotation().z = (float)Math.toRadians(i.floatValue()), + o -> Float.isNaN(o.getRotation().z) ? null : (float)Math.toDegrees(o.getRotation().z), + (o, p) -> o.getRotation().z = p.getRotation().z + ) + .add() + .build(); + @Nonnull + protected Vector3d position; + @Nonnull + protected Vector3f rotation; + public static final int X_IS_RELATIVE = 1; + public static final int Y_IS_RELATIVE = 2; + public static final int Z_IS_RELATIVE = 4; + public static final int YAW_IS_RELATIVE = 8; + public static final int PITCH_IS_RELATIVE = 16; + public static final int ROLL_IS_RELATIVE = 32; + public static final int RELATIVE_TO_BLOCK = 64; + + public Transform() { + this(new Vector3d(), new Vector3f(Float.NaN, Float.NaN, Float.NaN)); + } + + public Transform(@Nonnull Vector3i position) { + this(new Vector3d(position), new Vector3f(Float.NaN, Float.NaN, Float.NaN)); + } + + public Transform(@Nonnull Vector3d position) { + this(new Vector3d(position), new Vector3f(Float.NaN, Float.NaN, Float.NaN)); + } + + public Transform(double x, double y, double z) { + this(new Vector3d(x, y, z), new Vector3f(Float.NaN, Float.NaN, Float.NaN)); + } + + public Transform(double x, double y, double z, float pitch, float yaw, float roll) { + this(new Vector3d(x, y, z), new Vector3f(pitch, yaw, roll)); + } + + public Transform(@Nonnull Vector3d position, @Nonnull Vector3f rotation) { + this.position = position; + this.rotation = rotation; + } + + public void assign(@Nonnull Transform transform) { + this.position.assign(transform.getPosition()); + this.rotation.assign(transform.getRotation()); + } + + @Nonnull + public Vector3d getPosition() { + return this.position; + } + + public void setPosition(@Nonnull Vector3d position) { + this.position = position; + } + + @Nonnull + public Vector3f getRotation() { + return this.rotation; + } + + public void setRotation(@Nonnull Vector3f rotation) { + this.rotation = rotation; + } + + @Nonnull + public Vector3d getDirection() { + return getDirection(this.rotation.getPitch(), this.rotation.getYaw()); + } + + @Nonnull + public static Vector3d getDirection(float pitch, float yaw) { + if (Float.isNaN(pitch)) { + throw new IllegalStateException("Pitch can't be NaN"); + } else if (Float.isNaN(yaw)) { + throw new IllegalStateException("Yaw can't be NaN"); + } else { + double len = TrigMathUtil.cos(pitch); + double x = len * -TrigMathUtil.sin(yaw); + double y = TrigMathUtil.sin(pitch); + double z = len * -TrigMathUtil.cos(yaw); + return new Vector3d(x, y, z); + } + } + + @Nonnull + public Vector3i getAxisDirection() { + return this.getAxisDirection(this.rotation.getPitch(), this.rotation.getYaw()); + } + + @Nonnull + public Vector3i getAxisDirection(float pitch, float yaw) { + if (Float.isNaN(pitch)) { + throw new IllegalStateException("Pitch can't be NaN"); + } else if (Float.isNaN(yaw)) { + throw new IllegalStateException("Yaw can't be NaN"); + } else { + float len = TrigMathUtil.cos(pitch); + float x = len * -TrigMathUtil.sin(yaw); + float y = TrigMathUtil.sin(pitch); + float z = len * -TrigMathUtil.cos(yaw); + return new Vector3i(MathUtil.fastRound(x), MathUtil.fastRound(y), MathUtil.fastRound(z)); + } + } + + @Nonnull + public Axis getAxis() { + Vector3i axisDirection = this.getAxisDirection(); + if (axisDirection.getX() != 0) { + return Axis.X; + } else { + return axisDirection.getY() != 0 ? Axis.Y : Axis.Z; + } + } + + @Nonnull + public Transform clone() { + return new Transform(this.position.clone(), this.rotation.clone()); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Transform transform = (Transform)o; + return !Objects.equals(this.position, transform.position) ? false : Objects.equals(this.rotation, transform.rotation); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.position.hashCode(); + return 31 * result + this.rotation.hashCode(); + } + + @Nonnull + @Override + public String toString() { + return "Transform{position=" + this.position + ", rotation=" + this.rotation + "}"; + } + + public static void applyMaskedRelativeTransform( + @Nonnull Transform transform, byte relativeMask, @Nonnull Vector3d sourcePosition, @Nonnull Vector3f sourceRotation, @Nonnull Vector3i blockPosition + ) { + if (relativeMask != 0) { + if ((relativeMask & 64) != 0) { + if ((relativeMask & 1) != 0) { + transform.getPosition().setX(transform.getPosition().getX() + blockPosition.getX()); + } + + if ((relativeMask & 2) != 0) { + transform.getPosition().setY(transform.getPosition().getY() + blockPosition.getY()); + } + + if ((relativeMask & 4) != 0) { + transform.getPosition().setZ(transform.getPosition().getZ() + blockPosition.getZ()); + } + } else { + if ((relativeMask & 1) != 0) { + transform.getPosition().setX(transform.getPosition().getX() + sourcePosition.getX()); + } + + if ((relativeMask & 2) != 0) { + transform.getPosition().setY(transform.getPosition().getY() + sourcePosition.getY()); + } + + if ((relativeMask & 4) != 0) { + transform.getPosition().setZ(transform.getPosition().getZ() + sourcePosition.getZ()); + } + } + + if ((relativeMask & 8) != 0) { + transform.getRotation().setYaw(transform.getRotation().getYaw() + sourceRotation.getYaw()); + } + + if ((relativeMask & 16) != 0) { + transform.getRotation().setPitch(transform.getRotation().getPitch() + sourceRotation.getPitch()); + } + + if ((relativeMask & 32) != 0) { + transform.getRotation().setRoll(transform.getRotation().getRoll() + sourceRotation.getRoll()); + } + } + } +} diff --git a/src/com/hypixel/hytale/math/vector/Vector2d.java b/src/com/hypixel/hytale/math/vector/Vector2d.java new file mode 100644 index 0000000..bc7c45b --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/Vector2d.java @@ -0,0 +1,336 @@ +package com.hypixel.hytale.math.vector; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDisplayMode; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.codec.Vector2dArrayCodec; +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; +import java.util.Random; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Vector2d { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(Vector2d.class, Vector2d::new) + .metadata(UIDisplayMode.COMPACT) + .appendInherited(new KeyedCodec<>("X", Codec.DOUBLE), (o, i) -> o.x = i, o -> o.x, (o, p) -> o.x = p.x) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Y", Codec.DOUBLE), (o, i) -> o.y = i, o -> o.y, (o, p) -> o.y = p.y) + .addValidator(Validators.nonNull()) + .add() + .build(); + @Deprecated + public static final Vector2dArrayCodec AS_ARRAY_CODEC = new Vector2dArrayCodec(); + public static final Vector2d ZERO = new Vector2d(0.0, 0.0); + public static final Vector2d UP = new Vector2d(0.0, 1.0); + public static final Vector2d POS_Y = UP; + public static final Vector2d DOWN = new Vector2d(0.0, -1.0); + public static final Vector2d NEG_Y = DOWN; + public static final Vector2d RIGHT = new Vector2d(1.0, 0.0); + public static final Vector2d POS_X = RIGHT; + public static final Vector2d LEFT = new Vector2d(-1.0, 0.0); + public static final Vector2d NEG_X = LEFT; + public static final Vector2d ALL_ONES = new Vector2d(1.0, 1.0); + public static final Vector2d[] DIRECTIONS = new Vector2d[]{UP, DOWN, LEFT, RIGHT}; + public double x; + public double y; + private transient int hash; + + public Vector2d() { + this(0.0, 0.0); + } + + public Vector2d(@Nonnull Vector2d v) { + this(v.x, v.y); + } + + public Vector2d(double x, double y) { + this.x = x; + this.y = y; + this.hash = 0; + } + + public Vector2d(@Nonnull Random random, double length) { + float yaw = random.nextFloat() * (float) (Math.PI * 2); + float pitch = random.nextFloat() * (float) (Math.PI * 2); + this.x = TrigMathUtil.sin(pitch) * TrigMathUtil.cos(yaw); + this.y = TrigMathUtil.cos(pitch); + this.scale(length); + this.hash = 0; + } + + public double getX() { + return this.x; + } + + public void setX(double x) { + this.x = x; + this.hash = 0; + } + + public double getY() { + return this.y; + } + + public void setY(double y) { + this.y = y; + this.hash = 0; + } + + @Nonnull + public Vector2d assign(@Nonnull Vector2d v) { + this.x = v.x; + this.y = v.y; + this.hash = v.hash; + return this; + } + + @Nonnull + public Vector2d assign(double v) { + this.x = v; + this.y = v; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2d assign(@Nonnull double[] v) { + this.x = v[0]; + this.y = v[1]; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2d assign(@Nonnull float[] v) { + this.x = v[0]; + this.y = v[1]; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2d assign(double x, double y) { + this.x = x; + this.y = y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2d add(@Nonnull Vector2d v) { + this.x = this.x + v.x; + this.y = this.y + v.y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2d add(double x, double y) { + this.x += x; + this.y += y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2d addScaled(@Nonnull Vector2d v, double s) { + this.x = this.x + v.x * s; + this.y = this.y + v.y * s; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2d subtract(@Nonnull Vector2d v) { + this.x = this.x - v.x; + this.y = this.y - v.y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2d subtract(double x, double y) { + this.x -= x; + this.y -= y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2d negate() { + this.x = -this.x; + this.y = -this.y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2d scale(double s) { + this.x *= s; + this.y *= s; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2d scale(@Nonnull Vector2d p) { + this.x = this.x * p.x; + this.y = this.y * p.y; + this.hash = 0; + return this; + } + + public double dot(@Nonnull Vector2d other) { + return this.x * other.x + this.y * other.y; + } + + public double distanceTo(@Nonnull Vector2d v) { + return Math.sqrt(this.distanceSquaredTo(v)); + } + + public double distanceTo(double x, double y) { + return Math.sqrt(this.distanceSquaredTo(x, y)); + } + + public double distanceSquaredTo(@Nonnull Vector2d v) { + double x0 = v.x - this.x; + double y0 = v.y - this.y; + return x0 * x0 + y0 * y0; + } + + public double distanceSquaredTo(double x, double y) { + x -= this.x; + y -= this.y; + return x * x + y * y; + } + + @Nonnull + public Vector2d normalize() { + return this.setLength(1.0); + } + + public double length() { + return Math.sqrt(this.squaredLength()); + } + + public double squaredLength() { + return this.x * this.x + this.y * this.y; + } + + @Nonnull + public Vector2d setLength(double newLen) { + return this.scale(newLen / this.length()); + } + + @Nonnull + public Vector2d clampLength(double maxLength) { + double length = this.length(); + return maxLength > length ? this : this.scale(maxLength / length); + } + + @Nonnull + public Vector2d floor() { + this.x = Math.floor(this.x); + this.y = Math.floor(this.y); + this.hash = 0; + return this; + } + + @Nonnull + public Vector2d ceil() { + this.x = Math.ceil(this.x); + this.y = Math.ceil(this.y); + this.hash = 0; + return this; + } + + @Nonnull + public Vector2d clipToZero(double epsilon) { + this.x = MathUtil.clipToZero(this.x, epsilon); + this.y = MathUtil.clipToZero(this.y, epsilon); + this.hash = 0; + return this; + } + + public boolean closeToZero(double epsilon) { + return MathUtil.closeToZero(this.x, epsilon) && MathUtil.closeToZero(this.y, epsilon); + } + + public boolean isFinite() { + return Double.isFinite(this.x) && Double.isFinite(this.y); + } + + @Nonnull + public Vector2d dropHash() { + this.hash = 0; + return this; + } + + @Nonnull + public static Vector2d max(@Nonnull Vector2d a, @Nonnull Vector2d b) { + return new Vector2d(Math.max(a.x, b.x), Math.max(a.y, b.y)); + } + + @Nonnull + public static Vector2d min(@Nonnull Vector2d a, @Nonnull Vector2d b) { + return new Vector2d(Math.min(a.x, b.x), Math.min(a.y, b.y)); + } + + @Nonnull + public static Vector2d lerp(@Nonnull Vector2d a, @Nonnull Vector2d b, double t) { + return lerpUnclamped(a, b, MathUtil.clamp(t, 0.0, 1.0)); + } + + @Nonnull + public static Vector2d lerpUnclamped(@Nonnull Vector2d a, @Nonnull Vector2d b, double t) { + return new Vector2d(a.x + t * (b.x - a.x), a.y + t * (b.y - a.y)); + } + + public static double distance(double x1, double y1, double x2, double y2) { + return Math.sqrt(distanceSquared(x1, y1, x2, y2)); + } + + public static double distanceSquared(double x1, double y1, double x2, double y2) { + x1 -= x2; + y1 -= y2; + return x1 * x1 + y1 * y1; + } + + @Nonnull + public Vector2d clone() { + return new Vector2d(this.x, this.y); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Vector2d vector2d = (Vector2d)o; + return vector2d.x == this.x && vector2d.y == this.y; + } else { + return false; + } + } + + @Override + public int hashCode() { + if (this.hash == 0) { + this.hash = (int)HashUtil.hash(Double.doubleToLongBits(this.x), Double.doubleToLongBits(this.y)); + } + + return this.hash; + } + + @Nonnull + @Override + public String toString() { + return "Vector2d{x=" + this.x + ", y=" + this.y + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/vector/Vector2i.java b/src/com/hypixel/hytale/math/vector/Vector2i.java new file mode 100644 index 0000000..102eedb --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/Vector2i.java @@ -0,0 +1,273 @@ +package com.hypixel.hytale.math.vector; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDisplayMode; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.util.HashUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Vector2i { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(Vector2i.class, Vector2i::new) + .metadata(UIDisplayMode.COMPACT) + .appendInherited(new KeyedCodec<>("X", Codec.INTEGER), (o, i) -> o.x = i, o -> o.x, (o, p) -> o.x = p.x) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Y", Codec.INTEGER), (o, i) -> o.y = i, o -> o.y, (o, p) -> o.y = p.y) + .addValidator(Validators.nonNull()) + .add() + .build(); + public static final Vector2i ZERO = new Vector2i(0, 0); + public static final Vector2i UP = new Vector2i(0, 1); + public static final Vector2i POS_Y = UP; + public static final Vector2i DOWN = new Vector2i(0, -1); + public static final Vector2i NEG_Y = DOWN; + public static final Vector2i RIGHT = new Vector2i(1, 0); + public static final Vector2i POS_X = RIGHT; + public static final Vector2i LEFT = new Vector2i(-1, 0); + public static final Vector2i NEG_X = LEFT; + public static final Vector2i ALL_ONES = new Vector2i(1, 1); + public static final Vector2i[] DIRECTIONS = new Vector2i[]{UP, DOWN, LEFT, RIGHT}; + public int x; + public int y; + private transient int hash; + + public Vector2i() { + this(0, 0); + } + + public Vector2i(@Nonnull Vector2i v) { + this(v.x, v.y); + } + + public Vector2i(int x, int y) { + this.x = x; + this.y = y; + this.hash = 0; + } + + public int getX() { + return this.x; + } + + public void setX(int x) { + this.x = x; + this.hash = 0; + } + + public int getY() { + return this.y; + } + + public void setY(int y) { + this.y = y; + this.hash = 0; + } + + @Nonnull + public Vector2i assign(@Nonnull Vector2i v) { + this.x = v.x; + this.y = v.y; + this.hash = v.hash; + return this; + } + + @Nonnull + public Vector2i assign(int v) { + this.x = v; + this.y = v; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2i assign(@Nonnull int[] v) { + this.x = v[0]; + this.y = v[1]; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2i assign(int x, int y) { + this.x = x; + this.y = y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2i add(@Nonnull Vector2i v) { + this.x = this.x + v.x; + this.y = this.y + v.y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2i add(int x, int y) { + this.x += x; + this.y += y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2i addScaled(@Nonnull Vector2i v, int s) { + this.x = this.x + v.x * s; + this.y = this.y + v.y * s; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2i subtract(@Nonnull Vector2i v) { + this.x = this.x - v.x; + this.y = this.y - v.y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2i subtract(int x, int y) { + this.x -= x; + this.y -= y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2i negate() { + this.x = -this.x; + this.y = -this.y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2i scale(int s) { + this.x *= s; + this.y *= s; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2i scale(@Nonnull Vector2i p) { + this.x = this.x * p.x; + this.y = this.y * p.y; + this.hash = 0; + return this; + } + + public int dot(@Nonnull Vector2i other) { + return this.x * other.x + this.y * other.y; + } + + public double distanceTo(@Nonnull Vector2i v) { + return Math.sqrt(this.distanceSquaredTo(v)); + } + + public double distanceTo(int x, int y) { + return Math.sqrt(this.distanceSquaredTo(x, y)); + } + + public int distanceSquaredTo(@Nonnull Vector2i v) { + int x0 = v.x - this.x; + int y0 = v.y - this.y; + return x0 * x0 + y0 * y0; + } + + public int distanceSquaredTo(int x, int y) { + x -= this.x; + y -= this.y; + return x * x + y * y; + } + + @Nonnull + public Vector2i normalize() { + return this.setLength(1); + } + + public double length() { + return Math.sqrt(this.squaredLength()); + } + + public double squaredLength() { + return this.x * this.x + this.y * this.y; + } + + @Nonnull + public Vector2i setLength(int newLen) { + double scale = newLen / this.length(); + this.x = (int)(this.x * scale); + this.y = (int)(this.y * scale); + this.hash = 0; + return this; + } + + @Nonnull + public Vector2i clampLength(int maxLength) { + double length = this.length(); + if (maxLength > length) { + return this; + } else { + double scale = maxLength / length; + this.x = (int)(this.x * scale); + this.y = (int)(this.y * scale); + this.hash = 0; + return this; + } + } + + @Nonnull + public Vector2i dropHash() { + this.hash = 0; + return this; + } + + @Nonnull + public Vector2i clone() { + return new Vector2i(this.x, this.y); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Vector2i vector2i = (Vector2i)o; + return vector2i.x == this.x && vector2i.y == this.y; + } else { + return false; + } + } + + @Override + public int hashCode() { + if (this.hash == 0) { + this.hash = (int)HashUtil.hash(this.x, this.y); + } + + return this.hash; + } + + @Nonnull + @Override + public String toString() { + return "Vector2i{x=" + this.x + ", y=" + this.y + "}"; + } + + @Nonnull + public static Vector2i max(@Nonnull Vector2i a, @Nonnull Vector2i b) { + return new Vector2i(Math.max(a.x, b.x), Math.max(a.y, b.y)); + } + + @Nonnull + public static Vector2i min(@Nonnull Vector2i a, @Nonnull Vector2i b) { + return new Vector2i(Math.min(a.x, b.x), Math.min(a.y, b.y)); + } +} diff --git a/src/com/hypixel/hytale/math/vector/Vector2l.java b/src/com/hypixel/hytale/math/vector/Vector2l.java new file mode 100644 index 0000000..31450e5 --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/Vector2l.java @@ -0,0 +1,269 @@ +package com.hypixel.hytale.math.vector; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDisplayMode; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.util.HashUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Vector2l { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(Vector2l.class, Vector2l::new) + .metadata(UIDisplayMode.COMPACT) + .appendInherited(new KeyedCodec<>("X", Codec.LONG), (o, i) -> o.x = i, o -> o.x, (o, p) -> o.x = p.x) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Y", Codec.LONG), (o, i) -> o.y = i, o -> o.y, (o, p) -> o.y = p.y) + .addValidator(Validators.nonNull()) + .add() + .build(); + public static final Vector2l ZERO = new Vector2l(0L, 0L); + public static final Vector2l UP = new Vector2l(0L, 1L); + public static final Vector2l POS_Y = UP; + public static final Vector2l DOWN = new Vector2l(0L, -1L); + public static final Vector2l NEG_Y = DOWN; + public static final Vector2l RIGHT = new Vector2l(1L, 0L); + public static final Vector2l POS_X = RIGHT; + public static final Vector2l LEFT = new Vector2l(-1L, 0L); + public static final Vector2l NEG_X = LEFT; + public static final Vector2l ALL_ONES = new Vector2l(1L, 1L); + public static final Vector2l[] DIRECTIONS = new Vector2l[]{UP, DOWN, LEFT, RIGHT}; + public long x; + public long y; + private transient int hash; + + public Vector2l() { + this(0L, 0L); + } + + public Vector2l(@Nonnull Vector2l v) { + this(v.x, v.y); + } + + public Vector2l(long x, long y) { + this.x = x; + this.y = y; + this.hash = 0; + } + + public long getX() { + return this.x; + } + + public void setX(long x) { + this.x = x; + this.hash = 0; + } + + public long getY() { + return this.y; + } + + public void setY(long y) { + this.y = y; + this.hash = 0; + } + + @Nonnull + public Vector2l assign(@Nonnull Vector2l v) { + this.x = v.x; + this.y = v.y; + this.hash = v.hash; + return this; + } + + @Nonnull + public Vector2l assign(long v) { + this.x = v; + this.y = v; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2l assign(@Nonnull long[] v) { + this.x = v[0]; + this.y = v[1]; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2l assign(long x, long y) { + this.x = x; + this.y = y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2l add(@Nonnull Vector2l v) { + this.x = this.x + v.x; + this.y = this.y + v.y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2l add(long x, long y) { + this.x += x; + this.y += y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2l addScaled(@Nonnull Vector2l v, long s) { + this.x = this.x + v.x * s; + this.y = this.y + v.y * s; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2l subtract(@Nonnull Vector2l v) { + this.x = this.x - v.x; + this.y = this.y - v.y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2l subtract(long x, long y) { + this.x -= x; + this.y -= y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2l negate() { + this.x = -this.x; + this.y = -this.y; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2l scale(long s) { + this.x *= s; + this.y *= s; + this.hash = 0; + return this; + } + + @Nonnull + public Vector2l scale(double s) { + this.x = (long)(this.x * s); + this.y = (long)(this.y * s); + this.hash = 0; + return this; + } + + @Nonnull + public Vector2l scale(@Nonnull Vector2l p) { + this.x = this.x * p.x; + this.y = this.y * p.y; + this.hash = 0; + return this; + } + + public long dot(@Nonnull Vector2l other) { + return this.x * other.x + this.y * other.y; + } + + public double distanceTo(@Nonnull Vector2l v) { + return Math.sqrt(this.distanceSquaredTo(v)); + } + + public double distanceTo(long x, long y) { + return Math.sqrt(this.distanceSquaredTo(x, y)); + } + + public long distanceSquaredTo(@Nonnull Vector2l v) { + long x0 = v.x - this.x; + long y0 = v.y - this.y; + return x0 * x0 + y0 * y0; + } + + public long distanceSquaredTo(long x, long y) { + long dx = x - this.x; + long dy = y - this.y; + return dx * dx + dy * dy; + } + + @Nonnull + public Vector2l normalize() { + return this.setLength(1L); + } + + public double length() { + return Math.sqrt(this.squaredLength()); + } + + public long squaredLength() { + return this.x * this.x + this.y * this.y; + } + + @Nonnull + public Vector2l setLength(long newLen) { + return this.scale(newLen / this.length()); + } + + @Nonnull + public Vector2l clampLength(long maxLength) { + double length = this.length(); + return maxLength > length ? this : this.scale(maxLength / length); + } + + @Nonnull + public Vector2l dropHash() { + this.hash = 0; + return this; + } + + @Nonnull + public Vector2l clone() { + return new Vector2l(this.x, this.y); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Vector2l vector2l = (Vector2l)o; + return vector2l.x == this.x && vector2l.y == this.y; + } else { + return false; + } + } + + @Override + public int hashCode() { + if (this.hash == 0) { + this.hash = (int)HashUtil.hash(this.x, this.y); + } + + return this.hash; + } + + @Nonnull + @Override + public String toString() { + return "Vector2l{x=" + this.x + ", y=" + this.y + "}"; + } + + @Nonnull + public static Vector2l max(@Nonnull Vector2l a, @Nonnull Vector2l b) { + return new Vector2l(Math.max(a.x, b.x), Math.max(a.y, b.y)); + } + + @Nonnull + public static Vector2l min(@Nonnull Vector2l a, @Nonnull Vector2l b) { + return new Vector2l(Math.min(a.x, b.x), Math.min(a.y, b.y)); + } +} diff --git a/src/com/hypixel/hytale/math/vector/Vector3d.java b/src/com/hypixel/hytale/math/vector/Vector3d.java new file mode 100644 index 0000000..fe381ec --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/Vector3d.java @@ -0,0 +1,548 @@ +package com.hypixel.hytale.math.vector; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDisplayMode; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.codec.Vector3dArrayCodec; +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; +import java.util.Random; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Vector3d { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(Vector3d.class, Vector3d::new) + .metadata(UIDisplayMode.COMPACT) + .appendInherited(new KeyedCodec<>("X", Codec.DOUBLE), (o, i) -> o.x = i, o -> o.x, (o, p) -> o.x = p.x) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Y", Codec.DOUBLE), (o, i) -> o.y = i, o -> o.y, (o, p) -> o.y = p.y) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Z", Codec.DOUBLE), (o, i) -> o.z = i, o -> o.z, (o, p) -> o.z = p.z) + .addValidator(Validators.nonNull()) + .add() + .build(); + @Deprecated + public static final Vector3dArrayCodec AS_ARRAY_CODEC = new Vector3dArrayCodec(); + public static final Vector3d ZERO = new Vector3d(0.0, 0.0, 0.0); + public static final Vector3d UP = new Vector3d(0.0, 1.0, 0.0); + public static final Vector3d POS_Y = UP; + public static final Vector3d DOWN = new Vector3d(0.0, -1.0, 0.0); + public static final Vector3d NEG_Y = DOWN; + public static final Vector3d FORWARD = new Vector3d(0.0, 0.0, -1.0); + public static final Vector3d NEG_Z = FORWARD; + public static final Vector3d NORTH = FORWARD; + public static final Vector3d BACKWARD = new Vector3d(0.0, 0.0, 1.0); + public static final Vector3d POS_Z = BACKWARD; + public static final Vector3d SOUTH = BACKWARD; + public static final Vector3d RIGHT = new Vector3d(1.0, 0.0, 0.0); + public static final Vector3d POS_X = RIGHT; + public static final Vector3d EAST = RIGHT; + public static final Vector3d LEFT = new Vector3d(-1.0, 0.0, 0.0); + public static final Vector3d NEG_X = LEFT; + public static final Vector3d WEST = LEFT; + public static final Vector3d ALL_ONES = new Vector3d(1.0, 1.0, 1.0); + public static final Vector3d MIN = new Vector3d(-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE); + public static final Vector3d MAX = new Vector3d(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE); + public static final Vector3d[] BLOCK_SIDES = new Vector3d[]{UP, DOWN, FORWARD, BACKWARD, LEFT, RIGHT}; + public static final Vector3d[] BLOCK_EDGES = new Vector3d[]{ + add(UP, FORWARD), + add(DOWN, FORWARD), + add(UP, BACKWARD), + add(DOWN, BACKWARD), + add(UP, LEFT), + add(DOWN, LEFT), + add(UP, RIGHT), + add(DOWN, RIGHT), + add(FORWARD, LEFT), + add(FORWARD, RIGHT), + add(BACKWARD, LEFT), + add(BACKWARD, RIGHT) + }; + public static final Vector3d[] BLOCK_CORNERS = new Vector3d[]{ + add(UP, FORWARD, LEFT), + add(UP, FORWARD, RIGHT), + add(DOWN, FORWARD, LEFT), + add(DOWN, FORWARD, RIGHT), + add(UP, BACKWARD, LEFT), + add(UP, BACKWARD, RIGHT), + add(DOWN, BACKWARD, LEFT), + add(DOWN, BACKWARD, RIGHT) + }; + public static final Vector3d[][] BLOCK_PARTS = new Vector3d[][]{BLOCK_SIDES, BLOCK_EDGES, BLOCK_CORNERS}; + public static final Vector3d[] CARDINAL_DIRECTIONS = new Vector3d[]{NORTH, SOUTH, EAST, WEST}; + public double x; + public double y; + public double z; + private transient int hash; + + public Vector3d() { + this(0.0, 0.0, 0.0); + } + + public Vector3d(@Nonnull Vector3d v) { + this(v.x, v.y, v.z); + } + + public Vector3d(@Nonnull Vector3i v) { + this(v.x, v.y, v.z); + } + + public Vector3d(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + this.hash = 0; + } + + public Vector3d(float yaw, float pitch) { + this(); + this.assign(yaw, pitch); + } + + public Vector3d(@Nonnull Random random, double length) { + this(random.nextFloat() * (float) (Math.PI * 2), random.nextFloat() * (float) (Math.PI * 2)); + this.scale(length); + } + + public double getX() { + return this.x; + } + + public void setX(double x) { + this.x = x; + this.hash = 0; + } + + public double getY() { + return this.y; + } + + public void setY(double y) { + this.y = y; + this.hash = 0; + } + + public double getZ() { + return this.z; + } + + public void setZ(double z) { + this.z = z; + this.hash = 0; + } + + @Nonnull + public Vector3d assign(@Nonnull Vector3d v) { + this.x = v.x; + this.y = v.y; + this.z = v.z; + this.hash = v.hash; + return this; + } + + @Nonnull + public Vector3d assign(double v) { + this.x = v; + this.y = v; + this.z = v; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d assign(@Nonnull double[] v) { + this.x = v[0]; + this.y = v[1]; + this.z = v[2]; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d assign(@Nonnull float[] v) { + this.x = v[0]; + this.y = v[1]; + this.z = v[2]; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d assign(double yaw, double pitch) { + double len = TrigMathUtil.cos(pitch); + double x = len * -TrigMathUtil.sin(yaw); + double y = TrigMathUtil.sin(pitch); + double z = len * -TrigMathUtil.cos(yaw); + return this.assign(x, y, z); + } + + @Nonnull + public Vector3d assign(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d add(@Nonnull Vector3d v) { + this.x = this.x + v.x; + this.y = this.y + v.y; + this.z = this.z + v.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d add(@Nonnull Vector3i v) { + this.x = this.x + v.x; + this.y = this.y + v.y; + this.z = this.z + v.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d add(double x, double y, double z) { + this.x += x; + this.y += y; + this.z += z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d add(double value) { + this.x += value; + this.y += value; + this.z += value; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d addScaled(@Nonnull Vector3d v, double s) { + this.x = this.x + v.x * s; + this.y = this.y + v.y * s; + this.z = this.z + v.z * s; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d subtract(@Nonnull Vector3d v) { + this.x = this.x - v.x; + this.y = this.y - v.y; + this.z = this.z - v.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d subtract(@Nonnull Vector3i v) { + this.x = this.x - v.x; + this.y = this.y - v.y; + this.z = this.z - v.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d subtract(double x, double y, double z) { + this.x -= x; + this.y -= y; + this.z -= z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d subtract(double value) { + this.x -= value; + this.y -= value; + this.z -= value; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d negate() { + this.x = -this.x; + this.y = -this.y; + this.z = -this.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d scale(double s) { + this.x *= s; + this.y *= s; + this.z *= s; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d scale(@Nonnull Vector3d p) { + this.x = this.x * p.x; + this.y = this.y * p.y; + this.z = this.z * p.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d cross(@Nonnull Vector3d v) { + double x0 = this.y * v.z - this.z * v.y; + double y0 = this.z * v.x - this.x * v.z; + double z0 = this.x * v.y - this.y * v.x; + return new Vector3d(x0, y0, z0); + } + + @Nonnull + public Vector3d cross(@Nonnull Vector3d v, @Nonnull Vector3d res) { + res.assign(this.y * v.z - this.z * v.y, this.z * v.x - this.x * v.z, this.x * v.y - this.y * v.x); + return this; + } + + public double dot(@Nonnull Vector3d other) { + return this.x * other.x + this.y * other.y + this.z * other.z; + } + + public double distanceTo(@Nonnull Vector3d v) { + return Math.sqrt(this.distanceSquaredTo(v)); + } + + public double distanceTo(@Nonnull Vector3i v) { + return Math.sqrt(this.distanceSquaredTo(v)); + } + + public double distanceTo(double x, double y, double z) { + return Math.sqrt(this.distanceSquaredTo(x, y, z)); + } + + public double distanceSquaredTo(@Nonnull Vector3d v) { + double x0 = v.x - this.x; + double y0 = v.y - this.y; + double z0 = v.z - this.z; + return x0 * x0 + y0 * y0 + z0 * z0; + } + + public double distanceSquaredTo(@Nonnull Vector3i v) { + double x0 = v.x - this.x; + double y0 = v.y - this.y; + double z0 = v.z - this.z; + return x0 * x0 + y0 * y0 + z0 * z0; + } + + public double distanceSquaredTo(double x, double y, double z) { + x -= this.x; + y -= this.y; + z -= this.z; + return x * x + y * y + z * z; + } + + @Nonnull + public Vector3d normalize() { + return this.setLength(1.0); + } + + public double length() { + return Math.sqrt(this.squaredLength()); + } + + public double squaredLength() { + return this.x * this.x + this.y * this.y + this.z * this.z; + } + + @Nonnull + public Vector3d setLength(double newLen) { + return this.scale(newLen / this.length()); + } + + @Nonnull + public Vector3d clampLength(double maxLength) { + double length = this.length(); + return maxLength > length ? this : this.scale(maxLength / length); + } + + @Nonnull + public Vector3d rotateX(float angle) { + double cos = TrigMathUtil.cos(angle); + double sin = TrigMathUtil.sin(angle); + double cy = this.y * cos - this.z * sin; + double cz = this.y * sin + this.z * cos; + this.y = cy; + this.z = cz; + return this; + } + + @Nonnull + public Vector3d rotateY(float angle) { + double cos = TrigMathUtil.cos(angle); + double sin = TrigMathUtil.sin(angle); + double cx = this.x * cos + this.z * sin; + double cz = this.x * -sin + this.z * cos; + this.x = cx; + this.z = cz; + return this; + } + + @Nonnull + public Vector3d rotateZ(float angle) { + double cos = TrigMathUtil.cos(angle); + double sin = TrigMathUtil.sin(angle); + double cx = this.x * cos - this.y * sin; + double cy = this.x * sin + this.y * cos; + this.x = cx; + this.y = cy; + return this; + } + + @Nonnull + public Vector3d floor() { + this.x = MathUtil.fastFloor(this.x); + this.y = MathUtil.fastFloor(this.y); + this.z = MathUtil.fastFloor(this.z); + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d ceil() { + this.x = MathUtil.fastCeil(this.x); + this.y = MathUtil.fastCeil(this.y); + this.z = MathUtil.fastCeil(this.z); + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d clipToZero(double epsilon) { + this.x = MathUtil.clipToZero(this.x, epsilon); + this.y = MathUtil.clipToZero(this.y, epsilon); + this.z = MathUtil.clipToZero(this.z, epsilon); + this.hash = 0; + return this; + } + + public boolean closeToZero(double epsilon) { + return MathUtil.closeToZero(this.x, epsilon) && MathUtil.closeToZero(this.y, epsilon) && MathUtil.closeToZero(this.z, epsilon); + } + + public boolean isInside(int x, int y, int z) { + double dx = this.x - x; + double dy = this.y - y; + double dz = this.z - z; + return dx >= 0.0 && dx < 1.0 && dy >= 0.0 && dy < 1.0 && dz >= 0.0 && dz < 1.0; + } + + public boolean isFinite() { + return Double.isFinite(this.x) && Double.isFinite(this.y) && Double.isFinite(this.z); + } + + @Nonnull + public Vector3d dropHash() { + this.hash = 0; + return this; + } + + @Nonnull + public Vector3d clone() { + return new Vector3d(this.x, this.y, this.z); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Vector3d vector3d = (Vector3d)o; + return vector3d.x == this.x && vector3d.y == this.y && vector3d.z == this.z; + } else { + return false; + } + } + + public boolean equals(@Nullable Vector3d o) { + return o == null ? false : o.x == this.x && o.y == this.y && o.z == this.z; + } + + @Override + public int hashCode() { + if (this.hash == 0) { + this.hash = (int)HashUtil.hash(Double.doubleToLongBits(this.x), Double.doubleToLongBits(this.y), Double.doubleToLongBits(this.z)); + } + + return this.hash; + } + + @Nonnull + @Override + public String toString() { + return "Vector3d{x=" + this.x + ", y=" + this.y + ", z=" + this.z + "}"; + } + + @Nonnull + public static Vector3d max(@Nonnull Vector3d a, @Nonnull Vector3d b) { + return new Vector3d(Math.max(a.x, b.x), Math.max(a.y, b.y), Math.max(a.z, b.z)); + } + + @Nonnull + public static Vector3d min(@Nonnull Vector3d a, @Nonnull Vector3d b) { + return new Vector3d(Math.min(a.x, b.x), Math.min(a.y, b.y), Math.min(a.z, b.z)); + } + + @Nonnull + public static Vector3d lerp(@Nonnull Vector3d a, @Nonnull Vector3d b, double t) { + return lerpUnclamped(a, b, MathUtil.clamp(t, 0.0, 1.0)); + } + + @Nonnull + public static Vector3d lerpUnclamped(@Nonnull Vector3d a, @Nonnull Vector3d b, double t) { + return new Vector3d(a.x + t * (b.x - a.x), a.y + t * (b.y - a.y), a.z + t * (b.z - a.z)); + } + + @Nonnull + public static Vector3d directionTo(@Nonnull Vector3d from, @Nonnull Vector3d to) { + return to.clone().subtract(from).normalize(); + } + + @Nonnull + public static Vector3d directionTo(@Nonnull Vector3i from, @Nonnull Vector3d to) { + return to.clone().subtract(from).normalize(); + } + + @Nonnull + public static Vector3d add(@Nonnull Vector3d one, @Nonnull Vector3d two) { + return new Vector3d().add(one).add(two); + } + + @Nonnull + public static Vector3d add(@Nonnull Vector3d one, @Nonnull Vector3d two, @Nonnull Vector3d three) { + return new Vector3d().add(one).add(two).add(three); + } + + @Nonnull + public static String formatShortString(@Nullable Vector3d v) { + return v == null ? "" : v.x + "/" + v.y + "/" + v.z; + } + + @Nonnull + public Vector3i toVector3i() { + return new Vector3i(MathUtil.floor(this.x), MathUtil.floor(this.y), MathUtil.floor(this.z)); + } + + @Nonnull + public Vector3f toVector3f() { + return new Vector3f((float)this.x, (float)this.y, (float)this.z); + } +} diff --git a/src/com/hypixel/hytale/math/vector/Vector3f.java b/src/com/hypixel/hytale/math/vector/Vector3f.java new file mode 100644 index 0000000..cbcc899 --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/Vector3f.java @@ -0,0 +1,616 @@ +package com.hypixel.hytale.math.vector; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDisplayMode; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; +import java.util.Random; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Vector3f { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(Vector3f.class, () -> new Vector3f(Float.NaN, Float.NaN, Float.NaN)) + .metadata(UIDisplayMode.COMPACT) + .appendInherited(new KeyedCodec<>("X", Codec.FLOAT), (o, i) -> o.x = i, o -> Float.isNaN(o.x) ? null : o.x, (o, p) -> o.x = p.x) + .add() + .appendInherited(new KeyedCodec<>("Y", Codec.FLOAT), (o, i) -> o.y = i, o -> Float.isNaN(o.y) ? null : o.y, (o, p) -> o.y = p.y) + .add() + .appendInherited(new KeyedCodec<>("Z", Codec.FLOAT), (o, i) -> o.z = i, o -> Float.isNaN(o.z) ? null : o.z, (o, p) -> o.z = p.z) + .add() + .build(); + @Nonnull + public static final BuilderCodec ROTATION = BuilderCodec.builder(Vector3f.class, () -> new Vector3f(Float.NaN, Float.NaN, Float.NaN)) + .metadata(UIDisplayMode.COMPACT) + .appendInherited(new KeyedCodec<>("Pitch", Codec.FLOAT), (o, i) -> o.x = i, o -> Float.isNaN(o.x) ? null : o.x, (o, p) -> o.x = p.x) + .add() + .appendInherited(new KeyedCodec<>("Yaw", Codec.FLOAT), (o, i) -> o.y = i, o -> Float.isNaN(o.y) ? null : o.y, (o, p) -> o.y = p.y) + .add() + .appendInherited(new KeyedCodec<>("Roll", Codec.FLOAT), (o, i) -> o.z = i, o -> Float.isNaN(o.z) ? null : o.z, (o, p) -> o.z = p.z) + .add() + .build(); + public static final Vector3f ZERO = new Vector3f(0.0F, 0.0F, 0.0F); + public static final Vector3f UP = new Vector3f(0.0F, 1.0F, 0.0F); + public static final Vector3f POS_Y = UP; + public static final Vector3f DOWN = new Vector3f(0.0F, -1.0F, 0.0F); + public static final Vector3f NEG_Y = DOWN; + public static final Vector3f FORWARD = new Vector3f(0.0F, 0.0F, -1.0F); + public static final Vector3f NEG_Z = FORWARD; + public static final Vector3f NORTH = FORWARD; + public static final Vector3f BACKWARD = new Vector3f(0.0F, 0.0F, 1.0F); + public static final Vector3f POS_Z = BACKWARD; + public static final Vector3f SOUTH = BACKWARD; + public static final Vector3f RIGHT = new Vector3f(1.0F, 0.0F, 0.0F); + public static final Vector3f POS_X = RIGHT; + public static final Vector3f EAST = RIGHT; + public static final Vector3f LEFT = new Vector3f(-1.0F, 0.0F, 0.0F); + public static final Vector3f NEG_X = LEFT; + public static final Vector3f WEST = LEFT; + public static final Vector3f ALL_ONES = new Vector3f(1.0F, 1.0F, 1.0F); + public static final Vector3f MIN = new Vector3f(-Float.MAX_VALUE, -Float.MAX_VALUE, -Float.MAX_VALUE); + public static final Vector3f MAX = new Vector3f(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE); + public static final Vector3f NaN = new Vector3f(Float.NaN, Float.NaN, Float.NaN); + public static final Vector3f[] BLOCK_SIDES = new Vector3f[]{UP, DOWN, FORWARD, BACKWARD, LEFT, RIGHT}; + public static final Vector3f[] BLOCK_EDGES = new Vector3f[]{ + add(UP, FORWARD), + add(DOWN, FORWARD), + add(UP, BACKWARD), + add(DOWN, BACKWARD), + add(UP, LEFT), + add(DOWN, LEFT), + add(UP, RIGHT), + add(DOWN, RIGHT), + add(FORWARD, LEFT), + add(FORWARD, RIGHT), + add(BACKWARD, LEFT), + add(BACKWARD, RIGHT) + }; + public static final Vector3f[] BLOCK_CORNERS = new Vector3f[]{ + add(UP, FORWARD, LEFT), + add(UP, FORWARD, RIGHT), + add(DOWN, FORWARD, LEFT), + add(DOWN, FORWARD, RIGHT), + add(UP, BACKWARD, LEFT), + add(UP, BACKWARD, RIGHT), + add(DOWN, BACKWARD, LEFT), + add(DOWN, BACKWARD, RIGHT) + }; + public static final Vector3f[][] BLOCK_PARTS = new Vector3f[][]{BLOCK_SIDES, BLOCK_EDGES, BLOCK_CORNERS}; + public static final Vector3f[] CARDINAL_DIRECTIONS = new Vector3f[]{NORTH, SOUTH, EAST, WEST}; + public float x; + public float y; + public float z; + private transient int hash; + + public Vector3f() { + this(0.0F, 0.0F, 0.0F); + } + + public Vector3f(@Nonnull Vector3f v) { + this(v.x, v.y, v.z); + } + + public Vector3f(@Nonnull Vector3i v) { + this(v.x, v.y, v.z); + } + + public Vector3f(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + this.hash = 0; + } + + public Vector3f(float yaw, float pitch) { + this(); + this.assign(yaw, pitch); + } + + public Vector3f(@Nonnull Random random, float length) { + this(random.nextFloat() * (float) (Math.PI * 2), random.nextFloat() * (float) (Math.PI * 2)); + this.scale(length); + } + + public float getX() { + return this.x; + } + + public float getPitch() { + return this.x; + } + + public void setX(float x) { + this.x = x; + this.hash = 0; + } + + public void setPitch(float pitch) { + this.x = pitch; + this.hash = 0; + } + + public float getY() { + return this.y; + } + + public float getYaw() { + return this.y; + } + + public void setY(float y) { + this.y = y; + this.hash = 0; + } + + public void setYaw(float yaw) { + this.y = yaw; + this.hash = 0; + } + + public float getZ() { + return this.z; + } + + public float getRoll() { + return this.z; + } + + public void setZ(float z) { + this.z = z; + this.hash = 0; + } + + public void setRoll(float roll) { + this.z = roll; + this.hash = 0; + } + + @Nonnull + public Vector3f assign(@Nonnull Vector3f v) { + this.x = v.x; + this.y = v.y; + this.z = v.z; + this.hash = v.hash; + return this; + } + + @Nonnull + public Vector3f assign(float v) { + this.x = v; + this.y = v; + this.z = v; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3f assign(@Nonnull float[] v) { + this.x = v[0]; + this.y = v[1]; + this.z = v[2]; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3f assign(float yaw, float pitch) { + float len = TrigMathUtil.cos(pitch); + float x = len * -TrigMathUtil.sin(yaw); + float y = TrigMathUtil.sin(pitch); + float z = len * -TrigMathUtil.cos(yaw); + return this.assign(x, y, z); + } + + @Nonnull + public Vector3f assign(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3f add(@Nonnull Vector3f v) { + this.x = this.x + v.x; + this.y = this.y + v.y; + this.z = this.z + v.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3f add(@Nonnull Vector3i v) { + this.x = this.x + v.x; + this.y = this.y + v.y; + this.z = this.z + v.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3f add(float x, float y, float z) { + this.x += x; + this.y += y; + this.z += z; + this.hash = 0; + return this; + } + + public void addPitch(float pitch) { + this.x += pitch; + this.hash = 0; + } + + public void addYaw(float yaw) { + this.y += yaw; + this.hash = 0; + } + + public void addRoll(float roll) { + this.z += roll; + this.hash = 0; + } + + @Nonnull + public Vector3f addScaled(@Nonnull Vector3f v, float s) { + this.x = this.x + v.x * s; + this.y = this.y + v.y * s; + this.z = this.z + v.z * s; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3f subtract(@Nonnull Vector3f v) { + this.x = this.x - v.x; + this.y = this.y - v.y; + this.z = this.z - v.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3f subtract(@Nonnull Vector3i v) { + this.x = this.x - v.x; + this.y = this.y - v.y; + this.z = this.z - v.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3f subtract(float x, float y, float z) { + this.x -= x; + this.y -= y; + this.z -= z; + this.hash = 0; + return this; + } + + public void addRotationOnAxis(@Nonnull Axis axis, int angle) { + float rad = (float) (Math.PI / 180.0) * angle; + switch (axis) { + case X: + this.setPitch(this.getPitch() + rad); + break; + case Y: + this.setYaw(this.getYaw() + rad); + break; + case Z: + this.setRoll(this.getRoll() + rad); + } + } + + public void flipRotationOnAxis(@Nonnull Axis axis) { + this.addRotationOnAxis(axis, 180); + } + + @Nonnull + public Vector3f negate() { + this.x = -this.x; + this.y = -this.y; + this.z = -this.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3f scale(float s) { + this.x *= s; + this.y *= s; + this.z *= s; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3f scale(@Nonnull Vector3f p) { + this.x = this.x * p.x; + this.y = this.y * p.y; + this.z = this.z * p.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3f cross(@Nonnull Vector3f v) { + float x0 = this.y * v.z - this.z * v.y; + float y0 = this.z * v.x - this.x * v.z; + float z0 = this.x * v.y - this.y * v.x; + return new Vector3f(x0, y0, z0); + } + + @Nonnull + public Vector3f cross(@Nonnull Vector3f v, @Nonnull Vector3f res) { + res.assign(this.y * v.z - this.z * v.y, this.z * v.x - this.x * v.z, this.x * v.y - this.y * v.x); + return this; + } + + public float dot(@Nonnull Vector3f other) { + return this.x * other.x + this.y * other.y + this.z * other.z; + } + + public float distanceTo(@Nonnull Vector3f v) { + return (float)Math.sqrt(this.distanceSquaredTo(v)); + } + + public float distanceTo(@Nonnull Vector3i v) { + return (float)Math.sqrt(this.distanceSquaredTo(v)); + } + + public float distanceTo(float x, float y, float z) { + return (float)Math.sqrt(this.distanceSquaredTo(x, y, z)); + } + + public float distanceSquaredTo(@Nonnull Vector3f v) { + float x0 = v.x - this.x; + float y0 = v.y - this.y; + float z0 = v.z - this.z; + return x0 * x0 + y0 * y0 + z0 * z0; + } + + public float distanceSquaredTo(@Nonnull Vector3i v) { + float x0 = v.x - this.x; + float y0 = v.y - this.y; + float z0 = v.z - this.z; + return x0 * x0 + y0 * y0 + z0 * z0; + } + + public float distanceSquaredTo(float x, float y, float z) { + float dx = x - this.x; + float dy = y - this.y; + float dz = z - this.z; + return dx * dx + dy * dy + dz * dz; + } + + @Nonnull + public Vector3f normalize() { + return this.setLength(1.0F); + } + + public float length() { + return (float)Math.sqrt(this.squaredLength()); + } + + public float squaredLength() { + return this.x * this.x + this.y * this.y + this.z * this.z; + } + + @Nonnull + public Vector3f setLength(float newLen) { + return this.scale(newLen / this.length()); + } + + @Nonnull + public Vector3f clampLength(float maxLength) { + float length = this.length(); + return maxLength > length ? this : this.scale(maxLength / length); + } + + @Nonnull + public Vector3f rotateX(float angle) { + float cos = TrigMathUtil.cos(angle); + float sin = TrigMathUtil.sin(angle); + float cy = this.y * cos - this.z * sin; + float cz = this.y * sin + this.z * cos; + this.y = cy; + this.z = cz; + return this; + } + + @Nonnull + public Vector3f rotateY(float angle) { + float cos = TrigMathUtil.cos(angle); + float sin = TrigMathUtil.sin(angle); + float cx = this.x * cos + this.z * sin; + float cz = this.x * -sin + this.z * cos; + this.x = cx; + this.z = cz; + return this; + } + + @Nonnull + public Vector3f rotateZ(float angle) { + float cos = TrigMathUtil.cos(angle); + float sin = TrigMathUtil.sin(angle); + float cx = this.x * cos - this.y * sin; + float cy = this.x * sin + this.y * cos; + this.x = cx; + this.y = cy; + return this; + } + + @Nonnull + public Vector3f floor() { + this.x = MathUtil.fastFloor(this.x); + this.y = MathUtil.fastFloor(this.y); + this.z = MathUtil.fastFloor(this.z); + this.hash = 0; + return this; + } + + @Nonnull + public Vector3f ceil() { + this.x = MathUtil.fastCeil(this.x); + this.y = MathUtil.fastCeil(this.y); + this.z = MathUtil.fastCeil(this.z); + this.hash = 0; + return this; + } + + @Nonnull + public Vector3f clipToZero(float epsilon) { + this.x = MathUtil.clipToZero(this.x, epsilon); + this.y = MathUtil.clipToZero(this.y, epsilon); + this.z = MathUtil.clipToZero(this.z, epsilon); + this.hash = 0; + return this; + } + + public boolean closeToZero(float epsilon) { + return MathUtil.closeToZero(this.x, epsilon) && MathUtil.closeToZero(this.y, epsilon) && MathUtil.closeToZero(this.z, epsilon); + } + + public boolean isInside(int x, int y, int z) { + float dx = this.x - x; + float dy = this.y - y; + float dz = this.z - z; + return dx >= 0.0F && dx < 1.0F && dy >= 0.0F && dy < 1.0F && dz >= 0.0F && dz < 1.0F; + } + + public boolean isFinite() { + return Float.isFinite(this.x) && Float.isFinite(this.y) && Float.isFinite(this.z); + } + + @Nonnull + public Vector3f dropHash() { + this.hash = 0; + return this; + } + + @Nonnull + public Vector3f clone() { + return new Vector3f(this.x, this.y, this.z); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Vector3f vector3f = (Vector3f)o; + return vector3f.x == this.x && vector3f.y == this.y && vector3f.z == this.z + ? true + : Float.isNaN(vector3f.x) + && Float.isNaN(this.x) + && Float.isNaN(vector3f.y) + && Float.isNaN(this.y) + && Float.isNaN(vector3f.z) + && Float.isNaN(this.z); + } else { + return false; + } + } + + public boolean equals(@Nullable Vector3f o) { + if (o == null) { + return false; + } else { + return o.x == this.x && o.y == this.y && o.z == this.z + ? true + : Float.isNaN(o.x) && Float.isNaN(this.x) && Float.isNaN(o.y) && Float.isNaN(this.y) && Float.isNaN(o.z) && Float.isNaN(this.z); + } + } + + @Override + public int hashCode() { + if (this.hash == 0) { + this.hash = (int)HashUtil.hash(Float.floatToIntBits(this.x), Float.floatToIntBits(this.y), Float.floatToIntBits(this.z)); + } + + return this.hash; + } + + @Nonnull + @Override + public String toString() { + return "Vector3f{x=" + this.x + ", y=" + this.y + ", z=" + this.z + "}"; + } + + @Nonnull + public Vector3d toVector3d() { + return new Vector3d(this.x, this.y, this.z); + } + + @Nonnull + public static Vector3f max(@Nonnull Vector3f a, @Nonnull Vector3f b) { + return new Vector3f(Math.max(a.x, b.x), Math.max(a.y, b.y), Math.max(a.z, b.z)); + } + + @Nonnull + public static Vector3f min(@Nonnull Vector3f a, @Nonnull Vector3f b) { + return new Vector3f(Math.min(a.x, b.x), Math.min(a.y, b.y), Math.min(a.z, b.z)); + } + + @Nonnull + public static Vector3f lerp(@Nonnull Vector3f a, @Nonnull Vector3f b, float t) { + return lerpUnclamped(a, b, MathUtil.clamp(t, 0.0F, 1.0F)); + } + + @Nonnull + public static Vector3f lerpUnclamped(@Nonnull Vector3f a, @Nonnull Vector3f b, float t) { + return new Vector3f(a.x + t * (b.x - a.x), a.y + t * (b.y - a.y), a.z + t * (b.z - a.z)); + } + + @Nonnull + public static Vector3f lerpAngle(@Nonnull Vector3f a, @Nonnull Vector3f b, float t) { + return lerpAngle(a, b, t, new Vector3f()); + } + + @Nonnull + public static Vector3f lerpAngle(@Nonnull Vector3f a, @Nonnull Vector3f b, float t, @Nonnull Vector3f target) { + target.assign(MathUtil.lerpAngle(a.x, b.x, t), MathUtil.lerpAngle(a.y, b.y, t), MathUtil.lerpAngle(a.z, b.z, t)); + return target; + } + + @Nonnull + public static Vector3f directionTo(@Nonnull Vector3f from, @Nonnull Vector3f to) { + return to.clone().subtract(from).normalize(); + } + + @Nonnull + public static Vector3f add(@Nonnull Vector3f one, @Nonnull Vector3f two) { + return new Vector3f().add(one).add(two); + } + + @Nonnull + public static Vector3f add(@Nonnull Vector3f one, @Nonnull Vector3f two, @Nonnull Vector3f three) { + return new Vector3f().add(one).add(two).add(three); + } + + @Nonnull + public static Vector3f lookAt(@Nonnull Vector3d relative) { + return lookAt(relative, new Vector3f()); + } + + @Nonnull + public static Vector3f lookAt(@Nonnull Vector3d relative, @Nonnull Vector3f result) { + if (!MathUtil.closeToZero(relative.x) || !MathUtil.closeToZero(relative.z)) { + float yaw = TrigMathUtil.atan2((float)(-relative.x), (float)(-relative.z)); + result.setY(MathUtil.wrapAngle(yaw)); + } + + double length = relative.squaredLength(); + if (length > 0.0) { + float pitch = (float) (Math.PI / 2) - (float)Math.acos(relative.y / Math.sqrt(length)); + result.setX(MathUtil.clamp(pitch, (float) (-Math.PI / 2) + MathUtil.PITCH_EDGE_PADDING, (float) (Math.PI / 2) - MathUtil.PITCH_EDGE_PADDING)); + } + + return result; + } +} diff --git a/src/com/hypixel/hytale/math/vector/Vector3i.java b/src/com/hypixel/hytale/math/vector/Vector3i.java new file mode 100644 index 0000000..a440a01 --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/Vector3i.java @@ -0,0 +1,378 @@ +package com.hypixel.hytale.math.vector; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDisplayMode; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.util.HashUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Vector3i { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(Vector3i.class, Vector3i::new) + .metadata(UIDisplayMode.COMPACT) + .appendInherited(new KeyedCodec<>("X", Codec.INTEGER), (o, i) -> o.x = i, o -> o.x, (o, p) -> o.x = p.x) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Y", Codec.INTEGER), (o, i) -> o.y = i, o -> o.y, (o, p) -> o.y = p.y) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Z", Codec.INTEGER), (o, i) -> o.z = i, o -> o.z, (o, p) -> o.z = p.z) + .addValidator(Validators.nonNull()) + .add() + .build(); + public static final Vector3i ZERO = new Vector3i(0, 0, 0); + public static final Vector3i UP = new Vector3i(0, 1, 0); + public static final Vector3i POS_Y = UP; + public static final Vector3i DOWN = new Vector3i(0, -1, 0); + public static final Vector3i NEG_Y = DOWN; + public static final Vector3i FORWARD = new Vector3i(0, 0, -1); + public static final Vector3i NEG_Z = FORWARD; + public static final Vector3i NORTH = FORWARD; + public static final Vector3i BACKWARD = new Vector3i(0, 0, 1); + public static final Vector3i POS_Z = BACKWARD; + public static final Vector3i SOUTH = BACKWARD; + public static final Vector3i RIGHT = new Vector3i(1, 0, 0); + public static final Vector3i POS_X = RIGHT; + public static final Vector3i EAST = RIGHT; + public static final Vector3i LEFT = new Vector3i(-1, 0, 0); + public static final Vector3i NEG_X = LEFT; + public static final Vector3i WEST = LEFT; + public static final Vector3i ALL_ONES = new Vector3i(1, 1, 1); + public static final Vector3i MIN = new Vector3i(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE); + public static final Vector3i MAX = new Vector3i(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); + public static final Vector3i[] BLOCK_SIDES = new Vector3i[]{UP, DOWN, FORWARD, BACKWARD, LEFT, RIGHT}; + public static final Vector3i[] BLOCK_EDGES = new Vector3i[]{ + add(UP, FORWARD), + add(DOWN, FORWARD), + add(UP, BACKWARD), + add(DOWN, BACKWARD), + add(UP, LEFT), + add(DOWN, LEFT), + add(UP, RIGHT), + add(DOWN, RIGHT), + add(FORWARD, LEFT), + add(FORWARD, RIGHT), + add(BACKWARD, LEFT), + add(BACKWARD, RIGHT) + }; + public static final Vector3i[] BLOCK_CORNERS = new Vector3i[]{ + add(UP, FORWARD, LEFT), + add(UP, FORWARD, RIGHT), + add(DOWN, FORWARD, LEFT), + add(DOWN, FORWARD, RIGHT), + add(UP, BACKWARD, LEFT), + add(UP, BACKWARD, RIGHT), + add(DOWN, BACKWARD, LEFT), + add(DOWN, BACKWARD, RIGHT) + }; + public static final Vector3i[][] BLOCK_PARTS = new Vector3i[][]{BLOCK_SIDES, BLOCK_EDGES, BLOCK_CORNERS}; + public static final Vector3i[] CARDINAL_DIRECTIONS = new Vector3i[]{NORTH, SOUTH, EAST, WEST}; + public int x; + public int y; + public int z; + private transient int hash; + + public Vector3i() { + this(0, 0, 0); + } + + public Vector3i(@Nonnull Vector3i v) { + this(v.x, v.y, v.z); + } + + public Vector3i(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + this.hash = 0; + } + + public int getX() { + return this.x; + } + + public void setX(int x) { + this.x = x; + this.hash = 0; + } + + public int getY() { + return this.y; + } + + public void setY(int y) { + this.y = y; + this.hash = 0; + } + + public int getZ() { + return this.z; + } + + public void setZ(int z) { + this.z = z; + this.hash = 0; + } + + @Nonnull + public Vector3i assign(@Nonnull Vector3i v) { + this.x = v.x; + this.y = v.y; + this.z = v.z; + this.hash = v.hash; + return this; + } + + @Nonnull + public Vector3i assign(int v) { + this.x = v; + this.y = v; + this.z = v; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3i assign(@Nonnull int[] v) { + this.x = v[0]; + this.y = v[1]; + this.z = v[2]; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3i assign(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3i add(@Nonnull Vector3i v) { + this.x = this.x + v.x; + this.y = this.y + v.y; + this.z = this.z + v.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3i add(int x, int y, int z) { + this.x += x; + this.y += y; + this.z += z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3i addScaled(@Nonnull Vector3i v, int s) { + this.x = this.x + v.x * s; + this.y = this.y + v.y * s; + this.z = this.z + v.z * s; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3i subtract(@Nonnull Vector3i v) { + this.x = this.x - v.x; + this.y = this.y - v.y; + this.z = this.z - v.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3i subtract(int x, int y, int z) { + this.x -= x; + this.y -= y; + this.z -= z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3i negate() { + this.x = -this.x; + this.y = -this.y; + this.z = -this.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3i scale(int s) { + this.x *= s; + this.y *= s; + this.z *= s; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3i scale(double s) { + this.x = (int)(this.x * s); + this.y = (int)(this.y * s); + this.z = (int)(this.z * s); + this.hash = 0; + return this; + } + + @Nonnull + public Vector3i scale(@Nonnull Vector3i p) { + this.x = this.x * p.x; + this.y = this.y * p.y; + this.z = this.z * p.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3i cross(@Nonnull Vector3i v) { + int x0 = this.y * v.z - this.z * v.y; + int y0 = this.z * v.x - this.x * v.z; + int z0 = this.x * v.y - this.y * v.x; + return new Vector3i(x0, y0, z0); + } + + @Nonnull + public Vector3i cross(@Nonnull Vector3i v, @Nonnull Vector3i res) { + res.assign(this.y * v.z - this.z * v.y, this.z * v.x - this.x * v.z, this.x * v.y - this.y * v.x); + return this; + } + + public int dot(@Nonnull Vector3i other) { + return this.x * other.x + this.y * other.y + this.z * other.z; + } + + public double distanceTo(@Nonnull Vector3i v) { + return Math.sqrt(this.distanceSquaredTo(v)); + } + + public double distanceTo(int x, int y, int z) { + return Math.sqrt(this.distanceSquaredTo(x, y, z)); + } + + public int distanceSquaredTo(@Nonnull Vector3i v) { + int x0 = v.x - this.x; + int y0 = v.y - this.y; + int z0 = v.z - this.z; + return x0 * x0 + y0 * y0 + z0 * z0; + } + + public int distanceSquaredTo(int x, int y, int z) { + int dx = x - this.x; + int dy = y - this.y; + int dz = z - this.z; + return dx * dx + dy * dy + dz * dz; + } + + @Nonnull + public Vector3i normalize() { + return this.setLength(1); + } + + public double length() { + return Math.sqrt(this.squaredLength()); + } + + public int squaredLength() { + return this.x * this.x + this.y * this.y + this.z * this.z; + } + + @Nonnull + public Vector3i setLength(int newLen) { + return this.scale(newLen / this.length()); + } + + @Nonnull + public Vector3i clampLength(int maxLength) { + double length = this.length(); + return maxLength > length ? this : this.scale(maxLength / length); + } + + @Nonnull + public Vector3i dropHash() { + this.hash = 0; + return this; + } + + @Nonnull + public Vector3i clone() { + return new Vector3i(this.x, this.y, this.z); + } + + @Nonnull + public Vector3d toVector3d() { + return new Vector3d(this.x, this.y, this.z); + } + + @Nonnull + public Vector3f toVector3f() { + return new Vector3f(this.x, this.y, this.z); + } + + @Nonnull + public Vector3l toVector3l() { + return new Vector3l(this.x, this.y, this.z); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Vector3i vector3i = (Vector3i)o; + return vector3i.x == this.x && vector3i.y == this.y && vector3i.z == this.z; + } else { + return false; + } + } + + @Override + public int hashCode() { + if (this.hash == 0) { + this.hash = (int)HashUtil.hash(this.x, this.y, this.z); + } + + return this.hash; + } + + @Nonnull + @Override + public String toString() { + return "Vector3i{x=" + this.x + ", y=" + this.y + ", z=" + this.z + "}"; + } + + @Nonnull + public static Vector3i max(@Nonnull Vector3i a, @Nonnull Vector3i b) { + return new Vector3i(Math.max(a.x, b.x), Math.max(a.y, b.y), Math.max(a.z, b.z)); + } + + @Nonnull + public static Vector3i min(@Nonnull Vector3i a, @Nonnull Vector3i b) { + return new Vector3i(Math.min(a.x, b.x), Math.min(a.y, b.y), Math.min(a.z, b.z)); + } + + @Nonnull + public static Vector3i directionTo(@Nonnull Vector3i from, @Nonnull Vector3i to) { + return to.clone().subtract(from).normalize(); + } + + @Nonnull + public static Vector3i add(@Nonnull Vector3i one, @Nonnull Vector3i two) { + return new Vector3i().add(one).add(two); + } + + @Nonnull + public static Vector3i add(@Nonnull Vector3i one, @Nonnull Vector3i two, @Nonnull Vector3i three) { + return new Vector3i().add(one).add(two).add(three); + } +} diff --git a/src/com/hypixel/hytale/math/vector/Vector3l.java b/src/com/hypixel/hytale/math/vector/Vector3l.java new file mode 100644 index 0000000..f534938 --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/Vector3l.java @@ -0,0 +1,374 @@ +package com.hypixel.hytale.math.vector; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDisplayMode; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.math.util.MathUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Vector3l { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(Vector3l.class, Vector3l::new) + .metadata(UIDisplayMode.COMPACT) + .appendInherited(new KeyedCodec<>("X", Codec.LONG), (o, i) -> o.x = i, o -> o.x, (o, p) -> o.x = p.x) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Y", Codec.LONG), (o, i) -> o.y = i, o -> o.y, (o, p) -> o.y = p.y) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Z", Codec.LONG), (o, i) -> o.z = i, o -> o.z, (o, p) -> o.z = p.z) + .addValidator(Validators.nonNull()) + .add() + .build(); + public static final Vector3l ZERO = new Vector3l(0L, 0L, 0L); + public static final Vector3l UP = new Vector3l(0L, 1L, 0L); + public static final Vector3l POS_Y = UP; + public static final Vector3l DOWN = new Vector3l(0L, -1L, 0L); + public static final Vector3l NEG_Y = DOWN; + public static final Vector3l FORWARD = new Vector3l(0L, 0L, -1L); + public static final Vector3l NEG_Z = FORWARD; + public static final Vector3l NORTH = FORWARD; + public static final Vector3l BACKWARD = new Vector3l(0L, 0L, 1L); + public static final Vector3l POS_Z = BACKWARD; + public static final Vector3l SOUTH = BACKWARD; + public static final Vector3l RIGHT = new Vector3l(1L, 0L, 0L); + public static final Vector3l POS_X = RIGHT; + public static final Vector3l EAST = RIGHT; + public static final Vector3l LEFT = new Vector3l(-1L, 0L, 0L); + public static final Vector3l NEG_X = LEFT; + public static final Vector3l WEST = LEFT; + public static final Vector3l ALL_ONES = new Vector3l(1L, 1L, 1L); + public static final Vector3l MIN = new Vector3l(Long.MIN_VALUE, Long.MIN_VALUE, Long.MIN_VALUE); + public static final Vector3l MAX = new Vector3l(Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE); + public static final Vector3l[] BLOCK_SIDES = new Vector3l[]{UP, DOWN, FORWARD, BACKWARD, LEFT, RIGHT}; + public static final Vector3l[] BLOCK_EDGES = new Vector3l[]{ + add(UP, FORWARD), + add(DOWN, FORWARD), + add(UP, BACKWARD), + add(DOWN, BACKWARD), + add(UP, LEFT), + add(DOWN, LEFT), + add(UP, RIGHT), + add(DOWN, RIGHT), + add(FORWARD, LEFT), + add(FORWARD, RIGHT), + add(BACKWARD, LEFT), + add(BACKWARD, RIGHT) + }; + public static final Vector3l[] BLOCK_CORNERS = new Vector3l[]{ + add(UP, FORWARD, LEFT), + add(UP, FORWARD, RIGHT), + add(DOWN, FORWARD, LEFT), + add(DOWN, FORWARD, RIGHT), + add(UP, BACKWARD, LEFT), + add(UP, BACKWARD, RIGHT), + add(DOWN, BACKWARD, LEFT), + add(DOWN, BACKWARD, RIGHT) + }; + public static final Vector3l[][] BLOCK_PARTS = new Vector3l[][]{BLOCK_SIDES, BLOCK_EDGES, BLOCK_CORNERS}; + public static final Vector3l[] CARDINAL_DIRECTIONS = new Vector3l[]{NORTH, SOUTH, EAST, WEST}; + public long x; + public long y; + public long z; + private transient int hash; + + public Vector3l() { + this(0L, 0L, 0L); + } + + public Vector3l(@Nonnull Vector3l v) { + this(v.x, v.y, v.z); + } + + public Vector3l(long x, long y, long z) { + this.x = x; + this.y = y; + this.z = z; + this.hash = 0; + } + + public long getX() { + return this.x; + } + + public void setX(long x) { + this.x = x; + this.hash = 0; + } + + public long getY() { + return this.y; + } + + public void setY(long y) { + this.y = y; + this.hash = 0; + } + + public long getZ() { + return this.z; + } + + public void setZ(long z) { + this.z = z; + this.hash = 0; + } + + @Nonnull + public Vector3l assign(@Nonnull Vector3l v) { + this.x = v.x; + this.y = v.y; + this.z = v.z; + this.hash = v.hash; + return this; + } + + @Nonnull + public Vector3l assign(long v) { + this.x = v; + this.y = v; + this.z = v; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3l assign(@Nonnull long[] v) { + this.x = v[0]; + this.y = v[1]; + this.z = v[2]; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3l assign(long x, long y, long z) { + this.x = x; + this.y = y; + this.z = z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3l add(@Nonnull Vector3l v) { + this.x = this.x + v.x; + this.y = this.y + v.y; + this.z = this.z + v.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3l add(long x, long y, long z) { + this.x += x; + this.y += y; + this.z += z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3l addScaled(@Nonnull Vector3l v, long s) { + this.x = this.x + v.x * s; + this.y = this.y + v.y * s; + this.z = this.z + v.z * s; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3l subtract(@Nonnull Vector3l v) { + this.x = this.x - v.x; + this.y = this.y - v.y; + this.z = this.z - v.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3l subtract(long x, long y, long z) { + this.x -= x; + this.y -= y; + this.z -= z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3l negate() { + this.x = -this.x; + this.y = -this.y; + this.z = -this.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3l scale(long s) { + this.x *= s; + this.y *= s; + this.z *= s; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3l scale(double s) { + this.x = (long)(this.x * s); + this.y = (long)(this.y * s); + this.z = (long)(this.z * s); + this.hash = 0; + return this; + } + + @Nonnull + public Vector3l scale(@Nonnull Vector3l p) { + this.x = this.x * p.x; + this.y = this.y * p.y; + this.z = this.z * p.z; + this.hash = 0; + return this; + } + + @Nonnull + public Vector3l cross(@Nonnull Vector3l v) { + long x0 = this.y * v.z - this.z * v.y; + long y0 = this.z * v.x - this.x * v.z; + long z0 = this.x * v.y - this.y * v.x; + return new Vector3l(x0, y0, z0); + } + + @Nonnull + public Vector3l cross(@Nonnull Vector3l v, @Nonnull Vector3l res) { + res.assign(this.y * v.z - this.z * v.y, this.z * v.x - this.x * v.z, this.x * v.y - this.y * v.x); + return this; + } + + public long dot(@Nonnull Vector3l other) { + return this.x * other.x + this.y * other.y + this.z * other.z; + } + + public double distanceTo(@Nonnull Vector3l v) { + return Math.sqrt(this.distanceSquaredTo(v)); + } + + public double distanceTo(long x, long y, long z) { + return Math.sqrt(this.distanceSquaredTo(x, y, z)); + } + + public long distanceSquaredTo(@Nonnull Vector3l v) { + long x0 = v.x - this.x; + long y0 = v.y - this.y; + long z0 = v.z - this.z; + return x0 * x0 + y0 * y0 + z0 * z0; + } + + public long distanceSquaredTo(long x, long y, long z) { + long dx = x - this.x; + long dy = y - this.y; + long dz = z - this.z; + return dx * dx + dy * dy + dz * dz; + } + + @Nonnull + public Vector3l normalize() { + return this.setLength(1L); + } + + public double length() { + return Math.sqrt(this.squaredLength()); + } + + public long squaredLength() { + return this.x * this.x + this.y * this.y + this.z * this.z; + } + + @Nonnull + public Vector3l setLength(long newLen) { + return this.scale(newLen / this.length()); + } + + @Nonnull + public Vector3l clampLength(long maxLength) { + double length = this.length(); + return maxLength > length ? this : this.scale(maxLength / length); + } + + @Nonnull + public Vector3l dropHash() { + this.hash = 0; + return this; + } + + @Nonnull + public Vector3l clone() { + return new Vector3l(this.x, this.y, this.z); + } + + @Nonnull + public Vector3i toVector3i() { + return new Vector3i(MathUtil.floor(this.x), MathUtil.floor(this.y), MathUtil.floor(this.z)); + } + + @Nonnull + public Vector3d toVector3d() { + return new Vector3d(this.x, this.y, this.z); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Vector3l vector3l = (Vector3l)o; + return vector3l.x == this.x && vector3l.y == this.y && vector3l.z == this.z; + } else { + return false; + } + } + + @Override + public int hashCode() { + if (this.hash == 0) { + this.hash = (int)HashUtil.hash(this.x, this.y, this.z); + } + + return this.hash; + } + + @Nonnull + @Override + public String toString() { + return "Vector3l{x=" + this.x + ", y=" + this.y + ", z=" + this.z + "}"; + } + + @Nonnull + public static Vector3l max(@Nonnull Vector3l a, @Nonnull Vector3l b) { + return new Vector3l(Math.max(a.x, b.x), Math.max(a.y, b.y), Math.max(a.z, b.z)); + } + + @Nonnull + public static Vector3l min(@Nonnull Vector3l a, @Nonnull Vector3l b) { + return new Vector3l(Math.min(a.x, b.x), Math.min(a.y, b.y), Math.min(a.z, b.z)); + } + + @Nonnull + public static Vector3l directionTo(@Nonnull Vector3l from, @Nonnull Vector3l to) { + return to.clone().subtract(from).normalize(); + } + + @Nonnull + public static Vector3l add(@Nonnull Vector3l one, @Nonnull Vector3l two) { + return new Vector3l().add(one).add(two); + } + + @Nonnull + public static Vector3l add(@Nonnull Vector3l one, @Nonnull Vector3l two, @Nonnull Vector3l three) { + return new Vector3l().add(one).add(two).add(three); + } +} diff --git a/src/com/hypixel/hytale/math/vector/Vector4d.java b/src/com/hypixel/hytale/math/vector/Vector4d.java new file mode 100644 index 0000000..b61c400 --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/Vector4d.java @@ -0,0 +1,105 @@ +package com.hypixel.hytale.math.vector; + +import javax.annotation.Nonnull; + +public class Vector4d { + public static final int COMPONENT_X = 0; + public static final int COMPONENT_Y = 1; + public static final int COMPONENT_Z = 2; + public static final int COMPONENT_W = 3; + public double x; + public double y; + public double z; + public double w; + + public Vector4d() { + this(0.0, 0.0, 0.0, 0.0); + } + + public Vector4d(double x, double y, double z, double w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + @Nonnull + public static Vector4d newPosition(double x, double y, double z) { + return new Vector4d(x, y, z, 1.0); + } + + @Nonnull + public static Vector4d newPosition(@Nonnull Vector3d v) { + return new Vector4d(v.x, v.y, v.z, 1.0); + } + + @Nonnull + public static Vector4d newDirection(double x, double y, double z) { + return new Vector4d(x, y, z, 0.0); + } + + @Nonnull + public Vector4d setDirection() { + this.w = 0.0; + return this; + } + + @Nonnull + public Vector4d setPosition() { + this.w = 1.0; + return this; + } + + @Nonnull + public Vector4d assign(@Nonnull Vector4d v) { + return this.assign(v.x, v.y, v.z, v.w); + } + + @Nonnull + public Vector4d assign(double x, double y, double z, double w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + return this; + } + + @Nonnull + public Vector4d lerp(@Nonnull Vector4d dest, double lerpFactor, @Nonnull Vector4d target) { + target.assign( + (dest.x - this.x) * lerpFactor + this.x, + (dest.y - this.y) * lerpFactor + this.y, + (dest.z - this.z) * lerpFactor + this.z, + (dest.w - this.w) * lerpFactor + this.w + ); + return target; + } + + public void perspectiveTransform() { + double invW = 1.0 / this.w; + this.x *= invW; + this.y *= invW; + this.z *= invW; + this.w = 1.0; + } + + public boolean isInsideFrustum() { + return Math.abs(this.x) <= Math.abs(this.w) && Math.abs(this.y) <= Math.abs(this.w) && Math.abs(this.z) <= Math.abs(this.w); + } + + public double get(int component) { + return switch (component) { + case 0 -> this.x; + case 1 -> this.y; + case 2 -> this.z; + case 3 -> this.w; + default -> throw new IllegalArgumentException("Invalid component: " + component); + }; + } + + @Nonnull + @Override + public String toString() { + return "Vector4d{x=" + this.x + ", y=" + this.y + ", z=" + this.z + ", w=" + this.w + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/vector/VectorBoxUtil.java b/src/com/hypixel/hytale/math/vector/VectorBoxUtil.java new file mode 100644 index 0000000..d4aa054 --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/VectorBoxUtil.java @@ -0,0 +1,430 @@ +package com.hypixel.hytale.math.vector; + +import com.hypixel.fastutil.FastCollection; +import com.hypixel.hytale.function.consumer.IntBiObjectConsumer; +import com.hypixel.hytale.function.consumer.IntObjectConsumer; +import com.hypixel.hytale.function.consumer.IntTriObjectConsumer; +import com.hypixel.hytale.function.consumer.TriConsumer; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public class VectorBoxUtil { + public VectorBoxUtil() { + } + + public static void forEachVector(Iterable vectors, double originX, double originY, double originZ, double apothem, Consumer consumer) { + forEachVector(vectors, originX, originY, originZ, apothem, apothem, apothem, consumer); + } + + public static void forEachVector( + Iterable vectors, + double originX, + double originY, + double originZ, + double apothemX, + double apothemY, + double apothemZ, + Consumer consumer + ) { + forEachVector(vectors, Function.identity(), originX, originY, originZ, apothemX, apothemY, apothemZ, consumer); + } + + public static void forEachVector( + Iterable vectors, + double originX, + double originY, + double originZ, + double apothemXMin, + double apothemYMin, + double apothemZMin, + double apothemXMax, + double apothemYMax, + double apothemZMax, + Consumer consumer + ) { + forEachVector( + vectors, Function.identity(), originX, originY, originZ, apothemXMin, apothemYMin, apothemZMin, apothemXMax, apothemYMax, apothemZMax, consumer + ); + } + + public static void forEachVector( + Iterable input, @Nonnull Function func, double originX, double originY, double originZ, double apothem, Consumer consumer + ) { + forEachVector(input, func, originX, originY, originZ, apothem, apothem, apothem, consumer); + } + + public static void forEachVector( + Iterable input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double apothemX, + double apothemY, + double apothemZ, + Consumer consumer + ) { + forEachVector(input, func, originX, originY, originZ, -apothemX, -apothemY, -apothemZ, apothemX, apothemY, apothemZ, consumer); + } + + public static void forEachVector( + Iterable input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double apothemXMin, + double apothemYMin, + double apothemZMin, + double apothemXMax, + double apothemYMax, + double apothemZMax, + Consumer consumer + ) { + forEachVector( + input, + func, + originX, + originY, + originZ, + apothemXMin, + apothemYMin, + apothemZMin, + apothemXMax, + apothemYMax, + apothemZMax, + (t, c, n0) -> c.accept(t), + consumer, + null + ); + } + + public static void forEachVector( + Iterable input, @Nonnull Function func, double originX, double originY, double originZ, double apothem, BiConsumer consumer, V objV + ) { + forEachVector(input, func, originX, originY, originZ, apothem, apothem, apothem, consumer, objV); + } + + public static void forEachVector( + Iterable input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double apothemX, + double apothemY, + double apothemZ, + BiConsumer consumer, + V objV + ) { + forEachVector(input, func, originX, originY, originZ, -apothemX, -apothemY, -apothemZ, apothemX, apothemY, apothemZ, consumer, objV); + } + + public static void forEachVector( + Iterable input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double apothemXMin, + double apothemYMin, + double apothemZMin, + double apothemXMax, + double apothemYMax, + double apothemZMax, + BiConsumer consumer, + V objV + ) { + forEachVector( + input, + func, + originX, + originY, + originZ, + apothemXMin, + apothemYMin, + apothemZMin, + apothemXMax, + apothemYMax, + apothemZMax, + (t, objV1, c) -> c.accept(t, objV1), + objV, + consumer + ); + } + + public static void forEachVector( + Iterable input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double apothem, + @Nonnull TriConsumer consumer, + V1 objV1, + V2 objV2 + ) { + forEachVector(input, func, originX, originY, originZ, apothem, apothem, apothem, consumer, objV1, objV2); + } + + public static void forEachVector( + Iterable input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double apothemX, + double apothemY, + double apothemZ, + @Nonnull TriConsumer consumer, + V1 objV1, + V2 objV2 + ) { + forEachVector(input, func, originX, originY, originZ, -apothemX, -apothemY, -apothemZ, apothemX, apothemY, apothemZ, consumer, objV1, objV2); + } + + public static void forEachVector( + Iterable input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double apothemXMin, + double apothemYMin, + double apothemZMin, + double apothemXMax, + double apothemYMax, + double apothemZMax, + @Nonnull TriConsumer consumer, + V1 objV1, + V2 objV2 + ) { + if (input instanceof FastCollection) { + ((FastCollection)input) + .forEach( + (objx, _func, _originX, _originY, _originZ, _apothemXMin, _apothemYMin, _apothemZMin, _apothemXMax, _apothemYMax, _apothemZMax, _consumer, _objV1, _objV2) -> { + Vector3d vectorx = (Vector3d)_func.apply(objx); + if (isInside(_originX, _originY, _originZ, _apothemXMin, _apothemYMin, _apothemZMin, _apothemXMax, _apothemYMax, _apothemZMax, vectorx)) { + _consumer.accept(objx, _objV1, _objV2); + } + }, + func, + originX, + originY, + originZ, + apothemXMin, + apothemYMin, + apothemZMin, + apothemXMax, + apothemYMax, + apothemZMax, + consumer, + objV1, + objV2 + ); + } else { + for (T obj : input) { + Vector3d vector = func.apply(obj); + if (isInside(originX, originY, originZ, apothemXMin, apothemYMin, apothemZMin, apothemXMax, apothemYMax, apothemZMax, vector)) { + consumer.accept(obj, objV1, objV2); + } + } + } + } + + public static void forEachVector( + @Nonnull Int2ObjectMap input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double apothem, + IntObjectConsumer consumer + ) { + forEachVector(input, func, originX, originY, originZ, apothem, apothem, apothem, consumer); + } + + public static void forEachVector( + @Nonnull Int2ObjectMap input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double apothemX, + double apothemY, + double apothemZ, + IntObjectConsumer consumer + ) { + forEachVector(input, func, originX, originY, originZ, -apothemX, -apothemY, -apothemZ, apothemX, apothemY, apothemZ, consumer); + } + + public static void forEachVector( + @Nonnull Int2ObjectMap input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double apothemXMin, + double apothemYMin, + double apothemZMin, + double apothemXMax, + double apothemYMax, + double apothemZMax, + IntObjectConsumer consumer + ) { + forEachVector( + input, + func, + originX, + originY, + originZ, + apothemXMin, + apothemYMin, + apothemZMin, + apothemXMax, + apothemYMax, + apothemZMax, + (i, t, c, n0) -> c.accept(i, t), + consumer, + null + ); + } + + public static void forEachVector( + @Nonnull Int2ObjectMap input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double apothem, + IntBiObjectConsumer consumer, + V objV + ) { + forEachVector(input, func, originX, originY, originZ, apothem, apothem, apothem, consumer, objV); + } + + public static void forEachVector( + @Nonnull Int2ObjectMap input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double apothemX, + double apothemY, + double apothemZ, + IntBiObjectConsumer consumer, + V objV + ) { + forEachVector(input, func, originX, originY, originZ, -apothemX, -apothemY, -apothemZ, apothemX, apothemY, apothemZ, consumer, objV); + } + + public static void forEachVector( + @Nonnull Int2ObjectMap input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double apothemXMin, + double apothemYMin, + double apothemZMin, + double apothemXMax, + double apothemYMax, + double apothemZMax, + IntBiObjectConsumer consumer, + V objV + ) { + forEachVector( + input, + func, + originX, + originY, + originZ, + apothemXMin, + apothemYMin, + apothemZMin, + apothemXMax, + apothemYMax, + apothemZMax, + (i, t, objV1, c) -> c.accept(i, t, objV1), + objV, + consumer + ); + } + + public static void forEachVector( + @Nonnull Int2ObjectMap input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double apothem, + @Nonnull IntTriObjectConsumer consumer, + V1 objV1, + V2 objV2 + ) { + forEachVector(input, func, originX, originY, originZ, apothem, apothem, apothem, consumer, objV1, objV2); + } + + public static void forEachVector( + @Nonnull Int2ObjectMap input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double apothemX, + double apothemY, + double apothemZ, + @Nonnull IntTriObjectConsumer consumer, + V1 objV1, + V2 objV2 + ) { + forEachVector(input, func, originX, originY, originZ, -apothemX, -apothemY, -apothemZ, apothemX, apothemY, apothemZ, consumer, objV1, objV2); + } + + public static void forEachVector( + @Nonnull Int2ObjectMap input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double apothemXMin, + double apothemYMin, + double apothemZMin, + double apothemXMax, + double apothemYMax, + double apothemZMax, + @Nonnull IntTriObjectConsumer consumer, + V1 objV1, + V2 objV2 + ) { + for (Entry next : input.int2ObjectEntrySet()) { + int key = next.getIntKey(); + T value = next.getValue(); + Vector3d vector = func.apply(value); + if (isInside(originX, originY, originZ, apothemXMin, apothemYMin, apothemZMin, apothemXMax, apothemYMax, apothemZMax, vector)) { + consumer.accept(key, value, objV1, objV2); + } + } + } + + public static boolean isInside(double originX, double originY, double originZ, double apothem, @Nonnull Vector3d vector) { + return isInside(originX, originY, originZ, apothem, apothem, apothem, vector); + } + + public static boolean isInside(double originX, double originY, double originZ, double apothemX, double apothemY, double apothemZ, @Nonnull Vector3d vector) { + return isInside(originX, originY, originZ, -apothemX, -apothemY, -apothemZ, apothemX, apothemY, apothemZ, vector); + } + + public static boolean isInside( + double originX, double originY, double originZ, double xMin, double yMin, double zMin, double xMax, double yMax, double zMax, @Nonnull Vector3d vector + ) { + double x = vector.getX() - originX; + double y = vector.getY() - originY; + double z = vector.getZ() - originZ; + return x >= xMin && x <= xMax && y >= yMin && y <= yMax && z >= zMin && z <= zMax; + } +} diff --git a/src/com/hypixel/hytale/math/vector/VectorSphereUtil.java b/src/com/hypixel/hytale/math/vector/VectorSphereUtil.java new file mode 100644 index 0000000..e210f20 --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/VectorSphereUtil.java @@ -0,0 +1,218 @@ +package com.hypixel.hytale.math.vector; + +import com.hypixel.fastutil.FastCollection; +import com.hypixel.hytale.function.consumer.IntBiObjectConsumer; +import com.hypixel.hytale.function.consumer.IntObjectConsumer; +import com.hypixel.hytale.function.consumer.IntTriObjectConsumer; +import com.hypixel.hytale.function.consumer.TriConsumer; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public class VectorSphereUtil { + public VectorSphereUtil() { + } + + public static void forEachVector(Iterable vectors, double originX, double originY, double originZ, double radius, Consumer consumer) { + forEachVector(vectors, originX, originY, originZ, radius, radius, radius, consumer); + } + + public static void forEachVector( + Iterable vectors, double originX, double originY, double originZ, double radiusX, double radiusY, double radiusZ, Consumer consumer + ) { + forEachVector(vectors, Function.identity(), originX, originY, originZ, radiusX, radiusY, radiusZ, consumer); + } + + public static void forEachVector( + Iterable input, @Nonnull Function func, double originX, double originY, double originZ, double radius, Consumer consumer + ) { + forEachVector(input, func, originX, originY, originZ, radius, radius, radius, consumer); + } + + public static void forEachVector( + Iterable input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double radiusX, + double radiusY, + double radiusZ, + Consumer consumer + ) { + forEachVector(input, func, originX, originY, originZ, radiusX, radiusY, radiusZ, (t, c, n0) -> c.accept(t), consumer, null); + } + + public static void forEachVector( + Iterable input, @Nonnull Function func, double originX, double originY, double originZ, double radius, BiConsumer consumer, V objV + ) { + forEachVector(input, func, originX, originY, originZ, radius, radius, radius, consumer, objV); + } + + public static void forEachVector( + Iterable input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double radiusX, + double radiusY, + double radiusZ, + BiConsumer consumer, + V objV + ) { + forEachVector(input, func, originX, originY, originZ, radiusX, radiusY, radiusZ, (t, c, objV2) -> c.accept(t, objV2), consumer, objV); + } + + public static void forEachVector( + Iterable input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double radius, + @Nonnull TriConsumer consumer, + V1 objV1, + V2 objV2 + ) { + forEachVector(input, func, originX, originY, originZ, radius, radius, radius, consumer, objV1, objV2); + } + + public static void forEachVector( + Iterable input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double radiusX, + double radiusY, + double radiusZ, + @Nonnull TriConsumer consumer, + V1 objV1, + V2 objV2 + ) { + if (input instanceof FastCollection fastCollection) { + fastCollection.forEach((objx, _func, _originX, _originY, _originZ, _radiusX, _radiusY, _radiusZ, _consumer, _objV1, _objV2) -> { + Vector3d vectorx = (Vector3d)_func.apply(objx); + if (isInside(_originX, _originY, _originZ, _radiusX, _radiusY, _radiusZ, vectorx)) { + _consumer.accept(objx, _objV1, _objV2); + } + }, func, originX, originY, originZ, radiusX, radiusY, radiusZ, consumer, objV1, objV2); + } else { + for (T obj : input) { + Vector3d vector = func.apply(obj); + if (isInside(originX, originY, originZ, radiusX, radiusY, radiusZ, vector)) { + consumer.accept(obj, objV1, objV2); + } + } + } + } + + public static void forEachVector( + @Nonnull Int2ObjectMap input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double radius, + IntObjectConsumer consumer + ) { + forEachVector(input, func, originX, originY, originZ, radius, radius, radius, consumer); + } + + public static void forEachVector( + @Nonnull Int2ObjectMap input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double radiusX, + double radiusY, + double radiusZ, + IntObjectConsumer consumer + ) { + forEachVector(input, func, originX, originY, originZ, radiusX, radiusY, radiusZ, (i, t, c, n0) -> c.accept(i, t), consumer, null); + } + + public static void forEachVector( + @Nonnull Int2ObjectMap input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double radius, + IntBiObjectConsumer consumer, + V objV + ) { + forEachVector(input, func, originX, originY, originZ, radius, radius, radius, consumer, objV); + } + + public static void forEachVector( + @Nonnull Int2ObjectMap input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double radiusX, + double radiusY, + double radiusZ, + IntBiObjectConsumer consumer, + V objV + ) { + forEachVector(input, func, originX, originY, originZ, radiusX, radiusY, radiusZ, (i, t, objV1, c) -> c.accept(i, t, objV1), objV, consumer); + } + + public static void forEachVector( + @Nonnull Int2ObjectMap input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double radius, + @Nonnull IntTriObjectConsumer consumer, + V1 objV1, + V2 objV2 + ) { + forEachVector(input, func, originX, originY, originZ, radius, radius, radius, consumer, objV1, objV2); + } + + public static void forEachVector( + @Nonnull Int2ObjectMap input, + @Nonnull Function func, + double originX, + double originY, + double originZ, + double radiusX, + double radiusY, + double radiusZ, + @Nonnull IntTriObjectConsumer consumer, + V1 objV1, + V2 objV2 + ) { + for (Entry next : input.int2ObjectEntrySet()) { + int key = next.getIntKey(); + T value = next.getValue(); + Vector3d vector = func.apply(value); + if (isInside(originX, originY, originZ, radiusX, radiusY, radiusZ, vector)) { + consumer.accept(key, value, objV1, objV2); + } + } + } + + public static boolean isInside(double originX, double originY, double originZ, double radius, @Nonnull Vector3d vector) { + return isInside(originX, originY, originZ, radius, radius, radius, vector); + } + + public static boolean isInside(double originX, double originY, double originZ, double radiusX, double radiusY, double radiusZ, @Nonnull Vector3d vector) { + double x = vector.getX() - originX; + double y = vector.getY() - originY; + double z = vector.getZ() - originZ; + double xRatio = x / radiusX; + double yRatio = y / radiusY; + double zRatio = z / radiusZ; + return xRatio * xRatio + yRatio * yRatio + zRatio * zRatio <= 1.0; + } +} diff --git a/src/com/hypixel/hytale/math/vector/relative/RelativeVector2d.java b/src/com/hypixel/hytale/math/vector/relative/RelativeVector2d.java new file mode 100644 index 0000000..ddd4822 --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/relative/RelativeVector2d.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.math.vector.relative; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector2d; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RelativeVector2d { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(RelativeVector2d.class, RelativeVector2d::new) + .append(new KeyedCodec<>("Vector", Vector2d.CODEC), (o, i) -> o.vector = i, RelativeVector2d::getVector) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Relative", Codec.BOOLEAN), (o, i) -> o.relative = i, RelativeVector2d::isRelative) + .addValidator(Validators.nonNull()) + .add() + .build(); + private Vector2d vector; + private boolean relative; + + public RelativeVector2d(@Nonnull Vector2d vector, boolean relative) { + this.vector = vector; + this.relative = relative; + } + + protected RelativeVector2d() { + } + + @Nonnull + public Vector2d getVector() { + return this.vector; + } + + public boolean isRelative() { + return this.relative; + } + + @Nonnull + public Vector2d resolve(@Nonnull Vector2d vector) { + return this.relative ? vector.clone().add(vector) : vector.clone(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + RelativeVector2d that = (RelativeVector2d)o; + return this.relative != that.relative ? false : Objects.equals(this.vector, that.vector); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.vector != null ? this.vector.hashCode() : 0; + return 31 * result + (this.relative ? 1 : 0); + } + + @Nonnull + @Override + public String toString() { + return "RelativeVector2d{vector=" + this.vector + ", relative=" + this.relative + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/vector/relative/RelativeVector2i.java b/src/com/hypixel/hytale/math/vector/relative/RelativeVector2i.java new file mode 100644 index 0000000..dd61a68 --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/relative/RelativeVector2i.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.math.vector.relative; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector2i; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RelativeVector2i { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(RelativeVector2i.class, RelativeVector2i::new) + .append(new KeyedCodec<>("Vector", Vector2i.CODEC), (o, i) -> o.vector = i, RelativeVector2i::getVector) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Relative", Codec.BOOLEAN), (o, i) -> o.relative = i, RelativeVector2i::isRelative) + .addValidator(Validators.nonNull()) + .add() + .build(); + private Vector2i vector; + private boolean relative; + + public RelativeVector2i(@Nonnull Vector2i vector, boolean relative) { + this.vector = vector; + this.relative = relative; + } + + protected RelativeVector2i() { + } + + @Nonnull + public Vector2i getVector() { + return this.vector; + } + + public boolean isRelative() { + return this.relative; + } + + @Nonnull + public Vector2i resolve(@Nonnull Vector2i vector) { + return this.relative ? vector.clone().add(vector) : vector.clone(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + RelativeVector2i that = (RelativeVector2i)o; + return this.relative != that.relative ? false : Objects.equals(this.vector, that.vector); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.vector != null ? this.vector.hashCode() : 0; + return 31 * result + (this.relative ? 1 : 0); + } + + @Nonnull + @Override + public String toString() { + return "RelativeVector2i{vector=" + this.vector + ", relative=" + this.relative + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/vector/relative/RelativeVector2l.java b/src/com/hypixel/hytale/math/vector/relative/RelativeVector2l.java new file mode 100644 index 0000000..a96b9ca --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/relative/RelativeVector2l.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.math.vector.relative; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector2l; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RelativeVector2l { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(RelativeVector2l.class, RelativeVector2l::new) + .append(new KeyedCodec<>("Vector", Vector2l.CODEC), (o, i) -> o.vector = i, RelativeVector2l::getVector) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Relative", Codec.BOOLEAN), (o, i) -> o.relative = i, RelativeVector2l::isRelative) + .addValidator(Validators.nonNull()) + .add() + .build(); + private Vector2l vector; + private boolean relative; + + public RelativeVector2l(@Nonnull Vector2l vector, boolean relative) { + this.vector = vector; + this.relative = relative; + } + + protected RelativeVector2l() { + } + + @Nonnull + public Vector2l getVector() { + return this.vector; + } + + public boolean isRelative() { + return this.relative; + } + + @Nonnull + public Vector2l resolve(@Nonnull Vector2l vector) { + return this.relative ? vector.clone().add(vector) : vector.clone(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + RelativeVector2l that = (RelativeVector2l)o; + return this.relative != that.relative ? false : Objects.equals(this.vector, that.vector); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.vector != null ? this.vector.hashCode() : 0; + return 31 * result + (this.relative ? 1 : 0); + } + + @Nonnull + @Override + public String toString() { + return "RelativeVector2l{vector=" + this.vector + ", relative=" + this.relative + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/vector/relative/RelativeVector3d.java b/src/com/hypixel/hytale/math/vector/relative/RelativeVector3d.java new file mode 100644 index 0000000..24f74f8 --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/relative/RelativeVector3d.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.math.vector.relative; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3d; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RelativeVector3d { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(RelativeVector3d.class, RelativeVector3d::new) + .append(new KeyedCodec<>("Vector", Vector3d.CODEC), (o, i) -> o.vector = i, RelativeVector3d::getVector) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Relative", Codec.BOOLEAN), (o, i) -> o.relative = i, RelativeVector3d::isRelative) + .addValidator(Validators.nonNull()) + .add() + .build(); + private Vector3d vector; + private boolean relative; + + public RelativeVector3d(@Nonnull Vector3d vector, boolean relative) { + this.vector = vector; + this.relative = relative; + } + + protected RelativeVector3d() { + } + + @Nonnull + public Vector3d getVector() { + return this.vector; + } + + public boolean isRelative() { + return this.relative; + } + + @Nonnull + public Vector3d resolve(@Nonnull Vector3d vector) { + return this.relative ? vector.clone().add(vector) : vector.clone(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + RelativeVector3d that = (RelativeVector3d)o; + return this.relative != that.relative ? false : Objects.equals(this.vector, that.vector); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.vector != null ? this.vector.hashCode() : 0; + return 31 * result + (this.relative ? 1 : 0); + } + + @Nonnull + @Override + public String toString() { + return "RelativeVector3d{vector=" + this.vector + ", relative=" + this.relative + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/vector/relative/RelativeVector3i.java b/src/com/hypixel/hytale/math/vector/relative/RelativeVector3i.java new file mode 100644 index 0000000..220dd8b --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/relative/RelativeVector3i.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.math.vector.relative; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RelativeVector3i { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(RelativeVector3i.class, RelativeVector3i::new) + .append(new KeyedCodec<>("Vector", Vector3i.CODEC), (o, i) -> o.vector = i, RelativeVector3i::getVector) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Relative", Codec.BOOLEAN), (o, i) -> o.relative = i, RelativeVector3i::isRelative) + .addValidator(Validators.nonNull()) + .add() + .build(); + private Vector3i vector; + private boolean relative; + + public RelativeVector3i(@Nonnull Vector3i vector, boolean relative) { + this.vector = vector; + this.relative = relative; + } + + protected RelativeVector3i() { + } + + @Nonnull + public Vector3i getVector() { + return this.vector; + } + + public boolean isRelative() { + return this.relative; + } + + @Nonnull + public Vector3i resolve(@Nonnull Vector3i vector) { + return this.relative ? vector.clone().add(vector) : vector.clone(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + RelativeVector3i that = (RelativeVector3i)o; + return this.relative != that.relative ? false : Objects.equals(this.vector, that.vector); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.vector != null ? this.vector.hashCode() : 0; + return 31 * result + (this.relative ? 1 : 0); + } + + @Nonnull + @Override + public String toString() { + return "RelativeVector3i{vector=" + this.vector + ", relative=" + this.relative + "}"; + } +} diff --git a/src/com/hypixel/hytale/math/vector/relative/RelativeVector3l.java b/src/com/hypixel/hytale/math/vector/relative/RelativeVector3l.java new file mode 100644 index 0000000..35e92c0 --- /dev/null +++ b/src/com/hypixel/hytale/math/vector/relative/RelativeVector3l.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.math.vector.relative; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3l; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RelativeVector3l { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(RelativeVector3l.class, RelativeVector3l::new) + .append(new KeyedCodec<>("Vector", Vector3l.CODEC), (o, i) -> o.vector = i, RelativeVector3l::getVector) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Relative", Codec.BOOLEAN), (o, i) -> o.relative = i, RelativeVector3l::isRelative) + .addValidator(Validators.nonNull()) + .add() + .build(); + private Vector3l vector; + private boolean relative; + + public RelativeVector3l(@Nonnull Vector3l vector, boolean relative) { + this.vector = vector; + this.relative = relative; + } + + protected RelativeVector3l() { + } + + @Nonnull + public Vector3l getVector() { + return this.vector; + } + + public boolean isRelative() { + return this.relative; + } + + @Nonnull + public Vector3l resolve(@Nonnull Vector3l vector) { + return this.relative ? vector.clone().add(vector) : vector.clone(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + RelativeVector3l that = (RelativeVector3l)o; + return this.relative != that.relative ? false : Objects.equals(this.vector, that.vector); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.vector != null ? this.vector.hashCode() : 0; + return 31 * result + (this.relative ? 1 : 0); + } + + @Nonnull + @Override + public String toString() { + return "RelativeVector3l{vector=" + this.vector + ", relative=" + this.relative + "}"; + } +} diff --git a/src/com/hypixel/hytale/metrics/ExecutorMetricsRegistry.java b/src/com/hypixel/hytale/metrics/ExecutorMetricsRegistry.java new file mode 100644 index 0000000..6da7f51 --- /dev/null +++ b/src/com/hypixel/hytale/metrics/ExecutorMetricsRegistry.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.metrics; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Function; +import javax.annotation.Nonnull; +import org.bson.BsonValue; + +public class ExecutorMetricsRegistry extends MetricsRegistry { + public ExecutorMetricsRegistry() { + } + + public BsonValue encode(@Nonnull T t, ExtraInfo extraInfo) { + return t.isInThread() ? super.encode(t, extraInfo) : CompletableFuture.supplyAsync(() -> super.encode(t, extraInfo), t).join(); + } + + public ExecutorMetricsRegistry register(String id, @Nonnull Function func) { + return (ExecutorMetricsRegistry)super.register(id, func); + } + + public ExecutorMetricsRegistry register(String id, Function func, Codec codec) { + return (ExecutorMetricsRegistry)super.register(id, func, codec); + } + + public ExecutorMetricsRegistry register(String id, MetricsRegistry metricsRegistry) { + return (ExecutorMetricsRegistry)super.register(id, metricsRegistry); + } + + public ExecutorMetricsRegistry register(String id, Function func, Function> codecFunc) { + return (ExecutorMetricsRegistry)super.register(id, func, codecFunc); + } + + public interface ExecutorMetric extends Executor { + boolean isInThread(); + } +} diff --git a/src/com/hypixel/hytale/metrics/InitStackThread.java b/src/com/hypixel/hytale/metrics/InitStackThread.java new file mode 100644 index 0000000..3ec6ba8 --- /dev/null +++ b/src/com/hypixel/hytale/metrics/InitStackThread.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.metrics; + +public interface InitStackThread { + StackTraceElement[] getInitStack(); +} diff --git a/src/com/hypixel/hytale/metrics/JVMMetrics.java b/src/com/hypixel/hytale/metrics/JVMMetrics.java new file mode 100644 index 0000000..b886c4f --- /dev/null +++ b/src/com/hypixel/hytale/metrics/JVMMetrics.java @@ -0,0 +1,246 @@ +package com.hypixel.hytale.metrics; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import java.lang.Thread.State; +import java.lang.management.ClassLoadingMXBean; +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryManagerMXBean; +import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryType; +import java.lang.management.MemoryUsage; +import java.lang.management.OperatingSystemMXBean; +import java.lang.management.RuntimeMXBean; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.time.Duration; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class JVMMetrics { + @Nonnull + public static final MetricsRegistry CLASS_LOADER_METRICS_REGISTRY = new MetricsRegistry<>(); + @Nonnull + public static final MetricsRegistry MEMORY_USAGE_METRICS_REGISTRY = new MetricsRegistry<>(); + @Nonnull + public static final MetricsRegistry GARBAGE_COLLECTOR_METRICS_REGISTRY = new MetricsRegistry<>(); + @Nonnull + public static final MetricsRegistry MEMORY_POOL_METRICS_REGISTRY = new MetricsRegistry<>(); + @Nonnull + public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry<>(); + + public JVMMetrics() { + } + + static { + CLASS_LOADER_METRICS_REGISTRY.register("Name", ClassLoader::getName, Codec.STRING); + CLASS_LOADER_METRICS_REGISTRY.register("Parent", ClassLoader::getParent, CLASS_LOADER_METRICS_REGISTRY); + MEMORY_USAGE_METRICS_REGISTRY.register("Init", MemoryUsage::getInit, Codec.LONG); + MEMORY_USAGE_METRICS_REGISTRY.register("Used", MemoryUsage::getUsed, Codec.LONG); + MEMORY_USAGE_METRICS_REGISTRY.register("Committed", MemoryUsage::getCommitted, Codec.LONG); + MEMORY_USAGE_METRICS_REGISTRY.register("Max", MemoryUsage::getMax, Codec.LONG); + GARBAGE_COLLECTOR_METRICS_REGISTRY.register("Name", MemoryManagerMXBean::getName, Codec.STRING); + GARBAGE_COLLECTOR_METRICS_REGISTRY.register("MemoryPoolNames", MemoryManagerMXBean::getMemoryPoolNames, Codec.STRING_ARRAY); + GARBAGE_COLLECTOR_METRICS_REGISTRY.register("CollectionCount", GarbageCollectorMXBean::getCollectionCount, Codec.LONG); + GARBAGE_COLLECTOR_METRICS_REGISTRY.register("CollectionTime", GarbageCollectorMXBean::getCollectionTime, Codec.LONG); + MEMORY_POOL_METRICS_REGISTRY.register("Name", MemoryPoolMXBean::getName, Codec.STRING); + MEMORY_POOL_METRICS_REGISTRY.register("Type", MemoryPoolMXBean::getType, new EnumCodec<>(MemoryType.class)); + MEMORY_POOL_METRICS_REGISTRY.register("PeakUsage", MemoryPoolMXBean::getPeakUsage, MEMORY_USAGE_METRICS_REGISTRY); + MEMORY_POOL_METRICS_REGISTRY.register("Usage", MemoryPoolMXBean::getUsage, MEMORY_USAGE_METRICS_REGISTRY); + MEMORY_POOL_METRICS_REGISTRY.register("CollectionUsage", MemoryPoolMXBean::getCollectionUsage, MEMORY_USAGE_METRICS_REGISTRY); + MetricsRegistry usageThreshold = new MetricsRegistry<>(); + usageThreshold.register("Threshold", MemoryPoolMXBean::getUsageThreshold, Codec.LONG); + usageThreshold.register("ThresholdCount", MemoryPoolMXBean::getUsageThresholdCount, Codec.LONG); + usageThreshold.register("ThresholdExceeded", MemoryPoolMXBean::isUsageThresholdExceeded, Codec.BOOLEAN); + MEMORY_POOL_METRICS_REGISTRY.register( + "UsageThreshold", memoryPoolMXBean -> !memoryPoolMXBean.isUsageThresholdSupported() ? null : memoryPoolMXBean, usageThreshold + ); + usageThreshold = new MetricsRegistry<>(); + usageThreshold.register("Threshold", MemoryPoolMXBean::getCollectionUsageThreshold, Codec.LONG); + usageThreshold.register("ThresholdCount", MemoryPoolMXBean::getCollectionUsageThresholdCount, Codec.LONG); + usageThreshold.register("ThresholdExceeded", MemoryPoolMXBean::isCollectionUsageThresholdExceeded, Codec.BOOLEAN); + MEMORY_POOL_METRICS_REGISTRY.register( + "CollectionUsageThreshold", memoryPoolMXBean -> !memoryPoolMXBean.isCollectionUsageThresholdSupported() ? null : memoryPoolMXBean, usageThreshold + ); + usageThreshold = new MetricsRegistry<>(); + METRICS_REGISTRY.register("PROCESSOR", unused -> System.getenv("PROCESSOR_IDENTIFIER"), Codec.STRING); + METRICS_REGISTRY.register("PROCESSOR_ARCHITECTURE", unused -> System.getenv("PROCESSOR_ARCHITECTURE"), Codec.STRING); + METRICS_REGISTRY.register("PROCESSOR_ARCHITEW6432", unused -> System.getenv("PROCESSOR_ARCHITEW6432"), Codec.STRING); + usageThreshold.register("OSName", OperatingSystemMXBean::getName, Codec.STRING); + usageThreshold.register("OSArch", OperatingSystemMXBean::getArch, Codec.STRING); + usageThreshold.register("OSVersion", OperatingSystemMXBean::getVersion, Codec.STRING); + usageThreshold.register("AvailableProcessors", unused -> Runtime.getRuntime().availableProcessors(), Codec.INTEGER); + usageThreshold.register("SystemLoadAverage", OperatingSystemMXBean::getSystemLoadAverage, Codec.DOUBLE); + if (ManagementFactory.getOperatingSystemMXBean() instanceof com.sun.management.OperatingSystemMXBean) { + usageThreshold.register( + "CpuLoad", operatingSystemMXBean -> ((com.sun.management.OperatingSystemMXBean)operatingSystemMXBean).getCpuLoad(), Codec.DOUBLE + ); + usageThreshold.register( + "ProcessCpuLoad", operatingSystemMXBean -> ((com.sun.management.OperatingSystemMXBean)operatingSystemMXBean).getProcessCpuLoad(), Codec.DOUBLE + ); + usageThreshold.register( + "TotalMemorySize", operatingSystemMXBean -> ((com.sun.management.OperatingSystemMXBean)operatingSystemMXBean).getTotalMemorySize(), Codec.LONG + ); + usageThreshold.register( + "FreeMemorySize", operatingSystemMXBean -> ((com.sun.management.OperatingSystemMXBean)operatingSystemMXBean).getFreeMemorySize(), Codec.LONG + ); + usageThreshold.register( + "TotalSwapSpaceSize", + operatingSystemMXBean -> ((com.sun.management.OperatingSystemMXBean)operatingSystemMXBean).getTotalSwapSpaceSize(), + Codec.LONG + ); + usageThreshold.register( + "FreeSwapSpaceSize", operatingSystemMXBean -> ((com.sun.management.OperatingSystemMXBean)operatingSystemMXBean).getFreeSwapSpaceSize(), Codec.LONG + ); + } + + METRICS_REGISTRY.register("System", aVoid -> ManagementFactory.getOperatingSystemMXBean(), usageThreshold); + usageThreshold = new MetricsRegistry<>(); + usageThreshold.register("StartTime", runtimeMXBean -> Instant.ofEpochMilli(runtimeMXBean.getStartTime()), Codec.INSTANT); + usageThreshold.register("Uptime", runtimeMXBean -> Duration.ofMillis(runtimeMXBean.getUptime()), Codec.DURATION); + usageThreshold.register("RuntimeName", RuntimeMXBean::getName, Codec.STRING); + usageThreshold.register("SpecName", RuntimeMXBean::getSpecName, Codec.STRING); + usageThreshold.register("SpecVendor", RuntimeMXBean::getSpecVendor, Codec.STRING); + usageThreshold.register("SpecVersion", RuntimeMXBean::getSpecVersion, Codec.STRING); + usageThreshold.register("ManagementSpecVersion", RuntimeMXBean::getManagementSpecVersion, Codec.STRING); + usageThreshold.register("VMName", RuntimeMXBean::getVmName, Codec.STRING); + usageThreshold.register("VMVendor", RuntimeMXBean::getVmVendor, Codec.STRING); + usageThreshold.register("VMVersion", RuntimeMXBean::getVmVersion, Codec.STRING); + usageThreshold.register("LibraryPath", RuntimeMXBean::getLibraryPath, Codec.STRING); + + try { + ManagementFactory.getRuntimeMXBean().getBootClassPath(); + usageThreshold.register("BootClassPath", RuntimeMXBean::getBootClassPath, Codec.STRING); + } catch (UnsupportedOperationException var2) { + } + + usageThreshold.register("ClassPath", RuntimeMXBean::getClassPath, Codec.STRING); + usageThreshold.register("InputArguments", runtimeMXBean -> runtimeMXBean.getInputArguments().toArray(String[]::new), Codec.STRING_ARRAY); + usageThreshold.register("SystemProperties", RuntimeMXBean::getSystemProperties, new MapCodec<>(Codec.STRING, HashMap::new)); + METRICS_REGISTRY.register("Runtime", aVoid -> ManagementFactory.getRuntimeMXBean(), usageThreshold); + usageThreshold = new MetricsRegistry<>(); + usageThreshold.register("ObjectPendingFinalizationCount", memoryMXBean -> memoryMXBean.getObjectPendingFinalizationCount(), Codec.INTEGER); + usageThreshold.register("HeapMemoryUsage", memoryMXBean -> memoryMXBean.getHeapMemoryUsage(), MEMORY_USAGE_METRICS_REGISTRY); + usageThreshold.register("NonHeapMemoryUsage", memoryMXBean -> memoryMXBean.getNonHeapMemoryUsage(), MEMORY_USAGE_METRICS_REGISTRY); + METRICS_REGISTRY.register("Memory", aVoid -> ManagementFactory.getMemoryMXBean(), usageThreshold); + METRICS_REGISTRY.register( + "GarbageCollectors", + memoryMXBean -> ManagementFactory.getGarbageCollectorMXBeans().toArray(GarbageCollectorMXBean[]::new), + new ArrayCodec<>(GARBAGE_COLLECTOR_METRICS_REGISTRY, GarbageCollectorMXBean[]::new) + ); + METRICS_REGISTRY.register( + "MemoryPools", + memoryMXBean -> ManagementFactory.getMemoryPoolMXBeans().toArray(MemoryPoolMXBean[]::new), + new ArrayCodec<>(MEMORY_POOL_METRICS_REGISTRY, MemoryPoolMXBean[]::new) + ); + METRICS_REGISTRY.register("Threads", aVoid -> { + ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(true, true); + Map stackTraces = Thread.getAllStackTraces(); + Long2ObjectOpenHashMap threadIdMap = new Long2ObjectOpenHashMap<>(); + + for (Thread thread : stackTraces.keySet()) { + threadIdMap.put(thread.getId(), thread); + } + + JVMMetrics.ThreadMetricData[] data = new JVMMetrics.ThreadMetricData[threadInfos.length]; + + for (int i = 0; i < threadInfos.length; i++) { + ThreadInfo threadInfo = threadInfos[i]; + data[i] = new JVMMetrics.ThreadMetricData(threadInfo, threadIdMap.get(threadInfo.getThreadId()), threadMXBean); + } + + return data; + }, new ArrayCodec<>(JVMMetrics.ThreadMetricData.METRICS_REGISTRY, JVMMetrics.ThreadMetricData[]::new)); + METRICS_REGISTRY.register("SecurityManager", aVoid -> { + SecurityManager securityManager = System.getSecurityManager(); + return securityManager == null ? null : securityManager.getClass().getName(); + }, Codec.STRING); + usageThreshold = new MetricsRegistry<>(); + usageThreshold.register("LoadedClassCount", ClassLoadingMXBean::getLoadedClassCount, Codec.INTEGER); + usageThreshold.register("UnloadedClassCount", ClassLoadingMXBean::getUnloadedClassCount, Codec.LONG); + usageThreshold.register("TotalLoadedClassCount", ClassLoadingMXBean::getTotalLoadedClassCount, Codec.LONG); + usageThreshold.register("SystemClassloader", unused -> ClassLoader.getSystemClassLoader(), CLASS_LOADER_METRICS_REGISTRY); + usageThreshold.register("JVMMetricsClassloader", unused -> JVMMetrics.class.getClassLoader(), CLASS_LOADER_METRICS_REGISTRY); + METRICS_REGISTRY.register("ClassLoading", aVoid -> ManagementFactory.getClassLoadingMXBean(), usageThreshold); + } + + private static class ThreadMetricData { + @Nonnull + public static final MetricsRegistry STACK_TRACE_ELEMENT_METRICS_REGISTRY = new MetricsRegistry<>(); + @Nonnull + public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry<>(); + private final ThreadInfo threadInfo; + private final Thread thread; + private final ThreadMXBean threadMXBean; + + public ThreadMetricData(ThreadInfo threadInfo, Thread thread, ThreadMXBean threadMXBean) { + this.threadInfo = threadInfo; + this.thread = thread; + this.threadMXBean = threadMXBean; + } + + static { + STACK_TRACE_ELEMENT_METRICS_REGISTRY.register("FileName", StackTraceElement::getFileName, Codec.STRING); + STACK_TRACE_ELEMENT_METRICS_REGISTRY.register("LineNumber", StackTraceElement::getLineNumber, Codec.INTEGER); + STACK_TRACE_ELEMENT_METRICS_REGISTRY.register("ModuleName", StackTraceElement::getModuleName, Codec.STRING); + STACK_TRACE_ELEMENT_METRICS_REGISTRY.register("ModuleVersion", StackTraceElement::getModuleVersion, Codec.STRING); + STACK_TRACE_ELEMENT_METRICS_REGISTRY.register("ClassLoaderName", StackTraceElement::getClassLoaderName, Codec.STRING); + STACK_TRACE_ELEMENT_METRICS_REGISTRY.register("ClassName", StackTraceElement::getClassName, Codec.STRING); + STACK_TRACE_ELEMENT_METRICS_REGISTRY.register("MethodName", StackTraceElement::getMethodName, Codec.STRING); + METRICS_REGISTRY.register("Id", threadMetricData -> threadMetricData.threadInfo.getThreadId(), Codec.LONG); + METRICS_REGISTRY.register("Name", threadMetricData -> threadMetricData.threadInfo.getThreadName(), Codec.STRING); + METRICS_REGISTRY.register("State", threadMetricData -> threadMetricData.threadInfo.getThreadState(), new EnumCodec<>(State.class)); + METRICS_REGISTRY.register("Priority", threadMetricData -> threadMetricData.threadInfo.getPriority(), Codec.INTEGER); + METRICS_REGISTRY.register("Daemon", threadMetricData -> threadMetricData.threadInfo.isDaemon(), Codec.BOOLEAN); + METRICS_REGISTRY.register( + "CPUTime", threadMetricData -> threadMetricData.threadMXBean.getThreadCpuTime(threadMetricData.threadInfo.getThreadId()), Codec.LONG + ); + METRICS_REGISTRY.register("WaitedTime", threadMetricData -> threadMetricData.threadInfo.getWaitedTime(), Codec.LONG); + METRICS_REGISTRY.register("WaitedCount", threadMetricData -> threadMetricData.threadInfo.getWaitedCount(), Codec.LONG); + METRICS_REGISTRY.register("BlockedTime", threadMetricData -> threadMetricData.threadInfo.getBlockedTime(), Codec.LONG); + METRICS_REGISTRY.register("BlockedCount", threadMetricData -> threadMetricData.threadInfo.getBlockedCount(), Codec.LONG); + METRICS_REGISTRY.register("LockName", threadMetricData -> threadMetricData.threadInfo.getLockName(), Codec.STRING); + METRICS_REGISTRY.register("LockOwnerId", threadMetricData -> threadMetricData.threadInfo.getLockOwnerId(), Codec.LONG); + METRICS_REGISTRY.register("LockOwnerName", threadMetricData -> threadMetricData.threadInfo.getLockOwnerName(), Codec.STRING); + METRICS_REGISTRY.register( + "StackTrace", + threadMetricData -> threadMetricData.threadInfo.getStackTrace(), + new ArrayCodec<>(STACK_TRACE_ELEMENT_METRICS_REGISTRY, StackTraceElement[]::new) + ); + METRICS_REGISTRY.register( + "InitStackTrace", + threadMetricData -> threadMetricData.thread instanceof InitStackThread ? ((InitStackThread)threadMetricData.thread).getInitStack() : null, + new ArrayCodec<>(STACK_TRACE_ELEMENT_METRICS_REGISTRY, StackTraceElement[]::new) + ); + METRICS_REGISTRY.register( + "Interrupted", threadMetricData -> threadMetricData.thread != null ? threadMetricData.thread.isInterrupted() : null, Codec.BOOLEAN + ); + METRICS_REGISTRY.register( + "ThreadClass", threadMetricData -> threadMetricData.thread != null ? threadMetricData.thread.getClass().getName() : null, Codec.STRING + ); + MetricsRegistry threadGroup = new MetricsRegistry<>(); + threadGroup.register("Name", ThreadGroup::getName, Codec.STRING); + threadGroup.register("Parent", ThreadGroup::getParent, threadGroup); + threadGroup.register("MaxPriority", ThreadGroup::getMaxPriority, Codec.INTEGER); + threadGroup.register("Destroyed", ThreadGroup::isDestroyed, Codec.BOOLEAN); + threadGroup.register("Daemon", ThreadGroup::isDaemon, Codec.BOOLEAN); + threadGroup.register("ActiveCount", ThreadGroup::activeCount, Codec.INTEGER); + threadGroup.register("ActiveGroupCount", ThreadGroup::activeGroupCount, Codec.INTEGER); + METRICS_REGISTRY.register( + "ThreadGroup", threadMetricData -> threadMetricData.thread != null ? threadMetricData.thread.getThreadGroup() : null, threadGroup + ); + METRICS_REGISTRY.register( + "UncaughtExceptionHandler", + threadMetricData -> threadMetricData.thread != null ? threadMetricData.thread.getUncaughtExceptionHandler().getClass().getName() : null, + Codec.STRING + ); + } + } +} diff --git a/src/com/hypixel/hytale/metrics/MetricProvider.java b/src/com/hypixel/hytale/metrics/MetricProvider.java new file mode 100644 index 0000000..1a77daa --- /dev/null +++ b/src/com/hypixel/hytale/metrics/MetricProvider.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.metrics; + +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface MetricProvider { + @Nullable + MetricResults toMetricResults(); + + @Nonnull + static Function maybe(@Nonnull Function func) { + return t -> { + R r = func.apply(t); + return r instanceof MetricProvider ? (MetricProvider)r : null; + }; + } +} diff --git a/src/com/hypixel/hytale/metrics/MetricResults.java b/src/com/hypixel/hytale/metrics/MetricResults.java new file mode 100644 index 0000000..67666da --- /dev/null +++ b/src/com/hypixel/hytale/metrics/MetricResults.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.metrics; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import java.io.IOException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class MetricResults { + public static final Codec CODEC = new MetricResults.MetricResultsCodec(); + public static final Codec ARRAY_CODEC = new ArrayCodec<>(CODEC, MetricResults[]::new); + private final BsonDocument bson; + + protected MetricResults(BsonDocument bson) { + this.bson = bson; + } + + protected BsonDocument getBson() { + return this.bson; + } + + private static class MetricResultsCodec implements Codec { + private MetricResultsCodec() { + } + + @Nullable + public MetricResults decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + return Codec.isNullBsonValue(bsonValue) ? null : new MetricResults(bsonValue.asDocument()); + } + + public BsonValue encode(@Nullable MetricResults metricResults, ExtraInfo extraInfo) { + return metricResults == null ? null : metricResults.bson; + } + + @Nullable + public MetricResults decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + BsonValue bsonValue = RawJsonReader.readBsonValue(reader); + return Codec.isNullBsonValue(bsonValue) ? null : new MetricResults(bsonValue.asDocument()); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return new ObjectSchema(); + } + } +} diff --git a/src/com/hypixel/hytale/metrics/MetricsRegistry.java b/src/com/hypixel/hytale/metrics/MetricsRegistry.java new file mode 100644 index 0000000..6091ce3 --- /dev/null +++ b/src/com/hypixel/hytale/metrics/MetricsRegistry.java @@ -0,0 +1,253 @@ +package com.hypixel.hytale.metrics; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.backend.HytaleFileHandler; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.locks.StampedLock; +import java.util.function.Function; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonValue; +import org.bson.codecs.BsonDocumentCodec; +import org.bson.codecs.EncoderContext; +import org.bson.json.JsonMode; +import org.bson.json.JsonWriter; +import org.bson.json.JsonWriterSettings; + +public class MetricsRegistry implements Codec { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final JsonWriterSettings JSON_SETTINGS = JsonWriterSettings.builder() + .outputMode(JsonMode.STRICT) + .indent(false) + .newLineCharacters("\n") + .int64Converter((value, writer) -> writer.writeNumber(Long.toString(value))) + .build(); + private static final EncoderContext ENCODER_CONTEXT = EncoderContext.builder().build(); + private static final BsonDocumentCodec BSON_DOCUMENT_CODEC = new BsonDocumentCodec(); + @Nullable + private final Function appendFunc; + private final StampedLock lock = new StampedLock(); + private final Map> map = new Object2ObjectLinkedOpenHashMap<>(); + + public MetricsRegistry() { + this.appendFunc = null; + } + + public MetricsRegistry(Function appendFunc) { + this.appendFunc = appendFunc; + } + + public MetricsRegistry register(String id, MetricsRegistry metricsRegistry) { + long stamp = this.lock.writeLock(); + + try { + if (this.map.putIfAbsent(id, new MetricsRegistry.Metric<>(null, metricsRegistry)) != null) { + throw new IllegalArgumentException("Metric already registered: " + id); + } + } finally { + this.lock.unlockWrite(stamp); + } + + return this; + } + + public MetricsRegistry register(String id, Function func, Codec codec) { + long stamp = this.lock.writeLock(); + + try { + if (this.map.putIfAbsent(id, new MetricsRegistry.Metric<>(func, codec)) != null) { + throw new IllegalArgumentException("Metric already registered: " + id); + } + } finally { + this.lock.unlockWrite(stamp); + } + + return this; + } + + public MetricsRegistry register(String id, @Nonnull Function func) { + return this.register(id, func.andThen(r -> r == null ? null : r.toMetricResults()), MetricResults.CODEC); + } + + @Deprecated + public MetricsRegistry register(String id, Function func, Function> codecFunc) { + long stamp = this.lock.writeLock(); + + try { + if (this.map.putIfAbsent(id, new MetricsRegistry.Metric<>(func, codecFunc)) != null) { + throw new IllegalArgumentException("Metric already registered: " + id); + } + } finally { + this.lock.unlockWrite(stamp); + } + + return this; + } + + @Override + public T decode(BsonValue bsonValue, ExtraInfo extraInfo) { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public BsonValue encode(T t, ExtraInfo extraInfo) { + BsonDocument document = new BsonDocument(); + long stamp = this.lock.readLock(); + + try { + for (Entry> entry : this.map.entrySet()) { + String key = entry.getKey(); + BsonValue value = entry.getValue().encode(t, extraInfo); + if (value != null) { + document.put(key, value); + } + } + } finally { + this.lock.unlockRead(stamp); + } + + if (this.appendFunc != null) { + MetricProvider metricProvider = this.appendFunc.apply(t); + if (metricProvider != null) { + MetricResults metricResults = metricProvider.toMetricResults(); + if (metricResults != null) { + document.putAll(metricResults.getBson()); + } + } + } + + return document; + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + throw new UnsupportedOperationException("Not implemented"); + } + + @Nonnull + public MetricResults toMetricResults(T t) { + return new MetricResults(this.dumpToBson(t).asDocument()); + } + + public BsonValue dumpToBson(T t) { + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + BsonDocument bson = this.encode(t, extraInfo).asDocument(); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER); + return bson; + } + + @Nonnull + public Path dumpToJson(T t) throws IOException { + Path path = createDumpPath(".dump.json"); + this.dumpToJson(path, t); + return path; + } + + public void dumpToJson(@Nonnull Path path, T t) throws IOException { + BsonValue bson = this.dumpToBson(t); + + try (BufferedWriter writer = Files.newBufferedWriter(path)) { + BSON_DOCUMENT_CODEC.encode(new JsonWriter(writer, JSON_SETTINGS), bson.asDocument(), ENCODER_CONTEXT); + } + } + + @Nonnull + public static Path createDumpPath(@Nullable String ext) throws IOException { + return createDumpPath((String)null, ext); + } + + @Nonnull + public static Path createDumpPath(@Nonnull Path dir, @Nullable String ext) { + return createDatePath(dir, null, ext); + } + + @Nonnull + public static Path createDumpPath(@Nullable String prefix, @Nullable String ext) throws IOException { + Path path = Paths.get("dumps"); + if (!Files.exists(path)) { + Files.createDirectories(path); + } + + return createDatePath(path, prefix, ext); + } + + @Nonnull + public static Path createDatePath(@Nonnull Path dir, @Nullable String prefix, @Nullable String suffix) { + String name = HytaleFileHandler.LOG_FILE_DATE_FORMAT.format(LocalDateTime.now()); + if (prefix != null) { + name = prefix + name; + } + + Path file = suffix != null ? dir.resolve(name + suffix) : dir.resolve(name); + int i = 0; + + while (Files.exists(file)) { + if (suffix != null) { + file = dir.resolve(name + "_" + i++ + suffix); + } else { + file = dir.resolve(name + "_" + i++); + } + } + + return file; + } + + private static class Metric { + @Nullable + private final Function func; + @CheckForNull + private final Codec codec; + @CheckForNull + private final Function> codecFunc; + + public Metric(@Nullable Function func, @Nullable Codec codec) { + this.func = func; + this.codec = codec; + this.codecFunc = null; + } + + public Metric(@Nullable Function func, @Nullable Function> codecFunc) { + this.func = func; + this.codec = null; + this.codecFunc = codecFunc; + } + + @Nullable + public BsonValue encode(T t, ExtraInfo extraInfo) { + if (this.func == null) { + assert this.codec != null; + + return this.codec.encode(null, extraInfo); + } else { + R value = this.func.apply(t); + return value == null ? null : this.getCodec(value).encode(value, extraInfo); + } + } + + @Nonnull + public Codec getCodec(R value) { + if (this.codec != null) { + return this.codec; + } else { + assert this.codecFunc != null; + + return this.codecFunc.apply(value); + } + } + } +} diff --git a/src/com/hypixel/hytale/metrics/metric/AverageCollector.java b/src/com/hypixel/hytale/metrics/metric/AverageCollector.java new file mode 100644 index 0000000..bb1e3b9 --- /dev/null +++ b/src/com/hypixel/hytale/metrics/metric/AverageCollector.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.metrics.metric; + +public class AverageCollector { + private double val = 0.0; + private long n = 0L; + + public AverageCollector() { + } + + public double get() { + return this.val; + } + + public long size() { + return this.n; + } + + public double addAndGet(double v) { + this.add(v); + return this.get(); + } + + public void add(double v) { + this.n++; + this.val = this.val - this.val / this.n + v / this.n; + } + + public void remove(double v) { + if (this.n == 1L) { + this.n = 0L; + this.val = 0.0; + } else if (this.n > 1L) { + this.val = (this.val - v / this.n) / (1.0 - 1.0 / this.n); + this.n--; + } + } + + public void clear() { + this.val = 0.0; + this.n = 0L; + } + + public static double add(double val, double v, int n) { + return val - val / n + v / n; + } + + public void set(double v) { + this.n = 1L; + this.val = v; + } +} diff --git a/src/com/hypixel/hytale/metrics/metric/HistoricMetric.java b/src/com/hypixel/hytale/metrics/metric/HistoricMetric.java new file mode 100644 index 0000000..5daaf67 --- /dev/null +++ b/src/com/hypixel/hytale/metrics/metric/HistoricMetric.java @@ -0,0 +1,287 @@ +package com.hypixel.hytale.metrics.metric; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.util.MathUtil; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + +public class HistoricMetric { + public static final HistoricMetric[] EMPTY_ARRAY = new HistoricMetric[0]; + public static final Codec METRICS_CODEC = BuilderCodec.builder(HistoricMetric.class, HistoricMetric::new) + .append(new KeyedCodec<>("PeriodsNanos", Codec.LONG_ARRAY), (historicMetric, s) -> { + throw new UnsupportedOperationException("Not supported"); + }, historicMetric -> historicMetric.periodsNanos) + .add() + .append(new KeyedCodec<>("Timestamps", Codec.LONG_ARRAY), (historicMetric, s) -> { + throw new UnsupportedOperationException("Not supported"); + }, HistoricMetric::getAllTimestamps) + .add() + .append(new KeyedCodec<>("Values", Codec.LONG_ARRAY), (historicMetric, s) -> { + throw new UnsupportedOperationException("Not supported"); + }, HistoricMetric::getAllValues) + .add() + .build(); + private final long[] periodsNanos; + @Nonnull + private final AverageCollector[] periodAverages; + @Nonnull + private final int[] startIndices; + private final int bufferSize; + @Nonnull + private final long[] timestamps; + @Nonnull + private final long[] values; + int nextIndex; + + private HistoricMetric() { + throw new UnsupportedOperationException("Not supported"); + } + + private HistoricMetric(@Nonnull HistoricMetric.Builder builder) { + this.periodsNanos = builder.periods.toLongArray(); + this.periodAverages = new AverageCollector[this.periodsNanos.length]; + + for (int i = 0; i < this.periodAverages.length; i++) { + this.periodAverages[i] = new AverageCollector(); + } + + this.startIndices = new int[this.periodsNanos.length]; + long longestPeriod = 0L; + + for (long period : this.periodsNanos) { + if (period > longestPeriod) { + longestPeriod = period; + } + } + + this.bufferSize = (int)MathUtil.fastCeil((double)longestPeriod / builder.minimumInterval); + this.timestamps = new long[this.bufferSize]; + this.values = new long[this.bufferSize]; + Arrays.fill(this.timestamps, Long.MAX_VALUE); + } + + public long[] getPeriodsNanos() { + return this.periodsNanos; + } + + public long calculateMin(int periodIndex) { + int bufferSize = this.bufferSize; + long[] values = this.values; + int start = this.startIndices[periodIndex]; + int nextIndex = this.nextIndex; + long min = Long.MAX_VALUE; + if (start < nextIndex) { + for (int i = start; i < nextIndex; i++) { + long value = values[i]; + if (value < min) { + min = value; + } + } + } else { + for (int ix = start; ix < bufferSize; ix++) { + long value = values[ix]; + if (value < min) { + min = value; + } + } + + for (int ixx = 0; ixx < nextIndex; ixx++) { + long value = values[ixx]; + if (value < min) { + min = value; + } + } + } + + return min; + } + + public double getAverage(int periodIndex) { + return this.periodAverages[periodIndex].get(); + } + + public long calculateMax(int periodIndex) { + int bufferSize = this.bufferSize; + long[] values = this.values; + int start = this.startIndices[periodIndex]; + int nextIndex = this.nextIndex; + long max = Long.MIN_VALUE; + if (start < nextIndex) { + for (int i = start; i < nextIndex; i++) { + long value = values[i]; + if (value > max) { + max = value; + } + } + } else { + for (int ix = start; ix < bufferSize; ix++) { + long value = values[ix]; + if (value > max) { + max = value; + } + } + + for (int ixx = 0; ixx < nextIndex; ixx++) { + long value = values[ixx]; + if (value > max) { + max = value; + } + } + } + + return max; + } + + public void clear() { + for (AverageCollector average : this.periodAverages) { + average.clear(); + } + + Arrays.fill(this.startIndices, 0); + Arrays.fill(this.timestamps, Long.MAX_VALUE); + Arrays.fill(this.values, 0L); + this.nextIndex = 0; + } + + public void add(long timestampNanos, long value) { + long[] periodsNanos = this.periodsNanos; + AverageCollector[] periodAverages = this.periodAverages; + int[] startIndices = this.startIndices; + int bufferSize = this.bufferSize; + long[] timestamps = this.timestamps; + long[] values = this.values; + int nextIndex = this.nextIndex; + int periodLength = periodsNanos.length; + + for (int i = 0; i < periodLength; i++) { + long oldestPossibleTimestamp = timestampNanos - periodsNanos[i]; + AverageCollector average = periodAverages[i]; + int start = startIndices[i]; + + while (timestamps[start] < oldestPossibleTimestamp) { + long oldValue = values[start]; + average.remove(oldValue); + start = (start + 1) % bufferSize; + if (start == nextIndex) { + break; + } + } + + startIndices[i] = start; + average.add(value); + } + + timestamps[nextIndex] = timestampNanos; + values[nextIndex] = value; + this.nextIndex = (nextIndex + 1) % bufferSize; + } + + public long[] getTimestamps(int periodIndex) { + int start = this.startIndices[periodIndex]; + long[] timestamps = this.timestamps; + int nextIndex = this.nextIndex; + if (start < nextIndex) { + return Arrays.copyOfRange(timestamps, start, nextIndex); + } else { + int length = timestamps.length - start; + long[] data = new long[length + nextIndex]; + System.arraycopy(timestamps, start, data, 0, length); + System.arraycopy(timestamps, 0, data, length, nextIndex); + return data; + } + } + + public long[] getValues(int periodIndex) { + int start = this.startIndices[periodIndex]; + long[] values = this.values; + int nextIndex = this.nextIndex; + if (start < nextIndex) { + return Arrays.copyOfRange(values, start, nextIndex); + } else { + int length = this.bufferSize - start; + long[] data = new long[length + nextIndex]; + System.arraycopy(values, start, data, 0, length); + System.arraycopy(values, 0, data, length, nextIndex); + return data; + } + } + + public long[] getAllTimestamps() { + return this.getTimestamps(this.periodsNanos.length - 1); + } + + public long[] getAllValues() { + return this.getValues(this.periodsNanos.length - 1); + } + + public void setAllTimestamps(@Nonnull long[] timestamps) { + int length = timestamps.length; + System.arraycopy(timestamps, 0, this.timestamps, 0, length); + int periodIndex = 0; + long last = timestamps[length - 1]; + + for (int i = length - 2; i >= 0; i--) { + if (last - timestamps[i] >= this.periodsNanos[periodIndex]) { + this.startIndices[periodIndex] = i + 1; + if (++periodIndex >= this.periodsNanos.length) { + break; + } + } + } + + if (periodIndex < this.periodsNanos.length) { + while (periodIndex < this.periodsNanos.length) { + this.periodsNanos[periodIndex] = 0L; + periodIndex++; + } + } + + this.nextIndex = length; + } + + public void setAllValues(@Nonnull long[] values) { + System.arraycopy(values, 0, this.values, 0, values.length); + } + + public long getLastValue() { + return this.nextIndex == 0 ? this.values[this.bufferSize - 1] : this.values[this.nextIndex - 1]; + } + + @Nonnull + public static HistoricMetric.Builder builder(long minimumInterval, @Nonnull TimeUnit unit) { + return new HistoricMetric.Builder(minimumInterval, unit); + } + + public static class Builder { + private final long minimumInterval; + private final LongList periods = new LongArrayList(); + + private Builder(long minimumInterval, @Nonnull TimeUnit unit) { + this.minimumInterval = unit.toNanos(minimumInterval); + } + + @Nonnull + public HistoricMetric.Builder addPeriod(long period, @Nonnull TimeUnit unit) { + long nanos = unit.toNanos(period); + + for (int i = 0; i < this.periods.size(); i++) { + if (this.periods.getLong(i) > nanos) { + throw new IllegalArgumentException("Period's must be increasing in length"); + } + } + + this.periods.add(nanos); + return this; + } + + @Nonnull + public HistoricMetric build() { + return new HistoricMetric(this); + } + } +} diff --git a/src/com/hypixel/hytale/metrics/metric/Metric.java b/src/com/hypixel/hytale/metrics/metric/Metric.java new file mode 100644 index 0000000..1324a8a --- /dev/null +++ b/src/com/hypixel/hytale/metrics/metric/Metric.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.metrics.metric; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class Metric { + public static final Codec CODEC = BuilderCodec.builder(Metric.class, Metric::new) + .append(new KeyedCodec<>("Min", Codec.LONG), (metric, s) -> metric.min = s, metric -> metric.min) + .add() + .append(new KeyedCodec<>("Average", Codec.DOUBLE), (metric, s) -> metric.average.set(s), metric -> metric.average.get()) + .add() + .append(new KeyedCodec<>("Max", Codec.LONG), (metric, s) -> metric.max = s, metric -> metric.max) + .add() + .build(); + private long min; + private final AverageCollector average = new AverageCollector(); + private long max; + + public Metric() { + this.clear(); + } + + public void add(long value) { + if (value < this.min) { + this.min = value; + } + + this.average.add(value); + if (value > this.max) { + this.max = value; + } + } + + public void remove(long value) { + this.average.remove(value); + } + + public long getMin() { + return this.min; + } + + public double getAverage() { + return this.average.get(); + } + + public long getMax() { + return this.max; + } + + public void clear() { + this.min = Long.MAX_VALUE; + this.average.clear(); + this.max = Long.MIN_VALUE; + } + + public void resetMinMax() { + this.min = Long.MAX_VALUE; + this.max = Long.MIN_VALUE; + } + + public void calculateMinMax(long value) { + if (value < this.min) { + this.min = value; + } + + if (value > this.max) { + this.max = value; + } + } + + public void addToAverage(long value) { + this.average.add(value); + } + + public void set(@Nonnull Metric metric) { + this.min = metric.min; + this.average.set(metric.average.get()); + this.max = metric.max; + } + + @Nonnull + @Override + public String toString() { + return "Metric{min=" + this.min + ", average=" + this.average.get() + ", max=" + this.max + "}"; + } +} diff --git a/src/com/hypixel/hytale/metrics/metric/SynchronizedAverageCollector.java b/src/com/hypixel/hytale/metrics/metric/SynchronizedAverageCollector.java new file mode 100644 index 0000000..a272b7c --- /dev/null +++ b/src/com/hypixel/hytale/metrics/metric/SynchronizedAverageCollector.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.metrics.metric; + +public class SynchronizedAverageCollector extends AverageCollector { + public SynchronizedAverageCollector() { + } + + @Override + public synchronized double get() { + return super.get(); + } + + @Override + public synchronized long size() { + return super.size(); + } + + @Override + public synchronized double addAndGet(double v) { + return super.addAndGet(v); + } + + @Override + public synchronized void add(double v) { + super.add(v); + } + + @Override + public synchronized void remove(double v) { + super.remove(v); + } + + @Override + public synchronized void clear() { + super.clear(); + } +} diff --git a/src/com/hypixel/hytale/plugin/early/ClassTransformer.java b/src/com/hypixel/hytale/plugin/early/ClassTransformer.java new file mode 100644 index 0000000..b5a00ca --- /dev/null +++ b/src/com/hypixel/hytale/plugin/early/ClassTransformer.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.plugin.early; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface ClassTransformer { + default int priority() { + return 0; + } + + @Nullable + byte[] transform(@Nonnull String var1, @Nonnull String var2, @Nonnull byte[] var3); +} diff --git a/src/com/hypixel/hytale/plugin/early/EarlyPluginLoader.java b/src/com/hypixel/hytale/plugin/early/EarlyPluginLoader.java new file mode 100644 index 0000000..0cf368c --- /dev/null +++ b/src/com/hypixel/hytale/plugin/early/EarlyPluginLoader.java @@ -0,0 +1,124 @@ +package com.hypixel.hytale.plugin.early; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.ServiceLoader; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class EarlyPluginLoader { + @Nonnull + public static final Path EARLY_PLUGINS_PATH = Path.of("earlyplugins"); + @Nonnull + private static final List transformers = new ObjectArrayList<>(); + @Nullable + private static URLClassLoader pluginClassLoader; + + private EarlyPluginLoader() { + } + + public static void loadEarlyPlugins(@Nonnull String[] args) { + ObjectArrayList urls = new ObjectArrayList<>(); + collectPluginJars(EARLY_PLUGINS_PATH, urls); + + for (Path path : parseEarlyPluginPaths(args)) { + collectPluginJars(path, urls); + } + + if (!urls.isEmpty()) { + pluginClassLoader = new URLClassLoader(urls.toArray(URL[]::new), EarlyPluginLoader.class.getClassLoader()); + + for (ClassTransformer transformer : ServiceLoader.load(ClassTransformer.class, pluginClassLoader)) { + System.out.println("[EarlyPlugin] Loading transformer: " + transformer.getClass().getName() + " (priority=" + transformer.priority() + ")"); + transformers.add(transformer); + } + + transformers.sort(Comparator.comparingInt(ClassTransformer::priority).reversed()); + if (!transformers.isEmpty()) { + System.err + .printf( + "===============================================================================================\n Loaded %d class transformer(s)!!\n===============================================================================================\n This is unsupported and may cause stability issues.\n Use at your own risk!!\n===============================================================================================\n", + transformers.size() + ); + boolean isSingleplayer = hasFlag(args, "--singleplayer"); + boolean acceptEarlyPlugins = hasFlag(args, "--accept-early-plugins"); + if (!isSingleplayer && !acceptEarlyPlugins) { + if (System.console() == null) { + System.err.println("ERROR: Early plugins require interactive confirmation, but no console is available."); + System.err.println("Pass --accept-early-plugins to accept the risk and continue."); + System.exit(1); + } + + System.err.print("Press ENTER to accept and continue..."); + System.console().readLine(); + } + } + } + } + + private static List parseEarlyPluginPaths(@Nonnull String[] args) { + ObjectArrayList paths = new ObjectArrayList<>(); + + for (int i = 0; i < args.length; i++) { + if (args[i].equals("--early-plugins") && i + 1 < args.length) { + for (String pathStr : args[i + 1].split(",")) { + paths.add(Path.of(pathStr.trim())); + } + } else if (args[i].startsWith("--early-plugins=")) { + String value = args[i].substring("--early-plugins=".length()); + + for (String pathStr : value.split(",")) { + paths.add(Path.of(pathStr.trim())); + } + } + } + + return paths; + } + + private static boolean hasFlag(String[] args, String flag) { + for (String arg : args) { + if (arg.equals(flag)) { + return true; + } + } + + return false; + } + + private static void collectPluginJars(Path path, List urls) { + if (Files.isDirectory(path)) { + try (DirectoryStream stream = Files.newDirectoryStream(path, "*.jar")) { + for (Path file : stream) { + if (Files.isRegularFile(file)) { + urls.add(file.toUri().toURL()); + System.out.println("[EarlyPlugin] Found: " + file.getFileName()); + } + } + } catch (IOException var7) { + System.err.println("[EarlyPlugin] Failed to scan directory " + path + ": " + var7.getMessage()); + } + } + } + + public static boolean hasTransformers() { + return !transformers.isEmpty(); + } + + public static List getTransformers() { + return Collections.unmodifiableList(transformers); + } + + @Nullable + public static URLClassLoader getPluginClassLoader() { + return pluginClassLoader; + } +} diff --git a/src/com/hypixel/hytale/plugin/early/TransformingClassLoader.java b/src/com/hypixel/hytale/plugin/early/TransformingClassLoader.java new file mode 100644 index 0000000..3939d88 --- /dev/null +++ b/src/com/hypixel/hytale/plugin/early/TransformingClassLoader.java @@ -0,0 +1,141 @@ +package com.hypixel.hytale.plugin.early; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.security.cert.Certificate; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; + +public final class TransformingClassLoader extends URLClassLoader { + private static final Set SECURE_PACKAGE_PREFIXES = Set.of( + "java.", + "javax.", + "jdk.", + "sun.", + "com.sun.", + "org.bouncycastle.", + "server.io.netty.", + "org.objectweb.asm.", + "com.google.gson.", + "org.slf4j.", + "org.apache.logging.", + "ch.qos.logback.", + "com.google.flogger.", + "server.io.sentry.", + "com.hypixel.protoplus.", + "com.hypixel.fastutil.", + "com.hypixel.hytale.plugin.early." + ); + private final List transformers; + private final ClassLoader appClassLoader; + + public TransformingClassLoader(@Nonnull URL[] urls, @Nonnull List transformers, ClassLoader parent, ClassLoader appClassLoader) { + super(urls, parent); + this.transformers = transformers; + this.appClassLoader = appClassLoader; + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + synchronized (this.getClassLoadingLock(name)) { + Class loaded = this.findLoadedClass(name); + if (loaded != null) { + if (resolve) { + this.resolveClass(loaded); + } + + return loaded; + } else if (isPreloadedClass(name)) { + Class clazz = this.appClassLoader.loadClass(name); + if (resolve) { + this.resolveClass(clazz); + } + + return clazz; + } else { + String internalName = name.replace('.', '/'); + URL resource = this.findResource(internalName + ".class"); + if (resource != null) { + Class var9; + try (InputStream is = resource.openStream()) { + Class clazz = this.transformAndDefine(name, internalName, is.readAllBytes(), resource); + if (resolve) { + this.resolveClass(clazz); + } + + var9 = clazz; + } catch (IOException var13) { + return super.loadClass(name, resolve); + } + + return var9; + } else { + return super.loadClass(name, resolve); + } + } + } + } + + private Class transformAndDefine(String name, String internalName, byte[] classBytes, URL resource) { + if (!isSecureClass(name)) { + for (ClassTransformer transformer : this.transformers) { + try { + byte[] transformed = transformer.transform(name, internalName, classBytes); + if (transformed != null) { + classBytes = transformed; + } + } catch (Exception var8) { + System.err.println("[EarlyPlugin] Transformer " + transformer.getClass().getName() + " failed on " + name + ": " + var8.getMessage()); + var8.printStackTrace(); + } + } + } + + URL codeSourceUrl = getCodeSourceUrl(resource, internalName); + CodeSource codeSource = new CodeSource(codeSourceUrl, (Certificate[])null); + ProtectionDomain protectionDomain = new ProtectionDomain(codeSource, null, this, null); + return this.defineClass(name, classBytes, 0, classBytes.length, protectionDomain); + } + + private static URL getCodeSourceUrl(URL resource, String internalName) { + String urlStr = resource.toString(); + String classPath = internalName + ".class"; + if (urlStr.startsWith("jar:")) { + int bangIndex = urlStr.indexOf("!/"); + if (bangIndex > 0) { + try { + return new URL(urlStr.substring(4, bangIndex)); + } catch (Exception var6) { + return resource; + } + } + } else if (urlStr.endsWith(classPath)) { + try { + return new URL(urlStr.substring(0, urlStr.length() - classPath.length())); + } catch (Exception var7) { + return resource; + } + } + + return resource; + } + + private static boolean isPreloadedClass(@Nonnull String name) { + return name.equals("com.hypixel.hytale.Main") || name.startsWith("com.hypixel.hytale.plugin.early."); + } + + private static boolean isSecureClass(@Nonnull String name) { + for (String prefix : SECURE_PACKAGE_PREFIXES) { + if (name.startsWith(prefix)) { + return true; + } + } + + return false; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/NoiseFunction.java b/src/com/hypixel/hytale/procedurallib/NoiseFunction.java new file mode 100644 index 0000000..9e6c65f --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/NoiseFunction.java @@ -0,0 +1,4 @@ +package com.hypixel.hytale.procedurallib; + +public interface NoiseFunction extends NoiseFunction2d, NoiseFunction3d { +} diff --git a/src/com/hypixel/hytale/procedurallib/NoiseFunction2d.java b/src/com/hypixel/hytale/procedurallib/NoiseFunction2d.java new file mode 100644 index 0000000..85d2546 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/NoiseFunction2d.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.procedurallib; + +@FunctionalInterface +public interface NoiseFunction2d { + double get(int var1, int var2, double var3, double var5); +} diff --git a/src/com/hypixel/hytale/procedurallib/NoiseFunction3d.java b/src/com/hypixel/hytale/procedurallib/NoiseFunction3d.java new file mode 100644 index 0000000..dc2ff75 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/NoiseFunction3d.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.procedurallib; + +@FunctionalInterface +public interface NoiseFunction3d { + double get(int var1, int var2, double var3, double var5, double var7); +} diff --git a/src/com/hypixel/hytale/procedurallib/NoiseFunctionPair.java b/src/com/hypixel/hytale/procedurallib/NoiseFunctionPair.java new file mode 100644 index 0000000..f5f3d39 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/NoiseFunctionPair.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.procedurallib; + +import javax.annotation.Nonnull; + +public class NoiseFunctionPair implements NoiseFunction { + protected NoiseFunction2d noiseFunction2d; + protected NoiseFunction3d noiseFunction3d; + + public NoiseFunctionPair() { + } + + public NoiseFunctionPair(NoiseFunction2d noiseFunction2d, NoiseFunction3d noiseFunction3d) { + this.noiseFunction2d = noiseFunction2d; + this.noiseFunction3d = noiseFunction3d; + } + + public NoiseFunction2d getNoiseFunction2d() { + return this.noiseFunction2d; + } + + public void setNoiseFunction2d(NoiseFunction2d noiseFunction2d) { + this.noiseFunction2d = noiseFunction2d; + } + + public NoiseFunction3d getNoiseFunction3d() { + return this.noiseFunction3d; + } + + public void setNoiseFunction3d(NoiseFunction3d noiseFunction3d) { + this.noiseFunction3d = noiseFunction3d; + } + + @Override + public double get(int seed, int offsetSeed, double x, double y) { + return this.noiseFunction2d.get(seed, offsetSeed, x, y); + } + + @Override + public double get(int seed, int offsetSeed, double x, double y, double z) { + return this.noiseFunction3d.get(seed, offsetSeed, x, y, z); + } + + @Nonnull + @Override + public String toString() { + return "NoiseFunctionPair{noiseFunction2d=" + this.noiseFunction2d + ", noiseFunction3d=" + this.noiseFunction3d + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/NoiseType.java b/src/com/hypixel/hytale/procedurallib/NoiseType.java new file mode 100644 index 0000000..51ae549 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/NoiseType.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.procedurallib; + +public enum NoiseType { + SIMPLEX, + OLD_SIMPLEX, + VALUE, + PERLIN, + CELL, + DISTANCE, + CONSTANT, + GRID, + MESH, + BRANCH, + POINT; + + private NoiseType() { + } +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/BasicHeightThresholdInterpreter.java b/src/com/hypixel/hytale/procedurallib/condition/BasicHeightThresholdInterpreter.java new file mode 100644 index 0000000..ad5a7ca --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/BasicHeightThresholdInterpreter.java @@ -0,0 +1,110 @@ +package com.hypixel.hytale.procedurallib.condition; + +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class BasicHeightThresholdInterpreter implements IHeightThresholdInterpreter { + @Nonnull + protected final float[] interpolatedThresholds; + protected final int lowestNonOne; + protected final int highestNonZero; + + public BasicHeightThresholdInterpreter(@Nonnull int[] positions, @Nonnull float[] thresholds, int length) { + if (positions.length != thresholds.length) { + throw new IllegalArgumentException(String.format("Mismatching array lengths! positions: %s, thresholds: %s", positions.length, thresholds.length)); + } else { + this.interpolatedThresholds = new float[length]; + + for (int y = 0; y < this.interpolatedThresholds.length; y++) { + float threshold = thresholds[thresholds.length - 1]; + + for (int i = 0; i < positions.length; i++) { + if (y < positions[i]) { + if (i == 0) { + threshold = thresholds[i]; + } else { + float distance = (float)(y - positions[i - 1]) / (positions[i] - positions[i - 1]); + threshold = IHeightThresholdInterpreter.lerp(thresholds[i - 1], thresholds[i], distance); + } + break; + } + } + + this.interpolatedThresholds[y] = threshold; + } + + int lowestNonOne = 0; + + while (lowestNonOne < length && !(this.interpolatedThresholds[lowestNonOne] < 1.0F)) { + lowestNonOne++; + } + + this.lowestNonOne = lowestNonOne; + int highestNonZero = length - 1; + + while (highestNonZero >= 0 && !(this.interpolatedThresholds[highestNonZero] > 0.0F)) { + highestNonZero--; + } + + this.highestNonZero = highestNonZero; + } + } + + @Override + public int getLowestNonOne() { + return this.lowestNonOne; + } + + @Override + public int getHighestNonZero() { + return this.highestNonZero; + } + + @Override + public double getContext(int seed, double x, double y) { + return 0.0; + } + + @Override + public int getLength() { + return this.interpolatedThresholds.length; + } + + @Override + public float getThreshold(int seed, double x, double y, int height) { + return this.getThreshold(seed, x, y, height, this.getContext(seed, x, y)); + } + + @Override + public float getThreshold(int seed, double x, double y, int height, double context) { + if (height > this.highestNonZero) { + return 0.0F; + } else { + if (height < 0) { + height = 0; + } + + if (height >= this.interpolatedThresholds.length) { + height = this.interpolatedThresholds.length - 1; + } + + return this.interpolatedThresholds[height]; + } + } + + @Nonnull + @Override + public String toString() { + return "BasicHeightThresholdInterpreter{interpolatedThresholds=" + + Arrays.toString(this.interpolatedThresholds) + + ", lowestNonOne=" + + this.lowestNonOne + + ", highestNonZero=" + + this.highestNonZero + + "}"; + } + + public interface Constants { + String ERROR_ARRAY_LENGTH = "Mismatching array lengths! positions: %s, thresholds: %s"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/ConstantBlockFluidCondition.java b/src/com/hypixel/hytale/procedurallib/condition/ConstantBlockFluidCondition.java new file mode 100644 index 0000000..b56f207 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/ConstantBlockFluidCondition.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.procedurallib.condition; + +import javax.annotation.Nonnull; + +public class ConstantBlockFluidCondition implements IBlockFluidCondition { + public static final ConstantBlockFluidCondition DEFAULT_TRUE = new ConstantBlockFluidCondition(true); + public static final ConstantBlockFluidCondition DEFAULT_FALSE = new ConstantBlockFluidCondition(false); + protected final boolean result; + + public ConstantBlockFluidCondition(boolean result) { + this.result = result; + } + + @Override + public boolean eval(int block, int fluid) { + return this.result; + } + + @Nonnull + @Override + public String toString() { + return "ConstantBlockFluidCondition{result=" + this.result + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/ConstantIntCondition.java b/src/com/hypixel/hytale/procedurallib/condition/ConstantIntCondition.java new file mode 100644 index 0000000..10a459d --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/ConstantIntCondition.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.procedurallib.condition; + +import javax.annotation.Nonnull; + +public class ConstantIntCondition implements IIntCondition { + public static final ConstantIntCondition DEFAULT_TRUE = new ConstantIntCondition(true); + public static final ConstantIntCondition DEFAULT_FALSE = new ConstantIntCondition(false); + protected final boolean result; + + public ConstantIntCondition(boolean result) { + this.result = result; + } + + public boolean getResult() { + return this.result; + } + + @Override + public boolean eval(int value) { + return this.result; + } + + @Nonnull + @Override + public String toString() { + return "ConstantIntCondition{result=" + this.result + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/DefaultCoordinateCondition.java b/src/com/hypixel/hytale/procedurallib/condition/DefaultCoordinateCondition.java new file mode 100644 index 0000000..51b64d8 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/DefaultCoordinateCondition.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.procedurallib.condition; + +import javax.annotation.Nonnull; + +public class DefaultCoordinateCondition implements ICoordinateCondition { + public static final DefaultCoordinateCondition DEFAULT_TRUE = new DefaultCoordinateCondition(true); + public static final DefaultCoordinateCondition DEFAULT_FALSE = new DefaultCoordinateCondition(false); + protected final boolean result; + + private DefaultCoordinateCondition(boolean result) { + this.result = result; + } + + public boolean getResult() { + return this.result; + } + + @Override + public boolean eval(int seed, int x, int y) { + return this.result; + } + + @Override + public boolean eval(int seed, int x, int y, int z) { + return this.result; + } + + @Nonnull + @Override + public String toString() { + return "DefaultCoordinateCondition{result=" + this.result + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/DefaultCoordinateRndCondition.java b/src/com/hypixel/hytale/procedurallib/condition/DefaultCoordinateRndCondition.java new file mode 100644 index 0000000..a0b422f --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/DefaultCoordinateRndCondition.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.procedurallib.condition; + +import java.util.Random; +import javax.annotation.Nonnull; + +public class DefaultCoordinateRndCondition implements ICoordinateRndCondition { + public static final DefaultCoordinateRndCondition DEFAULT_TRUE = new DefaultCoordinateRndCondition(true); + public static final DefaultCoordinateRndCondition DEFAULT_FALSE = new DefaultCoordinateRndCondition(false); + protected final boolean result; + + public DefaultCoordinateRndCondition(boolean result) { + this.result = result; + } + + public boolean getResult() { + return this.result; + } + + @Override + public boolean eval(int seed, int x, int z, int y, Random random) { + return this.result; + } + + @Nonnull + @Override + public String toString() { + return "DefaultCoordinateRndCondition{result=" + this.result + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/DefaultDoubleCondition.java b/src/com/hypixel/hytale/procedurallib/condition/DefaultDoubleCondition.java new file mode 100644 index 0000000..2eb7806 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/DefaultDoubleCondition.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.procedurallib.condition; + +import javax.annotation.Nonnull; + +public class DefaultDoubleCondition implements IDoubleCondition { + public static final DefaultDoubleCondition DEFAULT_TRUE = new DefaultDoubleCondition(true); + public static final DefaultDoubleCondition DEFAULT_FALSE = new DefaultDoubleCondition(false); + protected final boolean result; + + public DefaultDoubleCondition(boolean result) { + this.result = result; + } + + public boolean getResult() { + return this.result; + } + + @Override + public boolean eval(double value) { + return this.result; + } + + @Nonnull + @Override + public String toString() { + return "DefaultDoubleCondition{result=" + this.result + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/DefaultDoubleThresholdCondition.java b/src/com/hypixel/hytale/procedurallib/condition/DefaultDoubleThresholdCondition.java new file mode 100644 index 0000000..163d0d1 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/DefaultDoubleThresholdCondition.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.procedurallib.condition; + +import javax.annotation.Nonnull; + +public class DefaultDoubleThresholdCondition implements IDoubleThreshold { + public static final DefaultDoubleThresholdCondition DEFAULT_TRUE = new DefaultDoubleThresholdCondition(true); + public static final DefaultDoubleThresholdCondition DEFAULT_FALSE = new DefaultDoubleThresholdCondition(false); + protected final boolean result; + + public DefaultDoubleThresholdCondition(boolean result) { + this.result = result; + } + + public boolean isResult() { + return this.result; + } + + @Override + public boolean eval(double d) { + return this.result; + } + + @Override + public boolean eval(double d, double factor) { + return this.result; + } + + @Nonnull + @Override + public String toString() { + return "DefaultDoubleThresholdCondition{result=" + this.result + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/DoubleThreshold.java b/src/com/hypixel/hytale/procedurallib/condition/DoubleThreshold.java new file mode 100644 index 0000000..fd298fb --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/DoubleThreshold.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.procedurallib.condition; + +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class DoubleThreshold { + public DoubleThreshold() { + } + + public static class Multiple implements IDoubleThreshold { + protected final DoubleThreshold.Single[] singles; + + public Multiple(DoubleThreshold.Single[] singles) { + this.singles = singles; + } + + @Override + public boolean eval(double d) { + for (DoubleThreshold.Single single : this.singles) { + if (single.eval(d)) { + return true; + } + } + + return false; + } + + @Override + public boolean eval(double d, double factor) { + for (DoubleThreshold.Single single : this.singles) { + if (single.eval(d, factor)) { + return true; + } + } + + return false; + } + + @Nonnull + @Override + public String toString() { + return "DoubleThreshold.Multiple{singles=" + Arrays.toString((Object[])this.singles) + "}"; + } + } + + public static class Single implements IDoubleThreshold { + protected final double min; + protected final double max; + protected final double halfRange; + + public Single(double min, double max) { + this.min = min; + this.max = max; + this.halfRange = (max - min) * 0.5; + } + + @Override + public boolean eval(double d) { + return this.min <= d && d <= this.max; + } + + @Override + public boolean eval(double d, double factor) { + double t0 = this.min + this.halfRange * factor; + double t1 = this.max - this.halfRange * factor; + return t0 <= d && d <= t1; + } + + @Nonnull + @Override + public String toString() { + return "DoubleThreshold.Single{min=" + this.min + ", max=" + this.max + ", halfRange=" + this.halfRange + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/DoubleThresholdCondition.java b/src/com/hypixel/hytale/procedurallib/condition/DoubleThresholdCondition.java new file mode 100644 index 0000000..d272a53 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/DoubleThresholdCondition.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.procedurallib.condition; + +import javax.annotation.Nonnull; + +public class DoubleThresholdCondition implements IDoubleCondition { + protected final IDoubleThreshold threshold; + + public DoubleThresholdCondition(IDoubleThreshold threshold) { + this.threshold = threshold; + } + + @Override + public boolean eval(double value) { + return this.threshold.eval(value); + } + + @Nonnull + @Override + public String toString() { + return "DoubleThresholdCondition{threshold=" + this.threshold + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/HeightCondition.java b/src/com/hypixel/hytale/procedurallib/condition/HeightCondition.java new file mode 100644 index 0000000..7c239d5 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/HeightCondition.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.procedurallib.condition; + +import java.util.Random; +import javax.annotation.Nonnull; + +public class HeightCondition implements ICoordinateRndCondition { + protected final IHeightThresholdInterpreter interpreter; + + public HeightCondition(IHeightThresholdInterpreter interpreter) { + this.interpreter = interpreter; + } + + @Override + public boolean eval(int seed, int x, int z, int y, @Nonnull Random random) { + double threshold = this.interpreter.getThreshold(seed, x, z, y); + return threshold > 0.0 && (threshold >= 1.0 || threshold > random.nextDouble()); + } + + @Nonnull + @Override + public String toString() { + return "HeightCondition{interpreter=" + this.interpreter + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/HeightThresholdCoordinateCondition.java b/src/com/hypixel/hytale/procedurallib/condition/HeightThresholdCoordinateCondition.java new file mode 100644 index 0000000..699c224 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/HeightThresholdCoordinateCondition.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.procedurallib.condition; + +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.procedurallib.logic.GeneralNoise; +import javax.annotation.Nonnull; + +public class HeightThresholdCoordinateCondition implements ICoordinateCondition { + private final IHeightThresholdInterpreter interpreter; + + public HeightThresholdCoordinateCondition(IHeightThresholdInterpreter interpreter) { + this.interpreter = interpreter; + } + + @Override + public boolean eval(int seed, int x, int y) { + throw new UnsupportedOperationException("This needs a height to operate."); + } + + @Override + public boolean eval(int seed, int x, int y, int z) { + return this.interpreter.getThreshold(seed, x, z, y) + >= HashUtil.random(seed, GeneralNoise.fastFloor(x), GeneralNoise.fastFloor(y), GeneralNoise.fastFloor(z)); + } + + @Nonnull + @Override + public String toString() { + return "HeightThresholdCoordinateCondition{interpreter=" + this.interpreter + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/IBlockFluidCondition.java b/src/com/hypixel/hytale/procedurallib/condition/IBlockFluidCondition.java new file mode 100644 index 0000000..46e09b9 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/IBlockFluidCondition.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.procedurallib.condition; + +public interface IBlockFluidCondition { + boolean eval(int var1, int var2); +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/ICoordinateCondition.java b/src/com/hypixel/hytale/procedurallib/condition/ICoordinateCondition.java new file mode 100644 index 0000000..3f42ec0 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/ICoordinateCondition.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.procedurallib.condition; + +public interface ICoordinateCondition { + boolean eval(int var1, int var2, int var3); + + boolean eval(int var1, int var2, int var3, int var4); +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/ICoordinateRndCondition.java b/src/com/hypixel/hytale/procedurallib/condition/ICoordinateRndCondition.java new file mode 100644 index 0000000..e53efb0 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/ICoordinateRndCondition.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.procedurallib.condition; + +import java.util.Random; + +public interface ICoordinateRndCondition { + boolean eval(int var1, int var2, int var3, int var4, Random var5); +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/IDoubleCondition.java b/src/com/hypixel/hytale/procedurallib/condition/IDoubleCondition.java new file mode 100644 index 0000000..93de328 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/IDoubleCondition.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.procedurallib.condition; + +import java.util.function.IntToDoubleFunction; +import javax.annotation.Nonnull; + +public interface IDoubleCondition { + boolean eval(double var1); + + default boolean eval(int seed, @Nonnull IntToDoubleFunction seedFunction) { + return this.eval(seedFunction.applyAsDouble(seed)); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/IDoubleThreshold.java b/src/com/hypixel/hytale/procedurallib/condition/IDoubleThreshold.java new file mode 100644 index 0000000..00b37ea --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/IDoubleThreshold.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.procedurallib.condition; + +public interface IDoubleThreshold { + boolean eval(double var1); + + boolean eval(double var1, double var3); +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/IHeightThresholdInterpreter.java b/src/com/hypixel/hytale/procedurallib/condition/IHeightThresholdInterpreter.java new file mode 100644 index 0000000..f510ed1 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/IHeightThresholdInterpreter.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.procedurallib.condition; + +public interface IHeightThresholdInterpreter { + int getLowestNonOne(); + + int getHighestNonZero(); + + float getThreshold(int var1, double var2, double var4, int var6); + + float getThreshold(int var1, double var2, double var4, int var6, double var7); + + double getContext(int var1, double var2, double var4); + + int getLength(); + + default boolean isSpawnable(int height) { + return height >= this.getLowestNonOne() && height <= this.getHighestNonZero(); + } + + static float lerp(float from, float to, float t) { + return from + (to - from) * t; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/IIntCondition.java b/src/com/hypixel/hytale/procedurallib/condition/IIntCondition.java new file mode 100644 index 0000000..1bd6d81 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/IIntCondition.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.procedurallib.condition; + +import com.hypixel.hytale.procedurallib.util.IntToIntFunction; +import javax.annotation.Nonnull; + +public interface IIntCondition { + boolean eval(int var1); + + default boolean eval(int seed, @Nonnull IntToIntFunction seedFunction) { + return this.eval(seedFunction.applyAsInt(seed)); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/NoiseHeightThresholdInterpreter.java b/src/com/hypixel/hytale/procedurallib/condition/NoiseHeightThresholdInterpreter.java new file mode 100644 index 0000000..b2c4da0 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/NoiseHeightThresholdInterpreter.java @@ -0,0 +1,131 @@ +package com.hypixel.hytale.procedurallib.condition; + +import com.hypixel.hytale.procedurallib.property.NoiseProperty; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class NoiseHeightThresholdInterpreter implements IHeightThresholdInterpreter { + protected final NoiseProperty noise; + @Nonnull + protected final float[] keys; + @Nonnull + protected final IHeightThresholdInterpreter[] values; + protected final int length; + protected final int lowestNonOne; + protected final int highestNonZero; + + public NoiseHeightThresholdInterpreter(NoiseProperty noise, @Nonnull float[] keys, @Nonnull IHeightThresholdInterpreter[] values) { + if (keys.length != values.length) { + throw new IllegalStateException("Length of keys and values are different!"); + } else { + checkInterpreterLength(values); + this.noise = noise; + this.keys = keys; + this.values = values; + this.length = values[0].getLength(); + int lowestNonOne = 0; + + for (IHeightThresholdInterpreter value : values) { + if (value.getLowestNonOne() < lowestNonOne) { + lowestNonOne = value.getLowestNonOne(); + } + } + + this.lowestNonOne = lowestNonOne; + int highestNonZero = this.length - 1; + + for (IHeightThresholdInterpreter valuex : values) { + if (valuex.getHighestNonZero() > highestNonZero) { + highestNonZero = valuex.getHighestNonZero(); + } + } + + this.highestNonZero = highestNonZero; + } + } + + @Override + public int getLowestNonOne() { + return this.lowestNonOne; + } + + @Override + public int getHighestNonZero() { + return this.highestNonZero; + } + + protected double noise(int seed, double x, double y) { + return this.noise.get(seed, x, y); + } + + @Override + public double getContext(int seed, double x, double y) { + return this.noise(seed, x, y); + } + + @Override + public int getLength() { + return this.length; + } + + @Override + public float getThreshold(int seed, double x, double z, int height) { + return this.getThreshold(seed, x, z, height, this.getContext(seed, x, z)); + } + + @Override + public float getThreshold(int seed, double x, double z, int height, double context) { + if (height > this.highestNonZero) { + return 0.0F; + } else { + int length = this.keys.length; + + for (int i = 0; i < length; i++) { + if (context <= this.keys[i]) { + if (i == 0) { + return this.values[0].getThreshold(seed, x, z, height); + } + + float distance = ((float)context - this.keys[i - 1]) / (this.keys[i] - this.keys[i - 1]); + return IHeightThresholdInterpreter.lerp( + this.values[i - 1].getThreshold(seed, x, z, height), this.values[i].getThreshold(seed, x, z, height), distance + ); + } + } + + return this.values[length - 1].getThreshold(seed, x, z, height); + } + } + + static float lerp(float from, float to, float t) { + return from + (to - from) * t; + } + + private static void checkInterpreterLength(@Nonnull IHeightThresholdInterpreter[] values) { + int length = values[0].getLength(); + + for (int i = 1; i < values.length; i++) { + if (values[i].getLength() != length) { + throw new IllegalStateException("ThresholdKeyInterpreter have different size!"); + } + } + } + + @Nonnull + @Override + public String toString() { + return "NoiseHeightThresholdInterpreter{noise=" + + this.noise + + ", keys=" + + Arrays.toString(this.keys) + + ", values=" + + Arrays.toString((Object[])this.values) + + ", length=" + + this.length + + ", lowestNonOne=" + + this.lowestNonOne + + ", highestNonZero=" + + this.highestNonZero + + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/NoiseMaskCondition.java b/src/com/hypixel/hytale/procedurallib/condition/NoiseMaskCondition.java new file mode 100644 index 0000000..8de1f1c --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/NoiseMaskCondition.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.procedurallib.condition; + +import com.hypixel.hytale.procedurallib.property.NoiseProperty; +import javax.annotation.Nonnull; + +public class NoiseMaskCondition implements ICoordinateCondition { + protected final NoiseProperty noiseMask; + protected final IDoubleCondition condition; + + public NoiseMaskCondition(NoiseProperty noiseMask, IDoubleCondition condition) { + this.noiseMask = noiseMask; + this.condition = condition; + } + + @Override + public boolean eval(int seed, int x, int y) { + return this.condition.eval(this.noiseMask.get(seed, x, y)); + } + + @Override + public boolean eval(int seed, int x, int y, int z) { + return this.condition.eval(this.noiseMask.get(seed, x, y, z)); + } + + @Nonnull + @Override + public String toString() { + return "NoiseMaskCondition{noiseMask=" + this.noiseMask + ", condition=" + this.condition + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/condition/SingleDoubleCondition.java b/src/com/hypixel/hytale/procedurallib/condition/SingleDoubleCondition.java new file mode 100644 index 0000000..484da2d --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/condition/SingleDoubleCondition.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.procedurallib.condition; + +import javax.annotation.Nonnull; + +public class SingleDoubleCondition implements IDoubleCondition { + protected final double value; + + public SingleDoubleCondition(double value) { + this.value = value; + } + + @Override + public boolean eval(double value) { + return value < this.value; + } + + @Nonnull + @Override + public String toString() { + return "SingleDoubleCondition{value=" + this.value + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/AbstractCellJitterJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/AbstractCellJitterJsonLoader.java new file mode 100644 index 0000000..7784e1d --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/AbstractCellJitterJsonLoader.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.logic.cell.jitter.CellJitter; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public abstract class AbstractCellJitterJsonLoader extends JsonLoader { + public AbstractCellJitterJsonLoader(SeedString seed, Path dataFolder, JsonElement json) { + super(seed, dataFolder, json); + } + + @Nonnull + protected CellJitter loadJitter() { + double defaultJitter = this.loadDefaultJitter(); + double x = this.loadJitterX(defaultJitter); + double y = this.loadJitterY(defaultJitter); + double z = this.loadJitterZ(defaultJitter); + return CellJitter.of(x, y, z); + } + + protected double loadDefaultJitter() { + return loadJitter(this, "Jitter", 1.0); + } + + protected double loadJitterX(double defaultJitter) { + return loadJitter(this, "JitterX", defaultJitter); + } + + protected double loadJitterY(double defaultJitter) { + return loadJitter(this, "JitterY", defaultJitter); + } + + protected double loadJitterZ(double defaultJitter) { + return loadJitter(this, "JitterZ", defaultJitter); + } + + protected static double loadJitter(@Nonnull JsonLoader loader, String key, double defaultJitter) { + return !loader.has(key) ? defaultJitter : loader.get(key).getAsDouble(); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/BasicHeightThresholdInterpreterJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/BasicHeightThresholdInterpreterJsonLoader.java new file mode 100644 index 0000000..4e9bf31 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/BasicHeightThresholdInterpreterJsonLoader.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.condition.BasicHeightThresholdInterpreter; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class BasicHeightThresholdInterpreterJsonLoader extends JsonLoader { + protected final int length; + + public BasicHeightThresholdInterpreterJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json, int length) { + super(seed.append(".BasicHeightThresholdInterpreter"), dataFolder, json); + this.length = length; + } + + @Nonnull + public BasicHeightThresholdInterpreter load() { + return new BasicHeightThresholdInterpreter(this.loadPositions(), this.loadValues(), this.length); + } + + protected int[] loadPositions() { + if (!this.has("Positions")) { + throw new IllegalStateException("Could not find position data in HeightThresholdInterpreter. Keyword: Positions"); + } else { + JsonArray terrainNoiseKeyPositionsJson = this.get("Positions").getAsJsonArray(); + int[] positions = new int[terrainNoiseKeyPositionsJson.size()]; + + for (int i = 0; i < positions.length; i++) { + positions[i] = terrainNoiseKeyPositionsJson.get(i).getAsInt(); + } + + return positions; + } + } + + protected float[] loadValues() { + if (!this.has("Values")) { + throw new IllegalStateException("Could not find value data in HeightThresholdInterpreter. Keyword: Values"); + } else { + JsonArray terrainNoiseKeyThresholdsJson = this.get("Values").getAsJsonArray(); + float[] thresholds = new float[terrainNoiseKeyThresholdsJson.size()]; + + for (int i = 0; i < thresholds.length; i++) { + thresholds[i] = terrainNoiseKeyThresholdsJson.get(i).getAsFloat(); + } + + return thresholds; + } + } + + public interface Constants { + String KEY_POSITIONS = "Positions"; + String KEY_VALUES = "Values"; + String ERROR_NO_POSITIONS = "Could not find position data in HeightThresholdInterpreter. Keyword: Positions"; + String ERROR_NO_VALUES = "Could not find value data in HeightThresholdInterpreter. Keyword: Values"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/BlendNoisePropertyJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/BlendNoisePropertyJsonLoader.java new file mode 100644 index 0000000..c51c434 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/BlendNoisePropertyJsonLoader.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.property.BlendNoiseProperty; +import com.hypixel.hytale.procedurallib.property.NoiseProperty; +import java.nio.file.Path; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlendNoisePropertyJsonLoader extends JsonLoader { + public BlendNoisePropertyJsonLoader(SeedString seed, Path dataFolder, @Nullable JsonElement json) { + super(seed, dataFolder, json); + } + + @Nonnull + public BlendNoiseProperty load() { + NoiseProperty alpha = this.loadAlpha(); + NoiseProperty[] noise = this.loadNoise(); + double[] thresholds = this.loadThresholds(); + validate(noise, thresholds); + return new BlendNoiseProperty(alpha, noise, thresholds); + } + + protected NoiseProperty loadAlpha() { + return new NoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.mustGetObject("Alpha", null)).load(); + } + + protected NoiseProperty[] loadNoise() { + JsonArray noise = this.mustGetArray("Noise", BlendNoisePropertyJsonLoader.Constants.EMPTY_ARRAY); + NoiseProperty[] noises = new NoiseProperty[noise.size()]; + + for (int i = 0; i < noise.size(); i++) { + noises[i] = new NoisePropertyJsonLoader<>(this.seed, this.dataFolder, noise.get(i)).load(); + } + + return noises; + } + + protected double[] loadThresholds() { + JsonArray thresholds = this.mustGetArray("Thresholds", BlendNoisePropertyJsonLoader.Constants.EMPTY_ARRAY); + double[] values = new double[thresholds.size()]; + + for (int i = 0; i < thresholds.size(); i++) { + values[i] = mustGet("$" + i, thresholds.get(i), null, Number.class, JsonLoader::isNumber, JsonElement::getAsNumber).doubleValue(); + } + + return values; + } + + protected static void validate(NoiseProperty[] noises, double[] thresholds) { + if (noises.length != thresholds.length) { + throw new IllegalStateException("Number of noises must match number of thresholds"); + } else { + double previous = Double.NEGATIVE_INFINITY; + + for (int i = 0; i < thresholds.length; i++) { + if (thresholds[i] <= previous) { + throw new IllegalStateException("Thresholds must be in ascending order and cannot be equal"); + } + + previous = thresholds[i]; + } + } + } + + public interface Constants { + String KEY_ALPHA = "Alpha"; + String KEY_NOISE = "Noise"; + String KEY_THRESHOLDS = "Thresholds"; + JsonArray EMPTY_ARRAY = new JsonArray(); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/BranchNoiseJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/BranchNoiseJsonLoader.java new file mode 100644 index 0000000..f6697c7 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/BranchNoiseJsonLoader.java @@ -0,0 +1,244 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.condition.ConstantIntCondition; +import com.hypixel.hytale.procedurallib.condition.IDoubleCondition; +import com.hypixel.hytale.procedurallib.condition.IIntCondition; +import com.hypixel.hytale.procedurallib.logic.BranchNoise; +import com.hypixel.hytale.procedurallib.logic.DistanceNoise; +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import com.hypixel.hytale.procedurallib.logic.cell.CellDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.CellPointFunction; +import com.hypixel.hytale.procedurallib.logic.cell.CellType; +import com.hypixel.hytale.procedurallib.logic.cell.DistanceCalculationMode; +import com.hypixel.hytale.procedurallib.logic.cell.GridCellDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.HexCellDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.BranchEvaluator; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.DensityPointEvaluator; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.PointEvaluator; +import com.hypixel.hytale.procedurallib.logic.cell.jitter.CellJitter; +import com.hypixel.hytale.procedurallib.property.NoiseFormulaProperty; +import com.hypixel.hytale.procedurallib.supplier.DoubleRange; +import com.hypixel.hytale.procedurallib.supplier.IDoubleRange; +import java.nio.file.Path; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BranchNoiseJsonLoader extends AbstractCellJitterJsonLoader { + public BranchNoiseJsonLoader(SeedString seed, Path dataFolder, JsonElement json) { + super(seed, dataFolder, json); + } + + @Nonnull + public BranchNoise load() { + T resource = this.seed.get(); + CellType parentCellType = this.loadParentCellType(); + CellDistanceFunction parentFunction = getCellDistanceFunction(parentCellType); + PointEvaluator parentEvaluator = this.loadParentEvaluator(); + double parentValue = this.loadDouble("ParentValue", 0.0); + IDoubleRange parentFade = this.loadRange("ParentFade", 0.0); + IIntCondition parentDensity = this.loadParentDensity(); + DistanceNoise.Distance2Function parentDistanceType = this.loadParentDistance2Function(); + NoiseFormulaProperty.NoiseFormula.Formula parentFormula = this.loadParentFormula(); + CellType lineCellType = this.loadLineCellType(); + CellPointFunction linePointFunction = getCellPointFunction(lineCellType); + double lineScale = this.loadDouble("LineScale", 0.1); + IDoubleRange lineThickness = this.loadRange("LineThickness", 0.1); + CellDistanceFunction lineFunction = getCellDistanceFunction(lineCellType); + PointEvaluator lineEvaluator = this.loadLineEvaluator(parentFunction, linePointFunction, lineScale); + return new BranchNoiseJsonLoader.LoadedBranchNoise( + parentFunction, + parentEvaluator, + parentValue, + parentFade, + parentDensity, + parentDistanceType, + parentFormula, + lineFunction, + lineEvaluator, + lineScale, + lineThickness, + resource + ); + } + + protected CellType loadParentCellType() { + return this.loadEnum("ParentType", CellType::valueOf, BranchNoiseJsonLoader.Constant.DEFAULT_PARENT_CELL_TYPE); + } + + protected CellType loadLineCellType() { + return this.loadEnum("LineType", CellType::valueOf, BranchNoiseJsonLoader.Constant.DEFAULT_LINE_CELL_TYPE); + } + + protected PointEvaluator loadParentEvaluator() { + double defaultJitter = this.loadDouble("ParentJitter", 1.0); + double jitterX = this.loadDouble("ParentJitterX", defaultJitter); + double jitterY = this.loadDouble("ParentJitterY", defaultJitter); + CellJitter jitter = CellJitter.of(jitterX, jitterY, 1.0); + DistanceCalculationMode distanceMode = this.loadEnum( + "ParentDistanceMode", DistanceCalculationMode::valueOf, BranchNoiseJsonLoader.Constant.DEFAULT_PARENT_DISTANCE_CAL_MODE + ); + return PointEvaluator.of(distanceMode.getFunction(), null, null, jitter); + } + + @Nonnull + protected IIntCondition loadParentDensity() { + IIntCondition density = ConstantIntCondition.DEFAULT_TRUE; + if (this.has("ParentDensity")) { + IDoubleCondition densityRange = new DoubleConditionJsonLoader<>(this.seed, this.dataFolder, this.get("ParentDensity")).load(); + density = DensityPointEvaluator.getDensityCondition(densityRange); + } + + return density; + } + + protected DistanceNoise.Distance2Function loadParentDistance2Function() { + return this.loadEnum("ParentDistanceType", DistanceNoise.Distance2Mode::valueOf, BranchNoiseJsonLoader.Constant.DEFAULT_PARENT_DISTANCE_TYPE) + .getFunction(); + } + + protected NoiseFormulaProperty.NoiseFormula.Formula loadParentFormula() { + return this.loadEnum("ParentFormula", NoiseFormulaProperty.NoiseFormula::valueOf, BranchNoiseJsonLoader.Constant.DEFAULT_PARENT_FORMULA).getFormula(); + } + + @Nonnull + protected PointEvaluator loadLineEvaluator(@Nonnull CellDistanceFunction parentFunction, @Nonnull CellPointFunction linePointFunction, double lineScale) { + double defaultJitter = this.loadDouble("LineJitter", 1.0); + double jitterX = this.loadDouble("LineJitterX", defaultJitter); + double jitterY = this.loadDouble("LineJitterY", defaultJitter); + CellJitter jitter = CellJitter.of(jitterX, jitterY, 1.0); + BranchEvaluator.Direction direction = this.loadEnum( + "LineDirection", BranchEvaluator.Direction::valueOf, BranchNoiseJsonLoader.Constant.DEFAULT_LINE_DIRECTION + ); + return new BranchEvaluator(parentFunction, linePointFunction, direction, jitter, lineScale); + } + + protected double loadDouble(String key, double def) { + return !this.has(key) ? def : this.get(key).getAsDouble(); + } + + @Nullable + protected IDoubleRange loadRange(String key, double def) { + return (IDoubleRange)(!this.has(key) ? new DoubleRange.Constant(def) : new DoubleRangeJsonLoader<>(this.seed, this.dataFolder, this.get(key)).load()); + } + + protected > E loadEnum(String key, @Nonnull Function valueOf, E def) { + return !this.has(key) ? def : valueOf.apply(this.get(key).getAsString()); + } + + @Nonnull + protected static CellDistanceFunction getCellDistanceFunction(@Nonnull CellType cellType) { + return (CellDistanceFunction)(switch (cellType) { + case SQUARE -> GridCellDistanceFunction.DISTANCE_FUNCTION; + case HEX -> HexCellDistanceFunction.DISTANCE_FUNCTION; + }); + } + + @Nonnull + protected static CellPointFunction getCellPointFunction(@Nonnull CellType cellType) { + return switch (cellType) { + case SQUARE -> GridCellDistanceFunction.POINT_FUNCTION; + case HEX -> HexCellDistanceFunction.POINT_FUNCTION; + }; + } + + public interface Constant { + String KEY_PARENT_TYPE = "ParentType"; + String KEY_PARENT_DISTANCE_CALCULATION_MODE = "ParentDistanceMode"; + String KEY_PARENT_DISTANCE_TYPE = "ParentDistanceType"; + String KEY_PARENT_FORMULA = "ParentFormula"; + String KEY_PARENT_JITTER = "ParentJitter"; + String KEY_PARENT_JITTER_X = "ParentJitterX"; + String KEY_PARENT_JITTER_Y = "ParentJitterY"; + String KEY_PARENT_VALUE = "ParentValue"; + String KEY_PARENT_FADE = "ParentFade"; + String KEY_PARENT_DENSITY = "ParentDensity"; + String KEY_LINE_TYPE = "LineType"; + String KEY_LINE_DIRECTION_MODE = "LineDirection"; + String KEY_LINE_SCALE = "LineScale"; + String KEY_LINE_JITTER = "LineJitter"; + String KEY_LINE_JITTER_X = "LineJitterX"; + String KEY_LINE_JITTER_Y = "LineJitterY"; + String KEY_LINE_THICKNESS = "LineThickness"; + double DEFAULT_JITTER = 1.0; + double DEFAULT_PARENT_VALUE = 0.0; + double DEFAULT_PARENT_FADE = 0.0; + double DEFAULT_LINE_SCALE = 0.1; + double DEFAULT_LINE_THICKNESS = 0.1; + CellType DEFAULT_PARENT_CELL_TYPE = CellType.SQUARE; + CellType DEFAULT_LINE_CELL_TYPE = CellType.SQUARE; + BranchEvaluator.Direction DEFAULT_LINE_DIRECTION = BranchEvaluator.Direction.OUTWARD; + DistanceCalculationMode DEFAULT_PARENT_DISTANCE_CAL_MODE = DistanceCalculationMode.EUCLIDEAN; + DistanceNoise.Distance2Mode DEFAULT_PARENT_DISTANCE_TYPE = DistanceNoise.Distance2Mode.DIV; + NoiseFormulaProperty.NoiseFormula DEFAULT_PARENT_FORMULA = NoiseFormulaProperty.NoiseFormula.SQRT; + } + + protected static class LoadedBranchNoise extends BranchNoise { + protected final SeedResource seedResource; + + public LoadedBranchNoise( + CellDistanceFunction parentFunction, + PointEvaluator parentEvaluator, + double parentValue, + IDoubleRange parentFade, + IIntCondition parentDensity, + DistanceNoise.Distance2Function distance2Function, + NoiseFormulaProperty.NoiseFormula.Formula noiseFormula, + CellDistanceFunction lineFunction, + PointEvaluator lineEvaluator, + double lineScale, + IDoubleRange lineThickness, + SeedResource seedResource + ) { + super( + parentFunction, + parentEvaluator, + parentValue, + parentFade, + parentDensity, + distance2Function, + noiseFormula, + lineFunction, + lineEvaluator, + lineScale, + lineThickness + ); + this.seedResource = seedResource; + } + + @Nonnull + @Override + protected ResultBuffer.ResultBuffer2d localBuffer2d() { + return this.seedResource.localBuffer2d(); + } + + @Nonnull + @Override + public String toString() { + return "LoadedBranchNoise{seedResource=" + + this.seedResource + + ", parentFunction=" + + this.parentFunction + + ", parentEvaluator=" + + this.parentEvaluator + + ", parentValue=" + + this.parentValue + + ", parentFade=" + + this.parentFade + + ", distance2Function=" + + this.distance2Function + + ", noiseFormula=" + + this.noiseFormula + + ", lineFunction=" + + this.lineFunction + + ", lineEvaluator=" + + this.lineEvaluator + + ", lineScale=" + + this.lineScale + + ", lineThickness=" + + this.lineThickness + + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/CellBorderDistanceFunctionJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/CellBorderDistanceFunctionJsonLoader.java new file mode 100644 index 0000000..59c9343 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/CellBorderDistanceFunctionJsonLoader.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.condition.IDoubleCondition; +import com.hypixel.hytale.procedurallib.logic.cell.BorderDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.CellDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.MeasurementMode; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.PointEvaluator; +import java.nio.file.Path; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CellBorderDistanceFunctionJsonLoader extends JsonLoader { + protected final CellDistanceFunction distanceFunction; + + public CellBorderDistanceFunctionJsonLoader(SeedString seed, Path dataFolder, JsonElement json, CellDistanceFunction distanceFunction) { + super(seed, dataFolder, json); + this.distanceFunction = distanceFunction; + } + + @Nonnull + public BorderDistanceFunction load() { + return new BorderDistanceFunction(this.distanceFunction, this.loadPointEvaluator(), this.loadDensity()); + } + + @Nullable + protected PointEvaluator loadPointEvaluator() { + return new PointEvaluatorJsonLoader<>(this.seed, this.dataFolder, this.json, MeasurementMode.BORDER_DISTANCE, null).load(); + } + + @Nullable + protected IDoubleCondition loadDensity() { + return new PointEvaluatorJsonLoader<>(this.seed, this.dataFolder, this.json).loadDensity(); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/CellDistanceFunctionJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/CellDistanceFunctionJsonLoader.java new file mode 100644 index 0000000..8988c4a --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/CellDistanceFunctionJsonLoader.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.logic.cell.CellDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.CellType; +import com.hypixel.hytale.procedurallib.logic.cell.GridCellDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.HexCellDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.MeasurementMode; +import com.hypixel.hytale.procedurallib.logic.cell.PointDistanceFunction; +import java.nio.file.Path; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CellDistanceFunctionJsonLoader extends JsonLoader { + protected final MeasurementMode measurementMode; + protected final PointDistanceFunction pointDistanceFunction; + + public CellDistanceFunctionJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json, @Nullable PointDistanceFunction pointDistanceFunction) { + this(seed, dataFolder, json, MeasurementMode.CENTRE_DISTANCE, pointDistanceFunction); + } + + public CellDistanceFunctionJsonLoader( + @Nonnull SeedString seed, Path dataFolder, JsonElement json, MeasurementMode measurementMode, @Nullable PointDistanceFunction pointDistanceFunction + ) { + super(seed.append(".CellDistanceFunction"), dataFolder, json); + this.measurementMode = measurementMode; + this.pointDistanceFunction = pointDistanceFunction; + } + + public CellDistanceFunction load() { + CellDistanceFunction distanceFunction = this.loadDistanceFunction(); + + return (CellDistanceFunction)(switch (this.measurementMode) { + case CENTRE_DISTANCE -> distanceFunction; + case BORDER_DISTANCE -> new CellBorderDistanceFunctionJsonLoader<>(this.seed, this.dataFolder, this.json, distanceFunction).load(); + }); + } + + @Nonnull + protected CellType loadCellType() { + CellType cellType = CellNoiseJsonLoader.Constants.DEFAULT_CELL_TYPE; + if (this.has("CellType")) { + cellType = CellType.valueOf(this.get("CellType").getAsString()); + } + + return cellType; + } + + @Nonnull + protected CellDistanceFunction loadDistanceFunction() { + return (CellDistanceFunction)(switch (this.loadCellType()) { + case SQUARE -> GridCellDistanceFunction.DISTANCE_FUNCTION; + case HEX -> HexCellDistanceFunction.DISTANCE_FUNCTION; + }); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/CellNoiseJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/CellNoiseJsonLoader.java new file mode 100644 index 0000000..259d375 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/CellNoiseJsonLoader.java @@ -0,0 +1,107 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.NoiseFunction; +import com.hypixel.hytale.procedurallib.logic.CellNoise; +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import com.hypixel.hytale.procedurallib.logic.cell.CellDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.CellType; +import com.hypixel.hytale.procedurallib.logic.cell.DistanceCalculationMode; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.PointEvaluator; +import com.hypixel.hytale.procedurallib.property.NoiseProperty; +import java.nio.file.Path; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CellNoiseJsonLoader extends JsonLoader { + public CellNoiseJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + super(seed.append(".CellNoise"), dataFolder, json); + } + + @Nonnull + public NoiseFunction load() { + CellDistanceFunction cellDistanceFunction = this.loadCellDistanceFunction(); + PointEvaluator pointEvaluator = this.loadPointEvaluator(); + CellNoise.CellFunction cellFunction = this.loadCellFunction(); + NoiseProperty noiseLookup = this.loadNoiseLookup(); + return new CellNoiseJsonLoader.LoadedCellNoise(cellDistanceFunction, pointEvaluator, cellFunction, noiseLookup, this.seed.get()); + } + + @Nullable + protected CellDistanceFunction loadCellDistanceFunction() { + return new CellDistanceFunctionJsonLoader<>(this.seed, this.dataFolder, this.json, null).load(); + } + + @Nullable + protected PointEvaluator loadPointEvaluator() { + return new PointEvaluatorJsonLoader<>(this.seed, this.dataFolder, this.json).load(); + } + + protected CellNoise.CellFunction loadCellFunction() { + CellNoise.CellMode cellMode = CellNoiseJsonLoader.Constants.DEFAULT_CELL_MODE; + if (this.has("CellMode")) { + cellMode = CellNoise.CellMode.valueOf(this.get("CellMode").getAsString()); + } + + return cellMode.getFunction(); + } + + @Nullable + protected NoiseProperty loadNoiseLookup() { + NoiseProperty noiseProperty = null; + if (this.has("NoiseLookup")) { + noiseProperty = new NoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.get("NoiseLookup")).load(); + } + + return noiseProperty; + } + + public interface Constants { + String KEY_JITTER = "Jitter"; + String KEY_JITTER_X = "JitterX"; + String KEY_JITTER_Y = "JitterY"; + String KEY_JITTER_Z = "JitterZ"; + String KEY_DENSITY = "Density"; + String KEY_CELL_MODE = "CellMode"; + String KEY_NOISE_LOOKUP = "NoiseLookup"; + String KEY_DISTANCE_MODE = "DistanceMode"; + String KEY_DISTANCE_RANGE = "DistanceRange"; + String KEY_CELL_TYPE = "CellType"; + String KEY_SKIP_CELLS = "Skip"; + String KEY_SKIP_MODE = "SkipMode"; + double DEFAULT_JITTER = 1.0; + double DEFAULT_DISTANCE_RANGE = 1.0; + double DEFAULT_DENSITY_LOWER = 0.0; + double DEFAULT_DENSITY_UPPER = 1.0; + DistanceCalculationMode DEFAULT_DISTANCE_MODE = DistanceCalculationMode.EUCLIDEAN; + CellNoise.CellMode DEFAULT_CELL_MODE = CellNoise.CellMode.CELL_VALUE; + CellType DEFAULT_CELL_TYPE = CellType.SQUARE; + } + + private static class LoadedCellNoise extends CellNoise { + private final SeedResource seedResource; + + public LoadedCellNoise( + CellDistanceFunction cellDistanceFunction, + PointEvaluator pointEvaluator, + CellNoise.CellFunction cellFunction, + @Nullable NoiseProperty noiseLookup, + SeedResource seedResource + ) { + super(cellDistanceFunction, pointEvaluator, cellFunction, noiseLookup); + this.seedResource = seedResource; + } + + @Nonnull + @Override + protected ResultBuffer.ResultBuffer2d localBuffer2d() { + return this.seedResource.localBuffer2d(); + } + + @Nonnull + @Override + protected ResultBuffer.ResultBuffer3d localBuffer3d() { + return this.seedResource.localBuffer3d(); + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/ConstantNoiseJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/ConstantNoiseJsonLoader.java new file mode 100644 index 0000000..8651773 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/ConstantNoiseJsonLoader.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.NoiseFunction; +import com.hypixel.hytale.procedurallib.logic.ConstantNoise; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class ConstantNoiseJsonLoader extends JsonLoader { + public ConstantNoiseJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + super(seed.append(".ConstantNoise"), dataFolder, json); + } + + @Nonnull + public NoiseFunction load() { + return new ConstantNoise(this.loadValue()); + } + + protected double loadValue() { + return this.has("Value") ? this.get("Value").getAsDouble() : 0.5; + } + + public interface Constants { + String KEY_VALUE = "Value"; + double DEFAULT_VALUE = 0.5; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/CoordinateRandomizerJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/CoordinateRandomizerJsonLoader.java new file mode 100644 index 0000000..481bb3a --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/CoordinateRandomizerJsonLoader.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.hypixel.hytale.procedurallib.property.NoiseProperty; +import com.hypixel.hytale.procedurallib.random.CoordinateRandomizer; +import com.hypixel.hytale.procedurallib.random.CoordinateRotator; +import com.hypixel.hytale.procedurallib.random.ICoordinateRandomizer; +import com.hypixel.hytale.procedurallib.random.RotatedCoordinateRandomizer; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class CoordinateRandomizerJsonLoader extends JsonLoader { + public CoordinateRandomizerJsonLoader(SeedString seed, Path dataFolder, JsonElement json) { + super(seed, dataFolder, json); + } + + @Nonnull + public ICoordinateRandomizer load() { + return this.json != null && !this.json.isJsonNull() ? this.loadRandomizer() : CoordinateRandomizer.EMPTY_RANDOMIZER; + } + + @Nonnull + protected ICoordinateRandomizer loadRandomizer() { + ICoordinateRandomizer randomizer = new CoordinateRandomizer( + this.loadGenerators(".X-Noise#%s"), this.loadGenerators(".Y-Noise#%s"), this.loadGenerators(".Z-Noise#%s") + ); + if (this.has("Rotate")) { + CoordinateRotator rotation = new CoordinateRotatorJsonLoader<>(this.seed, this.dataFolder, this.get("Rotate")).load(); + if (rotation != CoordinateRotator.NONE) { + randomizer = new RotatedCoordinateRandomizer(randomizer, rotation); + } + } + + return randomizer; + } + + @Nonnull + protected CoordinateRandomizer.AmplitudeNoiseProperty[] loadGenerators(@Nonnull String seedSuffix) { + JsonArray array = this.get("Generators").getAsJsonArray(); + CoordinateRandomizer.AmplitudeNoiseProperty[] generators = new CoordinateRandomizer.AmplitudeNoiseProperty[array.size()]; + + for (int i = 0; i < array.size(); i++) { + JsonObject object = array.get(i).getAsJsonObject(); + NoiseProperty property = new NoisePropertyJsonLoader<>(this.seed.alternateOriginal(String.format(seedSuffix, i)), this.dataFolder, object).load(); + double amplitude = object.get("Amplitude").getAsDouble(); + generators[i] = new CoordinateRandomizer.AmplitudeNoiseProperty(property, amplitude); + } + + return generators; + } + + public interface Constants { + String KEY_GENERATORS = "Generators"; + String KEY_GENERATORS_AMPLITUDE = "Amplitude"; + String SEED_X_NOISE_SUFFIX = ".X-Noise#%s"; + String SEED_Y_NOISE_SUFFIX = ".Y-Noise#%s"; + String SEED_Z_NOISE_SUFFIX = ".Z-Noise#%s"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/CoordinateRotatorJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/CoordinateRotatorJsonLoader.java new file mode 100644 index 0000000..5d25955 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/CoordinateRotatorJsonLoader.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.random.CoordinateOriginRotator; +import com.hypixel.hytale.procedurallib.random.CoordinateRotator; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class CoordinateRotatorJsonLoader extends JsonLoader { + public CoordinateRotatorJsonLoader(SeedString seed, Path dataFolder, JsonElement json) { + super(seed, dataFolder, json); + } + + @Nonnull + public CoordinateRotator load() { + double pitch = this.has("Pitch") ? this.get("Pitch").getAsDouble() * (float) (Math.PI / 180.0) : 0.0; + double yaw = this.has("Yaw") ? this.get("Yaw").getAsDouble() * (float) (Math.PI / 180.0) : 0.0; + if (pitch == 0.0 && yaw == 0.0) { + return CoordinateRotator.NONE; + } else { + double originX = this.has("OriginX") ? this.get("OriginX").getAsDouble() : 0.0; + double originY = this.has("OriginY") ? this.get("OriginY").getAsDouble() : 0.0; + double originZ = this.has("OriginZ") ? this.get("OriginZ").getAsDouble() : 0.0; + return (CoordinateRotator)(originX == 0.0 && originY == 0.0 && originZ == 0.0 + ? new CoordinateRotator(pitch, yaw) + : new CoordinateOriginRotator(pitch, yaw, originX, originY, originZ)); + } + } + + public interface Constants { + String KEY_ROTATE = "Rotate"; + String KEY_PITCH = "Pitch"; + String KEY_YAW = "Yaw"; + String KEY_ORIGIN_X = "OriginX"; + String KEY_ORIGIN_Y = "OriginY"; + String KEY_ORIGIN_Z = "OriginZ"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/CurveNoisePropertyJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/CurveNoisePropertyJsonLoader.java new file mode 100644 index 0000000..114bbed --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/CurveNoisePropertyJsonLoader.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.property.CurveNoiseProperty; +import com.hypixel.hytale.procedurallib.property.NoiseProperty; +import java.nio.file.Path; +import java.util.function.DoubleUnaryOperator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CurveNoisePropertyJsonLoader extends JsonLoader { + @Nullable + protected final NoiseProperty noise; + + public CurveNoisePropertyJsonLoader(SeedString seed, Path dataFolder, JsonElement json, @Nullable NoiseProperty noise) { + super(seed, dataFolder, json); + this.noise = noise; + } + + @Nonnull + public CurveNoiseProperty load() { + return new CurveNoiseProperty(this.loadNoise(), this.loadDCurve()); + } + + @Nullable + protected NoiseProperty loadNoise() { + NoiseProperty noise = this.noise; + if (noise == null) { + if (this.has("Noise")) { + return new NoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.get("Noise")).load(); + } else { + throw new Error("Missing Noise entry!"); + } + } else { + return noise; + } + } + + @Nonnull + protected DoubleUnaryOperator loadDCurve() { + double a = this.loadValue("A", 2.0); + double b = this.loadValue("B", -2.0); + return new CurveNoiseProperty.PowerCurve(a, b); + } + + protected double loadValue(String key, double def) { + double value = def; + if (this.has(key)) { + value = this.get(key).getAsDouble(); + } + + return value; + } + + public interface Constants { + String KEY_NOISE = "Noise"; + String KEY_CONST_A = "A"; + String KEY_CONST_B = "B"; + double DEFAULT_A = 2.0; + double DEFAULT_B = -2.0; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/DistanceNoiseJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/DistanceNoiseJsonLoader.java new file mode 100644 index 0000000..4c4c48d --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/DistanceNoiseJsonLoader.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.NoiseFunction; +import com.hypixel.hytale.procedurallib.logic.DistanceNoise; +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import com.hypixel.hytale.procedurallib.logic.cell.CellDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.MeasurementMode; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.PointEvaluator; +import java.nio.file.Path; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DistanceNoiseJsonLoader extends JsonLoader { + public DistanceNoiseJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + super(seed.append(".DistanceNoise"), dataFolder, json); + } + + @Nonnull + public NoiseFunction load() { + CellDistanceFunction cellDistanceFunction = this.loadCellDistanceFunction(); + PointEvaluator pointEvaluator = this.loadPointEvaluator(); + DistanceNoise.Distance2Function distance2Function = this.loadDistance2Function(); + return new DistanceNoiseJsonLoader.LoadedDistanceNoise(cellDistanceFunction, pointEvaluator, distance2Function, this.seed.get()); + } + + @Nullable + protected CellDistanceFunction loadCellDistanceFunction() { + MeasurementMode measurementMode = this.loadMeasurementMode(); + return new CellDistanceFunctionJsonLoader<>(this.seed, this.dataFolder, this.json, measurementMode, null).load(); + } + + @Nullable + protected PointEvaluator loadPointEvaluator() { + return new PointEvaluatorJsonLoader<>(this.seed, this.dataFolder, this.json).load(); + } + + @Nonnull + protected MeasurementMode loadMeasurementMode() { + return this.has("Measurement") ? MeasurementMode.valueOf(this.get("Measurement").getAsString()) : DistanceNoiseJsonLoader.Constants.DEFAULT_MEASUREMENT; + } + + protected DistanceNoise.Distance2Function loadDistance2Function() { + DistanceNoise.Distance2Mode distance2Mode = DistanceNoiseJsonLoader.Constants.DEFAULT_DISTANCE_2_MODE; + if (this.has("Distance2Mode")) { + distance2Mode = DistanceNoise.Distance2Mode.valueOf(this.get("Distance2Mode").getAsString()); + } + + return distance2Mode.getFunction(); + } + + public interface Constants { + String KEY_MEASUREMENT = "Measurement"; + String KEY_DISTANCE_2_MODE = "Distance2Mode"; + MeasurementMode DEFAULT_MEASUREMENT = MeasurementMode.CENTRE_DISTANCE; + DistanceNoise.Distance2Mode DEFAULT_DISTANCE_2_MODE = DistanceNoise.Distance2Mode.SUB; + } + + private static class LoadedDistanceNoise extends DistanceNoise { + private final SeedResource seedResource; + + public LoadedDistanceNoise( + CellDistanceFunction cellDistanceFunction, PointEvaluator pointEvaluator, DistanceNoise.Distance2Function distance2Function, SeedResource seedResource + ) { + super(cellDistanceFunction, pointEvaluator, distance2Function); + this.seedResource = seedResource; + } + + @Nonnull + @Override + protected ResultBuffer.ResultBuffer2d localBuffer2d() { + return this.seedResource.localBuffer2d(); + } + + @Nonnull + @Override + protected ResultBuffer.ResultBuffer3d localBuffer3d() { + return this.seedResource.localBuffer3d(); + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/DoubleConditionJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/DoubleConditionJsonLoader.java new file mode 100644 index 0000000..dcc67d4 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/DoubleConditionJsonLoader.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.condition.DefaultDoubleCondition; +import com.hypixel.hytale.procedurallib.condition.DoubleThresholdCondition; +import com.hypixel.hytale.procedurallib.condition.IDoubleCondition; +import com.hypixel.hytale.procedurallib.condition.SingleDoubleCondition; +import java.nio.file.Path; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class DoubleConditionJsonLoader extends JsonLoader { + protected final Boolean defaultValue; + + public DoubleConditionJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + this(seed.append(".DoubleCondition"), dataFolder, json, true); + } + + public DoubleConditionJsonLoader(SeedString seed, Path dataFolder, JsonElement json, Boolean defaultValue) { + super(seed, dataFolder, json); + this.defaultValue = defaultValue; + } + + @Nonnull + public IDoubleCondition load() { + if (this.json == null || this.json.isJsonNull()) { + Objects.requireNonNull(this.defaultValue, "Default value is not set and condition is not defined."); + return this.defaultValue ? DefaultDoubleCondition.DEFAULT_TRUE : DefaultDoubleCondition.DEFAULT_FALSE; + } else if (!this.json.isJsonPrimitive() + && (!this.json.isJsonArray() || this.json.getAsJsonArray().size() != 1 || !this.json.getAsJsonArray().get(0).isJsonPrimitive())) { + if (this.json.isJsonArray()) { + return new DoubleThresholdCondition(new DoubleThresholdJsonLoader<>(this.seed, this.dataFolder, this.json).load()); + } else { + throw new Error(String.format("Failed to load \"%s\" as DoubleCondition", this.json)); + } + } else { + double limit = this.json.getAsDouble(); + return new SingleDoubleCondition(limit); + } + } + + public interface Constants { + String ERROR_NO_DEFAULT = "Default value is not set and condition is not defined."; + String ERROR_FAILED = "Failed to load \"%s\" as DoubleCondition"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/DoubleRangeJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/DoubleRangeJsonLoader.java new file mode 100644 index 0000000..e5fe157 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/DoubleRangeJsonLoader.java @@ -0,0 +1,108 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.supplier.DoubleRange; +import com.hypixel.hytale.procedurallib.supplier.IDoubleRange; +import java.nio.file.Path; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class DoubleRangeJsonLoader extends JsonLoader { + protected final double default1; + protected final double default2; + @Nonnull + protected final DoubleRangeJsonLoader.DoubleToDoubleFunction function; + + public DoubleRangeJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + this(seed, dataFolder, json, 0.0, d -> d); + } + + public DoubleRangeJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json, DoubleRangeJsonLoader.DoubleToDoubleFunction function) { + this(seed, dataFolder, json, 0.0, function); + } + + public DoubleRangeJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json, double default1) { + this(seed, dataFolder, json, default1, default1, d -> d); + } + + public DoubleRangeJsonLoader( + @Nonnull SeedString seed, Path dataFolder, JsonElement json, double default1, DoubleRangeJsonLoader.DoubleToDoubleFunction function + ) { + this(seed, dataFolder, json, default1, default1, function); + } + + public DoubleRangeJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json, double default1, double default2) { + this(seed, dataFolder, json, default1, default2, d -> d); + } + + public DoubleRangeJsonLoader( + @Nonnull SeedString seed, Path dataFolder, JsonElement json, double default1, double default2, DoubleRangeJsonLoader.DoubleToDoubleFunction function + ) { + super(seed.append(".DoubleRange"), dataFolder, json); + this.default1 = default1; + this.default2 = default2; + this.function = Objects.requireNonNull(function); + } + + public IDoubleRange load() { + if (this.json != null && !this.json.isJsonNull()) { + if (this.json.isJsonArray()) { + JsonArray array = this.json.getAsJsonArray(); + if (array.size() != 1 && array.size() != 2) { + throw new IllegalStateException(String.format("Range array contains %s values. Only 1 or 2 entries are allowed.", array.size())); + } else { + return (IDoubleRange)(array.size() == 1 + ? new DoubleRange.Constant(this.function.get(array.get(0).getAsDouble())) + : new DoubleRange.Normal(this.function.get(array.get(0).getAsDouble()), this.function.get(array.get(1).getAsDouble()))); + } + } else if (!this.json.isJsonObject()) { + return new DoubleRange.Constant(this.function.get(this.json.getAsDouble())); + } else if (this.has("Thresholds") && this.has("Values")) { + return this.loadThreshold(); + } else if (!this.has("Min")) { + throw new IllegalStateException("Minimum value of range is not defined. Keyword: Min"); + } else if (!this.has("Max")) { + throw new IllegalStateException("Maximum value of range is not defined. Keyword: Max"); + } else { + double min = this.get("Min").getAsDouble(); + double max = this.get("Max").getAsDouble(); + return new DoubleRange.Normal(this.function.get(min), this.function.get(max)); + } + } else { + return (IDoubleRange)(this.default1 == this.default2 + ? new DoubleRange.Constant(this.function.get(this.default1)) + : new DoubleRange.Normal(this.function.get(this.default1), this.function.get(this.default2))); + } + } + + @Nonnull + protected IDoubleRange loadThreshold() { + JsonArray thresholdsJson = this.get("Thresholds").getAsJsonArray(); + JsonArray valuesJson = this.get("Values").getAsJsonArray(); + double[] thresholds = new double[thresholdsJson.size()]; + double[] values = new double[thresholdsJson.size()]; + + for (int i = 0; i < thresholds.length; i++) { + thresholds[i] = thresholdsJson.get(i).getAsDouble(); + values[i] = valuesJson.get(i).getAsDouble(); + } + + return new DoubleRange.Multiple(thresholds, values); + } + + public interface Constants { + String KEY_MIN = "Min"; + String KEY_MAX = "Max"; + String KEY_THRESHOLDS = "Thresholds"; + String KEY_VALUES = "Values"; + String ERROR_ARRAY_SIZE = "Range array contains %s values. Only 1 or 2 entries are allowed."; + String ERROR_NO_MIN = "Minimum value of range is not defined. Keyword: Min"; + String ERROR_NO_MAX = "Maximum value of range is not defined. Keyword: Max"; + } + + @FunctionalInterface + public interface DoubleToDoubleFunction { + double get(double var1); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/DoubleThresholdJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/DoubleThresholdJsonLoader.java new file mode 100644 index 0000000..61b574c --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/DoubleThresholdJsonLoader.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.condition.DefaultDoubleThresholdCondition; +import com.hypixel.hytale.procedurallib.condition.DoubleThreshold; +import com.hypixel.hytale.procedurallib.condition.IDoubleThreshold; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class DoubleThresholdJsonLoader extends JsonLoader { + protected final boolean defaultValue; + + public DoubleThresholdJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + this(seed, dataFolder, json, true); + } + + public DoubleThresholdJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json, boolean defaultValue) { + super(seed.append(".DoubleThreshold"), dataFolder, json); + this.defaultValue = defaultValue; + } + + @Nonnull + public IDoubleThreshold load() { + if (this.json == null || this.json.isJsonNull()) { + return this.defaultValue ? DefaultDoubleThresholdCondition.DEFAULT_TRUE : DefaultDoubleThresholdCondition.DEFAULT_FALSE; + } else if (this.json.isJsonPrimitive()) { + double value = this.json.getAsDouble(); + return new DoubleThreshold.Single(0.0, value); + } else { + JsonArray jsonArray = this.json.getAsJsonArray(); + if (jsonArray.size() <= 0) { + throw new IllegalArgumentException("Threshold array must contain at least one entry!"); + } else if (jsonArray.get(0).isJsonArray()) { + DoubleThreshold.Single[] entries = new DoubleThreshold.Single[jsonArray.size()]; + + for (int i = 0; i < entries.length; i++) { + JsonArray jsonArrayEntry = jsonArray.get(i).getAsJsonArray(); + if (jsonArrayEntry.size() != 2) { + throw new IllegalArgumentException("Threshold array entries must have 2 numbers for lower/upper limit!"); + } + + entries[i] = new DoubleThreshold.Single(jsonArrayEntry.get(0).getAsDouble(), jsonArrayEntry.get(1).getAsDouble()); + } + + return new DoubleThreshold.Multiple(entries); + } else if (jsonArray.size() != 2) { + throw new IllegalArgumentException("Threshold array entries must have 2 numbers for lower/upper limit!"); + } else { + return new DoubleThreshold.Single(jsonArray.get(0).getAsDouble(), jsonArray.get(1).getAsDouble()); + } + } + } + + public interface Constants { + String ERROR_NO_ENTRY = "Threshold array must contain at least one entry!"; + String ERROR_THRESHOLD_SIZE = "Threshold array entries must have 2 numbers for lower/upper limit!"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/FloatRangeJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/FloatRangeJsonLoader.java new file mode 100644 index 0000000..60e22be --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/FloatRangeJsonLoader.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.supplier.FloatRange; +import com.hypixel.hytale.procedurallib.supplier.IFloatRange; +import java.nio.file.Path; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class FloatRangeJsonLoader extends JsonLoader { + protected final float default1; + protected final float default2; + @Nonnull + protected final FloatRangeJsonLoader.FloatToFloatFunction function; + + public FloatRangeJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + this(seed, dataFolder, json, 0.0F, d -> d); + } + + public FloatRangeJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json, FloatRangeJsonLoader.FloatToFloatFunction function) { + this(seed, dataFolder, json, 0.0F, function); + } + + public FloatRangeJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json, float default1) { + this(seed, dataFolder, json, default1, default1, d -> d); + } + + public FloatRangeJsonLoader( + @Nonnull SeedString seed, Path dataFolder, JsonElement json, float default1, FloatRangeJsonLoader.FloatToFloatFunction function + ) { + this(seed, dataFolder, json, default1, default1, function); + } + + public FloatRangeJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json, float default1, float default2) { + this(seed, dataFolder, json, default1, default2, d -> d); + } + + public FloatRangeJsonLoader( + @Nonnull SeedString seed, Path dataFolder, JsonElement json, float default1, float default2, FloatRangeJsonLoader.FloatToFloatFunction function + ) { + super(seed.append(".DoubleRange"), dataFolder, json); + this.default1 = default1; + this.default2 = default2; + this.function = Objects.requireNonNull(function); + } + + @Nonnull + public IFloatRange load() { + if (this.json != null && !this.json.isJsonNull()) { + if (this.json.isJsonArray()) { + JsonArray array = this.json.getAsJsonArray(); + if (array.size() != 1 && array.size() != 2) { + throw new IllegalStateException(String.format("Range array contains %s values. Only 1 or 2 entries are allowed.", array.size())); + } else { + return (IFloatRange)(array.size() == 1 + ? new FloatRange.Constant(this.function.get(array.get(0).getAsFloat())) + : new FloatRange.Normal(this.function.get(array.get(0).getAsFloat()), this.function.get(array.get(1).getAsFloat()))); + } + } else if (!this.json.isJsonObject()) { + return new FloatRange.Constant(this.function.get(this.json.getAsFloat())); + } else if (!this.has("Min")) { + throw new IllegalStateException("Minimum value of range is not defined. Keyword: Min"); + } else if (!this.has("Max")) { + throw new IllegalStateException("Maximum value of range is not defined. Keyword: Max"); + } else { + float min = this.get("Min").getAsFloat(); + float max = this.get("Max").getAsFloat(); + return new FloatRange.Normal(this.function.get(min), this.function.get(max)); + } + } else { + return (IFloatRange)(this.default1 == this.default2 + ? new FloatRange.Constant(this.function.get(this.default1)) + : new FloatRange.Normal(this.function.get(this.default1), this.function.get(this.default2))); + } + } + + public interface Constants { + String KEY_MIN = "Min"; + String KEY_MAX = "Max"; + String ERROR_ARRAY_SIZE = "Range array contains %s values. Only 1 or 2 entries are allowed."; + String ERROR_NO_MIN = "Minimum value of range is not defined. Keyword: Min"; + String ERROR_NO_MAX = "Maximum value of range is not defined. Keyword: Max"; + } + + @FunctionalInterface + public interface FloatToFloatFunction { + float get(float var1); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/GradientNoisePropertyJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/GradientNoisePropertyJsonLoader.java new file mode 100644 index 0000000..758733f --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/GradientNoisePropertyJsonLoader.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.property.GradientNoiseProperty; +import com.hypixel.hytale.procedurallib.property.NoiseProperty; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class GradientNoisePropertyJsonLoader extends JsonLoader { + protected final NoiseProperty noise; + + public GradientNoisePropertyJsonLoader(SeedString seed, Path dataFolder, JsonElement json, NoiseProperty noise) { + super(seed, dataFolder, json); + this.noise = noise; + } + + @Nonnull + public GradientNoiseProperty load() { + return new GradientNoiseProperty(this.noise, this.loadMode(), this.loadDistance(), this.loadNormalization()); + } + + @Nonnull + protected GradientNoiseProperty.GradientMode loadMode() { + GradientNoiseProperty.GradientMode mode = GradientNoisePropertyJsonLoader.Constants.DEFAULT_MODE; + if (this.has("Mode")) { + mode = GradientNoiseProperty.GradientMode.valueOf(this.get("Mode").getAsString()); + } + + return mode; + } + + protected double loadDistance() { + double distance = 5.0; + if (this.has("Distance")) { + distance = this.get("Distance").getAsDouble(); + } + + return distance; + } + + protected double loadNormalization() { + double distance = 0.1; + if (this.has("Normalize")) { + distance = this.get("Normalize").getAsDouble(); + } + + return distance; + } + + public interface Constants { + String KEY_MODE = "Mode"; + String KEY_DISTANCE = "Distance"; + String KEY_NORMALIZE = "Normalize"; + GradientNoiseProperty.GradientMode DEFAULT_MODE = GradientNoiseProperty.GradientMode.MAGNITUDE; + double DEFAULT_DISTANCE = 5.0; + double DEFAULT_NORMALIZATION = 0.1; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/GridNoiseJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/GridNoiseJsonLoader.java new file mode 100644 index 0000000..84b130f --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/GridNoiseJsonLoader.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.NoiseFunction; +import com.hypixel.hytale.procedurallib.logic.GridNoise; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class GridNoiseJsonLoader extends JsonLoader { + public GridNoiseJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + super(seed.append(".GridNoise"), dataFolder, json); + } + + @Nonnull + public NoiseFunction load() { + double defaultThickness = this.loadDefaultThickness(); + return new GridNoise(this.loadThicknessX(defaultThickness), this.loadThicknessY(defaultThickness), this.loadThicknessZ(defaultThickness)); + } + + protected double loadDefaultThickness() { + return !this.has("Thickness") ? Double.NaN : this.get("Thickness").getAsDouble(); + } + + protected double loadThicknessX(double defaultThickness) { + return this.loadThickness("ThicknessX", defaultThickness); + } + + protected double loadThicknessY(double defaultThickness) { + return this.loadThickness("ThicknessY", defaultThickness); + } + + protected double loadThicknessZ(double defaultThickness) { + if (Double.isNaN(defaultThickness)) { + defaultThickness = 0.0; + } + + return this.loadThickness("ThicknessZ", defaultThickness); + } + + protected double loadThickness(String key, double defaultThickness) { + double value = defaultThickness; + if (this.has(key)) { + value = this.get(key).getAsDouble(); + } + + if (Double.isNaN(value)) { + throw new Error(String.format("Could not find thickness '%s' and no default 'Thickness' value defined!", key)); + } else { + return value; + } + } + + public interface Constants { + double DEFAULT_NO_THICKNESS = Double.NaN; + double DEFAULT_THICKNESS_Z = 0.0; + String KEY_THICKNESS = "Thickness"; + String KEY_THICKNESS_X = "ThicknessX"; + String KEY_THICKNESS_Y = "ThicknessY"; + String KEY_THICKNESS_Z = "ThicknessZ"; + String ERROR_NO_THICKNESS = "Could not find thickness '%s' and no default 'Thickness' value defined!"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/HeightThresholdInterpreterJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/HeightThresholdInterpreterJsonLoader.java new file mode 100644 index 0000000..838db3f --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/HeightThresholdInterpreterJsonLoader.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.condition.IHeightThresholdInterpreter; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class HeightThresholdInterpreterJsonLoader extends JsonLoader { + protected final int length; + + public HeightThresholdInterpreterJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json, int length) { + super(seed.append(".HeightThresholdInterpreter"), dataFolder, json); + this.length = length; + } + + @Nonnull + public IHeightThresholdInterpreter load() { + return (IHeightThresholdInterpreter)(NoiseHeightThresholdInterpreterJsonLoader.shouldHandle(this.json.getAsJsonObject()) + ? new NoiseHeightThresholdInterpreterJsonLoader<>(this.seed, this.dataFolder, this.json, this.length).load() + : new BasicHeightThresholdInterpreterJsonLoader<>(this.seed, this.dataFolder, this.json, this.length).load()); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/JsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/JsonLoader.java new file mode 100644 index 0000000..a31a581 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/JsonLoader.java @@ -0,0 +1,158 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Function; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class JsonLoader extends Loader { + @Nullable + protected final JsonElement json; + + public JsonLoader(SeedString seed, Path dataFolder, @Nullable JsonElement json) { + super(seed, dataFolder); + if (json != null && json.isJsonObject() && json.getAsJsonObject().has("File")) { + this.json = this.loadFileConstructor(json.getAsJsonObject().get("File").getAsString()); + } else { + this.json = json; + } + } + + public boolean has(String name) { + return this.json != null && this.json.isJsonObject() && this.json.getAsJsonObject().has(name); + } + + @Nullable + public JsonElement get(String name) { + if (this.json != null && this.json.isJsonObject()) { + JsonElement element = this.json.getAsJsonObject().get(name); + if (element != null && element.isJsonObject()) { + JsonObject object = element.getAsJsonObject(); + if (object.has("File")) { + element = this.loadFileElem(object.get("File").getAsString()); + } + } + + return element; + } else { + return null; + } + } + + @Nullable + public JsonElement getRaw(String name) { + return this.json != null && this.json.isJsonObject() ? this.json.getAsJsonObject().get(name) : null; + } + + protected JsonElement loadFile(@Nonnull String filePath) { + Path file = this.dataFolder.resolve(filePath.replace('.', File.separatorChar) + ".json"); + + try { + JsonElement var4; + try (JsonReader reader = new JsonReader(Files.newBufferedReader(file))) { + var4 = JsonParser.parseReader(reader); + } + + return var4; + } catch (Throwable var8) { + throw new Error("Error while loading file reference." + file.toString(), var8); + } + } + + protected JsonElement loadFileElem(@Nonnull String filePath) { + return this.loadFile(filePath); + } + + protected JsonElement loadFileConstructor(@Nonnull String filePath) { + return this.loadFile(filePath); + } + + @Nonnull + protected JsonObject mustGetObject(@Nonnull String key, @Nullable JsonObject defaultValue) { + return this.mustGet(key, defaultValue, JsonObject.class, JsonElement::isJsonObject, JsonElement::getAsJsonObject); + } + + @Nonnull + protected JsonArray mustGetArray(@Nonnull String key, @Nullable JsonArray defaultValue) { + return this.mustGet(key, defaultValue, JsonArray.class, JsonElement::isJsonArray, JsonElement::getAsJsonArray); + } + + @Nonnull + protected String mustGetString(@Nonnull String key, @Nullable String defaultValue) { + return this.mustGet(key, defaultValue, String.class, JsonLoader::isString, JsonElement::getAsString); + } + + @Nonnull + protected Boolean mustGetBool(@Nonnull String key, @Nullable Boolean defaultValue) { + return this.mustGet(key, defaultValue, Boolean.class, JsonLoader::isBoolean, JsonElement::getAsBoolean); + } + + @Nonnull + protected Number mustGetNumber(@Nonnull String key, @Nullable Number defaultValue) { + return this.mustGet(key, defaultValue, Number.class, JsonLoader::isNumber, JsonElement::getAsNumber); + } + + protected V mustGet( + @Nonnull String key, + @Nullable V defaultValue, + @Nonnull Class type, + @Nonnull Predicate predicate, + @Nonnull Function mapper + ) { + return mustGet(key, this.get(key), defaultValue, type, predicate, mapper); + } + + protected static V mustGet( + @Nonnull String key, + @Nullable JsonElement element, + @Nullable V defaultValue, + @Nonnull Class type, + @Nonnull Predicate predicate, + @Nonnull Function mapper + ) { + if (element == null) { + if (defaultValue != null) { + return defaultValue; + } else { + throw error("Missing property '%s'", key); + } + } else if (!predicate.test(element)) { + throw error("Property '%s' must be of type '%s'", key, type.getSimpleName()); + } else { + return mapper.apply(element); + } + } + + protected static Error error(String format, Object... args) { + return new Error(String.format(format, args)); + } + + protected static Error error(Throwable parent, String format, Object... args) { + return new Error(String.format(format, args), parent); + } + + private static boolean isString(JsonElement element) { + return element.isJsonPrimitive() && element.getAsJsonPrimitive().isString(); + } + + protected static boolean isNumber(JsonElement element) { + return element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber(); + } + + protected static boolean isBoolean(JsonElement element) { + return element.isJsonPrimitive() && element.getAsJsonPrimitive().isBoolean(); + } + + public interface Constants { + char JSON_FILEPATH_SEPARATOR = '.'; + String KEY_FILE = "File"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/Loader.java b/src/com/hypixel/hytale/procedurallib/json/Loader.java new file mode 100644 index 0000000..e08df7d --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/Loader.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.procedurallib.json; + +import java.nio.file.Path; +import javax.annotation.Nullable; + +public abstract class Loader { + protected SeedString seed; + protected final Path dataFolder; + + public Loader(SeedString seed, Path dataFolder) { + this.seed = seed; + this.dataFolder = dataFolder; + } + + public SeedString getSeed() { + return this.seed; + } + + public Path getDataFolder() { + return this.dataFolder; + } + + @Nullable + public abstract T load(); +} diff --git a/src/com/hypixel/hytale/procedurallib/json/MeshNoiseJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/MeshNoiseJsonLoader.java new file mode 100644 index 0000000..8cc4446 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/MeshNoiseJsonLoader.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.NoiseFunction; +import com.hypixel.hytale.procedurallib.condition.DoubleThresholdCondition; +import com.hypixel.hytale.procedurallib.condition.IIntCondition; +import com.hypixel.hytale.procedurallib.logic.HexMeshNoise; +import com.hypixel.hytale.procedurallib.logic.MeshNoise; +import com.hypixel.hytale.procedurallib.logic.cell.CellType; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.DensityPointEvaluator; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class MeshNoiseJsonLoader extends AbstractCellJitterJsonLoader { + public MeshNoiseJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + super(seed.append(".MeshNoise"), dataFolder, json); + } + + public NoiseFunction load() { + return (NoiseFunction)(switch (this.loadCellType()) { + case SQUARE -> this.loadGridMeshNoise(); + case HEX -> this.loadHexMeshNoise(); + }); + } + + @Nonnull + protected MeshNoise loadGridMeshNoise() { + double defaultJitter = this.loadDefaultJitter(); + return new MeshNoise(this.loadDensity(), this.loadThickness(), this.loadJitterX(defaultJitter), this.loadJitterY(defaultJitter)); + } + + @Nonnull + protected HexMeshNoise loadHexMeshNoise() { + return new HexMeshNoise(this.loadDensity(), this.loadThickness(), this.loadJitter(), this.loadLinesX(), this.loadLinesY(), this.loadLinesZ()); + } + + @Nonnull + protected CellType loadCellType() { + CellType cellType = CellNoiseJsonLoader.Constants.DEFAULT_CELL_TYPE; + if (this.has("CellType")) { + cellType = CellType.valueOf(this.get("CellType").getAsString()); + } + + return cellType; + } + + protected double loadThickness() { + if (!this.has("Thickness")) { + throw new IllegalStateException("Could not find thickness. Keyword: Thickness"); + } else { + return this.get("Thickness").getAsDouble(); + } + } + + @Nonnull + protected IIntCondition loadDensity() { + return DensityPointEvaluator.getDensityCondition( + this.has("Density") ? new DoubleThresholdCondition(new DoubleThresholdJsonLoader<>(this.seed, this.dataFolder, this.get("Density")).load()) : null + ); + } + + protected boolean loadLinesX() { + return this.loadLinesFlag("LinesX", true); + } + + protected boolean loadLinesY() { + return this.loadLinesFlag("LinesY", true); + } + + protected boolean loadLinesZ() { + return this.loadLinesFlag("LinesZ", true); + } + + protected boolean loadLinesFlag(String key, boolean defaulValue) { + return !this.has(key) ? defaulValue : this.get(key).getAsBoolean(); + } + + public interface Constants { + String KEY_THICKNESS = "Thickness"; + String KEY_LINES_X = "LinesX"; + String KEY_LINES_Y = "LinesY"; + String KEY_LINES_Z = "LinesZ"; + String ERROR_NO_THICKNESS = "Could not find thickness. Keyword: Thickness"; + boolean DEFAULT_LINES_X = true; + boolean DEFAULT_LINES_Y = true; + boolean DEFAULT_LINES_Z = true; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/NoiseFunctionJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/NoiseFunctionJsonLoader.java new file mode 100644 index 0000000..03e994f --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/NoiseFunctionJsonLoader.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.NoiseFunction; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class NoiseFunctionJsonLoader extends JsonLoader { + public NoiseFunctionJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + super(seed.append(".NoiseFunction"), dataFolder, json); + } + + public NoiseFunction load() { + if (!this.has("NoiseType")) { + throw new IllegalStateException(String.format("Could not find noise type for noise map! Keyword: %s", "NoiseType")); + } else { + NoiseTypeJson noiseTypeJson = NoiseTypeJson.valueOf(this.get("NoiseType").getAsString()); + return this.newLoader(noiseTypeJson).load(); + } + } + + @Nonnull + protected JsonLoader newLoader(@Nonnull NoiseTypeJson noiseTypeJson) { + return noiseTypeJson.newLoader(this.seed, this.dataFolder, this.json); + } + + public interface Constants { + String KEY_NOISE_TYPE = "NoiseType"; + String ERROR_NO_NOISE_TYPE = "Could not find noise type for noise map! Keyword: %s"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/NoiseHeightThresholdInterpreterJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/NoiseHeightThresholdInterpreterJsonLoader.java new file mode 100644 index 0000000..ec710b1 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/NoiseHeightThresholdInterpreterJsonLoader.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.hypixel.hytale.procedurallib.condition.IHeightThresholdInterpreter; +import com.hypixel.hytale.procedurallib.condition.NoiseHeightThresholdInterpreter; +import com.hypixel.hytale.procedurallib.property.NoiseProperty; +import java.nio.file.Path; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NoiseHeightThresholdInterpreterJsonLoader extends JsonLoader { + protected final int length; + + public NoiseHeightThresholdInterpreterJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json, int length) { + super(seed.append(".NoiseHeightThresholdInterpreter"), dataFolder, json); + this.length = length; + } + + @Nonnull + public NoiseHeightThresholdInterpreter load() { + IHeightThresholdInterpreter[] interpreters = this.loadInterpreters(); + float[] keys = this.loadKeys(); + if (keys.length != interpreters.length) { + throw new IllegalArgumentException("Keys and Thresholds array do not have the same length!"); + } else { + return new NoiseHeightThresholdInterpreter(this.loadNoise(), keys, interpreters); + } + } + + @Nullable + protected NoiseProperty loadNoise() { + if (!this.has("Noise")) { + throw new IllegalStateException("Could not find noise map data in NoiseHeightThresholdInterpreter. Keyword: Noise"); + } else { + return new NoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.get("Noise")).load(); + } + } + + @Nonnull + protected IHeightThresholdInterpreter[] loadInterpreters() { + if (!this.has("Thresholds")) { + throw new IllegalStateException("Could not find threshold data in NoiseHeightThresholdInterpreter. Keyword: Thresholds"); + } else { + JsonArray array = this.get("Thresholds").getAsJsonArray(); + IHeightThresholdInterpreter[] interpreters = new IHeightThresholdInterpreter[array.size()]; + + for (int i = 0; i < interpreters.length; i++) { + interpreters[i] = new HeightThresholdInterpreterJsonLoader<>(this.seed.append("-" + i), this.dataFolder, array.get(i), this.length).load(); + } + + return interpreters; + } + } + + protected float[] loadKeys() { + if (!this.has("Keys")) { + throw new IllegalStateException("Could not find key data in NoiseHeightThresholdInterpreter. Keyword: Keys"); + } else { + JsonArray array = this.get("Keys").getAsJsonArray(); + float[] keys = new float[array.size()]; + + for (int i = 0; i < keys.length; i++) { + keys[i] = array.get(i).getAsFloat(); + } + + return keys; + } + } + + public static boolean shouldHandle(@Nonnull JsonObject jsonObject) { + return jsonObject.has("Thresholds"); + } + + public interface Constants { + String KEY_NOISE = "Noise"; + String KEY_THRESHOLDS = "Thresholds"; + String KEY_KEYS = "Keys"; + String ERROR_NO_NOISE = "Could not find noise map data in NoiseHeightThresholdInterpreter. Keyword: Noise"; + String ERROR_NO_THRESHOLDS = "Could not find threshold data in NoiseHeightThresholdInterpreter. Keyword: Thresholds"; + String ERROR_NO_KEYS = "Could not find key data in NoiseHeightThresholdInterpreter. Keyword: Keys"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/NoiseMaskConditionJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/NoiseMaskConditionJsonLoader.java new file mode 100644 index 0000000..0c11e03 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/NoiseMaskConditionJsonLoader.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.condition.DefaultCoordinateCondition; +import com.hypixel.hytale.procedurallib.condition.ICoordinateCondition; +import com.hypixel.hytale.procedurallib.condition.IDoubleCondition; +import com.hypixel.hytale.procedurallib.condition.NoiseMaskCondition; +import com.hypixel.hytale.procedurallib.property.NoiseProperty; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class NoiseMaskConditionJsonLoader extends JsonLoader { + protected final boolean defaultValue; + + public NoiseMaskConditionJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + this(seed, dataFolder, json, true); + } + + public NoiseMaskConditionJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json, boolean defaultValue) { + super(seed.append(".NoiseMaskCondition"), dataFolder, json); + this.defaultValue = defaultValue; + } + + @Nonnull + public ICoordinateCondition load() { + ICoordinateCondition mapCondition = this.defaultValue ? DefaultCoordinateCondition.DEFAULT_TRUE : DefaultCoordinateCondition.DEFAULT_FALSE; + if (this.json != null && !this.json.isJsonNull()) { + if (!this.has("Threshold")) { + throw new IllegalStateException("Could not find threshold data in noise mask. Keyword: Threshold"); + } + + NoiseProperty noise = new NoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.json).load(); + IDoubleCondition threshold = new DoubleConditionJsonLoader<>(this.seed, this.dataFolder, this.get("Threshold")).load(); + mapCondition = new NoiseMaskCondition(noise, threshold); + } + + return mapCondition; + } + + public interface Constants { + String KEY_THRESHOLD = "Threshold"; + String ERROR_THRESHOLD = "Could not find threshold data in noise mask. Keyword: Threshold"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/NoisePropertyJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/NoisePropertyJsonLoader.java new file mode 100644 index 0000000..16ee8d6 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/NoisePropertyJsonLoader.java @@ -0,0 +1,320 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.NoiseFunction; +import com.hypixel.hytale.procedurallib.property.DistortedNoiseProperty; +import com.hypixel.hytale.procedurallib.property.FractalNoiseProperty; +import com.hypixel.hytale.procedurallib.property.InvertNoiseProperty; +import com.hypixel.hytale.procedurallib.property.MaxNoiseProperty; +import com.hypixel.hytale.procedurallib.property.MinNoiseProperty; +import com.hypixel.hytale.procedurallib.property.MultiplyNoiseProperty; +import com.hypixel.hytale.procedurallib.property.NoiseFormulaProperty; +import com.hypixel.hytale.procedurallib.property.NoiseProperty; +import com.hypixel.hytale.procedurallib.property.NoisePropertyType; +import com.hypixel.hytale.procedurallib.property.NormalizeNoiseProperty; +import com.hypixel.hytale.procedurallib.property.OffsetNoiseProperty; +import com.hypixel.hytale.procedurallib.property.RotateNoiseProperty; +import com.hypixel.hytale.procedurallib.property.ScaleNoiseProperty; +import com.hypixel.hytale.procedurallib.property.SingleNoiseProperty; +import com.hypixel.hytale.procedurallib.property.SumNoiseProperty; +import com.hypixel.hytale.procedurallib.random.CoordinateRotator; +import com.hypixel.hytale.procedurallib.supplier.IDoubleRange; +import java.nio.file.Path; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NoisePropertyJsonLoader extends JsonLoader { + public NoisePropertyJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + super(seed.append(".NoiseProperty"), dataFolder, json); + } + + @Nonnull + public NoiseProperty load() { + NoisePropertyType type = null; + NoiseProperty noiseProperty; + if (this.has("Type")) { + type = NoisePropertyType.valueOf(this.get("Type").getAsString()); + switch (type) { + case MAX: + if (!this.has("Noise")) { + throw new IllegalStateException("Could not find noise map data. Keyword: Noise"); + } + + NoiseProperty[] noiseProperties = this.loadNoiseProperties(this.get("Noise")); + noiseProperty = new MaxNoiseProperty(noiseProperties); + break; + case MIN: + if (!this.has("Noise")) { + throw new IllegalStateException("Could not find noise map data. Keyword: Noise"); + } + + NoiseProperty[] noiseProperties = this.loadNoiseProperties(this.get("Noise")); + noiseProperty = new MinNoiseProperty(noiseProperties); + break; + case SUM: + if (!this.has("Noise")) { + throw new IllegalStateException("Could not find noise map data. Keyword: Noise"); + } + + if (!this.has("Factors")) { + throw new IllegalStateException("Could not find factors for sum composed noise map. Keyword: Factors"); + } + + NoiseProperty[] noiseProperties = this.loadNoiseProperties(this.get("Noise")); + double[] factors = this.loadDoubleArray(this.get("Factors"), noiseProperties.length); + SumNoiseProperty.Entry[] entries = new SumNoiseProperty.Entry[noiseProperties.length]; + + for (int i = 0; i < entries.length; i++) { + entries[i] = new SumNoiseProperty.Entry(noiseProperties[i], factors[i]); + } + + noiseProperty = new SumNoiseProperty(entries); + break; + case SCALE: + if (!this.has("Noise")) { + throw new IllegalStateException("Could not find noise map data. Keyword: Noise"); + } + + if (!this.has("Scale")) { + throw new IllegalStateException("Could not find scale data for scaled noise map. Keyword: Scale"); + } + + NoiseProperty noise = new NoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.get("Noise")).load(); + double scale = this.get("Scale").getAsDouble(); + noiseProperty = new ScaleNoiseProperty(noise, scale); + break; + case FORMULA: + if (!this.has("Noise")) { + throw new IllegalStateException("Could not find noise map data. Keyword: Noise"); + } + + if (!this.has("Formula")) { + throw new IllegalStateException("Could not find formula type for noise map. Keyword: Formula"); + } + + NoiseProperty noise = new NoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.get("Noise")).load(); + NoiseFormulaProperty.NoiseFormula noiseFormula = NoiseFormulaProperty.NoiseFormula.valueOf(this.get("Formula").getAsString()); + noiseProperty = new NoiseFormulaProperty(noise, noiseFormula.getFormula()); + break; + case MULTIPLY: + if (!this.has("Noise")) { + throw new IllegalStateException("Could not find noise map data. Keyword: Noise"); + } + + NoiseProperty[] noiseProperties = this.loadNoiseProperties(this.get("Noise")); + noiseProperty = new MultiplyNoiseProperty(noiseProperties); + break; + case DISTORTED: + if (!this.has("Noise")) { + throw new IllegalStateException("Could not find noise map data. Keyword: Noise"); + } + + if (!this.has("Randomizer")) { + throw new IllegalStateException("Could not find randomizer for distorted noise map. Keyword: Randomizer"); + } + + NoiseProperty noise = new NoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.get("Noise")).load(); + noiseProperty = new DistortedNoiseProperty( + noise, new CoordinateRandomizerJsonLoader<>(this.seed, this.dataFolder, this.get("Randomizer")).load() + ); + break; + case NORMALIZE: + if (!this.has("Noise")) { + throw new IllegalStateException("Could not find noise map data. Keyword: Noise"); + } + + if (!this.has("Range")) { + throw new IllegalStateException("Could not find range data for normalized noise map. Keyword: Range"); + } + + NoiseProperty noise = new NoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.get("Noise")).load(); + IDoubleRange range = new DoubleRangeJsonLoader<>(this.seed, this.dataFolder, this.get("Range")).load(); + noiseProperty = new NormalizeNoiseProperty(noise, range.getValue(0.0), range.getValue(1.0) - range.getValue(0.0)); + break; + case INVERT: + if (!this.has("Noise")) { + throw new IllegalStateException("Could not find noise map data. Keyword: Noise"); + } + + NoiseProperty noise = new NoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.get("Noise")).load(); + noiseProperty = new InvertNoiseProperty(noise); + break; + case OFFSET: + if (!this.has("Noise")) { + throw new IllegalStateException("Could not find noise map data. Keyword: Noise"); + } + + NoiseProperty noise = new NoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.get("Noise")).load(); + double offset = this.has("Offset") ? this.get("Offset").getAsDouble() : 0.0; + double offsetX = this.has("OffsetX") ? this.get("OffsetX").getAsDouble() : offset; + double offsetY = this.has("OffsetY") ? this.get("OffsetY").getAsDouble() : offset; + double offsetZ = this.has("OffsetZ") ? this.get("OffsetZ").getAsDouble() : offset; + noiseProperty = new OffsetNoiseProperty(noise, offsetX, offsetY, offsetZ); + break; + case ROTATE: + if (!this.has("Noise")) { + throw new IllegalStateException("Could not find noise map data. Keyword: Noise"); + } + + if (!this.has("Rotate")) { + throw new IllegalStateException("Could not find noise map data. Keyword: Noise"); + } + + NoiseProperty noise = new NoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.get("Noise")).load(); + CoordinateRotator rotation = new CoordinateRotatorJsonLoader<>(this.seed, this.dataFolder, this.get("Rotate")).load(); + noiseProperty = new RotateNoiseProperty(noise, rotation); + break; + case GRADIENT: + if (!this.has("Noise")) { + throw new IllegalStateException("Could not find noise map data. Keyword: Noise"); + } + + NoiseProperty noise = new NoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.get("Noise")).load(); + noiseProperty = new GradientNoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.json, noise).load(); + break; + case CURVE: + noiseProperty = new CurveNoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.json, null).load(); + break; + case BLEND: + noiseProperty = new BlendNoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.json).load(); + break; + default: + throw new Error(String.format("Could not find instructions for noise property type: %s", type)); + } + } else { + NoiseFunction noiseFunction = this.newNoiseFunctionJsonLoader(this.seed, this.dataFolder, this.json).load(); + if (this.has("Octaves")) { + FractalNoiseProperty.FractalMode fractalMode = this.has("FractalMode") + ? FractalNoiseProperty.FractalMode.valueOf(this.get("FractalMode").getAsString()) + : NoisePropertyJsonLoader.Constants.DEFAULT_FRACTAL_MODE; + int octaves = this.get("Octaves").getAsInt(); + double lacunarity = this.has("Lacunarity") ? this.get("Lacunarity").getAsDouble() : 2.0; + double persistence = this.has("Persistence") ? this.get("Persistence").getAsDouble() : 0.5; + noiseProperty = new FractalNoiseProperty(this.loadSeed(), noiseFunction, fractalMode.getFunction(), octaves, lacunarity, persistence); + } else { + noiseProperty = new SingleNoiseProperty(this.loadSeed(), noiseFunction); + } + } + + if (type != NoisePropertyType.FORMULA && this.has("Formula")) { + NoiseFormulaProperty.NoiseFormula noiseFormula = NoiseFormulaProperty.NoiseFormula.valueOf(this.get("Formula").getAsString()); + noiseProperty = new NoiseFormulaProperty(noiseProperty, noiseFormula.getFormula()); + } + + if (type != NoisePropertyType.CURVE && this.has("Curve")) { + noiseProperty = new CurveNoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.get("Curve"), noiseProperty).load(); + } + + if (type != NoisePropertyType.SCALE && this.has("Scale")) { + double scale = this.get("Scale").getAsDouble(); + noiseProperty = new ScaleNoiseProperty(noiseProperty, scale); + } + + if (type != NoisePropertyType.NORMALIZE && this.has("Normalize") && type != NoisePropertyType.GRADIENT) { + IDoubleRange range = new DoubleRangeJsonLoader<>(this.seed, this.dataFolder, this.get("Normalize")).load(); + noiseProperty = new NormalizeNoiseProperty(noiseProperty, range.getValue(0.0), range.getValue(1.0) - range.getValue(0.0)); + } + + if (type != NoisePropertyType.OFFSET && (this.has("Offset") || this.has("OffsetX") || this.has("OffsetY") || this.has("OffsetZ"))) { + double offset = this.has("Offset") ? this.get("Offset").getAsDouble() : 0.0; + double offsetX = this.has("OffsetX") ? this.get("OffsetX").getAsDouble() : offset; + double offsetY = this.has("OffsetY") ? this.get("OffsetY").getAsDouble() : offset; + double offsetZ = this.has("OffsetZ") ? this.get("OffsetZ").getAsDouble() : offset; + noiseProperty = new OffsetNoiseProperty(noiseProperty, offsetX, offsetY, offsetZ); + } + + if (type != NoisePropertyType.ROTATE && (this.has("Pitch") || this.has("Yaw"))) { + CoordinateRotator rotation = new CoordinateRotatorJsonLoader<>(this.seed, this.dataFolder, this.json).load(); + noiseProperty = new RotateNoiseProperty(noiseProperty, rotation); + } + + if (type != NoisePropertyType.GRADIENT && this.has("Gradient")) { + noiseProperty = new GradientNoisePropertyJsonLoader<>(this.seed, this.dataFolder, this.get("Gradient"), noiseProperty).load(); + } + + return noiseProperty; + } + + protected int loadSeed() { + int seedVal = this.seed.hashCode(); + if (this.has("Seed")) { + SeedString overwritten = this.seed.appendToOriginal(this.get("Seed").getAsString()); + seedVal = overwritten.hashCode(); + this.seed.get().reportSeeds(seedVal, this.seed.original, this.seed.seed, overwritten.seed); + } else { + this.seed.get().reportSeeds(seedVal, this.seed.original, this.seed.seed, null); + } + + return seedVal; + } + + @Nonnull + protected NoiseProperty[] loadNoiseProperties(@Nonnull JsonElement element) { + if (!element.isJsonArray()) { + return new NoiseProperty[0]; + } else { + JsonArray array = element.getAsJsonArray(); + NoiseProperty[] noiseProperties = new NoiseProperty[array.size()]; + + for (int i = 0; i < noiseProperties.length; i++) { + noiseProperties[i] = new NoisePropertyJsonLoader<>(this.seed.append(String.format("-#%s", i)), this.dataFolder, array.get(i)).load(); + } + + return noiseProperties; + } + } + + protected double[] loadDoubleArray(@Nullable JsonElement element, int size) { + double[] values = new double[size]; + if (element == null || element.isJsonNull()) { + Arrays.fill(values, 1.0 / size); + } else if (element.isJsonArray()) { + JsonArray array = element.getAsJsonArray(); + + for (int i = 0; i < size; i++) { + values[i] = array.get(i).getAsDouble(); + } + } + + return values; + } + + @Nonnull + protected NoiseFunctionJsonLoader newNoiseFunctionJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + return new NoiseFunctionJsonLoader<>(seed, dataFolder, json); + } + + public interface Constants { + String KEY_SEED = "Seed"; + String KEY_SUM_FACTORS = "Factors"; + String KEY_NORMALIZE_RANGE = "Range"; + String KEY_DISTORTED_RANDOMIZER = "Randomizer"; + String KEY_TYPE = "Type"; + String KEY_NOISE = "Noise"; + String KEY_FRACTAL_MODE = "FractalMode"; + String KEY_OCTAVES = "Octaves"; + String KEY_LACUNARITY = "Lacunarity"; + String KEY_PERSISTENCE = "Persistence"; + String KEY_FORMULA = "Formula"; + String KEY_CURVE = "Curve"; + String KEY_SCALE = "Scale"; + String KEY_NORMALIZE = "Normalize"; + String KEY_OFFSET = "Offset"; + String KEY_OFFSET_X = "OffsetX"; + String KEY_OFFSET_Y = "OffsetY"; + String KEY_OFFSET_Z = "OffsetZ"; + String KEY_GRADIENT = "Gradient"; + String ERROR_NO_NOISE = "Could not find noise map data. Keyword: Noise"; + String ERROR_SUM_NO_FACTORS = "Could not find factors for sum composed noise map. Keyword: Factors"; + String ERROR_NO_FORMULA = "Could not find formula type for noise map. Keyword: Formula"; + String ERROR_NO_SCALE = "Could not find scale data for scaled noise map. Keyword: Scale"; + String ERROR_DISTORTED_RANDOMIZER = "Could not find randomizer for distorted noise map. Keyword: Randomizer"; + String ERROR_NORMALIZE_NO_RANGE = "Could not find range data for normalized noise map. Keyword: Range"; + String ERROR_UNKOWN_TYPE = "Could not find instructions for noise property type: %s"; + FractalNoiseProperty.FractalMode DEFAULT_FRACTAL_MODE = FractalNoiseProperty.FractalMode.FBM; + double DEFAULT_LACUNARITY = 2.0; + double DEFAULT_PERSISTENCE = 0.5; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/NoiseTypeJson.java b/src/com/hypixel/hytale/procedurallib/json/NoiseTypeJson.java new file mode 100644 index 0000000..9468169 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/NoiseTypeJson.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.NoiseFunction; +import com.hypixel.hytale.procedurallib.NoiseType; +import java.lang.reflect.Constructor; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public enum NoiseTypeJson { + CELL(NoiseType.CELL, CellNoiseJsonLoader.class), + CONSTANT(NoiseType.CONSTANT, ConstantNoiseJsonLoader.class), + DISTANCE(NoiseType.DISTANCE, DistanceNoiseJsonLoader.class), + PERLIN(NoiseType.PERLIN, PerlinNoiseJsonLoader.class), + SIMPLEX(NoiseType.SIMPLEX, SimplexNoiseJsonLoader.class), + OLD_SIMPLEX(NoiseType.OLD_SIMPLEX, OldSimplexNoiseJsonLoader.class), + VALUE(NoiseType.VALUE, ValueNoiseJsonLoader.class), + MESH(NoiseType.MESH, MeshNoiseJsonLoader.class), + GRID(NoiseType.GRID, GridNoiseJsonLoader.class), + BRANCH(NoiseType.BRANCH, BranchNoiseJsonLoader.class), + POINT(NoiseType.POINT, PointNoiseJsonLoader.class); + + private final NoiseType noiseType; + @Nonnull + private final Constructor constructor; + + private > NoiseTypeJson(NoiseType noiseType, @Nonnull Class loaderClass) { + this.noiseType = noiseType; + + try { + this.constructor = loaderClass.getConstructor(SeedString.class, Path.class, JsonElement.class); + this.constructor.setAccessible(true); + } catch (NoSuchMethodException var6) { + throw new Error(String.format("Could not find loader constructor for %s. NoiseType: %s", loaderClass.getName(), noiseType), var6); + } + } + + @Nonnull + public JsonLoader newLoader(SeedString seed, Path dataFolder, JsonElement json) { + try { + return (JsonLoader)this.constructor.newInstance(seed, dataFolder, json); + } catch (Exception var5) { + throw new Error(String.format("Failed to execute loader constructor! NoiseType: %s", this.noiseType), var5); + } + } + + public interface Constants { + String ERROR_NO_CONSTRUCTOR = "Could not find loader constructor for %s. NoiseType: %s"; + String ERROR_FAILED_CONSTRUCTOR = "Failed to execute loader constructor! NoiseType: %s"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/OldSimplexNoiseJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/OldSimplexNoiseJsonLoader.java new file mode 100644 index 0000000..2012840 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/OldSimplexNoiseJsonLoader.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.NoiseFunction; +import com.hypixel.hytale.procedurallib.logic.OldSimplexNoise; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class OldSimplexNoiseJsonLoader extends JsonLoader { + public OldSimplexNoiseJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + super(seed.append(".OldSimplexNoise"), dataFolder, json); + } + + @Nonnull + public NoiseFunction load() { + return OldSimplexNoise.INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/PerlinNoiseJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/PerlinNoiseJsonLoader.java new file mode 100644 index 0000000..60359fd --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/PerlinNoiseJsonLoader.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.NoiseFunction; +import com.hypixel.hytale.procedurallib.logic.GeneralNoise; +import com.hypixel.hytale.procedurallib.logic.PerlinNoise; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class PerlinNoiseJsonLoader extends JsonLoader { + public PerlinNoiseJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + super(seed.append(".PerlinNoise"), dataFolder, json); + } + + @Nonnull + public NoiseFunction load() { + return new PerlinNoise(this.loadInterpolationFunction()); + } + + protected GeneralNoise.InterpolationFunction loadInterpolationFunction() { + GeneralNoise.InterpolationMode interpolationMode = PerlinNoiseJsonLoader.Constants.DEFAULT_INTERPOLATION_MODE; + if (this.has("InterpolationMode")) { + interpolationMode = GeneralNoise.InterpolationMode.valueOf(this.get("InterpolationMode").getAsString()); + } + + return interpolationMode.getFunction(); + } + + public interface Constants { + String KEY_INTERPOLATION_MODE = "InterpolationMode"; + GeneralNoise.InterpolationMode DEFAULT_INTERPOLATION_MODE = GeneralNoise.InterpolationMode.QUINTIC; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/PointEvaluatorJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/PointEvaluatorJsonLoader.java new file mode 100644 index 0000000..c58218b --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/PointEvaluatorJsonLoader.java @@ -0,0 +1,102 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.condition.DoubleThresholdCondition; +import com.hypixel.hytale.procedurallib.condition.IDoubleCondition; +import com.hypixel.hytale.procedurallib.condition.IDoubleThreshold; +import com.hypixel.hytale.procedurallib.logic.cell.DistanceCalculationMode; +import com.hypixel.hytale.procedurallib.logic.cell.MeasurementMode; +import com.hypixel.hytale.procedurallib.logic.cell.PointDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.BorderPointEvaluator; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.JitterPointEvaluator; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.PointEvaluator; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.SkipCellPointEvaluator; +import com.hypixel.hytale.procedurallib.logic.cell.jitter.CellJitter; +import com.hypixel.hytale.procedurallib.logic.cell.jitter.DefaultCellJitter; +import com.hypixel.hytale.procedurallib.supplier.IDoubleRange; +import java.nio.file.Path; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PointEvaluatorJsonLoader extends AbstractCellJitterJsonLoader { + @Nonnull + protected final MeasurementMode measurementMode; + protected final PointDistanceFunction pointDistanceFunction; + + public PointEvaluatorJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + this(seed, dataFolder, json, null); + } + + public PointEvaluatorJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json, @Nullable PointDistanceFunction pointDistanceFunction) { + this(seed, dataFolder, json, MeasurementMode.CENTRE_DISTANCE, pointDistanceFunction); + } + + public PointEvaluatorJsonLoader( + @Nonnull SeedString seed, + Path dataFolder, + JsonElement json, + @Nonnull MeasurementMode measurementMode, + @Nullable PointDistanceFunction pointDistanceFunction + ) { + super(seed.append(".PointEvaluator"), dataFolder, json); + this.measurementMode = measurementMode; + this.pointDistanceFunction = pointDistanceFunction; + } + + public PointEvaluator load() { + return switch (this.measurementMode) { + case CENTRE_DISTANCE -> this.loadCentrePointEvaluator(); + case BORDER_DISTANCE -> this.loadBorderPointEvaluator(); + }; + } + + public PointEvaluator loadCentrePointEvaluator() { + return PointEvaluator.of( + this.loadPointDistanceFunction(), this.loadDensity(), this.loadDistanceRange(), this.loadSkipCount(), this.loadSkipMode(), this.loadJitter() + ); + } + + @Nonnull + public PointEvaluator loadBorderPointEvaluator() { + BorderPointEvaluator pointEvaluator = BorderPointEvaluator.INSTANCE; + CellJitter jitter = this.loadJitter(); + return (PointEvaluator)(jitter == DefaultCellJitter.DEFAULT_ONE ? pointEvaluator : new JitterPointEvaluator(pointEvaluator, jitter)); + } + + public PointDistanceFunction loadPointDistanceFunction() { + if (this.pointDistanceFunction != null) { + return this.pointDistanceFunction; + } else { + DistanceCalculationMode distanceCalculationMode = CellNoiseJsonLoader.Constants.DEFAULT_DISTANCE_MODE; + if (this.has("DistanceMode")) { + distanceCalculationMode = DistanceCalculationMode.valueOf(this.get("DistanceMode").getAsString()); + } + + return distanceCalculationMode.getFunction(); + } + } + + @Nullable + public IDoubleRange loadDistanceRange() { + return !this.has("DistanceRange") ? null : new DoubleRangeJsonLoader<>(this.seed, this.dataFolder, this.get("DistanceRange")).load(); + } + + @Nullable + public IDoubleCondition loadDensity() { + if (!this.has("Density")) { + return null; + } else { + IDoubleThreshold threshold = new DoubleThresholdJsonLoader<>(this.seed, this.dataFolder, this.get("Density")).load(); + return new DoubleThresholdCondition(threshold); + } + } + + public int loadSkipCount() { + return this.mustGetNumber("Skip", 0).intValue(); + } + + public SkipCellPointEvaluator.Mode loadSkipMode() { + String name = this.mustGetString("SkipMode", SkipCellPointEvaluator.DEFAULT_MODE.name()); + return SkipCellPointEvaluator.Mode.valueOf(name); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/PointGeneratorJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/PointGeneratorJsonLoader.java new file mode 100644 index 0000000..e30f59a --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/PointGeneratorJsonLoader.java @@ -0,0 +1,127 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.logic.cell.CellDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.DistanceCalculationMode; +import com.hypixel.hytale.procedurallib.logic.cell.PointDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.PointEvaluator; +import com.hypixel.hytale.procedurallib.logic.point.DistortedPointGenerator; +import com.hypixel.hytale.procedurallib.logic.point.IPointGenerator; +import com.hypixel.hytale.procedurallib.logic.point.OffsetPointGenerator; +import com.hypixel.hytale.procedurallib.logic.point.PointGenerator; +import com.hypixel.hytale.procedurallib.logic.point.ScaledPointGenerator; +import com.hypixel.hytale.procedurallib.random.CoordinateRotator; +import java.nio.file.Path; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PointGeneratorJsonLoader extends JsonLoader { + public PointGeneratorJsonLoader(SeedString seed, Path dataFolder, JsonElement json) { + super(seed, dataFolder, json); + } + + public IPointGenerator load() { + PointGenerator pointGenerator = this.newPointGenerator(this.loadSeed(), this.loadCellDistanceFunction()); + IPointGenerator generator = pointGenerator; + if (this.has("Scale")) { + generator = new ScaledPointGenerator(pointGenerator, this.get("Scale").getAsDouble()); + } + + if (this.has("Randomizer")) { + generator = new DistortedPointGenerator(generator, new CoordinateRandomizerJsonLoader<>(this.seed, this.dataFolder, this.get("Randomizer")).load()); + } + + double offsetX = Double.NEGATIVE_INFINITY; + double offsetY = Double.NEGATIVE_INFINITY; + double offsetZ = Double.NEGATIVE_INFINITY; + if (this.has("Offset")) { + offsetX = offsetY = offsetZ = this.get("Offset").getAsDouble(); + } + + if (this.has("OffsetX")) { + offsetX = this.get("OffsetX").getAsDouble(); + } + + if (this.has("OffsetY")) { + offsetY = this.get("OffsetY").getAsDouble(); + } + + if (this.has("OffsetZ")) { + offsetZ = this.get("OffsetZ").getAsDouble(); + } + + if (offsetX != Double.NEGATIVE_INFINITY || offsetY != Double.NEGATIVE_INFINITY || offsetZ != Double.NEGATIVE_INFINITY) { + if (offsetX == Double.NEGATIVE_INFINITY) { + offsetX = 0.0; + } + + if (offsetY == Double.NEGATIVE_INFINITY) { + offsetY = 0.0; + } + + if (offsetZ == Double.NEGATIVE_INFINITY) { + offsetZ = 0.0; + } + + generator = new OffsetPointGenerator(generator, offsetX, offsetY, offsetZ); + } + + if (this.has("Rotate")) { + CoordinateRotator rotation = new CoordinateRotatorJsonLoader<>(this.seed, this.dataFolder, this.get("Rotate")).load(); + if (rotation != CoordinateRotator.NONE) { + generator = new DistortedPointGenerator(generator, rotation); + } + } + + return generator; + } + + protected int loadSeed() { + int seedVal = this.seed.hashCode(); + if (this.has("Seed")) { + SeedString overwritten = this.seed.appendToOriginal(this.get("Seed").getAsString()); + seedVal = overwritten.hashCode(); + this.seed.get().reportSeeds(seedVal, this.seed.original, this.seed.seed, overwritten.seed); + } else { + this.seed.get().reportSeeds(seedVal, this.seed.original, this.seed.seed, null); + } + + return seedVal; + } + + @Nonnull + protected PointGenerator newPointGenerator(int seedOffset, CellDistanceFunction cellDistanceFunction) { + K seedResource = this.seed.get(); + PointEvaluator pointEvaluator = this.loadPointEvaluator(); + return new SeedResourcePointGenerator(seedOffset, cellDistanceFunction, pointEvaluator, seedResource); + } + + @Nullable + protected CellDistanceFunction loadCellDistanceFunction() { + return new CellDistanceFunctionJsonLoader<>(this.seed, this.dataFolder, this.json, this.loadPointDistanceFunction()).load(); + } + + @Nullable + protected PointEvaluator loadPointEvaluator() { + return new PointEvaluatorJsonLoader<>(this.seed, this.dataFolder, this.json).load(); + } + + protected PointDistanceFunction loadPointDistanceFunction() { + DistanceCalculationMode distanceCalculationMode = CellNoiseJsonLoader.Constants.DEFAULT_DISTANCE_MODE; + if (this.has("DistanceMode")) { + distanceCalculationMode = DistanceCalculationMode.valueOf(this.get("DistanceMode").getAsString()); + } + + return distanceCalculationMode.getFunction(); + } + + public interface Constants { + String KEY_SEED = "Seed"; + String KEY_SCALE = "Scale"; + String KEY_RANDOMIZER = "Randomizer"; + String KEY_OFFSET = "Offset"; + String KEY_OFFSET_X = "OffsetX"; + String KEY_OFFSET_Y = "OffsetY"; + String KEY_OFFSET_Z = "OffsetZ"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/PointNoiseJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/PointNoiseJsonLoader.java new file mode 100644 index 0000000..b2f1ce4 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/PointNoiseJsonLoader.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.logic.PointNoise; +import java.nio.file.Path; +import javax.annotation.Nullable; + +public class PointNoiseJsonLoader extends JsonLoader { + public PointNoiseJsonLoader(SeedString seed, Path dataFolder, @Nullable JsonElement json) { + super(seed, dataFolder, json); + } + + @Nullable + public PointNoise load() { + return new PointNoise( + this.mustGetNumber("X", PointNoiseJsonLoader.Constants.DEFAULT_COORD).doubleValue(), + this.mustGetNumber("Y", PointNoiseJsonLoader.Constants.DEFAULT_COORD).doubleValue(), + this.mustGetNumber("Z", PointNoiseJsonLoader.Constants.DEFAULT_COORD).doubleValue(), + this.mustGetNumber("InnerRadius", PointNoiseJsonLoader.Constants.DEFAULT_RADIUS).doubleValue(), + this.mustGetNumber("OuterRadius", PointNoiseJsonLoader.Constants.DEFAULT_RADIUS).doubleValue() + ); + } + + public interface Constants { + String KEY_X = "X"; + String KEY_Y = "Y"; + String KEY_Z = "Z"; + String KEY_INNER_RADIUS = "InnerRadius"; + String KEY_OUTER_RADIUS = "OuterRadius"; + Double DEFAULT_COORD = 0.0; + Double DEFAULT_RADIUS = 0.0; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/SeedResource.java b/src/com/hypixel/hytale/procedurallib/json/SeedResource.java new file mode 100644 index 0000000..06aa9e2 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/SeedResource.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface SeedResource { + String INFO_SEED_REPORT = "Seed Value: %s for seed %s / %s"; + String INFO_SEED_OVERWRITE_REPORT = "Seed Value: %s for seed %s / %s overwritten by %s"; + + @Nonnull + default ResultBuffer.Bounds2d localBounds2d() { + return ResultBuffer.bounds2d; + } + + @Nonnull + default ResultBuffer.ResultBuffer2d localBuffer2d() { + return ResultBuffer.buffer2d; + } + + @Nonnull + default ResultBuffer.ResultBuffer3d localBuffer3d() { + return ResultBuffer.buffer3d; + } + + default boolean shouldReportSeeds() { + return false; + } + + default void reportSeeds(int seedVal, String original, String seed, @Nullable String overwritten) { + if (this.shouldReportSeeds()) { + if (overwritten == null) { + this.writeSeedReport(String.format("Seed Value: %s for seed %s / %s", seedVal, original, seed)); + } else { + this.writeSeedReport(String.format("Seed Value: %s for seed %s / %s overwritten by %s", seedVal, original, seed, overwritten)); + } + } + } + + default void writeSeedReport(String seedReport) { + System.out.println(seedReport); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/SeedResourcePointGenerator.java b/src/com/hypixel/hytale/procedurallib/json/SeedResourcePointGenerator.java new file mode 100644 index 0000000..3930ccb --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/SeedResourcePointGenerator.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import com.hypixel.hytale.procedurallib.logic.cell.CellDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.PointEvaluator; +import com.hypixel.hytale.procedurallib.logic.point.PointGenerator; +import javax.annotation.Nonnull; + +public class SeedResourcePointGenerator extends PointGenerator { + private final SeedResource seedResource; + + public SeedResourcePointGenerator(int seedOffset, CellDistanceFunction cellDistanceFunction, PointEvaluator pointEvaluator, SeedResource seedResource) { + super(seedOffset, cellDistanceFunction, pointEvaluator); + this.seedResource = seedResource; + } + + @Nonnull + @Override + protected ResultBuffer.Bounds2d localBounds2d() { + return this.seedResource.localBounds2d(); + } + + @Nonnull + @Override + protected ResultBuffer.ResultBuffer2d localBuffer2d() { + return this.seedResource.localBuffer2d(); + } + + @Nonnull + @Override + protected ResultBuffer.ResultBuffer3d localBuffer3d() { + return this.seedResource.localBuffer3d(); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/SeedString.java b/src/com/hypixel/hytale/procedurallib/json/SeedString.java new file mode 100644 index 0000000..9f6dc3a --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/SeedString.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.procedurallib.json; + +import io.sentry.util.Objects; +import javax.annotation.Nonnull; + +public class SeedString { + public static final SeedResource DEFAULT_RESOURCE = new SeedResource() {}; + @Nonnull + protected final T t; + protected final String original; + protected final String seed; + protected final int hash; + + public SeedString(String original, @Nonnull T t) { + this(original, original, t); + } + + public SeedString(String original, String seed, @Nonnull T t) { + this.original = original; + this.seed = seed; + this.t = Objects.requireNonNull(t, "SeedResource must not be null. Use SeedString#DEFAULT"); + this.hash = this.seed.hashCode() * 114512143; + } + + @Nonnull + public SeedString append(String suffix) { + return new SeedString<>(this.original, this.seed + suffix, this.t); + } + + @Nonnull + public SeedString appendToOriginal(String suffix) { + return new SeedString<>(this.original, this.original + suffix, this.t); + } + + @Nonnull + public SeedString alternateOriginal(String suffix) { + String altOriginal = this.original + suffix; + String altSeed = altOriginal + this.seed.substring(this.original.length()); + return new SeedString<>(altOriginal, altSeed, this.t); + } + + @Nonnull + public T get() { + return this.t; + } + + @Override + public int hashCode() { + return this.hash; + } + + @Override + public String toString() { + return this.seed; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/SimplexNoiseJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/SimplexNoiseJsonLoader.java new file mode 100644 index 0000000..48a6a7d --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/SimplexNoiseJsonLoader.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.NoiseFunction; +import com.hypixel.hytale.procedurallib.logic.SimplexNoise; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class SimplexNoiseJsonLoader extends JsonLoader { + public SimplexNoiseJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + super(seed.append(".SimplexNoise"), dataFolder, json); + } + + @Nonnull + public NoiseFunction load() { + return SimplexNoise.INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/json/ValueNoiseJsonLoader.java b/src/com/hypixel/hytale/procedurallib/json/ValueNoiseJsonLoader.java new file mode 100644 index 0000000..681929c --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/json/ValueNoiseJsonLoader.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.procedurallib.json; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.procedurallib.logic.GeneralNoise; +import com.hypixel.hytale.procedurallib.logic.ValueNoise; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class ValueNoiseJsonLoader extends JsonLoader { + public ValueNoiseJsonLoader(@Nonnull SeedString seed, Path dataFolder, JsonElement json) { + super(seed.append(".ValueNoise"), dataFolder, json); + } + + @Nonnull + public ValueNoise load() { + return new ValueNoise(this.loadInterpolationFunction()); + } + + protected GeneralNoise.InterpolationFunction loadInterpolationFunction() { + GeneralNoise.InterpolationMode interpolationMode = ValueNoiseJsonLoader.Constants.DEFAULT_INTERPOLATION_MODE; + if (this.has("InterpolationMode")) { + interpolationMode = GeneralNoise.InterpolationMode.valueOf(this.get("InterpolationMode").getAsString()); + } + + return interpolationMode.getFunction(); + } + + public interface Constants { + String KEY_INTERPOLATION_MODE = "InterpolationMode"; + GeneralNoise.InterpolationMode DEFAULT_INTERPOLATION_MODE = GeneralNoise.InterpolationMode.QUINTIC; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/BranchNoise.java b/src/com/hypixel/hytale/procedurallib/logic/BranchNoise.java new file mode 100644 index 0000000..2d64c95 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/BranchNoise.java @@ -0,0 +1,143 @@ +package com.hypixel.hytale.procedurallib.logic; + +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.procedurallib.NoiseFunction; +import com.hypixel.hytale.procedurallib.condition.IIntCondition; +import com.hypixel.hytale.procedurallib.logic.cell.CellDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.PointEvaluator; +import com.hypixel.hytale.procedurallib.property.NoiseFormulaProperty; +import com.hypixel.hytale.procedurallib.supplier.IDoubleRange; +import javax.annotation.Nonnull; + +public class BranchNoise implements NoiseFunction { + protected final CellDistanceFunction parentFunction; + protected final PointEvaluator parentEvaluator; + protected final double parentValue; + protected final double emptyValue; + protected final IDoubleRange parentFade; + protected final IIntCondition parentDensity; + protected final DistanceNoise.Distance2Function distance2Function; + protected final NoiseFormulaProperty.NoiseFormula.Formula noiseFormula; + protected final CellDistanceFunction lineFunction; + protected final PointEvaluator lineEvaluator; + protected final double lineScale; + protected final IDoubleRange lineThickness; + + public BranchNoise( + CellDistanceFunction parentFunction, + PointEvaluator parentEvaluator, + double parentValue, + IDoubleRange parentFade, + IIntCondition parentDensity, + DistanceNoise.Distance2Function distance2Function, + NoiseFormulaProperty.NoiseFormula.Formula noiseFormula, + CellDistanceFunction lineFunction, + PointEvaluator lineEvaluator, + double lineScale, + IDoubleRange lineThickness + ) { + this.parentFunction = parentFunction; + this.parentEvaluator = parentEvaluator; + this.parentValue = parentValue; + this.emptyValue = toOutputRange(parentValue); + this.parentFade = parentFade; + this.parentDensity = parentDensity; + this.distance2Function = distance2Function; + this.noiseFormula = noiseFormula; + this.lineFunction = lineFunction; + this.lineEvaluator = lineEvaluator; + this.lineScale = lineScale; + this.lineThickness = lineThickness; + } + + @Override + public double get(int seed, int offsetSeed, double x, double y) { + ResultBuffer.ResultBuffer2d parent = this.getParentNoise(offsetSeed, x, y); + if (!this.parentDensity.eval(parent.hash)) { + return this.emptyValue; + } else { + double parentDistance = this.noiseFormula.eval(this.distance2Function.eval(parent.distance, parent.distance2)); + double lineValue = this.getLineValue(offsetSeed, x, y, parent.hash, parent.x, parent.y, parentDistance, parent); + double parentFade = this.parentFade.getValue(parentDistance); + double noiseValue = MathUtil.lerp(this.parentValue, lineValue, parentFade); + return toOutputRange(noiseValue); + } + } + + @Override + public double get(int seed, int offsetSeed, double x, double y, double z) { + throw new UnsupportedOperationException(); + } + + @Nonnull + protected ResultBuffer.ResultBuffer2d localBuffer2d() { + return ResultBuffer.buffer2d; + } + + @Nonnull + protected ResultBuffer.ResultBuffer2d getParentNoise(int seed, double x, double y) { + ResultBuffer.ResultBuffer2d buffer = this.localBuffer2d(); + buffer.distance = Double.POSITIVE_INFINITY; + buffer.distance2 = Double.POSITIVE_INFINITY; + x = this.parentFunction.scale(x); + y = this.parentFunction.scale(y); + int cellX = this.parentFunction.getCellX(x, y); + int cellY = this.parentFunction.getCellY(x, y); + this.parentFunction.transition2D(seed, x, y, cellX, cellY, buffer, this.parentEvaluator); + return buffer; + } + + protected double getLineValue( + int seed, double x, double y, int parentHash, double parentX, double parentY, double parentDistance, @Nonnull ResultBuffer.ResultBuffer2d buffer + ) { + double thickness = this.lineThickness.getValue(parentDistance); + if (thickness == 0.0) { + return 1.0; + } else { + buffer.distance = Double.POSITIVE_INFINITY; + buffer.x2 = parentX; + buffer.y2 = parentY; + buffer.ix2 = parentHash; + buffer.distance2 = thickness; + x *= this.lineScale; + y *= this.lineScale; + x = this.lineFunction.scale(x); + y = this.lineFunction.scale(y); + int cellX = this.lineFunction.getCellX(x, y); + int cellY = this.lineFunction.getCellY(x, y); + this.lineFunction.nearest2D(seed, x, y, cellX, cellY, buffer, this.lineEvaluator); + double distance = buffer.distance; + return distance >= thickness * thickness ? 1.0 : Math.sqrt(distance) / thickness; + } + } + + @Nonnull + @Override + public String toString() { + return "BranchNoise{parentFunction=" + + this.parentFunction + + ", parentEvaluator=" + + this.parentEvaluator + + ", parentValue=" + + this.parentValue + + ", parentFade=" + + this.parentFade + + ", distance2Function=" + + this.distance2Function + + ", noiseFormula=" + + this.noiseFormula + + ", lineFunction=" + + this.lineFunction + + ", lineEvaluator=" + + this.lineEvaluator + + ", lineScale=" + + this.lineScale + + ", lineThickness=" + + this.lineThickness + + "}"; + } + + protected static double toOutputRange(double value) { + return 2.0 * value - 1.0; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/CellNoise.java b/src/com/hypixel/hytale/procedurallib/logic/CellNoise.java new file mode 100644 index 0000000..05c0619 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/CellNoise.java @@ -0,0 +1,268 @@ +package com.hypixel.hytale.procedurallib.logic; + +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; +import com.hypixel.hytale.procedurallib.NoiseFunction; +import com.hypixel.hytale.procedurallib.logic.cell.CellDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.PointEvaluator; +import com.hypixel.hytale.procedurallib.property.NoiseProperty; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CellNoise implements NoiseFunction { + protected final CellDistanceFunction distanceFunction; + protected final PointEvaluator pointEvaluator; + protected final CellNoise.CellFunction cellFunction; + @Nullable + protected final NoiseProperty noiseLookup; + + public CellNoise( + CellDistanceFunction distanceFunction, PointEvaluator pointEvaluator, CellNoise.CellFunction cellFunction, @Nullable NoiseProperty noiseLookup + ) { + this.distanceFunction = distanceFunction; + this.pointEvaluator = pointEvaluator; + this.cellFunction = cellFunction; + this.noiseLookup = noiseLookup; + } + + public CellDistanceFunction getDistanceFunction() { + return this.distanceFunction; + } + + public CellNoise.CellFunction getCellFunction() { + return this.cellFunction; + } + + @Nullable + public NoiseProperty getNoiseLookup() { + return this.noiseLookup; + } + + @Override + public double get(int seed, int offsetSeed, double x, double y) { + x = this.distanceFunction.scale(x); + y = this.distanceFunction.scale(y); + int xr = this.distanceFunction.getCellX(x, y); + int yr = this.distanceFunction.getCellY(x, y); + ResultBuffer.ResultBuffer2d buffer = this.localBuffer2d(); + buffer.distance = Double.POSITIVE_INFINITY; + this.distanceFunction.nearest2D(offsetSeed, x, y, xr, yr, buffer, this.pointEvaluator); + return GeneralNoise.limit(this.cellFunction.eval(seed, offsetSeed, x, y, buffer, this.distanceFunction, this.noiseLookup)) * 2.0 - 1.0; + } + + @Override + public double get(int seed, int offsetSeed, double x, double y, double z) { + int xr = this.distanceFunction.getCellX(x, y, z); + int yr = this.distanceFunction.getCellY(x, y, z); + int zr = this.distanceFunction.getCellZ(x, y, z); + ResultBuffer.ResultBuffer3d buffer = this.localBuffer3d(); + buffer.distance = Double.POSITIVE_INFINITY; + this.distanceFunction.nearest3D(offsetSeed, x, y, z, xr, yr, zr, buffer, this.pointEvaluator); + return GeneralNoise.limit(this.cellFunction.eval(seed, offsetSeed, x, y, z, buffer, this.distanceFunction, this.noiseLookup)) * 2.0 - 1.0; + } + + protected ResultBuffer.ResultBuffer2d localBuffer2d() { + return ResultBuffer.buffer2d; + } + + protected ResultBuffer.ResultBuffer3d localBuffer3d() { + return ResultBuffer.buffer3d; + } + + @Nonnull + @Override + public String toString() { + return "CellNoise{distanceFunction=" + + this.distanceFunction + + ", pointEvaluator=" + + this.pointEvaluator + + ", cellFunction=" + + this.cellFunction + + ", noiseLookup=" + + this.noiseLookup + + "}"; + } + + public interface CellFunction { + double eval(int var1, int var2, double var3, double var5, ResultBuffer.ResultBuffer2d var7, CellDistanceFunction var8, NoiseProperty var9); + + double eval(int var1, int var2, double var3, double var5, double var7, ResultBuffer.ResultBuffer3d var9, CellDistanceFunction var10, NoiseProperty var11); + } + + public static enum CellMode { + CELL_VALUE( + new CellNoise.CellFunction() { + @Override + public double eval( + int seed, + int offsetSeed, + double x, + double y, + @Nonnull ResultBuffer.ResultBuffer2d buffer, + CellDistanceFunction cellFunction, + NoiseProperty noiseLookup + ) { + return HashUtil.random(offsetSeed, buffer.ix, buffer.iy); + } + + @Override + public double eval( + int seed, + int offsetSeed, + double x, + double y, + double z, + @Nonnull ResultBuffer.ResultBuffer3d buffer, + CellDistanceFunction cellFunction, + NoiseProperty noiseLookup + ) { + return HashUtil.random(offsetSeed, buffer.ix, buffer.iy, buffer.iz); + } + + @Nonnull + @Override + public String toString() { + return "CellValueCellFunction{}"; + } + } + ), + NOISE_LOOKUP( + new CellNoise.CellFunction() { + @Override + public double eval( + int seed, + int offsetSeed, + double x, + double y, + @Nonnull ResultBuffer.ResultBuffer2d buffer, + @Nonnull CellDistanceFunction cellFunction, + @Nonnull NoiseProperty noiseLookup + ) { + double px = cellFunction.invScale(buffer.x); + double py = cellFunction.invScale(buffer.y); + return noiseLookup.get(seed, px, py); + } + + @Override + public double eval( + int seed, + int offsetSeed, + double x, + double y, + double z, + @Nonnull ResultBuffer.ResultBuffer3d buffer, + @Nonnull CellDistanceFunction cellFunction, + @Nonnull NoiseProperty noiseLookup + ) { + double px = cellFunction.invScale(buffer.x); + double py = cellFunction.invScale(buffer.y); + double pz = cellFunction.invScale(buffer.z); + return noiseLookup.get(seed, px, py, pz); + } + + @Nonnull + @Override + public String toString() { + return "NoiseLookupCellFunction{}"; + } + } + ), + DISTANCE( + new CellNoise.CellFunction() { + @Override + public double eval( + int seed, + int offsetSeed, + double x, + double y, + @Nonnull ResultBuffer.ResultBuffer2d buffer, + CellDistanceFunction cellFunction, + NoiseProperty noiseLookup + ) { + return Math.sqrt(buffer.distance); + } + + @Override + public double eval( + int seed, + int offsetSeed, + double x, + double y, + double z, + @Nonnull ResultBuffer.ResultBuffer3d buffer, + CellDistanceFunction cellFunction, + NoiseProperty noiseLookup + ) { + return Math.sqrt(buffer.distance); + } + + @Nonnull + @Override + public String toString() { + return "DistanceCellFunction{}"; + } + } + ), + DIRECTION( + new CellNoise.CellFunction() { + @Override + public double eval( + int seed, + int offsetSeed, + double x, + double y, + @Nonnull ResultBuffer.ResultBuffer2d buffer, + CellDistanceFunction cellFunction, + NoiseProperty noiseLookup + ) { + float angle = (float)this.getAngleNoise(seed, offsetSeed, buffer, noiseLookup) * (float) (Math.PI * 2); + float dx = TrigMathUtil.sin(angle); + float dy = TrigMathUtil.cos(angle); + double ax = buffer.x; + double ay = buffer.y; + double bx = ax + dx; + double by = ay + dy; + double distance2 = MathUtil.distanceToInfLineSq(x, y, ax, ay, bx, by); + double distance = MathUtil.clamp(Math.sqrt(distance2), 0.0, 1.0); + int side = MathUtil.sideOfLine(x, y, ax, ay, bx, by); + return 0.5 + side * distance * 0.5; + } + + @Override + public double eval( + int seed, + int offsetSeed, + double x, + double y, + double z, + ResultBuffer.ResultBuffer3d buffer, + CellDistanceFunction cellFunction, + NoiseProperty noiseLookup + ) { + throw new UnsupportedOperationException(); + } + + @Nonnull + @Override + public String toString() { + return "DirectionCellFunction{}"; + } + + private double getAngleNoise(int seed, int offsetSeed, @Nonnull ResultBuffer.ResultBuffer2d buffer, @Nullable NoiseProperty noiseProperty) { + return noiseProperty != null ? noiseProperty.get(seed, buffer.x, buffer.y) : HashUtil.random(offsetSeed, buffer.ix, buffer.iy); + } + } + ); + + private final CellNoise.CellFunction function; + + private CellMode(CellNoise.CellFunction function) { + this.function = function; + } + + public CellNoise.CellFunction getFunction() { + return this.function; + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/CellularNoise.java b/src/com/hypixel/hytale/procedurallib/logic/CellularNoise.java new file mode 100644 index 0000000..686a7d6 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/CellularNoise.java @@ -0,0 +1,524 @@ +package com.hypixel.hytale.procedurallib.logic; + +public final class CellularNoise { + public static final DoubleArray.Double2[] CELL_2D = new DoubleArray.Double2[]{ + new DoubleArray.Double2(0.47180947589876066, 0.10010325570378009), + new DoubleArray.Double2(0.6582139851410512, 0.8262404986000542), + new DoubleArray.Double2(0.060299854174567735, 0.21787156789560047), + new DoubleArray.Double2(0.8808986116722551, 0.33658412953688055), + new DoubleArray.Double2(0.3714195695725244, 0.768120849768431), + new DoubleArray.Double2(0.09857066064836995, 0.9939939921911041), + new DoubleArray.Double2(0.15485458734766344, 0.3928954542002433), + new DoubleArray.Double2(0.9922045975726352, 0.8781419853882811), + new DoubleArray.Double2(0.008705013059587041, 0.974686972731753), + new DoubleArray.Double2(0.7983288471417401, 0.8054775351421807), + new DoubleArray.Double2(0.5254018802000332, 0.9423378417009262), + new DoubleArray.Double2(0.479576510054618, 0.28443303595399105), + new DoubleArray.Double2(0.8521033941678655, 0.16416123707637054), + new DoubleArray.Double2(0.8268814520348069, 0.3056028805792739), + new DoubleArray.Double2(0.455029345682364, 0.33712148754429716), + new DoubleArray.Double2(0.5625642639303857, 0.5031811052224963), + new DoubleArray.Double2(0.28832635638511517, 0.4498640176465032), + new DoubleArray.Double2(0.2528101074109763, 0.34601344865913897), + new DoubleArray.Double2(0.8854383157052419, 0.7225796883588538), + new DoubleArray.Double2(0.9835486064765037, 0.7949399860092702), + new DoubleArray.Double2(0.11458992377268296, 0.4081333197842737), + new DoubleArray.Double2(0.30550346902979353, 0.29767451039874593), + new DoubleArray.Double2(0.4698737794344445, 0.9765817242332272), + new DoubleArray.Double2(0.9121876470398682, 0.08328737771334582), + new DoubleArray.Double2(0.8055641969567519, 0.49915173459909945), + new DoubleArray.Double2(0.8788773613409282, 0.4654287964144853), + new DoubleArray.Double2(0.36366409349691786, 0.49794409355340885), + new DoubleArray.Double2(0.06110621135011585, 0.26674980416355654), + new DoubleArray.Double2(0.7933235058968467, 0.13407455447708283), + new DoubleArray.Double2(0.4279785331196997, 0.5365485450047299), + new DoubleArray.Double2(0.6156518645642396, 0.3696584646568726), + new DoubleArray.Double2(0.6886089674512008, 0.4494897513681063), + new DoubleArray.Double2(0.24857824294711872, 0.6579906472032528), + new DoubleArray.Double2(0.5592526595751096, 0.6453155441764402), + new DoubleArray.Double2(0.2737971253836019, 0.7914679445390865), + new DoubleArray.Double2(0.4141988848377518, 0.29778631340990025), + new DoubleArray.Double2(0.10146979716630666, 0.7682466626146758), + new DoubleArray.Double2(0.8652583182914103, 0.8856601596976689), + new DoubleArray.Double2(0.5682342121736954, 0.9263225533174646), + new DoubleArray.Double2(0.022681966258335517, 0.9793404596414274), + new DoubleArray.Double2(0.2902095526215689, 0.13766674385199218), + new DoubleArray.Double2(0.8046079846361107, 0.35389064300633244), + new DoubleArray.Double2(0.7850634272026246, 0.5552455270399949), + new DoubleArray.Double2(0.6826397765183345, 0.39243629337801367), + new DoubleArray.Double2(0.4758436622147303, 0.2621981755663656), + new DoubleArray.Double2(0.6779173195100315, 0.0535442251471665), + new DoubleArray.Double2(0.69415881250885, 0.18514135989533453), + new DoubleArray.Double2(0.2550508344044362, 0.6419895419031048), + new DoubleArray.Double2(0.7114241648953638, 0.8025147164663079), + new DoubleArray.Double2(5.440409414710912E-4, 0.2443907615760098), + new DoubleArray.Double2(0.6442734389572541, 0.5040771264552235), + new DoubleArray.Double2(0.6178421908977659, 0.9695144766210455), + new DoubleArray.Double2(0.8038685079665406, 0.9391086073549112), + new DoubleArray.Double2(0.8228974512344162, 0.06762070967481604), + new DoubleArray.Double2(0.6887230523035848, 0.3285680918635546), + new DoubleArray.Double2(0.18215246096333548, 0.5940405702262396), + new DoubleArray.Double2(0.35678794549012127, 0.5104026667658235), + new DoubleArray.Double2(0.7991002762513103, 0.5292483006740865), + new DoubleArray.Double2(0.35492984553782503, 0.43739903884424114), + new DoubleArray.Double2(0.026087430917164922, 0.17100999286660878), + new DoubleArray.Double2(0.7325666882329042, 0.9534136069095293), + new DoubleArray.Double2(0.05478352346186488, 0.1737613376488334), + new DoubleArray.Double2(0.9211070874210342, 0.9365124162997702), + new DoubleArray.Double2(0.3217497992313928, 0.8261875596440402), + new DoubleArray.Double2(0.42197268594244, 0.316497914983048), + new DoubleArray.Double2(0.1547960972948098, 0.03004874205509034), + new DoubleArray.Double2(0.31336638393678595, 0.049494701119678686), + new DoubleArray.Double2(0.981323852403765, 0.9716138365489692), + new DoubleArray.Double2(0.5595812840944749, 0.8637114698075148), + new DoubleArray.Double2(0.7216101689967135, 0.6616374071329467), + new DoubleArray.Double2(0.9865283100223344, 0.47819214502606355), + new DoubleArray.Double2(0.6979966710047217, 0.360633218337461), + new DoubleArray.Double2(0.573293433112957, 0.04031415689036677), + new DoubleArray.Double2(0.5052034360377421, 0.40326907086119124), + new DoubleArray.Double2(0.401918076541921, 0.7279871267120137), + new DoubleArray.Double2(0.8074971369150514, 0.08902776740432816), + new DoubleArray.Double2(0.20622902910289442, 0.8189508530422801), + new DoubleArray.Double2(0.9551999760736423, 0.1158490722633474), + new DoubleArray.Double2(0.9654272008353522, 0.04226642266110325), + new DoubleArray.Double2(0.6460336290621725, 0.49928839619264564), + new DoubleArray.Double2(0.39927719075686996, 0.6960397977764178), + new DoubleArray.Double2(0.5960504207218852, 0.04179766649727956), + new DoubleArray.Double2(0.5391225850339417, 0.8406126094532529), + new DoubleArray.Double2(0.33041201394413844, 0.1950544131612003), + new DoubleArray.Double2(0.772584535763241, 0.8578998343788431), + new DoubleArray.Double2(0.756032952995581, 0.5928356507694468), + new DoubleArray.Double2(0.8896842610495895, 0.805249918395682), + new DoubleArray.Double2(0.13611513862439495, 0.6227528434868839), + new DoubleArray.Double2(0.6942626131763145, 0.2861476347352079), + new DoubleArray.Double2(0.4508564869524504, 0.15223520747398012), + new DoubleArray.Double2(0.6869529158564468, 0.010275643525547551), + new DoubleArray.Double2(0.8991249034951607, 0.9274685966776277), + new DoubleArray.Double2(0.17459587545149402, 0.8218961151310872), + new DoubleArray.Double2(0.47264700441124174, 0.223567306815727), + new DoubleArray.Double2(0.9158684783226829, 0.7382084908782176), + new DoubleArray.Double2(0.11726815974536398, 0.04007704873508389), + new DoubleArray.Double2(0.07403127955046285, 0.45828561159648584), + new DoubleArray.Double2(0.9850985637550728, 0.5079770447040179), + new DoubleArray.Double2(0.8150813058948968, 0.8783850967276464), + new DoubleArray.Double2(0.6768680825257113, 0.20266986517348928), + new DoubleArray.Double2(0.3632311466885596, 0.380878493020666), + new DoubleArray.Double2(0.1755699780258253, 0.7420179378013317), + new DoubleArray.Double2(0.7055167085003047, 0.05173588662596251), + new DoubleArray.Double2(0.3804816612497942, 0.3893056250833372), + new DoubleArray.Double2(0.423469900742832, 0.5254490562793052), + new DoubleArray.Double2(0.7649086400761712, 0.37807666327232436), + new DoubleArray.Double2(0.32217445972795666, 0.856471629608604), + new DoubleArray.Double2(0.701505039980196, 0.32911642272358155), + new DoubleArray.Double2(0.6256565905644079, 0.6726996423704521), + new DoubleArray.Double2(0.4257826633467111, 0.43941931727146155), + new DoubleArray.Double2(0.5124445169418516, 0.8792118668426957), + new DoubleArray.Double2(0.4414277144169523, 0.08880164175254823), + new DoubleArray.Double2(0.6762640173793466, 0.3494340354903285), + new DoubleArray.Double2(0.4103024807586766, 0.1318905083513665), + new DoubleArray.Double2(0.41421987768088586, 0.366086442425132), + new DoubleArray.Double2(0.21551696826004196, 0.42154016053766596), + new DoubleArray.Double2(0.4684457988269013, 0.05572010477498579), + new DoubleArray.Double2(0.9377133012576763, 0.04429656099579915), + new DoubleArray.Double2(0.47708826338508314, 0.6983415316748953), + new DoubleArray.Double2(0.9427136076173241, 0.3393523699187059), + new DoubleArray.Double2(0.42662280921754636, 0.5875718789209357), + new DoubleArray.Double2(0.14293963355416217, 0.019503988997184973), + new DoubleArray.Double2(0.9398835503524586, 0.06698114875691696), + new DoubleArray.Double2(0.7655205922964359, 0.6159430010943204), + new DoubleArray.Double2(0.5828200816905413, 0.47468349769850116), + new DoubleArray.Double2(0.6367631052327425, 0.028284655443643003), + new DoubleArray.Double2(0.6278783483933233, 0.9909757748511685), + new DoubleArray.Double2(0.07566531717826297, 0.17705628656760442), + new DoubleArray.Double2(0.8730352467749348, 0.8027451486146205), + new DoubleArray.Double2(0.9784147495231154, 0.6279387119802035), + new DoubleArray.Double2(0.18289892275786424, 0.5735689322420974), + new DoubleArray.Double2(0.5713173287750111, 0.5853745039745303), + new DoubleArray.Double2(0.3919203157400989, 0.9398731272650476), + new DoubleArray.Double2(0.611363218872746, 0.8377265180423282), + new DoubleArray.Double2(0.08964374804509467, 0.6412132188822265), + new DoubleArray.Double2(0.5216422525222328, 0.0769912245653056), + new DoubleArray.Double2(0.5940573691496348, 0.9366847182285464), + new DoubleArray.Double2(0.06308000075651643, 0.5131410574095199), + new DoubleArray.Double2(0.11148697162539889, 0.1596670534881245), + new DoubleArray.Double2(0.664473529643324, 0.8564906365374716), + new DoubleArray.Double2(0.6801099068506787, 0.6195495209529641), + new DoubleArray.Double2(0.4595628638954705, 0.5510519410184213), + new DoubleArray.Double2(0.9990478741190895, 0.5717831912371493), + new DoubleArray.Double2(0.41288307441196914, 0.6076638701100168), + new DoubleArray.Double2(0.6561309488036686, 0.6465481186514548), + new DoubleArray.Double2(0.6553989306948486, 0.5077519843919702), + new DoubleArray.Double2(0.5848077311230713, 0.9515204761900101), + new DoubleArray.Double2(0.29643205577027976, 0.4268940000111303), + new DoubleArray.Double2(0.8043089798829277, 0.045669617801190054), + new DoubleArray.Double2(0.5301485966229557, 0.46193821603381346), + new DoubleArray.Double2(0.6270554543006722, 0.11871001716742124), + new DoubleArray.Double2(0.8128830705902976, 0.6362405405101608), + new DoubleArray.Double2(0.5534333123408667, 0.004779202131991456), + new DoubleArray.Double2(0.2518314017684128, 0.2046579549672547), + new DoubleArray.Double2(0.016442827430870555, 0.1091068583251188), + new DoubleArray.Double2(0.11615512860344301, 0.18325504161117812), + new DoubleArray.Double2(0.987359817505127, 0.9328869232234126), + new DoubleArray.Double2(0.558295172154883, 0.08825161260226644), + new DoubleArray.Double2(0.208431035906942, 0.6442586813328038), + new DoubleArray.Double2(0.21630242571293767, 0.5845589368148324), + new DoubleArray.Double2(0.47975845884043533, 0.06224899825757857), + new DoubleArray.Double2(0.35724985642578766, 0.11632328179293294), + new DoubleArray.Double2(0.9949095362487558, 0.49572263275302453), + new DoubleArray.Double2(0.617298407670169, 0.6202146773712007), + new DoubleArray.Double2(0.19342373440668603, 0.11083299350850262), + new DoubleArray.Double2(0.19574714451010755, 0.10677037338808326), + new DoubleArray.Double2(0.9782684053251989, 0.8881614782494331), + new DoubleArray.Double2(0.5684060784782837, 0.23592165360760453), + new DoubleArray.Double2(0.4324294533944407, 0.7746778288390596), + new DoubleArray.Double2(0.3998869139021711, 0.009903683512448413), + new DoubleArray.Double2(0.361205819347984, 0.33585051249655773), + new DoubleArray.Double2(0.3549162715708134, 0.36006815432092465), + new DoubleArray.Double2(0.0854089175164573, 0.06968025745781947), + new DoubleArray.Double2(0.9355322830705224, 0.7136888613084551), + new DoubleArray.Double2(0.9155513478479069, 0.5571262103536875), + new DoubleArray.Double2(0.3040345177536564, 0.5926703954318201), + new DoubleArray.Double2(0.6482419244858578, 0.7923386742505345), + new DoubleArray.Double2(0.29081399725380375, 0.918140950948637), + new DoubleArray.Double2(0.2433868393546761, 0.361308174834882), + new DoubleArray.Double2(0.43169841422273125, 0.9732300998583766), + new DoubleArray.Double2(0.5644296867112906, 0.6189858994879008), + new DoubleArray.Double2(0.9312057249930703, 0.9109028509889038), + new DoubleArray.Double2(0.693745770255897, 0.6526254195917649), + new DoubleArray.Double2(0.9061061211950484, 0.28454177156529814), + new DoubleArray.Double2(0.8714390153844689, 0.6130417688726467), + new DoubleArray.Double2(0.12795140044040476, 0.32603910410232784), + new DoubleArray.Double2(0.5481725727860118, 0.9456781912638283), + new DoubleArray.Double2(0.753737719102159, 0.5994808998770094), + new DoubleArray.Double2(0.39129701955263374, 0.15981718417815094), + new DoubleArray.Double2(0.21062578001131516, 0.21807634455142966), + new DoubleArray.Double2(0.33842295518181, 0.03696695800877303), + new DoubleArray.Double2(0.4836504775668222, 0.027804348769490672), + new DoubleArray.Double2(0.9537174691856497, 0.5984005224968657), + new DoubleArray.Double2(0.5446096240922672, 0.033321540767137336), + new DoubleArray.Double2(0.40958675230705033, 0.7270191660705817), + new DoubleArray.Double2(0.555829240120629, 0.8699942938890834), + new DoubleArray.Double2(0.3900402431936316, 0.07015793169088058), + new DoubleArray.Double2(0.784260709623738, 0.31048933804066026), + new DoubleArray.Double2(0.0919141186171345, 0.7376458004797859), + new DoubleArray.Double2(0.8693731919502589, 0.6351767310450896), + new DoubleArray.Double2(0.28626492944842696, 0.3533785963274141), + new DoubleArray.Double2(0.2025617787963354, 0.1926696610481956), + new DoubleArray.Double2(0.09462696684482363, 0.21885821018259044), + new DoubleArray.Double2(0.6963005350050747, 0.6301461051889178), + new DoubleArray.Double2(0.7334599903142709, 0.7035962707235721), + new DoubleArray.Double2(0.14802689045069295, 0.08266706747922048), + new DoubleArray.Double2(0.9849961588041299, 0.489977092132285), + new DoubleArray.Double2(0.7367517491578646, 0.0765270463736959), + new DoubleArray.Double2(0.5638286128950012, 0.9691183198453535), + new DoubleArray.Double2(0.9296473464739099, 0.023742617607517058), + new DoubleArray.Double2(0.8659662250335082, 0.9094645640261848), + new DoubleArray.Double2(0.2213733139287234, 0.9631045158379444), + new DoubleArray.Double2(0.8107125010409592, 0.5012935870369806), + new DoubleArray.Double2(0.6806582500013116, 0.6377902800669086), + new DoubleArray.Double2(0.6156087380736666, 0.018010497515211554), + new DoubleArray.Double2(0.23666274082723815, 0.09035374358800163), + new DoubleArray.Double2(0.3808150934014132, 0.6290502473691526), + new DoubleArray.Double2(0.0019072923993970203, 0.7029782735924006), + new DoubleArray.Double2(0.3016854380982885, 0.34940465996754677), + new DoubleArray.Double2(0.6990562171282618, 0.014201208468729853), + new DoubleArray.Double2(0.17692502498137508, 0.7172348389224261), + new DoubleArray.Double2(0.4356067885858421, 0.7028870893172243), + new DoubleArray.Double2(0.6346025709691117, 0.553725665250114), + new DoubleArray.Double2(0.6535403444357922, 0.8460474982048107), + new DoubleArray.Double2(0.1594215027531598, 0.07828404057625382), + new DoubleArray.Double2(0.07025022238630085, 0.9291755694125372), + new DoubleArray.Double2(0.5720374852795898, 0.7273781400482922), + new DoubleArray.Double2(0.822568877969468, 0.11608409761428362), + new DoubleArray.Double2(0.08484258414491475, 0.25675997883795587), + new DoubleArray.Double2(0.8274745971553557, 0.9698056765237659), + new DoubleArray.Double2(0.4385189410080055, 0.09450914344051808), + new DoubleArray.Double2(0.5810659871811015, 0.15693438368308876), + new DoubleArray.Double2(0.9097977532849856, 0.45628066953505464), + new DoubleArray.Double2(0.23164992041658194, 0.6648580077931808), + new DoubleArray.Double2(0.17324224339948502, 0.43433540271037285), + new DoubleArray.Double2(0.7832972193072434, 0.7146510917691076), + new DoubleArray.Double2(0.09850595518769889, 0.128376727082536), + new DoubleArray.Double2(0.7129769165834041, 0.8619685463616583), + new DoubleArray.Double2(0.5694005131367491, 0.898912350958748), + new DoubleArray.Double2(0.1173892617821034, 0.7441004347669138), + new DoubleArray.Double2(0.32224028159375206, 0.992805641674665), + new DoubleArray.Double2(0.7448741847569987, 0.5534184284677361), + new DoubleArray.Double2(0.6183131989684718, 0.1158762538272019), + new DoubleArray.Double2(0.9132800948987562, 0.5957651916557913), + new DoubleArray.Double2(0.28896087843272467, 0.008854615551886158), + new DoubleArray.Double2(0.3664364693576271, 0.22690783743510767), + new DoubleArray.Double2(0.3216333897242434, 0.7206909100840025), + new DoubleArray.Double2(0.5798802506633449, 0.050997222933716335), + new DoubleArray.Double2(0.691416814135484, 0.06716374298017558), + new DoubleArray.Double2(0.8907446570886256, 0.5120450501580214), + new DoubleArray.Double2(0.5849148903239736, 0.712929788700875), + new DoubleArray.Double2(0.09722707470401815, 0.9127509172465725), + new DoubleArray.Double2(0.5119985905227081, 0.42160016001492606), + new DoubleArray.Double2(0.8498092499458229, 0.45458214225650106), + new DoubleArray.Double2(0.05582035794041318, 0.28112524363413915), + new DoubleArray.Double2(0.23941274443221294, 0.10882900274135532) + }; + public static final DoubleArray.Double3[] CELL_3D = new DoubleArray.Double3[]{ + new DoubleArray.Double3(0.5551930115283641, 0.25944353613123683, 0.9897795547500035), + new DoubleArray.Double3(0.08903233731716276, 0.5766530544972668, 0.5581141126660686), + new DoubleArray.Double3(0.5016892353149837, 0.42279457901426454, 0.5116394539560017), + new DoubleArray.Double3(0.14039418314827878, 0.8815805778530666, 0.8869863095838658), + new DoubleArray.Double3(0.3394723423116427, 0.9851707932228085, 0.7370174077732932), + new DoubleArray.Double3(0.4475551695445372, 0.05031981969022781, 0.15112747781221292), + new DoubleArray.Double3(0.1748678669537691, 0.10541576014539822, 0.6317003027921491), + new DoubleArray.Double3(0.742925866537118, 0.11294353382189515, 0.35183412769933), + new DoubleArray.Double3(0.10605312336507067, 0.9744227533591625, 0.19100080557990295), + new DoubleArray.Double3(0.924537562176237, 3.894213601214336E-4, 0.7258037580932201), + new DoubleArray.Double3(0.9692453482631498, 0.7599653843515939, 0.53235565996726), + new DoubleArray.Double3(0.3401471697729571, 0.20892630059070283, 0.7138351361501816), + new DoubleArray.Double3(0.3566120519465583, 0.6598153024312823, 0.2923010250672964), + new DoubleArray.Double3(0.7052059475525418, 0.9312493041986528, 0.46420225560557515), + new DoubleArray.Double3(0.2581979303653379, 0.42847888690251557, 0.436036365216368), + new DoubleArray.Double3(0.5377508698485963, 0.5045988931383829, 0.6732815281684017), + new DoubleArray.Double3(0.7026131573312774, 0.5707837889991815, 0.9411090918835752), + new DoubleArray.Double3(0.5939424874001719, 0.8013116288048497, 0.9755769257790964), + new DoubleArray.Double3(0.26187677087919903, 0.9782686388840917, 0.7701099527320081), + new DoubleArray.Double3(0.48623563792824565, 0.05347108196979089, 0.6795962336667396), + new DoubleArray.Double3(0.4682935769549763, 0.10331773251711618, 0.6524215586658068), + new DoubleArray.Double3(0.8965945269937503, 0.6907184561452394, 0.5427792711902859), + new DoubleArray.Double3(0.023567394423507304, 0.912058624528632, 0.48814142043087616), + new DoubleArray.Double3(0.8537028635952884, 0.44306113277671355, 0.23493584318734861), + new DoubleArray.Double3(0.10490293663444628, 0.2803998954398492, 0.4388156376942073), + new DoubleArray.Double3(0.8290876364984132, 0.06731794700007787, 0.761862277735388), + new DoubleArray.Double3(0.26637911283205884, 0.25779759301782934, 0.7344512497448037), + new DoubleArray.Double3(0.6851706094193074, 0.17966816855908874, 0.12479260159244576), + new DoubleArray.Double3(0.057523212828050396, 0.6393499430569903, 0.8936840620001176), + new DoubleArray.Double3(0.15055351912716264, 0.3850126311394939, 0.9522958659572069), + new DoubleArray.Double3(0.9302827496039661, 0.586948299959885, 0.8975940573885638), + new DoubleArray.Double3(0.7357773337796518, 0.9882645041397796, 0.44372908093615193), + new DoubleArray.Double3(0.6603662281818136, 0.012155252908142011, 0.0854945122304922), + new DoubleArray.Double3(0.17719227197773135, 0.3393998453758579, 0.25133802107147984), + new DoubleArray.Double3(0.9834606093311484, 0.0743084406466985, 0.3454520861138973), + new DoubleArray.Double3(0.3076292933868209, 0.3236203318242352, 0.5548867456221304), + new DoubleArray.Double3(0.5397663031436933, 0.3135046579280607, 0.9800841182274359), + new DoubleArray.Double3(0.009846555206137309, 0.6651954896443023, 0.560987896490684), + new DoubleArray.Double3(0.36462712777478035, 0.5954262657270158, 0.3504581884333987), + new DoubleArray.Double3(0.7811794977782256, 0.8077177217587449, 0.6111677905329287), + new DoubleArray.Double3(0.8761560264179801, 0.5186233004247778, 0.6831950832236815), + new DoubleArray.Double3(0.7037724581931909, 0.8882524895526355, 0.48957993296513636), + new DoubleArray.Double3(0.6444250121354984, 0.15943210549925668, 0.8980325339512021), + new DoubleArray.Double3(0.911908414894721, 0.19620708116459795, 0.9739970530118442), + new DoubleArray.Double3(0.7171331535373155, 0.799140021435819, 0.8394023334408192), + new DoubleArray.Double3(0.22335018471691526, 0.9420554231648355, 0.12246186078908716), + new DoubleArray.Double3(0.19419588076266092, 0.49561450230804893, 0.2246385849729755), + new DoubleArray.Double3(0.5617093760289167, 0.8331038340241304, 0.7923288879969991), + new DoubleArray.Double3(0.3432972793282141, 0.29010111320593945, 0.03627975682270057), + new DoubleArray.Double3(0.35748533961998263, 0.02439326518533136, 0.8673307199699183), + new DoubleArray.Double3(0.3193707435114782, 0.35132783673623436, 0.053933255898743426), + new DoubleArray.Double3(0.8517647403710334, 0.37811844664834304, 0.15007163897115927), + new DoubleArray.Double3(0.46685475582805935, 0.1848772668967955, 0.04409657152561919), + new DoubleArray.Double3(0.32320901919052847, 0.504482422839805, 0.7496852413599778), + new DoubleArray.Double3(0.9524201495733514, 0.016825757204312786, 0.5903071907083671), + new DoubleArray.Double3(0.7598312811793699, 0.8849589752598893, 0.554360003920576), + new DoubleArray.Double3(0.7900613539341018, 0.7970935571282508, 0.17153119243922021), + new DoubleArray.Double3(0.5554935765731911, 0.094132661439432, 0.3824849247012785), + new DoubleArray.Double3(0.10674983653670711, 0.7693726061171945, 0.3886810178174597), + new DoubleArray.Double3(0.939736646002654, 0.07163933726839522, 0.12011364712774175), + new DoubleArray.Double3(0.16639165291433444, 0.8898446788126325, 0.9368347946836258), + new DoubleArray.Double3(0.1705576929487781, 0.8686186670321622, 0.40148916088520037), + new DoubleArray.Double3(0.36812227735190606, 0.3244933639325567, 0.24774388203650033), + new DoubleArray.Double3(0.01452082760185125, 0.8552732766688889, 0.6080780247926053), + new DoubleArray.Double3(0.2918953055370931, 0.95234080558753, 0.6415722011432319), + new DoubleArray.Double3(0.7979433178837376, 0.06374059884373162, 0.4438828367093458), + new DoubleArray.Double3(0.2441306508090123, 0.9964796024255511, 0.27089074934873036), + new DoubleArray.Double3(0.7870402063811773, 0.9240280670347372, 0.49405135004482426), + new DoubleArray.Double3(0.5075198450218701, 0.20276213618327932, 0.29051322511538413), + new DoubleArray.Double3(0.9034035883992947, 0.8851268383985244, 0.7526770326439741), + new DoubleArray.Double3(0.2049464609579177, 0.10327732160707292, 0.20493566188994883), + new DoubleArray.Double3(0.7229836854924606, 0.2878579773627251, 0.4862593397307977), + new DoubleArray.Double3(0.9152504790045757, 0.9134830611268804, 0.6264426873343731), + new DoubleArray.Double3(0.3701499878336967, 0.8441527503034566, 0.21300719589724681), + new DoubleArray.Double3(0.7300076410888507, 0.20264029975817832, 0.10124773814482091), + new DoubleArray.Double3(0.6911339698755927, 0.38733292806584463, 0.36584714455555056), + new DoubleArray.Double3(0.6017376863162799, 0.19095952985454479, 0.8426028334429069), + new DoubleArray.Double3(0.9702638003828182, 0.3819199462233218, 0.06883280220038246), + new DoubleArray.Double3(0.6255089785292011, 0.016940279115677304, 0.24727787467297946), + new DoubleArray.Double3(0.29433959224203965, 0.4463223184780939, 0.7022159637542817), + new DoubleArray.Double3(0.32265339459531617, 0.5974563130338045, 0.7981768944536132), + new DoubleArray.Double3(0.4410850954958726, 0.7585597239515783, 0.7296765401737483), + new DoubleArray.Double3(0.29650260751831226, 0.3932830004880451, 0.1885137112024966), + new DoubleArray.Double3(0.19204572666559927, 0.3294904551487714, 0.8904390933686249), + new DoubleArray.Double3(0.5293370689663268, 0.9754232445902116, 0.29366831056971154), + new DoubleArray.Double3(0.18315042874874088, 0.36969622491269183, 0.9569227978194618), + new DoubleArray.Double3(0.5451110377317063, 0.353587399444232, 0.5017211528872647), + new DoubleArray.Double3(0.6013654951192994, 0.20950416473509492, 0.07225087798956653), + new DoubleArray.Double3(0.1163633348601929, 0.5277128019185471, 0.16322490467861617), + new DoubleArray.Double3(0.11675812771399707, 0.8052235597644979, 0.31782885903919944), + new DoubleArray.Double3(0.8228005633840904, 0.2160423071543639, 0.7899007541744436), + new DoubleArray.Double3(0.6685553539549692, 0.8615493130847449, 0.8351086234215008), + new DoubleArray.Double3(0.7344830895390428, 0.6179639168075618, 0.6099030873539497), + new DoubleArray.Double3(0.9734975619033418, 0.08762296639050182, 0.11278459985377409), + new DoubleArray.Double3(0.689858738531359, 0.914863740702981, 0.1477080034810595), + new DoubleArray.Double3(0.6263915689215337, 0.38419794389027184, 0.13558664618353178), + new DoubleArray.Double3(0.9479600396004746, 0.8853597468018684, 0.34756218235861114), + new DoubleArray.Double3(0.25708730756314047, 0.6178649823146141, 0.01748930793601), + new DoubleArray.Double3(0.5291642541867672, 0.009532818969462231, 0.09315638250849223), + new DoubleArray.Double3(0.5953274391794848, 0.5958631160136587, 0.6638683230081853), + new DoubleArray.Double3(0.9186456959785692, 0.11659987473335243, 0.719524327823558), + new DoubleArray.Double3(0.2846240628584982, 0.4165131478641707, 0.4365971328291788), + new DoubleArray.Double3(0.32403317283385946, 0.45770280903441773, 0.6772426327565492), + new DoubleArray.Double3(0.5474436473573165, 0.27502363237293215, 0.8553607447736846), + new DoubleArray.Double3(0.6712051878120912, 0.8853106092214856, 0.03933089506524945), + new DoubleArray.Double3(0.11485278033779756, 0.27596516868196264, 0.03901575682135017), + new DoubleArray.Double3(0.5413710674808246, 0.027721112316091423, 0.16770406546657068), + new DoubleArray.Double3(0.3320871175283653, 0.8000966092232236, 0.963463485410795), + new DoubleArray.Double3(0.3347272194772254, 0.2552493452592245, 0.942871393253974), + new DoubleArray.Double3(0.7843256918886565, 0.43683827311841417, 0.13742509989904472), + new DoubleArray.Double3(0.8615550281682689, 0.247220270133241, 0.35643785081394064), + new DoubleArray.Double3(0.7607261917961589, 0.601280683031349, 0.034281777648108136), + new DoubleArray.Double3(0.0655217963044692, 0.8485420103011267, 0.5538565263437983), + new DoubleArray.Double3(0.1053260759624649, 0.3673257644700816, 0.4857160544531799), + new DoubleArray.Double3(0.6654161660820551, 0.0234895020900584, 0.16218253922512593), + new DoubleArray.Double3(0.5659333833872539, 0.16188830656932562, 0.2695812293032316), + new DoubleArray.Double3(0.634229252237772, 0.303825677791389, 0.7460517847937318), + new DoubleArray.Double3(0.33119035694888166, 0.1589216091795509, 0.6979322688694235), + new DoubleArray.Double3(0.8263870530365415, 0.5293462882936077, 0.7908827661257112), + new DoubleArray.Double3(0.5855391349838467, 0.10063057112332796, 0.12853991598779257), + new DoubleArray.Double3(0.9074348339427663, 0.5915505529396913, 0.7358839983288884), + new DoubleArray.Double3(0.2651696408401716, 0.955231137916289, 0.8409303273618777), + new DoubleArray.Double3(0.4623252488306563, 0.6249854569973817, 0.2757277258211993), + new DoubleArray.Double3(0.7778832002629723, 0.12901378304535538, 0.6018607318974056), + new DoubleArray.Double3(0.6698031107115021, 0.8148291834636354, 0.6579781141125466), + new DoubleArray.Double3(0.8782303016332506, 0.7026551843544294, 0.8203566624091975), + new DoubleArray.Double3(0.8767182370147683, 0.9817905318682998, 0.7471750292632029), + new DoubleArray.Double3(0.6477228202159842, 0.40945165331206457, 0.3935654586522942), + new DoubleArray.Double3(0.41731711414685235, 0.004102955637108829, 0.6638765873411655), + new DoubleArray.Double3(0.2092000421646183, 0.352556210160757, 0.9656726792863138), + new DoubleArray.Double3(0.9629211216839693, 0.188350575442788, 0.28034127214873117), + new DoubleArray.Double3(0.3547403186208262, 0.03989311050782696, 0.26629872547533917), + new DoubleArray.Double3(0.9055270845408044, 0.012529057697542867, 0.44063999799849274), + new DoubleArray.Double3(0.24742488026537557, 0.45408458792558115, 0.7248418412727607), + new DoubleArray.Double3(0.8577735624171913, 0.4519332619009482, 0.6329614332265222), + new DoubleArray.Double3(0.1877242320074335, 0.05971017226396558, 0.5452294420972783), + new DoubleArray.Double3(0.3587841296743227, 0.9363735340475503, 0.8712215451552611), + new DoubleArray.Double3(0.6436088388770627, 0.8551640081267599, 0.6029255876948976), + new DoubleArray.Double3(0.6123875205800504, 0.06042239162172225, 0.6216891159873457), + new DoubleArray.Double3(0.21738267008704937, 0.5092863306222466, 0.7336434281991293), + new DoubleArray.Double3(0.33725467353793126, 0.5208296922840966, 0.8524545530387537), + new DoubleArray.Double3(0.45876779695555137, 0.9762050790676927, 0.42121790684395033), + new DoubleArray.Double3(0.09809300924540287, 0.5402366545746456, 0.28783041333309756), + new DoubleArray.Double3(0.7893787462007533, 0.6821910345541384, 0.3700798302064422), + new DoubleArray.Double3(0.31808146763779466, 0.7665625634364529, 0.04918650626755461), + new DoubleArray.Double3(0.7442345807325769, 0.8369363898351897, 0.6346808043768971), + new DoubleArray.Double3(0.2844741527791975, 0.4350034027404276, 0.37567398150948317), + new DoubleArray.Double3(0.08656186746756289, 0.7840206393010661, 0.6523714398217124), + new DoubleArray.Double3(0.07537731840241846, 0.4259146438049157, 0.5945392447495207), + new DoubleArray.Double3(0.5474124358645674, 0.7013129484094527, 0.8253259126398865), + new DoubleArray.Double3(0.41412339456230307, 0.837769751886344, 0.9874005893919796), + new DoubleArray.Double3(0.27550094572861705, 0.7559324552465387, 0.9682069522560547), + new DoubleArray.Double3(0.9408087146529329, 0.27008928246053254, 0.4464697072350513), + new DoubleArray.Double3(0.958479862601588, 0.8587819172423637, 0.587820675122753), + new DoubleArray.Double3(0.4551864949821679, 0.23629053482220452, 0.5881256508323871), + new DoubleArray.Double3(0.7573086119804106, 0.03359306493897096, 0.30484045281544525), + new DoubleArray.Double3(0.9923453581223118, 0.9197621845876803, 0.2797508492459009), + new DoubleArray.Double3(0.05250909245345492, 0.6822558409715442, 0.84097943774331), + new DoubleArray.Double3(0.5379671228940043, 0.8764409304200491, 0.8129485429419503), + new DoubleArray.Double3(0.7663781562786075, 0.4937076125667442, 0.5623323079904746), + new DoubleArray.Double3(0.1533236465296488, 0.6614535259326615, 0.43018853799810497), + new DoubleArray.Double3(0.03946726054977623, 0.6939644679852478, 0.13493360352229689), + new DoubleArray.Double3(0.8758042180746624, 0.7130590431436316, 0.7749525638835225), + new DoubleArray.Double3(0.003037976737300885, 0.7904924115748846, 0.06521227228854087), + new DoubleArray.Double3(0.08729111464242001, 0.020626291609834202, 0.5143342445645108), + new DoubleArray.Double3(0.6113278894214029, 0.9299255741229043, 0.778657227868911), + new DoubleArray.Double3(0.9331313319417067, 0.10053173711362684, 0.7889202628531683), + new DoubleArray.Double3(0.14726076216542827, 0.7033977264093102, 0.05671420686120787), + new DoubleArray.Double3(0.64617000890589, 0.9298593968134953, 0.9231497269297885), + new DoubleArray.Double3(0.3710276074356652, 0.42209646426557257, 0.5861572237223065), + new DoubleArray.Double3(0.33507209717177766, 0.6771946682558865, 0.610098600596516), + new DoubleArray.Double3(0.9804480027931699, 0.005084152428392996, 0.6677395092964358), + new DoubleArray.Double3(0.4700978004918459, 0.11657654505973447, 0.432024886587165), + new DoubleArray.Double3(0.3422610604512416, 0.114337628672599, 0.7320205452735316), + new DoubleArray.Double3(0.2131213239422194, 0.41812561681378013, 0.4406692658059007), + new DoubleArray.Double3(0.44094259158048144, 0.7150997562517375, 0.7794638314166443), + new DoubleArray.Double3(0.14687343574510558, 0.8396950023446307, 0.68224486741136), + new DoubleArray.Double3(0.8921147102404609, 0.9941403326087088, 0.6181432083379866), + new DoubleArray.Double3(0.3504068372277467, 0.5701674848414066, 0.07864891808115804), + new DoubleArray.Double3(0.2563055544474513, 0.4299020821928744, 0.3440098076187752), + new DoubleArray.Double3(0.11725793388717698, 0.3281910676408052, 0.27044861962310673), + new DoubleArray.Double3(0.9502982737060197, 0.6566406306725997, 0.7833488128407673), + new DoubleArray.Double3(0.6277218936190216, 0.1410255816796907, 0.6747658323966523), + new DoubleArray.Double3(0.8315256177883653, 0.7138129608536559, 0.09186612321760912), + new DoubleArray.Double3(0.25944838741370857, 0.2169618508296941, 0.6328154742957175), + new DoubleArray.Double3(0.1792398551107638, 0.9207620056735526, 0.7362373881185166), + new DoubleArray.Double3(0.8779639634573118, 0.7910415698348294, 0.6224281975956593), + new DoubleArray.Double3(0.8272131010839673, 0.2981904954894221, 0.13774136348986632), + new DoubleArray.Double3(0.8018650091576529, 0.9364958645691591, 0.8296625797406464), + new DoubleArray.Double3(0.7385691326814895, 0.48939983068545323, 0.1589876182329456), + new DoubleArray.Double3(0.331810554821246, 0.3717541311630467, 0.1342206410567206), + new DoubleArray.Double3(0.3354623921001122, 0.06754750786267294, 0.7982984032630331), + new DoubleArray.Double3(0.8979604987974303, 0.3927476732078209, 0.7238306745612586), + new DoubleArray.Double3(0.4955204630990332, 0.9863803406196079, 0.21596899780254264), + new DoubleArray.Double3(0.38140819938346926, 0.9696140388171636, 0.0554356433512776), + new DoubleArray.Double3(0.7534503051779023, 0.511540483797011, 0.9557233822864469), + new DoubleArray.Double3(0.8500823081743665, 0.37224461339385617, 0.902680967565523), + new DoubleArray.Double3(0.9595459824656436, 0.012498248269421697, 0.7059026559904263), + new DoubleArray.Double3(0.40154609052796564, 0.146229792999914, 0.43096665193762285), + new DoubleArray.Double3(0.3659700752131103, 0.49069956197000686, 0.5403410284433319), + new DoubleArray.Double3(0.5914877332629478, 0.9248365863008896, 0.01872201109068583), + new DoubleArray.Double3(0.8649358178993333, 0.14125597463487427, 0.5199427620166607), + new DoubleArray.Double3(0.8492311464818055, 0.11827706302206842, 0.276459946416477), + new DoubleArray.Double3(0.2779839421957405, 0.8014643194286696, 0.16349540302397347), + new DoubleArray.Double3(0.6264325335745895, 0.5126324945582623, 0.01782945608743458), + new DoubleArray.Double3(0.18950134527463047, 0.02199928726940603, 0.3104333232939135), + new DoubleArray.Double3(0.8127764208645838, 0.8907133480730149, 0.9079432009512584), + new DoubleArray.Double3(0.6869097435178496, 0.1325359428798033, 0.6086738677237263), + new DoubleArray.Double3(0.18782435294406852, 0.9685500117016853, 0.9867184117231079), + new DoubleArray.Double3(0.48038438502543124, 0.9739634250755016, 0.43622746428941306), + new DoubleArray.Double3(0.6004603364366737, 0.8461970641637039, 0.056218131067642285), + new DoubleArray.Double3(0.5517400394672092, 0.5797516455289795, 0.7843590676100528), + new DoubleArray.Double3(0.9002517843848329, 0.2815377914567123, 0.08277189843334543), + new DoubleArray.Double3(0.6598134485923212, 0.6393144077098452, 0.13033982294120472), + new DoubleArray.Double3(0.8465095043813541, 0.0016180574258066738, 0.8331257314361565), + new DoubleArray.Double3(0.5022248593131786, 0.9306324911626138, 0.8563518788455525), + new DoubleArray.Double3(0.733271093708275, 0.9179124683500314, 0.20952560134830012), + new DoubleArray.Double3(0.004136023708390835, 0.4165905285155257, 0.4343349291006824), + new DoubleArray.Double3(0.9686028664824012, 0.06940425984077558, 0.42636229406563186), + new DoubleArray.Double3(0.33623210993239916, 0.4424509414737645, 0.6103162433544765), + new DoubleArray.Double3(0.49565212852843654, 0.7993852159833876, 0.77005330231469), + new DoubleArray.Double3(0.7287134768788592, 0.5180198312412576, 0.761237422198844), + new DoubleArray.Double3(0.6362908574588949, 0.6067772746618892, 0.1778452502975444), + new DoubleArray.Double3(0.6770325442773074, 0.8191098963586082, 0.6470540684366947), + new DoubleArray.Double3(0.5558558309543931, 0.18823576452277246, 0.9681934636768933), + new DoubleArray.Double3(0.5692729925625384, 0.5850083183524892, 0.9848830674895878), + new DoubleArray.Double3(0.20366778570232857, 0.9003868833861058, 0.44769762521397205), + new DoubleArray.Double3(0.30463888355536395, 0.542320437132706, 0.7835270682339041), + new DoubleArray.Double3(0.8773359873474937, 0.5787244275572943, 0.7340024306332796), + new DoubleArray.Double3(0.9270688520910705, 0.8556659180906798, 0.7085117136295229), + new DoubleArray.Double3(0.822331667861048, 0.8167634118338478, 0.3817408839385208), + new DoubleArray.Double3(0.3364614482786972, 0.6315104324970023, 0.02189916847325213), + new DoubleArray.Double3(0.8293277744198986, 0.2612153869096828, 0.4250149822548398), + new DoubleArray.Double3(0.5407134451777188, 0.9065556364329396, 0.8390368192832165), + new DoubleArray.Double3(0.1821107929361544, 0.650783243465774, 0.38495553341346045), + new DoubleArray.Double3(0.8124257326823348, 0.2945918634193615, 0.6568818795647159), + new DoubleArray.Double3(0.5746064748661988, 0.511617694169669, 0.9988382706245117), + new DoubleArray.Double3(0.26179543637258973, 0.98792293169735, 0.7858085638085969), + new DoubleArray.Double3(0.5272311394540087, 0.024851902825378636, 0.7566896580829734), + new DoubleArray.Double3(0.3383299050281371, 0.7881597800803695, 0.5184221257190608), + new DoubleArray.Double3(0.14526970911662762, 0.3636328136668592, 0.36530093045760204), + new DoubleArray.Double3(0.3444112943349762, 0.9374821073348284, 0.5138205009082668), + new DoubleArray.Double3(0.7645451533980683, 0.1307659994512228, 0.5654658551648482), + new DoubleArray.Double3(0.2586030823771436, 0.1308911157317758, 0.9922804391198922), + new DoubleArray.Double3(0.5959171378934188, 0.6449304540103431, 0.10782366396746224), + new DoubleArray.Double3(0.7104932974327056, 0.5924069407425991, 0.3111925438765011), + new DoubleArray.Double3(0.11813664923130518, 0.06422384175816098, 0.43205697366507323), + new DoubleArray.Double3(0.36575820390904856, 0.10785729774392694, 0.32530475943691906), + new DoubleArray.Double3(0.870671432332111, 0.40075767577954113, 0.7138736494597661), + new DoubleArray.Double3(0.9233259100355768, 0.522493033163327, 0.47939574202522284), + new DoubleArray.Double3(0.8910623276009204, 0.6787117657143095, 0.44907173876636963), + new DoubleArray.Double3(0.4087158753521226, 0.2661591506529354, 0.8052128400265315), + new DoubleArray.Double3(0.23630521733983756, 0.9896182717339765, 0.8733101241736266), + new DoubleArray.Double3(0.09089904173048868, 0.779529761019813, 0.2220675054886886), + new DoubleArray.Double3(0.4701859869629079, 0.2810013353080967, 0.9936116868191468), + new DoubleArray.Double3(0.6358086769379001, 0.3337387363739548, 0.7064549000649786) + }; + + private CellularNoise() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/ConstantNoise.java b/src/com/hypixel/hytale/procedurallib/logic/ConstantNoise.java new file mode 100644 index 0000000..804d9f8 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/ConstantNoise.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.procedurallib.logic; + +import com.hypixel.hytale.procedurallib.NoiseFunction; +import javax.annotation.Nonnull; + +public class ConstantNoise implements NoiseFunction { + protected final double value; + + public ConstantNoise(double value) { + this.value = value; + } + + public double getValue() { + return this.value; + } + + @Override + public double get(int seed, int offsetSeed, double x, double y) { + return this.value; + } + + @Override + public double get(int seed, int offsetSeed, double x, double y, double z) { + return this.value; + } + + @Nonnull + @Override + public String toString() { + return "ConstantNoise{value=" + this.value + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/DistanceNoise.java b/src/com/hypixel/hytale/procedurallib/logic/DistanceNoise.java new file mode 100644 index 0000000..2fef8b2 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/DistanceNoise.java @@ -0,0 +1,164 @@ +package com.hypixel.hytale.procedurallib.logic; + +import com.hypixel.hytale.procedurallib.NoiseFunction; +import com.hypixel.hytale.procedurallib.logic.cell.CellDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.PointEvaluator; +import javax.annotation.Nonnull; + +public abstract class DistanceNoise implements NoiseFunction { + protected final CellDistanceFunction cellDistanceFunction; + protected final PointEvaluator pointEvaluator; + protected final DistanceNoise.Distance2Function distance2Function; + + public DistanceNoise(CellDistanceFunction cellDistanceFunction, PointEvaluator pointEvaluator, DistanceNoise.Distance2Function distance2Function) { + this.cellDistanceFunction = cellDistanceFunction; + this.pointEvaluator = pointEvaluator; + this.distance2Function = distance2Function; + } + + public CellDistanceFunction getCellDistanceFunction() { + return this.cellDistanceFunction; + } + + public DistanceNoise.Distance2Function getDistance2Function() { + return this.distance2Function; + } + + @Override + public double get(int seed, int offsetSeed, double x, double y) { + x = this.cellDistanceFunction.scale(x); + y = this.cellDistanceFunction.scale(y); + int xr = this.cellDistanceFunction.getCellX(x, y); + int yr = this.cellDistanceFunction.getCellY(x, y); + ResultBuffer.ResultBuffer2d buffer = this.localBuffer2d(); + buffer.distance = Double.POSITIVE_INFINITY; + buffer.distance2 = Double.POSITIVE_INFINITY; + this.cellDistanceFunction.transition2D(offsetSeed, x, y, xr, yr, buffer, this.pointEvaluator); + buffer.distance = Math.sqrt(buffer.distance); + buffer.distance2 = Math.sqrt(buffer.distance2); + return GeneralNoise.limit(this.distance2Function.eval(buffer.distance, buffer.distance2)) * 2.0 - 1.0; + } + + @Override + public double get(int seed, int offsetSeed, double x, double y, double z) { + x = this.cellDistanceFunction.scale(x); + y = this.cellDistanceFunction.scale(y); + z = this.cellDistanceFunction.scale(z); + int xr = this.cellDistanceFunction.getCellX(x, y, z); + int yr = this.cellDistanceFunction.getCellY(x, y, z); + int zr = this.cellDistanceFunction.getCellZ(x, y, z); + ResultBuffer.ResultBuffer3d buffer = this.localBuffer3d(); + buffer.distance = Double.POSITIVE_INFINITY; + buffer.distance2 = Double.POSITIVE_INFINITY; + this.cellDistanceFunction.transition3D(offsetSeed, x, y, z, xr, yr, zr, buffer, this.pointEvaluator); + buffer.distance = Math.sqrt(buffer.distance); + buffer.distance2 = Math.sqrt(buffer.distance2); + return GeneralNoise.limit(this.distance2Function.eval(buffer.distance, buffer.distance2)) * 2.0 - 1.0; + } + + protected abstract ResultBuffer.ResultBuffer2d localBuffer2d(); + + protected abstract ResultBuffer.ResultBuffer3d localBuffer3d(); + + @Nonnull + @Override + public String toString() { + return "DistanceNoise{cellDistanceFunction=" + + this.cellDistanceFunction + + ", pointEvaluator=" + + this.pointEvaluator + + ", distance2Function=" + + this.distance2Function + + "}"; + } + + @FunctionalInterface + public interface Distance2Function { + double eval(double var1, double var3); + } + + public static enum Distance2Mode { + ADD(new DistanceNoise.Distance2Function() { + @Override + public double eval(double distance, double distance2) { + return distance + distance2; + } + + @Nonnull + @Override + public String toString() { + return "AddDistance2Function{}"; + } + }), + SUB(new DistanceNoise.Distance2Function() { + @Override + public double eval(double distance, double distance2) { + return distance2 - distance; + } + + @Nonnull + @Override + public String toString() { + return "SubDistance2Function{}"; + } + }), + MUL(new DistanceNoise.Distance2Function() { + @Override + public double eval(double distance, double distance2) { + return distance * distance2; + } + + @Nonnull + @Override + public String toString() { + return "MulDistance2Function{}"; + } + }), + DIV(new DistanceNoise.Distance2Function() { + @Override + public double eval(double distance, double distance2) { + return distance / distance2; + } + + @Nonnull + @Override + public String toString() { + return "DivDistance2Function{}"; + } + }), + MIN(new DistanceNoise.Distance2Function() { + @Override + public double eval(double distance, double distance2) { + return distance; + } + + @Nonnull + @Override + public String toString() { + return "MinDistance2Function{}"; + } + }), + MAX(new DistanceNoise.Distance2Function() { + @Override + public double eval(double distance, double distance2) { + return distance2; + } + + @Nonnull + @Override + public String toString() { + return "MaxDistance2Function{}"; + } + }); + + private final DistanceNoise.Distance2Function function; + + private Distance2Mode(DistanceNoise.Distance2Function function) { + this.function = function; + } + + public DistanceNoise.Distance2Function getFunction() { + return this.function; + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/DoubleArray.java b/src/com/hypixel/hytale/procedurallib/logic/DoubleArray.java new file mode 100644 index 0000000..0fb8eb8 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/DoubleArray.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.procedurallib.logic; + +import javax.annotation.Nonnull; + +public class DoubleArray { + public DoubleArray() { + } + + public static class Double2 { + public final double x; + public final double y; + + public Double2(double x, double y) { + this.x = x; + this.y = y; + } + + @Nonnull + @Override + public String toString() { + return "DoubleArray.Double2{x=" + this.x + ", y=" + this.y + "}"; + } + } + + public static class Double3 { + public final double x; + public final double y; + public final double z; + + public Double3(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Nonnull + @Override + public String toString() { + return "DoubleArray.Double3{x=" + this.x + ", y=" + this.y + ", z=" + this.z + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/GeneralNoise.java b/src/com/hypixel/hytale/procedurallib/logic/GeneralNoise.java new file mode 100644 index 0000000..cd8652c --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/GeneralNoise.java @@ -0,0 +1,142 @@ +package com.hypixel.hytale.procedurallib.logic; + +import javax.annotation.Nonnull; + +public final class GeneralNoise { + public static final int X_PRIME = 1619; + public static final int Y_PRIME = 31337; + public static final int Z_PRIME = 6971; + private static final DoubleArray.Double2[] GRAD_2D = new DoubleArray.Double2[]{ + new DoubleArray.Double2(-1.0, -1.0), + new DoubleArray.Double2(1.0, -1.0), + new DoubleArray.Double2(-1.0, 1.0), + new DoubleArray.Double2(1.0, 1.0), + new DoubleArray.Double2(0.0, -1.0), + new DoubleArray.Double2(-1.0, 0.0), + new DoubleArray.Double2(0.0, 1.0), + new DoubleArray.Double2(1.0, 0.0) + }; + private static final DoubleArray.Double3[] GRAD_3D = new DoubleArray.Double3[]{ + new DoubleArray.Double3(1.0, 1.0, 0.0), + new DoubleArray.Double3(-1.0, 1.0, 0.0), + new DoubleArray.Double3(1.0, -1.0, 0.0), + new DoubleArray.Double3(-1.0, -1.0, 0.0), + new DoubleArray.Double3(1.0, 0.0, 1.0), + new DoubleArray.Double3(-1.0, 0.0, 1.0), + new DoubleArray.Double3(1.0, 0.0, -1.0), + new DoubleArray.Double3(-1.0, 0.0, -1.0), + new DoubleArray.Double3(0.0, 1.0, 1.0), + new DoubleArray.Double3(0.0, -1.0, 1.0), + new DoubleArray.Double3(0.0, 1.0, -1.0), + new DoubleArray.Double3(0.0, -1.0, -1.0), + new DoubleArray.Double3(1.0, 1.0, 0.0), + new DoubleArray.Double3(0.0, -1.0, 1.0), + new DoubleArray.Double3(-1.0, 1.0, 0.0), + new DoubleArray.Double3(0.0, -1.0, -1.0) + }; + + private GeneralNoise() { + throw new UnsupportedOperationException(); + } + + public static int fastFloor(double f) { + return f >= 0.0 ? (int)f : (int)f - 1; + } + + public static int fastCeil(double f) { + return f >= 0.0 ? (int)f + 1 : (int)f; + } + + public static double lerp(double a, double b, double t) { + return a + t * (b - a); + } + + public static int hash2D(int seed, int x, int y) { + int hash = seed ^ 1619 * x; + hash ^= 31337 * y; + hash = hash * hash * hash * 60493; + return hash >> 13 ^ hash; + } + + public static int hash3D(int seed, int x, int y, int z) { + int hash = seed ^ 1619 * x; + hash ^= 31337 * y; + hash ^= 6971 * z; + hash = hash * hash * hash * 60493; + return hash >> 13 ^ hash; + } + + public static double gradCoord2D(int seed, int x, int y, double xd, double yd) { + int hash = hash2D(seed, x, y); + DoubleArray.Double2 g = GRAD_2D[hash & 7]; + return xd * g.x + yd * g.y; + } + + public static double gradCoord3D(int seed, int x, int y, int z, double xd, double yd, double zd) { + int hash = hash3D(seed, x, y, z); + DoubleArray.Double3 g = GRAD_3D[hash & 15]; + return xd * g.x + yd * g.y + zd * g.z; + } + + public static double limit(double val) { + if (val < 0.0) { + return 0.0; + } else { + return val > 1.0 ? 1.0 : val; + } + } + + @FunctionalInterface + public interface InterpolationFunction { + double interpolate(double var1); + } + + public static enum InterpolationMode { + LINEAR(new GeneralNoise.InterpolationFunction() { + @Override + public double interpolate(double t) { + return t; + } + + @Nonnull + @Override + public String toString() { + return "LinearInterpolationFunction{}"; + } + }), + HERMITE(new GeneralNoise.InterpolationFunction() { + @Override + public double interpolate(double t) { + return t * t * (3.0 - 2.0 * t); + } + + @Nonnull + @Override + public String toString() { + return "HermiteInterpolationFunction{}"; + } + }), + QUINTIC(new GeneralNoise.InterpolationFunction() { + @Override + public double interpolate(double t) { + return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); + } + + @Nonnull + @Override + public String toString() { + return "QuinticInterpolationFunction{}"; + } + }); + + public final GeneralNoise.InterpolationFunction function; + + private InterpolationMode(GeneralNoise.InterpolationFunction function) { + this.function = function; + } + + public GeneralNoise.InterpolationFunction getFunction() { + return this.function; + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/GridNoise.java b/src/com/hypixel/hytale/procedurallib/logic/GridNoise.java new file mode 100644 index 0000000..9360277 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/GridNoise.java @@ -0,0 +1,94 @@ +package com.hypixel.hytale.procedurallib.logic; + +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.procedurallib.NoiseFunction; +import javax.annotation.Nonnull; + +public class GridNoise implements NoiseFunction { + protected final double thicknessX; + protected final double thicknessY; + protected final double thicknessZ; + protected final double thicknessX_m1; + protected final double thicknessY_m1; + protected final double thicknessZ_m1; + + public GridNoise(double thicknessX, double thicknessY, double thicknessZ) { + this.thicknessX = thicknessX; + this.thicknessY = thicknessY; + this.thicknessZ = thicknessZ; + this.thicknessX_m1 = 1.0 - thicknessX; + this.thicknessY_m1 = 1.0 - thicknessY; + this.thicknessZ_m1 = 1.0 - thicknessZ; + } + + @Override + public double get(int seed, int offsetSeed, double x, double y) { + x -= MathUtil.floor(x); + y -= MathUtil.floor(y); + double d = 1.0; + if (x < this.thicknessX) { + d = x / this.thicknessX; + } else if (x > this.thicknessX_m1) { + d = (1.0 - x) / this.thicknessX; + } + + if (y < this.thicknessY) { + double t = y / this.thicknessY; + if (t < d) { + d = t; + } + } else if (y > this.thicknessY_m1) { + double t = (1.0 - y) / this.thicknessY; + if (t < d) { + d = t; + } + } + + return d * 2.0 - 1.0; + } + + @Override + public double get(int seed, int offsetSeed, double x, double y, double z) { + x -= MathUtil.floor(x); + y -= MathUtil.floor(y); + z -= MathUtil.floor(z); + double d = 1.0; + if (x < this.thicknessX) { + d = x / this.thicknessX; + } else if (x > this.thicknessX_m1) { + d = (1.0 - x) / this.thicknessX; + } + + if (y < this.thicknessY) { + double t = y / this.thicknessY; + if (t < d) { + d = t; + } + } else if (y > this.thicknessY_m1) { + double t = (1.0 - y) / this.thicknessY; + if (t < d) { + d = t; + } + } + + if (z < this.thicknessZ) { + double t = z / this.thicknessZ; + if (t < d) { + d = t; + } + } else if (z > this.thicknessZ_m1) { + double t = (1.0 - z) / this.thicknessZ; + if (t < d) { + d = t; + } + } + + return d * 2.0 - 1.0; + } + + @Nonnull + @Override + public String toString() { + return "GridNoise{thicknessX=" + this.thicknessX + ", thicknessY=" + this.thicknessY + ", thicknessZ=" + this.thicknessZ + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/HexMeshNoise.java b/src/com/hypixel/hytale/procedurallib/logic/HexMeshNoise.java new file mode 100644 index 0000000..8bea0eb --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/HexMeshNoise.java @@ -0,0 +1,119 @@ +package com.hypixel.hytale.procedurallib.logic; + +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.procedurallib.NoiseFunction; +import com.hypixel.hytale.procedurallib.condition.IIntCondition; +import com.hypixel.hytale.procedurallib.logic.cell.HexCellDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.jitter.CellJitter; + +public class HexMeshNoise implements NoiseFunction { + protected final IIntCondition density; + protected final double thickness; + protected final double thicknessSquared; + protected final CellJitter jitter; + protected final boolean linesX; + protected final boolean linesY; + protected final boolean linesZ; + + public HexMeshNoise(IIntCondition density, double thickness, CellJitter jitter, boolean linesX, boolean linesY, boolean linesZ) { + double domainLocalThickness = HexCellDistanceFunction.DISTANCE_FUNCTION.scale(thickness); + this.density = density; + this.thickness = domainLocalThickness; + this.thicknessSquared = domainLocalThickness * domainLocalThickness; + this.jitter = jitter; + this.linesX = linesX; + this.linesY = linesY; + this.linesZ = linesZ; + } + + @Override + public double get(int seed, int offsetSeed, double x, double y) { + x = HexCellDistanceFunction.DISTANCE_FUNCTION.scale(x); + y = HexCellDistanceFunction.DISTANCE_FUNCTION.scale(y); + int cx = HexCellDistanceFunction.toGridX(x, y); + int cy = HexCellDistanceFunction.toGridY(x, y); + double nearest = this.thicknessSquared; + nearest = this.checkConnections(offsetSeed, x, y, cx - 1, cy - 1, nearest); + nearest = this.checkConnections(offsetSeed, x, y, cx - 1, cy + 0, nearest); + nearest = this.checkConnections(offsetSeed, x, y, cx + 1, cy + 0, nearest); + nearest = this.checkConnections(offsetSeed, x, y, cx + 0, cy - 1, nearest); + nearest = this.checkConnections(offsetSeed, x, y, cx + 0, cy + 1, nearest); + if (this.linesZ) { + nearest = this.checkDiagonalConnections(offsetSeed, x, y, cx + 0, cy + 0, nearest); + nearest = this.checkDiagonalConnections(offsetSeed, x, y, cx + 0, cy - 1, nearest); + nearest = this.checkDiagonalConnections(offsetSeed, x, y, cx + 0, cy + 1, nearest); + nearest = this.checkDiagonalConnections(offsetSeed, x, y, cx - 1, cy + 0, nearest); + nearest = this.checkDiagonalConnections(offsetSeed, x, y, cx - 1, cy - 1, nearest); + } + + if (nearest < this.thicknessSquared) { + double distance = Math.sqrt(nearest); + double d = distance / this.thickness; + return d * 2.0 - 1.0; + } else { + return 1.0; + } + } + + @Override + public double get(int seed, int offsetSeed, double x, double y, double z) { + throw new UnsupportedOperationException("3d not supported"); + } + + protected double checkConnections(int offsetSeed, double x, double y, int cx, int cy, double nearest) { + int hash = HexCellDistanceFunction.getHash(offsetSeed, cx, cy); + if (!this.density.eval(hash)) { + return nearest; + } else { + DoubleArray.Double2 vec = HexCellDistanceFunction.HEX_CELL_2D[hash & 0xFF]; + double px = this.jitter.getPointX(cx, vec); + double py = this.jitter.getPointY(cy, vec); + double ax = HexCellDistanceFunction.toHexX(px, py); + double ay = HexCellDistanceFunction.toHexY(px, py); + double adx = x - ax; + double ady = y - ay; + if (this.linesX) { + nearest = Math.min(nearest, this.dist2Cell(offsetSeed, x, y, adx, ady, ax, ay, cx - 1, cy)); + nearest = Math.min(nearest, this.dist2Cell(offsetSeed, x, y, adx, ady, ax, ay, cx + 1, cy)); + } + + if (this.linesY) { + nearest = Math.min(nearest, this.dist2Cell(offsetSeed, x, y, adx, ady, ax, ay, cx, cy - 1)); + nearest = Math.min(nearest, this.dist2Cell(offsetSeed, x, y, adx, ady, ax, ay, cx, cy + 1)); + } + + return nearest; + } + } + + protected double checkDiagonalConnections(int offsetSeed, double x, double y, int cx, int cy, double nearest) { + int hash = HexCellDistanceFunction.getHash(offsetSeed, cx, cy); + if (!this.density.eval(hash)) { + return nearest; + } else { + DoubleArray.Double2 vec = HexCellDistanceFunction.HEX_CELL_2D[hash & 0xFF]; + double px = this.jitter.getPointX(cx, vec); + double py = this.jitter.getPointY(cy, vec); + double ax = HexCellDistanceFunction.toHexX(px, py); + double ay = HexCellDistanceFunction.toHexY(px, py); + double adx = x - ax; + double ady = y - ay; + nearest = Math.min(nearest, this.dist2Cell(offsetSeed, x, y, adx, ady, ax, ay, cx - 1, cy + 1)); + return Math.min(nearest, this.dist2Cell(offsetSeed, x, y, adx, ady, ax, ay, cx + 1, cy - 1)); + } + } + + protected double dist2Cell(int offsetSeed, double x, double y, double adx, double ady, double ax, double ay, int cx, int cy) { + int hash = HexCellDistanceFunction.getHash(offsetSeed, cx, cy); + if (!this.density.eval(hash)) { + return Double.MAX_VALUE; + } else { + DoubleArray.Double2 vec = HexCellDistanceFunction.HEX_CELL_2D[hash & 0xFF]; + double px = this.jitter.getPointX(cx, vec); + double py = this.jitter.getPointY(cy, vec); + double bx = HexCellDistanceFunction.toHexX(px, py); + double by = HexCellDistanceFunction.toHexY(px, py); + return MathUtil.distanceToLineSq(x, y, ax, ay, bx, by, adx, ady, bx - ax, by - ay); + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/MeshNoise.java b/src/com/hypixel/hytale/procedurallib/logic/MeshNoise.java new file mode 100644 index 0000000..f060ce6 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/MeshNoise.java @@ -0,0 +1,130 @@ +package com.hypixel.hytale.procedurallib.logic; + +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.procedurallib.NoiseFunction; +import com.hypixel.hytale.procedurallib.condition.IIntCondition; +import com.hypixel.hytale.procedurallib.logic.cell.GridCellDistanceFunction; + +public class MeshNoise implements NoiseFunction { + public static final Vector2i[] ADJACENT_CELLS = new Vector2i[]{new Vector2i(-1, 0), new Vector2i(0, -1), new Vector2i(1, 0), new Vector2i(0, 1)}; + private final IIntCondition density; + private final double thickness; + private final double thicknessSquared; + private final double jitterX; + private final double jitterY; + + public MeshNoise(IIntCondition density, double thickness, double jitterX, double jitterY) { + this.density = density; + this.thickness = thickness; + this.thicknessSquared = thickness * thickness; + this.jitterX = jitterX; + this.jitterY = jitterY; + } + + @Override + public double get(int seed, int offsetSeed, double x, double y) { + double thickness = this.thickness; + double lowest = this.thicknessSquared; + int _x = GeneralNoise.fastFloor(x); + int _y = GeneralNoise.fastFloor(y); + double rx = x - _x; + double ry = y - _y; + + for (int c = 0; c < 8; c++) { + switch (c) { + case 0: + if (rx >= thickness || ry >= thickness) { + continue; + } + break; + case 1: + if (rx >= thickness) { + continue; + } + break; + case 2: + if (rx >= thickness || ry < 1.0 - thickness) { + continue; + } + break; + case 3: + if (ry >= thickness) { + continue; + } + case 4: + default: + break; + case 5: + if (ry < 1.0 - thickness) { + continue; + } + break; + case 6: + if (rx < 1.0 - thickness || ry >= thickness) { + continue; + } + break; + case 7: + if (rx < 1.0 - thickness) { + continue; + } + } + + int cx = c / 3; + int cy = c % 3; + int xr = _x + cx - 1; + int yr = _y + cy - 1; + int cellHash = GridCellDistanceFunction.getHash(offsetSeed, xr, yr); + if (this.density.eval(cellHash)) { + DoubleArray.Double2 cell = CellularNoise.CELL_2D[cellHash & 0xFF]; + double cellX = cell.x * this.jitterX; + double cellY = cell.y * this.jitterY; + double centerX = xr + cellX; + double centerY = yr + cellY; + double qX = x - centerX; + double qY = y - centerY; + + for (Vector2i v : ADJACENT_CELLS) { + int xi = xr + v.x; + int yi = yr + v.y; + int vecHash = GridCellDistanceFunction.getHash(offsetSeed, xi, yi); + if (this.density.eval(vecHash)) { + DoubleArray.Double2 vec = CellularNoise.CELL_2D[vecHash & 0xFF]; + double vecX = vec.x * this.jitterX; + double vecY = vec.y * this.jitterY; + double vx = v.x + vecX - cellX; + double vy = v.y + vecY - cellY; + double t = (qX * vx + qY * vy) / (vx * vx + vy * vy); + if (t < 0.0) { + t = 0.0; + } else if (t > 1.0) { + t = 1.0; + } + + double lx = centerX + vx * t; + double ly = centerY + vy * t; + double dx = x - lx; + double dy = y - ly; + double distance = dx * dx + dy * dy; + if (distance < lowest) { + lowest = distance; + } + } + } + } + } + + if (lowest != this.thicknessSquared) { + double distance = Math.sqrt(lowest); + double d = distance / thickness; + return d * 2.0 - 1.0; + } else { + return 1.0; + } + } + + @Override + public double get(int seed, int offsetSeed, double x, double y, double z) { + throw new UnsupportedOperationException("3d not supported"); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/OldSimplexNoise.java b/src/com/hypixel/hytale/procedurallib/logic/OldSimplexNoise.java new file mode 100644 index 0000000..1991e6f --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/OldSimplexNoise.java @@ -0,0 +1,681 @@ +package com.hypixel.hytale.procedurallib.logic; + +import com.hypixel.hytale.procedurallib.NoiseFunction; +import javax.annotation.Nonnull; + +public class OldSimplexNoise implements NoiseFunction { + public static final OldSimplexNoise INSTANCE = new OldSimplexNoise(); + private static final double STRETCH_CONSTANT_2D = -0.211324865405187; + private static final double SQUISH_CONSTANT_2D = 0.366025403784439; + private static final double STRETCH_CONSTANT_3D = -0.16666666666666666; + private static final double SQUISH_CONSTANT_3D = 0.3333333333333333; + private static final double NORM_CONSTANT_2D = 47.0; + private static final double NORM_CONSTANT_3D = 103.0; + @Nonnull + private static DoubleArray.Double2[] gradients2D = new DoubleArray.Double2[]{ + new DoubleArray.Double2(5.0, 2.0), + new DoubleArray.Double2(2.0, 5.0), + new DoubleArray.Double2(-5.0, 2.0), + new DoubleArray.Double2(-2.0, 5.0), + new DoubleArray.Double2(5.0, -2.0), + new DoubleArray.Double2(2.0, -5.0), + new DoubleArray.Double2(-5.0, -2.0), + new DoubleArray.Double2(-2.0, -5.0) + }; + @Nonnull + private static DoubleArray.Double3[] gradients3D = new DoubleArray.Double3[]{ + new DoubleArray.Double3(-11.0, 4.0, 4.0), + new DoubleArray.Double3(-4.0, 11.0, 4.0), + new DoubleArray.Double3(-4.0, 4.0, 11.0), + new DoubleArray.Double3(11.0, 4.0, 4.0), + new DoubleArray.Double3(4.0, 11.0, 4.0), + new DoubleArray.Double3(4.0, 4.0, 11.0), + new DoubleArray.Double3(-11.0, -4.0, 4.0), + new DoubleArray.Double3(-4.0, -11.0, 4.0), + new DoubleArray.Double3(-4.0, -4.0, 11.0), + new DoubleArray.Double3(11.0, -4.0, 4.0), + new DoubleArray.Double3(4.0, -11.0, 4.0), + new DoubleArray.Double3(4.0, -4.0, 11.0), + new DoubleArray.Double3(-11.0, 4.0, -4.0), + new DoubleArray.Double3(-4.0, 11.0, -4.0), + new DoubleArray.Double3(-4.0, 4.0, -11.0), + new DoubleArray.Double3(11.0, 4.0, -4.0), + new DoubleArray.Double3(4.0, 11.0, -4.0), + new DoubleArray.Double3(4.0, 4.0, -11.0), + new DoubleArray.Double3(-11.0, -4.0, -4.0), + new DoubleArray.Double3(-4.0, -11.0, -4.0), + new DoubleArray.Double3(-4.0, -4.0, -11.0), + new DoubleArray.Double3(11.0, -4.0, -4.0), + new DoubleArray.Double3(4.0, -11.0, -4.0), + new DoubleArray.Double3(4.0, -4.0, -11.0) + }; + + private OldSimplexNoise() { + } + + @Override + public double get(int seed, int offsetSeed, double x, double y) { + double stretchOffset = (x + y) * -0.211324865405187; + double xs = x + stretchOffset; + double ys = y + stretchOffset; + int xsb = fastFloor(xs); + int ysb = fastFloor(ys); + double squishOffset = (xsb + ysb) * 0.366025403784439; + double xb = xsb + squishOffset; + double yb = ysb + squishOffset; + double xins = xs - xsb; + double yins = ys - ysb; + double inSum = xins + yins; + double dx0 = x - xb; + double dy0 = y - yb; + double value = 0.0; + double dx1 = dx0 - 1.0 - 0.366025403784439; + double dy1 = dy0 - 0.0 - 0.366025403784439; + double attn1 = 2.0 - dx1 * dx1 - dy1 * dy1; + if (attn1 > 0.0) { + attn1 *= attn1; + value += attn1 * attn1 * extrapolate(offsetSeed, xsb + 1, ysb + 0, dx1, dy1); + } + + double dx2 = dx0 - 0.0 - 0.366025403784439; + double dy2 = dy0 - 1.0 - 0.366025403784439; + double attn2 = 2.0 - dx2 * dx2 - dy2 * dy2; + if (attn2 > 0.0) { + attn2 *= attn2; + value += attn2 * attn2 * extrapolate(offsetSeed, xsb + 0, ysb + 1, dx2, dy2); + } + + double dx_ext; + double dy_ext; + int xsv_ext; + int ysv_ext; + if (inSum <= 1.0) { + double zins = 1.0 - inSum; + if (!(zins > xins) && !(zins > yins)) { + xsv_ext = xsb + 1; + ysv_ext = ysb + 1; + dx_ext = dx0 - 1.0 - 0.732050807568878; + dy_ext = dy0 - 1.0 - 0.732050807568878; + } else if (xins > yins) { + xsv_ext = xsb + 1; + ysv_ext = ysb - 1; + dx_ext = dx0 - 1.0; + dy_ext = dy0 + 1.0; + } else { + xsv_ext = xsb - 1; + ysv_ext = ysb + 1; + dx_ext = dx0 + 1.0; + dy_ext = dy0 - 1.0; + } + } else { + double zins = 2.0 - inSum; + if (!(zins < xins) && !(zins < yins)) { + dx_ext = dx0; + dy_ext = dy0; + xsv_ext = xsb; + ysv_ext = ysb; + } else if (xins > yins) { + xsv_ext = xsb + 2; + ysv_ext = ysb + 0; + dx_ext = dx0 - 2.0 - 0.732050807568878; + dy_ext = dy0 + 0.0 - 0.732050807568878; + } else { + xsv_ext = xsb + 0; + ysv_ext = ysb + 2; + dx_ext = dx0 + 0.0 - 0.732050807568878; + dy_ext = dy0 - 2.0 - 0.732050807568878; + } + + xsb++; + ysb++; + dx0 = dx0 - 1.0 - 0.732050807568878; + dy0 = dy0 - 1.0 - 0.732050807568878; + } + + double attn0 = 2.0 - dx0 * dx0 - dy0 * dy0; + if (attn0 > 0.0) { + attn0 *= attn0; + value += attn0 * attn0 * extrapolate(offsetSeed, xsb, ysb, dx0, dy0); + } + + double attn_ext = 2.0 - dx_ext * dx_ext - dy_ext * dy_ext; + if (attn_ext > 0.0) { + attn_ext *= attn_ext; + value += attn_ext * attn_ext * extrapolate(offsetSeed, xsv_ext, ysv_ext, dx_ext, dy_ext); + } + + return value / 47.0; + } + + @Override + public double get(int seed, int offsetSeed, double x, double y, double z) { + double stretchOffset = (x + y + z) * -0.16666666666666666; + double xs = x + stretchOffset; + double ys = y + stretchOffset; + double zs = z + stretchOffset; + int xsb = fastFloor(xs); + int ysb = fastFloor(ys); + int zsb = fastFloor(zs); + double squishOffset = (xsb + ysb + zsb) * 0.3333333333333333; + double xb = xsb + squishOffset; + double yb = ysb + squishOffset; + double zb = zsb + squishOffset; + double xins = xs - xsb; + double yins = ys - ysb; + double zins = zs - zsb; + double inSum = xins + yins + zins; + double dx0 = x - xb; + double dy0 = y - yb; + double dz0 = z - zb; + double value = 0.0; + double dx_ext0; + double dy_ext0; + double dz_ext0; + double dx_ext1; + double dy_ext1; + double dz_ext1; + int xsv_ext0; + int ysv_ext0; + int zsv_ext0; + int xsv_ext1; + int ysv_ext1; + int zsv_ext1; + if (inSum <= 1.0) { + byte aPoint = 1; + double aScore = xins; + byte bPoint = 2; + double bScore = yins; + if (xins >= yins && zins > yins) { + bScore = zins; + bPoint = 4; + } else if (xins < yins && zins > xins) { + aScore = zins; + aPoint = 4; + } + + double wins = 1.0 - inSum; + if (!(wins > aScore) && !(wins > bScore)) { + byte c = (byte)(aPoint | bPoint); + if ((c & 1) == 0) { + xsv_ext0 = xsb; + xsv_ext1 = xsb - 1; + dx_ext0 = dx0 - 0.6666666666666666; + dx_ext1 = dx0 + 1.0 - 0.3333333333333333; + } else { + xsv_ext0 = xsv_ext1 = xsb + 1; + dx_ext0 = dx0 - 1.0 - 0.6666666666666666; + dx_ext1 = dx0 - 1.0 - 0.3333333333333333; + } + + if ((c & 2) == 0) { + ysv_ext0 = ysb; + ysv_ext1 = ysb - 1; + dy_ext0 = dy0 - 0.6666666666666666; + dy_ext1 = dy0 + 1.0 - 0.3333333333333333; + } else { + ysv_ext0 = ysv_ext1 = ysb + 1; + dy_ext0 = dy0 - 1.0 - 0.6666666666666666; + dy_ext1 = dy0 - 1.0 - 0.3333333333333333; + } + + if ((c & 4) == 0) { + zsv_ext0 = zsb; + zsv_ext1 = zsb - 1; + dz_ext0 = dz0 - 0.6666666666666666; + dz_ext1 = dz0 + 1.0 - 0.3333333333333333; + } else { + zsv_ext0 = zsv_ext1 = zsb + 1; + dz_ext0 = dz0 - 1.0 - 0.6666666666666666; + dz_ext1 = dz0 - 1.0 - 0.3333333333333333; + } + } else { + byte cx = bScore > aScore ? bPoint : aPoint; + if ((cx & 1) == 0) { + xsv_ext0 = xsb - 1; + xsv_ext1 = xsb; + dx_ext0 = dx0 + 1.0; + dx_ext1 = dx0; + } else { + xsv_ext0 = xsv_ext1 = xsb + 1; + dx_ext0 = dx_ext1 = dx0 - 1.0; + } + + if ((cx & 2) == 0) { + ysv_ext1 = ysb; + ysv_ext0 = ysb; + dy_ext1 = dy0; + dy_ext0 = dy0; + if ((cx & 1) == 0) { + ysv_ext1 = ysb - 1; + dy_ext1 = dy0 + 1.0; + } else { + ysv_ext0 = ysb - 1; + dy_ext0 = dy0 + 1.0; + } + } else { + ysv_ext0 = ysv_ext1 = ysb + 1; + dy_ext0 = dy_ext1 = dy0 - 1.0; + } + + if ((cx & 4) == 0) { + zsv_ext0 = zsb; + zsv_ext1 = zsb - 1; + dz_ext0 = dz0; + dz_ext1 = dz0 + 1.0; + } else { + zsv_ext0 = zsv_ext1 = zsb + 1; + dz_ext0 = dz_ext1 = dz0 - 1.0; + } + } + + double attn0 = 2.0 - dx0 * dx0 - dy0 * dy0 - dz0 * dz0; + if (attn0 > 0.0) { + attn0 *= attn0; + value += attn0 * attn0 * extrapolate(offsetSeed, xsb + 0, ysb + 0, zsb + 0, dx0, dy0, dz0); + } + + double dx1 = dx0 - 1.0 - 0.3333333333333333; + double dy1 = dy0 - 0.0 - 0.3333333333333333; + double dz1 = dz0 - 0.0 - 0.3333333333333333; + double attn1 = 2.0 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1; + if (attn1 > 0.0) { + attn1 *= attn1; + value += attn1 * attn1 * extrapolate(offsetSeed, xsb + 1, ysb + 0, zsb + 0, dx1, dy1, dz1); + } + + double dx2 = dx0 - 0.0 - 0.3333333333333333; + double dy2 = dy0 - 1.0 - 0.3333333333333333; + double attn2 = 2.0 - dx2 * dx2 - dy2 * dy2 - dz1 * dz1; + if (attn2 > 0.0) { + attn2 *= attn2; + value += attn2 * attn2 * extrapolate(offsetSeed, xsb + 0, ysb + 1, zsb + 0, dx2, dy2, dz1); + } + + double dz3 = dz0 - 1.0 - 0.3333333333333333; + double attn3 = 2.0 - dx2 * dx2 - dy1 * dy1 - dz3 * dz3; + if (attn3 > 0.0) { + attn3 *= attn3; + value += attn3 * attn3 * extrapolate(offsetSeed, xsb + 0, ysb + 0, zsb + 1, dx2, dy1, dz3); + } + } else if (inSum >= 2.0) { + byte aPointx = 6; + double aScorex = xins; + byte bPointx = 5; + double bScorex = yins; + if (xins <= yins && zins < yins) { + bScorex = zins; + bPointx = 3; + } else if (xins > yins && zins < xins) { + aScorex = zins; + aPointx = 3; + } + + double winsx = 3.0 - inSum; + if (!(winsx < aScorex) && !(winsx < bScorex)) { + byte cxx = (byte)(aPointx & bPointx); + if ((cxx & 1) != 0) { + xsv_ext0 = xsb + 1; + xsv_ext1 = xsb + 2; + dx_ext0 = dx0 - 1.0 - 0.3333333333333333; + dx_ext1 = dx0 - 2.0 - 0.6666666666666666; + } else { + xsv_ext1 = xsb; + xsv_ext0 = xsb; + dx_ext0 = dx0 - 0.3333333333333333; + dx_ext1 = dx0 - 0.6666666666666666; + } + + if ((cxx & 2) != 0) { + ysv_ext0 = ysb + 1; + ysv_ext1 = ysb + 2; + dy_ext0 = dy0 - 1.0 - 0.3333333333333333; + dy_ext1 = dy0 - 2.0 - 0.6666666666666666; + } else { + ysv_ext1 = ysb; + ysv_ext0 = ysb; + dy_ext0 = dy0 - 0.3333333333333333; + dy_ext1 = dy0 - 0.6666666666666666; + } + + if ((cxx & 4) != 0) { + zsv_ext0 = zsb + 1; + zsv_ext1 = zsb + 2; + dz_ext0 = dz0 - 1.0 - 0.3333333333333333; + dz_ext1 = dz0 - 2.0 - 0.6666666666666666; + } else { + zsv_ext1 = zsb; + zsv_ext0 = zsb; + dz_ext0 = dz0 - 0.3333333333333333; + dz_ext1 = dz0 - 0.6666666666666666; + } + } else { + byte cxxx = bScorex < aScorex ? bPointx : aPointx; + if ((cxxx & 1) != 0) { + xsv_ext0 = xsb + 2; + xsv_ext1 = xsb + 1; + dx_ext0 = dx0 - 2.0 - 1.0; + dx_ext1 = dx0 - 1.0 - 1.0; + } else { + xsv_ext1 = xsb; + xsv_ext0 = xsb; + dx_ext0 = dx_ext1 = dx0 - 1.0; + } + + if ((cxxx & 2) != 0) { + ysv_ext0 = ysv_ext1 = ysb + 1; + dy_ext0 = dy_ext1 = dy0 - 1.0 - 1.0; + if ((cxxx & 1) != 0) { + ysv_ext1++; + dy_ext1--; + } else { + ysv_ext0++; + dy_ext0--; + } + } else { + ysv_ext1 = ysb; + ysv_ext0 = ysb; + dy_ext0 = dy_ext1 = dy0 - 1.0; + } + + if ((cxxx & 4) != 0) { + zsv_ext0 = zsb + 1; + zsv_ext1 = zsb + 2; + dz_ext0 = dz0 - 1.0 - 1.0; + dz_ext1 = dz0 - 2.0 - 1.0; + } else { + zsv_ext1 = zsb; + zsv_ext0 = zsb; + dz_ext0 = dz_ext1 = dz0 - 1.0; + } + } + + double dx3 = dx0 - 1.0 - 0.6666666666666666; + double dy3 = dy0 - 1.0 - 0.6666666666666666; + double dz3 = dz0 - 0.0 - 0.6666666666666666; + double attn3 = 2.0 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3; + if (attn3 > 0.0) { + attn3 *= attn3; + value += attn3 * attn3 * extrapolate(offsetSeed, xsb + 1, ysb + 1, zsb + 0, dx3, dy3, dz3); + } + + double dy2x = dy0 - 0.0 - 0.6666666666666666; + double dz2 = dz0 - 1.0 - 0.6666666666666666; + double attn2x = 2.0 - dx3 * dx3 - dy2x * dy2x - dz2 * dz2; + if (attn2x > 0.0) { + attn2x *= attn2x; + value += attn2x * attn2x * extrapolate(offsetSeed, xsb + 1, ysb + 0, zsb + 1, dx3, dy2x, dz2); + } + + double dx1x = dx0 - 0.0 - 0.6666666666666666; + double attn1x = 2.0 - dx1x * dx1x - dy3 * dy3 - dz2 * dz2; + if (attn1x > 0.0) { + attn1x *= attn1x; + value += attn1x * attn1x * extrapolate(offsetSeed, xsb + 0, ysb + 1, zsb + 1, dx1x, dy3, dz2); + } + + dx0 = dx0 - 1.0 - 1.0; + dy0 = dy0 - 1.0 - 1.0; + dz0 = dz0 - 1.0 - 1.0; + double attn0x = 2.0 - dx0 * dx0 - dy0 * dy0 - dz0 * dz0; + if (attn0x > 0.0) { + attn0x *= attn0x; + value += attn0x * attn0x * extrapolate(offsetSeed, xsb + 1, ysb + 1, zsb + 1, dx0, dy0, dz0); + } + } else { + double p1 = xins + yins; + byte aPointxx; + double aScorexx; + boolean aIsFurtherSide; + if (p1 > 1.0) { + aScorexx = p1 - 1.0; + aPointxx = 3; + aIsFurtherSide = true; + } else { + aScorexx = 1.0 - p1; + aPointxx = 4; + aIsFurtherSide = false; + } + + double p2 = xins + zins; + boolean bIsFurtherSide; + double bScorexx; + byte bPointxx; + if (p2 > 1.0) { + bScorexx = p2 - 1.0; + bPointxx = 5; + bIsFurtherSide = true; + } else { + bScorexx = 1.0 - p2; + bPointxx = 2; + bIsFurtherSide = false; + } + + double p3 = yins + zins; + if (p3 > 1.0) { + double score = p3 - 1.0; + if (aScorexx <= bScorexx && aScorexx < score) { + aPointxx = 6; + aIsFurtherSide = true; + } else if (aScorexx > bScorexx && bScorexx < score) { + bPointxx = 6; + bIsFurtherSide = true; + } + } else { + double score = 1.0 - p3; + if (aScorexx <= bScorexx && aScorexx < score) { + aPointxx = 1; + aIsFurtherSide = false; + } else if (aScorexx > bScorexx && bScorexx < score) { + bPointxx = 1; + bIsFurtherSide = false; + } + } + + if (aIsFurtherSide == bIsFurtherSide) { + if (aIsFurtherSide) { + dx_ext0 = dx0 - 1.0 - 1.0; + dy_ext0 = dy0 - 1.0 - 1.0; + dz_ext0 = dz0 - 1.0 - 1.0; + xsv_ext0 = xsb + 1; + ysv_ext0 = ysb + 1; + zsv_ext0 = zsb + 1; + byte cxxxx = (byte)(aPointxx & bPointxx); + if ((cxxxx & 1) != 0) { + dx_ext1 = dx0 - 2.0 - 0.6666666666666666; + dy_ext1 = dy0 - 0.6666666666666666; + dz_ext1 = dz0 - 0.6666666666666666; + xsv_ext1 = xsb + 2; + ysv_ext1 = ysb; + zsv_ext1 = zsb; + } else if ((cxxxx & 2) != 0) { + dx_ext1 = dx0 - 0.6666666666666666; + dy_ext1 = dy0 - 2.0 - 0.6666666666666666; + dz_ext1 = dz0 - 0.6666666666666666; + xsv_ext1 = xsb; + ysv_ext1 = ysb + 2; + zsv_ext1 = zsb; + } else { + dx_ext1 = dx0 - 0.6666666666666666; + dy_ext1 = dy0 - 0.6666666666666666; + dz_ext1 = dz0 - 2.0 - 0.6666666666666666; + xsv_ext1 = xsb; + ysv_ext1 = ysb; + zsv_ext1 = zsb + 2; + } + } else { + dx_ext0 = dx0; + dy_ext0 = dy0; + dz_ext0 = dz0; + xsv_ext0 = xsb; + ysv_ext0 = ysb; + zsv_ext0 = zsb; + byte cxxxx = (byte)(aPointxx | bPointxx); + if ((cxxxx & 1) == 0) { + dx_ext1 = dx0 + 1.0 - 0.3333333333333333; + dy_ext1 = dy0 - 1.0 - 0.3333333333333333; + dz_ext1 = dz0 - 1.0 - 0.3333333333333333; + xsv_ext1 = xsb - 1; + ysv_ext1 = ysb + 1; + zsv_ext1 = zsb + 1; + } else if ((cxxxx & 2) == 0) { + dx_ext1 = dx0 - 1.0 - 0.3333333333333333; + dy_ext1 = dy0 + 1.0 - 0.3333333333333333; + dz_ext1 = dz0 - 1.0 - 0.3333333333333333; + xsv_ext1 = xsb + 1; + ysv_ext1 = ysb - 1; + zsv_ext1 = zsb + 1; + } else { + dx_ext1 = dx0 - 1.0 - 0.3333333333333333; + dy_ext1 = dy0 - 1.0 - 0.3333333333333333; + dz_ext1 = dz0 + 1.0 - 0.3333333333333333; + xsv_ext1 = xsb + 1; + ysv_ext1 = ysb + 1; + zsv_ext1 = zsb - 1; + } + } + } else { + byte c2; + byte c1; + if (aIsFurtherSide) { + c1 = aPointxx; + c2 = bPointxx; + } else { + c1 = bPointxx; + c2 = aPointxx; + } + + if ((c1 & 1) == 0) { + dx_ext0 = dx0 + 1.0 - 0.3333333333333333; + dy_ext0 = dy0 - 1.0 - 0.3333333333333333; + dz_ext0 = dz0 - 1.0 - 0.3333333333333333; + xsv_ext0 = xsb - 1; + ysv_ext0 = ysb + 1; + zsv_ext0 = zsb + 1; + } else if ((c1 & 2) == 0) { + dx_ext0 = dx0 - 1.0 - 0.3333333333333333; + dy_ext0 = dy0 + 1.0 - 0.3333333333333333; + dz_ext0 = dz0 - 1.0 - 0.3333333333333333; + xsv_ext0 = xsb + 1; + ysv_ext0 = ysb - 1; + zsv_ext0 = zsb + 1; + } else { + dx_ext0 = dx0 - 1.0 - 0.3333333333333333; + dy_ext0 = dy0 - 1.0 - 0.3333333333333333; + dz_ext0 = dz0 + 1.0 - 0.3333333333333333; + xsv_ext0 = xsb + 1; + ysv_ext0 = ysb + 1; + zsv_ext0 = zsb - 1; + } + + dx_ext1 = dx0 - 0.6666666666666666; + dy_ext1 = dy0 - 0.6666666666666666; + dz_ext1 = dz0 - 0.6666666666666666; + xsv_ext1 = xsb; + ysv_ext1 = ysb; + zsv_ext1 = zsb; + if ((c2 & 1) != 0) { + dx_ext1 -= 2.0; + xsv_ext1 = xsb + 2; + } else if ((c2 & 2) != 0) { + dy_ext1 -= 2.0; + ysv_ext1 = ysb + 2; + } else { + dz_ext1 -= 2.0; + zsv_ext1 = zsb + 2; + } + } + + double dx1xx = dx0 - 1.0 - 0.3333333333333333; + double dy1x = dy0 - 0.0 - 0.3333333333333333; + double dz1x = dz0 - 0.0 - 0.3333333333333333; + double attn1xx = 2.0 - dx1xx * dx1xx - dy1x * dy1x - dz1x * dz1x; + if (attn1xx > 0.0) { + attn1xx *= attn1xx; + value += attn1xx * attn1xx * extrapolate(offsetSeed, xsb + 1, ysb + 0, zsb + 0, dx1xx, dy1x, dz1x); + } + + double dx2x = dx0 - 0.0 - 0.3333333333333333; + double dy2xx = dy0 - 1.0 - 0.3333333333333333; + double attn2xx = 2.0 - dx2x * dx2x - dy2xx * dy2xx - dz1x * dz1x; + if (attn2xx > 0.0) { + attn2xx *= attn2xx; + value += attn2xx * attn2xx * extrapolate(offsetSeed, xsb + 0, ysb + 1, zsb + 0, dx2x, dy2xx, dz1x); + } + + double dz3x = dz0 - 1.0 - 0.3333333333333333; + double attn3x = 2.0 - dx2x * dx2x - dy1x * dy1x - dz3x * dz3x; + if (attn3x > 0.0) { + attn3x *= attn3x; + value += attn3x * attn3x * extrapolate(offsetSeed, xsb + 0, ysb + 0, zsb + 1, dx2x, dy1x, dz3x); + } + + double dx4 = dx0 - 1.0 - 0.6666666666666666; + double dy4 = dy0 - 1.0 - 0.6666666666666666; + double dz4 = dz0 - 0.0 - 0.6666666666666666; + double attn4 = 2.0 - dx4 * dx4 - dy4 * dy4 - dz4 * dz4; + if (attn4 > 0.0) { + attn4 *= attn4; + value += attn4 * attn4 * extrapolate(offsetSeed, xsb + 1, ysb + 1, zsb + 0, dx4, dy4, dz4); + } + + double dy5 = dy0 - 0.0 - 0.6666666666666666; + double dz5 = dz0 - 1.0 - 0.6666666666666666; + double attn5 = 2.0 - dx4 * dx4 - dy5 * dy5 - dz5 * dz5; + if (attn5 > 0.0) { + attn5 *= attn5; + value += attn5 * attn5 * extrapolate(offsetSeed, xsb + 1, ysb + 0, zsb + 1, dx4, dy5, dz5); + } + + double dx6 = dx0 - 0.0 - 0.6666666666666666; + double attn6 = 2.0 - dx6 * dx6 - dy4 * dy4 - dz5 * dz5; + if (attn6 > 0.0) { + attn6 *= attn6; + value += attn6 * attn6 * extrapolate(offsetSeed, xsb + 0, ysb + 1, zsb + 1, dx6, dy4, dz5); + } + } + + double attn_ext0 = 2.0 - dx_ext0 * dx_ext0 - dy_ext0 * dy_ext0 - dz_ext0 * dz_ext0; + if (attn_ext0 > 0.0) { + attn_ext0 *= attn_ext0; + value += attn_ext0 * attn_ext0 * extrapolate(offsetSeed, xsv_ext0, ysv_ext0, zsv_ext0, dx_ext0, dy_ext0, dz_ext0); + } + + double attn_ext1 = 2.0 - dx_ext1 * dx_ext1 - dy_ext1 * dy_ext1 - dz_ext1 * dz_ext1; + if (attn_ext1 > 0.0) { + attn_ext1 *= attn_ext1; + value += attn_ext1 * attn_ext1 * extrapolate(offsetSeed, xsv_ext1, ysv_ext1, zsv_ext1, dx_ext1, dy_ext1, dz_ext1); + } + + return value / 103.0; + } + + @Nonnull + @Override + public String toString() { + return "OldSimplexNoise{}"; + } + + private static double extrapolate(int seed, int x, int y, double xd, double yd) { + int hash = seed ^ 1619 * x; + hash ^= 31337 * y; + hash = hash * hash * hash * 60493; + hash = hash >> 13 ^ hash; + DoubleArray.Double2 g = gradients2D[hash & 7]; + return xd * g.x + yd * g.y; + } + + private static double extrapolate(int seed, int x, int y, int z, double xd, double yd, double zd) { + int hash = seed ^ 1619 * x; + hash ^= 31337 * y; + hash ^= 6971 * z; + hash = hash * hash * hash * 60493; + hash = hash >> 13 ^ hash; + DoubleArray.Double3 g = gradients3D[hash % gradients3D.length]; + return xd * g.x + yd * g.y + zd * g.z; + } + + private static int fastFloor(double x) { + int xi = (int)x; + return x < xi ? xi - 1 : xi; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/PerlinNoise.java b/src/com/hypixel/hytale/procedurallib/logic/PerlinNoise.java new file mode 100644 index 0000000..724c35b --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/PerlinNoise.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.procedurallib.logic; + +import com.hypixel.hytale.procedurallib.NoiseFunction; +import javax.annotation.Nonnull; + +public class PerlinNoise implements NoiseFunction { + protected final GeneralNoise.InterpolationFunction interpolationFunction; + + public PerlinNoise(GeneralNoise.InterpolationFunction interpolationFunction) { + this.interpolationFunction = interpolationFunction; + } + + public GeneralNoise.InterpolationFunction getInterpolationFunction() { + return this.interpolationFunction; + } + + @Override + public double get(int seed, int offsetSeed, double x, double y) { + int x0 = GeneralNoise.fastFloor(x); + int y0 = GeneralNoise.fastFloor(y); + int x1 = x0 + 1; + int y1 = y0 + 1; + double xs = this.interpolationFunction.interpolate(x - x0); + double ys = this.interpolationFunction.interpolate(y - y0); + double xd0 = x - x0; + double yd0 = y - y0; + double xd1 = xd0 - 1.0; + double yd1 = yd0 - 1.0; + double xf0 = GeneralNoise.lerp(GeneralNoise.gradCoord2D(offsetSeed, x0, y0, xd0, yd0), GeneralNoise.gradCoord2D(offsetSeed, x1, y0, xd1, yd0), xs); + double xf1 = GeneralNoise.lerp(GeneralNoise.gradCoord2D(offsetSeed, x0, y1, xd0, yd1), GeneralNoise.gradCoord2D(offsetSeed, x1, y1, xd1, yd1), xs); + return GeneralNoise.lerp(xf0, xf1, ys); + } + + @Override + public double get(int seed, int offsetSeed, double x, double y, double z) { + int x0 = GeneralNoise.fastFloor(x); + int y0 = GeneralNoise.fastFloor(y); + int z0 = GeneralNoise.fastFloor(z); + int x1 = x0 + 1; + int y1 = y0 + 1; + int z1 = z0 + 1; + double xs = this.interpolationFunction.interpolate(x - x0); + double ys = this.interpolationFunction.interpolate(y - y0); + double zs = this.interpolationFunction.interpolate(z - z0); + double xd0 = x - x0; + double yd0 = y - y0; + double zd0 = z - z0; + double xd1 = xd0 - 1.0; + double yd1 = yd0 - 1.0; + double zd1 = zd0 - 1.0; + double xf00 = GeneralNoise.lerp( + GeneralNoise.gradCoord3D(offsetSeed, x0, y0, z0, xd0, yd0, zd0), GeneralNoise.gradCoord3D(offsetSeed, x1, y0, z0, xd1, yd0, zd0), xs + ); + double xf10 = GeneralNoise.lerp( + GeneralNoise.gradCoord3D(offsetSeed, x0, y1, z0, xd0, yd1, zd0), GeneralNoise.gradCoord3D(offsetSeed, x1, y1, z0, xd1, yd1, zd0), xs + ); + double xf01 = GeneralNoise.lerp( + GeneralNoise.gradCoord3D(offsetSeed, x0, y0, z1, xd0, yd0, zd1), GeneralNoise.gradCoord3D(offsetSeed, x1, y0, z1, xd1, yd0, zd1), xs + ); + double xf11 = GeneralNoise.lerp( + GeneralNoise.gradCoord3D(offsetSeed, x0, y1, z1, xd0, yd1, zd1), GeneralNoise.gradCoord3D(offsetSeed, x1, y1, z1, xd1, yd1, zd1), xs + ); + double yf0 = GeneralNoise.lerp(xf00, xf10, ys); + double yf1 = GeneralNoise.lerp(xf01, xf11, ys); + return GeneralNoise.lerp(yf0, yf1, zs); + } + + @Nonnull + @Override + public String toString() { + return "PerlinNoise{interpolationFunction=" + this.interpolationFunction + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/PointNoise.java b/src/com/hypixel/hytale/procedurallib/logic/PointNoise.java new file mode 100644 index 0000000..3af7c69 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/PointNoise.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.procedurallib.logic; + +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.procedurallib.NoiseFunction; + +public class PointNoise implements NoiseFunction { + private final double x; + private final double y; + private final double z; + private final double innerRadius2; + private final double outerRadius2; + private final transient double invRange2; + + public PointNoise(double x, double y, double z, double innerRadius, double outerRadius) { + this.x = x; + this.y = y; + this.z = z; + this.innerRadius2 = innerRadius * innerRadius; + this.outerRadius2 = outerRadius * outerRadius; + double range = this.outerRadius2 - this.innerRadius2; + this.invRange2 = range == 0.0 ? 1.0 : 1.0 / range; + } + + @Override + public double get(int seed, int seedOffset, double x, double y) { + double dist2 = MathUtil.lengthSquared(x - this.x, y - this.y); + if (dist2 <= this.innerRadius2) { + return -1.0; + } else { + return dist2 >= this.outerRadius2 ? 1.0 : -1.0 + 2.0 * (dist2 - this.innerRadius2) * this.invRange2; + } + } + + @Override + public double get(int seed, int seedOffset, double x, double y, double z) { + double dist2 = MathUtil.lengthSquared(x - this.x, y - this.y, this.z - z); + if (dist2 <= this.innerRadius2) { + return -1.0; + } else { + return dist2 >= this.outerRadius2 ? 1.0 : -1.0 + 2.0 * (dist2 - this.innerRadius2) * this.invRange2; + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/ResultBuffer.java b/src/com/hypixel/hytale/procedurallib/logic/ResultBuffer.java new file mode 100644 index 0000000..9a843d3 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/ResultBuffer.java @@ -0,0 +1,149 @@ +package com.hypixel.hytale.procedurallib.logic; + +public class ResultBuffer { + public static final ResultBuffer.Bounds2d bounds2d = new ResultBuffer.Bounds2d(); + public static final ResultBuffer.ResultBuffer2d buffer2d = new ResultBuffer.ResultBuffer2d(); + public static final ResultBuffer.ResultBuffer3d buffer3d = new ResultBuffer.ResultBuffer3d(); + + public ResultBuffer() { + } + + public static class Bounds2d { + public double minX; + public double minY; + public double maxX; + public double maxY; + + public Bounds2d() { + } + + public void assign(double minX, double minY, double maxX, double maxY) { + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + public boolean contains(double x, double y) { + return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY; + } + } + + public static class ResultBuffer2d { + public int hash; + public int hash2; + public int ix; + public int iy; + public int ix2; + public int iy2; + public double distance; + public double distance2; + public double x; + public double y; + public double x2; + public double y2; + + public ResultBuffer2d() { + } + + public void register(int hash, int ix, int iy, double distance, double x, double y) { + if (distance < this.distance) { + this.ix = ix; + this.iy = iy; + this.distance = distance; + this.x = x; + this.y = y; + this.hash = hash; + } + } + + public void register2(int hash, int ix, int iy, double distance, double x, double y) { + if (distance < this.distance) { + this.distance2 = this.distance; + this.x2 = this.x; + this.y2 = this.y; + this.ix2 = this.ix; + this.iy2 = this.iy; + this.distance = distance; + this.x = x; + this.y = y; + this.ix = ix; + this.iy = iy; + this.hash2 = this.hash; + this.hash = hash; + } else if (distance < this.distance2) { + this.distance2 = distance; + this.x2 = x; + this.y2 = y; + this.ix2 = ix; + this.iy2 = iy; + this.hash2 = hash; + } + } + } + + public static class ResultBuffer3d { + public int hash; + public int hash2; + public int ix; + public int iy; + public int iz; + public int ix2; + public int iy2; + public int iz2; + public double distance; + public double distance2; + public double x; + public double y; + public double z; + public double x2; + public double y2; + public double z2; + + public ResultBuffer3d() { + } + + public void register(int hash, int ix, int iy, int iz, double distance, double x, double y, double z) { + if (distance < this.distance) { + this.hash = hash; + this.ix = ix; + this.iy = iy; + this.iz = iz; + this.distance = distance; + this.x = x; + this.y = y; + this.z = z; + } + } + + public void register2(int hash, int ix, int iy, int iz, double distance, double x, double y, double z) { + if (distance < this.distance) { + this.distance2 = this.distance; + this.x2 = this.x; + this.y2 = this.y; + this.z2 = this.z; + this.ix2 = this.ix; + this.iy2 = this.iy; + this.iz2 = this.iz; + this.distance = distance; + this.x = x; + this.y = y; + this.z = z; + this.ix = ix; + this.iy = iy; + this.iz = iz; + this.hash2 = this.hash; + this.hash = hash; + } else if (distance < this.distance2) { + this.distance2 = distance; + this.x2 = x; + this.y2 = y; + this.z2 = z; + this.ix2 = ix; + this.iy2 = iy; + this.iz2 = iz; + this.hash2 = hash; + } + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/SimplexNoise.java b/src/com/hypixel/hytale/procedurallib/logic/SimplexNoise.java new file mode 100644 index 0000000..a991315 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/SimplexNoise.java @@ -0,0 +1,187 @@ +package com.hypixel.hytale.procedurallib.logic; + +import com.hypixel.hytale.procedurallib.NoiseFunction; +import javax.annotation.Nonnull; + +public class SimplexNoise implements NoiseFunction { + public static final SimplexNoise INSTANCE = new SimplexNoise(); + private static final double F2 = 0.5; + private static final double P1_F2 = -0.5; + private static final double G2 = 0.25; + private static final double F3 = 0.3333333333333333; + private static final double G3 = 0.16666666666666666; + private static final double G33 = -0.5; + + private SimplexNoise() { + } + + @Override + public double get(int seed, int offsetSeed, double x, double y) { + double t = (x + y) * 0.5; + int i = GeneralNoise.fastFloor(x + t); + int j = GeneralNoise.fastFloor(y + t); + t = (i + j) * 0.25; + double X0 = i - t; + double Y0 = j - t; + double x0 = x - X0; + double y0 = y - Y0; + int i1; + int j1; + if (x0 > y0) { + i1 = 1; + j1 = 0; + } else { + i1 = 0; + j1 = 1; + } + + t = 0.5 - x0 * x0 - y0 * y0; + double n0; + if (t < 0.0) { + n0 = 0.0; + } else { + t *= t; + n0 = t * t * GeneralNoise.gradCoord2D(offsetSeed, i, j, x0, y0); + } + + double x1 = x0 - i1 + 0.25; + double y1 = y0 - j1 + 0.25; + t = 0.5 - x1 * x1 - y1 * y1; + double n1; + if (t < 0.0) { + n1 = 0.0; + } else { + t *= t; + n1 = t * t * GeneralNoise.gradCoord2D(offsetSeed, i + i1, j + j1, x1, y1); + } + + double x2 = x0 + -0.5; + double y2 = y0 + -0.5; + t = 0.5 - x2 * x2 - y2 * y2; + double n2; + if (t < 0.0) { + n2 = 0.0; + } else { + t *= t; + n2 = t * t * GeneralNoise.gradCoord2D(offsetSeed, i + 1, j + 1, x2, y2); + } + + return 50.0 * (n0 + n1 + n2); + } + + @Override + public double get(int seed, int offsetSeed, double x, double y, double z) { + double t = (x + y + z) * 0.3333333333333333; + int i = GeneralNoise.fastFloor(x + t); + int j = GeneralNoise.fastFloor(y + t); + int k = GeneralNoise.fastFloor(z + t); + t = (i + j + k) * 0.16666666666666666; + double x0 = x - (i - t); + double y0 = y - (j - t); + double z0 = z - (k - t); + int i1; + int j1; + int k1; + int i2; + int j2; + int k2; + if (x0 >= y0) { + if (y0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 1; + k2 = 0; + } else if (x0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 0; + k2 = 1; + } else { + i1 = 0; + j1 = 0; + k1 = 1; + i2 = 1; + j2 = 0; + k2 = 1; + } + } else if (y0 < z0) { + i1 = 0; + j1 = 0; + k1 = 1; + i2 = 0; + j2 = 1; + k2 = 1; + } else if (x0 < z0) { + i1 = 0; + j1 = 1; + k1 = 0; + i2 = 0; + j2 = 1; + k2 = 1; + } else { + i1 = 0; + j1 = 1; + k1 = 0; + i2 = 1; + j2 = 1; + k2 = 0; + } + + double x1 = x0 - i1 + 0.16666666666666666; + double y1 = y0 - j1 + 0.16666666666666666; + double z1 = z0 - k1 + 0.16666666666666666; + double x2 = x0 - i2 + 0.3333333333333333; + double y2 = y0 - j2 + 0.3333333333333333; + double z2 = z0 - k2 + 0.3333333333333333; + double x3 = x0 + -0.5; + double y3 = y0 + -0.5; + double z3 = z0 + -0.5; + t = 0.6 - x0 * x0 - y0 * y0 - z0 * z0; + double n0; + if (t < 0.0) { + n0 = 0.0; + } else { + t *= t; + n0 = t * t * GeneralNoise.gradCoord3D(offsetSeed, i, j, k, x0, y0, z0); + } + + t = 0.6 - x1 * x1 - y1 * y1 - z1 * z1; + double n1; + if (t < 0.0) { + n1 = 0.0; + } else { + t *= t; + n1 = t * t * GeneralNoise.gradCoord3D(offsetSeed, i + i1, j + j1, k + k1, x1, y1, z1); + } + + t = 0.6 - x2 * x2 - y2 * y2 - z2 * z2; + double n2; + if (t < 0.0) { + n2 = 0.0; + } else { + t *= t; + n2 = t * t * GeneralNoise.gradCoord3D(offsetSeed, i + i2, j + j2, k + k2, x2, y2, z2); + } + + t = 0.6 - x3 * x3 - y3 * y3 - z3 * z3; + double n3; + if (t < 0.0) { + n3 = 0.0; + } else { + t *= t; + n3 = t * t * GeneralNoise.gradCoord3D(offsetSeed, i + 1, j + 1, k + 1, x3, y3, z3); + } + + return 32.0 * (n0 + n1 + n2 + n3); + } + + @Nonnull + @Override + public String toString() { + return "SimplexNoise{}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/ValueNoise.java b/src/com/hypixel/hytale/procedurallib/logic/ValueNoise.java new file mode 100644 index 0000000..50f3fe3 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/ValueNoise.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.procedurallib.logic; + +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.procedurallib.NoiseFunction; +import javax.annotation.Nonnull; + +public class ValueNoise implements NoiseFunction { + protected final GeneralNoise.InterpolationFunction interpolationFunction; + + public ValueNoise(GeneralNoise.InterpolationFunction interpolationFunction) { + this.interpolationFunction = interpolationFunction; + } + + public GeneralNoise.InterpolationFunction getInterpolationFunction() { + return this.interpolationFunction; + } + + @Override + public double get(int seed, int offsetSeed, double x, double y) { + int x0 = GeneralNoise.fastFloor(x); + int y0 = GeneralNoise.fastFloor(y); + int x1 = x0 + 1; + int y1 = y0 + 1; + double xs = this.interpolationFunction.interpolate(x - x0); + double ys = this.interpolationFunction.interpolate(y - y0); + double xf0 = GeneralNoise.lerp(HashUtil.random(offsetSeed, x0, y0), HashUtil.random(offsetSeed, x1, y0), xs); + double xf1 = GeneralNoise.lerp(HashUtil.random(offsetSeed, x0, y1), HashUtil.random(offsetSeed, x1, y1), xs); + return GeneralNoise.lerp(xf0, xf1, ys) * 2.0 - 1.0; + } + + @Override + public double get(int seed, int offsetSeed, double x, double y, double z) { + int x0 = GeneralNoise.fastFloor(x); + int y0 = GeneralNoise.fastFloor(y); + int z0 = GeneralNoise.fastFloor(z); + int x1 = x0 + 1; + int y1 = y0 + 1; + int z1 = z0 + 1; + double xs = this.interpolationFunction.interpolate(x - x0); + double ys = this.interpolationFunction.interpolate(y - y0); + double zs = this.interpolationFunction.interpolate(z - z0); + double xf00 = GeneralNoise.lerp(HashUtil.random(offsetSeed, x0, y0, z0), HashUtil.random(offsetSeed, x1, y0, z0), xs); + double xf10 = GeneralNoise.lerp(HashUtil.random(offsetSeed, x0, y1, z0), HashUtil.random(offsetSeed, x1, y1, z0), xs); + double xf01 = GeneralNoise.lerp(HashUtil.random(offsetSeed, x0, y0, z1), HashUtil.random(offsetSeed, x1, y0, z1), xs); + double xf11 = GeneralNoise.lerp(HashUtil.random(offsetSeed, x0, y1, z1), HashUtil.random(offsetSeed, x1, y1, z1), xs); + double yf0 = GeneralNoise.lerp(xf00, xf10, ys); + double yf1 = GeneralNoise.lerp(xf01, xf11, ys); + return GeneralNoise.lerp(yf0, yf1, zs) * 2.0 - 1.0; + } + + @Nonnull + @Override + public String toString() { + return "ValueNoise{interpolationFunction=" + this.interpolationFunction + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/BorderDistanceFunction.java b/src/com/hypixel/hytale/procedurallib/logic/cell/BorderDistanceFunction.java new file mode 100644 index 0000000..32ca696 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/BorderDistanceFunction.java @@ -0,0 +1,146 @@ +package com.hypixel.hytale.procedurallib.logic.cell; + +import com.hypixel.hytale.procedurallib.condition.IDoubleCondition; +import com.hypixel.hytale.procedurallib.condition.IIntCondition; +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.DensityPointEvaluator; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.JitterPointEvaluator; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.NormalPointEvaluator; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.PointEvaluator; +import com.hypixel.hytale.procedurallib.logic.point.PointConsumer; +import javax.annotation.Nonnull; + +public class BorderDistanceFunction implements CellDistanceFunction { + protected final CellDistanceFunction distanceFunction; + @Nonnull + protected final PointEvaluator cellEvaluator; + @Nonnull + protected final PointEvaluator borderEvaluator; + @Nonnull + protected final IIntCondition density; + + public BorderDistanceFunction(CellDistanceFunction distanceFunction, @Nonnull PointEvaluator borderEvaluator, IDoubleCondition density) { + this.distanceFunction = distanceFunction; + this.borderEvaluator = borderEvaluator; + this.cellEvaluator = new JitterPointEvaluator(NormalPointEvaluator.EUCLIDEAN, borderEvaluator.getJitter()); + this.density = DensityPointEvaluator.getDensityCondition(density); + } + + @Override + public double scale(double value) { + return this.distanceFunction.scale(value); + } + + @Override + public double invScale(double value) { + return this.distanceFunction.invScale(value); + } + + @Override + public int getCellX(double x, double y) { + return this.distanceFunction.getCellX(x, y); + } + + @Override + public int getCellY(double x, double y) { + return this.distanceFunction.getCellY(x, y); + } + + @Override + public void nearest2D(int seed, double x, double y, int cellX, int cellY, @Nonnull ResultBuffer.ResultBuffer2d buffer, PointEvaluator pointEvaluator) { + this.transition2D(seed, x, y, cellX, cellY, buffer, pointEvaluator); + } + + @Override + public void transition2D(int seed, double x, double y, int cellX, int cellY, @Nonnull ResultBuffer.ResultBuffer2d buffer, PointEvaluator pointEvaluator) { + this.distanceFunction.nearest2D(seed, x, y, cellX, cellY, buffer, this.cellEvaluator); + if (!this.density.eval(buffer.hash)) { + buffer.distance = 0.0; + } else { + cellX = buffer.ix; + cellY = buffer.iy; + buffer.ix2 = cellX; + buffer.iy2 = cellY; + buffer.x2 = buffer.x; + buffer.y2 = buffer.y; + buffer.distance = Double.POSITIVE_INFINITY; + buffer.distance2 = Double.POSITIVE_INFINITY; + int dx = this.borderEvaluator.getJitter().getMaxX() > 0.5 ? 2 : 1; + int dy = this.borderEvaluator.getJitter().getMaxY() > 0.5 ? 2 : 1; + + for (int cy = cellY - dy; cy <= cellY + dy; cy++) { + for (int cx = cellX - dx; cx <= cellX + dx; cx++) { + this.distanceFunction.evalPoint2(seed, x, y, cx, cy, buffer, this.borderEvaluator); + } + } + } + } + + @Override + public void nearest3D( + int seed, double x, double y, double z, int cellX, int cellY, int cellZ, ResultBuffer.ResultBuffer3d buffer, PointEvaluator pointEvaluator + ) { + throw new UnsupportedOperationException(); + } + + @Override + public void transition3D( + int seed, double x, double y, double z, int cellX, int cellY, int cellZ, ResultBuffer.ResultBuffer3d buffer, PointEvaluator pointEvaluator + ) { + throw new UnsupportedOperationException(); + } + + @Override + public void evalPoint(int seed, double x, double y, int cellX, int cellY, ResultBuffer.ResultBuffer2d buffer, PointEvaluator pointEvaluator) { + throw new UnsupportedOperationException(); + } + + @Override + public void evalPoint2(int seed, double x, double y, int cellX, int cellY, ResultBuffer.ResultBuffer2d buffer, PointEvaluator pointEvaluator) { + throw new UnsupportedOperationException(); + } + + @Override + public void evalPoint( + int seed, double x, double y, double z, int cellX, int cellY, int cellZ, ResultBuffer.ResultBuffer3d buffer, PointEvaluator pointEvaluator + ) { + throw new UnsupportedOperationException(); + } + + @Override + public void evalPoint2( + int seed, double x, double y, double z, int cellX, int cellY, int cellZ, ResultBuffer.ResultBuffer3d buffer, PointEvaluator pointEvaluator + ) { + throw new UnsupportedOperationException(); + } + + @Override + public void collect( + int originalSeed, + int seed, + int minX, + int minY, + int maxX, + int maxY, + ResultBuffer.Bounds2d bounds, + T ctx, + PointConsumer collector, + PointEvaluator pointEvaluator + ) { + this.distanceFunction.collect(originalSeed, seed, minX, minY, maxX, maxY, bounds, ctx, collector, pointEvaluator); + } + + @Nonnull + @Override + public String toString() { + return "BorderDistanceFunction{distanceFunction=" + + this.distanceFunction + + ", cellEvaluator=" + + this.cellEvaluator + + ", borderEvaluator=" + + this.borderEvaluator + + ", density=" + + this.density + + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/CellDistanceFunction.java b/src/com/hypixel/hytale/procedurallib/logic/cell/CellDistanceFunction.java new file mode 100644 index 0000000..442b48b --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/CellDistanceFunction.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.procedurallib.logic.cell; + +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.PointEvaluator; +import com.hypixel.hytale.procedurallib.logic.point.PointConsumer; + +public interface CellDistanceFunction { + default double scale(double value) { + return value; + } + + default double invScale(double value) { + return value; + } + + default int getCellX(double x, double y) { + return MathUtil.floor(x); + } + + default int getCellY(double x, double y) { + return MathUtil.floor(y); + } + + default int getCellX(double x, double y, double z) { + return MathUtil.floor(x); + } + + default int getCellY(double x, double y, double z) { + return MathUtil.floor(y); + } + + default int getCellZ(double x, double y, double z) { + return MathUtil.floor(z); + } + + void nearest2D(int var1, double var2, double var4, int var6, int var7, ResultBuffer.ResultBuffer2d var8, PointEvaluator var9); + + void nearest3D(int var1, double var2, double var4, double var6, int var8, int var9, int var10, ResultBuffer.ResultBuffer3d var11, PointEvaluator var12); + + void transition2D(int var1, double var2, double var4, int var6, int var7, ResultBuffer.ResultBuffer2d var8, PointEvaluator var9); + + void transition3D(int var1, double var2, double var4, double var6, int var8, int var9, int var10, ResultBuffer.ResultBuffer3d var11, PointEvaluator var12); + + void evalPoint(int var1, double var2, double var4, int var6, int var7, ResultBuffer.ResultBuffer2d var8, PointEvaluator var9); + + void evalPoint(int var1, double var2, double var4, double var6, int var8, int var9, int var10, ResultBuffer.ResultBuffer3d var11, PointEvaluator var12); + + void evalPoint2(int var1, double var2, double var4, int var6, int var7, ResultBuffer.ResultBuffer2d var8, PointEvaluator var9); + + void evalPoint2(int var1, double var2, double var4, double var6, int var8, int var9, int var10, ResultBuffer.ResultBuffer3d var11, PointEvaluator var12); + + void collect(int var1, int var2, int var3, int var4, int var5, int var6, ResultBuffer.Bounds2d var7, T var8, PointConsumer var9, PointEvaluator var10); +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/CellPointFunction.java b/src/com/hypixel/hytale/procedurallib/logic/cell/CellPointFunction.java new file mode 100644 index 0000000..b329fcc --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/CellPointFunction.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.procedurallib.logic.cell; + +import com.hypixel.hytale.procedurallib.logic.DoubleArray; + +public interface CellPointFunction { + default double scale(double value) { + return value; + } + + default double normalize(double value) { + return value; + } + + int getHash(int var1, int var2, int var3); + + double getX(double var1, double var3); + + double getY(double var1, double var3); + + DoubleArray.Double2 getOffsets(int var1); +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/CellType.java b/src/com/hypixel/hytale/procedurallib/logic/cell/CellType.java new file mode 100644 index 0000000..0342edb --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/CellType.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.procedurallib.logic.cell; + +public enum CellType { + SQUARE, + HEX; + + private CellType() { + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/DistanceCalculationMode.java b/src/com/hypixel/hytale/procedurallib/logic/cell/DistanceCalculationMode.java new file mode 100644 index 0000000..7c5e045 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/DistanceCalculationMode.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.procedurallib.logic.cell; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public enum DistanceCalculationMode { + EUCLIDEAN(new PointDistanceFunction() { + @Override + public double distance2D(double deltaX, double deltaY) { + return deltaX * deltaX + deltaY * deltaY; + } + + @Override + public double distance3D(double deltaX, double deltaY, double deltaZ) { + return deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ; + } + + @Nonnull + @Override + public String toString() { + return "EuclideanPointDistanceFunction{}"; + } + }), + MANHATTAN(new PointDistanceFunction() { + @Override + public double distance2D(double deltaX, double deltaY) { + return Math.abs(deltaX) + Math.abs(deltaY); + } + + @Override + public double distance3D(double deltaX, double deltaY, double deltaZ) { + return Math.abs(deltaX) + Math.abs(deltaY) + Math.abs(deltaZ); + } + + @Nonnull + @Override + public String toString() { + return "ManhattanPointDistanceFunction{}"; + } + }), + NATURAL(new PointDistanceFunction() { + @Override + public double distance2D(double deltaX, double deltaY) { + return Math.abs(deltaX) + Math.abs(deltaY) + deltaX * deltaX + deltaY * deltaY; + } + + @Override + public double distance3D(double deltaX, double deltaY, double deltaZ) { + return Math.abs(deltaX) + Math.abs(deltaY) + Math.abs(deltaZ) + deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ; + } + + @Nonnull + @Override + public String toString() { + return "NaturalPointDistanceFunction{}"; + } + }), + MAX(new PointDistanceFunction() { + @Override + public double distance2D(double deltaX, double deltaY) { + return Math.max(Math.abs(deltaX), Math.abs(deltaY)); + } + + @Override + public double distance3D(double deltaX, double deltaY, double deltaZ) { + return Math.max(Math.abs(deltaX), Math.max(Math.abs(deltaY), Math.abs(deltaZ))); + } + + @Nonnull + @Override + public String toString() { + return "MaxPointDistanceFunction{}"; + } + }); + + protected static final DistanceCalculationMode[] VALUES = values(); + private final PointDistanceFunction function; + + private DistanceCalculationMode(PointDistanceFunction function) { + this.function = function; + } + + public PointDistanceFunction getFunction() { + return this.function; + } + + @Nullable + public static DistanceCalculationMode from(PointDistanceFunction function) { + for (DistanceCalculationMode mode : VALUES) { + if (mode.function == function) { + return mode; + } + } + + return null; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/GridCellDistanceFunction.java b/src/com/hypixel/hytale/procedurallib/logic/cell/GridCellDistanceFunction.java new file mode 100644 index 0000000..5320757 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/GridCellDistanceFunction.java @@ -0,0 +1,164 @@ +package com.hypixel.hytale.procedurallib.logic.cell; + +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.procedurallib.logic.CellularNoise; +import com.hypixel.hytale.procedurallib.logic.DoubleArray; +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.PointEvaluator; +import com.hypixel.hytale.procedurallib.logic.cell.jitter.CellJitter; +import com.hypixel.hytale.procedurallib.logic.point.PointConsumer; +import javax.annotation.Nonnull; + +public class GridCellDistanceFunction implements CellDistanceFunction { + public static final GridCellDistanceFunction DISTANCE_FUNCTION = new GridCellDistanceFunction(); + public static final CellPointFunction POINT_FUNCTION = new CellPointFunction() { + @Override + public int getHash(int seed, int cellX, int cellY) { + return GridCellDistanceFunction.getHash(seed, cellX, cellY); + } + + @Override + public DoubleArray.Double2 getOffsets(int hash) { + return CellularNoise.CELL_2D[hash & 0xFF]; + } + + @Override + public double getX(double x, double y) { + return x; + } + + @Override + public double getY(double x, double y) { + return y; + } + }; + + public GridCellDistanceFunction() { + } + + @Override + public void nearest2D(int seed, double x, double y, int cellX, int cellY, ResultBuffer.ResultBuffer2d buffer, @Nonnull PointEvaluator pointEvaluator) { + for (int cy = cellY - 1; cy <= cellY + 1; cy++) { + for (int cx = cellX - 1; cx <= cellX + 1; cx++) { + this.evalPoint(seed, x, y, cx, cy, buffer, pointEvaluator); + } + } + } + + @Override + public void nearest3D( + int seed, double x, double y, double z, int cellX, int cellY, int cellZ, ResultBuffer.ResultBuffer3d buffer, @Nonnull PointEvaluator pointEvaluator + ) { + for (int cx = cellX - 1; cx <= cellX + 1; cx++) { + for (int cy = cellY - 1; cy <= cellY + 1; cy++) { + for (int cz = cellZ - 1; cz <= cellZ + 1; cz++) { + this.evalPoint(seed, x, y, z, cx, cy, cz, buffer, pointEvaluator); + } + } + } + } + + @Override + public void transition2D(int seed, double x, double y, int cellX, int cellY, ResultBuffer.ResultBuffer2d buffer, @Nonnull PointEvaluator pointEvaluator) { + for (int cy = cellY - 1; cy <= cellY + 1; cy++) { + for (int cx = cellX - 1; cx <= cellX + 1; cx++) { + this.evalPoint2(seed, x, y, cx, cy, buffer, pointEvaluator); + } + } + } + + @Override + public void transition3D( + int seed, double x, double y, double z, int cellX, int cellY, int cellZ, ResultBuffer.ResultBuffer3d buffer, @Nonnull PointEvaluator pointEvaluator + ) { + for (int cx = cellX - 1; cx <= cellX + 1; cx++) { + for (int cy = cellY - 1; cy <= cellY + 1; cy++) { + for (int cz = cellZ - 1; cz <= cellZ + 1; cz++) { + this.evalPoint2(seed, x, y, z, cx, cy, cz, buffer, pointEvaluator); + } + } + } + } + + @Override + public void evalPoint(int seed, double x, double y, int cellX, int cellY, ResultBuffer.ResultBuffer2d buffer, @Nonnull PointEvaluator pointEvaluator) { + int cellHash = getHash(seed, cellX, cellY); + DoubleArray.Double2 vec = CellularNoise.CELL_2D[cellHash & 0xFF]; + CellJitter jitter = pointEvaluator.getJitter(); + double px = jitter.getPointX(cellX, vec); + double py = jitter.getPointY(cellY, vec); + pointEvaluator.evalPoint(seed, x, y, cellHash, cellX, cellY, px, py, buffer); + } + + @Override + public void evalPoint( + int seed, double x, double y, double z, int cellX, int cellY, int cellZ, ResultBuffer.ResultBuffer3d buffer, @Nonnull PointEvaluator pointEvaluator + ) { + int cellHash = getHash(seed, cellX, cellY); + DoubleArray.Double3 vec = CellularNoise.CELL_3D[cellHash & 0xFF]; + CellJitter jitter = pointEvaluator.getJitter(); + double px = jitter.getPointX(cellX, vec); + double py = jitter.getPointY(cellX, vec); + double pz = jitter.getPointZ(cellX, vec); + pointEvaluator.evalPoint(seed, x, y, z, cellHash, cellX, cellY, cellZ, px, py, pz, buffer); + } + + @Override + public void evalPoint2(int seed, double x, double y, int cellX, int cellY, ResultBuffer.ResultBuffer2d buffer, @Nonnull PointEvaluator pointEvaluator) { + int cellHash = getHash(seed, cellX, cellY); + DoubleArray.Double2 vec = CellularNoise.CELL_2D[cellHash & 0xFF]; + CellJitter jitter = pointEvaluator.getJitter(); + double px = jitter.getPointX(cellX, vec); + double py = jitter.getPointY(cellY, vec); + pointEvaluator.evalPoint2(seed, x, y, cellHash, cellX, cellY, px, py, buffer); + } + + @Override + public void evalPoint2( + int seed, double x, double y, double z, int cellX, int cellY, int cellZ, ResultBuffer.ResultBuffer3d buffer, @Nonnull PointEvaluator pointEvaluator + ) { + int cellHash = getHash(seed, cellX, cellY); + DoubleArray.Double3 vec = CellularNoise.CELL_3D[cellHash & 0xFF]; + CellJitter jitter = pointEvaluator.getJitter(); + double px = jitter.getPointX(cellX, vec); + double py = jitter.getPointY(cellX, vec); + double pz = jitter.getPointZ(cellX, vec); + pointEvaluator.evalPoint2(seed, x, y, z, cellHash, cellX, cellY, cellZ, px, py, pz, buffer); + } + + @Override + public void collect( + int originalSeed, + int seed, + int minX, + int minY, + int maxX, + int maxY, + ResultBuffer.Bounds2d bounds, + T ctx, + @Nonnull PointConsumer collector, + @Nonnull PointEvaluator pointEvaluator + ) { + CellJitter jitter = pointEvaluator.getJitter(); + + for (int cy = minY; cy <= maxY; cy++) { + for (int cx = minX; cx <= maxX; cx++) { + int cellHash = getHash(seed, cx, cy); + DoubleArray.Double2 vec = CellularNoise.CELL_2D[cellHash & 0xFF]; + double px = jitter.getPointX(cx, vec); + double py = jitter.getPointY(cy, vec); + pointEvaluator.collectPoint(cellHash, cx, cy, px, py, ctx, collector); + } + } + } + + @Nonnull + @Override + public String toString() { + return "GridCellFunction{}"; + } + + public static int getHash(int seed, int cellX, int cellY) { + return (int)HashUtil.rehash(seed, cellX, cellY); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/HexCellDistanceFunction.java b/src/com/hypixel/hytale/procedurallib/logic/cell/HexCellDistanceFunction.java new file mode 100644 index 0000000..2d52427 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/HexCellDistanceFunction.java @@ -0,0 +1,273 @@ +package com.hypixel.hytale.procedurallib.logic.cell; + +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.procedurallib.logic.CellularNoise; +import com.hypixel.hytale.procedurallib.logic.DoubleArray; +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.PointEvaluator; +import com.hypixel.hytale.procedurallib.logic.cell.jitter.CellJitter; +import com.hypixel.hytale.procedurallib.logic.point.PointConsumer; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +public class HexCellDistanceFunction implements CellDistanceFunction { + public static final HexCellDistanceFunction DISTANCE_FUNCTION = new HexCellDistanceFunction(); + public static final CellPointFunction POINT_FUNCTION = new CellPointFunction() { + @Override + public double scale(double value) { + return value * HexCellDistanceFunction.SCALE; + } + + @Override + public double normalize(double value) { + return value * 0.3333333333333333; + } + + @Override + public int getHash(int seed, int cellX, int cellY) { + return HexCellDistanceFunction.getHash(seed, cellX, cellY); + } + + @Override + public double getX(double x, double y) { + return HexCellDistanceFunction.toHexX(x, y); + } + + @Override + public double getY(double x, double y) { + return HexCellDistanceFunction.toHexY(x, y); + } + + @Override + public DoubleArray.Double2 getOffsets(int hash) { + return HexCellDistanceFunction.HEX_CELL_2D[hash & 0xFF]; + } + }; + protected static final double X_TO_GRID_X = Math.sqrt(3.0) / 3.0; + protected static final double Y_TO_GRID_X = -0.3333333333333333; + protected static final double Y_TO_GRID_Y = 0.6666666666666666; + protected static final double X_TO_HEX_X = Math.sqrt(3.0); + protected static final double Y_TO_HEX_X = Math.sqrt(3.0) / 2.0; + protected static final double Y_TO_HEX_Y = 1.5; + protected static final double NORMALIZATION = 0.3333333333333333; + protected static final double SCALE = (X_TO_HEX_X + 1.5) / 2.0; + public static final DoubleArray.Double2[] HEX_CELL_2D = Stream.of(CellularNoise.CELL_2D) + .map(d -> new DoubleArray.Double2(d.x - 0.5, d.y - 0.5)) + .toArray(DoubleArray.Double2[]::new); + + public HexCellDistanceFunction() { + } + + @Override + public double scale(double value) { + return value * SCALE; + } + + @Override + public double invScale(double value) { + return value / SCALE; + } + + @Override + public int getCellX(double x, double y) { + return toGridX(x, y); + } + + @Override + public int getCellY(double x, double y) { + return toGridY(x, y); + } + + @Override + public void nearest2D( + int seed, double x, double y, int cellX, int cellY, @Nonnull ResultBuffer.ResultBuffer2d buffer, @Nonnull PointEvaluator pointEvaluator + ) { + this.evalPoint(seed, x, y, cellX - 1, cellY - 1, buffer, pointEvaluator); + this.evalPoint(seed, x, y, cellX + 0, cellY - 1, buffer, pointEvaluator); + this.evalPoint(seed, x, y, cellX + 1, cellY - 1, buffer, pointEvaluator); + this.evalPoint(seed, x, y, cellX - 1, cellY + 0, buffer, pointEvaluator); + this.evalPoint(seed, x, y, cellX + 0, cellY + 0, buffer, pointEvaluator); + this.evalPoint(seed, x, y, cellX + 1, cellY + 0, buffer, pointEvaluator); + this.evalPoint(seed, x, y, cellX - 1, cellY + 1, buffer, pointEvaluator); + this.evalPoint(seed, x, y, cellX + 0, cellY + 1, buffer, pointEvaluator); + this.evalPoint(seed, x, y, cellX + 1, cellY + 1, buffer, pointEvaluator); + buffer.distance *= 0.3333333333333333; + } + + @Override + public void nearest3D( + int seed, double x, double y, double z, int cellX, int cellY, int cellZ, ResultBuffer.ResultBuffer3d buffer, PointEvaluator pointEvaluator + ) { + throw new UnsupportedOperationException(); + } + + @Override + public void transition2D( + int seed, double x, double y, int cellX, int cellY, @Nonnull ResultBuffer.ResultBuffer2d buffer, @Nonnull PointEvaluator pointEvaluator + ) { + this.evalPoint2(seed, x, y, cellX - 1, cellY - 1, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX + 0, cellY - 1, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX + 1, cellY - 1, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX - 1, cellY + 0, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX + 0, cellY + 0, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX + 1, cellY + 0, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX - 1, cellY + 1, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX + 0, cellY + 1, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX + 1, cellY + 1, buffer, pointEvaluator); + CellJitter jitter = pointEvaluator.getJitter(); + if (jitter.getMaxX() > 0.5) { + this.evalPoint2(seed, x, y, cellX - 2, cellY - 1, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX - 2, cellY + 0, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX - 2, cellY + 1, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX + 2, cellY + 0, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX + 2, cellY - 1, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX + 2, cellY + 1, buffer, pointEvaluator); + } + + if (jitter.getMaxY() > 0.5) { + this.evalPoint2(seed, x, y, cellX - 1, cellY - 2, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX + 0, cellY - 2, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX + 1, cellY - 2, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX - 1, cellY + 2, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX + 0, cellY + 2, buffer, pointEvaluator); + this.evalPoint2(seed, x, y, cellX + 1, cellY + 2, buffer, pointEvaluator); + } + + buffer.distance *= 0.3333333333333333; + buffer.distance2 *= 0.3333333333333333; + } + + @Override + public void transition3D( + int seed, double x, double y, double z, int cellX, int cellY, int cellZ, ResultBuffer.ResultBuffer3d buffer, PointEvaluator pointEvaluator + ) { + throw new UnsupportedOperationException(); + } + + @Override + public void evalPoint(int seed, double x, double y, int cellX, int cellY, ResultBuffer.ResultBuffer2d buffer, @Nonnull PointEvaluator pointEvaluator) { + int cellHash = getHash(seed, cellX, cellY); + DoubleArray.Double2 vec = HEX_CELL_2D[cellHash & 0xFF]; + CellJitter jitter = pointEvaluator.getJitter(); + double px = jitter.getPointX(cellX, vec); + double py = jitter.getPointY(cellY, vec); + double hx = toHexX(px, py); + double hy = toHexY(px, py); + pointEvaluator.evalPoint(seed, x, y, cellHash, cellX, cellY, hx, hy, buffer); + } + + @Override + public void evalPoint( + int seed, double x, double y, double z, int cellX, int cellY, int cellZ, ResultBuffer.ResultBuffer3d buffer, PointEvaluator pointEvaluator + ) { + throw new UnsupportedOperationException(); + } + + @Override + public void evalPoint2(int seed, double x, double y, int cellX, int cellY, ResultBuffer.ResultBuffer2d buffer, @Nonnull PointEvaluator pointEvaluator) { + int cellHash = getHash(seed, cellX, cellY); + DoubleArray.Double2 vec = HEX_CELL_2D[cellHash & 0xFF]; + CellJitter jitter = pointEvaluator.getJitter(); + double px = jitter.getPointX(cellX, vec); + double py = jitter.getPointY(cellY, vec); + double hx = toHexX(px, py); + double hy = toHexY(px, py); + pointEvaluator.evalPoint2(seed, x, y, cellHash, cellX, cellY, hx, hy, buffer); + } + + @Override + public void evalPoint2( + int seed, double x, double y, double z, int cellX, int cellY, int cellZ, ResultBuffer.ResultBuffer3d buffer, PointEvaluator pointEvaluator + ) { + throw new UnsupportedOperationException(); + } + + @Override + public void collect( + int originalSeed, + int seed, + int minX, + int minY, + int maxX, + int maxY, + @Nonnull ResultBuffer.Bounds2d bounds, + T ctx, + @Nonnull PointConsumer collector, + @Nonnull PointEvaluator pointEvaluator + ) { + minX--; + minY--; + maxX++; + maxY++; + int height = maxY - minY; + int width = maxX - minX + (height >> 1); + CellJitter jitter = pointEvaluator.getJitter(); + + for (int dy = 0; dy <= height; dy++) { + int cy = minY + dy; + int startX = minX - (dy >> 1); + + for (int dx = 0; dx <= width; dx++) { + int cx = startX + dx; + int cellHash = getHash(seed, cx, cy); + DoubleArray.Double2 vec = HEX_CELL_2D[cellHash & 0xFF]; + double px = jitter.getPointX(cx, vec); + double py = jitter.getPointY(cy, vec); + double hx = toHexX(px, py); + double hy = toHexY(px, py); + if (bounds.contains(hx, hy)) { + hx /= SCALE; + hy /= SCALE; + pointEvaluator.collectPoint(cellHash, cx, cy, hx, hy, ctx, collector); + } + } + } + } + + @Nonnull + @Override + public String toString() { + return "HexCellDistanceFunction{}"; + } + + public static int getHash(int seed, int x, int y) { + return HexCellDistanceFunction.SquirrelHash.hash(seed, x, y); + } + + public static int toGridX(double x, double y) { + return (int)MathUtil.fastRound(X_TO_GRID_X * x + -0.3333333333333333 * y); + } + + public static int toGridY(double x, double y) { + return (int)MathUtil.fastRound(0.6666666666666666 * y); + } + + public static double toHexX(double hx, double hy) { + return X_TO_HEX_X * hx + Y_TO_HEX_X * hy; + } + + public static double toHexY(double hx, double hy) { + return 1.5 * hy; + } + + public static class SquirrelHash { + protected static final int HASH0 = 198491317; + protected static final int BIT_NOISE1 = -1255572915; + protected static final int BIT_NOISE2 = -1255572915; + protected static final int BIT_NOISE3 = -1255572915; + + public SquirrelHash() { + } + + public static int hash(int seed, int x, int y) { + int hash = x + y * 198491317; + hash *= -1255572915; + hash += seed; + hash ^= hash >> 8; + hash -= 1255572915; + hash ^= hash << 8; + hash *= -1255572915; + return hash ^ hash >> 8; + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/MeasurementMode.java b/src/com/hypixel/hytale/procedurallib/logic/cell/MeasurementMode.java new file mode 100644 index 0000000..d69ef4d --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/MeasurementMode.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.procedurallib.logic.cell; + +public enum MeasurementMode { + CENTRE_DISTANCE, + BORDER_DISTANCE; + + private MeasurementMode() { + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/PointDistanceFunction.java b/src/com/hypixel/hytale/procedurallib/logic/cell/PointDistanceFunction.java new file mode 100644 index 0000000..880f755 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/PointDistanceFunction.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.procedurallib.logic.cell; + +public interface PointDistanceFunction { + double distance2D(double var1, double var3); + + double distance3D(double var1, double var3, double var5); + + default double distance2D(int seed, int cellX, int cellY, double cellCentreX, double cellCentreY, double deltaX, double deltaY) { + return this.distance2D(deltaX, deltaY); + } + + default double distance3D( + int seed, int cellX, int cellY, int cellZ, double cellCentreX, double cellCentreY, double cellCentreZ, double deltaX, double deltaY, double deltaZ + ) { + return this.distance3D(deltaX, deltaY, deltaZ); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/BorderPointEvaluator.java b/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/BorderPointEvaluator.java new file mode 100644 index 0000000..27b2ddd --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/BorderPointEvaluator.java @@ -0,0 +1,95 @@ +package com.hypixel.hytale.procedurallib.logic.cell.evaluator; + +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import javax.annotation.Nonnull; + +public class BorderPointEvaluator implements PointEvaluator { + public static final BorderPointEvaluator INSTANCE = new BorderPointEvaluator(); + + public BorderPointEvaluator() { + } + + @Override + public void evalPoint( + int seed, double x, double y, int cellHash, int cellX, int cellY, double cellPointX, double cellPointY, @Nonnull ResultBuffer.ResultBuffer2d buffer + ) { + if (!isOrigin(cellX, cellY, buffer)) { + double distance = getBorderDistance(x, y, buffer.x2, buffer.y2, cellPointX, cellPointY); + if (distance < buffer.distance) { + buffer.distance = distance; + } + } + } + + @Override + public void evalPoint2( + int seed, double x, double y, int cellHash, int cellX, int cellY, double cellPointX, double cellPointY, @Nonnull ResultBuffer.ResultBuffer2d buffer + ) { + if (!isOrigin(cellX, cellY, buffer)) { + double distance = getBorderDistance(x, y, buffer.x2, buffer.y2, cellPointX, cellPointY); + if (distance < buffer.distance) { + buffer.distance2 = buffer.distance; + buffer.distance = distance; + } else if (distance < buffer.distance2) { + buffer.distance2 = distance; + } + } + } + + @Override + public void evalPoint( + int seed, + double x, + double y, + double z, + int cellHash, + int cellX, + int cellY, + int cellZ, + double cellPointX, + double cellPointY, + double cellPointZ, + ResultBuffer.ResultBuffer3d buffer + ) { + throw new UnsupportedOperationException(); + } + + @Override + public void evalPoint2( + int seed, + double x, + double y, + double z, + int cellHash, + int cellX, + int cellY, + int cellZ, + double cellPointX, + double cellPointY, + double cellPointZ, + ResultBuffer.ResultBuffer3d buffer + ) { + throw new UnsupportedOperationException(); + } + + @Nonnull + @Override + public String toString() { + return "BorderPointEvaluator{}"; + } + + protected static boolean isOrigin(int cellX, int cellY, @Nonnull ResultBuffer.ResultBuffer2d buffer) { + return cellX == buffer.ix2 && cellY == buffer.iy2; + } + + protected static double getBorderDistance(double x, double y, double originX, double originY, double cellPointX, double cellPointY) { + double ax = (cellPointX + originX) * 0.5; + double ay = (cellPointY + originY) * 0.5; + double normX = -(cellPointY - originY); + double normY = cellPointX - originX; + double bx = ax + normX; + double by = ay + normY; + return MathUtil.distanceToInfLineSq(x, y, ax, ay, bx, by); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/BranchEvaluator.java b/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/BranchEvaluator.java new file mode 100644 index 0000000..dac22eb --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/BranchEvaluator.java @@ -0,0 +1,142 @@ +package com.hypixel.hytale.procedurallib.logic.cell.evaluator; + +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.procedurallib.logic.DoubleArray; +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import com.hypixel.hytale.procedurallib.logic.cell.CellDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.CellPointFunction; +import com.hypixel.hytale.procedurallib.logic.cell.jitter.CellJitter; +import javax.annotation.Nonnull; + +public class BranchEvaluator implements PointEvaluator { + protected static final int CARDINAL_MASK = 1; + protected static final int CARDINAL_MASK_RESULT_X = 0; + protected static final int CARDINAL_MASK_RESULT_Y = 1; + protected static final int RANDOM_DIRECTION_MASK = 3; + protected static final Vector2i[] RANDOM_DIRECTIONS = new Vector2i[]{new Vector2i(1, 1), new Vector2i(1, -1), new Vector2i(-1, 1), new Vector2i(-1, -1)}; + @Nonnull + protected final CellPointFunction pointFunction; + protected final BranchEvaluator.Direction direction; + protected final CellJitter jitter; + protected final double branch2parentScale; + protected final double invLineNormalization; + + public BranchEvaluator( + @Nonnull CellDistanceFunction parentFunction, + @Nonnull CellPointFunction linePointFunction, + BranchEvaluator.Direction direction, + CellJitter jitter, + double branchScale + ) { + this.pointFunction = linePointFunction; + this.direction = direction; + this.jitter = jitter; + double inverseScalar = 1.0 / linePointFunction.scale(branchScale); + this.branch2parentScale = parentFunction.scale(inverseScalar); + this.invLineNormalization = 1.0 / linePointFunction.normalize(1.0); + } + + @Override + public CellJitter getJitter() { + return this.jitter; + } + + @Override + public void evalPoint(int seed, double x, double y, int hashA, int cax, int cay, double ax, double ay, @Nonnull ResultBuffer.ResultBuffer2d buffer) { + int dx = getConnectionX(this.direction, buffer.ix2, buffer.x2, hashA, ax * this.branch2parentScale); + int dy = getConnectionY(this.direction, buffer.ix2, buffer.y2, hashA, ay * this.branch2parentScale); + int cbx = cax + dx; + int cby = cay + dy; + int hashB = this.pointFunction.getHash(seed, cbx, cby); + DoubleArray.Double2 offsetsB = this.pointFunction.getOffsets(hashB); + double rawBx = this.getJitter().getPointX(cbx, offsetsB); + double rawBy = this.getJitter().getPointY(cby, offsetsB); + double bx = this.pointFunction.getX(rawBx, rawBy); + double by = this.pointFunction.getY(rawBx, rawBy); + if (checkBounds(x, y, ax, ay, bx, by, buffer.distance2)) { + double dist2 = MathUtil.distanceToLineSq(x, y, ax, ay, bx, by); + dist2 *= this.invLineNormalization; + buffer.register(hashA, cax, cay, dist2, ax, ay); + } + } + + @Override + public void evalPoint2(int seed, double x, double y, int cellHash, int xi, int yi, double vecX, double vecY, ResultBuffer.ResultBuffer2d buffer) { + } + + @Override + public void evalPoint( + int seed, + double x, + double y, + double z, + int cellHash, + int cellX, + int cellY, + int cellZ, + double cellPointX, + double cellPointY, + double cellPointZ, + ResultBuffer.ResultBuffer3d buffer + ) { + } + + @Override + public void evalPoint2( + int seed, + double x, + double y, + double z, + int cellHash, + int cellX, + int cellY, + int cellZ, + double cellPointX, + double cellPointY, + double cellPointZ, + ResultBuffer.ResultBuffer3d buffer + ) { + } + + protected static int getConnectionX(BranchEvaluator.Direction direction, int regionHash, double regionCoord, int cellHash, double cellCoord) { + if ((cellHash & 1) != 0) { + return 0; + } else { + return switch (direction) { + case OUTWARD -> cellCoord < regionCoord ? -1 : 1; + case INWARD -> cellCoord > regionCoord ? -1 : 1; + case RANDOM -> RANDOM_DIRECTIONS[regionHash & 3].x; + }; + } + } + + protected static int getConnectionY(BranchEvaluator.Direction direction, int regionHash, double regionCoord, int cellHash, double cellCoord) { + if ((cellHash & 1) != 1) { + return 0; + } else { + return switch (direction) { + case OUTWARD -> cellCoord < regionCoord ? -1 : 1; + case INWARD -> cellCoord > regionCoord ? -1 : 1; + case RANDOM -> RANDOM_DIRECTIONS[regionHash & 3].y; + }; + } + } + + protected static boolean checkBounds(double x, double y, double ax, double ay, double bx, double by, double thickness) { + double minX = Math.min(ax, bx) - thickness; + double minY = Math.min(ay, by) - thickness; + double maxX = Math.max(ax, bx) + thickness; + double maxY = Math.max(ay, by) + thickness; + return x > minX && x < maxX && y > minY && y < maxY; + } + + public static enum Direction { + OUTWARD, + INWARD, + RANDOM; + + private Direction() { + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/DensityPointEvaluator.java b/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/DensityPointEvaluator.java new file mode 100644 index 0000000..4a2fbf8 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/DensityPointEvaluator.java @@ -0,0 +1,109 @@ +package com.hypixel.hytale.procedurallib.logic.cell.evaluator; + +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.procedurallib.condition.IDoubleCondition; +import com.hypixel.hytale.procedurallib.condition.IIntCondition; +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import com.hypixel.hytale.procedurallib.logic.cell.jitter.CellJitter; +import com.hypixel.hytale.procedurallib.logic.point.PointConsumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DensityPointEvaluator implements PointEvaluator { + protected final PointEvaluator pointEvaluator; + protected final IIntCondition density; + + public DensityPointEvaluator(PointEvaluator pointEvaluator, IDoubleCondition density) { + this(pointEvaluator, getDensityCondition(density)); + } + + public DensityPointEvaluator(PointEvaluator pointEvaluator, IIntCondition density) { + this.pointEvaluator = pointEvaluator; + this.density = density; + } + + @Override + public CellJitter getJitter() { + return this.pointEvaluator.getJitter(); + } + + @Override + public void evalPoint( + int seed, double x, double y, int cellHash, int cellX, int cellY, double cellPointX, double cellPointY, ResultBuffer.ResultBuffer2d buffer + ) { + if (this.density.eval(cellHash)) { + this.pointEvaluator.evalPoint(seed, x, y, cellHash, cellX, cellY, cellPointX, cellPointY, buffer); + } + } + + @Override + public void evalPoint2( + int seed, double x, double y, int cellHash, int cellX, int cellY, double cellPointX, double cellPointY, ResultBuffer.ResultBuffer2d buffer + ) { + if (this.density.eval(cellHash)) { + this.pointEvaluator.evalPoint2(seed, x, y, cellHash, cellX, cellY, cellPointX, cellPointY, buffer); + } + } + + @Override + public void evalPoint( + int seed, + double x, + double y, + double z, + int cellHash, + int cellX, + int cellY, + int cellZ, + double cellPointX, + double cellPointY, + double cellPointZ, + ResultBuffer.ResultBuffer3d buffer + ) { + if (this.density.eval(cellHash)) { + this.pointEvaluator.evalPoint(seed, x, y, z, cellHash, cellX, cellY, cellZ, cellPointX, cellPointY, cellPointZ, buffer); + } + } + + @Override + public void evalPoint2( + int seed, + double x, + double y, + double z, + int cellHash, + int cellX, + int cellY, + int cellZ, + double cellPointX, + double cellPointY, + double cellPointZ, + ResultBuffer.ResultBuffer3d buffer + ) { + if (this.density.eval(cellHash)) { + this.pointEvaluator.evalPoint2(seed, x, y, z, cellHash, cellX, cellY, cellZ, cellPointX, cellPointY, cellPointZ, buffer); + } + } + + @Override + public void collectPoint(int cellHash, int cellX, int cellY, double x, double y, T t, @Nonnull PointConsumer consumer) { + if (this.density.eval(cellHash)) { + this.pointEvaluator.collectPoint(cellHash, cellX, cellY, x, y, t, consumer); + } + } + + @Nonnull + @Override + public String toString() { + return "DensityPointEvaluator{pointEvaluator=" + this.pointEvaluator + ", density=" + this.density + "}"; + } + + @Nonnull + public static IIntCondition getDensityCondition(@Nullable IDoubleCondition threshold) { + return threshold == null ? seed -> true : seed -> threshold.eval(randomDensityCondition(seed)); + } + + public static double randomDensityCondition(int seed) { + return HashUtil.random(seed, -1694747730L); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/DistancePointEvaluator.java b/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/DistancePointEvaluator.java new file mode 100644 index 0000000..c261a4c --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/DistancePointEvaluator.java @@ -0,0 +1,98 @@ +package com.hypixel.hytale.procedurallib.logic.cell.evaluator; + +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import com.hypixel.hytale.procedurallib.logic.cell.PointDistanceFunction; +import com.hypixel.hytale.procedurallib.supplier.IDoubleRange; +import com.hypixel.hytale.procedurallib.supplier.ISeedDoubleRange; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DistancePointEvaluator implements PointEvaluator { + protected final PointDistanceFunction distanceFunction; + protected final ISeedDoubleRange distanceMod; + + public DistancePointEvaluator(PointDistanceFunction distanceFunction, IDoubleRange distanceMod) { + this(distanceFunction, getDistanceModifier(distanceMod)); + } + + public DistancePointEvaluator(PointDistanceFunction distanceFunction, ISeedDoubleRange distanceMod) { + this.distanceFunction = distanceFunction; + this.distanceMod = distanceMod; + } + + @Override + public void evalPoint( + int seed, double x, double y, int cellHash, int cellX, int cellY, double cellPointX, double cellPointY, @Nonnull ResultBuffer.ResultBuffer2d buffer + ) { + double distance = this.distanceFunction.distance2D(seed, cellX, cellY, cellPointX, cellPointY, cellPointX - x, cellPointY - y); + distance = this.distanceMod.getValue(cellHash, distance); + buffer.register(cellHash, cellX, cellY, distance, cellPointX, cellPointY); + } + + @Override + public void evalPoint2( + int seed, double x, double y, int cellHash, int cellX, int cellY, double cellPointX, double cellPointY, @Nonnull ResultBuffer.ResultBuffer2d buffer + ) { + double distance = this.distanceFunction.distance2D(seed, cellX, cellY, cellPointX, cellPointY, cellPointX - x, cellPointY - y); + distance = this.distanceMod.getValue(cellHash, distance); + buffer.register2(cellHash, cellX, cellY, distance, cellPointX, cellPointY); + } + + @Override + public void evalPoint( + int seed, + double x, + double y, + double z, + int cellHash, + int cellX, + int cellY, + int cellZ, + double cellPointX, + double cellPointY, + double cellPointZ, + @Nonnull ResultBuffer.ResultBuffer3d buffer + ) { + double distance = this.distanceFunction + .distance3D(seed, cellX, cellY, cellZ, cellPointX, cellPointY, cellPointZ, cellPointX - x, cellPointY - y, cellPointZ - z); + distance = this.distanceMod.getValue(cellHash, distance); + buffer.register(cellHash, cellX, cellY, cellZ, distance, cellPointX, cellPointY, cellPointZ); + } + + @Override + public void evalPoint2( + int seed, + double x, + double y, + double z, + int cellHash, + int cellX, + int cellY, + int cellZ, + double cellPointX, + double cellPointY, + double cellPointZ, + @Nonnull ResultBuffer.ResultBuffer3d buffer + ) { + double distance = this.distanceFunction + .distance3D(seed, cellX, cellY, cellZ, cellPointX, cellPointY, cellPointZ, cellPointX - x, cellPointY - y, cellPointZ - z); + distance = this.distanceMod.getValue(cellHash, distance); + buffer.register2(cellHash, cellX, cellY, cellZ, distance, cellPointX, cellPointY, cellPointZ); + } + + @Nonnull + @Override + public String toString() { + return "DistancePointEvaluator{distanceFunction=" + this.distanceFunction + ", distanceMod=" + this.distanceMod + "}"; + } + + @Nonnull + public static ISeedDoubleRange getDistanceModifier(@Nullable IDoubleRange range) { + return range == null ? ISeedDoubleRange.DIRECT : (seed, value) -> value * range.getValue(randomDistanceModification(seed)); + } + + public static double randomDistanceModification(int seed) { + return HashUtil.random(seed, 1495661265L); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/JitterPointEvaluator.java b/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/JitterPointEvaluator.java new file mode 100644 index 0000000..dd17651 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/JitterPointEvaluator.java @@ -0,0 +1,82 @@ +package com.hypixel.hytale.procedurallib.logic.cell.evaluator; + +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import com.hypixel.hytale.procedurallib.logic.cell.jitter.CellJitter; +import com.hypixel.hytale.procedurallib.logic.point.PointConsumer; +import javax.annotation.Nonnull; + +public class JitterPointEvaluator implements PointEvaluator { + protected final PointEvaluator pointEvaluator; + protected final CellJitter jitter; + + public JitterPointEvaluator(PointEvaluator pointEvaluator, CellJitter jitter) { + this.pointEvaluator = pointEvaluator; + this.jitter = jitter; + } + + @Override + public CellJitter getJitter() { + return this.jitter; + } + + @Override + public void evalPoint( + int seed, double x, double y, int cellHash, int cellX, int cellY, double cellPointX, double cellPointY, ResultBuffer.ResultBuffer2d buffer + ) { + this.pointEvaluator.evalPoint(seed, x, y, cellHash, cellX, cellY, cellPointX, cellPointY, buffer); + } + + @Override + public void evalPoint2( + int seed, double x, double y, int cellHash, int cellX, int cellY, double cellPointX, double cellPointY, ResultBuffer.ResultBuffer2d buffer + ) { + this.pointEvaluator.evalPoint2(seed, x, y, cellHash, cellX, cellY, cellPointX, cellPointY, buffer); + } + + @Override + public void evalPoint( + int seed, + double x, + double y, + double z, + int cellHash, + int cellX, + int cellY, + int cellZ, + double cellPointX, + double cellPointY, + double cellPointZ, + ResultBuffer.ResultBuffer3d buffer + ) { + this.pointEvaluator.evalPoint(seed, x, y, z, cellHash, cellX, cellY, cellZ, cellPointX, cellPointY, cellPointZ, buffer); + } + + @Override + public void evalPoint2( + int seed, + double x, + double y, + double z, + int cellHash, + int cellX, + int cellY, + int cellZ, + double cellPointX, + double cellPointY, + double cellPointZ, + ResultBuffer.ResultBuffer3d buffer + ) { + this.pointEvaluator.evalPoint2(seed, x, y, z, cellHash, cellX, cellY, cellZ, cellPointX, cellPointY, cellPointZ, buffer); + } + + @Override + public void collectPoint(int cellHash, int cellX, int cellY, double cellCentreX, double cellCentreY, T ctx, @Nonnull PointConsumer consumer) { + this.pointEvaluator.collectPoint(cellHash, cellX, cellY, cellCentreX, cellCentreY, ctx, consumer); + } + + @Nonnull + @Override + public String toString() { + return "JitterPointEvaluator{pointEvaluator=" + this.pointEvaluator + ", jitter=" + this.jitter + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/NormalPointEvaluator.java b/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/NormalPointEvaluator.java new file mode 100644 index 0000000..9471f63 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/NormalPointEvaluator.java @@ -0,0 +1,94 @@ +package com.hypixel.hytale.procedurallib.logic.cell.evaluator; + +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import com.hypixel.hytale.procedurallib.logic.cell.DistanceCalculationMode; +import com.hypixel.hytale.procedurallib.logic.cell.PointDistanceFunction; +import javax.annotation.Nonnull; + +public class NormalPointEvaluator implements PointEvaluator { + public static final PointEvaluator EUCLIDEAN = new NormalPointEvaluator(DistanceCalculationMode.EUCLIDEAN.getFunction()); + public static final PointEvaluator MANHATTAN = new NormalPointEvaluator(DistanceCalculationMode.MANHATTAN.getFunction()); + public static final PointEvaluator NATURAL = new NormalPointEvaluator(DistanceCalculationMode.NATURAL.getFunction()); + public static final PointEvaluator MAX = new NormalPointEvaluator(DistanceCalculationMode.MAX.getFunction()); + protected final PointDistanceFunction distanceFunction; + + public NormalPointEvaluator(PointDistanceFunction distanceFunction) { + this.distanceFunction = distanceFunction; + } + + @Override + public void evalPoint( + int seed, double x, double y, int cellHash, int cellX, int cellY, double cellPointX, double cellPointY, @Nonnull ResultBuffer.ResultBuffer2d buffer + ) { + double distance = this.distanceFunction.distance2D(seed, cellX, cellY, cellPointX, cellPointY, cellPointX - x, cellPointY - y); + buffer.register(cellHash, cellX, cellY, distance, cellPointX, cellPointY); + } + + @Override + public void evalPoint2( + int seed, double x, double y, int cellHash, int cellX, int cellY, double cellPointX, double cellPointY, @Nonnull ResultBuffer.ResultBuffer2d buffer + ) { + double distance = this.distanceFunction.distance2D(seed, cellX, cellY, cellPointX, cellPointY, cellPointX - x, cellPointY - y); + buffer.register2(cellHash, cellX, cellY, distance, cellPointX, cellPointY); + } + + @Override + public void evalPoint( + int seed, + double x, + double y, + double z, + int cellHash, + int cellX, + int cellY, + int cellZ, + double cellPointX, + double cellPointY, + double cellPointZ, + @Nonnull ResultBuffer.ResultBuffer3d buffer + ) { + double distance = this.distanceFunction + .distance3D(seed, cellX, cellY, cellZ, cellPointX, cellPointY, cellPointZ, cellPointX - x, cellPointY - y, cellPointZ - z); + buffer.register(cellHash, cellX, cellY, cellZ, distance, cellPointX, cellPointY, cellPointZ); + } + + @Override + public void evalPoint2( + int seed, + double x, + double y, + double z, + int cellHash, + int cellX, + int cellY, + int cellZ, + double cellPointX, + double cellPointY, + double cellPointZ, + @Nonnull ResultBuffer.ResultBuffer3d buffer + ) { + double distance = this.distanceFunction + .distance3D(seed, cellX, cellY, cellZ, cellPointX, cellPointY, cellPointZ, cellPointX - x, cellPointY - y, cellPointZ - z); + buffer.register2(cellHash, cellX, cellY, cellZ, distance, cellPointX, cellPointY, cellPointZ); + } + + @Nonnull + @Override + public String toString() { + return "NormalPointEvaluator{distanceFunction=" + this.distanceFunction + "}"; + } + + public static PointEvaluator of(PointDistanceFunction distanceFunction) { + DistanceCalculationMode mode = DistanceCalculationMode.from(distanceFunction); + if (mode == null) { + return new NormalPointEvaluator(distanceFunction); + } else { + return switch (mode) { + case EUCLIDEAN -> EUCLIDEAN; + case MANHATTAN -> MANHATTAN; + case NATURAL -> NATURAL; + case MAX -> MAX; + }; + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/PointEvaluator.java b/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/PointEvaluator.java new file mode 100644 index 0000000..a2d9d4b --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/PointEvaluator.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.procedurallib.logic.cell.evaluator; + +import com.hypixel.hytale.procedurallib.condition.DefaultDoubleCondition; +import com.hypixel.hytale.procedurallib.condition.IDoubleCondition; +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import com.hypixel.hytale.procedurallib.logic.cell.PointDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.jitter.CellJitter; +import com.hypixel.hytale.procedurallib.logic.cell.jitter.DefaultCellJitter; +import com.hypixel.hytale.procedurallib.logic.point.PointConsumer; +import com.hypixel.hytale.procedurallib.supplier.IDoubleRange; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface PointEvaluator { + default CellJitter getJitter() { + return DefaultCellJitter.DEFAULT_ONE; + } + + void evalPoint(int var1, double var2, double var4, int var6, int var7, int var8, double var9, double var11, ResultBuffer.ResultBuffer2d var13); + + void evalPoint2(int var1, double var2, double var4, int var6, int var7, int var8, double var9, double var11, ResultBuffer.ResultBuffer2d var13); + + void evalPoint( + int var1, + double var2, + double var4, + double var6, + int var8, + int var9, + int var10, + int var11, + double var12, + double var14, + double var16, + ResultBuffer.ResultBuffer3d var18 + ); + + void evalPoint2( + int var1, + double var2, + double var4, + double var6, + int var8, + int var9, + int var10, + int var11, + double var12, + double var14, + double var16, + ResultBuffer.ResultBuffer3d var18 + ); + + default void collectPoint(int cellHash, int cellX, int cellY, double cellCentreX, double cellCentreY, T ctx, @Nonnull PointConsumer consumer) { + consumer.accept(cellCentreX, cellCentreY, ctx); + } + + static PointEvaluator of(PointDistanceFunction distanceFunction, @Nullable IDoubleCondition density, @Nullable IDoubleRange distanceMod, CellJitter jitter) { + return of(distanceFunction, density, distanceMod, 0, SkipCellPointEvaluator.DEFAULT_MODE, jitter); + } + + static PointEvaluator of( + PointDistanceFunction distanceFunction, + @Nullable IDoubleCondition density, + @Nullable IDoubleRange distanceMod, + int skipCount, + @Nonnull SkipCellPointEvaluator.Mode skipMode, + CellJitter jitter + ) { + PointEvaluator pointEvaluator = NormalPointEvaluator.of(distanceFunction); + if (distanceMod != null) { + pointEvaluator = new DistancePointEvaluator(distanceFunction, distanceMod); + } + + if (density != null && density != DefaultDoubleCondition.DEFAULT_TRUE) { + pointEvaluator = new DensityPointEvaluator(pointEvaluator, density); + } + + if (skipCount > 0) { + pointEvaluator = new SkipCellPointEvaluator(pointEvaluator, skipMode, skipCount); + } + + if (jitter != DefaultCellJitter.DEFAULT_ONE) { + pointEvaluator = new JitterPointEvaluator(pointEvaluator, jitter); + } + + return pointEvaluator; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/SkipCellPointEvaluator.java b/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/SkipCellPointEvaluator.java new file mode 100644 index 0000000..b22c79f --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/evaluator/SkipCellPointEvaluator.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.procedurallib.logic.cell.evaluator; + +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import com.hypixel.hytale.procedurallib.logic.cell.jitter.CellJitter; +import com.hypixel.hytale.procedurallib.logic.point.PointConsumer; +import it.unimi.dsi.fastutil.HashCommon; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class SkipCellPointEvaluator implements PointEvaluator { + @Nonnull + protected final PointEvaluator pointEvaluator; + @Nonnull + protected final SkipCellPointEvaluator.Mode mode; + protected final int mask; + protected final int mid; + public static final int DEFAULT_NO_SKIP = 0; + public static final SkipCellPointEvaluator.Mode DEFAULT_MODE = SkipCellPointEvaluator.Mode.CHECKERBOARD; + + public SkipCellPointEvaluator(@Nonnull PointEvaluator pointEvaluator, @Nonnull SkipCellPointEvaluator.Mode mode, int period) { + int interval = HashCommon.nextPowerOfTwo(Math.max(0, period) + 1); + this.pointEvaluator = pointEvaluator; + this.mode = mode; + this.mask = interval - 1; + this.mid = interval >> 1; + } + + @Override + public CellJitter getJitter() { + return this.pointEvaluator.getJitter(); + } + + @Override + public void evalPoint( + int seed, double x, double y, int cellHash, int cellX, int cellY, double cellPointX, double cellPointY, ResultBuffer.ResultBuffer2d buffer + ) { + if (!this.skip(this.mode, cellX, cellY)) { + this.pointEvaluator.evalPoint(seed, x, y, cellHash, cellX, cellY, cellPointX, cellPointY, buffer); + } + } + + @Override + public void evalPoint2( + int seed, double x, double y, int cellHash, int cellX, int cellY, double cellPointX, double cellPointY, ResultBuffer.ResultBuffer2d buffer + ) { + if (!this.skip(this.mode, cellX, cellY)) { + this.pointEvaluator.evalPoint2(seed, x, y, cellHash, cellX, cellY, cellPointX, cellPointY, buffer); + } + } + + @Override + public void evalPoint( + int seed, + double x, + double y, + double z, + int cellHash, + int cellX, + int cellY, + int cellZ, + double cellPointX, + double cellPointY, + double cellPointZ, + ResultBuffer.ResultBuffer3d buffer + ) { + } + + @Override + public void evalPoint2( + int seed, + double x, + double y, + double z, + int cellHash, + int cellX, + int cellY, + int cellZ, + double cellPointX, + double cellPointY, + double cellPointZ, + ResultBuffer.ResultBuffer3d buffer + ) { + } + + @Override + public void collectPoint(int cellHash, int cellX, int cellY, double cellCentreX, double cellCentreY, T ctx, @NonNullDecl PointConsumer consumer) { + if (!this.skip(this.mode, cellX, cellY)) { + this.pointEvaluator.collectPoint(cellHash, cellX, cellY, cellCentreX, cellCentreY, ctx, consumer); + } + } + + protected boolean skip(SkipCellPointEvaluator.Mode mode, int cx, int cy) { + int x0 = cx & this.mask; + int y0 = cy & this.mask; + boolean result = x0 == 0 && y0 == 0 || mode == SkipCellPointEvaluator.Mode.CHECKERBOARD && x0 == this.mid && y0 == this.mid; + return !result; + } + + public static enum Mode { + CHECKERBOARD, + GRID; + + private Mode() { + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/jitter/CellJitter.java b/src/com/hypixel/hytale/procedurallib/logic/cell/jitter/CellJitter.java new file mode 100644 index 0000000..b0a7503 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/jitter/CellJitter.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.procedurallib.logic.cell.jitter; + +import com.hypixel.hytale.procedurallib.logic.DoubleArray; +import javax.annotation.Nonnull; + +public interface CellJitter { + double getMaxX(); + + double getMaxY(); + + double getMaxZ(); + + double getPointX(int var1, DoubleArray.Double2 var2); + + double getPointY(int var1, DoubleArray.Double2 var2); + + double getPointX(int var1, DoubleArray.Double3 var2); + + double getPointY(int var1, DoubleArray.Double3 var2); + + double getPointZ(int var1, DoubleArray.Double3 var2); + + @Nonnull + static CellJitter of(double x, double y, double z) { + return (CellJitter)(x == 1.0 && y == 1.0 && z == 1.0 ? DefaultCellJitter.DEFAULT_ONE : new ConstantCellJitter(x, y, z)); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/jitter/ConstantCellJitter.java b/src/com/hypixel/hytale/procedurallib/logic/cell/jitter/ConstantCellJitter.java new file mode 100644 index 0000000..f46bd74 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/jitter/ConstantCellJitter.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.procedurallib.logic.cell.jitter; + +import com.hypixel.hytale.procedurallib.logic.DoubleArray; +import javax.annotation.Nonnull; + +public class ConstantCellJitter implements CellJitter { + protected final double jitterX; + protected final double jitterY; + protected final double jitterZ; + + public ConstantCellJitter(double jitterX, double jitterY, double jitterZ) { + this.jitterX = jitterX; + this.jitterY = jitterY; + this.jitterZ = jitterZ; + } + + @Override + public double getMaxX() { + return this.jitterX; + } + + @Override + public double getMaxY() { + return this.jitterY; + } + + @Override + public double getMaxZ() { + return this.jitterZ; + } + + @Override + public double getPointX(int cx, @Nonnull DoubleArray.Double2 vec) { + return cx + vec.x * this.jitterX; + } + + @Override + public double getPointY(int cy, @Nonnull DoubleArray.Double2 vec) { + return cy + vec.y * this.jitterY; + } + + @Override + public double getPointX(int cx, @Nonnull DoubleArray.Double3 vec) { + return cx + vec.x * this.jitterX; + } + + @Override + public double getPointY(int cy, @Nonnull DoubleArray.Double3 vec) { + return cy + vec.y * this.jitterY; + } + + @Override + public double getPointZ(int cz, @Nonnull DoubleArray.Double3 vec) { + return cz + vec.z * this.jitterZ; + } + + @Nonnull + @Override + public String toString() { + return "ConstantCellJitter{jitterX=" + this.jitterX + ", jitterY=" + this.jitterY + ", jitterZ=" + this.jitterZ + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/cell/jitter/DefaultCellJitter.java b/src/com/hypixel/hytale/procedurallib/logic/cell/jitter/DefaultCellJitter.java new file mode 100644 index 0000000..9ce5331 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/cell/jitter/DefaultCellJitter.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.procedurallib.logic.cell.jitter; + +import com.hypixel.hytale.procedurallib.logic.DoubleArray; +import javax.annotation.Nonnull; + +public class DefaultCellJitter implements CellJitter { + public static final CellJitter DEFAULT_ONE = new DefaultCellJitter(); + + public DefaultCellJitter() { + } + + @Override + public double getMaxX() { + return 1.0; + } + + @Override + public double getMaxY() { + return 1.0; + } + + @Override + public double getMaxZ() { + return 1.0; + } + + @Override + public double getPointX(int cx, @Nonnull DoubleArray.Double2 vec) { + return cx + vec.x; + } + + @Override + public double getPointY(int cy, @Nonnull DoubleArray.Double2 vec) { + return cy + vec.y; + } + + @Override + public double getPointX(int cx, @Nonnull DoubleArray.Double3 vec) { + return cx + vec.x; + } + + @Override + public double getPointY(int cy, @Nonnull DoubleArray.Double3 vec) { + return cy + vec.y; + } + + @Override + public double getPointZ(int cz, @Nonnull DoubleArray.Double3 vec) { + return cz + vec.z; + } + + @Nonnull + @Override + public String toString() { + return "DefaultCellJitter{}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/point/DistortedPointGenerator.java b/src/com/hypixel/hytale/procedurallib/logic/point/DistortedPointGenerator.java new file mode 100644 index 0000000..314f587 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/point/DistortedPointGenerator.java @@ -0,0 +1,82 @@ +package com.hypixel.hytale.procedurallib.logic.point; + +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import com.hypixel.hytale.procedurallib.random.ICoordinateRandomizer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DistortedPointGenerator implements IPointGenerator { + protected final IPointGenerator pointGenerator; + protected final ICoordinateRandomizer coordinateRandomizer; + + public DistortedPointGenerator(IPointGenerator pointGenerator, ICoordinateRandomizer coordinateRandomizer) { + this.pointGenerator = pointGenerator; + this.coordinateRandomizer = coordinateRandomizer; + } + + @Override + public ResultBuffer.ResultBuffer2d nearest2D(int seed, double x, double y) { + return this.pointGenerator.nearest2D(seed, this.coordinateRandomizer.randomDoubleX(seed, x, y), this.coordinateRandomizer.randomDoubleY(seed, x, y)); + } + + @Override + public ResultBuffer.ResultBuffer3d nearest3D(int seed, double x, double y, double z) { + return this.pointGenerator + .nearest3D( + seed, + this.coordinateRandomizer.randomDoubleX(seed, x, y, z), + this.coordinateRandomizer.randomDoubleY(seed, x, y, z), + this.coordinateRandomizer.randomDoubleZ(seed, x, y, z) + ); + } + + @Override + public ResultBuffer.ResultBuffer2d transition2D(int seed, double x, double y) { + return this.pointGenerator.transition2D(seed, this.coordinateRandomizer.randomDoubleX(seed, x, y), this.coordinateRandomizer.randomDoubleY(seed, x, y)); + } + + @Override + public ResultBuffer.ResultBuffer3d transition3D(int seed, double x, double y, double z) { + return this.pointGenerator + .transition3D( + seed, + this.coordinateRandomizer.randomDoubleX(seed, x, y, z), + this.coordinateRandomizer.randomDoubleY(seed, x, y, z), + this.coordinateRandomizer.randomDoubleZ(seed, x, y, z) + ); + } + + @Override + public double getInterval() { + return this.pointGenerator.getInterval(); + } + + @Override + public void collect(int seed, double minX, double minY, double maxX, double maxY, IPointGenerator.PointConsumer2d consumer) { + this.pointGenerator.collect(seed, minX, minY, maxX, maxY, consumer); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DistortedPointGenerator that = (DistortedPointGenerator)o; + return !this.pointGenerator.equals(that.pointGenerator) ? false : this.coordinateRandomizer.equals(that.coordinateRandomizer); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.pointGenerator.hashCode(); + return 31 * result + this.coordinateRandomizer.hashCode(); + } + + @Nonnull + @Override + public String toString() { + return "DistortedPointGenerator{pointGenerator=" + this.pointGenerator + ", coordinateRandomizer=" + this.coordinateRandomizer + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/point/IPointGenerator.java b/src/com/hypixel/hytale/procedurallib/logic/point/IPointGenerator.java new file mode 100644 index 0000000..e4bd8a7 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/point/IPointGenerator.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.procedurallib.logic.point; + +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; + +public interface IPointGenerator { + ResultBuffer.ResultBuffer2d nearest2D(int var1, double var2, double var4); + + ResultBuffer.ResultBuffer3d nearest3D(int var1, double var2, double var4, double var6); + + ResultBuffer.ResultBuffer2d transition2D(int var1, double var2, double var4); + + ResultBuffer.ResultBuffer3d transition3D(int var1, double var2, double var4, double var6); + + void collect(int var1, double var2, double var4, double var6, double var8, IPointGenerator.PointConsumer2d var10); + + double getInterval(); + + @FunctionalInterface + public interface PointConsumer2d { + void accept(double var1, double var3); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/point/OffsetPointGenerator.java b/src/com/hypixel/hytale/procedurallib/logic/point/OffsetPointGenerator.java new file mode 100644 index 0000000..857541a --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/point/OffsetPointGenerator.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.procedurallib.logic.point; + +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import javax.annotation.Nonnull; + +public class OffsetPointGenerator implements IPointGenerator { + private final IPointGenerator generator; + private final double offsetX; + private final double offsetY; + private final double offsetZ; + + public OffsetPointGenerator(IPointGenerator generator, double offsetX, double offsetY, double offsetZ) { + this.generator = generator; + this.offsetX = offsetX; + this.offsetY = offsetY; + this.offsetZ = offsetZ; + } + + public double getOffsetX() { + return this.offsetX; + } + + public double getOffsetY() { + return this.offsetY; + } + + public double getOffsetZ() { + return this.offsetZ; + } + + @Override + public ResultBuffer.ResultBuffer2d nearest2D(int seed, double x, double y) { + return this.generator.nearest2D(seed, x + this.offsetX, y + this.offsetY); + } + + @Override + public ResultBuffer.ResultBuffer3d nearest3D(int seed, double x, double y, double z) { + return this.generator.nearest3D(seed, x + this.offsetX, y + this.offsetY, z + this.offsetZ); + } + + @Override + public ResultBuffer.ResultBuffer2d transition2D(int seed, double x, double y) { + return this.generator.transition2D(seed, x + this.offsetX, y + this.offsetY); + } + + @Override + public ResultBuffer.ResultBuffer3d transition3D(int seed, double x, double y, double z) { + return this.generator.transition3D(seed, x + this.offsetX, y + this.offsetY, z + this.offsetZ); + } + + @Override + public void collect(int seed, double minX, double minY, double maxX, double maxY, @Nonnull IPointGenerator.PointConsumer2d consumer) { + this.generator.collect(seed, minX, minY, maxX, maxY, (x, y) -> consumer.accept(x + this.offsetX, y + this.offsetY)); + } + + @Override + public double getInterval() { + return this.generator.getInterval(); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/point/PointConsumer.java b/src/com/hypixel/hytale/procedurallib/logic/point/PointConsumer.java new file mode 100644 index 0000000..8b7da64 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/point/PointConsumer.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.procedurallib.logic.point; + +public interface PointConsumer { + void accept(double var1, double var3, T var5); +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/point/PointGenerator.java b/src/com/hypixel/hytale/procedurallib/logic/point/PointGenerator.java new file mode 100644 index 0000000..f1fc49a --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/point/PointGenerator.java @@ -0,0 +1,159 @@ +package com.hypixel.hytale.procedurallib.logic.point; + +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import com.hypixel.hytale.procedurallib.logic.cell.CellDistanceFunction; +import com.hypixel.hytale.procedurallib.logic.cell.evaluator.PointEvaluator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PointGenerator implements IPointGenerator { + protected final int seedOffset; + protected final CellDistanceFunction cellDistanceFunction; + protected final PointEvaluator pointEvaluator; + + public PointGenerator(int seedOffset, CellDistanceFunction cellDistanceFunction, PointEvaluator pointEvaluator) { + this.seedOffset = seedOffset; + this.cellDistanceFunction = cellDistanceFunction; + this.pointEvaluator = pointEvaluator; + } + + @Nonnull + protected ResultBuffer.Bounds2d localBounds2d() { + return ResultBuffer.bounds2d; + } + + @Nonnull + protected ResultBuffer.ResultBuffer2d localBuffer2d() { + return ResultBuffer.buffer2d; + } + + @Nonnull + protected ResultBuffer.ResultBuffer3d localBuffer3d() { + return ResultBuffer.buffer3d; + } + + @Nonnull + @Override + public ResultBuffer.ResultBuffer2d nearest2D(int seed, double x, double y) { + x = this.cellDistanceFunction.scale(x); + y = this.cellDistanceFunction.scale(y); + int xr = this.cellDistanceFunction.getCellX(x, y); + int yr = this.cellDistanceFunction.getCellY(x, y); + ResultBuffer.ResultBuffer2d buffer = this.localBuffer2d(); + buffer.distance = Double.POSITIVE_INFINITY; + this.cellDistanceFunction.nearest2D(seed + this.seedOffset, x, y, xr, yr, buffer, this.pointEvaluator); + return buffer; + } + + @Nonnull + @Override + public ResultBuffer.ResultBuffer3d nearest3D(int seed, double x, double y, double z) { + x = this.cellDistanceFunction.scale(x); + y = this.cellDistanceFunction.scale(y); + z = this.cellDistanceFunction.scale(z); + int xr = this.cellDistanceFunction.getCellX(x, y, z); + int yr = this.cellDistanceFunction.getCellY(x, y, z); + int zr = this.cellDistanceFunction.getCellZ(x, y, z); + ResultBuffer.ResultBuffer3d buffer = this.localBuffer3d(); + buffer.distance = Double.POSITIVE_INFINITY; + this.cellDistanceFunction.nearest3D(seed + this.seedOffset, x, y, z, xr, yr, zr, buffer, this.pointEvaluator); + return buffer; + } + + @Nonnull + @Override + public ResultBuffer.ResultBuffer2d transition2D(int seed, double x, double y) { + x = this.cellDistanceFunction.scale(x); + y = this.cellDistanceFunction.scale(y); + int xr = this.cellDistanceFunction.getCellX(x, y); + int yr = this.cellDistanceFunction.getCellY(x, y); + ResultBuffer.ResultBuffer2d buffer = this.localBuffer2d(); + buffer.distance = Double.POSITIVE_INFINITY; + buffer.distance2 = Double.POSITIVE_INFINITY; + this.cellDistanceFunction.transition2D(seed + this.seedOffset, x, y, xr, yr, buffer, this.pointEvaluator); + return buffer; + } + + @Nonnull + @Override + public ResultBuffer.ResultBuffer3d transition3D(int seed, double x, double y, double z) { + x = this.cellDistanceFunction.scale(x); + y = this.cellDistanceFunction.scale(y); + z = this.cellDistanceFunction.scale(z); + int xr = this.cellDistanceFunction.getCellX(x, y, z); + int yr = this.cellDistanceFunction.getCellY(x, y, z); + int zr = this.cellDistanceFunction.getCellZ(x, y, z); + ResultBuffer.ResultBuffer3d buffer = this.localBuffer3d(); + buffer.distance = Double.POSITIVE_INFINITY; + buffer.distance2 = Double.POSITIVE_INFINITY; + this.cellDistanceFunction.transition3D(seed + this.seedOffset, x, y, z, xr, yr, zr, buffer, this.pointEvaluator); + return buffer; + } + + @Override + public double getInterval() { + return 1.0; + } + + @Override + public void collect(int seed, double minX, double minY, double maxX, double maxY, IPointGenerator.PointConsumer2d consumer) { + this.collect0(seed, minX, minY, maxX, maxY, (x, y, t) -> t.accept(x, y), consumer); + } + + public void collect0( + int seed, + double minX, + double minY, + double maxX, + double maxY, + PointConsumer pointConsumer, + IPointGenerator.PointConsumer2d consumer + ) { + minX = this.cellDistanceFunction.scale(minX); + minY = this.cellDistanceFunction.scale(minY); + maxX = this.cellDistanceFunction.scale(maxX); + maxY = this.cellDistanceFunction.scale(maxY); + int x0 = this.cellDistanceFunction.getCellX(minX, minY); + int y0 = this.cellDistanceFunction.getCellY(minX, minY); + int x1 = this.cellDistanceFunction.getCellX(maxX, maxY); + int y1 = this.cellDistanceFunction.getCellY(maxX, maxY); + ResultBuffer.Bounds2d bounds = this.localBounds2d(); + bounds.assign(minX, minY, maxX, maxY); + this.cellDistanceFunction.collect(seed, seed + this.seedOffset, x0, y0, x1, y1, bounds, consumer, pointConsumer, this.pointEvaluator); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + PointGenerator that = (PointGenerator)o; + if (this.seedOffset != that.seedOffset) { + return false; + } else { + return !this.cellDistanceFunction.equals(that.cellDistanceFunction) ? false : this.pointEvaluator.equals(that.pointEvaluator); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.seedOffset; + result = 31 * result + this.cellDistanceFunction.hashCode(); + return 31 * result + this.pointEvaluator.hashCode(); + } + + @Nonnull + @Override + public String toString() { + return "PointGenerator{seedOffset=" + + this.seedOffset + + ", cellDistanceFunction=" + + this.cellDistanceFunction + + ", pointEvaluator=" + + this.pointEvaluator + + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/logic/point/ScaledPointGenerator.java b/src/com/hypixel/hytale/procedurallib/logic/point/ScaledPointGenerator.java new file mode 100644 index 0000000..a9acc56 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/logic/point/ScaledPointGenerator.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.procedurallib.logic.point; + +import com.hypixel.hytale.procedurallib.logic.ResultBuffer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ScaledPointGenerator implements IPointGenerator { + protected final PointGenerator pointGenerator; + protected final double scale; + + public ScaledPointGenerator(PointGenerator pointGenerator, double scale) { + this.pointGenerator = pointGenerator; + this.scale = scale; + } + + @Nonnull + @Override + public ResultBuffer.ResultBuffer2d nearest2D(int seed, double x, double y) { + ResultBuffer.ResultBuffer2d buf = this.pointGenerator.nearest2D(seed, x * this.scale, y * this.scale); + buf.x = buf.x / this.scale; + buf.y = buf.y / this.scale; + buf.distance = Math.sqrt(buf.distance) / this.scale; + return buf; + } + + @Nonnull + @Override + public ResultBuffer.ResultBuffer3d nearest3D(int seed, double x, double y, double z) { + ResultBuffer.ResultBuffer3d buf = this.pointGenerator.nearest3D(seed, x * this.scale, y * this.scale, z * this.scale); + buf.x = buf.x / this.scale; + buf.y = buf.y / this.scale; + buf.z = buf.z / this.scale; + buf.distance = Math.sqrt(buf.distance) / this.scale; + return buf; + } + + @Nonnull + @Override + public ResultBuffer.ResultBuffer2d transition2D(int seed, double x, double y) { + ResultBuffer.ResultBuffer2d buf = this.pointGenerator.transition2D(seed, x * this.scale, y * this.scale); + buf.x = buf.x / this.scale; + buf.x2 = buf.x2 / this.scale; + buf.y = buf.y / this.scale; + buf.y2 = buf.y2 / this.scale; + buf.distance = Math.sqrt(buf.distance) / this.scale; + buf.distance2 = Math.sqrt(buf.distance2) / this.scale; + return buf; + } + + @Nonnull + @Override + public ResultBuffer.ResultBuffer3d transition3D(int seed, double x, double y, double z) { + ResultBuffer.ResultBuffer3d buf = this.pointGenerator.transition3D(seed, x * this.scale, y * this.scale, z * this.scale); + buf.x = buf.x / this.scale; + buf.x2 = buf.x2 / this.scale; + buf.y = buf.y / this.scale; + buf.y2 = buf.y2 / this.scale; + buf.z = buf.z / this.scale; + buf.z2 = buf.z2 / this.scale; + buf.distance = Math.sqrt(buf.distance) / this.scale; + buf.distance2 = Math.sqrt(buf.distance2) / this.scale; + return buf; + } + + @Override + public double getInterval() { + return this.pointGenerator.getInterval() / this.scale; + } + + @Override + public void collect(int seed, double minX, double minY, double maxX, double maxY, IPointGenerator.PointConsumer2d consumer) { + minX *= this.scale; + minY *= this.scale; + maxX *= this.scale; + maxY *= this.scale; + this.pointGenerator.collect0(seed, minX, minY, maxX, maxY, (x, y, t) -> t.accept(x / this.scale, y / this.scale), consumer); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + ScaledPointGenerator that = (ScaledPointGenerator)o; + return Double.compare(that.scale, this.scale) != 0 ? false : this.pointGenerator.equals(that.pointGenerator); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.pointGenerator.hashCode(); + long temp = Double.doubleToLongBits(this.scale); + return 31 * result + (int)(temp ^ temp >>> 32); + } + + @Nonnull + @Override + public String toString() { + return "ScaledPointGenerator{pointGenerator=" + this.pointGenerator + ", scale=" + this.scale + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/property/BlendNoiseProperty.java b/src/com/hypixel/hytale/procedurallib/property/BlendNoiseProperty.java new file mode 100644 index 0000000..41002e7 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/BlendNoiseProperty.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.procedurallib.property; + +import com.hypixel.hytale.math.util.MathUtil; + +public class BlendNoiseProperty implements NoiseProperty { + private final NoiseProperty alpha; + private final NoiseProperty[] noises; + private final double[] thresholds; + private final transient double[] normalize; + + public BlendNoiseProperty(NoiseProperty alpha, NoiseProperty[] noises, double[] thresholds) { + this.alpha = alpha; + this.noises = noises; + this.thresholds = thresholds; + this.normalize = new double[thresholds.length]; + + for (int i = 1; i < thresholds.length; i++) { + if (thresholds[i] <= thresholds[i - 1]) { + throw new IllegalStateException("Thresholds must be in ascending order"); + } + + this.normalize[i] = 1.0 / (thresholds[i] - thresholds[i - 1]); + } + } + + @Override + public double get(int seed, double x, double y) { + if (this.noises.length == 0) { + return 0.0; + } else { + double alpha = this.alpha.get(seed, x, y); + if (alpha <= this.thresholds[0]) { + return this.noises[0].get(seed, x, y); + } else if (alpha >= this.thresholds[this.thresholds.length - 1]) { + return this.noises[this.noises.length - 1].get(seed, x, y); + } else { + for (int i = 1; i < this.noises.length; i++) { + if (alpha <= this.thresholds[i]) { + double t = (alpha - this.thresholds[i - 1]) * this.normalize[i]; + double lower = this.noises[i - 1].get(seed, x, y); + double upper = this.noises[i].get(seed, x, y); + return MathUtil.lerp(lower, upper, t); + } + } + + return 0.0; + } + } + } + + @Override + public double get(int seed, double x, double y, double z) { + if (this.noises.length == 0) { + return 0.0; + } else { + double alpha = this.alpha.get(seed, x, y, z); + if (alpha <= this.thresholds[0]) { + return this.noises[0].get(seed, x, y, z); + } else if (alpha >= this.thresholds[this.thresholds.length - 1]) { + return this.noises[this.noises.length - 1].get(seed, x, y, z); + } else { + for (int i = 1; i < this.noises.length; i++) { + if (alpha <= this.thresholds[i]) { + double t = (alpha - this.thresholds[i - 1]) * this.normalize[i]; + double lower = this.noises[i - 1].get(seed, x, y, z); + double upper = this.noises[i + 0].get(seed, x, y, z); + return MathUtil.lerp(lower, upper, t); + } + } + + return 0.0; + } + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/property/CurveNoiseProperty.java b/src/com/hypixel/hytale/procedurallib/property/CurveNoiseProperty.java new file mode 100644 index 0000000..e4f0e23 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/CurveNoiseProperty.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.procedurallib.property; + +import com.hypixel.hytale.math.util.MathUtil; +import java.util.function.DoubleUnaryOperator; +import javax.annotation.Nonnull; + +public class CurveNoiseProperty implements NoiseProperty { + protected final NoiseProperty noise; + protected final DoubleUnaryOperator function; + + public CurveNoiseProperty(NoiseProperty noise, DoubleUnaryOperator function) { + this.noise = noise; + this.function = function; + } + + @Override + public double get(int seed, double x, double y) { + double value = this.noise.get(seed, x, y); + return this.function.applyAsDouble(value); + } + + @Override + public double get(int seed, double x, double y, double z) { + double value = this.noise.get(seed, x, y, z); + return this.function.applyAsDouble(value); + } + + @Nonnull + @Override + public String toString() { + return "CurveNoiseProperty{noise=" + this.noise + ", function=" + this.function + "}"; + } + + public static class PowerCurve implements DoubleUnaryOperator { + protected static final double MAX = 10.0; + protected final double a; + protected final double b; + + public PowerCurve(double a, double b) { + this.a = MathUtil.clamp(a, 0.0, 10.0); + this.b = MathUtil.clamp(b, -this.a, 10.0); + } + + @Override + public double applyAsDouble(double operand) { + operand = 1.0 - operand; + return 1.0 - Math.pow(operand, this.a + this.b * operand); + } + + @Nonnull + @Override + public String toString() { + return "PowerCurve{a=" + this.a + ", b=" + this.b + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/property/DistortedNoiseProperty.java b/src/com/hypixel/hytale/procedurallib/property/DistortedNoiseProperty.java new file mode 100644 index 0000000..69ac227 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/DistortedNoiseProperty.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.procedurallib.property; + +import com.hypixel.hytale.procedurallib.random.ICoordinateRandomizer; +import javax.annotation.Nonnull; + +public class DistortedNoiseProperty implements NoiseProperty { + protected final NoiseProperty noiseProperty; + protected final ICoordinateRandomizer randomizer; + + public DistortedNoiseProperty(NoiseProperty noiseProperty, ICoordinateRandomizer randomizer) { + this.noiseProperty = noiseProperty; + this.randomizer = randomizer; + } + + public NoiseProperty getNoiseProperty() { + return this.noiseProperty; + } + + public ICoordinateRandomizer getRandomizer() { + return this.randomizer; + } + + @Override + public double get(int seed, double x, double y) { + return this.noiseProperty.get(seed, this.randomizer.randomDoubleX(seed, x, y), this.randomizer.randomDoubleY(seed, x, y)); + } + + @Override + public double get(int seed, double x, double y, double z) { + return this.noiseProperty + .get(seed, this.randomizer.randomDoubleX(seed, x, y, z), this.randomizer.randomDoubleY(seed, x, y, z), this.randomizer.randomDoubleZ(seed, x, y, z)); + } + + @Nonnull + @Override + public String toString() { + return "DistortedNoiseProperty{noiseProperty=" + this.noiseProperty + ", randomizer=" + this.randomizer + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/property/FractalNoiseProperty.java b/src/com/hypixel/hytale/procedurallib/property/FractalNoiseProperty.java new file mode 100644 index 0000000..4ee55e0 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/FractalNoiseProperty.java @@ -0,0 +1,365 @@ +package com.hypixel.hytale.procedurallib.property; + +import com.hypixel.hytale.procedurallib.NoiseFunction; +import com.hypixel.hytale.procedurallib.NoiseFunction2d; +import com.hypixel.hytale.procedurallib.NoiseFunction3d; +import com.hypixel.hytale.procedurallib.logic.GeneralNoise; +import javax.annotation.Nonnull; + +public class FractalNoiseProperty implements NoiseProperty { + protected final int seedOffset; + protected final NoiseFunction function; + protected final FractalNoiseProperty.FractalFunction fractalFunction; + protected final int octaves; + protected final double lacunarity; + protected final double persistence; + + public FractalNoiseProperty( + int seedOffset, NoiseFunction function, FractalNoiseProperty.FractalFunction fractalFunction, int octaves, double lacunarity, double persistence + ) { + this.seedOffset = seedOffset; + this.function = function; + this.fractalFunction = fractalFunction; + this.octaves = octaves; + this.lacunarity = lacunarity; + this.persistence = persistence; + } + + public int getSeedOffset() { + return this.seedOffset; + } + + public NoiseFunction getFunction() { + return this.function; + } + + public FractalNoiseProperty.FractalFunction getFractalFunction() { + return this.fractalFunction; + } + + public int getOctaves() { + return this.octaves; + } + + public double getLacunarity() { + return this.lacunarity; + } + + public double getPersistence() { + return this.persistence; + } + + @Override + public double get(int seed, double x, double y) { + return this.fractalFunction.get(seed, seed + this.seedOffset, x, y, this.octaves, this.lacunarity, this.persistence, this.function); + } + + @Override + public double get(int seed, double x, double y, double z) { + return this.fractalFunction.get(seed, seed + this.seedOffset, x, y, z, this.octaves, this.lacunarity, this.persistence, this.function); + } + + @Nonnull + @Override + public String toString() { + return "FractalNoiseProperty{seedOffset=" + + this.seedOffset + + ", function=" + + this.function + + ", fractalFunction=" + + this.fractalFunction + + ", octaves=" + + this.octaves + + ", lacunarity=" + + this.lacunarity + + ", persistence=" + + this.persistence + + "}"; + } + + private interface FractalFunction { + double get(int var1, int var2, double var3, double var5, int var7, double var8, double var10, NoiseFunction2d var12); + + double get(int var1, int var2, double var3, double var5, double var7, int var9, double var10, double var12, NoiseFunction3d var14); + } + + public static enum FractalMode { + FBM( + new FractalNoiseProperty.FractalFunction() { + @Override + public double get(int seed, int offsetSeed, double x, double y, int octaves, double lacunarity, double persistence, @Nonnull NoiseFunction2d noise) { + double sum = noise.get(seed, offsetSeed, x, y); + double amp = 1.0; + + for (int i = 1; i < octaves; i++) { + x *= lacunarity; + y *= lacunarity; + amp *= persistence; + sum += noise.get(seed, ++offsetSeed, x, y) * amp; + } + + return GeneralNoise.limit(sum * 0.5 + 0.5); + } + + @Override + public double get( + int seed, int offsetSeed, double x, double y, double z, int octaves, double lacunarity, double persistence, @Nonnull NoiseFunction3d noise + ) { + double sum = noise.get(seed, offsetSeed, x, y, z); + double amp = 1.0; + + for (int i = 1; i < octaves; i++) { + x *= lacunarity; + y *= lacunarity; + z *= lacunarity; + amp *= persistence; + sum += noise.get(seed, ++offsetSeed, x, y, z) * amp; + } + + return GeneralNoise.limit(sum * 0.5 + 0.5); + } + + @Nonnull + @Override + public String toString() { + return "FbmFractalFunction{}"; + } + } + ), + BILLOW( + new FractalNoiseProperty.FractalFunction() { + @Override + public double get(int seed, int offsetSeed, double x, double y, int octaves, double lacunarity, double persistence, @Nonnull NoiseFunction2d noise) { + double sum = Math.abs(noise.get(seed, offsetSeed, x, y)) * 2.0 - 1.0; + double amp = 1.0; + + for (int i = 1; i < octaves; i++) { + x *= lacunarity; + y *= lacunarity; + amp *= persistence; + offsetSeed++; + sum += (Math.abs(noise.get(seed, offsetSeed, x, y)) * 2.0 - 1.0) * amp; + } + + return GeneralNoise.limit(sum * 0.5 + 0.5); + } + + @Override + public double get( + int seed, int offsetSeed, double x, double y, double z, int octaves, double lacunarity, double persistence, @Nonnull NoiseFunction3d noise + ) { + double sum = Math.abs(noise.get(seed, offsetSeed, x, y, z)) * 2.0 - 1.0; + double amp = 1.0; + + for (int i = 1; i < octaves; i++) { + x *= lacunarity; + y *= lacunarity; + z *= lacunarity; + amp *= persistence; + offsetSeed++; + sum += (Math.abs(noise.get(seed, offsetSeed, x, y, z)) * 2.0 - 1.0) * amp; + } + + return GeneralNoise.limit(sum * 0.5 + 0.5); + } + + @Nonnull + @Override + public String toString() { + return "BillowFractalFunction{}"; + } + } + ), + MULTI_RIGID( + new FractalNoiseProperty.FractalFunction() { + @Override + public double get(int seed, int offsetSeed, double x, double y, int octaves, double lacunarity, double persistence, @Nonnull NoiseFunction2d noise) { + double sum = 1.0 - Math.abs(noise.get(seed, offsetSeed, x, y)); + double amp = 1.0; + + for (int i = 1; i < octaves; i++) { + x *= lacunarity; + y *= lacunarity; + amp *= persistence; + offsetSeed++; + sum -= (1.0 - Math.abs(noise.get(seed, offsetSeed, x, y))) * amp; + } + + return GeneralNoise.limit(sum * 0.5 + 0.5); + } + + @Override + public double get( + int seed, int offsetSeed, double x, double y, double z, int octaves, double lacunarity, double persistence, @Nonnull NoiseFunction3d noise + ) { + double sum = 1.0 - Math.abs(noise.get(seed, offsetSeed, x, y, z)); + float amp = 1.0F; + + for (int i = 1; i < octaves; i++) { + x *= lacunarity; + y *= lacunarity; + z *= lacunarity; + amp = (float)(amp * persistence); + offsetSeed++; + sum -= (1.0 - Math.abs(noise.get(seed, offsetSeed, x, y, z))) * amp; + } + + return GeneralNoise.limit(sum * 0.5 + 0.5); + } + + @Nonnull + @Override + public String toString() { + return "MultiRigidFractalFunction{}"; + } + } + ), + OLDSCHOOL( + new FractalNoiseProperty.FractalFunction() { + @Override + public double get(int seed, int offsetSeed, double x, double y, int octaves, double lacunarity, double persistence, @Nonnull NoiseFunction2d noise) { + double maxAmp = 0.0; + double amp = 1.0; + int freq = 1; + double sum = 0.0; + seed--; + + for (int i = 0; i < octaves; i++) { + sum += noise.get(seed, offsetSeed++, x * freq, y * freq) * amp; + maxAmp += amp; + amp *= persistence; + freq <<= 1; + } + + sum /= maxAmp; + sum *= 0.5; + return sum + 0.5; + } + + @Override + public double get( + int seed, int offsetSeed, double x, double y, double z, int octaves, double lacunarity, double persistence, @Nonnull NoiseFunction3d noise + ) { + double maxAmp = 0.0; + double amp = 1.0; + int freq = 1; + double sum = 0.0; + seed--; + + for (int i = 0; i < octaves; i++) { + sum += noise.get(seed, offsetSeed++, x * freq, y * freq, z * freq) * amp; + maxAmp += amp; + amp *= persistence; + freq <<= 1; + } + + sum /= maxAmp; + sum *= 0.5; + return sum + 0.5; + } + + @Nonnull + @Override + public String toString() { + return "OldschoolFractalFunction{}"; + } + } + ), + MIN( + new FractalNoiseProperty.FractalFunction() { + @Override + public double get(int seed, int offsetSeed, double x, double y, int octaves, double lacunarity, double persistence, @Nonnull NoiseFunction2d noise) { + double min = noise.get(seed, offsetSeed, x, y); + + for (int i = 0; i < octaves; i++) { + x *= lacunarity; + y *= lacunarity; + double d = noise.get(seed, ++offsetSeed, x, y); + if (d < min) { + min = d; + } + } + + return GeneralNoise.limit(min * 0.5 + 0.5); + } + + @Override + public double get( + int seed, int offsetSeed, double x, double y, double z, int octaves, double lacunarity, double persistence, @Nonnull NoiseFunction3d noise + ) { + double min = noise.get(seed, offsetSeed, x, y, z); + + for (int i = 0; i < octaves; i++) { + x *= lacunarity; + y *= lacunarity; + double d = noise.get(seed, ++offsetSeed, x, y, z); + if (d < min) { + min = d; + } + } + + return GeneralNoise.limit(min * 0.5 + 0.5); + } + + @Nonnull + @Override + public String toString() { + return "MinFractalFunction{}"; + } + } + ), + MAX( + new FractalNoiseProperty.FractalFunction() { + @Override + public double get(int seed, int offsetSeed, double x, double y, int octaves, double lacunarity, double persistence, @Nonnull NoiseFunction2d noise) { + double max = noise.get(seed, offsetSeed, x, y); + + for (int i = 0; i < octaves; i++) { + x *= lacunarity; + y *= lacunarity; + double d = noise.get(seed, ++offsetSeed, x, y); + if (d > max) { + max = d; + } + } + + return GeneralNoise.limit(max * 0.5 + 0.5); + } + + @Override + public double get( + int seed, int offsetSeed, double x, double y, double z, int octaves, double lacunarity, double persistence, @Nonnull NoiseFunction3d noise + ) { + double max = noise.get(seed, offsetSeed, x, y, z); + + for (int i = 0; i < octaves; i++) { + x *= lacunarity; + y *= lacunarity; + double d = noise.get(seed, ++offsetSeed, x, y, z); + if (d > max) { + max = d; + } + } + + return GeneralNoise.limit(max * 0.5 + 0.5); + } + + @Nonnull + @Override + public String toString() { + return "MaxFractalFunction{}"; + } + } + ); + + private final FractalNoiseProperty.FractalFunction function; + + private FractalMode(FractalNoiseProperty.FractalFunction function) { + this.function = function; + } + + public FractalNoiseProperty.FractalFunction getFunction() { + return this.function; + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/property/GradientNoiseProperty.java b/src/com/hypixel/hytale/procedurallib/property/GradientNoiseProperty.java new file mode 100644 index 0000000..b90b156 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/GradientNoiseProperty.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.procedurallib.property; + +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; + +public class GradientNoiseProperty implements NoiseProperty { + protected final NoiseProperty noise; + protected final GradientNoiseProperty.GradientMode mode; + protected final double distance; + protected final double invNormalize; + + public GradientNoiseProperty(NoiseProperty noise, GradientNoiseProperty.GradientMode mode, double distance, double normalize) { + this.noise = noise; + this.mode = mode; + this.distance = distance; + this.invNormalize = 1.0 / distance / normalize; + } + + @Override + public double get(int seed, double x, double y) { + double v = this.noise.get(seed, x, y); + double s = this.noise.get(seed, x, y + this.distance); + double e = this.noise.get(seed, x + this.distance, y); + double dx = e - v; + double dy = s - v; + + return switch (this.mode) { + case MAGNITUDE -> getMagnitude(dx, dy, this.invNormalize); + case ANGLE -> getAngle(dx, dy); + case ANGLE_ABS -> getAbsAngle(dx, dy); + }; + } + + @Override + public double get(int seed, double x, double y, double z) { + throw new UnsupportedOperationException(); + } + + protected static double getAngle(double dx, double dy) { + float angle = TrigMathUtil.atan2(dy, dx); + angle = (angle + (float) Math.PI) / (float) (Math.PI * 2); + return convertRange(angle); + } + + protected static double getAbsAngle(double dx, double dy) { + float angle = TrigMathUtil.atan2(dy, dx); + return Math.abs(angle) / (float) Math.PI; + } + + protected static double getMagnitude(double dx, double dy, double invNormalize) { + double mag = MathUtil.length(dx, dy); + return MathUtil.clamp(mag * invNormalize, 0.0, 1.0); + } + + protected static float convertRange(float angle) { + angle += 0.125F; + return angle > 1.0F ? angle - 1.0F : angle; + } + + public static enum GradientMode { + MAGNITUDE, + ANGLE, + ANGLE_ABS; + + private GradientMode() { + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/property/InvertNoiseProperty.java b/src/com/hypixel/hytale/procedurallib/property/InvertNoiseProperty.java new file mode 100644 index 0000000..9c1b741 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/InvertNoiseProperty.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.procedurallib.property; + +public class InvertNoiseProperty implements NoiseProperty { + protected final NoiseProperty noiseProperty; + + public InvertNoiseProperty(NoiseProperty noiseProperty) { + this.noiseProperty = noiseProperty; + } + + @Override + public double get(int seed, double x, double y) { + return 1.0 - this.noiseProperty.get(seed, x, y); + } + + @Override + public double get(int seed, double x, double y, double z) { + return 1.0 - this.noiseProperty.get(seed, x, y, z); + } +} diff --git a/src/com/hypixel/hytale/procedurallib/property/MaxNoiseProperty.java b/src/com/hypixel/hytale/procedurallib/property/MaxNoiseProperty.java new file mode 100644 index 0000000..99b17c7 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/MaxNoiseProperty.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.procedurallib.property; + +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class MaxNoiseProperty implements NoiseProperty { + public static final double MAX_EPSILON = 0.999999; + protected final NoiseProperty[] noiseProperties; + + public MaxNoiseProperty(NoiseProperty[] noiseProperties) { + this.noiseProperties = noiseProperties; + } + + public NoiseProperty[] getNoiseProperties() { + return this.noiseProperties; + } + + @Override + public double get(int seed, double x, double y) { + double val = this.noiseProperties[0].get(seed, x, y); + + for (int i = 1; i < this.noiseProperties.length; i++) { + if (val > 0.999999) { + return 1.0; + } + + double d; + if (val < (d = this.noiseProperties[i].get(seed, x, y))) { + val = d; + } + } + + return val; + } + + @Override + public double get(int seed, double x, double y, double z) { + double val = this.noiseProperties[0].get(seed, x, y, z); + + for (int i = 1; i < this.noiseProperties.length; i++) { + if (val > 0.999999) { + return 1.0; + } + + double d; + if (val < (d = this.noiseProperties[i].get(seed, x, y, z))) { + val = d; + } + } + + return val; + } + + @Nonnull + @Override + public String toString() { + return "MaxNoiseProperty{noiseProperties=" + Arrays.toString((Object[])this.noiseProperties) + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/property/MinNoiseProperty.java b/src/com/hypixel/hytale/procedurallib/property/MinNoiseProperty.java new file mode 100644 index 0000000..e4e3774 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/MinNoiseProperty.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.procedurallib.property; + +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class MinNoiseProperty implements NoiseProperty { + public static final double MIN_EPSILON = 1.0E-5; + protected final NoiseProperty[] noiseProperties; + + public MinNoiseProperty(NoiseProperty[] noiseProperties) { + this.noiseProperties = noiseProperties; + } + + public NoiseProperty[] getNoiseProperties() { + return this.noiseProperties; + } + + @Override + public double get(int seed, double x, double y) { + double val = this.noiseProperties[0].get(seed, x, y); + + for (int i = 1; i < this.noiseProperties.length; i++) { + if (val < 1.0E-5) { + return 0.0; + } + + double d; + if (val > (d = this.noiseProperties[i].get(seed, x, y))) { + val = d; + } + } + + return val; + } + + @Override + public double get(int seed, double x, double y, double z) { + double val = this.noiseProperties[0].get(seed, x, y, z); + + for (int i = 1; i < this.noiseProperties.length; i++) { + if (val < 1.0E-5) { + return 0.0; + } + + double d; + if (val > (d = this.noiseProperties[i].get(seed, x, y, z))) { + val = d; + } + } + + return val; + } + + @Nonnull + @Override + public String toString() { + return "MinNoiseProperty{noiseProperties=" + Arrays.toString((Object[])this.noiseProperties) + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/property/MultiplyNoiseProperty.java b/src/com/hypixel/hytale/procedurallib/property/MultiplyNoiseProperty.java new file mode 100644 index 0000000..ff24d07 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/MultiplyNoiseProperty.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.procedurallib.property; + +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class MultiplyNoiseProperty implements NoiseProperty { + protected final NoiseProperty[] noiseProperties; + + public MultiplyNoiseProperty(NoiseProperty[] noiseProperties) { + this.noiseProperties = noiseProperties; + } + + public NoiseProperty[] getNoiseProperties() { + return this.noiseProperties; + } + + @Override + public double get(int seed, double x, double y) { + double val = this.noiseProperties[0].get(seed, x, y); + + for (int i = 1; i < this.noiseProperties.length; i++) { + val *= this.noiseProperties[i].get(seed, x, y); + } + + return val; + } + + @Override + public double get(int seed, double x, double y, double z) { + double val = this.noiseProperties[0].get(seed, x, y, z); + + for (int i = 1; i < this.noiseProperties.length; i++) { + val *= this.noiseProperties[i].get(seed, x, y, z); + } + + return val; + } + + @Nonnull + @Override + public String toString() { + return "MultiplyNoiseProperty{noiseProperties=" + Arrays.toString((Object[])this.noiseProperties) + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/property/NoiseFormulaProperty.java b/src/com/hypixel/hytale/procedurallib/property/NoiseFormulaProperty.java new file mode 100644 index 0000000..d6cfa90 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/NoiseFormulaProperty.java @@ -0,0 +1,249 @@ +package com.hypixel.hytale.procedurallib.property; + +import javax.annotation.Nonnull; + +public class NoiseFormulaProperty implements NoiseProperty { + protected final NoiseProperty property; + protected final NoiseFormulaProperty.NoiseFormula.Formula formula; + + public NoiseFormulaProperty(NoiseProperty property, NoiseFormulaProperty.NoiseFormula.Formula formula) { + this.property = property; + this.formula = formula; + } + + public NoiseProperty getProperty() { + return this.property; + } + + public NoiseFormulaProperty.NoiseFormula.Formula getFormula() { + return this.formula; + } + + @Override + public double get(int seed, double x, double y) { + return this.formula.eval(this.property.get(seed, x, y)); + } + + @Override + public double get(int seed, double x, double y, double z) { + return this.formula.eval(this.property.get(seed, x, y, z)); + } + + @Nonnull + @Override + public String toString() { + return "NoiseFormulaProperty{property=" + this.property + ", formula=" + this.formula + "}"; + } + + public static enum NoiseFormula { + NORMAL(new NoiseFormulaProperty.NoiseFormula.Formula() { + @Override + public double eval(double noise) { + return noise; + } + + @Nonnull + @Override + public String toString() { + return "NormalFormula{}"; + } + }), + INVERTED(new NoiseFormulaProperty.NoiseFormula.Formula() { + @Override + public double eval(double noise) { + return 1.0 - noise; + } + + @Nonnull + @Override + public String toString() { + return "InvertedFormula{}"; + } + }), + SQUARED(new NoiseFormulaProperty.NoiseFormula.Formula() { + @Override + public double eval(double noise) { + return noise * noise; + } + + @Nonnull + @Override + public String toString() { + return "SquaredFormula{}"; + } + }), + INVERTED_SQUARED(new NoiseFormulaProperty.NoiseFormula.Formula() { + @Override + public double eval(double noise) { + return 1.0 - noise * noise; + } + + @Nonnull + @Override + public String toString() { + return "InvertedSquaredFormula{}"; + } + }), + SQRT(new NoiseFormulaProperty.NoiseFormula.Formula() { + @Override + public double eval(double noise) { + return Math.sqrt(noise); + } + + @Nonnull + @Override + public String toString() { + return "SqrtFormula{}"; + } + }), + INVERTED_SQRT(new NoiseFormulaProperty.NoiseFormula.Formula() { + @Override + public double eval(double noise) { + return 1.0 - Math.sqrt(noise); + } + + @Nonnull + @Override + public String toString() { + return "InvertedSqrtFormula{}"; + } + }), + RIDGED(new NoiseFormulaProperty.NoiseFormula.Formula() { + @Override + public double eval(double noise) { + return Math.abs(noise - 0.5); + } + + @Nonnull + @Override + public String toString() { + return "RidgedFormula{}"; + } + }), + INVERTED_RIDGED(new NoiseFormulaProperty.NoiseFormula.Formula() { + @Override + public double eval(double noise) { + return 1.0 - Math.abs(noise - 0.5); + } + + @Nonnull + @Override + public String toString() { + return "InvertedRidgedFormula{}"; + } + }), + RIDGED_SQRT(new NoiseFormulaProperty.NoiseFormula.Formula() { + @Override + public double eval(double noise) { + return Math.sqrt(Math.abs(noise - 0.5)); + } + + @Nonnull + @Override + public String toString() { + return "RidgedSqrtFormula{}"; + } + }), + INVERTED_RIDGED_SQRT(new NoiseFormulaProperty.NoiseFormula.Formula() { + @Override + public double eval(double noise) { + return 1.0 - Math.sqrt(Math.abs(noise - 0.5)); + } + + @Nonnull + @Override + public String toString() { + return "InvertedRidgedSqrtFormula{}"; + } + }), + RIDGED_FIX(new NoiseFormulaProperty.NoiseFormula.Formula() { + @Override + public double eval(double noise) { + return Math.abs(noise * 2.0 - 1.0); + } + + @Nonnull + @Override + public String toString() { + return "RidgedFixFormula{}"; + } + }), + INVERTED_RIDGED_FIX(new NoiseFormulaProperty.NoiseFormula.Formula() { + @Override + public double eval(double noise) { + return 1.0 - Math.abs(noise * 2.0 - 1.0); + } + + @Nonnull + @Override + public String toString() { + return "InvertedRidgedFixFormula{}"; + } + }), + RIDGED_SQRT_FIX(new NoiseFormulaProperty.NoiseFormula.Formula() { + @Override + public double eval(double noise) { + return Math.sqrt(Math.abs(noise * 2.0 - 1.0)); + } + + @Nonnull + @Override + public String toString() { + return "RidgedSqrtFixFormula{}"; + } + }), + INVERTED_RIDGED_SQRT_FIX(new NoiseFormulaProperty.NoiseFormula.Formula() { + @Override + public double eval(double noise) { + return 1.0 - Math.sqrt(Math.abs(noise * 2.0 - 1.0)); + } + + @Nonnull + @Override + public String toString() { + return "InvertedRidgedSqrtFixFormula{}"; + } + }), + RIDGED_SQUARED_FIX(new NoiseFormulaProperty.NoiseFormula.Formula() { + @Override + public double eval(double noise) { + noise = Math.abs(noise * 2.0 - 1.0); + return noise * noise; + } + + @Nonnull + @Override + public String toString() { + return "RidgedSquaredFixFormula{}"; + } + }), + INVERTED_RIDGED_SQUARED_FIX(new NoiseFormulaProperty.NoiseFormula.Formula() { + @Override + public double eval(double noise) { + noise = Math.abs(noise * 2.0 - 1.0); + return 1.0 - noise * noise; + } + + @Nonnull + @Override + public String toString() { + return "InvertedRidgedSquaredFixFormula{}"; + } + }); + + public final NoiseFormulaProperty.NoiseFormula.Formula formula; + + private NoiseFormula(NoiseFormulaProperty.NoiseFormula.Formula formula) { + this.formula = formula; + } + + public NoiseFormulaProperty.NoiseFormula.Formula getFormula() { + return this.formula; + } + + @FunctionalInterface + public interface Formula { + double eval(double var1); + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/property/NoiseProperty.java b/src/com/hypixel/hytale/procedurallib/property/NoiseProperty.java new file mode 100644 index 0000000..5be7db0 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/NoiseProperty.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.procedurallib.property; + +public interface NoiseProperty { + double get(int var1, double var2, double var4); + + double get(int var1, double var2, double var4, double var6); +} diff --git a/src/com/hypixel/hytale/procedurallib/property/NoisePropertyType.java b/src/com/hypixel/hytale/procedurallib/property/NoisePropertyType.java new file mode 100644 index 0000000..51981fb --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/NoisePropertyType.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.procedurallib.property; + +public enum NoisePropertyType { + DISTORTED, + MAX, + MIN, + MULTIPLY, + NORMALIZE, + FORMULA, + SCALE, + SUM, + INVERT, + OFFSET, + ROTATE, + GRADIENT, + CURVE, + BLEND; + + private NoisePropertyType() { + } +} diff --git a/src/com/hypixel/hytale/procedurallib/property/NormalizeNoiseProperty.java b/src/com/hypixel/hytale/procedurallib/property/NormalizeNoiseProperty.java new file mode 100644 index 0000000..39ea47a --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/NormalizeNoiseProperty.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.procedurallib.property; + +import com.hypixel.hytale.procedurallib.logic.GeneralNoise; +import javax.annotation.Nonnull; + +public class NormalizeNoiseProperty implements NoiseProperty { + protected final NoiseProperty noiseProperty; + protected final double min; + protected final double range; + + public NormalizeNoiseProperty(NoiseProperty noiseProperty, double min, double range) { + this.noiseProperty = noiseProperty; + this.min = min; + this.range = range; + } + + public NoiseProperty getNoiseProperty() { + return this.noiseProperty; + } + + public double getMin() { + return this.min; + } + + public double getRange() { + return this.range; + } + + @Override + public double get(int seed, double x, double y) { + return GeneralNoise.limit((this.noiseProperty.get(seed, x, y) - this.min) / this.range); + } + + @Override + public double get(int seed, double x, double y, double z) { + return GeneralNoise.limit((this.noiseProperty.get(seed, x, y, z) - this.min) / this.range); + } + + @Nonnull + @Override + public String toString() { + return "NormalizeNoiseProperty{noiseProperty=" + this.noiseProperty + ", min=" + this.min + ", range=" + this.range + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/property/OffsetNoiseProperty.java b/src/com/hypixel/hytale/procedurallib/property/OffsetNoiseProperty.java new file mode 100644 index 0000000..b34570c --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/OffsetNoiseProperty.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.procedurallib.property; + +import javax.annotation.Nonnull; + +public class OffsetNoiseProperty implements NoiseProperty { + protected final NoiseProperty noiseProperty; + protected final double offsetX; + protected final double offsetY; + protected final double offsetZ; + + public OffsetNoiseProperty(NoiseProperty noiseProperty, double offset) { + this(noiseProperty, offset, offset, offset); + } + + public OffsetNoiseProperty(NoiseProperty noiseProperty, double offsetX, double offsetY, double offsetZ) { + this.noiseProperty = noiseProperty; + this.offsetX = offsetX; + this.offsetY = offsetY; + this.offsetZ = offsetZ; + } + + public NoiseProperty getNoiseProperty() { + return this.noiseProperty; + } + + public double getOffsetX() { + return this.offsetX; + } + + public double getOffsetY() { + return this.offsetY; + } + + public double getOffsetZ() { + return this.offsetZ; + } + + @Override + public double get(int seed, double x, double y) { + return this.noiseProperty.get(seed, x + this.offsetX, y + this.offsetY); + } + + @Override + public double get(int seed, double x, double y, double z) { + return this.noiseProperty.get(seed, x + this.offsetX, y + this.offsetY, z + this.offsetZ); + } + + @Nonnull + @Override + public String toString() { + return "OffsetNoiseProperty{noiseProperty=" + + this.noiseProperty + + ", offsetX=" + + this.offsetX + + ", offsetY=" + + this.offsetY + + ", offsetZ=" + + this.offsetZ + + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/property/RotateNoiseProperty.java b/src/com/hypixel/hytale/procedurallib/property/RotateNoiseProperty.java new file mode 100644 index 0000000..d967454 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/RotateNoiseProperty.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.procedurallib.property; + +import com.hypixel.hytale.procedurallib.random.CoordinateRotator; +import javax.annotation.Nonnull; + +public class RotateNoiseProperty implements NoiseProperty { + protected final NoiseProperty noise; + protected final CoordinateRotator rotation; + + public RotateNoiseProperty(NoiseProperty noise, CoordinateRotator rotation) { + this.noise = noise; + this.rotation = rotation; + } + + @Override + public double get(int seed, double x, double y) { + double px = this.rotation.rotateX(x, y); + double py = this.rotation.rotateY(x, y); + return this.noise.get(seed, px, py); + } + + @Override + public double get(int seed, double x, double y, double z) { + double px = this.rotation.rotateX(x, y, z); + double py = this.rotation.rotateY(x, y, z); + double pz = this.rotation.rotateZ(x, y, z); + return this.noise.get(seed, px, py, pz); + } + + @Nonnull + @Override + public String toString() { + return "RotateNoiseProperty{noise=" + this.noise + ", rotation=" + this.rotation + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/property/ScaleNoiseProperty.java b/src/com/hypixel/hytale/procedurallib/property/ScaleNoiseProperty.java new file mode 100644 index 0000000..d0f29ec --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/ScaleNoiseProperty.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.procedurallib.property; + +import javax.annotation.Nonnull; + +public class ScaleNoiseProperty implements NoiseProperty { + protected final NoiseProperty noiseProperty; + protected final double scaleX; + protected final double scaleY; + protected final double scaleZ; + + public ScaleNoiseProperty(NoiseProperty noiseProperty, double scale) { + this(noiseProperty, scale, scale, scale); + } + + public ScaleNoiseProperty(NoiseProperty noiseProperty, double scaleX, double scaleY, double scaleZ) { + this.noiseProperty = noiseProperty; + this.scaleX = scaleX; + this.scaleY = scaleY; + this.scaleZ = scaleZ; + } + + public NoiseProperty getNoiseProperty() { + return this.noiseProperty; + } + + public double getScaleX() { + return this.scaleX; + } + + public double getScaleY() { + return this.scaleY; + } + + public double getScaleZ() { + return this.scaleZ; + } + + @Override + public double get(int seed, double x, double y) { + return this.noiseProperty.get(seed, x * this.scaleX, y * this.scaleY); + } + + @Override + public double get(int seed, double x, double y, double z) { + return this.noiseProperty.get(seed, x * this.scaleX, y * this.scaleY, z * this.scaleZ); + } + + @Nonnull + @Override + public String toString() { + return "ScaleNoiseProperty{noiseProperty=" + this.noiseProperty + ", scaleX=" + this.scaleX + ", scaleY=" + this.scaleY + ", scaleZ=" + this.scaleZ + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/property/SingleNoiseProperty.java b/src/com/hypixel/hytale/procedurallib/property/SingleNoiseProperty.java new file mode 100644 index 0000000..c7b4850 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/SingleNoiseProperty.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.procedurallib.property; + +import com.hypixel.hytale.procedurallib.NoiseFunction; +import com.hypixel.hytale.procedurallib.logic.GeneralNoise; +import javax.annotation.Nonnull; + +public class SingleNoiseProperty implements NoiseProperty { + protected final int seedOffset; + protected final NoiseFunction function; + + public SingleNoiseProperty(NoiseFunction function) { + this(0, function); + } + + public SingleNoiseProperty(int seedOffset, NoiseFunction function) { + this.seedOffset = seedOffset; + this.function = function; + } + + public int getSeedOffset() { + return this.seedOffset; + } + + public NoiseFunction getFunction() { + return this.function; + } + + @Override + public double get(int seed, double x, double y) { + return GeneralNoise.limit(this.function.get(seed, seed + this.seedOffset, x, y) * 0.5 + 0.5); + } + + @Override + public double get(int seed, double x, double y, double z) { + return GeneralNoise.limit(this.function.get(seed, seed + this.seedOffset, x, y, z) * 0.5 + 0.5); + } + + @Nonnull + @Override + public String toString() { + return "SingleNoiseProperty{seedOffset=" + this.seedOffset + ", function=" + this.function + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/property/SumNoiseProperty.java b/src/com/hypixel/hytale/procedurallib/property/SumNoiseProperty.java new file mode 100644 index 0000000..e9f2ff3 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/property/SumNoiseProperty.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.procedurallib.property; + +import com.hypixel.hytale.procedurallib.logic.GeneralNoise; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class SumNoiseProperty implements NoiseProperty { + protected final SumNoiseProperty.Entry[] entries; + + public SumNoiseProperty(SumNoiseProperty.Entry[] entries) { + this.entries = entries; + } + + public SumNoiseProperty.Entry[] getEntries() { + return this.entries; + } + + @Override + public double get(int seed, double x, double y) { + double val = 0.0; + + for (SumNoiseProperty.Entry entry : this.entries) { + val += entry.noiseProperty.get(seed, x, y) * entry.factor; + } + + return GeneralNoise.limit(val); + } + + @Override + public double get(int seed, double x, double y, double z) { + double val = 0.0; + + for (SumNoiseProperty.Entry entry : this.entries) { + val += entry.noiseProperty.get(seed, x, y, z) * entry.factor; + } + + return GeneralNoise.limit(val); + } + + @Nonnull + @Override + public String toString() { + return "SumNoiseProperty{entries=" + Arrays.toString((Object[])this.entries) + "}"; + } + + public static class Entry { + private NoiseProperty noiseProperty; + private double factor; + + public Entry(NoiseProperty noiseProperty, double factor) { + this.noiseProperty = noiseProperty; + this.factor = factor; + } + + public NoiseProperty getNoiseProperty() { + return this.noiseProperty; + } + + public void setNoiseProperty(NoiseProperty noiseProperty) { + this.noiseProperty = noiseProperty; + } + + public double getFactor() { + return this.factor; + } + + public void setFactor(double factor) { + this.factor = factor; + } + + @Nonnull + @Override + public String toString() { + return "Entry{noiseProperty=" + this.noiseProperty + ", factor=" + this.factor + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/random/CoordinateOriginRotator.java b/src/com/hypixel/hytale/procedurallib/random/CoordinateOriginRotator.java new file mode 100644 index 0000000..5a0be0e --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/random/CoordinateOriginRotator.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.procedurallib.random; + +import javax.annotation.Nonnull; + +public class CoordinateOriginRotator extends CoordinateRotator { + private final double originX; + private final double originY; + private final double originZ; + + public CoordinateOriginRotator(double pitch, double yaw, double originX, double originY, double originZ) { + super(pitch, yaw); + this.originX = originX; + this.originY = originY; + this.originZ = originZ; + } + + @Override + public double randomDoubleX(int seed, double x, double y) { + x -= this.originX; + y -= this.originY; + return this.originX + this.rotateX(x, y); + } + + @Override + public double randomDoubleY(int seed, double x, double y) { + x -= this.originX; + y -= this.originY; + return this.originY + this.rotateY(x, y); + } + + @Override + public double randomDoubleX(int seed, double x, double y, double z) { + x -= this.originX; + y -= this.originY; + z -= this.originZ; + return this.originX + this.rotateX(x, y, z); + } + + @Override + public double randomDoubleY(int seed, double x, double y, double z) { + x -= this.originX; + y -= this.originY; + z -= this.originZ; + return this.originY + this.rotateY(x, y, z); + } + + @Override + public double randomDoubleZ(int seed, double x, double y, double z) { + x -= this.originX; + y -= this.originY; + z -= this.originZ; + return this.originZ + this.rotateZ(x, y, z); + } + + @Nonnull + @Override + public String toString() { + return "CoordinateOriginRotator{pitch=" + + this.pitch + + ", yaw=" + + this.yaw + + ", originX=" + + this.originX + + ", originY=" + + this.originY + + ", originZ=" + + this.originZ + + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/random/CoordinateRandomizer.java b/src/com/hypixel/hytale/procedurallib/random/CoordinateRandomizer.java new file mode 100644 index 0000000..5dbc73a --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/random/CoordinateRandomizer.java @@ -0,0 +1,169 @@ +package com.hypixel.hytale.procedurallib.random; + +import com.hypixel.hytale.procedurallib.property.NoiseProperty; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class CoordinateRandomizer implements ICoordinateRandomizer { + public static final ICoordinateRandomizer EMPTY_RANDOMIZER = new CoordinateRandomizer.EmptyCoordinateRandomizer(); + protected final CoordinateRandomizer.AmplitudeNoiseProperty[] xNoise; + protected final CoordinateRandomizer.AmplitudeNoiseProperty[] yNoise; + protected final CoordinateRandomizer.AmplitudeNoiseProperty[] zNoise; + + public CoordinateRandomizer( + CoordinateRandomizer.AmplitudeNoiseProperty[] xNoise, + CoordinateRandomizer.AmplitudeNoiseProperty[] yNoise, + CoordinateRandomizer.AmplitudeNoiseProperty[] zNoise + ) { + this.xNoise = xNoise; + this.yNoise = yNoise; + this.zNoise = zNoise; + } + + public CoordinateRandomizer.AmplitudeNoiseProperty[] getXNoise() { + return this.xNoise; + } + + public CoordinateRandomizer.AmplitudeNoiseProperty[] getYNoise() { + return this.yNoise; + } + + public CoordinateRandomizer.AmplitudeNoiseProperty[] getZNoise() { + return this.zNoise; + } + + @Override + public double randomDoubleX(int seed, double x, double y) { + double offsetX = 0.0; + + for (CoordinateRandomizer.AmplitudeNoiseProperty property : this.xNoise) { + offsetX += (property.property.get(seed, x, y) * 2.0 - 1.0) * property.amplitude; + } + + return x + offsetX; + } + + @Override + public double randomDoubleY(int seed, double x, double y) { + double offsetY = 0.0; + + for (CoordinateRandomizer.AmplitudeNoiseProperty property : this.yNoise) { + offsetY += (property.property.get(seed, x, y) * 2.0 - 1.0) * property.amplitude; + } + + return y + offsetY; + } + + @Override + public double randomDoubleX(int seed, double x, double y, double z) { + double offsetX = 0.0; + + for (CoordinateRandomizer.AmplitudeNoiseProperty property : this.xNoise) { + offsetX += (property.property.get(seed, x, y, z) * 2.0 - 1.0) * property.amplitude; + } + + return x + offsetX; + } + + @Override + public double randomDoubleY(int seed, double x, double y, double z) { + double offsetY = 0.0; + + for (CoordinateRandomizer.AmplitudeNoiseProperty property : this.yNoise) { + offsetY += (property.property.get(seed, x, y, z) * 2.0 - 1.0) * property.amplitude; + } + + return y + offsetY; + } + + @Override + public double randomDoubleZ(int seed, double x, double y, double z) { + double offsetZ = 0.0; + + for (CoordinateRandomizer.AmplitudeNoiseProperty property : this.zNoise) { + offsetZ += (property.property.get(seed, x, y, z) * 2.0 - 1.0) * property.amplitude; + } + + return z + offsetZ; + } + + @Nonnull + @Override + public String toString() { + return "CoordinateRandomizer{xNoise=" + + Arrays.toString((Object[])this.xNoise) + + ", yNoise=" + + Arrays.toString((Object[])this.yNoise) + + ", zNoise=" + + Arrays.toString((Object[])this.zNoise) + + "}"; + } + + public static class AmplitudeNoiseProperty { + protected NoiseProperty property; + protected double amplitude; + + public AmplitudeNoiseProperty(NoiseProperty property, double amplitude) { + this.property = property; + this.amplitude = amplitude; + } + + public NoiseProperty getProperty() { + return this.property; + } + + public void setProperty(NoiseProperty property) { + this.property = property; + } + + public double getAmplitude() { + return this.amplitude; + } + + public void setAmplitude(double amplitude) { + this.amplitude = amplitude; + } + + @Nonnull + @Override + public String toString() { + return "AmplitudeNoiseProperty{property=" + this.property + ", amplitude=" + this.amplitude + "}"; + } + } + + private static class EmptyCoordinateRandomizer implements ICoordinateRandomizer { + private EmptyCoordinateRandomizer() { + } + + @Override + public double randomDoubleX(int seed, double x, double y) { + return x; + } + + @Override + public double randomDoubleY(int seed, double x, double y) { + return y; + } + + @Override + public double randomDoubleX(int seed, double x, double y, double z) { + return x; + } + + @Override + public double randomDoubleY(int seed, double x, double y, double z) { + return y; + } + + @Override + public double randomDoubleZ(int seed, double x, double y, double z) { + return z; + } + + @Nonnull + @Override + public String toString() { + return "EmptyCoordinateRandomizer{}"; + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/random/CoordinateRotator.java b/src/com/hypixel/hytale/procedurallib/random/CoordinateRotator.java new file mode 100644 index 0000000..b16cf7e --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/random/CoordinateRotator.java @@ -0,0 +1,112 @@ +package com.hypixel.hytale.procedurallib.random; + +import com.hypixel.hytale.math.util.TrigMathUtil; +import javax.annotation.Nonnull; + +public class CoordinateRotator implements ICoordinateRandomizer { + public static final CoordinateRotator NONE = new CoordinateRotator(0.0, 0.0); + public static final int X0 = 0; + public static final int Y0 = 1; + public static final int Z0 = 2; + public static final int X1 = 3; + public static final int Y1 = 4; + public static final int Z1 = 5; + public static final int X2 = 6; + public static final int Y2 = 7; + public static final int Z2 = 8; + protected final double pitch; + protected final double yaw; + @Nonnull + protected final double[] matrix; + + public CoordinateRotator(double pitch, double yaw) { + this.pitch = pitch; + this.yaw = yaw; + this.matrix = createRotationMatrix(pitch, yaw); + } + + public double rotateX(double x, double y) { + return x * this.matrix[0] + y * this.matrix[2]; + } + + public double rotateY(double x, double y) { + return x * this.matrix[6] + y * this.matrix[8]; + } + + public double rotateX(double x, double y, double z) { + return x * this.matrix[0] + y * this.matrix[1] + z * this.matrix[2]; + } + + public double rotateY(double x, double y, double z) { + return x * this.matrix[3] + y * this.matrix[4] + z * this.matrix[5]; + } + + public double rotateZ(double x, double y, double z) { + return x * this.matrix[6] + y * this.matrix[7] + z * this.matrix[8]; + } + + @Override + public double randomDoubleX(int seed, double x, double y) { + return this.rotateX(x, y); + } + + @Override + public double randomDoubleY(int seed, double x, double y) { + return this.rotateY(x, y); + } + + @Override + public double randomDoubleX(int seed, double x, double y, double z) { + return this.rotateX(x, y, z); + } + + @Override + public double randomDoubleY(int seed, double x, double y, double z) { + return this.rotateY(x, y, z); + } + + @Override + public double randomDoubleZ(int seed, double x, double y, double z) { + return this.rotateZ(x, y, z); + } + + @Nonnull + @Override + public String toString() { + return "CoordinateRotator{pitch=" + this.pitch + ", yaw=" + this.yaw + "}"; + } + + public static double[] createRotationMatrix(double pitch, double yaw) { + double sinYaw = TrigMathUtil.sin(yaw); + double cosYaw = TrigMathUtil.cos(yaw); + double sinPitch = TrigMathUtil.sin(pitch); + double cosPitch = TrigMathUtil.cos(pitch); + double px1 = 1.0; + double px2 = 0.0; + double px3 = 0.0; + double py1 = 0.0; + double py3 = -sinPitch; + double pz1 = 0.0; + double yx2 = 0.0; + double yy1 = 0.0; + double yy2 = 1.0; + double yy3 = 0.0; + double yz1 = -sinYaw; + double yz2 = 0.0; + return new double[]{ + dot(px1, px2, px3, cosYaw, yy1, yz1), + dot(px1, px2, px3, yx2, yy2, yz2), + dot(px1, px2, px3, sinYaw, yy3, cosYaw), + dot(py1, cosPitch, py3, cosYaw, yy1, yz1), + dot(py1, cosPitch, py3, yx2, yy2, yz2), + dot(py1, cosPitch, py3, sinYaw, yy3, cosYaw), + dot(pz1, sinPitch, cosPitch, cosYaw, yy1, yz1), + dot(pz1, sinPitch, cosPitch, yx2, yy2, yz2), + dot(pz1, sinPitch, cosPitch, sinYaw, yy3, cosYaw) + }; + } + + private static double dot(double x1, double y1, double z1, double x2, double y2, double z2) { + return x1 * x2 + y1 * y2 + z1 * z2; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/random/ICoordinateRandomizer.java b/src/com/hypixel/hytale/procedurallib/random/ICoordinateRandomizer.java new file mode 100644 index 0000000..11bb968 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/random/ICoordinateRandomizer.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.procedurallib.random; + +public interface ICoordinateRandomizer { + double randomDoubleX(int var1, double var2, double var4); + + double randomDoubleY(int var1, double var2, double var4); + + double randomDoubleX(int var1, double var2, double var4, double var6); + + double randomDoubleY(int var1, double var2, double var4, double var6); + + double randomDoubleZ(int var1, double var2, double var4, double var6); +} diff --git a/src/com/hypixel/hytale/procedurallib/random/RotatedCoordinateRandomizer.java b/src/com/hypixel/hytale/procedurallib/random/RotatedCoordinateRandomizer.java new file mode 100644 index 0000000..7330833 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/random/RotatedCoordinateRandomizer.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.procedurallib.random; + +import javax.annotation.Nonnull; + +public class RotatedCoordinateRandomizer implements ICoordinateRandomizer { + protected final ICoordinateRandomizer randomizer; + protected final CoordinateRotator rotation; + + public RotatedCoordinateRandomizer(ICoordinateRandomizer randomizer, CoordinateRotator rotation) { + this.randomizer = randomizer; + this.rotation = rotation; + } + + @Override + public double randomDoubleX(int seed, double x, double y) { + double px = this.rotation.rotateX(x, y); + double py = this.rotation.rotateY(x, y); + return this.randomizer.randomDoubleX(seed, px, py); + } + + @Override + public double randomDoubleY(int seed, double x, double y) { + double px = this.rotation.rotateX(x, y); + double py = this.rotation.rotateY(x, y); + return this.randomizer.randomDoubleY(seed, px, py); + } + + @Override + public double randomDoubleX(int seed, double x, double y, double z) { + double px = this.rotation.rotateX(x, y, z); + double py = this.rotation.rotateY(x, y, z); + double pz = this.rotation.rotateZ(x, y, z); + return this.randomizer.randomDoubleX(seed, px, py, pz); + } + + @Override + public double randomDoubleY(int seed, double x, double y, double z) { + double px = this.rotation.rotateX(x, y, z); + double py = this.rotation.rotateY(x, y, z); + double pz = this.rotation.rotateZ(x, y, z); + return this.randomizer.randomDoubleY(seed, px, py, pz); + } + + @Override + public double randomDoubleZ(int seed, double x, double y, double z) { + double px = this.rotation.rotateX(x, y, z); + double py = this.rotation.rotateY(x, y, z); + double pz = this.rotation.rotateZ(x, y, z); + return this.randomizer.randomDoubleZ(seed, px, py, pz); + } + + @Nonnull + @Override + public String toString() { + return "RotatedCoordinateRandomizer{randomizer=" + this.randomizer + ", rotation=" + this.rotation + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/supplier/ConstantDoubleCoordinateHashSupplier.java b/src/com/hypixel/hytale/procedurallib/supplier/ConstantDoubleCoordinateHashSupplier.java new file mode 100644 index 0000000..c6ec886 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/supplier/ConstantDoubleCoordinateHashSupplier.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.procedurallib.supplier; + +import javax.annotation.Nonnull; + +public class ConstantDoubleCoordinateHashSupplier implements IDoubleCoordinateHashSupplier { + public static final ConstantDoubleCoordinateHashSupplier ZERO = new ConstantDoubleCoordinateHashSupplier(0.0); + public static final ConstantDoubleCoordinateHashSupplier ONE = new ConstantDoubleCoordinateHashSupplier(1.0); + protected final double result; + + public ConstantDoubleCoordinateHashSupplier(double result) { + this.result = result; + } + + public double getResult() { + return this.result; + } + + @Override + public double get(int seed, int x, int y, long hash) { + return this.result; + } + + @Nonnull + @Override + public String toString() { + return "ConstantDoubleCoordinateHashSupplier{result=" + this.result + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/supplier/ConstantFloatCoordinateHashSupplier.java b/src/com/hypixel/hytale/procedurallib/supplier/ConstantFloatCoordinateHashSupplier.java new file mode 100644 index 0000000..75b3aaf --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/supplier/ConstantFloatCoordinateHashSupplier.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.procedurallib.supplier; + +import javax.annotation.Nonnull; + +public class ConstantFloatCoordinateHashSupplier implements IFloatCoordinateHashSupplier { + public static final ConstantFloatCoordinateHashSupplier ZERO = new ConstantFloatCoordinateHashSupplier(0.0F); + public static final ConstantFloatCoordinateHashSupplier ONE = new ConstantFloatCoordinateHashSupplier(1.0F); + protected final float result; + + public ConstantFloatCoordinateHashSupplier(float result) { + this.result = result; + } + + public float getResult() { + return this.result; + } + + @Override + public float get(int seed, double x, double y, long hash) { + return this.result; + } + + @Nonnull + @Override + public String toString() { + return "ConstantFloatCoordinateHashSupplier{result=" + this.result + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/supplier/DoubleRange.java b/src/com/hypixel/hytale/procedurallib/supplier/DoubleRange.java new file mode 100644 index 0000000..ab0c7a1 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/supplier/DoubleRange.java @@ -0,0 +1,170 @@ +package com.hypixel.hytale.procedurallib.supplier; + +import java.util.Arrays; +import java.util.Random; +import java.util.function.DoubleSupplier; +import javax.annotation.Nonnull; + +public class DoubleRange { + public static final DoubleRange.Constant ZERO = new DoubleRange.Constant(0.0); + public static final DoubleRange.Constant ONE = new DoubleRange.Constant(1.0); + + public DoubleRange() { + } + + public static class Constant implements IDoubleRange { + protected final double result; + + public Constant(double result) { + this.result = result; + } + + public double getResult() { + return this.result; + } + + @Override + public double getValue(double v) { + return this.result; + } + + @Override + public double getValue(DoubleSupplier supplier) { + return this.result; + } + + @Override + public double getValue(Random random) { + return this.result; + } + + @Override + public double getValue(int seed, double x, double y, IDoubleCoordinateSupplier2d supplier) { + return this.result; + } + + @Override + public double getValue(int seed, double x, double y, double z, IDoubleCoordinateSupplier3d supplier) { + return this.result; + } + + @Nonnull + @Override + public String toString() { + return "DoubleRange.Constant{result=" + this.result + "}"; + } + } + + public static class Multiple implements IDoubleRange { + protected final double[] thresholds; + protected final double[] values; + + public Multiple(double[] thresholds, double[] values) { + this.thresholds = thresholds; + this.values = values; + } + + @Override + public double getValue(double v) { + if (v > this.thresholds[this.thresholds.length - 1]) { + return this.values[this.values.length - 1]; + } else { + double min = 0.0; + + for (int i = 0; i < this.thresholds.length; i++) { + double max = this.thresholds[i]; + if (v < max) { + if (i == 0) { + return this.values[0]; + } + + double alpha = (v - min) / (max - min); + double valueMin = this.values[i - 1]; + double valueMax = this.values[i]; + double range = valueMax - valueMin; + return valueMin + alpha * range; + } + + min = max; + } + + return 0.0; + } + } + + @Override + public double getValue(@Nonnull DoubleSupplier supplier) { + return this.getValue(supplier.getAsDouble()); + } + + @Override + public double getValue(@Nonnull Random random) { + return this.getValue(random.nextDouble()); + } + + @Override + public double getValue(int seed, double x, double y, @Nonnull IDoubleCoordinateSupplier2d supplier) { + return this.getValue(supplier.get(seed, x, y)); + } + + @Override + public double getValue(int seed, double x, double y, double z, @Nonnull IDoubleCoordinateSupplier3d supplier) { + return this.getValue(supplier.get(seed, x, y, z)); + } + + @Nonnull + @Override + public String toString() { + return "Multiple{thresholds=" + Arrays.toString(this.thresholds) + ", values=" + Arrays.toString(this.values) + "}"; + } + } + + public static class Normal implements IDoubleRange { + protected final double min; + protected final double range; + + public Normal(double min, double max) { + this.min = min; + this.range = max - min; + } + + public double getMin() { + return this.min; + } + + public double getRange() { + return this.range; + } + + @Override + public double getValue(double v) { + return this.min + this.range * v; + } + + @Override + public double getValue(@Nonnull DoubleSupplier supplier) { + return this.min + this.range * supplier.getAsDouble(); + } + + @Override + public double getValue(@Nonnull Random random) { + return this.getValue(random.nextDouble()); + } + + @Override + public double getValue(int seed, double x, double y, @Nonnull IDoubleCoordinateSupplier2d supplier) { + return this.min + this.range * supplier.get(seed, x, y); + } + + @Override + public double getValue(int seed, double x, double y, double z, @Nonnull IDoubleCoordinateSupplier3d supplier) { + return this.min + this.range * supplier.get(seed, x, y, z); + } + + @Nonnull + @Override + public String toString() { + return "DoubleRange.Normal{min=" + this.min + ", range=" + this.range + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/supplier/DoubleRangeCoordinateHashSupplier.java b/src/com/hypixel/hytale/procedurallib/supplier/DoubleRangeCoordinateHashSupplier.java new file mode 100644 index 0000000..167b0e9 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/supplier/DoubleRangeCoordinateHashSupplier.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.procedurallib.supplier; + +import com.hypixel.hytale.math.util.HashUtil; +import javax.annotation.Nonnull; + +public class DoubleRangeCoordinateHashSupplier implements IDoubleCoordinateHashSupplier { + protected final IDoubleRange range; + + public DoubleRangeCoordinateHashSupplier(IDoubleRange range) { + this.range = range; + } + + @Override + public double get(int seed, int x, int y, long hash) { + return this.range.getValue(HashUtil.random(seed, x, y, hash)); + } + + @Nonnull + @Override + public String toString() { + return "DoubleRangeCoordinateHashSupplier{range=" + this.range + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/supplier/DoubleRangeNoiseSupplier.java b/src/com/hypixel/hytale/procedurallib/supplier/DoubleRangeNoiseSupplier.java new file mode 100644 index 0000000..4c0983d --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/supplier/DoubleRangeNoiseSupplier.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.procedurallib.supplier; + +import com.hypixel.hytale.procedurallib.property.NoiseProperty; +import javax.annotation.Nonnull; + +public class DoubleRangeNoiseSupplier implements IDoubleCoordinateSupplier { + protected final IDoubleRange range; + @Nonnull + protected final NoiseProperty noiseProperty; + @Nonnull + protected final IDoubleCoordinateSupplier2d supplier2d; + @Nonnull + protected final IDoubleCoordinateSupplier3d supplier3d; + + public DoubleRangeNoiseSupplier(IDoubleRange range, @Nonnull NoiseProperty noiseProperty) { + this.range = range; + this.noiseProperty = noiseProperty; + this.supplier2d = noiseProperty::get; + this.supplier3d = noiseProperty::get; + } + + @Override + public double get(int seed, double x, double y) { + return this.range.getValue(seed, x, y, this.supplier2d); + } + + @Override + public double get(int seed, double x, double y, double z) { + return this.range.getValue(seed, x, y, z, this.supplier3d); + } + + @Nonnull + @Override + public String toString() { + return "DoubleRangeNoiseSupplier{range=" + this.range + ", noiseProperty=" + this.noiseProperty + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/supplier/FloatRange.java b/src/com/hypixel/hytale/procedurallib/supplier/FloatRange.java new file mode 100644 index 0000000..1e4dff2 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/supplier/FloatRange.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.procedurallib.supplier; + +import java.util.Random; +import javax.annotation.Nonnull; + +public class FloatRange { + public static final FloatRange.Constant ZERO = new FloatRange.Constant(0.0F); + public static final FloatRange.Constant ONE = new FloatRange.Constant(1.0F); + + public FloatRange() { + } + + public static class Constant implements IFloatRange { + protected final float result; + + public Constant(float result) { + this.result = result; + } + + public float getResult() { + return this.result; + } + + @Override + public float getValue(float v) { + return this.result; + } + + @Override + public float getValue(FloatSupplier supplier) { + return this.result; + } + + @Override + public float getValue(Random random) { + return this.result; + } + + @Override + public float getValue(int seed, double x, double y, IDoubleCoordinateSupplier2d supplier) { + return this.result; + } + + @Override + public float getValue(int seed, double x, double y, double z, IDoubleCoordinateSupplier3d supplier) { + return this.result; + } + + @Nonnull + @Override + public String toString() { + return "FloatRange.Constant{result=" + this.result + "}"; + } + } + + public static class Normal implements IFloatRange { + protected final float min; + protected final float range; + + public Normal(float min, float max) { + this.min = min; + this.range = max - min; + } + + public float getMin() { + return this.min; + } + + public float getRange() { + return this.range; + } + + @Override + public float getValue(float v) { + return this.min + this.range * v; + } + + @Override + public float getValue(@Nonnull FloatSupplier supplier) { + return this.min + this.range * supplier.getAsFloat(); + } + + @Override + public float getValue(@Nonnull Random random) { + return this.getValue(random.nextFloat()); + } + + @Override + public float getValue(int seed, double x, double y, @Nonnull IDoubleCoordinateSupplier2d supplier) { + return this.min + this.range * (float)supplier.get(seed, x, y); + } + + @Override + public float getValue(int seed, double x, double y, double z, @Nonnull IDoubleCoordinateSupplier3d supplier) { + return this.min + this.range * (float)supplier.get(seed, x, y, z); + } + + @Nonnull + @Override + public String toString() { + return "FloatRange.Normal{min=" + this.min + ", range=" + this.range + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/procedurallib/supplier/FloatRangeNoiseSupplier.java b/src/com/hypixel/hytale/procedurallib/supplier/FloatRangeNoiseSupplier.java new file mode 100644 index 0000000..fb29968 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/supplier/FloatRangeNoiseSupplier.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.procedurallib.supplier; + +import com.hypixel.hytale.procedurallib.property.NoiseProperty; +import javax.annotation.Nonnull; + +public class FloatRangeNoiseSupplier implements IFloatCoordinateSupplier { + protected final IFloatRange range; + @Nonnull + protected final NoiseProperty noiseProperty; + @Nonnull + protected final IDoubleCoordinateSupplier2d supplier2d; + @Nonnull + protected final IDoubleCoordinateSupplier3d supplier3d; + + public FloatRangeNoiseSupplier(IFloatRange range, @Nonnull NoiseProperty noiseProperty) { + this.range = range; + this.noiseProperty = noiseProperty; + this.supplier2d = noiseProperty::get; + this.supplier3d = noiseProperty::get; + } + + @Override + public float get(int seed, double x, double y) { + return this.range.getValue(seed, x, y, this.supplier2d); + } + + @Override + public float get(int seed, double x, double y, double z) { + return this.range.getValue(seed, x, y, z, this.supplier3d); + } + + @Nonnull + @Override + public String toString() { + return "FloatRangeNoiseSupplier{range=" + this.range + ", noiseProperty=" + this.noiseProperty + "}"; + } +} diff --git a/src/com/hypixel/hytale/procedurallib/supplier/FloatSupplier.java b/src/com/hypixel/hytale/procedurallib/supplier/FloatSupplier.java new file mode 100644 index 0000000..0584d8c --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/supplier/FloatSupplier.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.procedurallib.supplier; + +@FunctionalInterface +public interface FloatSupplier { + float getAsFloat(); +} diff --git a/src/com/hypixel/hytale/procedurallib/supplier/IDoubleCoordinateHashSupplier.java b/src/com/hypixel/hytale/procedurallib/supplier/IDoubleCoordinateHashSupplier.java new file mode 100644 index 0000000..5b312fc --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/supplier/IDoubleCoordinateHashSupplier.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.procedurallib.supplier; + +public interface IDoubleCoordinateHashSupplier { + double get(int var1, int var2, int var3, long var4); +} diff --git a/src/com/hypixel/hytale/procedurallib/supplier/IDoubleCoordinateSupplier.java b/src/com/hypixel/hytale/procedurallib/supplier/IDoubleCoordinateSupplier.java new file mode 100644 index 0000000..c875263 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/supplier/IDoubleCoordinateSupplier.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.procedurallib.supplier; + +public interface IDoubleCoordinateSupplier { + double get(int var1, double var2, double var4); + + double get(int var1, double var2, double var4, double var6); +} diff --git a/src/com/hypixel/hytale/procedurallib/supplier/IDoubleCoordinateSupplier2d.java b/src/com/hypixel/hytale/procedurallib/supplier/IDoubleCoordinateSupplier2d.java new file mode 100644 index 0000000..1a6d474 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/supplier/IDoubleCoordinateSupplier2d.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.procedurallib.supplier; + +public interface IDoubleCoordinateSupplier2d { + double get(int var1, double var2, double var4); +} diff --git a/src/com/hypixel/hytale/procedurallib/supplier/IDoubleCoordinateSupplier3d.java b/src/com/hypixel/hytale/procedurallib/supplier/IDoubleCoordinateSupplier3d.java new file mode 100644 index 0000000..1b1e875 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/supplier/IDoubleCoordinateSupplier3d.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.procedurallib.supplier; + +public interface IDoubleCoordinateSupplier3d { + double get(int var1, double var2, double var4, double var6); +} diff --git a/src/com/hypixel/hytale/procedurallib/supplier/IDoubleRange.java b/src/com/hypixel/hytale/procedurallib/supplier/IDoubleRange.java new file mode 100644 index 0000000..1024c44 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/supplier/IDoubleRange.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.procedurallib.supplier; + +import java.util.Random; +import java.util.function.DoubleSupplier; + +public interface IDoubleRange { + double getValue(double var1); + + double getValue(DoubleSupplier var1); + + double getValue(Random var1); + + double getValue(int var1, double var2, double var4, IDoubleCoordinateSupplier2d var6); + + double getValue(int var1, double var2, double var4, double var6, IDoubleCoordinateSupplier3d var8); +} diff --git a/src/com/hypixel/hytale/procedurallib/supplier/IFloatCoordinateHashSupplier.java b/src/com/hypixel/hytale/procedurallib/supplier/IFloatCoordinateHashSupplier.java new file mode 100644 index 0000000..f189ad9 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/supplier/IFloatCoordinateHashSupplier.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.procedurallib.supplier; + +public interface IFloatCoordinateHashSupplier { + float get(int var1, double var2, double var4, long var6); +} diff --git a/src/com/hypixel/hytale/procedurallib/supplier/IFloatCoordinateSupplier.java b/src/com/hypixel/hytale/procedurallib/supplier/IFloatCoordinateSupplier.java new file mode 100644 index 0000000..2f6dfc9 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/supplier/IFloatCoordinateSupplier.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.procedurallib.supplier; + +public interface IFloatCoordinateSupplier { + float get(int var1, double var2, double var4); + + float get(int var1, double var2, double var4, double var6); +} diff --git a/src/com/hypixel/hytale/procedurallib/supplier/IFloatRange.java b/src/com/hypixel/hytale/procedurallib/supplier/IFloatRange.java new file mode 100644 index 0000000..c2e6c04 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/supplier/IFloatRange.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.procedurallib.supplier; + +import java.util.Random; + +public interface IFloatRange { + float getValue(float var1); + + float getValue(FloatSupplier var1); + + float getValue(Random var1); + + float getValue(int var1, double var2, double var4, IDoubleCoordinateSupplier2d var6); + + float getValue(int var1, double var2, double var4, double var6, IDoubleCoordinateSupplier3d var8); +} diff --git a/src/com/hypixel/hytale/procedurallib/supplier/ISeedDoubleRange.java b/src/com/hypixel/hytale/procedurallib/supplier/ISeedDoubleRange.java new file mode 100644 index 0000000..8f6b090 --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/supplier/ISeedDoubleRange.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.procedurallib.supplier; + +@FunctionalInterface +public interface ISeedDoubleRange { + ISeedDoubleRange DIRECT = (seed, value) -> value; + + double getValue(int var1, double var2); +} diff --git a/src/com/hypixel/hytale/procedurallib/util/IntToIntFunction.java b/src/com/hypixel/hytale/procedurallib/util/IntToIntFunction.java new file mode 100644 index 0000000..fdedc2b --- /dev/null +++ b/src/com/hypixel/hytale/procedurallib/util/IntToIntFunction.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.procedurallib.util; + +@FunctionalInterface +public interface IntToIntFunction { + int applyAsInt(int var1); +} diff --git a/src/com/hypixel/hytale/protocol/AOECircleSelector.java b/src/com/hypixel/hytale/protocol/AOECircleSelector.java new file mode 100644 index 0000000..bb6bf96 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AOECircleSelector.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AOECircleSelector extends Selector { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 17; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 17; + public static final int MAX_SIZE = 17; + public float range; + @Nullable + public Vector3f offset; + + public AOECircleSelector() { + } + + public AOECircleSelector(float range, @Nullable Vector3f offset) { + this.range = range; + this.offset = offset; + } + + public AOECircleSelector(@Nonnull AOECircleSelector other) { + this.range = other.range; + this.offset = other.offset; + } + + @Nonnull + public static AOECircleSelector deserialize(@Nonnull ByteBuf buf, int offset) { + AOECircleSelector obj = new AOECircleSelector(); + byte nullBits = buf.getByte(offset); + obj.range = buf.getFloatLE(offset + 1); + if ((nullBits & 1) != 0) { + obj.offset = Vector3f.deserialize(buf, offset + 5); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 17; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.offset != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.range); + if (this.offset != null) { + this.offset.serialize(buf); + } else { + buf.writeZero(12); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + return 17; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 17 ? ValidationResult.error("Buffer too small: expected at least 17 bytes") : ValidationResult.OK; + } + + public AOECircleSelector clone() { + AOECircleSelector copy = new AOECircleSelector(); + copy.range = this.range; + copy.offset = this.offset != null ? this.offset.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AOECircleSelector other) ? false : this.range == other.range && Objects.equals(this.offset, other.offset); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.range, this.offset); + } +} diff --git a/src/com/hypixel/hytale/protocol/AOECylinderSelector.java b/src/com/hypixel/hytale/protocol/AOECylinderSelector.java new file mode 100644 index 0000000..c84706d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AOECylinderSelector.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AOECylinderSelector extends Selector { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 21; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 21; + public static final int MAX_SIZE = 21; + public float range; + public float height; + @Nullable + public Vector3f offset; + + public AOECylinderSelector() { + } + + public AOECylinderSelector(float range, float height, @Nullable Vector3f offset) { + this.range = range; + this.height = height; + this.offset = offset; + } + + public AOECylinderSelector(@Nonnull AOECylinderSelector other) { + this.range = other.range; + this.height = other.height; + this.offset = other.offset; + } + + @Nonnull + public static AOECylinderSelector deserialize(@Nonnull ByteBuf buf, int offset) { + AOECylinderSelector obj = new AOECylinderSelector(); + byte nullBits = buf.getByte(offset); + obj.range = buf.getFloatLE(offset + 1); + obj.height = buf.getFloatLE(offset + 5); + if ((nullBits & 1) != 0) { + obj.offset = Vector3f.deserialize(buf, offset + 9); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 21; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.offset != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.range); + buf.writeFloatLE(this.height); + if (this.offset != null) { + this.offset.serialize(buf); + } else { + buf.writeZero(12); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + return 21; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 21 ? ValidationResult.error("Buffer too small: expected at least 21 bytes") : ValidationResult.OK; + } + + public AOECylinderSelector clone() { + AOECylinderSelector copy = new AOECylinderSelector(); + copy.range = this.range; + copy.height = this.height; + copy.offset = this.offset != null ? this.offset.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AOECylinderSelector other) + ? false + : this.range == other.range && this.height == other.height && Objects.equals(this.offset, other.offset); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.range, this.height, this.offset); + } +} diff --git a/src/com/hypixel/hytale/protocol/AbilityEffects.java b/src/com/hypixel/hytale/protocol/AbilityEffects.java new file mode 100644 index 0000000..e76098d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AbilityEffects.java @@ -0,0 +1,150 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AbilityEffects { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 4096006; + @Nullable + public InteractionType[] disabled; + + public AbilityEffects() { + } + + public AbilityEffects(@Nullable InteractionType[] disabled) { + this.disabled = disabled; + } + + public AbilityEffects(@Nonnull AbilityEffects other) { + this.disabled = other.disabled; + } + + @Nonnull + public static AbilityEffects deserialize(@Nonnull ByteBuf buf, int offset) { + AbilityEffects obj = new AbilityEffects(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int disabledCount = VarInt.peek(buf, pos); + if (disabledCount < 0) { + throw ProtocolException.negativeLength("Disabled", disabledCount); + } + + if (disabledCount > 4096000) { + throw ProtocolException.arrayTooLong("Disabled", disabledCount, 4096000); + } + + int disabledVarLen = VarInt.size(disabledCount); + if (pos + disabledVarLen + disabledCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Disabled", pos + disabledVarLen + disabledCount * 1, buf.readableBytes()); + } + + pos += disabledVarLen; + obj.disabled = new InteractionType[disabledCount]; + + for (int i = 0; i < disabledCount; i++) { + obj.disabled[i] = InteractionType.fromValue(buf.getByte(pos)); + pos++; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 1; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.disabled != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.disabled != null) { + if (this.disabled.length > 4096000) { + throw ProtocolException.arrayTooLong("Disabled", this.disabled.length, 4096000); + } + + VarInt.write(buf, this.disabled.length); + + for (InteractionType item : this.disabled) { + buf.writeByte(item.getValue()); + } + } + } + + public int computeSize() { + int size = 1; + if (this.disabled != null) { + size += VarInt.size(this.disabled.length) + this.disabled.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int disabledCount = VarInt.peek(buffer, pos); + if (disabledCount < 0) { + return ValidationResult.error("Invalid array count for Disabled"); + } + + if (disabledCount > 4096000) { + return ValidationResult.error("Disabled exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += disabledCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Disabled"); + } + } + + return ValidationResult.OK; + } + } + + public AbilityEffects clone() { + AbilityEffects copy = new AbilityEffects(); + copy.disabled = this.disabled != null ? Arrays.copyOf(this.disabled, this.disabled.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AbilityEffects other ? Arrays.equals((Object[])this.disabled, (Object[])other.disabled) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.disabled); + } +} diff --git a/src/com/hypixel/hytale/protocol/AccumulationMode.java b/src/com/hypixel/hytale/protocol/AccumulationMode.java new file mode 100644 index 0000000..1bbe835 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AccumulationMode.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum AccumulationMode { + Set(0), + Sum(1), + Average(2); + + public static final AccumulationMode[] VALUES = values(); + private final int value; + + private AccumulationMode(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static AccumulationMode fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("AccumulationMode", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/AmbienceFX.java b/src/com/hypixel/hytale/protocol/AmbienceFX.java new file mode 100644 index 0000000..3910966 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AmbienceFX.java @@ -0,0 +1,567 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AmbienceFX { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 18; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 42; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + @Nullable + public AmbienceFXConditions conditions; + @Nullable + public AmbienceFXSound[] sounds; + @Nullable + public AmbienceFXMusic music; + @Nullable + public AmbienceFXAmbientBed ambientBed; + @Nullable + public AmbienceFXSoundEffect soundEffect; + public int priority; + @Nullable + public int[] blockedAmbienceFxIndices; + public int audioCategoryIndex; + + public AmbienceFX() { + } + + public AmbienceFX( + @Nullable String id, + @Nullable AmbienceFXConditions conditions, + @Nullable AmbienceFXSound[] sounds, + @Nullable AmbienceFXMusic music, + @Nullable AmbienceFXAmbientBed ambientBed, + @Nullable AmbienceFXSoundEffect soundEffect, + int priority, + @Nullable int[] blockedAmbienceFxIndices, + int audioCategoryIndex + ) { + this.id = id; + this.conditions = conditions; + this.sounds = sounds; + this.music = music; + this.ambientBed = ambientBed; + this.soundEffect = soundEffect; + this.priority = priority; + this.blockedAmbienceFxIndices = blockedAmbienceFxIndices; + this.audioCategoryIndex = audioCategoryIndex; + } + + public AmbienceFX(@Nonnull AmbienceFX other) { + this.id = other.id; + this.conditions = other.conditions; + this.sounds = other.sounds; + this.music = other.music; + this.ambientBed = other.ambientBed; + this.soundEffect = other.soundEffect; + this.priority = other.priority; + this.blockedAmbienceFxIndices = other.blockedAmbienceFxIndices; + this.audioCategoryIndex = other.audioCategoryIndex; + } + + @Nonnull + public static AmbienceFX deserialize(@Nonnull ByteBuf buf, int offset) { + AmbienceFX obj = new AmbienceFX(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 32) != 0) { + obj.soundEffect = AmbienceFXSoundEffect.deserialize(buf, offset + 1); + } + + obj.priority = buf.getIntLE(offset + 10); + obj.audioCategoryIndex = buf.getIntLE(offset + 14); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 42 + buf.getIntLE(offset + 18); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 42 + buf.getIntLE(offset + 22); + obj.conditions = AmbienceFXConditions.deserialize(buf, varPos1); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 42 + buf.getIntLE(offset + 26); + int soundsCount = VarInt.peek(buf, varPos2); + if (soundsCount < 0) { + throw ProtocolException.negativeLength("Sounds", soundsCount); + } + + if (soundsCount > 4096000) { + throw ProtocolException.arrayTooLong("Sounds", soundsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + soundsCount * 27L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Sounds", varPos2 + varIntLen + soundsCount * 27, buf.readableBytes()); + } + + obj.sounds = new AmbienceFXSound[soundsCount]; + int elemPos = varPos2 + varIntLen; + + for (int i = 0; i < soundsCount; i++) { + obj.sounds[i] = AmbienceFXSound.deserialize(buf, elemPos); + elemPos += AmbienceFXSound.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 42 + buf.getIntLE(offset + 30); + obj.music = AmbienceFXMusic.deserialize(buf, varPos3); + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 42 + buf.getIntLE(offset + 34); + obj.ambientBed = AmbienceFXAmbientBed.deserialize(buf, varPos4); + } + + if ((nullBits & 64) != 0) { + int varPos5 = offset + 42 + buf.getIntLE(offset + 38); + int blockedAmbienceFxIndicesCount = VarInt.peek(buf, varPos5); + if (blockedAmbienceFxIndicesCount < 0) { + throw ProtocolException.negativeLength("BlockedAmbienceFxIndices", blockedAmbienceFxIndicesCount); + } + + if (blockedAmbienceFxIndicesCount > 4096000) { + throw ProtocolException.arrayTooLong("BlockedAmbienceFxIndices", blockedAmbienceFxIndicesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + if (varPos5 + varIntLen + blockedAmbienceFxIndicesCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("BlockedAmbienceFxIndices", varPos5 + varIntLen + blockedAmbienceFxIndicesCount * 4, buf.readableBytes()); + } + + obj.blockedAmbienceFxIndices = new int[blockedAmbienceFxIndicesCount]; + + for (int i = 0; i < blockedAmbienceFxIndicesCount; i++) { + obj.blockedAmbienceFxIndices[i] = buf.getIntLE(varPos5 + varIntLen + i * 4); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 42; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 18); + int pos0 = offset + 42 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 22); + int pos1 = offset + 42 + fieldOffset1; + pos1 += AmbienceFXConditions.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 26); + int pos2 = offset + 42 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2); + + for (int i = 0; i < arrLen; i++) { + pos2 += AmbienceFXSound.computeBytesConsumed(buf, pos2); + } + + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 30); + int pos3 = offset + 42 + fieldOffset3; + pos3 += AmbienceFXMusic.computeBytesConsumed(buf, pos3); + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 34); + int pos4 = offset + 42 + fieldOffset4; + pos4 += AmbienceFXAmbientBed.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 64) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 38); + int pos5 = offset + 42 + fieldOffset5; + int arrLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + arrLen * 4; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.conditions != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.sounds != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.music != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.ambientBed != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.soundEffect != null) { + nullBits = (byte)(nullBits | 32); + } + + if (this.blockedAmbienceFxIndices != null) { + nullBits = (byte)(nullBits | 64); + } + + buf.writeByte(nullBits); + if (this.soundEffect != null) { + this.soundEffect.serialize(buf); + } else { + buf.writeZero(9); + } + + buf.writeIntLE(this.priority); + buf.writeIntLE(this.audioCategoryIndex); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int conditionsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int soundsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int musicOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int ambientBedOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int blockedAmbienceFxIndicesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.conditions != null) { + buf.setIntLE(conditionsOffsetSlot, buf.writerIndex() - varBlockStart); + this.conditions.serialize(buf); + } else { + buf.setIntLE(conditionsOffsetSlot, -1); + } + + if (this.sounds != null) { + buf.setIntLE(soundsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.sounds.length > 4096000) { + throw ProtocolException.arrayTooLong("Sounds", this.sounds.length, 4096000); + } + + VarInt.write(buf, this.sounds.length); + + for (AmbienceFXSound item : this.sounds) { + item.serialize(buf); + } + } else { + buf.setIntLE(soundsOffsetSlot, -1); + } + + if (this.music != null) { + buf.setIntLE(musicOffsetSlot, buf.writerIndex() - varBlockStart); + this.music.serialize(buf); + } else { + buf.setIntLE(musicOffsetSlot, -1); + } + + if (this.ambientBed != null) { + buf.setIntLE(ambientBedOffsetSlot, buf.writerIndex() - varBlockStart); + this.ambientBed.serialize(buf); + } else { + buf.setIntLE(ambientBedOffsetSlot, -1); + } + + if (this.blockedAmbienceFxIndices != null) { + buf.setIntLE(blockedAmbienceFxIndicesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.blockedAmbienceFxIndices.length > 4096000) { + throw ProtocolException.arrayTooLong("BlockedAmbienceFxIndices", this.blockedAmbienceFxIndices.length, 4096000); + } + + VarInt.write(buf, this.blockedAmbienceFxIndices.length); + + for (int item : this.blockedAmbienceFxIndices) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(blockedAmbienceFxIndicesOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 42; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.conditions != null) { + size += this.conditions.computeSize(); + } + + if (this.sounds != null) { + size += VarInt.size(this.sounds.length) + this.sounds.length * 27; + } + + if (this.music != null) { + size += this.music.computeSize(); + } + + if (this.ambientBed != null) { + size += this.ambientBed.computeSize(); + } + + if (this.blockedAmbienceFxIndices != null) { + size += VarInt.size(this.blockedAmbienceFxIndices.length) + this.blockedAmbienceFxIndices.length * 4; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 42) { + return ValidationResult.error("Buffer too small: expected at least 42 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 18); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 42 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int conditionsOffset = buffer.getIntLE(offset + 22); + if (conditionsOffset < 0) { + return ValidationResult.error("Invalid offset for Conditions"); + } + + int posx = offset + 42 + conditionsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Conditions"); + } + + ValidationResult conditionsResult = AmbienceFXConditions.validateStructure(buffer, posx); + if (!conditionsResult.isValid()) { + return ValidationResult.error("Invalid Conditions: " + conditionsResult.error()); + } + + posx += AmbienceFXConditions.computeBytesConsumed(buffer, posx); + } + + if ((nullBits & 4) != 0) { + int soundsOffset = buffer.getIntLE(offset + 26); + if (soundsOffset < 0) { + return ValidationResult.error("Invalid offset for Sounds"); + } + + int posxx = offset + 42 + soundsOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Sounds"); + } + + int soundsCount = VarInt.peek(buffer, posxx); + if (soundsCount < 0) { + return ValidationResult.error("Invalid array count for Sounds"); + } + + if (soundsCount > 4096000) { + return ValidationResult.error("Sounds exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += soundsCount * 27; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Sounds"); + } + } + + if ((nullBits & 8) != 0) { + int musicOffset = buffer.getIntLE(offset + 30); + if (musicOffset < 0) { + return ValidationResult.error("Invalid offset for Music"); + } + + int posxxx = offset + 42 + musicOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Music"); + } + + ValidationResult musicResult = AmbienceFXMusic.validateStructure(buffer, posxxx); + if (!musicResult.isValid()) { + return ValidationResult.error("Invalid Music: " + musicResult.error()); + } + + posxxx += AmbienceFXMusic.computeBytesConsumed(buffer, posxxx); + } + + if ((nullBits & 16) != 0) { + int ambientBedOffset = buffer.getIntLE(offset + 34); + if (ambientBedOffset < 0) { + return ValidationResult.error("Invalid offset for AmbientBed"); + } + + int posxxxx = offset + 42 + ambientBedOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for AmbientBed"); + } + + ValidationResult ambientBedResult = AmbienceFXAmbientBed.validateStructure(buffer, posxxxx); + if (!ambientBedResult.isValid()) { + return ValidationResult.error("Invalid AmbientBed: " + ambientBedResult.error()); + } + + posxxxx += AmbienceFXAmbientBed.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 64) != 0) { + int blockedAmbienceFxIndicesOffset = buffer.getIntLE(offset + 38); + if (blockedAmbienceFxIndicesOffset < 0) { + return ValidationResult.error("Invalid offset for BlockedAmbienceFxIndices"); + } + + int posxxxxx = offset + 42 + blockedAmbienceFxIndicesOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BlockedAmbienceFxIndices"); + } + + int blockedAmbienceFxIndicesCount = VarInt.peek(buffer, posxxxxx); + if (blockedAmbienceFxIndicesCount < 0) { + return ValidationResult.error("Invalid array count for BlockedAmbienceFxIndices"); + } + + if (blockedAmbienceFxIndicesCount > 4096000) { + return ValidationResult.error("BlockedAmbienceFxIndices exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += blockedAmbienceFxIndicesCount * 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading BlockedAmbienceFxIndices"); + } + } + + return ValidationResult.OK; + } + } + + public AmbienceFX clone() { + AmbienceFX copy = new AmbienceFX(); + copy.id = this.id; + copy.conditions = this.conditions != null ? this.conditions.clone() : null; + copy.sounds = this.sounds != null ? Arrays.stream(this.sounds).map(e -> e.clone()).toArray(AmbienceFXSound[]::new) : null; + copy.music = this.music != null ? this.music.clone() : null; + copy.ambientBed = this.ambientBed != null ? this.ambientBed.clone() : null; + copy.soundEffect = this.soundEffect != null ? this.soundEffect.clone() : null; + copy.priority = this.priority; + copy.blockedAmbienceFxIndices = this.blockedAmbienceFxIndices != null + ? Arrays.copyOf(this.blockedAmbienceFxIndices, this.blockedAmbienceFxIndices.length) + : null; + copy.audioCategoryIndex = this.audioCategoryIndex; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AmbienceFX other) + ? false + : Objects.equals(this.id, other.id) + && Objects.equals(this.conditions, other.conditions) + && Arrays.equals((Object[])this.sounds, (Object[])other.sounds) + && Objects.equals(this.music, other.music) + && Objects.equals(this.ambientBed, other.ambientBed) + && Objects.equals(this.soundEffect, other.soundEffect) + && this.priority == other.priority + && Arrays.equals(this.blockedAmbienceFxIndices, other.blockedAmbienceFxIndices) + && this.audioCategoryIndex == other.audioCategoryIndex; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.id); + result = 31 * result + Objects.hashCode(this.conditions); + result = 31 * result + Arrays.hashCode((Object[])this.sounds); + result = 31 * result + Objects.hashCode(this.music); + result = 31 * result + Objects.hashCode(this.ambientBed); + result = 31 * result + Objects.hashCode(this.soundEffect); + result = 31 * result + Integer.hashCode(this.priority); + result = 31 * result + Arrays.hashCode(this.blockedAmbienceFxIndices); + return 31 * result + Integer.hashCode(this.audioCategoryIndex); + } +} diff --git a/src/com/hypixel/hytale/protocol/AmbienceFXAltitude.java b/src/com/hypixel/hytale/protocol/AmbienceFXAltitude.java new file mode 100644 index 0000000..54dc72f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AmbienceFXAltitude.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum AmbienceFXAltitude { + Normal(0), + Lowest(1), + Highest(2), + Random(3); + + public static final AmbienceFXAltitude[] VALUES = values(); + private final int value; + + private AmbienceFXAltitude(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static AmbienceFXAltitude fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("AmbienceFXAltitude", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/AmbienceFXAmbientBed.java b/src/com/hypixel/hytale/protocol/AmbienceFXAmbientBed.java new file mode 100644 index 0000000..f9602f6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AmbienceFXAmbientBed.java @@ -0,0 +1,148 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AmbienceFXAmbientBed { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 16384011; + @Nullable + public String track; + public float volume; + @Nonnull + public AmbienceTransitionSpeed transitionSpeed = AmbienceTransitionSpeed.Default; + + public AmbienceFXAmbientBed() { + } + + public AmbienceFXAmbientBed(@Nullable String track, float volume, @Nonnull AmbienceTransitionSpeed transitionSpeed) { + this.track = track; + this.volume = volume; + this.transitionSpeed = transitionSpeed; + } + + public AmbienceFXAmbientBed(@Nonnull AmbienceFXAmbientBed other) { + this.track = other.track; + this.volume = other.volume; + this.transitionSpeed = other.transitionSpeed; + } + + @Nonnull + public static AmbienceFXAmbientBed deserialize(@Nonnull ByteBuf buf, int offset) { + AmbienceFXAmbientBed obj = new AmbienceFXAmbientBed(); + byte nullBits = buf.getByte(offset); + obj.volume = buf.getFloatLE(offset + 1); + obj.transitionSpeed = AmbienceTransitionSpeed.fromValue(buf.getByte(offset + 5)); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int trackLen = VarInt.peek(buf, pos); + if (trackLen < 0) { + throw ProtocolException.negativeLength("Track", trackLen); + } + + if (trackLen > 4096000) { + throw ProtocolException.stringTooLong("Track", trackLen, 4096000); + } + + int trackVarLen = VarInt.length(buf, pos); + obj.track = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += trackVarLen + trackLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.track != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.volume); + buf.writeByte(this.transitionSpeed.getValue()); + if (this.track != null) { + PacketIO.writeVarString(buf, this.track, 4096000); + } + } + + public int computeSize() { + int size = 6; + if (this.track != null) { + size += PacketIO.stringSize(this.track); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int trackLen = VarInt.peek(buffer, pos); + if (trackLen < 0) { + return ValidationResult.error("Invalid string length for Track"); + } + + if (trackLen > 4096000) { + return ValidationResult.error("Track exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += trackLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Track"); + } + } + + return ValidationResult.OK; + } + } + + public AmbienceFXAmbientBed clone() { + AmbienceFXAmbientBed copy = new AmbienceFXAmbientBed(); + copy.track = this.track; + copy.volume = this.volume; + copy.transitionSpeed = this.transitionSpeed; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AmbienceFXAmbientBed other) + ? false + : Objects.equals(this.track, other.track) && this.volume == other.volume && Objects.equals(this.transitionSpeed, other.transitionSpeed); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.track, this.volume, this.transitionSpeed); + } +} diff --git a/src/com/hypixel/hytale/protocol/AmbienceFXBlockSoundSet.java b/src/com/hypixel/hytale/protocol/AmbienceFXBlockSoundSet.java new file mode 100644 index 0000000..82e7ba2 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AmbienceFXBlockSoundSet.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AmbienceFXBlockSoundSet { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 13; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 13; + public int blockSoundSetIndex; + @Nullable + public Rangef percent; + + public AmbienceFXBlockSoundSet() { + } + + public AmbienceFXBlockSoundSet(int blockSoundSetIndex, @Nullable Rangef percent) { + this.blockSoundSetIndex = blockSoundSetIndex; + this.percent = percent; + } + + public AmbienceFXBlockSoundSet(@Nonnull AmbienceFXBlockSoundSet other) { + this.blockSoundSetIndex = other.blockSoundSetIndex; + this.percent = other.percent; + } + + @Nonnull + public static AmbienceFXBlockSoundSet deserialize(@Nonnull ByteBuf buf, int offset) { + AmbienceFXBlockSoundSet obj = new AmbienceFXBlockSoundSet(); + byte nullBits = buf.getByte(offset); + obj.blockSoundSetIndex = buf.getIntLE(offset + 1); + if ((nullBits & 1) != 0) { + obj.percent = Rangef.deserialize(buf, offset + 5); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 13; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.percent != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.blockSoundSetIndex); + if (this.percent != null) { + this.percent.serialize(buf); + } else { + buf.writeZero(8); + } + } + + public int computeSize() { + return 13; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 13 ? ValidationResult.error("Buffer too small: expected at least 13 bytes") : ValidationResult.OK; + } + + public AmbienceFXBlockSoundSet clone() { + AmbienceFXBlockSoundSet copy = new AmbienceFXBlockSoundSet(); + copy.blockSoundSetIndex = this.blockSoundSetIndex; + copy.percent = this.percent != null ? this.percent.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AmbienceFXBlockSoundSet other) + ? false + : this.blockSoundSetIndex == other.blockSoundSetIndex && Objects.equals(this.percent, other.percent); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.blockSoundSetIndex, this.percent); + } +} diff --git a/src/com/hypixel/hytale/protocol/AmbienceFXConditions.java b/src/com/hypixel/hytale/protocol/AmbienceFXConditions.java new file mode 100644 index 0000000..e1a8828 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AmbienceFXConditions.java @@ -0,0 +1,649 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AmbienceFXConditions { + public static final int NULLABLE_BIT_FIELD_SIZE = 2; + public static final int FIXED_BLOCK_SIZE = 41; + public static final int VARIABLE_FIELD_COUNT = 4; + public static final int VARIABLE_BLOCK_START = 57; + public static final int MAX_SIZE = 102400077; + public boolean never; + @Nullable + public int[] environmentIndices; + @Nullable + public int[] weatherIndices; + @Nullable + public int[] fluidFXIndices; + public int environmentTagPatternIndex; + public int weatherTagPatternIndex; + @Nullable + public AmbienceFXBlockSoundSet[] surroundingBlockSoundSets; + @Nullable + public Range altitude; + @Nullable + public Rangeb walls; + public boolean roof; + public int roofMaterialTagPatternIndex; + public boolean floor; + @Nullable + public Rangeb sunLightLevel; + @Nullable + public Rangeb torchLightLevel; + @Nullable + public Rangeb globalLightLevel; + @Nullable + public Rangef dayTime; + + public AmbienceFXConditions() { + } + + public AmbienceFXConditions( + boolean never, + @Nullable int[] environmentIndices, + @Nullable int[] weatherIndices, + @Nullable int[] fluidFXIndices, + int environmentTagPatternIndex, + int weatherTagPatternIndex, + @Nullable AmbienceFXBlockSoundSet[] surroundingBlockSoundSets, + @Nullable Range altitude, + @Nullable Rangeb walls, + boolean roof, + int roofMaterialTagPatternIndex, + boolean floor, + @Nullable Rangeb sunLightLevel, + @Nullable Rangeb torchLightLevel, + @Nullable Rangeb globalLightLevel, + @Nullable Rangef dayTime + ) { + this.never = never; + this.environmentIndices = environmentIndices; + this.weatherIndices = weatherIndices; + this.fluidFXIndices = fluidFXIndices; + this.environmentTagPatternIndex = environmentTagPatternIndex; + this.weatherTagPatternIndex = weatherTagPatternIndex; + this.surroundingBlockSoundSets = surroundingBlockSoundSets; + this.altitude = altitude; + this.walls = walls; + this.roof = roof; + this.roofMaterialTagPatternIndex = roofMaterialTagPatternIndex; + this.floor = floor; + this.sunLightLevel = sunLightLevel; + this.torchLightLevel = torchLightLevel; + this.globalLightLevel = globalLightLevel; + this.dayTime = dayTime; + } + + public AmbienceFXConditions(@Nonnull AmbienceFXConditions other) { + this.never = other.never; + this.environmentIndices = other.environmentIndices; + this.weatherIndices = other.weatherIndices; + this.fluidFXIndices = other.fluidFXIndices; + this.environmentTagPatternIndex = other.environmentTagPatternIndex; + this.weatherTagPatternIndex = other.weatherTagPatternIndex; + this.surroundingBlockSoundSets = other.surroundingBlockSoundSets; + this.altitude = other.altitude; + this.walls = other.walls; + this.roof = other.roof; + this.roofMaterialTagPatternIndex = other.roofMaterialTagPatternIndex; + this.floor = other.floor; + this.sunLightLevel = other.sunLightLevel; + this.torchLightLevel = other.torchLightLevel; + this.globalLightLevel = other.globalLightLevel; + this.dayTime = other.dayTime; + } + + @Nonnull + public static AmbienceFXConditions deserialize(@Nonnull ByteBuf buf, int offset) { + AmbienceFXConditions obj = new AmbienceFXConditions(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + obj.never = buf.getByte(offset + 2) != 0; + obj.environmentTagPatternIndex = buf.getIntLE(offset + 3); + obj.weatherTagPatternIndex = buf.getIntLE(offset + 7); + if ((nullBits[0] & 16) != 0) { + obj.altitude = Range.deserialize(buf, offset + 11); + } + + if ((nullBits[0] & 32) != 0) { + obj.walls = Rangeb.deserialize(buf, offset + 19); + } + + obj.roof = buf.getByte(offset + 21) != 0; + obj.roofMaterialTagPatternIndex = buf.getIntLE(offset + 22); + obj.floor = buf.getByte(offset + 26) != 0; + if ((nullBits[0] & 64) != 0) { + obj.sunLightLevel = Rangeb.deserialize(buf, offset + 27); + } + + if ((nullBits[0] & 128) != 0) { + obj.torchLightLevel = Rangeb.deserialize(buf, offset + 29); + } + + if ((nullBits[1] & 1) != 0) { + obj.globalLightLevel = Rangeb.deserialize(buf, offset + 31); + } + + if ((nullBits[1] & 2) != 0) { + obj.dayTime = Rangef.deserialize(buf, offset + 33); + } + + if ((nullBits[0] & 1) != 0) { + int varPos0 = offset + 57 + buf.getIntLE(offset + 41); + int environmentIndicesCount = VarInt.peek(buf, varPos0); + if (environmentIndicesCount < 0) { + throw ProtocolException.negativeLength("EnvironmentIndices", environmentIndicesCount); + } + + if (environmentIndicesCount > 4096000) { + throw ProtocolException.arrayTooLong("EnvironmentIndices", environmentIndicesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + environmentIndicesCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("EnvironmentIndices", varPos0 + varIntLen + environmentIndicesCount * 4, buf.readableBytes()); + } + + obj.environmentIndices = new int[environmentIndicesCount]; + + for (int i = 0; i < environmentIndicesCount; i++) { + obj.environmentIndices[i] = buf.getIntLE(varPos0 + varIntLen + i * 4); + } + } + + if ((nullBits[0] & 2) != 0) { + int varPos1 = offset + 57 + buf.getIntLE(offset + 45); + int weatherIndicesCount = VarInt.peek(buf, varPos1); + if (weatherIndicesCount < 0) { + throw ProtocolException.negativeLength("WeatherIndices", weatherIndicesCount); + } + + if (weatherIndicesCount > 4096000) { + throw ProtocolException.arrayTooLong("WeatherIndices", weatherIndicesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + weatherIndicesCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("WeatherIndices", varPos1 + varIntLen + weatherIndicesCount * 4, buf.readableBytes()); + } + + obj.weatherIndices = new int[weatherIndicesCount]; + + for (int i = 0; i < weatherIndicesCount; i++) { + obj.weatherIndices[i] = buf.getIntLE(varPos1 + varIntLen + i * 4); + } + } + + if ((nullBits[0] & 4) != 0) { + int varPos2 = offset + 57 + buf.getIntLE(offset + 49); + int fluidFXIndicesCount = VarInt.peek(buf, varPos2); + if (fluidFXIndicesCount < 0) { + throw ProtocolException.negativeLength("FluidFXIndices", fluidFXIndicesCount); + } + + if (fluidFXIndicesCount > 4096000) { + throw ProtocolException.arrayTooLong("FluidFXIndices", fluidFXIndicesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + fluidFXIndicesCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("FluidFXIndices", varPos2 + varIntLen + fluidFXIndicesCount * 4, buf.readableBytes()); + } + + obj.fluidFXIndices = new int[fluidFXIndicesCount]; + + for (int i = 0; i < fluidFXIndicesCount; i++) { + obj.fluidFXIndices[i] = buf.getIntLE(varPos2 + varIntLen + i * 4); + } + } + + if ((nullBits[0] & 8) != 0) { + int varPos3 = offset + 57 + buf.getIntLE(offset + 53); + int surroundingBlockSoundSetsCount = VarInt.peek(buf, varPos3); + if (surroundingBlockSoundSetsCount < 0) { + throw ProtocolException.negativeLength("SurroundingBlockSoundSets", surroundingBlockSoundSetsCount); + } + + if (surroundingBlockSoundSetsCount > 4096000) { + throw ProtocolException.arrayTooLong("SurroundingBlockSoundSets", surroundingBlockSoundSetsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + surroundingBlockSoundSetsCount * 13L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("SurroundingBlockSoundSets", varPos3 + varIntLen + surroundingBlockSoundSetsCount * 13, buf.readableBytes()); + } + + obj.surroundingBlockSoundSets = new AmbienceFXBlockSoundSet[surroundingBlockSoundSetsCount]; + int elemPos = varPos3 + varIntLen; + + for (int i = 0; i < surroundingBlockSoundSetsCount; i++) { + obj.surroundingBlockSoundSets[i] = AmbienceFXBlockSoundSet.deserialize(buf, elemPos); + elemPos += AmbienceFXBlockSoundSet.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + int maxEnd = 57; + if ((nullBits[0] & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 41); + int pos0 = offset + 57 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + arrLen * 4; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits[0] & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 45); + int pos1 = offset + 57 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + arrLen * 4; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits[0] & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 49); + int pos2 = offset + 57 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + arrLen * 4; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits[0] & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 53); + int pos3 = offset + 57 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3); + + for (int i = 0; i < arrLen; i++) { + pos3 += AmbienceFXBlockSoundSet.computeBytesConsumed(buf, pos3); + } + + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte[] nullBits = new byte[2]; + if (this.environmentIndices != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.weatherIndices != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.fluidFXIndices != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.surroundingBlockSoundSets != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.altitude != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.walls != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.sunLightLevel != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.torchLightLevel != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.globalLightLevel != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + if (this.dayTime != null) { + nullBits[1] = (byte)(nullBits[1] | 2); + } + + buf.writeBytes(nullBits); + buf.writeByte(this.never ? 1 : 0); + buf.writeIntLE(this.environmentTagPatternIndex); + buf.writeIntLE(this.weatherTagPatternIndex); + if (this.altitude != null) { + this.altitude.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.walls != null) { + this.walls.serialize(buf); + } else { + buf.writeZero(2); + } + + buf.writeByte(this.roof ? 1 : 0); + buf.writeIntLE(this.roofMaterialTagPatternIndex); + buf.writeByte(this.floor ? 1 : 0); + if (this.sunLightLevel != null) { + this.sunLightLevel.serialize(buf); + } else { + buf.writeZero(2); + } + + if (this.torchLightLevel != null) { + this.torchLightLevel.serialize(buf); + } else { + buf.writeZero(2); + } + + if (this.globalLightLevel != null) { + this.globalLightLevel.serialize(buf); + } else { + buf.writeZero(2); + } + + if (this.dayTime != null) { + this.dayTime.serialize(buf); + } else { + buf.writeZero(8); + } + + int environmentIndicesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int weatherIndicesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int fluidFXIndicesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int surroundingBlockSoundSetsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.environmentIndices != null) { + buf.setIntLE(environmentIndicesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.environmentIndices.length > 4096000) { + throw ProtocolException.arrayTooLong("EnvironmentIndices", this.environmentIndices.length, 4096000); + } + + VarInt.write(buf, this.environmentIndices.length); + + for (int item : this.environmentIndices) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(environmentIndicesOffsetSlot, -1); + } + + if (this.weatherIndices != null) { + buf.setIntLE(weatherIndicesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.weatherIndices.length > 4096000) { + throw ProtocolException.arrayTooLong("WeatherIndices", this.weatherIndices.length, 4096000); + } + + VarInt.write(buf, this.weatherIndices.length); + + for (int item : this.weatherIndices) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(weatherIndicesOffsetSlot, -1); + } + + if (this.fluidFXIndices != null) { + buf.setIntLE(fluidFXIndicesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.fluidFXIndices.length > 4096000) { + throw ProtocolException.arrayTooLong("FluidFXIndices", this.fluidFXIndices.length, 4096000); + } + + VarInt.write(buf, this.fluidFXIndices.length); + + for (int item : this.fluidFXIndices) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(fluidFXIndicesOffsetSlot, -1); + } + + if (this.surroundingBlockSoundSets != null) { + buf.setIntLE(surroundingBlockSoundSetsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.surroundingBlockSoundSets.length > 4096000) { + throw ProtocolException.arrayTooLong("SurroundingBlockSoundSets", this.surroundingBlockSoundSets.length, 4096000); + } + + VarInt.write(buf, this.surroundingBlockSoundSets.length); + + for (AmbienceFXBlockSoundSet item : this.surroundingBlockSoundSets) { + item.serialize(buf); + } + } else { + buf.setIntLE(surroundingBlockSoundSetsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 57; + if (this.environmentIndices != null) { + size += VarInt.size(this.environmentIndices.length) + this.environmentIndices.length * 4; + } + + if (this.weatherIndices != null) { + size += VarInt.size(this.weatherIndices.length) + this.weatherIndices.length * 4; + } + + if (this.fluidFXIndices != null) { + size += VarInt.size(this.fluidFXIndices.length) + this.fluidFXIndices.length * 4; + } + + if (this.surroundingBlockSoundSets != null) { + size += VarInt.size(this.surroundingBlockSoundSets.length) + this.surroundingBlockSoundSets.length * 13; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 57) { + return ValidationResult.error("Buffer too small: expected at least 57 bytes"); + } else { + byte[] nullBits = PacketIO.readBytes(buffer, offset, 2); + if ((nullBits[0] & 1) != 0) { + int environmentIndicesOffset = buffer.getIntLE(offset + 41); + if (environmentIndicesOffset < 0) { + return ValidationResult.error("Invalid offset for EnvironmentIndices"); + } + + int pos = offset + 57 + environmentIndicesOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for EnvironmentIndices"); + } + + int environmentIndicesCount = VarInt.peek(buffer, pos); + if (environmentIndicesCount < 0) { + return ValidationResult.error("Invalid array count for EnvironmentIndices"); + } + + if (environmentIndicesCount > 4096000) { + return ValidationResult.error("EnvironmentIndices exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += environmentIndicesCount * 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading EnvironmentIndices"); + } + } + + if ((nullBits[0] & 2) != 0) { + int weatherIndicesOffset = buffer.getIntLE(offset + 45); + if (weatherIndicesOffset < 0) { + return ValidationResult.error("Invalid offset for WeatherIndices"); + } + + int posx = offset + 57 + weatherIndicesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for WeatherIndices"); + } + + int weatherIndicesCount = VarInt.peek(buffer, posx); + if (weatherIndicesCount < 0) { + return ValidationResult.error("Invalid array count for WeatherIndices"); + } + + if (weatherIndicesCount > 4096000) { + return ValidationResult.error("WeatherIndices exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += weatherIndicesCount * 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading WeatherIndices"); + } + } + + if ((nullBits[0] & 4) != 0) { + int fluidFXIndicesOffset = buffer.getIntLE(offset + 49); + if (fluidFXIndicesOffset < 0) { + return ValidationResult.error("Invalid offset for FluidFXIndices"); + } + + int posxx = offset + 57 + fluidFXIndicesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FluidFXIndices"); + } + + int fluidFXIndicesCount = VarInt.peek(buffer, posxx); + if (fluidFXIndicesCount < 0) { + return ValidationResult.error("Invalid array count for FluidFXIndices"); + } + + if (fluidFXIndicesCount > 4096000) { + return ValidationResult.error("FluidFXIndices exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += fluidFXIndicesCount * 4; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading FluidFXIndices"); + } + } + + if ((nullBits[0] & 8) != 0) { + int surroundingBlockSoundSetsOffset = buffer.getIntLE(offset + 53); + if (surroundingBlockSoundSetsOffset < 0) { + return ValidationResult.error("Invalid offset for SurroundingBlockSoundSets"); + } + + int posxxx = offset + 57 + surroundingBlockSoundSetsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SurroundingBlockSoundSets"); + } + + int surroundingBlockSoundSetsCount = VarInt.peek(buffer, posxxx); + if (surroundingBlockSoundSetsCount < 0) { + return ValidationResult.error("Invalid array count for SurroundingBlockSoundSets"); + } + + if (surroundingBlockSoundSetsCount > 4096000) { + return ValidationResult.error("SurroundingBlockSoundSets exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += surroundingBlockSoundSetsCount * 13; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading SurroundingBlockSoundSets"); + } + } + + return ValidationResult.OK; + } + } + + public AmbienceFXConditions clone() { + AmbienceFXConditions copy = new AmbienceFXConditions(); + copy.never = this.never; + copy.environmentIndices = this.environmentIndices != null ? Arrays.copyOf(this.environmentIndices, this.environmentIndices.length) : null; + copy.weatherIndices = this.weatherIndices != null ? Arrays.copyOf(this.weatherIndices, this.weatherIndices.length) : null; + copy.fluidFXIndices = this.fluidFXIndices != null ? Arrays.copyOf(this.fluidFXIndices, this.fluidFXIndices.length) : null; + copy.environmentTagPatternIndex = this.environmentTagPatternIndex; + copy.weatherTagPatternIndex = this.weatherTagPatternIndex; + copy.surroundingBlockSoundSets = this.surroundingBlockSoundSets != null + ? Arrays.stream(this.surroundingBlockSoundSets).map(e -> e.clone()).toArray(AmbienceFXBlockSoundSet[]::new) + : null; + copy.altitude = this.altitude != null ? this.altitude.clone() : null; + copy.walls = this.walls != null ? this.walls.clone() : null; + copy.roof = this.roof; + copy.roofMaterialTagPatternIndex = this.roofMaterialTagPatternIndex; + copy.floor = this.floor; + copy.sunLightLevel = this.sunLightLevel != null ? this.sunLightLevel.clone() : null; + copy.torchLightLevel = this.torchLightLevel != null ? this.torchLightLevel.clone() : null; + copy.globalLightLevel = this.globalLightLevel != null ? this.globalLightLevel.clone() : null; + copy.dayTime = this.dayTime != null ? this.dayTime.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AmbienceFXConditions other) + ? false + : this.never == other.never + && Arrays.equals(this.environmentIndices, other.environmentIndices) + && Arrays.equals(this.weatherIndices, other.weatherIndices) + && Arrays.equals(this.fluidFXIndices, other.fluidFXIndices) + && this.environmentTagPatternIndex == other.environmentTagPatternIndex + && this.weatherTagPatternIndex == other.weatherTagPatternIndex + && Arrays.equals((Object[])this.surroundingBlockSoundSets, (Object[])other.surroundingBlockSoundSets) + && Objects.equals(this.altitude, other.altitude) + && Objects.equals(this.walls, other.walls) + && this.roof == other.roof + && this.roofMaterialTagPatternIndex == other.roofMaterialTagPatternIndex + && this.floor == other.floor + && Objects.equals(this.sunLightLevel, other.sunLightLevel) + && Objects.equals(this.torchLightLevel, other.torchLightLevel) + && Objects.equals(this.globalLightLevel, other.globalLightLevel) + && Objects.equals(this.dayTime, other.dayTime); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Boolean.hashCode(this.never); + result = 31 * result + Arrays.hashCode(this.environmentIndices); + result = 31 * result + Arrays.hashCode(this.weatherIndices); + result = 31 * result + Arrays.hashCode(this.fluidFXIndices); + result = 31 * result + Integer.hashCode(this.environmentTagPatternIndex); + result = 31 * result + Integer.hashCode(this.weatherTagPatternIndex); + result = 31 * result + Arrays.hashCode((Object[])this.surroundingBlockSoundSets); + result = 31 * result + Objects.hashCode(this.altitude); + result = 31 * result + Objects.hashCode(this.walls); + result = 31 * result + Boolean.hashCode(this.roof); + result = 31 * result + Integer.hashCode(this.roofMaterialTagPatternIndex); + result = 31 * result + Boolean.hashCode(this.floor); + result = 31 * result + Objects.hashCode(this.sunLightLevel); + result = 31 * result + Objects.hashCode(this.torchLightLevel); + result = 31 * result + Objects.hashCode(this.globalLightLevel); + return 31 * result + Objects.hashCode(this.dayTime); + } +} diff --git a/src/com/hypixel/hytale/protocol/AmbienceFXMusic.java b/src/com/hypixel/hytale/protocol/AmbienceFXMusic.java new file mode 100644 index 0000000..67950ac --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AmbienceFXMusic.java @@ -0,0 +1,188 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AmbienceFXMusic { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String[] tracks; + public float volume; + + public AmbienceFXMusic() { + } + + public AmbienceFXMusic(@Nullable String[] tracks, float volume) { + this.tracks = tracks; + this.volume = volume; + } + + public AmbienceFXMusic(@Nonnull AmbienceFXMusic other) { + this.tracks = other.tracks; + this.volume = other.volume; + } + + @Nonnull + public static AmbienceFXMusic deserialize(@Nonnull ByteBuf buf, int offset) { + AmbienceFXMusic obj = new AmbienceFXMusic(); + byte nullBits = buf.getByte(offset); + obj.volume = buf.getFloatLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int tracksCount = VarInt.peek(buf, pos); + if (tracksCount < 0) { + throw ProtocolException.negativeLength("Tracks", tracksCount); + } + + if (tracksCount > 4096000) { + throw ProtocolException.arrayTooLong("Tracks", tracksCount, 4096000); + } + + int tracksVarLen = VarInt.size(tracksCount); + if (pos + tracksVarLen + tracksCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tracks", pos + tracksVarLen + tracksCount * 1, buf.readableBytes()); + } + + pos += tracksVarLen; + obj.tracks = new String[tracksCount]; + + for (int i = 0; i < tracksCount; i++) { + int strLen = VarInt.peek(buf, pos); + if (strLen < 0) { + throw ProtocolException.negativeLength("tracks[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("tracks[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, pos); + obj.tracks[i] = PacketIO.readVarString(buf, pos); + pos += strVarLen + strLen; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.tracks != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.volume); + if (this.tracks != null) { + if (this.tracks.length > 4096000) { + throw ProtocolException.arrayTooLong("Tracks", this.tracks.length, 4096000); + } + + VarInt.write(buf, this.tracks.length); + + for (String item : this.tracks) { + PacketIO.writeVarString(buf, item, 4096000); + } + } + } + + public int computeSize() { + int size = 5; + if (this.tracks != null) { + int tracksSize = 0; + + for (String elem : this.tracks) { + tracksSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.tracks.length) + tracksSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int tracksCount = VarInt.peek(buffer, pos); + if (tracksCount < 0) { + return ValidationResult.error("Invalid array count for Tracks"); + } + + if (tracksCount > 4096000) { + return ValidationResult.error("Tracks exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < tracksCount; i++) { + int strLen = VarInt.peek(buffer, pos); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in Tracks"); + } + + pos += VarInt.length(buffer, pos); + pos += strLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in Tracks"); + } + } + } + + return ValidationResult.OK; + } + } + + public AmbienceFXMusic clone() { + AmbienceFXMusic copy = new AmbienceFXMusic(); + copy.tracks = this.tracks != null ? Arrays.copyOf(this.tracks, this.tracks.length) : null; + copy.volume = this.volume; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AmbienceFXMusic other) ? false : Arrays.equals((Object[])this.tracks, (Object[])other.tracks) && this.volume == other.volume; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode((Object[])this.tracks); + return 31 * result + Float.hashCode(this.volume); + } +} diff --git a/src/com/hypixel/hytale/protocol/AmbienceFXSound.java b/src/com/hypixel/hytale/protocol/AmbienceFXSound.java new file mode 100644 index 0000000..3bf4f1a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AmbienceFXSound.java @@ -0,0 +1,144 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AmbienceFXSound { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 27; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 27; + public static final int MAX_SIZE = 27; + public int soundEventIndex; + @Nonnull + public AmbienceFXSoundPlay3D play3D = AmbienceFXSoundPlay3D.Random; + public int blockSoundSetIndex; + @Nonnull + public AmbienceFXAltitude altitude = AmbienceFXAltitude.Normal; + @Nullable + public Rangef frequency; + @Nullable + public Range radius; + + public AmbienceFXSound() { + } + + public AmbienceFXSound( + int soundEventIndex, + @Nonnull AmbienceFXSoundPlay3D play3D, + int blockSoundSetIndex, + @Nonnull AmbienceFXAltitude altitude, + @Nullable Rangef frequency, + @Nullable Range radius + ) { + this.soundEventIndex = soundEventIndex; + this.play3D = play3D; + this.blockSoundSetIndex = blockSoundSetIndex; + this.altitude = altitude; + this.frequency = frequency; + this.radius = radius; + } + + public AmbienceFXSound(@Nonnull AmbienceFXSound other) { + this.soundEventIndex = other.soundEventIndex; + this.play3D = other.play3D; + this.blockSoundSetIndex = other.blockSoundSetIndex; + this.altitude = other.altitude; + this.frequency = other.frequency; + this.radius = other.radius; + } + + @Nonnull + public static AmbienceFXSound deserialize(@Nonnull ByteBuf buf, int offset) { + AmbienceFXSound obj = new AmbienceFXSound(); + byte nullBits = buf.getByte(offset); + obj.soundEventIndex = buf.getIntLE(offset + 1); + obj.play3D = AmbienceFXSoundPlay3D.fromValue(buf.getByte(offset + 5)); + obj.blockSoundSetIndex = buf.getIntLE(offset + 6); + obj.altitude = AmbienceFXAltitude.fromValue(buf.getByte(offset + 10)); + if ((nullBits & 1) != 0) { + obj.frequency = Rangef.deserialize(buf, offset + 11); + } + + if ((nullBits & 2) != 0) { + obj.radius = Range.deserialize(buf, offset + 19); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 27; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.frequency != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.radius != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.soundEventIndex); + buf.writeByte(this.play3D.getValue()); + buf.writeIntLE(this.blockSoundSetIndex); + buf.writeByte(this.altitude.getValue()); + if (this.frequency != null) { + this.frequency.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.radius != null) { + this.radius.serialize(buf); + } else { + buf.writeZero(8); + } + } + + public int computeSize() { + return 27; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 27 ? ValidationResult.error("Buffer too small: expected at least 27 bytes") : ValidationResult.OK; + } + + public AmbienceFXSound clone() { + AmbienceFXSound copy = new AmbienceFXSound(); + copy.soundEventIndex = this.soundEventIndex; + copy.play3D = this.play3D; + copy.blockSoundSetIndex = this.blockSoundSetIndex; + copy.altitude = this.altitude; + copy.frequency = this.frequency != null ? this.frequency.clone() : null; + copy.radius = this.radius != null ? this.radius.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AmbienceFXSound other) + ? false + : this.soundEventIndex == other.soundEventIndex + && Objects.equals(this.play3D, other.play3D) + && this.blockSoundSetIndex == other.blockSoundSetIndex + && Objects.equals(this.altitude, other.altitude) + && Objects.equals(this.frequency, other.frequency) + && Objects.equals(this.radius, other.radius); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.soundEventIndex, this.play3D, this.blockSoundSetIndex, this.altitude, this.frequency, this.radius); + } +} diff --git a/src/com/hypixel/hytale/protocol/AmbienceFXSoundEffect.java b/src/com/hypixel/hytale/protocol/AmbienceFXSoundEffect.java new file mode 100644 index 0000000..3fe9096 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AmbienceFXSoundEffect.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class AmbienceFXSoundEffect { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 9; + public int reverbEffectIndex; + public int equalizerEffectIndex; + public boolean isInstant; + + public AmbienceFXSoundEffect() { + } + + public AmbienceFXSoundEffect(int reverbEffectIndex, int equalizerEffectIndex, boolean isInstant) { + this.reverbEffectIndex = reverbEffectIndex; + this.equalizerEffectIndex = equalizerEffectIndex; + this.isInstant = isInstant; + } + + public AmbienceFXSoundEffect(@Nonnull AmbienceFXSoundEffect other) { + this.reverbEffectIndex = other.reverbEffectIndex; + this.equalizerEffectIndex = other.equalizerEffectIndex; + this.isInstant = other.isInstant; + } + + @Nonnull + public static AmbienceFXSoundEffect deserialize(@Nonnull ByteBuf buf, int offset) { + AmbienceFXSoundEffect obj = new AmbienceFXSoundEffect(); + obj.reverbEffectIndex = buf.getIntLE(offset + 0); + obj.equalizerEffectIndex = buf.getIntLE(offset + 4); + obj.isInstant = buf.getByte(offset + 8) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 9; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.reverbEffectIndex); + buf.writeIntLE(this.equalizerEffectIndex); + buf.writeByte(this.isInstant ? 1 : 0); + } + + public int computeSize() { + return 9; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 9 ? ValidationResult.error("Buffer too small: expected at least 9 bytes") : ValidationResult.OK; + } + + public AmbienceFXSoundEffect clone() { + AmbienceFXSoundEffect copy = new AmbienceFXSoundEffect(); + copy.reverbEffectIndex = this.reverbEffectIndex; + copy.equalizerEffectIndex = this.equalizerEffectIndex; + copy.isInstant = this.isInstant; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AmbienceFXSoundEffect other) + ? false + : this.reverbEffectIndex == other.reverbEffectIndex && this.equalizerEffectIndex == other.equalizerEffectIndex && this.isInstant == other.isInstant; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.reverbEffectIndex, this.equalizerEffectIndex, this.isInstant); + } +} diff --git a/src/com/hypixel/hytale/protocol/AmbienceFXSoundPlay3D.java b/src/com/hypixel/hytale/protocol/AmbienceFXSoundPlay3D.java new file mode 100644 index 0000000..97c2533 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AmbienceFXSoundPlay3D.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum AmbienceFXSoundPlay3D { + Random(0), + LocationName(1), + No(2); + + public static final AmbienceFXSoundPlay3D[] VALUES = values(); + private final int value; + + private AmbienceFXSoundPlay3D(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static AmbienceFXSoundPlay3D fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("AmbienceFXSoundPlay3D", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/AmbienceTransitionSpeed.java b/src/com/hypixel/hytale/protocol/AmbienceTransitionSpeed.java new file mode 100644 index 0000000..41a4fdd --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AmbienceTransitionSpeed.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum AmbienceTransitionSpeed { + Default(0), + Fast(1), + Instant(2); + + public static final AmbienceTransitionSpeed[] VALUES = values(); + private final int value; + + private AmbienceTransitionSpeed(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static AmbienceTransitionSpeed fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("AmbienceTransitionSpeed", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/AngledDamage.java b/src/com/hypixel/hytale/protocol/AngledDamage.java new file mode 100644 index 0000000..b75fd31 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AngledDamage.java @@ -0,0 +1,134 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AngledDamage { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 21; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 21; + public static final int MAX_SIZE = 1677721600; + public double angle; + public double angleDistance; + @Nullable + public DamageEffects damageEffects; + public int next; + + public AngledDamage() { + } + + public AngledDamage(double angle, double angleDistance, @Nullable DamageEffects damageEffects, int next) { + this.angle = angle; + this.angleDistance = angleDistance; + this.damageEffects = damageEffects; + this.next = next; + } + + public AngledDamage(@Nonnull AngledDamage other) { + this.angle = other.angle; + this.angleDistance = other.angleDistance; + this.damageEffects = other.damageEffects; + this.next = other.next; + } + + @Nonnull + public static AngledDamage deserialize(@Nonnull ByteBuf buf, int offset) { + AngledDamage obj = new AngledDamage(); + byte nullBits = buf.getByte(offset); + obj.angle = buf.getDoubleLE(offset + 1); + obj.angleDistance = buf.getDoubleLE(offset + 9); + obj.next = buf.getIntLE(offset + 17); + int pos = offset + 21; + if ((nullBits & 1) != 0) { + obj.damageEffects = DamageEffects.deserialize(buf, pos); + pos += DamageEffects.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 21; + if ((nullBits & 1) != 0) { + pos += DamageEffects.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.damageEffects != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeDoubleLE(this.angle); + buf.writeDoubleLE(this.angleDistance); + buf.writeIntLE(this.next); + if (this.damageEffects != null) { + this.damageEffects.serialize(buf); + } + } + + public int computeSize() { + int size = 21; + if (this.damageEffects != null) { + size += this.damageEffects.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 21) { + return ValidationResult.error("Buffer too small: expected at least 21 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 21; + if ((nullBits & 1) != 0) { + ValidationResult damageEffectsResult = DamageEffects.validateStructure(buffer, pos); + if (!damageEffectsResult.isValid()) { + return ValidationResult.error("Invalid DamageEffects: " + damageEffectsResult.error()); + } + + pos += DamageEffects.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public AngledDamage clone() { + AngledDamage copy = new AngledDamage(); + copy.angle = this.angle; + copy.angleDistance = this.angleDistance; + copy.damageEffects = this.damageEffects != null ? this.damageEffects.clone() : null; + copy.next = this.next; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AngledDamage other) + ? false + : this.angle == other.angle + && this.angleDistance == other.angleDistance + && Objects.equals(this.damageEffects, other.damageEffects) + && this.next == other.next; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.angle, this.angleDistance, this.damageEffects, this.next); + } +} diff --git a/src/com/hypixel/hytale/protocol/AngledWielding.java b/src/com/hypixel/hytale/protocol/AngledWielding.java new file mode 100644 index 0000000..89f9a0c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AngledWielding.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class AngledWielding { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 9; + public float angleRad; + public float angleDistanceRad; + public boolean hasModifiers; + + public AngledWielding() { + } + + public AngledWielding(float angleRad, float angleDistanceRad, boolean hasModifiers) { + this.angleRad = angleRad; + this.angleDistanceRad = angleDistanceRad; + this.hasModifiers = hasModifiers; + } + + public AngledWielding(@Nonnull AngledWielding other) { + this.angleRad = other.angleRad; + this.angleDistanceRad = other.angleDistanceRad; + this.hasModifiers = other.hasModifiers; + } + + @Nonnull + public static AngledWielding deserialize(@Nonnull ByteBuf buf, int offset) { + AngledWielding obj = new AngledWielding(); + obj.angleRad = buf.getFloatLE(offset + 0); + obj.angleDistanceRad = buf.getFloatLE(offset + 4); + obj.hasModifiers = buf.getByte(offset + 8) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 9; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.angleRad); + buf.writeFloatLE(this.angleDistanceRad); + buf.writeByte(this.hasModifiers ? 1 : 0); + } + + public int computeSize() { + return 9; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 9 ? ValidationResult.error("Buffer too small: expected at least 9 bytes") : ValidationResult.OK; + } + + public AngledWielding clone() { + AngledWielding copy = new AngledWielding(); + copy.angleRad = this.angleRad; + copy.angleDistanceRad = this.angleDistanceRad; + copy.hasModifiers = this.hasModifiers; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AngledWielding other) + ? false + : this.angleRad == other.angleRad && this.angleDistanceRad == other.angleDistanceRad && this.hasModifiers == other.hasModifiers; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.angleRad, this.angleDistanceRad, this.hasModifiers); + } +} diff --git a/src/com/hypixel/hytale/protocol/Animation.java b/src/com/hypixel/hytale/protocol/Animation.java new file mode 100644 index 0000000..7728c03 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Animation.java @@ -0,0 +1,305 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Animation { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 22; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 30; + public static final int MAX_SIZE = 32768040; + @Nullable + public String name; + public float speed; + public float blendingDuration = 0.2F; + public boolean looping; + public float weight; + @Nullable + public int[] footstepIntervals; + public int soundEventIndex; + public int passiveLoopCount; + + public Animation() { + } + + public Animation( + @Nullable String name, + float speed, + float blendingDuration, + boolean looping, + float weight, + @Nullable int[] footstepIntervals, + int soundEventIndex, + int passiveLoopCount + ) { + this.name = name; + this.speed = speed; + this.blendingDuration = blendingDuration; + this.looping = looping; + this.weight = weight; + this.footstepIntervals = footstepIntervals; + this.soundEventIndex = soundEventIndex; + this.passiveLoopCount = passiveLoopCount; + } + + public Animation(@Nonnull Animation other) { + this.name = other.name; + this.speed = other.speed; + this.blendingDuration = other.blendingDuration; + this.looping = other.looping; + this.weight = other.weight; + this.footstepIntervals = other.footstepIntervals; + this.soundEventIndex = other.soundEventIndex; + this.passiveLoopCount = other.passiveLoopCount; + } + + @Nonnull + public static Animation deserialize(@Nonnull ByteBuf buf, int offset) { + Animation obj = new Animation(); + byte nullBits = buf.getByte(offset); + obj.speed = buf.getFloatLE(offset + 1); + obj.blendingDuration = buf.getFloatLE(offset + 5); + obj.looping = buf.getByte(offset + 9) != 0; + obj.weight = buf.getFloatLE(offset + 10); + obj.soundEventIndex = buf.getIntLE(offset + 14); + obj.passiveLoopCount = buf.getIntLE(offset + 18); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 30 + buf.getIntLE(offset + 22); + int nameLen = VarInt.peek(buf, varPos0); + if (nameLen < 0) { + throw ProtocolException.negativeLength("Name", nameLen); + } + + if (nameLen > 4096000) { + throw ProtocolException.stringTooLong("Name", nameLen, 4096000); + } + + obj.name = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 30 + buf.getIntLE(offset + 26); + int footstepIntervalsCount = VarInt.peek(buf, varPos1); + if (footstepIntervalsCount < 0) { + throw ProtocolException.negativeLength("FootstepIntervals", footstepIntervalsCount); + } + + if (footstepIntervalsCount > 4096000) { + throw ProtocolException.arrayTooLong("FootstepIntervals", footstepIntervalsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + footstepIntervalsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("FootstepIntervals", varPos1 + varIntLen + footstepIntervalsCount * 4, buf.readableBytes()); + } + + obj.footstepIntervals = new int[footstepIntervalsCount]; + + for (int i = 0; i < footstepIntervalsCount; i++) { + obj.footstepIntervals[i] = buf.getIntLE(varPos1 + varIntLen + i * 4); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 30; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 22); + int pos0 = offset + 30 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 26); + int pos1 = offset + 30 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + arrLen * 4; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.name != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.footstepIntervals != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.speed); + buf.writeFloatLE(this.blendingDuration); + buf.writeByte(this.looping ? 1 : 0); + buf.writeFloatLE(this.weight); + buf.writeIntLE(this.soundEventIndex); + buf.writeIntLE(this.passiveLoopCount); + int nameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int footstepIntervalsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.name != null) { + buf.setIntLE(nameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.name, 4096000); + } else { + buf.setIntLE(nameOffsetSlot, -1); + } + + if (this.footstepIntervals != null) { + buf.setIntLE(footstepIntervalsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.footstepIntervals.length > 4096000) { + throw ProtocolException.arrayTooLong("FootstepIntervals", this.footstepIntervals.length, 4096000); + } + + VarInt.write(buf, this.footstepIntervals.length); + + for (int item : this.footstepIntervals) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(footstepIntervalsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 30; + if (this.name != null) { + size += PacketIO.stringSize(this.name); + } + + if (this.footstepIntervals != null) { + size += VarInt.size(this.footstepIntervals.length) + this.footstepIntervals.length * 4; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 30) { + return ValidationResult.error("Buffer too small: expected at least 30 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int nameOffset = buffer.getIntLE(offset + 22); + if (nameOffset < 0) { + return ValidationResult.error("Invalid offset for Name"); + } + + int pos = offset + 30 + nameOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Name"); + } + + int nameLen = VarInt.peek(buffer, pos); + if (nameLen < 0) { + return ValidationResult.error("Invalid string length for Name"); + } + + if (nameLen > 4096000) { + return ValidationResult.error("Name exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += nameLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Name"); + } + } + + if ((nullBits & 2) != 0) { + int footstepIntervalsOffset = buffer.getIntLE(offset + 26); + if (footstepIntervalsOffset < 0) { + return ValidationResult.error("Invalid offset for FootstepIntervals"); + } + + int posx = offset + 30 + footstepIntervalsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FootstepIntervals"); + } + + int footstepIntervalsCount = VarInt.peek(buffer, posx); + if (footstepIntervalsCount < 0) { + return ValidationResult.error("Invalid array count for FootstepIntervals"); + } + + if (footstepIntervalsCount > 4096000) { + return ValidationResult.error("FootstepIntervals exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += footstepIntervalsCount * 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading FootstepIntervals"); + } + } + + return ValidationResult.OK; + } + } + + public Animation clone() { + Animation copy = new Animation(); + copy.name = this.name; + copy.speed = this.speed; + copy.blendingDuration = this.blendingDuration; + copy.looping = this.looping; + copy.weight = this.weight; + copy.footstepIntervals = this.footstepIntervals != null ? Arrays.copyOf(this.footstepIntervals, this.footstepIntervals.length) : null; + copy.soundEventIndex = this.soundEventIndex; + copy.passiveLoopCount = this.passiveLoopCount; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Animation other) + ? false + : Objects.equals(this.name, other.name) + && this.speed == other.speed + && this.blendingDuration == other.blendingDuration + && this.looping == other.looping + && this.weight == other.weight + && Arrays.equals(this.footstepIntervals, other.footstepIntervals) + && this.soundEventIndex == other.soundEventIndex + && this.passiveLoopCount == other.passiveLoopCount; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.name); + result = 31 * result + Float.hashCode(this.speed); + result = 31 * result + Float.hashCode(this.blendingDuration); + result = 31 * result + Boolean.hashCode(this.looping); + result = 31 * result + Float.hashCode(this.weight); + result = 31 * result + Arrays.hashCode(this.footstepIntervals); + result = 31 * result + Integer.hashCode(this.soundEventIndex); + return 31 * result + Integer.hashCode(this.passiveLoopCount); + } +} diff --git a/src/com/hypixel/hytale/protocol/AnimationSet.java b/src/com/hypixel/hytale/protocol/AnimationSet.java new file mode 100644 index 0000000..a6c4c2d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AnimationSet.java @@ -0,0 +1,287 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AnimationSet { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 17; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + @Nullable + public Animation[] animations; + @Nullable + public Rangef nextAnimationDelay; + + public AnimationSet() { + } + + public AnimationSet(@Nullable String id, @Nullable Animation[] animations, @Nullable Rangef nextAnimationDelay) { + this.id = id; + this.animations = animations; + this.nextAnimationDelay = nextAnimationDelay; + } + + public AnimationSet(@Nonnull AnimationSet other) { + this.id = other.id; + this.animations = other.animations; + this.nextAnimationDelay = other.nextAnimationDelay; + } + + @Nonnull + public static AnimationSet deserialize(@Nonnull ByteBuf buf, int offset) { + AnimationSet obj = new AnimationSet(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 4) != 0) { + obj.nextAnimationDelay = Rangef.deserialize(buf, offset + 1); + } + + if ((nullBits & 1) != 0) { + int varPos0 = offset + 17 + buf.getIntLE(offset + 9); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 17 + buf.getIntLE(offset + 13); + int animationsCount = VarInt.peek(buf, varPos1); + if (animationsCount < 0) { + throw ProtocolException.negativeLength("Animations", animationsCount); + } + + if (animationsCount > 4096000) { + throw ProtocolException.arrayTooLong("Animations", animationsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + animationsCount * 22L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Animations", varPos1 + varIntLen + animationsCount * 22, buf.readableBytes()); + } + + obj.animations = new Animation[animationsCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < animationsCount; i++) { + obj.animations[i] = Animation.deserialize(buf, elemPos); + elemPos += Animation.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 17; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 9); + int pos0 = offset + 17 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 13); + int pos1 = offset + 17 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += Animation.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.animations != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.nextAnimationDelay != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + if (this.nextAnimationDelay != null) { + this.nextAnimationDelay.serialize(buf); + } else { + buf.writeZero(8); + } + + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int animationsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.animations != null) { + buf.setIntLE(animationsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.animations.length > 4096000) { + throw ProtocolException.arrayTooLong("Animations", this.animations.length, 4096000); + } + + VarInt.write(buf, this.animations.length); + + for (Animation item : this.animations) { + item.serialize(buf); + } + } else { + buf.setIntLE(animationsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 17; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.animations != null) { + int animationsSize = 0; + + for (Animation elem : this.animations) { + animationsSize += elem.computeSize(); + } + + size += VarInt.size(this.animations.length) + animationsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 17) { + return ValidationResult.error("Buffer too small: expected at least 17 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 9); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 17 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int animationsOffset = buffer.getIntLE(offset + 13); + if (animationsOffset < 0) { + return ValidationResult.error("Invalid offset for Animations"); + } + + int posx = offset + 17 + animationsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Animations"); + } + + int animationsCount = VarInt.peek(buffer, posx); + if (animationsCount < 0) { + return ValidationResult.error("Invalid array count for Animations"); + } + + if (animationsCount > 4096000) { + return ValidationResult.error("Animations exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < animationsCount; i++) { + ValidationResult structResult = Animation.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid Animation in Animations[" + i + "]: " + structResult.error()); + } + + posx += Animation.computeBytesConsumed(buffer, posx); + } + } + + return ValidationResult.OK; + } + } + + public AnimationSet clone() { + AnimationSet copy = new AnimationSet(); + copy.id = this.id; + copy.animations = this.animations != null ? Arrays.stream(this.animations).map(e -> e.clone()).toArray(Animation[]::new) : null; + copy.nextAnimationDelay = this.nextAnimationDelay != null ? this.nextAnimationDelay.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AnimationSet other) + ? false + : Objects.equals(this.id, other.id) + && Arrays.equals((Object[])this.animations, (Object[])other.animations) + && Objects.equals(this.nextAnimationDelay, other.nextAnimationDelay); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.id); + result = 31 * result + Arrays.hashCode((Object[])this.animations); + return 31 * result + Objects.hashCode(this.nextAnimationDelay); + } +} diff --git a/src/com/hypixel/hytale/protocol/AnimationSlot.java b/src/com/hypixel/hytale/protocol/AnimationSlot.java new file mode 100644 index 0000000..79ef6d8 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AnimationSlot.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum AnimationSlot { + Movement(0), + Status(1), + Action(2), + Face(3), + Emote(4); + + public static final AnimationSlot[] VALUES = values(); + private final int value; + + private AnimationSlot(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static AnimationSlot fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("AnimationSlot", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ApplicationEffects.java b/src/com/hypixel/hytale/protocol/ApplicationEffects.java new file mode 100644 index 0000000..bb49f58 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ApplicationEffects.java @@ -0,0 +1,703 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ApplicationEffects { + public static final int NULLABLE_BIT_FIELD_SIZE = 2; + public static final int FIXED_BLOCK_SIZE = 35; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 59; + public static final int MAX_SIZE = 1677721600; + @Nullable + public Color entityBottomTint; + @Nullable + public Color entityTopTint; + @Nullable + public String entityAnimationId; + @Nullable + public ModelParticle[] particles; + @Nullable + public ModelParticle[] firstPersonParticles; + @Nullable + public String screenEffect; + public float horizontalSpeedMultiplier; + public int soundEventIndexLocal; + public int soundEventIndexWorld; + @Nullable + public String modelVFXId; + @Nullable + public MovementEffects movementEffects; + public float mouseSensitivityAdjustmentTarget; + public float mouseSensitivityAdjustmentDuration; + @Nullable + public AbilityEffects abilityEffects; + + public ApplicationEffects() { + } + + public ApplicationEffects( + @Nullable Color entityBottomTint, + @Nullable Color entityTopTint, + @Nullable String entityAnimationId, + @Nullable ModelParticle[] particles, + @Nullable ModelParticle[] firstPersonParticles, + @Nullable String screenEffect, + float horizontalSpeedMultiplier, + int soundEventIndexLocal, + int soundEventIndexWorld, + @Nullable String modelVFXId, + @Nullable MovementEffects movementEffects, + float mouseSensitivityAdjustmentTarget, + float mouseSensitivityAdjustmentDuration, + @Nullable AbilityEffects abilityEffects + ) { + this.entityBottomTint = entityBottomTint; + this.entityTopTint = entityTopTint; + this.entityAnimationId = entityAnimationId; + this.particles = particles; + this.firstPersonParticles = firstPersonParticles; + this.screenEffect = screenEffect; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.soundEventIndexLocal = soundEventIndexLocal; + this.soundEventIndexWorld = soundEventIndexWorld; + this.modelVFXId = modelVFXId; + this.movementEffects = movementEffects; + this.mouseSensitivityAdjustmentTarget = mouseSensitivityAdjustmentTarget; + this.mouseSensitivityAdjustmentDuration = mouseSensitivityAdjustmentDuration; + this.abilityEffects = abilityEffects; + } + + public ApplicationEffects(@Nonnull ApplicationEffects other) { + this.entityBottomTint = other.entityBottomTint; + this.entityTopTint = other.entityTopTint; + this.entityAnimationId = other.entityAnimationId; + this.particles = other.particles; + this.firstPersonParticles = other.firstPersonParticles; + this.screenEffect = other.screenEffect; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.soundEventIndexLocal = other.soundEventIndexLocal; + this.soundEventIndexWorld = other.soundEventIndexWorld; + this.modelVFXId = other.modelVFXId; + this.movementEffects = other.movementEffects; + this.mouseSensitivityAdjustmentTarget = other.mouseSensitivityAdjustmentTarget; + this.mouseSensitivityAdjustmentDuration = other.mouseSensitivityAdjustmentDuration; + this.abilityEffects = other.abilityEffects; + } + + @Nonnull + public static ApplicationEffects deserialize(@Nonnull ByteBuf buf, int offset) { + ApplicationEffects obj = new ApplicationEffects(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + if ((nullBits[0] & 1) != 0) { + obj.entityBottomTint = Color.deserialize(buf, offset + 2); + } + + if ((nullBits[0] & 2) != 0) { + obj.entityTopTint = Color.deserialize(buf, offset + 5); + } + + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 8); + obj.soundEventIndexLocal = buf.getIntLE(offset + 12); + obj.soundEventIndexWorld = buf.getIntLE(offset + 16); + if ((nullBits[0] & 128) != 0) { + obj.movementEffects = MovementEffects.deserialize(buf, offset + 20); + } + + obj.mouseSensitivityAdjustmentTarget = buf.getFloatLE(offset + 27); + obj.mouseSensitivityAdjustmentDuration = buf.getFloatLE(offset + 31); + if ((nullBits[0] & 4) != 0) { + int varPos0 = offset + 59 + buf.getIntLE(offset + 35); + int entityAnimationIdLen = VarInt.peek(buf, varPos0); + if (entityAnimationIdLen < 0) { + throw ProtocolException.negativeLength("EntityAnimationId", entityAnimationIdLen); + } + + if (entityAnimationIdLen > 4096000) { + throw ProtocolException.stringTooLong("EntityAnimationId", entityAnimationIdLen, 4096000); + } + + obj.entityAnimationId = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits[0] & 8) != 0) { + int varPos1 = offset + 59 + buf.getIntLE(offset + 39); + int particlesCount = VarInt.peek(buf, varPos1); + if (particlesCount < 0) { + throw ProtocolException.negativeLength("Particles", particlesCount); + } + + if (particlesCount > 4096000) { + throw ProtocolException.arrayTooLong("Particles", particlesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + particlesCount * 34L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Particles", varPos1 + varIntLen + particlesCount * 34, buf.readableBytes()); + } + + obj.particles = new ModelParticle[particlesCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < particlesCount; i++) { + obj.particles[i] = ModelParticle.deserialize(buf, elemPos); + elemPos += ModelParticle.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits[0] & 16) != 0) { + int varPos2 = offset + 59 + buf.getIntLE(offset + 43); + int firstPersonParticlesCount = VarInt.peek(buf, varPos2); + if (firstPersonParticlesCount < 0) { + throw ProtocolException.negativeLength("FirstPersonParticles", firstPersonParticlesCount); + } + + if (firstPersonParticlesCount > 4096000) { + throw ProtocolException.arrayTooLong("FirstPersonParticles", firstPersonParticlesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + firstPersonParticlesCount * 34L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("FirstPersonParticles", varPos2 + varIntLen + firstPersonParticlesCount * 34, buf.readableBytes()); + } + + obj.firstPersonParticles = new ModelParticle[firstPersonParticlesCount]; + int elemPos = varPos2 + varIntLen; + + for (int i = 0; i < firstPersonParticlesCount; i++) { + obj.firstPersonParticles[i] = ModelParticle.deserialize(buf, elemPos); + elemPos += ModelParticle.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits[0] & 32) != 0) { + int varPos3 = offset + 59 + buf.getIntLE(offset + 47); + int screenEffectLen = VarInt.peek(buf, varPos3); + if (screenEffectLen < 0) { + throw ProtocolException.negativeLength("ScreenEffect", screenEffectLen); + } + + if (screenEffectLen > 4096000) { + throw ProtocolException.stringTooLong("ScreenEffect", screenEffectLen, 4096000); + } + + obj.screenEffect = PacketIO.readVarString(buf, varPos3, PacketIO.UTF8); + } + + if ((nullBits[0] & 64) != 0) { + int varPos4 = offset + 59 + buf.getIntLE(offset + 51); + int modelVFXIdLen = VarInt.peek(buf, varPos4); + if (modelVFXIdLen < 0) { + throw ProtocolException.negativeLength("ModelVFXId", modelVFXIdLen); + } + + if (modelVFXIdLen > 4096000) { + throw ProtocolException.stringTooLong("ModelVFXId", modelVFXIdLen, 4096000); + } + + obj.modelVFXId = PacketIO.readVarString(buf, varPos4, PacketIO.UTF8); + } + + if ((nullBits[1] & 1) != 0) { + int varPos5 = offset + 59 + buf.getIntLE(offset + 55); + obj.abilityEffects = AbilityEffects.deserialize(buf, varPos5); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + int maxEnd = 59; + if ((nullBits[0] & 4) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 35); + int pos0 = offset + 59 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits[0] & 8) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 39); + int pos1 = offset + 59 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += ModelParticle.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits[0] & 16) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 43); + int pos2 = offset + 59 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2); + + for (int i = 0; i < arrLen; i++) { + pos2 += ModelParticle.computeBytesConsumed(buf, pos2); + } + + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits[0] & 32) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 47); + int pos3 = offset + 59 + fieldOffset3; + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits[0] & 64) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 51); + int pos4 = offset + 59 + fieldOffset4; + int sl = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4) + sl; + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits[1] & 1) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 55); + int pos5 = offset + 59 + fieldOffset5; + pos5 += AbilityEffects.computeBytesConsumed(buf, pos5); + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte[] nullBits = new byte[2]; + if (this.entityBottomTint != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.entityTopTint != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.entityAnimationId != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.particles != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.firstPersonParticles != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.screenEffect != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.modelVFXId != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.movementEffects != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.abilityEffects != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + buf.writeBytes(nullBits); + if (this.entityBottomTint != null) { + this.entityBottomTint.serialize(buf); + } else { + buf.writeZero(3); + } + + if (this.entityTopTint != null) { + this.entityTopTint.serialize(buf); + } else { + buf.writeZero(3); + } + + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeIntLE(this.soundEventIndexLocal); + buf.writeIntLE(this.soundEventIndexWorld); + if (this.movementEffects != null) { + this.movementEffects.serialize(buf); + } else { + buf.writeZero(7); + } + + buf.writeFloatLE(this.mouseSensitivityAdjustmentTarget); + buf.writeFloatLE(this.mouseSensitivityAdjustmentDuration); + int entityAnimationIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int particlesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int firstPersonParticlesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int screenEffectOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int modelVFXIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int abilityEffectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.entityAnimationId != null) { + buf.setIntLE(entityAnimationIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.entityAnimationId, 4096000); + } else { + buf.setIntLE(entityAnimationIdOffsetSlot, -1); + } + + if (this.particles != null) { + buf.setIntLE(particlesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.particles.length > 4096000) { + throw ProtocolException.arrayTooLong("Particles", this.particles.length, 4096000); + } + + VarInt.write(buf, this.particles.length); + + for (ModelParticle item : this.particles) { + item.serialize(buf); + } + } else { + buf.setIntLE(particlesOffsetSlot, -1); + } + + if (this.firstPersonParticles != null) { + buf.setIntLE(firstPersonParticlesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.firstPersonParticles.length > 4096000) { + throw ProtocolException.arrayTooLong("FirstPersonParticles", this.firstPersonParticles.length, 4096000); + } + + VarInt.write(buf, this.firstPersonParticles.length); + + for (ModelParticle item : this.firstPersonParticles) { + item.serialize(buf); + } + } else { + buf.setIntLE(firstPersonParticlesOffsetSlot, -1); + } + + if (this.screenEffect != null) { + buf.setIntLE(screenEffectOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.screenEffect, 4096000); + } else { + buf.setIntLE(screenEffectOffsetSlot, -1); + } + + if (this.modelVFXId != null) { + buf.setIntLE(modelVFXIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.modelVFXId, 4096000); + } else { + buf.setIntLE(modelVFXIdOffsetSlot, -1); + } + + if (this.abilityEffects != null) { + buf.setIntLE(abilityEffectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.abilityEffects.serialize(buf); + } else { + buf.setIntLE(abilityEffectsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 59; + if (this.entityAnimationId != null) { + size += PacketIO.stringSize(this.entityAnimationId); + } + + if (this.particles != null) { + int particlesSize = 0; + + for (ModelParticle elem : this.particles) { + particlesSize += elem.computeSize(); + } + + size += VarInt.size(this.particles.length) + particlesSize; + } + + if (this.firstPersonParticles != null) { + int firstPersonParticlesSize = 0; + + for (ModelParticle elem : this.firstPersonParticles) { + firstPersonParticlesSize += elem.computeSize(); + } + + size += VarInt.size(this.firstPersonParticles.length) + firstPersonParticlesSize; + } + + if (this.screenEffect != null) { + size += PacketIO.stringSize(this.screenEffect); + } + + if (this.modelVFXId != null) { + size += PacketIO.stringSize(this.modelVFXId); + } + + if (this.abilityEffects != null) { + size += this.abilityEffects.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 59) { + return ValidationResult.error("Buffer too small: expected at least 59 bytes"); + } else { + byte[] nullBits = PacketIO.readBytes(buffer, offset, 2); + if ((nullBits[0] & 4) != 0) { + int entityAnimationIdOffset = buffer.getIntLE(offset + 35); + if (entityAnimationIdOffset < 0) { + return ValidationResult.error("Invalid offset for EntityAnimationId"); + } + + int pos = offset + 59 + entityAnimationIdOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for EntityAnimationId"); + } + + int entityAnimationIdLen = VarInt.peek(buffer, pos); + if (entityAnimationIdLen < 0) { + return ValidationResult.error("Invalid string length for EntityAnimationId"); + } + + if (entityAnimationIdLen > 4096000) { + return ValidationResult.error("EntityAnimationId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += entityAnimationIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading EntityAnimationId"); + } + } + + if ((nullBits[0] & 8) != 0) { + int particlesOffset = buffer.getIntLE(offset + 39); + if (particlesOffset < 0) { + return ValidationResult.error("Invalid offset for Particles"); + } + + int posx = offset + 59 + particlesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Particles"); + } + + int particlesCount = VarInt.peek(buffer, posx); + if (particlesCount < 0) { + return ValidationResult.error("Invalid array count for Particles"); + } + + if (particlesCount > 4096000) { + return ValidationResult.error("Particles exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < particlesCount; i++) { + ValidationResult structResult = ModelParticle.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelParticle in Particles[" + i + "]: " + structResult.error()); + } + + posx += ModelParticle.computeBytesConsumed(buffer, posx); + } + } + + if ((nullBits[0] & 16) != 0) { + int firstPersonParticlesOffset = buffer.getIntLE(offset + 43); + if (firstPersonParticlesOffset < 0) { + return ValidationResult.error("Invalid offset for FirstPersonParticles"); + } + + int posxx = offset + 59 + firstPersonParticlesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FirstPersonParticles"); + } + + int firstPersonParticlesCount = VarInt.peek(buffer, posxx); + if (firstPersonParticlesCount < 0) { + return ValidationResult.error("Invalid array count for FirstPersonParticles"); + } + + if (firstPersonParticlesCount > 4096000) { + return ValidationResult.error("FirstPersonParticles exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + + for (int i = 0; i < firstPersonParticlesCount; i++) { + ValidationResult structResult = ModelParticle.validateStructure(buffer, posxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelParticle in FirstPersonParticles[" + i + "]: " + structResult.error()); + } + + posxx += ModelParticle.computeBytesConsumed(buffer, posxx); + } + } + + if ((nullBits[0] & 32) != 0) { + int screenEffectOffset = buffer.getIntLE(offset + 47); + if (screenEffectOffset < 0) { + return ValidationResult.error("Invalid offset for ScreenEffect"); + } + + int posxxx = offset + 59 + screenEffectOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ScreenEffect"); + } + + int screenEffectLen = VarInt.peek(buffer, posxxx); + if (screenEffectLen < 0) { + return ValidationResult.error("Invalid string length for ScreenEffect"); + } + + if (screenEffectLen > 4096000) { + return ValidationResult.error("ScreenEffect exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += screenEffectLen; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ScreenEffect"); + } + } + + if ((nullBits[0] & 64) != 0) { + int modelVFXIdOffset = buffer.getIntLE(offset + 51); + if (modelVFXIdOffset < 0) { + return ValidationResult.error("Invalid offset for ModelVFXId"); + } + + int posxxxx = offset + 59 + modelVFXIdOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ModelVFXId"); + } + + int modelVFXIdLen = VarInt.peek(buffer, posxxxx); + if (modelVFXIdLen < 0) { + return ValidationResult.error("Invalid string length for ModelVFXId"); + } + + if (modelVFXIdLen > 4096000) { + return ValidationResult.error("ModelVFXId exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + posxxxx += modelVFXIdLen; + if (posxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ModelVFXId"); + } + } + + if ((nullBits[1] & 1) != 0) { + int abilityEffectsOffset = buffer.getIntLE(offset + 55); + if (abilityEffectsOffset < 0) { + return ValidationResult.error("Invalid offset for AbilityEffects"); + } + + int posxxxxx = offset + 59 + abilityEffectsOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for AbilityEffects"); + } + + ValidationResult abilityEffectsResult = AbilityEffects.validateStructure(buffer, posxxxxx); + if (!abilityEffectsResult.isValid()) { + return ValidationResult.error("Invalid AbilityEffects: " + abilityEffectsResult.error()); + } + + posxxxxx += AbilityEffects.computeBytesConsumed(buffer, posxxxxx); + } + + return ValidationResult.OK; + } + } + + public ApplicationEffects clone() { + ApplicationEffects copy = new ApplicationEffects(); + copy.entityBottomTint = this.entityBottomTint != null ? this.entityBottomTint.clone() : null; + copy.entityTopTint = this.entityTopTint != null ? this.entityTopTint.clone() : null; + copy.entityAnimationId = this.entityAnimationId; + copy.particles = this.particles != null ? Arrays.stream(this.particles).map(e -> e.clone()).toArray(ModelParticle[]::new) : null; + copy.firstPersonParticles = this.firstPersonParticles != null + ? Arrays.stream(this.firstPersonParticles).map(e -> e.clone()).toArray(ModelParticle[]::new) + : null; + copy.screenEffect = this.screenEffect; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.soundEventIndexLocal = this.soundEventIndexLocal; + copy.soundEventIndexWorld = this.soundEventIndexWorld; + copy.modelVFXId = this.modelVFXId; + copy.movementEffects = this.movementEffects != null ? this.movementEffects.clone() : null; + copy.mouseSensitivityAdjustmentTarget = this.mouseSensitivityAdjustmentTarget; + copy.mouseSensitivityAdjustmentDuration = this.mouseSensitivityAdjustmentDuration; + copy.abilityEffects = this.abilityEffects != null ? this.abilityEffects.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ApplicationEffects other) + ? false + : Objects.equals(this.entityBottomTint, other.entityBottomTint) + && Objects.equals(this.entityTopTint, other.entityTopTint) + && Objects.equals(this.entityAnimationId, other.entityAnimationId) + && Arrays.equals((Object[])this.particles, (Object[])other.particles) + && Arrays.equals((Object[])this.firstPersonParticles, (Object[])other.firstPersonParticles) + && Objects.equals(this.screenEffect, other.screenEffect) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.soundEventIndexLocal == other.soundEventIndexLocal + && this.soundEventIndexWorld == other.soundEventIndexWorld + && Objects.equals(this.modelVFXId, other.modelVFXId) + && Objects.equals(this.movementEffects, other.movementEffects) + && this.mouseSensitivityAdjustmentTarget == other.mouseSensitivityAdjustmentTarget + && this.mouseSensitivityAdjustmentDuration == other.mouseSensitivityAdjustmentDuration + && Objects.equals(this.abilityEffects, other.abilityEffects); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.entityBottomTint); + result = 31 * result + Objects.hashCode(this.entityTopTint); + result = 31 * result + Objects.hashCode(this.entityAnimationId); + result = 31 * result + Arrays.hashCode((Object[])this.particles); + result = 31 * result + Arrays.hashCode((Object[])this.firstPersonParticles); + result = 31 * result + Objects.hashCode(this.screenEffect); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Integer.hashCode(this.soundEventIndexLocal); + result = 31 * result + Integer.hashCode(this.soundEventIndexWorld); + result = 31 * result + Objects.hashCode(this.modelVFXId); + result = 31 * result + Objects.hashCode(this.movementEffects); + result = 31 * result + Float.hashCode(this.mouseSensitivityAdjustmentTarget); + result = 31 * result + Float.hashCode(this.mouseSensitivityAdjustmentDuration); + return 31 * result + Objects.hashCode(this.abilityEffects); + } +} diff --git a/src/com/hypixel/hytale/protocol/AppliedForce.java b/src/com/hypixel/hytale/protocol/AppliedForce.java new file mode 100644 index 0000000..c4350a3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AppliedForce.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AppliedForce { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 18; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 18; + public static final int MAX_SIZE = 18; + @Nullable + public Vector3f direction; + public boolean adjustVertical; + public float force; + + public AppliedForce() { + } + + public AppliedForce(@Nullable Vector3f direction, boolean adjustVertical, float force) { + this.direction = direction; + this.adjustVertical = adjustVertical; + this.force = force; + } + + public AppliedForce(@Nonnull AppliedForce other) { + this.direction = other.direction; + this.adjustVertical = other.adjustVertical; + this.force = other.force; + } + + @Nonnull + public static AppliedForce deserialize(@Nonnull ByteBuf buf, int offset) { + AppliedForce obj = new AppliedForce(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.direction = Vector3f.deserialize(buf, offset + 1); + } + + obj.adjustVertical = buf.getByte(offset + 13) != 0; + obj.force = buf.getFloatLE(offset + 14); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 18; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.direction != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.direction != null) { + this.direction.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeByte(this.adjustVertical ? 1 : 0); + buf.writeFloatLE(this.force); + } + + public int computeSize() { + return 18; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 18 ? ValidationResult.error("Buffer too small: expected at least 18 bytes") : ValidationResult.OK; + } + + public AppliedForce clone() { + AppliedForce copy = new AppliedForce(); + copy.direction = this.direction != null ? this.direction.clone() : null; + copy.adjustVertical = this.adjustVertical; + copy.force = this.force; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AppliedForce other) + ? false + : Objects.equals(this.direction, other.direction) && this.adjustVertical == other.adjustVertical && this.force == other.force; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.direction, this.adjustVertical, this.force); + } +} diff --git a/src/com/hypixel/hytale/protocol/ApplyEffectInteraction.java b/src/com/hypixel/hytale/protocol/ApplyEffectInteraction.java new file mode 100644 index 0000000..7965383 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ApplyEffectInteraction.java @@ -0,0 +1,523 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ApplyEffectInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 24; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 44; + public static final int MAX_SIZE = 1677721600; + public int effectId; + @Nonnull + public InteractionTarget entityTarget = InteractionTarget.User; + + public ApplyEffectInteraction() { + } + + public ApplyEffectInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + int effectId, + @Nonnull InteractionTarget entityTarget + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.effectId = effectId; + this.entityTarget = entityTarget; + } + + public ApplyEffectInteraction(@Nonnull ApplyEffectInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.effectId = other.effectId; + this.entityTarget = other.entityTarget; + } + + @Nonnull + public static ApplyEffectInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + ApplyEffectInteraction obj = new ApplyEffectInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.effectId = buf.getIntLE(offset + 19); + obj.entityTarget = InteractionTarget.fromValue(buf.getByte(offset + 23)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 44 + buf.getIntLE(offset + 24); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 44 + buf.getIntLE(offset + 28); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 44 + buf.getIntLE(offset + 32); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 44 + buf.getIntLE(offset + 36); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 44 + buf.getIntLE(offset + 40); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 44; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 24); + int pos0 = offset + 44 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 28); + int pos1 = offset + 44 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 32); + int pos2 = offset + 44 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 36); + int pos3 = offset + 44 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 40); + int pos4 = offset + 44 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeIntLE(this.effectId); + buf.writeByte(this.entityTarget.getValue()); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 44; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 44) { + return ValidationResult.error("Buffer too small: expected at least 44 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 24); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 44 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 28); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 44 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 32); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 44 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 36); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 44 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 40); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 44 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public ApplyEffectInteraction clone() { + ApplyEffectInteraction copy = new ApplyEffectInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.effectId = this.effectId; + copy.entityTarget = this.entityTarget; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ApplyEffectInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && this.effectId == other.effectId + && Objects.equals(this.entityTarget, other.entityTarget); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Integer.hashCode(this.effectId); + return 31 * result + Objects.hashCode(this.entityTarget); + } +} diff --git a/src/com/hypixel/hytale/protocol/ApplyForceInteraction.java b/src/com/hypixel/hytale/protocol/ApplyForceInteraction.java new file mode 100644 index 0000000..fb7f95d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ApplyForceInteraction.java @@ -0,0 +1,749 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ApplyForceInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 80; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 104; + public static final int MAX_SIZE = 1677721600; + @Nullable + public VelocityConfig velocityConfig; + @Nonnull + public ChangeVelocityType changeVelocityType = ChangeVelocityType.Add; + @Nullable + public AppliedForce[] forces; + public float duration; + @Nullable + public FloatRange verticalClamp; + public boolean waitForGround; + public boolean waitForCollision; + public float groundCheckDelay; + public float collisionCheckDelay; + public int groundNext; + public int collisionNext; + public float raycastDistance; + public float raycastHeightOffset; + @Nonnull + public RaycastMode raycastMode = RaycastMode.FollowMotion; + + public ApplyForceInteraction() { + } + + public ApplyForceInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + @Nullable VelocityConfig velocityConfig, + @Nonnull ChangeVelocityType changeVelocityType, + @Nullable AppliedForce[] forces, + float duration, + @Nullable FloatRange verticalClamp, + boolean waitForGround, + boolean waitForCollision, + float groundCheckDelay, + float collisionCheckDelay, + int groundNext, + int collisionNext, + float raycastDistance, + float raycastHeightOffset, + @Nonnull RaycastMode raycastMode + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.velocityConfig = velocityConfig; + this.changeVelocityType = changeVelocityType; + this.forces = forces; + this.duration = duration; + this.verticalClamp = verticalClamp; + this.waitForGround = waitForGround; + this.waitForCollision = waitForCollision; + this.groundCheckDelay = groundCheckDelay; + this.collisionCheckDelay = collisionCheckDelay; + this.groundNext = groundNext; + this.collisionNext = collisionNext; + this.raycastDistance = raycastDistance; + this.raycastHeightOffset = raycastHeightOffset; + this.raycastMode = raycastMode; + } + + public ApplyForceInteraction(@Nonnull ApplyForceInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.velocityConfig = other.velocityConfig; + this.changeVelocityType = other.changeVelocityType; + this.forces = other.forces; + this.duration = other.duration; + this.verticalClamp = other.verticalClamp; + this.waitForGround = other.waitForGround; + this.waitForCollision = other.waitForCollision; + this.groundCheckDelay = other.groundCheckDelay; + this.collisionCheckDelay = other.collisionCheckDelay; + this.groundNext = other.groundNext; + this.collisionNext = other.collisionNext; + this.raycastDistance = other.raycastDistance; + this.raycastHeightOffset = other.raycastHeightOffset; + this.raycastMode = other.raycastMode; + } + + @Nonnull + public static ApplyForceInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + ApplyForceInteraction obj = new ApplyForceInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + if ((nullBits & 32) != 0) { + obj.velocityConfig = VelocityConfig.deserialize(buf, offset + 19); + } + + obj.changeVelocityType = ChangeVelocityType.fromValue(buf.getByte(offset + 40)); + obj.duration = buf.getFloatLE(offset + 41); + if ((nullBits & 128) != 0) { + obj.verticalClamp = FloatRange.deserialize(buf, offset + 45); + } + + obj.waitForGround = buf.getByte(offset + 53) != 0; + obj.waitForCollision = buf.getByte(offset + 54) != 0; + obj.groundCheckDelay = buf.getFloatLE(offset + 55); + obj.collisionCheckDelay = buf.getFloatLE(offset + 59); + obj.groundNext = buf.getIntLE(offset + 63); + obj.collisionNext = buf.getIntLE(offset + 67); + obj.raycastDistance = buf.getFloatLE(offset + 71); + obj.raycastHeightOffset = buf.getFloatLE(offset + 75); + obj.raycastMode = RaycastMode.fromValue(buf.getByte(offset + 79)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 104 + buf.getIntLE(offset + 80); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 104 + buf.getIntLE(offset + 84); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 104 + buf.getIntLE(offset + 88); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 104 + buf.getIntLE(offset + 92); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 104 + buf.getIntLE(offset + 96); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 64) != 0) { + int varPos5 = offset + 104 + buf.getIntLE(offset + 100); + int forcesCount = VarInt.peek(buf, varPos5); + if (forcesCount < 0) { + throw ProtocolException.negativeLength("Forces", forcesCount); + } + + if (forcesCount > 4096000) { + throw ProtocolException.arrayTooLong("Forces", forcesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + if (varPos5 + varIntLen + forcesCount * 18L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Forces", varPos5 + varIntLen + forcesCount * 18, buf.readableBytes()); + } + + obj.forces = new AppliedForce[forcesCount]; + int elemPos = varPos5 + varIntLen; + + for (int ix = 0; ix < forcesCount; ix++) { + obj.forces[ix] = AppliedForce.deserialize(buf, elemPos); + elemPos += AppliedForce.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 104; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 80); + int pos0 = offset + 104 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 84); + int pos1 = offset + 104 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 88); + int pos2 = offset + 104 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 92); + int pos3 = offset + 104 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 96); + int pos4 = offset + 104 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 64) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 100); + int pos5 = offset + 104 + fieldOffset5; + int arrLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5); + + for (int i = 0; i < arrLen; i++) { + pos5 += AppliedForce.computeBytesConsumed(buf, pos5); + } + + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.velocityConfig != null) { + nullBits = (byte)(nullBits | 32); + } + + if (this.forces != null) { + nullBits = (byte)(nullBits | 64); + } + + if (this.verticalClamp != null) { + nullBits = (byte)(nullBits | 128); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + if (this.velocityConfig != null) { + this.velocityConfig.serialize(buf); + } else { + buf.writeZero(21); + } + + buf.writeByte(this.changeVelocityType.getValue()); + buf.writeFloatLE(this.duration); + if (this.verticalClamp != null) { + this.verticalClamp.serialize(buf); + } else { + buf.writeZero(8); + } + + buf.writeByte(this.waitForGround ? 1 : 0); + buf.writeByte(this.waitForCollision ? 1 : 0); + buf.writeFloatLE(this.groundCheckDelay); + buf.writeFloatLE(this.collisionCheckDelay); + buf.writeIntLE(this.groundNext); + buf.writeIntLE(this.collisionNext); + buf.writeFloatLE(this.raycastDistance); + buf.writeFloatLE(this.raycastHeightOffset); + buf.writeByte(this.raycastMode.getValue()); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int forcesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.forces != null) { + buf.setIntLE(forcesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.forces.length > 4096000) { + throw ProtocolException.arrayTooLong("Forces", this.forces.length, 4096000); + } + + VarInt.write(buf, this.forces.length); + + for (AppliedForce item : this.forces) { + item.serialize(buf); + } + } else { + buf.setIntLE(forcesOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 104; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.forces != null) { + size += VarInt.size(this.forces.length) + this.forces.length * 18; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 104) { + return ValidationResult.error("Buffer too small: expected at least 104 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 80); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 104 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 84); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 104 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 88); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 104 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 92); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 104 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 96); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 104 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 64) != 0) { + int forcesOffset = buffer.getIntLE(offset + 100); + if (forcesOffset < 0) { + return ValidationResult.error("Invalid offset for Forces"); + } + + int posxxxxx = offset + 104 + forcesOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Forces"); + } + + int forcesCount = VarInt.peek(buffer, posxxxxx); + if (forcesCount < 0) { + return ValidationResult.error("Invalid array count for Forces"); + } + + if (forcesCount > 4096000) { + return ValidationResult.error("Forces exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += forcesCount * 18; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Forces"); + } + } + + return ValidationResult.OK; + } + } + + public ApplyForceInteraction clone() { + ApplyForceInteraction copy = new ApplyForceInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.velocityConfig = this.velocityConfig != null ? this.velocityConfig.clone() : null; + copy.changeVelocityType = this.changeVelocityType; + copy.forces = this.forces != null ? Arrays.stream(this.forces).map(ex -> ex.clone()).toArray(AppliedForce[]::new) : null; + copy.duration = this.duration; + copy.verticalClamp = this.verticalClamp != null ? this.verticalClamp.clone() : null; + copy.waitForGround = this.waitForGround; + copy.waitForCollision = this.waitForCollision; + copy.groundCheckDelay = this.groundCheckDelay; + copy.collisionCheckDelay = this.collisionCheckDelay; + copy.groundNext = this.groundNext; + copy.collisionNext = this.collisionNext; + copy.raycastDistance = this.raycastDistance; + copy.raycastHeightOffset = this.raycastHeightOffset; + copy.raycastMode = this.raycastMode; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ApplyForceInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && Objects.equals(this.velocityConfig, other.velocityConfig) + && Objects.equals(this.changeVelocityType, other.changeVelocityType) + && Arrays.equals((Object[])this.forces, (Object[])other.forces) + && this.duration == other.duration + && Objects.equals(this.verticalClamp, other.verticalClamp) + && this.waitForGround == other.waitForGround + && this.waitForCollision == other.waitForCollision + && this.groundCheckDelay == other.groundCheckDelay + && this.collisionCheckDelay == other.collisionCheckDelay + && this.groundNext == other.groundNext + && this.collisionNext == other.collisionNext + && this.raycastDistance == other.raycastDistance + && this.raycastHeightOffset == other.raycastHeightOffset + && Objects.equals(this.raycastMode, other.raycastMode); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Objects.hashCode(this.velocityConfig); + result = 31 * result + Objects.hashCode(this.changeVelocityType); + result = 31 * result + Arrays.hashCode((Object[])this.forces); + result = 31 * result + Float.hashCode(this.duration); + result = 31 * result + Objects.hashCode(this.verticalClamp); + result = 31 * result + Boolean.hashCode(this.waitForGround); + result = 31 * result + Boolean.hashCode(this.waitForCollision); + result = 31 * result + Float.hashCode(this.groundCheckDelay); + result = 31 * result + Float.hashCode(this.collisionCheckDelay); + result = 31 * result + Integer.hashCode(this.groundNext); + result = 31 * result + Integer.hashCode(this.collisionNext); + result = 31 * result + Float.hashCode(this.raycastDistance); + result = 31 * result + Float.hashCode(this.raycastHeightOffset); + return 31 * result + Objects.hashCode(this.raycastMode); + } +} diff --git a/src/com/hypixel/hytale/protocol/ApplyForceState.java b/src/com/hypixel/hytale/protocol/ApplyForceState.java new file mode 100644 index 0000000..dde27e4 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ApplyForceState.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ApplyForceState { + Waiting(0), + Ground(1), + Collision(2), + Timer(3); + + public static final ApplyForceState[] VALUES = values(); + private final int value; + + private ApplyForceState(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ApplyForceState fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ApplyForceState", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ApplyLookType.java b/src/com/hypixel/hytale/protocol/ApplyLookType.java new file mode 100644 index 0000000..9ce22dd --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ApplyLookType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ApplyLookType { + LocalPlayerLookOrientation(0), + Rotation(1); + + public static final ApplyLookType[] VALUES = values(); + private final int value; + + private ApplyLookType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ApplyLookType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ApplyLookType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ApplyMovementType.java b/src/com/hypixel/hytale/protocol/ApplyMovementType.java new file mode 100644 index 0000000..7b21d68 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ApplyMovementType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ApplyMovementType { + CharacterController(0), + Position(1); + + public static final ApplyMovementType[] VALUES = values(); + private final int value; + + private ApplyMovementType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ApplyMovementType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ApplyMovementType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/Asset.java b/src/com/hypixel/hytale/protocol/Asset.java new file mode 100644 index 0000000..4657bcd --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Asset.java @@ -0,0 +1,108 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class Asset { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 64; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 64; + public static final int MAX_SIZE = 2117; + @Nonnull + public String hash = ""; + @Nonnull + public String name = ""; + + public Asset() { + } + + public Asset(@Nonnull String hash, @Nonnull String name) { + this.hash = hash; + this.name = name; + } + + public Asset(@Nonnull Asset other) { + this.hash = other.hash; + this.name = other.name; + } + + @Nonnull + public static Asset deserialize(@Nonnull ByteBuf buf, int offset) { + Asset obj = new Asset(); + obj.hash = PacketIO.readFixedAsciiString(buf, offset + 0, 64); + int pos = offset + 64; + int nameLen = VarInt.peek(buf, pos); + if (nameLen < 0) { + throw ProtocolException.negativeLength("Name", nameLen); + } else if (nameLen > 512) { + throw ProtocolException.stringTooLong("Name", nameLen, 512); + } else { + int nameVarLen = VarInt.length(buf, pos); + obj.name = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += nameVarLen + nameLen; + return obj; + } + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + int pos = offset + 64; + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + PacketIO.writeFixedAsciiString(buf, this.hash, 64); + PacketIO.writeVarString(buf, this.name, 512); + } + + public int computeSize() { + int size = 64; + return size + PacketIO.stringSize(this.name); + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 64) { + return ValidationResult.error("Buffer too small: expected at least 64 bytes"); + } else { + int pos = offset + 64; + int nameLen = VarInt.peek(buffer, pos); + if (nameLen < 0) { + return ValidationResult.error("Invalid string length for Name"); + } else if (nameLen > 512) { + return ValidationResult.error("Name exceeds max length 512"); + } else { + pos += VarInt.length(buffer, pos); + pos += nameLen; + return pos > buffer.writerIndex() ? ValidationResult.error("Buffer overflow reading Name") : ValidationResult.OK; + } + } + } + + public Asset clone() { + Asset copy = new Asset(); + copy.hash = this.hash; + copy.name = this.name; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Asset other) ? false : Objects.equals(this.hash, other.hash) && Objects.equals(this.name, other.name); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.hash, this.name); + } +} diff --git a/src/com/hypixel/hytale/protocol/AssetIconProperties.java b/src/com/hypixel/hytale/protocol/AssetIconProperties.java new file mode 100644 index 0000000..ca1516d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AssetIconProperties.java @@ -0,0 +1,112 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetIconProperties { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 25; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 25; + public static final int MAX_SIZE = 25; + public float scale; + @Nullable + public Vector2f translation; + @Nullable + public Vector3f rotation; + + public AssetIconProperties() { + } + + public AssetIconProperties(float scale, @Nullable Vector2f translation, @Nullable Vector3f rotation) { + this.scale = scale; + this.translation = translation; + this.rotation = rotation; + } + + public AssetIconProperties(@Nonnull AssetIconProperties other) { + this.scale = other.scale; + this.translation = other.translation; + this.rotation = other.rotation; + } + + @Nonnull + public static AssetIconProperties deserialize(@Nonnull ByteBuf buf, int offset) { + AssetIconProperties obj = new AssetIconProperties(); + byte nullBits = buf.getByte(offset); + obj.scale = buf.getFloatLE(offset + 1); + if ((nullBits & 1) != 0) { + obj.translation = Vector2f.deserialize(buf, offset + 5); + } + + if ((nullBits & 2) != 0) { + obj.rotation = Vector3f.deserialize(buf, offset + 13); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 25; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.translation != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.rotation != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.scale); + if (this.translation != null) { + this.translation.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.rotation != null) { + this.rotation.serialize(buf); + } else { + buf.writeZero(12); + } + } + + public int computeSize() { + return 25; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 25 ? ValidationResult.error("Buffer too small: expected at least 25 bytes") : ValidationResult.OK; + } + + public AssetIconProperties clone() { + AssetIconProperties copy = new AssetIconProperties(); + copy.scale = this.scale; + copy.translation = this.translation != null ? this.translation.clone() : null; + copy.rotation = this.rotation != null ? this.rotation.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetIconProperties other) + ? false + : this.scale == other.scale && Objects.equals(this.translation, other.translation) && Objects.equals(this.rotation, other.rotation); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.scale, this.translation, this.rotation); + } +} diff --git a/src/com/hypixel/hytale/protocol/AttachedToType.java b/src/com/hypixel/hytale/protocol/AttachedToType.java new file mode 100644 index 0000000..c6b2c86 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AttachedToType.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum AttachedToType { + LocalPlayer(0), + EntityId(1), + None(2); + + public static final AttachedToType[] VALUES = values(); + private final int value; + + private AttachedToType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static AttachedToType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("AttachedToType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/AudioCategory.java b/src/com/hypixel/hytale/protocol/AudioCategory.java new file mode 100644 index 0000000..4410bb3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/AudioCategory.java @@ -0,0 +1,139 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AudioCategory { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 16384010; + @Nullable + public String id; + public float volume; + + public AudioCategory() { + } + + public AudioCategory(@Nullable String id, float volume) { + this.id = id; + this.volume = volume; + } + + public AudioCategory(@Nonnull AudioCategory other) { + this.id = other.id; + this.volume = other.volume; + } + + @Nonnull + public static AudioCategory deserialize(@Nonnull ByteBuf buf, int offset) { + AudioCategory obj = new AudioCategory(); + byte nullBits = buf.getByte(offset); + obj.volume = buf.getFloatLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int idLen = VarInt.peek(buf, pos); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + int idVarLen = VarInt.length(buf, pos); + obj.id = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += idVarLen + idLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.volume); + if (this.id != null) { + PacketIO.writeVarString(buf, this.id, 4096000); + } + } + + public int computeSize() { + int size = 5; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + return ValidationResult.OK; + } + } + + public AudioCategory clone() { + AudioCategory copy = new AudioCategory(); + copy.id = this.id; + copy.volume = this.volume; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AudioCategory other) ? false : Objects.equals(this.id, other.id) && this.volume == other.volume; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.volume); + } +} diff --git a/src/com/hypixel/hytale/protocol/Bench.java b/src/com/hypixel/hytale/protocol/Bench.java new file mode 100644 index 0000000..ce6c13e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Bench.java @@ -0,0 +1,165 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Bench { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public BenchTierLevel[] benchTierLevels; + + public Bench() { + } + + public Bench(@Nullable BenchTierLevel[] benchTierLevels) { + this.benchTierLevels = benchTierLevels; + } + + public Bench(@Nonnull Bench other) { + this.benchTierLevels = other.benchTierLevels; + } + + @Nonnull + public static Bench deserialize(@Nonnull ByteBuf buf, int offset) { + Bench obj = new Bench(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int benchTierLevelsCount = VarInt.peek(buf, pos); + if (benchTierLevelsCount < 0) { + throw ProtocolException.negativeLength("BenchTierLevels", benchTierLevelsCount); + } + + if (benchTierLevelsCount > 4096000) { + throw ProtocolException.arrayTooLong("BenchTierLevels", benchTierLevelsCount, 4096000); + } + + int benchTierLevelsVarLen = VarInt.size(benchTierLevelsCount); + if (pos + benchTierLevelsVarLen + benchTierLevelsCount * 17L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("BenchTierLevels", pos + benchTierLevelsVarLen + benchTierLevelsCount * 17, buf.readableBytes()); + } + + pos += benchTierLevelsVarLen; + obj.benchTierLevels = new BenchTierLevel[benchTierLevelsCount]; + + for (int i = 0; i < benchTierLevelsCount; i++) { + obj.benchTierLevels[i] = BenchTierLevel.deserialize(buf, pos); + pos += BenchTierLevel.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += BenchTierLevel.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.benchTierLevels != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.benchTierLevels != null) { + if (this.benchTierLevels.length > 4096000) { + throw ProtocolException.arrayTooLong("BenchTierLevels", this.benchTierLevels.length, 4096000); + } + + VarInt.write(buf, this.benchTierLevels.length); + + for (BenchTierLevel item : this.benchTierLevels) { + item.serialize(buf); + } + } + } + + public int computeSize() { + int size = 1; + if (this.benchTierLevels != null) { + int benchTierLevelsSize = 0; + + for (BenchTierLevel elem : this.benchTierLevels) { + benchTierLevelsSize += elem.computeSize(); + } + + size += VarInt.size(this.benchTierLevels.length) + benchTierLevelsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int benchTierLevelsCount = VarInt.peek(buffer, pos); + if (benchTierLevelsCount < 0) { + return ValidationResult.error("Invalid array count for BenchTierLevels"); + } + + if (benchTierLevelsCount > 4096000) { + return ValidationResult.error("BenchTierLevels exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < benchTierLevelsCount; i++) { + ValidationResult structResult = BenchTierLevel.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid BenchTierLevel in BenchTierLevels[" + i + "]: " + structResult.error()); + } + + pos += BenchTierLevel.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public Bench clone() { + Bench copy = new Bench(); + copy.benchTierLevels = this.benchTierLevels != null ? Arrays.stream(this.benchTierLevels).map(e -> e.clone()).toArray(BenchTierLevel[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof Bench other ? Arrays.equals((Object[])this.benchTierLevels, (Object[])other.benchTierLevels) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.benchTierLevels); + } +} diff --git a/src/com/hypixel/hytale/protocol/BenchRequirement.java b/src/com/hypixel/hytale/protocol/BenchRequirement.java new file mode 100644 index 0000000..446ddc9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BenchRequirement.java @@ -0,0 +1,298 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BenchRequirement { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 14; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public BenchType type = BenchType.Crafting; + @Nullable + public String id; + @Nullable + public String[] categories; + public int requiredTierLevel; + + public BenchRequirement() { + } + + public BenchRequirement(@Nonnull BenchType type, @Nullable String id, @Nullable String[] categories, int requiredTierLevel) { + this.type = type; + this.id = id; + this.categories = categories; + this.requiredTierLevel = requiredTierLevel; + } + + public BenchRequirement(@Nonnull BenchRequirement other) { + this.type = other.type; + this.id = other.id; + this.categories = other.categories; + this.requiredTierLevel = other.requiredTierLevel; + } + + @Nonnull + public static BenchRequirement deserialize(@Nonnull ByteBuf buf, int offset) { + BenchRequirement obj = new BenchRequirement(); + byte nullBits = buf.getByte(offset); + obj.type = BenchType.fromValue(buf.getByte(offset + 1)); + obj.requiredTierLevel = buf.getIntLE(offset + 2); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 14 + buf.getIntLE(offset + 6); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 14 + buf.getIntLE(offset + 10); + int categoriesCount = VarInt.peek(buf, varPos1); + if (categoriesCount < 0) { + throw ProtocolException.negativeLength("Categories", categoriesCount); + } + + if (categoriesCount > 4096000) { + throw ProtocolException.arrayTooLong("Categories", categoriesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + categoriesCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Categories", varPos1 + varIntLen + categoriesCount * 1, buf.readableBytes()); + } + + obj.categories = new String[categoriesCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < categoriesCount; i++) { + int strLen = VarInt.peek(buf, elemPos); + if (strLen < 0) { + throw ProtocolException.negativeLength("categories[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("categories[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, elemPos); + obj.categories[i] = PacketIO.readVarString(buf, elemPos); + elemPos += strVarLen + strLen; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 14; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 6); + int pos0 = offset + 14 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 10); + int pos1 = offset + 14 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.categories != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.requiredTierLevel); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int categoriesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.categories != null) { + buf.setIntLE(categoriesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.categories.length > 4096000) { + throw ProtocolException.arrayTooLong("Categories", this.categories.length, 4096000); + } + + VarInt.write(buf, this.categories.length); + + for (String item : this.categories) { + PacketIO.writeVarString(buf, item, 4096000); + } + } else { + buf.setIntLE(categoriesOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 14; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.categories != null) { + int categoriesSize = 0; + + for (String elem : this.categories) { + categoriesSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.categories.length) + categoriesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 14) { + return ValidationResult.error("Buffer too small: expected at least 14 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 6); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 14 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int categoriesOffset = buffer.getIntLE(offset + 10); + if (categoriesOffset < 0) { + return ValidationResult.error("Invalid offset for Categories"); + } + + int posx = offset + 14 + categoriesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Categories"); + } + + int categoriesCount = VarInt.peek(buffer, posx); + if (categoriesCount < 0) { + return ValidationResult.error("Invalid array count for Categories"); + } + + if (categoriesCount > 4096000) { + return ValidationResult.error("Categories exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < categoriesCount; i++) { + int strLen = VarInt.peek(buffer, posx); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in Categories"); + } + + posx += VarInt.length(buffer, posx); + posx += strLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in Categories"); + } + } + } + + return ValidationResult.OK; + } + } + + public BenchRequirement clone() { + BenchRequirement copy = new BenchRequirement(); + copy.type = this.type; + copy.id = this.id; + copy.categories = this.categories != null ? Arrays.copyOf(this.categories, this.categories.length) : null; + copy.requiredTierLevel = this.requiredTierLevel; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BenchRequirement other) + ? false + : Objects.equals(this.type, other.type) + && Objects.equals(this.id, other.id) + && Arrays.equals((Object[])this.categories, (Object[])other.categories) + && this.requiredTierLevel == other.requiredTierLevel; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.type); + result = 31 * result + Objects.hashCode(this.id); + result = 31 * result + Arrays.hashCode((Object[])this.categories); + return 31 * result + Integer.hashCode(this.requiredTierLevel); + } +} diff --git a/src/com/hypixel/hytale/protocol/BenchTierLevel.java b/src/com/hypixel/hytale/protocol/BenchTierLevel.java new file mode 100644 index 0000000..ee710bf --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BenchTierLevel.java @@ -0,0 +1,136 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BenchTierLevel { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 17; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 17; + public static final int MAX_SIZE = 1677721600; + @Nullable + public BenchUpgradeRequirement benchUpgradeRequirement; + public double craftingTimeReductionModifier; + public int extraInputSlot; + public int extraOutputSlot; + + public BenchTierLevel() { + } + + public BenchTierLevel( + @Nullable BenchUpgradeRequirement benchUpgradeRequirement, double craftingTimeReductionModifier, int extraInputSlot, int extraOutputSlot + ) { + this.benchUpgradeRequirement = benchUpgradeRequirement; + this.craftingTimeReductionModifier = craftingTimeReductionModifier; + this.extraInputSlot = extraInputSlot; + this.extraOutputSlot = extraOutputSlot; + } + + public BenchTierLevel(@Nonnull BenchTierLevel other) { + this.benchUpgradeRequirement = other.benchUpgradeRequirement; + this.craftingTimeReductionModifier = other.craftingTimeReductionModifier; + this.extraInputSlot = other.extraInputSlot; + this.extraOutputSlot = other.extraOutputSlot; + } + + @Nonnull + public static BenchTierLevel deserialize(@Nonnull ByteBuf buf, int offset) { + BenchTierLevel obj = new BenchTierLevel(); + byte nullBits = buf.getByte(offset); + obj.craftingTimeReductionModifier = buf.getDoubleLE(offset + 1); + obj.extraInputSlot = buf.getIntLE(offset + 9); + obj.extraOutputSlot = buf.getIntLE(offset + 13); + int pos = offset + 17; + if ((nullBits & 1) != 0) { + obj.benchUpgradeRequirement = BenchUpgradeRequirement.deserialize(buf, pos); + pos += BenchUpgradeRequirement.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 17; + if ((nullBits & 1) != 0) { + pos += BenchUpgradeRequirement.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.benchUpgradeRequirement != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeDoubleLE(this.craftingTimeReductionModifier); + buf.writeIntLE(this.extraInputSlot); + buf.writeIntLE(this.extraOutputSlot); + if (this.benchUpgradeRequirement != null) { + this.benchUpgradeRequirement.serialize(buf); + } + } + + public int computeSize() { + int size = 17; + if (this.benchUpgradeRequirement != null) { + size += this.benchUpgradeRequirement.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 17) { + return ValidationResult.error("Buffer too small: expected at least 17 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 17; + if ((nullBits & 1) != 0) { + ValidationResult benchUpgradeRequirementResult = BenchUpgradeRequirement.validateStructure(buffer, pos); + if (!benchUpgradeRequirementResult.isValid()) { + return ValidationResult.error("Invalid BenchUpgradeRequirement: " + benchUpgradeRequirementResult.error()); + } + + pos += BenchUpgradeRequirement.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public BenchTierLevel clone() { + BenchTierLevel copy = new BenchTierLevel(); + copy.benchUpgradeRequirement = this.benchUpgradeRequirement != null ? this.benchUpgradeRequirement.clone() : null; + copy.craftingTimeReductionModifier = this.craftingTimeReductionModifier; + copy.extraInputSlot = this.extraInputSlot; + copy.extraOutputSlot = this.extraOutputSlot; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BenchTierLevel other) + ? false + : Objects.equals(this.benchUpgradeRequirement, other.benchUpgradeRequirement) + && this.craftingTimeReductionModifier == other.craftingTimeReductionModifier + && this.extraInputSlot == other.extraInputSlot + && this.extraOutputSlot == other.extraOutputSlot; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.benchUpgradeRequirement, this.craftingTimeReductionModifier, this.extraInputSlot, this.extraOutputSlot); + } +} diff --git a/src/com/hypixel/hytale/protocol/BenchType.java b/src/com/hypixel/hytale/protocol/BenchType.java new file mode 100644 index 0000000..56809ab --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BenchType.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum BenchType { + Crafting(0), + Processing(1), + DiagramCrafting(2), + StructuralCrafting(3); + + public static final BenchType[] VALUES = values(); + private final int value; + + private BenchType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static BenchType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("BenchType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/BenchUpgradeRequirement.java b/src/com/hypixel/hytale/protocol/BenchUpgradeRequirement.java new file mode 100644 index 0000000..6604da3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BenchUpgradeRequirement.java @@ -0,0 +1,174 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BenchUpgradeRequirement { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 1677721600; + @Nullable + public MaterialQuantity[] material; + public double timeSeconds; + + public BenchUpgradeRequirement() { + } + + public BenchUpgradeRequirement(@Nullable MaterialQuantity[] material, double timeSeconds) { + this.material = material; + this.timeSeconds = timeSeconds; + } + + public BenchUpgradeRequirement(@Nonnull BenchUpgradeRequirement other) { + this.material = other.material; + this.timeSeconds = other.timeSeconds; + } + + @Nonnull + public static BenchUpgradeRequirement deserialize(@Nonnull ByteBuf buf, int offset) { + BenchUpgradeRequirement obj = new BenchUpgradeRequirement(); + byte nullBits = buf.getByte(offset); + obj.timeSeconds = buf.getDoubleLE(offset + 1); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int materialCount = VarInt.peek(buf, pos); + if (materialCount < 0) { + throw ProtocolException.negativeLength("Material", materialCount); + } + + if (materialCount > 4096000) { + throw ProtocolException.arrayTooLong("Material", materialCount, 4096000); + } + + int materialVarLen = VarInt.size(materialCount); + if (pos + materialVarLen + materialCount * 9L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Material", pos + materialVarLen + materialCount * 9, buf.readableBytes()); + } + + pos += materialVarLen; + obj.material = new MaterialQuantity[materialCount]; + + for (int i = 0; i < materialCount; i++) { + obj.material[i] = MaterialQuantity.deserialize(buf, pos); + pos += MaterialQuantity.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += MaterialQuantity.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.material != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeDoubleLE(this.timeSeconds); + if (this.material != null) { + if (this.material.length > 4096000) { + throw ProtocolException.arrayTooLong("Material", this.material.length, 4096000); + } + + VarInt.write(buf, this.material.length); + + for (MaterialQuantity item : this.material) { + item.serialize(buf); + } + } + } + + public int computeSize() { + int size = 9; + if (this.material != null) { + int materialSize = 0; + + for (MaterialQuantity elem : this.material) { + materialSize += elem.computeSize(); + } + + size += VarInt.size(this.material.length) + materialSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int materialCount = VarInt.peek(buffer, pos); + if (materialCount < 0) { + return ValidationResult.error("Invalid array count for Material"); + } + + if (materialCount > 4096000) { + return ValidationResult.error("Material exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < materialCount; i++) { + ValidationResult structResult = MaterialQuantity.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid MaterialQuantity in Material[" + i + "]: " + structResult.error()); + } + + pos += MaterialQuantity.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public BenchUpgradeRequirement clone() { + BenchUpgradeRequirement copy = new BenchUpgradeRequirement(); + copy.material = this.material != null ? Arrays.stream(this.material).map(e -> e.clone()).toArray(MaterialQuantity[]::new) : null; + copy.timeSeconds = this.timeSeconds; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BenchUpgradeRequirement other) + ? false + : Arrays.equals((Object[])this.material, (Object[])other.material) && this.timeSeconds == other.timeSeconds; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode((Object[])this.material); + return 31 * result + Double.hashCode(this.timeSeconds); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockBreaking.java b/src/com/hypixel/hytale/protocol/BlockBreaking.java new file mode 100644 index 0000000..42be2ce --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockBreaking.java @@ -0,0 +1,323 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockBreaking { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 13; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 25; + public static final int MAX_SIZE = 49152040; + @Nullable + public String gatherType; + public float health; + public int quantity = 1; + public int quality; + @Nullable + public String itemId; + @Nullable + public String dropListId; + + public BlockBreaking() { + } + + public BlockBreaking(@Nullable String gatherType, float health, int quantity, int quality, @Nullable String itemId, @Nullable String dropListId) { + this.gatherType = gatherType; + this.health = health; + this.quantity = quantity; + this.quality = quality; + this.itemId = itemId; + this.dropListId = dropListId; + } + + public BlockBreaking(@Nonnull BlockBreaking other) { + this.gatherType = other.gatherType; + this.health = other.health; + this.quantity = other.quantity; + this.quality = other.quality; + this.itemId = other.itemId; + this.dropListId = other.dropListId; + } + + @Nonnull + public static BlockBreaking deserialize(@Nonnull ByteBuf buf, int offset) { + BlockBreaking obj = new BlockBreaking(); + byte nullBits = buf.getByte(offset); + obj.health = buf.getFloatLE(offset + 1); + obj.quantity = buf.getIntLE(offset + 5); + obj.quality = buf.getIntLE(offset + 9); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 25 + buf.getIntLE(offset + 13); + int gatherTypeLen = VarInt.peek(buf, varPos0); + if (gatherTypeLen < 0) { + throw ProtocolException.negativeLength("GatherType", gatherTypeLen); + } + + if (gatherTypeLen > 4096000) { + throw ProtocolException.stringTooLong("GatherType", gatherTypeLen, 4096000); + } + + obj.gatherType = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 25 + buf.getIntLE(offset + 17); + int itemIdLen = VarInt.peek(buf, varPos1); + if (itemIdLen < 0) { + throw ProtocolException.negativeLength("ItemId", itemIdLen); + } + + if (itemIdLen > 4096000) { + throw ProtocolException.stringTooLong("ItemId", itemIdLen, 4096000); + } + + obj.itemId = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 25 + buf.getIntLE(offset + 21); + int dropListIdLen = VarInt.peek(buf, varPos2); + if (dropListIdLen < 0) { + throw ProtocolException.negativeLength("DropListId", dropListIdLen); + } + + if (dropListIdLen > 4096000) { + throw ProtocolException.stringTooLong("DropListId", dropListIdLen, 4096000); + } + + obj.dropListId = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 25; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 13); + int pos0 = offset + 25 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 17); + int pos1 = offset + 25 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 21); + int pos2 = offset + 25 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.gatherType != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.itemId != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.dropListId != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.health); + buf.writeIntLE(this.quantity); + buf.writeIntLE(this.quality); + int gatherTypeOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int itemIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int dropListIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.gatherType != null) { + buf.setIntLE(gatherTypeOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.gatherType, 4096000); + } else { + buf.setIntLE(gatherTypeOffsetSlot, -1); + } + + if (this.itemId != null) { + buf.setIntLE(itemIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.itemId, 4096000); + } else { + buf.setIntLE(itemIdOffsetSlot, -1); + } + + if (this.dropListId != null) { + buf.setIntLE(dropListIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.dropListId, 4096000); + } else { + buf.setIntLE(dropListIdOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 25; + if (this.gatherType != null) { + size += PacketIO.stringSize(this.gatherType); + } + + if (this.itemId != null) { + size += PacketIO.stringSize(this.itemId); + } + + if (this.dropListId != null) { + size += PacketIO.stringSize(this.dropListId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 25) { + return ValidationResult.error("Buffer too small: expected at least 25 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int gatherTypeOffset = buffer.getIntLE(offset + 13); + if (gatherTypeOffset < 0) { + return ValidationResult.error("Invalid offset for GatherType"); + } + + int pos = offset + 25 + gatherTypeOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for GatherType"); + } + + int gatherTypeLen = VarInt.peek(buffer, pos); + if (gatherTypeLen < 0) { + return ValidationResult.error("Invalid string length for GatherType"); + } + + if (gatherTypeLen > 4096000) { + return ValidationResult.error("GatherType exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += gatherTypeLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading GatherType"); + } + } + + if ((nullBits & 2) != 0) { + int itemIdOffset = buffer.getIntLE(offset + 17); + if (itemIdOffset < 0) { + return ValidationResult.error("Invalid offset for ItemId"); + } + + int posx = offset + 25 + itemIdOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ItemId"); + } + + int itemIdLen = VarInt.peek(buffer, posx); + if (itemIdLen < 0) { + return ValidationResult.error("Invalid string length for ItemId"); + } + + if (itemIdLen > 4096000) { + return ValidationResult.error("ItemId exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += itemIdLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ItemId"); + } + } + + if ((nullBits & 4) != 0) { + int dropListIdOffset = buffer.getIntLE(offset + 21); + if (dropListIdOffset < 0) { + return ValidationResult.error("Invalid offset for DropListId"); + } + + int posxx = offset + 25 + dropListIdOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for DropListId"); + } + + int dropListIdLen = VarInt.peek(buffer, posxx); + if (dropListIdLen < 0) { + return ValidationResult.error("Invalid string length for DropListId"); + } + + if (dropListIdLen > 4096000) { + return ValidationResult.error("DropListId exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += dropListIdLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading DropListId"); + } + } + + return ValidationResult.OK; + } + } + + public BlockBreaking clone() { + BlockBreaking copy = new BlockBreaking(); + copy.gatherType = this.gatherType; + copy.health = this.health; + copy.quantity = this.quantity; + copy.quality = this.quality; + copy.itemId = this.itemId; + copy.dropListId = this.dropListId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockBreaking other) + ? false + : Objects.equals(this.gatherType, other.gatherType) + && this.health == other.health + && this.quantity == other.quantity + && this.quality == other.quality + && Objects.equals(this.itemId, other.itemId) + && Objects.equals(this.dropListId, other.dropListId); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.gatherType, this.health, this.quantity, this.quality, this.itemId, this.dropListId); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockBreakingDecal.java b/src/com/hypixel/hytale/protocol/BlockBreakingDecal.java new file mode 100644 index 0000000..ec8bd2a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockBreakingDecal.java @@ -0,0 +1,181 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockBreakingDecal { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String[] stageTextures; + + public BlockBreakingDecal() { + } + + public BlockBreakingDecal(@Nullable String[] stageTextures) { + this.stageTextures = stageTextures; + } + + public BlockBreakingDecal(@Nonnull BlockBreakingDecal other) { + this.stageTextures = other.stageTextures; + } + + @Nonnull + public static BlockBreakingDecal deserialize(@Nonnull ByteBuf buf, int offset) { + BlockBreakingDecal obj = new BlockBreakingDecal(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int stageTexturesCount = VarInt.peek(buf, pos); + if (stageTexturesCount < 0) { + throw ProtocolException.negativeLength("StageTextures", stageTexturesCount); + } + + if (stageTexturesCount > 4096000) { + throw ProtocolException.arrayTooLong("StageTextures", stageTexturesCount, 4096000); + } + + int stageTexturesVarLen = VarInt.size(stageTexturesCount); + if (pos + stageTexturesVarLen + stageTexturesCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("StageTextures", pos + stageTexturesVarLen + stageTexturesCount * 1, buf.readableBytes()); + } + + pos += stageTexturesVarLen; + obj.stageTextures = new String[stageTexturesCount]; + + for (int i = 0; i < stageTexturesCount; i++) { + int strLen = VarInt.peek(buf, pos); + if (strLen < 0) { + throw ProtocolException.negativeLength("stageTextures[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("stageTextures[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, pos); + obj.stageTextures[i] = PacketIO.readVarString(buf, pos); + pos += strVarLen + strLen; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.stageTextures != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.stageTextures != null) { + if (this.stageTextures.length > 4096000) { + throw ProtocolException.arrayTooLong("StageTextures", this.stageTextures.length, 4096000); + } + + VarInt.write(buf, this.stageTextures.length); + + for (String item : this.stageTextures) { + PacketIO.writeVarString(buf, item, 4096000); + } + } + } + + public int computeSize() { + int size = 1; + if (this.stageTextures != null) { + int stageTexturesSize = 0; + + for (String elem : this.stageTextures) { + stageTexturesSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.stageTextures.length) + stageTexturesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int stageTexturesCount = VarInt.peek(buffer, pos); + if (stageTexturesCount < 0) { + return ValidationResult.error("Invalid array count for StageTextures"); + } + + if (stageTexturesCount > 4096000) { + return ValidationResult.error("StageTextures exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < stageTexturesCount; i++) { + int strLen = VarInt.peek(buffer, pos); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in StageTextures"); + } + + pos += VarInt.length(buffer, pos); + pos += strLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in StageTextures"); + } + } + } + + return ValidationResult.OK; + } + } + + public BlockBreakingDecal clone() { + BlockBreakingDecal copy = new BlockBreakingDecal(); + copy.stageTextures = this.stageTextures != null ? Arrays.copyOf(this.stageTextures, this.stageTextures.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof BlockBreakingDecal other ? Arrays.equals((Object[])this.stageTextures, (Object[])other.stageTextures) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.stageTextures); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockConditionInteraction.java b/src/com/hypixel/hytale/protocol/BlockConditionInteraction.java new file mode 100644 index 0000000..683dade --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockConditionInteraction.java @@ -0,0 +1,623 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockConditionInteraction extends SimpleBlockInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 20; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 44; + public static final int MAX_SIZE = 1677721600; + @Nullable + public BlockMatcher[] matchers; + + public BlockConditionInteraction() { + } + + public BlockConditionInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + boolean useLatestTarget, + @Nullable BlockMatcher[] matchers + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.useLatestTarget = useLatestTarget; + this.matchers = matchers; + } + + public BlockConditionInteraction(@Nonnull BlockConditionInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.useLatestTarget = other.useLatestTarget; + this.matchers = other.matchers; + } + + @Nonnull + public static BlockConditionInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + BlockConditionInteraction obj = new BlockConditionInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.useLatestTarget = buf.getByte(offset + 19) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 44 + buf.getIntLE(offset + 20); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 44 + buf.getIntLE(offset + 24); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 44 + buf.getIntLE(offset + 28); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 44 + buf.getIntLE(offset + 32); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 44 + buf.getIntLE(offset + 36); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 44 + buf.getIntLE(offset + 40); + int matchersCount = VarInt.peek(buf, varPos5); + if (matchersCount < 0) { + throw ProtocolException.negativeLength("Matchers", matchersCount); + } + + if (matchersCount > 4096000) { + throw ProtocolException.arrayTooLong("Matchers", matchersCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + if (varPos5 + varIntLen + matchersCount * 3L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Matchers", varPos5 + varIntLen + matchersCount * 3, buf.readableBytes()); + } + + obj.matchers = new BlockMatcher[matchersCount]; + int elemPos = varPos5 + varIntLen; + + for (int ix = 0; ix < matchersCount; ix++) { + obj.matchers[ix] = BlockMatcher.deserialize(buf, elemPos); + elemPos += BlockMatcher.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 44; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 20); + int pos0 = offset + 44 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 24); + int pos1 = offset + 44 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 28); + int pos2 = offset + 44 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 32); + int pos3 = offset + 44 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 36); + int pos4 = offset + 44 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 40); + int pos5 = offset + 44 + fieldOffset5; + int arrLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5); + + for (int i = 0; i < arrLen; i++) { + pos5 += BlockMatcher.computeBytesConsumed(buf, pos5); + } + + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.matchers != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeByte(this.useLatestTarget ? 1 : 0); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int matchersOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.matchers != null) { + buf.setIntLE(matchersOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.matchers.length > 4096000) { + throw ProtocolException.arrayTooLong("Matchers", this.matchers.length, 4096000); + } + + VarInt.write(buf, this.matchers.length); + + for (BlockMatcher item : this.matchers) { + item.serialize(buf); + } + } else { + buf.setIntLE(matchersOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 44; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.matchers != null) { + int matchersSize = 0; + + for (BlockMatcher elem : this.matchers) { + matchersSize += elem.computeSize(); + } + + size += VarInt.size(this.matchers.length) + matchersSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 44) { + return ValidationResult.error("Buffer too small: expected at least 44 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 20); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 44 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 24); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 44 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 28); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 44 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 32); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 44 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 36); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 44 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int matchersOffset = buffer.getIntLE(offset + 40); + if (matchersOffset < 0) { + return ValidationResult.error("Invalid offset for Matchers"); + } + + int posxxxxx = offset + 44 + matchersOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Matchers"); + } + + int matchersCount = VarInt.peek(buffer, posxxxxx); + if (matchersCount < 0) { + return ValidationResult.error("Invalid array count for Matchers"); + } + + if (matchersCount > 4096000) { + return ValidationResult.error("Matchers exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + + for (int i = 0; i < matchersCount; i++) { + ValidationResult structResult = BlockMatcher.validateStructure(buffer, posxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid BlockMatcher in Matchers[" + i + "]: " + structResult.error()); + } + + posxxxxx += BlockMatcher.computeBytesConsumed(buffer, posxxxxx); + } + } + + return ValidationResult.OK; + } + } + + public BlockConditionInteraction clone() { + BlockConditionInteraction copy = new BlockConditionInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.useLatestTarget = this.useLatestTarget; + copy.matchers = this.matchers != null ? Arrays.stream(this.matchers).map(ex -> ex.clone()).toArray(BlockMatcher[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockConditionInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && this.useLatestTarget == other.useLatestTarget + && Arrays.equals((Object[])this.matchers, (Object[])other.matchers); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Boolean.hashCode(this.useLatestTarget); + return 31 * result + Arrays.hashCode((Object[])this.matchers); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockFace.java b/src/com/hypixel/hytale/protocol/BlockFace.java new file mode 100644 index 0000000..e3a7602 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockFace.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum BlockFace { + None(0), + Up(1), + Down(2), + North(3), + South(4), + East(5), + West(6); + + public static final BlockFace[] VALUES = values(); + private final int value; + + private BlockFace(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static BlockFace fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("BlockFace", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockFaceSupport.java b/src/com/hypixel/hytale/protocol/BlockFaceSupport.java new file mode 100644 index 0000000..74174fa --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockFaceSupport.java @@ -0,0 +1,254 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockFaceSupport { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 65536019; + @Nullable + public String faceType; + @Nullable + public Vector3i[] filler; + + public BlockFaceSupport() { + } + + public BlockFaceSupport(@Nullable String faceType, @Nullable Vector3i[] filler) { + this.faceType = faceType; + this.filler = filler; + } + + public BlockFaceSupport(@Nonnull BlockFaceSupport other) { + this.faceType = other.faceType; + this.filler = other.filler; + } + + @Nonnull + public static BlockFaceSupport deserialize(@Nonnull ByteBuf buf, int offset) { + BlockFaceSupport obj = new BlockFaceSupport(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int faceTypeLen = VarInt.peek(buf, varPos0); + if (faceTypeLen < 0) { + throw ProtocolException.negativeLength("FaceType", faceTypeLen); + } + + if (faceTypeLen > 4096000) { + throw ProtocolException.stringTooLong("FaceType", faceTypeLen, 4096000); + } + + obj.faceType = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int fillerCount = VarInt.peek(buf, varPos1); + if (fillerCount < 0) { + throw ProtocolException.negativeLength("Filler", fillerCount); + } + + if (fillerCount > 4096000) { + throw ProtocolException.arrayTooLong("Filler", fillerCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + fillerCount * 12L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Filler", varPos1 + varIntLen + fillerCount * 12, buf.readableBytes()); + } + + obj.filler = new Vector3i[fillerCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < fillerCount; i++) { + obj.filler[i] = Vector3i.deserialize(buf, elemPos); + elemPos += Vector3i.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += Vector3i.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.faceType != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.filler != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int faceTypeOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int fillerOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.faceType != null) { + buf.setIntLE(faceTypeOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.faceType, 4096000); + } else { + buf.setIntLE(faceTypeOffsetSlot, -1); + } + + if (this.filler != null) { + buf.setIntLE(fillerOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.filler.length > 4096000) { + throw ProtocolException.arrayTooLong("Filler", this.filler.length, 4096000); + } + + VarInt.write(buf, this.filler.length); + + for (Vector3i item : this.filler) { + item.serialize(buf); + } + } else { + buf.setIntLE(fillerOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 9; + if (this.faceType != null) { + size += PacketIO.stringSize(this.faceType); + } + + if (this.filler != null) { + size += VarInt.size(this.filler.length) + this.filler.length * 12; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int faceTypeOffset = buffer.getIntLE(offset + 1); + if (faceTypeOffset < 0) { + return ValidationResult.error("Invalid offset for FaceType"); + } + + int pos = offset + 9 + faceTypeOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FaceType"); + } + + int faceTypeLen = VarInt.peek(buffer, pos); + if (faceTypeLen < 0) { + return ValidationResult.error("Invalid string length for FaceType"); + } + + if (faceTypeLen > 4096000) { + return ValidationResult.error("FaceType exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += faceTypeLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading FaceType"); + } + } + + if ((nullBits & 2) != 0) { + int fillerOffset = buffer.getIntLE(offset + 5); + if (fillerOffset < 0) { + return ValidationResult.error("Invalid offset for Filler"); + } + + int posx = offset + 9 + fillerOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Filler"); + } + + int fillerCount = VarInt.peek(buffer, posx); + if (fillerCount < 0) { + return ValidationResult.error("Invalid array count for Filler"); + } + + if (fillerCount > 4096000) { + return ValidationResult.error("Filler exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += fillerCount * 12; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Filler"); + } + } + + return ValidationResult.OK; + } + } + + public BlockFaceSupport clone() { + BlockFaceSupport copy = new BlockFaceSupport(); + copy.faceType = this.faceType; + copy.filler = this.filler != null ? Arrays.stream(this.filler).map(e -> e.clone()).toArray(Vector3i[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockFaceSupport other) + ? false + : Objects.equals(this.faceType, other.faceType) && Arrays.equals((Object[])this.filler, (Object[])other.filler); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.faceType); + return 31 * result + Arrays.hashCode((Object[])this.filler); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockFlags.java b/src/com/hypixel/hytale/protocol/BlockFlags.java new file mode 100644 index 0000000..929854c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockFlags.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BlockFlags { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 2; + public boolean isUsable; + public boolean isStackable; + + public BlockFlags() { + } + + public BlockFlags(boolean isUsable, boolean isStackable) { + this.isUsable = isUsable; + this.isStackable = isStackable; + } + + public BlockFlags(@Nonnull BlockFlags other) { + this.isUsable = other.isUsable; + this.isStackable = other.isStackable; + } + + @Nonnull + public static BlockFlags deserialize(@Nonnull ByteBuf buf, int offset) { + BlockFlags obj = new BlockFlags(); + obj.isUsable = buf.getByte(offset + 0) != 0; + obj.isStackable = buf.getByte(offset + 1) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 2; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.isUsable ? 1 : 0); + buf.writeByte(this.isStackable ? 1 : 0); + } + + public int computeSize() { + return 2; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 2 ? ValidationResult.error("Buffer too small: expected at least 2 bytes") : ValidationResult.OK; + } + + public BlockFlags clone() { + BlockFlags copy = new BlockFlags(); + copy.isUsable = this.isUsable; + copy.isStackable = this.isStackable; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockFlags other) ? false : this.isUsable == other.isUsable && this.isStackable == other.isStackable; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.isUsable, this.isStackable); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockGathering.java b/src/com/hypixel/hytale/protocol/BlockGathering.java new file mode 100644 index 0000000..9edbfdd --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockGathering.java @@ -0,0 +1,243 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockGathering { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 114688092; + @Nullable + public BlockBreaking breaking; + @Nullable + public Harvesting harvest; + @Nullable + public SoftBlock soft; + + public BlockGathering() { + } + + public BlockGathering(@Nullable BlockBreaking breaking, @Nullable Harvesting harvest, @Nullable SoftBlock soft) { + this.breaking = breaking; + this.harvest = harvest; + this.soft = soft; + } + + public BlockGathering(@Nonnull BlockGathering other) { + this.breaking = other.breaking; + this.harvest = other.harvest; + this.soft = other.soft; + } + + @Nonnull + public static BlockGathering deserialize(@Nonnull ByteBuf buf, int offset) { + BlockGathering obj = new BlockGathering(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 1); + obj.breaking = BlockBreaking.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 5); + obj.harvest = Harvesting.deserialize(buf, varPos1); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 13 + buf.getIntLE(offset + 9); + obj.soft = SoftBlock.deserialize(buf, varPos2); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 13 + fieldOffset0; + pos0 += BlockBreaking.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 13 + fieldOffset1; + pos1 += Harvesting.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 9); + int pos2 = offset + 13 + fieldOffset2; + pos2 += SoftBlock.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.breaking != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.harvest != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.soft != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + int breakingOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int harvestOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int softOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.breaking != null) { + buf.setIntLE(breakingOffsetSlot, buf.writerIndex() - varBlockStart); + this.breaking.serialize(buf); + } else { + buf.setIntLE(breakingOffsetSlot, -1); + } + + if (this.harvest != null) { + buf.setIntLE(harvestOffsetSlot, buf.writerIndex() - varBlockStart); + this.harvest.serialize(buf); + } else { + buf.setIntLE(harvestOffsetSlot, -1); + } + + if (this.soft != null) { + buf.setIntLE(softOffsetSlot, buf.writerIndex() - varBlockStart); + this.soft.serialize(buf); + } else { + buf.setIntLE(softOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 13; + if (this.breaking != null) { + size += this.breaking.computeSize(); + } + + if (this.harvest != null) { + size += this.harvest.computeSize(); + } + + if (this.soft != null) { + size += this.soft.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int breakingOffset = buffer.getIntLE(offset + 1); + if (breakingOffset < 0) { + return ValidationResult.error("Invalid offset for Breaking"); + } + + int pos = offset + 13 + breakingOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Breaking"); + } + + ValidationResult breakingResult = BlockBreaking.validateStructure(buffer, pos); + if (!breakingResult.isValid()) { + return ValidationResult.error("Invalid Breaking: " + breakingResult.error()); + } + + pos += BlockBreaking.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int harvestOffset = buffer.getIntLE(offset + 5); + if (harvestOffset < 0) { + return ValidationResult.error("Invalid offset for Harvest"); + } + + int posx = offset + 13 + harvestOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Harvest"); + } + + ValidationResult harvestResult = Harvesting.validateStructure(buffer, posx); + if (!harvestResult.isValid()) { + return ValidationResult.error("Invalid Harvest: " + harvestResult.error()); + } + + posx += Harvesting.computeBytesConsumed(buffer, posx); + } + + if ((nullBits & 4) != 0) { + int softOffset = buffer.getIntLE(offset + 9); + if (softOffset < 0) { + return ValidationResult.error("Invalid offset for Soft"); + } + + int posxx = offset + 13 + softOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Soft"); + } + + ValidationResult softResult = SoftBlock.validateStructure(buffer, posxx); + if (!softResult.isValid()) { + return ValidationResult.error("Invalid Soft: " + softResult.error()); + } + + posxx += SoftBlock.computeBytesConsumed(buffer, posxx); + } + + return ValidationResult.OK; + } + } + + public BlockGathering clone() { + BlockGathering copy = new BlockGathering(); + copy.breaking = this.breaking != null ? this.breaking.clone() : null; + copy.harvest = this.harvest != null ? this.harvest.clone() : null; + copy.soft = this.soft != null ? this.soft.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockGathering other) + ? false + : Objects.equals(this.breaking, other.breaking) && Objects.equals(this.harvest, other.harvest) && Objects.equals(this.soft, other.soft); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.breaking, this.harvest, this.soft); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockGroup.java b/src/com/hypixel/hytale/protocol/BlockGroup.java new file mode 100644 index 0000000..5ebbb66 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockGroup.java @@ -0,0 +1,181 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockGroup { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String[] names; + + public BlockGroup() { + } + + public BlockGroup(@Nullable String[] names) { + this.names = names; + } + + public BlockGroup(@Nonnull BlockGroup other) { + this.names = other.names; + } + + @Nonnull + public static BlockGroup deserialize(@Nonnull ByteBuf buf, int offset) { + BlockGroup obj = new BlockGroup(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int namesCount = VarInt.peek(buf, pos); + if (namesCount < 0) { + throw ProtocolException.negativeLength("Names", namesCount); + } + + if (namesCount > 4096000) { + throw ProtocolException.arrayTooLong("Names", namesCount, 4096000); + } + + int namesVarLen = VarInt.size(namesCount); + if (pos + namesVarLen + namesCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Names", pos + namesVarLen + namesCount * 1, buf.readableBytes()); + } + + pos += namesVarLen; + obj.names = new String[namesCount]; + + for (int i = 0; i < namesCount; i++) { + int strLen = VarInt.peek(buf, pos); + if (strLen < 0) { + throw ProtocolException.negativeLength("names[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("names[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, pos); + obj.names[i] = PacketIO.readVarString(buf, pos); + pos += strVarLen + strLen; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.names != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.names != null) { + if (this.names.length > 4096000) { + throw ProtocolException.arrayTooLong("Names", this.names.length, 4096000); + } + + VarInt.write(buf, this.names.length); + + for (String item : this.names) { + PacketIO.writeVarString(buf, item, 4096000); + } + } + } + + public int computeSize() { + int size = 1; + if (this.names != null) { + int namesSize = 0; + + for (String elem : this.names) { + namesSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.names.length) + namesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int namesCount = VarInt.peek(buffer, pos); + if (namesCount < 0) { + return ValidationResult.error("Invalid array count for Names"); + } + + if (namesCount > 4096000) { + return ValidationResult.error("Names exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < namesCount; i++) { + int strLen = VarInt.peek(buffer, pos); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in Names"); + } + + pos += VarInt.length(buffer, pos); + pos += strLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in Names"); + } + } + } + + return ValidationResult.OK; + } + } + + public BlockGroup clone() { + BlockGroup copy = new BlockGroup(); + copy.names = this.names != null ? Arrays.copyOf(this.names, this.names.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof BlockGroup other ? Arrays.equals((Object[])this.names, (Object[])other.names) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.names); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockIdMatcher.java b/src/com/hypixel/hytale/protocol/BlockIdMatcher.java new file mode 100644 index 0000000..869cc42 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockIdMatcher.java @@ -0,0 +1,233 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockIdMatcher { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 32768023; + @Nullable + public String id; + @Nullable + public String state; + public int tagIndex; + + public BlockIdMatcher() { + } + + public BlockIdMatcher(@Nullable String id, @Nullable String state, int tagIndex) { + this.id = id; + this.state = state; + this.tagIndex = tagIndex; + } + + public BlockIdMatcher(@Nonnull BlockIdMatcher other) { + this.id = other.id; + this.state = other.state; + this.tagIndex = other.tagIndex; + } + + @Nonnull + public static BlockIdMatcher deserialize(@Nonnull ByteBuf buf, int offset) { + BlockIdMatcher obj = new BlockIdMatcher(); + byte nullBits = buf.getByte(offset); + obj.tagIndex = buf.getIntLE(offset + 1); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 5); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 9); + int stateLen = VarInt.peek(buf, varPos1); + if (stateLen < 0) { + throw ProtocolException.negativeLength("State", stateLen); + } + + if (stateLen > 4096000) { + throw ProtocolException.stringTooLong("State", stateLen, 4096000); + } + + obj.state = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 5); + int pos0 = offset + 13 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 9); + int pos1 = offset + 13 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.state != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.tagIndex); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int stateOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.state != null) { + buf.setIntLE(stateOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.state, 4096000); + } else { + buf.setIntLE(stateOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 13; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.state != null) { + size += PacketIO.stringSize(this.state); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 5); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 13 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int stateOffset = buffer.getIntLE(offset + 9); + if (stateOffset < 0) { + return ValidationResult.error("Invalid offset for State"); + } + + int posx = offset + 13 + stateOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for State"); + } + + int stateLen = VarInt.peek(buffer, posx); + if (stateLen < 0) { + return ValidationResult.error("Invalid string length for State"); + } + + if (stateLen > 4096000) { + return ValidationResult.error("State exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += stateLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading State"); + } + } + + return ValidationResult.OK; + } + } + + public BlockIdMatcher clone() { + BlockIdMatcher copy = new BlockIdMatcher(); + copy.id = this.id; + copy.state = this.state; + copy.tagIndex = this.tagIndex; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockIdMatcher other) + ? false + : Objects.equals(this.id, other.id) && Objects.equals(this.state, other.state) && this.tagIndex == other.tagIndex; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.state, this.tagIndex); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockMatcher.java b/src/com/hypixel/hytale/protocol/BlockMatcher.java new file mode 100644 index 0000000..c7c4c08 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockMatcher.java @@ -0,0 +1,126 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockMatcher { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 3; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 3; + public static final int MAX_SIZE = 32768026; + @Nullable + public BlockIdMatcher block; + @Nonnull + public BlockFace face = BlockFace.None; + public boolean staticFace; + + public BlockMatcher() { + } + + public BlockMatcher(@Nullable BlockIdMatcher block, @Nonnull BlockFace face, boolean staticFace) { + this.block = block; + this.face = face; + this.staticFace = staticFace; + } + + public BlockMatcher(@Nonnull BlockMatcher other) { + this.block = other.block; + this.face = other.face; + this.staticFace = other.staticFace; + } + + @Nonnull + public static BlockMatcher deserialize(@Nonnull ByteBuf buf, int offset) { + BlockMatcher obj = new BlockMatcher(); + byte nullBits = buf.getByte(offset); + obj.face = BlockFace.fromValue(buf.getByte(offset + 1)); + obj.staticFace = buf.getByte(offset + 2) != 0; + int pos = offset + 3; + if ((nullBits & 1) != 0) { + obj.block = BlockIdMatcher.deserialize(buf, pos); + pos += BlockIdMatcher.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 3; + if ((nullBits & 1) != 0) { + pos += BlockIdMatcher.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.block != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.face.getValue()); + buf.writeByte(this.staticFace ? 1 : 0); + if (this.block != null) { + this.block.serialize(buf); + } + } + + public int computeSize() { + int size = 3; + if (this.block != null) { + size += this.block.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 3) { + return ValidationResult.error("Buffer too small: expected at least 3 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 3; + if ((nullBits & 1) != 0) { + ValidationResult blockResult = BlockIdMatcher.validateStructure(buffer, pos); + if (!blockResult.isValid()) { + return ValidationResult.error("Invalid Block: " + blockResult.error()); + } + + pos += BlockIdMatcher.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public BlockMatcher clone() { + BlockMatcher copy = new BlockMatcher(); + copy.block = this.block != null ? this.block.clone() : null; + copy.face = this.face; + copy.staticFace = this.staticFace; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockMatcher other) + ? false + : Objects.equals(this.block, other.block) && Objects.equals(this.face, other.face) && this.staticFace == other.staticFace; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.block, this.face, this.staticFace); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockMaterial.java b/src/com/hypixel/hytale/protocol/BlockMaterial.java new file mode 100644 index 0000000..242e2ad --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockMaterial.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum BlockMaterial { + Empty(0), + Solid(1); + + public static final BlockMaterial[] VALUES = values(); + private final int value; + + private BlockMaterial(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static BlockMaterial fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("BlockMaterial", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockMount.java b/src/com/hypixel/hytale/protocol/BlockMount.java new file mode 100644 index 0000000..5467b10 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockMount.java @@ -0,0 +1,123 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockMount { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 30; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 30; + public static final int MAX_SIZE = 30; + @Nonnull + public BlockMountType type = BlockMountType.Seat; + @Nullable + public Vector3f position; + @Nullable + public Vector3f orientation; + public int blockTypeId; + + public BlockMount() { + } + + public BlockMount(@Nonnull BlockMountType type, @Nullable Vector3f position, @Nullable Vector3f orientation, int blockTypeId) { + this.type = type; + this.position = position; + this.orientation = orientation; + this.blockTypeId = blockTypeId; + } + + public BlockMount(@Nonnull BlockMount other) { + this.type = other.type; + this.position = other.position; + this.orientation = other.orientation; + this.blockTypeId = other.blockTypeId; + } + + @Nonnull + public static BlockMount deserialize(@Nonnull ByteBuf buf, int offset) { + BlockMount obj = new BlockMount(); + byte nullBits = buf.getByte(offset); + obj.type = BlockMountType.fromValue(buf.getByte(offset + 1)); + if ((nullBits & 1) != 0) { + obj.position = Vector3f.deserialize(buf, offset + 2); + } + + if ((nullBits & 2) != 0) { + obj.orientation = Vector3f.deserialize(buf, offset + 14); + } + + obj.blockTypeId = buf.getIntLE(offset + 26); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 30; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.position != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.orientation != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.position != null) { + this.position.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.orientation != null) { + this.orientation.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeIntLE(this.blockTypeId); + } + + public int computeSize() { + return 30; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 30 ? ValidationResult.error("Buffer too small: expected at least 30 bytes") : ValidationResult.OK; + } + + public BlockMount clone() { + BlockMount copy = new BlockMount(); + copy.type = this.type; + copy.position = this.position != null ? this.position.clone() : null; + copy.orientation = this.orientation != null ? this.orientation.clone() : null; + copy.blockTypeId = this.blockTypeId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockMount other) + ? false + : Objects.equals(this.type, other.type) + && Objects.equals(this.position, other.position) + && Objects.equals(this.orientation, other.orientation) + && this.blockTypeId == other.blockTypeId; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.position, this.orientation, this.blockTypeId); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockMountType.java b/src/com/hypixel/hytale/protocol/BlockMountType.java new file mode 100644 index 0000000..8c50e30 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockMountType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum BlockMountType { + Seat(0), + Bed(1); + + public static final BlockMountType[] VALUES = values(); + private final int value; + + private BlockMountType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static BlockMountType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("BlockMountType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockMovementSettings.java b/src/com/hypixel/hytale/protocol/BlockMovementSettings.java new file mode 100644 index 0000000..68cae8b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockMovementSettings.java @@ -0,0 +1,174 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BlockMovementSettings { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 42; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 42; + public static final int MAX_SIZE = 42; + public boolean isClimbable; + public float climbUpSpeedMultiplier; + public float climbDownSpeedMultiplier; + public float climbLateralSpeedMultiplier; + public boolean isBouncy; + public float bounceVelocity; + public float drag; + public float friction; + public float terminalVelocityModifier; + public float horizontalSpeedMultiplier; + public float acceleration; + public float jumpForceMultiplier; + + public BlockMovementSettings() { + } + + public BlockMovementSettings( + boolean isClimbable, + float climbUpSpeedMultiplier, + float climbDownSpeedMultiplier, + float climbLateralSpeedMultiplier, + boolean isBouncy, + float bounceVelocity, + float drag, + float friction, + float terminalVelocityModifier, + float horizontalSpeedMultiplier, + float acceleration, + float jumpForceMultiplier + ) { + this.isClimbable = isClimbable; + this.climbUpSpeedMultiplier = climbUpSpeedMultiplier; + this.climbDownSpeedMultiplier = climbDownSpeedMultiplier; + this.climbLateralSpeedMultiplier = climbLateralSpeedMultiplier; + this.isBouncy = isBouncy; + this.bounceVelocity = bounceVelocity; + this.drag = drag; + this.friction = friction; + this.terminalVelocityModifier = terminalVelocityModifier; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.acceleration = acceleration; + this.jumpForceMultiplier = jumpForceMultiplier; + } + + public BlockMovementSettings(@Nonnull BlockMovementSettings other) { + this.isClimbable = other.isClimbable; + this.climbUpSpeedMultiplier = other.climbUpSpeedMultiplier; + this.climbDownSpeedMultiplier = other.climbDownSpeedMultiplier; + this.climbLateralSpeedMultiplier = other.climbLateralSpeedMultiplier; + this.isBouncy = other.isBouncy; + this.bounceVelocity = other.bounceVelocity; + this.drag = other.drag; + this.friction = other.friction; + this.terminalVelocityModifier = other.terminalVelocityModifier; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.acceleration = other.acceleration; + this.jumpForceMultiplier = other.jumpForceMultiplier; + } + + @Nonnull + public static BlockMovementSettings deserialize(@Nonnull ByteBuf buf, int offset) { + BlockMovementSettings obj = new BlockMovementSettings(); + obj.isClimbable = buf.getByte(offset + 0) != 0; + obj.climbUpSpeedMultiplier = buf.getFloatLE(offset + 1); + obj.climbDownSpeedMultiplier = buf.getFloatLE(offset + 5); + obj.climbLateralSpeedMultiplier = buf.getFloatLE(offset + 9); + obj.isBouncy = buf.getByte(offset + 13) != 0; + obj.bounceVelocity = buf.getFloatLE(offset + 14); + obj.drag = buf.getFloatLE(offset + 18); + obj.friction = buf.getFloatLE(offset + 22); + obj.terminalVelocityModifier = buf.getFloatLE(offset + 26); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 30); + obj.acceleration = buf.getFloatLE(offset + 34); + obj.jumpForceMultiplier = buf.getFloatLE(offset + 38); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 42; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.isClimbable ? 1 : 0); + buf.writeFloatLE(this.climbUpSpeedMultiplier); + buf.writeFloatLE(this.climbDownSpeedMultiplier); + buf.writeFloatLE(this.climbLateralSpeedMultiplier); + buf.writeByte(this.isBouncy ? 1 : 0); + buf.writeFloatLE(this.bounceVelocity); + buf.writeFloatLE(this.drag); + buf.writeFloatLE(this.friction); + buf.writeFloatLE(this.terminalVelocityModifier); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.acceleration); + buf.writeFloatLE(this.jumpForceMultiplier); + } + + public int computeSize() { + return 42; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 42 ? ValidationResult.error("Buffer too small: expected at least 42 bytes") : ValidationResult.OK; + } + + public BlockMovementSettings clone() { + BlockMovementSettings copy = new BlockMovementSettings(); + copy.isClimbable = this.isClimbable; + copy.climbUpSpeedMultiplier = this.climbUpSpeedMultiplier; + copy.climbDownSpeedMultiplier = this.climbDownSpeedMultiplier; + copy.climbLateralSpeedMultiplier = this.climbLateralSpeedMultiplier; + copy.isBouncy = this.isBouncy; + copy.bounceVelocity = this.bounceVelocity; + copy.drag = this.drag; + copy.friction = this.friction; + copy.terminalVelocityModifier = this.terminalVelocityModifier; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.acceleration = this.acceleration; + copy.jumpForceMultiplier = this.jumpForceMultiplier; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockMovementSettings other) + ? false + : this.isClimbable == other.isClimbable + && this.climbUpSpeedMultiplier == other.climbUpSpeedMultiplier + && this.climbDownSpeedMultiplier == other.climbDownSpeedMultiplier + && this.climbLateralSpeedMultiplier == other.climbLateralSpeedMultiplier + && this.isBouncy == other.isBouncy + && this.bounceVelocity == other.bounceVelocity + && this.drag == other.drag + && this.friction == other.friction + && this.terminalVelocityModifier == other.terminalVelocityModifier + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.acceleration == other.acceleration + && this.jumpForceMultiplier == other.jumpForceMultiplier; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.isClimbable, + this.climbUpSpeedMultiplier, + this.climbDownSpeedMultiplier, + this.climbLateralSpeedMultiplier, + this.isBouncy, + this.bounceVelocity, + this.drag, + this.friction, + this.terminalVelocityModifier, + this.horizontalSpeedMultiplier, + this.acceleration, + this.jumpForceMultiplier + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockNeighbor.java b/src/com/hypixel/hytale/protocol/BlockNeighbor.java new file mode 100644 index 0000000..8538f46 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockNeighbor.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum BlockNeighbor { + Up(0), + Down(1), + North(2), + East(3), + South(4), + West(5), + UpNorth(6), + UpSouth(7), + UpEast(8), + UpWest(9), + DownNorth(10), + DownSouth(11), + DownEast(12), + DownWest(13), + NorthEast(14), + SouthEast(15), + SouthWest(16), + NorthWest(17), + UpNorthEast(18), + UpSouthEast(19), + UpSouthWest(20), + UpNorthWest(21), + DownNorthEast(22), + DownSouthEast(23), + DownSouthWest(24), + DownNorthWest(25); + + public static final BlockNeighbor[] VALUES = values(); + private final int value; + + private BlockNeighbor(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static BlockNeighbor fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("BlockNeighbor", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockParticleEvent.java b/src/com/hypixel/hytale/protocol/BlockParticleEvent.java new file mode 100644 index 0000000..96f7125 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockParticleEvent.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum BlockParticleEvent { + Walk(0), + Run(1), + Sprint(2), + SoftLand(3), + HardLand(4), + MoveOut(5), + Hit(6), + Break(7), + Build(8), + Physics(9); + + public static final BlockParticleEvent[] VALUES = values(); + private final int value; + + private BlockParticleEvent(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static BlockParticleEvent fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("BlockParticleEvent", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockParticleSet.java b/src/com/hypixel/hytale/protocol/BlockParticleSet.java new file mode 100644 index 0000000..4ef7407 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockParticleSet.java @@ -0,0 +1,360 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockParticleSet { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 32; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 40; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + @Nullable + public Color color; + public float scale; + @Nullable + public Vector3f positionOffset; + @Nullable + public Direction rotationOffset; + @Nullable + public Map particleSystemIds; + + public BlockParticleSet() { + } + + public BlockParticleSet( + @Nullable String id, + @Nullable Color color, + float scale, + @Nullable Vector3f positionOffset, + @Nullable Direction rotationOffset, + @Nullable Map particleSystemIds + ) { + this.id = id; + this.color = color; + this.scale = scale; + this.positionOffset = positionOffset; + this.rotationOffset = rotationOffset; + this.particleSystemIds = particleSystemIds; + } + + public BlockParticleSet(@Nonnull BlockParticleSet other) { + this.id = other.id; + this.color = other.color; + this.scale = other.scale; + this.positionOffset = other.positionOffset; + this.rotationOffset = other.rotationOffset; + this.particleSystemIds = other.particleSystemIds; + } + + @Nonnull + public static BlockParticleSet deserialize(@Nonnull ByteBuf buf, int offset) { + BlockParticleSet obj = new BlockParticleSet(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 2) != 0) { + obj.color = Color.deserialize(buf, offset + 1); + } + + obj.scale = buf.getFloatLE(offset + 4); + if ((nullBits & 4) != 0) { + obj.positionOffset = Vector3f.deserialize(buf, offset + 8); + } + + if ((nullBits & 8) != 0) { + obj.rotationOffset = Direction.deserialize(buf, offset + 20); + } + + if ((nullBits & 1) != 0) { + int varPos0 = offset + 40 + buf.getIntLE(offset + 32); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 16) != 0) { + int varPos1 = offset + 40 + buf.getIntLE(offset + 36); + int particleSystemIdsCount = VarInt.peek(buf, varPos1); + if (particleSystemIdsCount < 0) { + throw ProtocolException.negativeLength("ParticleSystemIds", particleSystemIdsCount); + } + + if (particleSystemIdsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("ParticleSystemIds", particleSystemIdsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.particleSystemIds = new HashMap<>(particleSystemIdsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < particleSystemIdsCount; i++) { + BlockParticleEvent key = BlockParticleEvent.fromValue(buf.getByte(dictPos)); + int valLen = VarInt.peek(buf, ++dictPos); + if (valLen < 0) { + throw ProtocolException.negativeLength("val", valLen); + } + + if (valLen > 4096000) { + throw ProtocolException.stringTooLong("val", valLen, 4096000); + } + + int valVarLen = VarInt.length(buf, dictPos); + String val = PacketIO.readVarString(buf, dictPos); + dictPos += valVarLen + valLen; + if (obj.particleSystemIds.put(key, val) != null) { + throw ProtocolException.duplicateKey("particleSystemIds", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 40; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 32); + int pos0 = offset + 40 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 36); + int pos1 = offset + 40 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, ++pos1); + pos1 += VarInt.length(buf, pos1) + sl; + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.color != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.positionOffset != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.rotationOffset != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.particleSystemIds != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + if (this.color != null) { + this.color.serialize(buf); + } else { + buf.writeZero(3); + } + + buf.writeFloatLE(this.scale); + if (this.positionOffset != null) { + this.positionOffset.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.rotationOffset != null) { + this.rotationOffset.serialize(buf); + } else { + buf.writeZero(12); + } + + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int particleSystemIdsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.particleSystemIds != null) { + buf.setIntLE(particleSystemIdsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.particleSystemIds.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("ParticleSystemIds", this.particleSystemIds.size(), 4096000); + } + + VarInt.write(buf, this.particleSystemIds.size()); + + for (Entry e : this.particleSystemIds.entrySet()) { + buf.writeByte(e.getKey().getValue()); + PacketIO.writeVarString(buf, e.getValue(), 4096000); + } + } else { + buf.setIntLE(particleSystemIdsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 40; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.particleSystemIds != null) { + int particleSystemIdsSize = 0; + + for (Entry kvp : this.particleSystemIds.entrySet()) { + particleSystemIdsSize += 1 + PacketIO.stringSize(kvp.getValue()); + } + + size += VarInt.size(this.particleSystemIds.size()) + particleSystemIdsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 40) { + return ValidationResult.error("Buffer too small: expected at least 40 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 32); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 40 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 16) != 0) { + int particleSystemIdsOffset = buffer.getIntLE(offset + 36); + if (particleSystemIdsOffset < 0) { + return ValidationResult.error("Invalid offset for ParticleSystemIds"); + } + + int posx = offset + 40 + particleSystemIdsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ParticleSystemIds"); + } + + int particleSystemIdsCount = VarInt.peek(buffer, posx); + if (particleSystemIdsCount < 0) { + return ValidationResult.error("Invalid dictionary count for ParticleSystemIds"); + } + + if (particleSystemIdsCount > 4096000) { + return ValidationResult.error("ParticleSystemIds exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < particleSystemIdsCount; i++) { + int valueLen = VarInt.peek(buffer, ++posx); + if (valueLen < 0) { + return ValidationResult.error("Invalid string length for value"); + } + + if (valueLen > 4096000) { + return ValidationResult.error("value exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += valueLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public BlockParticleSet clone() { + BlockParticleSet copy = new BlockParticleSet(); + copy.id = this.id; + copy.color = this.color != null ? this.color.clone() : null; + copy.scale = this.scale; + copy.positionOffset = this.positionOffset != null ? this.positionOffset.clone() : null; + copy.rotationOffset = this.rotationOffset != null ? this.rotationOffset.clone() : null; + copy.particleSystemIds = this.particleSystemIds != null ? new HashMap<>(this.particleSystemIds) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockParticleSet other) + ? false + : Objects.equals(this.id, other.id) + && Objects.equals(this.color, other.color) + && this.scale == other.scale + && Objects.equals(this.positionOffset, other.positionOffset) + && Objects.equals(this.rotationOffset, other.rotationOffset) + && Objects.equals(this.particleSystemIds, other.particleSystemIds); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.color, this.scale, this.positionOffset, this.rotationOffset, this.particleSystemIds); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockPlacementRotationMode.java b/src/com/hypixel/hytale/protocol/BlockPlacementRotationMode.java new file mode 100644 index 0000000..369e570 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockPlacementRotationMode.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum BlockPlacementRotationMode { + FacingPlayer(0), + StairFacingPlayer(1), + BlockNormal(2), + Default(3); + + public static final BlockPlacementRotationMode[] VALUES = values(); + private final int value; + + private BlockPlacementRotationMode(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static BlockPlacementRotationMode fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("BlockPlacementRotationMode", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockPlacementSettings.java b/src/com/hypixel/hytale/protocol/BlockPlacementSettings.java new file mode 100644 index 0000000..9dd0e82 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockPlacementSettings.java @@ -0,0 +1,131 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BlockPlacementSettings { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 16; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 16; + public static final int MAX_SIZE = 16; + public boolean allowRotationKey; + public boolean placeInEmptyBlocks; + @Nonnull + public BlockPreviewVisibility previewVisibility = BlockPreviewVisibility.AlwaysVisible; + @Nonnull + public BlockPlacementRotationMode rotationMode = BlockPlacementRotationMode.FacingPlayer; + public int wallPlacementOverrideBlockId; + public int floorPlacementOverrideBlockId; + public int ceilingPlacementOverrideBlockId; + + public BlockPlacementSettings() { + } + + public BlockPlacementSettings( + boolean allowRotationKey, + boolean placeInEmptyBlocks, + @Nonnull BlockPreviewVisibility previewVisibility, + @Nonnull BlockPlacementRotationMode rotationMode, + int wallPlacementOverrideBlockId, + int floorPlacementOverrideBlockId, + int ceilingPlacementOverrideBlockId + ) { + this.allowRotationKey = allowRotationKey; + this.placeInEmptyBlocks = placeInEmptyBlocks; + this.previewVisibility = previewVisibility; + this.rotationMode = rotationMode; + this.wallPlacementOverrideBlockId = wallPlacementOverrideBlockId; + this.floorPlacementOverrideBlockId = floorPlacementOverrideBlockId; + this.ceilingPlacementOverrideBlockId = ceilingPlacementOverrideBlockId; + } + + public BlockPlacementSettings(@Nonnull BlockPlacementSettings other) { + this.allowRotationKey = other.allowRotationKey; + this.placeInEmptyBlocks = other.placeInEmptyBlocks; + this.previewVisibility = other.previewVisibility; + this.rotationMode = other.rotationMode; + this.wallPlacementOverrideBlockId = other.wallPlacementOverrideBlockId; + this.floorPlacementOverrideBlockId = other.floorPlacementOverrideBlockId; + this.ceilingPlacementOverrideBlockId = other.ceilingPlacementOverrideBlockId; + } + + @Nonnull + public static BlockPlacementSettings deserialize(@Nonnull ByteBuf buf, int offset) { + BlockPlacementSettings obj = new BlockPlacementSettings(); + obj.allowRotationKey = buf.getByte(offset + 0) != 0; + obj.placeInEmptyBlocks = buf.getByte(offset + 1) != 0; + obj.previewVisibility = BlockPreviewVisibility.fromValue(buf.getByte(offset + 2)); + obj.rotationMode = BlockPlacementRotationMode.fromValue(buf.getByte(offset + 3)); + obj.wallPlacementOverrideBlockId = buf.getIntLE(offset + 4); + obj.floorPlacementOverrideBlockId = buf.getIntLE(offset + 8); + obj.ceilingPlacementOverrideBlockId = buf.getIntLE(offset + 12); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 16; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.allowRotationKey ? 1 : 0); + buf.writeByte(this.placeInEmptyBlocks ? 1 : 0); + buf.writeByte(this.previewVisibility.getValue()); + buf.writeByte(this.rotationMode.getValue()); + buf.writeIntLE(this.wallPlacementOverrideBlockId); + buf.writeIntLE(this.floorPlacementOverrideBlockId); + buf.writeIntLE(this.ceilingPlacementOverrideBlockId); + } + + public int computeSize() { + return 16; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 16 ? ValidationResult.error("Buffer too small: expected at least 16 bytes") : ValidationResult.OK; + } + + public BlockPlacementSettings clone() { + BlockPlacementSettings copy = new BlockPlacementSettings(); + copy.allowRotationKey = this.allowRotationKey; + copy.placeInEmptyBlocks = this.placeInEmptyBlocks; + copy.previewVisibility = this.previewVisibility; + copy.rotationMode = this.rotationMode; + copy.wallPlacementOverrideBlockId = this.wallPlacementOverrideBlockId; + copy.floorPlacementOverrideBlockId = this.floorPlacementOverrideBlockId; + copy.ceilingPlacementOverrideBlockId = this.ceilingPlacementOverrideBlockId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockPlacementSettings other) + ? false + : this.allowRotationKey == other.allowRotationKey + && this.placeInEmptyBlocks == other.placeInEmptyBlocks + && Objects.equals(this.previewVisibility, other.previewVisibility) + && Objects.equals(this.rotationMode, other.rotationMode) + && this.wallPlacementOverrideBlockId == other.wallPlacementOverrideBlockId + && this.floorPlacementOverrideBlockId == other.floorPlacementOverrideBlockId + && this.ceilingPlacementOverrideBlockId == other.ceilingPlacementOverrideBlockId; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.allowRotationKey, + this.placeInEmptyBlocks, + this.previewVisibility, + this.rotationMode, + this.wallPlacementOverrideBlockId, + this.floorPlacementOverrideBlockId, + this.ceilingPlacementOverrideBlockId + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockPosition.java b/src/com/hypixel/hytale/protocol/BlockPosition.java new file mode 100644 index 0000000..bffa0bc --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockPosition.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BlockPosition { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 12; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 12; + public static final int MAX_SIZE = 12; + public int x; + public int y; + public int z; + + public BlockPosition() { + } + + public BlockPosition(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public BlockPosition(@Nonnull BlockPosition other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + } + + @Nonnull + public static BlockPosition deserialize(@Nonnull ByteBuf buf, int offset) { + BlockPosition obj = new BlockPosition(); + obj.x = buf.getIntLE(offset + 0); + obj.y = buf.getIntLE(offset + 4); + obj.z = buf.getIntLE(offset + 8); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 12; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.x); + buf.writeIntLE(this.y); + buf.writeIntLE(this.z); + } + + public int computeSize() { + return 12; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 12 ? ValidationResult.error("Buffer too small: expected at least 12 bytes") : ValidationResult.OK; + } + + public BlockPosition clone() { + BlockPosition copy = new BlockPosition(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockPosition other) ? false : this.x == other.x && this.y == other.y && this.z == other.z; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y, this.z); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockPreviewVisibility.java b/src/com/hypixel/hytale/protocol/BlockPreviewVisibility.java new file mode 100644 index 0000000..7c1b844 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockPreviewVisibility.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum BlockPreviewVisibility { + AlwaysVisible(0), + AlwaysHidden(1), + Default(2); + + public static final BlockPreviewVisibility[] VALUES = values(); + private final int value; + + private BlockPreviewVisibility(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static BlockPreviewVisibility fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("BlockPreviewVisibility", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockRotation.java b/src/com/hypixel/hytale/protocol/BlockRotation.java new file mode 100644 index 0000000..71cbabc --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockRotation.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BlockRotation { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 3; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 3; + public static final int MAX_SIZE = 3; + @Nonnull + public Rotation rotationYaw = Rotation.None; + @Nonnull + public Rotation rotationPitch = Rotation.None; + @Nonnull + public Rotation rotationRoll = Rotation.None; + + public BlockRotation() { + } + + public BlockRotation(@Nonnull Rotation rotationYaw, @Nonnull Rotation rotationPitch, @Nonnull Rotation rotationRoll) { + this.rotationYaw = rotationYaw; + this.rotationPitch = rotationPitch; + this.rotationRoll = rotationRoll; + } + + public BlockRotation(@Nonnull BlockRotation other) { + this.rotationYaw = other.rotationYaw; + this.rotationPitch = other.rotationPitch; + this.rotationRoll = other.rotationRoll; + } + + @Nonnull + public static BlockRotation deserialize(@Nonnull ByteBuf buf, int offset) { + BlockRotation obj = new BlockRotation(); + obj.rotationYaw = Rotation.fromValue(buf.getByte(offset + 0)); + obj.rotationPitch = Rotation.fromValue(buf.getByte(offset + 1)); + obj.rotationRoll = Rotation.fromValue(buf.getByte(offset + 2)); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 3; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.rotationYaw.getValue()); + buf.writeByte(this.rotationPitch.getValue()); + buf.writeByte(this.rotationRoll.getValue()); + } + + public int computeSize() { + return 3; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 3 ? ValidationResult.error("Buffer too small: expected at least 3 bytes") : ValidationResult.OK; + } + + public BlockRotation clone() { + BlockRotation copy = new BlockRotation(); + copy.rotationYaw = this.rotationYaw; + copy.rotationPitch = this.rotationPitch; + copy.rotationRoll = this.rotationRoll; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockRotation other) + ? false + : Objects.equals(this.rotationYaw, other.rotationYaw) + && Objects.equals(this.rotationPitch, other.rotationPitch) + && Objects.equals(this.rotationRoll, other.rotationRoll); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.rotationYaw, this.rotationPitch, this.rotationRoll); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockSelectorToolData.java b/src/com/hypixel/hytale/protocol/BlockSelectorToolData.java new file mode 100644 index 0000000..bae12a9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockSelectorToolData.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BlockSelectorToolData { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 4; + public float durabilityLossOnUse; + + public BlockSelectorToolData() { + } + + public BlockSelectorToolData(float durabilityLossOnUse) { + this.durabilityLossOnUse = durabilityLossOnUse; + } + + public BlockSelectorToolData(@Nonnull BlockSelectorToolData other) { + this.durabilityLossOnUse = other.durabilityLossOnUse; + } + + @Nonnull + public static BlockSelectorToolData deserialize(@Nonnull ByteBuf buf, int offset) { + BlockSelectorToolData obj = new BlockSelectorToolData(); + obj.durabilityLossOnUse = buf.getFloatLE(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 4; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.durabilityLossOnUse); + } + + public int computeSize() { + return 4; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 4 ? ValidationResult.error("Buffer too small: expected at least 4 bytes") : ValidationResult.OK; + } + + public BlockSelectorToolData clone() { + BlockSelectorToolData copy = new BlockSelectorToolData(); + copy.durabilityLossOnUse = this.durabilityLossOnUse; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof BlockSelectorToolData other ? this.durabilityLossOnUse == other.durabilityLossOnUse : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.durabilityLossOnUse); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockSet.java b/src/com/hypixel/hytale/protocol/BlockSet.java new file mode 100644 index 0000000..c5ca7aa --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockSet.java @@ -0,0 +1,245 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockSet { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 32768019; + @Nullable + public String name; + @Nullable + public int[] blocks; + + public BlockSet() { + } + + public BlockSet(@Nullable String name, @Nullable int[] blocks) { + this.name = name; + this.blocks = blocks; + } + + public BlockSet(@Nonnull BlockSet other) { + this.name = other.name; + this.blocks = other.blocks; + } + + @Nonnull + public static BlockSet deserialize(@Nonnull ByteBuf buf, int offset) { + BlockSet obj = new BlockSet(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int nameLen = VarInt.peek(buf, varPos0); + if (nameLen < 0) { + throw ProtocolException.negativeLength("Name", nameLen); + } + + if (nameLen > 4096000) { + throw ProtocolException.stringTooLong("Name", nameLen, 4096000); + } + + obj.name = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int blocksCount = VarInt.peek(buf, varPos1); + if (blocksCount < 0) { + throw ProtocolException.negativeLength("Blocks", blocksCount); + } + + if (blocksCount > 4096000) { + throw ProtocolException.arrayTooLong("Blocks", blocksCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + blocksCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Blocks", varPos1 + varIntLen + blocksCount * 4, buf.readableBytes()); + } + + obj.blocks = new int[blocksCount]; + + for (int i = 0; i < blocksCount; i++) { + obj.blocks[i] = buf.getIntLE(varPos1 + varIntLen + i * 4); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + arrLen * 4; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.name != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.blocks != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int nameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int blocksOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.name != null) { + buf.setIntLE(nameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.name, 4096000); + } else { + buf.setIntLE(nameOffsetSlot, -1); + } + + if (this.blocks != null) { + buf.setIntLE(blocksOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.blocks.length > 4096000) { + throw ProtocolException.arrayTooLong("Blocks", this.blocks.length, 4096000); + } + + VarInt.write(buf, this.blocks.length); + + for (int item : this.blocks) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(blocksOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 9; + if (this.name != null) { + size += PacketIO.stringSize(this.name); + } + + if (this.blocks != null) { + size += VarInt.size(this.blocks.length) + this.blocks.length * 4; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int nameOffset = buffer.getIntLE(offset + 1); + if (nameOffset < 0) { + return ValidationResult.error("Invalid offset for Name"); + } + + int pos = offset + 9 + nameOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Name"); + } + + int nameLen = VarInt.peek(buffer, pos); + if (nameLen < 0) { + return ValidationResult.error("Invalid string length for Name"); + } + + if (nameLen > 4096000) { + return ValidationResult.error("Name exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += nameLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Name"); + } + } + + if ((nullBits & 2) != 0) { + int blocksOffset = buffer.getIntLE(offset + 5); + if (blocksOffset < 0) { + return ValidationResult.error("Invalid offset for Blocks"); + } + + int posx = offset + 9 + blocksOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Blocks"); + } + + int blocksCount = VarInt.peek(buffer, posx); + if (blocksCount < 0) { + return ValidationResult.error("Invalid array count for Blocks"); + } + + if (blocksCount > 4096000) { + return ValidationResult.error("Blocks exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += blocksCount * 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Blocks"); + } + } + + return ValidationResult.OK; + } + } + + public BlockSet clone() { + BlockSet copy = new BlockSet(); + copy.name = this.name; + copy.blocks = this.blocks != null ? Arrays.copyOf(this.blocks, this.blocks.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockSet other) ? false : Objects.equals(this.name, other.name) && Arrays.equals(this.blocks, other.blocks); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.name); + return 31 * result + Arrays.hashCode(this.blocks); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockSoundEvent.java b/src/com/hypixel/hytale/protocol/BlockSoundEvent.java new file mode 100644 index 0000000..e7e70aa --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockSoundEvent.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum BlockSoundEvent { + Walk(0), + Land(1), + MoveIn(2), + MoveOut(3), + Hit(4), + Break(5), + Build(6), + Clone(7), + Harvest(8); + + public static final BlockSoundEvent[] VALUES = values(); + private final int value; + + private BlockSoundEvent(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static BlockSoundEvent fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("BlockSoundEvent", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockSoundSet.java b/src/com/hypixel/hytale/protocol/BlockSoundSet.java new file mode 100644 index 0000000..a699f34 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockSoundSet.java @@ -0,0 +1,279 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockSoundSet { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 17; + public static final int MAX_SIZE = 36864027; + @Nullable + public String id; + @Nullable + public Map soundEventIndices; + @Nullable + public FloatRange moveInRepeatRange; + + public BlockSoundSet() { + } + + public BlockSoundSet(@Nullable String id, @Nullable Map soundEventIndices, @Nullable FloatRange moveInRepeatRange) { + this.id = id; + this.soundEventIndices = soundEventIndices; + this.moveInRepeatRange = moveInRepeatRange; + } + + public BlockSoundSet(@Nonnull BlockSoundSet other) { + this.id = other.id; + this.soundEventIndices = other.soundEventIndices; + this.moveInRepeatRange = other.moveInRepeatRange; + } + + @Nonnull + public static BlockSoundSet deserialize(@Nonnull ByteBuf buf, int offset) { + BlockSoundSet obj = new BlockSoundSet(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 4) != 0) { + obj.moveInRepeatRange = FloatRange.deserialize(buf, offset + 1); + } + + if ((nullBits & 1) != 0) { + int varPos0 = offset + 17 + buf.getIntLE(offset + 9); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 17 + buf.getIntLE(offset + 13); + int soundEventIndicesCount = VarInt.peek(buf, varPos1); + if (soundEventIndicesCount < 0) { + throw ProtocolException.negativeLength("SoundEventIndices", soundEventIndicesCount); + } + + if (soundEventIndicesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("SoundEventIndices", soundEventIndicesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.soundEventIndices = new HashMap<>(soundEventIndicesCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < soundEventIndicesCount; i++) { + BlockSoundEvent key = BlockSoundEvent.fromValue(buf.getByte(dictPos)); + int val = buf.getIntLE(++dictPos); + dictPos += 4; + if (obj.soundEventIndices.put(key, val) != null) { + throw ProtocolException.duplicateKey("soundEventIndices", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 17; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 9); + int pos0 = offset + 17 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 13); + int pos1 = offset + 17 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + 4; + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.soundEventIndices != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.moveInRepeatRange != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + if (this.moveInRepeatRange != null) { + this.moveInRepeatRange.serialize(buf); + } else { + buf.writeZero(8); + } + + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int soundEventIndicesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.soundEventIndices != null) { + buf.setIntLE(soundEventIndicesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.soundEventIndices.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("SoundEventIndices", this.soundEventIndices.size(), 4096000); + } + + VarInt.write(buf, this.soundEventIndices.size()); + + for (Entry e : this.soundEventIndices.entrySet()) { + buf.writeByte(e.getKey().getValue()); + buf.writeIntLE(e.getValue()); + } + } else { + buf.setIntLE(soundEventIndicesOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 17; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.soundEventIndices != null) { + size += VarInt.size(this.soundEventIndices.size()) + this.soundEventIndices.size() * 5; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 17) { + return ValidationResult.error("Buffer too small: expected at least 17 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 9); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 17 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int soundEventIndicesOffset = buffer.getIntLE(offset + 13); + if (soundEventIndicesOffset < 0) { + return ValidationResult.error("Invalid offset for SoundEventIndices"); + } + + int posx = offset + 17 + soundEventIndicesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SoundEventIndices"); + } + + int soundEventIndicesCount = VarInt.peek(buffer, posx); + if (soundEventIndicesCount < 0) { + return ValidationResult.error("Invalid dictionary count for SoundEventIndices"); + } + + if (soundEventIndicesCount > 4096000) { + return ValidationResult.error("SoundEventIndices exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < soundEventIndicesCount; i++) { + posx = ++posx + 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public BlockSoundSet clone() { + BlockSoundSet copy = new BlockSoundSet(); + copy.id = this.id; + copy.soundEventIndices = this.soundEventIndices != null ? new HashMap<>(this.soundEventIndices) : null; + copy.moveInRepeatRange = this.moveInRepeatRange != null ? this.moveInRepeatRange.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockSoundSet other) + ? false + : Objects.equals(this.id, other.id) + && Objects.equals(this.soundEventIndices, other.soundEventIndices) + && Objects.equals(this.moveInRepeatRange, other.moveInRepeatRange); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.soundEventIndices, this.moveInRepeatRange); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockSupportsRequiredForType.java b/src/com/hypixel/hytale/protocol/BlockSupportsRequiredForType.java new file mode 100644 index 0000000..bc5f2e9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockSupportsRequiredForType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum BlockSupportsRequiredForType { + Any(0), + All(1); + + public static final BlockSupportsRequiredForType[] VALUES = values(); + private final int value; + + private BlockSupportsRequiredForType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static BlockSupportsRequiredForType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("BlockSupportsRequiredForType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockTextures.java b/src/com/hypixel/hytale/protocol/BlockTextures.java new file mode 100644 index 0000000..023838d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockTextures.java @@ -0,0 +1,533 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockTextures { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 29; + public static final int MAX_SIZE = 98304059; + @Nullable + public String top; + @Nullable + public String bottom; + @Nullable + public String front; + @Nullable + public String back; + @Nullable + public String left; + @Nullable + public String right; + public float weight; + + public BlockTextures() { + } + + public BlockTextures( + @Nullable String top, @Nullable String bottom, @Nullable String front, @Nullable String back, @Nullable String left, @Nullable String right, float weight + ) { + this.top = top; + this.bottom = bottom; + this.front = front; + this.back = back; + this.left = left; + this.right = right; + this.weight = weight; + } + + public BlockTextures(@Nonnull BlockTextures other) { + this.top = other.top; + this.bottom = other.bottom; + this.front = other.front; + this.back = other.back; + this.left = other.left; + this.right = other.right; + this.weight = other.weight; + } + + @Nonnull + public static BlockTextures deserialize(@Nonnull ByteBuf buf, int offset) { + BlockTextures obj = new BlockTextures(); + byte nullBits = buf.getByte(offset); + obj.weight = buf.getFloatLE(offset + 1); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 29 + buf.getIntLE(offset + 5); + int topLen = VarInt.peek(buf, varPos0); + if (topLen < 0) { + throw ProtocolException.negativeLength("Top", topLen); + } + + if (topLen > 4096000) { + throw ProtocolException.stringTooLong("Top", topLen, 4096000); + } + + obj.top = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 29 + buf.getIntLE(offset + 9); + int bottomLen = VarInt.peek(buf, varPos1); + if (bottomLen < 0) { + throw ProtocolException.negativeLength("Bottom", bottomLen); + } + + if (bottomLen > 4096000) { + throw ProtocolException.stringTooLong("Bottom", bottomLen, 4096000); + } + + obj.bottom = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 29 + buf.getIntLE(offset + 13); + int frontLen = VarInt.peek(buf, varPos2); + if (frontLen < 0) { + throw ProtocolException.negativeLength("Front", frontLen); + } + + if (frontLen > 4096000) { + throw ProtocolException.stringTooLong("Front", frontLen, 4096000); + } + + obj.front = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 29 + buf.getIntLE(offset + 17); + int backLen = VarInt.peek(buf, varPos3); + if (backLen < 0) { + throw ProtocolException.negativeLength("Back", backLen); + } + + if (backLen > 4096000) { + throw ProtocolException.stringTooLong("Back", backLen, 4096000); + } + + obj.back = PacketIO.readVarString(buf, varPos3, PacketIO.UTF8); + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 29 + buf.getIntLE(offset + 21); + int leftLen = VarInt.peek(buf, varPos4); + if (leftLen < 0) { + throw ProtocolException.negativeLength("Left", leftLen); + } + + if (leftLen > 4096000) { + throw ProtocolException.stringTooLong("Left", leftLen, 4096000); + } + + obj.left = PacketIO.readVarString(buf, varPos4, PacketIO.UTF8); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 29 + buf.getIntLE(offset + 25); + int rightLen = VarInt.peek(buf, varPos5); + if (rightLen < 0) { + throw ProtocolException.negativeLength("Right", rightLen); + } + + if (rightLen > 4096000) { + throw ProtocolException.stringTooLong("Right", rightLen, 4096000); + } + + obj.right = PacketIO.readVarString(buf, varPos5, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 29; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 5); + int pos0 = offset + 29 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 9); + int pos1 = offset + 29 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 13); + int pos2 = offset + 29 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 17); + int pos3 = offset + 29 + fieldOffset3; + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 21); + int pos4 = offset + 29 + fieldOffset4; + int sl = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4) + sl; + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 25); + int pos5 = offset + 29 + fieldOffset5; + int sl = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + sl; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.top != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.bottom != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.front != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.back != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.left != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.right != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.weight); + int topOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int bottomOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int frontOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int backOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int leftOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rightOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.top != null) { + buf.setIntLE(topOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.top, 4096000); + } else { + buf.setIntLE(topOffsetSlot, -1); + } + + if (this.bottom != null) { + buf.setIntLE(bottomOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.bottom, 4096000); + } else { + buf.setIntLE(bottomOffsetSlot, -1); + } + + if (this.front != null) { + buf.setIntLE(frontOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.front, 4096000); + } else { + buf.setIntLE(frontOffsetSlot, -1); + } + + if (this.back != null) { + buf.setIntLE(backOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.back, 4096000); + } else { + buf.setIntLE(backOffsetSlot, -1); + } + + if (this.left != null) { + buf.setIntLE(leftOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.left, 4096000); + } else { + buf.setIntLE(leftOffsetSlot, -1); + } + + if (this.right != null) { + buf.setIntLE(rightOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.right, 4096000); + } else { + buf.setIntLE(rightOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 29; + if (this.top != null) { + size += PacketIO.stringSize(this.top); + } + + if (this.bottom != null) { + size += PacketIO.stringSize(this.bottom); + } + + if (this.front != null) { + size += PacketIO.stringSize(this.front); + } + + if (this.back != null) { + size += PacketIO.stringSize(this.back); + } + + if (this.left != null) { + size += PacketIO.stringSize(this.left); + } + + if (this.right != null) { + size += PacketIO.stringSize(this.right); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 29) { + return ValidationResult.error("Buffer too small: expected at least 29 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int topOffset = buffer.getIntLE(offset + 5); + if (topOffset < 0) { + return ValidationResult.error("Invalid offset for Top"); + } + + int pos = offset + 29 + topOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Top"); + } + + int topLen = VarInt.peek(buffer, pos); + if (topLen < 0) { + return ValidationResult.error("Invalid string length for Top"); + } + + if (topLen > 4096000) { + return ValidationResult.error("Top exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += topLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Top"); + } + } + + if ((nullBits & 2) != 0) { + int bottomOffset = buffer.getIntLE(offset + 9); + if (bottomOffset < 0) { + return ValidationResult.error("Invalid offset for Bottom"); + } + + int posx = offset + 29 + bottomOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Bottom"); + } + + int bottomLen = VarInt.peek(buffer, posx); + if (bottomLen < 0) { + return ValidationResult.error("Invalid string length for Bottom"); + } + + if (bottomLen > 4096000) { + return ValidationResult.error("Bottom exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += bottomLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Bottom"); + } + } + + if ((nullBits & 4) != 0) { + int frontOffset = buffer.getIntLE(offset + 13); + if (frontOffset < 0) { + return ValidationResult.error("Invalid offset for Front"); + } + + int posxx = offset + 29 + frontOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Front"); + } + + int frontLen = VarInt.peek(buffer, posxx); + if (frontLen < 0) { + return ValidationResult.error("Invalid string length for Front"); + } + + if (frontLen > 4096000) { + return ValidationResult.error("Front exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += frontLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Front"); + } + } + + if ((nullBits & 8) != 0) { + int backOffset = buffer.getIntLE(offset + 17); + if (backOffset < 0) { + return ValidationResult.error("Invalid offset for Back"); + } + + int posxxx = offset + 29 + backOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Back"); + } + + int backLen = VarInt.peek(buffer, posxxx); + if (backLen < 0) { + return ValidationResult.error("Invalid string length for Back"); + } + + if (backLen > 4096000) { + return ValidationResult.error("Back exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += backLen; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Back"); + } + } + + if ((nullBits & 16) != 0) { + int leftOffset = buffer.getIntLE(offset + 21); + if (leftOffset < 0) { + return ValidationResult.error("Invalid offset for Left"); + } + + int posxxxx = offset + 29 + leftOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Left"); + } + + int leftLen = VarInt.peek(buffer, posxxxx); + if (leftLen < 0) { + return ValidationResult.error("Invalid string length for Left"); + } + + if (leftLen > 4096000) { + return ValidationResult.error("Left exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + posxxxx += leftLen; + if (posxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Left"); + } + } + + if ((nullBits & 32) != 0) { + int rightOffset = buffer.getIntLE(offset + 25); + if (rightOffset < 0) { + return ValidationResult.error("Invalid offset for Right"); + } + + int posxxxxx = offset + 29 + rightOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Right"); + } + + int rightLen = VarInt.peek(buffer, posxxxxx); + if (rightLen < 0) { + return ValidationResult.error("Invalid string length for Right"); + } + + if (rightLen > 4096000) { + return ValidationResult.error("Right exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += rightLen; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Right"); + } + } + + return ValidationResult.OK; + } + } + + public BlockTextures clone() { + BlockTextures copy = new BlockTextures(); + copy.top = this.top; + copy.bottom = this.bottom; + copy.front = this.front; + copy.back = this.back; + copy.left = this.left; + copy.right = this.right; + copy.weight = this.weight; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockTextures other) + ? false + : Objects.equals(this.top, other.top) + && Objects.equals(this.bottom, other.bottom) + && Objects.equals(this.front, other.front) + && Objects.equals(this.back, other.back) + && Objects.equals(this.left, other.left) + && Objects.equals(this.right, other.right) + && this.weight == other.weight; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.top, this.bottom, this.front, this.back, this.left, this.right, this.weight); + } +} diff --git a/src/com/hypixel/hytale/protocol/BlockType.java b/src/com/hypixel/hytale/protocol/BlockType.java new file mode 100644 index 0000000..47012cd --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BlockType.java @@ -0,0 +1,2565 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockType { + public static final int NULLABLE_BIT_FIELD_SIZE = 4; + public static final int FIXED_BLOCK_SIZE = 163; + public static final int VARIABLE_FIELD_COUNT = 24; + public static final int VARIABLE_BLOCK_START = 259; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String item; + @Nullable + public String name; + public boolean unknown; + @Nonnull + public DrawType drawType = DrawType.Empty; + @Nonnull + public BlockMaterial material = BlockMaterial.Empty; + @Nonnull + public Opacity opacity = Opacity.Solid; + @Nullable + public ShaderType[] shaderEffect; + public int hitbox; + public int interactionHitbox; + @Nullable + public String model; + @Nullable + public ModelTexture[] modelTexture; + public float modelScale; + @Nullable + public String modelAnimation; + public boolean looping; + public int maxSupportDistance; + @Nonnull + public BlockSupportsRequiredForType blockSupportsRequiredFor = BlockSupportsRequiredForType.Any; + @Nullable + public Map support; + @Nullable + public Map supporting; + public boolean requiresAlphaBlending; + @Nullable + public BlockTextures[] cubeTextures; + @Nullable + public String cubeSideMaskTexture; + @Nonnull + public ShadingMode cubeShadingMode = ShadingMode.Standard; + @Nonnull + public RandomRotation randomRotation = RandomRotation.None; + @Nonnull + public VariantRotation variantRotation = VariantRotation.None; + @Nonnull + public Rotation rotationYawPlacementOffset = Rotation.None; + public int blockSoundSetIndex; + public int ambientSoundEventIndex; + @Nullable + public ModelParticle[] particles; + @Nullable + public String blockParticleSetId; + @Nullable + public String blockBreakingDecalId; + @Nullable + public Color particleColor; + @Nullable + public ColorLight light; + @Nullable + public Tint tint; + @Nullable + public Tint biomeTint; + public int group; + @Nullable + public String transitionTexture; + @Nullable + public int[] transitionToGroups; + @Nullable + public BlockMovementSettings movementSettings; + @Nullable + public BlockFlags flags; + @Nullable + public String interactionHint; + @Nullable + public BlockGathering gathering; + @Nullable + public BlockPlacementSettings placementSettings; + @Nullable + public ModelDisplay display; + @Nullable + public RailConfig rail; + public boolean ignoreSupportWhenPlaced; + @Nullable + public Map interactions; + @Nullable + public Map states; + public int transitionToTag; + @Nullable + public int[] tagIndexes; + @Nullable + public Bench bench; + @Nullable + public ConnectedBlockRuleSet connectedBlockRuleSet; + + public BlockType() { + } + + public BlockType( + @Nullable String item, + @Nullable String name, + boolean unknown, + @Nonnull DrawType drawType, + @Nonnull BlockMaterial material, + @Nonnull Opacity opacity, + @Nullable ShaderType[] shaderEffect, + int hitbox, + int interactionHitbox, + @Nullable String model, + @Nullable ModelTexture[] modelTexture, + float modelScale, + @Nullable String modelAnimation, + boolean looping, + int maxSupportDistance, + @Nonnull BlockSupportsRequiredForType blockSupportsRequiredFor, + @Nullable Map support, + @Nullable Map supporting, + boolean requiresAlphaBlending, + @Nullable BlockTextures[] cubeTextures, + @Nullable String cubeSideMaskTexture, + @Nonnull ShadingMode cubeShadingMode, + @Nonnull RandomRotation randomRotation, + @Nonnull VariantRotation variantRotation, + @Nonnull Rotation rotationYawPlacementOffset, + int blockSoundSetIndex, + int ambientSoundEventIndex, + @Nullable ModelParticle[] particles, + @Nullable String blockParticleSetId, + @Nullable String blockBreakingDecalId, + @Nullable Color particleColor, + @Nullable ColorLight light, + @Nullable Tint tint, + @Nullable Tint biomeTint, + int group, + @Nullable String transitionTexture, + @Nullable int[] transitionToGroups, + @Nullable BlockMovementSettings movementSettings, + @Nullable BlockFlags flags, + @Nullable String interactionHint, + @Nullable BlockGathering gathering, + @Nullable BlockPlacementSettings placementSettings, + @Nullable ModelDisplay display, + @Nullable RailConfig rail, + boolean ignoreSupportWhenPlaced, + @Nullable Map interactions, + @Nullable Map states, + int transitionToTag, + @Nullable int[] tagIndexes, + @Nullable Bench bench, + @Nullable ConnectedBlockRuleSet connectedBlockRuleSet + ) { + this.item = item; + this.name = name; + this.unknown = unknown; + this.drawType = drawType; + this.material = material; + this.opacity = opacity; + this.shaderEffect = shaderEffect; + this.hitbox = hitbox; + this.interactionHitbox = interactionHitbox; + this.model = model; + this.modelTexture = modelTexture; + this.modelScale = modelScale; + this.modelAnimation = modelAnimation; + this.looping = looping; + this.maxSupportDistance = maxSupportDistance; + this.blockSupportsRequiredFor = blockSupportsRequiredFor; + this.support = support; + this.supporting = supporting; + this.requiresAlphaBlending = requiresAlphaBlending; + this.cubeTextures = cubeTextures; + this.cubeSideMaskTexture = cubeSideMaskTexture; + this.cubeShadingMode = cubeShadingMode; + this.randomRotation = randomRotation; + this.variantRotation = variantRotation; + this.rotationYawPlacementOffset = rotationYawPlacementOffset; + this.blockSoundSetIndex = blockSoundSetIndex; + this.ambientSoundEventIndex = ambientSoundEventIndex; + this.particles = particles; + this.blockParticleSetId = blockParticleSetId; + this.blockBreakingDecalId = blockBreakingDecalId; + this.particleColor = particleColor; + this.light = light; + this.tint = tint; + this.biomeTint = biomeTint; + this.group = group; + this.transitionTexture = transitionTexture; + this.transitionToGroups = transitionToGroups; + this.movementSettings = movementSettings; + this.flags = flags; + this.interactionHint = interactionHint; + this.gathering = gathering; + this.placementSettings = placementSettings; + this.display = display; + this.rail = rail; + this.ignoreSupportWhenPlaced = ignoreSupportWhenPlaced; + this.interactions = interactions; + this.states = states; + this.transitionToTag = transitionToTag; + this.tagIndexes = tagIndexes; + this.bench = bench; + this.connectedBlockRuleSet = connectedBlockRuleSet; + } + + public BlockType(@Nonnull BlockType other) { + this.item = other.item; + this.name = other.name; + this.unknown = other.unknown; + this.drawType = other.drawType; + this.material = other.material; + this.opacity = other.opacity; + this.shaderEffect = other.shaderEffect; + this.hitbox = other.hitbox; + this.interactionHitbox = other.interactionHitbox; + this.model = other.model; + this.modelTexture = other.modelTexture; + this.modelScale = other.modelScale; + this.modelAnimation = other.modelAnimation; + this.looping = other.looping; + this.maxSupportDistance = other.maxSupportDistance; + this.blockSupportsRequiredFor = other.blockSupportsRequiredFor; + this.support = other.support; + this.supporting = other.supporting; + this.requiresAlphaBlending = other.requiresAlphaBlending; + this.cubeTextures = other.cubeTextures; + this.cubeSideMaskTexture = other.cubeSideMaskTexture; + this.cubeShadingMode = other.cubeShadingMode; + this.randomRotation = other.randomRotation; + this.variantRotation = other.variantRotation; + this.rotationYawPlacementOffset = other.rotationYawPlacementOffset; + this.blockSoundSetIndex = other.blockSoundSetIndex; + this.ambientSoundEventIndex = other.ambientSoundEventIndex; + this.particles = other.particles; + this.blockParticleSetId = other.blockParticleSetId; + this.blockBreakingDecalId = other.blockBreakingDecalId; + this.particleColor = other.particleColor; + this.light = other.light; + this.tint = other.tint; + this.biomeTint = other.biomeTint; + this.group = other.group; + this.transitionTexture = other.transitionTexture; + this.transitionToGroups = other.transitionToGroups; + this.movementSettings = other.movementSettings; + this.flags = other.flags; + this.interactionHint = other.interactionHint; + this.gathering = other.gathering; + this.placementSettings = other.placementSettings; + this.display = other.display; + this.rail = other.rail; + this.ignoreSupportWhenPlaced = other.ignoreSupportWhenPlaced; + this.interactions = other.interactions; + this.states = other.states; + this.transitionToTag = other.transitionToTag; + this.tagIndexes = other.tagIndexes; + this.bench = other.bench; + this.connectedBlockRuleSet = other.connectedBlockRuleSet; + } + + @Nonnull + public static BlockType deserialize(@Nonnull ByteBuf buf, int offset) { + BlockType obj = new BlockType(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 4); + obj.unknown = buf.getByte(offset + 4) != 0; + obj.drawType = DrawType.fromValue(buf.getByte(offset + 5)); + obj.material = BlockMaterial.fromValue(buf.getByte(offset + 6)); + obj.opacity = Opacity.fromValue(buf.getByte(offset + 7)); + obj.hitbox = buf.getIntLE(offset + 8); + obj.interactionHitbox = buf.getIntLE(offset + 12); + obj.modelScale = buf.getFloatLE(offset + 16); + obj.looping = buf.getByte(offset + 20) != 0; + obj.maxSupportDistance = buf.getIntLE(offset + 21); + obj.blockSupportsRequiredFor = BlockSupportsRequiredForType.fromValue(buf.getByte(offset + 25)); + obj.requiresAlphaBlending = buf.getByte(offset + 26) != 0; + obj.cubeShadingMode = ShadingMode.fromValue(buf.getByte(offset + 27)); + obj.randomRotation = RandomRotation.fromValue(buf.getByte(offset + 28)); + obj.variantRotation = VariantRotation.fromValue(buf.getByte(offset + 29)); + obj.rotationYawPlacementOffset = Rotation.fromValue(buf.getByte(offset + 30)); + obj.blockSoundSetIndex = buf.getIntLE(offset + 31); + obj.ambientSoundEventIndex = buf.getIntLE(offset + 35); + if ((nullBits[1] & 32) != 0) { + obj.particleColor = Color.deserialize(buf, offset + 39); + } + + if ((nullBits[1] & 64) != 0) { + obj.light = ColorLight.deserialize(buf, offset + 42); + } + + if ((nullBits[1] & 128) != 0) { + obj.tint = Tint.deserialize(buf, offset + 46); + } + + if ((nullBits[2] & 1) != 0) { + obj.biomeTint = Tint.deserialize(buf, offset + 70); + } + + obj.group = buf.getIntLE(offset + 94); + if ((nullBits[2] & 8) != 0) { + obj.movementSettings = BlockMovementSettings.deserialize(buf, offset + 98); + } + + if ((nullBits[2] & 16) != 0) { + obj.flags = BlockFlags.deserialize(buf, offset + 140); + } + + if ((nullBits[2] & 128) != 0) { + obj.placementSettings = BlockPlacementSettings.deserialize(buf, offset + 142); + } + + obj.ignoreSupportWhenPlaced = buf.getByte(offset + 158) != 0; + obj.transitionToTag = buf.getIntLE(offset + 159); + if ((nullBits[0] & 1) != 0) { + int varPos0 = offset + 259 + buf.getIntLE(offset + 163); + int itemLen = VarInt.peek(buf, varPos0); + if (itemLen < 0) { + throw ProtocolException.negativeLength("Item", itemLen); + } + + if (itemLen > 4096000) { + throw ProtocolException.stringTooLong("Item", itemLen, 4096000); + } + + obj.item = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits[0] & 2) != 0) { + int varPos1 = offset + 259 + buf.getIntLE(offset + 167); + int nameLen = VarInt.peek(buf, varPos1); + if (nameLen < 0) { + throw ProtocolException.negativeLength("Name", nameLen); + } + + if (nameLen > 4096000) { + throw ProtocolException.stringTooLong("Name", nameLen, 4096000); + } + + obj.name = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits[0] & 4) != 0) { + int varPos2 = offset + 259 + buf.getIntLE(offset + 171); + int shaderEffectCount = VarInt.peek(buf, varPos2); + if (shaderEffectCount < 0) { + throw ProtocolException.negativeLength("ShaderEffect", shaderEffectCount); + } + + if (shaderEffectCount > 4096000) { + throw ProtocolException.arrayTooLong("ShaderEffect", shaderEffectCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + shaderEffectCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("ShaderEffect", varPos2 + varIntLen + shaderEffectCount * 1, buf.readableBytes()); + } + + obj.shaderEffect = new ShaderType[shaderEffectCount]; + int elemPos = varPos2 + varIntLen; + + for (int i = 0; i < shaderEffectCount; i++) { + obj.shaderEffect[i] = ShaderType.fromValue(buf.getByte(elemPos)); + elemPos++; + } + } + + if ((nullBits[0] & 8) != 0) { + int varPos3 = offset + 259 + buf.getIntLE(offset + 175); + int modelLen = VarInt.peek(buf, varPos3); + if (modelLen < 0) { + throw ProtocolException.negativeLength("Model", modelLen); + } + + if (modelLen > 4096000) { + throw ProtocolException.stringTooLong("Model", modelLen, 4096000); + } + + obj.model = PacketIO.readVarString(buf, varPos3, PacketIO.UTF8); + } + + if ((nullBits[0] & 16) != 0) { + int varPos4 = offset + 259 + buf.getIntLE(offset + 179); + int modelTextureCount = VarInt.peek(buf, varPos4); + if (modelTextureCount < 0) { + throw ProtocolException.negativeLength("ModelTexture", modelTextureCount); + } + + if (modelTextureCount > 4096000) { + throw ProtocolException.arrayTooLong("ModelTexture", modelTextureCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos4); + if (varPos4 + varIntLen + modelTextureCount * 5L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("ModelTexture", varPos4 + varIntLen + modelTextureCount * 5, buf.readableBytes()); + } + + obj.modelTexture = new ModelTexture[modelTextureCount]; + int elemPos = varPos4 + varIntLen; + + for (int i = 0; i < modelTextureCount; i++) { + obj.modelTexture[i] = ModelTexture.deserialize(buf, elemPos); + elemPos += ModelTexture.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits[0] & 32) != 0) { + int varPos5 = offset + 259 + buf.getIntLE(offset + 183); + int modelAnimationLen = VarInt.peek(buf, varPos5); + if (modelAnimationLen < 0) { + throw ProtocolException.negativeLength("ModelAnimation", modelAnimationLen); + } + + if (modelAnimationLen > 4096000) { + throw ProtocolException.stringTooLong("ModelAnimation", modelAnimationLen, 4096000); + } + + obj.modelAnimation = PacketIO.readVarString(buf, varPos5, PacketIO.UTF8); + } + + if ((nullBits[0] & 64) != 0) { + int varPos6 = offset + 259 + buf.getIntLE(offset + 187); + int supportCount = VarInt.peek(buf, varPos6); + if (supportCount < 0) { + throw ProtocolException.negativeLength("Support", supportCount); + } + + if (supportCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Support", supportCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos6); + obj.support = new HashMap<>(supportCount); + int dictPos = varPos6 + varIntLen; + + for (int i = 0; i < supportCount; i++) { + BlockNeighbor key = BlockNeighbor.fromValue(buf.getByte(dictPos)); + int valLen = VarInt.peek(buf, ++dictPos); + if (valLen < 0) { + throw ProtocolException.negativeLength("val", valLen); + } + + if (valLen > 64) { + throw ProtocolException.arrayTooLong("val", valLen, 64); + } + + int valVarLen = VarInt.length(buf, dictPos); + if (dictPos + valVarLen + valLen * 17L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("val", dictPos + valVarLen + valLen * 17, buf.readableBytes()); + } + + dictPos += valVarLen; + RequiredBlockFaceSupport[] val = new RequiredBlockFaceSupport[valLen]; + + for (int valIdx = 0; valIdx < valLen; valIdx++) { + val[valIdx] = RequiredBlockFaceSupport.deserialize(buf, dictPos); + dictPos += RequiredBlockFaceSupport.computeBytesConsumed(buf, dictPos); + } + + if (obj.support.put(key, val) != null) { + throw ProtocolException.duplicateKey("support", key); + } + } + } + + if ((nullBits[0] & 128) != 0) { + int varPos7 = offset + 259 + buf.getIntLE(offset + 191); + int supportingCount = VarInt.peek(buf, varPos7); + if (supportingCount < 0) { + throw ProtocolException.negativeLength("Supporting", supportingCount); + } + + if (supportingCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Supporting", supportingCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos7); + obj.supporting = new HashMap<>(supportingCount); + int dictPos = varPos7 + varIntLen; + + for (int i = 0; i < supportingCount; i++) { + BlockNeighbor keyx = BlockNeighbor.fromValue(buf.getByte(dictPos)); + int valLenx = VarInt.peek(buf, ++dictPos); + if (valLenx < 0) { + throw ProtocolException.negativeLength("val", valLenx); + } + + if (valLenx > 64) { + throw ProtocolException.arrayTooLong("val", valLenx, 64); + } + + int valVarLenx = VarInt.length(buf, dictPos); + if (dictPos + valVarLenx + valLenx * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("val", dictPos + valVarLenx + valLenx * 1, buf.readableBytes()); + } + + dictPos += valVarLenx; + BlockFaceSupport[] val = new BlockFaceSupport[valLenx]; + + for (int valIdx = 0; valIdx < valLenx; valIdx++) { + val[valIdx] = BlockFaceSupport.deserialize(buf, dictPos); + dictPos += BlockFaceSupport.computeBytesConsumed(buf, dictPos); + } + + if (obj.supporting.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("supporting", keyx); + } + } + } + + if ((nullBits[1] & 1) != 0) { + int varPos8 = offset + 259 + buf.getIntLE(offset + 195); + int cubeTexturesCount = VarInt.peek(buf, varPos8); + if (cubeTexturesCount < 0) { + throw ProtocolException.negativeLength("CubeTextures", cubeTexturesCount); + } + + if (cubeTexturesCount > 4096000) { + throw ProtocolException.arrayTooLong("CubeTextures", cubeTexturesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos8); + if (varPos8 + varIntLen + cubeTexturesCount * 5L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("CubeTextures", varPos8 + varIntLen + cubeTexturesCount * 5, buf.readableBytes()); + } + + obj.cubeTextures = new BlockTextures[cubeTexturesCount]; + int elemPos = varPos8 + varIntLen; + + for (int i = 0; i < cubeTexturesCount; i++) { + obj.cubeTextures[i] = BlockTextures.deserialize(buf, elemPos); + elemPos += BlockTextures.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits[1] & 2) != 0) { + int varPos9 = offset + 259 + buf.getIntLE(offset + 199); + int cubeSideMaskTextureLen = VarInt.peek(buf, varPos9); + if (cubeSideMaskTextureLen < 0) { + throw ProtocolException.negativeLength("CubeSideMaskTexture", cubeSideMaskTextureLen); + } + + if (cubeSideMaskTextureLen > 4096000) { + throw ProtocolException.stringTooLong("CubeSideMaskTexture", cubeSideMaskTextureLen, 4096000); + } + + obj.cubeSideMaskTexture = PacketIO.readVarString(buf, varPos9, PacketIO.UTF8); + } + + if ((nullBits[1] & 4) != 0) { + int varPos10 = offset + 259 + buf.getIntLE(offset + 203); + int particlesCount = VarInt.peek(buf, varPos10); + if (particlesCount < 0) { + throw ProtocolException.negativeLength("Particles", particlesCount); + } + + if (particlesCount > 4096000) { + throw ProtocolException.arrayTooLong("Particles", particlesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos10); + if (varPos10 + varIntLen + particlesCount * 34L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Particles", varPos10 + varIntLen + particlesCount * 34, buf.readableBytes()); + } + + obj.particles = new ModelParticle[particlesCount]; + int elemPos = varPos10 + varIntLen; + + for (int i = 0; i < particlesCount; i++) { + obj.particles[i] = ModelParticle.deserialize(buf, elemPos); + elemPos += ModelParticle.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits[1] & 8) != 0) { + int varPos11 = offset + 259 + buf.getIntLE(offset + 207); + int blockParticleSetIdLen = VarInt.peek(buf, varPos11); + if (blockParticleSetIdLen < 0) { + throw ProtocolException.negativeLength("BlockParticleSetId", blockParticleSetIdLen); + } + + if (blockParticleSetIdLen > 4096000) { + throw ProtocolException.stringTooLong("BlockParticleSetId", blockParticleSetIdLen, 4096000); + } + + obj.blockParticleSetId = PacketIO.readVarString(buf, varPos11, PacketIO.UTF8); + } + + if ((nullBits[1] & 16) != 0) { + int varPos12 = offset + 259 + buf.getIntLE(offset + 211); + int blockBreakingDecalIdLen = VarInt.peek(buf, varPos12); + if (blockBreakingDecalIdLen < 0) { + throw ProtocolException.negativeLength("BlockBreakingDecalId", blockBreakingDecalIdLen); + } + + if (blockBreakingDecalIdLen > 4096000) { + throw ProtocolException.stringTooLong("BlockBreakingDecalId", blockBreakingDecalIdLen, 4096000); + } + + obj.blockBreakingDecalId = PacketIO.readVarString(buf, varPos12, PacketIO.UTF8); + } + + if ((nullBits[2] & 2) != 0) { + int varPos13 = offset + 259 + buf.getIntLE(offset + 215); + int transitionTextureLen = VarInt.peek(buf, varPos13); + if (transitionTextureLen < 0) { + throw ProtocolException.negativeLength("TransitionTexture", transitionTextureLen); + } + + if (transitionTextureLen > 4096000) { + throw ProtocolException.stringTooLong("TransitionTexture", transitionTextureLen, 4096000); + } + + obj.transitionTexture = PacketIO.readVarString(buf, varPos13, PacketIO.UTF8); + } + + if ((nullBits[2] & 4) != 0) { + int varPos14 = offset + 259 + buf.getIntLE(offset + 219); + int transitionToGroupsCount = VarInt.peek(buf, varPos14); + if (transitionToGroupsCount < 0) { + throw ProtocolException.negativeLength("TransitionToGroups", transitionToGroupsCount); + } + + if (transitionToGroupsCount > 4096000) { + throw ProtocolException.arrayTooLong("TransitionToGroups", transitionToGroupsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos14); + if (varPos14 + varIntLen + transitionToGroupsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("TransitionToGroups", varPos14 + varIntLen + transitionToGroupsCount * 4, buf.readableBytes()); + } + + obj.transitionToGroups = new int[transitionToGroupsCount]; + + for (int i = 0; i < transitionToGroupsCount; i++) { + obj.transitionToGroups[i] = buf.getIntLE(varPos14 + varIntLen + i * 4); + } + } + + if ((nullBits[2] & 32) != 0) { + int varPos15 = offset + 259 + buf.getIntLE(offset + 223); + int interactionHintLen = VarInt.peek(buf, varPos15); + if (interactionHintLen < 0) { + throw ProtocolException.negativeLength("InteractionHint", interactionHintLen); + } + + if (interactionHintLen > 4096000) { + throw ProtocolException.stringTooLong("InteractionHint", interactionHintLen, 4096000); + } + + obj.interactionHint = PacketIO.readVarString(buf, varPos15, PacketIO.UTF8); + } + + if ((nullBits[2] & 64) != 0) { + int varPos16 = offset + 259 + buf.getIntLE(offset + 227); + obj.gathering = BlockGathering.deserialize(buf, varPos16); + } + + if ((nullBits[3] & 1) != 0) { + int varPos17 = offset + 259 + buf.getIntLE(offset + 231); + obj.display = ModelDisplay.deserialize(buf, varPos17); + } + + if ((nullBits[3] & 2) != 0) { + int varPos18 = offset + 259 + buf.getIntLE(offset + 235); + obj.rail = RailConfig.deserialize(buf, varPos18); + } + + if ((nullBits[3] & 4) != 0) { + int varPos19 = offset + 259 + buf.getIntLE(offset + 239); + int interactionsCount = VarInt.peek(buf, varPos19); + if (interactionsCount < 0) { + throw ProtocolException.negativeLength("Interactions", interactionsCount); + } + + if (interactionsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Interactions", interactionsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos19); + obj.interactions = new HashMap<>(interactionsCount); + int dictPos = varPos19 + varIntLen; + + for (int i = 0; i < interactionsCount; i++) { + InteractionType keyxx = InteractionType.fromValue(buf.getByte(dictPos)); + int val = buf.getIntLE(++dictPos); + dictPos += 4; + if (obj.interactions.put(keyxx, val) != null) { + throw ProtocolException.duplicateKey("interactions", keyxx); + } + } + } + + if ((nullBits[3] & 8) != 0) { + int varPos20 = offset + 259 + buf.getIntLE(offset + 243); + int statesCount = VarInt.peek(buf, varPos20); + if (statesCount < 0) { + throw ProtocolException.negativeLength("States", statesCount); + } + + if (statesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("States", statesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos20); + obj.states = new HashMap<>(statesCount); + int dictPos = varPos20 + varIntLen; + + for (int ix = 0; ix < statesCount; ix++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String keyxx = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + int val = buf.getIntLE(dictPos); + dictPos += 4; + if (obj.states.put(keyxx, val) != null) { + throw ProtocolException.duplicateKey("states", keyxx); + } + } + } + + if ((nullBits[3] & 16) != 0) { + int varPos21 = offset + 259 + buf.getIntLE(offset + 247); + int tagIndexesCount = VarInt.peek(buf, varPos21); + if (tagIndexesCount < 0) { + throw ProtocolException.negativeLength("TagIndexes", tagIndexesCount); + } + + if (tagIndexesCount > 4096000) { + throw ProtocolException.arrayTooLong("TagIndexes", tagIndexesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos21); + if (varPos21 + varIntLen + tagIndexesCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("TagIndexes", varPos21 + varIntLen + tagIndexesCount * 4, buf.readableBytes()); + } + + obj.tagIndexes = new int[tagIndexesCount]; + + for (int ix = 0; ix < tagIndexesCount; ix++) { + obj.tagIndexes[ix] = buf.getIntLE(varPos21 + varIntLen + ix * 4); + } + } + + if ((nullBits[3] & 32) != 0) { + int varPos22 = offset + 259 + buf.getIntLE(offset + 251); + obj.bench = Bench.deserialize(buf, varPos22); + } + + if ((nullBits[3] & 64) != 0) { + int varPos23 = offset + 259 + buf.getIntLE(offset + 255); + obj.connectedBlockRuleSet = ConnectedBlockRuleSet.deserialize(buf, varPos23); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte[] nullBits = PacketIO.readBytes(buf, offset, 4); + int maxEnd = 259; + if ((nullBits[0] & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 163); + int pos0 = offset + 259 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits[0] & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 167); + int pos1 = offset + 259 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits[0] & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 171); + int pos2 = offset + 259 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + arrLen * 1; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits[0] & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 175); + int pos3 = offset + 259 + fieldOffset3; + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits[0] & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 179); + int pos4 = offset + 259 + fieldOffset4; + int arrLen = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4); + + for (int i = 0; i < arrLen; i++) { + pos4 += ModelTexture.computeBytesConsumed(buf, pos4); + } + + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits[0] & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 183); + int pos5 = offset + 259 + fieldOffset5; + int sl = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + sl; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits[0] & 64) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 187); + int pos6 = offset + 259 + fieldOffset6; + int dictLen = VarInt.peek(buf, pos6); + pos6 += VarInt.length(buf, pos6); + + for (int i = 0; i < dictLen; i++) { + int al = VarInt.peek(buf, ++pos6); + pos6 += VarInt.length(buf, pos6); + + for (int j = 0; j < al; j++) { + pos6 += RequiredBlockFaceSupport.computeBytesConsumed(buf, pos6); + } + } + + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + if ((nullBits[0] & 128) != 0) { + int fieldOffset7 = buf.getIntLE(offset + 191); + int pos7 = offset + 259 + fieldOffset7; + int dictLen = VarInt.peek(buf, pos7); + pos7 += VarInt.length(buf, pos7); + + for (int i = 0; i < dictLen; i++) { + int al = VarInt.peek(buf, ++pos7); + pos7 += VarInt.length(buf, pos7); + + for (int j = 0; j < al; j++) { + pos7 += BlockFaceSupport.computeBytesConsumed(buf, pos7); + } + } + + if (pos7 - offset > maxEnd) { + maxEnd = pos7 - offset; + } + } + + if ((nullBits[1] & 1) != 0) { + int fieldOffset8 = buf.getIntLE(offset + 195); + int pos8 = offset + 259 + fieldOffset8; + int arrLen = VarInt.peek(buf, pos8); + pos8 += VarInt.length(buf, pos8); + + for (int i = 0; i < arrLen; i++) { + pos8 += BlockTextures.computeBytesConsumed(buf, pos8); + } + + if (pos8 - offset > maxEnd) { + maxEnd = pos8 - offset; + } + } + + if ((nullBits[1] & 2) != 0) { + int fieldOffset9 = buf.getIntLE(offset + 199); + int pos9 = offset + 259 + fieldOffset9; + int sl = VarInt.peek(buf, pos9); + pos9 += VarInt.length(buf, pos9) + sl; + if (pos9 - offset > maxEnd) { + maxEnd = pos9 - offset; + } + } + + if ((nullBits[1] & 4) != 0) { + int fieldOffset10 = buf.getIntLE(offset + 203); + int pos10 = offset + 259 + fieldOffset10; + int arrLen = VarInt.peek(buf, pos10); + pos10 += VarInt.length(buf, pos10); + + for (int i = 0; i < arrLen; i++) { + pos10 += ModelParticle.computeBytesConsumed(buf, pos10); + } + + if (pos10 - offset > maxEnd) { + maxEnd = pos10 - offset; + } + } + + if ((nullBits[1] & 8) != 0) { + int fieldOffset11 = buf.getIntLE(offset + 207); + int pos11 = offset + 259 + fieldOffset11; + int sl = VarInt.peek(buf, pos11); + pos11 += VarInt.length(buf, pos11) + sl; + if (pos11 - offset > maxEnd) { + maxEnd = pos11 - offset; + } + } + + if ((nullBits[1] & 16) != 0) { + int fieldOffset12 = buf.getIntLE(offset + 211); + int pos12 = offset + 259 + fieldOffset12; + int sl = VarInt.peek(buf, pos12); + pos12 += VarInt.length(buf, pos12) + sl; + if (pos12 - offset > maxEnd) { + maxEnd = pos12 - offset; + } + } + + if ((nullBits[2] & 2) != 0) { + int fieldOffset13 = buf.getIntLE(offset + 215); + int pos13 = offset + 259 + fieldOffset13; + int sl = VarInt.peek(buf, pos13); + pos13 += VarInt.length(buf, pos13) + sl; + if (pos13 - offset > maxEnd) { + maxEnd = pos13 - offset; + } + } + + if ((nullBits[2] & 4) != 0) { + int fieldOffset14 = buf.getIntLE(offset + 219); + int pos14 = offset + 259 + fieldOffset14; + int arrLen = VarInt.peek(buf, pos14); + pos14 += VarInt.length(buf, pos14) + arrLen * 4; + if (pos14 - offset > maxEnd) { + maxEnd = pos14 - offset; + } + } + + if ((nullBits[2] & 32) != 0) { + int fieldOffset15 = buf.getIntLE(offset + 223); + int pos15 = offset + 259 + fieldOffset15; + int sl = VarInt.peek(buf, pos15); + pos15 += VarInt.length(buf, pos15) + sl; + if (pos15 - offset > maxEnd) { + maxEnd = pos15 - offset; + } + } + + if ((nullBits[2] & 64) != 0) { + int fieldOffset16 = buf.getIntLE(offset + 227); + int pos16 = offset + 259 + fieldOffset16; + pos16 += BlockGathering.computeBytesConsumed(buf, pos16); + if (pos16 - offset > maxEnd) { + maxEnd = pos16 - offset; + } + } + + if ((nullBits[3] & 1) != 0) { + int fieldOffset17 = buf.getIntLE(offset + 231); + int pos17 = offset + 259 + fieldOffset17; + pos17 += ModelDisplay.computeBytesConsumed(buf, pos17); + if (pos17 - offset > maxEnd) { + maxEnd = pos17 - offset; + } + } + + if ((nullBits[3] & 2) != 0) { + int fieldOffset18 = buf.getIntLE(offset + 235); + int pos18 = offset + 259 + fieldOffset18; + pos18 += RailConfig.computeBytesConsumed(buf, pos18); + if (pos18 - offset > maxEnd) { + maxEnd = pos18 - offset; + } + } + + if ((nullBits[3] & 4) != 0) { + int fieldOffset19 = buf.getIntLE(offset + 239); + int pos19 = offset + 259 + fieldOffset19; + int dictLen = VarInt.peek(buf, pos19); + pos19 += VarInt.length(buf, pos19); + + for (int i = 0; i < dictLen; i++) { + pos19 = ++pos19 + 4; + } + + if (pos19 - offset > maxEnd) { + maxEnd = pos19 - offset; + } + } + + if ((nullBits[3] & 8) != 0) { + int fieldOffset20 = buf.getIntLE(offset + 243); + int pos20 = offset + 259 + fieldOffset20; + int dictLen = VarInt.peek(buf, pos20); + pos20 += VarInt.length(buf, pos20); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos20); + pos20 += VarInt.length(buf, pos20) + sl; + pos20 += 4; + } + + if (pos20 - offset > maxEnd) { + maxEnd = pos20 - offset; + } + } + + if ((nullBits[3] & 16) != 0) { + int fieldOffset21 = buf.getIntLE(offset + 247); + int pos21 = offset + 259 + fieldOffset21; + int arrLen = VarInt.peek(buf, pos21); + pos21 += VarInt.length(buf, pos21) + arrLen * 4; + if (pos21 - offset > maxEnd) { + maxEnd = pos21 - offset; + } + } + + if ((nullBits[3] & 32) != 0) { + int fieldOffset22 = buf.getIntLE(offset + 251); + int pos22 = offset + 259 + fieldOffset22; + pos22 += Bench.computeBytesConsumed(buf, pos22); + if (pos22 - offset > maxEnd) { + maxEnd = pos22 - offset; + } + } + + if ((nullBits[3] & 64) != 0) { + int fieldOffset23 = buf.getIntLE(offset + 255); + int pos23 = offset + 259 + fieldOffset23; + pos23 += ConnectedBlockRuleSet.computeBytesConsumed(buf, pos23); + if (pos23 - offset > maxEnd) { + maxEnd = pos23 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte[] nullBits = new byte[4]; + if (this.item != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.name != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.shaderEffect != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.model != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.modelTexture != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.modelAnimation != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.support != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.supporting != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.cubeTextures != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + if (this.cubeSideMaskTexture != null) { + nullBits[1] = (byte)(nullBits[1] | 2); + } + + if (this.particles != null) { + nullBits[1] = (byte)(nullBits[1] | 4); + } + + if (this.blockParticleSetId != null) { + nullBits[1] = (byte)(nullBits[1] | 8); + } + + if (this.blockBreakingDecalId != null) { + nullBits[1] = (byte)(nullBits[1] | 16); + } + + if (this.particleColor != null) { + nullBits[1] = (byte)(nullBits[1] | 32); + } + + if (this.light != null) { + nullBits[1] = (byte)(nullBits[1] | 64); + } + + if (this.tint != null) { + nullBits[1] = (byte)(nullBits[1] | 128); + } + + if (this.biomeTint != null) { + nullBits[2] = (byte)(nullBits[2] | 1); + } + + if (this.transitionTexture != null) { + nullBits[2] = (byte)(nullBits[2] | 2); + } + + if (this.transitionToGroups != null) { + nullBits[2] = (byte)(nullBits[2] | 4); + } + + if (this.movementSettings != null) { + nullBits[2] = (byte)(nullBits[2] | 8); + } + + if (this.flags != null) { + nullBits[2] = (byte)(nullBits[2] | 16); + } + + if (this.interactionHint != null) { + nullBits[2] = (byte)(nullBits[2] | 32); + } + + if (this.gathering != null) { + nullBits[2] = (byte)(nullBits[2] | 64); + } + + if (this.placementSettings != null) { + nullBits[2] = (byte)(nullBits[2] | 128); + } + + if (this.display != null) { + nullBits[3] = (byte)(nullBits[3] | 1); + } + + if (this.rail != null) { + nullBits[3] = (byte)(nullBits[3] | 2); + } + + if (this.interactions != null) { + nullBits[3] = (byte)(nullBits[3] | 4); + } + + if (this.states != null) { + nullBits[3] = (byte)(nullBits[3] | 8); + } + + if (this.tagIndexes != null) { + nullBits[3] = (byte)(nullBits[3] | 16); + } + + if (this.bench != null) { + nullBits[3] = (byte)(nullBits[3] | 32); + } + + if (this.connectedBlockRuleSet != null) { + nullBits[3] = (byte)(nullBits[3] | 64); + } + + buf.writeBytes(nullBits); + buf.writeByte(this.unknown ? 1 : 0); + buf.writeByte(this.drawType.getValue()); + buf.writeByte(this.material.getValue()); + buf.writeByte(this.opacity.getValue()); + buf.writeIntLE(this.hitbox); + buf.writeIntLE(this.interactionHitbox); + buf.writeFloatLE(this.modelScale); + buf.writeByte(this.looping ? 1 : 0); + buf.writeIntLE(this.maxSupportDistance); + buf.writeByte(this.blockSupportsRequiredFor.getValue()); + buf.writeByte(this.requiresAlphaBlending ? 1 : 0); + buf.writeByte(this.cubeShadingMode.getValue()); + buf.writeByte(this.randomRotation.getValue()); + buf.writeByte(this.variantRotation.getValue()); + buf.writeByte(this.rotationYawPlacementOffset.getValue()); + buf.writeIntLE(this.blockSoundSetIndex); + buf.writeIntLE(this.ambientSoundEventIndex); + if (this.particleColor != null) { + this.particleColor.serialize(buf); + } else { + buf.writeZero(3); + } + + if (this.light != null) { + this.light.serialize(buf); + } else { + buf.writeZero(4); + } + + if (this.tint != null) { + this.tint.serialize(buf); + } else { + buf.writeZero(24); + } + + if (this.biomeTint != null) { + this.biomeTint.serialize(buf); + } else { + buf.writeZero(24); + } + + buf.writeIntLE(this.group); + if (this.movementSettings != null) { + this.movementSettings.serialize(buf); + } else { + buf.writeZero(42); + } + + if (this.flags != null) { + this.flags.serialize(buf); + } else { + buf.writeZero(2); + } + + if (this.placementSettings != null) { + this.placementSettings.serialize(buf); + } else { + buf.writeZero(16); + } + + buf.writeByte(this.ignoreSupportWhenPlaced ? 1 : 0); + buf.writeIntLE(this.transitionToTag); + int itemOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int nameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int shaderEffectOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int modelOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int modelTextureOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int modelAnimationOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int supportOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int supportingOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cubeTexturesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cubeSideMaskTextureOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int particlesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int blockParticleSetIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int blockBreakingDecalIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int transitionTextureOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int transitionToGroupsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int interactionHintOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int gatheringOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int displayOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int railOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int interactionsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int statesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagIndexesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int benchOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int connectedBlockRuleSetOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.item != null) { + buf.setIntLE(itemOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.item, 4096000); + } else { + buf.setIntLE(itemOffsetSlot, -1); + } + + if (this.name != null) { + buf.setIntLE(nameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.name, 4096000); + } else { + buf.setIntLE(nameOffsetSlot, -1); + } + + if (this.shaderEffect != null) { + buf.setIntLE(shaderEffectOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.shaderEffect.length > 4096000) { + throw ProtocolException.arrayTooLong("ShaderEffect", this.shaderEffect.length, 4096000); + } + + VarInt.write(buf, this.shaderEffect.length); + + for (ShaderType item : this.shaderEffect) { + buf.writeByte(item.getValue()); + } + } else { + buf.setIntLE(shaderEffectOffsetSlot, -1); + } + + if (this.model != null) { + buf.setIntLE(modelOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.model, 4096000); + } else { + buf.setIntLE(modelOffsetSlot, -1); + } + + if (this.modelTexture != null) { + buf.setIntLE(modelTextureOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.modelTexture.length > 4096000) { + throw ProtocolException.arrayTooLong("ModelTexture", this.modelTexture.length, 4096000); + } + + VarInt.write(buf, this.modelTexture.length); + + for (ModelTexture item : this.modelTexture) { + item.serialize(buf); + } + } else { + buf.setIntLE(modelTextureOffsetSlot, -1); + } + + if (this.modelAnimation != null) { + buf.setIntLE(modelAnimationOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.modelAnimation, 4096000); + } else { + buf.setIntLE(modelAnimationOffsetSlot, -1); + } + + if (this.support != null) { + buf.setIntLE(supportOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.support.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Support", this.support.size(), 4096000); + } + + VarInt.write(buf, this.support.size()); + + for (Entry e : this.support.entrySet()) { + buf.writeByte(e.getKey().getValue()); + VarInt.write(buf, e.getValue().length); + + for (RequiredBlockFaceSupport arrItem : e.getValue()) { + arrItem.serialize(buf); + } + } + } else { + buf.setIntLE(supportOffsetSlot, -1); + } + + if (this.supporting != null) { + buf.setIntLE(supportingOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.supporting.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Supporting", this.supporting.size(), 4096000); + } + + VarInt.write(buf, this.supporting.size()); + + for (Entry e : this.supporting.entrySet()) { + buf.writeByte(e.getKey().getValue()); + VarInt.write(buf, e.getValue().length); + + for (BlockFaceSupport arrItem : e.getValue()) { + arrItem.serialize(buf); + } + } + } else { + buf.setIntLE(supportingOffsetSlot, -1); + } + + if (this.cubeTextures != null) { + buf.setIntLE(cubeTexturesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.cubeTextures.length > 4096000) { + throw ProtocolException.arrayTooLong("CubeTextures", this.cubeTextures.length, 4096000); + } + + VarInt.write(buf, this.cubeTextures.length); + + for (BlockTextures item : this.cubeTextures) { + item.serialize(buf); + } + } else { + buf.setIntLE(cubeTexturesOffsetSlot, -1); + } + + if (this.cubeSideMaskTexture != null) { + buf.setIntLE(cubeSideMaskTextureOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.cubeSideMaskTexture, 4096000); + } else { + buf.setIntLE(cubeSideMaskTextureOffsetSlot, -1); + } + + if (this.particles != null) { + buf.setIntLE(particlesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.particles.length > 4096000) { + throw ProtocolException.arrayTooLong("Particles", this.particles.length, 4096000); + } + + VarInt.write(buf, this.particles.length); + + for (ModelParticle item : this.particles) { + item.serialize(buf); + } + } else { + buf.setIntLE(particlesOffsetSlot, -1); + } + + if (this.blockParticleSetId != null) { + buf.setIntLE(blockParticleSetIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.blockParticleSetId, 4096000); + } else { + buf.setIntLE(blockParticleSetIdOffsetSlot, -1); + } + + if (this.blockBreakingDecalId != null) { + buf.setIntLE(blockBreakingDecalIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.blockBreakingDecalId, 4096000); + } else { + buf.setIntLE(blockBreakingDecalIdOffsetSlot, -1); + } + + if (this.transitionTexture != null) { + buf.setIntLE(transitionTextureOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.transitionTexture, 4096000); + } else { + buf.setIntLE(transitionTextureOffsetSlot, -1); + } + + if (this.transitionToGroups != null) { + buf.setIntLE(transitionToGroupsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.transitionToGroups.length > 4096000) { + throw ProtocolException.arrayTooLong("TransitionToGroups", this.transitionToGroups.length, 4096000); + } + + VarInt.write(buf, this.transitionToGroups.length); + + for (int item : this.transitionToGroups) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(transitionToGroupsOffsetSlot, -1); + } + + if (this.interactionHint != null) { + buf.setIntLE(interactionHintOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.interactionHint, 4096000); + } else { + buf.setIntLE(interactionHintOffsetSlot, -1); + } + + if (this.gathering != null) { + buf.setIntLE(gatheringOffsetSlot, buf.writerIndex() - varBlockStart); + this.gathering.serialize(buf); + } else { + buf.setIntLE(gatheringOffsetSlot, -1); + } + + if (this.display != null) { + buf.setIntLE(displayOffsetSlot, buf.writerIndex() - varBlockStart); + this.display.serialize(buf); + } else { + buf.setIntLE(displayOffsetSlot, -1); + } + + if (this.rail != null) { + buf.setIntLE(railOffsetSlot, buf.writerIndex() - varBlockStart); + this.rail.serialize(buf); + } else { + buf.setIntLE(railOffsetSlot, -1); + } + + if (this.interactions != null) { + buf.setIntLE(interactionsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.interactions.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Interactions", this.interactions.size(), 4096000); + } + + VarInt.write(buf, this.interactions.size()); + + for (Entry e : this.interactions.entrySet()) { + buf.writeByte(e.getKey().getValue()); + buf.writeIntLE(e.getValue()); + } + } else { + buf.setIntLE(interactionsOffsetSlot, -1); + } + + if (this.states != null) { + buf.setIntLE(statesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.states.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("States", this.states.size(), 4096000); + } + + VarInt.write(buf, this.states.size()); + + for (Entry e : this.states.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + buf.writeIntLE(e.getValue()); + } + } else { + buf.setIntLE(statesOffsetSlot, -1); + } + + if (this.tagIndexes != null) { + buf.setIntLE(tagIndexesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tagIndexes.length > 4096000) { + throw ProtocolException.arrayTooLong("TagIndexes", this.tagIndexes.length, 4096000); + } + + VarInt.write(buf, this.tagIndexes.length); + + for (int item : this.tagIndexes) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagIndexesOffsetSlot, -1); + } + + if (this.bench != null) { + buf.setIntLE(benchOffsetSlot, buf.writerIndex() - varBlockStart); + this.bench.serialize(buf); + } else { + buf.setIntLE(benchOffsetSlot, -1); + } + + if (this.connectedBlockRuleSet != null) { + buf.setIntLE(connectedBlockRuleSetOffsetSlot, buf.writerIndex() - varBlockStart); + this.connectedBlockRuleSet.serialize(buf); + } else { + buf.setIntLE(connectedBlockRuleSetOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 259; + if (this.item != null) { + size += PacketIO.stringSize(this.item); + } + + if (this.name != null) { + size += PacketIO.stringSize(this.name); + } + + if (this.shaderEffect != null) { + size += VarInt.size(this.shaderEffect.length) + this.shaderEffect.length * 1; + } + + if (this.model != null) { + size += PacketIO.stringSize(this.model); + } + + if (this.modelTexture != null) { + int modelTextureSize = 0; + + for (ModelTexture elem : this.modelTexture) { + modelTextureSize += elem.computeSize(); + } + + size += VarInt.size(this.modelTexture.length) + modelTextureSize; + } + + if (this.modelAnimation != null) { + size += PacketIO.stringSize(this.modelAnimation); + } + + if (this.support != null) { + int supportSize = 0; + + for (Entry kvp : this.support.entrySet()) { + supportSize += 1 + VarInt.size(kvp.getValue().length) + Arrays.stream(kvp.getValue()).mapToInt(inner -> inner.computeSize()).sum(); + } + + size += VarInt.size(this.support.size()) + supportSize; + } + + if (this.supporting != null) { + int supportingSize = 0; + + for (Entry kvp : this.supporting.entrySet()) { + supportingSize += 1 + VarInt.size(kvp.getValue().length) + Arrays.stream(kvp.getValue()).mapToInt(inner -> inner.computeSize()).sum(); + } + + size += VarInt.size(this.supporting.size()) + supportingSize; + } + + if (this.cubeTextures != null) { + int cubeTexturesSize = 0; + + for (BlockTextures elem : this.cubeTextures) { + cubeTexturesSize += elem.computeSize(); + } + + size += VarInt.size(this.cubeTextures.length) + cubeTexturesSize; + } + + if (this.cubeSideMaskTexture != null) { + size += PacketIO.stringSize(this.cubeSideMaskTexture); + } + + if (this.particles != null) { + int particlesSize = 0; + + for (ModelParticle elem : this.particles) { + particlesSize += elem.computeSize(); + } + + size += VarInt.size(this.particles.length) + particlesSize; + } + + if (this.blockParticleSetId != null) { + size += PacketIO.stringSize(this.blockParticleSetId); + } + + if (this.blockBreakingDecalId != null) { + size += PacketIO.stringSize(this.blockBreakingDecalId); + } + + if (this.transitionTexture != null) { + size += PacketIO.stringSize(this.transitionTexture); + } + + if (this.transitionToGroups != null) { + size += VarInt.size(this.transitionToGroups.length) + this.transitionToGroups.length * 4; + } + + if (this.interactionHint != null) { + size += PacketIO.stringSize(this.interactionHint); + } + + if (this.gathering != null) { + size += this.gathering.computeSize(); + } + + if (this.display != null) { + size += this.display.computeSize(); + } + + if (this.rail != null) { + size += this.rail.computeSize(); + } + + if (this.interactions != null) { + size += VarInt.size(this.interactions.size()) + this.interactions.size() * 5; + } + + if (this.states != null) { + int statesSize = 0; + + for (Entry kvp : this.states.entrySet()) { + statesSize += PacketIO.stringSize(kvp.getKey()) + 4; + } + + size += VarInt.size(this.states.size()) + statesSize; + } + + if (this.tagIndexes != null) { + size += VarInt.size(this.tagIndexes.length) + this.tagIndexes.length * 4; + } + + if (this.bench != null) { + size += this.bench.computeSize(); + } + + if (this.connectedBlockRuleSet != null) { + size += this.connectedBlockRuleSet.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 259) { + return ValidationResult.error("Buffer too small: expected at least 259 bytes"); + } else { + byte[] nullBits = PacketIO.readBytes(buffer, offset, 4); + if ((nullBits[0] & 1) != 0) { + int itemOffset = buffer.getIntLE(offset + 163); + if (itemOffset < 0) { + return ValidationResult.error("Invalid offset for Item"); + } + + int pos = offset + 259 + itemOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Item"); + } + + int itemLen = VarInt.peek(buffer, pos); + if (itemLen < 0) { + return ValidationResult.error("Invalid string length for Item"); + } + + if (itemLen > 4096000) { + return ValidationResult.error("Item exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += itemLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Item"); + } + } + + if ((nullBits[0] & 2) != 0) { + int nameOffset = buffer.getIntLE(offset + 167); + if (nameOffset < 0) { + return ValidationResult.error("Invalid offset for Name"); + } + + int posx = offset + 259 + nameOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Name"); + } + + int nameLen = VarInt.peek(buffer, posx); + if (nameLen < 0) { + return ValidationResult.error("Invalid string length for Name"); + } + + if (nameLen > 4096000) { + return ValidationResult.error("Name exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += nameLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Name"); + } + } + + if ((nullBits[0] & 4) != 0) { + int shaderEffectOffset = buffer.getIntLE(offset + 171); + if (shaderEffectOffset < 0) { + return ValidationResult.error("Invalid offset for ShaderEffect"); + } + + int posxx = offset + 259 + shaderEffectOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ShaderEffect"); + } + + int shaderEffectCount = VarInt.peek(buffer, posxx); + if (shaderEffectCount < 0) { + return ValidationResult.error("Invalid array count for ShaderEffect"); + } + + if (shaderEffectCount > 4096000) { + return ValidationResult.error("ShaderEffect exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += shaderEffectCount * 1; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ShaderEffect"); + } + } + + if ((nullBits[0] & 8) != 0) { + int modelOffset = buffer.getIntLE(offset + 175); + if (modelOffset < 0) { + return ValidationResult.error("Invalid offset for Model"); + } + + int posxxx = offset + 259 + modelOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Model"); + } + + int modelLen = VarInt.peek(buffer, posxxx); + if (modelLen < 0) { + return ValidationResult.error("Invalid string length for Model"); + } + + if (modelLen > 4096000) { + return ValidationResult.error("Model exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += modelLen; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Model"); + } + } + + if ((nullBits[0] & 16) != 0) { + int modelTextureOffset = buffer.getIntLE(offset + 179); + if (modelTextureOffset < 0) { + return ValidationResult.error("Invalid offset for ModelTexture"); + } + + int posxxxx = offset + 259 + modelTextureOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ModelTexture"); + } + + int modelTextureCount = VarInt.peek(buffer, posxxxx); + if (modelTextureCount < 0) { + return ValidationResult.error("Invalid array count for ModelTexture"); + } + + if (modelTextureCount > 4096000) { + return ValidationResult.error("ModelTexture exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + + for (int i = 0; i < modelTextureCount; i++) { + ValidationResult structResult = ModelTexture.validateStructure(buffer, posxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelTexture in ModelTexture[" + i + "]: " + structResult.error()); + } + + posxxxx += ModelTexture.computeBytesConsumed(buffer, posxxxx); + } + } + + if ((nullBits[0] & 32) != 0) { + int modelAnimationOffset = buffer.getIntLE(offset + 183); + if (modelAnimationOffset < 0) { + return ValidationResult.error("Invalid offset for ModelAnimation"); + } + + int posxxxxx = offset + 259 + modelAnimationOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ModelAnimation"); + } + + int modelAnimationLen = VarInt.peek(buffer, posxxxxx); + if (modelAnimationLen < 0) { + return ValidationResult.error("Invalid string length for ModelAnimation"); + } + + if (modelAnimationLen > 4096000) { + return ValidationResult.error("ModelAnimation exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += modelAnimationLen; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ModelAnimation"); + } + } + + if ((nullBits[0] & 64) != 0) { + int supportOffset = buffer.getIntLE(offset + 187); + if (supportOffset < 0) { + return ValidationResult.error("Invalid offset for Support"); + } + + int posxxxxxx = offset + 259 + supportOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Support"); + } + + int supportCount = VarInt.peek(buffer, posxxxxxx); + if (supportCount < 0) { + return ValidationResult.error("Invalid dictionary count for Support"); + } + + if (supportCount > 4096000) { + return ValidationResult.error("Support exceeds max length 4096000"); + } + + posxxxxxx += VarInt.length(buffer, posxxxxxx); + + for (int i = 0; i < supportCount; i++) { + int valueArrCount = VarInt.peek(buffer, ++posxxxxxx); + if (valueArrCount < 0) { + return ValidationResult.error("Invalid array count for value"); + } + + posxxxxxx += VarInt.length(buffer, posxxxxxx); + + for (int valueArrIdx = 0; valueArrIdx < valueArrCount; valueArrIdx++) { + posxxxxxx += RequiredBlockFaceSupport.computeBytesConsumed(buffer, posxxxxxx); + } + } + } + + if ((nullBits[0] & 128) != 0) { + int supportingOffset = buffer.getIntLE(offset + 191); + if (supportingOffset < 0) { + return ValidationResult.error("Invalid offset for Supporting"); + } + + int posxxxxxxx = offset + 259 + supportingOffset; + if (posxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Supporting"); + } + + int supportingCount = VarInt.peek(buffer, posxxxxxxx); + if (supportingCount < 0) { + return ValidationResult.error("Invalid dictionary count for Supporting"); + } + + if (supportingCount > 4096000) { + return ValidationResult.error("Supporting exceeds max length 4096000"); + } + + posxxxxxxx += VarInt.length(buffer, posxxxxxxx); + + for (int i = 0; i < supportingCount; i++) { + int valueArrCount = VarInt.peek(buffer, ++posxxxxxxx); + if (valueArrCount < 0) { + return ValidationResult.error("Invalid array count for value"); + } + + posxxxxxxx += VarInt.length(buffer, posxxxxxxx); + + for (int valueArrIdx = 0; valueArrIdx < valueArrCount; valueArrIdx++) { + posxxxxxxx += BlockFaceSupport.computeBytesConsumed(buffer, posxxxxxxx); + } + } + } + + if ((nullBits[1] & 1) != 0) { + int cubeTexturesOffset = buffer.getIntLE(offset + 195); + if (cubeTexturesOffset < 0) { + return ValidationResult.error("Invalid offset for CubeTextures"); + } + + int posxxxxxxxx = offset + 259 + cubeTexturesOffset; + if (posxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for CubeTextures"); + } + + int cubeTexturesCount = VarInt.peek(buffer, posxxxxxxxx); + if (cubeTexturesCount < 0) { + return ValidationResult.error("Invalid array count for CubeTextures"); + } + + if (cubeTexturesCount > 4096000) { + return ValidationResult.error("CubeTextures exceeds max length 4096000"); + } + + posxxxxxxxx += VarInt.length(buffer, posxxxxxxxx); + + for (int i = 0; i < cubeTexturesCount; i++) { + ValidationResult structResult = BlockTextures.validateStructure(buffer, posxxxxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid BlockTextures in CubeTextures[" + i + "]: " + structResult.error()); + } + + posxxxxxxxx += BlockTextures.computeBytesConsumed(buffer, posxxxxxxxx); + } + } + + if ((nullBits[1] & 2) != 0) { + int cubeSideMaskTextureOffset = buffer.getIntLE(offset + 199); + if (cubeSideMaskTextureOffset < 0) { + return ValidationResult.error("Invalid offset for CubeSideMaskTexture"); + } + + int posxxxxxxxxx = offset + 259 + cubeSideMaskTextureOffset; + if (posxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for CubeSideMaskTexture"); + } + + int cubeSideMaskTextureLen = VarInt.peek(buffer, posxxxxxxxxx); + if (cubeSideMaskTextureLen < 0) { + return ValidationResult.error("Invalid string length for CubeSideMaskTexture"); + } + + if (cubeSideMaskTextureLen > 4096000) { + return ValidationResult.error("CubeSideMaskTexture exceeds max length 4096000"); + } + + posxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxx); + posxxxxxxxxx += cubeSideMaskTextureLen; + if (posxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading CubeSideMaskTexture"); + } + } + + if ((nullBits[1] & 4) != 0) { + int particlesOffset = buffer.getIntLE(offset + 203); + if (particlesOffset < 0) { + return ValidationResult.error("Invalid offset for Particles"); + } + + int posxxxxxxxxxx = offset + 259 + particlesOffset; + if (posxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Particles"); + } + + int particlesCount = VarInt.peek(buffer, posxxxxxxxxxx); + if (particlesCount < 0) { + return ValidationResult.error("Invalid array count for Particles"); + } + + if (particlesCount > 4096000) { + return ValidationResult.error("Particles exceeds max length 4096000"); + } + + posxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxx); + + for (int i = 0; i < particlesCount; i++) { + ValidationResult structResult = ModelParticle.validateStructure(buffer, posxxxxxxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelParticle in Particles[" + i + "]: " + structResult.error()); + } + + posxxxxxxxxxx += ModelParticle.computeBytesConsumed(buffer, posxxxxxxxxxx); + } + } + + if ((nullBits[1] & 8) != 0) { + int blockParticleSetIdOffset = buffer.getIntLE(offset + 207); + if (blockParticleSetIdOffset < 0) { + return ValidationResult.error("Invalid offset for BlockParticleSetId"); + } + + int posxxxxxxxxxxx = offset + 259 + blockParticleSetIdOffset; + if (posxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BlockParticleSetId"); + } + + int blockParticleSetIdLen = VarInt.peek(buffer, posxxxxxxxxxxx); + if (blockParticleSetIdLen < 0) { + return ValidationResult.error("Invalid string length for BlockParticleSetId"); + } + + if (blockParticleSetIdLen > 4096000) { + return ValidationResult.error("BlockParticleSetId exceeds max length 4096000"); + } + + posxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxx); + posxxxxxxxxxxx += blockParticleSetIdLen; + if (posxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading BlockParticleSetId"); + } + } + + if ((nullBits[1] & 16) != 0) { + int blockBreakingDecalIdOffset = buffer.getIntLE(offset + 211); + if (blockBreakingDecalIdOffset < 0) { + return ValidationResult.error("Invalid offset for BlockBreakingDecalId"); + } + + int posxxxxxxxxxxxx = offset + 259 + blockBreakingDecalIdOffset; + if (posxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BlockBreakingDecalId"); + } + + int blockBreakingDecalIdLen = VarInt.peek(buffer, posxxxxxxxxxxxx); + if (blockBreakingDecalIdLen < 0) { + return ValidationResult.error("Invalid string length for BlockBreakingDecalId"); + } + + if (blockBreakingDecalIdLen > 4096000) { + return ValidationResult.error("BlockBreakingDecalId exceeds max length 4096000"); + } + + posxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxx); + posxxxxxxxxxxxx += blockBreakingDecalIdLen; + if (posxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading BlockBreakingDecalId"); + } + } + + if ((nullBits[2] & 2) != 0) { + int transitionTextureOffset = buffer.getIntLE(offset + 215); + if (transitionTextureOffset < 0) { + return ValidationResult.error("Invalid offset for TransitionTexture"); + } + + int posxxxxxxxxxxxxx = offset + 259 + transitionTextureOffset; + if (posxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for TransitionTexture"); + } + + int transitionTextureLen = VarInt.peek(buffer, posxxxxxxxxxxxxx); + if (transitionTextureLen < 0) { + return ValidationResult.error("Invalid string length for TransitionTexture"); + } + + if (transitionTextureLen > 4096000) { + return ValidationResult.error("TransitionTexture exceeds max length 4096000"); + } + + posxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxx); + posxxxxxxxxxxxxx += transitionTextureLen; + if (posxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading TransitionTexture"); + } + } + + if ((nullBits[2] & 4) != 0) { + int transitionToGroupsOffset = buffer.getIntLE(offset + 219); + if (transitionToGroupsOffset < 0) { + return ValidationResult.error("Invalid offset for TransitionToGroups"); + } + + int posxxxxxxxxxxxxxx = offset + 259 + transitionToGroupsOffset; + if (posxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for TransitionToGroups"); + } + + int transitionToGroupsCount = VarInt.peek(buffer, posxxxxxxxxxxxxxx); + if (transitionToGroupsCount < 0) { + return ValidationResult.error("Invalid array count for TransitionToGroups"); + } + + if (transitionToGroupsCount > 4096000) { + return ValidationResult.error("TransitionToGroups exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxx); + posxxxxxxxxxxxxxx += transitionToGroupsCount * 4; + if (posxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading TransitionToGroups"); + } + } + + if ((nullBits[2] & 32) != 0) { + int interactionHintOffset = buffer.getIntLE(offset + 223); + if (interactionHintOffset < 0) { + return ValidationResult.error("Invalid offset for InteractionHint"); + } + + int posxxxxxxxxxxxxxxx = offset + 259 + interactionHintOffset; + if (posxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for InteractionHint"); + } + + int interactionHintLen = VarInt.peek(buffer, posxxxxxxxxxxxxxxx); + if (interactionHintLen < 0) { + return ValidationResult.error("Invalid string length for InteractionHint"); + } + + if (interactionHintLen > 4096000) { + return ValidationResult.error("InteractionHint exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxx); + posxxxxxxxxxxxxxxx += interactionHintLen; + if (posxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading InteractionHint"); + } + } + + if ((nullBits[2] & 64) != 0) { + int gatheringOffset = buffer.getIntLE(offset + 227); + if (gatheringOffset < 0) { + return ValidationResult.error("Invalid offset for Gathering"); + } + + int posxxxxxxxxxxxxxxxx = offset + 259 + gatheringOffset; + if (posxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Gathering"); + } + + ValidationResult gatheringResult = BlockGathering.validateStructure(buffer, posxxxxxxxxxxxxxxxx); + if (!gatheringResult.isValid()) { + return ValidationResult.error("Invalid Gathering: " + gatheringResult.error()); + } + + posxxxxxxxxxxxxxxxx += BlockGathering.computeBytesConsumed(buffer, posxxxxxxxxxxxxxxxx); + } + + if ((nullBits[3] & 1) != 0) { + int displayOffset = buffer.getIntLE(offset + 231); + if (displayOffset < 0) { + return ValidationResult.error("Invalid offset for Display"); + } + + int posxxxxxxxxxxxxxxxxx = offset + 259 + displayOffset; + if (posxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Display"); + } + + ValidationResult displayResult = ModelDisplay.validateStructure(buffer, posxxxxxxxxxxxxxxxxx); + if (!displayResult.isValid()) { + return ValidationResult.error("Invalid Display: " + displayResult.error()); + } + + posxxxxxxxxxxxxxxxxx += ModelDisplay.computeBytesConsumed(buffer, posxxxxxxxxxxxxxxxxx); + } + + if ((nullBits[3] & 2) != 0) { + int railOffset = buffer.getIntLE(offset + 235); + if (railOffset < 0) { + return ValidationResult.error("Invalid offset for Rail"); + } + + int posxxxxxxxxxxxxxxxxxx = offset + 259 + railOffset; + if (posxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rail"); + } + + ValidationResult railResult = RailConfig.validateStructure(buffer, posxxxxxxxxxxxxxxxxxx); + if (!railResult.isValid()) { + return ValidationResult.error("Invalid Rail: " + railResult.error()); + } + + posxxxxxxxxxxxxxxxxxx += RailConfig.computeBytesConsumed(buffer, posxxxxxxxxxxxxxxxxxx); + } + + if ((nullBits[3] & 4) != 0) { + int interactionsOffset = buffer.getIntLE(offset + 239); + if (interactionsOffset < 0) { + return ValidationResult.error("Invalid offset for Interactions"); + } + + int posxxxxxxxxxxxxxxxxxxx = offset + 259 + interactionsOffset; + if (posxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Interactions"); + } + + int interactionsCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxxx); + if (interactionsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Interactions"); + } + + if (interactionsCount > 4096000) { + return ValidationResult.error("Interactions exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxxx); + + for (int i = 0; i < interactionsCount; i++) { + posxxxxxxxxxxxxxxxxxxx = ++posxxxxxxxxxxxxxxxxxxx + 4; + if (posxxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + if ((nullBits[3] & 8) != 0) { + int statesOffset = buffer.getIntLE(offset + 243); + if (statesOffset < 0) { + return ValidationResult.error("Invalid offset for States"); + } + + int posxxxxxxxxxxxxxxxxxxxx = offset + 259 + statesOffset; + if (posxxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for States"); + } + + int statesCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxxxx); + if (statesCount < 0) { + return ValidationResult.error("Invalid dictionary count for States"); + } + + if (statesCount > 4096000) { + return ValidationResult.error("States exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxxxx); + + for (int ix = 0; ix < statesCount; ix++) { + int keyLen = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxxxx); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxxxx); + posxxxxxxxxxxxxxxxxxxxx += keyLen; + if (posxxxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxxxxxxxxxxxxxxx += 4; + if (posxxxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + if ((nullBits[3] & 16) != 0) { + int tagIndexesOffset = buffer.getIntLE(offset + 247); + if (tagIndexesOffset < 0) { + return ValidationResult.error("Invalid offset for TagIndexes"); + } + + int posxxxxxxxxxxxxxxxxxxxxx = offset + 259 + tagIndexesOffset; + if (posxxxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for TagIndexes"); + } + + int tagIndexesCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxxxxx); + if (tagIndexesCount < 0) { + return ValidationResult.error("Invalid array count for TagIndexes"); + } + + if (tagIndexesCount > 4096000) { + return ValidationResult.error("TagIndexes exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxxxxx); + posxxxxxxxxxxxxxxxxxxxxx += tagIndexesCount * 4; + if (posxxxxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading TagIndexes"); + } + } + + if ((nullBits[3] & 32) != 0) { + int benchOffset = buffer.getIntLE(offset + 251); + if (benchOffset < 0) { + return ValidationResult.error("Invalid offset for Bench"); + } + + int posxxxxxxxxxxxxxxxxxxxxxx = offset + 259 + benchOffset; + if (posxxxxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Bench"); + } + + ValidationResult benchResult = Bench.validateStructure(buffer, posxxxxxxxxxxxxxxxxxxxxxx); + if (!benchResult.isValid()) { + return ValidationResult.error("Invalid Bench: " + benchResult.error()); + } + + posxxxxxxxxxxxxxxxxxxxxxx += Bench.computeBytesConsumed(buffer, posxxxxxxxxxxxxxxxxxxxxxx); + } + + if ((nullBits[3] & 64) != 0) { + int connectedBlockRuleSetOffset = buffer.getIntLE(offset + 255); + if (connectedBlockRuleSetOffset < 0) { + return ValidationResult.error("Invalid offset for ConnectedBlockRuleSet"); + } + + int posxxxxxxxxxxxxxxxxxxxxxxx = offset + 259 + connectedBlockRuleSetOffset; + if (posxxxxxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ConnectedBlockRuleSet"); + } + + ValidationResult connectedBlockRuleSetResult = ConnectedBlockRuleSet.validateStructure(buffer, posxxxxxxxxxxxxxxxxxxxxxxx); + if (!connectedBlockRuleSetResult.isValid()) { + return ValidationResult.error("Invalid ConnectedBlockRuleSet: " + connectedBlockRuleSetResult.error()); + } + + posxxxxxxxxxxxxxxxxxxxxxxx += ConnectedBlockRuleSet.computeBytesConsumed(buffer, posxxxxxxxxxxxxxxxxxxxxxxx); + } + + return ValidationResult.OK; + } + } + + public BlockType clone() { + BlockType copy = new BlockType(); + copy.item = this.item; + copy.name = this.name; + copy.unknown = this.unknown; + copy.drawType = this.drawType; + copy.material = this.material; + copy.opacity = this.opacity; + copy.shaderEffect = this.shaderEffect != null ? Arrays.copyOf(this.shaderEffect, this.shaderEffect.length) : null; + copy.hitbox = this.hitbox; + copy.interactionHitbox = this.interactionHitbox; + copy.model = this.model; + copy.modelTexture = this.modelTexture != null ? Arrays.stream(this.modelTexture).map(ex -> ex.clone()).toArray(ModelTexture[]::new) : null; + copy.modelScale = this.modelScale; + copy.modelAnimation = this.modelAnimation; + copy.looping = this.looping; + copy.maxSupportDistance = this.maxSupportDistance; + copy.blockSupportsRequiredFor = this.blockSupportsRequiredFor; + if (this.support != null) { + Map m = new HashMap<>(); + + for (Entry e : this.support.entrySet()) { + m.put(e.getKey(), Arrays.stream(e.getValue()).map(x -> x.clone()).toArray(RequiredBlockFaceSupport[]::new)); + } + + copy.support = m; + } + + if (this.supporting != null) { + Map m = new HashMap<>(); + + for (Entry e : this.supporting.entrySet()) { + m.put(e.getKey(), Arrays.stream(e.getValue()).map(x -> x.clone()).toArray(BlockFaceSupport[]::new)); + } + + copy.supporting = m; + } + + copy.requiresAlphaBlending = this.requiresAlphaBlending; + copy.cubeTextures = this.cubeTextures != null ? Arrays.stream(this.cubeTextures).map(ex -> ex.clone()).toArray(BlockTextures[]::new) : null; + copy.cubeSideMaskTexture = this.cubeSideMaskTexture; + copy.cubeShadingMode = this.cubeShadingMode; + copy.randomRotation = this.randomRotation; + copy.variantRotation = this.variantRotation; + copy.rotationYawPlacementOffset = this.rotationYawPlacementOffset; + copy.blockSoundSetIndex = this.blockSoundSetIndex; + copy.ambientSoundEventIndex = this.ambientSoundEventIndex; + copy.particles = this.particles != null ? Arrays.stream(this.particles).map(ex -> ex.clone()).toArray(ModelParticle[]::new) : null; + copy.blockParticleSetId = this.blockParticleSetId; + copy.blockBreakingDecalId = this.blockBreakingDecalId; + copy.particleColor = this.particleColor != null ? this.particleColor.clone() : null; + copy.light = this.light != null ? this.light.clone() : null; + copy.tint = this.tint != null ? this.tint.clone() : null; + copy.biomeTint = this.biomeTint != null ? this.biomeTint.clone() : null; + copy.group = this.group; + copy.transitionTexture = this.transitionTexture; + copy.transitionToGroups = this.transitionToGroups != null ? Arrays.copyOf(this.transitionToGroups, this.transitionToGroups.length) : null; + copy.movementSettings = this.movementSettings != null ? this.movementSettings.clone() : null; + copy.flags = this.flags != null ? this.flags.clone() : null; + copy.interactionHint = this.interactionHint; + copy.gathering = this.gathering != null ? this.gathering.clone() : null; + copy.placementSettings = this.placementSettings != null ? this.placementSettings.clone() : null; + copy.display = this.display != null ? this.display.clone() : null; + copy.rail = this.rail != null ? this.rail.clone() : null; + copy.ignoreSupportWhenPlaced = this.ignoreSupportWhenPlaced; + copy.interactions = this.interactions != null ? new HashMap<>(this.interactions) : null; + copy.states = this.states != null ? new HashMap<>(this.states) : null; + copy.transitionToTag = this.transitionToTag; + copy.tagIndexes = this.tagIndexes != null ? Arrays.copyOf(this.tagIndexes, this.tagIndexes.length) : null; + copy.bench = this.bench != null ? this.bench.clone() : null; + copy.connectedBlockRuleSet = this.connectedBlockRuleSet != null ? this.connectedBlockRuleSet.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockType other) + ? false + : Objects.equals(this.item, other.item) + && Objects.equals(this.name, other.name) + && this.unknown == other.unknown + && Objects.equals(this.drawType, other.drawType) + && Objects.equals(this.material, other.material) + && Objects.equals(this.opacity, other.opacity) + && Arrays.equals((Object[])this.shaderEffect, (Object[])other.shaderEffect) + && this.hitbox == other.hitbox + && this.interactionHitbox == other.interactionHitbox + && Objects.equals(this.model, other.model) + && Arrays.equals((Object[])this.modelTexture, (Object[])other.modelTexture) + && this.modelScale == other.modelScale + && Objects.equals(this.modelAnimation, other.modelAnimation) + && this.looping == other.looping + && this.maxSupportDistance == other.maxSupportDistance + && Objects.equals(this.blockSupportsRequiredFor, other.blockSupportsRequiredFor) + && Objects.equals(this.support, other.support) + && Objects.equals(this.supporting, other.supporting) + && this.requiresAlphaBlending == other.requiresAlphaBlending + && Arrays.equals((Object[])this.cubeTextures, (Object[])other.cubeTextures) + && Objects.equals(this.cubeSideMaskTexture, other.cubeSideMaskTexture) + && Objects.equals(this.cubeShadingMode, other.cubeShadingMode) + && Objects.equals(this.randomRotation, other.randomRotation) + && Objects.equals(this.variantRotation, other.variantRotation) + && Objects.equals(this.rotationYawPlacementOffset, other.rotationYawPlacementOffset) + && this.blockSoundSetIndex == other.blockSoundSetIndex + && this.ambientSoundEventIndex == other.ambientSoundEventIndex + && Arrays.equals((Object[])this.particles, (Object[])other.particles) + && Objects.equals(this.blockParticleSetId, other.blockParticleSetId) + && Objects.equals(this.blockBreakingDecalId, other.blockBreakingDecalId) + && Objects.equals(this.particleColor, other.particleColor) + && Objects.equals(this.light, other.light) + && Objects.equals(this.tint, other.tint) + && Objects.equals(this.biomeTint, other.biomeTint) + && this.group == other.group + && Objects.equals(this.transitionTexture, other.transitionTexture) + && Arrays.equals(this.transitionToGroups, other.transitionToGroups) + && Objects.equals(this.movementSettings, other.movementSettings) + && Objects.equals(this.flags, other.flags) + && Objects.equals(this.interactionHint, other.interactionHint) + && Objects.equals(this.gathering, other.gathering) + && Objects.equals(this.placementSettings, other.placementSettings) + && Objects.equals(this.display, other.display) + && Objects.equals(this.rail, other.rail) + && this.ignoreSupportWhenPlaced == other.ignoreSupportWhenPlaced + && Objects.equals(this.interactions, other.interactions) + && Objects.equals(this.states, other.states) + && this.transitionToTag == other.transitionToTag + && Arrays.equals(this.tagIndexes, other.tagIndexes) + && Objects.equals(this.bench, other.bench) + && Objects.equals(this.connectedBlockRuleSet, other.connectedBlockRuleSet); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.item); + result = 31 * result + Objects.hashCode(this.name); + result = 31 * result + Boolean.hashCode(this.unknown); + result = 31 * result + Objects.hashCode(this.drawType); + result = 31 * result + Objects.hashCode(this.material); + result = 31 * result + Objects.hashCode(this.opacity); + result = 31 * result + Arrays.hashCode((Object[])this.shaderEffect); + result = 31 * result + Integer.hashCode(this.hitbox); + result = 31 * result + Integer.hashCode(this.interactionHitbox); + result = 31 * result + Objects.hashCode(this.model); + result = 31 * result + Arrays.hashCode((Object[])this.modelTexture); + result = 31 * result + Float.hashCode(this.modelScale); + result = 31 * result + Objects.hashCode(this.modelAnimation); + result = 31 * result + Boolean.hashCode(this.looping); + result = 31 * result + Integer.hashCode(this.maxSupportDistance); + result = 31 * result + Objects.hashCode(this.blockSupportsRequiredFor); + result = 31 * result + Objects.hashCode(this.support); + result = 31 * result + Objects.hashCode(this.supporting); + result = 31 * result + Boolean.hashCode(this.requiresAlphaBlending); + result = 31 * result + Arrays.hashCode((Object[])this.cubeTextures); + result = 31 * result + Objects.hashCode(this.cubeSideMaskTexture); + result = 31 * result + Objects.hashCode(this.cubeShadingMode); + result = 31 * result + Objects.hashCode(this.randomRotation); + result = 31 * result + Objects.hashCode(this.variantRotation); + result = 31 * result + Objects.hashCode(this.rotationYawPlacementOffset); + result = 31 * result + Integer.hashCode(this.blockSoundSetIndex); + result = 31 * result + Integer.hashCode(this.ambientSoundEventIndex); + result = 31 * result + Arrays.hashCode((Object[])this.particles); + result = 31 * result + Objects.hashCode(this.blockParticleSetId); + result = 31 * result + Objects.hashCode(this.blockBreakingDecalId); + result = 31 * result + Objects.hashCode(this.particleColor); + result = 31 * result + Objects.hashCode(this.light); + result = 31 * result + Objects.hashCode(this.tint); + result = 31 * result + Objects.hashCode(this.biomeTint); + result = 31 * result + Integer.hashCode(this.group); + result = 31 * result + Objects.hashCode(this.transitionTexture); + result = 31 * result + Arrays.hashCode(this.transitionToGroups); + result = 31 * result + Objects.hashCode(this.movementSettings); + result = 31 * result + Objects.hashCode(this.flags); + result = 31 * result + Objects.hashCode(this.interactionHint); + result = 31 * result + Objects.hashCode(this.gathering); + result = 31 * result + Objects.hashCode(this.placementSettings); + result = 31 * result + Objects.hashCode(this.display); + result = 31 * result + Objects.hashCode(this.rail); + result = 31 * result + Boolean.hashCode(this.ignoreSupportWhenPlaced); + result = 31 * result + Objects.hashCode(this.interactions); + result = 31 * result + Objects.hashCode(this.states); + result = 31 * result + Integer.hashCode(this.transitionToTag); + result = 31 * result + Arrays.hashCode(this.tagIndexes); + result = 31 * result + Objects.hashCode(this.bench); + return 31 * result + Objects.hashCode(this.connectedBlockRuleSet); + } +} diff --git a/src/com/hypixel/hytale/protocol/BoolParamValue.java b/src/com/hypixel/hytale/protocol/BoolParamValue.java new file mode 100644 index 0000000..38f41e0 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BoolParamValue.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BoolParamValue extends ParamValue { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean value; + + public BoolParamValue() { + } + + public BoolParamValue(boolean value) { + this.value = value; + } + + public BoolParamValue(@Nonnull BoolParamValue other) { + this.value = other.value; + } + + @Nonnull + public static BoolParamValue deserialize(@Nonnull ByteBuf buf, int offset) { + BoolParamValue obj = new BoolParamValue(); + obj.value = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + buf.writeByte(this.value ? 1 : 0); + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public BoolParamValue clone() { + BoolParamValue copy = new BoolParamValue(); + copy.value = this.value; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof BoolParamValue other ? this.value == other.value : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.value); + } +} diff --git a/src/com/hypixel/hytale/protocol/BreakBlockInteraction.java b/src/com/hypixel/hytale/protocol/BreakBlockInteraction.java new file mode 100644 index 0000000..b10ba37 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BreakBlockInteraction.java @@ -0,0 +1,521 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BreakBlockInteraction extends SimpleBlockInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 21; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 41; + public static final int MAX_SIZE = 1677721600; + public boolean harvest; + + public BreakBlockInteraction() { + } + + public BreakBlockInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + boolean useLatestTarget, + boolean harvest + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.useLatestTarget = useLatestTarget; + this.harvest = harvest; + } + + public BreakBlockInteraction(@Nonnull BreakBlockInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.useLatestTarget = other.useLatestTarget; + this.harvest = other.harvest; + } + + @Nonnull + public static BreakBlockInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + BreakBlockInteraction obj = new BreakBlockInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.useLatestTarget = buf.getByte(offset + 19) != 0; + obj.harvest = buf.getByte(offset + 20) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 41 + buf.getIntLE(offset + 21); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 41 + buf.getIntLE(offset + 25); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 41 + buf.getIntLE(offset + 29); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 41 + buf.getIntLE(offset + 33); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 41 + buf.getIntLE(offset + 37); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 41; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 21); + int pos0 = offset + 41 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 25); + int pos1 = offset + 41 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 29); + int pos2 = offset + 41 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 33); + int pos3 = offset + 41 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 37); + int pos4 = offset + 41 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeByte(this.useLatestTarget ? 1 : 0); + buf.writeByte(this.harvest ? 1 : 0); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 41; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 41) { + return ValidationResult.error("Buffer too small: expected at least 41 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 21); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 41 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 25); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 41 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 29); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 41 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 33); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 41 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 37); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 41 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public BreakBlockInteraction clone() { + BreakBlockInteraction copy = new BreakBlockInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.useLatestTarget = this.useLatestTarget; + copy.harvest = this.harvest; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BreakBlockInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && this.useLatestTarget == other.useLatestTarget + && this.harvest == other.harvest; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Boolean.hashCode(this.useLatestTarget); + return 31 * result + Boolean.hashCode(this.harvest); + } +} diff --git a/src/com/hypixel/hytale/protocol/BuilderToolInteraction.java b/src/com/hypixel/hytale/protocol/BuilderToolInteraction.java new file mode 100644 index 0000000..6ba2524 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/BuilderToolInteraction.java @@ -0,0 +1,504 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderToolInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 19; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 39; + public static final int MAX_SIZE = 1677721600; + + public BuilderToolInteraction() { + } + + public BuilderToolInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + } + + public BuilderToolInteraction(@Nonnull BuilderToolInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + } + + @Nonnull + public static BuilderToolInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolInteraction obj = new BuilderToolInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 39 + buf.getIntLE(offset + 19); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 39 + buf.getIntLE(offset + 23); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 39 + buf.getIntLE(offset + 27); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 39 + buf.getIntLE(offset + 31); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 39 + buf.getIntLE(offset + 35); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 39; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 19); + int pos0 = offset + 39 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 23); + int pos1 = offset + 39 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 27); + int pos2 = offset + 39 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 31); + int pos3 = offset + 39 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 35); + int pos4 = offset + 39 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 39; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 39) { + return ValidationResult.error("Buffer too small: expected at least 39 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 19); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 39 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 23); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 39 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 27); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 39 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 31); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 39 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 35); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 39 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public BuilderToolInteraction clone() { + BuilderToolInteraction copy = new BuilderToolInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + return 31 * result + Integer.hashCode(this.failed); + } +} diff --git a/src/com/hypixel/hytale/protocol/CachedPacket.java b/src/com/hypixel/hytale/protocol/CachedPacket.java new file mode 100644 index 0000000..86859f6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CachedPacket.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import javax.annotation.Nonnull; + +public final class CachedPacket implements Packet, AutoCloseable { + private final Class packetType; + private final int packetId; + private final ByteBuf cachedBytes; + + private CachedPacket(Class packetType, int packetId, ByteBuf cachedBytes) { + this.packetType = packetType; + this.packetId = packetId; + this.cachedBytes = cachedBytes; + } + + public static CachedPacket cache(@Nonnull T packet) { + if (packet instanceof CachedPacket) { + throw new IllegalArgumentException("Cannot cache a CachedPacket"); + } else { + ByteBuf buf = Unpooled.buffer(); + packet.serialize(buf); + return new CachedPacket<>((Class)packet.getClass(), packet.getId(), buf); + } + } + + @Override + public int getId() { + return this.packetId; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + if (this.cachedBytes.refCnt() <= 0) { + throw new IllegalStateException("CachedPacket buffer was released before serialization completed"); + } else { + buf.writeBytes(this.cachedBytes, this.cachedBytes.readerIndex(), this.cachedBytes.readableBytes()); + } + } + + @Override + public int computeSize() { + return this.cachedBytes.readableBytes(); + } + + public Class getPacketType() { + return this.packetType; + } + + public int getCachedSize() { + return this.cachedBytes.readableBytes(); + } + + @Override + public void close() { + if (this.cachedBytes.refCnt() > 0) { + this.cachedBytes.release(); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/CalculationType.java b/src/com/hypixel/hytale/protocol/CalculationType.java new file mode 100644 index 0000000..f62cdf2 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CalculationType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum CalculationType { + Additive(0), + Multiplicative(1); + + public static final CalculationType[] VALUES = values(); + private final int value; + + private CalculationType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static CalculationType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("CalculationType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/CameraActionType.java b/src/com/hypixel/hytale/protocol/CameraActionType.java new file mode 100644 index 0000000..d68122a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CameraActionType.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum CameraActionType { + ForcePerspective(0), + Orbit(1), + Transition(2); + + public static final CameraActionType[] VALUES = values(); + private final int value; + + private CameraActionType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static CameraActionType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("CameraActionType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/CameraAxis.java b/src/com/hypixel/hytale/protocol/CameraAxis.java new file mode 100644 index 0000000..2eb6c9c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CameraAxis.java @@ -0,0 +1,173 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CameraAxis { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 4096014; + @Nullable + public Rangef angleRange; + @Nullable + public CameraNode[] targetNodes; + + public CameraAxis() { + } + + public CameraAxis(@Nullable Rangef angleRange, @Nullable CameraNode[] targetNodes) { + this.angleRange = angleRange; + this.targetNodes = targetNodes; + } + + public CameraAxis(@Nonnull CameraAxis other) { + this.angleRange = other.angleRange; + this.targetNodes = other.targetNodes; + } + + @Nonnull + public static CameraAxis deserialize(@Nonnull ByteBuf buf, int offset) { + CameraAxis obj = new CameraAxis(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.angleRange = Rangef.deserialize(buf, offset + 1); + } + + int pos = offset + 9; + if ((nullBits & 2) != 0) { + int targetNodesCount = VarInt.peek(buf, pos); + if (targetNodesCount < 0) { + throw ProtocolException.negativeLength("TargetNodes", targetNodesCount); + } + + if (targetNodesCount > 4096000) { + throw ProtocolException.arrayTooLong("TargetNodes", targetNodesCount, 4096000); + } + + int targetNodesVarLen = VarInt.size(targetNodesCount); + if (pos + targetNodesVarLen + targetNodesCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("TargetNodes", pos + targetNodesVarLen + targetNodesCount * 1, buf.readableBytes()); + } + + pos += targetNodesVarLen; + obj.targetNodes = new CameraNode[targetNodesCount]; + + for (int i = 0; i < targetNodesCount; i++) { + obj.targetNodes[i] = CameraNode.fromValue(buf.getByte(pos)); + pos++; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 9; + if ((nullBits & 2) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 1; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.angleRange != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.targetNodes != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + if (this.angleRange != null) { + this.angleRange.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.targetNodes != null) { + if (this.targetNodes.length > 4096000) { + throw ProtocolException.arrayTooLong("TargetNodes", this.targetNodes.length, 4096000); + } + + VarInt.write(buf, this.targetNodes.length); + + for (CameraNode item : this.targetNodes) { + buf.writeByte(item.getValue()); + } + } + } + + public int computeSize() { + int size = 9; + if (this.targetNodes != null) { + size += VarInt.size(this.targetNodes.length) + this.targetNodes.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 9; + if ((nullBits & 2) != 0) { + int targetNodesCount = VarInt.peek(buffer, pos); + if (targetNodesCount < 0) { + return ValidationResult.error("Invalid array count for TargetNodes"); + } + + if (targetNodesCount > 4096000) { + return ValidationResult.error("TargetNodes exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += targetNodesCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading TargetNodes"); + } + } + + return ValidationResult.OK; + } + } + + public CameraAxis clone() { + CameraAxis copy = new CameraAxis(); + copy.angleRange = this.angleRange != null ? this.angleRange.clone() : null; + copy.targetNodes = this.targetNodes != null ? Arrays.copyOf(this.targetNodes, this.targetNodes.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CameraAxis other) + ? false + : Objects.equals(this.angleRange, other.angleRange) && Arrays.equals((Object[])this.targetNodes, (Object[])other.targetNodes); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.angleRange); + return 31 * result + Arrays.hashCode((Object[])this.targetNodes); + } +} diff --git a/src/com/hypixel/hytale/protocol/CameraInteraction.java b/src/com/hypixel/hytale/protocol/CameraInteraction.java new file mode 100644 index 0000000..53d9255 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CameraInteraction.java @@ -0,0 +1,542 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CameraInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 26; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 46; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public CameraActionType cameraAction = CameraActionType.ForcePerspective; + @Nonnull + public CameraPerspectiveType cameraPerspective = CameraPerspectiveType.First; + public boolean cameraPersist; + public float cameraInteractionTime; + + public CameraInteraction() { + } + + public CameraInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + @Nonnull CameraActionType cameraAction, + @Nonnull CameraPerspectiveType cameraPerspective, + boolean cameraPersist, + float cameraInteractionTime + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.cameraAction = cameraAction; + this.cameraPerspective = cameraPerspective; + this.cameraPersist = cameraPersist; + this.cameraInteractionTime = cameraInteractionTime; + } + + public CameraInteraction(@Nonnull CameraInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.cameraAction = other.cameraAction; + this.cameraPerspective = other.cameraPerspective; + this.cameraPersist = other.cameraPersist; + this.cameraInteractionTime = other.cameraInteractionTime; + } + + @Nonnull + public static CameraInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + CameraInteraction obj = new CameraInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.cameraAction = CameraActionType.fromValue(buf.getByte(offset + 19)); + obj.cameraPerspective = CameraPerspectiveType.fromValue(buf.getByte(offset + 20)); + obj.cameraPersist = buf.getByte(offset + 21) != 0; + obj.cameraInteractionTime = buf.getFloatLE(offset + 22); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 46 + buf.getIntLE(offset + 26); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 46 + buf.getIntLE(offset + 30); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 46 + buf.getIntLE(offset + 34); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 46 + buf.getIntLE(offset + 38); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 46 + buf.getIntLE(offset + 42); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 46; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 26); + int pos0 = offset + 46 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 30); + int pos1 = offset + 46 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 34); + int pos2 = offset + 46 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 38); + int pos3 = offset + 46 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 42); + int pos4 = offset + 46 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeByte(this.cameraAction.getValue()); + buf.writeByte(this.cameraPerspective.getValue()); + buf.writeByte(this.cameraPersist ? 1 : 0); + buf.writeFloatLE(this.cameraInteractionTime); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 46; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 46) { + return ValidationResult.error("Buffer too small: expected at least 46 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 26); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 46 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 30); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 46 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 34); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 46 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 38); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 46 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 42); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 46 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public CameraInteraction clone() { + CameraInteraction copy = new CameraInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.cameraAction = this.cameraAction; + copy.cameraPerspective = this.cameraPerspective; + copy.cameraPersist = this.cameraPersist; + copy.cameraInteractionTime = this.cameraInteractionTime; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CameraInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && Objects.equals(this.cameraAction, other.cameraAction) + && Objects.equals(this.cameraPerspective, other.cameraPerspective) + && this.cameraPersist == other.cameraPersist + && this.cameraInteractionTime == other.cameraInteractionTime; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Objects.hashCode(this.cameraAction); + result = 31 * result + Objects.hashCode(this.cameraPerspective); + result = 31 * result + Boolean.hashCode(this.cameraPersist); + return 31 * result + Float.hashCode(this.cameraInteractionTime); + } +} diff --git a/src/com/hypixel/hytale/protocol/CameraNode.java b/src/com/hypixel/hytale/protocol/CameraNode.java new file mode 100644 index 0000000..7450dde --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CameraNode.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum CameraNode { + None(0), + Head(1), + LShoulder(2), + RShoulder(3), + Belly(4); + + public static final CameraNode[] VALUES = values(); + private final int value; + + private CameraNode(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static CameraNode fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("CameraNode", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/CameraPerspectiveType.java b/src/com/hypixel/hytale/protocol/CameraPerspectiveType.java new file mode 100644 index 0000000..853dae3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CameraPerspectiveType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum CameraPerspectiveType { + First(0), + Third(1); + + public static final CameraPerspectiveType[] VALUES = values(); + private final int value; + + private CameraPerspectiveType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static CameraPerspectiveType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("CameraPerspectiveType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/CameraSettings.java b/src/com/hypixel/hytale/protocol/CameraSettings.java new file mode 100644 index 0000000..eb33b73 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CameraSettings.java @@ -0,0 +1,207 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CameraSettings { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 13; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 21; + public static final int MAX_SIZE = 8192049; + @Nullable + public Vector3f positionOffset; + @Nullable + public CameraAxis yaw; + @Nullable + public CameraAxis pitch; + + public CameraSettings() { + } + + public CameraSettings(@Nullable Vector3f positionOffset, @Nullable CameraAxis yaw, @Nullable CameraAxis pitch) { + this.positionOffset = positionOffset; + this.yaw = yaw; + this.pitch = pitch; + } + + public CameraSettings(@Nonnull CameraSettings other) { + this.positionOffset = other.positionOffset; + this.yaw = other.yaw; + this.pitch = other.pitch; + } + + @Nonnull + public static CameraSettings deserialize(@Nonnull ByteBuf buf, int offset) { + CameraSettings obj = new CameraSettings(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.positionOffset = Vector3f.deserialize(buf, offset + 1); + } + + if ((nullBits & 2) != 0) { + int varPos0 = offset + 21 + buf.getIntLE(offset + 13); + obj.yaw = CameraAxis.deserialize(buf, varPos0); + } + + if ((nullBits & 4) != 0) { + int varPos1 = offset + 21 + buf.getIntLE(offset + 17); + obj.pitch = CameraAxis.deserialize(buf, varPos1); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 21; + if ((nullBits & 2) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 13); + int pos0 = offset + 21 + fieldOffset0; + pos0 += CameraAxis.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 17); + int pos1 = offset + 21 + fieldOffset1; + pos1 += CameraAxis.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.positionOffset != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.yaw != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.pitch != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + if (this.positionOffset != null) { + this.positionOffset.serialize(buf); + } else { + buf.writeZero(12); + } + + int yawOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int pitchOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.yaw != null) { + buf.setIntLE(yawOffsetSlot, buf.writerIndex() - varBlockStart); + this.yaw.serialize(buf); + } else { + buf.setIntLE(yawOffsetSlot, -1); + } + + if (this.pitch != null) { + buf.setIntLE(pitchOffsetSlot, buf.writerIndex() - varBlockStart); + this.pitch.serialize(buf); + } else { + buf.setIntLE(pitchOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 21; + if (this.yaw != null) { + size += this.yaw.computeSize(); + } + + if (this.pitch != null) { + size += this.pitch.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 21) { + return ValidationResult.error("Buffer too small: expected at least 21 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 2) != 0) { + int yawOffset = buffer.getIntLE(offset + 13); + if (yawOffset < 0) { + return ValidationResult.error("Invalid offset for Yaw"); + } + + int pos = offset + 21 + yawOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Yaw"); + } + + ValidationResult yawResult = CameraAxis.validateStructure(buffer, pos); + if (!yawResult.isValid()) { + return ValidationResult.error("Invalid Yaw: " + yawResult.error()); + } + + pos += CameraAxis.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 4) != 0) { + int pitchOffset = buffer.getIntLE(offset + 17); + if (pitchOffset < 0) { + return ValidationResult.error("Invalid offset for Pitch"); + } + + int posx = offset + 21 + pitchOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Pitch"); + } + + ValidationResult pitchResult = CameraAxis.validateStructure(buffer, posx); + if (!pitchResult.isValid()) { + return ValidationResult.error("Invalid Pitch: " + pitchResult.error()); + } + + posx += CameraAxis.computeBytesConsumed(buffer, posx); + } + + return ValidationResult.OK; + } + } + + public CameraSettings clone() { + CameraSettings copy = new CameraSettings(); + copy.positionOffset = this.positionOffset != null ? this.positionOffset.clone() : null; + copy.yaw = this.yaw != null ? this.yaw.clone() : null; + copy.pitch = this.pitch != null ? this.pitch.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CameraSettings other) + ? false + : Objects.equals(this.positionOffset, other.positionOffset) && Objects.equals(this.yaw, other.yaw) && Objects.equals(this.pitch, other.pitch); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.positionOffset, this.yaw, this.pitch); + } +} diff --git a/src/com/hypixel/hytale/protocol/CameraShake.java b/src/com/hypixel/hytale/protocol/CameraShake.java new file mode 100644 index 0000000..9937ce1 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CameraShake.java @@ -0,0 +1,188 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CameraShake { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 1130496177; + @Nullable + public CameraShakeConfig firstPerson; + @Nullable + public CameraShakeConfig thirdPerson; + + public CameraShake() { + } + + public CameraShake(@Nullable CameraShakeConfig firstPerson, @Nullable CameraShakeConfig thirdPerson) { + this.firstPerson = firstPerson; + this.thirdPerson = thirdPerson; + } + + public CameraShake(@Nonnull CameraShake other) { + this.firstPerson = other.firstPerson; + this.thirdPerson = other.thirdPerson; + } + + @Nonnull + public static CameraShake deserialize(@Nonnull ByteBuf buf, int offset) { + CameraShake obj = new CameraShake(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + obj.firstPerson = CameraShakeConfig.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + obj.thirdPerson = CameraShakeConfig.deserialize(buf, varPos1); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + pos0 += CameraShakeConfig.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + pos1 += CameraShakeConfig.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.firstPerson != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.thirdPerson != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int firstPersonOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int thirdPersonOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.firstPerson != null) { + buf.setIntLE(firstPersonOffsetSlot, buf.writerIndex() - varBlockStart); + this.firstPerson.serialize(buf); + } else { + buf.setIntLE(firstPersonOffsetSlot, -1); + } + + if (this.thirdPerson != null) { + buf.setIntLE(thirdPersonOffsetSlot, buf.writerIndex() - varBlockStart); + this.thirdPerson.serialize(buf); + } else { + buf.setIntLE(thirdPersonOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 9; + if (this.firstPerson != null) { + size += this.firstPerson.computeSize(); + } + + if (this.thirdPerson != null) { + size += this.thirdPerson.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int firstPersonOffset = buffer.getIntLE(offset + 1); + if (firstPersonOffset < 0) { + return ValidationResult.error("Invalid offset for FirstPerson"); + } + + int pos = offset + 9 + firstPersonOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FirstPerson"); + } + + ValidationResult firstPersonResult = CameraShakeConfig.validateStructure(buffer, pos); + if (!firstPersonResult.isValid()) { + return ValidationResult.error("Invalid FirstPerson: " + firstPersonResult.error()); + } + + pos += CameraShakeConfig.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int thirdPersonOffset = buffer.getIntLE(offset + 5); + if (thirdPersonOffset < 0) { + return ValidationResult.error("Invalid offset for ThirdPerson"); + } + + int posx = offset + 9 + thirdPersonOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ThirdPerson"); + } + + ValidationResult thirdPersonResult = CameraShakeConfig.validateStructure(buffer, posx); + if (!thirdPersonResult.isValid()) { + return ValidationResult.error("Invalid ThirdPerson: " + thirdPersonResult.error()); + } + + posx += CameraShakeConfig.computeBytesConsumed(buffer, posx); + } + + return ValidationResult.OK; + } + } + + public CameraShake clone() { + CameraShake copy = new CameraShake(); + copy.firstPerson = this.firstPerson != null ? this.firstPerson.clone() : null; + copy.thirdPerson = this.thirdPerson != null ? this.thirdPerson.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CameraShake other) + ? false + : Objects.equals(this.firstPerson, other.firstPerson) && Objects.equals(this.thirdPerson, other.thirdPerson); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.firstPerson, this.thirdPerson); + } +} diff --git a/src/com/hypixel/hytale/protocol/CameraShakeConfig.java b/src/com/hypixel/hytale/protocol/CameraShakeConfig.java new file mode 100644 index 0000000..a8fdc90 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CameraShakeConfig.java @@ -0,0 +1,258 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CameraShakeConfig { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 20; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 28; + public static final int MAX_SIZE = 565248084; + public float duration; + public float startTime; + public boolean continuous; + @Nullable + public EasingConfig easeIn; + @Nullable + public EasingConfig easeOut; + @Nullable + public OffsetNoise offset; + @Nullable + public RotationNoise rotation; + + public CameraShakeConfig() { + } + + public CameraShakeConfig( + float duration, + float startTime, + boolean continuous, + @Nullable EasingConfig easeIn, + @Nullable EasingConfig easeOut, + @Nullable OffsetNoise offset, + @Nullable RotationNoise rotation + ) { + this.duration = duration; + this.startTime = startTime; + this.continuous = continuous; + this.easeIn = easeIn; + this.easeOut = easeOut; + this.offset = offset; + this.rotation = rotation; + } + + public CameraShakeConfig(@Nonnull CameraShakeConfig other) { + this.duration = other.duration; + this.startTime = other.startTime; + this.continuous = other.continuous; + this.easeIn = other.easeIn; + this.easeOut = other.easeOut; + this.offset = other.offset; + this.rotation = other.rotation; + } + + @Nonnull + public static CameraShakeConfig deserialize(@Nonnull ByteBuf buf, int offset) { + CameraShakeConfig obj = new CameraShakeConfig(); + byte nullBits = buf.getByte(offset); + obj.duration = buf.getFloatLE(offset + 1); + obj.startTime = buf.getFloatLE(offset + 5); + obj.continuous = buf.getByte(offset + 9) != 0; + if ((nullBits & 1) != 0) { + obj.easeIn = EasingConfig.deserialize(buf, offset + 10); + } + + if ((nullBits & 2) != 0) { + obj.easeOut = EasingConfig.deserialize(buf, offset + 15); + } + + if ((nullBits & 4) != 0) { + int varPos0 = offset + 28 + buf.getIntLE(offset + 20); + obj.offset = OffsetNoise.deserialize(buf, varPos0); + } + + if ((nullBits & 8) != 0) { + int varPos1 = offset + 28 + buf.getIntLE(offset + 24); + obj.rotation = RotationNoise.deserialize(buf, varPos1); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 28; + if ((nullBits & 4) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 20); + int pos0 = offset + 28 + fieldOffset0; + pos0 += OffsetNoise.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 24); + int pos1 = offset + 28 + fieldOffset1; + pos1 += RotationNoise.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.easeIn != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.easeOut != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.offset != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.rotation != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.duration); + buf.writeFloatLE(this.startTime); + buf.writeByte(this.continuous ? 1 : 0); + if (this.easeIn != null) { + this.easeIn.serialize(buf); + } else { + buf.writeZero(5); + } + + if (this.easeOut != null) { + this.easeOut.serialize(buf); + } else { + buf.writeZero(5); + } + + int offsetOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rotationOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.offset != null) { + buf.setIntLE(offsetOffsetSlot, buf.writerIndex() - varBlockStart); + this.offset.serialize(buf); + } else { + buf.setIntLE(offsetOffsetSlot, -1); + } + + if (this.rotation != null) { + buf.setIntLE(rotationOffsetSlot, buf.writerIndex() - varBlockStart); + this.rotation.serialize(buf); + } else { + buf.setIntLE(rotationOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 28; + if (this.offset != null) { + size += this.offset.computeSize(); + } + + if (this.rotation != null) { + size += this.rotation.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 28) { + return ValidationResult.error("Buffer too small: expected at least 28 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 4) != 0) { + int offsetOffset = buffer.getIntLE(offset + 20); + if (offsetOffset < 0) { + return ValidationResult.error("Invalid offset for Offset"); + } + + int pos = offset + 28 + offsetOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Offset"); + } + + ValidationResult offsetResult = OffsetNoise.validateStructure(buffer, pos); + if (!offsetResult.isValid()) { + return ValidationResult.error("Invalid Offset: " + offsetResult.error()); + } + + pos += OffsetNoise.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 8) != 0) { + int rotationOffset = buffer.getIntLE(offset + 24); + if (rotationOffset < 0) { + return ValidationResult.error("Invalid offset for Rotation"); + } + + int posx = offset + 28 + rotationOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rotation"); + } + + ValidationResult rotationResult = RotationNoise.validateStructure(buffer, posx); + if (!rotationResult.isValid()) { + return ValidationResult.error("Invalid Rotation: " + rotationResult.error()); + } + + posx += RotationNoise.computeBytesConsumed(buffer, posx); + } + + return ValidationResult.OK; + } + } + + public CameraShakeConfig clone() { + CameraShakeConfig copy = new CameraShakeConfig(); + copy.duration = this.duration; + copy.startTime = this.startTime; + copy.continuous = this.continuous; + copy.easeIn = this.easeIn != null ? this.easeIn.clone() : null; + copy.easeOut = this.easeOut != null ? this.easeOut.clone() : null; + copy.offset = this.offset != null ? this.offset.clone() : null; + copy.rotation = this.rotation != null ? this.rotation.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CameraShakeConfig other) + ? false + : this.duration == other.duration + && this.startTime == other.startTime + && this.continuous == other.continuous + && Objects.equals(this.easeIn, other.easeIn) + && Objects.equals(this.easeOut, other.easeOut) + && Objects.equals(this.offset, other.offset) + && Objects.equals(this.rotation, other.rotation); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.duration, this.startTime, this.continuous, this.easeIn, this.easeOut, this.offset, this.rotation); + } +} diff --git a/src/com/hypixel/hytale/protocol/CanMoveType.java b/src/com/hypixel/hytale/protocol/CanMoveType.java new file mode 100644 index 0000000..9ada06b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CanMoveType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum CanMoveType { + AttachedToLocalPlayer(0), + Always(1); + + public static final CanMoveType[] VALUES = values(); + private final int value; + + private CanMoveType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static CanMoveType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("CanMoveType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/CancelChainInteraction.java b/src/com/hypixel/hytale/protocol/CancelChainInteraction.java new file mode 100644 index 0000000..317c1d4 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CancelChainInteraction.java @@ -0,0 +1,581 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CancelChainInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 19; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 43; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String chainId; + + public CancelChainInteraction() { + } + + public CancelChainInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + @Nullable String chainId + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.chainId = chainId; + } + + public CancelChainInteraction(@Nonnull CancelChainInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.chainId = other.chainId; + } + + @Nonnull + public static CancelChainInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + CancelChainInteraction obj = new CancelChainInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 43 + buf.getIntLE(offset + 19); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 43 + buf.getIntLE(offset + 23); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 43 + buf.getIntLE(offset + 27); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 43 + buf.getIntLE(offset + 31); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 43 + buf.getIntLE(offset + 35); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 43 + buf.getIntLE(offset + 39); + int chainIdLen = VarInt.peek(buf, varPos5); + if (chainIdLen < 0) { + throw ProtocolException.negativeLength("ChainId", chainIdLen); + } + + if (chainIdLen > 4096000) { + throw ProtocolException.stringTooLong("ChainId", chainIdLen, 4096000); + } + + obj.chainId = PacketIO.readVarString(buf, varPos5, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 43; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 19); + int pos0 = offset + 43 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 23); + int pos1 = offset + 43 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 27); + int pos2 = offset + 43 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 31); + int pos3 = offset + 43 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 35); + int pos4 = offset + 43 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 39); + int pos5 = offset + 43 + fieldOffset5; + int sl = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + sl; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.chainId != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int chainIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.chainId != null) { + buf.setIntLE(chainIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.chainId, 4096000); + } else { + buf.setIntLE(chainIdOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 43; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.chainId != null) { + size += PacketIO.stringSize(this.chainId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 43) { + return ValidationResult.error("Buffer too small: expected at least 43 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 19); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 43 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 23); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 43 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 27); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 43 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 31); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 43 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 35); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 43 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int chainIdOffset = buffer.getIntLE(offset + 39); + if (chainIdOffset < 0) { + return ValidationResult.error("Invalid offset for ChainId"); + } + + int posxxxxx = offset + 43 + chainIdOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ChainId"); + } + + int chainIdLen = VarInt.peek(buffer, posxxxxx); + if (chainIdLen < 0) { + return ValidationResult.error("Invalid string length for ChainId"); + } + + if (chainIdLen > 4096000) { + return ValidationResult.error("ChainId exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += chainIdLen; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ChainId"); + } + } + + return ValidationResult.OK; + } + } + + public CancelChainInteraction clone() { + CancelChainInteraction copy = new CancelChainInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.chainId = this.chainId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CancelChainInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && Objects.equals(this.chainId, other.chainId); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + return 31 * result + Objects.hashCode(this.chainId); + } +} diff --git a/src/com/hypixel/hytale/protocol/ChainFlagInteraction.java b/src/com/hypixel/hytale/protocol/ChainFlagInteraction.java new file mode 100644 index 0000000..337ffa6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ChainFlagInteraction.java @@ -0,0 +1,657 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChainFlagInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 19; + public static final int VARIABLE_FIELD_COUNT = 7; + public static final int VARIABLE_BLOCK_START = 47; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String chainId; + @Nullable + public String flag; + + public ChainFlagInteraction() { + } + + public ChainFlagInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + @Nullable String chainId, + @Nullable String flag + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.chainId = chainId; + this.flag = flag; + } + + public ChainFlagInteraction(@Nonnull ChainFlagInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.chainId = other.chainId; + this.flag = other.flag; + } + + @Nonnull + public static ChainFlagInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + ChainFlagInteraction obj = new ChainFlagInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 47 + buf.getIntLE(offset + 19); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 47 + buf.getIntLE(offset + 23); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 47 + buf.getIntLE(offset + 27); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 47 + buf.getIntLE(offset + 31); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 47 + buf.getIntLE(offset + 35); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 47 + buf.getIntLE(offset + 39); + int chainIdLen = VarInt.peek(buf, varPos5); + if (chainIdLen < 0) { + throw ProtocolException.negativeLength("ChainId", chainIdLen); + } + + if (chainIdLen > 4096000) { + throw ProtocolException.stringTooLong("ChainId", chainIdLen, 4096000); + } + + obj.chainId = PacketIO.readVarString(buf, varPos5, PacketIO.UTF8); + } + + if ((nullBits & 64) != 0) { + int varPos6 = offset + 47 + buf.getIntLE(offset + 43); + int flagLen = VarInt.peek(buf, varPos6); + if (flagLen < 0) { + throw ProtocolException.negativeLength("Flag", flagLen); + } + + if (flagLen > 4096000) { + throw ProtocolException.stringTooLong("Flag", flagLen, 4096000); + } + + obj.flag = PacketIO.readVarString(buf, varPos6, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 47; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 19); + int pos0 = offset + 47 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 23); + int pos1 = offset + 47 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 27); + int pos2 = offset + 47 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 31); + int pos3 = offset + 47 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 35); + int pos4 = offset + 47 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 39); + int pos5 = offset + 47 + fieldOffset5; + int sl = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + sl; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits & 64) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 43); + int pos6 = offset + 47 + fieldOffset6; + int sl = VarInt.peek(buf, pos6); + pos6 += VarInt.length(buf, pos6) + sl; + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.chainId != null) { + nullBits = (byte)(nullBits | 32); + } + + if (this.flag != null) { + nullBits = (byte)(nullBits | 64); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int chainIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int flagOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.chainId != null) { + buf.setIntLE(chainIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.chainId, 4096000); + } else { + buf.setIntLE(chainIdOffsetSlot, -1); + } + + if (this.flag != null) { + buf.setIntLE(flagOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.flag, 4096000); + } else { + buf.setIntLE(flagOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 47; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.chainId != null) { + size += PacketIO.stringSize(this.chainId); + } + + if (this.flag != null) { + size += PacketIO.stringSize(this.flag); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 47) { + return ValidationResult.error("Buffer too small: expected at least 47 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 19); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 47 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 23); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 47 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 27); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 47 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 31); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 47 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 35); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 47 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int chainIdOffset = buffer.getIntLE(offset + 39); + if (chainIdOffset < 0) { + return ValidationResult.error("Invalid offset for ChainId"); + } + + int posxxxxx = offset + 47 + chainIdOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ChainId"); + } + + int chainIdLen = VarInt.peek(buffer, posxxxxx); + if (chainIdLen < 0) { + return ValidationResult.error("Invalid string length for ChainId"); + } + + if (chainIdLen > 4096000) { + return ValidationResult.error("ChainId exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += chainIdLen; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ChainId"); + } + } + + if ((nullBits & 64) != 0) { + int flagOffset = buffer.getIntLE(offset + 43); + if (flagOffset < 0) { + return ValidationResult.error("Invalid offset for Flag"); + } + + int posxxxxxx = offset + 47 + flagOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Flag"); + } + + int flagLen = VarInt.peek(buffer, posxxxxxx); + if (flagLen < 0) { + return ValidationResult.error("Invalid string length for Flag"); + } + + if (flagLen > 4096000) { + return ValidationResult.error("Flag exceeds max length 4096000"); + } + + posxxxxxx += VarInt.length(buffer, posxxxxxx); + posxxxxxx += flagLen; + if (posxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Flag"); + } + } + + return ValidationResult.OK; + } + } + + public ChainFlagInteraction clone() { + ChainFlagInteraction copy = new ChainFlagInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.chainId = this.chainId; + copy.flag = this.flag; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ChainFlagInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && Objects.equals(this.chainId, other.chainId) + && Objects.equals(this.flag, other.flag); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Objects.hashCode(this.chainId); + return 31 * result + Objects.hashCode(this.flag); + } +} diff --git a/src/com/hypixel/hytale/protocol/ChainingInteraction.java b/src/com/hypixel/hytale/protocol/ChainingInteraction.java new file mode 100644 index 0000000..84c2d4a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ChainingInteraction.java @@ -0,0 +1,805 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChainingInteraction extends Interaction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 15; + public static final int VARIABLE_FIELD_COUNT = 8; + public static final int VARIABLE_BLOCK_START = 47; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String chainId; + public float chainingAllowance; + @Nullable + public int[] chainingNext; + @Nullable + public Map flags; + + public ChainingInteraction() { + } + + public ChainingInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + @Nullable String chainId, + float chainingAllowance, + @Nullable int[] chainingNext, + @Nullable Map flags + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.chainId = chainId; + this.chainingAllowance = chainingAllowance; + this.chainingNext = chainingNext; + this.flags = flags; + } + + public ChainingInteraction(@Nonnull ChainingInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.chainId = other.chainId; + this.chainingAllowance = other.chainingAllowance; + this.chainingNext = other.chainingNext; + this.flags = other.flags; + } + + @Nonnull + public static ChainingInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + ChainingInteraction obj = new ChainingInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.chainingAllowance = buf.getFloatLE(offset + 11); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 47 + buf.getIntLE(offset + 15); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 47 + buf.getIntLE(offset + 19); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 47 + buf.getIntLE(offset + 23); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 47 + buf.getIntLE(offset + 27); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 47 + buf.getIntLE(offset + 31); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 47 + buf.getIntLE(offset + 35); + int chainIdLen = VarInt.peek(buf, varPos5); + if (chainIdLen < 0) { + throw ProtocolException.negativeLength("ChainId", chainIdLen); + } + + if (chainIdLen > 4096000) { + throw ProtocolException.stringTooLong("ChainId", chainIdLen, 4096000); + } + + obj.chainId = PacketIO.readVarString(buf, varPos5, PacketIO.UTF8); + } + + if ((nullBits & 64) != 0) { + int varPos6 = offset + 47 + buf.getIntLE(offset + 39); + int chainingNextCount = VarInt.peek(buf, varPos6); + if (chainingNextCount < 0) { + throw ProtocolException.negativeLength("ChainingNext", chainingNextCount); + } + + if (chainingNextCount > 4096000) { + throw ProtocolException.arrayTooLong("ChainingNext", chainingNextCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos6); + if (varPos6 + varIntLen + chainingNextCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("ChainingNext", varPos6 + varIntLen + chainingNextCount * 4, buf.readableBytes()); + } + + obj.chainingNext = new int[chainingNextCount]; + + for (int ix = 0; ix < chainingNextCount; ix++) { + obj.chainingNext[ix] = buf.getIntLE(varPos6 + varIntLen + ix * 4); + } + } + + if ((nullBits & 128) != 0) { + int varPos7 = offset + 47 + buf.getIntLE(offset + 43); + int flagsCount = VarInt.peek(buf, varPos7); + if (flagsCount < 0) { + throw ProtocolException.negativeLength("Flags", flagsCount); + } + + if (flagsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Flags", flagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos7); + obj.flags = new HashMap<>(flagsCount); + int dictPos = varPos7 + varIntLen; + + for (int ix = 0; ix < flagsCount; ix++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + int val = buf.getIntLE(dictPos); + dictPos += 4; + if (obj.flags.put(key, val) != null) { + throw ProtocolException.duplicateKey("flags", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 47; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 15); + int pos0 = offset + 47 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 19); + int pos1 = offset + 47 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 23); + int pos2 = offset + 47 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 27); + int pos3 = offset + 47 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 31); + int pos4 = offset + 47 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 35); + int pos5 = offset + 47 + fieldOffset5; + int sl = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + sl; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits & 64) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 39); + int pos6 = offset + 47 + fieldOffset6; + int arrLen = VarInt.peek(buf, pos6); + pos6 += VarInt.length(buf, pos6) + arrLen * 4; + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + if ((nullBits & 128) != 0) { + int fieldOffset7 = buf.getIntLE(offset + 43); + int pos7 = offset + 47 + fieldOffset7; + int dictLen = VarInt.peek(buf, pos7); + pos7 += VarInt.length(buf, pos7); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos7); + pos7 += VarInt.length(buf, pos7) + sl; + pos7 += 4; + } + + if (pos7 - offset > maxEnd) { + maxEnd = pos7 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.chainId != null) { + nullBits = (byte)(nullBits | 32); + } + + if (this.chainingNext != null) { + nullBits = (byte)(nullBits | 64); + } + + if (this.flags != null) { + nullBits = (byte)(nullBits | 128); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeFloatLE(this.chainingAllowance); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int chainIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int chainingNextOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int flagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.chainId != null) { + buf.setIntLE(chainIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.chainId, 4096000); + } else { + buf.setIntLE(chainIdOffsetSlot, -1); + } + + if (this.chainingNext != null) { + buf.setIntLE(chainingNextOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.chainingNext.length > 4096000) { + throw ProtocolException.arrayTooLong("ChainingNext", this.chainingNext.length, 4096000); + } + + VarInt.write(buf, this.chainingNext.length); + + for (int item : this.chainingNext) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(chainingNextOffsetSlot, -1); + } + + if (this.flags != null) { + buf.setIntLE(flagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.flags.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Flags", this.flags.size(), 4096000); + } + + VarInt.write(buf, this.flags.size()); + + for (Entry e : this.flags.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + buf.writeIntLE(e.getValue()); + } + } else { + buf.setIntLE(flagsOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 47; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.chainId != null) { + size += PacketIO.stringSize(this.chainId); + } + + if (this.chainingNext != null) { + size += VarInt.size(this.chainingNext.length) + this.chainingNext.length * 4; + } + + if (this.flags != null) { + int flagsSize = 0; + + for (Entry kvp : this.flags.entrySet()) { + flagsSize += PacketIO.stringSize(kvp.getKey()) + 4; + } + + size += VarInt.size(this.flags.size()) + flagsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 47) { + return ValidationResult.error("Buffer too small: expected at least 47 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 15); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 47 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 19); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 47 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 23); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 47 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 27); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 47 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 31); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 47 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int chainIdOffset = buffer.getIntLE(offset + 35); + if (chainIdOffset < 0) { + return ValidationResult.error("Invalid offset for ChainId"); + } + + int posxxxxx = offset + 47 + chainIdOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ChainId"); + } + + int chainIdLen = VarInt.peek(buffer, posxxxxx); + if (chainIdLen < 0) { + return ValidationResult.error("Invalid string length for ChainId"); + } + + if (chainIdLen > 4096000) { + return ValidationResult.error("ChainId exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += chainIdLen; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ChainId"); + } + } + + if ((nullBits & 64) != 0) { + int chainingNextOffset = buffer.getIntLE(offset + 39); + if (chainingNextOffset < 0) { + return ValidationResult.error("Invalid offset for ChainingNext"); + } + + int posxxxxxx = offset + 47 + chainingNextOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ChainingNext"); + } + + int chainingNextCount = VarInt.peek(buffer, posxxxxxx); + if (chainingNextCount < 0) { + return ValidationResult.error("Invalid array count for ChainingNext"); + } + + if (chainingNextCount > 4096000) { + return ValidationResult.error("ChainingNext exceeds max length 4096000"); + } + + posxxxxxx += VarInt.length(buffer, posxxxxxx); + posxxxxxx += chainingNextCount * 4; + if (posxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ChainingNext"); + } + } + + if ((nullBits & 128) != 0) { + int flagsOffset = buffer.getIntLE(offset + 43); + if (flagsOffset < 0) { + return ValidationResult.error("Invalid offset for Flags"); + } + + int posxxxxxxx = offset + 47 + flagsOffset; + if (posxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Flags"); + } + + int flagsCount = VarInt.peek(buffer, posxxxxxxx); + if (flagsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Flags"); + } + + if (flagsCount > 4096000) { + return ValidationResult.error("Flags exceeds max length 4096000"); + } + + posxxxxxxx += VarInt.length(buffer, posxxxxxxx); + + for (int i = 0; i < flagsCount; i++) { + int keyLen = VarInt.peek(buffer, posxxxxxxx); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + posxxxxxxx += VarInt.length(buffer, posxxxxxxx); + posxxxxxxx += keyLen; + if (posxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxx += 4; + if (posxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public ChainingInteraction clone() { + ChainingInteraction copy = new ChainingInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.chainId = this.chainId; + copy.chainingAllowance = this.chainingAllowance; + copy.chainingNext = this.chainingNext != null ? Arrays.copyOf(this.chainingNext, this.chainingNext.length) : null; + copy.flags = this.flags != null ? new HashMap<>(this.flags) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ChainingInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && Objects.equals(this.chainId, other.chainId) + && this.chainingAllowance == other.chainingAllowance + && Arrays.equals(this.chainingNext, other.chainingNext) + && Objects.equals(this.flags, other.flags); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Objects.hashCode(this.chainId); + result = 31 * result + Float.hashCode(this.chainingAllowance); + result = 31 * result + Arrays.hashCode(this.chainingNext); + return 31 * result + Objects.hashCode(this.flags); + } +} diff --git a/src/com/hypixel/hytale/protocol/ChangeActiveSlotInteraction.java b/src/com/hypixel/hytale/protocol/ChangeActiveSlotInteraction.java new file mode 100644 index 0000000..0d72bd9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ChangeActiveSlotInteraction.java @@ -0,0 +1,497 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChangeActiveSlotInteraction extends Interaction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 15; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 35; + public static final int MAX_SIZE = 1677721600; + public int targetSlot = Integer.MIN_VALUE; + + public ChangeActiveSlotInteraction() { + } + + public ChangeActiveSlotInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int targetSlot + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.targetSlot = targetSlot; + } + + public ChangeActiveSlotInteraction(@Nonnull ChangeActiveSlotInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.targetSlot = other.targetSlot; + } + + @Nonnull + public static ChangeActiveSlotInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + ChangeActiveSlotInteraction obj = new ChangeActiveSlotInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.targetSlot = buf.getIntLE(offset + 11); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 35 + buf.getIntLE(offset + 15); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 35 + buf.getIntLE(offset + 19); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 35 + buf.getIntLE(offset + 23); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 35 + buf.getIntLE(offset + 27); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 35 + buf.getIntLE(offset + 31); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 35; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 15); + int pos0 = offset + 35 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 19); + int pos1 = offset + 35 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 23); + int pos2 = offset + 35 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 27); + int pos3 = offset + 35 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 31); + int pos4 = offset + 35 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.targetSlot); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 35; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 35) { + return ValidationResult.error("Buffer too small: expected at least 35 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 15); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 35 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 19); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 35 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 23); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 35 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 27); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 35 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 31); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 35 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public ChangeActiveSlotInteraction clone() { + ChangeActiveSlotInteraction copy = new ChangeActiveSlotInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.targetSlot = this.targetSlot; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ChangeActiveSlotInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.targetSlot == other.targetSlot; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + return 31 * result + Integer.hashCode(this.targetSlot); + } +} diff --git a/src/com/hypixel/hytale/protocol/ChangeBlockInteraction.java b/src/com/hypixel/hytale/protocol/ChangeBlockInteraction.java new file mode 100644 index 0000000..66fe812 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ChangeBlockInteraction.java @@ -0,0 +1,641 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChangeBlockInteraction extends SimpleBlockInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 25; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 49; + public static final int MAX_SIZE = 1677721600; + @Nullable + public Map blockChanges; + public int worldSoundEventIndex; + public boolean requireNotBroken; + + public ChangeBlockInteraction() { + } + + public ChangeBlockInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + boolean useLatestTarget, + @Nullable Map blockChanges, + int worldSoundEventIndex, + boolean requireNotBroken + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.useLatestTarget = useLatestTarget; + this.blockChanges = blockChanges; + this.worldSoundEventIndex = worldSoundEventIndex; + this.requireNotBroken = requireNotBroken; + } + + public ChangeBlockInteraction(@Nonnull ChangeBlockInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.useLatestTarget = other.useLatestTarget; + this.blockChanges = other.blockChanges; + this.worldSoundEventIndex = other.worldSoundEventIndex; + this.requireNotBroken = other.requireNotBroken; + } + + @Nonnull + public static ChangeBlockInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + ChangeBlockInteraction obj = new ChangeBlockInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.useLatestTarget = buf.getByte(offset + 19) != 0; + obj.worldSoundEventIndex = buf.getIntLE(offset + 20); + obj.requireNotBroken = buf.getByte(offset + 24) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 49 + buf.getIntLE(offset + 25); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 49 + buf.getIntLE(offset + 29); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 49 + buf.getIntLE(offset + 33); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 49 + buf.getIntLE(offset + 37); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 49 + buf.getIntLE(offset + 41); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 49 + buf.getIntLE(offset + 45); + int blockChangesCount = VarInt.peek(buf, varPos5); + if (blockChangesCount < 0) { + throw ProtocolException.negativeLength("BlockChanges", blockChangesCount); + } + + if (blockChangesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("BlockChanges", blockChangesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + obj.blockChanges = new HashMap<>(blockChangesCount); + int dictPos = varPos5 + varIntLen; + + for (int ix = 0; ix < blockChangesCount; ix++) { + int key = buf.getIntLE(dictPos); + dictPos += 4; + int val = buf.getIntLE(dictPos); + dictPos += 4; + if (obj.blockChanges.put(key, val) != null) { + throw ProtocolException.duplicateKey("blockChanges", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 49; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 25); + int pos0 = offset + 49 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 29); + int pos1 = offset + 49 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 33); + int pos2 = offset + 49 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 37); + int pos3 = offset + 49 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 41); + int pos4 = offset + 49 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 45); + int pos5 = offset + 49 + fieldOffset5; + int dictLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5); + + for (int i = 0; i < dictLen; i++) { + pos5 += 4; + pos5 += 4; + } + + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.blockChanges != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeByte(this.useLatestTarget ? 1 : 0); + buf.writeIntLE(this.worldSoundEventIndex); + buf.writeByte(this.requireNotBroken ? 1 : 0); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int blockChangesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.blockChanges != null) { + buf.setIntLE(blockChangesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.blockChanges.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("BlockChanges", this.blockChanges.size(), 4096000); + } + + VarInt.write(buf, this.blockChanges.size()); + + for (Entry e : this.blockChanges.entrySet()) { + buf.writeIntLE(e.getKey()); + buf.writeIntLE(e.getValue()); + } + } else { + buf.setIntLE(blockChangesOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 49; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.blockChanges != null) { + size += VarInt.size(this.blockChanges.size()) + this.blockChanges.size() * 8; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 49) { + return ValidationResult.error("Buffer too small: expected at least 49 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 25); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 49 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 29); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 49 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 33); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 49 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 37); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 49 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 41); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 49 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int blockChangesOffset = buffer.getIntLE(offset + 45); + if (blockChangesOffset < 0) { + return ValidationResult.error("Invalid offset for BlockChanges"); + } + + int posxxxxx = offset + 49 + blockChangesOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BlockChanges"); + } + + int blockChangesCount = VarInt.peek(buffer, posxxxxx); + if (blockChangesCount < 0) { + return ValidationResult.error("Invalid dictionary count for BlockChanges"); + } + + if (blockChangesCount > 4096000) { + return ValidationResult.error("BlockChanges exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + + for (int i = 0; i < blockChangesCount; i++) { + posxxxxx += 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxx += 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public ChangeBlockInteraction clone() { + ChangeBlockInteraction copy = new ChangeBlockInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.useLatestTarget = this.useLatestTarget; + copy.blockChanges = this.blockChanges != null ? new HashMap<>(this.blockChanges) : null; + copy.worldSoundEventIndex = this.worldSoundEventIndex; + copy.requireNotBroken = this.requireNotBroken; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ChangeBlockInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && this.useLatestTarget == other.useLatestTarget + && Objects.equals(this.blockChanges, other.blockChanges) + && this.worldSoundEventIndex == other.worldSoundEventIndex + && this.requireNotBroken == other.requireNotBroken; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Boolean.hashCode(this.useLatestTarget); + result = 31 * result + Objects.hashCode(this.blockChanges); + result = 31 * result + Integer.hashCode(this.worldSoundEventIndex); + return 31 * result + Boolean.hashCode(this.requireNotBroken); + } +} diff --git a/src/com/hypixel/hytale/protocol/ChangeStatBehaviour.java b/src/com/hypixel/hytale/protocol/ChangeStatBehaviour.java new file mode 100644 index 0000000..b3b2641 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ChangeStatBehaviour.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ChangeStatBehaviour { + Add(0), + Set(1); + + public static final ChangeStatBehaviour[] VALUES = values(); + private final int value; + + private ChangeStatBehaviour(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ChangeStatBehaviour fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ChangeStatBehaviour", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ChangeStatInteraction.java b/src/com/hypixel/hytale/protocol/ChangeStatInteraction.java new file mode 100644 index 0000000..73a6a12 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ChangeStatInteraction.java @@ -0,0 +1,645 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChangeStatInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 22; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 46; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public InteractionTarget entityTarget = InteractionTarget.User; + @Nonnull + public ValueType valueType = ValueType.Percent; + @Nullable + public Map statModifiers; + @Nonnull + public ChangeStatBehaviour changeStatBehaviour = ChangeStatBehaviour.Add; + + public ChangeStatInteraction() { + } + + public ChangeStatInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + @Nonnull InteractionTarget entityTarget, + @Nonnull ValueType valueType, + @Nullable Map statModifiers, + @Nonnull ChangeStatBehaviour changeStatBehaviour + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.entityTarget = entityTarget; + this.valueType = valueType; + this.statModifiers = statModifiers; + this.changeStatBehaviour = changeStatBehaviour; + } + + public ChangeStatInteraction(@Nonnull ChangeStatInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.entityTarget = other.entityTarget; + this.valueType = other.valueType; + this.statModifiers = other.statModifiers; + this.changeStatBehaviour = other.changeStatBehaviour; + } + + @Nonnull + public static ChangeStatInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + ChangeStatInteraction obj = new ChangeStatInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.entityTarget = InteractionTarget.fromValue(buf.getByte(offset + 19)); + obj.valueType = ValueType.fromValue(buf.getByte(offset + 20)); + obj.changeStatBehaviour = ChangeStatBehaviour.fromValue(buf.getByte(offset + 21)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 46 + buf.getIntLE(offset + 22); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 46 + buf.getIntLE(offset + 26); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 46 + buf.getIntLE(offset + 30); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 46 + buf.getIntLE(offset + 34); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 46 + buf.getIntLE(offset + 38); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 46 + buf.getIntLE(offset + 42); + int statModifiersCount = VarInt.peek(buf, varPos5); + if (statModifiersCount < 0) { + throw ProtocolException.negativeLength("StatModifiers", statModifiersCount); + } + + if (statModifiersCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("StatModifiers", statModifiersCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + obj.statModifiers = new HashMap<>(statModifiersCount); + int dictPos = varPos5 + varIntLen; + + for (int ix = 0; ix < statModifiersCount; ix++) { + int key = buf.getIntLE(dictPos); + dictPos += 4; + float val = buf.getFloatLE(dictPos); + dictPos += 4; + if (obj.statModifiers.put(key, val) != null) { + throw ProtocolException.duplicateKey("statModifiers", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 46; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 22); + int pos0 = offset + 46 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 26); + int pos1 = offset + 46 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 30); + int pos2 = offset + 46 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 34); + int pos3 = offset + 46 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 38); + int pos4 = offset + 46 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 42); + int pos5 = offset + 46 + fieldOffset5; + int dictLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5); + + for (int i = 0; i < dictLen; i++) { + pos5 += 4; + pos5 += 4; + } + + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.statModifiers != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeByte(this.entityTarget.getValue()); + buf.writeByte(this.valueType.getValue()); + buf.writeByte(this.changeStatBehaviour.getValue()); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int statModifiersOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.statModifiers != null) { + buf.setIntLE(statModifiersOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.statModifiers.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("StatModifiers", this.statModifiers.size(), 4096000); + } + + VarInt.write(buf, this.statModifiers.size()); + + for (Entry e : this.statModifiers.entrySet()) { + buf.writeIntLE(e.getKey()); + buf.writeFloatLE(e.getValue()); + } + } else { + buf.setIntLE(statModifiersOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 46; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.statModifiers != null) { + size += VarInt.size(this.statModifiers.size()) + this.statModifiers.size() * 8; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 46) { + return ValidationResult.error("Buffer too small: expected at least 46 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 22); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 46 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 26); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 46 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 30); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 46 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 34); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 46 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 38); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 46 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int statModifiersOffset = buffer.getIntLE(offset + 42); + if (statModifiersOffset < 0) { + return ValidationResult.error("Invalid offset for StatModifiers"); + } + + int posxxxxx = offset + 46 + statModifiersOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for StatModifiers"); + } + + int statModifiersCount = VarInt.peek(buffer, posxxxxx); + if (statModifiersCount < 0) { + return ValidationResult.error("Invalid dictionary count for StatModifiers"); + } + + if (statModifiersCount > 4096000) { + return ValidationResult.error("StatModifiers exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + + for (int i = 0; i < statModifiersCount; i++) { + posxxxxx += 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxx += 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public ChangeStatInteraction clone() { + ChangeStatInteraction copy = new ChangeStatInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.entityTarget = this.entityTarget; + copy.valueType = this.valueType; + copy.statModifiers = this.statModifiers != null ? new HashMap<>(this.statModifiers) : null; + copy.changeStatBehaviour = this.changeStatBehaviour; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ChangeStatInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && Objects.equals(this.entityTarget, other.entityTarget) + && Objects.equals(this.valueType, other.valueType) + && Objects.equals(this.statModifiers, other.statModifiers) + && Objects.equals(this.changeStatBehaviour, other.changeStatBehaviour); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Objects.hashCode(this.entityTarget); + result = 31 * result + Objects.hashCode(this.valueType); + result = 31 * result + Objects.hashCode(this.statModifiers); + return 31 * result + Objects.hashCode(this.changeStatBehaviour); + } +} diff --git a/src/com/hypixel/hytale/protocol/ChangeStateInteraction.java b/src/com/hypixel/hytale/protocol/ChangeStateInteraction.java new file mode 100644 index 0000000..0a8ed0d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ChangeStateInteraction.java @@ -0,0 +1,672 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChangeStateInteraction extends SimpleBlockInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 20; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 44; + public static final int MAX_SIZE = 1677721600; + @Nullable + public Map stateChanges; + + public ChangeStateInteraction() { + } + + public ChangeStateInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + boolean useLatestTarget, + @Nullable Map stateChanges + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.useLatestTarget = useLatestTarget; + this.stateChanges = stateChanges; + } + + public ChangeStateInteraction(@Nonnull ChangeStateInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.useLatestTarget = other.useLatestTarget; + this.stateChanges = other.stateChanges; + } + + @Nonnull + public static ChangeStateInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + ChangeStateInteraction obj = new ChangeStateInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.useLatestTarget = buf.getByte(offset + 19) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 44 + buf.getIntLE(offset + 20); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 44 + buf.getIntLE(offset + 24); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 44 + buf.getIntLE(offset + 28); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 44 + buf.getIntLE(offset + 32); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 44 + buf.getIntLE(offset + 36); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 44 + buf.getIntLE(offset + 40); + int stateChangesCount = VarInt.peek(buf, varPos5); + if (stateChangesCount < 0) { + throw ProtocolException.negativeLength("StateChanges", stateChangesCount); + } + + if (stateChangesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("StateChanges", stateChangesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + obj.stateChanges = new HashMap<>(stateChangesCount); + int dictPos = varPos5 + varIntLen; + + for (int ix = 0; ix < stateChangesCount; ix++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + int valLen = VarInt.peek(buf, dictPos); + if (valLen < 0) { + throw ProtocolException.negativeLength("val", valLen); + } + + if (valLen > 4096000) { + throw ProtocolException.stringTooLong("val", valLen, 4096000); + } + + int valVarLen = VarInt.length(buf, dictPos); + String val = PacketIO.readVarString(buf, dictPos); + dictPos += valVarLen + valLen; + if (obj.stateChanges.put(key, val) != null) { + throw ProtocolException.duplicateKey("stateChanges", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 44; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 20); + int pos0 = offset + 44 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 24); + int pos1 = offset + 44 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 28); + int pos2 = offset + 44 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 32); + int pos3 = offset + 44 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 36); + int pos4 = offset + 44 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 40); + int pos5 = offset + 44 + fieldOffset5; + int dictLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + sl; + sl = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + sl; + } + + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.stateChanges != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeByte(this.useLatestTarget ? 1 : 0); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int stateChangesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.stateChanges != null) { + buf.setIntLE(stateChangesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.stateChanges.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("StateChanges", this.stateChanges.size(), 4096000); + } + + VarInt.write(buf, this.stateChanges.size()); + + for (Entry e : this.stateChanges.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + PacketIO.writeVarString(buf, e.getValue(), 4096000); + } + } else { + buf.setIntLE(stateChangesOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 44; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.stateChanges != null) { + int stateChangesSize = 0; + + for (Entry kvp : this.stateChanges.entrySet()) { + stateChangesSize += PacketIO.stringSize(kvp.getKey()) + PacketIO.stringSize(kvp.getValue()); + } + + size += VarInt.size(this.stateChanges.size()) + stateChangesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 44) { + return ValidationResult.error("Buffer too small: expected at least 44 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 20); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 44 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 24); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 44 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 28); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 44 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 32); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 44 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 36); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 44 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int stateChangesOffset = buffer.getIntLE(offset + 40); + if (stateChangesOffset < 0) { + return ValidationResult.error("Invalid offset for StateChanges"); + } + + int posxxxxx = offset + 44 + stateChangesOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for StateChanges"); + } + + int stateChangesCount = VarInt.peek(buffer, posxxxxx); + if (stateChangesCount < 0) { + return ValidationResult.error("Invalid dictionary count for StateChanges"); + } + + if (stateChangesCount > 4096000) { + return ValidationResult.error("StateChanges exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + + for (int i = 0; i < stateChangesCount; i++) { + int keyLen = VarInt.peek(buffer, posxxxxx); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += keyLen; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + int valueLen = VarInt.peek(buffer, posxxxxx); + if (valueLen < 0) { + return ValidationResult.error("Invalid string length for value"); + } + + if (valueLen > 4096000) { + return ValidationResult.error("value exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += valueLen; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public ChangeStateInteraction clone() { + ChangeStateInteraction copy = new ChangeStateInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.useLatestTarget = this.useLatestTarget; + copy.stateChanges = this.stateChanges != null ? new HashMap<>(this.stateChanges) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ChangeStateInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && this.useLatestTarget == other.useLatestTarget + && Objects.equals(this.stateChanges, other.stateChanges); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Boolean.hashCode(this.useLatestTarget); + return 31 * result + Objects.hashCode(this.stateChanges); + } +} diff --git a/src/com/hypixel/hytale/protocol/ChangeVelocityType.java b/src/com/hypixel/hytale/protocol/ChangeVelocityType.java new file mode 100644 index 0000000..306f4da --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ChangeVelocityType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ChangeVelocityType { + Add(0), + Set(1); + + public static final ChangeVelocityType[] VALUES = values(); + private final int value; + + private ChangeVelocityType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ChangeVelocityType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ChangeVelocityType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ChargingDelay.java b/src/com/hypixel/hytale/protocol/ChargingDelay.java new file mode 100644 index 0000000..8eaef9c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ChargingDelay.java @@ -0,0 +1,99 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class ChargingDelay { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 20; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 20; + public static final int MAX_SIZE = 20; + public float minDelay; + public float maxDelay; + public float maxTotalDelay; + public float minHealth; + public float maxHealth; + + public ChargingDelay() { + } + + public ChargingDelay(float minDelay, float maxDelay, float maxTotalDelay, float minHealth, float maxHealth) { + this.minDelay = minDelay; + this.maxDelay = maxDelay; + this.maxTotalDelay = maxTotalDelay; + this.minHealth = minHealth; + this.maxHealth = maxHealth; + } + + public ChargingDelay(@Nonnull ChargingDelay other) { + this.minDelay = other.minDelay; + this.maxDelay = other.maxDelay; + this.maxTotalDelay = other.maxTotalDelay; + this.minHealth = other.minHealth; + this.maxHealth = other.maxHealth; + } + + @Nonnull + public static ChargingDelay deserialize(@Nonnull ByteBuf buf, int offset) { + ChargingDelay obj = new ChargingDelay(); + obj.minDelay = buf.getFloatLE(offset + 0); + obj.maxDelay = buf.getFloatLE(offset + 4); + obj.maxTotalDelay = buf.getFloatLE(offset + 8); + obj.minHealth = buf.getFloatLE(offset + 12); + obj.maxHealth = buf.getFloatLE(offset + 16); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 20; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.minDelay); + buf.writeFloatLE(this.maxDelay); + buf.writeFloatLE(this.maxTotalDelay); + buf.writeFloatLE(this.minHealth); + buf.writeFloatLE(this.maxHealth); + } + + public int computeSize() { + return 20; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 20 ? ValidationResult.error("Buffer too small: expected at least 20 bytes") : ValidationResult.OK; + } + + public ChargingDelay clone() { + ChargingDelay copy = new ChargingDelay(); + copy.minDelay = this.minDelay; + copy.maxDelay = this.maxDelay; + copy.maxTotalDelay = this.maxTotalDelay; + copy.minHealth = this.minHealth; + copy.maxHealth = this.maxHealth; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ChargingDelay other) + ? false + : this.minDelay == other.minDelay + && this.maxDelay == other.maxDelay + && this.maxTotalDelay == other.maxTotalDelay + && this.minHealth == other.minHealth + && this.maxHealth == other.maxHealth; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.minDelay, this.maxDelay, this.maxTotalDelay, this.minHealth, this.maxHealth); + } +} diff --git a/src/com/hypixel/hytale/protocol/ChargingInteraction.java b/src/com/hypixel/hytale/protocol/ChargingInteraction.java new file mode 100644 index 0000000..8381d6c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ChargingInteraction.java @@ -0,0 +1,788 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChargingInteraction extends Interaction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 47; + public static final int VARIABLE_FIELD_COUNT = 7; + public static final int VARIABLE_BLOCK_START = 75; + public static final int MAX_SIZE = 1677721600; + public int failed = Integer.MIN_VALUE; + public boolean allowIndefiniteHold; + public boolean displayProgress; + public boolean cancelOnOtherClick; + public boolean failOnDamage; + public float mouseSensitivityAdjustmentTarget; + public float mouseSensitivityAdjustmentDuration; + @Nullable + public Map chargedNext; + @Nullable + public Map forks; + @Nullable + public ChargingDelay chargingDelay; + + public ChargingInteraction() { + } + + public ChargingInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int failed, + boolean allowIndefiniteHold, + boolean displayProgress, + boolean cancelOnOtherClick, + boolean failOnDamage, + float mouseSensitivityAdjustmentTarget, + float mouseSensitivityAdjustmentDuration, + @Nullable Map chargedNext, + @Nullable Map forks, + @Nullable ChargingDelay chargingDelay + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.failed = failed; + this.allowIndefiniteHold = allowIndefiniteHold; + this.displayProgress = displayProgress; + this.cancelOnOtherClick = cancelOnOtherClick; + this.failOnDamage = failOnDamage; + this.mouseSensitivityAdjustmentTarget = mouseSensitivityAdjustmentTarget; + this.mouseSensitivityAdjustmentDuration = mouseSensitivityAdjustmentDuration; + this.chargedNext = chargedNext; + this.forks = forks; + this.chargingDelay = chargingDelay; + } + + public ChargingInteraction(@Nonnull ChargingInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.failed = other.failed; + this.allowIndefiniteHold = other.allowIndefiniteHold; + this.displayProgress = other.displayProgress; + this.cancelOnOtherClick = other.cancelOnOtherClick; + this.failOnDamage = other.failOnDamage; + this.mouseSensitivityAdjustmentTarget = other.mouseSensitivityAdjustmentTarget; + this.mouseSensitivityAdjustmentDuration = other.mouseSensitivityAdjustmentDuration; + this.chargedNext = other.chargedNext; + this.forks = other.forks; + this.chargingDelay = other.chargingDelay; + } + + @Nonnull + public static ChargingInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + ChargingInteraction obj = new ChargingInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.failed = buf.getIntLE(offset + 11); + obj.allowIndefiniteHold = buf.getByte(offset + 15) != 0; + obj.displayProgress = buf.getByte(offset + 16) != 0; + obj.cancelOnOtherClick = buf.getByte(offset + 17) != 0; + obj.failOnDamage = buf.getByte(offset + 18) != 0; + obj.mouseSensitivityAdjustmentTarget = buf.getFloatLE(offset + 19); + obj.mouseSensitivityAdjustmentDuration = buf.getFloatLE(offset + 23); + if ((nullBits & 128) != 0) { + obj.chargingDelay = ChargingDelay.deserialize(buf, offset + 27); + } + + if ((nullBits & 1) != 0) { + int varPos0 = offset + 75 + buf.getIntLE(offset + 47); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 75 + buf.getIntLE(offset + 51); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 75 + buf.getIntLE(offset + 55); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 75 + buf.getIntLE(offset + 59); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 75 + buf.getIntLE(offset + 63); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 75 + buf.getIntLE(offset + 67); + int chargedNextCount = VarInt.peek(buf, varPos5); + if (chargedNextCount < 0) { + throw ProtocolException.negativeLength("ChargedNext", chargedNextCount); + } + + if (chargedNextCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("ChargedNext", chargedNextCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + obj.chargedNext = new HashMap<>(chargedNextCount); + int dictPos = varPos5 + varIntLen; + + for (int ix = 0; ix < chargedNextCount; ix++) { + float key = buf.getFloatLE(dictPos); + dictPos += 4; + int val = buf.getIntLE(dictPos); + dictPos += 4; + if (obj.chargedNext.put(key, val) != null) { + throw ProtocolException.duplicateKey("chargedNext", key); + } + } + } + + if ((nullBits & 64) != 0) { + int varPos6 = offset + 75 + buf.getIntLE(offset + 71); + int forksCount = VarInt.peek(buf, varPos6); + if (forksCount < 0) { + throw ProtocolException.negativeLength("Forks", forksCount); + } + + if (forksCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Forks", forksCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos6); + obj.forks = new HashMap<>(forksCount); + int dictPos = varPos6 + varIntLen; + + for (int ixx = 0; ixx < forksCount; ixx++) { + InteractionType key = InteractionType.fromValue(buf.getByte(dictPos)); + int val = buf.getIntLE(++dictPos); + dictPos += 4; + if (obj.forks.put(key, val) != null) { + throw ProtocolException.duplicateKey("forks", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 75; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 47); + int pos0 = offset + 75 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 51); + int pos1 = offset + 75 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 55); + int pos2 = offset + 75 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 59); + int pos3 = offset + 75 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 63); + int pos4 = offset + 75 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 67); + int pos5 = offset + 75 + fieldOffset5; + int dictLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5); + + for (int i = 0; i < dictLen; i++) { + pos5 += 4; + pos5 += 4; + } + + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits & 64) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 71); + int pos6 = offset + 75 + fieldOffset6; + int dictLen = VarInt.peek(buf, pos6); + pos6 += VarInt.length(buf, pos6); + + for (int i = 0; i < dictLen; i++) { + pos6 = ++pos6 + 4; + } + + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.chargedNext != null) { + nullBits = (byte)(nullBits | 32); + } + + if (this.forks != null) { + nullBits = (byte)(nullBits | 64); + } + + if (this.chargingDelay != null) { + nullBits = (byte)(nullBits | 128); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.failed); + buf.writeByte(this.allowIndefiniteHold ? 1 : 0); + buf.writeByte(this.displayProgress ? 1 : 0); + buf.writeByte(this.cancelOnOtherClick ? 1 : 0); + buf.writeByte(this.failOnDamage ? 1 : 0); + buf.writeFloatLE(this.mouseSensitivityAdjustmentTarget); + buf.writeFloatLE(this.mouseSensitivityAdjustmentDuration); + if (this.chargingDelay != null) { + this.chargingDelay.serialize(buf); + } else { + buf.writeZero(20); + } + + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int chargedNextOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int forksOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.chargedNext != null) { + buf.setIntLE(chargedNextOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.chargedNext.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("ChargedNext", this.chargedNext.size(), 4096000); + } + + VarInt.write(buf, this.chargedNext.size()); + + for (Entry e : this.chargedNext.entrySet()) { + buf.writeFloatLE(e.getKey()); + buf.writeIntLE(e.getValue()); + } + } else { + buf.setIntLE(chargedNextOffsetSlot, -1); + } + + if (this.forks != null) { + buf.setIntLE(forksOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.forks.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Forks", this.forks.size(), 4096000); + } + + VarInt.write(buf, this.forks.size()); + + for (Entry e : this.forks.entrySet()) { + buf.writeByte(e.getKey().getValue()); + buf.writeIntLE(e.getValue()); + } + } else { + buf.setIntLE(forksOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 75; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.chargedNext != null) { + size += VarInt.size(this.chargedNext.size()) + this.chargedNext.size() * 8; + } + + if (this.forks != null) { + size += VarInt.size(this.forks.size()) + this.forks.size() * 5; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 75) { + return ValidationResult.error("Buffer too small: expected at least 75 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 47); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 75 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 51); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 75 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 55); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 75 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 59); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 75 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 63); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 75 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int chargedNextOffset = buffer.getIntLE(offset + 67); + if (chargedNextOffset < 0) { + return ValidationResult.error("Invalid offset for ChargedNext"); + } + + int posxxxxx = offset + 75 + chargedNextOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ChargedNext"); + } + + int chargedNextCount = VarInt.peek(buffer, posxxxxx); + if (chargedNextCount < 0) { + return ValidationResult.error("Invalid dictionary count for ChargedNext"); + } + + if (chargedNextCount > 4096000) { + return ValidationResult.error("ChargedNext exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + + for (int i = 0; i < chargedNextCount; i++) { + posxxxxx += 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxx += 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + if ((nullBits & 64) != 0) { + int forksOffset = buffer.getIntLE(offset + 71); + if (forksOffset < 0) { + return ValidationResult.error("Invalid offset for Forks"); + } + + int posxxxxxx = offset + 75 + forksOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Forks"); + } + + int forksCount = VarInt.peek(buffer, posxxxxxx); + if (forksCount < 0) { + return ValidationResult.error("Invalid dictionary count for Forks"); + } + + if (forksCount > 4096000) { + return ValidationResult.error("Forks exceeds max length 4096000"); + } + + posxxxxxx += VarInt.length(buffer, posxxxxxx); + + for (int i = 0; i < forksCount; i++) { + posxxxxxx = ++posxxxxxx + 4; + if (posxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public ChargingInteraction clone() { + ChargingInteraction copy = new ChargingInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.failed = this.failed; + copy.allowIndefiniteHold = this.allowIndefiniteHold; + copy.displayProgress = this.displayProgress; + copy.cancelOnOtherClick = this.cancelOnOtherClick; + copy.failOnDamage = this.failOnDamage; + copy.mouseSensitivityAdjustmentTarget = this.mouseSensitivityAdjustmentTarget; + copy.mouseSensitivityAdjustmentDuration = this.mouseSensitivityAdjustmentDuration; + copy.chargedNext = this.chargedNext != null ? new HashMap<>(this.chargedNext) : null; + copy.forks = this.forks != null ? new HashMap<>(this.forks) : null; + copy.chargingDelay = this.chargingDelay != null ? this.chargingDelay.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ChargingInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.failed == other.failed + && this.allowIndefiniteHold == other.allowIndefiniteHold + && this.displayProgress == other.displayProgress + && this.cancelOnOtherClick == other.cancelOnOtherClick + && this.failOnDamage == other.failOnDamage + && this.mouseSensitivityAdjustmentTarget == other.mouseSensitivityAdjustmentTarget + && this.mouseSensitivityAdjustmentDuration == other.mouseSensitivityAdjustmentDuration + && Objects.equals(this.chargedNext, other.chargedNext) + && Objects.equals(this.forks, other.forks) + && Objects.equals(this.chargingDelay, other.chargingDelay); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Boolean.hashCode(this.allowIndefiniteHold); + result = 31 * result + Boolean.hashCode(this.displayProgress); + result = 31 * result + Boolean.hashCode(this.cancelOnOtherClick); + result = 31 * result + Boolean.hashCode(this.failOnDamage); + result = 31 * result + Float.hashCode(this.mouseSensitivityAdjustmentTarget); + result = 31 * result + Float.hashCode(this.mouseSensitivityAdjustmentDuration); + result = 31 * result + Objects.hashCode(this.chargedNext); + result = 31 * result + Objects.hashCode(this.forks); + return 31 * result + Objects.hashCode(this.chargingDelay); + } +} diff --git a/src/com/hypixel/hytale/protocol/ClampConfig.java b/src/com/hypixel/hytale/protocol/ClampConfig.java new file mode 100644 index 0000000..1ec61e8 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ClampConfig.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class ClampConfig { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 9; + public float min; + public float max; + public boolean normalize; + + public ClampConfig() { + } + + public ClampConfig(float min, float max, boolean normalize) { + this.min = min; + this.max = max; + this.normalize = normalize; + } + + public ClampConfig(@Nonnull ClampConfig other) { + this.min = other.min; + this.max = other.max; + this.normalize = other.normalize; + } + + @Nonnull + public static ClampConfig deserialize(@Nonnull ByteBuf buf, int offset) { + ClampConfig obj = new ClampConfig(); + obj.min = buf.getFloatLE(offset + 0); + obj.max = buf.getFloatLE(offset + 4); + obj.normalize = buf.getByte(offset + 8) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 9; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.min); + buf.writeFloatLE(this.max); + buf.writeByte(this.normalize ? 1 : 0); + } + + public int computeSize() { + return 9; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 9 ? ValidationResult.error("Buffer too small: expected at least 9 bytes") : ValidationResult.OK; + } + + public ClampConfig clone() { + ClampConfig copy = new ClampConfig(); + copy.min = this.min; + copy.max = this.max; + copy.normalize = this.normalize; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ClampConfig other) ? false : this.min == other.min && this.max == other.max && this.normalize == other.normalize; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.min, this.max, this.normalize); + } +} diff --git a/src/com/hypixel/hytale/protocol/ClearEntityEffectInteraction.java b/src/com/hypixel/hytale/protocol/ClearEntityEffectInteraction.java new file mode 100644 index 0000000..bd34f6a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ClearEntityEffectInteraction.java @@ -0,0 +1,523 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ClearEntityEffectInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 24; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 44; + public static final int MAX_SIZE = 1677721600; + public int effectId; + @Nonnull + public InteractionTarget entityTarget = InteractionTarget.User; + + public ClearEntityEffectInteraction() { + } + + public ClearEntityEffectInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + int effectId, + @Nonnull InteractionTarget entityTarget + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.effectId = effectId; + this.entityTarget = entityTarget; + } + + public ClearEntityEffectInteraction(@Nonnull ClearEntityEffectInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.effectId = other.effectId; + this.entityTarget = other.entityTarget; + } + + @Nonnull + public static ClearEntityEffectInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + ClearEntityEffectInteraction obj = new ClearEntityEffectInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.effectId = buf.getIntLE(offset + 19); + obj.entityTarget = InteractionTarget.fromValue(buf.getByte(offset + 23)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 44 + buf.getIntLE(offset + 24); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 44 + buf.getIntLE(offset + 28); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 44 + buf.getIntLE(offset + 32); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 44 + buf.getIntLE(offset + 36); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 44 + buf.getIntLE(offset + 40); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 44; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 24); + int pos0 = offset + 44 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 28); + int pos1 = offset + 44 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 32); + int pos2 = offset + 44 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 36); + int pos3 = offset + 44 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 40); + int pos4 = offset + 44 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeIntLE(this.effectId); + buf.writeByte(this.entityTarget.getValue()); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 44; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 44) { + return ValidationResult.error("Buffer too small: expected at least 44 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 24); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 44 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 28); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 44 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 32); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 44 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 36); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 44 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 40); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 44 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public ClearEntityEffectInteraction clone() { + ClearEntityEffectInteraction copy = new ClearEntityEffectInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.effectId = this.effectId; + copy.entityTarget = this.entityTarget; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ClearEntityEffectInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && this.effectId == other.effectId + && Objects.equals(this.entityTarget, other.entityTarget); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Integer.hashCode(this.effectId); + return 31 * result + Objects.hashCode(this.entityTarget); + } +} diff --git a/src/com/hypixel/hytale/protocol/ClickType.java b/src/com/hypixel/hytale/protocol/ClickType.java new file mode 100644 index 0000000..fabae28 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ClickType.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ClickType { + None(0), + Left(1), + Right(2), + Middle(3); + + public static final ClickType[] VALUES = values(); + private final int value; + + private ClickType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ClickType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ClickType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ClientCameraView.java b/src/com/hypixel/hytale/protocol/ClientCameraView.java new file mode 100644 index 0000000..9666b99 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ClientCameraView.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ClientCameraView { + FirstPerson(0), + ThirdPerson(1), + Custom(2); + + public static final ClientCameraView[] VALUES = values(); + private final int value; + + private ClientCameraView(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ClientCameraView fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ClientCameraView", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/Cloud.java b/src/com/hypixel/hytale/protocol/Cloud.java new file mode 100644 index 0000000..386a255 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Cloud.java @@ -0,0 +1,379 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Cloud { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 81920028; + @Nullable + public String texture; + @Nullable + public Map speeds; + @Nullable + public Map colors; + + public Cloud() { + } + + public Cloud(@Nullable String texture, @Nullable Map speeds, @Nullable Map colors) { + this.texture = texture; + this.speeds = speeds; + this.colors = colors; + } + + public Cloud(@Nonnull Cloud other) { + this.texture = other.texture; + this.speeds = other.speeds; + this.colors = other.colors; + } + + @Nonnull + public static Cloud deserialize(@Nonnull ByteBuf buf, int offset) { + Cloud obj = new Cloud(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 1); + int textureLen = VarInt.peek(buf, varPos0); + if (textureLen < 0) { + throw ProtocolException.negativeLength("Texture", textureLen); + } + + if (textureLen > 4096000) { + throw ProtocolException.stringTooLong("Texture", textureLen, 4096000); + } + + obj.texture = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 5); + int speedsCount = VarInt.peek(buf, varPos1); + if (speedsCount < 0) { + throw ProtocolException.negativeLength("Speeds", speedsCount); + } + + if (speedsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Speeds", speedsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.speeds = new HashMap<>(speedsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < speedsCount; i++) { + float key = buf.getFloatLE(dictPos); + dictPos += 4; + float val = buf.getFloatLE(dictPos); + dictPos += 4; + if (obj.speeds.put(key, val) != null) { + throw ProtocolException.duplicateKey("speeds", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 13 + buf.getIntLE(offset + 9); + int colorsCount = VarInt.peek(buf, varPos2); + if (colorsCount < 0) { + throw ProtocolException.negativeLength("Colors", colorsCount); + } + + if (colorsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Colors", colorsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + obj.colors = new HashMap<>(colorsCount); + int dictPos = varPos2 + varIntLen; + + for (int ix = 0; ix < colorsCount; ix++) { + float key = buf.getFloatLE(dictPos); + dictPos += 4; + ColorAlpha val = ColorAlpha.deserialize(buf, dictPos); + dictPos += ColorAlpha.computeBytesConsumed(buf, dictPos); + if (obj.colors.put(key, val) != null) { + throw ProtocolException.duplicateKey("colors", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 13 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 13 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 += 4; + pos1 += 4; + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 9); + int pos2 = offset + 13 + fieldOffset2; + int dictLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2); + + for (int i = 0; i < dictLen; i++) { + pos2 += 4; + pos2 += ColorAlpha.computeBytesConsumed(buf, pos2); + } + + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.texture != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.speeds != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.colors != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + int textureOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int speedsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int colorsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.texture != null) { + buf.setIntLE(textureOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.texture, 4096000); + } else { + buf.setIntLE(textureOffsetSlot, -1); + } + + if (this.speeds != null) { + buf.setIntLE(speedsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.speeds.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Speeds", this.speeds.size(), 4096000); + } + + VarInt.write(buf, this.speeds.size()); + + for (Entry e : this.speeds.entrySet()) { + buf.writeFloatLE(e.getKey()); + buf.writeFloatLE(e.getValue()); + } + } else { + buf.setIntLE(speedsOffsetSlot, -1); + } + + if (this.colors != null) { + buf.setIntLE(colorsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.colors.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Colors", this.colors.size(), 4096000); + } + + VarInt.write(buf, this.colors.size()); + + for (Entry e : this.colors.entrySet()) { + buf.writeFloatLE(e.getKey()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(colorsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 13; + if (this.texture != null) { + size += PacketIO.stringSize(this.texture); + } + + if (this.speeds != null) { + size += VarInt.size(this.speeds.size()) + this.speeds.size() * 8; + } + + if (this.colors != null) { + size += VarInt.size(this.colors.size()) + this.colors.size() * 8; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int textureOffset = buffer.getIntLE(offset + 1); + if (textureOffset < 0) { + return ValidationResult.error("Invalid offset for Texture"); + } + + int pos = offset + 13 + textureOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Texture"); + } + + int textureLen = VarInt.peek(buffer, pos); + if (textureLen < 0) { + return ValidationResult.error("Invalid string length for Texture"); + } + + if (textureLen > 4096000) { + return ValidationResult.error("Texture exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += textureLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Texture"); + } + } + + if ((nullBits & 2) != 0) { + int speedsOffset = buffer.getIntLE(offset + 5); + if (speedsOffset < 0) { + return ValidationResult.error("Invalid offset for Speeds"); + } + + int posx = offset + 13 + speedsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Speeds"); + } + + int speedsCount = VarInt.peek(buffer, posx); + if (speedsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Speeds"); + } + + if (speedsCount > 4096000) { + return ValidationResult.error("Speeds exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < speedsCount; i++) { + posx += 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posx += 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + if ((nullBits & 4) != 0) { + int colorsOffset = buffer.getIntLE(offset + 9); + if (colorsOffset < 0) { + return ValidationResult.error("Invalid offset for Colors"); + } + + int posxx = offset + 13 + colorsOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Colors"); + } + + int colorsCount = VarInt.peek(buffer, posxx); + if (colorsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Colors"); + } + + if (colorsCount > 4096000) { + return ValidationResult.error("Colors exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + + for (int i = 0; i < colorsCount; i++) { + posxx += 4; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxx += 4; + } + } + + return ValidationResult.OK; + } + } + + public Cloud clone() { + Cloud copy = new Cloud(); + copy.texture = this.texture; + copy.speeds = this.speeds != null ? new HashMap<>(this.speeds) : null; + if (this.colors != null) { + Map m = new HashMap<>(); + + for (Entry e : this.colors.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.colors = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Cloud other) + ? false + : Objects.equals(this.texture, other.texture) && Objects.equals(this.speeds, other.speeds) && Objects.equals(this.colors, other.colors); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.texture, this.speeds, this.colors); + } +} diff --git a/src/com/hypixel/hytale/protocol/CollisionType.java b/src/com/hypixel/hytale/protocol/CollisionType.java new file mode 100644 index 0000000..f31087f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CollisionType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum CollisionType { + Hard(0), + Soft(1); + + public static final CollisionType[] VALUES = values(); + private final int value; + + private CollisionType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static CollisionType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("CollisionType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/Color.java b/src/com/hypixel/hytale/protocol/Color.java new file mode 100644 index 0000000..a908fbe --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Color.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class Color { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 3; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 3; + public static final int MAX_SIZE = 3; + public byte red; + public byte green; + public byte blue; + + public Color() { + } + + public Color(byte red, byte green, byte blue) { + this.red = red; + this.green = green; + this.blue = blue; + } + + public Color(@Nonnull Color other) { + this.red = other.red; + this.green = other.green; + this.blue = other.blue; + } + + @Nonnull + public static Color deserialize(@Nonnull ByteBuf buf, int offset) { + Color obj = new Color(); + obj.red = buf.getByte(offset + 0); + obj.green = buf.getByte(offset + 1); + obj.blue = buf.getByte(offset + 2); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 3; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.red); + buf.writeByte(this.green); + buf.writeByte(this.blue); + } + + public int computeSize() { + return 3; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 3 ? ValidationResult.error("Buffer too small: expected at least 3 bytes") : ValidationResult.OK; + } + + public Color clone() { + Color copy = new Color(); + copy.red = this.red; + copy.green = this.green; + copy.blue = this.blue; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Color other) ? false : this.red == other.red && this.green == other.green && this.blue == other.blue; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.red, this.green, this.blue); + } +} diff --git a/src/com/hypixel/hytale/protocol/ColorAlpha.java b/src/com/hypixel/hytale/protocol/ColorAlpha.java new file mode 100644 index 0000000..232ee6a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ColorAlpha.java @@ -0,0 +1,89 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class ColorAlpha { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 4; + public byte alpha; + public byte red; + public byte green; + public byte blue; + + public ColorAlpha() { + } + + public ColorAlpha(byte alpha, byte red, byte green, byte blue) { + this.alpha = alpha; + this.red = red; + this.green = green; + this.blue = blue; + } + + public ColorAlpha(@Nonnull ColorAlpha other) { + this.alpha = other.alpha; + this.red = other.red; + this.green = other.green; + this.blue = other.blue; + } + + @Nonnull + public static ColorAlpha deserialize(@Nonnull ByteBuf buf, int offset) { + ColorAlpha obj = new ColorAlpha(); + obj.alpha = buf.getByte(offset + 0); + obj.red = buf.getByte(offset + 1); + obj.green = buf.getByte(offset + 2); + obj.blue = buf.getByte(offset + 3); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 4; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.alpha); + buf.writeByte(this.red); + buf.writeByte(this.green); + buf.writeByte(this.blue); + } + + public int computeSize() { + return 4; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 4 ? ValidationResult.error("Buffer too small: expected at least 4 bytes") : ValidationResult.OK; + } + + public ColorAlpha clone() { + ColorAlpha copy = new ColorAlpha(); + copy.alpha = this.alpha; + copy.red = this.red; + copy.green = this.green; + copy.blue = this.blue; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ColorAlpha other) + ? false + : this.alpha == other.alpha && this.red == other.red && this.green == other.green && this.blue == other.blue; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.alpha, this.red, this.green, this.blue); + } +} diff --git a/src/com/hypixel/hytale/protocol/ColorLight.java b/src/com/hypixel/hytale/protocol/ColorLight.java new file mode 100644 index 0000000..f6f7f35 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ColorLight.java @@ -0,0 +1,89 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class ColorLight { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 4; + public byte radius; + public byte red; + public byte green; + public byte blue; + + public ColorLight() { + } + + public ColorLight(byte radius, byte red, byte green, byte blue) { + this.radius = radius; + this.red = red; + this.green = green; + this.blue = blue; + } + + public ColorLight(@Nonnull ColorLight other) { + this.radius = other.radius; + this.red = other.red; + this.green = other.green; + this.blue = other.blue; + } + + @Nonnull + public static ColorLight deserialize(@Nonnull ByteBuf buf, int offset) { + ColorLight obj = new ColorLight(); + obj.radius = buf.getByte(offset + 0); + obj.red = buf.getByte(offset + 1); + obj.green = buf.getByte(offset + 2); + obj.blue = buf.getByte(offset + 3); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 4; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.radius); + buf.writeByte(this.red); + buf.writeByte(this.green); + buf.writeByte(this.blue); + } + + public int computeSize() { + return 4; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 4 ? ValidationResult.error("Buffer too small: expected at least 4 bytes") : ValidationResult.OK; + } + + public ColorLight clone() { + ColorLight copy = new ColorLight(); + copy.radius = this.radius; + copy.red = this.red; + copy.green = this.green; + copy.blue = this.blue; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ColorLight other) + ? false + : this.radius == other.radius && this.red == other.red && this.green == other.green && this.blue == other.blue; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.radius, this.red, this.green, this.blue); + } +} diff --git a/src/com/hypixel/hytale/protocol/CombatTextEntityUIAnimationEventType.java b/src/com/hypixel/hytale/protocol/CombatTextEntityUIAnimationEventType.java new file mode 100644 index 0000000..8c938ed --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CombatTextEntityUIAnimationEventType.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum CombatTextEntityUIAnimationEventType { + Scale(0), + Position(1), + Opacity(2); + + public static final CombatTextEntityUIAnimationEventType[] VALUES = values(); + private final int value; + + private CombatTextEntityUIAnimationEventType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static CombatTextEntityUIAnimationEventType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("CombatTextEntityUIAnimationEventType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/CombatTextEntityUIComponentAnimationEvent.java b/src/com/hypixel/hytale/protocol/CombatTextEntityUIComponentAnimationEvent.java new file mode 100644 index 0000000..adb7eaf --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CombatTextEntityUIComponentAnimationEvent.java @@ -0,0 +1,147 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CombatTextEntityUIComponentAnimationEvent { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 34; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 34; + public static final int MAX_SIZE = 34; + @Nonnull + public CombatTextEntityUIAnimationEventType type = CombatTextEntityUIAnimationEventType.Scale; + public float startAt; + public float endAt; + public float startScale; + public float endScale; + @Nullable + public Vector2f positionOffset; + public float startOpacity; + public float endOpacity; + + public CombatTextEntityUIComponentAnimationEvent() { + } + + public CombatTextEntityUIComponentAnimationEvent( + @Nonnull CombatTextEntityUIAnimationEventType type, + float startAt, + float endAt, + float startScale, + float endScale, + @Nullable Vector2f positionOffset, + float startOpacity, + float endOpacity + ) { + this.type = type; + this.startAt = startAt; + this.endAt = endAt; + this.startScale = startScale; + this.endScale = endScale; + this.positionOffset = positionOffset; + this.startOpacity = startOpacity; + this.endOpacity = endOpacity; + } + + public CombatTextEntityUIComponentAnimationEvent(@Nonnull CombatTextEntityUIComponentAnimationEvent other) { + this.type = other.type; + this.startAt = other.startAt; + this.endAt = other.endAt; + this.startScale = other.startScale; + this.endScale = other.endScale; + this.positionOffset = other.positionOffset; + this.startOpacity = other.startOpacity; + this.endOpacity = other.endOpacity; + } + + @Nonnull + public static CombatTextEntityUIComponentAnimationEvent deserialize(@Nonnull ByteBuf buf, int offset) { + CombatTextEntityUIComponentAnimationEvent obj = new CombatTextEntityUIComponentAnimationEvent(); + byte nullBits = buf.getByte(offset); + obj.type = CombatTextEntityUIAnimationEventType.fromValue(buf.getByte(offset + 1)); + obj.startAt = buf.getFloatLE(offset + 2); + obj.endAt = buf.getFloatLE(offset + 6); + obj.startScale = buf.getFloatLE(offset + 10); + obj.endScale = buf.getFloatLE(offset + 14); + if ((nullBits & 1) != 0) { + obj.positionOffset = Vector2f.deserialize(buf, offset + 18); + } + + obj.startOpacity = buf.getFloatLE(offset + 26); + obj.endOpacity = buf.getFloatLE(offset + 30); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 34; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.positionOffset != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeFloatLE(this.startAt); + buf.writeFloatLE(this.endAt); + buf.writeFloatLE(this.startScale); + buf.writeFloatLE(this.endScale); + if (this.positionOffset != null) { + this.positionOffset.serialize(buf); + } else { + buf.writeZero(8); + } + + buf.writeFloatLE(this.startOpacity); + buf.writeFloatLE(this.endOpacity); + } + + public int computeSize() { + return 34; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 34 ? ValidationResult.error("Buffer too small: expected at least 34 bytes") : ValidationResult.OK; + } + + public CombatTextEntityUIComponentAnimationEvent clone() { + CombatTextEntityUIComponentAnimationEvent copy = new CombatTextEntityUIComponentAnimationEvent(); + copy.type = this.type; + copy.startAt = this.startAt; + copy.endAt = this.endAt; + copy.startScale = this.startScale; + copy.endScale = this.endScale; + copy.positionOffset = this.positionOffset != null ? this.positionOffset.clone() : null; + copy.startOpacity = this.startOpacity; + copy.endOpacity = this.endOpacity; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CombatTextEntityUIComponentAnimationEvent other) + ? false + : Objects.equals(this.type, other.type) + && this.startAt == other.startAt + && this.endAt == other.endAt + && this.startScale == other.startScale + && this.endScale == other.endScale + && Objects.equals(this.positionOffset, other.positionOffset) + && this.startOpacity == other.startOpacity + && this.endOpacity == other.endOpacity; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.startAt, this.endAt, this.startScale, this.endScale, this.positionOffset, this.startOpacity, this.endOpacity); + } +} diff --git a/src/com/hypixel/hytale/protocol/CombatTextUpdate.java b/src/com/hypixel/hytale/protocol/CombatTextUpdate.java new file mode 100644 index 0000000..e58bab3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CombatTextUpdate.java @@ -0,0 +1,139 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CombatTextUpdate { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 16384010; + public float hitAngleDeg; + @Nullable + public String text; + + public CombatTextUpdate() { + } + + public CombatTextUpdate(float hitAngleDeg, @Nullable String text) { + this.hitAngleDeg = hitAngleDeg; + this.text = text; + } + + public CombatTextUpdate(@Nonnull CombatTextUpdate other) { + this.hitAngleDeg = other.hitAngleDeg; + this.text = other.text; + } + + @Nonnull + public static CombatTextUpdate deserialize(@Nonnull ByteBuf buf, int offset) { + CombatTextUpdate obj = new CombatTextUpdate(); + byte nullBits = buf.getByte(offset); + obj.hitAngleDeg = buf.getFloatLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int textLen = VarInt.peek(buf, pos); + if (textLen < 0) { + throw ProtocolException.negativeLength("Text", textLen); + } + + if (textLen > 4096000) { + throw ProtocolException.stringTooLong("Text", textLen, 4096000); + } + + int textVarLen = VarInt.length(buf, pos); + obj.text = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += textVarLen + textLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.text != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.hitAngleDeg); + if (this.text != null) { + PacketIO.writeVarString(buf, this.text, 4096000); + } + } + + public int computeSize() { + int size = 5; + if (this.text != null) { + size += PacketIO.stringSize(this.text); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int textLen = VarInt.peek(buffer, pos); + if (textLen < 0) { + return ValidationResult.error("Invalid string length for Text"); + } + + if (textLen > 4096000) { + return ValidationResult.error("Text exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += textLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Text"); + } + } + + return ValidationResult.OK; + } + } + + public CombatTextUpdate clone() { + CombatTextUpdate copy = new CombatTextUpdate(); + copy.hitAngleDeg = this.hitAngleDeg; + copy.text = this.text; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CombatTextUpdate other) ? false : this.hitAngleDeg == other.hitAngleDeg && Objects.equals(this.text, other.text); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.hitAngleDeg, this.text); + } +} diff --git a/src/com/hypixel/hytale/protocol/ComponentUpdate.java b/src/com/hypixel/hytale/protocol/ComponentUpdate.java new file mode 100644 index 0000000..26c1d8a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ComponentUpdate.java @@ -0,0 +1,1363 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ComponentUpdate { + public static final int NULLABLE_BIT_FIELD_SIZE = 3; + public static final int FIXED_BLOCK_SIZE = 159; + public static final int VARIABLE_FIELD_COUNT = 13; + public static final int VARIABLE_BLOCK_START = 211; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public ComponentUpdateType type = ComponentUpdateType.Nameplate; + @Nullable + public Nameplate nameplate; + @Nullable + public int[] entityUIComponents; + @Nullable + public CombatTextUpdate combatTextUpdate; + @Nullable + public Model model; + @Nullable + public PlayerSkin skin; + @Nullable + public ItemWithAllMetadata item; + public int blockId; + public float entityScale; + @Nullable + public Equipment equipment; + @Nullable + public Map entityStatUpdates; + @Nullable + public ModelTransform transform; + @Nullable + public MovementStates movementStates; + @Nullable + public EntityEffectUpdate[] entityEffectUpdates; + @Nullable + public Map interactions; + @Nullable + public ColorLight dynamicLight; + public int hitboxCollisionConfigIndex; + public int repulsionConfigIndex; + @Nonnull + public UUID predictionId = new UUID(0L, 0L); + @Nullable + public int[] soundEventIds; + @Nullable + public String interactionHint; + @Nullable + public MountedUpdate mounted; + @Nullable + public String[] activeAnimations; + + public ComponentUpdate() { + } + + public ComponentUpdate( + @Nonnull ComponentUpdateType type, + @Nullable Nameplate nameplate, + @Nullable int[] entityUIComponents, + @Nullable CombatTextUpdate combatTextUpdate, + @Nullable Model model, + @Nullable PlayerSkin skin, + @Nullable ItemWithAllMetadata item, + int blockId, + float entityScale, + @Nullable Equipment equipment, + @Nullable Map entityStatUpdates, + @Nullable ModelTransform transform, + @Nullable MovementStates movementStates, + @Nullable EntityEffectUpdate[] entityEffectUpdates, + @Nullable Map interactions, + @Nullable ColorLight dynamicLight, + int hitboxCollisionConfigIndex, + int repulsionConfigIndex, + @Nonnull UUID predictionId, + @Nullable int[] soundEventIds, + @Nullable String interactionHint, + @Nullable MountedUpdate mounted, + @Nullable String[] activeAnimations + ) { + this.type = type; + this.nameplate = nameplate; + this.entityUIComponents = entityUIComponents; + this.combatTextUpdate = combatTextUpdate; + this.model = model; + this.skin = skin; + this.item = item; + this.blockId = blockId; + this.entityScale = entityScale; + this.equipment = equipment; + this.entityStatUpdates = entityStatUpdates; + this.transform = transform; + this.movementStates = movementStates; + this.entityEffectUpdates = entityEffectUpdates; + this.interactions = interactions; + this.dynamicLight = dynamicLight; + this.hitboxCollisionConfigIndex = hitboxCollisionConfigIndex; + this.repulsionConfigIndex = repulsionConfigIndex; + this.predictionId = predictionId; + this.soundEventIds = soundEventIds; + this.interactionHint = interactionHint; + this.mounted = mounted; + this.activeAnimations = activeAnimations; + } + + public ComponentUpdate(@Nonnull ComponentUpdate other) { + this.type = other.type; + this.nameplate = other.nameplate; + this.entityUIComponents = other.entityUIComponents; + this.combatTextUpdate = other.combatTextUpdate; + this.model = other.model; + this.skin = other.skin; + this.item = other.item; + this.blockId = other.blockId; + this.entityScale = other.entityScale; + this.equipment = other.equipment; + this.entityStatUpdates = other.entityStatUpdates; + this.transform = other.transform; + this.movementStates = other.movementStates; + this.entityEffectUpdates = other.entityEffectUpdates; + this.interactions = other.interactions; + this.dynamicLight = other.dynamicLight; + this.hitboxCollisionConfigIndex = other.hitboxCollisionConfigIndex; + this.repulsionConfigIndex = other.repulsionConfigIndex; + this.predictionId = other.predictionId; + this.soundEventIds = other.soundEventIds; + this.interactionHint = other.interactionHint; + this.mounted = other.mounted; + this.activeAnimations = other.activeAnimations; + } + + @Nonnull + public static ComponentUpdate deserialize(@Nonnull ByteBuf buf, int offset) { + ComponentUpdate obj = new ComponentUpdate(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 3); + obj.type = ComponentUpdateType.fromValue(buf.getByte(offset + 3)); + obj.blockId = buf.getIntLE(offset + 4); + obj.entityScale = buf.getFloatLE(offset + 8); + if ((nullBits[1] & 1) != 0) { + obj.transform = ModelTransform.deserialize(buf, offset + 12); + } + + if ((nullBits[1] & 2) != 0) { + obj.movementStates = MovementStates.deserialize(buf, offset + 61); + } + + if ((nullBits[1] & 16) != 0) { + obj.dynamicLight = ColorLight.deserialize(buf, offset + 83); + } + + obj.hitboxCollisionConfigIndex = buf.getIntLE(offset + 87); + obj.repulsionConfigIndex = buf.getIntLE(offset + 91); + obj.predictionId = PacketIO.readUUID(buf, offset + 95); + if ((nullBits[1] & 128) != 0) { + obj.mounted = MountedUpdate.deserialize(buf, offset + 111); + } + + if ((nullBits[0] & 1) != 0) { + int varPos0 = offset + 211 + buf.getIntLE(offset + 159); + obj.nameplate = Nameplate.deserialize(buf, varPos0); + } + + if ((nullBits[0] & 2) != 0) { + int varPos1 = offset + 211 + buf.getIntLE(offset + 163); + int entityUIComponentsCount = VarInt.peek(buf, varPos1); + if (entityUIComponentsCount < 0) { + throw ProtocolException.negativeLength("EntityUIComponents", entityUIComponentsCount); + } + + if (entityUIComponentsCount > 4096000) { + throw ProtocolException.arrayTooLong("EntityUIComponents", entityUIComponentsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + entityUIComponentsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("EntityUIComponents", varPos1 + varIntLen + entityUIComponentsCount * 4, buf.readableBytes()); + } + + obj.entityUIComponents = new int[entityUIComponentsCount]; + + for (int i = 0; i < entityUIComponentsCount; i++) { + obj.entityUIComponents[i] = buf.getIntLE(varPos1 + varIntLen + i * 4); + } + } + + if ((nullBits[0] & 4) != 0) { + int varPos2 = offset + 211 + buf.getIntLE(offset + 167); + obj.combatTextUpdate = CombatTextUpdate.deserialize(buf, varPos2); + } + + if ((nullBits[0] & 8) != 0) { + int varPos3 = offset + 211 + buf.getIntLE(offset + 171); + obj.model = Model.deserialize(buf, varPos3); + } + + if ((nullBits[0] & 16) != 0) { + int varPos4 = offset + 211 + buf.getIntLE(offset + 175); + obj.skin = PlayerSkin.deserialize(buf, varPos4); + } + + if ((nullBits[0] & 32) != 0) { + int varPos5 = offset + 211 + buf.getIntLE(offset + 179); + obj.item = ItemWithAllMetadata.deserialize(buf, varPos5); + } + + if ((nullBits[0] & 64) != 0) { + int varPos6 = offset + 211 + buf.getIntLE(offset + 183); + obj.equipment = Equipment.deserialize(buf, varPos6); + } + + if ((nullBits[0] & 128) != 0) { + int varPos7 = offset + 211 + buf.getIntLE(offset + 187); + int entityStatUpdatesCount = VarInt.peek(buf, varPos7); + if (entityStatUpdatesCount < 0) { + throw ProtocolException.negativeLength("EntityStatUpdates", entityStatUpdatesCount); + } + + if (entityStatUpdatesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("EntityStatUpdates", entityStatUpdatesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos7); + obj.entityStatUpdates = new HashMap<>(entityStatUpdatesCount); + int dictPos = varPos7 + varIntLen; + + for (int i = 0; i < entityStatUpdatesCount; i++) { + int key = buf.getIntLE(dictPos); + dictPos += 4; + int valLen = VarInt.peek(buf, dictPos); + if (valLen < 0) { + throw ProtocolException.negativeLength("val", valLen); + } + + if (valLen > 64) { + throw ProtocolException.arrayTooLong("val", valLen, 64); + } + + int valVarLen = VarInt.length(buf, dictPos); + if (dictPos + valVarLen + valLen * 13L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("val", dictPos + valVarLen + valLen * 13, buf.readableBytes()); + } + + dictPos += valVarLen; + EntityStatUpdate[] val = new EntityStatUpdate[valLen]; + + for (int valIdx = 0; valIdx < valLen; valIdx++) { + val[valIdx] = EntityStatUpdate.deserialize(buf, dictPos); + dictPos += EntityStatUpdate.computeBytesConsumed(buf, dictPos); + } + + if (obj.entityStatUpdates.put(key, val) != null) { + throw ProtocolException.duplicateKey("entityStatUpdates", key); + } + } + } + + if ((nullBits[1] & 4) != 0) { + int varPos8 = offset + 211 + buf.getIntLE(offset + 191); + int entityEffectUpdatesCount = VarInt.peek(buf, varPos8); + if (entityEffectUpdatesCount < 0) { + throw ProtocolException.negativeLength("EntityEffectUpdates", entityEffectUpdatesCount); + } + + if (entityEffectUpdatesCount > 4096000) { + throw ProtocolException.arrayTooLong("EntityEffectUpdates", entityEffectUpdatesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos8); + if (varPos8 + varIntLen + entityEffectUpdatesCount * 12L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("EntityEffectUpdates", varPos8 + varIntLen + entityEffectUpdatesCount * 12, buf.readableBytes()); + } + + obj.entityEffectUpdates = new EntityEffectUpdate[entityEffectUpdatesCount]; + int elemPos = varPos8 + varIntLen; + + for (int i = 0; i < entityEffectUpdatesCount; i++) { + obj.entityEffectUpdates[i] = EntityEffectUpdate.deserialize(buf, elemPos); + elemPos += EntityEffectUpdate.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits[1] & 8) != 0) { + int varPos9 = offset + 211 + buf.getIntLE(offset + 195); + int interactionsCount = VarInt.peek(buf, varPos9); + if (interactionsCount < 0) { + throw ProtocolException.negativeLength("Interactions", interactionsCount); + } + + if (interactionsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Interactions", interactionsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos9); + obj.interactions = new HashMap<>(interactionsCount); + int dictPos = varPos9 + varIntLen; + + for (int i = 0; i < interactionsCount; i++) { + InteractionType keyx = InteractionType.fromValue(buf.getByte(dictPos)); + int val = buf.getIntLE(++dictPos); + dictPos += 4; + if (obj.interactions.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("interactions", keyx); + } + } + } + + if ((nullBits[1] & 32) != 0) { + int varPos10 = offset + 211 + buf.getIntLE(offset + 199); + int soundEventIdsCount = VarInt.peek(buf, varPos10); + if (soundEventIdsCount < 0) { + throw ProtocolException.negativeLength("SoundEventIds", soundEventIdsCount); + } + + if (soundEventIdsCount > 4096000) { + throw ProtocolException.arrayTooLong("SoundEventIds", soundEventIdsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos10); + if (varPos10 + varIntLen + soundEventIdsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("SoundEventIds", varPos10 + varIntLen + soundEventIdsCount * 4, buf.readableBytes()); + } + + obj.soundEventIds = new int[soundEventIdsCount]; + + for (int ix = 0; ix < soundEventIdsCount; ix++) { + obj.soundEventIds[ix] = buf.getIntLE(varPos10 + varIntLen + ix * 4); + } + } + + if ((nullBits[1] & 64) != 0) { + int varPos11 = offset + 211 + buf.getIntLE(offset + 203); + int interactionHintLen = VarInt.peek(buf, varPos11); + if (interactionHintLen < 0) { + throw ProtocolException.negativeLength("InteractionHint", interactionHintLen); + } + + if (interactionHintLen > 4096000) { + throw ProtocolException.stringTooLong("InteractionHint", interactionHintLen, 4096000); + } + + obj.interactionHint = PacketIO.readVarString(buf, varPos11, PacketIO.UTF8); + } + + if ((nullBits[2] & 1) != 0) { + int varPos12 = offset + 211 + buf.getIntLE(offset + 207); + int activeAnimationsCount = VarInt.peek(buf, varPos12); + if (activeAnimationsCount < 0) { + throw ProtocolException.negativeLength("ActiveAnimations", activeAnimationsCount); + } + + if (activeAnimationsCount > 4096000) { + throw ProtocolException.arrayTooLong("ActiveAnimations", activeAnimationsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos12); + int activeAnimationsBitfieldSize = (activeAnimationsCount + 7) / 8; + byte[] activeAnimationsBitfield = PacketIO.readBytes(buf, varPos12 + varIntLen, activeAnimationsBitfieldSize); + obj.activeAnimations = new String[activeAnimationsCount]; + int elemPos = varPos12 + varIntLen + activeAnimationsBitfieldSize; + + for (int ix = 0; ix < activeAnimationsCount; ix++) { + if ((activeAnimationsBitfield[ix / 8] & 1 << ix % 8) != 0) { + int strLen = VarInt.peek(buf, elemPos); + if (strLen < 0) { + throw ProtocolException.negativeLength("activeAnimations[" + ix + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("activeAnimations[" + ix + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, elemPos); + obj.activeAnimations[ix] = PacketIO.readVarString(buf, elemPos); + elemPos += strVarLen + strLen; + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte[] nullBits = PacketIO.readBytes(buf, offset, 3); + int maxEnd = 211; + if ((nullBits[0] & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 159); + int pos0 = offset + 211 + fieldOffset0; + pos0 += Nameplate.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits[0] & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 163); + int pos1 = offset + 211 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + arrLen * 4; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits[0] & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 167); + int pos2 = offset + 211 + fieldOffset2; + pos2 += CombatTextUpdate.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits[0] & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 171); + int pos3 = offset + 211 + fieldOffset3; + pos3 += Model.computeBytesConsumed(buf, pos3); + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits[0] & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 175); + int pos4 = offset + 211 + fieldOffset4; + pos4 += PlayerSkin.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits[0] & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 179); + int pos5 = offset + 211 + fieldOffset5; + pos5 += ItemWithAllMetadata.computeBytesConsumed(buf, pos5); + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits[0] & 64) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 183); + int pos6 = offset + 211 + fieldOffset6; + pos6 += Equipment.computeBytesConsumed(buf, pos6); + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + if ((nullBits[0] & 128) != 0) { + int fieldOffset7 = buf.getIntLE(offset + 187); + int pos7 = offset + 211 + fieldOffset7; + int dictLen = VarInt.peek(buf, pos7); + pos7 += VarInt.length(buf, pos7); + + for (int i = 0; i < dictLen; i++) { + pos7 += 4; + int al = VarInt.peek(buf, pos7); + pos7 += VarInt.length(buf, pos7); + + for (int j = 0; j < al; j++) { + pos7 += EntityStatUpdate.computeBytesConsumed(buf, pos7); + } + } + + if (pos7 - offset > maxEnd) { + maxEnd = pos7 - offset; + } + } + + if ((nullBits[1] & 4) != 0) { + int fieldOffset8 = buf.getIntLE(offset + 191); + int pos8 = offset + 211 + fieldOffset8; + int arrLen = VarInt.peek(buf, pos8); + pos8 += VarInt.length(buf, pos8); + + for (int i = 0; i < arrLen; i++) { + pos8 += EntityEffectUpdate.computeBytesConsumed(buf, pos8); + } + + if (pos8 - offset > maxEnd) { + maxEnd = pos8 - offset; + } + } + + if ((nullBits[1] & 8) != 0) { + int fieldOffset9 = buf.getIntLE(offset + 195); + int pos9 = offset + 211 + fieldOffset9; + int dictLen = VarInt.peek(buf, pos9); + pos9 += VarInt.length(buf, pos9); + + for (int i = 0; i < dictLen; i++) { + pos9 = ++pos9 + 4; + } + + if (pos9 - offset > maxEnd) { + maxEnd = pos9 - offset; + } + } + + if ((nullBits[1] & 32) != 0) { + int fieldOffset10 = buf.getIntLE(offset + 199); + int pos10 = offset + 211 + fieldOffset10; + int arrLen = VarInt.peek(buf, pos10); + pos10 += VarInt.length(buf, pos10) + arrLen * 4; + if (pos10 - offset > maxEnd) { + maxEnd = pos10 - offset; + } + } + + if ((nullBits[1] & 64) != 0) { + int fieldOffset11 = buf.getIntLE(offset + 203); + int pos11 = offset + 211 + fieldOffset11; + int sl = VarInt.peek(buf, pos11); + pos11 += VarInt.length(buf, pos11) + sl; + if (pos11 - offset > maxEnd) { + maxEnd = pos11 - offset; + } + } + + if ((nullBits[2] & 1) != 0) { + int fieldOffset12 = buf.getIntLE(offset + 207); + int pos12 = offset + 211 + fieldOffset12; + int arrLen = VarInt.peek(buf, pos12); + pos12 += VarInt.length(buf, pos12); + int bitfieldSize = (arrLen + 7) / 8; + byte[] bitfield = PacketIO.readBytes(buf, pos12, bitfieldSize); + pos12 += bitfieldSize; + + for (int i = 0; i < arrLen; i++) { + if ((bitfield[i / 8] & 1 << i % 8) != 0) { + int sl = VarInt.peek(buf, pos12); + pos12 += VarInt.length(buf, pos12) + sl; + } + } + + if (pos12 - offset > maxEnd) { + maxEnd = pos12 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte[] nullBits = new byte[3]; + if (this.nameplate != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.entityUIComponents != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.combatTextUpdate != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.model != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.skin != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.item != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.equipment != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.entityStatUpdates != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.transform != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + if (this.movementStates != null) { + nullBits[1] = (byte)(nullBits[1] | 2); + } + + if (this.entityEffectUpdates != null) { + nullBits[1] = (byte)(nullBits[1] | 4); + } + + if (this.interactions != null) { + nullBits[1] = (byte)(nullBits[1] | 8); + } + + if (this.dynamicLight != null) { + nullBits[1] = (byte)(nullBits[1] | 16); + } + + if (this.soundEventIds != null) { + nullBits[1] = (byte)(nullBits[1] | 32); + } + + if (this.interactionHint != null) { + nullBits[1] = (byte)(nullBits[1] | 64); + } + + if (this.mounted != null) { + nullBits[1] = (byte)(nullBits[1] | 128); + } + + if (this.activeAnimations != null) { + nullBits[2] = (byte)(nullBits[2] | 1); + } + + buf.writeBytes(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.blockId); + buf.writeFloatLE(this.entityScale); + if (this.transform != null) { + this.transform.serialize(buf); + } else { + buf.writeZero(49); + } + + if (this.movementStates != null) { + this.movementStates.serialize(buf); + } else { + buf.writeZero(22); + } + + if (this.dynamicLight != null) { + this.dynamicLight.serialize(buf); + } else { + buf.writeZero(4); + } + + buf.writeIntLE(this.hitboxCollisionConfigIndex); + buf.writeIntLE(this.repulsionConfigIndex); + PacketIO.writeUUID(buf, this.predictionId); + if (this.mounted != null) { + this.mounted.serialize(buf); + } else { + buf.writeZero(48); + } + + int nameplateOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int entityUIComponentsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int combatTextUpdateOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int modelOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int skinOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int itemOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int equipmentOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int entityStatUpdatesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int entityEffectUpdatesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int interactionsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int soundEventIdsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int interactionHintOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int activeAnimationsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.nameplate != null) { + buf.setIntLE(nameplateOffsetSlot, buf.writerIndex() - varBlockStart); + this.nameplate.serialize(buf); + } else { + buf.setIntLE(nameplateOffsetSlot, -1); + } + + if (this.entityUIComponents != null) { + buf.setIntLE(entityUIComponentsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.entityUIComponents.length > 4096000) { + throw ProtocolException.arrayTooLong("EntityUIComponents", this.entityUIComponents.length, 4096000); + } + + VarInt.write(buf, this.entityUIComponents.length); + + for (int item : this.entityUIComponents) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(entityUIComponentsOffsetSlot, -1); + } + + if (this.combatTextUpdate != null) { + buf.setIntLE(combatTextUpdateOffsetSlot, buf.writerIndex() - varBlockStart); + this.combatTextUpdate.serialize(buf); + } else { + buf.setIntLE(combatTextUpdateOffsetSlot, -1); + } + + if (this.model != null) { + buf.setIntLE(modelOffsetSlot, buf.writerIndex() - varBlockStart); + this.model.serialize(buf); + } else { + buf.setIntLE(modelOffsetSlot, -1); + } + + if (this.skin != null) { + buf.setIntLE(skinOffsetSlot, buf.writerIndex() - varBlockStart); + this.skin.serialize(buf); + } else { + buf.setIntLE(skinOffsetSlot, -1); + } + + if (this.item != null) { + buf.setIntLE(itemOffsetSlot, buf.writerIndex() - varBlockStart); + this.item.serialize(buf); + } else { + buf.setIntLE(itemOffsetSlot, -1); + } + + if (this.equipment != null) { + buf.setIntLE(equipmentOffsetSlot, buf.writerIndex() - varBlockStart); + this.equipment.serialize(buf); + } else { + buf.setIntLE(equipmentOffsetSlot, -1); + } + + if (this.entityStatUpdates != null) { + buf.setIntLE(entityStatUpdatesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.entityStatUpdates.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("EntityStatUpdates", this.entityStatUpdates.size(), 4096000); + } + + VarInt.write(buf, this.entityStatUpdates.size()); + + for (Entry e : this.entityStatUpdates.entrySet()) { + buf.writeIntLE(e.getKey()); + VarInt.write(buf, e.getValue().length); + + for (EntityStatUpdate arrItem : e.getValue()) { + arrItem.serialize(buf); + } + } + } else { + buf.setIntLE(entityStatUpdatesOffsetSlot, -1); + } + + if (this.entityEffectUpdates != null) { + buf.setIntLE(entityEffectUpdatesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.entityEffectUpdates.length > 4096000) { + throw ProtocolException.arrayTooLong("EntityEffectUpdates", this.entityEffectUpdates.length, 4096000); + } + + VarInt.write(buf, this.entityEffectUpdates.length); + + for (EntityEffectUpdate item : this.entityEffectUpdates) { + item.serialize(buf); + } + } else { + buf.setIntLE(entityEffectUpdatesOffsetSlot, -1); + } + + if (this.interactions != null) { + buf.setIntLE(interactionsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.interactions.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Interactions", this.interactions.size(), 4096000); + } + + VarInt.write(buf, this.interactions.size()); + + for (Entry e : this.interactions.entrySet()) { + buf.writeByte(e.getKey().getValue()); + buf.writeIntLE(e.getValue()); + } + } else { + buf.setIntLE(interactionsOffsetSlot, -1); + } + + if (this.soundEventIds != null) { + buf.setIntLE(soundEventIdsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.soundEventIds.length > 4096000) { + throw ProtocolException.arrayTooLong("SoundEventIds", this.soundEventIds.length, 4096000); + } + + VarInt.write(buf, this.soundEventIds.length); + + for (int item : this.soundEventIds) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(soundEventIdsOffsetSlot, -1); + } + + if (this.interactionHint != null) { + buf.setIntLE(interactionHintOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.interactionHint, 4096000); + } else { + buf.setIntLE(interactionHintOffsetSlot, -1); + } + + if (this.activeAnimations != null) { + buf.setIntLE(activeAnimationsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.activeAnimations.length > 4096000) { + throw ProtocolException.arrayTooLong("ActiveAnimations", this.activeAnimations.length, 4096000); + } + + VarInt.write(buf, this.activeAnimations.length); + int activeAnimationsBitfieldSize = (this.activeAnimations.length + 7) / 8; + byte[] activeAnimationsBitfield = new byte[activeAnimationsBitfieldSize]; + + for (int i = 0; i < this.activeAnimations.length; i++) { + if (this.activeAnimations[i] != null) { + activeAnimationsBitfield[i / 8] = (byte)(activeAnimationsBitfield[i / 8] | (byte)(1 << i % 8)); + } + } + + buf.writeBytes(activeAnimationsBitfield); + + for (int ix = 0; ix < this.activeAnimations.length; ix++) { + if (this.activeAnimations[ix] != null) { + PacketIO.writeVarString(buf, this.activeAnimations[ix], 4096000); + } + } + } else { + buf.setIntLE(activeAnimationsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 211; + if (this.nameplate != null) { + size += this.nameplate.computeSize(); + } + + if (this.entityUIComponents != null) { + size += VarInt.size(this.entityUIComponents.length) + this.entityUIComponents.length * 4; + } + + if (this.combatTextUpdate != null) { + size += this.combatTextUpdate.computeSize(); + } + + if (this.model != null) { + size += this.model.computeSize(); + } + + if (this.skin != null) { + size += this.skin.computeSize(); + } + + if (this.item != null) { + size += this.item.computeSize(); + } + + if (this.equipment != null) { + size += this.equipment.computeSize(); + } + + if (this.entityStatUpdates != null) { + int entityStatUpdatesSize = 0; + + for (Entry kvp : this.entityStatUpdates.entrySet()) { + entityStatUpdatesSize += 4 + VarInt.size(kvp.getValue().length) + Arrays.stream(kvp.getValue()).mapToInt(inner -> inner.computeSize()).sum(); + } + + size += VarInt.size(this.entityStatUpdates.size()) + entityStatUpdatesSize; + } + + if (this.entityEffectUpdates != null) { + int entityEffectUpdatesSize = 0; + + for (EntityEffectUpdate elem : this.entityEffectUpdates) { + entityEffectUpdatesSize += elem.computeSize(); + } + + size += VarInt.size(this.entityEffectUpdates.length) + entityEffectUpdatesSize; + } + + if (this.interactions != null) { + size += VarInt.size(this.interactions.size()) + this.interactions.size() * 5; + } + + if (this.soundEventIds != null) { + size += VarInt.size(this.soundEventIds.length) + this.soundEventIds.length * 4; + } + + if (this.interactionHint != null) { + size += PacketIO.stringSize(this.interactionHint); + } + + if (this.activeAnimations != null) { + int activeAnimationsSize = 0; + + for (String elem : this.activeAnimations) { + if (elem != null) { + activeAnimationsSize += PacketIO.stringSize(elem); + } + } + + size += VarInt.size(this.activeAnimations.length) + (this.activeAnimations.length + 7) / 8 + activeAnimationsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 211) { + return ValidationResult.error("Buffer too small: expected at least 211 bytes"); + } else { + byte[] nullBits = PacketIO.readBytes(buffer, offset, 3); + if ((nullBits[0] & 1) != 0) { + int nameplateOffset = buffer.getIntLE(offset + 159); + if (nameplateOffset < 0) { + return ValidationResult.error("Invalid offset for Nameplate"); + } + + int pos = offset + 211 + nameplateOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Nameplate"); + } + + ValidationResult nameplateResult = Nameplate.validateStructure(buffer, pos); + if (!nameplateResult.isValid()) { + return ValidationResult.error("Invalid Nameplate: " + nameplateResult.error()); + } + + pos += Nameplate.computeBytesConsumed(buffer, pos); + } + + if ((nullBits[0] & 2) != 0) { + int entityUIComponentsOffset = buffer.getIntLE(offset + 163); + if (entityUIComponentsOffset < 0) { + return ValidationResult.error("Invalid offset for EntityUIComponents"); + } + + int posx = offset + 211 + entityUIComponentsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for EntityUIComponents"); + } + + int entityUIComponentsCount = VarInt.peek(buffer, posx); + if (entityUIComponentsCount < 0) { + return ValidationResult.error("Invalid array count for EntityUIComponents"); + } + + if (entityUIComponentsCount > 4096000) { + return ValidationResult.error("EntityUIComponents exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += entityUIComponentsCount * 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading EntityUIComponents"); + } + } + + if ((nullBits[0] & 4) != 0) { + int combatTextUpdateOffset = buffer.getIntLE(offset + 167); + if (combatTextUpdateOffset < 0) { + return ValidationResult.error("Invalid offset for CombatTextUpdate"); + } + + int posxx = offset + 211 + combatTextUpdateOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for CombatTextUpdate"); + } + + ValidationResult combatTextUpdateResult = CombatTextUpdate.validateStructure(buffer, posxx); + if (!combatTextUpdateResult.isValid()) { + return ValidationResult.error("Invalid CombatTextUpdate: " + combatTextUpdateResult.error()); + } + + posxx += CombatTextUpdate.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits[0] & 8) != 0) { + int modelOffset = buffer.getIntLE(offset + 171); + if (modelOffset < 0) { + return ValidationResult.error("Invalid offset for Model"); + } + + int posxxx = offset + 211 + modelOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Model"); + } + + ValidationResult modelResult = Model.validateStructure(buffer, posxxx); + if (!modelResult.isValid()) { + return ValidationResult.error("Invalid Model: " + modelResult.error()); + } + + posxxx += Model.computeBytesConsumed(buffer, posxxx); + } + + if ((nullBits[0] & 16) != 0) { + int skinOffset = buffer.getIntLE(offset + 175); + if (skinOffset < 0) { + return ValidationResult.error("Invalid offset for Skin"); + } + + int posxxxx = offset + 211 + skinOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Skin"); + } + + ValidationResult skinResult = PlayerSkin.validateStructure(buffer, posxxxx); + if (!skinResult.isValid()) { + return ValidationResult.error("Invalid Skin: " + skinResult.error()); + } + + posxxxx += PlayerSkin.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits[0] & 32) != 0) { + int itemOffset = buffer.getIntLE(offset + 179); + if (itemOffset < 0) { + return ValidationResult.error("Invalid offset for Item"); + } + + int posxxxxx = offset + 211 + itemOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Item"); + } + + ValidationResult itemResult = ItemWithAllMetadata.validateStructure(buffer, posxxxxx); + if (!itemResult.isValid()) { + return ValidationResult.error("Invalid Item: " + itemResult.error()); + } + + posxxxxx += ItemWithAllMetadata.computeBytesConsumed(buffer, posxxxxx); + } + + if ((nullBits[0] & 64) != 0) { + int equipmentOffset = buffer.getIntLE(offset + 183); + if (equipmentOffset < 0) { + return ValidationResult.error("Invalid offset for Equipment"); + } + + int posxxxxxx = offset + 211 + equipmentOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Equipment"); + } + + ValidationResult equipmentResult = Equipment.validateStructure(buffer, posxxxxxx); + if (!equipmentResult.isValid()) { + return ValidationResult.error("Invalid Equipment: " + equipmentResult.error()); + } + + posxxxxxx += Equipment.computeBytesConsumed(buffer, posxxxxxx); + } + + if ((nullBits[0] & 128) != 0) { + int entityStatUpdatesOffset = buffer.getIntLE(offset + 187); + if (entityStatUpdatesOffset < 0) { + return ValidationResult.error("Invalid offset for EntityStatUpdates"); + } + + int posxxxxxxx = offset + 211 + entityStatUpdatesOffset; + if (posxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for EntityStatUpdates"); + } + + int entityStatUpdatesCount = VarInt.peek(buffer, posxxxxxxx); + if (entityStatUpdatesCount < 0) { + return ValidationResult.error("Invalid dictionary count for EntityStatUpdates"); + } + + if (entityStatUpdatesCount > 4096000) { + return ValidationResult.error("EntityStatUpdates exceeds max length 4096000"); + } + + posxxxxxxx += VarInt.length(buffer, posxxxxxxx); + + for (int i = 0; i < entityStatUpdatesCount; i++) { + posxxxxxxx += 4; + if (posxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + int valueArrCount = VarInt.peek(buffer, posxxxxxxx); + if (valueArrCount < 0) { + return ValidationResult.error("Invalid array count for value"); + } + + posxxxxxxx += VarInt.length(buffer, posxxxxxxx); + + for (int valueArrIdx = 0; valueArrIdx < valueArrCount; valueArrIdx++) { + posxxxxxxx += EntityStatUpdate.computeBytesConsumed(buffer, posxxxxxxx); + } + } + } + + if ((nullBits[1] & 4) != 0) { + int entityEffectUpdatesOffset = buffer.getIntLE(offset + 191); + if (entityEffectUpdatesOffset < 0) { + return ValidationResult.error("Invalid offset for EntityEffectUpdates"); + } + + int posxxxxxxxx = offset + 211 + entityEffectUpdatesOffset; + if (posxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for EntityEffectUpdates"); + } + + int entityEffectUpdatesCount = VarInt.peek(buffer, posxxxxxxxx); + if (entityEffectUpdatesCount < 0) { + return ValidationResult.error("Invalid array count for EntityEffectUpdates"); + } + + if (entityEffectUpdatesCount > 4096000) { + return ValidationResult.error("EntityEffectUpdates exceeds max length 4096000"); + } + + posxxxxxxxx += VarInt.length(buffer, posxxxxxxxx); + + for (int i = 0; i < entityEffectUpdatesCount; i++) { + ValidationResult structResult = EntityEffectUpdate.validateStructure(buffer, posxxxxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid EntityEffectUpdate in EntityEffectUpdates[" + i + "]: " + structResult.error()); + } + + posxxxxxxxx += EntityEffectUpdate.computeBytesConsumed(buffer, posxxxxxxxx); + } + } + + if ((nullBits[1] & 8) != 0) { + int interactionsOffset = buffer.getIntLE(offset + 195); + if (interactionsOffset < 0) { + return ValidationResult.error("Invalid offset for Interactions"); + } + + int posxxxxxxxxx = offset + 211 + interactionsOffset; + if (posxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Interactions"); + } + + int interactionsCount = VarInt.peek(buffer, posxxxxxxxxx); + if (interactionsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Interactions"); + } + + if (interactionsCount > 4096000) { + return ValidationResult.error("Interactions exceeds max length 4096000"); + } + + posxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxx); + + for (int i = 0; i < interactionsCount; i++) { + posxxxxxxxxx = ++posxxxxxxxxx + 4; + if (posxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + if ((nullBits[1] & 32) != 0) { + int soundEventIdsOffset = buffer.getIntLE(offset + 199); + if (soundEventIdsOffset < 0) { + return ValidationResult.error("Invalid offset for SoundEventIds"); + } + + int posxxxxxxxxxx = offset + 211 + soundEventIdsOffset; + if (posxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SoundEventIds"); + } + + int soundEventIdsCount = VarInt.peek(buffer, posxxxxxxxxxx); + if (soundEventIdsCount < 0) { + return ValidationResult.error("Invalid array count for SoundEventIds"); + } + + if (soundEventIdsCount > 4096000) { + return ValidationResult.error("SoundEventIds exceeds max length 4096000"); + } + + posxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxx); + posxxxxxxxxxx += soundEventIdsCount * 4; + if (posxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading SoundEventIds"); + } + } + + if ((nullBits[1] & 64) != 0) { + int interactionHintOffset = buffer.getIntLE(offset + 203); + if (interactionHintOffset < 0) { + return ValidationResult.error("Invalid offset for InteractionHint"); + } + + int posxxxxxxxxxxx = offset + 211 + interactionHintOffset; + if (posxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for InteractionHint"); + } + + int interactionHintLen = VarInt.peek(buffer, posxxxxxxxxxxx); + if (interactionHintLen < 0) { + return ValidationResult.error("Invalid string length for InteractionHint"); + } + + if (interactionHintLen > 4096000) { + return ValidationResult.error("InteractionHint exceeds max length 4096000"); + } + + posxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxx); + posxxxxxxxxxxx += interactionHintLen; + if (posxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading InteractionHint"); + } + } + + if ((nullBits[2] & 1) != 0) { + int activeAnimationsOffset = buffer.getIntLE(offset + 207); + if (activeAnimationsOffset < 0) { + return ValidationResult.error("Invalid offset for ActiveAnimations"); + } + + int posxxxxxxxxxxxx = offset + 211 + activeAnimationsOffset; + if (posxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ActiveAnimations"); + } + + int activeAnimationsCount = VarInt.peek(buffer, posxxxxxxxxxxxx); + if (activeAnimationsCount < 0) { + return ValidationResult.error("Invalid array count for ActiveAnimations"); + } + + if (activeAnimationsCount > 4096000) { + return ValidationResult.error("ActiveAnimations exceeds max length 4096000"); + } + + posxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxx); + + for (int ix = 0; ix < activeAnimationsCount; ix++) { + int strLen = VarInt.peek(buffer, posxxxxxxxxxxxx); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in ActiveAnimations"); + } + + posxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxx); + posxxxxxxxxxxxx += strLen; + if (posxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in ActiveAnimations"); + } + } + } + + return ValidationResult.OK; + } + } + + public ComponentUpdate clone() { + ComponentUpdate copy = new ComponentUpdate(); + copy.type = this.type; + copy.nameplate = this.nameplate != null ? this.nameplate.clone() : null; + copy.entityUIComponents = this.entityUIComponents != null ? Arrays.copyOf(this.entityUIComponents, this.entityUIComponents.length) : null; + copy.combatTextUpdate = this.combatTextUpdate != null ? this.combatTextUpdate.clone() : null; + copy.model = this.model != null ? this.model.clone() : null; + copy.skin = this.skin != null ? this.skin.clone() : null; + copy.item = this.item != null ? this.item.clone() : null; + copy.blockId = this.blockId; + copy.entityScale = this.entityScale; + copy.equipment = this.equipment != null ? this.equipment.clone() : null; + if (this.entityStatUpdates != null) { + Map m = new HashMap<>(); + + for (Entry e : this.entityStatUpdates.entrySet()) { + m.put(e.getKey(), Arrays.stream(e.getValue()).map(x -> x.clone()).toArray(EntityStatUpdate[]::new)); + } + + copy.entityStatUpdates = m; + } + + copy.transform = this.transform != null ? this.transform.clone() : null; + copy.movementStates = this.movementStates != null ? this.movementStates.clone() : null; + copy.entityEffectUpdates = this.entityEffectUpdates != null + ? Arrays.stream(this.entityEffectUpdates).map(ex -> ex.clone()).toArray(EntityEffectUpdate[]::new) + : null; + copy.interactions = this.interactions != null ? new HashMap<>(this.interactions) : null; + copy.dynamicLight = this.dynamicLight != null ? this.dynamicLight.clone() : null; + copy.hitboxCollisionConfigIndex = this.hitboxCollisionConfigIndex; + copy.repulsionConfigIndex = this.repulsionConfigIndex; + copy.predictionId = this.predictionId; + copy.soundEventIds = this.soundEventIds != null ? Arrays.copyOf(this.soundEventIds, this.soundEventIds.length) : null; + copy.interactionHint = this.interactionHint; + copy.mounted = this.mounted != null ? this.mounted.clone() : null; + copy.activeAnimations = this.activeAnimations != null ? Arrays.copyOf(this.activeAnimations, this.activeAnimations.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ComponentUpdate other) + ? false + : Objects.equals(this.type, other.type) + && Objects.equals(this.nameplate, other.nameplate) + && Arrays.equals(this.entityUIComponents, other.entityUIComponents) + && Objects.equals(this.combatTextUpdate, other.combatTextUpdate) + && Objects.equals(this.model, other.model) + && Objects.equals(this.skin, other.skin) + && Objects.equals(this.item, other.item) + && this.blockId == other.blockId + && this.entityScale == other.entityScale + && Objects.equals(this.equipment, other.equipment) + && Objects.equals(this.entityStatUpdates, other.entityStatUpdates) + && Objects.equals(this.transform, other.transform) + && Objects.equals(this.movementStates, other.movementStates) + && Arrays.equals((Object[])this.entityEffectUpdates, (Object[])other.entityEffectUpdates) + && Objects.equals(this.interactions, other.interactions) + && Objects.equals(this.dynamicLight, other.dynamicLight) + && this.hitboxCollisionConfigIndex == other.hitboxCollisionConfigIndex + && this.repulsionConfigIndex == other.repulsionConfigIndex + && Objects.equals(this.predictionId, other.predictionId) + && Arrays.equals(this.soundEventIds, other.soundEventIds) + && Objects.equals(this.interactionHint, other.interactionHint) + && Objects.equals(this.mounted, other.mounted) + && Arrays.equals((Object[])this.activeAnimations, (Object[])other.activeAnimations); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.type); + result = 31 * result + Objects.hashCode(this.nameplate); + result = 31 * result + Arrays.hashCode(this.entityUIComponents); + result = 31 * result + Objects.hashCode(this.combatTextUpdate); + result = 31 * result + Objects.hashCode(this.model); + result = 31 * result + Objects.hashCode(this.skin); + result = 31 * result + Objects.hashCode(this.item); + result = 31 * result + Integer.hashCode(this.blockId); + result = 31 * result + Float.hashCode(this.entityScale); + result = 31 * result + Objects.hashCode(this.equipment); + result = 31 * result + Objects.hashCode(this.entityStatUpdates); + result = 31 * result + Objects.hashCode(this.transform); + result = 31 * result + Objects.hashCode(this.movementStates); + result = 31 * result + Arrays.hashCode((Object[])this.entityEffectUpdates); + result = 31 * result + Objects.hashCode(this.interactions); + result = 31 * result + Objects.hashCode(this.dynamicLight); + result = 31 * result + Integer.hashCode(this.hitboxCollisionConfigIndex); + result = 31 * result + Integer.hashCode(this.repulsionConfigIndex); + result = 31 * result + Objects.hashCode(this.predictionId); + result = 31 * result + Arrays.hashCode(this.soundEventIds); + result = 31 * result + Objects.hashCode(this.interactionHint); + result = 31 * result + Objects.hashCode(this.mounted); + return 31 * result + Arrays.hashCode((Object[])this.activeAnimations); + } +} diff --git a/src/com/hypixel/hytale/protocol/ComponentUpdateType.java b/src/com/hypixel/hytale/protocol/ComponentUpdateType.java new file mode 100644 index 0000000..eb2b777 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ComponentUpdateType.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ComponentUpdateType { + Nameplate(0), + UIComponents(1), + CombatText(2), + Model(3), + PlayerSkin(4), + Item(5), + Block(6), + Equipment(7), + EntityStats(8), + Transform(9), + MovementStates(10), + EntityEffects(11), + Interactions(12), + DynamicLight(13), + Interactable(14), + Intangible(15), + Invulnerable(16), + RespondToHit(17), + HitboxCollision(18), + Repulsion(19), + Prediction(20), + Audio(21), + Mounted(22), + NewSpawn(23), + ActiveAnimations(24); + + public static final ComponentUpdateType[] VALUES = values(); + private final int value; + + private ComponentUpdateType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ComponentUpdateType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ComponentUpdateType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ConditionInteraction.java b/src/com/hypixel/hytale/protocol/ConditionInteraction.java new file mode 100644 index 0000000..4c7b46a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ConditionInteraction.java @@ -0,0 +1,637 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ConditionInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 2; + public static final int FIXED_BLOCK_SIZE = 26; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 46; + public static final int MAX_SIZE = 1677721600; + @Nullable + public GameMode requiredGameMode; + @Nullable + public Boolean jumping; + @Nullable + public Boolean swimming; + @Nullable + public Boolean crouching; + @Nullable + public Boolean running; + @Nullable + public Boolean flying; + + public ConditionInteraction() { + } + + public ConditionInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + @Nullable GameMode requiredGameMode, + @Nullable Boolean jumping, + @Nullable Boolean swimming, + @Nullable Boolean crouching, + @Nullable Boolean running, + @Nullable Boolean flying + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.requiredGameMode = requiredGameMode; + this.jumping = jumping; + this.swimming = swimming; + this.crouching = crouching; + this.running = running; + this.flying = flying; + } + + public ConditionInteraction(@Nonnull ConditionInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.requiredGameMode = other.requiredGameMode; + this.jumping = other.jumping; + this.swimming = other.swimming; + this.crouching = other.crouching; + this.running = other.running; + this.flying = other.flying; + } + + @Nonnull + public static ConditionInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + ConditionInteraction obj = new ConditionInteraction(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 2)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 3); + obj.runTime = buf.getFloatLE(offset + 7); + obj.cancelOnItemChange = buf.getByte(offset + 11) != 0; + obj.next = buf.getIntLE(offset + 12); + obj.failed = buf.getIntLE(offset + 16); + if ((nullBits[0] & 32) != 0) { + obj.requiredGameMode = GameMode.fromValue(buf.getByte(offset + 20)); + } + + if ((nullBits[0] & 64) != 0) { + obj.jumping = buf.getByte(offset + 21) != 0; + } + + if ((nullBits[0] & 128) != 0) { + obj.swimming = buf.getByte(offset + 22) != 0; + } + + if ((nullBits[1] & 1) != 0) { + obj.crouching = buf.getByte(offset + 23) != 0; + } + + if ((nullBits[1] & 2) != 0) { + obj.running = buf.getByte(offset + 24) != 0; + } + + if ((nullBits[1] & 4) != 0) { + obj.flying = buf.getByte(offset + 25) != 0; + } + + if ((nullBits[0] & 1) != 0) { + int varPos0 = offset + 46 + buf.getIntLE(offset + 26); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits[0] & 2) != 0) { + int varPos1 = offset + 46 + buf.getIntLE(offset + 30); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits[0] & 4) != 0) { + int varPos2 = offset + 46 + buf.getIntLE(offset + 34); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits[0] & 8) != 0) { + int varPos3 = offset + 46 + buf.getIntLE(offset + 38); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits[0] & 16) != 0) { + int varPos4 = offset + 46 + buf.getIntLE(offset + 42); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + int maxEnd = 46; + if ((nullBits[0] & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 26); + int pos0 = offset + 46 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits[0] & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 30); + int pos1 = offset + 46 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits[0] & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 34); + int pos2 = offset + 46 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits[0] & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 38); + int pos3 = offset + 46 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits[0] & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 42); + int pos4 = offset + 46 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte[] nullBits = new byte[2]; + if (this.effects != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.settings != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.rules != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.tags != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.camera != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.requiredGameMode != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.jumping != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.swimming != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.crouching != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + if (this.running != null) { + nullBits[1] = (byte)(nullBits[1] | 2); + } + + if (this.flying != null) { + nullBits[1] = (byte)(nullBits[1] | 4); + } + + buf.writeBytes(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + if (this.requiredGameMode != null) { + buf.writeByte(this.requiredGameMode.getValue()); + } else { + buf.writeZero(1); + } + + if (this.jumping != null) { + buf.writeByte(this.jumping ? 1 : 0); + } else { + buf.writeZero(1); + } + + if (this.swimming != null) { + buf.writeByte(this.swimming ? 1 : 0); + } else { + buf.writeZero(1); + } + + if (this.crouching != null) { + buf.writeByte(this.crouching ? 1 : 0); + } else { + buf.writeZero(1); + } + + if (this.running != null) { + buf.writeByte(this.running ? 1 : 0); + } else { + buf.writeZero(1); + } + + if (this.flying != null) { + buf.writeByte(this.flying ? 1 : 0); + } else { + buf.writeZero(1); + } + + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 46; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 46) { + return ValidationResult.error("Buffer too small: expected at least 46 bytes"); + } else { + byte[] nullBits = PacketIO.readBytes(buffer, offset, 2); + if ((nullBits[0] & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 26); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 46 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits[0] & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 30); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 46 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits[0] & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 34); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 46 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits[0] & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 38); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 46 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits[0] & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 42); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 46 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public ConditionInteraction clone() { + ConditionInteraction copy = new ConditionInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.requiredGameMode = this.requiredGameMode; + copy.jumping = this.jumping; + copy.swimming = this.swimming; + copy.crouching = this.crouching; + copy.running = this.running; + copy.flying = this.flying; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ConditionInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && Objects.equals(this.requiredGameMode, other.requiredGameMode) + && Objects.equals(this.jumping, other.jumping) + && Objects.equals(this.swimming, other.swimming) + && Objects.equals(this.crouching, other.crouching) + && Objects.equals(this.running, other.running) + && Objects.equals(this.flying, other.flying); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Objects.hashCode(this.requiredGameMode); + result = 31 * result + Objects.hashCode(this.jumping); + result = 31 * result + Objects.hashCode(this.swimming); + result = 31 * result + Objects.hashCode(this.crouching); + result = 31 * result + Objects.hashCode(this.running); + return 31 * result + Objects.hashCode(this.flying); + } +} diff --git a/src/com/hypixel/hytale/protocol/ConnectedBlockRuleSet.java b/src/com/hypixel/hytale/protocol/ConnectedBlockRuleSet.java new file mode 100644 index 0000000..c5b5dce --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ConnectedBlockRuleSet.java @@ -0,0 +1,195 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ConnectedBlockRuleSet { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 10; + public static final int MAX_SIZE = 65536114; + @Nonnull + public ConnectedBlockRuleSetType type = ConnectedBlockRuleSetType.Stair; + @Nullable + public StairConnectedBlockRuleSet stair; + @Nullable + public RoofConnectedBlockRuleSet roof; + + public ConnectedBlockRuleSet() { + } + + public ConnectedBlockRuleSet(@Nonnull ConnectedBlockRuleSetType type, @Nullable StairConnectedBlockRuleSet stair, @Nullable RoofConnectedBlockRuleSet roof) { + this.type = type; + this.stair = stair; + this.roof = roof; + } + + public ConnectedBlockRuleSet(@Nonnull ConnectedBlockRuleSet other) { + this.type = other.type; + this.stair = other.stair; + this.roof = other.roof; + } + + @Nonnull + public static ConnectedBlockRuleSet deserialize(@Nonnull ByteBuf buf, int offset) { + ConnectedBlockRuleSet obj = new ConnectedBlockRuleSet(); + byte nullBits = buf.getByte(offset); + obj.type = ConnectedBlockRuleSetType.fromValue(buf.getByte(offset + 1)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 10 + buf.getIntLE(offset + 2); + obj.stair = StairConnectedBlockRuleSet.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 10 + buf.getIntLE(offset + 6); + obj.roof = RoofConnectedBlockRuleSet.deserialize(buf, varPos1); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 10; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 2); + int pos0 = offset + 10 + fieldOffset0; + pos0 += StairConnectedBlockRuleSet.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 6); + int pos1 = offset + 10 + fieldOffset1; + pos1 += RoofConnectedBlockRuleSet.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.stair != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.roof != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + int stairOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int roofOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.stair != null) { + buf.setIntLE(stairOffsetSlot, buf.writerIndex() - varBlockStart); + this.stair.serialize(buf); + } else { + buf.setIntLE(stairOffsetSlot, -1); + } + + if (this.roof != null) { + buf.setIntLE(roofOffsetSlot, buf.writerIndex() - varBlockStart); + this.roof.serialize(buf); + } else { + buf.setIntLE(roofOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 10; + if (this.stair != null) { + size += this.stair.computeSize(); + } + + if (this.roof != null) { + size += this.roof.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 10) { + return ValidationResult.error("Buffer too small: expected at least 10 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int stairOffset = buffer.getIntLE(offset + 2); + if (stairOffset < 0) { + return ValidationResult.error("Invalid offset for Stair"); + } + + int pos = offset + 10 + stairOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Stair"); + } + + ValidationResult stairResult = StairConnectedBlockRuleSet.validateStructure(buffer, pos); + if (!stairResult.isValid()) { + return ValidationResult.error("Invalid Stair: " + stairResult.error()); + } + + pos += StairConnectedBlockRuleSet.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int roofOffset = buffer.getIntLE(offset + 6); + if (roofOffset < 0) { + return ValidationResult.error("Invalid offset for Roof"); + } + + int posx = offset + 10 + roofOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Roof"); + } + + ValidationResult roofResult = RoofConnectedBlockRuleSet.validateStructure(buffer, posx); + if (!roofResult.isValid()) { + return ValidationResult.error("Invalid Roof: " + roofResult.error()); + } + + posx += RoofConnectedBlockRuleSet.computeBytesConsumed(buffer, posx); + } + + return ValidationResult.OK; + } + } + + public ConnectedBlockRuleSet clone() { + ConnectedBlockRuleSet copy = new ConnectedBlockRuleSet(); + copy.type = this.type; + copy.stair = this.stair != null ? this.stair.clone() : null; + copy.roof = this.roof != null ? this.roof.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ConnectedBlockRuleSet other) + ? false + : Objects.equals(this.type, other.type) && Objects.equals(this.stair, other.stair) && Objects.equals(this.roof, other.roof); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.stair, this.roof); + } +} diff --git a/src/com/hypixel/hytale/protocol/ConnectedBlockRuleSetType.java b/src/com/hypixel/hytale/protocol/ConnectedBlockRuleSetType.java new file mode 100644 index 0000000..b490bc8 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ConnectedBlockRuleSetType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ConnectedBlockRuleSetType { + Stair(0), + Roof(1); + + public static final ConnectedBlockRuleSetType[] VALUES = values(); + private final int value; + + private ConnectedBlockRuleSetType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ConnectedBlockRuleSetType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ConnectedBlockRuleSetType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/CooldownConditionInteraction.java b/src/com/hypixel/hytale/protocol/CooldownConditionInteraction.java new file mode 100644 index 0000000..5bcded3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CooldownConditionInteraction.java @@ -0,0 +1,581 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CooldownConditionInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 19; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 43; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String cooldownId; + + public CooldownConditionInteraction() { + } + + public CooldownConditionInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + @Nullable String cooldownId + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.cooldownId = cooldownId; + } + + public CooldownConditionInteraction(@Nonnull CooldownConditionInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.cooldownId = other.cooldownId; + } + + @Nonnull + public static CooldownConditionInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + CooldownConditionInteraction obj = new CooldownConditionInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 43 + buf.getIntLE(offset + 19); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 43 + buf.getIntLE(offset + 23); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 43 + buf.getIntLE(offset + 27); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 43 + buf.getIntLE(offset + 31); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 43 + buf.getIntLE(offset + 35); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 43 + buf.getIntLE(offset + 39); + int cooldownIdLen = VarInt.peek(buf, varPos5); + if (cooldownIdLen < 0) { + throw ProtocolException.negativeLength("CooldownId", cooldownIdLen); + } + + if (cooldownIdLen > 4096000) { + throw ProtocolException.stringTooLong("CooldownId", cooldownIdLen, 4096000); + } + + obj.cooldownId = PacketIO.readVarString(buf, varPos5, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 43; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 19); + int pos0 = offset + 43 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 23); + int pos1 = offset + 43 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 27); + int pos2 = offset + 43 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 31); + int pos3 = offset + 43 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 35); + int pos4 = offset + 43 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 39); + int pos5 = offset + 43 + fieldOffset5; + int sl = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + sl; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.cooldownId != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cooldownIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.cooldownId != null) { + buf.setIntLE(cooldownIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.cooldownId, 4096000); + } else { + buf.setIntLE(cooldownIdOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 43; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.cooldownId != null) { + size += PacketIO.stringSize(this.cooldownId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 43) { + return ValidationResult.error("Buffer too small: expected at least 43 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 19); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 43 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 23); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 43 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 27); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 43 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 31); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 43 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 35); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 43 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int cooldownIdOffset = buffer.getIntLE(offset + 39); + if (cooldownIdOffset < 0) { + return ValidationResult.error("Invalid offset for CooldownId"); + } + + int posxxxxx = offset + 43 + cooldownIdOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for CooldownId"); + } + + int cooldownIdLen = VarInt.peek(buffer, posxxxxx); + if (cooldownIdLen < 0) { + return ValidationResult.error("Invalid string length for CooldownId"); + } + + if (cooldownIdLen > 4096000) { + return ValidationResult.error("CooldownId exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += cooldownIdLen; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading CooldownId"); + } + } + + return ValidationResult.OK; + } + } + + public CooldownConditionInteraction clone() { + CooldownConditionInteraction copy = new CooldownConditionInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.cooldownId = this.cooldownId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CooldownConditionInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && Objects.equals(this.cooldownId, other.cooldownId); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + return 31 * result + Objects.hashCode(this.cooldownId); + } +} diff --git a/src/com/hypixel/hytale/protocol/Cosmetic.java b/src/com/hypixel/hytale/protocol/Cosmetic.java new file mode 100644 index 0000000..79fd58e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Cosmetic.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum Cosmetic { + Haircut(0), + FacialHair(1), + Undertop(2), + Overtop(3), + Pants(4), + Overpants(5), + Shoes(6), + Gloves(7), + Cape(8), + HeadAccessory(9), + FaceAccessory(10), + EarAccessory(11), + Ear(12); + + public static final Cosmetic[] VALUES = values(); + private final int value; + + private Cosmetic(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static Cosmetic fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("Cosmetic", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/CraftingRecipe.java b/src/com/hypixel/hytale/protocol/CraftingRecipe.java new file mode 100644 index 0000000..47029ce --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CraftingRecipe.java @@ -0,0 +1,576 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CraftingRecipe { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 10; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 30; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + @Nullable + public MaterialQuantity[] inputs; + @Nullable + public MaterialQuantity[] outputs; + @Nullable + public MaterialQuantity primaryOutput; + @Nullable + public BenchRequirement[] benchRequirement; + public boolean knowledgeRequired; + public float timeSeconds; + public int requiredMemoriesLevel; + + public CraftingRecipe() { + } + + public CraftingRecipe( + @Nullable String id, + @Nullable MaterialQuantity[] inputs, + @Nullable MaterialQuantity[] outputs, + @Nullable MaterialQuantity primaryOutput, + @Nullable BenchRequirement[] benchRequirement, + boolean knowledgeRequired, + float timeSeconds, + int requiredMemoriesLevel + ) { + this.id = id; + this.inputs = inputs; + this.outputs = outputs; + this.primaryOutput = primaryOutput; + this.benchRequirement = benchRequirement; + this.knowledgeRequired = knowledgeRequired; + this.timeSeconds = timeSeconds; + this.requiredMemoriesLevel = requiredMemoriesLevel; + } + + public CraftingRecipe(@Nonnull CraftingRecipe other) { + this.id = other.id; + this.inputs = other.inputs; + this.outputs = other.outputs; + this.primaryOutput = other.primaryOutput; + this.benchRequirement = other.benchRequirement; + this.knowledgeRequired = other.knowledgeRequired; + this.timeSeconds = other.timeSeconds; + this.requiredMemoriesLevel = other.requiredMemoriesLevel; + } + + @Nonnull + public static CraftingRecipe deserialize(@Nonnull ByteBuf buf, int offset) { + CraftingRecipe obj = new CraftingRecipe(); + byte nullBits = buf.getByte(offset); + obj.knowledgeRequired = buf.getByte(offset + 1) != 0; + obj.timeSeconds = buf.getFloatLE(offset + 2); + obj.requiredMemoriesLevel = buf.getIntLE(offset + 6); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 30 + buf.getIntLE(offset + 10); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 30 + buf.getIntLE(offset + 14); + int inputsCount = VarInt.peek(buf, varPos1); + if (inputsCount < 0) { + throw ProtocolException.negativeLength("Inputs", inputsCount); + } + + if (inputsCount > 4096000) { + throw ProtocolException.arrayTooLong("Inputs", inputsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + inputsCount * 9L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Inputs", varPos1 + varIntLen + inputsCount * 9, buf.readableBytes()); + } + + obj.inputs = new MaterialQuantity[inputsCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < inputsCount; i++) { + obj.inputs[i] = MaterialQuantity.deserialize(buf, elemPos); + elemPos += MaterialQuantity.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 30 + buf.getIntLE(offset + 18); + int outputsCount = VarInt.peek(buf, varPos2); + if (outputsCount < 0) { + throw ProtocolException.negativeLength("Outputs", outputsCount); + } + + if (outputsCount > 4096000) { + throw ProtocolException.arrayTooLong("Outputs", outputsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + outputsCount * 9L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Outputs", varPos2 + varIntLen + outputsCount * 9, buf.readableBytes()); + } + + obj.outputs = new MaterialQuantity[outputsCount]; + int elemPos = varPos2 + varIntLen; + + for (int i = 0; i < outputsCount; i++) { + obj.outputs[i] = MaterialQuantity.deserialize(buf, elemPos); + elemPos += MaterialQuantity.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 30 + buf.getIntLE(offset + 22); + obj.primaryOutput = MaterialQuantity.deserialize(buf, varPos3); + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 30 + buf.getIntLE(offset + 26); + int benchRequirementCount = VarInt.peek(buf, varPos4); + if (benchRequirementCount < 0) { + throw ProtocolException.negativeLength("BenchRequirement", benchRequirementCount); + } + + if (benchRequirementCount > 4096000) { + throw ProtocolException.arrayTooLong("BenchRequirement", benchRequirementCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos4); + if (varPos4 + varIntLen + benchRequirementCount * 6L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("BenchRequirement", varPos4 + varIntLen + benchRequirementCount * 6, buf.readableBytes()); + } + + obj.benchRequirement = new BenchRequirement[benchRequirementCount]; + int elemPos = varPos4 + varIntLen; + + for (int i = 0; i < benchRequirementCount; i++) { + obj.benchRequirement[i] = BenchRequirement.deserialize(buf, elemPos); + elemPos += BenchRequirement.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 30; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 10); + int pos0 = offset + 30 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 14); + int pos1 = offset + 30 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += MaterialQuantity.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 18); + int pos2 = offset + 30 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2); + + for (int i = 0; i < arrLen; i++) { + pos2 += MaterialQuantity.computeBytesConsumed(buf, pos2); + } + + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 22); + int pos3 = offset + 30 + fieldOffset3; + pos3 += MaterialQuantity.computeBytesConsumed(buf, pos3); + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 26); + int pos4 = offset + 30 + fieldOffset4; + int arrLen = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4); + + for (int i = 0; i < arrLen; i++) { + pos4 += BenchRequirement.computeBytesConsumed(buf, pos4); + } + + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.inputs != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.outputs != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.primaryOutput != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.benchRequirement != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.knowledgeRequired ? 1 : 0); + buf.writeFloatLE(this.timeSeconds); + buf.writeIntLE(this.requiredMemoriesLevel); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int inputsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int outputsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int primaryOutputOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int benchRequirementOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.inputs != null) { + buf.setIntLE(inputsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.inputs.length > 4096000) { + throw ProtocolException.arrayTooLong("Inputs", this.inputs.length, 4096000); + } + + VarInt.write(buf, this.inputs.length); + + for (MaterialQuantity item : this.inputs) { + item.serialize(buf); + } + } else { + buf.setIntLE(inputsOffsetSlot, -1); + } + + if (this.outputs != null) { + buf.setIntLE(outputsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.outputs.length > 4096000) { + throw ProtocolException.arrayTooLong("Outputs", this.outputs.length, 4096000); + } + + VarInt.write(buf, this.outputs.length); + + for (MaterialQuantity item : this.outputs) { + item.serialize(buf); + } + } else { + buf.setIntLE(outputsOffsetSlot, -1); + } + + if (this.primaryOutput != null) { + buf.setIntLE(primaryOutputOffsetSlot, buf.writerIndex() - varBlockStart); + this.primaryOutput.serialize(buf); + } else { + buf.setIntLE(primaryOutputOffsetSlot, -1); + } + + if (this.benchRequirement != null) { + buf.setIntLE(benchRequirementOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.benchRequirement.length > 4096000) { + throw ProtocolException.arrayTooLong("BenchRequirement", this.benchRequirement.length, 4096000); + } + + VarInt.write(buf, this.benchRequirement.length); + + for (BenchRequirement item : this.benchRequirement) { + item.serialize(buf); + } + } else { + buf.setIntLE(benchRequirementOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 30; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.inputs != null) { + int inputsSize = 0; + + for (MaterialQuantity elem : this.inputs) { + inputsSize += elem.computeSize(); + } + + size += VarInt.size(this.inputs.length) + inputsSize; + } + + if (this.outputs != null) { + int outputsSize = 0; + + for (MaterialQuantity elem : this.outputs) { + outputsSize += elem.computeSize(); + } + + size += VarInt.size(this.outputs.length) + outputsSize; + } + + if (this.primaryOutput != null) { + size += this.primaryOutput.computeSize(); + } + + if (this.benchRequirement != null) { + int benchRequirementSize = 0; + + for (BenchRequirement elem : this.benchRequirement) { + benchRequirementSize += elem.computeSize(); + } + + size += VarInt.size(this.benchRequirement.length) + benchRequirementSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 30) { + return ValidationResult.error("Buffer too small: expected at least 30 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 10); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 30 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int inputsOffset = buffer.getIntLE(offset + 14); + if (inputsOffset < 0) { + return ValidationResult.error("Invalid offset for Inputs"); + } + + int posx = offset + 30 + inputsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Inputs"); + } + + int inputsCount = VarInt.peek(buffer, posx); + if (inputsCount < 0) { + return ValidationResult.error("Invalid array count for Inputs"); + } + + if (inputsCount > 4096000) { + return ValidationResult.error("Inputs exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < inputsCount; i++) { + ValidationResult structResult = MaterialQuantity.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid MaterialQuantity in Inputs[" + i + "]: " + structResult.error()); + } + + posx += MaterialQuantity.computeBytesConsumed(buffer, posx); + } + } + + if ((nullBits & 4) != 0) { + int outputsOffset = buffer.getIntLE(offset + 18); + if (outputsOffset < 0) { + return ValidationResult.error("Invalid offset for Outputs"); + } + + int posxx = offset + 30 + outputsOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Outputs"); + } + + int outputsCount = VarInt.peek(buffer, posxx); + if (outputsCount < 0) { + return ValidationResult.error("Invalid array count for Outputs"); + } + + if (outputsCount > 4096000) { + return ValidationResult.error("Outputs exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + + for (int i = 0; i < outputsCount; i++) { + ValidationResult structResult = MaterialQuantity.validateStructure(buffer, posxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid MaterialQuantity in Outputs[" + i + "]: " + structResult.error()); + } + + posxx += MaterialQuantity.computeBytesConsumed(buffer, posxx); + } + } + + if ((nullBits & 8) != 0) { + int primaryOutputOffset = buffer.getIntLE(offset + 22); + if (primaryOutputOffset < 0) { + return ValidationResult.error("Invalid offset for PrimaryOutput"); + } + + int posxxx = offset + 30 + primaryOutputOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for PrimaryOutput"); + } + + ValidationResult primaryOutputResult = MaterialQuantity.validateStructure(buffer, posxxx); + if (!primaryOutputResult.isValid()) { + return ValidationResult.error("Invalid PrimaryOutput: " + primaryOutputResult.error()); + } + + posxxx += MaterialQuantity.computeBytesConsumed(buffer, posxxx); + } + + if ((nullBits & 16) != 0) { + int benchRequirementOffset = buffer.getIntLE(offset + 26); + if (benchRequirementOffset < 0) { + return ValidationResult.error("Invalid offset for BenchRequirement"); + } + + int posxxxx = offset + 30 + benchRequirementOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BenchRequirement"); + } + + int benchRequirementCount = VarInt.peek(buffer, posxxxx); + if (benchRequirementCount < 0) { + return ValidationResult.error("Invalid array count for BenchRequirement"); + } + + if (benchRequirementCount > 4096000) { + return ValidationResult.error("BenchRequirement exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + + for (int i = 0; i < benchRequirementCount; i++) { + ValidationResult structResult = BenchRequirement.validateStructure(buffer, posxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid BenchRequirement in BenchRequirement[" + i + "]: " + structResult.error()); + } + + posxxxx += BenchRequirement.computeBytesConsumed(buffer, posxxxx); + } + } + + return ValidationResult.OK; + } + } + + public CraftingRecipe clone() { + CraftingRecipe copy = new CraftingRecipe(); + copy.id = this.id; + copy.inputs = this.inputs != null ? Arrays.stream(this.inputs).map(e -> e.clone()).toArray(MaterialQuantity[]::new) : null; + copy.outputs = this.outputs != null ? Arrays.stream(this.outputs).map(e -> e.clone()).toArray(MaterialQuantity[]::new) : null; + copy.primaryOutput = this.primaryOutput != null ? this.primaryOutput.clone() : null; + copy.benchRequirement = this.benchRequirement != null ? Arrays.stream(this.benchRequirement).map(e -> e.clone()).toArray(BenchRequirement[]::new) : null; + copy.knowledgeRequired = this.knowledgeRequired; + copy.timeSeconds = this.timeSeconds; + copy.requiredMemoriesLevel = this.requiredMemoriesLevel; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CraftingRecipe other) + ? false + : Objects.equals(this.id, other.id) + && Arrays.equals((Object[])this.inputs, (Object[])other.inputs) + && Arrays.equals((Object[])this.outputs, (Object[])other.outputs) + && Objects.equals(this.primaryOutput, other.primaryOutput) + && Arrays.equals((Object[])this.benchRequirement, (Object[])other.benchRequirement) + && this.knowledgeRequired == other.knowledgeRequired + && this.timeSeconds == other.timeSeconds + && this.requiredMemoriesLevel == other.requiredMemoriesLevel; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.id); + result = 31 * result + Arrays.hashCode((Object[])this.inputs); + result = 31 * result + Arrays.hashCode((Object[])this.outputs); + result = 31 * result + Objects.hashCode(this.primaryOutput); + result = 31 * result + Arrays.hashCode((Object[])this.benchRequirement); + result = 31 * result + Boolean.hashCode(this.knowledgeRequired); + result = 31 * result + Float.hashCode(this.timeSeconds); + return 31 * result + Integer.hashCode(this.requiredMemoriesLevel); + } +} diff --git a/src/com/hypixel/hytale/protocol/CurveType.java b/src/com/hypixel/hytale/protocol/CurveType.java new file mode 100644 index 0000000..af950fa --- /dev/null +++ b/src/com/hypixel/hytale/protocol/CurveType.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum CurveType { + Linear(0), + QuartIn(1), + QuartOut(2), + QuartInOut(3); + + public static final CurveType[] VALUES = values(); + private final int value; + + private CurveType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static CurveType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("CurveType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/DamageCause.java b/src/com/hypixel/hytale/protocol/DamageCause.java new file mode 100644 index 0000000..da7052b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/DamageCause.java @@ -0,0 +1,225 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DamageCause { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 32768019; + @Nullable + public String id; + @Nullable + public String damageTextColor; + + public DamageCause() { + } + + public DamageCause(@Nullable String id, @Nullable String damageTextColor) { + this.id = id; + this.damageTextColor = damageTextColor; + } + + public DamageCause(@Nonnull DamageCause other) { + this.id = other.id; + this.damageTextColor = other.damageTextColor; + } + + @Nonnull + public static DamageCause deserialize(@Nonnull ByteBuf buf, int offset) { + DamageCause obj = new DamageCause(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int damageTextColorLen = VarInt.peek(buf, varPos1); + if (damageTextColorLen < 0) { + throw ProtocolException.negativeLength("DamageTextColor", damageTextColorLen); + } + + if (damageTextColorLen > 4096000) { + throw ProtocolException.stringTooLong("DamageTextColor", damageTextColorLen, 4096000); + } + + obj.damageTextColor = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.damageTextColor != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int damageTextColorOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.damageTextColor != null) { + buf.setIntLE(damageTextColorOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.damageTextColor, 4096000); + } else { + buf.setIntLE(damageTextColorOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 9; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.damageTextColor != null) { + size += PacketIO.stringSize(this.damageTextColor); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 1); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 9 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int damageTextColorOffset = buffer.getIntLE(offset + 5); + if (damageTextColorOffset < 0) { + return ValidationResult.error("Invalid offset for DamageTextColor"); + } + + int posx = offset + 9 + damageTextColorOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for DamageTextColor"); + } + + int damageTextColorLen = VarInt.peek(buffer, posx); + if (damageTextColorLen < 0) { + return ValidationResult.error("Invalid string length for DamageTextColor"); + } + + if (damageTextColorLen > 4096000) { + return ValidationResult.error("DamageTextColor exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += damageTextColorLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading DamageTextColor"); + } + } + + return ValidationResult.OK; + } + } + + public DamageCause clone() { + DamageCause copy = new DamageCause(); + copy.id = this.id; + copy.damageTextColor = this.damageTextColor; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof DamageCause other) ? false : Objects.equals(this.id, other.id) && Objects.equals(this.damageTextColor, other.damageTextColor); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.damageTextColor); + } +} diff --git a/src/com/hypixel/hytale/protocol/DamageEffects.java b/src/com/hypixel/hytale/protocol/DamageEffects.java new file mode 100644 index 0000000..aa964d6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/DamageEffects.java @@ -0,0 +1,307 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DamageEffects { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 1677721600; + @Nullable + public ModelParticle[] modelParticles; + @Nullable + public WorldParticle[] worldParticles; + public int soundEventIndex; + + public DamageEffects() { + } + + public DamageEffects(@Nullable ModelParticle[] modelParticles, @Nullable WorldParticle[] worldParticles, int soundEventIndex) { + this.modelParticles = modelParticles; + this.worldParticles = worldParticles; + this.soundEventIndex = soundEventIndex; + } + + public DamageEffects(@Nonnull DamageEffects other) { + this.modelParticles = other.modelParticles; + this.worldParticles = other.worldParticles; + this.soundEventIndex = other.soundEventIndex; + } + + @Nonnull + public static DamageEffects deserialize(@Nonnull ByteBuf buf, int offset) { + DamageEffects obj = new DamageEffects(); + byte nullBits = buf.getByte(offset); + obj.soundEventIndex = buf.getIntLE(offset + 1); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 5); + int modelParticlesCount = VarInt.peek(buf, varPos0); + if (modelParticlesCount < 0) { + throw ProtocolException.negativeLength("ModelParticles", modelParticlesCount); + } + + if (modelParticlesCount > 4096000) { + throw ProtocolException.arrayTooLong("ModelParticles", modelParticlesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + modelParticlesCount * 34L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("ModelParticles", varPos0 + varIntLen + modelParticlesCount * 34, buf.readableBytes()); + } + + obj.modelParticles = new ModelParticle[modelParticlesCount]; + int elemPos = varPos0 + varIntLen; + + for (int i = 0; i < modelParticlesCount; i++) { + obj.modelParticles[i] = ModelParticle.deserialize(buf, elemPos); + elemPos += ModelParticle.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 9); + int worldParticlesCount = VarInt.peek(buf, varPos1); + if (worldParticlesCount < 0) { + throw ProtocolException.negativeLength("WorldParticles", worldParticlesCount); + } + + if (worldParticlesCount > 4096000) { + throw ProtocolException.arrayTooLong("WorldParticles", worldParticlesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + worldParticlesCount * 32L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("WorldParticles", varPos1 + varIntLen + worldParticlesCount * 32, buf.readableBytes()); + } + + obj.worldParticles = new WorldParticle[worldParticlesCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < worldParticlesCount; i++) { + obj.worldParticles[i] = WorldParticle.deserialize(buf, elemPos); + elemPos += WorldParticle.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 5); + int pos0 = offset + 13 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < arrLen; i++) { + pos0 += ModelParticle.computeBytesConsumed(buf, pos0); + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 9); + int pos1 = offset + 13 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += WorldParticle.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.modelParticles != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.worldParticles != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.soundEventIndex); + int modelParticlesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int worldParticlesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.modelParticles != null) { + buf.setIntLE(modelParticlesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.modelParticles.length > 4096000) { + throw ProtocolException.arrayTooLong("ModelParticles", this.modelParticles.length, 4096000); + } + + VarInt.write(buf, this.modelParticles.length); + + for (ModelParticle item : this.modelParticles) { + item.serialize(buf); + } + } else { + buf.setIntLE(modelParticlesOffsetSlot, -1); + } + + if (this.worldParticles != null) { + buf.setIntLE(worldParticlesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.worldParticles.length > 4096000) { + throw ProtocolException.arrayTooLong("WorldParticles", this.worldParticles.length, 4096000); + } + + VarInt.write(buf, this.worldParticles.length); + + for (WorldParticle item : this.worldParticles) { + item.serialize(buf); + } + } else { + buf.setIntLE(worldParticlesOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 13; + if (this.modelParticles != null) { + int modelParticlesSize = 0; + + for (ModelParticle elem : this.modelParticles) { + modelParticlesSize += elem.computeSize(); + } + + size += VarInt.size(this.modelParticles.length) + modelParticlesSize; + } + + if (this.worldParticles != null) { + int worldParticlesSize = 0; + + for (WorldParticle elem : this.worldParticles) { + worldParticlesSize += elem.computeSize(); + } + + size += VarInt.size(this.worldParticles.length) + worldParticlesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int modelParticlesOffset = buffer.getIntLE(offset + 5); + if (modelParticlesOffset < 0) { + return ValidationResult.error("Invalid offset for ModelParticles"); + } + + int pos = offset + 13 + modelParticlesOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ModelParticles"); + } + + int modelParticlesCount = VarInt.peek(buffer, pos); + if (modelParticlesCount < 0) { + return ValidationResult.error("Invalid array count for ModelParticles"); + } + + if (modelParticlesCount > 4096000) { + return ValidationResult.error("ModelParticles exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < modelParticlesCount; i++) { + ValidationResult structResult = ModelParticle.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelParticle in ModelParticles[" + i + "]: " + structResult.error()); + } + + pos += ModelParticle.computeBytesConsumed(buffer, pos); + } + } + + if ((nullBits & 2) != 0) { + int worldParticlesOffset = buffer.getIntLE(offset + 9); + if (worldParticlesOffset < 0) { + return ValidationResult.error("Invalid offset for WorldParticles"); + } + + int posx = offset + 13 + worldParticlesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for WorldParticles"); + } + + int worldParticlesCount = VarInt.peek(buffer, posx); + if (worldParticlesCount < 0) { + return ValidationResult.error("Invalid array count for WorldParticles"); + } + + if (worldParticlesCount > 4096000) { + return ValidationResult.error("WorldParticles exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < worldParticlesCount; i++) { + ValidationResult structResult = WorldParticle.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid WorldParticle in WorldParticles[" + i + "]: " + structResult.error()); + } + + posx += WorldParticle.computeBytesConsumed(buffer, posx); + } + } + + return ValidationResult.OK; + } + } + + public DamageEffects clone() { + DamageEffects copy = new DamageEffects(); + copy.modelParticles = this.modelParticles != null ? Arrays.stream(this.modelParticles).map(e -> e.clone()).toArray(ModelParticle[]::new) : null; + copy.worldParticles = this.worldParticles != null ? Arrays.stream(this.worldParticles).map(e -> e.clone()).toArray(WorldParticle[]::new) : null; + copy.soundEventIndex = this.soundEventIndex; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof DamageEffects other) + ? false + : Arrays.equals((Object[])this.modelParticles, (Object[])other.modelParticles) + && Arrays.equals((Object[])this.worldParticles, (Object[])other.worldParticles) + && this.soundEventIndex == other.soundEventIndex; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode((Object[])this.modelParticles); + result = 31 * result + Arrays.hashCode((Object[])this.worldParticles); + return 31 * result + Integer.hashCode(this.soundEventIndex); + } +} diff --git a/src/com/hypixel/hytale/protocol/DamageEntityInteraction.java b/src/com/hypixel/hytale/protocol/DamageEntityInteraction.java new file mode 100644 index 0000000..7dab7a9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/DamageEntityInteraction.java @@ -0,0 +1,940 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DamageEntityInteraction extends Interaction { + public static final int NULLABLE_BIT_FIELD_SIZE = 2; + public static final int FIXED_BLOCK_SIZE = 24; + public static final int VARIABLE_FIELD_COUNT = 9; + public static final int VARIABLE_BLOCK_START = 60; + public static final int MAX_SIZE = 1677721600; + public int next = Integer.MIN_VALUE; + public int failed = Integer.MIN_VALUE; + public int blocked = Integer.MIN_VALUE; + @Nullable + public DamageEffects damageEffects; + @Nullable + public AngledDamage[] angledDamage; + @Nullable + public Map targetedDamage; + @Nullable + public EntityStatOnHit[] entityStatsOnHit; + + public DamageEntityInteraction() { + } + + public DamageEntityInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + int blocked, + @Nullable DamageEffects damageEffects, + @Nullable AngledDamage[] angledDamage, + @Nullable Map targetedDamage, + @Nullable EntityStatOnHit[] entityStatsOnHit + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.blocked = blocked; + this.damageEffects = damageEffects; + this.angledDamage = angledDamage; + this.targetedDamage = targetedDamage; + this.entityStatsOnHit = entityStatsOnHit; + } + + public DamageEntityInteraction(@Nonnull DamageEntityInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.blocked = other.blocked; + this.damageEffects = other.damageEffects; + this.angledDamage = other.angledDamage; + this.targetedDamage = other.targetedDamage; + this.entityStatsOnHit = other.entityStatsOnHit; + } + + @Nonnull + public static DamageEntityInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + DamageEntityInteraction obj = new DamageEntityInteraction(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 2)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 3); + obj.runTime = buf.getFloatLE(offset + 7); + obj.cancelOnItemChange = buf.getByte(offset + 11) != 0; + obj.next = buf.getIntLE(offset + 12); + obj.failed = buf.getIntLE(offset + 16); + obj.blocked = buf.getIntLE(offset + 20); + if ((nullBits[0] & 1) != 0) { + int varPos0 = offset + 60 + buf.getIntLE(offset + 24); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits[0] & 2) != 0) { + int varPos1 = offset + 60 + buf.getIntLE(offset + 28); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits[0] & 4) != 0) { + int varPos2 = offset + 60 + buf.getIntLE(offset + 32); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits[0] & 8) != 0) { + int varPos3 = offset + 60 + buf.getIntLE(offset + 36); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits[0] & 16) != 0) { + int varPos4 = offset + 60 + buf.getIntLE(offset + 40); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits[0] & 32) != 0) { + int varPos5 = offset + 60 + buf.getIntLE(offset + 44); + obj.damageEffects = DamageEffects.deserialize(buf, varPos5); + } + + if ((nullBits[0] & 64) != 0) { + int varPos6 = offset + 60 + buf.getIntLE(offset + 48); + int angledDamageCount = VarInt.peek(buf, varPos6); + if (angledDamageCount < 0) { + throw ProtocolException.negativeLength("AngledDamage", angledDamageCount); + } + + if (angledDamageCount > 4096000) { + throw ProtocolException.arrayTooLong("AngledDamage", angledDamageCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos6); + if (varPos6 + varIntLen + angledDamageCount * 21L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("AngledDamage", varPos6 + varIntLen + angledDamageCount * 21, buf.readableBytes()); + } + + obj.angledDamage = new AngledDamage[angledDamageCount]; + int elemPos = varPos6 + varIntLen; + + for (int ix = 0; ix < angledDamageCount; ix++) { + obj.angledDamage[ix] = AngledDamage.deserialize(buf, elemPos); + elemPos += AngledDamage.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits[0] & 128) != 0) { + int varPos7 = offset + 60 + buf.getIntLE(offset + 52); + int targetedDamageCount = VarInt.peek(buf, varPos7); + if (targetedDamageCount < 0) { + throw ProtocolException.negativeLength("TargetedDamage", targetedDamageCount); + } + + if (targetedDamageCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("TargetedDamage", targetedDamageCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos7); + obj.targetedDamage = new HashMap<>(targetedDamageCount); + int dictPos = varPos7 + varIntLen; + + for (int ix = 0; ix < targetedDamageCount; ix++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + TargetedDamage val = TargetedDamage.deserialize(buf, dictPos); + dictPos += TargetedDamage.computeBytesConsumed(buf, dictPos); + if (obj.targetedDamage.put(key, val) != null) { + throw ProtocolException.duplicateKey("targetedDamage", key); + } + } + } + + if ((nullBits[1] & 1) != 0) { + int varPos8 = offset + 60 + buf.getIntLE(offset + 56); + int entityStatsOnHitCount = VarInt.peek(buf, varPos8); + if (entityStatsOnHitCount < 0) { + throw ProtocolException.negativeLength("EntityStatsOnHit", entityStatsOnHitCount); + } + + if (entityStatsOnHitCount > 4096000) { + throw ProtocolException.arrayTooLong("EntityStatsOnHit", entityStatsOnHitCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos8); + if (varPos8 + varIntLen + entityStatsOnHitCount * 13L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("EntityStatsOnHit", varPos8 + varIntLen + entityStatsOnHitCount * 13, buf.readableBytes()); + } + + obj.entityStatsOnHit = new EntityStatOnHit[entityStatsOnHitCount]; + int elemPos = varPos8 + varIntLen; + + for (int ix = 0; ix < entityStatsOnHitCount; ix++) { + obj.entityStatsOnHit[ix] = EntityStatOnHit.deserialize(buf, elemPos); + elemPos += EntityStatOnHit.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + int maxEnd = 60; + if ((nullBits[0] & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 24); + int pos0 = offset + 60 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits[0] & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 28); + int pos1 = offset + 60 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits[0] & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 32); + int pos2 = offset + 60 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits[0] & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 36); + int pos3 = offset + 60 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits[0] & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 40); + int pos4 = offset + 60 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits[0] & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 44); + int pos5 = offset + 60 + fieldOffset5; + pos5 += DamageEffects.computeBytesConsumed(buf, pos5); + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits[0] & 64) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 48); + int pos6 = offset + 60 + fieldOffset6; + int arrLen = VarInt.peek(buf, pos6); + pos6 += VarInt.length(buf, pos6); + + for (int i = 0; i < arrLen; i++) { + pos6 += AngledDamage.computeBytesConsumed(buf, pos6); + } + + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + if ((nullBits[0] & 128) != 0) { + int fieldOffset7 = buf.getIntLE(offset + 52); + int pos7 = offset + 60 + fieldOffset7; + int dictLen = VarInt.peek(buf, pos7); + pos7 += VarInt.length(buf, pos7); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos7); + pos7 += VarInt.length(buf, pos7) + sl; + pos7 += TargetedDamage.computeBytesConsumed(buf, pos7); + } + + if (pos7 - offset > maxEnd) { + maxEnd = pos7 - offset; + } + } + + if ((nullBits[1] & 1) != 0) { + int fieldOffset8 = buf.getIntLE(offset + 56); + int pos8 = offset + 60 + fieldOffset8; + int arrLen = VarInt.peek(buf, pos8); + pos8 += VarInt.length(buf, pos8); + + for (int i = 0; i < arrLen; i++) { + pos8 += EntityStatOnHit.computeBytesConsumed(buf, pos8); + } + + if (pos8 - offset > maxEnd) { + maxEnd = pos8 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte[] nullBits = new byte[2]; + if (this.effects != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.settings != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.rules != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.tags != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.camera != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.damageEffects != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.angledDamage != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.targetedDamage != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.entityStatsOnHit != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + buf.writeBytes(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeIntLE(this.blocked); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int damageEffectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int angledDamageOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int targetedDamageOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int entityStatsOnHitOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.damageEffects != null) { + buf.setIntLE(damageEffectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.damageEffects.serialize(buf); + } else { + buf.setIntLE(damageEffectsOffsetSlot, -1); + } + + if (this.angledDamage != null) { + buf.setIntLE(angledDamageOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.angledDamage.length > 4096000) { + throw ProtocolException.arrayTooLong("AngledDamage", this.angledDamage.length, 4096000); + } + + VarInt.write(buf, this.angledDamage.length); + + for (AngledDamage item : this.angledDamage) { + item.serialize(buf); + } + } else { + buf.setIntLE(angledDamageOffsetSlot, -1); + } + + if (this.targetedDamage != null) { + buf.setIntLE(targetedDamageOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.targetedDamage.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("TargetedDamage", this.targetedDamage.size(), 4096000); + } + + VarInt.write(buf, this.targetedDamage.size()); + + for (Entry e : this.targetedDamage.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(targetedDamageOffsetSlot, -1); + } + + if (this.entityStatsOnHit != null) { + buf.setIntLE(entityStatsOnHitOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.entityStatsOnHit.length > 4096000) { + throw ProtocolException.arrayTooLong("EntityStatsOnHit", this.entityStatsOnHit.length, 4096000); + } + + VarInt.write(buf, this.entityStatsOnHit.length); + + for (EntityStatOnHit item : this.entityStatsOnHit) { + item.serialize(buf); + } + } else { + buf.setIntLE(entityStatsOnHitOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 60; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.damageEffects != null) { + size += this.damageEffects.computeSize(); + } + + if (this.angledDamage != null) { + int angledDamageSize = 0; + + for (AngledDamage elem : this.angledDamage) { + angledDamageSize += elem.computeSize(); + } + + size += VarInt.size(this.angledDamage.length) + angledDamageSize; + } + + if (this.targetedDamage != null) { + int targetedDamageSize = 0; + + for (Entry kvp : this.targetedDamage.entrySet()) { + targetedDamageSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.targetedDamage.size()) + targetedDamageSize; + } + + if (this.entityStatsOnHit != null) { + int entityStatsOnHitSize = 0; + + for (EntityStatOnHit elem : this.entityStatsOnHit) { + entityStatsOnHitSize += elem.computeSize(); + } + + size += VarInt.size(this.entityStatsOnHit.length) + entityStatsOnHitSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 60) { + return ValidationResult.error("Buffer too small: expected at least 60 bytes"); + } else { + byte[] nullBits = PacketIO.readBytes(buffer, offset, 2); + if ((nullBits[0] & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 24); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 60 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits[0] & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 28); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 60 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits[0] & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 32); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 60 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits[0] & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 36); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 60 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits[0] & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 40); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 60 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits[0] & 32) != 0) { + int damageEffectsOffset = buffer.getIntLE(offset + 44); + if (damageEffectsOffset < 0) { + return ValidationResult.error("Invalid offset for DamageEffects"); + } + + int posxxxxx = offset + 60 + damageEffectsOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for DamageEffects"); + } + + ValidationResult damageEffectsResult = DamageEffects.validateStructure(buffer, posxxxxx); + if (!damageEffectsResult.isValid()) { + return ValidationResult.error("Invalid DamageEffects: " + damageEffectsResult.error()); + } + + posxxxxx += DamageEffects.computeBytesConsumed(buffer, posxxxxx); + } + + if ((nullBits[0] & 64) != 0) { + int angledDamageOffset = buffer.getIntLE(offset + 48); + if (angledDamageOffset < 0) { + return ValidationResult.error("Invalid offset for AngledDamage"); + } + + int posxxxxxx = offset + 60 + angledDamageOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for AngledDamage"); + } + + int angledDamageCount = VarInt.peek(buffer, posxxxxxx); + if (angledDamageCount < 0) { + return ValidationResult.error("Invalid array count for AngledDamage"); + } + + if (angledDamageCount > 4096000) { + return ValidationResult.error("AngledDamage exceeds max length 4096000"); + } + + posxxxxxx += VarInt.length(buffer, posxxxxxx); + + for (int i = 0; i < angledDamageCount; i++) { + ValidationResult structResult = AngledDamage.validateStructure(buffer, posxxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid AngledDamage in AngledDamage[" + i + "]: " + structResult.error()); + } + + posxxxxxx += AngledDamage.computeBytesConsumed(buffer, posxxxxxx); + } + } + + if ((nullBits[0] & 128) != 0) { + int targetedDamageOffset = buffer.getIntLE(offset + 52); + if (targetedDamageOffset < 0) { + return ValidationResult.error("Invalid offset for TargetedDamage"); + } + + int posxxxxxxx = offset + 60 + targetedDamageOffset; + if (posxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for TargetedDamage"); + } + + int targetedDamageCount = VarInt.peek(buffer, posxxxxxxx); + if (targetedDamageCount < 0) { + return ValidationResult.error("Invalid dictionary count for TargetedDamage"); + } + + if (targetedDamageCount > 4096000) { + return ValidationResult.error("TargetedDamage exceeds max length 4096000"); + } + + posxxxxxxx += VarInt.length(buffer, posxxxxxxx); + + for (int i = 0; i < targetedDamageCount; i++) { + int keyLen = VarInt.peek(buffer, posxxxxxxx); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + posxxxxxxx += VarInt.length(buffer, posxxxxxxx); + posxxxxxxx += keyLen; + if (posxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxx += TargetedDamage.computeBytesConsumed(buffer, posxxxxxxx); + } + } + + if ((nullBits[1] & 1) != 0) { + int entityStatsOnHitOffset = buffer.getIntLE(offset + 56); + if (entityStatsOnHitOffset < 0) { + return ValidationResult.error("Invalid offset for EntityStatsOnHit"); + } + + int posxxxxxxxx = offset + 60 + entityStatsOnHitOffset; + if (posxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for EntityStatsOnHit"); + } + + int entityStatsOnHitCount = VarInt.peek(buffer, posxxxxxxxx); + if (entityStatsOnHitCount < 0) { + return ValidationResult.error("Invalid array count for EntityStatsOnHit"); + } + + if (entityStatsOnHitCount > 4096000) { + return ValidationResult.error("EntityStatsOnHit exceeds max length 4096000"); + } + + posxxxxxxxx += VarInt.length(buffer, posxxxxxxxx); + + for (int i = 0; i < entityStatsOnHitCount; i++) { + ValidationResult structResult = EntityStatOnHit.validateStructure(buffer, posxxxxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid EntityStatOnHit in EntityStatsOnHit[" + i + "]: " + structResult.error()); + } + + posxxxxxxxx += EntityStatOnHit.computeBytesConsumed(buffer, posxxxxxxxx); + } + } + + return ValidationResult.OK; + } + } + + public DamageEntityInteraction clone() { + DamageEntityInteraction copy = new DamageEntityInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.blocked = this.blocked; + copy.damageEffects = this.damageEffects != null ? this.damageEffects.clone() : null; + copy.angledDamage = this.angledDamage != null ? Arrays.stream(this.angledDamage).map(ex -> ex.clone()).toArray(AngledDamage[]::new) : null; + if (this.targetedDamage != null) { + Map m = new HashMap<>(); + + for (Entry e : this.targetedDamage.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.targetedDamage = m; + } + + copy.entityStatsOnHit = this.entityStatsOnHit != null ? Arrays.stream(this.entityStatsOnHit).map(ex -> ex.clone()).toArray(EntityStatOnHit[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof DamageEntityInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && this.blocked == other.blocked + && Objects.equals(this.damageEffects, other.damageEffects) + && Arrays.equals((Object[])this.angledDamage, (Object[])other.angledDamage) + && Objects.equals(this.targetedDamage, other.targetedDamage) + && Arrays.equals((Object[])this.entityStatsOnHit, (Object[])other.entityStatsOnHit); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Integer.hashCode(this.blocked); + result = 31 * result + Objects.hashCode(this.damageEffects); + result = 31 * result + Arrays.hashCode((Object[])this.angledDamage); + result = 31 * result + Objects.hashCode(this.targetedDamage); + return 31 * result + Arrays.hashCode((Object[])this.entityStatsOnHit); + } +} diff --git a/src/com/hypixel/hytale/protocol/DebugShape.java b/src/com/hypixel/hytale/protocol/DebugShape.java new file mode 100644 index 0000000..f275629 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/DebugShape.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum DebugShape { + Sphere(0), + Cylinder(1), + Cone(2), + Cube(3), + Frustum(4); + + public static final DebugShape[] VALUES = values(); + private final int value; + + private DebugShape(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static DebugShape fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("DebugShape", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/DeployableConfig.java b/src/com/hypixel/hytale/protocol/DeployableConfig.java new file mode 100644 index 0000000..b960146 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/DeployableConfig.java @@ -0,0 +1,196 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DeployableConfig { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 10; + public static final int MAX_SIZE = 2058; + @Nullable + public Model model; + @Nullable + public Model modelPreview; + public boolean allowPlaceOnWalls; + + public DeployableConfig() { + } + + public DeployableConfig(@Nullable Model model, @Nullable Model modelPreview, boolean allowPlaceOnWalls) { + this.model = model; + this.modelPreview = modelPreview; + this.allowPlaceOnWalls = allowPlaceOnWalls; + } + + public DeployableConfig(@Nonnull DeployableConfig other) { + this.model = other.model; + this.modelPreview = other.modelPreview; + this.allowPlaceOnWalls = other.allowPlaceOnWalls; + } + + @Nonnull + public static DeployableConfig deserialize(@Nonnull ByteBuf buf, int offset) { + DeployableConfig obj = new DeployableConfig(); + byte nullBits = buf.getByte(offset); + obj.allowPlaceOnWalls = buf.getByte(offset + 1) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 10 + buf.getIntLE(offset + 2); + obj.model = Model.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 10 + buf.getIntLE(offset + 6); + obj.modelPreview = Model.deserialize(buf, varPos1); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 10; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 2); + int pos0 = offset + 10 + fieldOffset0; + pos0 += Model.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 6); + int pos1 = offset + 10 + fieldOffset1; + pos1 += Model.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.model != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.modelPreview != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.allowPlaceOnWalls ? 1 : 0); + int modelOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int modelPreviewOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.model != null) { + buf.setIntLE(modelOffsetSlot, buf.writerIndex() - varBlockStart); + this.model.serialize(buf); + } else { + buf.setIntLE(modelOffsetSlot, -1); + } + + if (this.modelPreview != null) { + buf.setIntLE(modelPreviewOffsetSlot, buf.writerIndex() - varBlockStart); + this.modelPreview.serialize(buf); + } else { + buf.setIntLE(modelPreviewOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 10; + if (this.model != null) { + size += this.model.computeSize(); + } + + if (this.modelPreview != null) { + size += this.modelPreview.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 10) { + return ValidationResult.error("Buffer too small: expected at least 10 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int modelOffset = buffer.getIntLE(offset + 2); + if (modelOffset < 0) { + return ValidationResult.error("Invalid offset for Model"); + } + + int pos = offset + 10 + modelOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Model"); + } + + ValidationResult modelResult = Model.validateStructure(buffer, pos); + if (!modelResult.isValid()) { + return ValidationResult.error("Invalid Model: " + modelResult.error()); + } + + pos += Model.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int modelPreviewOffset = buffer.getIntLE(offset + 6); + if (modelPreviewOffset < 0) { + return ValidationResult.error("Invalid offset for ModelPreview"); + } + + int posx = offset + 10 + modelPreviewOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ModelPreview"); + } + + ValidationResult modelPreviewResult = Model.validateStructure(buffer, posx); + if (!modelPreviewResult.isValid()) { + return ValidationResult.error("Invalid ModelPreview: " + modelPreviewResult.error()); + } + + posx += Model.computeBytesConsumed(buffer, posx); + } + + return ValidationResult.OK; + } + } + + public DeployableConfig clone() { + DeployableConfig copy = new DeployableConfig(); + copy.model = this.model != null ? this.model.clone() : null; + copy.modelPreview = this.modelPreview != null ? this.modelPreview.clone() : null; + copy.allowPlaceOnWalls = this.allowPlaceOnWalls; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof DeployableConfig other) + ? false + : Objects.equals(this.model, other.model) + && Objects.equals(this.modelPreview, other.modelPreview) + && this.allowPlaceOnWalls == other.allowPlaceOnWalls; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.model, this.modelPreview, this.allowPlaceOnWalls); + } +} diff --git a/src/com/hypixel/hytale/protocol/DetailBox.java b/src/com/hypixel/hytale/protocol/DetailBox.java new file mode 100644 index 0000000..9addf0d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/DetailBox.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DetailBox { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 37; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 37; + public static final int MAX_SIZE = 37; + @Nullable + public Vector3f offset; + @Nullable + public Hitbox box; + + public DetailBox() { + } + + public DetailBox(@Nullable Vector3f offset, @Nullable Hitbox box) { + this.offset = offset; + this.box = box; + } + + public DetailBox(@Nonnull DetailBox other) { + this.offset = other.offset; + this.box = other.box; + } + + @Nonnull + public static DetailBox deserialize(@Nonnull ByteBuf buf, int offset) { + DetailBox obj = new DetailBox(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.offset = Vector3f.deserialize(buf, offset + 1); + } + + if ((nullBits & 2) != 0) { + obj.box = Hitbox.deserialize(buf, offset + 13); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 37; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.offset != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.box != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + if (this.offset != null) { + this.offset.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.box != null) { + this.box.serialize(buf); + } else { + buf.writeZero(24); + } + } + + public int computeSize() { + return 37; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 37 ? ValidationResult.error("Buffer too small: expected at least 37 bytes") : ValidationResult.OK; + } + + public DetailBox clone() { + DetailBox copy = new DetailBox(); + copy.offset = this.offset != null ? this.offset.clone() : null; + copy.box = this.box != null ? this.box.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof DetailBox other) ? false : Objects.equals(this.offset, other.offset) && Objects.equals(this.box, other.box); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.offset, this.box); + } +} diff --git a/src/com/hypixel/hytale/protocol/Direction.java b/src/com/hypixel/hytale/protocol/Direction.java new file mode 100644 index 0000000..96f1d02 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Direction.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class Direction { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 12; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 12; + public static final int MAX_SIZE = 12; + public float yaw; + public float pitch; + public float roll; + + public Direction() { + } + + public Direction(float yaw, float pitch, float roll) { + this.yaw = yaw; + this.pitch = pitch; + this.roll = roll; + } + + public Direction(@Nonnull Direction other) { + this.yaw = other.yaw; + this.pitch = other.pitch; + this.roll = other.roll; + } + + @Nonnull + public static Direction deserialize(@Nonnull ByteBuf buf, int offset) { + Direction obj = new Direction(); + obj.yaw = buf.getFloatLE(offset + 0); + obj.pitch = buf.getFloatLE(offset + 4); + obj.roll = buf.getFloatLE(offset + 8); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 12; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.yaw); + buf.writeFloatLE(this.pitch); + buf.writeFloatLE(this.roll); + } + + public int computeSize() { + return 12; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 12 ? ValidationResult.error("Buffer too small: expected at least 12 bytes") : ValidationResult.OK; + } + + public Direction clone() { + Direction copy = new Direction(); + copy.yaw = this.yaw; + copy.pitch = this.pitch; + copy.roll = this.roll; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Direction other) ? false : this.yaw == other.yaw && this.pitch == other.pitch && this.roll == other.roll; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.yaw, this.pitch, this.roll); + } +} diff --git a/src/com/hypixel/hytale/protocol/DoubleParamValue.java b/src/com/hypixel/hytale/protocol/DoubleParamValue.java new file mode 100644 index 0000000..7f17bc3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/DoubleParamValue.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class DoubleParamValue extends ParamValue { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public double value; + + public DoubleParamValue() { + } + + public DoubleParamValue(double value) { + this.value = value; + } + + public DoubleParamValue(@Nonnull DoubleParamValue other) { + this.value = other.value; + } + + @Nonnull + public static DoubleParamValue deserialize(@Nonnull ByteBuf buf, int offset) { + DoubleParamValue obj = new DoubleParamValue(); + obj.value = buf.getDoubleLE(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + buf.writeDoubleLE(this.value); + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public DoubleParamValue clone() { + DoubleParamValue copy = new DoubleParamValue(); + copy.value = this.value; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof DoubleParamValue other ? this.value == other.value : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.value); + } +} diff --git a/src/com/hypixel/hytale/protocol/DrawType.java b/src/com/hypixel/hytale/protocol/DrawType.java new file mode 100644 index 0000000..87c5e1a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/DrawType.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum DrawType { + Empty(0), + GizmoCube(1), + Cube(2), + Model(3), + CubeWithModel(4); + + public static final DrawType[] VALUES = values(); + private final int value; + + private DrawType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static DrawType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("DrawType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/EasingConfig.java b/src/com/hypixel/hytale/protocol/EasingConfig.java new file mode 100644 index 0000000..52a4537 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EasingConfig.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class EasingConfig { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 5; + public float time; + @Nonnull + public EasingType type = EasingType.Linear; + + public EasingConfig() { + } + + public EasingConfig(float time, @Nonnull EasingType type) { + this.time = time; + this.type = type; + } + + public EasingConfig(@Nonnull EasingConfig other) { + this.time = other.time; + this.type = other.type; + } + + @Nonnull + public static EasingConfig deserialize(@Nonnull ByteBuf buf, int offset) { + EasingConfig obj = new EasingConfig(); + obj.time = buf.getFloatLE(offset + 0); + obj.type = EasingType.fromValue(buf.getByte(offset + 4)); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 5; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.time); + buf.writeByte(this.type.getValue()); + } + + public int computeSize() { + return 5; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 5 ? ValidationResult.error("Buffer too small: expected at least 5 bytes") : ValidationResult.OK; + } + + public EasingConfig clone() { + EasingConfig copy = new EasingConfig(); + copy.time = this.time; + copy.type = this.type; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof EasingConfig other) ? false : this.time == other.time && Objects.equals(this.type, other.type); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.time, this.type); + } +} diff --git a/src/com/hypixel/hytale/protocol/EasingType.java b/src/com/hypixel/hytale/protocol/EasingType.java new file mode 100644 index 0000000..b524c6b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EasingType.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum EasingType { + Linear(0), + QuadIn(1), + QuadOut(2), + QuadInOut(3), + CubicIn(4), + CubicOut(5), + CubicInOut(6), + QuartIn(7), + QuartOut(8), + QuartInOut(9), + QuintIn(10), + QuintOut(11), + QuintInOut(12), + SineIn(13), + SineOut(14), + SineInOut(15), + ExpoIn(16), + ExpoOut(17), + ExpoInOut(18), + CircIn(19), + CircOut(20), + CircInOut(21), + ElasticIn(22), + ElasticOut(23), + ElasticInOut(24), + BackIn(25), + BackOut(26), + BackInOut(27), + BounceIn(28), + BounceOut(29), + BounceInOut(30); + + public static final EasingType[] VALUES = values(); + private final int value; + + private EasingType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static EasingType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("EasingType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/Edge.java b/src/com/hypixel/hytale/protocol/Edge.java new file mode 100644 index 0000000..60c529a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Edge.java @@ -0,0 +1,92 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Edge { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 9; + @Nullable + public ColorAlpha color; + public float width; + + public Edge() { + } + + public Edge(@Nullable ColorAlpha color, float width) { + this.color = color; + this.width = width; + } + + public Edge(@Nonnull Edge other) { + this.color = other.color; + this.width = other.width; + } + + @Nonnull + public static Edge deserialize(@Nonnull ByteBuf buf, int offset) { + Edge obj = new Edge(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.color = ColorAlpha.deserialize(buf, offset + 1); + } + + obj.width = buf.getFloatLE(offset + 5); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 9; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.color != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.color != null) { + this.color.serialize(buf); + } else { + buf.writeZero(4); + } + + buf.writeFloatLE(this.width); + } + + public int computeSize() { + return 9; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 9 ? ValidationResult.error("Buffer too small: expected at least 9 bytes") : ValidationResult.OK; + } + + public Edge clone() { + Edge copy = new Edge(); + copy.color = this.color != null ? this.color.clone() : null; + copy.width = this.width; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Edge other) ? false : Objects.equals(this.color, other.color) && this.width == other.width; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.color, this.width); + } +} diff --git a/src/com/hypixel/hytale/protocol/EffectConditionInteraction.java b/src/com/hypixel/hytale/protocol/EffectConditionInteraction.java new file mode 100644 index 0000000..94a6872 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EffectConditionInteraction.java @@ -0,0 +1,617 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EffectConditionInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 21; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 45; + public static final int MAX_SIZE = 1677721600; + @Nullable + public int[] entityEffects; + @Nonnull + public Match match = Match.All; + @Nonnull + public InteractionTarget entityTarget = InteractionTarget.User; + + public EffectConditionInteraction() { + } + + public EffectConditionInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + @Nullable int[] entityEffects, + @Nonnull Match match, + @Nonnull InteractionTarget entityTarget + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.entityEffects = entityEffects; + this.match = match; + this.entityTarget = entityTarget; + } + + public EffectConditionInteraction(@Nonnull EffectConditionInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.entityEffects = other.entityEffects; + this.match = other.match; + this.entityTarget = other.entityTarget; + } + + @Nonnull + public static EffectConditionInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + EffectConditionInteraction obj = new EffectConditionInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.match = Match.fromValue(buf.getByte(offset + 19)); + obj.entityTarget = InteractionTarget.fromValue(buf.getByte(offset + 20)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 45 + buf.getIntLE(offset + 21); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 45 + buf.getIntLE(offset + 25); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 45 + buf.getIntLE(offset + 29); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 45 + buf.getIntLE(offset + 33); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 45 + buf.getIntLE(offset + 37); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 45 + buf.getIntLE(offset + 41); + int entityEffectsCount = VarInt.peek(buf, varPos5); + if (entityEffectsCount < 0) { + throw ProtocolException.negativeLength("EntityEffects", entityEffectsCount); + } + + if (entityEffectsCount > 4096000) { + throw ProtocolException.arrayTooLong("EntityEffects", entityEffectsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + if (varPos5 + varIntLen + entityEffectsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("EntityEffects", varPos5 + varIntLen + entityEffectsCount * 4, buf.readableBytes()); + } + + obj.entityEffects = new int[entityEffectsCount]; + + for (int ix = 0; ix < entityEffectsCount; ix++) { + obj.entityEffects[ix] = buf.getIntLE(varPos5 + varIntLen + ix * 4); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 45; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 21); + int pos0 = offset + 45 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 25); + int pos1 = offset + 45 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 29); + int pos2 = offset + 45 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 33); + int pos3 = offset + 45 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 37); + int pos4 = offset + 45 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 41); + int pos5 = offset + 45 + fieldOffset5; + int arrLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + arrLen * 4; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.entityEffects != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeByte(this.match.getValue()); + buf.writeByte(this.entityTarget.getValue()); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int entityEffectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.entityEffects != null) { + buf.setIntLE(entityEffectsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.entityEffects.length > 4096000) { + throw ProtocolException.arrayTooLong("EntityEffects", this.entityEffects.length, 4096000); + } + + VarInt.write(buf, this.entityEffects.length); + + for (int item : this.entityEffects) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(entityEffectsOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 45; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.entityEffects != null) { + size += VarInt.size(this.entityEffects.length) + this.entityEffects.length * 4; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 45) { + return ValidationResult.error("Buffer too small: expected at least 45 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 21); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 45 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 25); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 45 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 29); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 45 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 33); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 45 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 37); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 45 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int entityEffectsOffset = buffer.getIntLE(offset + 41); + if (entityEffectsOffset < 0) { + return ValidationResult.error("Invalid offset for EntityEffects"); + } + + int posxxxxx = offset + 45 + entityEffectsOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for EntityEffects"); + } + + int entityEffectsCount = VarInt.peek(buffer, posxxxxx); + if (entityEffectsCount < 0) { + return ValidationResult.error("Invalid array count for EntityEffects"); + } + + if (entityEffectsCount > 4096000) { + return ValidationResult.error("EntityEffects exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += entityEffectsCount * 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading EntityEffects"); + } + } + + return ValidationResult.OK; + } + } + + public EffectConditionInteraction clone() { + EffectConditionInteraction copy = new EffectConditionInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.entityEffects = this.entityEffects != null ? Arrays.copyOf(this.entityEffects, this.entityEffects.length) : null; + copy.match = this.match; + copy.entityTarget = this.entityTarget; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof EffectConditionInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && Arrays.equals(this.entityEffects, other.entityEffects) + && Objects.equals(this.match, other.match) + && Objects.equals(this.entityTarget, other.entityTarget); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Arrays.hashCode(this.entityEffects); + result = 31 * result + Objects.hashCode(this.match); + return 31 * result + Objects.hashCode(this.entityTarget); + } +} diff --git a/src/com/hypixel/hytale/protocol/EffectDirection.java b/src/com/hypixel/hytale/protocol/EffectDirection.java new file mode 100644 index 0000000..17a49ac --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EffectDirection.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum EffectDirection { + None(0), + BottomUp(1), + TopDown(2), + ToCenter(3), + FromCenter(4); + + public static final EffectDirection[] VALUES = values(); + private final int value; + + private EffectDirection(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static EffectDirection fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("EffectDirection", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/EffectOp.java b/src/com/hypixel/hytale/protocol/EffectOp.java new file mode 100644 index 0000000..932e4f7 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EffectOp.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum EffectOp { + Add(0), + Remove(1); + + public static final EffectOp[] VALUES = values(); + private final int value; + + private EffectOp(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static EffectOp fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("EffectOp", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/EmitShape.java b/src/com/hypixel/hytale/protocol/EmitShape.java new file mode 100644 index 0000000..0359406 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EmitShape.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum EmitShape { + Sphere(0), + Cube(1); + + public static final EmitShape[] VALUES = values(); + private final int value; + + private EmitShape(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static EmitShape fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("EmitShape", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/EntityEffect.java b/src/com/hypixel/hytale/protocol/EntityEffect.java new file mode 100644 index 0000000..2dd0710 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EntityEffect.java @@ -0,0 +1,614 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityEffect { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 25; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 49; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + @Nullable + public String name; + @Nullable + public ApplicationEffects applicationEffects; + public int worldRemovalSoundEventIndex; + public int localRemovalSoundEventIndex; + @Nullable + public ModelOverride modelOverride; + public float duration; + public boolean infinite; + public boolean debuff; + @Nullable + public String statusEffectIcon; + @Nonnull + public OverlapBehavior overlapBehavior = OverlapBehavior.Extend; + public double damageCalculatorCooldown; + @Nullable + public Map statModifiers; + @Nonnull + public ValueType valueType = ValueType.Percent; + + public EntityEffect() { + } + + public EntityEffect( + @Nullable String id, + @Nullable String name, + @Nullable ApplicationEffects applicationEffects, + int worldRemovalSoundEventIndex, + int localRemovalSoundEventIndex, + @Nullable ModelOverride modelOverride, + float duration, + boolean infinite, + boolean debuff, + @Nullable String statusEffectIcon, + @Nonnull OverlapBehavior overlapBehavior, + double damageCalculatorCooldown, + @Nullable Map statModifiers, + @Nonnull ValueType valueType + ) { + this.id = id; + this.name = name; + this.applicationEffects = applicationEffects; + this.worldRemovalSoundEventIndex = worldRemovalSoundEventIndex; + this.localRemovalSoundEventIndex = localRemovalSoundEventIndex; + this.modelOverride = modelOverride; + this.duration = duration; + this.infinite = infinite; + this.debuff = debuff; + this.statusEffectIcon = statusEffectIcon; + this.overlapBehavior = overlapBehavior; + this.damageCalculatorCooldown = damageCalculatorCooldown; + this.statModifiers = statModifiers; + this.valueType = valueType; + } + + public EntityEffect(@Nonnull EntityEffect other) { + this.id = other.id; + this.name = other.name; + this.applicationEffects = other.applicationEffects; + this.worldRemovalSoundEventIndex = other.worldRemovalSoundEventIndex; + this.localRemovalSoundEventIndex = other.localRemovalSoundEventIndex; + this.modelOverride = other.modelOverride; + this.duration = other.duration; + this.infinite = other.infinite; + this.debuff = other.debuff; + this.statusEffectIcon = other.statusEffectIcon; + this.overlapBehavior = other.overlapBehavior; + this.damageCalculatorCooldown = other.damageCalculatorCooldown; + this.statModifiers = other.statModifiers; + this.valueType = other.valueType; + } + + @Nonnull + public static EntityEffect deserialize(@Nonnull ByteBuf buf, int offset) { + EntityEffect obj = new EntityEffect(); + byte nullBits = buf.getByte(offset); + obj.worldRemovalSoundEventIndex = buf.getIntLE(offset + 1); + obj.localRemovalSoundEventIndex = buf.getIntLE(offset + 5); + obj.duration = buf.getFloatLE(offset + 9); + obj.infinite = buf.getByte(offset + 13) != 0; + obj.debuff = buf.getByte(offset + 14) != 0; + obj.overlapBehavior = OverlapBehavior.fromValue(buf.getByte(offset + 15)); + obj.damageCalculatorCooldown = buf.getDoubleLE(offset + 16); + obj.valueType = ValueType.fromValue(buf.getByte(offset + 24)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 49 + buf.getIntLE(offset + 25); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 49 + buf.getIntLE(offset + 29); + int nameLen = VarInt.peek(buf, varPos1); + if (nameLen < 0) { + throw ProtocolException.negativeLength("Name", nameLen); + } + + if (nameLen > 4096000) { + throw ProtocolException.stringTooLong("Name", nameLen, 4096000); + } + + obj.name = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 49 + buf.getIntLE(offset + 33); + obj.applicationEffects = ApplicationEffects.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 49 + buf.getIntLE(offset + 37); + obj.modelOverride = ModelOverride.deserialize(buf, varPos3); + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 49 + buf.getIntLE(offset + 41); + int statusEffectIconLen = VarInt.peek(buf, varPos4); + if (statusEffectIconLen < 0) { + throw ProtocolException.negativeLength("StatusEffectIcon", statusEffectIconLen); + } + + if (statusEffectIconLen > 4096000) { + throw ProtocolException.stringTooLong("StatusEffectIcon", statusEffectIconLen, 4096000); + } + + obj.statusEffectIcon = PacketIO.readVarString(buf, varPos4, PacketIO.UTF8); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 49 + buf.getIntLE(offset + 45); + int statModifiersCount = VarInt.peek(buf, varPos5); + if (statModifiersCount < 0) { + throw ProtocolException.negativeLength("StatModifiers", statModifiersCount); + } + + if (statModifiersCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("StatModifiers", statModifiersCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + obj.statModifiers = new HashMap<>(statModifiersCount); + int dictPos = varPos5 + varIntLen; + + for (int i = 0; i < statModifiersCount; i++) { + int key = buf.getIntLE(dictPos); + dictPos += 4; + float val = buf.getFloatLE(dictPos); + dictPos += 4; + if (obj.statModifiers.put(key, val) != null) { + throw ProtocolException.duplicateKey("statModifiers", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 49; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 25); + int pos0 = offset + 49 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 29); + int pos1 = offset + 49 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 33); + int pos2 = offset + 49 + fieldOffset2; + pos2 += ApplicationEffects.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 37); + int pos3 = offset + 49 + fieldOffset3; + pos3 += ModelOverride.computeBytesConsumed(buf, pos3); + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 41); + int pos4 = offset + 49 + fieldOffset4; + int sl = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4) + sl; + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 45); + int pos5 = offset + 49 + fieldOffset5; + int dictLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5); + + for (int i = 0; i < dictLen; i++) { + pos5 += 4; + pos5 += 4; + } + + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.name != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.applicationEffects != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.modelOverride != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.statusEffectIcon != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.statModifiers != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.worldRemovalSoundEventIndex); + buf.writeIntLE(this.localRemovalSoundEventIndex); + buf.writeFloatLE(this.duration); + buf.writeByte(this.infinite ? 1 : 0); + buf.writeByte(this.debuff ? 1 : 0); + buf.writeByte(this.overlapBehavior.getValue()); + buf.writeDoubleLE(this.damageCalculatorCooldown); + buf.writeByte(this.valueType.getValue()); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int nameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int applicationEffectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int modelOverrideOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int statusEffectIconOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int statModifiersOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.name != null) { + buf.setIntLE(nameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.name, 4096000); + } else { + buf.setIntLE(nameOffsetSlot, -1); + } + + if (this.applicationEffects != null) { + buf.setIntLE(applicationEffectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.applicationEffects.serialize(buf); + } else { + buf.setIntLE(applicationEffectsOffsetSlot, -1); + } + + if (this.modelOverride != null) { + buf.setIntLE(modelOverrideOffsetSlot, buf.writerIndex() - varBlockStart); + this.modelOverride.serialize(buf); + } else { + buf.setIntLE(modelOverrideOffsetSlot, -1); + } + + if (this.statusEffectIcon != null) { + buf.setIntLE(statusEffectIconOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.statusEffectIcon, 4096000); + } else { + buf.setIntLE(statusEffectIconOffsetSlot, -1); + } + + if (this.statModifiers != null) { + buf.setIntLE(statModifiersOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.statModifiers.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("StatModifiers", this.statModifiers.size(), 4096000); + } + + VarInt.write(buf, this.statModifiers.size()); + + for (Entry e : this.statModifiers.entrySet()) { + buf.writeIntLE(e.getKey()); + buf.writeFloatLE(e.getValue()); + } + } else { + buf.setIntLE(statModifiersOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 49; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.name != null) { + size += PacketIO.stringSize(this.name); + } + + if (this.applicationEffects != null) { + size += this.applicationEffects.computeSize(); + } + + if (this.modelOverride != null) { + size += this.modelOverride.computeSize(); + } + + if (this.statusEffectIcon != null) { + size += PacketIO.stringSize(this.statusEffectIcon); + } + + if (this.statModifiers != null) { + size += VarInt.size(this.statModifiers.size()) + this.statModifiers.size() * 8; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 49) { + return ValidationResult.error("Buffer too small: expected at least 49 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 25); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 49 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int nameOffset = buffer.getIntLE(offset + 29); + if (nameOffset < 0) { + return ValidationResult.error("Invalid offset for Name"); + } + + int posx = offset + 49 + nameOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Name"); + } + + int nameLen = VarInt.peek(buffer, posx); + if (nameLen < 0) { + return ValidationResult.error("Invalid string length for Name"); + } + + if (nameLen > 4096000) { + return ValidationResult.error("Name exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += nameLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Name"); + } + } + + if ((nullBits & 4) != 0) { + int applicationEffectsOffset = buffer.getIntLE(offset + 33); + if (applicationEffectsOffset < 0) { + return ValidationResult.error("Invalid offset for ApplicationEffects"); + } + + int posxx = offset + 49 + applicationEffectsOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ApplicationEffects"); + } + + ValidationResult applicationEffectsResult = ApplicationEffects.validateStructure(buffer, posxx); + if (!applicationEffectsResult.isValid()) { + return ValidationResult.error("Invalid ApplicationEffects: " + applicationEffectsResult.error()); + } + + posxx += ApplicationEffects.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int modelOverrideOffset = buffer.getIntLE(offset + 37); + if (modelOverrideOffset < 0) { + return ValidationResult.error("Invalid offset for ModelOverride"); + } + + int posxxx = offset + 49 + modelOverrideOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ModelOverride"); + } + + ValidationResult modelOverrideResult = ModelOverride.validateStructure(buffer, posxxx); + if (!modelOverrideResult.isValid()) { + return ValidationResult.error("Invalid ModelOverride: " + modelOverrideResult.error()); + } + + posxxx += ModelOverride.computeBytesConsumed(buffer, posxxx); + } + + if ((nullBits & 16) != 0) { + int statusEffectIconOffset = buffer.getIntLE(offset + 41); + if (statusEffectIconOffset < 0) { + return ValidationResult.error("Invalid offset for StatusEffectIcon"); + } + + int posxxxx = offset + 49 + statusEffectIconOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for StatusEffectIcon"); + } + + int statusEffectIconLen = VarInt.peek(buffer, posxxxx); + if (statusEffectIconLen < 0) { + return ValidationResult.error("Invalid string length for StatusEffectIcon"); + } + + if (statusEffectIconLen > 4096000) { + return ValidationResult.error("StatusEffectIcon exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + posxxxx += statusEffectIconLen; + if (posxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading StatusEffectIcon"); + } + } + + if ((nullBits & 32) != 0) { + int statModifiersOffset = buffer.getIntLE(offset + 45); + if (statModifiersOffset < 0) { + return ValidationResult.error("Invalid offset for StatModifiers"); + } + + int posxxxxx = offset + 49 + statModifiersOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for StatModifiers"); + } + + int statModifiersCount = VarInt.peek(buffer, posxxxxx); + if (statModifiersCount < 0) { + return ValidationResult.error("Invalid dictionary count for StatModifiers"); + } + + if (statModifiersCount > 4096000) { + return ValidationResult.error("StatModifiers exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + + for (int i = 0; i < statModifiersCount; i++) { + posxxxxx += 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxx += 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public EntityEffect clone() { + EntityEffect copy = new EntityEffect(); + copy.id = this.id; + copy.name = this.name; + copy.applicationEffects = this.applicationEffects != null ? this.applicationEffects.clone() : null; + copy.worldRemovalSoundEventIndex = this.worldRemovalSoundEventIndex; + copy.localRemovalSoundEventIndex = this.localRemovalSoundEventIndex; + copy.modelOverride = this.modelOverride != null ? this.modelOverride.clone() : null; + copy.duration = this.duration; + copy.infinite = this.infinite; + copy.debuff = this.debuff; + copy.statusEffectIcon = this.statusEffectIcon; + copy.overlapBehavior = this.overlapBehavior; + copy.damageCalculatorCooldown = this.damageCalculatorCooldown; + copy.statModifiers = this.statModifiers != null ? new HashMap<>(this.statModifiers) : null; + copy.valueType = this.valueType; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof EntityEffect other) + ? false + : Objects.equals(this.id, other.id) + && Objects.equals(this.name, other.name) + && Objects.equals(this.applicationEffects, other.applicationEffects) + && this.worldRemovalSoundEventIndex == other.worldRemovalSoundEventIndex + && this.localRemovalSoundEventIndex == other.localRemovalSoundEventIndex + && Objects.equals(this.modelOverride, other.modelOverride) + && this.duration == other.duration + && this.infinite == other.infinite + && this.debuff == other.debuff + && Objects.equals(this.statusEffectIcon, other.statusEffectIcon) + && Objects.equals(this.overlapBehavior, other.overlapBehavior) + && this.damageCalculatorCooldown == other.damageCalculatorCooldown + && Objects.equals(this.statModifiers, other.statModifiers) + && Objects.equals(this.valueType, other.valueType); + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.id, + this.name, + this.applicationEffects, + this.worldRemovalSoundEventIndex, + this.localRemovalSoundEventIndex, + this.modelOverride, + this.duration, + this.infinite, + this.debuff, + this.statusEffectIcon, + this.overlapBehavior, + this.damageCalculatorCooldown, + this.statModifiers, + this.valueType + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/EntityEffectUpdate.java b/src/com/hypixel/hytale/protocol/EntityEffectUpdate.java new file mode 100644 index 0000000..ffd6c07 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EntityEffectUpdate.java @@ -0,0 +1,171 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityEffectUpdate { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 12; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 12; + public static final int MAX_SIZE = 16384017; + @Nonnull + public EffectOp type = EffectOp.Add; + public int id; + public float remainingTime; + public boolean infinite; + public boolean debuff; + @Nullable + public String statusEffectIcon; + + public EntityEffectUpdate() { + } + + public EntityEffectUpdate(@Nonnull EffectOp type, int id, float remainingTime, boolean infinite, boolean debuff, @Nullable String statusEffectIcon) { + this.type = type; + this.id = id; + this.remainingTime = remainingTime; + this.infinite = infinite; + this.debuff = debuff; + this.statusEffectIcon = statusEffectIcon; + } + + public EntityEffectUpdate(@Nonnull EntityEffectUpdate other) { + this.type = other.type; + this.id = other.id; + this.remainingTime = other.remainingTime; + this.infinite = other.infinite; + this.debuff = other.debuff; + this.statusEffectIcon = other.statusEffectIcon; + } + + @Nonnull + public static EntityEffectUpdate deserialize(@Nonnull ByteBuf buf, int offset) { + EntityEffectUpdate obj = new EntityEffectUpdate(); + byte nullBits = buf.getByte(offset); + obj.type = EffectOp.fromValue(buf.getByte(offset + 1)); + obj.id = buf.getIntLE(offset + 2); + obj.remainingTime = buf.getFloatLE(offset + 6); + obj.infinite = buf.getByte(offset + 10) != 0; + obj.debuff = buf.getByte(offset + 11) != 0; + int pos = offset + 12; + if ((nullBits & 1) != 0) { + int statusEffectIconLen = VarInt.peek(buf, pos); + if (statusEffectIconLen < 0) { + throw ProtocolException.negativeLength("StatusEffectIcon", statusEffectIconLen); + } + + if (statusEffectIconLen > 4096000) { + throw ProtocolException.stringTooLong("StatusEffectIcon", statusEffectIconLen, 4096000); + } + + int statusEffectIconVarLen = VarInt.length(buf, pos); + obj.statusEffectIcon = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += statusEffectIconVarLen + statusEffectIconLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 12; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.statusEffectIcon != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.id); + buf.writeFloatLE(this.remainingTime); + buf.writeByte(this.infinite ? 1 : 0); + buf.writeByte(this.debuff ? 1 : 0); + if (this.statusEffectIcon != null) { + PacketIO.writeVarString(buf, this.statusEffectIcon, 4096000); + } + } + + public int computeSize() { + int size = 12; + if (this.statusEffectIcon != null) { + size += PacketIO.stringSize(this.statusEffectIcon); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 12) { + return ValidationResult.error("Buffer too small: expected at least 12 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 12; + if ((nullBits & 1) != 0) { + int statusEffectIconLen = VarInt.peek(buffer, pos); + if (statusEffectIconLen < 0) { + return ValidationResult.error("Invalid string length for StatusEffectIcon"); + } + + if (statusEffectIconLen > 4096000) { + return ValidationResult.error("StatusEffectIcon exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += statusEffectIconLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading StatusEffectIcon"); + } + } + + return ValidationResult.OK; + } + } + + public EntityEffectUpdate clone() { + EntityEffectUpdate copy = new EntityEffectUpdate(); + copy.type = this.type; + copy.id = this.id; + copy.remainingTime = this.remainingTime; + copy.infinite = this.infinite; + copy.debuff = this.debuff; + copy.statusEffectIcon = this.statusEffectIcon; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof EntityEffectUpdate other) + ? false + : Objects.equals(this.type, other.type) + && this.id == other.id + && this.remainingTime == other.remainingTime + && this.infinite == other.infinite + && this.debuff == other.debuff + && Objects.equals(this.statusEffectIcon, other.statusEffectIcon); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.id, this.remainingTime, this.infinite, this.debuff, this.statusEffectIcon); + } +} diff --git a/src/com/hypixel/hytale/protocol/EntityMatcher.java b/src/com/hypixel/hytale/protocol/EntityMatcher.java new file mode 100644 index 0000000..3634969 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EntityMatcher.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class EntityMatcher { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 2; + @Nonnull + public EntityMatcherType type = EntityMatcherType.Server; + public boolean invert; + + public EntityMatcher() { + } + + public EntityMatcher(@Nonnull EntityMatcherType type, boolean invert) { + this.type = type; + this.invert = invert; + } + + public EntityMatcher(@Nonnull EntityMatcher other) { + this.type = other.type; + this.invert = other.invert; + } + + @Nonnull + public static EntityMatcher deserialize(@Nonnull ByteBuf buf, int offset) { + EntityMatcher obj = new EntityMatcher(); + obj.type = EntityMatcherType.fromValue(buf.getByte(offset + 0)); + obj.invert = buf.getByte(offset + 1) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 2; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.type.getValue()); + buf.writeByte(this.invert ? 1 : 0); + } + + public int computeSize() { + return 2; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 2 ? ValidationResult.error("Buffer too small: expected at least 2 bytes") : ValidationResult.OK; + } + + public EntityMatcher clone() { + EntityMatcher copy = new EntityMatcher(); + copy.type = this.type; + copy.invert = this.invert; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof EntityMatcher other) ? false : Objects.equals(this.type, other.type) && this.invert == other.invert; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.invert); + } +} diff --git a/src/com/hypixel/hytale/protocol/EntityMatcherType.java b/src/com/hypixel/hytale/protocol/EntityMatcherType.java new file mode 100644 index 0000000..61b10aa --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EntityMatcherType.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum EntityMatcherType { + Server(0), + VulnerableMatcher(1), + Player(2); + + public static final EntityMatcherType[] VALUES = values(); + private final int value; + + private EntityMatcherType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static EntityMatcherType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("EntityMatcherType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/EntityPart.java b/src/com/hypixel/hytale/protocol/EntityPart.java new file mode 100644 index 0000000..af153cb --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EntityPart.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum EntityPart { + Self(0), + Entity(1), + PrimaryItem(2), + SecondaryItem(3); + + public static final EntityPart[] VALUES = values(); + private final int value; + + private EntityPart(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static EntityPart fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("EntityPart", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/EntityStatEffects.java b/src/com/hypixel/hytale/protocol/EntityStatEffects.java new file mode 100644 index 0000000..906ec4d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EntityStatEffects.java @@ -0,0 +1,183 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityStatEffects { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + public boolean triggerAtZero; + public int soundEventIndex; + @Nullable + public ModelParticle[] particles; + + public EntityStatEffects() { + } + + public EntityStatEffects(boolean triggerAtZero, int soundEventIndex, @Nullable ModelParticle[] particles) { + this.triggerAtZero = triggerAtZero; + this.soundEventIndex = soundEventIndex; + this.particles = particles; + } + + public EntityStatEffects(@Nonnull EntityStatEffects other) { + this.triggerAtZero = other.triggerAtZero; + this.soundEventIndex = other.soundEventIndex; + this.particles = other.particles; + } + + @Nonnull + public static EntityStatEffects deserialize(@Nonnull ByteBuf buf, int offset) { + EntityStatEffects obj = new EntityStatEffects(); + byte nullBits = buf.getByte(offset); + obj.triggerAtZero = buf.getByte(offset + 1) != 0; + obj.soundEventIndex = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int particlesCount = VarInt.peek(buf, pos); + if (particlesCount < 0) { + throw ProtocolException.negativeLength("Particles", particlesCount); + } + + if (particlesCount > 4096000) { + throw ProtocolException.arrayTooLong("Particles", particlesCount, 4096000); + } + + int particlesVarLen = VarInt.size(particlesCount); + if (pos + particlesVarLen + particlesCount * 34L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Particles", pos + particlesVarLen + particlesCount * 34, buf.readableBytes()); + } + + pos += particlesVarLen; + obj.particles = new ModelParticle[particlesCount]; + + for (int i = 0; i < particlesCount; i++) { + obj.particles[i] = ModelParticle.deserialize(buf, pos); + pos += ModelParticle.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += ModelParticle.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.particles != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.triggerAtZero ? 1 : 0); + buf.writeIntLE(this.soundEventIndex); + if (this.particles != null) { + if (this.particles.length > 4096000) { + throw ProtocolException.arrayTooLong("Particles", this.particles.length, 4096000); + } + + VarInt.write(buf, this.particles.length); + + for (ModelParticle item : this.particles) { + item.serialize(buf); + } + } + } + + public int computeSize() { + int size = 6; + if (this.particles != null) { + int particlesSize = 0; + + for (ModelParticle elem : this.particles) { + particlesSize += elem.computeSize(); + } + + size += VarInt.size(this.particles.length) + particlesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int particlesCount = VarInt.peek(buffer, pos); + if (particlesCount < 0) { + return ValidationResult.error("Invalid array count for Particles"); + } + + if (particlesCount > 4096000) { + return ValidationResult.error("Particles exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < particlesCount; i++) { + ValidationResult structResult = ModelParticle.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelParticle in Particles[" + i + "]: " + structResult.error()); + } + + pos += ModelParticle.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public EntityStatEffects clone() { + EntityStatEffects copy = new EntityStatEffects(); + copy.triggerAtZero = this.triggerAtZero; + copy.soundEventIndex = this.soundEventIndex; + copy.particles = this.particles != null ? Arrays.stream(this.particles).map(e -> e.clone()).toArray(ModelParticle[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof EntityStatEffects other) + ? false + : this.triggerAtZero == other.triggerAtZero + && this.soundEventIndex == other.soundEventIndex + && Arrays.equals((Object[])this.particles, (Object[])other.particles); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Boolean.hashCode(this.triggerAtZero); + result = 31 * result + Integer.hashCode(this.soundEventIndex); + return 31 * result + Arrays.hashCode((Object[])this.particles); + } +} diff --git a/src/com/hypixel/hytale/protocol/EntityStatOnHit.java b/src/com/hypixel/hytale/protocol/EntityStatOnHit.java new file mode 100644 index 0000000..9c00c85 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EntityStatOnHit.java @@ -0,0 +1,181 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityStatOnHit { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 13; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 16384018; + public int entityStatIndex; + public float amount; + @Nullable + public float[] multipliersPerEntitiesHit; + public float multiplierPerExtraEntityHit; + + public EntityStatOnHit() { + } + + public EntityStatOnHit(int entityStatIndex, float amount, @Nullable float[] multipliersPerEntitiesHit, float multiplierPerExtraEntityHit) { + this.entityStatIndex = entityStatIndex; + this.amount = amount; + this.multipliersPerEntitiesHit = multipliersPerEntitiesHit; + this.multiplierPerExtraEntityHit = multiplierPerExtraEntityHit; + } + + public EntityStatOnHit(@Nonnull EntityStatOnHit other) { + this.entityStatIndex = other.entityStatIndex; + this.amount = other.amount; + this.multipliersPerEntitiesHit = other.multipliersPerEntitiesHit; + this.multiplierPerExtraEntityHit = other.multiplierPerExtraEntityHit; + } + + @Nonnull + public static EntityStatOnHit deserialize(@Nonnull ByteBuf buf, int offset) { + EntityStatOnHit obj = new EntityStatOnHit(); + byte nullBits = buf.getByte(offset); + obj.entityStatIndex = buf.getIntLE(offset + 1); + obj.amount = buf.getFloatLE(offset + 5); + obj.multiplierPerExtraEntityHit = buf.getFloatLE(offset + 9); + int pos = offset + 13; + if ((nullBits & 1) != 0) { + int multipliersPerEntitiesHitCount = VarInt.peek(buf, pos); + if (multipliersPerEntitiesHitCount < 0) { + throw ProtocolException.negativeLength("MultipliersPerEntitiesHit", multipliersPerEntitiesHitCount); + } + + if (multipliersPerEntitiesHitCount > 4096000) { + throw ProtocolException.arrayTooLong("MultipliersPerEntitiesHit", multipliersPerEntitiesHitCount, 4096000); + } + + int multipliersPerEntitiesHitVarLen = VarInt.size(multipliersPerEntitiesHitCount); + if (pos + multipliersPerEntitiesHitVarLen + multipliersPerEntitiesHitCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall( + "MultipliersPerEntitiesHit", pos + multipliersPerEntitiesHitVarLen + multipliersPerEntitiesHitCount * 4, buf.readableBytes() + ); + } + + pos += multipliersPerEntitiesHitVarLen; + obj.multipliersPerEntitiesHit = new float[multipliersPerEntitiesHitCount]; + + for (int i = 0; i < multipliersPerEntitiesHitCount; i++) { + obj.multipliersPerEntitiesHit[i] = buf.getFloatLE(pos + i * 4); + } + + pos += multipliersPerEntitiesHitCount * 4; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 13; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 4; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.multipliersPerEntitiesHit != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.entityStatIndex); + buf.writeFloatLE(this.amount); + buf.writeFloatLE(this.multiplierPerExtraEntityHit); + if (this.multipliersPerEntitiesHit != null) { + if (this.multipliersPerEntitiesHit.length > 4096000) { + throw ProtocolException.arrayTooLong("MultipliersPerEntitiesHit", this.multipliersPerEntitiesHit.length, 4096000); + } + + VarInt.write(buf, this.multipliersPerEntitiesHit.length); + + for (float item : this.multipliersPerEntitiesHit) { + buf.writeFloatLE(item); + } + } + } + + public int computeSize() { + int size = 13; + if (this.multipliersPerEntitiesHit != null) { + size += VarInt.size(this.multipliersPerEntitiesHit.length) + this.multipliersPerEntitiesHit.length * 4; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 13; + if ((nullBits & 1) != 0) { + int multipliersPerEntitiesHitCount = VarInt.peek(buffer, pos); + if (multipliersPerEntitiesHitCount < 0) { + return ValidationResult.error("Invalid array count for MultipliersPerEntitiesHit"); + } + + if (multipliersPerEntitiesHitCount > 4096000) { + return ValidationResult.error("MultipliersPerEntitiesHit exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += multipliersPerEntitiesHitCount * 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading MultipliersPerEntitiesHit"); + } + } + + return ValidationResult.OK; + } + } + + public EntityStatOnHit clone() { + EntityStatOnHit copy = new EntityStatOnHit(); + copy.entityStatIndex = this.entityStatIndex; + copy.amount = this.amount; + copy.multipliersPerEntitiesHit = this.multipliersPerEntitiesHit != null + ? Arrays.copyOf(this.multipliersPerEntitiesHit, this.multipliersPerEntitiesHit.length) + : null; + copy.multiplierPerExtraEntityHit = this.multiplierPerExtraEntityHit; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof EntityStatOnHit other) + ? false + : this.entityStatIndex == other.entityStatIndex + && this.amount == other.amount + && Arrays.equals(this.multipliersPerEntitiesHit, other.multipliersPerEntitiesHit) + && this.multiplierPerExtraEntityHit == other.multiplierPerExtraEntityHit; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.entityStatIndex); + result = 31 * result + Float.hashCode(this.amount); + result = 31 * result + Arrays.hashCode(this.multipliersPerEntitiesHit); + return 31 * result + Float.hashCode(this.multiplierPerExtraEntityHit); + } +} diff --git a/src/com/hypixel/hytale/protocol/EntityStatOp.java b/src/com/hypixel/hytale/protocol/EntityStatOp.java new file mode 100644 index 0000000..a262e60 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EntityStatOp.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum EntityStatOp { + Init(0), + Remove(1), + PutModifier(2), + RemoveModifier(3), + Add(4), + Set(5), + Minimize(6), + Maximize(7), + Reset(8); + + public static final EntityStatOp[] VALUES = values(); + private final int value; + + private EntityStatOp(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static EntityStatOp fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("EntityStatOp", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/EntityStatResetBehavior.java b/src/com/hypixel/hytale/protocol/EntityStatResetBehavior.java new file mode 100644 index 0000000..dfbd439 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EntityStatResetBehavior.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum EntityStatResetBehavior { + InitialValue(0), + MaxValue(1); + + public static final EntityStatResetBehavior[] VALUES = values(); + private final int value; + + private EntityStatResetBehavior(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static EntityStatResetBehavior fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("EntityStatResetBehavior", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/EntityStatType.java b/src/com/hypixel/hytale/protocol/EntityStatType.java new file mode 100644 index 0000000..5d72ac6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EntityStatType.java @@ -0,0 +1,303 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityStatType { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 14; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 26; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + public float value; + public float min; + public float max; + @Nullable + public EntityStatEffects minValueEffects; + @Nullable + public EntityStatEffects maxValueEffects; + @Nonnull + public EntityStatResetBehavior resetBehavior = EntityStatResetBehavior.InitialValue; + + public EntityStatType() { + } + + public EntityStatType( + @Nullable String id, + float value, + float min, + float max, + @Nullable EntityStatEffects minValueEffects, + @Nullable EntityStatEffects maxValueEffects, + @Nonnull EntityStatResetBehavior resetBehavior + ) { + this.id = id; + this.value = value; + this.min = min; + this.max = max; + this.minValueEffects = minValueEffects; + this.maxValueEffects = maxValueEffects; + this.resetBehavior = resetBehavior; + } + + public EntityStatType(@Nonnull EntityStatType other) { + this.id = other.id; + this.value = other.value; + this.min = other.min; + this.max = other.max; + this.minValueEffects = other.minValueEffects; + this.maxValueEffects = other.maxValueEffects; + this.resetBehavior = other.resetBehavior; + } + + @Nonnull + public static EntityStatType deserialize(@Nonnull ByteBuf buf, int offset) { + EntityStatType obj = new EntityStatType(); + byte nullBits = buf.getByte(offset); + obj.value = buf.getFloatLE(offset + 1); + obj.min = buf.getFloatLE(offset + 5); + obj.max = buf.getFloatLE(offset + 9); + obj.resetBehavior = EntityStatResetBehavior.fromValue(buf.getByte(offset + 13)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 26 + buf.getIntLE(offset + 14); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 26 + buf.getIntLE(offset + 18); + obj.minValueEffects = EntityStatEffects.deserialize(buf, varPos1); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 26 + buf.getIntLE(offset + 22); + obj.maxValueEffects = EntityStatEffects.deserialize(buf, varPos2); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 26; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 14); + int pos0 = offset + 26 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 18); + int pos1 = offset + 26 + fieldOffset1; + pos1 += EntityStatEffects.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 22); + int pos2 = offset + 26 + fieldOffset2; + pos2 += EntityStatEffects.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.minValueEffects != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.maxValueEffects != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.value); + buf.writeFloatLE(this.min); + buf.writeFloatLE(this.max); + buf.writeByte(this.resetBehavior.getValue()); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int minValueEffectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int maxValueEffectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.minValueEffects != null) { + buf.setIntLE(minValueEffectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.minValueEffects.serialize(buf); + } else { + buf.setIntLE(minValueEffectsOffsetSlot, -1); + } + + if (this.maxValueEffects != null) { + buf.setIntLE(maxValueEffectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.maxValueEffects.serialize(buf); + } else { + buf.setIntLE(maxValueEffectsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 26; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.minValueEffects != null) { + size += this.minValueEffects.computeSize(); + } + + if (this.maxValueEffects != null) { + size += this.maxValueEffects.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 26) { + return ValidationResult.error("Buffer too small: expected at least 26 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 14); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 26 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int minValueEffectsOffset = buffer.getIntLE(offset + 18); + if (minValueEffectsOffset < 0) { + return ValidationResult.error("Invalid offset for MinValueEffects"); + } + + int posx = offset + 26 + minValueEffectsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MinValueEffects"); + } + + ValidationResult minValueEffectsResult = EntityStatEffects.validateStructure(buffer, posx); + if (!minValueEffectsResult.isValid()) { + return ValidationResult.error("Invalid MinValueEffects: " + minValueEffectsResult.error()); + } + + posx += EntityStatEffects.computeBytesConsumed(buffer, posx); + } + + if ((nullBits & 4) != 0) { + int maxValueEffectsOffset = buffer.getIntLE(offset + 22); + if (maxValueEffectsOffset < 0) { + return ValidationResult.error("Invalid offset for MaxValueEffects"); + } + + int posxx = offset + 26 + maxValueEffectsOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MaxValueEffects"); + } + + ValidationResult maxValueEffectsResult = EntityStatEffects.validateStructure(buffer, posxx); + if (!maxValueEffectsResult.isValid()) { + return ValidationResult.error("Invalid MaxValueEffects: " + maxValueEffectsResult.error()); + } + + posxx += EntityStatEffects.computeBytesConsumed(buffer, posxx); + } + + return ValidationResult.OK; + } + } + + public EntityStatType clone() { + EntityStatType copy = new EntityStatType(); + copy.id = this.id; + copy.value = this.value; + copy.min = this.min; + copy.max = this.max; + copy.minValueEffects = this.minValueEffects != null ? this.minValueEffects.clone() : null; + copy.maxValueEffects = this.maxValueEffects != null ? this.maxValueEffects.clone() : null; + copy.resetBehavior = this.resetBehavior; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof EntityStatType other) + ? false + : Objects.equals(this.id, other.id) + && this.value == other.value + && this.min == other.min + && this.max == other.max + && Objects.equals(this.minValueEffects, other.minValueEffects) + && Objects.equals(this.maxValueEffects, other.maxValueEffects) + && Objects.equals(this.resetBehavior, other.resetBehavior); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.value, this.min, this.max, this.minValueEffects, this.maxValueEffects, this.resetBehavior); + } +} diff --git a/src/com/hypixel/hytale/protocol/EntityStatUpdate.java b/src/com/hypixel/hytale/protocol/EntityStatUpdate.java new file mode 100644 index 0000000..07edfc2 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EntityStatUpdate.java @@ -0,0 +1,348 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityStatUpdate { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 13; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 21; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public EntityStatOp op = EntityStatOp.Init; + public boolean predictable; + public float value; + @Nullable + public Map modifiers; + @Nullable + public String modifierKey; + @Nullable + public Modifier modifier; + + public EntityStatUpdate() { + } + + public EntityStatUpdate( + @Nonnull EntityStatOp op, + boolean predictable, + float value, + @Nullable Map modifiers, + @Nullable String modifierKey, + @Nullable Modifier modifier + ) { + this.op = op; + this.predictable = predictable; + this.value = value; + this.modifiers = modifiers; + this.modifierKey = modifierKey; + this.modifier = modifier; + } + + public EntityStatUpdate(@Nonnull EntityStatUpdate other) { + this.op = other.op; + this.predictable = other.predictable; + this.value = other.value; + this.modifiers = other.modifiers; + this.modifierKey = other.modifierKey; + this.modifier = other.modifier; + } + + @Nonnull + public static EntityStatUpdate deserialize(@Nonnull ByteBuf buf, int offset) { + EntityStatUpdate obj = new EntityStatUpdate(); + byte nullBits = buf.getByte(offset); + obj.op = EntityStatOp.fromValue(buf.getByte(offset + 1)); + obj.predictable = buf.getByte(offset + 2) != 0; + obj.value = buf.getFloatLE(offset + 3); + if ((nullBits & 4) != 0) { + obj.modifier = Modifier.deserialize(buf, offset + 7); + } + + if ((nullBits & 1) != 0) { + int varPos0 = offset + 21 + buf.getIntLE(offset + 13); + int modifiersCount = VarInt.peek(buf, varPos0); + if (modifiersCount < 0) { + throw ProtocolException.negativeLength("Modifiers", modifiersCount); + } + + if (modifiersCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Modifiers", modifiersCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + obj.modifiers = new HashMap<>(modifiersCount); + int dictPos = varPos0 + varIntLen; + + for (int i = 0; i < modifiersCount; i++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + Modifier val = Modifier.deserialize(buf, dictPos); + dictPos += Modifier.computeBytesConsumed(buf, dictPos); + if (obj.modifiers.put(key, val) != null) { + throw ProtocolException.duplicateKey("modifiers", key); + } + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 21 + buf.getIntLE(offset + 17); + int modifierKeyLen = VarInt.peek(buf, varPos1); + if (modifierKeyLen < 0) { + throw ProtocolException.negativeLength("ModifierKey", modifierKeyLen); + } + + if (modifierKeyLen > 4096000) { + throw ProtocolException.stringTooLong("ModifierKey", modifierKeyLen, 4096000); + } + + obj.modifierKey = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 21; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 13); + int pos0 = offset + 21 + fieldOffset0; + int dictLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + pos0 += Modifier.computeBytesConsumed(buf, pos0); + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 17); + int pos1 = offset + 21 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.modifiers != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.modifierKey != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.modifier != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + buf.writeByte(this.op.getValue()); + buf.writeByte(this.predictable ? 1 : 0); + buf.writeFloatLE(this.value); + if (this.modifier != null) { + this.modifier.serialize(buf); + } else { + buf.writeZero(6); + } + + int modifiersOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int modifierKeyOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.modifiers != null) { + buf.setIntLE(modifiersOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.modifiers.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Modifiers", this.modifiers.size(), 4096000); + } + + VarInt.write(buf, this.modifiers.size()); + + for (Entry e : this.modifiers.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(modifiersOffsetSlot, -1); + } + + if (this.modifierKey != null) { + buf.setIntLE(modifierKeyOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.modifierKey, 4096000); + } else { + buf.setIntLE(modifierKeyOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 21; + if (this.modifiers != null) { + int modifiersSize = 0; + + for (Entry kvp : this.modifiers.entrySet()) { + modifiersSize += PacketIO.stringSize(kvp.getKey()) + 6; + } + + size += VarInt.size(this.modifiers.size()) + modifiersSize; + } + + if (this.modifierKey != null) { + size += PacketIO.stringSize(this.modifierKey); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 21) { + return ValidationResult.error("Buffer too small: expected at least 21 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int modifiersOffset = buffer.getIntLE(offset + 13); + if (modifiersOffset < 0) { + return ValidationResult.error("Invalid offset for Modifiers"); + } + + int pos = offset + 21 + modifiersOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Modifiers"); + } + + int modifiersCount = VarInt.peek(buffer, pos); + if (modifiersCount < 0) { + return ValidationResult.error("Invalid dictionary count for Modifiers"); + } + + if (modifiersCount > 4096000) { + return ValidationResult.error("Modifiers exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < modifiersCount; i++) { + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += 6; + } + } + + if ((nullBits & 2) != 0) { + int modifierKeyOffset = buffer.getIntLE(offset + 17); + if (modifierKeyOffset < 0) { + return ValidationResult.error("Invalid offset for ModifierKey"); + } + + int posx = offset + 21 + modifierKeyOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ModifierKey"); + } + + int modifierKeyLen = VarInt.peek(buffer, posx); + if (modifierKeyLen < 0) { + return ValidationResult.error("Invalid string length for ModifierKey"); + } + + if (modifierKeyLen > 4096000) { + return ValidationResult.error("ModifierKey exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += modifierKeyLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ModifierKey"); + } + } + + return ValidationResult.OK; + } + } + + public EntityStatUpdate clone() { + EntityStatUpdate copy = new EntityStatUpdate(); + copy.op = this.op; + copy.predictable = this.predictable; + copy.value = this.value; + if (this.modifiers != null) { + Map m = new HashMap<>(); + + for (Entry e : this.modifiers.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.modifiers = m; + } + + copy.modifierKey = this.modifierKey; + copy.modifier = this.modifier != null ? this.modifier.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof EntityStatUpdate other) + ? false + : Objects.equals(this.op, other.op) + && this.predictable == other.predictable + && this.value == other.value + && Objects.equals(this.modifiers, other.modifiers) + && Objects.equals(this.modifierKey, other.modifierKey) + && Objects.equals(this.modifier, other.modifier); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.op, this.predictable, this.value, this.modifiers, this.modifierKey, this.modifier); + } +} diff --git a/src/com/hypixel/hytale/protocol/EntityUIComponent.java b/src/com/hypixel/hytale/protocol/EntityUIComponent.java new file mode 100644 index 0000000..ce34f6d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EntityUIComponent.java @@ -0,0 +1,293 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityUIComponent { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 51; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 51; + public static final int MAX_SIZE = 139264056; + @Nonnull + public EntityUIType type = EntityUIType.EntityStat; + @Nullable + public Vector2f hitboxOffset; + public boolean unknown; + public int entityStatIndex; + @Nullable + public RangeVector2f combatTextRandomPositionOffsetRange; + public float combatTextViewportMargin; + public float combatTextDuration; + public float combatTextHitAngleModifierStrength; + public float combatTextFontSize; + @Nullable + public Color combatTextColor; + @Nullable + public CombatTextEntityUIComponentAnimationEvent[] combatTextAnimationEvents; + + public EntityUIComponent() { + } + + public EntityUIComponent( + @Nonnull EntityUIType type, + @Nullable Vector2f hitboxOffset, + boolean unknown, + int entityStatIndex, + @Nullable RangeVector2f combatTextRandomPositionOffsetRange, + float combatTextViewportMargin, + float combatTextDuration, + float combatTextHitAngleModifierStrength, + float combatTextFontSize, + @Nullable Color combatTextColor, + @Nullable CombatTextEntityUIComponentAnimationEvent[] combatTextAnimationEvents + ) { + this.type = type; + this.hitboxOffset = hitboxOffset; + this.unknown = unknown; + this.entityStatIndex = entityStatIndex; + this.combatTextRandomPositionOffsetRange = combatTextRandomPositionOffsetRange; + this.combatTextViewportMargin = combatTextViewportMargin; + this.combatTextDuration = combatTextDuration; + this.combatTextHitAngleModifierStrength = combatTextHitAngleModifierStrength; + this.combatTextFontSize = combatTextFontSize; + this.combatTextColor = combatTextColor; + this.combatTextAnimationEvents = combatTextAnimationEvents; + } + + public EntityUIComponent(@Nonnull EntityUIComponent other) { + this.type = other.type; + this.hitboxOffset = other.hitboxOffset; + this.unknown = other.unknown; + this.entityStatIndex = other.entityStatIndex; + this.combatTextRandomPositionOffsetRange = other.combatTextRandomPositionOffsetRange; + this.combatTextViewportMargin = other.combatTextViewportMargin; + this.combatTextDuration = other.combatTextDuration; + this.combatTextHitAngleModifierStrength = other.combatTextHitAngleModifierStrength; + this.combatTextFontSize = other.combatTextFontSize; + this.combatTextColor = other.combatTextColor; + this.combatTextAnimationEvents = other.combatTextAnimationEvents; + } + + @Nonnull + public static EntityUIComponent deserialize(@Nonnull ByteBuf buf, int offset) { + EntityUIComponent obj = new EntityUIComponent(); + byte nullBits = buf.getByte(offset); + obj.type = EntityUIType.fromValue(buf.getByte(offset + 1)); + if ((nullBits & 1) != 0) { + obj.hitboxOffset = Vector2f.deserialize(buf, offset + 2); + } + + obj.unknown = buf.getByte(offset + 10) != 0; + obj.entityStatIndex = buf.getIntLE(offset + 11); + if ((nullBits & 2) != 0) { + obj.combatTextRandomPositionOffsetRange = RangeVector2f.deserialize(buf, offset + 15); + } + + obj.combatTextViewportMargin = buf.getFloatLE(offset + 32); + obj.combatTextDuration = buf.getFloatLE(offset + 36); + obj.combatTextHitAngleModifierStrength = buf.getFloatLE(offset + 40); + obj.combatTextFontSize = buf.getFloatLE(offset + 44); + if ((nullBits & 4) != 0) { + obj.combatTextColor = Color.deserialize(buf, offset + 48); + } + + int pos = offset + 51; + if ((nullBits & 8) != 0) { + int combatTextAnimationEventsCount = VarInt.peek(buf, pos); + if (combatTextAnimationEventsCount < 0) { + throw ProtocolException.negativeLength("CombatTextAnimationEvents", combatTextAnimationEventsCount); + } + + if (combatTextAnimationEventsCount > 4096000) { + throw ProtocolException.arrayTooLong("CombatTextAnimationEvents", combatTextAnimationEventsCount, 4096000); + } + + int combatTextAnimationEventsVarLen = VarInt.size(combatTextAnimationEventsCount); + if (pos + combatTextAnimationEventsVarLen + combatTextAnimationEventsCount * 34L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall( + "CombatTextAnimationEvents", pos + combatTextAnimationEventsVarLen + combatTextAnimationEventsCount * 34, buf.readableBytes() + ); + } + + pos += combatTextAnimationEventsVarLen; + obj.combatTextAnimationEvents = new CombatTextEntityUIComponentAnimationEvent[combatTextAnimationEventsCount]; + + for (int i = 0; i < combatTextAnimationEventsCount; i++) { + obj.combatTextAnimationEvents[i] = CombatTextEntityUIComponentAnimationEvent.deserialize(buf, pos); + pos += CombatTextEntityUIComponentAnimationEvent.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 51; + if ((nullBits & 8) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += CombatTextEntityUIComponentAnimationEvent.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.hitboxOffset != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.combatTextRandomPositionOffsetRange != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.combatTextColor != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.combatTextAnimationEvents != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.hitboxOffset != null) { + this.hitboxOffset.serialize(buf); + } else { + buf.writeZero(8); + } + + buf.writeByte(this.unknown ? 1 : 0); + buf.writeIntLE(this.entityStatIndex); + if (this.combatTextRandomPositionOffsetRange != null) { + this.combatTextRandomPositionOffsetRange.serialize(buf); + } else { + buf.writeZero(17); + } + + buf.writeFloatLE(this.combatTextViewportMargin); + buf.writeFloatLE(this.combatTextDuration); + buf.writeFloatLE(this.combatTextHitAngleModifierStrength); + buf.writeFloatLE(this.combatTextFontSize); + if (this.combatTextColor != null) { + this.combatTextColor.serialize(buf); + } else { + buf.writeZero(3); + } + + if (this.combatTextAnimationEvents != null) { + if (this.combatTextAnimationEvents.length > 4096000) { + throw ProtocolException.arrayTooLong("CombatTextAnimationEvents", this.combatTextAnimationEvents.length, 4096000); + } + + VarInt.write(buf, this.combatTextAnimationEvents.length); + + for (CombatTextEntityUIComponentAnimationEvent item : this.combatTextAnimationEvents) { + item.serialize(buf); + } + } + } + + public int computeSize() { + int size = 51; + if (this.combatTextAnimationEvents != null) { + size += VarInt.size(this.combatTextAnimationEvents.length) + this.combatTextAnimationEvents.length * 34; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 51) { + return ValidationResult.error("Buffer too small: expected at least 51 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 51; + if ((nullBits & 8) != 0) { + int combatTextAnimationEventsCount = VarInt.peek(buffer, pos); + if (combatTextAnimationEventsCount < 0) { + return ValidationResult.error("Invalid array count for CombatTextAnimationEvents"); + } + + if (combatTextAnimationEventsCount > 4096000) { + return ValidationResult.error("CombatTextAnimationEvents exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += combatTextAnimationEventsCount * 34; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading CombatTextAnimationEvents"); + } + } + + return ValidationResult.OK; + } + } + + public EntityUIComponent clone() { + EntityUIComponent copy = new EntityUIComponent(); + copy.type = this.type; + copy.hitboxOffset = this.hitboxOffset != null ? this.hitboxOffset.clone() : null; + copy.unknown = this.unknown; + copy.entityStatIndex = this.entityStatIndex; + copy.combatTextRandomPositionOffsetRange = this.combatTextRandomPositionOffsetRange != null ? this.combatTextRandomPositionOffsetRange.clone() : null; + copy.combatTextViewportMargin = this.combatTextViewportMargin; + copy.combatTextDuration = this.combatTextDuration; + copy.combatTextHitAngleModifierStrength = this.combatTextHitAngleModifierStrength; + copy.combatTextFontSize = this.combatTextFontSize; + copy.combatTextColor = this.combatTextColor != null ? this.combatTextColor.clone() : null; + copy.combatTextAnimationEvents = this.combatTextAnimationEvents != null + ? Arrays.stream(this.combatTextAnimationEvents).map(e -> e.clone()).toArray(CombatTextEntityUIComponentAnimationEvent[]::new) + : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof EntityUIComponent other) + ? false + : Objects.equals(this.type, other.type) + && Objects.equals(this.hitboxOffset, other.hitboxOffset) + && this.unknown == other.unknown + && this.entityStatIndex == other.entityStatIndex + && Objects.equals(this.combatTextRandomPositionOffsetRange, other.combatTextRandomPositionOffsetRange) + && this.combatTextViewportMargin == other.combatTextViewportMargin + && this.combatTextDuration == other.combatTextDuration + && this.combatTextHitAngleModifierStrength == other.combatTextHitAngleModifierStrength + && this.combatTextFontSize == other.combatTextFontSize + && Objects.equals(this.combatTextColor, other.combatTextColor) + && Arrays.equals((Object[])this.combatTextAnimationEvents, (Object[])other.combatTextAnimationEvents); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.type); + result = 31 * result + Objects.hashCode(this.hitboxOffset); + result = 31 * result + Boolean.hashCode(this.unknown); + result = 31 * result + Integer.hashCode(this.entityStatIndex); + result = 31 * result + Objects.hashCode(this.combatTextRandomPositionOffsetRange); + result = 31 * result + Float.hashCode(this.combatTextViewportMargin); + result = 31 * result + Float.hashCode(this.combatTextDuration); + result = 31 * result + Float.hashCode(this.combatTextHitAngleModifierStrength); + result = 31 * result + Float.hashCode(this.combatTextFontSize); + result = 31 * result + Objects.hashCode(this.combatTextColor); + return 31 * result + Arrays.hashCode((Object[])this.combatTextAnimationEvents); + } +} diff --git a/src/com/hypixel/hytale/protocol/EntityUIType.java b/src/com/hypixel/hytale/protocol/EntityUIType.java new file mode 100644 index 0000000..0a7da30 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EntityUIType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum EntityUIType { + EntityStat(0), + CombatText(1); + + public static final EntityUIType[] VALUES = values(); + private final int value; + + private EntityUIType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static EntityUIType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("EntityUIType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/EntityUpdate.java b/src/com/hypixel/hytale/protocol/EntityUpdate.java new file mode 100644 index 0000000..eab33df --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EntityUpdate.java @@ -0,0 +1,291 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityUpdate { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 1677721600; + public int networkId; + @Nullable + public ComponentUpdateType[] removed; + @Nullable + public ComponentUpdate[] updates; + + public EntityUpdate() { + } + + public EntityUpdate(int networkId, @Nullable ComponentUpdateType[] removed, @Nullable ComponentUpdate[] updates) { + this.networkId = networkId; + this.removed = removed; + this.updates = updates; + } + + public EntityUpdate(@Nonnull EntityUpdate other) { + this.networkId = other.networkId; + this.removed = other.removed; + this.updates = other.updates; + } + + @Nonnull + public static EntityUpdate deserialize(@Nonnull ByteBuf buf, int offset) { + EntityUpdate obj = new EntityUpdate(); + byte nullBits = buf.getByte(offset); + obj.networkId = buf.getIntLE(offset + 1); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 5); + int removedCount = VarInt.peek(buf, varPos0); + if (removedCount < 0) { + throw ProtocolException.negativeLength("Removed", removedCount); + } + + if (removedCount > 4096000) { + throw ProtocolException.arrayTooLong("Removed", removedCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + removedCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Removed", varPos0 + varIntLen + removedCount * 1, buf.readableBytes()); + } + + obj.removed = new ComponentUpdateType[removedCount]; + int elemPos = varPos0 + varIntLen; + + for (int i = 0; i < removedCount; i++) { + obj.removed[i] = ComponentUpdateType.fromValue(buf.getByte(elemPos)); + elemPos++; + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 9); + int updatesCount = VarInt.peek(buf, varPos1); + if (updatesCount < 0) { + throw ProtocolException.negativeLength("Updates", updatesCount); + } + + if (updatesCount > 4096000) { + throw ProtocolException.arrayTooLong("Updates", updatesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + updatesCount * 159L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Updates", varPos1 + varIntLen + updatesCount * 159, buf.readableBytes()); + } + + obj.updates = new ComponentUpdate[updatesCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < updatesCount; i++) { + obj.updates[i] = ComponentUpdate.deserialize(buf, elemPos); + elemPos += ComponentUpdate.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 5); + int pos0 = offset + 13 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + arrLen * 1; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 9); + int pos1 = offset + 13 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += ComponentUpdate.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.removed != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.updates != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.networkId); + int removedOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int updatesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.removed != null) { + buf.setIntLE(removedOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.removed.length > 4096000) { + throw ProtocolException.arrayTooLong("Removed", this.removed.length, 4096000); + } + + VarInt.write(buf, this.removed.length); + + for (ComponentUpdateType item : this.removed) { + buf.writeByte(item.getValue()); + } + } else { + buf.setIntLE(removedOffsetSlot, -1); + } + + if (this.updates != null) { + buf.setIntLE(updatesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.updates.length > 4096000) { + throw ProtocolException.arrayTooLong("Updates", this.updates.length, 4096000); + } + + VarInt.write(buf, this.updates.length); + + for (ComponentUpdate item : this.updates) { + item.serialize(buf); + } + } else { + buf.setIntLE(updatesOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 13; + if (this.removed != null) { + size += VarInt.size(this.removed.length) + this.removed.length * 1; + } + + if (this.updates != null) { + int updatesSize = 0; + + for (ComponentUpdate elem : this.updates) { + updatesSize += elem.computeSize(); + } + + size += VarInt.size(this.updates.length) + updatesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int removedOffset = buffer.getIntLE(offset + 5); + if (removedOffset < 0) { + return ValidationResult.error("Invalid offset for Removed"); + } + + int pos = offset + 13 + removedOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Removed"); + } + + int removedCount = VarInt.peek(buffer, pos); + if (removedCount < 0) { + return ValidationResult.error("Invalid array count for Removed"); + } + + if (removedCount > 4096000) { + return ValidationResult.error("Removed exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += removedCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Removed"); + } + } + + if ((nullBits & 2) != 0) { + int updatesOffset = buffer.getIntLE(offset + 9); + if (updatesOffset < 0) { + return ValidationResult.error("Invalid offset for Updates"); + } + + int posx = offset + 13 + updatesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Updates"); + } + + int updatesCount = VarInt.peek(buffer, posx); + if (updatesCount < 0) { + return ValidationResult.error("Invalid array count for Updates"); + } + + if (updatesCount > 4096000) { + return ValidationResult.error("Updates exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < updatesCount; i++) { + ValidationResult structResult = ComponentUpdate.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ComponentUpdate in Updates[" + i + "]: " + structResult.error()); + } + + posx += ComponentUpdate.computeBytesConsumed(buffer, posx); + } + } + + return ValidationResult.OK; + } + } + + public EntityUpdate clone() { + EntityUpdate copy = new EntityUpdate(); + copy.networkId = this.networkId; + copy.removed = this.removed != null ? Arrays.copyOf(this.removed, this.removed.length) : null; + copy.updates = this.updates != null ? Arrays.stream(this.updates).map(e -> e.clone()).toArray(ComponentUpdate[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof EntityUpdate other) + ? false + : this.networkId == other.networkId + && Arrays.equals((Object[])this.removed, (Object[])other.removed) + && Arrays.equals((Object[])this.updates, (Object[])other.updates); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.networkId); + result = 31 * result + Arrays.hashCode((Object[])this.removed); + return 31 * result + Arrays.hashCode((Object[])this.updates); + } +} diff --git a/src/com/hypixel/hytale/protocol/EqualizerEffect.java b/src/com/hypixel/hytale/protocol/EqualizerEffect.java new file mode 100644 index 0000000..5a6035f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/EqualizerEffect.java @@ -0,0 +1,229 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EqualizerEffect { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 41; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 41; + public static final int MAX_SIZE = 16384046; + @Nullable + public String id; + public float lowGain; + public float lowCutOff; + public float lowMidGain; + public float lowMidCenter; + public float lowMidWidth; + public float highMidGain; + public float highMidCenter; + public float highMidWidth; + public float highGain; + public float highCutOff; + + public EqualizerEffect() { + } + + public EqualizerEffect( + @Nullable String id, + float lowGain, + float lowCutOff, + float lowMidGain, + float lowMidCenter, + float lowMidWidth, + float highMidGain, + float highMidCenter, + float highMidWidth, + float highGain, + float highCutOff + ) { + this.id = id; + this.lowGain = lowGain; + this.lowCutOff = lowCutOff; + this.lowMidGain = lowMidGain; + this.lowMidCenter = lowMidCenter; + this.lowMidWidth = lowMidWidth; + this.highMidGain = highMidGain; + this.highMidCenter = highMidCenter; + this.highMidWidth = highMidWidth; + this.highGain = highGain; + this.highCutOff = highCutOff; + } + + public EqualizerEffect(@Nonnull EqualizerEffect other) { + this.id = other.id; + this.lowGain = other.lowGain; + this.lowCutOff = other.lowCutOff; + this.lowMidGain = other.lowMidGain; + this.lowMidCenter = other.lowMidCenter; + this.lowMidWidth = other.lowMidWidth; + this.highMidGain = other.highMidGain; + this.highMidCenter = other.highMidCenter; + this.highMidWidth = other.highMidWidth; + this.highGain = other.highGain; + this.highCutOff = other.highCutOff; + } + + @Nonnull + public static EqualizerEffect deserialize(@Nonnull ByteBuf buf, int offset) { + EqualizerEffect obj = new EqualizerEffect(); + byte nullBits = buf.getByte(offset); + obj.lowGain = buf.getFloatLE(offset + 1); + obj.lowCutOff = buf.getFloatLE(offset + 5); + obj.lowMidGain = buf.getFloatLE(offset + 9); + obj.lowMidCenter = buf.getFloatLE(offset + 13); + obj.lowMidWidth = buf.getFloatLE(offset + 17); + obj.highMidGain = buf.getFloatLE(offset + 21); + obj.highMidCenter = buf.getFloatLE(offset + 25); + obj.highMidWidth = buf.getFloatLE(offset + 29); + obj.highGain = buf.getFloatLE(offset + 33); + obj.highCutOff = buf.getFloatLE(offset + 37); + int pos = offset + 41; + if ((nullBits & 1) != 0) { + int idLen = VarInt.peek(buf, pos); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + int idVarLen = VarInt.length(buf, pos); + obj.id = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += idVarLen + idLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 41; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.lowGain); + buf.writeFloatLE(this.lowCutOff); + buf.writeFloatLE(this.lowMidGain); + buf.writeFloatLE(this.lowMidCenter); + buf.writeFloatLE(this.lowMidWidth); + buf.writeFloatLE(this.highMidGain); + buf.writeFloatLE(this.highMidCenter); + buf.writeFloatLE(this.highMidWidth); + buf.writeFloatLE(this.highGain); + buf.writeFloatLE(this.highCutOff); + if (this.id != null) { + PacketIO.writeVarString(buf, this.id, 4096000); + } + } + + public int computeSize() { + int size = 41; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 41) { + return ValidationResult.error("Buffer too small: expected at least 41 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 41; + if ((nullBits & 1) != 0) { + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + return ValidationResult.OK; + } + } + + public EqualizerEffect clone() { + EqualizerEffect copy = new EqualizerEffect(); + copy.id = this.id; + copy.lowGain = this.lowGain; + copy.lowCutOff = this.lowCutOff; + copy.lowMidGain = this.lowMidGain; + copy.lowMidCenter = this.lowMidCenter; + copy.lowMidWidth = this.lowMidWidth; + copy.highMidGain = this.highMidGain; + copy.highMidCenter = this.highMidCenter; + copy.highMidWidth = this.highMidWidth; + copy.highGain = this.highGain; + copy.highCutOff = this.highCutOff; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof EqualizerEffect other) + ? false + : Objects.equals(this.id, other.id) + && this.lowGain == other.lowGain + && this.lowCutOff == other.lowCutOff + && this.lowMidGain == other.lowMidGain + && this.lowMidCenter == other.lowMidCenter + && this.lowMidWidth == other.lowMidWidth + && this.highMidGain == other.highMidGain + && this.highMidCenter == other.highMidCenter + && this.highMidWidth == other.highMidWidth + && this.highGain == other.highGain + && this.highCutOff == other.highCutOff; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.id, + this.lowGain, + this.lowCutOff, + this.lowMidGain, + this.lowMidCenter, + this.lowMidWidth, + this.highMidGain, + this.highMidCenter, + this.highMidWidth, + this.highGain, + this.highCutOff + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/Equipment.java b/src/com/hypixel/hytale/protocol/Equipment.java new file mode 100644 index 0000000..cfd76d8 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Equipment.java @@ -0,0 +1,356 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Equipment { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String[] armorIds; + @Nullable + public String rightHandItemId; + @Nullable + public String leftHandItemId; + + public Equipment() { + } + + public Equipment(@Nullable String[] armorIds, @Nullable String rightHandItemId, @Nullable String leftHandItemId) { + this.armorIds = armorIds; + this.rightHandItemId = rightHandItemId; + this.leftHandItemId = leftHandItemId; + } + + public Equipment(@Nonnull Equipment other) { + this.armorIds = other.armorIds; + this.rightHandItemId = other.rightHandItemId; + this.leftHandItemId = other.leftHandItemId; + } + + @Nonnull + public static Equipment deserialize(@Nonnull ByteBuf buf, int offset) { + Equipment obj = new Equipment(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 1); + int armorIdsCount = VarInt.peek(buf, varPos0); + if (armorIdsCount < 0) { + throw ProtocolException.negativeLength("ArmorIds", armorIdsCount); + } + + if (armorIdsCount > 4096000) { + throw ProtocolException.arrayTooLong("ArmorIds", armorIdsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + armorIdsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("ArmorIds", varPos0 + varIntLen + armorIdsCount * 1, buf.readableBytes()); + } + + obj.armorIds = new String[armorIdsCount]; + int elemPos = varPos0 + varIntLen; + + for (int i = 0; i < armorIdsCount; i++) { + int strLen = VarInt.peek(buf, elemPos); + if (strLen < 0) { + throw ProtocolException.negativeLength("armorIds[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("armorIds[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, elemPos); + obj.armorIds[i] = PacketIO.readVarString(buf, elemPos); + elemPos += strVarLen + strLen; + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 5); + int rightHandItemIdLen = VarInt.peek(buf, varPos1); + if (rightHandItemIdLen < 0) { + throw ProtocolException.negativeLength("RightHandItemId", rightHandItemIdLen); + } + + if (rightHandItemIdLen > 4096000) { + throw ProtocolException.stringTooLong("RightHandItemId", rightHandItemIdLen, 4096000); + } + + obj.rightHandItemId = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 13 + buf.getIntLE(offset + 9); + int leftHandItemIdLen = VarInt.peek(buf, varPos2); + if (leftHandItemIdLen < 0) { + throw ProtocolException.negativeLength("LeftHandItemId", leftHandItemIdLen); + } + + if (leftHandItemIdLen > 4096000) { + throw ProtocolException.stringTooLong("LeftHandItemId", leftHandItemIdLen, 4096000); + } + + obj.leftHandItemId = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 13 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 13 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 9); + int pos2 = offset + 13 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.armorIds != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.rightHandItemId != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.leftHandItemId != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + int armorIdsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rightHandItemIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int leftHandItemIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.armorIds != null) { + buf.setIntLE(armorIdsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.armorIds.length > 4096000) { + throw ProtocolException.arrayTooLong("ArmorIds", this.armorIds.length, 4096000); + } + + VarInt.write(buf, this.armorIds.length); + + for (String item : this.armorIds) { + PacketIO.writeVarString(buf, item, 4096000); + } + } else { + buf.setIntLE(armorIdsOffsetSlot, -1); + } + + if (this.rightHandItemId != null) { + buf.setIntLE(rightHandItemIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.rightHandItemId, 4096000); + } else { + buf.setIntLE(rightHandItemIdOffsetSlot, -1); + } + + if (this.leftHandItemId != null) { + buf.setIntLE(leftHandItemIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.leftHandItemId, 4096000); + } else { + buf.setIntLE(leftHandItemIdOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 13; + if (this.armorIds != null) { + int armorIdsSize = 0; + + for (String elem : this.armorIds) { + armorIdsSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.armorIds.length) + armorIdsSize; + } + + if (this.rightHandItemId != null) { + size += PacketIO.stringSize(this.rightHandItemId); + } + + if (this.leftHandItemId != null) { + size += PacketIO.stringSize(this.leftHandItemId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int armorIdsOffset = buffer.getIntLE(offset + 1); + if (armorIdsOffset < 0) { + return ValidationResult.error("Invalid offset for ArmorIds"); + } + + int pos = offset + 13 + armorIdsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ArmorIds"); + } + + int armorIdsCount = VarInt.peek(buffer, pos); + if (armorIdsCount < 0) { + return ValidationResult.error("Invalid array count for ArmorIds"); + } + + if (armorIdsCount > 4096000) { + return ValidationResult.error("ArmorIds exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < armorIdsCount; i++) { + int strLen = VarInt.peek(buffer, pos); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in ArmorIds"); + } + + pos += VarInt.length(buffer, pos); + pos += strLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in ArmorIds"); + } + } + } + + if ((nullBits & 2) != 0) { + int rightHandItemIdOffset = buffer.getIntLE(offset + 5); + if (rightHandItemIdOffset < 0) { + return ValidationResult.error("Invalid offset for RightHandItemId"); + } + + int posx = offset + 13 + rightHandItemIdOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for RightHandItemId"); + } + + int rightHandItemIdLen = VarInt.peek(buffer, posx); + if (rightHandItemIdLen < 0) { + return ValidationResult.error("Invalid string length for RightHandItemId"); + } + + if (rightHandItemIdLen > 4096000) { + return ValidationResult.error("RightHandItemId exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += rightHandItemIdLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading RightHandItemId"); + } + } + + if ((nullBits & 4) != 0) { + int leftHandItemIdOffset = buffer.getIntLE(offset + 9); + if (leftHandItemIdOffset < 0) { + return ValidationResult.error("Invalid offset for LeftHandItemId"); + } + + int posxx = offset + 13 + leftHandItemIdOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for LeftHandItemId"); + } + + int leftHandItemIdLen = VarInt.peek(buffer, posxx); + if (leftHandItemIdLen < 0) { + return ValidationResult.error("Invalid string length for LeftHandItemId"); + } + + if (leftHandItemIdLen > 4096000) { + return ValidationResult.error("LeftHandItemId exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += leftHandItemIdLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading LeftHandItemId"); + } + } + + return ValidationResult.OK; + } + } + + public Equipment clone() { + Equipment copy = new Equipment(); + copy.armorIds = this.armorIds != null ? Arrays.copyOf(this.armorIds, this.armorIds.length) : null; + copy.rightHandItemId = this.rightHandItemId; + copy.leftHandItemId = this.leftHandItemId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Equipment other) + ? false + : Arrays.equals((Object[])this.armorIds, (Object[])other.armorIds) + && Objects.equals(this.rightHandItemId, other.rightHandItemId) + && Objects.equals(this.leftHandItemId, other.leftHandItemId); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode((Object[])this.armorIds); + result = 31 * result + Objects.hashCode(this.rightHandItemId); + return 31 * result + Objects.hashCode(this.leftHandItemId); + } +} diff --git a/src/com/hypixel/hytale/protocol/ExtraResources.java b/src/com/hypixel/hytale/protocol/ExtraResources.java new file mode 100644 index 0000000..f3cdb4b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ExtraResources.java @@ -0,0 +1,165 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ExtraResources { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public ItemQuantity[] resources; + + public ExtraResources() { + } + + public ExtraResources(@Nullable ItemQuantity[] resources) { + this.resources = resources; + } + + public ExtraResources(@Nonnull ExtraResources other) { + this.resources = other.resources; + } + + @Nonnull + public static ExtraResources deserialize(@Nonnull ByteBuf buf, int offset) { + ExtraResources obj = new ExtraResources(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int resourcesCount = VarInt.peek(buf, pos); + if (resourcesCount < 0) { + throw ProtocolException.negativeLength("Resources", resourcesCount); + } + + if (resourcesCount > 4096000) { + throw ProtocolException.arrayTooLong("Resources", resourcesCount, 4096000); + } + + int resourcesVarLen = VarInt.size(resourcesCount); + if (pos + resourcesVarLen + resourcesCount * 5L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Resources", pos + resourcesVarLen + resourcesCount * 5, buf.readableBytes()); + } + + pos += resourcesVarLen; + obj.resources = new ItemQuantity[resourcesCount]; + + for (int i = 0; i < resourcesCount; i++) { + obj.resources[i] = ItemQuantity.deserialize(buf, pos); + pos += ItemQuantity.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += ItemQuantity.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.resources != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.resources != null) { + if (this.resources.length > 4096000) { + throw ProtocolException.arrayTooLong("Resources", this.resources.length, 4096000); + } + + VarInt.write(buf, this.resources.length); + + for (ItemQuantity item : this.resources) { + item.serialize(buf); + } + } + } + + public int computeSize() { + int size = 1; + if (this.resources != null) { + int resourcesSize = 0; + + for (ItemQuantity elem : this.resources) { + resourcesSize += elem.computeSize(); + } + + size += VarInt.size(this.resources.length) + resourcesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int resourcesCount = VarInt.peek(buffer, pos); + if (resourcesCount < 0) { + return ValidationResult.error("Invalid array count for Resources"); + } + + if (resourcesCount > 4096000) { + return ValidationResult.error("Resources exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < resourcesCount; i++) { + ValidationResult structResult = ItemQuantity.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ItemQuantity in Resources[" + i + "]: " + structResult.error()); + } + + pos += ItemQuantity.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public ExtraResources clone() { + ExtraResources copy = new ExtraResources(); + copy.resources = this.resources != null ? Arrays.stream(this.resources).map(e -> e.clone()).toArray(ItemQuantity[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof ExtraResources other ? Arrays.equals((Object[])this.resources, (Object[])other.resources) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.resources); + } +} diff --git a/src/com/hypixel/hytale/protocol/FXRenderMode.java b/src/com/hypixel/hytale/protocol/FXRenderMode.java new file mode 100644 index 0000000..3d0f8e2 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/FXRenderMode.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum FXRenderMode { + BlendLinear(0), + BlendAdd(1), + Erosion(2), + Distortion(3); + + public static final FXRenderMode[] VALUES = values(); + private final int value; + + private FXRenderMode(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static FXRenderMode fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("FXRenderMode", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/FailOnType.java b/src/com/hypixel/hytale/protocol/FailOnType.java new file mode 100644 index 0000000..9b2299c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/FailOnType.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum FailOnType { + Neither(0), + Entity(1), + Block(2), + Either(3); + + public static final FailOnType[] VALUES = values(); + private final int value; + + private FailOnType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static FailOnType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("FailOnType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/FirstClickInteraction.java b/src/com/hypixel/hytale/protocol/FirstClickInteraction.java new file mode 100644 index 0000000..a1623ab --- /dev/null +++ b/src/com/hypixel/hytale/protocol/FirstClickInteraction.java @@ -0,0 +1,506 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FirstClickInteraction extends Interaction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 19; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 39; + public static final int MAX_SIZE = 1677721600; + public int click = Integer.MIN_VALUE; + public int held = Integer.MIN_VALUE; + + public FirstClickInteraction() { + } + + public FirstClickInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int click, + int held + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.click = click; + this.held = held; + } + + public FirstClickInteraction(@Nonnull FirstClickInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.click = other.click; + this.held = other.held; + } + + @Nonnull + public static FirstClickInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + FirstClickInteraction obj = new FirstClickInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.click = buf.getIntLE(offset + 11); + obj.held = buf.getIntLE(offset + 15); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 39 + buf.getIntLE(offset + 19); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 39 + buf.getIntLE(offset + 23); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 39 + buf.getIntLE(offset + 27); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 39 + buf.getIntLE(offset + 31); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 39 + buf.getIntLE(offset + 35); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 39; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 19); + int pos0 = offset + 39 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 23); + int pos1 = offset + 39 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 27); + int pos2 = offset + 39 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 31); + int pos3 = offset + 39 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 35); + int pos4 = offset + 39 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.click); + buf.writeIntLE(this.held); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 39; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 39) { + return ValidationResult.error("Buffer too small: expected at least 39 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 19); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 39 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 23); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 39 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 27); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 39 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 31); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 39 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 35); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 39 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public FirstClickInteraction clone() { + FirstClickInteraction copy = new FirstClickInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.click = this.click; + copy.held = this.held; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof FirstClickInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.click == other.click + && this.held == other.held; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.click); + return 31 * result + Integer.hashCode(this.held); + } +} diff --git a/src/com/hypixel/hytale/protocol/FloatRange.java b/src/com/hypixel/hytale/protocol/FloatRange.java new file mode 100644 index 0000000..d95d191 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/FloatRange.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class FloatRange { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public float inclusiveMin; + public float inclusiveMax; + + public FloatRange() { + } + + public FloatRange(float inclusiveMin, float inclusiveMax) { + this.inclusiveMin = inclusiveMin; + this.inclusiveMax = inclusiveMax; + } + + public FloatRange(@Nonnull FloatRange other) { + this.inclusiveMin = other.inclusiveMin; + this.inclusiveMax = other.inclusiveMax; + } + + @Nonnull + public static FloatRange deserialize(@Nonnull ByteBuf buf, int offset) { + FloatRange obj = new FloatRange(); + obj.inclusiveMin = buf.getFloatLE(offset + 0); + obj.inclusiveMax = buf.getFloatLE(offset + 4); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.inclusiveMin); + buf.writeFloatLE(this.inclusiveMax); + } + + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public FloatRange clone() { + FloatRange copy = new FloatRange(); + copy.inclusiveMin = this.inclusiveMin; + copy.inclusiveMax = this.inclusiveMax; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof FloatRange other) ? false : this.inclusiveMin == other.inclusiveMin && this.inclusiveMax == other.inclusiveMax; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.inclusiveMin, this.inclusiveMax); + } +} diff --git a/src/com/hypixel/hytale/protocol/Fluid.java b/src/com/hypixel/hytale/protocol/Fluid.java new file mode 100644 index 0000000..629c81e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Fluid.java @@ -0,0 +1,623 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Fluid { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 22; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 42; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + public int maxFluidLevel; + @Nullable + public BlockTextures[] cubeTextures; + public boolean requiresAlphaBlending; + @Nonnull + public Opacity opacity = Opacity.Solid; + @Nullable + public ShaderType[] shaderEffect; + @Nullable + public ColorLight light; + public int fluidFXIndex; + public int blockSoundSetIndex; + @Nullable + public String blockParticleSetId; + @Nullable + public Color particleColor; + @Nullable + public int[] tagIndexes; + + public Fluid() { + } + + public Fluid( + @Nullable String id, + int maxFluidLevel, + @Nullable BlockTextures[] cubeTextures, + boolean requiresAlphaBlending, + @Nonnull Opacity opacity, + @Nullable ShaderType[] shaderEffect, + @Nullable ColorLight light, + int fluidFXIndex, + int blockSoundSetIndex, + @Nullable String blockParticleSetId, + @Nullable Color particleColor, + @Nullable int[] tagIndexes + ) { + this.id = id; + this.maxFluidLevel = maxFluidLevel; + this.cubeTextures = cubeTextures; + this.requiresAlphaBlending = requiresAlphaBlending; + this.opacity = opacity; + this.shaderEffect = shaderEffect; + this.light = light; + this.fluidFXIndex = fluidFXIndex; + this.blockSoundSetIndex = blockSoundSetIndex; + this.blockParticleSetId = blockParticleSetId; + this.particleColor = particleColor; + this.tagIndexes = tagIndexes; + } + + public Fluid(@Nonnull Fluid other) { + this.id = other.id; + this.maxFluidLevel = other.maxFluidLevel; + this.cubeTextures = other.cubeTextures; + this.requiresAlphaBlending = other.requiresAlphaBlending; + this.opacity = other.opacity; + this.shaderEffect = other.shaderEffect; + this.light = other.light; + this.fluidFXIndex = other.fluidFXIndex; + this.blockSoundSetIndex = other.blockSoundSetIndex; + this.blockParticleSetId = other.blockParticleSetId; + this.particleColor = other.particleColor; + this.tagIndexes = other.tagIndexes; + } + + @Nonnull + public static Fluid deserialize(@Nonnull ByteBuf buf, int offset) { + Fluid obj = new Fluid(); + byte nullBits = buf.getByte(offset); + obj.maxFluidLevel = buf.getIntLE(offset + 1); + obj.requiresAlphaBlending = buf.getByte(offset + 5) != 0; + obj.opacity = Opacity.fromValue(buf.getByte(offset + 6)); + if ((nullBits & 8) != 0) { + obj.light = ColorLight.deserialize(buf, offset + 7); + } + + obj.fluidFXIndex = buf.getIntLE(offset + 11); + obj.blockSoundSetIndex = buf.getIntLE(offset + 15); + if ((nullBits & 32) != 0) { + obj.particleColor = Color.deserialize(buf, offset + 19); + } + + if ((nullBits & 1) != 0) { + int varPos0 = offset + 42 + buf.getIntLE(offset + 22); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 42 + buf.getIntLE(offset + 26); + int cubeTexturesCount = VarInt.peek(buf, varPos1); + if (cubeTexturesCount < 0) { + throw ProtocolException.negativeLength("CubeTextures", cubeTexturesCount); + } + + if (cubeTexturesCount > 4096000) { + throw ProtocolException.arrayTooLong("CubeTextures", cubeTexturesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + cubeTexturesCount * 5L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("CubeTextures", varPos1 + varIntLen + cubeTexturesCount * 5, buf.readableBytes()); + } + + obj.cubeTextures = new BlockTextures[cubeTexturesCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < cubeTexturesCount; i++) { + obj.cubeTextures[i] = BlockTextures.deserialize(buf, elemPos); + elemPos += BlockTextures.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 42 + buf.getIntLE(offset + 30); + int shaderEffectCount = VarInt.peek(buf, varPos2); + if (shaderEffectCount < 0) { + throw ProtocolException.negativeLength("ShaderEffect", shaderEffectCount); + } + + if (shaderEffectCount > 4096000) { + throw ProtocolException.arrayTooLong("ShaderEffect", shaderEffectCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + shaderEffectCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("ShaderEffect", varPos2 + varIntLen + shaderEffectCount * 1, buf.readableBytes()); + } + + obj.shaderEffect = new ShaderType[shaderEffectCount]; + int elemPos = varPos2 + varIntLen; + + for (int i = 0; i < shaderEffectCount; i++) { + obj.shaderEffect[i] = ShaderType.fromValue(buf.getByte(elemPos)); + elemPos++; + } + } + + if ((nullBits & 16) != 0) { + int varPos3 = offset + 42 + buf.getIntLE(offset + 34); + int blockParticleSetIdLen = VarInt.peek(buf, varPos3); + if (blockParticleSetIdLen < 0) { + throw ProtocolException.negativeLength("BlockParticleSetId", blockParticleSetIdLen); + } + + if (blockParticleSetIdLen > 4096000) { + throw ProtocolException.stringTooLong("BlockParticleSetId", blockParticleSetIdLen, 4096000); + } + + obj.blockParticleSetId = PacketIO.readVarString(buf, varPos3, PacketIO.UTF8); + } + + if ((nullBits & 64) != 0) { + int varPos4 = offset + 42 + buf.getIntLE(offset + 38); + int tagIndexesCount = VarInt.peek(buf, varPos4); + if (tagIndexesCount < 0) { + throw ProtocolException.negativeLength("TagIndexes", tagIndexesCount); + } + + if (tagIndexesCount > 4096000) { + throw ProtocolException.arrayTooLong("TagIndexes", tagIndexesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos4); + if (varPos4 + varIntLen + tagIndexesCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("TagIndexes", varPos4 + varIntLen + tagIndexesCount * 4, buf.readableBytes()); + } + + obj.tagIndexes = new int[tagIndexesCount]; + + for (int i = 0; i < tagIndexesCount; i++) { + obj.tagIndexes[i] = buf.getIntLE(varPos4 + varIntLen + i * 4); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 42; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 22); + int pos0 = offset + 42 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 26); + int pos1 = offset + 42 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += BlockTextures.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 30); + int pos2 = offset + 42 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + arrLen * 1; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 34); + int pos3 = offset + 42 + fieldOffset3; + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 64) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 38); + int pos4 = offset + 42 + fieldOffset4; + int arrLen = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4) + arrLen * 4; + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.cubeTextures != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.shaderEffect != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.light != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.blockParticleSetId != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.particleColor != null) { + nullBits = (byte)(nullBits | 32); + } + + if (this.tagIndexes != null) { + nullBits = (byte)(nullBits | 64); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.maxFluidLevel); + buf.writeByte(this.requiresAlphaBlending ? 1 : 0); + buf.writeByte(this.opacity.getValue()); + if (this.light != null) { + this.light.serialize(buf); + } else { + buf.writeZero(4); + } + + buf.writeIntLE(this.fluidFXIndex); + buf.writeIntLE(this.blockSoundSetIndex); + if (this.particleColor != null) { + this.particleColor.serialize(buf); + } else { + buf.writeZero(3); + } + + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cubeTexturesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int shaderEffectOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int blockParticleSetIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagIndexesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.cubeTextures != null) { + buf.setIntLE(cubeTexturesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.cubeTextures.length > 4096000) { + throw ProtocolException.arrayTooLong("CubeTextures", this.cubeTextures.length, 4096000); + } + + VarInt.write(buf, this.cubeTextures.length); + + for (BlockTextures item : this.cubeTextures) { + item.serialize(buf); + } + } else { + buf.setIntLE(cubeTexturesOffsetSlot, -1); + } + + if (this.shaderEffect != null) { + buf.setIntLE(shaderEffectOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.shaderEffect.length > 4096000) { + throw ProtocolException.arrayTooLong("ShaderEffect", this.shaderEffect.length, 4096000); + } + + VarInt.write(buf, this.shaderEffect.length); + + for (ShaderType item : this.shaderEffect) { + buf.writeByte(item.getValue()); + } + } else { + buf.setIntLE(shaderEffectOffsetSlot, -1); + } + + if (this.blockParticleSetId != null) { + buf.setIntLE(blockParticleSetIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.blockParticleSetId, 4096000); + } else { + buf.setIntLE(blockParticleSetIdOffsetSlot, -1); + } + + if (this.tagIndexes != null) { + buf.setIntLE(tagIndexesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tagIndexes.length > 4096000) { + throw ProtocolException.arrayTooLong("TagIndexes", this.tagIndexes.length, 4096000); + } + + VarInt.write(buf, this.tagIndexes.length); + + for (int item : this.tagIndexes) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagIndexesOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 42; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.cubeTextures != null) { + int cubeTexturesSize = 0; + + for (BlockTextures elem : this.cubeTextures) { + cubeTexturesSize += elem.computeSize(); + } + + size += VarInt.size(this.cubeTextures.length) + cubeTexturesSize; + } + + if (this.shaderEffect != null) { + size += VarInt.size(this.shaderEffect.length) + this.shaderEffect.length * 1; + } + + if (this.blockParticleSetId != null) { + size += PacketIO.stringSize(this.blockParticleSetId); + } + + if (this.tagIndexes != null) { + size += VarInt.size(this.tagIndexes.length) + this.tagIndexes.length * 4; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 42) { + return ValidationResult.error("Buffer too small: expected at least 42 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 22); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 42 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int cubeTexturesOffset = buffer.getIntLE(offset + 26); + if (cubeTexturesOffset < 0) { + return ValidationResult.error("Invalid offset for CubeTextures"); + } + + int posx = offset + 42 + cubeTexturesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for CubeTextures"); + } + + int cubeTexturesCount = VarInt.peek(buffer, posx); + if (cubeTexturesCount < 0) { + return ValidationResult.error("Invalid array count for CubeTextures"); + } + + if (cubeTexturesCount > 4096000) { + return ValidationResult.error("CubeTextures exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < cubeTexturesCount; i++) { + ValidationResult structResult = BlockTextures.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid BlockTextures in CubeTextures[" + i + "]: " + structResult.error()); + } + + posx += BlockTextures.computeBytesConsumed(buffer, posx); + } + } + + if ((nullBits & 4) != 0) { + int shaderEffectOffset = buffer.getIntLE(offset + 30); + if (shaderEffectOffset < 0) { + return ValidationResult.error("Invalid offset for ShaderEffect"); + } + + int posxx = offset + 42 + shaderEffectOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ShaderEffect"); + } + + int shaderEffectCount = VarInt.peek(buffer, posxx); + if (shaderEffectCount < 0) { + return ValidationResult.error("Invalid array count for ShaderEffect"); + } + + if (shaderEffectCount > 4096000) { + return ValidationResult.error("ShaderEffect exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += shaderEffectCount * 1; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ShaderEffect"); + } + } + + if ((nullBits & 16) != 0) { + int blockParticleSetIdOffset = buffer.getIntLE(offset + 34); + if (blockParticleSetIdOffset < 0) { + return ValidationResult.error("Invalid offset for BlockParticleSetId"); + } + + int posxxx = offset + 42 + blockParticleSetIdOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BlockParticleSetId"); + } + + int blockParticleSetIdLen = VarInt.peek(buffer, posxxx); + if (blockParticleSetIdLen < 0) { + return ValidationResult.error("Invalid string length for BlockParticleSetId"); + } + + if (blockParticleSetIdLen > 4096000) { + return ValidationResult.error("BlockParticleSetId exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += blockParticleSetIdLen; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading BlockParticleSetId"); + } + } + + if ((nullBits & 64) != 0) { + int tagIndexesOffset = buffer.getIntLE(offset + 38); + if (tagIndexesOffset < 0) { + return ValidationResult.error("Invalid offset for TagIndexes"); + } + + int posxxxx = offset + 42 + tagIndexesOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for TagIndexes"); + } + + int tagIndexesCount = VarInt.peek(buffer, posxxxx); + if (tagIndexesCount < 0) { + return ValidationResult.error("Invalid array count for TagIndexes"); + } + + if (tagIndexesCount > 4096000) { + return ValidationResult.error("TagIndexes exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + posxxxx += tagIndexesCount * 4; + if (posxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading TagIndexes"); + } + } + + return ValidationResult.OK; + } + } + + public Fluid clone() { + Fluid copy = new Fluid(); + copy.id = this.id; + copy.maxFluidLevel = this.maxFluidLevel; + copy.cubeTextures = this.cubeTextures != null ? Arrays.stream(this.cubeTextures).map(e -> e.clone()).toArray(BlockTextures[]::new) : null; + copy.requiresAlphaBlending = this.requiresAlphaBlending; + copy.opacity = this.opacity; + copy.shaderEffect = this.shaderEffect != null ? Arrays.copyOf(this.shaderEffect, this.shaderEffect.length) : null; + copy.light = this.light != null ? this.light.clone() : null; + copy.fluidFXIndex = this.fluidFXIndex; + copy.blockSoundSetIndex = this.blockSoundSetIndex; + copy.blockParticleSetId = this.blockParticleSetId; + copy.particleColor = this.particleColor != null ? this.particleColor.clone() : null; + copy.tagIndexes = this.tagIndexes != null ? Arrays.copyOf(this.tagIndexes, this.tagIndexes.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Fluid other) + ? false + : Objects.equals(this.id, other.id) + && this.maxFluidLevel == other.maxFluidLevel + && Arrays.equals((Object[])this.cubeTextures, (Object[])other.cubeTextures) + && this.requiresAlphaBlending == other.requiresAlphaBlending + && Objects.equals(this.opacity, other.opacity) + && Arrays.equals((Object[])this.shaderEffect, (Object[])other.shaderEffect) + && Objects.equals(this.light, other.light) + && this.fluidFXIndex == other.fluidFXIndex + && this.blockSoundSetIndex == other.blockSoundSetIndex + && Objects.equals(this.blockParticleSetId, other.blockParticleSetId) + && Objects.equals(this.particleColor, other.particleColor) + && Arrays.equals(this.tagIndexes, other.tagIndexes); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.id); + result = 31 * result + Integer.hashCode(this.maxFluidLevel); + result = 31 * result + Arrays.hashCode((Object[])this.cubeTextures); + result = 31 * result + Boolean.hashCode(this.requiresAlphaBlending); + result = 31 * result + Objects.hashCode(this.opacity); + result = 31 * result + Arrays.hashCode((Object[])this.shaderEffect); + result = 31 * result + Objects.hashCode(this.light); + result = 31 * result + Integer.hashCode(this.fluidFXIndex); + result = 31 * result + Integer.hashCode(this.blockSoundSetIndex); + result = 31 * result + Objects.hashCode(this.blockParticleSetId); + result = 31 * result + Objects.hashCode(this.particleColor); + return 31 * result + Arrays.hashCode(this.tagIndexes); + } +} diff --git a/src/com/hypixel/hytale/protocol/FluidFX.java b/src/com/hypixel/hytale/protocol/FluidFX.java new file mode 100644 index 0000000..d3b216c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/FluidFX.java @@ -0,0 +1,369 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FluidFX { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 61; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 69; + public static final int MAX_SIZE = 32768087; + @Nullable + public String id; + @Nonnull + public ShaderType shader = ShaderType.None; + @Nonnull + public FluidFog fogMode = FluidFog.Color; + @Nullable + public Color fogColor; + @Nullable + public NearFar fogDistance; + public float fogDepthStart; + public float fogDepthFalloff; + @Nullable + public Color colorFilter; + public float colorSaturation; + public float distortionAmplitude; + public float distortionFrequency; + @Nullable + public FluidParticle particle; + @Nullable + public FluidFXMovementSettings movementSettings; + + public FluidFX() { + } + + public FluidFX( + @Nullable String id, + @Nonnull ShaderType shader, + @Nonnull FluidFog fogMode, + @Nullable Color fogColor, + @Nullable NearFar fogDistance, + float fogDepthStart, + float fogDepthFalloff, + @Nullable Color colorFilter, + float colorSaturation, + float distortionAmplitude, + float distortionFrequency, + @Nullable FluidParticle particle, + @Nullable FluidFXMovementSettings movementSettings + ) { + this.id = id; + this.shader = shader; + this.fogMode = fogMode; + this.fogColor = fogColor; + this.fogDistance = fogDistance; + this.fogDepthStart = fogDepthStart; + this.fogDepthFalloff = fogDepthFalloff; + this.colorFilter = colorFilter; + this.colorSaturation = colorSaturation; + this.distortionAmplitude = distortionAmplitude; + this.distortionFrequency = distortionFrequency; + this.particle = particle; + this.movementSettings = movementSettings; + } + + public FluidFX(@Nonnull FluidFX other) { + this.id = other.id; + this.shader = other.shader; + this.fogMode = other.fogMode; + this.fogColor = other.fogColor; + this.fogDistance = other.fogDistance; + this.fogDepthStart = other.fogDepthStart; + this.fogDepthFalloff = other.fogDepthFalloff; + this.colorFilter = other.colorFilter; + this.colorSaturation = other.colorSaturation; + this.distortionAmplitude = other.distortionAmplitude; + this.distortionFrequency = other.distortionFrequency; + this.particle = other.particle; + this.movementSettings = other.movementSettings; + } + + @Nonnull + public static FluidFX deserialize(@Nonnull ByteBuf buf, int offset) { + FluidFX obj = new FluidFX(); + byte nullBits = buf.getByte(offset); + obj.shader = ShaderType.fromValue(buf.getByte(offset + 1)); + obj.fogMode = FluidFog.fromValue(buf.getByte(offset + 2)); + if ((nullBits & 2) != 0) { + obj.fogColor = Color.deserialize(buf, offset + 3); + } + + if ((nullBits & 4) != 0) { + obj.fogDistance = NearFar.deserialize(buf, offset + 6); + } + + obj.fogDepthStart = buf.getFloatLE(offset + 14); + obj.fogDepthFalloff = buf.getFloatLE(offset + 18); + if ((nullBits & 8) != 0) { + obj.colorFilter = Color.deserialize(buf, offset + 22); + } + + obj.colorSaturation = buf.getFloatLE(offset + 25); + obj.distortionAmplitude = buf.getFloatLE(offset + 29); + obj.distortionFrequency = buf.getFloatLE(offset + 33); + if ((nullBits & 32) != 0) { + obj.movementSettings = FluidFXMovementSettings.deserialize(buf, offset + 37); + } + + if ((nullBits & 1) != 0) { + int varPos0 = offset + 69 + buf.getIntLE(offset + 61); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 16) != 0) { + int varPos1 = offset + 69 + buf.getIntLE(offset + 65); + obj.particle = FluidParticle.deserialize(buf, varPos1); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 69; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 61); + int pos0 = offset + 69 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 65); + int pos1 = offset + 69 + fieldOffset1; + pos1 += FluidParticle.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.fogColor != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.fogDistance != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.colorFilter != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.particle != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.movementSettings != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.shader.getValue()); + buf.writeByte(this.fogMode.getValue()); + if (this.fogColor != null) { + this.fogColor.serialize(buf); + } else { + buf.writeZero(3); + } + + if (this.fogDistance != null) { + this.fogDistance.serialize(buf); + } else { + buf.writeZero(8); + } + + buf.writeFloatLE(this.fogDepthStart); + buf.writeFloatLE(this.fogDepthFalloff); + if (this.colorFilter != null) { + this.colorFilter.serialize(buf); + } else { + buf.writeZero(3); + } + + buf.writeFloatLE(this.colorSaturation); + buf.writeFloatLE(this.distortionAmplitude); + buf.writeFloatLE(this.distortionFrequency); + if (this.movementSettings != null) { + this.movementSettings.serialize(buf); + } else { + buf.writeZero(24); + } + + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int particleOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.particle != null) { + buf.setIntLE(particleOffsetSlot, buf.writerIndex() - varBlockStart); + this.particle.serialize(buf); + } else { + buf.setIntLE(particleOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 69; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.particle != null) { + size += this.particle.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 69) { + return ValidationResult.error("Buffer too small: expected at least 69 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 61); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 69 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 16) != 0) { + int particleOffset = buffer.getIntLE(offset + 65); + if (particleOffset < 0) { + return ValidationResult.error("Invalid offset for Particle"); + } + + int posx = offset + 69 + particleOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Particle"); + } + + ValidationResult particleResult = FluidParticle.validateStructure(buffer, posx); + if (!particleResult.isValid()) { + return ValidationResult.error("Invalid Particle: " + particleResult.error()); + } + + posx += FluidParticle.computeBytesConsumed(buffer, posx); + } + + return ValidationResult.OK; + } + } + + public FluidFX clone() { + FluidFX copy = new FluidFX(); + copy.id = this.id; + copy.shader = this.shader; + copy.fogMode = this.fogMode; + copy.fogColor = this.fogColor != null ? this.fogColor.clone() : null; + copy.fogDistance = this.fogDistance != null ? this.fogDistance.clone() : null; + copy.fogDepthStart = this.fogDepthStart; + copy.fogDepthFalloff = this.fogDepthFalloff; + copy.colorFilter = this.colorFilter != null ? this.colorFilter.clone() : null; + copy.colorSaturation = this.colorSaturation; + copy.distortionAmplitude = this.distortionAmplitude; + copy.distortionFrequency = this.distortionFrequency; + copy.particle = this.particle != null ? this.particle.clone() : null; + copy.movementSettings = this.movementSettings != null ? this.movementSettings.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof FluidFX other) + ? false + : Objects.equals(this.id, other.id) + && Objects.equals(this.shader, other.shader) + && Objects.equals(this.fogMode, other.fogMode) + && Objects.equals(this.fogColor, other.fogColor) + && Objects.equals(this.fogDistance, other.fogDistance) + && this.fogDepthStart == other.fogDepthStart + && this.fogDepthFalloff == other.fogDepthFalloff + && Objects.equals(this.colorFilter, other.colorFilter) + && this.colorSaturation == other.colorSaturation + && this.distortionAmplitude == other.distortionAmplitude + && this.distortionFrequency == other.distortionFrequency + && Objects.equals(this.particle, other.particle) + && Objects.equals(this.movementSettings, other.movementSettings); + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.id, + this.shader, + this.fogMode, + this.fogColor, + this.fogDistance, + this.fogDepthStart, + this.fogDepthFalloff, + this.colorFilter, + this.colorSaturation, + this.distortionAmplitude, + this.distortionFrequency, + this.particle, + this.movementSettings + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/FluidFXMovementSettings.java b/src/com/hypixel/hytale/protocol/FluidFXMovementSettings.java new file mode 100644 index 0000000..1822076 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/FluidFXMovementSettings.java @@ -0,0 +1,110 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class FluidFXMovementSettings { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 24; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 24; + public static final int MAX_SIZE = 24; + public float swimUpSpeed; + public float swimDownSpeed; + public float sinkSpeed; + public float horizontalSpeedMultiplier; + public float fieldOfViewMultiplier; + public float entryVelocityMultiplier; + + public FluidFXMovementSettings() { + } + + public FluidFXMovementSettings( + float swimUpSpeed, float swimDownSpeed, float sinkSpeed, float horizontalSpeedMultiplier, float fieldOfViewMultiplier, float entryVelocityMultiplier + ) { + this.swimUpSpeed = swimUpSpeed; + this.swimDownSpeed = swimDownSpeed; + this.sinkSpeed = sinkSpeed; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.fieldOfViewMultiplier = fieldOfViewMultiplier; + this.entryVelocityMultiplier = entryVelocityMultiplier; + } + + public FluidFXMovementSettings(@Nonnull FluidFXMovementSettings other) { + this.swimUpSpeed = other.swimUpSpeed; + this.swimDownSpeed = other.swimDownSpeed; + this.sinkSpeed = other.sinkSpeed; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.fieldOfViewMultiplier = other.fieldOfViewMultiplier; + this.entryVelocityMultiplier = other.entryVelocityMultiplier; + } + + @Nonnull + public static FluidFXMovementSettings deserialize(@Nonnull ByteBuf buf, int offset) { + FluidFXMovementSettings obj = new FluidFXMovementSettings(); + obj.swimUpSpeed = buf.getFloatLE(offset + 0); + obj.swimDownSpeed = buf.getFloatLE(offset + 4); + obj.sinkSpeed = buf.getFloatLE(offset + 8); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 12); + obj.fieldOfViewMultiplier = buf.getFloatLE(offset + 16); + obj.entryVelocityMultiplier = buf.getFloatLE(offset + 20); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 24; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.swimUpSpeed); + buf.writeFloatLE(this.swimDownSpeed); + buf.writeFloatLE(this.sinkSpeed); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.fieldOfViewMultiplier); + buf.writeFloatLE(this.entryVelocityMultiplier); + } + + public int computeSize() { + return 24; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 24 ? ValidationResult.error("Buffer too small: expected at least 24 bytes") : ValidationResult.OK; + } + + public FluidFXMovementSettings clone() { + FluidFXMovementSettings copy = new FluidFXMovementSettings(); + copy.swimUpSpeed = this.swimUpSpeed; + copy.swimDownSpeed = this.swimDownSpeed; + copy.sinkSpeed = this.sinkSpeed; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.fieldOfViewMultiplier = this.fieldOfViewMultiplier; + copy.entryVelocityMultiplier = this.entryVelocityMultiplier; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof FluidFXMovementSettings other) + ? false + : this.swimUpSpeed == other.swimUpSpeed + && this.swimDownSpeed == other.swimDownSpeed + && this.sinkSpeed == other.sinkSpeed + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.fieldOfViewMultiplier == other.fieldOfViewMultiplier + && this.entryVelocityMultiplier == other.entryVelocityMultiplier; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.swimUpSpeed, this.swimDownSpeed, this.sinkSpeed, this.horizontalSpeedMultiplier, this.fieldOfViewMultiplier, this.entryVelocityMultiplier + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/FluidFog.java b/src/com/hypixel/hytale/protocol/FluidFog.java new file mode 100644 index 0000000..0aea466 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/FluidFog.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum FluidFog { + Color(0), + ColorLight(1), + EnvironmentTint(2); + + public static final FluidFog[] VALUES = values(); + private final int value; + + private FluidFog(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static FluidFog fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("FluidFog", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/FluidParticle.java b/src/com/hypixel/hytale/protocol/FluidParticle.java new file mode 100644 index 0000000..ddaf033 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/FluidParticle.java @@ -0,0 +1,160 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FluidParticle { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 16384013; + @Nullable + public String systemId; + @Nullable + public Color color; + public float scale; + + public FluidParticle() { + } + + public FluidParticle(@Nullable String systemId, @Nullable Color color, float scale) { + this.systemId = systemId; + this.color = color; + this.scale = scale; + } + + public FluidParticle(@Nonnull FluidParticle other) { + this.systemId = other.systemId; + this.color = other.color; + this.scale = other.scale; + } + + @Nonnull + public static FluidParticle deserialize(@Nonnull ByteBuf buf, int offset) { + FluidParticle obj = new FluidParticle(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 2) != 0) { + obj.color = Color.deserialize(buf, offset + 1); + } + + obj.scale = buf.getFloatLE(offset + 4); + int pos = offset + 8; + if ((nullBits & 1) != 0) { + int systemIdLen = VarInt.peek(buf, pos); + if (systemIdLen < 0) { + throw ProtocolException.negativeLength("SystemId", systemIdLen); + } + + if (systemIdLen > 4096000) { + throw ProtocolException.stringTooLong("SystemId", systemIdLen, 4096000); + } + + int systemIdVarLen = VarInt.length(buf, pos); + obj.systemId = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += systemIdVarLen + systemIdLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 8; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.systemId != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.color != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + if (this.color != null) { + this.color.serialize(buf); + } else { + buf.writeZero(3); + } + + buf.writeFloatLE(this.scale); + if (this.systemId != null) { + PacketIO.writeVarString(buf, this.systemId, 4096000); + } + } + + public int computeSize() { + int size = 8; + if (this.systemId != null) { + size += PacketIO.stringSize(this.systemId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 8) { + return ValidationResult.error("Buffer too small: expected at least 8 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 8; + if ((nullBits & 1) != 0) { + int systemIdLen = VarInt.peek(buffer, pos); + if (systemIdLen < 0) { + return ValidationResult.error("Invalid string length for SystemId"); + } + + if (systemIdLen > 4096000) { + return ValidationResult.error("SystemId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += systemIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading SystemId"); + } + } + + return ValidationResult.OK; + } + } + + public FluidParticle clone() { + FluidParticle copy = new FluidParticle(); + copy.systemId = this.systemId; + copy.color = this.color != null ? this.color.clone() : null; + copy.scale = this.scale; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof FluidParticle other) + ? false + : Objects.equals(this.systemId, other.systemId) && Objects.equals(this.color, other.color) && this.scale == other.scale; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.systemId, this.color, this.scale); + } +} diff --git a/src/com/hypixel/hytale/protocol/FogOptions.java b/src/com/hypixel/hytale/protocol/FogOptions.java new file mode 100644 index 0000000..3e7cff6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/FogOptions.java @@ -0,0 +1,120 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class FogOptions { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 18; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 18; + public static final int MAX_SIZE = 18; + public boolean ignoreFogLimits; + public float effectiveViewDistanceMultiplier; + public float fogFarViewDistance; + public float fogHeightCameraOffset; + public boolean fogHeightCameraOverriden; + public float fogHeightCameraFixed; + + public FogOptions() { + } + + public FogOptions( + boolean ignoreFogLimits, + float effectiveViewDistanceMultiplier, + float fogFarViewDistance, + float fogHeightCameraOffset, + boolean fogHeightCameraOverriden, + float fogHeightCameraFixed + ) { + this.ignoreFogLimits = ignoreFogLimits; + this.effectiveViewDistanceMultiplier = effectiveViewDistanceMultiplier; + this.fogFarViewDistance = fogFarViewDistance; + this.fogHeightCameraOffset = fogHeightCameraOffset; + this.fogHeightCameraOverriden = fogHeightCameraOverriden; + this.fogHeightCameraFixed = fogHeightCameraFixed; + } + + public FogOptions(@Nonnull FogOptions other) { + this.ignoreFogLimits = other.ignoreFogLimits; + this.effectiveViewDistanceMultiplier = other.effectiveViewDistanceMultiplier; + this.fogFarViewDistance = other.fogFarViewDistance; + this.fogHeightCameraOffset = other.fogHeightCameraOffset; + this.fogHeightCameraOverriden = other.fogHeightCameraOverriden; + this.fogHeightCameraFixed = other.fogHeightCameraFixed; + } + + @Nonnull + public static FogOptions deserialize(@Nonnull ByteBuf buf, int offset) { + FogOptions obj = new FogOptions(); + obj.ignoreFogLimits = buf.getByte(offset + 0) != 0; + obj.effectiveViewDistanceMultiplier = buf.getFloatLE(offset + 1); + obj.fogFarViewDistance = buf.getFloatLE(offset + 5); + obj.fogHeightCameraOffset = buf.getFloatLE(offset + 9); + obj.fogHeightCameraOverriden = buf.getByte(offset + 13) != 0; + obj.fogHeightCameraFixed = buf.getFloatLE(offset + 14); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 18; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.ignoreFogLimits ? 1 : 0); + buf.writeFloatLE(this.effectiveViewDistanceMultiplier); + buf.writeFloatLE(this.fogFarViewDistance); + buf.writeFloatLE(this.fogHeightCameraOffset); + buf.writeByte(this.fogHeightCameraOverriden ? 1 : 0); + buf.writeFloatLE(this.fogHeightCameraFixed); + } + + public int computeSize() { + return 18; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 18 ? ValidationResult.error("Buffer too small: expected at least 18 bytes") : ValidationResult.OK; + } + + public FogOptions clone() { + FogOptions copy = new FogOptions(); + copy.ignoreFogLimits = this.ignoreFogLimits; + copy.effectiveViewDistanceMultiplier = this.effectiveViewDistanceMultiplier; + copy.fogFarViewDistance = this.fogFarViewDistance; + copy.fogHeightCameraOffset = this.fogHeightCameraOffset; + copy.fogHeightCameraOverriden = this.fogHeightCameraOverriden; + copy.fogHeightCameraFixed = this.fogHeightCameraFixed; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof FogOptions other) + ? false + : this.ignoreFogLimits == other.ignoreFogLimits + && this.effectiveViewDistanceMultiplier == other.effectiveViewDistanceMultiplier + && this.fogFarViewDistance == other.fogFarViewDistance + && this.fogHeightCameraOffset == other.fogHeightCameraOffset + && this.fogHeightCameraOverriden == other.fogHeightCameraOverriden + && this.fogHeightCameraFixed == other.fogHeightCameraFixed; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.ignoreFogLimits, + this.effectiveViewDistanceMultiplier, + this.fogFarViewDistance, + this.fogHeightCameraOffset, + this.fogHeightCameraOverriden, + this.fogHeightCameraFixed + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/ForkedChainId.java b/src/com/hypixel/hytale/protocol/ForkedChainId.java new file mode 100644 index 0000000..6913f2a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ForkedChainId.java @@ -0,0 +1,125 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ForkedChainId { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 1033; + public int entryIndex; + public int subIndex; + @Nullable + public ForkedChainId forkedId; + + public ForkedChainId() { + } + + public ForkedChainId(int entryIndex, int subIndex, @Nullable ForkedChainId forkedId) { + this.entryIndex = entryIndex; + this.subIndex = subIndex; + this.forkedId = forkedId; + } + + public ForkedChainId(@Nonnull ForkedChainId other) { + this.entryIndex = other.entryIndex; + this.subIndex = other.subIndex; + this.forkedId = other.forkedId; + } + + @Nonnull + public static ForkedChainId deserialize(@Nonnull ByteBuf buf, int offset) { + ForkedChainId obj = new ForkedChainId(); + byte nullBits = buf.getByte(offset); + obj.entryIndex = buf.getIntLE(offset + 1); + obj.subIndex = buf.getIntLE(offset + 5); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + obj.forkedId = deserialize(buf, pos); + pos += computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + pos += computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.forkedId != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.entryIndex); + buf.writeIntLE(this.subIndex); + if (this.forkedId != null) { + this.forkedId.serialize(buf); + } + } + + public int computeSize() { + int size = 9; + if (this.forkedId != null) { + size += this.forkedId.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + ValidationResult forkedIdResult = validateStructure(buffer, pos); + if (!forkedIdResult.isValid()) { + return ValidationResult.error("Invalid ForkedId: " + forkedIdResult.error()); + } + + pos += computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public ForkedChainId clone() { + ForkedChainId copy = new ForkedChainId(); + copy.entryIndex = this.entryIndex; + copy.subIndex = this.subIndex; + copy.forkedId = this.forkedId != null ? this.forkedId.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ForkedChainId other) + ? false + : this.entryIndex == other.entryIndex && this.subIndex == other.subIndex && Objects.equals(this.forkedId, other.forkedId); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.entryIndex, this.subIndex, this.forkedId); + } +} diff --git a/src/com/hypixel/hytale/protocol/FormattedMessage.java b/src/com/hypixel/hytale/protocol/FormattedMessage.java new file mode 100644 index 0000000..4b96fc3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/FormattedMessage.java @@ -0,0 +1,828 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FormattedMessage { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 7; + public static final int VARIABLE_BLOCK_START = 34; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String rawText; + @Nullable + public String messageId; + @Nullable + public FormattedMessage[] children; + @Nullable + public Map params; + @Nullable + public Map messageParams; + @Nullable + public String color; + @Nonnull + public MaybeBool bold = MaybeBool.Null; + @Nonnull + public MaybeBool italic = MaybeBool.Null; + @Nonnull + public MaybeBool monospace = MaybeBool.Null; + @Nonnull + public MaybeBool underlined = MaybeBool.Null; + @Nullable + public String link; + public boolean markupEnabled; + + public FormattedMessage() { + } + + public FormattedMessage( + @Nullable String rawText, + @Nullable String messageId, + @Nullable FormattedMessage[] children, + @Nullable Map params, + @Nullable Map messageParams, + @Nullable String color, + @Nonnull MaybeBool bold, + @Nonnull MaybeBool italic, + @Nonnull MaybeBool monospace, + @Nonnull MaybeBool underlined, + @Nullable String link, + boolean markupEnabled + ) { + this.rawText = rawText; + this.messageId = messageId; + this.children = children; + this.params = params; + this.messageParams = messageParams; + this.color = color; + this.bold = bold; + this.italic = italic; + this.monospace = monospace; + this.underlined = underlined; + this.link = link; + this.markupEnabled = markupEnabled; + } + + public FormattedMessage(@Nonnull FormattedMessage other) { + this.rawText = other.rawText; + this.messageId = other.messageId; + this.children = other.children; + this.params = other.params; + this.messageParams = other.messageParams; + this.color = other.color; + this.bold = other.bold; + this.italic = other.italic; + this.monospace = other.monospace; + this.underlined = other.underlined; + this.link = other.link; + this.markupEnabled = other.markupEnabled; + } + + @Nonnull + public static FormattedMessage deserialize(@Nonnull ByteBuf buf, int offset) { + FormattedMessage obj = new FormattedMessage(); + byte nullBits = buf.getByte(offset); + obj.bold = MaybeBool.fromValue(buf.getByte(offset + 1)); + obj.italic = MaybeBool.fromValue(buf.getByte(offset + 2)); + obj.monospace = MaybeBool.fromValue(buf.getByte(offset + 3)); + obj.underlined = MaybeBool.fromValue(buf.getByte(offset + 4)); + obj.markupEnabled = buf.getByte(offset + 5) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 34 + buf.getIntLE(offset + 6); + int rawTextLen = VarInt.peek(buf, varPos0); + if (rawTextLen < 0) { + throw ProtocolException.negativeLength("RawText", rawTextLen); + } + + if (rawTextLen > 4096000) { + throw ProtocolException.stringTooLong("RawText", rawTextLen, 4096000); + } + + obj.rawText = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 34 + buf.getIntLE(offset + 10); + int messageIdLen = VarInt.peek(buf, varPos1); + if (messageIdLen < 0) { + throw ProtocolException.negativeLength("MessageId", messageIdLen); + } + + if (messageIdLen > 4096000) { + throw ProtocolException.stringTooLong("MessageId", messageIdLen, 4096000); + } + + obj.messageId = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 34 + buf.getIntLE(offset + 14); + int childrenCount = VarInt.peek(buf, varPos2); + if (childrenCount < 0) { + throw ProtocolException.negativeLength("Children", childrenCount); + } + + if (childrenCount > 4096000) { + throw ProtocolException.arrayTooLong("Children", childrenCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + childrenCount * 6L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Children", varPos2 + varIntLen + childrenCount * 6, buf.readableBytes()); + } + + obj.children = new FormattedMessage[childrenCount]; + int elemPos = varPos2 + varIntLen; + + for (int i = 0; i < childrenCount; i++) { + obj.children[i] = deserialize(buf, elemPos); + elemPos += computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 34 + buf.getIntLE(offset + 18); + int paramsCount = VarInt.peek(buf, varPos3); + if (paramsCount < 0) { + throw ProtocolException.negativeLength("Params", paramsCount); + } + + if (paramsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Params", paramsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + obj.params = new HashMap<>(paramsCount); + int dictPos = varPos3 + varIntLen; + + for (int i = 0; i < paramsCount; i++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + ParamValue val = ParamValue.deserialize(buf, dictPos); + dictPos += ParamValue.computeBytesConsumed(buf, dictPos); + if (obj.params.put(key, val) != null) { + throw ProtocolException.duplicateKey("params", key); + } + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 34 + buf.getIntLE(offset + 22); + int messageParamsCount = VarInt.peek(buf, varPos4); + if (messageParamsCount < 0) { + throw ProtocolException.negativeLength("MessageParams", messageParamsCount); + } + + if (messageParamsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("MessageParams", messageParamsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos4); + obj.messageParams = new HashMap<>(messageParamsCount); + int dictPos = varPos4 + varIntLen; + + for (int i = 0; i < messageParamsCount; i++) { + int keyLenx = VarInt.peek(buf, dictPos); + if (keyLenx < 0) { + throw ProtocolException.negativeLength("key", keyLenx); + } + + if (keyLenx > 4096000) { + throw ProtocolException.stringTooLong("key", keyLenx, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLenx; + FormattedMessage val = deserialize(buf, dictPos); + dictPos += computeBytesConsumed(buf, dictPos); + if (obj.messageParams.put(key, val) != null) { + throw ProtocolException.duplicateKey("messageParams", key); + } + } + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 34 + buf.getIntLE(offset + 26); + int colorLen = VarInt.peek(buf, varPos5); + if (colorLen < 0) { + throw ProtocolException.negativeLength("Color", colorLen); + } + + if (colorLen > 4096000) { + throw ProtocolException.stringTooLong("Color", colorLen, 4096000); + } + + obj.color = PacketIO.readVarString(buf, varPos5, PacketIO.UTF8); + } + + if ((nullBits & 64) != 0) { + int varPos6 = offset + 34 + buf.getIntLE(offset + 30); + int linkLen = VarInt.peek(buf, varPos6); + if (linkLen < 0) { + throw ProtocolException.negativeLength("Link", linkLen); + } + + if (linkLen > 4096000) { + throw ProtocolException.stringTooLong("Link", linkLen, 4096000); + } + + obj.link = PacketIO.readVarString(buf, varPos6, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 34; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 6); + int pos0 = offset + 34 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 10); + int pos1 = offset + 34 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 14); + int pos2 = offset + 34 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2); + + for (int i = 0; i < arrLen; i++) { + pos2 += computeBytesConsumed(buf, pos2); + } + + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 18); + int pos3 = offset + 34 + fieldOffset3; + int dictLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + pos3 += ParamValue.computeBytesConsumed(buf, pos3); + } + + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 22); + int pos4 = offset + 34 + fieldOffset4; + int dictLen = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4) + sl; + pos4 += computeBytesConsumed(buf, pos4); + } + + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 26); + int pos5 = offset + 34 + fieldOffset5; + int sl = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + sl; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits & 64) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 30); + int pos6 = offset + 34 + fieldOffset6; + int sl = VarInt.peek(buf, pos6); + pos6 += VarInt.length(buf, pos6) + sl; + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.rawText != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.messageId != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.children != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.params != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.messageParams != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.color != null) { + nullBits = (byte)(nullBits | 32); + } + + if (this.link != null) { + nullBits = (byte)(nullBits | 64); + } + + buf.writeByte(nullBits); + buf.writeByte(this.bold.getValue()); + buf.writeByte(this.italic.getValue()); + buf.writeByte(this.monospace.getValue()); + buf.writeByte(this.underlined.getValue()); + buf.writeByte(this.markupEnabled ? 1 : 0); + int rawTextOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int messageIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int childrenOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int paramsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int messageParamsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int colorOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int linkOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.rawText != null) { + buf.setIntLE(rawTextOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.rawText, 4096000); + } else { + buf.setIntLE(rawTextOffsetSlot, -1); + } + + if (this.messageId != null) { + buf.setIntLE(messageIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.messageId, 4096000); + } else { + buf.setIntLE(messageIdOffsetSlot, -1); + } + + if (this.children != null) { + buf.setIntLE(childrenOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.children.length > 4096000) { + throw ProtocolException.arrayTooLong("Children", this.children.length, 4096000); + } + + VarInt.write(buf, this.children.length); + + for (FormattedMessage item : this.children) { + item.serialize(buf); + } + } else { + buf.setIntLE(childrenOffsetSlot, -1); + } + + if (this.params != null) { + buf.setIntLE(paramsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.params.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Params", this.params.size(), 4096000); + } + + VarInt.write(buf, this.params.size()); + + for (Entry e : this.params.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serializeWithTypeId(buf); + } + } else { + buf.setIntLE(paramsOffsetSlot, -1); + } + + if (this.messageParams != null) { + buf.setIntLE(messageParamsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.messageParams.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("MessageParams", this.messageParams.size(), 4096000); + } + + VarInt.write(buf, this.messageParams.size()); + + for (Entry e : this.messageParams.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(messageParamsOffsetSlot, -1); + } + + if (this.color != null) { + buf.setIntLE(colorOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.color, 4096000); + } else { + buf.setIntLE(colorOffsetSlot, -1); + } + + if (this.link != null) { + buf.setIntLE(linkOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.link, 4096000); + } else { + buf.setIntLE(linkOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 34; + if (this.rawText != null) { + size += PacketIO.stringSize(this.rawText); + } + + if (this.messageId != null) { + size += PacketIO.stringSize(this.messageId); + } + + if (this.children != null) { + int childrenSize = 0; + + for (FormattedMessage elem : this.children) { + childrenSize += elem.computeSize(); + } + + size += VarInt.size(this.children.length) + childrenSize; + } + + if (this.params != null) { + int paramsSize = 0; + + for (Entry kvp : this.params.entrySet()) { + paramsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSizeWithTypeId(); + } + + size += VarInt.size(this.params.size()) + paramsSize; + } + + if (this.messageParams != null) { + int messageParamsSize = 0; + + for (Entry kvp : this.messageParams.entrySet()) { + messageParamsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.messageParams.size()) + messageParamsSize; + } + + if (this.color != null) { + size += PacketIO.stringSize(this.color); + } + + if (this.link != null) { + size += PacketIO.stringSize(this.link); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 34) { + return ValidationResult.error("Buffer too small: expected at least 34 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int rawTextOffset = buffer.getIntLE(offset + 6); + if (rawTextOffset < 0) { + return ValidationResult.error("Invalid offset for RawText"); + } + + int pos = offset + 34 + rawTextOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for RawText"); + } + + int rawTextLen = VarInt.peek(buffer, pos); + if (rawTextLen < 0) { + return ValidationResult.error("Invalid string length for RawText"); + } + + if (rawTextLen > 4096000) { + return ValidationResult.error("RawText exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += rawTextLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading RawText"); + } + } + + if ((nullBits & 2) != 0) { + int messageIdOffset = buffer.getIntLE(offset + 10); + if (messageIdOffset < 0) { + return ValidationResult.error("Invalid offset for MessageId"); + } + + int posx = offset + 34 + messageIdOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MessageId"); + } + + int messageIdLen = VarInt.peek(buffer, posx); + if (messageIdLen < 0) { + return ValidationResult.error("Invalid string length for MessageId"); + } + + if (messageIdLen > 4096000) { + return ValidationResult.error("MessageId exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += messageIdLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading MessageId"); + } + } + + if ((nullBits & 4) != 0) { + int childrenOffset = buffer.getIntLE(offset + 14); + if (childrenOffset < 0) { + return ValidationResult.error("Invalid offset for Children"); + } + + int posxx = offset + 34 + childrenOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Children"); + } + + int childrenCount = VarInt.peek(buffer, posxx); + if (childrenCount < 0) { + return ValidationResult.error("Invalid array count for Children"); + } + + if (childrenCount > 4096000) { + return ValidationResult.error("Children exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + + for (int i = 0; i < childrenCount; i++) { + ValidationResult structResult = validateStructure(buffer, posxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid FormattedMessage in Children[" + i + "]: " + structResult.error()); + } + + posxx += computeBytesConsumed(buffer, posxx); + } + } + + if ((nullBits & 8) != 0) { + int paramsOffset = buffer.getIntLE(offset + 18); + if (paramsOffset < 0) { + return ValidationResult.error("Invalid offset for Params"); + } + + int posxxx = offset + 34 + paramsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Params"); + } + + int paramsCount = VarInt.peek(buffer, posxxx); + if (paramsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Params"); + } + + if (paramsCount > 4096000) { + return ValidationResult.error("Params exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + + for (int i = 0; i < paramsCount; i++) { + int keyLen = VarInt.peek(buffer, posxxx); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += keyLen; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxx += ParamValue.computeBytesConsumed(buffer, posxxx); + } + } + + if ((nullBits & 16) != 0) { + int messageParamsOffset = buffer.getIntLE(offset + 22); + if (messageParamsOffset < 0) { + return ValidationResult.error("Invalid offset for MessageParams"); + } + + int posxxxx = offset + 34 + messageParamsOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MessageParams"); + } + + int messageParamsCount = VarInt.peek(buffer, posxxxx); + if (messageParamsCount < 0) { + return ValidationResult.error("Invalid dictionary count for MessageParams"); + } + + if (messageParamsCount > 4096000) { + return ValidationResult.error("MessageParams exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + + for (int i = 0; i < messageParamsCount; i++) { + int keyLenx = VarInt.peek(buffer, posxxxx); + if (keyLenx < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLenx > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + posxxxx += keyLenx; + if (posxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxx += computeBytesConsumed(buffer, posxxxx); + } + } + + if ((nullBits & 32) != 0) { + int colorOffset = buffer.getIntLE(offset + 26); + if (colorOffset < 0) { + return ValidationResult.error("Invalid offset for Color"); + } + + int posxxxxx = offset + 34 + colorOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Color"); + } + + int colorLen = VarInt.peek(buffer, posxxxxx); + if (colorLen < 0) { + return ValidationResult.error("Invalid string length for Color"); + } + + if (colorLen > 4096000) { + return ValidationResult.error("Color exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += colorLen; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Color"); + } + } + + if ((nullBits & 64) != 0) { + int linkOffset = buffer.getIntLE(offset + 30); + if (linkOffset < 0) { + return ValidationResult.error("Invalid offset for Link"); + } + + int posxxxxxx = offset + 34 + linkOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Link"); + } + + int linkLen = VarInt.peek(buffer, posxxxxxx); + if (linkLen < 0) { + return ValidationResult.error("Invalid string length for Link"); + } + + if (linkLen > 4096000) { + return ValidationResult.error("Link exceeds max length 4096000"); + } + + posxxxxxx += VarInt.length(buffer, posxxxxxx); + posxxxxxx += linkLen; + if (posxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Link"); + } + } + + return ValidationResult.OK; + } + } + + public FormattedMessage clone() { + FormattedMessage copy = new FormattedMessage(); + copy.rawText = this.rawText; + copy.messageId = this.messageId; + copy.children = this.children != null ? Arrays.stream(this.children).map(ex -> ex.clone()).toArray(FormattedMessage[]::new) : null; + copy.params = this.params != null ? new HashMap<>(this.params) : null; + if (this.messageParams != null) { + Map m = new HashMap<>(); + + for (Entry e : this.messageParams.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.messageParams = m; + } + + copy.color = this.color; + copy.bold = this.bold; + copy.italic = this.italic; + copy.monospace = this.monospace; + copy.underlined = this.underlined; + copy.link = this.link; + copy.markupEnabled = this.markupEnabled; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof FormattedMessage other) + ? false + : Objects.equals(this.rawText, other.rawText) + && Objects.equals(this.messageId, other.messageId) + && Arrays.equals((Object[])this.children, (Object[])other.children) + && Objects.equals(this.params, other.params) + && Objects.equals(this.messageParams, other.messageParams) + && Objects.equals(this.color, other.color) + && Objects.equals(this.bold, other.bold) + && Objects.equals(this.italic, other.italic) + && Objects.equals(this.monospace, other.monospace) + && Objects.equals(this.underlined, other.underlined) + && Objects.equals(this.link, other.link) + && this.markupEnabled == other.markupEnabled; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.rawText); + result = 31 * result + Objects.hashCode(this.messageId); + result = 31 * result + Arrays.hashCode((Object[])this.children); + result = 31 * result + Objects.hashCode(this.params); + result = 31 * result + Objects.hashCode(this.messageParams); + result = 31 * result + Objects.hashCode(this.color); + result = 31 * result + Objects.hashCode(this.bold); + result = 31 * result + Objects.hashCode(this.italic); + result = 31 * result + Objects.hashCode(this.monospace); + result = 31 * result + Objects.hashCode(this.underlined); + result = 31 * result + Objects.hashCode(this.link); + return 31 * result + Boolean.hashCode(this.markupEnabled); + } +} diff --git a/src/com/hypixel/hytale/protocol/GameMode.java b/src/com/hypixel/hytale/protocol/GameMode.java new file mode 100644 index 0000000..6cb1550 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/GameMode.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum GameMode { + Adventure(0), + Creative(1); + + public static final GameMode[] VALUES = values(); + private final int value; + + private GameMode(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static GameMode fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("GameMode", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/HalfFloatPosition.java b/src/com/hypixel/hytale/protocol/HalfFloatPosition.java new file mode 100644 index 0000000..0c79b48 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/HalfFloatPosition.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class HalfFloatPosition { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 6; + public short x; + public short y; + public short z; + + public HalfFloatPosition() { + } + + public HalfFloatPosition(short x, short y, short z) { + this.x = x; + this.y = y; + this.z = z; + } + + public HalfFloatPosition(@Nonnull HalfFloatPosition other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + } + + @Nonnull + public static HalfFloatPosition deserialize(@Nonnull ByteBuf buf, int offset) { + HalfFloatPosition obj = new HalfFloatPosition(); + obj.x = buf.getShortLE(offset + 0); + obj.y = buf.getShortLE(offset + 2); + obj.z = buf.getShortLE(offset + 4); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 6; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeShortLE(this.x); + buf.writeShortLE(this.y); + buf.writeShortLE(this.z); + } + + public int computeSize() { + return 6; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 6 ? ValidationResult.error("Buffer too small: expected at least 6 bytes") : ValidationResult.OK; + } + + public HalfFloatPosition clone() { + HalfFloatPosition copy = new HalfFloatPosition(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof HalfFloatPosition other) ? false : this.x == other.x && this.y == other.y && this.z == other.z; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y, this.z); + } +} diff --git a/src/com/hypixel/hytale/protocol/Harvesting.java b/src/com/hypixel/hytale/protocol/Harvesting.java new file mode 100644 index 0000000..64afc06 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Harvesting.java @@ -0,0 +1,225 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Harvesting { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 32768019; + @Nullable + public String itemId; + @Nullable + public String dropListId; + + public Harvesting() { + } + + public Harvesting(@Nullable String itemId, @Nullable String dropListId) { + this.itemId = itemId; + this.dropListId = dropListId; + } + + public Harvesting(@Nonnull Harvesting other) { + this.itemId = other.itemId; + this.dropListId = other.dropListId; + } + + @Nonnull + public static Harvesting deserialize(@Nonnull ByteBuf buf, int offset) { + Harvesting obj = new Harvesting(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int itemIdLen = VarInt.peek(buf, varPos0); + if (itemIdLen < 0) { + throw ProtocolException.negativeLength("ItemId", itemIdLen); + } + + if (itemIdLen > 4096000) { + throw ProtocolException.stringTooLong("ItemId", itemIdLen, 4096000); + } + + obj.itemId = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int dropListIdLen = VarInt.peek(buf, varPos1); + if (dropListIdLen < 0) { + throw ProtocolException.negativeLength("DropListId", dropListIdLen); + } + + if (dropListIdLen > 4096000) { + throw ProtocolException.stringTooLong("DropListId", dropListIdLen, 4096000); + } + + obj.dropListId = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.itemId != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.dropListId != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int itemIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int dropListIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.itemId != null) { + buf.setIntLE(itemIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.itemId, 4096000); + } else { + buf.setIntLE(itemIdOffsetSlot, -1); + } + + if (this.dropListId != null) { + buf.setIntLE(dropListIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.dropListId, 4096000); + } else { + buf.setIntLE(dropListIdOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 9; + if (this.itemId != null) { + size += PacketIO.stringSize(this.itemId); + } + + if (this.dropListId != null) { + size += PacketIO.stringSize(this.dropListId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int itemIdOffset = buffer.getIntLE(offset + 1); + if (itemIdOffset < 0) { + return ValidationResult.error("Invalid offset for ItemId"); + } + + int pos = offset + 9 + itemIdOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ItemId"); + } + + int itemIdLen = VarInt.peek(buffer, pos); + if (itemIdLen < 0) { + return ValidationResult.error("Invalid string length for ItemId"); + } + + if (itemIdLen > 4096000) { + return ValidationResult.error("ItemId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += itemIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ItemId"); + } + } + + if ((nullBits & 2) != 0) { + int dropListIdOffset = buffer.getIntLE(offset + 5); + if (dropListIdOffset < 0) { + return ValidationResult.error("Invalid offset for DropListId"); + } + + int posx = offset + 9 + dropListIdOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for DropListId"); + } + + int dropListIdLen = VarInt.peek(buffer, posx); + if (dropListIdLen < 0) { + return ValidationResult.error("Invalid string length for DropListId"); + } + + if (dropListIdLen > 4096000) { + return ValidationResult.error("DropListId exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += dropListIdLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading DropListId"); + } + } + + return ValidationResult.OK; + } + } + + public Harvesting clone() { + Harvesting copy = new Harvesting(); + copy.itemId = this.itemId; + copy.dropListId = this.dropListId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Harvesting other) ? false : Objects.equals(this.itemId, other.itemId) && Objects.equals(this.dropListId, other.dropListId); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.itemId, this.dropListId); + } +} diff --git a/src/com/hypixel/hytale/protocol/HitEntity.java b/src/com/hypixel/hytale/protocol/HitEntity.java new file mode 100644 index 0000000..5edab1f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/HitEntity.java @@ -0,0 +1,161 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HitEntity { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 8192010; + public int next; + @Nullable + public EntityMatcher[] matchers; + + public HitEntity() { + } + + public HitEntity(int next, @Nullable EntityMatcher[] matchers) { + this.next = next; + this.matchers = matchers; + } + + public HitEntity(@Nonnull HitEntity other) { + this.next = other.next; + this.matchers = other.matchers; + } + + @Nonnull + public static HitEntity deserialize(@Nonnull ByteBuf buf, int offset) { + HitEntity obj = new HitEntity(); + byte nullBits = buf.getByte(offset); + obj.next = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int matchersCount = VarInt.peek(buf, pos); + if (matchersCount < 0) { + throw ProtocolException.negativeLength("Matchers", matchersCount); + } + + if (matchersCount > 4096000) { + throw ProtocolException.arrayTooLong("Matchers", matchersCount, 4096000); + } + + int matchersVarLen = VarInt.size(matchersCount); + if (pos + matchersVarLen + matchersCount * 2L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Matchers", pos + matchersVarLen + matchersCount * 2, buf.readableBytes()); + } + + pos += matchersVarLen; + obj.matchers = new EntityMatcher[matchersCount]; + + for (int i = 0; i < matchersCount; i++) { + obj.matchers[i] = EntityMatcher.deserialize(buf, pos); + pos += EntityMatcher.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += EntityMatcher.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.matchers != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.next); + if (this.matchers != null) { + if (this.matchers.length > 4096000) { + throw ProtocolException.arrayTooLong("Matchers", this.matchers.length, 4096000); + } + + VarInt.write(buf, this.matchers.length); + + for (EntityMatcher item : this.matchers) { + item.serialize(buf); + } + } + } + + public int computeSize() { + int size = 5; + if (this.matchers != null) { + size += VarInt.size(this.matchers.length) + this.matchers.length * 2; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int matchersCount = VarInt.peek(buffer, pos); + if (matchersCount < 0) { + return ValidationResult.error("Invalid array count for Matchers"); + } + + if (matchersCount > 4096000) { + return ValidationResult.error("Matchers exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += matchersCount * 2; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Matchers"); + } + } + + return ValidationResult.OK; + } + } + + public HitEntity clone() { + HitEntity copy = new HitEntity(); + copy.next = this.next; + copy.matchers = this.matchers != null ? Arrays.stream(this.matchers).map(e -> e.clone()).toArray(EntityMatcher[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof HitEntity other) ? false : this.next == other.next && Arrays.equals((Object[])this.matchers, (Object[])other.matchers); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.next); + return 31 * result + Arrays.hashCode((Object[])this.matchers); + } +} diff --git a/src/com/hypixel/hytale/protocol/Hitbox.java b/src/com/hypixel/hytale/protocol/Hitbox.java new file mode 100644 index 0000000..5b3310e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Hitbox.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class Hitbox { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 24; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 24; + public static final int MAX_SIZE = 24; + public float minX; + public float minY; + public float minZ; + public float maxX; + public float maxY; + public float maxZ; + + public Hitbox() { + } + + public Hitbox(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) { + this.minX = minX; + this.minY = minY; + this.minZ = minZ; + this.maxX = maxX; + this.maxY = maxY; + this.maxZ = maxZ; + } + + public Hitbox(@Nonnull Hitbox other) { + this.minX = other.minX; + this.minY = other.minY; + this.minZ = other.minZ; + this.maxX = other.maxX; + this.maxY = other.maxY; + this.maxZ = other.maxZ; + } + + @Nonnull + public static Hitbox deserialize(@Nonnull ByteBuf buf, int offset) { + Hitbox obj = new Hitbox(); + obj.minX = buf.getFloatLE(offset + 0); + obj.minY = buf.getFloatLE(offset + 4); + obj.minZ = buf.getFloatLE(offset + 8); + obj.maxX = buf.getFloatLE(offset + 12); + obj.maxY = buf.getFloatLE(offset + 16); + obj.maxZ = buf.getFloatLE(offset + 20); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 24; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.minX); + buf.writeFloatLE(this.minY); + buf.writeFloatLE(this.minZ); + buf.writeFloatLE(this.maxX); + buf.writeFloatLE(this.maxY); + buf.writeFloatLE(this.maxZ); + } + + public int computeSize() { + return 24; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 24 ? ValidationResult.error("Buffer too small: expected at least 24 bytes") : ValidationResult.OK; + } + + public Hitbox clone() { + Hitbox copy = new Hitbox(); + copy.minX = this.minX; + copy.minY = this.minY; + copy.minZ = this.minZ; + copy.maxX = this.maxX; + copy.maxY = this.maxY; + copy.maxZ = this.maxZ; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Hitbox other) + ? false + : this.minX == other.minX + && this.minY == other.minY + && this.minZ == other.minZ + && this.maxX == other.maxX + && this.maxY == other.maxY + && this.maxZ == other.maxZ; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.minX, this.minY, this.minZ, this.maxX, this.maxY, this.maxZ); + } +} diff --git a/src/com/hypixel/hytale/protocol/HitboxCollisionConfig.java b/src/com/hypixel/hytale/protocol/HitboxCollisionConfig.java new file mode 100644 index 0000000..a11c00b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/HitboxCollisionConfig.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class HitboxCollisionConfig { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 5; + @Nonnull + public CollisionType collisionType = CollisionType.Hard; + public float softCollisionOffsetRatio; + + public HitboxCollisionConfig() { + } + + public HitboxCollisionConfig(@Nonnull CollisionType collisionType, float softCollisionOffsetRatio) { + this.collisionType = collisionType; + this.softCollisionOffsetRatio = softCollisionOffsetRatio; + } + + public HitboxCollisionConfig(@Nonnull HitboxCollisionConfig other) { + this.collisionType = other.collisionType; + this.softCollisionOffsetRatio = other.softCollisionOffsetRatio; + } + + @Nonnull + public static HitboxCollisionConfig deserialize(@Nonnull ByteBuf buf, int offset) { + HitboxCollisionConfig obj = new HitboxCollisionConfig(); + obj.collisionType = CollisionType.fromValue(buf.getByte(offset + 0)); + obj.softCollisionOffsetRatio = buf.getFloatLE(offset + 1); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 5; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.collisionType.getValue()); + buf.writeFloatLE(this.softCollisionOffsetRatio); + } + + public int computeSize() { + return 5; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 5 ? ValidationResult.error("Buffer too small: expected at least 5 bytes") : ValidationResult.OK; + } + + public HitboxCollisionConfig clone() { + HitboxCollisionConfig copy = new HitboxCollisionConfig(); + copy.collisionType = this.collisionType; + copy.softCollisionOffsetRatio = this.softCollisionOffsetRatio; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof HitboxCollisionConfig other) + ? false + : Objects.equals(this.collisionType, other.collisionType) && this.softCollisionOffsetRatio == other.softCollisionOffsetRatio; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.collisionType, this.softCollisionOffsetRatio); + } +} diff --git a/src/com/hypixel/hytale/protocol/HorizontalSelector.java b/src/com/hypixel/hytale/protocol/HorizontalSelector.java new file mode 100644 index 0000000..c2a9c8b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/HorizontalSelector.java @@ -0,0 +1,161 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class HorizontalSelector extends Selector { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 34; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 34; + public static final int MAX_SIZE = 34; + public float extendTop; + public float extendBottom; + public float yawLength; + public float yawStartOffset; + public float pitchOffset; + public float rollOffset; + public float startDistance; + public float endDistance; + @Nonnull + public HorizontalSelectorDirection direction = HorizontalSelectorDirection.ToLeft; + public boolean testLineOfSight; + + public HorizontalSelector() { + } + + public HorizontalSelector( + float extendTop, + float extendBottom, + float yawLength, + float yawStartOffset, + float pitchOffset, + float rollOffset, + float startDistance, + float endDistance, + @Nonnull HorizontalSelectorDirection direction, + boolean testLineOfSight + ) { + this.extendTop = extendTop; + this.extendBottom = extendBottom; + this.yawLength = yawLength; + this.yawStartOffset = yawStartOffset; + this.pitchOffset = pitchOffset; + this.rollOffset = rollOffset; + this.startDistance = startDistance; + this.endDistance = endDistance; + this.direction = direction; + this.testLineOfSight = testLineOfSight; + } + + public HorizontalSelector(@Nonnull HorizontalSelector other) { + this.extendTop = other.extendTop; + this.extendBottom = other.extendBottom; + this.yawLength = other.yawLength; + this.yawStartOffset = other.yawStartOffset; + this.pitchOffset = other.pitchOffset; + this.rollOffset = other.rollOffset; + this.startDistance = other.startDistance; + this.endDistance = other.endDistance; + this.direction = other.direction; + this.testLineOfSight = other.testLineOfSight; + } + + @Nonnull + public static HorizontalSelector deserialize(@Nonnull ByteBuf buf, int offset) { + HorizontalSelector obj = new HorizontalSelector(); + obj.extendTop = buf.getFloatLE(offset + 0); + obj.extendBottom = buf.getFloatLE(offset + 4); + obj.yawLength = buf.getFloatLE(offset + 8); + obj.yawStartOffset = buf.getFloatLE(offset + 12); + obj.pitchOffset = buf.getFloatLE(offset + 16); + obj.rollOffset = buf.getFloatLE(offset + 20); + obj.startDistance = buf.getFloatLE(offset + 24); + obj.endDistance = buf.getFloatLE(offset + 28); + obj.direction = HorizontalSelectorDirection.fromValue(buf.getByte(offset + 32)); + obj.testLineOfSight = buf.getByte(offset + 33) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 34; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + buf.writeFloatLE(this.extendTop); + buf.writeFloatLE(this.extendBottom); + buf.writeFloatLE(this.yawLength); + buf.writeFloatLE(this.yawStartOffset); + buf.writeFloatLE(this.pitchOffset); + buf.writeFloatLE(this.rollOffset); + buf.writeFloatLE(this.startDistance); + buf.writeFloatLE(this.endDistance); + buf.writeByte(this.direction.getValue()); + buf.writeByte(this.testLineOfSight ? 1 : 0); + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + return 34; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 34 ? ValidationResult.error("Buffer too small: expected at least 34 bytes") : ValidationResult.OK; + } + + public HorizontalSelector clone() { + HorizontalSelector copy = new HorizontalSelector(); + copy.extendTop = this.extendTop; + copy.extendBottom = this.extendBottom; + copy.yawLength = this.yawLength; + copy.yawStartOffset = this.yawStartOffset; + copy.pitchOffset = this.pitchOffset; + copy.rollOffset = this.rollOffset; + copy.startDistance = this.startDistance; + copy.endDistance = this.endDistance; + copy.direction = this.direction; + copy.testLineOfSight = this.testLineOfSight; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof HorizontalSelector other) + ? false + : this.extendTop == other.extendTop + && this.extendBottom == other.extendBottom + && this.yawLength == other.yawLength + && this.yawStartOffset == other.yawStartOffset + && this.pitchOffset == other.pitchOffset + && this.rollOffset == other.rollOffset + && this.startDistance == other.startDistance + && this.endDistance == other.endDistance + && Objects.equals(this.direction, other.direction) + && this.testLineOfSight == other.testLineOfSight; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.extendTop, + this.extendBottom, + this.yawLength, + this.yawStartOffset, + this.pitchOffset, + this.rollOffset, + this.startDistance, + this.endDistance, + this.direction, + this.testLineOfSight + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/HorizontalSelectorDirection.java b/src/com/hypixel/hytale/protocol/HorizontalSelectorDirection.java new file mode 100644 index 0000000..c5543c8 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/HorizontalSelectorDirection.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum HorizontalSelectorDirection { + ToLeft(0), + ToRight(1); + + public static final HorizontalSelectorDirection[] VALUES = values(); + private final int value; + + private HorizontalSelectorDirection(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static HorizontalSelectorDirection fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("HorizontalSelectorDirection", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/HostAddress.java b/src/com/hypixel/hytale/protocol/HostAddress.java new file mode 100644 index 0000000..8bdfde3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/HostAddress.java @@ -0,0 +1,107 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class HostAddress { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 1031; + @Nonnull + public String host = ""; + public short port; + + public HostAddress() { + } + + public HostAddress(@Nonnull String host, short port) { + this.host = host; + this.port = port; + } + + public HostAddress(@Nonnull HostAddress other) { + this.host = other.host; + this.port = other.port; + } + + @Nonnull + public static HostAddress deserialize(@Nonnull ByteBuf buf, int offset) { + HostAddress obj = new HostAddress(); + obj.port = buf.getShortLE(offset + 0); + int pos = offset + 2; + int hostLen = VarInt.peek(buf, pos); + if (hostLen < 0) { + throw ProtocolException.negativeLength("Host", hostLen); + } else if (hostLen > 256) { + throw ProtocolException.stringTooLong("Host", hostLen, 256); + } else { + int hostVarLen = VarInt.length(buf, pos); + obj.host = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += hostVarLen + hostLen; + return obj; + } + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + int pos = offset + 2; + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeShortLE(this.port); + PacketIO.writeVarString(buf, this.host, 256); + } + + public int computeSize() { + int size = 2; + return size + PacketIO.stringSize(this.host); + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + int pos = offset + 2; + int hostLen = VarInt.peek(buffer, pos); + if (hostLen < 0) { + return ValidationResult.error("Invalid string length for Host"); + } else if (hostLen > 256) { + return ValidationResult.error("Host exceeds max length 256"); + } else { + pos += VarInt.length(buffer, pos); + pos += hostLen; + return pos > buffer.writerIndex() ? ValidationResult.error("Buffer overflow reading Host") : ValidationResult.OK; + } + } + } + + public HostAddress clone() { + HostAddress copy = new HostAddress(); + copy.host = this.host; + copy.port = this.port; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof HostAddress other) ? false : Objects.equals(this.host, other.host) && this.port == other.port; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.host, this.port); + } +} diff --git a/src/com/hypixel/hytale/protocol/IncrementCooldownInteraction.java b/src/com/hypixel/hytale/protocol/IncrementCooldownInteraction.java new file mode 100644 index 0000000..1817a60 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/IncrementCooldownInteraction.java @@ -0,0 +1,617 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class IncrementCooldownInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 32; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 56; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String cooldownId; + public float cooldownIncrementTime; + public int cooldownIncrementCharge; + public float cooldownIncrementChargeTime; + public boolean cooldownIncrementInterrupt; + + public IncrementCooldownInteraction() { + } + + public IncrementCooldownInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + @Nullable String cooldownId, + float cooldownIncrementTime, + int cooldownIncrementCharge, + float cooldownIncrementChargeTime, + boolean cooldownIncrementInterrupt + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.cooldownId = cooldownId; + this.cooldownIncrementTime = cooldownIncrementTime; + this.cooldownIncrementCharge = cooldownIncrementCharge; + this.cooldownIncrementChargeTime = cooldownIncrementChargeTime; + this.cooldownIncrementInterrupt = cooldownIncrementInterrupt; + } + + public IncrementCooldownInteraction(@Nonnull IncrementCooldownInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.cooldownId = other.cooldownId; + this.cooldownIncrementTime = other.cooldownIncrementTime; + this.cooldownIncrementCharge = other.cooldownIncrementCharge; + this.cooldownIncrementChargeTime = other.cooldownIncrementChargeTime; + this.cooldownIncrementInterrupt = other.cooldownIncrementInterrupt; + } + + @Nonnull + public static IncrementCooldownInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + IncrementCooldownInteraction obj = new IncrementCooldownInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.cooldownIncrementTime = buf.getFloatLE(offset + 19); + obj.cooldownIncrementCharge = buf.getIntLE(offset + 23); + obj.cooldownIncrementChargeTime = buf.getFloatLE(offset + 27); + obj.cooldownIncrementInterrupt = buf.getByte(offset + 31) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 56 + buf.getIntLE(offset + 32); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 56 + buf.getIntLE(offset + 36); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 56 + buf.getIntLE(offset + 40); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 56 + buf.getIntLE(offset + 44); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 56 + buf.getIntLE(offset + 48); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 56 + buf.getIntLE(offset + 52); + int cooldownIdLen = VarInt.peek(buf, varPos5); + if (cooldownIdLen < 0) { + throw ProtocolException.negativeLength("CooldownId", cooldownIdLen); + } + + if (cooldownIdLen > 4096000) { + throw ProtocolException.stringTooLong("CooldownId", cooldownIdLen, 4096000); + } + + obj.cooldownId = PacketIO.readVarString(buf, varPos5, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 56; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 32); + int pos0 = offset + 56 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 36); + int pos1 = offset + 56 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 40); + int pos2 = offset + 56 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 44); + int pos3 = offset + 56 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 48); + int pos4 = offset + 56 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 52); + int pos5 = offset + 56 + fieldOffset5; + int sl = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + sl; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.cooldownId != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeFloatLE(this.cooldownIncrementTime); + buf.writeIntLE(this.cooldownIncrementCharge); + buf.writeFloatLE(this.cooldownIncrementChargeTime); + buf.writeByte(this.cooldownIncrementInterrupt ? 1 : 0); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cooldownIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.cooldownId != null) { + buf.setIntLE(cooldownIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.cooldownId, 4096000); + } else { + buf.setIntLE(cooldownIdOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 56; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.cooldownId != null) { + size += PacketIO.stringSize(this.cooldownId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 56) { + return ValidationResult.error("Buffer too small: expected at least 56 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 32); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 56 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 36); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 56 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 40); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 56 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 44); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 56 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 48); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 56 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int cooldownIdOffset = buffer.getIntLE(offset + 52); + if (cooldownIdOffset < 0) { + return ValidationResult.error("Invalid offset for CooldownId"); + } + + int posxxxxx = offset + 56 + cooldownIdOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for CooldownId"); + } + + int cooldownIdLen = VarInt.peek(buffer, posxxxxx); + if (cooldownIdLen < 0) { + return ValidationResult.error("Invalid string length for CooldownId"); + } + + if (cooldownIdLen > 4096000) { + return ValidationResult.error("CooldownId exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += cooldownIdLen; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading CooldownId"); + } + } + + return ValidationResult.OK; + } + } + + public IncrementCooldownInteraction clone() { + IncrementCooldownInteraction copy = new IncrementCooldownInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.cooldownId = this.cooldownId; + copy.cooldownIncrementTime = this.cooldownIncrementTime; + copy.cooldownIncrementCharge = this.cooldownIncrementCharge; + copy.cooldownIncrementChargeTime = this.cooldownIncrementChargeTime; + copy.cooldownIncrementInterrupt = this.cooldownIncrementInterrupt; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof IncrementCooldownInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && Objects.equals(this.cooldownId, other.cooldownId) + && this.cooldownIncrementTime == other.cooldownIncrementTime + && this.cooldownIncrementCharge == other.cooldownIncrementCharge + && this.cooldownIncrementChargeTime == other.cooldownIncrementChargeTime + && this.cooldownIncrementInterrupt == other.cooldownIncrementInterrupt; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Objects.hashCode(this.cooldownId); + result = 31 * result + Float.hashCode(this.cooldownIncrementTime); + result = 31 * result + Integer.hashCode(this.cooldownIncrementCharge); + result = 31 * result + Float.hashCode(this.cooldownIncrementChargeTime); + return 31 * result + Boolean.hashCode(this.cooldownIncrementInterrupt); + } +} diff --git a/src/com/hypixel/hytale/protocol/InitialVelocity.java b/src/com/hypixel/hytale/protocol/InitialVelocity.java new file mode 100644 index 0000000..c853368 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/InitialVelocity.java @@ -0,0 +1,125 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InitialVelocity { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 25; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 25; + public static final int MAX_SIZE = 25; + @Nullable + public Rangef yaw; + @Nullable + public Rangef pitch; + @Nullable + public Rangef speed; + + public InitialVelocity() { + } + + public InitialVelocity(@Nullable Rangef yaw, @Nullable Rangef pitch, @Nullable Rangef speed) { + this.yaw = yaw; + this.pitch = pitch; + this.speed = speed; + } + + public InitialVelocity(@Nonnull InitialVelocity other) { + this.yaw = other.yaw; + this.pitch = other.pitch; + this.speed = other.speed; + } + + @Nonnull + public static InitialVelocity deserialize(@Nonnull ByteBuf buf, int offset) { + InitialVelocity obj = new InitialVelocity(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.yaw = Rangef.deserialize(buf, offset + 1); + } + + if ((nullBits & 2) != 0) { + obj.pitch = Rangef.deserialize(buf, offset + 9); + } + + if ((nullBits & 4) != 0) { + obj.speed = Rangef.deserialize(buf, offset + 17); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 25; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.yaw != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.pitch != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.speed != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + if (this.yaw != null) { + this.yaw.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.pitch != null) { + this.pitch.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.speed != null) { + this.speed.serialize(buf); + } else { + buf.writeZero(8); + } + } + + public int computeSize() { + return 25; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 25 ? ValidationResult.error("Buffer too small: expected at least 25 bytes") : ValidationResult.OK; + } + + public InitialVelocity clone() { + InitialVelocity copy = new InitialVelocity(); + copy.yaw = this.yaw != null ? this.yaw.clone() : null; + copy.pitch = this.pitch != null ? this.pitch.clone() : null; + copy.speed = this.speed != null ? this.speed.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof InitialVelocity other) + ? false + : Objects.equals(this.yaw, other.yaw) && Objects.equals(this.pitch, other.pitch) && Objects.equals(this.speed, other.speed); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.yaw, this.pitch, this.speed); + } +} diff --git a/src/com/hypixel/hytale/protocol/InstantData.java b/src/com/hypixel/hytale/protocol/InstantData.java new file mode 100644 index 0000000..73fe775 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/InstantData.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class InstantData { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 12; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 12; + public static final int MAX_SIZE = 12; + public long seconds; + public int nanos; + + public InstantData() { + } + + public InstantData(long seconds, int nanos) { + this.seconds = seconds; + this.nanos = nanos; + } + + public InstantData(@Nonnull InstantData other) { + this.seconds = other.seconds; + this.nanos = other.nanos; + } + + @Nonnull + public static InstantData deserialize(@Nonnull ByteBuf buf, int offset) { + InstantData obj = new InstantData(); + obj.seconds = buf.getLongLE(offset + 0); + obj.nanos = buf.getIntLE(offset + 8); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 12; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeLongLE(this.seconds); + buf.writeIntLE(this.nanos); + } + + public int computeSize() { + return 12; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 12 ? ValidationResult.error("Buffer too small: expected at least 12 bytes") : ValidationResult.OK; + } + + public InstantData clone() { + InstantData copy = new InstantData(); + copy.seconds = this.seconds; + copy.nanos = this.nanos; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof InstantData other) ? false : this.seconds == other.seconds && this.nanos == other.nanos; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.seconds, this.nanos); + } +} diff --git a/src/com/hypixel/hytale/protocol/IntParamValue.java b/src/com/hypixel/hytale/protocol/IntParamValue.java new file mode 100644 index 0000000..a363005 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/IntParamValue.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class IntParamValue extends ParamValue { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 4; + public int value; + + public IntParamValue() { + } + + public IntParamValue(int value) { + this.value = value; + } + + public IntParamValue(@Nonnull IntParamValue other) { + this.value = other.value; + } + + @Nonnull + public static IntParamValue deserialize(@Nonnull ByteBuf buf, int offset) { + IntParamValue obj = new IntParamValue(); + obj.value = buf.getIntLE(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 4; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + buf.writeIntLE(this.value); + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + return 4; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 4 ? ValidationResult.error("Buffer too small: expected at least 4 bytes") : ValidationResult.OK; + } + + public IntParamValue clone() { + IntParamValue copy = new IntParamValue(); + copy.value = this.value; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof IntParamValue other ? this.value == other.value : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.value); + } +} diff --git a/src/com/hypixel/hytale/protocol/Interaction.java b/src/com/hypixel/hytale/protocol/Interaction.java new file mode 100644 index 0000000..3454e0e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Interaction.java @@ -0,0 +1,305 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class Interaction { + public static final int MAX_SIZE = 1677721605; + @Nonnull + public WaitForDataFrom waitForDataFrom = WaitForDataFrom.Client; + @Nullable + public InteractionEffects effects; + public float horizontalSpeedMultiplier; + public float runTime; + public boolean cancelOnItemChange; + @Nullable + public Map settings; + @Nullable + public InteractionRules rules; + @Nullable + public int[] tags; + @Nullable + public InteractionCameraSettings camera; + + public Interaction() { + } + + @Nonnull + public static Interaction deserialize(@Nonnull ByteBuf buf, int offset) { + int typeId = VarInt.peek(buf, offset); + int typeIdLen = VarInt.length(buf, offset); + + return (Interaction)(switch (typeId) { + case 0 -> SimpleBlockInteraction.deserialize(buf, offset + typeIdLen); + case 1 -> SimpleInteraction.deserialize(buf, offset + typeIdLen); + case 2 -> PlaceBlockInteraction.deserialize(buf, offset + typeIdLen); + case 3 -> BreakBlockInteraction.deserialize(buf, offset + typeIdLen); + case 4 -> PickBlockInteraction.deserialize(buf, offset + typeIdLen); + case 5 -> UseBlockInteraction.deserialize(buf, offset + typeIdLen); + case 6 -> UseEntityInteraction.deserialize(buf, offset + typeIdLen); + case 7 -> BuilderToolInteraction.deserialize(buf, offset + typeIdLen); + case 8 -> ModifyInventoryInteraction.deserialize(buf, offset + typeIdLen); + case 9 -> ChargingInteraction.deserialize(buf, offset + typeIdLen); + case 10 -> WieldingInteraction.deserialize(buf, offset + typeIdLen); + case 11 -> ChainingInteraction.deserialize(buf, offset + typeIdLen); + case 12 -> ConditionInteraction.deserialize(buf, offset + typeIdLen); + case 13 -> StatsConditionInteraction.deserialize(buf, offset + typeIdLen); + case 14 -> BlockConditionInteraction.deserialize(buf, offset + typeIdLen); + case 15 -> ReplaceInteraction.deserialize(buf, offset + typeIdLen); + case 16 -> ChangeBlockInteraction.deserialize(buf, offset + typeIdLen); + case 17 -> ChangeStateInteraction.deserialize(buf, offset + typeIdLen); + case 18 -> FirstClickInteraction.deserialize(buf, offset + typeIdLen); + case 19 -> RefillContainerInteraction.deserialize(buf, offset + typeIdLen); + case 20 -> SelectInteraction.deserialize(buf, offset + typeIdLen); + case 21 -> DamageEntityInteraction.deserialize(buf, offset + typeIdLen); + case 22 -> RepeatInteraction.deserialize(buf, offset + typeIdLen); + case 23 -> ParallelInteraction.deserialize(buf, offset + typeIdLen); + case 24 -> ChangeActiveSlotInteraction.deserialize(buf, offset + typeIdLen); + case 25 -> EffectConditionInteraction.deserialize(buf, offset + typeIdLen); + case 26 -> ApplyForceInteraction.deserialize(buf, offset + typeIdLen); + case 27 -> ApplyEffectInteraction.deserialize(buf, offset + typeIdLen); + case 28 -> ClearEntityEffectInteraction.deserialize(buf, offset + typeIdLen); + case 29 -> SerialInteraction.deserialize(buf, offset + typeIdLen); + case 30 -> ChangeStatInteraction.deserialize(buf, offset + typeIdLen); + case 31 -> MovementConditionInteraction.deserialize(buf, offset + typeIdLen); + case 32 -> ProjectileInteraction.deserialize(buf, offset + typeIdLen); + case 33 -> RemoveEntityInteraction.deserialize(buf, offset + typeIdLen); + case 34 -> ResetCooldownInteraction.deserialize(buf, offset + typeIdLen); + case 35 -> TriggerCooldownInteraction.deserialize(buf, offset + typeIdLen); + case 36 -> CooldownConditionInteraction.deserialize(buf, offset + typeIdLen); + case 37 -> ChainFlagInteraction.deserialize(buf, offset + typeIdLen); + case 38 -> IncrementCooldownInteraction.deserialize(buf, offset + typeIdLen); + case 39 -> CancelChainInteraction.deserialize(buf, offset + typeIdLen); + case 40 -> RunRootInteraction.deserialize(buf, offset + typeIdLen); + case 41 -> CameraInteraction.deserialize(buf, offset + typeIdLen); + case 42 -> SpawnDeployableFromRaycastInteraction.deserialize(buf, offset + typeIdLen); + case 43 -> MemoriesConditionInteraction.deserialize(buf, offset + typeIdLen); + case 44 -> ToggleGliderInteraction.deserialize(buf, offset + typeIdLen); + default -> throw ProtocolException.unknownPolymorphicType("Interaction", typeId); + }); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + int typeId = VarInt.peek(buf, offset); + int typeIdLen = VarInt.length(buf, offset); + + return typeIdLen + switch (typeId) { + case 0 -> SimpleBlockInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 1 -> SimpleInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 2 -> PlaceBlockInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 3 -> BreakBlockInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 4 -> PickBlockInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 5 -> UseBlockInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 6 -> UseEntityInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 7 -> BuilderToolInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 8 -> ModifyInventoryInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 9 -> ChargingInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 10 -> WieldingInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 11 -> ChainingInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 12 -> ConditionInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 13 -> StatsConditionInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 14 -> BlockConditionInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 15 -> ReplaceInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 16 -> ChangeBlockInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 17 -> ChangeStateInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 18 -> FirstClickInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 19 -> RefillContainerInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 20 -> SelectInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 21 -> DamageEntityInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 22 -> RepeatInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 23 -> ParallelInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 24 -> ChangeActiveSlotInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 25 -> EffectConditionInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 26 -> ApplyForceInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 27 -> ApplyEffectInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 28 -> ClearEntityEffectInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 29 -> SerialInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 30 -> ChangeStatInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 31 -> MovementConditionInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 32 -> ProjectileInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 33 -> RemoveEntityInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 34 -> ResetCooldownInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 35 -> TriggerCooldownInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 36 -> CooldownConditionInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 37 -> ChainFlagInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 38 -> IncrementCooldownInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 39 -> CancelChainInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 40 -> RunRootInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 41 -> CameraInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 42 -> SpawnDeployableFromRaycastInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 43 -> MemoriesConditionInteraction.computeBytesConsumed(buf, offset + typeIdLen); + case 44 -> ToggleGliderInteraction.computeBytesConsumed(buf, offset + typeIdLen); + default -> throw ProtocolException.unknownPolymorphicType("Interaction", typeId); + }; + } + + public int getTypeId() { + if (this instanceof BreakBlockInteraction sub) { + return 3; + } else if (this instanceof PickBlockInteraction sub) { + return 4; + } else if (this instanceof UseBlockInteraction sub) { + return 5; + } else if (this instanceof BlockConditionInteraction sub) { + return 14; + } else if (this instanceof ChangeBlockInteraction sub) { + return 16; + } else if (this instanceof ChangeStateInteraction sub) { + return 17; + } else if (this instanceof RefillContainerInteraction sub) { + return 19; + } else if (this instanceof SimpleBlockInteraction sub) { + return 0; + } else if (this instanceof PlaceBlockInteraction sub) { + return 2; + } else if (this instanceof UseEntityInteraction sub) { + return 6; + } else if (this instanceof BuilderToolInteraction sub) { + return 7; + } else if (this instanceof ModifyInventoryInteraction sub) { + return 8; + } else if (this instanceof WieldingInteraction sub) { + return 10; + } else if (this instanceof ConditionInteraction sub) { + return 12; + } else if (this instanceof StatsConditionInteraction sub) { + return 13; + } else if (this instanceof SelectInteraction sub) { + return 20; + } else if (this instanceof RepeatInteraction sub) { + return 22; + } else if (this instanceof EffectConditionInteraction sub) { + return 25; + } else if (this instanceof ApplyForceInteraction sub) { + return 26; + } else if (this instanceof ApplyEffectInteraction sub) { + return 27; + } else if (this instanceof ClearEntityEffectInteraction sub) { + return 28; + } else if (this instanceof ChangeStatInteraction sub) { + return 30; + } else if (this instanceof MovementConditionInteraction sub) { + return 31; + } else if (this instanceof ProjectileInteraction sub) { + return 32; + } else if (this instanceof RemoveEntityInteraction sub) { + return 33; + } else if (this instanceof ResetCooldownInteraction sub) { + return 34; + } else if (this instanceof TriggerCooldownInteraction sub) { + return 35; + } else if (this instanceof CooldownConditionInteraction sub) { + return 36; + } else if (this instanceof ChainFlagInteraction sub) { + return 37; + } else if (this instanceof IncrementCooldownInteraction sub) { + return 38; + } else if (this instanceof CancelChainInteraction sub) { + return 39; + } else if (this instanceof RunRootInteraction sub) { + return 40; + } else if (this instanceof CameraInteraction sub) { + return 41; + } else if (this instanceof SpawnDeployableFromRaycastInteraction sub) { + return 42; + } else if (this instanceof ToggleGliderInteraction sub) { + return 44; + } else if (this instanceof SimpleInteraction sub) { + return 1; + } else if (this instanceof ChargingInteraction sub) { + return 9; + } else if (this instanceof ChainingInteraction sub) { + return 11; + } else if (this instanceof ReplaceInteraction sub) { + return 15; + } else if (this instanceof FirstClickInteraction sub) { + return 18; + } else if (this instanceof DamageEntityInteraction sub) { + return 21; + } else if (this instanceof ParallelInteraction sub) { + return 23; + } else if (this instanceof ChangeActiveSlotInteraction sub) { + return 24; + } else if (this instanceof SerialInteraction sub) { + return 29; + } else if (this instanceof MemoriesConditionInteraction sub) { + return 43; + } else { + throw new IllegalStateException("Unknown subtype: " + this.getClass().getName()); + } + } + + public abstract int serialize(@Nonnull ByteBuf var1); + + public abstract int computeSize(); + + public int serializeWithTypeId(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + VarInt.write(buf, this.getTypeId()); + this.serialize(buf); + return buf.writerIndex() - startPos; + } + + public int computeSizeWithTypeId() { + return VarInt.size(this.getTypeId()) + this.computeSize(); + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + int typeId = VarInt.peek(buffer, offset); + int typeIdLen = VarInt.length(buffer, offset); + + return switch (typeId) { + case 0 -> SimpleBlockInteraction.validateStructure(buffer, offset + typeIdLen); + case 1 -> SimpleInteraction.validateStructure(buffer, offset + typeIdLen); + case 2 -> PlaceBlockInteraction.validateStructure(buffer, offset + typeIdLen); + case 3 -> BreakBlockInteraction.validateStructure(buffer, offset + typeIdLen); + case 4 -> PickBlockInteraction.validateStructure(buffer, offset + typeIdLen); + case 5 -> UseBlockInteraction.validateStructure(buffer, offset + typeIdLen); + case 6 -> UseEntityInteraction.validateStructure(buffer, offset + typeIdLen); + case 7 -> BuilderToolInteraction.validateStructure(buffer, offset + typeIdLen); + case 8 -> ModifyInventoryInteraction.validateStructure(buffer, offset + typeIdLen); + case 9 -> ChargingInteraction.validateStructure(buffer, offset + typeIdLen); + case 10 -> WieldingInteraction.validateStructure(buffer, offset + typeIdLen); + case 11 -> ChainingInteraction.validateStructure(buffer, offset + typeIdLen); + case 12 -> ConditionInteraction.validateStructure(buffer, offset + typeIdLen); + case 13 -> StatsConditionInteraction.validateStructure(buffer, offset + typeIdLen); + case 14 -> BlockConditionInteraction.validateStructure(buffer, offset + typeIdLen); + case 15 -> ReplaceInteraction.validateStructure(buffer, offset + typeIdLen); + case 16 -> ChangeBlockInteraction.validateStructure(buffer, offset + typeIdLen); + case 17 -> ChangeStateInteraction.validateStructure(buffer, offset + typeIdLen); + case 18 -> FirstClickInteraction.validateStructure(buffer, offset + typeIdLen); + case 19 -> RefillContainerInteraction.validateStructure(buffer, offset + typeIdLen); + case 20 -> SelectInteraction.validateStructure(buffer, offset + typeIdLen); + case 21 -> DamageEntityInteraction.validateStructure(buffer, offset + typeIdLen); + case 22 -> RepeatInteraction.validateStructure(buffer, offset + typeIdLen); + case 23 -> ParallelInteraction.validateStructure(buffer, offset + typeIdLen); + case 24 -> ChangeActiveSlotInteraction.validateStructure(buffer, offset + typeIdLen); + case 25 -> EffectConditionInteraction.validateStructure(buffer, offset + typeIdLen); + case 26 -> ApplyForceInteraction.validateStructure(buffer, offset + typeIdLen); + case 27 -> ApplyEffectInteraction.validateStructure(buffer, offset + typeIdLen); + case 28 -> ClearEntityEffectInteraction.validateStructure(buffer, offset + typeIdLen); + case 29 -> SerialInteraction.validateStructure(buffer, offset + typeIdLen); + case 30 -> ChangeStatInteraction.validateStructure(buffer, offset + typeIdLen); + case 31 -> MovementConditionInteraction.validateStructure(buffer, offset + typeIdLen); + case 32 -> ProjectileInteraction.validateStructure(buffer, offset + typeIdLen); + case 33 -> RemoveEntityInteraction.validateStructure(buffer, offset + typeIdLen); + case 34 -> ResetCooldownInteraction.validateStructure(buffer, offset + typeIdLen); + case 35 -> TriggerCooldownInteraction.validateStructure(buffer, offset + typeIdLen); + case 36 -> CooldownConditionInteraction.validateStructure(buffer, offset + typeIdLen); + case 37 -> ChainFlagInteraction.validateStructure(buffer, offset + typeIdLen); + case 38 -> IncrementCooldownInteraction.validateStructure(buffer, offset + typeIdLen); + case 39 -> CancelChainInteraction.validateStructure(buffer, offset + typeIdLen); + case 40 -> RunRootInteraction.validateStructure(buffer, offset + typeIdLen); + case 41 -> CameraInteraction.validateStructure(buffer, offset + typeIdLen); + case 42 -> SpawnDeployableFromRaycastInteraction.validateStructure(buffer, offset + typeIdLen); + case 43 -> MemoriesConditionInteraction.validateStructure(buffer, offset + typeIdLen); + case 44 -> ToggleGliderInteraction.validateStructure(buffer, offset + typeIdLen); + default -> ValidationResult.error("Unknown polymorphic type ID " + typeId + " for Interaction"); + }; + } +} diff --git a/src/com/hypixel/hytale/protocol/InteractionCamera.java b/src/com/hypixel/hytale/protocol/InteractionCamera.java new file mode 100644 index 0000000..1514d4d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/InteractionCamera.java @@ -0,0 +1,112 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InteractionCamera { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 29; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 29; + public static final int MAX_SIZE = 29; + public float time; + @Nullable + public Vector3f position; + @Nullable + public Direction rotation; + + public InteractionCamera() { + } + + public InteractionCamera(float time, @Nullable Vector3f position, @Nullable Direction rotation) { + this.time = time; + this.position = position; + this.rotation = rotation; + } + + public InteractionCamera(@Nonnull InteractionCamera other) { + this.time = other.time; + this.position = other.position; + this.rotation = other.rotation; + } + + @Nonnull + public static InteractionCamera deserialize(@Nonnull ByteBuf buf, int offset) { + InteractionCamera obj = new InteractionCamera(); + byte nullBits = buf.getByte(offset); + obj.time = buf.getFloatLE(offset + 1); + if ((nullBits & 1) != 0) { + obj.position = Vector3f.deserialize(buf, offset + 5); + } + + if ((nullBits & 2) != 0) { + obj.rotation = Direction.deserialize(buf, offset + 17); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 29; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.position != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.rotation != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.time); + if (this.position != null) { + this.position.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.rotation != null) { + this.rotation.serialize(buf); + } else { + buf.writeZero(12); + } + } + + public int computeSize() { + return 29; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 29 ? ValidationResult.error("Buffer too small: expected at least 29 bytes") : ValidationResult.OK; + } + + public InteractionCamera clone() { + InteractionCamera copy = new InteractionCamera(); + copy.time = this.time; + copy.position = this.position != null ? this.position.clone() : null; + copy.rotation = this.rotation != null ? this.rotation.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof InteractionCamera other) + ? false + : this.time == other.time && Objects.equals(this.position, other.position) && Objects.equals(this.rotation, other.rotation); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.time, this.position, this.rotation); + } +} diff --git a/src/com/hypixel/hytale/protocol/InteractionCameraSettings.java b/src/com/hypixel/hytale/protocol/InteractionCameraSettings.java new file mode 100644 index 0000000..a5a3161 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/InteractionCameraSettings.java @@ -0,0 +1,276 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InteractionCameraSettings { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 237568019; + @Nullable + public InteractionCamera[] firstPerson; + @Nullable + public InteractionCamera[] thirdPerson; + + public InteractionCameraSettings() { + } + + public InteractionCameraSettings(@Nullable InteractionCamera[] firstPerson, @Nullable InteractionCamera[] thirdPerson) { + this.firstPerson = firstPerson; + this.thirdPerson = thirdPerson; + } + + public InteractionCameraSettings(@Nonnull InteractionCameraSettings other) { + this.firstPerson = other.firstPerson; + this.thirdPerson = other.thirdPerson; + } + + @Nonnull + public static InteractionCameraSettings deserialize(@Nonnull ByteBuf buf, int offset) { + InteractionCameraSettings obj = new InteractionCameraSettings(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int firstPersonCount = VarInt.peek(buf, varPos0); + if (firstPersonCount < 0) { + throw ProtocolException.negativeLength("FirstPerson", firstPersonCount); + } + + if (firstPersonCount > 4096000) { + throw ProtocolException.arrayTooLong("FirstPerson", firstPersonCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + firstPersonCount * 29L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("FirstPerson", varPos0 + varIntLen + firstPersonCount * 29, buf.readableBytes()); + } + + obj.firstPerson = new InteractionCamera[firstPersonCount]; + int elemPos = varPos0 + varIntLen; + + for (int i = 0; i < firstPersonCount; i++) { + obj.firstPerson[i] = InteractionCamera.deserialize(buf, elemPos); + elemPos += InteractionCamera.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int thirdPersonCount = VarInt.peek(buf, varPos1); + if (thirdPersonCount < 0) { + throw ProtocolException.negativeLength("ThirdPerson", thirdPersonCount); + } + + if (thirdPersonCount > 4096000) { + throw ProtocolException.arrayTooLong("ThirdPerson", thirdPersonCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + thirdPersonCount * 29L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("ThirdPerson", varPos1 + varIntLen + thirdPersonCount * 29, buf.readableBytes()); + } + + obj.thirdPerson = new InteractionCamera[thirdPersonCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < thirdPersonCount; i++) { + obj.thirdPerson[i] = InteractionCamera.deserialize(buf, elemPos); + elemPos += InteractionCamera.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < arrLen; i++) { + pos0 += InteractionCamera.computeBytesConsumed(buf, pos0); + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += InteractionCamera.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.firstPerson != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.thirdPerson != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int firstPersonOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int thirdPersonOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.firstPerson != null) { + buf.setIntLE(firstPersonOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.firstPerson.length > 4096000) { + throw ProtocolException.arrayTooLong("FirstPerson", this.firstPerson.length, 4096000); + } + + VarInt.write(buf, this.firstPerson.length); + + for (InteractionCamera item : this.firstPerson) { + item.serialize(buf); + } + } else { + buf.setIntLE(firstPersonOffsetSlot, -1); + } + + if (this.thirdPerson != null) { + buf.setIntLE(thirdPersonOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.thirdPerson.length > 4096000) { + throw ProtocolException.arrayTooLong("ThirdPerson", this.thirdPerson.length, 4096000); + } + + VarInt.write(buf, this.thirdPerson.length); + + for (InteractionCamera item : this.thirdPerson) { + item.serialize(buf); + } + } else { + buf.setIntLE(thirdPersonOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 9; + if (this.firstPerson != null) { + size += VarInt.size(this.firstPerson.length) + this.firstPerson.length * 29; + } + + if (this.thirdPerson != null) { + size += VarInt.size(this.thirdPerson.length) + this.thirdPerson.length * 29; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int firstPersonOffset = buffer.getIntLE(offset + 1); + if (firstPersonOffset < 0) { + return ValidationResult.error("Invalid offset for FirstPerson"); + } + + int pos = offset + 9 + firstPersonOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FirstPerson"); + } + + int firstPersonCount = VarInt.peek(buffer, pos); + if (firstPersonCount < 0) { + return ValidationResult.error("Invalid array count for FirstPerson"); + } + + if (firstPersonCount > 4096000) { + return ValidationResult.error("FirstPerson exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += firstPersonCount * 29; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading FirstPerson"); + } + } + + if ((nullBits & 2) != 0) { + int thirdPersonOffset = buffer.getIntLE(offset + 5); + if (thirdPersonOffset < 0) { + return ValidationResult.error("Invalid offset for ThirdPerson"); + } + + int posx = offset + 9 + thirdPersonOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ThirdPerson"); + } + + int thirdPersonCount = VarInt.peek(buffer, posx); + if (thirdPersonCount < 0) { + return ValidationResult.error("Invalid array count for ThirdPerson"); + } + + if (thirdPersonCount > 4096000) { + return ValidationResult.error("ThirdPerson exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += thirdPersonCount * 29; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ThirdPerson"); + } + } + + return ValidationResult.OK; + } + } + + public InteractionCameraSettings clone() { + InteractionCameraSettings copy = new InteractionCameraSettings(); + copy.firstPerson = this.firstPerson != null ? Arrays.stream(this.firstPerson).map(e -> e.clone()).toArray(InteractionCamera[]::new) : null; + copy.thirdPerson = this.thirdPerson != null ? Arrays.stream(this.thirdPerson).map(e -> e.clone()).toArray(InteractionCamera[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof InteractionCameraSettings other) + ? false + : Arrays.equals((Object[])this.firstPerson, (Object[])other.firstPerson) && Arrays.equals((Object[])this.thirdPerson, (Object[])other.thirdPerson); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode((Object[])this.firstPerson); + return 31 * result + Arrays.hashCode((Object[])this.thirdPerson); + } +} diff --git a/src/com/hypixel/hytale/protocol/InteractionChainData.java b/src/com/hypixel/hytale/protocol/InteractionChainData.java new file mode 100644 index 0000000..cc34cb7 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/InteractionChainData.java @@ -0,0 +1,226 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InteractionChainData { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 61; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 61; + public static final int MAX_SIZE = 16384066; + public int entityId = -1; + @Nonnull + public UUID proxyId = new UUID(0L, 0L); + @Nullable + public Vector3f hitLocation; + @Nullable + public String hitDetail; + @Nullable + public BlockPosition blockPosition; + public int targetSlot = Integer.MIN_VALUE; + @Nullable + public Vector3f hitNormal; + + public InteractionChainData() { + } + + public InteractionChainData( + int entityId, + @Nonnull UUID proxyId, + @Nullable Vector3f hitLocation, + @Nullable String hitDetail, + @Nullable BlockPosition blockPosition, + int targetSlot, + @Nullable Vector3f hitNormal + ) { + this.entityId = entityId; + this.proxyId = proxyId; + this.hitLocation = hitLocation; + this.hitDetail = hitDetail; + this.blockPosition = blockPosition; + this.targetSlot = targetSlot; + this.hitNormal = hitNormal; + } + + public InteractionChainData(@Nonnull InteractionChainData other) { + this.entityId = other.entityId; + this.proxyId = other.proxyId; + this.hitLocation = other.hitLocation; + this.hitDetail = other.hitDetail; + this.blockPosition = other.blockPosition; + this.targetSlot = other.targetSlot; + this.hitNormal = other.hitNormal; + } + + @Nonnull + public static InteractionChainData deserialize(@Nonnull ByteBuf buf, int offset) { + InteractionChainData obj = new InteractionChainData(); + byte nullBits = buf.getByte(offset); + obj.entityId = buf.getIntLE(offset + 1); + obj.proxyId = PacketIO.readUUID(buf, offset + 5); + if ((nullBits & 1) != 0) { + obj.hitLocation = Vector3f.deserialize(buf, offset + 21); + } + + if ((nullBits & 4) != 0) { + obj.blockPosition = BlockPosition.deserialize(buf, offset + 33); + } + + obj.targetSlot = buf.getIntLE(offset + 45); + if ((nullBits & 8) != 0) { + obj.hitNormal = Vector3f.deserialize(buf, offset + 49); + } + + int pos = offset + 61; + if ((nullBits & 2) != 0) { + int hitDetailLen = VarInt.peek(buf, pos); + if (hitDetailLen < 0) { + throw ProtocolException.negativeLength("HitDetail", hitDetailLen); + } + + if (hitDetailLen > 4096000) { + throw ProtocolException.stringTooLong("HitDetail", hitDetailLen, 4096000); + } + + int hitDetailVarLen = VarInt.length(buf, pos); + obj.hitDetail = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += hitDetailVarLen + hitDetailLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 61; + if ((nullBits & 2) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.hitLocation != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.hitDetail != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.blockPosition != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.hitNormal != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.entityId); + PacketIO.writeUUID(buf, this.proxyId); + if (this.hitLocation != null) { + this.hitLocation.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.blockPosition != null) { + this.blockPosition.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeIntLE(this.targetSlot); + if (this.hitNormal != null) { + this.hitNormal.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.hitDetail != null) { + PacketIO.writeVarString(buf, this.hitDetail, 4096000); + } + } + + public int computeSize() { + int size = 61; + if (this.hitDetail != null) { + size += PacketIO.stringSize(this.hitDetail); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 61) { + return ValidationResult.error("Buffer too small: expected at least 61 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 61; + if ((nullBits & 2) != 0) { + int hitDetailLen = VarInt.peek(buffer, pos); + if (hitDetailLen < 0) { + return ValidationResult.error("Invalid string length for HitDetail"); + } + + if (hitDetailLen > 4096000) { + return ValidationResult.error("HitDetail exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += hitDetailLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading HitDetail"); + } + } + + return ValidationResult.OK; + } + } + + public InteractionChainData clone() { + InteractionChainData copy = new InteractionChainData(); + copy.entityId = this.entityId; + copy.proxyId = this.proxyId; + copy.hitLocation = this.hitLocation != null ? this.hitLocation.clone() : null; + copy.hitDetail = this.hitDetail; + copy.blockPosition = this.blockPosition != null ? this.blockPosition.clone() : null; + copy.targetSlot = this.targetSlot; + copy.hitNormal = this.hitNormal != null ? this.hitNormal.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof InteractionChainData other) + ? false + : this.entityId == other.entityId + && Objects.equals(this.proxyId, other.proxyId) + && Objects.equals(this.hitLocation, other.hitLocation) + && Objects.equals(this.hitDetail, other.hitDetail) + && Objects.equals(this.blockPosition, other.blockPosition) + && this.targetSlot == other.targetSlot + && Objects.equals(this.hitNormal, other.hitNormal); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.entityId, this.proxyId, this.hitLocation, this.hitDetail, this.blockPosition, this.targetSlot, this.hitNormal); + } +} diff --git a/src/com/hypixel/hytale/protocol/InteractionConfiguration.java b/src/com/hypixel/hytale/protocol/InteractionConfiguration.java new file mode 100644 index 0000000..a914dae --- /dev/null +++ b/src/com/hypixel/hytale/protocol/InteractionConfiguration.java @@ -0,0 +1,325 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InteractionConfiguration { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 12; + public static final int MAX_SIZE = 1677721600; + public boolean displayOutlines = true; + public boolean debugOutlines; + @Nullable + public Map useDistance; + public boolean allEntities; + @Nullable + public Map priorities; + + public InteractionConfiguration() { + } + + public InteractionConfiguration( + boolean displayOutlines, + boolean debugOutlines, + @Nullable Map useDistance, + boolean allEntities, + @Nullable Map priorities + ) { + this.displayOutlines = displayOutlines; + this.debugOutlines = debugOutlines; + this.useDistance = useDistance; + this.allEntities = allEntities; + this.priorities = priorities; + } + + public InteractionConfiguration(@Nonnull InteractionConfiguration other) { + this.displayOutlines = other.displayOutlines; + this.debugOutlines = other.debugOutlines; + this.useDistance = other.useDistance; + this.allEntities = other.allEntities; + this.priorities = other.priorities; + } + + @Nonnull + public static InteractionConfiguration deserialize(@Nonnull ByteBuf buf, int offset) { + InteractionConfiguration obj = new InteractionConfiguration(); + byte nullBits = buf.getByte(offset); + obj.displayOutlines = buf.getByte(offset + 1) != 0; + obj.debugOutlines = buf.getByte(offset + 2) != 0; + obj.allEntities = buf.getByte(offset + 3) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 12 + buf.getIntLE(offset + 4); + int useDistanceCount = VarInt.peek(buf, varPos0); + if (useDistanceCount < 0) { + throw ProtocolException.negativeLength("UseDistance", useDistanceCount); + } + + if (useDistanceCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("UseDistance", useDistanceCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + obj.useDistance = new HashMap<>(useDistanceCount); + int dictPos = varPos0 + varIntLen; + + for (int i = 0; i < useDistanceCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + float val = buf.getFloatLE(++dictPos); + dictPos += 4; + if (obj.useDistance.put(key, val) != null) { + throw ProtocolException.duplicateKey("useDistance", key); + } + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 12 + buf.getIntLE(offset + 8); + int prioritiesCount = VarInt.peek(buf, varPos1); + if (prioritiesCount < 0) { + throw ProtocolException.negativeLength("Priorities", prioritiesCount); + } + + if (prioritiesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Priorities", prioritiesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.priorities = new HashMap<>(prioritiesCount); + int dictPos = varPos1 + varIntLen; + + for (int ix = 0; ix < prioritiesCount; ix++) { + InteractionType key = InteractionType.fromValue(buf.getByte(dictPos)); + InteractionPriority val = InteractionPriority.deserialize(buf, ++dictPos); + dictPos += InteractionPriority.computeBytesConsumed(buf, dictPos); + if (obj.priorities.put(key, val) != null) { + throw ProtocolException.duplicateKey("priorities", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 12; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 4); + int pos0 = offset + 12 + fieldOffset0; + int dictLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < dictLen; i++) { + pos0 = ++pos0 + 4; + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 8); + int pos1 = offset + 12 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionPriority.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.useDistance != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.priorities != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.displayOutlines ? 1 : 0); + buf.writeByte(this.debugOutlines ? 1 : 0); + buf.writeByte(this.allEntities ? 1 : 0); + int useDistanceOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int prioritiesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.useDistance != null) { + buf.setIntLE(useDistanceOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.useDistance.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("UseDistance", this.useDistance.size(), 4096000); + } + + VarInt.write(buf, this.useDistance.size()); + + for (Entry e : this.useDistance.entrySet()) { + buf.writeByte(e.getKey().getValue()); + buf.writeFloatLE(e.getValue()); + } + } else { + buf.setIntLE(useDistanceOffsetSlot, -1); + } + + if (this.priorities != null) { + buf.setIntLE(prioritiesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.priorities.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Priorities", this.priorities.size(), 4096000); + } + + VarInt.write(buf, this.priorities.size()); + + for (Entry e : this.priorities.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(prioritiesOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 12; + if (this.useDistance != null) { + size += VarInt.size(this.useDistance.size()) + this.useDistance.size() * 5; + } + + if (this.priorities != null) { + int prioritiesSize = 0; + + for (Entry kvp : this.priorities.entrySet()) { + prioritiesSize += 1 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.priorities.size()) + prioritiesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 12) { + return ValidationResult.error("Buffer too small: expected at least 12 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int useDistanceOffset = buffer.getIntLE(offset + 4); + if (useDistanceOffset < 0) { + return ValidationResult.error("Invalid offset for UseDistance"); + } + + int pos = offset + 12 + useDistanceOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for UseDistance"); + } + + int useDistanceCount = VarInt.peek(buffer, pos); + if (useDistanceCount < 0) { + return ValidationResult.error("Invalid dictionary count for UseDistance"); + } + + if (useDistanceCount > 4096000) { + return ValidationResult.error("UseDistance exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < useDistanceCount; i++) { + pos = ++pos + 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + if ((nullBits & 2) != 0) { + int prioritiesOffset = buffer.getIntLE(offset + 8); + if (prioritiesOffset < 0) { + return ValidationResult.error("Invalid offset for Priorities"); + } + + int posx = offset + 12 + prioritiesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Priorities"); + } + + int prioritiesCount = VarInt.peek(buffer, posx); + if (prioritiesCount < 0) { + return ValidationResult.error("Invalid dictionary count for Priorities"); + } + + if (prioritiesCount > 4096000) { + return ValidationResult.error("Priorities exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int ix = 0; ix < prioritiesCount; ix++) { + posx = ++posx + InteractionPriority.computeBytesConsumed(buffer, posx); + } + } + + return ValidationResult.OK; + } + } + + public InteractionConfiguration clone() { + InteractionConfiguration copy = new InteractionConfiguration(); + copy.displayOutlines = this.displayOutlines; + copy.debugOutlines = this.debugOutlines; + copy.useDistance = this.useDistance != null ? new HashMap<>(this.useDistance) : null; + copy.allEntities = this.allEntities; + if (this.priorities != null) { + Map m = new HashMap<>(); + + for (Entry e : this.priorities.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.priorities = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof InteractionConfiguration other) + ? false + : this.displayOutlines == other.displayOutlines + && this.debugOutlines == other.debugOutlines + && Objects.equals(this.useDistance, other.useDistance) + && this.allEntities == other.allEntities + && Objects.equals(this.priorities, other.priorities); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.displayOutlines, this.debugOutlines, this.useDistance, this.allEntities, this.priorities); + } +} diff --git a/src/com/hypixel/hytale/protocol/InteractionCooldown.java b/src/com/hypixel/hytale/protocol/InteractionCooldown.java new file mode 100644 index 0000000..d7be03f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/InteractionCooldown.java @@ -0,0 +1,282 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InteractionCooldown { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 16; + public static final int MAX_SIZE = 32768026; + @Nullable + public String cooldownId; + public float cooldown; + public boolean clickBypass; + @Nullable + public float[] chargeTimes; + public boolean skipCooldownReset; + public boolean interruptRecharge; + + public InteractionCooldown() { + } + + public InteractionCooldown( + @Nullable String cooldownId, float cooldown, boolean clickBypass, @Nullable float[] chargeTimes, boolean skipCooldownReset, boolean interruptRecharge + ) { + this.cooldownId = cooldownId; + this.cooldown = cooldown; + this.clickBypass = clickBypass; + this.chargeTimes = chargeTimes; + this.skipCooldownReset = skipCooldownReset; + this.interruptRecharge = interruptRecharge; + } + + public InteractionCooldown(@Nonnull InteractionCooldown other) { + this.cooldownId = other.cooldownId; + this.cooldown = other.cooldown; + this.clickBypass = other.clickBypass; + this.chargeTimes = other.chargeTimes; + this.skipCooldownReset = other.skipCooldownReset; + this.interruptRecharge = other.interruptRecharge; + } + + @Nonnull + public static InteractionCooldown deserialize(@Nonnull ByteBuf buf, int offset) { + InteractionCooldown obj = new InteractionCooldown(); + byte nullBits = buf.getByte(offset); + obj.cooldown = buf.getFloatLE(offset + 1); + obj.clickBypass = buf.getByte(offset + 5) != 0; + obj.skipCooldownReset = buf.getByte(offset + 6) != 0; + obj.interruptRecharge = buf.getByte(offset + 7) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 16 + buf.getIntLE(offset + 8); + int cooldownIdLen = VarInt.peek(buf, varPos0); + if (cooldownIdLen < 0) { + throw ProtocolException.negativeLength("CooldownId", cooldownIdLen); + } + + if (cooldownIdLen > 4096000) { + throw ProtocolException.stringTooLong("CooldownId", cooldownIdLen, 4096000); + } + + obj.cooldownId = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 16 + buf.getIntLE(offset + 12); + int chargeTimesCount = VarInt.peek(buf, varPos1); + if (chargeTimesCount < 0) { + throw ProtocolException.negativeLength("ChargeTimes", chargeTimesCount); + } + + if (chargeTimesCount > 4096000) { + throw ProtocolException.arrayTooLong("ChargeTimes", chargeTimesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + chargeTimesCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("ChargeTimes", varPos1 + varIntLen + chargeTimesCount * 4, buf.readableBytes()); + } + + obj.chargeTimes = new float[chargeTimesCount]; + + for (int i = 0; i < chargeTimesCount; i++) { + obj.chargeTimes[i] = buf.getFloatLE(varPos1 + varIntLen + i * 4); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 16; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 8); + int pos0 = offset + 16 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 12); + int pos1 = offset + 16 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + arrLen * 4; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.cooldownId != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.chargeTimes != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.cooldown); + buf.writeByte(this.clickBypass ? 1 : 0); + buf.writeByte(this.skipCooldownReset ? 1 : 0); + buf.writeByte(this.interruptRecharge ? 1 : 0); + int cooldownIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int chargeTimesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.cooldownId != null) { + buf.setIntLE(cooldownIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.cooldownId, 4096000); + } else { + buf.setIntLE(cooldownIdOffsetSlot, -1); + } + + if (this.chargeTimes != null) { + buf.setIntLE(chargeTimesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.chargeTimes.length > 4096000) { + throw ProtocolException.arrayTooLong("ChargeTimes", this.chargeTimes.length, 4096000); + } + + VarInt.write(buf, this.chargeTimes.length); + + for (float item : this.chargeTimes) { + buf.writeFloatLE(item); + } + } else { + buf.setIntLE(chargeTimesOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 16; + if (this.cooldownId != null) { + size += PacketIO.stringSize(this.cooldownId); + } + + if (this.chargeTimes != null) { + size += VarInt.size(this.chargeTimes.length) + this.chargeTimes.length * 4; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 16) { + return ValidationResult.error("Buffer too small: expected at least 16 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int cooldownIdOffset = buffer.getIntLE(offset + 8); + if (cooldownIdOffset < 0) { + return ValidationResult.error("Invalid offset for CooldownId"); + } + + int pos = offset + 16 + cooldownIdOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for CooldownId"); + } + + int cooldownIdLen = VarInt.peek(buffer, pos); + if (cooldownIdLen < 0) { + return ValidationResult.error("Invalid string length for CooldownId"); + } + + if (cooldownIdLen > 4096000) { + return ValidationResult.error("CooldownId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += cooldownIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading CooldownId"); + } + } + + if ((nullBits & 2) != 0) { + int chargeTimesOffset = buffer.getIntLE(offset + 12); + if (chargeTimesOffset < 0) { + return ValidationResult.error("Invalid offset for ChargeTimes"); + } + + int posx = offset + 16 + chargeTimesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ChargeTimes"); + } + + int chargeTimesCount = VarInt.peek(buffer, posx); + if (chargeTimesCount < 0) { + return ValidationResult.error("Invalid array count for ChargeTimes"); + } + + if (chargeTimesCount > 4096000) { + return ValidationResult.error("ChargeTimes exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += chargeTimesCount * 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ChargeTimes"); + } + } + + return ValidationResult.OK; + } + } + + public InteractionCooldown clone() { + InteractionCooldown copy = new InteractionCooldown(); + copy.cooldownId = this.cooldownId; + copy.cooldown = this.cooldown; + copy.clickBypass = this.clickBypass; + copy.chargeTimes = this.chargeTimes != null ? Arrays.copyOf(this.chargeTimes, this.chargeTimes.length) : null; + copy.skipCooldownReset = this.skipCooldownReset; + copy.interruptRecharge = this.interruptRecharge; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof InteractionCooldown other) + ? false + : Objects.equals(this.cooldownId, other.cooldownId) + && this.cooldown == other.cooldown + && this.clickBypass == other.clickBypass + && Arrays.equals(this.chargeTimes, other.chargeTimes) + && this.skipCooldownReset == other.skipCooldownReset + && this.interruptRecharge == other.interruptRecharge; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.cooldownId); + result = 31 * result + Float.hashCode(this.cooldown); + result = 31 * result + Boolean.hashCode(this.clickBypass); + result = 31 * result + Arrays.hashCode(this.chargeTimes); + result = 31 * result + Boolean.hashCode(this.skipCooldownReset); + return 31 * result + Boolean.hashCode(this.interruptRecharge); + } +} diff --git a/src/com/hypixel/hytale/protocol/InteractionEffects.java b/src/com/hypixel/hytale/protocol/InteractionEffects.java new file mode 100644 index 0000000..5b31d88 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/InteractionEffects.java @@ -0,0 +1,668 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import com.hypixel.hytale.protocol.packets.camera.CameraShakeEffect; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InteractionEffects { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 32; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 52; + public static final int MAX_SIZE = 1677721600; + @Nullable + public ModelParticle[] particles; + @Nullable + public ModelParticle[] firstPersonParticles; + public int worldSoundEventIndex; + public int localSoundEventIndex; + @Nullable + public ModelTrail[] trails; + public boolean waitForAnimationToFinish = true; + @Nullable + public String itemPlayerAnimationsId; + @Nullable + public String itemAnimationId; + public boolean clearAnimationOnFinish; + public boolean clearSoundEventOnFinish; + @Nullable + public CameraShakeEffect cameraShake; + @Nullable + public MovementEffects movementEffects; + public float startDelay; + + public InteractionEffects() { + } + + public InteractionEffects( + @Nullable ModelParticle[] particles, + @Nullable ModelParticle[] firstPersonParticles, + int worldSoundEventIndex, + int localSoundEventIndex, + @Nullable ModelTrail[] trails, + boolean waitForAnimationToFinish, + @Nullable String itemPlayerAnimationsId, + @Nullable String itemAnimationId, + boolean clearAnimationOnFinish, + boolean clearSoundEventOnFinish, + @Nullable CameraShakeEffect cameraShake, + @Nullable MovementEffects movementEffects, + float startDelay + ) { + this.particles = particles; + this.firstPersonParticles = firstPersonParticles; + this.worldSoundEventIndex = worldSoundEventIndex; + this.localSoundEventIndex = localSoundEventIndex; + this.trails = trails; + this.waitForAnimationToFinish = waitForAnimationToFinish; + this.itemPlayerAnimationsId = itemPlayerAnimationsId; + this.itemAnimationId = itemAnimationId; + this.clearAnimationOnFinish = clearAnimationOnFinish; + this.clearSoundEventOnFinish = clearSoundEventOnFinish; + this.cameraShake = cameraShake; + this.movementEffects = movementEffects; + this.startDelay = startDelay; + } + + public InteractionEffects(@Nonnull InteractionEffects other) { + this.particles = other.particles; + this.firstPersonParticles = other.firstPersonParticles; + this.worldSoundEventIndex = other.worldSoundEventIndex; + this.localSoundEventIndex = other.localSoundEventIndex; + this.trails = other.trails; + this.waitForAnimationToFinish = other.waitForAnimationToFinish; + this.itemPlayerAnimationsId = other.itemPlayerAnimationsId; + this.itemAnimationId = other.itemAnimationId; + this.clearAnimationOnFinish = other.clearAnimationOnFinish; + this.clearSoundEventOnFinish = other.clearSoundEventOnFinish; + this.cameraShake = other.cameraShake; + this.movementEffects = other.movementEffects; + this.startDelay = other.startDelay; + } + + @Nonnull + public static InteractionEffects deserialize(@Nonnull ByteBuf buf, int offset) { + InteractionEffects obj = new InteractionEffects(); + byte nullBits = buf.getByte(offset); + obj.worldSoundEventIndex = buf.getIntLE(offset + 1); + obj.localSoundEventIndex = buf.getIntLE(offset + 5); + obj.waitForAnimationToFinish = buf.getByte(offset + 9) != 0; + obj.clearAnimationOnFinish = buf.getByte(offset + 10) != 0; + obj.clearSoundEventOnFinish = buf.getByte(offset + 11) != 0; + if ((nullBits & 32) != 0) { + obj.cameraShake = CameraShakeEffect.deserialize(buf, offset + 12); + } + + if ((nullBits & 64) != 0) { + obj.movementEffects = MovementEffects.deserialize(buf, offset + 21); + } + + obj.startDelay = buf.getFloatLE(offset + 28); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 52 + buf.getIntLE(offset + 32); + int particlesCount = VarInt.peek(buf, varPos0); + if (particlesCount < 0) { + throw ProtocolException.negativeLength("Particles", particlesCount); + } + + if (particlesCount > 4096000) { + throw ProtocolException.arrayTooLong("Particles", particlesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + particlesCount * 34L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Particles", varPos0 + varIntLen + particlesCount * 34, buf.readableBytes()); + } + + obj.particles = new ModelParticle[particlesCount]; + int elemPos = varPos0 + varIntLen; + + for (int i = 0; i < particlesCount; i++) { + obj.particles[i] = ModelParticle.deserialize(buf, elemPos); + elemPos += ModelParticle.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 52 + buf.getIntLE(offset + 36); + int firstPersonParticlesCount = VarInt.peek(buf, varPos1); + if (firstPersonParticlesCount < 0) { + throw ProtocolException.negativeLength("FirstPersonParticles", firstPersonParticlesCount); + } + + if (firstPersonParticlesCount > 4096000) { + throw ProtocolException.arrayTooLong("FirstPersonParticles", firstPersonParticlesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + firstPersonParticlesCount * 34L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("FirstPersonParticles", varPos1 + varIntLen + firstPersonParticlesCount * 34, buf.readableBytes()); + } + + obj.firstPersonParticles = new ModelParticle[firstPersonParticlesCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < firstPersonParticlesCount; i++) { + obj.firstPersonParticles[i] = ModelParticle.deserialize(buf, elemPos); + elemPos += ModelParticle.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 52 + buf.getIntLE(offset + 40); + int trailsCount = VarInt.peek(buf, varPos2); + if (trailsCount < 0) { + throw ProtocolException.negativeLength("Trails", trailsCount); + } + + if (trailsCount > 4096000) { + throw ProtocolException.arrayTooLong("Trails", trailsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + trailsCount * 27L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Trails", varPos2 + varIntLen + trailsCount * 27, buf.readableBytes()); + } + + obj.trails = new ModelTrail[trailsCount]; + int elemPos = varPos2 + varIntLen; + + for (int i = 0; i < trailsCount; i++) { + obj.trails[i] = ModelTrail.deserialize(buf, elemPos); + elemPos += ModelTrail.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 52 + buf.getIntLE(offset + 44); + int itemPlayerAnimationsIdLen = VarInt.peek(buf, varPos3); + if (itemPlayerAnimationsIdLen < 0) { + throw ProtocolException.negativeLength("ItemPlayerAnimationsId", itemPlayerAnimationsIdLen); + } + + if (itemPlayerAnimationsIdLen > 4096000) { + throw ProtocolException.stringTooLong("ItemPlayerAnimationsId", itemPlayerAnimationsIdLen, 4096000); + } + + obj.itemPlayerAnimationsId = PacketIO.readVarString(buf, varPos3, PacketIO.UTF8); + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 52 + buf.getIntLE(offset + 48); + int itemAnimationIdLen = VarInt.peek(buf, varPos4); + if (itemAnimationIdLen < 0) { + throw ProtocolException.negativeLength("ItemAnimationId", itemAnimationIdLen); + } + + if (itemAnimationIdLen > 4096000) { + throw ProtocolException.stringTooLong("ItemAnimationId", itemAnimationIdLen, 4096000); + } + + obj.itemAnimationId = PacketIO.readVarString(buf, varPos4, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 52; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 32); + int pos0 = offset + 52 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < arrLen; i++) { + pos0 += ModelParticle.computeBytesConsumed(buf, pos0); + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 36); + int pos1 = offset + 52 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += ModelParticle.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 40); + int pos2 = offset + 52 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2); + + for (int i = 0; i < arrLen; i++) { + pos2 += ModelTrail.computeBytesConsumed(buf, pos2); + } + + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 44); + int pos3 = offset + 52 + fieldOffset3; + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 48); + int pos4 = offset + 52 + fieldOffset4; + int sl = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4) + sl; + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.particles != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.firstPersonParticles != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.trails != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.itemPlayerAnimationsId != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.itemAnimationId != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.cameraShake != null) { + nullBits = (byte)(nullBits | 32); + } + + if (this.movementEffects != null) { + nullBits = (byte)(nullBits | 64); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.worldSoundEventIndex); + buf.writeIntLE(this.localSoundEventIndex); + buf.writeByte(this.waitForAnimationToFinish ? 1 : 0); + buf.writeByte(this.clearAnimationOnFinish ? 1 : 0); + buf.writeByte(this.clearSoundEventOnFinish ? 1 : 0); + if (this.cameraShake != null) { + this.cameraShake.serialize(buf); + } else { + buf.writeZero(9); + } + + if (this.movementEffects != null) { + this.movementEffects.serialize(buf); + } else { + buf.writeZero(7); + } + + buf.writeFloatLE(this.startDelay); + int particlesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int firstPersonParticlesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int trailsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int itemPlayerAnimationsIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int itemAnimationIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.particles != null) { + buf.setIntLE(particlesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.particles.length > 4096000) { + throw ProtocolException.arrayTooLong("Particles", this.particles.length, 4096000); + } + + VarInt.write(buf, this.particles.length); + + for (ModelParticle item : this.particles) { + item.serialize(buf); + } + } else { + buf.setIntLE(particlesOffsetSlot, -1); + } + + if (this.firstPersonParticles != null) { + buf.setIntLE(firstPersonParticlesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.firstPersonParticles.length > 4096000) { + throw ProtocolException.arrayTooLong("FirstPersonParticles", this.firstPersonParticles.length, 4096000); + } + + VarInt.write(buf, this.firstPersonParticles.length); + + for (ModelParticle item : this.firstPersonParticles) { + item.serialize(buf); + } + } else { + buf.setIntLE(firstPersonParticlesOffsetSlot, -1); + } + + if (this.trails != null) { + buf.setIntLE(trailsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.trails.length > 4096000) { + throw ProtocolException.arrayTooLong("Trails", this.trails.length, 4096000); + } + + VarInt.write(buf, this.trails.length); + + for (ModelTrail item : this.trails) { + item.serialize(buf); + } + } else { + buf.setIntLE(trailsOffsetSlot, -1); + } + + if (this.itemPlayerAnimationsId != null) { + buf.setIntLE(itemPlayerAnimationsIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.itemPlayerAnimationsId, 4096000); + } else { + buf.setIntLE(itemPlayerAnimationsIdOffsetSlot, -1); + } + + if (this.itemAnimationId != null) { + buf.setIntLE(itemAnimationIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.itemAnimationId, 4096000); + } else { + buf.setIntLE(itemAnimationIdOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 52; + if (this.particles != null) { + int particlesSize = 0; + + for (ModelParticle elem : this.particles) { + particlesSize += elem.computeSize(); + } + + size += VarInt.size(this.particles.length) + particlesSize; + } + + if (this.firstPersonParticles != null) { + int firstPersonParticlesSize = 0; + + for (ModelParticle elem : this.firstPersonParticles) { + firstPersonParticlesSize += elem.computeSize(); + } + + size += VarInt.size(this.firstPersonParticles.length) + firstPersonParticlesSize; + } + + if (this.trails != null) { + int trailsSize = 0; + + for (ModelTrail elem : this.trails) { + trailsSize += elem.computeSize(); + } + + size += VarInt.size(this.trails.length) + trailsSize; + } + + if (this.itemPlayerAnimationsId != null) { + size += PacketIO.stringSize(this.itemPlayerAnimationsId); + } + + if (this.itemAnimationId != null) { + size += PacketIO.stringSize(this.itemAnimationId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 52) { + return ValidationResult.error("Buffer too small: expected at least 52 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int particlesOffset = buffer.getIntLE(offset + 32); + if (particlesOffset < 0) { + return ValidationResult.error("Invalid offset for Particles"); + } + + int pos = offset + 52 + particlesOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Particles"); + } + + int particlesCount = VarInt.peek(buffer, pos); + if (particlesCount < 0) { + return ValidationResult.error("Invalid array count for Particles"); + } + + if (particlesCount > 4096000) { + return ValidationResult.error("Particles exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < particlesCount; i++) { + ValidationResult structResult = ModelParticle.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelParticle in Particles[" + i + "]: " + structResult.error()); + } + + pos += ModelParticle.computeBytesConsumed(buffer, pos); + } + } + + if ((nullBits & 2) != 0) { + int firstPersonParticlesOffset = buffer.getIntLE(offset + 36); + if (firstPersonParticlesOffset < 0) { + return ValidationResult.error("Invalid offset for FirstPersonParticles"); + } + + int posx = offset + 52 + firstPersonParticlesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FirstPersonParticles"); + } + + int firstPersonParticlesCount = VarInt.peek(buffer, posx); + if (firstPersonParticlesCount < 0) { + return ValidationResult.error("Invalid array count for FirstPersonParticles"); + } + + if (firstPersonParticlesCount > 4096000) { + return ValidationResult.error("FirstPersonParticles exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < firstPersonParticlesCount; i++) { + ValidationResult structResult = ModelParticle.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelParticle in FirstPersonParticles[" + i + "]: " + structResult.error()); + } + + posx += ModelParticle.computeBytesConsumed(buffer, posx); + } + } + + if ((nullBits & 4) != 0) { + int trailsOffset = buffer.getIntLE(offset + 40); + if (trailsOffset < 0) { + return ValidationResult.error("Invalid offset for Trails"); + } + + int posxx = offset + 52 + trailsOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Trails"); + } + + int trailsCount = VarInt.peek(buffer, posxx); + if (trailsCount < 0) { + return ValidationResult.error("Invalid array count for Trails"); + } + + if (trailsCount > 4096000) { + return ValidationResult.error("Trails exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + + for (int i = 0; i < trailsCount; i++) { + ValidationResult structResult = ModelTrail.validateStructure(buffer, posxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelTrail in Trails[" + i + "]: " + structResult.error()); + } + + posxx += ModelTrail.computeBytesConsumed(buffer, posxx); + } + } + + if ((nullBits & 8) != 0) { + int itemPlayerAnimationsIdOffset = buffer.getIntLE(offset + 44); + if (itemPlayerAnimationsIdOffset < 0) { + return ValidationResult.error("Invalid offset for ItemPlayerAnimationsId"); + } + + int posxxx = offset + 52 + itemPlayerAnimationsIdOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ItemPlayerAnimationsId"); + } + + int itemPlayerAnimationsIdLen = VarInt.peek(buffer, posxxx); + if (itemPlayerAnimationsIdLen < 0) { + return ValidationResult.error("Invalid string length for ItemPlayerAnimationsId"); + } + + if (itemPlayerAnimationsIdLen > 4096000) { + return ValidationResult.error("ItemPlayerAnimationsId exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += itemPlayerAnimationsIdLen; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ItemPlayerAnimationsId"); + } + } + + if ((nullBits & 16) != 0) { + int itemAnimationIdOffset = buffer.getIntLE(offset + 48); + if (itemAnimationIdOffset < 0) { + return ValidationResult.error("Invalid offset for ItemAnimationId"); + } + + int posxxxx = offset + 52 + itemAnimationIdOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ItemAnimationId"); + } + + int itemAnimationIdLen = VarInt.peek(buffer, posxxxx); + if (itemAnimationIdLen < 0) { + return ValidationResult.error("Invalid string length for ItemAnimationId"); + } + + if (itemAnimationIdLen > 4096000) { + return ValidationResult.error("ItemAnimationId exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + posxxxx += itemAnimationIdLen; + if (posxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ItemAnimationId"); + } + } + + return ValidationResult.OK; + } + } + + public InteractionEffects clone() { + InteractionEffects copy = new InteractionEffects(); + copy.particles = this.particles != null ? Arrays.stream(this.particles).map(e -> e.clone()).toArray(ModelParticle[]::new) : null; + copy.firstPersonParticles = this.firstPersonParticles != null + ? Arrays.stream(this.firstPersonParticles).map(e -> e.clone()).toArray(ModelParticle[]::new) + : null; + copy.worldSoundEventIndex = this.worldSoundEventIndex; + copy.localSoundEventIndex = this.localSoundEventIndex; + copy.trails = this.trails != null ? Arrays.stream(this.trails).map(e -> e.clone()).toArray(ModelTrail[]::new) : null; + copy.waitForAnimationToFinish = this.waitForAnimationToFinish; + copy.itemPlayerAnimationsId = this.itemPlayerAnimationsId; + copy.itemAnimationId = this.itemAnimationId; + copy.clearAnimationOnFinish = this.clearAnimationOnFinish; + copy.clearSoundEventOnFinish = this.clearSoundEventOnFinish; + copy.cameraShake = this.cameraShake != null ? this.cameraShake.clone() : null; + copy.movementEffects = this.movementEffects != null ? this.movementEffects.clone() : null; + copy.startDelay = this.startDelay; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof InteractionEffects other) + ? false + : Arrays.equals((Object[])this.particles, (Object[])other.particles) + && Arrays.equals((Object[])this.firstPersonParticles, (Object[])other.firstPersonParticles) + && this.worldSoundEventIndex == other.worldSoundEventIndex + && this.localSoundEventIndex == other.localSoundEventIndex + && Arrays.equals((Object[])this.trails, (Object[])other.trails) + && this.waitForAnimationToFinish == other.waitForAnimationToFinish + && Objects.equals(this.itemPlayerAnimationsId, other.itemPlayerAnimationsId) + && Objects.equals(this.itemAnimationId, other.itemAnimationId) + && this.clearAnimationOnFinish == other.clearAnimationOnFinish + && this.clearSoundEventOnFinish == other.clearSoundEventOnFinish + && Objects.equals(this.cameraShake, other.cameraShake) + && Objects.equals(this.movementEffects, other.movementEffects) + && this.startDelay == other.startDelay; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode((Object[])this.particles); + result = 31 * result + Arrays.hashCode((Object[])this.firstPersonParticles); + result = 31 * result + Integer.hashCode(this.worldSoundEventIndex); + result = 31 * result + Integer.hashCode(this.localSoundEventIndex); + result = 31 * result + Arrays.hashCode((Object[])this.trails); + result = 31 * result + Boolean.hashCode(this.waitForAnimationToFinish); + result = 31 * result + Objects.hashCode(this.itemPlayerAnimationsId); + result = 31 * result + Objects.hashCode(this.itemAnimationId); + result = 31 * result + Boolean.hashCode(this.clearAnimationOnFinish); + result = 31 * result + Boolean.hashCode(this.clearSoundEventOnFinish); + result = 31 * result + Objects.hashCode(this.cameraShake); + result = 31 * result + Objects.hashCode(this.movementEffects); + return 31 * result + Float.hashCode(this.startDelay); + } +} diff --git a/src/com/hypixel/hytale/protocol/InteractionPriority.java b/src/com/hypixel/hytale/protocol/InteractionPriority.java new file mode 100644 index 0000000..717e139 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/InteractionPriority.java @@ -0,0 +1,159 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InteractionPriority { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 20480006; + @Nullable + public Map values; + + public InteractionPriority() { + } + + public InteractionPriority(@Nullable Map values) { + this.values = values; + } + + public InteractionPriority(@Nonnull InteractionPriority other) { + this.values = other.values; + } + + @Nonnull + public static InteractionPriority deserialize(@Nonnull ByteBuf buf, int offset) { + InteractionPriority obj = new InteractionPriority(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int valuesCount = VarInt.peek(buf, pos); + if (valuesCount < 0) { + throw ProtocolException.negativeLength("Values", valuesCount); + } + + if (valuesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Values", valuesCount, 4096000); + } + + pos += VarInt.size(valuesCount); + obj.values = new HashMap<>(valuesCount); + + for (int i = 0; i < valuesCount; i++) { + PrioritySlot key = PrioritySlot.fromValue(buf.getByte(pos)); + int val = buf.getIntLE(++pos); + pos += 4; + if (obj.values.put(key, val) != null) { + throw ProtocolException.duplicateKey("values", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos = ++pos + 4; + } + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.values != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.values != null) { + if (this.values.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Values", this.values.size(), 4096000); + } + + VarInt.write(buf, this.values.size()); + + for (Entry e : this.values.entrySet()) { + buf.writeByte(e.getKey().getValue()); + buf.writeIntLE(e.getValue()); + } + } + } + + public int computeSize() { + int size = 1; + if (this.values != null) { + size += VarInt.size(this.values.size()) + this.values.size() * 5; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int valuesCount = VarInt.peek(buffer, pos); + if (valuesCount < 0) { + return ValidationResult.error("Invalid dictionary count for Values"); + } + + if (valuesCount > 4096000) { + return ValidationResult.error("Values exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < valuesCount; i++) { + pos = ++pos + 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public InteractionPriority clone() { + InteractionPriority copy = new InteractionPriority(); + copy.values = this.values != null ? new HashMap<>(this.values) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof InteractionPriority other ? Objects.equals(this.values, other.values) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.values); + } +} diff --git a/src/com/hypixel/hytale/protocol/InteractionRules.java b/src/com/hypixel/hytale/protocol/InteractionRules.java new file mode 100644 index 0000000..d83c5bf --- /dev/null +++ b/src/com/hypixel/hytale/protocol/InteractionRules.java @@ -0,0 +1,496 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InteractionRules { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 17; + public static final int VARIABLE_FIELD_COUNT = 4; + public static final int VARIABLE_BLOCK_START = 33; + public static final int MAX_SIZE = 16384053; + @Nullable + public InteractionType[] blockedBy; + @Nullable + public InteractionType[] blocking; + @Nullable + public InteractionType[] interruptedBy; + @Nullable + public InteractionType[] interrupting; + public int blockedByBypassIndex; + public int blockingBypassIndex; + public int interruptedByBypassIndex; + public int interruptingBypassIndex; + + public InteractionRules() { + } + + public InteractionRules( + @Nullable InteractionType[] blockedBy, + @Nullable InteractionType[] blocking, + @Nullable InteractionType[] interruptedBy, + @Nullable InteractionType[] interrupting, + int blockedByBypassIndex, + int blockingBypassIndex, + int interruptedByBypassIndex, + int interruptingBypassIndex + ) { + this.blockedBy = blockedBy; + this.blocking = blocking; + this.interruptedBy = interruptedBy; + this.interrupting = interrupting; + this.blockedByBypassIndex = blockedByBypassIndex; + this.blockingBypassIndex = blockingBypassIndex; + this.interruptedByBypassIndex = interruptedByBypassIndex; + this.interruptingBypassIndex = interruptingBypassIndex; + } + + public InteractionRules(@Nonnull InteractionRules other) { + this.blockedBy = other.blockedBy; + this.blocking = other.blocking; + this.interruptedBy = other.interruptedBy; + this.interrupting = other.interrupting; + this.blockedByBypassIndex = other.blockedByBypassIndex; + this.blockingBypassIndex = other.blockingBypassIndex; + this.interruptedByBypassIndex = other.interruptedByBypassIndex; + this.interruptingBypassIndex = other.interruptingBypassIndex; + } + + @Nonnull + public static InteractionRules deserialize(@Nonnull ByteBuf buf, int offset) { + InteractionRules obj = new InteractionRules(); + byte nullBits = buf.getByte(offset); + obj.blockedByBypassIndex = buf.getIntLE(offset + 1); + obj.blockingBypassIndex = buf.getIntLE(offset + 5); + obj.interruptedByBypassIndex = buf.getIntLE(offset + 9); + obj.interruptingBypassIndex = buf.getIntLE(offset + 13); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 33 + buf.getIntLE(offset + 17); + int blockedByCount = VarInt.peek(buf, varPos0); + if (blockedByCount < 0) { + throw ProtocolException.negativeLength("BlockedBy", blockedByCount); + } + + if (blockedByCount > 4096000) { + throw ProtocolException.arrayTooLong("BlockedBy", blockedByCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + blockedByCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("BlockedBy", varPos0 + varIntLen + blockedByCount * 1, buf.readableBytes()); + } + + obj.blockedBy = new InteractionType[blockedByCount]; + int elemPos = varPos0 + varIntLen; + + for (int i = 0; i < blockedByCount; i++) { + obj.blockedBy[i] = InteractionType.fromValue(buf.getByte(elemPos)); + elemPos++; + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 33 + buf.getIntLE(offset + 21); + int blockingCount = VarInt.peek(buf, varPos1); + if (blockingCount < 0) { + throw ProtocolException.negativeLength("Blocking", blockingCount); + } + + if (blockingCount > 4096000) { + throw ProtocolException.arrayTooLong("Blocking", blockingCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + blockingCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Blocking", varPos1 + varIntLen + blockingCount * 1, buf.readableBytes()); + } + + obj.blocking = new InteractionType[blockingCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < blockingCount; i++) { + obj.blocking[i] = InteractionType.fromValue(buf.getByte(elemPos)); + elemPos++; + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 33 + buf.getIntLE(offset + 25); + int interruptedByCount = VarInt.peek(buf, varPos2); + if (interruptedByCount < 0) { + throw ProtocolException.negativeLength("InterruptedBy", interruptedByCount); + } + + if (interruptedByCount > 4096000) { + throw ProtocolException.arrayTooLong("InterruptedBy", interruptedByCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + interruptedByCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("InterruptedBy", varPos2 + varIntLen + interruptedByCount * 1, buf.readableBytes()); + } + + obj.interruptedBy = new InteractionType[interruptedByCount]; + int elemPos = varPos2 + varIntLen; + + for (int i = 0; i < interruptedByCount; i++) { + obj.interruptedBy[i] = InteractionType.fromValue(buf.getByte(elemPos)); + elemPos++; + } + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 33 + buf.getIntLE(offset + 29); + int interruptingCount = VarInt.peek(buf, varPos3); + if (interruptingCount < 0) { + throw ProtocolException.negativeLength("Interrupting", interruptingCount); + } + + if (interruptingCount > 4096000) { + throw ProtocolException.arrayTooLong("Interrupting", interruptingCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + interruptingCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Interrupting", varPos3 + varIntLen + interruptingCount * 1, buf.readableBytes()); + } + + obj.interrupting = new InteractionType[interruptingCount]; + int elemPos = varPos3 + varIntLen; + + for (int i = 0; i < interruptingCount; i++) { + obj.interrupting[i] = InteractionType.fromValue(buf.getByte(elemPos)); + elemPos++; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 33; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 17); + int pos0 = offset + 33 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + arrLen * 1; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 21); + int pos1 = offset + 33 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + arrLen * 1; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 25); + int pos2 = offset + 33 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + arrLen * 1; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 29); + int pos3 = offset + 33 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 1; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.blockedBy != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.blocking != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.interruptedBy != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.interrupting != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.blockedByBypassIndex); + buf.writeIntLE(this.blockingBypassIndex); + buf.writeIntLE(this.interruptedByBypassIndex); + buf.writeIntLE(this.interruptingBypassIndex); + int blockedByOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int blockingOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int interruptedByOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int interruptingOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.blockedBy != null) { + buf.setIntLE(blockedByOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.blockedBy.length > 4096000) { + throw ProtocolException.arrayTooLong("BlockedBy", this.blockedBy.length, 4096000); + } + + VarInt.write(buf, this.blockedBy.length); + + for (InteractionType item : this.blockedBy) { + buf.writeByte(item.getValue()); + } + } else { + buf.setIntLE(blockedByOffsetSlot, -1); + } + + if (this.blocking != null) { + buf.setIntLE(blockingOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.blocking.length > 4096000) { + throw ProtocolException.arrayTooLong("Blocking", this.blocking.length, 4096000); + } + + VarInt.write(buf, this.blocking.length); + + for (InteractionType item : this.blocking) { + buf.writeByte(item.getValue()); + } + } else { + buf.setIntLE(blockingOffsetSlot, -1); + } + + if (this.interruptedBy != null) { + buf.setIntLE(interruptedByOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.interruptedBy.length > 4096000) { + throw ProtocolException.arrayTooLong("InterruptedBy", this.interruptedBy.length, 4096000); + } + + VarInt.write(buf, this.interruptedBy.length); + + for (InteractionType item : this.interruptedBy) { + buf.writeByte(item.getValue()); + } + } else { + buf.setIntLE(interruptedByOffsetSlot, -1); + } + + if (this.interrupting != null) { + buf.setIntLE(interruptingOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.interrupting.length > 4096000) { + throw ProtocolException.arrayTooLong("Interrupting", this.interrupting.length, 4096000); + } + + VarInt.write(buf, this.interrupting.length); + + for (InteractionType item : this.interrupting) { + buf.writeByte(item.getValue()); + } + } else { + buf.setIntLE(interruptingOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 33; + if (this.blockedBy != null) { + size += VarInt.size(this.blockedBy.length) + this.blockedBy.length * 1; + } + + if (this.blocking != null) { + size += VarInt.size(this.blocking.length) + this.blocking.length * 1; + } + + if (this.interruptedBy != null) { + size += VarInt.size(this.interruptedBy.length) + this.interruptedBy.length * 1; + } + + if (this.interrupting != null) { + size += VarInt.size(this.interrupting.length) + this.interrupting.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 33) { + return ValidationResult.error("Buffer too small: expected at least 33 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int blockedByOffset = buffer.getIntLE(offset + 17); + if (blockedByOffset < 0) { + return ValidationResult.error("Invalid offset for BlockedBy"); + } + + int pos = offset + 33 + blockedByOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BlockedBy"); + } + + int blockedByCount = VarInt.peek(buffer, pos); + if (blockedByCount < 0) { + return ValidationResult.error("Invalid array count for BlockedBy"); + } + + if (blockedByCount > 4096000) { + return ValidationResult.error("BlockedBy exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += blockedByCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading BlockedBy"); + } + } + + if ((nullBits & 2) != 0) { + int blockingOffset = buffer.getIntLE(offset + 21); + if (blockingOffset < 0) { + return ValidationResult.error("Invalid offset for Blocking"); + } + + int posx = offset + 33 + blockingOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Blocking"); + } + + int blockingCount = VarInt.peek(buffer, posx); + if (blockingCount < 0) { + return ValidationResult.error("Invalid array count for Blocking"); + } + + if (blockingCount > 4096000) { + return ValidationResult.error("Blocking exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += blockingCount * 1; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Blocking"); + } + } + + if ((nullBits & 4) != 0) { + int interruptedByOffset = buffer.getIntLE(offset + 25); + if (interruptedByOffset < 0) { + return ValidationResult.error("Invalid offset for InterruptedBy"); + } + + int posxx = offset + 33 + interruptedByOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for InterruptedBy"); + } + + int interruptedByCount = VarInt.peek(buffer, posxx); + if (interruptedByCount < 0) { + return ValidationResult.error("Invalid array count for InterruptedBy"); + } + + if (interruptedByCount > 4096000) { + return ValidationResult.error("InterruptedBy exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += interruptedByCount * 1; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading InterruptedBy"); + } + } + + if ((nullBits & 8) != 0) { + int interruptingOffset = buffer.getIntLE(offset + 29); + if (interruptingOffset < 0) { + return ValidationResult.error("Invalid offset for Interrupting"); + } + + int posxxx = offset + 33 + interruptingOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Interrupting"); + } + + int interruptingCount = VarInt.peek(buffer, posxxx); + if (interruptingCount < 0) { + return ValidationResult.error("Invalid array count for Interrupting"); + } + + if (interruptingCount > 4096000) { + return ValidationResult.error("Interrupting exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += interruptingCount * 1; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Interrupting"); + } + } + + return ValidationResult.OK; + } + } + + public InteractionRules clone() { + InteractionRules copy = new InteractionRules(); + copy.blockedBy = this.blockedBy != null ? Arrays.copyOf(this.blockedBy, this.blockedBy.length) : null; + copy.blocking = this.blocking != null ? Arrays.copyOf(this.blocking, this.blocking.length) : null; + copy.interruptedBy = this.interruptedBy != null ? Arrays.copyOf(this.interruptedBy, this.interruptedBy.length) : null; + copy.interrupting = this.interrupting != null ? Arrays.copyOf(this.interrupting, this.interrupting.length) : null; + copy.blockedByBypassIndex = this.blockedByBypassIndex; + copy.blockingBypassIndex = this.blockingBypassIndex; + copy.interruptedByBypassIndex = this.interruptedByBypassIndex; + copy.interruptingBypassIndex = this.interruptingBypassIndex; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof InteractionRules other) + ? false + : Arrays.equals((Object[])this.blockedBy, (Object[])other.blockedBy) + && Arrays.equals((Object[])this.blocking, (Object[])other.blocking) + && Arrays.equals((Object[])this.interruptedBy, (Object[])other.interruptedBy) + && Arrays.equals((Object[])this.interrupting, (Object[])other.interrupting) + && this.blockedByBypassIndex == other.blockedByBypassIndex + && this.blockingBypassIndex == other.blockingBypassIndex + && this.interruptedByBypassIndex == other.interruptedByBypassIndex + && this.interruptingBypassIndex == other.interruptingBypassIndex; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode((Object[])this.blockedBy); + result = 31 * result + Arrays.hashCode((Object[])this.blocking); + result = 31 * result + Arrays.hashCode((Object[])this.interruptedBy); + result = 31 * result + Arrays.hashCode((Object[])this.interrupting); + result = 31 * result + Integer.hashCode(this.blockedByBypassIndex); + result = 31 * result + Integer.hashCode(this.blockingBypassIndex); + result = 31 * result + Integer.hashCode(this.interruptedByBypassIndex); + return 31 * result + Integer.hashCode(this.interruptingBypassIndex); + } +} diff --git a/src/com/hypixel/hytale/protocol/InteractionSettings.java b/src/com/hypixel/hytale/protocol/InteractionSettings.java new file mode 100644 index 0000000..fb2a8a9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/InteractionSettings.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class InteractionSettings { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean allowSkipOnClick; + + public InteractionSettings() { + } + + public InteractionSettings(boolean allowSkipOnClick) { + this.allowSkipOnClick = allowSkipOnClick; + } + + public InteractionSettings(@Nonnull InteractionSettings other) { + this.allowSkipOnClick = other.allowSkipOnClick; + } + + @Nonnull + public static InteractionSettings deserialize(@Nonnull ByteBuf buf, int offset) { + InteractionSettings obj = new InteractionSettings(); + obj.allowSkipOnClick = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.allowSkipOnClick ? 1 : 0); + } + + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public InteractionSettings clone() { + InteractionSettings copy = new InteractionSettings(); + copy.allowSkipOnClick = this.allowSkipOnClick; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof InteractionSettings other ? this.allowSkipOnClick == other.allowSkipOnClick : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.allowSkipOnClick); + } +} diff --git a/src/com/hypixel/hytale/protocol/InteractionState.java b/src/com/hypixel/hytale/protocol/InteractionState.java new file mode 100644 index 0000000..de338c1 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/InteractionState.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum InteractionState { + Finished(0), + Skip(1), + ItemChanged(2), + Failed(3), + NotFinished(4); + + public static final InteractionState[] VALUES = values(); + private final int value; + + private InteractionState(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static InteractionState fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("InteractionState", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/InteractionSyncData.java b/src/com/hypixel/hytale/protocol/InteractionSyncData.java new file mode 100644 index 0000000..6c344af --- /dev/null +++ b/src/com/hypixel/hytale/protocol/InteractionSyncData.java @@ -0,0 +1,592 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InteractionSyncData { + public static final int NULLABLE_BIT_FIELD_SIZE = 2; + public static final int FIXED_BLOCK_SIZE = 157; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 165; + public static final int MAX_SIZE = 237568175; + @Nonnull + public InteractionState state = InteractionState.Finished; + public float progress; + public int operationCounter; + public int rootInteraction; + public int totalForks; + public int entityId; + public int enteredRootInteraction = Integer.MIN_VALUE; + @Nullable + public BlockPosition blockPosition; + @Nonnull + public BlockFace blockFace = BlockFace.None; + @Nullable + public BlockRotation blockRotation; + public int placedBlockId = Integer.MIN_VALUE; + public float chargeValue = -1.0F; + @Nullable + public Map forkCounts; + public int chainingIndex = -1; + public int flagIndex = -1; + @Nullable + public SelectedHitEntity[] hitEntities; + @Nullable + public Position attackerPos; + @Nullable + public Direction attackerRot; + @Nullable + public Position raycastHit; + public float raycastDistance; + @Nullable + public Vector3f raycastNormal; + @Nonnull + public MovementDirection movementDirection = MovementDirection.None; + @Nonnull + public ApplyForceState applyForceState = ApplyForceState.Waiting; + public int nextLabel; + @Nullable + public UUID generatedUUID = null; + + public InteractionSyncData() { + } + + public InteractionSyncData( + @Nonnull InteractionState state, + float progress, + int operationCounter, + int rootInteraction, + int totalForks, + int entityId, + int enteredRootInteraction, + @Nullable BlockPosition blockPosition, + @Nonnull BlockFace blockFace, + @Nullable BlockRotation blockRotation, + int placedBlockId, + float chargeValue, + @Nullable Map forkCounts, + int chainingIndex, + int flagIndex, + @Nullable SelectedHitEntity[] hitEntities, + @Nullable Position attackerPos, + @Nullable Direction attackerRot, + @Nullable Position raycastHit, + float raycastDistance, + @Nullable Vector3f raycastNormal, + @Nonnull MovementDirection movementDirection, + @Nonnull ApplyForceState applyForceState, + int nextLabel, + @Nullable UUID generatedUUID + ) { + this.state = state; + this.progress = progress; + this.operationCounter = operationCounter; + this.rootInteraction = rootInteraction; + this.totalForks = totalForks; + this.entityId = entityId; + this.enteredRootInteraction = enteredRootInteraction; + this.blockPosition = blockPosition; + this.blockFace = blockFace; + this.blockRotation = blockRotation; + this.placedBlockId = placedBlockId; + this.chargeValue = chargeValue; + this.forkCounts = forkCounts; + this.chainingIndex = chainingIndex; + this.flagIndex = flagIndex; + this.hitEntities = hitEntities; + this.attackerPos = attackerPos; + this.attackerRot = attackerRot; + this.raycastHit = raycastHit; + this.raycastDistance = raycastDistance; + this.raycastNormal = raycastNormal; + this.movementDirection = movementDirection; + this.applyForceState = applyForceState; + this.nextLabel = nextLabel; + this.generatedUUID = generatedUUID; + } + + public InteractionSyncData(@Nonnull InteractionSyncData other) { + this.state = other.state; + this.progress = other.progress; + this.operationCounter = other.operationCounter; + this.rootInteraction = other.rootInteraction; + this.totalForks = other.totalForks; + this.entityId = other.entityId; + this.enteredRootInteraction = other.enteredRootInteraction; + this.blockPosition = other.blockPosition; + this.blockFace = other.blockFace; + this.blockRotation = other.blockRotation; + this.placedBlockId = other.placedBlockId; + this.chargeValue = other.chargeValue; + this.forkCounts = other.forkCounts; + this.chainingIndex = other.chainingIndex; + this.flagIndex = other.flagIndex; + this.hitEntities = other.hitEntities; + this.attackerPos = other.attackerPos; + this.attackerRot = other.attackerRot; + this.raycastHit = other.raycastHit; + this.raycastDistance = other.raycastDistance; + this.raycastNormal = other.raycastNormal; + this.movementDirection = other.movementDirection; + this.applyForceState = other.applyForceState; + this.nextLabel = other.nextLabel; + this.generatedUUID = other.generatedUUID; + } + + @Nonnull + public static InteractionSyncData deserialize(@Nonnull ByteBuf buf, int offset) { + InteractionSyncData obj = new InteractionSyncData(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + obj.state = InteractionState.fromValue(buf.getByte(offset + 2)); + obj.progress = buf.getFloatLE(offset + 3); + obj.operationCounter = buf.getIntLE(offset + 7); + obj.rootInteraction = buf.getIntLE(offset + 11); + obj.totalForks = buf.getIntLE(offset + 15); + obj.entityId = buf.getIntLE(offset + 19); + obj.enteredRootInteraction = buf.getIntLE(offset + 23); + if ((nullBits[0] & 1) != 0) { + obj.blockPosition = BlockPosition.deserialize(buf, offset + 27); + } + + obj.blockFace = BlockFace.fromValue(buf.getByte(offset + 39)); + if ((nullBits[0] & 2) != 0) { + obj.blockRotation = BlockRotation.deserialize(buf, offset + 40); + } + + obj.placedBlockId = buf.getIntLE(offset + 43); + obj.chargeValue = buf.getFloatLE(offset + 47); + obj.chainingIndex = buf.getIntLE(offset + 51); + obj.flagIndex = buf.getIntLE(offset + 55); + if ((nullBits[0] & 16) != 0) { + obj.attackerPos = Position.deserialize(buf, offset + 59); + } + + if ((nullBits[0] & 32) != 0) { + obj.attackerRot = Direction.deserialize(buf, offset + 83); + } + + if ((nullBits[0] & 64) != 0) { + obj.raycastHit = Position.deserialize(buf, offset + 95); + } + + obj.raycastDistance = buf.getFloatLE(offset + 119); + if ((nullBits[0] & 128) != 0) { + obj.raycastNormal = Vector3f.deserialize(buf, offset + 123); + } + + obj.movementDirection = MovementDirection.fromValue(buf.getByte(offset + 135)); + obj.applyForceState = ApplyForceState.fromValue(buf.getByte(offset + 136)); + obj.nextLabel = buf.getIntLE(offset + 137); + if ((nullBits[1] & 1) != 0) { + obj.generatedUUID = PacketIO.readUUID(buf, offset + 141); + } + + if ((nullBits[0] & 4) != 0) { + int varPos0 = offset + 165 + buf.getIntLE(offset + 157); + int forkCountsCount = VarInt.peek(buf, varPos0); + if (forkCountsCount < 0) { + throw ProtocolException.negativeLength("ForkCounts", forkCountsCount); + } + + if (forkCountsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("ForkCounts", forkCountsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + obj.forkCounts = new HashMap<>(forkCountsCount); + int dictPos = varPos0 + varIntLen; + + for (int i = 0; i < forkCountsCount; i++) { + InteractionType key = InteractionType.fromValue(buf.getByte(dictPos)); + int val = buf.getIntLE(++dictPos); + dictPos += 4; + if (obj.forkCounts.put(key, val) != null) { + throw ProtocolException.duplicateKey("forkCounts", key); + } + } + } + + if ((nullBits[0] & 8) != 0) { + int varPos1 = offset + 165 + buf.getIntLE(offset + 161); + int hitEntitiesCount = VarInt.peek(buf, varPos1); + if (hitEntitiesCount < 0) { + throw ProtocolException.negativeLength("HitEntities", hitEntitiesCount); + } + + if (hitEntitiesCount > 4096000) { + throw ProtocolException.arrayTooLong("HitEntities", hitEntitiesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + hitEntitiesCount * 53L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("HitEntities", varPos1 + varIntLen + hitEntitiesCount * 53, buf.readableBytes()); + } + + obj.hitEntities = new SelectedHitEntity[hitEntitiesCount]; + int elemPos = varPos1 + varIntLen; + + for (int ix = 0; ix < hitEntitiesCount; ix++) { + obj.hitEntities[ix] = SelectedHitEntity.deserialize(buf, elemPos); + elemPos += SelectedHitEntity.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + int maxEnd = 165; + if ((nullBits[0] & 4) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 157); + int pos0 = offset + 165 + fieldOffset0; + int dictLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < dictLen; i++) { + pos0 = ++pos0 + 4; + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits[0] & 8) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 161); + int pos1 = offset + 165 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += SelectedHitEntity.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte[] nullBits = new byte[2]; + if (this.blockPosition != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.blockRotation != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.forkCounts != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.hitEntities != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.attackerPos != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.attackerRot != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.raycastHit != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.raycastNormal != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.generatedUUID != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + buf.writeBytes(nullBits); + buf.writeByte(this.state.getValue()); + buf.writeFloatLE(this.progress); + buf.writeIntLE(this.operationCounter); + buf.writeIntLE(this.rootInteraction); + buf.writeIntLE(this.totalForks); + buf.writeIntLE(this.entityId); + buf.writeIntLE(this.enteredRootInteraction); + if (this.blockPosition != null) { + this.blockPosition.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeByte(this.blockFace.getValue()); + if (this.blockRotation != null) { + this.blockRotation.serialize(buf); + } else { + buf.writeZero(3); + } + + buf.writeIntLE(this.placedBlockId); + buf.writeFloatLE(this.chargeValue); + buf.writeIntLE(this.chainingIndex); + buf.writeIntLE(this.flagIndex); + if (this.attackerPos != null) { + this.attackerPos.serialize(buf); + } else { + buf.writeZero(24); + } + + if (this.attackerRot != null) { + this.attackerRot.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.raycastHit != null) { + this.raycastHit.serialize(buf); + } else { + buf.writeZero(24); + } + + buf.writeFloatLE(this.raycastDistance); + if (this.raycastNormal != null) { + this.raycastNormal.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeByte(this.movementDirection.getValue()); + buf.writeByte(this.applyForceState.getValue()); + buf.writeIntLE(this.nextLabel); + if (this.generatedUUID != null) { + PacketIO.writeUUID(buf, this.generatedUUID); + } else { + buf.writeZero(16); + } + + int forkCountsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int hitEntitiesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.forkCounts != null) { + buf.setIntLE(forkCountsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.forkCounts.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("ForkCounts", this.forkCounts.size(), 4096000); + } + + VarInt.write(buf, this.forkCounts.size()); + + for (Entry e : this.forkCounts.entrySet()) { + buf.writeByte(e.getKey().getValue()); + buf.writeIntLE(e.getValue()); + } + } else { + buf.setIntLE(forkCountsOffsetSlot, -1); + } + + if (this.hitEntities != null) { + buf.setIntLE(hitEntitiesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.hitEntities.length > 4096000) { + throw ProtocolException.arrayTooLong("HitEntities", this.hitEntities.length, 4096000); + } + + VarInt.write(buf, this.hitEntities.length); + + for (SelectedHitEntity item : this.hitEntities) { + item.serialize(buf); + } + } else { + buf.setIntLE(hitEntitiesOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 165; + if (this.forkCounts != null) { + size += VarInt.size(this.forkCounts.size()) + this.forkCounts.size() * 5; + } + + if (this.hitEntities != null) { + size += VarInt.size(this.hitEntities.length) + this.hitEntities.length * 53; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 165) { + return ValidationResult.error("Buffer too small: expected at least 165 bytes"); + } else { + byte[] nullBits = PacketIO.readBytes(buffer, offset, 2); + if ((nullBits[0] & 4) != 0) { + int forkCountsOffset = buffer.getIntLE(offset + 157); + if (forkCountsOffset < 0) { + return ValidationResult.error("Invalid offset for ForkCounts"); + } + + int pos = offset + 165 + forkCountsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ForkCounts"); + } + + int forkCountsCount = VarInt.peek(buffer, pos); + if (forkCountsCount < 0) { + return ValidationResult.error("Invalid dictionary count for ForkCounts"); + } + + if (forkCountsCount > 4096000) { + return ValidationResult.error("ForkCounts exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < forkCountsCount; i++) { + pos = ++pos + 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + if ((nullBits[0] & 8) != 0) { + int hitEntitiesOffset = buffer.getIntLE(offset + 161); + if (hitEntitiesOffset < 0) { + return ValidationResult.error("Invalid offset for HitEntities"); + } + + int posx = offset + 165 + hitEntitiesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for HitEntities"); + } + + int hitEntitiesCount = VarInt.peek(buffer, posx); + if (hitEntitiesCount < 0) { + return ValidationResult.error("Invalid array count for HitEntities"); + } + + if (hitEntitiesCount > 4096000) { + return ValidationResult.error("HitEntities exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += hitEntitiesCount * 53; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading HitEntities"); + } + } + + return ValidationResult.OK; + } + } + + public InteractionSyncData clone() { + InteractionSyncData copy = new InteractionSyncData(); + copy.state = this.state; + copy.progress = this.progress; + copy.operationCounter = this.operationCounter; + copy.rootInteraction = this.rootInteraction; + copy.totalForks = this.totalForks; + copy.entityId = this.entityId; + copy.enteredRootInteraction = this.enteredRootInteraction; + copy.blockPosition = this.blockPosition != null ? this.blockPosition.clone() : null; + copy.blockFace = this.blockFace; + copy.blockRotation = this.blockRotation != null ? this.blockRotation.clone() : null; + copy.placedBlockId = this.placedBlockId; + copy.chargeValue = this.chargeValue; + copy.forkCounts = this.forkCounts != null ? new HashMap<>(this.forkCounts) : null; + copy.chainingIndex = this.chainingIndex; + copy.flagIndex = this.flagIndex; + copy.hitEntities = this.hitEntities != null ? Arrays.stream(this.hitEntities).map(e -> e.clone()).toArray(SelectedHitEntity[]::new) : null; + copy.attackerPos = this.attackerPos != null ? this.attackerPos.clone() : null; + copy.attackerRot = this.attackerRot != null ? this.attackerRot.clone() : null; + copy.raycastHit = this.raycastHit != null ? this.raycastHit.clone() : null; + copy.raycastDistance = this.raycastDistance; + copy.raycastNormal = this.raycastNormal != null ? this.raycastNormal.clone() : null; + copy.movementDirection = this.movementDirection; + copy.applyForceState = this.applyForceState; + copy.nextLabel = this.nextLabel; + copy.generatedUUID = this.generatedUUID; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof InteractionSyncData other) + ? false + : Objects.equals(this.state, other.state) + && this.progress == other.progress + && this.operationCounter == other.operationCounter + && this.rootInteraction == other.rootInteraction + && this.totalForks == other.totalForks + && this.entityId == other.entityId + && this.enteredRootInteraction == other.enteredRootInteraction + && Objects.equals(this.blockPosition, other.blockPosition) + && Objects.equals(this.blockFace, other.blockFace) + && Objects.equals(this.blockRotation, other.blockRotation) + && this.placedBlockId == other.placedBlockId + && this.chargeValue == other.chargeValue + && Objects.equals(this.forkCounts, other.forkCounts) + && this.chainingIndex == other.chainingIndex + && this.flagIndex == other.flagIndex + && Arrays.equals((Object[])this.hitEntities, (Object[])other.hitEntities) + && Objects.equals(this.attackerPos, other.attackerPos) + && Objects.equals(this.attackerRot, other.attackerRot) + && Objects.equals(this.raycastHit, other.raycastHit) + && this.raycastDistance == other.raycastDistance + && Objects.equals(this.raycastNormal, other.raycastNormal) + && Objects.equals(this.movementDirection, other.movementDirection) + && Objects.equals(this.applyForceState, other.applyForceState) + && this.nextLabel == other.nextLabel + && Objects.equals(this.generatedUUID, other.generatedUUID); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.state); + result = 31 * result + Float.hashCode(this.progress); + result = 31 * result + Integer.hashCode(this.operationCounter); + result = 31 * result + Integer.hashCode(this.rootInteraction); + result = 31 * result + Integer.hashCode(this.totalForks); + result = 31 * result + Integer.hashCode(this.entityId); + result = 31 * result + Integer.hashCode(this.enteredRootInteraction); + result = 31 * result + Objects.hashCode(this.blockPosition); + result = 31 * result + Objects.hashCode(this.blockFace); + result = 31 * result + Objects.hashCode(this.blockRotation); + result = 31 * result + Integer.hashCode(this.placedBlockId); + result = 31 * result + Float.hashCode(this.chargeValue); + result = 31 * result + Objects.hashCode(this.forkCounts); + result = 31 * result + Integer.hashCode(this.chainingIndex); + result = 31 * result + Integer.hashCode(this.flagIndex); + result = 31 * result + Arrays.hashCode((Object[])this.hitEntities); + result = 31 * result + Objects.hashCode(this.attackerPos); + result = 31 * result + Objects.hashCode(this.attackerRot); + result = 31 * result + Objects.hashCode(this.raycastHit); + result = 31 * result + Float.hashCode(this.raycastDistance); + result = 31 * result + Objects.hashCode(this.raycastNormal); + result = 31 * result + Objects.hashCode(this.movementDirection); + result = 31 * result + Objects.hashCode(this.applyForceState); + result = 31 * result + Integer.hashCode(this.nextLabel); + return 31 * result + Objects.hashCode(this.generatedUUID); + } +} diff --git a/src/com/hypixel/hytale/protocol/InteractionTarget.java b/src/com/hypixel/hytale/protocol/InteractionTarget.java new file mode 100644 index 0000000..e4cb76e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/InteractionTarget.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum InteractionTarget { + User(0), + Owner(1), + Target(2); + + public static final InteractionTarget[] VALUES = values(); + private final int value; + + private InteractionTarget(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static InteractionTarget fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("InteractionTarget", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/InteractionType.java b/src/com/hypixel/hytale/protocol/InteractionType.java new file mode 100644 index 0000000..f59509e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/InteractionType.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum InteractionType { + Primary(0), + Secondary(1), + Ability1(2), + Ability2(3), + Ability3(4), + Use(5), + Pick(6), + Pickup(7), + CollisionEnter(8), + CollisionLeave(9), + Collision(10), + EntityStatEffect(11), + SwapTo(12), + SwapFrom(13), + Death(14), + Wielding(15), + ProjectileSpawn(16), + ProjectileHit(17), + ProjectileMiss(18), + ProjectileBounce(19), + Held(20), + HeldOffhand(21), + Equipped(22), + Dodge(23), + GameModeSwap(24); + + public static final InteractionType[] VALUES = values(); + private final int value; + + private InteractionType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static InteractionType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("InteractionType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/IntersectionHighlight.java b/src/com/hypixel/hytale/protocol/IntersectionHighlight.java new file mode 100644 index 0000000..daa14f4 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/IntersectionHighlight.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class IntersectionHighlight { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public float highlightThreshold; + @Nullable + public Color highlightColor; + + public IntersectionHighlight() { + } + + public IntersectionHighlight(float highlightThreshold, @Nullable Color highlightColor) { + this.highlightThreshold = highlightThreshold; + this.highlightColor = highlightColor; + } + + public IntersectionHighlight(@Nonnull IntersectionHighlight other) { + this.highlightThreshold = other.highlightThreshold; + this.highlightColor = other.highlightColor; + } + + @Nonnull + public static IntersectionHighlight deserialize(@Nonnull ByteBuf buf, int offset) { + IntersectionHighlight obj = new IntersectionHighlight(); + byte nullBits = buf.getByte(offset); + obj.highlightThreshold = buf.getFloatLE(offset + 1); + if ((nullBits & 1) != 0) { + obj.highlightColor = Color.deserialize(buf, offset + 5); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.highlightColor != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.highlightThreshold); + if (this.highlightColor != null) { + this.highlightColor.serialize(buf); + } else { + buf.writeZero(3); + } + } + + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public IntersectionHighlight clone() { + IntersectionHighlight copy = new IntersectionHighlight(); + copy.highlightThreshold = this.highlightThreshold; + copy.highlightColor = this.highlightColor != null ? this.highlightColor.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof IntersectionHighlight other) + ? false + : this.highlightThreshold == other.highlightThreshold && Objects.equals(this.highlightColor, other.highlightColor); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.highlightThreshold, this.highlightColor); + } +} diff --git a/src/com/hypixel/hytale/protocol/InventoryActionType.java b/src/com/hypixel/hytale/protocol/InventoryActionType.java new file mode 100644 index 0000000..4942b16 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/InventoryActionType.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum InventoryActionType { + TakeAll(0), + PutAll(1), + QuickStack(2), + Sort(3); + + public static final InventoryActionType[] VALUES = values(); + private final int value; + + private InventoryActionType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static InventoryActionType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("InventoryActionType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/InventorySection.java b/src/com/hypixel/hytale/protocol/InventorySection.java new file mode 100644 index 0000000..9e5835e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/InventorySection.java @@ -0,0 +1,184 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InventorySection { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 3; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 3; + public static final int MAX_SIZE = 1677721600; + @Nullable + public Map items; + public short capacity; + + public InventorySection() { + } + + public InventorySection(@Nullable Map items, short capacity) { + this.items = items; + this.capacity = capacity; + } + + public InventorySection(@Nonnull InventorySection other) { + this.items = other.items; + this.capacity = other.capacity; + } + + @Nonnull + public static InventorySection deserialize(@Nonnull ByteBuf buf, int offset) { + InventorySection obj = new InventorySection(); + byte nullBits = buf.getByte(offset); + obj.capacity = buf.getShortLE(offset + 1); + int pos = offset + 3; + if ((nullBits & 1) != 0) { + int itemsCount = VarInt.peek(buf, pos); + if (itemsCount < 0) { + throw ProtocolException.negativeLength("Items", itemsCount); + } + + if (itemsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Items", itemsCount, 4096000); + } + + pos += VarInt.size(itemsCount); + obj.items = new HashMap<>(itemsCount); + + for (int i = 0; i < itemsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + ItemWithAllMetadata val = ItemWithAllMetadata.deserialize(buf, pos); + pos += ItemWithAllMetadata.computeBytesConsumed(buf, pos); + if (obj.items.put(key, val) != null) { + throw ProtocolException.duplicateKey("items", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 3; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += ItemWithAllMetadata.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.items != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeShortLE(this.capacity); + if (this.items != null) { + if (this.items.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Items", this.items.size(), 4096000); + } + + VarInt.write(buf, this.items.size()); + + for (Entry e : this.items.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + public int computeSize() { + int size = 3; + if (this.items != null) { + int itemsSize = 0; + + for (Entry kvp : this.items.entrySet()) { + itemsSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.items.size()) + itemsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 3) { + return ValidationResult.error("Buffer too small: expected at least 3 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 3; + if ((nullBits & 1) != 0) { + int itemsCount = VarInt.peek(buffer, pos); + if (itemsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Items"); + } + + if (itemsCount > 4096000) { + return ValidationResult.error("Items exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < itemsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += ItemWithAllMetadata.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public InventorySection clone() { + InventorySection copy = new InventorySection(); + if (this.items != null) { + Map m = new HashMap<>(); + + for (Entry e : this.items.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.items = m; + } + + copy.capacity = this.capacity; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof InventorySection other) ? false : Objects.equals(this.items, other.items) && this.capacity == other.capacity; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.items, this.capacity); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemAnimation.java b/src/com/hypixel/hytale/protocol/ItemAnimation.java new file mode 100644 index 0000000..b110f00 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemAnimation.java @@ -0,0 +1,507 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemAnimation { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 12; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 32; + public static final int MAX_SIZE = 81920057; + @Nullable + public String thirdPerson; + @Nullable + public String thirdPersonMoving; + @Nullable + public String thirdPersonFace; + @Nullable + public String firstPerson; + @Nullable + public String firstPersonOverride; + public boolean keepPreviousFirstPersonAnimation; + public float speed; + public float blendingDuration = 0.2F; + public boolean looping; + public boolean clipsGeometry; + + public ItemAnimation() { + } + + public ItemAnimation( + @Nullable String thirdPerson, + @Nullable String thirdPersonMoving, + @Nullable String thirdPersonFace, + @Nullable String firstPerson, + @Nullable String firstPersonOverride, + boolean keepPreviousFirstPersonAnimation, + float speed, + float blendingDuration, + boolean looping, + boolean clipsGeometry + ) { + this.thirdPerson = thirdPerson; + this.thirdPersonMoving = thirdPersonMoving; + this.thirdPersonFace = thirdPersonFace; + this.firstPerson = firstPerson; + this.firstPersonOverride = firstPersonOverride; + this.keepPreviousFirstPersonAnimation = keepPreviousFirstPersonAnimation; + this.speed = speed; + this.blendingDuration = blendingDuration; + this.looping = looping; + this.clipsGeometry = clipsGeometry; + } + + public ItemAnimation(@Nonnull ItemAnimation other) { + this.thirdPerson = other.thirdPerson; + this.thirdPersonMoving = other.thirdPersonMoving; + this.thirdPersonFace = other.thirdPersonFace; + this.firstPerson = other.firstPerson; + this.firstPersonOverride = other.firstPersonOverride; + this.keepPreviousFirstPersonAnimation = other.keepPreviousFirstPersonAnimation; + this.speed = other.speed; + this.blendingDuration = other.blendingDuration; + this.looping = other.looping; + this.clipsGeometry = other.clipsGeometry; + } + + @Nonnull + public static ItemAnimation deserialize(@Nonnull ByteBuf buf, int offset) { + ItemAnimation obj = new ItemAnimation(); + byte nullBits = buf.getByte(offset); + obj.keepPreviousFirstPersonAnimation = buf.getByte(offset + 1) != 0; + obj.speed = buf.getFloatLE(offset + 2); + obj.blendingDuration = buf.getFloatLE(offset + 6); + obj.looping = buf.getByte(offset + 10) != 0; + obj.clipsGeometry = buf.getByte(offset + 11) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 32 + buf.getIntLE(offset + 12); + int thirdPersonLen = VarInt.peek(buf, varPos0); + if (thirdPersonLen < 0) { + throw ProtocolException.negativeLength("ThirdPerson", thirdPersonLen); + } + + if (thirdPersonLen > 4096000) { + throw ProtocolException.stringTooLong("ThirdPerson", thirdPersonLen, 4096000); + } + + obj.thirdPerson = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 32 + buf.getIntLE(offset + 16); + int thirdPersonMovingLen = VarInt.peek(buf, varPos1); + if (thirdPersonMovingLen < 0) { + throw ProtocolException.negativeLength("ThirdPersonMoving", thirdPersonMovingLen); + } + + if (thirdPersonMovingLen > 4096000) { + throw ProtocolException.stringTooLong("ThirdPersonMoving", thirdPersonMovingLen, 4096000); + } + + obj.thirdPersonMoving = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 32 + buf.getIntLE(offset + 20); + int thirdPersonFaceLen = VarInt.peek(buf, varPos2); + if (thirdPersonFaceLen < 0) { + throw ProtocolException.negativeLength("ThirdPersonFace", thirdPersonFaceLen); + } + + if (thirdPersonFaceLen > 4096000) { + throw ProtocolException.stringTooLong("ThirdPersonFace", thirdPersonFaceLen, 4096000); + } + + obj.thirdPersonFace = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 32 + buf.getIntLE(offset + 24); + int firstPersonLen = VarInt.peek(buf, varPos3); + if (firstPersonLen < 0) { + throw ProtocolException.negativeLength("FirstPerson", firstPersonLen); + } + + if (firstPersonLen > 4096000) { + throw ProtocolException.stringTooLong("FirstPerson", firstPersonLen, 4096000); + } + + obj.firstPerson = PacketIO.readVarString(buf, varPos3, PacketIO.UTF8); + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 32 + buf.getIntLE(offset + 28); + int firstPersonOverrideLen = VarInt.peek(buf, varPos4); + if (firstPersonOverrideLen < 0) { + throw ProtocolException.negativeLength("FirstPersonOverride", firstPersonOverrideLen); + } + + if (firstPersonOverrideLen > 4096000) { + throw ProtocolException.stringTooLong("FirstPersonOverride", firstPersonOverrideLen, 4096000); + } + + obj.firstPersonOverride = PacketIO.readVarString(buf, varPos4, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 32; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 12); + int pos0 = offset + 32 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 16); + int pos1 = offset + 32 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 20); + int pos2 = offset + 32 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 24); + int pos3 = offset + 32 + fieldOffset3; + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 28); + int pos4 = offset + 32 + fieldOffset4; + int sl = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4) + sl; + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.thirdPerson != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.thirdPersonMoving != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.thirdPersonFace != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.firstPerson != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.firstPersonOverride != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.keepPreviousFirstPersonAnimation ? 1 : 0); + buf.writeFloatLE(this.speed); + buf.writeFloatLE(this.blendingDuration); + buf.writeByte(this.looping ? 1 : 0); + buf.writeByte(this.clipsGeometry ? 1 : 0); + int thirdPersonOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int thirdPersonMovingOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int thirdPersonFaceOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int firstPersonOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int firstPersonOverrideOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.thirdPerson != null) { + buf.setIntLE(thirdPersonOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.thirdPerson, 4096000); + } else { + buf.setIntLE(thirdPersonOffsetSlot, -1); + } + + if (this.thirdPersonMoving != null) { + buf.setIntLE(thirdPersonMovingOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.thirdPersonMoving, 4096000); + } else { + buf.setIntLE(thirdPersonMovingOffsetSlot, -1); + } + + if (this.thirdPersonFace != null) { + buf.setIntLE(thirdPersonFaceOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.thirdPersonFace, 4096000); + } else { + buf.setIntLE(thirdPersonFaceOffsetSlot, -1); + } + + if (this.firstPerson != null) { + buf.setIntLE(firstPersonOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.firstPerson, 4096000); + } else { + buf.setIntLE(firstPersonOffsetSlot, -1); + } + + if (this.firstPersonOverride != null) { + buf.setIntLE(firstPersonOverrideOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.firstPersonOverride, 4096000); + } else { + buf.setIntLE(firstPersonOverrideOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 32; + if (this.thirdPerson != null) { + size += PacketIO.stringSize(this.thirdPerson); + } + + if (this.thirdPersonMoving != null) { + size += PacketIO.stringSize(this.thirdPersonMoving); + } + + if (this.thirdPersonFace != null) { + size += PacketIO.stringSize(this.thirdPersonFace); + } + + if (this.firstPerson != null) { + size += PacketIO.stringSize(this.firstPerson); + } + + if (this.firstPersonOverride != null) { + size += PacketIO.stringSize(this.firstPersonOverride); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 32) { + return ValidationResult.error("Buffer too small: expected at least 32 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int thirdPersonOffset = buffer.getIntLE(offset + 12); + if (thirdPersonOffset < 0) { + return ValidationResult.error("Invalid offset for ThirdPerson"); + } + + int pos = offset + 32 + thirdPersonOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ThirdPerson"); + } + + int thirdPersonLen = VarInt.peek(buffer, pos); + if (thirdPersonLen < 0) { + return ValidationResult.error("Invalid string length for ThirdPerson"); + } + + if (thirdPersonLen > 4096000) { + return ValidationResult.error("ThirdPerson exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += thirdPersonLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ThirdPerson"); + } + } + + if ((nullBits & 2) != 0) { + int thirdPersonMovingOffset = buffer.getIntLE(offset + 16); + if (thirdPersonMovingOffset < 0) { + return ValidationResult.error("Invalid offset for ThirdPersonMoving"); + } + + int posx = offset + 32 + thirdPersonMovingOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ThirdPersonMoving"); + } + + int thirdPersonMovingLen = VarInt.peek(buffer, posx); + if (thirdPersonMovingLen < 0) { + return ValidationResult.error("Invalid string length for ThirdPersonMoving"); + } + + if (thirdPersonMovingLen > 4096000) { + return ValidationResult.error("ThirdPersonMoving exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += thirdPersonMovingLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ThirdPersonMoving"); + } + } + + if ((nullBits & 4) != 0) { + int thirdPersonFaceOffset = buffer.getIntLE(offset + 20); + if (thirdPersonFaceOffset < 0) { + return ValidationResult.error("Invalid offset for ThirdPersonFace"); + } + + int posxx = offset + 32 + thirdPersonFaceOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ThirdPersonFace"); + } + + int thirdPersonFaceLen = VarInt.peek(buffer, posxx); + if (thirdPersonFaceLen < 0) { + return ValidationResult.error("Invalid string length for ThirdPersonFace"); + } + + if (thirdPersonFaceLen > 4096000) { + return ValidationResult.error("ThirdPersonFace exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += thirdPersonFaceLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ThirdPersonFace"); + } + } + + if ((nullBits & 8) != 0) { + int firstPersonOffset = buffer.getIntLE(offset + 24); + if (firstPersonOffset < 0) { + return ValidationResult.error("Invalid offset for FirstPerson"); + } + + int posxxx = offset + 32 + firstPersonOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FirstPerson"); + } + + int firstPersonLen = VarInt.peek(buffer, posxxx); + if (firstPersonLen < 0) { + return ValidationResult.error("Invalid string length for FirstPerson"); + } + + if (firstPersonLen > 4096000) { + return ValidationResult.error("FirstPerson exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += firstPersonLen; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading FirstPerson"); + } + } + + if ((nullBits & 16) != 0) { + int firstPersonOverrideOffset = buffer.getIntLE(offset + 28); + if (firstPersonOverrideOffset < 0) { + return ValidationResult.error("Invalid offset for FirstPersonOverride"); + } + + int posxxxx = offset + 32 + firstPersonOverrideOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FirstPersonOverride"); + } + + int firstPersonOverrideLen = VarInt.peek(buffer, posxxxx); + if (firstPersonOverrideLen < 0) { + return ValidationResult.error("Invalid string length for FirstPersonOverride"); + } + + if (firstPersonOverrideLen > 4096000) { + return ValidationResult.error("FirstPersonOverride exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + posxxxx += firstPersonOverrideLen; + if (posxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading FirstPersonOverride"); + } + } + + return ValidationResult.OK; + } + } + + public ItemAnimation clone() { + ItemAnimation copy = new ItemAnimation(); + copy.thirdPerson = this.thirdPerson; + copy.thirdPersonMoving = this.thirdPersonMoving; + copy.thirdPersonFace = this.thirdPersonFace; + copy.firstPerson = this.firstPerson; + copy.firstPersonOverride = this.firstPersonOverride; + copy.keepPreviousFirstPersonAnimation = this.keepPreviousFirstPersonAnimation; + copy.speed = this.speed; + copy.blendingDuration = this.blendingDuration; + copy.looping = this.looping; + copy.clipsGeometry = this.clipsGeometry; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemAnimation other) + ? false + : Objects.equals(this.thirdPerson, other.thirdPerson) + && Objects.equals(this.thirdPersonMoving, other.thirdPersonMoving) + && Objects.equals(this.thirdPersonFace, other.thirdPersonFace) + && Objects.equals(this.firstPerson, other.firstPerson) + && Objects.equals(this.firstPersonOverride, other.firstPersonOverride) + && this.keepPreviousFirstPersonAnimation == other.keepPreviousFirstPersonAnimation + && this.speed == other.speed + && this.blendingDuration == other.blendingDuration + && this.looping == other.looping + && this.clipsGeometry == other.clipsGeometry; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.thirdPerson, + this.thirdPersonMoving, + this.thirdPersonFace, + this.firstPerson, + this.firstPersonOverride, + this.keepPreviousFirstPersonAnimation, + this.speed, + this.blendingDuration, + this.looping, + this.clipsGeometry + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemAppearanceCondition.java b/src/com/hypixel/hytale/protocol/ItemAppearanceCondition.java new file mode 100644 index 0000000..05e7f27 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemAppearanceCondition.java @@ -0,0 +1,584 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemAppearanceCondition { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 18; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 38; + public static final int MAX_SIZE = 1677721600; + @Nullable + public ModelParticle[] particles; + @Nullable + public ModelParticle[] firstPersonParticles; + @Nullable + public String model; + @Nullable + public String texture; + @Nullable + public String modelVFXId; + @Nullable + public FloatRange condition; + @Nonnull + public ValueType conditionValueType = ValueType.Percent; + public int localSoundEventId; + public int worldSoundEventId; + + public ItemAppearanceCondition() { + } + + public ItemAppearanceCondition( + @Nullable ModelParticle[] particles, + @Nullable ModelParticle[] firstPersonParticles, + @Nullable String model, + @Nullable String texture, + @Nullable String modelVFXId, + @Nullable FloatRange condition, + @Nonnull ValueType conditionValueType, + int localSoundEventId, + int worldSoundEventId + ) { + this.particles = particles; + this.firstPersonParticles = firstPersonParticles; + this.model = model; + this.texture = texture; + this.modelVFXId = modelVFXId; + this.condition = condition; + this.conditionValueType = conditionValueType; + this.localSoundEventId = localSoundEventId; + this.worldSoundEventId = worldSoundEventId; + } + + public ItemAppearanceCondition(@Nonnull ItemAppearanceCondition other) { + this.particles = other.particles; + this.firstPersonParticles = other.firstPersonParticles; + this.model = other.model; + this.texture = other.texture; + this.modelVFXId = other.modelVFXId; + this.condition = other.condition; + this.conditionValueType = other.conditionValueType; + this.localSoundEventId = other.localSoundEventId; + this.worldSoundEventId = other.worldSoundEventId; + } + + @Nonnull + public static ItemAppearanceCondition deserialize(@Nonnull ByteBuf buf, int offset) { + ItemAppearanceCondition obj = new ItemAppearanceCondition(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 32) != 0) { + obj.condition = FloatRange.deserialize(buf, offset + 1); + } + + obj.conditionValueType = ValueType.fromValue(buf.getByte(offset + 9)); + obj.localSoundEventId = buf.getIntLE(offset + 10); + obj.worldSoundEventId = buf.getIntLE(offset + 14); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 38 + buf.getIntLE(offset + 18); + int particlesCount = VarInt.peek(buf, varPos0); + if (particlesCount < 0) { + throw ProtocolException.negativeLength("Particles", particlesCount); + } + + if (particlesCount > 4096000) { + throw ProtocolException.arrayTooLong("Particles", particlesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + particlesCount * 34L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Particles", varPos0 + varIntLen + particlesCount * 34, buf.readableBytes()); + } + + obj.particles = new ModelParticle[particlesCount]; + int elemPos = varPos0 + varIntLen; + + for (int i = 0; i < particlesCount; i++) { + obj.particles[i] = ModelParticle.deserialize(buf, elemPos); + elemPos += ModelParticle.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 38 + buf.getIntLE(offset + 22); + int firstPersonParticlesCount = VarInt.peek(buf, varPos1); + if (firstPersonParticlesCount < 0) { + throw ProtocolException.negativeLength("FirstPersonParticles", firstPersonParticlesCount); + } + + if (firstPersonParticlesCount > 4096000) { + throw ProtocolException.arrayTooLong("FirstPersonParticles", firstPersonParticlesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + firstPersonParticlesCount * 34L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("FirstPersonParticles", varPos1 + varIntLen + firstPersonParticlesCount * 34, buf.readableBytes()); + } + + obj.firstPersonParticles = new ModelParticle[firstPersonParticlesCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < firstPersonParticlesCount; i++) { + obj.firstPersonParticles[i] = ModelParticle.deserialize(buf, elemPos); + elemPos += ModelParticle.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 38 + buf.getIntLE(offset + 26); + int modelLen = VarInt.peek(buf, varPos2); + if (modelLen < 0) { + throw ProtocolException.negativeLength("Model", modelLen); + } + + if (modelLen > 4096000) { + throw ProtocolException.stringTooLong("Model", modelLen, 4096000); + } + + obj.model = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 38 + buf.getIntLE(offset + 30); + int textureLen = VarInt.peek(buf, varPos3); + if (textureLen < 0) { + throw ProtocolException.negativeLength("Texture", textureLen); + } + + if (textureLen > 4096000) { + throw ProtocolException.stringTooLong("Texture", textureLen, 4096000); + } + + obj.texture = PacketIO.readVarString(buf, varPos3, PacketIO.UTF8); + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 38 + buf.getIntLE(offset + 34); + int modelVFXIdLen = VarInt.peek(buf, varPos4); + if (modelVFXIdLen < 0) { + throw ProtocolException.negativeLength("ModelVFXId", modelVFXIdLen); + } + + if (modelVFXIdLen > 4096000) { + throw ProtocolException.stringTooLong("ModelVFXId", modelVFXIdLen, 4096000); + } + + obj.modelVFXId = PacketIO.readVarString(buf, varPos4, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 38; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 18); + int pos0 = offset + 38 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < arrLen; i++) { + pos0 += ModelParticle.computeBytesConsumed(buf, pos0); + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 22); + int pos1 = offset + 38 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += ModelParticle.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 26); + int pos2 = offset + 38 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 30); + int pos3 = offset + 38 + fieldOffset3; + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 34); + int pos4 = offset + 38 + fieldOffset4; + int sl = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4) + sl; + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.particles != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.firstPersonParticles != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.model != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.texture != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.modelVFXId != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.condition != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + if (this.condition != null) { + this.condition.serialize(buf); + } else { + buf.writeZero(8); + } + + buf.writeByte(this.conditionValueType.getValue()); + buf.writeIntLE(this.localSoundEventId); + buf.writeIntLE(this.worldSoundEventId); + int particlesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int firstPersonParticlesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int modelOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int textureOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int modelVFXIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.particles != null) { + buf.setIntLE(particlesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.particles.length > 4096000) { + throw ProtocolException.arrayTooLong("Particles", this.particles.length, 4096000); + } + + VarInt.write(buf, this.particles.length); + + for (ModelParticle item : this.particles) { + item.serialize(buf); + } + } else { + buf.setIntLE(particlesOffsetSlot, -1); + } + + if (this.firstPersonParticles != null) { + buf.setIntLE(firstPersonParticlesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.firstPersonParticles.length > 4096000) { + throw ProtocolException.arrayTooLong("FirstPersonParticles", this.firstPersonParticles.length, 4096000); + } + + VarInt.write(buf, this.firstPersonParticles.length); + + for (ModelParticle item : this.firstPersonParticles) { + item.serialize(buf); + } + } else { + buf.setIntLE(firstPersonParticlesOffsetSlot, -1); + } + + if (this.model != null) { + buf.setIntLE(modelOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.model, 4096000); + } else { + buf.setIntLE(modelOffsetSlot, -1); + } + + if (this.texture != null) { + buf.setIntLE(textureOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.texture, 4096000); + } else { + buf.setIntLE(textureOffsetSlot, -1); + } + + if (this.modelVFXId != null) { + buf.setIntLE(modelVFXIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.modelVFXId, 4096000); + } else { + buf.setIntLE(modelVFXIdOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 38; + if (this.particles != null) { + int particlesSize = 0; + + for (ModelParticle elem : this.particles) { + particlesSize += elem.computeSize(); + } + + size += VarInt.size(this.particles.length) + particlesSize; + } + + if (this.firstPersonParticles != null) { + int firstPersonParticlesSize = 0; + + for (ModelParticle elem : this.firstPersonParticles) { + firstPersonParticlesSize += elem.computeSize(); + } + + size += VarInt.size(this.firstPersonParticles.length) + firstPersonParticlesSize; + } + + if (this.model != null) { + size += PacketIO.stringSize(this.model); + } + + if (this.texture != null) { + size += PacketIO.stringSize(this.texture); + } + + if (this.modelVFXId != null) { + size += PacketIO.stringSize(this.modelVFXId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 38) { + return ValidationResult.error("Buffer too small: expected at least 38 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int particlesOffset = buffer.getIntLE(offset + 18); + if (particlesOffset < 0) { + return ValidationResult.error("Invalid offset for Particles"); + } + + int pos = offset + 38 + particlesOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Particles"); + } + + int particlesCount = VarInt.peek(buffer, pos); + if (particlesCount < 0) { + return ValidationResult.error("Invalid array count for Particles"); + } + + if (particlesCount > 4096000) { + return ValidationResult.error("Particles exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < particlesCount; i++) { + ValidationResult structResult = ModelParticle.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelParticle in Particles[" + i + "]: " + structResult.error()); + } + + pos += ModelParticle.computeBytesConsumed(buffer, pos); + } + } + + if ((nullBits & 2) != 0) { + int firstPersonParticlesOffset = buffer.getIntLE(offset + 22); + if (firstPersonParticlesOffset < 0) { + return ValidationResult.error("Invalid offset for FirstPersonParticles"); + } + + int posx = offset + 38 + firstPersonParticlesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FirstPersonParticles"); + } + + int firstPersonParticlesCount = VarInt.peek(buffer, posx); + if (firstPersonParticlesCount < 0) { + return ValidationResult.error("Invalid array count for FirstPersonParticles"); + } + + if (firstPersonParticlesCount > 4096000) { + return ValidationResult.error("FirstPersonParticles exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < firstPersonParticlesCount; i++) { + ValidationResult structResult = ModelParticle.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelParticle in FirstPersonParticles[" + i + "]: " + structResult.error()); + } + + posx += ModelParticle.computeBytesConsumed(buffer, posx); + } + } + + if ((nullBits & 4) != 0) { + int modelOffset = buffer.getIntLE(offset + 26); + if (modelOffset < 0) { + return ValidationResult.error("Invalid offset for Model"); + } + + int posxx = offset + 38 + modelOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Model"); + } + + int modelLen = VarInt.peek(buffer, posxx); + if (modelLen < 0) { + return ValidationResult.error("Invalid string length for Model"); + } + + if (modelLen > 4096000) { + return ValidationResult.error("Model exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += modelLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Model"); + } + } + + if ((nullBits & 8) != 0) { + int textureOffset = buffer.getIntLE(offset + 30); + if (textureOffset < 0) { + return ValidationResult.error("Invalid offset for Texture"); + } + + int posxxx = offset + 38 + textureOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Texture"); + } + + int textureLen = VarInt.peek(buffer, posxxx); + if (textureLen < 0) { + return ValidationResult.error("Invalid string length for Texture"); + } + + if (textureLen > 4096000) { + return ValidationResult.error("Texture exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += textureLen; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Texture"); + } + } + + if ((nullBits & 16) != 0) { + int modelVFXIdOffset = buffer.getIntLE(offset + 34); + if (modelVFXIdOffset < 0) { + return ValidationResult.error("Invalid offset for ModelVFXId"); + } + + int posxxxx = offset + 38 + modelVFXIdOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ModelVFXId"); + } + + int modelVFXIdLen = VarInt.peek(buffer, posxxxx); + if (modelVFXIdLen < 0) { + return ValidationResult.error("Invalid string length for ModelVFXId"); + } + + if (modelVFXIdLen > 4096000) { + return ValidationResult.error("ModelVFXId exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + posxxxx += modelVFXIdLen; + if (posxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ModelVFXId"); + } + } + + return ValidationResult.OK; + } + } + + public ItemAppearanceCondition clone() { + ItemAppearanceCondition copy = new ItemAppearanceCondition(); + copy.particles = this.particles != null ? Arrays.stream(this.particles).map(e -> e.clone()).toArray(ModelParticle[]::new) : null; + copy.firstPersonParticles = this.firstPersonParticles != null + ? Arrays.stream(this.firstPersonParticles).map(e -> e.clone()).toArray(ModelParticle[]::new) + : null; + copy.model = this.model; + copy.texture = this.texture; + copy.modelVFXId = this.modelVFXId; + copy.condition = this.condition != null ? this.condition.clone() : null; + copy.conditionValueType = this.conditionValueType; + copy.localSoundEventId = this.localSoundEventId; + copy.worldSoundEventId = this.worldSoundEventId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemAppearanceCondition other) + ? false + : Arrays.equals((Object[])this.particles, (Object[])other.particles) + && Arrays.equals((Object[])this.firstPersonParticles, (Object[])other.firstPersonParticles) + && Objects.equals(this.model, other.model) + && Objects.equals(this.texture, other.texture) + && Objects.equals(this.modelVFXId, other.modelVFXId) + && Objects.equals(this.condition, other.condition) + && Objects.equals(this.conditionValueType, other.conditionValueType) + && this.localSoundEventId == other.localSoundEventId + && this.worldSoundEventId == other.worldSoundEventId; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode((Object[])this.particles); + result = 31 * result + Arrays.hashCode((Object[])this.firstPersonParticles); + result = 31 * result + Objects.hashCode(this.model); + result = 31 * result + Objects.hashCode(this.texture); + result = 31 * result + Objects.hashCode(this.modelVFXId); + result = 31 * result + Objects.hashCode(this.condition); + result = 31 * result + Objects.hashCode(this.conditionValueType); + result = 31 * result + Integer.hashCode(this.localSoundEventId); + return 31 * result + Integer.hashCode(this.worldSoundEventId); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemArmor.java b/src/com/hypixel/hytale/protocol/ItemArmor.java new file mode 100644 index 0000000..af3bdb7 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemArmor.java @@ -0,0 +1,906 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemArmor { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 10; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 30; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public ItemArmorSlot armorSlot = ItemArmorSlot.Head; + @Nullable + public Cosmetic[] cosmeticsToHide; + @Nullable + public Map statModifiers; + public double baseDamageResistance; + @Nullable + public Map damageResistance; + @Nullable + public Map damageEnhancement; + @Nullable + public Map damageClassEnhancement; + + public ItemArmor() { + } + + public ItemArmor( + @Nonnull ItemArmorSlot armorSlot, + @Nullable Cosmetic[] cosmeticsToHide, + @Nullable Map statModifiers, + double baseDamageResistance, + @Nullable Map damageResistance, + @Nullable Map damageEnhancement, + @Nullable Map damageClassEnhancement + ) { + this.armorSlot = armorSlot; + this.cosmeticsToHide = cosmeticsToHide; + this.statModifiers = statModifiers; + this.baseDamageResistance = baseDamageResistance; + this.damageResistance = damageResistance; + this.damageEnhancement = damageEnhancement; + this.damageClassEnhancement = damageClassEnhancement; + } + + public ItemArmor(@Nonnull ItemArmor other) { + this.armorSlot = other.armorSlot; + this.cosmeticsToHide = other.cosmeticsToHide; + this.statModifiers = other.statModifiers; + this.baseDamageResistance = other.baseDamageResistance; + this.damageResistance = other.damageResistance; + this.damageEnhancement = other.damageEnhancement; + this.damageClassEnhancement = other.damageClassEnhancement; + } + + @Nonnull + public static ItemArmor deserialize(@Nonnull ByteBuf buf, int offset) { + ItemArmor obj = new ItemArmor(); + byte nullBits = buf.getByte(offset); + obj.armorSlot = ItemArmorSlot.fromValue(buf.getByte(offset + 1)); + obj.baseDamageResistance = buf.getDoubleLE(offset + 2); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 30 + buf.getIntLE(offset + 10); + int cosmeticsToHideCount = VarInt.peek(buf, varPos0); + if (cosmeticsToHideCount < 0) { + throw ProtocolException.negativeLength("CosmeticsToHide", cosmeticsToHideCount); + } + + if (cosmeticsToHideCount > 4096000) { + throw ProtocolException.arrayTooLong("CosmeticsToHide", cosmeticsToHideCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + cosmeticsToHideCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("CosmeticsToHide", varPos0 + varIntLen + cosmeticsToHideCount * 1, buf.readableBytes()); + } + + obj.cosmeticsToHide = new Cosmetic[cosmeticsToHideCount]; + int elemPos = varPos0 + varIntLen; + + for (int i = 0; i < cosmeticsToHideCount; i++) { + obj.cosmeticsToHide[i] = Cosmetic.fromValue(buf.getByte(elemPos)); + elemPos++; + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 30 + buf.getIntLE(offset + 14); + int statModifiersCount = VarInt.peek(buf, varPos1); + if (statModifiersCount < 0) { + throw ProtocolException.negativeLength("StatModifiers", statModifiersCount); + } + + if (statModifiersCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("StatModifiers", statModifiersCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.statModifiers = new HashMap<>(statModifiersCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < statModifiersCount; i++) { + int key = buf.getIntLE(dictPos); + dictPos += 4; + int valLen = VarInt.peek(buf, dictPos); + if (valLen < 0) { + throw ProtocolException.negativeLength("val", valLen); + } + + if (valLen > 64) { + throw ProtocolException.arrayTooLong("val", valLen, 64); + } + + int valVarLen = VarInt.length(buf, dictPos); + if (dictPos + valVarLen + valLen * 6L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("val", dictPos + valVarLen + valLen * 6, buf.readableBytes()); + } + + dictPos += valVarLen; + Modifier[] val = new Modifier[valLen]; + + for (int valIdx = 0; valIdx < valLen; valIdx++) { + val[valIdx] = Modifier.deserialize(buf, dictPos); + dictPos += Modifier.computeBytesConsumed(buf, dictPos); + } + + if (obj.statModifiers.put(key, val) != null) { + throw ProtocolException.duplicateKey("statModifiers", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 30 + buf.getIntLE(offset + 18); + int damageResistanceCount = VarInt.peek(buf, varPos2); + if (damageResistanceCount < 0) { + throw ProtocolException.negativeLength("DamageResistance", damageResistanceCount); + } + + if (damageResistanceCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("DamageResistance", damageResistanceCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + obj.damageResistance = new HashMap<>(damageResistanceCount); + int dictPos = varPos2 + varIntLen; + + for (int i = 0; i < damageResistanceCount; i++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String keyx = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + int valLenx = VarInt.peek(buf, dictPos); + if (valLenx < 0) { + throw ProtocolException.negativeLength("val", valLenx); + } + + if (valLenx > 64) { + throw ProtocolException.arrayTooLong("val", valLenx, 64); + } + + int valVarLenx = VarInt.length(buf, dictPos); + if (dictPos + valVarLenx + valLenx * 6L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("val", dictPos + valVarLenx + valLenx * 6, buf.readableBytes()); + } + + dictPos += valVarLenx; + Modifier[] val = new Modifier[valLenx]; + + for (int valIdx = 0; valIdx < valLenx; valIdx++) { + val[valIdx] = Modifier.deserialize(buf, dictPos); + dictPos += Modifier.computeBytesConsumed(buf, dictPos); + } + + if (obj.damageResistance.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("damageResistance", keyx); + } + } + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 30 + buf.getIntLE(offset + 22); + int damageEnhancementCount = VarInt.peek(buf, varPos3); + if (damageEnhancementCount < 0) { + throw ProtocolException.negativeLength("DamageEnhancement", damageEnhancementCount); + } + + if (damageEnhancementCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("DamageEnhancement", damageEnhancementCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + obj.damageEnhancement = new HashMap<>(damageEnhancementCount); + int dictPos = varPos3 + varIntLen; + + for (int i = 0; i < damageEnhancementCount; i++) { + int keyLenx = VarInt.peek(buf, dictPos); + if (keyLenx < 0) { + throw ProtocolException.negativeLength("key", keyLenx); + } + + if (keyLenx > 4096000) { + throw ProtocolException.stringTooLong("key", keyLenx, 4096000); + } + + int keyVarLenx = VarInt.length(buf, dictPos); + String keyxx = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLenx + keyLenx; + int valLenxx = VarInt.peek(buf, dictPos); + if (valLenxx < 0) { + throw ProtocolException.negativeLength("val", valLenxx); + } + + if (valLenxx > 64) { + throw ProtocolException.arrayTooLong("val", valLenxx, 64); + } + + int valVarLenxx = VarInt.length(buf, dictPos); + if (dictPos + valVarLenxx + valLenxx * 6L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("val", dictPos + valVarLenxx + valLenxx * 6, buf.readableBytes()); + } + + dictPos += valVarLenxx; + Modifier[] val = new Modifier[valLenxx]; + + for (int valIdx = 0; valIdx < valLenxx; valIdx++) { + val[valIdx] = Modifier.deserialize(buf, dictPos); + dictPos += Modifier.computeBytesConsumed(buf, dictPos); + } + + if (obj.damageEnhancement.put(keyxx, val) != null) { + throw ProtocolException.duplicateKey("damageEnhancement", keyxx); + } + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 30 + buf.getIntLE(offset + 26); + int damageClassEnhancementCount = VarInt.peek(buf, varPos4); + if (damageClassEnhancementCount < 0) { + throw ProtocolException.negativeLength("DamageClassEnhancement", damageClassEnhancementCount); + } + + if (damageClassEnhancementCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("DamageClassEnhancement", damageClassEnhancementCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos4); + obj.damageClassEnhancement = new HashMap<>(damageClassEnhancementCount); + int dictPos = varPos4 + varIntLen; + + for (int i = 0; i < damageClassEnhancementCount; i++) { + int keyLenxx = VarInt.peek(buf, dictPos); + if (keyLenxx < 0) { + throw ProtocolException.negativeLength("key", keyLenxx); + } + + if (keyLenxx > 4096000) { + throw ProtocolException.stringTooLong("key", keyLenxx, 4096000); + } + + int keyVarLenxx = VarInt.length(buf, dictPos); + String keyxxx = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLenxx + keyLenxx; + int valLenxxx = VarInt.peek(buf, dictPos); + if (valLenxxx < 0) { + throw ProtocolException.negativeLength("val", valLenxxx); + } + + if (valLenxxx > 64) { + throw ProtocolException.arrayTooLong("val", valLenxxx, 64); + } + + int valVarLenxxx = VarInt.length(buf, dictPos); + if (dictPos + valVarLenxxx + valLenxxx * 6L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("val", dictPos + valVarLenxxx + valLenxxx * 6, buf.readableBytes()); + } + + dictPos += valVarLenxxx; + Modifier[] val = new Modifier[valLenxxx]; + + for (int valIdx = 0; valIdx < valLenxxx; valIdx++) { + val[valIdx] = Modifier.deserialize(buf, dictPos); + dictPos += Modifier.computeBytesConsumed(buf, dictPos); + } + + if (obj.damageClassEnhancement.put(keyxxx, val) != null) { + throw ProtocolException.duplicateKey("damageClassEnhancement", keyxxx); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 30; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 10); + int pos0 = offset + 30 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + arrLen * 1; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 14); + int pos1 = offset + 30 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 += 4; + int al = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int j = 0; j < al; j++) { + pos1 += Modifier.computeBytesConsumed(buf, pos1); + } + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 18); + int pos2 = offset + 30 + fieldOffset2; + int dictLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2); + + for (int j = 0; j < sl; j++) { + pos2 += Modifier.computeBytesConsumed(buf, pos2); + } + } + + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 22); + int pos3 = offset + 30 + fieldOffset3; + int dictLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3); + + for (int j = 0; j < sl; j++) { + pos3 += Modifier.computeBytesConsumed(buf, pos3); + } + } + + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 26); + int pos4 = offset + 30 + fieldOffset4; + int dictLen = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4) + sl; + sl = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4); + + for (int j = 0; j < sl; j++) { + pos4 += Modifier.computeBytesConsumed(buf, pos4); + } + } + + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.cosmeticsToHide != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.statModifiers != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.damageResistance != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.damageEnhancement != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.damageClassEnhancement != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.armorSlot.getValue()); + buf.writeDoubleLE(this.baseDamageResistance); + int cosmeticsToHideOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int statModifiersOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int damageResistanceOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int damageEnhancementOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int damageClassEnhancementOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.cosmeticsToHide != null) { + buf.setIntLE(cosmeticsToHideOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.cosmeticsToHide.length > 4096000) { + throw ProtocolException.arrayTooLong("CosmeticsToHide", this.cosmeticsToHide.length, 4096000); + } + + VarInt.write(buf, this.cosmeticsToHide.length); + + for (Cosmetic item : this.cosmeticsToHide) { + buf.writeByte(item.getValue()); + } + } else { + buf.setIntLE(cosmeticsToHideOffsetSlot, -1); + } + + if (this.statModifiers != null) { + buf.setIntLE(statModifiersOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.statModifiers.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("StatModifiers", this.statModifiers.size(), 4096000); + } + + VarInt.write(buf, this.statModifiers.size()); + + for (Entry e : this.statModifiers.entrySet()) { + buf.writeIntLE(e.getKey()); + VarInt.write(buf, e.getValue().length); + + for (Modifier arrItem : e.getValue()) { + arrItem.serialize(buf); + } + } + } else { + buf.setIntLE(statModifiersOffsetSlot, -1); + } + + if (this.damageResistance != null) { + buf.setIntLE(damageResistanceOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.damageResistance.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("DamageResistance", this.damageResistance.size(), 4096000); + } + + VarInt.write(buf, this.damageResistance.size()); + + for (Entry e : this.damageResistance.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + VarInt.write(buf, e.getValue().length); + + for (Modifier arrItem : e.getValue()) { + arrItem.serialize(buf); + } + } + } else { + buf.setIntLE(damageResistanceOffsetSlot, -1); + } + + if (this.damageEnhancement != null) { + buf.setIntLE(damageEnhancementOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.damageEnhancement.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("DamageEnhancement", this.damageEnhancement.size(), 4096000); + } + + VarInt.write(buf, this.damageEnhancement.size()); + + for (Entry e : this.damageEnhancement.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + VarInt.write(buf, e.getValue().length); + + for (Modifier arrItem : e.getValue()) { + arrItem.serialize(buf); + } + } + } else { + buf.setIntLE(damageEnhancementOffsetSlot, -1); + } + + if (this.damageClassEnhancement != null) { + buf.setIntLE(damageClassEnhancementOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.damageClassEnhancement.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("DamageClassEnhancement", this.damageClassEnhancement.size(), 4096000); + } + + VarInt.write(buf, this.damageClassEnhancement.size()); + + for (Entry e : this.damageClassEnhancement.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + VarInt.write(buf, e.getValue().length); + + for (Modifier arrItem : e.getValue()) { + arrItem.serialize(buf); + } + } + } else { + buf.setIntLE(damageClassEnhancementOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 30; + if (this.cosmeticsToHide != null) { + size += VarInt.size(this.cosmeticsToHide.length) + this.cosmeticsToHide.length * 1; + } + + if (this.statModifiers != null) { + int statModifiersSize = 0; + + for (Entry kvp : this.statModifiers.entrySet()) { + statModifiersSize += 4 + VarInt.size(kvp.getValue().length) + ((Modifier[])kvp.getValue()).length * 6; + } + + size += VarInt.size(this.statModifiers.size()) + statModifiersSize; + } + + if (this.damageResistance != null) { + int damageResistanceSize = 0; + + for (Entry kvp : this.damageResistance.entrySet()) { + damageResistanceSize += PacketIO.stringSize(kvp.getKey()) + VarInt.size(kvp.getValue().length) + ((Modifier[])kvp.getValue()).length * 6; + } + + size += VarInt.size(this.damageResistance.size()) + damageResistanceSize; + } + + if (this.damageEnhancement != null) { + int damageEnhancementSize = 0; + + for (Entry kvp : this.damageEnhancement.entrySet()) { + damageEnhancementSize += PacketIO.stringSize(kvp.getKey()) + VarInt.size(kvp.getValue().length) + ((Modifier[])kvp.getValue()).length * 6; + } + + size += VarInt.size(this.damageEnhancement.size()) + damageEnhancementSize; + } + + if (this.damageClassEnhancement != null) { + int damageClassEnhancementSize = 0; + + for (Entry kvp : this.damageClassEnhancement.entrySet()) { + damageClassEnhancementSize += PacketIO.stringSize(kvp.getKey()) + VarInt.size(kvp.getValue().length) + ((Modifier[])kvp.getValue()).length * 6; + } + + size += VarInt.size(this.damageClassEnhancement.size()) + damageClassEnhancementSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 30) { + return ValidationResult.error("Buffer too small: expected at least 30 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int cosmeticsToHideOffset = buffer.getIntLE(offset + 10); + if (cosmeticsToHideOffset < 0) { + return ValidationResult.error("Invalid offset for CosmeticsToHide"); + } + + int pos = offset + 30 + cosmeticsToHideOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for CosmeticsToHide"); + } + + int cosmeticsToHideCount = VarInt.peek(buffer, pos); + if (cosmeticsToHideCount < 0) { + return ValidationResult.error("Invalid array count for CosmeticsToHide"); + } + + if (cosmeticsToHideCount > 4096000) { + return ValidationResult.error("CosmeticsToHide exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += cosmeticsToHideCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading CosmeticsToHide"); + } + } + + if ((nullBits & 2) != 0) { + int statModifiersOffset = buffer.getIntLE(offset + 14); + if (statModifiersOffset < 0) { + return ValidationResult.error("Invalid offset for StatModifiers"); + } + + int posx = offset + 30 + statModifiersOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for StatModifiers"); + } + + int statModifiersCount = VarInt.peek(buffer, posx); + if (statModifiersCount < 0) { + return ValidationResult.error("Invalid dictionary count for StatModifiers"); + } + + if (statModifiersCount > 4096000) { + return ValidationResult.error("StatModifiers exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < statModifiersCount; i++) { + posx += 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + int valueArrCount = VarInt.peek(buffer, posx); + if (valueArrCount < 0) { + return ValidationResult.error("Invalid array count for value"); + } + + posx += VarInt.length(buffer, posx); + + for (int valueArrIdx = 0; valueArrIdx < valueArrCount; valueArrIdx++) { + posx += 6; + } + } + } + + if ((nullBits & 4) != 0) { + int damageResistanceOffset = buffer.getIntLE(offset + 18); + if (damageResistanceOffset < 0) { + return ValidationResult.error("Invalid offset for DamageResistance"); + } + + int posxx = offset + 30 + damageResistanceOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for DamageResistance"); + } + + int damageResistanceCount = VarInt.peek(buffer, posxx); + if (damageResistanceCount < 0) { + return ValidationResult.error("Invalid dictionary count for DamageResistance"); + } + + if (damageResistanceCount > 4096000) { + return ValidationResult.error("DamageResistance exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + + for (int i = 0; i < damageResistanceCount; i++) { + int keyLen = VarInt.peek(buffer, posxx); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += keyLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + int valueArrCount = VarInt.peek(buffer, posxx); + if (valueArrCount < 0) { + return ValidationResult.error("Invalid array count for value"); + } + + posxx += VarInt.length(buffer, posxx); + + for (int valueArrIdx = 0; valueArrIdx < valueArrCount; valueArrIdx++) { + posxx += 6; + } + } + } + + if ((nullBits & 8) != 0) { + int damageEnhancementOffset = buffer.getIntLE(offset + 22); + if (damageEnhancementOffset < 0) { + return ValidationResult.error("Invalid offset for DamageEnhancement"); + } + + int posxxx = offset + 30 + damageEnhancementOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for DamageEnhancement"); + } + + int damageEnhancementCount = VarInt.peek(buffer, posxxx); + if (damageEnhancementCount < 0) { + return ValidationResult.error("Invalid dictionary count for DamageEnhancement"); + } + + if (damageEnhancementCount > 4096000) { + return ValidationResult.error("DamageEnhancement exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + + for (int i = 0; i < damageEnhancementCount; i++) { + int keyLenx = VarInt.peek(buffer, posxxx); + if (keyLenx < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLenx > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += keyLenx; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + int valueArrCount = VarInt.peek(buffer, posxxx); + if (valueArrCount < 0) { + return ValidationResult.error("Invalid array count for value"); + } + + posxxx += VarInt.length(buffer, posxxx); + + for (int valueArrIdx = 0; valueArrIdx < valueArrCount; valueArrIdx++) { + posxxx += 6; + } + } + } + + if ((nullBits & 16) != 0) { + int damageClassEnhancementOffset = buffer.getIntLE(offset + 26); + if (damageClassEnhancementOffset < 0) { + return ValidationResult.error("Invalid offset for DamageClassEnhancement"); + } + + int posxxxx = offset + 30 + damageClassEnhancementOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for DamageClassEnhancement"); + } + + int damageClassEnhancementCount = VarInt.peek(buffer, posxxxx); + if (damageClassEnhancementCount < 0) { + return ValidationResult.error("Invalid dictionary count for DamageClassEnhancement"); + } + + if (damageClassEnhancementCount > 4096000) { + return ValidationResult.error("DamageClassEnhancement exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + + for (int i = 0; i < damageClassEnhancementCount; i++) { + int keyLenxx = VarInt.peek(buffer, posxxxx); + if (keyLenxx < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLenxx > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + posxxxx += keyLenxx; + if (posxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + int valueArrCount = VarInt.peek(buffer, posxxxx); + if (valueArrCount < 0) { + return ValidationResult.error("Invalid array count for value"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + + for (int valueArrIdx = 0; valueArrIdx < valueArrCount; valueArrIdx++) { + posxxxx += 6; + } + } + } + + return ValidationResult.OK; + } + } + + public ItemArmor clone() { + ItemArmor copy = new ItemArmor(); + copy.armorSlot = this.armorSlot; + copy.cosmeticsToHide = this.cosmeticsToHide != null ? Arrays.copyOf(this.cosmeticsToHide, this.cosmeticsToHide.length) : null; + if (this.statModifiers != null) { + Map m = new HashMap<>(); + + for (Entry e : this.statModifiers.entrySet()) { + m.put(e.getKey(), Arrays.stream(e.getValue()).map(x -> x.clone()).toArray(Modifier[]::new)); + } + + copy.statModifiers = m; + } + + copy.baseDamageResistance = this.baseDamageResistance; + if (this.damageResistance != null) { + Map m = new HashMap<>(); + + for (Entry e : this.damageResistance.entrySet()) { + m.put(e.getKey(), Arrays.stream(e.getValue()).map(x -> x.clone()).toArray(Modifier[]::new)); + } + + copy.damageResistance = m; + } + + if (this.damageEnhancement != null) { + Map m = new HashMap<>(); + + for (Entry e : this.damageEnhancement.entrySet()) { + m.put(e.getKey(), Arrays.stream(e.getValue()).map(x -> x.clone()).toArray(Modifier[]::new)); + } + + copy.damageEnhancement = m; + } + + if (this.damageClassEnhancement != null) { + Map m = new HashMap<>(); + + for (Entry e : this.damageClassEnhancement.entrySet()) { + m.put(e.getKey(), Arrays.stream(e.getValue()).map(x -> x.clone()).toArray(Modifier[]::new)); + } + + copy.damageClassEnhancement = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemArmor other) + ? false + : Objects.equals(this.armorSlot, other.armorSlot) + && Arrays.equals((Object[])this.cosmeticsToHide, (Object[])other.cosmeticsToHide) + && Objects.equals(this.statModifiers, other.statModifiers) + && this.baseDamageResistance == other.baseDamageResistance + && Objects.equals(this.damageResistance, other.damageResistance) + && Objects.equals(this.damageEnhancement, other.damageEnhancement) + && Objects.equals(this.damageClassEnhancement, other.damageClassEnhancement); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.armorSlot); + result = 31 * result + Arrays.hashCode((Object[])this.cosmeticsToHide); + result = 31 * result + Objects.hashCode(this.statModifiers); + result = 31 * result + Double.hashCode(this.baseDamageResistance); + result = 31 * result + Objects.hashCode(this.damageResistance); + result = 31 * result + Objects.hashCode(this.damageEnhancement); + return 31 * result + Objects.hashCode(this.damageClassEnhancement); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemArmorSlot.java b/src/com/hypixel/hytale/protocol/ItemArmorSlot.java new file mode 100644 index 0000000..4468e04 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemArmorSlot.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ItemArmorSlot { + Head(0), + Chest(1), + Hands(2), + Legs(3); + + public static final ItemArmorSlot[] VALUES = values(); + private final int value; + + private ItemArmorSlot(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ItemArmorSlot fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ItemArmorSlot", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemBase.java b/src/com/hypixel/hytale/protocol/ItemBase.java new file mode 100644 index 0000000..17bdbfd --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemBase.java @@ -0,0 +1,2554 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemBase { + public static final int NULLABLE_BIT_FIELD_SIZE = 4; + public static final int FIXED_BLOCK_SIZE = 147; + public static final int VARIABLE_FIELD_COUNT = 26; + public static final int VARIABLE_BLOCK_START = 251; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + @Nullable + public String model; + public float scale; + @Nullable + public String texture; + @Nullable + public String animation; + @Nullable + public String playerAnimationsId; + public boolean usePlayerAnimations; + public int maxStack; + public int reticleIndex; + @Nullable + public String icon; + @Nullable + public AssetIconProperties iconProperties; + @Nullable + public ItemTranslationProperties translationProperties; + public int itemLevel; + public int qualityIndex; + @Nullable + public ItemResourceType[] resourceTypes; + public boolean consumable; + public boolean variant; + public int blockId; + @Nullable + public ItemTool tool; + @Nullable + public ItemWeapon weapon; + @Nullable + public ItemArmor armor; + @Nullable + public ItemGlider gliderConfig; + @Nullable + public ItemUtility utility; + @Nullable + public BlockSelectorToolData blockSelectorTool; + @Nullable + public ItemBuilderToolData builderToolData; + @Nullable + public ItemEntityConfig itemEntity; + @Nullable + public String set; + @Nullable + public String[] categories; + @Nullable + public ModelParticle[] particles; + @Nullable + public ModelParticle[] firstPersonParticles; + @Nullable + public ModelTrail[] trails; + @Nullable + public ColorLight light; + public double durability; + public int soundEventIndex; + public int itemSoundSetIndex; + @Nullable + public Map interactions; + @Nullable + public Map interactionVars; + @Nullable + public InteractionConfiguration interactionConfig; + @Nullable + public String droppedItemAnimation; + @Nullable + public int[] tagIndexes; + @Nullable + public Map itemAppearanceConditions; + @Nullable + public int[] displayEntityStatsHUD; + @Nullable + public ItemPullbackConfiguration pullbackConfig; + public boolean clipsGeometry; + public boolean renderDeployablePreview; + + public ItemBase() { + } + + public ItemBase( + @Nullable String id, + @Nullable String model, + float scale, + @Nullable String texture, + @Nullable String animation, + @Nullable String playerAnimationsId, + boolean usePlayerAnimations, + int maxStack, + int reticleIndex, + @Nullable String icon, + @Nullable AssetIconProperties iconProperties, + @Nullable ItemTranslationProperties translationProperties, + int itemLevel, + int qualityIndex, + @Nullable ItemResourceType[] resourceTypes, + boolean consumable, + boolean variant, + int blockId, + @Nullable ItemTool tool, + @Nullable ItemWeapon weapon, + @Nullable ItemArmor armor, + @Nullable ItemGlider gliderConfig, + @Nullable ItemUtility utility, + @Nullable BlockSelectorToolData blockSelectorTool, + @Nullable ItemBuilderToolData builderToolData, + @Nullable ItemEntityConfig itemEntity, + @Nullable String set, + @Nullable String[] categories, + @Nullable ModelParticle[] particles, + @Nullable ModelParticle[] firstPersonParticles, + @Nullable ModelTrail[] trails, + @Nullable ColorLight light, + double durability, + int soundEventIndex, + int itemSoundSetIndex, + @Nullable Map interactions, + @Nullable Map interactionVars, + @Nullable InteractionConfiguration interactionConfig, + @Nullable String droppedItemAnimation, + @Nullable int[] tagIndexes, + @Nullable Map itemAppearanceConditions, + @Nullable int[] displayEntityStatsHUD, + @Nullable ItemPullbackConfiguration pullbackConfig, + boolean clipsGeometry, + boolean renderDeployablePreview + ) { + this.id = id; + this.model = model; + this.scale = scale; + this.texture = texture; + this.animation = animation; + this.playerAnimationsId = playerAnimationsId; + this.usePlayerAnimations = usePlayerAnimations; + this.maxStack = maxStack; + this.reticleIndex = reticleIndex; + this.icon = icon; + this.iconProperties = iconProperties; + this.translationProperties = translationProperties; + this.itemLevel = itemLevel; + this.qualityIndex = qualityIndex; + this.resourceTypes = resourceTypes; + this.consumable = consumable; + this.variant = variant; + this.blockId = blockId; + this.tool = tool; + this.weapon = weapon; + this.armor = armor; + this.gliderConfig = gliderConfig; + this.utility = utility; + this.blockSelectorTool = blockSelectorTool; + this.builderToolData = builderToolData; + this.itemEntity = itemEntity; + this.set = set; + this.categories = categories; + this.particles = particles; + this.firstPersonParticles = firstPersonParticles; + this.trails = trails; + this.light = light; + this.durability = durability; + this.soundEventIndex = soundEventIndex; + this.itemSoundSetIndex = itemSoundSetIndex; + this.interactions = interactions; + this.interactionVars = interactionVars; + this.interactionConfig = interactionConfig; + this.droppedItemAnimation = droppedItemAnimation; + this.tagIndexes = tagIndexes; + this.itemAppearanceConditions = itemAppearanceConditions; + this.displayEntityStatsHUD = displayEntityStatsHUD; + this.pullbackConfig = pullbackConfig; + this.clipsGeometry = clipsGeometry; + this.renderDeployablePreview = renderDeployablePreview; + } + + public ItemBase(@Nonnull ItemBase other) { + this.id = other.id; + this.model = other.model; + this.scale = other.scale; + this.texture = other.texture; + this.animation = other.animation; + this.playerAnimationsId = other.playerAnimationsId; + this.usePlayerAnimations = other.usePlayerAnimations; + this.maxStack = other.maxStack; + this.reticleIndex = other.reticleIndex; + this.icon = other.icon; + this.iconProperties = other.iconProperties; + this.translationProperties = other.translationProperties; + this.itemLevel = other.itemLevel; + this.qualityIndex = other.qualityIndex; + this.resourceTypes = other.resourceTypes; + this.consumable = other.consumable; + this.variant = other.variant; + this.blockId = other.blockId; + this.tool = other.tool; + this.weapon = other.weapon; + this.armor = other.armor; + this.gliderConfig = other.gliderConfig; + this.utility = other.utility; + this.blockSelectorTool = other.blockSelectorTool; + this.builderToolData = other.builderToolData; + this.itemEntity = other.itemEntity; + this.set = other.set; + this.categories = other.categories; + this.particles = other.particles; + this.firstPersonParticles = other.firstPersonParticles; + this.trails = other.trails; + this.light = other.light; + this.durability = other.durability; + this.soundEventIndex = other.soundEventIndex; + this.itemSoundSetIndex = other.itemSoundSetIndex; + this.interactions = other.interactions; + this.interactionVars = other.interactionVars; + this.interactionConfig = other.interactionConfig; + this.droppedItemAnimation = other.droppedItemAnimation; + this.tagIndexes = other.tagIndexes; + this.itemAppearanceConditions = other.itemAppearanceConditions; + this.displayEntityStatsHUD = other.displayEntityStatsHUD; + this.pullbackConfig = other.pullbackConfig; + this.clipsGeometry = other.clipsGeometry; + this.renderDeployablePreview = other.renderDeployablePreview; + } + + @Nonnull + public static ItemBase deserialize(@Nonnull ByteBuf buf, int offset) { + ItemBase obj = new ItemBase(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 4); + obj.scale = buf.getFloatLE(offset + 4); + obj.usePlayerAnimations = buf.getByte(offset + 8) != 0; + obj.maxStack = buf.getIntLE(offset + 9); + obj.reticleIndex = buf.getIntLE(offset + 13); + if ((nullBits[0] & 64) != 0) { + obj.iconProperties = AssetIconProperties.deserialize(buf, offset + 17); + } + + obj.itemLevel = buf.getIntLE(offset + 42); + obj.qualityIndex = buf.getIntLE(offset + 46); + obj.consumable = buf.getByte(offset + 50) != 0; + obj.variant = buf.getByte(offset + 51) != 0; + obj.blockId = buf.getIntLE(offset + 52); + if ((nullBits[1] & 16) != 0) { + obj.gliderConfig = ItemGlider.deserialize(buf, offset + 56); + } + + if ((nullBits[1] & 64) != 0) { + obj.blockSelectorTool = BlockSelectorToolData.deserialize(buf, offset + 72); + } + + if ((nullBits[2] & 64) != 0) { + obj.light = ColorLight.deserialize(buf, offset + 76); + } + + obj.durability = buf.getDoubleLE(offset + 80); + obj.soundEventIndex = buf.getIntLE(offset + 88); + obj.itemSoundSetIndex = buf.getIntLE(offset + 92); + if ((nullBits[3] & 64) != 0) { + obj.pullbackConfig = ItemPullbackConfiguration.deserialize(buf, offset + 96); + } + + obj.clipsGeometry = buf.getByte(offset + 145) != 0; + obj.renderDeployablePreview = buf.getByte(offset + 146) != 0; + if ((nullBits[0] & 1) != 0) { + int varPos0 = offset + 251 + buf.getIntLE(offset + 147); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits[0] & 2) != 0) { + int varPos1 = offset + 251 + buf.getIntLE(offset + 151); + int modelLen = VarInt.peek(buf, varPos1); + if (modelLen < 0) { + throw ProtocolException.negativeLength("Model", modelLen); + } + + if (modelLen > 4096000) { + throw ProtocolException.stringTooLong("Model", modelLen, 4096000); + } + + obj.model = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits[0] & 4) != 0) { + int varPos2 = offset + 251 + buf.getIntLE(offset + 155); + int textureLen = VarInt.peek(buf, varPos2); + if (textureLen < 0) { + throw ProtocolException.negativeLength("Texture", textureLen); + } + + if (textureLen > 4096000) { + throw ProtocolException.stringTooLong("Texture", textureLen, 4096000); + } + + obj.texture = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits[0] & 8) != 0) { + int varPos3 = offset + 251 + buf.getIntLE(offset + 159); + int animationLen = VarInt.peek(buf, varPos3); + if (animationLen < 0) { + throw ProtocolException.negativeLength("Animation", animationLen); + } + + if (animationLen > 4096000) { + throw ProtocolException.stringTooLong("Animation", animationLen, 4096000); + } + + obj.animation = PacketIO.readVarString(buf, varPos3, PacketIO.UTF8); + } + + if ((nullBits[0] & 16) != 0) { + int varPos4 = offset + 251 + buf.getIntLE(offset + 163); + int playerAnimationsIdLen = VarInt.peek(buf, varPos4); + if (playerAnimationsIdLen < 0) { + throw ProtocolException.negativeLength("PlayerAnimationsId", playerAnimationsIdLen); + } + + if (playerAnimationsIdLen > 4096000) { + throw ProtocolException.stringTooLong("PlayerAnimationsId", playerAnimationsIdLen, 4096000); + } + + obj.playerAnimationsId = PacketIO.readVarString(buf, varPos4, PacketIO.UTF8); + } + + if ((nullBits[0] & 32) != 0) { + int varPos5 = offset + 251 + buf.getIntLE(offset + 167); + int iconLen = VarInt.peek(buf, varPos5); + if (iconLen < 0) { + throw ProtocolException.negativeLength("Icon", iconLen); + } + + if (iconLen > 4096000) { + throw ProtocolException.stringTooLong("Icon", iconLen, 4096000); + } + + obj.icon = PacketIO.readVarString(buf, varPos5, PacketIO.UTF8); + } + + if ((nullBits[0] & 128) != 0) { + int varPos6 = offset + 251 + buf.getIntLE(offset + 171); + obj.translationProperties = ItemTranslationProperties.deserialize(buf, varPos6); + } + + if ((nullBits[1] & 1) != 0) { + int varPos7 = offset + 251 + buf.getIntLE(offset + 175); + int resourceTypesCount = VarInt.peek(buf, varPos7); + if (resourceTypesCount < 0) { + throw ProtocolException.negativeLength("ResourceTypes", resourceTypesCount); + } + + if (resourceTypesCount > 4096000) { + throw ProtocolException.arrayTooLong("ResourceTypes", resourceTypesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos7); + if (varPos7 + varIntLen + resourceTypesCount * 5L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("ResourceTypes", varPos7 + varIntLen + resourceTypesCount * 5, buf.readableBytes()); + } + + obj.resourceTypes = new ItemResourceType[resourceTypesCount]; + int elemPos = varPos7 + varIntLen; + + for (int i = 0; i < resourceTypesCount; i++) { + obj.resourceTypes[i] = ItemResourceType.deserialize(buf, elemPos); + elemPos += ItemResourceType.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits[1] & 2) != 0) { + int varPos8 = offset + 251 + buf.getIntLE(offset + 179); + obj.tool = ItemTool.deserialize(buf, varPos8); + } + + if ((nullBits[1] & 4) != 0) { + int varPos9 = offset + 251 + buf.getIntLE(offset + 183); + obj.weapon = ItemWeapon.deserialize(buf, varPos9); + } + + if ((nullBits[1] & 8) != 0) { + int varPos10 = offset + 251 + buf.getIntLE(offset + 187); + obj.armor = ItemArmor.deserialize(buf, varPos10); + } + + if ((nullBits[1] & 32) != 0) { + int varPos11 = offset + 251 + buf.getIntLE(offset + 191); + obj.utility = ItemUtility.deserialize(buf, varPos11); + } + + if ((nullBits[1] & 128) != 0) { + int varPos12 = offset + 251 + buf.getIntLE(offset + 195); + obj.builderToolData = ItemBuilderToolData.deserialize(buf, varPos12); + } + + if ((nullBits[2] & 1) != 0) { + int varPos13 = offset + 251 + buf.getIntLE(offset + 199); + obj.itemEntity = ItemEntityConfig.deserialize(buf, varPos13); + } + + if ((nullBits[2] & 2) != 0) { + int varPos14 = offset + 251 + buf.getIntLE(offset + 203); + int setLen = VarInt.peek(buf, varPos14); + if (setLen < 0) { + throw ProtocolException.negativeLength("Set", setLen); + } + + if (setLen > 4096000) { + throw ProtocolException.stringTooLong("Set", setLen, 4096000); + } + + obj.set = PacketIO.readVarString(buf, varPos14, PacketIO.UTF8); + } + + if ((nullBits[2] & 4) != 0) { + int varPos15 = offset + 251 + buf.getIntLE(offset + 207); + int categoriesCount = VarInt.peek(buf, varPos15); + if (categoriesCount < 0) { + throw ProtocolException.negativeLength("Categories", categoriesCount); + } + + if (categoriesCount > 4096000) { + throw ProtocolException.arrayTooLong("Categories", categoriesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos15); + if (varPos15 + varIntLen + categoriesCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Categories", varPos15 + varIntLen + categoriesCount * 1, buf.readableBytes()); + } + + obj.categories = new String[categoriesCount]; + int elemPos = varPos15 + varIntLen; + + for (int i = 0; i < categoriesCount; i++) { + int strLen = VarInt.peek(buf, elemPos); + if (strLen < 0) { + throw ProtocolException.negativeLength("categories[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("categories[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, elemPos); + obj.categories[i] = PacketIO.readVarString(buf, elemPos); + elemPos += strVarLen + strLen; + } + } + + if ((nullBits[2] & 8) != 0) { + int varPos16 = offset + 251 + buf.getIntLE(offset + 211); + int particlesCount = VarInt.peek(buf, varPos16); + if (particlesCount < 0) { + throw ProtocolException.negativeLength("Particles", particlesCount); + } + + if (particlesCount > 4096000) { + throw ProtocolException.arrayTooLong("Particles", particlesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos16); + if (varPos16 + varIntLen + particlesCount * 34L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Particles", varPos16 + varIntLen + particlesCount * 34, buf.readableBytes()); + } + + obj.particles = new ModelParticle[particlesCount]; + int elemPos = varPos16 + varIntLen; + + for (int i = 0; i < particlesCount; i++) { + obj.particles[i] = ModelParticle.deserialize(buf, elemPos); + elemPos += ModelParticle.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits[2] & 16) != 0) { + int varPos17 = offset + 251 + buf.getIntLE(offset + 215); + int firstPersonParticlesCount = VarInt.peek(buf, varPos17); + if (firstPersonParticlesCount < 0) { + throw ProtocolException.negativeLength("FirstPersonParticles", firstPersonParticlesCount); + } + + if (firstPersonParticlesCount > 4096000) { + throw ProtocolException.arrayTooLong("FirstPersonParticles", firstPersonParticlesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos17); + if (varPos17 + varIntLen + firstPersonParticlesCount * 34L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("FirstPersonParticles", varPos17 + varIntLen + firstPersonParticlesCount * 34, buf.readableBytes()); + } + + obj.firstPersonParticles = new ModelParticle[firstPersonParticlesCount]; + int elemPos = varPos17 + varIntLen; + + for (int i = 0; i < firstPersonParticlesCount; i++) { + obj.firstPersonParticles[i] = ModelParticle.deserialize(buf, elemPos); + elemPos += ModelParticle.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits[2] & 32) != 0) { + int varPos18 = offset + 251 + buf.getIntLE(offset + 219); + int trailsCount = VarInt.peek(buf, varPos18); + if (trailsCount < 0) { + throw ProtocolException.negativeLength("Trails", trailsCount); + } + + if (trailsCount > 4096000) { + throw ProtocolException.arrayTooLong("Trails", trailsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos18); + if (varPos18 + varIntLen + trailsCount * 27L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Trails", varPos18 + varIntLen + trailsCount * 27, buf.readableBytes()); + } + + obj.trails = new ModelTrail[trailsCount]; + int elemPos = varPos18 + varIntLen; + + for (int i = 0; i < trailsCount; i++) { + obj.trails[i] = ModelTrail.deserialize(buf, elemPos); + elemPos += ModelTrail.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits[2] & 128) != 0) { + int varPos19 = offset + 251 + buf.getIntLE(offset + 223); + int interactionsCount = VarInt.peek(buf, varPos19); + if (interactionsCount < 0) { + throw ProtocolException.negativeLength("Interactions", interactionsCount); + } + + if (interactionsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Interactions", interactionsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos19); + obj.interactions = new HashMap<>(interactionsCount); + int dictPos = varPos19 + varIntLen; + + for (int i = 0; i < interactionsCount; i++) { + InteractionType key = InteractionType.fromValue(buf.getByte(dictPos)); + int val = buf.getIntLE(++dictPos); + dictPos += 4; + if (obj.interactions.put(key, val) != null) { + throw ProtocolException.duplicateKey("interactions", key); + } + } + } + + if ((nullBits[3] & 1) != 0) { + int varPos20 = offset + 251 + buf.getIntLE(offset + 227); + int interactionVarsCount = VarInt.peek(buf, varPos20); + if (interactionVarsCount < 0) { + throw ProtocolException.negativeLength("InteractionVars", interactionVarsCount); + } + + if (interactionVarsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("InteractionVars", interactionVarsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos20); + obj.interactionVars = new HashMap<>(interactionVarsCount); + int dictPos = varPos20 + varIntLen; + + for (int ix = 0; ix < interactionVarsCount; ix++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + int val = buf.getIntLE(dictPos); + dictPos += 4; + if (obj.interactionVars.put(key, val) != null) { + throw ProtocolException.duplicateKey("interactionVars", key); + } + } + } + + if ((nullBits[3] & 2) != 0) { + int varPos21 = offset + 251 + buf.getIntLE(offset + 231); + obj.interactionConfig = InteractionConfiguration.deserialize(buf, varPos21); + } + + if ((nullBits[3] & 4) != 0) { + int varPos22 = offset + 251 + buf.getIntLE(offset + 235); + int droppedItemAnimationLen = VarInt.peek(buf, varPos22); + if (droppedItemAnimationLen < 0) { + throw ProtocolException.negativeLength("DroppedItemAnimation", droppedItemAnimationLen); + } + + if (droppedItemAnimationLen > 4096000) { + throw ProtocolException.stringTooLong("DroppedItemAnimation", droppedItemAnimationLen, 4096000); + } + + obj.droppedItemAnimation = PacketIO.readVarString(buf, varPos22, PacketIO.UTF8); + } + + if ((nullBits[3] & 8) != 0) { + int varPos23 = offset + 251 + buf.getIntLE(offset + 239); + int tagIndexesCount = VarInt.peek(buf, varPos23); + if (tagIndexesCount < 0) { + throw ProtocolException.negativeLength("TagIndexes", tagIndexesCount); + } + + if (tagIndexesCount > 4096000) { + throw ProtocolException.arrayTooLong("TagIndexes", tagIndexesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos23); + if (varPos23 + varIntLen + tagIndexesCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("TagIndexes", varPos23 + varIntLen + tagIndexesCount * 4, buf.readableBytes()); + } + + obj.tagIndexes = new int[tagIndexesCount]; + + for (int ix = 0; ix < tagIndexesCount; ix++) { + obj.tagIndexes[ix] = buf.getIntLE(varPos23 + varIntLen + ix * 4); + } + } + + if ((nullBits[3] & 16) != 0) { + int varPos24 = offset + 251 + buf.getIntLE(offset + 243); + int itemAppearanceConditionsCount = VarInt.peek(buf, varPos24); + if (itemAppearanceConditionsCount < 0) { + throw ProtocolException.negativeLength("ItemAppearanceConditions", itemAppearanceConditionsCount); + } + + if (itemAppearanceConditionsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("ItemAppearanceConditions", itemAppearanceConditionsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos24); + obj.itemAppearanceConditions = new HashMap<>(itemAppearanceConditionsCount); + int dictPos = varPos24 + varIntLen; + + for (int ix = 0; ix < itemAppearanceConditionsCount; ix++) { + int key = buf.getIntLE(dictPos); + dictPos += 4; + int valLen = VarInt.peek(buf, dictPos); + if (valLen < 0) { + throw ProtocolException.negativeLength("val", valLen); + } + + if (valLen > 64) { + throw ProtocolException.arrayTooLong("val", valLen, 64); + } + + int valVarLen = VarInt.length(buf, dictPos); + if (dictPos + valVarLen + valLen * 18L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("val", dictPos + valVarLen + valLen * 18, buf.readableBytes()); + } + + dictPos += valVarLen; + ItemAppearanceCondition[] val = new ItemAppearanceCondition[valLen]; + + for (int valIdx = 0; valIdx < valLen; valIdx++) { + val[valIdx] = ItemAppearanceCondition.deserialize(buf, dictPos); + dictPos += ItemAppearanceCondition.computeBytesConsumed(buf, dictPos); + } + + if (obj.itemAppearanceConditions.put(key, val) != null) { + throw ProtocolException.duplicateKey("itemAppearanceConditions", key); + } + } + } + + if ((nullBits[3] & 32) != 0) { + int varPos25 = offset + 251 + buf.getIntLE(offset + 247); + int displayEntityStatsHUDCount = VarInt.peek(buf, varPos25); + if (displayEntityStatsHUDCount < 0) { + throw ProtocolException.negativeLength("DisplayEntityStatsHUD", displayEntityStatsHUDCount); + } + + if (displayEntityStatsHUDCount > 4096000) { + throw ProtocolException.arrayTooLong("DisplayEntityStatsHUD", displayEntityStatsHUDCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos25); + if (varPos25 + varIntLen + displayEntityStatsHUDCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("DisplayEntityStatsHUD", varPos25 + varIntLen + displayEntityStatsHUDCount * 4, buf.readableBytes()); + } + + obj.displayEntityStatsHUD = new int[displayEntityStatsHUDCount]; + + for (int ix = 0; ix < displayEntityStatsHUDCount; ix++) { + obj.displayEntityStatsHUD[ix] = buf.getIntLE(varPos25 + varIntLen + ix * 4); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte[] nullBits = PacketIO.readBytes(buf, offset, 4); + int maxEnd = 251; + if ((nullBits[0] & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 147); + int pos0 = offset + 251 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits[0] & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 151); + int pos1 = offset + 251 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits[0] & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 155); + int pos2 = offset + 251 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits[0] & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 159); + int pos3 = offset + 251 + fieldOffset3; + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits[0] & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 163); + int pos4 = offset + 251 + fieldOffset4; + int sl = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4) + sl; + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits[0] & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 167); + int pos5 = offset + 251 + fieldOffset5; + int sl = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + sl; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits[0] & 128) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 171); + int pos6 = offset + 251 + fieldOffset6; + pos6 += ItemTranslationProperties.computeBytesConsumed(buf, pos6); + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + if ((nullBits[1] & 1) != 0) { + int fieldOffset7 = buf.getIntLE(offset + 175); + int pos7 = offset + 251 + fieldOffset7; + int arrLen = VarInt.peek(buf, pos7); + pos7 += VarInt.length(buf, pos7); + + for (int i = 0; i < arrLen; i++) { + pos7 += ItemResourceType.computeBytesConsumed(buf, pos7); + } + + if (pos7 - offset > maxEnd) { + maxEnd = pos7 - offset; + } + } + + if ((nullBits[1] & 2) != 0) { + int fieldOffset8 = buf.getIntLE(offset + 179); + int pos8 = offset + 251 + fieldOffset8; + pos8 += ItemTool.computeBytesConsumed(buf, pos8); + if (pos8 - offset > maxEnd) { + maxEnd = pos8 - offset; + } + } + + if ((nullBits[1] & 4) != 0) { + int fieldOffset9 = buf.getIntLE(offset + 183); + int pos9 = offset + 251 + fieldOffset9; + pos9 += ItemWeapon.computeBytesConsumed(buf, pos9); + if (pos9 - offset > maxEnd) { + maxEnd = pos9 - offset; + } + } + + if ((nullBits[1] & 8) != 0) { + int fieldOffset10 = buf.getIntLE(offset + 187); + int pos10 = offset + 251 + fieldOffset10; + pos10 += ItemArmor.computeBytesConsumed(buf, pos10); + if (pos10 - offset > maxEnd) { + maxEnd = pos10 - offset; + } + } + + if ((nullBits[1] & 32) != 0) { + int fieldOffset11 = buf.getIntLE(offset + 191); + int pos11 = offset + 251 + fieldOffset11; + pos11 += ItemUtility.computeBytesConsumed(buf, pos11); + if (pos11 - offset > maxEnd) { + maxEnd = pos11 - offset; + } + } + + if ((nullBits[1] & 128) != 0) { + int fieldOffset12 = buf.getIntLE(offset + 195); + int pos12 = offset + 251 + fieldOffset12; + pos12 += ItemBuilderToolData.computeBytesConsumed(buf, pos12); + if (pos12 - offset > maxEnd) { + maxEnd = pos12 - offset; + } + } + + if ((nullBits[2] & 1) != 0) { + int fieldOffset13 = buf.getIntLE(offset + 199); + int pos13 = offset + 251 + fieldOffset13; + pos13 += ItemEntityConfig.computeBytesConsumed(buf, pos13); + if (pos13 - offset > maxEnd) { + maxEnd = pos13 - offset; + } + } + + if ((nullBits[2] & 2) != 0) { + int fieldOffset14 = buf.getIntLE(offset + 203); + int pos14 = offset + 251 + fieldOffset14; + int sl = VarInt.peek(buf, pos14); + pos14 += VarInt.length(buf, pos14) + sl; + if (pos14 - offset > maxEnd) { + maxEnd = pos14 - offset; + } + } + + if ((nullBits[2] & 4) != 0) { + int fieldOffset15 = buf.getIntLE(offset + 207); + int pos15 = offset + 251 + fieldOffset15; + int arrLen = VarInt.peek(buf, pos15); + pos15 += VarInt.length(buf, pos15); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos15); + pos15 += VarInt.length(buf, pos15) + sl; + } + + if (pos15 - offset > maxEnd) { + maxEnd = pos15 - offset; + } + } + + if ((nullBits[2] & 8) != 0) { + int fieldOffset16 = buf.getIntLE(offset + 211); + int pos16 = offset + 251 + fieldOffset16; + int arrLen = VarInt.peek(buf, pos16); + pos16 += VarInt.length(buf, pos16); + + for (int i = 0; i < arrLen; i++) { + pos16 += ModelParticle.computeBytesConsumed(buf, pos16); + } + + if (pos16 - offset > maxEnd) { + maxEnd = pos16 - offset; + } + } + + if ((nullBits[2] & 16) != 0) { + int fieldOffset17 = buf.getIntLE(offset + 215); + int pos17 = offset + 251 + fieldOffset17; + int arrLen = VarInt.peek(buf, pos17); + pos17 += VarInt.length(buf, pos17); + + for (int i = 0; i < arrLen; i++) { + pos17 += ModelParticle.computeBytesConsumed(buf, pos17); + } + + if (pos17 - offset > maxEnd) { + maxEnd = pos17 - offset; + } + } + + if ((nullBits[2] & 32) != 0) { + int fieldOffset18 = buf.getIntLE(offset + 219); + int pos18 = offset + 251 + fieldOffset18; + int arrLen = VarInt.peek(buf, pos18); + pos18 += VarInt.length(buf, pos18); + + for (int i = 0; i < arrLen; i++) { + pos18 += ModelTrail.computeBytesConsumed(buf, pos18); + } + + if (pos18 - offset > maxEnd) { + maxEnd = pos18 - offset; + } + } + + if ((nullBits[2] & 128) != 0) { + int fieldOffset19 = buf.getIntLE(offset + 223); + int pos19 = offset + 251 + fieldOffset19; + int dictLen = VarInt.peek(buf, pos19); + pos19 += VarInt.length(buf, pos19); + + for (int i = 0; i < dictLen; i++) { + pos19 = ++pos19 + 4; + } + + if (pos19 - offset > maxEnd) { + maxEnd = pos19 - offset; + } + } + + if ((nullBits[3] & 1) != 0) { + int fieldOffset20 = buf.getIntLE(offset + 227); + int pos20 = offset + 251 + fieldOffset20; + int dictLen = VarInt.peek(buf, pos20); + pos20 += VarInt.length(buf, pos20); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos20); + pos20 += VarInt.length(buf, pos20) + sl; + pos20 += 4; + } + + if (pos20 - offset > maxEnd) { + maxEnd = pos20 - offset; + } + } + + if ((nullBits[3] & 2) != 0) { + int fieldOffset21 = buf.getIntLE(offset + 231); + int pos21 = offset + 251 + fieldOffset21; + pos21 += InteractionConfiguration.computeBytesConsumed(buf, pos21); + if (pos21 - offset > maxEnd) { + maxEnd = pos21 - offset; + } + } + + if ((nullBits[3] & 4) != 0) { + int fieldOffset22 = buf.getIntLE(offset + 235); + int pos22 = offset + 251 + fieldOffset22; + int sl = VarInt.peek(buf, pos22); + pos22 += VarInt.length(buf, pos22) + sl; + if (pos22 - offset > maxEnd) { + maxEnd = pos22 - offset; + } + } + + if ((nullBits[3] & 8) != 0) { + int fieldOffset23 = buf.getIntLE(offset + 239); + int pos23 = offset + 251 + fieldOffset23; + int arrLen = VarInt.peek(buf, pos23); + pos23 += VarInt.length(buf, pos23) + arrLen * 4; + if (pos23 - offset > maxEnd) { + maxEnd = pos23 - offset; + } + } + + if ((nullBits[3] & 16) != 0) { + int fieldOffset24 = buf.getIntLE(offset + 243); + int pos24 = offset + 251 + fieldOffset24; + int dictLen = VarInt.peek(buf, pos24); + pos24 += VarInt.length(buf, pos24); + + for (int i = 0; i < dictLen; i++) { + pos24 += 4; + int al = VarInt.peek(buf, pos24); + pos24 += VarInt.length(buf, pos24); + + for (int j = 0; j < al; j++) { + pos24 += ItemAppearanceCondition.computeBytesConsumed(buf, pos24); + } + } + + if (pos24 - offset > maxEnd) { + maxEnd = pos24 - offset; + } + } + + if ((nullBits[3] & 32) != 0) { + int fieldOffset25 = buf.getIntLE(offset + 247); + int pos25 = offset + 251 + fieldOffset25; + int arrLen = VarInt.peek(buf, pos25); + pos25 += VarInt.length(buf, pos25) + arrLen * 4; + if (pos25 - offset > maxEnd) { + maxEnd = pos25 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte[] nullBits = new byte[4]; + if (this.id != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.model != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.texture != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.animation != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.playerAnimationsId != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.icon != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.iconProperties != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.translationProperties != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.resourceTypes != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + if (this.tool != null) { + nullBits[1] = (byte)(nullBits[1] | 2); + } + + if (this.weapon != null) { + nullBits[1] = (byte)(nullBits[1] | 4); + } + + if (this.armor != null) { + nullBits[1] = (byte)(nullBits[1] | 8); + } + + if (this.gliderConfig != null) { + nullBits[1] = (byte)(nullBits[1] | 16); + } + + if (this.utility != null) { + nullBits[1] = (byte)(nullBits[1] | 32); + } + + if (this.blockSelectorTool != null) { + nullBits[1] = (byte)(nullBits[1] | 64); + } + + if (this.builderToolData != null) { + nullBits[1] = (byte)(nullBits[1] | 128); + } + + if (this.itemEntity != null) { + nullBits[2] = (byte)(nullBits[2] | 1); + } + + if (this.set != null) { + nullBits[2] = (byte)(nullBits[2] | 2); + } + + if (this.categories != null) { + nullBits[2] = (byte)(nullBits[2] | 4); + } + + if (this.particles != null) { + nullBits[2] = (byte)(nullBits[2] | 8); + } + + if (this.firstPersonParticles != null) { + nullBits[2] = (byte)(nullBits[2] | 16); + } + + if (this.trails != null) { + nullBits[2] = (byte)(nullBits[2] | 32); + } + + if (this.light != null) { + nullBits[2] = (byte)(nullBits[2] | 64); + } + + if (this.interactions != null) { + nullBits[2] = (byte)(nullBits[2] | 128); + } + + if (this.interactionVars != null) { + nullBits[3] = (byte)(nullBits[3] | 1); + } + + if (this.interactionConfig != null) { + nullBits[3] = (byte)(nullBits[3] | 2); + } + + if (this.droppedItemAnimation != null) { + nullBits[3] = (byte)(nullBits[3] | 4); + } + + if (this.tagIndexes != null) { + nullBits[3] = (byte)(nullBits[3] | 8); + } + + if (this.itemAppearanceConditions != null) { + nullBits[3] = (byte)(nullBits[3] | 16); + } + + if (this.displayEntityStatsHUD != null) { + nullBits[3] = (byte)(nullBits[3] | 32); + } + + if (this.pullbackConfig != null) { + nullBits[3] = (byte)(nullBits[3] | 64); + } + + buf.writeBytes(nullBits); + buf.writeFloatLE(this.scale); + buf.writeByte(this.usePlayerAnimations ? 1 : 0); + buf.writeIntLE(this.maxStack); + buf.writeIntLE(this.reticleIndex); + if (this.iconProperties != null) { + this.iconProperties.serialize(buf); + } else { + buf.writeZero(25); + } + + buf.writeIntLE(this.itemLevel); + buf.writeIntLE(this.qualityIndex); + buf.writeByte(this.consumable ? 1 : 0); + buf.writeByte(this.variant ? 1 : 0); + buf.writeIntLE(this.blockId); + if (this.gliderConfig != null) { + this.gliderConfig.serialize(buf); + } else { + buf.writeZero(16); + } + + if (this.blockSelectorTool != null) { + this.blockSelectorTool.serialize(buf); + } else { + buf.writeZero(4); + } + + if (this.light != null) { + this.light.serialize(buf); + } else { + buf.writeZero(4); + } + + buf.writeDoubleLE(this.durability); + buf.writeIntLE(this.soundEventIndex); + buf.writeIntLE(this.itemSoundSetIndex); + if (this.pullbackConfig != null) { + this.pullbackConfig.serialize(buf); + } else { + buf.writeZero(49); + } + + buf.writeByte(this.clipsGeometry ? 1 : 0); + buf.writeByte(this.renderDeployablePreview ? 1 : 0); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int modelOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int textureOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int animationOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int playerAnimationsIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int iconOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int translationPropertiesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int resourceTypesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int toolOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int weaponOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int armorOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int utilityOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int builderToolDataOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int itemEntityOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int setOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int categoriesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int particlesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int firstPersonParticlesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int trailsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int interactionsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int interactionVarsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int interactionConfigOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int droppedItemAnimationOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagIndexesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int itemAppearanceConditionsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int displayEntityStatsHUDOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.model != null) { + buf.setIntLE(modelOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.model, 4096000); + } else { + buf.setIntLE(modelOffsetSlot, -1); + } + + if (this.texture != null) { + buf.setIntLE(textureOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.texture, 4096000); + } else { + buf.setIntLE(textureOffsetSlot, -1); + } + + if (this.animation != null) { + buf.setIntLE(animationOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.animation, 4096000); + } else { + buf.setIntLE(animationOffsetSlot, -1); + } + + if (this.playerAnimationsId != null) { + buf.setIntLE(playerAnimationsIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.playerAnimationsId, 4096000); + } else { + buf.setIntLE(playerAnimationsIdOffsetSlot, -1); + } + + if (this.icon != null) { + buf.setIntLE(iconOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.icon, 4096000); + } else { + buf.setIntLE(iconOffsetSlot, -1); + } + + if (this.translationProperties != null) { + buf.setIntLE(translationPropertiesOffsetSlot, buf.writerIndex() - varBlockStart); + this.translationProperties.serialize(buf); + } else { + buf.setIntLE(translationPropertiesOffsetSlot, -1); + } + + if (this.resourceTypes != null) { + buf.setIntLE(resourceTypesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.resourceTypes.length > 4096000) { + throw ProtocolException.arrayTooLong("ResourceTypes", this.resourceTypes.length, 4096000); + } + + VarInt.write(buf, this.resourceTypes.length); + + for (ItemResourceType item : this.resourceTypes) { + item.serialize(buf); + } + } else { + buf.setIntLE(resourceTypesOffsetSlot, -1); + } + + if (this.tool != null) { + buf.setIntLE(toolOffsetSlot, buf.writerIndex() - varBlockStart); + this.tool.serialize(buf); + } else { + buf.setIntLE(toolOffsetSlot, -1); + } + + if (this.weapon != null) { + buf.setIntLE(weaponOffsetSlot, buf.writerIndex() - varBlockStart); + this.weapon.serialize(buf); + } else { + buf.setIntLE(weaponOffsetSlot, -1); + } + + if (this.armor != null) { + buf.setIntLE(armorOffsetSlot, buf.writerIndex() - varBlockStart); + this.armor.serialize(buf); + } else { + buf.setIntLE(armorOffsetSlot, -1); + } + + if (this.utility != null) { + buf.setIntLE(utilityOffsetSlot, buf.writerIndex() - varBlockStart); + this.utility.serialize(buf); + } else { + buf.setIntLE(utilityOffsetSlot, -1); + } + + if (this.builderToolData != null) { + buf.setIntLE(builderToolDataOffsetSlot, buf.writerIndex() - varBlockStart); + this.builderToolData.serialize(buf); + } else { + buf.setIntLE(builderToolDataOffsetSlot, -1); + } + + if (this.itemEntity != null) { + buf.setIntLE(itemEntityOffsetSlot, buf.writerIndex() - varBlockStart); + this.itemEntity.serialize(buf); + } else { + buf.setIntLE(itemEntityOffsetSlot, -1); + } + + if (this.set != null) { + buf.setIntLE(setOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.set, 4096000); + } else { + buf.setIntLE(setOffsetSlot, -1); + } + + if (this.categories != null) { + buf.setIntLE(categoriesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.categories.length > 4096000) { + throw ProtocolException.arrayTooLong("Categories", this.categories.length, 4096000); + } + + VarInt.write(buf, this.categories.length); + + for (String item : this.categories) { + PacketIO.writeVarString(buf, item, 4096000); + } + } else { + buf.setIntLE(categoriesOffsetSlot, -1); + } + + if (this.particles != null) { + buf.setIntLE(particlesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.particles.length > 4096000) { + throw ProtocolException.arrayTooLong("Particles", this.particles.length, 4096000); + } + + VarInt.write(buf, this.particles.length); + + for (ModelParticle item : this.particles) { + item.serialize(buf); + } + } else { + buf.setIntLE(particlesOffsetSlot, -1); + } + + if (this.firstPersonParticles != null) { + buf.setIntLE(firstPersonParticlesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.firstPersonParticles.length > 4096000) { + throw ProtocolException.arrayTooLong("FirstPersonParticles", this.firstPersonParticles.length, 4096000); + } + + VarInt.write(buf, this.firstPersonParticles.length); + + for (ModelParticle item : this.firstPersonParticles) { + item.serialize(buf); + } + } else { + buf.setIntLE(firstPersonParticlesOffsetSlot, -1); + } + + if (this.trails != null) { + buf.setIntLE(trailsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.trails.length > 4096000) { + throw ProtocolException.arrayTooLong("Trails", this.trails.length, 4096000); + } + + VarInt.write(buf, this.trails.length); + + for (ModelTrail item : this.trails) { + item.serialize(buf); + } + } else { + buf.setIntLE(trailsOffsetSlot, -1); + } + + if (this.interactions != null) { + buf.setIntLE(interactionsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.interactions.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Interactions", this.interactions.size(), 4096000); + } + + VarInt.write(buf, this.interactions.size()); + + for (Entry e : this.interactions.entrySet()) { + buf.writeByte(e.getKey().getValue()); + buf.writeIntLE(e.getValue()); + } + } else { + buf.setIntLE(interactionsOffsetSlot, -1); + } + + if (this.interactionVars != null) { + buf.setIntLE(interactionVarsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.interactionVars.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("InteractionVars", this.interactionVars.size(), 4096000); + } + + VarInt.write(buf, this.interactionVars.size()); + + for (Entry e : this.interactionVars.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + buf.writeIntLE(e.getValue()); + } + } else { + buf.setIntLE(interactionVarsOffsetSlot, -1); + } + + if (this.interactionConfig != null) { + buf.setIntLE(interactionConfigOffsetSlot, buf.writerIndex() - varBlockStart); + this.interactionConfig.serialize(buf); + } else { + buf.setIntLE(interactionConfigOffsetSlot, -1); + } + + if (this.droppedItemAnimation != null) { + buf.setIntLE(droppedItemAnimationOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.droppedItemAnimation, 4096000); + } else { + buf.setIntLE(droppedItemAnimationOffsetSlot, -1); + } + + if (this.tagIndexes != null) { + buf.setIntLE(tagIndexesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tagIndexes.length > 4096000) { + throw ProtocolException.arrayTooLong("TagIndexes", this.tagIndexes.length, 4096000); + } + + VarInt.write(buf, this.tagIndexes.length); + + for (int item : this.tagIndexes) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagIndexesOffsetSlot, -1); + } + + if (this.itemAppearanceConditions != null) { + buf.setIntLE(itemAppearanceConditionsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.itemAppearanceConditions.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("ItemAppearanceConditions", this.itemAppearanceConditions.size(), 4096000); + } + + VarInt.write(buf, this.itemAppearanceConditions.size()); + + for (Entry e : this.itemAppearanceConditions.entrySet()) { + buf.writeIntLE(e.getKey()); + VarInt.write(buf, e.getValue().length); + + for (ItemAppearanceCondition arrItem : e.getValue()) { + arrItem.serialize(buf); + } + } + } else { + buf.setIntLE(itemAppearanceConditionsOffsetSlot, -1); + } + + if (this.displayEntityStatsHUD != null) { + buf.setIntLE(displayEntityStatsHUDOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.displayEntityStatsHUD.length > 4096000) { + throw ProtocolException.arrayTooLong("DisplayEntityStatsHUD", this.displayEntityStatsHUD.length, 4096000); + } + + VarInt.write(buf, this.displayEntityStatsHUD.length); + + for (int item : this.displayEntityStatsHUD) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(displayEntityStatsHUDOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 251; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.model != null) { + size += PacketIO.stringSize(this.model); + } + + if (this.texture != null) { + size += PacketIO.stringSize(this.texture); + } + + if (this.animation != null) { + size += PacketIO.stringSize(this.animation); + } + + if (this.playerAnimationsId != null) { + size += PacketIO.stringSize(this.playerAnimationsId); + } + + if (this.icon != null) { + size += PacketIO.stringSize(this.icon); + } + + if (this.translationProperties != null) { + size += this.translationProperties.computeSize(); + } + + if (this.resourceTypes != null) { + int resourceTypesSize = 0; + + for (ItemResourceType elem : this.resourceTypes) { + resourceTypesSize += elem.computeSize(); + } + + size += VarInt.size(this.resourceTypes.length) + resourceTypesSize; + } + + if (this.tool != null) { + size += this.tool.computeSize(); + } + + if (this.weapon != null) { + size += this.weapon.computeSize(); + } + + if (this.armor != null) { + size += this.armor.computeSize(); + } + + if (this.utility != null) { + size += this.utility.computeSize(); + } + + if (this.builderToolData != null) { + size += this.builderToolData.computeSize(); + } + + if (this.itemEntity != null) { + size += this.itemEntity.computeSize(); + } + + if (this.set != null) { + size += PacketIO.stringSize(this.set); + } + + if (this.categories != null) { + int categoriesSize = 0; + + for (String elem : this.categories) { + categoriesSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.categories.length) + categoriesSize; + } + + if (this.particles != null) { + int particlesSize = 0; + + for (ModelParticle elem : this.particles) { + particlesSize += elem.computeSize(); + } + + size += VarInt.size(this.particles.length) + particlesSize; + } + + if (this.firstPersonParticles != null) { + int firstPersonParticlesSize = 0; + + for (ModelParticle elem : this.firstPersonParticles) { + firstPersonParticlesSize += elem.computeSize(); + } + + size += VarInt.size(this.firstPersonParticles.length) + firstPersonParticlesSize; + } + + if (this.trails != null) { + int trailsSize = 0; + + for (ModelTrail elem : this.trails) { + trailsSize += elem.computeSize(); + } + + size += VarInt.size(this.trails.length) + trailsSize; + } + + if (this.interactions != null) { + size += VarInt.size(this.interactions.size()) + this.interactions.size() * 5; + } + + if (this.interactionVars != null) { + int interactionVarsSize = 0; + + for (Entry kvp : this.interactionVars.entrySet()) { + interactionVarsSize += PacketIO.stringSize(kvp.getKey()) + 4; + } + + size += VarInt.size(this.interactionVars.size()) + interactionVarsSize; + } + + if (this.interactionConfig != null) { + size += this.interactionConfig.computeSize(); + } + + if (this.droppedItemAnimation != null) { + size += PacketIO.stringSize(this.droppedItemAnimation); + } + + if (this.tagIndexes != null) { + size += VarInt.size(this.tagIndexes.length) + this.tagIndexes.length * 4; + } + + if (this.itemAppearanceConditions != null) { + int itemAppearanceConditionsSize = 0; + + for (Entry kvp : this.itemAppearanceConditions.entrySet()) { + itemAppearanceConditionsSize += 4 + VarInt.size(kvp.getValue().length) + Arrays.stream(kvp.getValue()).mapToInt(inner -> inner.computeSize()).sum(); + } + + size += VarInt.size(this.itemAppearanceConditions.size()) + itemAppearanceConditionsSize; + } + + if (this.displayEntityStatsHUD != null) { + size += VarInt.size(this.displayEntityStatsHUD.length) + this.displayEntityStatsHUD.length * 4; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 251) { + return ValidationResult.error("Buffer too small: expected at least 251 bytes"); + } else { + byte[] nullBits = PacketIO.readBytes(buffer, offset, 4); + if ((nullBits[0] & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 147); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 251 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits[0] & 2) != 0) { + int modelOffset = buffer.getIntLE(offset + 151); + if (modelOffset < 0) { + return ValidationResult.error("Invalid offset for Model"); + } + + int posx = offset + 251 + modelOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Model"); + } + + int modelLen = VarInt.peek(buffer, posx); + if (modelLen < 0) { + return ValidationResult.error("Invalid string length for Model"); + } + + if (modelLen > 4096000) { + return ValidationResult.error("Model exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += modelLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Model"); + } + } + + if ((nullBits[0] & 4) != 0) { + int textureOffset = buffer.getIntLE(offset + 155); + if (textureOffset < 0) { + return ValidationResult.error("Invalid offset for Texture"); + } + + int posxx = offset + 251 + textureOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Texture"); + } + + int textureLen = VarInt.peek(buffer, posxx); + if (textureLen < 0) { + return ValidationResult.error("Invalid string length for Texture"); + } + + if (textureLen > 4096000) { + return ValidationResult.error("Texture exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += textureLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Texture"); + } + } + + if ((nullBits[0] & 8) != 0) { + int animationOffset = buffer.getIntLE(offset + 159); + if (animationOffset < 0) { + return ValidationResult.error("Invalid offset for Animation"); + } + + int posxxx = offset + 251 + animationOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Animation"); + } + + int animationLen = VarInt.peek(buffer, posxxx); + if (animationLen < 0) { + return ValidationResult.error("Invalid string length for Animation"); + } + + if (animationLen > 4096000) { + return ValidationResult.error("Animation exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += animationLen; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Animation"); + } + } + + if ((nullBits[0] & 16) != 0) { + int playerAnimationsIdOffset = buffer.getIntLE(offset + 163); + if (playerAnimationsIdOffset < 0) { + return ValidationResult.error("Invalid offset for PlayerAnimationsId"); + } + + int posxxxx = offset + 251 + playerAnimationsIdOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for PlayerAnimationsId"); + } + + int playerAnimationsIdLen = VarInt.peek(buffer, posxxxx); + if (playerAnimationsIdLen < 0) { + return ValidationResult.error("Invalid string length for PlayerAnimationsId"); + } + + if (playerAnimationsIdLen > 4096000) { + return ValidationResult.error("PlayerAnimationsId exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + posxxxx += playerAnimationsIdLen; + if (posxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading PlayerAnimationsId"); + } + } + + if ((nullBits[0] & 32) != 0) { + int iconOffset = buffer.getIntLE(offset + 167); + if (iconOffset < 0) { + return ValidationResult.error("Invalid offset for Icon"); + } + + int posxxxxx = offset + 251 + iconOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Icon"); + } + + int iconLen = VarInt.peek(buffer, posxxxxx); + if (iconLen < 0) { + return ValidationResult.error("Invalid string length for Icon"); + } + + if (iconLen > 4096000) { + return ValidationResult.error("Icon exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += iconLen; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Icon"); + } + } + + if ((nullBits[0] & 128) != 0) { + int translationPropertiesOffset = buffer.getIntLE(offset + 171); + if (translationPropertiesOffset < 0) { + return ValidationResult.error("Invalid offset for TranslationProperties"); + } + + int posxxxxxx = offset + 251 + translationPropertiesOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for TranslationProperties"); + } + + ValidationResult translationPropertiesResult = ItemTranslationProperties.validateStructure(buffer, posxxxxxx); + if (!translationPropertiesResult.isValid()) { + return ValidationResult.error("Invalid TranslationProperties: " + translationPropertiesResult.error()); + } + + posxxxxxx += ItemTranslationProperties.computeBytesConsumed(buffer, posxxxxxx); + } + + if ((nullBits[1] & 1) != 0) { + int resourceTypesOffset = buffer.getIntLE(offset + 175); + if (resourceTypesOffset < 0) { + return ValidationResult.error("Invalid offset for ResourceTypes"); + } + + int posxxxxxxx = offset + 251 + resourceTypesOffset; + if (posxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ResourceTypes"); + } + + int resourceTypesCount = VarInt.peek(buffer, posxxxxxxx); + if (resourceTypesCount < 0) { + return ValidationResult.error("Invalid array count for ResourceTypes"); + } + + if (resourceTypesCount > 4096000) { + return ValidationResult.error("ResourceTypes exceeds max length 4096000"); + } + + posxxxxxxx += VarInt.length(buffer, posxxxxxxx); + + for (int i = 0; i < resourceTypesCount; i++) { + ValidationResult structResult = ItemResourceType.validateStructure(buffer, posxxxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ItemResourceType in ResourceTypes[" + i + "]: " + structResult.error()); + } + + posxxxxxxx += ItemResourceType.computeBytesConsumed(buffer, posxxxxxxx); + } + } + + if ((nullBits[1] & 2) != 0) { + int toolOffset = buffer.getIntLE(offset + 179); + if (toolOffset < 0) { + return ValidationResult.error("Invalid offset for Tool"); + } + + int posxxxxxxxx = offset + 251 + toolOffset; + if (posxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tool"); + } + + ValidationResult toolResult = ItemTool.validateStructure(buffer, posxxxxxxxx); + if (!toolResult.isValid()) { + return ValidationResult.error("Invalid Tool: " + toolResult.error()); + } + + posxxxxxxxx += ItemTool.computeBytesConsumed(buffer, posxxxxxxxx); + } + + if ((nullBits[1] & 4) != 0) { + int weaponOffset = buffer.getIntLE(offset + 183); + if (weaponOffset < 0) { + return ValidationResult.error("Invalid offset for Weapon"); + } + + int posxxxxxxxxx = offset + 251 + weaponOffset; + if (posxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Weapon"); + } + + ValidationResult weaponResult = ItemWeapon.validateStructure(buffer, posxxxxxxxxx); + if (!weaponResult.isValid()) { + return ValidationResult.error("Invalid Weapon: " + weaponResult.error()); + } + + posxxxxxxxxx += ItemWeapon.computeBytesConsumed(buffer, posxxxxxxxxx); + } + + if ((nullBits[1] & 8) != 0) { + int armorOffset = buffer.getIntLE(offset + 187); + if (armorOffset < 0) { + return ValidationResult.error("Invalid offset for Armor"); + } + + int posxxxxxxxxxx = offset + 251 + armorOffset; + if (posxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Armor"); + } + + ValidationResult armorResult = ItemArmor.validateStructure(buffer, posxxxxxxxxxx); + if (!armorResult.isValid()) { + return ValidationResult.error("Invalid Armor: " + armorResult.error()); + } + + posxxxxxxxxxx += ItemArmor.computeBytesConsumed(buffer, posxxxxxxxxxx); + } + + if ((nullBits[1] & 32) != 0) { + int utilityOffset = buffer.getIntLE(offset + 191); + if (utilityOffset < 0) { + return ValidationResult.error("Invalid offset for Utility"); + } + + int posxxxxxxxxxxx = offset + 251 + utilityOffset; + if (posxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Utility"); + } + + ValidationResult utilityResult = ItemUtility.validateStructure(buffer, posxxxxxxxxxxx); + if (!utilityResult.isValid()) { + return ValidationResult.error("Invalid Utility: " + utilityResult.error()); + } + + posxxxxxxxxxxx += ItemUtility.computeBytesConsumed(buffer, posxxxxxxxxxxx); + } + + if ((nullBits[1] & 128) != 0) { + int builderToolDataOffset = buffer.getIntLE(offset + 195); + if (builderToolDataOffset < 0) { + return ValidationResult.error("Invalid offset for BuilderToolData"); + } + + int posxxxxxxxxxxxx = offset + 251 + builderToolDataOffset; + if (posxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BuilderToolData"); + } + + ValidationResult builderToolDataResult = ItemBuilderToolData.validateStructure(buffer, posxxxxxxxxxxxx); + if (!builderToolDataResult.isValid()) { + return ValidationResult.error("Invalid BuilderToolData: " + builderToolDataResult.error()); + } + + posxxxxxxxxxxxx += ItemBuilderToolData.computeBytesConsumed(buffer, posxxxxxxxxxxxx); + } + + if ((nullBits[2] & 1) != 0) { + int itemEntityOffset = buffer.getIntLE(offset + 199); + if (itemEntityOffset < 0) { + return ValidationResult.error("Invalid offset for ItemEntity"); + } + + int posxxxxxxxxxxxxx = offset + 251 + itemEntityOffset; + if (posxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ItemEntity"); + } + + ValidationResult itemEntityResult = ItemEntityConfig.validateStructure(buffer, posxxxxxxxxxxxxx); + if (!itemEntityResult.isValid()) { + return ValidationResult.error("Invalid ItemEntity: " + itemEntityResult.error()); + } + + posxxxxxxxxxxxxx += ItemEntityConfig.computeBytesConsumed(buffer, posxxxxxxxxxxxxx); + } + + if ((nullBits[2] & 2) != 0) { + int setOffset = buffer.getIntLE(offset + 203); + if (setOffset < 0) { + return ValidationResult.error("Invalid offset for Set"); + } + + int posxxxxxxxxxxxxxx = offset + 251 + setOffset; + if (posxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Set"); + } + + int setLen = VarInt.peek(buffer, posxxxxxxxxxxxxxx); + if (setLen < 0) { + return ValidationResult.error("Invalid string length for Set"); + } + + if (setLen > 4096000) { + return ValidationResult.error("Set exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxx); + posxxxxxxxxxxxxxx += setLen; + if (posxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Set"); + } + } + + if ((nullBits[2] & 4) != 0) { + int categoriesOffset = buffer.getIntLE(offset + 207); + if (categoriesOffset < 0) { + return ValidationResult.error("Invalid offset for Categories"); + } + + int posxxxxxxxxxxxxxxx = offset + 251 + categoriesOffset; + if (posxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Categories"); + } + + int categoriesCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxx); + if (categoriesCount < 0) { + return ValidationResult.error("Invalid array count for Categories"); + } + + if (categoriesCount > 4096000) { + return ValidationResult.error("Categories exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxx); + + for (int i = 0; i < categoriesCount; i++) { + int strLen = VarInt.peek(buffer, posxxxxxxxxxxxxxxx); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in Categories"); + } + + posxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxx); + posxxxxxxxxxxxxxxx += strLen; + if (posxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in Categories"); + } + } + } + + if ((nullBits[2] & 8) != 0) { + int particlesOffset = buffer.getIntLE(offset + 211); + if (particlesOffset < 0) { + return ValidationResult.error("Invalid offset for Particles"); + } + + int posxxxxxxxxxxxxxxxx = offset + 251 + particlesOffset; + if (posxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Particles"); + } + + int particlesCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxx); + if (particlesCount < 0) { + return ValidationResult.error("Invalid array count for Particles"); + } + + if (particlesCount > 4096000) { + return ValidationResult.error("Particles exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxx); + + for (int i = 0; i < particlesCount; i++) { + ValidationResult structResult = ModelParticle.validateStructure(buffer, posxxxxxxxxxxxxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelParticle in Particles[" + i + "]: " + structResult.error()); + } + + posxxxxxxxxxxxxxxxx += ModelParticle.computeBytesConsumed(buffer, posxxxxxxxxxxxxxxxx); + } + } + + if ((nullBits[2] & 16) != 0) { + int firstPersonParticlesOffset = buffer.getIntLE(offset + 215); + if (firstPersonParticlesOffset < 0) { + return ValidationResult.error("Invalid offset for FirstPersonParticles"); + } + + int posxxxxxxxxxxxxxxxxx = offset + 251 + firstPersonParticlesOffset; + if (posxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FirstPersonParticles"); + } + + int firstPersonParticlesCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxx); + if (firstPersonParticlesCount < 0) { + return ValidationResult.error("Invalid array count for FirstPersonParticles"); + } + + if (firstPersonParticlesCount > 4096000) { + return ValidationResult.error("FirstPersonParticles exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxx); + + for (int i = 0; i < firstPersonParticlesCount; i++) { + ValidationResult structResult = ModelParticle.validateStructure(buffer, posxxxxxxxxxxxxxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelParticle in FirstPersonParticles[" + i + "]: " + structResult.error()); + } + + posxxxxxxxxxxxxxxxxx += ModelParticle.computeBytesConsumed(buffer, posxxxxxxxxxxxxxxxxx); + } + } + + if ((nullBits[2] & 32) != 0) { + int trailsOffset = buffer.getIntLE(offset + 219); + if (trailsOffset < 0) { + return ValidationResult.error("Invalid offset for Trails"); + } + + int posxxxxxxxxxxxxxxxxxx = offset + 251 + trailsOffset; + if (posxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Trails"); + } + + int trailsCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxx); + if (trailsCount < 0) { + return ValidationResult.error("Invalid array count for Trails"); + } + + if (trailsCount > 4096000) { + return ValidationResult.error("Trails exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxx); + + for (int i = 0; i < trailsCount; i++) { + ValidationResult structResult = ModelTrail.validateStructure(buffer, posxxxxxxxxxxxxxxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelTrail in Trails[" + i + "]: " + structResult.error()); + } + + posxxxxxxxxxxxxxxxxxx += ModelTrail.computeBytesConsumed(buffer, posxxxxxxxxxxxxxxxxxx); + } + } + + if ((nullBits[2] & 128) != 0) { + int interactionsOffset = buffer.getIntLE(offset + 223); + if (interactionsOffset < 0) { + return ValidationResult.error("Invalid offset for Interactions"); + } + + int posxxxxxxxxxxxxxxxxxxx = offset + 251 + interactionsOffset; + if (posxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Interactions"); + } + + int interactionsCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxxx); + if (interactionsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Interactions"); + } + + if (interactionsCount > 4096000) { + return ValidationResult.error("Interactions exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxxx); + + for (int i = 0; i < interactionsCount; i++) { + posxxxxxxxxxxxxxxxxxxx = ++posxxxxxxxxxxxxxxxxxxx + 4; + if (posxxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + if ((nullBits[3] & 1) != 0) { + int interactionVarsOffset = buffer.getIntLE(offset + 227); + if (interactionVarsOffset < 0) { + return ValidationResult.error("Invalid offset for InteractionVars"); + } + + int posxxxxxxxxxxxxxxxxxxxx = offset + 251 + interactionVarsOffset; + if (posxxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for InteractionVars"); + } + + int interactionVarsCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxxxx); + if (interactionVarsCount < 0) { + return ValidationResult.error("Invalid dictionary count for InteractionVars"); + } + + if (interactionVarsCount > 4096000) { + return ValidationResult.error("InteractionVars exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxxxx); + + for (int ix = 0; ix < interactionVarsCount; ix++) { + int keyLen = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxxxx); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxxxx); + posxxxxxxxxxxxxxxxxxxxx += keyLen; + if (posxxxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxxxxxxxxxxxxxxx += 4; + if (posxxxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + if ((nullBits[3] & 2) != 0) { + int interactionConfigOffset = buffer.getIntLE(offset + 231); + if (interactionConfigOffset < 0) { + return ValidationResult.error("Invalid offset for InteractionConfig"); + } + + int posxxxxxxxxxxxxxxxxxxxxx = offset + 251 + interactionConfigOffset; + if (posxxxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for InteractionConfig"); + } + + ValidationResult interactionConfigResult = InteractionConfiguration.validateStructure(buffer, posxxxxxxxxxxxxxxxxxxxxx); + if (!interactionConfigResult.isValid()) { + return ValidationResult.error("Invalid InteractionConfig: " + interactionConfigResult.error()); + } + + posxxxxxxxxxxxxxxxxxxxxx += InteractionConfiguration.computeBytesConsumed(buffer, posxxxxxxxxxxxxxxxxxxxxx); + } + + if ((nullBits[3] & 4) != 0) { + int droppedItemAnimationOffset = buffer.getIntLE(offset + 235); + if (droppedItemAnimationOffset < 0) { + return ValidationResult.error("Invalid offset for DroppedItemAnimation"); + } + + int posxxxxxxxxxxxxxxxxxxxxxx = offset + 251 + droppedItemAnimationOffset; + if (posxxxxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for DroppedItemAnimation"); + } + + int droppedItemAnimationLen = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxxxxxx); + if (droppedItemAnimationLen < 0) { + return ValidationResult.error("Invalid string length for DroppedItemAnimation"); + } + + if (droppedItemAnimationLen > 4096000) { + return ValidationResult.error("DroppedItemAnimation exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxxxxxx); + posxxxxxxxxxxxxxxxxxxxxxx += droppedItemAnimationLen; + if (posxxxxxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading DroppedItemAnimation"); + } + } + + if ((nullBits[3] & 8) != 0) { + int tagIndexesOffset = buffer.getIntLE(offset + 239); + if (tagIndexesOffset < 0) { + return ValidationResult.error("Invalid offset for TagIndexes"); + } + + int posxxxxxxxxxxxxxxxxxxxxxxx = offset + 251 + tagIndexesOffset; + if (posxxxxxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for TagIndexes"); + } + + int tagIndexesCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxxxxxxx); + if (tagIndexesCount < 0) { + return ValidationResult.error("Invalid array count for TagIndexes"); + } + + if (tagIndexesCount > 4096000) { + return ValidationResult.error("TagIndexes exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxxxxxxx); + posxxxxxxxxxxxxxxxxxxxxxxx += tagIndexesCount * 4; + if (posxxxxxxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading TagIndexes"); + } + } + + if ((nullBits[3] & 16) != 0) { + int itemAppearanceConditionsOffset = buffer.getIntLE(offset + 243); + if (itemAppearanceConditionsOffset < 0) { + return ValidationResult.error("Invalid offset for ItemAppearanceConditions"); + } + + int posxxxxxxxxxxxxxxxxxxxxxxxx = offset + 251 + itemAppearanceConditionsOffset; + if (posxxxxxxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ItemAppearanceConditions"); + } + + int itemAppearanceConditionsCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxxxxxxxx); + if (itemAppearanceConditionsCount < 0) { + return ValidationResult.error("Invalid dictionary count for ItemAppearanceConditions"); + } + + if (itemAppearanceConditionsCount > 4096000) { + return ValidationResult.error("ItemAppearanceConditions exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxxxxxxxx); + + for (int ix = 0; ix < itemAppearanceConditionsCount; ix++) { + posxxxxxxxxxxxxxxxxxxxxxxxx += 4; + if (posxxxxxxxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + int valueArrCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxxxxxxxx); + if (valueArrCount < 0) { + return ValidationResult.error("Invalid array count for value"); + } + + posxxxxxxxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxxxxxxxx); + + for (int valueArrIdx = 0; valueArrIdx < valueArrCount; valueArrIdx++) { + posxxxxxxxxxxxxxxxxxxxxxxxx += ItemAppearanceCondition.computeBytesConsumed(buffer, posxxxxxxxxxxxxxxxxxxxxxxxx); + } + } + } + + if ((nullBits[3] & 32) != 0) { + int displayEntityStatsHUDOffset = buffer.getIntLE(offset + 247); + if (displayEntityStatsHUDOffset < 0) { + return ValidationResult.error("Invalid offset for DisplayEntityStatsHUD"); + } + + int posxxxxxxxxxxxxxxxxxxxxxxxxx = offset + 251 + displayEntityStatsHUDOffset; + if (posxxxxxxxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for DisplayEntityStatsHUD"); + } + + int displayEntityStatsHUDCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxxxxxxxxx); + if (displayEntityStatsHUDCount < 0) { + return ValidationResult.error("Invalid array count for DisplayEntityStatsHUD"); + } + + if (displayEntityStatsHUDCount > 4096000) { + return ValidationResult.error("DisplayEntityStatsHUD exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxxxxxxxxx); + posxxxxxxxxxxxxxxxxxxxxxxxxx += displayEntityStatsHUDCount * 4; + if (posxxxxxxxxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading DisplayEntityStatsHUD"); + } + } + + return ValidationResult.OK; + } + } + + public ItemBase clone() { + ItemBase copy = new ItemBase(); + copy.id = this.id; + copy.model = this.model; + copy.scale = this.scale; + copy.texture = this.texture; + copy.animation = this.animation; + copy.playerAnimationsId = this.playerAnimationsId; + copy.usePlayerAnimations = this.usePlayerAnimations; + copy.maxStack = this.maxStack; + copy.reticleIndex = this.reticleIndex; + copy.icon = this.icon; + copy.iconProperties = this.iconProperties != null ? this.iconProperties.clone() : null; + copy.translationProperties = this.translationProperties != null ? this.translationProperties.clone() : null; + copy.itemLevel = this.itemLevel; + copy.qualityIndex = this.qualityIndex; + copy.resourceTypes = this.resourceTypes != null ? Arrays.stream(this.resourceTypes).map(ex -> ex.clone()).toArray(ItemResourceType[]::new) : null; + copy.consumable = this.consumable; + copy.variant = this.variant; + copy.blockId = this.blockId; + copy.tool = this.tool != null ? this.tool.clone() : null; + copy.weapon = this.weapon != null ? this.weapon.clone() : null; + copy.armor = this.armor != null ? this.armor.clone() : null; + copy.gliderConfig = this.gliderConfig != null ? this.gliderConfig.clone() : null; + copy.utility = this.utility != null ? this.utility.clone() : null; + copy.blockSelectorTool = this.blockSelectorTool != null ? this.blockSelectorTool.clone() : null; + copy.builderToolData = this.builderToolData != null ? this.builderToolData.clone() : null; + copy.itemEntity = this.itemEntity != null ? this.itemEntity.clone() : null; + copy.set = this.set; + copy.categories = this.categories != null ? Arrays.copyOf(this.categories, this.categories.length) : null; + copy.particles = this.particles != null ? Arrays.stream(this.particles).map(ex -> ex.clone()).toArray(ModelParticle[]::new) : null; + copy.firstPersonParticles = this.firstPersonParticles != null + ? Arrays.stream(this.firstPersonParticles).map(ex -> ex.clone()).toArray(ModelParticle[]::new) + : null; + copy.trails = this.trails != null ? Arrays.stream(this.trails).map(ex -> ex.clone()).toArray(ModelTrail[]::new) : null; + copy.light = this.light != null ? this.light.clone() : null; + copy.durability = this.durability; + copy.soundEventIndex = this.soundEventIndex; + copy.itemSoundSetIndex = this.itemSoundSetIndex; + copy.interactions = this.interactions != null ? new HashMap<>(this.interactions) : null; + copy.interactionVars = this.interactionVars != null ? new HashMap<>(this.interactionVars) : null; + copy.interactionConfig = this.interactionConfig != null ? this.interactionConfig.clone() : null; + copy.droppedItemAnimation = this.droppedItemAnimation; + copy.tagIndexes = this.tagIndexes != null ? Arrays.copyOf(this.tagIndexes, this.tagIndexes.length) : null; + if (this.itemAppearanceConditions != null) { + Map m = new HashMap<>(); + + for (Entry e : this.itemAppearanceConditions.entrySet()) { + m.put(e.getKey(), Arrays.stream(e.getValue()).map(x -> x.clone()).toArray(ItemAppearanceCondition[]::new)); + } + + copy.itemAppearanceConditions = m; + } + + copy.displayEntityStatsHUD = this.displayEntityStatsHUD != null ? Arrays.copyOf(this.displayEntityStatsHUD, this.displayEntityStatsHUD.length) : null; + copy.pullbackConfig = this.pullbackConfig != null ? this.pullbackConfig.clone() : null; + copy.clipsGeometry = this.clipsGeometry; + copy.renderDeployablePreview = this.renderDeployablePreview; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemBase other) + ? false + : Objects.equals(this.id, other.id) + && Objects.equals(this.model, other.model) + && this.scale == other.scale + && Objects.equals(this.texture, other.texture) + && Objects.equals(this.animation, other.animation) + && Objects.equals(this.playerAnimationsId, other.playerAnimationsId) + && this.usePlayerAnimations == other.usePlayerAnimations + && this.maxStack == other.maxStack + && this.reticleIndex == other.reticleIndex + && Objects.equals(this.icon, other.icon) + && Objects.equals(this.iconProperties, other.iconProperties) + && Objects.equals(this.translationProperties, other.translationProperties) + && this.itemLevel == other.itemLevel + && this.qualityIndex == other.qualityIndex + && Arrays.equals((Object[])this.resourceTypes, (Object[])other.resourceTypes) + && this.consumable == other.consumable + && this.variant == other.variant + && this.blockId == other.blockId + && Objects.equals(this.tool, other.tool) + && Objects.equals(this.weapon, other.weapon) + && Objects.equals(this.armor, other.armor) + && Objects.equals(this.gliderConfig, other.gliderConfig) + && Objects.equals(this.utility, other.utility) + && Objects.equals(this.blockSelectorTool, other.blockSelectorTool) + && Objects.equals(this.builderToolData, other.builderToolData) + && Objects.equals(this.itemEntity, other.itemEntity) + && Objects.equals(this.set, other.set) + && Arrays.equals((Object[])this.categories, (Object[])other.categories) + && Arrays.equals((Object[])this.particles, (Object[])other.particles) + && Arrays.equals((Object[])this.firstPersonParticles, (Object[])other.firstPersonParticles) + && Arrays.equals((Object[])this.trails, (Object[])other.trails) + && Objects.equals(this.light, other.light) + && this.durability == other.durability + && this.soundEventIndex == other.soundEventIndex + && this.itemSoundSetIndex == other.itemSoundSetIndex + && Objects.equals(this.interactions, other.interactions) + && Objects.equals(this.interactionVars, other.interactionVars) + && Objects.equals(this.interactionConfig, other.interactionConfig) + && Objects.equals(this.droppedItemAnimation, other.droppedItemAnimation) + && Arrays.equals(this.tagIndexes, other.tagIndexes) + && Objects.equals(this.itemAppearanceConditions, other.itemAppearanceConditions) + && Arrays.equals(this.displayEntityStatsHUD, other.displayEntityStatsHUD) + && Objects.equals(this.pullbackConfig, other.pullbackConfig) + && this.clipsGeometry == other.clipsGeometry + && this.renderDeployablePreview == other.renderDeployablePreview; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.id); + result = 31 * result + Objects.hashCode(this.model); + result = 31 * result + Float.hashCode(this.scale); + result = 31 * result + Objects.hashCode(this.texture); + result = 31 * result + Objects.hashCode(this.animation); + result = 31 * result + Objects.hashCode(this.playerAnimationsId); + result = 31 * result + Boolean.hashCode(this.usePlayerAnimations); + result = 31 * result + Integer.hashCode(this.maxStack); + result = 31 * result + Integer.hashCode(this.reticleIndex); + result = 31 * result + Objects.hashCode(this.icon); + result = 31 * result + Objects.hashCode(this.iconProperties); + result = 31 * result + Objects.hashCode(this.translationProperties); + result = 31 * result + Integer.hashCode(this.itemLevel); + result = 31 * result + Integer.hashCode(this.qualityIndex); + result = 31 * result + Arrays.hashCode((Object[])this.resourceTypes); + result = 31 * result + Boolean.hashCode(this.consumable); + result = 31 * result + Boolean.hashCode(this.variant); + result = 31 * result + Integer.hashCode(this.blockId); + result = 31 * result + Objects.hashCode(this.tool); + result = 31 * result + Objects.hashCode(this.weapon); + result = 31 * result + Objects.hashCode(this.armor); + result = 31 * result + Objects.hashCode(this.gliderConfig); + result = 31 * result + Objects.hashCode(this.utility); + result = 31 * result + Objects.hashCode(this.blockSelectorTool); + result = 31 * result + Objects.hashCode(this.builderToolData); + result = 31 * result + Objects.hashCode(this.itemEntity); + result = 31 * result + Objects.hashCode(this.set); + result = 31 * result + Arrays.hashCode((Object[])this.categories); + result = 31 * result + Arrays.hashCode((Object[])this.particles); + result = 31 * result + Arrays.hashCode((Object[])this.firstPersonParticles); + result = 31 * result + Arrays.hashCode((Object[])this.trails); + result = 31 * result + Objects.hashCode(this.light); + result = 31 * result + Double.hashCode(this.durability); + result = 31 * result + Integer.hashCode(this.soundEventIndex); + result = 31 * result + Integer.hashCode(this.itemSoundSetIndex); + result = 31 * result + Objects.hashCode(this.interactions); + result = 31 * result + Objects.hashCode(this.interactionVars); + result = 31 * result + Objects.hashCode(this.interactionConfig); + result = 31 * result + Objects.hashCode(this.droppedItemAnimation); + result = 31 * result + Arrays.hashCode(this.tagIndexes); + result = 31 * result + Objects.hashCode(this.itemAppearanceConditions); + result = 31 * result + Arrays.hashCode(this.displayEntityStatsHUD); + result = 31 * result + Objects.hashCode(this.pullbackConfig); + result = 31 * result + Boolean.hashCode(this.clipsGeometry); + return 31 * result + Boolean.hashCode(this.renderDeployablePreview); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemBuilderToolData.java b/src/com/hypixel/hytale/protocol/ItemBuilderToolData.java new file mode 100644 index 0000000..3548ccb --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemBuilderToolData.java @@ -0,0 +1,315 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolState; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemBuilderToolData { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String[] ui; + @Nullable + public BuilderToolState[] tools; + + public ItemBuilderToolData() { + } + + public ItemBuilderToolData(@Nullable String[] ui, @Nullable BuilderToolState[] tools) { + this.ui = ui; + this.tools = tools; + } + + public ItemBuilderToolData(@Nonnull ItemBuilderToolData other) { + this.ui = other.ui; + this.tools = other.tools; + } + + @Nonnull + public static ItemBuilderToolData deserialize(@Nonnull ByteBuf buf, int offset) { + ItemBuilderToolData obj = new ItemBuilderToolData(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int uiCount = VarInt.peek(buf, varPos0); + if (uiCount < 0) { + throw ProtocolException.negativeLength("Ui", uiCount); + } + + if (uiCount > 4096000) { + throw ProtocolException.arrayTooLong("Ui", uiCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + uiCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Ui", varPos0 + varIntLen + uiCount * 1, buf.readableBytes()); + } + + obj.ui = new String[uiCount]; + int elemPos = varPos0 + varIntLen; + + for (int i = 0; i < uiCount; i++) { + int strLen = VarInt.peek(buf, elemPos); + if (strLen < 0) { + throw ProtocolException.negativeLength("ui[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("ui[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, elemPos); + obj.ui[i] = PacketIO.readVarString(buf, elemPos); + elemPos += strVarLen + strLen; + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int toolsCount = VarInt.peek(buf, varPos1); + if (toolsCount < 0) { + throw ProtocolException.negativeLength("Tools", toolsCount); + } + + if (toolsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tools", toolsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + toolsCount * 2L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tools", varPos1 + varIntLen + toolsCount * 2, buf.readableBytes()); + } + + obj.tools = new BuilderToolState[toolsCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < toolsCount; i++) { + obj.tools[i] = BuilderToolState.deserialize(buf, elemPos); + elemPos += BuilderToolState.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += BuilderToolState.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.ui != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.tools != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int uiOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int toolsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.ui != null) { + buf.setIntLE(uiOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.ui.length > 4096000) { + throw ProtocolException.arrayTooLong("Ui", this.ui.length, 4096000); + } + + VarInt.write(buf, this.ui.length); + + for (String item : this.ui) { + PacketIO.writeVarString(buf, item, 4096000); + } + } else { + buf.setIntLE(uiOffsetSlot, -1); + } + + if (this.tools != null) { + buf.setIntLE(toolsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tools.length > 4096000) { + throw ProtocolException.arrayTooLong("Tools", this.tools.length, 4096000); + } + + VarInt.write(buf, this.tools.length); + + for (BuilderToolState item : this.tools) { + item.serialize(buf); + } + } else { + buf.setIntLE(toolsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 9; + if (this.ui != null) { + int uiSize = 0; + + for (String elem : this.ui) { + uiSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.ui.length) + uiSize; + } + + if (this.tools != null) { + int toolsSize = 0; + + for (BuilderToolState elem : this.tools) { + toolsSize += elem.computeSize(); + } + + size += VarInt.size(this.tools.length) + toolsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int uiOffset = buffer.getIntLE(offset + 1); + if (uiOffset < 0) { + return ValidationResult.error("Invalid offset for Ui"); + } + + int pos = offset + 9 + uiOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Ui"); + } + + int uiCount = VarInt.peek(buffer, pos); + if (uiCount < 0) { + return ValidationResult.error("Invalid array count for Ui"); + } + + if (uiCount > 4096000) { + return ValidationResult.error("Ui exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < uiCount; i++) { + int strLen = VarInt.peek(buffer, pos); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in Ui"); + } + + pos += VarInt.length(buffer, pos); + pos += strLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in Ui"); + } + } + } + + if ((nullBits & 2) != 0) { + int toolsOffset = buffer.getIntLE(offset + 5); + if (toolsOffset < 0) { + return ValidationResult.error("Invalid offset for Tools"); + } + + int posx = offset + 9 + toolsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tools"); + } + + int toolsCount = VarInt.peek(buffer, posx); + if (toolsCount < 0) { + return ValidationResult.error("Invalid array count for Tools"); + } + + if (toolsCount > 4096000) { + return ValidationResult.error("Tools exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < toolsCount; i++) { + ValidationResult structResult = BuilderToolState.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid BuilderToolState in Tools[" + i + "]: " + structResult.error()); + } + + posx += BuilderToolState.computeBytesConsumed(buffer, posx); + } + } + + return ValidationResult.OK; + } + } + + public ItemBuilderToolData clone() { + ItemBuilderToolData copy = new ItemBuilderToolData(); + copy.ui = this.ui != null ? Arrays.copyOf(this.ui, this.ui.length) : null; + copy.tools = this.tools != null ? Arrays.stream(this.tools).map(e -> e.clone()).toArray(BuilderToolState[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemBuilderToolData other) + ? false + : Arrays.equals((Object[])this.ui, (Object[])other.ui) && Arrays.equals((Object[])this.tools, (Object[])other.tools); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode((Object[])this.ui); + return 31 * result + Arrays.hashCode((Object[])this.tools); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemCategory.java b/src/com/hypixel/hytale/protocol/ItemCategory.java new file mode 100644 index 0000000..da5c11a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemCategory.java @@ -0,0 +1,440 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemCategory { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 4; + public static final int VARIABLE_BLOCK_START = 22; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + @Nullable + public String name; + @Nullable + public String icon; + public int order; + @Nonnull + public ItemGridInfoDisplayMode infoDisplayMode = ItemGridInfoDisplayMode.Tooltip; + @Nullable + public ItemCategory[] children; + + public ItemCategory() { + } + + public ItemCategory( + @Nullable String id, + @Nullable String name, + @Nullable String icon, + int order, + @Nonnull ItemGridInfoDisplayMode infoDisplayMode, + @Nullable ItemCategory[] children + ) { + this.id = id; + this.name = name; + this.icon = icon; + this.order = order; + this.infoDisplayMode = infoDisplayMode; + this.children = children; + } + + public ItemCategory(@Nonnull ItemCategory other) { + this.id = other.id; + this.name = other.name; + this.icon = other.icon; + this.order = other.order; + this.infoDisplayMode = other.infoDisplayMode; + this.children = other.children; + } + + @Nonnull + public static ItemCategory deserialize(@Nonnull ByteBuf buf, int offset) { + ItemCategory obj = new ItemCategory(); + byte nullBits = buf.getByte(offset); + obj.order = buf.getIntLE(offset + 1); + obj.infoDisplayMode = ItemGridInfoDisplayMode.fromValue(buf.getByte(offset + 5)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 22 + buf.getIntLE(offset + 6); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 22 + buf.getIntLE(offset + 10); + int nameLen = VarInt.peek(buf, varPos1); + if (nameLen < 0) { + throw ProtocolException.negativeLength("Name", nameLen); + } + + if (nameLen > 4096000) { + throw ProtocolException.stringTooLong("Name", nameLen, 4096000); + } + + obj.name = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 22 + buf.getIntLE(offset + 14); + int iconLen = VarInt.peek(buf, varPos2); + if (iconLen < 0) { + throw ProtocolException.negativeLength("Icon", iconLen); + } + + if (iconLen > 4096000) { + throw ProtocolException.stringTooLong("Icon", iconLen, 4096000); + } + + obj.icon = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 22 + buf.getIntLE(offset + 18); + int childrenCount = VarInt.peek(buf, varPos3); + if (childrenCount < 0) { + throw ProtocolException.negativeLength("Children", childrenCount); + } + + if (childrenCount > 4096000) { + throw ProtocolException.arrayTooLong("Children", childrenCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + childrenCount * 6L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Children", varPos3 + varIntLen + childrenCount * 6, buf.readableBytes()); + } + + obj.children = new ItemCategory[childrenCount]; + int elemPos = varPos3 + varIntLen; + + for (int i = 0; i < childrenCount; i++) { + obj.children[i] = deserialize(buf, elemPos); + elemPos += computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 22; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 6); + int pos0 = offset + 22 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 10); + int pos1 = offset + 22 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 14); + int pos2 = offset + 22 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 18); + int pos3 = offset + 22 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3); + + for (int i = 0; i < arrLen; i++) { + pos3 += computeBytesConsumed(buf, pos3); + } + + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.name != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.icon != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.children != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.order); + buf.writeByte(this.infoDisplayMode.getValue()); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int nameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int iconOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int childrenOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.name != null) { + buf.setIntLE(nameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.name, 4096000); + } else { + buf.setIntLE(nameOffsetSlot, -1); + } + + if (this.icon != null) { + buf.setIntLE(iconOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.icon, 4096000); + } else { + buf.setIntLE(iconOffsetSlot, -1); + } + + if (this.children != null) { + buf.setIntLE(childrenOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.children.length > 4096000) { + throw ProtocolException.arrayTooLong("Children", this.children.length, 4096000); + } + + VarInt.write(buf, this.children.length); + + for (ItemCategory item : this.children) { + item.serialize(buf); + } + } else { + buf.setIntLE(childrenOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 22; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.name != null) { + size += PacketIO.stringSize(this.name); + } + + if (this.icon != null) { + size += PacketIO.stringSize(this.icon); + } + + if (this.children != null) { + int childrenSize = 0; + + for (ItemCategory elem : this.children) { + childrenSize += elem.computeSize(); + } + + size += VarInt.size(this.children.length) + childrenSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 22) { + return ValidationResult.error("Buffer too small: expected at least 22 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 6); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 22 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int nameOffset = buffer.getIntLE(offset + 10); + if (nameOffset < 0) { + return ValidationResult.error("Invalid offset for Name"); + } + + int posx = offset + 22 + nameOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Name"); + } + + int nameLen = VarInt.peek(buffer, posx); + if (nameLen < 0) { + return ValidationResult.error("Invalid string length for Name"); + } + + if (nameLen > 4096000) { + return ValidationResult.error("Name exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += nameLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Name"); + } + } + + if ((nullBits & 4) != 0) { + int iconOffset = buffer.getIntLE(offset + 14); + if (iconOffset < 0) { + return ValidationResult.error("Invalid offset for Icon"); + } + + int posxx = offset + 22 + iconOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Icon"); + } + + int iconLen = VarInt.peek(buffer, posxx); + if (iconLen < 0) { + return ValidationResult.error("Invalid string length for Icon"); + } + + if (iconLen > 4096000) { + return ValidationResult.error("Icon exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += iconLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Icon"); + } + } + + if ((nullBits & 8) != 0) { + int childrenOffset = buffer.getIntLE(offset + 18); + if (childrenOffset < 0) { + return ValidationResult.error("Invalid offset for Children"); + } + + int posxxx = offset + 22 + childrenOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Children"); + } + + int childrenCount = VarInt.peek(buffer, posxxx); + if (childrenCount < 0) { + return ValidationResult.error("Invalid array count for Children"); + } + + if (childrenCount > 4096000) { + return ValidationResult.error("Children exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + + for (int i = 0; i < childrenCount; i++) { + ValidationResult structResult = validateStructure(buffer, posxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ItemCategory in Children[" + i + "]: " + structResult.error()); + } + + posxxx += computeBytesConsumed(buffer, posxxx); + } + } + + return ValidationResult.OK; + } + } + + public ItemCategory clone() { + ItemCategory copy = new ItemCategory(); + copy.id = this.id; + copy.name = this.name; + copy.icon = this.icon; + copy.order = this.order; + copy.infoDisplayMode = this.infoDisplayMode; + copy.children = this.children != null ? Arrays.stream(this.children).map(e -> e.clone()).toArray(ItemCategory[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemCategory other) + ? false + : Objects.equals(this.id, other.id) + && Objects.equals(this.name, other.name) + && Objects.equals(this.icon, other.icon) + && this.order == other.order + && Objects.equals(this.infoDisplayMode, other.infoDisplayMode) + && Arrays.equals((Object[])this.children, (Object[])other.children); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.id); + result = 31 * result + Objects.hashCode(this.name); + result = 31 * result + Objects.hashCode(this.icon); + result = 31 * result + Integer.hashCode(this.order); + result = 31 * result + Objects.hashCode(this.infoDisplayMode); + return 31 * result + Arrays.hashCode((Object[])this.children); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemEntityConfig.java b/src/com/hypixel/hytale/protocol/ItemEntityConfig.java new file mode 100644 index 0000000..2a9069e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemEntityConfig.java @@ -0,0 +1,162 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemEntityConfig { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 16384010; + @Nullable + public String particleSystemId; + @Nullable + public Color particleColor; + public boolean showItemParticles; + + public ItemEntityConfig() { + } + + public ItemEntityConfig(@Nullable String particleSystemId, @Nullable Color particleColor, boolean showItemParticles) { + this.particleSystemId = particleSystemId; + this.particleColor = particleColor; + this.showItemParticles = showItemParticles; + } + + public ItemEntityConfig(@Nonnull ItemEntityConfig other) { + this.particleSystemId = other.particleSystemId; + this.particleColor = other.particleColor; + this.showItemParticles = other.showItemParticles; + } + + @Nonnull + public static ItemEntityConfig deserialize(@Nonnull ByteBuf buf, int offset) { + ItemEntityConfig obj = new ItemEntityConfig(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 2) != 0) { + obj.particleColor = Color.deserialize(buf, offset + 1); + } + + obj.showItemParticles = buf.getByte(offset + 4) != 0; + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int particleSystemIdLen = VarInt.peek(buf, pos); + if (particleSystemIdLen < 0) { + throw ProtocolException.negativeLength("ParticleSystemId", particleSystemIdLen); + } + + if (particleSystemIdLen > 4096000) { + throw ProtocolException.stringTooLong("ParticleSystemId", particleSystemIdLen, 4096000); + } + + int particleSystemIdVarLen = VarInt.length(buf, pos); + obj.particleSystemId = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += particleSystemIdVarLen + particleSystemIdLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.particleSystemId != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.particleColor != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + if (this.particleColor != null) { + this.particleColor.serialize(buf); + } else { + buf.writeZero(3); + } + + buf.writeByte(this.showItemParticles ? 1 : 0); + if (this.particleSystemId != null) { + PacketIO.writeVarString(buf, this.particleSystemId, 4096000); + } + } + + public int computeSize() { + int size = 5; + if (this.particleSystemId != null) { + size += PacketIO.stringSize(this.particleSystemId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int particleSystemIdLen = VarInt.peek(buffer, pos); + if (particleSystemIdLen < 0) { + return ValidationResult.error("Invalid string length for ParticleSystemId"); + } + + if (particleSystemIdLen > 4096000) { + return ValidationResult.error("ParticleSystemId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += particleSystemIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ParticleSystemId"); + } + } + + return ValidationResult.OK; + } + } + + public ItemEntityConfig clone() { + ItemEntityConfig copy = new ItemEntityConfig(); + copy.particleSystemId = this.particleSystemId; + copy.particleColor = this.particleColor != null ? this.particleColor.clone() : null; + copy.showItemParticles = this.showItemParticles; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemEntityConfig other) + ? false + : Objects.equals(this.particleSystemId, other.particleSystemId) + && Objects.equals(this.particleColor, other.particleColor) + && this.showItemParticles == other.showItemParticles; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.particleSystemId, this.particleColor, this.showItemParticles); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemGlider.java b/src/com/hypixel/hytale/protocol/ItemGlider.java new file mode 100644 index 0000000..46888df --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemGlider.java @@ -0,0 +1,92 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class ItemGlider { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 16; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 16; + public static final int MAX_SIZE = 16; + public float terminalVelocity; + public float fallSpeedMultiplier; + public float horizontalSpeedMultiplier; + public float speed; + + public ItemGlider() { + } + + public ItemGlider(float terminalVelocity, float fallSpeedMultiplier, float horizontalSpeedMultiplier, float speed) { + this.terminalVelocity = terminalVelocity; + this.fallSpeedMultiplier = fallSpeedMultiplier; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.speed = speed; + } + + public ItemGlider(@Nonnull ItemGlider other) { + this.terminalVelocity = other.terminalVelocity; + this.fallSpeedMultiplier = other.fallSpeedMultiplier; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.speed = other.speed; + } + + @Nonnull + public static ItemGlider deserialize(@Nonnull ByteBuf buf, int offset) { + ItemGlider obj = new ItemGlider(); + obj.terminalVelocity = buf.getFloatLE(offset + 0); + obj.fallSpeedMultiplier = buf.getFloatLE(offset + 4); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 8); + obj.speed = buf.getFloatLE(offset + 12); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 16; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.terminalVelocity); + buf.writeFloatLE(this.fallSpeedMultiplier); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.speed); + } + + public int computeSize() { + return 16; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 16 ? ValidationResult.error("Buffer too small: expected at least 16 bytes") : ValidationResult.OK; + } + + public ItemGlider clone() { + ItemGlider copy = new ItemGlider(); + copy.terminalVelocity = this.terminalVelocity; + copy.fallSpeedMultiplier = this.fallSpeedMultiplier; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.speed = this.speed; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemGlider other) + ? false + : this.terminalVelocity == other.terminalVelocity + && this.fallSpeedMultiplier == other.fallSpeedMultiplier + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.speed == other.speed; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.terminalVelocity, this.fallSpeedMultiplier, this.horizontalSpeedMultiplier, this.speed); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemGridInfoDisplayMode.java b/src/com/hypixel/hytale/protocol/ItemGridInfoDisplayMode.java new file mode 100644 index 0000000..06e45dd --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemGridInfoDisplayMode.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ItemGridInfoDisplayMode { + Tooltip(0), + Adjacent(1), + None(2); + + public static final ItemGridInfoDisplayMode[] VALUES = values(); + private final int value; + + private ItemGridInfoDisplayMode(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ItemGridInfoDisplayMode fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ItemGridInfoDisplayMode", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemLibrary.java b/src/com/hypixel/hytale/protocol/ItemLibrary.java new file mode 100644 index 0000000..5590433 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemLibrary.java @@ -0,0 +1,360 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemLibrary { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 1677721600; + @Nullable + public ItemBase[] items; + @Nullable + public Map[] blockMap; + + public ItemLibrary() { + } + + public ItemLibrary(@Nullable ItemBase[] items, @Nullable Map[] blockMap) { + this.items = items; + this.blockMap = blockMap; + } + + public ItemLibrary(@Nonnull ItemLibrary other) { + this.items = other.items; + this.blockMap = other.blockMap; + } + + @Nonnull + public static ItemLibrary deserialize(@Nonnull ByteBuf buf, int offset) { + ItemLibrary obj = new ItemLibrary(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int itemsCount = VarInt.peek(buf, varPos0); + if (itemsCount < 0) { + throw ProtocolException.negativeLength("Items", itemsCount); + } + + if (itemsCount > 4096000) { + throw ProtocolException.arrayTooLong("Items", itemsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + itemsCount * 147L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Items", varPos0 + varIntLen + itemsCount * 147, buf.readableBytes()); + } + + obj.items = new ItemBase[itemsCount]; + int elemPos = varPos0 + varIntLen; + + for (int i = 0; i < itemsCount; i++) { + obj.items[i] = ItemBase.deserialize(buf, elemPos); + elemPos += ItemBase.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int blockMapCount = VarInt.peek(buf, varPos1); + if (blockMapCount < 0) { + throw ProtocolException.negativeLength("BlockMap", blockMapCount); + } + + if (blockMapCount > 4096000) { + throw ProtocolException.arrayTooLong("BlockMap", blockMapCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + Map[] blockMapArr = new Map[blockMapCount]; + obj.blockMap = blockMapArr; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < blockMapCount; i++) { + int mapLen = VarInt.peek(buf, elemPos); + int mapVarLen = VarInt.length(buf, elemPos); + HashMap map = new HashMap<>(mapLen); + int mapPos = elemPos + mapVarLen; + + for (int j = 0; j < mapLen; j++) { + int key = buf.getIntLE(mapPos); + mapPos += 4; + int valLen = VarInt.peek(buf, mapPos); + if (valLen < 0) { + throw ProtocolException.negativeLength("val", valLen); + } + + if (valLen > 4096000) { + throw ProtocolException.stringTooLong("val", valLen, 4096000); + } + + int valVarLen = VarInt.length(buf, mapPos); + String val = PacketIO.readVarString(buf, mapPos); + mapPos += valVarLen + valLen; + if (map.put(key, val) != null) { + throw ProtocolException.duplicateKey("BlockMap[" + i + "]", key); + } + } + + obj.blockMap[i] = map; + elemPos = mapPos; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < arrLen; i++) { + pos0 += ItemBase.computeBytesConsumed(buf, pos0); + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int j = 0; j < dictLen; j++) { + pos1 += 4; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + } + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.items != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.blockMap != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int itemsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int blockMapOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.items != null) { + buf.setIntLE(itemsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.items.length > 4096000) { + throw ProtocolException.arrayTooLong("Items", this.items.length, 4096000); + } + + VarInt.write(buf, this.items.length); + + for (ItemBase item : this.items) { + item.serialize(buf); + } + } else { + buf.setIntLE(itemsOffsetSlot, -1); + } + + if (this.blockMap != null) { + buf.setIntLE(blockMapOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.blockMap.length > 4096000) { + throw ProtocolException.arrayTooLong("BlockMap", this.blockMap.length, 4096000); + } + + VarInt.write(buf, this.blockMap.length); + + for (Map item : this.blockMap) { + VarInt.write(buf, item.size()); + + for (Entry entry : item.entrySet()) { + buf.writeIntLE(entry.getKey()); + PacketIO.writeVarString(buf, entry.getValue(), 4096000); + } + } + } else { + buf.setIntLE(blockMapOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 9; + if (this.items != null) { + int itemsSize = 0; + + for (ItemBase elem : this.items) { + itemsSize += elem.computeSize(); + } + + size += VarInt.size(this.items.length) + itemsSize; + } + + if (this.blockMap != null) { + int blockMapSize = 0; + + for (Map elem : this.blockMap) { + blockMapSize += VarInt.size(elem.size()) + elem.entrySet().stream().mapToInt(kvpInner -> 4 + PacketIO.stringSize(kvpInner.getValue())).sum(); + } + + size += VarInt.size(this.blockMap.length) + blockMapSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int itemsOffset = buffer.getIntLE(offset + 1); + if (itemsOffset < 0) { + return ValidationResult.error("Invalid offset for Items"); + } + + int pos = offset + 9 + itemsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Items"); + } + + int itemsCount = VarInt.peek(buffer, pos); + if (itemsCount < 0) { + return ValidationResult.error("Invalid array count for Items"); + } + + if (itemsCount > 4096000) { + return ValidationResult.error("Items exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < itemsCount; i++) { + ValidationResult structResult = ItemBase.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ItemBase in Items[" + i + "]: " + structResult.error()); + } + + pos += ItemBase.computeBytesConsumed(buffer, pos); + } + } + + if ((nullBits & 2) != 0) { + int blockMapOffset = buffer.getIntLE(offset + 5); + if (blockMapOffset < 0) { + return ValidationResult.error("Invalid offset for BlockMap"); + } + + int posx = offset + 9 + blockMapOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BlockMap"); + } + + int blockMapCount = VarInt.peek(buffer, posx); + if (blockMapCount < 0) { + return ValidationResult.error("Invalid array count for BlockMap"); + } + + if (blockMapCount > 4096000) { + return ValidationResult.error("BlockMap exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < blockMapCount; i++) { + int blockMapDictLen = VarInt.peek(buffer, posx); + if (blockMapDictLen < 0) { + return ValidationResult.error("Invalid dictionary count in BlockMap[" + i + "]"); + } + + posx += VarInt.length(buffer, posx); + + for (int j = 0; j < blockMapDictLen; j++) { + posx += 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading blockMapKey"); + } + + int blockMapValLen = VarInt.peek(buffer, posx); + if (blockMapValLen < 0) { + return ValidationResult.error("Invalid string length for blockMapVal"); + } + + if (blockMapValLen > 4096000) { + return ValidationResult.error("blockMapVal exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += blockMapValLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading blockMapVal"); + } + } + } + } + + return ValidationResult.OK; + } + } + + public ItemLibrary clone() { + ItemLibrary copy = new ItemLibrary(); + copy.items = this.items != null ? Arrays.stream(this.items).map(e -> e.clone()).toArray(ItemBase[]::new) : null; + copy.blockMap = this.blockMap != null + ? Arrays.stream(this.blockMap).map(d -> new HashMap<>((Map)d)).toArray(Map[]::new) + : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemLibrary other) + ? false + : Arrays.equals((Object[])this.items, (Object[])other.items) && Arrays.equals((Object[])this.blockMap, (Object[])other.blockMap); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode((Object[])this.items); + return 31 * result + Arrays.hashCode((Object[])this.blockMap); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemPlayerAnimations.java b/src/com/hypixel/hytale/protocol/ItemPlayerAnimations.java new file mode 100644 index 0000000..8fcbbd9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemPlayerAnimations.java @@ -0,0 +1,409 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemPlayerAnimations { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 91; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 103; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + @Nullable + public Map animations; + @Nullable + public WiggleWeights wiggleWeights; + @Nullable + public CameraSettings camera; + @Nullable + public ItemPullbackConfiguration pullbackConfig; + public boolean useFirstPersonOverride; + + public ItemPlayerAnimations() { + } + + public ItemPlayerAnimations( + @Nullable String id, + @Nullable Map animations, + @Nullable WiggleWeights wiggleWeights, + @Nullable CameraSettings camera, + @Nullable ItemPullbackConfiguration pullbackConfig, + boolean useFirstPersonOverride + ) { + this.id = id; + this.animations = animations; + this.wiggleWeights = wiggleWeights; + this.camera = camera; + this.pullbackConfig = pullbackConfig; + this.useFirstPersonOverride = useFirstPersonOverride; + } + + public ItemPlayerAnimations(@Nonnull ItemPlayerAnimations other) { + this.id = other.id; + this.animations = other.animations; + this.wiggleWeights = other.wiggleWeights; + this.camera = other.camera; + this.pullbackConfig = other.pullbackConfig; + this.useFirstPersonOverride = other.useFirstPersonOverride; + } + + @Nonnull + public static ItemPlayerAnimations deserialize(@Nonnull ByteBuf buf, int offset) { + ItemPlayerAnimations obj = new ItemPlayerAnimations(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 4) != 0) { + obj.wiggleWeights = WiggleWeights.deserialize(buf, offset + 1); + } + + if ((nullBits & 16) != 0) { + obj.pullbackConfig = ItemPullbackConfiguration.deserialize(buf, offset + 41); + } + + obj.useFirstPersonOverride = buf.getByte(offset + 90) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 103 + buf.getIntLE(offset + 91); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 103 + buf.getIntLE(offset + 95); + int animationsCount = VarInt.peek(buf, varPos1); + if (animationsCount < 0) { + throw ProtocolException.negativeLength("Animations", animationsCount); + } + + if (animationsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Animations", animationsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.animations = new HashMap<>(animationsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < animationsCount; i++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + ItemAnimation val = ItemAnimation.deserialize(buf, dictPos); + dictPos += ItemAnimation.computeBytesConsumed(buf, dictPos); + if (obj.animations.put(key, val) != null) { + throw ProtocolException.duplicateKey("animations", key); + } + } + } + + if ((nullBits & 8) != 0) { + int varPos2 = offset + 103 + buf.getIntLE(offset + 99); + obj.camera = CameraSettings.deserialize(buf, varPos2); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 103; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 91); + int pos0 = offset + 103 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 95); + int pos1 = offset + 103 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + pos1 += ItemAnimation.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 99); + int pos2 = offset + 103 + fieldOffset2; + pos2 += CameraSettings.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.animations != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.wiggleWeights != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.pullbackConfig != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + if (this.wiggleWeights != null) { + this.wiggleWeights.serialize(buf); + } else { + buf.writeZero(40); + } + + if (this.pullbackConfig != null) { + this.pullbackConfig.serialize(buf); + } else { + buf.writeZero(49); + } + + buf.writeByte(this.useFirstPersonOverride ? 1 : 0); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int animationsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.animations != null) { + buf.setIntLE(animationsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.animations.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Animations", this.animations.size(), 4096000); + } + + VarInt.write(buf, this.animations.size()); + + for (Entry e : this.animations.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(animationsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 103; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.animations != null) { + int animationsSize = 0; + + for (Entry kvp : this.animations.entrySet()) { + animationsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.animations.size()) + animationsSize; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 103) { + return ValidationResult.error("Buffer too small: expected at least 103 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 91); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 103 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int animationsOffset = buffer.getIntLE(offset + 95); + if (animationsOffset < 0) { + return ValidationResult.error("Invalid offset for Animations"); + } + + int posx = offset + 103 + animationsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Animations"); + } + + int animationsCount = VarInt.peek(buffer, posx); + if (animationsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Animations"); + } + + if (animationsCount > 4096000) { + return ValidationResult.error("Animations exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < animationsCount; i++) { + int keyLen = VarInt.peek(buffer, posx); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += keyLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posx += ItemAnimation.computeBytesConsumed(buffer, posx); + } + } + + if ((nullBits & 8) != 0) { + int cameraOffset = buffer.getIntLE(offset + 99); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxx = offset + 103 + cameraOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = CameraSettings.validateStructure(buffer, posxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxx += CameraSettings.computeBytesConsumed(buffer, posxx); + } + + return ValidationResult.OK; + } + } + + public ItemPlayerAnimations clone() { + ItemPlayerAnimations copy = new ItemPlayerAnimations(); + copy.id = this.id; + if (this.animations != null) { + Map m = new HashMap<>(); + + for (Entry e : this.animations.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.animations = m; + } + + copy.wiggleWeights = this.wiggleWeights != null ? this.wiggleWeights.clone() : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.pullbackConfig = this.pullbackConfig != null ? this.pullbackConfig.clone() : null; + copy.useFirstPersonOverride = this.useFirstPersonOverride; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemPlayerAnimations other) + ? false + : Objects.equals(this.id, other.id) + && Objects.equals(this.animations, other.animations) + && Objects.equals(this.wiggleWeights, other.wiggleWeights) + && Objects.equals(this.camera, other.camera) + && Objects.equals(this.pullbackConfig, other.pullbackConfig) + && this.useFirstPersonOverride == other.useFirstPersonOverride; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.animations, this.wiggleWeights, this.camera, this.pullbackConfig, this.useFirstPersonOverride); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemPullbackConfiguration.java b/src/com/hypixel/hytale/protocol/ItemPullbackConfiguration.java new file mode 100644 index 0000000..8cc6180 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemPullbackConfiguration.java @@ -0,0 +1,152 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemPullbackConfiguration { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 49; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 49; + public static final int MAX_SIZE = 49; + @Nullable + public Vector3f leftOffsetOverride; + @Nullable + public Vector3f leftRotationOverride; + @Nullable + public Vector3f rightOffsetOverride; + @Nullable + public Vector3f rightRotationOverride; + + public ItemPullbackConfiguration() { + } + + public ItemPullbackConfiguration( + @Nullable Vector3f leftOffsetOverride, + @Nullable Vector3f leftRotationOverride, + @Nullable Vector3f rightOffsetOverride, + @Nullable Vector3f rightRotationOverride + ) { + this.leftOffsetOverride = leftOffsetOverride; + this.leftRotationOverride = leftRotationOverride; + this.rightOffsetOverride = rightOffsetOverride; + this.rightRotationOverride = rightRotationOverride; + } + + public ItemPullbackConfiguration(@Nonnull ItemPullbackConfiguration other) { + this.leftOffsetOverride = other.leftOffsetOverride; + this.leftRotationOverride = other.leftRotationOverride; + this.rightOffsetOverride = other.rightOffsetOverride; + this.rightRotationOverride = other.rightRotationOverride; + } + + @Nonnull + public static ItemPullbackConfiguration deserialize(@Nonnull ByteBuf buf, int offset) { + ItemPullbackConfiguration obj = new ItemPullbackConfiguration(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.leftOffsetOverride = Vector3f.deserialize(buf, offset + 1); + } + + if ((nullBits & 2) != 0) { + obj.leftRotationOverride = Vector3f.deserialize(buf, offset + 13); + } + + if ((nullBits & 4) != 0) { + obj.rightOffsetOverride = Vector3f.deserialize(buf, offset + 25); + } + + if ((nullBits & 8) != 0) { + obj.rightRotationOverride = Vector3f.deserialize(buf, offset + 37); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 49; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.leftOffsetOverride != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.leftRotationOverride != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rightOffsetOverride != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.rightRotationOverride != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + if (this.leftOffsetOverride != null) { + this.leftOffsetOverride.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.leftRotationOverride != null) { + this.leftRotationOverride.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.rightOffsetOverride != null) { + this.rightOffsetOverride.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.rightRotationOverride != null) { + this.rightRotationOverride.serialize(buf); + } else { + buf.writeZero(12); + } + } + + public int computeSize() { + return 49; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 49 ? ValidationResult.error("Buffer too small: expected at least 49 bytes") : ValidationResult.OK; + } + + public ItemPullbackConfiguration clone() { + ItemPullbackConfiguration copy = new ItemPullbackConfiguration(); + copy.leftOffsetOverride = this.leftOffsetOverride != null ? this.leftOffsetOverride.clone() : null; + copy.leftRotationOverride = this.leftRotationOverride != null ? this.leftRotationOverride.clone() : null; + copy.rightOffsetOverride = this.rightOffsetOverride != null ? this.rightOffsetOverride.clone() : null; + copy.rightRotationOverride = this.rightRotationOverride != null ? this.rightRotationOverride.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemPullbackConfiguration other) + ? false + : Objects.equals(this.leftOffsetOverride, other.leftOffsetOverride) + && Objects.equals(this.leftRotationOverride, other.leftRotationOverride) + && Objects.equals(this.rightOffsetOverride, other.rightOffsetOverride) + && Objects.equals(this.rightRotationOverride, other.rightRotationOverride); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.leftOffsetOverride, this.leftRotationOverride, this.rightOffsetOverride, this.rightRotationOverride); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemQuality.java b/src/com/hypixel/hytale/protocol/ItemQuality.java new file mode 100644 index 0000000..6de703a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemQuality.java @@ -0,0 +1,663 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemQuality { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 7; + public static final int VARIABLE_FIELD_COUNT = 7; + public static final int VARIABLE_BLOCK_START = 35; + public static final int MAX_SIZE = 114688070; + @Nullable + public String id; + @Nullable + public String itemTooltipTexture; + @Nullable + public String itemTooltipArrowTexture; + @Nullable + public String slotTexture; + @Nullable + public String blockSlotTexture; + @Nullable + public String specialSlotTexture; + @Nullable + public Color textColor; + @Nullable + public String localizationKey; + public boolean visibleQualityLabel; + public boolean renderSpecialSlot; + public boolean hideFromSearch; + + public ItemQuality() { + } + + public ItemQuality( + @Nullable String id, + @Nullable String itemTooltipTexture, + @Nullable String itemTooltipArrowTexture, + @Nullable String slotTexture, + @Nullable String blockSlotTexture, + @Nullable String specialSlotTexture, + @Nullable Color textColor, + @Nullable String localizationKey, + boolean visibleQualityLabel, + boolean renderSpecialSlot, + boolean hideFromSearch + ) { + this.id = id; + this.itemTooltipTexture = itemTooltipTexture; + this.itemTooltipArrowTexture = itemTooltipArrowTexture; + this.slotTexture = slotTexture; + this.blockSlotTexture = blockSlotTexture; + this.specialSlotTexture = specialSlotTexture; + this.textColor = textColor; + this.localizationKey = localizationKey; + this.visibleQualityLabel = visibleQualityLabel; + this.renderSpecialSlot = renderSpecialSlot; + this.hideFromSearch = hideFromSearch; + } + + public ItemQuality(@Nonnull ItemQuality other) { + this.id = other.id; + this.itemTooltipTexture = other.itemTooltipTexture; + this.itemTooltipArrowTexture = other.itemTooltipArrowTexture; + this.slotTexture = other.slotTexture; + this.blockSlotTexture = other.blockSlotTexture; + this.specialSlotTexture = other.specialSlotTexture; + this.textColor = other.textColor; + this.localizationKey = other.localizationKey; + this.visibleQualityLabel = other.visibleQualityLabel; + this.renderSpecialSlot = other.renderSpecialSlot; + this.hideFromSearch = other.hideFromSearch; + } + + @Nonnull + public static ItemQuality deserialize(@Nonnull ByteBuf buf, int offset) { + ItemQuality obj = new ItemQuality(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 64) != 0) { + obj.textColor = Color.deserialize(buf, offset + 1); + } + + obj.visibleQualityLabel = buf.getByte(offset + 4) != 0; + obj.renderSpecialSlot = buf.getByte(offset + 5) != 0; + obj.hideFromSearch = buf.getByte(offset + 6) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 35 + buf.getIntLE(offset + 7); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 35 + buf.getIntLE(offset + 11); + int itemTooltipTextureLen = VarInt.peek(buf, varPos1); + if (itemTooltipTextureLen < 0) { + throw ProtocolException.negativeLength("ItemTooltipTexture", itemTooltipTextureLen); + } + + if (itemTooltipTextureLen > 4096000) { + throw ProtocolException.stringTooLong("ItemTooltipTexture", itemTooltipTextureLen, 4096000); + } + + obj.itemTooltipTexture = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 35 + buf.getIntLE(offset + 15); + int itemTooltipArrowTextureLen = VarInt.peek(buf, varPos2); + if (itemTooltipArrowTextureLen < 0) { + throw ProtocolException.negativeLength("ItemTooltipArrowTexture", itemTooltipArrowTextureLen); + } + + if (itemTooltipArrowTextureLen > 4096000) { + throw ProtocolException.stringTooLong("ItemTooltipArrowTexture", itemTooltipArrowTextureLen, 4096000); + } + + obj.itemTooltipArrowTexture = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 35 + buf.getIntLE(offset + 19); + int slotTextureLen = VarInt.peek(buf, varPos3); + if (slotTextureLen < 0) { + throw ProtocolException.negativeLength("SlotTexture", slotTextureLen); + } + + if (slotTextureLen > 4096000) { + throw ProtocolException.stringTooLong("SlotTexture", slotTextureLen, 4096000); + } + + obj.slotTexture = PacketIO.readVarString(buf, varPos3, PacketIO.UTF8); + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 35 + buf.getIntLE(offset + 23); + int blockSlotTextureLen = VarInt.peek(buf, varPos4); + if (blockSlotTextureLen < 0) { + throw ProtocolException.negativeLength("BlockSlotTexture", blockSlotTextureLen); + } + + if (blockSlotTextureLen > 4096000) { + throw ProtocolException.stringTooLong("BlockSlotTexture", blockSlotTextureLen, 4096000); + } + + obj.blockSlotTexture = PacketIO.readVarString(buf, varPos4, PacketIO.UTF8); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 35 + buf.getIntLE(offset + 27); + int specialSlotTextureLen = VarInt.peek(buf, varPos5); + if (specialSlotTextureLen < 0) { + throw ProtocolException.negativeLength("SpecialSlotTexture", specialSlotTextureLen); + } + + if (specialSlotTextureLen > 4096000) { + throw ProtocolException.stringTooLong("SpecialSlotTexture", specialSlotTextureLen, 4096000); + } + + obj.specialSlotTexture = PacketIO.readVarString(buf, varPos5, PacketIO.UTF8); + } + + if ((nullBits & 128) != 0) { + int varPos6 = offset + 35 + buf.getIntLE(offset + 31); + int localizationKeyLen = VarInt.peek(buf, varPos6); + if (localizationKeyLen < 0) { + throw ProtocolException.negativeLength("LocalizationKey", localizationKeyLen); + } + + if (localizationKeyLen > 4096000) { + throw ProtocolException.stringTooLong("LocalizationKey", localizationKeyLen, 4096000); + } + + obj.localizationKey = PacketIO.readVarString(buf, varPos6, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 35; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 7); + int pos0 = offset + 35 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 11); + int pos1 = offset + 35 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 15); + int pos2 = offset + 35 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 19); + int pos3 = offset + 35 + fieldOffset3; + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 23); + int pos4 = offset + 35 + fieldOffset4; + int sl = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4) + sl; + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 27); + int pos5 = offset + 35 + fieldOffset5; + int sl = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + sl; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits & 128) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 31); + int pos6 = offset + 35 + fieldOffset6; + int sl = VarInt.peek(buf, pos6); + pos6 += VarInt.length(buf, pos6) + sl; + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.itemTooltipTexture != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.itemTooltipArrowTexture != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.slotTexture != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.blockSlotTexture != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.specialSlotTexture != null) { + nullBits = (byte)(nullBits | 32); + } + + if (this.textColor != null) { + nullBits = (byte)(nullBits | 64); + } + + if (this.localizationKey != null) { + nullBits = (byte)(nullBits | 128); + } + + buf.writeByte(nullBits); + if (this.textColor != null) { + this.textColor.serialize(buf); + } else { + buf.writeZero(3); + } + + buf.writeByte(this.visibleQualityLabel ? 1 : 0); + buf.writeByte(this.renderSpecialSlot ? 1 : 0); + buf.writeByte(this.hideFromSearch ? 1 : 0); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int itemTooltipTextureOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int itemTooltipArrowTextureOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int slotTextureOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int blockSlotTextureOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int specialSlotTextureOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int localizationKeyOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.itemTooltipTexture != null) { + buf.setIntLE(itemTooltipTextureOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.itemTooltipTexture, 4096000); + } else { + buf.setIntLE(itemTooltipTextureOffsetSlot, -1); + } + + if (this.itemTooltipArrowTexture != null) { + buf.setIntLE(itemTooltipArrowTextureOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.itemTooltipArrowTexture, 4096000); + } else { + buf.setIntLE(itemTooltipArrowTextureOffsetSlot, -1); + } + + if (this.slotTexture != null) { + buf.setIntLE(slotTextureOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.slotTexture, 4096000); + } else { + buf.setIntLE(slotTextureOffsetSlot, -1); + } + + if (this.blockSlotTexture != null) { + buf.setIntLE(blockSlotTextureOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.blockSlotTexture, 4096000); + } else { + buf.setIntLE(blockSlotTextureOffsetSlot, -1); + } + + if (this.specialSlotTexture != null) { + buf.setIntLE(specialSlotTextureOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.specialSlotTexture, 4096000); + } else { + buf.setIntLE(specialSlotTextureOffsetSlot, -1); + } + + if (this.localizationKey != null) { + buf.setIntLE(localizationKeyOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.localizationKey, 4096000); + } else { + buf.setIntLE(localizationKeyOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 35; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.itemTooltipTexture != null) { + size += PacketIO.stringSize(this.itemTooltipTexture); + } + + if (this.itemTooltipArrowTexture != null) { + size += PacketIO.stringSize(this.itemTooltipArrowTexture); + } + + if (this.slotTexture != null) { + size += PacketIO.stringSize(this.slotTexture); + } + + if (this.blockSlotTexture != null) { + size += PacketIO.stringSize(this.blockSlotTexture); + } + + if (this.specialSlotTexture != null) { + size += PacketIO.stringSize(this.specialSlotTexture); + } + + if (this.localizationKey != null) { + size += PacketIO.stringSize(this.localizationKey); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 35) { + return ValidationResult.error("Buffer too small: expected at least 35 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 7); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 35 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int itemTooltipTextureOffset = buffer.getIntLE(offset + 11); + if (itemTooltipTextureOffset < 0) { + return ValidationResult.error("Invalid offset for ItemTooltipTexture"); + } + + int posx = offset + 35 + itemTooltipTextureOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ItemTooltipTexture"); + } + + int itemTooltipTextureLen = VarInt.peek(buffer, posx); + if (itemTooltipTextureLen < 0) { + return ValidationResult.error("Invalid string length for ItemTooltipTexture"); + } + + if (itemTooltipTextureLen > 4096000) { + return ValidationResult.error("ItemTooltipTexture exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += itemTooltipTextureLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ItemTooltipTexture"); + } + } + + if ((nullBits & 4) != 0) { + int itemTooltipArrowTextureOffset = buffer.getIntLE(offset + 15); + if (itemTooltipArrowTextureOffset < 0) { + return ValidationResult.error("Invalid offset for ItemTooltipArrowTexture"); + } + + int posxx = offset + 35 + itemTooltipArrowTextureOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ItemTooltipArrowTexture"); + } + + int itemTooltipArrowTextureLen = VarInt.peek(buffer, posxx); + if (itemTooltipArrowTextureLen < 0) { + return ValidationResult.error("Invalid string length for ItemTooltipArrowTexture"); + } + + if (itemTooltipArrowTextureLen > 4096000) { + return ValidationResult.error("ItemTooltipArrowTexture exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += itemTooltipArrowTextureLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ItemTooltipArrowTexture"); + } + } + + if ((nullBits & 8) != 0) { + int slotTextureOffset = buffer.getIntLE(offset + 19); + if (slotTextureOffset < 0) { + return ValidationResult.error("Invalid offset for SlotTexture"); + } + + int posxxx = offset + 35 + slotTextureOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SlotTexture"); + } + + int slotTextureLen = VarInt.peek(buffer, posxxx); + if (slotTextureLen < 0) { + return ValidationResult.error("Invalid string length for SlotTexture"); + } + + if (slotTextureLen > 4096000) { + return ValidationResult.error("SlotTexture exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += slotTextureLen; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading SlotTexture"); + } + } + + if ((nullBits & 16) != 0) { + int blockSlotTextureOffset = buffer.getIntLE(offset + 23); + if (blockSlotTextureOffset < 0) { + return ValidationResult.error("Invalid offset for BlockSlotTexture"); + } + + int posxxxx = offset + 35 + blockSlotTextureOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BlockSlotTexture"); + } + + int blockSlotTextureLen = VarInt.peek(buffer, posxxxx); + if (blockSlotTextureLen < 0) { + return ValidationResult.error("Invalid string length for BlockSlotTexture"); + } + + if (blockSlotTextureLen > 4096000) { + return ValidationResult.error("BlockSlotTexture exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + posxxxx += blockSlotTextureLen; + if (posxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading BlockSlotTexture"); + } + } + + if ((nullBits & 32) != 0) { + int specialSlotTextureOffset = buffer.getIntLE(offset + 27); + if (specialSlotTextureOffset < 0) { + return ValidationResult.error("Invalid offset for SpecialSlotTexture"); + } + + int posxxxxx = offset + 35 + specialSlotTextureOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SpecialSlotTexture"); + } + + int specialSlotTextureLen = VarInt.peek(buffer, posxxxxx); + if (specialSlotTextureLen < 0) { + return ValidationResult.error("Invalid string length for SpecialSlotTexture"); + } + + if (specialSlotTextureLen > 4096000) { + return ValidationResult.error("SpecialSlotTexture exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += specialSlotTextureLen; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading SpecialSlotTexture"); + } + } + + if ((nullBits & 128) != 0) { + int localizationKeyOffset = buffer.getIntLE(offset + 31); + if (localizationKeyOffset < 0) { + return ValidationResult.error("Invalid offset for LocalizationKey"); + } + + int posxxxxxx = offset + 35 + localizationKeyOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for LocalizationKey"); + } + + int localizationKeyLen = VarInt.peek(buffer, posxxxxxx); + if (localizationKeyLen < 0) { + return ValidationResult.error("Invalid string length for LocalizationKey"); + } + + if (localizationKeyLen > 4096000) { + return ValidationResult.error("LocalizationKey exceeds max length 4096000"); + } + + posxxxxxx += VarInt.length(buffer, posxxxxxx); + posxxxxxx += localizationKeyLen; + if (posxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading LocalizationKey"); + } + } + + return ValidationResult.OK; + } + } + + public ItemQuality clone() { + ItemQuality copy = new ItemQuality(); + copy.id = this.id; + copy.itemTooltipTexture = this.itemTooltipTexture; + copy.itemTooltipArrowTexture = this.itemTooltipArrowTexture; + copy.slotTexture = this.slotTexture; + copy.blockSlotTexture = this.blockSlotTexture; + copy.specialSlotTexture = this.specialSlotTexture; + copy.textColor = this.textColor != null ? this.textColor.clone() : null; + copy.localizationKey = this.localizationKey; + copy.visibleQualityLabel = this.visibleQualityLabel; + copy.renderSpecialSlot = this.renderSpecialSlot; + copy.hideFromSearch = this.hideFromSearch; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemQuality other) + ? false + : Objects.equals(this.id, other.id) + && Objects.equals(this.itemTooltipTexture, other.itemTooltipTexture) + && Objects.equals(this.itemTooltipArrowTexture, other.itemTooltipArrowTexture) + && Objects.equals(this.slotTexture, other.slotTexture) + && Objects.equals(this.blockSlotTexture, other.blockSlotTexture) + && Objects.equals(this.specialSlotTexture, other.specialSlotTexture) + && Objects.equals(this.textColor, other.textColor) + && Objects.equals(this.localizationKey, other.localizationKey) + && this.visibleQualityLabel == other.visibleQualityLabel + && this.renderSpecialSlot == other.renderSpecialSlot + && this.hideFromSearch == other.hideFromSearch; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.id, + this.itemTooltipTexture, + this.itemTooltipArrowTexture, + this.slotTexture, + this.blockSlotTexture, + this.specialSlotTexture, + this.textColor, + this.localizationKey, + this.visibleQualityLabel, + this.renderSpecialSlot, + this.hideFromSearch + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemQuantity.java b/src/com/hypixel/hytale/protocol/ItemQuantity.java new file mode 100644 index 0000000..1ecb51e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemQuantity.java @@ -0,0 +1,139 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemQuantity { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 16384010; + @Nullable + public String itemId; + public int quantity; + + public ItemQuantity() { + } + + public ItemQuantity(@Nullable String itemId, int quantity) { + this.itemId = itemId; + this.quantity = quantity; + } + + public ItemQuantity(@Nonnull ItemQuantity other) { + this.itemId = other.itemId; + this.quantity = other.quantity; + } + + @Nonnull + public static ItemQuantity deserialize(@Nonnull ByteBuf buf, int offset) { + ItemQuantity obj = new ItemQuantity(); + byte nullBits = buf.getByte(offset); + obj.quantity = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int itemIdLen = VarInt.peek(buf, pos); + if (itemIdLen < 0) { + throw ProtocolException.negativeLength("ItemId", itemIdLen); + } + + if (itemIdLen > 4096000) { + throw ProtocolException.stringTooLong("ItemId", itemIdLen, 4096000); + } + + int itemIdVarLen = VarInt.length(buf, pos); + obj.itemId = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += itemIdVarLen + itemIdLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.itemId != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.quantity); + if (this.itemId != null) { + PacketIO.writeVarString(buf, this.itemId, 4096000); + } + } + + public int computeSize() { + int size = 5; + if (this.itemId != null) { + size += PacketIO.stringSize(this.itemId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int itemIdLen = VarInt.peek(buffer, pos); + if (itemIdLen < 0) { + return ValidationResult.error("Invalid string length for ItemId"); + } + + if (itemIdLen > 4096000) { + return ValidationResult.error("ItemId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += itemIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ItemId"); + } + } + + return ValidationResult.OK; + } + } + + public ItemQuantity clone() { + ItemQuantity copy = new ItemQuantity(); + copy.itemId = this.itemId; + copy.quantity = this.quantity; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemQuantity other) ? false : Objects.equals(this.itemId, other.itemId) && this.quantity == other.quantity; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.itemId, this.quantity); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemResourceType.java b/src/com/hypixel/hytale/protocol/ItemResourceType.java new file mode 100644 index 0000000..90a96aa --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemResourceType.java @@ -0,0 +1,139 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemResourceType { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 16384010; + @Nullable + public String id; + public int quantity = 1; + + public ItemResourceType() { + } + + public ItemResourceType(@Nullable String id, int quantity) { + this.id = id; + this.quantity = quantity; + } + + public ItemResourceType(@Nonnull ItemResourceType other) { + this.id = other.id; + this.quantity = other.quantity; + } + + @Nonnull + public static ItemResourceType deserialize(@Nonnull ByteBuf buf, int offset) { + ItemResourceType obj = new ItemResourceType(); + byte nullBits = buf.getByte(offset); + obj.quantity = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int idLen = VarInt.peek(buf, pos); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + int idVarLen = VarInt.length(buf, pos); + obj.id = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += idVarLen + idLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.quantity); + if (this.id != null) { + PacketIO.writeVarString(buf, this.id, 4096000); + } + } + + public int computeSize() { + int size = 5; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + return ValidationResult.OK; + } + } + + public ItemResourceType clone() { + ItemResourceType copy = new ItemResourceType(); + copy.id = this.id; + copy.quantity = this.quantity; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemResourceType other) ? false : Objects.equals(this.id, other.id) && this.quantity == other.quantity; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.quantity); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemReticle.java b/src/com/hypixel/hytale/protocol/ItemReticle.java new file mode 100644 index 0000000..700993f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemReticle.java @@ -0,0 +1,197 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemReticle { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + public boolean hideBase; + @Nullable + public String[] parts; + public float duration; + + public ItemReticle() { + } + + public ItemReticle(boolean hideBase, @Nullable String[] parts, float duration) { + this.hideBase = hideBase; + this.parts = parts; + this.duration = duration; + } + + public ItemReticle(@Nonnull ItemReticle other) { + this.hideBase = other.hideBase; + this.parts = other.parts; + this.duration = other.duration; + } + + @Nonnull + public static ItemReticle deserialize(@Nonnull ByteBuf buf, int offset) { + ItemReticle obj = new ItemReticle(); + byte nullBits = buf.getByte(offset); + obj.hideBase = buf.getByte(offset + 1) != 0; + obj.duration = buf.getFloatLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int partsCount = VarInt.peek(buf, pos); + if (partsCount < 0) { + throw ProtocolException.negativeLength("Parts", partsCount); + } + + if (partsCount > 4096000) { + throw ProtocolException.arrayTooLong("Parts", partsCount, 4096000); + } + + int partsVarLen = VarInt.size(partsCount); + if (pos + partsVarLen + partsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Parts", pos + partsVarLen + partsCount * 1, buf.readableBytes()); + } + + pos += partsVarLen; + obj.parts = new String[partsCount]; + + for (int i = 0; i < partsCount; i++) { + int strLen = VarInt.peek(buf, pos); + if (strLen < 0) { + throw ProtocolException.negativeLength("parts[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("parts[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, pos); + obj.parts[i] = PacketIO.readVarString(buf, pos); + pos += strVarLen + strLen; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.parts != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.hideBase ? 1 : 0); + buf.writeFloatLE(this.duration); + if (this.parts != null) { + if (this.parts.length > 4096000) { + throw ProtocolException.arrayTooLong("Parts", this.parts.length, 4096000); + } + + VarInt.write(buf, this.parts.length); + + for (String item : this.parts) { + PacketIO.writeVarString(buf, item, 4096000); + } + } + } + + public int computeSize() { + int size = 6; + if (this.parts != null) { + int partsSize = 0; + + for (String elem : this.parts) { + partsSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.parts.length) + partsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int partsCount = VarInt.peek(buffer, pos); + if (partsCount < 0) { + return ValidationResult.error("Invalid array count for Parts"); + } + + if (partsCount > 4096000) { + return ValidationResult.error("Parts exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < partsCount; i++) { + int strLen = VarInt.peek(buffer, pos); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in Parts"); + } + + pos += VarInt.length(buffer, pos); + pos += strLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in Parts"); + } + } + } + + return ValidationResult.OK; + } + } + + public ItemReticle clone() { + ItemReticle copy = new ItemReticle(); + copy.hideBase = this.hideBase; + copy.parts = this.parts != null ? Arrays.copyOf(this.parts, this.parts.length) : null; + copy.duration = this.duration; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemReticle other) + ? false + : this.hideBase == other.hideBase && Arrays.equals((Object[])this.parts, (Object[])other.parts) && this.duration == other.duration; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Boolean.hashCode(this.hideBase); + result = 31 * result + Arrays.hashCode((Object[])this.parts); + return 31 * result + Float.hashCode(this.duration); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemReticleClientEvent.java b/src/com/hypixel/hytale/protocol/ItemReticleClientEvent.java new file mode 100644 index 0000000..fc38b81 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemReticleClientEvent.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ItemReticleClientEvent { + OnHit(0), + Wielding(1), + OnMovementLeft(2), + OnMovementRight(3), + OnMovementBack(4); + + public static final ItemReticleClientEvent[] VALUES = values(); + private final int value; + + private ItemReticleClientEvent(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ItemReticleClientEvent fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ItemReticleClientEvent", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemReticleConfig.java b/src/com/hypixel/hytale/protocol/ItemReticleConfig.java new file mode 100644 index 0000000..7e453ae --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemReticleConfig.java @@ -0,0 +1,526 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemReticleConfig { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 4; + public static final int VARIABLE_BLOCK_START = 17; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + @Nullable + public String[] base; + @Nullable + public Map serverEvents; + @Nullable + public Map clientEvents; + + public ItemReticleConfig() { + } + + public ItemReticleConfig( + @Nullable String id, + @Nullable String[] base, + @Nullable Map serverEvents, + @Nullable Map clientEvents + ) { + this.id = id; + this.base = base; + this.serverEvents = serverEvents; + this.clientEvents = clientEvents; + } + + public ItemReticleConfig(@Nonnull ItemReticleConfig other) { + this.id = other.id; + this.base = other.base; + this.serverEvents = other.serverEvents; + this.clientEvents = other.clientEvents; + } + + @Nonnull + public static ItemReticleConfig deserialize(@Nonnull ByteBuf buf, int offset) { + ItemReticleConfig obj = new ItemReticleConfig(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 17 + buf.getIntLE(offset + 1); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 17 + buf.getIntLE(offset + 5); + int baseCount = VarInt.peek(buf, varPos1); + if (baseCount < 0) { + throw ProtocolException.negativeLength("Base", baseCount); + } + + if (baseCount > 4096000) { + throw ProtocolException.arrayTooLong("Base", baseCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + baseCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Base", varPos1 + varIntLen + baseCount * 1, buf.readableBytes()); + } + + obj.base = new String[baseCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < baseCount; i++) { + int strLen = VarInt.peek(buf, elemPos); + if (strLen < 0) { + throw ProtocolException.negativeLength("base[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("base[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, elemPos); + obj.base[i] = PacketIO.readVarString(buf, elemPos); + elemPos += strVarLen + strLen; + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 17 + buf.getIntLE(offset + 9); + int serverEventsCount = VarInt.peek(buf, varPos2); + if (serverEventsCount < 0) { + throw ProtocolException.negativeLength("ServerEvents", serverEventsCount); + } + + if (serverEventsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("ServerEvents", serverEventsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + obj.serverEvents = new HashMap<>(serverEventsCount); + int dictPos = varPos2 + varIntLen; + + for (int i = 0; i < serverEventsCount; i++) { + int key = buf.getIntLE(dictPos); + dictPos += 4; + ItemReticle val = ItemReticle.deserialize(buf, dictPos); + dictPos += ItemReticle.computeBytesConsumed(buf, dictPos); + if (obj.serverEvents.put(key, val) != null) { + throw ProtocolException.duplicateKey("serverEvents", key); + } + } + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 17 + buf.getIntLE(offset + 13); + int clientEventsCount = VarInt.peek(buf, varPos3); + if (clientEventsCount < 0) { + throw ProtocolException.negativeLength("ClientEvents", clientEventsCount); + } + + if (clientEventsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("ClientEvents", clientEventsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + obj.clientEvents = new HashMap<>(clientEventsCount); + int dictPos = varPos3 + varIntLen; + + for (int ix = 0; ix < clientEventsCount; ix++) { + ItemReticleClientEvent key = ItemReticleClientEvent.fromValue(buf.getByte(dictPos)); + ItemReticle val = ItemReticle.deserialize(buf, ++dictPos); + dictPos += ItemReticle.computeBytesConsumed(buf, dictPos); + if (obj.clientEvents.put(key, val) != null) { + throw ProtocolException.duplicateKey("clientEvents", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 17; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 17 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 17 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 9); + int pos2 = offset + 17 + fieldOffset2; + int dictLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2); + + for (int i = 0; i < dictLen; i++) { + pos2 += 4; + pos2 += ItemReticle.computeBytesConsumed(buf, pos2); + } + + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 13); + int pos3 = offset + 17 + fieldOffset3; + int dictLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3); + + for (int i = 0; i < dictLen; i++) { + pos3 = ++pos3 + ItemReticle.computeBytesConsumed(buf, pos3); + } + + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.base != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.serverEvents != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.clientEvents != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int baseOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int serverEventsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int clientEventsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.base != null) { + buf.setIntLE(baseOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.base.length > 4096000) { + throw ProtocolException.arrayTooLong("Base", this.base.length, 4096000); + } + + VarInt.write(buf, this.base.length); + + for (String item : this.base) { + PacketIO.writeVarString(buf, item, 4096000); + } + } else { + buf.setIntLE(baseOffsetSlot, -1); + } + + if (this.serverEvents != null) { + buf.setIntLE(serverEventsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.serverEvents.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("ServerEvents", this.serverEvents.size(), 4096000); + } + + VarInt.write(buf, this.serverEvents.size()); + + for (Entry e : this.serverEvents.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(serverEventsOffsetSlot, -1); + } + + if (this.clientEvents != null) { + buf.setIntLE(clientEventsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.clientEvents.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("ClientEvents", this.clientEvents.size(), 4096000); + } + + VarInt.write(buf, this.clientEvents.size()); + + for (Entry e : this.clientEvents.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(clientEventsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 17; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.base != null) { + int baseSize = 0; + + for (String elem : this.base) { + baseSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.base.length) + baseSize; + } + + if (this.serverEvents != null) { + int serverEventsSize = 0; + + for (Entry kvp : this.serverEvents.entrySet()) { + serverEventsSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.serverEvents.size()) + serverEventsSize; + } + + if (this.clientEvents != null) { + int clientEventsSize = 0; + + for (Entry kvp : this.clientEvents.entrySet()) { + clientEventsSize += 1 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.clientEvents.size()) + clientEventsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 17) { + return ValidationResult.error("Buffer too small: expected at least 17 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 1); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 17 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int baseOffset = buffer.getIntLE(offset + 5); + if (baseOffset < 0) { + return ValidationResult.error("Invalid offset for Base"); + } + + int posx = offset + 17 + baseOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Base"); + } + + int baseCount = VarInt.peek(buffer, posx); + if (baseCount < 0) { + return ValidationResult.error("Invalid array count for Base"); + } + + if (baseCount > 4096000) { + return ValidationResult.error("Base exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < baseCount; i++) { + int strLen = VarInt.peek(buffer, posx); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in Base"); + } + + posx += VarInt.length(buffer, posx); + posx += strLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in Base"); + } + } + } + + if ((nullBits & 4) != 0) { + int serverEventsOffset = buffer.getIntLE(offset + 9); + if (serverEventsOffset < 0) { + return ValidationResult.error("Invalid offset for ServerEvents"); + } + + int posxx = offset + 17 + serverEventsOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ServerEvents"); + } + + int serverEventsCount = VarInt.peek(buffer, posxx); + if (serverEventsCount < 0) { + return ValidationResult.error("Invalid dictionary count for ServerEvents"); + } + + if (serverEventsCount > 4096000) { + return ValidationResult.error("ServerEvents exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + + for (int i = 0; i < serverEventsCount; i++) { + posxx += 4; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxx += ItemReticle.computeBytesConsumed(buffer, posxx); + } + } + + if ((nullBits & 8) != 0) { + int clientEventsOffset = buffer.getIntLE(offset + 13); + if (clientEventsOffset < 0) { + return ValidationResult.error("Invalid offset for ClientEvents"); + } + + int posxxx = offset + 17 + clientEventsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ClientEvents"); + } + + int clientEventsCount = VarInt.peek(buffer, posxxx); + if (clientEventsCount < 0) { + return ValidationResult.error("Invalid dictionary count for ClientEvents"); + } + + if (clientEventsCount > 4096000) { + return ValidationResult.error("ClientEvents exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + + for (int i = 0; i < clientEventsCount; i++) { + posxxx = ++posxxx + ItemReticle.computeBytesConsumed(buffer, posxxx); + } + } + + return ValidationResult.OK; + } + } + + public ItemReticleConfig clone() { + ItemReticleConfig copy = new ItemReticleConfig(); + copy.id = this.id; + copy.base = this.base != null ? Arrays.copyOf(this.base, this.base.length) : null; + if (this.serverEvents != null) { + Map m = new HashMap<>(); + + for (Entry e : this.serverEvents.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.serverEvents = m; + } + + if (this.clientEvents != null) { + Map m = new HashMap<>(); + + for (Entry e : this.clientEvents.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.clientEvents = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemReticleConfig other) + ? false + : Objects.equals(this.id, other.id) + && Arrays.equals((Object[])this.base, (Object[])other.base) + && Objects.equals(this.serverEvents, other.serverEvents) + && Objects.equals(this.clientEvents, other.clientEvents); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.id); + result = 31 * result + Arrays.hashCode((Object[])this.base); + result = 31 * result + Objects.hashCode(this.serverEvents); + return 31 * result + Objects.hashCode(this.clientEvents); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemSoundEvent.java b/src/com/hypixel/hytale/protocol/ItemSoundEvent.java new file mode 100644 index 0000000..1f4b74b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemSoundEvent.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ItemSoundEvent { + Drag(0), + Drop(1); + + public static final ItemSoundEvent[] VALUES = values(); + private final int value; + + private ItemSoundEvent(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ItemSoundEvent fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ItemSoundEvent", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemSoundSet.java b/src/com/hypixel/hytale/protocol/ItemSoundSet.java new file mode 100644 index 0000000..122dbfc --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemSoundSet.java @@ -0,0 +1,258 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemSoundSet { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 36864019; + @Nullable + public String id; + @Nullable + public Map soundEventIndices; + + public ItemSoundSet() { + } + + public ItemSoundSet(@Nullable String id, @Nullable Map soundEventIndices) { + this.id = id; + this.soundEventIndices = soundEventIndices; + } + + public ItemSoundSet(@Nonnull ItemSoundSet other) { + this.id = other.id; + this.soundEventIndices = other.soundEventIndices; + } + + @Nonnull + public static ItemSoundSet deserialize(@Nonnull ByteBuf buf, int offset) { + ItemSoundSet obj = new ItemSoundSet(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int soundEventIndicesCount = VarInt.peek(buf, varPos1); + if (soundEventIndicesCount < 0) { + throw ProtocolException.negativeLength("SoundEventIndices", soundEventIndicesCount); + } + + if (soundEventIndicesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("SoundEventIndices", soundEventIndicesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.soundEventIndices = new HashMap<>(soundEventIndicesCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < soundEventIndicesCount; i++) { + ItemSoundEvent key = ItemSoundEvent.fromValue(buf.getByte(dictPos)); + int val = buf.getIntLE(++dictPos); + dictPos += 4; + if (obj.soundEventIndices.put(key, val) != null) { + throw ProtocolException.duplicateKey("soundEventIndices", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + 4; + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.soundEventIndices != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int soundEventIndicesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.soundEventIndices != null) { + buf.setIntLE(soundEventIndicesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.soundEventIndices.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("SoundEventIndices", this.soundEventIndices.size(), 4096000); + } + + VarInt.write(buf, this.soundEventIndices.size()); + + for (Entry e : this.soundEventIndices.entrySet()) { + buf.writeByte(e.getKey().getValue()); + buf.writeIntLE(e.getValue()); + } + } else { + buf.setIntLE(soundEventIndicesOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 9; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.soundEventIndices != null) { + size += VarInt.size(this.soundEventIndices.size()) + this.soundEventIndices.size() * 5; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 1); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 9 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int soundEventIndicesOffset = buffer.getIntLE(offset + 5); + if (soundEventIndicesOffset < 0) { + return ValidationResult.error("Invalid offset for SoundEventIndices"); + } + + int posx = offset + 9 + soundEventIndicesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SoundEventIndices"); + } + + int soundEventIndicesCount = VarInt.peek(buffer, posx); + if (soundEventIndicesCount < 0) { + return ValidationResult.error("Invalid dictionary count for SoundEventIndices"); + } + + if (soundEventIndicesCount > 4096000) { + return ValidationResult.error("SoundEventIndices exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < soundEventIndicesCount; i++) { + posx = ++posx + 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public ItemSoundSet clone() { + ItemSoundSet copy = new ItemSoundSet(); + copy.id = this.id; + copy.soundEventIndices = this.soundEventIndices != null ? new HashMap<>(this.soundEventIndices) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemSoundSet other) + ? false + : Objects.equals(this.id, other.id) && Objects.equals(this.soundEventIndices, other.soundEventIndices); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.soundEventIndices); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemTool.java b/src/com/hypixel/hytale/protocol/ItemTool.java new file mode 100644 index 0000000..ebf35e6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemTool.java @@ -0,0 +1,172 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemTool { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 1677721600; + @Nullable + public ItemToolSpec[] specs; + public float speed; + + public ItemTool() { + } + + public ItemTool(@Nullable ItemToolSpec[] specs, float speed) { + this.specs = specs; + this.speed = speed; + } + + public ItemTool(@Nonnull ItemTool other) { + this.specs = other.specs; + this.speed = other.speed; + } + + @Nonnull + public static ItemTool deserialize(@Nonnull ByteBuf buf, int offset) { + ItemTool obj = new ItemTool(); + byte nullBits = buf.getByte(offset); + obj.speed = buf.getFloatLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int specsCount = VarInt.peek(buf, pos); + if (specsCount < 0) { + throw ProtocolException.negativeLength("Specs", specsCount); + } + + if (specsCount > 4096000) { + throw ProtocolException.arrayTooLong("Specs", specsCount, 4096000); + } + + int specsVarLen = VarInt.size(specsCount); + if (pos + specsVarLen + specsCount * 9L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Specs", pos + specsVarLen + specsCount * 9, buf.readableBytes()); + } + + pos += specsVarLen; + obj.specs = new ItemToolSpec[specsCount]; + + for (int i = 0; i < specsCount; i++) { + obj.specs[i] = ItemToolSpec.deserialize(buf, pos); + pos += ItemToolSpec.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += ItemToolSpec.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.specs != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.speed); + if (this.specs != null) { + if (this.specs.length > 4096000) { + throw ProtocolException.arrayTooLong("Specs", this.specs.length, 4096000); + } + + VarInt.write(buf, this.specs.length); + + for (ItemToolSpec item : this.specs) { + item.serialize(buf); + } + } + } + + public int computeSize() { + int size = 5; + if (this.specs != null) { + int specsSize = 0; + + for (ItemToolSpec elem : this.specs) { + specsSize += elem.computeSize(); + } + + size += VarInt.size(this.specs.length) + specsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int specsCount = VarInt.peek(buffer, pos); + if (specsCount < 0) { + return ValidationResult.error("Invalid array count for Specs"); + } + + if (specsCount > 4096000) { + return ValidationResult.error("Specs exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < specsCount; i++) { + ValidationResult structResult = ItemToolSpec.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ItemToolSpec in Specs[" + i + "]: " + structResult.error()); + } + + pos += ItemToolSpec.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public ItemTool clone() { + ItemTool copy = new ItemTool(); + copy.specs = this.specs != null ? Arrays.stream(this.specs).map(e -> e.clone()).toArray(ItemToolSpec[]::new) : null; + copy.speed = this.speed; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemTool other) ? false : Arrays.equals((Object[])this.specs, (Object[])other.specs) && this.speed == other.speed; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode((Object[])this.specs); + return 31 * result + Float.hashCode(this.speed); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemToolSpec.java b/src/com/hypixel/hytale/protocol/ItemToolSpec.java new file mode 100644 index 0000000..4f7a993 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemToolSpec.java @@ -0,0 +1,147 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemToolSpec { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 16384014; + @Nullable + public String gatherType; + public float power; + public int quality; + + public ItemToolSpec() { + } + + public ItemToolSpec(@Nullable String gatherType, float power, int quality) { + this.gatherType = gatherType; + this.power = power; + this.quality = quality; + } + + public ItemToolSpec(@Nonnull ItemToolSpec other) { + this.gatherType = other.gatherType; + this.power = other.power; + this.quality = other.quality; + } + + @Nonnull + public static ItemToolSpec deserialize(@Nonnull ByteBuf buf, int offset) { + ItemToolSpec obj = new ItemToolSpec(); + byte nullBits = buf.getByte(offset); + obj.power = buf.getFloatLE(offset + 1); + obj.quality = buf.getIntLE(offset + 5); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int gatherTypeLen = VarInt.peek(buf, pos); + if (gatherTypeLen < 0) { + throw ProtocolException.negativeLength("GatherType", gatherTypeLen); + } + + if (gatherTypeLen > 4096000) { + throw ProtocolException.stringTooLong("GatherType", gatherTypeLen, 4096000); + } + + int gatherTypeVarLen = VarInt.length(buf, pos); + obj.gatherType = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += gatherTypeVarLen + gatherTypeLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.gatherType != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.power); + buf.writeIntLE(this.quality); + if (this.gatherType != null) { + PacketIO.writeVarString(buf, this.gatherType, 4096000); + } + } + + public int computeSize() { + int size = 9; + if (this.gatherType != null) { + size += PacketIO.stringSize(this.gatherType); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int gatherTypeLen = VarInt.peek(buffer, pos); + if (gatherTypeLen < 0) { + return ValidationResult.error("Invalid string length for GatherType"); + } + + if (gatherTypeLen > 4096000) { + return ValidationResult.error("GatherType exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += gatherTypeLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading GatherType"); + } + } + + return ValidationResult.OK; + } + } + + public ItemToolSpec clone() { + ItemToolSpec copy = new ItemToolSpec(); + copy.gatherType = this.gatherType; + copy.power = this.power; + copy.quality = this.quality; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemToolSpec other) + ? false + : Objects.equals(this.gatherType, other.gatherType) && this.power == other.power && this.quality == other.quality; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.gatherType, this.power, this.quality); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemTranslationProperties.java b/src/com/hypixel/hytale/protocol/ItemTranslationProperties.java new file mode 100644 index 0000000..ba536d7 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemTranslationProperties.java @@ -0,0 +1,227 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemTranslationProperties { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 32768019; + @Nullable + public String name; + @Nullable + public String description; + + public ItemTranslationProperties() { + } + + public ItemTranslationProperties(@Nullable String name, @Nullable String description) { + this.name = name; + this.description = description; + } + + public ItemTranslationProperties(@Nonnull ItemTranslationProperties other) { + this.name = other.name; + this.description = other.description; + } + + @Nonnull + public static ItemTranslationProperties deserialize(@Nonnull ByteBuf buf, int offset) { + ItemTranslationProperties obj = new ItemTranslationProperties(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int nameLen = VarInt.peek(buf, varPos0); + if (nameLen < 0) { + throw ProtocolException.negativeLength("Name", nameLen); + } + + if (nameLen > 4096000) { + throw ProtocolException.stringTooLong("Name", nameLen, 4096000); + } + + obj.name = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int descriptionLen = VarInt.peek(buf, varPos1); + if (descriptionLen < 0) { + throw ProtocolException.negativeLength("Description", descriptionLen); + } + + if (descriptionLen > 4096000) { + throw ProtocolException.stringTooLong("Description", descriptionLen, 4096000); + } + + obj.description = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.name != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.description != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int nameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int descriptionOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.name != null) { + buf.setIntLE(nameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.name, 4096000); + } else { + buf.setIntLE(nameOffsetSlot, -1); + } + + if (this.description != null) { + buf.setIntLE(descriptionOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.description, 4096000); + } else { + buf.setIntLE(descriptionOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 9; + if (this.name != null) { + size += PacketIO.stringSize(this.name); + } + + if (this.description != null) { + size += PacketIO.stringSize(this.description); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int nameOffset = buffer.getIntLE(offset + 1); + if (nameOffset < 0) { + return ValidationResult.error("Invalid offset for Name"); + } + + int pos = offset + 9 + nameOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Name"); + } + + int nameLen = VarInt.peek(buffer, pos); + if (nameLen < 0) { + return ValidationResult.error("Invalid string length for Name"); + } + + if (nameLen > 4096000) { + return ValidationResult.error("Name exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += nameLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Name"); + } + } + + if ((nullBits & 2) != 0) { + int descriptionOffset = buffer.getIntLE(offset + 5); + if (descriptionOffset < 0) { + return ValidationResult.error("Invalid offset for Description"); + } + + int posx = offset + 9 + descriptionOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Description"); + } + + int descriptionLen = VarInt.peek(buffer, posx); + if (descriptionLen < 0) { + return ValidationResult.error("Invalid string length for Description"); + } + + if (descriptionLen > 4096000) { + return ValidationResult.error("Description exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += descriptionLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Description"); + } + } + + return ValidationResult.OK; + } + } + + public ItemTranslationProperties clone() { + ItemTranslationProperties copy = new ItemTranslationProperties(); + copy.name = this.name; + copy.description = this.description; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemTranslationProperties other) + ? false + : Objects.equals(this.name, other.name) && Objects.equals(this.description, other.description); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.name, this.description); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemUtility.java b/src/com/hypixel/hytale/protocol/ItemUtility.java new file mode 100644 index 0000000..56af5c8 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemUtility.java @@ -0,0 +1,351 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemUtility { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 3; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 11; + public static final int MAX_SIZE = 1626112021; + public boolean usable; + public boolean compatible; + @Nullable + public int[] entityStatsToClear; + @Nullable + public Map statModifiers; + + public ItemUtility() { + } + + public ItemUtility(boolean usable, boolean compatible, @Nullable int[] entityStatsToClear, @Nullable Map statModifiers) { + this.usable = usable; + this.compatible = compatible; + this.entityStatsToClear = entityStatsToClear; + this.statModifiers = statModifiers; + } + + public ItemUtility(@Nonnull ItemUtility other) { + this.usable = other.usable; + this.compatible = other.compatible; + this.entityStatsToClear = other.entityStatsToClear; + this.statModifiers = other.statModifiers; + } + + @Nonnull + public static ItemUtility deserialize(@Nonnull ByteBuf buf, int offset) { + ItemUtility obj = new ItemUtility(); + byte nullBits = buf.getByte(offset); + obj.usable = buf.getByte(offset + 1) != 0; + obj.compatible = buf.getByte(offset + 2) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 11 + buf.getIntLE(offset + 3); + int entityStatsToClearCount = VarInt.peek(buf, varPos0); + if (entityStatsToClearCount < 0) { + throw ProtocolException.negativeLength("EntityStatsToClear", entityStatsToClearCount); + } + + if (entityStatsToClearCount > 4096000) { + throw ProtocolException.arrayTooLong("EntityStatsToClear", entityStatsToClearCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + entityStatsToClearCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("EntityStatsToClear", varPos0 + varIntLen + entityStatsToClearCount * 4, buf.readableBytes()); + } + + obj.entityStatsToClear = new int[entityStatsToClearCount]; + + for (int i = 0; i < entityStatsToClearCount; i++) { + obj.entityStatsToClear[i] = buf.getIntLE(varPos0 + varIntLen + i * 4); + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 11 + buf.getIntLE(offset + 7); + int statModifiersCount = VarInt.peek(buf, varPos1); + if (statModifiersCount < 0) { + throw ProtocolException.negativeLength("StatModifiers", statModifiersCount); + } + + if (statModifiersCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("StatModifiers", statModifiersCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.statModifiers = new HashMap<>(statModifiersCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < statModifiersCount; i++) { + int key = buf.getIntLE(dictPos); + dictPos += 4; + int valLen = VarInt.peek(buf, dictPos); + if (valLen < 0) { + throw ProtocolException.negativeLength("val", valLen); + } + + if (valLen > 64) { + throw ProtocolException.arrayTooLong("val", valLen, 64); + } + + int valVarLen = VarInt.length(buf, dictPos); + if (dictPos + valVarLen + valLen * 6L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("val", dictPos + valVarLen + valLen * 6, buf.readableBytes()); + } + + dictPos += valVarLen; + Modifier[] val = new Modifier[valLen]; + + for (int valIdx = 0; valIdx < valLen; valIdx++) { + val[valIdx] = Modifier.deserialize(buf, dictPos); + dictPos += Modifier.computeBytesConsumed(buf, dictPos); + } + + if (obj.statModifiers.put(key, val) != null) { + throw ProtocolException.duplicateKey("statModifiers", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 11; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 3); + int pos0 = offset + 11 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + arrLen * 4; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 7); + int pos1 = offset + 11 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 += 4; + int al = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int j = 0; j < al; j++) { + pos1 += Modifier.computeBytesConsumed(buf, pos1); + } + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.entityStatsToClear != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.statModifiers != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.usable ? 1 : 0); + buf.writeByte(this.compatible ? 1 : 0); + int entityStatsToClearOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int statModifiersOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.entityStatsToClear != null) { + buf.setIntLE(entityStatsToClearOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.entityStatsToClear.length > 4096000) { + throw ProtocolException.arrayTooLong("EntityStatsToClear", this.entityStatsToClear.length, 4096000); + } + + VarInt.write(buf, this.entityStatsToClear.length); + + for (int item : this.entityStatsToClear) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(entityStatsToClearOffsetSlot, -1); + } + + if (this.statModifiers != null) { + buf.setIntLE(statModifiersOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.statModifiers.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("StatModifiers", this.statModifiers.size(), 4096000); + } + + VarInt.write(buf, this.statModifiers.size()); + + for (Entry e : this.statModifiers.entrySet()) { + buf.writeIntLE(e.getKey()); + VarInt.write(buf, e.getValue().length); + + for (Modifier arrItem : e.getValue()) { + arrItem.serialize(buf); + } + } + } else { + buf.setIntLE(statModifiersOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 11; + if (this.entityStatsToClear != null) { + size += VarInt.size(this.entityStatsToClear.length) + this.entityStatsToClear.length * 4; + } + + if (this.statModifiers != null) { + int statModifiersSize = 0; + + for (Entry kvp : this.statModifiers.entrySet()) { + statModifiersSize += 4 + VarInt.size(kvp.getValue().length) + ((Modifier[])kvp.getValue()).length * 6; + } + + size += VarInt.size(this.statModifiers.size()) + statModifiersSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 11) { + return ValidationResult.error("Buffer too small: expected at least 11 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int entityStatsToClearOffset = buffer.getIntLE(offset + 3); + if (entityStatsToClearOffset < 0) { + return ValidationResult.error("Invalid offset for EntityStatsToClear"); + } + + int pos = offset + 11 + entityStatsToClearOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for EntityStatsToClear"); + } + + int entityStatsToClearCount = VarInt.peek(buffer, pos); + if (entityStatsToClearCount < 0) { + return ValidationResult.error("Invalid array count for EntityStatsToClear"); + } + + if (entityStatsToClearCount > 4096000) { + return ValidationResult.error("EntityStatsToClear exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += entityStatsToClearCount * 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading EntityStatsToClear"); + } + } + + if ((nullBits & 2) != 0) { + int statModifiersOffset = buffer.getIntLE(offset + 7); + if (statModifiersOffset < 0) { + return ValidationResult.error("Invalid offset for StatModifiers"); + } + + int posx = offset + 11 + statModifiersOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for StatModifiers"); + } + + int statModifiersCount = VarInt.peek(buffer, posx); + if (statModifiersCount < 0) { + return ValidationResult.error("Invalid dictionary count for StatModifiers"); + } + + if (statModifiersCount > 4096000) { + return ValidationResult.error("StatModifiers exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < statModifiersCount; i++) { + posx += 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + int valueArrCount = VarInt.peek(buffer, posx); + if (valueArrCount < 0) { + return ValidationResult.error("Invalid array count for value"); + } + + posx += VarInt.length(buffer, posx); + + for (int valueArrIdx = 0; valueArrIdx < valueArrCount; valueArrIdx++) { + posx += 6; + } + } + } + + return ValidationResult.OK; + } + } + + public ItemUtility clone() { + ItemUtility copy = new ItemUtility(); + copy.usable = this.usable; + copy.compatible = this.compatible; + copy.entityStatsToClear = this.entityStatsToClear != null ? Arrays.copyOf(this.entityStatsToClear, this.entityStatsToClear.length) : null; + if (this.statModifiers != null) { + Map m = new HashMap<>(); + + for (Entry e : this.statModifiers.entrySet()) { + m.put(e.getKey(), Arrays.stream(e.getValue()).map(x -> x.clone()).toArray(Modifier[]::new)); + } + + copy.statModifiers = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemUtility other) + ? false + : this.usable == other.usable + && this.compatible == other.compatible + && Arrays.equals(this.entityStatsToClear, other.entityStatsToClear) + && Objects.equals(this.statModifiers, other.statModifiers); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Boolean.hashCode(this.usable); + result = 31 * result + Boolean.hashCode(this.compatible); + result = 31 * result + Arrays.hashCode(this.entityStatsToClear); + return 31 * result + Objects.hashCode(this.statModifiers); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemWeapon.java b/src/com/hypixel/hytale/protocol/ItemWeapon.java new file mode 100644 index 0000000..5c01a1a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemWeapon.java @@ -0,0 +1,343 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemWeapon { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 10; + public static final int MAX_SIZE = 1626112020; + @Nullable + public int[] entityStatsToClear; + @Nullable + public Map statModifiers; + public boolean renderDualWielded; + + public ItemWeapon() { + } + + public ItemWeapon(@Nullable int[] entityStatsToClear, @Nullable Map statModifiers, boolean renderDualWielded) { + this.entityStatsToClear = entityStatsToClear; + this.statModifiers = statModifiers; + this.renderDualWielded = renderDualWielded; + } + + public ItemWeapon(@Nonnull ItemWeapon other) { + this.entityStatsToClear = other.entityStatsToClear; + this.statModifiers = other.statModifiers; + this.renderDualWielded = other.renderDualWielded; + } + + @Nonnull + public static ItemWeapon deserialize(@Nonnull ByteBuf buf, int offset) { + ItemWeapon obj = new ItemWeapon(); + byte nullBits = buf.getByte(offset); + obj.renderDualWielded = buf.getByte(offset + 1) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 10 + buf.getIntLE(offset + 2); + int entityStatsToClearCount = VarInt.peek(buf, varPos0); + if (entityStatsToClearCount < 0) { + throw ProtocolException.negativeLength("EntityStatsToClear", entityStatsToClearCount); + } + + if (entityStatsToClearCount > 4096000) { + throw ProtocolException.arrayTooLong("EntityStatsToClear", entityStatsToClearCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + entityStatsToClearCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("EntityStatsToClear", varPos0 + varIntLen + entityStatsToClearCount * 4, buf.readableBytes()); + } + + obj.entityStatsToClear = new int[entityStatsToClearCount]; + + for (int i = 0; i < entityStatsToClearCount; i++) { + obj.entityStatsToClear[i] = buf.getIntLE(varPos0 + varIntLen + i * 4); + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 10 + buf.getIntLE(offset + 6); + int statModifiersCount = VarInt.peek(buf, varPos1); + if (statModifiersCount < 0) { + throw ProtocolException.negativeLength("StatModifiers", statModifiersCount); + } + + if (statModifiersCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("StatModifiers", statModifiersCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.statModifiers = new HashMap<>(statModifiersCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < statModifiersCount; i++) { + int key = buf.getIntLE(dictPos); + dictPos += 4; + int valLen = VarInt.peek(buf, dictPos); + if (valLen < 0) { + throw ProtocolException.negativeLength("val", valLen); + } + + if (valLen > 64) { + throw ProtocolException.arrayTooLong("val", valLen, 64); + } + + int valVarLen = VarInt.length(buf, dictPos); + if (dictPos + valVarLen + valLen * 6L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("val", dictPos + valVarLen + valLen * 6, buf.readableBytes()); + } + + dictPos += valVarLen; + Modifier[] val = new Modifier[valLen]; + + for (int valIdx = 0; valIdx < valLen; valIdx++) { + val[valIdx] = Modifier.deserialize(buf, dictPos); + dictPos += Modifier.computeBytesConsumed(buf, dictPos); + } + + if (obj.statModifiers.put(key, val) != null) { + throw ProtocolException.duplicateKey("statModifiers", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 10; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 2); + int pos0 = offset + 10 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + arrLen * 4; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 6); + int pos1 = offset + 10 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 += 4; + int al = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int j = 0; j < al; j++) { + pos1 += Modifier.computeBytesConsumed(buf, pos1); + } + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.entityStatsToClear != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.statModifiers != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.renderDualWielded ? 1 : 0); + int entityStatsToClearOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int statModifiersOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.entityStatsToClear != null) { + buf.setIntLE(entityStatsToClearOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.entityStatsToClear.length > 4096000) { + throw ProtocolException.arrayTooLong("EntityStatsToClear", this.entityStatsToClear.length, 4096000); + } + + VarInt.write(buf, this.entityStatsToClear.length); + + for (int item : this.entityStatsToClear) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(entityStatsToClearOffsetSlot, -1); + } + + if (this.statModifiers != null) { + buf.setIntLE(statModifiersOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.statModifiers.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("StatModifiers", this.statModifiers.size(), 4096000); + } + + VarInt.write(buf, this.statModifiers.size()); + + for (Entry e : this.statModifiers.entrySet()) { + buf.writeIntLE(e.getKey()); + VarInt.write(buf, e.getValue().length); + + for (Modifier arrItem : e.getValue()) { + arrItem.serialize(buf); + } + } + } else { + buf.setIntLE(statModifiersOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 10; + if (this.entityStatsToClear != null) { + size += VarInt.size(this.entityStatsToClear.length) + this.entityStatsToClear.length * 4; + } + + if (this.statModifiers != null) { + int statModifiersSize = 0; + + for (Entry kvp : this.statModifiers.entrySet()) { + statModifiersSize += 4 + VarInt.size(kvp.getValue().length) + ((Modifier[])kvp.getValue()).length * 6; + } + + size += VarInt.size(this.statModifiers.size()) + statModifiersSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 10) { + return ValidationResult.error("Buffer too small: expected at least 10 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int entityStatsToClearOffset = buffer.getIntLE(offset + 2); + if (entityStatsToClearOffset < 0) { + return ValidationResult.error("Invalid offset for EntityStatsToClear"); + } + + int pos = offset + 10 + entityStatsToClearOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for EntityStatsToClear"); + } + + int entityStatsToClearCount = VarInt.peek(buffer, pos); + if (entityStatsToClearCount < 0) { + return ValidationResult.error("Invalid array count for EntityStatsToClear"); + } + + if (entityStatsToClearCount > 4096000) { + return ValidationResult.error("EntityStatsToClear exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += entityStatsToClearCount * 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading EntityStatsToClear"); + } + } + + if ((nullBits & 2) != 0) { + int statModifiersOffset = buffer.getIntLE(offset + 6); + if (statModifiersOffset < 0) { + return ValidationResult.error("Invalid offset for StatModifiers"); + } + + int posx = offset + 10 + statModifiersOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for StatModifiers"); + } + + int statModifiersCount = VarInt.peek(buffer, posx); + if (statModifiersCount < 0) { + return ValidationResult.error("Invalid dictionary count for StatModifiers"); + } + + if (statModifiersCount > 4096000) { + return ValidationResult.error("StatModifiers exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < statModifiersCount; i++) { + posx += 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + int valueArrCount = VarInt.peek(buffer, posx); + if (valueArrCount < 0) { + return ValidationResult.error("Invalid array count for value"); + } + + posx += VarInt.length(buffer, posx); + + for (int valueArrIdx = 0; valueArrIdx < valueArrCount; valueArrIdx++) { + posx += 6; + } + } + } + + return ValidationResult.OK; + } + } + + public ItemWeapon clone() { + ItemWeapon copy = new ItemWeapon(); + copy.entityStatsToClear = this.entityStatsToClear != null ? Arrays.copyOf(this.entityStatsToClear, this.entityStatsToClear.length) : null; + if (this.statModifiers != null) { + Map m = new HashMap<>(); + + for (Entry e : this.statModifiers.entrySet()) { + m.put(e.getKey(), Arrays.stream(e.getValue()).map(x -> x.clone()).toArray(Modifier[]::new)); + } + + copy.statModifiers = m; + } + + copy.renderDualWielded = this.renderDualWielded; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemWeapon other) + ? false + : Arrays.equals(this.entityStatsToClear, other.entityStatsToClear) + && Objects.equals(this.statModifiers, other.statModifiers) + && this.renderDualWielded == other.renderDualWielded; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode(this.entityStatsToClear); + result = 31 * result + Objects.hashCode(this.statModifiers); + return 31 * result + Boolean.hashCode(this.renderDualWielded); + } +} diff --git a/src/com/hypixel/hytale/protocol/ItemWithAllMetadata.java b/src/com/hypixel/hytale/protocol/ItemWithAllMetadata.java new file mode 100644 index 0000000..189d80e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ItemWithAllMetadata.java @@ -0,0 +1,235 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemWithAllMetadata { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 22; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 30; + public static final int MAX_SIZE = 32768040; + @Nonnull + public String itemId = ""; + public int quantity; + public double durability; + public double maxDurability; + public boolean overrideDroppedItemAnimation; + @Nullable + public String metadata; + + public ItemWithAllMetadata() { + } + + public ItemWithAllMetadata( + @Nonnull String itemId, int quantity, double durability, double maxDurability, boolean overrideDroppedItemAnimation, @Nullable String metadata + ) { + this.itemId = itemId; + this.quantity = quantity; + this.durability = durability; + this.maxDurability = maxDurability; + this.overrideDroppedItemAnimation = overrideDroppedItemAnimation; + this.metadata = metadata; + } + + public ItemWithAllMetadata(@Nonnull ItemWithAllMetadata other) { + this.itemId = other.itemId; + this.quantity = other.quantity; + this.durability = other.durability; + this.maxDurability = other.maxDurability; + this.overrideDroppedItemAnimation = other.overrideDroppedItemAnimation; + this.metadata = other.metadata; + } + + @Nonnull + public static ItemWithAllMetadata deserialize(@Nonnull ByteBuf buf, int offset) { + ItemWithAllMetadata obj = new ItemWithAllMetadata(); + byte nullBits = buf.getByte(offset); + obj.quantity = buf.getIntLE(offset + 1); + obj.durability = buf.getDoubleLE(offset + 5); + obj.maxDurability = buf.getDoubleLE(offset + 13); + obj.overrideDroppedItemAnimation = buf.getByte(offset + 21) != 0; + int varPos0 = offset + 30 + buf.getIntLE(offset + 22); + int itemIdLen = VarInt.peek(buf, varPos0); + if (itemIdLen < 0) { + throw ProtocolException.negativeLength("ItemId", itemIdLen); + } else if (itemIdLen > 4096000) { + throw ProtocolException.stringTooLong("ItemId", itemIdLen, 4096000); + } else { + obj.itemId = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + if ((nullBits & 1) != 0) { + varPos0 = offset + 30 + buf.getIntLE(offset + 26); + itemIdLen = VarInt.peek(buf, varPos0); + if (itemIdLen < 0) { + throw ProtocolException.negativeLength("Metadata", itemIdLen); + } + + if (itemIdLen > 4096000) { + throw ProtocolException.stringTooLong("Metadata", itemIdLen, 4096000); + } + + obj.metadata = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + return obj; + } + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 30; + int fieldOffset0 = buf.getIntLE(offset + 22); + int pos0 = offset + 30 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + + if ((nullBits & 1) != 0) { + fieldOffset0 = buf.getIntLE(offset + 26); + pos0 = offset + 30 + fieldOffset0; + sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.metadata != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.quantity); + buf.writeDoubleLE(this.durability); + buf.writeDoubleLE(this.maxDurability); + buf.writeByte(this.overrideDroppedItemAnimation ? 1 : 0); + int itemIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int metadataOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + buf.setIntLE(itemIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.itemId, 4096000); + if (this.metadata != null) { + buf.setIntLE(metadataOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.metadata, 4096000); + } else { + buf.setIntLE(metadataOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 30; + size += PacketIO.stringSize(this.itemId); + if (this.metadata != null) { + size += PacketIO.stringSize(this.metadata); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 30) { + return ValidationResult.error("Buffer too small: expected at least 30 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int itemIdOffset = buffer.getIntLE(offset + 22); + if (itemIdOffset < 0) { + return ValidationResult.error("Invalid offset for ItemId"); + } else { + int pos = offset + 30 + itemIdOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ItemId"); + } else { + int itemIdLen = VarInt.peek(buffer, pos); + if (itemIdLen < 0) { + return ValidationResult.error("Invalid string length for ItemId"); + } else if (itemIdLen > 4096000) { + return ValidationResult.error("ItemId exceeds max length 4096000"); + } else { + pos += VarInt.length(buffer, pos); + pos += itemIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ItemId"); + } else { + if ((nullBits & 1) != 0) { + itemIdOffset = buffer.getIntLE(offset + 26); + if (itemIdOffset < 0) { + return ValidationResult.error("Invalid offset for Metadata"); + } + + pos = offset + 30 + itemIdOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Metadata"); + } + + itemIdLen = VarInt.peek(buffer, pos); + if (itemIdLen < 0) { + return ValidationResult.error("Invalid string length for Metadata"); + } + + if (itemIdLen > 4096000) { + return ValidationResult.error("Metadata exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += itemIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Metadata"); + } + } + + return ValidationResult.OK; + } + } + } + } + } + } + + public ItemWithAllMetadata clone() { + ItemWithAllMetadata copy = new ItemWithAllMetadata(); + copy.itemId = this.itemId; + copy.quantity = this.quantity; + copy.durability = this.durability; + copy.maxDurability = this.maxDurability; + copy.overrideDroppedItemAnimation = this.overrideDroppedItemAnimation; + copy.metadata = this.metadata; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ItemWithAllMetadata other) + ? false + : Objects.equals(this.itemId, other.itemId) + && this.quantity == other.quantity + && this.durability == other.durability + && this.maxDurability == other.maxDurability + && this.overrideDroppedItemAnimation == other.overrideDroppedItemAnimation + && Objects.equals(this.metadata, other.metadata); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.itemId, this.quantity, this.durability, this.maxDurability, this.overrideDroppedItemAnimation, this.metadata); + } +} diff --git a/src/com/hypixel/hytale/protocol/LongParamValue.java b/src/com/hypixel/hytale/protocol/LongParamValue.java new file mode 100644 index 0000000..f982921 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/LongParamValue.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class LongParamValue extends ParamValue { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public long value; + + public LongParamValue() { + } + + public LongParamValue(long value) { + this.value = value; + } + + public LongParamValue(@Nonnull LongParamValue other) { + this.value = other.value; + } + + @Nonnull + public static LongParamValue deserialize(@Nonnull ByteBuf buf, int offset) { + LongParamValue obj = new LongParamValue(); + obj.value = buf.getLongLE(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + buf.writeLongLE(this.value); + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public LongParamValue clone() { + LongParamValue copy = new LongParamValue(); + copy.value = this.value; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof LongParamValue other ? this.value == other.value : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.value); + } +} diff --git a/src/com/hypixel/hytale/protocol/LoopOption.java b/src/com/hypixel/hytale/protocol/LoopOption.java new file mode 100644 index 0000000..f444298 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/LoopOption.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum LoopOption { + PlayOnce(0), + Loop(1), + LoopMirror(2); + + public static final LoopOption[] VALUES = values(); + private final int value; + + private LoopOption(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static LoopOption fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("LoopOption", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/Match.java b/src/com/hypixel/hytale/protocol/Match.java new file mode 100644 index 0000000..228c7d3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Match.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum Match { + All(0), + None(1); + + public static final Match[] VALUES = values(); + private final int value; + + private Match(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static Match fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("Match", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/MaterialQuantity.java b/src/com/hypixel/hytale/protocol/MaterialQuantity.java new file mode 100644 index 0000000..c44bc2e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MaterialQuantity.java @@ -0,0 +1,242 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MaterialQuantity { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 17; + public static final int MAX_SIZE = 32768027; + @Nullable + public String itemId; + public int itemTag; + @Nullable + public String resourceTypeId; + public int quantity; + + public MaterialQuantity() { + } + + public MaterialQuantity(@Nullable String itemId, int itemTag, @Nullable String resourceTypeId, int quantity) { + this.itemId = itemId; + this.itemTag = itemTag; + this.resourceTypeId = resourceTypeId; + this.quantity = quantity; + } + + public MaterialQuantity(@Nonnull MaterialQuantity other) { + this.itemId = other.itemId; + this.itemTag = other.itemTag; + this.resourceTypeId = other.resourceTypeId; + this.quantity = other.quantity; + } + + @Nonnull + public static MaterialQuantity deserialize(@Nonnull ByteBuf buf, int offset) { + MaterialQuantity obj = new MaterialQuantity(); + byte nullBits = buf.getByte(offset); + obj.itemTag = buf.getIntLE(offset + 1); + obj.quantity = buf.getIntLE(offset + 5); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 17 + buf.getIntLE(offset + 9); + int itemIdLen = VarInt.peek(buf, varPos0); + if (itemIdLen < 0) { + throw ProtocolException.negativeLength("ItemId", itemIdLen); + } + + if (itemIdLen > 4096000) { + throw ProtocolException.stringTooLong("ItemId", itemIdLen, 4096000); + } + + obj.itemId = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 17 + buf.getIntLE(offset + 13); + int resourceTypeIdLen = VarInt.peek(buf, varPos1); + if (resourceTypeIdLen < 0) { + throw ProtocolException.negativeLength("ResourceTypeId", resourceTypeIdLen); + } + + if (resourceTypeIdLen > 4096000) { + throw ProtocolException.stringTooLong("ResourceTypeId", resourceTypeIdLen, 4096000); + } + + obj.resourceTypeId = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 17; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 9); + int pos0 = offset + 17 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 13); + int pos1 = offset + 17 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.itemId != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.resourceTypeId != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.itemTag); + buf.writeIntLE(this.quantity); + int itemIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int resourceTypeIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.itemId != null) { + buf.setIntLE(itemIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.itemId, 4096000); + } else { + buf.setIntLE(itemIdOffsetSlot, -1); + } + + if (this.resourceTypeId != null) { + buf.setIntLE(resourceTypeIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.resourceTypeId, 4096000); + } else { + buf.setIntLE(resourceTypeIdOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 17; + if (this.itemId != null) { + size += PacketIO.stringSize(this.itemId); + } + + if (this.resourceTypeId != null) { + size += PacketIO.stringSize(this.resourceTypeId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 17) { + return ValidationResult.error("Buffer too small: expected at least 17 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int itemIdOffset = buffer.getIntLE(offset + 9); + if (itemIdOffset < 0) { + return ValidationResult.error("Invalid offset for ItemId"); + } + + int pos = offset + 17 + itemIdOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ItemId"); + } + + int itemIdLen = VarInt.peek(buffer, pos); + if (itemIdLen < 0) { + return ValidationResult.error("Invalid string length for ItemId"); + } + + if (itemIdLen > 4096000) { + return ValidationResult.error("ItemId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += itemIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ItemId"); + } + } + + if ((nullBits & 2) != 0) { + int resourceTypeIdOffset = buffer.getIntLE(offset + 13); + if (resourceTypeIdOffset < 0) { + return ValidationResult.error("Invalid offset for ResourceTypeId"); + } + + int posx = offset + 17 + resourceTypeIdOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ResourceTypeId"); + } + + int resourceTypeIdLen = VarInt.peek(buffer, posx); + if (resourceTypeIdLen < 0) { + return ValidationResult.error("Invalid string length for ResourceTypeId"); + } + + if (resourceTypeIdLen > 4096000) { + return ValidationResult.error("ResourceTypeId exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += resourceTypeIdLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ResourceTypeId"); + } + } + + return ValidationResult.OK; + } + } + + public MaterialQuantity clone() { + MaterialQuantity copy = new MaterialQuantity(); + copy.itemId = this.itemId; + copy.itemTag = this.itemTag; + copy.resourceTypeId = this.resourceTypeId; + copy.quantity = this.quantity; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof MaterialQuantity other) + ? false + : Objects.equals(this.itemId, other.itemId) + && this.itemTag == other.itemTag + && Objects.equals(this.resourceTypeId, other.resourceTypeId) + && this.quantity == other.quantity; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.itemId, this.itemTag, this.resourceTypeId, this.quantity); + } +} diff --git a/src/com/hypixel/hytale/protocol/MaybeBool.java b/src/com/hypixel/hytale/protocol/MaybeBool.java new file mode 100644 index 0000000..b375e16 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MaybeBool.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum MaybeBool { + Null(0), + False(1), + True(2); + + public static final MaybeBool[] VALUES = values(); + private final int value; + + private MaybeBool(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static MaybeBool fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("MaybeBool", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/MemoriesConditionInteraction.java b/src/com/hypixel/hytale/protocol/MemoriesConditionInteraction.java new file mode 100644 index 0000000..7d3cfe8 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MemoriesConditionInteraction.java @@ -0,0 +1,608 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MemoriesConditionInteraction extends Interaction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 15; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 39; + public static final int MAX_SIZE = 1677721600; + @Nullable + public Map memoriesNext; + public int failed = Integer.MIN_VALUE; + + public MemoriesConditionInteraction() { + } + + public MemoriesConditionInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + @Nullable Map memoriesNext, + int failed + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.memoriesNext = memoriesNext; + this.failed = failed; + } + + public MemoriesConditionInteraction(@Nonnull MemoriesConditionInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.memoriesNext = other.memoriesNext; + this.failed = other.failed; + } + + @Nonnull + public static MemoriesConditionInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + MemoriesConditionInteraction obj = new MemoriesConditionInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.failed = buf.getIntLE(offset + 11); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 39 + buf.getIntLE(offset + 15); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 39 + buf.getIntLE(offset + 19); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 39 + buf.getIntLE(offset + 23); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 39 + buf.getIntLE(offset + 27); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 39 + buf.getIntLE(offset + 31); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 39 + buf.getIntLE(offset + 35); + int memoriesNextCount = VarInt.peek(buf, varPos5); + if (memoriesNextCount < 0) { + throw ProtocolException.negativeLength("MemoriesNext", memoriesNextCount); + } + + if (memoriesNextCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("MemoriesNext", memoriesNextCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + obj.memoriesNext = new HashMap<>(memoriesNextCount); + int dictPos = varPos5 + varIntLen; + + for (int ix = 0; ix < memoriesNextCount; ix++) { + int key = buf.getIntLE(dictPos); + dictPos += 4; + int val = buf.getIntLE(dictPos); + dictPos += 4; + if (obj.memoriesNext.put(key, val) != null) { + throw ProtocolException.duplicateKey("memoriesNext", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 39; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 15); + int pos0 = offset + 39 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 19); + int pos1 = offset + 39 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 23); + int pos2 = offset + 39 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 27); + int pos3 = offset + 39 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 31); + int pos4 = offset + 39 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 35); + int pos5 = offset + 39 + fieldOffset5; + int dictLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5); + + for (int i = 0; i < dictLen; i++) { + pos5 += 4; + pos5 += 4; + } + + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.memoriesNext != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.failed); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int memoriesNextOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.memoriesNext != null) { + buf.setIntLE(memoriesNextOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.memoriesNext.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("MemoriesNext", this.memoriesNext.size(), 4096000); + } + + VarInt.write(buf, this.memoriesNext.size()); + + for (Entry e : this.memoriesNext.entrySet()) { + buf.writeIntLE(e.getKey()); + buf.writeIntLE(e.getValue()); + } + } else { + buf.setIntLE(memoriesNextOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 39; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.memoriesNext != null) { + size += VarInt.size(this.memoriesNext.size()) + this.memoriesNext.size() * 8; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 39) { + return ValidationResult.error("Buffer too small: expected at least 39 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 15); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 39 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 19); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 39 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 23); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 39 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 27); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 39 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 31); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 39 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int memoriesNextOffset = buffer.getIntLE(offset + 35); + if (memoriesNextOffset < 0) { + return ValidationResult.error("Invalid offset for MemoriesNext"); + } + + int posxxxxx = offset + 39 + memoriesNextOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MemoriesNext"); + } + + int memoriesNextCount = VarInt.peek(buffer, posxxxxx); + if (memoriesNextCount < 0) { + return ValidationResult.error("Invalid dictionary count for MemoriesNext"); + } + + if (memoriesNextCount > 4096000) { + return ValidationResult.error("MemoriesNext exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + + for (int i = 0; i < memoriesNextCount; i++) { + posxxxxx += 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxx += 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public MemoriesConditionInteraction clone() { + MemoriesConditionInteraction copy = new MemoriesConditionInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.memoriesNext = this.memoriesNext != null ? new HashMap<>(this.memoriesNext) : null; + copy.failed = this.failed; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof MemoriesConditionInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && Objects.equals(this.memoriesNext, other.memoriesNext) + && this.failed == other.failed; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Objects.hashCode(this.memoriesNext); + return 31 * result + Integer.hashCode(this.failed); + } +} diff --git a/src/com/hypixel/hytale/protocol/Model.java b/src/com/hypixel/hytale/protocol/Model.java new file mode 100644 index 0000000..d154c75 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Model.java @@ -0,0 +1,1321 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Model { + public static final int NULLABLE_BIT_FIELD_SIZE = 2; + public static final int FIXED_BLOCK_SIZE = 43; + public static final int VARIABLE_FIELD_COUNT = 12; + public static final int VARIABLE_BLOCK_START = 91; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String assetId; + @Nullable + public String path; + @Nullable + public String texture; + @Nullable + public String gradientSet; + @Nullable + public String gradientId; + @Nullable + public CameraSettings camera; + public float scale; + public float eyeHeight; + public float crouchOffset; + @Nullable + public Map animationSets; + @Nullable + public ModelAttachment[] attachments; + @Nullable + public Hitbox hitbox; + @Nullable + public ModelParticle[] particles; + @Nullable + public ModelTrail[] trails; + @Nullable + public ColorLight light; + @Nullable + public Map detailBoxes; + @Nonnull + public Phobia phobia = Phobia.None; + @Nullable + public Model phobiaModel; + + public Model() { + } + + public Model( + @Nullable String assetId, + @Nullable String path, + @Nullable String texture, + @Nullable String gradientSet, + @Nullable String gradientId, + @Nullable CameraSettings camera, + float scale, + float eyeHeight, + float crouchOffset, + @Nullable Map animationSets, + @Nullable ModelAttachment[] attachments, + @Nullable Hitbox hitbox, + @Nullable ModelParticle[] particles, + @Nullable ModelTrail[] trails, + @Nullable ColorLight light, + @Nullable Map detailBoxes, + @Nonnull Phobia phobia, + @Nullable Model phobiaModel + ) { + this.assetId = assetId; + this.path = path; + this.texture = texture; + this.gradientSet = gradientSet; + this.gradientId = gradientId; + this.camera = camera; + this.scale = scale; + this.eyeHeight = eyeHeight; + this.crouchOffset = crouchOffset; + this.animationSets = animationSets; + this.attachments = attachments; + this.hitbox = hitbox; + this.particles = particles; + this.trails = trails; + this.light = light; + this.detailBoxes = detailBoxes; + this.phobia = phobia; + this.phobiaModel = phobiaModel; + } + + public Model(@Nonnull Model other) { + this.assetId = other.assetId; + this.path = other.path; + this.texture = other.texture; + this.gradientSet = other.gradientSet; + this.gradientId = other.gradientId; + this.camera = other.camera; + this.scale = other.scale; + this.eyeHeight = other.eyeHeight; + this.crouchOffset = other.crouchOffset; + this.animationSets = other.animationSets; + this.attachments = other.attachments; + this.hitbox = other.hitbox; + this.particles = other.particles; + this.trails = other.trails; + this.light = other.light; + this.detailBoxes = other.detailBoxes; + this.phobia = other.phobia; + this.phobiaModel = other.phobiaModel; + } + + @Nonnull + public static Model deserialize(@Nonnull ByteBuf buf, int offset) { + Model obj = new Model(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + obj.scale = buf.getFloatLE(offset + 2); + obj.eyeHeight = buf.getFloatLE(offset + 6); + obj.crouchOffset = buf.getFloatLE(offset + 10); + if ((nullBits[1] & 1) != 0) { + obj.hitbox = Hitbox.deserialize(buf, offset + 14); + } + + if ((nullBits[1] & 8) != 0) { + obj.light = ColorLight.deserialize(buf, offset + 38); + } + + obj.phobia = Phobia.fromValue(buf.getByte(offset + 42)); + if ((nullBits[0] & 1) != 0) { + int varPos0 = offset + 91 + buf.getIntLE(offset + 43); + int assetIdLen = VarInt.peek(buf, varPos0); + if (assetIdLen < 0) { + throw ProtocolException.negativeLength("AssetId", assetIdLen); + } + + if (assetIdLen > 4096000) { + throw ProtocolException.stringTooLong("AssetId", assetIdLen, 4096000); + } + + obj.assetId = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits[0] & 2) != 0) { + int varPos1 = offset + 91 + buf.getIntLE(offset + 47); + int pathLen = VarInt.peek(buf, varPos1); + if (pathLen < 0) { + throw ProtocolException.negativeLength("Path", pathLen); + } + + if (pathLen > 4096000) { + throw ProtocolException.stringTooLong("Path", pathLen, 4096000); + } + + obj.path = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits[0] & 4) != 0) { + int varPos2 = offset + 91 + buf.getIntLE(offset + 51); + int textureLen = VarInt.peek(buf, varPos2); + if (textureLen < 0) { + throw ProtocolException.negativeLength("Texture", textureLen); + } + + if (textureLen > 4096000) { + throw ProtocolException.stringTooLong("Texture", textureLen, 4096000); + } + + obj.texture = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits[0] & 8) != 0) { + int varPos3 = offset + 91 + buf.getIntLE(offset + 55); + int gradientSetLen = VarInt.peek(buf, varPos3); + if (gradientSetLen < 0) { + throw ProtocolException.negativeLength("GradientSet", gradientSetLen); + } + + if (gradientSetLen > 4096000) { + throw ProtocolException.stringTooLong("GradientSet", gradientSetLen, 4096000); + } + + obj.gradientSet = PacketIO.readVarString(buf, varPos3, PacketIO.UTF8); + } + + if ((nullBits[0] & 16) != 0) { + int varPos4 = offset + 91 + buf.getIntLE(offset + 59); + int gradientIdLen = VarInt.peek(buf, varPos4); + if (gradientIdLen < 0) { + throw ProtocolException.negativeLength("GradientId", gradientIdLen); + } + + if (gradientIdLen > 4096000) { + throw ProtocolException.stringTooLong("GradientId", gradientIdLen, 4096000); + } + + obj.gradientId = PacketIO.readVarString(buf, varPos4, PacketIO.UTF8); + } + + if ((nullBits[0] & 32) != 0) { + int varPos5 = offset + 91 + buf.getIntLE(offset + 63); + obj.camera = CameraSettings.deserialize(buf, varPos5); + } + + if ((nullBits[0] & 64) != 0) { + int varPos6 = offset + 91 + buf.getIntLE(offset + 67); + int animationSetsCount = VarInt.peek(buf, varPos6); + if (animationSetsCount < 0) { + throw ProtocolException.negativeLength("AnimationSets", animationSetsCount); + } + + if (animationSetsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("AnimationSets", animationSetsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos6); + obj.animationSets = new HashMap<>(animationSetsCount); + int dictPos = varPos6 + varIntLen; + + for (int i = 0; i < animationSetsCount; i++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + AnimationSet val = AnimationSet.deserialize(buf, dictPos); + dictPos += AnimationSet.computeBytesConsumed(buf, dictPos); + if (obj.animationSets.put(key, val) != null) { + throw ProtocolException.duplicateKey("animationSets", key); + } + } + } + + if ((nullBits[0] & 128) != 0) { + int varPos7 = offset + 91 + buf.getIntLE(offset + 71); + int attachmentsCount = VarInt.peek(buf, varPos7); + if (attachmentsCount < 0) { + throw ProtocolException.negativeLength("Attachments", attachmentsCount); + } + + if (attachmentsCount > 4096000) { + throw ProtocolException.arrayTooLong("Attachments", attachmentsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos7); + if (varPos7 + varIntLen + attachmentsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Attachments", varPos7 + varIntLen + attachmentsCount * 1, buf.readableBytes()); + } + + obj.attachments = new ModelAttachment[attachmentsCount]; + int elemPos = varPos7 + varIntLen; + + for (int i = 0; i < attachmentsCount; i++) { + obj.attachments[i] = ModelAttachment.deserialize(buf, elemPos); + elemPos += ModelAttachment.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits[1] & 2) != 0) { + int varPos8 = offset + 91 + buf.getIntLE(offset + 75); + int particlesCount = VarInt.peek(buf, varPos8); + if (particlesCount < 0) { + throw ProtocolException.negativeLength("Particles", particlesCount); + } + + if (particlesCount > 4096000) { + throw ProtocolException.arrayTooLong("Particles", particlesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos8); + if (varPos8 + varIntLen + particlesCount * 34L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Particles", varPos8 + varIntLen + particlesCount * 34, buf.readableBytes()); + } + + obj.particles = new ModelParticle[particlesCount]; + int elemPos = varPos8 + varIntLen; + + for (int i = 0; i < particlesCount; i++) { + obj.particles[i] = ModelParticle.deserialize(buf, elemPos); + elemPos += ModelParticle.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits[1] & 4) != 0) { + int varPos9 = offset + 91 + buf.getIntLE(offset + 79); + int trailsCount = VarInt.peek(buf, varPos9); + if (trailsCount < 0) { + throw ProtocolException.negativeLength("Trails", trailsCount); + } + + if (trailsCount > 4096000) { + throw ProtocolException.arrayTooLong("Trails", trailsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos9); + if (varPos9 + varIntLen + trailsCount * 27L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Trails", varPos9 + varIntLen + trailsCount * 27, buf.readableBytes()); + } + + obj.trails = new ModelTrail[trailsCount]; + int elemPos = varPos9 + varIntLen; + + for (int i = 0; i < trailsCount; i++) { + obj.trails[i] = ModelTrail.deserialize(buf, elemPos); + elemPos += ModelTrail.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits[1] & 16) != 0) { + int varPos10 = offset + 91 + buf.getIntLE(offset + 83); + int detailBoxesCount = VarInt.peek(buf, varPos10); + if (detailBoxesCount < 0) { + throw ProtocolException.negativeLength("DetailBoxes", detailBoxesCount); + } + + if (detailBoxesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("DetailBoxes", detailBoxesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos10); + obj.detailBoxes = new HashMap<>(detailBoxesCount); + int dictPos = varPos10 + varIntLen; + + for (int i = 0; i < detailBoxesCount; i++) { + int keyLenx = VarInt.peek(buf, dictPos); + if (keyLenx < 0) { + throw ProtocolException.negativeLength("key", keyLenx); + } + + if (keyLenx > 4096000) { + throw ProtocolException.stringTooLong("key", keyLenx, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLenx; + int valLen = VarInt.peek(buf, dictPos); + if (valLen < 0) { + throw ProtocolException.negativeLength("val", valLen); + } + + if (valLen > 64) { + throw ProtocolException.arrayTooLong("val", valLen, 64); + } + + int valVarLen = VarInt.length(buf, dictPos); + if (dictPos + valVarLen + valLen * 37L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("val", dictPos + valVarLen + valLen * 37, buf.readableBytes()); + } + + dictPos += valVarLen; + DetailBox[] val = new DetailBox[valLen]; + + for (int valIdx = 0; valIdx < valLen; valIdx++) { + val[valIdx] = DetailBox.deserialize(buf, dictPos); + dictPos += DetailBox.computeBytesConsumed(buf, dictPos); + } + + if (obj.detailBoxes.put(key, val) != null) { + throw ProtocolException.duplicateKey("detailBoxes", key); + } + } + } + + if ((nullBits[1] & 32) != 0) { + int varPos11 = offset + 91 + buf.getIntLE(offset + 87); + obj.phobiaModel = deserialize(buf, varPos11); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + int maxEnd = 91; + if ((nullBits[0] & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 43); + int pos0 = offset + 91 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits[0] & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 47); + int pos1 = offset + 91 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits[0] & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 51); + int pos2 = offset + 91 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits[0] & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 55); + int pos3 = offset + 91 + fieldOffset3; + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits[0] & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 59); + int pos4 = offset + 91 + fieldOffset4; + int sl = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4) + sl; + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits[0] & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 63); + int pos5 = offset + 91 + fieldOffset5; + pos5 += CameraSettings.computeBytesConsumed(buf, pos5); + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits[0] & 64) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 67); + int pos6 = offset + 91 + fieldOffset6; + int dictLen = VarInt.peek(buf, pos6); + pos6 += VarInt.length(buf, pos6); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos6); + pos6 += VarInt.length(buf, pos6) + sl; + pos6 += AnimationSet.computeBytesConsumed(buf, pos6); + } + + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + if ((nullBits[0] & 128) != 0) { + int fieldOffset7 = buf.getIntLE(offset + 71); + int pos7 = offset + 91 + fieldOffset7; + int arrLen = VarInt.peek(buf, pos7); + pos7 += VarInt.length(buf, pos7); + + for (int i = 0; i < arrLen; i++) { + pos7 += ModelAttachment.computeBytesConsumed(buf, pos7); + } + + if (pos7 - offset > maxEnd) { + maxEnd = pos7 - offset; + } + } + + if ((nullBits[1] & 2) != 0) { + int fieldOffset8 = buf.getIntLE(offset + 75); + int pos8 = offset + 91 + fieldOffset8; + int arrLen = VarInt.peek(buf, pos8); + pos8 += VarInt.length(buf, pos8); + + for (int i = 0; i < arrLen; i++) { + pos8 += ModelParticle.computeBytesConsumed(buf, pos8); + } + + if (pos8 - offset > maxEnd) { + maxEnd = pos8 - offset; + } + } + + if ((nullBits[1] & 4) != 0) { + int fieldOffset9 = buf.getIntLE(offset + 79); + int pos9 = offset + 91 + fieldOffset9; + int arrLen = VarInt.peek(buf, pos9); + pos9 += VarInt.length(buf, pos9); + + for (int i = 0; i < arrLen; i++) { + pos9 += ModelTrail.computeBytesConsumed(buf, pos9); + } + + if (pos9 - offset > maxEnd) { + maxEnd = pos9 - offset; + } + } + + if ((nullBits[1] & 16) != 0) { + int fieldOffset10 = buf.getIntLE(offset + 83); + int pos10 = offset + 91 + fieldOffset10; + int dictLen = VarInt.peek(buf, pos10); + pos10 += VarInt.length(buf, pos10); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos10); + pos10 += VarInt.length(buf, pos10) + sl; + sl = VarInt.peek(buf, pos10); + pos10 += VarInt.length(buf, pos10); + + for (int j = 0; j < sl; j++) { + pos10 += DetailBox.computeBytesConsumed(buf, pos10); + } + } + + if (pos10 - offset > maxEnd) { + maxEnd = pos10 - offset; + } + } + + if ((nullBits[1] & 32) != 0) { + int fieldOffset11 = buf.getIntLE(offset + 87); + int pos11 = offset + 91 + fieldOffset11; + pos11 += computeBytesConsumed(buf, pos11); + if (pos11 - offset > maxEnd) { + maxEnd = pos11 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte[] nullBits = new byte[2]; + if (this.assetId != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.path != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.texture != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.gradientSet != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.gradientId != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.camera != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.animationSets != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.attachments != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.hitbox != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + if (this.particles != null) { + nullBits[1] = (byte)(nullBits[1] | 2); + } + + if (this.trails != null) { + nullBits[1] = (byte)(nullBits[1] | 4); + } + + if (this.light != null) { + nullBits[1] = (byte)(nullBits[1] | 8); + } + + if (this.detailBoxes != null) { + nullBits[1] = (byte)(nullBits[1] | 16); + } + + if (this.phobiaModel != null) { + nullBits[1] = (byte)(nullBits[1] | 32); + } + + buf.writeBytes(nullBits); + buf.writeFloatLE(this.scale); + buf.writeFloatLE(this.eyeHeight); + buf.writeFloatLE(this.crouchOffset); + if (this.hitbox != null) { + this.hitbox.serialize(buf); + } else { + buf.writeZero(24); + } + + if (this.light != null) { + this.light.serialize(buf); + } else { + buf.writeZero(4); + } + + buf.writeByte(this.phobia.getValue()); + int assetIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int pathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int textureOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int gradientSetOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int gradientIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int animationSetsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int attachmentsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int particlesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int trailsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int detailBoxesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int phobiaModelOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.assetId != null) { + buf.setIntLE(assetIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.assetId, 4096000); + } else { + buf.setIntLE(assetIdOffsetSlot, -1); + } + + if (this.path != null) { + buf.setIntLE(pathOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.path, 4096000); + } else { + buf.setIntLE(pathOffsetSlot, -1); + } + + if (this.texture != null) { + buf.setIntLE(textureOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.texture, 4096000); + } else { + buf.setIntLE(textureOffsetSlot, -1); + } + + if (this.gradientSet != null) { + buf.setIntLE(gradientSetOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.gradientSet, 4096000); + } else { + buf.setIntLE(gradientSetOffsetSlot, -1); + } + + if (this.gradientId != null) { + buf.setIntLE(gradientIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.gradientId, 4096000); + } else { + buf.setIntLE(gradientIdOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.animationSets != null) { + buf.setIntLE(animationSetsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.animationSets.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("AnimationSets", this.animationSets.size(), 4096000); + } + + VarInt.write(buf, this.animationSets.size()); + + for (Entry e : this.animationSets.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(animationSetsOffsetSlot, -1); + } + + if (this.attachments != null) { + buf.setIntLE(attachmentsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.attachments.length > 4096000) { + throw ProtocolException.arrayTooLong("Attachments", this.attachments.length, 4096000); + } + + VarInt.write(buf, this.attachments.length); + + for (ModelAttachment item : this.attachments) { + item.serialize(buf); + } + } else { + buf.setIntLE(attachmentsOffsetSlot, -1); + } + + if (this.particles != null) { + buf.setIntLE(particlesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.particles.length > 4096000) { + throw ProtocolException.arrayTooLong("Particles", this.particles.length, 4096000); + } + + VarInt.write(buf, this.particles.length); + + for (ModelParticle item : this.particles) { + item.serialize(buf); + } + } else { + buf.setIntLE(particlesOffsetSlot, -1); + } + + if (this.trails != null) { + buf.setIntLE(trailsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.trails.length > 4096000) { + throw ProtocolException.arrayTooLong("Trails", this.trails.length, 4096000); + } + + VarInt.write(buf, this.trails.length); + + for (ModelTrail item : this.trails) { + item.serialize(buf); + } + } else { + buf.setIntLE(trailsOffsetSlot, -1); + } + + if (this.detailBoxes != null) { + buf.setIntLE(detailBoxesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.detailBoxes.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("DetailBoxes", this.detailBoxes.size(), 4096000); + } + + VarInt.write(buf, this.detailBoxes.size()); + + for (Entry e : this.detailBoxes.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + VarInt.write(buf, e.getValue().length); + + for (DetailBox arrItem : e.getValue()) { + arrItem.serialize(buf); + } + } + } else { + buf.setIntLE(detailBoxesOffsetSlot, -1); + } + + if (this.phobiaModel != null) { + buf.setIntLE(phobiaModelOffsetSlot, buf.writerIndex() - varBlockStart); + this.phobiaModel.serialize(buf); + } else { + buf.setIntLE(phobiaModelOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 91; + if (this.assetId != null) { + size += PacketIO.stringSize(this.assetId); + } + + if (this.path != null) { + size += PacketIO.stringSize(this.path); + } + + if (this.texture != null) { + size += PacketIO.stringSize(this.texture); + } + + if (this.gradientSet != null) { + size += PacketIO.stringSize(this.gradientSet); + } + + if (this.gradientId != null) { + size += PacketIO.stringSize(this.gradientId); + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.animationSets != null) { + int animationSetsSize = 0; + + for (Entry kvp : this.animationSets.entrySet()) { + animationSetsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.animationSets.size()) + animationSetsSize; + } + + if (this.attachments != null) { + int attachmentsSize = 0; + + for (ModelAttachment elem : this.attachments) { + attachmentsSize += elem.computeSize(); + } + + size += VarInt.size(this.attachments.length) + attachmentsSize; + } + + if (this.particles != null) { + int particlesSize = 0; + + for (ModelParticle elem : this.particles) { + particlesSize += elem.computeSize(); + } + + size += VarInt.size(this.particles.length) + particlesSize; + } + + if (this.trails != null) { + int trailsSize = 0; + + for (ModelTrail elem : this.trails) { + trailsSize += elem.computeSize(); + } + + size += VarInt.size(this.trails.length) + trailsSize; + } + + if (this.detailBoxes != null) { + int detailBoxesSize = 0; + + for (Entry kvp : this.detailBoxes.entrySet()) { + detailBoxesSize += PacketIO.stringSize(kvp.getKey()) + VarInt.size(kvp.getValue().length) + ((DetailBox[])kvp.getValue()).length * 37; + } + + size += VarInt.size(this.detailBoxes.size()) + detailBoxesSize; + } + + if (this.phobiaModel != null) { + size += this.phobiaModel.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 91) { + return ValidationResult.error("Buffer too small: expected at least 91 bytes"); + } else { + byte[] nullBits = PacketIO.readBytes(buffer, offset, 2); + if ((nullBits[0] & 1) != 0) { + int assetIdOffset = buffer.getIntLE(offset + 43); + if (assetIdOffset < 0) { + return ValidationResult.error("Invalid offset for AssetId"); + } + + int pos = offset + 91 + assetIdOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for AssetId"); + } + + int assetIdLen = VarInt.peek(buffer, pos); + if (assetIdLen < 0) { + return ValidationResult.error("Invalid string length for AssetId"); + } + + if (assetIdLen > 4096000) { + return ValidationResult.error("AssetId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += assetIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading AssetId"); + } + } + + if ((nullBits[0] & 2) != 0) { + int pathOffset = buffer.getIntLE(offset + 47); + if (pathOffset < 0) { + return ValidationResult.error("Invalid offset for Path"); + } + + int posx = offset + 91 + pathOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Path"); + } + + int pathLen = VarInt.peek(buffer, posx); + if (pathLen < 0) { + return ValidationResult.error("Invalid string length for Path"); + } + + if (pathLen > 4096000) { + return ValidationResult.error("Path exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += pathLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Path"); + } + } + + if ((nullBits[0] & 4) != 0) { + int textureOffset = buffer.getIntLE(offset + 51); + if (textureOffset < 0) { + return ValidationResult.error("Invalid offset for Texture"); + } + + int posxx = offset + 91 + textureOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Texture"); + } + + int textureLen = VarInt.peek(buffer, posxx); + if (textureLen < 0) { + return ValidationResult.error("Invalid string length for Texture"); + } + + if (textureLen > 4096000) { + return ValidationResult.error("Texture exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += textureLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Texture"); + } + } + + if ((nullBits[0] & 8) != 0) { + int gradientSetOffset = buffer.getIntLE(offset + 55); + if (gradientSetOffset < 0) { + return ValidationResult.error("Invalid offset for GradientSet"); + } + + int posxxx = offset + 91 + gradientSetOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for GradientSet"); + } + + int gradientSetLen = VarInt.peek(buffer, posxxx); + if (gradientSetLen < 0) { + return ValidationResult.error("Invalid string length for GradientSet"); + } + + if (gradientSetLen > 4096000) { + return ValidationResult.error("GradientSet exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += gradientSetLen; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading GradientSet"); + } + } + + if ((nullBits[0] & 16) != 0) { + int gradientIdOffset = buffer.getIntLE(offset + 59); + if (gradientIdOffset < 0) { + return ValidationResult.error("Invalid offset for GradientId"); + } + + int posxxxx = offset + 91 + gradientIdOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for GradientId"); + } + + int gradientIdLen = VarInt.peek(buffer, posxxxx); + if (gradientIdLen < 0) { + return ValidationResult.error("Invalid string length for GradientId"); + } + + if (gradientIdLen > 4096000) { + return ValidationResult.error("GradientId exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + posxxxx += gradientIdLen; + if (posxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading GradientId"); + } + } + + if ((nullBits[0] & 32) != 0) { + int cameraOffset = buffer.getIntLE(offset + 63); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxxx = offset + 91 + cameraOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = CameraSettings.validateStructure(buffer, posxxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxxx += CameraSettings.computeBytesConsumed(buffer, posxxxxx); + } + + if ((nullBits[0] & 64) != 0) { + int animationSetsOffset = buffer.getIntLE(offset + 67); + if (animationSetsOffset < 0) { + return ValidationResult.error("Invalid offset for AnimationSets"); + } + + int posxxxxxx = offset + 91 + animationSetsOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for AnimationSets"); + } + + int animationSetsCount = VarInt.peek(buffer, posxxxxxx); + if (animationSetsCount < 0) { + return ValidationResult.error("Invalid dictionary count for AnimationSets"); + } + + if (animationSetsCount > 4096000) { + return ValidationResult.error("AnimationSets exceeds max length 4096000"); + } + + posxxxxxx += VarInt.length(buffer, posxxxxxx); + + for (int i = 0; i < animationSetsCount; i++) { + int keyLen = VarInt.peek(buffer, posxxxxxx); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + posxxxxxx += VarInt.length(buffer, posxxxxxx); + posxxxxxx += keyLen; + if (posxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxx += AnimationSet.computeBytesConsumed(buffer, posxxxxxx); + } + } + + if ((nullBits[0] & 128) != 0) { + int attachmentsOffset = buffer.getIntLE(offset + 71); + if (attachmentsOffset < 0) { + return ValidationResult.error("Invalid offset for Attachments"); + } + + int posxxxxxxx = offset + 91 + attachmentsOffset; + if (posxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Attachments"); + } + + int attachmentsCount = VarInt.peek(buffer, posxxxxxxx); + if (attachmentsCount < 0) { + return ValidationResult.error("Invalid array count for Attachments"); + } + + if (attachmentsCount > 4096000) { + return ValidationResult.error("Attachments exceeds max length 4096000"); + } + + posxxxxxxx += VarInt.length(buffer, posxxxxxxx); + + for (int i = 0; i < attachmentsCount; i++) { + ValidationResult structResult = ModelAttachment.validateStructure(buffer, posxxxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelAttachment in Attachments[" + i + "]: " + structResult.error()); + } + + posxxxxxxx += ModelAttachment.computeBytesConsumed(buffer, posxxxxxxx); + } + } + + if ((nullBits[1] & 2) != 0) { + int particlesOffset = buffer.getIntLE(offset + 75); + if (particlesOffset < 0) { + return ValidationResult.error("Invalid offset for Particles"); + } + + int posxxxxxxxx = offset + 91 + particlesOffset; + if (posxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Particles"); + } + + int particlesCount = VarInt.peek(buffer, posxxxxxxxx); + if (particlesCount < 0) { + return ValidationResult.error("Invalid array count for Particles"); + } + + if (particlesCount > 4096000) { + return ValidationResult.error("Particles exceeds max length 4096000"); + } + + posxxxxxxxx += VarInt.length(buffer, posxxxxxxxx); + + for (int i = 0; i < particlesCount; i++) { + ValidationResult structResult = ModelParticle.validateStructure(buffer, posxxxxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelParticle in Particles[" + i + "]: " + structResult.error()); + } + + posxxxxxxxx += ModelParticle.computeBytesConsumed(buffer, posxxxxxxxx); + } + } + + if ((nullBits[1] & 4) != 0) { + int trailsOffset = buffer.getIntLE(offset + 79); + if (trailsOffset < 0) { + return ValidationResult.error("Invalid offset for Trails"); + } + + int posxxxxxxxxx = offset + 91 + trailsOffset; + if (posxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Trails"); + } + + int trailsCount = VarInt.peek(buffer, posxxxxxxxxx); + if (trailsCount < 0) { + return ValidationResult.error("Invalid array count for Trails"); + } + + if (trailsCount > 4096000) { + return ValidationResult.error("Trails exceeds max length 4096000"); + } + + posxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxx); + + for (int i = 0; i < trailsCount; i++) { + ValidationResult structResult = ModelTrail.validateStructure(buffer, posxxxxxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelTrail in Trails[" + i + "]: " + structResult.error()); + } + + posxxxxxxxxx += ModelTrail.computeBytesConsumed(buffer, posxxxxxxxxx); + } + } + + if ((nullBits[1] & 16) != 0) { + int detailBoxesOffset = buffer.getIntLE(offset + 83); + if (detailBoxesOffset < 0) { + return ValidationResult.error("Invalid offset for DetailBoxes"); + } + + int posxxxxxxxxxx = offset + 91 + detailBoxesOffset; + if (posxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for DetailBoxes"); + } + + int detailBoxesCount = VarInt.peek(buffer, posxxxxxxxxxx); + if (detailBoxesCount < 0) { + return ValidationResult.error("Invalid dictionary count for DetailBoxes"); + } + + if (detailBoxesCount > 4096000) { + return ValidationResult.error("DetailBoxes exceeds max length 4096000"); + } + + posxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxx); + + for (int i = 0; i < detailBoxesCount; i++) { + int keyLenx = VarInt.peek(buffer, posxxxxxxxxxx); + if (keyLenx < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLenx > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + posxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxx); + posxxxxxxxxxx += keyLenx; + if (posxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + int valueArrCount = VarInt.peek(buffer, posxxxxxxxxxx); + if (valueArrCount < 0) { + return ValidationResult.error("Invalid array count for value"); + } + + posxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxx); + + for (int valueArrIdx = 0; valueArrIdx < valueArrCount; valueArrIdx++) { + posxxxxxxxxxx += 37; + } + } + } + + if ((nullBits[1] & 32) != 0) { + int phobiaModelOffset = buffer.getIntLE(offset + 87); + if (phobiaModelOffset < 0) { + return ValidationResult.error("Invalid offset for PhobiaModel"); + } + + int posxxxxxxxxxxx = offset + 91 + phobiaModelOffset; + if (posxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for PhobiaModel"); + } + + ValidationResult phobiaModelResult = validateStructure(buffer, posxxxxxxxxxxx); + if (!phobiaModelResult.isValid()) { + return ValidationResult.error("Invalid PhobiaModel: " + phobiaModelResult.error()); + } + + posxxxxxxxxxxx += computeBytesConsumed(buffer, posxxxxxxxxxxx); + } + + return ValidationResult.OK; + } + } + + public Model clone() { + Model copy = new Model(); + copy.assetId = this.assetId; + copy.path = this.path; + copy.texture = this.texture; + copy.gradientSet = this.gradientSet; + copy.gradientId = this.gradientId; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.scale = this.scale; + copy.eyeHeight = this.eyeHeight; + copy.crouchOffset = this.crouchOffset; + if (this.animationSets != null) { + Map m = new HashMap<>(); + + for (Entry e : this.animationSets.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.animationSets = m; + } + + copy.attachments = this.attachments != null ? Arrays.stream(this.attachments).map(ex -> ex.clone()).toArray(ModelAttachment[]::new) : null; + copy.hitbox = this.hitbox != null ? this.hitbox.clone() : null; + copy.particles = this.particles != null ? Arrays.stream(this.particles).map(ex -> ex.clone()).toArray(ModelParticle[]::new) : null; + copy.trails = this.trails != null ? Arrays.stream(this.trails).map(ex -> ex.clone()).toArray(ModelTrail[]::new) : null; + copy.light = this.light != null ? this.light.clone() : null; + if (this.detailBoxes != null) { + Map m = new HashMap<>(); + + for (Entry e : this.detailBoxes.entrySet()) { + m.put(e.getKey(), Arrays.stream(e.getValue()).map(x -> x.clone()).toArray(DetailBox[]::new)); + } + + copy.detailBoxes = m; + } + + copy.phobia = this.phobia; + copy.phobiaModel = this.phobiaModel != null ? this.phobiaModel.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Model other) + ? false + : Objects.equals(this.assetId, other.assetId) + && Objects.equals(this.path, other.path) + && Objects.equals(this.texture, other.texture) + && Objects.equals(this.gradientSet, other.gradientSet) + && Objects.equals(this.gradientId, other.gradientId) + && Objects.equals(this.camera, other.camera) + && this.scale == other.scale + && this.eyeHeight == other.eyeHeight + && this.crouchOffset == other.crouchOffset + && Objects.equals(this.animationSets, other.animationSets) + && Arrays.equals((Object[])this.attachments, (Object[])other.attachments) + && Objects.equals(this.hitbox, other.hitbox) + && Arrays.equals((Object[])this.particles, (Object[])other.particles) + && Arrays.equals((Object[])this.trails, (Object[])other.trails) + && Objects.equals(this.light, other.light) + && Objects.equals(this.detailBoxes, other.detailBoxes) + && Objects.equals(this.phobia, other.phobia) + && Objects.equals(this.phobiaModel, other.phobiaModel); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.assetId); + result = 31 * result + Objects.hashCode(this.path); + result = 31 * result + Objects.hashCode(this.texture); + result = 31 * result + Objects.hashCode(this.gradientSet); + result = 31 * result + Objects.hashCode(this.gradientId); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Float.hashCode(this.scale); + result = 31 * result + Float.hashCode(this.eyeHeight); + result = 31 * result + Float.hashCode(this.crouchOffset); + result = 31 * result + Objects.hashCode(this.animationSets); + result = 31 * result + Arrays.hashCode((Object[])this.attachments); + result = 31 * result + Objects.hashCode(this.hitbox); + result = 31 * result + Arrays.hashCode((Object[])this.particles); + result = 31 * result + Arrays.hashCode((Object[])this.trails); + result = 31 * result + Objects.hashCode(this.light); + result = 31 * result + Objects.hashCode(this.detailBoxes); + result = 31 * result + Objects.hashCode(this.phobia); + return 31 * result + Objects.hashCode(this.phobiaModel); + } +} diff --git a/src/com/hypixel/hytale/protocol/ModelAttachment.java b/src/com/hypixel/hytale/protocol/ModelAttachment.java new file mode 100644 index 0000000..cf2b6ce --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ModelAttachment.java @@ -0,0 +1,376 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ModelAttachment { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 4; + public static final int VARIABLE_BLOCK_START = 17; + public static final int MAX_SIZE = 65536037; + @Nullable + public String model; + @Nullable + public String texture; + @Nullable + public String gradientSet; + @Nullable + public String gradientId; + + public ModelAttachment() { + } + + public ModelAttachment(@Nullable String model, @Nullable String texture, @Nullable String gradientSet, @Nullable String gradientId) { + this.model = model; + this.texture = texture; + this.gradientSet = gradientSet; + this.gradientId = gradientId; + } + + public ModelAttachment(@Nonnull ModelAttachment other) { + this.model = other.model; + this.texture = other.texture; + this.gradientSet = other.gradientSet; + this.gradientId = other.gradientId; + } + + @Nonnull + public static ModelAttachment deserialize(@Nonnull ByteBuf buf, int offset) { + ModelAttachment obj = new ModelAttachment(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 17 + buf.getIntLE(offset + 1); + int modelLen = VarInt.peek(buf, varPos0); + if (modelLen < 0) { + throw ProtocolException.negativeLength("Model", modelLen); + } + + if (modelLen > 4096000) { + throw ProtocolException.stringTooLong("Model", modelLen, 4096000); + } + + obj.model = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 17 + buf.getIntLE(offset + 5); + int textureLen = VarInt.peek(buf, varPos1); + if (textureLen < 0) { + throw ProtocolException.negativeLength("Texture", textureLen); + } + + if (textureLen > 4096000) { + throw ProtocolException.stringTooLong("Texture", textureLen, 4096000); + } + + obj.texture = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 17 + buf.getIntLE(offset + 9); + int gradientSetLen = VarInt.peek(buf, varPos2); + if (gradientSetLen < 0) { + throw ProtocolException.negativeLength("GradientSet", gradientSetLen); + } + + if (gradientSetLen > 4096000) { + throw ProtocolException.stringTooLong("GradientSet", gradientSetLen, 4096000); + } + + obj.gradientSet = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 17 + buf.getIntLE(offset + 13); + int gradientIdLen = VarInt.peek(buf, varPos3); + if (gradientIdLen < 0) { + throw ProtocolException.negativeLength("GradientId", gradientIdLen); + } + + if (gradientIdLen > 4096000) { + throw ProtocolException.stringTooLong("GradientId", gradientIdLen, 4096000); + } + + obj.gradientId = PacketIO.readVarString(buf, varPos3, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 17; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 17 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 17 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 9); + int pos2 = offset + 17 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 13); + int pos3 = offset + 17 + fieldOffset3; + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.model != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.texture != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.gradientSet != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.gradientId != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + int modelOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int textureOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int gradientSetOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int gradientIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.model != null) { + buf.setIntLE(modelOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.model, 4096000); + } else { + buf.setIntLE(modelOffsetSlot, -1); + } + + if (this.texture != null) { + buf.setIntLE(textureOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.texture, 4096000); + } else { + buf.setIntLE(textureOffsetSlot, -1); + } + + if (this.gradientSet != null) { + buf.setIntLE(gradientSetOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.gradientSet, 4096000); + } else { + buf.setIntLE(gradientSetOffsetSlot, -1); + } + + if (this.gradientId != null) { + buf.setIntLE(gradientIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.gradientId, 4096000); + } else { + buf.setIntLE(gradientIdOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 17; + if (this.model != null) { + size += PacketIO.stringSize(this.model); + } + + if (this.texture != null) { + size += PacketIO.stringSize(this.texture); + } + + if (this.gradientSet != null) { + size += PacketIO.stringSize(this.gradientSet); + } + + if (this.gradientId != null) { + size += PacketIO.stringSize(this.gradientId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 17) { + return ValidationResult.error("Buffer too small: expected at least 17 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int modelOffset = buffer.getIntLE(offset + 1); + if (modelOffset < 0) { + return ValidationResult.error("Invalid offset for Model"); + } + + int pos = offset + 17 + modelOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Model"); + } + + int modelLen = VarInt.peek(buffer, pos); + if (modelLen < 0) { + return ValidationResult.error("Invalid string length for Model"); + } + + if (modelLen > 4096000) { + return ValidationResult.error("Model exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += modelLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Model"); + } + } + + if ((nullBits & 2) != 0) { + int textureOffset = buffer.getIntLE(offset + 5); + if (textureOffset < 0) { + return ValidationResult.error("Invalid offset for Texture"); + } + + int posx = offset + 17 + textureOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Texture"); + } + + int textureLen = VarInt.peek(buffer, posx); + if (textureLen < 0) { + return ValidationResult.error("Invalid string length for Texture"); + } + + if (textureLen > 4096000) { + return ValidationResult.error("Texture exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += textureLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Texture"); + } + } + + if ((nullBits & 4) != 0) { + int gradientSetOffset = buffer.getIntLE(offset + 9); + if (gradientSetOffset < 0) { + return ValidationResult.error("Invalid offset for GradientSet"); + } + + int posxx = offset + 17 + gradientSetOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for GradientSet"); + } + + int gradientSetLen = VarInt.peek(buffer, posxx); + if (gradientSetLen < 0) { + return ValidationResult.error("Invalid string length for GradientSet"); + } + + if (gradientSetLen > 4096000) { + return ValidationResult.error("GradientSet exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += gradientSetLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading GradientSet"); + } + } + + if ((nullBits & 8) != 0) { + int gradientIdOffset = buffer.getIntLE(offset + 13); + if (gradientIdOffset < 0) { + return ValidationResult.error("Invalid offset for GradientId"); + } + + int posxxx = offset + 17 + gradientIdOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for GradientId"); + } + + int gradientIdLen = VarInt.peek(buffer, posxxx); + if (gradientIdLen < 0) { + return ValidationResult.error("Invalid string length for GradientId"); + } + + if (gradientIdLen > 4096000) { + return ValidationResult.error("GradientId exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += gradientIdLen; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading GradientId"); + } + } + + return ValidationResult.OK; + } + } + + public ModelAttachment clone() { + ModelAttachment copy = new ModelAttachment(); + copy.model = this.model; + copy.texture = this.texture; + copy.gradientSet = this.gradientSet; + copy.gradientId = this.gradientId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ModelAttachment other) + ? false + : Objects.equals(this.model, other.model) + && Objects.equals(this.texture, other.texture) + && Objects.equals(this.gradientSet, other.gradientSet) + && Objects.equals(this.gradientId, other.gradientId); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.model, this.texture, this.gradientSet, this.gradientId); + } +} diff --git a/src/com/hypixel/hytale/protocol/ModelDisplay.java b/src/com/hypixel/hytale/protocol/ModelDisplay.java new file mode 100644 index 0000000..1d0de9c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ModelDisplay.java @@ -0,0 +1,288 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ModelDisplay { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 37; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 45; + public static final int MAX_SIZE = 32768055; + @Nullable + public String node; + @Nullable + public String attachTo; + @Nullable + public Vector3f translation; + @Nullable + public Vector3f rotation; + @Nullable + public Vector3f scale; + + public ModelDisplay() { + } + + public ModelDisplay(@Nullable String node, @Nullable String attachTo, @Nullable Vector3f translation, @Nullable Vector3f rotation, @Nullable Vector3f scale) { + this.node = node; + this.attachTo = attachTo; + this.translation = translation; + this.rotation = rotation; + this.scale = scale; + } + + public ModelDisplay(@Nonnull ModelDisplay other) { + this.node = other.node; + this.attachTo = other.attachTo; + this.translation = other.translation; + this.rotation = other.rotation; + this.scale = other.scale; + } + + @Nonnull + public static ModelDisplay deserialize(@Nonnull ByteBuf buf, int offset) { + ModelDisplay obj = new ModelDisplay(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 4) != 0) { + obj.translation = Vector3f.deserialize(buf, offset + 1); + } + + if ((nullBits & 8) != 0) { + obj.rotation = Vector3f.deserialize(buf, offset + 13); + } + + if ((nullBits & 16) != 0) { + obj.scale = Vector3f.deserialize(buf, offset + 25); + } + + if ((nullBits & 1) != 0) { + int varPos0 = offset + 45 + buf.getIntLE(offset + 37); + int nodeLen = VarInt.peek(buf, varPos0); + if (nodeLen < 0) { + throw ProtocolException.negativeLength("Node", nodeLen); + } + + if (nodeLen > 4096000) { + throw ProtocolException.stringTooLong("Node", nodeLen, 4096000); + } + + obj.node = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 45 + buf.getIntLE(offset + 41); + int attachToLen = VarInt.peek(buf, varPos1); + if (attachToLen < 0) { + throw ProtocolException.negativeLength("AttachTo", attachToLen); + } + + if (attachToLen > 4096000) { + throw ProtocolException.stringTooLong("AttachTo", attachToLen, 4096000); + } + + obj.attachTo = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 45; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 37); + int pos0 = offset + 45 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 41); + int pos1 = offset + 45 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.node != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.attachTo != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.translation != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.rotation != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.scale != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + if (this.translation != null) { + this.translation.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.rotation != null) { + this.rotation.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.scale != null) { + this.scale.serialize(buf); + } else { + buf.writeZero(12); + } + + int nodeOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int attachToOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.node != null) { + buf.setIntLE(nodeOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.node, 4096000); + } else { + buf.setIntLE(nodeOffsetSlot, -1); + } + + if (this.attachTo != null) { + buf.setIntLE(attachToOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.attachTo, 4096000); + } else { + buf.setIntLE(attachToOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 45; + if (this.node != null) { + size += PacketIO.stringSize(this.node); + } + + if (this.attachTo != null) { + size += PacketIO.stringSize(this.attachTo); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 45) { + return ValidationResult.error("Buffer too small: expected at least 45 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int nodeOffset = buffer.getIntLE(offset + 37); + if (nodeOffset < 0) { + return ValidationResult.error("Invalid offset for Node"); + } + + int pos = offset + 45 + nodeOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Node"); + } + + int nodeLen = VarInt.peek(buffer, pos); + if (nodeLen < 0) { + return ValidationResult.error("Invalid string length for Node"); + } + + if (nodeLen > 4096000) { + return ValidationResult.error("Node exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += nodeLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Node"); + } + } + + if ((nullBits & 2) != 0) { + int attachToOffset = buffer.getIntLE(offset + 41); + if (attachToOffset < 0) { + return ValidationResult.error("Invalid offset for AttachTo"); + } + + int posx = offset + 45 + attachToOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for AttachTo"); + } + + int attachToLen = VarInt.peek(buffer, posx); + if (attachToLen < 0) { + return ValidationResult.error("Invalid string length for AttachTo"); + } + + if (attachToLen > 4096000) { + return ValidationResult.error("AttachTo exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += attachToLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading AttachTo"); + } + } + + return ValidationResult.OK; + } + } + + public ModelDisplay clone() { + ModelDisplay copy = new ModelDisplay(); + copy.node = this.node; + copy.attachTo = this.attachTo; + copy.translation = this.translation != null ? this.translation.clone() : null; + copy.rotation = this.rotation != null ? this.rotation.clone() : null; + copy.scale = this.scale != null ? this.scale.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ModelDisplay other) + ? false + : Objects.equals(this.node, other.node) + && Objects.equals(this.attachTo, other.attachTo) + && Objects.equals(this.translation, other.translation) + && Objects.equals(this.rotation, other.rotation) + && Objects.equals(this.scale, other.scale); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.node, this.attachTo, this.translation, this.rotation, this.scale); + } +} diff --git a/src/com/hypixel/hytale/protocol/ModelOverride.java b/src/com/hypixel/hytale/protocol/ModelOverride.java new file mode 100644 index 0000000..6358807 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ModelOverride.java @@ -0,0 +1,371 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ModelOverride { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String model; + @Nullable + public String texture; + @Nullable + public Map animationSets; + + public ModelOverride() { + } + + public ModelOverride(@Nullable String model, @Nullable String texture, @Nullable Map animationSets) { + this.model = model; + this.texture = texture; + this.animationSets = animationSets; + } + + public ModelOverride(@Nonnull ModelOverride other) { + this.model = other.model; + this.texture = other.texture; + this.animationSets = other.animationSets; + } + + @Nonnull + public static ModelOverride deserialize(@Nonnull ByteBuf buf, int offset) { + ModelOverride obj = new ModelOverride(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 1); + int modelLen = VarInt.peek(buf, varPos0); + if (modelLen < 0) { + throw ProtocolException.negativeLength("Model", modelLen); + } + + if (modelLen > 4096000) { + throw ProtocolException.stringTooLong("Model", modelLen, 4096000); + } + + obj.model = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 5); + int textureLen = VarInt.peek(buf, varPos1); + if (textureLen < 0) { + throw ProtocolException.negativeLength("Texture", textureLen); + } + + if (textureLen > 4096000) { + throw ProtocolException.stringTooLong("Texture", textureLen, 4096000); + } + + obj.texture = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 13 + buf.getIntLE(offset + 9); + int animationSetsCount = VarInt.peek(buf, varPos2); + if (animationSetsCount < 0) { + throw ProtocolException.negativeLength("AnimationSets", animationSetsCount); + } + + if (animationSetsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("AnimationSets", animationSetsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + obj.animationSets = new HashMap<>(animationSetsCount); + int dictPos = varPos2 + varIntLen; + + for (int i = 0; i < animationSetsCount; i++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + AnimationSet val = AnimationSet.deserialize(buf, dictPos); + dictPos += AnimationSet.computeBytesConsumed(buf, dictPos); + if (obj.animationSets.put(key, val) != null) { + throw ProtocolException.duplicateKey("animationSets", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 13 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 13 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 9); + int pos2 = offset + 13 + fieldOffset2; + int dictLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + pos2 += AnimationSet.computeBytesConsumed(buf, pos2); + } + + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.model != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.texture != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.animationSets != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + int modelOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int textureOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int animationSetsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.model != null) { + buf.setIntLE(modelOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.model, 4096000); + } else { + buf.setIntLE(modelOffsetSlot, -1); + } + + if (this.texture != null) { + buf.setIntLE(textureOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.texture, 4096000); + } else { + buf.setIntLE(textureOffsetSlot, -1); + } + + if (this.animationSets != null) { + buf.setIntLE(animationSetsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.animationSets.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("AnimationSets", this.animationSets.size(), 4096000); + } + + VarInt.write(buf, this.animationSets.size()); + + for (Entry e : this.animationSets.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(animationSetsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 13; + if (this.model != null) { + size += PacketIO.stringSize(this.model); + } + + if (this.texture != null) { + size += PacketIO.stringSize(this.texture); + } + + if (this.animationSets != null) { + int animationSetsSize = 0; + + for (Entry kvp : this.animationSets.entrySet()) { + animationSetsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.animationSets.size()) + animationSetsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int modelOffset = buffer.getIntLE(offset + 1); + if (modelOffset < 0) { + return ValidationResult.error("Invalid offset for Model"); + } + + int pos = offset + 13 + modelOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Model"); + } + + int modelLen = VarInt.peek(buffer, pos); + if (modelLen < 0) { + return ValidationResult.error("Invalid string length for Model"); + } + + if (modelLen > 4096000) { + return ValidationResult.error("Model exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += modelLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Model"); + } + } + + if ((nullBits & 2) != 0) { + int textureOffset = buffer.getIntLE(offset + 5); + if (textureOffset < 0) { + return ValidationResult.error("Invalid offset for Texture"); + } + + int posx = offset + 13 + textureOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Texture"); + } + + int textureLen = VarInt.peek(buffer, posx); + if (textureLen < 0) { + return ValidationResult.error("Invalid string length for Texture"); + } + + if (textureLen > 4096000) { + return ValidationResult.error("Texture exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += textureLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Texture"); + } + } + + if ((nullBits & 4) != 0) { + int animationSetsOffset = buffer.getIntLE(offset + 9); + if (animationSetsOffset < 0) { + return ValidationResult.error("Invalid offset for AnimationSets"); + } + + int posxx = offset + 13 + animationSetsOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for AnimationSets"); + } + + int animationSetsCount = VarInt.peek(buffer, posxx); + if (animationSetsCount < 0) { + return ValidationResult.error("Invalid dictionary count for AnimationSets"); + } + + if (animationSetsCount > 4096000) { + return ValidationResult.error("AnimationSets exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + + for (int i = 0; i < animationSetsCount; i++) { + int keyLen = VarInt.peek(buffer, posxx); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += keyLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxx += AnimationSet.computeBytesConsumed(buffer, posxx); + } + } + + return ValidationResult.OK; + } + } + + public ModelOverride clone() { + ModelOverride copy = new ModelOverride(); + copy.model = this.model; + copy.texture = this.texture; + if (this.animationSets != null) { + Map m = new HashMap<>(); + + for (Entry e : this.animationSets.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.animationSets = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ModelOverride other) + ? false + : Objects.equals(this.model, other.model) && Objects.equals(this.texture, other.texture) && Objects.equals(this.animationSets, other.animationSets); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.model, this.texture, this.animationSets); + } +} diff --git a/src/com/hypixel/hytale/protocol/ModelParticle.java b/src/com/hypixel/hytale/protocol/ModelParticle.java new file mode 100644 index 0000000..f25b815 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ModelParticle.java @@ -0,0 +1,321 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ModelParticle { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 34; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 42; + public static final int MAX_SIZE = 32768052; + @Nullable + public String systemId; + public float scale; + @Nullable + public Color color; + @Nonnull + public EntityPart targetEntityPart = EntityPart.Self; + @Nullable + public String targetNodeName; + @Nullable + public Vector3f positionOffset; + @Nullable + public Direction rotationOffset; + public boolean detachedFromModel; + + public ModelParticle() { + } + + public ModelParticle( + @Nullable String systemId, + float scale, + @Nullable Color color, + @Nonnull EntityPart targetEntityPart, + @Nullable String targetNodeName, + @Nullable Vector3f positionOffset, + @Nullable Direction rotationOffset, + boolean detachedFromModel + ) { + this.systemId = systemId; + this.scale = scale; + this.color = color; + this.targetEntityPart = targetEntityPart; + this.targetNodeName = targetNodeName; + this.positionOffset = positionOffset; + this.rotationOffset = rotationOffset; + this.detachedFromModel = detachedFromModel; + } + + public ModelParticle(@Nonnull ModelParticle other) { + this.systemId = other.systemId; + this.scale = other.scale; + this.color = other.color; + this.targetEntityPart = other.targetEntityPart; + this.targetNodeName = other.targetNodeName; + this.positionOffset = other.positionOffset; + this.rotationOffset = other.rotationOffset; + this.detachedFromModel = other.detachedFromModel; + } + + @Nonnull + public static ModelParticle deserialize(@Nonnull ByteBuf buf, int offset) { + ModelParticle obj = new ModelParticle(); + byte nullBits = buf.getByte(offset); + obj.scale = buf.getFloatLE(offset + 1); + if ((nullBits & 2) != 0) { + obj.color = Color.deserialize(buf, offset + 5); + } + + obj.targetEntityPart = EntityPart.fromValue(buf.getByte(offset + 8)); + if ((nullBits & 8) != 0) { + obj.positionOffset = Vector3f.deserialize(buf, offset + 9); + } + + if ((nullBits & 16) != 0) { + obj.rotationOffset = Direction.deserialize(buf, offset + 21); + } + + obj.detachedFromModel = buf.getByte(offset + 33) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 42 + buf.getIntLE(offset + 34); + int systemIdLen = VarInt.peek(buf, varPos0); + if (systemIdLen < 0) { + throw ProtocolException.negativeLength("SystemId", systemIdLen); + } + + if (systemIdLen > 4096000) { + throw ProtocolException.stringTooLong("SystemId", systemIdLen, 4096000); + } + + obj.systemId = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos1 = offset + 42 + buf.getIntLE(offset + 38); + int targetNodeNameLen = VarInt.peek(buf, varPos1); + if (targetNodeNameLen < 0) { + throw ProtocolException.negativeLength("TargetNodeName", targetNodeNameLen); + } + + if (targetNodeNameLen > 4096000) { + throw ProtocolException.stringTooLong("TargetNodeName", targetNodeNameLen, 4096000); + } + + obj.targetNodeName = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 42; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 34); + int pos0 = offset + 42 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 38); + int pos1 = offset + 42 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.systemId != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.color != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.targetNodeName != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.positionOffset != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.rotationOffset != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.scale); + if (this.color != null) { + this.color.serialize(buf); + } else { + buf.writeZero(3); + } + + buf.writeByte(this.targetEntityPart.getValue()); + if (this.positionOffset != null) { + this.positionOffset.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.rotationOffset != null) { + this.rotationOffset.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeByte(this.detachedFromModel ? 1 : 0); + int systemIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int targetNodeNameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.systemId != null) { + buf.setIntLE(systemIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.systemId, 4096000); + } else { + buf.setIntLE(systemIdOffsetSlot, -1); + } + + if (this.targetNodeName != null) { + buf.setIntLE(targetNodeNameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.targetNodeName, 4096000); + } else { + buf.setIntLE(targetNodeNameOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 42; + if (this.systemId != null) { + size += PacketIO.stringSize(this.systemId); + } + + if (this.targetNodeName != null) { + size += PacketIO.stringSize(this.targetNodeName); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 42) { + return ValidationResult.error("Buffer too small: expected at least 42 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int systemIdOffset = buffer.getIntLE(offset + 34); + if (systemIdOffset < 0) { + return ValidationResult.error("Invalid offset for SystemId"); + } + + int pos = offset + 42 + systemIdOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SystemId"); + } + + int systemIdLen = VarInt.peek(buffer, pos); + if (systemIdLen < 0) { + return ValidationResult.error("Invalid string length for SystemId"); + } + + if (systemIdLen > 4096000) { + return ValidationResult.error("SystemId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += systemIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading SystemId"); + } + } + + if ((nullBits & 4) != 0) { + int targetNodeNameOffset = buffer.getIntLE(offset + 38); + if (targetNodeNameOffset < 0) { + return ValidationResult.error("Invalid offset for TargetNodeName"); + } + + int posx = offset + 42 + targetNodeNameOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for TargetNodeName"); + } + + int targetNodeNameLen = VarInt.peek(buffer, posx); + if (targetNodeNameLen < 0) { + return ValidationResult.error("Invalid string length for TargetNodeName"); + } + + if (targetNodeNameLen > 4096000) { + return ValidationResult.error("TargetNodeName exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += targetNodeNameLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading TargetNodeName"); + } + } + + return ValidationResult.OK; + } + } + + public ModelParticle clone() { + ModelParticle copy = new ModelParticle(); + copy.systemId = this.systemId; + copy.scale = this.scale; + copy.color = this.color != null ? this.color.clone() : null; + copy.targetEntityPart = this.targetEntityPart; + copy.targetNodeName = this.targetNodeName; + copy.positionOffset = this.positionOffset != null ? this.positionOffset.clone() : null; + copy.rotationOffset = this.rotationOffset != null ? this.rotationOffset.clone() : null; + copy.detachedFromModel = this.detachedFromModel; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ModelParticle other) + ? false + : Objects.equals(this.systemId, other.systemId) + && this.scale == other.scale + && Objects.equals(this.color, other.color) + && Objects.equals(this.targetEntityPart, other.targetEntityPart) + && Objects.equals(this.targetNodeName, other.targetNodeName) + && Objects.equals(this.positionOffset, other.positionOffset) + && Objects.equals(this.rotationOffset, other.rotationOffset) + && this.detachedFromModel == other.detachedFromModel; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.systemId, this.scale, this.color, this.targetEntityPart, this.targetNodeName, this.positionOffset, this.rotationOffset, this.detachedFromModel + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/ModelTexture.java b/src/com/hypixel/hytale/protocol/ModelTexture.java new file mode 100644 index 0000000..58578b3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ModelTexture.java @@ -0,0 +1,139 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ModelTexture { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 16384010; + @Nullable + public String texture; + public float weight; + + public ModelTexture() { + } + + public ModelTexture(@Nullable String texture, float weight) { + this.texture = texture; + this.weight = weight; + } + + public ModelTexture(@Nonnull ModelTexture other) { + this.texture = other.texture; + this.weight = other.weight; + } + + @Nonnull + public static ModelTexture deserialize(@Nonnull ByteBuf buf, int offset) { + ModelTexture obj = new ModelTexture(); + byte nullBits = buf.getByte(offset); + obj.weight = buf.getFloatLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int textureLen = VarInt.peek(buf, pos); + if (textureLen < 0) { + throw ProtocolException.negativeLength("Texture", textureLen); + } + + if (textureLen > 4096000) { + throw ProtocolException.stringTooLong("Texture", textureLen, 4096000); + } + + int textureVarLen = VarInt.length(buf, pos); + obj.texture = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += textureVarLen + textureLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.texture != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.weight); + if (this.texture != null) { + PacketIO.writeVarString(buf, this.texture, 4096000); + } + } + + public int computeSize() { + int size = 5; + if (this.texture != null) { + size += PacketIO.stringSize(this.texture); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int textureLen = VarInt.peek(buffer, pos); + if (textureLen < 0) { + return ValidationResult.error("Invalid string length for Texture"); + } + + if (textureLen > 4096000) { + return ValidationResult.error("Texture exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += textureLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Texture"); + } + } + + return ValidationResult.OK; + } + } + + public ModelTexture clone() { + ModelTexture copy = new ModelTexture(); + copy.texture = this.texture; + copy.weight = this.weight; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ModelTexture other) ? false : Objects.equals(this.texture, other.texture) && this.weight == other.weight; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.texture, this.weight); + } +} diff --git a/src/com/hypixel/hytale/protocol/ModelTrail.java b/src/com/hypixel/hytale/protocol/ModelTrail.java new file mode 100644 index 0000000..1b2c683 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ModelTrail.java @@ -0,0 +1,290 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ModelTrail { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 27; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 35; + public static final int MAX_SIZE = 32768045; + @Nullable + public String trailId; + @Nonnull + public EntityPart targetEntityPart = EntityPart.Self; + @Nullable + public String targetNodeName; + @Nullable + public Vector3f positionOffset; + @Nullable + public Direction rotationOffset; + public boolean fixedRotation; + + public ModelTrail() { + } + + public ModelTrail( + @Nullable String trailId, + @Nonnull EntityPart targetEntityPart, + @Nullable String targetNodeName, + @Nullable Vector3f positionOffset, + @Nullable Direction rotationOffset, + boolean fixedRotation + ) { + this.trailId = trailId; + this.targetEntityPart = targetEntityPart; + this.targetNodeName = targetNodeName; + this.positionOffset = positionOffset; + this.rotationOffset = rotationOffset; + this.fixedRotation = fixedRotation; + } + + public ModelTrail(@Nonnull ModelTrail other) { + this.trailId = other.trailId; + this.targetEntityPart = other.targetEntityPart; + this.targetNodeName = other.targetNodeName; + this.positionOffset = other.positionOffset; + this.rotationOffset = other.rotationOffset; + this.fixedRotation = other.fixedRotation; + } + + @Nonnull + public static ModelTrail deserialize(@Nonnull ByteBuf buf, int offset) { + ModelTrail obj = new ModelTrail(); + byte nullBits = buf.getByte(offset); + obj.targetEntityPart = EntityPart.fromValue(buf.getByte(offset + 1)); + if ((nullBits & 4) != 0) { + obj.positionOffset = Vector3f.deserialize(buf, offset + 2); + } + + if ((nullBits & 8) != 0) { + obj.rotationOffset = Direction.deserialize(buf, offset + 14); + } + + obj.fixedRotation = buf.getByte(offset + 26) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 35 + buf.getIntLE(offset + 27); + int trailIdLen = VarInt.peek(buf, varPos0); + if (trailIdLen < 0) { + throw ProtocolException.negativeLength("TrailId", trailIdLen); + } + + if (trailIdLen > 4096000) { + throw ProtocolException.stringTooLong("TrailId", trailIdLen, 4096000); + } + + obj.trailId = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 35 + buf.getIntLE(offset + 31); + int targetNodeNameLen = VarInt.peek(buf, varPos1); + if (targetNodeNameLen < 0) { + throw ProtocolException.negativeLength("TargetNodeName", targetNodeNameLen); + } + + if (targetNodeNameLen > 4096000) { + throw ProtocolException.stringTooLong("TargetNodeName", targetNodeNameLen, 4096000); + } + + obj.targetNodeName = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 35; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 27); + int pos0 = offset + 35 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 31); + int pos1 = offset + 35 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.trailId != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.targetNodeName != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.positionOffset != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.rotationOffset != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + buf.writeByte(this.targetEntityPart.getValue()); + if (this.positionOffset != null) { + this.positionOffset.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.rotationOffset != null) { + this.rotationOffset.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeByte(this.fixedRotation ? 1 : 0); + int trailIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int targetNodeNameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.trailId != null) { + buf.setIntLE(trailIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.trailId, 4096000); + } else { + buf.setIntLE(trailIdOffsetSlot, -1); + } + + if (this.targetNodeName != null) { + buf.setIntLE(targetNodeNameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.targetNodeName, 4096000); + } else { + buf.setIntLE(targetNodeNameOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 35; + if (this.trailId != null) { + size += PacketIO.stringSize(this.trailId); + } + + if (this.targetNodeName != null) { + size += PacketIO.stringSize(this.targetNodeName); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 35) { + return ValidationResult.error("Buffer too small: expected at least 35 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int trailIdOffset = buffer.getIntLE(offset + 27); + if (trailIdOffset < 0) { + return ValidationResult.error("Invalid offset for TrailId"); + } + + int pos = offset + 35 + trailIdOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for TrailId"); + } + + int trailIdLen = VarInt.peek(buffer, pos); + if (trailIdLen < 0) { + return ValidationResult.error("Invalid string length for TrailId"); + } + + if (trailIdLen > 4096000) { + return ValidationResult.error("TrailId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += trailIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading TrailId"); + } + } + + if ((nullBits & 2) != 0) { + int targetNodeNameOffset = buffer.getIntLE(offset + 31); + if (targetNodeNameOffset < 0) { + return ValidationResult.error("Invalid offset for TargetNodeName"); + } + + int posx = offset + 35 + targetNodeNameOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for TargetNodeName"); + } + + int targetNodeNameLen = VarInt.peek(buffer, posx); + if (targetNodeNameLen < 0) { + return ValidationResult.error("Invalid string length for TargetNodeName"); + } + + if (targetNodeNameLen > 4096000) { + return ValidationResult.error("TargetNodeName exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += targetNodeNameLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading TargetNodeName"); + } + } + + return ValidationResult.OK; + } + } + + public ModelTrail clone() { + ModelTrail copy = new ModelTrail(); + copy.trailId = this.trailId; + copy.targetEntityPart = this.targetEntityPart; + copy.targetNodeName = this.targetNodeName; + copy.positionOffset = this.positionOffset != null ? this.positionOffset.clone() : null; + copy.rotationOffset = this.rotationOffset != null ? this.rotationOffset.clone() : null; + copy.fixedRotation = this.fixedRotation; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ModelTrail other) + ? false + : Objects.equals(this.trailId, other.trailId) + && Objects.equals(this.targetEntityPart, other.targetEntityPart) + && Objects.equals(this.targetNodeName, other.targetNodeName) + && Objects.equals(this.positionOffset, other.positionOffset) + && Objects.equals(this.rotationOffset, other.rotationOffset) + && this.fixedRotation == other.fixedRotation; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.trailId, this.targetEntityPart, this.targetNodeName, this.positionOffset, this.rotationOffset, this.fixedRotation); + } +} diff --git a/src/com/hypixel/hytale/protocol/ModelTransform.java b/src/com/hypixel/hytale/protocol/ModelTransform.java new file mode 100644 index 0000000..191dbc6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ModelTransform.java @@ -0,0 +1,127 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ModelTransform { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 49; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 49; + public static final int MAX_SIZE = 49; + @Nullable + public Position position; + @Nullable + public Direction bodyOrientation; + @Nullable + public Direction lookOrientation; + + public ModelTransform() { + } + + public ModelTransform(@Nullable Position position, @Nullable Direction bodyOrientation, @Nullable Direction lookOrientation) { + this.position = position; + this.bodyOrientation = bodyOrientation; + this.lookOrientation = lookOrientation; + } + + public ModelTransform(@Nonnull ModelTransform other) { + this.position = other.position; + this.bodyOrientation = other.bodyOrientation; + this.lookOrientation = other.lookOrientation; + } + + @Nonnull + public static ModelTransform deserialize(@Nonnull ByteBuf buf, int offset) { + ModelTransform obj = new ModelTransform(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.position = Position.deserialize(buf, offset + 1); + } + + if ((nullBits & 2) != 0) { + obj.bodyOrientation = Direction.deserialize(buf, offset + 25); + } + + if ((nullBits & 4) != 0) { + obj.lookOrientation = Direction.deserialize(buf, offset + 37); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 49; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.position != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.bodyOrientation != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.lookOrientation != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + if (this.position != null) { + this.position.serialize(buf); + } else { + buf.writeZero(24); + } + + if (this.bodyOrientation != null) { + this.bodyOrientation.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.lookOrientation != null) { + this.lookOrientation.serialize(buf); + } else { + buf.writeZero(12); + } + } + + public int computeSize() { + return 49; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 49 ? ValidationResult.error("Buffer too small: expected at least 49 bytes") : ValidationResult.OK; + } + + public ModelTransform clone() { + ModelTransform copy = new ModelTransform(); + copy.position = this.position != null ? this.position.clone() : null; + copy.bodyOrientation = this.bodyOrientation != null ? this.bodyOrientation.clone() : null; + copy.lookOrientation = this.lookOrientation != null ? this.lookOrientation.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ModelTransform other) + ? false + : Objects.equals(this.position, other.position) + && Objects.equals(this.bodyOrientation, other.bodyOrientation) + && Objects.equals(this.lookOrientation, other.lookOrientation); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.position, this.bodyOrientation, this.lookOrientation); + } +} diff --git a/src/com/hypixel/hytale/protocol/ModelVFX.java b/src/com/hypixel/hytale/protocol/ModelVFX.java new file mode 100644 index 0000000..9ff73da --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ModelVFX.java @@ -0,0 +1,334 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ModelVFX { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 49; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 49; + public static final int MAX_SIZE = 16384054; + @Nullable + public String id; + @Nonnull + public SwitchTo switchTo = SwitchTo.Disappear; + @Nonnull + public EffectDirection effectDirection = EffectDirection.None; + public float animationDuration; + @Nullable + public Vector2f animationRange; + @Nonnull + public LoopOption loopOption = LoopOption.PlayOnce; + @Nonnull + public CurveType curveType = CurveType.Linear; + @Nullable + public Color highlightColor; + public float highlightThickness; + public boolean useBloomOnHighlight; + public boolean useProgessiveHighlight; + @Nullable + public Vector2f noiseScale; + @Nullable + public Vector2f noiseScrollSpeed; + @Nullable + public Color postColor; + public float postColorOpacity; + + public ModelVFX() { + } + + public ModelVFX( + @Nullable String id, + @Nonnull SwitchTo switchTo, + @Nonnull EffectDirection effectDirection, + float animationDuration, + @Nullable Vector2f animationRange, + @Nonnull LoopOption loopOption, + @Nonnull CurveType curveType, + @Nullable Color highlightColor, + float highlightThickness, + boolean useBloomOnHighlight, + boolean useProgessiveHighlight, + @Nullable Vector2f noiseScale, + @Nullable Vector2f noiseScrollSpeed, + @Nullable Color postColor, + float postColorOpacity + ) { + this.id = id; + this.switchTo = switchTo; + this.effectDirection = effectDirection; + this.animationDuration = animationDuration; + this.animationRange = animationRange; + this.loopOption = loopOption; + this.curveType = curveType; + this.highlightColor = highlightColor; + this.highlightThickness = highlightThickness; + this.useBloomOnHighlight = useBloomOnHighlight; + this.useProgessiveHighlight = useProgessiveHighlight; + this.noiseScale = noiseScale; + this.noiseScrollSpeed = noiseScrollSpeed; + this.postColor = postColor; + this.postColorOpacity = postColorOpacity; + } + + public ModelVFX(@Nonnull ModelVFX other) { + this.id = other.id; + this.switchTo = other.switchTo; + this.effectDirection = other.effectDirection; + this.animationDuration = other.animationDuration; + this.animationRange = other.animationRange; + this.loopOption = other.loopOption; + this.curveType = other.curveType; + this.highlightColor = other.highlightColor; + this.highlightThickness = other.highlightThickness; + this.useBloomOnHighlight = other.useBloomOnHighlight; + this.useProgessiveHighlight = other.useProgessiveHighlight; + this.noiseScale = other.noiseScale; + this.noiseScrollSpeed = other.noiseScrollSpeed; + this.postColor = other.postColor; + this.postColorOpacity = other.postColorOpacity; + } + + @Nonnull + public static ModelVFX deserialize(@Nonnull ByteBuf buf, int offset) { + ModelVFX obj = new ModelVFX(); + byte nullBits = buf.getByte(offset); + obj.switchTo = SwitchTo.fromValue(buf.getByte(offset + 1)); + obj.effectDirection = EffectDirection.fromValue(buf.getByte(offset + 2)); + obj.animationDuration = buf.getFloatLE(offset + 3); + if ((nullBits & 2) != 0) { + obj.animationRange = Vector2f.deserialize(buf, offset + 7); + } + + obj.loopOption = LoopOption.fromValue(buf.getByte(offset + 15)); + obj.curveType = CurveType.fromValue(buf.getByte(offset + 16)); + if ((nullBits & 4) != 0) { + obj.highlightColor = Color.deserialize(buf, offset + 17); + } + + obj.highlightThickness = buf.getFloatLE(offset + 20); + obj.useBloomOnHighlight = buf.getByte(offset + 24) != 0; + obj.useProgessiveHighlight = buf.getByte(offset + 25) != 0; + if ((nullBits & 8) != 0) { + obj.noiseScale = Vector2f.deserialize(buf, offset + 26); + } + + if ((nullBits & 16) != 0) { + obj.noiseScrollSpeed = Vector2f.deserialize(buf, offset + 34); + } + + if ((nullBits & 32) != 0) { + obj.postColor = Color.deserialize(buf, offset + 42); + } + + obj.postColorOpacity = buf.getFloatLE(offset + 45); + int pos = offset + 49; + if ((nullBits & 1) != 0) { + int idLen = VarInt.peek(buf, pos); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + int idVarLen = VarInt.length(buf, pos); + obj.id = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += idVarLen + idLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 49; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.animationRange != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.highlightColor != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.noiseScale != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.noiseScrollSpeed != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.postColor != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.switchTo.getValue()); + buf.writeByte(this.effectDirection.getValue()); + buf.writeFloatLE(this.animationDuration); + if (this.animationRange != null) { + this.animationRange.serialize(buf); + } else { + buf.writeZero(8); + } + + buf.writeByte(this.loopOption.getValue()); + buf.writeByte(this.curveType.getValue()); + if (this.highlightColor != null) { + this.highlightColor.serialize(buf); + } else { + buf.writeZero(3); + } + + buf.writeFloatLE(this.highlightThickness); + buf.writeByte(this.useBloomOnHighlight ? 1 : 0); + buf.writeByte(this.useProgessiveHighlight ? 1 : 0); + if (this.noiseScale != null) { + this.noiseScale.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.noiseScrollSpeed != null) { + this.noiseScrollSpeed.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.postColor != null) { + this.postColor.serialize(buf); + } else { + buf.writeZero(3); + } + + buf.writeFloatLE(this.postColorOpacity); + if (this.id != null) { + PacketIO.writeVarString(buf, this.id, 4096000); + } + } + + public int computeSize() { + int size = 49; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 49) { + return ValidationResult.error("Buffer too small: expected at least 49 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 49; + if ((nullBits & 1) != 0) { + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + return ValidationResult.OK; + } + } + + public ModelVFX clone() { + ModelVFX copy = new ModelVFX(); + copy.id = this.id; + copy.switchTo = this.switchTo; + copy.effectDirection = this.effectDirection; + copy.animationDuration = this.animationDuration; + copy.animationRange = this.animationRange != null ? this.animationRange.clone() : null; + copy.loopOption = this.loopOption; + copy.curveType = this.curveType; + copy.highlightColor = this.highlightColor != null ? this.highlightColor.clone() : null; + copy.highlightThickness = this.highlightThickness; + copy.useBloomOnHighlight = this.useBloomOnHighlight; + copy.useProgessiveHighlight = this.useProgessiveHighlight; + copy.noiseScale = this.noiseScale != null ? this.noiseScale.clone() : null; + copy.noiseScrollSpeed = this.noiseScrollSpeed != null ? this.noiseScrollSpeed.clone() : null; + copy.postColor = this.postColor != null ? this.postColor.clone() : null; + copy.postColorOpacity = this.postColorOpacity; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ModelVFX other) + ? false + : Objects.equals(this.id, other.id) + && Objects.equals(this.switchTo, other.switchTo) + && Objects.equals(this.effectDirection, other.effectDirection) + && this.animationDuration == other.animationDuration + && Objects.equals(this.animationRange, other.animationRange) + && Objects.equals(this.loopOption, other.loopOption) + && Objects.equals(this.curveType, other.curveType) + && Objects.equals(this.highlightColor, other.highlightColor) + && this.highlightThickness == other.highlightThickness + && this.useBloomOnHighlight == other.useBloomOnHighlight + && this.useProgessiveHighlight == other.useProgessiveHighlight + && Objects.equals(this.noiseScale, other.noiseScale) + && Objects.equals(this.noiseScrollSpeed, other.noiseScrollSpeed) + && Objects.equals(this.postColor, other.postColor) + && this.postColorOpacity == other.postColorOpacity; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.id, + this.switchTo, + this.effectDirection, + this.animationDuration, + this.animationRange, + this.loopOption, + this.curveType, + this.highlightColor, + this.highlightThickness, + this.useBloomOnHighlight, + this.useProgessiveHighlight, + this.noiseScale, + this.noiseScrollSpeed, + this.postColor, + this.postColorOpacity + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/Modifier.java b/src/com/hypixel/hytale/protocol/Modifier.java new file mode 100644 index 0000000..81a9bd4 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Modifier.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class Modifier { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 6; + @Nonnull + public ModifierTarget target = ModifierTarget.Min; + @Nonnull + public CalculationType calculationType = CalculationType.Additive; + public float amount; + + public Modifier() { + } + + public Modifier(@Nonnull ModifierTarget target, @Nonnull CalculationType calculationType, float amount) { + this.target = target; + this.calculationType = calculationType; + this.amount = amount; + } + + public Modifier(@Nonnull Modifier other) { + this.target = other.target; + this.calculationType = other.calculationType; + this.amount = other.amount; + } + + @Nonnull + public static Modifier deserialize(@Nonnull ByteBuf buf, int offset) { + Modifier obj = new Modifier(); + obj.target = ModifierTarget.fromValue(buf.getByte(offset + 0)); + obj.calculationType = CalculationType.fromValue(buf.getByte(offset + 1)); + obj.amount = buf.getFloatLE(offset + 2); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 6; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.target.getValue()); + buf.writeByte(this.calculationType.getValue()); + buf.writeFloatLE(this.amount); + } + + public int computeSize() { + return 6; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 6 ? ValidationResult.error("Buffer too small: expected at least 6 bytes") : ValidationResult.OK; + } + + public Modifier clone() { + Modifier copy = new Modifier(); + copy.target = this.target; + copy.calculationType = this.calculationType; + copy.amount = this.amount; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Modifier other) + ? false + : Objects.equals(this.target, other.target) && Objects.equals(this.calculationType, other.calculationType) && this.amount == other.amount; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.target, this.calculationType, this.amount); + } +} diff --git a/src/com/hypixel/hytale/protocol/ModifierTarget.java b/src/com/hypixel/hytale/protocol/ModifierTarget.java new file mode 100644 index 0000000..48cb29d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ModifierTarget.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ModifierTarget { + Min(0), + Max(1); + + public static final ModifierTarget[] VALUES = values(); + private final int value; + + private ModifierTarget(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ModifierTarget fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ModifierTarget", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ModifyInventoryInteraction.java b/src/com/hypixel/hytale/protocol/ModifyInventoryInteraction.java new file mode 100644 index 0000000..3acccf3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ModifyInventoryInteraction.java @@ -0,0 +1,737 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ModifyInventoryInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 2; + public static final int FIXED_BLOCK_SIZE = 33; + public static final int VARIABLE_FIELD_COUNT = 8; + public static final int VARIABLE_BLOCK_START = 65; + public static final int MAX_SIZE = 1677721600; + @Nullable + public GameMode requiredGameMode; + @Nullable + public ItemWithAllMetadata itemToRemove; + public int adjustHeldItemQuantity; + @Nullable + public ItemWithAllMetadata itemToAdd; + @Nullable + public String brokenItem; + public double adjustHeldItemDurability; + + public ModifyInventoryInteraction() { + } + + public ModifyInventoryInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + @Nullable GameMode requiredGameMode, + @Nullable ItemWithAllMetadata itemToRemove, + int adjustHeldItemQuantity, + @Nullable ItemWithAllMetadata itemToAdd, + @Nullable String brokenItem, + double adjustHeldItemDurability + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.requiredGameMode = requiredGameMode; + this.itemToRemove = itemToRemove; + this.adjustHeldItemQuantity = adjustHeldItemQuantity; + this.itemToAdd = itemToAdd; + this.brokenItem = brokenItem; + this.adjustHeldItemDurability = adjustHeldItemDurability; + } + + public ModifyInventoryInteraction(@Nonnull ModifyInventoryInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.requiredGameMode = other.requiredGameMode; + this.itemToRemove = other.itemToRemove; + this.adjustHeldItemQuantity = other.adjustHeldItemQuantity; + this.itemToAdd = other.itemToAdd; + this.brokenItem = other.brokenItem; + this.adjustHeldItemDurability = other.adjustHeldItemDurability; + } + + @Nonnull + public static ModifyInventoryInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + ModifyInventoryInteraction obj = new ModifyInventoryInteraction(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 2)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 3); + obj.runTime = buf.getFloatLE(offset + 7); + obj.cancelOnItemChange = buf.getByte(offset + 11) != 0; + obj.next = buf.getIntLE(offset + 12); + obj.failed = buf.getIntLE(offset + 16); + if ((nullBits[0] & 32) != 0) { + obj.requiredGameMode = GameMode.fromValue(buf.getByte(offset + 20)); + } + + obj.adjustHeldItemQuantity = buf.getIntLE(offset + 21); + obj.adjustHeldItemDurability = buf.getDoubleLE(offset + 25); + if ((nullBits[0] & 1) != 0) { + int varPos0 = offset + 65 + buf.getIntLE(offset + 33); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits[0] & 2) != 0) { + int varPos1 = offset + 65 + buf.getIntLE(offset + 37); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits[0] & 4) != 0) { + int varPos2 = offset + 65 + buf.getIntLE(offset + 41); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits[0] & 8) != 0) { + int varPos3 = offset + 65 + buf.getIntLE(offset + 45); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits[0] & 16) != 0) { + int varPos4 = offset + 65 + buf.getIntLE(offset + 49); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits[0] & 64) != 0) { + int varPos5 = offset + 65 + buf.getIntLE(offset + 53); + obj.itemToRemove = ItemWithAllMetadata.deserialize(buf, varPos5); + } + + if ((nullBits[0] & 128) != 0) { + int varPos6 = offset + 65 + buf.getIntLE(offset + 57); + obj.itemToAdd = ItemWithAllMetadata.deserialize(buf, varPos6); + } + + if ((nullBits[1] & 1) != 0) { + int varPos7 = offset + 65 + buf.getIntLE(offset + 61); + int brokenItemLen = VarInt.peek(buf, varPos7); + if (brokenItemLen < 0) { + throw ProtocolException.negativeLength("BrokenItem", brokenItemLen); + } + + if (brokenItemLen > 4096000) { + throw ProtocolException.stringTooLong("BrokenItem", brokenItemLen, 4096000); + } + + obj.brokenItem = PacketIO.readVarString(buf, varPos7, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + int maxEnd = 65; + if ((nullBits[0] & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 33); + int pos0 = offset + 65 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits[0] & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 37); + int pos1 = offset + 65 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits[0] & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 41); + int pos2 = offset + 65 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits[0] & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 45); + int pos3 = offset + 65 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits[0] & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 49); + int pos4 = offset + 65 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits[0] & 64) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 53); + int pos5 = offset + 65 + fieldOffset5; + pos5 += ItemWithAllMetadata.computeBytesConsumed(buf, pos5); + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits[0] & 128) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 57); + int pos6 = offset + 65 + fieldOffset6; + pos6 += ItemWithAllMetadata.computeBytesConsumed(buf, pos6); + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + if ((nullBits[1] & 1) != 0) { + int fieldOffset7 = buf.getIntLE(offset + 61); + int pos7 = offset + 65 + fieldOffset7; + int sl = VarInt.peek(buf, pos7); + pos7 += VarInt.length(buf, pos7) + sl; + if (pos7 - offset > maxEnd) { + maxEnd = pos7 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte[] nullBits = new byte[2]; + if (this.effects != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.settings != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.rules != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.tags != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.camera != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.requiredGameMode != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.itemToRemove != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.itemToAdd != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.brokenItem != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + buf.writeBytes(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + if (this.requiredGameMode != null) { + buf.writeByte(this.requiredGameMode.getValue()); + } else { + buf.writeZero(1); + } + + buf.writeIntLE(this.adjustHeldItemQuantity); + buf.writeDoubleLE(this.adjustHeldItemDurability); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int itemToRemoveOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int itemToAddOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int brokenItemOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.itemToRemove != null) { + buf.setIntLE(itemToRemoveOffsetSlot, buf.writerIndex() - varBlockStart); + this.itemToRemove.serialize(buf); + } else { + buf.setIntLE(itemToRemoveOffsetSlot, -1); + } + + if (this.itemToAdd != null) { + buf.setIntLE(itemToAddOffsetSlot, buf.writerIndex() - varBlockStart); + this.itemToAdd.serialize(buf); + } else { + buf.setIntLE(itemToAddOffsetSlot, -1); + } + + if (this.brokenItem != null) { + buf.setIntLE(brokenItemOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.brokenItem, 4096000); + } else { + buf.setIntLE(brokenItemOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 65; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.itemToRemove != null) { + size += this.itemToRemove.computeSize(); + } + + if (this.itemToAdd != null) { + size += this.itemToAdd.computeSize(); + } + + if (this.brokenItem != null) { + size += PacketIO.stringSize(this.brokenItem); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 65) { + return ValidationResult.error("Buffer too small: expected at least 65 bytes"); + } else { + byte[] nullBits = PacketIO.readBytes(buffer, offset, 2); + if ((nullBits[0] & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 33); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 65 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits[0] & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 37); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 65 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits[0] & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 41); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 65 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits[0] & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 45); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 65 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits[0] & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 49); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 65 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits[0] & 64) != 0) { + int itemToRemoveOffset = buffer.getIntLE(offset + 53); + if (itemToRemoveOffset < 0) { + return ValidationResult.error("Invalid offset for ItemToRemove"); + } + + int posxxxxx = offset + 65 + itemToRemoveOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ItemToRemove"); + } + + ValidationResult itemToRemoveResult = ItemWithAllMetadata.validateStructure(buffer, posxxxxx); + if (!itemToRemoveResult.isValid()) { + return ValidationResult.error("Invalid ItemToRemove: " + itemToRemoveResult.error()); + } + + posxxxxx += ItemWithAllMetadata.computeBytesConsumed(buffer, posxxxxx); + } + + if ((nullBits[0] & 128) != 0) { + int itemToAddOffset = buffer.getIntLE(offset + 57); + if (itemToAddOffset < 0) { + return ValidationResult.error("Invalid offset for ItemToAdd"); + } + + int posxxxxxx = offset + 65 + itemToAddOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ItemToAdd"); + } + + ValidationResult itemToAddResult = ItemWithAllMetadata.validateStructure(buffer, posxxxxxx); + if (!itemToAddResult.isValid()) { + return ValidationResult.error("Invalid ItemToAdd: " + itemToAddResult.error()); + } + + posxxxxxx += ItemWithAllMetadata.computeBytesConsumed(buffer, posxxxxxx); + } + + if ((nullBits[1] & 1) != 0) { + int brokenItemOffset = buffer.getIntLE(offset + 61); + if (brokenItemOffset < 0) { + return ValidationResult.error("Invalid offset for BrokenItem"); + } + + int posxxxxxxx = offset + 65 + brokenItemOffset; + if (posxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BrokenItem"); + } + + int brokenItemLen = VarInt.peek(buffer, posxxxxxxx); + if (brokenItemLen < 0) { + return ValidationResult.error("Invalid string length for BrokenItem"); + } + + if (brokenItemLen > 4096000) { + return ValidationResult.error("BrokenItem exceeds max length 4096000"); + } + + posxxxxxxx += VarInt.length(buffer, posxxxxxxx); + posxxxxxxx += brokenItemLen; + if (posxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading BrokenItem"); + } + } + + return ValidationResult.OK; + } + } + + public ModifyInventoryInteraction clone() { + ModifyInventoryInteraction copy = new ModifyInventoryInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.requiredGameMode = this.requiredGameMode; + copy.itemToRemove = this.itemToRemove != null ? this.itemToRemove.clone() : null; + copy.adjustHeldItemQuantity = this.adjustHeldItemQuantity; + copy.itemToAdd = this.itemToAdd != null ? this.itemToAdd.clone() : null; + copy.brokenItem = this.brokenItem; + copy.adjustHeldItemDurability = this.adjustHeldItemDurability; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ModifyInventoryInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && Objects.equals(this.requiredGameMode, other.requiredGameMode) + && Objects.equals(this.itemToRemove, other.itemToRemove) + && this.adjustHeldItemQuantity == other.adjustHeldItemQuantity + && Objects.equals(this.itemToAdd, other.itemToAdd) + && Objects.equals(this.brokenItem, other.brokenItem) + && this.adjustHeldItemDurability == other.adjustHeldItemDurability; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Objects.hashCode(this.requiredGameMode); + result = 31 * result + Objects.hashCode(this.itemToRemove); + result = 31 * result + Integer.hashCode(this.adjustHeldItemQuantity); + result = 31 * result + Objects.hashCode(this.itemToAdd); + result = 31 * result + Objects.hashCode(this.brokenItem); + return 31 * result + Double.hashCode(this.adjustHeldItemDurability); + } +} diff --git a/src/com/hypixel/hytale/protocol/MountController.java b/src/com/hypixel/hytale/protocol/MountController.java new file mode 100644 index 0000000..e3b86c4 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MountController.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum MountController { + Minecart(0), + BlockMount(1); + + public static final MountController[] VALUES = values(); + private final int value; + + private MountController(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static MountController fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("MountController", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/MountedUpdate.java b/src/com/hypixel/hytale/protocol/MountedUpdate.java new file mode 100644 index 0000000..31d35e1 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MountedUpdate.java @@ -0,0 +1,122 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MountedUpdate { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 48; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 48; + public static final int MAX_SIZE = 48; + public int mountedToEntity; + @Nullable + public Vector3f attachmentOffset; + @Nonnull + public MountController controller = MountController.Minecart; + @Nullable + public BlockMount block; + + public MountedUpdate() { + } + + public MountedUpdate(int mountedToEntity, @Nullable Vector3f attachmentOffset, @Nonnull MountController controller, @Nullable BlockMount block) { + this.mountedToEntity = mountedToEntity; + this.attachmentOffset = attachmentOffset; + this.controller = controller; + this.block = block; + } + + public MountedUpdate(@Nonnull MountedUpdate other) { + this.mountedToEntity = other.mountedToEntity; + this.attachmentOffset = other.attachmentOffset; + this.controller = other.controller; + this.block = other.block; + } + + @Nonnull + public static MountedUpdate deserialize(@Nonnull ByteBuf buf, int offset) { + MountedUpdate obj = new MountedUpdate(); + byte nullBits = buf.getByte(offset); + obj.mountedToEntity = buf.getIntLE(offset + 1); + if ((nullBits & 1) != 0) { + obj.attachmentOffset = Vector3f.deserialize(buf, offset + 5); + } + + obj.controller = MountController.fromValue(buf.getByte(offset + 17)); + if ((nullBits & 2) != 0) { + obj.block = BlockMount.deserialize(buf, offset + 18); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 48; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.attachmentOffset != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.block != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.mountedToEntity); + if (this.attachmentOffset != null) { + this.attachmentOffset.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeByte(this.controller.getValue()); + if (this.block != null) { + this.block.serialize(buf); + } else { + buf.writeZero(30); + } + } + + public int computeSize() { + return 48; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 48 ? ValidationResult.error("Buffer too small: expected at least 48 bytes") : ValidationResult.OK; + } + + public MountedUpdate clone() { + MountedUpdate copy = new MountedUpdate(); + copy.mountedToEntity = this.mountedToEntity; + copy.attachmentOffset = this.attachmentOffset != null ? this.attachmentOffset.clone() : null; + copy.controller = this.controller; + copy.block = this.block != null ? this.block.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof MountedUpdate other) + ? false + : this.mountedToEntity == other.mountedToEntity + && Objects.equals(this.attachmentOffset, other.attachmentOffset) + && Objects.equals(this.controller, other.controller) + && Objects.equals(this.block, other.block); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.mountedToEntity, this.attachmentOffset, this.controller, this.block); + } +} diff --git a/src/com/hypixel/hytale/protocol/MouseButtonEvent.java b/src/com/hypixel/hytale/protocol/MouseButtonEvent.java new file mode 100644 index 0000000..55e85fc --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MouseButtonEvent.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class MouseButtonEvent { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 3; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 3; + public static final int MAX_SIZE = 3; + @Nonnull + public MouseButtonType mouseButtonType = MouseButtonType.Left; + @Nonnull + public MouseButtonState state = MouseButtonState.Pressed; + public byte clicks; + + public MouseButtonEvent() { + } + + public MouseButtonEvent(@Nonnull MouseButtonType mouseButtonType, @Nonnull MouseButtonState state, byte clicks) { + this.mouseButtonType = mouseButtonType; + this.state = state; + this.clicks = clicks; + } + + public MouseButtonEvent(@Nonnull MouseButtonEvent other) { + this.mouseButtonType = other.mouseButtonType; + this.state = other.state; + this.clicks = other.clicks; + } + + @Nonnull + public static MouseButtonEvent deserialize(@Nonnull ByteBuf buf, int offset) { + MouseButtonEvent obj = new MouseButtonEvent(); + obj.mouseButtonType = MouseButtonType.fromValue(buf.getByte(offset + 0)); + obj.state = MouseButtonState.fromValue(buf.getByte(offset + 1)); + obj.clicks = buf.getByte(offset + 2); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 3; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.mouseButtonType.getValue()); + buf.writeByte(this.state.getValue()); + buf.writeByte(this.clicks); + } + + public int computeSize() { + return 3; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 3 ? ValidationResult.error("Buffer too small: expected at least 3 bytes") : ValidationResult.OK; + } + + public MouseButtonEvent clone() { + MouseButtonEvent copy = new MouseButtonEvent(); + copy.mouseButtonType = this.mouseButtonType; + copy.state = this.state; + copy.clicks = this.clicks; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof MouseButtonEvent other) + ? false + : Objects.equals(this.mouseButtonType, other.mouseButtonType) && Objects.equals(this.state, other.state) && this.clicks == other.clicks; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.mouseButtonType, this.state, this.clicks); + } +} diff --git a/src/com/hypixel/hytale/protocol/MouseButtonState.java b/src/com/hypixel/hytale/protocol/MouseButtonState.java new file mode 100644 index 0000000..d73e134 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MouseButtonState.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum MouseButtonState { + Pressed(0), + Released(1); + + public static final MouseButtonState[] VALUES = values(); + private final int value; + + private MouseButtonState(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static MouseButtonState fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("MouseButtonState", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/MouseButtonType.java b/src/com/hypixel/hytale/protocol/MouseButtonType.java new file mode 100644 index 0000000..0e06502 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MouseButtonType.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum MouseButtonType { + Left(0), + Middle(1), + Right(2), + X1(3), + X2(4); + + public static final MouseButtonType[] VALUES = values(); + private final int value; + + private MouseButtonType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static MouseButtonType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("MouseButtonType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/MouseInputTargetType.java b/src/com/hypixel/hytale/protocol/MouseInputTargetType.java new file mode 100644 index 0000000..174b7fa --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MouseInputTargetType.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum MouseInputTargetType { + Any(0), + Block(1), + Entity(2), + None(3); + + public static final MouseInputTargetType[] VALUES = values(); + private final int value; + + private MouseInputTargetType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static MouseInputTargetType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("MouseInputTargetType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/MouseInputType.java b/src/com/hypixel/hytale/protocol/MouseInputType.java new file mode 100644 index 0000000..63acff3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MouseInputType.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum MouseInputType { + LookAtTarget(0), + LookAtTargetBlock(1), + LookAtTargetEntity(2), + LookAtPlane(3); + + public static final MouseInputType[] VALUES = values(); + private final int value; + + private MouseInputType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static MouseInputType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("MouseInputType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/MouseMotionEvent.java b/src/com/hypixel/hytale/protocol/MouseMotionEvent.java new file mode 100644 index 0000000..b23f7db --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MouseMotionEvent.java @@ -0,0 +1,173 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MouseMotionEvent { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 4096014; + @Nullable + public MouseButtonType[] mouseButtonType; + @Nullable + public Vector2i relativeMotion; + + public MouseMotionEvent() { + } + + public MouseMotionEvent(@Nullable MouseButtonType[] mouseButtonType, @Nullable Vector2i relativeMotion) { + this.mouseButtonType = mouseButtonType; + this.relativeMotion = relativeMotion; + } + + public MouseMotionEvent(@Nonnull MouseMotionEvent other) { + this.mouseButtonType = other.mouseButtonType; + this.relativeMotion = other.relativeMotion; + } + + @Nonnull + public static MouseMotionEvent deserialize(@Nonnull ByteBuf buf, int offset) { + MouseMotionEvent obj = new MouseMotionEvent(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 2) != 0) { + obj.relativeMotion = Vector2i.deserialize(buf, offset + 1); + } + + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int mouseButtonTypeCount = VarInt.peek(buf, pos); + if (mouseButtonTypeCount < 0) { + throw ProtocolException.negativeLength("MouseButtonType", mouseButtonTypeCount); + } + + if (mouseButtonTypeCount > 4096000) { + throw ProtocolException.arrayTooLong("MouseButtonType", mouseButtonTypeCount, 4096000); + } + + int mouseButtonTypeVarLen = VarInt.size(mouseButtonTypeCount); + if (pos + mouseButtonTypeVarLen + mouseButtonTypeCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("MouseButtonType", pos + mouseButtonTypeVarLen + mouseButtonTypeCount * 1, buf.readableBytes()); + } + + pos += mouseButtonTypeVarLen; + obj.mouseButtonType = new MouseButtonType[mouseButtonTypeCount]; + + for (int i = 0; i < mouseButtonTypeCount; i++) { + obj.mouseButtonType[i] = MouseButtonType.fromValue(buf.getByte(pos)); + pos++; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 1; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.mouseButtonType != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.relativeMotion != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + if (this.relativeMotion != null) { + this.relativeMotion.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.mouseButtonType != null) { + if (this.mouseButtonType.length > 4096000) { + throw ProtocolException.arrayTooLong("MouseButtonType", this.mouseButtonType.length, 4096000); + } + + VarInt.write(buf, this.mouseButtonType.length); + + for (MouseButtonType item : this.mouseButtonType) { + buf.writeByte(item.getValue()); + } + } + } + + public int computeSize() { + int size = 9; + if (this.mouseButtonType != null) { + size += VarInt.size(this.mouseButtonType.length) + this.mouseButtonType.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int mouseButtonTypeCount = VarInt.peek(buffer, pos); + if (mouseButtonTypeCount < 0) { + return ValidationResult.error("Invalid array count for MouseButtonType"); + } + + if (mouseButtonTypeCount > 4096000) { + return ValidationResult.error("MouseButtonType exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += mouseButtonTypeCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading MouseButtonType"); + } + } + + return ValidationResult.OK; + } + } + + public MouseMotionEvent clone() { + MouseMotionEvent copy = new MouseMotionEvent(); + copy.mouseButtonType = this.mouseButtonType != null ? Arrays.copyOf(this.mouseButtonType, this.mouseButtonType.length) : null; + copy.relativeMotion = this.relativeMotion != null ? this.relativeMotion.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof MouseMotionEvent other) + ? false + : Arrays.equals((Object[])this.mouseButtonType, (Object[])other.mouseButtonType) && Objects.equals(this.relativeMotion, other.relativeMotion); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode((Object[])this.mouseButtonType); + return 31 * result + Objects.hashCode(this.relativeMotion); + } +} diff --git a/src/com/hypixel/hytale/protocol/MovementConditionInteraction.java b/src/com/hypixel/hytale/protocol/MovementConditionInteraction.java new file mode 100644 index 0000000..c4a2d2e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MovementConditionInteraction.java @@ -0,0 +1,576 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MovementConditionInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 51; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 71; + public static final int MAX_SIZE = 1677721600; + public int forward; + public int back; + public int left; + public int right; + public int forwardLeft; + public int forwardRight; + public int backLeft; + public int backRight; + + public MovementConditionInteraction() { + } + + public MovementConditionInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + int forward, + int back, + int left, + int right, + int forwardLeft, + int forwardRight, + int backLeft, + int backRight + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.forward = forward; + this.back = back; + this.left = left; + this.right = right; + this.forwardLeft = forwardLeft; + this.forwardRight = forwardRight; + this.backLeft = backLeft; + this.backRight = backRight; + } + + public MovementConditionInteraction(@Nonnull MovementConditionInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.forward = other.forward; + this.back = other.back; + this.left = other.left; + this.right = other.right; + this.forwardLeft = other.forwardLeft; + this.forwardRight = other.forwardRight; + this.backLeft = other.backLeft; + this.backRight = other.backRight; + } + + @Nonnull + public static MovementConditionInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + MovementConditionInteraction obj = new MovementConditionInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.forward = buf.getIntLE(offset + 19); + obj.back = buf.getIntLE(offset + 23); + obj.left = buf.getIntLE(offset + 27); + obj.right = buf.getIntLE(offset + 31); + obj.forwardLeft = buf.getIntLE(offset + 35); + obj.forwardRight = buf.getIntLE(offset + 39); + obj.backLeft = buf.getIntLE(offset + 43); + obj.backRight = buf.getIntLE(offset + 47); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 71 + buf.getIntLE(offset + 51); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 71 + buf.getIntLE(offset + 55); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 71 + buf.getIntLE(offset + 59); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 71 + buf.getIntLE(offset + 63); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 71 + buf.getIntLE(offset + 67); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 71; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 51); + int pos0 = offset + 71 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 55); + int pos1 = offset + 71 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 59); + int pos2 = offset + 71 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 63); + int pos3 = offset + 71 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 67); + int pos4 = offset + 71 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeIntLE(this.forward); + buf.writeIntLE(this.back); + buf.writeIntLE(this.left); + buf.writeIntLE(this.right); + buf.writeIntLE(this.forwardLeft); + buf.writeIntLE(this.forwardRight); + buf.writeIntLE(this.backLeft); + buf.writeIntLE(this.backRight); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 71; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 71) { + return ValidationResult.error("Buffer too small: expected at least 71 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 51); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 71 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 55); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 71 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 59); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 71 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 63); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 71 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 67); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 71 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public MovementConditionInteraction clone() { + MovementConditionInteraction copy = new MovementConditionInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.forward = this.forward; + copy.back = this.back; + copy.left = this.left; + copy.right = this.right; + copy.forwardLeft = this.forwardLeft; + copy.forwardRight = this.forwardRight; + copy.backLeft = this.backLeft; + copy.backRight = this.backRight; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof MovementConditionInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && this.forward == other.forward + && this.back == other.back + && this.left == other.left + && this.right == other.right + && this.forwardLeft == other.forwardLeft + && this.forwardRight == other.forwardRight + && this.backLeft == other.backLeft + && this.backRight == other.backRight; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Integer.hashCode(this.forward); + result = 31 * result + Integer.hashCode(this.back); + result = 31 * result + Integer.hashCode(this.left); + result = 31 * result + Integer.hashCode(this.right); + result = 31 * result + Integer.hashCode(this.forwardLeft); + result = 31 * result + Integer.hashCode(this.forwardRight); + result = 31 * result + Integer.hashCode(this.backLeft); + return 31 * result + Integer.hashCode(this.backRight); + } +} diff --git a/src/com/hypixel/hytale/protocol/MovementDirection.java b/src/com/hypixel/hytale/protocol/MovementDirection.java new file mode 100644 index 0000000..3f6fcf3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MovementDirection.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum MovementDirection { + None(0), + Forward(1), + Back(2), + Left(3), + Right(4), + ForwardLeft(5), + ForwardRight(6), + BackLeft(7), + BackRight(8); + + public static final MovementDirection[] VALUES = values(); + private final int value; + + private MovementDirection(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static MovementDirection fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("MovementDirection", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/MovementEffects.java b/src/com/hypixel/hytale/protocol/MovementEffects.java new file mode 100644 index 0000000..7c64efd --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MovementEffects.java @@ -0,0 +1,123 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class MovementEffects { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 7; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 7; + public static final int MAX_SIZE = 7; + public boolean disableForward; + public boolean disableBackward; + public boolean disableLeft; + public boolean disableRight; + public boolean disableSprint; + public boolean disableJump; + public boolean disableCrouch; + + public MovementEffects() { + } + + public MovementEffects( + boolean disableForward, + boolean disableBackward, + boolean disableLeft, + boolean disableRight, + boolean disableSprint, + boolean disableJump, + boolean disableCrouch + ) { + this.disableForward = disableForward; + this.disableBackward = disableBackward; + this.disableLeft = disableLeft; + this.disableRight = disableRight; + this.disableSprint = disableSprint; + this.disableJump = disableJump; + this.disableCrouch = disableCrouch; + } + + public MovementEffects(@Nonnull MovementEffects other) { + this.disableForward = other.disableForward; + this.disableBackward = other.disableBackward; + this.disableLeft = other.disableLeft; + this.disableRight = other.disableRight; + this.disableSprint = other.disableSprint; + this.disableJump = other.disableJump; + this.disableCrouch = other.disableCrouch; + } + + @Nonnull + public static MovementEffects deserialize(@Nonnull ByteBuf buf, int offset) { + MovementEffects obj = new MovementEffects(); + obj.disableForward = buf.getByte(offset + 0) != 0; + obj.disableBackward = buf.getByte(offset + 1) != 0; + obj.disableLeft = buf.getByte(offset + 2) != 0; + obj.disableRight = buf.getByte(offset + 3) != 0; + obj.disableSprint = buf.getByte(offset + 4) != 0; + obj.disableJump = buf.getByte(offset + 5) != 0; + obj.disableCrouch = buf.getByte(offset + 6) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 7; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.disableForward ? 1 : 0); + buf.writeByte(this.disableBackward ? 1 : 0); + buf.writeByte(this.disableLeft ? 1 : 0); + buf.writeByte(this.disableRight ? 1 : 0); + buf.writeByte(this.disableSprint ? 1 : 0); + buf.writeByte(this.disableJump ? 1 : 0); + buf.writeByte(this.disableCrouch ? 1 : 0); + } + + public int computeSize() { + return 7; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 7 ? ValidationResult.error("Buffer too small: expected at least 7 bytes") : ValidationResult.OK; + } + + public MovementEffects clone() { + MovementEffects copy = new MovementEffects(); + copy.disableForward = this.disableForward; + copy.disableBackward = this.disableBackward; + copy.disableLeft = this.disableLeft; + copy.disableRight = this.disableRight; + copy.disableSprint = this.disableSprint; + copy.disableJump = this.disableJump; + copy.disableCrouch = this.disableCrouch; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof MovementEffects other) + ? false + : this.disableForward == other.disableForward + && this.disableBackward == other.disableBackward + && this.disableLeft == other.disableLeft + && this.disableRight == other.disableRight + && this.disableSprint == other.disableSprint + && this.disableJump == other.disableJump + && this.disableCrouch == other.disableCrouch; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.disableForward, this.disableBackward, this.disableLeft, this.disableRight, this.disableSprint, this.disableJump, this.disableCrouch + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/MovementForceRotationType.java b/src/com/hypixel/hytale/protocol/MovementForceRotationType.java new file mode 100644 index 0000000..b17a152 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MovementForceRotationType.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum MovementForceRotationType { + AttachedToHead(0), + CameraRotation(1), + Custom(2); + + public static final MovementForceRotationType[] VALUES = values(); + private final int value; + + private MovementForceRotationType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static MovementForceRotationType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("MovementForceRotationType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/MovementSettings.java b/src/com/hypixel/hytale/protocol/MovementSettings.java new file mode 100644 index 0000000..5230d85 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MovementSettings.java @@ -0,0 +1,651 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class MovementSettings { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 251; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 251; + public static final int MAX_SIZE = 251; + public float mass; + public float dragCoefficient; + public boolean invertedGravity; + public float velocityResistance; + public float jumpForce; + public float swimJumpForce; + public float jumpBufferDuration; + public float jumpBufferMaxYVelocity; + public float acceleration; + public float airDragMin; + public float airDragMax; + public float airDragMinSpeed; + public float airDragMaxSpeed; + public float airFrictionMin; + public float airFrictionMax; + public float airFrictionMinSpeed; + public float airFrictionMaxSpeed; + public float airSpeedMultiplier; + public float airControlMinSpeed; + public float airControlMaxSpeed; + public float airControlMinMultiplier; + public float airControlMaxMultiplier; + public float comboAirSpeedMultiplier; + public float baseSpeed; + public float climbSpeed; + public float climbSpeedLateral; + public float climbUpSprintSpeed; + public float climbDownSprintSpeed; + public float horizontalFlySpeed; + public float verticalFlySpeed; + public float maxSpeedMultiplier; + public float minSpeedMultiplier; + public float wishDirectionGravityX; + public float wishDirectionGravityY; + public float wishDirectionWeightX; + public float wishDirectionWeightY; + public boolean canFly; + public float collisionExpulsionForce; + public float forwardWalkSpeedMultiplier; + public float backwardWalkSpeedMultiplier; + public float strafeWalkSpeedMultiplier; + public float forwardRunSpeedMultiplier; + public float backwardRunSpeedMultiplier; + public float strafeRunSpeedMultiplier; + public float forwardCrouchSpeedMultiplier; + public float backwardCrouchSpeedMultiplier; + public float strafeCrouchSpeedMultiplier; + public float forwardSprintSpeedMultiplier; + public float variableJumpFallForce; + public float fallEffectDuration; + public float fallJumpForce; + public float fallMomentumLoss; + public float autoJumpObstacleSpeedLoss; + public float autoJumpObstacleSprintSpeedLoss; + public float autoJumpObstacleEffectDuration; + public float autoJumpObstacleSprintEffectDuration; + public float autoJumpObstacleMaxAngle; + public boolean autoJumpDisableJumping; + public float minSlideEntrySpeed; + public float slideExitSpeed; + public float minFallSpeedToEngageRoll; + public float maxFallSpeedToEngageRoll; + public float rollStartSpeedModifier; + public float rollExitSpeedModifier; + public float rollTimeToComplete; + + public MovementSettings() { + } + + public MovementSettings( + float mass, + float dragCoefficient, + boolean invertedGravity, + float velocityResistance, + float jumpForce, + float swimJumpForce, + float jumpBufferDuration, + float jumpBufferMaxYVelocity, + float acceleration, + float airDragMin, + float airDragMax, + float airDragMinSpeed, + float airDragMaxSpeed, + float airFrictionMin, + float airFrictionMax, + float airFrictionMinSpeed, + float airFrictionMaxSpeed, + float airSpeedMultiplier, + float airControlMinSpeed, + float airControlMaxSpeed, + float airControlMinMultiplier, + float airControlMaxMultiplier, + float comboAirSpeedMultiplier, + float baseSpeed, + float climbSpeed, + float climbSpeedLateral, + float climbUpSprintSpeed, + float climbDownSprintSpeed, + float horizontalFlySpeed, + float verticalFlySpeed, + float maxSpeedMultiplier, + float minSpeedMultiplier, + float wishDirectionGravityX, + float wishDirectionGravityY, + float wishDirectionWeightX, + float wishDirectionWeightY, + boolean canFly, + float collisionExpulsionForce, + float forwardWalkSpeedMultiplier, + float backwardWalkSpeedMultiplier, + float strafeWalkSpeedMultiplier, + float forwardRunSpeedMultiplier, + float backwardRunSpeedMultiplier, + float strafeRunSpeedMultiplier, + float forwardCrouchSpeedMultiplier, + float backwardCrouchSpeedMultiplier, + float strafeCrouchSpeedMultiplier, + float forwardSprintSpeedMultiplier, + float variableJumpFallForce, + float fallEffectDuration, + float fallJumpForce, + float fallMomentumLoss, + float autoJumpObstacleSpeedLoss, + float autoJumpObstacleSprintSpeedLoss, + float autoJumpObstacleEffectDuration, + float autoJumpObstacleSprintEffectDuration, + float autoJumpObstacleMaxAngle, + boolean autoJumpDisableJumping, + float minSlideEntrySpeed, + float slideExitSpeed, + float minFallSpeedToEngageRoll, + float maxFallSpeedToEngageRoll, + float rollStartSpeedModifier, + float rollExitSpeedModifier, + float rollTimeToComplete + ) { + this.mass = mass; + this.dragCoefficient = dragCoefficient; + this.invertedGravity = invertedGravity; + this.velocityResistance = velocityResistance; + this.jumpForce = jumpForce; + this.swimJumpForce = swimJumpForce; + this.jumpBufferDuration = jumpBufferDuration; + this.jumpBufferMaxYVelocity = jumpBufferMaxYVelocity; + this.acceleration = acceleration; + this.airDragMin = airDragMin; + this.airDragMax = airDragMax; + this.airDragMinSpeed = airDragMinSpeed; + this.airDragMaxSpeed = airDragMaxSpeed; + this.airFrictionMin = airFrictionMin; + this.airFrictionMax = airFrictionMax; + this.airFrictionMinSpeed = airFrictionMinSpeed; + this.airFrictionMaxSpeed = airFrictionMaxSpeed; + this.airSpeedMultiplier = airSpeedMultiplier; + this.airControlMinSpeed = airControlMinSpeed; + this.airControlMaxSpeed = airControlMaxSpeed; + this.airControlMinMultiplier = airControlMinMultiplier; + this.airControlMaxMultiplier = airControlMaxMultiplier; + this.comboAirSpeedMultiplier = comboAirSpeedMultiplier; + this.baseSpeed = baseSpeed; + this.climbSpeed = climbSpeed; + this.climbSpeedLateral = climbSpeedLateral; + this.climbUpSprintSpeed = climbUpSprintSpeed; + this.climbDownSprintSpeed = climbDownSprintSpeed; + this.horizontalFlySpeed = horizontalFlySpeed; + this.verticalFlySpeed = verticalFlySpeed; + this.maxSpeedMultiplier = maxSpeedMultiplier; + this.minSpeedMultiplier = minSpeedMultiplier; + this.wishDirectionGravityX = wishDirectionGravityX; + this.wishDirectionGravityY = wishDirectionGravityY; + this.wishDirectionWeightX = wishDirectionWeightX; + this.wishDirectionWeightY = wishDirectionWeightY; + this.canFly = canFly; + this.collisionExpulsionForce = collisionExpulsionForce; + this.forwardWalkSpeedMultiplier = forwardWalkSpeedMultiplier; + this.backwardWalkSpeedMultiplier = backwardWalkSpeedMultiplier; + this.strafeWalkSpeedMultiplier = strafeWalkSpeedMultiplier; + this.forwardRunSpeedMultiplier = forwardRunSpeedMultiplier; + this.backwardRunSpeedMultiplier = backwardRunSpeedMultiplier; + this.strafeRunSpeedMultiplier = strafeRunSpeedMultiplier; + this.forwardCrouchSpeedMultiplier = forwardCrouchSpeedMultiplier; + this.backwardCrouchSpeedMultiplier = backwardCrouchSpeedMultiplier; + this.strafeCrouchSpeedMultiplier = strafeCrouchSpeedMultiplier; + this.forwardSprintSpeedMultiplier = forwardSprintSpeedMultiplier; + this.variableJumpFallForce = variableJumpFallForce; + this.fallEffectDuration = fallEffectDuration; + this.fallJumpForce = fallJumpForce; + this.fallMomentumLoss = fallMomentumLoss; + this.autoJumpObstacleSpeedLoss = autoJumpObstacleSpeedLoss; + this.autoJumpObstacleSprintSpeedLoss = autoJumpObstacleSprintSpeedLoss; + this.autoJumpObstacleEffectDuration = autoJumpObstacleEffectDuration; + this.autoJumpObstacleSprintEffectDuration = autoJumpObstacleSprintEffectDuration; + this.autoJumpObstacleMaxAngle = autoJumpObstacleMaxAngle; + this.autoJumpDisableJumping = autoJumpDisableJumping; + this.minSlideEntrySpeed = minSlideEntrySpeed; + this.slideExitSpeed = slideExitSpeed; + this.minFallSpeedToEngageRoll = minFallSpeedToEngageRoll; + this.maxFallSpeedToEngageRoll = maxFallSpeedToEngageRoll; + this.rollStartSpeedModifier = rollStartSpeedModifier; + this.rollExitSpeedModifier = rollExitSpeedModifier; + this.rollTimeToComplete = rollTimeToComplete; + } + + public MovementSettings(@Nonnull MovementSettings other) { + this.mass = other.mass; + this.dragCoefficient = other.dragCoefficient; + this.invertedGravity = other.invertedGravity; + this.velocityResistance = other.velocityResistance; + this.jumpForce = other.jumpForce; + this.swimJumpForce = other.swimJumpForce; + this.jumpBufferDuration = other.jumpBufferDuration; + this.jumpBufferMaxYVelocity = other.jumpBufferMaxYVelocity; + this.acceleration = other.acceleration; + this.airDragMin = other.airDragMin; + this.airDragMax = other.airDragMax; + this.airDragMinSpeed = other.airDragMinSpeed; + this.airDragMaxSpeed = other.airDragMaxSpeed; + this.airFrictionMin = other.airFrictionMin; + this.airFrictionMax = other.airFrictionMax; + this.airFrictionMinSpeed = other.airFrictionMinSpeed; + this.airFrictionMaxSpeed = other.airFrictionMaxSpeed; + this.airSpeedMultiplier = other.airSpeedMultiplier; + this.airControlMinSpeed = other.airControlMinSpeed; + this.airControlMaxSpeed = other.airControlMaxSpeed; + this.airControlMinMultiplier = other.airControlMinMultiplier; + this.airControlMaxMultiplier = other.airControlMaxMultiplier; + this.comboAirSpeedMultiplier = other.comboAirSpeedMultiplier; + this.baseSpeed = other.baseSpeed; + this.climbSpeed = other.climbSpeed; + this.climbSpeedLateral = other.climbSpeedLateral; + this.climbUpSprintSpeed = other.climbUpSprintSpeed; + this.climbDownSprintSpeed = other.climbDownSprintSpeed; + this.horizontalFlySpeed = other.horizontalFlySpeed; + this.verticalFlySpeed = other.verticalFlySpeed; + this.maxSpeedMultiplier = other.maxSpeedMultiplier; + this.minSpeedMultiplier = other.minSpeedMultiplier; + this.wishDirectionGravityX = other.wishDirectionGravityX; + this.wishDirectionGravityY = other.wishDirectionGravityY; + this.wishDirectionWeightX = other.wishDirectionWeightX; + this.wishDirectionWeightY = other.wishDirectionWeightY; + this.canFly = other.canFly; + this.collisionExpulsionForce = other.collisionExpulsionForce; + this.forwardWalkSpeedMultiplier = other.forwardWalkSpeedMultiplier; + this.backwardWalkSpeedMultiplier = other.backwardWalkSpeedMultiplier; + this.strafeWalkSpeedMultiplier = other.strafeWalkSpeedMultiplier; + this.forwardRunSpeedMultiplier = other.forwardRunSpeedMultiplier; + this.backwardRunSpeedMultiplier = other.backwardRunSpeedMultiplier; + this.strafeRunSpeedMultiplier = other.strafeRunSpeedMultiplier; + this.forwardCrouchSpeedMultiplier = other.forwardCrouchSpeedMultiplier; + this.backwardCrouchSpeedMultiplier = other.backwardCrouchSpeedMultiplier; + this.strafeCrouchSpeedMultiplier = other.strafeCrouchSpeedMultiplier; + this.forwardSprintSpeedMultiplier = other.forwardSprintSpeedMultiplier; + this.variableJumpFallForce = other.variableJumpFallForce; + this.fallEffectDuration = other.fallEffectDuration; + this.fallJumpForce = other.fallJumpForce; + this.fallMomentumLoss = other.fallMomentumLoss; + this.autoJumpObstacleSpeedLoss = other.autoJumpObstacleSpeedLoss; + this.autoJumpObstacleSprintSpeedLoss = other.autoJumpObstacleSprintSpeedLoss; + this.autoJumpObstacleEffectDuration = other.autoJumpObstacleEffectDuration; + this.autoJumpObstacleSprintEffectDuration = other.autoJumpObstacleSprintEffectDuration; + this.autoJumpObstacleMaxAngle = other.autoJumpObstacleMaxAngle; + this.autoJumpDisableJumping = other.autoJumpDisableJumping; + this.minSlideEntrySpeed = other.minSlideEntrySpeed; + this.slideExitSpeed = other.slideExitSpeed; + this.minFallSpeedToEngageRoll = other.minFallSpeedToEngageRoll; + this.maxFallSpeedToEngageRoll = other.maxFallSpeedToEngageRoll; + this.rollStartSpeedModifier = other.rollStartSpeedModifier; + this.rollExitSpeedModifier = other.rollExitSpeedModifier; + this.rollTimeToComplete = other.rollTimeToComplete; + } + + @Nonnull + public static MovementSettings deserialize(@Nonnull ByteBuf buf, int offset) { + MovementSettings obj = new MovementSettings(); + obj.mass = buf.getFloatLE(offset + 0); + obj.dragCoefficient = buf.getFloatLE(offset + 4); + obj.invertedGravity = buf.getByte(offset + 8) != 0; + obj.velocityResistance = buf.getFloatLE(offset + 9); + obj.jumpForce = buf.getFloatLE(offset + 13); + obj.swimJumpForce = buf.getFloatLE(offset + 17); + obj.jumpBufferDuration = buf.getFloatLE(offset + 21); + obj.jumpBufferMaxYVelocity = buf.getFloatLE(offset + 25); + obj.acceleration = buf.getFloatLE(offset + 29); + obj.airDragMin = buf.getFloatLE(offset + 33); + obj.airDragMax = buf.getFloatLE(offset + 37); + obj.airDragMinSpeed = buf.getFloatLE(offset + 41); + obj.airDragMaxSpeed = buf.getFloatLE(offset + 45); + obj.airFrictionMin = buf.getFloatLE(offset + 49); + obj.airFrictionMax = buf.getFloatLE(offset + 53); + obj.airFrictionMinSpeed = buf.getFloatLE(offset + 57); + obj.airFrictionMaxSpeed = buf.getFloatLE(offset + 61); + obj.airSpeedMultiplier = buf.getFloatLE(offset + 65); + obj.airControlMinSpeed = buf.getFloatLE(offset + 69); + obj.airControlMaxSpeed = buf.getFloatLE(offset + 73); + obj.airControlMinMultiplier = buf.getFloatLE(offset + 77); + obj.airControlMaxMultiplier = buf.getFloatLE(offset + 81); + obj.comboAirSpeedMultiplier = buf.getFloatLE(offset + 85); + obj.baseSpeed = buf.getFloatLE(offset + 89); + obj.climbSpeed = buf.getFloatLE(offset + 93); + obj.climbSpeedLateral = buf.getFloatLE(offset + 97); + obj.climbUpSprintSpeed = buf.getFloatLE(offset + 101); + obj.climbDownSprintSpeed = buf.getFloatLE(offset + 105); + obj.horizontalFlySpeed = buf.getFloatLE(offset + 109); + obj.verticalFlySpeed = buf.getFloatLE(offset + 113); + obj.maxSpeedMultiplier = buf.getFloatLE(offset + 117); + obj.minSpeedMultiplier = buf.getFloatLE(offset + 121); + obj.wishDirectionGravityX = buf.getFloatLE(offset + 125); + obj.wishDirectionGravityY = buf.getFloatLE(offset + 129); + obj.wishDirectionWeightX = buf.getFloatLE(offset + 133); + obj.wishDirectionWeightY = buf.getFloatLE(offset + 137); + obj.canFly = buf.getByte(offset + 141) != 0; + obj.collisionExpulsionForce = buf.getFloatLE(offset + 142); + obj.forwardWalkSpeedMultiplier = buf.getFloatLE(offset + 146); + obj.backwardWalkSpeedMultiplier = buf.getFloatLE(offset + 150); + obj.strafeWalkSpeedMultiplier = buf.getFloatLE(offset + 154); + obj.forwardRunSpeedMultiplier = buf.getFloatLE(offset + 158); + obj.backwardRunSpeedMultiplier = buf.getFloatLE(offset + 162); + obj.strafeRunSpeedMultiplier = buf.getFloatLE(offset + 166); + obj.forwardCrouchSpeedMultiplier = buf.getFloatLE(offset + 170); + obj.backwardCrouchSpeedMultiplier = buf.getFloatLE(offset + 174); + obj.strafeCrouchSpeedMultiplier = buf.getFloatLE(offset + 178); + obj.forwardSprintSpeedMultiplier = buf.getFloatLE(offset + 182); + obj.variableJumpFallForce = buf.getFloatLE(offset + 186); + obj.fallEffectDuration = buf.getFloatLE(offset + 190); + obj.fallJumpForce = buf.getFloatLE(offset + 194); + obj.fallMomentumLoss = buf.getFloatLE(offset + 198); + obj.autoJumpObstacleSpeedLoss = buf.getFloatLE(offset + 202); + obj.autoJumpObstacleSprintSpeedLoss = buf.getFloatLE(offset + 206); + obj.autoJumpObstacleEffectDuration = buf.getFloatLE(offset + 210); + obj.autoJumpObstacleSprintEffectDuration = buf.getFloatLE(offset + 214); + obj.autoJumpObstacleMaxAngle = buf.getFloatLE(offset + 218); + obj.autoJumpDisableJumping = buf.getByte(offset + 222) != 0; + obj.minSlideEntrySpeed = buf.getFloatLE(offset + 223); + obj.slideExitSpeed = buf.getFloatLE(offset + 227); + obj.minFallSpeedToEngageRoll = buf.getFloatLE(offset + 231); + obj.maxFallSpeedToEngageRoll = buf.getFloatLE(offset + 235); + obj.rollStartSpeedModifier = buf.getFloatLE(offset + 239); + obj.rollExitSpeedModifier = buf.getFloatLE(offset + 243); + obj.rollTimeToComplete = buf.getFloatLE(offset + 247); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 251; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.mass); + buf.writeFloatLE(this.dragCoefficient); + buf.writeByte(this.invertedGravity ? 1 : 0); + buf.writeFloatLE(this.velocityResistance); + buf.writeFloatLE(this.jumpForce); + buf.writeFloatLE(this.swimJumpForce); + buf.writeFloatLE(this.jumpBufferDuration); + buf.writeFloatLE(this.jumpBufferMaxYVelocity); + buf.writeFloatLE(this.acceleration); + buf.writeFloatLE(this.airDragMin); + buf.writeFloatLE(this.airDragMax); + buf.writeFloatLE(this.airDragMinSpeed); + buf.writeFloatLE(this.airDragMaxSpeed); + buf.writeFloatLE(this.airFrictionMin); + buf.writeFloatLE(this.airFrictionMax); + buf.writeFloatLE(this.airFrictionMinSpeed); + buf.writeFloatLE(this.airFrictionMaxSpeed); + buf.writeFloatLE(this.airSpeedMultiplier); + buf.writeFloatLE(this.airControlMinSpeed); + buf.writeFloatLE(this.airControlMaxSpeed); + buf.writeFloatLE(this.airControlMinMultiplier); + buf.writeFloatLE(this.airControlMaxMultiplier); + buf.writeFloatLE(this.comboAirSpeedMultiplier); + buf.writeFloatLE(this.baseSpeed); + buf.writeFloatLE(this.climbSpeed); + buf.writeFloatLE(this.climbSpeedLateral); + buf.writeFloatLE(this.climbUpSprintSpeed); + buf.writeFloatLE(this.climbDownSprintSpeed); + buf.writeFloatLE(this.horizontalFlySpeed); + buf.writeFloatLE(this.verticalFlySpeed); + buf.writeFloatLE(this.maxSpeedMultiplier); + buf.writeFloatLE(this.minSpeedMultiplier); + buf.writeFloatLE(this.wishDirectionGravityX); + buf.writeFloatLE(this.wishDirectionGravityY); + buf.writeFloatLE(this.wishDirectionWeightX); + buf.writeFloatLE(this.wishDirectionWeightY); + buf.writeByte(this.canFly ? 1 : 0); + buf.writeFloatLE(this.collisionExpulsionForce); + buf.writeFloatLE(this.forwardWalkSpeedMultiplier); + buf.writeFloatLE(this.backwardWalkSpeedMultiplier); + buf.writeFloatLE(this.strafeWalkSpeedMultiplier); + buf.writeFloatLE(this.forwardRunSpeedMultiplier); + buf.writeFloatLE(this.backwardRunSpeedMultiplier); + buf.writeFloatLE(this.strafeRunSpeedMultiplier); + buf.writeFloatLE(this.forwardCrouchSpeedMultiplier); + buf.writeFloatLE(this.backwardCrouchSpeedMultiplier); + buf.writeFloatLE(this.strafeCrouchSpeedMultiplier); + buf.writeFloatLE(this.forwardSprintSpeedMultiplier); + buf.writeFloatLE(this.variableJumpFallForce); + buf.writeFloatLE(this.fallEffectDuration); + buf.writeFloatLE(this.fallJumpForce); + buf.writeFloatLE(this.fallMomentumLoss); + buf.writeFloatLE(this.autoJumpObstacleSpeedLoss); + buf.writeFloatLE(this.autoJumpObstacleSprintSpeedLoss); + buf.writeFloatLE(this.autoJumpObstacleEffectDuration); + buf.writeFloatLE(this.autoJumpObstacleSprintEffectDuration); + buf.writeFloatLE(this.autoJumpObstacleMaxAngle); + buf.writeByte(this.autoJumpDisableJumping ? 1 : 0); + buf.writeFloatLE(this.minSlideEntrySpeed); + buf.writeFloatLE(this.slideExitSpeed); + buf.writeFloatLE(this.minFallSpeedToEngageRoll); + buf.writeFloatLE(this.maxFallSpeedToEngageRoll); + buf.writeFloatLE(this.rollStartSpeedModifier); + buf.writeFloatLE(this.rollExitSpeedModifier); + buf.writeFloatLE(this.rollTimeToComplete); + } + + public int computeSize() { + return 251; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 251 ? ValidationResult.error("Buffer too small: expected at least 251 bytes") : ValidationResult.OK; + } + + public MovementSettings clone() { + MovementSettings copy = new MovementSettings(); + copy.mass = this.mass; + copy.dragCoefficient = this.dragCoefficient; + copy.invertedGravity = this.invertedGravity; + copy.velocityResistance = this.velocityResistance; + copy.jumpForce = this.jumpForce; + copy.swimJumpForce = this.swimJumpForce; + copy.jumpBufferDuration = this.jumpBufferDuration; + copy.jumpBufferMaxYVelocity = this.jumpBufferMaxYVelocity; + copy.acceleration = this.acceleration; + copy.airDragMin = this.airDragMin; + copy.airDragMax = this.airDragMax; + copy.airDragMinSpeed = this.airDragMinSpeed; + copy.airDragMaxSpeed = this.airDragMaxSpeed; + copy.airFrictionMin = this.airFrictionMin; + copy.airFrictionMax = this.airFrictionMax; + copy.airFrictionMinSpeed = this.airFrictionMinSpeed; + copy.airFrictionMaxSpeed = this.airFrictionMaxSpeed; + copy.airSpeedMultiplier = this.airSpeedMultiplier; + copy.airControlMinSpeed = this.airControlMinSpeed; + copy.airControlMaxSpeed = this.airControlMaxSpeed; + copy.airControlMinMultiplier = this.airControlMinMultiplier; + copy.airControlMaxMultiplier = this.airControlMaxMultiplier; + copy.comboAirSpeedMultiplier = this.comboAirSpeedMultiplier; + copy.baseSpeed = this.baseSpeed; + copy.climbSpeed = this.climbSpeed; + copy.climbSpeedLateral = this.climbSpeedLateral; + copy.climbUpSprintSpeed = this.climbUpSprintSpeed; + copy.climbDownSprintSpeed = this.climbDownSprintSpeed; + copy.horizontalFlySpeed = this.horizontalFlySpeed; + copy.verticalFlySpeed = this.verticalFlySpeed; + copy.maxSpeedMultiplier = this.maxSpeedMultiplier; + copy.minSpeedMultiplier = this.minSpeedMultiplier; + copy.wishDirectionGravityX = this.wishDirectionGravityX; + copy.wishDirectionGravityY = this.wishDirectionGravityY; + copy.wishDirectionWeightX = this.wishDirectionWeightX; + copy.wishDirectionWeightY = this.wishDirectionWeightY; + copy.canFly = this.canFly; + copy.collisionExpulsionForce = this.collisionExpulsionForce; + copy.forwardWalkSpeedMultiplier = this.forwardWalkSpeedMultiplier; + copy.backwardWalkSpeedMultiplier = this.backwardWalkSpeedMultiplier; + copy.strafeWalkSpeedMultiplier = this.strafeWalkSpeedMultiplier; + copy.forwardRunSpeedMultiplier = this.forwardRunSpeedMultiplier; + copy.backwardRunSpeedMultiplier = this.backwardRunSpeedMultiplier; + copy.strafeRunSpeedMultiplier = this.strafeRunSpeedMultiplier; + copy.forwardCrouchSpeedMultiplier = this.forwardCrouchSpeedMultiplier; + copy.backwardCrouchSpeedMultiplier = this.backwardCrouchSpeedMultiplier; + copy.strafeCrouchSpeedMultiplier = this.strafeCrouchSpeedMultiplier; + copy.forwardSprintSpeedMultiplier = this.forwardSprintSpeedMultiplier; + copy.variableJumpFallForce = this.variableJumpFallForce; + copy.fallEffectDuration = this.fallEffectDuration; + copy.fallJumpForce = this.fallJumpForce; + copy.fallMomentumLoss = this.fallMomentumLoss; + copy.autoJumpObstacleSpeedLoss = this.autoJumpObstacleSpeedLoss; + copy.autoJumpObstacleSprintSpeedLoss = this.autoJumpObstacleSprintSpeedLoss; + copy.autoJumpObstacleEffectDuration = this.autoJumpObstacleEffectDuration; + copy.autoJumpObstacleSprintEffectDuration = this.autoJumpObstacleSprintEffectDuration; + copy.autoJumpObstacleMaxAngle = this.autoJumpObstacleMaxAngle; + copy.autoJumpDisableJumping = this.autoJumpDisableJumping; + copy.minSlideEntrySpeed = this.minSlideEntrySpeed; + copy.slideExitSpeed = this.slideExitSpeed; + copy.minFallSpeedToEngageRoll = this.minFallSpeedToEngageRoll; + copy.maxFallSpeedToEngageRoll = this.maxFallSpeedToEngageRoll; + copy.rollStartSpeedModifier = this.rollStartSpeedModifier; + copy.rollExitSpeedModifier = this.rollExitSpeedModifier; + copy.rollTimeToComplete = this.rollTimeToComplete; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof MovementSettings other) + ? false + : this.mass == other.mass + && this.dragCoefficient == other.dragCoefficient + && this.invertedGravity == other.invertedGravity + && this.velocityResistance == other.velocityResistance + && this.jumpForce == other.jumpForce + && this.swimJumpForce == other.swimJumpForce + && this.jumpBufferDuration == other.jumpBufferDuration + && this.jumpBufferMaxYVelocity == other.jumpBufferMaxYVelocity + && this.acceleration == other.acceleration + && this.airDragMin == other.airDragMin + && this.airDragMax == other.airDragMax + && this.airDragMinSpeed == other.airDragMinSpeed + && this.airDragMaxSpeed == other.airDragMaxSpeed + && this.airFrictionMin == other.airFrictionMin + && this.airFrictionMax == other.airFrictionMax + && this.airFrictionMinSpeed == other.airFrictionMinSpeed + && this.airFrictionMaxSpeed == other.airFrictionMaxSpeed + && this.airSpeedMultiplier == other.airSpeedMultiplier + && this.airControlMinSpeed == other.airControlMinSpeed + && this.airControlMaxSpeed == other.airControlMaxSpeed + && this.airControlMinMultiplier == other.airControlMinMultiplier + && this.airControlMaxMultiplier == other.airControlMaxMultiplier + && this.comboAirSpeedMultiplier == other.comboAirSpeedMultiplier + && this.baseSpeed == other.baseSpeed + && this.climbSpeed == other.climbSpeed + && this.climbSpeedLateral == other.climbSpeedLateral + && this.climbUpSprintSpeed == other.climbUpSprintSpeed + && this.climbDownSprintSpeed == other.climbDownSprintSpeed + && this.horizontalFlySpeed == other.horizontalFlySpeed + && this.verticalFlySpeed == other.verticalFlySpeed + && this.maxSpeedMultiplier == other.maxSpeedMultiplier + && this.minSpeedMultiplier == other.minSpeedMultiplier + && this.wishDirectionGravityX == other.wishDirectionGravityX + && this.wishDirectionGravityY == other.wishDirectionGravityY + && this.wishDirectionWeightX == other.wishDirectionWeightX + && this.wishDirectionWeightY == other.wishDirectionWeightY + && this.canFly == other.canFly + && this.collisionExpulsionForce == other.collisionExpulsionForce + && this.forwardWalkSpeedMultiplier == other.forwardWalkSpeedMultiplier + && this.backwardWalkSpeedMultiplier == other.backwardWalkSpeedMultiplier + && this.strafeWalkSpeedMultiplier == other.strafeWalkSpeedMultiplier + && this.forwardRunSpeedMultiplier == other.forwardRunSpeedMultiplier + && this.backwardRunSpeedMultiplier == other.backwardRunSpeedMultiplier + && this.strafeRunSpeedMultiplier == other.strafeRunSpeedMultiplier + && this.forwardCrouchSpeedMultiplier == other.forwardCrouchSpeedMultiplier + && this.backwardCrouchSpeedMultiplier == other.backwardCrouchSpeedMultiplier + && this.strafeCrouchSpeedMultiplier == other.strafeCrouchSpeedMultiplier + && this.forwardSprintSpeedMultiplier == other.forwardSprintSpeedMultiplier + && this.variableJumpFallForce == other.variableJumpFallForce + && this.fallEffectDuration == other.fallEffectDuration + && this.fallJumpForce == other.fallJumpForce + && this.fallMomentumLoss == other.fallMomentumLoss + && this.autoJumpObstacleSpeedLoss == other.autoJumpObstacleSpeedLoss + && this.autoJumpObstacleSprintSpeedLoss == other.autoJumpObstacleSprintSpeedLoss + && this.autoJumpObstacleEffectDuration == other.autoJumpObstacleEffectDuration + && this.autoJumpObstacleSprintEffectDuration == other.autoJumpObstacleSprintEffectDuration + && this.autoJumpObstacleMaxAngle == other.autoJumpObstacleMaxAngle + && this.autoJumpDisableJumping == other.autoJumpDisableJumping + && this.minSlideEntrySpeed == other.minSlideEntrySpeed + && this.slideExitSpeed == other.slideExitSpeed + && this.minFallSpeedToEngageRoll == other.minFallSpeedToEngageRoll + && this.maxFallSpeedToEngageRoll == other.maxFallSpeedToEngageRoll + && this.rollStartSpeedModifier == other.rollStartSpeedModifier + && this.rollExitSpeedModifier == other.rollExitSpeedModifier + && this.rollTimeToComplete == other.rollTimeToComplete; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.mass, + this.dragCoefficient, + this.invertedGravity, + this.velocityResistance, + this.jumpForce, + this.swimJumpForce, + this.jumpBufferDuration, + this.jumpBufferMaxYVelocity, + this.acceleration, + this.airDragMin, + this.airDragMax, + this.airDragMinSpeed, + this.airDragMaxSpeed, + this.airFrictionMin, + this.airFrictionMax, + this.airFrictionMinSpeed, + this.airFrictionMaxSpeed, + this.airSpeedMultiplier, + this.airControlMinSpeed, + this.airControlMaxSpeed, + this.airControlMinMultiplier, + this.airControlMaxMultiplier, + this.comboAirSpeedMultiplier, + this.baseSpeed, + this.climbSpeed, + this.climbSpeedLateral, + this.climbUpSprintSpeed, + this.climbDownSprintSpeed, + this.horizontalFlySpeed, + this.verticalFlySpeed, + this.maxSpeedMultiplier, + this.minSpeedMultiplier, + this.wishDirectionGravityX, + this.wishDirectionGravityY, + this.wishDirectionWeightX, + this.wishDirectionWeightY, + this.canFly, + this.collisionExpulsionForce, + this.forwardWalkSpeedMultiplier, + this.backwardWalkSpeedMultiplier, + this.strafeWalkSpeedMultiplier, + this.forwardRunSpeedMultiplier, + this.backwardRunSpeedMultiplier, + this.strafeRunSpeedMultiplier, + this.forwardCrouchSpeedMultiplier, + this.backwardCrouchSpeedMultiplier, + this.strafeCrouchSpeedMultiplier, + this.forwardSprintSpeedMultiplier, + this.variableJumpFallForce, + this.fallEffectDuration, + this.fallJumpForce, + this.fallMomentumLoss, + this.autoJumpObstacleSpeedLoss, + this.autoJumpObstacleSprintSpeedLoss, + this.autoJumpObstacleEffectDuration, + this.autoJumpObstacleSprintEffectDuration, + this.autoJumpObstacleMaxAngle, + this.autoJumpDisableJumping, + this.minSlideEntrySpeed, + this.slideExitSpeed, + this.minFallSpeedToEngageRoll, + this.maxFallSpeedToEngageRoll, + this.rollStartSpeedModifier, + this.rollExitSpeedModifier, + this.rollTimeToComplete + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/MovementStates.java b/src/com/hypixel/hytale/protocol/MovementStates.java new file mode 100644 index 0000000..f38bf62 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MovementStates.java @@ -0,0 +1,264 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class MovementStates { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 22; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 22; + public static final int MAX_SIZE = 22; + public boolean idle; + public boolean horizontalIdle; + public boolean jumping; + public boolean flying; + public boolean walking; + public boolean running; + public boolean sprinting; + public boolean crouching; + public boolean forcedCrouching; + public boolean falling; + public boolean climbing; + public boolean inFluid; + public boolean swimming; + public boolean swimJumping; + public boolean onGround; + public boolean mantling; + public boolean sliding; + public boolean mounting; + public boolean rolling; + public boolean sitting; + public boolean gliding; + public boolean sleeping; + + public MovementStates() { + } + + public MovementStates( + boolean idle, + boolean horizontalIdle, + boolean jumping, + boolean flying, + boolean walking, + boolean running, + boolean sprinting, + boolean crouching, + boolean forcedCrouching, + boolean falling, + boolean climbing, + boolean inFluid, + boolean swimming, + boolean swimJumping, + boolean onGround, + boolean mantling, + boolean sliding, + boolean mounting, + boolean rolling, + boolean sitting, + boolean gliding, + boolean sleeping + ) { + this.idle = idle; + this.horizontalIdle = horizontalIdle; + this.jumping = jumping; + this.flying = flying; + this.walking = walking; + this.running = running; + this.sprinting = sprinting; + this.crouching = crouching; + this.forcedCrouching = forcedCrouching; + this.falling = falling; + this.climbing = climbing; + this.inFluid = inFluid; + this.swimming = swimming; + this.swimJumping = swimJumping; + this.onGround = onGround; + this.mantling = mantling; + this.sliding = sliding; + this.mounting = mounting; + this.rolling = rolling; + this.sitting = sitting; + this.gliding = gliding; + this.sleeping = sleeping; + } + + public MovementStates(@Nonnull MovementStates other) { + this.idle = other.idle; + this.horizontalIdle = other.horizontalIdle; + this.jumping = other.jumping; + this.flying = other.flying; + this.walking = other.walking; + this.running = other.running; + this.sprinting = other.sprinting; + this.crouching = other.crouching; + this.forcedCrouching = other.forcedCrouching; + this.falling = other.falling; + this.climbing = other.climbing; + this.inFluid = other.inFluid; + this.swimming = other.swimming; + this.swimJumping = other.swimJumping; + this.onGround = other.onGround; + this.mantling = other.mantling; + this.sliding = other.sliding; + this.mounting = other.mounting; + this.rolling = other.rolling; + this.sitting = other.sitting; + this.gliding = other.gliding; + this.sleeping = other.sleeping; + } + + @Nonnull + public static MovementStates deserialize(@Nonnull ByteBuf buf, int offset) { + MovementStates obj = new MovementStates(); + obj.idle = buf.getByte(offset + 0) != 0; + obj.horizontalIdle = buf.getByte(offset + 1) != 0; + obj.jumping = buf.getByte(offset + 2) != 0; + obj.flying = buf.getByte(offset + 3) != 0; + obj.walking = buf.getByte(offset + 4) != 0; + obj.running = buf.getByte(offset + 5) != 0; + obj.sprinting = buf.getByte(offset + 6) != 0; + obj.crouching = buf.getByte(offset + 7) != 0; + obj.forcedCrouching = buf.getByte(offset + 8) != 0; + obj.falling = buf.getByte(offset + 9) != 0; + obj.climbing = buf.getByte(offset + 10) != 0; + obj.inFluid = buf.getByte(offset + 11) != 0; + obj.swimming = buf.getByte(offset + 12) != 0; + obj.swimJumping = buf.getByte(offset + 13) != 0; + obj.onGround = buf.getByte(offset + 14) != 0; + obj.mantling = buf.getByte(offset + 15) != 0; + obj.sliding = buf.getByte(offset + 16) != 0; + obj.mounting = buf.getByte(offset + 17) != 0; + obj.rolling = buf.getByte(offset + 18) != 0; + obj.sitting = buf.getByte(offset + 19) != 0; + obj.gliding = buf.getByte(offset + 20) != 0; + obj.sleeping = buf.getByte(offset + 21) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 22; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.idle ? 1 : 0); + buf.writeByte(this.horizontalIdle ? 1 : 0); + buf.writeByte(this.jumping ? 1 : 0); + buf.writeByte(this.flying ? 1 : 0); + buf.writeByte(this.walking ? 1 : 0); + buf.writeByte(this.running ? 1 : 0); + buf.writeByte(this.sprinting ? 1 : 0); + buf.writeByte(this.crouching ? 1 : 0); + buf.writeByte(this.forcedCrouching ? 1 : 0); + buf.writeByte(this.falling ? 1 : 0); + buf.writeByte(this.climbing ? 1 : 0); + buf.writeByte(this.inFluid ? 1 : 0); + buf.writeByte(this.swimming ? 1 : 0); + buf.writeByte(this.swimJumping ? 1 : 0); + buf.writeByte(this.onGround ? 1 : 0); + buf.writeByte(this.mantling ? 1 : 0); + buf.writeByte(this.sliding ? 1 : 0); + buf.writeByte(this.mounting ? 1 : 0); + buf.writeByte(this.rolling ? 1 : 0); + buf.writeByte(this.sitting ? 1 : 0); + buf.writeByte(this.gliding ? 1 : 0); + buf.writeByte(this.sleeping ? 1 : 0); + } + + public int computeSize() { + return 22; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 22 ? ValidationResult.error("Buffer too small: expected at least 22 bytes") : ValidationResult.OK; + } + + public MovementStates clone() { + MovementStates copy = new MovementStates(); + copy.idle = this.idle; + copy.horizontalIdle = this.horizontalIdle; + copy.jumping = this.jumping; + copy.flying = this.flying; + copy.walking = this.walking; + copy.running = this.running; + copy.sprinting = this.sprinting; + copy.crouching = this.crouching; + copy.forcedCrouching = this.forcedCrouching; + copy.falling = this.falling; + copy.climbing = this.climbing; + copy.inFluid = this.inFluid; + copy.swimming = this.swimming; + copy.swimJumping = this.swimJumping; + copy.onGround = this.onGround; + copy.mantling = this.mantling; + copy.sliding = this.sliding; + copy.mounting = this.mounting; + copy.rolling = this.rolling; + copy.sitting = this.sitting; + copy.gliding = this.gliding; + copy.sleeping = this.sleeping; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof MovementStates other) + ? false + : this.idle == other.idle + && this.horizontalIdle == other.horizontalIdle + && this.jumping == other.jumping + && this.flying == other.flying + && this.walking == other.walking + && this.running == other.running + && this.sprinting == other.sprinting + && this.crouching == other.crouching + && this.forcedCrouching == other.forcedCrouching + && this.falling == other.falling + && this.climbing == other.climbing + && this.inFluid == other.inFluid + && this.swimming == other.swimming + && this.swimJumping == other.swimJumping + && this.onGround == other.onGround + && this.mantling == other.mantling + && this.sliding == other.sliding + && this.mounting == other.mounting + && this.rolling == other.rolling + && this.sitting == other.sitting + && this.gliding == other.gliding + && this.sleeping == other.sleeping; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.idle, + this.horizontalIdle, + this.jumping, + this.flying, + this.walking, + this.running, + this.sprinting, + this.crouching, + this.forcedCrouching, + this.falling, + this.climbing, + this.inFluid, + this.swimming, + this.swimJumping, + this.onGround, + this.mantling, + this.sliding, + this.mounting, + this.rolling, + this.sitting, + this.gliding, + this.sleeping + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/MovementType.java b/src/com/hypixel/hytale/protocol/MovementType.java new file mode 100644 index 0000000..86c483b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/MovementType.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum MovementType { + None(0), + Idle(1), + Crouching(2), + Walking(3), + Running(4), + Sprinting(5), + Climbing(6), + Swimming(7), + Flying(8), + Sliding(9), + Rolling(10), + Mounting(11), + SprintMounting(12); + + public static final MovementType[] VALUES = values(); + private final int value; + + private MovementType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static MovementType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("MovementType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/Nameplate.java b/src/com/hypixel/hytale/protocol/Nameplate.java new file mode 100644 index 0000000..1055645 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Nameplate.java @@ -0,0 +1,133 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Nameplate { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 16384006; + @Nullable + public String text; + + public Nameplate() { + } + + public Nameplate(@Nullable String text) { + this.text = text; + } + + public Nameplate(@Nonnull Nameplate other) { + this.text = other.text; + } + + @Nonnull + public static Nameplate deserialize(@Nonnull ByteBuf buf, int offset) { + Nameplate obj = new Nameplate(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int textLen = VarInt.peek(buf, pos); + if (textLen < 0) { + throw ProtocolException.negativeLength("Text", textLen); + } + + if (textLen > 4096000) { + throw ProtocolException.stringTooLong("Text", textLen, 4096000); + } + + int textVarLen = VarInt.length(buf, pos); + obj.text = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += textVarLen + textLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.text != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.text != null) { + PacketIO.writeVarString(buf, this.text, 4096000); + } + } + + public int computeSize() { + int size = 1; + if (this.text != null) { + size += PacketIO.stringSize(this.text); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int textLen = VarInt.peek(buffer, pos); + if (textLen < 0) { + return ValidationResult.error("Invalid string length for Text"); + } + + if (textLen > 4096000) { + return ValidationResult.error("Text exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += textLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Text"); + } + } + + return ValidationResult.OK; + } + } + + public Nameplate clone() { + Nameplate copy = new Nameplate(); + copy.text = this.text; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof Nameplate other ? Objects.equals(this.text, other.text) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.text); + } +} diff --git a/src/com/hypixel/hytale/protocol/NearFar.java b/src/com/hypixel/hytale/protocol/NearFar.java new file mode 100644 index 0000000..6ef93bb --- /dev/null +++ b/src/com/hypixel/hytale/protocol/NearFar.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class NearFar { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public float near; + public float far; + + public NearFar() { + } + + public NearFar(float near, float far) { + this.near = near; + this.far = far; + } + + public NearFar(@Nonnull NearFar other) { + this.near = other.near; + this.far = other.far; + } + + @Nonnull + public static NearFar deserialize(@Nonnull ByteBuf buf, int offset) { + NearFar obj = new NearFar(); + obj.near = buf.getFloatLE(offset + 0); + obj.far = buf.getFloatLE(offset + 4); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.near); + buf.writeFloatLE(this.far); + } + + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public NearFar clone() { + NearFar copy = new NearFar(); + copy.near = this.near; + copy.far = this.far; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof NearFar other) ? false : this.near == other.near && this.far == other.far; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.near, this.far); + } +} diff --git a/src/com/hypixel/hytale/protocol/NoiseConfig.java b/src/com/hypixel/hytale/protocol/NoiseConfig.java new file mode 100644 index 0000000..aaeb8f1 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/NoiseConfig.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NoiseConfig { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 23; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 23; + public static final int MAX_SIZE = 23; + public int seed; + @Nonnull + public NoiseType type = NoiseType.Sin; + public float frequency; + public float amplitude; + @Nullable + public ClampConfig clamp; + + public NoiseConfig() { + } + + public NoiseConfig(int seed, @Nonnull NoiseType type, float frequency, float amplitude, @Nullable ClampConfig clamp) { + this.seed = seed; + this.type = type; + this.frequency = frequency; + this.amplitude = amplitude; + this.clamp = clamp; + } + + public NoiseConfig(@Nonnull NoiseConfig other) { + this.seed = other.seed; + this.type = other.type; + this.frequency = other.frequency; + this.amplitude = other.amplitude; + this.clamp = other.clamp; + } + + @Nonnull + public static NoiseConfig deserialize(@Nonnull ByteBuf buf, int offset) { + NoiseConfig obj = new NoiseConfig(); + byte nullBits = buf.getByte(offset); + obj.seed = buf.getIntLE(offset + 1); + obj.type = NoiseType.fromValue(buf.getByte(offset + 5)); + obj.frequency = buf.getFloatLE(offset + 6); + obj.amplitude = buf.getFloatLE(offset + 10); + if ((nullBits & 1) != 0) { + obj.clamp = ClampConfig.deserialize(buf, offset + 14); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 23; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.clamp != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.seed); + buf.writeByte(this.type.getValue()); + buf.writeFloatLE(this.frequency); + buf.writeFloatLE(this.amplitude); + if (this.clamp != null) { + this.clamp.serialize(buf); + } else { + buf.writeZero(9); + } + } + + public int computeSize() { + return 23; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 23 ? ValidationResult.error("Buffer too small: expected at least 23 bytes") : ValidationResult.OK; + } + + public NoiseConfig clone() { + NoiseConfig copy = new NoiseConfig(); + copy.seed = this.seed; + copy.type = this.type; + copy.frequency = this.frequency; + copy.amplitude = this.amplitude; + copy.clamp = this.clamp != null ? this.clamp.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof NoiseConfig other) + ? false + : this.seed == other.seed + && Objects.equals(this.type, other.type) + && this.frequency == other.frequency + && this.amplitude == other.amplitude + && Objects.equals(this.clamp, other.clamp); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.seed, this.type, this.frequency, this.amplitude, this.clamp); + } +} diff --git a/src/com/hypixel/hytale/protocol/NoiseType.java b/src/com/hypixel/hytale/protocol/NoiseType.java new file mode 100644 index 0000000..596d17a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/NoiseType.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum NoiseType { + Sin(0), + Cos(1), + Perlin_Linear(2), + Perlin_Hermite(3), + Perlin_Quintic(4), + Random(5); + + public static final NoiseType[] VALUES = values(); + private final int value; + + private NoiseType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static NoiseType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("NoiseType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/Objective.java b/src/com/hypixel/hytale/protocol/Objective.java new file mode 100644 index 0000000..9dea626 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Objective.java @@ -0,0 +1,432 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Objective { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 17; + public static final int VARIABLE_FIELD_COUNT = 4; + public static final int VARIABLE_BLOCK_START = 33; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UUID objectiveUuid = new UUID(0L, 0L); + @Nullable + public String objectiveTitleKey; + @Nullable + public String objectiveDescriptionKey; + @Nullable + public String objectiveLineId; + @Nullable + public ObjectiveTask[] tasks; + + public Objective() { + } + + public Objective( + @Nonnull UUID objectiveUuid, + @Nullable String objectiveTitleKey, + @Nullable String objectiveDescriptionKey, + @Nullable String objectiveLineId, + @Nullable ObjectiveTask[] tasks + ) { + this.objectiveUuid = objectiveUuid; + this.objectiveTitleKey = objectiveTitleKey; + this.objectiveDescriptionKey = objectiveDescriptionKey; + this.objectiveLineId = objectiveLineId; + this.tasks = tasks; + } + + public Objective(@Nonnull Objective other) { + this.objectiveUuid = other.objectiveUuid; + this.objectiveTitleKey = other.objectiveTitleKey; + this.objectiveDescriptionKey = other.objectiveDescriptionKey; + this.objectiveLineId = other.objectiveLineId; + this.tasks = other.tasks; + } + + @Nonnull + public static Objective deserialize(@Nonnull ByteBuf buf, int offset) { + Objective obj = new Objective(); + byte nullBits = buf.getByte(offset); + obj.objectiveUuid = PacketIO.readUUID(buf, offset + 1); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 33 + buf.getIntLE(offset + 17); + int objectiveTitleKeyLen = VarInt.peek(buf, varPos0); + if (objectiveTitleKeyLen < 0) { + throw ProtocolException.negativeLength("ObjectiveTitleKey", objectiveTitleKeyLen); + } + + if (objectiveTitleKeyLen > 4096000) { + throw ProtocolException.stringTooLong("ObjectiveTitleKey", objectiveTitleKeyLen, 4096000); + } + + obj.objectiveTitleKey = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 33 + buf.getIntLE(offset + 21); + int objectiveDescriptionKeyLen = VarInt.peek(buf, varPos1); + if (objectiveDescriptionKeyLen < 0) { + throw ProtocolException.negativeLength("ObjectiveDescriptionKey", objectiveDescriptionKeyLen); + } + + if (objectiveDescriptionKeyLen > 4096000) { + throw ProtocolException.stringTooLong("ObjectiveDescriptionKey", objectiveDescriptionKeyLen, 4096000); + } + + obj.objectiveDescriptionKey = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 33 + buf.getIntLE(offset + 25); + int objectiveLineIdLen = VarInt.peek(buf, varPos2); + if (objectiveLineIdLen < 0) { + throw ProtocolException.negativeLength("ObjectiveLineId", objectiveLineIdLen); + } + + if (objectiveLineIdLen > 4096000) { + throw ProtocolException.stringTooLong("ObjectiveLineId", objectiveLineIdLen, 4096000); + } + + obj.objectiveLineId = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 33 + buf.getIntLE(offset + 29); + int tasksCount = VarInt.peek(buf, varPos3); + if (tasksCount < 0) { + throw ProtocolException.negativeLength("Tasks", tasksCount); + } + + if (tasksCount > 4096000) { + throw ProtocolException.arrayTooLong("Tasks", tasksCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tasksCount * 9L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tasks", varPos3 + varIntLen + tasksCount * 9, buf.readableBytes()); + } + + obj.tasks = new ObjectiveTask[tasksCount]; + int elemPos = varPos3 + varIntLen; + + for (int i = 0; i < tasksCount; i++) { + obj.tasks[i] = ObjectiveTask.deserialize(buf, elemPos); + elemPos += ObjectiveTask.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 33; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 17); + int pos0 = offset + 33 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 21); + int pos1 = offset + 33 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 25); + int pos2 = offset + 33 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 29); + int pos3 = offset + 33 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3); + + for (int i = 0; i < arrLen; i++) { + pos3 += ObjectiveTask.computeBytesConsumed(buf, pos3); + } + + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.objectiveTitleKey != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.objectiveDescriptionKey != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.objectiveLineId != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tasks != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + PacketIO.writeUUID(buf, this.objectiveUuid); + int objectiveTitleKeyOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int objectiveDescriptionKeyOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int objectiveLineIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tasksOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.objectiveTitleKey != null) { + buf.setIntLE(objectiveTitleKeyOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.objectiveTitleKey, 4096000); + } else { + buf.setIntLE(objectiveTitleKeyOffsetSlot, -1); + } + + if (this.objectiveDescriptionKey != null) { + buf.setIntLE(objectiveDescriptionKeyOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.objectiveDescriptionKey, 4096000); + } else { + buf.setIntLE(objectiveDescriptionKeyOffsetSlot, -1); + } + + if (this.objectiveLineId != null) { + buf.setIntLE(objectiveLineIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.objectiveLineId, 4096000); + } else { + buf.setIntLE(objectiveLineIdOffsetSlot, -1); + } + + if (this.tasks != null) { + buf.setIntLE(tasksOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tasks.length > 4096000) { + throw ProtocolException.arrayTooLong("Tasks", this.tasks.length, 4096000); + } + + VarInt.write(buf, this.tasks.length); + + for (ObjectiveTask item : this.tasks) { + item.serialize(buf); + } + } else { + buf.setIntLE(tasksOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 33; + if (this.objectiveTitleKey != null) { + size += PacketIO.stringSize(this.objectiveTitleKey); + } + + if (this.objectiveDescriptionKey != null) { + size += PacketIO.stringSize(this.objectiveDescriptionKey); + } + + if (this.objectiveLineId != null) { + size += PacketIO.stringSize(this.objectiveLineId); + } + + if (this.tasks != null) { + int tasksSize = 0; + + for (ObjectiveTask elem : this.tasks) { + tasksSize += elem.computeSize(); + } + + size += VarInt.size(this.tasks.length) + tasksSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 33) { + return ValidationResult.error("Buffer too small: expected at least 33 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int objectiveTitleKeyOffset = buffer.getIntLE(offset + 17); + if (objectiveTitleKeyOffset < 0) { + return ValidationResult.error("Invalid offset for ObjectiveTitleKey"); + } + + int pos = offset + 33 + objectiveTitleKeyOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ObjectiveTitleKey"); + } + + int objectiveTitleKeyLen = VarInt.peek(buffer, pos); + if (objectiveTitleKeyLen < 0) { + return ValidationResult.error("Invalid string length for ObjectiveTitleKey"); + } + + if (objectiveTitleKeyLen > 4096000) { + return ValidationResult.error("ObjectiveTitleKey exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += objectiveTitleKeyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ObjectiveTitleKey"); + } + } + + if ((nullBits & 2) != 0) { + int objectiveDescriptionKeyOffset = buffer.getIntLE(offset + 21); + if (objectiveDescriptionKeyOffset < 0) { + return ValidationResult.error("Invalid offset for ObjectiveDescriptionKey"); + } + + int posx = offset + 33 + objectiveDescriptionKeyOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ObjectiveDescriptionKey"); + } + + int objectiveDescriptionKeyLen = VarInt.peek(buffer, posx); + if (objectiveDescriptionKeyLen < 0) { + return ValidationResult.error("Invalid string length for ObjectiveDescriptionKey"); + } + + if (objectiveDescriptionKeyLen > 4096000) { + return ValidationResult.error("ObjectiveDescriptionKey exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += objectiveDescriptionKeyLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ObjectiveDescriptionKey"); + } + } + + if ((nullBits & 4) != 0) { + int objectiveLineIdOffset = buffer.getIntLE(offset + 25); + if (objectiveLineIdOffset < 0) { + return ValidationResult.error("Invalid offset for ObjectiveLineId"); + } + + int posxx = offset + 33 + objectiveLineIdOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ObjectiveLineId"); + } + + int objectiveLineIdLen = VarInt.peek(buffer, posxx); + if (objectiveLineIdLen < 0) { + return ValidationResult.error("Invalid string length for ObjectiveLineId"); + } + + if (objectiveLineIdLen > 4096000) { + return ValidationResult.error("ObjectiveLineId exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += objectiveLineIdLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ObjectiveLineId"); + } + } + + if ((nullBits & 8) != 0) { + int tasksOffset = buffer.getIntLE(offset + 29); + if (tasksOffset < 0) { + return ValidationResult.error("Invalid offset for Tasks"); + } + + int posxxx = offset + 33 + tasksOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tasks"); + } + + int tasksCount = VarInt.peek(buffer, posxxx); + if (tasksCount < 0) { + return ValidationResult.error("Invalid array count for Tasks"); + } + + if (tasksCount > 4096000) { + return ValidationResult.error("Tasks exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + + for (int i = 0; i < tasksCount; i++) { + ValidationResult structResult = ObjectiveTask.validateStructure(buffer, posxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ObjectiveTask in Tasks[" + i + "]: " + structResult.error()); + } + + posxxx += ObjectiveTask.computeBytesConsumed(buffer, posxxx); + } + } + + return ValidationResult.OK; + } + } + + public Objective clone() { + Objective copy = new Objective(); + copy.objectiveUuid = this.objectiveUuid; + copy.objectiveTitleKey = this.objectiveTitleKey; + copy.objectiveDescriptionKey = this.objectiveDescriptionKey; + copy.objectiveLineId = this.objectiveLineId; + copy.tasks = this.tasks != null ? Arrays.stream(this.tasks).map(e -> e.clone()).toArray(ObjectiveTask[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Objective other) + ? false + : Objects.equals(this.objectiveUuid, other.objectiveUuid) + && Objects.equals(this.objectiveTitleKey, other.objectiveTitleKey) + && Objects.equals(this.objectiveDescriptionKey, other.objectiveDescriptionKey) + && Objects.equals(this.objectiveLineId, other.objectiveLineId) + && Arrays.equals((Object[])this.tasks, (Object[])other.tasks); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.objectiveUuid); + result = 31 * result + Objects.hashCode(this.objectiveTitleKey); + result = 31 * result + Objects.hashCode(this.objectiveDescriptionKey); + result = 31 * result + Objects.hashCode(this.objectiveLineId); + return 31 * result + Arrays.hashCode((Object[])this.tasks); + } +} diff --git a/src/com/hypixel/hytale/protocol/ObjectiveTask.java b/src/com/hypixel/hytale/protocol/ObjectiveTask.java new file mode 100644 index 0000000..80a4cc4 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ObjectiveTask.java @@ -0,0 +1,149 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ObjectiveTask { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 16384014; + @Nullable + public String taskDescriptionKey; + public int currentCompletion; + public int completionNeeded; + + public ObjectiveTask() { + } + + public ObjectiveTask(@Nullable String taskDescriptionKey, int currentCompletion, int completionNeeded) { + this.taskDescriptionKey = taskDescriptionKey; + this.currentCompletion = currentCompletion; + this.completionNeeded = completionNeeded; + } + + public ObjectiveTask(@Nonnull ObjectiveTask other) { + this.taskDescriptionKey = other.taskDescriptionKey; + this.currentCompletion = other.currentCompletion; + this.completionNeeded = other.completionNeeded; + } + + @Nonnull + public static ObjectiveTask deserialize(@Nonnull ByteBuf buf, int offset) { + ObjectiveTask obj = new ObjectiveTask(); + byte nullBits = buf.getByte(offset); + obj.currentCompletion = buf.getIntLE(offset + 1); + obj.completionNeeded = buf.getIntLE(offset + 5); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int taskDescriptionKeyLen = VarInt.peek(buf, pos); + if (taskDescriptionKeyLen < 0) { + throw ProtocolException.negativeLength("TaskDescriptionKey", taskDescriptionKeyLen); + } + + if (taskDescriptionKeyLen > 4096000) { + throw ProtocolException.stringTooLong("TaskDescriptionKey", taskDescriptionKeyLen, 4096000); + } + + int taskDescriptionKeyVarLen = VarInt.length(buf, pos); + obj.taskDescriptionKey = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += taskDescriptionKeyVarLen + taskDescriptionKeyLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.taskDescriptionKey != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.currentCompletion); + buf.writeIntLE(this.completionNeeded); + if (this.taskDescriptionKey != null) { + PacketIO.writeVarString(buf, this.taskDescriptionKey, 4096000); + } + } + + public int computeSize() { + int size = 9; + if (this.taskDescriptionKey != null) { + size += PacketIO.stringSize(this.taskDescriptionKey); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int taskDescriptionKeyLen = VarInt.peek(buffer, pos); + if (taskDescriptionKeyLen < 0) { + return ValidationResult.error("Invalid string length for TaskDescriptionKey"); + } + + if (taskDescriptionKeyLen > 4096000) { + return ValidationResult.error("TaskDescriptionKey exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += taskDescriptionKeyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading TaskDescriptionKey"); + } + } + + return ValidationResult.OK; + } + } + + public ObjectiveTask clone() { + ObjectiveTask copy = new ObjectiveTask(); + copy.taskDescriptionKey = this.taskDescriptionKey; + copy.currentCompletion = this.currentCompletion; + copy.completionNeeded = this.completionNeeded; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ObjectiveTask other) + ? false + : Objects.equals(this.taskDescriptionKey, other.taskDescriptionKey) + && this.currentCompletion == other.currentCompletion + && this.completionNeeded == other.completionNeeded; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.taskDescriptionKey, this.currentCompletion, this.completionNeeded); + } +} diff --git a/src/com/hypixel/hytale/protocol/OffsetNoise.java b/src/com/hypixel/hytale/protocol/OffsetNoise.java new file mode 100644 index 0000000..efbee01 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/OffsetNoise.java @@ -0,0 +1,376 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class OffsetNoise { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 282624028; + @Nullable + public NoiseConfig[] x; + @Nullable + public NoiseConfig[] y; + @Nullable + public NoiseConfig[] z; + + public OffsetNoise() { + } + + public OffsetNoise(@Nullable NoiseConfig[] x, @Nullable NoiseConfig[] y, @Nullable NoiseConfig[] z) { + this.x = x; + this.y = y; + this.z = z; + } + + public OffsetNoise(@Nonnull OffsetNoise other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + } + + @Nonnull + public static OffsetNoise deserialize(@Nonnull ByteBuf buf, int offset) { + OffsetNoise obj = new OffsetNoise(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 1); + int xCount = VarInt.peek(buf, varPos0); + if (xCount < 0) { + throw ProtocolException.negativeLength("X", xCount); + } + + if (xCount > 4096000) { + throw ProtocolException.arrayTooLong("X", xCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + xCount * 23L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("X", varPos0 + varIntLen + xCount * 23, buf.readableBytes()); + } + + obj.x = new NoiseConfig[xCount]; + int elemPos = varPos0 + varIntLen; + + for (int i = 0; i < xCount; i++) { + obj.x[i] = NoiseConfig.deserialize(buf, elemPos); + elemPos += NoiseConfig.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 5); + int yCount = VarInt.peek(buf, varPos1); + if (yCount < 0) { + throw ProtocolException.negativeLength("Y", yCount); + } + + if (yCount > 4096000) { + throw ProtocolException.arrayTooLong("Y", yCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + yCount * 23L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Y", varPos1 + varIntLen + yCount * 23, buf.readableBytes()); + } + + obj.y = new NoiseConfig[yCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < yCount; i++) { + obj.y[i] = NoiseConfig.deserialize(buf, elemPos); + elemPos += NoiseConfig.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 13 + buf.getIntLE(offset + 9); + int zCount = VarInt.peek(buf, varPos2); + if (zCount < 0) { + throw ProtocolException.negativeLength("Z", zCount); + } + + if (zCount > 4096000) { + throw ProtocolException.arrayTooLong("Z", zCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + zCount * 23L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Z", varPos2 + varIntLen + zCount * 23, buf.readableBytes()); + } + + obj.z = new NoiseConfig[zCount]; + int elemPos = varPos2 + varIntLen; + + for (int i = 0; i < zCount; i++) { + obj.z[i] = NoiseConfig.deserialize(buf, elemPos); + elemPos += NoiseConfig.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 13 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < arrLen; i++) { + pos0 += NoiseConfig.computeBytesConsumed(buf, pos0); + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 13 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += NoiseConfig.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 9); + int pos2 = offset + 13 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2); + + for (int i = 0; i < arrLen; i++) { + pos2 += NoiseConfig.computeBytesConsumed(buf, pos2); + } + + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.x != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.y != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.z != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + int xOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int yOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int zOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.x != null) { + buf.setIntLE(xOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.x.length > 4096000) { + throw ProtocolException.arrayTooLong("X", this.x.length, 4096000); + } + + VarInt.write(buf, this.x.length); + + for (NoiseConfig item : this.x) { + item.serialize(buf); + } + } else { + buf.setIntLE(xOffsetSlot, -1); + } + + if (this.y != null) { + buf.setIntLE(yOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.y.length > 4096000) { + throw ProtocolException.arrayTooLong("Y", this.y.length, 4096000); + } + + VarInt.write(buf, this.y.length); + + for (NoiseConfig item : this.y) { + item.serialize(buf); + } + } else { + buf.setIntLE(yOffsetSlot, -1); + } + + if (this.z != null) { + buf.setIntLE(zOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.z.length > 4096000) { + throw ProtocolException.arrayTooLong("Z", this.z.length, 4096000); + } + + VarInt.write(buf, this.z.length); + + for (NoiseConfig item : this.z) { + item.serialize(buf); + } + } else { + buf.setIntLE(zOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 13; + if (this.x != null) { + size += VarInt.size(this.x.length) + this.x.length * 23; + } + + if (this.y != null) { + size += VarInt.size(this.y.length) + this.y.length * 23; + } + + if (this.z != null) { + size += VarInt.size(this.z.length) + this.z.length * 23; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int xOffset = buffer.getIntLE(offset + 1); + if (xOffset < 0) { + return ValidationResult.error("Invalid offset for X"); + } + + int pos = offset + 13 + xOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for X"); + } + + int xCount = VarInt.peek(buffer, pos); + if (xCount < 0) { + return ValidationResult.error("Invalid array count for X"); + } + + if (xCount > 4096000) { + return ValidationResult.error("X exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += xCount * 23; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading X"); + } + } + + if ((nullBits & 2) != 0) { + int yOffset = buffer.getIntLE(offset + 5); + if (yOffset < 0) { + return ValidationResult.error("Invalid offset for Y"); + } + + int posx = offset + 13 + yOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Y"); + } + + int yCount = VarInt.peek(buffer, posx); + if (yCount < 0) { + return ValidationResult.error("Invalid array count for Y"); + } + + if (yCount > 4096000) { + return ValidationResult.error("Y exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += yCount * 23; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Y"); + } + } + + if ((nullBits & 4) != 0) { + int zOffset = buffer.getIntLE(offset + 9); + if (zOffset < 0) { + return ValidationResult.error("Invalid offset for Z"); + } + + int posxx = offset + 13 + zOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Z"); + } + + int zCount = VarInt.peek(buffer, posxx); + if (zCount < 0) { + return ValidationResult.error("Invalid array count for Z"); + } + + if (zCount > 4096000) { + return ValidationResult.error("Z exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += zCount * 23; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Z"); + } + } + + return ValidationResult.OK; + } + } + + public OffsetNoise clone() { + OffsetNoise copy = new OffsetNoise(); + copy.x = this.x != null ? Arrays.stream(this.x).map(e -> e.clone()).toArray(NoiseConfig[]::new) : null; + copy.y = this.y != null ? Arrays.stream(this.y).map(e -> e.clone()).toArray(NoiseConfig[]::new) : null; + copy.z = this.z != null ? Arrays.stream(this.z).map(e -> e.clone()).toArray(NoiseConfig[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof OffsetNoise other) + ? false + : Arrays.equals((Object[])this.x, (Object[])other.x) + && Arrays.equals((Object[])this.y, (Object[])other.y) + && Arrays.equals((Object[])this.z, (Object[])other.z); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode((Object[])this.x); + result = 31 * result + Arrays.hashCode((Object[])this.y); + return 31 * result + Arrays.hashCode((Object[])this.z); + } +} diff --git a/src/com/hypixel/hytale/protocol/Opacity.java b/src/com/hypixel/hytale/protocol/Opacity.java new file mode 100644 index 0000000..4f77008 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Opacity.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum Opacity { + Solid(0), + Semitransparent(1), + Cutout(2), + Transparent(3); + + public static final Opacity[] VALUES = values(); + private final int value; + + private Opacity(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static Opacity fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("Opacity", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/OverlapBehavior.java b/src/com/hypixel/hytale/protocol/OverlapBehavior.java new file mode 100644 index 0000000..8303627 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/OverlapBehavior.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum OverlapBehavior { + Extend(0), + Overwrite(1), + Ignore(2); + + public static final OverlapBehavior[] VALUES = values(); + private final int value; + + private OverlapBehavior(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static OverlapBehavior fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("OverlapBehavior", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/Packet.java b/src/com/hypixel/hytale/protocol/Packet.java new file mode 100644 index 0000000..673767a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Packet.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.protocol; + +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public interface Packet { + int getId(); + + void serialize(@Nonnull ByteBuf var1); + + int computeSize(); +} diff --git a/src/com/hypixel/hytale/protocol/PacketRegistry.java b/src/com/hypixel/hytale/protocol/PacketRegistry.java new file mode 100644 index 0000000..bb17cbb --- /dev/null +++ b/src/com/hypixel/hytale/protocol/PacketRegistry.java @@ -0,0 +1,1693 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorActivateButton; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetListSetup; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetListUpdate; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetPackSetup; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetUpdated; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAuthorization; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorCapabilities; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorCreateAsset; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorCreateAssetPack; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorCreateDirectory; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorDeleteAsset; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorDeleteAssetPack; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorDeleteDirectory; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorDiscardChanges; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorEnableAssetPack; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorExportAssetFinalize; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorExportAssetInitialize; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorExportAssetPart; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorExportAssets; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorExportComplete; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorExportDeleteAssets; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFetchAsset; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFetchAssetReply; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFetchAutoCompleteData; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFetchAutoCompleteDataReply; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFetchJsonAssetWithParents; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFetchJsonAssetWithParentsReply; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFetchLastModifiedAssets; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorInitialize; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorJsonAssetUpdated; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorLastModifiedAssets; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorModifiedAssetsCount; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorPopupNotification; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRebuildCaches; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRedoChanges; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRenameAsset; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRenameDirectory; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRequestChildrenList; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRequestChildrenListReply; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRequestDataset; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRequestDatasetReply; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorSelectAsset; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorSetGameTime; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorSetupAssetTypes; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorSetupSchemas; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorSubscribeModifiedAssetsChanges; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUndoChanges; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUndoRedoReply; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUpdateAsset; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUpdateAssetPack; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUpdateJsonAsset; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUpdateModelPreview; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUpdateSecondsPerGameDay; +import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUpdateWeatherPreviewLock; +import com.hypixel.hytale.protocol.packets.asseteditor.FailureReply; +import com.hypixel.hytale.protocol.packets.asseteditor.SuccessReply; +import com.hypixel.hytale.protocol.packets.assets.TrackOrUpdateObjective; +import com.hypixel.hytale.protocol.packets.assets.UntrackObjective; +import com.hypixel.hytale.protocol.packets.assets.UpdateAmbienceFX; +import com.hypixel.hytale.protocol.packets.assets.UpdateAudioCategories; +import com.hypixel.hytale.protocol.packets.assets.UpdateBlockBreakingDecals; +import com.hypixel.hytale.protocol.packets.assets.UpdateBlockGroups; +import com.hypixel.hytale.protocol.packets.assets.UpdateBlockHitboxes; +import com.hypixel.hytale.protocol.packets.assets.UpdateBlockParticleSets; +import com.hypixel.hytale.protocol.packets.assets.UpdateBlockSets; +import com.hypixel.hytale.protocol.packets.assets.UpdateBlockSoundSets; +import com.hypixel.hytale.protocol.packets.assets.UpdateBlockTypes; +import com.hypixel.hytale.protocol.packets.assets.UpdateCameraShake; +import com.hypixel.hytale.protocol.packets.assets.UpdateEntityEffects; +import com.hypixel.hytale.protocol.packets.assets.UpdateEntityStatTypes; +import com.hypixel.hytale.protocol.packets.assets.UpdateEntityUIComponents; +import com.hypixel.hytale.protocol.packets.assets.UpdateEnvironments; +import com.hypixel.hytale.protocol.packets.assets.UpdateEqualizerEffects; +import com.hypixel.hytale.protocol.packets.assets.UpdateFieldcraftCategories; +import com.hypixel.hytale.protocol.packets.assets.UpdateFluidFX; +import com.hypixel.hytale.protocol.packets.assets.UpdateFluids; +import com.hypixel.hytale.protocol.packets.assets.UpdateHitboxCollisionConfig; +import com.hypixel.hytale.protocol.packets.assets.UpdateInteractions; +import com.hypixel.hytale.protocol.packets.assets.UpdateItemCategories; +import com.hypixel.hytale.protocol.packets.assets.UpdateItemPlayerAnimations; +import com.hypixel.hytale.protocol.packets.assets.UpdateItemQualities; +import com.hypixel.hytale.protocol.packets.assets.UpdateItemReticles; +import com.hypixel.hytale.protocol.packets.assets.UpdateItemSoundSets; +import com.hypixel.hytale.protocol.packets.assets.UpdateItems; +import com.hypixel.hytale.protocol.packets.assets.UpdateModelvfxs; +import com.hypixel.hytale.protocol.packets.assets.UpdateObjectiveTask; +import com.hypixel.hytale.protocol.packets.assets.UpdateParticleSpawners; +import com.hypixel.hytale.protocol.packets.assets.UpdateParticleSystems; +import com.hypixel.hytale.protocol.packets.assets.UpdateProjectileConfigs; +import com.hypixel.hytale.protocol.packets.assets.UpdateRecipes; +import com.hypixel.hytale.protocol.packets.assets.UpdateRepulsionConfig; +import com.hypixel.hytale.protocol.packets.assets.UpdateResourceTypes; +import com.hypixel.hytale.protocol.packets.assets.UpdateReverbEffects; +import com.hypixel.hytale.protocol.packets.assets.UpdateRootInteractions; +import com.hypixel.hytale.protocol.packets.assets.UpdateSoundEvents; +import com.hypixel.hytale.protocol.packets.assets.UpdateSoundSets; +import com.hypixel.hytale.protocol.packets.assets.UpdateTagPatterns; +import com.hypixel.hytale.protocol.packets.assets.UpdateTrails; +import com.hypixel.hytale.protocol.packets.assets.UpdateTranslations; +import com.hypixel.hytale.protocol.packets.assets.UpdateUnarmedInteractions; +import com.hypixel.hytale.protocol.packets.assets.UpdateViewBobbing; +import com.hypixel.hytale.protocol.packets.assets.UpdateWeathers; +import com.hypixel.hytale.protocol.packets.auth.AuthGrant; +import com.hypixel.hytale.protocol.packets.auth.AuthToken; +import com.hypixel.hytale.protocol.packets.auth.ClientReferral; +import com.hypixel.hytale.protocol.packets.auth.ConnectAccept; +import com.hypixel.hytale.protocol.packets.auth.PasswordAccepted; +import com.hypixel.hytale.protocol.packets.auth.PasswordRejected; +import com.hypixel.hytale.protocol.packets.auth.PasswordResponse; +import com.hypixel.hytale.protocol.packets.auth.ServerAuthToken; +import com.hypixel.hytale.protocol.packets.auth.Status; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArgUpdate; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolEntityAction; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolExtrudeAction; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolGeneralAction; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolHideAnchors; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolLaserPointer; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolLineAction; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolOnUseInteraction; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolPasteClipboard; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolRotateClipboard; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSelectionToolAskForClipboard; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSelectionToolReplyWithClipboard; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSelectionTransform; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSelectionUpdate; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSetEntityLight; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSetEntityPickupEnabled; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSetEntityScale; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSetEntityTransform; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSetNPCDebug; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolSetTransformationModeState; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolShowAnchor; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolStackArea; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolsSetSoundSet; +import com.hypixel.hytale.protocol.packets.buildertools.PrefabUnselectPrefab; +import com.hypixel.hytale.protocol.packets.camera.CameraShakeEffect; +import com.hypixel.hytale.protocol.packets.camera.RequestFlyCameraMode; +import com.hypixel.hytale.protocol.packets.camera.SetFlyCameraMode; +import com.hypixel.hytale.protocol.packets.camera.SetServerCamera; +import com.hypixel.hytale.protocol.packets.connection.Connect; +import com.hypixel.hytale.protocol.packets.connection.Disconnect; +import com.hypixel.hytale.protocol.packets.connection.Ping; +import com.hypixel.hytale.protocol.packets.connection.Pong; +import com.hypixel.hytale.protocol.packets.entities.ApplyKnockback; +import com.hypixel.hytale.protocol.packets.entities.ChangeVelocity; +import com.hypixel.hytale.protocol.packets.entities.EntityUpdates; +import com.hypixel.hytale.protocol.packets.entities.MountMovement; +import com.hypixel.hytale.protocol.packets.entities.PlayAnimation; +import com.hypixel.hytale.protocol.packets.entities.SetEntitySeed; +import com.hypixel.hytale.protocol.packets.entities.SpawnModelParticles; +import com.hypixel.hytale.protocol.packets.interaction.CancelInteractionChain; +import com.hypixel.hytale.protocol.packets.interaction.DismountNPC; +import com.hypixel.hytale.protocol.packets.interaction.MountNPC; +import com.hypixel.hytale.protocol.packets.interaction.PlayInteractionFor; +import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChains; +import com.hypixel.hytale.protocol.packets.interface_.AddToServerPlayerList; +import com.hypixel.hytale.protocol.packets.interface_.ChatMessage; +import com.hypixel.hytale.protocol.packets.interface_.CustomHud; +import com.hypixel.hytale.protocol.packets.interface_.CustomPage; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageEvent; +import com.hypixel.hytale.protocol.packets.interface_.EditorBlocksChange; +import com.hypixel.hytale.protocol.packets.interface_.HideEventTitle; +import com.hypixel.hytale.protocol.packets.interface_.KillFeedMessage; +import com.hypixel.hytale.protocol.packets.interface_.Notification; +import com.hypixel.hytale.protocol.packets.interface_.OpenChatWithCommand; +import com.hypixel.hytale.protocol.packets.interface_.RemoveFromServerPlayerList; +import com.hypixel.hytale.protocol.packets.interface_.ResetUserInterfaceState; +import com.hypixel.hytale.protocol.packets.interface_.ServerInfo; +import com.hypixel.hytale.protocol.packets.interface_.ServerMessage; +import com.hypixel.hytale.protocol.packets.interface_.SetPage; +import com.hypixel.hytale.protocol.packets.interface_.ShowEventTitle; +import com.hypixel.hytale.protocol.packets.interface_.UpdateKnownRecipes; +import com.hypixel.hytale.protocol.packets.interface_.UpdateLanguage; +import com.hypixel.hytale.protocol.packets.interface_.UpdatePortal; +import com.hypixel.hytale.protocol.packets.interface_.UpdateServerPlayerList; +import com.hypixel.hytale.protocol.packets.interface_.UpdateServerPlayerListPing; +import com.hypixel.hytale.protocol.packets.interface_.UpdateVisibleHudComponents; +import com.hypixel.hytale.protocol.packets.interface_.WorldSavingStatus; +import com.hypixel.hytale.protocol.packets.inventory.DropCreativeItem; +import com.hypixel.hytale.protocol.packets.inventory.DropItemStack; +import com.hypixel.hytale.protocol.packets.inventory.InventoryAction; +import com.hypixel.hytale.protocol.packets.inventory.MoveItemStack; +import com.hypixel.hytale.protocol.packets.inventory.SetActiveSlot; +import com.hypixel.hytale.protocol.packets.inventory.SetCreativeItem; +import com.hypixel.hytale.protocol.packets.inventory.SmartGiveCreativeItem; +import com.hypixel.hytale.protocol.packets.inventory.SmartMoveItemStack; +import com.hypixel.hytale.protocol.packets.inventory.SwitchHotbarBlockSet; +import com.hypixel.hytale.protocol.packets.inventory.UpdatePlayerInventory; +import com.hypixel.hytale.protocol.packets.machinima.RequestMachinimaActorModel; +import com.hypixel.hytale.protocol.packets.machinima.SetMachinimaActorModel; +import com.hypixel.hytale.protocol.packets.machinima.UpdateMachinimaScene; +import com.hypixel.hytale.protocol.packets.player.ClearDebugShapes; +import com.hypixel.hytale.protocol.packets.player.ClientMovement; +import com.hypixel.hytale.protocol.packets.player.ClientPlaceBlock; +import com.hypixel.hytale.protocol.packets.player.ClientReady; +import com.hypixel.hytale.protocol.packets.player.ClientTeleport; +import com.hypixel.hytale.protocol.packets.player.DamageInfo; +import com.hypixel.hytale.protocol.packets.player.DisplayDebug; +import com.hypixel.hytale.protocol.packets.player.JoinWorld; +import com.hypixel.hytale.protocol.packets.player.LoadHotbar; +import com.hypixel.hytale.protocol.packets.player.MouseInteraction; +import com.hypixel.hytale.protocol.packets.player.RemoveMapMarker; +import com.hypixel.hytale.protocol.packets.player.ReticleEvent; +import com.hypixel.hytale.protocol.packets.player.SaveHotbar; +import com.hypixel.hytale.protocol.packets.player.SetBlockPlacementOverride; +import com.hypixel.hytale.protocol.packets.player.SetClientId; +import com.hypixel.hytale.protocol.packets.player.SetGameMode; +import com.hypixel.hytale.protocol.packets.player.SetMovementStates; +import com.hypixel.hytale.protocol.packets.player.SyncPlayerPreferences; +import com.hypixel.hytale.protocol.packets.player.UpdateMemoriesFeatureStatus; +import com.hypixel.hytale.protocol.packets.player.UpdateMovementSettings; +import com.hypixel.hytale.protocol.packets.serveraccess.RequestServerAccess; +import com.hypixel.hytale.protocol.packets.serveraccess.SetServerAccess; +import com.hypixel.hytale.protocol.packets.serveraccess.UpdateServerAccess; +import com.hypixel.hytale.protocol.packets.setup.AssetFinalize; +import com.hypixel.hytale.protocol.packets.setup.AssetInitialize; +import com.hypixel.hytale.protocol.packets.setup.AssetPart; +import com.hypixel.hytale.protocol.packets.setup.PlayerOptions; +import com.hypixel.hytale.protocol.packets.setup.RemoveAssets; +import com.hypixel.hytale.protocol.packets.setup.RequestAssets; +import com.hypixel.hytale.protocol.packets.setup.RequestCommonAssetsRebuild; +import com.hypixel.hytale.protocol.packets.setup.ServerTags; +import com.hypixel.hytale.protocol.packets.setup.SetTimeDilation; +import com.hypixel.hytale.protocol.packets.setup.SetUpdateRate; +import com.hypixel.hytale.protocol.packets.setup.UpdateFeatures; +import com.hypixel.hytale.protocol.packets.setup.ViewRadius; +import com.hypixel.hytale.protocol.packets.setup.WorldLoadFinished; +import com.hypixel.hytale.protocol.packets.setup.WorldLoadProgress; +import com.hypixel.hytale.protocol.packets.setup.WorldSettings; +import com.hypixel.hytale.protocol.packets.window.ClientOpenWindow; +import com.hypixel.hytale.protocol.packets.window.CloseWindow; +import com.hypixel.hytale.protocol.packets.window.OpenWindow; +import com.hypixel.hytale.protocol.packets.window.SendWindowAction; +import com.hypixel.hytale.protocol.packets.window.UpdateWindow; +import com.hypixel.hytale.protocol.packets.world.ClearEditorTimeOverride; +import com.hypixel.hytale.protocol.packets.world.PlaySoundEvent2D; +import com.hypixel.hytale.protocol.packets.world.PlaySoundEvent3D; +import com.hypixel.hytale.protocol.packets.world.PlaySoundEventEntity; +import com.hypixel.hytale.protocol.packets.world.ServerSetBlock; +import com.hypixel.hytale.protocol.packets.world.ServerSetBlocks; +import com.hypixel.hytale.protocol.packets.world.ServerSetFluid; +import com.hypixel.hytale.protocol.packets.world.ServerSetFluids; +import com.hypixel.hytale.protocol.packets.world.ServerSetPaused; +import com.hypixel.hytale.protocol.packets.world.SetChunk; +import com.hypixel.hytale.protocol.packets.world.SetChunkEnvironments; +import com.hypixel.hytale.protocol.packets.world.SetChunkHeightmap; +import com.hypixel.hytale.protocol.packets.world.SetChunkTintmap; +import com.hypixel.hytale.protocol.packets.world.SetFluids; +import com.hypixel.hytale.protocol.packets.world.SetPaused; +import com.hypixel.hytale.protocol.packets.world.SpawnBlockParticleSystem; +import com.hypixel.hytale.protocol.packets.world.SpawnParticleSystem; +import com.hypixel.hytale.protocol.packets.world.UnloadChunk; +import com.hypixel.hytale.protocol.packets.world.UpdateBlockDamage; +import com.hypixel.hytale.protocol.packets.world.UpdateEditorTimeOverride; +import com.hypixel.hytale.protocol.packets.world.UpdateEditorWeatherOverride; +import com.hypixel.hytale.protocol.packets.world.UpdateEnvironmentMusic; +import com.hypixel.hytale.protocol.packets.world.UpdatePostFxSettings; +import com.hypixel.hytale.protocol.packets.world.UpdateSleepState; +import com.hypixel.hytale.protocol.packets.world.UpdateSunSettings; +import com.hypixel.hytale.protocol.packets.world.UpdateTime; +import com.hypixel.hytale.protocol.packets.world.UpdateTimeSettings; +import com.hypixel.hytale.protocol.packets.world.UpdateWeather; +import com.hypixel.hytale.protocol.packets.worldmap.ClearWorldMap; +import com.hypixel.hytale.protocol.packets.worldmap.TeleportToWorldMapMarker; +import com.hypixel.hytale.protocol.packets.worldmap.TeleportToWorldMapPosition; +import com.hypixel.hytale.protocol.packets.worldmap.UpdateWorldMap; +import com.hypixel.hytale.protocol.packets.worldmap.UpdateWorldMapSettings; +import com.hypixel.hytale.protocol.packets.worldmap.UpdateWorldMapVisible; +import io.netty.buffer.ByteBuf; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class PacketRegistry { + private static final Map BY_ID = new HashMap<>(); + private static final Map BY_ID_UNMODIFIABLE = Collections.unmodifiableMap(BY_ID); + private static final Map, Integer> BY_TYPE = new HashMap<>(); + + private PacketRegistry() { + } + + private static void register( + int id, + String name, + Class type, + int fixedBlockSize, + int maxSize, + boolean compressed, + BiFunction validate, + BiFunction deserialize + ) { + PacketRegistry.PacketInfo existing = BY_ID.get(id); + if (existing != null) { + throw new IllegalStateException("Duplicate packet ID " + id + ": '" + name + "' conflicts with '" + existing.name() + "'"); + } else { + PacketRegistry.PacketInfo info = new PacketRegistry.PacketInfo(id, name, type, fixedBlockSize, maxSize, compressed, validate, deserialize); + BY_ID.put(id, info); + BY_TYPE.put(type, id); + } + } + + @Nullable + public static PacketRegistry.PacketInfo getById(int id) { + return BY_ID.get(id); + } + + @Nullable + public static Integer getId(Class type) { + return BY_TYPE.get(type); + } + + @Nonnull + public static Map all() { + return BY_ID_UNMODIFIABLE; + } + + static { + register(0, "Connect", Connect.class, 82, 38161, false, Connect::validateStructure, Connect::deserialize); + register(1, "Disconnect", Disconnect.class, 2, 16384007, false, Disconnect::validateStructure, Disconnect::deserialize); + register(2, "Ping", Ping.class, 29, 29, false, Ping::validateStructure, Ping::deserialize); + register(3, "Pong", Pong.class, 20, 20, false, Pong::validateStructure, Pong::deserialize); + register(10, "Status", Status.class, 9, 2587, false, Status::validateStructure, Status::deserialize); + register(11, "AuthGrant", AuthGrant.class, 1, 49171, false, AuthGrant::validateStructure, AuthGrant::deserialize); + register(12, "AuthToken", AuthToken.class, 1, 49171, false, AuthToken::validateStructure, AuthToken::deserialize); + register(13, "ServerAuthToken", ServerAuthToken.class, 1, 32851, false, ServerAuthToken::validateStructure, ServerAuthToken::deserialize); + register(14, "ConnectAccept", ConnectAccept.class, 1, 70, false, ConnectAccept::validateStructure, ConnectAccept::deserialize); + register(15, "PasswordResponse", PasswordResponse.class, 1, 70, false, PasswordResponse::validateStructure, PasswordResponse::deserialize); + register(16, "PasswordAccepted", PasswordAccepted.class, 0, 0, false, PasswordAccepted::validateStructure, PasswordAccepted::deserialize); + register(17, "PasswordRejected", PasswordRejected.class, 5, 74, false, PasswordRejected::validateStructure, PasswordRejected::deserialize); + register(18, "ClientReferral", ClientReferral.class, 1, 5141, false, ClientReferral::validateStructure, ClientReferral::deserialize); + register(20, "WorldSettings", WorldSettings.class, 5, 1677721600, true, WorldSettings::validateStructure, WorldSettings::deserialize); + register(21, "WorldLoadProgress", WorldLoadProgress.class, 9, 16384014, false, WorldLoadProgress::validateStructure, WorldLoadProgress::deserialize); + register(22, "WorldLoadFinished", WorldLoadFinished.class, 0, 0, false, WorldLoadFinished::validateStructure, WorldLoadFinished::deserialize); + register(23, "RequestAssets", RequestAssets.class, 1, 1677721600, true, RequestAssets::validateStructure, RequestAssets::deserialize); + register(24, "AssetInitialize", AssetInitialize.class, 4, 2121, false, AssetInitialize::validateStructure, AssetInitialize::deserialize); + register(25, "AssetPart", AssetPart.class, 1, 4096006, true, AssetPart::validateStructure, AssetPart::deserialize); + register(26, "AssetFinalize", AssetFinalize.class, 0, 0, false, AssetFinalize::validateStructure, AssetFinalize::deserialize); + register(27, "RemoveAssets", RemoveAssets.class, 1, 1677721600, false, RemoveAssets::validateStructure, RemoveAssets::deserialize); + register( + 28, + "RequestCommonAssetsRebuild", + RequestCommonAssetsRebuild.class, + 0, + 0, + false, + RequestCommonAssetsRebuild::validateStructure, + RequestCommonAssetsRebuild::deserialize + ); + register(29, "SetUpdateRate", SetUpdateRate.class, 4, 4, false, SetUpdateRate::validateStructure, SetUpdateRate::deserialize); + register(30, "SetTimeDilation", SetTimeDilation.class, 4, 4, false, SetTimeDilation::validateStructure, SetTimeDilation::deserialize); + register(31, "UpdateFeatures", UpdateFeatures.class, 1, 8192006, false, UpdateFeatures::validateStructure, UpdateFeatures::deserialize); + register(32, "ViewRadius", ViewRadius.class, 4, 4, false, ViewRadius::validateStructure, ViewRadius::deserialize); + register(33, "PlayerOptions", PlayerOptions.class, 1, 327680184, false, PlayerOptions::validateStructure, PlayerOptions::deserialize); + register(34, "ServerTags", ServerTags.class, 1, 1677721600, false, ServerTags::validateStructure, ServerTags::deserialize); + register(40, "UpdateBlockTypes", UpdateBlockTypes.class, 10, 1677721600, true, UpdateBlockTypes::validateStructure, UpdateBlockTypes::deserialize); + register( + 41, "UpdateBlockHitboxes", UpdateBlockHitboxes.class, 6, 1677721600, true, UpdateBlockHitboxes::validateStructure, UpdateBlockHitboxes::deserialize + ); + register( + 42, + "UpdateBlockSoundSets", + UpdateBlockSoundSets.class, + 6, + 1677721600, + true, + UpdateBlockSoundSets::validateStructure, + UpdateBlockSoundSets::deserialize + ); + register( + 43, "UpdateItemSoundSets", UpdateItemSoundSets.class, 6, 1677721600, true, UpdateItemSoundSets::validateStructure, UpdateItemSoundSets::deserialize + ); + register( + 44, + "UpdateBlockParticleSets", + UpdateBlockParticleSets.class, + 2, + 1677721600, + true, + UpdateBlockParticleSets::validateStructure, + UpdateBlockParticleSets::deserialize + ); + register( + 45, + "UpdateBlockBreakingDecals", + UpdateBlockBreakingDecals.class, + 2, + 1677721600, + true, + UpdateBlockBreakingDecals::validateStructure, + UpdateBlockBreakingDecals::deserialize + ); + register(46, "UpdateBlockSets", UpdateBlockSets.class, 2, 1677721600, true, UpdateBlockSets::validateStructure, UpdateBlockSets::deserialize); + register(47, "UpdateWeathers", UpdateWeathers.class, 6, 1677721600, true, UpdateWeathers::validateStructure, UpdateWeathers::deserialize); + register(48, "UpdateTrails", UpdateTrails.class, 2, 1677721600, true, UpdateTrails::validateStructure, UpdateTrails::deserialize); + register( + 49, + "UpdateParticleSystems", + UpdateParticleSystems.class, + 2, + 1677721600, + true, + UpdateParticleSystems::validateStructure, + UpdateParticleSystems::deserialize + ); + register( + 50, + "UpdateParticleSpawners", + UpdateParticleSpawners.class, + 2, + 1677721600, + true, + UpdateParticleSpawners::validateStructure, + UpdateParticleSpawners::deserialize + ); + register( + 51, "UpdateEntityEffects", UpdateEntityEffects.class, 6, 1677721600, true, UpdateEntityEffects::validateStructure, UpdateEntityEffects::deserialize + ); + register( + 52, + "UpdateItemPlayerAnimations", + UpdateItemPlayerAnimations.class, + 2, + 1677721600, + true, + UpdateItemPlayerAnimations::validateStructure, + UpdateItemPlayerAnimations::deserialize + ); + register(53, "UpdateModelvfxs", UpdateModelvfxs.class, 6, 1677721600, true, UpdateModelvfxs::validateStructure, UpdateModelvfxs::deserialize); + register(54, "UpdateItems", UpdateItems.class, 4, 1677721600, true, UpdateItems::validateStructure, UpdateItems::deserialize); + register( + 55, "UpdateItemQualities", UpdateItemQualities.class, 6, 1677721600, true, UpdateItemQualities::validateStructure, UpdateItemQualities::deserialize + ); + register( + 56, + "UpdateItemCategories", + UpdateItemCategories.class, + 2, + 1677721600, + true, + UpdateItemCategories::validateStructure, + UpdateItemCategories::deserialize + ); + register(57, "UpdateItemReticles", UpdateItemReticles.class, 6, 1677721600, true, UpdateItemReticles::validateStructure, UpdateItemReticles::deserialize); + register( + 58, + "UpdateFieldcraftCategories", + UpdateFieldcraftCategories.class, + 2, + 1677721600, + true, + UpdateFieldcraftCategories::validateStructure, + UpdateFieldcraftCategories::deserialize + ); + register( + 59, "UpdateResourceTypes", UpdateResourceTypes.class, 2, 1677721600, true, UpdateResourceTypes::validateStructure, UpdateResourceTypes::deserialize + ); + register(60, "UpdateRecipes", UpdateRecipes.class, 2, 1677721600, true, UpdateRecipes::validateStructure, UpdateRecipes::deserialize); + register(61, "UpdateEnvironments", UpdateEnvironments.class, 7, 1677721600, true, UpdateEnvironments::validateStructure, UpdateEnvironments::deserialize); + register(62, "UpdateAmbienceFX", UpdateAmbienceFX.class, 6, 1677721600, true, UpdateAmbienceFX::validateStructure, UpdateAmbienceFX::deserialize); + register(63, "UpdateFluidFX", UpdateFluidFX.class, 6, 1677721600, true, UpdateFluidFX::validateStructure, UpdateFluidFX::deserialize); + register(64, "UpdateTranslations", UpdateTranslations.class, 2, 1677721600, true, UpdateTranslations::validateStructure, UpdateTranslations::deserialize); + register(65, "UpdateSoundEvents", UpdateSoundEvents.class, 6, 1677721600, true, UpdateSoundEvents::validateStructure, UpdateSoundEvents::deserialize); + register(66, "UpdateInteractions", UpdateInteractions.class, 6, 1677721600, true, UpdateInteractions::validateStructure, UpdateInteractions::deserialize); + register( + 67, + "UpdateRootInteractions", + UpdateRootInteractions.class, + 6, + 1677721600, + true, + UpdateRootInteractions::validateStructure, + UpdateRootInteractions::deserialize + ); + register( + 68, + "UpdateUnarmedInteractions", + UpdateUnarmedInteractions.class, + 2, + 20480007, + true, + UpdateUnarmedInteractions::validateStructure, + UpdateUnarmedInteractions::deserialize + ); + register( + 69, + "TrackOrUpdateObjective", + TrackOrUpdateObjective.class, + 1, + 1677721600, + false, + TrackOrUpdateObjective::validateStructure, + TrackOrUpdateObjective::deserialize + ); + register(70, "UntrackObjective", UntrackObjective.class, 16, 16, false, UntrackObjective::validateStructure, UntrackObjective::deserialize); + register( + 71, "UpdateObjectiveTask", UpdateObjectiveTask.class, 21, 16384035, false, UpdateObjectiveTask::validateStructure, UpdateObjectiveTask::deserialize + ); + register( + 72, + "UpdateEntityStatTypes", + UpdateEntityStatTypes.class, + 6, + 1677721600, + true, + UpdateEntityStatTypes::validateStructure, + UpdateEntityStatTypes::deserialize + ); + register( + 73, + "UpdateEntityUIComponents", + UpdateEntityUIComponents.class, + 6, + 1677721600, + true, + UpdateEntityUIComponents::validateStructure, + UpdateEntityUIComponents::deserialize + ); + register( + 74, + "UpdateHitboxCollisionConfig", + UpdateHitboxCollisionConfig.class, + 6, + 36864011, + true, + UpdateHitboxCollisionConfig::validateStructure, + UpdateHitboxCollisionConfig::deserialize + ); + register( + 75, + "UpdateRepulsionConfig", + UpdateRepulsionConfig.class, + 6, + 65536011, + true, + UpdateRepulsionConfig::validateStructure, + UpdateRepulsionConfig::deserialize + ); + register(76, "UpdateViewBobbing", UpdateViewBobbing.class, 2, 1677721600, true, UpdateViewBobbing::validateStructure, UpdateViewBobbing::deserialize); + register(77, "UpdateCameraShake", UpdateCameraShake.class, 2, 1677721600, true, UpdateCameraShake::validateStructure, UpdateCameraShake::deserialize); + register(78, "UpdateBlockGroups", UpdateBlockGroups.class, 2, 1677721600, true, UpdateBlockGroups::validateStructure, UpdateBlockGroups::deserialize); + register(79, "UpdateSoundSets", UpdateSoundSets.class, 6, 1677721600, true, UpdateSoundSets::validateStructure, UpdateSoundSets::deserialize); + register( + 80, + "UpdateAudioCategories", + UpdateAudioCategories.class, + 6, + 1677721600, + true, + UpdateAudioCategories::validateStructure, + UpdateAudioCategories::deserialize + ); + register( + 81, "UpdateReverbEffects", UpdateReverbEffects.class, 6, 1677721600, true, UpdateReverbEffects::validateStructure, UpdateReverbEffects::deserialize + ); + register( + 82, + "UpdateEqualizerEffects", + UpdateEqualizerEffects.class, + 6, + 1677721600, + true, + UpdateEqualizerEffects::validateStructure, + UpdateEqualizerEffects::deserialize + ); + register(83, "UpdateFluids", UpdateFluids.class, 6, 1677721600, true, UpdateFluids::validateStructure, UpdateFluids::deserialize); + register(84, "UpdateTagPatterns", UpdateTagPatterns.class, 6, 1677721600, true, UpdateTagPatterns::validateStructure, UpdateTagPatterns::deserialize); + register( + 85, + "UpdateProjectileConfigs", + UpdateProjectileConfigs.class, + 2, + 1677721600, + true, + UpdateProjectileConfigs::validateStructure, + UpdateProjectileConfigs::deserialize + ); + register(100, "SetClientId", SetClientId.class, 4, 4, false, SetClientId::validateStructure, SetClientId::deserialize); + register(101, "SetGameMode", SetGameMode.class, 1, 1, false, SetGameMode::validateStructure, SetGameMode::deserialize); + register(102, "SetMovementStates", SetMovementStates.class, 2, 2, false, SetMovementStates::validateStructure, SetMovementStates::deserialize); + register( + 103, + "SetBlockPlacementOverride", + SetBlockPlacementOverride.class, + 1, + 1, + false, + SetBlockPlacementOverride::validateStructure, + SetBlockPlacementOverride::deserialize + ); + register(104, "JoinWorld", JoinWorld.class, 18, 18, false, JoinWorld::validateStructure, JoinWorld::deserialize); + register(105, "ClientReady", ClientReady.class, 2, 2, false, ClientReady::validateStructure, ClientReady::deserialize); + register(106, "LoadHotbar", LoadHotbar.class, 1, 1, false, LoadHotbar::validateStructure, LoadHotbar::deserialize); + register(107, "SaveHotbar", SaveHotbar.class, 1, 1, false, SaveHotbar::validateStructure, SaveHotbar::deserialize); + register(108, "ClientMovement", ClientMovement.class, 153, 153, false, ClientMovement::validateStructure, ClientMovement::deserialize); + register(109, "ClientTeleport", ClientTeleport.class, 52, 52, false, ClientTeleport::validateStructure, ClientTeleport::deserialize); + register( + 110, + "UpdateMovementSettings", + UpdateMovementSettings.class, + 252, + 252, + false, + UpdateMovementSettings::validateStructure, + UpdateMovementSettings::deserialize + ); + register(111, "MouseInteraction", MouseInteraction.class, 44, 20480071, false, MouseInteraction::validateStructure, MouseInteraction::deserialize); + register(112, "DamageInfo", DamageInfo.class, 29, 32768048, false, DamageInfo::validateStructure, DamageInfo::deserialize); + register(113, "ReticleEvent", ReticleEvent.class, 4, 4, false, ReticleEvent::validateStructure, ReticleEvent::deserialize); + register(114, "DisplayDebug", DisplayDebug.class, 19, 32768037, false, DisplayDebug::validateStructure, DisplayDebug::deserialize); + register(115, "ClearDebugShapes", ClearDebugShapes.class, 0, 0, false, ClearDebugShapes::validateStructure, ClearDebugShapes::deserialize); + register( + 116, "SyncPlayerPreferences", SyncPlayerPreferences.class, 8, 8, false, SyncPlayerPreferences::validateStructure, SyncPlayerPreferences::deserialize + ); + register(117, "ClientPlaceBlock", ClientPlaceBlock.class, 20, 20, false, ClientPlaceBlock::validateStructure, ClientPlaceBlock::deserialize); + register( + 118, + "UpdateMemoriesFeatureStatus", + UpdateMemoriesFeatureStatus.class, + 1, + 1, + false, + UpdateMemoriesFeatureStatus::validateStructure, + UpdateMemoriesFeatureStatus::deserialize + ); + register(119, "RemoveMapMarker", RemoveMapMarker.class, 1, 16384006, false, RemoveMapMarker::validateStructure, RemoveMapMarker::deserialize); + register(131, "SetChunk", SetChunk.class, 13, 12288040, true, SetChunk::validateStructure, SetChunk::deserialize); + register(132, "SetChunkHeightmap", SetChunkHeightmap.class, 9, 4096014, true, SetChunkHeightmap::validateStructure, SetChunkHeightmap::deserialize); + register(133, "SetChunkTintmap", SetChunkTintmap.class, 9, 4096014, true, SetChunkTintmap::validateStructure, SetChunkTintmap::deserialize); + register( + 134, "SetChunkEnvironments", SetChunkEnvironments.class, 9, 4096014, true, SetChunkEnvironments::validateStructure, SetChunkEnvironments::deserialize + ); + register(135, "UnloadChunk", UnloadChunk.class, 8, 8, false, UnloadChunk::validateStructure, UnloadChunk::deserialize); + register(136, "SetFluids", SetFluids.class, 13, 4096018, true, SetFluids::validateStructure, SetFluids::deserialize); + register(140, "ServerSetBlock", ServerSetBlock.class, 19, 19, false, ServerSetBlock::validateStructure, ServerSetBlock::deserialize); + register(141, "ServerSetBlocks", ServerSetBlocks.class, 12, 36864017, false, ServerSetBlocks::validateStructure, ServerSetBlocks::deserialize); + register(142, "ServerSetFluid", ServerSetFluid.class, 17, 17, false, ServerSetFluid::validateStructure, ServerSetFluid::deserialize); + register(143, "ServerSetFluids", ServerSetFluids.class, 12, 28672017, false, ServerSetFluids::validateStructure, ServerSetFluids::deserialize); + register(144, "UpdateBlockDamage", UpdateBlockDamage.class, 21, 21, false, UpdateBlockDamage::validateStructure, UpdateBlockDamage::deserialize); + register(145, "UpdateTimeSettings", UpdateTimeSettings.class, 10, 10, false, UpdateTimeSettings::validateStructure, UpdateTimeSettings::deserialize); + register(146, "UpdateTime", UpdateTime.class, 13, 13, false, UpdateTime::validateStructure, UpdateTime::deserialize); + register( + 147, + "UpdateEditorTimeOverride", + UpdateEditorTimeOverride.class, + 14, + 14, + false, + UpdateEditorTimeOverride::validateStructure, + UpdateEditorTimeOverride::deserialize + ); + register( + 148, + "ClearEditorTimeOverride", + ClearEditorTimeOverride.class, + 0, + 0, + false, + ClearEditorTimeOverride::validateStructure, + ClearEditorTimeOverride::deserialize + ); + register(149, "UpdateWeather", UpdateWeather.class, 8, 8, false, UpdateWeather::validateStructure, UpdateWeather::deserialize); + register( + 150, + "UpdateEditorWeatherOverride", + UpdateEditorWeatherOverride.class, + 4, + 4, + false, + UpdateEditorWeatherOverride::validateStructure, + UpdateEditorWeatherOverride::deserialize + ); + register( + 151, + "UpdateEnvironmentMusic", + UpdateEnvironmentMusic.class, + 4, + 4, + false, + UpdateEnvironmentMusic::validateStructure, + UpdateEnvironmentMusic::deserialize + ); + register( + 152, "SpawnParticleSystem", SpawnParticleSystem.class, 44, 16384049, false, SpawnParticleSystem::validateStructure, SpawnParticleSystem::deserialize + ); + register( + 153, + "SpawnBlockParticleSystem", + SpawnBlockParticleSystem.class, + 30, + 30, + false, + SpawnBlockParticleSystem::validateStructure, + SpawnBlockParticleSystem::deserialize + ); + register(154, "PlaySoundEvent2D", PlaySoundEvent2D.class, 13, 13, false, PlaySoundEvent2D::validateStructure, PlaySoundEvent2D::deserialize); + register(155, "PlaySoundEvent3D", PlaySoundEvent3D.class, 38, 38, false, PlaySoundEvent3D::validateStructure, PlaySoundEvent3D::deserialize); + register( + 156, "PlaySoundEventEntity", PlaySoundEventEntity.class, 16, 16, false, PlaySoundEventEntity::validateStructure, PlaySoundEventEntity::deserialize + ); + register(157, "UpdateSleepState", UpdateSleepState.class, 36, 65536050, false, UpdateSleepState::validateStructure, UpdateSleepState::deserialize); + register(158, "SetPaused", SetPaused.class, 1, 1, false, SetPaused::validateStructure, SetPaused::deserialize); + register(159, "ServerSetPaused", ServerSetPaused.class, 1, 1, false, ServerSetPaused::validateStructure, ServerSetPaused::deserialize); + register(160, "SetEntitySeed", SetEntitySeed.class, 4, 4, false, SetEntitySeed::validateStructure, SetEntitySeed::deserialize); + register(161, "EntityUpdates", EntityUpdates.class, 1, 1677721600, true, EntityUpdates::validateStructure, EntityUpdates::deserialize); + register(162, "PlayAnimation", PlayAnimation.class, 6, 32768024, false, PlayAnimation::validateStructure, PlayAnimation::deserialize); + register(163, "ChangeVelocity", ChangeVelocity.class, 35, 35, false, ChangeVelocity::validateStructure, ChangeVelocity::deserialize); + register(164, "ApplyKnockback", ApplyKnockback.class, 38, 38, false, ApplyKnockback::validateStructure, ApplyKnockback::deserialize); + register( + 165, "SpawnModelParticles", SpawnModelParticles.class, 5, 1677721600, false, SpawnModelParticles::validateStructure, SpawnModelParticles::deserialize + ); + register(166, "MountMovement", MountMovement.class, 59, 59, false, MountMovement::validateStructure, MountMovement::deserialize); + register( + 170, + "UpdatePlayerInventory", + UpdatePlayerInventory.class, + 2, + 1677721600, + true, + UpdatePlayerInventory::validateStructure, + UpdatePlayerInventory::deserialize + ); + register(171, "SetCreativeItem", SetCreativeItem.class, 9, 16384019, false, SetCreativeItem::validateStructure, SetCreativeItem::deserialize); + register(172, "DropCreativeItem", DropCreativeItem.class, 0, 16384010, false, DropCreativeItem::validateStructure, DropCreativeItem::deserialize); + register( + 173, + "SmartGiveCreativeItem", + SmartGiveCreativeItem.class, + 1, + 16384011, + false, + SmartGiveCreativeItem::validateStructure, + SmartGiveCreativeItem::deserialize + ); + register(174, "DropItemStack", DropItemStack.class, 12, 12, false, DropItemStack::validateStructure, DropItemStack::deserialize); + register(175, "MoveItemStack", MoveItemStack.class, 20, 20, false, MoveItemStack::validateStructure, MoveItemStack::deserialize); + register(176, "SmartMoveItemStack", SmartMoveItemStack.class, 13, 13, false, SmartMoveItemStack::validateStructure, SmartMoveItemStack::deserialize); + register(177, "SetActiveSlot", SetActiveSlot.class, 8, 8, false, SetActiveSlot::validateStructure, SetActiveSlot::deserialize); + register( + 178, + "SwitchHotbarBlockSet", + SwitchHotbarBlockSet.class, + 1, + 16384006, + false, + SwitchHotbarBlockSet::validateStructure, + SwitchHotbarBlockSet::deserialize + ); + register(179, "InventoryAction", InventoryAction.class, 6, 6, false, InventoryAction::validateStructure, InventoryAction::deserialize); + register(200, "OpenWindow", OpenWindow.class, 6, 1677721600, true, OpenWindow::validateStructure, OpenWindow::deserialize); + register(201, "UpdateWindow", UpdateWindow.class, 5, 1677721600, true, UpdateWindow::validateStructure, UpdateWindow::deserialize); + register(202, "CloseWindow", CloseWindow.class, 4, 4, false, CloseWindow::validateStructure, CloseWindow::deserialize); + register(203, "SendWindowAction", SendWindowAction.class, 4, 32768027, false, SendWindowAction::validateStructure, SendWindowAction::deserialize); + register(204, "ClientOpenWindow", ClientOpenWindow.class, 1, 1, false, ClientOpenWindow::validateStructure, ClientOpenWindow::deserialize); + register(210, "ServerMessage", ServerMessage.class, 2, 1677721600, false, ServerMessage::validateStructure, ServerMessage::deserialize); + register(211, "ChatMessage", ChatMessage.class, 1, 16384006, false, ChatMessage::validateStructure, ChatMessage::deserialize); + register(212, "Notification", Notification.class, 2, 1677721600, false, Notification::validateStructure, Notification::deserialize); + register(213, "KillFeedMessage", KillFeedMessage.class, 1, 1677721600, false, KillFeedMessage::validateStructure, KillFeedMessage::deserialize); + register(214, "ShowEventTitle", ShowEventTitle.class, 14, 1677721600, false, ShowEventTitle::validateStructure, ShowEventTitle::deserialize); + register(215, "HideEventTitle", HideEventTitle.class, 4, 4, false, HideEventTitle::validateStructure, HideEventTitle::deserialize); + register(216, "SetPage", SetPage.class, 2, 2, false, SetPage::validateStructure, SetPage::deserialize); + register(217, "CustomHud", CustomHud.class, 2, 1677721600, true, CustomHud::validateStructure, CustomHud::deserialize); + register(218, "CustomPage", CustomPage.class, 4, 1677721600, true, CustomPage::validateStructure, CustomPage::deserialize); + register(219, "CustomPageEvent", CustomPageEvent.class, 2, 16384007, false, CustomPageEvent::validateStructure, CustomPageEvent::deserialize); + register(222, "EditorBlocksChange", EditorBlocksChange.class, 30, 139264048, true, EditorBlocksChange::validateStructure, EditorBlocksChange::deserialize); + register(223, "ServerInfo", ServerInfo.class, 5, 32768023, false, ServerInfo::validateStructure, ServerInfo::deserialize); + register( + 224, + "AddToServerPlayerList", + AddToServerPlayerList.class, + 1, + 1677721600, + false, + AddToServerPlayerList::validateStructure, + AddToServerPlayerList::deserialize + ); + register( + 225, + "RemoveFromServerPlayerList", + RemoveFromServerPlayerList.class, + 1, + 65536006, + false, + RemoveFromServerPlayerList::validateStructure, + RemoveFromServerPlayerList::deserialize + ); + register( + 226, + "UpdateServerPlayerList", + UpdateServerPlayerList.class, + 1, + 131072006, + false, + UpdateServerPlayerList::validateStructure, + UpdateServerPlayerList::deserialize + ); + register( + 227, + "UpdateServerPlayerListPing", + UpdateServerPlayerListPing.class, + 1, + 81920006, + false, + UpdateServerPlayerListPing::validateStructure, + UpdateServerPlayerListPing::deserialize + ); + register( + 228, "UpdateKnownRecipes", UpdateKnownRecipes.class, 1, 1677721600, false, UpdateKnownRecipes::validateStructure, UpdateKnownRecipes::deserialize + ); + register(229, "UpdatePortal", UpdatePortal.class, 6, 16384020, false, UpdatePortal::validateStructure, UpdatePortal::deserialize); + register( + 230, + "UpdateVisibleHudComponents", + UpdateVisibleHudComponents.class, + 1, + 4096006, + false, + UpdateVisibleHudComponents::validateStructure, + UpdateVisibleHudComponents::deserialize + ); + register( + 231, + "ResetUserInterfaceState", + ResetUserInterfaceState.class, + 0, + 0, + false, + ResetUserInterfaceState::validateStructure, + ResetUserInterfaceState::deserialize + ); + register(232, "UpdateLanguage", UpdateLanguage.class, 1, 16384006, false, UpdateLanguage::validateStructure, UpdateLanguage::deserialize); + register(233, "WorldSavingStatus", WorldSavingStatus.class, 1, 1, false, WorldSavingStatus::validateStructure, WorldSavingStatus::deserialize); + register( + 234, "OpenChatWithCommand", OpenChatWithCommand.class, 1, 16384006, false, OpenChatWithCommand::validateStructure, OpenChatWithCommand::deserialize + ); + register( + 240, + "UpdateWorldMapSettings", + UpdateWorldMapSettings.class, + 16, + 1677721600, + false, + UpdateWorldMapSettings::validateStructure, + UpdateWorldMapSettings::deserialize + ); + register(241, "UpdateWorldMap", UpdateWorldMap.class, 1, 1677721600, true, UpdateWorldMap::validateStructure, UpdateWorldMap::deserialize); + register(242, "ClearWorldMap", ClearWorldMap.class, 0, 0, false, ClearWorldMap::validateStructure, ClearWorldMap::deserialize); + register( + 243, "UpdateWorldMapVisible", UpdateWorldMapVisible.class, 1, 1, false, UpdateWorldMapVisible::validateStructure, UpdateWorldMapVisible::deserialize + ); + register( + 244, + "TeleportToWorldMapMarker", + TeleportToWorldMapMarker.class, + 1, + 16384006, + false, + TeleportToWorldMapMarker::validateStructure, + TeleportToWorldMapMarker::deserialize + ); + register( + 245, + "TeleportToWorldMapPosition", + TeleportToWorldMapPosition.class, + 8, + 8, + false, + TeleportToWorldMapPosition::validateStructure, + TeleportToWorldMapPosition::deserialize + ); + register(250, "RequestServerAccess", RequestServerAccess.class, 3, 3, false, RequestServerAccess::validateStructure, RequestServerAccess::deserialize); + register( + 251, "UpdateServerAccess", UpdateServerAccess.class, 2, 1677721600, false, UpdateServerAccess::validateStructure, UpdateServerAccess::deserialize + ); + register(252, "SetServerAccess", SetServerAccess.class, 2, 16384007, false, SetServerAccess::validateStructure, SetServerAccess::deserialize); + register( + 260, + "RequestMachinimaActorModel", + RequestMachinimaActorModel.class, + 1, + 49152028, + false, + RequestMachinimaActorModel::validateStructure, + RequestMachinimaActorModel::deserialize + ); + register( + 261, + "SetMachinimaActorModel", + SetMachinimaActorModel.class, + 1, + 1677721600, + false, + SetMachinimaActorModel::validateStructure, + SetMachinimaActorModel::deserialize + ); + register( + 262, "UpdateMachinimaScene", UpdateMachinimaScene.class, 6, 36864033, true, UpdateMachinimaScene::validateStructure, UpdateMachinimaScene::deserialize + ); + register(280, "SetServerCamera", SetServerCamera.class, 157, 157, false, SetServerCamera::validateStructure, SetServerCamera::deserialize); + register(281, "CameraShakeEffect", CameraShakeEffect.class, 9, 9, false, CameraShakeEffect::validateStructure, CameraShakeEffect::deserialize); + register(282, "RequestFlyCameraMode", RequestFlyCameraMode.class, 1, 1, false, RequestFlyCameraMode::validateStructure, RequestFlyCameraMode::deserialize); + register(283, "SetFlyCameraMode", SetFlyCameraMode.class, 1, 1, false, SetFlyCameraMode::validateStructure, SetFlyCameraMode::deserialize); + register( + 290, + "SyncInteractionChains", + SyncInteractionChains.class, + 0, + 1677721600, + false, + SyncInteractionChains::validateStructure, + SyncInteractionChains::deserialize + ); + register( + 291, + "CancelInteractionChain", + CancelInteractionChain.class, + 5, + 1038, + false, + CancelInteractionChain::validateStructure, + CancelInteractionChain::deserialize + ); + register(292, "PlayInteractionFor", PlayInteractionFor.class, 19, 16385065, false, PlayInteractionFor::validateStructure, PlayInteractionFor::deserialize); + register(293, "MountNPC", MountNPC.class, 16, 16, false, MountNPC::validateStructure, MountNPC::deserialize); + register(294, "DismountNPC", DismountNPC.class, 0, 0, false, DismountNPC::validateStructure, DismountNPC::deserialize); + register(300, "FailureReply", FailureReply.class, 5, 1677721600, false, FailureReply::validateStructure, FailureReply::deserialize); + register(301, "SuccessReply", SuccessReply.class, 5, 1677721600, false, SuccessReply::validateStructure, SuccessReply::deserialize); + register( + 302, "AssetEditorInitialize", AssetEditorInitialize.class, 0, 0, false, AssetEditorInitialize::validateStructure, AssetEditorInitialize::deserialize + ); + register( + 303, + "AssetEditorAuthorization", + AssetEditorAuthorization.class, + 1, + 1, + false, + AssetEditorAuthorization::validateStructure, + AssetEditorAuthorization::deserialize + ); + register( + 304, + "AssetEditorCapabilities", + AssetEditorCapabilities.class, + 5, + 5, + false, + AssetEditorCapabilities::validateStructure, + AssetEditorCapabilities::deserialize + ); + register( + 305, + "AssetEditorSetupSchemas", + AssetEditorSetupSchemas.class, + 1, + 1677721600, + true, + AssetEditorSetupSchemas::validateStructure, + AssetEditorSetupSchemas::deserialize + ); + register( + 306, + "AssetEditorSetupAssetTypes", + AssetEditorSetupAssetTypes.class, + 1, + 1677721600, + false, + AssetEditorSetupAssetTypes::validateStructure, + AssetEditorSetupAssetTypes::deserialize + ); + register( + 307, + "AssetEditorCreateDirectory", + AssetEditorCreateDirectory.class, + 5, + 32768024, + false, + AssetEditorCreateDirectory::validateStructure, + AssetEditorCreateDirectory::deserialize + ); + register( + 308, + "AssetEditorDeleteDirectory", + AssetEditorDeleteDirectory.class, + 5, + 32768024, + false, + AssetEditorDeleteDirectory::validateStructure, + AssetEditorDeleteDirectory::deserialize + ); + register( + 309, + "AssetEditorRenameDirectory", + AssetEditorRenameDirectory.class, + 5, + 65536051, + false, + AssetEditorRenameDirectory::validateStructure, + AssetEditorRenameDirectory::deserialize + ); + register( + 310, + "AssetEditorFetchAsset", + AssetEditorFetchAsset.class, + 6, + 32768025, + false, + AssetEditorFetchAsset::validateStructure, + AssetEditorFetchAsset::deserialize + ); + register( + 311, + "AssetEditorFetchJsonAssetWithParents", + AssetEditorFetchJsonAssetWithParents.class, + 6, + 32768025, + false, + AssetEditorFetchJsonAssetWithParents::validateStructure, + AssetEditorFetchJsonAssetWithParents::deserialize + ); + register( + 312, + "AssetEditorFetchAssetReply", + AssetEditorFetchAssetReply.class, + 5, + 4096010, + false, + AssetEditorFetchAssetReply::validateStructure, + AssetEditorFetchAssetReply::deserialize + ); + register( + 313, + "AssetEditorFetchJsonAssetWithParentsReply", + AssetEditorFetchJsonAssetWithParentsReply.class, + 5, + 1677721600, + true, + AssetEditorFetchJsonAssetWithParentsReply::validateStructure, + AssetEditorFetchJsonAssetWithParentsReply::deserialize + ); + register( + 314, + "AssetEditorAssetPackSetup", + AssetEditorAssetPackSetup.class, + 1, + 1677721600, + false, + AssetEditorAssetPackSetup::validateStructure, + AssetEditorAssetPackSetup::deserialize + ); + register( + 315, + "AssetEditorUpdateAssetPack", + AssetEditorUpdateAssetPack.class, + 1, + 1677721600, + false, + AssetEditorUpdateAssetPack::validateStructure, + AssetEditorUpdateAssetPack::deserialize + ); + register( + 316, + "AssetEditorCreateAssetPack", + AssetEditorCreateAssetPack.class, + 5, + 1677721600, + false, + AssetEditorCreateAssetPack::validateStructure, + AssetEditorCreateAssetPack::deserialize + ); + register( + 317, + "AssetEditorDeleteAssetPack", + AssetEditorDeleteAssetPack.class, + 1, + 16384006, + false, + AssetEditorDeleteAssetPack::validateStructure, + AssetEditorDeleteAssetPack::deserialize + ); + register( + 318, + "AssetEditorEnableAssetPack", + AssetEditorEnableAssetPack.class, + 2, + 16384007, + false, + AssetEditorEnableAssetPack::validateStructure, + AssetEditorEnableAssetPack::deserialize + ); + register( + 319, + "AssetEditorAssetListSetup", + AssetEditorAssetListSetup.class, + 4, + 1677721600, + true, + AssetEditorAssetListSetup::validateStructure, + AssetEditorAssetListSetup::deserialize + ); + register( + 320, + "AssetEditorAssetListUpdate", + AssetEditorAssetListUpdate.class, + 1, + 1677721600, + true, + AssetEditorAssetListUpdate::validateStructure, + AssetEditorAssetListUpdate::deserialize + ); + register( + 321, + "AssetEditorRequestChildrenList", + AssetEditorRequestChildrenList.class, + 1, + 32768020, + false, + AssetEditorRequestChildrenList::validateStructure, + AssetEditorRequestChildrenList::deserialize + ); + register( + 322, + "AssetEditorRequestChildrenListReply", + AssetEditorRequestChildrenListReply.class, + 1, + 1677721600, + false, + AssetEditorRequestChildrenListReply::validateStructure, + AssetEditorRequestChildrenListReply::deserialize + ); + register( + 323, + "AssetEditorUpdateJsonAsset", + AssetEditorUpdateJsonAsset.class, + 9, + 1677721600, + true, + AssetEditorUpdateJsonAsset::validateStructure, + AssetEditorUpdateJsonAsset::deserialize + ); + register( + 324, + "AssetEditorUpdateAsset", + AssetEditorUpdateAsset.class, + 9, + 53248050, + false, + AssetEditorUpdateAsset::validateStructure, + AssetEditorUpdateAsset::deserialize + ); + register( + 325, + "AssetEditorJsonAssetUpdated", + AssetEditorJsonAssetUpdated.class, + 1, + 1677721600, + false, + AssetEditorJsonAssetUpdated::validateStructure, + AssetEditorJsonAssetUpdated::deserialize + ); + register( + 326, + "AssetEditorAssetUpdated", + AssetEditorAssetUpdated.class, + 1, + 36864033, + false, + AssetEditorAssetUpdated::validateStructure, + AssetEditorAssetUpdated::deserialize + ); + register( + 327, + "AssetEditorCreateAsset", + AssetEditorCreateAsset.class, + 10, + 53248051, + false, + AssetEditorCreateAsset::validateStructure, + AssetEditorCreateAsset::deserialize + ); + register( + 328, + "AssetEditorRenameAsset", + AssetEditorRenameAsset.class, + 5, + 65536051, + false, + AssetEditorRenameAsset::validateStructure, + AssetEditorRenameAsset::deserialize + ); + register( + 329, + "AssetEditorDeleteAsset", + AssetEditorDeleteAsset.class, + 5, + 32768024, + false, + AssetEditorDeleteAsset::validateStructure, + AssetEditorDeleteAsset::deserialize + ); + register( + 330, + "AssetEditorDiscardChanges", + AssetEditorDiscardChanges.class, + 1, + 1677721600, + false, + AssetEditorDiscardChanges::validateStructure, + AssetEditorDiscardChanges::deserialize + ); + register( + 331, + "AssetEditorFetchAutoCompleteData", + AssetEditorFetchAutoCompleteData.class, + 5, + 32768023, + false, + AssetEditorFetchAutoCompleteData::validateStructure, + AssetEditorFetchAutoCompleteData::deserialize + ); + register( + 332, + "AssetEditorFetchAutoCompleteDataReply", + AssetEditorFetchAutoCompleteDataReply.class, + 5, + 1677721600, + false, + AssetEditorFetchAutoCompleteDataReply::validateStructure, + AssetEditorFetchAutoCompleteDataReply::deserialize + ); + register( + 333, + "AssetEditorRequestDataset", + AssetEditorRequestDataset.class, + 1, + 16384006, + false, + AssetEditorRequestDataset::validateStructure, + AssetEditorRequestDataset::deserialize + ); + register( + 334, + "AssetEditorRequestDatasetReply", + AssetEditorRequestDatasetReply.class, + 1, + 1677721600, + false, + AssetEditorRequestDatasetReply::validateStructure, + AssetEditorRequestDatasetReply::deserialize + ); + register( + 335, + "AssetEditorActivateButton", + AssetEditorActivateButton.class, + 1, + 16384006, + false, + AssetEditorActivateButton::validateStructure, + AssetEditorActivateButton::deserialize + ); + register( + 336, + "AssetEditorSelectAsset", + AssetEditorSelectAsset.class, + 1, + 32768020, + false, + AssetEditorSelectAsset::validateStructure, + AssetEditorSelectAsset::deserialize + ); + register( + 337, + "AssetEditorPopupNotification", + AssetEditorPopupNotification.class, + 2, + 1677721600, + false, + AssetEditorPopupNotification::validateStructure, + AssetEditorPopupNotification::deserialize + ); + register( + 338, + "AssetEditorFetchLastModifiedAssets", + AssetEditorFetchLastModifiedAssets.class, + 0, + 0, + false, + AssetEditorFetchLastModifiedAssets::validateStructure, + AssetEditorFetchLastModifiedAssets::deserialize + ); + register( + 339, + "AssetEditorLastModifiedAssets", + AssetEditorLastModifiedAssets.class, + 1, + 1677721600, + false, + AssetEditorLastModifiedAssets::validateStructure, + AssetEditorLastModifiedAssets::deserialize + ); + register( + 340, + "AssetEditorModifiedAssetsCount", + AssetEditorModifiedAssetsCount.class, + 4, + 4, + false, + AssetEditorModifiedAssetsCount::validateStructure, + AssetEditorModifiedAssetsCount::deserialize + ); + register( + 341, + "AssetEditorSubscribeModifiedAssetsChanges", + AssetEditorSubscribeModifiedAssetsChanges.class, + 1, + 1, + false, + AssetEditorSubscribeModifiedAssetsChanges::validateStructure, + AssetEditorSubscribeModifiedAssetsChanges::deserialize + ); + register( + 342, + "AssetEditorExportAssets", + AssetEditorExportAssets.class, + 1, + 1677721600, + false, + AssetEditorExportAssets::validateStructure, + AssetEditorExportAssets::deserialize + ); + register( + 343, + "AssetEditorExportAssetInitialize", + AssetEditorExportAssetInitialize.class, + 6, + 81920066, + false, + AssetEditorExportAssetInitialize::validateStructure, + AssetEditorExportAssetInitialize::deserialize + ); + register( + 344, + "AssetEditorExportAssetPart", + AssetEditorExportAssetPart.class, + 1, + 4096006, + true, + AssetEditorExportAssetPart::validateStructure, + AssetEditorExportAssetPart::deserialize + ); + register( + 345, + "AssetEditorExportAssetFinalize", + AssetEditorExportAssetFinalize.class, + 0, + 0, + false, + AssetEditorExportAssetFinalize::validateStructure, + AssetEditorExportAssetFinalize::deserialize + ); + register( + 346, + "AssetEditorExportDeleteAssets", + AssetEditorExportDeleteAssets.class, + 1, + 1677721600, + false, + AssetEditorExportDeleteAssets::validateStructure, + AssetEditorExportDeleteAssets::deserialize + ); + register( + 347, + "AssetEditorExportComplete", + AssetEditorExportComplete.class, + 1, + 1677721600, + false, + AssetEditorExportComplete::validateStructure, + AssetEditorExportComplete::deserialize + ); + register( + 348, + "AssetEditorRebuildCaches", + AssetEditorRebuildCaches.class, + 5, + 5, + false, + AssetEditorRebuildCaches::validateStructure, + AssetEditorRebuildCaches::deserialize + ); + register( + 349, + "AssetEditorUndoChanges", + AssetEditorUndoChanges.class, + 5, + 32768024, + false, + AssetEditorUndoChanges::validateStructure, + AssetEditorUndoChanges::deserialize + ); + register( + 350, + "AssetEditorRedoChanges", + AssetEditorRedoChanges.class, + 5, + 32768024, + false, + AssetEditorRedoChanges::validateStructure, + AssetEditorRedoChanges::deserialize + ); + register( + 351, + "AssetEditorUndoRedoReply", + AssetEditorUndoRedoReply.class, + 5, + 1677721600, + false, + AssetEditorUndoRedoReply::validateStructure, + AssetEditorUndoRedoReply::deserialize + ); + register( + 352, + "AssetEditorSetGameTime", + AssetEditorSetGameTime.class, + 14, + 14, + false, + AssetEditorSetGameTime::validateStructure, + AssetEditorSetGameTime::deserialize + ); + register( + 353, + "AssetEditorUpdateSecondsPerGameDay", + AssetEditorUpdateSecondsPerGameDay.class, + 8, + 8, + false, + AssetEditorUpdateSecondsPerGameDay::validateStructure, + AssetEditorUpdateSecondsPerGameDay::deserialize + ); + register( + 354, + "AssetEditorUpdateWeatherPreviewLock", + AssetEditorUpdateWeatherPreviewLock.class, + 1, + 1, + false, + AssetEditorUpdateWeatherPreviewLock::validateStructure, + AssetEditorUpdateWeatherPreviewLock::deserialize + ); + register( + 355, + "AssetEditorUpdateModelPreview", + AssetEditorUpdateModelPreview.class, + 30, + 1677721600, + false, + AssetEditorUpdateModelPreview::validateStructure, + AssetEditorUpdateModelPreview::deserialize + ); + register(360, "UpdateSunSettings", UpdateSunSettings.class, 8, 8, false, UpdateSunSettings::validateStructure, UpdateSunSettings::deserialize); + register( + 361, "UpdatePostFxSettings", UpdatePostFxSettings.class, 20, 20, false, UpdatePostFxSettings::validateStructure, UpdatePostFxSettings::deserialize + ); + register( + 400, + "BuilderToolArgUpdate", + BuilderToolArgUpdate.class, + 14, + 32768032, + false, + BuilderToolArgUpdate::validateStructure, + BuilderToolArgUpdate::deserialize + ); + register( + 401, + "BuilderToolEntityAction", + BuilderToolEntityAction.class, + 5, + 5, + false, + BuilderToolEntityAction::validateStructure, + BuilderToolEntityAction::deserialize + ); + register( + 402, + "BuilderToolSetEntityTransform", + BuilderToolSetEntityTransform.class, + 54, + 54, + false, + BuilderToolSetEntityTransform::validateStructure, + BuilderToolSetEntityTransform::deserialize + ); + register( + 403, + "BuilderToolExtrudeAction", + BuilderToolExtrudeAction.class, + 24, + 24, + false, + BuilderToolExtrudeAction::validateStructure, + BuilderToolExtrudeAction::deserialize + ); + register( + 404, "BuilderToolStackArea", BuilderToolStackArea.class, 41, 41, false, BuilderToolStackArea::validateStructure, BuilderToolStackArea::deserialize + ); + register( + 405, + "BuilderToolSelectionTransform", + BuilderToolSelectionTransform.class, + 52, + 16384057, + false, + BuilderToolSelectionTransform::validateStructure, + BuilderToolSelectionTransform::deserialize + ); + register( + 406, + "BuilderToolRotateClipboard", + BuilderToolRotateClipboard.class, + 5, + 5, + false, + BuilderToolRotateClipboard::validateStructure, + BuilderToolRotateClipboard::deserialize + ); + register( + 407, + "BuilderToolPasteClipboard", + BuilderToolPasteClipboard.class, + 12, + 12, + false, + BuilderToolPasteClipboard::validateStructure, + BuilderToolPasteClipboard::deserialize + ); + register( + 408, + "BuilderToolSetTransformationModeState", + BuilderToolSetTransformationModeState.class, + 1, + 1, + false, + BuilderToolSetTransformationModeState::validateStructure, + BuilderToolSetTransformationModeState::deserialize + ); + register( + 409, + "BuilderToolSelectionUpdate", + BuilderToolSelectionUpdate.class, + 24, + 24, + false, + BuilderToolSelectionUpdate::validateStructure, + BuilderToolSelectionUpdate::deserialize + ); + register( + 410, + "BuilderToolSelectionToolAskForClipboard", + BuilderToolSelectionToolAskForClipboard.class, + 0, + 0, + false, + BuilderToolSelectionToolAskForClipboard::validateStructure, + BuilderToolSelectionToolAskForClipboard::deserialize + ); + register( + 411, + "BuilderToolSelectionToolReplyWithClipboard", + BuilderToolSelectionToolReplyWithClipboard.class, + 1, + 139264019, + true, + BuilderToolSelectionToolReplyWithClipboard::validateStructure, + BuilderToolSelectionToolReplyWithClipboard::deserialize + ); + register( + 412, + "BuilderToolGeneralAction", + BuilderToolGeneralAction.class, + 1, + 1, + false, + BuilderToolGeneralAction::validateStructure, + BuilderToolGeneralAction::deserialize + ); + register( + 413, + "BuilderToolOnUseInteraction", + BuilderToolOnUseInteraction.class, + 57, + 57, + false, + BuilderToolOnUseInteraction::validateStructure, + BuilderToolOnUseInteraction::deserialize + ); + register( + 414, "BuilderToolLineAction", BuilderToolLineAction.class, 24, 24, false, BuilderToolLineAction::validateStructure, BuilderToolLineAction::deserialize + ); + register( + 415, "BuilderToolShowAnchor", BuilderToolShowAnchor.class, 12, 12, false, BuilderToolShowAnchor::validateStructure, BuilderToolShowAnchor::deserialize + ); + register( + 416, + "BuilderToolHideAnchors", + BuilderToolHideAnchors.class, + 0, + 0, + false, + BuilderToolHideAnchors::validateStructure, + BuilderToolHideAnchors::deserialize + ); + register(417, "PrefabUnselectPrefab", PrefabUnselectPrefab.class, 0, 0, false, PrefabUnselectPrefab::validateStructure, PrefabUnselectPrefab::deserialize); + register( + 418, + "BuilderToolsSetSoundSet", + BuilderToolsSetSoundSet.class, + 4, + 4, + false, + BuilderToolsSetSoundSet::validateStructure, + BuilderToolsSetSoundSet::deserialize + ); + register( + 419, + "BuilderToolLaserPointer", + BuilderToolLaserPointer.class, + 36, + 36, + false, + BuilderToolLaserPointer::validateStructure, + BuilderToolLaserPointer::deserialize + ); + register( + 420, + "BuilderToolSetEntityScale", + BuilderToolSetEntityScale.class, + 8, + 8, + false, + BuilderToolSetEntityScale::validateStructure, + BuilderToolSetEntityScale::deserialize + ); + register( + 421, + "BuilderToolSetEntityPickupEnabled", + BuilderToolSetEntityPickupEnabled.class, + 5, + 5, + false, + BuilderToolSetEntityPickupEnabled::validateStructure, + BuilderToolSetEntityPickupEnabled::deserialize + ); + register( + 422, + "BuilderToolSetEntityLight", + BuilderToolSetEntityLight.class, + 9, + 9, + false, + BuilderToolSetEntityLight::validateStructure, + BuilderToolSetEntityLight::deserialize + ); + register( + 423, + "BuilderToolSetNPCDebug", + BuilderToolSetNPCDebug.class, + 5, + 5, + false, + BuilderToolSetNPCDebug::validateStructure, + BuilderToolSetNPCDebug::deserialize + ); + } + + public record PacketInfo( + int id, + @Nonnull String name, + @Nonnull Class type, + int fixedBlockSize, + int maxSize, + boolean compressed, + @Nonnull BiFunction validate, + @Nonnull BiFunction deserialize + ) { + } +} diff --git a/src/com/hypixel/hytale/protocol/ParallelInteraction.java b/src/com/hypixel/hytale/protocol/ParallelInteraction.java new file mode 100644 index 0000000..0c33d83 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ParallelInteraction.java @@ -0,0 +1,581 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ParallelInteraction extends Interaction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 11; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 35; + public static final int MAX_SIZE = 1677721600; + @Nullable + public int[] next; + + public ParallelInteraction() { + } + + public ParallelInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + @Nullable int[] next + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + } + + public ParallelInteraction(@Nonnull ParallelInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + } + + @Nonnull + public static ParallelInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + ParallelInteraction obj = new ParallelInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 35 + buf.getIntLE(offset + 11); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 35 + buf.getIntLE(offset + 15); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 35 + buf.getIntLE(offset + 19); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 35 + buf.getIntLE(offset + 23); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 35 + buf.getIntLE(offset + 27); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 35 + buf.getIntLE(offset + 31); + int nextCount = VarInt.peek(buf, varPos5); + if (nextCount < 0) { + throw ProtocolException.negativeLength("Next", nextCount); + } + + if (nextCount > 4096000) { + throw ProtocolException.arrayTooLong("Next", nextCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + if (varPos5 + varIntLen + nextCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Next", varPos5 + varIntLen + nextCount * 4, buf.readableBytes()); + } + + obj.next = new int[nextCount]; + + for (int ix = 0; ix < nextCount; ix++) { + obj.next[ix] = buf.getIntLE(varPos5 + varIntLen + ix * 4); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 35; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 11); + int pos0 = offset + 35 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 15); + int pos1 = offset + 35 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 19); + int pos2 = offset + 35 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 23); + int pos3 = offset + 35 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 27); + int pos4 = offset + 35 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 31); + int pos5 = offset + 35 + fieldOffset5; + int arrLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + arrLen * 4; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.next != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int nextOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.next != null) { + buf.setIntLE(nextOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.next.length > 4096000) { + throw ProtocolException.arrayTooLong("Next", this.next.length, 4096000); + } + + VarInt.write(buf, this.next.length); + + for (int item : this.next) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(nextOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 35; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.next != null) { + size += VarInt.size(this.next.length) + this.next.length * 4; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 35) { + return ValidationResult.error("Buffer too small: expected at least 35 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 11); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 35 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 15); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 35 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 19); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 35 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 23); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 35 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 27); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 35 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int nextOffset = buffer.getIntLE(offset + 31); + if (nextOffset < 0) { + return ValidationResult.error("Invalid offset for Next"); + } + + int posxxxxx = offset + 35 + nextOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Next"); + } + + int nextCount = VarInt.peek(buffer, posxxxxx); + if (nextCount < 0) { + return ValidationResult.error("Invalid array count for Next"); + } + + if (nextCount > 4096000) { + return ValidationResult.error("Next exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += nextCount * 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Next"); + } + } + + return ValidationResult.OK; + } + } + + public ParallelInteraction clone() { + ParallelInteraction copy = new ParallelInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next != null ? Arrays.copyOf(this.next, this.next.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ParallelInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && Arrays.equals(this.next, other.next); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + return 31 * result + Arrays.hashCode(this.next); + } +} diff --git a/src/com/hypixel/hytale/protocol/ParamValue.java b/src/com/hypixel/hytale/protocol/ParamValue.java new file mode 100644 index 0000000..9956b5f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ParamValue.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public abstract class ParamValue { + public static final int MAX_SIZE = 16384011; + + public ParamValue() { + } + + @Nonnull + public static ParamValue deserialize(@Nonnull ByteBuf buf, int offset) { + int typeId = VarInt.peek(buf, offset); + int typeIdLen = VarInt.length(buf, offset); + + return (ParamValue)(switch (typeId) { + case 0 -> StringParamValue.deserialize(buf, offset + typeIdLen); + case 1 -> BoolParamValue.deserialize(buf, offset + typeIdLen); + case 2 -> DoubleParamValue.deserialize(buf, offset + typeIdLen); + case 3 -> IntParamValue.deserialize(buf, offset + typeIdLen); + case 4 -> LongParamValue.deserialize(buf, offset + typeIdLen); + default -> throw ProtocolException.unknownPolymorphicType("ParamValue", typeId); + }); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + int typeId = VarInt.peek(buf, offset); + int typeIdLen = VarInt.length(buf, offset); + + return typeIdLen + switch (typeId) { + case 0 -> StringParamValue.computeBytesConsumed(buf, offset + typeIdLen); + case 1 -> BoolParamValue.computeBytesConsumed(buf, offset + typeIdLen); + case 2 -> DoubleParamValue.computeBytesConsumed(buf, offset + typeIdLen); + case 3 -> IntParamValue.computeBytesConsumed(buf, offset + typeIdLen); + case 4 -> LongParamValue.computeBytesConsumed(buf, offset + typeIdLen); + default -> throw ProtocolException.unknownPolymorphicType("ParamValue", typeId); + }; + } + + public int getTypeId() { + if (this instanceof StringParamValue sub) { + return 0; + } else if (this instanceof BoolParamValue sub) { + return 1; + } else if (this instanceof DoubleParamValue sub) { + return 2; + } else if (this instanceof IntParamValue sub) { + return 3; + } else if (this instanceof LongParamValue sub) { + return 4; + } else { + throw new IllegalStateException("Unknown subtype: " + this.getClass().getName()); + } + } + + public abstract int serialize(@Nonnull ByteBuf var1); + + public abstract int computeSize(); + + public int serializeWithTypeId(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + VarInt.write(buf, this.getTypeId()); + this.serialize(buf); + return buf.writerIndex() - startPos; + } + + public int computeSizeWithTypeId() { + return VarInt.size(this.getTypeId()) + this.computeSize(); + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + int typeId = VarInt.peek(buffer, offset); + int typeIdLen = VarInt.length(buffer, offset); + + return switch (typeId) { + case 0 -> StringParamValue.validateStructure(buffer, offset + typeIdLen); + case 1 -> BoolParamValue.validateStructure(buffer, offset + typeIdLen); + case 2 -> DoubleParamValue.validateStructure(buffer, offset + typeIdLen); + case 3 -> IntParamValue.validateStructure(buffer, offset + typeIdLen); + case 4 -> LongParamValue.validateStructure(buffer, offset + typeIdLen); + default -> ValidationResult.error("Unknown polymorphic type ID " + typeId + " for ParamValue"); + }; + } +} diff --git a/src/com/hypixel/hytale/protocol/Particle.java b/src/com/hypixel/hytale/protocol/Particle.java new file mode 100644 index 0000000..af30391 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Particle.java @@ -0,0 +1,392 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Particle { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 133; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 141; + public static final int MAX_SIZE = 270336151; + @Nullable + public String texturePath; + @Nullable + public Size frameSize; + @Nonnull + public ParticleUVOption uvOption = ParticleUVOption.None; + @Nonnull + public ParticleScaleRatioConstraint scaleRatioConstraint = ParticleScaleRatioConstraint.OneToOne; + @Nonnull + public SoftParticle softParticles = SoftParticle.Enable; + public float softParticlesFadeFactor; + public boolean useSpriteBlending; + @Nullable + public ParticleAnimationFrame initialAnimationFrame; + @Nullable + public ParticleAnimationFrame collisionAnimationFrame; + @Nullable + public Map animationFrames; + + public Particle() { + } + + public Particle( + @Nullable String texturePath, + @Nullable Size frameSize, + @Nonnull ParticleUVOption uvOption, + @Nonnull ParticleScaleRatioConstraint scaleRatioConstraint, + @Nonnull SoftParticle softParticles, + float softParticlesFadeFactor, + boolean useSpriteBlending, + @Nullable ParticleAnimationFrame initialAnimationFrame, + @Nullable ParticleAnimationFrame collisionAnimationFrame, + @Nullable Map animationFrames + ) { + this.texturePath = texturePath; + this.frameSize = frameSize; + this.uvOption = uvOption; + this.scaleRatioConstraint = scaleRatioConstraint; + this.softParticles = softParticles; + this.softParticlesFadeFactor = softParticlesFadeFactor; + this.useSpriteBlending = useSpriteBlending; + this.initialAnimationFrame = initialAnimationFrame; + this.collisionAnimationFrame = collisionAnimationFrame; + this.animationFrames = animationFrames; + } + + public Particle(@Nonnull Particle other) { + this.texturePath = other.texturePath; + this.frameSize = other.frameSize; + this.uvOption = other.uvOption; + this.scaleRatioConstraint = other.scaleRatioConstraint; + this.softParticles = other.softParticles; + this.softParticlesFadeFactor = other.softParticlesFadeFactor; + this.useSpriteBlending = other.useSpriteBlending; + this.initialAnimationFrame = other.initialAnimationFrame; + this.collisionAnimationFrame = other.collisionAnimationFrame; + this.animationFrames = other.animationFrames; + } + + @Nonnull + public static Particle deserialize(@Nonnull ByteBuf buf, int offset) { + Particle obj = new Particle(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 2) != 0) { + obj.frameSize = Size.deserialize(buf, offset + 1); + } + + obj.uvOption = ParticleUVOption.fromValue(buf.getByte(offset + 9)); + obj.scaleRatioConstraint = ParticleScaleRatioConstraint.fromValue(buf.getByte(offset + 10)); + obj.softParticles = SoftParticle.fromValue(buf.getByte(offset + 11)); + obj.softParticlesFadeFactor = buf.getFloatLE(offset + 12); + obj.useSpriteBlending = buf.getByte(offset + 16) != 0; + if ((nullBits & 4) != 0) { + obj.initialAnimationFrame = ParticleAnimationFrame.deserialize(buf, offset + 17); + } + + if ((nullBits & 8) != 0) { + obj.collisionAnimationFrame = ParticleAnimationFrame.deserialize(buf, offset + 75); + } + + if ((nullBits & 1) != 0) { + int varPos0 = offset + 141 + buf.getIntLE(offset + 133); + int texturePathLen = VarInt.peek(buf, varPos0); + if (texturePathLen < 0) { + throw ProtocolException.negativeLength("TexturePath", texturePathLen); + } + + if (texturePathLen > 4096000) { + throw ProtocolException.stringTooLong("TexturePath", texturePathLen, 4096000); + } + + obj.texturePath = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 16) != 0) { + int varPos1 = offset + 141 + buf.getIntLE(offset + 137); + int animationFramesCount = VarInt.peek(buf, varPos1); + if (animationFramesCount < 0) { + throw ProtocolException.negativeLength("AnimationFrames", animationFramesCount); + } + + if (animationFramesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("AnimationFrames", animationFramesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.animationFrames = new HashMap<>(animationFramesCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < animationFramesCount; i++) { + int key = buf.getIntLE(dictPos); + dictPos += 4; + ParticleAnimationFrame val = ParticleAnimationFrame.deserialize(buf, dictPos); + dictPos += ParticleAnimationFrame.computeBytesConsumed(buf, dictPos); + if (obj.animationFrames.put(key, val) != null) { + throw ProtocolException.duplicateKey("animationFrames", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 141; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 133); + int pos0 = offset + 141 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 137); + int pos1 = offset + 141 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 += 4; + pos1 += ParticleAnimationFrame.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.texturePath != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.frameSize != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.initialAnimationFrame != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.collisionAnimationFrame != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.animationFrames != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + if (this.frameSize != null) { + this.frameSize.serialize(buf); + } else { + buf.writeZero(8); + } + + buf.writeByte(this.uvOption.getValue()); + buf.writeByte(this.scaleRatioConstraint.getValue()); + buf.writeByte(this.softParticles.getValue()); + buf.writeFloatLE(this.softParticlesFadeFactor); + buf.writeByte(this.useSpriteBlending ? 1 : 0); + if (this.initialAnimationFrame != null) { + this.initialAnimationFrame.serialize(buf); + } else { + buf.writeZero(58); + } + + if (this.collisionAnimationFrame != null) { + this.collisionAnimationFrame.serialize(buf); + } else { + buf.writeZero(58); + } + + int texturePathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int animationFramesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.texturePath != null) { + buf.setIntLE(texturePathOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.texturePath, 4096000); + } else { + buf.setIntLE(texturePathOffsetSlot, -1); + } + + if (this.animationFrames != null) { + buf.setIntLE(animationFramesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.animationFrames.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("AnimationFrames", this.animationFrames.size(), 4096000); + } + + VarInt.write(buf, this.animationFrames.size()); + + for (Entry e : this.animationFrames.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(animationFramesOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 141; + if (this.texturePath != null) { + size += PacketIO.stringSize(this.texturePath); + } + + if (this.animationFrames != null) { + size += VarInt.size(this.animationFrames.size()) + this.animationFrames.size() * 62; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 141) { + return ValidationResult.error("Buffer too small: expected at least 141 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int texturePathOffset = buffer.getIntLE(offset + 133); + if (texturePathOffset < 0) { + return ValidationResult.error("Invalid offset for TexturePath"); + } + + int pos = offset + 141 + texturePathOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for TexturePath"); + } + + int texturePathLen = VarInt.peek(buffer, pos); + if (texturePathLen < 0) { + return ValidationResult.error("Invalid string length for TexturePath"); + } + + if (texturePathLen > 4096000) { + return ValidationResult.error("TexturePath exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += texturePathLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading TexturePath"); + } + } + + if ((nullBits & 16) != 0) { + int animationFramesOffset = buffer.getIntLE(offset + 137); + if (animationFramesOffset < 0) { + return ValidationResult.error("Invalid offset for AnimationFrames"); + } + + int posx = offset + 141 + animationFramesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for AnimationFrames"); + } + + int animationFramesCount = VarInt.peek(buffer, posx); + if (animationFramesCount < 0) { + return ValidationResult.error("Invalid dictionary count for AnimationFrames"); + } + + if (animationFramesCount > 4096000) { + return ValidationResult.error("AnimationFrames exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < animationFramesCount; i++) { + posx += 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posx += 58; + } + } + + return ValidationResult.OK; + } + } + + public Particle clone() { + Particle copy = new Particle(); + copy.texturePath = this.texturePath; + copy.frameSize = this.frameSize != null ? this.frameSize.clone() : null; + copy.uvOption = this.uvOption; + copy.scaleRatioConstraint = this.scaleRatioConstraint; + copy.softParticles = this.softParticles; + copy.softParticlesFadeFactor = this.softParticlesFadeFactor; + copy.useSpriteBlending = this.useSpriteBlending; + copy.initialAnimationFrame = this.initialAnimationFrame != null ? this.initialAnimationFrame.clone() : null; + copy.collisionAnimationFrame = this.collisionAnimationFrame != null ? this.collisionAnimationFrame.clone() : null; + if (this.animationFrames != null) { + Map m = new HashMap<>(); + + for (Entry e : this.animationFrames.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.animationFrames = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Particle other) + ? false + : Objects.equals(this.texturePath, other.texturePath) + && Objects.equals(this.frameSize, other.frameSize) + && Objects.equals(this.uvOption, other.uvOption) + && Objects.equals(this.scaleRatioConstraint, other.scaleRatioConstraint) + && Objects.equals(this.softParticles, other.softParticles) + && this.softParticlesFadeFactor == other.softParticlesFadeFactor + && this.useSpriteBlending == other.useSpriteBlending + && Objects.equals(this.initialAnimationFrame, other.initialAnimationFrame) + && Objects.equals(this.collisionAnimationFrame, other.collisionAnimationFrame) + && Objects.equals(this.animationFrames, other.animationFrames); + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.texturePath, + this.frameSize, + this.uvOption, + this.scaleRatioConstraint, + this.softParticles, + this.softParticlesFadeFactor, + this.useSpriteBlending, + this.initialAnimationFrame, + this.collisionAnimationFrame, + this.animationFrames + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/ParticleAnimationFrame.java b/src/com/hypixel/hytale/protocol/ParticleAnimationFrame.java new file mode 100644 index 0000000..7baf615 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ParticleAnimationFrame.java @@ -0,0 +1,157 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ParticleAnimationFrame { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 58; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 58; + public static final int MAX_SIZE = 58; + @Nullable + public Range frameIndex; + @Nullable + public RangeVector2f scale; + @Nullable + public RangeVector3f rotation; + @Nullable + public Color color; + public float opacity; + + public ParticleAnimationFrame() { + } + + public ParticleAnimationFrame( + @Nullable Range frameIndex, @Nullable RangeVector2f scale, @Nullable RangeVector3f rotation, @Nullable Color color, float opacity + ) { + this.frameIndex = frameIndex; + this.scale = scale; + this.rotation = rotation; + this.color = color; + this.opacity = opacity; + } + + public ParticleAnimationFrame(@Nonnull ParticleAnimationFrame other) { + this.frameIndex = other.frameIndex; + this.scale = other.scale; + this.rotation = other.rotation; + this.color = other.color; + this.opacity = other.opacity; + } + + @Nonnull + public static ParticleAnimationFrame deserialize(@Nonnull ByteBuf buf, int offset) { + ParticleAnimationFrame obj = new ParticleAnimationFrame(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.frameIndex = Range.deserialize(buf, offset + 1); + } + + if ((nullBits & 2) != 0) { + obj.scale = RangeVector2f.deserialize(buf, offset + 9); + } + + if ((nullBits & 4) != 0) { + obj.rotation = RangeVector3f.deserialize(buf, offset + 26); + } + + if ((nullBits & 8) != 0) { + obj.color = Color.deserialize(buf, offset + 51); + } + + obj.opacity = buf.getFloatLE(offset + 54); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 58; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.frameIndex != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.scale != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rotation != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.color != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + if (this.frameIndex != null) { + this.frameIndex.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.scale != null) { + this.scale.serialize(buf); + } else { + buf.writeZero(17); + } + + if (this.rotation != null) { + this.rotation.serialize(buf); + } else { + buf.writeZero(25); + } + + if (this.color != null) { + this.color.serialize(buf); + } else { + buf.writeZero(3); + } + + buf.writeFloatLE(this.opacity); + } + + public int computeSize() { + return 58; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 58 ? ValidationResult.error("Buffer too small: expected at least 58 bytes") : ValidationResult.OK; + } + + public ParticleAnimationFrame clone() { + ParticleAnimationFrame copy = new ParticleAnimationFrame(); + copy.frameIndex = this.frameIndex != null ? this.frameIndex.clone() : null; + copy.scale = this.scale != null ? this.scale.clone() : null; + copy.rotation = this.rotation != null ? this.rotation.clone() : null; + copy.color = this.color != null ? this.color.clone() : null; + copy.opacity = this.opacity; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ParticleAnimationFrame other) + ? false + : Objects.equals(this.frameIndex, other.frameIndex) + && Objects.equals(this.scale, other.scale) + && Objects.equals(this.rotation, other.rotation) + && Objects.equals(this.color, other.color) + && this.opacity == other.opacity; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.frameIndex, this.scale, this.rotation, this.color, this.opacity); + } +} diff --git a/src/com/hypixel/hytale/protocol/ParticleAttractor.java b/src/com/hypixel/hytale/protocol/ParticleAttractor.java new file mode 100644 index 0000000..da861a1 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ParticleAttractor.java @@ -0,0 +1,233 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ParticleAttractor { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 85; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 85; + public static final int MAX_SIZE = 85; + @Nullable + public Vector3f position; + @Nullable + public Vector3f radialAxis; + public float trailPositionMultiplier; + public float radius; + public float radialAcceleration; + public float radialTangentAcceleration; + @Nullable + public Vector3f linearAcceleration; + public float radialImpulse; + public float radialTangentImpulse; + @Nullable + public Vector3f linearImpulse; + @Nullable + public Vector3f dampingMultiplier; + + public ParticleAttractor() { + } + + public ParticleAttractor( + @Nullable Vector3f position, + @Nullable Vector3f radialAxis, + float trailPositionMultiplier, + float radius, + float radialAcceleration, + float radialTangentAcceleration, + @Nullable Vector3f linearAcceleration, + float radialImpulse, + float radialTangentImpulse, + @Nullable Vector3f linearImpulse, + @Nullable Vector3f dampingMultiplier + ) { + this.position = position; + this.radialAxis = radialAxis; + this.trailPositionMultiplier = trailPositionMultiplier; + this.radius = radius; + this.radialAcceleration = radialAcceleration; + this.radialTangentAcceleration = radialTangentAcceleration; + this.linearAcceleration = linearAcceleration; + this.radialImpulse = radialImpulse; + this.radialTangentImpulse = radialTangentImpulse; + this.linearImpulse = linearImpulse; + this.dampingMultiplier = dampingMultiplier; + } + + public ParticleAttractor(@Nonnull ParticleAttractor other) { + this.position = other.position; + this.radialAxis = other.radialAxis; + this.trailPositionMultiplier = other.trailPositionMultiplier; + this.radius = other.radius; + this.radialAcceleration = other.radialAcceleration; + this.radialTangentAcceleration = other.radialTangentAcceleration; + this.linearAcceleration = other.linearAcceleration; + this.radialImpulse = other.radialImpulse; + this.radialTangentImpulse = other.radialTangentImpulse; + this.linearImpulse = other.linearImpulse; + this.dampingMultiplier = other.dampingMultiplier; + } + + @Nonnull + public static ParticleAttractor deserialize(@Nonnull ByteBuf buf, int offset) { + ParticleAttractor obj = new ParticleAttractor(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.position = Vector3f.deserialize(buf, offset + 1); + } + + if ((nullBits & 2) != 0) { + obj.radialAxis = Vector3f.deserialize(buf, offset + 13); + } + + obj.trailPositionMultiplier = buf.getFloatLE(offset + 25); + obj.radius = buf.getFloatLE(offset + 29); + obj.radialAcceleration = buf.getFloatLE(offset + 33); + obj.radialTangentAcceleration = buf.getFloatLE(offset + 37); + if ((nullBits & 4) != 0) { + obj.linearAcceleration = Vector3f.deserialize(buf, offset + 41); + } + + obj.radialImpulse = buf.getFloatLE(offset + 53); + obj.radialTangentImpulse = buf.getFloatLE(offset + 57); + if ((nullBits & 8) != 0) { + obj.linearImpulse = Vector3f.deserialize(buf, offset + 61); + } + + if ((nullBits & 16) != 0) { + obj.dampingMultiplier = Vector3f.deserialize(buf, offset + 73); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 85; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.position != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.radialAxis != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.linearAcceleration != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.linearImpulse != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.dampingMultiplier != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + if (this.position != null) { + this.position.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.radialAxis != null) { + this.radialAxis.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeFloatLE(this.trailPositionMultiplier); + buf.writeFloatLE(this.radius); + buf.writeFloatLE(this.radialAcceleration); + buf.writeFloatLE(this.radialTangentAcceleration); + if (this.linearAcceleration != null) { + this.linearAcceleration.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeFloatLE(this.radialImpulse); + buf.writeFloatLE(this.radialTangentImpulse); + if (this.linearImpulse != null) { + this.linearImpulse.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.dampingMultiplier != null) { + this.dampingMultiplier.serialize(buf); + } else { + buf.writeZero(12); + } + } + + public int computeSize() { + return 85; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 85 ? ValidationResult.error("Buffer too small: expected at least 85 bytes") : ValidationResult.OK; + } + + public ParticleAttractor clone() { + ParticleAttractor copy = new ParticleAttractor(); + copy.position = this.position != null ? this.position.clone() : null; + copy.radialAxis = this.radialAxis != null ? this.radialAxis.clone() : null; + copy.trailPositionMultiplier = this.trailPositionMultiplier; + copy.radius = this.radius; + copy.radialAcceleration = this.radialAcceleration; + copy.radialTangentAcceleration = this.radialTangentAcceleration; + copy.linearAcceleration = this.linearAcceleration != null ? this.linearAcceleration.clone() : null; + copy.radialImpulse = this.radialImpulse; + copy.radialTangentImpulse = this.radialTangentImpulse; + copy.linearImpulse = this.linearImpulse != null ? this.linearImpulse.clone() : null; + copy.dampingMultiplier = this.dampingMultiplier != null ? this.dampingMultiplier.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ParticleAttractor other) + ? false + : Objects.equals(this.position, other.position) + && Objects.equals(this.radialAxis, other.radialAxis) + && this.trailPositionMultiplier == other.trailPositionMultiplier + && this.radius == other.radius + && this.radialAcceleration == other.radialAcceleration + && this.radialTangentAcceleration == other.radialTangentAcceleration + && Objects.equals(this.linearAcceleration, other.linearAcceleration) + && this.radialImpulse == other.radialImpulse + && this.radialTangentImpulse == other.radialTangentImpulse + && Objects.equals(this.linearImpulse, other.linearImpulse) + && Objects.equals(this.dampingMultiplier, other.dampingMultiplier); + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.position, + this.radialAxis, + this.trailPositionMultiplier, + this.radius, + this.radialAcceleration, + this.radialTangentAcceleration, + this.linearAcceleration, + this.radialImpulse, + this.radialTangentImpulse, + this.linearImpulse, + this.dampingMultiplier + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/ParticleCollision.java b/src/com/hypixel/hytale/protocol/ParticleCollision.java new file mode 100644 index 0000000..5732829 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ParticleCollision.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class ParticleCollision { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 3; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 3; + public static final int MAX_SIZE = 3; + @Nonnull + public ParticleCollisionBlockType blockType = ParticleCollisionBlockType.None; + @Nonnull + public ParticleCollisionAction action = ParticleCollisionAction.Expire; + @Nonnull + public ParticleRotationInfluence particleRotationInfluence = ParticleRotationInfluence.None; + + public ParticleCollision() { + } + + public ParticleCollision( + @Nonnull ParticleCollisionBlockType blockType, @Nonnull ParticleCollisionAction action, @Nonnull ParticleRotationInfluence particleRotationInfluence + ) { + this.blockType = blockType; + this.action = action; + this.particleRotationInfluence = particleRotationInfluence; + } + + public ParticleCollision(@Nonnull ParticleCollision other) { + this.blockType = other.blockType; + this.action = other.action; + this.particleRotationInfluence = other.particleRotationInfluence; + } + + @Nonnull + public static ParticleCollision deserialize(@Nonnull ByteBuf buf, int offset) { + ParticleCollision obj = new ParticleCollision(); + obj.blockType = ParticleCollisionBlockType.fromValue(buf.getByte(offset + 0)); + obj.action = ParticleCollisionAction.fromValue(buf.getByte(offset + 1)); + obj.particleRotationInfluence = ParticleRotationInfluence.fromValue(buf.getByte(offset + 2)); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 3; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.blockType.getValue()); + buf.writeByte(this.action.getValue()); + buf.writeByte(this.particleRotationInfluence.getValue()); + } + + public int computeSize() { + return 3; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 3 ? ValidationResult.error("Buffer too small: expected at least 3 bytes") : ValidationResult.OK; + } + + public ParticleCollision clone() { + ParticleCollision copy = new ParticleCollision(); + copy.blockType = this.blockType; + copy.action = this.action; + copy.particleRotationInfluence = this.particleRotationInfluence; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ParticleCollision other) + ? false + : Objects.equals(this.blockType, other.blockType) + && Objects.equals(this.action, other.action) + && Objects.equals(this.particleRotationInfluence, other.particleRotationInfluence); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.blockType, this.action, this.particleRotationInfluence); + } +} diff --git a/src/com/hypixel/hytale/protocol/ParticleCollisionAction.java b/src/com/hypixel/hytale/protocol/ParticleCollisionAction.java new file mode 100644 index 0000000..e2f7ded --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ParticleCollisionAction.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ParticleCollisionAction { + Expire(0), + LastFrame(1), + Linger(2); + + public static final ParticleCollisionAction[] VALUES = values(); + private final int value; + + private ParticleCollisionAction(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ParticleCollisionAction fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ParticleCollisionAction", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ParticleCollisionBlockType.java b/src/com/hypixel/hytale/protocol/ParticleCollisionBlockType.java new file mode 100644 index 0000000..5a8f374 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ParticleCollisionBlockType.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ParticleCollisionBlockType { + None(0), + Air(1), + Solid(2), + All(3); + + public static final ParticleCollisionBlockType[] VALUES = values(); + private final int value; + + private ParticleCollisionBlockType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ParticleCollisionBlockType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ParticleCollisionBlockType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ParticleRotationInfluence.java b/src/com/hypixel/hytale/protocol/ParticleRotationInfluence.java new file mode 100644 index 0000000..779f1c3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ParticleRotationInfluence.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ParticleRotationInfluence { + None(0), + Billboard(1), + BillboardY(2), + BillboardVelocity(3), + Velocity(4); + + public static final ParticleRotationInfluence[] VALUES = values(); + private final int value; + + private ParticleRotationInfluence(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ParticleRotationInfluence fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ParticleRotationInfluence", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ParticleScaleRatioConstraint.java b/src/com/hypixel/hytale/protocol/ParticleScaleRatioConstraint.java new file mode 100644 index 0000000..7268e3b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ParticleScaleRatioConstraint.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ParticleScaleRatioConstraint { + OneToOne(0), + Preserved(1), + None(2); + + public static final ParticleScaleRatioConstraint[] VALUES = values(); + private final int value; + + private ParticleScaleRatioConstraint(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ParticleScaleRatioConstraint fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ParticleScaleRatioConstraint", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ParticleSpawner.java b/src/com/hypixel/hytale/protocol/ParticleSpawner.java new file mode 100644 index 0000000..37ed7aa --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ParticleSpawner.java @@ -0,0 +1,688 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ParticleSpawner { + public static final int NULLABLE_BIT_FIELD_SIZE = 2; + public static final int FIXED_BLOCK_SIZE = 131; + public static final int VARIABLE_FIELD_COUNT = 4; + public static final int VARIABLE_BLOCK_START = 147; + public static final int MAX_SIZE = 651264332; + @Nullable + public String id; + @Nullable + public Particle particle; + @Nonnull + public EmitShape shape = EmitShape.Sphere; + @Nullable + public RangeVector3f emitOffset; + public float cameraOffset; + public boolean useEmitDirection; + public float lifeSpan; + @Nullable + public Rangef spawnRate; + public boolean spawnBurst; + @Nullable + public Rangef waveDelay; + @Nullable + public Range totalParticles; + public int maxConcurrentParticles; + @Nullable + public InitialVelocity initialVelocity; + public float velocityStretchMultiplier; + @Nonnull + public ParticleRotationInfluence particleRotationInfluence = ParticleRotationInfluence.None; + public boolean particleRotateWithSpawner; + public boolean isLowRes; + public float trailSpawnerPositionMultiplier; + public float trailSpawnerRotationMultiplier; + @Nullable + public ParticleCollision particleCollision; + @Nonnull + public FXRenderMode renderMode = FXRenderMode.BlendLinear; + public float lightInfluence; + public boolean linearFiltering; + @Nullable + public Rangef particleLifeSpan; + @Nullable + public UVMotion uvMotion; + @Nullable + public ParticleAttractor[] attractors; + @Nullable + public IntersectionHighlight intersectionHighlight; + + public ParticleSpawner() { + } + + public ParticleSpawner( + @Nullable String id, + @Nullable Particle particle, + @Nonnull EmitShape shape, + @Nullable RangeVector3f emitOffset, + float cameraOffset, + boolean useEmitDirection, + float lifeSpan, + @Nullable Rangef spawnRate, + boolean spawnBurst, + @Nullable Rangef waveDelay, + @Nullable Range totalParticles, + int maxConcurrentParticles, + @Nullable InitialVelocity initialVelocity, + float velocityStretchMultiplier, + @Nonnull ParticleRotationInfluence particleRotationInfluence, + boolean particleRotateWithSpawner, + boolean isLowRes, + float trailSpawnerPositionMultiplier, + float trailSpawnerRotationMultiplier, + @Nullable ParticleCollision particleCollision, + @Nonnull FXRenderMode renderMode, + float lightInfluence, + boolean linearFiltering, + @Nullable Rangef particleLifeSpan, + @Nullable UVMotion uvMotion, + @Nullable ParticleAttractor[] attractors, + @Nullable IntersectionHighlight intersectionHighlight + ) { + this.id = id; + this.particle = particle; + this.shape = shape; + this.emitOffset = emitOffset; + this.cameraOffset = cameraOffset; + this.useEmitDirection = useEmitDirection; + this.lifeSpan = lifeSpan; + this.spawnRate = spawnRate; + this.spawnBurst = spawnBurst; + this.waveDelay = waveDelay; + this.totalParticles = totalParticles; + this.maxConcurrentParticles = maxConcurrentParticles; + this.initialVelocity = initialVelocity; + this.velocityStretchMultiplier = velocityStretchMultiplier; + this.particleRotationInfluence = particleRotationInfluence; + this.particleRotateWithSpawner = particleRotateWithSpawner; + this.isLowRes = isLowRes; + this.trailSpawnerPositionMultiplier = trailSpawnerPositionMultiplier; + this.trailSpawnerRotationMultiplier = trailSpawnerRotationMultiplier; + this.particleCollision = particleCollision; + this.renderMode = renderMode; + this.lightInfluence = lightInfluence; + this.linearFiltering = linearFiltering; + this.particleLifeSpan = particleLifeSpan; + this.uvMotion = uvMotion; + this.attractors = attractors; + this.intersectionHighlight = intersectionHighlight; + } + + public ParticleSpawner(@Nonnull ParticleSpawner other) { + this.id = other.id; + this.particle = other.particle; + this.shape = other.shape; + this.emitOffset = other.emitOffset; + this.cameraOffset = other.cameraOffset; + this.useEmitDirection = other.useEmitDirection; + this.lifeSpan = other.lifeSpan; + this.spawnRate = other.spawnRate; + this.spawnBurst = other.spawnBurst; + this.waveDelay = other.waveDelay; + this.totalParticles = other.totalParticles; + this.maxConcurrentParticles = other.maxConcurrentParticles; + this.initialVelocity = other.initialVelocity; + this.velocityStretchMultiplier = other.velocityStretchMultiplier; + this.particleRotationInfluence = other.particleRotationInfluence; + this.particleRotateWithSpawner = other.particleRotateWithSpawner; + this.isLowRes = other.isLowRes; + this.trailSpawnerPositionMultiplier = other.trailSpawnerPositionMultiplier; + this.trailSpawnerRotationMultiplier = other.trailSpawnerRotationMultiplier; + this.particleCollision = other.particleCollision; + this.renderMode = other.renderMode; + this.lightInfluence = other.lightInfluence; + this.linearFiltering = other.linearFiltering; + this.particleLifeSpan = other.particleLifeSpan; + this.uvMotion = other.uvMotion; + this.attractors = other.attractors; + this.intersectionHighlight = other.intersectionHighlight; + } + + @Nonnull + public static ParticleSpawner deserialize(@Nonnull ByteBuf buf, int offset) { + ParticleSpawner obj = new ParticleSpawner(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + obj.shape = EmitShape.fromValue(buf.getByte(offset + 2)); + if ((nullBits[0] & 4) != 0) { + obj.emitOffset = RangeVector3f.deserialize(buf, offset + 3); + } + + obj.cameraOffset = buf.getFloatLE(offset + 28); + obj.useEmitDirection = buf.getByte(offset + 32) != 0; + obj.lifeSpan = buf.getFloatLE(offset + 33); + if ((nullBits[0] & 8) != 0) { + obj.spawnRate = Rangef.deserialize(buf, offset + 37); + } + + obj.spawnBurst = buf.getByte(offset + 45) != 0; + if ((nullBits[0] & 16) != 0) { + obj.waveDelay = Rangef.deserialize(buf, offset + 46); + } + + if ((nullBits[0] & 32) != 0) { + obj.totalParticles = Range.deserialize(buf, offset + 54); + } + + obj.maxConcurrentParticles = buf.getIntLE(offset + 62); + if ((nullBits[0] & 64) != 0) { + obj.initialVelocity = InitialVelocity.deserialize(buf, offset + 66); + } + + obj.velocityStretchMultiplier = buf.getFloatLE(offset + 91); + obj.particleRotationInfluence = ParticleRotationInfluence.fromValue(buf.getByte(offset + 95)); + obj.particleRotateWithSpawner = buf.getByte(offset + 96) != 0; + obj.isLowRes = buf.getByte(offset + 97) != 0; + obj.trailSpawnerPositionMultiplier = buf.getFloatLE(offset + 98); + obj.trailSpawnerRotationMultiplier = buf.getFloatLE(offset + 102); + if ((nullBits[0] & 128) != 0) { + obj.particleCollision = ParticleCollision.deserialize(buf, offset + 106); + } + + obj.renderMode = FXRenderMode.fromValue(buf.getByte(offset + 109)); + obj.lightInfluence = buf.getFloatLE(offset + 110); + obj.linearFiltering = buf.getByte(offset + 114) != 0; + if ((nullBits[1] & 1) != 0) { + obj.particleLifeSpan = Rangef.deserialize(buf, offset + 115); + } + + if ((nullBits[1] & 8) != 0) { + obj.intersectionHighlight = IntersectionHighlight.deserialize(buf, offset + 123); + } + + if ((nullBits[0] & 1) != 0) { + int varPos0 = offset + 147 + buf.getIntLE(offset + 131); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits[0] & 2) != 0) { + int varPos1 = offset + 147 + buf.getIntLE(offset + 135); + obj.particle = Particle.deserialize(buf, varPos1); + } + + if ((nullBits[1] & 2) != 0) { + int varPos2 = offset + 147 + buf.getIntLE(offset + 139); + obj.uvMotion = UVMotion.deserialize(buf, varPos2); + } + + if ((nullBits[1] & 4) != 0) { + int varPos3 = offset + 147 + buf.getIntLE(offset + 143); + int attractorsCount = VarInt.peek(buf, varPos3); + if (attractorsCount < 0) { + throw ProtocolException.negativeLength("Attractors", attractorsCount); + } + + if (attractorsCount > 4096000) { + throw ProtocolException.arrayTooLong("Attractors", attractorsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + attractorsCount * 85L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Attractors", varPos3 + varIntLen + attractorsCount * 85, buf.readableBytes()); + } + + obj.attractors = new ParticleAttractor[attractorsCount]; + int elemPos = varPos3 + varIntLen; + + for (int i = 0; i < attractorsCount; i++) { + obj.attractors[i] = ParticleAttractor.deserialize(buf, elemPos); + elemPos += ParticleAttractor.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + int maxEnd = 147; + if ((nullBits[0] & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 131); + int pos0 = offset + 147 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits[0] & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 135); + int pos1 = offset + 147 + fieldOffset1; + pos1 += Particle.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits[1] & 2) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 139); + int pos2 = offset + 147 + fieldOffset2; + pos2 += UVMotion.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits[1] & 4) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 143); + int pos3 = offset + 147 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3); + + for (int i = 0; i < arrLen; i++) { + pos3 += ParticleAttractor.computeBytesConsumed(buf, pos3); + } + + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte[] nullBits = new byte[2]; + if (this.id != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.particle != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.emitOffset != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.spawnRate != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.waveDelay != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.totalParticles != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.initialVelocity != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.particleCollision != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.particleLifeSpan != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + if (this.uvMotion != null) { + nullBits[1] = (byte)(nullBits[1] | 2); + } + + if (this.attractors != null) { + nullBits[1] = (byte)(nullBits[1] | 4); + } + + if (this.intersectionHighlight != null) { + nullBits[1] = (byte)(nullBits[1] | 8); + } + + buf.writeBytes(nullBits); + buf.writeByte(this.shape.getValue()); + if (this.emitOffset != null) { + this.emitOffset.serialize(buf); + } else { + buf.writeZero(25); + } + + buf.writeFloatLE(this.cameraOffset); + buf.writeByte(this.useEmitDirection ? 1 : 0); + buf.writeFloatLE(this.lifeSpan); + if (this.spawnRate != null) { + this.spawnRate.serialize(buf); + } else { + buf.writeZero(8); + } + + buf.writeByte(this.spawnBurst ? 1 : 0); + if (this.waveDelay != null) { + this.waveDelay.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.totalParticles != null) { + this.totalParticles.serialize(buf); + } else { + buf.writeZero(8); + } + + buf.writeIntLE(this.maxConcurrentParticles); + if (this.initialVelocity != null) { + this.initialVelocity.serialize(buf); + } else { + buf.writeZero(25); + } + + buf.writeFloatLE(this.velocityStretchMultiplier); + buf.writeByte(this.particleRotationInfluence.getValue()); + buf.writeByte(this.particleRotateWithSpawner ? 1 : 0); + buf.writeByte(this.isLowRes ? 1 : 0); + buf.writeFloatLE(this.trailSpawnerPositionMultiplier); + buf.writeFloatLE(this.trailSpawnerRotationMultiplier); + if (this.particleCollision != null) { + this.particleCollision.serialize(buf); + } else { + buf.writeZero(3); + } + + buf.writeByte(this.renderMode.getValue()); + buf.writeFloatLE(this.lightInfluence); + buf.writeByte(this.linearFiltering ? 1 : 0); + if (this.particleLifeSpan != null) { + this.particleLifeSpan.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.intersectionHighlight != null) { + this.intersectionHighlight.serialize(buf); + } else { + buf.writeZero(8); + } + + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int particleOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int uvMotionOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int attractorsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.particle != null) { + buf.setIntLE(particleOffsetSlot, buf.writerIndex() - varBlockStart); + this.particle.serialize(buf); + } else { + buf.setIntLE(particleOffsetSlot, -1); + } + + if (this.uvMotion != null) { + buf.setIntLE(uvMotionOffsetSlot, buf.writerIndex() - varBlockStart); + this.uvMotion.serialize(buf); + } else { + buf.setIntLE(uvMotionOffsetSlot, -1); + } + + if (this.attractors != null) { + buf.setIntLE(attractorsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.attractors.length > 4096000) { + throw ProtocolException.arrayTooLong("Attractors", this.attractors.length, 4096000); + } + + VarInt.write(buf, this.attractors.length); + + for (ParticleAttractor item : this.attractors) { + item.serialize(buf); + } + } else { + buf.setIntLE(attractorsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 147; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.particle != null) { + size += this.particle.computeSize(); + } + + if (this.uvMotion != null) { + size += this.uvMotion.computeSize(); + } + + if (this.attractors != null) { + size += VarInt.size(this.attractors.length) + this.attractors.length * 85; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 147) { + return ValidationResult.error("Buffer too small: expected at least 147 bytes"); + } else { + byte[] nullBits = PacketIO.readBytes(buffer, offset, 2); + if ((nullBits[0] & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 131); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 147 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits[0] & 2) != 0) { + int particleOffset = buffer.getIntLE(offset + 135); + if (particleOffset < 0) { + return ValidationResult.error("Invalid offset for Particle"); + } + + int posx = offset + 147 + particleOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Particle"); + } + + ValidationResult particleResult = Particle.validateStructure(buffer, posx); + if (!particleResult.isValid()) { + return ValidationResult.error("Invalid Particle: " + particleResult.error()); + } + + posx += Particle.computeBytesConsumed(buffer, posx); + } + + if ((nullBits[1] & 2) != 0) { + int uvMotionOffset = buffer.getIntLE(offset + 139); + if (uvMotionOffset < 0) { + return ValidationResult.error("Invalid offset for UvMotion"); + } + + int posxx = offset + 147 + uvMotionOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for UvMotion"); + } + + ValidationResult uvMotionResult = UVMotion.validateStructure(buffer, posxx); + if (!uvMotionResult.isValid()) { + return ValidationResult.error("Invalid UvMotion: " + uvMotionResult.error()); + } + + posxx += UVMotion.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits[1] & 4) != 0) { + int attractorsOffset = buffer.getIntLE(offset + 143); + if (attractorsOffset < 0) { + return ValidationResult.error("Invalid offset for Attractors"); + } + + int posxxx = offset + 147 + attractorsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Attractors"); + } + + int attractorsCount = VarInt.peek(buffer, posxxx); + if (attractorsCount < 0) { + return ValidationResult.error("Invalid array count for Attractors"); + } + + if (attractorsCount > 4096000) { + return ValidationResult.error("Attractors exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += attractorsCount * 85; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Attractors"); + } + } + + return ValidationResult.OK; + } + } + + public ParticleSpawner clone() { + ParticleSpawner copy = new ParticleSpawner(); + copy.id = this.id; + copy.particle = this.particle != null ? this.particle.clone() : null; + copy.shape = this.shape; + copy.emitOffset = this.emitOffset != null ? this.emitOffset.clone() : null; + copy.cameraOffset = this.cameraOffset; + copy.useEmitDirection = this.useEmitDirection; + copy.lifeSpan = this.lifeSpan; + copy.spawnRate = this.spawnRate != null ? this.spawnRate.clone() : null; + copy.spawnBurst = this.spawnBurst; + copy.waveDelay = this.waveDelay != null ? this.waveDelay.clone() : null; + copy.totalParticles = this.totalParticles != null ? this.totalParticles.clone() : null; + copy.maxConcurrentParticles = this.maxConcurrentParticles; + copy.initialVelocity = this.initialVelocity != null ? this.initialVelocity.clone() : null; + copy.velocityStretchMultiplier = this.velocityStretchMultiplier; + copy.particleRotationInfluence = this.particleRotationInfluence; + copy.particleRotateWithSpawner = this.particleRotateWithSpawner; + copy.isLowRes = this.isLowRes; + copy.trailSpawnerPositionMultiplier = this.trailSpawnerPositionMultiplier; + copy.trailSpawnerRotationMultiplier = this.trailSpawnerRotationMultiplier; + copy.particleCollision = this.particleCollision != null ? this.particleCollision.clone() : null; + copy.renderMode = this.renderMode; + copy.lightInfluence = this.lightInfluence; + copy.linearFiltering = this.linearFiltering; + copy.particleLifeSpan = this.particleLifeSpan != null ? this.particleLifeSpan.clone() : null; + copy.uvMotion = this.uvMotion != null ? this.uvMotion.clone() : null; + copy.attractors = this.attractors != null ? Arrays.stream(this.attractors).map(e -> e.clone()).toArray(ParticleAttractor[]::new) : null; + copy.intersectionHighlight = this.intersectionHighlight != null ? this.intersectionHighlight.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ParticleSpawner other) + ? false + : Objects.equals(this.id, other.id) + && Objects.equals(this.particle, other.particle) + && Objects.equals(this.shape, other.shape) + && Objects.equals(this.emitOffset, other.emitOffset) + && this.cameraOffset == other.cameraOffset + && this.useEmitDirection == other.useEmitDirection + && this.lifeSpan == other.lifeSpan + && Objects.equals(this.spawnRate, other.spawnRate) + && this.spawnBurst == other.spawnBurst + && Objects.equals(this.waveDelay, other.waveDelay) + && Objects.equals(this.totalParticles, other.totalParticles) + && this.maxConcurrentParticles == other.maxConcurrentParticles + && Objects.equals(this.initialVelocity, other.initialVelocity) + && this.velocityStretchMultiplier == other.velocityStretchMultiplier + && Objects.equals(this.particleRotationInfluence, other.particleRotationInfluence) + && this.particleRotateWithSpawner == other.particleRotateWithSpawner + && this.isLowRes == other.isLowRes + && this.trailSpawnerPositionMultiplier == other.trailSpawnerPositionMultiplier + && this.trailSpawnerRotationMultiplier == other.trailSpawnerRotationMultiplier + && Objects.equals(this.particleCollision, other.particleCollision) + && Objects.equals(this.renderMode, other.renderMode) + && this.lightInfluence == other.lightInfluence + && this.linearFiltering == other.linearFiltering + && Objects.equals(this.particleLifeSpan, other.particleLifeSpan) + && Objects.equals(this.uvMotion, other.uvMotion) + && Arrays.equals((Object[])this.attractors, (Object[])other.attractors) + && Objects.equals(this.intersectionHighlight, other.intersectionHighlight); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.id); + result = 31 * result + Objects.hashCode(this.particle); + result = 31 * result + Objects.hashCode(this.shape); + result = 31 * result + Objects.hashCode(this.emitOffset); + result = 31 * result + Float.hashCode(this.cameraOffset); + result = 31 * result + Boolean.hashCode(this.useEmitDirection); + result = 31 * result + Float.hashCode(this.lifeSpan); + result = 31 * result + Objects.hashCode(this.spawnRate); + result = 31 * result + Boolean.hashCode(this.spawnBurst); + result = 31 * result + Objects.hashCode(this.waveDelay); + result = 31 * result + Objects.hashCode(this.totalParticles); + result = 31 * result + Integer.hashCode(this.maxConcurrentParticles); + result = 31 * result + Objects.hashCode(this.initialVelocity); + result = 31 * result + Float.hashCode(this.velocityStretchMultiplier); + result = 31 * result + Objects.hashCode(this.particleRotationInfluence); + result = 31 * result + Boolean.hashCode(this.particleRotateWithSpawner); + result = 31 * result + Boolean.hashCode(this.isLowRes); + result = 31 * result + Float.hashCode(this.trailSpawnerPositionMultiplier); + result = 31 * result + Float.hashCode(this.trailSpawnerRotationMultiplier); + result = 31 * result + Objects.hashCode(this.particleCollision); + result = 31 * result + Objects.hashCode(this.renderMode); + result = 31 * result + Float.hashCode(this.lightInfluence); + result = 31 * result + Boolean.hashCode(this.linearFiltering); + result = 31 * result + Objects.hashCode(this.particleLifeSpan); + result = 31 * result + Objects.hashCode(this.uvMotion); + result = 31 * result + Arrays.hashCode((Object[])this.attractors); + return 31 * result + Objects.hashCode(this.intersectionHighlight); + } +} diff --git a/src/com/hypixel/hytale/protocol/ParticleSpawnerGroup.java b/src/com/hypixel/hytale/protocol/ParticleSpawnerGroup.java new file mode 100644 index 0000000..6768b57 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ParticleSpawnerGroup.java @@ -0,0 +1,448 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ParticleSpawnerGroup { + public static final int NULLABLE_BIT_FIELD_SIZE = 2; + public static final int FIXED_BLOCK_SIZE = 113; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 121; + public static final int MAX_SIZE = 364544131; + @Nullable + public String spawnerId; + @Nullable + public Vector3f positionOffset; + @Nullable + public Direction rotationOffset; + public boolean fixedRotation; + public float startDelay; + @Nullable + public Rangef spawnRate; + @Nullable + public Rangef waveDelay; + public int totalSpawners; + public int maxConcurrent; + @Nullable + public InitialVelocity initialVelocity; + @Nullable + public RangeVector3f emitOffset; + @Nullable + public Rangef lifeSpan; + @Nullable + public ParticleAttractor[] attractors; + + public ParticleSpawnerGroup() { + } + + public ParticleSpawnerGroup( + @Nullable String spawnerId, + @Nullable Vector3f positionOffset, + @Nullable Direction rotationOffset, + boolean fixedRotation, + float startDelay, + @Nullable Rangef spawnRate, + @Nullable Rangef waveDelay, + int totalSpawners, + int maxConcurrent, + @Nullable InitialVelocity initialVelocity, + @Nullable RangeVector3f emitOffset, + @Nullable Rangef lifeSpan, + @Nullable ParticleAttractor[] attractors + ) { + this.spawnerId = spawnerId; + this.positionOffset = positionOffset; + this.rotationOffset = rotationOffset; + this.fixedRotation = fixedRotation; + this.startDelay = startDelay; + this.spawnRate = spawnRate; + this.waveDelay = waveDelay; + this.totalSpawners = totalSpawners; + this.maxConcurrent = maxConcurrent; + this.initialVelocity = initialVelocity; + this.emitOffset = emitOffset; + this.lifeSpan = lifeSpan; + this.attractors = attractors; + } + + public ParticleSpawnerGroup(@Nonnull ParticleSpawnerGroup other) { + this.spawnerId = other.spawnerId; + this.positionOffset = other.positionOffset; + this.rotationOffset = other.rotationOffset; + this.fixedRotation = other.fixedRotation; + this.startDelay = other.startDelay; + this.spawnRate = other.spawnRate; + this.waveDelay = other.waveDelay; + this.totalSpawners = other.totalSpawners; + this.maxConcurrent = other.maxConcurrent; + this.initialVelocity = other.initialVelocity; + this.emitOffset = other.emitOffset; + this.lifeSpan = other.lifeSpan; + this.attractors = other.attractors; + } + + @Nonnull + public static ParticleSpawnerGroup deserialize(@Nonnull ByteBuf buf, int offset) { + ParticleSpawnerGroup obj = new ParticleSpawnerGroup(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + if ((nullBits[0] & 2) != 0) { + obj.positionOffset = Vector3f.deserialize(buf, offset + 2); + } + + if ((nullBits[0] & 4) != 0) { + obj.rotationOffset = Direction.deserialize(buf, offset + 14); + } + + obj.fixedRotation = buf.getByte(offset + 26) != 0; + obj.startDelay = buf.getFloatLE(offset + 27); + if ((nullBits[0] & 8) != 0) { + obj.spawnRate = Rangef.deserialize(buf, offset + 31); + } + + if ((nullBits[0] & 16) != 0) { + obj.waveDelay = Rangef.deserialize(buf, offset + 39); + } + + obj.totalSpawners = buf.getIntLE(offset + 47); + obj.maxConcurrent = buf.getIntLE(offset + 51); + if ((nullBits[0] & 32) != 0) { + obj.initialVelocity = InitialVelocity.deserialize(buf, offset + 55); + } + + if ((nullBits[0] & 64) != 0) { + obj.emitOffset = RangeVector3f.deserialize(buf, offset + 80); + } + + if ((nullBits[0] & 128) != 0) { + obj.lifeSpan = Rangef.deserialize(buf, offset + 105); + } + + if ((nullBits[0] & 1) != 0) { + int varPos0 = offset + 121 + buf.getIntLE(offset + 113); + int spawnerIdLen = VarInt.peek(buf, varPos0); + if (spawnerIdLen < 0) { + throw ProtocolException.negativeLength("SpawnerId", spawnerIdLen); + } + + if (spawnerIdLen > 4096000) { + throw ProtocolException.stringTooLong("SpawnerId", spawnerIdLen, 4096000); + } + + obj.spawnerId = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits[1] & 1) != 0) { + int varPos1 = offset + 121 + buf.getIntLE(offset + 117); + int attractorsCount = VarInt.peek(buf, varPos1); + if (attractorsCount < 0) { + throw ProtocolException.negativeLength("Attractors", attractorsCount); + } + + if (attractorsCount > 4096000) { + throw ProtocolException.arrayTooLong("Attractors", attractorsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + attractorsCount * 85L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Attractors", varPos1 + varIntLen + attractorsCount * 85, buf.readableBytes()); + } + + obj.attractors = new ParticleAttractor[attractorsCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < attractorsCount; i++) { + obj.attractors[i] = ParticleAttractor.deserialize(buf, elemPos); + elemPos += ParticleAttractor.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + int maxEnd = 121; + if ((nullBits[0] & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 113); + int pos0 = offset + 121 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits[1] & 1) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 117); + int pos1 = offset + 121 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += ParticleAttractor.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte[] nullBits = new byte[2]; + if (this.spawnerId != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.positionOffset != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.rotationOffset != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.spawnRate != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.waveDelay != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.initialVelocity != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.emitOffset != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.lifeSpan != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.attractors != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + buf.writeBytes(nullBits); + if (this.positionOffset != null) { + this.positionOffset.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.rotationOffset != null) { + this.rotationOffset.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeByte(this.fixedRotation ? 1 : 0); + buf.writeFloatLE(this.startDelay); + if (this.spawnRate != null) { + this.spawnRate.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.waveDelay != null) { + this.waveDelay.serialize(buf); + } else { + buf.writeZero(8); + } + + buf.writeIntLE(this.totalSpawners); + buf.writeIntLE(this.maxConcurrent); + if (this.initialVelocity != null) { + this.initialVelocity.serialize(buf); + } else { + buf.writeZero(25); + } + + if (this.emitOffset != null) { + this.emitOffset.serialize(buf); + } else { + buf.writeZero(25); + } + + if (this.lifeSpan != null) { + this.lifeSpan.serialize(buf); + } else { + buf.writeZero(8); + } + + int spawnerIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int attractorsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.spawnerId != null) { + buf.setIntLE(spawnerIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.spawnerId, 4096000); + } else { + buf.setIntLE(spawnerIdOffsetSlot, -1); + } + + if (this.attractors != null) { + buf.setIntLE(attractorsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.attractors.length > 4096000) { + throw ProtocolException.arrayTooLong("Attractors", this.attractors.length, 4096000); + } + + VarInt.write(buf, this.attractors.length); + + for (ParticleAttractor item : this.attractors) { + item.serialize(buf); + } + } else { + buf.setIntLE(attractorsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 121; + if (this.spawnerId != null) { + size += PacketIO.stringSize(this.spawnerId); + } + + if (this.attractors != null) { + size += VarInt.size(this.attractors.length) + this.attractors.length * 85; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 121) { + return ValidationResult.error("Buffer too small: expected at least 121 bytes"); + } else { + byte[] nullBits = PacketIO.readBytes(buffer, offset, 2); + if ((nullBits[0] & 1) != 0) { + int spawnerIdOffset = buffer.getIntLE(offset + 113); + if (spawnerIdOffset < 0) { + return ValidationResult.error("Invalid offset for SpawnerId"); + } + + int pos = offset + 121 + spawnerIdOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SpawnerId"); + } + + int spawnerIdLen = VarInt.peek(buffer, pos); + if (spawnerIdLen < 0) { + return ValidationResult.error("Invalid string length for SpawnerId"); + } + + if (spawnerIdLen > 4096000) { + return ValidationResult.error("SpawnerId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += spawnerIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading SpawnerId"); + } + } + + if ((nullBits[1] & 1) != 0) { + int attractorsOffset = buffer.getIntLE(offset + 117); + if (attractorsOffset < 0) { + return ValidationResult.error("Invalid offset for Attractors"); + } + + int posx = offset + 121 + attractorsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Attractors"); + } + + int attractorsCount = VarInt.peek(buffer, posx); + if (attractorsCount < 0) { + return ValidationResult.error("Invalid array count for Attractors"); + } + + if (attractorsCount > 4096000) { + return ValidationResult.error("Attractors exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += attractorsCount * 85; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Attractors"); + } + } + + return ValidationResult.OK; + } + } + + public ParticleSpawnerGroup clone() { + ParticleSpawnerGroup copy = new ParticleSpawnerGroup(); + copy.spawnerId = this.spawnerId; + copy.positionOffset = this.positionOffset != null ? this.positionOffset.clone() : null; + copy.rotationOffset = this.rotationOffset != null ? this.rotationOffset.clone() : null; + copy.fixedRotation = this.fixedRotation; + copy.startDelay = this.startDelay; + copy.spawnRate = this.spawnRate != null ? this.spawnRate.clone() : null; + copy.waveDelay = this.waveDelay != null ? this.waveDelay.clone() : null; + copy.totalSpawners = this.totalSpawners; + copy.maxConcurrent = this.maxConcurrent; + copy.initialVelocity = this.initialVelocity != null ? this.initialVelocity.clone() : null; + copy.emitOffset = this.emitOffset != null ? this.emitOffset.clone() : null; + copy.lifeSpan = this.lifeSpan != null ? this.lifeSpan.clone() : null; + copy.attractors = this.attractors != null ? Arrays.stream(this.attractors).map(e -> e.clone()).toArray(ParticleAttractor[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ParticleSpawnerGroup other) + ? false + : Objects.equals(this.spawnerId, other.spawnerId) + && Objects.equals(this.positionOffset, other.positionOffset) + && Objects.equals(this.rotationOffset, other.rotationOffset) + && this.fixedRotation == other.fixedRotation + && this.startDelay == other.startDelay + && Objects.equals(this.spawnRate, other.spawnRate) + && Objects.equals(this.waveDelay, other.waveDelay) + && this.totalSpawners == other.totalSpawners + && this.maxConcurrent == other.maxConcurrent + && Objects.equals(this.initialVelocity, other.initialVelocity) + && Objects.equals(this.emitOffset, other.emitOffset) + && Objects.equals(this.lifeSpan, other.lifeSpan) + && Arrays.equals((Object[])this.attractors, (Object[])other.attractors); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.spawnerId); + result = 31 * result + Objects.hashCode(this.positionOffset); + result = 31 * result + Objects.hashCode(this.rotationOffset); + result = 31 * result + Boolean.hashCode(this.fixedRotation); + result = 31 * result + Float.hashCode(this.startDelay); + result = 31 * result + Objects.hashCode(this.spawnRate); + result = 31 * result + Objects.hashCode(this.waveDelay); + result = 31 * result + Integer.hashCode(this.totalSpawners); + result = 31 * result + Integer.hashCode(this.maxConcurrent); + result = 31 * result + Objects.hashCode(this.initialVelocity); + result = 31 * result + Objects.hashCode(this.emitOffset); + result = 31 * result + Objects.hashCode(this.lifeSpan); + return 31 * result + Arrays.hashCode((Object[])this.attractors); + } +} diff --git a/src/com/hypixel/hytale/protocol/ParticleSystem.java b/src/com/hypixel/hytale/protocol/ParticleSystem.java new file mode 100644 index 0000000..6462f9d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ParticleSystem.java @@ -0,0 +1,300 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ParticleSystem { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 14; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 22; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + @Nullable + public ParticleSpawnerGroup[] spawners; + public float lifeSpan; + public float cullDistance; + public float boundingRadius; + public boolean isImportant; + + public ParticleSystem() { + } + + public ParticleSystem( + @Nullable String id, @Nullable ParticleSpawnerGroup[] spawners, float lifeSpan, float cullDistance, float boundingRadius, boolean isImportant + ) { + this.id = id; + this.spawners = spawners; + this.lifeSpan = lifeSpan; + this.cullDistance = cullDistance; + this.boundingRadius = boundingRadius; + this.isImportant = isImportant; + } + + public ParticleSystem(@Nonnull ParticleSystem other) { + this.id = other.id; + this.spawners = other.spawners; + this.lifeSpan = other.lifeSpan; + this.cullDistance = other.cullDistance; + this.boundingRadius = other.boundingRadius; + this.isImportant = other.isImportant; + } + + @Nonnull + public static ParticleSystem deserialize(@Nonnull ByteBuf buf, int offset) { + ParticleSystem obj = new ParticleSystem(); + byte nullBits = buf.getByte(offset); + obj.lifeSpan = buf.getFloatLE(offset + 1); + obj.cullDistance = buf.getFloatLE(offset + 5); + obj.boundingRadius = buf.getFloatLE(offset + 9); + obj.isImportant = buf.getByte(offset + 13) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 22 + buf.getIntLE(offset + 14); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 22 + buf.getIntLE(offset + 18); + int spawnersCount = VarInt.peek(buf, varPos1); + if (spawnersCount < 0) { + throw ProtocolException.negativeLength("Spawners", spawnersCount); + } + + if (spawnersCount > 4096000) { + throw ProtocolException.arrayTooLong("Spawners", spawnersCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + spawnersCount * 113L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Spawners", varPos1 + varIntLen + spawnersCount * 113, buf.readableBytes()); + } + + obj.spawners = new ParticleSpawnerGroup[spawnersCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < spawnersCount; i++) { + obj.spawners[i] = ParticleSpawnerGroup.deserialize(buf, elemPos); + elemPos += ParticleSpawnerGroup.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 22; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 14); + int pos0 = offset + 22 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 18); + int pos1 = offset + 22 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += ParticleSpawnerGroup.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.spawners != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.lifeSpan); + buf.writeFloatLE(this.cullDistance); + buf.writeFloatLE(this.boundingRadius); + buf.writeByte(this.isImportant ? 1 : 0); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int spawnersOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.spawners != null) { + buf.setIntLE(spawnersOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.spawners.length > 4096000) { + throw ProtocolException.arrayTooLong("Spawners", this.spawners.length, 4096000); + } + + VarInt.write(buf, this.spawners.length); + + for (ParticleSpawnerGroup item : this.spawners) { + item.serialize(buf); + } + } else { + buf.setIntLE(spawnersOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 22; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.spawners != null) { + int spawnersSize = 0; + + for (ParticleSpawnerGroup elem : this.spawners) { + spawnersSize += elem.computeSize(); + } + + size += VarInt.size(this.spawners.length) + spawnersSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 22) { + return ValidationResult.error("Buffer too small: expected at least 22 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 14); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 22 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int spawnersOffset = buffer.getIntLE(offset + 18); + if (spawnersOffset < 0) { + return ValidationResult.error("Invalid offset for Spawners"); + } + + int posx = offset + 22 + spawnersOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Spawners"); + } + + int spawnersCount = VarInt.peek(buffer, posx); + if (spawnersCount < 0) { + return ValidationResult.error("Invalid array count for Spawners"); + } + + if (spawnersCount > 4096000) { + return ValidationResult.error("Spawners exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < spawnersCount; i++) { + ValidationResult structResult = ParticleSpawnerGroup.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ParticleSpawnerGroup in Spawners[" + i + "]: " + structResult.error()); + } + + posx += ParticleSpawnerGroup.computeBytesConsumed(buffer, posx); + } + } + + return ValidationResult.OK; + } + } + + public ParticleSystem clone() { + ParticleSystem copy = new ParticleSystem(); + copy.id = this.id; + copy.spawners = this.spawners != null ? Arrays.stream(this.spawners).map(e -> e.clone()).toArray(ParticleSpawnerGroup[]::new) : null; + copy.lifeSpan = this.lifeSpan; + copy.cullDistance = this.cullDistance; + copy.boundingRadius = this.boundingRadius; + copy.isImportant = this.isImportant; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ParticleSystem other) + ? false + : Objects.equals(this.id, other.id) + && Arrays.equals((Object[])this.spawners, (Object[])other.spawners) + && this.lifeSpan == other.lifeSpan + && this.cullDistance == other.cullDistance + && this.boundingRadius == other.boundingRadius + && this.isImportant == other.isImportant; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.id); + result = 31 * result + Arrays.hashCode((Object[])this.spawners); + result = 31 * result + Float.hashCode(this.lifeSpan); + result = 31 * result + Float.hashCode(this.cullDistance); + result = 31 * result + Float.hashCode(this.boundingRadius); + return 31 * result + Boolean.hashCode(this.isImportant); + } +} diff --git a/src/com/hypixel/hytale/protocol/ParticleUVOption.java b/src/com/hypixel/hytale/protocol/ParticleUVOption.java new file mode 100644 index 0000000..93c2dcc --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ParticleUVOption.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ParticleUVOption { + None(0), + RandomFlipU(1), + RandomFlipV(2), + RandomFlipUV(3), + FlipU(4), + FlipV(5), + FlipUV(6); + + public static final ParticleUVOption[] VALUES = values(); + private final int value; + + private ParticleUVOption(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ParticleUVOption fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ParticleUVOption", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/Phobia.java b/src/com/hypixel/hytale/protocol/Phobia.java new file mode 100644 index 0000000..2d566f7 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Phobia.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum Phobia { + None(0), + Arachnophobia(1); + + public static final Phobia[] VALUES = values(); + private final int value; + + private Phobia(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static Phobia fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("Phobia", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/PhysicsConfig.java b/src/com/hypixel/hytale/protocol/PhysicsConfig.java new file mode 100644 index 0000000..7e00eb3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/PhysicsConfig.java @@ -0,0 +1,266 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class PhysicsConfig { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 122; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 122; + public static final int MAX_SIZE = 122; + @Nonnull + public PhysicsType type = PhysicsType.Standard; + public double density; + public double gravity; + public double bounciness; + public int bounceCount; + public double bounceLimit; + public boolean sticksVertically; + public boolean computeYaw; + public boolean computePitch; + @Nonnull + public RotationMode rotationMode = RotationMode.None; + public double moveOutOfSolidSpeed; + public double terminalVelocityAir; + public double densityAir; + public double terminalVelocityWater; + public double densityWater; + public double hitWaterImpulseLoss; + public double rotationForce; + public float speedRotationFactor; + public double swimmingDampingFactor; + public boolean allowRolling; + public double rollingFrictionFactor; + public float rollingSpeed; + + public PhysicsConfig() { + } + + public PhysicsConfig( + @Nonnull PhysicsType type, + double density, + double gravity, + double bounciness, + int bounceCount, + double bounceLimit, + boolean sticksVertically, + boolean computeYaw, + boolean computePitch, + @Nonnull RotationMode rotationMode, + double moveOutOfSolidSpeed, + double terminalVelocityAir, + double densityAir, + double terminalVelocityWater, + double densityWater, + double hitWaterImpulseLoss, + double rotationForce, + float speedRotationFactor, + double swimmingDampingFactor, + boolean allowRolling, + double rollingFrictionFactor, + float rollingSpeed + ) { + this.type = type; + this.density = density; + this.gravity = gravity; + this.bounciness = bounciness; + this.bounceCount = bounceCount; + this.bounceLimit = bounceLimit; + this.sticksVertically = sticksVertically; + this.computeYaw = computeYaw; + this.computePitch = computePitch; + this.rotationMode = rotationMode; + this.moveOutOfSolidSpeed = moveOutOfSolidSpeed; + this.terminalVelocityAir = terminalVelocityAir; + this.densityAir = densityAir; + this.terminalVelocityWater = terminalVelocityWater; + this.densityWater = densityWater; + this.hitWaterImpulseLoss = hitWaterImpulseLoss; + this.rotationForce = rotationForce; + this.speedRotationFactor = speedRotationFactor; + this.swimmingDampingFactor = swimmingDampingFactor; + this.allowRolling = allowRolling; + this.rollingFrictionFactor = rollingFrictionFactor; + this.rollingSpeed = rollingSpeed; + } + + public PhysicsConfig(@Nonnull PhysicsConfig other) { + this.type = other.type; + this.density = other.density; + this.gravity = other.gravity; + this.bounciness = other.bounciness; + this.bounceCount = other.bounceCount; + this.bounceLimit = other.bounceLimit; + this.sticksVertically = other.sticksVertically; + this.computeYaw = other.computeYaw; + this.computePitch = other.computePitch; + this.rotationMode = other.rotationMode; + this.moveOutOfSolidSpeed = other.moveOutOfSolidSpeed; + this.terminalVelocityAir = other.terminalVelocityAir; + this.densityAir = other.densityAir; + this.terminalVelocityWater = other.terminalVelocityWater; + this.densityWater = other.densityWater; + this.hitWaterImpulseLoss = other.hitWaterImpulseLoss; + this.rotationForce = other.rotationForce; + this.speedRotationFactor = other.speedRotationFactor; + this.swimmingDampingFactor = other.swimmingDampingFactor; + this.allowRolling = other.allowRolling; + this.rollingFrictionFactor = other.rollingFrictionFactor; + this.rollingSpeed = other.rollingSpeed; + } + + @Nonnull + public static PhysicsConfig deserialize(@Nonnull ByteBuf buf, int offset) { + PhysicsConfig obj = new PhysicsConfig(); + obj.type = PhysicsType.fromValue(buf.getByte(offset + 0)); + obj.density = buf.getDoubleLE(offset + 1); + obj.gravity = buf.getDoubleLE(offset + 9); + obj.bounciness = buf.getDoubleLE(offset + 17); + obj.bounceCount = buf.getIntLE(offset + 25); + obj.bounceLimit = buf.getDoubleLE(offset + 29); + obj.sticksVertically = buf.getByte(offset + 37) != 0; + obj.computeYaw = buf.getByte(offset + 38) != 0; + obj.computePitch = buf.getByte(offset + 39) != 0; + obj.rotationMode = RotationMode.fromValue(buf.getByte(offset + 40)); + obj.moveOutOfSolidSpeed = buf.getDoubleLE(offset + 41); + obj.terminalVelocityAir = buf.getDoubleLE(offset + 49); + obj.densityAir = buf.getDoubleLE(offset + 57); + obj.terminalVelocityWater = buf.getDoubleLE(offset + 65); + obj.densityWater = buf.getDoubleLE(offset + 73); + obj.hitWaterImpulseLoss = buf.getDoubleLE(offset + 81); + obj.rotationForce = buf.getDoubleLE(offset + 89); + obj.speedRotationFactor = buf.getFloatLE(offset + 97); + obj.swimmingDampingFactor = buf.getDoubleLE(offset + 101); + obj.allowRolling = buf.getByte(offset + 109) != 0; + obj.rollingFrictionFactor = buf.getDoubleLE(offset + 110); + obj.rollingSpeed = buf.getFloatLE(offset + 118); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 122; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.type.getValue()); + buf.writeDoubleLE(this.density); + buf.writeDoubleLE(this.gravity); + buf.writeDoubleLE(this.bounciness); + buf.writeIntLE(this.bounceCount); + buf.writeDoubleLE(this.bounceLimit); + buf.writeByte(this.sticksVertically ? 1 : 0); + buf.writeByte(this.computeYaw ? 1 : 0); + buf.writeByte(this.computePitch ? 1 : 0); + buf.writeByte(this.rotationMode.getValue()); + buf.writeDoubleLE(this.moveOutOfSolidSpeed); + buf.writeDoubleLE(this.terminalVelocityAir); + buf.writeDoubleLE(this.densityAir); + buf.writeDoubleLE(this.terminalVelocityWater); + buf.writeDoubleLE(this.densityWater); + buf.writeDoubleLE(this.hitWaterImpulseLoss); + buf.writeDoubleLE(this.rotationForce); + buf.writeFloatLE(this.speedRotationFactor); + buf.writeDoubleLE(this.swimmingDampingFactor); + buf.writeByte(this.allowRolling ? 1 : 0); + buf.writeDoubleLE(this.rollingFrictionFactor); + buf.writeFloatLE(this.rollingSpeed); + } + + public int computeSize() { + return 122; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 122 ? ValidationResult.error("Buffer too small: expected at least 122 bytes") : ValidationResult.OK; + } + + public PhysicsConfig clone() { + PhysicsConfig copy = new PhysicsConfig(); + copy.type = this.type; + copy.density = this.density; + copy.gravity = this.gravity; + copy.bounciness = this.bounciness; + copy.bounceCount = this.bounceCount; + copy.bounceLimit = this.bounceLimit; + copy.sticksVertically = this.sticksVertically; + copy.computeYaw = this.computeYaw; + copy.computePitch = this.computePitch; + copy.rotationMode = this.rotationMode; + copy.moveOutOfSolidSpeed = this.moveOutOfSolidSpeed; + copy.terminalVelocityAir = this.terminalVelocityAir; + copy.densityAir = this.densityAir; + copy.terminalVelocityWater = this.terminalVelocityWater; + copy.densityWater = this.densityWater; + copy.hitWaterImpulseLoss = this.hitWaterImpulseLoss; + copy.rotationForce = this.rotationForce; + copy.speedRotationFactor = this.speedRotationFactor; + copy.swimmingDampingFactor = this.swimmingDampingFactor; + copy.allowRolling = this.allowRolling; + copy.rollingFrictionFactor = this.rollingFrictionFactor; + copy.rollingSpeed = this.rollingSpeed; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof PhysicsConfig other) + ? false + : Objects.equals(this.type, other.type) + && this.density == other.density + && this.gravity == other.gravity + && this.bounciness == other.bounciness + && this.bounceCount == other.bounceCount + && this.bounceLimit == other.bounceLimit + && this.sticksVertically == other.sticksVertically + && this.computeYaw == other.computeYaw + && this.computePitch == other.computePitch + && Objects.equals(this.rotationMode, other.rotationMode) + && this.moveOutOfSolidSpeed == other.moveOutOfSolidSpeed + && this.terminalVelocityAir == other.terminalVelocityAir + && this.densityAir == other.densityAir + && this.terminalVelocityWater == other.terminalVelocityWater + && this.densityWater == other.densityWater + && this.hitWaterImpulseLoss == other.hitWaterImpulseLoss + && this.rotationForce == other.rotationForce + && this.speedRotationFactor == other.speedRotationFactor + && this.swimmingDampingFactor == other.swimmingDampingFactor + && this.allowRolling == other.allowRolling + && this.rollingFrictionFactor == other.rollingFrictionFactor + && this.rollingSpeed == other.rollingSpeed; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.type, + this.density, + this.gravity, + this.bounciness, + this.bounceCount, + this.bounceLimit, + this.sticksVertically, + this.computeYaw, + this.computePitch, + this.rotationMode, + this.moveOutOfSolidSpeed, + this.terminalVelocityAir, + this.densityAir, + this.terminalVelocityWater, + this.densityWater, + this.hitWaterImpulseLoss, + this.rotationForce, + this.speedRotationFactor, + this.swimmingDampingFactor, + this.allowRolling, + this.rollingFrictionFactor, + this.rollingSpeed + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/PhysicsType.java b/src/com/hypixel/hytale/protocol/PhysicsType.java new file mode 100644 index 0000000..1c421bb --- /dev/null +++ b/src/com/hypixel/hytale/protocol/PhysicsType.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum PhysicsType { + Standard(0); + + public static final PhysicsType[] VALUES = values(); + private final int value; + + private PhysicsType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static PhysicsType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("PhysicsType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/PickBlockInteraction.java b/src/com/hypixel/hytale/protocol/PickBlockInteraction.java new file mode 100644 index 0000000..5571a1f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/PickBlockInteraction.java @@ -0,0 +1,512 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PickBlockInteraction extends SimpleBlockInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 20; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 40; + public static final int MAX_SIZE = 1677721600; + + public PickBlockInteraction() { + } + + public PickBlockInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + boolean useLatestTarget + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.useLatestTarget = useLatestTarget; + } + + public PickBlockInteraction(@Nonnull PickBlockInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.useLatestTarget = other.useLatestTarget; + } + + @Nonnull + public static PickBlockInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + PickBlockInteraction obj = new PickBlockInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.useLatestTarget = buf.getByte(offset + 19) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 40 + buf.getIntLE(offset + 20); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 40 + buf.getIntLE(offset + 24); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 40 + buf.getIntLE(offset + 28); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 40 + buf.getIntLE(offset + 32); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 40 + buf.getIntLE(offset + 36); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 40; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 20); + int pos0 = offset + 40 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 24); + int pos1 = offset + 40 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 28); + int pos2 = offset + 40 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 32); + int pos3 = offset + 40 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 36); + int pos4 = offset + 40 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeByte(this.useLatestTarget ? 1 : 0); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 40; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 40) { + return ValidationResult.error("Buffer too small: expected at least 40 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 20); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 40 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 24); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 40 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 28); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 40 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 32); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 40 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 36); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 40 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public PickBlockInteraction clone() { + PickBlockInteraction copy = new PickBlockInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.useLatestTarget = this.useLatestTarget; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof PickBlockInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && this.useLatestTarget == other.useLatestTarget; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + return 31 * result + Boolean.hashCode(this.useLatestTarget); + } +} diff --git a/src/com/hypixel/hytale/protocol/PickupLocation.java b/src/com/hypixel/hytale/protocol/PickupLocation.java new file mode 100644 index 0000000..14a5f75 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/PickupLocation.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum PickupLocation { + Hotbar(0), + Storage(1); + + public static final PickupLocation[] VALUES = values(); + private final int value; + + private PickupLocation(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static PickupLocation fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("PickupLocation", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/PlaceBlockInteraction.java b/src/com/hypixel/hytale/protocol/PlaceBlockInteraction.java new file mode 100644 index 0000000..01e02a8 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/PlaceBlockInteraction.java @@ -0,0 +1,531 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlaceBlockInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 25; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 45; + public static final int MAX_SIZE = 1677721600; + public int blockId; + public boolean removeItemInHand; + public boolean allowDragPlacement; + + public PlaceBlockInteraction() { + } + + public PlaceBlockInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + int blockId, + boolean removeItemInHand, + boolean allowDragPlacement + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.blockId = blockId; + this.removeItemInHand = removeItemInHand; + this.allowDragPlacement = allowDragPlacement; + } + + public PlaceBlockInteraction(@Nonnull PlaceBlockInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.blockId = other.blockId; + this.removeItemInHand = other.removeItemInHand; + this.allowDragPlacement = other.allowDragPlacement; + } + + @Nonnull + public static PlaceBlockInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + PlaceBlockInteraction obj = new PlaceBlockInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.blockId = buf.getIntLE(offset + 19); + obj.removeItemInHand = buf.getByte(offset + 23) != 0; + obj.allowDragPlacement = buf.getByte(offset + 24) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 45 + buf.getIntLE(offset + 25); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 45 + buf.getIntLE(offset + 29); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 45 + buf.getIntLE(offset + 33); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 45 + buf.getIntLE(offset + 37); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 45 + buf.getIntLE(offset + 41); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 45; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 25); + int pos0 = offset + 45 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 29); + int pos1 = offset + 45 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 33); + int pos2 = offset + 45 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 37); + int pos3 = offset + 45 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 41); + int pos4 = offset + 45 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeIntLE(this.blockId); + buf.writeByte(this.removeItemInHand ? 1 : 0); + buf.writeByte(this.allowDragPlacement ? 1 : 0); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 45; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 45) { + return ValidationResult.error("Buffer too small: expected at least 45 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 25); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 45 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 29); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 45 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 33); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 45 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 37); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 45 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 41); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 45 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public PlaceBlockInteraction clone() { + PlaceBlockInteraction copy = new PlaceBlockInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.blockId = this.blockId; + copy.removeItemInHand = this.removeItemInHand; + copy.allowDragPlacement = this.allowDragPlacement; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof PlaceBlockInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && this.blockId == other.blockId + && this.removeItemInHand == other.removeItemInHand + && this.allowDragPlacement == other.allowDragPlacement; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Integer.hashCode(this.blockId); + result = 31 * result + Boolean.hashCode(this.removeItemInHand); + return 31 * result + Boolean.hashCode(this.allowDragPlacement); + } +} diff --git a/src/com/hypixel/hytale/protocol/PlayerSkin.java b/src/com/hypixel/hytale/protocol/PlayerSkin.java new file mode 100644 index 0000000..f13657e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/PlayerSkin.java @@ -0,0 +1,1602 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerSkin { + public static final int NULLABLE_BIT_FIELD_SIZE = 3; + public static final int FIXED_BLOCK_SIZE = 3; + public static final int VARIABLE_FIELD_COUNT = 20; + public static final int VARIABLE_BLOCK_START = 83; + public static final int MAX_SIZE = 327680183; + @Nullable + public String bodyCharacteristic; + @Nullable + public String underwear; + @Nullable + public String face; + @Nullable + public String eyes; + @Nullable + public String ears; + @Nullable + public String mouth; + @Nullable + public String facialHair; + @Nullable + public String haircut; + @Nullable + public String eyebrows; + @Nullable + public String pants; + @Nullable + public String overpants; + @Nullable + public String undertop; + @Nullable + public String overtop; + @Nullable + public String shoes; + @Nullable + public String headAccessory; + @Nullable + public String faceAccessory; + @Nullable + public String earAccessory; + @Nullable + public String skinFeature; + @Nullable + public String gloves; + @Nullable + public String cape; + + public PlayerSkin() { + } + + public PlayerSkin( + @Nullable String bodyCharacteristic, + @Nullable String underwear, + @Nullable String face, + @Nullable String eyes, + @Nullable String ears, + @Nullable String mouth, + @Nullable String facialHair, + @Nullable String haircut, + @Nullable String eyebrows, + @Nullable String pants, + @Nullable String overpants, + @Nullable String undertop, + @Nullable String overtop, + @Nullable String shoes, + @Nullable String headAccessory, + @Nullable String faceAccessory, + @Nullable String earAccessory, + @Nullable String skinFeature, + @Nullable String gloves, + @Nullable String cape + ) { + this.bodyCharacteristic = bodyCharacteristic; + this.underwear = underwear; + this.face = face; + this.eyes = eyes; + this.ears = ears; + this.mouth = mouth; + this.facialHair = facialHair; + this.haircut = haircut; + this.eyebrows = eyebrows; + this.pants = pants; + this.overpants = overpants; + this.undertop = undertop; + this.overtop = overtop; + this.shoes = shoes; + this.headAccessory = headAccessory; + this.faceAccessory = faceAccessory; + this.earAccessory = earAccessory; + this.skinFeature = skinFeature; + this.gloves = gloves; + this.cape = cape; + } + + public PlayerSkin(@Nonnull PlayerSkin other) { + this.bodyCharacteristic = other.bodyCharacteristic; + this.underwear = other.underwear; + this.face = other.face; + this.eyes = other.eyes; + this.ears = other.ears; + this.mouth = other.mouth; + this.facialHair = other.facialHair; + this.haircut = other.haircut; + this.eyebrows = other.eyebrows; + this.pants = other.pants; + this.overpants = other.overpants; + this.undertop = other.undertop; + this.overtop = other.overtop; + this.shoes = other.shoes; + this.headAccessory = other.headAccessory; + this.faceAccessory = other.faceAccessory; + this.earAccessory = other.earAccessory; + this.skinFeature = other.skinFeature; + this.gloves = other.gloves; + this.cape = other.cape; + } + + @Nonnull + public static PlayerSkin deserialize(@Nonnull ByteBuf buf, int offset) { + PlayerSkin obj = new PlayerSkin(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 3); + if ((nullBits[0] & 1) != 0) { + int varPos0 = offset + 83 + buf.getIntLE(offset + 3); + int bodyCharacteristicLen = VarInt.peek(buf, varPos0); + if (bodyCharacteristicLen < 0) { + throw ProtocolException.negativeLength("BodyCharacteristic", bodyCharacteristicLen); + } + + if (bodyCharacteristicLen > 4096000) { + throw ProtocolException.stringTooLong("BodyCharacteristic", bodyCharacteristicLen, 4096000); + } + + obj.bodyCharacteristic = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits[0] & 2) != 0) { + int varPos1 = offset + 83 + buf.getIntLE(offset + 7); + int underwearLen = VarInt.peek(buf, varPos1); + if (underwearLen < 0) { + throw ProtocolException.negativeLength("Underwear", underwearLen); + } + + if (underwearLen > 4096000) { + throw ProtocolException.stringTooLong("Underwear", underwearLen, 4096000); + } + + obj.underwear = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits[0] & 4) != 0) { + int varPos2 = offset + 83 + buf.getIntLE(offset + 11); + int faceLen = VarInt.peek(buf, varPos2); + if (faceLen < 0) { + throw ProtocolException.negativeLength("Face", faceLen); + } + + if (faceLen > 4096000) { + throw ProtocolException.stringTooLong("Face", faceLen, 4096000); + } + + obj.face = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits[0] & 8) != 0) { + int varPos3 = offset + 83 + buf.getIntLE(offset + 15); + int eyesLen = VarInt.peek(buf, varPos3); + if (eyesLen < 0) { + throw ProtocolException.negativeLength("Eyes", eyesLen); + } + + if (eyesLen > 4096000) { + throw ProtocolException.stringTooLong("Eyes", eyesLen, 4096000); + } + + obj.eyes = PacketIO.readVarString(buf, varPos3, PacketIO.UTF8); + } + + if ((nullBits[0] & 16) != 0) { + int varPos4 = offset + 83 + buf.getIntLE(offset + 19); + int earsLen = VarInt.peek(buf, varPos4); + if (earsLen < 0) { + throw ProtocolException.negativeLength("Ears", earsLen); + } + + if (earsLen > 4096000) { + throw ProtocolException.stringTooLong("Ears", earsLen, 4096000); + } + + obj.ears = PacketIO.readVarString(buf, varPos4, PacketIO.UTF8); + } + + if ((nullBits[0] & 32) != 0) { + int varPos5 = offset + 83 + buf.getIntLE(offset + 23); + int mouthLen = VarInt.peek(buf, varPos5); + if (mouthLen < 0) { + throw ProtocolException.negativeLength("Mouth", mouthLen); + } + + if (mouthLen > 4096000) { + throw ProtocolException.stringTooLong("Mouth", mouthLen, 4096000); + } + + obj.mouth = PacketIO.readVarString(buf, varPos5, PacketIO.UTF8); + } + + if ((nullBits[0] & 64) != 0) { + int varPos6 = offset + 83 + buf.getIntLE(offset + 27); + int facialHairLen = VarInt.peek(buf, varPos6); + if (facialHairLen < 0) { + throw ProtocolException.negativeLength("FacialHair", facialHairLen); + } + + if (facialHairLen > 4096000) { + throw ProtocolException.stringTooLong("FacialHair", facialHairLen, 4096000); + } + + obj.facialHair = PacketIO.readVarString(buf, varPos6, PacketIO.UTF8); + } + + if ((nullBits[0] & 128) != 0) { + int varPos7 = offset + 83 + buf.getIntLE(offset + 31); + int haircutLen = VarInt.peek(buf, varPos7); + if (haircutLen < 0) { + throw ProtocolException.negativeLength("Haircut", haircutLen); + } + + if (haircutLen > 4096000) { + throw ProtocolException.stringTooLong("Haircut", haircutLen, 4096000); + } + + obj.haircut = PacketIO.readVarString(buf, varPos7, PacketIO.UTF8); + } + + if ((nullBits[1] & 1) != 0) { + int varPos8 = offset + 83 + buf.getIntLE(offset + 35); + int eyebrowsLen = VarInt.peek(buf, varPos8); + if (eyebrowsLen < 0) { + throw ProtocolException.negativeLength("Eyebrows", eyebrowsLen); + } + + if (eyebrowsLen > 4096000) { + throw ProtocolException.stringTooLong("Eyebrows", eyebrowsLen, 4096000); + } + + obj.eyebrows = PacketIO.readVarString(buf, varPos8, PacketIO.UTF8); + } + + if ((nullBits[1] & 2) != 0) { + int varPos9 = offset + 83 + buf.getIntLE(offset + 39); + int pantsLen = VarInt.peek(buf, varPos9); + if (pantsLen < 0) { + throw ProtocolException.negativeLength("Pants", pantsLen); + } + + if (pantsLen > 4096000) { + throw ProtocolException.stringTooLong("Pants", pantsLen, 4096000); + } + + obj.pants = PacketIO.readVarString(buf, varPos9, PacketIO.UTF8); + } + + if ((nullBits[1] & 4) != 0) { + int varPos10 = offset + 83 + buf.getIntLE(offset + 43); + int overpantsLen = VarInt.peek(buf, varPos10); + if (overpantsLen < 0) { + throw ProtocolException.negativeLength("Overpants", overpantsLen); + } + + if (overpantsLen > 4096000) { + throw ProtocolException.stringTooLong("Overpants", overpantsLen, 4096000); + } + + obj.overpants = PacketIO.readVarString(buf, varPos10, PacketIO.UTF8); + } + + if ((nullBits[1] & 8) != 0) { + int varPos11 = offset + 83 + buf.getIntLE(offset + 47); + int undertopLen = VarInt.peek(buf, varPos11); + if (undertopLen < 0) { + throw ProtocolException.negativeLength("Undertop", undertopLen); + } + + if (undertopLen > 4096000) { + throw ProtocolException.stringTooLong("Undertop", undertopLen, 4096000); + } + + obj.undertop = PacketIO.readVarString(buf, varPos11, PacketIO.UTF8); + } + + if ((nullBits[1] & 16) != 0) { + int varPos12 = offset + 83 + buf.getIntLE(offset + 51); + int overtopLen = VarInt.peek(buf, varPos12); + if (overtopLen < 0) { + throw ProtocolException.negativeLength("Overtop", overtopLen); + } + + if (overtopLen > 4096000) { + throw ProtocolException.stringTooLong("Overtop", overtopLen, 4096000); + } + + obj.overtop = PacketIO.readVarString(buf, varPos12, PacketIO.UTF8); + } + + if ((nullBits[1] & 32) != 0) { + int varPos13 = offset + 83 + buf.getIntLE(offset + 55); + int shoesLen = VarInt.peek(buf, varPos13); + if (shoesLen < 0) { + throw ProtocolException.negativeLength("Shoes", shoesLen); + } + + if (shoesLen > 4096000) { + throw ProtocolException.stringTooLong("Shoes", shoesLen, 4096000); + } + + obj.shoes = PacketIO.readVarString(buf, varPos13, PacketIO.UTF8); + } + + if ((nullBits[1] & 64) != 0) { + int varPos14 = offset + 83 + buf.getIntLE(offset + 59); + int headAccessoryLen = VarInt.peek(buf, varPos14); + if (headAccessoryLen < 0) { + throw ProtocolException.negativeLength("HeadAccessory", headAccessoryLen); + } + + if (headAccessoryLen > 4096000) { + throw ProtocolException.stringTooLong("HeadAccessory", headAccessoryLen, 4096000); + } + + obj.headAccessory = PacketIO.readVarString(buf, varPos14, PacketIO.UTF8); + } + + if ((nullBits[1] & 128) != 0) { + int varPos15 = offset + 83 + buf.getIntLE(offset + 63); + int faceAccessoryLen = VarInt.peek(buf, varPos15); + if (faceAccessoryLen < 0) { + throw ProtocolException.negativeLength("FaceAccessory", faceAccessoryLen); + } + + if (faceAccessoryLen > 4096000) { + throw ProtocolException.stringTooLong("FaceAccessory", faceAccessoryLen, 4096000); + } + + obj.faceAccessory = PacketIO.readVarString(buf, varPos15, PacketIO.UTF8); + } + + if ((nullBits[2] & 1) != 0) { + int varPos16 = offset + 83 + buf.getIntLE(offset + 67); + int earAccessoryLen = VarInt.peek(buf, varPos16); + if (earAccessoryLen < 0) { + throw ProtocolException.negativeLength("EarAccessory", earAccessoryLen); + } + + if (earAccessoryLen > 4096000) { + throw ProtocolException.stringTooLong("EarAccessory", earAccessoryLen, 4096000); + } + + obj.earAccessory = PacketIO.readVarString(buf, varPos16, PacketIO.UTF8); + } + + if ((nullBits[2] & 2) != 0) { + int varPos17 = offset + 83 + buf.getIntLE(offset + 71); + int skinFeatureLen = VarInt.peek(buf, varPos17); + if (skinFeatureLen < 0) { + throw ProtocolException.negativeLength("SkinFeature", skinFeatureLen); + } + + if (skinFeatureLen > 4096000) { + throw ProtocolException.stringTooLong("SkinFeature", skinFeatureLen, 4096000); + } + + obj.skinFeature = PacketIO.readVarString(buf, varPos17, PacketIO.UTF8); + } + + if ((nullBits[2] & 4) != 0) { + int varPos18 = offset + 83 + buf.getIntLE(offset + 75); + int glovesLen = VarInt.peek(buf, varPos18); + if (glovesLen < 0) { + throw ProtocolException.negativeLength("Gloves", glovesLen); + } + + if (glovesLen > 4096000) { + throw ProtocolException.stringTooLong("Gloves", glovesLen, 4096000); + } + + obj.gloves = PacketIO.readVarString(buf, varPos18, PacketIO.UTF8); + } + + if ((nullBits[2] & 8) != 0) { + int varPos19 = offset + 83 + buf.getIntLE(offset + 79); + int capeLen = VarInt.peek(buf, varPos19); + if (capeLen < 0) { + throw ProtocolException.negativeLength("Cape", capeLen); + } + + if (capeLen > 4096000) { + throw ProtocolException.stringTooLong("Cape", capeLen, 4096000); + } + + obj.cape = PacketIO.readVarString(buf, varPos19, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte[] nullBits = PacketIO.readBytes(buf, offset, 3); + int maxEnd = 83; + if ((nullBits[0] & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 3); + int pos0 = offset + 83 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits[0] & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 7); + int pos1 = offset + 83 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits[0] & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 11); + int pos2 = offset + 83 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits[0] & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 15); + int pos3 = offset + 83 + fieldOffset3; + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits[0] & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 19); + int pos4 = offset + 83 + fieldOffset4; + int sl = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4) + sl; + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits[0] & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 23); + int pos5 = offset + 83 + fieldOffset5; + int sl = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + sl; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits[0] & 64) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 27); + int pos6 = offset + 83 + fieldOffset6; + int sl = VarInt.peek(buf, pos6); + pos6 += VarInt.length(buf, pos6) + sl; + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + if ((nullBits[0] & 128) != 0) { + int fieldOffset7 = buf.getIntLE(offset + 31); + int pos7 = offset + 83 + fieldOffset7; + int sl = VarInt.peek(buf, pos7); + pos7 += VarInt.length(buf, pos7) + sl; + if (pos7 - offset > maxEnd) { + maxEnd = pos7 - offset; + } + } + + if ((nullBits[1] & 1) != 0) { + int fieldOffset8 = buf.getIntLE(offset + 35); + int pos8 = offset + 83 + fieldOffset8; + int sl = VarInt.peek(buf, pos8); + pos8 += VarInt.length(buf, pos8) + sl; + if (pos8 - offset > maxEnd) { + maxEnd = pos8 - offset; + } + } + + if ((nullBits[1] & 2) != 0) { + int fieldOffset9 = buf.getIntLE(offset + 39); + int pos9 = offset + 83 + fieldOffset9; + int sl = VarInt.peek(buf, pos9); + pos9 += VarInt.length(buf, pos9) + sl; + if (pos9 - offset > maxEnd) { + maxEnd = pos9 - offset; + } + } + + if ((nullBits[1] & 4) != 0) { + int fieldOffset10 = buf.getIntLE(offset + 43); + int pos10 = offset + 83 + fieldOffset10; + int sl = VarInt.peek(buf, pos10); + pos10 += VarInt.length(buf, pos10) + sl; + if (pos10 - offset > maxEnd) { + maxEnd = pos10 - offset; + } + } + + if ((nullBits[1] & 8) != 0) { + int fieldOffset11 = buf.getIntLE(offset + 47); + int pos11 = offset + 83 + fieldOffset11; + int sl = VarInt.peek(buf, pos11); + pos11 += VarInt.length(buf, pos11) + sl; + if (pos11 - offset > maxEnd) { + maxEnd = pos11 - offset; + } + } + + if ((nullBits[1] & 16) != 0) { + int fieldOffset12 = buf.getIntLE(offset + 51); + int pos12 = offset + 83 + fieldOffset12; + int sl = VarInt.peek(buf, pos12); + pos12 += VarInt.length(buf, pos12) + sl; + if (pos12 - offset > maxEnd) { + maxEnd = pos12 - offset; + } + } + + if ((nullBits[1] & 32) != 0) { + int fieldOffset13 = buf.getIntLE(offset + 55); + int pos13 = offset + 83 + fieldOffset13; + int sl = VarInt.peek(buf, pos13); + pos13 += VarInt.length(buf, pos13) + sl; + if (pos13 - offset > maxEnd) { + maxEnd = pos13 - offset; + } + } + + if ((nullBits[1] & 64) != 0) { + int fieldOffset14 = buf.getIntLE(offset + 59); + int pos14 = offset + 83 + fieldOffset14; + int sl = VarInt.peek(buf, pos14); + pos14 += VarInt.length(buf, pos14) + sl; + if (pos14 - offset > maxEnd) { + maxEnd = pos14 - offset; + } + } + + if ((nullBits[1] & 128) != 0) { + int fieldOffset15 = buf.getIntLE(offset + 63); + int pos15 = offset + 83 + fieldOffset15; + int sl = VarInt.peek(buf, pos15); + pos15 += VarInt.length(buf, pos15) + sl; + if (pos15 - offset > maxEnd) { + maxEnd = pos15 - offset; + } + } + + if ((nullBits[2] & 1) != 0) { + int fieldOffset16 = buf.getIntLE(offset + 67); + int pos16 = offset + 83 + fieldOffset16; + int sl = VarInt.peek(buf, pos16); + pos16 += VarInt.length(buf, pos16) + sl; + if (pos16 - offset > maxEnd) { + maxEnd = pos16 - offset; + } + } + + if ((nullBits[2] & 2) != 0) { + int fieldOffset17 = buf.getIntLE(offset + 71); + int pos17 = offset + 83 + fieldOffset17; + int sl = VarInt.peek(buf, pos17); + pos17 += VarInt.length(buf, pos17) + sl; + if (pos17 - offset > maxEnd) { + maxEnd = pos17 - offset; + } + } + + if ((nullBits[2] & 4) != 0) { + int fieldOffset18 = buf.getIntLE(offset + 75); + int pos18 = offset + 83 + fieldOffset18; + int sl = VarInt.peek(buf, pos18); + pos18 += VarInt.length(buf, pos18) + sl; + if (pos18 - offset > maxEnd) { + maxEnd = pos18 - offset; + } + } + + if ((nullBits[2] & 8) != 0) { + int fieldOffset19 = buf.getIntLE(offset + 79); + int pos19 = offset + 83 + fieldOffset19; + int sl = VarInt.peek(buf, pos19); + pos19 += VarInt.length(buf, pos19) + sl; + if (pos19 - offset > maxEnd) { + maxEnd = pos19 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte[] nullBits = new byte[3]; + if (this.bodyCharacteristic != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.underwear != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.face != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.eyes != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.ears != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.mouth != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.facialHair != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.haircut != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.eyebrows != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + if (this.pants != null) { + nullBits[1] = (byte)(nullBits[1] | 2); + } + + if (this.overpants != null) { + nullBits[1] = (byte)(nullBits[1] | 4); + } + + if (this.undertop != null) { + nullBits[1] = (byte)(nullBits[1] | 8); + } + + if (this.overtop != null) { + nullBits[1] = (byte)(nullBits[1] | 16); + } + + if (this.shoes != null) { + nullBits[1] = (byte)(nullBits[1] | 32); + } + + if (this.headAccessory != null) { + nullBits[1] = (byte)(nullBits[1] | 64); + } + + if (this.faceAccessory != null) { + nullBits[1] = (byte)(nullBits[1] | 128); + } + + if (this.earAccessory != null) { + nullBits[2] = (byte)(nullBits[2] | 1); + } + + if (this.skinFeature != null) { + nullBits[2] = (byte)(nullBits[2] | 2); + } + + if (this.gloves != null) { + nullBits[2] = (byte)(nullBits[2] | 4); + } + + if (this.cape != null) { + nullBits[2] = (byte)(nullBits[2] | 8); + } + + buf.writeBytes(nullBits); + int bodyCharacteristicOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int underwearOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int faceOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int eyesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int earsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int mouthOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int facialHairOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int haircutOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int eyebrowsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int pantsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int overpantsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int undertopOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int overtopOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int shoesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int headAccessoryOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int faceAccessoryOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int earAccessoryOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int skinFeatureOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int glovesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int capeOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.bodyCharacteristic != null) { + buf.setIntLE(bodyCharacteristicOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.bodyCharacteristic, 4096000); + } else { + buf.setIntLE(bodyCharacteristicOffsetSlot, -1); + } + + if (this.underwear != null) { + buf.setIntLE(underwearOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.underwear, 4096000); + } else { + buf.setIntLE(underwearOffsetSlot, -1); + } + + if (this.face != null) { + buf.setIntLE(faceOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.face, 4096000); + } else { + buf.setIntLE(faceOffsetSlot, -1); + } + + if (this.eyes != null) { + buf.setIntLE(eyesOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.eyes, 4096000); + } else { + buf.setIntLE(eyesOffsetSlot, -1); + } + + if (this.ears != null) { + buf.setIntLE(earsOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.ears, 4096000); + } else { + buf.setIntLE(earsOffsetSlot, -1); + } + + if (this.mouth != null) { + buf.setIntLE(mouthOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.mouth, 4096000); + } else { + buf.setIntLE(mouthOffsetSlot, -1); + } + + if (this.facialHair != null) { + buf.setIntLE(facialHairOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.facialHair, 4096000); + } else { + buf.setIntLE(facialHairOffsetSlot, -1); + } + + if (this.haircut != null) { + buf.setIntLE(haircutOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.haircut, 4096000); + } else { + buf.setIntLE(haircutOffsetSlot, -1); + } + + if (this.eyebrows != null) { + buf.setIntLE(eyebrowsOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.eyebrows, 4096000); + } else { + buf.setIntLE(eyebrowsOffsetSlot, -1); + } + + if (this.pants != null) { + buf.setIntLE(pantsOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.pants, 4096000); + } else { + buf.setIntLE(pantsOffsetSlot, -1); + } + + if (this.overpants != null) { + buf.setIntLE(overpantsOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.overpants, 4096000); + } else { + buf.setIntLE(overpantsOffsetSlot, -1); + } + + if (this.undertop != null) { + buf.setIntLE(undertopOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.undertop, 4096000); + } else { + buf.setIntLE(undertopOffsetSlot, -1); + } + + if (this.overtop != null) { + buf.setIntLE(overtopOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.overtop, 4096000); + } else { + buf.setIntLE(overtopOffsetSlot, -1); + } + + if (this.shoes != null) { + buf.setIntLE(shoesOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.shoes, 4096000); + } else { + buf.setIntLE(shoesOffsetSlot, -1); + } + + if (this.headAccessory != null) { + buf.setIntLE(headAccessoryOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.headAccessory, 4096000); + } else { + buf.setIntLE(headAccessoryOffsetSlot, -1); + } + + if (this.faceAccessory != null) { + buf.setIntLE(faceAccessoryOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.faceAccessory, 4096000); + } else { + buf.setIntLE(faceAccessoryOffsetSlot, -1); + } + + if (this.earAccessory != null) { + buf.setIntLE(earAccessoryOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.earAccessory, 4096000); + } else { + buf.setIntLE(earAccessoryOffsetSlot, -1); + } + + if (this.skinFeature != null) { + buf.setIntLE(skinFeatureOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.skinFeature, 4096000); + } else { + buf.setIntLE(skinFeatureOffsetSlot, -1); + } + + if (this.gloves != null) { + buf.setIntLE(glovesOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.gloves, 4096000); + } else { + buf.setIntLE(glovesOffsetSlot, -1); + } + + if (this.cape != null) { + buf.setIntLE(capeOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.cape, 4096000); + } else { + buf.setIntLE(capeOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 83; + if (this.bodyCharacteristic != null) { + size += PacketIO.stringSize(this.bodyCharacteristic); + } + + if (this.underwear != null) { + size += PacketIO.stringSize(this.underwear); + } + + if (this.face != null) { + size += PacketIO.stringSize(this.face); + } + + if (this.eyes != null) { + size += PacketIO.stringSize(this.eyes); + } + + if (this.ears != null) { + size += PacketIO.stringSize(this.ears); + } + + if (this.mouth != null) { + size += PacketIO.stringSize(this.mouth); + } + + if (this.facialHair != null) { + size += PacketIO.stringSize(this.facialHair); + } + + if (this.haircut != null) { + size += PacketIO.stringSize(this.haircut); + } + + if (this.eyebrows != null) { + size += PacketIO.stringSize(this.eyebrows); + } + + if (this.pants != null) { + size += PacketIO.stringSize(this.pants); + } + + if (this.overpants != null) { + size += PacketIO.stringSize(this.overpants); + } + + if (this.undertop != null) { + size += PacketIO.stringSize(this.undertop); + } + + if (this.overtop != null) { + size += PacketIO.stringSize(this.overtop); + } + + if (this.shoes != null) { + size += PacketIO.stringSize(this.shoes); + } + + if (this.headAccessory != null) { + size += PacketIO.stringSize(this.headAccessory); + } + + if (this.faceAccessory != null) { + size += PacketIO.stringSize(this.faceAccessory); + } + + if (this.earAccessory != null) { + size += PacketIO.stringSize(this.earAccessory); + } + + if (this.skinFeature != null) { + size += PacketIO.stringSize(this.skinFeature); + } + + if (this.gloves != null) { + size += PacketIO.stringSize(this.gloves); + } + + if (this.cape != null) { + size += PacketIO.stringSize(this.cape); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 83) { + return ValidationResult.error("Buffer too small: expected at least 83 bytes"); + } else { + byte[] nullBits = PacketIO.readBytes(buffer, offset, 3); + if ((nullBits[0] & 1) != 0) { + int bodyCharacteristicOffset = buffer.getIntLE(offset + 3); + if (bodyCharacteristicOffset < 0) { + return ValidationResult.error("Invalid offset for BodyCharacteristic"); + } + + int pos = offset + 83 + bodyCharacteristicOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BodyCharacteristic"); + } + + int bodyCharacteristicLen = VarInt.peek(buffer, pos); + if (bodyCharacteristicLen < 0) { + return ValidationResult.error("Invalid string length for BodyCharacteristic"); + } + + if (bodyCharacteristicLen > 4096000) { + return ValidationResult.error("BodyCharacteristic exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += bodyCharacteristicLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading BodyCharacteristic"); + } + } + + if ((nullBits[0] & 2) != 0) { + int underwearOffset = buffer.getIntLE(offset + 7); + if (underwearOffset < 0) { + return ValidationResult.error("Invalid offset for Underwear"); + } + + int posx = offset + 83 + underwearOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Underwear"); + } + + int underwearLen = VarInt.peek(buffer, posx); + if (underwearLen < 0) { + return ValidationResult.error("Invalid string length for Underwear"); + } + + if (underwearLen > 4096000) { + return ValidationResult.error("Underwear exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += underwearLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Underwear"); + } + } + + if ((nullBits[0] & 4) != 0) { + int faceOffset = buffer.getIntLE(offset + 11); + if (faceOffset < 0) { + return ValidationResult.error("Invalid offset for Face"); + } + + int posxx = offset + 83 + faceOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Face"); + } + + int faceLen = VarInt.peek(buffer, posxx); + if (faceLen < 0) { + return ValidationResult.error("Invalid string length for Face"); + } + + if (faceLen > 4096000) { + return ValidationResult.error("Face exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += faceLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Face"); + } + } + + if ((nullBits[0] & 8) != 0) { + int eyesOffset = buffer.getIntLE(offset + 15); + if (eyesOffset < 0) { + return ValidationResult.error("Invalid offset for Eyes"); + } + + int posxxx = offset + 83 + eyesOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Eyes"); + } + + int eyesLen = VarInt.peek(buffer, posxxx); + if (eyesLen < 0) { + return ValidationResult.error("Invalid string length for Eyes"); + } + + if (eyesLen > 4096000) { + return ValidationResult.error("Eyes exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += eyesLen; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Eyes"); + } + } + + if ((nullBits[0] & 16) != 0) { + int earsOffset = buffer.getIntLE(offset + 19); + if (earsOffset < 0) { + return ValidationResult.error("Invalid offset for Ears"); + } + + int posxxxx = offset + 83 + earsOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Ears"); + } + + int earsLen = VarInt.peek(buffer, posxxxx); + if (earsLen < 0) { + return ValidationResult.error("Invalid string length for Ears"); + } + + if (earsLen > 4096000) { + return ValidationResult.error("Ears exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + posxxxx += earsLen; + if (posxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Ears"); + } + } + + if ((nullBits[0] & 32) != 0) { + int mouthOffset = buffer.getIntLE(offset + 23); + if (mouthOffset < 0) { + return ValidationResult.error("Invalid offset for Mouth"); + } + + int posxxxxx = offset + 83 + mouthOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Mouth"); + } + + int mouthLen = VarInt.peek(buffer, posxxxxx); + if (mouthLen < 0) { + return ValidationResult.error("Invalid string length for Mouth"); + } + + if (mouthLen > 4096000) { + return ValidationResult.error("Mouth exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += mouthLen; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Mouth"); + } + } + + if ((nullBits[0] & 64) != 0) { + int facialHairOffset = buffer.getIntLE(offset + 27); + if (facialHairOffset < 0) { + return ValidationResult.error("Invalid offset for FacialHair"); + } + + int posxxxxxx = offset + 83 + facialHairOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FacialHair"); + } + + int facialHairLen = VarInt.peek(buffer, posxxxxxx); + if (facialHairLen < 0) { + return ValidationResult.error("Invalid string length for FacialHair"); + } + + if (facialHairLen > 4096000) { + return ValidationResult.error("FacialHair exceeds max length 4096000"); + } + + posxxxxxx += VarInt.length(buffer, posxxxxxx); + posxxxxxx += facialHairLen; + if (posxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading FacialHair"); + } + } + + if ((nullBits[0] & 128) != 0) { + int haircutOffset = buffer.getIntLE(offset + 31); + if (haircutOffset < 0) { + return ValidationResult.error("Invalid offset for Haircut"); + } + + int posxxxxxxx = offset + 83 + haircutOffset; + if (posxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Haircut"); + } + + int haircutLen = VarInt.peek(buffer, posxxxxxxx); + if (haircutLen < 0) { + return ValidationResult.error("Invalid string length for Haircut"); + } + + if (haircutLen > 4096000) { + return ValidationResult.error("Haircut exceeds max length 4096000"); + } + + posxxxxxxx += VarInt.length(buffer, posxxxxxxx); + posxxxxxxx += haircutLen; + if (posxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Haircut"); + } + } + + if ((nullBits[1] & 1) != 0) { + int eyebrowsOffset = buffer.getIntLE(offset + 35); + if (eyebrowsOffset < 0) { + return ValidationResult.error("Invalid offset for Eyebrows"); + } + + int posxxxxxxxx = offset + 83 + eyebrowsOffset; + if (posxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Eyebrows"); + } + + int eyebrowsLen = VarInt.peek(buffer, posxxxxxxxx); + if (eyebrowsLen < 0) { + return ValidationResult.error("Invalid string length for Eyebrows"); + } + + if (eyebrowsLen > 4096000) { + return ValidationResult.error("Eyebrows exceeds max length 4096000"); + } + + posxxxxxxxx += VarInt.length(buffer, posxxxxxxxx); + posxxxxxxxx += eyebrowsLen; + if (posxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Eyebrows"); + } + } + + if ((nullBits[1] & 2) != 0) { + int pantsOffset = buffer.getIntLE(offset + 39); + if (pantsOffset < 0) { + return ValidationResult.error("Invalid offset for Pants"); + } + + int posxxxxxxxxx = offset + 83 + pantsOffset; + if (posxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Pants"); + } + + int pantsLen = VarInt.peek(buffer, posxxxxxxxxx); + if (pantsLen < 0) { + return ValidationResult.error("Invalid string length for Pants"); + } + + if (pantsLen > 4096000) { + return ValidationResult.error("Pants exceeds max length 4096000"); + } + + posxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxx); + posxxxxxxxxx += pantsLen; + if (posxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Pants"); + } + } + + if ((nullBits[1] & 4) != 0) { + int overpantsOffset = buffer.getIntLE(offset + 43); + if (overpantsOffset < 0) { + return ValidationResult.error("Invalid offset for Overpants"); + } + + int posxxxxxxxxxx = offset + 83 + overpantsOffset; + if (posxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Overpants"); + } + + int overpantsLen = VarInt.peek(buffer, posxxxxxxxxxx); + if (overpantsLen < 0) { + return ValidationResult.error("Invalid string length for Overpants"); + } + + if (overpantsLen > 4096000) { + return ValidationResult.error("Overpants exceeds max length 4096000"); + } + + posxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxx); + posxxxxxxxxxx += overpantsLen; + if (posxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Overpants"); + } + } + + if ((nullBits[1] & 8) != 0) { + int undertopOffset = buffer.getIntLE(offset + 47); + if (undertopOffset < 0) { + return ValidationResult.error("Invalid offset for Undertop"); + } + + int posxxxxxxxxxxx = offset + 83 + undertopOffset; + if (posxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Undertop"); + } + + int undertopLen = VarInt.peek(buffer, posxxxxxxxxxxx); + if (undertopLen < 0) { + return ValidationResult.error("Invalid string length for Undertop"); + } + + if (undertopLen > 4096000) { + return ValidationResult.error("Undertop exceeds max length 4096000"); + } + + posxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxx); + posxxxxxxxxxxx += undertopLen; + if (posxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Undertop"); + } + } + + if ((nullBits[1] & 16) != 0) { + int overtopOffset = buffer.getIntLE(offset + 51); + if (overtopOffset < 0) { + return ValidationResult.error("Invalid offset for Overtop"); + } + + int posxxxxxxxxxxxx = offset + 83 + overtopOffset; + if (posxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Overtop"); + } + + int overtopLen = VarInt.peek(buffer, posxxxxxxxxxxxx); + if (overtopLen < 0) { + return ValidationResult.error("Invalid string length for Overtop"); + } + + if (overtopLen > 4096000) { + return ValidationResult.error("Overtop exceeds max length 4096000"); + } + + posxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxx); + posxxxxxxxxxxxx += overtopLen; + if (posxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Overtop"); + } + } + + if ((nullBits[1] & 32) != 0) { + int shoesOffset = buffer.getIntLE(offset + 55); + if (shoesOffset < 0) { + return ValidationResult.error("Invalid offset for Shoes"); + } + + int posxxxxxxxxxxxxx = offset + 83 + shoesOffset; + if (posxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Shoes"); + } + + int shoesLen = VarInt.peek(buffer, posxxxxxxxxxxxxx); + if (shoesLen < 0) { + return ValidationResult.error("Invalid string length for Shoes"); + } + + if (shoesLen > 4096000) { + return ValidationResult.error("Shoes exceeds max length 4096000"); + } + + posxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxx); + posxxxxxxxxxxxxx += shoesLen; + if (posxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Shoes"); + } + } + + if ((nullBits[1] & 64) != 0) { + int headAccessoryOffset = buffer.getIntLE(offset + 59); + if (headAccessoryOffset < 0) { + return ValidationResult.error("Invalid offset for HeadAccessory"); + } + + int posxxxxxxxxxxxxxx = offset + 83 + headAccessoryOffset; + if (posxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for HeadAccessory"); + } + + int headAccessoryLen = VarInt.peek(buffer, posxxxxxxxxxxxxxx); + if (headAccessoryLen < 0) { + return ValidationResult.error("Invalid string length for HeadAccessory"); + } + + if (headAccessoryLen > 4096000) { + return ValidationResult.error("HeadAccessory exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxx); + posxxxxxxxxxxxxxx += headAccessoryLen; + if (posxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading HeadAccessory"); + } + } + + if ((nullBits[1] & 128) != 0) { + int faceAccessoryOffset = buffer.getIntLE(offset + 63); + if (faceAccessoryOffset < 0) { + return ValidationResult.error("Invalid offset for FaceAccessory"); + } + + int posxxxxxxxxxxxxxxx = offset + 83 + faceAccessoryOffset; + if (posxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FaceAccessory"); + } + + int faceAccessoryLen = VarInt.peek(buffer, posxxxxxxxxxxxxxxx); + if (faceAccessoryLen < 0) { + return ValidationResult.error("Invalid string length for FaceAccessory"); + } + + if (faceAccessoryLen > 4096000) { + return ValidationResult.error("FaceAccessory exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxx); + posxxxxxxxxxxxxxxx += faceAccessoryLen; + if (posxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading FaceAccessory"); + } + } + + if ((nullBits[2] & 1) != 0) { + int earAccessoryOffset = buffer.getIntLE(offset + 67); + if (earAccessoryOffset < 0) { + return ValidationResult.error("Invalid offset for EarAccessory"); + } + + int posxxxxxxxxxxxxxxxx = offset + 83 + earAccessoryOffset; + if (posxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for EarAccessory"); + } + + int earAccessoryLen = VarInt.peek(buffer, posxxxxxxxxxxxxxxxx); + if (earAccessoryLen < 0) { + return ValidationResult.error("Invalid string length for EarAccessory"); + } + + if (earAccessoryLen > 4096000) { + return ValidationResult.error("EarAccessory exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxx); + posxxxxxxxxxxxxxxxx += earAccessoryLen; + if (posxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading EarAccessory"); + } + } + + if ((nullBits[2] & 2) != 0) { + int skinFeatureOffset = buffer.getIntLE(offset + 71); + if (skinFeatureOffset < 0) { + return ValidationResult.error("Invalid offset for SkinFeature"); + } + + int posxxxxxxxxxxxxxxxxx = offset + 83 + skinFeatureOffset; + if (posxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SkinFeature"); + } + + int skinFeatureLen = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxx); + if (skinFeatureLen < 0) { + return ValidationResult.error("Invalid string length for SkinFeature"); + } + + if (skinFeatureLen > 4096000) { + return ValidationResult.error("SkinFeature exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxx); + posxxxxxxxxxxxxxxxxx += skinFeatureLen; + if (posxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading SkinFeature"); + } + } + + if ((nullBits[2] & 4) != 0) { + int glovesOffset = buffer.getIntLE(offset + 75); + if (glovesOffset < 0) { + return ValidationResult.error("Invalid offset for Gloves"); + } + + int posxxxxxxxxxxxxxxxxxx = offset + 83 + glovesOffset; + if (posxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Gloves"); + } + + int glovesLen = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxx); + if (glovesLen < 0) { + return ValidationResult.error("Invalid string length for Gloves"); + } + + if (glovesLen > 4096000) { + return ValidationResult.error("Gloves exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxx); + posxxxxxxxxxxxxxxxxxx += glovesLen; + if (posxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Gloves"); + } + } + + if ((nullBits[2] & 8) != 0) { + int capeOffset = buffer.getIntLE(offset + 79); + if (capeOffset < 0) { + return ValidationResult.error("Invalid offset for Cape"); + } + + int posxxxxxxxxxxxxxxxxxxx = offset + 83 + capeOffset; + if (posxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Cape"); + } + + int capeLen = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxxx); + if (capeLen < 0) { + return ValidationResult.error("Invalid string length for Cape"); + } + + if (capeLen > 4096000) { + return ValidationResult.error("Cape exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxxx); + posxxxxxxxxxxxxxxxxxxx += capeLen; + if (posxxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Cape"); + } + } + + return ValidationResult.OK; + } + } + + public PlayerSkin clone() { + PlayerSkin copy = new PlayerSkin(); + copy.bodyCharacteristic = this.bodyCharacteristic; + copy.underwear = this.underwear; + copy.face = this.face; + copy.eyes = this.eyes; + copy.ears = this.ears; + copy.mouth = this.mouth; + copy.facialHair = this.facialHair; + copy.haircut = this.haircut; + copy.eyebrows = this.eyebrows; + copy.pants = this.pants; + copy.overpants = this.overpants; + copy.undertop = this.undertop; + copy.overtop = this.overtop; + copy.shoes = this.shoes; + copy.headAccessory = this.headAccessory; + copy.faceAccessory = this.faceAccessory; + copy.earAccessory = this.earAccessory; + copy.skinFeature = this.skinFeature; + copy.gloves = this.gloves; + copy.cape = this.cape; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof PlayerSkin other) + ? false + : Objects.equals(this.bodyCharacteristic, other.bodyCharacteristic) + && Objects.equals(this.underwear, other.underwear) + && Objects.equals(this.face, other.face) + && Objects.equals(this.eyes, other.eyes) + && Objects.equals(this.ears, other.ears) + && Objects.equals(this.mouth, other.mouth) + && Objects.equals(this.facialHair, other.facialHair) + && Objects.equals(this.haircut, other.haircut) + && Objects.equals(this.eyebrows, other.eyebrows) + && Objects.equals(this.pants, other.pants) + && Objects.equals(this.overpants, other.overpants) + && Objects.equals(this.undertop, other.undertop) + && Objects.equals(this.overtop, other.overtop) + && Objects.equals(this.shoes, other.shoes) + && Objects.equals(this.headAccessory, other.headAccessory) + && Objects.equals(this.faceAccessory, other.faceAccessory) + && Objects.equals(this.earAccessory, other.earAccessory) + && Objects.equals(this.skinFeature, other.skinFeature) + && Objects.equals(this.gloves, other.gloves) + && Objects.equals(this.cape, other.cape); + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.bodyCharacteristic, + this.underwear, + this.face, + this.eyes, + this.ears, + this.mouth, + this.facialHair, + this.haircut, + this.eyebrows, + this.pants, + this.overpants, + this.undertop, + this.overtop, + this.shoes, + this.headAccessory, + this.faceAccessory, + this.earAccessory, + this.skinFeature, + this.gloves, + this.cape + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/Position.java b/src/com/hypixel/hytale/protocol/Position.java new file mode 100644 index 0000000..c5e9c3e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Position.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class Position { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 24; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 24; + public static final int MAX_SIZE = 24; + public double x; + public double y; + public double z; + + public Position() { + } + + public Position(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public Position(@Nonnull Position other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + } + + @Nonnull + public static Position deserialize(@Nonnull ByteBuf buf, int offset) { + Position obj = new Position(); + obj.x = buf.getDoubleLE(offset + 0); + obj.y = buf.getDoubleLE(offset + 8); + obj.z = buf.getDoubleLE(offset + 16); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 24; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeDoubleLE(this.x); + buf.writeDoubleLE(this.y); + buf.writeDoubleLE(this.z); + } + + public int computeSize() { + return 24; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 24 ? ValidationResult.error("Buffer too small: expected at least 24 bytes") : ValidationResult.OK; + } + + public Position clone() { + Position copy = new Position(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Position other) ? false : this.x == other.x && this.y == other.y && this.z == other.z; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y, this.z); + } +} diff --git a/src/com/hypixel/hytale/protocol/PositionDistanceOffsetType.java b/src/com/hypixel/hytale/protocol/PositionDistanceOffsetType.java new file mode 100644 index 0000000..8f4dcf3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/PositionDistanceOffsetType.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum PositionDistanceOffsetType { + DistanceOffset(0), + DistanceOffsetRaycast(1), + None(2); + + public static final PositionDistanceOffsetType[] VALUES = values(); + private final int value; + + private PositionDistanceOffsetType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static PositionDistanceOffsetType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("PositionDistanceOffsetType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/PositionType.java b/src/com/hypixel/hytale/protocol/PositionType.java new file mode 100644 index 0000000..8a7c6fd --- /dev/null +++ b/src/com/hypixel/hytale/protocol/PositionType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum PositionType { + AttachedToPlusOffset(0), + Custom(1); + + public static final PositionType[] VALUES = values(); + private final int value; + + private PositionType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static PositionType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("PositionType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/PrioritySlot.java b/src/com/hypixel/hytale/protocol/PrioritySlot.java new file mode 100644 index 0000000..86b1cb0 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/PrioritySlot.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum PrioritySlot { + Default(0), + MainHand(1), + OffHand(2); + + public static final PrioritySlot[] VALUES = values(); + private final int value; + + private PrioritySlot(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static PrioritySlot fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("PrioritySlot", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ProjectileConfig.java b/src/com/hypixel/hytale/protocol/ProjectileConfig.java new file mode 100644 index 0000000..61c1362 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ProjectileConfig.java @@ -0,0 +1,339 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ProjectileConfig { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 163; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 171; + public static final int MAX_SIZE = 1677721600; + @Nullable + public PhysicsConfig physicsConfig; + @Nullable + public Model model; + public double launchForce; + @Nullable + public Vector3f spawnOffset; + @Nullable + public Direction rotationOffset; + @Nullable + public Map interactions; + public int launchLocalSoundEventIndex; + public int projectileSoundEventIndex; + + public ProjectileConfig() { + } + + public ProjectileConfig( + @Nullable PhysicsConfig physicsConfig, + @Nullable Model model, + double launchForce, + @Nullable Vector3f spawnOffset, + @Nullable Direction rotationOffset, + @Nullable Map interactions, + int launchLocalSoundEventIndex, + int projectileSoundEventIndex + ) { + this.physicsConfig = physicsConfig; + this.model = model; + this.launchForce = launchForce; + this.spawnOffset = spawnOffset; + this.rotationOffset = rotationOffset; + this.interactions = interactions; + this.launchLocalSoundEventIndex = launchLocalSoundEventIndex; + this.projectileSoundEventIndex = projectileSoundEventIndex; + } + + public ProjectileConfig(@Nonnull ProjectileConfig other) { + this.physicsConfig = other.physicsConfig; + this.model = other.model; + this.launchForce = other.launchForce; + this.spawnOffset = other.spawnOffset; + this.rotationOffset = other.rotationOffset; + this.interactions = other.interactions; + this.launchLocalSoundEventIndex = other.launchLocalSoundEventIndex; + this.projectileSoundEventIndex = other.projectileSoundEventIndex; + } + + @Nonnull + public static ProjectileConfig deserialize(@Nonnull ByteBuf buf, int offset) { + ProjectileConfig obj = new ProjectileConfig(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.physicsConfig = PhysicsConfig.deserialize(buf, offset + 1); + } + + obj.launchForce = buf.getDoubleLE(offset + 123); + if ((nullBits & 4) != 0) { + obj.spawnOffset = Vector3f.deserialize(buf, offset + 131); + } + + if ((nullBits & 8) != 0) { + obj.rotationOffset = Direction.deserialize(buf, offset + 143); + } + + obj.launchLocalSoundEventIndex = buf.getIntLE(offset + 155); + obj.projectileSoundEventIndex = buf.getIntLE(offset + 159); + if ((nullBits & 2) != 0) { + int varPos0 = offset + 171 + buf.getIntLE(offset + 163); + obj.model = Model.deserialize(buf, varPos0); + } + + if ((nullBits & 16) != 0) { + int varPos1 = offset + 171 + buf.getIntLE(offset + 167); + int interactionsCount = VarInt.peek(buf, varPos1); + if (interactionsCount < 0) { + throw ProtocolException.negativeLength("Interactions", interactionsCount); + } + + if (interactionsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Interactions", interactionsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.interactions = new HashMap<>(interactionsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < interactionsCount; i++) { + InteractionType key = InteractionType.fromValue(buf.getByte(dictPos)); + int val = buf.getIntLE(++dictPos); + dictPos += 4; + if (obj.interactions.put(key, val) != null) { + throw ProtocolException.duplicateKey("interactions", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 171; + if ((nullBits & 2) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 163); + int pos0 = offset + 171 + fieldOffset0; + pos0 += Model.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 167); + int pos1 = offset + 171 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + 4; + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.physicsConfig != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.model != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.spawnOffset != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.rotationOffset != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.interactions != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + if (this.physicsConfig != null) { + this.physicsConfig.serialize(buf); + } else { + buf.writeZero(122); + } + + buf.writeDoubleLE(this.launchForce); + if (this.spawnOffset != null) { + this.spawnOffset.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.rotationOffset != null) { + this.rotationOffset.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeIntLE(this.launchLocalSoundEventIndex); + buf.writeIntLE(this.projectileSoundEventIndex); + int modelOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int interactionsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.model != null) { + buf.setIntLE(modelOffsetSlot, buf.writerIndex() - varBlockStart); + this.model.serialize(buf); + } else { + buf.setIntLE(modelOffsetSlot, -1); + } + + if (this.interactions != null) { + buf.setIntLE(interactionsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.interactions.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Interactions", this.interactions.size(), 4096000); + } + + VarInt.write(buf, this.interactions.size()); + + for (Entry e : this.interactions.entrySet()) { + buf.writeByte(e.getKey().getValue()); + buf.writeIntLE(e.getValue()); + } + } else { + buf.setIntLE(interactionsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 171; + if (this.model != null) { + size += this.model.computeSize(); + } + + if (this.interactions != null) { + size += VarInt.size(this.interactions.size()) + this.interactions.size() * 5; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 171) { + return ValidationResult.error("Buffer too small: expected at least 171 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 2) != 0) { + int modelOffset = buffer.getIntLE(offset + 163); + if (modelOffset < 0) { + return ValidationResult.error("Invalid offset for Model"); + } + + int pos = offset + 171 + modelOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Model"); + } + + ValidationResult modelResult = Model.validateStructure(buffer, pos); + if (!modelResult.isValid()) { + return ValidationResult.error("Invalid Model: " + modelResult.error()); + } + + pos += Model.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 16) != 0) { + int interactionsOffset = buffer.getIntLE(offset + 167); + if (interactionsOffset < 0) { + return ValidationResult.error("Invalid offset for Interactions"); + } + + int posx = offset + 171 + interactionsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Interactions"); + } + + int interactionsCount = VarInt.peek(buffer, posx); + if (interactionsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Interactions"); + } + + if (interactionsCount > 4096000) { + return ValidationResult.error("Interactions exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < interactionsCount; i++) { + posx = ++posx + 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public ProjectileConfig clone() { + ProjectileConfig copy = new ProjectileConfig(); + copy.physicsConfig = this.physicsConfig != null ? this.physicsConfig.clone() : null; + copy.model = this.model != null ? this.model.clone() : null; + copy.launchForce = this.launchForce; + copy.spawnOffset = this.spawnOffset != null ? this.spawnOffset.clone() : null; + copy.rotationOffset = this.rotationOffset != null ? this.rotationOffset.clone() : null; + copy.interactions = this.interactions != null ? new HashMap<>(this.interactions) : null; + copy.launchLocalSoundEventIndex = this.launchLocalSoundEventIndex; + copy.projectileSoundEventIndex = this.projectileSoundEventIndex; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ProjectileConfig other) + ? false + : Objects.equals(this.physicsConfig, other.physicsConfig) + && Objects.equals(this.model, other.model) + && this.launchForce == other.launchForce + && Objects.equals(this.spawnOffset, other.spawnOffset) + && Objects.equals(this.rotationOffset, other.rotationOffset) + && Objects.equals(this.interactions, other.interactions) + && this.launchLocalSoundEventIndex == other.launchLocalSoundEventIndex + && this.projectileSoundEventIndex == other.projectileSoundEventIndex; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.physicsConfig, + this.model, + this.launchForce, + this.spawnOffset, + this.rotationOffset, + this.interactions, + this.launchLocalSoundEventIndex, + this.projectileSoundEventIndex + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/ProjectileInteraction.java b/src/com/hypixel/hytale/protocol/ProjectileInteraction.java new file mode 100644 index 0000000..5accf55 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ProjectileInteraction.java @@ -0,0 +1,581 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ProjectileInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 19; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 43; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String configId; + + public ProjectileInteraction() { + } + + public ProjectileInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + @Nullable String configId + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.configId = configId; + } + + public ProjectileInteraction(@Nonnull ProjectileInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.configId = other.configId; + } + + @Nonnull + public static ProjectileInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + ProjectileInteraction obj = new ProjectileInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 43 + buf.getIntLE(offset + 19); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 43 + buf.getIntLE(offset + 23); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 43 + buf.getIntLE(offset + 27); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 43 + buf.getIntLE(offset + 31); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 43 + buf.getIntLE(offset + 35); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 43 + buf.getIntLE(offset + 39); + int configIdLen = VarInt.peek(buf, varPos5); + if (configIdLen < 0) { + throw ProtocolException.negativeLength("ConfigId", configIdLen); + } + + if (configIdLen > 4096000) { + throw ProtocolException.stringTooLong("ConfigId", configIdLen, 4096000); + } + + obj.configId = PacketIO.readVarString(buf, varPos5, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 43; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 19); + int pos0 = offset + 43 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 23); + int pos1 = offset + 43 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 27); + int pos2 = offset + 43 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 31); + int pos3 = offset + 43 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 35); + int pos4 = offset + 43 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 39); + int pos5 = offset + 43 + fieldOffset5; + int sl = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + sl; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.configId != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int configIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.configId != null) { + buf.setIntLE(configIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.configId, 4096000); + } else { + buf.setIntLE(configIdOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 43; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.configId != null) { + size += PacketIO.stringSize(this.configId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 43) { + return ValidationResult.error("Buffer too small: expected at least 43 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 19); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 43 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 23); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 43 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 27); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 43 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 31); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 43 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 35); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 43 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int configIdOffset = buffer.getIntLE(offset + 39); + if (configIdOffset < 0) { + return ValidationResult.error("Invalid offset for ConfigId"); + } + + int posxxxxx = offset + 43 + configIdOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ConfigId"); + } + + int configIdLen = VarInt.peek(buffer, posxxxxx); + if (configIdLen < 0) { + return ValidationResult.error("Invalid string length for ConfigId"); + } + + if (configIdLen > 4096000) { + return ValidationResult.error("ConfigId exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += configIdLen; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ConfigId"); + } + } + + return ValidationResult.OK; + } + } + + public ProjectileInteraction clone() { + ProjectileInteraction copy = new ProjectileInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.configId = this.configId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ProjectileInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && Objects.equals(this.configId, other.configId); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + return 31 * result + Objects.hashCode(this.configId); + } +} diff --git a/src/com/hypixel/hytale/protocol/ProtocolSettings.java b/src/com/hypixel/hytale/protocol/ProtocolSettings.java new file mode 100644 index 0000000..ff23d2c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ProtocolSettings.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.protocol; + +public final class ProtocolSettings { + public static final String PROTOCOL_HASH = "6708f121966c1c443f4b0eb525b2f81d0a8dc61f5003a692a8fa157e5e02cea9"; + public static final int PROTOCOL_VERSION = 1; + public static final int PACKET_COUNT = 268; + public static final int STRUCT_COUNT = 315; + public static final int ENUM_COUNT = 136; + public static final int MAX_PACKET_SIZE = 1677721600; + + private ProtocolSettings() { + } + + public static boolean validateHash(String hash) { + return "6708f121966c1c443f4b0eb525b2f81d0a8dc61f5003a692a8fa157e5e02cea9".equals(hash); + } +} diff --git a/src/com/hypixel/hytale/protocol/RailConfig.java b/src/com/hypixel/hytale/protocol/RailConfig.java new file mode 100644 index 0000000..bcdabb2 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RailConfig.java @@ -0,0 +1,154 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RailConfig { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 102400006; + @Nullable + public RailPoint[] points; + + public RailConfig() { + } + + public RailConfig(@Nullable RailPoint[] points) { + this.points = points; + } + + public RailConfig(@Nonnull RailConfig other) { + this.points = other.points; + } + + @Nonnull + public static RailConfig deserialize(@Nonnull ByteBuf buf, int offset) { + RailConfig obj = new RailConfig(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int pointsCount = VarInt.peek(buf, pos); + if (pointsCount < 0) { + throw ProtocolException.negativeLength("Points", pointsCount); + } + + if (pointsCount > 4096000) { + throw ProtocolException.arrayTooLong("Points", pointsCount, 4096000); + } + + int pointsVarLen = VarInt.size(pointsCount); + if (pos + pointsVarLen + pointsCount * 25L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Points", pos + pointsVarLen + pointsCount * 25, buf.readableBytes()); + } + + pos += pointsVarLen; + obj.points = new RailPoint[pointsCount]; + + for (int i = 0; i < pointsCount; i++) { + obj.points[i] = RailPoint.deserialize(buf, pos); + pos += RailPoint.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += RailPoint.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.points != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.points != null) { + if (this.points.length > 4096000) { + throw ProtocolException.arrayTooLong("Points", this.points.length, 4096000); + } + + VarInt.write(buf, this.points.length); + + for (RailPoint item : this.points) { + item.serialize(buf); + } + } + } + + public int computeSize() { + int size = 1; + if (this.points != null) { + size += VarInt.size(this.points.length) + this.points.length * 25; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int pointsCount = VarInt.peek(buffer, pos); + if (pointsCount < 0) { + return ValidationResult.error("Invalid array count for Points"); + } + + if (pointsCount > 4096000) { + return ValidationResult.error("Points exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += pointsCount * 25; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Points"); + } + } + + return ValidationResult.OK; + } + } + + public RailConfig clone() { + RailConfig copy = new RailConfig(); + copy.points = this.points != null ? Arrays.stream(this.points).map(e -> e.clone()).toArray(RailPoint[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof RailConfig other ? Arrays.equals((Object[])this.points, (Object[])other.points) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.points); + } +} diff --git a/src/com/hypixel/hytale/protocol/RailPoint.java b/src/com/hypixel/hytale/protocol/RailPoint.java new file mode 100644 index 0000000..a0a356a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RailPoint.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RailPoint { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 25; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 25; + public static final int MAX_SIZE = 25; + @Nullable + public Vector3f point; + @Nullable + public Vector3f normal; + + public RailPoint() { + } + + public RailPoint(@Nullable Vector3f point, @Nullable Vector3f normal) { + this.point = point; + this.normal = normal; + } + + public RailPoint(@Nonnull RailPoint other) { + this.point = other.point; + this.normal = other.normal; + } + + @Nonnull + public static RailPoint deserialize(@Nonnull ByteBuf buf, int offset) { + RailPoint obj = new RailPoint(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.point = Vector3f.deserialize(buf, offset + 1); + } + + if ((nullBits & 2) != 0) { + obj.normal = Vector3f.deserialize(buf, offset + 13); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 25; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.point != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.normal != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + if (this.point != null) { + this.point.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.normal != null) { + this.normal.serialize(buf); + } else { + buf.writeZero(12); + } + } + + public int computeSize() { + return 25; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 25 ? ValidationResult.error("Buffer too small: expected at least 25 bytes") : ValidationResult.OK; + } + + public RailPoint clone() { + RailPoint copy = new RailPoint(); + copy.point = this.point != null ? this.point.clone() : null; + copy.normal = this.normal != null ? this.normal.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof RailPoint other) ? false : Objects.equals(this.point, other.point) && Objects.equals(this.normal, other.normal); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.point, this.normal); + } +} diff --git a/src/com/hypixel/hytale/protocol/RandomRotation.java b/src/com/hypixel/hytale/protocol/RandomRotation.java new file mode 100644 index 0000000..ba4ef72 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RandomRotation.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum RandomRotation { + None(0), + YawPitchRollStep1(1), + YawStep1(2), + YawStep1XZ(3), + YawStep90(4); + + public static final RandomRotation[] VALUES = values(); + private final int value; + + private RandomRotation(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static RandomRotation fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("RandomRotation", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/Range.java b/src/com/hypixel/hytale/protocol/Range.java new file mode 100644 index 0000000..8b58bab --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Range.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class Range { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public int min; + public int max; + + public Range() { + } + + public Range(int min, int max) { + this.min = min; + this.max = max; + } + + public Range(@Nonnull Range other) { + this.min = other.min; + this.max = other.max; + } + + @Nonnull + public static Range deserialize(@Nonnull ByteBuf buf, int offset) { + Range obj = new Range(); + obj.min = buf.getIntLE(offset + 0); + obj.max = buf.getIntLE(offset + 4); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.min); + buf.writeIntLE(this.max); + } + + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public Range clone() { + Range copy = new Range(); + copy.min = this.min; + copy.max = this.max; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Range other) ? false : this.min == other.min && this.max == other.max; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.min, this.max); + } +} diff --git a/src/com/hypixel/hytale/protocol/RangeVector2f.java b/src/com/hypixel/hytale/protocol/RangeVector2f.java new file mode 100644 index 0000000..55d8264 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RangeVector2f.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RangeVector2f { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 17; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 17; + public static final int MAX_SIZE = 17; + @Nullable + public Rangef x; + @Nullable + public Rangef y; + + public RangeVector2f() { + } + + public RangeVector2f(@Nullable Rangef x, @Nullable Rangef y) { + this.x = x; + this.y = y; + } + + public RangeVector2f(@Nonnull RangeVector2f other) { + this.x = other.x; + this.y = other.y; + } + + @Nonnull + public static RangeVector2f deserialize(@Nonnull ByteBuf buf, int offset) { + RangeVector2f obj = new RangeVector2f(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.x = Rangef.deserialize(buf, offset + 1); + } + + if ((nullBits & 2) != 0) { + obj.y = Rangef.deserialize(buf, offset + 9); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 17; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.x != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.y != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + if (this.x != null) { + this.x.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.y != null) { + this.y.serialize(buf); + } else { + buf.writeZero(8); + } + } + + public int computeSize() { + return 17; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 17 ? ValidationResult.error("Buffer too small: expected at least 17 bytes") : ValidationResult.OK; + } + + public RangeVector2f clone() { + RangeVector2f copy = new RangeVector2f(); + copy.x = this.x != null ? this.x.clone() : null; + copy.y = this.y != null ? this.y.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof RangeVector2f other) ? false : Objects.equals(this.x, other.x) && Objects.equals(this.y, other.y); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y); + } +} diff --git a/src/com/hypixel/hytale/protocol/RangeVector3f.java b/src/com/hypixel/hytale/protocol/RangeVector3f.java new file mode 100644 index 0000000..b378611 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RangeVector3f.java @@ -0,0 +1,125 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RangeVector3f { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 25; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 25; + public static final int MAX_SIZE = 25; + @Nullable + public Rangef x; + @Nullable + public Rangef y; + @Nullable + public Rangef z; + + public RangeVector3f() { + } + + public RangeVector3f(@Nullable Rangef x, @Nullable Rangef y, @Nullable Rangef z) { + this.x = x; + this.y = y; + this.z = z; + } + + public RangeVector3f(@Nonnull RangeVector3f other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + } + + @Nonnull + public static RangeVector3f deserialize(@Nonnull ByteBuf buf, int offset) { + RangeVector3f obj = new RangeVector3f(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.x = Rangef.deserialize(buf, offset + 1); + } + + if ((nullBits & 2) != 0) { + obj.y = Rangef.deserialize(buf, offset + 9); + } + + if ((nullBits & 4) != 0) { + obj.z = Rangef.deserialize(buf, offset + 17); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 25; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.x != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.y != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.z != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + if (this.x != null) { + this.x.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.y != null) { + this.y.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.z != null) { + this.z.serialize(buf); + } else { + buf.writeZero(8); + } + } + + public int computeSize() { + return 25; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 25 ? ValidationResult.error("Buffer too small: expected at least 25 bytes") : ValidationResult.OK; + } + + public RangeVector3f clone() { + RangeVector3f copy = new RangeVector3f(); + copy.x = this.x != null ? this.x.clone() : null; + copy.y = this.y != null ? this.y.clone() : null; + copy.z = this.z != null ? this.z.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof RangeVector3f other) + ? false + : Objects.equals(this.x, other.x) && Objects.equals(this.y, other.y) && Objects.equals(this.z, other.z); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y, this.z); + } +} diff --git a/src/com/hypixel/hytale/protocol/Rangeb.java b/src/com/hypixel/hytale/protocol/Rangeb.java new file mode 100644 index 0000000..e80f7c5 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Rangeb.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class Rangeb { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 2; + public byte min; + public byte max; + + public Rangeb() { + } + + public Rangeb(byte min, byte max) { + this.min = min; + this.max = max; + } + + public Rangeb(@Nonnull Rangeb other) { + this.min = other.min; + this.max = other.max; + } + + @Nonnull + public static Rangeb deserialize(@Nonnull ByteBuf buf, int offset) { + Rangeb obj = new Rangeb(); + obj.min = buf.getByte(offset + 0); + obj.max = buf.getByte(offset + 1); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 2; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.min); + buf.writeByte(this.max); + } + + public int computeSize() { + return 2; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 2 ? ValidationResult.error("Buffer too small: expected at least 2 bytes") : ValidationResult.OK; + } + + public Rangeb clone() { + Rangeb copy = new Rangeb(); + copy.min = this.min; + copy.max = this.max; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Rangeb other) ? false : this.min == other.min && this.max == other.max; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.min, this.max); + } +} diff --git a/src/com/hypixel/hytale/protocol/Rangef.java b/src/com/hypixel/hytale/protocol/Rangef.java new file mode 100644 index 0000000..5fb0819 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Rangef.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class Rangef { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public float min; + public float max; + + public Rangef() { + } + + public Rangef(float min, float max) { + this.min = min; + this.max = max; + } + + public Rangef(@Nonnull Rangef other) { + this.min = other.min; + this.max = other.max; + } + + @Nonnull + public static Rangef deserialize(@Nonnull ByteBuf buf, int offset) { + Rangef obj = new Rangef(); + obj.min = buf.getFloatLE(offset + 0); + obj.max = buf.getFloatLE(offset + 4); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.min); + buf.writeFloatLE(this.max); + } + + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public Rangef clone() { + Rangef copy = new Rangef(); + copy.min = this.min; + copy.max = this.max; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Rangef other) ? false : this.min == other.min && this.max == other.max; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.min, this.max); + } +} diff --git a/src/com/hypixel/hytale/protocol/RaycastMode.java b/src/com/hypixel/hytale/protocol/RaycastMode.java new file mode 100644 index 0000000..3270785 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RaycastMode.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum RaycastMode { + FollowMotion(0), + FollowLook(1); + + public static final RaycastMode[] VALUES = values(); + private final int value; + + private RaycastMode(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static RaycastMode fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("RaycastMode", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/RaycastSelector.java b/src/com/hypixel/hytale/protocol/RaycastSelector.java new file mode 100644 index 0000000..86941cb --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RaycastSelector.java @@ -0,0 +1,120 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RaycastSelector extends Selector { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 23; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 23; + public static final int MAX_SIZE = 23; + @Nullable + public Vector3f offset; + public int distance; + public int blockTagIndex = Integer.MIN_VALUE; + public boolean ignoreFluids; + public boolean ignoreEmptyCollisionMaterial; + + public RaycastSelector() { + } + + public RaycastSelector(@Nullable Vector3f offset, int distance, int blockTagIndex, boolean ignoreFluids, boolean ignoreEmptyCollisionMaterial) { + this.offset = offset; + this.distance = distance; + this.blockTagIndex = blockTagIndex; + this.ignoreFluids = ignoreFluids; + this.ignoreEmptyCollisionMaterial = ignoreEmptyCollisionMaterial; + } + + public RaycastSelector(@Nonnull RaycastSelector other) { + this.offset = other.offset; + this.distance = other.distance; + this.blockTagIndex = other.blockTagIndex; + this.ignoreFluids = other.ignoreFluids; + this.ignoreEmptyCollisionMaterial = other.ignoreEmptyCollisionMaterial; + } + + @Nonnull + public static RaycastSelector deserialize(@Nonnull ByteBuf buf, int offset) { + RaycastSelector obj = new RaycastSelector(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.offset = Vector3f.deserialize(buf, offset + 1); + } + + obj.distance = buf.getIntLE(offset + 13); + obj.blockTagIndex = buf.getIntLE(offset + 17); + obj.ignoreFluids = buf.getByte(offset + 21) != 0; + obj.ignoreEmptyCollisionMaterial = buf.getByte(offset + 22) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 23; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.offset != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.offset != null) { + this.offset.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeIntLE(this.distance); + buf.writeIntLE(this.blockTagIndex); + buf.writeByte(this.ignoreFluids ? 1 : 0); + buf.writeByte(this.ignoreEmptyCollisionMaterial ? 1 : 0); + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + return 23; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 23 ? ValidationResult.error("Buffer too small: expected at least 23 bytes") : ValidationResult.OK; + } + + public RaycastSelector clone() { + RaycastSelector copy = new RaycastSelector(); + copy.offset = this.offset != null ? this.offset.clone() : null; + copy.distance = this.distance; + copy.blockTagIndex = this.blockTagIndex; + copy.ignoreFluids = this.ignoreFluids; + copy.ignoreEmptyCollisionMaterial = this.ignoreEmptyCollisionMaterial; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof RaycastSelector other) + ? false + : Objects.equals(this.offset, other.offset) + && this.distance == other.distance + && this.blockTagIndex == other.blockTagIndex + && this.ignoreFluids == other.ignoreFluids + && this.ignoreEmptyCollisionMaterial == other.ignoreEmptyCollisionMaterial; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.offset, this.distance, this.blockTagIndex, this.ignoreFluids, this.ignoreEmptyCollisionMaterial); + } +} diff --git a/src/com/hypixel/hytale/protocol/RefillContainerInteraction.java b/src/com/hypixel/hytale/protocol/RefillContainerInteraction.java new file mode 100644 index 0000000..af3211d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RefillContainerInteraction.java @@ -0,0 +1,605 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RefillContainerInteraction extends SimpleBlockInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 20; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 44; + public static final int MAX_SIZE = 1677721600; + @Nullable + public int[] refillFluids; + + public RefillContainerInteraction() { + } + + public RefillContainerInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + boolean useLatestTarget, + @Nullable int[] refillFluids + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.useLatestTarget = useLatestTarget; + this.refillFluids = refillFluids; + } + + public RefillContainerInteraction(@Nonnull RefillContainerInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.useLatestTarget = other.useLatestTarget; + this.refillFluids = other.refillFluids; + } + + @Nonnull + public static RefillContainerInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + RefillContainerInteraction obj = new RefillContainerInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.useLatestTarget = buf.getByte(offset + 19) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 44 + buf.getIntLE(offset + 20); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 44 + buf.getIntLE(offset + 24); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 44 + buf.getIntLE(offset + 28); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 44 + buf.getIntLE(offset + 32); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 44 + buf.getIntLE(offset + 36); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 44 + buf.getIntLE(offset + 40); + int refillFluidsCount = VarInt.peek(buf, varPos5); + if (refillFluidsCount < 0) { + throw ProtocolException.negativeLength("RefillFluids", refillFluidsCount); + } + + if (refillFluidsCount > 4096000) { + throw ProtocolException.arrayTooLong("RefillFluids", refillFluidsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + if (varPos5 + varIntLen + refillFluidsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("RefillFluids", varPos5 + varIntLen + refillFluidsCount * 4, buf.readableBytes()); + } + + obj.refillFluids = new int[refillFluidsCount]; + + for (int ix = 0; ix < refillFluidsCount; ix++) { + obj.refillFluids[ix] = buf.getIntLE(varPos5 + varIntLen + ix * 4); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 44; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 20); + int pos0 = offset + 44 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 24); + int pos1 = offset + 44 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 28); + int pos2 = offset + 44 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 32); + int pos3 = offset + 44 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 36); + int pos4 = offset + 44 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 40); + int pos5 = offset + 44 + fieldOffset5; + int arrLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + arrLen * 4; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.refillFluids != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeByte(this.useLatestTarget ? 1 : 0); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int refillFluidsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.refillFluids != null) { + buf.setIntLE(refillFluidsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.refillFluids.length > 4096000) { + throw ProtocolException.arrayTooLong("RefillFluids", this.refillFluids.length, 4096000); + } + + VarInt.write(buf, this.refillFluids.length); + + for (int item : this.refillFluids) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(refillFluidsOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 44; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.refillFluids != null) { + size += VarInt.size(this.refillFluids.length) + this.refillFluids.length * 4; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 44) { + return ValidationResult.error("Buffer too small: expected at least 44 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 20); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 44 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 24); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 44 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 28); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 44 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 32); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 44 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 36); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 44 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int refillFluidsOffset = buffer.getIntLE(offset + 40); + if (refillFluidsOffset < 0) { + return ValidationResult.error("Invalid offset for RefillFluids"); + } + + int posxxxxx = offset + 44 + refillFluidsOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for RefillFluids"); + } + + int refillFluidsCount = VarInt.peek(buffer, posxxxxx); + if (refillFluidsCount < 0) { + return ValidationResult.error("Invalid array count for RefillFluids"); + } + + if (refillFluidsCount > 4096000) { + return ValidationResult.error("RefillFluids exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += refillFluidsCount * 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading RefillFluids"); + } + } + + return ValidationResult.OK; + } + } + + public RefillContainerInteraction clone() { + RefillContainerInteraction copy = new RefillContainerInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.useLatestTarget = this.useLatestTarget; + copy.refillFluids = this.refillFluids != null ? Arrays.copyOf(this.refillFluids, this.refillFluids.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof RefillContainerInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && this.useLatestTarget == other.useLatestTarget + && Arrays.equals(this.refillFluids, other.refillFluids); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Boolean.hashCode(this.useLatestTarget); + return 31 * result + Arrays.hashCode(this.refillFluids); + } +} diff --git a/src/com/hypixel/hytale/protocol/RemoveEntityInteraction.java b/src/com/hypixel/hytale/protocol/RemoveEntityInteraction.java new file mode 100644 index 0000000..45aa3b9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RemoveEntityInteraction.java @@ -0,0 +1,514 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RemoveEntityInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 20; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 40; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public InteractionTarget entityTarget = InteractionTarget.User; + + public RemoveEntityInteraction() { + } + + public RemoveEntityInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + @Nonnull InteractionTarget entityTarget + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.entityTarget = entityTarget; + } + + public RemoveEntityInteraction(@Nonnull RemoveEntityInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.entityTarget = other.entityTarget; + } + + @Nonnull + public static RemoveEntityInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + RemoveEntityInteraction obj = new RemoveEntityInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.entityTarget = InteractionTarget.fromValue(buf.getByte(offset + 19)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 40 + buf.getIntLE(offset + 20); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 40 + buf.getIntLE(offset + 24); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 40 + buf.getIntLE(offset + 28); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 40 + buf.getIntLE(offset + 32); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 40 + buf.getIntLE(offset + 36); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 40; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 20); + int pos0 = offset + 40 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 24); + int pos1 = offset + 40 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 28); + int pos2 = offset + 40 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 32); + int pos3 = offset + 40 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 36); + int pos4 = offset + 40 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeByte(this.entityTarget.getValue()); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 40; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 40) { + return ValidationResult.error("Buffer too small: expected at least 40 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 20); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 40 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 24); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 40 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 28); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 40 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 32); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 40 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 36); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 40 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public RemoveEntityInteraction clone() { + RemoveEntityInteraction copy = new RemoveEntityInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.entityTarget = this.entityTarget; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof RemoveEntityInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && Objects.equals(this.entityTarget, other.entityTarget); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + return 31 * result + Objects.hashCode(this.entityTarget); + } +} diff --git a/src/com/hypixel/hytale/protocol/RepeatInteraction.java b/src/com/hypixel/hytale/protocol/RepeatInteraction.java new file mode 100644 index 0000000..5409012 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RepeatInteraction.java @@ -0,0 +1,522 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RepeatInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 27; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 47; + public static final int MAX_SIZE = 1677721600; + public int forkInteractions; + public int repeat; + + public RepeatInteraction() { + } + + public RepeatInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + int forkInteractions, + int repeat + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.forkInteractions = forkInteractions; + this.repeat = repeat; + } + + public RepeatInteraction(@Nonnull RepeatInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.forkInteractions = other.forkInteractions; + this.repeat = other.repeat; + } + + @Nonnull + public static RepeatInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + RepeatInteraction obj = new RepeatInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.forkInteractions = buf.getIntLE(offset + 19); + obj.repeat = buf.getIntLE(offset + 23); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 47 + buf.getIntLE(offset + 27); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 47 + buf.getIntLE(offset + 31); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 47 + buf.getIntLE(offset + 35); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 47 + buf.getIntLE(offset + 39); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 47 + buf.getIntLE(offset + 43); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 47; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 27); + int pos0 = offset + 47 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 31); + int pos1 = offset + 47 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 35); + int pos2 = offset + 47 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 39); + int pos3 = offset + 47 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 43); + int pos4 = offset + 47 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeIntLE(this.forkInteractions); + buf.writeIntLE(this.repeat); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 47; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 47) { + return ValidationResult.error("Buffer too small: expected at least 47 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 27); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 47 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 31); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 47 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 35); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 47 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 39); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 47 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 43); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 47 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public RepeatInteraction clone() { + RepeatInteraction copy = new RepeatInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.forkInteractions = this.forkInteractions; + copy.repeat = this.repeat; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof RepeatInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && this.forkInteractions == other.forkInteractions + && this.repeat == other.repeat; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Integer.hashCode(this.forkInteractions); + return 31 * result + Integer.hashCode(this.repeat); + } +} diff --git a/src/com/hypixel/hytale/protocol/ReplaceInteraction.java b/src/com/hypixel/hytale/protocol/ReplaceInteraction.java new file mode 100644 index 0000000..9919d65 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ReplaceInteraction.java @@ -0,0 +1,574 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ReplaceInteraction extends Interaction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 15; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 39; + public static final int MAX_SIZE = 1677721600; + public int defaultValue; + @Nullable + public String variable; + + public ReplaceInteraction() { + } + + public ReplaceInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int defaultValue, + @Nullable String variable + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.defaultValue = defaultValue; + this.variable = variable; + } + + public ReplaceInteraction(@Nonnull ReplaceInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.defaultValue = other.defaultValue; + this.variable = other.variable; + } + + @Nonnull + public static ReplaceInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + ReplaceInteraction obj = new ReplaceInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.defaultValue = buf.getIntLE(offset + 11); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 39 + buf.getIntLE(offset + 15); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 39 + buf.getIntLE(offset + 19); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 39 + buf.getIntLE(offset + 23); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 39 + buf.getIntLE(offset + 27); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 39 + buf.getIntLE(offset + 31); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 39 + buf.getIntLE(offset + 35); + int variableLen = VarInt.peek(buf, varPos5); + if (variableLen < 0) { + throw ProtocolException.negativeLength("Variable", variableLen); + } + + if (variableLen > 4096000) { + throw ProtocolException.stringTooLong("Variable", variableLen, 4096000); + } + + obj.variable = PacketIO.readVarString(buf, varPos5, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 39; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 15); + int pos0 = offset + 39 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 19); + int pos1 = offset + 39 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 23); + int pos2 = offset + 39 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 27); + int pos3 = offset + 39 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 31); + int pos4 = offset + 39 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 35); + int pos5 = offset + 39 + fieldOffset5; + int sl = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + sl; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.variable != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.defaultValue); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int variableOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.variable != null) { + buf.setIntLE(variableOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.variable, 4096000); + } else { + buf.setIntLE(variableOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 39; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.variable != null) { + size += PacketIO.stringSize(this.variable); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 39) { + return ValidationResult.error("Buffer too small: expected at least 39 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 15); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 39 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 19); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 39 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 23); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 39 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 27); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 39 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 31); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 39 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int variableOffset = buffer.getIntLE(offset + 35); + if (variableOffset < 0) { + return ValidationResult.error("Invalid offset for Variable"); + } + + int posxxxxx = offset + 39 + variableOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Variable"); + } + + int variableLen = VarInt.peek(buffer, posxxxxx); + if (variableLen < 0) { + return ValidationResult.error("Invalid string length for Variable"); + } + + if (variableLen > 4096000) { + return ValidationResult.error("Variable exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += variableLen; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Variable"); + } + } + + return ValidationResult.OK; + } + } + + public ReplaceInteraction clone() { + ReplaceInteraction copy = new ReplaceInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.defaultValue = this.defaultValue; + copy.variable = this.variable; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ReplaceInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.defaultValue == other.defaultValue + && Objects.equals(this.variable, other.variable); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.defaultValue); + return 31 * result + Objects.hashCode(this.variable); + } +} diff --git a/src/com/hypixel/hytale/protocol/RepulsionConfig.java b/src/com/hypixel/hytale/protocol/RepulsionConfig.java new file mode 100644 index 0000000..aa51d34 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RepulsionConfig.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class RepulsionConfig { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 12; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 12; + public static final int MAX_SIZE = 12; + public float radius; + public float minForce; + public float maxForce; + + public RepulsionConfig() { + } + + public RepulsionConfig(float radius, float minForce, float maxForce) { + this.radius = radius; + this.minForce = minForce; + this.maxForce = maxForce; + } + + public RepulsionConfig(@Nonnull RepulsionConfig other) { + this.radius = other.radius; + this.minForce = other.minForce; + this.maxForce = other.maxForce; + } + + @Nonnull + public static RepulsionConfig deserialize(@Nonnull ByteBuf buf, int offset) { + RepulsionConfig obj = new RepulsionConfig(); + obj.radius = buf.getFloatLE(offset + 0); + obj.minForce = buf.getFloatLE(offset + 4); + obj.maxForce = buf.getFloatLE(offset + 8); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 12; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.radius); + buf.writeFloatLE(this.minForce); + buf.writeFloatLE(this.maxForce); + } + + public int computeSize() { + return 12; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 12 ? ValidationResult.error("Buffer too small: expected at least 12 bytes") : ValidationResult.OK; + } + + public RepulsionConfig clone() { + RepulsionConfig copy = new RepulsionConfig(); + copy.radius = this.radius; + copy.minForce = this.minForce; + copy.maxForce = this.maxForce; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof RepulsionConfig other) + ? false + : this.radius == other.radius && this.minForce == other.minForce && this.maxForce == other.maxForce; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.radius, this.minForce, this.maxForce); + } +} diff --git a/src/com/hypixel/hytale/protocol/RequiredBlockFaceSupport.java b/src/com/hypixel/hytale/protocol/RequiredBlockFaceSupport.java new file mode 100644 index 0000000..b4889cd --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RequiredBlockFaceSupport.java @@ -0,0 +1,475 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RequiredBlockFaceSupport { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 17; + public static final int VARIABLE_FIELD_COUNT = 4; + public static final int VARIABLE_BLOCK_START = 33; + public static final int MAX_SIZE = 98304053; + @Nullable + public String faceType; + @Nullable + public String selfFaceType; + @Nullable + public String blockSetId; + public int blockTypeId; + public int tagIndex; + public int fluidId; + @Nonnull + public SupportMatch support = SupportMatch.Ignored; + @Nonnull + public SupportMatch matchSelf = SupportMatch.Ignored; + public boolean allowSupportPropagation; + public boolean rotate; + @Nullable + public Vector3i[] filler; + + public RequiredBlockFaceSupport() { + } + + public RequiredBlockFaceSupport( + @Nullable String faceType, + @Nullable String selfFaceType, + @Nullable String blockSetId, + int blockTypeId, + int tagIndex, + int fluidId, + @Nonnull SupportMatch support, + @Nonnull SupportMatch matchSelf, + boolean allowSupportPropagation, + boolean rotate, + @Nullable Vector3i[] filler + ) { + this.faceType = faceType; + this.selfFaceType = selfFaceType; + this.blockSetId = blockSetId; + this.blockTypeId = blockTypeId; + this.tagIndex = tagIndex; + this.fluidId = fluidId; + this.support = support; + this.matchSelf = matchSelf; + this.allowSupportPropagation = allowSupportPropagation; + this.rotate = rotate; + this.filler = filler; + } + + public RequiredBlockFaceSupport(@Nonnull RequiredBlockFaceSupport other) { + this.faceType = other.faceType; + this.selfFaceType = other.selfFaceType; + this.blockSetId = other.blockSetId; + this.blockTypeId = other.blockTypeId; + this.tagIndex = other.tagIndex; + this.fluidId = other.fluidId; + this.support = other.support; + this.matchSelf = other.matchSelf; + this.allowSupportPropagation = other.allowSupportPropagation; + this.rotate = other.rotate; + this.filler = other.filler; + } + + @Nonnull + public static RequiredBlockFaceSupport deserialize(@Nonnull ByteBuf buf, int offset) { + RequiredBlockFaceSupport obj = new RequiredBlockFaceSupport(); + byte nullBits = buf.getByte(offset); + obj.blockTypeId = buf.getIntLE(offset + 1); + obj.tagIndex = buf.getIntLE(offset + 5); + obj.fluidId = buf.getIntLE(offset + 9); + obj.support = SupportMatch.fromValue(buf.getByte(offset + 13)); + obj.matchSelf = SupportMatch.fromValue(buf.getByte(offset + 14)); + obj.allowSupportPropagation = buf.getByte(offset + 15) != 0; + obj.rotate = buf.getByte(offset + 16) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 33 + buf.getIntLE(offset + 17); + int faceTypeLen = VarInt.peek(buf, varPos0); + if (faceTypeLen < 0) { + throw ProtocolException.negativeLength("FaceType", faceTypeLen); + } + + if (faceTypeLen > 4096000) { + throw ProtocolException.stringTooLong("FaceType", faceTypeLen, 4096000); + } + + obj.faceType = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 33 + buf.getIntLE(offset + 21); + int selfFaceTypeLen = VarInt.peek(buf, varPos1); + if (selfFaceTypeLen < 0) { + throw ProtocolException.negativeLength("SelfFaceType", selfFaceTypeLen); + } + + if (selfFaceTypeLen > 4096000) { + throw ProtocolException.stringTooLong("SelfFaceType", selfFaceTypeLen, 4096000); + } + + obj.selfFaceType = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 33 + buf.getIntLE(offset + 25); + int blockSetIdLen = VarInt.peek(buf, varPos2); + if (blockSetIdLen < 0) { + throw ProtocolException.negativeLength("BlockSetId", blockSetIdLen); + } + + if (blockSetIdLen > 4096000) { + throw ProtocolException.stringTooLong("BlockSetId", blockSetIdLen, 4096000); + } + + obj.blockSetId = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 33 + buf.getIntLE(offset + 29); + int fillerCount = VarInt.peek(buf, varPos3); + if (fillerCount < 0) { + throw ProtocolException.negativeLength("Filler", fillerCount); + } + + if (fillerCount > 4096000) { + throw ProtocolException.arrayTooLong("Filler", fillerCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + fillerCount * 12L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Filler", varPos3 + varIntLen + fillerCount * 12, buf.readableBytes()); + } + + obj.filler = new Vector3i[fillerCount]; + int elemPos = varPos3 + varIntLen; + + for (int i = 0; i < fillerCount; i++) { + obj.filler[i] = Vector3i.deserialize(buf, elemPos); + elemPos += Vector3i.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 33; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 17); + int pos0 = offset + 33 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 21); + int pos1 = offset + 33 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 25); + int pos2 = offset + 33 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 29); + int pos3 = offset + 33 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3); + + for (int i = 0; i < arrLen; i++) { + pos3 += Vector3i.computeBytesConsumed(buf, pos3); + } + + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.faceType != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.selfFaceType != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.blockSetId != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.filler != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.blockTypeId); + buf.writeIntLE(this.tagIndex); + buf.writeIntLE(this.fluidId); + buf.writeByte(this.support.getValue()); + buf.writeByte(this.matchSelf.getValue()); + buf.writeByte(this.allowSupportPropagation ? 1 : 0); + buf.writeByte(this.rotate ? 1 : 0); + int faceTypeOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int selfFaceTypeOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int blockSetIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int fillerOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.faceType != null) { + buf.setIntLE(faceTypeOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.faceType, 4096000); + } else { + buf.setIntLE(faceTypeOffsetSlot, -1); + } + + if (this.selfFaceType != null) { + buf.setIntLE(selfFaceTypeOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.selfFaceType, 4096000); + } else { + buf.setIntLE(selfFaceTypeOffsetSlot, -1); + } + + if (this.blockSetId != null) { + buf.setIntLE(blockSetIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.blockSetId, 4096000); + } else { + buf.setIntLE(blockSetIdOffsetSlot, -1); + } + + if (this.filler != null) { + buf.setIntLE(fillerOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.filler.length > 4096000) { + throw ProtocolException.arrayTooLong("Filler", this.filler.length, 4096000); + } + + VarInt.write(buf, this.filler.length); + + for (Vector3i item : this.filler) { + item.serialize(buf); + } + } else { + buf.setIntLE(fillerOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 33; + if (this.faceType != null) { + size += PacketIO.stringSize(this.faceType); + } + + if (this.selfFaceType != null) { + size += PacketIO.stringSize(this.selfFaceType); + } + + if (this.blockSetId != null) { + size += PacketIO.stringSize(this.blockSetId); + } + + if (this.filler != null) { + size += VarInt.size(this.filler.length) + this.filler.length * 12; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 33) { + return ValidationResult.error("Buffer too small: expected at least 33 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int faceTypeOffset = buffer.getIntLE(offset + 17); + if (faceTypeOffset < 0) { + return ValidationResult.error("Invalid offset for FaceType"); + } + + int pos = offset + 33 + faceTypeOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FaceType"); + } + + int faceTypeLen = VarInt.peek(buffer, pos); + if (faceTypeLen < 0) { + return ValidationResult.error("Invalid string length for FaceType"); + } + + if (faceTypeLen > 4096000) { + return ValidationResult.error("FaceType exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += faceTypeLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading FaceType"); + } + } + + if ((nullBits & 2) != 0) { + int selfFaceTypeOffset = buffer.getIntLE(offset + 21); + if (selfFaceTypeOffset < 0) { + return ValidationResult.error("Invalid offset for SelfFaceType"); + } + + int posx = offset + 33 + selfFaceTypeOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SelfFaceType"); + } + + int selfFaceTypeLen = VarInt.peek(buffer, posx); + if (selfFaceTypeLen < 0) { + return ValidationResult.error("Invalid string length for SelfFaceType"); + } + + if (selfFaceTypeLen > 4096000) { + return ValidationResult.error("SelfFaceType exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += selfFaceTypeLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading SelfFaceType"); + } + } + + if ((nullBits & 4) != 0) { + int blockSetIdOffset = buffer.getIntLE(offset + 25); + if (blockSetIdOffset < 0) { + return ValidationResult.error("Invalid offset for BlockSetId"); + } + + int posxx = offset + 33 + blockSetIdOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BlockSetId"); + } + + int blockSetIdLen = VarInt.peek(buffer, posxx); + if (blockSetIdLen < 0) { + return ValidationResult.error("Invalid string length for BlockSetId"); + } + + if (blockSetIdLen > 4096000) { + return ValidationResult.error("BlockSetId exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += blockSetIdLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading BlockSetId"); + } + } + + if ((nullBits & 8) != 0) { + int fillerOffset = buffer.getIntLE(offset + 29); + if (fillerOffset < 0) { + return ValidationResult.error("Invalid offset for Filler"); + } + + int posxxx = offset + 33 + fillerOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Filler"); + } + + int fillerCount = VarInt.peek(buffer, posxxx); + if (fillerCount < 0) { + return ValidationResult.error("Invalid array count for Filler"); + } + + if (fillerCount > 4096000) { + return ValidationResult.error("Filler exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += fillerCount * 12; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Filler"); + } + } + + return ValidationResult.OK; + } + } + + public RequiredBlockFaceSupport clone() { + RequiredBlockFaceSupport copy = new RequiredBlockFaceSupport(); + copy.faceType = this.faceType; + copy.selfFaceType = this.selfFaceType; + copy.blockSetId = this.blockSetId; + copy.blockTypeId = this.blockTypeId; + copy.tagIndex = this.tagIndex; + copy.fluidId = this.fluidId; + copy.support = this.support; + copy.matchSelf = this.matchSelf; + copy.allowSupportPropagation = this.allowSupportPropagation; + copy.rotate = this.rotate; + copy.filler = this.filler != null ? Arrays.stream(this.filler).map(e -> e.clone()).toArray(Vector3i[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof RequiredBlockFaceSupport other) + ? false + : Objects.equals(this.faceType, other.faceType) + && Objects.equals(this.selfFaceType, other.selfFaceType) + && Objects.equals(this.blockSetId, other.blockSetId) + && this.blockTypeId == other.blockTypeId + && this.tagIndex == other.tagIndex + && this.fluidId == other.fluidId + && Objects.equals(this.support, other.support) + && Objects.equals(this.matchSelf, other.matchSelf) + && this.allowSupportPropagation == other.allowSupportPropagation + && this.rotate == other.rotate + && Arrays.equals((Object[])this.filler, (Object[])other.filler); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.faceType); + result = 31 * result + Objects.hashCode(this.selfFaceType); + result = 31 * result + Objects.hashCode(this.blockSetId); + result = 31 * result + Integer.hashCode(this.blockTypeId); + result = 31 * result + Integer.hashCode(this.tagIndex); + result = 31 * result + Integer.hashCode(this.fluidId); + result = 31 * result + Objects.hashCode(this.support); + result = 31 * result + Objects.hashCode(this.matchSelf); + result = 31 * result + Boolean.hashCode(this.allowSupportPropagation); + result = 31 * result + Boolean.hashCode(this.rotate); + return 31 * result + Arrays.hashCode((Object[])this.filler); + } +} diff --git a/src/com/hypixel/hytale/protocol/ResetCooldownInteraction.java b/src/com/hypixel/hytale/protocol/ResetCooldownInteraction.java new file mode 100644 index 0000000..4c72795 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ResetCooldownInteraction.java @@ -0,0 +1,562 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ResetCooldownInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 19; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 43; + public static final int MAX_SIZE = 1677721600; + @Nullable + public InteractionCooldown cooldown; + + public ResetCooldownInteraction() { + } + + public ResetCooldownInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + @Nullable InteractionCooldown cooldown + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.cooldown = cooldown; + } + + public ResetCooldownInteraction(@Nonnull ResetCooldownInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.cooldown = other.cooldown; + } + + @Nonnull + public static ResetCooldownInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + ResetCooldownInteraction obj = new ResetCooldownInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 43 + buf.getIntLE(offset + 19); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 43 + buf.getIntLE(offset + 23); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 43 + buf.getIntLE(offset + 27); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 43 + buf.getIntLE(offset + 31); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 43 + buf.getIntLE(offset + 35); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 43 + buf.getIntLE(offset + 39); + obj.cooldown = InteractionCooldown.deserialize(buf, varPos5); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 43; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 19); + int pos0 = offset + 43 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 23); + int pos1 = offset + 43 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 27); + int pos2 = offset + 43 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 31); + int pos3 = offset + 43 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 35); + int pos4 = offset + 43 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 39); + int pos5 = offset + 43 + fieldOffset5; + pos5 += InteractionCooldown.computeBytesConsumed(buf, pos5); + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.cooldown != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cooldownOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.cooldown != null) { + buf.setIntLE(cooldownOffsetSlot, buf.writerIndex() - varBlockStart); + this.cooldown.serialize(buf); + } else { + buf.setIntLE(cooldownOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 43; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.cooldown != null) { + size += this.cooldown.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 43) { + return ValidationResult.error("Buffer too small: expected at least 43 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 19); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 43 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 23); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 43 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 27); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 43 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 31); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 43 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 35); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 43 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int cooldownOffset = buffer.getIntLE(offset + 39); + if (cooldownOffset < 0) { + return ValidationResult.error("Invalid offset for Cooldown"); + } + + int posxxxxx = offset + 43 + cooldownOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Cooldown"); + } + + ValidationResult cooldownResult = InteractionCooldown.validateStructure(buffer, posxxxxx); + if (!cooldownResult.isValid()) { + return ValidationResult.error("Invalid Cooldown: " + cooldownResult.error()); + } + + posxxxxx += InteractionCooldown.computeBytesConsumed(buffer, posxxxxx); + } + + return ValidationResult.OK; + } + } + + public ResetCooldownInteraction clone() { + ResetCooldownInteraction copy = new ResetCooldownInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.cooldown = this.cooldown != null ? this.cooldown.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ResetCooldownInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && Objects.equals(this.cooldown, other.cooldown); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + return 31 * result + Objects.hashCode(this.cooldown); + } +} diff --git a/src/com/hypixel/hytale/protocol/ResourceType.java b/src/com/hypixel/hytale/protocol/ResourceType.java new file mode 100644 index 0000000..efa0135 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ResourceType.java @@ -0,0 +1,225 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ResourceType { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 32768019; + @Nullable + public String id; + @Nullable + public String icon; + + public ResourceType() { + } + + public ResourceType(@Nullable String id, @Nullable String icon) { + this.id = id; + this.icon = icon; + } + + public ResourceType(@Nonnull ResourceType other) { + this.id = other.id; + this.icon = other.icon; + } + + @Nonnull + public static ResourceType deserialize(@Nonnull ByteBuf buf, int offset) { + ResourceType obj = new ResourceType(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int iconLen = VarInt.peek(buf, varPos1); + if (iconLen < 0) { + throw ProtocolException.negativeLength("Icon", iconLen); + } + + if (iconLen > 4096000) { + throw ProtocolException.stringTooLong("Icon", iconLen, 4096000); + } + + obj.icon = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.icon != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int iconOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.icon != null) { + buf.setIntLE(iconOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.icon, 4096000); + } else { + buf.setIntLE(iconOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 9; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.icon != null) { + size += PacketIO.stringSize(this.icon); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 1); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 9 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int iconOffset = buffer.getIntLE(offset + 5); + if (iconOffset < 0) { + return ValidationResult.error("Invalid offset for Icon"); + } + + int posx = offset + 9 + iconOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Icon"); + } + + int iconLen = VarInt.peek(buffer, posx); + if (iconLen < 0) { + return ValidationResult.error("Invalid string length for Icon"); + } + + if (iconLen > 4096000) { + return ValidationResult.error("Icon exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += iconLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Icon"); + } + } + + return ValidationResult.OK; + } + } + + public ResourceType clone() { + ResourceType copy = new ResourceType(); + copy.id = this.id; + copy.icon = this.icon; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ResourceType other) ? false : Objects.equals(this.id, other.id) && Objects.equals(this.icon, other.icon); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.icon); + } +} diff --git a/src/com/hypixel/hytale/protocol/ReverbEffect.java b/src/com/hypixel/hytale/protocol/ReverbEffect.java new file mode 100644 index 0000000..66b31a8 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ReverbEffect.java @@ -0,0 +1,265 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ReverbEffect { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 54; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 54; + public static final int MAX_SIZE = 16384059; + @Nullable + public String id; + public float dryGain; + public float modalDensity; + public float diffusion; + public float gain; + public float highFrequencyGain; + public float decayTime; + public float highFrequencyDecayRatio; + public float reflectionGain; + public float reflectionDelay; + public float lateReverbGain; + public float lateReverbDelay; + public float roomRolloffFactor; + public float airAbsorptionHighFrequencyGain; + public boolean limitDecayHighFrequency; + + public ReverbEffect() { + } + + public ReverbEffect( + @Nullable String id, + float dryGain, + float modalDensity, + float diffusion, + float gain, + float highFrequencyGain, + float decayTime, + float highFrequencyDecayRatio, + float reflectionGain, + float reflectionDelay, + float lateReverbGain, + float lateReverbDelay, + float roomRolloffFactor, + float airAbsorptionHighFrequencyGain, + boolean limitDecayHighFrequency + ) { + this.id = id; + this.dryGain = dryGain; + this.modalDensity = modalDensity; + this.diffusion = diffusion; + this.gain = gain; + this.highFrequencyGain = highFrequencyGain; + this.decayTime = decayTime; + this.highFrequencyDecayRatio = highFrequencyDecayRatio; + this.reflectionGain = reflectionGain; + this.reflectionDelay = reflectionDelay; + this.lateReverbGain = lateReverbGain; + this.lateReverbDelay = lateReverbDelay; + this.roomRolloffFactor = roomRolloffFactor; + this.airAbsorptionHighFrequencyGain = airAbsorptionHighFrequencyGain; + this.limitDecayHighFrequency = limitDecayHighFrequency; + } + + public ReverbEffect(@Nonnull ReverbEffect other) { + this.id = other.id; + this.dryGain = other.dryGain; + this.modalDensity = other.modalDensity; + this.diffusion = other.diffusion; + this.gain = other.gain; + this.highFrequencyGain = other.highFrequencyGain; + this.decayTime = other.decayTime; + this.highFrequencyDecayRatio = other.highFrequencyDecayRatio; + this.reflectionGain = other.reflectionGain; + this.reflectionDelay = other.reflectionDelay; + this.lateReverbGain = other.lateReverbGain; + this.lateReverbDelay = other.lateReverbDelay; + this.roomRolloffFactor = other.roomRolloffFactor; + this.airAbsorptionHighFrequencyGain = other.airAbsorptionHighFrequencyGain; + this.limitDecayHighFrequency = other.limitDecayHighFrequency; + } + + @Nonnull + public static ReverbEffect deserialize(@Nonnull ByteBuf buf, int offset) { + ReverbEffect obj = new ReverbEffect(); + byte nullBits = buf.getByte(offset); + obj.dryGain = buf.getFloatLE(offset + 1); + obj.modalDensity = buf.getFloatLE(offset + 5); + obj.diffusion = buf.getFloatLE(offset + 9); + obj.gain = buf.getFloatLE(offset + 13); + obj.highFrequencyGain = buf.getFloatLE(offset + 17); + obj.decayTime = buf.getFloatLE(offset + 21); + obj.highFrequencyDecayRatio = buf.getFloatLE(offset + 25); + obj.reflectionGain = buf.getFloatLE(offset + 29); + obj.reflectionDelay = buf.getFloatLE(offset + 33); + obj.lateReverbGain = buf.getFloatLE(offset + 37); + obj.lateReverbDelay = buf.getFloatLE(offset + 41); + obj.roomRolloffFactor = buf.getFloatLE(offset + 45); + obj.airAbsorptionHighFrequencyGain = buf.getFloatLE(offset + 49); + obj.limitDecayHighFrequency = buf.getByte(offset + 53) != 0; + int pos = offset + 54; + if ((nullBits & 1) != 0) { + int idLen = VarInt.peek(buf, pos); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + int idVarLen = VarInt.length(buf, pos); + obj.id = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += idVarLen + idLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 54; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.dryGain); + buf.writeFloatLE(this.modalDensity); + buf.writeFloatLE(this.diffusion); + buf.writeFloatLE(this.gain); + buf.writeFloatLE(this.highFrequencyGain); + buf.writeFloatLE(this.decayTime); + buf.writeFloatLE(this.highFrequencyDecayRatio); + buf.writeFloatLE(this.reflectionGain); + buf.writeFloatLE(this.reflectionDelay); + buf.writeFloatLE(this.lateReverbGain); + buf.writeFloatLE(this.lateReverbDelay); + buf.writeFloatLE(this.roomRolloffFactor); + buf.writeFloatLE(this.airAbsorptionHighFrequencyGain); + buf.writeByte(this.limitDecayHighFrequency ? 1 : 0); + if (this.id != null) { + PacketIO.writeVarString(buf, this.id, 4096000); + } + } + + public int computeSize() { + int size = 54; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 54) { + return ValidationResult.error("Buffer too small: expected at least 54 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 54; + if ((nullBits & 1) != 0) { + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + return ValidationResult.OK; + } + } + + public ReverbEffect clone() { + ReverbEffect copy = new ReverbEffect(); + copy.id = this.id; + copy.dryGain = this.dryGain; + copy.modalDensity = this.modalDensity; + copy.diffusion = this.diffusion; + copy.gain = this.gain; + copy.highFrequencyGain = this.highFrequencyGain; + copy.decayTime = this.decayTime; + copy.highFrequencyDecayRatio = this.highFrequencyDecayRatio; + copy.reflectionGain = this.reflectionGain; + copy.reflectionDelay = this.reflectionDelay; + copy.lateReverbGain = this.lateReverbGain; + copy.lateReverbDelay = this.lateReverbDelay; + copy.roomRolloffFactor = this.roomRolloffFactor; + copy.airAbsorptionHighFrequencyGain = this.airAbsorptionHighFrequencyGain; + copy.limitDecayHighFrequency = this.limitDecayHighFrequency; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ReverbEffect other) + ? false + : Objects.equals(this.id, other.id) + && this.dryGain == other.dryGain + && this.modalDensity == other.modalDensity + && this.diffusion == other.diffusion + && this.gain == other.gain + && this.highFrequencyGain == other.highFrequencyGain + && this.decayTime == other.decayTime + && this.highFrequencyDecayRatio == other.highFrequencyDecayRatio + && this.reflectionGain == other.reflectionGain + && this.reflectionDelay == other.reflectionDelay + && this.lateReverbGain == other.lateReverbGain + && this.lateReverbDelay == other.lateReverbDelay + && this.roomRolloffFactor == other.roomRolloffFactor + && this.airAbsorptionHighFrequencyGain == other.airAbsorptionHighFrequencyGain + && this.limitDecayHighFrequency == other.limitDecayHighFrequency; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.id, + this.dryGain, + this.modalDensity, + this.diffusion, + this.gain, + this.highFrequencyGain, + this.decayTime, + this.highFrequencyDecayRatio, + this.reflectionGain, + this.reflectionDelay, + this.lateReverbGain, + this.lateReverbDelay, + this.roomRolloffFactor, + this.airAbsorptionHighFrequencyGain, + this.limitDecayHighFrequency + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/RoofConnectedBlockRuleSet.java b/src/com/hypixel/hytale/protocol/RoofConnectedBlockRuleSet.java new file mode 100644 index 0000000..b00b5fe --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RoofConnectedBlockRuleSet.java @@ -0,0 +1,282 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RoofConnectedBlockRuleSet { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 21; + public static final int MAX_SIZE = 49152078; + @Nullable + public StairConnectedBlockRuleSet regular; + @Nullable + public StairConnectedBlockRuleSet hollow; + public int topperBlockId; + public int width; + @Nullable + public String materialName; + + public RoofConnectedBlockRuleSet() { + } + + public RoofConnectedBlockRuleSet( + @Nullable StairConnectedBlockRuleSet regular, @Nullable StairConnectedBlockRuleSet hollow, int topperBlockId, int width, @Nullable String materialName + ) { + this.regular = regular; + this.hollow = hollow; + this.topperBlockId = topperBlockId; + this.width = width; + this.materialName = materialName; + } + + public RoofConnectedBlockRuleSet(@Nonnull RoofConnectedBlockRuleSet other) { + this.regular = other.regular; + this.hollow = other.hollow; + this.topperBlockId = other.topperBlockId; + this.width = other.width; + this.materialName = other.materialName; + } + + @Nonnull + public static RoofConnectedBlockRuleSet deserialize(@Nonnull ByteBuf buf, int offset) { + RoofConnectedBlockRuleSet obj = new RoofConnectedBlockRuleSet(); + byte nullBits = buf.getByte(offset); + obj.topperBlockId = buf.getIntLE(offset + 1); + obj.width = buf.getIntLE(offset + 5); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 21 + buf.getIntLE(offset + 9); + obj.regular = StairConnectedBlockRuleSet.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 21 + buf.getIntLE(offset + 13); + obj.hollow = StairConnectedBlockRuleSet.deserialize(buf, varPos1); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 21 + buf.getIntLE(offset + 17); + int materialNameLen = VarInt.peek(buf, varPos2); + if (materialNameLen < 0) { + throw ProtocolException.negativeLength("MaterialName", materialNameLen); + } + + if (materialNameLen > 4096000) { + throw ProtocolException.stringTooLong("MaterialName", materialNameLen, 4096000); + } + + obj.materialName = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 21; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 9); + int pos0 = offset + 21 + fieldOffset0; + pos0 += StairConnectedBlockRuleSet.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 13); + int pos1 = offset + 21 + fieldOffset1; + pos1 += StairConnectedBlockRuleSet.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 17); + int pos2 = offset + 21 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.regular != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.hollow != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.materialName != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.topperBlockId); + buf.writeIntLE(this.width); + int regularOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int hollowOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int materialNameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.regular != null) { + buf.setIntLE(regularOffsetSlot, buf.writerIndex() - varBlockStart); + this.regular.serialize(buf); + } else { + buf.setIntLE(regularOffsetSlot, -1); + } + + if (this.hollow != null) { + buf.setIntLE(hollowOffsetSlot, buf.writerIndex() - varBlockStart); + this.hollow.serialize(buf); + } else { + buf.setIntLE(hollowOffsetSlot, -1); + } + + if (this.materialName != null) { + buf.setIntLE(materialNameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.materialName, 4096000); + } else { + buf.setIntLE(materialNameOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 21; + if (this.regular != null) { + size += this.regular.computeSize(); + } + + if (this.hollow != null) { + size += this.hollow.computeSize(); + } + + if (this.materialName != null) { + size += PacketIO.stringSize(this.materialName); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 21) { + return ValidationResult.error("Buffer too small: expected at least 21 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int regularOffset = buffer.getIntLE(offset + 9); + if (regularOffset < 0) { + return ValidationResult.error("Invalid offset for Regular"); + } + + int pos = offset + 21 + regularOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Regular"); + } + + ValidationResult regularResult = StairConnectedBlockRuleSet.validateStructure(buffer, pos); + if (!regularResult.isValid()) { + return ValidationResult.error("Invalid Regular: " + regularResult.error()); + } + + pos += StairConnectedBlockRuleSet.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int hollowOffset = buffer.getIntLE(offset + 13); + if (hollowOffset < 0) { + return ValidationResult.error("Invalid offset for Hollow"); + } + + int posx = offset + 21 + hollowOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Hollow"); + } + + ValidationResult hollowResult = StairConnectedBlockRuleSet.validateStructure(buffer, posx); + if (!hollowResult.isValid()) { + return ValidationResult.error("Invalid Hollow: " + hollowResult.error()); + } + + posx += StairConnectedBlockRuleSet.computeBytesConsumed(buffer, posx); + } + + if ((nullBits & 4) != 0) { + int materialNameOffset = buffer.getIntLE(offset + 17); + if (materialNameOffset < 0) { + return ValidationResult.error("Invalid offset for MaterialName"); + } + + int posxx = offset + 21 + materialNameOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MaterialName"); + } + + int materialNameLen = VarInt.peek(buffer, posxx); + if (materialNameLen < 0) { + return ValidationResult.error("Invalid string length for MaterialName"); + } + + if (materialNameLen > 4096000) { + return ValidationResult.error("MaterialName exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += materialNameLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading MaterialName"); + } + } + + return ValidationResult.OK; + } + } + + public RoofConnectedBlockRuleSet clone() { + RoofConnectedBlockRuleSet copy = new RoofConnectedBlockRuleSet(); + copy.regular = this.regular != null ? this.regular.clone() : null; + copy.hollow = this.hollow != null ? this.hollow.clone() : null; + copy.topperBlockId = this.topperBlockId; + copy.width = this.width; + copy.materialName = this.materialName; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof RoofConnectedBlockRuleSet other) + ? false + : Objects.equals(this.regular, other.regular) + && Objects.equals(this.hollow, other.hollow) + && this.topperBlockId == other.topperBlockId + && this.width == other.width + && Objects.equals(this.materialName, other.materialName); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.regular, this.hollow, this.topperBlockId, this.width, this.materialName); + } +} diff --git a/src/com/hypixel/hytale/protocol/RootInteraction.java b/src/com/hypixel/hytale/protocol/RootInteraction.java new file mode 100644 index 0000000..042bd17 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RootInteraction.java @@ -0,0 +1,597 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RootInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 30; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + @Nullable + public int[] interactions; + @Nullable + public InteractionCooldown cooldown; + @Nullable + public Map settings; + @Nullable + public InteractionRules rules; + @Nullable + public int[] tags; + public float clickQueuingTimeout; + public boolean requireNewClick; + + public RootInteraction() { + } + + public RootInteraction( + @Nullable String id, + @Nullable int[] interactions, + @Nullable InteractionCooldown cooldown, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + float clickQueuingTimeout, + boolean requireNewClick + ) { + this.id = id; + this.interactions = interactions; + this.cooldown = cooldown; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.clickQueuingTimeout = clickQueuingTimeout; + this.requireNewClick = requireNewClick; + } + + public RootInteraction(@Nonnull RootInteraction other) { + this.id = other.id; + this.interactions = other.interactions; + this.cooldown = other.cooldown; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.clickQueuingTimeout = other.clickQueuingTimeout; + this.requireNewClick = other.requireNewClick; + } + + @Nonnull + public static RootInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + RootInteraction obj = new RootInteraction(); + byte nullBits = buf.getByte(offset); + obj.clickQueuingTimeout = buf.getFloatLE(offset + 1); + obj.requireNewClick = buf.getByte(offset + 5) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 30 + buf.getIntLE(offset + 6); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 30 + buf.getIntLE(offset + 10); + int interactionsCount = VarInt.peek(buf, varPos1); + if (interactionsCount < 0) { + throw ProtocolException.negativeLength("Interactions", interactionsCount); + } + + if (interactionsCount > 4096000) { + throw ProtocolException.arrayTooLong("Interactions", interactionsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + interactionsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Interactions", varPos1 + varIntLen + interactionsCount * 4, buf.readableBytes()); + } + + obj.interactions = new int[interactionsCount]; + + for (int i = 0; i < interactionsCount; i++) { + obj.interactions[i] = buf.getIntLE(varPos1 + varIntLen + i * 4); + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 30 + buf.getIntLE(offset + 14); + obj.cooldown = InteractionCooldown.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 30 + buf.getIntLE(offset + 18); + int settingsCount = VarInt.peek(buf, varPos3); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos3 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + RootInteractionSettings val = RootInteractionSettings.deserialize(buf, ++dictPos); + dictPos += RootInteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 30 + buf.getIntLE(offset + 22); + obj.rules = InteractionRules.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 30 + buf.getIntLE(offset + 26); + int tagsCount = VarInt.peek(buf, varPos5); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + if (varPos5 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos5 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos5 + varIntLen + ix * 4); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 30; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 6); + int pos0 = offset + 30 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 10); + int pos1 = offset + 30 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + arrLen * 4; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 14); + int pos2 = offset + 30 + fieldOffset2; + pos2 += InteractionCooldown.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 18); + int pos3 = offset + 30 + fieldOffset3; + int dictLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3); + + for (int i = 0; i < dictLen; i++) { + pos3 = ++pos3 + RootInteractionSettings.computeBytesConsumed(buf, pos3); + } + + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 22); + int pos4 = offset + 30 + fieldOffset4; + pos4 += InteractionRules.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 26); + int pos5 = offset + 30 + fieldOffset5; + int arrLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + arrLen * 4; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.interactions != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.cooldown != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.clickQueuingTimeout); + buf.writeByte(this.requireNewClick ? 1 : 0); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int interactionsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cooldownOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.interactions != null) { + buf.setIntLE(interactionsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.interactions.length > 4096000) { + throw ProtocolException.arrayTooLong("Interactions", this.interactions.length, 4096000); + } + + VarInt.write(buf, this.interactions.length); + + for (int item : this.interactions) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(interactionsOffsetSlot, -1); + } + + if (this.cooldown != null) { + buf.setIntLE(cooldownOffsetSlot, buf.writerIndex() - varBlockStart); + this.cooldown.serialize(buf); + } else { + buf.setIntLE(cooldownOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 30; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.interactions != null) { + size += VarInt.size(this.interactions.length) + this.interactions.length * 4; + } + + if (this.cooldown != null) { + size += this.cooldown.computeSize(); + } + + if (this.settings != null) { + int settingsSize = 0; + + for (Entry kvp : this.settings.entrySet()) { + settingsSize += 1 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.settings.size()) + settingsSize; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 30) { + return ValidationResult.error("Buffer too small: expected at least 30 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 6); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 30 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int interactionsOffset = buffer.getIntLE(offset + 10); + if (interactionsOffset < 0) { + return ValidationResult.error("Invalid offset for Interactions"); + } + + int posx = offset + 30 + interactionsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Interactions"); + } + + int interactionsCount = VarInt.peek(buffer, posx); + if (interactionsCount < 0) { + return ValidationResult.error("Invalid array count for Interactions"); + } + + if (interactionsCount > 4096000) { + return ValidationResult.error("Interactions exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += interactionsCount * 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Interactions"); + } + } + + if ((nullBits & 4) != 0) { + int cooldownOffset = buffer.getIntLE(offset + 14); + if (cooldownOffset < 0) { + return ValidationResult.error("Invalid offset for Cooldown"); + } + + int posxx = offset + 30 + cooldownOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Cooldown"); + } + + ValidationResult cooldownResult = InteractionCooldown.validateStructure(buffer, posxx); + if (!cooldownResult.isValid()) { + return ValidationResult.error("Invalid Cooldown: " + cooldownResult.error()); + } + + posxx += InteractionCooldown.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int settingsOffset = buffer.getIntLE(offset + 18); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posxxx = offset + 30 + settingsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posxxx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + + for (int i = 0; i < settingsCount; i++) { + posxxx = ++posxxx + RootInteractionSettings.computeBytesConsumed(buffer, posxxx); + } + } + + if ((nullBits & 16) != 0) { + int rulesOffset = buffer.getIntLE(offset + 22); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxxxx = offset + 30 + rulesOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxxxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxxxx += InteractionRules.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int tagsOffset = buffer.getIntLE(offset + 26); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxxxx = offset + 30 + tagsOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += tagsCount * 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + return ValidationResult.OK; + } + } + + public RootInteraction clone() { + RootInteraction copy = new RootInteraction(); + copy.id = this.id; + copy.interactions = this.interactions != null ? Arrays.copyOf(this.interactions, this.interactions.length) : null; + copy.cooldown = this.cooldown != null ? this.cooldown.clone() : null; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.clickQueuingTimeout = this.clickQueuingTimeout; + copy.requireNewClick = this.requireNewClick; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof RootInteraction other) + ? false + : Objects.equals(this.id, other.id) + && Arrays.equals(this.interactions, other.interactions) + && Objects.equals(this.cooldown, other.cooldown) + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && this.clickQueuingTimeout == other.clickQueuingTimeout + && this.requireNewClick == other.requireNewClick; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.id); + result = 31 * result + Arrays.hashCode(this.interactions); + result = 31 * result + Objects.hashCode(this.cooldown); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Float.hashCode(this.clickQueuingTimeout); + return 31 * result + Boolean.hashCode(this.requireNewClick); + } +} diff --git a/src/com/hypixel/hytale/protocol/RootInteractionSettings.java b/src/com/hypixel/hytale/protocol/RootInteractionSettings.java new file mode 100644 index 0000000..fe78dfe --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RootInteractionSettings.java @@ -0,0 +1,119 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RootInteractionSettings { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 32768028; + public boolean allowSkipChainOnClick; + @Nullable + public InteractionCooldown cooldown; + + public RootInteractionSettings() { + } + + public RootInteractionSettings(boolean allowSkipChainOnClick, @Nullable InteractionCooldown cooldown) { + this.allowSkipChainOnClick = allowSkipChainOnClick; + this.cooldown = cooldown; + } + + public RootInteractionSettings(@Nonnull RootInteractionSettings other) { + this.allowSkipChainOnClick = other.allowSkipChainOnClick; + this.cooldown = other.cooldown; + } + + @Nonnull + public static RootInteractionSettings deserialize(@Nonnull ByteBuf buf, int offset) { + RootInteractionSettings obj = new RootInteractionSettings(); + byte nullBits = buf.getByte(offset); + obj.allowSkipChainOnClick = buf.getByte(offset + 1) != 0; + int pos = offset + 2; + if ((nullBits & 1) != 0) { + obj.cooldown = InteractionCooldown.deserialize(buf, pos); + pos += InteractionCooldown.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + pos += InteractionCooldown.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.cooldown != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.allowSkipChainOnClick ? 1 : 0); + if (this.cooldown != null) { + this.cooldown.serialize(buf); + } + } + + public int computeSize() { + int size = 2; + if (this.cooldown != null) { + size += this.cooldown.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + ValidationResult cooldownResult = InteractionCooldown.validateStructure(buffer, pos); + if (!cooldownResult.isValid()) { + return ValidationResult.error("Invalid Cooldown: " + cooldownResult.error()); + } + + pos += InteractionCooldown.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public RootInteractionSettings clone() { + RootInteractionSettings copy = new RootInteractionSettings(); + copy.allowSkipChainOnClick = this.allowSkipChainOnClick; + copy.cooldown = this.cooldown != null ? this.cooldown.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof RootInteractionSettings other) + ? false + : this.allowSkipChainOnClick == other.allowSkipChainOnClick && Objects.equals(this.cooldown, other.cooldown); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.allowSkipChainOnClick, this.cooldown); + } +} diff --git a/src/com/hypixel/hytale/protocol/Rotation.java b/src/com/hypixel/hytale/protocol/Rotation.java new file mode 100644 index 0000000..c7790a1 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Rotation.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum Rotation { + None(0), + Ninety(1), + OneEighty(2), + TwoSeventy(3); + + public static final Rotation[] VALUES = values(); + private final int value; + + private Rotation(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static Rotation fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("Rotation", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/RotationMode.java b/src/com/hypixel/hytale/protocol/RotationMode.java new file mode 100644 index 0000000..a4cfc2f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RotationMode.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum RotationMode { + None(0), + Velocity(1), + VelocityDamped(2), + VelocityRoll(3); + + public static final RotationMode[] VALUES = values(); + private final int value; + + private RotationMode(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static RotationMode fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("RotationMode", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/RotationNoise.java b/src/com/hypixel/hytale/protocol/RotationNoise.java new file mode 100644 index 0000000..f90b4cb --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RotationNoise.java @@ -0,0 +1,376 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RotationNoise { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 282624028; + @Nullable + public NoiseConfig[] pitch; + @Nullable + public NoiseConfig[] yaw; + @Nullable + public NoiseConfig[] roll; + + public RotationNoise() { + } + + public RotationNoise(@Nullable NoiseConfig[] pitch, @Nullable NoiseConfig[] yaw, @Nullable NoiseConfig[] roll) { + this.pitch = pitch; + this.yaw = yaw; + this.roll = roll; + } + + public RotationNoise(@Nonnull RotationNoise other) { + this.pitch = other.pitch; + this.yaw = other.yaw; + this.roll = other.roll; + } + + @Nonnull + public static RotationNoise deserialize(@Nonnull ByteBuf buf, int offset) { + RotationNoise obj = new RotationNoise(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 1); + int pitchCount = VarInt.peek(buf, varPos0); + if (pitchCount < 0) { + throw ProtocolException.negativeLength("Pitch", pitchCount); + } + + if (pitchCount > 4096000) { + throw ProtocolException.arrayTooLong("Pitch", pitchCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + pitchCount * 23L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Pitch", varPos0 + varIntLen + pitchCount * 23, buf.readableBytes()); + } + + obj.pitch = new NoiseConfig[pitchCount]; + int elemPos = varPos0 + varIntLen; + + for (int i = 0; i < pitchCount; i++) { + obj.pitch[i] = NoiseConfig.deserialize(buf, elemPos); + elemPos += NoiseConfig.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 5); + int yawCount = VarInt.peek(buf, varPos1); + if (yawCount < 0) { + throw ProtocolException.negativeLength("Yaw", yawCount); + } + + if (yawCount > 4096000) { + throw ProtocolException.arrayTooLong("Yaw", yawCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + yawCount * 23L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Yaw", varPos1 + varIntLen + yawCount * 23, buf.readableBytes()); + } + + obj.yaw = new NoiseConfig[yawCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < yawCount; i++) { + obj.yaw[i] = NoiseConfig.deserialize(buf, elemPos); + elemPos += NoiseConfig.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 13 + buf.getIntLE(offset + 9); + int rollCount = VarInt.peek(buf, varPos2); + if (rollCount < 0) { + throw ProtocolException.negativeLength("Roll", rollCount); + } + + if (rollCount > 4096000) { + throw ProtocolException.arrayTooLong("Roll", rollCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + rollCount * 23L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Roll", varPos2 + varIntLen + rollCount * 23, buf.readableBytes()); + } + + obj.roll = new NoiseConfig[rollCount]; + int elemPos = varPos2 + varIntLen; + + for (int i = 0; i < rollCount; i++) { + obj.roll[i] = NoiseConfig.deserialize(buf, elemPos); + elemPos += NoiseConfig.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 13 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < arrLen; i++) { + pos0 += NoiseConfig.computeBytesConsumed(buf, pos0); + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 13 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += NoiseConfig.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 9); + int pos2 = offset + 13 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2); + + for (int i = 0; i < arrLen; i++) { + pos2 += NoiseConfig.computeBytesConsumed(buf, pos2); + } + + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.pitch != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.yaw != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.roll != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + int pitchOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int yawOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rollOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.pitch != null) { + buf.setIntLE(pitchOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.pitch.length > 4096000) { + throw ProtocolException.arrayTooLong("Pitch", this.pitch.length, 4096000); + } + + VarInt.write(buf, this.pitch.length); + + for (NoiseConfig item : this.pitch) { + item.serialize(buf); + } + } else { + buf.setIntLE(pitchOffsetSlot, -1); + } + + if (this.yaw != null) { + buf.setIntLE(yawOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.yaw.length > 4096000) { + throw ProtocolException.arrayTooLong("Yaw", this.yaw.length, 4096000); + } + + VarInt.write(buf, this.yaw.length); + + for (NoiseConfig item : this.yaw) { + item.serialize(buf); + } + } else { + buf.setIntLE(yawOffsetSlot, -1); + } + + if (this.roll != null) { + buf.setIntLE(rollOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.roll.length > 4096000) { + throw ProtocolException.arrayTooLong("Roll", this.roll.length, 4096000); + } + + VarInt.write(buf, this.roll.length); + + for (NoiseConfig item : this.roll) { + item.serialize(buf); + } + } else { + buf.setIntLE(rollOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 13; + if (this.pitch != null) { + size += VarInt.size(this.pitch.length) + this.pitch.length * 23; + } + + if (this.yaw != null) { + size += VarInt.size(this.yaw.length) + this.yaw.length * 23; + } + + if (this.roll != null) { + size += VarInt.size(this.roll.length) + this.roll.length * 23; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int pitchOffset = buffer.getIntLE(offset + 1); + if (pitchOffset < 0) { + return ValidationResult.error("Invalid offset for Pitch"); + } + + int pos = offset + 13 + pitchOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Pitch"); + } + + int pitchCount = VarInt.peek(buffer, pos); + if (pitchCount < 0) { + return ValidationResult.error("Invalid array count for Pitch"); + } + + if (pitchCount > 4096000) { + return ValidationResult.error("Pitch exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += pitchCount * 23; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Pitch"); + } + } + + if ((nullBits & 2) != 0) { + int yawOffset = buffer.getIntLE(offset + 5); + if (yawOffset < 0) { + return ValidationResult.error("Invalid offset for Yaw"); + } + + int posx = offset + 13 + yawOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Yaw"); + } + + int yawCount = VarInt.peek(buffer, posx); + if (yawCount < 0) { + return ValidationResult.error("Invalid array count for Yaw"); + } + + if (yawCount > 4096000) { + return ValidationResult.error("Yaw exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += yawCount * 23; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Yaw"); + } + } + + if ((nullBits & 4) != 0) { + int rollOffset = buffer.getIntLE(offset + 9); + if (rollOffset < 0) { + return ValidationResult.error("Invalid offset for Roll"); + } + + int posxx = offset + 13 + rollOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Roll"); + } + + int rollCount = VarInt.peek(buffer, posxx); + if (rollCount < 0) { + return ValidationResult.error("Invalid array count for Roll"); + } + + if (rollCount > 4096000) { + return ValidationResult.error("Roll exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += rollCount * 23; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Roll"); + } + } + + return ValidationResult.OK; + } + } + + public RotationNoise clone() { + RotationNoise copy = new RotationNoise(); + copy.pitch = this.pitch != null ? Arrays.stream(this.pitch).map(e -> e.clone()).toArray(NoiseConfig[]::new) : null; + copy.yaw = this.yaw != null ? Arrays.stream(this.yaw).map(e -> e.clone()).toArray(NoiseConfig[]::new) : null; + copy.roll = this.roll != null ? Arrays.stream(this.roll).map(e -> e.clone()).toArray(NoiseConfig[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof RotationNoise other) + ? false + : Arrays.equals((Object[])this.pitch, (Object[])other.pitch) + && Arrays.equals((Object[])this.yaw, (Object[])other.yaw) + && Arrays.equals((Object[])this.roll, (Object[])other.roll); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode((Object[])this.pitch); + result = 31 * result + Arrays.hashCode((Object[])this.yaw); + return 31 * result + Arrays.hashCode((Object[])this.roll); + } +} diff --git a/src/com/hypixel/hytale/protocol/RotationType.java b/src/com/hypixel/hytale/protocol/RotationType.java new file mode 100644 index 0000000..e3219f1 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RotationType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum RotationType { + AttachedToPlusOffset(0), + Custom(1); + + public static final RotationType[] VALUES = values(); + private final int value; + + private RotationType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static RotationType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("RotationType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/RunRootInteraction.java b/src/com/hypixel/hytale/protocol/RunRootInteraction.java new file mode 100644 index 0000000..1ea4c84 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/RunRootInteraction.java @@ -0,0 +1,513 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RunRootInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 23; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 43; + public static final int MAX_SIZE = 1677721600; + public int rootInteraction; + + public RunRootInteraction() { + } + + public RunRootInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + int rootInteraction + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.rootInteraction = rootInteraction; + } + + public RunRootInteraction(@Nonnull RunRootInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.rootInteraction = other.rootInteraction; + } + + @Nonnull + public static RunRootInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + RunRootInteraction obj = new RunRootInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.rootInteraction = buf.getIntLE(offset + 19); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 43 + buf.getIntLE(offset + 23); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 43 + buf.getIntLE(offset + 27); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 43 + buf.getIntLE(offset + 31); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 43 + buf.getIntLE(offset + 35); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 43 + buf.getIntLE(offset + 39); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 43; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 23); + int pos0 = offset + 43 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 27); + int pos1 = offset + 43 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 31); + int pos2 = offset + 43 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 35); + int pos3 = offset + 43 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 39); + int pos4 = offset + 43 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeIntLE(this.rootInteraction); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 43; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 43) { + return ValidationResult.error("Buffer too small: expected at least 43 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 23); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 43 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 27); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 43 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 31); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 43 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 35); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 43 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 39); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 43 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public RunRootInteraction clone() { + RunRootInteraction copy = new RunRootInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.rootInteraction = this.rootInteraction; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof RunRootInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && this.rootInteraction == other.rootInteraction; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + return 31 * result + Integer.hashCode(this.rootInteraction); + } +} diff --git a/src/com/hypixel/hytale/protocol/SavedMovementStates.java b/src/com/hypixel/hytale/protocol/SavedMovementStates.java new file mode 100644 index 0000000..816daac --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SavedMovementStates.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SavedMovementStates { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean flying; + + public SavedMovementStates() { + } + + public SavedMovementStates(boolean flying) { + this.flying = flying; + } + + public SavedMovementStates(@Nonnull SavedMovementStates other) { + this.flying = other.flying; + } + + @Nonnull + public static SavedMovementStates deserialize(@Nonnull ByteBuf buf, int offset) { + SavedMovementStates obj = new SavedMovementStates(); + obj.flying = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.flying ? 1 : 0); + } + + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public SavedMovementStates clone() { + SavedMovementStates copy = new SavedMovementStates(); + copy.flying = this.flying; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof SavedMovementStates other ? this.flying == other.flying : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.flying); + } +} diff --git a/src/com/hypixel/hytale/protocol/SelectInteraction.java b/src/com/hypixel/hytale/protocol/SelectInteraction.java new file mode 100644 index 0000000..7eb4aaa --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SelectInteraction.java @@ -0,0 +1,701 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SelectInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 25; + public static final int VARIABLE_FIELD_COUNT = 7; + public static final int VARIABLE_BLOCK_START = 53; + public static final int MAX_SIZE = 1677721600; + @Nullable + public Selector selector; + public boolean ignoreOwner; + public int hitEntity; + @Nullable + public HitEntity[] hitEntityRules; + @Nonnull + public FailOnType failOn = FailOnType.Neither; + + public SelectInteraction() { + } + + public SelectInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + @Nullable Selector selector, + boolean ignoreOwner, + int hitEntity, + @Nullable HitEntity[] hitEntityRules, + @Nonnull FailOnType failOn + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.selector = selector; + this.ignoreOwner = ignoreOwner; + this.hitEntity = hitEntity; + this.hitEntityRules = hitEntityRules; + this.failOn = failOn; + } + + public SelectInteraction(@Nonnull SelectInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.selector = other.selector; + this.ignoreOwner = other.ignoreOwner; + this.hitEntity = other.hitEntity; + this.hitEntityRules = other.hitEntityRules; + this.failOn = other.failOn; + } + + @Nonnull + public static SelectInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + SelectInteraction obj = new SelectInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.ignoreOwner = buf.getByte(offset + 19) != 0; + obj.hitEntity = buf.getIntLE(offset + 20); + obj.failOn = FailOnType.fromValue(buf.getByte(offset + 24)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 53 + buf.getIntLE(offset + 25); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 53 + buf.getIntLE(offset + 29); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 53 + buf.getIntLE(offset + 33); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 53 + buf.getIntLE(offset + 37); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 53 + buf.getIntLE(offset + 41); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 53 + buf.getIntLE(offset + 45); + obj.selector = Selector.deserialize(buf, varPos5); + } + + if ((nullBits & 64) != 0) { + int varPos6 = offset + 53 + buf.getIntLE(offset + 49); + int hitEntityRulesCount = VarInt.peek(buf, varPos6); + if (hitEntityRulesCount < 0) { + throw ProtocolException.negativeLength("HitEntityRules", hitEntityRulesCount); + } + + if (hitEntityRulesCount > 4096000) { + throw ProtocolException.arrayTooLong("HitEntityRules", hitEntityRulesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos6); + if (varPos6 + varIntLen + hitEntityRulesCount * 5L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("HitEntityRules", varPos6 + varIntLen + hitEntityRulesCount * 5, buf.readableBytes()); + } + + obj.hitEntityRules = new HitEntity[hitEntityRulesCount]; + int elemPos = varPos6 + varIntLen; + + for (int ix = 0; ix < hitEntityRulesCount; ix++) { + obj.hitEntityRules[ix] = HitEntity.deserialize(buf, elemPos); + elemPos += HitEntity.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 53; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 25); + int pos0 = offset + 53 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 29); + int pos1 = offset + 53 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 33); + int pos2 = offset + 53 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 37); + int pos3 = offset + 53 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 41); + int pos4 = offset + 53 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 45); + int pos5 = offset + 53 + fieldOffset5; + pos5 += Selector.computeBytesConsumed(buf, pos5); + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits & 64) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 49); + int pos6 = offset + 53 + fieldOffset6; + int arrLen = VarInt.peek(buf, pos6); + pos6 += VarInt.length(buf, pos6); + + for (int i = 0; i < arrLen; i++) { + pos6 += HitEntity.computeBytesConsumed(buf, pos6); + } + + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.selector != null) { + nullBits = (byte)(nullBits | 32); + } + + if (this.hitEntityRules != null) { + nullBits = (byte)(nullBits | 64); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeByte(this.ignoreOwner ? 1 : 0); + buf.writeIntLE(this.hitEntity); + buf.writeByte(this.failOn.getValue()); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int selectorOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int hitEntityRulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.selector != null) { + buf.setIntLE(selectorOffsetSlot, buf.writerIndex() - varBlockStart); + this.selector.serializeWithTypeId(buf); + } else { + buf.setIntLE(selectorOffsetSlot, -1); + } + + if (this.hitEntityRules != null) { + buf.setIntLE(hitEntityRulesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.hitEntityRules.length > 4096000) { + throw ProtocolException.arrayTooLong("HitEntityRules", this.hitEntityRules.length, 4096000); + } + + VarInt.write(buf, this.hitEntityRules.length); + + for (HitEntity item : this.hitEntityRules) { + item.serialize(buf); + } + } else { + buf.setIntLE(hitEntityRulesOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 53; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.selector != null) { + size += this.selector.computeSizeWithTypeId(); + } + + if (this.hitEntityRules != null) { + int hitEntityRulesSize = 0; + + for (HitEntity elem : this.hitEntityRules) { + hitEntityRulesSize += elem.computeSize(); + } + + size += VarInt.size(this.hitEntityRules.length) + hitEntityRulesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 53) { + return ValidationResult.error("Buffer too small: expected at least 53 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 25); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 53 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 29); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 53 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 33); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 53 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 37); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 53 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 41); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 53 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int selectorOffset = buffer.getIntLE(offset + 45); + if (selectorOffset < 0) { + return ValidationResult.error("Invalid offset for Selector"); + } + + int posxxxxx = offset + 53 + selectorOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Selector"); + } + + ValidationResult selectorResult = Selector.validateStructure(buffer, posxxxxx); + if (!selectorResult.isValid()) { + return ValidationResult.error("Invalid Selector: " + selectorResult.error()); + } + + posxxxxx += Selector.computeBytesConsumed(buffer, posxxxxx); + } + + if ((nullBits & 64) != 0) { + int hitEntityRulesOffset = buffer.getIntLE(offset + 49); + if (hitEntityRulesOffset < 0) { + return ValidationResult.error("Invalid offset for HitEntityRules"); + } + + int posxxxxxx = offset + 53 + hitEntityRulesOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for HitEntityRules"); + } + + int hitEntityRulesCount = VarInt.peek(buffer, posxxxxxx); + if (hitEntityRulesCount < 0) { + return ValidationResult.error("Invalid array count for HitEntityRules"); + } + + if (hitEntityRulesCount > 4096000) { + return ValidationResult.error("HitEntityRules exceeds max length 4096000"); + } + + posxxxxxx += VarInt.length(buffer, posxxxxxx); + + for (int i = 0; i < hitEntityRulesCount; i++) { + ValidationResult structResult = HitEntity.validateStructure(buffer, posxxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid HitEntity in HitEntityRules[" + i + "]: " + structResult.error()); + } + + posxxxxxx += HitEntity.computeBytesConsumed(buffer, posxxxxxx); + } + } + + return ValidationResult.OK; + } + } + + public SelectInteraction clone() { + SelectInteraction copy = new SelectInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.selector = this.selector; + copy.ignoreOwner = this.ignoreOwner; + copy.hitEntity = this.hitEntity; + copy.hitEntityRules = this.hitEntityRules != null ? Arrays.stream(this.hitEntityRules).map(ex -> ex.clone()).toArray(HitEntity[]::new) : null; + copy.failOn = this.failOn; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SelectInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && Objects.equals(this.selector, other.selector) + && this.ignoreOwner == other.ignoreOwner + && this.hitEntity == other.hitEntity + && Arrays.equals((Object[])this.hitEntityRules, (Object[])other.hitEntityRules) + && Objects.equals(this.failOn, other.failOn); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Objects.hashCode(this.selector); + result = 31 * result + Boolean.hashCode(this.ignoreOwner); + result = 31 * result + Integer.hashCode(this.hitEntity); + result = 31 * result + Arrays.hashCode((Object[])this.hitEntityRules); + return 31 * result + Objects.hashCode(this.failOn); + } +} diff --git a/src/com/hypixel/hytale/protocol/SelectedHitEntity.java b/src/com/hypixel/hytale/protocol/SelectedHitEntity.java new file mode 100644 index 0000000..fefc6f8 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SelectedHitEntity.java @@ -0,0 +1,134 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SelectedHitEntity { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 53; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 53; + public static final int MAX_SIZE = 53; + public int networkId; + @Nullable + public Vector3f hitLocation; + @Nullable + public Position position; + @Nullable + public Direction bodyRotation; + + public SelectedHitEntity() { + } + + public SelectedHitEntity(int networkId, @Nullable Vector3f hitLocation, @Nullable Position position, @Nullable Direction bodyRotation) { + this.networkId = networkId; + this.hitLocation = hitLocation; + this.position = position; + this.bodyRotation = bodyRotation; + } + + public SelectedHitEntity(@Nonnull SelectedHitEntity other) { + this.networkId = other.networkId; + this.hitLocation = other.hitLocation; + this.position = other.position; + this.bodyRotation = other.bodyRotation; + } + + @Nonnull + public static SelectedHitEntity deserialize(@Nonnull ByteBuf buf, int offset) { + SelectedHitEntity obj = new SelectedHitEntity(); + byte nullBits = buf.getByte(offset); + obj.networkId = buf.getIntLE(offset + 1); + if ((nullBits & 1) != 0) { + obj.hitLocation = Vector3f.deserialize(buf, offset + 5); + } + + if ((nullBits & 2) != 0) { + obj.position = Position.deserialize(buf, offset + 17); + } + + if ((nullBits & 4) != 0) { + obj.bodyRotation = Direction.deserialize(buf, offset + 41); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 53; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.hitLocation != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.position != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.bodyRotation != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.networkId); + if (this.hitLocation != null) { + this.hitLocation.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.position != null) { + this.position.serialize(buf); + } else { + buf.writeZero(24); + } + + if (this.bodyRotation != null) { + this.bodyRotation.serialize(buf); + } else { + buf.writeZero(12); + } + } + + public int computeSize() { + return 53; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 53 ? ValidationResult.error("Buffer too small: expected at least 53 bytes") : ValidationResult.OK; + } + + public SelectedHitEntity clone() { + SelectedHitEntity copy = new SelectedHitEntity(); + copy.networkId = this.networkId; + copy.hitLocation = this.hitLocation != null ? this.hitLocation.clone() : null; + copy.position = this.position != null ? this.position.clone() : null; + copy.bodyRotation = this.bodyRotation != null ? this.bodyRotation.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SelectedHitEntity other) + ? false + : this.networkId == other.networkId + && Objects.equals(this.hitLocation, other.hitLocation) + && Objects.equals(this.position, other.position) + && Objects.equals(this.bodyRotation, other.bodyRotation); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.networkId, this.hitLocation, this.position, this.bodyRotation); + } +} diff --git a/src/com/hypixel/hytale/protocol/Selector.java b/src/com/hypixel/hytale/protocol/Selector.java new file mode 100644 index 0000000..a6b1075 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Selector.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public abstract class Selector { + public static final int MAX_SIZE = 42; + + public Selector() { + } + + @Nonnull + public static Selector deserialize(@Nonnull ByteBuf buf, int offset) { + int typeId = VarInt.peek(buf, offset); + int typeIdLen = VarInt.length(buf, offset); + + return (Selector)(switch (typeId) { + case 0 -> AOECircleSelector.deserialize(buf, offset + typeIdLen); + case 1 -> AOECylinderSelector.deserialize(buf, offset + typeIdLen); + case 2 -> RaycastSelector.deserialize(buf, offset + typeIdLen); + case 3 -> HorizontalSelector.deserialize(buf, offset + typeIdLen); + case 4 -> StabSelector.deserialize(buf, offset + typeIdLen); + default -> throw ProtocolException.unknownPolymorphicType("Selector", typeId); + }); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + int typeId = VarInt.peek(buf, offset); + int typeIdLen = VarInt.length(buf, offset); + + return typeIdLen + switch (typeId) { + case 0 -> AOECircleSelector.computeBytesConsumed(buf, offset + typeIdLen); + case 1 -> AOECylinderSelector.computeBytesConsumed(buf, offset + typeIdLen); + case 2 -> RaycastSelector.computeBytesConsumed(buf, offset + typeIdLen); + case 3 -> HorizontalSelector.computeBytesConsumed(buf, offset + typeIdLen); + case 4 -> StabSelector.computeBytesConsumed(buf, offset + typeIdLen); + default -> throw ProtocolException.unknownPolymorphicType("Selector", typeId); + }; + } + + public int getTypeId() { + if (this instanceof AOECircleSelector sub) { + return 0; + } else if (this instanceof AOECylinderSelector sub) { + return 1; + } else if (this instanceof RaycastSelector sub) { + return 2; + } else if (this instanceof HorizontalSelector sub) { + return 3; + } else if (this instanceof StabSelector sub) { + return 4; + } else { + throw new IllegalStateException("Unknown subtype: " + this.getClass().getName()); + } + } + + public abstract int serialize(@Nonnull ByteBuf var1); + + public abstract int computeSize(); + + public int serializeWithTypeId(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + VarInt.write(buf, this.getTypeId()); + this.serialize(buf); + return buf.writerIndex() - startPos; + } + + public int computeSizeWithTypeId() { + return VarInt.size(this.getTypeId()) + this.computeSize(); + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + int typeId = VarInt.peek(buffer, offset); + int typeIdLen = VarInt.length(buffer, offset); + + return switch (typeId) { + case 0 -> AOECircleSelector.validateStructure(buffer, offset + typeIdLen); + case 1 -> AOECylinderSelector.validateStructure(buffer, offset + typeIdLen); + case 2 -> RaycastSelector.validateStructure(buffer, offset + typeIdLen); + case 3 -> HorizontalSelector.validateStructure(buffer, offset + typeIdLen); + case 4 -> StabSelector.validateStructure(buffer, offset + typeIdLen); + default -> ValidationResult.error("Unknown polymorphic type ID " + typeId + " for Selector"); + }; + } +} diff --git a/src/com/hypixel/hytale/protocol/SerialInteraction.java b/src/com/hypixel/hytale/protocol/SerialInteraction.java new file mode 100644 index 0000000..775d5ee --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SerialInteraction.java @@ -0,0 +1,581 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SerialInteraction extends Interaction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 11; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 35; + public static final int MAX_SIZE = 1677721600; + @Nullable + public int[] serialInteractions; + + public SerialInteraction() { + } + + public SerialInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + @Nullable int[] serialInteractions + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.serialInteractions = serialInteractions; + } + + public SerialInteraction(@Nonnull SerialInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.serialInteractions = other.serialInteractions; + } + + @Nonnull + public static SerialInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + SerialInteraction obj = new SerialInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 35 + buf.getIntLE(offset + 11); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 35 + buf.getIntLE(offset + 15); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 35 + buf.getIntLE(offset + 19); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 35 + buf.getIntLE(offset + 23); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 35 + buf.getIntLE(offset + 27); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 35 + buf.getIntLE(offset + 31); + int serialInteractionsCount = VarInt.peek(buf, varPos5); + if (serialInteractionsCount < 0) { + throw ProtocolException.negativeLength("SerialInteractions", serialInteractionsCount); + } + + if (serialInteractionsCount > 4096000) { + throw ProtocolException.arrayTooLong("SerialInteractions", serialInteractionsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + if (varPos5 + varIntLen + serialInteractionsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("SerialInteractions", varPos5 + varIntLen + serialInteractionsCount * 4, buf.readableBytes()); + } + + obj.serialInteractions = new int[serialInteractionsCount]; + + for (int ix = 0; ix < serialInteractionsCount; ix++) { + obj.serialInteractions[ix] = buf.getIntLE(varPos5 + varIntLen + ix * 4); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 35; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 11); + int pos0 = offset + 35 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 15); + int pos1 = offset + 35 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 19); + int pos2 = offset + 35 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 23); + int pos3 = offset + 35 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 27); + int pos4 = offset + 35 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 31); + int pos5 = offset + 35 + fieldOffset5; + int arrLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5) + arrLen * 4; + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.serialInteractions != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int serialInteractionsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.serialInteractions != null) { + buf.setIntLE(serialInteractionsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.serialInteractions.length > 4096000) { + throw ProtocolException.arrayTooLong("SerialInteractions", this.serialInteractions.length, 4096000); + } + + VarInt.write(buf, this.serialInteractions.length); + + for (int item : this.serialInteractions) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(serialInteractionsOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 35; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.serialInteractions != null) { + size += VarInt.size(this.serialInteractions.length) + this.serialInteractions.length * 4; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 35) { + return ValidationResult.error("Buffer too small: expected at least 35 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 11); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 35 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 15); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 35 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 19); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 35 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 23); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 35 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 27); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 35 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int serialInteractionsOffset = buffer.getIntLE(offset + 31); + if (serialInteractionsOffset < 0) { + return ValidationResult.error("Invalid offset for SerialInteractions"); + } + + int posxxxxx = offset + 35 + serialInteractionsOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SerialInteractions"); + } + + int serialInteractionsCount = VarInt.peek(buffer, posxxxxx); + if (serialInteractionsCount < 0) { + return ValidationResult.error("Invalid array count for SerialInteractions"); + } + + if (serialInteractionsCount > 4096000) { + return ValidationResult.error("SerialInteractions exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + posxxxxx += serialInteractionsCount * 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading SerialInteractions"); + } + } + + return ValidationResult.OK; + } + } + + public SerialInteraction clone() { + SerialInteraction copy = new SerialInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.serialInteractions = this.serialInteractions != null ? Arrays.copyOf(this.serialInteractions, this.serialInteractions.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SerialInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && Arrays.equals(this.serialInteractions, other.serialInteractions); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + return 31 * result + Arrays.hashCode(this.serialInteractions); + } +} diff --git a/src/com/hypixel/hytale/protocol/ServerCameraSettings.java b/src/com/hypixel/hytale/protocol/ServerCameraSettings.java new file mode 100644 index 0000000..d06428d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ServerCameraSettings.java @@ -0,0 +1,453 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ServerCameraSettings { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 154; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 154; + public static final int MAX_SIZE = 154; + public float positionLerpSpeed = 1.0F; + public float rotationLerpSpeed = 1.0F; + public float distance; + public float speedModifier = 1.0F; + public boolean allowPitchControls; + public boolean displayCursor; + public boolean displayReticle; + @Nonnull + public MouseInputTargetType mouseInputTargetType = MouseInputTargetType.Any; + public boolean sendMouseMotion; + public boolean skipCharacterPhysics; + public boolean isFirstPerson = true; + @Nonnull + public MovementForceRotationType movementForceRotationType = MovementForceRotationType.AttachedToHead; + @Nullable + public Direction movementForceRotation; + @Nonnull + public AttachedToType attachedToType = AttachedToType.LocalPlayer; + public int attachedToEntityId; + public boolean eyeOffset; + @Nonnull + public PositionDistanceOffsetType positionDistanceOffsetType = PositionDistanceOffsetType.DistanceOffset; + @Nullable + public Position positionOffset; + @Nullable + public Direction rotationOffset; + @Nonnull + public PositionType positionType = PositionType.AttachedToPlusOffset; + @Nullable + public Position position; + @Nonnull + public RotationType rotationType = RotationType.AttachedToPlusOffset; + @Nullable + public Direction rotation; + @Nonnull + public CanMoveType canMoveType = CanMoveType.AttachedToLocalPlayer; + @Nonnull + public ApplyMovementType applyMovementType = ApplyMovementType.CharacterController; + @Nullable + public Vector3f movementMultiplier; + @Nonnull + public ApplyLookType applyLookType = ApplyLookType.LocalPlayerLookOrientation; + @Nullable + public Vector2f lookMultiplier; + @Nonnull + public MouseInputType mouseInputType = MouseInputType.LookAtTarget; + @Nullable + public Vector3f planeNormal; + + public ServerCameraSettings() { + } + + public ServerCameraSettings( + float positionLerpSpeed, + float rotationLerpSpeed, + float distance, + float speedModifier, + boolean allowPitchControls, + boolean displayCursor, + boolean displayReticle, + @Nonnull MouseInputTargetType mouseInputTargetType, + boolean sendMouseMotion, + boolean skipCharacterPhysics, + boolean isFirstPerson, + @Nonnull MovementForceRotationType movementForceRotationType, + @Nullable Direction movementForceRotation, + @Nonnull AttachedToType attachedToType, + int attachedToEntityId, + boolean eyeOffset, + @Nonnull PositionDistanceOffsetType positionDistanceOffsetType, + @Nullable Position positionOffset, + @Nullable Direction rotationOffset, + @Nonnull PositionType positionType, + @Nullable Position position, + @Nonnull RotationType rotationType, + @Nullable Direction rotation, + @Nonnull CanMoveType canMoveType, + @Nonnull ApplyMovementType applyMovementType, + @Nullable Vector3f movementMultiplier, + @Nonnull ApplyLookType applyLookType, + @Nullable Vector2f lookMultiplier, + @Nonnull MouseInputType mouseInputType, + @Nullable Vector3f planeNormal + ) { + this.positionLerpSpeed = positionLerpSpeed; + this.rotationLerpSpeed = rotationLerpSpeed; + this.distance = distance; + this.speedModifier = speedModifier; + this.allowPitchControls = allowPitchControls; + this.displayCursor = displayCursor; + this.displayReticle = displayReticle; + this.mouseInputTargetType = mouseInputTargetType; + this.sendMouseMotion = sendMouseMotion; + this.skipCharacterPhysics = skipCharacterPhysics; + this.isFirstPerson = isFirstPerson; + this.movementForceRotationType = movementForceRotationType; + this.movementForceRotation = movementForceRotation; + this.attachedToType = attachedToType; + this.attachedToEntityId = attachedToEntityId; + this.eyeOffset = eyeOffset; + this.positionDistanceOffsetType = positionDistanceOffsetType; + this.positionOffset = positionOffset; + this.rotationOffset = rotationOffset; + this.positionType = positionType; + this.position = position; + this.rotationType = rotationType; + this.rotation = rotation; + this.canMoveType = canMoveType; + this.applyMovementType = applyMovementType; + this.movementMultiplier = movementMultiplier; + this.applyLookType = applyLookType; + this.lookMultiplier = lookMultiplier; + this.mouseInputType = mouseInputType; + this.planeNormal = planeNormal; + } + + public ServerCameraSettings(@Nonnull ServerCameraSettings other) { + this.positionLerpSpeed = other.positionLerpSpeed; + this.rotationLerpSpeed = other.rotationLerpSpeed; + this.distance = other.distance; + this.speedModifier = other.speedModifier; + this.allowPitchControls = other.allowPitchControls; + this.displayCursor = other.displayCursor; + this.displayReticle = other.displayReticle; + this.mouseInputTargetType = other.mouseInputTargetType; + this.sendMouseMotion = other.sendMouseMotion; + this.skipCharacterPhysics = other.skipCharacterPhysics; + this.isFirstPerson = other.isFirstPerson; + this.movementForceRotationType = other.movementForceRotationType; + this.movementForceRotation = other.movementForceRotation; + this.attachedToType = other.attachedToType; + this.attachedToEntityId = other.attachedToEntityId; + this.eyeOffset = other.eyeOffset; + this.positionDistanceOffsetType = other.positionDistanceOffsetType; + this.positionOffset = other.positionOffset; + this.rotationOffset = other.rotationOffset; + this.positionType = other.positionType; + this.position = other.position; + this.rotationType = other.rotationType; + this.rotation = other.rotation; + this.canMoveType = other.canMoveType; + this.applyMovementType = other.applyMovementType; + this.movementMultiplier = other.movementMultiplier; + this.applyLookType = other.applyLookType; + this.lookMultiplier = other.lookMultiplier; + this.mouseInputType = other.mouseInputType; + this.planeNormal = other.planeNormal; + } + + @Nonnull + public static ServerCameraSettings deserialize(@Nonnull ByteBuf buf, int offset) { + ServerCameraSettings obj = new ServerCameraSettings(); + byte nullBits = buf.getByte(offset); + obj.positionLerpSpeed = buf.getFloatLE(offset + 1); + obj.rotationLerpSpeed = buf.getFloatLE(offset + 5); + obj.distance = buf.getFloatLE(offset + 9); + obj.speedModifier = buf.getFloatLE(offset + 13); + obj.allowPitchControls = buf.getByte(offset + 17) != 0; + obj.displayCursor = buf.getByte(offset + 18) != 0; + obj.displayReticle = buf.getByte(offset + 19) != 0; + obj.mouseInputTargetType = MouseInputTargetType.fromValue(buf.getByte(offset + 20)); + obj.sendMouseMotion = buf.getByte(offset + 21) != 0; + obj.skipCharacterPhysics = buf.getByte(offset + 22) != 0; + obj.isFirstPerson = buf.getByte(offset + 23) != 0; + obj.movementForceRotationType = MovementForceRotationType.fromValue(buf.getByte(offset + 24)); + if ((nullBits & 1) != 0) { + obj.movementForceRotation = Direction.deserialize(buf, offset + 25); + } + + obj.attachedToType = AttachedToType.fromValue(buf.getByte(offset + 37)); + obj.attachedToEntityId = buf.getIntLE(offset + 38); + obj.eyeOffset = buf.getByte(offset + 42) != 0; + obj.positionDistanceOffsetType = PositionDistanceOffsetType.fromValue(buf.getByte(offset + 43)); + if ((nullBits & 2) != 0) { + obj.positionOffset = Position.deserialize(buf, offset + 44); + } + + if ((nullBits & 4) != 0) { + obj.rotationOffset = Direction.deserialize(buf, offset + 68); + } + + obj.positionType = PositionType.fromValue(buf.getByte(offset + 80)); + if ((nullBits & 8) != 0) { + obj.position = Position.deserialize(buf, offset + 81); + } + + obj.rotationType = RotationType.fromValue(buf.getByte(offset + 105)); + if ((nullBits & 16) != 0) { + obj.rotation = Direction.deserialize(buf, offset + 106); + } + + obj.canMoveType = CanMoveType.fromValue(buf.getByte(offset + 118)); + obj.applyMovementType = ApplyMovementType.fromValue(buf.getByte(offset + 119)); + if ((nullBits & 32) != 0) { + obj.movementMultiplier = Vector3f.deserialize(buf, offset + 120); + } + + obj.applyLookType = ApplyLookType.fromValue(buf.getByte(offset + 132)); + if ((nullBits & 64) != 0) { + obj.lookMultiplier = Vector2f.deserialize(buf, offset + 133); + } + + obj.mouseInputType = MouseInputType.fromValue(buf.getByte(offset + 141)); + if ((nullBits & 128) != 0) { + obj.planeNormal = Vector3f.deserialize(buf, offset + 142); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 154; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.movementForceRotation != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.positionOffset != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rotationOffset != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.position != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.rotation != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.movementMultiplier != null) { + nullBits = (byte)(nullBits | 32); + } + + if (this.lookMultiplier != null) { + nullBits = (byte)(nullBits | 64); + } + + if (this.planeNormal != null) { + nullBits = (byte)(nullBits | 128); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.positionLerpSpeed); + buf.writeFloatLE(this.rotationLerpSpeed); + buf.writeFloatLE(this.distance); + buf.writeFloatLE(this.speedModifier); + buf.writeByte(this.allowPitchControls ? 1 : 0); + buf.writeByte(this.displayCursor ? 1 : 0); + buf.writeByte(this.displayReticle ? 1 : 0); + buf.writeByte(this.mouseInputTargetType.getValue()); + buf.writeByte(this.sendMouseMotion ? 1 : 0); + buf.writeByte(this.skipCharacterPhysics ? 1 : 0); + buf.writeByte(this.isFirstPerson ? 1 : 0); + buf.writeByte(this.movementForceRotationType.getValue()); + if (this.movementForceRotation != null) { + this.movementForceRotation.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeByte(this.attachedToType.getValue()); + buf.writeIntLE(this.attachedToEntityId); + buf.writeByte(this.eyeOffset ? 1 : 0); + buf.writeByte(this.positionDistanceOffsetType.getValue()); + if (this.positionOffset != null) { + this.positionOffset.serialize(buf); + } else { + buf.writeZero(24); + } + + if (this.rotationOffset != null) { + this.rotationOffset.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeByte(this.positionType.getValue()); + if (this.position != null) { + this.position.serialize(buf); + } else { + buf.writeZero(24); + } + + buf.writeByte(this.rotationType.getValue()); + if (this.rotation != null) { + this.rotation.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeByte(this.canMoveType.getValue()); + buf.writeByte(this.applyMovementType.getValue()); + if (this.movementMultiplier != null) { + this.movementMultiplier.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeByte(this.applyLookType.getValue()); + if (this.lookMultiplier != null) { + this.lookMultiplier.serialize(buf); + } else { + buf.writeZero(8); + } + + buf.writeByte(this.mouseInputType.getValue()); + if (this.planeNormal != null) { + this.planeNormal.serialize(buf); + } else { + buf.writeZero(12); + } + } + + public int computeSize() { + return 154; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 154 ? ValidationResult.error("Buffer too small: expected at least 154 bytes") : ValidationResult.OK; + } + + public ServerCameraSettings clone() { + ServerCameraSettings copy = new ServerCameraSettings(); + copy.positionLerpSpeed = this.positionLerpSpeed; + copy.rotationLerpSpeed = this.rotationLerpSpeed; + copy.distance = this.distance; + copy.speedModifier = this.speedModifier; + copy.allowPitchControls = this.allowPitchControls; + copy.displayCursor = this.displayCursor; + copy.displayReticle = this.displayReticle; + copy.mouseInputTargetType = this.mouseInputTargetType; + copy.sendMouseMotion = this.sendMouseMotion; + copy.skipCharacterPhysics = this.skipCharacterPhysics; + copy.isFirstPerson = this.isFirstPerson; + copy.movementForceRotationType = this.movementForceRotationType; + copy.movementForceRotation = this.movementForceRotation != null ? this.movementForceRotation.clone() : null; + copy.attachedToType = this.attachedToType; + copy.attachedToEntityId = this.attachedToEntityId; + copy.eyeOffset = this.eyeOffset; + copy.positionDistanceOffsetType = this.positionDistanceOffsetType; + copy.positionOffset = this.positionOffset != null ? this.positionOffset.clone() : null; + copy.rotationOffset = this.rotationOffset != null ? this.rotationOffset.clone() : null; + copy.positionType = this.positionType; + copy.position = this.position != null ? this.position.clone() : null; + copy.rotationType = this.rotationType; + copy.rotation = this.rotation != null ? this.rotation.clone() : null; + copy.canMoveType = this.canMoveType; + copy.applyMovementType = this.applyMovementType; + copy.movementMultiplier = this.movementMultiplier != null ? this.movementMultiplier.clone() : null; + copy.applyLookType = this.applyLookType; + copy.lookMultiplier = this.lookMultiplier != null ? this.lookMultiplier.clone() : null; + copy.mouseInputType = this.mouseInputType; + copy.planeNormal = this.planeNormal != null ? this.planeNormal.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ServerCameraSettings other) + ? false + : this.positionLerpSpeed == other.positionLerpSpeed + && this.rotationLerpSpeed == other.rotationLerpSpeed + && this.distance == other.distance + && this.speedModifier == other.speedModifier + && this.allowPitchControls == other.allowPitchControls + && this.displayCursor == other.displayCursor + && this.displayReticle == other.displayReticle + && Objects.equals(this.mouseInputTargetType, other.mouseInputTargetType) + && this.sendMouseMotion == other.sendMouseMotion + && this.skipCharacterPhysics == other.skipCharacterPhysics + && this.isFirstPerson == other.isFirstPerson + && Objects.equals(this.movementForceRotationType, other.movementForceRotationType) + && Objects.equals(this.movementForceRotation, other.movementForceRotation) + && Objects.equals(this.attachedToType, other.attachedToType) + && this.attachedToEntityId == other.attachedToEntityId + && this.eyeOffset == other.eyeOffset + && Objects.equals(this.positionDistanceOffsetType, other.positionDistanceOffsetType) + && Objects.equals(this.positionOffset, other.positionOffset) + && Objects.equals(this.rotationOffset, other.rotationOffset) + && Objects.equals(this.positionType, other.positionType) + && Objects.equals(this.position, other.position) + && Objects.equals(this.rotationType, other.rotationType) + && Objects.equals(this.rotation, other.rotation) + && Objects.equals(this.canMoveType, other.canMoveType) + && Objects.equals(this.applyMovementType, other.applyMovementType) + && Objects.equals(this.movementMultiplier, other.movementMultiplier) + && Objects.equals(this.applyLookType, other.applyLookType) + && Objects.equals(this.lookMultiplier, other.lookMultiplier) + && Objects.equals(this.mouseInputType, other.mouseInputType) + && Objects.equals(this.planeNormal, other.planeNormal); + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.positionLerpSpeed, + this.rotationLerpSpeed, + this.distance, + this.speedModifier, + this.allowPitchControls, + this.displayCursor, + this.displayReticle, + this.mouseInputTargetType, + this.sendMouseMotion, + this.skipCharacterPhysics, + this.isFirstPerson, + this.movementForceRotationType, + this.movementForceRotation, + this.attachedToType, + this.attachedToEntityId, + this.eyeOffset, + this.positionDistanceOffsetType, + this.positionOffset, + this.rotationOffset, + this.positionType, + this.position, + this.rotationType, + this.rotation, + this.canMoveType, + this.applyMovementType, + this.movementMultiplier, + this.applyLookType, + this.lookMultiplier, + this.mouseInputType, + this.planeNormal + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/ShaderType.java b/src/com/hypixel/hytale/protocol/ShaderType.java new file mode 100644 index 0000000..5313a95 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ShaderType.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ShaderType { + None(0), + Wind(1), + WindAttached(2), + WindRandom(3), + WindFractal(4), + Ice(5), + Water(6), + Lava(7), + Slime(8), + Ripple(9); + + public static final ShaderType[] VALUES = values(); + private final int value; + + private ShaderType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ShaderType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ShaderType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ShadingMode.java b/src/com/hypixel/hytale/protocol/ShadingMode.java new file mode 100644 index 0000000..41d7e8d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ShadingMode.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ShadingMode { + Standard(0), + Flat(1), + Fullbright(2), + Reflective(3); + + public static final ShadingMode[] VALUES = values(); + private final int value; + + private ShadingMode(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ShadingMode fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ShadingMode", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/SimpleBlockInteraction.java b/src/com/hypixel/hytale/protocol/SimpleBlockInteraction.java new file mode 100644 index 0000000..2514e7b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SimpleBlockInteraction.java @@ -0,0 +1,513 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SimpleBlockInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 20; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 40; + public static final int MAX_SIZE = 1677721600; + public boolean useLatestTarget; + + public SimpleBlockInteraction() { + } + + public SimpleBlockInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + boolean useLatestTarget + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.useLatestTarget = useLatestTarget; + } + + public SimpleBlockInteraction(@Nonnull SimpleBlockInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.useLatestTarget = other.useLatestTarget; + } + + @Nonnull + public static SimpleBlockInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + SimpleBlockInteraction obj = new SimpleBlockInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.useLatestTarget = buf.getByte(offset + 19) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 40 + buf.getIntLE(offset + 20); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 40 + buf.getIntLE(offset + 24); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 40 + buf.getIntLE(offset + 28); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 40 + buf.getIntLE(offset + 32); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 40 + buf.getIntLE(offset + 36); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 40; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 20); + int pos0 = offset + 40 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 24); + int pos1 = offset + 40 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 28); + int pos2 = offset + 40 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 32); + int pos3 = offset + 40 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 36); + int pos4 = offset + 40 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeByte(this.useLatestTarget ? 1 : 0); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 40; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 40) { + return ValidationResult.error("Buffer too small: expected at least 40 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 20); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 40 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 24); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 40 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 28); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 40 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 32); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 40 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 36); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 40 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public SimpleBlockInteraction clone() { + SimpleBlockInteraction copy = new SimpleBlockInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.useLatestTarget = this.useLatestTarget; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SimpleBlockInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && this.useLatestTarget == other.useLatestTarget; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + return 31 * result + Boolean.hashCode(this.useLatestTarget); + } +} diff --git a/src/com/hypixel/hytale/protocol/SimpleInteraction.java b/src/com/hypixel/hytale/protocol/SimpleInteraction.java new file mode 100644 index 0000000..c1d11b9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SimpleInteraction.java @@ -0,0 +1,506 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SimpleInteraction extends Interaction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 19; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 39; + public static final int MAX_SIZE = 1677721600; + public int next = Integer.MIN_VALUE; + public int failed = Integer.MIN_VALUE; + + public SimpleInteraction() { + } + + public SimpleInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + } + + public SimpleInteraction(@Nonnull SimpleInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + } + + @Nonnull + public static SimpleInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + SimpleInteraction obj = new SimpleInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 39 + buf.getIntLE(offset + 19); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 39 + buf.getIntLE(offset + 23); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 39 + buf.getIntLE(offset + 27); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 39 + buf.getIntLE(offset + 31); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 39 + buf.getIntLE(offset + 35); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 39; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 19); + int pos0 = offset + 39 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 23); + int pos1 = offset + 39 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 27); + int pos2 = offset + 39 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 31); + int pos3 = offset + 39 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 35); + int pos4 = offset + 39 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 39; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 39) { + return ValidationResult.error("Buffer too small: expected at least 39 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 19); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 39 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 23); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 39 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 27); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 39 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 31); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 39 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 35); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 39 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public SimpleInteraction clone() { + SimpleInteraction copy = new SimpleInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SimpleInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + return 31 * result + Integer.hashCode(this.failed); + } +} diff --git a/src/com/hypixel/hytale/protocol/Size.java b/src/com/hypixel/hytale/protocol/Size.java new file mode 100644 index 0000000..46836de --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Size.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class Size { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public int width; + public int height; + + public Size() { + } + + public Size(int width, int height) { + this.width = width; + this.height = height; + } + + public Size(@Nonnull Size other) { + this.width = other.width; + this.height = other.height; + } + + @Nonnull + public static Size deserialize(@Nonnull ByteBuf buf, int offset) { + Size obj = new Size(); + obj.width = buf.getIntLE(offset + 0); + obj.height = buf.getIntLE(offset + 4); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.width); + buf.writeIntLE(this.height); + } + + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public Size clone() { + Size copy = new Size(); + copy.width = this.width; + copy.height = this.height; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Size other) ? false : this.width == other.width && this.height == other.height; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.width, this.height); + } +} diff --git a/src/com/hypixel/hytale/protocol/SmartMoveType.java b/src/com/hypixel/hytale/protocol/SmartMoveType.java new file mode 100644 index 0000000..3b5a3c7 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SmartMoveType.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum SmartMoveType { + EquipOrMergeStack(0), + PutInHotbarOrWindow(1), + PutInHotbarOrBackpack(2); + + public static final SmartMoveType[] VALUES = values(); + private final int value; + + private SmartMoveType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static SmartMoveType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("SmartMoveType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/SoftBlock.java b/src/com/hypixel/hytale/protocol/SoftBlock.java new file mode 100644 index 0000000..7bdaddf --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SoftBlock.java @@ -0,0 +1,235 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SoftBlock { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 10; + public static final int MAX_SIZE = 32768020; + @Nullable + public String itemId; + @Nullable + public String dropListId; + public boolean isWeaponBreakable; + + public SoftBlock() { + } + + public SoftBlock(@Nullable String itemId, @Nullable String dropListId, boolean isWeaponBreakable) { + this.itemId = itemId; + this.dropListId = dropListId; + this.isWeaponBreakable = isWeaponBreakable; + } + + public SoftBlock(@Nonnull SoftBlock other) { + this.itemId = other.itemId; + this.dropListId = other.dropListId; + this.isWeaponBreakable = other.isWeaponBreakable; + } + + @Nonnull + public static SoftBlock deserialize(@Nonnull ByteBuf buf, int offset) { + SoftBlock obj = new SoftBlock(); + byte nullBits = buf.getByte(offset); + obj.isWeaponBreakable = buf.getByte(offset + 1) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 10 + buf.getIntLE(offset + 2); + int itemIdLen = VarInt.peek(buf, varPos0); + if (itemIdLen < 0) { + throw ProtocolException.negativeLength("ItemId", itemIdLen); + } + + if (itemIdLen > 4096000) { + throw ProtocolException.stringTooLong("ItemId", itemIdLen, 4096000); + } + + obj.itemId = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 10 + buf.getIntLE(offset + 6); + int dropListIdLen = VarInt.peek(buf, varPos1); + if (dropListIdLen < 0) { + throw ProtocolException.negativeLength("DropListId", dropListIdLen); + } + + if (dropListIdLen > 4096000) { + throw ProtocolException.stringTooLong("DropListId", dropListIdLen, 4096000); + } + + obj.dropListId = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 10; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 2); + int pos0 = offset + 10 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 6); + int pos1 = offset + 10 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.itemId != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.dropListId != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.isWeaponBreakable ? 1 : 0); + int itemIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int dropListIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.itemId != null) { + buf.setIntLE(itemIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.itemId, 4096000); + } else { + buf.setIntLE(itemIdOffsetSlot, -1); + } + + if (this.dropListId != null) { + buf.setIntLE(dropListIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.dropListId, 4096000); + } else { + buf.setIntLE(dropListIdOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 10; + if (this.itemId != null) { + size += PacketIO.stringSize(this.itemId); + } + + if (this.dropListId != null) { + size += PacketIO.stringSize(this.dropListId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 10) { + return ValidationResult.error("Buffer too small: expected at least 10 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int itemIdOffset = buffer.getIntLE(offset + 2); + if (itemIdOffset < 0) { + return ValidationResult.error("Invalid offset for ItemId"); + } + + int pos = offset + 10 + itemIdOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ItemId"); + } + + int itemIdLen = VarInt.peek(buffer, pos); + if (itemIdLen < 0) { + return ValidationResult.error("Invalid string length for ItemId"); + } + + if (itemIdLen > 4096000) { + return ValidationResult.error("ItemId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += itemIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ItemId"); + } + } + + if ((nullBits & 2) != 0) { + int dropListIdOffset = buffer.getIntLE(offset + 6); + if (dropListIdOffset < 0) { + return ValidationResult.error("Invalid offset for DropListId"); + } + + int posx = offset + 10 + dropListIdOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for DropListId"); + } + + int dropListIdLen = VarInt.peek(buffer, posx); + if (dropListIdLen < 0) { + return ValidationResult.error("Invalid string length for DropListId"); + } + + if (dropListIdLen > 4096000) { + return ValidationResult.error("DropListId exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += dropListIdLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading DropListId"); + } + } + + return ValidationResult.OK; + } + } + + public SoftBlock clone() { + SoftBlock copy = new SoftBlock(); + copy.itemId = this.itemId; + copy.dropListId = this.dropListId; + copy.isWeaponBreakable = this.isWeaponBreakable; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SoftBlock other) + ? false + : Objects.equals(this.itemId, other.itemId) + && Objects.equals(this.dropListId, other.dropListId) + && this.isWeaponBreakable == other.isWeaponBreakable; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.itemId, this.dropListId, this.isWeaponBreakable); + } +} diff --git a/src/com/hypixel/hytale/protocol/SoftParticle.java b/src/com/hypixel/hytale/protocol/SoftParticle.java new file mode 100644 index 0000000..34701e9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SoftParticle.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum SoftParticle { + Enable(0), + Disable(1), + Require(2); + + public static final SoftParticle[] VALUES = values(); + private final int value; + + private SoftParticle(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static SoftParticle fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("SoftParticle", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/SortType.java b/src/com/hypixel/hytale/protocol/SortType.java new file mode 100644 index 0000000..6cdd3e0 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SortType.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum SortType { + Name(0), + Type(1), + Rarity(2); + + public static final SortType[] VALUES = values(); + private final int value; + + private SortType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static SortType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("SortType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/SoundCategory.java b/src/com/hypixel/hytale/protocol/SoundCategory.java new file mode 100644 index 0000000..b28845c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SoundCategory.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum SoundCategory { + Music(0), + Ambient(1), + SFX(2), + UI(3); + + public static final SoundCategory[] VALUES = values(); + private final int value; + + private SoundCategory(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static SoundCategory fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("SoundCategory", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/SoundEvent.java b/src/com/hypixel/hytale/protocol/SoundEvent.java new file mode 100644 index 0000000..bb73342 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SoundEvent.java @@ -0,0 +1,350 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SoundEvent { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 34; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 42; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + public float volume; + public float pitch; + public float musicDuckingVolume; + public float ambientDuckingVolume; + public int maxInstance; + public boolean preventSoundInterruption; + public float startAttenuationDistance; + public float maxDistance; + @Nullable + public SoundEventLayer[] layers; + public int audioCategory; + + public SoundEvent() { + } + + public SoundEvent( + @Nullable String id, + float volume, + float pitch, + float musicDuckingVolume, + float ambientDuckingVolume, + int maxInstance, + boolean preventSoundInterruption, + float startAttenuationDistance, + float maxDistance, + @Nullable SoundEventLayer[] layers, + int audioCategory + ) { + this.id = id; + this.volume = volume; + this.pitch = pitch; + this.musicDuckingVolume = musicDuckingVolume; + this.ambientDuckingVolume = ambientDuckingVolume; + this.maxInstance = maxInstance; + this.preventSoundInterruption = preventSoundInterruption; + this.startAttenuationDistance = startAttenuationDistance; + this.maxDistance = maxDistance; + this.layers = layers; + this.audioCategory = audioCategory; + } + + public SoundEvent(@Nonnull SoundEvent other) { + this.id = other.id; + this.volume = other.volume; + this.pitch = other.pitch; + this.musicDuckingVolume = other.musicDuckingVolume; + this.ambientDuckingVolume = other.ambientDuckingVolume; + this.maxInstance = other.maxInstance; + this.preventSoundInterruption = other.preventSoundInterruption; + this.startAttenuationDistance = other.startAttenuationDistance; + this.maxDistance = other.maxDistance; + this.layers = other.layers; + this.audioCategory = other.audioCategory; + } + + @Nonnull + public static SoundEvent deserialize(@Nonnull ByteBuf buf, int offset) { + SoundEvent obj = new SoundEvent(); + byte nullBits = buf.getByte(offset); + obj.volume = buf.getFloatLE(offset + 1); + obj.pitch = buf.getFloatLE(offset + 5); + obj.musicDuckingVolume = buf.getFloatLE(offset + 9); + obj.ambientDuckingVolume = buf.getFloatLE(offset + 13); + obj.maxInstance = buf.getIntLE(offset + 17); + obj.preventSoundInterruption = buf.getByte(offset + 21) != 0; + obj.startAttenuationDistance = buf.getFloatLE(offset + 22); + obj.maxDistance = buf.getFloatLE(offset + 26); + obj.audioCategory = buf.getIntLE(offset + 30); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 42 + buf.getIntLE(offset + 34); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 42 + buf.getIntLE(offset + 38); + int layersCount = VarInt.peek(buf, varPos1); + if (layersCount < 0) { + throw ProtocolException.negativeLength("Layers", layersCount); + } + + if (layersCount > 4096000) { + throw ProtocolException.arrayTooLong("Layers", layersCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + layersCount * 42L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Layers", varPos1 + varIntLen + layersCount * 42, buf.readableBytes()); + } + + obj.layers = new SoundEventLayer[layersCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < layersCount; i++) { + obj.layers[i] = SoundEventLayer.deserialize(buf, elemPos); + elemPos += SoundEventLayer.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 42; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 34); + int pos0 = offset + 42 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 38); + int pos1 = offset + 42 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += SoundEventLayer.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.layers != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.volume); + buf.writeFloatLE(this.pitch); + buf.writeFloatLE(this.musicDuckingVolume); + buf.writeFloatLE(this.ambientDuckingVolume); + buf.writeIntLE(this.maxInstance); + buf.writeByte(this.preventSoundInterruption ? 1 : 0); + buf.writeFloatLE(this.startAttenuationDistance); + buf.writeFloatLE(this.maxDistance); + buf.writeIntLE(this.audioCategory); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int layersOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.layers != null) { + buf.setIntLE(layersOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.layers.length > 4096000) { + throw ProtocolException.arrayTooLong("Layers", this.layers.length, 4096000); + } + + VarInt.write(buf, this.layers.length); + + for (SoundEventLayer item : this.layers) { + item.serialize(buf); + } + } else { + buf.setIntLE(layersOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 42; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.layers != null) { + int layersSize = 0; + + for (SoundEventLayer elem : this.layers) { + layersSize += elem.computeSize(); + } + + size += VarInt.size(this.layers.length) + layersSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 42) { + return ValidationResult.error("Buffer too small: expected at least 42 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 34); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 42 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int layersOffset = buffer.getIntLE(offset + 38); + if (layersOffset < 0) { + return ValidationResult.error("Invalid offset for Layers"); + } + + int posx = offset + 42 + layersOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Layers"); + } + + int layersCount = VarInt.peek(buffer, posx); + if (layersCount < 0) { + return ValidationResult.error("Invalid array count for Layers"); + } + + if (layersCount > 4096000) { + return ValidationResult.error("Layers exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < layersCount; i++) { + ValidationResult structResult = SoundEventLayer.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid SoundEventLayer in Layers[" + i + "]: " + structResult.error()); + } + + posx += SoundEventLayer.computeBytesConsumed(buffer, posx); + } + } + + return ValidationResult.OK; + } + } + + public SoundEvent clone() { + SoundEvent copy = new SoundEvent(); + copy.id = this.id; + copy.volume = this.volume; + copy.pitch = this.pitch; + copy.musicDuckingVolume = this.musicDuckingVolume; + copy.ambientDuckingVolume = this.ambientDuckingVolume; + copy.maxInstance = this.maxInstance; + copy.preventSoundInterruption = this.preventSoundInterruption; + copy.startAttenuationDistance = this.startAttenuationDistance; + copy.maxDistance = this.maxDistance; + copy.layers = this.layers != null ? Arrays.stream(this.layers).map(e -> e.clone()).toArray(SoundEventLayer[]::new) : null; + copy.audioCategory = this.audioCategory; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SoundEvent other) + ? false + : Objects.equals(this.id, other.id) + && this.volume == other.volume + && this.pitch == other.pitch + && this.musicDuckingVolume == other.musicDuckingVolume + && this.ambientDuckingVolume == other.ambientDuckingVolume + && this.maxInstance == other.maxInstance + && this.preventSoundInterruption == other.preventSoundInterruption + && this.startAttenuationDistance == other.startAttenuationDistance + && this.maxDistance == other.maxDistance + && Arrays.equals((Object[])this.layers, (Object[])other.layers) + && this.audioCategory == other.audioCategory; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.id); + result = 31 * result + Float.hashCode(this.volume); + result = 31 * result + Float.hashCode(this.pitch); + result = 31 * result + Float.hashCode(this.musicDuckingVolume); + result = 31 * result + Float.hashCode(this.ambientDuckingVolume); + result = 31 * result + Integer.hashCode(this.maxInstance); + result = 31 * result + Boolean.hashCode(this.preventSoundInterruption); + result = 31 * result + Float.hashCode(this.startAttenuationDistance); + result = 31 * result + Float.hashCode(this.maxDistance); + result = 31 * result + Arrays.hashCode((Object[])this.layers); + return 31 * result + Integer.hashCode(this.audioCategory); + } +} diff --git a/src/com/hypixel/hytale/protocol/SoundEventLayer.java b/src/com/hypixel/hytale/protocol/SoundEventLayer.java new file mode 100644 index 0000000..98c36fc --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SoundEventLayer.java @@ -0,0 +1,262 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SoundEventLayer { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 42; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 42; + public static final int MAX_SIZE = 1677721600; + public float volume; + public float startDelay; + public boolean looping; + public int probability; + public float probabilityRerollDelay; + public int roundRobinHistorySize; + @Nullable + public SoundEventLayerRandomSettings randomSettings; + @Nullable + public String[] files; + + public SoundEventLayer() { + } + + public SoundEventLayer( + float volume, + float startDelay, + boolean looping, + int probability, + float probabilityRerollDelay, + int roundRobinHistorySize, + @Nullable SoundEventLayerRandomSettings randomSettings, + @Nullable String[] files + ) { + this.volume = volume; + this.startDelay = startDelay; + this.looping = looping; + this.probability = probability; + this.probabilityRerollDelay = probabilityRerollDelay; + this.roundRobinHistorySize = roundRobinHistorySize; + this.randomSettings = randomSettings; + this.files = files; + } + + public SoundEventLayer(@Nonnull SoundEventLayer other) { + this.volume = other.volume; + this.startDelay = other.startDelay; + this.looping = other.looping; + this.probability = other.probability; + this.probabilityRerollDelay = other.probabilityRerollDelay; + this.roundRobinHistorySize = other.roundRobinHistorySize; + this.randomSettings = other.randomSettings; + this.files = other.files; + } + + @Nonnull + public static SoundEventLayer deserialize(@Nonnull ByteBuf buf, int offset) { + SoundEventLayer obj = new SoundEventLayer(); + byte nullBits = buf.getByte(offset); + obj.volume = buf.getFloatLE(offset + 1); + obj.startDelay = buf.getFloatLE(offset + 5); + obj.looping = buf.getByte(offset + 9) != 0; + obj.probability = buf.getIntLE(offset + 10); + obj.probabilityRerollDelay = buf.getFloatLE(offset + 14); + obj.roundRobinHistorySize = buf.getIntLE(offset + 18); + if ((nullBits & 1) != 0) { + obj.randomSettings = SoundEventLayerRandomSettings.deserialize(buf, offset + 22); + } + + int pos = offset + 42; + if ((nullBits & 2) != 0) { + int filesCount = VarInt.peek(buf, pos); + if (filesCount < 0) { + throw ProtocolException.negativeLength("Files", filesCount); + } + + if (filesCount > 4096000) { + throw ProtocolException.arrayTooLong("Files", filesCount, 4096000); + } + + int filesVarLen = VarInt.size(filesCount); + if (pos + filesVarLen + filesCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Files", pos + filesVarLen + filesCount * 1, buf.readableBytes()); + } + + pos += filesVarLen; + obj.files = new String[filesCount]; + + for (int i = 0; i < filesCount; i++) { + int strLen = VarInt.peek(buf, pos); + if (strLen < 0) { + throw ProtocolException.negativeLength("files[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("files[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, pos); + obj.files[i] = PacketIO.readVarString(buf, pos); + pos += strVarLen + strLen; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 42; + if ((nullBits & 2) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.randomSettings != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.files != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.volume); + buf.writeFloatLE(this.startDelay); + buf.writeByte(this.looping ? 1 : 0); + buf.writeIntLE(this.probability); + buf.writeFloatLE(this.probabilityRerollDelay); + buf.writeIntLE(this.roundRobinHistorySize); + if (this.randomSettings != null) { + this.randomSettings.serialize(buf); + } else { + buf.writeZero(20); + } + + if (this.files != null) { + if (this.files.length > 4096000) { + throw ProtocolException.arrayTooLong("Files", this.files.length, 4096000); + } + + VarInt.write(buf, this.files.length); + + for (String item : this.files) { + PacketIO.writeVarString(buf, item, 4096000); + } + } + } + + public int computeSize() { + int size = 42; + if (this.files != null) { + int filesSize = 0; + + for (String elem : this.files) { + filesSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.files.length) + filesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 42) { + return ValidationResult.error("Buffer too small: expected at least 42 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 42; + if ((nullBits & 2) != 0) { + int filesCount = VarInt.peek(buffer, pos); + if (filesCount < 0) { + return ValidationResult.error("Invalid array count for Files"); + } + + if (filesCount > 4096000) { + return ValidationResult.error("Files exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < filesCount; i++) { + int strLen = VarInt.peek(buffer, pos); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in Files"); + } + + pos += VarInt.length(buffer, pos); + pos += strLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in Files"); + } + } + } + + return ValidationResult.OK; + } + } + + public SoundEventLayer clone() { + SoundEventLayer copy = new SoundEventLayer(); + copy.volume = this.volume; + copy.startDelay = this.startDelay; + copy.looping = this.looping; + copy.probability = this.probability; + copy.probabilityRerollDelay = this.probabilityRerollDelay; + copy.roundRobinHistorySize = this.roundRobinHistorySize; + copy.randomSettings = this.randomSettings != null ? this.randomSettings.clone() : null; + copy.files = this.files != null ? Arrays.copyOf(this.files, this.files.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SoundEventLayer other) + ? false + : this.volume == other.volume + && this.startDelay == other.startDelay + && this.looping == other.looping + && this.probability == other.probability + && this.probabilityRerollDelay == other.probabilityRerollDelay + && this.roundRobinHistorySize == other.roundRobinHistorySize + && Objects.equals(this.randomSettings, other.randomSettings) + && Arrays.equals((Object[])this.files, (Object[])other.files); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Float.hashCode(this.volume); + result = 31 * result + Float.hashCode(this.startDelay); + result = 31 * result + Boolean.hashCode(this.looping); + result = 31 * result + Integer.hashCode(this.probability); + result = 31 * result + Float.hashCode(this.probabilityRerollDelay); + result = 31 * result + Integer.hashCode(this.roundRobinHistorySize); + result = 31 * result + Objects.hashCode(this.randomSettings); + return 31 * result + Arrays.hashCode((Object[])this.files); + } +} diff --git a/src/com/hypixel/hytale/protocol/SoundEventLayerRandomSettings.java b/src/com/hypixel/hytale/protocol/SoundEventLayerRandomSettings.java new file mode 100644 index 0000000..65fd95d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SoundEventLayerRandomSettings.java @@ -0,0 +1,99 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SoundEventLayerRandomSettings { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 20; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 20; + public static final int MAX_SIZE = 20; + public float minVolume; + public float maxVolume; + public float minPitch; + public float maxPitch; + public float maxStartOffset; + + public SoundEventLayerRandomSettings() { + } + + public SoundEventLayerRandomSettings(float minVolume, float maxVolume, float minPitch, float maxPitch, float maxStartOffset) { + this.minVolume = minVolume; + this.maxVolume = maxVolume; + this.minPitch = minPitch; + this.maxPitch = maxPitch; + this.maxStartOffset = maxStartOffset; + } + + public SoundEventLayerRandomSettings(@Nonnull SoundEventLayerRandomSettings other) { + this.minVolume = other.minVolume; + this.maxVolume = other.maxVolume; + this.minPitch = other.minPitch; + this.maxPitch = other.maxPitch; + this.maxStartOffset = other.maxStartOffset; + } + + @Nonnull + public static SoundEventLayerRandomSettings deserialize(@Nonnull ByteBuf buf, int offset) { + SoundEventLayerRandomSettings obj = new SoundEventLayerRandomSettings(); + obj.minVolume = buf.getFloatLE(offset + 0); + obj.maxVolume = buf.getFloatLE(offset + 4); + obj.minPitch = buf.getFloatLE(offset + 8); + obj.maxPitch = buf.getFloatLE(offset + 12); + obj.maxStartOffset = buf.getFloatLE(offset + 16); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 20; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.minVolume); + buf.writeFloatLE(this.maxVolume); + buf.writeFloatLE(this.minPitch); + buf.writeFloatLE(this.maxPitch); + buf.writeFloatLE(this.maxStartOffset); + } + + public int computeSize() { + return 20; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 20 ? ValidationResult.error("Buffer too small: expected at least 20 bytes") : ValidationResult.OK; + } + + public SoundEventLayerRandomSettings clone() { + SoundEventLayerRandomSettings copy = new SoundEventLayerRandomSettings(); + copy.minVolume = this.minVolume; + copy.maxVolume = this.maxVolume; + copy.minPitch = this.minPitch; + copy.maxPitch = this.maxPitch; + copy.maxStartOffset = this.maxStartOffset; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SoundEventLayerRandomSettings other) + ? false + : this.minVolume == other.minVolume + && this.maxVolume == other.maxVolume + && this.minPitch == other.minPitch + && this.maxPitch == other.maxPitch + && this.maxStartOffset == other.maxStartOffset; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.minVolume, this.maxVolume, this.minPitch, this.maxPitch, this.maxStartOffset); + } +} diff --git a/src/com/hypixel/hytale/protocol/SoundSet.java b/src/com/hypixel/hytale/protocol/SoundSet.java new file mode 100644 index 0000000..6846c6f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SoundSet.java @@ -0,0 +1,299 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SoundSet { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 10; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + @Nullable + public Map sounds; + @Nonnull + public SoundCategory category = SoundCategory.Music; + + public SoundSet() { + } + + public SoundSet(@Nullable String id, @Nullable Map sounds, @Nonnull SoundCategory category) { + this.id = id; + this.sounds = sounds; + this.category = category; + } + + public SoundSet(@Nonnull SoundSet other) { + this.id = other.id; + this.sounds = other.sounds; + this.category = other.category; + } + + @Nonnull + public static SoundSet deserialize(@Nonnull ByteBuf buf, int offset) { + SoundSet obj = new SoundSet(); + byte nullBits = buf.getByte(offset); + obj.category = SoundCategory.fromValue(buf.getByte(offset + 1)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 10 + buf.getIntLE(offset + 2); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 10 + buf.getIntLE(offset + 6); + int soundsCount = VarInt.peek(buf, varPos1); + if (soundsCount < 0) { + throw ProtocolException.negativeLength("Sounds", soundsCount); + } + + if (soundsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Sounds", soundsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.sounds = new HashMap<>(soundsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < soundsCount; i++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + int val = buf.getIntLE(dictPos); + dictPos += 4; + if (obj.sounds.put(key, val) != null) { + throw ProtocolException.duplicateKey("sounds", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 10; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 2); + int pos0 = offset + 10 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 6); + int pos1 = offset + 10 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + pos1 += 4; + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.sounds != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.category.getValue()); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int soundsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.sounds != null) { + buf.setIntLE(soundsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.sounds.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Sounds", this.sounds.size(), 4096000); + } + + VarInt.write(buf, this.sounds.size()); + + for (Entry e : this.sounds.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + buf.writeIntLE(e.getValue()); + } + } else { + buf.setIntLE(soundsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 10; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.sounds != null) { + int soundsSize = 0; + + for (Entry kvp : this.sounds.entrySet()) { + soundsSize += PacketIO.stringSize(kvp.getKey()) + 4; + } + + size += VarInt.size(this.sounds.size()) + soundsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 10) { + return ValidationResult.error("Buffer too small: expected at least 10 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 2); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 10 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int soundsOffset = buffer.getIntLE(offset + 6); + if (soundsOffset < 0) { + return ValidationResult.error("Invalid offset for Sounds"); + } + + int posx = offset + 10 + soundsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Sounds"); + } + + int soundsCount = VarInt.peek(buffer, posx); + if (soundsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Sounds"); + } + + if (soundsCount > 4096000) { + return ValidationResult.error("Sounds exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < soundsCount; i++) { + int keyLen = VarInt.peek(buffer, posx); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += keyLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posx += 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public SoundSet clone() { + SoundSet copy = new SoundSet(); + copy.id = this.id; + copy.sounds = this.sounds != null ? new HashMap<>(this.sounds) : null; + copy.category = this.category; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SoundSet other) + ? false + : Objects.equals(this.id, other.id) && Objects.equals(this.sounds, other.sounds) && Objects.equals(this.category, other.category); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.sounds, this.category); + } +} diff --git a/src/com/hypixel/hytale/protocol/SpawnDeployableFromRaycastInteraction.java b/src/com/hypixel/hytale/protocol/SpawnDeployableFromRaycastInteraction.java new file mode 100644 index 0000000..6ab6fe0 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SpawnDeployableFromRaycastInteraction.java @@ -0,0 +1,682 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SpawnDeployableFromRaycastInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 23; + public static final int VARIABLE_FIELD_COUNT = 7; + public static final int VARIABLE_BLOCK_START = 51; + public static final int MAX_SIZE = 1677721600; + @Nullable + public DeployableConfig deployableConfig; + public float maxDistance; + @Nullable + public Map costs; + + public SpawnDeployableFromRaycastInteraction() { + } + + public SpawnDeployableFromRaycastInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + @Nullable DeployableConfig deployableConfig, + float maxDistance, + @Nullable Map costs + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.deployableConfig = deployableConfig; + this.maxDistance = maxDistance; + this.costs = costs; + } + + public SpawnDeployableFromRaycastInteraction(@Nonnull SpawnDeployableFromRaycastInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.deployableConfig = other.deployableConfig; + this.maxDistance = other.maxDistance; + this.costs = other.costs; + } + + @Nonnull + public static SpawnDeployableFromRaycastInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + SpawnDeployableFromRaycastInteraction obj = new SpawnDeployableFromRaycastInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.maxDistance = buf.getFloatLE(offset + 19); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 51 + buf.getIntLE(offset + 23); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 51 + buf.getIntLE(offset + 27); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 51 + buf.getIntLE(offset + 31); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 51 + buf.getIntLE(offset + 35); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 51 + buf.getIntLE(offset + 39); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 51 + buf.getIntLE(offset + 43); + obj.deployableConfig = DeployableConfig.deserialize(buf, varPos5); + } + + if ((nullBits & 64) != 0) { + int varPos6 = offset + 51 + buf.getIntLE(offset + 47); + int costsCount = VarInt.peek(buf, varPos6); + if (costsCount < 0) { + throw ProtocolException.negativeLength("Costs", costsCount); + } + + if (costsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Costs", costsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos6); + obj.costs = new HashMap<>(costsCount); + int dictPos = varPos6 + varIntLen; + + for (int ix = 0; ix < costsCount; ix++) { + int key = buf.getIntLE(dictPos); + dictPos += 4; + float val = buf.getFloatLE(dictPos); + dictPos += 4; + if (obj.costs.put(key, val) != null) { + throw ProtocolException.duplicateKey("costs", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 51; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 23); + int pos0 = offset + 51 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 27); + int pos1 = offset + 51 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 31); + int pos2 = offset + 51 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 35); + int pos3 = offset + 51 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 39); + int pos4 = offset + 51 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 43); + int pos5 = offset + 51 + fieldOffset5; + pos5 += DeployableConfig.computeBytesConsumed(buf, pos5); + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits & 64) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 47); + int pos6 = offset + 51 + fieldOffset6; + int dictLen = VarInt.peek(buf, pos6); + pos6 += VarInt.length(buf, pos6); + + for (int i = 0; i < dictLen; i++) { + pos6 += 4; + pos6 += 4; + } + + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.deployableConfig != null) { + nullBits = (byte)(nullBits | 32); + } + + if (this.costs != null) { + nullBits = (byte)(nullBits | 64); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeFloatLE(this.maxDistance); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int deployableConfigOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int costsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.deployableConfig != null) { + buf.setIntLE(deployableConfigOffsetSlot, buf.writerIndex() - varBlockStart); + this.deployableConfig.serialize(buf); + } else { + buf.setIntLE(deployableConfigOffsetSlot, -1); + } + + if (this.costs != null) { + buf.setIntLE(costsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.costs.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Costs", this.costs.size(), 4096000); + } + + VarInt.write(buf, this.costs.size()); + + for (Entry e : this.costs.entrySet()) { + buf.writeIntLE(e.getKey()); + buf.writeFloatLE(e.getValue()); + } + } else { + buf.setIntLE(costsOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 51; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.deployableConfig != null) { + size += this.deployableConfig.computeSize(); + } + + if (this.costs != null) { + size += VarInt.size(this.costs.size()) + this.costs.size() * 8; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 51) { + return ValidationResult.error("Buffer too small: expected at least 51 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 23); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 51 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 27); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 51 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 31); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 51 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 35); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 51 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 39); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 51 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int deployableConfigOffset = buffer.getIntLE(offset + 43); + if (deployableConfigOffset < 0) { + return ValidationResult.error("Invalid offset for DeployableConfig"); + } + + int posxxxxx = offset + 51 + deployableConfigOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for DeployableConfig"); + } + + ValidationResult deployableConfigResult = DeployableConfig.validateStructure(buffer, posxxxxx); + if (!deployableConfigResult.isValid()) { + return ValidationResult.error("Invalid DeployableConfig: " + deployableConfigResult.error()); + } + + posxxxxx += DeployableConfig.computeBytesConsumed(buffer, posxxxxx); + } + + if ((nullBits & 64) != 0) { + int costsOffset = buffer.getIntLE(offset + 47); + if (costsOffset < 0) { + return ValidationResult.error("Invalid offset for Costs"); + } + + int posxxxxxx = offset + 51 + costsOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Costs"); + } + + int costsCount = VarInt.peek(buffer, posxxxxxx); + if (costsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Costs"); + } + + if (costsCount > 4096000) { + return ValidationResult.error("Costs exceeds max length 4096000"); + } + + posxxxxxx += VarInt.length(buffer, posxxxxxx); + + for (int i = 0; i < costsCount; i++) { + posxxxxxx += 4; + if (posxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxx += 4; + if (posxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public SpawnDeployableFromRaycastInteraction clone() { + SpawnDeployableFromRaycastInteraction copy = new SpawnDeployableFromRaycastInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.deployableConfig = this.deployableConfig != null ? this.deployableConfig.clone() : null; + copy.maxDistance = this.maxDistance; + copy.costs = this.costs != null ? new HashMap<>(this.costs) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SpawnDeployableFromRaycastInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && Objects.equals(this.deployableConfig, other.deployableConfig) + && this.maxDistance == other.maxDistance + && Objects.equals(this.costs, other.costs); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Objects.hashCode(this.deployableConfig); + result = 31 * result + Float.hashCode(this.maxDistance); + return 31 * result + Objects.hashCode(this.costs); + } +} diff --git a/src/com/hypixel/hytale/protocol/StabSelector.java b/src/com/hypixel/hytale/protocol/StabSelector.java new file mode 100644 index 0000000..090b2c2 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/StabSelector.java @@ -0,0 +1,160 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class StabSelector extends Selector { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 37; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 37; + public static final int MAX_SIZE = 37; + public float extendTop; + public float extendBottom; + public float extendLeft; + public float extendRight; + public float yawOffset; + public float pitchOffset; + public float rollOffset; + public float startDistance; + public float endDistance; + public boolean testLineOfSight; + + public StabSelector() { + } + + public StabSelector( + float extendTop, + float extendBottom, + float extendLeft, + float extendRight, + float yawOffset, + float pitchOffset, + float rollOffset, + float startDistance, + float endDistance, + boolean testLineOfSight + ) { + this.extendTop = extendTop; + this.extendBottom = extendBottom; + this.extendLeft = extendLeft; + this.extendRight = extendRight; + this.yawOffset = yawOffset; + this.pitchOffset = pitchOffset; + this.rollOffset = rollOffset; + this.startDistance = startDistance; + this.endDistance = endDistance; + this.testLineOfSight = testLineOfSight; + } + + public StabSelector(@Nonnull StabSelector other) { + this.extendTop = other.extendTop; + this.extendBottom = other.extendBottom; + this.extendLeft = other.extendLeft; + this.extendRight = other.extendRight; + this.yawOffset = other.yawOffset; + this.pitchOffset = other.pitchOffset; + this.rollOffset = other.rollOffset; + this.startDistance = other.startDistance; + this.endDistance = other.endDistance; + this.testLineOfSight = other.testLineOfSight; + } + + @Nonnull + public static StabSelector deserialize(@Nonnull ByteBuf buf, int offset) { + StabSelector obj = new StabSelector(); + obj.extendTop = buf.getFloatLE(offset + 0); + obj.extendBottom = buf.getFloatLE(offset + 4); + obj.extendLeft = buf.getFloatLE(offset + 8); + obj.extendRight = buf.getFloatLE(offset + 12); + obj.yawOffset = buf.getFloatLE(offset + 16); + obj.pitchOffset = buf.getFloatLE(offset + 20); + obj.rollOffset = buf.getFloatLE(offset + 24); + obj.startDistance = buf.getFloatLE(offset + 28); + obj.endDistance = buf.getFloatLE(offset + 32); + obj.testLineOfSight = buf.getByte(offset + 36) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 37; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + buf.writeFloatLE(this.extendTop); + buf.writeFloatLE(this.extendBottom); + buf.writeFloatLE(this.extendLeft); + buf.writeFloatLE(this.extendRight); + buf.writeFloatLE(this.yawOffset); + buf.writeFloatLE(this.pitchOffset); + buf.writeFloatLE(this.rollOffset); + buf.writeFloatLE(this.startDistance); + buf.writeFloatLE(this.endDistance); + buf.writeByte(this.testLineOfSight ? 1 : 0); + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + return 37; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 37 ? ValidationResult.error("Buffer too small: expected at least 37 bytes") : ValidationResult.OK; + } + + public StabSelector clone() { + StabSelector copy = new StabSelector(); + copy.extendTop = this.extendTop; + copy.extendBottom = this.extendBottom; + copy.extendLeft = this.extendLeft; + copy.extendRight = this.extendRight; + copy.yawOffset = this.yawOffset; + copy.pitchOffset = this.pitchOffset; + copy.rollOffset = this.rollOffset; + copy.startDistance = this.startDistance; + copy.endDistance = this.endDistance; + copy.testLineOfSight = this.testLineOfSight; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof StabSelector other) + ? false + : this.extendTop == other.extendTop + && this.extendBottom == other.extendBottom + && this.extendLeft == other.extendLeft + && this.extendRight == other.extendRight + && this.yawOffset == other.yawOffset + && this.pitchOffset == other.pitchOffset + && this.rollOffset == other.rollOffset + && this.startDistance == other.startDistance + && this.endDistance == other.endDistance + && this.testLineOfSight == other.testLineOfSight; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.extendTop, + this.extendBottom, + this.extendLeft, + this.extendRight, + this.yawOffset, + this.pitchOffset, + this.rollOffset, + this.startDistance, + this.endDistance, + this.testLineOfSight + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/StairConnectedBlockRuleSet.java b/src/com/hypixel/hytale/protocol/StairConnectedBlockRuleSet.java new file mode 100644 index 0000000..20ff4d6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/StairConnectedBlockRuleSet.java @@ -0,0 +1,184 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StairConnectedBlockRuleSet { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 21; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 21; + public static final int MAX_SIZE = 16384026; + public int straightBlockId; + public int cornerLeftBlockId; + public int cornerRightBlockId; + public int invertedCornerLeftBlockId; + public int invertedCornerRightBlockId; + @Nullable + public String materialName; + + public StairConnectedBlockRuleSet() { + } + + public StairConnectedBlockRuleSet( + int straightBlockId, + int cornerLeftBlockId, + int cornerRightBlockId, + int invertedCornerLeftBlockId, + int invertedCornerRightBlockId, + @Nullable String materialName + ) { + this.straightBlockId = straightBlockId; + this.cornerLeftBlockId = cornerLeftBlockId; + this.cornerRightBlockId = cornerRightBlockId; + this.invertedCornerLeftBlockId = invertedCornerLeftBlockId; + this.invertedCornerRightBlockId = invertedCornerRightBlockId; + this.materialName = materialName; + } + + public StairConnectedBlockRuleSet(@Nonnull StairConnectedBlockRuleSet other) { + this.straightBlockId = other.straightBlockId; + this.cornerLeftBlockId = other.cornerLeftBlockId; + this.cornerRightBlockId = other.cornerRightBlockId; + this.invertedCornerLeftBlockId = other.invertedCornerLeftBlockId; + this.invertedCornerRightBlockId = other.invertedCornerRightBlockId; + this.materialName = other.materialName; + } + + @Nonnull + public static StairConnectedBlockRuleSet deserialize(@Nonnull ByteBuf buf, int offset) { + StairConnectedBlockRuleSet obj = new StairConnectedBlockRuleSet(); + byte nullBits = buf.getByte(offset); + obj.straightBlockId = buf.getIntLE(offset + 1); + obj.cornerLeftBlockId = buf.getIntLE(offset + 5); + obj.cornerRightBlockId = buf.getIntLE(offset + 9); + obj.invertedCornerLeftBlockId = buf.getIntLE(offset + 13); + obj.invertedCornerRightBlockId = buf.getIntLE(offset + 17); + int pos = offset + 21; + if ((nullBits & 1) != 0) { + int materialNameLen = VarInt.peek(buf, pos); + if (materialNameLen < 0) { + throw ProtocolException.negativeLength("MaterialName", materialNameLen); + } + + if (materialNameLen > 4096000) { + throw ProtocolException.stringTooLong("MaterialName", materialNameLen, 4096000); + } + + int materialNameVarLen = VarInt.length(buf, pos); + obj.materialName = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += materialNameVarLen + materialNameLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 21; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.materialName != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.straightBlockId); + buf.writeIntLE(this.cornerLeftBlockId); + buf.writeIntLE(this.cornerRightBlockId); + buf.writeIntLE(this.invertedCornerLeftBlockId); + buf.writeIntLE(this.invertedCornerRightBlockId); + if (this.materialName != null) { + PacketIO.writeVarString(buf, this.materialName, 4096000); + } + } + + public int computeSize() { + int size = 21; + if (this.materialName != null) { + size += PacketIO.stringSize(this.materialName); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 21) { + return ValidationResult.error("Buffer too small: expected at least 21 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 21; + if ((nullBits & 1) != 0) { + int materialNameLen = VarInt.peek(buffer, pos); + if (materialNameLen < 0) { + return ValidationResult.error("Invalid string length for MaterialName"); + } + + if (materialNameLen > 4096000) { + return ValidationResult.error("MaterialName exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += materialNameLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading MaterialName"); + } + } + + return ValidationResult.OK; + } + } + + public StairConnectedBlockRuleSet clone() { + StairConnectedBlockRuleSet copy = new StairConnectedBlockRuleSet(); + copy.straightBlockId = this.straightBlockId; + copy.cornerLeftBlockId = this.cornerLeftBlockId; + copy.cornerRightBlockId = this.cornerRightBlockId; + copy.invertedCornerLeftBlockId = this.invertedCornerLeftBlockId; + copy.invertedCornerRightBlockId = this.invertedCornerRightBlockId; + copy.materialName = this.materialName; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof StairConnectedBlockRuleSet other) + ? false + : this.straightBlockId == other.straightBlockId + && this.cornerLeftBlockId == other.cornerLeftBlockId + && this.cornerRightBlockId == other.cornerRightBlockId + && this.invertedCornerLeftBlockId == other.invertedCornerLeftBlockId + && this.invertedCornerRightBlockId == other.invertedCornerRightBlockId + && Objects.equals(this.materialName, other.materialName); + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.straightBlockId, + this.cornerLeftBlockId, + this.cornerRightBlockId, + this.invertedCornerLeftBlockId, + this.invertedCornerRightBlockId, + this.materialName + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/StatsConditionInteraction.java b/src/com/hypixel/hytale/protocol/StatsConditionInteraction.java new file mode 100644 index 0000000..a69cbc8 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/StatsConditionInteraction.java @@ -0,0 +1,643 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StatsConditionInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 22; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 46; + public static final int MAX_SIZE = 1677721600; + @Nullable + public Map costs; + public boolean lessThan; + public boolean lenient; + @Nonnull + public ValueType valueType = ValueType.Percent; + + public StatsConditionInteraction() { + } + + public StatsConditionInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + @Nullable Map costs, + boolean lessThan, + boolean lenient, + @Nonnull ValueType valueType + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.costs = costs; + this.lessThan = lessThan; + this.lenient = lenient; + this.valueType = valueType; + } + + public StatsConditionInteraction(@Nonnull StatsConditionInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.costs = other.costs; + this.lessThan = other.lessThan; + this.lenient = other.lenient; + this.valueType = other.valueType; + } + + @Nonnull + public static StatsConditionInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + StatsConditionInteraction obj = new StatsConditionInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.lessThan = buf.getByte(offset + 19) != 0; + obj.lenient = buf.getByte(offset + 20) != 0; + obj.valueType = ValueType.fromValue(buf.getByte(offset + 21)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 46 + buf.getIntLE(offset + 22); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 46 + buf.getIntLE(offset + 26); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 46 + buf.getIntLE(offset + 30); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 46 + buf.getIntLE(offset + 34); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 46 + buf.getIntLE(offset + 38); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 46 + buf.getIntLE(offset + 42); + int costsCount = VarInt.peek(buf, varPos5); + if (costsCount < 0) { + throw ProtocolException.negativeLength("Costs", costsCount); + } + + if (costsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Costs", costsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + obj.costs = new HashMap<>(costsCount); + int dictPos = varPos5 + varIntLen; + + for (int ix = 0; ix < costsCount; ix++) { + int key = buf.getIntLE(dictPos); + dictPos += 4; + float val = buf.getFloatLE(dictPos); + dictPos += 4; + if (obj.costs.put(key, val) != null) { + throw ProtocolException.duplicateKey("costs", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 46; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 22); + int pos0 = offset + 46 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 26); + int pos1 = offset + 46 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 30); + int pos2 = offset + 46 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 34); + int pos3 = offset + 46 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 38); + int pos4 = offset + 46 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 42); + int pos5 = offset + 46 + fieldOffset5; + int dictLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5); + + for (int i = 0; i < dictLen; i++) { + pos5 += 4; + pos5 += 4; + } + + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.costs != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeByte(this.lessThan ? 1 : 0); + buf.writeByte(this.lenient ? 1 : 0); + buf.writeByte(this.valueType.getValue()); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int costsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.costs != null) { + buf.setIntLE(costsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.costs.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Costs", this.costs.size(), 4096000); + } + + VarInt.write(buf, this.costs.size()); + + for (Entry e : this.costs.entrySet()) { + buf.writeIntLE(e.getKey()); + buf.writeFloatLE(e.getValue()); + } + } else { + buf.setIntLE(costsOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 46; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.costs != null) { + size += VarInt.size(this.costs.size()) + this.costs.size() * 8; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 46) { + return ValidationResult.error("Buffer too small: expected at least 46 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 22); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 46 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 26); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 46 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 30); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 46 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 34); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 46 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 38); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 46 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int costsOffset = buffer.getIntLE(offset + 42); + if (costsOffset < 0) { + return ValidationResult.error("Invalid offset for Costs"); + } + + int posxxxxx = offset + 46 + costsOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Costs"); + } + + int costsCount = VarInt.peek(buffer, posxxxxx); + if (costsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Costs"); + } + + if (costsCount > 4096000) { + return ValidationResult.error("Costs exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + + for (int i = 0; i < costsCount; i++) { + posxxxxx += 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxx += 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public StatsConditionInteraction clone() { + StatsConditionInteraction copy = new StatsConditionInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.costs = this.costs != null ? new HashMap<>(this.costs) : null; + copy.lessThan = this.lessThan; + copy.lenient = this.lenient; + copy.valueType = this.valueType; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof StatsConditionInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && Objects.equals(this.costs, other.costs) + && this.lessThan == other.lessThan + && this.lenient == other.lenient + && Objects.equals(this.valueType, other.valueType); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Objects.hashCode(this.costs); + result = 31 * result + Boolean.hashCode(this.lessThan); + result = 31 * result + Boolean.hashCode(this.lenient); + return 31 * result + Objects.hashCode(this.valueType); + } +} diff --git a/src/com/hypixel/hytale/protocol/StringParamValue.java b/src/com/hypixel/hytale/protocol/StringParamValue.java new file mode 100644 index 0000000..dc99da0 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/StringParamValue.java @@ -0,0 +1,138 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StringParamValue extends ParamValue { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 16384006; + @Nullable + public String value; + + public StringParamValue() { + } + + public StringParamValue(@Nullable String value) { + this.value = value; + } + + public StringParamValue(@Nonnull StringParamValue other) { + this.value = other.value; + } + + @Nonnull + public static StringParamValue deserialize(@Nonnull ByteBuf buf, int offset) { + StringParamValue obj = new StringParamValue(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int valueLen = VarInt.peek(buf, pos); + if (valueLen < 0) { + throw ProtocolException.negativeLength("Value", valueLen); + } + + if (valueLen > 4096000) { + throw ProtocolException.stringTooLong("Value", valueLen, 4096000); + } + + int valueVarLen = VarInt.length(buf, pos); + obj.value = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += valueVarLen + valueLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.value != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.value != null) { + PacketIO.writeVarString(buf, this.value, 4096000); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 1; + if (this.value != null) { + size += PacketIO.stringSize(this.value); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int valueLen = VarInt.peek(buffer, pos); + if (valueLen < 0) { + return ValidationResult.error("Invalid string length for Value"); + } + + if (valueLen > 4096000) { + return ValidationResult.error("Value exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += valueLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Value"); + } + } + + return ValidationResult.OK; + } + } + + public StringParamValue clone() { + StringParamValue copy = new StringParamValue(); + copy.value = this.value; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof StringParamValue other ? Objects.equals(this.value, other.value) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.value); + } +} diff --git a/src/com/hypixel/hytale/protocol/SupportMatch.java b/src/com/hypixel/hytale/protocol/SupportMatch.java new file mode 100644 index 0000000..2406909 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SupportMatch.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum SupportMatch { + Ignored(0), + Required(1), + Disallowed(2); + + public static final SupportMatch[] VALUES = values(); + private final int value; + + private SupportMatch(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static SupportMatch fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("SupportMatch", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/SwitchTo.java b/src/com/hypixel/hytale/protocol/SwitchTo.java new file mode 100644 index 0000000..b7ba4ec --- /dev/null +++ b/src/com/hypixel/hytale/protocol/SwitchTo.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum SwitchTo { + Disappear(0), + PostColor(1), + Distortion(2), + Transparency(3); + + public static final SwitchTo[] VALUES = values(); + private final int value; + + private SwitchTo(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static SwitchTo fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("SwitchTo", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/TagPattern.java b/src/com/hypixel/hytale/protocol/TagPattern.java new file mode 100644 index 0000000..9827988 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/TagPattern.java @@ -0,0 +1,264 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TagPattern { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 14; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public TagPatternType type = TagPatternType.Equals; + public int tagIndex; + @Nullable + public TagPattern[] operands; + @Nullable + public TagPattern not; + + public TagPattern() { + } + + public TagPattern(@Nonnull TagPatternType type, int tagIndex, @Nullable TagPattern[] operands, @Nullable TagPattern not) { + this.type = type; + this.tagIndex = tagIndex; + this.operands = operands; + this.not = not; + } + + public TagPattern(@Nonnull TagPattern other) { + this.type = other.type; + this.tagIndex = other.tagIndex; + this.operands = other.operands; + this.not = other.not; + } + + @Nonnull + public static TagPattern deserialize(@Nonnull ByteBuf buf, int offset) { + TagPattern obj = new TagPattern(); + byte nullBits = buf.getByte(offset); + obj.type = TagPatternType.fromValue(buf.getByte(offset + 1)); + obj.tagIndex = buf.getIntLE(offset + 2); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 14 + buf.getIntLE(offset + 6); + int operandsCount = VarInt.peek(buf, varPos0); + if (operandsCount < 0) { + throw ProtocolException.negativeLength("Operands", operandsCount); + } + + if (operandsCount > 4096000) { + throw ProtocolException.arrayTooLong("Operands", operandsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + operandsCount * 6L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Operands", varPos0 + varIntLen + operandsCount * 6, buf.readableBytes()); + } + + obj.operands = new TagPattern[operandsCount]; + int elemPos = varPos0 + varIntLen; + + for (int i = 0; i < operandsCount; i++) { + obj.operands[i] = deserialize(buf, elemPos); + elemPos += computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 14 + buf.getIntLE(offset + 10); + obj.not = deserialize(buf, varPos1); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 14; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 6); + int pos0 = offset + 14 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < arrLen; i++) { + pos0 += computeBytesConsumed(buf, pos0); + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 10); + int pos1 = offset + 14 + fieldOffset1; + pos1 += computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.operands != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.not != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.tagIndex); + int operandsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int notOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.operands != null) { + buf.setIntLE(operandsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.operands.length > 4096000) { + throw ProtocolException.arrayTooLong("Operands", this.operands.length, 4096000); + } + + VarInt.write(buf, this.operands.length); + + for (TagPattern item : this.operands) { + item.serialize(buf); + } + } else { + buf.setIntLE(operandsOffsetSlot, -1); + } + + if (this.not != null) { + buf.setIntLE(notOffsetSlot, buf.writerIndex() - varBlockStart); + this.not.serialize(buf); + } else { + buf.setIntLE(notOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 14; + if (this.operands != null) { + int operandsSize = 0; + + for (TagPattern elem : this.operands) { + operandsSize += elem.computeSize(); + } + + size += VarInt.size(this.operands.length) + operandsSize; + } + + if (this.not != null) { + size += this.not.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 14) { + return ValidationResult.error("Buffer too small: expected at least 14 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int operandsOffset = buffer.getIntLE(offset + 6); + if (operandsOffset < 0) { + return ValidationResult.error("Invalid offset for Operands"); + } + + int pos = offset + 14 + operandsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Operands"); + } + + int operandsCount = VarInt.peek(buffer, pos); + if (operandsCount < 0) { + return ValidationResult.error("Invalid array count for Operands"); + } + + if (operandsCount > 4096000) { + return ValidationResult.error("Operands exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < operandsCount; i++) { + ValidationResult structResult = validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid TagPattern in Operands[" + i + "]: " + structResult.error()); + } + + pos += computeBytesConsumed(buffer, pos); + } + } + + if ((nullBits & 2) != 0) { + int notOffset = buffer.getIntLE(offset + 10); + if (notOffset < 0) { + return ValidationResult.error("Invalid offset for Not"); + } + + int posx = offset + 14 + notOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Not"); + } + + ValidationResult notResult = validateStructure(buffer, posx); + if (!notResult.isValid()) { + return ValidationResult.error("Invalid Not: " + notResult.error()); + } + + posx += computeBytesConsumed(buffer, posx); + } + + return ValidationResult.OK; + } + } + + public TagPattern clone() { + TagPattern copy = new TagPattern(); + copy.type = this.type; + copy.tagIndex = this.tagIndex; + copy.operands = this.operands != null ? Arrays.stream(this.operands).map(e -> e.clone()).toArray(TagPattern[]::new) : null; + copy.not = this.not != null ? this.not.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof TagPattern other) + ? false + : Objects.equals(this.type, other.type) + && this.tagIndex == other.tagIndex + && Arrays.equals((Object[])this.operands, (Object[])other.operands) + && Objects.equals(this.not, other.not); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.type); + result = 31 * result + Integer.hashCode(this.tagIndex); + result = 31 * result + Arrays.hashCode((Object[])this.operands); + return 31 * result + Objects.hashCode(this.not); + } +} diff --git a/src/com/hypixel/hytale/protocol/TagPatternType.java b/src/com/hypixel/hytale/protocol/TagPatternType.java new file mode 100644 index 0000000..6d360e6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/TagPatternType.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum TagPatternType { + Equals(0), + And(1), + Or(2), + Not(3); + + public static final TagPatternType[] VALUES = values(); + private final int value; + + private TagPatternType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static TagPatternType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("TagPatternType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/TargetedDamage.java b/src/com/hypixel/hytale/protocol/TargetedDamage.java new file mode 100644 index 0000000..cda3da7 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/TargetedDamage.java @@ -0,0 +1,125 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TargetedDamage { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 1677721600; + public int index; + @Nullable + public DamageEffects damageEffects; + public int next; + + public TargetedDamage() { + } + + public TargetedDamage(int index, @Nullable DamageEffects damageEffects, int next) { + this.index = index; + this.damageEffects = damageEffects; + this.next = next; + } + + public TargetedDamage(@Nonnull TargetedDamage other) { + this.index = other.index; + this.damageEffects = other.damageEffects; + this.next = other.next; + } + + @Nonnull + public static TargetedDamage deserialize(@Nonnull ByteBuf buf, int offset) { + TargetedDamage obj = new TargetedDamage(); + byte nullBits = buf.getByte(offset); + obj.index = buf.getIntLE(offset + 1); + obj.next = buf.getIntLE(offset + 5); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + obj.damageEffects = DamageEffects.deserialize(buf, pos); + pos += DamageEffects.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + pos += DamageEffects.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.damageEffects != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.index); + buf.writeIntLE(this.next); + if (this.damageEffects != null) { + this.damageEffects.serialize(buf); + } + } + + public int computeSize() { + int size = 9; + if (this.damageEffects != null) { + size += this.damageEffects.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + ValidationResult damageEffectsResult = DamageEffects.validateStructure(buffer, pos); + if (!damageEffectsResult.isValid()) { + return ValidationResult.error("Invalid DamageEffects: " + damageEffectsResult.error()); + } + + pos += DamageEffects.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public TargetedDamage clone() { + TargetedDamage copy = new TargetedDamage(); + copy.index = this.index; + copy.damageEffects = this.damageEffects != null ? this.damageEffects.clone() : null; + copy.next = this.next; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof TargetedDamage other) + ? false + : this.index == other.index && Objects.equals(this.damageEffects, other.damageEffects) && this.next == other.next; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.index, this.damageEffects, this.next); + } +} diff --git a/src/com/hypixel/hytale/protocol/TeleportAck.java b/src/com/hypixel/hytale/protocol/TeleportAck.java new file mode 100644 index 0000000..9593ea4 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/TeleportAck.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class TeleportAck { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public byte teleportId; + + public TeleportAck() { + } + + public TeleportAck(byte teleportId) { + this.teleportId = teleportId; + } + + public TeleportAck(@Nonnull TeleportAck other) { + this.teleportId = other.teleportId; + } + + @Nonnull + public static TeleportAck deserialize(@Nonnull ByteBuf buf, int offset) { + TeleportAck obj = new TeleportAck(); + obj.teleportId = buf.getByte(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.teleportId); + } + + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public TeleportAck clone() { + TeleportAck copy = new TeleportAck(); + copy.teleportId = this.teleportId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof TeleportAck other ? this.teleportId == other.teleportId : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.teleportId); + } +} diff --git a/src/com/hypixel/hytale/protocol/Tint.java b/src/com/hypixel/hytale/protocol/Tint.java new file mode 100644 index 0000000..6bfc52d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Tint.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class Tint { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 24; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 24; + public static final int MAX_SIZE = 24; + public int top; + public int bottom; + public int front; + public int back; + public int left; + public int right; + + public Tint() { + } + + public Tint(int top, int bottom, int front, int back, int left, int right) { + this.top = top; + this.bottom = bottom; + this.front = front; + this.back = back; + this.left = left; + this.right = right; + } + + public Tint(@Nonnull Tint other) { + this.top = other.top; + this.bottom = other.bottom; + this.front = other.front; + this.back = other.back; + this.left = other.left; + this.right = other.right; + } + + @Nonnull + public static Tint deserialize(@Nonnull ByteBuf buf, int offset) { + Tint obj = new Tint(); + obj.top = buf.getIntLE(offset + 0); + obj.bottom = buf.getIntLE(offset + 4); + obj.front = buf.getIntLE(offset + 8); + obj.back = buf.getIntLE(offset + 12); + obj.left = buf.getIntLE(offset + 16); + obj.right = buf.getIntLE(offset + 20); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 24; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.top); + buf.writeIntLE(this.bottom); + buf.writeIntLE(this.front); + buf.writeIntLE(this.back); + buf.writeIntLE(this.left); + buf.writeIntLE(this.right); + } + + public int computeSize() { + return 24; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 24 ? ValidationResult.error("Buffer too small: expected at least 24 bytes") : ValidationResult.OK; + } + + public Tint clone() { + Tint copy = new Tint(); + copy.top = this.top; + copy.bottom = this.bottom; + copy.front = this.front; + copy.back = this.back; + copy.left = this.left; + copy.right = this.right; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Tint other) + ? false + : this.top == other.top + && this.bottom == other.bottom + && this.front == other.front + && this.back == other.back + && this.left == other.left + && this.right == other.right; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.top, this.bottom, this.front, this.back, this.left, this.right); + } +} diff --git a/src/com/hypixel/hytale/protocol/ToggleGliderInteraction.java b/src/com/hypixel/hytale/protocol/ToggleGliderInteraction.java new file mode 100644 index 0000000..5b8946d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ToggleGliderInteraction.java @@ -0,0 +1,504 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ToggleGliderInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 19; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 39; + public static final int MAX_SIZE = 1677721600; + + public ToggleGliderInteraction() { + } + + public ToggleGliderInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + } + + public ToggleGliderInteraction(@Nonnull ToggleGliderInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + } + + @Nonnull + public static ToggleGliderInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + ToggleGliderInteraction obj = new ToggleGliderInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 39 + buf.getIntLE(offset + 19); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 39 + buf.getIntLE(offset + 23); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 39 + buf.getIntLE(offset + 27); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 39 + buf.getIntLE(offset + 31); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 39 + buf.getIntLE(offset + 35); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 39; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 19); + int pos0 = offset + 39 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 23); + int pos1 = offset + 39 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 27); + int pos2 = offset + 39 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 31); + int pos3 = offset + 39 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 35); + int pos4 = offset + 39 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 39; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 39) { + return ValidationResult.error("Buffer too small: expected at least 39 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 19); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 39 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 23); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 39 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 27); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 39 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 31); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 39 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 35); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 39 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public ToggleGliderInteraction clone() { + ToggleGliderInteraction copy = new ToggleGliderInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ToggleGliderInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + return 31 * result + Integer.hashCode(this.failed); + } +} diff --git a/src/com/hypixel/hytale/protocol/Trail.java b/src/com/hypixel/hytale/protocol/Trail.java new file mode 100644 index 0000000..a60a804 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Trail.java @@ -0,0 +1,399 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Trail { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 61; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 69; + public static final int MAX_SIZE = 32768079; + @Nullable + public String id; + @Nullable + public String texture; + public int lifeSpan; + public float roll; + @Nullable + public Edge start; + @Nullable + public Edge end; + public float lightInfluence; + @Nonnull + public FXRenderMode renderMode = FXRenderMode.BlendLinear; + @Nullable + public IntersectionHighlight intersectionHighlight; + public boolean smooth; + @Nullable + public Vector2i frameSize; + @Nullable + public Range frameRange; + public int frameLifeSpan; + + public Trail() { + } + + public Trail( + @Nullable String id, + @Nullable String texture, + int lifeSpan, + float roll, + @Nullable Edge start, + @Nullable Edge end, + float lightInfluence, + @Nonnull FXRenderMode renderMode, + @Nullable IntersectionHighlight intersectionHighlight, + boolean smooth, + @Nullable Vector2i frameSize, + @Nullable Range frameRange, + int frameLifeSpan + ) { + this.id = id; + this.texture = texture; + this.lifeSpan = lifeSpan; + this.roll = roll; + this.start = start; + this.end = end; + this.lightInfluence = lightInfluence; + this.renderMode = renderMode; + this.intersectionHighlight = intersectionHighlight; + this.smooth = smooth; + this.frameSize = frameSize; + this.frameRange = frameRange; + this.frameLifeSpan = frameLifeSpan; + } + + public Trail(@Nonnull Trail other) { + this.id = other.id; + this.texture = other.texture; + this.lifeSpan = other.lifeSpan; + this.roll = other.roll; + this.start = other.start; + this.end = other.end; + this.lightInfluence = other.lightInfluence; + this.renderMode = other.renderMode; + this.intersectionHighlight = other.intersectionHighlight; + this.smooth = other.smooth; + this.frameSize = other.frameSize; + this.frameRange = other.frameRange; + this.frameLifeSpan = other.frameLifeSpan; + } + + @Nonnull + public static Trail deserialize(@Nonnull ByteBuf buf, int offset) { + Trail obj = new Trail(); + byte nullBits = buf.getByte(offset); + obj.lifeSpan = buf.getIntLE(offset + 1); + obj.roll = buf.getFloatLE(offset + 5); + if ((nullBits & 4) != 0) { + obj.start = Edge.deserialize(buf, offset + 9); + } + + if ((nullBits & 8) != 0) { + obj.end = Edge.deserialize(buf, offset + 18); + } + + obj.lightInfluence = buf.getFloatLE(offset + 27); + obj.renderMode = FXRenderMode.fromValue(buf.getByte(offset + 31)); + if ((nullBits & 16) != 0) { + obj.intersectionHighlight = IntersectionHighlight.deserialize(buf, offset + 32); + } + + obj.smooth = buf.getByte(offset + 40) != 0; + if ((nullBits & 32) != 0) { + obj.frameSize = Vector2i.deserialize(buf, offset + 41); + } + + if ((nullBits & 64) != 0) { + obj.frameRange = Range.deserialize(buf, offset + 49); + } + + obj.frameLifeSpan = buf.getIntLE(offset + 57); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 69 + buf.getIntLE(offset + 61); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 69 + buf.getIntLE(offset + 65); + int textureLen = VarInt.peek(buf, varPos1); + if (textureLen < 0) { + throw ProtocolException.negativeLength("Texture", textureLen); + } + + if (textureLen > 4096000) { + throw ProtocolException.stringTooLong("Texture", textureLen, 4096000); + } + + obj.texture = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 69; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 61); + int pos0 = offset + 69 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 65); + int pos1 = offset + 69 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.texture != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.start != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.end != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.intersectionHighlight != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.frameSize != null) { + nullBits = (byte)(nullBits | 32); + } + + if (this.frameRange != null) { + nullBits = (byte)(nullBits | 64); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.lifeSpan); + buf.writeFloatLE(this.roll); + if (this.start != null) { + this.start.serialize(buf); + } else { + buf.writeZero(9); + } + + if (this.end != null) { + this.end.serialize(buf); + } else { + buf.writeZero(9); + } + + buf.writeFloatLE(this.lightInfluence); + buf.writeByte(this.renderMode.getValue()); + if (this.intersectionHighlight != null) { + this.intersectionHighlight.serialize(buf); + } else { + buf.writeZero(8); + } + + buf.writeByte(this.smooth ? 1 : 0); + if (this.frameSize != null) { + this.frameSize.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.frameRange != null) { + this.frameRange.serialize(buf); + } else { + buf.writeZero(8); + } + + buf.writeIntLE(this.frameLifeSpan); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int textureOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.texture != null) { + buf.setIntLE(textureOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.texture, 4096000); + } else { + buf.setIntLE(textureOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 69; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.texture != null) { + size += PacketIO.stringSize(this.texture); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 69) { + return ValidationResult.error("Buffer too small: expected at least 69 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 61); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 69 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int textureOffset = buffer.getIntLE(offset + 65); + if (textureOffset < 0) { + return ValidationResult.error("Invalid offset for Texture"); + } + + int posx = offset + 69 + textureOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Texture"); + } + + int textureLen = VarInt.peek(buffer, posx); + if (textureLen < 0) { + return ValidationResult.error("Invalid string length for Texture"); + } + + if (textureLen > 4096000) { + return ValidationResult.error("Texture exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += textureLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Texture"); + } + } + + return ValidationResult.OK; + } + } + + public Trail clone() { + Trail copy = new Trail(); + copy.id = this.id; + copy.texture = this.texture; + copy.lifeSpan = this.lifeSpan; + copy.roll = this.roll; + copy.start = this.start != null ? this.start.clone() : null; + copy.end = this.end != null ? this.end.clone() : null; + copy.lightInfluence = this.lightInfluence; + copy.renderMode = this.renderMode; + copy.intersectionHighlight = this.intersectionHighlight != null ? this.intersectionHighlight.clone() : null; + copy.smooth = this.smooth; + copy.frameSize = this.frameSize != null ? this.frameSize.clone() : null; + copy.frameRange = this.frameRange != null ? this.frameRange.clone() : null; + copy.frameLifeSpan = this.frameLifeSpan; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Trail other) + ? false + : Objects.equals(this.id, other.id) + && Objects.equals(this.texture, other.texture) + && this.lifeSpan == other.lifeSpan + && this.roll == other.roll + && Objects.equals(this.start, other.start) + && Objects.equals(this.end, other.end) + && this.lightInfluence == other.lightInfluence + && Objects.equals(this.renderMode, other.renderMode) + && Objects.equals(this.intersectionHighlight, other.intersectionHighlight) + && this.smooth == other.smooth + && Objects.equals(this.frameSize, other.frameSize) + && Objects.equals(this.frameRange, other.frameRange) + && this.frameLifeSpan == other.frameLifeSpan; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.id, + this.texture, + this.lifeSpan, + this.roll, + this.start, + this.end, + this.lightInfluence, + this.renderMode, + this.intersectionHighlight, + this.smooth, + this.frameSize, + this.frameRange, + this.frameLifeSpan + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/Transform.java b/src/com/hypixel/hytale/protocol/Transform.java new file mode 100644 index 0000000..e593786 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Transform.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Transform { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 37; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 37; + public static final int MAX_SIZE = 37; + @Nullable + public Position position; + @Nullable + public Direction orientation; + + public Transform() { + } + + public Transform(@Nullable Position position, @Nullable Direction orientation) { + this.position = position; + this.orientation = orientation; + } + + public Transform(@Nonnull Transform other) { + this.position = other.position; + this.orientation = other.orientation; + } + + @Nonnull + public static Transform deserialize(@Nonnull ByteBuf buf, int offset) { + Transform obj = new Transform(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.position = Position.deserialize(buf, offset + 1); + } + + if ((nullBits & 2) != 0) { + obj.orientation = Direction.deserialize(buf, offset + 25); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 37; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.position != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.orientation != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + if (this.position != null) { + this.position.serialize(buf); + } else { + buf.writeZero(24); + } + + if (this.orientation != null) { + this.orientation.serialize(buf); + } else { + buf.writeZero(12); + } + } + + public int computeSize() { + return 37; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 37 ? ValidationResult.error("Buffer too small: expected at least 37 bytes") : ValidationResult.OK; + } + + public Transform clone() { + Transform copy = new Transform(); + copy.position = this.position != null ? this.position.clone() : null; + copy.orientation = this.orientation != null ? this.orientation.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Transform other) + ? false + : Objects.equals(this.position, other.position) && Objects.equals(this.orientation, other.orientation); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.position, this.orientation); + } +} diff --git a/src/com/hypixel/hytale/protocol/TriggerCooldownInteraction.java b/src/com/hypixel/hytale/protocol/TriggerCooldownInteraction.java new file mode 100644 index 0000000..2fe3287 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/TriggerCooldownInteraction.java @@ -0,0 +1,562 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TriggerCooldownInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 19; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 43; + public static final int MAX_SIZE = 1677721600; + @Nullable + public InteractionCooldown cooldown; + + public TriggerCooldownInteraction() { + } + + public TriggerCooldownInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + @Nullable InteractionCooldown cooldown + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.cooldown = cooldown; + } + + public TriggerCooldownInteraction(@Nonnull TriggerCooldownInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.cooldown = other.cooldown; + } + + @Nonnull + public static TriggerCooldownInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + TriggerCooldownInteraction obj = new TriggerCooldownInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 43 + buf.getIntLE(offset + 19); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 43 + buf.getIntLE(offset + 23); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 43 + buf.getIntLE(offset + 27); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 43 + buf.getIntLE(offset + 31); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 43 + buf.getIntLE(offset + 35); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 43 + buf.getIntLE(offset + 39); + obj.cooldown = InteractionCooldown.deserialize(buf, varPos5); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 43; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 19); + int pos0 = offset + 43 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 23); + int pos1 = offset + 43 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 27); + int pos2 = offset + 43 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 31); + int pos3 = offset + 43 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 35); + int pos4 = offset + 43 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 39); + int pos5 = offset + 43 + fieldOffset5; + pos5 += InteractionCooldown.computeBytesConsumed(buf, pos5); + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.cooldown != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cooldownOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.cooldown != null) { + buf.setIntLE(cooldownOffsetSlot, buf.writerIndex() - varBlockStart); + this.cooldown.serialize(buf); + } else { + buf.setIntLE(cooldownOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 43; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.cooldown != null) { + size += this.cooldown.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 43) { + return ValidationResult.error("Buffer too small: expected at least 43 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 19); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 43 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 23); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 43 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 27); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 43 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 31); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 43 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 35); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 43 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int cooldownOffset = buffer.getIntLE(offset + 39); + if (cooldownOffset < 0) { + return ValidationResult.error("Invalid offset for Cooldown"); + } + + int posxxxxx = offset + 43 + cooldownOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Cooldown"); + } + + ValidationResult cooldownResult = InteractionCooldown.validateStructure(buffer, posxxxxx); + if (!cooldownResult.isValid()) { + return ValidationResult.error("Invalid Cooldown: " + cooldownResult.error()); + } + + posxxxxx += InteractionCooldown.computeBytesConsumed(buffer, posxxxxx); + } + + return ValidationResult.OK; + } + } + + public TriggerCooldownInteraction clone() { + TriggerCooldownInteraction copy = new TriggerCooldownInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.cooldown = this.cooldown != null ? this.cooldown.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof TriggerCooldownInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && Objects.equals(this.cooldown, other.cooldown); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + return 31 * result + Objects.hashCode(this.cooldown); + } +} diff --git a/src/com/hypixel/hytale/protocol/UVMotion.java b/src/com/hypixel/hytale/protocol/UVMotion.java new file mode 100644 index 0000000..d066d0b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/UVMotion.java @@ -0,0 +1,186 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UVMotion { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 19; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 19; + public static final int MAX_SIZE = 16384024; + @Nullable + public String texture; + public boolean addRandomUVOffset; + public float speedX; + public float speedY; + public float scale; + public float strength; + @Nonnull + public UVMotionCurveType strengthCurveType = UVMotionCurveType.Constant; + + public UVMotion() { + } + + public UVMotion( + @Nullable String texture, + boolean addRandomUVOffset, + float speedX, + float speedY, + float scale, + float strength, + @Nonnull UVMotionCurveType strengthCurveType + ) { + this.texture = texture; + this.addRandomUVOffset = addRandomUVOffset; + this.speedX = speedX; + this.speedY = speedY; + this.scale = scale; + this.strength = strength; + this.strengthCurveType = strengthCurveType; + } + + public UVMotion(@Nonnull UVMotion other) { + this.texture = other.texture; + this.addRandomUVOffset = other.addRandomUVOffset; + this.speedX = other.speedX; + this.speedY = other.speedY; + this.scale = other.scale; + this.strength = other.strength; + this.strengthCurveType = other.strengthCurveType; + } + + @Nonnull + public static UVMotion deserialize(@Nonnull ByteBuf buf, int offset) { + UVMotion obj = new UVMotion(); + byte nullBits = buf.getByte(offset); + obj.addRandomUVOffset = buf.getByte(offset + 1) != 0; + obj.speedX = buf.getFloatLE(offset + 2); + obj.speedY = buf.getFloatLE(offset + 6); + obj.scale = buf.getFloatLE(offset + 10); + obj.strength = buf.getFloatLE(offset + 14); + obj.strengthCurveType = UVMotionCurveType.fromValue(buf.getByte(offset + 18)); + int pos = offset + 19; + if ((nullBits & 1) != 0) { + int textureLen = VarInt.peek(buf, pos); + if (textureLen < 0) { + throw ProtocolException.negativeLength("Texture", textureLen); + } + + if (textureLen > 4096000) { + throw ProtocolException.stringTooLong("Texture", textureLen, 4096000); + } + + int textureVarLen = VarInt.length(buf, pos); + obj.texture = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += textureVarLen + textureLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 19; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.texture != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.addRandomUVOffset ? 1 : 0); + buf.writeFloatLE(this.speedX); + buf.writeFloatLE(this.speedY); + buf.writeFloatLE(this.scale); + buf.writeFloatLE(this.strength); + buf.writeByte(this.strengthCurveType.getValue()); + if (this.texture != null) { + PacketIO.writeVarString(buf, this.texture, 4096000); + } + } + + public int computeSize() { + int size = 19; + if (this.texture != null) { + size += PacketIO.stringSize(this.texture); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 19) { + return ValidationResult.error("Buffer too small: expected at least 19 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 19; + if ((nullBits & 1) != 0) { + int textureLen = VarInt.peek(buffer, pos); + if (textureLen < 0) { + return ValidationResult.error("Invalid string length for Texture"); + } + + if (textureLen > 4096000) { + return ValidationResult.error("Texture exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += textureLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Texture"); + } + } + + return ValidationResult.OK; + } + } + + public UVMotion clone() { + UVMotion copy = new UVMotion(); + copy.texture = this.texture; + copy.addRandomUVOffset = this.addRandomUVOffset; + copy.speedX = this.speedX; + copy.speedY = this.speedY; + copy.scale = this.scale; + copy.strength = this.strength; + copy.strengthCurveType = this.strengthCurveType; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UVMotion other) + ? false + : Objects.equals(this.texture, other.texture) + && this.addRandomUVOffset == other.addRandomUVOffset + && this.speedX == other.speedX + && this.speedY == other.speedY + && this.scale == other.scale + && this.strength == other.strength + && Objects.equals(this.strengthCurveType, other.strengthCurveType); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.texture, this.addRandomUVOffset, this.speedX, this.speedY, this.scale, this.strength, this.strengthCurveType); + } +} diff --git a/src/com/hypixel/hytale/protocol/UVMotionCurveType.java b/src/com/hypixel/hytale/protocol/UVMotionCurveType.java new file mode 100644 index 0000000..3cf83cf --- /dev/null +++ b/src/com/hypixel/hytale/protocol/UVMotionCurveType.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum UVMotionCurveType { + Constant(0), + IncreaseLinear(1), + IncreaseQuartIn(2), + IncreaseQuartInOut(3), + IncreaseQuartOut(4), + DecreaseLinear(5), + DecreaseQuartIn(6), + DecreaseQuartInOut(7), + DecreaseQuartOut(8); + + public static final UVMotionCurveType[] VALUES = values(); + private final int value; + + private UVMotionCurveType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static UVMotionCurveType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("UVMotionCurveType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/UpdateType.java b/src/com/hypixel/hytale/protocol/UpdateType.java new file mode 100644 index 0000000..1e91aa1 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/UpdateType.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum UpdateType { + Init(0), + AddOrUpdate(1), + Remove(2); + + public static final UpdateType[] VALUES = values(); + private final int value; + + private UpdateType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static UpdateType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("UpdateType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/UseBlockInteraction.java b/src/com/hypixel/hytale/protocol/UseBlockInteraction.java new file mode 100644 index 0000000..932aa51 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/UseBlockInteraction.java @@ -0,0 +1,512 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UseBlockInteraction extends SimpleBlockInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 20; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 40; + public static final int MAX_SIZE = 1677721600; + + public UseBlockInteraction() { + } + + public UseBlockInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed, + boolean useLatestTarget + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + this.useLatestTarget = useLatestTarget; + } + + public UseBlockInteraction(@Nonnull UseBlockInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + this.useLatestTarget = other.useLatestTarget; + } + + @Nonnull + public static UseBlockInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + UseBlockInteraction obj = new UseBlockInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + obj.useLatestTarget = buf.getByte(offset + 19) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 40 + buf.getIntLE(offset + 20); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 40 + buf.getIntLE(offset + 24); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 40 + buf.getIntLE(offset + 28); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 40 + buf.getIntLE(offset + 32); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 40 + buf.getIntLE(offset + 36); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 40; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 20); + int pos0 = offset + 40 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 24); + int pos1 = offset + 40 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 28); + int pos2 = offset + 40 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 32); + int pos3 = offset + 40 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 36); + int pos4 = offset + 40 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + buf.writeByte(this.useLatestTarget ? 1 : 0); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 40; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 40) { + return ValidationResult.error("Buffer too small: expected at least 40 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 20); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 40 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 24); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 40 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 28); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 40 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 32); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 40 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 36); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 40 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public UseBlockInteraction clone() { + UseBlockInteraction copy = new UseBlockInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + copy.useLatestTarget = this.useLatestTarget; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UseBlockInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed + && this.useLatestTarget == other.useLatestTarget; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + result = 31 * result + Integer.hashCode(this.failed); + return 31 * result + Boolean.hashCode(this.useLatestTarget); + } +} diff --git a/src/com/hypixel/hytale/protocol/UseEntityInteraction.java b/src/com/hypixel/hytale/protocol/UseEntityInteraction.java new file mode 100644 index 0000000..05e83b1 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/UseEntityInteraction.java @@ -0,0 +1,504 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UseEntityInteraction extends SimpleInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 19; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 39; + public static final int MAX_SIZE = 1677721600; + + public UseEntityInteraction() { + } + + public UseEntityInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int next, + int failed + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.next = next; + this.failed = failed; + } + + public UseEntityInteraction(@Nonnull UseEntityInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.next = other.next; + this.failed = other.failed; + } + + @Nonnull + public static UseEntityInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + UseEntityInteraction obj = new UseEntityInteraction(); + byte nullBits = buf.getByte(offset); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 1)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 2); + obj.runTime = buf.getFloatLE(offset + 6); + obj.cancelOnItemChange = buf.getByte(offset + 10) != 0; + obj.next = buf.getIntLE(offset + 11); + obj.failed = buf.getIntLE(offset + 15); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 39 + buf.getIntLE(offset + 19); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 39 + buf.getIntLE(offset + 23); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 39 + buf.getIntLE(offset + 27); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 39 + buf.getIntLE(offset + 31); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 39 + buf.getIntLE(offset + 35); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 39; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 19); + int pos0 = offset + 39 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 23); + int pos1 = offset + 39 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 27); + int pos2 = offset + 39 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 31); + int pos3 = offset + 39 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 35); + int pos4 = offset + 39 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.settings != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rules != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tags != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.next); + buf.writeIntLE(this.failed); + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 39; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 39) { + return ValidationResult.error("Buffer too small: expected at least 39 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 19); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 39 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 23); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 39 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 27); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 39 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 31); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 39 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 35); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 39 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + return ValidationResult.OK; + } + } + + public UseEntityInteraction clone() { + UseEntityInteraction copy = new UseEntityInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.next = this.next; + copy.failed = this.failed; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UseEntityInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.next == other.next + && this.failed == other.failed; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.next); + return 31 * result + Integer.hashCode(this.failed); + } +} diff --git a/src/com/hypixel/hytale/protocol/ValueType.java b/src/com/hypixel/hytale/protocol/ValueType.java new file mode 100644 index 0000000..efc6d7e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ValueType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ValueType { + Percent(0), + Absolute(1); + + public static final ValueType[] VALUES = values(); + private final int value; + + private ValueType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ValueType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ValueType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/VariantRotation.java b/src/com/hypixel/hytale/protocol/VariantRotation.java new file mode 100644 index 0000000..3ee641b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/VariantRotation.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum VariantRotation { + None(0), + Wall(1), + UpDown(2), + Pipe(3), + DoublePipe(4), + NESW(5), + UpDownNESW(6), + All(7); + + public static final VariantRotation[] VALUES = values(); + private final int value; + + private VariantRotation(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static VariantRotation fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("VariantRotation", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/Vector2f.java b/src/com/hypixel/hytale/protocol/Vector2f.java new file mode 100644 index 0000000..66a342d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Vector2f.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class Vector2f { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public float x; + public float y; + + public Vector2f() { + } + + public Vector2f(float x, float y) { + this.x = x; + this.y = y; + } + + public Vector2f(@Nonnull Vector2f other) { + this.x = other.x; + this.y = other.y; + } + + @Nonnull + public static Vector2f deserialize(@Nonnull ByteBuf buf, int offset) { + Vector2f obj = new Vector2f(); + obj.x = buf.getFloatLE(offset + 0); + obj.y = buf.getFloatLE(offset + 4); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.x); + buf.writeFloatLE(this.y); + } + + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public Vector2f clone() { + Vector2f copy = new Vector2f(); + copy.x = this.x; + copy.y = this.y; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Vector2f other) ? false : this.x == other.x && this.y == other.y; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y); + } +} diff --git a/src/com/hypixel/hytale/protocol/Vector2i.java b/src/com/hypixel/hytale/protocol/Vector2i.java new file mode 100644 index 0000000..9084b74 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Vector2i.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class Vector2i { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public int x; + public int y; + + public Vector2i() { + } + + public Vector2i(int x, int y) { + this.x = x; + this.y = y; + } + + public Vector2i(@Nonnull Vector2i other) { + this.x = other.x; + this.y = other.y; + } + + @Nonnull + public static Vector2i deserialize(@Nonnull ByteBuf buf, int offset) { + Vector2i obj = new Vector2i(); + obj.x = buf.getIntLE(offset + 0); + obj.y = buf.getIntLE(offset + 4); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.x); + buf.writeIntLE(this.y); + } + + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public Vector2i clone() { + Vector2i copy = new Vector2i(); + copy.x = this.x; + copy.y = this.y; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Vector2i other) ? false : this.x == other.x && this.y == other.y; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y); + } +} diff --git a/src/com/hypixel/hytale/protocol/Vector3d.java b/src/com/hypixel/hytale/protocol/Vector3d.java new file mode 100644 index 0000000..2cdecc4 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Vector3d.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class Vector3d { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 24; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 24; + public static final int MAX_SIZE = 24; + public double x; + public double y; + public double z; + + public Vector3d() { + } + + public Vector3d(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public Vector3d(@Nonnull Vector3d other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + } + + @Nonnull + public static Vector3d deserialize(@Nonnull ByteBuf buf, int offset) { + Vector3d obj = new Vector3d(); + obj.x = buf.getDoubleLE(offset + 0); + obj.y = buf.getDoubleLE(offset + 8); + obj.z = buf.getDoubleLE(offset + 16); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 24; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeDoubleLE(this.x); + buf.writeDoubleLE(this.y); + buf.writeDoubleLE(this.z); + } + + public int computeSize() { + return 24; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 24 ? ValidationResult.error("Buffer too small: expected at least 24 bytes") : ValidationResult.OK; + } + + public Vector3d clone() { + Vector3d copy = new Vector3d(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Vector3d other) ? false : this.x == other.x && this.y == other.y && this.z == other.z; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y, this.z); + } +} diff --git a/src/com/hypixel/hytale/protocol/Vector3f.java b/src/com/hypixel/hytale/protocol/Vector3f.java new file mode 100644 index 0000000..dbf2338 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Vector3f.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class Vector3f { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 12; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 12; + public static final int MAX_SIZE = 12; + public float x; + public float y; + public float z; + + public Vector3f() { + } + + public Vector3f(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public Vector3f(@Nonnull Vector3f other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + } + + @Nonnull + public static Vector3f deserialize(@Nonnull ByteBuf buf, int offset) { + Vector3f obj = new Vector3f(); + obj.x = buf.getFloatLE(offset + 0); + obj.y = buf.getFloatLE(offset + 4); + obj.z = buf.getFloatLE(offset + 8); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 12; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.x); + buf.writeFloatLE(this.y); + buf.writeFloatLE(this.z); + } + + public int computeSize() { + return 12; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 12 ? ValidationResult.error("Buffer too small: expected at least 12 bytes") : ValidationResult.OK; + } + + public Vector3f clone() { + Vector3f copy = new Vector3f(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Vector3f other) ? false : this.x == other.x && this.y == other.y && this.z == other.z; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y, this.z); + } +} diff --git a/src/com/hypixel/hytale/protocol/Vector3i.java b/src/com/hypixel/hytale/protocol/Vector3i.java new file mode 100644 index 0000000..276abae --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Vector3i.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class Vector3i { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 12; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 12; + public static final int MAX_SIZE = 12; + public int x; + public int y; + public int z; + + public Vector3i() { + } + + public Vector3i(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public Vector3i(@Nonnull Vector3i other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + } + + @Nonnull + public static Vector3i deserialize(@Nonnull ByteBuf buf, int offset) { + Vector3i obj = new Vector3i(); + obj.x = buf.getIntLE(offset + 0); + obj.y = buf.getIntLE(offset + 4); + obj.z = buf.getIntLE(offset + 8); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 12; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.x); + buf.writeIntLE(this.y); + buf.writeIntLE(this.z); + } + + public int computeSize() { + return 12; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 12 ? ValidationResult.error("Buffer too small: expected at least 12 bytes") : ValidationResult.OK; + } + + public Vector3i clone() { + Vector3i copy = new Vector3i(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Vector3i other) ? false : this.x == other.x && this.y == other.y && this.z == other.z; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y, this.z); + } +} diff --git a/src/com/hypixel/hytale/protocol/VelocityConfig.java b/src/com/hypixel/hytale/protocol/VelocityConfig.java new file mode 100644 index 0000000..31bd8c7 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/VelocityConfig.java @@ -0,0 +1,109 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class VelocityConfig { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 21; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 21; + public static final int MAX_SIZE = 21; + public float groundResistance; + public float groundResistanceMax; + public float airResistance; + public float airResistanceMax; + public float threshold; + @Nonnull + public VelocityThresholdStyle style = VelocityThresholdStyle.Linear; + + public VelocityConfig() { + } + + public VelocityConfig( + float groundResistance, float groundResistanceMax, float airResistance, float airResistanceMax, float threshold, @Nonnull VelocityThresholdStyle style + ) { + this.groundResistance = groundResistance; + this.groundResistanceMax = groundResistanceMax; + this.airResistance = airResistance; + this.airResistanceMax = airResistanceMax; + this.threshold = threshold; + this.style = style; + } + + public VelocityConfig(@Nonnull VelocityConfig other) { + this.groundResistance = other.groundResistance; + this.groundResistanceMax = other.groundResistanceMax; + this.airResistance = other.airResistance; + this.airResistanceMax = other.airResistanceMax; + this.threshold = other.threshold; + this.style = other.style; + } + + @Nonnull + public static VelocityConfig deserialize(@Nonnull ByteBuf buf, int offset) { + VelocityConfig obj = new VelocityConfig(); + obj.groundResistance = buf.getFloatLE(offset + 0); + obj.groundResistanceMax = buf.getFloatLE(offset + 4); + obj.airResistance = buf.getFloatLE(offset + 8); + obj.airResistanceMax = buf.getFloatLE(offset + 12); + obj.threshold = buf.getFloatLE(offset + 16); + obj.style = VelocityThresholdStyle.fromValue(buf.getByte(offset + 20)); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 21; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.groundResistance); + buf.writeFloatLE(this.groundResistanceMax); + buf.writeFloatLE(this.airResistance); + buf.writeFloatLE(this.airResistanceMax); + buf.writeFloatLE(this.threshold); + buf.writeByte(this.style.getValue()); + } + + public int computeSize() { + return 21; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 21 ? ValidationResult.error("Buffer too small: expected at least 21 bytes") : ValidationResult.OK; + } + + public VelocityConfig clone() { + VelocityConfig copy = new VelocityConfig(); + copy.groundResistance = this.groundResistance; + copy.groundResistanceMax = this.groundResistanceMax; + copy.airResistance = this.airResistance; + copy.airResistanceMax = this.airResistanceMax; + copy.threshold = this.threshold; + copy.style = this.style; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof VelocityConfig other) + ? false + : this.groundResistance == other.groundResistance + && this.groundResistanceMax == other.groundResistanceMax + && this.airResistance == other.airResistance + && this.airResistanceMax == other.airResistanceMax + && this.threshold == other.threshold + && Objects.equals(this.style, other.style); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.groundResistance, this.groundResistanceMax, this.airResistance, this.airResistanceMax, this.threshold, this.style); + } +} diff --git a/src/com/hypixel/hytale/protocol/VelocityThresholdStyle.java b/src/com/hypixel/hytale/protocol/VelocityThresholdStyle.java new file mode 100644 index 0000000..7013e03 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/VelocityThresholdStyle.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum VelocityThresholdStyle { + Linear(0), + Exp(1); + + public static final VelocityThresholdStyle[] VALUES = values(); + private final int value; + + private VelocityThresholdStyle(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static VelocityThresholdStyle fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("VelocityThresholdStyle", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/ViewBobbing.java b/src/com/hypixel/hytale/protocol/ViewBobbing.java new file mode 100644 index 0000000..2f1244a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/ViewBobbing.java @@ -0,0 +1,111 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ViewBobbing { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 565248085; + @Nullable + public CameraShakeConfig firstPerson; + + public ViewBobbing() { + } + + public ViewBobbing(@Nullable CameraShakeConfig firstPerson) { + this.firstPerson = firstPerson; + } + + public ViewBobbing(@Nonnull ViewBobbing other) { + this.firstPerson = other.firstPerson; + } + + @Nonnull + public static ViewBobbing deserialize(@Nonnull ByteBuf buf, int offset) { + ViewBobbing obj = new ViewBobbing(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + obj.firstPerson = CameraShakeConfig.deserialize(buf, pos); + pos += CameraShakeConfig.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + pos += CameraShakeConfig.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.firstPerson != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.firstPerson != null) { + this.firstPerson.serialize(buf); + } + } + + public int computeSize() { + int size = 1; + if (this.firstPerson != null) { + size += this.firstPerson.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + ValidationResult firstPersonResult = CameraShakeConfig.validateStructure(buffer, pos); + if (!firstPersonResult.isValid()) { + return ValidationResult.error("Invalid FirstPerson: " + firstPersonResult.error()); + } + + pos += CameraShakeConfig.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public ViewBobbing clone() { + ViewBobbing copy = new ViewBobbing(); + copy.firstPerson = this.firstPerson != null ? this.firstPerson.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof ViewBobbing other ? Objects.equals(this.firstPerson, other.firstPerson) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.firstPerson); + } +} diff --git a/src/com/hypixel/hytale/protocol/WaitForDataFrom.java b/src/com/hypixel/hytale/protocol/WaitForDataFrom.java new file mode 100644 index 0000000..7b28129 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/WaitForDataFrom.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum WaitForDataFrom { + Client(0), + Server(1), + None(2); + + public static final WaitForDataFrom[] VALUES = values(); + private final int value; + + private WaitForDataFrom(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static WaitForDataFrom fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("WaitForDataFrom", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/Weather.java b/src/com/hypixel/hytale/protocol/Weather.java new file mode 100644 index 0000000..c6ddb25 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/Weather.java @@ -0,0 +1,2716 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Weather { + public static final int NULLABLE_BIT_FIELD_SIZE = 4; + public static final int FIXED_BLOCK_SIZE = 30; + public static final int VARIABLE_FIELD_COUNT = 24; + public static final int VARIABLE_BLOCK_START = 126; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + @Nullable + public int[] tagIndexes; + @Nullable + public String stars; + @Nullable + public Map moons; + @Nullable + public Cloud[] clouds; + @Nullable + public Map sunlightDampingMultiplier; + @Nullable + public Map sunlightColors; + @Nullable + public Map skyTopColors; + @Nullable + public Map skyBottomColors; + @Nullable + public Map skySunsetColors; + @Nullable + public Map sunColors; + @Nullable + public Map sunScales; + @Nullable + public Map sunGlowColors; + @Nullable + public Map moonColors; + @Nullable + public Map moonScales; + @Nullable + public Map moonGlowColors; + @Nullable + public Map fogColors; + @Nullable + public Map fogHeightFalloffs; + @Nullable + public Map fogDensities; + @Nullable + public String screenEffect; + @Nullable + public Map screenEffectColors; + @Nullable + public Map colorFilters; + @Nullable + public Map waterTints; + @Nullable + public WeatherParticle particle; + @Nullable + public NearFar fog; + @Nullable + public FogOptions fogOptions; + + public Weather() { + } + + public Weather( + @Nullable String id, + @Nullable int[] tagIndexes, + @Nullable String stars, + @Nullable Map moons, + @Nullable Cloud[] clouds, + @Nullable Map sunlightDampingMultiplier, + @Nullable Map sunlightColors, + @Nullable Map skyTopColors, + @Nullable Map skyBottomColors, + @Nullable Map skySunsetColors, + @Nullable Map sunColors, + @Nullable Map sunScales, + @Nullable Map sunGlowColors, + @Nullable Map moonColors, + @Nullable Map moonScales, + @Nullable Map moonGlowColors, + @Nullable Map fogColors, + @Nullable Map fogHeightFalloffs, + @Nullable Map fogDensities, + @Nullable String screenEffect, + @Nullable Map screenEffectColors, + @Nullable Map colorFilters, + @Nullable Map waterTints, + @Nullable WeatherParticle particle, + @Nullable NearFar fog, + @Nullable FogOptions fogOptions + ) { + this.id = id; + this.tagIndexes = tagIndexes; + this.stars = stars; + this.moons = moons; + this.clouds = clouds; + this.sunlightDampingMultiplier = sunlightDampingMultiplier; + this.sunlightColors = sunlightColors; + this.skyTopColors = skyTopColors; + this.skyBottomColors = skyBottomColors; + this.skySunsetColors = skySunsetColors; + this.sunColors = sunColors; + this.sunScales = sunScales; + this.sunGlowColors = sunGlowColors; + this.moonColors = moonColors; + this.moonScales = moonScales; + this.moonGlowColors = moonGlowColors; + this.fogColors = fogColors; + this.fogHeightFalloffs = fogHeightFalloffs; + this.fogDensities = fogDensities; + this.screenEffect = screenEffect; + this.screenEffectColors = screenEffectColors; + this.colorFilters = colorFilters; + this.waterTints = waterTints; + this.particle = particle; + this.fog = fog; + this.fogOptions = fogOptions; + } + + public Weather(@Nonnull Weather other) { + this.id = other.id; + this.tagIndexes = other.tagIndexes; + this.stars = other.stars; + this.moons = other.moons; + this.clouds = other.clouds; + this.sunlightDampingMultiplier = other.sunlightDampingMultiplier; + this.sunlightColors = other.sunlightColors; + this.skyTopColors = other.skyTopColors; + this.skyBottomColors = other.skyBottomColors; + this.skySunsetColors = other.skySunsetColors; + this.sunColors = other.sunColors; + this.sunScales = other.sunScales; + this.sunGlowColors = other.sunGlowColors; + this.moonColors = other.moonColors; + this.moonScales = other.moonScales; + this.moonGlowColors = other.moonGlowColors; + this.fogColors = other.fogColors; + this.fogHeightFalloffs = other.fogHeightFalloffs; + this.fogDensities = other.fogDensities; + this.screenEffect = other.screenEffect; + this.screenEffectColors = other.screenEffectColors; + this.colorFilters = other.colorFilters; + this.waterTints = other.waterTints; + this.particle = other.particle; + this.fog = other.fog; + this.fogOptions = other.fogOptions; + } + + @Nonnull + public static Weather deserialize(@Nonnull ByteBuf buf, int offset) { + Weather obj = new Weather(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 4); + if ((nullBits[3] & 1) != 0) { + obj.fog = NearFar.deserialize(buf, offset + 4); + } + + if ((nullBits[3] & 2) != 0) { + obj.fogOptions = FogOptions.deserialize(buf, offset + 12); + } + + if ((nullBits[0] & 1) != 0) { + int varPos0 = offset + 126 + buf.getIntLE(offset + 30); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits[0] & 2) != 0) { + int varPos1 = offset + 126 + buf.getIntLE(offset + 34); + int tagIndexesCount = VarInt.peek(buf, varPos1); + if (tagIndexesCount < 0) { + throw ProtocolException.negativeLength("TagIndexes", tagIndexesCount); + } + + if (tagIndexesCount > 4096000) { + throw ProtocolException.arrayTooLong("TagIndexes", tagIndexesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + tagIndexesCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("TagIndexes", varPos1 + varIntLen + tagIndexesCount * 4, buf.readableBytes()); + } + + obj.tagIndexes = new int[tagIndexesCount]; + + for (int i = 0; i < tagIndexesCount; i++) { + obj.tagIndexes[i] = buf.getIntLE(varPos1 + varIntLen + i * 4); + } + } + + if ((nullBits[0] & 4) != 0) { + int varPos2 = offset + 126 + buf.getIntLE(offset + 38); + int starsLen = VarInt.peek(buf, varPos2); + if (starsLen < 0) { + throw ProtocolException.negativeLength("Stars", starsLen); + } + + if (starsLen > 4096000) { + throw ProtocolException.stringTooLong("Stars", starsLen, 4096000); + } + + obj.stars = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits[0] & 8) != 0) { + int varPos3 = offset + 126 + buf.getIntLE(offset + 42); + int moonsCount = VarInt.peek(buf, varPos3); + if (moonsCount < 0) { + throw ProtocolException.negativeLength("Moons", moonsCount); + } + + if (moonsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Moons", moonsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + obj.moons = new HashMap<>(moonsCount); + int dictPos = varPos3 + varIntLen; + + for (int i = 0; i < moonsCount; i++) { + int key = buf.getIntLE(dictPos); + dictPos += 4; + int valLen = VarInt.peek(buf, dictPos); + if (valLen < 0) { + throw ProtocolException.negativeLength("val", valLen); + } + + if (valLen > 4096000) { + throw ProtocolException.stringTooLong("val", valLen, 4096000); + } + + int valVarLen = VarInt.length(buf, dictPos); + String val = PacketIO.readVarString(buf, dictPos); + dictPos += valVarLen + valLen; + if (obj.moons.put(key, val) != null) { + throw ProtocolException.duplicateKey("moons", key); + } + } + } + + if ((nullBits[0] & 16) != 0) { + int varPos4 = offset + 126 + buf.getIntLE(offset + 46); + int cloudsCount = VarInt.peek(buf, varPos4); + if (cloudsCount < 0) { + throw ProtocolException.negativeLength("Clouds", cloudsCount); + } + + if (cloudsCount > 4096000) { + throw ProtocolException.arrayTooLong("Clouds", cloudsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos4); + if (varPos4 + varIntLen + cloudsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Clouds", varPos4 + varIntLen + cloudsCount * 1, buf.readableBytes()); + } + + obj.clouds = new Cloud[cloudsCount]; + int elemPos = varPos4 + varIntLen; + + for (int i = 0; i < cloudsCount; i++) { + obj.clouds[i] = Cloud.deserialize(buf, elemPos); + elemPos += Cloud.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits[0] & 32) != 0) { + int varPos5 = offset + 126 + buf.getIntLE(offset + 50); + int sunlightDampingMultiplierCount = VarInt.peek(buf, varPos5); + if (sunlightDampingMultiplierCount < 0) { + throw ProtocolException.negativeLength("SunlightDampingMultiplier", sunlightDampingMultiplierCount); + } + + if (sunlightDampingMultiplierCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("SunlightDampingMultiplier", sunlightDampingMultiplierCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + obj.sunlightDampingMultiplier = new HashMap<>(sunlightDampingMultiplierCount); + int dictPos = varPos5 + varIntLen; + + for (int i = 0; i < sunlightDampingMultiplierCount; i++) { + float keyx = buf.getFloatLE(dictPos); + dictPos += 4; + float val = buf.getFloatLE(dictPos); + dictPos += 4; + if (obj.sunlightDampingMultiplier.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("sunlightDampingMultiplier", keyx); + } + } + } + + if ((nullBits[0] & 64) != 0) { + int varPos6 = offset + 126 + buf.getIntLE(offset + 54); + int sunlightColorsCount = VarInt.peek(buf, varPos6); + if (sunlightColorsCount < 0) { + throw ProtocolException.negativeLength("SunlightColors", sunlightColorsCount); + } + + if (sunlightColorsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("SunlightColors", sunlightColorsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos6); + obj.sunlightColors = new HashMap<>(sunlightColorsCount); + int dictPos = varPos6 + varIntLen; + + for (int ix = 0; ix < sunlightColorsCount; ix++) { + float keyx = buf.getFloatLE(dictPos); + dictPos += 4; + Color val = Color.deserialize(buf, dictPos); + dictPos += Color.computeBytesConsumed(buf, dictPos); + if (obj.sunlightColors.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("sunlightColors", keyx); + } + } + } + + if ((nullBits[0] & 128) != 0) { + int varPos7 = offset + 126 + buf.getIntLE(offset + 58); + int skyTopColorsCount = VarInt.peek(buf, varPos7); + if (skyTopColorsCount < 0) { + throw ProtocolException.negativeLength("SkyTopColors", skyTopColorsCount); + } + + if (skyTopColorsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("SkyTopColors", skyTopColorsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos7); + obj.skyTopColors = new HashMap<>(skyTopColorsCount); + int dictPos = varPos7 + varIntLen; + + for (int ixx = 0; ixx < skyTopColorsCount; ixx++) { + float keyx = buf.getFloatLE(dictPos); + dictPos += 4; + ColorAlpha val = ColorAlpha.deserialize(buf, dictPos); + dictPos += ColorAlpha.computeBytesConsumed(buf, dictPos); + if (obj.skyTopColors.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("skyTopColors", keyx); + } + } + } + + if ((nullBits[1] & 1) != 0) { + int varPos8 = offset + 126 + buf.getIntLE(offset + 62); + int skyBottomColorsCount = VarInt.peek(buf, varPos8); + if (skyBottomColorsCount < 0) { + throw ProtocolException.negativeLength("SkyBottomColors", skyBottomColorsCount); + } + + if (skyBottomColorsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("SkyBottomColors", skyBottomColorsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos8); + obj.skyBottomColors = new HashMap<>(skyBottomColorsCount); + int dictPos = varPos8 + varIntLen; + + for (int ixxx = 0; ixxx < skyBottomColorsCount; ixxx++) { + float keyx = buf.getFloatLE(dictPos); + dictPos += 4; + ColorAlpha val = ColorAlpha.deserialize(buf, dictPos); + dictPos += ColorAlpha.computeBytesConsumed(buf, dictPos); + if (obj.skyBottomColors.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("skyBottomColors", keyx); + } + } + } + + if ((nullBits[1] & 2) != 0) { + int varPos9 = offset + 126 + buf.getIntLE(offset + 66); + int skySunsetColorsCount = VarInt.peek(buf, varPos9); + if (skySunsetColorsCount < 0) { + throw ProtocolException.negativeLength("SkySunsetColors", skySunsetColorsCount); + } + + if (skySunsetColorsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("SkySunsetColors", skySunsetColorsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos9); + obj.skySunsetColors = new HashMap<>(skySunsetColorsCount); + int dictPos = varPos9 + varIntLen; + + for (int ixxxx = 0; ixxxx < skySunsetColorsCount; ixxxx++) { + float keyx = buf.getFloatLE(dictPos); + dictPos += 4; + ColorAlpha val = ColorAlpha.deserialize(buf, dictPos); + dictPos += ColorAlpha.computeBytesConsumed(buf, dictPos); + if (obj.skySunsetColors.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("skySunsetColors", keyx); + } + } + } + + if ((nullBits[1] & 4) != 0) { + int varPos10 = offset + 126 + buf.getIntLE(offset + 70); + int sunColorsCount = VarInt.peek(buf, varPos10); + if (sunColorsCount < 0) { + throw ProtocolException.negativeLength("SunColors", sunColorsCount); + } + + if (sunColorsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("SunColors", sunColorsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos10); + obj.sunColors = new HashMap<>(sunColorsCount); + int dictPos = varPos10 + varIntLen; + + for (int ixxxxx = 0; ixxxxx < sunColorsCount; ixxxxx++) { + float keyx = buf.getFloatLE(dictPos); + dictPos += 4; + Color val = Color.deserialize(buf, dictPos); + dictPos += Color.computeBytesConsumed(buf, dictPos); + if (obj.sunColors.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("sunColors", keyx); + } + } + } + + if ((nullBits[1] & 8) != 0) { + int varPos11 = offset + 126 + buf.getIntLE(offset + 74); + int sunScalesCount = VarInt.peek(buf, varPos11); + if (sunScalesCount < 0) { + throw ProtocolException.negativeLength("SunScales", sunScalesCount); + } + + if (sunScalesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("SunScales", sunScalesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos11); + obj.sunScales = new HashMap<>(sunScalesCount); + int dictPos = varPos11 + varIntLen; + + for (int ixxxxxx = 0; ixxxxxx < sunScalesCount; ixxxxxx++) { + float keyx = buf.getFloatLE(dictPos); + dictPos += 4; + float val = buf.getFloatLE(dictPos); + dictPos += 4; + if (obj.sunScales.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("sunScales", keyx); + } + } + } + + if ((nullBits[1] & 16) != 0) { + int varPos12 = offset + 126 + buf.getIntLE(offset + 78); + int sunGlowColorsCount = VarInt.peek(buf, varPos12); + if (sunGlowColorsCount < 0) { + throw ProtocolException.negativeLength("SunGlowColors", sunGlowColorsCount); + } + + if (sunGlowColorsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("SunGlowColors", sunGlowColorsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos12); + obj.sunGlowColors = new HashMap<>(sunGlowColorsCount); + int dictPos = varPos12 + varIntLen; + + for (int ixxxxxxx = 0; ixxxxxxx < sunGlowColorsCount; ixxxxxxx++) { + float keyx = buf.getFloatLE(dictPos); + dictPos += 4; + ColorAlpha val = ColorAlpha.deserialize(buf, dictPos); + dictPos += ColorAlpha.computeBytesConsumed(buf, dictPos); + if (obj.sunGlowColors.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("sunGlowColors", keyx); + } + } + } + + if ((nullBits[1] & 32) != 0) { + int varPos13 = offset + 126 + buf.getIntLE(offset + 82); + int moonColorsCount = VarInt.peek(buf, varPos13); + if (moonColorsCount < 0) { + throw ProtocolException.negativeLength("MoonColors", moonColorsCount); + } + + if (moonColorsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("MoonColors", moonColorsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos13); + obj.moonColors = new HashMap<>(moonColorsCount); + int dictPos = varPos13 + varIntLen; + + for (int ixxxxxxxx = 0; ixxxxxxxx < moonColorsCount; ixxxxxxxx++) { + float keyx = buf.getFloatLE(dictPos); + dictPos += 4; + ColorAlpha val = ColorAlpha.deserialize(buf, dictPos); + dictPos += ColorAlpha.computeBytesConsumed(buf, dictPos); + if (obj.moonColors.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("moonColors", keyx); + } + } + } + + if ((nullBits[1] & 64) != 0) { + int varPos14 = offset + 126 + buf.getIntLE(offset + 86); + int moonScalesCount = VarInt.peek(buf, varPos14); + if (moonScalesCount < 0) { + throw ProtocolException.negativeLength("MoonScales", moonScalesCount); + } + + if (moonScalesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("MoonScales", moonScalesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos14); + obj.moonScales = new HashMap<>(moonScalesCount); + int dictPos = varPos14 + varIntLen; + + for (int ixxxxxxxxx = 0; ixxxxxxxxx < moonScalesCount; ixxxxxxxxx++) { + float keyx = buf.getFloatLE(dictPos); + dictPos += 4; + float val = buf.getFloatLE(dictPos); + dictPos += 4; + if (obj.moonScales.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("moonScales", keyx); + } + } + } + + if ((nullBits[1] & 128) != 0) { + int varPos15 = offset + 126 + buf.getIntLE(offset + 90); + int moonGlowColorsCount = VarInt.peek(buf, varPos15); + if (moonGlowColorsCount < 0) { + throw ProtocolException.negativeLength("MoonGlowColors", moonGlowColorsCount); + } + + if (moonGlowColorsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("MoonGlowColors", moonGlowColorsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos15); + obj.moonGlowColors = new HashMap<>(moonGlowColorsCount); + int dictPos = varPos15 + varIntLen; + + for (int ixxxxxxxxxx = 0; ixxxxxxxxxx < moonGlowColorsCount; ixxxxxxxxxx++) { + float keyx = buf.getFloatLE(dictPos); + dictPos += 4; + ColorAlpha val = ColorAlpha.deserialize(buf, dictPos); + dictPos += ColorAlpha.computeBytesConsumed(buf, dictPos); + if (obj.moonGlowColors.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("moonGlowColors", keyx); + } + } + } + + if ((nullBits[2] & 1) != 0) { + int varPos16 = offset + 126 + buf.getIntLE(offset + 94); + int fogColorsCount = VarInt.peek(buf, varPos16); + if (fogColorsCount < 0) { + throw ProtocolException.negativeLength("FogColors", fogColorsCount); + } + + if (fogColorsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("FogColors", fogColorsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos16); + obj.fogColors = new HashMap<>(fogColorsCount); + int dictPos = varPos16 + varIntLen; + + for (int ixxxxxxxxxxx = 0; ixxxxxxxxxxx < fogColorsCount; ixxxxxxxxxxx++) { + float keyx = buf.getFloatLE(dictPos); + dictPos += 4; + Color val = Color.deserialize(buf, dictPos); + dictPos += Color.computeBytesConsumed(buf, dictPos); + if (obj.fogColors.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("fogColors", keyx); + } + } + } + + if ((nullBits[2] & 2) != 0) { + int varPos17 = offset + 126 + buf.getIntLE(offset + 98); + int fogHeightFalloffsCount = VarInt.peek(buf, varPos17); + if (fogHeightFalloffsCount < 0) { + throw ProtocolException.negativeLength("FogHeightFalloffs", fogHeightFalloffsCount); + } + + if (fogHeightFalloffsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("FogHeightFalloffs", fogHeightFalloffsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos17); + obj.fogHeightFalloffs = new HashMap<>(fogHeightFalloffsCount); + int dictPos = varPos17 + varIntLen; + + for (int ixxxxxxxxxxxx = 0; ixxxxxxxxxxxx < fogHeightFalloffsCount; ixxxxxxxxxxxx++) { + float keyx = buf.getFloatLE(dictPos); + dictPos += 4; + float val = buf.getFloatLE(dictPos); + dictPos += 4; + if (obj.fogHeightFalloffs.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("fogHeightFalloffs", keyx); + } + } + } + + if ((nullBits[2] & 4) != 0) { + int varPos18 = offset + 126 + buf.getIntLE(offset + 102); + int fogDensitiesCount = VarInt.peek(buf, varPos18); + if (fogDensitiesCount < 0) { + throw ProtocolException.negativeLength("FogDensities", fogDensitiesCount); + } + + if (fogDensitiesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("FogDensities", fogDensitiesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos18); + obj.fogDensities = new HashMap<>(fogDensitiesCount); + int dictPos = varPos18 + varIntLen; + + for (int ixxxxxxxxxxxxx = 0; ixxxxxxxxxxxxx < fogDensitiesCount; ixxxxxxxxxxxxx++) { + float keyx = buf.getFloatLE(dictPos); + dictPos += 4; + float val = buf.getFloatLE(dictPos); + dictPos += 4; + if (obj.fogDensities.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("fogDensities", keyx); + } + } + } + + if ((nullBits[2] & 8) != 0) { + int varPos19 = offset + 126 + buf.getIntLE(offset + 106); + int screenEffectLen = VarInt.peek(buf, varPos19); + if (screenEffectLen < 0) { + throw ProtocolException.negativeLength("ScreenEffect", screenEffectLen); + } + + if (screenEffectLen > 4096000) { + throw ProtocolException.stringTooLong("ScreenEffect", screenEffectLen, 4096000); + } + + obj.screenEffect = PacketIO.readVarString(buf, varPos19, PacketIO.UTF8); + } + + if ((nullBits[2] & 16) != 0) { + int varPos20 = offset + 126 + buf.getIntLE(offset + 110); + int screenEffectColorsCount = VarInt.peek(buf, varPos20); + if (screenEffectColorsCount < 0) { + throw ProtocolException.negativeLength("ScreenEffectColors", screenEffectColorsCount); + } + + if (screenEffectColorsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("ScreenEffectColors", screenEffectColorsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos20); + obj.screenEffectColors = new HashMap<>(screenEffectColorsCount); + int dictPos = varPos20 + varIntLen; + + for (int ixxxxxxxxxxxxxx = 0; ixxxxxxxxxxxxxx < screenEffectColorsCount; ixxxxxxxxxxxxxx++) { + float keyx = buf.getFloatLE(dictPos); + dictPos += 4; + ColorAlpha val = ColorAlpha.deserialize(buf, dictPos); + dictPos += ColorAlpha.computeBytesConsumed(buf, dictPos); + if (obj.screenEffectColors.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("screenEffectColors", keyx); + } + } + } + + if ((nullBits[2] & 32) != 0) { + int varPos21 = offset + 126 + buf.getIntLE(offset + 114); + int colorFiltersCount = VarInt.peek(buf, varPos21); + if (colorFiltersCount < 0) { + throw ProtocolException.negativeLength("ColorFilters", colorFiltersCount); + } + + if (colorFiltersCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("ColorFilters", colorFiltersCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos21); + obj.colorFilters = new HashMap<>(colorFiltersCount); + int dictPos = varPos21 + varIntLen; + + for (int ixxxxxxxxxxxxxxx = 0; ixxxxxxxxxxxxxxx < colorFiltersCount; ixxxxxxxxxxxxxxx++) { + float keyx = buf.getFloatLE(dictPos); + dictPos += 4; + Color val = Color.deserialize(buf, dictPos); + dictPos += Color.computeBytesConsumed(buf, dictPos); + if (obj.colorFilters.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("colorFilters", keyx); + } + } + } + + if ((nullBits[2] & 64) != 0) { + int varPos22 = offset + 126 + buf.getIntLE(offset + 118); + int waterTintsCount = VarInt.peek(buf, varPos22); + if (waterTintsCount < 0) { + throw ProtocolException.negativeLength("WaterTints", waterTintsCount); + } + + if (waterTintsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("WaterTints", waterTintsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos22); + obj.waterTints = new HashMap<>(waterTintsCount); + int dictPos = varPos22 + varIntLen; + + for (int ixxxxxxxxxxxxxxxx = 0; ixxxxxxxxxxxxxxxx < waterTintsCount; ixxxxxxxxxxxxxxxx++) { + float keyx = buf.getFloatLE(dictPos); + dictPos += 4; + Color val = Color.deserialize(buf, dictPos); + dictPos += Color.computeBytesConsumed(buf, dictPos); + if (obj.waterTints.put(keyx, val) != null) { + throw ProtocolException.duplicateKey("waterTints", keyx); + } + } + } + + if ((nullBits[2] & 128) != 0) { + int varPos23 = offset + 126 + buf.getIntLE(offset + 122); + obj.particle = WeatherParticle.deserialize(buf, varPos23); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte[] nullBits = PacketIO.readBytes(buf, offset, 4); + int maxEnd = 126; + if ((nullBits[0] & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 30); + int pos0 = offset + 126 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits[0] & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 34); + int pos1 = offset + 126 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + arrLen * 4; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits[0] & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 38); + int pos2 = offset + 126 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits[0] & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 42); + int pos3 = offset + 126 + fieldOffset3; + int dictLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3); + + for (int i = 0; i < dictLen; i++) { + pos3 += 4; + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + } + + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits[0] & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 46); + int pos4 = offset + 126 + fieldOffset4; + int arrLen = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4); + + for (int i = 0; i < arrLen; i++) { + pos4 += Cloud.computeBytesConsumed(buf, pos4); + } + + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits[0] & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 50); + int pos5 = offset + 126 + fieldOffset5; + int dictLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5); + + for (int i = 0; i < dictLen; i++) { + pos5 += 4; + pos5 += 4; + } + + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits[0] & 64) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 54); + int pos6 = offset + 126 + fieldOffset6; + int dictLen = VarInt.peek(buf, pos6); + pos6 += VarInt.length(buf, pos6); + + for (int i = 0; i < dictLen; i++) { + pos6 += 4; + pos6 += Color.computeBytesConsumed(buf, pos6); + } + + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + if ((nullBits[0] & 128) != 0) { + int fieldOffset7 = buf.getIntLE(offset + 58); + int pos7 = offset + 126 + fieldOffset7; + int dictLen = VarInt.peek(buf, pos7); + pos7 += VarInt.length(buf, pos7); + + for (int i = 0; i < dictLen; i++) { + pos7 += 4; + pos7 += ColorAlpha.computeBytesConsumed(buf, pos7); + } + + if (pos7 - offset > maxEnd) { + maxEnd = pos7 - offset; + } + } + + if ((nullBits[1] & 1) != 0) { + int fieldOffset8 = buf.getIntLE(offset + 62); + int pos8 = offset + 126 + fieldOffset8; + int dictLen = VarInt.peek(buf, pos8); + pos8 += VarInt.length(buf, pos8); + + for (int i = 0; i < dictLen; i++) { + pos8 += 4; + pos8 += ColorAlpha.computeBytesConsumed(buf, pos8); + } + + if (pos8 - offset > maxEnd) { + maxEnd = pos8 - offset; + } + } + + if ((nullBits[1] & 2) != 0) { + int fieldOffset9 = buf.getIntLE(offset + 66); + int pos9 = offset + 126 + fieldOffset9; + int dictLen = VarInt.peek(buf, pos9); + pos9 += VarInt.length(buf, pos9); + + for (int i = 0; i < dictLen; i++) { + pos9 += 4; + pos9 += ColorAlpha.computeBytesConsumed(buf, pos9); + } + + if (pos9 - offset > maxEnd) { + maxEnd = pos9 - offset; + } + } + + if ((nullBits[1] & 4) != 0) { + int fieldOffset10 = buf.getIntLE(offset + 70); + int pos10 = offset + 126 + fieldOffset10; + int dictLen = VarInt.peek(buf, pos10); + pos10 += VarInt.length(buf, pos10); + + for (int i = 0; i < dictLen; i++) { + pos10 += 4; + pos10 += Color.computeBytesConsumed(buf, pos10); + } + + if (pos10 - offset > maxEnd) { + maxEnd = pos10 - offset; + } + } + + if ((nullBits[1] & 8) != 0) { + int fieldOffset11 = buf.getIntLE(offset + 74); + int pos11 = offset + 126 + fieldOffset11; + int dictLen = VarInt.peek(buf, pos11); + pos11 += VarInt.length(buf, pos11); + + for (int i = 0; i < dictLen; i++) { + pos11 += 4; + pos11 += 4; + } + + if (pos11 - offset > maxEnd) { + maxEnd = pos11 - offset; + } + } + + if ((nullBits[1] & 16) != 0) { + int fieldOffset12 = buf.getIntLE(offset + 78); + int pos12 = offset + 126 + fieldOffset12; + int dictLen = VarInt.peek(buf, pos12); + pos12 += VarInt.length(buf, pos12); + + for (int i = 0; i < dictLen; i++) { + pos12 += 4; + pos12 += ColorAlpha.computeBytesConsumed(buf, pos12); + } + + if (pos12 - offset > maxEnd) { + maxEnd = pos12 - offset; + } + } + + if ((nullBits[1] & 32) != 0) { + int fieldOffset13 = buf.getIntLE(offset + 82); + int pos13 = offset + 126 + fieldOffset13; + int dictLen = VarInt.peek(buf, pos13); + pos13 += VarInt.length(buf, pos13); + + for (int i = 0; i < dictLen; i++) { + pos13 += 4; + pos13 += ColorAlpha.computeBytesConsumed(buf, pos13); + } + + if (pos13 - offset > maxEnd) { + maxEnd = pos13 - offset; + } + } + + if ((nullBits[1] & 64) != 0) { + int fieldOffset14 = buf.getIntLE(offset + 86); + int pos14 = offset + 126 + fieldOffset14; + int dictLen = VarInt.peek(buf, pos14); + pos14 += VarInt.length(buf, pos14); + + for (int i = 0; i < dictLen; i++) { + pos14 += 4; + pos14 += 4; + } + + if (pos14 - offset > maxEnd) { + maxEnd = pos14 - offset; + } + } + + if ((nullBits[1] & 128) != 0) { + int fieldOffset15 = buf.getIntLE(offset + 90); + int pos15 = offset + 126 + fieldOffset15; + int dictLen = VarInt.peek(buf, pos15); + pos15 += VarInt.length(buf, pos15); + + for (int i = 0; i < dictLen; i++) { + pos15 += 4; + pos15 += ColorAlpha.computeBytesConsumed(buf, pos15); + } + + if (pos15 - offset > maxEnd) { + maxEnd = pos15 - offset; + } + } + + if ((nullBits[2] & 1) != 0) { + int fieldOffset16 = buf.getIntLE(offset + 94); + int pos16 = offset + 126 + fieldOffset16; + int dictLen = VarInt.peek(buf, pos16); + pos16 += VarInt.length(buf, pos16); + + for (int i = 0; i < dictLen; i++) { + pos16 += 4; + pos16 += Color.computeBytesConsumed(buf, pos16); + } + + if (pos16 - offset > maxEnd) { + maxEnd = pos16 - offset; + } + } + + if ((nullBits[2] & 2) != 0) { + int fieldOffset17 = buf.getIntLE(offset + 98); + int pos17 = offset + 126 + fieldOffset17; + int dictLen = VarInt.peek(buf, pos17); + pos17 += VarInt.length(buf, pos17); + + for (int i = 0; i < dictLen; i++) { + pos17 += 4; + pos17 += 4; + } + + if (pos17 - offset > maxEnd) { + maxEnd = pos17 - offset; + } + } + + if ((nullBits[2] & 4) != 0) { + int fieldOffset18 = buf.getIntLE(offset + 102); + int pos18 = offset + 126 + fieldOffset18; + int dictLen = VarInt.peek(buf, pos18); + pos18 += VarInt.length(buf, pos18); + + for (int i = 0; i < dictLen; i++) { + pos18 += 4; + pos18 += 4; + } + + if (pos18 - offset > maxEnd) { + maxEnd = pos18 - offset; + } + } + + if ((nullBits[2] & 8) != 0) { + int fieldOffset19 = buf.getIntLE(offset + 106); + int pos19 = offset + 126 + fieldOffset19; + int sl = VarInt.peek(buf, pos19); + pos19 += VarInt.length(buf, pos19) + sl; + if (pos19 - offset > maxEnd) { + maxEnd = pos19 - offset; + } + } + + if ((nullBits[2] & 16) != 0) { + int fieldOffset20 = buf.getIntLE(offset + 110); + int pos20 = offset + 126 + fieldOffset20; + int dictLen = VarInt.peek(buf, pos20); + pos20 += VarInt.length(buf, pos20); + + for (int i = 0; i < dictLen; i++) { + pos20 += 4; + pos20 += ColorAlpha.computeBytesConsumed(buf, pos20); + } + + if (pos20 - offset > maxEnd) { + maxEnd = pos20 - offset; + } + } + + if ((nullBits[2] & 32) != 0) { + int fieldOffset21 = buf.getIntLE(offset + 114); + int pos21 = offset + 126 + fieldOffset21; + int dictLen = VarInt.peek(buf, pos21); + pos21 += VarInt.length(buf, pos21); + + for (int i = 0; i < dictLen; i++) { + pos21 += 4; + pos21 += Color.computeBytesConsumed(buf, pos21); + } + + if (pos21 - offset > maxEnd) { + maxEnd = pos21 - offset; + } + } + + if ((nullBits[2] & 64) != 0) { + int fieldOffset22 = buf.getIntLE(offset + 118); + int pos22 = offset + 126 + fieldOffset22; + int dictLen = VarInt.peek(buf, pos22); + pos22 += VarInt.length(buf, pos22); + + for (int i = 0; i < dictLen; i++) { + pos22 += 4; + pos22 += Color.computeBytesConsumed(buf, pos22); + } + + if (pos22 - offset > maxEnd) { + maxEnd = pos22 - offset; + } + } + + if ((nullBits[2] & 128) != 0) { + int fieldOffset23 = buf.getIntLE(offset + 122); + int pos23 = offset + 126 + fieldOffset23; + pos23 += WeatherParticle.computeBytesConsumed(buf, pos23); + if (pos23 - offset > maxEnd) { + maxEnd = pos23 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte[] nullBits = new byte[4]; + if (this.id != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.tagIndexes != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.stars != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.moons != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.clouds != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.sunlightDampingMultiplier != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.sunlightColors != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.skyTopColors != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.skyBottomColors != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + if (this.skySunsetColors != null) { + nullBits[1] = (byte)(nullBits[1] | 2); + } + + if (this.sunColors != null) { + nullBits[1] = (byte)(nullBits[1] | 4); + } + + if (this.sunScales != null) { + nullBits[1] = (byte)(nullBits[1] | 8); + } + + if (this.sunGlowColors != null) { + nullBits[1] = (byte)(nullBits[1] | 16); + } + + if (this.moonColors != null) { + nullBits[1] = (byte)(nullBits[1] | 32); + } + + if (this.moonScales != null) { + nullBits[1] = (byte)(nullBits[1] | 64); + } + + if (this.moonGlowColors != null) { + nullBits[1] = (byte)(nullBits[1] | 128); + } + + if (this.fogColors != null) { + nullBits[2] = (byte)(nullBits[2] | 1); + } + + if (this.fogHeightFalloffs != null) { + nullBits[2] = (byte)(nullBits[2] | 2); + } + + if (this.fogDensities != null) { + nullBits[2] = (byte)(nullBits[2] | 4); + } + + if (this.screenEffect != null) { + nullBits[2] = (byte)(nullBits[2] | 8); + } + + if (this.screenEffectColors != null) { + nullBits[2] = (byte)(nullBits[2] | 16); + } + + if (this.colorFilters != null) { + nullBits[2] = (byte)(nullBits[2] | 32); + } + + if (this.waterTints != null) { + nullBits[2] = (byte)(nullBits[2] | 64); + } + + if (this.particle != null) { + nullBits[2] = (byte)(nullBits[2] | 128); + } + + if (this.fog != null) { + nullBits[3] = (byte)(nullBits[3] | 1); + } + + if (this.fogOptions != null) { + nullBits[3] = (byte)(nullBits[3] | 2); + } + + buf.writeBytes(nullBits); + if (this.fog != null) { + this.fog.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.fogOptions != null) { + this.fogOptions.serialize(buf); + } else { + buf.writeZero(18); + } + + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagIndexesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int starsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int moonsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cloudsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int sunlightDampingMultiplierOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int sunlightColorsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int skyTopColorsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int skyBottomColorsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int skySunsetColorsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int sunColorsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int sunScalesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int sunGlowColorsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int moonColorsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int moonScalesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int moonGlowColorsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int fogColorsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int fogHeightFalloffsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int fogDensitiesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int screenEffectOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int screenEffectColorsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int colorFiltersOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int waterTintsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int particleOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.tagIndexes != null) { + buf.setIntLE(tagIndexesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tagIndexes.length > 4096000) { + throw ProtocolException.arrayTooLong("TagIndexes", this.tagIndexes.length, 4096000); + } + + VarInt.write(buf, this.tagIndexes.length); + + for (int item : this.tagIndexes) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagIndexesOffsetSlot, -1); + } + + if (this.stars != null) { + buf.setIntLE(starsOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.stars, 4096000); + } else { + buf.setIntLE(starsOffsetSlot, -1); + } + + if (this.moons != null) { + buf.setIntLE(moonsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.moons.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Moons", this.moons.size(), 4096000); + } + + VarInt.write(buf, this.moons.size()); + + for (Entry e : this.moons.entrySet()) { + buf.writeIntLE(e.getKey()); + PacketIO.writeVarString(buf, e.getValue(), 4096000); + } + } else { + buf.setIntLE(moonsOffsetSlot, -1); + } + + if (this.clouds != null) { + buf.setIntLE(cloudsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.clouds.length > 4096000) { + throw ProtocolException.arrayTooLong("Clouds", this.clouds.length, 4096000); + } + + VarInt.write(buf, this.clouds.length); + + for (Cloud item : this.clouds) { + item.serialize(buf); + } + } else { + buf.setIntLE(cloudsOffsetSlot, -1); + } + + if (this.sunlightDampingMultiplier != null) { + buf.setIntLE(sunlightDampingMultiplierOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.sunlightDampingMultiplier.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("SunlightDampingMultiplier", this.sunlightDampingMultiplier.size(), 4096000); + } + + VarInt.write(buf, this.sunlightDampingMultiplier.size()); + + for (Entry e : this.sunlightDampingMultiplier.entrySet()) { + buf.writeFloatLE(e.getKey()); + buf.writeFloatLE(e.getValue()); + } + } else { + buf.setIntLE(sunlightDampingMultiplierOffsetSlot, -1); + } + + if (this.sunlightColors != null) { + buf.setIntLE(sunlightColorsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.sunlightColors.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("SunlightColors", this.sunlightColors.size(), 4096000); + } + + VarInt.write(buf, this.sunlightColors.size()); + + for (Entry e : this.sunlightColors.entrySet()) { + buf.writeFloatLE(e.getKey()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(sunlightColorsOffsetSlot, -1); + } + + if (this.skyTopColors != null) { + buf.setIntLE(skyTopColorsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.skyTopColors.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("SkyTopColors", this.skyTopColors.size(), 4096000); + } + + VarInt.write(buf, this.skyTopColors.size()); + + for (Entry e : this.skyTopColors.entrySet()) { + buf.writeFloatLE(e.getKey()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(skyTopColorsOffsetSlot, -1); + } + + if (this.skyBottomColors != null) { + buf.setIntLE(skyBottomColorsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.skyBottomColors.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("SkyBottomColors", this.skyBottomColors.size(), 4096000); + } + + VarInt.write(buf, this.skyBottomColors.size()); + + for (Entry e : this.skyBottomColors.entrySet()) { + buf.writeFloatLE(e.getKey()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(skyBottomColorsOffsetSlot, -1); + } + + if (this.skySunsetColors != null) { + buf.setIntLE(skySunsetColorsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.skySunsetColors.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("SkySunsetColors", this.skySunsetColors.size(), 4096000); + } + + VarInt.write(buf, this.skySunsetColors.size()); + + for (Entry e : this.skySunsetColors.entrySet()) { + buf.writeFloatLE(e.getKey()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(skySunsetColorsOffsetSlot, -1); + } + + if (this.sunColors != null) { + buf.setIntLE(sunColorsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.sunColors.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("SunColors", this.sunColors.size(), 4096000); + } + + VarInt.write(buf, this.sunColors.size()); + + for (Entry e : this.sunColors.entrySet()) { + buf.writeFloatLE(e.getKey()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(sunColorsOffsetSlot, -1); + } + + if (this.sunScales != null) { + buf.setIntLE(sunScalesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.sunScales.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("SunScales", this.sunScales.size(), 4096000); + } + + VarInt.write(buf, this.sunScales.size()); + + for (Entry e : this.sunScales.entrySet()) { + buf.writeFloatLE(e.getKey()); + buf.writeFloatLE(e.getValue()); + } + } else { + buf.setIntLE(sunScalesOffsetSlot, -1); + } + + if (this.sunGlowColors != null) { + buf.setIntLE(sunGlowColorsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.sunGlowColors.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("SunGlowColors", this.sunGlowColors.size(), 4096000); + } + + VarInt.write(buf, this.sunGlowColors.size()); + + for (Entry e : this.sunGlowColors.entrySet()) { + buf.writeFloatLE(e.getKey()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(sunGlowColorsOffsetSlot, -1); + } + + if (this.moonColors != null) { + buf.setIntLE(moonColorsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.moonColors.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("MoonColors", this.moonColors.size(), 4096000); + } + + VarInt.write(buf, this.moonColors.size()); + + for (Entry e : this.moonColors.entrySet()) { + buf.writeFloatLE(e.getKey()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(moonColorsOffsetSlot, -1); + } + + if (this.moonScales != null) { + buf.setIntLE(moonScalesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.moonScales.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("MoonScales", this.moonScales.size(), 4096000); + } + + VarInt.write(buf, this.moonScales.size()); + + for (Entry e : this.moonScales.entrySet()) { + buf.writeFloatLE(e.getKey()); + buf.writeFloatLE(e.getValue()); + } + } else { + buf.setIntLE(moonScalesOffsetSlot, -1); + } + + if (this.moonGlowColors != null) { + buf.setIntLE(moonGlowColorsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.moonGlowColors.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("MoonGlowColors", this.moonGlowColors.size(), 4096000); + } + + VarInt.write(buf, this.moonGlowColors.size()); + + for (Entry e : this.moonGlowColors.entrySet()) { + buf.writeFloatLE(e.getKey()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(moonGlowColorsOffsetSlot, -1); + } + + if (this.fogColors != null) { + buf.setIntLE(fogColorsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.fogColors.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("FogColors", this.fogColors.size(), 4096000); + } + + VarInt.write(buf, this.fogColors.size()); + + for (Entry e : this.fogColors.entrySet()) { + buf.writeFloatLE(e.getKey()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(fogColorsOffsetSlot, -1); + } + + if (this.fogHeightFalloffs != null) { + buf.setIntLE(fogHeightFalloffsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.fogHeightFalloffs.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("FogHeightFalloffs", this.fogHeightFalloffs.size(), 4096000); + } + + VarInt.write(buf, this.fogHeightFalloffs.size()); + + for (Entry e : this.fogHeightFalloffs.entrySet()) { + buf.writeFloatLE(e.getKey()); + buf.writeFloatLE(e.getValue()); + } + } else { + buf.setIntLE(fogHeightFalloffsOffsetSlot, -1); + } + + if (this.fogDensities != null) { + buf.setIntLE(fogDensitiesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.fogDensities.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("FogDensities", this.fogDensities.size(), 4096000); + } + + VarInt.write(buf, this.fogDensities.size()); + + for (Entry e : this.fogDensities.entrySet()) { + buf.writeFloatLE(e.getKey()); + buf.writeFloatLE(e.getValue()); + } + } else { + buf.setIntLE(fogDensitiesOffsetSlot, -1); + } + + if (this.screenEffect != null) { + buf.setIntLE(screenEffectOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.screenEffect, 4096000); + } else { + buf.setIntLE(screenEffectOffsetSlot, -1); + } + + if (this.screenEffectColors != null) { + buf.setIntLE(screenEffectColorsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.screenEffectColors.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("ScreenEffectColors", this.screenEffectColors.size(), 4096000); + } + + VarInt.write(buf, this.screenEffectColors.size()); + + for (Entry e : this.screenEffectColors.entrySet()) { + buf.writeFloatLE(e.getKey()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(screenEffectColorsOffsetSlot, -1); + } + + if (this.colorFilters != null) { + buf.setIntLE(colorFiltersOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.colorFilters.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("ColorFilters", this.colorFilters.size(), 4096000); + } + + VarInt.write(buf, this.colorFilters.size()); + + for (Entry e : this.colorFilters.entrySet()) { + buf.writeFloatLE(e.getKey()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(colorFiltersOffsetSlot, -1); + } + + if (this.waterTints != null) { + buf.setIntLE(waterTintsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.waterTints.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("WaterTints", this.waterTints.size(), 4096000); + } + + VarInt.write(buf, this.waterTints.size()); + + for (Entry e : this.waterTints.entrySet()) { + buf.writeFloatLE(e.getKey()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(waterTintsOffsetSlot, -1); + } + + if (this.particle != null) { + buf.setIntLE(particleOffsetSlot, buf.writerIndex() - varBlockStart); + this.particle.serialize(buf); + } else { + buf.setIntLE(particleOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 126; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.tagIndexes != null) { + size += VarInt.size(this.tagIndexes.length) + this.tagIndexes.length * 4; + } + + if (this.stars != null) { + size += PacketIO.stringSize(this.stars); + } + + if (this.moons != null) { + int moonsSize = 0; + + for (Entry kvp : this.moons.entrySet()) { + moonsSize += 4 + PacketIO.stringSize(kvp.getValue()); + } + + size += VarInt.size(this.moons.size()) + moonsSize; + } + + if (this.clouds != null) { + int cloudsSize = 0; + + for (Cloud elem : this.clouds) { + cloudsSize += elem.computeSize(); + } + + size += VarInt.size(this.clouds.length) + cloudsSize; + } + + if (this.sunlightDampingMultiplier != null) { + size += VarInt.size(this.sunlightDampingMultiplier.size()) + this.sunlightDampingMultiplier.size() * 8; + } + + if (this.sunlightColors != null) { + size += VarInt.size(this.sunlightColors.size()) + this.sunlightColors.size() * 7; + } + + if (this.skyTopColors != null) { + size += VarInt.size(this.skyTopColors.size()) + this.skyTopColors.size() * 8; + } + + if (this.skyBottomColors != null) { + size += VarInt.size(this.skyBottomColors.size()) + this.skyBottomColors.size() * 8; + } + + if (this.skySunsetColors != null) { + size += VarInt.size(this.skySunsetColors.size()) + this.skySunsetColors.size() * 8; + } + + if (this.sunColors != null) { + size += VarInt.size(this.sunColors.size()) + this.sunColors.size() * 7; + } + + if (this.sunScales != null) { + size += VarInt.size(this.sunScales.size()) + this.sunScales.size() * 8; + } + + if (this.sunGlowColors != null) { + size += VarInt.size(this.sunGlowColors.size()) + this.sunGlowColors.size() * 8; + } + + if (this.moonColors != null) { + size += VarInt.size(this.moonColors.size()) + this.moonColors.size() * 8; + } + + if (this.moonScales != null) { + size += VarInt.size(this.moonScales.size()) + this.moonScales.size() * 8; + } + + if (this.moonGlowColors != null) { + size += VarInt.size(this.moonGlowColors.size()) + this.moonGlowColors.size() * 8; + } + + if (this.fogColors != null) { + size += VarInt.size(this.fogColors.size()) + this.fogColors.size() * 7; + } + + if (this.fogHeightFalloffs != null) { + size += VarInt.size(this.fogHeightFalloffs.size()) + this.fogHeightFalloffs.size() * 8; + } + + if (this.fogDensities != null) { + size += VarInt.size(this.fogDensities.size()) + this.fogDensities.size() * 8; + } + + if (this.screenEffect != null) { + size += PacketIO.stringSize(this.screenEffect); + } + + if (this.screenEffectColors != null) { + size += VarInt.size(this.screenEffectColors.size()) + this.screenEffectColors.size() * 8; + } + + if (this.colorFilters != null) { + size += VarInt.size(this.colorFilters.size()) + this.colorFilters.size() * 7; + } + + if (this.waterTints != null) { + size += VarInt.size(this.waterTints.size()) + this.waterTints.size() * 7; + } + + if (this.particle != null) { + size += this.particle.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 126) { + return ValidationResult.error("Buffer too small: expected at least 126 bytes"); + } else { + byte[] nullBits = PacketIO.readBytes(buffer, offset, 4); + if ((nullBits[0] & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 30); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 126 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits[0] & 2) != 0) { + int tagIndexesOffset = buffer.getIntLE(offset + 34); + if (tagIndexesOffset < 0) { + return ValidationResult.error("Invalid offset for TagIndexes"); + } + + int posx = offset + 126 + tagIndexesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for TagIndexes"); + } + + int tagIndexesCount = VarInt.peek(buffer, posx); + if (tagIndexesCount < 0) { + return ValidationResult.error("Invalid array count for TagIndexes"); + } + + if (tagIndexesCount > 4096000) { + return ValidationResult.error("TagIndexes exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += tagIndexesCount * 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading TagIndexes"); + } + } + + if ((nullBits[0] & 4) != 0) { + int starsOffset = buffer.getIntLE(offset + 38); + if (starsOffset < 0) { + return ValidationResult.error("Invalid offset for Stars"); + } + + int posxx = offset + 126 + starsOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Stars"); + } + + int starsLen = VarInt.peek(buffer, posxx); + if (starsLen < 0) { + return ValidationResult.error("Invalid string length for Stars"); + } + + if (starsLen > 4096000) { + return ValidationResult.error("Stars exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += starsLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Stars"); + } + } + + if ((nullBits[0] & 8) != 0) { + int moonsOffset = buffer.getIntLE(offset + 42); + if (moonsOffset < 0) { + return ValidationResult.error("Invalid offset for Moons"); + } + + int posxxx = offset + 126 + moonsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Moons"); + } + + int moonsCount = VarInt.peek(buffer, posxxx); + if (moonsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Moons"); + } + + if (moonsCount > 4096000) { + return ValidationResult.error("Moons exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + + for (int i = 0; i < moonsCount; i++) { + posxxx += 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + int valueLen = VarInt.peek(buffer, posxxx); + if (valueLen < 0) { + return ValidationResult.error("Invalid string length for value"); + } + + if (valueLen > 4096000) { + return ValidationResult.error("value exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += valueLen; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + if ((nullBits[0] & 16) != 0) { + int cloudsOffset = buffer.getIntLE(offset + 46); + if (cloudsOffset < 0) { + return ValidationResult.error("Invalid offset for Clouds"); + } + + int posxxxx = offset + 126 + cloudsOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Clouds"); + } + + int cloudsCount = VarInt.peek(buffer, posxxxx); + if (cloudsCount < 0) { + return ValidationResult.error("Invalid array count for Clouds"); + } + + if (cloudsCount > 4096000) { + return ValidationResult.error("Clouds exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + + for (int i = 0; i < cloudsCount; i++) { + ValidationResult structResult = Cloud.validateStructure(buffer, posxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid Cloud in Clouds[" + i + "]: " + structResult.error()); + } + + posxxxx += Cloud.computeBytesConsumed(buffer, posxxxx); + } + } + + if ((nullBits[0] & 32) != 0) { + int sunlightDampingMultiplierOffset = buffer.getIntLE(offset + 50); + if (sunlightDampingMultiplierOffset < 0) { + return ValidationResult.error("Invalid offset for SunlightDampingMultiplier"); + } + + int posxxxxx = offset + 126 + sunlightDampingMultiplierOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SunlightDampingMultiplier"); + } + + int sunlightDampingMultiplierCount = VarInt.peek(buffer, posxxxxx); + if (sunlightDampingMultiplierCount < 0) { + return ValidationResult.error("Invalid dictionary count for SunlightDampingMultiplier"); + } + + if (sunlightDampingMultiplierCount > 4096000) { + return ValidationResult.error("SunlightDampingMultiplier exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + + for (int i = 0; i < sunlightDampingMultiplierCount; i++) { + posxxxxx += 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxx += 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + if ((nullBits[0] & 64) != 0) { + int sunlightColorsOffset = buffer.getIntLE(offset + 54); + if (sunlightColorsOffset < 0) { + return ValidationResult.error("Invalid offset for SunlightColors"); + } + + int posxxxxxx = offset + 126 + sunlightColorsOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SunlightColors"); + } + + int sunlightColorsCount = VarInt.peek(buffer, posxxxxxx); + if (sunlightColorsCount < 0) { + return ValidationResult.error("Invalid dictionary count for SunlightColors"); + } + + if (sunlightColorsCount > 4096000) { + return ValidationResult.error("SunlightColors exceeds max length 4096000"); + } + + posxxxxxx += VarInt.length(buffer, posxxxxxx); + + for (int i = 0; i < sunlightColorsCount; i++) { + posxxxxxx += 4; + if (posxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxx += 3; + } + } + + if ((nullBits[0] & 128) != 0) { + int skyTopColorsOffset = buffer.getIntLE(offset + 58); + if (skyTopColorsOffset < 0) { + return ValidationResult.error("Invalid offset for SkyTopColors"); + } + + int posxxxxxxx = offset + 126 + skyTopColorsOffset; + if (posxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SkyTopColors"); + } + + int skyTopColorsCount = VarInt.peek(buffer, posxxxxxxx); + if (skyTopColorsCount < 0) { + return ValidationResult.error("Invalid dictionary count for SkyTopColors"); + } + + if (skyTopColorsCount > 4096000) { + return ValidationResult.error("SkyTopColors exceeds max length 4096000"); + } + + posxxxxxxx += VarInt.length(buffer, posxxxxxxx); + + for (int i = 0; i < skyTopColorsCount; i++) { + posxxxxxxx += 4; + if (posxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxx += 4; + } + } + + if ((nullBits[1] & 1) != 0) { + int skyBottomColorsOffset = buffer.getIntLE(offset + 62); + if (skyBottomColorsOffset < 0) { + return ValidationResult.error("Invalid offset for SkyBottomColors"); + } + + int posxxxxxxxx = offset + 126 + skyBottomColorsOffset; + if (posxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SkyBottomColors"); + } + + int skyBottomColorsCount = VarInt.peek(buffer, posxxxxxxxx); + if (skyBottomColorsCount < 0) { + return ValidationResult.error("Invalid dictionary count for SkyBottomColors"); + } + + if (skyBottomColorsCount > 4096000) { + return ValidationResult.error("SkyBottomColors exceeds max length 4096000"); + } + + posxxxxxxxx += VarInt.length(buffer, posxxxxxxxx); + + for (int i = 0; i < skyBottomColorsCount; i++) { + posxxxxxxxx += 4; + if (posxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxxx += 4; + } + } + + if ((nullBits[1] & 2) != 0) { + int skySunsetColorsOffset = buffer.getIntLE(offset + 66); + if (skySunsetColorsOffset < 0) { + return ValidationResult.error("Invalid offset for SkySunsetColors"); + } + + int posxxxxxxxxx = offset + 126 + skySunsetColorsOffset; + if (posxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SkySunsetColors"); + } + + int skySunsetColorsCount = VarInt.peek(buffer, posxxxxxxxxx); + if (skySunsetColorsCount < 0) { + return ValidationResult.error("Invalid dictionary count for SkySunsetColors"); + } + + if (skySunsetColorsCount > 4096000) { + return ValidationResult.error("SkySunsetColors exceeds max length 4096000"); + } + + posxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxx); + + for (int i = 0; i < skySunsetColorsCount; i++) { + posxxxxxxxxx += 4; + if (posxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxxxx += 4; + } + } + + if ((nullBits[1] & 4) != 0) { + int sunColorsOffset = buffer.getIntLE(offset + 70); + if (sunColorsOffset < 0) { + return ValidationResult.error("Invalid offset for SunColors"); + } + + int posxxxxxxxxxx = offset + 126 + sunColorsOffset; + if (posxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SunColors"); + } + + int sunColorsCount = VarInt.peek(buffer, posxxxxxxxxxx); + if (sunColorsCount < 0) { + return ValidationResult.error("Invalid dictionary count for SunColors"); + } + + if (sunColorsCount > 4096000) { + return ValidationResult.error("SunColors exceeds max length 4096000"); + } + + posxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxx); + + for (int i = 0; i < sunColorsCount; i++) { + posxxxxxxxxxx += 4; + if (posxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxxxxx += 3; + } + } + + if ((nullBits[1] & 8) != 0) { + int sunScalesOffset = buffer.getIntLE(offset + 74); + if (sunScalesOffset < 0) { + return ValidationResult.error("Invalid offset for SunScales"); + } + + int posxxxxxxxxxxx = offset + 126 + sunScalesOffset; + if (posxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SunScales"); + } + + int sunScalesCount = VarInt.peek(buffer, posxxxxxxxxxxx); + if (sunScalesCount < 0) { + return ValidationResult.error("Invalid dictionary count for SunScales"); + } + + if (sunScalesCount > 4096000) { + return ValidationResult.error("SunScales exceeds max length 4096000"); + } + + posxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxx); + + for (int i = 0; i < sunScalesCount; i++) { + posxxxxxxxxxxx += 4; + if (posxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxxxxxx += 4; + if (posxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + if ((nullBits[1] & 16) != 0) { + int sunGlowColorsOffset = buffer.getIntLE(offset + 78); + if (sunGlowColorsOffset < 0) { + return ValidationResult.error("Invalid offset for SunGlowColors"); + } + + int posxxxxxxxxxxxx = offset + 126 + sunGlowColorsOffset; + if (posxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SunGlowColors"); + } + + int sunGlowColorsCount = VarInt.peek(buffer, posxxxxxxxxxxxx); + if (sunGlowColorsCount < 0) { + return ValidationResult.error("Invalid dictionary count for SunGlowColors"); + } + + if (sunGlowColorsCount > 4096000) { + return ValidationResult.error("SunGlowColors exceeds max length 4096000"); + } + + posxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxx); + + for (int i = 0; i < sunGlowColorsCount; i++) { + posxxxxxxxxxxxx += 4; + if (posxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxxxxxxx += 4; + } + } + + if ((nullBits[1] & 32) != 0) { + int moonColorsOffset = buffer.getIntLE(offset + 82); + if (moonColorsOffset < 0) { + return ValidationResult.error("Invalid offset for MoonColors"); + } + + int posxxxxxxxxxxxxx = offset + 126 + moonColorsOffset; + if (posxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MoonColors"); + } + + int moonColorsCount = VarInt.peek(buffer, posxxxxxxxxxxxxx); + if (moonColorsCount < 0) { + return ValidationResult.error("Invalid dictionary count for MoonColors"); + } + + if (moonColorsCount > 4096000) { + return ValidationResult.error("MoonColors exceeds max length 4096000"); + } + + posxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxx); + + for (int i = 0; i < moonColorsCount; i++) { + posxxxxxxxxxxxxx += 4; + if (posxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxxxxxxxx += 4; + } + } + + if ((nullBits[1] & 64) != 0) { + int moonScalesOffset = buffer.getIntLE(offset + 86); + if (moonScalesOffset < 0) { + return ValidationResult.error("Invalid offset for MoonScales"); + } + + int posxxxxxxxxxxxxxx = offset + 126 + moonScalesOffset; + if (posxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MoonScales"); + } + + int moonScalesCount = VarInt.peek(buffer, posxxxxxxxxxxxxxx); + if (moonScalesCount < 0) { + return ValidationResult.error("Invalid dictionary count for MoonScales"); + } + + if (moonScalesCount > 4096000) { + return ValidationResult.error("MoonScales exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxx); + + for (int i = 0; i < moonScalesCount; i++) { + posxxxxxxxxxxxxxx += 4; + if (posxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxxxxxxxxx += 4; + if (posxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + if ((nullBits[1] & 128) != 0) { + int moonGlowColorsOffset = buffer.getIntLE(offset + 90); + if (moonGlowColorsOffset < 0) { + return ValidationResult.error("Invalid offset for MoonGlowColors"); + } + + int posxxxxxxxxxxxxxxx = offset + 126 + moonGlowColorsOffset; + if (posxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MoonGlowColors"); + } + + int moonGlowColorsCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxx); + if (moonGlowColorsCount < 0) { + return ValidationResult.error("Invalid dictionary count for MoonGlowColors"); + } + + if (moonGlowColorsCount > 4096000) { + return ValidationResult.error("MoonGlowColors exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxx); + + for (int i = 0; i < moonGlowColorsCount; i++) { + posxxxxxxxxxxxxxxx += 4; + if (posxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxxxxxxxxxx += 4; + } + } + + if ((nullBits[2] & 1) != 0) { + int fogColorsOffset = buffer.getIntLE(offset + 94); + if (fogColorsOffset < 0) { + return ValidationResult.error("Invalid offset for FogColors"); + } + + int posxxxxxxxxxxxxxxxx = offset + 126 + fogColorsOffset; + if (posxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FogColors"); + } + + int fogColorsCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxx); + if (fogColorsCount < 0) { + return ValidationResult.error("Invalid dictionary count for FogColors"); + } + + if (fogColorsCount > 4096000) { + return ValidationResult.error("FogColors exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxx); + + for (int i = 0; i < fogColorsCount; i++) { + posxxxxxxxxxxxxxxxx += 4; + if (posxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxxxxxxxxxxx += 3; + } + } + + if ((nullBits[2] & 2) != 0) { + int fogHeightFalloffsOffset = buffer.getIntLE(offset + 98); + if (fogHeightFalloffsOffset < 0) { + return ValidationResult.error("Invalid offset for FogHeightFalloffs"); + } + + int posxxxxxxxxxxxxxxxxx = offset + 126 + fogHeightFalloffsOffset; + if (posxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FogHeightFalloffs"); + } + + int fogHeightFalloffsCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxx); + if (fogHeightFalloffsCount < 0) { + return ValidationResult.error("Invalid dictionary count for FogHeightFalloffs"); + } + + if (fogHeightFalloffsCount > 4096000) { + return ValidationResult.error("FogHeightFalloffs exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxx); + + for (int i = 0; i < fogHeightFalloffsCount; i++) { + posxxxxxxxxxxxxxxxxx += 4; + if (posxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxxxxxxxxxxxx += 4; + if (posxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + if ((nullBits[2] & 4) != 0) { + int fogDensitiesOffset = buffer.getIntLE(offset + 102); + if (fogDensitiesOffset < 0) { + return ValidationResult.error("Invalid offset for FogDensities"); + } + + int posxxxxxxxxxxxxxxxxxx = offset + 126 + fogDensitiesOffset; + if (posxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FogDensities"); + } + + int fogDensitiesCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxx); + if (fogDensitiesCount < 0) { + return ValidationResult.error("Invalid dictionary count for FogDensities"); + } + + if (fogDensitiesCount > 4096000) { + return ValidationResult.error("FogDensities exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxx); + + for (int i = 0; i < fogDensitiesCount; i++) { + posxxxxxxxxxxxxxxxxxx += 4; + if (posxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxxxxxxxxxxxxx += 4; + if (posxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + if ((nullBits[2] & 8) != 0) { + int screenEffectOffset = buffer.getIntLE(offset + 106); + if (screenEffectOffset < 0) { + return ValidationResult.error("Invalid offset for ScreenEffect"); + } + + int posxxxxxxxxxxxxxxxxxxx = offset + 126 + screenEffectOffset; + if (posxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ScreenEffect"); + } + + int screenEffectLen = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxxx); + if (screenEffectLen < 0) { + return ValidationResult.error("Invalid string length for ScreenEffect"); + } + + if (screenEffectLen > 4096000) { + return ValidationResult.error("ScreenEffect exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxxx); + posxxxxxxxxxxxxxxxxxxx += screenEffectLen; + if (posxxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ScreenEffect"); + } + } + + if ((nullBits[2] & 16) != 0) { + int screenEffectColorsOffset = buffer.getIntLE(offset + 110); + if (screenEffectColorsOffset < 0) { + return ValidationResult.error("Invalid offset for ScreenEffectColors"); + } + + int posxxxxxxxxxxxxxxxxxxxx = offset + 126 + screenEffectColorsOffset; + if (posxxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ScreenEffectColors"); + } + + int screenEffectColorsCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxxxx); + if (screenEffectColorsCount < 0) { + return ValidationResult.error("Invalid dictionary count for ScreenEffectColors"); + } + + if (screenEffectColorsCount > 4096000) { + return ValidationResult.error("ScreenEffectColors exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxxxx); + + for (int i = 0; i < screenEffectColorsCount; i++) { + posxxxxxxxxxxxxxxxxxxxx += 4; + if (posxxxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxxxxxxxxxxxxxxx += 4; + } + } + + if ((nullBits[2] & 32) != 0) { + int colorFiltersOffset = buffer.getIntLE(offset + 114); + if (colorFiltersOffset < 0) { + return ValidationResult.error("Invalid offset for ColorFilters"); + } + + int posxxxxxxxxxxxxxxxxxxxxx = offset + 126 + colorFiltersOffset; + if (posxxxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ColorFilters"); + } + + int colorFiltersCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxxxxx); + if (colorFiltersCount < 0) { + return ValidationResult.error("Invalid dictionary count for ColorFilters"); + } + + if (colorFiltersCount > 4096000) { + return ValidationResult.error("ColorFilters exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxxxxx); + + for (int i = 0; i < colorFiltersCount; i++) { + posxxxxxxxxxxxxxxxxxxxxx += 4; + if (posxxxxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxxxxxxxxxxxxxxxx += 3; + } + } + + if ((nullBits[2] & 64) != 0) { + int waterTintsOffset = buffer.getIntLE(offset + 118); + if (waterTintsOffset < 0) { + return ValidationResult.error("Invalid offset for WaterTints"); + } + + int posxxxxxxxxxxxxxxxxxxxxxx = offset + 126 + waterTintsOffset; + if (posxxxxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for WaterTints"); + } + + int waterTintsCount = VarInt.peek(buffer, posxxxxxxxxxxxxxxxxxxxxxx); + if (waterTintsCount < 0) { + return ValidationResult.error("Invalid dictionary count for WaterTints"); + } + + if (waterTintsCount > 4096000) { + return ValidationResult.error("WaterTints exceeds max length 4096000"); + } + + posxxxxxxxxxxxxxxxxxxxxxx += VarInt.length(buffer, posxxxxxxxxxxxxxxxxxxxxxx); + + for (int i = 0; i < waterTintsCount; i++) { + posxxxxxxxxxxxxxxxxxxxxxx += 4; + if (posxxxxxxxxxxxxxxxxxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxxxxxxxxxxxxxxxxxxx += 3; + } + } + + if ((nullBits[2] & 128) != 0) { + int particleOffset = buffer.getIntLE(offset + 122); + if (particleOffset < 0) { + return ValidationResult.error("Invalid offset for Particle"); + } + + int posxxxxxxxxxxxxxxxxxxxxxxx = offset + 126 + particleOffset; + if (posxxxxxxxxxxxxxxxxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Particle"); + } + + ValidationResult particleResult = WeatherParticle.validateStructure(buffer, posxxxxxxxxxxxxxxxxxxxxxxx); + if (!particleResult.isValid()) { + return ValidationResult.error("Invalid Particle: " + particleResult.error()); + } + + posxxxxxxxxxxxxxxxxxxxxxxx += WeatherParticle.computeBytesConsumed(buffer, posxxxxxxxxxxxxxxxxxxxxxxx); + } + + return ValidationResult.OK; + } + } + + public Weather clone() { + Weather copy = new Weather(); + copy.id = this.id; + copy.tagIndexes = this.tagIndexes != null ? Arrays.copyOf(this.tagIndexes, this.tagIndexes.length) : null; + copy.stars = this.stars; + copy.moons = this.moons != null ? new HashMap<>(this.moons) : null; + copy.clouds = this.clouds != null ? Arrays.stream(this.clouds).map(ex -> ex.clone()).toArray(Cloud[]::new) : null; + copy.sunlightDampingMultiplier = this.sunlightDampingMultiplier != null ? new HashMap<>(this.sunlightDampingMultiplier) : null; + if (this.sunlightColors != null) { + Map m = new HashMap<>(); + + for (Entry e : this.sunlightColors.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.sunlightColors = m; + } + + if (this.skyTopColors != null) { + Map m = new HashMap<>(); + + for (Entry e : this.skyTopColors.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.skyTopColors = m; + } + + if (this.skyBottomColors != null) { + Map m = new HashMap<>(); + + for (Entry e : this.skyBottomColors.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.skyBottomColors = m; + } + + if (this.skySunsetColors != null) { + Map m = new HashMap<>(); + + for (Entry e : this.skySunsetColors.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.skySunsetColors = m; + } + + if (this.sunColors != null) { + Map m = new HashMap<>(); + + for (Entry e : this.sunColors.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.sunColors = m; + } + + copy.sunScales = this.sunScales != null ? new HashMap<>(this.sunScales) : null; + if (this.sunGlowColors != null) { + Map m = new HashMap<>(); + + for (Entry e : this.sunGlowColors.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.sunGlowColors = m; + } + + if (this.moonColors != null) { + Map m = new HashMap<>(); + + for (Entry e : this.moonColors.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.moonColors = m; + } + + copy.moonScales = this.moonScales != null ? new HashMap<>(this.moonScales) : null; + if (this.moonGlowColors != null) { + Map m = new HashMap<>(); + + for (Entry e : this.moonGlowColors.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.moonGlowColors = m; + } + + if (this.fogColors != null) { + Map m = new HashMap<>(); + + for (Entry e : this.fogColors.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.fogColors = m; + } + + copy.fogHeightFalloffs = this.fogHeightFalloffs != null ? new HashMap<>(this.fogHeightFalloffs) : null; + copy.fogDensities = this.fogDensities != null ? new HashMap<>(this.fogDensities) : null; + copy.screenEffect = this.screenEffect; + if (this.screenEffectColors != null) { + Map m = new HashMap<>(); + + for (Entry e : this.screenEffectColors.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.screenEffectColors = m; + } + + if (this.colorFilters != null) { + Map m = new HashMap<>(); + + for (Entry e : this.colorFilters.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.colorFilters = m; + } + + if (this.waterTints != null) { + Map m = new HashMap<>(); + + for (Entry e : this.waterTints.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.waterTints = m; + } + + copy.particle = this.particle != null ? this.particle.clone() : null; + copy.fog = this.fog != null ? this.fog.clone() : null; + copy.fogOptions = this.fogOptions != null ? this.fogOptions.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Weather other) + ? false + : Objects.equals(this.id, other.id) + && Arrays.equals(this.tagIndexes, other.tagIndexes) + && Objects.equals(this.stars, other.stars) + && Objects.equals(this.moons, other.moons) + && Arrays.equals((Object[])this.clouds, (Object[])other.clouds) + && Objects.equals(this.sunlightDampingMultiplier, other.sunlightDampingMultiplier) + && Objects.equals(this.sunlightColors, other.sunlightColors) + && Objects.equals(this.skyTopColors, other.skyTopColors) + && Objects.equals(this.skyBottomColors, other.skyBottomColors) + && Objects.equals(this.skySunsetColors, other.skySunsetColors) + && Objects.equals(this.sunColors, other.sunColors) + && Objects.equals(this.sunScales, other.sunScales) + && Objects.equals(this.sunGlowColors, other.sunGlowColors) + && Objects.equals(this.moonColors, other.moonColors) + && Objects.equals(this.moonScales, other.moonScales) + && Objects.equals(this.moonGlowColors, other.moonGlowColors) + && Objects.equals(this.fogColors, other.fogColors) + && Objects.equals(this.fogHeightFalloffs, other.fogHeightFalloffs) + && Objects.equals(this.fogDensities, other.fogDensities) + && Objects.equals(this.screenEffect, other.screenEffect) + && Objects.equals(this.screenEffectColors, other.screenEffectColors) + && Objects.equals(this.colorFilters, other.colorFilters) + && Objects.equals(this.waterTints, other.waterTints) + && Objects.equals(this.particle, other.particle) + && Objects.equals(this.fog, other.fog) + && Objects.equals(this.fogOptions, other.fogOptions); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.id); + result = 31 * result + Arrays.hashCode(this.tagIndexes); + result = 31 * result + Objects.hashCode(this.stars); + result = 31 * result + Objects.hashCode(this.moons); + result = 31 * result + Arrays.hashCode((Object[])this.clouds); + result = 31 * result + Objects.hashCode(this.sunlightDampingMultiplier); + result = 31 * result + Objects.hashCode(this.sunlightColors); + result = 31 * result + Objects.hashCode(this.skyTopColors); + result = 31 * result + Objects.hashCode(this.skyBottomColors); + result = 31 * result + Objects.hashCode(this.skySunsetColors); + result = 31 * result + Objects.hashCode(this.sunColors); + result = 31 * result + Objects.hashCode(this.sunScales); + result = 31 * result + Objects.hashCode(this.sunGlowColors); + result = 31 * result + Objects.hashCode(this.moonColors); + result = 31 * result + Objects.hashCode(this.moonScales); + result = 31 * result + Objects.hashCode(this.moonGlowColors); + result = 31 * result + Objects.hashCode(this.fogColors); + result = 31 * result + Objects.hashCode(this.fogHeightFalloffs); + result = 31 * result + Objects.hashCode(this.fogDensities); + result = 31 * result + Objects.hashCode(this.screenEffect); + result = 31 * result + Objects.hashCode(this.screenEffectColors); + result = 31 * result + Objects.hashCode(this.colorFilters); + result = 31 * result + Objects.hashCode(this.waterTints); + result = 31 * result + Objects.hashCode(this.particle); + result = 31 * result + Objects.hashCode(this.fog); + return 31 * result + Objects.hashCode(this.fogOptions); + } +} diff --git a/src/com/hypixel/hytale/protocol/WeatherParticle.java b/src/com/hypixel/hytale/protocol/WeatherParticle.java new file mode 100644 index 0000000..703f65b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/WeatherParticle.java @@ -0,0 +1,176 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WeatherParticle { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 13; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 16384018; + @Nullable + public String systemId; + @Nullable + public Color color; + public float scale; + public boolean isOvergroundOnly; + public float positionOffsetMultiplier; + + public WeatherParticle() { + } + + public WeatherParticle(@Nullable String systemId, @Nullable Color color, float scale, boolean isOvergroundOnly, float positionOffsetMultiplier) { + this.systemId = systemId; + this.color = color; + this.scale = scale; + this.isOvergroundOnly = isOvergroundOnly; + this.positionOffsetMultiplier = positionOffsetMultiplier; + } + + public WeatherParticle(@Nonnull WeatherParticle other) { + this.systemId = other.systemId; + this.color = other.color; + this.scale = other.scale; + this.isOvergroundOnly = other.isOvergroundOnly; + this.positionOffsetMultiplier = other.positionOffsetMultiplier; + } + + @Nonnull + public static WeatherParticle deserialize(@Nonnull ByteBuf buf, int offset) { + WeatherParticle obj = new WeatherParticle(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 2) != 0) { + obj.color = Color.deserialize(buf, offset + 1); + } + + obj.scale = buf.getFloatLE(offset + 4); + obj.isOvergroundOnly = buf.getByte(offset + 8) != 0; + obj.positionOffsetMultiplier = buf.getFloatLE(offset + 9); + int pos = offset + 13; + if ((nullBits & 1) != 0) { + int systemIdLen = VarInt.peek(buf, pos); + if (systemIdLen < 0) { + throw ProtocolException.negativeLength("SystemId", systemIdLen); + } + + if (systemIdLen > 4096000) { + throw ProtocolException.stringTooLong("SystemId", systemIdLen, 4096000); + } + + int systemIdVarLen = VarInt.length(buf, pos); + obj.systemId = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += systemIdVarLen + systemIdLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 13; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.systemId != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.color != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + if (this.color != null) { + this.color.serialize(buf); + } else { + buf.writeZero(3); + } + + buf.writeFloatLE(this.scale); + buf.writeByte(this.isOvergroundOnly ? 1 : 0); + buf.writeFloatLE(this.positionOffsetMultiplier); + if (this.systemId != null) { + PacketIO.writeVarString(buf, this.systemId, 4096000); + } + } + + public int computeSize() { + int size = 13; + if (this.systemId != null) { + size += PacketIO.stringSize(this.systemId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 13; + if ((nullBits & 1) != 0) { + int systemIdLen = VarInt.peek(buffer, pos); + if (systemIdLen < 0) { + return ValidationResult.error("Invalid string length for SystemId"); + } + + if (systemIdLen > 4096000) { + return ValidationResult.error("SystemId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += systemIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading SystemId"); + } + } + + return ValidationResult.OK; + } + } + + public WeatherParticle clone() { + WeatherParticle copy = new WeatherParticle(); + copy.systemId = this.systemId; + copy.color = this.color != null ? this.color.clone() : null; + copy.scale = this.scale; + copy.isOvergroundOnly = this.isOvergroundOnly; + copy.positionOffsetMultiplier = this.positionOffsetMultiplier; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof WeatherParticle other) + ? false + : Objects.equals(this.systemId, other.systemId) + && Objects.equals(this.color, other.color) + && this.scale == other.scale + && this.isOvergroundOnly == other.isOvergroundOnly + && this.positionOffsetMultiplier == other.positionOffsetMultiplier; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.systemId, this.color, this.scale, this.isOvergroundOnly, this.positionOffsetMultiplier); + } +} diff --git a/src/com/hypixel/hytale/protocol/WieldingInteraction.java b/src/com/hypixel/hytale/protocol/WieldingInteraction.java new file mode 100644 index 0000000..b22b08f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/WieldingInteraction.java @@ -0,0 +1,865 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WieldingInteraction extends ChargingInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 2; + public static final int FIXED_BLOCK_SIZE = 58; + public static final int VARIABLE_FIELD_COUNT = 8; + public static final int VARIABLE_BLOCK_START = 90; + public static final int MAX_SIZE = 1677721600; + @Nullable + public DamageEffects blockedEffects; + public boolean hasModifiers; + @Nullable + public AngledWielding angledWielding; + + public WieldingInteraction() { + } + + public WieldingInteraction( + @Nonnull WaitForDataFrom waitForDataFrom, + @Nullable InteractionEffects effects, + float horizontalSpeedMultiplier, + float runTime, + boolean cancelOnItemChange, + @Nullable Map settings, + @Nullable InteractionRules rules, + @Nullable int[] tags, + @Nullable InteractionCameraSettings camera, + int failed, + boolean allowIndefiniteHold, + boolean displayProgress, + boolean cancelOnOtherClick, + boolean failOnDamage, + float mouseSensitivityAdjustmentTarget, + float mouseSensitivityAdjustmentDuration, + @Nullable Map chargedNext, + @Nullable Map forks, + @Nullable ChargingDelay chargingDelay, + @Nullable DamageEffects blockedEffects, + boolean hasModifiers, + @Nullable AngledWielding angledWielding + ) { + this.waitForDataFrom = waitForDataFrom; + this.effects = effects; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.runTime = runTime; + this.cancelOnItemChange = cancelOnItemChange; + this.settings = settings; + this.rules = rules; + this.tags = tags; + this.camera = camera; + this.failed = failed; + this.allowIndefiniteHold = allowIndefiniteHold; + this.displayProgress = displayProgress; + this.cancelOnOtherClick = cancelOnOtherClick; + this.failOnDamage = failOnDamage; + this.mouseSensitivityAdjustmentTarget = mouseSensitivityAdjustmentTarget; + this.mouseSensitivityAdjustmentDuration = mouseSensitivityAdjustmentDuration; + this.chargedNext = chargedNext; + this.forks = forks; + this.chargingDelay = chargingDelay; + this.blockedEffects = blockedEffects; + this.hasModifiers = hasModifiers; + this.angledWielding = angledWielding; + } + + public WieldingInteraction(@Nonnull WieldingInteraction other) { + this.waitForDataFrom = other.waitForDataFrom; + this.effects = other.effects; + this.horizontalSpeedMultiplier = other.horizontalSpeedMultiplier; + this.runTime = other.runTime; + this.cancelOnItemChange = other.cancelOnItemChange; + this.settings = other.settings; + this.rules = other.rules; + this.tags = other.tags; + this.camera = other.camera; + this.failed = other.failed; + this.allowIndefiniteHold = other.allowIndefiniteHold; + this.displayProgress = other.displayProgress; + this.cancelOnOtherClick = other.cancelOnOtherClick; + this.failOnDamage = other.failOnDamage; + this.mouseSensitivityAdjustmentTarget = other.mouseSensitivityAdjustmentTarget; + this.mouseSensitivityAdjustmentDuration = other.mouseSensitivityAdjustmentDuration; + this.chargedNext = other.chargedNext; + this.forks = other.forks; + this.chargingDelay = other.chargingDelay; + this.blockedEffects = other.blockedEffects; + this.hasModifiers = other.hasModifiers; + this.angledWielding = other.angledWielding; + } + + @Nonnull + public static WieldingInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + WieldingInteraction obj = new WieldingInteraction(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + obj.waitForDataFrom = WaitForDataFrom.fromValue(buf.getByte(offset + 2)); + obj.horizontalSpeedMultiplier = buf.getFloatLE(offset + 3); + obj.runTime = buf.getFloatLE(offset + 7); + obj.cancelOnItemChange = buf.getByte(offset + 11) != 0; + obj.failed = buf.getIntLE(offset + 12); + obj.allowIndefiniteHold = buf.getByte(offset + 16) != 0; + obj.displayProgress = buf.getByte(offset + 17) != 0; + obj.cancelOnOtherClick = buf.getByte(offset + 18) != 0; + obj.failOnDamage = buf.getByte(offset + 19) != 0; + obj.mouseSensitivityAdjustmentTarget = buf.getFloatLE(offset + 20); + obj.mouseSensitivityAdjustmentDuration = buf.getFloatLE(offset + 24); + if ((nullBits[0] & 128) != 0) { + obj.chargingDelay = ChargingDelay.deserialize(buf, offset + 28); + } + + obj.hasModifiers = buf.getByte(offset + 48) != 0; + if ((nullBits[1] & 2) != 0) { + obj.angledWielding = AngledWielding.deserialize(buf, offset + 49); + } + + if ((nullBits[0] & 1) != 0) { + int varPos0 = offset + 90 + buf.getIntLE(offset + 58); + obj.effects = InteractionEffects.deserialize(buf, varPos0); + } + + if ((nullBits[0] & 2) != 0) { + int varPos1 = offset + 90 + buf.getIntLE(offset + 62); + int settingsCount = VarInt.peek(buf, varPos1); + if (settingsCount < 0) { + throw ProtocolException.negativeLength("Settings", settingsCount); + } + + if (settingsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", settingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.settings = new HashMap<>(settingsCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < settingsCount; i++) { + GameMode key = GameMode.fromValue(buf.getByte(dictPos)); + InteractionSettings val = InteractionSettings.deserialize(buf, ++dictPos); + dictPos += InteractionSettings.computeBytesConsumed(buf, dictPos); + if (obj.settings.put(key, val) != null) { + throw ProtocolException.duplicateKey("settings", key); + } + } + } + + if ((nullBits[0] & 4) != 0) { + int varPos2 = offset + 90 + buf.getIntLE(offset + 66); + obj.rules = InteractionRules.deserialize(buf, varPos2); + } + + if ((nullBits[0] & 8) != 0) { + int varPos3 = offset + 90 + buf.getIntLE(offset + 70); + int tagsCount = VarInt.peek(buf, varPos3); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.arrayTooLong("Tags", tagsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + tagsCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tags", varPos3 + varIntLen + tagsCount * 4, buf.readableBytes()); + } + + obj.tags = new int[tagsCount]; + + for (int ix = 0; ix < tagsCount; ix++) { + obj.tags[ix] = buf.getIntLE(varPos3 + varIntLen + ix * 4); + } + } + + if ((nullBits[0] & 16) != 0) { + int varPos4 = offset + 90 + buf.getIntLE(offset + 74); + obj.camera = InteractionCameraSettings.deserialize(buf, varPos4); + } + + if ((nullBits[0] & 32) != 0) { + int varPos5 = offset + 90 + buf.getIntLE(offset + 78); + int chargedNextCount = VarInt.peek(buf, varPos5); + if (chargedNextCount < 0) { + throw ProtocolException.negativeLength("ChargedNext", chargedNextCount); + } + + if (chargedNextCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("ChargedNext", chargedNextCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + obj.chargedNext = new HashMap<>(chargedNextCount); + int dictPos = varPos5 + varIntLen; + + for (int ix = 0; ix < chargedNextCount; ix++) { + float key = buf.getFloatLE(dictPos); + dictPos += 4; + int val = buf.getIntLE(dictPos); + dictPos += 4; + if (obj.chargedNext.put(key, val) != null) { + throw ProtocolException.duplicateKey("chargedNext", key); + } + } + } + + if ((nullBits[0] & 64) != 0) { + int varPos6 = offset + 90 + buf.getIntLE(offset + 82); + int forksCount = VarInt.peek(buf, varPos6); + if (forksCount < 0) { + throw ProtocolException.negativeLength("Forks", forksCount); + } + + if (forksCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Forks", forksCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos6); + obj.forks = new HashMap<>(forksCount); + int dictPos = varPos6 + varIntLen; + + for (int ixx = 0; ixx < forksCount; ixx++) { + InteractionType key = InteractionType.fromValue(buf.getByte(dictPos)); + int val = buf.getIntLE(++dictPos); + dictPos += 4; + if (obj.forks.put(key, val) != null) { + throw ProtocolException.duplicateKey("forks", key); + } + } + } + + if ((nullBits[1] & 1) != 0) { + int varPos7 = offset + 90 + buf.getIntLE(offset + 86); + obj.blockedEffects = DamageEffects.deserialize(buf, varPos7); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + int maxEnd = 90; + if ((nullBits[0] & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 58); + int pos0 = offset + 90 + fieldOffset0; + pos0 += InteractionEffects.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits[0] & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 62); + int pos1 = offset + 90 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 = ++pos1 + InteractionSettings.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits[0] & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 66); + int pos2 = offset + 90 + fieldOffset2; + pos2 += InteractionRules.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits[0] & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 70); + int pos3 = offset + 90 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + arrLen * 4; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits[0] & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 74); + int pos4 = offset + 90 + fieldOffset4; + pos4 += InteractionCameraSettings.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits[0] & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 78); + int pos5 = offset + 90 + fieldOffset5; + int dictLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5); + + for (int i = 0; i < dictLen; i++) { + pos5 += 4; + pos5 += 4; + } + + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits[0] & 64) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 82); + int pos6 = offset + 90 + fieldOffset6; + int dictLen = VarInt.peek(buf, pos6); + pos6 += VarInt.length(buf, pos6); + + for (int i = 0; i < dictLen; i++) { + pos6 = ++pos6 + 4; + } + + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + if ((nullBits[1] & 1) != 0) { + int fieldOffset7 = buf.getIntLE(offset + 86); + int pos7 = offset + 90 + fieldOffset7; + pos7 += DamageEffects.computeBytesConsumed(buf, pos7); + if (pos7 - offset > maxEnd) { + maxEnd = pos7 - offset; + } + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte[] nullBits = new byte[2]; + if (this.effects != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.settings != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.rules != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.tags != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.camera != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.chargedNext != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.forks != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.chargingDelay != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.blockedEffects != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + if (this.angledWielding != null) { + nullBits[1] = (byte)(nullBits[1] | 2); + } + + buf.writeBytes(nullBits); + buf.writeByte(this.waitForDataFrom.getValue()); + buf.writeFloatLE(this.horizontalSpeedMultiplier); + buf.writeFloatLE(this.runTime); + buf.writeByte(this.cancelOnItemChange ? 1 : 0); + buf.writeIntLE(this.failed); + buf.writeByte(this.allowIndefiniteHold ? 1 : 0); + buf.writeByte(this.displayProgress ? 1 : 0); + buf.writeByte(this.cancelOnOtherClick ? 1 : 0); + buf.writeByte(this.failOnDamage ? 1 : 0); + buf.writeFloatLE(this.mouseSensitivityAdjustmentTarget); + buf.writeFloatLE(this.mouseSensitivityAdjustmentDuration); + if (this.chargingDelay != null) { + this.chargingDelay.serialize(buf); + } else { + buf.writeZero(20); + } + + buf.writeByte(this.hasModifiers ? 1 : 0); + if (this.angledWielding != null) { + this.angledWielding.serialize(buf); + } else { + buf.writeZero(9); + } + + int effectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int settingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int rulesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int cameraOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int chargedNextOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int forksOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int blockedEffectsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.effects != null) { + buf.setIntLE(effectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.effects.serialize(buf); + } else { + buf.setIntLE(effectsOffsetSlot, -1); + } + + if (this.settings != null) { + buf.setIntLE(settingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.settings.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Settings", this.settings.size(), 4096000); + } + + VarInt.write(buf, this.settings.size()); + + for (Entry e : this.settings.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(settingsOffsetSlot, -1); + } + + if (this.rules != null) { + buf.setIntLE(rulesOffsetSlot, buf.writerIndex() - varBlockStart); + this.rules.serialize(buf); + } else { + buf.setIntLE(rulesOffsetSlot, -1); + } + + if (this.tags != null) { + buf.setIntLE(tagsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tags.length > 4096000) { + throw ProtocolException.arrayTooLong("Tags", this.tags.length, 4096000); + } + + VarInt.write(buf, this.tags.length); + + for (int item : this.tags) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagsOffsetSlot, -1); + } + + if (this.camera != null) { + buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart); + this.camera.serialize(buf); + } else { + buf.setIntLE(cameraOffsetSlot, -1); + } + + if (this.chargedNext != null) { + buf.setIntLE(chargedNextOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.chargedNext.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("ChargedNext", this.chargedNext.size(), 4096000); + } + + VarInt.write(buf, this.chargedNext.size()); + + for (Entry e : this.chargedNext.entrySet()) { + buf.writeFloatLE(e.getKey()); + buf.writeIntLE(e.getValue()); + } + } else { + buf.setIntLE(chargedNextOffsetSlot, -1); + } + + if (this.forks != null) { + buf.setIntLE(forksOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.forks.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Forks", this.forks.size(), 4096000); + } + + VarInt.write(buf, this.forks.size()); + + for (Entry e : this.forks.entrySet()) { + buf.writeByte(e.getKey().getValue()); + buf.writeIntLE(e.getValue()); + } + } else { + buf.setIntLE(forksOffsetSlot, -1); + } + + if (this.blockedEffects != null) { + buf.setIntLE(blockedEffectsOffsetSlot, buf.writerIndex() - varBlockStart); + this.blockedEffects.serialize(buf); + } else { + buf.setIntLE(blockedEffectsOffsetSlot, -1); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 90; + if (this.effects != null) { + size += this.effects.computeSize(); + } + + if (this.settings != null) { + size += VarInt.size(this.settings.size()) + this.settings.size() * 2; + } + + if (this.rules != null) { + size += this.rules.computeSize(); + } + + if (this.tags != null) { + size += VarInt.size(this.tags.length) + this.tags.length * 4; + } + + if (this.camera != null) { + size += this.camera.computeSize(); + } + + if (this.chargedNext != null) { + size += VarInt.size(this.chargedNext.size()) + this.chargedNext.size() * 8; + } + + if (this.forks != null) { + size += VarInt.size(this.forks.size()) + this.forks.size() * 5; + } + + if (this.blockedEffects != null) { + size += this.blockedEffects.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 90) { + return ValidationResult.error("Buffer too small: expected at least 90 bytes"); + } else { + byte[] nullBits = PacketIO.readBytes(buffer, offset, 2); + if ((nullBits[0] & 1) != 0) { + int effectsOffset = buffer.getIntLE(offset + 58); + if (effectsOffset < 0) { + return ValidationResult.error("Invalid offset for Effects"); + } + + int pos = offset + 90 + effectsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Effects"); + } + + ValidationResult effectsResult = InteractionEffects.validateStructure(buffer, pos); + if (!effectsResult.isValid()) { + return ValidationResult.error("Invalid Effects: " + effectsResult.error()); + } + + pos += InteractionEffects.computeBytesConsumed(buffer, pos); + } + + if ((nullBits[0] & 2) != 0) { + int settingsOffset = buffer.getIntLE(offset + 62); + if (settingsOffset < 0) { + return ValidationResult.error("Invalid offset for Settings"); + } + + int posx = offset + 90 + settingsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Settings"); + } + + int settingsCount = VarInt.peek(buffer, posx); + if (settingsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Settings"); + } + + if (settingsCount > 4096000) { + return ValidationResult.error("Settings exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < settingsCount; i++) { + posx++; + posx++; + } + } + + if ((nullBits[0] & 4) != 0) { + int rulesOffset = buffer.getIntLE(offset + 66); + if (rulesOffset < 0) { + return ValidationResult.error("Invalid offset for Rules"); + } + + int posxx = offset + 90 + rulesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Rules"); + } + + ValidationResult rulesResult = InteractionRules.validateStructure(buffer, posxx); + if (!rulesResult.isValid()) { + return ValidationResult.error("Invalid Rules: " + rulesResult.error()); + } + + posxx += InteractionRules.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits[0] & 8) != 0) { + int tagsOffset = buffer.getIntLE(offset + 70); + if (tagsOffset < 0) { + return ValidationResult.error("Invalid offset for Tags"); + } + + int posxxx = offset + 90 + tagsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tags"); + } + + int tagsCount = VarInt.peek(buffer, posxxx); + if (tagsCount < 0) { + return ValidationResult.error("Invalid array count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += tagsCount * 4; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tags"); + } + } + + if ((nullBits[0] & 16) != 0) { + int cameraOffset = buffer.getIntLE(offset + 74); + if (cameraOffset < 0) { + return ValidationResult.error("Invalid offset for Camera"); + } + + int posxxxx = offset + 90 + cameraOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Camera"); + } + + ValidationResult cameraResult = InteractionCameraSettings.validateStructure(buffer, posxxxx); + if (!cameraResult.isValid()) { + return ValidationResult.error("Invalid Camera: " + cameraResult.error()); + } + + posxxxx += InteractionCameraSettings.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits[0] & 32) != 0) { + int chargedNextOffset = buffer.getIntLE(offset + 78); + if (chargedNextOffset < 0) { + return ValidationResult.error("Invalid offset for ChargedNext"); + } + + int posxxxxx = offset + 90 + chargedNextOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ChargedNext"); + } + + int chargedNextCount = VarInt.peek(buffer, posxxxxx); + if (chargedNextCount < 0) { + return ValidationResult.error("Invalid dictionary count for ChargedNext"); + } + + if (chargedNextCount > 4096000) { + return ValidationResult.error("ChargedNext exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + + for (int i = 0; i < chargedNextCount; i++) { + posxxxxx += 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxxxxx += 4; + if (posxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + if ((nullBits[0] & 64) != 0) { + int forksOffset = buffer.getIntLE(offset + 82); + if (forksOffset < 0) { + return ValidationResult.error("Invalid offset for Forks"); + } + + int posxxxxxx = offset + 90 + forksOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Forks"); + } + + int forksCount = VarInt.peek(buffer, posxxxxxx); + if (forksCount < 0) { + return ValidationResult.error("Invalid dictionary count for Forks"); + } + + if (forksCount > 4096000) { + return ValidationResult.error("Forks exceeds max length 4096000"); + } + + posxxxxxx += VarInt.length(buffer, posxxxxxx); + + for (int i = 0; i < forksCount; i++) { + posxxxxxx = ++posxxxxxx + 4; + if (posxxxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + if ((nullBits[1] & 1) != 0) { + int blockedEffectsOffset = buffer.getIntLE(offset + 86); + if (blockedEffectsOffset < 0) { + return ValidationResult.error("Invalid offset for BlockedEffects"); + } + + int posxxxxxxx = offset + 90 + blockedEffectsOffset; + if (posxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BlockedEffects"); + } + + ValidationResult blockedEffectsResult = DamageEffects.validateStructure(buffer, posxxxxxxx); + if (!blockedEffectsResult.isValid()) { + return ValidationResult.error("Invalid BlockedEffects: " + blockedEffectsResult.error()); + } + + posxxxxxxx += DamageEffects.computeBytesConsumed(buffer, posxxxxxxx); + } + + return ValidationResult.OK; + } + } + + public WieldingInteraction clone() { + WieldingInteraction copy = new WieldingInteraction(); + copy.waitForDataFrom = this.waitForDataFrom; + copy.effects = this.effects != null ? this.effects.clone() : null; + copy.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + copy.runTime = this.runTime; + copy.cancelOnItemChange = this.cancelOnItemChange; + if (this.settings != null) { + Map m = new HashMap<>(); + + for (Entry e : this.settings.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.settings = m; + } + + copy.rules = this.rules != null ? this.rules.clone() : null; + copy.tags = this.tags != null ? Arrays.copyOf(this.tags, this.tags.length) : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + copy.failed = this.failed; + copy.allowIndefiniteHold = this.allowIndefiniteHold; + copy.displayProgress = this.displayProgress; + copy.cancelOnOtherClick = this.cancelOnOtherClick; + copy.failOnDamage = this.failOnDamage; + copy.mouseSensitivityAdjustmentTarget = this.mouseSensitivityAdjustmentTarget; + copy.mouseSensitivityAdjustmentDuration = this.mouseSensitivityAdjustmentDuration; + copy.chargedNext = this.chargedNext != null ? new HashMap<>(this.chargedNext) : null; + copy.forks = this.forks != null ? new HashMap<>(this.forks) : null; + copy.chargingDelay = this.chargingDelay != null ? this.chargingDelay.clone() : null; + copy.blockedEffects = this.blockedEffects != null ? this.blockedEffects.clone() : null; + copy.hasModifiers = this.hasModifiers; + copy.angledWielding = this.angledWielding != null ? this.angledWielding.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof WieldingInteraction other) + ? false + : Objects.equals(this.waitForDataFrom, other.waitForDataFrom) + && Objects.equals(this.effects, other.effects) + && this.horizontalSpeedMultiplier == other.horizontalSpeedMultiplier + && this.runTime == other.runTime + && this.cancelOnItemChange == other.cancelOnItemChange + && Objects.equals(this.settings, other.settings) + && Objects.equals(this.rules, other.rules) + && Arrays.equals(this.tags, other.tags) + && Objects.equals(this.camera, other.camera) + && this.failed == other.failed + && this.allowIndefiniteHold == other.allowIndefiniteHold + && this.displayProgress == other.displayProgress + && this.cancelOnOtherClick == other.cancelOnOtherClick + && this.failOnDamage == other.failOnDamage + && this.mouseSensitivityAdjustmentTarget == other.mouseSensitivityAdjustmentTarget + && this.mouseSensitivityAdjustmentDuration == other.mouseSensitivityAdjustmentDuration + && Objects.equals(this.chargedNext, other.chargedNext) + && Objects.equals(this.forks, other.forks) + && Objects.equals(this.chargingDelay, other.chargingDelay) + && Objects.equals(this.blockedEffects, other.blockedEffects) + && this.hasModifiers == other.hasModifiers + && Objects.equals(this.angledWielding, other.angledWielding); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.waitForDataFrom); + result = 31 * result + Objects.hashCode(this.effects); + result = 31 * result + Float.hashCode(this.horizontalSpeedMultiplier); + result = 31 * result + Float.hashCode(this.runTime); + result = 31 * result + Boolean.hashCode(this.cancelOnItemChange); + result = 31 * result + Objects.hashCode(this.settings); + result = 31 * result + Objects.hashCode(this.rules); + result = 31 * result + Arrays.hashCode(this.tags); + result = 31 * result + Objects.hashCode(this.camera); + result = 31 * result + Integer.hashCode(this.failed); + result = 31 * result + Boolean.hashCode(this.allowIndefiniteHold); + result = 31 * result + Boolean.hashCode(this.displayProgress); + result = 31 * result + Boolean.hashCode(this.cancelOnOtherClick); + result = 31 * result + Boolean.hashCode(this.failOnDamage); + result = 31 * result + Float.hashCode(this.mouseSensitivityAdjustmentTarget); + result = 31 * result + Float.hashCode(this.mouseSensitivityAdjustmentDuration); + result = 31 * result + Objects.hashCode(this.chargedNext); + result = 31 * result + Objects.hashCode(this.forks); + result = 31 * result + Objects.hashCode(this.chargingDelay); + result = 31 * result + Objects.hashCode(this.blockedEffects); + result = 31 * result + Boolean.hashCode(this.hasModifiers); + return 31 * result + Objects.hashCode(this.angledWielding); + } +} diff --git a/src/com/hypixel/hytale/protocol/WiggleWeights.java b/src/com/hypixel/hytale/protocol/WiggleWeights.java new file mode 100644 index 0000000..2a57a55 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/WiggleWeights.java @@ -0,0 +1,156 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class WiggleWeights { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 40; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 40; + public static final int MAX_SIZE = 40; + public float x; + public float xDeceleration; + public float y; + public float yDeceleration; + public float z; + public float zDeceleration; + public float roll; + public float rollDeceleration; + public float pitch; + public float pitchDeceleration; + + public WiggleWeights() { + } + + public WiggleWeights( + float x, + float xDeceleration, + float y, + float yDeceleration, + float z, + float zDeceleration, + float roll, + float rollDeceleration, + float pitch, + float pitchDeceleration + ) { + this.x = x; + this.xDeceleration = xDeceleration; + this.y = y; + this.yDeceleration = yDeceleration; + this.z = z; + this.zDeceleration = zDeceleration; + this.roll = roll; + this.rollDeceleration = rollDeceleration; + this.pitch = pitch; + this.pitchDeceleration = pitchDeceleration; + } + + public WiggleWeights(@Nonnull WiggleWeights other) { + this.x = other.x; + this.xDeceleration = other.xDeceleration; + this.y = other.y; + this.yDeceleration = other.yDeceleration; + this.z = other.z; + this.zDeceleration = other.zDeceleration; + this.roll = other.roll; + this.rollDeceleration = other.rollDeceleration; + this.pitch = other.pitch; + this.pitchDeceleration = other.pitchDeceleration; + } + + @Nonnull + public static WiggleWeights deserialize(@Nonnull ByteBuf buf, int offset) { + WiggleWeights obj = new WiggleWeights(); + obj.x = buf.getFloatLE(offset + 0); + obj.xDeceleration = buf.getFloatLE(offset + 4); + obj.y = buf.getFloatLE(offset + 8); + obj.yDeceleration = buf.getFloatLE(offset + 12); + obj.z = buf.getFloatLE(offset + 16); + obj.zDeceleration = buf.getFloatLE(offset + 20); + obj.roll = buf.getFloatLE(offset + 24); + obj.rollDeceleration = buf.getFloatLE(offset + 28); + obj.pitch = buf.getFloatLE(offset + 32); + obj.pitchDeceleration = buf.getFloatLE(offset + 36); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 40; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.x); + buf.writeFloatLE(this.xDeceleration); + buf.writeFloatLE(this.y); + buf.writeFloatLE(this.yDeceleration); + buf.writeFloatLE(this.z); + buf.writeFloatLE(this.zDeceleration); + buf.writeFloatLE(this.roll); + buf.writeFloatLE(this.rollDeceleration); + buf.writeFloatLE(this.pitch); + buf.writeFloatLE(this.pitchDeceleration); + } + + public int computeSize() { + return 40; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 40 ? ValidationResult.error("Buffer too small: expected at least 40 bytes") : ValidationResult.OK; + } + + public WiggleWeights clone() { + WiggleWeights copy = new WiggleWeights(); + copy.x = this.x; + copy.xDeceleration = this.xDeceleration; + copy.y = this.y; + copy.yDeceleration = this.yDeceleration; + copy.z = this.z; + copy.zDeceleration = this.zDeceleration; + copy.roll = this.roll; + copy.rollDeceleration = this.rollDeceleration; + copy.pitch = this.pitch; + copy.pitchDeceleration = this.pitchDeceleration; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof WiggleWeights other) + ? false + : this.x == other.x + && this.xDeceleration == other.xDeceleration + && this.y == other.y + && this.yDeceleration == other.yDeceleration + && this.z == other.z + && this.zDeceleration == other.zDeceleration + && this.roll == other.roll + && this.rollDeceleration == other.rollDeceleration + && this.pitch == other.pitch + && this.pitchDeceleration == other.pitchDeceleration; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.x, + this.xDeceleration, + this.y, + this.yDeceleration, + this.z, + this.zDeceleration, + this.roll, + this.rollDeceleration, + this.pitch, + this.pitchDeceleration + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/WorldEnvironment.java b/src/com/hypixel/hytale/protocol/WorldEnvironment.java new file mode 100644 index 0000000..484f271 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/WorldEnvironment.java @@ -0,0 +1,394 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WorldEnvironment { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 16; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + @Nullable + public Color waterTint; + @Nullable + public Map fluidParticles; + @Nullable + public int[] tagIndexes; + + public WorldEnvironment() { + } + + public WorldEnvironment(@Nullable String id, @Nullable Color waterTint, @Nullable Map fluidParticles, @Nullable int[] tagIndexes) { + this.id = id; + this.waterTint = waterTint; + this.fluidParticles = fluidParticles; + this.tagIndexes = tagIndexes; + } + + public WorldEnvironment(@Nonnull WorldEnvironment other) { + this.id = other.id; + this.waterTint = other.waterTint; + this.fluidParticles = other.fluidParticles; + this.tagIndexes = other.tagIndexes; + } + + @Nonnull + public static WorldEnvironment deserialize(@Nonnull ByteBuf buf, int offset) { + WorldEnvironment obj = new WorldEnvironment(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 2) != 0) { + obj.waterTint = Color.deserialize(buf, offset + 1); + } + + if ((nullBits & 1) != 0) { + int varPos0 = offset + 16 + buf.getIntLE(offset + 4); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos1 = offset + 16 + buf.getIntLE(offset + 8); + int fluidParticlesCount = VarInt.peek(buf, varPos1); + if (fluidParticlesCount < 0) { + throw ProtocolException.negativeLength("FluidParticles", fluidParticlesCount); + } + + if (fluidParticlesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("FluidParticles", fluidParticlesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + obj.fluidParticles = new HashMap<>(fluidParticlesCount); + int dictPos = varPos1 + varIntLen; + + for (int i = 0; i < fluidParticlesCount; i++) { + int key = buf.getIntLE(dictPos); + dictPos += 4; + FluidParticle val = FluidParticle.deserialize(buf, dictPos); + dictPos += FluidParticle.computeBytesConsumed(buf, dictPos); + if (obj.fluidParticles.put(key, val) != null) { + throw ProtocolException.duplicateKey("fluidParticles", key); + } + } + } + + if ((nullBits & 8) != 0) { + int varPos2 = offset + 16 + buf.getIntLE(offset + 12); + int tagIndexesCount = VarInt.peek(buf, varPos2); + if (tagIndexesCount < 0) { + throw ProtocolException.negativeLength("TagIndexes", tagIndexesCount); + } + + if (tagIndexesCount > 4096000) { + throw ProtocolException.arrayTooLong("TagIndexes", tagIndexesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + tagIndexesCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("TagIndexes", varPos2 + varIntLen + tagIndexesCount * 4, buf.readableBytes()); + } + + obj.tagIndexes = new int[tagIndexesCount]; + + for (int ix = 0; ix < tagIndexesCount; ix++) { + obj.tagIndexes[ix] = buf.getIntLE(varPos2 + varIntLen + ix * 4); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 16; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 4); + int pos0 = offset + 16 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 8); + int pos1 = offset + 16 + fieldOffset1; + int dictLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < dictLen; i++) { + pos1 += 4; + pos1 += FluidParticle.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 12); + int pos2 = offset + 16 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + arrLen * 4; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.waterTint != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.fluidParticles != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.tagIndexes != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + if (this.waterTint != null) { + this.waterTint.serialize(buf); + } else { + buf.writeZero(3); + } + + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int fluidParticlesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int tagIndexesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.fluidParticles != null) { + buf.setIntLE(fluidParticlesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.fluidParticles.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("FluidParticles", this.fluidParticles.size(), 4096000); + } + + VarInt.write(buf, this.fluidParticles.size()); + + for (Entry e : this.fluidParticles.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(fluidParticlesOffsetSlot, -1); + } + + if (this.tagIndexes != null) { + buf.setIntLE(tagIndexesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.tagIndexes.length > 4096000) { + throw ProtocolException.arrayTooLong("TagIndexes", this.tagIndexes.length, 4096000); + } + + VarInt.write(buf, this.tagIndexes.length); + + for (int item : this.tagIndexes) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(tagIndexesOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 16; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.fluidParticles != null) { + int fluidParticlesSize = 0; + + for (Entry kvp : this.fluidParticles.entrySet()) { + fluidParticlesSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.fluidParticles.size()) + fluidParticlesSize; + } + + if (this.tagIndexes != null) { + size += VarInt.size(this.tagIndexes.length) + this.tagIndexes.length * 4; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 16) { + return ValidationResult.error("Buffer too small: expected at least 16 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 4); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 16 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 4) != 0) { + int fluidParticlesOffset = buffer.getIntLE(offset + 8); + if (fluidParticlesOffset < 0) { + return ValidationResult.error("Invalid offset for FluidParticles"); + } + + int posx = offset + 16 + fluidParticlesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FluidParticles"); + } + + int fluidParticlesCount = VarInt.peek(buffer, posx); + if (fluidParticlesCount < 0) { + return ValidationResult.error("Invalid dictionary count for FluidParticles"); + } + + if (fluidParticlesCount > 4096000) { + return ValidationResult.error("FluidParticles exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < fluidParticlesCount; i++) { + posx += 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posx += FluidParticle.computeBytesConsumed(buffer, posx); + } + } + + if ((nullBits & 8) != 0) { + int tagIndexesOffset = buffer.getIntLE(offset + 12); + if (tagIndexesOffset < 0) { + return ValidationResult.error("Invalid offset for TagIndexes"); + } + + int posxx = offset + 16 + tagIndexesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for TagIndexes"); + } + + int tagIndexesCount = VarInt.peek(buffer, posxx); + if (tagIndexesCount < 0) { + return ValidationResult.error("Invalid array count for TagIndexes"); + } + + if (tagIndexesCount > 4096000) { + return ValidationResult.error("TagIndexes exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += tagIndexesCount * 4; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading TagIndexes"); + } + } + + return ValidationResult.OK; + } + } + + public WorldEnvironment clone() { + WorldEnvironment copy = new WorldEnvironment(); + copy.id = this.id; + copy.waterTint = this.waterTint != null ? this.waterTint.clone() : null; + if (this.fluidParticles != null) { + Map m = new HashMap<>(); + + for (Entry e : this.fluidParticles.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.fluidParticles = m; + } + + copy.tagIndexes = this.tagIndexes != null ? Arrays.copyOf(this.tagIndexes, this.tagIndexes.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof WorldEnvironment other) + ? false + : Objects.equals(this.id, other.id) + && Objects.equals(this.waterTint, other.waterTint) + && Objects.equals(this.fluidParticles, other.fluidParticles) + && Arrays.equals(this.tagIndexes, other.tagIndexes); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.id); + result = 31 * result + Objects.hashCode(this.waterTint); + result = 31 * result + Objects.hashCode(this.fluidParticles); + return 31 * result + Arrays.hashCode(this.tagIndexes); + } +} diff --git a/src/com/hypixel/hytale/protocol/WorldInteraction.java b/src/com/hypixel/hytale/protocol/WorldInteraction.java new file mode 100644 index 0000000..1bf9a3f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/WorldInteraction.java @@ -0,0 +1,114 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WorldInteraction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 20; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 20; + public static final int MAX_SIZE = 20; + public int entityId; + @Nullable + public BlockPosition blockPosition; + @Nullable + public BlockRotation blockRotation; + + public WorldInteraction() { + } + + public WorldInteraction(int entityId, @Nullable BlockPosition blockPosition, @Nullable BlockRotation blockRotation) { + this.entityId = entityId; + this.blockPosition = blockPosition; + this.blockRotation = blockRotation; + } + + public WorldInteraction(@Nonnull WorldInteraction other) { + this.entityId = other.entityId; + this.blockPosition = other.blockPosition; + this.blockRotation = other.blockRotation; + } + + @Nonnull + public static WorldInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + WorldInteraction obj = new WorldInteraction(); + byte nullBits = buf.getByte(offset); + obj.entityId = buf.getIntLE(offset + 1); + if ((nullBits & 1) != 0) { + obj.blockPosition = BlockPosition.deserialize(buf, offset + 5); + } + + if ((nullBits & 2) != 0) { + obj.blockRotation = BlockRotation.deserialize(buf, offset + 17); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 20; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.blockPosition != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.blockRotation != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.entityId); + if (this.blockPosition != null) { + this.blockPosition.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.blockRotation != null) { + this.blockRotation.serialize(buf); + } else { + buf.writeZero(3); + } + } + + public int computeSize() { + return 20; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 20 ? ValidationResult.error("Buffer too small: expected at least 20 bytes") : ValidationResult.OK; + } + + public WorldInteraction clone() { + WorldInteraction copy = new WorldInteraction(); + copy.entityId = this.entityId; + copy.blockPosition = this.blockPosition != null ? this.blockPosition.clone() : null; + copy.blockRotation = this.blockRotation != null ? this.blockRotation.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof WorldInteraction other) + ? false + : this.entityId == other.entityId + && Objects.equals(this.blockPosition, other.blockPosition) + && Objects.equals(this.blockRotation, other.blockRotation); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.entityId, this.blockPosition, this.blockRotation); + } +} diff --git a/src/com/hypixel/hytale/protocol/WorldParticle.java b/src/com/hypixel/hytale/protocol/WorldParticle.java new file mode 100644 index 0000000..a9dfc24 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/WorldParticle.java @@ -0,0 +1,202 @@ +package com.hypixel.hytale.protocol; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WorldParticle { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 32; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 32; + public static final int MAX_SIZE = 16384037; + @Nullable + public String systemId; + public float scale; + @Nullable + public Color color; + @Nullable + public Vector3f positionOffset; + @Nullable + public Direction rotationOffset; + + public WorldParticle() { + } + + public WorldParticle(@Nullable String systemId, float scale, @Nullable Color color, @Nullable Vector3f positionOffset, @Nullable Direction rotationOffset) { + this.systemId = systemId; + this.scale = scale; + this.color = color; + this.positionOffset = positionOffset; + this.rotationOffset = rotationOffset; + } + + public WorldParticle(@Nonnull WorldParticle other) { + this.systemId = other.systemId; + this.scale = other.scale; + this.color = other.color; + this.positionOffset = other.positionOffset; + this.rotationOffset = other.rotationOffset; + } + + @Nonnull + public static WorldParticle deserialize(@Nonnull ByteBuf buf, int offset) { + WorldParticle obj = new WorldParticle(); + byte nullBits = buf.getByte(offset); + obj.scale = buf.getFloatLE(offset + 1); + if ((nullBits & 2) != 0) { + obj.color = Color.deserialize(buf, offset + 5); + } + + if ((nullBits & 4) != 0) { + obj.positionOffset = Vector3f.deserialize(buf, offset + 8); + } + + if ((nullBits & 8) != 0) { + obj.rotationOffset = Direction.deserialize(buf, offset + 20); + } + + int pos = offset + 32; + if ((nullBits & 1) != 0) { + int systemIdLen = VarInt.peek(buf, pos); + if (systemIdLen < 0) { + throw ProtocolException.negativeLength("SystemId", systemIdLen); + } + + if (systemIdLen > 4096000) { + throw ProtocolException.stringTooLong("SystemId", systemIdLen, 4096000); + } + + int systemIdVarLen = VarInt.length(buf, pos); + obj.systemId = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += systemIdVarLen + systemIdLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 32; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.systemId != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.color != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.positionOffset != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.rotationOffset != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.scale); + if (this.color != null) { + this.color.serialize(buf); + } else { + buf.writeZero(3); + } + + if (this.positionOffset != null) { + this.positionOffset.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.rotationOffset != null) { + this.rotationOffset.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.systemId != null) { + PacketIO.writeVarString(buf, this.systemId, 4096000); + } + } + + public int computeSize() { + int size = 32; + if (this.systemId != null) { + size += PacketIO.stringSize(this.systemId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 32) { + return ValidationResult.error("Buffer too small: expected at least 32 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 32; + if ((nullBits & 1) != 0) { + int systemIdLen = VarInt.peek(buffer, pos); + if (systemIdLen < 0) { + return ValidationResult.error("Invalid string length for SystemId"); + } + + if (systemIdLen > 4096000) { + return ValidationResult.error("SystemId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += systemIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading SystemId"); + } + } + + return ValidationResult.OK; + } + } + + public WorldParticle clone() { + WorldParticle copy = new WorldParticle(); + copy.systemId = this.systemId; + copy.scale = this.scale; + copy.color = this.color != null ? this.color.clone() : null; + copy.positionOffset = this.positionOffset != null ? this.positionOffset.clone() : null; + copy.rotationOffset = this.rotationOffset != null ? this.rotationOffset.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof WorldParticle other) + ? false + : Objects.equals(this.systemId, other.systemId) + && this.scale == other.scale + && Objects.equals(this.color, other.color) + && Objects.equals(this.positionOffset, other.positionOffset) + && Objects.equals(this.rotationOffset, other.rotationOffset); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.systemId, this.scale, this.color, this.positionOffset, this.rotationOffset); + } +} diff --git a/src/com/hypixel/hytale/protocol/io/NoopPacketStatsRecorder.java b/src/com/hypixel/hytale/protocol/io/NoopPacketStatsRecorder.java new file mode 100644 index 0000000..99b0c89 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/io/NoopPacketStatsRecorder.java @@ -0,0 +1,143 @@ +package com.hypixel.hytale.protocol.io; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +final class NoopPacketStatsRecorder implements PacketStatsRecorder { + private static final PacketStatsRecorder.PacketStatsEntry EMPTY_ENTRY = new PacketStatsRecorder.PacketStatsEntry() { + @Override + public int getPacketId() { + return 0; + } + + @Nullable + @Override + public String getName() { + return null; + } + + @Override + public boolean hasData() { + return false; + } + + @Override + public int getSentCount() { + return 0; + } + + @Override + public long getSentUncompressedTotal() { + return 0L; + } + + @Override + public long getSentCompressedTotal() { + return 0L; + } + + @Override + public long getSentUncompressedMin() { + return 0L; + } + + @Override + public long getSentUncompressedMax() { + return 0L; + } + + @Override + public long getSentCompressedMin() { + return 0L; + } + + @Override + public long getSentCompressedMax() { + return 0L; + } + + @Override + public double getSentUncompressedAvg() { + return 0.0; + } + + @Override + public double getSentCompressedAvg() { + return 0.0; + } + + @Nonnull + @Override + public PacketStatsRecorder.RecentStats getSentRecently() { + return PacketStatsRecorder.RecentStats.EMPTY; + } + + @Override + public int getReceivedCount() { + return 0; + } + + @Override + public long getReceivedUncompressedTotal() { + return 0L; + } + + @Override + public long getReceivedCompressedTotal() { + return 0L; + } + + @Override + public long getReceivedUncompressedMin() { + return 0L; + } + + @Override + public long getReceivedUncompressedMax() { + return 0L; + } + + @Override + public long getReceivedCompressedMin() { + return 0L; + } + + @Override + public long getReceivedCompressedMax() { + return 0L; + } + + @Override + public double getReceivedUncompressedAvg() { + return 0.0; + } + + @Override + public double getReceivedCompressedAvg() { + return 0.0; + } + + @Nonnull + @Override + public PacketStatsRecorder.RecentStats getReceivedRecently() { + return PacketStatsRecorder.RecentStats.EMPTY; + } + }; + + NoopPacketStatsRecorder() { + } + + @Override + public void recordSend(int packetId, int uncompressedSize, int compressedSize) { + } + + @Override + public void recordReceive(int packetId, int uncompressedSize, int compressedSize) { + } + + @Nonnull + @Override + public PacketStatsRecorder.PacketStatsEntry getEntry(int packetId) { + return EMPTY_ENTRY; + } +} diff --git a/src/com/hypixel/hytale/protocol/io/PacketIO.java b/src/com/hypixel/hytale/protocol/io/PacketIO.java new file mode 100644 index 0000000..9e45dda --- /dev/null +++ b/src/com/hypixel/hytale/protocol/io/PacketIO.java @@ -0,0 +1,402 @@ +package com.hypixel.hytale.protocol.io; + +import com.github.luben.zstd.Zstd; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.PacketRegistry; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class PacketIO { + public static final int FRAME_HEADER_SIZE = 4; + public static final Charset UTF8 = StandardCharsets.UTF_8; + public static final Charset ASCII = StandardCharsets.US_ASCII; + private static final int COMPRESSION_LEVEL = Integer.getInteger("hytale.protocol.compressionLevel", Zstd.defaultCompressionLevel()); + + private PacketIO() { + } + + public static float readHalfLE(@Nonnull ByteBuf buf, int index) { + short bits = buf.getShortLE(index); + return halfToFloat(bits); + } + + public static void writeHalfLE(@Nonnull ByteBuf buf, float value) { + buf.writeShortLE(floatToHalf(value)); + } + + @Nonnull + public static byte[] readBytes(@Nonnull ByteBuf buf, int offset, int length) { + byte[] bytes = new byte[length]; + buf.getBytes(offset, bytes); + return bytes; + } + + @Nonnull + public static byte[] readByteArray(@Nonnull ByteBuf buf, int offset, int length) { + byte[] result = new byte[length]; + buf.getBytes(offset, result); + return result; + } + + @Nonnull + public static short[] readShortArrayLE(@Nonnull ByteBuf buf, int offset, int length) { + short[] result = new short[length]; + + for (int i = 0; i < length; i++) { + result[i] = buf.getShortLE(offset + i * 2); + } + + return result; + } + + @Nonnull + public static float[] readFloatArrayLE(@Nonnull ByteBuf buf, int offset, int length) { + float[] result = new float[length]; + + for (int i = 0; i < length; i++) { + result[i] = buf.getFloatLE(offset + i * 4); + } + + return result; + } + + @Nonnull + public static String readFixedAsciiString(@Nonnull ByteBuf buf, int offset, int length) { + byte[] bytes = new byte[length]; + buf.getBytes(offset, bytes); + int end = 0; + + while (end < length && bytes[end] != 0) { + end++; + } + + return new String(bytes, 0, end, StandardCharsets.US_ASCII); + } + + @Nonnull + public static String readFixedString(@Nonnull ByteBuf buf, int offset, int length) { + byte[] bytes = new byte[length]; + buf.getBytes(offset, bytes); + int end = 0; + + while (end < length && bytes[end] != 0) { + end++; + } + + return new String(bytes, 0, end, StandardCharsets.UTF_8); + } + + @Nonnull + public static String readVarString(@Nonnull ByteBuf buf, int offset) { + return readVarString(buf, offset, StandardCharsets.UTF_8); + } + + @Nonnull + public static String readVarAsciiString(@Nonnull ByteBuf buf, int offset) { + return readVarString(buf, offset, StandardCharsets.US_ASCII); + } + + @Nonnull + public static String readVarString(@Nonnull ByteBuf buf, int offset, Charset charset) { + int len = VarInt.peek(buf, offset); + int varIntLen = VarInt.length(buf, offset); + byte[] bytes = new byte[len]; + buf.getBytes(offset + varIntLen, bytes); + return new String(bytes, charset); + } + + public static int utf8ByteLength(@Nonnull String s) { + int len = 0; + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c < 128) { + len++; + } else if (c < 2048) { + len += 2; + } else if (Character.isHighSurrogate(c)) { + len += 4; + i++; + } else { + len += 3; + } + } + + return len; + } + + public static int stringSize(@Nonnull String s) { + int len = utf8ByteLength(s); + return VarInt.size(len) + len; + } + + public static void writeFixedBytes(@Nonnull ByteBuf buf, @Nonnull byte[] data, int length) { + buf.writeBytes(data, 0, Math.min(data.length, length)); + + for (int i = data.length; i < length; i++) { + buf.writeByte(0); + } + } + + public static void writeFixedAsciiString(@Nonnull ByteBuf buf, @Nullable String value, int length) { + if (value != null) { + byte[] bytes = value.getBytes(StandardCharsets.US_ASCII); + if (bytes.length > length) { + throw new ProtocolException("Fixed ASCII string exceeds length: " + bytes.length + " > " + length); + } + + buf.writeBytes(bytes); + buf.writeZero(length - bytes.length); + } else { + buf.writeZero(length); + } + } + + public static void writeFixedString(@Nonnull ByteBuf buf, @Nullable String value, int length) { + if (value != null) { + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + if (bytes.length > length) { + throw new ProtocolException("Fixed UTF-8 string exceeds length: " + bytes.length + " > " + length); + } + + buf.writeBytes(bytes); + buf.writeZero(length - bytes.length); + } else { + buf.writeZero(length); + } + } + + public static void writeVarString(@Nonnull ByteBuf buf, @Nonnull String value, int maxLength) { + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + if (bytes.length > maxLength) { + throw new ProtocolException("String exceeds max bytes: " + bytes.length + " > " + maxLength); + } else { + VarInt.write(buf, bytes.length); + buf.writeBytes(bytes); + } + } + + public static void writeVarAsciiString(@Nonnull ByteBuf buf, @Nonnull String value, int maxLength) { + byte[] bytes = value.getBytes(StandardCharsets.US_ASCII); + if (bytes.length > maxLength) { + throw new ProtocolException("String exceeds max bytes: " + bytes.length + " > " + maxLength); + } else { + VarInt.write(buf, bytes.length); + buf.writeBytes(bytes); + } + } + + @Nonnull + public static UUID readUUID(@Nonnull ByteBuf buf, int offset) { + long mostSig = buf.getLong(offset); + long leastSig = buf.getLong(offset + 8); + return new UUID(mostSig, leastSig); + } + + public static void writeUUID(@Nonnull ByteBuf buf, @Nonnull UUID value) { + buf.writeLong(value.getMostSignificantBits()); + buf.writeLong(value.getLeastSignificantBits()); + } + + private static float halfToFloat(short half) { + int h = half & '\uffff'; + int sign = h >>> 15 & 1; + int exp = h >>> 10 & 31; + int mant = h & 1023; + if (exp == 0) { + if (mant == 0) { + return sign == 0 ? 0.0F : -0.0F; + } + + for (exp = 1; (mant & 1024) == 0; exp--) { + mant <<= 1; + } + + mant &= 1023; + } else if (exp == 31) { + return mant == 0 ? (sign == 0 ? Float.POSITIVE_INFINITY : Float.NEGATIVE_INFINITY) : Float.NaN; + } + + int floatBits = sign << 31 | exp + 112 << 23 | mant << 13; + return Float.intBitsToFloat(floatBits); + } + + private static short floatToHalf(float f) { + int bits = Float.floatToRawIntBits(f); + int sign = bits >>> 16 & 32768; + int val = (bits & 2147483647) + 4096; + if (val >= 1199570944) { + if ((bits & 2147483647) >= 1199570944) { + return val < 2139095040 ? (short)(sign | 31744) : (short)(sign | 31744 | (bits & 8388607) >>> 13); + } else { + return (short)(sign | 31743); + } + } else if (val >= 947912704) { + return (short)(sign | val - 939524096 >>> 13); + } else if (val < 855638016) { + return (short)sign; + } else { + val = (bits & 2147483647) >>> 23; + return (short)(sign | (bits & 8388607 | 8388608) + (8388608 >>> val - 102) >>> 126 - val); + } + } + + private static int compressToBuffer(@Nonnull ByteBuf src, @Nonnull ByteBuf dst, int dstOffset, int maxDstSize) { + if (src.isDirect() && dst.isDirect()) { + return Zstd.compress(dst.nioBuffer(dstOffset, maxDstSize), src.nioBuffer(), COMPRESSION_LEVEL); + } else { + int srcSize = src.readableBytes(); + byte[] srcBytes = new byte[srcSize]; + src.getBytes(src.readerIndex(), srcBytes); + byte[] compressed = Zstd.compress(srcBytes, COMPRESSION_LEVEL); + dst.setBytes(dstOffset, compressed); + return compressed.length; + } + } + + @Nonnull + private static ByteBuf decompressFromBuffer(@Nonnull ByteBuf src, int srcOffset, int srcLength, int maxDecompressedSize) { + if (srcLength > maxDecompressedSize) { + throw new ProtocolException("Compressed size " + srcLength + " exceeds max decompressed size " + maxDecompressedSize); + } else if (src.isDirect()) { + ByteBuffer srcNio = src.nioBuffer(srcOffset, srcLength); + long decompressedSize = Zstd.getFrameContentSize(srcNio); + if (decompressedSize < 0L) { + throw new ProtocolException("Invalid Zstd frame or unknown content size"); + } else if (decompressedSize > maxDecompressedSize) { + throw new ProtocolException("Decompressed size " + decompressedSize + " exceeds maximum " + maxDecompressedSize); + } else { + ByteBuf dst = Unpooled.directBuffer((int)decompressedSize); + ByteBuffer dstNio = dst.nioBuffer(0, (int)decompressedSize); + int result = Zstd.decompress(dstNio, srcNio); + if (Zstd.isError(result)) { + dst.release(); + throw new ProtocolException("Zstd decompression failed: " + Zstd.getErrorName(result)); + } else { + dst.writerIndex(result); + return dst; + } + } + } else { + byte[] srcBytes = new byte[srcLength]; + src.getBytes(srcOffset, srcBytes); + long decompressedSize = Zstd.getFrameContentSize(srcBytes); + if (decompressedSize < 0L) { + throw new ProtocolException("Invalid Zstd frame or unknown content size"); + } else if (decompressedSize > maxDecompressedSize) { + throw new ProtocolException("Decompressed size " + decompressedSize + " exceeds maximum " + maxDecompressedSize); + } else { + byte[] decompressed = Zstd.decompress(srcBytes, (int)decompressedSize); + return Unpooled.wrappedBuffer(decompressed); + } + } + } + + public static void writeFramedPacket( + @Nonnull Packet packet, @Nonnull Class packetClass, @Nonnull ByteBuf out, @Nonnull PacketStatsRecorder statsRecorder + ) { + Integer id = PacketRegistry.getId(packetClass); + if (id == null) { + throw new ProtocolException("Unknown packet type: " + packetClass.getName()); + } else { + PacketRegistry.PacketInfo info = PacketRegistry.getById(id); + int lengthIndex = out.writerIndex(); + out.writeIntLE(0); + out.writeIntLE(id); + ByteBuf payloadBuf = Unpooled.buffer(Math.min(info.maxSize(), 65536)); + + try { + packet.serialize(payloadBuf); + int serializedSize = payloadBuf.readableBytes(); + if (serializedSize > info.maxSize()) { + throw new ProtocolException("Packet " + info.name() + " serialized to " + serializedSize + " bytes, exceeds max size " + info.maxSize()); + } + + if (info.compressed() && serializedSize > 0) { + int compressBound = (int)Zstd.compressBound(serializedSize); + out.ensureWritable(compressBound); + int compressedSize = compressToBuffer(payloadBuf, out, out.writerIndex(), compressBound); + if (Zstd.isError(compressedSize)) { + throw new ProtocolException("Zstd compression failed: " + Zstd.getErrorName(compressedSize)); + } + + if (compressedSize > 1677721600) { + throw new ProtocolException("Packet " + info.name() + " compressed payload size " + compressedSize + " exceeds protocol maximum"); + } + + out.writerIndex(out.writerIndex() + compressedSize); + out.setIntLE(lengthIndex, compressedSize); + statsRecorder.recordSend(id, serializedSize, compressedSize); + } else { + if (serializedSize > 1677721600) { + throw new ProtocolException("Packet " + info.name() + " payload size " + serializedSize + " exceeds protocol maximum"); + } + + out.writeBytes(payloadBuf); + out.setIntLE(lengthIndex, serializedSize); + statsRecorder.recordSend(id, serializedSize, 0); + } + } finally { + payloadBuf.release(); + } + } + } + + @Nonnull + public static Packet readFramedPacket(@Nonnull ByteBuf in, int payloadLength, @Nonnull PacketStatsRecorder statsRecorder) { + int packetId = in.readIntLE(); + PacketRegistry.PacketInfo info = PacketRegistry.getById(packetId); + if (info == null) { + in.skipBytes(payloadLength); + throw new ProtocolException("Unknown packet ID: " + packetId); + } else { + return readFramedPacketWithInfo(in, payloadLength, info, statsRecorder); + } + } + + @Nonnull + public static Packet readFramedPacketWithInfo( + @Nonnull ByteBuf in, int payloadLength, @Nonnull PacketRegistry.PacketInfo info, @Nonnull PacketStatsRecorder statsRecorder + ) { + int compressedSize = 0; + ByteBuf payload; + int uncompressedSize; + if (info.compressed() && payloadLength > 0) { + try { + payload = decompressFromBuffer(in, in.readerIndex(), payloadLength, info.maxSize()); + } catch (ProtocolException var12) { + in.skipBytes(payloadLength); + throw var12; + } + + in.skipBytes(payloadLength); + uncompressedSize = payload.readableBytes(); + compressedSize = payloadLength; + } else if (payloadLength > 0) { + payload = in.readRetainedSlice(payloadLength); + uncompressedSize = payloadLength; + } else { + payload = Unpooled.EMPTY_BUFFER; + uncompressedSize = 0; + } + + Packet var8; + try { + Packet packet = info.deserialize().apply(payload, 0); + statsRecorder.recordReceive(info.id(), uncompressedSize, compressedSize); + var8 = packet; + } finally { + if (payloadLength > 0) { + payload.release(); + } + } + + return var8; + } +} diff --git a/src/com/hypixel/hytale/protocol/io/PacketStatsRecorder.java b/src/com/hypixel/hytale/protocol/io/PacketStatsRecorder.java new file mode 100644 index 0000000..dace58f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/io/PacketStatsRecorder.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.protocol.io; + +import io.netty.util.AttributeKey; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface PacketStatsRecorder { + AttributeKey CHANNEL_KEY = AttributeKey.valueOf("PacketStatsRecorder"); + PacketStatsRecorder NOOP = new NoopPacketStatsRecorder(); + + void recordSend(int var1, int var2, int var3); + + void recordReceive(int var1, int var2, int var3); + + @Nonnull + PacketStatsRecorder.PacketStatsEntry getEntry(int var1); + + public interface PacketStatsEntry { + int RECENT_SECONDS = 30; + + int getPacketId(); + + @Nullable + String getName(); + + boolean hasData(); + + int getSentCount(); + + long getSentUncompressedTotal(); + + long getSentCompressedTotal(); + + long getSentUncompressedMin(); + + long getSentUncompressedMax(); + + long getSentCompressedMin(); + + long getSentCompressedMax(); + + double getSentUncompressedAvg(); + + double getSentCompressedAvg(); + + @Nonnull + PacketStatsRecorder.RecentStats getSentRecently(); + + int getReceivedCount(); + + long getReceivedUncompressedTotal(); + + long getReceivedCompressedTotal(); + + long getReceivedUncompressedMin(); + + long getReceivedUncompressedMax(); + + long getReceivedCompressedMin(); + + long getReceivedCompressedMax(); + + double getReceivedUncompressedAvg(); + + double getReceivedCompressedAvg(); + + @Nonnull + PacketStatsRecorder.RecentStats getReceivedRecently(); + } + + public record RecentStats( + int count, long uncompressedTotal, long compressedTotal, int uncompressedMin, int uncompressedMax, int compressedMin, int compressedMax + ) { + public static final PacketStatsRecorder.RecentStats EMPTY = new PacketStatsRecorder.RecentStats(0, 0L, 0L, 0, 0, 0, 0); + } +} diff --git a/src/com/hypixel/hytale/protocol/io/ProtocolException.java b/src/com/hypixel/hytale/protocol/io/ProtocolException.java new file mode 100644 index 0000000..cfe5dfe --- /dev/null +++ b/src/com/hypixel/hytale/protocol/io/ProtocolException.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.protocol.io; + +import javax.annotation.Nonnull; + +public class ProtocolException extends RuntimeException { + public ProtocolException(@Nonnull String message) { + super(message); + } + + public ProtocolException(@Nonnull String message, @Nonnull Throwable cause) { + super(message, cause); + } + + @Nonnull + public static ProtocolException arrayTooLong(@Nonnull String fieldName, int actual, int max) { + return new ProtocolException(fieldName + ": array length " + actual + " exceeds maximum " + max); + } + + @Nonnull + public static ProtocolException stringTooLong(@Nonnull String fieldName, int actual, int max) { + return new ProtocolException(fieldName + ": string length " + actual + " exceeds maximum " + max); + } + + @Nonnull + public static ProtocolException dictionaryTooLarge(@Nonnull String fieldName, int actual, int max) { + return new ProtocolException(fieldName + ": dictionary count " + actual + " exceeds maximum " + max); + } + + @Nonnull + public static ProtocolException bufferTooSmall(@Nonnull String fieldName, int required, int available) { + return new ProtocolException(fieldName + ": buffer too small, need " + required + " bytes but only " + available + " available"); + } + + @Nonnull + public static ProtocolException invalidVarInt(@Nonnull String fieldName) { + return new ProtocolException(fieldName + ": invalid or incomplete VarInt"); + } + + @Nonnull + public static ProtocolException negativeLength(@Nonnull String fieldName, int value) { + return new ProtocolException(fieldName + ": negative length " + value); + } + + @Nonnull + public static ProtocolException invalidOffset(@Nonnull String fieldName, int offset, int bufferLength) { + return new ProtocolException(fieldName + ": offset " + offset + " is out of bounds (buffer length: " + bufferLength + ")"); + } + + @Nonnull + public static ProtocolException unknownPolymorphicType(@Nonnull String typeName, int typeId) { + return new ProtocolException(typeName + ": unknown polymorphic type ID " + typeId); + } + + @Nonnull + public static ProtocolException duplicateKey(@Nonnull String fieldName, @Nonnull Object key) { + return new ProtocolException(fieldName + ": duplicate key '" + key + "'"); + } + + @Nonnull + public static ProtocolException invalidEnumValue(@Nonnull String enumName, int value) { + return new ProtocolException(enumName + ": invalid enum value " + value); + } + + @Nonnull + public static ProtocolException arrayTooShort(@Nonnull String fieldName, int actual, int min) { + return new ProtocolException(fieldName + ": array length " + actual + " is below minimum " + min); + } + + @Nonnull + public static ProtocolException stringTooShort(@Nonnull String fieldName, int actual, int min) { + return new ProtocolException(fieldName + ": string length " + actual + " is below minimum " + min); + } + + @Nonnull + public static ProtocolException dictionaryTooSmall(@Nonnull String fieldName, int actual, int min) { + return new ProtocolException(fieldName + ": dictionary count " + actual + " is below minimum " + min); + } + + @Nonnull + public static ProtocolException valueOutOfRange(@Nonnull String fieldName, @Nonnull Object value, double min, double max) { + return new ProtocolException(fieldName + ": value " + value + " is outside allowed range [" + min + ", " + max + "]"); + } + + @Nonnull + public static ProtocolException valueBelowMinimum(@Nonnull String fieldName, @Nonnull Object value, double min) { + return new ProtocolException(fieldName + ": value " + value + " is below minimum " + min); + } + + @Nonnull + public static ProtocolException valueAboveMaximum(@Nonnull String fieldName, @Nonnull Object value, double max) { + return new ProtocolException(fieldName + ": value " + value + " exceeds maximum " + max); + } +} diff --git a/src/com/hypixel/hytale/protocol/io/ValidationResult.java b/src/com/hypixel/hytale/protocol/io/ValidationResult.java new file mode 100644 index 0000000..b674b6c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/io/ValidationResult.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.protocol.io; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public record ValidationResult(boolean isValid, @Nullable String error) { + public static final ValidationResult OK = new ValidationResult(true, null); + + @Nonnull + public static ValidationResult error(@Nonnull String message) { + return new ValidationResult(false, message); + } + + public void throwIfInvalid() { + if (!this.isValid) { + throw new ProtocolException(this.error != null ? this.error : "Validation failed"); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/io/VarInt.java b/src/com/hypixel/hytale/protocol/io/VarInt.java new file mode 100644 index 0000000..a0f43ec --- /dev/null +++ b/src/com/hypixel/hytale/protocol/io/VarInt.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.protocol.io; + +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public final class VarInt { + private VarInt() { + } + + public static void write(@Nonnull ByteBuf buf, int value) { + if (value < 0) { + throw new IllegalArgumentException("VarInt cannot encode negative values: " + value); + } else { + while ((value & -128) != 0) { + buf.writeByte(value & 127 | 128); + value >>>= 7; + } + + buf.writeByte(value); + } + } + + public static int read(@Nonnull ByteBuf buf) { + int value = 0; + int shift = 0; + + do { + byte b = buf.readByte(); + value |= (b & 127) << shift; + if ((b & 128) == 0) { + return value; + } + + shift += 7; + } while (shift <= 28); + + throw new ProtocolException("VarInt exceeds maximum length (5 bytes)"); + } + + public static int peek(@Nonnull ByteBuf buf, int index) { + int value = 0; + int shift = 0; + int pos = index; + + while (pos < buf.writerIndex()) { + byte b = buf.getByte(pos++); + value |= (b & 127) << shift; + if ((b & 128) == 0) { + return value; + } + + shift += 7; + if (shift > 28) { + return -1; + } + } + + return -1; + } + + public static int length(@Nonnull ByteBuf buf, int index) { + int pos = index; + + while (pos < buf.writerIndex()) { + if ((buf.getByte(pos++) & 128) == 0) { + return pos - index; + } + + if (pos - index > 5) { + return -1; + } + } + + return -1; + } + + public static int size(int value) { + if ((value & -128) == 0) { + return 1; + } else if ((value & -16384) == 0) { + return 2; + } else if ((value & -2097152) == 0) { + return 3; + } else { + return (value & -268435456) == 0 ? 4 : 5; + } + } +} diff --git a/src/com/hypixel/hytale/protocol/io/netty/PacketDecoder.java b/src/com/hypixel/hytale/protocol/io/netty/PacketDecoder.java new file mode 100644 index 0000000..5fdd5cf --- /dev/null +++ b/src/com/hypixel/hytale/protocol/io/netty/PacketDecoder.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.protocol.io.netty; + +import com.hypixel.hytale.protocol.PacketRegistry; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.PacketStatsRecorder; +import com.hypixel.hytale.protocol.io.ProtocolException; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import java.util.List; +import javax.annotation.Nonnull; + +public class PacketDecoder extends ByteToMessageDecoder { + private static final int LENGTH_PREFIX_SIZE = 4; + private static final int PACKET_ID_SIZE = 4; + private static final int MIN_FRAME_SIZE = 8; + + public PacketDecoder() { + } + + @Override + protected void decode(@Nonnull ChannelHandlerContext ctx, @Nonnull ByteBuf in, @Nonnull List out) { + if (in.readableBytes() >= 8) { + in.markReaderIndex(); + int payloadLength = in.readIntLE(); + if (payloadLength >= 0 && payloadLength <= 1677721600) { + int packetId = in.readIntLE(); + PacketRegistry.PacketInfo packetInfo = PacketRegistry.getById(packetId); + if (packetInfo == null) { + in.skipBytes(in.readableBytes()); + ProtocolUtil.closeConnection(ctx.channel()); + } else if (payloadLength > packetInfo.maxSize()) { + in.skipBytes(in.readableBytes()); + ProtocolUtil.closeConnection(ctx.channel()); + } else if (in.readableBytes() < payloadLength) { + in.resetReaderIndex(); + } else { + PacketStatsRecorder statsRecorder = ctx.channel().attr(PacketStatsRecorder.CHANNEL_KEY).get(); + if (statsRecorder == null) { + statsRecorder = PacketStatsRecorder.NOOP; + } + + try { + out.add(PacketIO.readFramedPacketWithInfo(in, payloadLength, packetInfo, statsRecorder)); + } catch (ProtocolException var9) { + in.skipBytes(in.readableBytes()); + ProtocolUtil.closeConnection(ctx.channel()); + } catch (IndexOutOfBoundsException var10) { + in.skipBytes(in.readableBytes()); + ProtocolUtil.closeConnection(ctx.channel()); + } + } + } else { + in.skipBytes(in.readableBytes()); + ProtocolUtil.closeConnection(ctx.channel()); + } + } + } +} diff --git a/src/com/hypixel/hytale/protocol/io/netty/PacketEncoder.java b/src/com/hypixel/hytale/protocol/io/netty/PacketEncoder.java new file mode 100644 index 0000000..19a1e76 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/io/netty/PacketEncoder.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.protocol.io.netty; + +import com.hypixel.hytale.protocol.CachedPacket; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.PacketStatsRecorder; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.handler.codec.MessageToByteEncoder; +import javax.annotation.Nonnull; + +@Sharable +public class PacketEncoder extends MessageToByteEncoder { + public PacketEncoder() { + } + + protected void encode(@Nonnull ChannelHandlerContext ctx, @Nonnull Packet packet, @Nonnull ByteBuf out) { + Class packetClass; + if (packet instanceof CachedPacket cached) { + packetClass = (Class)cached.getPacketType(); + } else { + packetClass = (Class)packet.getClass(); + } + + PacketStatsRecorder statsRecorder = ctx.channel().attr(PacketStatsRecorder.CHANNEL_KEY).get(); + if (statsRecorder == null) { + statsRecorder = PacketStatsRecorder.NOOP; + } + + PacketIO.writeFramedPacket(packet, packetClass, out, statsRecorder); + } +} diff --git a/src/com/hypixel/hytale/protocol/io/netty/ProtocolUtil.java b/src/com/hypixel/hytale/protocol/io/netty/ProtocolUtil.java new file mode 100644 index 0000000..64b6b6a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/io/netty/ProtocolUtil.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.protocol.io.netty; + +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.handler.codec.quic.QuicChannel; +import io.netty.handler.codec.quic.QuicTransportError; +import javax.annotation.Nonnull; + +public final class ProtocolUtil { + public static final int APPLICATION_NO_ERROR = 0; + public static final int APPLICATION_RATE_LIMITED = 1; + public static final int APPLICATION_AUTH_FAILED = 2; + public static final int APPLICATION_INVALID_VERSION = 3; + public static final ChannelFutureListener CLOSE_ON_COMPLETE = ProtocolUtil::closeApplicationOnComplete; + + private ProtocolUtil() { + } + + public static void closeConnection(@Nonnull Channel channel) { + closeConnection(channel, QuicTransportError.PROTOCOL_VIOLATION); + } + + public static void closeConnection(@Nonnull Channel channel, @Nonnull QuicTransportError error) { + int errorCode = (int)error.code(); + if (channel instanceof QuicChannel quicChannel) { + quicChannel.close(false, errorCode, Unpooled.EMPTY_BUFFER); + } else { + if (channel.parent() instanceof QuicChannel quicChannel) { + quicChannel.close(false, errorCode, Unpooled.EMPTY_BUFFER); + } else { + channel.close(); + } + } + } + + public static void closeApplicationConnection(@Nonnull Channel channel) { + closeApplicationConnection(channel, 0); + } + + public static void closeApplicationConnection(@Nonnull Channel channel, int errorCode) { + if (channel instanceof QuicChannel quicChannel) { + quicChannel.close(true, errorCode, Unpooled.EMPTY_BUFFER); + } else { + if (channel.parent() instanceof QuicChannel quicChannel) { + quicChannel.close(true, errorCode, Unpooled.EMPTY_BUFFER); + } else { + channel.close(); + } + } + } + + private static void closeApplicationOnComplete(ChannelFuture future) { + closeApplicationConnection(future.channel()); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorActivateButton.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorActivateButton.java new file mode 100644 index 0000000..2285387 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorActivateButton.java @@ -0,0 +1,143 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorActivateButton implements Packet { + public static final int PACKET_ID = 335; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 16384006; + @Nullable + public String buttonId; + + @Override + public int getId() { + return 335; + } + + public AssetEditorActivateButton() { + } + + public AssetEditorActivateButton(@Nullable String buttonId) { + this.buttonId = buttonId; + } + + public AssetEditorActivateButton(@Nonnull AssetEditorActivateButton other) { + this.buttonId = other.buttonId; + } + + @Nonnull + public static AssetEditorActivateButton deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorActivateButton obj = new AssetEditorActivateButton(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int buttonIdLen = VarInt.peek(buf, pos); + if (buttonIdLen < 0) { + throw ProtocolException.negativeLength("ButtonId", buttonIdLen); + } + + if (buttonIdLen > 4096000) { + throw ProtocolException.stringTooLong("ButtonId", buttonIdLen, 4096000); + } + + int buttonIdVarLen = VarInt.length(buf, pos); + obj.buttonId = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += buttonIdVarLen + buttonIdLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.buttonId != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.buttonId != null) { + PacketIO.writeVarString(buf, this.buttonId, 4096000); + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.buttonId != null) { + size += PacketIO.stringSize(this.buttonId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int buttonIdLen = VarInt.peek(buffer, pos); + if (buttonIdLen < 0) { + return ValidationResult.error("Invalid string length for ButtonId"); + } + + if (buttonIdLen > 4096000) { + return ValidationResult.error("ButtonId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += buttonIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ButtonId"); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorActivateButton clone() { + AssetEditorActivateButton copy = new AssetEditorActivateButton(); + copy.buttonId = this.buttonId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorActivateButton other ? Objects.equals(this.buttonId, other.buttonId) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.buttonId); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAsset.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAsset.java new file mode 100644 index 0000000..1d07556 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAsset.java @@ -0,0 +1,207 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorAsset { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 49152033; + @Nullable + public String hash; + @Nullable + public AssetPath path; + + public AssetEditorAsset() { + } + + public AssetEditorAsset(@Nullable String hash, @Nullable AssetPath path) { + this.hash = hash; + this.path = path; + } + + public AssetEditorAsset(@Nonnull AssetEditorAsset other) { + this.hash = other.hash; + this.path = other.path; + } + + @Nonnull + public static AssetEditorAsset deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorAsset obj = new AssetEditorAsset(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int hashLen = VarInt.peek(buf, varPos0); + if (hashLen < 0) { + throw ProtocolException.negativeLength("Hash", hashLen); + } + + if (hashLen > 4096000) { + throw ProtocolException.stringTooLong("Hash", hashLen, 4096000); + } + + obj.hash = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + obj.path = AssetPath.deserialize(buf, varPos1); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + pos1 += AssetPath.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.hash != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.path != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int hashOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int pathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.hash != null) { + buf.setIntLE(hashOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.hash, 4096000); + } else { + buf.setIntLE(hashOffsetSlot, -1); + } + + if (this.path != null) { + buf.setIntLE(pathOffsetSlot, buf.writerIndex() - varBlockStart); + this.path.serialize(buf); + } else { + buf.setIntLE(pathOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 9; + if (this.hash != null) { + size += PacketIO.stringSize(this.hash); + } + + if (this.path != null) { + size += this.path.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int hashOffset = buffer.getIntLE(offset + 1); + if (hashOffset < 0) { + return ValidationResult.error("Invalid offset for Hash"); + } + + int pos = offset + 9 + hashOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Hash"); + } + + int hashLen = VarInt.peek(buffer, pos); + if (hashLen < 0) { + return ValidationResult.error("Invalid string length for Hash"); + } + + if (hashLen > 4096000) { + return ValidationResult.error("Hash exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += hashLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Hash"); + } + } + + if ((nullBits & 2) != 0) { + int pathOffset = buffer.getIntLE(offset + 5); + if (pathOffset < 0) { + return ValidationResult.error("Invalid offset for Path"); + } + + int posx = offset + 9 + pathOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Path"); + } + + ValidationResult pathResult = AssetPath.validateStructure(buffer, posx); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + posx += AssetPath.computeBytesConsumed(buffer, posx); + } + + return ValidationResult.OK; + } + } + + public AssetEditorAsset clone() { + AssetEditorAsset copy = new AssetEditorAsset(); + copy.hash = this.hash; + copy.path = this.path != null ? this.path.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorAsset other) ? false : Objects.equals(this.hash, other.hash) && Objects.equals(this.path, other.path); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.hash, this.path); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAssetListSetup.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAssetListSetup.java new file mode 100644 index 0000000..f65a3db --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAssetListSetup.java @@ -0,0 +1,303 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorAssetListSetup implements Packet { + public static final int PACKET_ID = 319; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 12; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String pack; + public boolean isReadOnly; + public boolean canBeDeleted; + @Nonnull + public AssetEditorFileTree tree = AssetEditorFileTree.Server; + @Nullable + public AssetEditorFileEntry[] paths; + + @Override + public int getId() { + return 319; + } + + public AssetEditorAssetListSetup() { + } + + public AssetEditorAssetListSetup( + @Nullable String pack, boolean isReadOnly, boolean canBeDeleted, @Nonnull AssetEditorFileTree tree, @Nullable AssetEditorFileEntry[] paths + ) { + this.pack = pack; + this.isReadOnly = isReadOnly; + this.canBeDeleted = canBeDeleted; + this.tree = tree; + this.paths = paths; + } + + public AssetEditorAssetListSetup(@Nonnull AssetEditorAssetListSetup other) { + this.pack = other.pack; + this.isReadOnly = other.isReadOnly; + this.canBeDeleted = other.canBeDeleted; + this.tree = other.tree; + this.paths = other.paths; + } + + @Nonnull + public static AssetEditorAssetListSetup deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorAssetListSetup obj = new AssetEditorAssetListSetup(); + byte nullBits = buf.getByte(offset); + obj.isReadOnly = buf.getByte(offset + 1) != 0; + obj.canBeDeleted = buf.getByte(offset + 2) != 0; + obj.tree = AssetEditorFileTree.fromValue(buf.getByte(offset + 3)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 12 + buf.getIntLE(offset + 4); + int packLen = VarInt.peek(buf, varPos0); + if (packLen < 0) { + throw ProtocolException.negativeLength("Pack", packLen); + } + + if (packLen > 4096000) { + throw ProtocolException.stringTooLong("Pack", packLen, 4096000); + } + + obj.pack = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 12 + buf.getIntLE(offset + 8); + int pathsCount = VarInt.peek(buf, varPos1); + if (pathsCount < 0) { + throw ProtocolException.negativeLength("Paths", pathsCount); + } + + if (pathsCount > 4096000) { + throw ProtocolException.arrayTooLong("Paths", pathsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + pathsCount * 2L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Paths", varPos1 + varIntLen + pathsCount * 2, buf.readableBytes()); + } + + obj.paths = new AssetEditorFileEntry[pathsCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < pathsCount; i++) { + obj.paths[i] = AssetEditorFileEntry.deserialize(buf, elemPos); + elemPos += AssetEditorFileEntry.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 12; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 4); + int pos0 = offset + 12 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 8); + int pos1 = offset + 12 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += AssetEditorFileEntry.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.pack != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.paths != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.isReadOnly ? 1 : 0); + buf.writeByte(this.canBeDeleted ? 1 : 0); + buf.writeByte(this.tree.getValue()); + int packOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int pathsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.pack != null) { + buf.setIntLE(packOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.pack, 4096000); + } else { + buf.setIntLE(packOffsetSlot, -1); + } + + if (this.paths != null) { + buf.setIntLE(pathsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.paths.length > 4096000) { + throw ProtocolException.arrayTooLong("Paths", this.paths.length, 4096000); + } + + VarInt.write(buf, this.paths.length); + + for (AssetEditorFileEntry item : this.paths) { + item.serialize(buf); + } + } else { + buf.setIntLE(pathsOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 12; + if (this.pack != null) { + size += PacketIO.stringSize(this.pack); + } + + if (this.paths != null) { + int pathsSize = 0; + + for (AssetEditorFileEntry elem : this.paths) { + pathsSize += elem.computeSize(); + } + + size += VarInt.size(this.paths.length) + pathsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 12) { + return ValidationResult.error("Buffer too small: expected at least 12 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int packOffset = buffer.getIntLE(offset + 4); + if (packOffset < 0) { + return ValidationResult.error("Invalid offset for Pack"); + } + + int pos = offset + 12 + packOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Pack"); + } + + int packLen = VarInt.peek(buffer, pos); + if (packLen < 0) { + return ValidationResult.error("Invalid string length for Pack"); + } + + if (packLen > 4096000) { + return ValidationResult.error("Pack exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += packLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Pack"); + } + } + + if ((nullBits & 2) != 0) { + int pathsOffset = buffer.getIntLE(offset + 8); + if (pathsOffset < 0) { + return ValidationResult.error("Invalid offset for Paths"); + } + + int posx = offset + 12 + pathsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Paths"); + } + + int pathsCount = VarInt.peek(buffer, posx); + if (pathsCount < 0) { + return ValidationResult.error("Invalid array count for Paths"); + } + + if (pathsCount > 4096000) { + return ValidationResult.error("Paths exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < pathsCount; i++) { + ValidationResult structResult = AssetEditorFileEntry.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid AssetEditorFileEntry in Paths[" + i + "]: " + structResult.error()); + } + + posx += AssetEditorFileEntry.computeBytesConsumed(buffer, posx); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorAssetListSetup clone() { + AssetEditorAssetListSetup copy = new AssetEditorAssetListSetup(); + copy.pack = this.pack; + copy.isReadOnly = this.isReadOnly; + copy.canBeDeleted = this.canBeDeleted; + copy.tree = this.tree; + copy.paths = this.paths != null ? Arrays.stream(this.paths).map(e -> e.clone()).toArray(AssetEditorFileEntry[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorAssetListSetup other) + ? false + : Objects.equals(this.pack, other.pack) + && this.isReadOnly == other.isReadOnly + && this.canBeDeleted == other.canBeDeleted + && Objects.equals(this.tree, other.tree) + && Arrays.equals((Object[])this.paths, (Object[])other.paths); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.pack); + result = 31 * result + Boolean.hashCode(this.isReadOnly); + result = 31 * result + Boolean.hashCode(this.canBeDeleted); + result = 31 * result + Objects.hashCode(this.tree); + return 31 * result + Arrays.hashCode((Object[])this.paths); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAssetListUpdate.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAssetListUpdate.java new file mode 100644 index 0000000..33bb225 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAssetListUpdate.java @@ -0,0 +1,386 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorAssetListUpdate implements Packet { + public static final int PACKET_ID = 320; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String pack; + @Nullable + public AssetEditorFileEntry[] additions; + @Nullable + public AssetEditorFileEntry[] deletions; + + @Override + public int getId() { + return 320; + } + + public AssetEditorAssetListUpdate() { + } + + public AssetEditorAssetListUpdate(@Nullable String pack, @Nullable AssetEditorFileEntry[] additions, @Nullable AssetEditorFileEntry[] deletions) { + this.pack = pack; + this.additions = additions; + this.deletions = deletions; + } + + public AssetEditorAssetListUpdate(@Nonnull AssetEditorAssetListUpdate other) { + this.pack = other.pack; + this.additions = other.additions; + this.deletions = other.deletions; + } + + @Nonnull + public static AssetEditorAssetListUpdate deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorAssetListUpdate obj = new AssetEditorAssetListUpdate(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 1); + int packLen = VarInt.peek(buf, varPos0); + if (packLen < 0) { + throw ProtocolException.negativeLength("Pack", packLen); + } + + if (packLen > 4096000) { + throw ProtocolException.stringTooLong("Pack", packLen, 4096000); + } + + obj.pack = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 5); + int additionsCount = VarInt.peek(buf, varPos1); + if (additionsCount < 0) { + throw ProtocolException.negativeLength("Additions", additionsCount); + } + + if (additionsCount > 4096000) { + throw ProtocolException.arrayTooLong("Additions", additionsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + additionsCount * 2L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Additions", varPos1 + varIntLen + additionsCount * 2, buf.readableBytes()); + } + + obj.additions = new AssetEditorFileEntry[additionsCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < additionsCount; i++) { + obj.additions[i] = AssetEditorFileEntry.deserialize(buf, elemPos); + elemPos += AssetEditorFileEntry.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 13 + buf.getIntLE(offset + 9); + int deletionsCount = VarInt.peek(buf, varPos2); + if (deletionsCount < 0) { + throw ProtocolException.negativeLength("Deletions", deletionsCount); + } + + if (deletionsCount > 4096000) { + throw ProtocolException.arrayTooLong("Deletions", deletionsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + deletionsCount * 2L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Deletions", varPos2 + varIntLen + deletionsCount * 2, buf.readableBytes()); + } + + obj.deletions = new AssetEditorFileEntry[deletionsCount]; + int elemPos = varPos2 + varIntLen; + + for (int i = 0; i < deletionsCount; i++) { + obj.deletions[i] = AssetEditorFileEntry.deserialize(buf, elemPos); + elemPos += AssetEditorFileEntry.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 13 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 13 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += AssetEditorFileEntry.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 9); + int pos2 = offset + 13 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2); + + for (int i = 0; i < arrLen; i++) { + pos2 += AssetEditorFileEntry.computeBytesConsumed(buf, pos2); + } + + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.pack != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.additions != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.deletions != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + int packOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int additionsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int deletionsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.pack != null) { + buf.setIntLE(packOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.pack, 4096000); + } else { + buf.setIntLE(packOffsetSlot, -1); + } + + if (this.additions != null) { + buf.setIntLE(additionsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.additions.length > 4096000) { + throw ProtocolException.arrayTooLong("Additions", this.additions.length, 4096000); + } + + VarInt.write(buf, this.additions.length); + + for (AssetEditorFileEntry item : this.additions) { + item.serialize(buf); + } + } else { + buf.setIntLE(additionsOffsetSlot, -1); + } + + if (this.deletions != null) { + buf.setIntLE(deletionsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.deletions.length > 4096000) { + throw ProtocolException.arrayTooLong("Deletions", this.deletions.length, 4096000); + } + + VarInt.write(buf, this.deletions.length); + + for (AssetEditorFileEntry item : this.deletions) { + item.serialize(buf); + } + } else { + buf.setIntLE(deletionsOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 13; + if (this.pack != null) { + size += PacketIO.stringSize(this.pack); + } + + if (this.additions != null) { + int additionsSize = 0; + + for (AssetEditorFileEntry elem : this.additions) { + additionsSize += elem.computeSize(); + } + + size += VarInt.size(this.additions.length) + additionsSize; + } + + if (this.deletions != null) { + int deletionsSize = 0; + + for (AssetEditorFileEntry elem : this.deletions) { + deletionsSize += elem.computeSize(); + } + + size += VarInt.size(this.deletions.length) + deletionsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int packOffset = buffer.getIntLE(offset + 1); + if (packOffset < 0) { + return ValidationResult.error("Invalid offset for Pack"); + } + + int pos = offset + 13 + packOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Pack"); + } + + int packLen = VarInt.peek(buffer, pos); + if (packLen < 0) { + return ValidationResult.error("Invalid string length for Pack"); + } + + if (packLen > 4096000) { + return ValidationResult.error("Pack exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += packLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Pack"); + } + } + + if ((nullBits & 2) != 0) { + int additionsOffset = buffer.getIntLE(offset + 5); + if (additionsOffset < 0) { + return ValidationResult.error("Invalid offset for Additions"); + } + + int posx = offset + 13 + additionsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Additions"); + } + + int additionsCount = VarInt.peek(buffer, posx); + if (additionsCount < 0) { + return ValidationResult.error("Invalid array count for Additions"); + } + + if (additionsCount > 4096000) { + return ValidationResult.error("Additions exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < additionsCount; i++) { + ValidationResult structResult = AssetEditorFileEntry.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid AssetEditorFileEntry in Additions[" + i + "]: " + structResult.error()); + } + + posx += AssetEditorFileEntry.computeBytesConsumed(buffer, posx); + } + } + + if ((nullBits & 4) != 0) { + int deletionsOffset = buffer.getIntLE(offset + 9); + if (deletionsOffset < 0) { + return ValidationResult.error("Invalid offset for Deletions"); + } + + int posxx = offset + 13 + deletionsOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Deletions"); + } + + int deletionsCount = VarInt.peek(buffer, posxx); + if (deletionsCount < 0) { + return ValidationResult.error("Invalid array count for Deletions"); + } + + if (deletionsCount > 4096000) { + return ValidationResult.error("Deletions exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + + for (int i = 0; i < deletionsCount; i++) { + ValidationResult structResult = AssetEditorFileEntry.validateStructure(buffer, posxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid AssetEditorFileEntry in Deletions[" + i + "]: " + structResult.error()); + } + + posxx += AssetEditorFileEntry.computeBytesConsumed(buffer, posxx); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorAssetListUpdate clone() { + AssetEditorAssetListUpdate copy = new AssetEditorAssetListUpdate(); + copy.pack = this.pack; + copy.additions = this.additions != null ? Arrays.stream(this.additions).map(e -> e.clone()).toArray(AssetEditorFileEntry[]::new) : null; + copy.deletions = this.deletions != null ? Arrays.stream(this.deletions).map(e -> e.clone()).toArray(AssetEditorFileEntry[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorAssetListUpdate other) + ? false + : Objects.equals(this.pack, other.pack) + && Arrays.equals((Object[])this.additions, (Object[])other.additions) + && Arrays.equals((Object[])this.deletions, (Object[])other.deletions); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.pack); + result = 31 * result + Arrays.hashCode((Object[])this.additions); + return 31 * result + Arrays.hashCode((Object[])this.deletions); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAssetPackSetup.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAssetPackSetup.java new file mode 100644 index 0000000..57ae324 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAssetPackSetup.java @@ -0,0 +1,210 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorAssetPackSetup implements Packet { + public static final int PACKET_ID = 314; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public Map packs; + + @Override + public int getId() { + return 314; + } + + public AssetEditorAssetPackSetup() { + } + + public AssetEditorAssetPackSetup(@Nullable Map packs) { + this.packs = packs; + } + + public AssetEditorAssetPackSetup(@Nonnull AssetEditorAssetPackSetup other) { + this.packs = other.packs; + } + + @Nonnull + public static AssetEditorAssetPackSetup deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorAssetPackSetup obj = new AssetEditorAssetPackSetup(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int packsCount = VarInt.peek(buf, pos); + if (packsCount < 0) { + throw ProtocolException.negativeLength("Packs", packsCount); + } + + if (packsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Packs", packsCount, 4096000); + } + + pos += VarInt.size(packsCount); + obj.packs = new HashMap<>(packsCount); + + for (int i = 0; i < packsCount; i++) { + int keyLen = VarInt.peek(buf, pos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, pos); + String key = PacketIO.readVarString(buf, pos); + pos += keyVarLen + keyLen; + AssetPackManifest val = AssetPackManifest.deserialize(buf, pos); + pos += AssetPackManifest.computeBytesConsumed(buf, pos); + if (obj.packs.put(key, val) != null) { + throw ProtocolException.duplicateKey("packs", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + pos += AssetPackManifest.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.packs != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.packs != null) { + if (this.packs.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Packs", this.packs.size(), 4096000); + } + + VarInt.write(buf, this.packs.size()); + + for (Entry e : this.packs.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.packs != null) { + int packsSize = 0; + + for (Entry kvp : this.packs.entrySet()) { + packsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.packs.size()) + packsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int packsCount = VarInt.peek(buffer, pos); + if (packsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Packs"); + } + + if (packsCount > 4096000) { + return ValidationResult.error("Packs exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < packsCount; i++) { + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += AssetPackManifest.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorAssetPackSetup clone() { + AssetEditorAssetPackSetup copy = new AssetEditorAssetPackSetup(); + if (this.packs != null) { + Map m = new HashMap<>(); + + for (Entry e : this.packs.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.packs = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorAssetPackSetup other ? Objects.equals(this.packs, other.packs) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.packs); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAssetType.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAssetType.java new file mode 100644 index 0000000..584b542 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAssetType.java @@ -0,0 +1,398 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorAssetType { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 3; + public static final int VARIABLE_FIELD_COUNT = 4; + public static final int VARIABLE_BLOCK_START = 19; + public static final int MAX_SIZE = 65536039; + @Nullable + public String id; + @Nullable + public String icon; + public boolean isColoredIcon; + @Nullable + public String path; + @Nullable + public String fileExtension; + @Nonnull + public AssetEditorEditorType editorType = AssetEditorEditorType.None; + + public AssetEditorAssetType() { + } + + public AssetEditorAssetType( + @Nullable String id, + @Nullable String icon, + boolean isColoredIcon, + @Nullable String path, + @Nullable String fileExtension, + @Nonnull AssetEditorEditorType editorType + ) { + this.id = id; + this.icon = icon; + this.isColoredIcon = isColoredIcon; + this.path = path; + this.fileExtension = fileExtension; + this.editorType = editorType; + } + + public AssetEditorAssetType(@Nonnull AssetEditorAssetType other) { + this.id = other.id; + this.icon = other.icon; + this.isColoredIcon = other.isColoredIcon; + this.path = other.path; + this.fileExtension = other.fileExtension; + this.editorType = other.editorType; + } + + @Nonnull + public static AssetEditorAssetType deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorAssetType obj = new AssetEditorAssetType(); + byte nullBits = buf.getByte(offset); + obj.isColoredIcon = buf.getByte(offset + 1) != 0; + obj.editorType = AssetEditorEditorType.fromValue(buf.getByte(offset + 2)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 19 + buf.getIntLE(offset + 3); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 19 + buf.getIntLE(offset + 7); + int iconLen = VarInt.peek(buf, varPos1); + if (iconLen < 0) { + throw ProtocolException.negativeLength("Icon", iconLen); + } + + if (iconLen > 4096000) { + throw ProtocolException.stringTooLong("Icon", iconLen, 4096000); + } + + obj.icon = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 19 + buf.getIntLE(offset + 11); + int pathLen = VarInt.peek(buf, varPos2); + if (pathLen < 0) { + throw ProtocolException.negativeLength("Path", pathLen); + } + + if (pathLen > 4096000) { + throw ProtocolException.stringTooLong("Path", pathLen, 4096000); + } + + obj.path = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 19 + buf.getIntLE(offset + 15); + int fileExtensionLen = VarInt.peek(buf, varPos3); + if (fileExtensionLen < 0) { + throw ProtocolException.negativeLength("FileExtension", fileExtensionLen); + } + + if (fileExtensionLen > 4096000) { + throw ProtocolException.stringTooLong("FileExtension", fileExtensionLen, 4096000); + } + + obj.fileExtension = PacketIO.readVarString(buf, varPos3, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 19; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 3); + int pos0 = offset + 19 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 7); + int pos1 = offset + 19 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 11); + int pos2 = offset + 19 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 15); + int pos3 = offset + 19 + fieldOffset3; + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.icon != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.path != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.fileExtension != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + buf.writeByte(this.isColoredIcon ? 1 : 0); + buf.writeByte(this.editorType.getValue()); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int iconOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int pathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int fileExtensionOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.icon != null) { + buf.setIntLE(iconOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.icon, 4096000); + } else { + buf.setIntLE(iconOffsetSlot, -1); + } + + if (this.path != null) { + buf.setIntLE(pathOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.path, 4096000); + } else { + buf.setIntLE(pathOffsetSlot, -1); + } + + if (this.fileExtension != null) { + buf.setIntLE(fileExtensionOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.fileExtension, 4096000); + } else { + buf.setIntLE(fileExtensionOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 19; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.icon != null) { + size += PacketIO.stringSize(this.icon); + } + + if (this.path != null) { + size += PacketIO.stringSize(this.path); + } + + if (this.fileExtension != null) { + size += PacketIO.stringSize(this.fileExtension); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 19) { + return ValidationResult.error("Buffer too small: expected at least 19 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 3); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 19 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int iconOffset = buffer.getIntLE(offset + 7); + if (iconOffset < 0) { + return ValidationResult.error("Invalid offset for Icon"); + } + + int posx = offset + 19 + iconOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Icon"); + } + + int iconLen = VarInt.peek(buffer, posx); + if (iconLen < 0) { + return ValidationResult.error("Invalid string length for Icon"); + } + + if (iconLen > 4096000) { + return ValidationResult.error("Icon exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += iconLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Icon"); + } + } + + if ((nullBits & 4) != 0) { + int pathOffset = buffer.getIntLE(offset + 11); + if (pathOffset < 0) { + return ValidationResult.error("Invalid offset for Path"); + } + + int posxx = offset + 19 + pathOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Path"); + } + + int pathLen = VarInt.peek(buffer, posxx); + if (pathLen < 0) { + return ValidationResult.error("Invalid string length for Path"); + } + + if (pathLen > 4096000) { + return ValidationResult.error("Path exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += pathLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Path"); + } + } + + if ((nullBits & 8) != 0) { + int fileExtensionOffset = buffer.getIntLE(offset + 15); + if (fileExtensionOffset < 0) { + return ValidationResult.error("Invalid offset for FileExtension"); + } + + int posxxx = offset + 19 + fileExtensionOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FileExtension"); + } + + int fileExtensionLen = VarInt.peek(buffer, posxxx); + if (fileExtensionLen < 0) { + return ValidationResult.error("Invalid string length for FileExtension"); + } + + if (fileExtensionLen > 4096000) { + return ValidationResult.error("FileExtension exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += fileExtensionLen; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading FileExtension"); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorAssetType clone() { + AssetEditorAssetType copy = new AssetEditorAssetType(); + copy.id = this.id; + copy.icon = this.icon; + copy.isColoredIcon = this.isColoredIcon; + copy.path = this.path; + copy.fileExtension = this.fileExtension; + copy.editorType = this.editorType; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorAssetType other) + ? false + : Objects.equals(this.id, other.id) + && Objects.equals(this.icon, other.icon) + && this.isColoredIcon == other.isColoredIcon + && Objects.equals(this.path, other.path) + && Objects.equals(this.fileExtension, other.fileExtension) + && Objects.equals(this.editorType, other.editorType); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.icon, this.isColoredIcon, this.path, this.fileExtension, this.editorType); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAssetUpdated.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAssetUpdated.java new file mode 100644 index 0000000..1198f8e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAssetUpdated.java @@ -0,0 +1,236 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorAssetUpdated implements Packet { + public static final int PACKET_ID = 326; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 36864033; + @Nullable + public AssetPath path; + @Nullable + public byte[] data; + + @Override + public int getId() { + return 326; + } + + public AssetEditorAssetUpdated() { + } + + public AssetEditorAssetUpdated(@Nullable AssetPath path, @Nullable byte[] data) { + this.path = path; + this.data = data; + } + + public AssetEditorAssetUpdated(@Nonnull AssetEditorAssetUpdated other) { + this.path = other.path; + this.data = other.data; + } + + @Nonnull + public static AssetEditorAssetUpdated deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorAssetUpdated obj = new AssetEditorAssetUpdated(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + obj.path = AssetPath.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int dataCount = VarInt.peek(buf, varPos1); + if (dataCount < 0) { + throw ProtocolException.negativeLength("Data", dataCount); + } + + if (dataCount > 4096000) { + throw ProtocolException.arrayTooLong("Data", dataCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + dataCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Data", varPos1 + varIntLen + dataCount * 1, buf.readableBytes()); + } + + obj.data = new byte[dataCount]; + + for (int i = 0; i < dataCount; i++) { + obj.data[i] = buf.getByte(varPos1 + varIntLen + i * 1); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + pos0 += AssetPath.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + arrLen * 1; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.data != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int pathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int dataOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.path != null) { + buf.setIntLE(pathOffsetSlot, buf.writerIndex() - varBlockStart); + this.path.serialize(buf); + } else { + buf.setIntLE(pathOffsetSlot, -1); + } + + if (this.data != null) { + buf.setIntLE(dataOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.data.length > 4096000) { + throw ProtocolException.arrayTooLong("Data", this.data.length, 4096000); + } + + VarInt.write(buf, this.data.length); + + for (byte item : this.data) { + buf.writeByte(item); + } + } else { + buf.setIntLE(dataOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 9; + if (this.path != null) { + size += this.path.computeSize(); + } + + if (this.data != null) { + size += VarInt.size(this.data.length) + this.data.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int pathOffset = buffer.getIntLE(offset + 1); + if (pathOffset < 0) { + return ValidationResult.error("Invalid offset for Path"); + } + + int pos = offset + 9 + pathOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Path"); + } + + ValidationResult pathResult = AssetPath.validateStructure(buffer, pos); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int dataOffset = buffer.getIntLE(offset + 5); + if (dataOffset < 0) { + return ValidationResult.error("Invalid offset for Data"); + } + + int posx = offset + 9 + dataOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Data"); + } + + int dataCount = VarInt.peek(buffer, posx); + if (dataCount < 0) { + return ValidationResult.error("Invalid array count for Data"); + } + + if (dataCount > 4096000) { + return ValidationResult.error("Data exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += dataCount * 1; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Data"); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorAssetUpdated clone() { + AssetEditorAssetUpdated copy = new AssetEditorAssetUpdated(); + copy.path = this.path != null ? this.path.clone() : null; + copy.data = this.data != null ? Arrays.copyOf(this.data, this.data.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorAssetUpdated other) ? false : Objects.equals(this.path, other.path) && Arrays.equals(this.data, other.data); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.path); + return 31 * result + Arrays.hashCode(this.data); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAuthorization.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAuthorization.java new file mode 100644 index 0000000..8ce5f44 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorAuthorization.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class AssetEditorAuthorization implements Packet { + public static final int PACKET_ID = 303; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean canUse; + + @Override + public int getId() { + return 303; + } + + public AssetEditorAuthorization() { + } + + public AssetEditorAuthorization(boolean canUse) { + this.canUse = canUse; + } + + public AssetEditorAuthorization(@Nonnull AssetEditorAuthorization other) { + this.canUse = other.canUse; + } + + @Nonnull + public static AssetEditorAuthorization deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorAuthorization obj = new AssetEditorAuthorization(); + obj.canUse = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.canUse ? 1 : 0); + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public AssetEditorAuthorization clone() { + AssetEditorAuthorization copy = new AssetEditorAuthorization(); + copy.canUse = this.canUse; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorAuthorization other ? this.canUse == other.canUse : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.canUse); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorCapabilities.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorCapabilities.java new file mode 100644 index 0000000..cea7bd5 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorCapabilities.java @@ -0,0 +1,111 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class AssetEditorCapabilities implements Packet { + public static final int PACKET_ID = 304; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 5; + public boolean canDiscardAssets; + public boolean canEditAssets; + public boolean canCreateAssetPacks; + public boolean canEditAssetPacks; + public boolean canDeleteAssetPacks; + + @Override + public int getId() { + return 304; + } + + public AssetEditorCapabilities() { + } + + public AssetEditorCapabilities( + boolean canDiscardAssets, boolean canEditAssets, boolean canCreateAssetPacks, boolean canEditAssetPacks, boolean canDeleteAssetPacks + ) { + this.canDiscardAssets = canDiscardAssets; + this.canEditAssets = canEditAssets; + this.canCreateAssetPacks = canCreateAssetPacks; + this.canEditAssetPacks = canEditAssetPacks; + this.canDeleteAssetPacks = canDeleteAssetPacks; + } + + public AssetEditorCapabilities(@Nonnull AssetEditorCapabilities other) { + this.canDiscardAssets = other.canDiscardAssets; + this.canEditAssets = other.canEditAssets; + this.canCreateAssetPacks = other.canCreateAssetPacks; + this.canEditAssetPacks = other.canEditAssetPacks; + this.canDeleteAssetPacks = other.canDeleteAssetPacks; + } + + @Nonnull + public static AssetEditorCapabilities deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorCapabilities obj = new AssetEditorCapabilities(); + obj.canDiscardAssets = buf.getByte(offset + 0) != 0; + obj.canEditAssets = buf.getByte(offset + 1) != 0; + obj.canCreateAssetPacks = buf.getByte(offset + 2) != 0; + obj.canEditAssetPacks = buf.getByte(offset + 3) != 0; + obj.canDeleteAssetPacks = buf.getByte(offset + 4) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 5; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.canDiscardAssets ? 1 : 0); + buf.writeByte(this.canEditAssets ? 1 : 0); + buf.writeByte(this.canCreateAssetPacks ? 1 : 0); + buf.writeByte(this.canEditAssetPacks ? 1 : 0); + buf.writeByte(this.canDeleteAssetPacks ? 1 : 0); + } + + @Override + public int computeSize() { + return 5; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 5 ? ValidationResult.error("Buffer too small: expected at least 5 bytes") : ValidationResult.OK; + } + + public AssetEditorCapabilities clone() { + AssetEditorCapabilities copy = new AssetEditorCapabilities(); + copy.canDiscardAssets = this.canDiscardAssets; + copy.canEditAssets = this.canEditAssets; + copy.canCreateAssetPacks = this.canCreateAssetPacks; + copy.canEditAssetPacks = this.canEditAssetPacks; + copy.canDeleteAssetPacks = this.canDeleteAssetPacks; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorCapabilities other) + ? false + : this.canDiscardAssets == other.canDiscardAssets + && this.canEditAssets == other.canEditAssets + && this.canCreateAssetPacks == other.canCreateAssetPacks + && this.canEditAssetPacks == other.canEditAssetPacks + && this.canDeleteAssetPacks == other.canDeleteAssetPacks; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.canDiscardAssets, this.canEditAssets, this.canCreateAssetPacks, this.canEditAssetPacks, this.canDeleteAssetPacks); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorCreateAsset.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorCreateAsset.java new file mode 100644 index 0000000..5f313f1 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorCreateAsset.java @@ -0,0 +1,346 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorCreateAsset implements Packet { + public static final int PACKET_ID = 327; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 10; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 22; + public static final int MAX_SIZE = 53248051; + public int token; + @Nullable + public AssetPath path; + @Nullable + public byte[] data; + @Nullable + public AssetEditorRebuildCaches rebuildCaches; + @Nullable + public String buttonId; + + @Override + public int getId() { + return 327; + } + + public AssetEditorCreateAsset() { + } + + public AssetEditorCreateAsset( + int token, @Nullable AssetPath path, @Nullable byte[] data, @Nullable AssetEditorRebuildCaches rebuildCaches, @Nullable String buttonId + ) { + this.token = token; + this.path = path; + this.data = data; + this.rebuildCaches = rebuildCaches; + this.buttonId = buttonId; + } + + public AssetEditorCreateAsset(@Nonnull AssetEditorCreateAsset other) { + this.token = other.token; + this.path = other.path; + this.data = other.data; + this.rebuildCaches = other.rebuildCaches; + this.buttonId = other.buttonId; + } + + @Nonnull + public static AssetEditorCreateAsset deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorCreateAsset obj = new AssetEditorCreateAsset(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + if ((nullBits & 4) != 0) { + obj.rebuildCaches = AssetEditorRebuildCaches.deserialize(buf, offset + 5); + } + + if ((nullBits & 1) != 0) { + int varPos0 = offset + 22 + buf.getIntLE(offset + 10); + obj.path = AssetPath.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 22 + buf.getIntLE(offset + 14); + int dataCount = VarInt.peek(buf, varPos1); + if (dataCount < 0) { + throw ProtocolException.negativeLength("Data", dataCount); + } + + if (dataCount > 4096000) { + throw ProtocolException.arrayTooLong("Data", dataCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + dataCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Data", varPos1 + varIntLen + dataCount * 1, buf.readableBytes()); + } + + obj.data = new byte[dataCount]; + + for (int i = 0; i < dataCount; i++) { + obj.data[i] = buf.getByte(varPos1 + varIntLen + i * 1); + } + } + + if ((nullBits & 8) != 0) { + int varPos2 = offset + 22 + buf.getIntLE(offset + 18); + int buttonIdLen = VarInt.peek(buf, varPos2); + if (buttonIdLen < 0) { + throw ProtocolException.negativeLength("ButtonId", buttonIdLen); + } + + if (buttonIdLen > 4096000) { + throw ProtocolException.stringTooLong("ButtonId", buttonIdLen, 4096000); + } + + obj.buttonId = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 22; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 10); + int pos0 = offset + 22 + fieldOffset0; + pos0 += AssetPath.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 14); + int pos1 = offset + 22 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + arrLen * 1; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 18); + int pos2 = offset + 22 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.data != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rebuildCaches != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.buttonId != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + if (this.rebuildCaches != null) { + this.rebuildCaches.serialize(buf); + } else { + buf.writeZero(5); + } + + int pathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int dataOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int buttonIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.path != null) { + buf.setIntLE(pathOffsetSlot, buf.writerIndex() - varBlockStart); + this.path.serialize(buf); + } else { + buf.setIntLE(pathOffsetSlot, -1); + } + + if (this.data != null) { + buf.setIntLE(dataOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.data.length > 4096000) { + throw ProtocolException.arrayTooLong("Data", this.data.length, 4096000); + } + + VarInt.write(buf, this.data.length); + + for (byte item : this.data) { + buf.writeByte(item); + } + } else { + buf.setIntLE(dataOffsetSlot, -1); + } + + if (this.buttonId != null) { + buf.setIntLE(buttonIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.buttonId, 4096000); + } else { + buf.setIntLE(buttonIdOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 22; + if (this.path != null) { + size += this.path.computeSize(); + } + + if (this.data != null) { + size += VarInt.size(this.data.length) + this.data.length * 1; + } + + if (this.buttonId != null) { + size += PacketIO.stringSize(this.buttonId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 22) { + return ValidationResult.error("Buffer too small: expected at least 22 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int pathOffset = buffer.getIntLE(offset + 10); + if (pathOffset < 0) { + return ValidationResult.error("Invalid offset for Path"); + } + + int pos = offset + 22 + pathOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Path"); + } + + ValidationResult pathResult = AssetPath.validateStructure(buffer, pos); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int dataOffset = buffer.getIntLE(offset + 14); + if (dataOffset < 0) { + return ValidationResult.error("Invalid offset for Data"); + } + + int posx = offset + 22 + dataOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Data"); + } + + int dataCount = VarInt.peek(buffer, posx); + if (dataCount < 0) { + return ValidationResult.error("Invalid array count for Data"); + } + + if (dataCount > 4096000) { + return ValidationResult.error("Data exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += dataCount * 1; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Data"); + } + } + + if ((nullBits & 8) != 0) { + int buttonIdOffset = buffer.getIntLE(offset + 18); + if (buttonIdOffset < 0) { + return ValidationResult.error("Invalid offset for ButtonId"); + } + + int posxx = offset + 22 + buttonIdOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ButtonId"); + } + + int buttonIdLen = VarInt.peek(buffer, posxx); + if (buttonIdLen < 0) { + return ValidationResult.error("Invalid string length for ButtonId"); + } + + if (buttonIdLen > 4096000) { + return ValidationResult.error("ButtonId exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += buttonIdLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ButtonId"); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorCreateAsset clone() { + AssetEditorCreateAsset copy = new AssetEditorCreateAsset(); + copy.token = this.token; + copy.path = this.path != null ? this.path.clone() : null; + copy.data = this.data != null ? Arrays.copyOf(this.data, this.data.length) : null; + copy.rebuildCaches = this.rebuildCaches != null ? this.rebuildCaches.clone() : null; + copy.buttonId = this.buttonId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorCreateAsset other) + ? false + : this.token == other.token + && Objects.equals(this.path, other.path) + && Arrays.equals(this.data, other.data) + && Objects.equals(this.rebuildCaches, other.rebuildCaches) + && Objects.equals(this.buttonId, other.buttonId); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.token); + result = 31 * result + Objects.hashCode(this.path); + result = 31 * result + Arrays.hashCode(this.data); + result = 31 * result + Objects.hashCode(this.rebuildCaches); + return 31 * result + Objects.hashCode(this.buttonId); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorCreateAssetPack.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorCreateAssetPack.java new file mode 100644 index 0000000..065c6fa --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorCreateAssetPack.java @@ -0,0 +1,127 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorCreateAssetPack implements Packet { + public static final int PACKET_ID = 316; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 1677721600; + public int token; + @Nullable + public AssetPackManifest manifest; + + @Override + public int getId() { + return 316; + } + + public AssetEditorCreateAssetPack() { + } + + public AssetEditorCreateAssetPack(int token, @Nullable AssetPackManifest manifest) { + this.token = token; + this.manifest = manifest; + } + + public AssetEditorCreateAssetPack(@Nonnull AssetEditorCreateAssetPack other) { + this.token = other.token; + this.manifest = other.manifest; + } + + @Nonnull + public static AssetEditorCreateAssetPack deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorCreateAssetPack obj = new AssetEditorCreateAssetPack(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + obj.manifest = AssetPackManifest.deserialize(buf, pos); + pos += AssetPackManifest.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + pos += AssetPackManifest.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.manifest != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + if (this.manifest != null) { + this.manifest.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 5; + if (this.manifest != null) { + size += this.manifest.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + ValidationResult manifestResult = AssetPackManifest.validateStructure(buffer, pos); + if (!manifestResult.isValid()) { + return ValidationResult.error("Invalid Manifest: " + manifestResult.error()); + } + + pos += AssetPackManifest.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public AssetEditorCreateAssetPack clone() { + AssetEditorCreateAssetPack copy = new AssetEditorCreateAssetPack(); + copy.token = this.token; + copy.manifest = this.manifest != null ? this.manifest.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorCreateAssetPack other) ? false : this.token == other.token && Objects.equals(this.manifest, other.manifest); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.token, this.manifest); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorCreateDirectory.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorCreateDirectory.java new file mode 100644 index 0000000..81d8810 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorCreateDirectory.java @@ -0,0 +1,127 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorCreateDirectory implements Packet { + public static final int PACKET_ID = 307; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 32768024; + public int token; + @Nullable + public AssetPath path; + + @Override + public int getId() { + return 307; + } + + public AssetEditorCreateDirectory() { + } + + public AssetEditorCreateDirectory(int token, @Nullable AssetPath path) { + this.token = token; + this.path = path; + } + + public AssetEditorCreateDirectory(@Nonnull AssetEditorCreateDirectory other) { + this.token = other.token; + this.path = other.path; + } + + @Nonnull + public static AssetEditorCreateDirectory deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorCreateDirectory obj = new AssetEditorCreateDirectory(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + obj.path = AssetPath.deserialize(buf, pos); + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + if (this.path != null) { + this.path.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 5; + if (this.path != null) { + size += this.path.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + ValidationResult pathResult = AssetPath.validateStructure(buffer, pos); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public AssetEditorCreateDirectory clone() { + AssetEditorCreateDirectory copy = new AssetEditorCreateDirectory(); + copy.token = this.token; + copy.path = this.path != null ? this.path.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorCreateDirectory other) ? false : this.token == other.token && Objects.equals(this.path, other.path); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.token, this.path); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorDeleteAsset.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorDeleteAsset.java new file mode 100644 index 0000000..12e6cf9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorDeleteAsset.java @@ -0,0 +1,127 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorDeleteAsset implements Packet { + public static final int PACKET_ID = 329; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 32768024; + public int token; + @Nullable + public AssetPath path; + + @Override + public int getId() { + return 329; + } + + public AssetEditorDeleteAsset() { + } + + public AssetEditorDeleteAsset(int token, @Nullable AssetPath path) { + this.token = token; + this.path = path; + } + + public AssetEditorDeleteAsset(@Nonnull AssetEditorDeleteAsset other) { + this.token = other.token; + this.path = other.path; + } + + @Nonnull + public static AssetEditorDeleteAsset deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorDeleteAsset obj = new AssetEditorDeleteAsset(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + obj.path = AssetPath.deserialize(buf, pos); + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + if (this.path != null) { + this.path.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 5; + if (this.path != null) { + size += this.path.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + ValidationResult pathResult = AssetPath.validateStructure(buffer, pos); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public AssetEditorDeleteAsset clone() { + AssetEditorDeleteAsset copy = new AssetEditorDeleteAsset(); + copy.token = this.token; + copy.path = this.path != null ? this.path.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorDeleteAsset other) ? false : this.token == other.token && Objects.equals(this.path, other.path); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.token, this.path); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorDeleteAssetPack.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorDeleteAssetPack.java new file mode 100644 index 0000000..cf3c706 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorDeleteAssetPack.java @@ -0,0 +1,143 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorDeleteAssetPack implements Packet { + public static final int PACKET_ID = 317; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 16384006; + @Nullable + public String id; + + @Override + public int getId() { + return 317; + } + + public AssetEditorDeleteAssetPack() { + } + + public AssetEditorDeleteAssetPack(@Nullable String id) { + this.id = id; + } + + public AssetEditorDeleteAssetPack(@Nonnull AssetEditorDeleteAssetPack other) { + this.id = other.id; + } + + @Nonnull + public static AssetEditorDeleteAssetPack deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorDeleteAssetPack obj = new AssetEditorDeleteAssetPack(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int idLen = VarInt.peek(buf, pos); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + int idVarLen = VarInt.length(buf, pos); + obj.id = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += idVarLen + idLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.id != null) { + PacketIO.writeVarString(buf, this.id, 4096000); + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorDeleteAssetPack clone() { + AssetEditorDeleteAssetPack copy = new AssetEditorDeleteAssetPack(); + copy.id = this.id; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorDeleteAssetPack other ? Objects.equals(this.id, other.id) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorDeleteDirectory.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorDeleteDirectory.java new file mode 100644 index 0000000..4215467 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorDeleteDirectory.java @@ -0,0 +1,127 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorDeleteDirectory implements Packet { + public static final int PACKET_ID = 308; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 32768024; + public int token; + @Nullable + public AssetPath path; + + @Override + public int getId() { + return 308; + } + + public AssetEditorDeleteDirectory() { + } + + public AssetEditorDeleteDirectory(int token, @Nullable AssetPath path) { + this.token = token; + this.path = path; + } + + public AssetEditorDeleteDirectory(@Nonnull AssetEditorDeleteDirectory other) { + this.token = other.token; + this.path = other.path; + } + + @Nonnull + public static AssetEditorDeleteDirectory deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorDeleteDirectory obj = new AssetEditorDeleteDirectory(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + obj.path = AssetPath.deserialize(buf, pos); + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + if (this.path != null) { + this.path.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 5; + if (this.path != null) { + size += this.path.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + ValidationResult pathResult = AssetPath.validateStructure(buffer, pos); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public AssetEditorDeleteDirectory clone() { + AssetEditorDeleteDirectory copy = new AssetEditorDeleteDirectory(); + copy.token = this.token; + copy.path = this.path != null ? this.path.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorDeleteDirectory other) ? false : this.token == other.token && Objects.equals(this.path, other.path); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.token, this.path); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorDiscardChanges.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorDiscardChanges.java new file mode 100644 index 0000000..94e49ac --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorDiscardChanges.java @@ -0,0 +1,175 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorDiscardChanges implements Packet { + public static final int PACKET_ID = 330; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public TimestampedAssetReference[] assets; + + @Override + public int getId() { + return 330; + } + + public AssetEditorDiscardChanges() { + } + + public AssetEditorDiscardChanges(@Nullable TimestampedAssetReference[] assets) { + this.assets = assets; + } + + public AssetEditorDiscardChanges(@Nonnull AssetEditorDiscardChanges other) { + this.assets = other.assets; + } + + @Nonnull + public static AssetEditorDiscardChanges deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorDiscardChanges obj = new AssetEditorDiscardChanges(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int assetsCount = VarInt.peek(buf, pos); + if (assetsCount < 0) { + throw ProtocolException.negativeLength("Assets", assetsCount); + } + + if (assetsCount > 4096000) { + throw ProtocolException.arrayTooLong("Assets", assetsCount, 4096000); + } + + int assetsVarLen = VarInt.size(assetsCount); + if (pos + assetsVarLen + assetsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Assets", pos + assetsVarLen + assetsCount * 1, buf.readableBytes()); + } + + pos += assetsVarLen; + obj.assets = new TimestampedAssetReference[assetsCount]; + + for (int i = 0; i < assetsCount; i++) { + obj.assets[i] = TimestampedAssetReference.deserialize(buf, pos); + pos += TimestampedAssetReference.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += TimestampedAssetReference.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.assets != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.assets != null) { + if (this.assets.length > 4096000) { + throw ProtocolException.arrayTooLong("Assets", this.assets.length, 4096000); + } + + VarInt.write(buf, this.assets.length); + + for (TimestampedAssetReference item : this.assets) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.assets != null) { + int assetsSize = 0; + + for (TimestampedAssetReference elem : this.assets) { + assetsSize += elem.computeSize(); + } + + size += VarInt.size(this.assets.length) + assetsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int assetsCount = VarInt.peek(buffer, pos); + if (assetsCount < 0) { + return ValidationResult.error("Invalid array count for Assets"); + } + + if (assetsCount > 4096000) { + return ValidationResult.error("Assets exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < assetsCount; i++) { + ValidationResult structResult = TimestampedAssetReference.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid TimestampedAssetReference in Assets[" + i + "]: " + structResult.error()); + } + + pos += TimestampedAssetReference.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorDiscardChanges clone() { + AssetEditorDiscardChanges copy = new AssetEditorDiscardChanges(); + copy.assets = this.assets != null ? Arrays.stream(this.assets).map(e -> e.clone()).toArray(TimestampedAssetReference[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorDiscardChanges other ? Arrays.equals((Object[])this.assets, (Object[])other.assets) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.assets); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorEditorType.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorEditorType.java new file mode 100644 index 0000000..2afbcd6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorEditorType.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum AssetEditorEditorType { + None(0), + Text(1), + JsonSource(2), + JsonConfig(3), + Model(4), + Texture(5), + Animation(6); + + public static final AssetEditorEditorType[] VALUES = values(); + private final int value; + + private AssetEditorEditorType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static AssetEditorEditorType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("AssetEditorEditorType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorEnableAssetPack.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorEnableAssetPack.java new file mode 100644 index 0000000..d1e6585 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorEnableAssetPack.java @@ -0,0 +1,149 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorEnableAssetPack implements Packet { + public static final int PACKET_ID = 318; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 16384007; + @Nullable + public String id; + public boolean enabled; + + @Override + public int getId() { + return 318; + } + + public AssetEditorEnableAssetPack() { + } + + public AssetEditorEnableAssetPack(@Nullable String id, boolean enabled) { + this.id = id; + this.enabled = enabled; + } + + public AssetEditorEnableAssetPack(@Nonnull AssetEditorEnableAssetPack other) { + this.id = other.id; + this.enabled = other.enabled; + } + + @Nonnull + public static AssetEditorEnableAssetPack deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorEnableAssetPack obj = new AssetEditorEnableAssetPack(); + byte nullBits = buf.getByte(offset); + obj.enabled = buf.getByte(offset + 1) != 0; + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int idLen = VarInt.peek(buf, pos); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + int idVarLen = VarInt.length(buf, pos); + obj.id = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += idVarLen + idLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.enabled ? 1 : 0); + if (this.id != null) { + PacketIO.writeVarString(buf, this.id, 4096000); + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorEnableAssetPack clone() { + AssetEditorEnableAssetPack copy = new AssetEditorEnableAssetPack(); + copy.id = this.id; + copy.enabled = this.enabled; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorEnableAssetPack other) ? false : Objects.equals(this.id, other.id) && this.enabled == other.enabled; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.enabled); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportAssetFinalize.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportAssetFinalize.java new file mode 100644 index 0000000..2ee553a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportAssetFinalize.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class AssetEditorExportAssetFinalize implements Packet { + public static final int PACKET_ID = 345; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public AssetEditorExportAssetFinalize() { + } + + @Override + public int getId() { + return 345; + } + + @Nonnull + public static AssetEditorExportAssetFinalize deserialize(@Nonnull ByteBuf buf, int offset) { + return new AssetEditorExportAssetFinalize(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public AssetEditorExportAssetFinalize clone() { + return new AssetEditorExportAssetFinalize(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof AssetEditorExportAssetFinalize other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportAssetInitialize.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportAssetInitialize.java new file mode 100644 index 0000000..81394c5 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportAssetInitialize.java @@ -0,0 +1,210 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorExportAssetInitialize implements Packet { + public static final int PACKET_ID = 343; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 14; + public static final int MAX_SIZE = 81920066; + @Nullable + public AssetEditorAsset asset; + @Nullable + public AssetPath oldPath; + public int size; + public boolean failed; + + @Override + public int getId() { + return 343; + } + + public AssetEditorExportAssetInitialize() { + } + + public AssetEditorExportAssetInitialize(@Nullable AssetEditorAsset asset, @Nullable AssetPath oldPath, int size, boolean failed) { + this.asset = asset; + this.oldPath = oldPath; + this.size = size; + this.failed = failed; + } + + public AssetEditorExportAssetInitialize(@Nonnull AssetEditorExportAssetInitialize other) { + this.asset = other.asset; + this.oldPath = other.oldPath; + this.size = other.size; + this.failed = other.failed; + } + + @Nonnull + public static AssetEditorExportAssetInitialize deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorExportAssetInitialize obj = new AssetEditorExportAssetInitialize(); + byte nullBits = buf.getByte(offset); + obj.size = buf.getIntLE(offset + 1); + obj.failed = buf.getByte(offset + 5) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 14 + buf.getIntLE(offset + 6); + obj.asset = AssetEditorAsset.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 14 + buf.getIntLE(offset + 10); + obj.oldPath = AssetPath.deserialize(buf, varPos1); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 14; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 6); + int pos0 = offset + 14 + fieldOffset0; + pos0 += AssetEditorAsset.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 10); + int pos1 = offset + 14 + fieldOffset1; + pos1 += AssetPath.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.asset != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.oldPath != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.size); + buf.writeByte(this.failed ? 1 : 0); + int assetOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int oldPathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.asset != null) { + buf.setIntLE(assetOffsetSlot, buf.writerIndex() - varBlockStart); + this.asset.serialize(buf); + } else { + buf.setIntLE(assetOffsetSlot, -1); + } + + if (this.oldPath != null) { + buf.setIntLE(oldPathOffsetSlot, buf.writerIndex() - varBlockStart); + this.oldPath.serialize(buf); + } else { + buf.setIntLE(oldPathOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 14; + if (this.asset != null) { + size += this.asset.computeSize(); + } + + if (this.oldPath != null) { + size += this.oldPath.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 14) { + return ValidationResult.error("Buffer too small: expected at least 14 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int assetOffset = buffer.getIntLE(offset + 6); + if (assetOffset < 0) { + return ValidationResult.error("Invalid offset for Asset"); + } + + int pos = offset + 14 + assetOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Asset"); + } + + ValidationResult assetResult = AssetEditorAsset.validateStructure(buffer, pos); + if (!assetResult.isValid()) { + return ValidationResult.error("Invalid Asset: " + assetResult.error()); + } + + pos += AssetEditorAsset.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int oldPathOffset = buffer.getIntLE(offset + 10); + if (oldPathOffset < 0) { + return ValidationResult.error("Invalid offset for OldPath"); + } + + int posx = offset + 14 + oldPathOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for OldPath"); + } + + ValidationResult oldPathResult = AssetPath.validateStructure(buffer, posx); + if (!oldPathResult.isValid()) { + return ValidationResult.error("Invalid OldPath: " + oldPathResult.error()); + } + + posx += AssetPath.computeBytesConsumed(buffer, posx); + } + + return ValidationResult.OK; + } + } + + public AssetEditorExportAssetInitialize clone() { + AssetEditorExportAssetInitialize copy = new AssetEditorExportAssetInitialize(); + copy.asset = this.asset != null ? this.asset.clone() : null; + copy.oldPath = this.oldPath != null ? this.oldPath.clone() : null; + copy.size = this.size; + copy.failed = this.failed; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorExportAssetInitialize other) + ? false + : Objects.equals(this.asset, other.asset) && Objects.equals(this.oldPath, other.oldPath) && this.size == other.size && this.failed == other.failed; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.asset, this.oldPath, this.size, this.failed); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportAssetPart.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportAssetPart.java new file mode 100644 index 0000000..9cf34b0 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportAssetPart.java @@ -0,0 +1,161 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorExportAssetPart implements Packet { + public static final int PACKET_ID = 344; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 4096006; + @Nullable + public byte[] part; + + @Override + public int getId() { + return 344; + } + + public AssetEditorExportAssetPart() { + } + + public AssetEditorExportAssetPart(@Nullable byte[] part) { + this.part = part; + } + + public AssetEditorExportAssetPart(@Nonnull AssetEditorExportAssetPart other) { + this.part = other.part; + } + + @Nonnull + public static AssetEditorExportAssetPart deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorExportAssetPart obj = new AssetEditorExportAssetPart(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int partCount = VarInt.peek(buf, pos); + if (partCount < 0) { + throw ProtocolException.negativeLength("Part", partCount); + } + + if (partCount > 4096000) { + throw ProtocolException.arrayTooLong("Part", partCount, 4096000); + } + + int partVarLen = VarInt.size(partCount); + if (pos + partVarLen + partCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Part", pos + partVarLen + partCount * 1, buf.readableBytes()); + } + + pos += partVarLen; + obj.part = new byte[partCount]; + + for (int i = 0; i < partCount; i++) { + obj.part[i] = buf.getByte(pos + i * 1); + } + + pos += partCount * 1; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 1; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.part != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.part != null) { + if (this.part.length > 4096000) { + throw ProtocolException.arrayTooLong("Part", this.part.length, 4096000); + } + + VarInt.write(buf, this.part.length); + + for (byte item : this.part) { + buf.writeByte(item); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.part != null) { + size += VarInt.size(this.part.length) + this.part.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int partCount = VarInt.peek(buffer, pos); + if (partCount < 0) { + return ValidationResult.error("Invalid array count for Part"); + } + + if (partCount > 4096000) { + return ValidationResult.error("Part exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += partCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Part"); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorExportAssetPart clone() { + AssetEditorExportAssetPart copy = new AssetEditorExportAssetPart(); + copy.part = this.part != null ? Arrays.copyOf(this.part, this.part.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorExportAssetPart other ? Arrays.equals(this.part, other.part) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode(this.part); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportAssets.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportAssets.java new file mode 100644 index 0000000..5cc961a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportAssets.java @@ -0,0 +1,175 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorExportAssets implements Packet { + public static final int PACKET_ID = 342; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public AssetPath[] paths; + + @Override + public int getId() { + return 342; + } + + public AssetEditorExportAssets() { + } + + public AssetEditorExportAssets(@Nullable AssetPath[] paths) { + this.paths = paths; + } + + public AssetEditorExportAssets(@Nonnull AssetEditorExportAssets other) { + this.paths = other.paths; + } + + @Nonnull + public static AssetEditorExportAssets deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorExportAssets obj = new AssetEditorExportAssets(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int pathsCount = VarInt.peek(buf, pos); + if (pathsCount < 0) { + throw ProtocolException.negativeLength("Paths", pathsCount); + } + + if (pathsCount > 4096000) { + throw ProtocolException.arrayTooLong("Paths", pathsCount, 4096000); + } + + int pathsVarLen = VarInt.size(pathsCount); + if (pos + pathsVarLen + pathsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Paths", pos + pathsVarLen + pathsCount * 1, buf.readableBytes()); + } + + pos += pathsVarLen; + obj.paths = new AssetPath[pathsCount]; + + for (int i = 0; i < pathsCount; i++) { + obj.paths[i] = AssetPath.deserialize(buf, pos); + pos += AssetPath.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += AssetPath.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.paths != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.paths != null) { + if (this.paths.length > 4096000) { + throw ProtocolException.arrayTooLong("Paths", this.paths.length, 4096000); + } + + VarInt.write(buf, this.paths.length); + + for (AssetPath item : this.paths) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.paths != null) { + int pathsSize = 0; + + for (AssetPath elem : this.paths) { + pathsSize += elem.computeSize(); + } + + size += VarInt.size(this.paths.length) + pathsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int pathsCount = VarInt.peek(buffer, pos); + if (pathsCount < 0) { + return ValidationResult.error("Invalid array count for Paths"); + } + + if (pathsCount > 4096000) { + return ValidationResult.error("Paths exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < pathsCount; i++) { + ValidationResult structResult = AssetPath.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid AssetPath in Paths[" + i + "]: " + structResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorExportAssets clone() { + AssetEditorExportAssets copy = new AssetEditorExportAssets(); + copy.paths = this.paths != null ? Arrays.stream(this.paths).map(e -> e.clone()).toArray(AssetPath[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorExportAssets other ? Arrays.equals((Object[])this.paths, (Object[])other.paths) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.paths); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportComplete.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportComplete.java new file mode 100644 index 0000000..514e49e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportComplete.java @@ -0,0 +1,175 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorExportComplete implements Packet { + public static final int PACKET_ID = 347; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public TimestampedAssetReference[] assets; + + @Override + public int getId() { + return 347; + } + + public AssetEditorExportComplete() { + } + + public AssetEditorExportComplete(@Nullable TimestampedAssetReference[] assets) { + this.assets = assets; + } + + public AssetEditorExportComplete(@Nonnull AssetEditorExportComplete other) { + this.assets = other.assets; + } + + @Nonnull + public static AssetEditorExportComplete deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorExportComplete obj = new AssetEditorExportComplete(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int assetsCount = VarInt.peek(buf, pos); + if (assetsCount < 0) { + throw ProtocolException.negativeLength("Assets", assetsCount); + } + + if (assetsCount > 4096000) { + throw ProtocolException.arrayTooLong("Assets", assetsCount, 4096000); + } + + int assetsVarLen = VarInt.size(assetsCount); + if (pos + assetsVarLen + assetsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Assets", pos + assetsVarLen + assetsCount * 1, buf.readableBytes()); + } + + pos += assetsVarLen; + obj.assets = new TimestampedAssetReference[assetsCount]; + + for (int i = 0; i < assetsCount; i++) { + obj.assets[i] = TimestampedAssetReference.deserialize(buf, pos); + pos += TimestampedAssetReference.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += TimestampedAssetReference.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.assets != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.assets != null) { + if (this.assets.length > 4096000) { + throw ProtocolException.arrayTooLong("Assets", this.assets.length, 4096000); + } + + VarInt.write(buf, this.assets.length); + + for (TimestampedAssetReference item : this.assets) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.assets != null) { + int assetsSize = 0; + + for (TimestampedAssetReference elem : this.assets) { + assetsSize += elem.computeSize(); + } + + size += VarInt.size(this.assets.length) + assetsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int assetsCount = VarInt.peek(buffer, pos); + if (assetsCount < 0) { + return ValidationResult.error("Invalid array count for Assets"); + } + + if (assetsCount > 4096000) { + return ValidationResult.error("Assets exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < assetsCount; i++) { + ValidationResult structResult = TimestampedAssetReference.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid TimestampedAssetReference in Assets[" + i + "]: " + structResult.error()); + } + + pos += TimestampedAssetReference.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorExportComplete clone() { + AssetEditorExportComplete copy = new AssetEditorExportComplete(); + copy.assets = this.assets != null ? Arrays.stream(this.assets).map(e -> e.clone()).toArray(TimestampedAssetReference[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorExportComplete other ? Arrays.equals((Object[])this.assets, (Object[])other.assets) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.assets); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportDeleteAssets.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportDeleteAssets.java new file mode 100644 index 0000000..8bac5c2 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorExportDeleteAssets.java @@ -0,0 +1,175 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorExportDeleteAssets implements Packet { + public static final int PACKET_ID = 346; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public AssetEditorAsset[] asset; + + @Override + public int getId() { + return 346; + } + + public AssetEditorExportDeleteAssets() { + } + + public AssetEditorExportDeleteAssets(@Nullable AssetEditorAsset[] asset) { + this.asset = asset; + } + + public AssetEditorExportDeleteAssets(@Nonnull AssetEditorExportDeleteAssets other) { + this.asset = other.asset; + } + + @Nonnull + public static AssetEditorExportDeleteAssets deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorExportDeleteAssets obj = new AssetEditorExportDeleteAssets(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int assetCount = VarInt.peek(buf, pos); + if (assetCount < 0) { + throw ProtocolException.negativeLength("Asset", assetCount); + } + + if (assetCount > 4096000) { + throw ProtocolException.arrayTooLong("Asset", assetCount, 4096000); + } + + int assetVarLen = VarInt.size(assetCount); + if (pos + assetVarLen + assetCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Asset", pos + assetVarLen + assetCount * 1, buf.readableBytes()); + } + + pos += assetVarLen; + obj.asset = new AssetEditorAsset[assetCount]; + + for (int i = 0; i < assetCount; i++) { + obj.asset[i] = AssetEditorAsset.deserialize(buf, pos); + pos += AssetEditorAsset.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += AssetEditorAsset.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.asset != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.asset != null) { + if (this.asset.length > 4096000) { + throw ProtocolException.arrayTooLong("Asset", this.asset.length, 4096000); + } + + VarInt.write(buf, this.asset.length); + + for (AssetEditorAsset item : this.asset) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.asset != null) { + int assetSize = 0; + + for (AssetEditorAsset elem : this.asset) { + assetSize += elem.computeSize(); + } + + size += VarInt.size(this.asset.length) + assetSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int assetCount = VarInt.peek(buffer, pos); + if (assetCount < 0) { + return ValidationResult.error("Invalid array count for Asset"); + } + + if (assetCount > 4096000) { + return ValidationResult.error("Asset exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < assetCount; i++) { + ValidationResult structResult = AssetEditorAsset.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid AssetEditorAsset in Asset[" + i + "]: " + structResult.error()); + } + + pos += AssetEditorAsset.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorExportDeleteAssets clone() { + AssetEditorExportDeleteAssets copy = new AssetEditorExportDeleteAssets(); + copy.asset = this.asset != null ? Arrays.stream(this.asset).map(e -> e.clone()).toArray(AssetEditorAsset[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorExportDeleteAssets other ? Arrays.equals((Object[])this.asset, (Object[])other.asset) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.asset); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchAsset.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchAsset.java new file mode 100644 index 0000000..faf9a38 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchAsset.java @@ -0,0 +1,135 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorFetchAsset implements Packet { + public static final int PACKET_ID = 310; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 32768025; + public int token; + @Nullable + public AssetPath path; + public boolean isFromOpenedTab; + + @Override + public int getId() { + return 310; + } + + public AssetEditorFetchAsset() { + } + + public AssetEditorFetchAsset(int token, @Nullable AssetPath path, boolean isFromOpenedTab) { + this.token = token; + this.path = path; + this.isFromOpenedTab = isFromOpenedTab; + } + + public AssetEditorFetchAsset(@Nonnull AssetEditorFetchAsset other) { + this.token = other.token; + this.path = other.path; + this.isFromOpenedTab = other.isFromOpenedTab; + } + + @Nonnull + public static AssetEditorFetchAsset deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorFetchAsset obj = new AssetEditorFetchAsset(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + obj.isFromOpenedTab = buf.getByte(offset + 5) != 0; + int pos = offset + 6; + if ((nullBits & 1) != 0) { + obj.path = AssetPath.deserialize(buf, pos); + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + buf.writeByte(this.isFromOpenedTab ? 1 : 0); + if (this.path != null) { + this.path.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.path != null) { + size += this.path.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + ValidationResult pathResult = AssetPath.validateStructure(buffer, pos); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public AssetEditorFetchAsset clone() { + AssetEditorFetchAsset copy = new AssetEditorFetchAsset(); + copy.token = this.token; + copy.path = this.path != null ? this.path.clone() : null; + copy.isFromOpenedTab = this.isFromOpenedTab; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorFetchAsset other) + ? false + : this.token == other.token && Objects.equals(this.path, other.path) && this.isFromOpenedTab == other.isFromOpenedTab; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.token, this.path, this.isFromOpenedTab); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchAssetReply.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchAssetReply.java new file mode 100644 index 0000000..d80d944 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchAssetReply.java @@ -0,0 +1,168 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorFetchAssetReply implements Packet { + public static final int PACKET_ID = 312; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 4096010; + public int token; + @Nullable + public byte[] contents; + + @Override + public int getId() { + return 312; + } + + public AssetEditorFetchAssetReply() { + } + + public AssetEditorFetchAssetReply(int token, @Nullable byte[] contents) { + this.token = token; + this.contents = contents; + } + + public AssetEditorFetchAssetReply(@Nonnull AssetEditorFetchAssetReply other) { + this.token = other.token; + this.contents = other.contents; + } + + @Nonnull + public static AssetEditorFetchAssetReply deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorFetchAssetReply obj = new AssetEditorFetchAssetReply(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int contentsCount = VarInt.peek(buf, pos); + if (contentsCount < 0) { + throw ProtocolException.negativeLength("Contents", contentsCount); + } + + if (contentsCount > 4096000) { + throw ProtocolException.arrayTooLong("Contents", contentsCount, 4096000); + } + + int contentsVarLen = VarInt.size(contentsCount); + if (pos + contentsVarLen + contentsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Contents", pos + contentsVarLen + contentsCount * 1, buf.readableBytes()); + } + + pos += contentsVarLen; + obj.contents = new byte[contentsCount]; + + for (int i = 0; i < contentsCount; i++) { + obj.contents[i] = buf.getByte(pos + i * 1); + } + + pos += contentsCount * 1; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 1; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.contents != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + if (this.contents != null) { + if (this.contents.length > 4096000) { + throw ProtocolException.arrayTooLong("Contents", this.contents.length, 4096000); + } + + VarInt.write(buf, this.contents.length); + + for (byte item : this.contents) { + buf.writeByte(item); + } + } + } + + @Override + public int computeSize() { + int size = 5; + if (this.contents != null) { + size += VarInt.size(this.contents.length) + this.contents.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int contentsCount = VarInt.peek(buffer, pos); + if (contentsCount < 0) { + return ValidationResult.error("Invalid array count for Contents"); + } + + if (contentsCount > 4096000) { + return ValidationResult.error("Contents exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += contentsCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Contents"); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorFetchAssetReply clone() { + AssetEditorFetchAssetReply copy = new AssetEditorFetchAssetReply(); + copy.token = this.token; + copy.contents = this.contents != null ? Arrays.copyOf(this.contents, this.contents.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorFetchAssetReply other) ? false : this.token == other.token && Arrays.equals(this.contents, other.contents); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.token); + return 31 * result + Arrays.hashCode(this.contents); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchAutoCompleteData.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchAutoCompleteData.java new file mode 100644 index 0000000..667664e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchAutoCompleteData.java @@ -0,0 +1,243 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorFetchAutoCompleteData implements Packet { + public static final int PACKET_ID = 331; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 32768023; + public int token; + @Nullable + public String dataset; + @Nullable + public String query; + + @Override + public int getId() { + return 331; + } + + public AssetEditorFetchAutoCompleteData() { + } + + public AssetEditorFetchAutoCompleteData(int token, @Nullable String dataset, @Nullable String query) { + this.token = token; + this.dataset = dataset; + this.query = query; + } + + public AssetEditorFetchAutoCompleteData(@Nonnull AssetEditorFetchAutoCompleteData other) { + this.token = other.token; + this.dataset = other.dataset; + this.query = other.query; + } + + @Nonnull + public static AssetEditorFetchAutoCompleteData deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorFetchAutoCompleteData obj = new AssetEditorFetchAutoCompleteData(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 5); + int datasetLen = VarInt.peek(buf, varPos0); + if (datasetLen < 0) { + throw ProtocolException.negativeLength("Dataset", datasetLen); + } + + if (datasetLen > 4096000) { + throw ProtocolException.stringTooLong("Dataset", datasetLen, 4096000); + } + + obj.dataset = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 9); + int queryLen = VarInt.peek(buf, varPos1); + if (queryLen < 0) { + throw ProtocolException.negativeLength("Query", queryLen); + } + + if (queryLen > 4096000) { + throw ProtocolException.stringTooLong("Query", queryLen, 4096000); + } + + obj.query = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 5); + int pos0 = offset + 13 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 9); + int pos1 = offset + 13 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.dataset != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.query != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + int datasetOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int queryOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.dataset != null) { + buf.setIntLE(datasetOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.dataset, 4096000); + } else { + buf.setIntLE(datasetOffsetSlot, -1); + } + + if (this.query != null) { + buf.setIntLE(queryOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.query, 4096000); + } else { + buf.setIntLE(queryOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 13; + if (this.dataset != null) { + size += PacketIO.stringSize(this.dataset); + } + + if (this.query != null) { + size += PacketIO.stringSize(this.query); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int datasetOffset = buffer.getIntLE(offset + 5); + if (datasetOffset < 0) { + return ValidationResult.error("Invalid offset for Dataset"); + } + + int pos = offset + 13 + datasetOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Dataset"); + } + + int datasetLen = VarInt.peek(buffer, pos); + if (datasetLen < 0) { + return ValidationResult.error("Invalid string length for Dataset"); + } + + if (datasetLen > 4096000) { + return ValidationResult.error("Dataset exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += datasetLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Dataset"); + } + } + + if ((nullBits & 2) != 0) { + int queryOffset = buffer.getIntLE(offset + 9); + if (queryOffset < 0) { + return ValidationResult.error("Invalid offset for Query"); + } + + int posx = offset + 13 + queryOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Query"); + } + + int queryLen = VarInt.peek(buffer, posx); + if (queryLen < 0) { + return ValidationResult.error("Invalid string length for Query"); + } + + if (queryLen > 4096000) { + return ValidationResult.error("Query exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += queryLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Query"); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorFetchAutoCompleteData clone() { + AssetEditorFetchAutoCompleteData copy = new AssetEditorFetchAutoCompleteData(); + copy.token = this.token; + copy.dataset = this.dataset; + copy.query = this.query; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorFetchAutoCompleteData other) + ? false + : this.token == other.token && Objects.equals(this.dataset, other.dataset) && Objects.equals(this.query, other.query); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.token, this.dataset, this.query); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchAutoCompleteDataReply.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchAutoCompleteDataReply.java new file mode 100644 index 0000000..4720cd6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchAutoCompleteDataReply.java @@ -0,0 +1,200 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorFetchAutoCompleteDataReply implements Packet { + public static final int PACKET_ID = 332; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 1677721600; + public int token; + @Nullable + public String[] results; + + @Override + public int getId() { + return 332; + } + + public AssetEditorFetchAutoCompleteDataReply() { + } + + public AssetEditorFetchAutoCompleteDataReply(int token, @Nullable String[] results) { + this.token = token; + this.results = results; + } + + public AssetEditorFetchAutoCompleteDataReply(@Nonnull AssetEditorFetchAutoCompleteDataReply other) { + this.token = other.token; + this.results = other.results; + } + + @Nonnull + public static AssetEditorFetchAutoCompleteDataReply deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorFetchAutoCompleteDataReply obj = new AssetEditorFetchAutoCompleteDataReply(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int resultsCount = VarInt.peek(buf, pos); + if (resultsCount < 0) { + throw ProtocolException.negativeLength("Results", resultsCount); + } + + if (resultsCount > 4096000) { + throw ProtocolException.arrayTooLong("Results", resultsCount, 4096000); + } + + int resultsVarLen = VarInt.size(resultsCount); + if (pos + resultsVarLen + resultsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Results", pos + resultsVarLen + resultsCount * 1, buf.readableBytes()); + } + + pos += resultsVarLen; + obj.results = new String[resultsCount]; + + for (int i = 0; i < resultsCount; i++) { + int strLen = VarInt.peek(buf, pos); + if (strLen < 0) { + throw ProtocolException.negativeLength("results[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("results[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, pos); + obj.results[i] = PacketIO.readVarString(buf, pos); + pos += strVarLen + strLen; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.results != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + if (this.results != null) { + if (this.results.length > 4096000) { + throw ProtocolException.arrayTooLong("Results", this.results.length, 4096000); + } + + VarInt.write(buf, this.results.length); + + for (String item : this.results) { + PacketIO.writeVarString(buf, item, 4096000); + } + } + } + + @Override + public int computeSize() { + int size = 5; + if (this.results != null) { + int resultsSize = 0; + + for (String elem : this.results) { + resultsSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.results.length) + resultsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int resultsCount = VarInt.peek(buffer, pos); + if (resultsCount < 0) { + return ValidationResult.error("Invalid array count for Results"); + } + + if (resultsCount > 4096000) { + return ValidationResult.error("Results exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < resultsCount; i++) { + int strLen = VarInt.peek(buffer, pos); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in Results"); + } + + pos += VarInt.length(buffer, pos); + pos += strLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in Results"); + } + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorFetchAutoCompleteDataReply clone() { + AssetEditorFetchAutoCompleteDataReply copy = new AssetEditorFetchAutoCompleteDataReply(); + copy.token = this.token; + copy.results = this.results != null ? Arrays.copyOf(this.results, this.results.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorFetchAutoCompleteDataReply other) + ? false + : this.token == other.token && Arrays.equals((Object[])this.results, (Object[])other.results); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.token); + return 31 * result + Arrays.hashCode((Object[])this.results); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchJsonAssetWithParents.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchJsonAssetWithParents.java new file mode 100644 index 0000000..349814f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchJsonAssetWithParents.java @@ -0,0 +1,135 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorFetchJsonAssetWithParents implements Packet { + public static final int PACKET_ID = 311; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 32768025; + public int token; + @Nullable + public AssetPath path; + public boolean isFromOpenedTab; + + @Override + public int getId() { + return 311; + } + + public AssetEditorFetchJsonAssetWithParents() { + } + + public AssetEditorFetchJsonAssetWithParents(int token, @Nullable AssetPath path, boolean isFromOpenedTab) { + this.token = token; + this.path = path; + this.isFromOpenedTab = isFromOpenedTab; + } + + public AssetEditorFetchJsonAssetWithParents(@Nonnull AssetEditorFetchJsonAssetWithParents other) { + this.token = other.token; + this.path = other.path; + this.isFromOpenedTab = other.isFromOpenedTab; + } + + @Nonnull + public static AssetEditorFetchJsonAssetWithParents deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorFetchJsonAssetWithParents obj = new AssetEditorFetchJsonAssetWithParents(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + obj.isFromOpenedTab = buf.getByte(offset + 5) != 0; + int pos = offset + 6; + if ((nullBits & 1) != 0) { + obj.path = AssetPath.deserialize(buf, pos); + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + buf.writeByte(this.isFromOpenedTab ? 1 : 0); + if (this.path != null) { + this.path.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.path != null) { + size += this.path.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + ValidationResult pathResult = AssetPath.validateStructure(buffer, pos); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public AssetEditorFetchJsonAssetWithParents clone() { + AssetEditorFetchJsonAssetWithParents copy = new AssetEditorFetchJsonAssetWithParents(); + copy.token = this.token; + copy.path = this.path != null ? this.path.clone() : null; + copy.isFromOpenedTab = this.isFromOpenedTab; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorFetchJsonAssetWithParents other) + ? false + : this.token == other.token && Objects.equals(this.path, other.path) && this.isFromOpenedTab == other.isFromOpenedTab; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.token, this.path, this.isFromOpenedTab); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchJsonAssetWithParentsReply.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchJsonAssetWithParentsReply.java new file mode 100644 index 0000000..1d9fb15 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchJsonAssetWithParentsReply.java @@ -0,0 +1,208 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorFetchJsonAssetWithParentsReply implements Packet { + public static final int PACKET_ID = 313; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 1677721600; + public int token; + @Nullable + public Map assets; + + @Override + public int getId() { + return 313; + } + + public AssetEditorFetchJsonAssetWithParentsReply() { + } + + public AssetEditorFetchJsonAssetWithParentsReply(int token, @Nullable Map assets) { + this.token = token; + this.assets = assets; + } + + public AssetEditorFetchJsonAssetWithParentsReply(@Nonnull AssetEditorFetchJsonAssetWithParentsReply other) { + this.token = other.token; + this.assets = other.assets; + } + + @Nonnull + public static AssetEditorFetchJsonAssetWithParentsReply deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorFetchJsonAssetWithParentsReply obj = new AssetEditorFetchJsonAssetWithParentsReply(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int assetsCount = VarInt.peek(buf, pos); + if (assetsCount < 0) { + throw ProtocolException.negativeLength("Assets", assetsCount); + } + + if (assetsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Assets", assetsCount, 4096000); + } + + pos += VarInt.size(assetsCount); + obj.assets = new HashMap<>(assetsCount); + + for (int i = 0; i < assetsCount; i++) { + AssetPath key = AssetPath.deserialize(buf, pos); + pos += AssetPath.computeBytesConsumed(buf, pos); + int valLen = VarInt.peek(buf, pos); + if (valLen < 0) { + throw ProtocolException.negativeLength("val", valLen); + } + + if (valLen > 4096000) { + throw ProtocolException.stringTooLong("val", valLen, 4096000); + } + + int valVarLen = VarInt.length(buf, pos); + String val = PacketIO.readVarString(buf, pos); + pos += valVarLen + valLen; + if (obj.assets.put(key, val) != null) { + throw ProtocolException.duplicateKey("assets", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += AssetPath.computeBytesConsumed(buf, pos); + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.assets != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + if (this.assets != null) { + if (this.assets.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Assets", this.assets.size(), 4096000); + } + + VarInt.write(buf, this.assets.size()); + + for (Entry e : this.assets.entrySet()) { + e.getKey().serialize(buf); + PacketIO.writeVarString(buf, e.getValue(), 4096000); + } + } + } + + @Override + public int computeSize() { + int size = 5; + if (this.assets != null) { + int assetsSize = 0; + + for (Entry kvp : this.assets.entrySet()) { + assetsSize += kvp.getKey().computeSize() + PacketIO.stringSize(kvp.getValue()); + } + + size += VarInt.size(this.assets.size()) + assetsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int assetsCount = VarInt.peek(buffer, pos); + if (assetsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Assets"); + } + + if (assetsCount > 4096000) { + return ValidationResult.error("Assets exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < assetsCount; i++) { + pos += AssetPath.computeBytesConsumed(buffer, pos); + int valueLen = VarInt.peek(buffer, pos); + if (valueLen < 0) { + return ValidationResult.error("Invalid string length for value"); + } + + if (valueLen > 4096000) { + return ValidationResult.error("value exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += valueLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorFetchJsonAssetWithParentsReply clone() { + AssetEditorFetchJsonAssetWithParentsReply copy = new AssetEditorFetchJsonAssetWithParentsReply(); + copy.token = this.token; + copy.assets = this.assets != null ? new HashMap<>(this.assets) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorFetchJsonAssetWithParentsReply other) + ? false + : this.token == other.token && Objects.equals(this.assets, other.assets); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.token, this.assets); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchLastModifiedAssets.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchLastModifiedAssets.java new file mode 100644 index 0000000..3e44c76 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFetchLastModifiedAssets.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class AssetEditorFetchLastModifiedAssets implements Packet { + public static final int PACKET_ID = 338; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public AssetEditorFetchLastModifiedAssets() { + } + + @Override + public int getId() { + return 338; + } + + @Nonnull + public static AssetEditorFetchLastModifiedAssets deserialize(@Nonnull ByteBuf buf, int offset) { + return new AssetEditorFetchLastModifiedAssets(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public AssetEditorFetchLastModifiedAssets clone() { + return new AssetEditorFetchLastModifiedAssets(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof AssetEditorFetchLastModifiedAssets other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFileEntry.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFileEntry.java new file mode 100644 index 0000000..5896de8 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFileEntry.java @@ -0,0 +1,139 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorFileEntry { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 16384007; + @Nullable + public String path; + public boolean isDirectory; + + public AssetEditorFileEntry() { + } + + public AssetEditorFileEntry(@Nullable String path, boolean isDirectory) { + this.path = path; + this.isDirectory = isDirectory; + } + + public AssetEditorFileEntry(@Nonnull AssetEditorFileEntry other) { + this.path = other.path; + this.isDirectory = other.isDirectory; + } + + @Nonnull + public static AssetEditorFileEntry deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorFileEntry obj = new AssetEditorFileEntry(); + byte nullBits = buf.getByte(offset); + obj.isDirectory = buf.getByte(offset + 1) != 0; + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int pathLen = VarInt.peek(buf, pos); + if (pathLen < 0) { + throw ProtocolException.negativeLength("Path", pathLen); + } + + if (pathLen > 4096000) { + throw ProtocolException.stringTooLong("Path", pathLen, 4096000); + } + + int pathVarLen = VarInt.length(buf, pos); + obj.path = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += pathVarLen + pathLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.isDirectory ? 1 : 0); + if (this.path != null) { + PacketIO.writeVarString(buf, this.path, 4096000); + } + } + + public int computeSize() { + int size = 2; + if (this.path != null) { + size += PacketIO.stringSize(this.path); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int pathLen = VarInt.peek(buffer, pos); + if (pathLen < 0) { + return ValidationResult.error("Invalid string length for Path"); + } + + if (pathLen > 4096000) { + return ValidationResult.error("Path exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += pathLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Path"); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorFileEntry clone() { + AssetEditorFileEntry copy = new AssetEditorFileEntry(); + copy.path = this.path; + copy.isDirectory = this.isDirectory; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorFileEntry other) ? false : Objects.equals(this.path, other.path) && this.isDirectory == other.isDirectory; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.path, this.isDirectory); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFileTree.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFileTree.java new file mode 100644 index 0000000..d2aee72 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorFileTree.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum AssetEditorFileTree { + Server(0), + Common(1); + + public static final AssetEditorFileTree[] VALUES = values(); + private final int value; + + private AssetEditorFileTree(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static AssetEditorFileTree fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("AssetEditorFileTree", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorInitialize.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorInitialize.java new file mode 100644 index 0000000..de06b2b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorInitialize.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class AssetEditorInitialize implements Packet { + public static final int PACKET_ID = 302; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public AssetEditorInitialize() { + } + + @Override + public int getId() { + return 302; + } + + @Nonnull + public static AssetEditorInitialize deserialize(@Nonnull ByteBuf buf, int offset) { + return new AssetEditorInitialize(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public AssetEditorInitialize clone() { + return new AssetEditorInitialize(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof AssetEditorInitialize other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorJsonAssetUpdated.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorJsonAssetUpdated.java new file mode 100644 index 0000000..a654f9a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorJsonAssetUpdated.java @@ -0,0 +1,256 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorJsonAssetUpdated implements Packet { + public static final int PACKET_ID = 325; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 1677721600; + @Nullable + public AssetPath path; + @Nullable + public JsonUpdateCommand[] commands; + + @Override + public int getId() { + return 325; + } + + public AssetEditorJsonAssetUpdated() { + } + + public AssetEditorJsonAssetUpdated(@Nullable AssetPath path, @Nullable JsonUpdateCommand[] commands) { + this.path = path; + this.commands = commands; + } + + public AssetEditorJsonAssetUpdated(@Nonnull AssetEditorJsonAssetUpdated other) { + this.path = other.path; + this.commands = other.commands; + } + + @Nonnull + public static AssetEditorJsonAssetUpdated deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorJsonAssetUpdated obj = new AssetEditorJsonAssetUpdated(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + obj.path = AssetPath.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int commandsCount = VarInt.peek(buf, varPos1); + if (commandsCount < 0) { + throw ProtocolException.negativeLength("Commands", commandsCount); + } + + if (commandsCount > 4096000) { + throw ProtocolException.arrayTooLong("Commands", commandsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + commandsCount * 7L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Commands", varPos1 + varIntLen + commandsCount * 7, buf.readableBytes()); + } + + obj.commands = new JsonUpdateCommand[commandsCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < commandsCount; i++) { + obj.commands[i] = JsonUpdateCommand.deserialize(buf, elemPos); + elemPos += JsonUpdateCommand.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + pos0 += AssetPath.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += JsonUpdateCommand.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.commands != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int pathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int commandsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.path != null) { + buf.setIntLE(pathOffsetSlot, buf.writerIndex() - varBlockStart); + this.path.serialize(buf); + } else { + buf.setIntLE(pathOffsetSlot, -1); + } + + if (this.commands != null) { + buf.setIntLE(commandsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.commands.length > 4096000) { + throw ProtocolException.arrayTooLong("Commands", this.commands.length, 4096000); + } + + VarInt.write(buf, this.commands.length); + + for (JsonUpdateCommand item : this.commands) { + item.serialize(buf); + } + } else { + buf.setIntLE(commandsOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 9; + if (this.path != null) { + size += this.path.computeSize(); + } + + if (this.commands != null) { + int commandsSize = 0; + + for (JsonUpdateCommand elem : this.commands) { + commandsSize += elem.computeSize(); + } + + size += VarInt.size(this.commands.length) + commandsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int pathOffset = buffer.getIntLE(offset + 1); + if (pathOffset < 0) { + return ValidationResult.error("Invalid offset for Path"); + } + + int pos = offset + 9 + pathOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Path"); + } + + ValidationResult pathResult = AssetPath.validateStructure(buffer, pos); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int commandsOffset = buffer.getIntLE(offset + 5); + if (commandsOffset < 0) { + return ValidationResult.error("Invalid offset for Commands"); + } + + int posx = offset + 9 + commandsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Commands"); + } + + int commandsCount = VarInt.peek(buffer, posx); + if (commandsCount < 0) { + return ValidationResult.error("Invalid array count for Commands"); + } + + if (commandsCount > 4096000) { + return ValidationResult.error("Commands exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < commandsCount; i++) { + ValidationResult structResult = JsonUpdateCommand.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid JsonUpdateCommand in Commands[" + i + "]: " + structResult.error()); + } + + posx += JsonUpdateCommand.computeBytesConsumed(buffer, posx); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorJsonAssetUpdated clone() { + AssetEditorJsonAssetUpdated copy = new AssetEditorJsonAssetUpdated(); + copy.path = this.path != null ? this.path.clone() : null; + copy.commands = this.commands != null ? Arrays.stream(this.commands).map(e -> e.clone()).toArray(JsonUpdateCommand[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorJsonAssetUpdated other) + ? false + : Objects.equals(this.path, other.path) && Arrays.equals((Object[])this.commands, (Object[])other.commands); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.path); + return 31 * result + Arrays.hashCode((Object[])this.commands); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorLastModifiedAssets.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorLastModifiedAssets.java new file mode 100644 index 0000000..32ca9af --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorLastModifiedAssets.java @@ -0,0 +1,175 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorLastModifiedAssets implements Packet { + public static final int PACKET_ID = 339; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public AssetInfo[] assets; + + @Override + public int getId() { + return 339; + } + + public AssetEditorLastModifiedAssets() { + } + + public AssetEditorLastModifiedAssets(@Nullable AssetInfo[] assets) { + this.assets = assets; + } + + public AssetEditorLastModifiedAssets(@Nonnull AssetEditorLastModifiedAssets other) { + this.assets = other.assets; + } + + @Nonnull + public static AssetEditorLastModifiedAssets deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorLastModifiedAssets obj = new AssetEditorLastModifiedAssets(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int assetsCount = VarInt.peek(buf, pos); + if (assetsCount < 0) { + throw ProtocolException.negativeLength("Assets", assetsCount); + } + + if (assetsCount > 4096000) { + throw ProtocolException.arrayTooLong("Assets", assetsCount, 4096000); + } + + int assetsVarLen = VarInt.size(assetsCount); + if (pos + assetsVarLen + assetsCount * 11L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Assets", pos + assetsVarLen + assetsCount * 11, buf.readableBytes()); + } + + pos += assetsVarLen; + obj.assets = new AssetInfo[assetsCount]; + + for (int i = 0; i < assetsCount; i++) { + obj.assets[i] = AssetInfo.deserialize(buf, pos); + pos += AssetInfo.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += AssetInfo.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.assets != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.assets != null) { + if (this.assets.length > 4096000) { + throw ProtocolException.arrayTooLong("Assets", this.assets.length, 4096000); + } + + VarInt.write(buf, this.assets.length); + + for (AssetInfo item : this.assets) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.assets != null) { + int assetsSize = 0; + + for (AssetInfo elem : this.assets) { + assetsSize += elem.computeSize(); + } + + size += VarInt.size(this.assets.length) + assetsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int assetsCount = VarInt.peek(buffer, pos); + if (assetsCount < 0) { + return ValidationResult.error("Invalid array count for Assets"); + } + + if (assetsCount > 4096000) { + return ValidationResult.error("Assets exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < assetsCount; i++) { + ValidationResult structResult = AssetInfo.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid AssetInfo in Assets[" + i + "]: " + structResult.error()); + } + + pos += AssetInfo.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorLastModifiedAssets clone() { + AssetEditorLastModifiedAssets copy = new AssetEditorLastModifiedAssets(); + copy.assets = this.assets != null ? Arrays.stream(this.assets).map(e -> e.clone()).toArray(AssetInfo[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorLastModifiedAssets other ? Arrays.equals((Object[])this.assets, (Object[])other.assets) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.assets); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorModifiedAssetsCount.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorModifiedAssetsCount.java new file mode 100644 index 0000000..3a5738c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorModifiedAssetsCount.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class AssetEditorModifiedAssetsCount implements Packet { + public static final int PACKET_ID = 340; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 4; + public int count; + + @Override + public int getId() { + return 340; + } + + public AssetEditorModifiedAssetsCount() { + } + + public AssetEditorModifiedAssetsCount(int count) { + this.count = count; + } + + public AssetEditorModifiedAssetsCount(@Nonnull AssetEditorModifiedAssetsCount other) { + this.count = other.count; + } + + @Nonnull + public static AssetEditorModifiedAssetsCount deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorModifiedAssetsCount obj = new AssetEditorModifiedAssetsCount(); + obj.count = buf.getIntLE(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 4; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.count); + } + + @Override + public int computeSize() { + return 4; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 4 ? ValidationResult.error("Buffer too small: expected at least 4 bytes") : ValidationResult.OK; + } + + public AssetEditorModifiedAssetsCount clone() { + AssetEditorModifiedAssetsCount copy = new AssetEditorModifiedAssetsCount(); + copy.count = this.count; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorModifiedAssetsCount other ? this.count == other.count : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.count); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorPopupNotification.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorPopupNotification.java new file mode 100644 index 0000000..f8a93c7 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorPopupNotification.java @@ -0,0 +1,131 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.FormattedMessage; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorPopupNotification implements Packet { + public static final int PACKET_ID = 337; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public AssetEditorPopupNotificationType type = AssetEditorPopupNotificationType.Info; + @Nullable + public FormattedMessage message; + + @Override + public int getId() { + return 337; + } + + public AssetEditorPopupNotification() { + } + + public AssetEditorPopupNotification(@Nonnull AssetEditorPopupNotificationType type, @Nullable FormattedMessage message) { + this.type = type; + this.message = message; + } + + public AssetEditorPopupNotification(@Nonnull AssetEditorPopupNotification other) { + this.type = other.type; + this.message = other.message; + } + + @Nonnull + public static AssetEditorPopupNotification deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorPopupNotification obj = new AssetEditorPopupNotification(); + byte nullBits = buf.getByte(offset); + obj.type = AssetEditorPopupNotificationType.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + obj.message = FormattedMessage.deserialize(buf, pos); + pos += FormattedMessage.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + pos += FormattedMessage.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.message != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.message != null) { + this.message.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.message != null) { + size += this.message.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + ValidationResult messageResult = FormattedMessage.validateStructure(buffer, pos); + if (!messageResult.isValid()) { + return ValidationResult.error("Invalid Message: " + messageResult.error()); + } + + pos += FormattedMessage.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public AssetEditorPopupNotification clone() { + AssetEditorPopupNotification copy = new AssetEditorPopupNotification(); + copy.type = this.type; + copy.message = this.message != null ? this.message.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorPopupNotification other) + ? false + : Objects.equals(this.type, other.type) && Objects.equals(this.message, other.message); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.message); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorPopupNotificationType.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorPopupNotificationType.java new file mode 100644 index 0000000..250c1de --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorPopupNotificationType.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum AssetEditorPopupNotificationType { + Info(0), + Success(1), + Error(2), + Warning(3); + + public static final AssetEditorPopupNotificationType[] VALUES = values(); + private final int value; + + private AssetEditorPopupNotificationType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static AssetEditorPopupNotificationType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("AssetEditorPopupNotificationType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorPreviewCameraSettings.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorPreviewCameraSettings.java new file mode 100644 index 0000000..1be1483 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorPreviewCameraSettings.java @@ -0,0 +1,115 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorPreviewCameraSettings { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 29; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 29; + public static final int MAX_SIZE = 29; + public float modelScale; + @Nullable + public Vector3f cameraPosition; + @Nullable + public Vector3f cameraOrientation; + + public AssetEditorPreviewCameraSettings() { + } + + public AssetEditorPreviewCameraSettings(float modelScale, @Nullable Vector3f cameraPosition, @Nullable Vector3f cameraOrientation) { + this.modelScale = modelScale; + this.cameraPosition = cameraPosition; + this.cameraOrientation = cameraOrientation; + } + + public AssetEditorPreviewCameraSettings(@Nonnull AssetEditorPreviewCameraSettings other) { + this.modelScale = other.modelScale; + this.cameraPosition = other.cameraPosition; + this.cameraOrientation = other.cameraOrientation; + } + + @Nonnull + public static AssetEditorPreviewCameraSettings deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorPreviewCameraSettings obj = new AssetEditorPreviewCameraSettings(); + byte nullBits = buf.getByte(offset); + obj.modelScale = buf.getFloatLE(offset + 1); + if ((nullBits & 1) != 0) { + obj.cameraPosition = Vector3f.deserialize(buf, offset + 5); + } + + if ((nullBits & 2) != 0) { + obj.cameraOrientation = Vector3f.deserialize(buf, offset + 17); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 29; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.cameraPosition != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.cameraOrientation != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.modelScale); + if (this.cameraPosition != null) { + this.cameraPosition.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.cameraOrientation != null) { + this.cameraOrientation.serialize(buf); + } else { + buf.writeZero(12); + } + } + + public int computeSize() { + return 29; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 29 ? ValidationResult.error("Buffer too small: expected at least 29 bytes") : ValidationResult.OK; + } + + public AssetEditorPreviewCameraSettings clone() { + AssetEditorPreviewCameraSettings copy = new AssetEditorPreviewCameraSettings(); + copy.modelScale = this.modelScale; + copy.cameraPosition = this.cameraPosition != null ? this.cameraPosition.clone() : null; + copy.cameraOrientation = this.cameraOrientation != null ? this.cameraOrientation.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorPreviewCameraSettings other) + ? false + : this.modelScale == other.modelScale + && Objects.equals(this.cameraPosition, other.cameraPosition) + && Objects.equals(this.cameraOrientation, other.cameraOrientation); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.modelScale, this.cameraPosition, this.cameraOrientation); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRebuildCaches.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRebuildCaches.java new file mode 100644 index 0000000..60d2e72 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRebuildCaches.java @@ -0,0 +1,109 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class AssetEditorRebuildCaches implements Packet { + public static final int PACKET_ID = 348; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 5; + public boolean blockTextures; + public boolean models; + public boolean modelTextures; + public boolean mapGeometry; + public boolean itemIcons; + + @Override + public int getId() { + return 348; + } + + public AssetEditorRebuildCaches() { + } + + public AssetEditorRebuildCaches(boolean blockTextures, boolean models, boolean modelTextures, boolean mapGeometry, boolean itemIcons) { + this.blockTextures = blockTextures; + this.models = models; + this.modelTextures = modelTextures; + this.mapGeometry = mapGeometry; + this.itemIcons = itemIcons; + } + + public AssetEditorRebuildCaches(@Nonnull AssetEditorRebuildCaches other) { + this.blockTextures = other.blockTextures; + this.models = other.models; + this.modelTextures = other.modelTextures; + this.mapGeometry = other.mapGeometry; + this.itemIcons = other.itemIcons; + } + + @Nonnull + public static AssetEditorRebuildCaches deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorRebuildCaches obj = new AssetEditorRebuildCaches(); + obj.blockTextures = buf.getByte(offset + 0) != 0; + obj.models = buf.getByte(offset + 1) != 0; + obj.modelTextures = buf.getByte(offset + 2) != 0; + obj.mapGeometry = buf.getByte(offset + 3) != 0; + obj.itemIcons = buf.getByte(offset + 4) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 5; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.blockTextures ? 1 : 0); + buf.writeByte(this.models ? 1 : 0); + buf.writeByte(this.modelTextures ? 1 : 0); + buf.writeByte(this.mapGeometry ? 1 : 0); + buf.writeByte(this.itemIcons ? 1 : 0); + } + + @Override + public int computeSize() { + return 5; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 5 ? ValidationResult.error("Buffer too small: expected at least 5 bytes") : ValidationResult.OK; + } + + public AssetEditorRebuildCaches clone() { + AssetEditorRebuildCaches copy = new AssetEditorRebuildCaches(); + copy.blockTextures = this.blockTextures; + copy.models = this.models; + copy.modelTextures = this.modelTextures; + copy.mapGeometry = this.mapGeometry; + copy.itemIcons = this.itemIcons; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorRebuildCaches other) + ? false + : this.blockTextures == other.blockTextures + && this.models == other.models + && this.modelTextures == other.modelTextures + && this.mapGeometry == other.mapGeometry + && this.itemIcons == other.itemIcons; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.blockTextures, this.models, this.modelTextures, this.mapGeometry, this.itemIcons); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRedoChanges.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRedoChanges.java new file mode 100644 index 0000000..03391a5 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRedoChanges.java @@ -0,0 +1,127 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorRedoChanges implements Packet { + public static final int PACKET_ID = 350; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 32768024; + public int token; + @Nullable + public AssetPath path; + + @Override + public int getId() { + return 350; + } + + public AssetEditorRedoChanges() { + } + + public AssetEditorRedoChanges(int token, @Nullable AssetPath path) { + this.token = token; + this.path = path; + } + + public AssetEditorRedoChanges(@Nonnull AssetEditorRedoChanges other) { + this.token = other.token; + this.path = other.path; + } + + @Nonnull + public static AssetEditorRedoChanges deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorRedoChanges obj = new AssetEditorRedoChanges(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + obj.path = AssetPath.deserialize(buf, pos); + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + if (this.path != null) { + this.path.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 5; + if (this.path != null) { + size += this.path.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + ValidationResult pathResult = AssetPath.validateStructure(buffer, pos); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public AssetEditorRedoChanges clone() { + AssetEditorRedoChanges copy = new AssetEditorRedoChanges(); + copy.token = this.token; + copy.path = this.path != null ? this.path.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorRedoChanges other) ? false : this.token == other.token && Objects.equals(this.path, other.path); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.token, this.path); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRenameAsset.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRenameAsset.java new file mode 100644 index 0000000..99c8016 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRenameAsset.java @@ -0,0 +1,204 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorRenameAsset implements Packet { + public static final int PACKET_ID = 328; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 65536051; + public int token; + @Nullable + public AssetPath path; + @Nullable + public AssetPath newPath; + + @Override + public int getId() { + return 328; + } + + public AssetEditorRenameAsset() { + } + + public AssetEditorRenameAsset(int token, @Nullable AssetPath path, @Nullable AssetPath newPath) { + this.token = token; + this.path = path; + this.newPath = newPath; + } + + public AssetEditorRenameAsset(@Nonnull AssetEditorRenameAsset other) { + this.token = other.token; + this.path = other.path; + this.newPath = other.newPath; + } + + @Nonnull + public static AssetEditorRenameAsset deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorRenameAsset obj = new AssetEditorRenameAsset(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 5); + obj.path = AssetPath.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 9); + obj.newPath = AssetPath.deserialize(buf, varPos1); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 5); + int pos0 = offset + 13 + fieldOffset0; + pos0 += AssetPath.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 9); + int pos1 = offset + 13 + fieldOffset1; + pos1 += AssetPath.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.newPath != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + int pathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int newPathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.path != null) { + buf.setIntLE(pathOffsetSlot, buf.writerIndex() - varBlockStart); + this.path.serialize(buf); + } else { + buf.setIntLE(pathOffsetSlot, -1); + } + + if (this.newPath != null) { + buf.setIntLE(newPathOffsetSlot, buf.writerIndex() - varBlockStart); + this.newPath.serialize(buf); + } else { + buf.setIntLE(newPathOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 13; + if (this.path != null) { + size += this.path.computeSize(); + } + + if (this.newPath != null) { + size += this.newPath.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int pathOffset = buffer.getIntLE(offset + 5); + if (pathOffset < 0) { + return ValidationResult.error("Invalid offset for Path"); + } + + int pos = offset + 13 + pathOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Path"); + } + + ValidationResult pathResult = AssetPath.validateStructure(buffer, pos); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int newPathOffset = buffer.getIntLE(offset + 9); + if (newPathOffset < 0) { + return ValidationResult.error("Invalid offset for NewPath"); + } + + int posx = offset + 13 + newPathOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for NewPath"); + } + + ValidationResult newPathResult = AssetPath.validateStructure(buffer, posx); + if (!newPathResult.isValid()) { + return ValidationResult.error("Invalid NewPath: " + newPathResult.error()); + } + + posx += AssetPath.computeBytesConsumed(buffer, posx); + } + + return ValidationResult.OK; + } + } + + public AssetEditorRenameAsset clone() { + AssetEditorRenameAsset copy = new AssetEditorRenameAsset(); + copy.token = this.token; + copy.path = this.path != null ? this.path.clone() : null; + copy.newPath = this.newPath != null ? this.newPath.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorRenameAsset other) + ? false + : this.token == other.token && Objects.equals(this.path, other.path) && Objects.equals(this.newPath, other.newPath); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.token, this.path, this.newPath); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRenameDirectory.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRenameDirectory.java new file mode 100644 index 0000000..55c3ce0 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRenameDirectory.java @@ -0,0 +1,204 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorRenameDirectory implements Packet { + public static final int PACKET_ID = 309; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 65536051; + public int token; + @Nullable + public AssetPath path; + @Nullable + public AssetPath newPath; + + @Override + public int getId() { + return 309; + } + + public AssetEditorRenameDirectory() { + } + + public AssetEditorRenameDirectory(int token, @Nullable AssetPath path, @Nullable AssetPath newPath) { + this.token = token; + this.path = path; + this.newPath = newPath; + } + + public AssetEditorRenameDirectory(@Nonnull AssetEditorRenameDirectory other) { + this.token = other.token; + this.path = other.path; + this.newPath = other.newPath; + } + + @Nonnull + public static AssetEditorRenameDirectory deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorRenameDirectory obj = new AssetEditorRenameDirectory(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 5); + obj.path = AssetPath.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 9); + obj.newPath = AssetPath.deserialize(buf, varPos1); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 5); + int pos0 = offset + 13 + fieldOffset0; + pos0 += AssetPath.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 9); + int pos1 = offset + 13 + fieldOffset1; + pos1 += AssetPath.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.newPath != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + int pathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int newPathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.path != null) { + buf.setIntLE(pathOffsetSlot, buf.writerIndex() - varBlockStart); + this.path.serialize(buf); + } else { + buf.setIntLE(pathOffsetSlot, -1); + } + + if (this.newPath != null) { + buf.setIntLE(newPathOffsetSlot, buf.writerIndex() - varBlockStart); + this.newPath.serialize(buf); + } else { + buf.setIntLE(newPathOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 13; + if (this.path != null) { + size += this.path.computeSize(); + } + + if (this.newPath != null) { + size += this.newPath.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int pathOffset = buffer.getIntLE(offset + 5); + if (pathOffset < 0) { + return ValidationResult.error("Invalid offset for Path"); + } + + int pos = offset + 13 + pathOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Path"); + } + + ValidationResult pathResult = AssetPath.validateStructure(buffer, pos); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int newPathOffset = buffer.getIntLE(offset + 9); + if (newPathOffset < 0) { + return ValidationResult.error("Invalid offset for NewPath"); + } + + int posx = offset + 13 + newPathOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for NewPath"); + } + + ValidationResult newPathResult = AssetPath.validateStructure(buffer, posx); + if (!newPathResult.isValid()) { + return ValidationResult.error("Invalid NewPath: " + newPathResult.error()); + } + + posx += AssetPath.computeBytesConsumed(buffer, posx); + } + + return ValidationResult.OK; + } + } + + public AssetEditorRenameDirectory clone() { + AssetEditorRenameDirectory copy = new AssetEditorRenameDirectory(); + copy.token = this.token; + copy.path = this.path != null ? this.path.clone() : null; + copy.newPath = this.newPath != null ? this.newPath.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorRenameDirectory other) + ? false + : this.token == other.token && Objects.equals(this.path, other.path) && Objects.equals(this.newPath, other.newPath); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.token, this.path, this.newPath); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRequestChildrenList.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRequestChildrenList.java new file mode 100644 index 0000000..4db6279 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRequestChildrenList.java @@ -0,0 +1,121 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorRequestChildrenList implements Packet { + public static final int PACKET_ID = 321; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 32768020; + @Nullable + public AssetPath path; + + @Override + public int getId() { + return 321; + } + + public AssetEditorRequestChildrenList() { + } + + public AssetEditorRequestChildrenList(@Nullable AssetPath path) { + this.path = path; + } + + public AssetEditorRequestChildrenList(@Nonnull AssetEditorRequestChildrenList other) { + this.path = other.path; + } + + @Nonnull + public static AssetEditorRequestChildrenList deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorRequestChildrenList obj = new AssetEditorRequestChildrenList(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + obj.path = AssetPath.deserialize(buf, pos); + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.path != null) { + this.path.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.path != null) { + size += this.path.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + ValidationResult pathResult = AssetPath.validateStructure(buffer, pos); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public AssetEditorRequestChildrenList clone() { + AssetEditorRequestChildrenList copy = new AssetEditorRequestChildrenList(); + copy.path = this.path != null ? this.path.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorRequestChildrenList other ? Objects.equals(this.path, other.path) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.path); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRequestChildrenListReply.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRequestChildrenListReply.java new file mode 100644 index 0000000..b6521ba --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRequestChildrenListReply.java @@ -0,0 +1,272 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorRequestChildrenListReply implements Packet { + public static final int PACKET_ID = 322; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 1677721600; + @Nullable + public AssetPath path; + @Nullable + public String[] childrenIds; + + @Override + public int getId() { + return 322; + } + + public AssetEditorRequestChildrenListReply() { + } + + public AssetEditorRequestChildrenListReply(@Nullable AssetPath path, @Nullable String[] childrenIds) { + this.path = path; + this.childrenIds = childrenIds; + } + + public AssetEditorRequestChildrenListReply(@Nonnull AssetEditorRequestChildrenListReply other) { + this.path = other.path; + this.childrenIds = other.childrenIds; + } + + @Nonnull + public static AssetEditorRequestChildrenListReply deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorRequestChildrenListReply obj = new AssetEditorRequestChildrenListReply(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + obj.path = AssetPath.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int childrenIdsCount = VarInt.peek(buf, varPos1); + if (childrenIdsCount < 0) { + throw ProtocolException.negativeLength("ChildrenIds", childrenIdsCount); + } + + if (childrenIdsCount > 4096000) { + throw ProtocolException.arrayTooLong("ChildrenIds", childrenIdsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + childrenIdsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("ChildrenIds", varPos1 + varIntLen + childrenIdsCount * 1, buf.readableBytes()); + } + + obj.childrenIds = new String[childrenIdsCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < childrenIdsCount; i++) { + int strLen = VarInt.peek(buf, elemPos); + if (strLen < 0) { + throw ProtocolException.negativeLength("childrenIds[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("childrenIds[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, elemPos); + obj.childrenIds[i] = PacketIO.readVarString(buf, elemPos); + elemPos += strVarLen + strLen; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + pos0 += AssetPath.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.childrenIds != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int pathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int childrenIdsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.path != null) { + buf.setIntLE(pathOffsetSlot, buf.writerIndex() - varBlockStart); + this.path.serialize(buf); + } else { + buf.setIntLE(pathOffsetSlot, -1); + } + + if (this.childrenIds != null) { + buf.setIntLE(childrenIdsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.childrenIds.length > 4096000) { + throw ProtocolException.arrayTooLong("ChildrenIds", this.childrenIds.length, 4096000); + } + + VarInt.write(buf, this.childrenIds.length); + + for (String item : this.childrenIds) { + PacketIO.writeVarString(buf, item, 4096000); + } + } else { + buf.setIntLE(childrenIdsOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 9; + if (this.path != null) { + size += this.path.computeSize(); + } + + if (this.childrenIds != null) { + int childrenIdsSize = 0; + + for (String elem : this.childrenIds) { + childrenIdsSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.childrenIds.length) + childrenIdsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int pathOffset = buffer.getIntLE(offset + 1); + if (pathOffset < 0) { + return ValidationResult.error("Invalid offset for Path"); + } + + int pos = offset + 9 + pathOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Path"); + } + + ValidationResult pathResult = AssetPath.validateStructure(buffer, pos); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int childrenIdsOffset = buffer.getIntLE(offset + 5); + if (childrenIdsOffset < 0) { + return ValidationResult.error("Invalid offset for ChildrenIds"); + } + + int posx = offset + 9 + childrenIdsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ChildrenIds"); + } + + int childrenIdsCount = VarInt.peek(buffer, posx); + if (childrenIdsCount < 0) { + return ValidationResult.error("Invalid array count for ChildrenIds"); + } + + if (childrenIdsCount > 4096000) { + return ValidationResult.error("ChildrenIds exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < childrenIdsCount; i++) { + int strLen = VarInt.peek(buffer, posx); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in ChildrenIds"); + } + + posx += VarInt.length(buffer, posx); + posx += strLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in ChildrenIds"); + } + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorRequestChildrenListReply clone() { + AssetEditorRequestChildrenListReply copy = new AssetEditorRequestChildrenListReply(); + copy.path = this.path != null ? this.path.clone() : null; + copy.childrenIds = this.childrenIds != null ? Arrays.copyOf(this.childrenIds, this.childrenIds.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorRequestChildrenListReply other) + ? false + : Objects.equals(this.path, other.path) && Arrays.equals((Object[])this.childrenIds, (Object[])other.childrenIds); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.path); + return 31 * result + Arrays.hashCode((Object[])this.childrenIds); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRequestDataset.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRequestDataset.java new file mode 100644 index 0000000..8e76ead --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRequestDataset.java @@ -0,0 +1,143 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorRequestDataset implements Packet { + public static final int PACKET_ID = 333; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 16384006; + @Nullable + public String name; + + @Override + public int getId() { + return 333; + } + + public AssetEditorRequestDataset() { + } + + public AssetEditorRequestDataset(@Nullable String name) { + this.name = name; + } + + public AssetEditorRequestDataset(@Nonnull AssetEditorRequestDataset other) { + this.name = other.name; + } + + @Nonnull + public static AssetEditorRequestDataset deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorRequestDataset obj = new AssetEditorRequestDataset(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int nameLen = VarInt.peek(buf, pos); + if (nameLen < 0) { + throw ProtocolException.negativeLength("Name", nameLen); + } + + if (nameLen > 4096000) { + throw ProtocolException.stringTooLong("Name", nameLen, 4096000); + } + + int nameVarLen = VarInt.length(buf, pos); + obj.name = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += nameVarLen + nameLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.name != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.name != null) { + PacketIO.writeVarString(buf, this.name, 4096000); + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.name != null) { + size += PacketIO.stringSize(this.name); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int nameLen = VarInt.peek(buffer, pos); + if (nameLen < 0) { + return ValidationResult.error("Invalid string length for Name"); + } + + if (nameLen > 4096000) { + return ValidationResult.error("Name exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += nameLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Name"); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorRequestDataset clone() { + AssetEditorRequestDataset copy = new AssetEditorRequestDataset(); + copy.name = this.name; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorRequestDataset other ? Objects.equals(this.name, other.name) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.name); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRequestDatasetReply.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRequestDatasetReply.java new file mode 100644 index 0000000..ee31b2f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorRequestDatasetReply.java @@ -0,0 +1,290 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorRequestDatasetReply implements Packet { + public static final int PACKET_ID = 334; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String name; + @Nullable + public String[] ids; + + @Override + public int getId() { + return 334; + } + + public AssetEditorRequestDatasetReply() { + } + + public AssetEditorRequestDatasetReply(@Nullable String name, @Nullable String[] ids) { + this.name = name; + this.ids = ids; + } + + public AssetEditorRequestDatasetReply(@Nonnull AssetEditorRequestDatasetReply other) { + this.name = other.name; + this.ids = other.ids; + } + + @Nonnull + public static AssetEditorRequestDatasetReply deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorRequestDatasetReply obj = new AssetEditorRequestDatasetReply(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int nameLen = VarInt.peek(buf, varPos0); + if (nameLen < 0) { + throw ProtocolException.negativeLength("Name", nameLen); + } + + if (nameLen > 4096000) { + throw ProtocolException.stringTooLong("Name", nameLen, 4096000); + } + + obj.name = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int idsCount = VarInt.peek(buf, varPos1); + if (idsCount < 0) { + throw ProtocolException.negativeLength("Ids", idsCount); + } + + if (idsCount > 4096000) { + throw ProtocolException.arrayTooLong("Ids", idsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + idsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Ids", varPos1 + varIntLen + idsCount * 1, buf.readableBytes()); + } + + obj.ids = new String[idsCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < idsCount; i++) { + int strLen = VarInt.peek(buf, elemPos); + if (strLen < 0) { + throw ProtocolException.negativeLength("ids[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("ids[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, elemPos); + obj.ids[i] = PacketIO.readVarString(buf, elemPos); + elemPos += strVarLen + strLen; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.name != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.ids != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int nameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int idsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.name != null) { + buf.setIntLE(nameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.name, 4096000); + } else { + buf.setIntLE(nameOffsetSlot, -1); + } + + if (this.ids != null) { + buf.setIntLE(idsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.ids.length > 4096000) { + throw ProtocolException.arrayTooLong("Ids", this.ids.length, 4096000); + } + + VarInt.write(buf, this.ids.length); + + for (String item : this.ids) { + PacketIO.writeVarString(buf, item, 4096000); + } + } else { + buf.setIntLE(idsOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 9; + if (this.name != null) { + size += PacketIO.stringSize(this.name); + } + + if (this.ids != null) { + int idsSize = 0; + + for (String elem : this.ids) { + idsSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.ids.length) + idsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int nameOffset = buffer.getIntLE(offset + 1); + if (nameOffset < 0) { + return ValidationResult.error("Invalid offset for Name"); + } + + int pos = offset + 9 + nameOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Name"); + } + + int nameLen = VarInt.peek(buffer, pos); + if (nameLen < 0) { + return ValidationResult.error("Invalid string length for Name"); + } + + if (nameLen > 4096000) { + return ValidationResult.error("Name exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += nameLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Name"); + } + } + + if ((nullBits & 2) != 0) { + int idsOffset = buffer.getIntLE(offset + 5); + if (idsOffset < 0) { + return ValidationResult.error("Invalid offset for Ids"); + } + + int posx = offset + 9 + idsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Ids"); + } + + int idsCount = VarInt.peek(buffer, posx); + if (idsCount < 0) { + return ValidationResult.error("Invalid array count for Ids"); + } + + if (idsCount > 4096000) { + return ValidationResult.error("Ids exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < idsCount; i++) { + int strLen = VarInt.peek(buffer, posx); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in Ids"); + } + + posx += VarInt.length(buffer, posx); + posx += strLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in Ids"); + } + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorRequestDatasetReply clone() { + AssetEditorRequestDatasetReply copy = new AssetEditorRequestDatasetReply(); + copy.name = this.name; + copy.ids = this.ids != null ? Arrays.copyOf(this.ids, this.ids.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorRequestDatasetReply other) + ? false + : Objects.equals(this.name, other.name) && Arrays.equals((Object[])this.ids, (Object[])other.ids); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.name); + return 31 * result + Arrays.hashCode((Object[])this.ids); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorSelectAsset.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorSelectAsset.java new file mode 100644 index 0000000..13a7f8f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorSelectAsset.java @@ -0,0 +1,121 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorSelectAsset implements Packet { + public static final int PACKET_ID = 336; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 32768020; + @Nullable + public AssetPath path; + + @Override + public int getId() { + return 336; + } + + public AssetEditorSelectAsset() { + } + + public AssetEditorSelectAsset(@Nullable AssetPath path) { + this.path = path; + } + + public AssetEditorSelectAsset(@Nonnull AssetEditorSelectAsset other) { + this.path = other.path; + } + + @Nonnull + public static AssetEditorSelectAsset deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorSelectAsset obj = new AssetEditorSelectAsset(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + obj.path = AssetPath.deserialize(buf, pos); + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.path != null) { + this.path.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.path != null) { + size += this.path.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + ValidationResult pathResult = AssetPath.validateStructure(buffer, pos); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public AssetEditorSelectAsset clone() { + AssetEditorSelectAsset copy = new AssetEditorSelectAsset(); + copy.path = this.path != null ? this.path.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorSelectAsset other ? Objects.equals(this.path, other.path) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.path); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorSetGameTime.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorSetGameTime.java new file mode 100644 index 0000000..ce87694 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorSetGameTime.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.InstantData; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorSetGameTime implements Packet { + public static final int PACKET_ID = 352; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 14; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 14; + public static final int MAX_SIZE = 14; + @Nullable + public InstantData gameTime; + public boolean paused; + + @Override + public int getId() { + return 352; + } + + public AssetEditorSetGameTime() { + } + + public AssetEditorSetGameTime(@Nullable InstantData gameTime, boolean paused) { + this.gameTime = gameTime; + this.paused = paused; + } + + public AssetEditorSetGameTime(@Nonnull AssetEditorSetGameTime other) { + this.gameTime = other.gameTime; + this.paused = other.paused; + } + + @Nonnull + public static AssetEditorSetGameTime deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorSetGameTime obj = new AssetEditorSetGameTime(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.gameTime = InstantData.deserialize(buf, offset + 1); + } + + obj.paused = buf.getByte(offset + 13) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 14; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.gameTime != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.gameTime != null) { + this.gameTime.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeByte(this.paused ? 1 : 0); + } + + @Override + public int computeSize() { + return 14; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 14 ? ValidationResult.error("Buffer too small: expected at least 14 bytes") : ValidationResult.OK; + } + + public AssetEditorSetGameTime clone() { + AssetEditorSetGameTime copy = new AssetEditorSetGameTime(); + copy.gameTime = this.gameTime != null ? this.gameTime.clone() : null; + copy.paused = this.paused; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorSetGameTime other) ? false : Objects.equals(this.gameTime, other.gameTime) && this.paused == other.paused; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.gameTime, this.paused); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorSetupAssetTypes.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorSetupAssetTypes.java new file mode 100644 index 0000000..c3a4523 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorSetupAssetTypes.java @@ -0,0 +1,175 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorSetupAssetTypes implements Packet { + public static final int PACKET_ID = 306; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public AssetEditorAssetType[] assetTypes; + + @Override + public int getId() { + return 306; + } + + public AssetEditorSetupAssetTypes() { + } + + public AssetEditorSetupAssetTypes(@Nullable AssetEditorAssetType[] assetTypes) { + this.assetTypes = assetTypes; + } + + public AssetEditorSetupAssetTypes(@Nonnull AssetEditorSetupAssetTypes other) { + this.assetTypes = other.assetTypes; + } + + @Nonnull + public static AssetEditorSetupAssetTypes deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorSetupAssetTypes obj = new AssetEditorSetupAssetTypes(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int assetTypesCount = VarInt.peek(buf, pos); + if (assetTypesCount < 0) { + throw ProtocolException.negativeLength("AssetTypes", assetTypesCount); + } + + if (assetTypesCount > 4096000) { + throw ProtocolException.arrayTooLong("AssetTypes", assetTypesCount, 4096000); + } + + int assetTypesVarLen = VarInt.size(assetTypesCount); + if (pos + assetTypesVarLen + assetTypesCount * 3L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("AssetTypes", pos + assetTypesVarLen + assetTypesCount * 3, buf.readableBytes()); + } + + pos += assetTypesVarLen; + obj.assetTypes = new AssetEditorAssetType[assetTypesCount]; + + for (int i = 0; i < assetTypesCount; i++) { + obj.assetTypes[i] = AssetEditorAssetType.deserialize(buf, pos); + pos += AssetEditorAssetType.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += AssetEditorAssetType.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.assetTypes != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.assetTypes != null) { + if (this.assetTypes.length > 4096000) { + throw ProtocolException.arrayTooLong("AssetTypes", this.assetTypes.length, 4096000); + } + + VarInt.write(buf, this.assetTypes.length); + + for (AssetEditorAssetType item : this.assetTypes) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.assetTypes != null) { + int assetTypesSize = 0; + + for (AssetEditorAssetType elem : this.assetTypes) { + assetTypesSize += elem.computeSize(); + } + + size += VarInt.size(this.assetTypes.length) + assetTypesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int assetTypesCount = VarInt.peek(buffer, pos); + if (assetTypesCount < 0) { + return ValidationResult.error("Invalid array count for AssetTypes"); + } + + if (assetTypesCount > 4096000) { + return ValidationResult.error("AssetTypes exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < assetTypesCount; i++) { + ValidationResult structResult = AssetEditorAssetType.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid AssetEditorAssetType in AssetTypes[" + i + "]: " + structResult.error()); + } + + pos += AssetEditorAssetType.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorSetupAssetTypes clone() { + AssetEditorSetupAssetTypes copy = new AssetEditorSetupAssetTypes(); + copy.assetTypes = this.assetTypes != null ? Arrays.stream(this.assetTypes).map(e -> e.clone()).toArray(AssetEditorAssetType[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorSetupAssetTypes other ? Arrays.equals((Object[])this.assetTypes, (Object[])other.assetTypes) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.assetTypes); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorSetupSchemas.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorSetupSchemas.java new file mode 100644 index 0000000..6def1bd --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorSetupSchemas.java @@ -0,0 +1,175 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorSetupSchemas implements Packet { + public static final int PACKET_ID = 305; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public SchemaFile[] schemas; + + @Override + public int getId() { + return 305; + } + + public AssetEditorSetupSchemas() { + } + + public AssetEditorSetupSchemas(@Nullable SchemaFile[] schemas) { + this.schemas = schemas; + } + + public AssetEditorSetupSchemas(@Nonnull AssetEditorSetupSchemas other) { + this.schemas = other.schemas; + } + + @Nonnull + public static AssetEditorSetupSchemas deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorSetupSchemas obj = new AssetEditorSetupSchemas(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int schemasCount = VarInt.peek(buf, pos); + if (schemasCount < 0) { + throw ProtocolException.negativeLength("Schemas", schemasCount); + } + + if (schemasCount > 4096000) { + throw ProtocolException.arrayTooLong("Schemas", schemasCount, 4096000); + } + + int schemasVarLen = VarInt.size(schemasCount); + if (pos + schemasVarLen + schemasCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Schemas", pos + schemasVarLen + schemasCount * 1, buf.readableBytes()); + } + + pos += schemasVarLen; + obj.schemas = new SchemaFile[schemasCount]; + + for (int i = 0; i < schemasCount; i++) { + obj.schemas[i] = SchemaFile.deserialize(buf, pos); + pos += SchemaFile.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += SchemaFile.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.schemas != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.schemas != null) { + if (this.schemas.length > 4096000) { + throw ProtocolException.arrayTooLong("Schemas", this.schemas.length, 4096000); + } + + VarInt.write(buf, this.schemas.length); + + for (SchemaFile item : this.schemas) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.schemas != null) { + int schemasSize = 0; + + for (SchemaFile elem : this.schemas) { + schemasSize += elem.computeSize(); + } + + size += VarInt.size(this.schemas.length) + schemasSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int schemasCount = VarInt.peek(buffer, pos); + if (schemasCount < 0) { + return ValidationResult.error("Invalid array count for Schemas"); + } + + if (schemasCount > 4096000) { + return ValidationResult.error("Schemas exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < schemasCount; i++) { + ValidationResult structResult = SchemaFile.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid SchemaFile in Schemas[" + i + "]: " + structResult.error()); + } + + pos += SchemaFile.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorSetupSchemas clone() { + AssetEditorSetupSchemas copy = new AssetEditorSetupSchemas(); + copy.schemas = this.schemas != null ? Arrays.stream(this.schemas).map(e -> e.clone()).toArray(SchemaFile[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorSetupSchemas other ? Arrays.equals((Object[])this.schemas, (Object[])other.schemas) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.schemas); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorSubscribeModifiedAssetsChanges.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorSubscribeModifiedAssetsChanges.java new file mode 100644 index 0000000..176f8b1 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorSubscribeModifiedAssetsChanges.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class AssetEditorSubscribeModifiedAssetsChanges implements Packet { + public static final int PACKET_ID = 341; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean subscribe; + + @Override + public int getId() { + return 341; + } + + public AssetEditorSubscribeModifiedAssetsChanges() { + } + + public AssetEditorSubscribeModifiedAssetsChanges(boolean subscribe) { + this.subscribe = subscribe; + } + + public AssetEditorSubscribeModifiedAssetsChanges(@Nonnull AssetEditorSubscribeModifiedAssetsChanges other) { + this.subscribe = other.subscribe; + } + + @Nonnull + public static AssetEditorSubscribeModifiedAssetsChanges deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorSubscribeModifiedAssetsChanges obj = new AssetEditorSubscribeModifiedAssetsChanges(); + obj.subscribe = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.subscribe ? 1 : 0); + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public AssetEditorSubscribeModifiedAssetsChanges clone() { + AssetEditorSubscribeModifiedAssetsChanges copy = new AssetEditorSubscribeModifiedAssetsChanges(); + copy.subscribe = this.subscribe; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorSubscribeModifiedAssetsChanges other ? this.subscribe == other.subscribe : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.subscribe); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUndoChanges.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUndoChanges.java new file mode 100644 index 0000000..a35b8a2 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUndoChanges.java @@ -0,0 +1,127 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorUndoChanges implements Packet { + public static final int PACKET_ID = 349; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 32768024; + public int token; + @Nullable + public AssetPath path; + + @Override + public int getId() { + return 349; + } + + public AssetEditorUndoChanges() { + } + + public AssetEditorUndoChanges(int token, @Nullable AssetPath path) { + this.token = token; + this.path = path; + } + + public AssetEditorUndoChanges(@Nonnull AssetEditorUndoChanges other) { + this.token = other.token; + this.path = other.path; + } + + @Nonnull + public static AssetEditorUndoChanges deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorUndoChanges obj = new AssetEditorUndoChanges(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + obj.path = AssetPath.deserialize(buf, pos); + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + pos += AssetPath.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + if (this.path != null) { + this.path.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 5; + if (this.path != null) { + size += this.path.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + ValidationResult pathResult = AssetPath.validateStructure(buffer, pos); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public AssetEditorUndoChanges clone() { + AssetEditorUndoChanges copy = new AssetEditorUndoChanges(); + copy.token = this.token; + copy.path = this.path != null ? this.path.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorUndoChanges other) ? false : this.token == other.token && Objects.equals(this.path, other.path); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.token, this.path); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUndoRedoReply.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUndoRedoReply.java new file mode 100644 index 0000000..25bb0dc --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUndoRedoReply.java @@ -0,0 +1,127 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorUndoRedoReply implements Packet { + public static final int PACKET_ID = 351; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 1677721600; + public int token; + @Nullable + public JsonUpdateCommand command; + + @Override + public int getId() { + return 351; + } + + public AssetEditorUndoRedoReply() { + } + + public AssetEditorUndoRedoReply(int token, @Nullable JsonUpdateCommand command) { + this.token = token; + this.command = command; + } + + public AssetEditorUndoRedoReply(@Nonnull AssetEditorUndoRedoReply other) { + this.token = other.token; + this.command = other.command; + } + + @Nonnull + public static AssetEditorUndoRedoReply deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorUndoRedoReply obj = new AssetEditorUndoRedoReply(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + obj.command = JsonUpdateCommand.deserialize(buf, pos); + pos += JsonUpdateCommand.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + pos += JsonUpdateCommand.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.command != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + if (this.command != null) { + this.command.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 5; + if (this.command != null) { + size += this.command.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + ValidationResult commandResult = JsonUpdateCommand.validateStructure(buffer, pos); + if (!commandResult.isValid()) { + return ValidationResult.error("Invalid Command: " + commandResult.error()); + } + + pos += JsonUpdateCommand.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public AssetEditorUndoRedoReply clone() { + AssetEditorUndoRedoReply copy = new AssetEditorUndoRedoReply(); + copy.token = this.token; + copy.command = this.command != null ? this.command.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorUndoRedoReply other) ? false : this.token == other.token && Objects.equals(this.command, other.command); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.token, this.command); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateAsset.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateAsset.java new file mode 100644 index 0000000..dd08696 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateAsset.java @@ -0,0 +1,331 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorUpdateAsset implements Packet { + public static final int PACKET_ID = 324; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 21; + public static final int MAX_SIZE = 53248050; + public int token; + @Nullable + public String assetType; + @Nullable + public AssetPath path; + public int assetIndex = Integer.MIN_VALUE; + @Nullable + public byte[] data; + + @Override + public int getId() { + return 324; + } + + public AssetEditorUpdateAsset() { + } + + public AssetEditorUpdateAsset(int token, @Nullable String assetType, @Nullable AssetPath path, int assetIndex, @Nullable byte[] data) { + this.token = token; + this.assetType = assetType; + this.path = path; + this.assetIndex = assetIndex; + this.data = data; + } + + public AssetEditorUpdateAsset(@Nonnull AssetEditorUpdateAsset other) { + this.token = other.token; + this.assetType = other.assetType; + this.path = other.path; + this.assetIndex = other.assetIndex; + this.data = other.data; + } + + @Nonnull + public static AssetEditorUpdateAsset deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorUpdateAsset obj = new AssetEditorUpdateAsset(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + obj.assetIndex = buf.getIntLE(offset + 5); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 21 + buf.getIntLE(offset + 9); + int assetTypeLen = VarInt.peek(buf, varPos0); + if (assetTypeLen < 0) { + throw ProtocolException.negativeLength("AssetType", assetTypeLen); + } + + if (assetTypeLen > 4096000) { + throw ProtocolException.stringTooLong("AssetType", assetTypeLen, 4096000); + } + + obj.assetType = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 21 + buf.getIntLE(offset + 13); + obj.path = AssetPath.deserialize(buf, varPos1); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 21 + buf.getIntLE(offset + 17); + int dataCount = VarInt.peek(buf, varPos2); + if (dataCount < 0) { + throw ProtocolException.negativeLength("Data", dataCount); + } + + if (dataCount > 4096000) { + throw ProtocolException.arrayTooLong("Data", dataCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + dataCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Data", varPos2 + varIntLen + dataCount * 1, buf.readableBytes()); + } + + obj.data = new byte[dataCount]; + + for (int i = 0; i < dataCount; i++) { + obj.data[i] = buf.getByte(varPos2 + varIntLen + i * 1); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 21; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 9); + int pos0 = offset + 21 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 13); + int pos1 = offset + 21 + fieldOffset1; + pos1 += AssetPath.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 17); + int pos2 = offset + 21 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + arrLen * 1; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.assetType != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.path != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.data != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + buf.writeIntLE(this.assetIndex); + int assetTypeOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int pathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int dataOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.assetType != null) { + buf.setIntLE(assetTypeOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.assetType, 4096000); + } else { + buf.setIntLE(assetTypeOffsetSlot, -1); + } + + if (this.path != null) { + buf.setIntLE(pathOffsetSlot, buf.writerIndex() - varBlockStart); + this.path.serialize(buf); + } else { + buf.setIntLE(pathOffsetSlot, -1); + } + + if (this.data != null) { + buf.setIntLE(dataOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.data.length > 4096000) { + throw ProtocolException.arrayTooLong("Data", this.data.length, 4096000); + } + + VarInt.write(buf, this.data.length); + + for (byte item : this.data) { + buf.writeByte(item); + } + } else { + buf.setIntLE(dataOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 21; + if (this.assetType != null) { + size += PacketIO.stringSize(this.assetType); + } + + if (this.path != null) { + size += this.path.computeSize(); + } + + if (this.data != null) { + size += VarInt.size(this.data.length) + this.data.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 21) { + return ValidationResult.error("Buffer too small: expected at least 21 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int assetTypeOffset = buffer.getIntLE(offset + 9); + if (assetTypeOffset < 0) { + return ValidationResult.error("Invalid offset for AssetType"); + } + + int pos = offset + 21 + assetTypeOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for AssetType"); + } + + int assetTypeLen = VarInt.peek(buffer, pos); + if (assetTypeLen < 0) { + return ValidationResult.error("Invalid string length for AssetType"); + } + + if (assetTypeLen > 4096000) { + return ValidationResult.error("AssetType exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += assetTypeLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading AssetType"); + } + } + + if ((nullBits & 2) != 0) { + int pathOffset = buffer.getIntLE(offset + 13); + if (pathOffset < 0) { + return ValidationResult.error("Invalid offset for Path"); + } + + int posx = offset + 21 + pathOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Path"); + } + + ValidationResult pathResult = AssetPath.validateStructure(buffer, posx); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + posx += AssetPath.computeBytesConsumed(buffer, posx); + } + + if ((nullBits & 4) != 0) { + int dataOffset = buffer.getIntLE(offset + 17); + if (dataOffset < 0) { + return ValidationResult.error("Invalid offset for Data"); + } + + int posxx = offset + 21 + dataOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Data"); + } + + int dataCount = VarInt.peek(buffer, posxx); + if (dataCount < 0) { + return ValidationResult.error("Invalid array count for Data"); + } + + if (dataCount > 4096000) { + return ValidationResult.error("Data exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += dataCount * 1; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Data"); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorUpdateAsset clone() { + AssetEditorUpdateAsset copy = new AssetEditorUpdateAsset(); + copy.token = this.token; + copy.assetType = this.assetType; + copy.path = this.path != null ? this.path.clone() : null; + copy.assetIndex = this.assetIndex; + copy.data = this.data != null ? Arrays.copyOf(this.data, this.data.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorUpdateAsset other) + ? false + : this.token == other.token + && Objects.equals(this.assetType, other.assetType) + && Objects.equals(this.path, other.path) + && this.assetIndex == other.assetIndex + && Arrays.equals(this.data, other.data); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.token); + result = 31 * result + Objects.hashCode(this.assetType); + result = 31 * result + Objects.hashCode(this.path); + result = 31 * result + Integer.hashCode(this.assetIndex); + return 31 * result + Arrays.hashCode(this.data); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateAssetPack.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateAssetPack.java new file mode 100644 index 0000000..256d0f8 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateAssetPack.java @@ -0,0 +1,217 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorUpdateAssetPack implements Packet { + public static final int PACKET_ID = 315; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + @Nullable + public AssetPackManifest manifest; + + @Override + public int getId() { + return 315; + } + + public AssetEditorUpdateAssetPack() { + } + + public AssetEditorUpdateAssetPack(@Nullable String id, @Nullable AssetPackManifest manifest) { + this.id = id; + this.manifest = manifest; + } + + public AssetEditorUpdateAssetPack(@Nonnull AssetEditorUpdateAssetPack other) { + this.id = other.id; + this.manifest = other.manifest; + } + + @Nonnull + public static AssetEditorUpdateAssetPack deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorUpdateAssetPack obj = new AssetEditorUpdateAssetPack(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + obj.manifest = AssetPackManifest.deserialize(buf, varPos1); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + pos1 += AssetPackManifest.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.manifest != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int manifestOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.manifest != null) { + buf.setIntLE(manifestOffsetSlot, buf.writerIndex() - varBlockStart); + this.manifest.serialize(buf); + } else { + buf.setIntLE(manifestOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 9; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.manifest != null) { + size += this.manifest.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 1); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 9 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int manifestOffset = buffer.getIntLE(offset + 5); + if (manifestOffset < 0) { + return ValidationResult.error("Invalid offset for Manifest"); + } + + int posx = offset + 9 + manifestOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Manifest"); + } + + ValidationResult manifestResult = AssetPackManifest.validateStructure(buffer, posx); + if (!manifestResult.isValid()) { + return ValidationResult.error("Invalid Manifest: " + manifestResult.error()); + } + + posx += AssetPackManifest.computeBytesConsumed(buffer, posx); + } + + return ValidationResult.OK; + } + } + + public AssetEditorUpdateAssetPack clone() { + AssetEditorUpdateAssetPack copy = new AssetEditorUpdateAssetPack(); + copy.id = this.id; + copy.manifest = this.manifest != null ? this.manifest.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorUpdateAssetPack other) ? false : Objects.equals(this.id, other.id) && Objects.equals(this.manifest, other.manifest); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.manifest); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateJsonAsset.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateJsonAsset.java new file mode 100644 index 0000000..a612673 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateJsonAsset.java @@ -0,0 +1,349 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorUpdateJsonAsset implements Packet { + public static final int PACKET_ID = 323; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 21; + public static final int MAX_SIZE = 1677721600; + public int token; + @Nullable + public String assetType; + @Nullable + public AssetPath path; + public int assetIndex = Integer.MIN_VALUE; + @Nullable + public JsonUpdateCommand[] commands; + + @Override + public int getId() { + return 323; + } + + public AssetEditorUpdateJsonAsset() { + } + + public AssetEditorUpdateJsonAsset(int token, @Nullable String assetType, @Nullable AssetPath path, int assetIndex, @Nullable JsonUpdateCommand[] commands) { + this.token = token; + this.assetType = assetType; + this.path = path; + this.assetIndex = assetIndex; + this.commands = commands; + } + + public AssetEditorUpdateJsonAsset(@Nonnull AssetEditorUpdateJsonAsset other) { + this.token = other.token; + this.assetType = other.assetType; + this.path = other.path; + this.assetIndex = other.assetIndex; + this.commands = other.commands; + } + + @Nonnull + public static AssetEditorUpdateJsonAsset deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorUpdateJsonAsset obj = new AssetEditorUpdateJsonAsset(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + obj.assetIndex = buf.getIntLE(offset + 5); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 21 + buf.getIntLE(offset + 9); + int assetTypeLen = VarInt.peek(buf, varPos0); + if (assetTypeLen < 0) { + throw ProtocolException.negativeLength("AssetType", assetTypeLen); + } + + if (assetTypeLen > 4096000) { + throw ProtocolException.stringTooLong("AssetType", assetTypeLen, 4096000); + } + + obj.assetType = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 21 + buf.getIntLE(offset + 13); + obj.path = AssetPath.deserialize(buf, varPos1); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 21 + buf.getIntLE(offset + 17); + int commandsCount = VarInt.peek(buf, varPos2); + if (commandsCount < 0) { + throw ProtocolException.negativeLength("Commands", commandsCount); + } + + if (commandsCount > 4096000) { + throw ProtocolException.arrayTooLong("Commands", commandsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + commandsCount * 7L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Commands", varPos2 + varIntLen + commandsCount * 7, buf.readableBytes()); + } + + obj.commands = new JsonUpdateCommand[commandsCount]; + int elemPos = varPos2 + varIntLen; + + for (int i = 0; i < commandsCount; i++) { + obj.commands[i] = JsonUpdateCommand.deserialize(buf, elemPos); + elemPos += JsonUpdateCommand.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 21; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 9); + int pos0 = offset + 21 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 13); + int pos1 = offset + 21 + fieldOffset1; + pos1 += AssetPath.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 17); + int pos2 = offset + 21 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2); + + for (int i = 0; i < arrLen; i++) { + pos2 += JsonUpdateCommand.computeBytesConsumed(buf, pos2); + } + + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.assetType != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.path != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.commands != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + buf.writeIntLE(this.assetIndex); + int assetTypeOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int pathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int commandsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.assetType != null) { + buf.setIntLE(assetTypeOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.assetType, 4096000); + } else { + buf.setIntLE(assetTypeOffsetSlot, -1); + } + + if (this.path != null) { + buf.setIntLE(pathOffsetSlot, buf.writerIndex() - varBlockStart); + this.path.serialize(buf); + } else { + buf.setIntLE(pathOffsetSlot, -1); + } + + if (this.commands != null) { + buf.setIntLE(commandsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.commands.length > 4096000) { + throw ProtocolException.arrayTooLong("Commands", this.commands.length, 4096000); + } + + VarInt.write(buf, this.commands.length); + + for (JsonUpdateCommand item : this.commands) { + item.serialize(buf); + } + } else { + buf.setIntLE(commandsOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 21; + if (this.assetType != null) { + size += PacketIO.stringSize(this.assetType); + } + + if (this.path != null) { + size += this.path.computeSize(); + } + + if (this.commands != null) { + int commandsSize = 0; + + for (JsonUpdateCommand elem : this.commands) { + commandsSize += elem.computeSize(); + } + + size += VarInt.size(this.commands.length) + commandsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 21) { + return ValidationResult.error("Buffer too small: expected at least 21 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int assetTypeOffset = buffer.getIntLE(offset + 9); + if (assetTypeOffset < 0) { + return ValidationResult.error("Invalid offset for AssetType"); + } + + int pos = offset + 21 + assetTypeOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for AssetType"); + } + + int assetTypeLen = VarInt.peek(buffer, pos); + if (assetTypeLen < 0) { + return ValidationResult.error("Invalid string length for AssetType"); + } + + if (assetTypeLen > 4096000) { + return ValidationResult.error("AssetType exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += assetTypeLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading AssetType"); + } + } + + if ((nullBits & 2) != 0) { + int pathOffset = buffer.getIntLE(offset + 13); + if (pathOffset < 0) { + return ValidationResult.error("Invalid offset for Path"); + } + + int posx = offset + 21 + pathOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Path"); + } + + ValidationResult pathResult = AssetPath.validateStructure(buffer, posx); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + posx += AssetPath.computeBytesConsumed(buffer, posx); + } + + if ((nullBits & 4) != 0) { + int commandsOffset = buffer.getIntLE(offset + 17); + if (commandsOffset < 0) { + return ValidationResult.error("Invalid offset for Commands"); + } + + int posxx = offset + 21 + commandsOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Commands"); + } + + int commandsCount = VarInt.peek(buffer, posxx); + if (commandsCount < 0) { + return ValidationResult.error("Invalid array count for Commands"); + } + + if (commandsCount > 4096000) { + return ValidationResult.error("Commands exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + + for (int i = 0; i < commandsCount; i++) { + ValidationResult structResult = JsonUpdateCommand.validateStructure(buffer, posxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid JsonUpdateCommand in Commands[" + i + "]: " + structResult.error()); + } + + posxx += JsonUpdateCommand.computeBytesConsumed(buffer, posxx); + } + } + + return ValidationResult.OK; + } + } + + public AssetEditorUpdateJsonAsset clone() { + AssetEditorUpdateJsonAsset copy = new AssetEditorUpdateJsonAsset(); + copy.token = this.token; + copy.assetType = this.assetType; + copy.path = this.path != null ? this.path.clone() : null; + copy.assetIndex = this.assetIndex; + copy.commands = this.commands != null ? Arrays.stream(this.commands).map(e -> e.clone()).toArray(JsonUpdateCommand[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorUpdateJsonAsset other) + ? false + : this.token == other.token + && Objects.equals(this.assetType, other.assetType) + && Objects.equals(this.path, other.path) + && this.assetIndex == other.assetIndex + && Arrays.equals((Object[])this.commands, (Object[])other.commands); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.token); + result = 31 * result + Objects.hashCode(this.assetType); + result = 31 * result + Objects.hashCode(this.path); + result = 31 * result + Integer.hashCode(this.assetIndex); + return 31 * result + Arrays.hashCode((Object[])this.commands); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateModelPreview.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateModelPreview.java new file mode 100644 index 0000000..4f3fb2c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateModelPreview.java @@ -0,0 +1,279 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.BlockType; +import com.hypixel.hytale.protocol.Model; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetEditorUpdateModelPreview implements Packet { + public static final int PACKET_ID = 355; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 30; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 42; + public static final int MAX_SIZE = 1677721600; + @Nullable + public AssetPath assetPath; + @Nullable + public Model model; + @Nullable + public BlockType block; + @Nullable + public AssetEditorPreviewCameraSettings camera; + + @Override + public int getId() { + return 355; + } + + public AssetEditorUpdateModelPreview() { + } + + public AssetEditorUpdateModelPreview( + @Nullable AssetPath assetPath, @Nullable Model model, @Nullable BlockType block, @Nullable AssetEditorPreviewCameraSettings camera + ) { + this.assetPath = assetPath; + this.model = model; + this.block = block; + this.camera = camera; + } + + public AssetEditorUpdateModelPreview(@Nonnull AssetEditorUpdateModelPreview other) { + this.assetPath = other.assetPath; + this.model = other.model; + this.block = other.block; + this.camera = other.camera; + } + + @Nonnull + public static AssetEditorUpdateModelPreview deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorUpdateModelPreview obj = new AssetEditorUpdateModelPreview(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 8) != 0) { + obj.camera = AssetEditorPreviewCameraSettings.deserialize(buf, offset + 1); + } + + if ((nullBits & 1) != 0) { + int varPos0 = offset + 42 + buf.getIntLE(offset + 30); + obj.assetPath = AssetPath.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 42 + buf.getIntLE(offset + 34); + obj.model = Model.deserialize(buf, varPos1); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 42 + buf.getIntLE(offset + 38); + obj.block = BlockType.deserialize(buf, varPos2); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 42; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 30); + int pos0 = offset + 42 + fieldOffset0; + pos0 += AssetPath.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 34); + int pos1 = offset + 42 + fieldOffset1; + pos1 += Model.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 38); + int pos2 = offset + 42 + fieldOffset2; + pos2 += BlockType.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.assetPath != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.model != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.block != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.camera != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + if (this.camera != null) { + this.camera.serialize(buf); + } else { + buf.writeZero(29); + } + + int assetPathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int modelOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int blockOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.assetPath != null) { + buf.setIntLE(assetPathOffsetSlot, buf.writerIndex() - varBlockStart); + this.assetPath.serialize(buf); + } else { + buf.setIntLE(assetPathOffsetSlot, -1); + } + + if (this.model != null) { + buf.setIntLE(modelOffsetSlot, buf.writerIndex() - varBlockStart); + this.model.serialize(buf); + } else { + buf.setIntLE(modelOffsetSlot, -1); + } + + if (this.block != null) { + buf.setIntLE(blockOffsetSlot, buf.writerIndex() - varBlockStart); + this.block.serialize(buf); + } else { + buf.setIntLE(blockOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 42; + if (this.assetPath != null) { + size += this.assetPath.computeSize(); + } + + if (this.model != null) { + size += this.model.computeSize(); + } + + if (this.block != null) { + size += this.block.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 42) { + return ValidationResult.error("Buffer too small: expected at least 42 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int assetPathOffset = buffer.getIntLE(offset + 30); + if (assetPathOffset < 0) { + return ValidationResult.error("Invalid offset for AssetPath"); + } + + int pos = offset + 42 + assetPathOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for AssetPath"); + } + + ValidationResult assetPathResult = AssetPath.validateStructure(buffer, pos); + if (!assetPathResult.isValid()) { + return ValidationResult.error("Invalid AssetPath: " + assetPathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int modelOffset = buffer.getIntLE(offset + 34); + if (modelOffset < 0) { + return ValidationResult.error("Invalid offset for Model"); + } + + int posx = offset + 42 + modelOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Model"); + } + + ValidationResult modelResult = Model.validateStructure(buffer, posx); + if (!modelResult.isValid()) { + return ValidationResult.error("Invalid Model: " + modelResult.error()); + } + + posx += Model.computeBytesConsumed(buffer, posx); + } + + if ((nullBits & 4) != 0) { + int blockOffset = buffer.getIntLE(offset + 38); + if (blockOffset < 0) { + return ValidationResult.error("Invalid offset for Block"); + } + + int posxx = offset + 42 + blockOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Block"); + } + + ValidationResult blockResult = BlockType.validateStructure(buffer, posxx); + if (!blockResult.isValid()) { + return ValidationResult.error("Invalid Block: " + blockResult.error()); + } + + posxx += BlockType.computeBytesConsumed(buffer, posxx); + } + + return ValidationResult.OK; + } + } + + public AssetEditorUpdateModelPreview clone() { + AssetEditorUpdateModelPreview copy = new AssetEditorUpdateModelPreview(); + copy.assetPath = this.assetPath != null ? this.assetPath.clone() : null; + copy.model = this.model != null ? this.model.clone() : null; + copy.block = this.block != null ? this.block.clone() : null; + copy.camera = this.camera != null ? this.camera.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorUpdateModelPreview other) + ? false + : Objects.equals(this.assetPath, other.assetPath) + && Objects.equals(this.model, other.model) + && Objects.equals(this.block, other.block) + && Objects.equals(this.camera, other.camera); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.assetPath, this.model, this.block, this.camera); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateSecondsPerGameDay.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateSecondsPerGameDay.java new file mode 100644 index 0000000..6eff03f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateSecondsPerGameDay.java @@ -0,0 +1,87 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class AssetEditorUpdateSecondsPerGameDay implements Packet { + public static final int PACKET_ID = 353; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public int daytimeDurationSeconds; + public int nighttimeDurationSeconds; + + @Override + public int getId() { + return 353; + } + + public AssetEditorUpdateSecondsPerGameDay() { + } + + public AssetEditorUpdateSecondsPerGameDay(int daytimeDurationSeconds, int nighttimeDurationSeconds) { + this.daytimeDurationSeconds = daytimeDurationSeconds; + this.nighttimeDurationSeconds = nighttimeDurationSeconds; + } + + public AssetEditorUpdateSecondsPerGameDay(@Nonnull AssetEditorUpdateSecondsPerGameDay other) { + this.daytimeDurationSeconds = other.daytimeDurationSeconds; + this.nighttimeDurationSeconds = other.nighttimeDurationSeconds; + } + + @Nonnull + public static AssetEditorUpdateSecondsPerGameDay deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorUpdateSecondsPerGameDay obj = new AssetEditorUpdateSecondsPerGameDay(); + obj.daytimeDurationSeconds = buf.getIntLE(offset + 0); + obj.nighttimeDurationSeconds = buf.getIntLE(offset + 4); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.daytimeDurationSeconds); + buf.writeIntLE(this.nighttimeDurationSeconds); + } + + @Override + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public AssetEditorUpdateSecondsPerGameDay clone() { + AssetEditorUpdateSecondsPerGameDay copy = new AssetEditorUpdateSecondsPerGameDay(); + copy.daytimeDurationSeconds = this.daytimeDurationSeconds; + copy.nighttimeDurationSeconds = this.nighttimeDurationSeconds; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetEditorUpdateSecondsPerGameDay other) + ? false + : this.daytimeDurationSeconds == other.daytimeDurationSeconds && this.nighttimeDurationSeconds == other.nighttimeDurationSeconds; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.daytimeDurationSeconds, this.nighttimeDurationSeconds); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateWeatherPreviewLock.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateWeatherPreviewLock.java new file mode 100644 index 0000000..e103dda --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetEditorUpdateWeatherPreviewLock.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class AssetEditorUpdateWeatherPreviewLock implements Packet { + public static final int PACKET_ID = 354; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean locked; + + @Override + public int getId() { + return 354; + } + + public AssetEditorUpdateWeatherPreviewLock() { + } + + public AssetEditorUpdateWeatherPreviewLock(boolean locked) { + this.locked = locked; + } + + public AssetEditorUpdateWeatherPreviewLock(@Nonnull AssetEditorUpdateWeatherPreviewLock other) { + this.locked = other.locked; + } + + @Nonnull + public static AssetEditorUpdateWeatherPreviewLock deserialize(@Nonnull ByteBuf buf, int offset) { + AssetEditorUpdateWeatherPreviewLock obj = new AssetEditorUpdateWeatherPreviewLock(); + obj.locked = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.locked ? 1 : 0); + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public AssetEditorUpdateWeatherPreviewLock clone() { + AssetEditorUpdateWeatherPreviewLock copy = new AssetEditorUpdateWeatherPreviewLock(); + copy.locked = this.locked; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetEditorUpdateWeatherPreviewLock other ? this.locked == other.locked : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.locked); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetInfo.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetInfo.java new file mode 100644 index 0000000..d306553 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetInfo.java @@ -0,0 +1,294 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetInfo { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 11; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 23; + public static final int MAX_SIZE = 81920066; + @Nullable + public AssetPath path; + @Nullable + public AssetPath oldPath; + public boolean isDeleted; + public boolean isNew; + public long lastModificationDate; + @Nullable + public String lastModificationUsername; + + public AssetInfo() { + } + + public AssetInfo( + @Nullable AssetPath path, + @Nullable AssetPath oldPath, + boolean isDeleted, + boolean isNew, + long lastModificationDate, + @Nullable String lastModificationUsername + ) { + this.path = path; + this.oldPath = oldPath; + this.isDeleted = isDeleted; + this.isNew = isNew; + this.lastModificationDate = lastModificationDate; + this.lastModificationUsername = lastModificationUsername; + } + + public AssetInfo(@Nonnull AssetInfo other) { + this.path = other.path; + this.oldPath = other.oldPath; + this.isDeleted = other.isDeleted; + this.isNew = other.isNew; + this.lastModificationDate = other.lastModificationDate; + this.lastModificationUsername = other.lastModificationUsername; + } + + @Nonnull + public static AssetInfo deserialize(@Nonnull ByteBuf buf, int offset) { + AssetInfo obj = new AssetInfo(); + byte nullBits = buf.getByte(offset); + obj.isDeleted = buf.getByte(offset + 1) != 0; + obj.isNew = buf.getByte(offset + 2) != 0; + obj.lastModificationDate = buf.getLongLE(offset + 3); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 23 + buf.getIntLE(offset + 11); + obj.path = AssetPath.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 23 + buf.getIntLE(offset + 15); + obj.oldPath = AssetPath.deserialize(buf, varPos1); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 23 + buf.getIntLE(offset + 19); + int lastModificationUsernameLen = VarInt.peek(buf, varPos2); + if (lastModificationUsernameLen < 0) { + throw ProtocolException.negativeLength("LastModificationUsername", lastModificationUsernameLen); + } + + if (lastModificationUsernameLen > 4096000) { + throw ProtocolException.stringTooLong("LastModificationUsername", lastModificationUsernameLen, 4096000); + } + + obj.lastModificationUsername = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 23; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 11); + int pos0 = offset + 23 + fieldOffset0; + pos0 += AssetPath.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 15); + int pos1 = offset + 23 + fieldOffset1; + pos1 += AssetPath.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 19); + int pos2 = offset + 23 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.oldPath != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.lastModificationUsername != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + buf.writeByte(this.isDeleted ? 1 : 0); + buf.writeByte(this.isNew ? 1 : 0); + buf.writeLongLE(this.lastModificationDate); + int pathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int oldPathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int lastModificationUsernameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.path != null) { + buf.setIntLE(pathOffsetSlot, buf.writerIndex() - varBlockStart); + this.path.serialize(buf); + } else { + buf.setIntLE(pathOffsetSlot, -1); + } + + if (this.oldPath != null) { + buf.setIntLE(oldPathOffsetSlot, buf.writerIndex() - varBlockStart); + this.oldPath.serialize(buf); + } else { + buf.setIntLE(oldPathOffsetSlot, -1); + } + + if (this.lastModificationUsername != null) { + buf.setIntLE(lastModificationUsernameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.lastModificationUsername, 4096000); + } else { + buf.setIntLE(lastModificationUsernameOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 23; + if (this.path != null) { + size += this.path.computeSize(); + } + + if (this.oldPath != null) { + size += this.oldPath.computeSize(); + } + + if (this.lastModificationUsername != null) { + size += PacketIO.stringSize(this.lastModificationUsername); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 23) { + return ValidationResult.error("Buffer too small: expected at least 23 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int pathOffset = buffer.getIntLE(offset + 11); + if (pathOffset < 0) { + return ValidationResult.error("Invalid offset for Path"); + } + + int pos = offset + 23 + pathOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Path"); + } + + ValidationResult pathResult = AssetPath.validateStructure(buffer, pos); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int oldPathOffset = buffer.getIntLE(offset + 15); + if (oldPathOffset < 0) { + return ValidationResult.error("Invalid offset for OldPath"); + } + + int posx = offset + 23 + oldPathOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for OldPath"); + } + + ValidationResult oldPathResult = AssetPath.validateStructure(buffer, posx); + if (!oldPathResult.isValid()) { + return ValidationResult.error("Invalid OldPath: " + oldPathResult.error()); + } + + posx += AssetPath.computeBytesConsumed(buffer, posx); + } + + if ((nullBits & 4) != 0) { + int lastModificationUsernameOffset = buffer.getIntLE(offset + 19); + if (lastModificationUsernameOffset < 0) { + return ValidationResult.error("Invalid offset for LastModificationUsername"); + } + + int posxx = offset + 23 + lastModificationUsernameOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for LastModificationUsername"); + } + + int lastModificationUsernameLen = VarInt.peek(buffer, posxx); + if (lastModificationUsernameLen < 0) { + return ValidationResult.error("Invalid string length for LastModificationUsername"); + } + + if (lastModificationUsernameLen > 4096000) { + return ValidationResult.error("LastModificationUsername exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += lastModificationUsernameLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading LastModificationUsername"); + } + } + + return ValidationResult.OK; + } + } + + public AssetInfo clone() { + AssetInfo copy = new AssetInfo(); + copy.path = this.path != null ? this.path.clone() : null; + copy.oldPath = this.oldPath != null ? this.oldPath.clone() : null; + copy.isDeleted = this.isDeleted; + copy.isNew = this.isNew; + copy.lastModificationDate = this.lastModificationDate; + copy.lastModificationUsername = this.lastModificationUsername; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetInfo other) + ? false + : Objects.equals(this.path, other.path) + && Objects.equals(this.oldPath, other.oldPath) + && this.isDeleted == other.isDeleted + && this.isNew == other.isNew + && this.lastModificationDate == other.lastModificationDate + && Objects.equals(this.lastModificationUsername, other.lastModificationUsername); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.path, this.oldPath, this.isDeleted, this.isNew, this.lastModificationDate, this.lastModificationUsername); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetPackManifest.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetPackManifest.java new file mode 100644 index 0000000..11172d6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetPackManifest.java @@ -0,0 +1,573 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetPackManifest { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 6; + public static final int VARIABLE_BLOCK_START = 25; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String name; + @Nullable + public String group; + @Nullable + public String website; + @Nullable + public String description; + @Nullable + public String version; + @Nullable + public AuthorInfo[] authors; + + public AssetPackManifest() { + } + + public AssetPackManifest( + @Nullable String name, + @Nullable String group, + @Nullable String website, + @Nullable String description, + @Nullable String version, + @Nullable AuthorInfo[] authors + ) { + this.name = name; + this.group = group; + this.website = website; + this.description = description; + this.version = version; + this.authors = authors; + } + + public AssetPackManifest(@Nonnull AssetPackManifest other) { + this.name = other.name; + this.group = other.group; + this.website = other.website; + this.description = other.description; + this.version = other.version; + this.authors = other.authors; + } + + @Nonnull + public static AssetPackManifest deserialize(@Nonnull ByteBuf buf, int offset) { + AssetPackManifest obj = new AssetPackManifest(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 25 + buf.getIntLE(offset + 1); + int nameLen = VarInt.peek(buf, varPos0); + if (nameLen < 0) { + throw ProtocolException.negativeLength("Name", nameLen); + } + + if (nameLen > 4096000) { + throw ProtocolException.stringTooLong("Name", nameLen, 4096000); + } + + obj.name = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 25 + buf.getIntLE(offset + 5); + int groupLen = VarInt.peek(buf, varPos1); + if (groupLen < 0) { + throw ProtocolException.negativeLength("Group", groupLen); + } + + if (groupLen > 4096000) { + throw ProtocolException.stringTooLong("Group", groupLen, 4096000); + } + + obj.group = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 25 + buf.getIntLE(offset + 9); + int websiteLen = VarInt.peek(buf, varPos2); + if (websiteLen < 0) { + throw ProtocolException.negativeLength("Website", websiteLen); + } + + if (websiteLen > 4096000) { + throw ProtocolException.stringTooLong("Website", websiteLen, 4096000); + } + + obj.website = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 25 + buf.getIntLE(offset + 13); + int descriptionLen = VarInt.peek(buf, varPos3); + if (descriptionLen < 0) { + throw ProtocolException.negativeLength("Description", descriptionLen); + } + + if (descriptionLen > 4096000) { + throw ProtocolException.stringTooLong("Description", descriptionLen, 4096000); + } + + obj.description = PacketIO.readVarString(buf, varPos3, PacketIO.UTF8); + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 25 + buf.getIntLE(offset + 17); + int versionLen = VarInt.peek(buf, varPos4); + if (versionLen < 0) { + throw ProtocolException.negativeLength("Version", versionLen); + } + + if (versionLen > 4096000) { + throw ProtocolException.stringTooLong("Version", versionLen, 4096000); + } + + obj.version = PacketIO.readVarString(buf, varPos4, PacketIO.UTF8); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 25 + buf.getIntLE(offset + 21); + int authorsCount = VarInt.peek(buf, varPos5); + if (authorsCount < 0) { + throw ProtocolException.negativeLength("Authors", authorsCount); + } + + if (authorsCount > 4096000) { + throw ProtocolException.arrayTooLong("Authors", authorsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + if (varPos5 + varIntLen + authorsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Authors", varPos5 + varIntLen + authorsCount * 1, buf.readableBytes()); + } + + obj.authors = new AuthorInfo[authorsCount]; + int elemPos = varPos5 + varIntLen; + + for (int i = 0; i < authorsCount; i++) { + obj.authors[i] = AuthorInfo.deserialize(buf, elemPos); + elemPos += AuthorInfo.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 25; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 25 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 25 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 9); + int pos2 = offset + 25 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 13); + int pos3 = offset + 25 + fieldOffset3; + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 17); + int pos4 = offset + 25 + fieldOffset4; + int sl = VarInt.peek(buf, pos4); + pos4 += VarInt.length(buf, pos4) + sl; + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 21); + int pos5 = offset + 25 + fieldOffset5; + int arrLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5); + + for (int i = 0; i < arrLen; i++) { + pos5 += AuthorInfo.computeBytesConsumed(buf, pos5); + } + + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.name != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.group != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.website != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.description != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.version != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.authors != null) { + nullBits = (byte)(nullBits | 32); + } + + buf.writeByte(nullBits); + int nameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int groupOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int websiteOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int descriptionOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int versionOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int authorsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.name != null) { + buf.setIntLE(nameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.name, 4096000); + } else { + buf.setIntLE(nameOffsetSlot, -1); + } + + if (this.group != null) { + buf.setIntLE(groupOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.group, 4096000); + } else { + buf.setIntLE(groupOffsetSlot, -1); + } + + if (this.website != null) { + buf.setIntLE(websiteOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.website, 4096000); + } else { + buf.setIntLE(websiteOffsetSlot, -1); + } + + if (this.description != null) { + buf.setIntLE(descriptionOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.description, 4096000); + } else { + buf.setIntLE(descriptionOffsetSlot, -1); + } + + if (this.version != null) { + buf.setIntLE(versionOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.version, 4096000); + } else { + buf.setIntLE(versionOffsetSlot, -1); + } + + if (this.authors != null) { + buf.setIntLE(authorsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.authors.length > 4096000) { + throw ProtocolException.arrayTooLong("Authors", this.authors.length, 4096000); + } + + VarInt.write(buf, this.authors.length); + + for (AuthorInfo item : this.authors) { + item.serialize(buf); + } + } else { + buf.setIntLE(authorsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 25; + if (this.name != null) { + size += PacketIO.stringSize(this.name); + } + + if (this.group != null) { + size += PacketIO.stringSize(this.group); + } + + if (this.website != null) { + size += PacketIO.stringSize(this.website); + } + + if (this.description != null) { + size += PacketIO.stringSize(this.description); + } + + if (this.version != null) { + size += PacketIO.stringSize(this.version); + } + + if (this.authors != null) { + int authorsSize = 0; + + for (AuthorInfo elem : this.authors) { + authorsSize += elem.computeSize(); + } + + size += VarInt.size(this.authors.length) + authorsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 25) { + return ValidationResult.error("Buffer too small: expected at least 25 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int nameOffset = buffer.getIntLE(offset + 1); + if (nameOffset < 0) { + return ValidationResult.error("Invalid offset for Name"); + } + + int pos = offset + 25 + nameOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Name"); + } + + int nameLen = VarInt.peek(buffer, pos); + if (nameLen < 0) { + return ValidationResult.error("Invalid string length for Name"); + } + + if (nameLen > 4096000) { + return ValidationResult.error("Name exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += nameLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Name"); + } + } + + if ((nullBits & 2) != 0) { + int groupOffset = buffer.getIntLE(offset + 5); + if (groupOffset < 0) { + return ValidationResult.error("Invalid offset for Group"); + } + + int posx = offset + 25 + groupOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Group"); + } + + int groupLen = VarInt.peek(buffer, posx); + if (groupLen < 0) { + return ValidationResult.error("Invalid string length for Group"); + } + + if (groupLen > 4096000) { + return ValidationResult.error("Group exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += groupLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Group"); + } + } + + if ((nullBits & 4) != 0) { + int websiteOffset = buffer.getIntLE(offset + 9); + if (websiteOffset < 0) { + return ValidationResult.error("Invalid offset for Website"); + } + + int posxx = offset + 25 + websiteOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Website"); + } + + int websiteLen = VarInt.peek(buffer, posxx); + if (websiteLen < 0) { + return ValidationResult.error("Invalid string length for Website"); + } + + if (websiteLen > 4096000) { + return ValidationResult.error("Website exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += websiteLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Website"); + } + } + + if ((nullBits & 8) != 0) { + int descriptionOffset = buffer.getIntLE(offset + 13); + if (descriptionOffset < 0) { + return ValidationResult.error("Invalid offset for Description"); + } + + int posxxx = offset + 25 + descriptionOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Description"); + } + + int descriptionLen = VarInt.peek(buffer, posxxx); + if (descriptionLen < 0) { + return ValidationResult.error("Invalid string length for Description"); + } + + if (descriptionLen > 4096000) { + return ValidationResult.error("Description exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += descriptionLen; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Description"); + } + } + + if ((nullBits & 16) != 0) { + int versionOffset = buffer.getIntLE(offset + 17); + if (versionOffset < 0) { + return ValidationResult.error("Invalid offset for Version"); + } + + int posxxxx = offset + 25 + versionOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Version"); + } + + int versionLen = VarInt.peek(buffer, posxxxx); + if (versionLen < 0) { + return ValidationResult.error("Invalid string length for Version"); + } + + if (versionLen > 4096000) { + return ValidationResult.error("Version exceeds max length 4096000"); + } + + posxxxx += VarInt.length(buffer, posxxxx); + posxxxx += versionLen; + if (posxxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Version"); + } + } + + if ((nullBits & 32) != 0) { + int authorsOffset = buffer.getIntLE(offset + 21); + if (authorsOffset < 0) { + return ValidationResult.error("Invalid offset for Authors"); + } + + int posxxxxx = offset + 25 + authorsOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Authors"); + } + + int authorsCount = VarInt.peek(buffer, posxxxxx); + if (authorsCount < 0) { + return ValidationResult.error("Invalid array count for Authors"); + } + + if (authorsCount > 4096000) { + return ValidationResult.error("Authors exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + + for (int i = 0; i < authorsCount; i++) { + ValidationResult structResult = AuthorInfo.validateStructure(buffer, posxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid AuthorInfo in Authors[" + i + "]: " + structResult.error()); + } + + posxxxxx += AuthorInfo.computeBytesConsumed(buffer, posxxxxx); + } + } + + return ValidationResult.OK; + } + } + + public AssetPackManifest clone() { + AssetPackManifest copy = new AssetPackManifest(); + copy.name = this.name; + copy.group = this.group; + copy.website = this.website; + copy.description = this.description; + copy.version = this.version; + copy.authors = this.authors != null ? Arrays.stream(this.authors).map(e -> e.clone()).toArray(AuthorInfo[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetPackManifest other) + ? false + : Objects.equals(this.name, other.name) + && Objects.equals(this.group, other.group) + && Objects.equals(this.website, other.website) + && Objects.equals(this.description, other.description) + && Objects.equals(this.version, other.version) + && Arrays.equals((Object[])this.authors, (Object[])other.authors); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.name); + result = 31 * result + Objects.hashCode(this.group); + result = 31 * result + Objects.hashCode(this.website); + result = 31 * result + Objects.hashCode(this.description); + result = 31 * result + Objects.hashCode(this.version); + return 31 * result + Arrays.hashCode((Object[])this.authors); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetPath.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetPath.java new file mode 100644 index 0000000..7e5d235 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AssetPath.java @@ -0,0 +1,225 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetPath { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 32768019; + @Nullable + public String pack; + @Nullable + public String path; + + public AssetPath() { + } + + public AssetPath(@Nullable String pack, @Nullable String path) { + this.pack = pack; + this.path = path; + } + + public AssetPath(@Nonnull AssetPath other) { + this.pack = other.pack; + this.path = other.path; + } + + @Nonnull + public static AssetPath deserialize(@Nonnull ByteBuf buf, int offset) { + AssetPath obj = new AssetPath(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int packLen = VarInt.peek(buf, varPos0); + if (packLen < 0) { + throw ProtocolException.negativeLength("Pack", packLen); + } + + if (packLen > 4096000) { + throw ProtocolException.stringTooLong("Pack", packLen, 4096000); + } + + obj.pack = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int pathLen = VarInt.peek(buf, varPos1); + if (pathLen < 0) { + throw ProtocolException.negativeLength("Path", pathLen); + } + + if (pathLen > 4096000) { + throw ProtocolException.stringTooLong("Path", pathLen, 4096000); + } + + obj.path = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.pack != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.path != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int packOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int pathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.pack != null) { + buf.setIntLE(packOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.pack, 4096000); + } else { + buf.setIntLE(packOffsetSlot, -1); + } + + if (this.path != null) { + buf.setIntLE(pathOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.path, 4096000); + } else { + buf.setIntLE(pathOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 9; + if (this.pack != null) { + size += PacketIO.stringSize(this.pack); + } + + if (this.path != null) { + size += PacketIO.stringSize(this.path); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int packOffset = buffer.getIntLE(offset + 1); + if (packOffset < 0) { + return ValidationResult.error("Invalid offset for Pack"); + } + + int pos = offset + 9 + packOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Pack"); + } + + int packLen = VarInt.peek(buffer, pos); + if (packLen < 0) { + return ValidationResult.error("Invalid string length for Pack"); + } + + if (packLen > 4096000) { + return ValidationResult.error("Pack exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += packLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Pack"); + } + } + + if ((nullBits & 2) != 0) { + int pathOffset = buffer.getIntLE(offset + 5); + if (pathOffset < 0) { + return ValidationResult.error("Invalid offset for Path"); + } + + int posx = offset + 9 + pathOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Path"); + } + + int pathLen = VarInt.peek(buffer, posx); + if (pathLen < 0) { + return ValidationResult.error("Invalid string length for Path"); + } + + if (pathLen > 4096000) { + return ValidationResult.error("Path exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += pathLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Path"); + } + } + + return ValidationResult.OK; + } + } + + public AssetPath clone() { + AssetPath copy = new AssetPath(); + copy.pack = this.pack; + copy.path = this.path; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetPath other) ? false : Objects.equals(this.pack, other.pack) && Objects.equals(this.path, other.path); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.pack, this.path); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/AuthorInfo.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/AuthorInfo.java new file mode 100644 index 0000000..68c2963 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/AuthorInfo.java @@ -0,0 +1,300 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AuthorInfo { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 49152028; + @Nullable + public String name; + @Nullable + public String email; + @Nullable + public String url; + + public AuthorInfo() { + } + + public AuthorInfo(@Nullable String name, @Nullable String email, @Nullable String url) { + this.name = name; + this.email = email; + this.url = url; + } + + public AuthorInfo(@Nonnull AuthorInfo other) { + this.name = other.name; + this.email = other.email; + this.url = other.url; + } + + @Nonnull + public static AuthorInfo deserialize(@Nonnull ByteBuf buf, int offset) { + AuthorInfo obj = new AuthorInfo(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 1); + int nameLen = VarInt.peek(buf, varPos0); + if (nameLen < 0) { + throw ProtocolException.negativeLength("Name", nameLen); + } + + if (nameLen > 4096000) { + throw ProtocolException.stringTooLong("Name", nameLen, 4096000); + } + + obj.name = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 5); + int emailLen = VarInt.peek(buf, varPos1); + if (emailLen < 0) { + throw ProtocolException.negativeLength("Email", emailLen); + } + + if (emailLen > 4096000) { + throw ProtocolException.stringTooLong("Email", emailLen, 4096000); + } + + obj.email = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 13 + buf.getIntLE(offset + 9); + int urlLen = VarInt.peek(buf, varPos2); + if (urlLen < 0) { + throw ProtocolException.negativeLength("Url", urlLen); + } + + if (urlLen > 4096000) { + throw ProtocolException.stringTooLong("Url", urlLen, 4096000); + } + + obj.url = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 13 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 13 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 9); + int pos2 = offset + 13 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.name != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.email != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.url != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + int nameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int emailOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int urlOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.name != null) { + buf.setIntLE(nameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.name, 4096000); + } else { + buf.setIntLE(nameOffsetSlot, -1); + } + + if (this.email != null) { + buf.setIntLE(emailOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.email, 4096000); + } else { + buf.setIntLE(emailOffsetSlot, -1); + } + + if (this.url != null) { + buf.setIntLE(urlOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.url, 4096000); + } else { + buf.setIntLE(urlOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 13; + if (this.name != null) { + size += PacketIO.stringSize(this.name); + } + + if (this.email != null) { + size += PacketIO.stringSize(this.email); + } + + if (this.url != null) { + size += PacketIO.stringSize(this.url); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int nameOffset = buffer.getIntLE(offset + 1); + if (nameOffset < 0) { + return ValidationResult.error("Invalid offset for Name"); + } + + int pos = offset + 13 + nameOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Name"); + } + + int nameLen = VarInt.peek(buffer, pos); + if (nameLen < 0) { + return ValidationResult.error("Invalid string length for Name"); + } + + if (nameLen > 4096000) { + return ValidationResult.error("Name exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += nameLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Name"); + } + } + + if ((nullBits & 2) != 0) { + int emailOffset = buffer.getIntLE(offset + 5); + if (emailOffset < 0) { + return ValidationResult.error("Invalid offset for Email"); + } + + int posx = offset + 13 + emailOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Email"); + } + + int emailLen = VarInt.peek(buffer, posx); + if (emailLen < 0) { + return ValidationResult.error("Invalid string length for Email"); + } + + if (emailLen > 4096000) { + return ValidationResult.error("Email exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += emailLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Email"); + } + } + + if ((nullBits & 4) != 0) { + int urlOffset = buffer.getIntLE(offset + 9); + if (urlOffset < 0) { + return ValidationResult.error("Invalid offset for Url"); + } + + int posxx = offset + 13 + urlOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Url"); + } + + int urlLen = VarInt.peek(buffer, posxx); + if (urlLen < 0) { + return ValidationResult.error("Invalid string length for Url"); + } + + if (urlLen > 4096000) { + return ValidationResult.error("Url exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += urlLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Url"); + } + } + + return ValidationResult.OK; + } + } + + public AuthorInfo clone() { + AuthorInfo copy = new AuthorInfo(); + copy.name = this.name; + copy.email = this.email; + copy.url = this.url; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AuthorInfo other) + ? false + : Objects.equals(this.name, other.name) && Objects.equals(this.email, other.email) && Objects.equals(this.url, other.url); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.name, this.email, this.url); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/FailureReply.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/FailureReply.java new file mode 100644 index 0000000..0c1d9a1 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/FailureReply.java @@ -0,0 +1,128 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.FormattedMessage; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FailureReply implements Packet { + public static final int PACKET_ID = 300; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 1677721600; + public int token; + @Nullable + public FormattedMessage message; + + @Override + public int getId() { + return 300; + } + + public FailureReply() { + } + + public FailureReply(int token, @Nullable FormattedMessage message) { + this.token = token; + this.message = message; + } + + public FailureReply(@Nonnull FailureReply other) { + this.token = other.token; + this.message = other.message; + } + + @Nonnull + public static FailureReply deserialize(@Nonnull ByteBuf buf, int offset) { + FailureReply obj = new FailureReply(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + obj.message = FormattedMessage.deserialize(buf, pos); + pos += FormattedMessage.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + pos += FormattedMessage.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.message != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + if (this.message != null) { + this.message.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 5; + if (this.message != null) { + size += this.message.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + ValidationResult messageResult = FormattedMessage.validateStructure(buffer, pos); + if (!messageResult.isValid()) { + return ValidationResult.error("Invalid Message: " + messageResult.error()); + } + + pos += FormattedMessage.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public FailureReply clone() { + FailureReply copy = new FailureReply(); + copy.token = this.token; + copy.message = this.message != null ? this.message.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof FailureReply other) ? false : this.token == other.token && Objects.equals(this.message, other.message); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.token, this.message); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/JsonUpdateCommand.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/JsonUpdateCommand.java new file mode 100644 index 0000000..88bdd6a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/JsonUpdateCommand.java @@ -0,0 +1,518 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class JsonUpdateCommand { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 7; + public static final int VARIABLE_FIELD_COUNT = 4; + public static final int VARIABLE_BLOCK_START = 23; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public JsonUpdateType type = JsonUpdateType.SetProperty; + @Nullable + public String[] path; + @Nullable + public String value; + @Nullable + public String previousValue; + @Nullable + public String[] firstCreatedProperty; + @Nullable + public AssetEditorRebuildCaches rebuildCaches; + + public JsonUpdateCommand() { + } + + public JsonUpdateCommand( + @Nonnull JsonUpdateType type, + @Nullable String[] path, + @Nullable String value, + @Nullable String previousValue, + @Nullable String[] firstCreatedProperty, + @Nullable AssetEditorRebuildCaches rebuildCaches + ) { + this.type = type; + this.path = path; + this.value = value; + this.previousValue = previousValue; + this.firstCreatedProperty = firstCreatedProperty; + this.rebuildCaches = rebuildCaches; + } + + public JsonUpdateCommand(@Nonnull JsonUpdateCommand other) { + this.type = other.type; + this.path = other.path; + this.value = other.value; + this.previousValue = other.previousValue; + this.firstCreatedProperty = other.firstCreatedProperty; + this.rebuildCaches = other.rebuildCaches; + } + + @Nonnull + public static JsonUpdateCommand deserialize(@Nonnull ByteBuf buf, int offset) { + JsonUpdateCommand obj = new JsonUpdateCommand(); + byte nullBits = buf.getByte(offset); + obj.type = JsonUpdateType.fromValue(buf.getByte(offset + 1)); + if ((nullBits & 16) != 0) { + obj.rebuildCaches = AssetEditorRebuildCaches.deserialize(buf, offset + 2); + } + + if ((nullBits & 1) != 0) { + int varPos0 = offset + 23 + buf.getIntLE(offset + 7); + int pathCount = VarInt.peek(buf, varPos0); + if (pathCount < 0) { + throw ProtocolException.negativeLength("Path", pathCount); + } + + if (pathCount > 4096000) { + throw ProtocolException.arrayTooLong("Path", pathCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + pathCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Path", varPos0 + varIntLen + pathCount * 1, buf.readableBytes()); + } + + obj.path = new String[pathCount]; + int elemPos = varPos0 + varIntLen; + + for (int i = 0; i < pathCount; i++) { + int strLen = VarInt.peek(buf, elemPos); + if (strLen < 0) { + throw ProtocolException.negativeLength("path[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("path[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, elemPos); + obj.path[i] = PacketIO.readVarString(buf, elemPos); + elemPos += strVarLen + strLen; + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 23 + buf.getIntLE(offset + 11); + int valueLen = VarInt.peek(buf, varPos1); + if (valueLen < 0) { + throw ProtocolException.negativeLength("Value", valueLen); + } + + if (valueLen > 4096000) { + throw ProtocolException.stringTooLong("Value", valueLen, 4096000); + } + + obj.value = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 23 + buf.getIntLE(offset + 15); + int previousValueLen = VarInt.peek(buf, varPos2); + if (previousValueLen < 0) { + throw ProtocolException.negativeLength("PreviousValue", previousValueLen); + } + + if (previousValueLen > 4096000) { + throw ProtocolException.stringTooLong("PreviousValue", previousValueLen, 4096000); + } + + obj.previousValue = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 23 + buf.getIntLE(offset + 19); + int firstCreatedPropertyCount = VarInt.peek(buf, varPos3); + if (firstCreatedPropertyCount < 0) { + throw ProtocolException.negativeLength("FirstCreatedProperty", firstCreatedPropertyCount); + } + + if (firstCreatedPropertyCount > 4096000) { + throw ProtocolException.arrayTooLong("FirstCreatedProperty", firstCreatedPropertyCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + firstCreatedPropertyCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("FirstCreatedProperty", varPos3 + varIntLen + firstCreatedPropertyCount * 1, buf.readableBytes()); + } + + obj.firstCreatedProperty = new String[firstCreatedPropertyCount]; + int elemPos = varPos3 + varIntLen; + + for (int i = 0; i < firstCreatedPropertyCount; i++) { + int strLenx = VarInt.peek(buf, elemPos); + if (strLenx < 0) { + throw ProtocolException.negativeLength("firstCreatedProperty[" + i + "]", strLenx); + } + + if (strLenx > 4096000) { + throw ProtocolException.stringTooLong("firstCreatedProperty[" + i + "]", strLenx, 4096000); + } + + int strVarLen = VarInt.length(buf, elemPos); + obj.firstCreatedProperty[i] = PacketIO.readVarString(buf, elemPos); + elemPos += strVarLen + strLenx; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 23; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 7); + int pos0 = offset + 23 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 11); + int pos1 = offset + 23 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 15); + int pos2 = offset + 23 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 19); + int pos3 = offset + 23 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3) + sl; + } + + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.value != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.previousValue != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.firstCreatedProperty != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.rebuildCaches != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.rebuildCaches != null) { + this.rebuildCaches.serialize(buf); + } else { + buf.writeZero(5); + } + + int pathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int valueOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int previousValueOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int firstCreatedPropertyOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.path != null) { + buf.setIntLE(pathOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.path.length > 4096000) { + throw ProtocolException.arrayTooLong("Path", this.path.length, 4096000); + } + + VarInt.write(buf, this.path.length); + + for (String item : this.path) { + PacketIO.writeVarString(buf, item, 4096000); + } + } else { + buf.setIntLE(pathOffsetSlot, -1); + } + + if (this.value != null) { + buf.setIntLE(valueOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.value, 4096000); + } else { + buf.setIntLE(valueOffsetSlot, -1); + } + + if (this.previousValue != null) { + buf.setIntLE(previousValueOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.previousValue, 4096000); + } else { + buf.setIntLE(previousValueOffsetSlot, -1); + } + + if (this.firstCreatedProperty != null) { + buf.setIntLE(firstCreatedPropertyOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.firstCreatedProperty.length > 4096000) { + throw ProtocolException.arrayTooLong("FirstCreatedProperty", this.firstCreatedProperty.length, 4096000); + } + + VarInt.write(buf, this.firstCreatedProperty.length); + + for (String item : this.firstCreatedProperty) { + PacketIO.writeVarString(buf, item, 4096000); + } + } else { + buf.setIntLE(firstCreatedPropertyOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 23; + if (this.path != null) { + int pathSize = 0; + + for (String elem : this.path) { + pathSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.path.length) + pathSize; + } + + if (this.value != null) { + size += PacketIO.stringSize(this.value); + } + + if (this.previousValue != null) { + size += PacketIO.stringSize(this.previousValue); + } + + if (this.firstCreatedProperty != null) { + int firstCreatedPropertySize = 0; + + for (String elem : this.firstCreatedProperty) { + firstCreatedPropertySize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.firstCreatedProperty.length) + firstCreatedPropertySize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 23) { + return ValidationResult.error("Buffer too small: expected at least 23 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int pathOffset = buffer.getIntLE(offset + 7); + if (pathOffset < 0) { + return ValidationResult.error("Invalid offset for Path"); + } + + int pos = offset + 23 + pathOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Path"); + } + + int pathCount = VarInt.peek(buffer, pos); + if (pathCount < 0) { + return ValidationResult.error("Invalid array count for Path"); + } + + if (pathCount > 4096000) { + return ValidationResult.error("Path exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < pathCount; i++) { + int strLen = VarInt.peek(buffer, pos); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in Path"); + } + + pos += VarInt.length(buffer, pos); + pos += strLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in Path"); + } + } + } + + if ((nullBits & 2) != 0) { + int valueOffset = buffer.getIntLE(offset + 11); + if (valueOffset < 0) { + return ValidationResult.error("Invalid offset for Value"); + } + + int posx = offset + 23 + valueOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Value"); + } + + int valueLen = VarInt.peek(buffer, posx); + if (valueLen < 0) { + return ValidationResult.error("Invalid string length for Value"); + } + + if (valueLen > 4096000) { + return ValidationResult.error("Value exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += valueLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Value"); + } + } + + if ((nullBits & 4) != 0) { + int previousValueOffset = buffer.getIntLE(offset + 15); + if (previousValueOffset < 0) { + return ValidationResult.error("Invalid offset for PreviousValue"); + } + + int posxx = offset + 23 + previousValueOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for PreviousValue"); + } + + int previousValueLen = VarInt.peek(buffer, posxx); + if (previousValueLen < 0) { + return ValidationResult.error("Invalid string length for PreviousValue"); + } + + if (previousValueLen > 4096000) { + return ValidationResult.error("PreviousValue exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += previousValueLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading PreviousValue"); + } + } + + if ((nullBits & 8) != 0) { + int firstCreatedPropertyOffset = buffer.getIntLE(offset + 19); + if (firstCreatedPropertyOffset < 0) { + return ValidationResult.error("Invalid offset for FirstCreatedProperty"); + } + + int posxxx = offset + 23 + firstCreatedPropertyOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FirstCreatedProperty"); + } + + int firstCreatedPropertyCount = VarInt.peek(buffer, posxxx); + if (firstCreatedPropertyCount < 0) { + return ValidationResult.error("Invalid array count for FirstCreatedProperty"); + } + + if (firstCreatedPropertyCount > 4096000) { + return ValidationResult.error("FirstCreatedProperty exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + + for (int i = 0; i < firstCreatedPropertyCount; i++) { + int strLenx = VarInt.peek(buffer, posxxx); + if (strLenx < 0) { + return ValidationResult.error("Invalid string length in FirstCreatedProperty"); + } + + posxxx += VarInt.length(buffer, posxxx); + posxxx += strLenx; + if (posxxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in FirstCreatedProperty"); + } + } + } + + return ValidationResult.OK; + } + } + + public JsonUpdateCommand clone() { + JsonUpdateCommand copy = new JsonUpdateCommand(); + copy.type = this.type; + copy.path = this.path != null ? Arrays.copyOf(this.path, this.path.length) : null; + copy.value = this.value; + copy.previousValue = this.previousValue; + copy.firstCreatedProperty = this.firstCreatedProperty != null ? Arrays.copyOf(this.firstCreatedProperty, this.firstCreatedProperty.length) : null; + copy.rebuildCaches = this.rebuildCaches != null ? this.rebuildCaches.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof JsonUpdateCommand other) + ? false + : Objects.equals(this.type, other.type) + && Arrays.equals((Object[])this.path, (Object[])other.path) + && Objects.equals(this.value, other.value) + && Objects.equals(this.previousValue, other.previousValue) + && Arrays.equals((Object[])this.firstCreatedProperty, (Object[])other.firstCreatedProperty) + && Objects.equals(this.rebuildCaches, other.rebuildCaches); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.type); + result = 31 * result + Arrays.hashCode((Object[])this.path); + result = 31 * result + Objects.hashCode(this.value); + result = 31 * result + Objects.hashCode(this.previousValue); + result = 31 * result + Arrays.hashCode((Object[])this.firstCreatedProperty); + return 31 * result + Objects.hashCode(this.rebuildCaches); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/JsonUpdateType.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/JsonUpdateType.java new file mode 100644 index 0000000..b0d3f58 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/JsonUpdateType.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum JsonUpdateType { + SetProperty(0), + InsertProperty(1), + RemoveProperty(2); + + public static final JsonUpdateType[] VALUES = values(); + private final int value; + + private JsonUpdateType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static JsonUpdateType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("JsonUpdateType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/SchemaFile.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/SchemaFile.java new file mode 100644 index 0000000..53c1304 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/SchemaFile.java @@ -0,0 +1,133 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SchemaFile { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 16384006; + @Nullable + public String content; + + public SchemaFile() { + } + + public SchemaFile(@Nullable String content) { + this.content = content; + } + + public SchemaFile(@Nonnull SchemaFile other) { + this.content = other.content; + } + + @Nonnull + public static SchemaFile deserialize(@Nonnull ByteBuf buf, int offset) { + SchemaFile obj = new SchemaFile(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int contentLen = VarInt.peek(buf, pos); + if (contentLen < 0) { + throw ProtocolException.negativeLength("Content", contentLen); + } + + if (contentLen > 4096000) { + throw ProtocolException.stringTooLong("Content", contentLen, 4096000); + } + + int contentVarLen = VarInt.length(buf, pos); + obj.content = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += contentVarLen + contentLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.content != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.content != null) { + PacketIO.writeVarString(buf, this.content, 4096000); + } + } + + public int computeSize() { + int size = 1; + if (this.content != null) { + size += PacketIO.stringSize(this.content); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int contentLen = VarInt.peek(buffer, pos); + if (contentLen < 0) { + return ValidationResult.error("Invalid string length for Content"); + } + + if (contentLen > 4096000) { + return ValidationResult.error("Content exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += contentLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Content"); + } + } + + return ValidationResult.OK; + } + } + + public SchemaFile clone() { + SchemaFile copy = new SchemaFile(); + copy.content = this.content; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof SchemaFile other ? Objects.equals(this.content, other.content) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.content); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/SuccessReply.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/SuccessReply.java new file mode 100644 index 0000000..0eaf52e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/SuccessReply.java @@ -0,0 +1,128 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.FormattedMessage; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SuccessReply implements Packet { + public static final int PACKET_ID = 301; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 1677721600; + public int token; + @Nullable + public FormattedMessage message; + + @Override + public int getId() { + return 301; + } + + public SuccessReply() { + } + + public SuccessReply(int token, @Nullable FormattedMessage message) { + this.token = token; + this.message = message; + } + + public SuccessReply(@Nonnull SuccessReply other) { + this.token = other.token; + this.message = other.message; + } + + @Nonnull + public static SuccessReply deserialize(@Nonnull ByteBuf buf, int offset) { + SuccessReply obj = new SuccessReply(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + obj.message = FormattedMessage.deserialize(buf, pos); + pos += FormattedMessage.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + pos += FormattedMessage.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.message != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + if (this.message != null) { + this.message.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 5; + if (this.message != null) { + size += this.message.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + ValidationResult messageResult = FormattedMessage.validateStructure(buffer, pos); + if (!messageResult.isValid()) { + return ValidationResult.error("Invalid Message: " + messageResult.error()); + } + + pos += FormattedMessage.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public SuccessReply clone() { + SuccessReply copy = new SuccessReply(); + copy.token = this.token; + copy.message = this.message != null ? this.message.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SuccessReply other) ? false : this.token == other.token && Objects.equals(this.message, other.message); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.token, this.message); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/TimestampedAssetReference.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/TimestampedAssetReference.java new file mode 100644 index 0000000..e9aabcd --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/TimestampedAssetReference.java @@ -0,0 +1,209 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TimestampedAssetReference { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 49152033; + @Nullable + public AssetPath path; + @Nullable + public String timestamp; + + public TimestampedAssetReference() { + } + + public TimestampedAssetReference(@Nullable AssetPath path, @Nullable String timestamp) { + this.path = path; + this.timestamp = timestamp; + } + + public TimestampedAssetReference(@Nonnull TimestampedAssetReference other) { + this.path = other.path; + this.timestamp = other.timestamp; + } + + @Nonnull + public static TimestampedAssetReference deserialize(@Nonnull ByteBuf buf, int offset) { + TimestampedAssetReference obj = new TimestampedAssetReference(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + obj.path = AssetPath.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int timestampLen = VarInt.peek(buf, varPos1); + if (timestampLen < 0) { + throw ProtocolException.negativeLength("Timestamp", timestampLen); + } + + if (timestampLen > 4096000) { + throw ProtocolException.stringTooLong("Timestamp", timestampLen, 4096000); + } + + obj.timestamp = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + pos0 += AssetPath.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.path != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.timestamp != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int pathOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int timestampOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.path != null) { + buf.setIntLE(pathOffsetSlot, buf.writerIndex() - varBlockStart); + this.path.serialize(buf); + } else { + buf.setIntLE(pathOffsetSlot, -1); + } + + if (this.timestamp != null) { + buf.setIntLE(timestampOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.timestamp, 4096000); + } else { + buf.setIntLE(timestampOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 9; + if (this.path != null) { + size += this.path.computeSize(); + } + + if (this.timestamp != null) { + size += PacketIO.stringSize(this.timestamp); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int pathOffset = buffer.getIntLE(offset + 1); + if (pathOffset < 0) { + return ValidationResult.error("Invalid offset for Path"); + } + + int pos = offset + 9 + pathOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Path"); + } + + ValidationResult pathResult = AssetPath.validateStructure(buffer, pos); + if (!pathResult.isValid()) { + return ValidationResult.error("Invalid Path: " + pathResult.error()); + } + + pos += AssetPath.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int timestampOffset = buffer.getIntLE(offset + 5); + if (timestampOffset < 0) { + return ValidationResult.error("Invalid offset for Timestamp"); + } + + int posx = offset + 9 + timestampOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Timestamp"); + } + + int timestampLen = VarInt.peek(buffer, posx); + if (timestampLen < 0) { + return ValidationResult.error("Invalid string length for Timestamp"); + } + + if (timestampLen > 4096000) { + return ValidationResult.error("Timestamp exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += timestampLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Timestamp"); + } + } + + return ValidationResult.OK; + } + } + + public TimestampedAssetReference clone() { + TimestampedAssetReference copy = new TimestampedAssetReference(); + copy.path = this.path != null ? this.path.clone() : null; + copy.timestamp = this.timestamp; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof TimestampedAssetReference other) + ? false + : Objects.equals(this.path, other.path) && Objects.equals(this.timestamp, other.timestamp); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.path, this.timestamp); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/asseteditor/WriteUpdateType.java b/src/com/hypixel/hytale/protocol/packets/asseteditor/WriteUpdateType.java new file mode 100644 index 0000000..b0c6a87 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/asseteditor/WriteUpdateType.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol.packets.asseteditor; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum WriteUpdateType { + Add(0), + Update(1), + Remove(2); + + public static final WriteUpdateType[] VALUES = values(); + private final int value; + + private WriteUpdateType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static WriteUpdateType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("WriteUpdateType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/TrackOrUpdateObjective.java b/src/com/hypixel/hytale/protocol/packets/assets/TrackOrUpdateObjective.java new file mode 100644 index 0000000..2a03707 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/TrackOrUpdateObjective.java @@ -0,0 +1,122 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Objective; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TrackOrUpdateObjective implements Packet { + public static final int PACKET_ID = 69; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public Objective objective; + + @Override + public int getId() { + return 69; + } + + public TrackOrUpdateObjective() { + } + + public TrackOrUpdateObjective(@Nullable Objective objective) { + this.objective = objective; + } + + public TrackOrUpdateObjective(@Nonnull TrackOrUpdateObjective other) { + this.objective = other.objective; + } + + @Nonnull + public static TrackOrUpdateObjective deserialize(@Nonnull ByteBuf buf, int offset) { + TrackOrUpdateObjective obj = new TrackOrUpdateObjective(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + obj.objective = Objective.deserialize(buf, pos); + pos += Objective.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + pos += Objective.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.objective != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.objective != null) { + this.objective.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.objective != null) { + size += this.objective.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + ValidationResult objectiveResult = Objective.validateStructure(buffer, pos); + if (!objectiveResult.isValid()) { + return ValidationResult.error("Invalid Objective: " + objectiveResult.error()); + } + + pos += Objective.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public TrackOrUpdateObjective clone() { + TrackOrUpdateObjective copy = new TrackOrUpdateObjective(); + copy.objective = this.objective != null ? this.objective.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof TrackOrUpdateObjective other ? Objects.equals(this.objective, other.objective) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.objective); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UntrackObjective.java b/src/com/hypixel/hytale/protocol/packets/assets/UntrackObjective.java new file mode 100644 index 0000000..b13cd66 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UntrackObjective.java @@ -0,0 +1,82 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class UntrackObjective implements Packet { + public static final int PACKET_ID = 70; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 16; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 16; + public static final int MAX_SIZE = 16; + @Nonnull + public UUID objectiveUuid = new UUID(0L, 0L); + + @Override + public int getId() { + return 70; + } + + public UntrackObjective() { + } + + public UntrackObjective(@Nonnull UUID objectiveUuid) { + this.objectiveUuid = objectiveUuid; + } + + public UntrackObjective(@Nonnull UntrackObjective other) { + this.objectiveUuid = other.objectiveUuid; + } + + @Nonnull + public static UntrackObjective deserialize(@Nonnull ByteBuf buf, int offset) { + UntrackObjective obj = new UntrackObjective(); + obj.objectiveUuid = PacketIO.readUUID(buf, offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 16; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + PacketIO.writeUUID(buf, this.objectiveUuid); + } + + @Override + public int computeSize() { + return 16; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 16 ? ValidationResult.error("Buffer too small: expected at least 16 bytes") : ValidationResult.OK; + } + + public UntrackObjective clone() { + UntrackObjective copy = new UntrackObjective(); + copy.objectiveUuid = this.objectiveUuid; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof UntrackObjective other ? Objects.equals(this.objectiveUuid, other.objectiveUuid) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.objectiveUuid); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateAmbienceFX.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateAmbienceFX.java new file mode 100644 index 0000000..590e6c3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateAmbienceFX.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.AmbienceFX; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateAmbienceFX implements Packet { + public static final int PACKET_ID = 62; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map ambienceFX; + + @Override + public int getId() { + return 62; + } + + public UpdateAmbienceFX() { + } + + public UpdateAmbienceFX(@Nonnull UpdateType type, int maxId, @Nullable Map ambienceFX) { + this.type = type; + this.maxId = maxId; + this.ambienceFX = ambienceFX; + } + + public UpdateAmbienceFX(@Nonnull UpdateAmbienceFX other) { + this.type = other.type; + this.maxId = other.maxId; + this.ambienceFX = other.ambienceFX; + } + + @Nonnull + public static UpdateAmbienceFX deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateAmbienceFX obj = new UpdateAmbienceFX(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int ambienceFXCount = VarInt.peek(buf, pos); + if (ambienceFXCount < 0) { + throw ProtocolException.negativeLength("AmbienceFX", ambienceFXCount); + } + + if (ambienceFXCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("AmbienceFX", ambienceFXCount, 4096000); + } + + pos += VarInt.size(ambienceFXCount); + obj.ambienceFX = new HashMap<>(ambienceFXCount); + + for (int i = 0; i < ambienceFXCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + AmbienceFX val = AmbienceFX.deserialize(buf, pos); + pos += AmbienceFX.computeBytesConsumed(buf, pos); + if (obj.ambienceFX.put(key, val) != null) { + throw ProtocolException.duplicateKey("ambienceFX", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += AmbienceFX.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.ambienceFX != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.ambienceFX != null) { + if (this.ambienceFX.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("AmbienceFX", this.ambienceFX.size(), 4096000); + } + + VarInt.write(buf, this.ambienceFX.size()); + + for (Entry e : this.ambienceFX.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.ambienceFX != null) { + int ambienceFXSize = 0; + + for (Entry kvp : this.ambienceFX.entrySet()) { + ambienceFXSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.ambienceFX.size()) + ambienceFXSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int ambienceFXCount = VarInt.peek(buffer, pos); + if (ambienceFXCount < 0) { + return ValidationResult.error("Invalid dictionary count for AmbienceFX"); + } + + if (ambienceFXCount > 4096000) { + return ValidationResult.error("AmbienceFX exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < ambienceFXCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += AmbienceFX.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateAmbienceFX clone() { + UpdateAmbienceFX copy = new UpdateAmbienceFX(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.ambienceFX != null) { + Map m = new HashMap<>(); + + for (Entry e : this.ambienceFX.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.ambienceFX = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateAmbienceFX other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.ambienceFX, other.ambienceFX); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.ambienceFX); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateAudioCategories.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateAudioCategories.java new file mode 100644 index 0000000..36c3deb --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateAudioCategories.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.AudioCategory; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateAudioCategories implements Packet { + public static final int PACKET_ID = 80; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map categories; + + @Override + public int getId() { + return 80; + } + + public UpdateAudioCategories() { + } + + public UpdateAudioCategories(@Nonnull UpdateType type, int maxId, @Nullable Map categories) { + this.type = type; + this.maxId = maxId; + this.categories = categories; + } + + public UpdateAudioCategories(@Nonnull UpdateAudioCategories other) { + this.type = other.type; + this.maxId = other.maxId; + this.categories = other.categories; + } + + @Nonnull + public static UpdateAudioCategories deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateAudioCategories obj = new UpdateAudioCategories(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int categoriesCount = VarInt.peek(buf, pos); + if (categoriesCount < 0) { + throw ProtocolException.negativeLength("Categories", categoriesCount); + } + + if (categoriesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Categories", categoriesCount, 4096000); + } + + pos += VarInt.size(categoriesCount); + obj.categories = new HashMap<>(categoriesCount); + + for (int i = 0; i < categoriesCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + AudioCategory val = AudioCategory.deserialize(buf, pos); + pos += AudioCategory.computeBytesConsumed(buf, pos); + if (obj.categories.put(key, val) != null) { + throw ProtocolException.duplicateKey("categories", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += AudioCategory.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.categories != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.categories != null) { + if (this.categories.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Categories", this.categories.size(), 4096000); + } + + VarInt.write(buf, this.categories.size()); + + for (Entry e : this.categories.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.categories != null) { + int categoriesSize = 0; + + for (Entry kvp : this.categories.entrySet()) { + categoriesSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.categories.size()) + categoriesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int categoriesCount = VarInt.peek(buffer, pos); + if (categoriesCount < 0) { + return ValidationResult.error("Invalid dictionary count for Categories"); + } + + if (categoriesCount > 4096000) { + return ValidationResult.error("Categories exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < categoriesCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += AudioCategory.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateAudioCategories clone() { + UpdateAudioCategories copy = new UpdateAudioCategories(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.categories != null) { + Map m = new HashMap<>(); + + for (Entry e : this.categories.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.categories = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateAudioCategories other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.categories, other.categories); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.categories); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockBreakingDecals.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockBreakingDecals.java new file mode 100644 index 0000000..b5fa7c6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockBreakingDecals.java @@ -0,0 +1,221 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.BlockBreakingDecal; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateBlockBreakingDecals implements Packet { + public static final int PACKET_ID = 45; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public Map blockBreakingDecals; + + @Override + public int getId() { + return 45; + } + + public UpdateBlockBreakingDecals() { + } + + public UpdateBlockBreakingDecals(@Nonnull UpdateType type, @Nullable Map blockBreakingDecals) { + this.type = type; + this.blockBreakingDecals = blockBreakingDecals; + } + + public UpdateBlockBreakingDecals(@Nonnull UpdateBlockBreakingDecals other) { + this.type = other.type; + this.blockBreakingDecals = other.blockBreakingDecals; + } + + @Nonnull + public static UpdateBlockBreakingDecals deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateBlockBreakingDecals obj = new UpdateBlockBreakingDecals(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int blockBreakingDecalsCount = VarInt.peek(buf, pos); + if (blockBreakingDecalsCount < 0) { + throw ProtocolException.negativeLength("BlockBreakingDecals", blockBreakingDecalsCount); + } + + if (blockBreakingDecalsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("BlockBreakingDecals", blockBreakingDecalsCount, 4096000); + } + + pos += VarInt.size(blockBreakingDecalsCount); + obj.blockBreakingDecals = new HashMap<>(blockBreakingDecalsCount); + + for (int i = 0; i < blockBreakingDecalsCount; i++) { + int keyLen = VarInt.peek(buf, pos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, pos); + String key = PacketIO.readVarString(buf, pos); + pos += keyVarLen + keyLen; + BlockBreakingDecal val = BlockBreakingDecal.deserialize(buf, pos); + pos += BlockBreakingDecal.computeBytesConsumed(buf, pos); + if (obj.blockBreakingDecals.put(key, val) != null) { + throw ProtocolException.duplicateKey("blockBreakingDecals", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + pos += BlockBreakingDecal.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.blockBreakingDecals != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.blockBreakingDecals != null) { + if (this.blockBreakingDecals.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("BlockBreakingDecals", this.blockBreakingDecals.size(), 4096000); + } + + VarInt.write(buf, this.blockBreakingDecals.size()); + + for (Entry e : this.blockBreakingDecals.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.blockBreakingDecals != null) { + int blockBreakingDecalsSize = 0; + + for (Entry kvp : this.blockBreakingDecals.entrySet()) { + blockBreakingDecalsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.blockBreakingDecals.size()) + blockBreakingDecalsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int blockBreakingDecalsCount = VarInt.peek(buffer, pos); + if (blockBreakingDecalsCount < 0) { + return ValidationResult.error("Invalid dictionary count for BlockBreakingDecals"); + } + + if (blockBreakingDecalsCount > 4096000) { + return ValidationResult.error("BlockBreakingDecals exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < blockBreakingDecalsCount; i++) { + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += BlockBreakingDecal.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateBlockBreakingDecals clone() { + UpdateBlockBreakingDecals copy = new UpdateBlockBreakingDecals(); + copy.type = this.type; + if (this.blockBreakingDecals != null) { + Map m = new HashMap<>(); + + for (Entry e : this.blockBreakingDecals.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.blockBreakingDecals = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateBlockBreakingDecals other) + ? false + : Objects.equals(this.type, other.type) && Objects.equals(this.blockBreakingDecals, other.blockBreakingDecals); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.blockBreakingDecals); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockGroups.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockGroups.java new file mode 100644 index 0000000..e793156 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockGroups.java @@ -0,0 +1,219 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.BlockGroup; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateBlockGroups implements Packet { + public static final int PACKET_ID = 78; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public Map groups; + + @Override + public int getId() { + return 78; + } + + public UpdateBlockGroups() { + } + + public UpdateBlockGroups(@Nonnull UpdateType type, @Nullable Map groups) { + this.type = type; + this.groups = groups; + } + + public UpdateBlockGroups(@Nonnull UpdateBlockGroups other) { + this.type = other.type; + this.groups = other.groups; + } + + @Nonnull + public static UpdateBlockGroups deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateBlockGroups obj = new UpdateBlockGroups(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int groupsCount = VarInt.peek(buf, pos); + if (groupsCount < 0) { + throw ProtocolException.negativeLength("Groups", groupsCount); + } + + if (groupsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Groups", groupsCount, 4096000); + } + + pos += VarInt.size(groupsCount); + obj.groups = new HashMap<>(groupsCount); + + for (int i = 0; i < groupsCount; i++) { + int keyLen = VarInt.peek(buf, pos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, pos); + String key = PacketIO.readVarString(buf, pos); + pos += keyVarLen + keyLen; + BlockGroup val = BlockGroup.deserialize(buf, pos); + pos += BlockGroup.computeBytesConsumed(buf, pos); + if (obj.groups.put(key, val) != null) { + throw ProtocolException.duplicateKey("groups", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + pos += BlockGroup.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.groups != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.groups != null) { + if (this.groups.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Groups", this.groups.size(), 4096000); + } + + VarInt.write(buf, this.groups.size()); + + for (Entry e : this.groups.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.groups != null) { + int groupsSize = 0; + + for (Entry kvp : this.groups.entrySet()) { + groupsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.groups.size()) + groupsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int groupsCount = VarInt.peek(buffer, pos); + if (groupsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Groups"); + } + + if (groupsCount > 4096000) { + return ValidationResult.error("Groups exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < groupsCount; i++) { + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += BlockGroup.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateBlockGroups clone() { + UpdateBlockGroups copy = new UpdateBlockGroups(); + copy.type = this.type; + if (this.groups != null) { + Map m = new HashMap<>(); + + for (Entry e : this.groups.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.groups = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateBlockGroups other) ? false : Objects.equals(this.type, other.type) && Objects.equals(this.groups, other.groups); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.groups); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockHitboxes.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockHitboxes.java new file mode 100644 index 0000000..486339b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockHitboxes.java @@ -0,0 +1,244 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Hitbox; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateBlockHitboxes implements Packet { + public static final int PACKET_ID = 41; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map blockBaseHitboxes; + + @Override + public int getId() { + return 41; + } + + public UpdateBlockHitboxes() { + } + + public UpdateBlockHitboxes(@Nonnull UpdateType type, int maxId, @Nullable Map blockBaseHitboxes) { + this.type = type; + this.maxId = maxId; + this.blockBaseHitboxes = blockBaseHitboxes; + } + + public UpdateBlockHitboxes(@Nonnull UpdateBlockHitboxes other) { + this.type = other.type; + this.maxId = other.maxId; + this.blockBaseHitboxes = other.blockBaseHitboxes; + } + + @Nonnull + public static UpdateBlockHitboxes deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateBlockHitboxes obj = new UpdateBlockHitboxes(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int blockBaseHitboxesCount = VarInt.peek(buf, pos); + if (blockBaseHitboxesCount < 0) { + throw ProtocolException.negativeLength("BlockBaseHitboxes", blockBaseHitboxesCount); + } + + if (blockBaseHitboxesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("BlockBaseHitboxes", blockBaseHitboxesCount, 4096000); + } + + pos += VarInt.size(blockBaseHitboxesCount); + obj.blockBaseHitboxes = new HashMap<>(blockBaseHitboxesCount); + + for (int i = 0; i < blockBaseHitboxesCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + int valLen = VarInt.peek(buf, pos); + if (valLen < 0) { + throw ProtocolException.negativeLength("val", valLen); + } + + if (valLen > 64) { + throw ProtocolException.arrayTooLong("val", valLen, 64); + } + + int valVarLen = VarInt.length(buf, pos); + if (pos + valVarLen + valLen * 24L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("val", pos + valVarLen + valLen * 24, buf.readableBytes()); + } + + pos += valVarLen; + Hitbox[] val = new Hitbox[valLen]; + + for (int valIdx = 0; valIdx < valLen; valIdx++) { + val[valIdx] = Hitbox.deserialize(buf, pos); + pos += Hitbox.computeBytesConsumed(buf, pos); + } + + if (obj.blockBaseHitboxes.put(key, val) != null) { + throw ProtocolException.duplicateKey("blockBaseHitboxes", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + int al = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int j = 0; j < al; j++) { + pos += Hitbox.computeBytesConsumed(buf, pos); + } + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.blockBaseHitboxes != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.blockBaseHitboxes != null) { + if (this.blockBaseHitboxes.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("BlockBaseHitboxes", this.blockBaseHitboxes.size(), 4096000); + } + + VarInt.write(buf, this.blockBaseHitboxes.size()); + + for (Entry e : this.blockBaseHitboxes.entrySet()) { + buf.writeIntLE(e.getKey()); + VarInt.write(buf, e.getValue().length); + + for (Hitbox arrItem : e.getValue()) { + arrItem.serialize(buf); + } + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.blockBaseHitboxes != null) { + int blockBaseHitboxesSize = 0; + + for (Entry kvp : this.blockBaseHitboxes.entrySet()) { + blockBaseHitboxesSize += 4 + VarInt.size(kvp.getValue().length) + ((Hitbox[])kvp.getValue()).length * 24; + } + + size += VarInt.size(this.blockBaseHitboxes.size()) + blockBaseHitboxesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int blockBaseHitboxesCount = VarInt.peek(buffer, pos); + if (blockBaseHitboxesCount < 0) { + return ValidationResult.error("Invalid dictionary count for BlockBaseHitboxes"); + } + + if (blockBaseHitboxesCount > 4096000) { + return ValidationResult.error("BlockBaseHitboxes exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < blockBaseHitboxesCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + int valueArrCount = VarInt.peek(buffer, pos); + if (valueArrCount < 0) { + return ValidationResult.error("Invalid array count for value"); + } + + pos += VarInt.length(buffer, pos); + + for (int valueArrIdx = 0; valueArrIdx < valueArrCount; valueArrIdx++) { + pos += 24; + } + } + } + + return ValidationResult.OK; + } + } + + public UpdateBlockHitboxes clone() { + UpdateBlockHitboxes copy = new UpdateBlockHitboxes(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.blockBaseHitboxes != null) { + Map m = new HashMap<>(); + + for (Entry e : this.blockBaseHitboxes.entrySet()) { + m.put(e.getKey(), Arrays.stream(e.getValue()).map(x -> x.clone()).toArray(Hitbox[]::new)); + } + + copy.blockBaseHitboxes = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateBlockHitboxes other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.blockBaseHitboxes, other.blockBaseHitboxes); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.blockBaseHitboxes); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockParticleSets.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockParticleSets.java new file mode 100644 index 0000000..9ff35d9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockParticleSets.java @@ -0,0 +1,221 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.BlockParticleSet; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateBlockParticleSets implements Packet { + public static final int PACKET_ID = 44; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public Map blockParticleSets; + + @Override + public int getId() { + return 44; + } + + public UpdateBlockParticleSets() { + } + + public UpdateBlockParticleSets(@Nonnull UpdateType type, @Nullable Map blockParticleSets) { + this.type = type; + this.blockParticleSets = blockParticleSets; + } + + public UpdateBlockParticleSets(@Nonnull UpdateBlockParticleSets other) { + this.type = other.type; + this.blockParticleSets = other.blockParticleSets; + } + + @Nonnull + public static UpdateBlockParticleSets deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateBlockParticleSets obj = new UpdateBlockParticleSets(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int blockParticleSetsCount = VarInt.peek(buf, pos); + if (blockParticleSetsCount < 0) { + throw ProtocolException.negativeLength("BlockParticleSets", blockParticleSetsCount); + } + + if (blockParticleSetsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("BlockParticleSets", blockParticleSetsCount, 4096000); + } + + pos += VarInt.size(blockParticleSetsCount); + obj.blockParticleSets = new HashMap<>(blockParticleSetsCount); + + for (int i = 0; i < blockParticleSetsCount; i++) { + int keyLen = VarInt.peek(buf, pos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, pos); + String key = PacketIO.readVarString(buf, pos); + pos += keyVarLen + keyLen; + BlockParticleSet val = BlockParticleSet.deserialize(buf, pos); + pos += BlockParticleSet.computeBytesConsumed(buf, pos); + if (obj.blockParticleSets.put(key, val) != null) { + throw ProtocolException.duplicateKey("blockParticleSets", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + pos += BlockParticleSet.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.blockParticleSets != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.blockParticleSets != null) { + if (this.blockParticleSets.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("BlockParticleSets", this.blockParticleSets.size(), 4096000); + } + + VarInt.write(buf, this.blockParticleSets.size()); + + for (Entry e : this.blockParticleSets.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.blockParticleSets != null) { + int blockParticleSetsSize = 0; + + for (Entry kvp : this.blockParticleSets.entrySet()) { + blockParticleSetsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.blockParticleSets.size()) + blockParticleSetsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int blockParticleSetsCount = VarInt.peek(buffer, pos); + if (blockParticleSetsCount < 0) { + return ValidationResult.error("Invalid dictionary count for BlockParticleSets"); + } + + if (blockParticleSetsCount > 4096000) { + return ValidationResult.error("BlockParticleSets exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < blockParticleSetsCount; i++) { + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += BlockParticleSet.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateBlockParticleSets clone() { + UpdateBlockParticleSets copy = new UpdateBlockParticleSets(); + copy.type = this.type; + if (this.blockParticleSets != null) { + Map m = new HashMap<>(); + + for (Entry e : this.blockParticleSets.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.blockParticleSets = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateBlockParticleSets other) + ? false + : Objects.equals(this.type, other.type) && Objects.equals(this.blockParticleSets, other.blockParticleSets); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.blockParticleSets); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockSets.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockSets.java new file mode 100644 index 0000000..b338177 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockSets.java @@ -0,0 +1,219 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.BlockSet; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateBlockSets implements Packet { + public static final int PACKET_ID = 46; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public Map blockSets; + + @Override + public int getId() { + return 46; + } + + public UpdateBlockSets() { + } + + public UpdateBlockSets(@Nonnull UpdateType type, @Nullable Map blockSets) { + this.type = type; + this.blockSets = blockSets; + } + + public UpdateBlockSets(@Nonnull UpdateBlockSets other) { + this.type = other.type; + this.blockSets = other.blockSets; + } + + @Nonnull + public static UpdateBlockSets deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateBlockSets obj = new UpdateBlockSets(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int blockSetsCount = VarInt.peek(buf, pos); + if (blockSetsCount < 0) { + throw ProtocolException.negativeLength("BlockSets", blockSetsCount); + } + + if (blockSetsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("BlockSets", blockSetsCount, 4096000); + } + + pos += VarInt.size(blockSetsCount); + obj.blockSets = new HashMap<>(blockSetsCount); + + for (int i = 0; i < blockSetsCount; i++) { + int keyLen = VarInt.peek(buf, pos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, pos); + String key = PacketIO.readVarString(buf, pos); + pos += keyVarLen + keyLen; + BlockSet val = BlockSet.deserialize(buf, pos); + pos += BlockSet.computeBytesConsumed(buf, pos); + if (obj.blockSets.put(key, val) != null) { + throw ProtocolException.duplicateKey("blockSets", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + pos += BlockSet.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.blockSets != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.blockSets != null) { + if (this.blockSets.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("BlockSets", this.blockSets.size(), 4096000); + } + + VarInt.write(buf, this.blockSets.size()); + + for (Entry e : this.blockSets.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.blockSets != null) { + int blockSetsSize = 0; + + for (Entry kvp : this.blockSets.entrySet()) { + blockSetsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.blockSets.size()) + blockSetsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int blockSetsCount = VarInt.peek(buffer, pos); + if (blockSetsCount < 0) { + return ValidationResult.error("Invalid dictionary count for BlockSets"); + } + + if (blockSetsCount > 4096000) { + return ValidationResult.error("BlockSets exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < blockSetsCount; i++) { + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += BlockSet.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateBlockSets clone() { + UpdateBlockSets copy = new UpdateBlockSets(); + copy.type = this.type; + if (this.blockSets != null) { + Map m = new HashMap<>(); + + for (Entry e : this.blockSets.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.blockSets = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateBlockSets other) ? false : Objects.equals(this.type, other.type) && Objects.equals(this.blockSets, other.blockSets); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.blockSets); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockSoundSets.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockSoundSets.java new file mode 100644 index 0000000..500a6ec --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockSoundSets.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.BlockSoundSet; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateBlockSoundSets implements Packet { + public static final int PACKET_ID = 42; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map blockSoundSets; + + @Override + public int getId() { + return 42; + } + + public UpdateBlockSoundSets() { + } + + public UpdateBlockSoundSets(@Nonnull UpdateType type, int maxId, @Nullable Map blockSoundSets) { + this.type = type; + this.maxId = maxId; + this.blockSoundSets = blockSoundSets; + } + + public UpdateBlockSoundSets(@Nonnull UpdateBlockSoundSets other) { + this.type = other.type; + this.maxId = other.maxId; + this.blockSoundSets = other.blockSoundSets; + } + + @Nonnull + public static UpdateBlockSoundSets deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateBlockSoundSets obj = new UpdateBlockSoundSets(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int blockSoundSetsCount = VarInt.peek(buf, pos); + if (blockSoundSetsCount < 0) { + throw ProtocolException.negativeLength("BlockSoundSets", blockSoundSetsCount); + } + + if (blockSoundSetsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("BlockSoundSets", blockSoundSetsCount, 4096000); + } + + pos += VarInt.size(blockSoundSetsCount); + obj.blockSoundSets = new HashMap<>(blockSoundSetsCount); + + for (int i = 0; i < blockSoundSetsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + BlockSoundSet val = BlockSoundSet.deserialize(buf, pos); + pos += BlockSoundSet.computeBytesConsumed(buf, pos); + if (obj.blockSoundSets.put(key, val) != null) { + throw ProtocolException.duplicateKey("blockSoundSets", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += BlockSoundSet.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.blockSoundSets != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.blockSoundSets != null) { + if (this.blockSoundSets.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("BlockSoundSets", this.blockSoundSets.size(), 4096000); + } + + VarInt.write(buf, this.blockSoundSets.size()); + + for (Entry e : this.blockSoundSets.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.blockSoundSets != null) { + int blockSoundSetsSize = 0; + + for (Entry kvp : this.blockSoundSets.entrySet()) { + blockSoundSetsSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.blockSoundSets.size()) + blockSoundSetsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int blockSoundSetsCount = VarInt.peek(buffer, pos); + if (blockSoundSetsCount < 0) { + return ValidationResult.error("Invalid dictionary count for BlockSoundSets"); + } + + if (blockSoundSetsCount > 4096000) { + return ValidationResult.error("BlockSoundSets exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < blockSoundSetsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += BlockSoundSet.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateBlockSoundSets clone() { + UpdateBlockSoundSets copy = new UpdateBlockSoundSets(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.blockSoundSets != null) { + Map m = new HashMap<>(); + + for (Entry e : this.blockSoundSets.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.blockSoundSets = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateBlockSoundSets other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.blockSoundSets, other.blockSoundSets); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.blockSoundSets); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockTypes.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockTypes.java new file mode 100644 index 0000000..cec9596 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateBlockTypes.java @@ -0,0 +1,243 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.BlockType; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateBlockTypes implements Packet { + public static final int PACKET_ID = 40; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 10; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 10; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map blockTypes; + public boolean updateBlockTextures; + public boolean updateModelTextures; + public boolean updateModels; + public boolean updateMapGeometry; + + @Override + public int getId() { + return 40; + } + + public UpdateBlockTypes() { + } + + public UpdateBlockTypes( + @Nonnull UpdateType type, + int maxId, + @Nullable Map blockTypes, + boolean updateBlockTextures, + boolean updateModelTextures, + boolean updateModels, + boolean updateMapGeometry + ) { + this.type = type; + this.maxId = maxId; + this.blockTypes = blockTypes; + this.updateBlockTextures = updateBlockTextures; + this.updateModelTextures = updateModelTextures; + this.updateModels = updateModels; + this.updateMapGeometry = updateMapGeometry; + } + + public UpdateBlockTypes(@Nonnull UpdateBlockTypes other) { + this.type = other.type; + this.maxId = other.maxId; + this.blockTypes = other.blockTypes; + this.updateBlockTextures = other.updateBlockTextures; + this.updateModelTextures = other.updateModelTextures; + this.updateModels = other.updateModels; + this.updateMapGeometry = other.updateMapGeometry; + } + + @Nonnull + public static UpdateBlockTypes deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateBlockTypes obj = new UpdateBlockTypes(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + obj.updateBlockTextures = buf.getByte(offset + 6) != 0; + obj.updateModelTextures = buf.getByte(offset + 7) != 0; + obj.updateModels = buf.getByte(offset + 8) != 0; + obj.updateMapGeometry = buf.getByte(offset + 9) != 0; + int pos = offset + 10; + if ((nullBits & 1) != 0) { + int blockTypesCount = VarInt.peek(buf, pos); + if (blockTypesCount < 0) { + throw ProtocolException.negativeLength("BlockTypes", blockTypesCount); + } + + if (blockTypesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("BlockTypes", blockTypesCount, 4096000); + } + + pos += VarInt.size(blockTypesCount); + obj.blockTypes = new HashMap<>(blockTypesCount); + + for (int i = 0; i < blockTypesCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + BlockType val = BlockType.deserialize(buf, pos); + pos += BlockType.computeBytesConsumed(buf, pos); + if (obj.blockTypes.put(key, val) != null) { + throw ProtocolException.duplicateKey("blockTypes", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 10; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += BlockType.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.blockTypes != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + buf.writeByte(this.updateBlockTextures ? 1 : 0); + buf.writeByte(this.updateModelTextures ? 1 : 0); + buf.writeByte(this.updateModels ? 1 : 0); + buf.writeByte(this.updateMapGeometry ? 1 : 0); + if (this.blockTypes != null) { + if (this.blockTypes.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("BlockTypes", this.blockTypes.size(), 4096000); + } + + VarInt.write(buf, this.blockTypes.size()); + + for (Entry e : this.blockTypes.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 10; + if (this.blockTypes != null) { + int blockTypesSize = 0; + + for (Entry kvp : this.blockTypes.entrySet()) { + blockTypesSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.blockTypes.size()) + blockTypesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 10) { + return ValidationResult.error("Buffer too small: expected at least 10 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 10; + if ((nullBits & 1) != 0) { + int blockTypesCount = VarInt.peek(buffer, pos); + if (blockTypesCount < 0) { + return ValidationResult.error("Invalid dictionary count for BlockTypes"); + } + + if (blockTypesCount > 4096000) { + return ValidationResult.error("BlockTypes exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < blockTypesCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += BlockType.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateBlockTypes clone() { + UpdateBlockTypes copy = new UpdateBlockTypes(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.blockTypes != null) { + Map m = new HashMap<>(); + + for (Entry e : this.blockTypes.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.blockTypes = m; + } + + copy.updateBlockTextures = this.updateBlockTextures; + copy.updateModelTextures = this.updateModelTextures; + copy.updateModels = this.updateModels; + copy.updateMapGeometry = this.updateMapGeometry; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateBlockTypes other) + ? false + : Objects.equals(this.type, other.type) + && this.maxId == other.maxId + && Objects.equals(this.blockTypes, other.blockTypes) + && this.updateBlockTextures == other.updateBlockTextures + && this.updateModelTextures == other.updateModelTextures + && this.updateModels == other.updateModels + && this.updateMapGeometry == other.updateMapGeometry; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.blockTypes, this.updateBlockTextures, this.updateModelTextures, this.updateModels, this.updateMapGeometry); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateCameraShake.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateCameraShake.java new file mode 100644 index 0000000..ce620b8 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateCameraShake.java @@ -0,0 +1,197 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.CameraShake; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateCameraShake implements Packet { + public static final int PACKET_ID = 77; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public Map profiles; + + @Override + public int getId() { + return 77; + } + + public UpdateCameraShake() { + } + + public UpdateCameraShake(@Nonnull UpdateType type, @Nullable Map profiles) { + this.type = type; + this.profiles = profiles; + } + + public UpdateCameraShake(@Nonnull UpdateCameraShake other) { + this.type = other.type; + this.profiles = other.profiles; + } + + @Nonnull + public static UpdateCameraShake deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateCameraShake obj = new UpdateCameraShake(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int profilesCount = VarInt.peek(buf, pos); + if (profilesCount < 0) { + throw ProtocolException.negativeLength("Profiles", profilesCount); + } + + if (profilesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Profiles", profilesCount, 4096000); + } + + pos += VarInt.size(profilesCount); + obj.profiles = new HashMap<>(profilesCount); + + for (int i = 0; i < profilesCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + CameraShake val = CameraShake.deserialize(buf, pos); + pos += CameraShake.computeBytesConsumed(buf, pos); + if (obj.profiles.put(key, val) != null) { + throw ProtocolException.duplicateKey("profiles", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += CameraShake.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.profiles != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.profiles != null) { + if (this.profiles.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Profiles", this.profiles.size(), 4096000); + } + + VarInt.write(buf, this.profiles.size()); + + for (Entry e : this.profiles.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.profiles != null) { + int profilesSize = 0; + + for (Entry kvp : this.profiles.entrySet()) { + profilesSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.profiles.size()) + profilesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int profilesCount = VarInt.peek(buffer, pos); + if (profilesCount < 0) { + return ValidationResult.error("Invalid dictionary count for Profiles"); + } + + if (profilesCount > 4096000) { + return ValidationResult.error("Profiles exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < profilesCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += CameraShake.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateCameraShake clone() { + UpdateCameraShake copy = new UpdateCameraShake(); + copy.type = this.type; + if (this.profiles != null) { + Map m = new HashMap<>(); + + for (Entry e : this.profiles.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.profiles = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateCameraShake other) ? false : Objects.equals(this.type, other.type) && Objects.equals(this.profiles, other.profiles); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.profiles); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateEntityEffects.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateEntityEffects.java new file mode 100644 index 0000000..1b53e62 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateEntityEffects.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.EntityEffect; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateEntityEffects implements Packet { + public static final int PACKET_ID = 51; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map entityEffects; + + @Override + public int getId() { + return 51; + } + + public UpdateEntityEffects() { + } + + public UpdateEntityEffects(@Nonnull UpdateType type, int maxId, @Nullable Map entityEffects) { + this.type = type; + this.maxId = maxId; + this.entityEffects = entityEffects; + } + + public UpdateEntityEffects(@Nonnull UpdateEntityEffects other) { + this.type = other.type; + this.maxId = other.maxId; + this.entityEffects = other.entityEffects; + } + + @Nonnull + public static UpdateEntityEffects deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateEntityEffects obj = new UpdateEntityEffects(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int entityEffectsCount = VarInt.peek(buf, pos); + if (entityEffectsCount < 0) { + throw ProtocolException.negativeLength("EntityEffects", entityEffectsCount); + } + + if (entityEffectsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("EntityEffects", entityEffectsCount, 4096000); + } + + pos += VarInt.size(entityEffectsCount); + obj.entityEffects = new HashMap<>(entityEffectsCount); + + for (int i = 0; i < entityEffectsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + EntityEffect val = EntityEffect.deserialize(buf, pos); + pos += EntityEffect.computeBytesConsumed(buf, pos); + if (obj.entityEffects.put(key, val) != null) { + throw ProtocolException.duplicateKey("entityEffects", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += EntityEffect.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.entityEffects != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.entityEffects != null) { + if (this.entityEffects.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("EntityEffects", this.entityEffects.size(), 4096000); + } + + VarInt.write(buf, this.entityEffects.size()); + + for (Entry e : this.entityEffects.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.entityEffects != null) { + int entityEffectsSize = 0; + + for (Entry kvp : this.entityEffects.entrySet()) { + entityEffectsSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.entityEffects.size()) + entityEffectsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int entityEffectsCount = VarInt.peek(buffer, pos); + if (entityEffectsCount < 0) { + return ValidationResult.error("Invalid dictionary count for EntityEffects"); + } + + if (entityEffectsCount > 4096000) { + return ValidationResult.error("EntityEffects exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < entityEffectsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += EntityEffect.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateEntityEffects clone() { + UpdateEntityEffects copy = new UpdateEntityEffects(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.entityEffects != null) { + Map m = new HashMap<>(); + + for (Entry e : this.entityEffects.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.entityEffects = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateEntityEffects other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.entityEffects, other.entityEffects); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.entityEffects); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateEntityStatTypes.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateEntityStatTypes.java new file mode 100644 index 0000000..72b948d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateEntityStatTypes.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.EntityStatType; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateEntityStatTypes implements Packet { + public static final int PACKET_ID = 72; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map types; + + @Override + public int getId() { + return 72; + } + + public UpdateEntityStatTypes() { + } + + public UpdateEntityStatTypes(@Nonnull UpdateType type, int maxId, @Nullable Map types) { + this.type = type; + this.maxId = maxId; + this.types = types; + } + + public UpdateEntityStatTypes(@Nonnull UpdateEntityStatTypes other) { + this.type = other.type; + this.maxId = other.maxId; + this.types = other.types; + } + + @Nonnull + public static UpdateEntityStatTypes deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateEntityStatTypes obj = new UpdateEntityStatTypes(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int typesCount = VarInt.peek(buf, pos); + if (typesCount < 0) { + throw ProtocolException.negativeLength("Types", typesCount); + } + + if (typesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Types", typesCount, 4096000); + } + + pos += VarInt.size(typesCount); + obj.types = new HashMap<>(typesCount); + + for (int i = 0; i < typesCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + EntityStatType val = EntityStatType.deserialize(buf, pos); + pos += EntityStatType.computeBytesConsumed(buf, pos); + if (obj.types.put(key, val) != null) { + throw ProtocolException.duplicateKey("types", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += EntityStatType.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.types != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.types != null) { + if (this.types.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Types", this.types.size(), 4096000); + } + + VarInt.write(buf, this.types.size()); + + for (Entry e : this.types.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.types != null) { + int typesSize = 0; + + for (Entry kvp : this.types.entrySet()) { + typesSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.types.size()) + typesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int typesCount = VarInt.peek(buffer, pos); + if (typesCount < 0) { + return ValidationResult.error("Invalid dictionary count for Types"); + } + + if (typesCount > 4096000) { + return ValidationResult.error("Types exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < typesCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += EntityStatType.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateEntityStatTypes clone() { + UpdateEntityStatTypes copy = new UpdateEntityStatTypes(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.types != null) { + Map m = new HashMap<>(); + + for (Entry e : this.types.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.types = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateEntityStatTypes other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.types, other.types); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.types); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateEntityUIComponents.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateEntityUIComponents.java new file mode 100644 index 0000000..88d5634 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateEntityUIComponents.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.EntityUIComponent; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateEntityUIComponents implements Packet { + public static final int PACKET_ID = 73; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map components; + + @Override + public int getId() { + return 73; + } + + public UpdateEntityUIComponents() { + } + + public UpdateEntityUIComponents(@Nonnull UpdateType type, int maxId, @Nullable Map components) { + this.type = type; + this.maxId = maxId; + this.components = components; + } + + public UpdateEntityUIComponents(@Nonnull UpdateEntityUIComponents other) { + this.type = other.type; + this.maxId = other.maxId; + this.components = other.components; + } + + @Nonnull + public static UpdateEntityUIComponents deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateEntityUIComponents obj = new UpdateEntityUIComponents(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int componentsCount = VarInt.peek(buf, pos); + if (componentsCount < 0) { + throw ProtocolException.negativeLength("Components", componentsCount); + } + + if (componentsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Components", componentsCount, 4096000); + } + + pos += VarInt.size(componentsCount); + obj.components = new HashMap<>(componentsCount); + + for (int i = 0; i < componentsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + EntityUIComponent val = EntityUIComponent.deserialize(buf, pos); + pos += EntityUIComponent.computeBytesConsumed(buf, pos); + if (obj.components.put(key, val) != null) { + throw ProtocolException.duplicateKey("components", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += EntityUIComponent.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.components != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.components != null) { + if (this.components.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Components", this.components.size(), 4096000); + } + + VarInt.write(buf, this.components.size()); + + for (Entry e : this.components.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.components != null) { + int componentsSize = 0; + + for (Entry kvp : this.components.entrySet()) { + componentsSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.components.size()) + componentsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int componentsCount = VarInt.peek(buffer, pos); + if (componentsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Components"); + } + + if (componentsCount > 4096000) { + return ValidationResult.error("Components exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < componentsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += EntityUIComponent.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateEntityUIComponents clone() { + UpdateEntityUIComponents copy = new UpdateEntityUIComponents(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.components != null) { + Map m = new HashMap<>(); + + for (Entry e : this.components.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.components = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateEntityUIComponents other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.components, other.components); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.components); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateEnvironments.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateEnvironments.java new file mode 100644 index 0000000..a3f3c40 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateEnvironments.java @@ -0,0 +1,214 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.WorldEnvironment; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateEnvironments implements Packet { + public static final int PACKET_ID = 61; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 7; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 7; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map environments; + public boolean rebuildMapGeometry; + + @Override + public int getId() { + return 61; + } + + public UpdateEnvironments() { + } + + public UpdateEnvironments(@Nonnull UpdateType type, int maxId, @Nullable Map environments, boolean rebuildMapGeometry) { + this.type = type; + this.maxId = maxId; + this.environments = environments; + this.rebuildMapGeometry = rebuildMapGeometry; + } + + public UpdateEnvironments(@Nonnull UpdateEnvironments other) { + this.type = other.type; + this.maxId = other.maxId; + this.environments = other.environments; + this.rebuildMapGeometry = other.rebuildMapGeometry; + } + + @Nonnull + public static UpdateEnvironments deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateEnvironments obj = new UpdateEnvironments(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + obj.rebuildMapGeometry = buf.getByte(offset + 6) != 0; + int pos = offset + 7; + if ((nullBits & 1) != 0) { + int environmentsCount = VarInt.peek(buf, pos); + if (environmentsCount < 0) { + throw ProtocolException.negativeLength("Environments", environmentsCount); + } + + if (environmentsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Environments", environmentsCount, 4096000); + } + + pos += VarInt.size(environmentsCount); + obj.environments = new HashMap<>(environmentsCount); + + for (int i = 0; i < environmentsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + WorldEnvironment val = WorldEnvironment.deserialize(buf, pos); + pos += WorldEnvironment.computeBytesConsumed(buf, pos); + if (obj.environments.put(key, val) != null) { + throw ProtocolException.duplicateKey("environments", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 7; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += WorldEnvironment.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.environments != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + buf.writeByte(this.rebuildMapGeometry ? 1 : 0); + if (this.environments != null) { + if (this.environments.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Environments", this.environments.size(), 4096000); + } + + VarInt.write(buf, this.environments.size()); + + for (Entry e : this.environments.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 7; + if (this.environments != null) { + int environmentsSize = 0; + + for (Entry kvp : this.environments.entrySet()) { + environmentsSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.environments.size()) + environmentsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 7) { + return ValidationResult.error("Buffer too small: expected at least 7 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 7; + if ((nullBits & 1) != 0) { + int environmentsCount = VarInt.peek(buffer, pos); + if (environmentsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Environments"); + } + + if (environmentsCount > 4096000) { + return ValidationResult.error("Environments exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < environmentsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += WorldEnvironment.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateEnvironments clone() { + UpdateEnvironments copy = new UpdateEnvironments(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.environments != null) { + Map m = new HashMap<>(); + + for (Entry e : this.environments.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.environments = m; + } + + copy.rebuildMapGeometry = this.rebuildMapGeometry; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateEnvironments other) + ? false + : Objects.equals(this.type, other.type) + && this.maxId == other.maxId + && Objects.equals(this.environments, other.environments) + && this.rebuildMapGeometry == other.rebuildMapGeometry; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.environments, this.rebuildMapGeometry); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateEqualizerEffects.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateEqualizerEffects.java new file mode 100644 index 0000000..880850b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateEqualizerEffects.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.EqualizerEffect; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateEqualizerEffects implements Packet { + public static final int PACKET_ID = 82; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map effects; + + @Override + public int getId() { + return 82; + } + + public UpdateEqualizerEffects() { + } + + public UpdateEqualizerEffects(@Nonnull UpdateType type, int maxId, @Nullable Map effects) { + this.type = type; + this.maxId = maxId; + this.effects = effects; + } + + public UpdateEqualizerEffects(@Nonnull UpdateEqualizerEffects other) { + this.type = other.type; + this.maxId = other.maxId; + this.effects = other.effects; + } + + @Nonnull + public static UpdateEqualizerEffects deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateEqualizerEffects obj = new UpdateEqualizerEffects(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int effectsCount = VarInt.peek(buf, pos); + if (effectsCount < 0) { + throw ProtocolException.negativeLength("Effects", effectsCount); + } + + if (effectsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Effects", effectsCount, 4096000); + } + + pos += VarInt.size(effectsCount); + obj.effects = new HashMap<>(effectsCount); + + for (int i = 0; i < effectsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + EqualizerEffect val = EqualizerEffect.deserialize(buf, pos); + pos += EqualizerEffect.computeBytesConsumed(buf, pos); + if (obj.effects.put(key, val) != null) { + throw ProtocolException.duplicateKey("effects", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += EqualizerEffect.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.effects != null) { + if (this.effects.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Effects", this.effects.size(), 4096000); + } + + VarInt.write(buf, this.effects.size()); + + for (Entry e : this.effects.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.effects != null) { + int effectsSize = 0; + + for (Entry kvp : this.effects.entrySet()) { + effectsSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.effects.size()) + effectsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int effectsCount = VarInt.peek(buffer, pos); + if (effectsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Effects"); + } + + if (effectsCount > 4096000) { + return ValidationResult.error("Effects exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < effectsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += EqualizerEffect.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateEqualizerEffects clone() { + UpdateEqualizerEffects copy = new UpdateEqualizerEffects(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.effects != null) { + Map m = new HashMap<>(); + + for (Entry e : this.effects.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.effects = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateEqualizerEffects other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.effects, other.effects); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.effects); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateFieldcraftCategories.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateFieldcraftCategories.java new file mode 100644 index 0000000..116c257 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateFieldcraftCategories.java @@ -0,0 +1,188 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.ItemCategory; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateFieldcraftCategories implements Packet { + public static final int PACKET_ID = 58; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public ItemCategory[] itemCategories; + + @Override + public int getId() { + return 58; + } + + public UpdateFieldcraftCategories() { + } + + public UpdateFieldcraftCategories(@Nonnull UpdateType type, @Nullable ItemCategory[] itemCategories) { + this.type = type; + this.itemCategories = itemCategories; + } + + public UpdateFieldcraftCategories(@Nonnull UpdateFieldcraftCategories other) { + this.type = other.type; + this.itemCategories = other.itemCategories; + } + + @Nonnull + public static UpdateFieldcraftCategories deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateFieldcraftCategories obj = new UpdateFieldcraftCategories(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int itemCategoriesCount = VarInt.peek(buf, pos); + if (itemCategoriesCount < 0) { + throw ProtocolException.negativeLength("ItemCategories", itemCategoriesCount); + } + + if (itemCategoriesCount > 4096000) { + throw ProtocolException.arrayTooLong("ItemCategories", itemCategoriesCount, 4096000); + } + + int itemCategoriesVarLen = VarInt.size(itemCategoriesCount); + if (pos + itemCategoriesVarLen + itemCategoriesCount * 6L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("ItemCategories", pos + itemCategoriesVarLen + itemCategoriesCount * 6, buf.readableBytes()); + } + + pos += itemCategoriesVarLen; + obj.itemCategories = new ItemCategory[itemCategoriesCount]; + + for (int i = 0; i < itemCategoriesCount; i++) { + obj.itemCategories[i] = ItemCategory.deserialize(buf, pos); + pos += ItemCategory.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += ItemCategory.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.itemCategories != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.itemCategories != null) { + if (this.itemCategories.length > 4096000) { + throw ProtocolException.arrayTooLong("ItemCategories", this.itemCategories.length, 4096000); + } + + VarInt.write(buf, this.itemCategories.length); + + for (ItemCategory item : this.itemCategories) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.itemCategories != null) { + int itemCategoriesSize = 0; + + for (ItemCategory elem : this.itemCategories) { + itemCategoriesSize += elem.computeSize(); + } + + size += VarInt.size(this.itemCategories.length) + itemCategoriesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int itemCategoriesCount = VarInt.peek(buffer, pos); + if (itemCategoriesCount < 0) { + return ValidationResult.error("Invalid array count for ItemCategories"); + } + + if (itemCategoriesCount > 4096000) { + return ValidationResult.error("ItemCategories exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < itemCategoriesCount; i++) { + ValidationResult structResult = ItemCategory.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ItemCategory in ItemCategories[" + i + "]: " + structResult.error()); + } + + pos += ItemCategory.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateFieldcraftCategories clone() { + UpdateFieldcraftCategories copy = new UpdateFieldcraftCategories(); + copy.type = this.type; + copy.itemCategories = this.itemCategories != null ? Arrays.stream(this.itemCategories).map(e -> e.clone()).toArray(ItemCategory[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateFieldcraftCategories other) + ? false + : Objects.equals(this.type, other.type) && Arrays.equals((Object[])this.itemCategories, (Object[])other.itemCategories); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.type); + return 31 * result + Arrays.hashCode((Object[])this.itemCategories); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateFluidFX.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateFluidFX.java new file mode 100644 index 0000000..8d7e56b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateFluidFX.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.FluidFX; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateFluidFX implements Packet { + public static final int PACKET_ID = 63; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map fluidFX; + + @Override + public int getId() { + return 63; + } + + public UpdateFluidFX() { + } + + public UpdateFluidFX(@Nonnull UpdateType type, int maxId, @Nullable Map fluidFX) { + this.type = type; + this.maxId = maxId; + this.fluidFX = fluidFX; + } + + public UpdateFluidFX(@Nonnull UpdateFluidFX other) { + this.type = other.type; + this.maxId = other.maxId; + this.fluidFX = other.fluidFX; + } + + @Nonnull + public static UpdateFluidFX deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateFluidFX obj = new UpdateFluidFX(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int fluidFXCount = VarInt.peek(buf, pos); + if (fluidFXCount < 0) { + throw ProtocolException.negativeLength("FluidFX", fluidFXCount); + } + + if (fluidFXCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("FluidFX", fluidFXCount, 4096000); + } + + pos += VarInt.size(fluidFXCount); + obj.fluidFX = new HashMap<>(fluidFXCount); + + for (int i = 0; i < fluidFXCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + FluidFX val = FluidFX.deserialize(buf, pos); + pos += FluidFX.computeBytesConsumed(buf, pos); + if (obj.fluidFX.put(key, val) != null) { + throw ProtocolException.duplicateKey("fluidFX", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += FluidFX.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.fluidFX != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.fluidFX != null) { + if (this.fluidFX.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("FluidFX", this.fluidFX.size(), 4096000); + } + + VarInt.write(buf, this.fluidFX.size()); + + for (Entry e : this.fluidFX.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.fluidFX != null) { + int fluidFXSize = 0; + + for (Entry kvp : this.fluidFX.entrySet()) { + fluidFXSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.fluidFX.size()) + fluidFXSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int fluidFXCount = VarInt.peek(buffer, pos); + if (fluidFXCount < 0) { + return ValidationResult.error("Invalid dictionary count for FluidFX"); + } + + if (fluidFXCount > 4096000) { + return ValidationResult.error("FluidFX exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < fluidFXCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += FluidFX.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateFluidFX clone() { + UpdateFluidFX copy = new UpdateFluidFX(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.fluidFX != null) { + Map m = new HashMap<>(); + + for (Entry e : this.fluidFX.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.fluidFX = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateFluidFX other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.fluidFX, other.fluidFX); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.fluidFX); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateFluids.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateFluids.java new file mode 100644 index 0000000..f102399 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateFluids.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Fluid; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateFluids implements Packet { + public static final int PACKET_ID = 83; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map fluids; + + @Override + public int getId() { + return 83; + } + + public UpdateFluids() { + } + + public UpdateFluids(@Nonnull UpdateType type, int maxId, @Nullable Map fluids) { + this.type = type; + this.maxId = maxId; + this.fluids = fluids; + } + + public UpdateFluids(@Nonnull UpdateFluids other) { + this.type = other.type; + this.maxId = other.maxId; + this.fluids = other.fluids; + } + + @Nonnull + public static UpdateFluids deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateFluids obj = new UpdateFluids(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int fluidsCount = VarInt.peek(buf, pos); + if (fluidsCount < 0) { + throw ProtocolException.negativeLength("Fluids", fluidsCount); + } + + if (fluidsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Fluids", fluidsCount, 4096000); + } + + pos += VarInt.size(fluidsCount); + obj.fluids = new HashMap<>(fluidsCount); + + for (int i = 0; i < fluidsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + Fluid val = Fluid.deserialize(buf, pos); + pos += Fluid.computeBytesConsumed(buf, pos); + if (obj.fluids.put(key, val) != null) { + throw ProtocolException.duplicateKey("fluids", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += Fluid.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.fluids != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.fluids != null) { + if (this.fluids.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Fluids", this.fluids.size(), 4096000); + } + + VarInt.write(buf, this.fluids.size()); + + for (Entry e : this.fluids.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.fluids != null) { + int fluidsSize = 0; + + for (Entry kvp : this.fluids.entrySet()) { + fluidsSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.fluids.size()) + fluidsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int fluidsCount = VarInt.peek(buffer, pos); + if (fluidsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Fluids"); + } + + if (fluidsCount > 4096000) { + return ValidationResult.error("Fluids exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < fluidsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += Fluid.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateFluids clone() { + UpdateFluids copy = new UpdateFluids(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.fluids != null) { + Map m = new HashMap<>(); + + for (Entry e : this.fluids.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.fluids = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateFluids other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.fluids, other.fluids); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.fluids); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateHitboxCollisionConfig.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateHitboxCollisionConfig.java new file mode 100644 index 0000000..b3c8d86 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateHitboxCollisionConfig.java @@ -0,0 +1,199 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.HitboxCollisionConfig; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateHitboxCollisionConfig implements Packet { + public static final int PACKET_ID = 74; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 36864011; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map hitboxCollisionConfigs; + + @Override + public int getId() { + return 74; + } + + public UpdateHitboxCollisionConfig() { + } + + public UpdateHitboxCollisionConfig(@Nonnull UpdateType type, int maxId, @Nullable Map hitboxCollisionConfigs) { + this.type = type; + this.maxId = maxId; + this.hitboxCollisionConfigs = hitboxCollisionConfigs; + } + + public UpdateHitboxCollisionConfig(@Nonnull UpdateHitboxCollisionConfig other) { + this.type = other.type; + this.maxId = other.maxId; + this.hitboxCollisionConfigs = other.hitboxCollisionConfigs; + } + + @Nonnull + public static UpdateHitboxCollisionConfig deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateHitboxCollisionConfig obj = new UpdateHitboxCollisionConfig(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int hitboxCollisionConfigsCount = VarInt.peek(buf, pos); + if (hitboxCollisionConfigsCount < 0) { + throw ProtocolException.negativeLength("HitboxCollisionConfigs", hitboxCollisionConfigsCount); + } + + if (hitboxCollisionConfigsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("HitboxCollisionConfigs", hitboxCollisionConfigsCount, 4096000); + } + + pos += VarInt.size(hitboxCollisionConfigsCount); + obj.hitboxCollisionConfigs = new HashMap<>(hitboxCollisionConfigsCount); + + for (int i = 0; i < hitboxCollisionConfigsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + HitboxCollisionConfig val = HitboxCollisionConfig.deserialize(buf, pos); + pos += HitboxCollisionConfig.computeBytesConsumed(buf, pos); + if (obj.hitboxCollisionConfigs.put(key, val) != null) { + throw ProtocolException.duplicateKey("hitboxCollisionConfigs", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += HitboxCollisionConfig.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.hitboxCollisionConfigs != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.hitboxCollisionConfigs != null) { + if (this.hitboxCollisionConfigs.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("HitboxCollisionConfigs", this.hitboxCollisionConfigs.size(), 4096000); + } + + VarInt.write(buf, this.hitboxCollisionConfigs.size()); + + for (Entry e : this.hitboxCollisionConfigs.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.hitboxCollisionConfigs != null) { + size += VarInt.size(this.hitboxCollisionConfigs.size()) + this.hitboxCollisionConfigs.size() * 9; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int hitboxCollisionConfigsCount = VarInt.peek(buffer, pos); + if (hitboxCollisionConfigsCount < 0) { + return ValidationResult.error("Invalid dictionary count for HitboxCollisionConfigs"); + } + + if (hitboxCollisionConfigsCount > 4096000) { + return ValidationResult.error("HitboxCollisionConfigs exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < hitboxCollisionConfigsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += 5; + } + } + + return ValidationResult.OK; + } + } + + public UpdateHitboxCollisionConfig clone() { + UpdateHitboxCollisionConfig copy = new UpdateHitboxCollisionConfig(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.hitboxCollisionConfigs != null) { + Map m = new HashMap<>(); + + for (Entry e : this.hitboxCollisionConfigs.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.hitboxCollisionConfigs = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateHitboxCollisionConfig other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.hitboxCollisionConfigs, other.hitboxCollisionConfigs); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.hitboxCollisionConfigs); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateInteractions.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateInteractions.java new file mode 100644 index 0000000..7d5df16 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateInteractions.java @@ -0,0 +1,196 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateInteractions implements Packet { + public static final int PACKET_ID = 66; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map interactions; + + @Override + public int getId() { + return 66; + } + + public UpdateInteractions() { + } + + public UpdateInteractions(@Nonnull UpdateType type, int maxId, @Nullable Map interactions) { + this.type = type; + this.maxId = maxId; + this.interactions = interactions; + } + + public UpdateInteractions(@Nonnull UpdateInteractions other) { + this.type = other.type; + this.maxId = other.maxId; + this.interactions = other.interactions; + } + + @Nonnull + public static UpdateInteractions deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateInteractions obj = new UpdateInteractions(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int interactionsCount = VarInt.peek(buf, pos); + if (interactionsCount < 0) { + throw ProtocolException.negativeLength("Interactions", interactionsCount); + } + + if (interactionsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Interactions", interactionsCount, 4096000); + } + + pos += VarInt.size(interactionsCount); + obj.interactions = new HashMap<>(interactionsCount); + + for (int i = 0; i < interactionsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + Interaction val = Interaction.deserialize(buf, pos); + pos += Interaction.computeBytesConsumed(buf, pos); + if (obj.interactions.put(key, val) != null) { + throw ProtocolException.duplicateKey("interactions", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += Interaction.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.interactions != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.interactions != null) { + if (this.interactions.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Interactions", this.interactions.size(), 4096000); + } + + VarInt.write(buf, this.interactions.size()); + + for (Entry e : this.interactions.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serializeWithTypeId(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.interactions != null) { + int interactionsSize = 0; + + for (Entry kvp : this.interactions.entrySet()) { + interactionsSize += 4 + kvp.getValue().computeSizeWithTypeId(); + } + + size += VarInt.size(this.interactions.size()) + interactionsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int interactionsCount = VarInt.peek(buffer, pos); + if (interactionsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Interactions"); + } + + if (interactionsCount > 4096000) { + return ValidationResult.error("Interactions exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < interactionsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += Interaction.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateInteractions clone() { + UpdateInteractions copy = new UpdateInteractions(); + copy.type = this.type; + copy.maxId = this.maxId; + copy.interactions = this.interactions != null ? new HashMap<>(this.interactions) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateInteractions other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.interactions, other.interactions); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.interactions); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateItemCategories.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateItemCategories.java new file mode 100644 index 0000000..944f46f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateItemCategories.java @@ -0,0 +1,188 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.ItemCategory; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateItemCategories implements Packet { + public static final int PACKET_ID = 56; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public ItemCategory[] itemCategories; + + @Override + public int getId() { + return 56; + } + + public UpdateItemCategories() { + } + + public UpdateItemCategories(@Nonnull UpdateType type, @Nullable ItemCategory[] itemCategories) { + this.type = type; + this.itemCategories = itemCategories; + } + + public UpdateItemCategories(@Nonnull UpdateItemCategories other) { + this.type = other.type; + this.itemCategories = other.itemCategories; + } + + @Nonnull + public static UpdateItemCategories deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateItemCategories obj = new UpdateItemCategories(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int itemCategoriesCount = VarInt.peek(buf, pos); + if (itemCategoriesCount < 0) { + throw ProtocolException.negativeLength("ItemCategories", itemCategoriesCount); + } + + if (itemCategoriesCount > 4096000) { + throw ProtocolException.arrayTooLong("ItemCategories", itemCategoriesCount, 4096000); + } + + int itemCategoriesVarLen = VarInt.size(itemCategoriesCount); + if (pos + itemCategoriesVarLen + itemCategoriesCount * 6L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("ItemCategories", pos + itemCategoriesVarLen + itemCategoriesCount * 6, buf.readableBytes()); + } + + pos += itemCategoriesVarLen; + obj.itemCategories = new ItemCategory[itemCategoriesCount]; + + for (int i = 0; i < itemCategoriesCount; i++) { + obj.itemCategories[i] = ItemCategory.deserialize(buf, pos); + pos += ItemCategory.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += ItemCategory.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.itemCategories != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.itemCategories != null) { + if (this.itemCategories.length > 4096000) { + throw ProtocolException.arrayTooLong("ItemCategories", this.itemCategories.length, 4096000); + } + + VarInt.write(buf, this.itemCategories.length); + + for (ItemCategory item : this.itemCategories) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.itemCategories != null) { + int itemCategoriesSize = 0; + + for (ItemCategory elem : this.itemCategories) { + itemCategoriesSize += elem.computeSize(); + } + + size += VarInt.size(this.itemCategories.length) + itemCategoriesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int itemCategoriesCount = VarInt.peek(buffer, pos); + if (itemCategoriesCount < 0) { + return ValidationResult.error("Invalid array count for ItemCategories"); + } + + if (itemCategoriesCount > 4096000) { + return ValidationResult.error("ItemCategories exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < itemCategoriesCount; i++) { + ValidationResult structResult = ItemCategory.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ItemCategory in ItemCategories[" + i + "]: " + structResult.error()); + } + + pos += ItemCategory.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateItemCategories clone() { + UpdateItemCategories copy = new UpdateItemCategories(); + copy.type = this.type; + copy.itemCategories = this.itemCategories != null ? Arrays.stream(this.itemCategories).map(e -> e.clone()).toArray(ItemCategory[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateItemCategories other) + ? false + : Objects.equals(this.type, other.type) && Arrays.equals((Object[])this.itemCategories, (Object[])other.itemCategories); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.type); + return 31 * result + Arrays.hashCode((Object[])this.itemCategories); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateItemPlayerAnimations.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateItemPlayerAnimations.java new file mode 100644 index 0000000..296e073 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateItemPlayerAnimations.java @@ -0,0 +1,221 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.ItemPlayerAnimations; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateItemPlayerAnimations implements Packet { + public static final int PACKET_ID = 52; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public Map itemPlayerAnimations; + + @Override + public int getId() { + return 52; + } + + public UpdateItemPlayerAnimations() { + } + + public UpdateItemPlayerAnimations(@Nonnull UpdateType type, @Nullable Map itemPlayerAnimations) { + this.type = type; + this.itemPlayerAnimations = itemPlayerAnimations; + } + + public UpdateItemPlayerAnimations(@Nonnull UpdateItemPlayerAnimations other) { + this.type = other.type; + this.itemPlayerAnimations = other.itemPlayerAnimations; + } + + @Nonnull + public static UpdateItemPlayerAnimations deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateItemPlayerAnimations obj = new UpdateItemPlayerAnimations(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int itemPlayerAnimationsCount = VarInt.peek(buf, pos); + if (itemPlayerAnimationsCount < 0) { + throw ProtocolException.negativeLength("ItemPlayerAnimations", itemPlayerAnimationsCount); + } + + if (itemPlayerAnimationsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("ItemPlayerAnimations", itemPlayerAnimationsCount, 4096000); + } + + pos += VarInt.size(itemPlayerAnimationsCount); + obj.itemPlayerAnimations = new HashMap<>(itemPlayerAnimationsCount); + + for (int i = 0; i < itemPlayerAnimationsCount; i++) { + int keyLen = VarInt.peek(buf, pos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, pos); + String key = PacketIO.readVarString(buf, pos); + pos += keyVarLen + keyLen; + ItemPlayerAnimations val = ItemPlayerAnimations.deserialize(buf, pos); + pos += ItemPlayerAnimations.computeBytesConsumed(buf, pos); + if (obj.itemPlayerAnimations.put(key, val) != null) { + throw ProtocolException.duplicateKey("itemPlayerAnimations", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + pos += ItemPlayerAnimations.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.itemPlayerAnimations != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.itemPlayerAnimations != null) { + if (this.itemPlayerAnimations.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("ItemPlayerAnimations", this.itemPlayerAnimations.size(), 4096000); + } + + VarInt.write(buf, this.itemPlayerAnimations.size()); + + for (Entry e : this.itemPlayerAnimations.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.itemPlayerAnimations != null) { + int itemPlayerAnimationsSize = 0; + + for (Entry kvp : this.itemPlayerAnimations.entrySet()) { + itemPlayerAnimationsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.itemPlayerAnimations.size()) + itemPlayerAnimationsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int itemPlayerAnimationsCount = VarInt.peek(buffer, pos); + if (itemPlayerAnimationsCount < 0) { + return ValidationResult.error("Invalid dictionary count for ItemPlayerAnimations"); + } + + if (itemPlayerAnimationsCount > 4096000) { + return ValidationResult.error("ItemPlayerAnimations exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < itemPlayerAnimationsCount; i++) { + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += ItemPlayerAnimations.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateItemPlayerAnimations clone() { + UpdateItemPlayerAnimations copy = new UpdateItemPlayerAnimations(); + copy.type = this.type; + if (this.itemPlayerAnimations != null) { + Map m = new HashMap<>(); + + for (Entry e : this.itemPlayerAnimations.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.itemPlayerAnimations = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateItemPlayerAnimations other) + ? false + : Objects.equals(this.type, other.type) && Objects.equals(this.itemPlayerAnimations, other.itemPlayerAnimations); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.itemPlayerAnimations); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateItemQualities.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateItemQualities.java new file mode 100644 index 0000000..194d561 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateItemQualities.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.ItemQuality; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateItemQualities implements Packet { + public static final int PACKET_ID = 55; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map itemQualities; + + @Override + public int getId() { + return 55; + } + + public UpdateItemQualities() { + } + + public UpdateItemQualities(@Nonnull UpdateType type, int maxId, @Nullable Map itemQualities) { + this.type = type; + this.maxId = maxId; + this.itemQualities = itemQualities; + } + + public UpdateItemQualities(@Nonnull UpdateItemQualities other) { + this.type = other.type; + this.maxId = other.maxId; + this.itemQualities = other.itemQualities; + } + + @Nonnull + public static UpdateItemQualities deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateItemQualities obj = new UpdateItemQualities(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int itemQualitiesCount = VarInt.peek(buf, pos); + if (itemQualitiesCount < 0) { + throw ProtocolException.negativeLength("ItemQualities", itemQualitiesCount); + } + + if (itemQualitiesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("ItemQualities", itemQualitiesCount, 4096000); + } + + pos += VarInt.size(itemQualitiesCount); + obj.itemQualities = new HashMap<>(itemQualitiesCount); + + for (int i = 0; i < itemQualitiesCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + ItemQuality val = ItemQuality.deserialize(buf, pos); + pos += ItemQuality.computeBytesConsumed(buf, pos); + if (obj.itemQualities.put(key, val) != null) { + throw ProtocolException.duplicateKey("itemQualities", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += ItemQuality.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.itemQualities != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.itemQualities != null) { + if (this.itemQualities.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("ItemQualities", this.itemQualities.size(), 4096000); + } + + VarInt.write(buf, this.itemQualities.size()); + + for (Entry e : this.itemQualities.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.itemQualities != null) { + int itemQualitiesSize = 0; + + for (Entry kvp : this.itemQualities.entrySet()) { + itemQualitiesSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.itemQualities.size()) + itemQualitiesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int itemQualitiesCount = VarInt.peek(buffer, pos); + if (itemQualitiesCount < 0) { + return ValidationResult.error("Invalid dictionary count for ItemQualities"); + } + + if (itemQualitiesCount > 4096000) { + return ValidationResult.error("ItemQualities exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < itemQualitiesCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += ItemQuality.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateItemQualities clone() { + UpdateItemQualities copy = new UpdateItemQualities(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.itemQualities != null) { + Map m = new HashMap<>(); + + for (Entry e : this.itemQualities.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.itemQualities = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateItemQualities other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.itemQualities, other.itemQualities); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.itemQualities); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateItemReticles.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateItemReticles.java new file mode 100644 index 0000000..3bf2c2f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateItemReticles.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.ItemReticleConfig; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateItemReticles implements Packet { + public static final int PACKET_ID = 57; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map itemReticleConfigs; + + @Override + public int getId() { + return 57; + } + + public UpdateItemReticles() { + } + + public UpdateItemReticles(@Nonnull UpdateType type, int maxId, @Nullable Map itemReticleConfigs) { + this.type = type; + this.maxId = maxId; + this.itemReticleConfigs = itemReticleConfigs; + } + + public UpdateItemReticles(@Nonnull UpdateItemReticles other) { + this.type = other.type; + this.maxId = other.maxId; + this.itemReticleConfigs = other.itemReticleConfigs; + } + + @Nonnull + public static UpdateItemReticles deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateItemReticles obj = new UpdateItemReticles(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int itemReticleConfigsCount = VarInt.peek(buf, pos); + if (itemReticleConfigsCount < 0) { + throw ProtocolException.negativeLength("ItemReticleConfigs", itemReticleConfigsCount); + } + + if (itemReticleConfigsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("ItemReticleConfigs", itemReticleConfigsCount, 4096000); + } + + pos += VarInt.size(itemReticleConfigsCount); + obj.itemReticleConfigs = new HashMap<>(itemReticleConfigsCount); + + for (int i = 0; i < itemReticleConfigsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + ItemReticleConfig val = ItemReticleConfig.deserialize(buf, pos); + pos += ItemReticleConfig.computeBytesConsumed(buf, pos); + if (obj.itemReticleConfigs.put(key, val) != null) { + throw ProtocolException.duplicateKey("itemReticleConfigs", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += ItemReticleConfig.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.itemReticleConfigs != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.itemReticleConfigs != null) { + if (this.itemReticleConfigs.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("ItemReticleConfigs", this.itemReticleConfigs.size(), 4096000); + } + + VarInt.write(buf, this.itemReticleConfigs.size()); + + for (Entry e : this.itemReticleConfigs.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.itemReticleConfigs != null) { + int itemReticleConfigsSize = 0; + + for (Entry kvp : this.itemReticleConfigs.entrySet()) { + itemReticleConfigsSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.itemReticleConfigs.size()) + itemReticleConfigsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int itemReticleConfigsCount = VarInt.peek(buffer, pos); + if (itemReticleConfigsCount < 0) { + return ValidationResult.error("Invalid dictionary count for ItemReticleConfigs"); + } + + if (itemReticleConfigsCount > 4096000) { + return ValidationResult.error("ItemReticleConfigs exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < itemReticleConfigsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += ItemReticleConfig.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateItemReticles clone() { + UpdateItemReticles copy = new UpdateItemReticles(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.itemReticleConfigs != null) { + Map m = new HashMap<>(); + + for (Entry e : this.itemReticleConfigs.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.itemReticleConfigs = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateItemReticles other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.itemReticleConfigs, other.itemReticleConfigs); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.itemReticleConfigs); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateItemSoundSets.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateItemSoundSets.java new file mode 100644 index 0000000..ca2a0c3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateItemSoundSets.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.ItemSoundSet; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateItemSoundSets implements Packet { + public static final int PACKET_ID = 43; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map itemSoundSets; + + @Override + public int getId() { + return 43; + } + + public UpdateItemSoundSets() { + } + + public UpdateItemSoundSets(@Nonnull UpdateType type, int maxId, @Nullable Map itemSoundSets) { + this.type = type; + this.maxId = maxId; + this.itemSoundSets = itemSoundSets; + } + + public UpdateItemSoundSets(@Nonnull UpdateItemSoundSets other) { + this.type = other.type; + this.maxId = other.maxId; + this.itemSoundSets = other.itemSoundSets; + } + + @Nonnull + public static UpdateItemSoundSets deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateItemSoundSets obj = new UpdateItemSoundSets(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int itemSoundSetsCount = VarInt.peek(buf, pos); + if (itemSoundSetsCount < 0) { + throw ProtocolException.negativeLength("ItemSoundSets", itemSoundSetsCount); + } + + if (itemSoundSetsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("ItemSoundSets", itemSoundSetsCount, 4096000); + } + + pos += VarInt.size(itemSoundSetsCount); + obj.itemSoundSets = new HashMap<>(itemSoundSetsCount); + + for (int i = 0; i < itemSoundSetsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + ItemSoundSet val = ItemSoundSet.deserialize(buf, pos); + pos += ItemSoundSet.computeBytesConsumed(buf, pos); + if (obj.itemSoundSets.put(key, val) != null) { + throw ProtocolException.duplicateKey("itemSoundSets", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += ItemSoundSet.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.itemSoundSets != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.itemSoundSets != null) { + if (this.itemSoundSets.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("ItemSoundSets", this.itemSoundSets.size(), 4096000); + } + + VarInt.write(buf, this.itemSoundSets.size()); + + for (Entry e : this.itemSoundSets.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.itemSoundSets != null) { + int itemSoundSetsSize = 0; + + for (Entry kvp : this.itemSoundSets.entrySet()) { + itemSoundSetsSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.itemSoundSets.size()) + itemSoundSetsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int itemSoundSetsCount = VarInt.peek(buffer, pos); + if (itemSoundSetsCount < 0) { + return ValidationResult.error("Invalid dictionary count for ItemSoundSets"); + } + + if (itemSoundSetsCount > 4096000) { + return ValidationResult.error("ItemSoundSets exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < itemSoundSetsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += ItemSoundSet.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateItemSoundSets clone() { + UpdateItemSoundSets copy = new UpdateItemSoundSets(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.itemSoundSets != null) { + Map m = new HashMap<>(); + + for (Entry e : this.itemSoundSets.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.itemSoundSets = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateItemSoundSets other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.itemSoundSets, other.itemSoundSets); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.itemSoundSets); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateItems.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateItems.java new file mode 100644 index 0000000..e8cfae5 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateItems.java @@ -0,0 +1,391 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.ItemBase; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateItems implements Packet { + public static final int PACKET_ID = 54; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 12; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public Map items; + @Nullable + public String[] removedItems; + public boolean updateModels; + public boolean updateIcons; + + @Override + public int getId() { + return 54; + } + + public UpdateItems() { + } + + public UpdateItems( + @Nonnull UpdateType type, @Nullable Map items, @Nullable String[] removedItems, boolean updateModels, boolean updateIcons + ) { + this.type = type; + this.items = items; + this.removedItems = removedItems; + this.updateModels = updateModels; + this.updateIcons = updateIcons; + } + + public UpdateItems(@Nonnull UpdateItems other) { + this.type = other.type; + this.items = other.items; + this.removedItems = other.removedItems; + this.updateModels = other.updateModels; + this.updateIcons = other.updateIcons; + } + + @Nonnull + public static UpdateItems deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateItems obj = new UpdateItems(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.updateModels = buf.getByte(offset + 2) != 0; + obj.updateIcons = buf.getByte(offset + 3) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 12 + buf.getIntLE(offset + 4); + int itemsCount = VarInt.peek(buf, varPos0); + if (itemsCount < 0) { + throw ProtocolException.negativeLength("Items", itemsCount); + } + + if (itemsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Items", itemsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + obj.items = new HashMap<>(itemsCount); + int dictPos = varPos0 + varIntLen; + + for (int i = 0; i < itemsCount; i++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + ItemBase val = ItemBase.deserialize(buf, dictPos); + dictPos += ItemBase.computeBytesConsumed(buf, dictPos); + if (obj.items.put(key, val) != null) { + throw ProtocolException.duplicateKey("items", key); + } + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 12 + buf.getIntLE(offset + 8); + int removedItemsCount = VarInt.peek(buf, varPos1); + if (removedItemsCount < 0) { + throw ProtocolException.negativeLength("RemovedItems", removedItemsCount); + } + + if (removedItemsCount > 4096000) { + throw ProtocolException.arrayTooLong("RemovedItems", removedItemsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + removedItemsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("RemovedItems", varPos1 + varIntLen + removedItemsCount * 1, buf.readableBytes()); + } + + obj.removedItems = new String[removedItemsCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < removedItemsCount; i++) { + int strLen = VarInt.peek(buf, elemPos); + if (strLen < 0) { + throw ProtocolException.negativeLength("removedItems[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("removedItems[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, elemPos); + obj.removedItems[i] = PacketIO.readVarString(buf, elemPos); + elemPos += strVarLen + strLen; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 12; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 4); + int pos0 = offset + 12 + fieldOffset0; + int dictLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + pos0 += ItemBase.computeBytesConsumed(buf, pos0); + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 8); + int pos1 = offset + 12 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.items != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.removedItems != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeByte(this.updateModels ? 1 : 0); + buf.writeByte(this.updateIcons ? 1 : 0); + int itemsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int removedItemsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.items != null) { + buf.setIntLE(itemsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.items.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Items", this.items.size(), 4096000); + } + + VarInt.write(buf, this.items.size()); + + for (Entry e : this.items.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(itemsOffsetSlot, -1); + } + + if (this.removedItems != null) { + buf.setIntLE(removedItemsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.removedItems.length > 4096000) { + throw ProtocolException.arrayTooLong("RemovedItems", this.removedItems.length, 4096000); + } + + VarInt.write(buf, this.removedItems.length); + + for (String item : this.removedItems) { + PacketIO.writeVarString(buf, item, 4096000); + } + } else { + buf.setIntLE(removedItemsOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 12; + if (this.items != null) { + int itemsSize = 0; + + for (Entry kvp : this.items.entrySet()) { + itemsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.items.size()) + itemsSize; + } + + if (this.removedItems != null) { + int removedItemsSize = 0; + + for (String elem : this.removedItems) { + removedItemsSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.removedItems.length) + removedItemsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 12) { + return ValidationResult.error("Buffer too small: expected at least 12 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int itemsOffset = buffer.getIntLE(offset + 4); + if (itemsOffset < 0) { + return ValidationResult.error("Invalid offset for Items"); + } + + int pos = offset + 12 + itemsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Items"); + } + + int itemsCount = VarInt.peek(buffer, pos); + if (itemsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Items"); + } + + if (itemsCount > 4096000) { + return ValidationResult.error("Items exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < itemsCount; i++) { + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += ItemBase.computeBytesConsumed(buffer, pos); + } + } + + if ((nullBits & 2) != 0) { + int removedItemsOffset = buffer.getIntLE(offset + 8); + if (removedItemsOffset < 0) { + return ValidationResult.error("Invalid offset for RemovedItems"); + } + + int posx = offset + 12 + removedItemsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for RemovedItems"); + } + + int removedItemsCount = VarInt.peek(buffer, posx); + if (removedItemsCount < 0) { + return ValidationResult.error("Invalid array count for RemovedItems"); + } + + if (removedItemsCount > 4096000) { + return ValidationResult.error("RemovedItems exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < removedItemsCount; i++) { + int strLen = VarInt.peek(buffer, posx); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in RemovedItems"); + } + + posx += VarInt.length(buffer, posx); + posx += strLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in RemovedItems"); + } + } + } + + return ValidationResult.OK; + } + } + + public UpdateItems clone() { + UpdateItems copy = new UpdateItems(); + copy.type = this.type; + if (this.items != null) { + Map m = new HashMap<>(); + + for (Entry e : this.items.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.items = m; + } + + copy.removedItems = this.removedItems != null ? Arrays.copyOf(this.removedItems, this.removedItems.length) : null; + copy.updateModels = this.updateModels; + copy.updateIcons = this.updateIcons; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateItems other) + ? false + : Objects.equals(this.type, other.type) + && Objects.equals(this.items, other.items) + && Arrays.equals((Object[])this.removedItems, (Object[])other.removedItems) + && this.updateModels == other.updateModels + && this.updateIcons == other.updateIcons; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.type); + result = 31 * result + Objects.hashCode(this.items); + result = 31 * result + Arrays.hashCode((Object[])this.removedItems); + result = 31 * result + Boolean.hashCode(this.updateModels); + return 31 * result + Boolean.hashCode(this.updateIcons); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateModelvfxs.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateModelvfxs.java new file mode 100644 index 0000000..ae366ae --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateModelvfxs.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.ModelVFX; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateModelvfxs implements Packet { + public static final int PACKET_ID = 53; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map modelVFXs; + + @Override + public int getId() { + return 53; + } + + public UpdateModelvfxs() { + } + + public UpdateModelvfxs(@Nonnull UpdateType type, int maxId, @Nullable Map modelVFXs) { + this.type = type; + this.maxId = maxId; + this.modelVFXs = modelVFXs; + } + + public UpdateModelvfxs(@Nonnull UpdateModelvfxs other) { + this.type = other.type; + this.maxId = other.maxId; + this.modelVFXs = other.modelVFXs; + } + + @Nonnull + public static UpdateModelvfxs deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateModelvfxs obj = new UpdateModelvfxs(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int modelVFXsCount = VarInt.peek(buf, pos); + if (modelVFXsCount < 0) { + throw ProtocolException.negativeLength("ModelVFXs", modelVFXsCount); + } + + if (modelVFXsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("ModelVFXs", modelVFXsCount, 4096000); + } + + pos += VarInt.size(modelVFXsCount); + obj.modelVFXs = new HashMap<>(modelVFXsCount); + + for (int i = 0; i < modelVFXsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + ModelVFX val = ModelVFX.deserialize(buf, pos); + pos += ModelVFX.computeBytesConsumed(buf, pos); + if (obj.modelVFXs.put(key, val) != null) { + throw ProtocolException.duplicateKey("modelVFXs", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += ModelVFX.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.modelVFXs != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.modelVFXs != null) { + if (this.modelVFXs.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("ModelVFXs", this.modelVFXs.size(), 4096000); + } + + VarInt.write(buf, this.modelVFXs.size()); + + for (Entry e : this.modelVFXs.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.modelVFXs != null) { + int modelVFXsSize = 0; + + for (Entry kvp : this.modelVFXs.entrySet()) { + modelVFXsSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.modelVFXs.size()) + modelVFXsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int modelVFXsCount = VarInt.peek(buffer, pos); + if (modelVFXsCount < 0) { + return ValidationResult.error("Invalid dictionary count for ModelVFXs"); + } + + if (modelVFXsCount > 4096000) { + return ValidationResult.error("ModelVFXs exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < modelVFXsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += ModelVFX.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateModelvfxs clone() { + UpdateModelvfxs copy = new UpdateModelvfxs(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.modelVFXs != null) { + Map m = new HashMap<>(); + + for (Entry e : this.modelVFXs.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.modelVFXs = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateModelvfxs other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.modelVFXs, other.modelVFXs); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.modelVFXs); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateObjectiveTask.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateObjectiveTask.java new file mode 100644 index 0000000..0f07666 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateObjectiveTask.java @@ -0,0 +1,139 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.ObjectiveTask; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateObjectiveTask implements Packet { + public static final int PACKET_ID = 71; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 21; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 21; + public static final int MAX_SIZE = 16384035; + @Nonnull + public UUID objectiveUuid = new UUID(0L, 0L); + public int taskIndex; + @Nullable + public ObjectiveTask task; + + @Override + public int getId() { + return 71; + } + + public UpdateObjectiveTask() { + } + + public UpdateObjectiveTask(@Nonnull UUID objectiveUuid, int taskIndex, @Nullable ObjectiveTask task) { + this.objectiveUuid = objectiveUuid; + this.taskIndex = taskIndex; + this.task = task; + } + + public UpdateObjectiveTask(@Nonnull UpdateObjectiveTask other) { + this.objectiveUuid = other.objectiveUuid; + this.taskIndex = other.taskIndex; + this.task = other.task; + } + + @Nonnull + public static UpdateObjectiveTask deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateObjectiveTask obj = new UpdateObjectiveTask(); + byte nullBits = buf.getByte(offset); + obj.objectiveUuid = PacketIO.readUUID(buf, offset + 1); + obj.taskIndex = buf.getIntLE(offset + 17); + int pos = offset + 21; + if ((nullBits & 1) != 0) { + obj.task = ObjectiveTask.deserialize(buf, pos); + pos += ObjectiveTask.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 21; + if ((nullBits & 1) != 0) { + pos += ObjectiveTask.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.task != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + PacketIO.writeUUID(buf, this.objectiveUuid); + buf.writeIntLE(this.taskIndex); + if (this.task != null) { + this.task.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 21; + if (this.task != null) { + size += this.task.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 21) { + return ValidationResult.error("Buffer too small: expected at least 21 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 21; + if ((nullBits & 1) != 0) { + ValidationResult taskResult = ObjectiveTask.validateStructure(buffer, pos); + if (!taskResult.isValid()) { + return ValidationResult.error("Invalid Task: " + taskResult.error()); + } + + pos += ObjectiveTask.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public UpdateObjectiveTask clone() { + UpdateObjectiveTask copy = new UpdateObjectiveTask(); + copy.objectiveUuid = this.objectiveUuid; + copy.taskIndex = this.taskIndex; + copy.task = this.task != null ? this.task.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateObjectiveTask other) + ? false + : Objects.equals(this.objectiveUuid, other.objectiveUuid) && this.taskIndex == other.taskIndex && Objects.equals(this.task, other.task); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.objectiveUuid, this.taskIndex, this.task); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateParticleSpawners.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateParticleSpawners.java new file mode 100644 index 0000000..e944b67 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateParticleSpawners.java @@ -0,0 +1,375 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.ParticleSpawner; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateParticleSpawners implements Packet { + public static final int PACKET_ID = 50; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 10; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public Map particleSpawners; + @Nullable + public String[] removedParticleSpawners; + + @Override + public int getId() { + return 50; + } + + public UpdateParticleSpawners() { + } + + public UpdateParticleSpawners(@Nonnull UpdateType type, @Nullable Map particleSpawners, @Nullable String[] removedParticleSpawners) { + this.type = type; + this.particleSpawners = particleSpawners; + this.removedParticleSpawners = removedParticleSpawners; + } + + public UpdateParticleSpawners(@Nonnull UpdateParticleSpawners other) { + this.type = other.type; + this.particleSpawners = other.particleSpawners; + this.removedParticleSpawners = other.removedParticleSpawners; + } + + @Nonnull + public static UpdateParticleSpawners deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateParticleSpawners obj = new UpdateParticleSpawners(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 10 + buf.getIntLE(offset + 2); + int particleSpawnersCount = VarInt.peek(buf, varPos0); + if (particleSpawnersCount < 0) { + throw ProtocolException.negativeLength("ParticleSpawners", particleSpawnersCount); + } + + if (particleSpawnersCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("ParticleSpawners", particleSpawnersCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + obj.particleSpawners = new HashMap<>(particleSpawnersCount); + int dictPos = varPos0 + varIntLen; + + for (int i = 0; i < particleSpawnersCount; i++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + ParticleSpawner val = ParticleSpawner.deserialize(buf, dictPos); + dictPos += ParticleSpawner.computeBytesConsumed(buf, dictPos); + if (obj.particleSpawners.put(key, val) != null) { + throw ProtocolException.duplicateKey("particleSpawners", key); + } + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 10 + buf.getIntLE(offset + 6); + int removedParticleSpawnersCount = VarInt.peek(buf, varPos1); + if (removedParticleSpawnersCount < 0) { + throw ProtocolException.negativeLength("RemovedParticleSpawners", removedParticleSpawnersCount); + } + + if (removedParticleSpawnersCount > 4096000) { + throw ProtocolException.arrayTooLong("RemovedParticleSpawners", removedParticleSpawnersCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + removedParticleSpawnersCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("RemovedParticleSpawners", varPos1 + varIntLen + removedParticleSpawnersCount * 1, buf.readableBytes()); + } + + obj.removedParticleSpawners = new String[removedParticleSpawnersCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < removedParticleSpawnersCount; i++) { + int strLen = VarInt.peek(buf, elemPos); + if (strLen < 0) { + throw ProtocolException.negativeLength("removedParticleSpawners[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("removedParticleSpawners[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, elemPos); + obj.removedParticleSpawners[i] = PacketIO.readVarString(buf, elemPos); + elemPos += strVarLen + strLen; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 10; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 2); + int pos0 = offset + 10 + fieldOffset0; + int dictLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + pos0 += ParticleSpawner.computeBytesConsumed(buf, pos0); + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 6); + int pos1 = offset + 10 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.particleSpawners != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.removedParticleSpawners != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + int particleSpawnersOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int removedParticleSpawnersOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.particleSpawners != null) { + buf.setIntLE(particleSpawnersOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.particleSpawners.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("ParticleSpawners", this.particleSpawners.size(), 4096000); + } + + VarInt.write(buf, this.particleSpawners.size()); + + for (Entry e : this.particleSpawners.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(particleSpawnersOffsetSlot, -1); + } + + if (this.removedParticleSpawners != null) { + buf.setIntLE(removedParticleSpawnersOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.removedParticleSpawners.length > 4096000) { + throw ProtocolException.arrayTooLong("RemovedParticleSpawners", this.removedParticleSpawners.length, 4096000); + } + + VarInt.write(buf, this.removedParticleSpawners.length); + + for (String item : this.removedParticleSpawners) { + PacketIO.writeVarString(buf, item, 4096000); + } + } else { + buf.setIntLE(removedParticleSpawnersOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 10; + if (this.particleSpawners != null) { + int particleSpawnersSize = 0; + + for (Entry kvp : this.particleSpawners.entrySet()) { + particleSpawnersSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.particleSpawners.size()) + particleSpawnersSize; + } + + if (this.removedParticleSpawners != null) { + int removedParticleSpawnersSize = 0; + + for (String elem : this.removedParticleSpawners) { + removedParticleSpawnersSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.removedParticleSpawners.length) + removedParticleSpawnersSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 10) { + return ValidationResult.error("Buffer too small: expected at least 10 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int particleSpawnersOffset = buffer.getIntLE(offset + 2); + if (particleSpawnersOffset < 0) { + return ValidationResult.error("Invalid offset for ParticleSpawners"); + } + + int pos = offset + 10 + particleSpawnersOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ParticleSpawners"); + } + + int particleSpawnersCount = VarInt.peek(buffer, pos); + if (particleSpawnersCount < 0) { + return ValidationResult.error("Invalid dictionary count for ParticleSpawners"); + } + + if (particleSpawnersCount > 4096000) { + return ValidationResult.error("ParticleSpawners exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < particleSpawnersCount; i++) { + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += ParticleSpawner.computeBytesConsumed(buffer, pos); + } + } + + if ((nullBits & 2) != 0) { + int removedParticleSpawnersOffset = buffer.getIntLE(offset + 6); + if (removedParticleSpawnersOffset < 0) { + return ValidationResult.error("Invalid offset for RemovedParticleSpawners"); + } + + int posx = offset + 10 + removedParticleSpawnersOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for RemovedParticleSpawners"); + } + + int removedParticleSpawnersCount = VarInt.peek(buffer, posx); + if (removedParticleSpawnersCount < 0) { + return ValidationResult.error("Invalid array count for RemovedParticleSpawners"); + } + + if (removedParticleSpawnersCount > 4096000) { + return ValidationResult.error("RemovedParticleSpawners exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < removedParticleSpawnersCount; i++) { + int strLen = VarInt.peek(buffer, posx); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in RemovedParticleSpawners"); + } + + posx += VarInt.length(buffer, posx); + posx += strLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in RemovedParticleSpawners"); + } + } + } + + return ValidationResult.OK; + } + } + + public UpdateParticleSpawners clone() { + UpdateParticleSpawners copy = new UpdateParticleSpawners(); + copy.type = this.type; + if (this.particleSpawners != null) { + Map m = new HashMap<>(); + + for (Entry e : this.particleSpawners.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.particleSpawners = m; + } + + copy.removedParticleSpawners = this.removedParticleSpawners != null + ? Arrays.copyOf(this.removedParticleSpawners, this.removedParticleSpawners.length) + : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateParticleSpawners other) + ? false + : Objects.equals(this.type, other.type) + && Objects.equals(this.particleSpawners, other.particleSpawners) + && Arrays.equals((Object[])this.removedParticleSpawners, (Object[])other.removedParticleSpawners); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.type); + result = 31 * result + Objects.hashCode(this.particleSpawners); + return 31 * result + Arrays.hashCode((Object[])this.removedParticleSpawners); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateParticleSystems.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateParticleSystems.java new file mode 100644 index 0000000..2b75539 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateParticleSystems.java @@ -0,0 +1,373 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.ParticleSystem; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateParticleSystems implements Packet { + public static final int PACKET_ID = 49; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 10; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public Map particleSystems; + @Nullable + public String[] removedParticleSystems; + + @Override + public int getId() { + return 49; + } + + public UpdateParticleSystems() { + } + + public UpdateParticleSystems(@Nonnull UpdateType type, @Nullable Map particleSystems, @Nullable String[] removedParticleSystems) { + this.type = type; + this.particleSystems = particleSystems; + this.removedParticleSystems = removedParticleSystems; + } + + public UpdateParticleSystems(@Nonnull UpdateParticleSystems other) { + this.type = other.type; + this.particleSystems = other.particleSystems; + this.removedParticleSystems = other.removedParticleSystems; + } + + @Nonnull + public static UpdateParticleSystems deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateParticleSystems obj = new UpdateParticleSystems(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 10 + buf.getIntLE(offset + 2); + int particleSystemsCount = VarInt.peek(buf, varPos0); + if (particleSystemsCount < 0) { + throw ProtocolException.negativeLength("ParticleSystems", particleSystemsCount); + } + + if (particleSystemsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("ParticleSystems", particleSystemsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + obj.particleSystems = new HashMap<>(particleSystemsCount); + int dictPos = varPos0 + varIntLen; + + for (int i = 0; i < particleSystemsCount; i++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + ParticleSystem val = ParticleSystem.deserialize(buf, dictPos); + dictPos += ParticleSystem.computeBytesConsumed(buf, dictPos); + if (obj.particleSystems.put(key, val) != null) { + throw ProtocolException.duplicateKey("particleSystems", key); + } + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 10 + buf.getIntLE(offset + 6); + int removedParticleSystemsCount = VarInt.peek(buf, varPos1); + if (removedParticleSystemsCount < 0) { + throw ProtocolException.negativeLength("RemovedParticleSystems", removedParticleSystemsCount); + } + + if (removedParticleSystemsCount > 4096000) { + throw ProtocolException.arrayTooLong("RemovedParticleSystems", removedParticleSystemsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + removedParticleSystemsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("RemovedParticleSystems", varPos1 + varIntLen + removedParticleSystemsCount * 1, buf.readableBytes()); + } + + obj.removedParticleSystems = new String[removedParticleSystemsCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < removedParticleSystemsCount; i++) { + int strLen = VarInt.peek(buf, elemPos); + if (strLen < 0) { + throw ProtocolException.negativeLength("removedParticleSystems[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("removedParticleSystems[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, elemPos); + obj.removedParticleSystems[i] = PacketIO.readVarString(buf, elemPos); + elemPos += strVarLen + strLen; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 10; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 2); + int pos0 = offset + 10 + fieldOffset0; + int dictLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + pos0 += ParticleSystem.computeBytesConsumed(buf, pos0); + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 6); + int pos1 = offset + 10 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.particleSystems != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.removedParticleSystems != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + int particleSystemsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int removedParticleSystemsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.particleSystems != null) { + buf.setIntLE(particleSystemsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.particleSystems.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("ParticleSystems", this.particleSystems.size(), 4096000); + } + + VarInt.write(buf, this.particleSystems.size()); + + for (Entry e : this.particleSystems.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(particleSystemsOffsetSlot, -1); + } + + if (this.removedParticleSystems != null) { + buf.setIntLE(removedParticleSystemsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.removedParticleSystems.length > 4096000) { + throw ProtocolException.arrayTooLong("RemovedParticleSystems", this.removedParticleSystems.length, 4096000); + } + + VarInt.write(buf, this.removedParticleSystems.length); + + for (String item : this.removedParticleSystems) { + PacketIO.writeVarString(buf, item, 4096000); + } + } else { + buf.setIntLE(removedParticleSystemsOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 10; + if (this.particleSystems != null) { + int particleSystemsSize = 0; + + for (Entry kvp : this.particleSystems.entrySet()) { + particleSystemsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.particleSystems.size()) + particleSystemsSize; + } + + if (this.removedParticleSystems != null) { + int removedParticleSystemsSize = 0; + + for (String elem : this.removedParticleSystems) { + removedParticleSystemsSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.removedParticleSystems.length) + removedParticleSystemsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 10) { + return ValidationResult.error("Buffer too small: expected at least 10 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int particleSystemsOffset = buffer.getIntLE(offset + 2); + if (particleSystemsOffset < 0) { + return ValidationResult.error("Invalid offset for ParticleSystems"); + } + + int pos = offset + 10 + particleSystemsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ParticleSystems"); + } + + int particleSystemsCount = VarInt.peek(buffer, pos); + if (particleSystemsCount < 0) { + return ValidationResult.error("Invalid dictionary count for ParticleSystems"); + } + + if (particleSystemsCount > 4096000) { + return ValidationResult.error("ParticleSystems exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < particleSystemsCount; i++) { + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += ParticleSystem.computeBytesConsumed(buffer, pos); + } + } + + if ((nullBits & 2) != 0) { + int removedParticleSystemsOffset = buffer.getIntLE(offset + 6); + if (removedParticleSystemsOffset < 0) { + return ValidationResult.error("Invalid offset for RemovedParticleSystems"); + } + + int posx = offset + 10 + removedParticleSystemsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for RemovedParticleSystems"); + } + + int removedParticleSystemsCount = VarInt.peek(buffer, posx); + if (removedParticleSystemsCount < 0) { + return ValidationResult.error("Invalid array count for RemovedParticleSystems"); + } + + if (removedParticleSystemsCount > 4096000) { + return ValidationResult.error("RemovedParticleSystems exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < removedParticleSystemsCount; i++) { + int strLen = VarInt.peek(buffer, posx); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in RemovedParticleSystems"); + } + + posx += VarInt.length(buffer, posx); + posx += strLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in RemovedParticleSystems"); + } + } + } + + return ValidationResult.OK; + } + } + + public UpdateParticleSystems clone() { + UpdateParticleSystems copy = new UpdateParticleSystems(); + copy.type = this.type; + if (this.particleSystems != null) { + Map m = new HashMap<>(); + + for (Entry e : this.particleSystems.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.particleSystems = m; + } + + copy.removedParticleSystems = this.removedParticleSystems != null ? Arrays.copyOf(this.removedParticleSystems, this.removedParticleSystems.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateParticleSystems other) + ? false + : Objects.equals(this.type, other.type) + && Objects.equals(this.particleSystems, other.particleSystems) + && Arrays.equals((Object[])this.removedParticleSystems, (Object[])other.removedParticleSystems); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.type); + result = 31 * result + Objects.hashCode(this.particleSystems); + return 31 * result + Arrays.hashCode((Object[])this.removedParticleSystems); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateProjectileConfigs.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateProjectileConfigs.java new file mode 100644 index 0000000..05ac4d0 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateProjectileConfigs.java @@ -0,0 +1,373 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.ProjectileConfig; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateProjectileConfigs implements Packet { + public static final int PACKET_ID = 85; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 10; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public Map configs; + @Nullable + public String[] removedConfigs; + + @Override + public int getId() { + return 85; + } + + public UpdateProjectileConfigs() { + } + + public UpdateProjectileConfigs(@Nonnull UpdateType type, @Nullable Map configs, @Nullable String[] removedConfigs) { + this.type = type; + this.configs = configs; + this.removedConfigs = removedConfigs; + } + + public UpdateProjectileConfigs(@Nonnull UpdateProjectileConfigs other) { + this.type = other.type; + this.configs = other.configs; + this.removedConfigs = other.removedConfigs; + } + + @Nonnull + public static UpdateProjectileConfigs deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateProjectileConfigs obj = new UpdateProjectileConfigs(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 10 + buf.getIntLE(offset + 2); + int configsCount = VarInt.peek(buf, varPos0); + if (configsCount < 0) { + throw ProtocolException.negativeLength("Configs", configsCount); + } + + if (configsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Configs", configsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + obj.configs = new HashMap<>(configsCount); + int dictPos = varPos0 + varIntLen; + + for (int i = 0; i < configsCount; i++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + ProjectileConfig val = ProjectileConfig.deserialize(buf, dictPos); + dictPos += ProjectileConfig.computeBytesConsumed(buf, dictPos); + if (obj.configs.put(key, val) != null) { + throw ProtocolException.duplicateKey("configs", key); + } + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 10 + buf.getIntLE(offset + 6); + int removedConfigsCount = VarInt.peek(buf, varPos1); + if (removedConfigsCount < 0) { + throw ProtocolException.negativeLength("RemovedConfigs", removedConfigsCount); + } + + if (removedConfigsCount > 4096000) { + throw ProtocolException.arrayTooLong("RemovedConfigs", removedConfigsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + removedConfigsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("RemovedConfigs", varPos1 + varIntLen + removedConfigsCount * 1, buf.readableBytes()); + } + + obj.removedConfigs = new String[removedConfigsCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < removedConfigsCount; i++) { + int strLen = VarInt.peek(buf, elemPos); + if (strLen < 0) { + throw ProtocolException.negativeLength("removedConfigs[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("removedConfigs[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, elemPos); + obj.removedConfigs[i] = PacketIO.readVarString(buf, elemPos); + elemPos += strVarLen + strLen; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 10; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 2); + int pos0 = offset + 10 + fieldOffset0; + int dictLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + pos0 += ProjectileConfig.computeBytesConsumed(buf, pos0); + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 6); + int pos1 = offset + 10 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.configs != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.removedConfigs != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + int configsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int removedConfigsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.configs != null) { + buf.setIntLE(configsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.configs.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Configs", this.configs.size(), 4096000); + } + + VarInt.write(buf, this.configs.size()); + + for (Entry e : this.configs.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(configsOffsetSlot, -1); + } + + if (this.removedConfigs != null) { + buf.setIntLE(removedConfigsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.removedConfigs.length > 4096000) { + throw ProtocolException.arrayTooLong("RemovedConfigs", this.removedConfigs.length, 4096000); + } + + VarInt.write(buf, this.removedConfigs.length); + + for (String item : this.removedConfigs) { + PacketIO.writeVarString(buf, item, 4096000); + } + } else { + buf.setIntLE(removedConfigsOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 10; + if (this.configs != null) { + int configsSize = 0; + + for (Entry kvp : this.configs.entrySet()) { + configsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.configs.size()) + configsSize; + } + + if (this.removedConfigs != null) { + int removedConfigsSize = 0; + + for (String elem : this.removedConfigs) { + removedConfigsSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.removedConfigs.length) + removedConfigsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 10) { + return ValidationResult.error("Buffer too small: expected at least 10 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int configsOffset = buffer.getIntLE(offset + 2); + if (configsOffset < 0) { + return ValidationResult.error("Invalid offset for Configs"); + } + + int pos = offset + 10 + configsOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Configs"); + } + + int configsCount = VarInt.peek(buffer, pos); + if (configsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Configs"); + } + + if (configsCount > 4096000) { + return ValidationResult.error("Configs exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < configsCount; i++) { + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += ProjectileConfig.computeBytesConsumed(buffer, pos); + } + } + + if ((nullBits & 2) != 0) { + int removedConfigsOffset = buffer.getIntLE(offset + 6); + if (removedConfigsOffset < 0) { + return ValidationResult.error("Invalid offset for RemovedConfigs"); + } + + int posx = offset + 10 + removedConfigsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for RemovedConfigs"); + } + + int removedConfigsCount = VarInt.peek(buffer, posx); + if (removedConfigsCount < 0) { + return ValidationResult.error("Invalid array count for RemovedConfigs"); + } + + if (removedConfigsCount > 4096000) { + return ValidationResult.error("RemovedConfigs exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < removedConfigsCount; i++) { + int strLen = VarInt.peek(buffer, posx); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in RemovedConfigs"); + } + + posx += VarInt.length(buffer, posx); + posx += strLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in RemovedConfigs"); + } + } + } + + return ValidationResult.OK; + } + } + + public UpdateProjectileConfigs clone() { + UpdateProjectileConfigs copy = new UpdateProjectileConfigs(); + copy.type = this.type; + if (this.configs != null) { + Map m = new HashMap<>(); + + for (Entry e : this.configs.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.configs = m; + } + + copy.removedConfigs = this.removedConfigs != null ? Arrays.copyOf(this.removedConfigs, this.removedConfigs.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateProjectileConfigs other) + ? false + : Objects.equals(this.type, other.type) + && Objects.equals(this.configs, other.configs) + && Arrays.equals((Object[])this.removedConfigs, (Object[])other.removedConfigs); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.type); + result = 31 * result + Objects.hashCode(this.configs); + return 31 * result + Arrays.hashCode((Object[])this.removedConfigs); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateRecipes.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateRecipes.java new file mode 100644 index 0000000..fd274c2 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateRecipes.java @@ -0,0 +1,373 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.CraftingRecipe; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateRecipes implements Packet { + public static final int PACKET_ID = 60; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 10; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public Map recipes; + @Nullable + public String[] removedRecipes; + + @Override + public int getId() { + return 60; + } + + public UpdateRecipes() { + } + + public UpdateRecipes(@Nonnull UpdateType type, @Nullable Map recipes, @Nullable String[] removedRecipes) { + this.type = type; + this.recipes = recipes; + this.removedRecipes = removedRecipes; + } + + public UpdateRecipes(@Nonnull UpdateRecipes other) { + this.type = other.type; + this.recipes = other.recipes; + this.removedRecipes = other.removedRecipes; + } + + @Nonnull + public static UpdateRecipes deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateRecipes obj = new UpdateRecipes(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 10 + buf.getIntLE(offset + 2); + int recipesCount = VarInt.peek(buf, varPos0); + if (recipesCount < 0) { + throw ProtocolException.negativeLength("Recipes", recipesCount); + } + + if (recipesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Recipes", recipesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + obj.recipes = new HashMap<>(recipesCount); + int dictPos = varPos0 + varIntLen; + + for (int i = 0; i < recipesCount; i++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + CraftingRecipe val = CraftingRecipe.deserialize(buf, dictPos); + dictPos += CraftingRecipe.computeBytesConsumed(buf, dictPos); + if (obj.recipes.put(key, val) != null) { + throw ProtocolException.duplicateKey("recipes", key); + } + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 10 + buf.getIntLE(offset + 6); + int removedRecipesCount = VarInt.peek(buf, varPos1); + if (removedRecipesCount < 0) { + throw ProtocolException.negativeLength("RemovedRecipes", removedRecipesCount); + } + + if (removedRecipesCount > 4096000) { + throw ProtocolException.arrayTooLong("RemovedRecipes", removedRecipesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + removedRecipesCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("RemovedRecipes", varPos1 + varIntLen + removedRecipesCount * 1, buf.readableBytes()); + } + + obj.removedRecipes = new String[removedRecipesCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < removedRecipesCount; i++) { + int strLen = VarInt.peek(buf, elemPos); + if (strLen < 0) { + throw ProtocolException.negativeLength("removedRecipes[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("removedRecipes[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, elemPos); + obj.removedRecipes[i] = PacketIO.readVarString(buf, elemPos); + elemPos += strVarLen + strLen; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 10; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 2); + int pos0 = offset + 10 + fieldOffset0; + int dictLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + pos0 += CraftingRecipe.computeBytesConsumed(buf, pos0); + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 6); + int pos1 = offset + 10 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.recipes != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.removedRecipes != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + int recipesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int removedRecipesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.recipes != null) { + buf.setIntLE(recipesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.recipes.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Recipes", this.recipes.size(), 4096000); + } + + VarInt.write(buf, this.recipes.size()); + + for (Entry e : this.recipes.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(recipesOffsetSlot, -1); + } + + if (this.removedRecipes != null) { + buf.setIntLE(removedRecipesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.removedRecipes.length > 4096000) { + throw ProtocolException.arrayTooLong("RemovedRecipes", this.removedRecipes.length, 4096000); + } + + VarInt.write(buf, this.removedRecipes.length); + + for (String item : this.removedRecipes) { + PacketIO.writeVarString(buf, item, 4096000); + } + } else { + buf.setIntLE(removedRecipesOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 10; + if (this.recipes != null) { + int recipesSize = 0; + + for (Entry kvp : this.recipes.entrySet()) { + recipesSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.recipes.size()) + recipesSize; + } + + if (this.removedRecipes != null) { + int removedRecipesSize = 0; + + for (String elem : this.removedRecipes) { + removedRecipesSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.removedRecipes.length) + removedRecipesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 10) { + return ValidationResult.error("Buffer too small: expected at least 10 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int recipesOffset = buffer.getIntLE(offset + 2); + if (recipesOffset < 0) { + return ValidationResult.error("Invalid offset for Recipes"); + } + + int pos = offset + 10 + recipesOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Recipes"); + } + + int recipesCount = VarInt.peek(buffer, pos); + if (recipesCount < 0) { + return ValidationResult.error("Invalid dictionary count for Recipes"); + } + + if (recipesCount > 4096000) { + return ValidationResult.error("Recipes exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < recipesCount; i++) { + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += CraftingRecipe.computeBytesConsumed(buffer, pos); + } + } + + if ((nullBits & 2) != 0) { + int removedRecipesOffset = buffer.getIntLE(offset + 6); + if (removedRecipesOffset < 0) { + return ValidationResult.error("Invalid offset for RemovedRecipes"); + } + + int posx = offset + 10 + removedRecipesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for RemovedRecipes"); + } + + int removedRecipesCount = VarInt.peek(buffer, posx); + if (removedRecipesCount < 0) { + return ValidationResult.error("Invalid array count for RemovedRecipes"); + } + + if (removedRecipesCount > 4096000) { + return ValidationResult.error("RemovedRecipes exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < removedRecipesCount; i++) { + int strLen = VarInt.peek(buffer, posx); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in RemovedRecipes"); + } + + posx += VarInt.length(buffer, posx); + posx += strLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in RemovedRecipes"); + } + } + } + + return ValidationResult.OK; + } + } + + public UpdateRecipes clone() { + UpdateRecipes copy = new UpdateRecipes(); + copy.type = this.type; + if (this.recipes != null) { + Map m = new HashMap<>(); + + for (Entry e : this.recipes.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.recipes = m; + } + + copy.removedRecipes = this.removedRecipes != null ? Arrays.copyOf(this.removedRecipes, this.removedRecipes.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateRecipes other) + ? false + : Objects.equals(this.type, other.type) + && Objects.equals(this.recipes, other.recipes) + && Arrays.equals((Object[])this.removedRecipes, (Object[])other.removedRecipes); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.type); + result = 31 * result + Objects.hashCode(this.recipes); + return 31 * result + Arrays.hashCode((Object[])this.removedRecipes); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateRepulsionConfig.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateRepulsionConfig.java new file mode 100644 index 0000000..2ca2b9e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateRepulsionConfig.java @@ -0,0 +1,199 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.RepulsionConfig; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateRepulsionConfig implements Packet { + public static final int PACKET_ID = 75; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 65536011; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map repulsionConfigs; + + @Override + public int getId() { + return 75; + } + + public UpdateRepulsionConfig() { + } + + public UpdateRepulsionConfig(@Nonnull UpdateType type, int maxId, @Nullable Map repulsionConfigs) { + this.type = type; + this.maxId = maxId; + this.repulsionConfigs = repulsionConfigs; + } + + public UpdateRepulsionConfig(@Nonnull UpdateRepulsionConfig other) { + this.type = other.type; + this.maxId = other.maxId; + this.repulsionConfigs = other.repulsionConfigs; + } + + @Nonnull + public static UpdateRepulsionConfig deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateRepulsionConfig obj = new UpdateRepulsionConfig(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int repulsionConfigsCount = VarInt.peek(buf, pos); + if (repulsionConfigsCount < 0) { + throw ProtocolException.negativeLength("RepulsionConfigs", repulsionConfigsCount); + } + + if (repulsionConfigsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("RepulsionConfigs", repulsionConfigsCount, 4096000); + } + + pos += VarInt.size(repulsionConfigsCount); + obj.repulsionConfigs = new HashMap<>(repulsionConfigsCount); + + for (int i = 0; i < repulsionConfigsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + RepulsionConfig val = RepulsionConfig.deserialize(buf, pos); + pos += RepulsionConfig.computeBytesConsumed(buf, pos); + if (obj.repulsionConfigs.put(key, val) != null) { + throw ProtocolException.duplicateKey("repulsionConfigs", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += RepulsionConfig.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.repulsionConfigs != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.repulsionConfigs != null) { + if (this.repulsionConfigs.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("RepulsionConfigs", this.repulsionConfigs.size(), 4096000); + } + + VarInt.write(buf, this.repulsionConfigs.size()); + + for (Entry e : this.repulsionConfigs.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.repulsionConfigs != null) { + size += VarInt.size(this.repulsionConfigs.size()) + this.repulsionConfigs.size() * 16; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int repulsionConfigsCount = VarInt.peek(buffer, pos); + if (repulsionConfigsCount < 0) { + return ValidationResult.error("Invalid dictionary count for RepulsionConfigs"); + } + + if (repulsionConfigsCount > 4096000) { + return ValidationResult.error("RepulsionConfigs exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < repulsionConfigsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += 12; + } + } + + return ValidationResult.OK; + } + } + + public UpdateRepulsionConfig clone() { + UpdateRepulsionConfig copy = new UpdateRepulsionConfig(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.repulsionConfigs != null) { + Map m = new HashMap<>(); + + for (Entry e : this.repulsionConfigs.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.repulsionConfigs = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateRepulsionConfig other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.repulsionConfigs, other.repulsionConfigs); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.repulsionConfigs); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateResourceTypes.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateResourceTypes.java new file mode 100644 index 0000000..bf74099 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateResourceTypes.java @@ -0,0 +1,221 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.ResourceType; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateResourceTypes implements Packet { + public static final int PACKET_ID = 59; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public Map resourceTypes; + + @Override + public int getId() { + return 59; + } + + public UpdateResourceTypes() { + } + + public UpdateResourceTypes(@Nonnull UpdateType type, @Nullable Map resourceTypes) { + this.type = type; + this.resourceTypes = resourceTypes; + } + + public UpdateResourceTypes(@Nonnull UpdateResourceTypes other) { + this.type = other.type; + this.resourceTypes = other.resourceTypes; + } + + @Nonnull + public static UpdateResourceTypes deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateResourceTypes obj = new UpdateResourceTypes(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int resourceTypesCount = VarInt.peek(buf, pos); + if (resourceTypesCount < 0) { + throw ProtocolException.negativeLength("ResourceTypes", resourceTypesCount); + } + + if (resourceTypesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("ResourceTypes", resourceTypesCount, 4096000); + } + + pos += VarInt.size(resourceTypesCount); + obj.resourceTypes = new HashMap<>(resourceTypesCount); + + for (int i = 0; i < resourceTypesCount; i++) { + int keyLen = VarInt.peek(buf, pos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, pos); + String key = PacketIO.readVarString(buf, pos); + pos += keyVarLen + keyLen; + ResourceType val = ResourceType.deserialize(buf, pos); + pos += ResourceType.computeBytesConsumed(buf, pos); + if (obj.resourceTypes.put(key, val) != null) { + throw ProtocolException.duplicateKey("resourceTypes", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + pos += ResourceType.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.resourceTypes != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.resourceTypes != null) { + if (this.resourceTypes.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("ResourceTypes", this.resourceTypes.size(), 4096000); + } + + VarInt.write(buf, this.resourceTypes.size()); + + for (Entry e : this.resourceTypes.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.resourceTypes != null) { + int resourceTypesSize = 0; + + for (Entry kvp : this.resourceTypes.entrySet()) { + resourceTypesSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.resourceTypes.size()) + resourceTypesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int resourceTypesCount = VarInt.peek(buffer, pos); + if (resourceTypesCount < 0) { + return ValidationResult.error("Invalid dictionary count for ResourceTypes"); + } + + if (resourceTypesCount > 4096000) { + return ValidationResult.error("ResourceTypes exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < resourceTypesCount; i++) { + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += ResourceType.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateResourceTypes clone() { + UpdateResourceTypes copy = new UpdateResourceTypes(); + copy.type = this.type; + if (this.resourceTypes != null) { + Map m = new HashMap<>(); + + for (Entry e : this.resourceTypes.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.resourceTypes = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateResourceTypes other) + ? false + : Objects.equals(this.type, other.type) && Objects.equals(this.resourceTypes, other.resourceTypes); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.resourceTypes); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateReverbEffects.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateReverbEffects.java new file mode 100644 index 0000000..3e544b6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateReverbEffects.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.ReverbEffect; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateReverbEffects implements Packet { + public static final int PACKET_ID = 81; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map effects; + + @Override + public int getId() { + return 81; + } + + public UpdateReverbEffects() { + } + + public UpdateReverbEffects(@Nonnull UpdateType type, int maxId, @Nullable Map effects) { + this.type = type; + this.maxId = maxId; + this.effects = effects; + } + + public UpdateReverbEffects(@Nonnull UpdateReverbEffects other) { + this.type = other.type; + this.maxId = other.maxId; + this.effects = other.effects; + } + + @Nonnull + public static UpdateReverbEffects deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateReverbEffects obj = new UpdateReverbEffects(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int effectsCount = VarInt.peek(buf, pos); + if (effectsCount < 0) { + throw ProtocolException.negativeLength("Effects", effectsCount); + } + + if (effectsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Effects", effectsCount, 4096000); + } + + pos += VarInt.size(effectsCount); + obj.effects = new HashMap<>(effectsCount); + + for (int i = 0; i < effectsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + ReverbEffect val = ReverbEffect.deserialize(buf, pos); + pos += ReverbEffect.computeBytesConsumed(buf, pos); + if (obj.effects.put(key, val) != null) { + throw ProtocolException.duplicateKey("effects", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += ReverbEffect.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.effects != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.effects != null) { + if (this.effects.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Effects", this.effects.size(), 4096000); + } + + VarInt.write(buf, this.effects.size()); + + for (Entry e : this.effects.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.effects != null) { + int effectsSize = 0; + + for (Entry kvp : this.effects.entrySet()) { + effectsSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.effects.size()) + effectsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int effectsCount = VarInt.peek(buffer, pos); + if (effectsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Effects"); + } + + if (effectsCount > 4096000) { + return ValidationResult.error("Effects exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < effectsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += ReverbEffect.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateReverbEffects clone() { + UpdateReverbEffects copy = new UpdateReverbEffects(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.effects != null) { + Map m = new HashMap<>(); + + for (Entry e : this.effects.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.effects = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateReverbEffects other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.effects, other.effects); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.effects); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateRootInteractions.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateRootInteractions.java new file mode 100644 index 0000000..143cc2d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateRootInteractions.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.RootInteraction; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateRootInteractions implements Packet { + public static final int PACKET_ID = 67; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map interactions; + + @Override + public int getId() { + return 67; + } + + public UpdateRootInteractions() { + } + + public UpdateRootInteractions(@Nonnull UpdateType type, int maxId, @Nullable Map interactions) { + this.type = type; + this.maxId = maxId; + this.interactions = interactions; + } + + public UpdateRootInteractions(@Nonnull UpdateRootInteractions other) { + this.type = other.type; + this.maxId = other.maxId; + this.interactions = other.interactions; + } + + @Nonnull + public static UpdateRootInteractions deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateRootInteractions obj = new UpdateRootInteractions(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int interactionsCount = VarInt.peek(buf, pos); + if (interactionsCount < 0) { + throw ProtocolException.negativeLength("Interactions", interactionsCount); + } + + if (interactionsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Interactions", interactionsCount, 4096000); + } + + pos += VarInt.size(interactionsCount); + obj.interactions = new HashMap<>(interactionsCount); + + for (int i = 0; i < interactionsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + RootInteraction val = RootInteraction.deserialize(buf, pos); + pos += RootInteraction.computeBytesConsumed(buf, pos); + if (obj.interactions.put(key, val) != null) { + throw ProtocolException.duplicateKey("interactions", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += RootInteraction.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.interactions != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.interactions != null) { + if (this.interactions.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Interactions", this.interactions.size(), 4096000); + } + + VarInt.write(buf, this.interactions.size()); + + for (Entry e : this.interactions.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.interactions != null) { + int interactionsSize = 0; + + for (Entry kvp : this.interactions.entrySet()) { + interactionsSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.interactions.size()) + interactionsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int interactionsCount = VarInt.peek(buffer, pos); + if (interactionsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Interactions"); + } + + if (interactionsCount > 4096000) { + return ValidationResult.error("Interactions exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < interactionsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += RootInteraction.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateRootInteractions clone() { + UpdateRootInteractions copy = new UpdateRootInteractions(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.interactions != null) { + Map m = new HashMap<>(); + + for (Entry e : this.interactions.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.interactions = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateRootInteractions other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.interactions, other.interactions); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.interactions); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateSoundEvents.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateSoundEvents.java new file mode 100644 index 0000000..dd7ac61 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateSoundEvents.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.SoundEvent; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateSoundEvents implements Packet { + public static final int PACKET_ID = 65; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map soundEvents; + + @Override + public int getId() { + return 65; + } + + public UpdateSoundEvents() { + } + + public UpdateSoundEvents(@Nonnull UpdateType type, int maxId, @Nullable Map soundEvents) { + this.type = type; + this.maxId = maxId; + this.soundEvents = soundEvents; + } + + public UpdateSoundEvents(@Nonnull UpdateSoundEvents other) { + this.type = other.type; + this.maxId = other.maxId; + this.soundEvents = other.soundEvents; + } + + @Nonnull + public static UpdateSoundEvents deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateSoundEvents obj = new UpdateSoundEvents(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int soundEventsCount = VarInt.peek(buf, pos); + if (soundEventsCount < 0) { + throw ProtocolException.negativeLength("SoundEvents", soundEventsCount); + } + + if (soundEventsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("SoundEvents", soundEventsCount, 4096000); + } + + pos += VarInt.size(soundEventsCount); + obj.soundEvents = new HashMap<>(soundEventsCount); + + for (int i = 0; i < soundEventsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + SoundEvent val = SoundEvent.deserialize(buf, pos); + pos += SoundEvent.computeBytesConsumed(buf, pos); + if (obj.soundEvents.put(key, val) != null) { + throw ProtocolException.duplicateKey("soundEvents", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += SoundEvent.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.soundEvents != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.soundEvents != null) { + if (this.soundEvents.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("SoundEvents", this.soundEvents.size(), 4096000); + } + + VarInt.write(buf, this.soundEvents.size()); + + for (Entry e : this.soundEvents.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.soundEvents != null) { + int soundEventsSize = 0; + + for (Entry kvp : this.soundEvents.entrySet()) { + soundEventsSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.soundEvents.size()) + soundEventsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int soundEventsCount = VarInt.peek(buffer, pos); + if (soundEventsCount < 0) { + return ValidationResult.error("Invalid dictionary count for SoundEvents"); + } + + if (soundEventsCount > 4096000) { + return ValidationResult.error("SoundEvents exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < soundEventsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += SoundEvent.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateSoundEvents clone() { + UpdateSoundEvents copy = new UpdateSoundEvents(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.soundEvents != null) { + Map m = new HashMap<>(); + + for (Entry e : this.soundEvents.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.soundEvents = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateSoundEvents other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.soundEvents, other.soundEvents); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.soundEvents); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateSoundSets.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateSoundSets.java new file mode 100644 index 0000000..7aa6349 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateSoundSets.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.SoundSet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateSoundSets implements Packet { + public static final int PACKET_ID = 79; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map soundSets; + + @Override + public int getId() { + return 79; + } + + public UpdateSoundSets() { + } + + public UpdateSoundSets(@Nonnull UpdateType type, int maxId, @Nullable Map soundSets) { + this.type = type; + this.maxId = maxId; + this.soundSets = soundSets; + } + + public UpdateSoundSets(@Nonnull UpdateSoundSets other) { + this.type = other.type; + this.maxId = other.maxId; + this.soundSets = other.soundSets; + } + + @Nonnull + public static UpdateSoundSets deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateSoundSets obj = new UpdateSoundSets(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int soundSetsCount = VarInt.peek(buf, pos); + if (soundSetsCount < 0) { + throw ProtocolException.negativeLength("SoundSets", soundSetsCount); + } + + if (soundSetsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("SoundSets", soundSetsCount, 4096000); + } + + pos += VarInt.size(soundSetsCount); + obj.soundSets = new HashMap<>(soundSetsCount); + + for (int i = 0; i < soundSetsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + SoundSet val = SoundSet.deserialize(buf, pos); + pos += SoundSet.computeBytesConsumed(buf, pos); + if (obj.soundSets.put(key, val) != null) { + throw ProtocolException.duplicateKey("soundSets", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += SoundSet.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.soundSets != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.soundSets != null) { + if (this.soundSets.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("SoundSets", this.soundSets.size(), 4096000); + } + + VarInt.write(buf, this.soundSets.size()); + + for (Entry e : this.soundSets.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.soundSets != null) { + int soundSetsSize = 0; + + for (Entry kvp : this.soundSets.entrySet()) { + soundSetsSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.soundSets.size()) + soundSetsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int soundSetsCount = VarInt.peek(buffer, pos); + if (soundSetsCount < 0) { + return ValidationResult.error("Invalid dictionary count for SoundSets"); + } + + if (soundSetsCount > 4096000) { + return ValidationResult.error("SoundSets exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < soundSetsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += SoundSet.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateSoundSets clone() { + UpdateSoundSets copy = new UpdateSoundSets(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.soundSets != null) { + Map m = new HashMap<>(); + + for (Entry e : this.soundSets.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.soundSets = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateSoundSets other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.soundSets, other.soundSets); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.soundSets); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateTagPatterns.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateTagPatterns.java new file mode 100644 index 0000000..f15f123 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateTagPatterns.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.TagPattern; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateTagPatterns implements Packet { + public static final int PACKET_ID = 84; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map patterns; + + @Override + public int getId() { + return 84; + } + + public UpdateTagPatterns() { + } + + public UpdateTagPatterns(@Nonnull UpdateType type, int maxId, @Nullable Map patterns) { + this.type = type; + this.maxId = maxId; + this.patterns = patterns; + } + + public UpdateTagPatterns(@Nonnull UpdateTagPatterns other) { + this.type = other.type; + this.maxId = other.maxId; + this.patterns = other.patterns; + } + + @Nonnull + public static UpdateTagPatterns deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateTagPatterns obj = new UpdateTagPatterns(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int patternsCount = VarInt.peek(buf, pos); + if (patternsCount < 0) { + throw ProtocolException.negativeLength("Patterns", patternsCount); + } + + if (patternsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Patterns", patternsCount, 4096000); + } + + pos += VarInt.size(patternsCount); + obj.patterns = new HashMap<>(patternsCount); + + for (int i = 0; i < patternsCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + TagPattern val = TagPattern.deserialize(buf, pos); + pos += TagPattern.computeBytesConsumed(buf, pos); + if (obj.patterns.put(key, val) != null) { + throw ProtocolException.duplicateKey("patterns", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += TagPattern.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.patterns != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.patterns != null) { + if (this.patterns.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Patterns", this.patterns.size(), 4096000); + } + + VarInt.write(buf, this.patterns.size()); + + for (Entry e : this.patterns.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.patterns != null) { + int patternsSize = 0; + + for (Entry kvp : this.patterns.entrySet()) { + patternsSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.patterns.size()) + patternsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int patternsCount = VarInt.peek(buffer, pos); + if (patternsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Patterns"); + } + + if (patternsCount > 4096000) { + return ValidationResult.error("Patterns exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < patternsCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += TagPattern.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateTagPatterns clone() { + UpdateTagPatterns copy = new UpdateTagPatterns(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.patterns != null) { + Map m = new HashMap<>(); + + for (Entry e : this.patterns.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.patterns = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateTagPatterns other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.patterns, other.patterns); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.patterns); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateTrails.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateTrails.java new file mode 100644 index 0000000..f5e7e83 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateTrails.java @@ -0,0 +1,219 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.Trail; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateTrails implements Packet { + public static final int PACKET_ID = 48; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public Map trails; + + @Override + public int getId() { + return 48; + } + + public UpdateTrails() { + } + + public UpdateTrails(@Nonnull UpdateType type, @Nullable Map trails) { + this.type = type; + this.trails = trails; + } + + public UpdateTrails(@Nonnull UpdateTrails other) { + this.type = other.type; + this.trails = other.trails; + } + + @Nonnull + public static UpdateTrails deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateTrails obj = new UpdateTrails(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int trailsCount = VarInt.peek(buf, pos); + if (trailsCount < 0) { + throw ProtocolException.negativeLength("Trails", trailsCount); + } + + if (trailsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Trails", trailsCount, 4096000); + } + + pos += VarInt.size(trailsCount); + obj.trails = new HashMap<>(trailsCount); + + for (int i = 0; i < trailsCount; i++) { + int keyLen = VarInt.peek(buf, pos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, pos); + String key = PacketIO.readVarString(buf, pos); + pos += keyVarLen + keyLen; + Trail val = Trail.deserialize(buf, pos); + pos += Trail.computeBytesConsumed(buf, pos); + if (obj.trails.put(key, val) != null) { + throw ProtocolException.duplicateKey("trails", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + pos += Trail.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.trails != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.trails != null) { + if (this.trails.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Trails", this.trails.size(), 4096000); + } + + VarInt.write(buf, this.trails.size()); + + for (Entry e : this.trails.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.trails != null) { + int trailsSize = 0; + + for (Entry kvp : this.trails.entrySet()) { + trailsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.trails.size()) + trailsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int trailsCount = VarInt.peek(buffer, pos); + if (trailsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Trails"); + } + + if (trailsCount > 4096000) { + return ValidationResult.error("Trails exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < trailsCount; i++) { + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += Trail.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateTrails clone() { + UpdateTrails copy = new UpdateTrails(); + copy.type = this.type; + if (this.trails != null) { + Map m = new HashMap<>(); + + for (Entry e : this.trails.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.trails = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateTrails other) ? false : Objects.equals(this.type, other.type) && Objects.equals(this.trails, other.trails); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.trails); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateTranslations.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateTranslations.java new file mode 100644 index 0000000..c2fa0ad --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateTranslations.java @@ -0,0 +1,235 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateTranslations implements Packet { + public static final int PACKET_ID = 64; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public Map translations; + + @Override + public int getId() { + return 64; + } + + public UpdateTranslations() { + } + + public UpdateTranslations(@Nonnull UpdateType type, @Nullable Map translations) { + this.type = type; + this.translations = translations; + } + + public UpdateTranslations(@Nonnull UpdateTranslations other) { + this.type = other.type; + this.translations = other.translations; + } + + @Nonnull + public static UpdateTranslations deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateTranslations obj = new UpdateTranslations(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int translationsCount = VarInt.peek(buf, pos); + if (translationsCount < 0) { + throw ProtocolException.negativeLength("Translations", translationsCount); + } + + if (translationsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Translations", translationsCount, 4096000); + } + + pos += VarInt.size(translationsCount); + obj.translations = new HashMap<>(translationsCount); + + for (int i = 0; i < translationsCount; i++) { + int keyLen = VarInt.peek(buf, pos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, pos); + String key = PacketIO.readVarString(buf, pos); + pos += keyVarLen + keyLen; + int valLen = VarInt.peek(buf, pos); + if (valLen < 0) { + throw ProtocolException.negativeLength("val", valLen); + } + + if (valLen > 4096000) { + throw ProtocolException.stringTooLong("val", valLen, 4096000); + } + + int valVarLen = VarInt.length(buf, pos); + String val = PacketIO.readVarString(buf, pos); + pos += valVarLen + valLen; + if (obj.translations.put(key, val) != null) { + throw ProtocolException.duplicateKey("translations", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.translations != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.translations != null) { + if (this.translations.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Translations", this.translations.size(), 4096000); + } + + VarInt.write(buf, this.translations.size()); + + for (Entry e : this.translations.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + PacketIO.writeVarString(buf, e.getValue(), 4096000); + } + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.translations != null) { + int translationsSize = 0; + + for (Entry kvp : this.translations.entrySet()) { + translationsSize += PacketIO.stringSize(kvp.getKey()) + PacketIO.stringSize(kvp.getValue()); + } + + size += VarInt.size(this.translations.size()) + translationsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int translationsCount = VarInt.peek(buffer, pos); + if (translationsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Translations"); + } + + if (translationsCount > 4096000) { + return ValidationResult.error("Translations exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < translationsCount; i++) { + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + int valueLen = VarInt.peek(buffer, pos); + if (valueLen < 0) { + return ValidationResult.error("Invalid string length for value"); + } + + if (valueLen > 4096000) { + return ValidationResult.error("value exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += valueLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public UpdateTranslations clone() { + UpdateTranslations copy = new UpdateTranslations(); + copy.type = this.type; + copy.translations = this.translations != null ? new HashMap<>(this.translations) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateTranslations other) + ? false + : Objects.equals(this.type, other.type) && Objects.equals(this.translations, other.translations); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.translations); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateUnarmedInteractions.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateUnarmedInteractions.java new file mode 100644 index 0000000..337157b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateUnarmedInteractions.java @@ -0,0 +1,180 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateUnarmedInteractions implements Packet { + public static final int PACKET_ID = 68; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 20480007; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public Map interactions; + + @Override + public int getId() { + return 68; + } + + public UpdateUnarmedInteractions() { + } + + public UpdateUnarmedInteractions(@Nonnull UpdateType type, @Nullable Map interactions) { + this.type = type; + this.interactions = interactions; + } + + public UpdateUnarmedInteractions(@Nonnull UpdateUnarmedInteractions other) { + this.type = other.type; + this.interactions = other.interactions; + } + + @Nonnull + public static UpdateUnarmedInteractions deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateUnarmedInteractions obj = new UpdateUnarmedInteractions(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int interactionsCount = VarInt.peek(buf, pos); + if (interactionsCount < 0) { + throw ProtocolException.negativeLength("Interactions", interactionsCount); + } + + if (interactionsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Interactions", interactionsCount, 4096000); + } + + pos += VarInt.size(interactionsCount); + obj.interactions = new HashMap<>(interactionsCount); + + for (int i = 0; i < interactionsCount; i++) { + InteractionType key = InteractionType.fromValue(buf.getByte(pos)); + int val = buf.getIntLE(++pos); + pos += 4; + if (obj.interactions.put(key, val) != null) { + throw ProtocolException.duplicateKey("interactions", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos = ++pos + 4; + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.interactions != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.interactions != null) { + if (this.interactions.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Interactions", this.interactions.size(), 4096000); + } + + VarInt.write(buf, this.interactions.size()); + + for (Entry e : this.interactions.entrySet()) { + buf.writeByte(e.getKey().getValue()); + buf.writeIntLE(e.getValue()); + } + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.interactions != null) { + size += VarInt.size(this.interactions.size()) + this.interactions.size() * 5; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int interactionsCount = VarInt.peek(buffer, pos); + if (interactionsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Interactions"); + } + + if (interactionsCount > 4096000) { + return ValidationResult.error("Interactions exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < interactionsCount; i++) { + pos = ++pos + 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public UpdateUnarmedInteractions clone() { + UpdateUnarmedInteractions copy = new UpdateUnarmedInteractions(); + copy.type = this.type; + copy.interactions = this.interactions != null ? new HashMap<>(this.interactions) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateUnarmedInteractions other) + ? false + : Objects.equals(this.type, other.type) && Objects.equals(this.interactions, other.interactions); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.interactions); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateViewBobbing.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateViewBobbing.java new file mode 100644 index 0000000..3587905 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateViewBobbing.java @@ -0,0 +1,191 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.MovementType; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.ViewBobbing; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateViewBobbing implements Packet { + public static final int PACKET_ID = 76; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + @Nullable + public Map profiles; + + @Override + public int getId() { + return 76; + } + + public UpdateViewBobbing() { + } + + public UpdateViewBobbing(@Nonnull UpdateType type, @Nullable Map profiles) { + this.type = type; + this.profiles = profiles; + } + + public UpdateViewBobbing(@Nonnull UpdateViewBobbing other) { + this.type = other.type; + this.profiles = other.profiles; + } + + @Nonnull + public static UpdateViewBobbing deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateViewBobbing obj = new UpdateViewBobbing(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int profilesCount = VarInt.peek(buf, pos); + if (profilesCount < 0) { + throw ProtocolException.negativeLength("Profiles", profilesCount); + } + + if (profilesCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Profiles", profilesCount, 4096000); + } + + pos += VarInt.size(profilesCount); + obj.profiles = new HashMap<>(profilesCount); + + for (int i = 0; i < profilesCount; i++) { + MovementType key = MovementType.fromValue(buf.getByte(pos)); + ViewBobbing val = ViewBobbing.deserialize(buf, ++pos); + pos += ViewBobbing.computeBytesConsumed(buf, pos); + if (obj.profiles.put(key, val) != null) { + throw ProtocolException.duplicateKey("profiles", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos = ++pos + ViewBobbing.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.profiles != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.profiles != null) { + if (this.profiles.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Profiles", this.profiles.size(), 4096000); + } + + VarInt.write(buf, this.profiles.size()); + + for (Entry e : this.profiles.entrySet()) { + buf.writeByte(e.getKey().getValue()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.profiles != null) { + int profilesSize = 0; + + for (Entry kvp : this.profiles.entrySet()) { + profilesSize += 1 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.profiles.size()) + profilesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int profilesCount = VarInt.peek(buffer, pos); + if (profilesCount < 0) { + return ValidationResult.error("Invalid dictionary count for Profiles"); + } + + if (profilesCount > 4096000) { + return ValidationResult.error("Profiles exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < profilesCount; i++) { + pos = ++pos + ViewBobbing.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateViewBobbing clone() { + UpdateViewBobbing copy = new UpdateViewBobbing(); + copy.type = this.type; + if (this.profiles != null) { + Map m = new HashMap<>(); + + for (Entry e : this.profiles.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.profiles = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateViewBobbing other) ? false : Objects.equals(this.type, other.type) && Objects.equals(this.profiles, other.profiles); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.profiles); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/assets/UpdateWeathers.java b/src/com/hypixel/hytale/protocol/packets/assets/UpdateWeathers.java new file mode 100644 index 0000000..15c840a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/assets/UpdateWeathers.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.protocol.packets.assets; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.Weather; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateWeathers implements Packet { + public static final int PACKET_ID = 47; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public UpdateType type = UpdateType.Init; + public int maxId; + @Nullable + public Map weathers; + + @Override + public int getId() { + return 47; + } + + public UpdateWeathers() { + } + + public UpdateWeathers(@Nonnull UpdateType type, int maxId, @Nullable Map weathers) { + this.type = type; + this.maxId = maxId; + this.weathers = weathers; + } + + public UpdateWeathers(@Nonnull UpdateWeathers other) { + this.type = other.type; + this.maxId = other.maxId; + this.weathers = other.weathers; + } + + @Nonnull + public static UpdateWeathers deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateWeathers obj = new UpdateWeathers(); + byte nullBits = buf.getByte(offset); + obj.type = UpdateType.fromValue(buf.getByte(offset + 1)); + obj.maxId = buf.getIntLE(offset + 2); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int weathersCount = VarInt.peek(buf, pos); + if (weathersCount < 0) { + throw ProtocolException.negativeLength("Weathers", weathersCount); + } + + if (weathersCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Weathers", weathersCount, 4096000); + } + + pos += VarInt.size(weathersCount); + obj.weathers = new HashMap<>(weathersCount); + + for (int i = 0; i < weathersCount; i++) { + int key = buf.getIntLE(pos); + pos += 4; + Weather val = Weather.deserialize(buf, pos); + pos += Weather.computeBytesConsumed(buf, pos); + if (obj.weathers.put(key, val) != null) { + throw ProtocolException.duplicateKey("weathers", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 4; + pos += Weather.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.weathers != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.maxId); + if (this.weathers != null) { + if (this.weathers.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Weathers", this.weathers.size(), 4096000); + } + + VarInt.write(buf, this.weathers.size()); + + for (Entry e : this.weathers.entrySet()) { + buf.writeIntLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.weathers != null) { + int weathersSize = 0; + + for (Entry kvp : this.weathers.entrySet()) { + weathersSize += 4 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.weathers.size()) + weathersSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 1) != 0) { + int weathersCount = VarInt.peek(buffer, pos); + if (weathersCount < 0) { + return ValidationResult.error("Invalid dictionary count for Weathers"); + } + + if (weathersCount > 4096000) { + return ValidationResult.error("Weathers exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < weathersCount; i++) { + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += Weather.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateWeathers clone() { + UpdateWeathers copy = new UpdateWeathers(); + copy.type = this.type; + copy.maxId = this.maxId; + if (this.weathers != null) { + Map m = new HashMap<>(); + + for (Entry e : this.weathers.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.weathers = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateWeathers other) + ? false + : Objects.equals(this.type, other.type) && this.maxId == other.maxId && Objects.equals(this.weathers, other.weathers); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.maxId, this.weathers); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/auth/AuthGrant.java b/src/com/hypixel/hytale/protocol/packets/auth/AuthGrant.java new file mode 100644 index 0000000..8b9228b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/auth/AuthGrant.java @@ -0,0 +1,237 @@ +package com.hypixel.hytale.protocol.packets.auth; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AuthGrant implements Packet { + public static final int PACKET_ID = 11; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 49171; + @Nullable + public String authorizationGrant; + @Nullable + public String serverIdentityToken; + + @Override + public int getId() { + return 11; + } + + public AuthGrant() { + } + + public AuthGrant(@Nullable String authorizationGrant, @Nullable String serverIdentityToken) { + this.authorizationGrant = authorizationGrant; + this.serverIdentityToken = serverIdentityToken; + } + + public AuthGrant(@Nonnull AuthGrant other) { + this.authorizationGrant = other.authorizationGrant; + this.serverIdentityToken = other.serverIdentityToken; + } + + @Nonnull + public static AuthGrant deserialize(@Nonnull ByteBuf buf, int offset) { + AuthGrant obj = new AuthGrant(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int authorizationGrantLen = VarInt.peek(buf, varPos0); + if (authorizationGrantLen < 0) { + throw ProtocolException.negativeLength("AuthorizationGrant", authorizationGrantLen); + } + + if (authorizationGrantLen > 4096) { + throw ProtocolException.stringTooLong("AuthorizationGrant", authorizationGrantLen, 4096); + } + + obj.authorizationGrant = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int serverIdentityTokenLen = VarInt.peek(buf, varPos1); + if (serverIdentityTokenLen < 0) { + throw ProtocolException.negativeLength("ServerIdentityToken", serverIdentityTokenLen); + } + + if (serverIdentityTokenLen > 8192) { + throw ProtocolException.stringTooLong("ServerIdentityToken", serverIdentityTokenLen, 8192); + } + + obj.serverIdentityToken = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.authorizationGrant != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.serverIdentityToken != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int authorizationGrantOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int serverIdentityTokenOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.authorizationGrant != null) { + buf.setIntLE(authorizationGrantOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.authorizationGrant, 4096); + } else { + buf.setIntLE(authorizationGrantOffsetSlot, -1); + } + + if (this.serverIdentityToken != null) { + buf.setIntLE(serverIdentityTokenOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.serverIdentityToken, 8192); + } else { + buf.setIntLE(serverIdentityTokenOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 9; + if (this.authorizationGrant != null) { + size += PacketIO.stringSize(this.authorizationGrant); + } + + if (this.serverIdentityToken != null) { + size += PacketIO.stringSize(this.serverIdentityToken); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int authorizationGrantOffset = buffer.getIntLE(offset + 1); + if (authorizationGrantOffset < 0) { + return ValidationResult.error("Invalid offset for AuthorizationGrant"); + } + + int pos = offset + 9 + authorizationGrantOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for AuthorizationGrant"); + } + + int authorizationGrantLen = VarInt.peek(buffer, pos); + if (authorizationGrantLen < 0) { + return ValidationResult.error("Invalid string length for AuthorizationGrant"); + } + + if (authorizationGrantLen > 4096) { + return ValidationResult.error("AuthorizationGrant exceeds max length 4096"); + } + + pos += VarInt.length(buffer, pos); + pos += authorizationGrantLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading AuthorizationGrant"); + } + } + + if ((nullBits & 2) != 0) { + int serverIdentityTokenOffset = buffer.getIntLE(offset + 5); + if (serverIdentityTokenOffset < 0) { + return ValidationResult.error("Invalid offset for ServerIdentityToken"); + } + + int posx = offset + 9 + serverIdentityTokenOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ServerIdentityToken"); + } + + int serverIdentityTokenLen = VarInt.peek(buffer, posx); + if (serverIdentityTokenLen < 0) { + return ValidationResult.error("Invalid string length for ServerIdentityToken"); + } + + if (serverIdentityTokenLen > 8192) { + return ValidationResult.error("ServerIdentityToken exceeds max length 8192"); + } + + posx += VarInt.length(buffer, posx); + posx += serverIdentityTokenLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ServerIdentityToken"); + } + } + + return ValidationResult.OK; + } + } + + public AuthGrant clone() { + AuthGrant copy = new AuthGrant(); + copy.authorizationGrant = this.authorizationGrant; + copy.serverIdentityToken = this.serverIdentityToken; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AuthGrant other) + ? false + : Objects.equals(this.authorizationGrant, other.authorizationGrant) && Objects.equals(this.serverIdentityToken, other.serverIdentityToken); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.authorizationGrant, this.serverIdentityToken); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/auth/AuthToken.java b/src/com/hypixel/hytale/protocol/packets/auth/AuthToken.java new file mode 100644 index 0000000..2aa925e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/auth/AuthToken.java @@ -0,0 +1,237 @@ +package com.hypixel.hytale.protocol.packets.auth; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AuthToken implements Packet { + public static final int PACKET_ID = 12; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 49171; + @Nullable + public String accessToken; + @Nullable + public String serverAuthorizationGrant; + + @Override + public int getId() { + return 12; + } + + public AuthToken() { + } + + public AuthToken(@Nullable String accessToken, @Nullable String serverAuthorizationGrant) { + this.accessToken = accessToken; + this.serverAuthorizationGrant = serverAuthorizationGrant; + } + + public AuthToken(@Nonnull AuthToken other) { + this.accessToken = other.accessToken; + this.serverAuthorizationGrant = other.serverAuthorizationGrant; + } + + @Nonnull + public static AuthToken deserialize(@Nonnull ByteBuf buf, int offset) { + AuthToken obj = new AuthToken(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int accessTokenLen = VarInt.peek(buf, varPos0); + if (accessTokenLen < 0) { + throw ProtocolException.negativeLength("AccessToken", accessTokenLen); + } + + if (accessTokenLen > 8192) { + throw ProtocolException.stringTooLong("AccessToken", accessTokenLen, 8192); + } + + obj.accessToken = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int serverAuthorizationGrantLen = VarInt.peek(buf, varPos1); + if (serverAuthorizationGrantLen < 0) { + throw ProtocolException.negativeLength("ServerAuthorizationGrant", serverAuthorizationGrantLen); + } + + if (serverAuthorizationGrantLen > 4096) { + throw ProtocolException.stringTooLong("ServerAuthorizationGrant", serverAuthorizationGrantLen, 4096); + } + + obj.serverAuthorizationGrant = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.accessToken != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.serverAuthorizationGrant != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int accessTokenOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int serverAuthorizationGrantOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.accessToken != null) { + buf.setIntLE(accessTokenOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.accessToken, 8192); + } else { + buf.setIntLE(accessTokenOffsetSlot, -1); + } + + if (this.serverAuthorizationGrant != null) { + buf.setIntLE(serverAuthorizationGrantOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.serverAuthorizationGrant, 4096); + } else { + buf.setIntLE(serverAuthorizationGrantOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 9; + if (this.accessToken != null) { + size += PacketIO.stringSize(this.accessToken); + } + + if (this.serverAuthorizationGrant != null) { + size += PacketIO.stringSize(this.serverAuthorizationGrant); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int accessTokenOffset = buffer.getIntLE(offset + 1); + if (accessTokenOffset < 0) { + return ValidationResult.error("Invalid offset for AccessToken"); + } + + int pos = offset + 9 + accessTokenOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for AccessToken"); + } + + int accessTokenLen = VarInt.peek(buffer, pos); + if (accessTokenLen < 0) { + return ValidationResult.error("Invalid string length for AccessToken"); + } + + if (accessTokenLen > 8192) { + return ValidationResult.error("AccessToken exceeds max length 8192"); + } + + pos += VarInt.length(buffer, pos); + pos += accessTokenLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading AccessToken"); + } + } + + if ((nullBits & 2) != 0) { + int serverAuthorizationGrantOffset = buffer.getIntLE(offset + 5); + if (serverAuthorizationGrantOffset < 0) { + return ValidationResult.error("Invalid offset for ServerAuthorizationGrant"); + } + + int posx = offset + 9 + serverAuthorizationGrantOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ServerAuthorizationGrant"); + } + + int serverAuthorizationGrantLen = VarInt.peek(buffer, posx); + if (serverAuthorizationGrantLen < 0) { + return ValidationResult.error("Invalid string length for ServerAuthorizationGrant"); + } + + if (serverAuthorizationGrantLen > 4096) { + return ValidationResult.error("ServerAuthorizationGrant exceeds max length 4096"); + } + + posx += VarInt.length(buffer, posx); + posx += serverAuthorizationGrantLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ServerAuthorizationGrant"); + } + } + + return ValidationResult.OK; + } + } + + public AuthToken clone() { + AuthToken copy = new AuthToken(); + copy.accessToken = this.accessToken; + copy.serverAuthorizationGrant = this.serverAuthorizationGrant; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AuthToken other) + ? false + : Objects.equals(this.accessToken, other.accessToken) && Objects.equals(this.serverAuthorizationGrant, other.serverAuthorizationGrant); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.accessToken, this.serverAuthorizationGrant); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/auth/ClientReferral.java b/src/com/hypixel/hytale/protocol/packets/auth/ClientReferral.java new file mode 100644 index 0000000..6d8dbe9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/auth/ClientReferral.java @@ -0,0 +1,237 @@ +package com.hypixel.hytale.protocol.packets.auth; + +import com.hypixel.hytale.protocol.HostAddress; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ClientReferral implements Packet { + public static final int PACKET_ID = 18; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 5141; + @Nullable + public HostAddress hostTo; + @Nullable + public byte[] data; + + @Override + public int getId() { + return 18; + } + + public ClientReferral() { + } + + public ClientReferral(@Nullable HostAddress hostTo, @Nullable byte[] data) { + this.hostTo = hostTo; + this.data = data; + } + + public ClientReferral(@Nonnull ClientReferral other) { + this.hostTo = other.hostTo; + this.data = other.data; + } + + @Nonnull + public static ClientReferral deserialize(@Nonnull ByteBuf buf, int offset) { + ClientReferral obj = new ClientReferral(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + obj.hostTo = HostAddress.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int dataCount = VarInt.peek(buf, varPos1); + if (dataCount < 0) { + throw ProtocolException.negativeLength("Data", dataCount); + } + + if (dataCount > 4096) { + throw ProtocolException.arrayTooLong("Data", dataCount, 4096); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + dataCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Data", varPos1 + varIntLen + dataCount * 1, buf.readableBytes()); + } + + obj.data = new byte[dataCount]; + + for (int i = 0; i < dataCount; i++) { + obj.data[i] = buf.getByte(varPos1 + varIntLen + i * 1); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + pos0 += HostAddress.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + arrLen * 1; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.hostTo != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.data != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int hostToOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int dataOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.hostTo != null) { + buf.setIntLE(hostToOffsetSlot, buf.writerIndex() - varBlockStart); + this.hostTo.serialize(buf); + } else { + buf.setIntLE(hostToOffsetSlot, -1); + } + + if (this.data != null) { + buf.setIntLE(dataOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.data.length > 4096) { + throw ProtocolException.arrayTooLong("Data", this.data.length, 4096); + } + + VarInt.write(buf, this.data.length); + + for (byte item : this.data) { + buf.writeByte(item); + } + } else { + buf.setIntLE(dataOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 9; + if (this.hostTo != null) { + size += this.hostTo.computeSize(); + } + + if (this.data != null) { + size += VarInt.size(this.data.length) + this.data.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int hostToOffset = buffer.getIntLE(offset + 1); + if (hostToOffset < 0) { + return ValidationResult.error("Invalid offset for HostTo"); + } + + int pos = offset + 9 + hostToOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for HostTo"); + } + + ValidationResult hostToResult = HostAddress.validateStructure(buffer, pos); + if (!hostToResult.isValid()) { + return ValidationResult.error("Invalid HostTo: " + hostToResult.error()); + } + + pos += HostAddress.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int dataOffset = buffer.getIntLE(offset + 5); + if (dataOffset < 0) { + return ValidationResult.error("Invalid offset for Data"); + } + + int posx = offset + 9 + dataOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Data"); + } + + int dataCount = VarInt.peek(buffer, posx); + if (dataCount < 0) { + return ValidationResult.error("Invalid array count for Data"); + } + + if (dataCount > 4096) { + return ValidationResult.error("Data exceeds max length 4096"); + } + + posx += VarInt.length(buffer, posx); + posx += dataCount * 1; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Data"); + } + } + + return ValidationResult.OK; + } + } + + public ClientReferral clone() { + ClientReferral copy = new ClientReferral(); + copy.hostTo = this.hostTo != null ? this.hostTo.clone() : null; + copy.data = this.data != null ? Arrays.copyOf(this.data, this.data.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ClientReferral other) ? false : Objects.equals(this.hostTo, other.hostTo) && Arrays.equals(this.data, other.data); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.hostTo); + return 31 * result + Arrays.hashCode(this.data); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/auth/ConnectAccept.java b/src/com/hypixel/hytale/protocol/packets/auth/ConnectAccept.java new file mode 100644 index 0000000..15bd080 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/auth/ConnectAccept.java @@ -0,0 +1,161 @@ +package com.hypixel.hytale.protocol.packets.auth; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ConnectAccept implements Packet { + public static final int PACKET_ID = 14; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 70; + @Nullable + public byte[] passwordChallenge; + + @Override + public int getId() { + return 14; + } + + public ConnectAccept() { + } + + public ConnectAccept(@Nullable byte[] passwordChallenge) { + this.passwordChallenge = passwordChallenge; + } + + public ConnectAccept(@Nonnull ConnectAccept other) { + this.passwordChallenge = other.passwordChallenge; + } + + @Nonnull + public static ConnectAccept deserialize(@Nonnull ByteBuf buf, int offset) { + ConnectAccept obj = new ConnectAccept(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int passwordChallengeCount = VarInt.peek(buf, pos); + if (passwordChallengeCount < 0) { + throw ProtocolException.negativeLength("PasswordChallenge", passwordChallengeCount); + } + + if (passwordChallengeCount > 64) { + throw ProtocolException.arrayTooLong("PasswordChallenge", passwordChallengeCount, 64); + } + + int passwordChallengeVarLen = VarInt.size(passwordChallengeCount); + if (pos + passwordChallengeVarLen + passwordChallengeCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("PasswordChallenge", pos + passwordChallengeVarLen + passwordChallengeCount * 1, buf.readableBytes()); + } + + pos += passwordChallengeVarLen; + obj.passwordChallenge = new byte[passwordChallengeCount]; + + for (int i = 0; i < passwordChallengeCount; i++) { + obj.passwordChallenge[i] = buf.getByte(pos + i * 1); + } + + pos += passwordChallengeCount * 1; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 1; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.passwordChallenge != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.passwordChallenge != null) { + if (this.passwordChallenge.length > 64) { + throw ProtocolException.arrayTooLong("PasswordChallenge", this.passwordChallenge.length, 64); + } + + VarInt.write(buf, this.passwordChallenge.length); + + for (byte item : this.passwordChallenge) { + buf.writeByte(item); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.passwordChallenge != null) { + size += VarInt.size(this.passwordChallenge.length) + this.passwordChallenge.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int passwordChallengeCount = VarInt.peek(buffer, pos); + if (passwordChallengeCount < 0) { + return ValidationResult.error("Invalid array count for PasswordChallenge"); + } + + if (passwordChallengeCount > 64) { + return ValidationResult.error("PasswordChallenge exceeds max length 64"); + } + + pos += VarInt.length(buffer, pos); + pos += passwordChallengeCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading PasswordChallenge"); + } + } + + return ValidationResult.OK; + } + } + + public ConnectAccept clone() { + ConnectAccept copy = new ConnectAccept(); + copy.passwordChallenge = this.passwordChallenge != null ? Arrays.copyOf(this.passwordChallenge, this.passwordChallenge.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof ConnectAccept other ? Arrays.equals(this.passwordChallenge, other.passwordChallenge) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode(this.passwordChallenge); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/auth/PasswordAccepted.java b/src/com/hypixel/hytale/protocol/packets/auth/PasswordAccepted.java new file mode 100644 index 0000000..d35433a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/auth/PasswordAccepted.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.protocol.packets.auth; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class PasswordAccepted implements Packet { + public static final int PACKET_ID = 16; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public PasswordAccepted() { + } + + @Override + public int getId() { + return 16; + } + + @Nonnull + public static PasswordAccepted deserialize(@Nonnull ByteBuf buf, int offset) { + return new PasswordAccepted(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public PasswordAccepted clone() { + return new PasswordAccepted(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof PasswordAccepted other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/auth/PasswordRejected.java b/src/com/hypixel/hytale/protocol/packets/auth/PasswordRejected.java new file mode 100644 index 0000000..5708348 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/auth/PasswordRejected.java @@ -0,0 +1,170 @@ +package com.hypixel.hytale.protocol.packets.auth; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PasswordRejected implements Packet { + public static final int PACKET_ID = 17; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 74; + @Nullable + public byte[] newChallenge; + public int attemptsRemaining; + + @Override + public int getId() { + return 17; + } + + public PasswordRejected() { + } + + public PasswordRejected(@Nullable byte[] newChallenge, int attemptsRemaining) { + this.newChallenge = newChallenge; + this.attemptsRemaining = attemptsRemaining; + } + + public PasswordRejected(@Nonnull PasswordRejected other) { + this.newChallenge = other.newChallenge; + this.attemptsRemaining = other.attemptsRemaining; + } + + @Nonnull + public static PasswordRejected deserialize(@Nonnull ByteBuf buf, int offset) { + PasswordRejected obj = new PasswordRejected(); + byte nullBits = buf.getByte(offset); + obj.attemptsRemaining = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int newChallengeCount = VarInt.peek(buf, pos); + if (newChallengeCount < 0) { + throw ProtocolException.negativeLength("NewChallenge", newChallengeCount); + } + + if (newChallengeCount > 64) { + throw ProtocolException.arrayTooLong("NewChallenge", newChallengeCount, 64); + } + + int newChallengeVarLen = VarInt.size(newChallengeCount); + if (pos + newChallengeVarLen + newChallengeCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("NewChallenge", pos + newChallengeVarLen + newChallengeCount * 1, buf.readableBytes()); + } + + pos += newChallengeVarLen; + obj.newChallenge = new byte[newChallengeCount]; + + for (int i = 0; i < newChallengeCount; i++) { + obj.newChallenge[i] = buf.getByte(pos + i * 1); + } + + pos += newChallengeCount * 1; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 1; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.newChallenge != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.attemptsRemaining); + if (this.newChallenge != null) { + if (this.newChallenge.length > 64) { + throw ProtocolException.arrayTooLong("NewChallenge", this.newChallenge.length, 64); + } + + VarInt.write(buf, this.newChallenge.length); + + for (byte item : this.newChallenge) { + buf.writeByte(item); + } + } + } + + @Override + public int computeSize() { + int size = 5; + if (this.newChallenge != null) { + size += VarInt.size(this.newChallenge.length) + this.newChallenge.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int newChallengeCount = VarInt.peek(buffer, pos); + if (newChallengeCount < 0) { + return ValidationResult.error("Invalid array count for NewChallenge"); + } + + if (newChallengeCount > 64) { + return ValidationResult.error("NewChallenge exceeds max length 64"); + } + + pos += VarInt.length(buffer, pos); + pos += newChallengeCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading NewChallenge"); + } + } + + return ValidationResult.OK; + } + } + + public PasswordRejected clone() { + PasswordRejected copy = new PasswordRejected(); + copy.newChallenge = this.newChallenge != null ? Arrays.copyOf(this.newChallenge, this.newChallenge.length) : null; + copy.attemptsRemaining = this.attemptsRemaining; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof PasswordRejected other) + ? false + : Arrays.equals(this.newChallenge, other.newChallenge) && this.attemptsRemaining == other.attemptsRemaining; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode(this.newChallenge); + return 31 * result + Integer.hashCode(this.attemptsRemaining); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/auth/PasswordResponse.java b/src/com/hypixel/hytale/protocol/packets/auth/PasswordResponse.java new file mode 100644 index 0000000..571e89b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/auth/PasswordResponse.java @@ -0,0 +1,161 @@ +package com.hypixel.hytale.protocol.packets.auth; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PasswordResponse implements Packet { + public static final int PACKET_ID = 15; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 70; + @Nullable + public byte[] hash; + + @Override + public int getId() { + return 15; + } + + public PasswordResponse() { + } + + public PasswordResponse(@Nullable byte[] hash) { + this.hash = hash; + } + + public PasswordResponse(@Nonnull PasswordResponse other) { + this.hash = other.hash; + } + + @Nonnull + public static PasswordResponse deserialize(@Nonnull ByteBuf buf, int offset) { + PasswordResponse obj = new PasswordResponse(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int hashCount = VarInt.peek(buf, pos); + if (hashCount < 0) { + throw ProtocolException.negativeLength("Hash", hashCount); + } + + if (hashCount > 64) { + throw ProtocolException.arrayTooLong("Hash", hashCount, 64); + } + + int hashVarLen = VarInt.size(hashCount); + if (pos + hashVarLen + hashCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Hash", pos + hashVarLen + hashCount * 1, buf.readableBytes()); + } + + pos += hashVarLen; + obj.hash = new byte[hashCount]; + + for (int i = 0; i < hashCount; i++) { + obj.hash[i] = buf.getByte(pos + i * 1); + } + + pos += hashCount * 1; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 1; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.hash != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.hash != null) { + if (this.hash.length > 64) { + throw ProtocolException.arrayTooLong("Hash", this.hash.length, 64); + } + + VarInt.write(buf, this.hash.length); + + for (byte item : this.hash) { + buf.writeByte(item); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.hash != null) { + size += VarInt.size(this.hash.length) + this.hash.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int hashCount = VarInt.peek(buffer, pos); + if (hashCount < 0) { + return ValidationResult.error("Invalid array count for Hash"); + } + + if (hashCount > 64) { + return ValidationResult.error("Hash exceeds max length 64"); + } + + pos += VarInt.length(buffer, pos); + pos += hashCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Hash"); + } + } + + return ValidationResult.OK; + } + } + + public PasswordResponse clone() { + PasswordResponse copy = new PasswordResponse(); + copy.hash = this.hash != null ? Arrays.copyOf(this.hash, this.hash.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof PasswordResponse other ? Arrays.equals(this.hash, other.hash) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode(this.hash); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/auth/ServerAuthToken.java b/src/com/hypixel/hytale/protocol/packets/auth/ServerAuthToken.java new file mode 100644 index 0000000..4687039 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/auth/ServerAuthToken.java @@ -0,0 +1,257 @@ +package com.hypixel.hytale.protocol.packets.auth; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ServerAuthToken implements Packet { + public static final int PACKET_ID = 13; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 32851; + @Nullable + public String serverAccessToken; + @Nullable + public byte[] passwordChallenge; + + @Override + public int getId() { + return 13; + } + + public ServerAuthToken() { + } + + public ServerAuthToken(@Nullable String serverAccessToken, @Nullable byte[] passwordChallenge) { + this.serverAccessToken = serverAccessToken; + this.passwordChallenge = passwordChallenge; + } + + public ServerAuthToken(@Nonnull ServerAuthToken other) { + this.serverAccessToken = other.serverAccessToken; + this.passwordChallenge = other.passwordChallenge; + } + + @Nonnull + public static ServerAuthToken deserialize(@Nonnull ByteBuf buf, int offset) { + ServerAuthToken obj = new ServerAuthToken(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int serverAccessTokenLen = VarInt.peek(buf, varPos0); + if (serverAccessTokenLen < 0) { + throw ProtocolException.negativeLength("ServerAccessToken", serverAccessTokenLen); + } + + if (serverAccessTokenLen > 8192) { + throw ProtocolException.stringTooLong("ServerAccessToken", serverAccessTokenLen, 8192); + } + + obj.serverAccessToken = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int passwordChallengeCount = VarInt.peek(buf, varPos1); + if (passwordChallengeCount < 0) { + throw ProtocolException.negativeLength("PasswordChallenge", passwordChallengeCount); + } + + if (passwordChallengeCount > 64) { + throw ProtocolException.arrayTooLong("PasswordChallenge", passwordChallengeCount, 64); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + passwordChallengeCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("PasswordChallenge", varPos1 + varIntLen + passwordChallengeCount * 1, buf.readableBytes()); + } + + obj.passwordChallenge = new byte[passwordChallengeCount]; + + for (int i = 0; i < passwordChallengeCount; i++) { + obj.passwordChallenge[i] = buf.getByte(varPos1 + varIntLen + i * 1); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + arrLen * 1; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.serverAccessToken != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.passwordChallenge != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int serverAccessTokenOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int passwordChallengeOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.serverAccessToken != null) { + buf.setIntLE(serverAccessTokenOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.serverAccessToken, 8192); + } else { + buf.setIntLE(serverAccessTokenOffsetSlot, -1); + } + + if (this.passwordChallenge != null) { + buf.setIntLE(passwordChallengeOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.passwordChallenge.length > 64) { + throw ProtocolException.arrayTooLong("PasswordChallenge", this.passwordChallenge.length, 64); + } + + VarInt.write(buf, this.passwordChallenge.length); + + for (byte item : this.passwordChallenge) { + buf.writeByte(item); + } + } else { + buf.setIntLE(passwordChallengeOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 9; + if (this.serverAccessToken != null) { + size += PacketIO.stringSize(this.serverAccessToken); + } + + if (this.passwordChallenge != null) { + size += VarInt.size(this.passwordChallenge.length) + this.passwordChallenge.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int serverAccessTokenOffset = buffer.getIntLE(offset + 1); + if (serverAccessTokenOffset < 0) { + return ValidationResult.error("Invalid offset for ServerAccessToken"); + } + + int pos = offset + 9 + serverAccessTokenOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ServerAccessToken"); + } + + int serverAccessTokenLen = VarInt.peek(buffer, pos); + if (serverAccessTokenLen < 0) { + return ValidationResult.error("Invalid string length for ServerAccessToken"); + } + + if (serverAccessTokenLen > 8192) { + return ValidationResult.error("ServerAccessToken exceeds max length 8192"); + } + + pos += VarInt.length(buffer, pos); + pos += serverAccessTokenLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ServerAccessToken"); + } + } + + if ((nullBits & 2) != 0) { + int passwordChallengeOffset = buffer.getIntLE(offset + 5); + if (passwordChallengeOffset < 0) { + return ValidationResult.error("Invalid offset for PasswordChallenge"); + } + + int posx = offset + 9 + passwordChallengeOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for PasswordChallenge"); + } + + int passwordChallengeCount = VarInt.peek(buffer, posx); + if (passwordChallengeCount < 0) { + return ValidationResult.error("Invalid array count for PasswordChallenge"); + } + + if (passwordChallengeCount > 64) { + return ValidationResult.error("PasswordChallenge exceeds max length 64"); + } + + posx += VarInt.length(buffer, posx); + posx += passwordChallengeCount * 1; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading PasswordChallenge"); + } + } + + return ValidationResult.OK; + } + } + + public ServerAuthToken clone() { + ServerAuthToken copy = new ServerAuthToken(); + copy.serverAccessToken = this.serverAccessToken; + copy.passwordChallenge = this.passwordChallenge != null ? Arrays.copyOf(this.passwordChallenge, this.passwordChallenge.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ServerAuthToken other) + ? false + : Objects.equals(this.serverAccessToken, other.serverAccessToken) && Arrays.equals(this.passwordChallenge, other.passwordChallenge); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.serverAccessToken); + return 31 * result + Arrays.hashCode(this.passwordChallenge); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/auth/Status.java b/src/com/hypixel/hytale/protocol/packets/auth/Status.java new file mode 100644 index 0000000..96700e1 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/auth/Status.java @@ -0,0 +1,252 @@ +package com.hypixel.hytale.protocol.packets.auth; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Status implements Packet { + public static final int PACKET_ID = 10; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 17; + public static final int MAX_SIZE = 2587; + @Nullable + public String name; + @Nullable + public String motd; + public int playerCount; + public int maxPlayers; + + @Override + public int getId() { + return 10; + } + + public Status() { + } + + public Status(@Nullable String name, @Nullable String motd, int playerCount, int maxPlayers) { + this.name = name; + this.motd = motd; + this.playerCount = playerCount; + this.maxPlayers = maxPlayers; + } + + public Status(@Nonnull Status other) { + this.name = other.name; + this.motd = other.motd; + this.playerCount = other.playerCount; + this.maxPlayers = other.maxPlayers; + } + + @Nonnull + public static Status deserialize(@Nonnull ByteBuf buf, int offset) { + Status obj = new Status(); + byte nullBits = buf.getByte(offset); + obj.playerCount = buf.getIntLE(offset + 1); + obj.maxPlayers = buf.getIntLE(offset + 5); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 17 + buf.getIntLE(offset + 9); + int nameLen = VarInt.peek(buf, varPos0); + if (nameLen < 0) { + throw ProtocolException.negativeLength("Name", nameLen); + } + + if (nameLen > 128) { + throw ProtocolException.stringTooLong("Name", nameLen, 128); + } + + obj.name = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 17 + buf.getIntLE(offset + 13); + int motdLen = VarInt.peek(buf, varPos1); + if (motdLen < 0) { + throw ProtocolException.negativeLength("Motd", motdLen); + } + + if (motdLen > 512) { + throw ProtocolException.stringTooLong("Motd", motdLen, 512); + } + + obj.motd = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 17; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 9); + int pos0 = offset + 17 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 13); + int pos1 = offset + 17 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.name != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.motd != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.playerCount); + buf.writeIntLE(this.maxPlayers); + int nameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int motdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.name != null) { + buf.setIntLE(nameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.name, 128); + } else { + buf.setIntLE(nameOffsetSlot, -1); + } + + if (this.motd != null) { + buf.setIntLE(motdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.motd, 512); + } else { + buf.setIntLE(motdOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 17; + if (this.name != null) { + size += PacketIO.stringSize(this.name); + } + + if (this.motd != null) { + size += PacketIO.stringSize(this.motd); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 17) { + return ValidationResult.error("Buffer too small: expected at least 17 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int nameOffset = buffer.getIntLE(offset + 9); + if (nameOffset < 0) { + return ValidationResult.error("Invalid offset for Name"); + } + + int pos = offset + 17 + nameOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Name"); + } + + int nameLen = VarInt.peek(buffer, pos); + if (nameLen < 0) { + return ValidationResult.error("Invalid string length for Name"); + } + + if (nameLen > 128) { + return ValidationResult.error("Name exceeds max length 128"); + } + + pos += VarInt.length(buffer, pos); + pos += nameLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Name"); + } + } + + if ((nullBits & 2) != 0) { + int motdOffset = buffer.getIntLE(offset + 13); + if (motdOffset < 0) { + return ValidationResult.error("Invalid offset for Motd"); + } + + int posx = offset + 17 + motdOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Motd"); + } + + int motdLen = VarInt.peek(buffer, posx); + if (motdLen < 0) { + return ValidationResult.error("Invalid string length for Motd"); + } + + if (motdLen > 512) { + return ValidationResult.error("Motd exceeds max length 512"); + } + + posx += VarInt.length(buffer, posx); + posx += motdLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Motd"); + } + } + + return ValidationResult.OK; + } + } + + public Status clone() { + Status copy = new Status(); + copy.name = this.name; + copy.motd = this.motd; + copy.playerCount = this.playerCount; + copy.maxPlayers = this.maxPlayers; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Status other) + ? false + : Objects.equals(this.name, other.name) + && Objects.equals(this.motd, other.motd) + && this.playerCount == other.playerCount + && this.maxPlayers == other.maxPlayers; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.name, this.motd, this.playerCount, this.maxPlayers); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/Axis.java b/src/com/hypixel/hytale/protocol/packets/buildertools/Axis.java new file mode 100644 index 0000000..386a989 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/Axis.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum Axis { + X(0), + Y(1), + Z(2); + + public static final Axis[] VALUES = values(); + private final int value; + + private Axis(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static Axis fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("Axis", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BrushAxis.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BrushAxis.java new file mode 100644 index 0000000..d19f13a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BrushAxis.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum BrushAxis { + None(0), + Auto(1), + X(2), + Y(3), + Z(4); + + public static final BrushAxis[] VALUES = values(); + private final int value; + + private BrushAxis(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static BrushAxis fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("BrushAxis", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BrushOrigin.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BrushOrigin.java new file mode 100644 index 0000000..4e528e3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BrushOrigin.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum BrushOrigin { + Center(0), + Bottom(1), + Top(2); + + public static final BrushOrigin[] VALUES = values(); + private final int value; + + private BrushOrigin(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static BrushOrigin fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("BrushOrigin", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BrushShape.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BrushShape.java new file mode 100644 index 0000000..9d249e9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BrushShape.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum BrushShape { + Cube(0), + Sphere(1), + Cylinder(2), + Cone(3), + InvertedCone(4), + Pyramid(5), + InvertedPyramid(6), + Dome(7), + InvertedDome(8), + Diamond(9), + Torus(10); + + public static final BrushShape[] VALUES = values(); + private final int value; + + private BrushShape(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static BrushShape fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("BrushShape", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolAction.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolAction.java new file mode 100644 index 0000000..dfd4686 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolAction.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum BuilderToolAction { + SelectionPosition1(0), + SelectionPosition2(1), + SelectionCopy(2), + HistoryUndo(3), + HistoryRedo(4), + ActivateToolMode(5), + DeactivateToolMode(6); + + public static final BuilderToolAction[] VALUES = values(); + private final int value; + + private BuilderToolAction(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static BuilderToolAction fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("BuilderToolAction", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolArg.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolArg.java new file mode 100644 index 0000000..6c33482 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolArg.java @@ -0,0 +1,485 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderToolArg { + public static final int NULLABLE_BIT_FIELD_SIZE = 2; + public static final int FIXED_BLOCK_SIZE = 33; + public static final int VARIABLE_FIELD_COUNT = 4; + public static final int VARIABLE_BLOCK_START = 49; + public static final int MAX_SIZE = 1677721600; + public boolean required; + @Nonnull + public BuilderToolArgType argType = BuilderToolArgType.Bool; + @Nullable + public BuilderToolBoolArg boolArg; + @Nullable + public BuilderToolFloatArg floatArg; + @Nullable + public BuilderToolIntArg intArg; + @Nullable + public BuilderToolStringArg stringArg; + @Nullable + public BuilderToolBlockArg blockArg; + @Nullable + public BuilderToolMaskArg maskArg; + @Nullable + public BuilderToolBrushShapeArg brushShapeArg; + @Nullable + public BuilderToolBrushOriginArg brushOriginArg; + @Nullable + public BuilderToolBrushAxisArg brushAxisArg; + @Nullable + public BuilderToolRotationArg rotationArg; + @Nullable + public BuilderToolOptionArg optionArg; + + public BuilderToolArg() { + } + + public BuilderToolArg( + boolean required, + @Nonnull BuilderToolArgType argType, + @Nullable BuilderToolBoolArg boolArg, + @Nullable BuilderToolFloatArg floatArg, + @Nullable BuilderToolIntArg intArg, + @Nullable BuilderToolStringArg stringArg, + @Nullable BuilderToolBlockArg blockArg, + @Nullable BuilderToolMaskArg maskArg, + @Nullable BuilderToolBrushShapeArg brushShapeArg, + @Nullable BuilderToolBrushOriginArg brushOriginArg, + @Nullable BuilderToolBrushAxisArg brushAxisArg, + @Nullable BuilderToolRotationArg rotationArg, + @Nullable BuilderToolOptionArg optionArg + ) { + this.required = required; + this.argType = argType; + this.boolArg = boolArg; + this.floatArg = floatArg; + this.intArg = intArg; + this.stringArg = stringArg; + this.blockArg = blockArg; + this.maskArg = maskArg; + this.brushShapeArg = brushShapeArg; + this.brushOriginArg = brushOriginArg; + this.brushAxisArg = brushAxisArg; + this.rotationArg = rotationArg; + this.optionArg = optionArg; + } + + public BuilderToolArg(@Nonnull BuilderToolArg other) { + this.required = other.required; + this.argType = other.argType; + this.boolArg = other.boolArg; + this.floatArg = other.floatArg; + this.intArg = other.intArg; + this.stringArg = other.stringArg; + this.blockArg = other.blockArg; + this.maskArg = other.maskArg; + this.brushShapeArg = other.brushShapeArg; + this.brushOriginArg = other.brushOriginArg; + this.brushAxisArg = other.brushAxisArg; + this.rotationArg = other.rotationArg; + this.optionArg = other.optionArg; + } + + @Nonnull + public static BuilderToolArg deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolArg obj = new BuilderToolArg(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + obj.required = buf.getByte(offset + 2) != 0; + obj.argType = BuilderToolArgType.fromValue(buf.getByte(offset + 3)); + if ((nullBits[0] & 1) != 0) { + obj.boolArg = BuilderToolBoolArg.deserialize(buf, offset + 4); + } + + if ((nullBits[0] & 2) != 0) { + obj.floatArg = BuilderToolFloatArg.deserialize(buf, offset + 5); + } + + if ((nullBits[0] & 4) != 0) { + obj.intArg = BuilderToolIntArg.deserialize(buf, offset + 17); + } + + if ((nullBits[0] & 64) != 0) { + obj.brushShapeArg = BuilderToolBrushShapeArg.deserialize(buf, offset + 29); + } + + if ((nullBits[0] & 128) != 0) { + obj.brushOriginArg = BuilderToolBrushOriginArg.deserialize(buf, offset + 30); + } + + if ((nullBits[1] & 1) != 0) { + obj.brushAxisArg = BuilderToolBrushAxisArg.deserialize(buf, offset + 31); + } + + if ((nullBits[1] & 2) != 0) { + obj.rotationArg = BuilderToolRotationArg.deserialize(buf, offset + 32); + } + + if ((nullBits[0] & 8) != 0) { + int varPos0 = offset + 49 + buf.getIntLE(offset + 33); + obj.stringArg = BuilderToolStringArg.deserialize(buf, varPos0); + } + + if ((nullBits[0] & 16) != 0) { + int varPos1 = offset + 49 + buf.getIntLE(offset + 37); + obj.blockArg = BuilderToolBlockArg.deserialize(buf, varPos1); + } + + if ((nullBits[0] & 32) != 0) { + int varPos2 = offset + 49 + buf.getIntLE(offset + 41); + obj.maskArg = BuilderToolMaskArg.deserialize(buf, varPos2); + } + + if ((nullBits[1] & 4) != 0) { + int varPos3 = offset + 49 + buf.getIntLE(offset + 45); + obj.optionArg = BuilderToolOptionArg.deserialize(buf, varPos3); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + int maxEnd = 49; + if ((nullBits[0] & 8) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 33); + int pos0 = offset + 49 + fieldOffset0; + pos0 += BuilderToolStringArg.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits[0] & 16) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 37); + int pos1 = offset + 49 + fieldOffset1; + pos1 += BuilderToolBlockArg.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits[0] & 32) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 41); + int pos2 = offset + 49 + fieldOffset2; + pos2 += BuilderToolMaskArg.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits[1] & 4) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 45); + int pos3 = offset + 49 + fieldOffset3; + pos3 += BuilderToolOptionArg.computeBytesConsumed(buf, pos3); + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte[] nullBits = new byte[2]; + if (this.boolArg != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.floatArg != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.intArg != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.stringArg != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.blockArg != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.maskArg != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.brushShapeArg != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.brushOriginArg != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.brushAxisArg != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + if (this.rotationArg != null) { + nullBits[1] = (byte)(nullBits[1] | 2); + } + + if (this.optionArg != null) { + nullBits[1] = (byte)(nullBits[1] | 4); + } + + buf.writeBytes(nullBits); + buf.writeByte(this.required ? 1 : 0); + buf.writeByte(this.argType.getValue()); + if (this.boolArg != null) { + this.boolArg.serialize(buf); + } else { + buf.writeZero(1); + } + + if (this.floatArg != null) { + this.floatArg.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.intArg != null) { + this.intArg.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.brushShapeArg != null) { + this.brushShapeArg.serialize(buf); + } else { + buf.writeZero(1); + } + + if (this.brushOriginArg != null) { + this.brushOriginArg.serialize(buf); + } else { + buf.writeZero(1); + } + + if (this.brushAxisArg != null) { + this.brushAxisArg.serialize(buf); + } else { + buf.writeZero(1); + } + + if (this.rotationArg != null) { + this.rotationArg.serialize(buf); + } else { + buf.writeZero(1); + } + + int stringArgOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int blockArgOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int maskArgOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int optionArgOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.stringArg != null) { + buf.setIntLE(stringArgOffsetSlot, buf.writerIndex() - varBlockStart); + this.stringArg.serialize(buf); + } else { + buf.setIntLE(stringArgOffsetSlot, -1); + } + + if (this.blockArg != null) { + buf.setIntLE(blockArgOffsetSlot, buf.writerIndex() - varBlockStart); + this.blockArg.serialize(buf); + } else { + buf.setIntLE(blockArgOffsetSlot, -1); + } + + if (this.maskArg != null) { + buf.setIntLE(maskArgOffsetSlot, buf.writerIndex() - varBlockStart); + this.maskArg.serialize(buf); + } else { + buf.setIntLE(maskArgOffsetSlot, -1); + } + + if (this.optionArg != null) { + buf.setIntLE(optionArgOffsetSlot, buf.writerIndex() - varBlockStart); + this.optionArg.serialize(buf); + } else { + buf.setIntLE(optionArgOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 49; + if (this.stringArg != null) { + size += this.stringArg.computeSize(); + } + + if (this.blockArg != null) { + size += this.blockArg.computeSize(); + } + + if (this.maskArg != null) { + size += this.maskArg.computeSize(); + } + + if (this.optionArg != null) { + size += this.optionArg.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 49) { + return ValidationResult.error("Buffer too small: expected at least 49 bytes"); + } else { + byte[] nullBits = PacketIO.readBytes(buffer, offset, 2); + if ((nullBits[0] & 8) != 0) { + int stringArgOffset = buffer.getIntLE(offset + 33); + if (stringArgOffset < 0) { + return ValidationResult.error("Invalid offset for StringArg"); + } + + int pos = offset + 49 + stringArgOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for StringArg"); + } + + ValidationResult stringArgResult = BuilderToolStringArg.validateStructure(buffer, pos); + if (!stringArgResult.isValid()) { + return ValidationResult.error("Invalid StringArg: " + stringArgResult.error()); + } + + pos += BuilderToolStringArg.computeBytesConsumed(buffer, pos); + } + + if ((nullBits[0] & 16) != 0) { + int blockArgOffset = buffer.getIntLE(offset + 37); + if (blockArgOffset < 0) { + return ValidationResult.error("Invalid offset for BlockArg"); + } + + int posx = offset + 49 + blockArgOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BlockArg"); + } + + ValidationResult blockArgResult = BuilderToolBlockArg.validateStructure(buffer, posx); + if (!blockArgResult.isValid()) { + return ValidationResult.error("Invalid BlockArg: " + blockArgResult.error()); + } + + posx += BuilderToolBlockArg.computeBytesConsumed(buffer, posx); + } + + if ((nullBits[0] & 32) != 0) { + int maskArgOffset = buffer.getIntLE(offset + 41); + if (maskArgOffset < 0) { + return ValidationResult.error("Invalid offset for MaskArg"); + } + + int posxx = offset + 49 + maskArgOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MaskArg"); + } + + ValidationResult maskArgResult = BuilderToolMaskArg.validateStructure(buffer, posxx); + if (!maskArgResult.isValid()) { + return ValidationResult.error("Invalid MaskArg: " + maskArgResult.error()); + } + + posxx += BuilderToolMaskArg.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits[1] & 4) != 0) { + int optionArgOffset = buffer.getIntLE(offset + 45); + if (optionArgOffset < 0) { + return ValidationResult.error("Invalid offset for OptionArg"); + } + + int posxxx = offset + 49 + optionArgOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for OptionArg"); + } + + ValidationResult optionArgResult = BuilderToolOptionArg.validateStructure(buffer, posxxx); + if (!optionArgResult.isValid()) { + return ValidationResult.error("Invalid OptionArg: " + optionArgResult.error()); + } + + posxxx += BuilderToolOptionArg.computeBytesConsumed(buffer, posxxx); + } + + return ValidationResult.OK; + } + } + + public BuilderToolArg clone() { + BuilderToolArg copy = new BuilderToolArg(); + copy.required = this.required; + copy.argType = this.argType; + copy.boolArg = this.boolArg != null ? this.boolArg.clone() : null; + copy.floatArg = this.floatArg != null ? this.floatArg.clone() : null; + copy.intArg = this.intArg != null ? this.intArg.clone() : null; + copy.stringArg = this.stringArg != null ? this.stringArg.clone() : null; + copy.blockArg = this.blockArg != null ? this.blockArg.clone() : null; + copy.maskArg = this.maskArg != null ? this.maskArg.clone() : null; + copy.brushShapeArg = this.brushShapeArg != null ? this.brushShapeArg.clone() : null; + copy.brushOriginArg = this.brushOriginArg != null ? this.brushOriginArg.clone() : null; + copy.brushAxisArg = this.brushAxisArg != null ? this.brushAxisArg.clone() : null; + copy.rotationArg = this.rotationArg != null ? this.rotationArg.clone() : null; + copy.optionArg = this.optionArg != null ? this.optionArg.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolArg other) + ? false + : this.required == other.required + && Objects.equals(this.argType, other.argType) + && Objects.equals(this.boolArg, other.boolArg) + && Objects.equals(this.floatArg, other.floatArg) + && Objects.equals(this.intArg, other.intArg) + && Objects.equals(this.stringArg, other.stringArg) + && Objects.equals(this.blockArg, other.blockArg) + && Objects.equals(this.maskArg, other.maskArg) + && Objects.equals(this.brushShapeArg, other.brushShapeArg) + && Objects.equals(this.brushOriginArg, other.brushOriginArg) + && Objects.equals(this.brushAxisArg, other.brushAxisArg) + && Objects.equals(this.rotationArg, other.rotationArg) + && Objects.equals(this.optionArg, other.optionArg); + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.required, + this.argType, + this.boolArg, + this.floatArg, + this.intArg, + this.stringArg, + this.blockArg, + this.maskArg, + this.brushShapeArg, + this.brushOriginArg, + this.brushAxisArg, + this.rotationArg, + this.optionArg + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolArgGroup.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolArgGroup.java new file mode 100644 index 0000000..9bb4840 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolArgGroup.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum BuilderToolArgGroup { + Tool(0), + Brush(1); + + public static final BuilderToolArgGroup[] VALUES = values(); + private final int value; + + private BuilderToolArgGroup(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static BuilderToolArgGroup fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("BuilderToolArgGroup", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolArgType.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolArgType.java new file mode 100644 index 0000000..dfc5c74 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolArgType.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum BuilderToolArgType { + Bool(0), + Float(1), + Int(2), + String(3), + Block(4), + Mask(5), + BrushShape(6), + BrushOrigin(7), + BrushAxis(8), + Rotation(9), + Option(10); + + public static final BuilderToolArgType[] VALUES = values(); + private final int value; + + private BuilderToolArgType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static BuilderToolArgType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("BuilderToolArgType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolArgUpdate.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolArgUpdate.java new file mode 100644 index 0000000..8626a2b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolArgUpdate.java @@ -0,0 +1,267 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderToolArgUpdate implements Packet { + public static final int PACKET_ID = 400; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 14; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 22; + public static final int MAX_SIZE = 32768032; + public int token; + public int section; + public int slot; + @Nonnull + public BuilderToolArgGroup group = BuilderToolArgGroup.Tool; + @Nullable + public String id; + @Nullable + public String value; + + @Override + public int getId() { + return 400; + } + + public BuilderToolArgUpdate() { + } + + public BuilderToolArgUpdate(int token, int section, int slot, @Nonnull BuilderToolArgGroup group, @Nullable String id, @Nullable String value) { + this.token = token; + this.section = section; + this.slot = slot; + this.group = group; + this.id = id; + this.value = value; + } + + public BuilderToolArgUpdate(@Nonnull BuilderToolArgUpdate other) { + this.token = other.token; + this.section = other.section; + this.slot = other.slot; + this.group = other.group; + this.id = other.id; + this.value = other.value; + } + + @Nonnull + public static BuilderToolArgUpdate deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolArgUpdate obj = new BuilderToolArgUpdate(); + byte nullBits = buf.getByte(offset); + obj.token = buf.getIntLE(offset + 1); + obj.section = buf.getIntLE(offset + 5); + obj.slot = buf.getIntLE(offset + 9); + obj.group = BuilderToolArgGroup.fromValue(buf.getByte(offset + 13)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 22 + buf.getIntLE(offset + 14); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 22 + buf.getIntLE(offset + 18); + int valueLen = VarInt.peek(buf, varPos1); + if (valueLen < 0) { + throw ProtocolException.negativeLength("Value", valueLen); + } + + if (valueLen > 4096000) { + throw ProtocolException.stringTooLong("Value", valueLen, 4096000); + } + + obj.value = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 22; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 14); + int pos0 = offset + 22 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 18); + int pos1 = offset + 22 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.value != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.token); + buf.writeIntLE(this.section); + buf.writeIntLE(this.slot); + buf.writeByte(this.group.getValue()); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int valueOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.value != null) { + buf.setIntLE(valueOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.value, 4096000); + } else { + buf.setIntLE(valueOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 22; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.value != null) { + size += PacketIO.stringSize(this.value); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 22) { + return ValidationResult.error("Buffer too small: expected at least 22 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 14); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 22 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int valueOffset = buffer.getIntLE(offset + 18); + if (valueOffset < 0) { + return ValidationResult.error("Invalid offset for Value"); + } + + int posx = offset + 22 + valueOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Value"); + } + + int valueLen = VarInt.peek(buffer, posx); + if (valueLen < 0) { + return ValidationResult.error("Invalid string length for Value"); + } + + if (valueLen > 4096000) { + return ValidationResult.error("Value exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += valueLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Value"); + } + } + + return ValidationResult.OK; + } + } + + public BuilderToolArgUpdate clone() { + BuilderToolArgUpdate copy = new BuilderToolArgUpdate(); + copy.token = this.token; + copy.section = this.section; + copy.slot = this.slot; + copy.group = this.group; + copy.id = this.id; + copy.value = this.value; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolArgUpdate other) + ? false + : this.token == other.token + && this.section == other.section + && this.slot == other.slot + && Objects.equals(this.group, other.group) + && Objects.equals(this.id, other.id) + && Objects.equals(this.value, other.value); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.token, this.section, this.slot, this.group, this.id, this.value); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBlockArg.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBlockArg.java new file mode 100644 index 0000000..ccab944 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBlockArg.java @@ -0,0 +1,141 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderToolBlockArg { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 16384007; + @Nullable + public String defaultValue; + public boolean allowPattern; + + public BuilderToolBlockArg() { + } + + public BuilderToolBlockArg(@Nullable String defaultValue, boolean allowPattern) { + this.defaultValue = defaultValue; + this.allowPattern = allowPattern; + } + + public BuilderToolBlockArg(@Nonnull BuilderToolBlockArg other) { + this.defaultValue = other.defaultValue; + this.allowPattern = other.allowPattern; + } + + @Nonnull + public static BuilderToolBlockArg deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolBlockArg obj = new BuilderToolBlockArg(); + byte nullBits = buf.getByte(offset); + obj.allowPattern = buf.getByte(offset + 1) != 0; + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int defaultValueLen = VarInt.peek(buf, pos); + if (defaultValueLen < 0) { + throw ProtocolException.negativeLength("Default", defaultValueLen); + } + + if (defaultValueLen > 4096000) { + throw ProtocolException.stringTooLong("Default", defaultValueLen, 4096000); + } + + int defaultValueVarLen = VarInt.length(buf, pos); + obj.defaultValue = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += defaultValueVarLen + defaultValueLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.defaultValue != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.allowPattern ? 1 : 0); + if (this.defaultValue != null) { + PacketIO.writeVarString(buf, this.defaultValue, 4096000); + } + } + + public int computeSize() { + int size = 2; + if (this.defaultValue != null) { + size += PacketIO.stringSize(this.defaultValue); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int defaultLen = VarInt.peek(buffer, pos); + if (defaultLen < 0) { + return ValidationResult.error("Invalid string length for Default"); + } + + if (defaultLen > 4096000) { + return ValidationResult.error("Default exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += defaultLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Default"); + } + } + + return ValidationResult.OK; + } + } + + public BuilderToolBlockArg clone() { + BuilderToolBlockArg copy = new BuilderToolBlockArg(); + copy.defaultValue = this.defaultValue; + copy.allowPattern = this.allowPattern; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolBlockArg other) + ? false + : Objects.equals(this.defaultValue, other.defaultValue) && this.allowPattern == other.allowPattern; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.defaultValue, this.allowPattern); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBoolArg.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBoolArg.java new file mode 100644 index 0000000..dca9338 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBoolArg.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolBoolArg { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean defaultValue; + + public BuilderToolBoolArg() { + } + + public BuilderToolBoolArg(boolean defaultValue) { + this.defaultValue = defaultValue; + } + + public BuilderToolBoolArg(@Nonnull BuilderToolBoolArg other) { + this.defaultValue = other.defaultValue; + } + + @Nonnull + public static BuilderToolBoolArg deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolBoolArg obj = new BuilderToolBoolArg(); + obj.defaultValue = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.defaultValue ? 1 : 0); + } + + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public BuilderToolBoolArg clone() { + BuilderToolBoolArg copy = new BuilderToolBoolArg(); + copy.defaultValue = this.defaultValue; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof BuilderToolBoolArg other ? this.defaultValue == other.defaultValue : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.defaultValue); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBrushAxisArg.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBrushAxisArg.java new file mode 100644 index 0000000..0f89164 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBrushAxisArg.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolBrushAxisArg { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + @Nonnull + public BrushAxis defaultValue = BrushAxis.None; + + public BuilderToolBrushAxisArg() { + } + + public BuilderToolBrushAxisArg(@Nonnull BrushAxis defaultValue) { + this.defaultValue = defaultValue; + } + + public BuilderToolBrushAxisArg(@Nonnull BuilderToolBrushAxisArg other) { + this.defaultValue = other.defaultValue; + } + + @Nonnull + public static BuilderToolBrushAxisArg deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolBrushAxisArg obj = new BuilderToolBrushAxisArg(); + obj.defaultValue = BrushAxis.fromValue(buf.getByte(offset + 0)); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.defaultValue.getValue()); + } + + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public BuilderToolBrushAxisArg clone() { + BuilderToolBrushAxisArg copy = new BuilderToolBrushAxisArg(); + copy.defaultValue = this.defaultValue; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof BuilderToolBrushAxisArg other ? Objects.equals(this.defaultValue, other.defaultValue) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.defaultValue); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBrushData.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBrushData.java new file mode 100644 index 0000000..67a69ee --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBrushData.java @@ -0,0 +1,976 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderToolBrushData { + public static final int NULLABLE_BIT_FIELD_SIZE = 3; + public static final int FIXED_BLOCK_SIZE = 48; + public static final int VARIABLE_FIELD_COUNT = 9; + public static final int VARIABLE_BLOCK_START = 84; + public static final int MAX_SIZE = 1677721600; + @Nullable + public BuilderToolIntArg width; + @Nullable + public BuilderToolIntArg height; + @Nullable + public BuilderToolIntArg thickness; + @Nullable + public BuilderToolBoolArg capped; + @Nullable + public BuilderToolBrushShapeArg shape; + @Nullable + public BuilderToolBrushOriginArg origin; + @Nullable + public BuilderToolBoolArg originRotation; + @Nullable + public BuilderToolBrushAxisArg rotationAxis; + @Nullable + public BuilderToolRotationArg rotationAngle; + @Nullable + public BuilderToolBrushAxisArg mirrorAxis; + @Nullable + public BuilderToolBlockArg material; + @Nullable + public BuilderToolBlockArg[] favoriteMaterials; + @Nullable + public BuilderToolMaskArg mask; + @Nullable + public BuilderToolMaskArg maskAbove; + @Nullable + public BuilderToolMaskArg maskNot; + @Nullable + public BuilderToolMaskArg maskBelow; + @Nullable + public BuilderToolMaskArg maskAdjacent; + @Nullable + public BuilderToolMaskArg maskNeighbor; + @Nullable + public BuilderToolStringArg[] maskCommands; + @Nullable + public BuilderToolBoolArg useMaskCommands; + @Nullable + public BuilderToolBoolArg invertMask; + + public BuilderToolBrushData() { + } + + public BuilderToolBrushData( + @Nullable BuilderToolIntArg width, + @Nullable BuilderToolIntArg height, + @Nullable BuilderToolIntArg thickness, + @Nullable BuilderToolBoolArg capped, + @Nullable BuilderToolBrushShapeArg shape, + @Nullable BuilderToolBrushOriginArg origin, + @Nullable BuilderToolBoolArg originRotation, + @Nullable BuilderToolBrushAxisArg rotationAxis, + @Nullable BuilderToolRotationArg rotationAngle, + @Nullable BuilderToolBrushAxisArg mirrorAxis, + @Nullable BuilderToolBlockArg material, + @Nullable BuilderToolBlockArg[] favoriteMaterials, + @Nullable BuilderToolMaskArg mask, + @Nullable BuilderToolMaskArg maskAbove, + @Nullable BuilderToolMaskArg maskNot, + @Nullable BuilderToolMaskArg maskBelow, + @Nullable BuilderToolMaskArg maskAdjacent, + @Nullable BuilderToolMaskArg maskNeighbor, + @Nullable BuilderToolStringArg[] maskCommands, + @Nullable BuilderToolBoolArg useMaskCommands, + @Nullable BuilderToolBoolArg invertMask + ) { + this.width = width; + this.height = height; + this.thickness = thickness; + this.capped = capped; + this.shape = shape; + this.origin = origin; + this.originRotation = originRotation; + this.rotationAxis = rotationAxis; + this.rotationAngle = rotationAngle; + this.mirrorAxis = mirrorAxis; + this.material = material; + this.favoriteMaterials = favoriteMaterials; + this.mask = mask; + this.maskAbove = maskAbove; + this.maskNot = maskNot; + this.maskBelow = maskBelow; + this.maskAdjacent = maskAdjacent; + this.maskNeighbor = maskNeighbor; + this.maskCommands = maskCommands; + this.useMaskCommands = useMaskCommands; + this.invertMask = invertMask; + } + + public BuilderToolBrushData(@Nonnull BuilderToolBrushData other) { + this.width = other.width; + this.height = other.height; + this.thickness = other.thickness; + this.capped = other.capped; + this.shape = other.shape; + this.origin = other.origin; + this.originRotation = other.originRotation; + this.rotationAxis = other.rotationAxis; + this.rotationAngle = other.rotationAngle; + this.mirrorAxis = other.mirrorAxis; + this.material = other.material; + this.favoriteMaterials = other.favoriteMaterials; + this.mask = other.mask; + this.maskAbove = other.maskAbove; + this.maskNot = other.maskNot; + this.maskBelow = other.maskBelow; + this.maskAdjacent = other.maskAdjacent; + this.maskNeighbor = other.maskNeighbor; + this.maskCommands = other.maskCommands; + this.useMaskCommands = other.useMaskCommands; + this.invertMask = other.invertMask; + } + + @Nonnull + public static BuilderToolBrushData deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolBrushData obj = new BuilderToolBrushData(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 3); + if ((nullBits[0] & 1) != 0) { + obj.width = BuilderToolIntArg.deserialize(buf, offset + 3); + } + + if ((nullBits[0] & 2) != 0) { + obj.height = BuilderToolIntArg.deserialize(buf, offset + 15); + } + + if ((nullBits[0] & 4) != 0) { + obj.thickness = BuilderToolIntArg.deserialize(buf, offset + 27); + } + + if ((nullBits[0] & 8) != 0) { + obj.capped = BuilderToolBoolArg.deserialize(buf, offset + 39); + } + + if ((nullBits[0] & 16) != 0) { + obj.shape = BuilderToolBrushShapeArg.deserialize(buf, offset + 40); + } + + if ((nullBits[0] & 32) != 0) { + obj.origin = BuilderToolBrushOriginArg.deserialize(buf, offset + 41); + } + + if ((nullBits[0] & 64) != 0) { + obj.originRotation = BuilderToolBoolArg.deserialize(buf, offset + 42); + } + + if ((nullBits[0] & 128) != 0) { + obj.rotationAxis = BuilderToolBrushAxisArg.deserialize(buf, offset + 43); + } + + if ((nullBits[1] & 1) != 0) { + obj.rotationAngle = BuilderToolRotationArg.deserialize(buf, offset + 44); + } + + if ((nullBits[1] & 2) != 0) { + obj.mirrorAxis = BuilderToolBrushAxisArg.deserialize(buf, offset + 45); + } + + if ((nullBits[2] & 8) != 0) { + obj.useMaskCommands = BuilderToolBoolArg.deserialize(buf, offset + 46); + } + + if ((nullBits[2] & 16) != 0) { + obj.invertMask = BuilderToolBoolArg.deserialize(buf, offset + 47); + } + + if ((nullBits[1] & 4) != 0) { + int varPos0 = offset + 84 + buf.getIntLE(offset + 48); + obj.material = BuilderToolBlockArg.deserialize(buf, varPos0); + } + + if ((nullBits[1] & 8) != 0) { + int varPos1 = offset + 84 + buf.getIntLE(offset + 52); + int favoriteMaterialsCount = VarInt.peek(buf, varPos1); + if (favoriteMaterialsCount < 0) { + throw ProtocolException.negativeLength("FavoriteMaterials", favoriteMaterialsCount); + } + + if (favoriteMaterialsCount > 4096000) { + throw ProtocolException.arrayTooLong("FavoriteMaterials", favoriteMaterialsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + favoriteMaterialsCount * 2L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("FavoriteMaterials", varPos1 + varIntLen + favoriteMaterialsCount * 2, buf.readableBytes()); + } + + obj.favoriteMaterials = new BuilderToolBlockArg[favoriteMaterialsCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < favoriteMaterialsCount; i++) { + obj.favoriteMaterials[i] = BuilderToolBlockArg.deserialize(buf, elemPos); + elemPos += BuilderToolBlockArg.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits[1] & 16) != 0) { + int varPos2 = offset + 84 + buf.getIntLE(offset + 56); + obj.mask = BuilderToolMaskArg.deserialize(buf, varPos2); + } + + if ((nullBits[1] & 32) != 0) { + int varPos3 = offset + 84 + buf.getIntLE(offset + 60); + obj.maskAbove = BuilderToolMaskArg.deserialize(buf, varPos3); + } + + if ((nullBits[1] & 64) != 0) { + int varPos4 = offset + 84 + buf.getIntLE(offset + 64); + obj.maskNot = BuilderToolMaskArg.deserialize(buf, varPos4); + } + + if ((nullBits[1] & 128) != 0) { + int varPos5 = offset + 84 + buf.getIntLE(offset + 68); + obj.maskBelow = BuilderToolMaskArg.deserialize(buf, varPos5); + } + + if ((nullBits[2] & 1) != 0) { + int varPos6 = offset + 84 + buf.getIntLE(offset + 72); + obj.maskAdjacent = BuilderToolMaskArg.deserialize(buf, varPos6); + } + + if ((nullBits[2] & 2) != 0) { + int varPos7 = offset + 84 + buf.getIntLE(offset + 76); + obj.maskNeighbor = BuilderToolMaskArg.deserialize(buf, varPos7); + } + + if ((nullBits[2] & 4) != 0) { + int varPos8 = offset + 84 + buf.getIntLE(offset + 80); + int maskCommandsCount = VarInt.peek(buf, varPos8); + if (maskCommandsCount < 0) { + throw ProtocolException.negativeLength("MaskCommands", maskCommandsCount); + } + + if (maskCommandsCount > 4096000) { + throw ProtocolException.arrayTooLong("MaskCommands", maskCommandsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos8); + if (varPos8 + varIntLen + maskCommandsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("MaskCommands", varPos8 + varIntLen + maskCommandsCount * 1, buf.readableBytes()); + } + + obj.maskCommands = new BuilderToolStringArg[maskCommandsCount]; + int elemPos = varPos8 + varIntLen; + + for (int i = 0; i < maskCommandsCount; i++) { + obj.maskCommands[i] = BuilderToolStringArg.deserialize(buf, elemPos); + elemPos += BuilderToolStringArg.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte[] nullBits = PacketIO.readBytes(buf, offset, 3); + int maxEnd = 84; + if ((nullBits[1] & 4) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 48); + int pos0 = offset + 84 + fieldOffset0; + pos0 += BuilderToolBlockArg.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits[1] & 8) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 52); + int pos1 = offset + 84 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += BuilderToolBlockArg.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits[1] & 16) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 56); + int pos2 = offset + 84 + fieldOffset2; + pos2 += BuilderToolMaskArg.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits[1] & 32) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 60); + int pos3 = offset + 84 + fieldOffset3; + pos3 += BuilderToolMaskArg.computeBytesConsumed(buf, pos3); + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits[1] & 64) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 64); + int pos4 = offset + 84 + fieldOffset4; + pos4 += BuilderToolMaskArg.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits[1] & 128) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 68); + int pos5 = offset + 84 + fieldOffset5; + pos5 += BuilderToolMaskArg.computeBytesConsumed(buf, pos5); + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits[2] & 1) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 72); + int pos6 = offset + 84 + fieldOffset6; + pos6 += BuilderToolMaskArg.computeBytesConsumed(buf, pos6); + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + if ((nullBits[2] & 2) != 0) { + int fieldOffset7 = buf.getIntLE(offset + 76); + int pos7 = offset + 84 + fieldOffset7; + pos7 += BuilderToolMaskArg.computeBytesConsumed(buf, pos7); + if (pos7 - offset > maxEnd) { + maxEnd = pos7 - offset; + } + } + + if ((nullBits[2] & 4) != 0) { + int fieldOffset8 = buf.getIntLE(offset + 80); + int pos8 = offset + 84 + fieldOffset8; + int arrLen = VarInt.peek(buf, pos8); + pos8 += VarInt.length(buf, pos8); + + for (int i = 0; i < arrLen; i++) { + pos8 += BuilderToolStringArg.computeBytesConsumed(buf, pos8); + } + + if (pos8 - offset > maxEnd) { + maxEnd = pos8 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte[] nullBits = new byte[3]; + if (this.width != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.height != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.thickness != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.capped != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.shape != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.origin != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.originRotation != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.rotationAxis != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.rotationAngle != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + if (this.mirrorAxis != null) { + nullBits[1] = (byte)(nullBits[1] | 2); + } + + if (this.material != null) { + nullBits[1] = (byte)(nullBits[1] | 4); + } + + if (this.favoriteMaterials != null) { + nullBits[1] = (byte)(nullBits[1] | 8); + } + + if (this.mask != null) { + nullBits[1] = (byte)(nullBits[1] | 16); + } + + if (this.maskAbove != null) { + nullBits[1] = (byte)(nullBits[1] | 32); + } + + if (this.maskNot != null) { + nullBits[1] = (byte)(nullBits[1] | 64); + } + + if (this.maskBelow != null) { + nullBits[1] = (byte)(nullBits[1] | 128); + } + + if (this.maskAdjacent != null) { + nullBits[2] = (byte)(nullBits[2] | 1); + } + + if (this.maskNeighbor != null) { + nullBits[2] = (byte)(nullBits[2] | 2); + } + + if (this.maskCommands != null) { + nullBits[2] = (byte)(nullBits[2] | 4); + } + + if (this.useMaskCommands != null) { + nullBits[2] = (byte)(nullBits[2] | 8); + } + + if (this.invertMask != null) { + nullBits[2] = (byte)(nullBits[2] | 16); + } + + buf.writeBytes(nullBits); + if (this.width != null) { + this.width.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.height != null) { + this.height.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.thickness != null) { + this.thickness.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.capped != null) { + this.capped.serialize(buf); + } else { + buf.writeZero(1); + } + + if (this.shape != null) { + this.shape.serialize(buf); + } else { + buf.writeZero(1); + } + + if (this.origin != null) { + this.origin.serialize(buf); + } else { + buf.writeZero(1); + } + + if (this.originRotation != null) { + this.originRotation.serialize(buf); + } else { + buf.writeZero(1); + } + + if (this.rotationAxis != null) { + this.rotationAxis.serialize(buf); + } else { + buf.writeZero(1); + } + + if (this.rotationAngle != null) { + this.rotationAngle.serialize(buf); + } else { + buf.writeZero(1); + } + + if (this.mirrorAxis != null) { + this.mirrorAxis.serialize(buf); + } else { + buf.writeZero(1); + } + + if (this.useMaskCommands != null) { + this.useMaskCommands.serialize(buf); + } else { + buf.writeZero(1); + } + + if (this.invertMask != null) { + this.invertMask.serialize(buf); + } else { + buf.writeZero(1); + } + + int materialOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int favoriteMaterialsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int maskOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int maskAboveOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int maskNotOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int maskBelowOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int maskAdjacentOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int maskNeighborOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int maskCommandsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.material != null) { + buf.setIntLE(materialOffsetSlot, buf.writerIndex() - varBlockStart); + this.material.serialize(buf); + } else { + buf.setIntLE(materialOffsetSlot, -1); + } + + if (this.favoriteMaterials != null) { + buf.setIntLE(favoriteMaterialsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.favoriteMaterials.length > 4096000) { + throw ProtocolException.arrayTooLong("FavoriteMaterials", this.favoriteMaterials.length, 4096000); + } + + VarInt.write(buf, this.favoriteMaterials.length); + + for (BuilderToolBlockArg item : this.favoriteMaterials) { + item.serialize(buf); + } + } else { + buf.setIntLE(favoriteMaterialsOffsetSlot, -1); + } + + if (this.mask != null) { + buf.setIntLE(maskOffsetSlot, buf.writerIndex() - varBlockStart); + this.mask.serialize(buf); + } else { + buf.setIntLE(maskOffsetSlot, -1); + } + + if (this.maskAbove != null) { + buf.setIntLE(maskAboveOffsetSlot, buf.writerIndex() - varBlockStart); + this.maskAbove.serialize(buf); + } else { + buf.setIntLE(maskAboveOffsetSlot, -1); + } + + if (this.maskNot != null) { + buf.setIntLE(maskNotOffsetSlot, buf.writerIndex() - varBlockStart); + this.maskNot.serialize(buf); + } else { + buf.setIntLE(maskNotOffsetSlot, -1); + } + + if (this.maskBelow != null) { + buf.setIntLE(maskBelowOffsetSlot, buf.writerIndex() - varBlockStart); + this.maskBelow.serialize(buf); + } else { + buf.setIntLE(maskBelowOffsetSlot, -1); + } + + if (this.maskAdjacent != null) { + buf.setIntLE(maskAdjacentOffsetSlot, buf.writerIndex() - varBlockStart); + this.maskAdjacent.serialize(buf); + } else { + buf.setIntLE(maskAdjacentOffsetSlot, -1); + } + + if (this.maskNeighbor != null) { + buf.setIntLE(maskNeighborOffsetSlot, buf.writerIndex() - varBlockStart); + this.maskNeighbor.serialize(buf); + } else { + buf.setIntLE(maskNeighborOffsetSlot, -1); + } + + if (this.maskCommands != null) { + buf.setIntLE(maskCommandsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.maskCommands.length > 4096000) { + throw ProtocolException.arrayTooLong("MaskCommands", this.maskCommands.length, 4096000); + } + + VarInt.write(buf, this.maskCommands.length); + + for (BuilderToolStringArg item : this.maskCommands) { + item.serialize(buf); + } + } else { + buf.setIntLE(maskCommandsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 84; + if (this.material != null) { + size += this.material.computeSize(); + } + + if (this.favoriteMaterials != null) { + int favoriteMaterialsSize = 0; + + for (BuilderToolBlockArg elem : this.favoriteMaterials) { + favoriteMaterialsSize += elem.computeSize(); + } + + size += VarInt.size(this.favoriteMaterials.length) + favoriteMaterialsSize; + } + + if (this.mask != null) { + size += this.mask.computeSize(); + } + + if (this.maskAbove != null) { + size += this.maskAbove.computeSize(); + } + + if (this.maskNot != null) { + size += this.maskNot.computeSize(); + } + + if (this.maskBelow != null) { + size += this.maskBelow.computeSize(); + } + + if (this.maskAdjacent != null) { + size += this.maskAdjacent.computeSize(); + } + + if (this.maskNeighbor != null) { + size += this.maskNeighbor.computeSize(); + } + + if (this.maskCommands != null) { + int maskCommandsSize = 0; + + for (BuilderToolStringArg elem : this.maskCommands) { + maskCommandsSize += elem.computeSize(); + } + + size += VarInt.size(this.maskCommands.length) + maskCommandsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 84) { + return ValidationResult.error("Buffer too small: expected at least 84 bytes"); + } else { + byte[] nullBits = PacketIO.readBytes(buffer, offset, 3); + if ((nullBits[1] & 4) != 0) { + int materialOffset = buffer.getIntLE(offset + 48); + if (materialOffset < 0) { + return ValidationResult.error("Invalid offset for Material"); + } + + int pos = offset + 84 + materialOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Material"); + } + + ValidationResult materialResult = BuilderToolBlockArg.validateStructure(buffer, pos); + if (!materialResult.isValid()) { + return ValidationResult.error("Invalid Material: " + materialResult.error()); + } + + pos += BuilderToolBlockArg.computeBytesConsumed(buffer, pos); + } + + if ((nullBits[1] & 8) != 0) { + int favoriteMaterialsOffset = buffer.getIntLE(offset + 52); + if (favoriteMaterialsOffset < 0) { + return ValidationResult.error("Invalid offset for FavoriteMaterials"); + } + + int posx = offset + 84 + favoriteMaterialsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FavoriteMaterials"); + } + + int favoriteMaterialsCount = VarInt.peek(buffer, posx); + if (favoriteMaterialsCount < 0) { + return ValidationResult.error("Invalid array count for FavoriteMaterials"); + } + + if (favoriteMaterialsCount > 4096000) { + return ValidationResult.error("FavoriteMaterials exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < favoriteMaterialsCount; i++) { + ValidationResult structResult = BuilderToolBlockArg.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid BuilderToolBlockArg in FavoriteMaterials[" + i + "]: " + structResult.error()); + } + + posx += BuilderToolBlockArg.computeBytesConsumed(buffer, posx); + } + } + + if ((nullBits[1] & 16) != 0) { + int maskOffset = buffer.getIntLE(offset + 56); + if (maskOffset < 0) { + return ValidationResult.error("Invalid offset for Mask"); + } + + int posxx = offset + 84 + maskOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Mask"); + } + + ValidationResult maskResult = BuilderToolMaskArg.validateStructure(buffer, posxx); + if (!maskResult.isValid()) { + return ValidationResult.error("Invalid Mask: " + maskResult.error()); + } + + posxx += BuilderToolMaskArg.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits[1] & 32) != 0) { + int maskAboveOffset = buffer.getIntLE(offset + 60); + if (maskAboveOffset < 0) { + return ValidationResult.error("Invalid offset for MaskAbove"); + } + + int posxxx = offset + 84 + maskAboveOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MaskAbove"); + } + + ValidationResult maskAboveResult = BuilderToolMaskArg.validateStructure(buffer, posxxx); + if (!maskAboveResult.isValid()) { + return ValidationResult.error("Invalid MaskAbove: " + maskAboveResult.error()); + } + + posxxx += BuilderToolMaskArg.computeBytesConsumed(buffer, posxxx); + } + + if ((nullBits[1] & 64) != 0) { + int maskNotOffset = buffer.getIntLE(offset + 64); + if (maskNotOffset < 0) { + return ValidationResult.error("Invalid offset for MaskNot"); + } + + int posxxxx = offset + 84 + maskNotOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MaskNot"); + } + + ValidationResult maskNotResult = BuilderToolMaskArg.validateStructure(buffer, posxxxx); + if (!maskNotResult.isValid()) { + return ValidationResult.error("Invalid MaskNot: " + maskNotResult.error()); + } + + posxxxx += BuilderToolMaskArg.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits[1] & 128) != 0) { + int maskBelowOffset = buffer.getIntLE(offset + 68); + if (maskBelowOffset < 0) { + return ValidationResult.error("Invalid offset for MaskBelow"); + } + + int posxxxxx = offset + 84 + maskBelowOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MaskBelow"); + } + + ValidationResult maskBelowResult = BuilderToolMaskArg.validateStructure(buffer, posxxxxx); + if (!maskBelowResult.isValid()) { + return ValidationResult.error("Invalid MaskBelow: " + maskBelowResult.error()); + } + + posxxxxx += BuilderToolMaskArg.computeBytesConsumed(buffer, posxxxxx); + } + + if ((nullBits[2] & 1) != 0) { + int maskAdjacentOffset = buffer.getIntLE(offset + 72); + if (maskAdjacentOffset < 0) { + return ValidationResult.error("Invalid offset for MaskAdjacent"); + } + + int posxxxxxx = offset + 84 + maskAdjacentOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MaskAdjacent"); + } + + ValidationResult maskAdjacentResult = BuilderToolMaskArg.validateStructure(buffer, posxxxxxx); + if (!maskAdjacentResult.isValid()) { + return ValidationResult.error("Invalid MaskAdjacent: " + maskAdjacentResult.error()); + } + + posxxxxxx += BuilderToolMaskArg.computeBytesConsumed(buffer, posxxxxxx); + } + + if ((nullBits[2] & 2) != 0) { + int maskNeighborOffset = buffer.getIntLE(offset + 76); + if (maskNeighborOffset < 0) { + return ValidationResult.error("Invalid offset for MaskNeighbor"); + } + + int posxxxxxxx = offset + 84 + maskNeighborOffset; + if (posxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MaskNeighbor"); + } + + ValidationResult maskNeighborResult = BuilderToolMaskArg.validateStructure(buffer, posxxxxxxx); + if (!maskNeighborResult.isValid()) { + return ValidationResult.error("Invalid MaskNeighbor: " + maskNeighborResult.error()); + } + + posxxxxxxx += BuilderToolMaskArg.computeBytesConsumed(buffer, posxxxxxxx); + } + + if ((nullBits[2] & 4) != 0) { + int maskCommandsOffset = buffer.getIntLE(offset + 80); + if (maskCommandsOffset < 0) { + return ValidationResult.error("Invalid offset for MaskCommands"); + } + + int posxxxxxxxx = offset + 84 + maskCommandsOffset; + if (posxxxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MaskCommands"); + } + + int maskCommandsCount = VarInt.peek(buffer, posxxxxxxxx); + if (maskCommandsCount < 0) { + return ValidationResult.error("Invalid array count for MaskCommands"); + } + + if (maskCommandsCount > 4096000) { + return ValidationResult.error("MaskCommands exceeds max length 4096000"); + } + + posxxxxxxxx += VarInt.length(buffer, posxxxxxxxx); + + for (int i = 0; i < maskCommandsCount; i++) { + ValidationResult structResult = BuilderToolStringArg.validateStructure(buffer, posxxxxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid BuilderToolStringArg in MaskCommands[" + i + "]: " + structResult.error()); + } + + posxxxxxxxx += BuilderToolStringArg.computeBytesConsumed(buffer, posxxxxxxxx); + } + } + + return ValidationResult.OK; + } + } + + public BuilderToolBrushData clone() { + BuilderToolBrushData copy = new BuilderToolBrushData(); + copy.width = this.width != null ? this.width.clone() : null; + copy.height = this.height != null ? this.height.clone() : null; + copy.thickness = this.thickness != null ? this.thickness.clone() : null; + copy.capped = this.capped != null ? this.capped.clone() : null; + copy.shape = this.shape != null ? this.shape.clone() : null; + copy.origin = this.origin != null ? this.origin.clone() : null; + copy.originRotation = this.originRotation != null ? this.originRotation.clone() : null; + copy.rotationAxis = this.rotationAxis != null ? this.rotationAxis.clone() : null; + copy.rotationAngle = this.rotationAngle != null ? this.rotationAngle.clone() : null; + copy.mirrorAxis = this.mirrorAxis != null ? this.mirrorAxis.clone() : null; + copy.material = this.material != null ? this.material.clone() : null; + copy.favoriteMaterials = this.favoriteMaterials != null + ? Arrays.stream(this.favoriteMaterials).map(e -> e.clone()).toArray(BuilderToolBlockArg[]::new) + : null; + copy.mask = this.mask != null ? this.mask.clone() : null; + copy.maskAbove = this.maskAbove != null ? this.maskAbove.clone() : null; + copy.maskNot = this.maskNot != null ? this.maskNot.clone() : null; + copy.maskBelow = this.maskBelow != null ? this.maskBelow.clone() : null; + copy.maskAdjacent = this.maskAdjacent != null ? this.maskAdjacent.clone() : null; + copy.maskNeighbor = this.maskNeighbor != null ? this.maskNeighbor.clone() : null; + copy.maskCommands = this.maskCommands != null ? Arrays.stream(this.maskCommands).map(e -> e.clone()).toArray(BuilderToolStringArg[]::new) : null; + copy.useMaskCommands = this.useMaskCommands != null ? this.useMaskCommands.clone() : null; + copy.invertMask = this.invertMask != null ? this.invertMask.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolBrushData other) + ? false + : Objects.equals(this.width, other.width) + && Objects.equals(this.height, other.height) + && Objects.equals(this.thickness, other.thickness) + && Objects.equals(this.capped, other.capped) + && Objects.equals(this.shape, other.shape) + && Objects.equals(this.origin, other.origin) + && Objects.equals(this.originRotation, other.originRotation) + && Objects.equals(this.rotationAxis, other.rotationAxis) + && Objects.equals(this.rotationAngle, other.rotationAngle) + && Objects.equals(this.mirrorAxis, other.mirrorAxis) + && Objects.equals(this.material, other.material) + && Arrays.equals((Object[])this.favoriteMaterials, (Object[])other.favoriteMaterials) + && Objects.equals(this.mask, other.mask) + && Objects.equals(this.maskAbove, other.maskAbove) + && Objects.equals(this.maskNot, other.maskNot) + && Objects.equals(this.maskBelow, other.maskBelow) + && Objects.equals(this.maskAdjacent, other.maskAdjacent) + && Objects.equals(this.maskNeighbor, other.maskNeighbor) + && Arrays.equals((Object[])this.maskCommands, (Object[])other.maskCommands) + && Objects.equals(this.useMaskCommands, other.useMaskCommands) + && Objects.equals(this.invertMask, other.invertMask); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.width); + result = 31 * result + Objects.hashCode(this.height); + result = 31 * result + Objects.hashCode(this.thickness); + result = 31 * result + Objects.hashCode(this.capped); + result = 31 * result + Objects.hashCode(this.shape); + result = 31 * result + Objects.hashCode(this.origin); + result = 31 * result + Objects.hashCode(this.originRotation); + result = 31 * result + Objects.hashCode(this.rotationAxis); + result = 31 * result + Objects.hashCode(this.rotationAngle); + result = 31 * result + Objects.hashCode(this.mirrorAxis); + result = 31 * result + Objects.hashCode(this.material); + result = 31 * result + Arrays.hashCode((Object[])this.favoriteMaterials); + result = 31 * result + Objects.hashCode(this.mask); + result = 31 * result + Objects.hashCode(this.maskAbove); + result = 31 * result + Objects.hashCode(this.maskNot); + result = 31 * result + Objects.hashCode(this.maskBelow); + result = 31 * result + Objects.hashCode(this.maskAdjacent); + result = 31 * result + Objects.hashCode(this.maskNeighbor); + result = 31 * result + Arrays.hashCode((Object[])this.maskCommands); + result = 31 * result + Objects.hashCode(this.useMaskCommands); + return 31 * result + Objects.hashCode(this.invertMask); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBrushOriginArg.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBrushOriginArg.java new file mode 100644 index 0000000..3c1459e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBrushOriginArg.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolBrushOriginArg { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + @Nonnull + public BrushOrigin defaultValue = BrushOrigin.Center; + + public BuilderToolBrushOriginArg() { + } + + public BuilderToolBrushOriginArg(@Nonnull BrushOrigin defaultValue) { + this.defaultValue = defaultValue; + } + + public BuilderToolBrushOriginArg(@Nonnull BuilderToolBrushOriginArg other) { + this.defaultValue = other.defaultValue; + } + + @Nonnull + public static BuilderToolBrushOriginArg deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolBrushOriginArg obj = new BuilderToolBrushOriginArg(); + obj.defaultValue = BrushOrigin.fromValue(buf.getByte(offset + 0)); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.defaultValue.getValue()); + } + + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public BuilderToolBrushOriginArg clone() { + BuilderToolBrushOriginArg copy = new BuilderToolBrushOriginArg(); + copy.defaultValue = this.defaultValue; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof BuilderToolBrushOriginArg other ? Objects.equals(this.defaultValue, other.defaultValue) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.defaultValue); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBrushShapeArg.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBrushShapeArg.java new file mode 100644 index 0000000..f3e20a7 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolBrushShapeArg.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolBrushShapeArg { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + @Nonnull + public BrushShape defaultValue = BrushShape.Cube; + + public BuilderToolBrushShapeArg() { + } + + public BuilderToolBrushShapeArg(@Nonnull BrushShape defaultValue) { + this.defaultValue = defaultValue; + } + + public BuilderToolBrushShapeArg(@Nonnull BuilderToolBrushShapeArg other) { + this.defaultValue = other.defaultValue; + } + + @Nonnull + public static BuilderToolBrushShapeArg deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolBrushShapeArg obj = new BuilderToolBrushShapeArg(); + obj.defaultValue = BrushShape.fromValue(buf.getByte(offset + 0)); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.defaultValue.getValue()); + } + + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public BuilderToolBrushShapeArg clone() { + BuilderToolBrushShapeArg copy = new BuilderToolBrushShapeArg(); + copy.defaultValue = this.defaultValue; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof BuilderToolBrushShapeArg other ? Objects.equals(this.defaultValue, other.defaultValue) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.defaultValue); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolEntityAction.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolEntityAction.java new file mode 100644 index 0000000..6c22872 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolEntityAction.java @@ -0,0 +1,86 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolEntityAction implements Packet { + public static final int PACKET_ID = 401; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 5; + public int entityId; + @Nonnull + public EntityToolAction action = EntityToolAction.Remove; + + @Override + public int getId() { + return 401; + } + + public BuilderToolEntityAction() { + } + + public BuilderToolEntityAction(int entityId, @Nonnull EntityToolAction action) { + this.entityId = entityId; + this.action = action; + } + + public BuilderToolEntityAction(@Nonnull BuilderToolEntityAction other) { + this.entityId = other.entityId; + this.action = other.action; + } + + @Nonnull + public static BuilderToolEntityAction deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolEntityAction obj = new BuilderToolEntityAction(); + obj.entityId = buf.getIntLE(offset + 0); + obj.action = EntityToolAction.fromValue(buf.getByte(offset + 4)); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 5; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.entityId); + buf.writeByte(this.action.getValue()); + } + + @Override + public int computeSize() { + return 5; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 5 ? ValidationResult.error("Buffer too small: expected at least 5 bytes") : ValidationResult.OK; + } + + public BuilderToolEntityAction clone() { + BuilderToolEntityAction copy = new BuilderToolEntityAction(); + copy.entityId = this.entityId; + copy.action = this.action; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolEntityAction other) ? false : this.entityId == other.entityId && Objects.equals(this.action, other.action); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.entityId, this.action); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolExtrudeAction.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolExtrudeAction.java new file mode 100644 index 0000000..86ad924 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolExtrudeAction.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolExtrudeAction implements Packet { + public static final int PACKET_ID = 403; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 24; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 24; + public static final int MAX_SIZE = 24; + public int x; + public int y; + public int z; + public int xNormal; + public int yNormal; + public int zNormal; + + @Override + public int getId() { + return 403; + } + + public BuilderToolExtrudeAction() { + } + + public BuilderToolExtrudeAction(int x, int y, int z, int xNormal, int yNormal, int zNormal) { + this.x = x; + this.y = y; + this.z = z; + this.xNormal = xNormal; + this.yNormal = yNormal; + this.zNormal = zNormal; + } + + public BuilderToolExtrudeAction(@Nonnull BuilderToolExtrudeAction other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.xNormal = other.xNormal; + this.yNormal = other.yNormal; + this.zNormal = other.zNormal; + } + + @Nonnull + public static BuilderToolExtrudeAction deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolExtrudeAction obj = new BuilderToolExtrudeAction(); + obj.x = buf.getIntLE(offset + 0); + obj.y = buf.getIntLE(offset + 4); + obj.z = buf.getIntLE(offset + 8); + obj.xNormal = buf.getIntLE(offset + 12); + obj.yNormal = buf.getIntLE(offset + 16); + obj.zNormal = buf.getIntLE(offset + 20); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 24; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.x); + buf.writeIntLE(this.y); + buf.writeIntLE(this.z); + buf.writeIntLE(this.xNormal); + buf.writeIntLE(this.yNormal); + buf.writeIntLE(this.zNormal); + } + + @Override + public int computeSize() { + return 24; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 24 ? ValidationResult.error("Buffer too small: expected at least 24 bytes") : ValidationResult.OK; + } + + public BuilderToolExtrudeAction clone() { + BuilderToolExtrudeAction copy = new BuilderToolExtrudeAction(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + copy.xNormal = this.xNormal; + copy.yNormal = this.yNormal; + copy.zNormal = this.zNormal; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolExtrudeAction other) + ? false + : this.x == other.x + && this.y == other.y + && this.z == other.z + && this.xNormal == other.xNormal + && this.yNormal == other.yNormal + && this.zNormal == other.zNormal; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y, this.z, this.xNormal, this.yNormal, this.zNormal); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolFloatArg.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolFloatArg.java new file mode 100644 index 0000000..8b5687c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolFloatArg.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolFloatArg { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 12; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 12; + public static final int MAX_SIZE = 12; + public float defaultValue; + public float min; + public float max; + + public BuilderToolFloatArg() { + } + + public BuilderToolFloatArg(float defaultValue, float min, float max) { + this.defaultValue = defaultValue; + this.min = min; + this.max = max; + } + + public BuilderToolFloatArg(@Nonnull BuilderToolFloatArg other) { + this.defaultValue = other.defaultValue; + this.min = other.min; + this.max = other.max; + } + + @Nonnull + public static BuilderToolFloatArg deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolFloatArg obj = new BuilderToolFloatArg(); + obj.defaultValue = buf.getFloatLE(offset + 0); + obj.min = buf.getFloatLE(offset + 4); + obj.max = buf.getFloatLE(offset + 8); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 12; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.defaultValue); + buf.writeFloatLE(this.min); + buf.writeFloatLE(this.max); + } + + public int computeSize() { + return 12; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 12 ? ValidationResult.error("Buffer too small: expected at least 12 bytes") : ValidationResult.OK; + } + + public BuilderToolFloatArg clone() { + BuilderToolFloatArg copy = new BuilderToolFloatArg(); + copy.defaultValue = this.defaultValue; + copy.min = this.min; + copy.max = this.max; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolFloatArg other) ? false : this.defaultValue == other.defaultValue && this.min == other.min && this.max == other.max; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.defaultValue, this.min, this.max); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolGeneralAction.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolGeneralAction.java new file mode 100644 index 0000000..2801687 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolGeneralAction.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolGeneralAction implements Packet { + public static final int PACKET_ID = 412; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + @Nonnull + public BuilderToolAction action = BuilderToolAction.SelectionPosition1; + + @Override + public int getId() { + return 412; + } + + public BuilderToolGeneralAction() { + } + + public BuilderToolGeneralAction(@Nonnull BuilderToolAction action) { + this.action = action; + } + + public BuilderToolGeneralAction(@Nonnull BuilderToolGeneralAction other) { + this.action = other.action; + } + + @Nonnull + public static BuilderToolGeneralAction deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolGeneralAction obj = new BuilderToolGeneralAction(); + obj.action = BuilderToolAction.fromValue(buf.getByte(offset + 0)); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.action.getValue()); + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public BuilderToolGeneralAction clone() { + BuilderToolGeneralAction copy = new BuilderToolGeneralAction(); + copy.action = this.action; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof BuilderToolGeneralAction other ? Objects.equals(this.action, other.action) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.action); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolHideAnchors.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolHideAnchors.java new file mode 100644 index 0000000..bb34860 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolHideAnchors.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class BuilderToolHideAnchors implements Packet { + public static final int PACKET_ID = 416; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public BuilderToolHideAnchors() { + } + + @Override + public int getId() { + return 416; + } + + @Nonnull + public static BuilderToolHideAnchors deserialize(@Nonnull ByteBuf buf, int offset) { + return new BuilderToolHideAnchors(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public BuilderToolHideAnchors clone() { + return new BuilderToolHideAnchors(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof BuilderToolHideAnchors other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolIntArg.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolIntArg.java new file mode 100644 index 0000000..65495ab --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolIntArg.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolIntArg { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 12; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 12; + public static final int MAX_SIZE = 12; + public int defaultValue; + public int min; + public int max; + + public BuilderToolIntArg() { + } + + public BuilderToolIntArg(int defaultValue, int min, int max) { + this.defaultValue = defaultValue; + this.min = min; + this.max = max; + } + + public BuilderToolIntArg(@Nonnull BuilderToolIntArg other) { + this.defaultValue = other.defaultValue; + this.min = other.min; + this.max = other.max; + } + + @Nonnull + public static BuilderToolIntArg deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolIntArg obj = new BuilderToolIntArg(); + obj.defaultValue = buf.getIntLE(offset + 0); + obj.min = buf.getIntLE(offset + 4); + obj.max = buf.getIntLE(offset + 8); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 12; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.defaultValue); + buf.writeIntLE(this.min); + buf.writeIntLE(this.max); + } + + public int computeSize() { + return 12; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 12 ? ValidationResult.error("Buffer too small: expected at least 12 bytes") : ValidationResult.OK; + } + + public BuilderToolIntArg clone() { + BuilderToolIntArg copy = new BuilderToolIntArg(); + copy.defaultValue = this.defaultValue; + copy.min = this.min; + copy.max = this.max; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolIntArg other) ? false : this.defaultValue == other.defaultValue && this.min == other.min && this.max == other.max; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.defaultValue, this.min, this.max); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolLaserPointer.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolLaserPointer.java new file mode 100644 index 0000000..e504f60 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolLaserPointer.java @@ -0,0 +1,137 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolLaserPointer implements Packet { + public static final int PACKET_ID = 419; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 36; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 36; + public static final int MAX_SIZE = 36; + public int playerNetworkId; + public float startX; + public float startY; + public float startZ; + public float endX; + public float endY; + public float endZ; + public int color; + public int durationMs; + + @Override + public int getId() { + return 419; + } + + public BuilderToolLaserPointer() { + } + + public BuilderToolLaserPointer(int playerNetworkId, float startX, float startY, float startZ, float endX, float endY, float endZ, int color, int durationMs) { + this.playerNetworkId = playerNetworkId; + this.startX = startX; + this.startY = startY; + this.startZ = startZ; + this.endX = endX; + this.endY = endY; + this.endZ = endZ; + this.color = color; + this.durationMs = durationMs; + } + + public BuilderToolLaserPointer(@Nonnull BuilderToolLaserPointer other) { + this.playerNetworkId = other.playerNetworkId; + this.startX = other.startX; + this.startY = other.startY; + this.startZ = other.startZ; + this.endX = other.endX; + this.endY = other.endY; + this.endZ = other.endZ; + this.color = other.color; + this.durationMs = other.durationMs; + } + + @Nonnull + public static BuilderToolLaserPointer deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolLaserPointer obj = new BuilderToolLaserPointer(); + obj.playerNetworkId = buf.getIntLE(offset + 0); + obj.startX = buf.getFloatLE(offset + 4); + obj.startY = buf.getFloatLE(offset + 8); + obj.startZ = buf.getFloatLE(offset + 12); + obj.endX = buf.getFloatLE(offset + 16); + obj.endY = buf.getFloatLE(offset + 20); + obj.endZ = buf.getFloatLE(offset + 24); + obj.color = buf.getIntLE(offset + 28); + obj.durationMs = buf.getIntLE(offset + 32); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 36; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.playerNetworkId); + buf.writeFloatLE(this.startX); + buf.writeFloatLE(this.startY); + buf.writeFloatLE(this.startZ); + buf.writeFloatLE(this.endX); + buf.writeFloatLE(this.endY); + buf.writeFloatLE(this.endZ); + buf.writeIntLE(this.color); + buf.writeIntLE(this.durationMs); + } + + @Override + public int computeSize() { + return 36; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 36 ? ValidationResult.error("Buffer too small: expected at least 36 bytes") : ValidationResult.OK; + } + + public BuilderToolLaserPointer clone() { + BuilderToolLaserPointer copy = new BuilderToolLaserPointer(); + copy.playerNetworkId = this.playerNetworkId; + copy.startX = this.startX; + copy.startY = this.startY; + copy.startZ = this.startZ; + copy.endX = this.endX; + copy.endY = this.endY; + copy.endZ = this.endZ; + copy.color = this.color; + copy.durationMs = this.durationMs; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolLaserPointer other) + ? false + : this.playerNetworkId == other.playerNetworkId + && this.startX == other.startX + && this.startY == other.startY + && this.startZ == other.startZ + && this.endX == other.endX + && this.endY == other.endY + && this.endZ == other.endZ + && this.color == other.color + && this.durationMs == other.durationMs; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.playerNetworkId, this.startX, this.startY, this.startZ, this.endX, this.endY, this.endZ, this.color, this.durationMs); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolLineAction.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolLineAction.java new file mode 100644 index 0000000..a551534 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolLineAction.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolLineAction implements Packet { + public static final int PACKET_ID = 414; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 24; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 24; + public static final int MAX_SIZE = 24; + public int xStart; + public int yStart; + public int zStart; + public int xEnd; + public int yEnd; + public int zEnd; + + @Override + public int getId() { + return 414; + } + + public BuilderToolLineAction() { + } + + public BuilderToolLineAction(int xStart, int yStart, int zStart, int xEnd, int yEnd, int zEnd) { + this.xStart = xStart; + this.yStart = yStart; + this.zStart = zStart; + this.xEnd = xEnd; + this.yEnd = yEnd; + this.zEnd = zEnd; + } + + public BuilderToolLineAction(@Nonnull BuilderToolLineAction other) { + this.xStart = other.xStart; + this.yStart = other.yStart; + this.zStart = other.zStart; + this.xEnd = other.xEnd; + this.yEnd = other.yEnd; + this.zEnd = other.zEnd; + } + + @Nonnull + public static BuilderToolLineAction deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolLineAction obj = new BuilderToolLineAction(); + obj.xStart = buf.getIntLE(offset + 0); + obj.yStart = buf.getIntLE(offset + 4); + obj.zStart = buf.getIntLE(offset + 8); + obj.xEnd = buf.getIntLE(offset + 12); + obj.yEnd = buf.getIntLE(offset + 16); + obj.zEnd = buf.getIntLE(offset + 20); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 24; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.xStart); + buf.writeIntLE(this.yStart); + buf.writeIntLE(this.zStart); + buf.writeIntLE(this.xEnd); + buf.writeIntLE(this.yEnd); + buf.writeIntLE(this.zEnd); + } + + @Override + public int computeSize() { + return 24; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 24 ? ValidationResult.error("Buffer too small: expected at least 24 bytes") : ValidationResult.OK; + } + + public BuilderToolLineAction clone() { + BuilderToolLineAction copy = new BuilderToolLineAction(); + copy.xStart = this.xStart; + copy.yStart = this.yStart; + copy.zStart = this.zStart; + copy.xEnd = this.xEnd; + copy.yEnd = this.yEnd; + copy.zEnd = this.zEnd; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolLineAction other) + ? false + : this.xStart == other.xStart + && this.yStart == other.yStart + && this.zStart == other.zStart + && this.xEnd == other.xEnd + && this.yEnd == other.yEnd + && this.zEnd == other.zEnd; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.xStart, this.yStart, this.zStart, this.xEnd, this.yEnd, this.zEnd); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolMaskArg.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolMaskArg.java new file mode 100644 index 0000000..7453bf4 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolMaskArg.java @@ -0,0 +1,133 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderToolMaskArg { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 16384006; + @Nullable + public String defaultValue; + + public BuilderToolMaskArg() { + } + + public BuilderToolMaskArg(@Nullable String defaultValue) { + this.defaultValue = defaultValue; + } + + public BuilderToolMaskArg(@Nonnull BuilderToolMaskArg other) { + this.defaultValue = other.defaultValue; + } + + @Nonnull + public static BuilderToolMaskArg deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolMaskArg obj = new BuilderToolMaskArg(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int defaultValueLen = VarInt.peek(buf, pos); + if (defaultValueLen < 0) { + throw ProtocolException.negativeLength("Default", defaultValueLen); + } + + if (defaultValueLen > 4096000) { + throw ProtocolException.stringTooLong("Default", defaultValueLen, 4096000); + } + + int defaultValueVarLen = VarInt.length(buf, pos); + obj.defaultValue = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += defaultValueVarLen + defaultValueLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.defaultValue != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.defaultValue != null) { + PacketIO.writeVarString(buf, this.defaultValue, 4096000); + } + } + + public int computeSize() { + int size = 1; + if (this.defaultValue != null) { + size += PacketIO.stringSize(this.defaultValue); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int defaultLen = VarInt.peek(buffer, pos); + if (defaultLen < 0) { + return ValidationResult.error("Invalid string length for Default"); + } + + if (defaultLen > 4096000) { + return ValidationResult.error("Default exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += defaultLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Default"); + } + } + + return ValidationResult.OK; + } + } + + public BuilderToolMaskArg clone() { + BuilderToolMaskArg copy = new BuilderToolMaskArg(); + copy.defaultValue = this.defaultValue; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof BuilderToolMaskArg other ? Objects.equals(this.defaultValue, other.defaultValue) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.defaultValue); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolOnUseInteraction.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolOnUseInteraction.java new file mode 100644 index 0000000..65dca52 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolOnUseInteraction.java @@ -0,0 +1,240 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolOnUseInteraction implements Packet { + public static final int PACKET_ID = 413; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 57; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 57; + public static final int MAX_SIZE = 57; + @Nonnull + public InteractionType type = InteractionType.Primary; + public int x; + public int y; + public int z; + public int offsetForPaintModeX; + public int offsetForPaintModeY; + public int offsetForPaintModeZ; + public boolean isAltPlaySculptBrushModDown; + public boolean isHoldDownInteraction; + public boolean isDoServerRaytraceForPosition; + public boolean isShowEditNotifications; + public int maxLengthToolIgnoreHistory; + public float raycastOriginX; + public float raycastOriginY; + public float raycastOriginZ; + public float raycastDirectionX; + public float raycastDirectionY; + public float raycastDirectionZ; + + @Override + public int getId() { + return 413; + } + + public BuilderToolOnUseInteraction() { + } + + public BuilderToolOnUseInteraction( + @Nonnull InteractionType type, + int x, + int y, + int z, + int offsetForPaintModeX, + int offsetForPaintModeY, + int offsetForPaintModeZ, + boolean isAltPlaySculptBrushModDown, + boolean isHoldDownInteraction, + boolean isDoServerRaytraceForPosition, + boolean isShowEditNotifications, + int maxLengthToolIgnoreHistory, + float raycastOriginX, + float raycastOriginY, + float raycastOriginZ, + float raycastDirectionX, + float raycastDirectionY, + float raycastDirectionZ + ) { + this.type = type; + this.x = x; + this.y = y; + this.z = z; + this.offsetForPaintModeX = offsetForPaintModeX; + this.offsetForPaintModeY = offsetForPaintModeY; + this.offsetForPaintModeZ = offsetForPaintModeZ; + this.isAltPlaySculptBrushModDown = isAltPlaySculptBrushModDown; + this.isHoldDownInteraction = isHoldDownInteraction; + this.isDoServerRaytraceForPosition = isDoServerRaytraceForPosition; + this.isShowEditNotifications = isShowEditNotifications; + this.maxLengthToolIgnoreHistory = maxLengthToolIgnoreHistory; + this.raycastOriginX = raycastOriginX; + this.raycastOriginY = raycastOriginY; + this.raycastOriginZ = raycastOriginZ; + this.raycastDirectionX = raycastDirectionX; + this.raycastDirectionY = raycastDirectionY; + this.raycastDirectionZ = raycastDirectionZ; + } + + public BuilderToolOnUseInteraction(@Nonnull BuilderToolOnUseInteraction other) { + this.type = other.type; + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.offsetForPaintModeX = other.offsetForPaintModeX; + this.offsetForPaintModeY = other.offsetForPaintModeY; + this.offsetForPaintModeZ = other.offsetForPaintModeZ; + this.isAltPlaySculptBrushModDown = other.isAltPlaySculptBrushModDown; + this.isHoldDownInteraction = other.isHoldDownInteraction; + this.isDoServerRaytraceForPosition = other.isDoServerRaytraceForPosition; + this.isShowEditNotifications = other.isShowEditNotifications; + this.maxLengthToolIgnoreHistory = other.maxLengthToolIgnoreHistory; + this.raycastOriginX = other.raycastOriginX; + this.raycastOriginY = other.raycastOriginY; + this.raycastOriginZ = other.raycastOriginZ; + this.raycastDirectionX = other.raycastDirectionX; + this.raycastDirectionY = other.raycastDirectionY; + this.raycastDirectionZ = other.raycastDirectionZ; + } + + @Nonnull + public static BuilderToolOnUseInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolOnUseInteraction obj = new BuilderToolOnUseInteraction(); + obj.type = InteractionType.fromValue(buf.getByte(offset + 0)); + obj.x = buf.getIntLE(offset + 1); + obj.y = buf.getIntLE(offset + 5); + obj.z = buf.getIntLE(offset + 9); + obj.offsetForPaintModeX = buf.getIntLE(offset + 13); + obj.offsetForPaintModeY = buf.getIntLE(offset + 17); + obj.offsetForPaintModeZ = buf.getIntLE(offset + 21); + obj.isAltPlaySculptBrushModDown = buf.getByte(offset + 25) != 0; + obj.isHoldDownInteraction = buf.getByte(offset + 26) != 0; + obj.isDoServerRaytraceForPosition = buf.getByte(offset + 27) != 0; + obj.isShowEditNotifications = buf.getByte(offset + 28) != 0; + obj.maxLengthToolIgnoreHistory = buf.getIntLE(offset + 29); + obj.raycastOriginX = buf.getFloatLE(offset + 33); + obj.raycastOriginY = buf.getFloatLE(offset + 37); + obj.raycastOriginZ = buf.getFloatLE(offset + 41); + obj.raycastDirectionX = buf.getFloatLE(offset + 45); + obj.raycastDirectionY = buf.getFloatLE(offset + 49); + obj.raycastDirectionZ = buf.getFloatLE(offset + 53); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 57; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.type.getValue()); + buf.writeIntLE(this.x); + buf.writeIntLE(this.y); + buf.writeIntLE(this.z); + buf.writeIntLE(this.offsetForPaintModeX); + buf.writeIntLE(this.offsetForPaintModeY); + buf.writeIntLE(this.offsetForPaintModeZ); + buf.writeByte(this.isAltPlaySculptBrushModDown ? 1 : 0); + buf.writeByte(this.isHoldDownInteraction ? 1 : 0); + buf.writeByte(this.isDoServerRaytraceForPosition ? 1 : 0); + buf.writeByte(this.isShowEditNotifications ? 1 : 0); + buf.writeIntLE(this.maxLengthToolIgnoreHistory); + buf.writeFloatLE(this.raycastOriginX); + buf.writeFloatLE(this.raycastOriginY); + buf.writeFloatLE(this.raycastOriginZ); + buf.writeFloatLE(this.raycastDirectionX); + buf.writeFloatLE(this.raycastDirectionY); + buf.writeFloatLE(this.raycastDirectionZ); + } + + @Override + public int computeSize() { + return 57; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 57 ? ValidationResult.error("Buffer too small: expected at least 57 bytes") : ValidationResult.OK; + } + + public BuilderToolOnUseInteraction clone() { + BuilderToolOnUseInteraction copy = new BuilderToolOnUseInteraction(); + copy.type = this.type; + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + copy.offsetForPaintModeX = this.offsetForPaintModeX; + copy.offsetForPaintModeY = this.offsetForPaintModeY; + copy.offsetForPaintModeZ = this.offsetForPaintModeZ; + copy.isAltPlaySculptBrushModDown = this.isAltPlaySculptBrushModDown; + copy.isHoldDownInteraction = this.isHoldDownInteraction; + copy.isDoServerRaytraceForPosition = this.isDoServerRaytraceForPosition; + copy.isShowEditNotifications = this.isShowEditNotifications; + copy.maxLengthToolIgnoreHistory = this.maxLengthToolIgnoreHistory; + copy.raycastOriginX = this.raycastOriginX; + copy.raycastOriginY = this.raycastOriginY; + copy.raycastOriginZ = this.raycastOriginZ; + copy.raycastDirectionX = this.raycastDirectionX; + copy.raycastDirectionY = this.raycastDirectionY; + copy.raycastDirectionZ = this.raycastDirectionZ; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolOnUseInteraction other) + ? false + : Objects.equals(this.type, other.type) + && this.x == other.x + && this.y == other.y + && this.z == other.z + && this.offsetForPaintModeX == other.offsetForPaintModeX + && this.offsetForPaintModeY == other.offsetForPaintModeY + && this.offsetForPaintModeZ == other.offsetForPaintModeZ + && this.isAltPlaySculptBrushModDown == other.isAltPlaySculptBrushModDown + && this.isHoldDownInteraction == other.isHoldDownInteraction + && this.isDoServerRaytraceForPosition == other.isDoServerRaytraceForPosition + && this.isShowEditNotifications == other.isShowEditNotifications + && this.maxLengthToolIgnoreHistory == other.maxLengthToolIgnoreHistory + && this.raycastOriginX == other.raycastOriginX + && this.raycastOriginY == other.raycastOriginY + && this.raycastOriginZ == other.raycastOriginZ + && this.raycastDirectionX == other.raycastDirectionX + && this.raycastDirectionY == other.raycastDirectionY + && this.raycastDirectionZ == other.raycastDirectionZ; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.type, + this.x, + this.y, + this.z, + this.offsetForPaintModeX, + this.offsetForPaintModeY, + this.offsetForPaintModeZ, + this.isAltPlaySculptBrushModDown, + this.isHoldDownInteraction, + this.isDoServerRaytraceForPosition, + this.isShowEditNotifications, + this.maxLengthToolIgnoreHistory, + this.raycastOriginX, + this.raycastOriginY, + this.raycastOriginZ, + this.raycastDirectionX, + this.raycastDirectionY, + this.raycastDirectionZ + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolOptionArg.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolOptionArg.java new file mode 100644 index 0000000..dcd92b8 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolOptionArg.java @@ -0,0 +1,280 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderToolOptionArg { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String defaultValue; + @Nullable + public String[] options; + + public BuilderToolOptionArg() { + } + + public BuilderToolOptionArg(@Nullable String defaultValue, @Nullable String[] options) { + this.defaultValue = defaultValue; + this.options = options; + } + + public BuilderToolOptionArg(@Nonnull BuilderToolOptionArg other) { + this.defaultValue = other.defaultValue; + this.options = other.options; + } + + @Nonnull + public static BuilderToolOptionArg deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolOptionArg obj = new BuilderToolOptionArg(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int defaultValueLen = VarInt.peek(buf, varPos0); + if (defaultValueLen < 0) { + throw ProtocolException.negativeLength("Default", defaultValueLen); + } + + if (defaultValueLen > 4096000) { + throw ProtocolException.stringTooLong("Default", defaultValueLen, 4096000); + } + + obj.defaultValue = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int optionsCount = VarInt.peek(buf, varPos1); + if (optionsCount < 0) { + throw ProtocolException.negativeLength("Options", optionsCount); + } + + if (optionsCount > 4096000) { + throw ProtocolException.arrayTooLong("Options", optionsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + optionsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Options", varPos1 + varIntLen + optionsCount * 1, buf.readableBytes()); + } + + obj.options = new String[optionsCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < optionsCount; i++) { + int strLen = VarInt.peek(buf, elemPos); + if (strLen < 0) { + throw ProtocolException.negativeLength("options[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("options[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, elemPos); + obj.options[i] = PacketIO.readVarString(buf, elemPos); + elemPos += strVarLen + strLen; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.defaultValue != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.options != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int defaultValueOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int optionsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.defaultValue != null) { + buf.setIntLE(defaultValueOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.defaultValue, 4096000); + } else { + buf.setIntLE(defaultValueOffsetSlot, -1); + } + + if (this.options != null) { + buf.setIntLE(optionsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.options.length > 4096000) { + throw ProtocolException.arrayTooLong("Options", this.options.length, 4096000); + } + + VarInt.write(buf, this.options.length); + + for (String item : this.options) { + PacketIO.writeVarString(buf, item, 4096000); + } + } else { + buf.setIntLE(optionsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 9; + if (this.defaultValue != null) { + size += PacketIO.stringSize(this.defaultValue); + } + + if (this.options != null) { + int optionsSize = 0; + + for (String elem : this.options) { + optionsSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.options.length) + optionsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int defaultOffset = buffer.getIntLE(offset + 1); + if (defaultOffset < 0) { + return ValidationResult.error("Invalid offset for Default"); + } + + int pos = offset + 9 + defaultOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Default"); + } + + int defaultLen = VarInt.peek(buffer, pos); + if (defaultLen < 0) { + return ValidationResult.error("Invalid string length for Default"); + } + + if (defaultLen > 4096000) { + return ValidationResult.error("Default exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += defaultLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Default"); + } + } + + if ((nullBits & 2) != 0) { + int optionsOffset = buffer.getIntLE(offset + 5); + if (optionsOffset < 0) { + return ValidationResult.error("Invalid offset for Options"); + } + + int posx = offset + 9 + optionsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Options"); + } + + int optionsCount = VarInt.peek(buffer, posx); + if (optionsCount < 0) { + return ValidationResult.error("Invalid array count for Options"); + } + + if (optionsCount > 4096000) { + return ValidationResult.error("Options exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < optionsCount; i++) { + int strLen = VarInt.peek(buffer, posx); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in Options"); + } + + posx += VarInt.length(buffer, posx); + posx += strLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in Options"); + } + } + } + + return ValidationResult.OK; + } + } + + public BuilderToolOptionArg clone() { + BuilderToolOptionArg copy = new BuilderToolOptionArg(); + copy.defaultValue = this.defaultValue; + copy.options = this.options != null ? Arrays.copyOf(this.options, this.options.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolOptionArg other) + ? false + : Objects.equals(this.defaultValue, other.defaultValue) && Arrays.equals((Object[])this.options, (Object[])other.options); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.defaultValue); + return 31 * result + Arrays.hashCode((Object[])this.options); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolPasteClipboard.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolPasteClipboard.java new file mode 100644 index 0000000..c9f4c6c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolPasteClipboard.java @@ -0,0 +1,91 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolPasteClipboard implements Packet { + public static final int PACKET_ID = 407; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 12; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 12; + public static final int MAX_SIZE = 12; + public int x; + public int y; + public int z; + + @Override + public int getId() { + return 407; + } + + public BuilderToolPasteClipboard() { + } + + public BuilderToolPasteClipboard(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public BuilderToolPasteClipboard(@Nonnull BuilderToolPasteClipboard other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + } + + @Nonnull + public static BuilderToolPasteClipboard deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolPasteClipboard obj = new BuilderToolPasteClipboard(); + obj.x = buf.getIntLE(offset + 0); + obj.y = buf.getIntLE(offset + 4); + obj.z = buf.getIntLE(offset + 8); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 12; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.x); + buf.writeIntLE(this.y); + buf.writeIntLE(this.z); + } + + @Override + public int computeSize() { + return 12; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 12 ? ValidationResult.error("Buffer too small: expected at least 12 bytes") : ValidationResult.OK; + } + + public BuilderToolPasteClipboard clone() { + BuilderToolPasteClipboard copy = new BuilderToolPasteClipboard(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolPasteClipboard other) ? false : this.x == other.x && this.y == other.y && this.z == other.z; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y, this.z); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolRotateClipboard.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolRotateClipboard.java new file mode 100644 index 0000000..c818e40 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolRotateClipboard.java @@ -0,0 +1,86 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolRotateClipboard implements Packet { + public static final int PACKET_ID = 406; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 5; + public int angle; + @Nonnull + public Axis axis = Axis.X; + + @Override + public int getId() { + return 406; + } + + public BuilderToolRotateClipboard() { + } + + public BuilderToolRotateClipboard(int angle, @Nonnull Axis axis) { + this.angle = angle; + this.axis = axis; + } + + public BuilderToolRotateClipboard(@Nonnull BuilderToolRotateClipboard other) { + this.angle = other.angle; + this.axis = other.axis; + } + + @Nonnull + public static BuilderToolRotateClipboard deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolRotateClipboard obj = new BuilderToolRotateClipboard(); + obj.angle = buf.getIntLE(offset + 0); + obj.axis = Axis.fromValue(buf.getByte(offset + 4)); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 5; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.angle); + buf.writeByte(this.axis.getValue()); + } + + @Override + public int computeSize() { + return 5; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 5 ? ValidationResult.error("Buffer too small: expected at least 5 bytes") : ValidationResult.OK; + } + + public BuilderToolRotateClipboard clone() { + BuilderToolRotateClipboard copy = new BuilderToolRotateClipboard(); + copy.angle = this.angle; + copy.axis = this.axis; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolRotateClipboard other) ? false : this.angle == other.angle && Objects.equals(this.axis, other.axis); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.angle, this.axis); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolRotationArg.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolRotationArg.java new file mode 100644 index 0000000..f36fa1d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolRotationArg.java @@ -0,0 +1,71 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Rotation; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolRotationArg { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + @Nonnull + public Rotation defaultValue = Rotation.None; + + public BuilderToolRotationArg() { + } + + public BuilderToolRotationArg(@Nonnull Rotation defaultValue) { + this.defaultValue = defaultValue; + } + + public BuilderToolRotationArg(@Nonnull BuilderToolRotationArg other) { + this.defaultValue = other.defaultValue; + } + + @Nonnull + public static BuilderToolRotationArg deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolRotationArg obj = new BuilderToolRotationArg(); + obj.defaultValue = Rotation.fromValue(buf.getByte(offset + 0)); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.defaultValue.getValue()); + } + + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public BuilderToolRotationArg clone() { + BuilderToolRotationArg copy = new BuilderToolRotationArg(); + copy.defaultValue = this.defaultValue; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof BuilderToolRotationArg other ? Objects.equals(this.defaultValue, other.defaultValue) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.defaultValue); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSelectionToolAskForClipboard.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSelectionToolAskForClipboard.java new file mode 100644 index 0000000..23c03b7 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSelectionToolAskForClipboard.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class BuilderToolSelectionToolAskForClipboard implements Packet { + public static final int PACKET_ID = 410; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public BuilderToolSelectionToolAskForClipboard() { + } + + @Override + public int getId() { + return 410; + } + + @Nonnull + public static BuilderToolSelectionToolAskForClipboard deserialize(@Nonnull ByteBuf buf, int offset) { + return new BuilderToolSelectionToolAskForClipboard(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public BuilderToolSelectionToolAskForClipboard clone() { + return new BuilderToolSelectionToolAskForClipboard(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof BuilderToolSelectionToolAskForClipboard other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSelectionToolReplyWithClipboard.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSelectionToolReplyWithClipboard.java new file mode 100644 index 0000000..571d4f4 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSelectionToolReplyWithClipboard.java @@ -0,0 +1,289 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import com.hypixel.hytale.protocol.packets.interface_.BlockChange; +import com.hypixel.hytale.protocol.packets.interface_.FluidChange; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderToolSelectionToolReplyWithClipboard implements Packet { + public static final int PACKET_ID = 411; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 139264019; + @Nullable + public BlockChange[] blocksChange; + @Nullable + public FluidChange[] fluidsChange; + + @Override + public int getId() { + return 411; + } + + public BuilderToolSelectionToolReplyWithClipboard() { + } + + public BuilderToolSelectionToolReplyWithClipboard(@Nullable BlockChange[] blocksChange, @Nullable FluidChange[] fluidsChange) { + this.blocksChange = blocksChange; + this.fluidsChange = fluidsChange; + } + + public BuilderToolSelectionToolReplyWithClipboard(@Nonnull BuilderToolSelectionToolReplyWithClipboard other) { + this.blocksChange = other.blocksChange; + this.fluidsChange = other.fluidsChange; + } + + @Nonnull + public static BuilderToolSelectionToolReplyWithClipboard deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolSelectionToolReplyWithClipboard obj = new BuilderToolSelectionToolReplyWithClipboard(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int blocksChangeCount = VarInt.peek(buf, varPos0); + if (blocksChangeCount < 0) { + throw ProtocolException.negativeLength("BlocksChange", blocksChangeCount); + } + + if (blocksChangeCount > 4096000) { + throw ProtocolException.arrayTooLong("BlocksChange", blocksChangeCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + blocksChangeCount * 17L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("BlocksChange", varPos0 + varIntLen + blocksChangeCount * 17, buf.readableBytes()); + } + + obj.blocksChange = new BlockChange[blocksChangeCount]; + int elemPos = varPos0 + varIntLen; + + for (int i = 0; i < blocksChangeCount; i++) { + obj.blocksChange[i] = BlockChange.deserialize(buf, elemPos); + elemPos += BlockChange.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int fluidsChangeCount = VarInt.peek(buf, varPos1); + if (fluidsChangeCount < 0) { + throw ProtocolException.negativeLength("FluidsChange", fluidsChangeCount); + } + + if (fluidsChangeCount > 4096000) { + throw ProtocolException.arrayTooLong("FluidsChange", fluidsChangeCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + fluidsChangeCount * 17L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("FluidsChange", varPos1 + varIntLen + fluidsChangeCount * 17, buf.readableBytes()); + } + + obj.fluidsChange = new FluidChange[fluidsChangeCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < fluidsChangeCount; i++) { + obj.fluidsChange[i] = FluidChange.deserialize(buf, elemPos); + elemPos += FluidChange.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < arrLen; i++) { + pos0 += BlockChange.computeBytesConsumed(buf, pos0); + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += FluidChange.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.blocksChange != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.fluidsChange != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int blocksChangeOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int fluidsChangeOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.blocksChange != null) { + buf.setIntLE(blocksChangeOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.blocksChange.length > 4096000) { + throw ProtocolException.arrayTooLong("BlocksChange", this.blocksChange.length, 4096000); + } + + VarInt.write(buf, this.blocksChange.length); + + for (BlockChange item : this.blocksChange) { + item.serialize(buf); + } + } else { + buf.setIntLE(blocksChangeOffsetSlot, -1); + } + + if (this.fluidsChange != null) { + buf.setIntLE(fluidsChangeOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.fluidsChange.length > 4096000) { + throw ProtocolException.arrayTooLong("FluidsChange", this.fluidsChange.length, 4096000); + } + + VarInt.write(buf, this.fluidsChange.length); + + for (FluidChange item : this.fluidsChange) { + item.serialize(buf); + } + } else { + buf.setIntLE(fluidsChangeOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 9; + if (this.blocksChange != null) { + size += VarInt.size(this.blocksChange.length) + this.blocksChange.length * 17; + } + + if (this.fluidsChange != null) { + size += VarInt.size(this.fluidsChange.length) + this.fluidsChange.length * 17; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int blocksChangeOffset = buffer.getIntLE(offset + 1); + if (blocksChangeOffset < 0) { + return ValidationResult.error("Invalid offset for BlocksChange"); + } + + int pos = offset + 9 + blocksChangeOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BlocksChange"); + } + + int blocksChangeCount = VarInt.peek(buffer, pos); + if (blocksChangeCount < 0) { + return ValidationResult.error("Invalid array count for BlocksChange"); + } + + if (blocksChangeCount > 4096000) { + return ValidationResult.error("BlocksChange exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += blocksChangeCount * 17; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading BlocksChange"); + } + } + + if ((nullBits & 2) != 0) { + int fluidsChangeOffset = buffer.getIntLE(offset + 5); + if (fluidsChangeOffset < 0) { + return ValidationResult.error("Invalid offset for FluidsChange"); + } + + int posx = offset + 9 + fluidsChangeOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FluidsChange"); + } + + int fluidsChangeCount = VarInt.peek(buffer, posx); + if (fluidsChangeCount < 0) { + return ValidationResult.error("Invalid array count for FluidsChange"); + } + + if (fluidsChangeCount > 4096000) { + return ValidationResult.error("FluidsChange exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += fluidsChangeCount * 17; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading FluidsChange"); + } + } + + return ValidationResult.OK; + } + } + + public BuilderToolSelectionToolReplyWithClipboard clone() { + BuilderToolSelectionToolReplyWithClipboard copy = new BuilderToolSelectionToolReplyWithClipboard(); + copy.blocksChange = this.blocksChange != null ? Arrays.stream(this.blocksChange).map(e -> e.clone()).toArray(BlockChange[]::new) : null; + copy.fluidsChange = this.fluidsChange != null ? Arrays.stream(this.fluidsChange).map(e -> e.clone()).toArray(FluidChange[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolSelectionToolReplyWithClipboard other) + ? false + : Arrays.equals((Object[])this.blocksChange, (Object[])other.blocksChange) + && Arrays.equals((Object[])this.fluidsChange, (Object[])other.fluidsChange); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode((Object[])this.blocksChange); + return 31 * result + Arrays.hashCode((Object[])this.fluidsChange); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSelectionTransform.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSelectionTransform.java new file mode 100644 index 0000000..1746e93 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSelectionTransform.java @@ -0,0 +1,285 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderToolSelectionTransform implements Packet { + public static final int PACKET_ID = 405; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 52; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 52; + public static final int MAX_SIZE = 16384057; + @Nullable + public float[] transformationMatrix; + @Nullable + public BlockPosition initialSelectionMin; + @Nullable + public BlockPosition initialSelectionMax; + @Nullable + public Vector3f initialRotationOrigin; + public boolean cutOriginal; + public boolean applyTransformationToSelectionMinMax; + public boolean isExitingTransformMode; + @Nullable + public BlockPosition initialPastePointForClipboardPaste; + + @Override + public int getId() { + return 405; + } + + public BuilderToolSelectionTransform() { + } + + public BuilderToolSelectionTransform( + @Nullable float[] transformationMatrix, + @Nullable BlockPosition initialSelectionMin, + @Nullable BlockPosition initialSelectionMax, + @Nullable Vector3f initialRotationOrigin, + boolean cutOriginal, + boolean applyTransformationToSelectionMinMax, + boolean isExitingTransformMode, + @Nullable BlockPosition initialPastePointForClipboardPaste + ) { + this.transformationMatrix = transformationMatrix; + this.initialSelectionMin = initialSelectionMin; + this.initialSelectionMax = initialSelectionMax; + this.initialRotationOrigin = initialRotationOrigin; + this.cutOriginal = cutOriginal; + this.applyTransformationToSelectionMinMax = applyTransformationToSelectionMinMax; + this.isExitingTransformMode = isExitingTransformMode; + this.initialPastePointForClipboardPaste = initialPastePointForClipboardPaste; + } + + public BuilderToolSelectionTransform(@Nonnull BuilderToolSelectionTransform other) { + this.transformationMatrix = other.transformationMatrix; + this.initialSelectionMin = other.initialSelectionMin; + this.initialSelectionMax = other.initialSelectionMax; + this.initialRotationOrigin = other.initialRotationOrigin; + this.cutOriginal = other.cutOriginal; + this.applyTransformationToSelectionMinMax = other.applyTransformationToSelectionMinMax; + this.isExitingTransformMode = other.isExitingTransformMode; + this.initialPastePointForClipboardPaste = other.initialPastePointForClipboardPaste; + } + + @Nonnull + public static BuilderToolSelectionTransform deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolSelectionTransform obj = new BuilderToolSelectionTransform(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 2) != 0) { + obj.initialSelectionMin = BlockPosition.deserialize(buf, offset + 1); + } + + if ((nullBits & 4) != 0) { + obj.initialSelectionMax = BlockPosition.deserialize(buf, offset + 13); + } + + if ((nullBits & 8) != 0) { + obj.initialRotationOrigin = Vector3f.deserialize(buf, offset + 25); + } + + obj.cutOriginal = buf.getByte(offset + 37) != 0; + obj.applyTransformationToSelectionMinMax = buf.getByte(offset + 38) != 0; + obj.isExitingTransformMode = buf.getByte(offset + 39) != 0; + if ((nullBits & 16) != 0) { + obj.initialPastePointForClipboardPaste = BlockPosition.deserialize(buf, offset + 40); + } + + int pos = offset + 52; + if ((nullBits & 1) != 0) { + int transformationMatrixCount = VarInt.peek(buf, pos); + if (transformationMatrixCount < 0) { + throw ProtocolException.negativeLength("TransformationMatrix", transformationMatrixCount); + } + + if (transformationMatrixCount > 4096000) { + throw ProtocolException.arrayTooLong("TransformationMatrix", transformationMatrixCount, 4096000); + } + + int transformationMatrixVarLen = VarInt.size(transformationMatrixCount); + if (pos + transformationMatrixVarLen + transformationMatrixCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall( + "TransformationMatrix", pos + transformationMatrixVarLen + transformationMatrixCount * 4, buf.readableBytes() + ); + } + + pos += transformationMatrixVarLen; + obj.transformationMatrix = new float[transformationMatrixCount]; + + for (int i = 0; i < transformationMatrixCount; i++) { + obj.transformationMatrix[i] = buf.getFloatLE(pos + i * 4); + } + + pos += transformationMatrixCount * 4; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 52; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 4; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.transformationMatrix != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.initialSelectionMin != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.initialSelectionMax != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.initialRotationOrigin != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.initialPastePointForClipboardPaste != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + if (this.initialSelectionMin != null) { + this.initialSelectionMin.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.initialSelectionMax != null) { + this.initialSelectionMax.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.initialRotationOrigin != null) { + this.initialRotationOrigin.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeByte(this.cutOriginal ? 1 : 0); + buf.writeByte(this.applyTransformationToSelectionMinMax ? 1 : 0); + buf.writeByte(this.isExitingTransformMode ? 1 : 0); + if (this.initialPastePointForClipboardPaste != null) { + this.initialPastePointForClipboardPaste.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.transformationMatrix != null) { + if (this.transformationMatrix.length > 4096000) { + throw ProtocolException.arrayTooLong("TransformationMatrix", this.transformationMatrix.length, 4096000); + } + + VarInt.write(buf, this.transformationMatrix.length); + + for (float item : this.transformationMatrix) { + buf.writeFloatLE(item); + } + } + } + + @Override + public int computeSize() { + int size = 52; + if (this.transformationMatrix != null) { + size += VarInt.size(this.transformationMatrix.length) + this.transformationMatrix.length * 4; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 52) { + return ValidationResult.error("Buffer too small: expected at least 52 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 52; + if ((nullBits & 1) != 0) { + int transformationMatrixCount = VarInt.peek(buffer, pos); + if (transformationMatrixCount < 0) { + return ValidationResult.error("Invalid array count for TransformationMatrix"); + } + + if (transformationMatrixCount > 4096000) { + return ValidationResult.error("TransformationMatrix exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += transformationMatrixCount * 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading TransformationMatrix"); + } + } + + return ValidationResult.OK; + } + } + + public BuilderToolSelectionTransform clone() { + BuilderToolSelectionTransform copy = new BuilderToolSelectionTransform(); + copy.transformationMatrix = this.transformationMatrix != null ? Arrays.copyOf(this.transformationMatrix, this.transformationMatrix.length) : null; + copy.initialSelectionMin = this.initialSelectionMin != null ? this.initialSelectionMin.clone() : null; + copy.initialSelectionMax = this.initialSelectionMax != null ? this.initialSelectionMax.clone() : null; + copy.initialRotationOrigin = this.initialRotationOrigin != null ? this.initialRotationOrigin.clone() : null; + copy.cutOriginal = this.cutOriginal; + copy.applyTransformationToSelectionMinMax = this.applyTransformationToSelectionMinMax; + copy.isExitingTransformMode = this.isExitingTransformMode; + copy.initialPastePointForClipboardPaste = this.initialPastePointForClipboardPaste != null ? this.initialPastePointForClipboardPaste.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolSelectionTransform other) + ? false + : Arrays.equals(this.transformationMatrix, other.transformationMatrix) + && Objects.equals(this.initialSelectionMin, other.initialSelectionMin) + && Objects.equals(this.initialSelectionMax, other.initialSelectionMax) + && Objects.equals(this.initialRotationOrigin, other.initialRotationOrigin) + && this.cutOriginal == other.cutOriginal + && this.applyTransformationToSelectionMinMax == other.applyTransformationToSelectionMinMax + && this.isExitingTransformMode == other.isExitingTransformMode + && Objects.equals(this.initialPastePointForClipboardPaste, other.initialPastePointForClipboardPaste); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode(this.transformationMatrix); + result = 31 * result + Objects.hashCode(this.initialSelectionMin); + result = 31 * result + Objects.hashCode(this.initialSelectionMax); + result = 31 * result + Objects.hashCode(this.initialRotationOrigin); + result = 31 * result + Boolean.hashCode(this.cutOriginal); + result = 31 * result + Boolean.hashCode(this.applyTransformationToSelectionMinMax); + result = 31 * result + Boolean.hashCode(this.isExitingTransformMode); + return 31 * result + Objects.hashCode(this.initialPastePointForClipboardPaste); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSelectionUpdate.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSelectionUpdate.java new file mode 100644 index 0000000..c3b10d6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSelectionUpdate.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolSelectionUpdate implements Packet { + public static final int PACKET_ID = 409; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 24; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 24; + public static final int MAX_SIZE = 24; + public int xMin; + public int yMin; + public int zMin; + public int xMax; + public int yMax; + public int zMax; + + @Override + public int getId() { + return 409; + } + + public BuilderToolSelectionUpdate() { + } + + public BuilderToolSelectionUpdate(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax) { + this.xMin = xMin; + this.yMin = yMin; + this.zMin = zMin; + this.xMax = xMax; + this.yMax = yMax; + this.zMax = zMax; + } + + public BuilderToolSelectionUpdate(@Nonnull BuilderToolSelectionUpdate other) { + this.xMin = other.xMin; + this.yMin = other.yMin; + this.zMin = other.zMin; + this.xMax = other.xMax; + this.yMax = other.yMax; + this.zMax = other.zMax; + } + + @Nonnull + public static BuilderToolSelectionUpdate deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolSelectionUpdate obj = new BuilderToolSelectionUpdate(); + obj.xMin = buf.getIntLE(offset + 0); + obj.yMin = buf.getIntLE(offset + 4); + obj.zMin = buf.getIntLE(offset + 8); + obj.xMax = buf.getIntLE(offset + 12); + obj.yMax = buf.getIntLE(offset + 16); + obj.zMax = buf.getIntLE(offset + 20); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 24; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.xMin); + buf.writeIntLE(this.yMin); + buf.writeIntLE(this.zMin); + buf.writeIntLE(this.xMax); + buf.writeIntLE(this.yMax); + buf.writeIntLE(this.zMax); + } + + @Override + public int computeSize() { + return 24; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 24 ? ValidationResult.error("Buffer too small: expected at least 24 bytes") : ValidationResult.OK; + } + + public BuilderToolSelectionUpdate clone() { + BuilderToolSelectionUpdate copy = new BuilderToolSelectionUpdate(); + copy.xMin = this.xMin; + copy.yMin = this.yMin; + copy.zMin = this.zMin; + copy.xMax = this.xMax; + copy.yMax = this.yMax; + copy.zMax = this.zMax; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolSelectionUpdate other) + ? false + : this.xMin == other.xMin + && this.yMin == other.yMin + && this.zMin == other.zMin + && this.xMax == other.xMax + && this.yMax == other.yMax + && this.zMax == other.zMax; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.xMin, this.yMin, this.zMin, this.xMax, this.yMax, this.zMax); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetEntityLight.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetEntityLight.java new file mode 100644 index 0000000..92f1f44 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetEntityLight.java @@ -0,0 +1,102 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.ColorLight; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderToolSetEntityLight implements Packet { + public static final int PACKET_ID = 422; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 9; + public int entityId; + @Nullable + public ColorLight light; + + @Override + public int getId() { + return 422; + } + + public BuilderToolSetEntityLight() { + } + + public BuilderToolSetEntityLight(int entityId, @Nullable ColorLight light) { + this.entityId = entityId; + this.light = light; + } + + public BuilderToolSetEntityLight(@Nonnull BuilderToolSetEntityLight other) { + this.entityId = other.entityId; + this.light = other.light; + } + + @Nonnull + public static BuilderToolSetEntityLight deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolSetEntityLight obj = new BuilderToolSetEntityLight(); + byte nullBits = buf.getByte(offset); + obj.entityId = buf.getIntLE(offset + 1); + if ((nullBits & 1) != 0) { + obj.light = ColorLight.deserialize(buf, offset + 5); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 9; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.light != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.entityId); + if (this.light != null) { + this.light.serialize(buf); + } else { + buf.writeZero(4); + } + } + + @Override + public int computeSize() { + return 9; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 9 ? ValidationResult.error("Buffer too small: expected at least 9 bytes") : ValidationResult.OK; + } + + public BuilderToolSetEntityLight clone() { + BuilderToolSetEntityLight copy = new BuilderToolSetEntityLight(); + copy.entityId = this.entityId; + copy.light = this.light != null ? this.light.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolSetEntityLight other) ? false : this.entityId == other.entityId && Objects.equals(this.light, other.light); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.entityId, this.light); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetEntityPickupEnabled.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetEntityPickupEnabled.java new file mode 100644 index 0000000..a46d96b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetEntityPickupEnabled.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolSetEntityPickupEnabled implements Packet { + public static final int PACKET_ID = 421; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 5; + public int entityId; + public boolean enabled; + + @Override + public int getId() { + return 421; + } + + public BuilderToolSetEntityPickupEnabled() { + } + + public BuilderToolSetEntityPickupEnabled(int entityId, boolean enabled) { + this.entityId = entityId; + this.enabled = enabled; + } + + public BuilderToolSetEntityPickupEnabled(@Nonnull BuilderToolSetEntityPickupEnabled other) { + this.entityId = other.entityId; + this.enabled = other.enabled; + } + + @Nonnull + public static BuilderToolSetEntityPickupEnabled deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolSetEntityPickupEnabled obj = new BuilderToolSetEntityPickupEnabled(); + obj.entityId = buf.getIntLE(offset + 0); + obj.enabled = buf.getByte(offset + 4) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 5; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.entityId); + buf.writeByte(this.enabled ? 1 : 0); + } + + @Override + public int computeSize() { + return 5; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 5 ? ValidationResult.error("Buffer too small: expected at least 5 bytes") : ValidationResult.OK; + } + + public BuilderToolSetEntityPickupEnabled clone() { + BuilderToolSetEntityPickupEnabled copy = new BuilderToolSetEntityPickupEnabled(); + copy.entityId = this.entityId; + copy.enabled = this.enabled; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolSetEntityPickupEnabled other) ? false : this.entityId == other.entityId && this.enabled == other.enabled; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.entityId, this.enabled); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetEntityScale.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetEntityScale.java new file mode 100644 index 0000000..bd32254 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetEntityScale.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolSetEntityScale implements Packet { + public static final int PACKET_ID = 420; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public int entityId; + public float scale; + + @Override + public int getId() { + return 420; + } + + public BuilderToolSetEntityScale() { + } + + public BuilderToolSetEntityScale(int entityId, float scale) { + this.entityId = entityId; + this.scale = scale; + } + + public BuilderToolSetEntityScale(@Nonnull BuilderToolSetEntityScale other) { + this.entityId = other.entityId; + this.scale = other.scale; + } + + @Nonnull + public static BuilderToolSetEntityScale deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolSetEntityScale obj = new BuilderToolSetEntityScale(); + obj.entityId = buf.getIntLE(offset + 0); + obj.scale = buf.getFloatLE(offset + 4); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.entityId); + buf.writeFloatLE(this.scale); + } + + @Override + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public BuilderToolSetEntityScale clone() { + BuilderToolSetEntityScale copy = new BuilderToolSetEntityScale(); + copy.entityId = this.entityId; + copy.scale = this.scale; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolSetEntityScale other) ? false : this.entityId == other.entityId && this.scale == other.scale; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.entityId, this.scale); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetEntityTransform.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetEntityTransform.java new file mode 100644 index 0000000..0bd7e00 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetEntityTransform.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.ModelTransform; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderToolSetEntityTransform implements Packet { + public static final int PACKET_ID = 402; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 54; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 54; + public static final int MAX_SIZE = 54; + public int entityId; + @Nullable + public ModelTransform modelTransform; + + @Override + public int getId() { + return 402; + } + + public BuilderToolSetEntityTransform() { + } + + public BuilderToolSetEntityTransform(int entityId, @Nullable ModelTransform modelTransform) { + this.entityId = entityId; + this.modelTransform = modelTransform; + } + + public BuilderToolSetEntityTransform(@Nonnull BuilderToolSetEntityTransform other) { + this.entityId = other.entityId; + this.modelTransform = other.modelTransform; + } + + @Nonnull + public static BuilderToolSetEntityTransform deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolSetEntityTransform obj = new BuilderToolSetEntityTransform(); + byte nullBits = buf.getByte(offset); + obj.entityId = buf.getIntLE(offset + 1); + if ((nullBits & 1) != 0) { + obj.modelTransform = ModelTransform.deserialize(buf, offset + 5); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 54; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.modelTransform != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.entityId); + if (this.modelTransform != null) { + this.modelTransform.serialize(buf); + } else { + buf.writeZero(49); + } + } + + @Override + public int computeSize() { + return 54; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 54 ? ValidationResult.error("Buffer too small: expected at least 54 bytes") : ValidationResult.OK; + } + + public BuilderToolSetEntityTransform clone() { + BuilderToolSetEntityTransform copy = new BuilderToolSetEntityTransform(); + copy.entityId = this.entityId; + copy.modelTransform = this.modelTransform != null ? this.modelTransform.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolSetEntityTransform other) + ? false + : this.entityId == other.entityId && Objects.equals(this.modelTransform, other.modelTransform); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.entityId, this.modelTransform); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetNPCDebug.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetNPCDebug.java new file mode 100644 index 0000000..f12ddcc --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetNPCDebug.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolSetNPCDebug implements Packet { + public static final int PACKET_ID = 423; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 5; + public int entityId; + public boolean enabled; + + @Override + public int getId() { + return 423; + } + + public BuilderToolSetNPCDebug() { + } + + public BuilderToolSetNPCDebug(int entityId, boolean enabled) { + this.entityId = entityId; + this.enabled = enabled; + } + + public BuilderToolSetNPCDebug(@Nonnull BuilderToolSetNPCDebug other) { + this.entityId = other.entityId; + this.enabled = other.enabled; + } + + @Nonnull + public static BuilderToolSetNPCDebug deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolSetNPCDebug obj = new BuilderToolSetNPCDebug(); + obj.entityId = buf.getIntLE(offset + 0); + obj.enabled = buf.getByte(offset + 4) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 5; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.entityId); + buf.writeByte(this.enabled ? 1 : 0); + } + + @Override + public int computeSize() { + return 5; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 5 ? ValidationResult.error("Buffer too small: expected at least 5 bytes") : ValidationResult.OK; + } + + public BuilderToolSetNPCDebug clone() { + BuilderToolSetNPCDebug copy = new BuilderToolSetNPCDebug(); + copy.entityId = this.entityId; + copy.enabled = this.enabled; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolSetNPCDebug other) ? false : this.entityId == other.entityId && this.enabled == other.enabled; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.entityId, this.enabled); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetTransformationModeState.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetTransformationModeState.java new file mode 100644 index 0000000..b4265a1 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolSetTransformationModeState.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolSetTransformationModeState implements Packet { + public static final int PACKET_ID = 408; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean enabled; + + @Override + public int getId() { + return 408; + } + + public BuilderToolSetTransformationModeState() { + } + + public BuilderToolSetTransformationModeState(boolean enabled) { + this.enabled = enabled; + } + + public BuilderToolSetTransformationModeState(@Nonnull BuilderToolSetTransformationModeState other) { + this.enabled = other.enabled; + } + + @Nonnull + public static BuilderToolSetTransformationModeState deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolSetTransformationModeState obj = new BuilderToolSetTransformationModeState(); + obj.enabled = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.enabled ? 1 : 0); + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public BuilderToolSetTransformationModeState clone() { + BuilderToolSetTransformationModeState copy = new BuilderToolSetTransformationModeState(); + copy.enabled = this.enabled; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof BuilderToolSetTransformationModeState other ? this.enabled == other.enabled : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.enabled); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolShowAnchor.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolShowAnchor.java new file mode 100644 index 0000000..ea027ae --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolShowAnchor.java @@ -0,0 +1,91 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolShowAnchor implements Packet { + public static final int PACKET_ID = 415; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 12; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 12; + public static final int MAX_SIZE = 12; + public int x; + public int y; + public int z; + + @Override + public int getId() { + return 415; + } + + public BuilderToolShowAnchor() { + } + + public BuilderToolShowAnchor(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public BuilderToolShowAnchor(@Nonnull BuilderToolShowAnchor other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + } + + @Nonnull + public static BuilderToolShowAnchor deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolShowAnchor obj = new BuilderToolShowAnchor(); + obj.x = buf.getIntLE(offset + 0); + obj.y = buf.getIntLE(offset + 4); + obj.z = buf.getIntLE(offset + 8); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 12; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.x); + buf.writeIntLE(this.y); + buf.writeIntLE(this.z); + } + + @Override + public int computeSize() { + return 12; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 12 ? ValidationResult.error("Buffer too small: expected at least 12 bytes") : ValidationResult.OK; + } + + public BuilderToolShowAnchor clone() { + BuilderToolShowAnchor copy = new BuilderToolShowAnchor(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolShowAnchor other) ? false : this.x == other.x && this.y == other.y && this.z == other.z; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y, this.z); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolStackArea.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolStackArea.java new file mode 100644 index 0000000..e4ca72b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolStackArea.java @@ -0,0 +1,147 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderToolStackArea implements Packet { + public static final int PACKET_ID = 404; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 41; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 41; + public static final int MAX_SIZE = 41; + @Nullable + public BlockPosition selectionMin; + @Nullable + public BlockPosition selectionMax; + public int xNormal; + public int yNormal; + public int zNormal; + public int numStacks; + + @Override + public int getId() { + return 404; + } + + public BuilderToolStackArea() { + } + + public BuilderToolStackArea(@Nullable BlockPosition selectionMin, @Nullable BlockPosition selectionMax, int xNormal, int yNormal, int zNormal, int numStacks) { + this.selectionMin = selectionMin; + this.selectionMax = selectionMax; + this.xNormal = xNormal; + this.yNormal = yNormal; + this.zNormal = zNormal; + this.numStacks = numStacks; + } + + public BuilderToolStackArea(@Nonnull BuilderToolStackArea other) { + this.selectionMin = other.selectionMin; + this.selectionMax = other.selectionMax; + this.xNormal = other.xNormal; + this.yNormal = other.yNormal; + this.zNormal = other.zNormal; + this.numStacks = other.numStacks; + } + + @Nonnull + public static BuilderToolStackArea deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolStackArea obj = new BuilderToolStackArea(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.selectionMin = BlockPosition.deserialize(buf, offset + 1); + } + + if ((nullBits & 2) != 0) { + obj.selectionMax = BlockPosition.deserialize(buf, offset + 13); + } + + obj.xNormal = buf.getIntLE(offset + 25); + obj.yNormal = buf.getIntLE(offset + 29); + obj.zNormal = buf.getIntLE(offset + 33); + obj.numStacks = buf.getIntLE(offset + 37); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 41; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.selectionMin != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.selectionMax != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + if (this.selectionMin != null) { + this.selectionMin.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.selectionMax != null) { + this.selectionMax.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeIntLE(this.xNormal); + buf.writeIntLE(this.yNormal); + buf.writeIntLE(this.zNormal); + buf.writeIntLE(this.numStacks); + } + + @Override + public int computeSize() { + return 41; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 41 ? ValidationResult.error("Buffer too small: expected at least 41 bytes") : ValidationResult.OK; + } + + public BuilderToolStackArea clone() { + BuilderToolStackArea copy = new BuilderToolStackArea(); + copy.selectionMin = this.selectionMin != null ? this.selectionMin.clone() : null; + copy.selectionMax = this.selectionMax != null ? this.selectionMax.clone() : null; + copy.xNormal = this.xNormal; + copy.yNormal = this.yNormal; + copy.zNormal = this.zNormal; + copy.numStacks = this.numStacks; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolStackArea other) + ? false + : Objects.equals(this.selectionMin, other.selectionMin) + && Objects.equals(this.selectionMax, other.selectionMax) + && this.xNormal == other.xNormal + && this.yNormal == other.yNormal + && this.zNormal == other.zNormal + && this.numStacks == other.numStacks; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.selectionMin, this.selectionMax, this.xNormal, this.yNormal, this.zNormal, this.numStacks); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolState.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolState.java new file mode 100644 index 0000000..f21e7a8 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolState.java @@ -0,0 +1,362 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderToolState { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 14; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + public boolean isBrush; + @Nullable + public BuilderToolBrushData brushData; + @Nullable + public Map args; + + public BuilderToolState() { + } + + public BuilderToolState(@Nullable String id, boolean isBrush, @Nullable BuilderToolBrushData brushData, @Nullable Map args) { + this.id = id; + this.isBrush = isBrush; + this.brushData = brushData; + this.args = args; + } + + public BuilderToolState(@Nonnull BuilderToolState other) { + this.id = other.id; + this.isBrush = other.isBrush; + this.brushData = other.brushData; + this.args = other.args; + } + + @Nonnull + public static BuilderToolState deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolState obj = new BuilderToolState(); + byte nullBits = buf.getByte(offset); + obj.isBrush = buf.getByte(offset + 1) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 14 + buf.getIntLE(offset + 2); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 14 + buf.getIntLE(offset + 6); + obj.brushData = BuilderToolBrushData.deserialize(buf, varPos1); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 14 + buf.getIntLE(offset + 10); + int argsCount = VarInt.peek(buf, varPos2); + if (argsCount < 0) { + throw ProtocolException.negativeLength("Args", argsCount); + } + + if (argsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Args", argsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + obj.args = new HashMap<>(argsCount); + int dictPos = varPos2 + varIntLen; + + for (int i = 0; i < argsCount; i++) { + int keyLen = VarInt.peek(buf, dictPos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, dictPos); + String key = PacketIO.readVarString(buf, dictPos); + dictPos += keyVarLen + keyLen; + BuilderToolArg val = BuilderToolArg.deserialize(buf, dictPos); + dictPos += BuilderToolArg.computeBytesConsumed(buf, dictPos); + if (obj.args.put(key, val) != null) { + throw ProtocolException.duplicateKey("args", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 14; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 2); + int pos0 = offset + 14 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 6); + int pos1 = offset + 14 + fieldOffset1; + pos1 += BuilderToolBrushData.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 10); + int pos2 = offset + 14 + fieldOffset2; + int dictLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + pos2 += BuilderToolArg.computeBytesConsumed(buf, pos2); + } + + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.brushData != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.args != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + buf.writeByte(this.isBrush ? 1 : 0); + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int brushDataOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int argsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.brushData != null) { + buf.setIntLE(brushDataOffsetSlot, buf.writerIndex() - varBlockStart); + this.brushData.serialize(buf); + } else { + buf.setIntLE(brushDataOffsetSlot, -1); + } + + if (this.args != null) { + buf.setIntLE(argsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.args.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Args", this.args.size(), 4096000); + } + + VarInt.write(buf, this.args.size()); + + for (Entry e : this.args.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } else { + buf.setIntLE(argsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 14; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.brushData != null) { + size += this.brushData.computeSize(); + } + + if (this.args != null) { + int argsSize = 0; + + for (Entry kvp : this.args.entrySet()) { + argsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.args.size()) + argsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 14) { + return ValidationResult.error("Buffer too small: expected at least 14 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 2); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 14 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int brushDataOffset = buffer.getIntLE(offset + 6); + if (brushDataOffset < 0) { + return ValidationResult.error("Invalid offset for BrushData"); + } + + int posx = offset + 14 + brushDataOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BrushData"); + } + + ValidationResult brushDataResult = BuilderToolBrushData.validateStructure(buffer, posx); + if (!brushDataResult.isValid()) { + return ValidationResult.error("Invalid BrushData: " + brushDataResult.error()); + } + + posx += BuilderToolBrushData.computeBytesConsumed(buffer, posx); + } + + if ((nullBits & 4) != 0) { + int argsOffset = buffer.getIntLE(offset + 10); + if (argsOffset < 0) { + return ValidationResult.error("Invalid offset for Args"); + } + + int posxx = offset + 14 + argsOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Args"); + } + + int argsCount = VarInt.peek(buffer, posxx); + if (argsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Args"); + } + + if (argsCount > 4096000) { + return ValidationResult.error("Args exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + + for (int i = 0; i < argsCount; i++) { + int keyLen = VarInt.peek(buffer, posxx); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += keyLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + posxx += BuilderToolArg.computeBytesConsumed(buffer, posxx); + } + } + + return ValidationResult.OK; + } + } + + public BuilderToolState clone() { + BuilderToolState copy = new BuilderToolState(); + copy.id = this.id; + copy.isBrush = this.isBrush; + copy.brushData = this.brushData != null ? this.brushData.clone() : null; + if (this.args != null) { + Map m = new HashMap<>(); + + for (Entry e : this.args.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.args = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BuilderToolState other) + ? false + : Objects.equals(this.id, other.id) + && this.isBrush == other.isBrush + && Objects.equals(this.brushData, other.brushData) + && Objects.equals(this.args, other.args); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.isBrush, this.brushData, this.args); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolStringArg.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolStringArg.java new file mode 100644 index 0000000..312d34e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolStringArg.java @@ -0,0 +1,133 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderToolStringArg { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 16384006; + @Nullable + public String defaultValue; + + public BuilderToolStringArg() { + } + + public BuilderToolStringArg(@Nullable String defaultValue) { + this.defaultValue = defaultValue; + } + + public BuilderToolStringArg(@Nonnull BuilderToolStringArg other) { + this.defaultValue = other.defaultValue; + } + + @Nonnull + public static BuilderToolStringArg deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolStringArg obj = new BuilderToolStringArg(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int defaultValueLen = VarInt.peek(buf, pos); + if (defaultValueLen < 0) { + throw ProtocolException.negativeLength("Default", defaultValueLen); + } + + if (defaultValueLen > 4096000) { + throw ProtocolException.stringTooLong("Default", defaultValueLen, 4096000); + } + + int defaultValueVarLen = VarInt.length(buf, pos); + obj.defaultValue = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += defaultValueVarLen + defaultValueLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.defaultValue != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.defaultValue != null) { + PacketIO.writeVarString(buf, this.defaultValue, 4096000); + } + } + + public int computeSize() { + int size = 1; + if (this.defaultValue != null) { + size += PacketIO.stringSize(this.defaultValue); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int defaultLen = VarInt.peek(buffer, pos); + if (defaultLen < 0) { + return ValidationResult.error("Invalid string length for Default"); + } + + if (defaultLen > 4096000) { + return ValidationResult.error("Default exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += defaultLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Default"); + } + } + + return ValidationResult.OK; + } + } + + public BuilderToolStringArg clone() { + BuilderToolStringArg copy = new BuilderToolStringArg(); + copy.defaultValue = this.defaultValue; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof BuilderToolStringArg other ? Objects.equals(this.defaultValue, other.defaultValue) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.defaultValue); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolsSetSoundSet.java b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolsSetSoundSet.java new file mode 100644 index 0000000..ebf515b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/BuilderToolsSetSoundSet.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BuilderToolsSetSoundSet implements Packet { + public static final int PACKET_ID = 418; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 4; + public int soundSetIndex; + + @Override + public int getId() { + return 418; + } + + public BuilderToolsSetSoundSet() { + } + + public BuilderToolsSetSoundSet(int soundSetIndex) { + this.soundSetIndex = soundSetIndex; + } + + public BuilderToolsSetSoundSet(@Nonnull BuilderToolsSetSoundSet other) { + this.soundSetIndex = other.soundSetIndex; + } + + @Nonnull + public static BuilderToolsSetSoundSet deserialize(@Nonnull ByteBuf buf, int offset) { + BuilderToolsSetSoundSet obj = new BuilderToolsSetSoundSet(); + obj.soundSetIndex = buf.getIntLE(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 4; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.soundSetIndex); + } + + @Override + public int computeSize() { + return 4; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 4 ? ValidationResult.error("Buffer too small: expected at least 4 bytes") : ValidationResult.OK; + } + + public BuilderToolsSetSoundSet clone() { + BuilderToolsSetSoundSet copy = new BuilderToolsSetSoundSet(); + copy.soundSetIndex = this.soundSetIndex; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof BuilderToolsSetSoundSet other ? this.soundSetIndex == other.soundSetIndex : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.soundSetIndex); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/EntityToolAction.java b/src/com/hypixel/hytale/protocol/packets/buildertools/EntityToolAction.java new file mode 100644 index 0000000..225b68f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/EntityToolAction.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum EntityToolAction { + Remove(0), + Clone(1), + Freeze(2); + + public static final EntityToolAction[] VALUES = values(); + private final int value; + + private EntityToolAction(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static EntityToolAction fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("EntityToolAction", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/buildertools/PrefabUnselectPrefab.java b/src/com/hypixel/hytale/protocol/packets/buildertools/PrefabUnselectPrefab.java new file mode 100644 index 0000000..781a8cb --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/buildertools/PrefabUnselectPrefab.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.protocol.packets.buildertools; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class PrefabUnselectPrefab implements Packet { + public static final int PACKET_ID = 417; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public PrefabUnselectPrefab() { + } + + @Override + public int getId() { + return 417; + } + + @Nonnull + public static PrefabUnselectPrefab deserialize(@Nonnull ByteBuf buf, int offset) { + return new PrefabUnselectPrefab(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public PrefabUnselectPrefab clone() { + return new PrefabUnselectPrefab(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof PrefabUnselectPrefab other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/camera/CameraShakeEffect.java b/src/com/hypixel/hytale/protocol/packets/camera/CameraShakeEffect.java new file mode 100644 index 0000000..8199620 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/camera/CameraShakeEffect.java @@ -0,0 +1,95 @@ +package com.hypixel.hytale.protocol.packets.camera; + +import com.hypixel.hytale.protocol.AccumulationMode; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class CameraShakeEffect implements Packet { + public static final int PACKET_ID = 281; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 9; + public int cameraShakeId; + public float intensity; + @Nonnull + public AccumulationMode mode = AccumulationMode.Set; + + @Override + public int getId() { + return 281; + } + + public CameraShakeEffect() { + } + + public CameraShakeEffect(int cameraShakeId, float intensity, @Nonnull AccumulationMode mode) { + this.cameraShakeId = cameraShakeId; + this.intensity = intensity; + this.mode = mode; + } + + public CameraShakeEffect(@Nonnull CameraShakeEffect other) { + this.cameraShakeId = other.cameraShakeId; + this.intensity = other.intensity; + this.mode = other.mode; + } + + @Nonnull + public static CameraShakeEffect deserialize(@Nonnull ByteBuf buf, int offset) { + CameraShakeEffect obj = new CameraShakeEffect(); + obj.cameraShakeId = buf.getIntLE(offset + 0); + obj.intensity = buf.getFloatLE(offset + 4); + obj.mode = AccumulationMode.fromValue(buf.getByte(offset + 8)); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 9; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.cameraShakeId); + buf.writeFloatLE(this.intensity); + buf.writeByte(this.mode.getValue()); + } + + @Override + public int computeSize() { + return 9; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 9 ? ValidationResult.error("Buffer too small: expected at least 9 bytes") : ValidationResult.OK; + } + + public CameraShakeEffect clone() { + CameraShakeEffect copy = new CameraShakeEffect(); + copy.cameraShakeId = this.cameraShakeId; + copy.intensity = this.intensity; + copy.mode = this.mode; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CameraShakeEffect other) + ? false + : this.cameraShakeId == other.cameraShakeId && this.intensity == other.intensity && Objects.equals(this.mode, other.mode); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.cameraShakeId, this.intensity, this.mode); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/camera/RequestFlyCameraMode.java b/src/com/hypixel/hytale/protocol/packets/camera/RequestFlyCameraMode.java new file mode 100644 index 0000000..968428e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/camera/RequestFlyCameraMode.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.camera; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class RequestFlyCameraMode implements Packet { + public static final int PACKET_ID = 282; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean entering; + + @Override + public int getId() { + return 282; + } + + public RequestFlyCameraMode() { + } + + public RequestFlyCameraMode(boolean entering) { + this.entering = entering; + } + + public RequestFlyCameraMode(@Nonnull RequestFlyCameraMode other) { + this.entering = other.entering; + } + + @Nonnull + public static RequestFlyCameraMode deserialize(@Nonnull ByteBuf buf, int offset) { + RequestFlyCameraMode obj = new RequestFlyCameraMode(); + obj.entering = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.entering ? 1 : 0); + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public RequestFlyCameraMode clone() { + RequestFlyCameraMode copy = new RequestFlyCameraMode(); + copy.entering = this.entering; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof RequestFlyCameraMode other ? this.entering == other.entering : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.entering); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/camera/SetFlyCameraMode.java b/src/com/hypixel/hytale/protocol/packets/camera/SetFlyCameraMode.java new file mode 100644 index 0000000..7ee7f4c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/camera/SetFlyCameraMode.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.camera; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SetFlyCameraMode implements Packet { + public static final int PACKET_ID = 283; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean entering; + + @Override + public int getId() { + return 283; + } + + public SetFlyCameraMode() { + } + + public SetFlyCameraMode(boolean entering) { + this.entering = entering; + } + + public SetFlyCameraMode(@Nonnull SetFlyCameraMode other) { + this.entering = other.entering; + } + + @Nonnull + public static SetFlyCameraMode deserialize(@Nonnull ByteBuf buf, int offset) { + SetFlyCameraMode obj = new SetFlyCameraMode(); + obj.entering = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.entering ? 1 : 0); + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public SetFlyCameraMode clone() { + SetFlyCameraMode copy = new SetFlyCameraMode(); + copy.entering = this.entering; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof SetFlyCameraMode other ? this.entering == other.entering : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.entering); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/camera/SetServerCamera.java b/src/com/hypixel/hytale/protocol/packets/camera/SetServerCamera.java new file mode 100644 index 0000000..19109ac --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/camera/SetServerCamera.java @@ -0,0 +1,114 @@ +package com.hypixel.hytale.protocol.packets.camera; + +import com.hypixel.hytale.protocol.ClientCameraView; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.ServerCameraSettings; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SetServerCamera implements Packet { + public static final int PACKET_ID = 280; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 157; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 157; + public static final int MAX_SIZE = 157; + @Nonnull + public ClientCameraView clientCameraView = ClientCameraView.FirstPerson; + public boolean isLocked; + @Nullable + public ServerCameraSettings cameraSettings; + + @Override + public int getId() { + return 280; + } + + public SetServerCamera() { + } + + public SetServerCamera(@Nonnull ClientCameraView clientCameraView, boolean isLocked, @Nullable ServerCameraSettings cameraSettings) { + this.clientCameraView = clientCameraView; + this.isLocked = isLocked; + this.cameraSettings = cameraSettings; + } + + public SetServerCamera(@Nonnull SetServerCamera other) { + this.clientCameraView = other.clientCameraView; + this.isLocked = other.isLocked; + this.cameraSettings = other.cameraSettings; + } + + @Nonnull + public static SetServerCamera deserialize(@Nonnull ByteBuf buf, int offset) { + SetServerCamera obj = new SetServerCamera(); + byte nullBits = buf.getByte(offset); + obj.clientCameraView = ClientCameraView.fromValue(buf.getByte(offset + 1)); + obj.isLocked = buf.getByte(offset + 2) != 0; + if ((nullBits & 1) != 0) { + obj.cameraSettings = ServerCameraSettings.deserialize(buf, offset + 3); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 157; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.cameraSettings != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.clientCameraView.getValue()); + buf.writeByte(this.isLocked ? 1 : 0); + if (this.cameraSettings != null) { + this.cameraSettings.serialize(buf); + } else { + buf.writeZero(154); + } + } + + @Override + public int computeSize() { + return 157; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 157 ? ValidationResult.error("Buffer too small: expected at least 157 bytes") : ValidationResult.OK; + } + + public SetServerCamera clone() { + SetServerCamera copy = new SetServerCamera(); + copy.clientCameraView = this.clientCameraView; + copy.isLocked = this.isLocked; + copy.cameraSettings = this.cameraSettings != null ? this.cameraSettings.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SetServerCamera other) + ? false + : Objects.equals(this.clientCameraView, other.clientCameraView) + && this.isLocked == other.isLocked + && Objects.equals(this.cameraSettings, other.cameraSettings); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.clientCameraView, this.isLocked, this.cameraSettings); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/connection/ClientType.java b/src/com/hypixel/hytale/protocol/packets/connection/ClientType.java new file mode 100644 index 0000000..8a00f7f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/connection/ClientType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol.packets.connection; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ClientType { + Game(0), + Editor(1); + + public static final ClientType[] VALUES = values(); + private final int value; + + private ClientType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ClientType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ClientType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/connection/Connect.java b/src/com/hypixel/hytale/protocol/packets/connection/Connect.java new file mode 100644 index 0000000..e7411f6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/connection/Connect.java @@ -0,0 +1,480 @@ +package com.hypixel.hytale.protocol.packets.connection; + +import com.hypixel.hytale.protocol.HostAddress; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Connect implements Packet { + public static final int PACKET_ID = 0; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 82; + public static final int VARIABLE_FIELD_COUNT = 5; + public static final int VARIABLE_BLOCK_START = 102; + public static final int MAX_SIZE = 38161; + @Nonnull + public String protocolHash = ""; + @Nonnull + public ClientType clientType = ClientType.Game; + @Nullable + public String language; + @Nullable + public String identityToken; + @Nonnull + public UUID uuid = new UUID(0L, 0L); + @Nonnull + public String username = ""; + @Nullable + public byte[] referralData; + @Nullable + public HostAddress referralSource; + + @Override + public int getId() { + return 0; + } + + public Connect() { + } + + public Connect( + @Nonnull String protocolHash, + @Nonnull ClientType clientType, + @Nullable String language, + @Nullable String identityToken, + @Nonnull UUID uuid, + @Nonnull String username, + @Nullable byte[] referralData, + @Nullable HostAddress referralSource + ) { + this.protocolHash = protocolHash; + this.clientType = clientType; + this.language = language; + this.identityToken = identityToken; + this.uuid = uuid; + this.username = username; + this.referralData = referralData; + this.referralSource = referralSource; + } + + public Connect(@Nonnull Connect other) { + this.protocolHash = other.protocolHash; + this.clientType = other.clientType; + this.language = other.language; + this.identityToken = other.identityToken; + this.uuid = other.uuid; + this.username = other.username; + this.referralData = other.referralData; + this.referralSource = other.referralSource; + } + + @Nonnull + public static Connect deserialize(@Nonnull ByteBuf buf, int offset) { + Connect obj = new Connect(); + byte nullBits = buf.getByte(offset); + obj.protocolHash = PacketIO.readFixedAsciiString(buf, offset + 1, 64); + obj.clientType = ClientType.fromValue(buf.getByte(offset + 65)); + obj.uuid = PacketIO.readUUID(buf, offset + 66); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 102 + buf.getIntLE(offset + 82); + int languageLen = VarInt.peek(buf, varPos0); + if (languageLen < 0) { + throw ProtocolException.negativeLength("Language", languageLen); + } + + if (languageLen > 128) { + throw ProtocolException.stringTooLong("Language", languageLen, 128); + } + + obj.language = PacketIO.readVarString(buf, varPos0, PacketIO.ASCII); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 102 + buf.getIntLE(offset + 86); + int identityTokenLen = VarInt.peek(buf, varPos1); + if (identityTokenLen < 0) { + throw ProtocolException.negativeLength("IdentityToken", identityTokenLen); + } + + if (identityTokenLen > 8192) { + throw ProtocolException.stringTooLong("IdentityToken", identityTokenLen, 8192); + } + + obj.identityToken = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + int varPos2 = offset + 102 + buf.getIntLE(offset + 90); + int usernameLen = VarInt.peek(buf, varPos2); + if (usernameLen < 0) { + throw ProtocolException.negativeLength("Username", usernameLen); + } else if (usernameLen > 16) { + throw ProtocolException.stringTooLong("Username", usernameLen, 16); + } else { + obj.username = PacketIO.readVarString(buf, varPos2, PacketIO.ASCII); + if ((nullBits & 4) != 0) { + varPos2 = offset + 102 + buf.getIntLE(offset + 94); + usernameLen = VarInt.peek(buf, varPos2); + if (usernameLen < 0) { + throw ProtocolException.negativeLength("ReferralData", usernameLen); + } + + if (usernameLen > 4096) { + throw ProtocolException.arrayTooLong("ReferralData", usernameLen, 4096); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + usernameLen * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("ReferralData", varPos2 + varIntLen + usernameLen * 1, buf.readableBytes()); + } + + obj.referralData = new byte[usernameLen]; + + for (int i = 0; i < usernameLen; i++) { + obj.referralData[i] = buf.getByte(varPos2 + varIntLen + i * 1); + } + } + + if ((nullBits & 8) != 0) { + varPos2 = offset + 102 + buf.getIntLE(offset + 98); + obj.referralSource = HostAddress.deserialize(buf, varPos2); + } + + return obj; + } + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 102; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 82); + int pos0 = offset + 102 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 86); + int pos1 = offset + 102 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + int fieldOffset2 = buf.getIntLE(offset + 90); + int pos2 = offset + 102 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + + if ((nullBits & 4) != 0) { + fieldOffset2 = buf.getIntLE(offset + 94); + pos2 = offset + 102 + fieldOffset2; + sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl * 1; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + fieldOffset2 = buf.getIntLE(offset + 98); + pos2 = offset + 102 + fieldOffset2; + pos2 += HostAddress.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.language != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.identityToken != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.referralData != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.referralSource != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + PacketIO.writeFixedAsciiString(buf, this.protocolHash, 64); + buf.writeByte(this.clientType.getValue()); + PacketIO.writeUUID(buf, this.uuid); + int languageOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int identityTokenOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int usernameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int referralDataOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int referralSourceOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.language != null) { + buf.setIntLE(languageOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarAsciiString(buf, this.language, 128); + } else { + buf.setIntLE(languageOffsetSlot, -1); + } + + if (this.identityToken != null) { + buf.setIntLE(identityTokenOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.identityToken, 8192); + } else { + buf.setIntLE(identityTokenOffsetSlot, -1); + } + + buf.setIntLE(usernameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarAsciiString(buf, this.username, 16); + if (this.referralData != null) { + buf.setIntLE(referralDataOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.referralData.length > 4096) { + throw ProtocolException.arrayTooLong("ReferralData", this.referralData.length, 4096); + } + + VarInt.write(buf, this.referralData.length); + + for (byte item : this.referralData) { + buf.writeByte(item); + } + } else { + buf.setIntLE(referralDataOffsetSlot, -1); + } + + if (this.referralSource != null) { + buf.setIntLE(referralSourceOffsetSlot, buf.writerIndex() - varBlockStart); + this.referralSource.serialize(buf); + } else { + buf.setIntLE(referralSourceOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 102; + if (this.language != null) { + size += VarInt.size(this.language.length()) + this.language.length(); + } + + if (this.identityToken != null) { + size += PacketIO.stringSize(this.identityToken); + } + + size += VarInt.size(this.username.length()) + this.username.length(); + if (this.referralData != null) { + size += VarInt.size(this.referralData.length) + this.referralData.length * 1; + } + + if (this.referralSource != null) { + size += this.referralSource.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 102) { + return ValidationResult.error("Buffer too small: expected at least 102 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int languageOffset = buffer.getIntLE(offset + 82); + if (languageOffset < 0) { + return ValidationResult.error("Invalid offset for Language"); + } + + int pos = offset + 102 + languageOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Language"); + } + + int languageLen = VarInt.peek(buffer, pos); + if (languageLen < 0) { + return ValidationResult.error("Invalid string length for Language"); + } + + if (languageLen > 128) { + return ValidationResult.error("Language exceeds max length 128"); + } + + pos += VarInt.length(buffer, pos); + pos += languageLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Language"); + } + } + + if ((nullBits & 2) != 0) { + int identityTokenOffset = buffer.getIntLE(offset + 86); + if (identityTokenOffset < 0) { + return ValidationResult.error("Invalid offset for IdentityToken"); + } + + int posx = offset + 102 + identityTokenOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for IdentityToken"); + } + + int identityTokenLen = VarInt.peek(buffer, posx); + if (identityTokenLen < 0) { + return ValidationResult.error("Invalid string length for IdentityToken"); + } + + if (identityTokenLen > 8192) { + return ValidationResult.error("IdentityToken exceeds max length 8192"); + } + + posx += VarInt.length(buffer, posx); + posx += identityTokenLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading IdentityToken"); + } + } + + int usernameOffset = buffer.getIntLE(offset + 90); + if (usernameOffset < 0) { + return ValidationResult.error("Invalid offset for Username"); + } else { + int posxx = offset + 102 + usernameOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Username"); + } else { + int usernameLen = VarInt.peek(buffer, posxx); + if (usernameLen < 0) { + return ValidationResult.error("Invalid string length for Username"); + } else if (usernameLen > 16) { + return ValidationResult.error("Username exceeds max length 16"); + } else { + posxx += VarInt.length(buffer, posxx); + posxx += usernameLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Username"); + } else { + if ((nullBits & 4) != 0) { + usernameOffset = buffer.getIntLE(offset + 94); + if (usernameOffset < 0) { + return ValidationResult.error("Invalid offset for ReferralData"); + } + + posxx = offset + 102 + usernameOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ReferralData"); + } + + usernameLen = VarInt.peek(buffer, posxx); + if (usernameLen < 0) { + return ValidationResult.error("Invalid array count for ReferralData"); + } + + if (usernameLen > 4096) { + return ValidationResult.error("ReferralData exceeds max length 4096"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += usernameLen * 1; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ReferralData"); + } + } + + if ((nullBits & 8) != 0) { + usernameOffset = buffer.getIntLE(offset + 98); + if (usernameOffset < 0) { + return ValidationResult.error("Invalid offset for ReferralSource"); + } + + posxx = offset + 102 + usernameOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ReferralSource"); + } + + ValidationResult referralSourceResult = HostAddress.validateStructure(buffer, posxx); + if (!referralSourceResult.isValid()) { + return ValidationResult.error("Invalid ReferralSource: " + referralSourceResult.error()); + } + + posxx += HostAddress.computeBytesConsumed(buffer, posxx); + } + + return ValidationResult.OK; + } + } + } + } + } + } + + public Connect clone() { + Connect copy = new Connect(); + copy.protocolHash = this.protocolHash; + copy.clientType = this.clientType; + copy.language = this.language; + copy.identityToken = this.identityToken; + copy.uuid = this.uuid; + copy.username = this.username; + copy.referralData = this.referralData != null ? Arrays.copyOf(this.referralData, this.referralData.length) : null; + copy.referralSource = this.referralSource != null ? this.referralSource.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Connect other) + ? false + : Objects.equals(this.protocolHash, other.protocolHash) + && Objects.equals(this.clientType, other.clientType) + && Objects.equals(this.language, other.language) + && Objects.equals(this.identityToken, other.identityToken) + && Objects.equals(this.uuid, other.uuid) + && Objects.equals(this.username, other.username) + && Arrays.equals(this.referralData, other.referralData) + && Objects.equals(this.referralSource, other.referralSource); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.protocolHash); + result = 31 * result + Objects.hashCode(this.clientType); + result = 31 * result + Objects.hashCode(this.language); + result = 31 * result + Objects.hashCode(this.identityToken); + result = 31 * result + Objects.hashCode(this.uuid); + result = 31 * result + Objects.hashCode(this.username); + result = 31 * result + Arrays.hashCode(this.referralData); + return 31 * result + Objects.hashCode(this.referralSource); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/connection/Disconnect.java b/src/com/hypixel/hytale/protocol/packets/connection/Disconnect.java new file mode 100644 index 0000000..ae099cd --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/connection/Disconnect.java @@ -0,0 +1,150 @@ +package com.hypixel.hytale.protocol.packets.connection; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Disconnect implements Packet { + public static final int PACKET_ID = 1; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 16384007; + @Nullable + public String reason; + @Nonnull + public DisconnectType type = DisconnectType.Disconnect; + + @Override + public int getId() { + return 1; + } + + public Disconnect() { + } + + public Disconnect(@Nullable String reason, @Nonnull DisconnectType type) { + this.reason = reason; + this.type = type; + } + + public Disconnect(@Nonnull Disconnect other) { + this.reason = other.reason; + this.type = other.type; + } + + @Nonnull + public static Disconnect deserialize(@Nonnull ByteBuf buf, int offset) { + Disconnect obj = new Disconnect(); + byte nullBits = buf.getByte(offset); + obj.type = DisconnectType.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int reasonLen = VarInt.peek(buf, pos); + if (reasonLen < 0) { + throw ProtocolException.negativeLength("Reason", reasonLen); + } + + if (reasonLen > 4096000) { + throw ProtocolException.stringTooLong("Reason", reasonLen, 4096000); + } + + int reasonVarLen = VarInt.length(buf, pos); + obj.reason = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += reasonVarLen + reasonLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.reason != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.reason != null) { + PacketIO.writeVarString(buf, this.reason, 4096000); + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.reason != null) { + size += PacketIO.stringSize(this.reason); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int reasonLen = VarInt.peek(buffer, pos); + if (reasonLen < 0) { + return ValidationResult.error("Invalid string length for Reason"); + } + + if (reasonLen > 4096000) { + return ValidationResult.error("Reason exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += reasonLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Reason"); + } + } + + return ValidationResult.OK; + } + } + + public Disconnect clone() { + Disconnect copy = new Disconnect(); + copy.reason = this.reason; + copy.type = this.type; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Disconnect other) ? false : Objects.equals(this.reason, other.reason) && Objects.equals(this.type, other.type); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.reason, this.type); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/connection/DisconnectType.java b/src/com/hypixel/hytale/protocol/packets/connection/DisconnectType.java new file mode 100644 index 0000000..0ace11e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/connection/DisconnectType.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol.packets.connection; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum DisconnectType { + Disconnect(0), + Crash(1); + + public static final DisconnectType[] VALUES = values(); + private final int value; + + private DisconnectType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static DisconnectType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("DisconnectType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/connection/Ping.java b/src/com/hypixel/hytale/protocol/packets/connection/Ping.java new file mode 100644 index 0000000..30dc2bb --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/connection/Ping.java @@ -0,0 +1,127 @@ +package com.hypixel.hytale.protocol.packets.connection; + +import com.hypixel.hytale.protocol.InstantData; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Ping implements Packet { + public static final int PACKET_ID = 2; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 29; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 29; + public static final int MAX_SIZE = 29; + public int id; + @Nullable + public InstantData time; + public int lastPingValueRaw; + public int lastPingValueDirect; + public int lastPingValueTick; + + @Override + public int getId() { + return 2; + } + + public Ping() { + } + + public Ping(int id, @Nullable InstantData time, int lastPingValueRaw, int lastPingValueDirect, int lastPingValueTick) { + this.id = id; + this.time = time; + this.lastPingValueRaw = lastPingValueRaw; + this.lastPingValueDirect = lastPingValueDirect; + this.lastPingValueTick = lastPingValueTick; + } + + public Ping(@Nonnull Ping other) { + this.id = other.id; + this.time = other.time; + this.lastPingValueRaw = other.lastPingValueRaw; + this.lastPingValueDirect = other.lastPingValueDirect; + this.lastPingValueTick = other.lastPingValueTick; + } + + @Nonnull + public static Ping deserialize(@Nonnull ByteBuf buf, int offset) { + Ping obj = new Ping(); + byte nullBits = buf.getByte(offset); + obj.id = buf.getIntLE(offset + 1); + if ((nullBits & 1) != 0) { + obj.time = InstantData.deserialize(buf, offset + 5); + } + + obj.lastPingValueRaw = buf.getIntLE(offset + 17); + obj.lastPingValueDirect = buf.getIntLE(offset + 21); + obj.lastPingValueTick = buf.getIntLE(offset + 25); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 29; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.time != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.id); + if (this.time != null) { + this.time.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeIntLE(this.lastPingValueRaw); + buf.writeIntLE(this.lastPingValueDirect); + buf.writeIntLE(this.lastPingValueTick); + } + + @Override + public int computeSize() { + return 29; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 29 ? ValidationResult.error("Buffer too small: expected at least 29 bytes") : ValidationResult.OK; + } + + public Ping clone() { + Ping copy = new Ping(); + copy.id = this.id; + copy.time = this.time != null ? this.time.clone() : null; + copy.lastPingValueRaw = this.lastPingValueRaw; + copy.lastPingValueDirect = this.lastPingValueDirect; + copy.lastPingValueTick = this.lastPingValueTick; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Ping other) + ? false + : this.id == other.id + && Objects.equals(this.time, other.time) + && this.lastPingValueRaw == other.lastPingValueRaw + && this.lastPingValueDirect == other.lastPingValueDirect + && this.lastPingValueTick == other.lastPingValueTick; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.time, this.lastPingValueRaw, this.lastPingValueDirect, this.lastPingValueTick); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/connection/Pong.java b/src/com/hypixel/hytale/protocol/packets/connection/Pong.java new file mode 100644 index 0000000..5c4d034 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/connection/Pong.java @@ -0,0 +1,121 @@ +package com.hypixel.hytale.protocol.packets.connection; + +import com.hypixel.hytale.protocol.InstantData; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Pong implements Packet { + public static final int PACKET_ID = 3; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 20; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 20; + public static final int MAX_SIZE = 20; + public int id; + @Nullable + public InstantData time; + @Nonnull + public PongType type = PongType.Raw; + public short packetQueueSize; + + @Override + public int getId() { + return 3; + } + + public Pong() { + } + + public Pong(int id, @Nullable InstantData time, @Nonnull PongType type, short packetQueueSize) { + this.id = id; + this.time = time; + this.type = type; + this.packetQueueSize = packetQueueSize; + } + + public Pong(@Nonnull Pong other) { + this.id = other.id; + this.time = other.time; + this.type = other.type; + this.packetQueueSize = other.packetQueueSize; + } + + @Nonnull + public static Pong deserialize(@Nonnull ByteBuf buf, int offset) { + Pong obj = new Pong(); + byte nullBits = buf.getByte(offset); + obj.id = buf.getIntLE(offset + 1); + if ((nullBits & 1) != 0) { + obj.time = InstantData.deserialize(buf, offset + 5); + } + + obj.type = PongType.fromValue(buf.getByte(offset + 17)); + obj.packetQueueSize = buf.getShortLE(offset + 18); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 20; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.time != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.id); + if (this.time != null) { + this.time.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeByte(this.type.getValue()); + buf.writeShortLE(this.packetQueueSize); + } + + @Override + public int computeSize() { + return 20; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 20 ? ValidationResult.error("Buffer too small: expected at least 20 bytes") : ValidationResult.OK; + } + + public Pong clone() { + Pong copy = new Pong(); + copy.id = this.id; + copy.time = this.time != null ? this.time.clone() : null; + copy.type = this.type; + copy.packetQueueSize = this.packetQueueSize; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Pong other) + ? false + : this.id == other.id + && Objects.equals(this.time, other.time) + && Objects.equals(this.type, other.type) + && this.packetQueueSize == other.packetQueueSize; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.time, this.type, this.packetQueueSize); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/connection/PongType.java b/src/com/hypixel/hytale/protocol/packets/connection/PongType.java new file mode 100644 index 0000000..6466044 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/connection/PongType.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol.packets.connection; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum PongType { + Raw(0), + Direct(1), + Tick(2); + + public static final PongType[] VALUES = values(); + private final int value; + + private PongType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static PongType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("PongType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/entities/ApplyKnockback.java b/src/com/hypixel/hytale/protocol/packets/entities/ApplyKnockback.java new file mode 100644 index 0000000..fdc5a86 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/entities/ApplyKnockback.java @@ -0,0 +1,129 @@ +package com.hypixel.hytale.protocol.packets.entities; + +import com.hypixel.hytale.protocol.ChangeVelocityType; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.Position; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ApplyKnockback implements Packet { + public static final int PACKET_ID = 164; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 38; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 38; + public static final int MAX_SIZE = 38; + @Nullable + public Position hitPosition; + public float x; + public float y; + public float z; + @Nonnull + public ChangeVelocityType changeType = ChangeVelocityType.Add; + + @Override + public int getId() { + return 164; + } + + public ApplyKnockback() { + } + + public ApplyKnockback(@Nullable Position hitPosition, float x, float y, float z, @Nonnull ChangeVelocityType changeType) { + this.hitPosition = hitPosition; + this.x = x; + this.y = y; + this.z = z; + this.changeType = changeType; + } + + public ApplyKnockback(@Nonnull ApplyKnockback other) { + this.hitPosition = other.hitPosition; + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.changeType = other.changeType; + } + + @Nonnull + public static ApplyKnockback deserialize(@Nonnull ByteBuf buf, int offset) { + ApplyKnockback obj = new ApplyKnockback(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.hitPosition = Position.deserialize(buf, offset + 1); + } + + obj.x = buf.getFloatLE(offset + 25); + obj.y = buf.getFloatLE(offset + 29); + obj.z = buf.getFloatLE(offset + 33); + obj.changeType = ChangeVelocityType.fromValue(buf.getByte(offset + 37)); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 38; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.hitPosition != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.hitPosition != null) { + this.hitPosition.serialize(buf); + } else { + buf.writeZero(24); + } + + buf.writeFloatLE(this.x); + buf.writeFloatLE(this.y); + buf.writeFloatLE(this.z); + buf.writeByte(this.changeType.getValue()); + } + + @Override + public int computeSize() { + return 38; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 38 ? ValidationResult.error("Buffer too small: expected at least 38 bytes") : ValidationResult.OK; + } + + public ApplyKnockback clone() { + ApplyKnockback copy = new ApplyKnockback(); + copy.hitPosition = this.hitPosition != null ? this.hitPosition.clone() : null; + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + copy.changeType = this.changeType; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ApplyKnockback other) + ? false + : Objects.equals(this.hitPosition, other.hitPosition) + && this.x == other.x + && this.y == other.y + && this.z == other.z + && Objects.equals(this.changeType, other.changeType); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.hitPosition, this.x, this.y, this.z, this.changeType); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/entities/ChangeVelocity.java b/src/com/hypixel/hytale/protocol/packets/entities/ChangeVelocity.java new file mode 100644 index 0000000..cc0f215 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/entities/ChangeVelocity.java @@ -0,0 +1,128 @@ +package com.hypixel.hytale.protocol.packets.entities; + +import com.hypixel.hytale.protocol.ChangeVelocityType; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.VelocityConfig; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChangeVelocity implements Packet { + public static final int PACKET_ID = 163; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 35; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 35; + public static final int MAX_SIZE = 35; + public float x; + public float y; + public float z; + @Nonnull + public ChangeVelocityType changeType = ChangeVelocityType.Add; + @Nullable + public VelocityConfig config; + + @Override + public int getId() { + return 163; + } + + public ChangeVelocity() { + } + + public ChangeVelocity(float x, float y, float z, @Nonnull ChangeVelocityType changeType, @Nullable VelocityConfig config) { + this.x = x; + this.y = y; + this.z = z; + this.changeType = changeType; + this.config = config; + } + + public ChangeVelocity(@Nonnull ChangeVelocity other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.changeType = other.changeType; + this.config = other.config; + } + + @Nonnull + public static ChangeVelocity deserialize(@Nonnull ByteBuf buf, int offset) { + ChangeVelocity obj = new ChangeVelocity(); + byte nullBits = buf.getByte(offset); + obj.x = buf.getFloatLE(offset + 1); + obj.y = buf.getFloatLE(offset + 5); + obj.z = buf.getFloatLE(offset + 9); + obj.changeType = ChangeVelocityType.fromValue(buf.getByte(offset + 13)); + if ((nullBits & 1) != 0) { + obj.config = VelocityConfig.deserialize(buf, offset + 14); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 35; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.config != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.x); + buf.writeFloatLE(this.y); + buf.writeFloatLE(this.z); + buf.writeByte(this.changeType.getValue()); + if (this.config != null) { + this.config.serialize(buf); + } else { + buf.writeZero(21); + } + } + + @Override + public int computeSize() { + return 35; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 35 ? ValidationResult.error("Buffer too small: expected at least 35 bytes") : ValidationResult.OK; + } + + public ChangeVelocity clone() { + ChangeVelocity copy = new ChangeVelocity(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + copy.changeType = this.changeType; + copy.config = this.config != null ? this.config.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ChangeVelocity other) + ? false + : this.x == other.x + && this.y == other.y + && this.z == other.z + && Objects.equals(this.changeType, other.changeType) + && Objects.equals(this.config, other.config); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y, this.z, this.changeType, this.config); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/entities/EntityUpdates.java b/src/com/hypixel/hytale/protocol/packets/entities/EntityUpdates.java new file mode 100644 index 0000000..4005243 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/entities/EntityUpdates.java @@ -0,0 +1,291 @@ +package com.hypixel.hytale.protocol.packets.entities; + +import com.hypixel.hytale.protocol.EntityUpdate; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityUpdates implements Packet { + public static final int PACKET_ID = 161; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 1677721600; + @Nullable + public int[] removed; + @Nullable + public EntityUpdate[] updates; + + @Override + public int getId() { + return 161; + } + + public EntityUpdates() { + } + + public EntityUpdates(@Nullable int[] removed, @Nullable EntityUpdate[] updates) { + this.removed = removed; + this.updates = updates; + } + + public EntityUpdates(@Nonnull EntityUpdates other) { + this.removed = other.removed; + this.updates = other.updates; + } + + @Nonnull + public static EntityUpdates deserialize(@Nonnull ByteBuf buf, int offset) { + EntityUpdates obj = new EntityUpdates(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 9 + buf.getIntLE(offset + 1); + int removedCount = VarInt.peek(buf, varPos0); + if (removedCount < 0) { + throw ProtocolException.negativeLength("Removed", removedCount); + } + + if (removedCount > 4096000) { + throw ProtocolException.arrayTooLong("Removed", removedCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + removedCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Removed", varPos0 + varIntLen + removedCount * 4, buf.readableBytes()); + } + + obj.removed = new int[removedCount]; + + for (int i = 0; i < removedCount; i++) { + obj.removed[i] = buf.getIntLE(varPos0 + varIntLen + i * 4); + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 9 + buf.getIntLE(offset + 5); + int updatesCount = VarInt.peek(buf, varPos1); + if (updatesCount < 0) { + throw ProtocolException.negativeLength("Updates", updatesCount); + } + + if (updatesCount > 4096000) { + throw ProtocolException.arrayTooLong("Updates", updatesCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + updatesCount * 5L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Updates", varPos1 + varIntLen + updatesCount * 5, buf.readableBytes()); + } + + obj.updates = new EntityUpdate[updatesCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < updatesCount; i++) { + obj.updates[i] = EntityUpdate.deserialize(buf, elemPos); + elemPos += EntityUpdate.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 9; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 9 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + arrLen * 4; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 9 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += EntityUpdate.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.removed != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.updates != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + int removedOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int updatesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.removed != null) { + buf.setIntLE(removedOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.removed.length > 4096000) { + throw ProtocolException.arrayTooLong("Removed", this.removed.length, 4096000); + } + + VarInt.write(buf, this.removed.length); + + for (int item : this.removed) { + buf.writeIntLE(item); + } + } else { + buf.setIntLE(removedOffsetSlot, -1); + } + + if (this.updates != null) { + buf.setIntLE(updatesOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.updates.length > 4096000) { + throw ProtocolException.arrayTooLong("Updates", this.updates.length, 4096000); + } + + VarInt.write(buf, this.updates.length); + + for (EntityUpdate item : this.updates) { + item.serialize(buf); + } + } else { + buf.setIntLE(updatesOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 9; + if (this.removed != null) { + size += VarInt.size(this.removed.length) + this.removed.length * 4; + } + + if (this.updates != null) { + int updatesSize = 0; + + for (EntityUpdate elem : this.updates) { + updatesSize += elem.computeSize(); + } + + size += VarInt.size(this.updates.length) + updatesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int removedOffset = buffer.getIntLE(offset + 1); + if (removedOffset < 0) { + return ValidationResult.error("Invalid offset for Removed"); + } + + int pos = offset + 9 + removedOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Removed"); + } + + int removedCount = VarInt.peek(buffer, pos); + if (removedCount < 0) { + return ValidationResult.error("Invalid array count for Removed"); + } + + if (removedCount > 4096000) { + return ValidationResult.error("Removed exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += removedCount * 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Removed"); + } + } + + if ((nullBits & 2) != 0) { + int updatesOffset = buffer.getIntLE(offset + 5); + if (updatesOffset < 0) { + return ValidationResult.error("Invalid offset for Updates"); + } + + int posx = offset + 9 + updatesOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Updates"); + } + + int updatesCount = VarInt.peek(buffer, posx); + if (updatesCount < 0) { + return ValidationResult.error("Invalid array count for Updates"); + } + + if (updatesCount > 4096000) { + return ValidationResult.error("Updates exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < updatesCount; i++) { + ValidationResult structResult = EntityUpdate.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid EntityUpdate in Updates[" + i + "]: " + structResult.error()); + } + + posx += EntityUpdate.computeBytesConsumed(buffer, posx); + } + } + + return ValidationResult.OK; + } + } + + public EntityUpdates clone() { + EntityUpdates copy = new EntityUpdates(); + copy.removed = this.removed != null ? Arrays.copyOf(this.removed, this.removed.length) : null; + copy.updates = this.updates != null ? Arrays.stream(this.updates).map(e -> e.clone()).toArray(EntityUpdate[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof EntityUpdates other) + ? false + : Arrays.equals(this.removed, other.removed) && Arrays.equals((Object[])this.updates, (Object[])other.updates); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode(this.removed); + return 31 * result + Arrays.hashCode((Object[])this.updates); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/entities/MountMovement.java b/src/com/hypixel/hytale/protocol/packets/entities/MountMovement.java new file mode 100644 index 0000000..40d1179 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/entities/MountMovement.java @@ -0,0 +1,140 @@ +package com.hypixel.hytale.protocol.packets.entities; + +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.Position; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MountMovement implements Packet { + public static final int PACKET_ID = 166; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 59; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 59; + public static final int MAX_SIZE = 59; + @Nullable + public Position absolutePosition; + @Nullable + public Direction bodyOrientation; + @Nullable + public MovementStates movementStates; + + @Override + public int getId() { + return 166; + } + + public MountMovement() { + } + + public MountMovement(@Nullable Position absolutePosition, @Nullable Direction bodyOrientation, @Nullable MovementStates movementStates) { + this.absolutePosition = absolutePosition; + this.bodyOrientation = bodyOrientation; + this.movementStates = movementStates; + } + + public MountMovement(@Nonnull MountMovement other) { + this.absolutePosition = other.absolutePosition; + this.bodyOrientation = other.bodyOrientation; + this.movementStates = other.movementStates; + } + + @Nonnull + public static MountMovement deserialize(@Nonnull ByteBuf buf, int offset) { + MountMovement obj = new MountMovement(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.absolutePosition = Position.deserialize(buf, offset + 1); + } + + if ((nullBits & 2) != 0) { + obj.bodyOrientation = Direction.deserialize(buf, offset + 25); + } + + if ((nullBits & 4) != 0) { + obj.movementStates = MovementStates.deserialize(buf, offset + 37); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 59; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.absolutePosition != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.bodyOrientation != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.movementStates != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + if (this.absolutePosition != null) { + this.absolutePosition.serialize(buf); + } else { + buf.writeZero(24); + } + + if (this.bodyOrientation != null) { + this.bodyOrientation.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.movementStates != null) { + this.movementStates.serialize(buf); + } else { + buf.writeZero(22); + } + } + + @Override + public int computeSize() { + return 59; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 59 ? ValidationResult.error("Buffer too small: expected at least 59 bytes") : ValidationResult.OK; + } + + public MountMovement clone() { + MountMovement copy = new MountMovement(); + copy.absolutePosition = this.absolutePosition != null ? this.absolutePosition.clone() : null; + copy.bodyOrientation = this.bodyOrientation != null ? this.bodyOrientation.clone() : null; + copy.movementStates = this.movementStates != null ? this.movementStates.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof MountMovement other) + ? false + : Objects.equals(this.absolutePosition, other.absolutePosition) + && Objects.equals(this.bodyOrientation, other.bodyOrientation) + && Objects.equals(this.movementStates, other.movementStates); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.absolutePosition, this.bodyOrientation, this.movementStates); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/entities/PlayAnimation.java b/src/com/hypixel/hytale/protocol/packets/entities/PlayAnimation.java new file mode 100644 index 0000000..48cbe7a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/entities/PlayAnimation.java @@ -0,0 +1,254 @@ +package com.hypixel.hytale.protocol.packets.entities; + +import com.hypixel.hytale.protocol.AnimationSlot; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayAnimation implements Packet { + public static final int PACKET_ID = 162; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 14; + public static final int MAX_SIZE = 32768024; + public int entityId; + @Nullable + public String itemAnimationsId; + @Nullable + public String animationId; + @Nonnull + public AnimationSlot slot = AnimationSlot.Movement; + + @Override + public int getId() { + return 162; + } + + public PlayAnimation() { + } + + public PlayAnimation(int entityId, @Nullable String itemAnimationsId, @Nullable String animationId, @Nonnull AnimationSlot slot) { + this.entityId = entityId; + this.itemAnimationsId = itemAnimationsId; + this.animationId = animationId; + this.slot = slot; + } + + public PlayAnimation(@Nonnull PlayAnimation other) { + this.entityId = other.entityId; + this.itemAnimationsId = other.itemAnimationsId; + this.animationId = other.animationId; + this.slot = other.slot; + } + + @Nonnull + public static PlayAnimation deserialize(@Nonnull ByteBuf buf, int offset) { + PlayAnimation obj = new PlayAnimation(); + byte nullBits = buf.getByte(offset); + obj.entityId = buf.getIntLE(offset + 1); + obj.slot = AnimationSlot.fromValue(buf.getByte(offset + 5)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 14 + buf.getIntLE(offset + 6); + int itemAnimationsIdLen = VarInt.peek(buf, varPos0); + if (itemAnimationsIdLen < 0) { + throw ProtocolException.negativeLength("ItemAnimationsId", itemAnimationsIdLen); + } + + if (itemAnimationsIdLen > 4096000) { + throw ProtocolException.stringTooLong("ItemAnimationsId", itemAnimationsIdLen, 4096000); + } + + obj.itemAnimationsId = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 14 + buf.getIntLE(offset + 10); + int animationIdLen = VarInt.peek(buf, varPos1); + if (animationIdLen < 0) { + throw ProtocolException.negativeLength("AnimationId", animationIdLen); + } + + if (animationIdLen > 4096000) { + throw ProtocolException.stringTooLong("AnimationId", animationIdLen, 4096000); + } + + obj.animationId = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 14; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 6); + int pos0 = offset + 14 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 10); + int pos1 = offset + 14 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.itemAnimationsId != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.animationId != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.entityId); + buf.writeByte(this.slot.getValue()); + int itemAnimationsIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int animationIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.itemAnimationsId != null) { + buf.setIntLE(itemAnimationsIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.itemAnimationsId, 4096000); + } else { + buf.setIntLE(itemAnimationsIdOffsetSlot, -1); + } + + if (this.animationId != null) { + buf.setIntLE(animationIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.animationId, 4096000); + } else { + buf.setIntLE(animationIdOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 14; + if (this.itemAnimationsId != null) { + size += PacketIO.stringSize(this.itemAnimationsId); + } + + if (this.animationId != null) { + size += PacketIO.stringSize(this.animationId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 14) { + return ValidationResult.error("Buffer too small: expected at least 14 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int itemAnimationsIdOffset = buffer.getIntLE(offset + 6); + if (itemAnimationsIdOffset < 0) { + return ValidationResult.error("Invalid offset for ItemAnimationsId"); + } + + int pos = offset + 14 + itemAnimationsIdOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ItemAnimationsId"); + } + + int itemAnimationsIdLen = VarInt.peek(buffer, pos); + if (itemAnimationsIdLen < 0) { + return ValidationResult.error("Invalid string length for ItemAnimationsId"); + } + + if (itemAnimationsIdLen > 4096000) { + return ValidationResult.error("ItemAnimationsId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += itemAnimationsIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ItemAnimationsId"); + } + } + + if ((nullBits & 2) != 0) { + int animationIdOffset = buffer.getIntLE(offset + 10); + if (animationIdOffset < 0) { + return ValidationResult.error("Invalid offset for AnimationId"); + } + + int posx = offset + 14 + animationIdOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for AnimationId"); + } + + int animationIdLen = VarInt.peek(buffer, posx); + if (animationIdLen < 0) { + return ValidationResult.error("Invalid string length for AnimationId"); + } + + if (animationIdLen > 4096000) { + return ValidationResult.error("AnimationId exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += animationIdLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading AnimationId"); + } + } + + return ValidationResult.OK; + } + } + + public PlayAnimation clone() { + PlayAnimation copy = new PlayAnimation(); + copy.entityId = this.entityId; + copy.itemAnimationsId = this.itemAnimationsId; + copy.animationId = this.animationId; + copy.slot = this.slot; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof PlayAnimation other) + ? false + : this.entityId == other.entityId + && Objects.equals(this.itemAnimationsId, other.itemAnimationsId) + && Objects.equals(this.animationId, other.animationId) + && Objects.equals(this.slot, other.slot); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.entityId, this.itemAnimationsId, this.animationId, this.slot); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/entities/SetEntitySeed.java b/src/com/hypixel/hytale/protocol/packets/entities/SetEntitySeed.java new file mode 100644 index 0000000..db75f19 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/entities/SetEntitySeed.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.entities; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SetEntitySeed implements Packet { + public static final int PACKET_ID = 160; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 4; + public int entitySeed; + + @Override + public int getId() { + return 160; + } + + public SetEntitySeed() { + } + + public SetEntitySeed(int entitySeed) { + this.entitySeed = entitySeed; + } + + public SetEntitySeed(@Nonnull SetEntitySeed other) { + this.entitySeed = other.entitySeed; + } + + @Nonnull + public static SetEntitySeed deserialize(@Nonnull ByteBuf buf, int offset) { + SetEntitySeed obj = new SetEntitySeed(); + obj.entitySeed = buf.getIntLE(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 4; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.entitySeed); + } + + @Override + public int computeSize() { + return 4; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 4 ? ValidationResult.error("Buffer too small: expected at least 4 bytes") : ValidationResult.OK; + } + + public SetEntitySeed clone() { + SetEntitySeed copy = new SetEntitySeed(); + copy.entitySeed = this.entitySeed; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof SetEntitySeed other ? this.entitySeed == other.entitySeed : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.entitySeed); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/entities/SpawnModelParticles.java b/src/com/hypixel/hytale/protocol/packets/entities/SpawnModelParticles.java new file mode 100644 index 0000000..5a3bb04 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/entities/SpawnModelParticles.java @@ -0,0 +1,185 @@ +package com.hypixel.hytale.protocol.packets.entities; + +import com.hypixel.hytale.protocol.ModelParticle; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SpawnModelParticles implements Packet { + public static final int PACKET_ID = 165; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 1677721600; + public int entityId; + @Nullable + public ModelParticle[] modelParticles; + + @Override + public int getId() { + return 165; + } + + public SpawnModelParticles() { + } + + public SpawnModelParticles(int entityId, @Nullable ModelParticle[] modelParticles) { + this.entityId = entityId; + this.modelParticles = modelParticles; + } + + public SpawnModelParticles(@Nonnull SpawnModelParticles other) { + this.entityId = other.entityId; + this.modelParticles = other.modelParticles; + } + + @Nonnull + public static SpawnModelParticles deserialize(@Nonnull ByteBuf buf, int offset) { + SpawnModelParticles obj = new SpawnModelParticles(); + byte nullBits = buf.getByte(offset); + obj.entityId = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int modelParticlesCount = VarInt.peek(buf, pos); + if (modelParticlesCount < 0) { + throw ProtocolException.negativeLength("ModelParticles", modelParticlesCount); + } + + if (modelParticlesCount > 4096000) { + throw ProtocolException.arrayTooLong("ModelParticles", modelParticlesCount, 4096000); + } + + int modelParticlesVarLen = VarInt.size(modelParticlesCount); + if (pos + modelParticlesVarLen + modelParticlesCount * 34L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("ModelParticles", pos + modelParticlesVarLen + modelParticlesCount * 34, buf.readableBytes()); + } + + pos += modelParticlesVarLen; + obj.modelParticles = new ModelParticle[modelParticlesCount]; + + for (int i = 0; i < modelParticlesCount; i++) { + obj.modelParticles[i] = ModelParticle.deserialize(buf, pos); + pos += ModelParticle.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += ModelParticle.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.modelParticles != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.entityId); + if (this.modelParticles != null) { + if (this.modelParticles.length > 4096000) { + throw ProtocolException.arrayTooLong("ModelParticles", this.modelParticles.length, 4096000); + } + + VarInt.write(buf, this.modelParticles.length); + + for (ModelParticle item : this.modelParticles) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 5; + if (this.modelParticles != null) { + int modelParticlesSize = 0; + + for (ModelParticle elem : this.modelParticles) { + modelParticlesSize += elem.computeSize(); + } + + size += VarInt.size(this.modelParticles.length) + modelParticlesSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int modelParticlesCount = VarInt.peek(buffer, pos); + if (modelParticlesCount < 0) { + return ValidationResult.error("Invalid array count for ModelParticles"); + } + + if (modelParticlesCount > 4096000) { + return ValidationResult.error("ModelParticles exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < modelParticlesCount; i++) { + ValidationResult structResult = ModelParticle.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ModelParticle in ModelParticles[" + i + "]: " + structResult.error()); + } + + pos += ModelParticle.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public SpawnModelParticles clone() { + SpawnModelParticles copy = new SpawnModelParticles(); + copy.entityId = this.entityId; + copy.modelParticles = this.modelParticles != null ? Arrays.stream(this.modelParticles).map(e -> e.clone()).toArray(ModelParticle[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SpawnModelParticles other) + ? false + : this.entityId == other.entityId && Arrays.equals((Object[])this.modelParticles, (Object[])other.modelParticles); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.entityId); + return 31 * result + Arrays.hashCode((Object[])this.modelParticles); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interaction/CancelInteractionChain.java b/src/com/hypixel/hytale/protocol/packets/interaction/CancelInteractionChain.java new file mode 100644 index 0000000..c5ab78e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interaction/CancelInteractionChain.java @@ -0,0 +1,128 @@ +package com.hypixel.hytale.protocol.packets.interaction; + +import com.hypixel.hytale.protocol.ForkedChainId; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CancelInteractionChain implements Packet { + public static final int PACKET_ID = 291; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 1038; + public int chainId; + @Nullable + public ForkedChainId forkedId; + + @Override + public int getId() { + return 291; + } + + public CancelInteractionChain() { + } + + public CancelInteractionChain(int chainId, @Nullable ForkedChainId forkedId) { + this.chainId = chainId; + this.forkedId = forkedId; + } + + public CancelInteractionChain(@Nonnull CancelInteractionChain other) { + this.chainId = other.chainId; + this.forkedId = other.forkedId; + } + + @Nonnull + public static CancelInteractionChain deserialize(@Nonnull ByteBuf buf, int offset) { + CancelInteractionChain obj = new CancelInteractionChain(); + byte nullBits = buf.getByte(offset); + obj.chainId = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + obj.forkedId = ForkedChainId.deserialize(buf, pos); + pos += ForkedChainId.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + pos += ForkedChainId.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.forkedId != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.chainId); + if (this.forkedId != null) { + this.forkedId.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 5; + if (this.forkedId != null) { + size += this.forkedId.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + ValidationResult forkedIdResult = ForkedChainId.validateStructure(buffer, pos); + if (!forkedIdResult.isValid()) { + return ValidationResult.error("Invalid ForkedId: " + forkedIdResult.error()); + } + + pos += ForkedChainId.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public CancelInteractionChain clone() { + CancelInteractionChain copy = new CancelInteractionChain(); + copy.chainId = this.chainId; + copy.forkedId = this.forkedId != null ? this.forkedId.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CancelInteractionChain other) ? false : this.chainId == other.chainId && Objects.equals(this.forkedId, other.forkedId); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.chainId, this.forkedId); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interaction/DismountNPC.java b/src/com/hypixel/hytale/protocol/packets/interaction/DismountNPC.java new file mode 100644 index 0000000..69a1775 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interaction/DismountNPC.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.protocol.packets.interaction; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class DismountNPC implements Packet { + public static final int PACKET_ID = 294; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public DismountNPC() { + } + + @Override + public int getId() { + return 294; + } + + @Nonnull + public static DismountNPC deserialize(@Nonnull ByteBuf buf, int offset) { + return new DismountNPC(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public DismountNPC clone() { + return new DismountNPC(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof DismountNPC other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interaction/MountNPC.java b/src/com/hypixel/hytale/protocol/packets/interaction/MountNPC.java new file mode 100644 index 0000000..004b4e2 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interaction/MountNPC.java @@ -0,0 +1,99 @@ +package com.hypixel.hytale.protocol.packets.interaction; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class MountNPC implements Packet { + public static final int PACKET_ID = 293; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 16; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 16; + public static final int MAX_SIZE = 16; + public float anchorX; + public float anchorY; + public float anchorZ; + public int entityId; + + @Override + public int getId() { + return 293; + } + + public MountNPC() { + } + + public MountNPC(float anchorX, float anchorY, float anchorZ, int entityId) { + this.anchorX = anchorX; + this.anchorY = anchorY; + this.anchorZ = anchorZ; + this.entityId = entityId; + } + + public MountNPC(@Nonnull MountNPC other) { + this.anchorX = other.anchorX; + this.anchorY = other.anchorY; + this.anchorZ = other.anchorZ; + this.entityId = other.entityId; + } + + @Nonnull + public static MountNPC deserialize(@Nonnull ByteBuf buf, int offset) { + MountNPC obj = new MountNPC(); + obj.anchorX = buf.getFloatLE(offset + 0); + obj.anchorY = buf.getFloatLE(offset + 4); + obj.anchorZ = buf.getFloatLE(offset + 8); + obj.entityId = buf.getIntLE(offset + 12); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 16; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.anchorX); + buf.writeFloatLE(this.anchorY); + buf.writeFloatLE(this.anchorZ); + buf.writeIntLE(this.entityId); + } + + @Override + public int computeSize() { + return 16; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 16 ? ValidationResult.error("Buffer too small: expected at least 16 bytes") : ValidationResult.OK; + } + + public MountNPC clone() { + MountNPC copy = new MountNPC(); + copy.anchorX = this.anchorX; + copy.anchorY = this.anchorY; + copy.anchorZ = this.anchorZ; + copy.entityId = this.entityId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof MountNPC other) + ? false + : this.anchorX == other.anchorX && this.anchorY == other.anchorY && this.anchorZ == other.anchorZ && this.entityId == other.entityId; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.anchorX, this.anchorY, this.anchorZ, this.entityId); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interaction/PlayInteractionFor.java b/src/com/hypixel/hytale/protocol/packets/interaction/PlayInteractionFor.java new file mode 100644 index 0000000..d2b5676 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interaction/PlayInteractionFor.java @@ -0,0 +1,276 @@ +package com.hypixel.hytale.protocol.packets.interaction; + +import com.hypixel.hytale.protocol.ForkedChainId; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayInteractionFor implements Packet { + public static final int PACKET_ID = 292; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 19; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 27; + public static final int MAX_SIZE = 16385065; + public int entityId; + public int chainId; + @Nullable + public ForkedChainId forkedId; + public int operationIndex; + public int interactionId; + @Nullable + public String interactedItemId; + @Nonnull + public InteractionType interactionType = InteractionType.Primary; + public boolean cancel; + + @Override + public int getId() { + return 292; + } + + public PlayInteractionFor() { + } + + public PlayInteractionFor( + int entityId, + int chainId, + @Nullable ForkedChainId forkedId, + int operationIndex, + int interactionId, + @Nullable String interactedItemId, + @Nonnull InteractionType interactionType, + boolean cancel + ) { + this.entityId = entityId; + this.chainId = chainId; + this.forkedId = forkedId; + this.operationIndex = operationIndex; + this.interactionId = interactionId; + this.interactedItemId = interactedItemId; + this.interactionType = interactionType; + this.cancel = cancel; + } + + public PlayInteractionFor(@Nonnull PlayInteractionFor other) { + this.entityId = other.entityId; + this.chainId = other.chainId; + this.forkedId = other.forkedId; + this.operationIndex = other.operationIndex; + this.interactionId = other.interactionId; + this.interactedItemId = other.interactedItemId; + this.interactionType = other.interactionType; + this.cancel = other.cancel; + } + + @Nonnull + public static PlayInteractionFor deserialize(@Nonnull ByteBuf buf, int offset) { + PlayInteractionFor obj = new PlayInteractionFor(); + byte nullBits = buf.getByte(offset); + obj.entityId = buf.getIntLE(offset + 1); + obj.chainId = buf.getIntLE(offset + 5); + obj.operationIndex = buf.getIntLE(offset + 9); + obj.interactionId = buf.getIntLE(offset + 13); + obj.interactionType = InteractionType.fromValue(buf.getByte(offset + 17)); + obj.cancel = buf.getByte(offset + 18) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 27 + buf.getIntLE(offset + 19); + obj.forkedId = ForkedChainId.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 27 + buf.getIntLE(offset + 23); + int interactedItemIdLen = VarInt.peek(buf, varPos1); + if (interactedItemIdLen < 0) { + throw ProtocolException.negativeLength("InteractedItemId", interactedItemIdLen); + } + + if (interactedItemIdLen > 4096000) { + throw ProtocolException.stringTooLong("InteractedItemId", interactedItemIdLen, 4096000); + } + + obj.interactedItemId = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 27; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 19); + int pos0 = offset + 27 + fieldOffset0; + pos0 += ForkedChainId.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 23); + int pos1 = offset + 27 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.forkedId != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.interactedItemId != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.entityId); + buf.writeIntLE(this.chainId); + buf.writeIntLE(this.operationIndex); + buf.writeIntLE(this.interactionId); + buf.writeByte(this.interactionType.getValue()); + buf.writeByte(this.cancel ? 1 : 0); + int forkedIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int interactedItemIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.forkedId != null) { + buf.setIntLE(forkedIdOffsetSlot, buf.writerIndex() - varBlockStart); + this.forkedId.serialize(buf); + } else { + buf.setIntLE(forkedIdOffsetSlot, -1); + } + + if (this.interactedItemId != null) { + buf.setIntLE(interactedItemIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.interactedItemId, 4096000); + } else { + buf.setIntLE(interactedItemIdOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 27; + if (this.forkedId != null) { + size += this.forkedId.computeSize(); + } + + if (this.interactedItemId != null) { + size += PacketIO.stringSize(this.interactedItemId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 27) { + return ValidationResult.error("Buffer too small: expected at least 27 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int forkedIdOffset = buffer.getIntLE(offset + 19); + if (forkedIdOffset < 0) { + return ValidationResult.error("Invalid offset for ForkedId"); + } + + int pos = offset + 27 + forkedIdOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ForkedId"); + } + + ValidationResult forkedIdResult = ForkedChainId.validateStructure(buffer, pos); + if (!forkedIdResult.isValid()) { + return ValidationResult.error("Invalid ForkedId: " + forkedIdResult.error()); + } + + pos += ForkedChainId.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int interactedItemIdOffset = buffer.getIntLE(offset + 23); + if (interactedItemIdOffset < 0) { + return ValidationResult.error("Invalid offset for InteractedItemId"); + } + + int posx = offset + 27 + interactedItemIdOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for InteractedItemId"); + } + + int interactedItemIdLen = VarInt.peek(buffer, posx); + if (interactedItemIdLen < 0) { + return ValidationResult.error("Invalid string length for InteractedItemId"); + } + + if (interactedItemIdLen > 4096000) { + return ValidationResult.error("InteractedItemId exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += interactedItemIdLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading InteractedItemId"); + } + } + + return ValidationResult.OK; + } + } + + public PlayInteractionFor clone() { + PlayInteractionFor copy = new PlayInteractionFor(); + copy.entityId = this.entityId; + copy.chainId = this.chainId; + copy.forkedId = this.forkedId != null ? this.forkedId.clone() : null; + copy.operationIndex = this.operationIndex; + copy.interactionId = this.interactionId; + copy.interactedItemId = this.interactedItemId; + copy.interactionType = this.interactionType; + copy.cancel = this.cancel; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof PlayInteractionFor other) + ? false + : this.entityId == other.entityId + && this.chainId == other.chainId + && Objects.equals(this.forkedId, other.forkedId) + && this.operationIndex == other.operationIndex + && this.interactionId == other.interactionId + && Objects.equals(this.interactedItemId, other.interactedItemId) + && Objects.equals(this.interactionType, other.interactionType) + && this.cancel == other.cancel; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.entityId, this.chainId, this.forkedId, this.operationIndex, this.interactionId, this.interactedItemId, this.interactionType, this.cancel + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interaction/SyncInteractionChain.java b/src/com/hypixel/hytale/protocol/packets/interaction/SyncInteractionChain.java new file mode 100644 index 0000000..bd98345 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interaction/SyncInteractionChain.java @@ -0,0 +1,775 @@ +package com.hypixel.hytale.protocol.packets.interaction; + +import com.hypixel.hytale.protocol.ForkedChainId; +import com.hypixel.hytale.protocol.InteractionChainData; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SyncInteractionChain { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 33; + public static final int VARIABLE_FIELD_COUNT = 7; + public static final int VARIABLE_BLOCK_START = 61; + public static final int MAX_SIZE = 1677721600; + public int activeHotbarSlot; + public int activeUtilitySlot; + public int activeToolsSlot; + @Nullable + public String itemInHandId; + @Nullable + public String utilityItemId; + @Nullable + public String toolsItemId; + public boolean initial; + public boolean desync; + public int overrideRootInteraction = Integer.MIN_VALUE; + @Nonnull + public InteractionType interactionType = InteractionType.Primary; + public int equipSlot; + public int chainId; + @Nullable + public ForkedChainId forkedId; + @Nullable + public InteractionChainData data; + @Nonnull + public InteractionState state = InteractionState.Finished; + @Nullable + public SyncInteractionChain[] newForks; + public int operationBaseIndex; + @Nullable + public InteractionSyncData[] interactionData; + + public SyncInteractionChain() { + } + + public SyncInteractionChain( + int activeHotbarSlot, + int activeUtilitySlot, + int activeToolsSlot, + @Nullable String itemInHandId, + @Nullable String utilityItemId, + @Nullable String toolsItemId, + boolean initial, + boolean desync, + int overrideRootInteraction, + @Nonnull InteractionType interactionType, + int equipSlot, + int chainId, + @Nullable ForkedChainId forkedId, + @Nullable InteractionChainData data, + @Nonnull InteractionState state, + @Nullable SyncInteractionChain[] newForks, + int operationBaseIndex, + @Nullable InteractionSyncData[] interactionData + ) { + this.activeHotbarSlot = activeHotbarSlot; + this.activeUtilitySlot = activeUtilitySlot; + this.activeToolsSlot = activeToolsSlot; + this.itemInHandId = itemInHandId; + this.utilityItemId = utilityItemId; + this.toolsItemId = toolsItemId; + this.initial = initial; + this.desync = desync; + this.overrideRootInteraction = overrideRootInteraction; + this.interactionType = interactionType; + this.equipSlot = equipSlot; + this.chainId = chainId; + this.forkedId = forkedId; + this.data = data; + this.state = state; + this.newForks = newForks; + this.operationBaseIndex = operationBaseIndex; + this.interactionData = interactionData; + } + + public SyncInteractionChain(@Nonnull SyncInteractionChain other) { + this.activeHotbarSlot = other.activeHotbarSlot; + this.activeUtilitySlot = other.activeUtilitySlot; + this.activeToolsSlot = other.activeToolsSlot; + this.itemInHandId = other.itemInHandId; + this.utilityItemId = other.utilityItemId; + this.toolsItemId = other.toolsItemId; + this.initial = other.initial; + this.desync = other.desync; + this.overrideRootInteraction = other.overrideRootInteraction; + this.interactionType = other.interactionType; + this.equipSlot = other.equipSlot; + this.chainId = other.chainId; + this.forkedId = other.forkedId; + this.data = other.data; + this.state = other.state; + this.newForks = other.newForks; + this.operationBaseIndex = other.operationBaseIndex; + this.interactionData = other.interactionData; + } + + @Nonnull + public static SyncInteractionChain deserialize(@Nonnull ByteBuf buf, int offset) { + SyncInteractionChain obj = new SyncInteractionChain(); + byte nullBits = buf.getByte(offset); + obj.activeHotbarSlot = buf.getIntLE(offset + 1); + obj.activeUtilitySlot = buf.getIntLE(offset + 5); + obj.activeToolsSlot = buf.getIntLE(offset + 9); + obj.initial = buf.getByte(offset + 13) != 0; + obj.desync = buf.getByte(offset + 14) != 0; + obj.overrideRootInteraction = buf.getIntLE(offset + 15); + obj.interactionType = InteractionType.fromValue(buf.getByte(offset + 19)); + obj.equipSlot = buf.getIntLE(offset + 20); + obj.chainId = buf.getIntLE(offset + 24); + obj.state = InteractionState.fromValue(buf.getByte(offset + 28)); + obj.operationBaseIndex = buf.getIntLE(offset + 29); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 61 + buf.getIntLE(offset + 33); + int itemInHandIdLen = VarInt.peek(buf, varPos0); + if (itemInHandIdLen < 0) { + throw ProtocolException.negativeLength("ItemInHandId", itemInHandIdLen); + } + + if (itemInHandIdLen > 4096000) { + throw ProtocolException.stringTooLong("ItemInHandId", itemInHandIdLen, 4096000); + } + + obj.itemInHandId = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 61 + buf.getIntLE(offset + 37); + int utilityItemIdLen = VarInt.peek(buf, varPos1); + if (utilityItemIdLen < 0) { + throw ProtocolException.negativeLength("UtilityItemId", utilityItemIdLen); + } + + if (utilityItemIdLen > 4096000) { + throw ProtocolException.stringTooLong("UtilityItemId", utilityItemIdLen, 4096000); + } + + obj.utilityItemId = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 61 + buf.getIntLE(offset + 41); + int toolsItemIdLen = VarInt.peek(buf, varPos2); + if (toolsItemIdLen < 0) { + throw ProtocolException.negativeLength("ToolsItemId", toolsItemIdLen); + } + + if (toolsItemIdLen > 4096000) { + throw ProtocolException.stringTooLong("ToolsItemId", toolsItemIdLen, 4096000); + } + + obj.toolsItemId = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 61 + buf.getIntLE(offset + 45); + obj.forkedId = ForkedChainId.deserialize(buf, varPos3); + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 61 + buf.getIntLE(offset + 49); + obj.data = InteractionChainData.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 61 + buf.getIntLE(offset + 53); + int newForksCount = VarInt.peek(buf, varPos5); + if (newForksCount < 0) { + throw ProtocolException.negativeLength("NewForks", newForksCount); + } + + if (newForksCount > 4096000) { + throw ProtocolException.arrayTooLong("NewForks", newForksCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos5); + if (varPos5 + varIntLen + newForksCount * 33L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("NewForks", varPos5 + varIntLen + newForksCount * 33, buf.readableBytes()); + } + + obj.newForks = new SyncInteractionChain[newForksCount]; + int elemPos = varPos5 + varIntLen; + + for (int i = 0; i < newForksCount; i++) { + obj.newForks[i] = deserialize(buf, elemPos); + elemPos += computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 64) != 0) { + int varPos6 = offset + 61 + buf.getIntLE(offset + 57); + int interactionDataCount = VarInt.peek(buf, varPos6); + if (interactionDataCount < 0) { + throw ProtocolException.negativeLength("InteractionData", interactionDataCount); + } + + if (interactionDataCount > 4096000) { + throw ProtocolException.arrayTooLong("InteractionData", interactionDataCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos6); + int interactionDataBitfieldSize = (interactionDataCount + 7) / 8; + byte[] interactionDataBitfield = PacketIO.readBytes(buf, varPos6 + varIntLen, interactionDataBitfieldSize); + obj.interactionData = new InteractionSyncData[interactionDataCount]; + int elemPos = varPos6 + varIntLen + interactionDataBitfieldSize; + + for (int i = 0; i < interactionDataCount; i++) { + if ((interactionDataBitfield[i / 8] & 1 << i % 8) != 0) { + obj.interactionData[i] = InteractionSyncData.deserialize(buf, elemPos); + elemPos += InteractionSyncData.computeBytesConsumed(buf, elemPos); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 61; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 33); + int pos0 = offset + 61 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 37); + int pos1 = offset + 61 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 41); + int pos2 = offset + 61 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 45); + int pos3 = offset + 61 + fieldOffset3; + pos3 += ForkedChainId.computeBytesConsumed(buf, pos3); + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 49); + int pos4 = offset + 61 + fieldOffset4; + pos4 += InteractionChainData.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 53); + int pos5 = offset + 61 + fieldOffset5; + int arrLen = VarInt.peek(buf, pos5); + pos5 += VarInt.length(buf, pos5); + + for (int i = 0; i < arrLen; i++) { + pos5 += computeBytesConsumed(buf, pos5); + } + + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits & 64) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 57); + int pos6 = offset + 61 + fieldOffset6; + int arrLen = VarInt.peek(buf, pos6); + pos6 += VarInt.length(buf, pos6); + int bitfieldSize = (arrLen + 7) / 8; + byte[] bitfield = PacketIO.readBytes(buf, pos6, bitfieldSize); + pos6 += bitfieldSize; + + for (int i = 0; i < arrLen; i++) { + if ((bitfield[i / 8] & 1 << i % 8) != 0) { + pos6 += InteractionSyncData.computeBytesConsumed(buf, pos6); + } + } + + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.itemInHandId != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.utilityItemId != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.toolsItemId != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.forkedId != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.data != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.newForks != null) { + nullBits = (byte)(nullBits | 32); + } + + if (this.interactionData != null) { + nullBits = (byte)(nullBits | 64); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.activeHotbarSlot); + buf.writeIntLE(this.activeUtilitySlot); + buf.writeIntLE(this.activeToolsSlot); + buf.writeByte(this.initial ? 1 : 0); + buf.writeByte(this.desync ? 1 : 0); + buf.writeIntLE(this.overrideRootInteraction); + buf.writeByte(this.interactionType.getValue()); + buf.writeIntLE(this.equipSlot); + buf.writeIntLE(this.chainId); + buf.writeByte(this.state.getValue()); + buf.writeIntLE(this.operationBaseIndex); + int itemInHandIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int utilityItemIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int toolsItemIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int forkedIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int dataOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int newForksOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int interactionDataOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.itemInHandId != null) { + buf.setIntLE(itemInHandIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.itemInHandId, 4096000); + } else { + buf.setIntLE(itemInHandIdOffsetSlot, -1); + } + + if (this.utilityItemId != null) { + buf.setIntLE(utilityItemIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.utilityItemId, 4096000); + } else { + buf.setIntLE(utilityItemIdOffsetSlot, -1); + } + + if (this.toolsItemId != null) { + buf.setIntLE(toolsItemIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.toolsItemId, 4096000); + } else { + buf.setIntLE(toolsItemIdOffsetSlot, -1); + } + + if (this.forkedId != null) { + buf.setIntLE(forkedIdOffsetSlot, buf.writerIndex() - varBlockStart); + this.forkedId.serialize(buf); + } else { + buf.setIntLE(forkedIdOffsetSlot, -1); + } + + if (this.data != null) { + buf.setIntLE(dataOffsetSlot, buf.writerIndex() - varBlockStart); + this.data.serialize(buf); + } else { + buf.setIntLE(dataOffsetSlot, -1); + } + + if (this.newForks != null) { + buf.setIntLE(newForksOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.newForks.length > 4096000) { + throw ProtocolException.arrayTooLong("NewForks", this.newForks.length, 4096000); + } + + VarInt.write(buf, this.newForks.length); + + for (SyncInteractionChain item : this.newForks) { + item.serialize(buf); + } + } else { + buf.setIntLE(newForksOffsetSlot, -1); + } + + if (this.interactionData != null) { + buf.setIntLE(interactionDataOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.interactionData.length > 4096000) { + throw ProtocolException.arrayTooLong("InteractionData", this.interactionData.length, 4096000); + } + + VarInt.write(buf, this.interactionData.length); + int interactionDataBitfieldSize = (this.interactionData.length + 7) / 8; + byte[] interactionDataBitfield = new byte[interactionDataBitfieldSize]; + + for (int i = 0; i < this.interactionData.length; i++) { + if (this.interactionData[i] != null) { + interactionDataBitfield[i / 8] = (byte)(interactionDataBitfield[i / 8] | (byte)(1 << i % 8)); + } + } + + buf.writeBytes(interactionDataBitfield); + + for (int ix = 0; ix < this.interactionData.length; ix++) { + if (this.interactionData[ix] != null) { + this.interactionData[ix].serialize(buf); + } + } + } else { + buf.setIntLE(interactionDataOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 61; + if (this.itemInHandId != null) { + size += PacketIO.stringSize(this.itemInHandId); + } + + if (this.utilityItemId != null) { + size += PacketIO.stringSize(this.utilityItemId); + } + + if (this.toolsItemId != null) { + size += PacketIO.stringSize(this.toolsItemId); + } + + if (this.forkedId != null) { + size += this.forkedId.computeSize(); + } + + if (this.data != null) { + size += this.data.computeSize(); + } + + if (this.newForks != null) { + int newForksSize = 0; + + for (SyncInteractionChain elem : this.newForks) { + newForksSize += elem.computeSize(); + } + + size += VarInt.size(this.newForks.length) + newForksSize; + } + + if (this.interactionData != null) { + int interactionDataSize = 0; + + for (InteractionSyncData elem : this.interactionData) { + if (elem != null) { + interactionDataSize += elem.computeSize(); + } + } + + size += VarInt.size(this.interactionData.length) + (this.interactionData.length + 7) / 8 + interactionDataSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 61) { + return ValidationResult.error("Buffer too small: expected at least 61 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int itemInHandIdOffset = buffer.getIntLE(offset + 33); + if (itemInHandIdOffset < 0) { + return ValidationResult.error("Invalid offset for ItemInHandId"); + } + + int pos = offset + 61 + itemInHandIdOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ItemInHandId"); + } + + int itemInHandIdLen = VarInt.peek(buffer, pos); + if (itemInHandIdLen < 0) { + return ValidationResult.error("Invalid string length for ItemInHandId"); + } + + if (itemInHandIdLen > 4096000) { + return ValidationResult.error("ItemInHandId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += itemInHandIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ItemInHandId"); + } + } + + if ((nullBits & 2) != 0) { + int utilityItemIdOffset = buffer.getIntLE(offset + 37); + if (utilityItemIdOffset < 0) { + return ValidationResult.error("Invalid offset for UtilityItemId"); + } + + int posx = offset + 61 + utilityItemIdOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for UtilityItemId"); + } + + int utilityItemIdLen = VarInt.peek(buffer, posx); + if (utilityItemIdLen < 0) { + return ValidationResult.error("Invalid string length for UtilityItemId"); + } + + if (utilityItemIdLen > 4096000) { + return ValidationResult.error("UtilityItemId exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += utilityItemIdLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading UtilityItemId"); + } + } + + if ((nullBits & 4) != 0) { + int toolsItemIdOffset = buffer.getIntLE(offset + 41); + if (toolsItemIdOffset < 0) { + return ValidationResult.error("Invalid offset for ToolsItemId"); + } + + int posxx = offset + 61 + toolsItemIdOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ToolsItemId"); + } + + int toolsItemIdLen = VarInt.peek(buffer, posxx); + if (toolsItemIdLen < 0) { + return ValidationResult.error("Invalid string length for ToolsItemId"); + } + + if (toolsItemIdLen > 4096000) { + return ValidationResult.error("ToolsItemId exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += toolsItemIdLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ToolsItemId"); + } + } + + if ((nullBits & 8) != 0) { + int forkedIdOffset = buffer.getIntLE(offset + 45); + if (forkedIdOffset < 0) { + return ValidationResult.error("Invalid offset for ForkedId"); + } + + int posxxx = offset + 61 + forkedIdOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ForkedId"); + } + + ValidationResult forkedIdResult = ForkedChainId.validateStructure(buffer, posxxx); + if (!forkedIdResult.isValid()) { + return ValidationResult.error("Invalid ForkedId: " + forkedIdResult.error()); + } + + posxxx += ForkedChainId.computeBytesConsumed(buffer, posxxx); + } + + if ((nullBits & 16) != 0) { + int dataOffset = buffer.getIntLE(offset + 49); + if (dataOffset < 0) { + return ValidationResult.error("Invalid offset for Data"); + } + + int posxxxx = offset + 61 + dataOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Data"); + } + + ValidationResult dataResult = InteractionChainData.validateStructure(buffer, posxxxx); + if (!dataResult.isValid()) { + return ValidationResult.error("Invalid Data: " + dataResult.error()); + } + + posxxxx += InteractionChainData.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int newForksOffset = buffer.getIntLE(offset + 53); + if (newForksOffset < 0) { + return ValidationResult.error("Invalid offset for NewForks"); + } + + int posxxxxx = offset + 61 + newForksOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for NewForks"); + } + + int newForksCount = VarInt.peek(buffer, posxxxxx); + if (newForksCount < 0) { + return ValidationResult.error("Invalid array count for NewForks"); + } + + if (newForksCount > 4096000) { + return ValidationResult.error("NewForks exceeds max length 4096000"); + } + + posxxxxx += VarInt.length(buffer, posxxxxx); + + for (int i = 0; i < newForksCount; i++) { + ValidationResult structResult = validateStructure(buffer, posxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid SyncInteractionChain in NewForks[" + i + "]: " + structResult.error()); + } + + posxxxxx += computeBytesConsumed(buffer, posxxxxx); + } + } + + if ((nullBits & 64) != 0) { + int interactionDataOffset = buffer.getIntLE(offset + 57); + if (interactionDataOffset < 0) { + return ValidationResult.error("Invalid offset for InteractionData"); + } + + int posxxxxxx = offset + 61 + interactionDataOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for InteractionData"); + } + + int interactionDataCount = VarInt.peek(buffer, posxxxxxx); + if (interactionDataCount < 0) { + return ValidationResult.error("Invalid array count for InteractionData"); + } + + if (interactionDataCount > 4096000) { + return ValidationResult.error("InteractionData exceeds max length 4096000"); + } + + posxxxxxx += VarInt.length(buffer, posxxxxxx); + + for (int i = 0; i < interactionDataCount; i++) { + ValidationResult structResult = InteractionSyncData.validateStructure(buffer, posxxxxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid InteractionSyncData in InteractionData[" + i + "]: " + structResult.error()); + } + + posxxxxxx += InteractionSyncData.computeBytesConsumed(buffer, posxxxxxx); + } + } + + return ValidationResult.OK; + } + } + + public SyncInteractionChain clone() { + SyncInteractionChain copy = new SyncInteractionChain(); + copy.activeHotbarSlot = this.activeHotbarSlot; + copy.activeUtilitySlot = this.activeUtilitySlot; + copy.activeToolsSlot = this.activeToolsSlot; + copy.itemInHandId = this.itemInHandId; + copy.utilityItemId = this.utilityItemId; + copy.toolsItemId = this.toolsItemId; + copy.initial = this.initial; + copy.desync = this.desync; + copy.overrideRootInteraction = this.overrideRootInteraction; + copy.interactionType = this.interactionType; + copy.equipSlot = this.equipSlot; + copy.chainId = this.chainId; + copy.forkedId = this.forkedId != null ? this.forkedId.clone() : null; + copy.data = this.data != null ? this.data.clone() : null; + copy.state = this.state; + copy.newForks = this.newForks != null ? Arrays.stream(this.newForks).map(e -> e.clone()).toArray(SyncInteractionChain[]::new) : null; + copy.operationBaseIndex = this.operationBaseIndex; + copy.interactionData = this.interactionData != null + ? Arrays.stream(this.interactionData).map(e -> e != null ? e.clone() : null).toArray(InteractionSyncData[]::new) + : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SyncInteractionChain other) + ? false + : this.activeHotbarSlot == other.activeHotbarSlot + && this.activeUtilitySlot == other.activeUtilitySlot + && this.activeToolsSlot == other.activeToolsSlot + && Objects.equals(this.itemInHandId, other.itemInHandId) + && Objects.equals(this.utilityItemId, other.utilityItemId) + && Objects.equals(this.toolsItemId, other.toolsItemId) + && this.initial == other.initial + && this.desync == other.desync + && this.overrideRootInteraction == other.overrideRootInteraction + && Objects.equals(this.interactionType, other.interactionType) + && this.equipSlot == other.equipSlot + && this.chainId == other.chainId + && Objects.equals(this.forkedId, other.forkedId) + && Objects.equals(this.data, other.data) + && Objects.equals(this.state, other.state) + && Arrays.equals((Object[])this.newForks, (Object[])other.newForks) + && this.operationBaseIndex == other.operationBaseIndex + && Arrays.equals((Object[])this.interactionData, (Object[])other.interactionData); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.activeHotbarSlot); + result = 31 * result + Integer.hashCode(this.activeUtilitySlot); + result = 31 * result + Integer.hashCode(this.activeToolsSlot); + result = 31 * result + Objects.hashCode(this.itemInHandId); + result = 31 * result + Objects.hashCode(this.utilityItemId); + result = 31 * result + Objects.hashCode(this.toolsItemId); + result = 31 * result + Boolean.hashCode(this.initial); + result = 31 * result + Boolean.hashCode(this.desync); + result = 31 * result + Integer.hashCode(this.overrideRootInteraction); + result = 31 * result + Objects.hashCode(this.interactionType); + result = 31 * result + Integer.hashCode(this.equipSlot); + result = 31 * result + Integer.hashCode(this.chainId); + result = 31 * result + Objects.hashCode(this.forkedId); + result = 31 * result + Objects.hashCode(this.data); + result = 31 * result + Objects.hashCode(this.state); + result = 31 * result + Arrays.hashCode((Object[])this.newForks); + result = 31 * result + Integer.hashCode(this.operationBaseIndex); + return 31 * result + Arrays.hashCode((Object[])this.interactionData); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interaction/SyncInteractionChains.java b/src/com/hypixel/hytale/protocol/packets/interaction/SyncInteractionChains.java new file mode 100644 index 0000000..67b632f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interaction/SyncInteractionChains.java @@ -0,0 +1,149 @@ +package com.hypixel.hytale.protocol.packets.interaction; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class SyncInteractionChains implements Packet { + public static final int PACKET_ID = 290; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public SyncInteractionChain[] updates = new SyncInteractionChain[0]; + + @Override + public int getId() { + return 290; + } + + public SyncInteractionChains() { + } + + public SyncInteractionChains(@Nonnull SyncInteractionChain[] updates) { + this.updates = updates; + } + + public SyncInteractionChains(@Nonnull SyncInteractionChains other) { + this.updates = other.updates; + } + + @Nonnull + public static SyncInteractionChains deserialize(@Nonnull ByteBuf buf, int offset) { + SyncInteractionChains obj = new SyncInteractionChains(); + int pos = offset + 0; + int updatesCount = VarInt.peek(buf, pos); + if (updatesCount < 0) { + throw ProtocolException.negativeLength("Updates", updatesCount); + } else if (updatesCount > 4096000) { + throw ProtocolException.arrayTooLong("Updates", updatesCount, 4096000); + } else { + int updatesVarLen = VarInt.size(updatesCount); + if (pos + updatesVarLen + updatesCount * 33L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Updates", pos + updatesVarLen + updatesCount * 33, buf.readableBytes()); + } else { + pos += updatesVarLen; + obj.updates = new SyncInteractionChain[updatesCount]; + + for (int i = 0; i < updatesCount; i++) { + obj.updates[i] = SyncInteractionChain.deserialize(buf, pos); + pos += SyncInteractionChain.computeBytesConsumed(buf, pos); + } + + return obj; + } + } + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + int pos = offset + 0; + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += SyncInteractionChain.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + if (this.updates.length > 4096000) { + throw ProtocolException.arrayTooLong("Updates", this.updates.length, 4096000); + } else { + VarInt.write(buf, this.updates.length); + + for (SyncInteractionChain item : this.updates) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 0; + int updatesSize = 0; + + for (SyncInteractionChain elem : this.updates) { + updatesSize += elem.computeSize(); + } + + return size + VarInt.size(this.updates.length) + updatesSize; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 0) { + return ValidationResult.error("Buffer too small: expected at least 0 bytes"); + } else { + int pos = offset + 0; + int updatesCount = VarInt.peek(buffer, pos); + if (updatesCount < 0) { + return ValidationResult.error("Invalid array count for Updates"); + } else if (updatesCount > 4096000) { + return ValidationResult.error("Updates exceeds max length 4096000"); + } else { + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < updatesCount; i++) { + ValidationResult structResult = SyncInteractionChain.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid SyncInteractionChain in Updates[" + i + "]: " + structResult.error()); + } + + pos += SyncInteractionChain.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + } + + public SyncInteractionChains clone() { + SyncInteractionChains copy = new SyncInteractionChains(); + copy.updates = Arrays.stream(this.updates).map(e -> e.clone()).toArray(SyncInteractionChain[]::new); + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof SyncInteractionChains other ? Arrays.equals((Object[])this.updates, (Object[])other.updates) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.updates); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/AddToServerPlayerList.java b/src/com/hypixel/hytale/protocol/packets/interface_/AddToServerPlayerList.java new file mode 100644 index 0000000..c4fae67 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/AddToServerPlayerList.java @@ -0,0 +1,175 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AddToServerPlayerList implements Packet { + public static final int PACKET_ID = 224; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public ServerPlayerListPlayer[] players; + + @Override + public int getId() { + return 224; + } + + public AddToServerPlayerList() { + } + + public AddToServerPlayerList(@Nullable ServerPlayerListPlayer[] players) { + this.players = players; + } + + public AddToServerPlayerList(@Nonnull AddToServerPlayerList other) { + this.players = other.players; + } + + @Nonnull + public static AddToServerPlayerList deserialize(@Nonnull ByteBuf buf, int offset) { + AddToServerPlayerList obj = new AddToServerPlayerList(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int playersCount = VarInt.peek(buf, pos); + if (playersCount < 0) { + throw ProtocolException.negativeLength("Players", playersCount); + } + + if (playersCount > 4096000) { + throw ProtocolException.arrayTooLong("Players", playersCount, 4096000); + } + + int playersVarLen = VarInt.size(playersCount); + if (pos + playersVarLen + playersCount * 37L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Players", pos + playersVarLen + playersCount * 37, buf.readableBytes()); + } + + pos += playersVarLen; + obj.players = new ServerPlayerListPlayer[playersCount]; + + for (int i = 0; i < playersCount; i++) { + obj.players[i] = ServerPlayerListPlayer.deserialize(buf, pos); + pos += ServerPlayerListPlayer.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += ServerPlayerListPlayer.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.players != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.players != null) { + if (this.players.length > 4096000) { + throw ProtocolException.arrayTooLong("Players", this.players.length, 4096000); + } + + VarInt.write(buf, this.players.length); + + for (ServerPlayerListPlayer item : this.players) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.players != null) { + int playersSize = 0; + + for (ServerPlayerListPlayer elem : this.players) { + playersSize += elem.computeSize(); + } + + size += VarInt.size(this.players.length) + playersSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int playersCount = VarInt.peek(buffer, pos); + if (playersCount < 0) { + return ValidationResult.error("Invalid array count for Players"); + } + + if (playersCount > 4096000) { + return ValidationResult.error("Players exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < playersCount; i++) { + ValidationResult structResult = ServerPlayerListPlayer.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ServerPlayerListPlayer in Players[" + i + "]: " + structResult.error()); + } + + pos += ServerPlayerListPlayer.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public AddToServerPlayerList clone() { + AddToServerPlayerList copy = new AddToServerPlayerList(); + copy.players = this.players != null ? Arrays.stream(this.players).map(e -> e.clone()).toArray(ServerPlayerListPlayer[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AddToServerPlayerList other ? Arrays.equals((Object[])this.players, (Object[])other.players) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.players); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/BlockChange.java b/src/com/hypixel/hytale/protocol/packets/interface_/BlockChange.java new file mode 100644 index 0000000..6cc8f83 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/BlockChange.java @@ -0,0 +1,95 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class BlockChange { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 17; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 17; + public static final int MAX_SIZE = 17; + public int x; + public int y; + public int z; + public int block; + public byte rotation; + + public BlockChange() { + } + + public BlockChange(int x, int y, int z, int block, byte rotation) { + this.x = x; + this.y = y; + this.z = z; + this.block = block; + this.rotation = rotation; + } + + public BlockChange(@Nonnull BlockChange other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.block = other.block; + this.rotation = other.rotation; + } + + @Nonnull + public static BlockChange deserialize(@Nonnull ByteBuf buf, int offset) { + BlockChange obj = new BlockChange(); + obj.x = buf.getIntLE(offset + 0); + obj.y = buf.getIntLE(offset + 4); + obj.z = buf.getIntLE(offset + 8); + obj.block = buf.getIntLE(offset + 12); + obj.rotation = buf.getByte(offset + 16); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 17; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.x); + buf.writeIntLE(this.y); + buf.writeIntLE(this.z); + buf.writeIntLE(this.block); + buf.writeByte(this.rotation); + } + + public int computeSize() { + return 17; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 17 ? ValidationResult.error("Buffer too small: expected at least 17 bytes") : ValidationResult.OK; + } + + public BlockChange clone() { + BlockChange copy = new BlockChange(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + copy.block = this.block; + copy.rotation = this.rotation; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BlockChange other) + ? false + : this.x == other.x && this.y == other.y && this.z == other.z && this.block == other.block && this.rotation == other.rotation; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y, this.z, this.block, this.rotation); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/ChatMessage.java b/src/com/hypixel/hytale/protocol/packets/interface_/ChatMessage.java new file mode 100644 index 0000000..909bf12 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/ChatMessage.java @@ -0,0 +1,143 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChatMessage implements Packet { + public static final int PACKET_ID = 211; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 16384006; + @Nullable + public String message; + + @Override + public int getId() { + return 211; + } + + public ChatMessage() { + } + + public ChatMessage(@Nullable String message) { + this.message = message; + } + + public ChatMessage(@Nonnull ChatMessage other) { + this.message = other.message; + } + + @Nonnull + public static ChatMessage deserialize(@Nonnull ByteBuf buf, int offset) { + ChatMessage obj = new ChatMessage(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int messageLen = VarInt.peek(buf, pos); + if (messageLen < 0) { + throw ProtocolException.negativeLength("Message", messageLen); + } + + if (messageLen > 4096000) { + throw ProtocolException.stringTooLong("Message", messageLen, 4096000); + } + + int messageVarLen = VarInt.length(buf, pos); + obj.message = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += messageVarLen + messageLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.message != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.message != null) { + PacketIO.writeVarString(buf, this.message, 4096000); + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.message != null) { + size += PacketIO.stringSize(this.message); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int messageLen = VarInt.peek(buffer, pos); + if (messageLen < 0) { + return ValidationResult.error("Invalid string length for Message"); + } + + if (messageLen > 4096000) { + return ValidationResult.error("Message exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += messageLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Message"); + } + } + + return ValidationResult.OK; + } + } + + public ChatMessage clone() { + ChatMessage copy = new ChatMessage(); + copy.message = this.message; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof ChatMessage other ? Objects.equals(this.message, other.message) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.message); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/ChatTagType.java b/src/com/hypixel/hytale/protocol/packets/interface_/ChatTagType.java new file mode 100644 index 0000000..0570855 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/ChatTagType.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ChatTagType { + Item(0); + + public static final ChatTagType[] VALUES = values(); + private final int value; + + private ChatTagType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ChatTagType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ChatTagType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/ChatType.java b/src/com/hypixel/hytale/protocol/packets/interface_/ChatType.java new file mode 100644 index 0000000..fd85ceb --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/ChatType.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ChatType { + Chat(0); + + public static final ChatType[] VALUES = values(); + private final int value; + + private ChatType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ChatType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ChatType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/CustomHud.java b/src/com/hypixel/hytale/protocol/packets/interface_/CustomHud.java new file mode 100644 index 0000000..dca66dd --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/CustomHud.java @@ -0,0 +1,182 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CustomHud implements Packet { + public static final int PACKET_ID = 217; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 1677721600; + public boolean clear; + @Nullable + public CustomUICommand[] commands; + + @Override + public int getId() { + return 217; + } + + public CustomHud() { + } + + public CustomHud(boolean clear, @Nullable CustomUICommand[] commands) { + this.clear = clear; + this.commands = commands; + } + + public CustomHud(@Nonnull CustomHud other) { + this.clear = other.clear; + this.commands = other.commands; + } + + @Nonnull + public static CustomHud deserialize(@Nonnull ByteBuf buf, int offset) { + CustomHud obj = new CustomHud(); + byte nullBits = buf.getByte(offset); + obj.clear = buf.getByte(offset + 1) != 0; + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int commandsCount = VarInt.peek(buf, pos); + if (commandsCount < 0) { + throw ProtocolException.negativeLength("Commands", commandsCount); + } + + if (commandsCount > 4096000) { + throw ProtocolException.arrayTooLong("Commands", commandsCount, 4096000); + } + + int commandsVarLen = VarInt.size(commandsCount); + if (pos + commandsVarLen + commandsCount * 2L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Commands", pos + commandsVarLen + commandsCount * 2, buf.readableBytes()); + } + + pos += commandsVarLen; + obj.commands = new CustomUICommand[commandsCount]; + + for (int i = 0; i < commandsCount; i++) { + obj.commands[i] = CustomUICommand.deserialize(buf, pos); + pos += CustomUICommand.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += CustomUICommand.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.commands != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.clear ? 1 : 0); + if (this.commands != null) { + if (this.commands.length > 4096000) { + throw ProtocolException.arrayTooLong("Commands", this.commands.length, 4096000); + } + + VarInt.write(buf, this.commands.length); + + for (CustomUICommand item : this.commands) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.commands != null) { + int commandsSize = 0; + + for (CustomUICommand elem : this.commands) { + commandsSize += elem.computeSize(); + } + + size += VarInt.size(this.commands.length) + commandsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int commandsCount = VarInt.peek(buffer, pos); + if (commandsCount < 0) { + return ValidationResult.error("Invalid array count for Commands"); + } + + if (commandsCount > 4096000) { + return ValidationResult.error("Commands exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < commandsCount; i++) { + ValidationResult structResult = CustomUICommand.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid CustomUICommand in Commands[" + i + "]: " + structResult.error()); + } + + pos += CustomUICommand.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public CustomHud clone() { + CustomHud copy = new CustomHud(); + copy.clear = this.clear; + copy.commands = this.commands != null ? Arrays.stream(this.commands).map(e -> e.clone()).toArray(CustomUICommand[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CustomHud other) ? false : this.clear == other.clear && Arrays.equals((Object[])this.commands, (Object[])other.commands); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Boolean.hashCode(this.clear); + return 31 * result + Arrays.hashCode((Object[])this.commands); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/CustomPage.java b/src/com/hypixel/hytale/protocol/packets/interface_/CustomPage.java new file mode 100644 index 0000000..a237093 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/CustomPage.java @@ -0,0 +1,418 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CustomPage implements Packet { + public static final int PACKET_ID = 218; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 16; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String key; + public boolean isInitial; + public boolean clear; + @Nonnull + public CustomPageLifetime lifetime = CustomPageLifetime.CantClose; + @Nullable + public CustomUICommand[] commands; + @Nullable + public CustomUIEventBinding[] eventBindings; + + @Override + public int getId() { + return 218; + } + + public CustomPage() { + } + + public CustomPage( + @Nullable String key, + boolean isInitial, + boolean clear, + @Nonnull CustomPageLifetime lifetime, + @Nullable CustomUICommand[] commands, + @Nullable CustomUIEventBinding[] eventBindings + ) { + this.key = key; + this.isInitial = isInitial; + this.clear = clear; + this.lifetime = lifetime; + this.commands = commands; + this.eventBindings = eventBindings; + } + + public CustomPage(@Nonnull CustomPage other) { + this.key = other.key; + this.isInitial = other.isInitial; + this.clear = other.clear; + this.lifetime = other.lifetime; + this.commands = other.commands; + this.eventBindings = other.eventBindings; + } + + @Nonnull + public static CustomPage deserialize(@Nonnull ByteBuf buf, int offset) { + CustomPage obj = new CustomPage(); + byte nullBits = buf.getByte(offset); + obj.isInitial = buf.getByte(offset + 1) != 0; + obj.clear = buf.getByte(offset + 2) != 0; + obj.lifetime = CustomPageLifetime.fromValue(buf.getByte(offset + 3)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 16 + buf.getIntLE(offset + 4); + int keyLen = VarInt.peek(buf, varPos0); + if (keyLen < 0) { + throw ProtocolException.negativeLength("Key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("Key", keyLen, 4096000); + } + + obj.key = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 16 + buf.getIntLE(offset + 8); + int commandsCount = VarInt.peek(buf, varPos1); + if (commandsCount < 0) { + throw ProtocolException.negativeLength("Commands", commandsCount); + } + + if (commandsCount > 4096000) { + throw ProtocolException.arrayTooLong("Commands", commandsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + commandsCount * 2L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Commands", varPos1 + varIntLen + commandsCount * 2, buf.readableBytes()); + } + + obj.commands = new CustomUICommand[commandsCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < commandsCount; i++) { + obj.commands[i] = CustomUICommand.deserialize(buf, elemPos); + elemPos += CustomUICommand.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 16 + buf.getIntLE(offset + 12); + int eventBindingsCount = VarInt.peek(buf, varPos2); + if (eventBindingsCount < 0) { + throw ProtocolException.negativeLength("EventBindings", eventBindingsCount); + } + + if (eventBindingsCount > 4096000) { + throw ProtocolException.arrayTooLong("EventBindings", eventBindingsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + eventBindingsCount * 3L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("EventBindings", varPos2 + varIntLen + eventBindingsCount * 3, buf.readableBytes()); + } + + obj.eventBindings = new CustomUIEventBinding[eventBindingsCount]; + int elemPos = varPos2 + varIntLen; + + for (int i = 0; i < eventBindingsCount; i++) { + obj.eventBindings[i] = CustomUIEventBinding.deserialize(buf, elemPos); + elemPos += CustomUIEventBinding.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 16; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 4); + int pos0 = offset + 16 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 8); + int pos1 = offset + 16 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += CustomUICommand.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 12); + int pos2 = offset + 16 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2); + + for (int i = 0; i < arrLen; i++) { + pos2 += CustomUIEventBinding.computeBytesConsumed(buf, pos2); + } + + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.key != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.commands != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.eventBindings != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + buf.writeByte(this.isInitial ? 1 : 0); + buf.writeByte(this.clear ? 1 : 0); + buf.writeByte(this.lifetime.getValue()); + int keyOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int commandsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int eventBindingsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.key != null) { + buf.setIntLE(keyOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.key, 4096000); + } else { + buf.setIntLE(keyOffsetSlot, -1); + } + + if (this.commands != null) { + buf.setIntLE(commandsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.commands.length > 4096000) { + throw ProtocolException.arrayTooLong("Commands", this.commands.length, 4096000); + } + + VarInt.write(buf, this.commands.length); + + for (CustomUICommand item : this.commands) { + item.serialize(buf); + } + } else { + buf.setIntLE(commandsOffsetSlot, -1); + } + + if (this.eventBindings != null) { + buf.setIntLE(eventBindingsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.eventBindings.length > 4096000) { + throw ProtocolException.arrayTooLong("EventBindings", this.eventBindings.length, 4096000); + } + + VarInt.write(buf, this.eventBindings.length); + + for (CustomUIEventBinding item : this.eventBindings) { + item.serialize(buf); + } + } else { + buf.setIntLE(eventBindingsOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 16; + if (this.key != null) { + size += PacketIO.stringSize(this.key); + } + + if (this.commands != null) { + int commandsSize = 0; + + for (CustomUICommand elem : this.commands) { + commandsSize += elem.computeSize(); + } + + size += VarInt.size(this.commands.length) + commandsSize; + } + + if (this.eventBindings != null) { + int eventBindingsSize = 0; + + for (CustomUIEventBinding elem : this.eventBindings) { + eventBindingsSize += elem.computeSize(); + } + + size += VarInt.size(this.eventBindings.length) + eventBindingsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 16) { + return ValidationResult.error("Buffer too small: expected at least 16 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int keyOffset = buffer.getIntLE(offset + 4); + if (keyOffset < 0) { + return ValidationResult.error("Invalid offset for Key"); + } + + int pos = offset + 16 + keyOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Key"); + } + + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for Key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("Key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Key"); + } + } + + if ((nullBits & 2) != 0) { + int commandsOffset = buffer.getIntLE(offset + 8); + if (commandsOffset < 0) { + return ValidationResult.error("Invalid offset for Commands"); + } + + int posx = offset + 16 + commandsOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Commands"); + } + + int commandsCount = VarInt.peek(buffer, posx); + if (commandsCount < 0) { + return ValidationResult.error("Invalid array count for Commands"); + } + + if (commandsCount > 4096000) { + return ValidationResult.error("Commands exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < commandsCount; i++) { + ValidationResult structResult = CustomUICommand.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid CustomUICommand in Commands[" + i + "]: " + structResult.error()); + } + + posx += CustomUICommand.computeBytesConsumed(buffer, posx); + } + } + + if ((nullBits & 4) != 0) { + int eventBindingsOffset = buffer.getIntLE(offset + 12); + if (eventBindingsOffset < 0) { + return ValidationResult.error("Invalid offset for EventBindings"); + } + + int posxx = offset + 16 + eventBindingsOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for EventBindings"); + } + + int eventBindingsCount = VarInt.peek(buffer, posxx); + if (eventBindingsCount < 0) { + return ValidationResult.error("Invalid array count for EventBindings"); + } + + if (eventBindingsCount > 4096000) { + return ValidationResult.error("EventBindings exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + + for (int i = 0; i < eventBindingsCount; i++) { + ValidationResult structResult = CustomUIEventBinding.validateStructure(buffer, posxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid CustomUIEventBinding in EventBindings[" + i + "]: " + structResult.error()); + } + + posxx += CustomUIEventBinding.computeBytesConsumed(buffer, posxx); + } + } + + return ValidationResult.OK; + } + } + + public CustomPage clone() { + CustomPage copy = new CustomPage(); + copy.key = this.key; + copy.isInitial = this.isInitial; + copy.clear = this.clear; + copy.lifetime = this.lifetime; + copy.commands = this.commands != null ? Arrays.stream(this.commands).map(e -> e.clone()).toArray(CustomUICommand[]::new) : null; + copy.eventBindings = this.eventBindings != null ? Arrays.stream(this.eventBindings).map(e -> e.clone()).toArray(CustomUIEventBinding[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CustomPage other) + ? false + : Objects.equals(this.key, other.key) + && this.isInitial == other.isInitial + && this.clear == other.clear + && Objects.equals(this.lifetime, other.lifetime) + && Arrays.equals((Object[])this.commands, (Object[])other.commands) + && Arrays.equals((Object[])this.eventBindings, (Object[])other.eventBindings); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.key); + result = 31 * result + Boolean.hashCode(this.isInitial); + result = 31 * result + Boolean.hashCode(this.clear); + result = 31 * result + Objects.hashCode(this.lifetime); + result = 31 * result + Arrays.hashCode((Object[])this.commands); + return 31 * result + Arrays.hashCode((Object[])this.eventBindings); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/CustomPageEvent.java b/src/com/hypixel/hytale/protocol/packets/interface_/CustomPageEvent.java new file mode 100644 index 0000000..2496534 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/CustomPageEvent.java @@ -0,0 +1,150 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CustomPageEvent implements Packet { + public static final int PACKET_ID = 219; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 16384007; + @Nonnull + public CustomPageEventType type = CustomPageEventType.Acknowledge; + @Nullable + public String data; + + @Override + public int getId() { + return 219; + } + + public CustomPageEvent() { + } + + public CustomPageEvent(@Nonnull CustomPageEventType type, @Nullable String data) { + this.type = type; + this.data = data; + } + + public CustomPageEvent(@Nonnull CustomPageEvent other) { + this.type = other.type; + this.data = other.data; + } + + @Nonnull + public static CustomPageEvent deserialize(@Nonnull ByteBuf buf, int offset) { + CustomPageEvent obj = new CustomPageEvent(); + byte nullBits = buf.getByte(offset); + obj.type = CustomPageEventType.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int dataLen = VarInt.peek(buf, pos); + if (dataLen < 0) { + throw ProtocolException.negativeLength("Data", dataLen); + } + + if (dataLen > 4096000) { + throw ProtocolException.stringTooLong("Data", dataLen, 4096000); + } + + int dataVarLen = VarInt.length(buf, pos); + obj.data = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += dataVarLen + dataLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.data != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.data != null) { + PacketIO.writeVarString(buf, this.data, 4096000); + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.data != null) { + size += PacketIO.stringSize(this.data); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int dataLen = VarInt.peek(buffer, pos); + if (dataLen < 0) { + return ValidationResult.error("Invalid string length for Data"); + } + + if (dataLen > 4096000) { + return ValidationResult.error("Data exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += dataLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Data"); + } + } + + return ValidationResult.OK; + } + } + + public CustomPageEvent clone() { + CustomPageEvent copy = new CustomPageEvent(); + copy.type = this.type; + copy.data = this.data; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CustomPageEvent other) ? false : Objects.equals(this.type, other.type) && Objects.equals(this.data, other.data); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.data); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/CustomPageEventType.java b/src/com/hypixel/hytale/protocol/packets/interface_/CustomPageEventType.java new file mode 100644 index 0000000..960bad8 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/CustomPageEventType.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum CustomPageEventType { + Acknowledge(0), + Data(1), + Dismiss(2); + + public static final CustomPageEventType[] VALUES = values(); + private final int value; + + private CustomPageEventType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static CustomPageEventType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("CustomPageEventType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/CustomPageLifetime.java b/src/com/hypixel/hytale/protocol/packets/interface_/CustomPageLifetime.java new file mode 100644 index 0000000..882bdd5 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/CustomPageLifetime.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum CustomPageLifetime { + CantClose(0), + CanDismiss(1), + CanDismissOrCloseThroughInteraction(2); + + public static final CustomPageLifetime[] VALUES = values(); + private final int value; + + private CustomPageLifetime(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static CustomPageLifetime fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("CustomPageLifetime", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/CustomUICommand.java b/src/com/hypixel/hytale/protocol/packets/interface_/CustomUICommand.java new file mode 100644 index 0000000..64f98bd --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/CustomUICommand.java @@ -0,0 +1,310 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CustomUICommand { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 14; + public static final int MAX_SIZE = 49152029; + @Nonnull + public CustomUICommandType type = CustomUICommandType.Append; + @Nullable + public String selector; + @Nullable + public String data; + @Nullable + public String text; + + public CustomUICommand() { + } + + public CustomUICommand(@Nonnull CustomUICommandType type, @Nullable String selector, @Nullable String data, @Nullable String text) { + this.type = type; + this.selector = selector; + this.data = data; + this.text = text; + } + + public CustomUICommand(@Nonnull CustomUICommand other) { + this.type = other.type; + this.selector = other.selector; + this.data = other.data; + this.text = other.text; + } + + @Nonnull + public static CustomUICommand deserialize(@Nonnull ByteBuf buf, int offset) { + CustomUICommand obj = new CustomUICommand(); + byte nullBits = buf.getByte(offset); + obj.type = CustomUICommandType.fromValue(buf.getByte(offset + 1)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 14 + buf.getIntLE(offset + 2); + int selectorLen = VarInt.peek(buf, varPos0); + if (selectorLen < 0) { + throw ProtocolException.negativeLength("Selector", selectorLen); + } + + if (selectorLen > 4096000) { + throw ProtocolException.stringTooLong("Selector", selectorLen, 4096000); + } + + obj.selector = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 14 + buf.getIntLE(offset + 6); + int dataLen = VarInt.peek(buf, varPos1); + if (dataLen < 0) { + throw ProtocolException.negativeLength("Data", dataLen); + } + + if (dataLen > 4096000) { + throw ProtocolException.stringTooLong("Data", dataLen, 4096000); + } + + obj.data = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 14 + buf.getIntLE(offset + 10); + int textLen = VarInt.peek(buf, varPos2); + if (textLen < 0) { + throw ProtocolException.negativeLength("Text", textLen); + } + + if (textLen > 4096000) { + throw ProtocolException.stringTooLong("Text", textLen, 4096000); + } + + obj.text = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 14; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 2); + int pos0 = offset + 14 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 6); + int pos1 = offset + 14 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 10); + int pos2 = offset + 14 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.selector != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.data != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.text != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + int selectorOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int dataOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int textOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.selector != null) { + buf.setIntLE(selectorOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.selector, 4096000); + } else { + buf.setIntLE(selectorOffsetSlot, -1); + } + + if (this.data != null) { + buf.setIntLE(dataOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.data, 4096000); + } else { + buf.setIntLE(dataOffsetSlot, -1); + } + + if (this.text != null) { + buf.setIntLE(textOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.text, 4096000); + } else { + buf.setIntLE(textOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 14; + if (this.selector != null) { + size += PacketIO.stringSize(this.selector); + } + + if (this.data != null) { + size += PacketIO.stringSize(this.data); + } + + if (this.text != null) { + size += PacketIO.stringSize(this.text); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 14) { + return ValidationResult.error("Buffer too small: expected at least 14 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int selectorOffset = buffer.getIntLE(offset + 2); + if (selectorOffset < 0) { + return ValidationResult.error("Invalid offset for Selector"); + } + + int pos = offset + 14 + selectorOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Selector"); + } + + int selectorLen = VarInt.peek(buffer, pos); + if (selectorLen < 0) { + return ValidationResult.error("Invalid string length for Selector"); + } + + if (selectorLen > 4096000) { + return ValidationResult.error("Selector exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += selectorLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Selector"); + } + } + + if ((nullBits & 2) != 0) { + int dataOffset = buffer.getIntLE(offset + 6); + if (dataOffset < 0) { + return ValidationResult.error("Invalid offset for Data"); + } + + int posx = offset + 14 + dataOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Data"); + } + + int dataLen = VarInt.peek(buffer, posx); + if (dataLen < 0) { + return ValidationResult.error("Invalid string length for Data"); + } + + if (dataLen > 4096000) { + return ValidationResult.error("Data exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += dataLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Data"); + } + } + + if ((nullBits & 4) != 0) { + int textOffset = buffer.getIntLE(offset + 10); + if (textOffset < 0) { + return ValidationResult.error("Invalid offset for Text"); + } + + int posxx = offset + 14 + textOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Text"); + } + + int textLen = VarInt.peek(buffer, posxx); + if (textLen < 0) { + return ValidationResult.error("Invalid string length for Text"); + } + + if (textLen > 4096000) { + return ValidationResult.error("Text exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += textLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Text"); + } + } + + return ValidationResult.OK; + } + } + + public CustomUICommand clone() { + CustomUICommand copy = new CustomUICommand(); + copy.type = this.type; + copy.selector = this.selector; + copy.data = this.data; + copy.text = this.text; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CustomUICommand other) + ? false + : Objects.equals(this.type, other.type) + && Objects.equals(this.selector, other.selector) + && Objects.equals(this.data, other.data) + && Objects.equals(this.text, other.text); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.selector, this.data, this.text); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/CustomUICommandType.java b/src/com/hypixel/hytale/protocol/packets/interface_/CustomUICommandType.java new file mode 100644 index 0000000..9ce7699 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/CustomUICommandType.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum CustomUICommandType { + Append(0), + AppendInline(1), + InsertBefore(2), + InsertBeforeInline(3), + Remove(4), + Set(5), + Clear(6); + + public static final CustomUICommandType[] VALUES = values(); + private final int value; + + private CustomUICommandType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static CustomUICommandType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("CustomUICommandType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/CustomUIEventBinding.java b/src/com/hypixel/hytale/protocol/packets/interface_/CustomUIEventBinding.java new file mode 100644 index 0000000..015e560 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/CustomUIEventBinding.java @@ -0,0 +1,243 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CustomUIEventBinding { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 3; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 11; + public static final int MAX_SIZE = 32768021; + @Nonnull + public CustomUIEventBindingType type = CustomUIEventBindingType.Activating; + @Nullable + public String selector; + @Nullable + public String data; + public boolean locksInterface; + + public CustomUIEventBinding() { + } + + public CustomUIEventBinding(@Nonnull CustomUIEventBindingType type, @Nullable String selector, @Nullable String data, boolean locksInterface) { + this.type = type; + this.selector = selector; + this.data = data; + this.locksInterface = locksInterface; + } + + public CustomUIEventBinding(@Nonnull CustomUIEventBinding other) { + this.type = other.type; + this.selector = other.selector; + this.data = other.data; + this.locksInterface = other.locksInterface; + } + + @Nonnull + public static CustomUIEventBinding deserialize(@Nonnull ByteBuf buf, int offset) { + CustomUIEventBinding obj = new CustomUIEventBinding(); + byte nullBits = buf.getByte(offset); + obj.type = CustomUIEventBindingType.fromValue(buf.getByte(offset + 1)); + obj.locksInterface = buf.getByte(offset + 2) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 11 + buf.getIntLE(offset + 3); + int selectorLen = VarInt.peek(buf, varPos0); + if (selectorLen < 0) { + throw ProtocolException.negativeLength("Selector", selectorLen); + } + + if (selectorLen > 4096000) { + throw ProtocolException.stringTooLong("Selector", selectorLen, 4096000); + } + + obj.selector = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 11 + buf.getIntLE(offset + 7); + int dataLen = VarInt.peek(buf, varPos1); + if (dataLen < 0) { + throw ProtocolException.negativeLength("Data", dataLen); + } + + if (dataLen > 4096000) { + throw ProtocolException.stringTooLong("Data", dataLen, 4096000); + } + + obj.data = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 11; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 3); + int pos0 = offset + 11 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 7); + int pos1 = offset + 11 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.selector != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.data != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + buf.writeByte(this.locksInterface ? 1 : 0); + int selectorOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int dataOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.selector != null) { + buf.setIntLE(selectorOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.selector, 4096000); + } else { + buf.setIntLE(selectorOffsetSlot, -1); + } + + if (this.data != null) { + buf.setIntLE(dataOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.data, 4096000); + } else { + buf.setIntLE(dataOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 11; + if (this.selector != null) { + size += PacketIO.stringSize(this.selector); + } + + if (this.data != null) { + size += PacketIO.stringSize(this.data); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 11) { + return ValidationResult.error("Buffer too small: expected at least 11 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int selectorOffset = buffer.getIntLE(offset + 3); + if (selectorOffset < 0) { + return ValidationResult.error("Invalid offset for Selector"); + } + + int pos = offset + 11 + selectorOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Selector"); + } + + int selectorLen = VarInt.peek(buffer, pos); + if (selectorLen < 0) { + return ValidationResult.error("Invalid string length for Selector"); + } + + if (selectorLen > 4096000) { + return ValidationResult.error("Selector exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += selectorLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Selector"); + } + } + + if ((nullBits & 2) != 0) { + int dataOffset = buffer.getIntLE(offset + 7); + if (dataOffset < 0) { + return ValidationResult.error("Invalid offset for Data"); + } + + int posx = offset + 11 + dataOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Data"); + } + + int dataLen = VarInt.peek(buffer, posx); + if (dataLen < 0) { + return ValidationResult.error("Invalid string length for Data"); + } + + if (dataLen > 4096000) { + return ValidationResult.error("Data exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += dataLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Data"); + } + } + + return ValidationResult.OK; + } + } + + public CustomUIEventBinding clone() { + CustomUIEventBinding copy = new CustomUIEventBinding(); + copy.type = this.type; + copy.selector = this.selector; + copy.data = this.data; + copy.locksInterface = this.locksInterface; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CustomUIEventBinding other) + ? false + : Objects.equals(this.type, other.type) + && Objects.equals(this.selector, other.selector) + && Objects.equals(this.data, other.data) + && this.locksInterface == other.locksInterface; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.selector, this.data, this.locksInterface); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/CustomUIEventBindingType.java b/src/com/hypixel/hytale/protocol/packets/interface_/CustomUIEventBindingType.java new file mode 100644 index 0000000..5d57d54 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/CustomUIEventBindingType.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum CustomUIEventBindingType { + Activating(0), + RightClicking(1), + DoubleClicking(2), + MouseEntered(3), + MouseExited(4), + ValueChanged(5), + ElementReordered(6), + Validating(7), + Dismissing(8), + FocusGained(9), + FocusLost(10), + KeyDown(11), + MouseButtonReleased(12), + SlotClicking(13), + SlotDoubleClicking(14), + SlotMouseEntered(15), + SlotMouseExited(16), + DragCancelled(17), + Dropped(18), + SlotMouseDragCompleted(19), + SlotMouseDragExited(20), + SlotClickReleaseWhileDragging(21), + SlotClickPressWhileDragging(22), + SelectedTabChanged(23); + + public static final CustomUIEventBindingType[] VALUES = values(); + private final int value; + + private CustomUIEventBindingType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static CustomUIEventBindingType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("CustomUIEventBindingType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/EditorBlocksChange.java b/src/com/hypixel/hytale/protocol/packets/interface_/EditorBlocksChange.java new file mode 100644 index 0000000..a94f38f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/EditorBlocksChange.java @@ -0,0 +1,327 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EditorBlocksChange implements Packet { + public static final int PACKET_ID = 222; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 30; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 38; + public static final int MAX_SIZE = 139264048; + @Nullable + public EditorSelection selection; + @Nullable + public BlockChange[] blocksChange; + @Nullable + public FluidChange[] fluidsChange; + public int blocksCount; + public boolean advancedPreview; + + @Override + public int getId() { + return 222; + } + + public EditorBlocksChange() { + } + + public EditorBlocksChange( + @Nullable EditorSelection selection, @Nullable BlockChange[] blocksChange, @Nullable FluidChange[] fluidsChange, int blocksCount, boolean advancedPreview + ) { + this.selection = selection; + this.blocksChange = blocksChange; + this.fluidsChange = fluidsChange; + this.blocksCount = blocksCount; + this.advancedPreview = advancedPreview; + } + + public EditorBlocksChange(@Nonnull EditorBlocksChange other) { + this.selection = other.selection; + this.blocksChange = other.blocksChange; + this.fluidsChange = other.fluidsChange; + this.blocksCount = other.blocksCount; + this.advancedPreview = other.advancedPreview; + } + + @Nonnull + public static EditorBlocksChange deserialize(@Nonnull ByteBuf buf, int offset) { + EditorBlocksChange obj = new EditorBlocksChange(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.selection = EditorSelection.deserialize(buf, offset + 1); + } + + obj.blocksCount = buf.getIntLE(offset + 25); + obj.advancedPreview = buf.getByte(offset + 29) != 0; + if ((nullBits & 2) != 0) { + int varPos0 = offset + 38 + buf.getIntLE(offset + 30); + int blocksChangeCount = VarInt.peek(buf, varPos0); + if (blocksChangeCount < 0) { + throw ProtocolException.negativeLength("BlocksChange", blocksChangeCount); + } + + if (blocksChangeCount > 4096000) { + throw ProtocolException.arrayTooLong("BlocksChange", blocksChangeCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + blocksChangeCount * 17L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("BlocksChange", varPos0 + varIntLen + blocksChangeCount * 17, buf.readableBytes()); + } + + obj.blocksChange = new BlockChange[blocksChangeCount]; + int elemPos = varPos0 + varIntLen; + + for (int i = 0; i < blocksChangeCount; i++) { + obj.blocksChange[i] = BlockChange.deserialize(buf, elemPos); + elemPos += BlockChange.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 4) != 0) { + int varPos1 = offset + 38 + buf.getIntLE(offset + 34); + int fluidsChangeCount = VarInt.peek(buf, varPos1); + if (fluidsChangeCount < 0) { + throw ProtocolException.negativeLength("FluidsChange", fluidsChangeCount); + } + + if (fluidsChangeCount > 4096000) { + throw ProtocolException.arrayTooLong("FluidsChange", fluidsChangeCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + fluidsChangeCount * 17L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("FluidsChange", varPos1 + varIntLen + fluidsChangeCount * 17, buf.readableBytes()); + } + + obj.fluidsChange = new FluidChange[fluidsChangeCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < fluidsChangeCount; i++) { + obj.fluidsChange[i] = FluidChange.deserialize(buf, elemPos); + elemPos += FluidChange.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 38; + if ((nullBits & 2) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 30); + int pos0 = offset + 38 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < arrLen; i++) { + pos0 += BlockChange.computeBytesConsumed(buf, pos0); + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 34); + int pos1 = offset + 38 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += FluidChange.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.selection != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.blocksChange != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.fluidsChange != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + if (this.selection != null) { + this.selection.serialize(buf); + } else { + buf.writeZero(24); + } + + buf.writeIntLE(this.blocksCount); + buf.writeByte(this.advancedPreview ? 1 : 0); + int blocksChangeOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int fluidsChangeOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.blocksChange != null) { + buf.setIntLE(blocksChangeOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.blocksChange.length > 4096000) { + throw ProtocolException.arrayTooLong("BlocksChange", this.blocksChange.length, 4096000); + } + + VarInt.write(buf, this.blocksChange.length); + + for (BlockChange item : this.blocksChange) { + item.serialize(buf); + } + } else { + buf.setIntLE(blocksChangeOffsetSlot, -1); + } + + if (this.fluidsChange != null) { + buf.setIntLE(fluidsChangeOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.fluidsChange.length > 4096000) { + throw ProtocolException.arrayTooLong("FluidsChange", this.fluidsChange.length, 4096000); + } + + VarInt.write(buf, this.fluidsChange.length); + + for (FluidChange item : this.fluidsChange) { + item.serialize(buf); + } + } else { + buf.setIntLE(fluidsChangeOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 38; + if (this.blocksChange != null) { + size += VarInt.size(this.blocksChange.length) + this.blocksChange.length * 17; + } + + if (this.fluidsChange != null) { + size += VarInt.size(this.fluidsChange.length) + this.fluidsChange.length * 17; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 38) { + return ValidationResult.error("Buffer too small: expected at least 38 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 2) != 0) { + int blocksChangeOffset = buffer.getIntLE(offset + 30); + if (blocksChangeOffset < 0) { + return ValidationResult.error("Invalid offset for BlocksChange"); + } + + int pos = offset + 38 + blocksChangeOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BlocksChange"); + } + + int blocksChangeCount = VarInt.peek(buffer, pos); + if (blocksChangeCount < 0) { + return ValidationResult.error("Invalid array count for BlocksChange"); + } + + if (blocksChangeCount > 4096000) { + return ValidationResult.error("BlocksChange exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += blocksChangeCount * 17; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading BlocksChange"); + } + } + + if ((nullBits & 4) != 0) { + int fluidsChangeOffset = buffer.getIntLE(offset + 34); + if (fluidsChangeOffset < 0) { + return ValidationResult.error("Invalid offset for FluidsChange"); + } + + int posx = offset + 38 + fluidsChangeOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FluidsChange"); + } + + int fluidsChangeCount = VarInt.peek(buffer, posx); + if (fluidsChangeCount < 0) { + return ValidationResult.error("Invalid array count for FluidsChange"); + } + + if (fluidsChangeCount > 4096000) { + return ValidationResult.error("FluidsChange exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += fluidsChangeCount * 17; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading FluidsChange"); + } + } + + return ValidationResult.OK; + } + } + + public EditorBlocksChange clone() { + EditorBlocksChange copy = new EditorBlocksChange(); + copy.selection = this.selection != null ? this.selection.clone() : null; + copy.blocksChange = this.blocksChange != null ? Arrays.stream(this.blocksChange).map(e -> e.clone()).toArray(BlockChange[]::new) : null; + copy.fluidsChange = this.fluidsChange != null ? Arrays.stream(this.fluidsChange).map(e -> e.clone()).toArray(FluidChange[]::new) : null; + copy.blocksCount = this.blocksCount; + copy.advancedPreview = this.advancedPreview; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof EditorBlocksChange other) + ? false + : Objects.equals(this.selection, other.selection) + && Arrays.equals((Object[])this.blocksChange, (Object[])other.blocksChange) + && Arrays.equals((Object[])this.fluidsChange, (Object[])other.fluidsChange) + && this.blocksCount == other.blocksCount + && this.advancedPreview == other.advancedPreview; + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.selection); + result = 31 * result + Arrays.hashCode((Object[])this.blocksChange); + result = 31 * result + Arrays.hashCode((Object[])this.fluidsChange); + result = 31 * result + Integer.hashCode(this.blocksCount); + return 31 * result + Boolean.hashCode(this.advancedPreview); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/EditorSelection.java b/src/com/hypixel/hytale/protocol/packets/interface_/EditorSelection.java new file mode 100644 index 0000000..f19264a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/EditorSelection.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class EditorSelection { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 24; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 24; + public static final int MAX_SIZE = 24; + public int minX; + public int minY; + public int minZ; + public int maxX; + public int maxY; + public int maxZ; + + public EditorSelection() { + } + + public EditorSelection(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { + this.minX = minX; + this.minY = minY; + this.minZ = minZ; + this.maxX = maxX; + this.maxY = maxY; + this.maxZ = maxZ; + } + + public EditorSelection(@Nonnull EditorSelection other) { + this.minX = other.minX; + this.minY = other.minY; + this.minZ = other.minZ; + this.maxX = other.maxX; + this.maxY = other.maxY; + this.maxZ = other.maxZ; + } + + @Nonnull + public static EditorSelection deserialize(@Nonnull ByteBuf buf, int offset) { + EditorSelection obj = new EditorSelection(); + obj.minX = buf.getIntLE(offset + 0); + obj.minY = buf.getIntLE(offset + 4); + obj.minZ = buf.getIntLE(offset + 8); + obj.maxX = buf.getIntLE(offset + 12); + obj.maxY = buf.getIntLE(offset + 16); + obj.maxZ = buf.getIntLE(offset + 20); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 24; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.minX); + buf.writeIntLE(this.minY); + buf.writeIntLE(this.minZ); + buf.writeIntLE(this.maxX); + buf.writeIntLE(this.maxY); + buf.writeIntLE(this.maxZ); + } + + public int computeSize() { + return 24; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 24 ? ValidationResult.error("Buffer too small: expected at least 24 bytes") : ValidationResult.OK; + } + + public EditorSelection clone() { + EditorSelection copy = new EditorSelection(); + copy.minX = this.minX; + copy.minY = this.minY; + copy.minZ = this.minZ; + copy.maxX = this.maxX; + copy.maxY = this.maxY; + copy.maxZ = this.maxZ; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof EditorSelection other) + ? false + : this.minX == other.minX + && this.minY == other.minY + && this.minZ == other.minZ + && this.maxX == other.maxX + && this.maxY == other.maxY + && this.maxZ == other.maxZ; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.minX, this.minY, this.minZ, this.maxX, this.maxY, this.maxZ); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/FluidChange.java b/src/com/hypixel/hytale/protocol/packets/interface_/FluidChange.java new file mode 100644 index 0000000..2bcc632 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/FluidChange.java @@ -0,0 +1,95 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class FluidChange { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 17; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 17; + public static final int MAX_SIZE = 17; + public int x; + public int y; + public int z; + public int fluidId; + public byte fluidLevel; + + public FluidChange() { + } + + public FluidChange(int x, int y, int z, int fluidId, byte fluidLevel) { + this.x = x; + this.y = y; + this.z = z; + this.fluidId = fluidId; + this.fluidLevel = fluidLevel; + } + + public FluidChange(@Nonnull FluidChange other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.fluidId = other.fluidId; + this.fluidLevel = other.fluidLevel; + } + + @Nonnull + public static FluidChange deserialize(@Nonnull ByteBuf buf, int offset) { + FluidChange obj = new FluidChange(); + obj.x = buf.getIntLE(offset + 0); + obj.y = buf.getIntLE(offset + 4); + obj.z = buf.getIntLE(offset + 8); + obj.fluidId = buf.getIntLE(offset + 12); + obj.fluidLevel = buf.getByte(offset + 16); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 17; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.x); + buf.writeIntLE(this.y); + buf.writeIntLE(this.z); + buf.writeIntLE(this.fluidId); + buf.writeByte(this.fluidLevel); + } + + public int computeSize() { + return 17; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 17 ? ValidationResult.error("Buffer too small: expected at least 17 bytes") : ValidationResult.OK; + } + + public FluidChange clone() { + FluidChange copy = new FluidChange(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + copy.fluidId = this.fluidId; + copy.fluidLevel = this.fluidLevel; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof FluidChange other) + ? false + : this.x == other.x && this.y == other.y && this.z == other.z && this.fluidId == other.fluidId && this.fluidLevel == other.fluidLevel; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y, this.z, this.fluidId, this.fluidLevel); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/HideEventTitle.java b/src/com/hypixel/hytale/protocol/packets/interface_/HideEventTitle.java new file mode 100644 index 0000000..79105a7 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/HideEventTitle.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class HideEventTitle implements Packet { + public static final int PACKET_ID = 215; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 4; + public float fadeOutDuration; + + @Override + public int getId() { + return 215; + } + + public HideEventTitle() { + } + + public HideEventTitle(float fadeOutDuration) { + this.fadeOutDuration = fadeOutDuration; + } + + public HideEventTitle(@Nonnull HideEventTitle other) { + this.fadeOutDuration = other.fadeOutDuration; + } + + @Nonnull + public static HideEventTitle deserialize(@Nonnull ByteBuf buf, int offset) { + HideEventTitle obj = new HideEventTitle(); + obj.fadeOutDuration = buf.getFloatLE(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 4; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.fadeOutDuration); + } + + @Override + public int computeSize() { + return 4; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 4 ? ValidationResult.error("Buffer too small: expected at least 4 bytes") : ValidationResult.OK; + } + + public HideEventTitle clone() { + HideEventTitle copy = new HideEventTitle(); + copy.fadeOutDuration = this.fadeOutDuration; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof HideEventTitle other ? this.fadeOutDuration == other.fadeOutDuration : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.fadeOutDuration); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/HudComponent.java b/src/com/hypixel/hytale/protocol/packets/interface_/HudComponent.java new file mode 100644 index 0000000..554d127 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/HudComponent.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum HudComponent { + Hotbar(0), + StatusIcons(1), + Reticle(2), + Chat(3), + Requests(4), + Notifications(5), + KillFeed(6), + InputBindings(7), + PlayerList(8), + EventTitle(9), + Compass(10), + ObjectivePanel(11), + PortalPanel(12), + BuilderToolsLegend(13), + Speedometer(14), + UtilitySlotSelector(15), + BlockVariantSelector(16), + BuilderToolsMaterialSlotSelector(17), + Stamina(18), + AmmoIndicator(19), + Health(20), + Mana(21), + Oxygen(22), + Sleep(23); + + public static final HudComponent[] VALUES = values(); + private final int value; + + private HudComponent(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static HudComponent fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("HudComponent", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/KillFeedMessage.java b/src/com/hypixel/hytale/protocol/packets/interface_/KillFeedMessage.java new file mode 100644 index 0000000..2a7ca9c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/KillFeedMessage.java @@ -0,0 +1,275 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.FormattedMessage; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class KillFeedMessage implements Packet { + public static final int PACKET_ID = 213; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 1677721600; + @Nullable + public FormattedMessage killer; + @Nullable + public FormattedMessage decedent; + @Nullable + public String icon; + + @Override + public int getId() { + return 213; + } + + public KillFeedMessage() { + } + + public KillFeedMessage(@Nullable FormattedMessage killer, @Nullable FormattedMessage decedent, @Nullable String icon) { + this.killer = killer; + this.decedent = decedent; + this.icon = icon; + } + + public KillFeedMessage(@Nonnull KillFeedMessage other) { + this.killer = other.killer; + this.decedent = other.decedent; + this.icon = other.icon; + } + + @Nonnull + public static KillFeedMessage deserialize(@Nonnull ByteBuf buf, int offset) { + KillFeedMessage obj = new KillFeedMessage(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 1); + obj.killer = FormattedMessage.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 5); + obj.decedent = FormattedMessage.deserialize(buf, varPos1); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 13 + buf.getIntLE(offset + 9); + int iconLen = VarInt.peek(buf, varPos2); + if (iconLen < 0) { + throw ProtocolException.negativeLength("Icon", iconLen); + } + + if (iconLen > 4096000) { + throw ProtocolException.stringTooLong("Icon", iconLen, 4096000); + } + + obj.icon = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 13 + fieldOffset0; + pos0 += FormattedMessage.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 13 + fieldOffset1; + pos1 += FormattedMessage.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 9); + int pos2 = offset + 13 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.killer != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.decedent != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.icon != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + int killerOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int decedentOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int iconOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.killer != null) { + buf.setIntLE(killerOffsetSlot, buf.writerIndex() - varBlockStart); + this.killer.serialize(buf); + } else { + buf.setIntLE(killerOffsetSlot, -1); + } + + if (this.decedent != null) { + buf.setIntLE(decedentOffsetSlot, buf.writerIndex() - varBlockStart); + this.decedent.serialize(buf); + } else { + buf.setIntLE(decedentOffsetSlot, -1); + } + + if (this.icon != null) { + buf.setIntLE(iconOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.icon, 4096000); + } else { + buf.setIntLE(iconOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 13; + if (this.killer != null) { + size += this.killer.computeSize(); + } + + if (this.decedent != null) { + size += this.decedent.computeSize(); + } + + if (this.icon != null) { + size += PacketIO.stringSize(this.icon); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int killerOffset = buffer.getIntLE(offset + 1); + if (killerOffset < 0) { + return ValidationResult.error("Invalid offset for Killer"); + } + + int pos = offset + 13 + killerOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Killer"); + } + + ValidationResult killerResult = FormattedMessage.validateStructure(buffer, pos); + if (!killerResult.isValid()) { + return ValidationResult.error("Invalid Killer: " + killerResult.error()); + } + + pos += FormattedMessage.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int decedentOffset = buffer.getIntLE(offset + 5); + if (decedentOffset < 0) { + return ValidationResult.error("Invalid offset for Decedent"); + } + + int posx = offset + 13 + decedentOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Decedent"); + } + + ValidationResult decedentResult = FormattedMessage.validateStructure(buffer, posx); + if (!decedentResult.isValid()) { + return ValidationResult.error("Invalid Decedent: " + decedentResult.error()); + } + + posx += FormattedMessage.computeBytesConsumed(buffer, posx); + } + + if ((nullBits & 4) != 0) { + int iconOffset = buffer.getIntLE(offset + 9); + if (iconOffset < 0) { + return ValidationResult.error("Invalid offset for Icon"); + } + + int posxx = offset + 13 + iconOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Icon"); + } + + int iconLen = VarInt.peek(buffer, posxx); + if (iconLen < 0) { + return ValidationResult.error("Invalid string length for Icon"); + } + + if (iconLen > 4096000) { + return ValidationResult.error("Icon exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += iconLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Icon"); + } + } + + return ValidationResult.OK; + } + } + + public KillFeedMessage clone() { + KillFeedMessage copy = new KillFeedMessage(); + copy.killer = this.killer != null ? this.killer.clone() : null; + copy.decedent = this.decedent != null ? this.decedent.clone() : null; + copy.icon = this.icon; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof KillFeedMessage other) + ? false + : Objects.equals(this.killer, other.killer) && Objects.equals(this.decedent, other.decedent) && Objects.equals(this.icon, other.icon); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.killer, this.decedent, this.icon); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/Notification.java b/src/com/hypixel/hytale/protocol/packets/interface_/Notification.java new file mode 100644 index 0000000..c9e2445 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/Notification.java @@ -0,0 +1,348 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.FormattedMessage; +import com.hypixel.hytale.protocol.ItemWithAllMetadata; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Notification implements Packet { + public static final int PACKET_ID = 212; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 4; + public static final int VARIABLE_BLOCK_START = 18; + public static final int MAX_SIZE = 1677721600; + @Nullable + public FormattedMessage message; + @Nullable + public FormattedMessage secondaryMessage; + @Nullable + public String icon; + @Nullable + public ItemWithAllMetadata item; + @Nonnull + public NotificationStyle style = NotificationStyle.Default; + + @Override + public int getId() { + return 212; + } + + public Notification() { + } + + public Notification( + @Nullable FormattedMessage message, + @Nullable FormattedMessage secondaryMessage, + @Nullable String icon, + @Nullable ItemWithAllMetadata item, + @Nonnull NotificationStyle style + ) { + this.message = message; + this.secondaryMessage = secondaryMessage; + this.icon = icon; + this.item = item; + this.style = style; + } + + public Notification(@Nonnull Notification other) { + this.message = other.message; + this.secondaryMessage = other.secondaryMessage; + this.icon = other.icon; + this.item = other.item; + this.style = other.style; + } + + @Nonnull + public static Notification deserialize(@Nonnull ByteBuf buf, int offset) { + Notification obj = new Notification(); + byte nullBits = buf.getByte(offset); + obj.style = NotificationStyle.fromValue(buf.getByte(offset + 1)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 18 + buf.getIntLE(offset + 2); + obj.message = FormattedMessage.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 18 + buf.getIntLE(offset + 6); + obj.secondaryMessage = FormattedMessage.deserialize(buf, varPos1); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 18 + buf.getIntLE(offset + 10); + int iconLen = VarInt.peek(buf, varPos2); + if (iconLen < 0) { + throw ProtocolException.negativeLength("Icon", iconLen); + } + + if (iconLen > 4096000) { + throw ProtocolException.stringTooLong("Icon", iconLen, 4096000); + } + + obj.icon = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 18 + buf.getIntLE(offset + 14); + obj.item = ItemWithAllMetadata.deserialize(buf, varPos3); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 18; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 2); + int pos0 = offset + 18 + fieldOffset0; + pos0 += FormattedMessage.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 6); + int pos1 = offset + 18 + fieldOffset1; + pos1 += FormattedMessage.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 10); + int pos2 = offset + 18 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 14); + int pos3 = offset + 18 + fieldOffset3; + pos3 += ItemWithAllMetadata.computeBytesConsumed(buf, pos3); + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.message != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.secondaryMessage != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.icon != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.item != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + buf.writeByte(this.style.getValue()); + int messageOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int secondaryMessageOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int iconOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int itemOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.message != null) { + buf.setIntLE(messageOffsetSlot, buf.writerIndex() - varBlockStart); + this.message.serialize(buf); + } else { + buf.setIntLE(messageOffsetSlot, -1); + } + + if (this.secondaryMessage != null) { + buf.setIntLE(secondaryMessageOffsetSlot, buf.writerIndex() - varBlockStart); + this.secondaryMessage.serialize(buf); + } else { + buf.setIntLE(secondaryMessageOffsetSlot, -1); + } + + if (this.icon != null) { + buf.setIntLE(iconOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.icon, 4096000); + } else { + buf.setIntLE(iconOffsetSlot, -1); + } + + if (this.item != null) { + buf.setIntLE(itemOffsetSlot, buf.writerIndex() - varBlockStart); + this.item.serialize(buf); + } else { + buf.setIntLE(itemOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 18; + if (this.message != null) { + size += this.message.computeSize(); + } + + if (this.secondaryMessage != null) { + size += this.secondaryMessage.computeSize(); + } + + if (this.icon != null) { + size += PacketIO.stringSize(this.icon); + } + + if (this.item != null) { + size += this.item.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 18) { + return ValidationResult.error("Buffer too small: expected at least 18 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int messageOffset = buffer.getIntLE(offset + 2); + if (messageOffset < 0) { + return ValidationResult.error("Invalid offset for Message"); + } + + int pos = offset + 18 + messageOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Message"); + } + + ValidationResult messageResult = FormattedMessage.validateStructure(buffer, pos); + if (!messageResult.isValid()) { + return ValidationResult.error("Invalid Message: " + messageResult.error()); + } + + pos += FormattedMessage.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int secondaryMessageOffset = buffer.getIntLE(offset + 6); + if (secondaryMessageOffset < 0) { + return ValidationResult.error("Invalid offset for SecondaryMessage"); + } + + int posx = offset + 18 + secondaryMessageOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SecondaryMessage"); + } + + ValidationResult secondaryMessageResult = FormattedMessage.validateStructure(buffer, posx); + if (!secondaryMessageResult.isValid()) { + return ValidationResult.error("Invalid SecondaryMessage: " + secondaryMessageResult.error()); + } + + posx += FormattedMessage.computeBytesConsumed(buffer, posx); + } + + if ((nullBits & 4) != 0) { + int iconOffset = buffer.getIntLE(offset + 10); + if (iconOffset < 0) { + return ValidationResult.error("Invalid offset for Icon"); + } + + int posxx = offset + 18 + iconOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Icon"); + } + + int iconLen = VarInt.peek(buffer, posxx); + if (iconLen < 0) { + return ValidationResult.error("Invalid string length for Icon"); + } + + if (iconLen > 4096000) { + return ValidationResult.error("Icon exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += iconLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Icon"); + } + } + + if ((nullBits & 8) != 0) { + int itemOffset = buffer.getIntLE(offset + 14); + if (itemOffset < 0) { + return ValidationResult.error("Invalid offset for Item"); + } + + int posxxx = offset + 18 + itemOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Item"); + } + + ValidationResult itemResult = ItemWithAllMetadata.validateStructure(buffer, posxxx); + if (!itemResult.isValid()) { + return ValidationResult.error("Invalid Item: " + itemResult.error()); + } + + posxxx += ItemWithAllMetadata.computeBytesConsumed(buffer, posxxx); + } + + return ValidationResult.OK; + } + } + + public Notification clone() { + Notification copy = new Notification(); + copy.message = this.message != null ? this.message.clone() : null; + copy.secondaryMessage = this.secondaryMessage != null ? this.secondaryMessage.clone() : null; + copy.icon = this.icon; + copy.item = this.item != null ? this.item.clone() : null; + copy.style = this.style; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof Notification other) + ? false + : Objects.equals(this.message, other.message) + && Objects.equals(this.secondaryMessage, other.secondaryMessage) + && Objects.equals(this.icon, other.icon) + && Objects.equals(this.item, other.item) + && Objects.equals(this.style, other.style); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.message, this.secondaryMessage, this.icon, this.item, this.style); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/NotificationStyle.java b/src/com/hypixel/hytale/protocol/packets/interface_/NotificationStyle.java new file mode 100644 index 0000000..ca27cb7 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/NotificationStyle.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum NotificationStyle { + Default(0), + Danger(1), + Warning(2), + Success(3); + + public static final NotificationStyle[] VALUES = values(); + private final int value; + + private NotificationStyle(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static NotificationStyle fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("NotificationStyle", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/OpenChatWithCommand.java b/src/com/hypixel/hytale/protocol/packets/interface_/OpenChatWithCommand.java new file mode 100644 index 0000000..33f6bc9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/OpenChatWithCommand.java @@ -0,0 +1,143 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class OpenChatWithCommand implements Packet { + public static final int PACKET_ID = 234; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 16384006; + @Nullable + public String command; + + @Override + public int getId() { + return 234; + } + + public OpenChatWithCommand() { + } + + public OpenChatWithCommand(@Nullable String command) { + this.command = command; + } + + public OpenChatWithCommand(@Nonnull OpenChatWithCommand other) { + this.command = other.command; + } + + @Nonnull + public static OpenChatWithCommand deserialize(@Nonnull ByteBuf buf, int offset) { + OpenChatWithCommand obj = new OpenChatWithCommand(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int commandLen = VarInt.peek(buf, pos); + if (commandLen < 0) { + throw ProtocolException.negativeLength("Command", commandLen); + } + + if (commandLen > 4096000) { + throw ProtocolException.stringTooLong("Command", commandLen, 4096000); + } + + int commandVarLen = VarInt.length(buf, pos); + obj.command = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += commandVarLen + commandLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.command != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.command != null) { + PacketIO.writeVarString(buf, this.command, 4096000); + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.command != null) { + size += PacketIO.stringSize(this.command); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int commandLen = VarInt.peek(buffer, pos); + if (commandLen < 0) { + return ValidationResult.error("Invalid string length for Command"); + } + + if (commandLen > 4096000) { + return ValidationResult.error("Command exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += commandLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Command"); + } + } + + return ValidationResult.OK; + } + } + + public OpenChatWithCommand clone() { + OpenChatWithCommand copy = new OpenChatWithCommand(); + copy.command = this.command; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof OpenChatWithCommand other ? Objects.equals(this.command, other.command) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.command); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/Page.java b/src/com/hypixel/hytale/protocol/packets/interface_/Page.java new file mode 100644 index 0000000..2e2d274 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/Page.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum Page { + None(0), + Bench(1), + Inventory(2), + ToolsSettings(3), + Map(4), + MachinimaEditor(5), + ContentCreation(6), + Custom(7); + + public static final Page[] VALUES = values(); + private final int value; + + private Page(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static Page fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("Page", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/PortalDef.java b/src/com/hypixel/hytale/protocol/packets/interface_/PortalDef.java new file mode 100644 index 0000000..0e8e92b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/PortalDef.java @@ -0,0 +1,147 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PortalDef { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 16384014; + @Nullable + public String nameKey; + public int explorationSeconds; + public int breachSeconds; + + public PortalDef() { + } + + public PortalDef(@Nullable String nameKey, int explorationSeconds, int breachSeconds) { + this.nameKey = nameKey; + this.explorationSeconds = explorationSeconds; + this.breachSeconds = breachSeconds; + } + + public PortalDef(@Nonnull PortalDef other) { + this.nameKey = other.nameKey; + this.explorationSeconds = other.explorationSeconds; + this.breachSeconds = other.breachSeconds; + } + + @Nonnull + public static PortalDef deserialize(@Nonnull ByteBuf buf, int offset) { + PortalDef obj = new PortalDef(); + byte nullBits = buf.getByte(offset); + obj.explorationSeconds = buf.getIntLE(offset + 1); + obj.breachSeconds = buf.getIntLE(offset + 5); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int nameKeyLen = VarInt.peek(buf, pos); + if (nameKeyLen < 0) { + throw ProtocolException.negativeLength("NameKey", nameKeyLen); + } + + if (nameKeyLen > 4096000) { + throw ProtocolException.stringTooLong("NameKey", nameKeyLen, 4096000); + } + + int nameKeyVarLen = VarInt.length(buf, pos); + obj.nameKey = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += nameKeyVarLen + nameKeyLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.nameKey != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.explorationSeconds); + buf.writeIntLE(this.breachSeconds); + if (this.nameKey != null) { + PacketIO.writeVarString(buf, this.nameKey, 4096000); + } + } + + public int computeSize() { + int size = 9; + if (this.nameKey != null) { + size += PacketIO.stringSize(this.nameKey); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int nameKeyLen = VarInt.peek(buffer, pos); + if (nameKeyLen < 0) { + return ValidationResult.error("Invalid string length for NameKey"); + } + + if (nameKeyLen > 4096000) { + return ValidationResult.error("NameKey exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += nameKeyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading NameKey"); + } + } + + return ValidationResult.OK; + } + } + + public PortalDef clone() { + PortalDef copy = new PortalDef(); + copy.nameKey = this.nameKey; + copy.explorationSeconds = this.explorationSeconds; + copy.breachSeconds = this.breachSeconds; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof PortalDef other) + ? false + : Objects.equals(this.nameKey, other.nameKey) && this.explorationSeconds == other.explorationSeconds && this.breachSeconds == other.breachSeconds; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.nameKey, this.explorationSeconds, this.breachSeconds); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/PortalState.java b/src/com/hypixel/hytale/protocol/packets/interface_/PortalState.java new file mode 100644 index 0000000..2ea8128 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/PortalState.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class PortalState { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 5; + public int remainingSeconds; + public boolean breaching; + + public PortalState() { + } + + public PortalState(int remainingSeconds, boolean breaching) { + this.remainingSeconds = remainingSeconds; + this.breaching = breaching; + } + + public PortalState(@Nonnull PortalState other) { + this.remainingSeconds = other.remainingSeconds; + this.breaching = other.breaching; + } + + @Nonnull + public static PortalState deserialize(@Nonnull ByteBuf buf, int offset) { + PortalState obj = new PortalState(); + obj.remainingSeconds = buf.getIntLE(offset + 0); + obj.breaching = buf.getByte(offset + 4) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 5; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.remainingSeconds); + buf.writeByte(this.breaching ? 1 : 0); + } + + public int computeSize() { + return 5; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 5 ? ValidationResult.error("Buffer too small: expected at least 5 bytes") : ValidationResult.OK; + } + + public PortalState clone() { + PortalState copy = new PortalState(); + copy.remainingSeconds = this.remainingSeconds; + copy.breaching = this.breaching; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof PortalState other) ? false : this.remainingSeconds == other.remainingSeconds && this.breaching == other.breaching; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.remainingSeconds, this.breaching); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/RemoveFromServerPlayerList.java b/src/com/hypixel/hytale/protocol/packets/interface_/RemoveFromServerPlayerList.java new file mode 100644 index 0000000..3102b74 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/RemoveFromServerPlayerList.java @@ -0,0 +1,163 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RemoveFromServerPlayerList implements Packet { + public static final int PACKET_ID = 225; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 65536006; + @Nullable + public UUID[] players; + + @Override + public int getId() { + return 225; + } + + public RemoveFromServerPlayerList() { + } + + public RemoveFromServerPlayerList(@Nullable UUID[] players) { + this.players = players; + } + + public RemoveFromServerPlayerList(@Nonnull RemoveFromServerPlayerList other) { + this.players = other.players; + } + + @Nonnull + public static RemoveFromServerPlayerList deserialize(@Nonnull ByteBuf buf, int offset) { + RemoveFromServerPlayerList obj = new RemoveFromServerPlayerList(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int playersCount = VarInt.peek(buf, pos); + if (playersCount < 0) { + throw ProtocolException.negativeLength("Players", playersCount); + } + + if (playersCount > 4096000) { + throw ProtocolException.arrayTooLong("Players", playersCount, 4096000); + } + + int playersVarLen = VarInt.size(playersCount); + if (pos + playersVarLen + playersCount * 16L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Players", pos + playersVarLen + playersCount * 16, buf.readableBytes()); + } + + pos += playersVarLen; + obj.players = new UUID[playersCount]; + + for (int i = 0; i < playersCount; i++) { + obj.players[i] = PacketIO.readUUID(buf, pos + i * 16); + } + + pos += playersCount * 16; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 16; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.players != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.players != null) { + if (this.players.length > 4096000) { + throw ProtocolException.arrayTooLong("Players", this.players.length, 4096000); + } + + VarInt.write(buf, this.players.length); + + for (UUID item : this.players) { + PacketIO.writeUUID(buf, item); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.players != null) { + size += VarInt.size(this.players.length) + this.players.length * 16; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int playersCount = VarInt.peek(buffer, pos); + if (playersCount < 0) { + return ValidationResult.error("Invalid array count for Players"); + } + + if (playersCount > 4096000) { + return ValidationResult.error("Players exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += playersCount * 16; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Players"); + } + } + + return ValidationResult.OK; + } + } + + public RemoveFromServerPlayerList clone() { + RemoveFromServerPlayerList copy = new RemoveFromServerPlayerList(); + copy.players = this.players != null ? Arrays.copyOf(this.players, this.players.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof RemoveFromServerPlayerList other ? Arrays.equals((Object[])this.players, (Object[])other.players) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.players); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/ResetUserInterfaceState.java b/src/com/hypixel/hytale/protocol/packets/interface_/ResetUserInterfaceState.java new file mode 100644 index 0000000..ccf1d40 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/ResetUserInterfaceState.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class ResetUserInterfaceState implements Packet { + public static final int PACKET_ID = 231; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public ResetUserInterfaceState() { + } + + @Override + public int getId() { + return 231; + } + + @Nonnull + public static ResetUserInterfaceState deserialize(@Nonnull ByteBuf buf, int offset) { + return new ResetUserInterfaceState(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public ResetUserInterfaceState clone() { + return new ResetUserInterfaceState(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof ResetUserInterfaceState other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/ServerInfo.java b/src/com/hypixel/hytale/protocol/packets/interface_/ServerInfo.java new file mode 100644 index 0000000..ae18da2 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/ServerInfo.java @@ -0,0 +1,243 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ServerInfo implements Packet { + public static final int PACKET_ID = 223; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 32768023; + @Nullable + public String serverName; + @Nullable + public String motd; + public int maxPlayers; + + @Override + public int getId() { + return 223; + } + + public ServerInfo() { + } + + public ServerInfo(@Nullable String serverName, @Nullable String motd, int maxPlayers) { + this.serverName = serverName; + this.motd = motd; + this.maxPlayers = maxPlayers; + } + + public ServerInfo(@Nonnull ServerInfo other) { + this.serverName = other.serverName; + this.motd = other.motd; + this.maxPlayers = other.maxPlayers; + } + + @Nonnull + public static ServerInfo deserialize(@Nonnull ByteBuf buf, int offset) { + ServerInfo obj = new ServerInfo(); + byte nullBits = buf.getByte(offset); + obj.maxPlayers = buf.getIntLE(offset + 1); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 5); + int serverNameLen = VarInt.peek(buf, varPos0); + if (serverNameLen < 0) { + throw ProtocolException.negativeLength("ServerName", serverNameLen); + } + + if (serverNameLen > 4096000) { + throw ProtocolException.stringTooLong("ServerName", serverNameLen, 4096000); + } + + obj.serverName = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 9); + int motdLen = VarInt.peek(buf, varPos1); + if (motdLen < 0) { + throw ProtocolException.negativeLength("Motd", motdLen); + } + + if (motdLen > 4096000) { + throw ProtocolException.stringTooLong("Motd", motdLen, 4096000); + } + + obj.motd = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 5); + int pos0 = offset + 13 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 9); + int pos1 = offset + 13 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.serverName != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.motd != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.maxPlayers); + int serverNameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int motdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.serverName != null) { + buf.setIntLE(serverNameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.serverName, 4096000); + } else { + buf.setIntLE(serverNameOffsetSlot, -1); + } + + if (this.motd != null) { + buf.setIntLE(motdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.motd, 4096000); + } else { + buf.setIntLE(motdOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 13; + if (this.serverName != null) { + size += PacketIO.stringSize(this.serverName); + } + + if (this.motd != null) { + size += PacketIO.stringSize(this.motd); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int serverNameOffset = buffer.getIntLE(offset + 5); + if (serverNameOffset < 0) { + return ValidationResult.error("Invalid offset for ServerName"); + } + + int pos = offset + 13 + serverNameOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ServerName"); + } + + int serverNameLen = VarInt.peek(buffer, pos); + if (serverNameLen < 0) { + return ValidationResult.error("Invalid string length for ServerName"); + } + + if (serverNameLen > 4096000) { + return ValidationResult.error("ServerName exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += serverNameLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ServerName"); + } + } + + if ((nullBits & 2) != 0) { + int motdOffset = buffer.getIntLE(offset + 9); + if (motdOffset < 0) { + return ValidationResult.error("Invalid offset for Motd"); + } + + int posx = offset + 13 + motdOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Motd"); + } + + int motdLen = VarInt.peek(buffer, posx); + if (motdLen < 0) { + return ValidationResult.error("Invalid string length for Motd"); + } + + if (motdLen > 4096000) { + return ValidationResult.error("Motd exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += motdLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Motd"); + } + } + + return ValidationResult.OK; + } + } + + public ServerInfo clone() { + ServerInfo copy = new ServerInfo(); + copy.serverName = this.serverName; + copy.motd = this.motd; + copy.maxPlayers = this.maxPlayers; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ServerInfo other) + ? false + : Objects.equals(this.serverName, other.serverName) && Objects.equals(this.motd, other.motd) && this.maxPlayers == other.maxPlayers; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.serverName, this.motd, this.maxPlayers); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/ServerMessage.java b/src/com/hypixel/hytale/protocol/packets/interface_/ServerMessage.java new file mode 100644 index 0000000..356444c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/ServerMessage.java @@ -0,0 +1,129 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.FormattedMessage; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ServerMessage implements Packet { + public static final int PACKET_ID = 210; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public ChatType type = ChatType.Chat; + @Nullable + public FormattedMessage message; + + @Override + public int getId() { + return 210; + } + + public ServerMessage() { + } + + public ServerMessage(@Nonnull ChatType type, @Nullable FormattedMessage message) { + this.type = type; + this.message = message; + } + + public ServerMessage(@Nonnull ServerMessage other) { + this.type = other.type; + this.message = other.message; + } + + @Nonnull + public static ServerMessage deserialize(@Nonnull ByteBuf buf, int offset) { + ServerMessage obj = new ServerMessage(); + byte nullBits = buf.getByte(offset); + obj.type = ChatType.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + obj.message = FormattedMessage.deserialize(buf, pos); + pos += FormattedMessage.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + pos += FormattedMessage.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.message != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.type.getValue()); + if (this.message != null) { + this.message.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.message != null) { + size += this.message.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + ValidationResult messageResult = FormattedMessage.validateStructure(buffer, pos); + if (!messageResult.isValid()) { + return ValidationResult.error("Invalid Message: " + messageResult.error()); + } + + pos += FormattedMessage.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public ServerMessage clone() { + ServerMessage copy = new ServerMessage(); + copy.type = this.type; + copy.message = this.message != null ? this.message.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ServerMessage other) ? false : Objects.equals(this.type, other.type) && Objects.equals(this.message, other.message); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.message); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/ServerPlayerListPlayer.java b/src/com/hypixel/hytale/protocol/packets/interface_/ServerPlayerListPlayer.java new file mode 100644 index 0000000..f40193d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/ServerPlayerListPlayer.java @@ -0,0 +1,171 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ServerPlayerListPlayer { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 37; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 37; + public static final int MAX_SIZE = 16384042; + @Nonnull + public UUID uuid = new UUID(0L, 0L); + @Nullable + public String username; + @Nullable + public UUID worldUuid; + public int ping; + + public ServerPlayerListPlayer() { + } + + public ServerPlayerListPlayer(@Nonnull UUID uuid, @Nullable String username, @Nullable UUID worldUuid, int ping) { + this.uuid = uuid; + this.username = username; + this.worldUuid = worldUuid; + this.ping = ping; + } + + public ServerPlayerListPlayer(@Nonnull ServerPlayerListPlayer other) { + this.uuid = other.uuid; + this.username = other.username; + this.worldUuid = other.worldUuid; + this.ping = other.ping; + } + + @Nonnull + public static ServerPlayerListPlayer deserialize(@Nonnull ByteBuf buf, int offset) { + ServerPlayerListPlayer obj = new ServerPlayerListPlayer(); + byte nullBits = buf.getByte(offset); + obj.uuid = PacketIO.readUUID(buf, offset + 1); + if ((nullBits & 2) != 0) { + obj.worldUuid = PacketIO.readUUID(buf, offset + 17); + } + + obj.ping = buf.getIntLE(offset + 33); + int pos = offset + 37; + if ((nullBits & 1) != 0) { + int usernameLen = VarInt.peek(buf, pos); + if (usernameLen < 0) { + throw ProtocolException.negativeLength("Username", usernameLen); + } + + if (usernameLen > 4096000) { + throw ProtocolException.stringTooLong("Username", usernameLen, 4096000); + } + + int usernameVarLen = VarInt.length(buf, pos); + obj.username = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += usernameVarLen + usernameLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 37; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.username != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.worldUuid != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + PacketIO.writeUUID(buf, this.uuid); + if (this.worldUuid != null) { + PacketIO.writeUUID(buf, this.worldUuid); + } else { + buf.writeZero(16); + } + + buf.writeIntLE(this.ping); + if (this.username != null) { + PacketIO.writeVarString(buf, this.username, 4096000); + } + } + + public int computeSize() { + int size = 37; + if (this.username != null) { + size += PacketIO.stringSize(this.username); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 37) { + return ValidationResult.error("Buffer too small: expected at least 37 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 37; + if ((nullBits & 1) != 0) { + int usernameLen = VarInt.peek(buffer, pos); + if (usernameLen < 0) { + return ValidationResult.error("Invalid string length for Username"); + } + + if (usernameLen > 4096000) { + return ValidationResult.error("Username exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += usernameLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Username"); + } + } + + return ValidationResult.OK; + } + } + + public ServerPlayerListPlayer clone() { + ServerPlayerListPlayer copy = new ServerPlayerListPlayer(); + copy.uuid = this.uuid; + copy.username = this.username; + copy.worldUuid = this.worldUuid; + copy.ping = this.ping; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ServerPlayerListPlayer other) + ? false + : Objects.equals(this.uuid, other.uuid) + && Objects.equals(this.username, other.username) + && Objects.equals(this.worldUuid, other.worldUuid) + && this.ping == other.ping; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.uuid, this.username, this.worldUuid, this.ping); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/ServerPlayerListUpdate.java b/src/com/hypixel/hytale/protocol/packets/interface_/ServerPlayerListUpdate.java new file mode 100644 index 0000000..9902340 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/ServerPlayerListUpdate.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class ServerPlayerListUpdate { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 32; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 32; + public static final int MAX_SIZE = 32; + @Nonnull + public UUID uuid = new UUID(0L, 0L); + @Nonnull + public UUID worldUuid = new UUID(0L, 0L); + + public ServerPlayerListUpdate() { + } + + public ServerPlayerListUpdate(@Nonnull UUID uuid, @Nonnull UUID worldUuid) { + this.uuid = uuid; + this.worldUuid = worldUuid; + } + + public ServerPlayerListUpdate(@Nonnull ServerPlayerListUpdate other) { + this.uuid = other.uuid; + this.worldUuid = other.worldUuid; + } + + @Nonnull + public static ServerPlayerListUpdate deserialize(@Nonnull ByteBuf buf, int offset) { + ServerPlayerListUpdate obj = new ServerPlayerListUpdate(); + obj.uuid = PacketIO.readUUID(buf, offset + 0); + obj.worldUuid = PacketIO.readUUID(buf, offset + 16); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 32; + } + + public void serialize(@Nonnull ByteBuf buf) { + PacketIO.writeUUID(buf, this.uuid); + PacketIO.writeUUID(buf, this.worldUuid); + } + + public int computeSize() { + return 32; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 32 ? ValidationResult.error("Buffer too small: expected at least 32 bytes") : ValidationResult.OK; + } + + public ServerPlayerListUpdate clone() { + ServerPlayerListUpdate copy = new ServerPlayerListUpdate(); + copy.uuid = this.uuid; + copy.worldUuid = this.worldUuid; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ServerPlayerListUpdate other) + ? false + : Objects.equals(this.uuid, other.uuid) && Objects.equals(this.worldUuid, other.worldUuid); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.uuid, this.worldUuid); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/SetPage.java b/src/com/hypixel/hytale/protocol/packets/interface_/SetPage.java new file mode 100644 index 0000000..6733b84 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/SetPage.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SetPage implements Packet { + public static final int PACKET_ID = 216; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 2; + @Nonnull + public Page page = Page.None; + public boolean canCloseThroughInteraction; + + @Override + public int getId() { + return 216; + } + + public SetPage() { + } + + public SetPage(@Nonnull Page page, boolean canCloseThroughInteraction) { + this.page = page; + this.canCloseThroughInteraction = canCloseThroughInteraction; + } + + public SetPage(@Nonnull SetPage other) { + this.page = other.page; + this.canCloseThroughInteraction = other.canCloseThroughInteraction; + } + + @Nonnull + public static SetPage deserialize(@Nonnull ByteBuf buf, int offset) { + SetPage obj = new SetPage(); + obj.page = Page.fromValue(buf.getByte(offset + 0)); + obj.canCloseThroughInteraction = buf.getByte(offset + 1) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 2; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.page.getValue()); + buf.writeByte(this.canCloseThroughInteraction ? 1 : 0); + } + + @Override + public int computeSize() { + return 2; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 2 ? ValidationResult.error("Buffer too small: expected at least 2 bytes") : ValidationResult.OK; + } + + public SetPage clone() { + SetPage copy = new SetPage(); + copy.page = this.page; + copy.canCloseThroughInteraction = this.canCloseThroughInteraction; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SetPage other) + ? false + : Objects.equals(this.page, other.page) && this.canCloseThroughInteraction == other.canCloseThroughInteraction; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.page, this.canCloseThroughInteraction); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/ShowEventTitle.java b/src/com/hypixel/hytale/protocol/packets/interface_/ShowEventTitle.java new file mode 100644 index 0000000..74f3b92 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/ShowEventTitle.java @@ -0,0 +1,313 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.FormattedMessage; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ShowEventTitle implements Packet { + public static final int PACKET_ID = 214; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 14; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 26; + public static final int MAX_SIZE = 1677721600; + public float fadeInDuration; + public float fadeOutDuration; + public float duration; + @Nullable + public String icon; + public boolean isMajor; + @Nullable + public FormattedMessage primaryTitle; + @Nullable + public FormattedMessage secondaryTitle; + + @Override + public int getId() { + return 214; + } + + public ShowEventTitle() { + } + + public ShowEventTitle( + float fadeInDuration, + float fadeOutDuration, + float duration, + @Nullable String icon, + boolean isMajor, + @Nullable FormattedMessage primaryTitle, + @Nullable FormattedMessage secondaryTitle + ) { + this.fadeInDuration = fadeInDuration; + this.fadeOutDuration = fadeOutDuration; + this.duration = duration; + this.icon = icon; + this.isMajor = isMajor; + this.primaryTitle = primaryTitle; + this.secondaryTitle = secondaryTitle; + } + + public ShowEventTitle(@Nonnull ShowEventTitle other) { + this.fadeInDuration = other.fadeInDuration; + this.fadeOutDuration = other.fadeOutDuration; + this.duration = other.duration; + this.icon = other.icon; + this.isMajor = other.isMajor; + this.primaryTitle = other.primaryTitle; + this.secondaryTitle = other.secondaryTitle; + } + + @Nonnull + public static ShowEventTitle deserialize(@Nonnull ByteBuf buf, int offset) { + ShowEventTitle obj = new ShowEventTitle(); + byte nullBits = buf.getByte(offset); + obj.fadeInDuration = buf.getFloatLE(offset + 1); + obj.fadeOutDuration = buf.getFloatLE(offset + 5); + obj.duration = buf.getFloatLE(offset + 9); + obj.isMajor = buf.getByte(offset + 13) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 26 + buf.getIntLE(offset + 14); + int iconLen = VarInt.peek(buf, varPos0); + if (iconLen < 0) { + throw ProtocolException.negativeLength("Icon", iconLen); + } + + if (iconLen > 4096000) { + throw ProtocolException.stringTooLong("Icon", iconLen, 4096000); + } + + obj.icon = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 26 + buf.getIntLE(offset + 18); + obj.primaryTitle = FormattedMessage.deserialize(buf, varPos1); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 26 + buf.getIntLE(offset + 22); + obj.secondaryTitle = FormattedMessage.deserialize(buf, varPos2); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 26; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 14); + int pos0 = offset + 26 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 18); + int pos1 = offset + 26 + fieldOffset1; + pos1 += FormattedMessage.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 22); + int pos2 = offset + 26 + fieldOffset2; + pos2 += FormattedMessage.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.icon != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.primaryTitle != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.secondaryTitle != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.fadeInDuration); + buf.writeFloatLE(this.fadeOutDuration); + buf.writeFloatLE(this.duration); + buf.writeByte(this.isMajor ? 1 : 0); + int iconOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int primaryTitleOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int secondaryTitleOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.icon != null) { + buf.setIntLE(iconOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.icon, 4096000); + } else { + buf.setIntLE(iconOffsetSlot, -1); + } + + if (this.primaryTitle != null) { + buf.setIntLE(primaryTitleOffsetSlot, buf.writerIndex() - varBlockStart); + this.primaryTitle.serialize(buf); + } else { + buf.setIntLE(primaryTitleOffsetSlot, -1); + } + + if (this.secondaryTitle != null) { + buf.setIntLE(secondaryTitleOffsetSlot, buf.writerIndex() - varBlockStart); + this.secondaryTitle.serialize(buf); + } else { + buf.setIntLE(secondaryTitleOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 26; + if (this.icon != null) { + size += PacketIO.stringSize(this.icon); + } + + if (this.primaryTitle != null) { + size += this.primaryTitle.computeSize(); + } + + if (this.secondaryTitle != null) { + size += this.secondaryTitle.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 26) { + return ValidationResult.error("Buffer too small: expected at least 26 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int iconOffset = buffer.getIntLE(offset + 14); + if (iconOffset < 0) { + return ValidationResult.error("Invalid offset for Icon"); + } + + int pos = offset + 26 + iconOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Icon"); + } + + int iconLen = VarInt.peek(buffer, pos); + if (iconLen < 0) { + return ValidationResult.error("Invalid string length for Icon"); + } + + if (iconLen > 4096000) { + return ValidationResult.error("Icon exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += iconLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Icon"); + } + } + + if ((nullBits & 2) != 0) { + int primaryTitleOffset = buffer.getIntLE(offset + 18); + if (primaryTitleOffset < 0) { + return ValidationResult.error("Invalid offset for PrimaryTitle"); + } + + int posx = offset + 26 + primaryTitleOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for PrimaryTitle"); + } + + ValidationResult primaryTitleResult = FormattedMessage.validateStructure(buffer, posx); + if (!primaryTitleResult.isValid()) { + return ValidationResult.error("Invalid PrimaryTitle: " + primaryTitleResult.error()); + } + + posx += FormattedMessage.computeBytesConsumed(buffer, posx); + } + + if ((nullBits & 4) != 0) { + int secondaryTitleOffset = buffer.getIntLE(offset + 22); + if (secondaryTitleOffset < 0) { + return ValidationResult.error("Invalid offset for SecondaryTitle"); + } + + int posxx = offset + 26 + secondaryTitleOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SecondaryTitle"); + } + + ValidationResult secondaryTitleResult = FormattedMessage.validateStructure(buffer, posxx); + if (!secondaryTitleResult.isValid()) { + return ValidationResult.error("Invalid SecondaryTitle: " + secondaryTitleResult.error()); + } + + posxx += FormattedMessage.computeBytesConsumed(buffer, posxx); + } + + return ValidationResult.OK; + } + } + + public ShowEventTitle clone() { + ShowEventTitle copy = new ShowEventTitle(); + copy.fadeInDuration = this.fadeInDuration; + copy.fadeOutDuration = this.fadeOutDuration; + copy.duration = this.duration; + copy.icon = this.icon; + copy.isMajor = this.isMajor; + copy.primaryTitle = this.primaryTitle != null ? this.primaryTitle.clone() : null; + copy.secondaryTitle = this.secondaryTitle != null ? this.secondaryTitle.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ShowEventTitle other) + ? false + : this.fadeInDuration == other.fadeInDuration + && this.fadeOutDuration == other.fadeOutDuration + && this.duration == other.duration + && Objects.equals(this.icon, other.icon) + && this.isMajor == other.isMajor + && Objects.equals(this.primaryTitle, other.primaryTitle) + && Objects.equals(this.secondaryTitle, other.secondaryTitle); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.fadeInDuration, this.fadeOutDuration, this.duration, this.icon, this.isMajor, this.primaryTitle, this.secondaryTitle); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/UpdateKnownRecipes.java b/src/com/hypixel/hytale/protocol/packets/interface_/UpdateKnownRecipes.java new file mode 100644 index 0000000..b3305b2 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/UpdateKnownRecipes.java @@ -0,0 +1,211 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.CraftingRecipe; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateKnownRecipes implements Packet { + public static final int PACKET_ID = 228; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public Map known; + + @Override + public int getId() { + return 228; + } + + public UpdateKnownRecipes() { + } + + public UpdateKnownRecipes(@Nullable Map known) { + this.known = known; + } + + public UpdateKnownRecipes(@Nonnull UpdateKnownRecipes other) { + this.known = other.known; + } + + @Nonnull + public static UpdateKnownRecipes deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateKnownRecipes obj = new UpdateKnownRecipes(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int knownCount = VarInt.peek(buf, pos); + if (knownCount < 0) { + throw ProtocolException.negativeLength("Known", knownCount); + } + + if (knownCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Known", knownCount, 4096000); + } + + pos += VarInt.size(knownCount); + obj.known = new HashMap<>(knownCount); + + for (int i = 0; i < knownCount; i++) { + int keyLen = VarInt.peek(buf, pos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, pos); + String key = PacketIO.readVarString(buf, pos); + pos += keyVarLen + keyLen; + CraftingRecipe val = CraftingRecipe.deserialize(buf, pos); + pos += CraftingRecipe.computeBytesConsumed(buf, pos); + if (obj.known.put(key, val) != null) { + throw ProtocolException.duplicateKey("known", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + pos += CraftingRecipe.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.known != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.known != null) { + if (this.known.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Known", this.known.size(), 4096000); + } + + VarInt.write(buf, this.known.size()); + + for (Entry e : this.known.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.known != null) { + int knownSize = 0; + + for (Entry kvp : this.known.entrySet()) { + knownSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.known.size()) + knownSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int knownCount = VarInt.peek(buffer, pos); + if (knownCount < 0) { + return ValidationResult.error("Invalid dictionary count for Known"); + } + + if (knownCount > 4096000) { + return ValidationResult.error("Known exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < knownCount; i++) { + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += CraftingRecipe.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateKnownRecipes clone() { + UpdateKnownRecipes copy = new UpdateKnownRecipes(); + if (this.known != null) { + Map m = new HashMap<>(); + + for (Entry e : this.known.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.known = m; + } + + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof UpdateKnownRecipes other ? Objects.equals(this.known, other.known) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.known); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/UpdateLanguage.java b/src/com/hypixel/hytale/protocol/packets/interface_/UpdateLanguage.java new file mode 100644 index 0000000..c707172 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/UpdateLanguage.java @@ -0,0 +1,143 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateLanguage implements Packet { + public static final int PACKET_ID = 232; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 16384006; + @Nullable + public String language; + + @Override + public int getId() { + return 232; + } + + public UpdateLanguage() { + } + + public UpdateLanguage(@Nullable String language) { + this.language = language; + } + + public UpdateLanguage(@Nonnull UpdateLanguage other) { + this.language = other.language; + } + + @Nonnull + public static UpdateLanguage deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateLanguage obj = new UpdateLanguage(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int languageLen = VarInt.peek(buf, pos); + if (languageLen < 0) { + throw ProtocolException.negativeLength("Language", languageLen); + } + + if (languageLen > 4096000) { + throw ProtocolException.stringTooLong("Language", languageLen, 4096000); + } + + int languageVarLen = VarInt.length(buf, pos); + obj.language = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += languageVarLen + languageLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.language != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.language != null) { + PacketIO.writeVarString(buf, this.language, 4096000); + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.language != null) { + size += PacketIO.stringSize(this.language); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int languageLen = VarInt.peek(buffer, pos); + if (languageLen < 0) { + return ValidationResult.error("Invalid string length for Language"); + } + + if (languageLen > 4096000) { + return ValidationResult.error("Language exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += languageLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Language"); + } + } + + return ValidationResult.OK; + } + } + + public UpdateLanguage clone() { + UpdateLanguage copy = new UpdateLanguage(); + copy.language = this.language; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof UpdateLanguage other ? Objects.equals(this.language, other.language) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.language); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/UpdatePortal.java b/src/com/hypixel/hytale/protocol/packets/interface_/UpdatePortal.java new file mode 100644 index 0000000..d475e6e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/UpdatePortal.java @@ -0,0 +1,140 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdatePortal implements Packet { + public static final int PACKET_ID = 229; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 16384020; + @Nullable + public PortalState state; + @Nullable + public PortalDef definition; + + @Override + public int getId() { + return 229; + } + + public UpdatePortal() { + } + + public UpdatePortal(@Nullable PortalState state, @Nullable PortalDef definition) { + this.state = state; + this.definition = definition; + } + + public UpdatePortal(@Nonnull UpdatePortal other) { + this.state = other.state; + this.definition = other.definition; + } + + @Nonnull + public static UpdatePortal deserialize(@Nonnull ByteBuf buf, int offset) { + UpdatePortal obj = new UpdatePortal(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.state = PortalState.deserialize(buf, offset + 1); + } + + int pos = offset + 6; + if ((nullBits & 2) != 0) { + obj.definition = PortalDef.deserialize(buf, pos); + pos += PortalDef.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 6; + if ((nullBits & 2) != 0) { + pos += PortalDef.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.state != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.definition != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + if (this.state != null) { + this.state.serialize(buf); + } else { + buf.writeZero(5); + } + + if (this.definition != null) { + this.definition.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 6; + if (this.definition != null) { + size += this.definition.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 6) { + return ValidationResult.error("Buffer too small: expected at least 6 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 6; + if ((nullBits & 2) != 0) { + ValidationResult definitionResult = PortalDef.validateStructure(buffer, pos); + if (!definitionResult.isValid()) { + return ValidationResult.error("Invalid Definition: " + definitionResult.error()); + } + + pos += PortalDef.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public UpdatePortal clone() { + UpdatePortal copy = new UpdatePortal(); + copy.state = this.state != null ? this.state.clone() : null; + copy.definition = this.definition != null ? this.definition.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdatePortal other) ? false : Objects.equals(this.state, other.state) && Objects.equals(this.definition, other.definition); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.state, this.definition); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/UpdateServerPlayerList.java b/src/com/hypixel/hytale/protocol/packets/interface_/UpdateServerPlayerList.java new file mode 100644 index 0000000..da0362d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/UpdateServerPlayerList.java @@ -0,0 +1,164 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateServerPlayerList implements Packet { + public static final int PACKET_ID = 226; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 131072006; + @Nullable + public ServerPlayerListUpdate[] players; + + @Override + public int getId() { + return 226; + } + + public UpdateServerPlayerList() { + } + + public UpdateServerPlayerList(@Nullable ServerPlayerListUpdate[] players) { + this.players = players; + } + + public UpdateServerPlayerList(@Nonnull UpdateServerPlayerList other) { + this.players = other.players; + } + + @Nonnull + public static UpdateServerPlayerList deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateServerPlayerList obj = new UpdateServerPlayerList(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int playersCount = VarInt.peek(buf, pos); + if (playersCount < 0) { + throw ProtocolException.negativeLength("Players", playersCount); + } + + if (playersCount > 4096000) { + throw ProtocolException.arrayTooLong("Players", playersCount, 4096000); + } + + int playersVarLen = VarInt.size(playersCount); + if (pos + playersVarLen + playersCount * 32L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Players", pos + playersVarLen + playersCount * 32, buf.readableBytes()); + } + + pos += playersVarLen; + obj.players = new ServerPlayerListUpdate[playersCount]; + + for (int i = 0; i < playersCount; i++) { + obj.players[i] = ServerPlayerListUpdate.deserialize(buf, pos); + pos += ServerPlayerListUpdate.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += ServerPlayerListUpdate.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.players != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.players != null) { + if (this.players.length > 4096000) { + throw ProtocolException.arrayTooLong("Players", this.players.length, 4096000); + } + + VarInt.write(buf, this.players.length); + + for (ServerPlayerListUpdate item : this.players) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.players != null) { + size += VarInt.size(this.players.length) + this.players.length * 32; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int playersCount = VarInt.peek(buffer, pos); + if (playersCount < 0) { + return ValidationResult.error("Invalid array count for Players"); + } + + if (playersCount > 4096000) { + return ValidationResult.error("Players exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += playersCount * 32; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Players"); + } + } + + return ValidationResult.OK; + } + } + + public UpdateServerPlayerList clone() { + UpdateServerPlayerList copy = new UpdateServerPlayerList(); + copy.players = this.players != null ? Arrays.stream(this.players).map(e -> e.clone()).toArray(ServerPlayerListUpdate[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof UpdateServerPlayerList other ? Arrays.equals((Object[])this.players, (Object[])other.players) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.players); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/UpdateServerPlayerListPing.java b/src/com/hypixel/hytale/protocol/packets/interface_/UpdateServerPlayerListPing.java new file mode 100644 index 0000000..91906fa --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/UpdateServerPlayerListPing.java @@ -0,0 +1,178 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateServerPlayerListPing implements Packet { + public static final int PACKET_ID = 227; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 81920006; + @Nullable + public Map players; + + @Override + public int getId() { + return 227; + } + + public UpdateServerPlayerListPing() { + } + + public UpdateServerPlayerListPing(@Nullable Map players) { + this.players = players; + } + + public UpdateServerPlayerListPing(@Nonnull UpdateServerPlayerListPing other) { + this.players = other.players; + } + + @Nonnull + public static UpdateServerPlayerListPing deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateServerPlayerListPing obj = new UpdateServerPlayerListPing(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int playersCount = VarInt.peek(buf, pos); + if (playersCount < 0) { + throw ProtocolException.negativeLength("Players", playersCount); + } + + if (playersCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Players", playersCount, 4096000); + } + + pos += VarInt.size(playersCount); + obj.players = new HashMap<>(playersCount); + + for (int i = 0; i < playersCount; i++) { + UUID key = PacketIO.readUUID(buf, pos); + pos += 16; + int val = buf.getIntLE(pos); + pos += 4; + if (obj.players.put(key, val) != null) { + throw ProtocolException.duplicateKey("players", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 16; + pos += 4; + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.players != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.players != null) { + if (this.players.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Players", this.players.size(), 4096000); + } + + VarInt.write(buf, this.players.size()); + + for (Entry e : this.players.entrySet()) { + PacketIO.writeUUID(buf, e.getKey()); + buf.writeIntLE(e.getValue()); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.players != null) { + size += VarInt.size(this.players.size()) + this.players.size() * 20; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int playersCount = VarInt.peek(buffer, pos); + if (playersCount < 0) { + return ValidationResult.error("Invalid dictionary count for Players"); + } + + if (playersCount > 4096000) { + return ValidationResult.error("Players exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < playersCount; i++) { + pos += 16; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public UpdateServerPlayerListPing clone() { + UpdateServerPlayerListPing copy = new UpdateServerPlayerListPing(); + copy.players = this.players != null ? new HashMap<>(this.players) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof UpdateServerPlayerListPing other ? Objects.equals(this.players, other.players) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.players); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/UpdateVisibleHudComponents.java b/src/com/hypixel/hytale/protocol/packets/interface_/UpdateVisibleHudComponents.java new file mode 100644 index 0000000..501821e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/UpdateVisibleHudComponents.java @@ -0,0 +1,160 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateVisibleHudComponents implements Packet { + public static final int PACKET_ID = 230; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 4096006; + @Nullable + public HudComponent[] visibleComponents; + + @Override + public int getId() { + return 230; + } + + public UpdateVisibleHudComponents() { + } + + public UpdateVisibleHudComponents(@Nullable HudComponent[] visibleComponents) { + this.visibleComponents = visibleComponents; + } + + public UpdateVisibleHudComponents(@Nonnull UpdateVisibleHudComponents other) { + this.visibleComponents = other.visibleComponents; + } + + @Nonnull + public static UpdateVisibleHudComponents deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateVisibleHudComponents obj = new UpdateVisibleHudComponents(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int visibleComponentsCount = VarInt.peek(buf, pos); + if (visibleComponentsCount < 0) { + throw ProtocolException.negativeLength("VisibleComponents", visibleComponentsCount); + } + + if (visibleComponentsCount > 4096000) { + throw ProtocolException.arrayTooLong("VisibleComponents", visibleComponentsCount, 4096000); + } + + int visibleComponentsVarLen = VarInt.size(visibleComponentsCount); + if (pos + visibleComponentsVarLen + visibleComponentsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("VisibleComponents", pos + visibleComponentsVarLen + visibleComponentsCount * 1, buf.readableBytes()); + } + + pos += visibleComponentsVarLen; + obj.visibleComponents = new HudComponent[visibleComponentsCount]; + + for (int i = 0; i < visibleComponentsCount; i++) { + obj.visibleComponents[i] = HudComponent.fromValue(buf.getByte(pos)); + pos++; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 1; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.visibleComponents != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.visibleComponents != null) { + if (this.visibleComponents.length > 4096000) { + throw ProtocolException.arrayTooLong("VisibleComponents", this.visibleComponents.length, 4096000); + } + + VarInt.write(buf, this.visibleComponents.length); + + for (HudComponent item : this.visibleComponents) { + buf.writeByte(item.getValue()); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.visibleComponents != null) { + size += VarInt.size(this.visibleComponents.length) + this.visibleComponents.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int visibleComponentsCount = VarInt.peek(buffer, pos); + if (visibleComponentsCount < 0) { + return ValidationResult.error("Invalid array count for VisibleComponents"); + } + + if (visibleComponentsCount > 4096000) { + return ValidationResult.error("VisibleComponents exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += visibleComponentsCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading VisibleComponents"); + } + } + + return ValidationResult.OK; + } + } + + public UpdateVisibleHudComponents clone() { + UpdateVisibleHudComponents copy = new UpdateVisibleHudComponents(); + copy.visibleComponents = this.visibleComponents != null ? Arrays.copyOf(this.visibleComponents, this.visibleComponents.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof UpdateVisibleHudComponents other ? Arrays.equals((Object[])this.visibleComponents, (Object[])other.visibleComponents) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.visibleComponents); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/interface_/WorldSavingStatus.java b/src/com/hypixel/hytale/protocol/packets/interface_/WorldSavingStatus.java new file mode 100644 index 0000000..259058d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/interface_/WorldSavingStatus.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.interface_; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class WorldSavingStatus implements Packet { + public static final int PACKET_ID = 233; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean isWorldSaving; + + @Override + public int getId() { + return 233; + } + + public WorldSavingStatus() { + } + + public WorldSavingStatus(boolean isWorldSaving) { + this.isWorldSaving = isWorldSaving; + } + + public WorldSavingStatus(@Nonnull WorldSavingStatus other) { + this.isWorldSaving = other.isWorldSaving; + } + + @Nonnull + public static WorldSavingStatus deserialize(@Nonnull ByteBuf buf, int offset) { + WorldSavingStatus obj = new WorldSavingStatus(); + obj.isWorldSaving = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.isWorldSaving ? 1 : 0); + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public WorldSavingStatus clone() { + WorldSavingStatus copy = new WorldSavingStatus(); + copy.isWorldSaving = this.isWorldSaving; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof WorldSavingStatus other ? this.isWorldSaving == other.isWorldSaving : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.isWorldSaving); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/inventory/DropCreativeItem.java b/src/com/hypixel/hytale/protocol/packets/inventory/DropCreativeItem.java new file mode 100644 index 0000000..cc356ca --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/inventory/DropCreativeItem.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.protocol.packets.inventory; + +import com.hypixel.hytale.protocol.ItemQuantity; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class DropCreativeItem implements Packet { + public static final int PACKET_ID = 172; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 16384010; + @Nonnull + public ItemQuantity item = new ItemQuantity(); + + @Override + public int getId() { + return 172; + } + + public DropCreativeItem() { + } + + public DropCreativeItem(@Nonnull ItemQuantity item) { + this.item = item; + } + + public DropCreativeItem(@Nonnull DropCreativeItem other) { + this.item = other.item; + } + + @Nonnull + public static DropCreativeItem deserialize(@Nonnull ByteBuf buf, int offset) { + DropCreativeItem obj = new DropCreativeItem(); + int pos = offset + 0; + obj.item = ItemQuantity.deserialize(buf, pos); + pos += ItemQuantity.computeBytesConsumed(buf, pos); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + int pos = offset + 0; + pos += ItemQuantity.computeBytesConsumed(buf, pos); + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + this.item.serialize(buf); + } + + @Override + public int computeSize() { + int size = 0; + return size + this.item.computeSize(); + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 0) { + return ValidationResult.error("Buffer too small: expected at least 0 bytes"); + } else { + int pos = offset + 0; + ValidationResult itemResult = ItemQuantity.validateStructure(buffer, pos); + if (!itemResult.isValid()) { + return ValidationResult.error("Invalid Item: " + itemResult.error()); + } else { + pos += ItemQuantity.computeBytesConsumed(buffer, pos); + return ValidationResult.OK; + } + } + } + + public DropCreativeItem clone() { + DropCreativeItem copy = new DropCreativeItem(); + copy.item = this.item.clone(); + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof DropCreativeItem other ? Objects.equals(this.item, other.item) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.item); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/inventory/DropItemStack.java b/src/com/hypixel/hytale/protocol/packets/inventory/DropItemStack.java new file mode 100644 index 0000000..dcb5a14 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/inventory/DropItemStack.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.protocol.packets.inventory; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class DropItemStack implements Packet { + public static final int PACKET_ID = 174; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 12; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 12; + public static final int MAX_SIZE = 12; + public int inventorySectionId; + public int slotId; + public int quantity; + + @Override + public int getId() { + return 174; + } + + public DropItemStack() { + } + + public DropItemStack(int inventorySectionId, int slotId, int quantity) { + this.inventorySectionId = inventorySectionId; + this.slotId = slotId; + this.quantity = quantity; + } + + public DropItemStack(@Nonnull DropItemStack other) { + this.inventorySectionId = other.inventorySectionId; + this.slotId = other.slotId; + this.quantity = other.quantity; + } + + @Nonnull + public static DropItemStack deserialize(@Nonnull ByteBuf buf, int offset) { + DropItemStack obj = new DropItemStack(); + obj.inventorySectionId = buf.getIntLE(offset + 0); + obj.slotId = buf.getIntLE(offset + 4); + obj.quantity = buf.getIntLE(offset + 8); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 12; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.inventorySectionId); + buf.writeIntLE(this.slotId); + buf.writeIntLE(this.quantity); + } + + @Override + public int computeSize() { + return 12; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 12 ? ValidationResult.error("Buffer too small: expected at least 12 bytes") : ValidationResult.OK; + } + + public DropItemStack clone() { + DropItemStack copy = new DropItemStack(); + copy.inventorySectionId = this.inventorySectionId; + copy.slotId = this.slotId; + copy.quantity = this.quantity; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof DropItemStack other) + ? false + : this.inventorySectionId == other.inventorySectionId && this.slotId == other.slotId && this.quantity == other.quantity; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.inventorySectionId, this.slotId, this.quantity); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/inventory/InventoryAction.java b/src/com/hypixel/hytale/protocol/packets/inventory/InventoryAction.java new file mode 100644 index 0000000..c95b668 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/inventory/InventoryAction.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.protocol.packets.inventory; + +import com.hypixel.hytale.protocol.InventoryActionType; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class InventoryAction implements Packet { + public static final int PACKET_ID = 179; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 6; + public static final int MAX_SIZE = 6; + public int inventorySectionId; + @Nonnull + public InventoryActionType inventoryActionType = InventoryActionType.TakeAll; + public byte actionData; + + @Override + public int getId() { + return 179; + } + + public InventoryAction() { + } + + public InventoryAction(int inventorySectionId, @Nonnull InventoryActionType inventoryActionType, byte actionData) { + this.inventorySectionId = inventorySectionId; + this.inventoryActionType = inventoryActionType; + this.actionData = actionData; + } + + public InventoryAction(@Nonnull InventoryAction other) { + this.inventorySectionId = other.inventorySectionId; + this.inventoryActionType = other.inventoryActionType; + this.actionData = other.actionData; + } + + @Nonnull + public static InventoryAction deserialize(@Nonnull ByteBuf buf, int offset) { + InventoryAction obj = new InventoryAction(); + obj.inventorySectionId = buf.getIntLE(offset + 0); + obj.inventoryActionType = InventoryActionType.fromValue(buf.getByte(offset + 4)); + obj.actionData = buf.getByte(offset + 5); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 6; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.inventorySectionId); + buf.writeByte(this.inventoryActionType.getValue()); + buf.writeByte(this.actionData); + } + + @Override + public int computeSize() { + return 6; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 6 ? ValidationResult.error("Buffer too small: expected at least 6 bytes") : ValidationResult.OK; + } + + public InventoryAction clone() { + InventoryAction copy = new InventoryAction(); + copy.inventorySectionId = this.inventorySectionId; + copy.inventoryActionType = this.inventoryActionType; + copy.actionData = this.actionData; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof InventoryAction other) + ? false + : this.inventorySectionId == other.inventorySectionId + && Objects.equals(this.inventoryActionType, other.inventoryActionType) + && this.actionData == other.actionData; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.inventorySectionId, this.inventoryActionType, this.actionData); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/inventory/MoveItemStack.java b/src/com/hypixel/hytale/protocol/packets/inventory/MoveItemStack.java new file mode 100644 index 0000000..468418f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/inventory/MoveItemStack.java @@ -0,0 +1,109 @@ +package com.hypixel.hytale.protocol.packets.inventory; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class MoveItemStack implements Packet { + public static final int PACKET_ID = 175; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 20; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 20; + public static final int MAX_SIZE = 20; + public int fromSectionId; + public int fromSlotId; + public int quantity; + public int toSectionId; + public int toSlotId; + + @Override + public int getId() { + return 175; + } + + public MoveItemStack() { + } + + public MoveItemStack(int fromSectionId, int fromSlotId, int quantity, int toSectionId, int toSlotId) { + this.fromSectionId = fromSectionId; + this.fromSlotId = fromSlotId; + this.quantity = quantity; + this.toSectionId = toSectionId; + this.toSlotId = toSlotId; + } + + public MoveItemStack(@Nonnull MoveItemStack other) { + this.fromSectionId = other.fromSectionId; + this.fromSlotId = other.fromSlotId; + this.quantity = other.quantity; + this.toSectionId = other.toSectionId; + this.toSlotId = other.toSlotId; + } + + @Nonnull + public static MoveItemStack deserialize(@Nonnull ByteBuf buf, int offset) { + MoveItemStack obj = new MoveItemStack(); + obj.fromSectionId = buf.getIntLE(offset + 0); + obj.fromSlotId = buf.getIntLE(offset + 4); + obj.quantity = buf.getIntLE(offset + 8); + obj.toSectionId = buf.getIntLE(offset + 12); + obj.toSlotId = buf.getIntLE(offset + 16); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 20; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.fromSectionId); + buf.writeIntLE(this.fromSlotId); + buf.writeIntLE(this.quantity); + buf.writeIntLE(this.toSectionId); + buf.writeIntLE(this.toSlotId); + } + + @Override + public int computeSize() { + return 20; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 20 ? ValidationResult.error("Buffer too small: expected at least 20 bytes") : ValidationResult.OK; + } + + public MoveItemStack clone() { + MoveItemStack copy = new MoveItemStack(); + copy.fromSectionId = this.fromSectionId; + copy.fromSlotId = this.fromSlotId; + copy.quantity = this.quantity; + copy.toSectionId = this.toSectionId; + copy.toSlotId = this.toSlotId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof MoveItemStack other) + ? false + : this.fromSectionId == other.fromSectionId + && this.fromSlotId == other.fromSlotId + && this.quantity == other.quantity + && this.toSectionId == other.toSectionId + && this.toSlotId == other.toSlotId; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.fromSectionId, this.fromSlotId, this.quantity, this.toSectionId, this.toSlotId); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/inventory/SetActiveSlot.java b/src/com/hypixel/hytale/protocol/packets/inventory/SetActiveSlot.java new file mode 100644 index 0000000..e78fa43 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/inventory/SetActiveSlot.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.protocol.packets.inventory; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SetActiveSlot implements Packet { + public static final int PACKET_ID = 177; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public int inventorySectionId; + public int activeSlot; + + @Override + public int getId() { + return 177; + } + + public SetActiveSlot() { + } + + public SetActiveSlot(int inventorySectionId, int activeSlot) { + this.inventorySectionId = inventorySectionId; + this.activeSlot = activeSlot; + } + + public SetActiveSlot(@Nonnull SetActiveSlot other) { + this.inventorySectionId = other.inventorySectionId; + this.activeSlot = other.activeSlot; + } + + @Nonnull + public static SetActiveSlot deserialize(@Nonnull ByteBuf buf, int offset) { + SetActiveSlot obj = new SetActiveSlot(); + obj.inventorySectionId = buf.getIntLE(offset + 0); + obj.activeSlot = buf.getIntLE(offset + 4); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.inventorySectionId); + buf.writeIntLE(this.activeSlot); + } + + @Override + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public SetActiveSlot clone() { + SetActiveSlot copy = new SetActiveSlot(); + copy.inventorySectionId = this.inventorySectionId; + copy.activeSlot = this.activeSlot; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SetActiveSlot other) ? false : this.inventorySectionId == other.inventorySectionId && this.activeSlot == other.activeSlot; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.inventorySectionId, this.activeSlot); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/inventory/SetCreativeItem.java b/src/com/hypixel/hytale/protocol/packets/inventory/SetCreativeItem.java new file mode 100644 index 0000000..214a850 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/inventory/SetCreativeItem.java @@ -0,0 +1,120 @@ +package com.hypixel.hytale.protocol.packets.inventory; + +import com.hypixel.hytale.protocol.ItemQuantity; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SetCreativeItem implements Packet { + public static final int PACKET_ID = 171; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 16384019; + public int inventorySectionId; + public int slotId; + @Nonnull + public ItemQuantity item = new ItemQuantity(); + public boolean override; + + @Override + public int getId() { + return 171; + } + + public SetCreativeItem() { + } + + public SetCreativeItem(int inventorySectionId, int slotId, @Nonnull ItemQuantity item, boolean override) { + this.inventorySectionId = inventorySectionId; + this.slotId = slotId; + this.item = item; + this.override = override; + } + + public SetCreativeItem(@Nonnull SetCreativeItem other) { + this.inventorySectionId = other.inventorySectionId; + this.slotId = other.slotId; + this.item = other.item; + this.override = other.override; + } + + @Nonnull + public static SetCreativeItem deserialize(@Nonnull ByteBuf buf, int offset) { + SetCreativeItem obj = new SetCreativeItem(); + obj.inventorySectionId = buf.getIntLE(offset + 0); + obj.slotId = buf.getIntLE(offset + 4); + obj.override = buf.getByte(offset + 8) != 0; + int pos = offset + 9; + obj.item = ItemQuantity.deserialize(buf, pos); + pos += ItemQuantity.computeBytesConsumed(buf, pos); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + int pos = offset + 9; + pos += ItemQuantity.computeBytesConsumed(buf, pos); + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.inventorySectionId); + buf.writeIntLE(this.slotId); + buf.writeByte(this.override ? 1 : 0); + this.item.serialize(buf); + } + + @Override + public int computeSize() { + int size = 9; + return size + this.item.computeSize(); + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + int pos = offset + 9; + ValidationResult itemResult = ItemQuantity.validateStructure(buffer, pos); + if (!itemResult.isValid()) { + return ValidationResult.error("Invalid Item: " + itemResult.error()); + } else { + pos += ItemQuantity.computeBytesConsumed(buffer, pos); + return ValidationResult.OK; + } + } + } + + public SetCreativeItem clone() { + SetCreativeItem copy = new SetCreativeItem(); + copy.inventorySectionId = this.inventorySectionId; + copy.slotId = this.slotId; + copy.item = this.item.clone(); + copy.override = this.override; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SetCreativeItem other) + ? false + : this.inventorySectionId == other.inventorySectionId + && this.slotId == other.slotId + && Objects.equals(this.item, other.item) + && this.override == other.override; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.inventorySectionId, this.slotId, this.item, this.override); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/inventory/SmartGiveCreativeItem.java b/src/com/hypixel/hytale/protocol/packets/inventory/SmartGiveCreativeItem.java new file mode 100644 index 0000000..b3e9584 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/inventory/SmartGiveCreativeItem.java @@ -0,0 +1,105 @@ +package com.hypixel.hytale.protocol.packets.inventory; + +import com.hypixel.hytale.protocol.ItemQuantity; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.SmartMoveType; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SmartGiveCreativeItem implements Packet { + public static final int PACKET_ID = 173; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 16384011; + @Nonnull + public ItemQuantity item = new ItemQuantity(); + @Nonnull + public SmartMoveType moveType = SmartMoveType.EquipOrMergeStack; + + @Override + public int getId() { + return 173; + } + + public SmartGiveCreativeItem() { + } + + public SmartGiveCreativeItem(@Nonnull ItemQuantity item, @Nonnull SmartMoveType moveType) { + this.item = item; + this.moveType = moveType; + } + + public SmartGiveCreativeItem(@Nonnull SmartGiveCreativeItem other) { + this.item = other.item; + this.moveType = other.moveType; + } + + @Nonnull + public static SmartGiveCreativeItem deserialize(@Nonnull ByteBuf buf, int offset) { + SmartGiveCreativeItem obj = new SmartGiveCreativeItem(); + obj.moveType = SmartMoveType.fromValue(buf.getByte(offset + 0)); + int pos = offset + 1; + obj.item = ItemQuantity.deserialize(buf, pos); + pos += ItemQuantity.computeBytesConsumed(buf, pos); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + int pos = offset + 1; + pos += ItemQuantity.computeBytesConsumed(buf, pos); + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.moveType.getValue()); + this.item.serialize(buf); + } + + @Override + public int computeSize() { + int size = 1; + return size + this.item.computeSize(); + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + int pos = offset + 1; + ValidationResult itemResult = ItemQuantity.validateStructure(buffer, pos); + if (!itemResult.isValid()) { + return ValidationResult.error("Invalid Item: " + itemResult.error()); + } else { + pos += ItemQuantity.computeBytesConsumed(buffer, pos); + return ValidationResult.OK; + } + } + } + + public SmartGiveCreativeItem clone() { + SmartGiveCreativeItem copy = new SmartGiveCreativeItem(); + copy.item = this.item.clone(); + copy.moveType = this.moveType; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SmartGiveCreativeItem other) ? false : Objects.equals(this.item, other.item) && Objects.equals(this.moveType, other.moveType); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.item, this.moveType); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/inventory/SmartMoveItemStack.java b/src/com/hypixel/hytale/protocol/packets/inventory/SmartMoveItemStack.java new file mode 100644 index 0000000..b20b10e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/inventory/SmartMoveItemStack.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.protocol.packets.inventory; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.SmartMoveType; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SmartMoveItemStack implements Packet { + public static final int PACKET_ID = 176; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 13; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 13; + public int fromSectionId; + public int fromSlotId; + public int quantity; + @Nonnull + public SmartMoveType moveType = SmartMoveType.EquipOrMergeStack; + + @Override + public int getId() { + return 176; + } + + public SmartMoveItemStack() { + } + + public SmartMoveItemStack(int fromSectionId, int fromSlotId, int quantity, @Nonnull SmartMoveType moveType) { + this.fromSectionId = fromSectionId; + this.fromSlotId = fromSlotId; + this.quantity = quantity; + this.moveType = moveType; + } + + public SmartMoveItemStack(@Nonnull SmartMoveItemStack other) { + this.fromSectionId = other.fromSectionId; + this.fromSlotId = other.fromSlotId; + this.quantity = other.quantity; + this.moveType = other.moveType; + } + + @Nonnull + public static SmartMoveItemStack deserialize(@Nonnull ByteBuf buf, int offset) { + SmartMoveItemStack obj = new SmartMoveItemStack(); + obj.fromSectionId = buf.getIntLE(offset + 0); + obj.fromSlotId = buf.getIntLE(offset + 4); + obj.quantity = buf.getIntLE(offset + 8); + obj.moveType = SmartMoveType.fromValue(buf.getByte(offset + 12)); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 13; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.fromSectionId); + buf.writeIntLE(this.fromSlotId); + buf.writeIntLE(this.quantity); + buf.writeByte(this.moveType.getValue()); + } + + @Override + public int computeSize() { + return 13; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 13 ? ValidationResult.error("Buffer too small: expected at least 13 bytes") : ValidationResult.OK; + } + + public SmartMoveItemStack clone() { + SmartMoveItemStack copy = new SmartMoveItemStack(); + copy.fromSectionId = this.fromSectionId; + copy.fromSlotId = this.fromSlotId; + copy.quantity = this.quantity; + copy.moveType = this.moveType; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SmartMoveItemStack other) + ? false + : this.fromSectionId == other.fromSectionId + && this.fromSlotId == other.fromSlotId + && this.quantity == other.quantity + && Objects.equals(this.moveType, other.moveType); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.fromSectionId, this.fromSlotId, this.quantity, this.moveType); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/inventory/SwitchHotbarBlockSet.java b/src/com/hypixel/hytale/protocol/packets/inventory/SwitchHotbarBlockSet.java new file mode 100644 index 0000000..ef36b9d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/inventory/SwitchHotbarBlockSet.java @@ -0,0 +1,143 @@ +package com.hypixel.hytale.protocol.packets.inventory; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SwitchHotbarBlockSet implements Packet { + public static final int PACKET_ID = 178; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 16384006; + @Nullable + public String itemId; + + @Override + public int getId() { + return 178; + } + + public SwitchHotbarBlockSet() { + } + + public SwitchHotbarBlockSet(@Nullable String itemId) { + this.itemId = itemId; + } + + public SwitchHotbarBlockSet(@Nonnull SwitchHotbarBlockSet other) { + this.itemId = other.itemId; + } + + @Nonnull + public static SwitchHotbarBlockSet deserialize(@Nonnull ByteBuf buf, int offset) { + SwitchHotbarBlockSet obj = new SwitchHotbarBlockSet(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int itemIdLen = VarInt.peek(buf, pos); + if (itemIdLen < 0) { + throw ProtocolException.negativeLength("ItemId", itemIdLen); + } + + if (itemIdLen > 4096000) { + throw ProtocolException.stringTooLong("ItemId", itemIdLen, 4096000); + } + + int itemIdVarLen = VarInt.length(buf, pos); + obj.itemId = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += itemIdVarLen + itemIdLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.itemId != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.itemId != null) { + PacketIO.writeVarString(buf, this.itemId, 4096000); + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.itemId != null) { + size += PacketIO.stringSize(this.itemId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int itemIdLen = VarInt.peek(buffer, pos); + if (itemIdLen < 0) { + return ValidationResult.error("Invalid string length for ItemId"); + } + + if (itemIdLen > 4096000) { + return ValidationResult.error("ItemId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += itemIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ItemId"); + } + } + + return ValidationResult.OK; + } + } + + public SwitchHotbarBlockSet clone() { + SwitchHotbarBlockSet copy = new SwitchHotbarBlockSet(); + copy.itemId = this.itemId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof SwitchHotbarBlockSet other ? Objects.equals(this.itemId, other.itemId) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.itemId); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/inventory/UpdatePlayerInventory.java b/src/com/hypixel/hytale/protocol/packets/inventory/UpdatePlayerInventory.java new file mode 100644 index 0000000..c7fa33a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/inventory/UpdatePlayerInventory.java @@ -0,0 +1,498 @@ +package com.hypixel.hytale.protocol.packets.inventory; + +import com.hypixel.hytale.protocol.InventorySection; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.SortType; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdatePlayerInventory implements Packet { + public static final int PACKET_ID = 170; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 7; + public static final int VARIABLE_BLOCK_START = 30; + public static final int MAX_SIZE = 1677721600; + @Nullable + public InventorySection storage; + @Nullable + public InventorySection armor; + @Nullable + public InventorySection hotbar; + @Nullable + public InventorySection utility; + @Nullable + public InventorySection builderMaterial; + @Nullable + public InventorySection tools; + @Nullable + public InventorySection backpack; + @Nonnull + public SortType sortType = SortType.Name; + + @Override + public int getId() { + return 170; + } + + public UpdatePlayerInventory() { + } + + public UpdatePlayerInventory( + @Nullable InventorySection storage, + @Nullable InventorySection armor, + @Nullable InventorySection hotbar, + @Nullable InventorySection utility, + @Nullable InventorySection builderMaterial, + @Nullable InventorySection tools, + @Nullable InventorySection backpack, + @Nonnull SortType sortType + ) { + this.storage = storage; + this.armor = armor; + this.hotbar = hotbar; + this.utility = utility; + this.builderMaterial = builderMaterial; + this.tools = tools; + this.backpack = backpack; + this.sortType = sortType; + } + + public UpdatePlayerInventory(@Nonnull UpdatePlayerInventory other) { + this.storage = other.storage; + this.armor = other.armor; + this.hotbar = other.hotbar; + this.utility = other.utility; + this.builderMaterial = other.builderMaterial; + this.tools = other.tools; + this.backpack = other.backpack; + this.sortType = other.sortType; + } + + @Nonnull + public static UpdatePlayerInventory deserialize(@Nonnull ByteBuf buf, int offset) { + UpdatePlayerInventory obj = new UpdatePlayerInventory(); + byte nullBits = buf.getByte(offset); + obj.sortType = SortType.fromValue(buf.getByte(offset + 1)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 30 + buf.getIntLE(offset + 2); + obj.storage = InventorySection.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 30 + buf.getIntLE(offset + 6); + obj.armor = InventorySection.deserialize(buf, varPos1); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 30 + buf.getIntLE(offset + 10); + obj.hotbar = InventorySection.deserialize(buf, varPos2); + } + + if ((nullBits & 8) != 0) { + int varPos3 = offset + 30 + buf.getIntLE(offset + 14); + obj.utility = InventorySection.deserialize(buf, varPos3); + } + + if ((nullBits & 16) != 0) { + int varPos4 = offset + 30 + buf.getIntLE(offset + 18); + obj.builderMaterial = InventorySection.deserialize(buf, varPos4); + } + + if ((nullBits & 32) != 0) { + int varPos5 = offset + 30 + buf.getIntLE(offset + 22); + obj.tools = InventorySection.deserialize(buf, varPos5); + } + + if ((nullBits & 64) != 0) { + int varPos6 = offset + 30 + buf.getIntLE(offset + 26); + obj.backpack = InventorySection.deserialize(buf, varPos6); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 30; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 2); + int pos0 = offset + 30 + fieldOffset0; + pos0 += InventorySection.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 6); + int pos1 = offset + 30 + fieldOffset1; + pos1 += InventorySection.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 10); + int pos2 = offset + 30 + fieldOffset2; + pos2 += InventorySection.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 14); + int pos3 = offset + 30 + fieldOffset3; + pos3 += InventorySection.computeBytesConsumed(buf, pos3); + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset4 = buf.getIntLE(offset + 18); + int pos4 = offset + 30 + fieldOffset4; + pos4 += InventorySection.computeBytesConsumed(buf, pos4); + if (pos4 - offset > maxEnd) { + maxEnd = pos4 - offset; + } + } + + if ((nullBits & 32) != 0) { + int fieldOffset5 = buf.getIntLE(offset + 22); + int pos5 = offset + 30 + fieldOffset5; + pos5 += InventorySection.computeBytesConsumed(buf, pos5); + if (pos5 - offset > maxEnd) { + maxEnd = pos5 - offset; + } + } + + if ((nullBits & 64) != 0) { + int fieldOffset6 = buf.getIntLE(offset + 26); + int pos6 = offset + 30 + fieldOffset6; + pos6 += InventorySection.computeBytesConsumed(buf, pos6); + if (pos6 - offset > maxEnd) { + maxEnd = pos6 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.storage != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.armor != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.hotbar != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.utility != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.builderMaterial != null) { + nullBits = (byte)(nullBits | 16); + } + + if (this.tools != null) { + nullBits = (byte)(nullBits | 32); + } + + if (this.backpack != null) { + nullBits = (byte)(nullBits | 64); + } + + buf.writeByte(nullBits); + buf.writeByte(this.sortType.getValue()); + int storageOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int armorOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int hotbarOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int utilityOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int builderMaterialOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int toolsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int backpackOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.storage != null) { + buf.setIntLE(storageOffsetSlot, buf.writerIndex() - varBlockStart); + this.storage.serialize(buf); + } else { + buf.setIntLE(storageOffsetSlot, -1); + } + + if (this.armor != null) { + buf.setIntLE(armorOffsetSlot, buf.writerIndex() - varBlockStart); + this.armor.serialize(buf); + } else { + buf.setIntLE(armorOffsetSlot, -1); + } + + if (this.hotbar != null) { + buf.setIntLE(hotbarOffsetSlot, buf.writerIndex() - varBlockStart); + this.hotbar.serialize(buf); + } else { + buf.setIntLE(hotbarOffsetSlot, -1); + } + + if (this.utility != null) { + buf.setIntLE(utilityOffsetSlot, buf.writerIndex() - varBlockStart); + this.utility.serialize(buf); + } else { + buf.setIntLE(utilityOffsetSlot, -1); + } + + if (this.builderMaterial != null) { + buf.setIntLE(builderMaterialOffsetSlot, buf.writerIndex() - varBlockStart); + this.builderMaterial.serialize(buf); + } else { + buf.setIntLE(builderMaterialOffsetSlot, -1); + } + + if (this.tools != null) { + buf.setIntLE(toolsOffsetSlot, buf.writerIndex() - varBlockStart); + this.tools.serialize(buf); + } else { + buf.setIntLE(toolsOffsetSlot, -1); + } + + if (this.backpack != null) { + buf.setIntLE(backpackOffsetSlot, buf.writerIndex() - varBlockStart); + this.backpack.serialize(buf); + } else { + buf.setIntLE(backpackOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 30; + if (this.storage != null) { + size += this.storage.computeSize(); + } + + if (this.armor != null) { + size += this.armor.computeSize(); + } + + if (this.hotbar != null) { + size += this.hotbar.computeSize(); + } + + if (this.utility != null) { + size += this.utility.computeSize(); + } + + if (this.builderMaterial != null) { + size += this.builderMaterial.computeSize(); + } + + if (this.tools != null) { + size += this.tools.computeSize(); + } + + if (this.backpack != null) { + size += this.backpack.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 30) { + return ValidationResult.error("Buffer too small: expected at least 30 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int storageOffset = buffer.getIntLE(offset + 2); + if (storageOffset < 0) { + return ValidationResult.error("Invalid offset for Storage"); + } + + int pos = offset + 30 + storageOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Storage"); + } + + ValidationResult storageResult = InventorySection.validateStructure(buffer, pos); + if (!storageResult.isValid()) { + return ValidationResult.error("Invalid Storage: " + storageResult.error()); + } + + pos += InventorySection.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int armorOffset = buffer.getIntLE(offset + 6); + if (armorOffset < 0) { + return ValidationResult.error("Invalid offset for Armor"); + } + + int posx = offset + 30 + armorOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Armor"); + } + + ValidationResult armorResult = InventorySection.validateStructure(buffer, posx); + if (!armorResult.isValid()) { + return ValidationResult.error("Invalid Armor: " + armorResult.error()); + } + + posx += InventorySection.computeBytesConsumed(buffer, posx); + } + + if ((nullBits & 4) != 0) { + int hotbarOffset = buffer.getIntLE(offset + 10); + if (hotbarOffset < 0) { + return ValidationResult.error("Invalid offset for Hotbar"); + } + + int posxx = offset + 30 + hotbarOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Hotbar"); + } + + ValidationResult hotbarResult = InventorySection.validateStructure(buffer, posxx); + if (!hotbarResult.isValid()) { + return ValidationResult.error("Invalid Hotbar: " + hotbarResult.error()); + } + + posxx += InventorySection.computeBytesConsumed(buffer, posxx); + } + + if ((nullBits & 8) != 0) { + int utilityOffset = buffer.getIntLE(offset + 14); + if (utilityOffset < 0) { + return ValidationResult.error("Invalid offset for Utility"); + } + + int posxxx = offset + 30 + utilityOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Utility"); + } + + ValidationResult utilityResult = InventorySection.validateStructure(buffer, posxxx); + if (!utilityResult.isValid()) { + return ValidationResult.error("Invalid Utility: " + utilityResult.error()); + } + + posxxx += InventorySection.computeBytesConsumed(buffer, posxxx); + } + + if ((nullBits & 16) != 0) { + int builderMaterialOffset = buffer.getIntLE(offset + 18); + if (builderMaterialOffset < 0) { + return ValidationResult.error("Invalid offset for BuilderMaterial"); + } + + int posxxxx = offset + 30 + builderMaterialOffset; + if (posxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BuilderMaterial"); + } + + ValidationResult builderMaterialResult = InventorySection.validateStructure(buffer, posxxxx); + if (!builderMaterialResult.isValid()) { + return ValidationResult.error("Invalid BuilderMaterial: " + builderMaterialResult.error()); + } + + posxxxx += InventorySection.computeBytesConsumed(buffer, posxxxx); + } + + if ((nullBits & 32) != 0) { + int toolsOffset = buffer.getIntLE(offset + 22); + if (toolsOffset < 0) { + return ValidationResult.error("Invalid offset for Tools"); + } + + int posxxxxx = offset + 30 + toolsOffset; + if (posxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Tools"); + } + + ValidationResult toolsResult = InventorySection.validateStructure(buffer, posxxxxx); + if (!toolsResult.isValid()) { + return ValidationResult.error("Invalid Tools: " + toolsResult.error()); + } + + posxxxxx += InventorySection.computeBytesConsumed(buffer, posxxxxx); + } + + if ((nullBits & 64) != 0) { + int backpackOffset = buffer.getIntLE(offset + 26); + if (backpackOffset < 0) { + return ValidationResult.error("Invalid offset for Backpack"); + } + + int posxxxxxx = offset + 30 + backpackOffset; + if (posxxxxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Backpack"); + } + + ValidationResult backpackResult = InventorySection.validateStructure(buffer, posxxxxxx); + if (!backpackResult.isValid()) { + return ValidationResult.error("Invalid Backpack: " + backpackResult.error()); + } + + posxxxxxx += InventorySection.computeBytesConsumed(buffer, posxxxxxx); + } + + return ValidationResult.OK; + } + } + + public UpdatePlayerInventory clone() { + UpdatePlayerInventory copy = new UpdatePlayerInventory(); + copy.storage = this.storage != null ? this.storage.clone() : null; + copy.armor = this.armor != null ? this.armor.clone() : null; + copy.hotbar = this.hotbar != null ? this.hotbar.clone() : null; + copy.utility = this.utility != null ? this.utility.clone() : null; + copy.builderMaterial = this.builderMaterial != null ? this.builderMaterial.clone() : null; + copy.tools = this.tools != null ? this.tools.clone() : null; + copy.backpack = this.backpack != null ? this.backpack.clone() : null; + copy.sortType = this.sortType; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdatePlayerInventory other) + ? false + : Objects.equals(this.storage, other.storage) + && Objects.equals(this.armor, other.armor) + && Objects.equals(this.hotbar, other.hotbar) + && Objects.equals(this.utility, other.utility) + && Objects.equals(this.builderMaterial, other.builderMaterial) + && Objects.equals(this.tools, other.tools) + && Objects.equals(this.backpack, other.backpack) + && Objects.equals(this.sortType, other.sortType); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.storage, this.armor, this.hotbar, this.utility, this.builderMaterial, this.tools, this.backpack, this.sortType); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/machinima/RequestMachinimaActorModel.java b/src/com/hypixel/hytale/protocol/packets/machinima/RequestMachinimaActorModel.java new file mode 100644 index 0000000..2214eb0 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/machinima/RequestMachinimaActorModel.java @@ -0,0 +1,310 @@ +package com.hypixel.hytale.protocol.packets.machinima; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RequestMachinimaActorModel implements Packet { + public static final int PACKET_ID = 260; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 49152028; + @Nullable + public String modelId; + @Nullable + public String sceneName; + @Nullable + public String actorName; + + @Override + public int getId() { + return 260; + } + + public RequestMachinimaActorModel() { + } + + public RequestMachinimaActorModel(@Nullable String modelId, @Nullable String sceneName, @Nullable String actorName) { + this.modelId = modelId; + this.sceneName = sceneName; + this.actorName = actorName; + } + + public RequestMachinimaActorModel(@Nonnull RequestMachinimaActorModel other) { + this.modelId = other.modelId; + this.sceneName = other.sceneName; + this.actorName = other.actorName; + } + + @Nonnull + public static RequestMachinimaActorModel deserialize(@Nonnull ByteBuf buf, int offset) { + RequestMachinimaActorModel obj = new RequestMachinimaActorModel(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 1); + int modelIdLen = VarInt.peek(buf, varPos0); + if (modelIdLen < 0) { + throw ProtocolException.negativeLength("ModelId", modelIdLen); + } + + if (modelIdLen > 4096000) { + throw ProtocolException.stringTooLong("ModelId", modelIdLen, 4096000); + } + + obj.modelId = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 5); + int sceneNameLen = VarInt.peek(buf, varPos1); + if (sceneNameLen < 0) { + throw ProtocolException.negativeLength("SceneName", sceneNameLen); + } + + if (sceneNameLen > 4096000) { + throw ProtocolException.stringTooLong("SceneName", sceneNameLen, 4096000); + } + + obj.sceneName = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 13 + buf.getIntLE(offset + 9); + int actorNameLen = VarInt.peek(buf, varPos2); + if (actorNameLen < 0) { + throw ProtocolException.negativeLength("ActorName", actorNameLen); + } + + if (actorNameLen > 4096000) { + throw ProtocolException.stringTooLong("ActorName", actorNameLen, 4096000); + } + + obj.actorName = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 13 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 13 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 9); + int pos2 = offset + 13 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.modelId != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.sceneName != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.actorName != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + int modelIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int sceneNameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int actorNameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.modelId != null) { + buf.setIntLE(modelIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.modelId, 4096000); + } else { + buf.setIntLE(modelIdOffsetSlot, -1); + } + + if (this.sceneName != null) { + buf.setIntLE(sceneNameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.sceneName, 4096000); + } else { + buf.setIntLE(sceneNameOffsetSlot, -1); + } + + if (this.actorName != null) { + buf.setIntLE(actorNameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.actorName, 4096000); + } else { + buf.setIntLE(actorNameOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 13; + if (this.modelId != null) { + size += PacketIO.stringSize(this.modelId); + } + + if (this.sceneName != null) { + size += PacketIO.stringSize(this.sceneName); + } + + if (this.actorName != null) { + size += PacketIO.stringSize(this.actorName); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int modelIdOffset = buffer.getIntLE(offset + 1); + if (modelIdOffset < 0) { + return ValidationResult.error("Invalid offset for ModelId"); + } + + int pos = offset + 13 + modelIdOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ModelId"); + } + + int modelIdLen = VarInt.peek(buffer, pos); + if (modelIdLen < 0) { + return ValidationResult.error("Invalid string length for ModelId"); + } + + if (modelIdLen > 4096000) { + return ValidationResult.error("ModelId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += modelIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ModelId"); + } + } + + if ((nullBits & 2) != 0) { + int sceneNameOffset = buffer.getIntLE(offset + 5); + if (sceneNameOffset < 0) { + return ValidationResult.error("Invalid offset for SceneName"); + } + + int posx = offset + 13 + sceneNameOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SceneName"); + } + + int sceneNameLen = VarInt.peek(buffer, posx); + if (sceneNameLen < 0) { + return ValidationResult.error("Invalid string length for SceneName"); + } + + if (sceneNameLen > 4096000) { + return ValidationResult.error("SceneName exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += sceneNameLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading SceneName"); + } + } + + if ((nullBits & 4) != 0) { + int actorNameOffset = buffer.getIntLE(offset + 9); + if (actorNameOffset < 0) { + return ValidationResult.error("Invalid offset for ActorName"); + } + + int posxx = offset + 13 + actorNameOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ActorName"); + } + + int actorNameLen = VarInt.peek(buffer, posxx); + if (actorNameLen < 0) { + return ValidationResult.error("Invalid string length for ActorName"); + } + + if (actorNameLen > 4096000) { + return ValidationResult.error("ActorName exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += actorNameLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ActorName"); + } + } + + return ValidationResult.OK; + } + } + + public RequestMachinimaActorModel clone() { + RequestMachinimaActorModel copy = new RequestMachinimaActorModel(); + copy.modelId = this.modelId; + copy.sceneName = this.sceneName; + copy.actorName = this.actorName; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof RequestMachinimaActorModel other) + ? false + : Objects.equals(this.modelId, other.modelId) && Objects.equals(this.sceneName, other.sceneName) && Objects.equals(this.actorName, other.actorName); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.modelId, this.sceneName, this.actorName); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/machinima/SceneUpdateType.java b/src/com/hypixel/hytale/protocol/packets/machinima/SceneUpdateType.java new file mode 100644 index 0000000..2a81eb3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/machinima/SceneUpdateType.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.protocol.packets.machinima; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum SceneUpdateType { + Update(0), + Play(1), + Stop(2), + Frame(3), + Save(4); + + public static final SceneUpdateType[] VALUES = values(); + private final int value; + + private SceneUpdateType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static SceneUpdateType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("SceneUpdateType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/machinima/SetMachinimaActorModel.java b/src/com/hypixel/hytale/protocol/packets/machinima/SetMachinimaActorModel.java new file mode 100644 index 0000000..6c768a4 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/machinima/SetMachinimaActorModel.java @@ -0,0 +1,293 @@ +package com.hypixel.hytale.protocol.packets.machinima; + +import com.hypixel.hytale.protocol.Model; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SetMachinimaActorModel implements Packet { + public static final int PACKET_ID = 261; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 1677721600; + @Nullable + public Model model; + @Nullable + public String sceneName; + @Nullable + public String actorName; + + @Override + public int getId() { + return 261; + } + + public SetMachinimaActorModel() { + } + + public SetMachinimaActorModel(@Nullable Model model, @Nullable String sceneName, @Nullable String actorName) { + this.model = model; + this.sceneName = sceneName; + this.actorName = actorName; + } + + public SetMachinimaActorModel(@Nonnull SetMachinimaActorModel other) { + this.model = other.model; + this.sceneName = other.sceneName; + this.actorName = other.actorName; + } + + @Nonnull + public static SetMachinimaActorModel deserialize(@Nonnull ByteBuf buf, int offset) { + SetMachinimaActorModel obj = new SetMachinimaActorModel(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 1); + obj.model = Model.deserialize(buf, varPos0); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 5); + int sceneNameLen = VarInt.peek(buf, varPos1); + if (sceneNameLen < 0) { + throw ProtocolException.negativeLength("SceneName", sceneNameLen); + } + + if (sceneNameLen > 4096000) { + throw ProtocolException.stringTooLong("SceneName", sceneNameLen, 4096000); + } + + obj.sceneName = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 13 + buf.getIntLE(offset + 9); + int actorNameLen = VarInt.peek(buf, varPos2); + if (actorNameLen < 0) { + throw ProtocolException.negativeLength("ActorName", actorNameLen); + } + + if (actorNameLen > 4096000) { + throw ProtocolException.stringTooLong("ActorName", actorNameLen, 4096000); + } + + obj.actorName = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 13 + fieldOffset0; + pos0 += Model.computeBytesConsumed(buf, pos0); + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 13 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 9); + int pos2 = offset + 13 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.model != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.sceneName != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.actorName != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + int modelOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int sceneNameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int actorNameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.model != null) { + buf.setIntLE(modelOffsetSlot, buf.writerIndex() - varBlockStart); + this.model.serialize(buf); + } else { + buf.setIntLE(modelOffsetSlot, -1); + } + + if (this.sceneName != null) { + buf.setIntLE(sceneNameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.sceneName, 4096000); + } else { + buf.setIntLE(sceneNameOffsetSlot, -1); + } + + if (this.actorName != null) { + buf.setIntLE(actorNameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.actorName, 4096000); + } else { + buf.setIntLE(actorNameOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 13; + if (this.model != null) { + size += this.model.computeSize(); + } + + if (this.sceneName != null) { + size += PacketIO.stringSize(this.sceneName); + } + + if (this.actorName != null) { + size += PacketIO.stringSize(this.actorName); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int modelOffset = buffer.getIntLE(offset + 1); + if (modelOffset < 0) { + return ValidationResult.error("Invalid offset for Model"); + } + + int pos = offset + 13 + modelOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Model"); + } + + ValidationResult modelResult = Model.validateStructure(buffer, pos); + if (!modelResult.isValid()) { + return ValidationResult.error("Invalid Model: " + modelResult.error()); + } + + pos += Model.computeBytesConsumed(buffer, pos); + } + + if ((nullBits & 2) != 0) { + int sceneNameOffset = buffer.getIntLE(offset + 5); + if (sceneNameOffset < 0) { + return ValidationResult.error("Invalid offset for SceneName"); + } + + int posx = offset + 13 + sceneNameOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SceneName"); + } + + int sceneNameLen = VarInt.peek(buffer, posx); + if (sceneNameLen < 0) { + return ValidationResult.error("Invalid string length for SceneName"); + } + + if (sceneNameLen > 4096000) { + return ValidationResult.error("SceneName exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += sceneNameLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading SceneName"); + } + } + + if ((nullBits & 4) != 0) { + int actorNameOffset = buffer.getIntLE(offset + 9); + if (actorNameOffset < 0) { + return ValidationResult.error("Invalid offset for ActorName"); + } + + int posxx = offset + 13 + actorNameOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ActorName"); + } + + int actorNameLen = VarInt.peek(buffer, posxx); + if (actorNameLen < 0) { + return ValidationResult.error("Invalid string length for ActorName"); + } + + if (actorNameLen > 4096000) { + return ValidationResult.error("ActorName exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += actorNameLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ActorName"); + } + } + + return ValidationResult.OK; + } + } + + public SetMachinimaActorModel clone() { + SetMachinimaActorModel copy = new SetMachinimaActorModel(); + copy.model = this.model != null ? this.model.clone() : null; + copy.sceneName = this.sceneName; + copy.actorName = this.actorName; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SetMachinimaActorModel other) + ? false + : Objects.equals(this.model, other.model) && Objects.equals(this.sceneName, other.sceneName) && Objects.equals(this.actorName, other.actorName); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.model, this.sceneName, this.actorName); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/machinima/UpdateMachinimaScene.java b/src/com/hypixel/hytale/protocol/packets/machinima/UpdateMachinimaScene.java new file mode 100644 index 0000000..989ab81 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/machinima/UpdateMachinimaScene.java @@ -0,0 +1,350 @@ +package com.hypixel.hytale.protocol.packets.machinima; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateMachinimaScene implements Packet { + public static final int PACKET_ID = 262; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 18; + public static final int MAX_SIZE = 36864033; + @Nullable + public String player; + @Nullable + public String sceneName; + public float frame; + @Nonnull + public SceneUpdateType updateType = SceneUpdateType.Update; + @Nullable + public byte[] scene; + + @Override + public int getId() { + return 262; + } + + public UpdateMachinimaScene() { + } + + public UpdateMachinimaScene(@Nullable String player, @Nullable String sceneName, float frame, @Nonnull SceneUpdateType updateType, @Nullable byte[] scene) { + this.player = player; + this.sceneName = sceneName; + this.frame = frame; + this.updateType = updateType; + this.scene = scene; + } + + public UpdateMachinimaScene(@Nonnull UpdateMachinimaScene other) { + this.player = other.player; + this.sceneName = other.sceneName; + this.frame = other.frame; + this.updateType = other.updateType; + this.scene = other.scene; + } + + @Nonnull + public static UpdateMachinimaScene deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateMachinimaScene obj = new UpdateMachinimaScene(); + byte nullBits = buf.getByte(offset); + obj.frame = buf.getFloatLE(offset + 1); + obj.updateType = SceneUpdateType.fromValue(buf.getByte(offset + 5)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 18 + buf.getIntLE(offset + 6); + int playerLen = VarInt.peek(buf, varPos0); + if (playerLen < 0) { + throw ProtocolException.negativeLength("Player", playerLen); + } + + if (playerLen > 4096000) { + throw ProtocolException.stringTooLong("Player", playerLen, 4096000); + } + + obj.player = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 18 + buf.getIntLE(offset + 10); + int sceneNameLen = VarInt.peek(buf, varPos1); + if (sceneNameLen < 0) { + throw ProtocolException.negativeLength("SceneName", sceneNameLen); + } + + if (sceneNameLen > 4096000) { + throw ProtocolException.stringTooLong("SceneName", sceneNameLen, 4096000); + } + + obj.sceneName = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 18 + buf.getIntLE(offset + 14); + int sceneCount = VarInt.peek(buf, varPos2); + if (sceneCount < 0) { + throw ProtocolException.negativeLength("Scene", sceneCount); + } + + if (sceneCount > 4096000) { + throw ProtocolException.arrayTooLong("Scene", sceneCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + sceneCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Scene", varPos2 + varIntLen + sceneCount * 1, buf.readableBytes()); + } + + obj.scene = new byte[sceneCount]; + + for (int i = 0; i < sceneCount; i++) { + obj.scene[i] = buf.getByte(varPos2 + varIntLen + i * 1); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 18; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 6); + int pos0 = offset + 18 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 10); + int pos1 = offset + 18 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 14); + int pos2 = offset + 18 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + arrLen * 1; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.player != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.sceneName != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.scene != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + buf.writeFloatLE(this.frame); + buf.writeByte(this.updateType.getValue()); + int playerOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int sceneNameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int sceneOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.player != null) { + buf.setIntLE(playerOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.player, 4096000); + } else { + buf.setIntLE(playerOffsetSlot, -1); + } + + if (this.sceneName != null) { + buf.setIntLE(sceneNameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.sceneName, 4096000); + } else { + buf.setIntLE(sceneNameOffsetSlot, -1); + } + + if (this.scene != null) { + buf.setIntLE(sceneOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.scene.length > 4096000) { + throw ProtocolException.arrayTooLong("Scene", this.scene.length, 4096000); + } + + VarInt.write(buf, this.scene.length); + + for (byte item : this.scene) { + buf.writeByte(item); + } + } else { + buf.setIntLE(sceneOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 18; + if (this.player != null) { + size += PacketIO.stringSize(this.player); + } + + if (this.sceneName != null) { + size += PacketIO.stringSize(this.sceneName); + } + + if (this.scene != null) { + size += VarInt.size(this.scene.length) + this.scene.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 18) { + return ValidationResult.error("Buffer too small: expected at least 18 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int playerOffset = buffer.getIntLE(offset + 6); + if (playerOffset < 0) { + return ValidationResult.error("Invalid offset for Player"); + } + + int pos = offset + 18 + playerOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Player"); + } + + int playerLen = VarInt.peek(buffer, pos); + if (playerLen < 0) { + return ValidationResult.error("Invalid string length for Player"); + } + + if (playerLen > 4096000) { + return ValidationResult.error("Player exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += playerLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Player"); + } + } + + if ((nullBits & 2) != 0) { + int sceneNameOffset = buffer.getIntLE(offset + 10); + if (sceneNameOffset < 0) { + return ValidationResult.error("Invalid offset for SceneName"); + } + + int posx = offset + 18 + sceneNameOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for SceneName"); + } + + int sceneNameLen = VarInt.peek(buffer, posx); + if (sceneNameLen < 0) { + return ValidationResult.error("Invalid string length for SceneName"); + } + + if (sceneNameLen > 4096000) { + return ValidationResult.error("SceneName exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += sceneNameLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading SceneName"); + } + } + + if ((nullBits & 4) != 0) { + int sceneOffset = buffer.getIntLE(offset + 14); + if (sceneOffset < 0) { + return ValidationResult.error("Invalid offset for Scene"); + } + + int posxx = offset + 18 + sceneOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Scene"); + } + + int sceneCount = VarInt.peek(buffer, posxx); + if (sceneCount < 0) { + return ValidationResult.error("Invalid array count for Scene"); + } + + if (sceneCount > 4096000) { + return ValidationResult.error("Scene exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += sceneCount * 1; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Scene"); + } + } + + return ValidationResult.OK; + } + } + + public UpdateMachinimaScene clone() { + UpdateMachinimaScene copy = new UpdateMachinimaScene(); + copy.player = this.player; + copy.sceneName = this.sceneName; + copy.frame = this.frame; + copy.updateType = this.updateType; + copy.scene = this.scene != null ? Arrays.copyOf(this.scene, this.scene.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateMachinimaScene other) + ? false + : Objects.equals(this.player, other.player) + && Objects.equals(this.sceneName, other.sceneName) + && this.frame == other.frame + && Objects.equals(this.updateType, other.updateType) + && Arrays.equals(this.scene, other.scene); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.player); + result = 31 * result + Objects.hashCode(this.sceneName); + result = 31 * result + Float.hashCode(this.frame); + result = 31 * result + Objects.hashCode(this.updateType); + return 31 * result + Arrays.hashCode(this.scene); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/ClearDebugShapes.java b/src/com/hypixel/hytale/protocol/packets/player/ClearDebugShapes.java new file mode 100644 index 0000000..b05fd8c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/ClearDebugShapes.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class ClearDebugShapes implements Packet { + public static final int PACKET_ID = 115; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public ClearDebugShapes() { + } + + @Override + public int getId() { + return 115; + } + + @Nonnull + public static ClearDebugShapes deserialize(@Nonnull ByteBuf buf, int offset) { + return new ClearDebugShapes(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public ClearDebugShapes clone() { + return new ClearDebugShapes(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof ClearDebugShapes other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/ClientMovement.java b/src/com/hypixel/hytale/protocol/packets/player/ClientMovement.java new file mode 100644 index 0000000..6757ef5 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/ClientMovement.java @@ -0,0 +1,293 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.HalfFloatPosition; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.Position; +import com.hypixel.hytale.protocol.TeleportAck; +import com.hypixel.hytale.protocol.Vector3d; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ClientMovement implements Packet { + public static final int PACKET_ID = 108; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 2; + public static final int FIXED_BLOCK_SIZE = 153; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 153; + public static final int MAX_SIZE = 153; + @Nullable + public MovementStates movementStates; + @Nullable + public HalfFloatPosition relativePosition; + @Nullable + public Position absolutePosition; + @Nullable + public Direction bodyOrientation; + @Nullable + public Direction lookOrientation; + @Nullable + public TeleportAck teleportAck; + @Nullable + public Position wishMovement; + @Nullable + public Vector3d velocity; + public int mountedTo; + @Nullable + public MovementStates riderMovementStates; + + @Override + public int getId() { + return 108; + } + + public ClientMovement() { + } + + public ClientMovement( + @Nullable MovementStates movementStates, + @Nullable HalfFloatPosition relativePosition, + @Nullable Position absolutePosition, + @Nullable Direction bodyOrientation, + @Nullable Direction lookOrientation, + @Nullable TeleportAck teleportAck, + @Nullable Position wishMovement, + @Nullable Vector3d velocity, + int mountedTo, + @Nullable MovementStates riderMovementStates + ) { + this.movementStates = movementStates; + this.relativePosition = relativePosition; + this.absolutePosition = absolutePosition; + this.bodyOrientation = bodyOrientation; + this.lookOrientation = lookOrientation; + this.teleportAck = teleportAck; + this.wishMovement = wishMovement; + this.velocity = velocity; + this.mountedTo = mountedTo; + this.riderMovementStates = riderMovementStates; + } + + public ClientMovement(@Nonnull ClientMovement other) { + this.movementStates = other.movementStates; + this.relativePosition = other.relativePosition; + this.absolutePosition = other.absolutePosition; + this.bodyOrientation = other.bodyOrientation; + this.lookOrientation = other.lookOrientation; + this.teleportAck = other.teleportAck; + this.wishMovement = other.wishMovement; + this.velocity = other.velocity; + this.mountedTo = other.mountedTo; + this.riderMovementStates = other.riderMovementStates; + } + + @Nonnull + public static ClientMovement deserialize(@Nonnull ByteBuf buf, int offset) { + ClientMovement obj = new ClientMovement(); + byte[] nullBits = PacketIO.readBytes(buf, offset, 2); + if ((nullBits[0] & 1) != 0) { + obj.movementStates = MovementStates.deserialize(buf, offset + 2); + } + + if ((nullBits[0] & 2) != 0) { + obj.relativePosition = HalfFloatPosition.deserialize(buf, offset + 24); + } + + if ((nullBits[0] & 4) != 0) { + obj.absolutePosition = Position.deserialize(buf, offset + 30); + } + + if ((nullBits[0] & 8) != 0) { + obj.bodyOrientation = Direction.deserialize(buf, offset + 54); + } + + if ((nullBits[0] & 16) != 0) { + obj.lookOrientation = Direction.deserialize(buf, offset + 66); + } + + if ((nullBits[0] & 32) != 0) { + obj.teleportAck = TeleportAck.deserialize(buf, offset + 78); + } + + if ((nullBits[0] & 64) != 0) { + obj.wishMovement = Position.deserialize(buf, offset + 79); + } + + if ((nullBits[0] & 128) != 0) { + obj.velocity = Vector3d.deserialize(buf, offset + 103); + } + + obj.mountedTo = buf.getIntLE(offset + 127); + if ((nullBits[1] & 1) != 0) { + obj.riderMovementStates = MovementStates.deserialize(buf, offset + 131); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 153; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte[] nullBits = new byte[2]; + if (this.movementStates != null) { + nullBits[0] = (byte)(nullBits[0] | 1); + } + + if (this.relativePosition != null) { + nullBits[0] = (byte)(nullBits[0] | 2); + } + + if (this.absolutePosition != null) { + nullBits[0] = (byte)(nullBits[0] | 4); + } + + if (this.bodyOrientation != null) { + nullBits[0] = (byte)(nullBits[0] | 8); + } + + if (this.lookOrientation != null) { + nullBits[0] = (byte)(nullBits[0] | 16); + } + + if (this.teleportAck != null) { + nullBits[0] = (byte)(nullBits[0] | 32); + } + + if (this.wishMovement != null) { + nullBits[0] = (byte)(nullBits[0] | 64); + } + + if (this.velocity != null) { + nullBits[0] = (byte)(nullBits[0] | 128); + } + + if (this.riderMovementStates != null) { + nullBits[1] = (byte)(nullBits[1] | 1); + } + + buf.writeBytes(nullBits); + if (this.movementStates != null) { + this.movementStates.serialize(buf); + } else { + buf.writeZero(22); + } + + if (this.relativePosition != null) { + this.relativePosition.serialize(buf); + } else { + buf.writeZero(6); + } + + if (this.absolutePosition != null) { + this.absolutePosition.serialize(buf); + } else { + buf.writeZero(24); + } + + if (this.bodyOrientation != null) { + this.bodyOrientation.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.lookOrientation != null) { + this.lookOrientation.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.teleportAck != null) { + this.teleportAck.serialize(buf); + } else { + buf.writeZero(1); + } + + if (this.wishMovement != null) { + this.wishMovement.serialize(buf); + } else { + buf.writeZero(24); + } + + if (this.velocity != null) { + this.velocity.serialize(buf); + } else { + buf.writeZero(24); + } + + buf.writeIntLE(this.mountedTo); + if (this.riderMovementStates != null) { + this.riderMovementStates.serialize(buf); + } else { + buf.writeZero(22); + } + } + + @Override + public int computeSize() { + return 153; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 153 ? ValidationResult.error("Buffer too small: expected at least 153 bytes") : ValidationResult.OK; + } + + public ClientMovement clone() { + ClientMovement copy = new ClientMovement(); + copy.movementStates = this.movementStates != null ? this.movementStates.clone() : null; + copy.relativePosition = this.relativePosition != null ? this.relativePosition.clone() : null; + copy.absolutePosition = this.absolutePosition != null ? this.absolutePosition.clone() : null; + copy.bodyOrientation = this.bodyOrientation != null ? this.bodyOrientation.clone() : null; + copy.lookOrientation = this.lookOrientation != null ? this.lookOrientation.clone() : null; + copy.teleportAck = this.teleportAck != null ? this.teleportAck.clone() : null; + copy.wishMovement = this.wishMovement != null ? this.wishMovement.clone() : null; + copy.velocity = this.velocity != null ? this.velocity.clone() : null; + copy.mountedTo = this.mountedTo; + copy.riderMovementStates = this.riderMovementStates != null ? this.riderMovementStates.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ClientMovement other) + ? false + : Objects.equals(this.movementStates, other.movementStates) + && Objects.equals(this.relativePosition, other.relativePosition) + && Objects.equals(this.absolutePosition, other.absolutePosition) + && Objects.equals(this.bodyOrientation, other.bodyOrientation) + && Objects.equals(this.lookOrientation, other.lookOrientation) + && Objects.equals(this.teleportAck, other.teleportAck) + && Objects.equals(this.wishMovement, other.wishMovement) + && Objects.equals(this.velocity, other.velocity) + && this.mountedTo == other.mountedTo + && Objects.equals(this.riderMovementStates, other.riderMovementStates); + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.movementStates, + this.relativePosition, + this.absolutePosition, + this.bodyOrientation, + this.lookOrientation, + this.teleportAck, + this.wishMovement, + this.velocity, + this.mountedTo, + this.riderMovementStates + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/ClientPlaceBlock.java b/src/com/hypixel/hytale/protocol/packets/player/ClientPlaceBlock.java new file mode 100644 index 0000000..4499544 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/ClientPlaceBlock.java @@ -0,0 +1,125 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.BlockRotation; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ClientPlaceBlock implements Packet { + public static final int PACKET_ID = 117; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 20; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 20; + public static final int MAX_SIZE = 20; + @Nullable + public BlockPosition position; + @Nullable + public BlockRotation rotation; + public int placedBlockId; + + @Override + public int getId() { + return 117; + } + + public ClientPlaceBlock() { + } + + public ClientPlaceBlock(@Nullable BlockPosition position, @Nullable BlockRotation rotation, int placedBlockId) { + this.position = position; + this.rotation = rotation; + this.placedBlockId = placedBlockId; + } + + public ClientPlaceBlock(@Nonnull ClientPlaceBlock other) { + this.position = other.position; + this.rotation = other.rotation; + this.placedBlockId = other.placedBlockId; + } + + @Nonnull + public static ClientPlaceBlock deserialize(@Nonnull ByteBuf buf, int offset) { + ClientPlaceBlock obj = new ClientPlaceBlock(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.position = BlockPosition.deserialize(buf, offset + 1); + } + + if ((nullBits & 2) != 0) { + obj.rotation = BlockRotation.deserialize(buf, offset + 13); + } + + obj.placedBlockId = buf.getIntLE(offset + 16); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 20; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.position != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.rotation != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + if (this.position != null) { + this.position.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.rotation != null) { + this.rotation.serialize(buf); + } else { + buf.writeZero(3); + } + + buf.writeIntLE(this.placedBlockId); + } + + @Override + public int computeSize() { + return 20; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 20 ? ValidationResult.error("Buffer too small: expected at least 20 bytes") : ValidationResult.OK; + } + + public ClientPlaceBlock clone() { + ClientPlaceBlock copy = new ClientPlaceBlock(); + copy.position = this.position != null ? this.position.clone() : null; + copy.rotation = this.rotation != null ? this.rotation.clone() : null; + copy.placedBlockId = this.placedBlockId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ClientPlaceBlock other) + ? false + : Objects.equals(this.position, other.position) && Objects.equals(this.rotation, other.rotation) && this.placedBlockId == other.placedBlockId; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.position, this.rotation, this.placedBlockId); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/ClientReady.java b/src/com/hypixel/hytale/protocol/packets/player/ClientReady.java new file mode 100644 index 0000000..f68c938 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/ClientReady.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class ClientReady implements Packet { + public static final int PACKET_ID = 105; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 2; + public boolean readyForChunks; + public boolean readyForGameplay; + + @Override + public int getId() { + return 105; + } + + public ClientReady() { + } + + public ClientReady(boolean readyForChunks, boolean readyForGameplay) { + this.readyForChunks = readyForChunks; + this.readyForGameplay = readyForGameplay; + } + + public ClientReady(@Nonnull ClientReady other) { + this.readyForChunks = other.readyForChunks; + this.readyForGameplay = other.readyForGameplay; + } + + @Nonnull + public static ClientReady deserialize(@Nonnull ByteBuf buf, int offset) { + ClientReady obj = new ClientReady(); + obj.readyForChunks = buf.getByte(offset + 0) != 0; + obj.readyForGameplay = buf.getByte(offset + 1) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 2; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.readyForChunks ? 1 : 0); + buf.writeByte(this.readyForGameplay ? 1 : 0); + } + + @Override + public int computeSize() { + return 2; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 2 ? ValidationResult.error("Buffer too small: expected at least 2 bytes") : ValidationResult.OK; + } + + public ClientReady clone() { + ClientReady copy = new ClientReady(); + copy.readyForChunks = this.readyForChunks; + copy.readyForGameplay = this.readyForGameplay; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ClientReady other) ? false : this.readyForChunks == other.readyForChunks && this.readyForGameplay == other.readyForGameplay; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.readyForChunks, this.readyForGameplay); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/ClientTeleport.java b/src/com/hypixel/hytale/protocol/packets/player/ClientTeleport.java new file mode 100644 index 0000000..105377b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/ClientTeleport.java @@ -0,0 +1,111 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.ModelTransform; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ClientTeleport implements Packet { + public static final int PACKET_ID = 109; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 52; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 52; + public static final int MAX_SIZE = 52; + public byte teleportId; + @Nullable + public ModelTransform modelTransform; + public boolean resetVelocity; + + @Override + public int getId() { + return 109; + } + + public ClientTeleport() { + } + + public ClientTeleport(byte teleportId, @Nullable ModelTransform modelTransform, boolean resetVelocity) { + this.teleportId = teleportId; + this.modelTransform = modelTransform; + this.resetVelocity = resetVelocity; + } + + public ClientTeleport(@Nonnull ClientTeleport other) { + this.teleportId = other.teleportId; + this.modelTransform = other.modelTransform; + this.resetVelocity = other.resetVelocity; + } + + @Nonnull + public static ClientTeleport deserialize(@Nonnull ByteBuf buf, int offset) { + ClientTeleport obj = new ClientTeleport(); + byte nullBits = buf.getByte(offset); + obj.teleportId = buf.getByte(offset + 1); + if ((nullBits & 1) != 0) { + obj.modelTransform = ModelTransform.deserialize(buf, offset + 2); + } + + obj.resetVelocity = buf.getByte(offset + 51) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 52; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.modelTransform != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.teleportId); + if (this.modelTransform != null) { + this.modelTransform.serialize(buf); + } else { + buf.writeZero(49); + } + + buf.writeByte(this.resetVelocity ? 1 : 0); + } + + @Override + public int computeSize() { + return 52; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 52 ? ValidationResult.error("Buffer too small: expected at least 52 bytes") : ValidationResult.OK; + } + + public ClientTeleport clone() { + ClientTeleport copy = new ClientTeleport(); + copy.teleportId = this.teleportId; + copy.modelTransform = this.modelTransform != null ? this.modelTransform.clone() : null; + copy.resetVelocity = this.resetVelocity; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ClientTeleport other) + ? false + : this.teleportId == other.teleportId && Objects.equals(this.modelTransform, other.modelTransform) && this.resetVelocity == other.resetVelocity; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.teleportId, this.modelTransform, this.resetVelocity); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/DamageInfo.java b/src/com/hypixel/hytale/protocol/packets/player/DamageInfo.java new file mode 100644 index 0000000..3ccd703 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/DamageInfo.java @@ -0,0 +1,152 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.DamageCause; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.Vector3d; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DamageInfo implements Packet { + public static final int PACKET_ID = 112; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 29; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 29; + public static final int MAX_SIZE = 32768048; + @Nullable + public Vector3d damageSourcePosition; + public float damageAmount; + @Nullable + public DamageCause damageCause; + + @Override + public int getId() { + return 112; + } + + public DamageInfo() { + } + + public DamageInfo(@Nullable Vector3d damageSourcePosition, float damageAmount, @Nullable DamageCause damageCause) { + this.damageSourcePosition = damageSourcePosition; + this.damageAmount = damageAmount; + this.damageCause = damageCause; + } + + public DamageInfo(@Nonnull DamageInfo other) { + this.damageSourcePosition = other.damageSourcePosition; + this.damageAmount = other.damageAmount; + this.damageCause = other.damageCause; + } + + @Nonnull + public static DamageInfo deserialize(@Nonnull ByteBuf buf, int offset) { + DamageInfo obj = new DamageInfo(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.damageSourcePosition = Vector3d.deserialize(buf, offset + 1); + } + + obj.damageAmount = buf.getFloatLE(offset + 25); + int pos = offset + 29; + if ((nullBits & 2) != 0) { + obj.damageCause = DamageCause.deserialize(buf, pos); + pos += DamageCause.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 29; + if ((nullBits & 2) != 0) { + pos += DamageCause.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.damageSourcePosition != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.damageCause != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + if (this.damageSourcePosition != null) { + this.damageSourcePosition.serialize(buf); + } else { + buf.writeZero(24); + } + + buf.writeFloatLE(this.damageAmount); + if (this.damageCause != null) { + this.damageCause.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 29; + if (this.damageCause != null) { + size += this.damageCause.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 29) { + return ValidationResult.error("Buffer too small: expected at least 29 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 29; + if ((nullBits & 2) != 0) { + ValidationResult damageCauseResult = DamageCause.validateStructure(buffer, pos); + if (!damageCauseResult.isValid()) { + return ValidationResult.error("Invalid DamageCause: " + damageCauseResult.error()); + } + + pos += DamageCause.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public DamageInfo clone() { + DamageInfo copy = new DamageInfo(); + copy.damageSourcePosition = this.damageSourcePosition != null ? this.damageSourcePosition.clone() : null; + copy.damageAmount = this.damageAmount; + copy.damageCause = this.damageCause != null ? this.damageCause.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof DamageInfo other) + ? false + : Objects.equals(this.damageSourcePosition, other.damageSourcePosition) + && this.damageAmount == other.damageAmount + && Objects.equals(this.damageCause, other.damageCause); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.damageSourcePosition, this.damageAmount, this.damageCause); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/DisplayDebug.java b/src/com/hypixel/hytale/protocol/packets/player/DisplayDebug.java new file mode 100644 index 0000000..d875030 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/DisplayDebug.java @@ -0,0 +1,324 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.DebugShape; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DisplayDebug implements Packet { + public static final int PACKET_ID = 114; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 19; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 27; + public static final int MAX_SIZE = 32768037; + @Nonnull + public DebugShape shape = DebugShape.Sphere; + @Nullable + public float[] matrix; + @Nullable + public Vector3f color; + public float time; + public boolean fade; + @Nullable + public float[] frustumProjection; + + @Override + public int getId() { + return 114; + } + + public DisplayDebug() { + } + + public DisplayDebug( + @Nonnull DebugShape shape, @Nullable float[] matrix, @Nullable Vector3f color, float time, boolean fade, @Nullable float[] frustumProjection + ) { + this.shape = shape; + this.matrix = matrix; + this.color = color; + this.time = time; + this.fade = fade; + this.frustumProjection = frustumProjection; + } + + public DisplayDebug(@Nonnull DisplayDebug other) { + this.shape = other.shape; + this.matrix = other.matrix; + this.color = other.color; + this.time = other.time; + this.fade = other.fade; + this.frustumProjection = other.frustumProjection; + } + + @Nonnull + public static DisplayDebug deserialize(@Nonnull ByteBuf buf, int offset) { + DisplayDebug obj = new DisplayDebug(); + byte nullBits = buf.getByte(offset); + obj.shape = DebugShape.fromValue(buf.getByte(offset + 1)); + if ((nullBits & 2) != 0) { + obj.color = Vector3f.deserialize(buf, offset + 2); + } + + obj.time = buf.getFloatLE(offset + 14); + obj.fade = buf.getByte(offset + 18) != 0; + if ((nullBits & 1) != 0) { + int varPos0 = offset + 27 + buf.getIntLE(offset + 19); + int matrixCount = VarInt.peek(buf, varPos0); + if (matrixCount < 0) { + throw ProtocolException.negativeLength("Matrix", matrixCount); + } + + if (matrixCount > 4096000) { + throw ProtocolException.arrayTooLong("Matrix", matrixCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + matrixCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Matrix", varPos0 + varIntLen + matrixCount * 4, buf.readableBytes()); + } + + obj.matrix = new float[matrixCount]; + + for (int i = 0; i < matrixCount; i++) { + obj.matrix[i] = buf.getFloatLE(varPos0 + varIntLen + i * 4); + } + } + + if ((nullBits & 4) != 0) { + int varPos1 = offset + 27 + buf.getIntLE(offset + 23); + int frustumProjectionCount = VarInt.peek(buf, varPos1); + if (frustumProjectionCount < 0) { + throw ProtocolException.negativeLength("FrustumProjection", frustumProjectionCount); + } + + if (frustumProjectionCount > 4096000) { + throw ProtocolException.arrayTooLong("FrustumProjection", frustumProjectionCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + frustumProjectionCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("FrustumProjection", varPos1 + varIntLen + frustumProjectionCount * 4, buf.readableBytes()); + } + + obj.frustumProjection = new float[frustumProjectionCount]; + + for (int i = 0; i < frustumProjectionCount; i++) { + obj.frustumProjection[i] = buf.getFloatLE(varPos1 + varIntLen + i * 4); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 27; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 19); + int pos0 = offset + 27 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + arrLen * 4; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 23); + int pos1 = offset + 27 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + arrLen * 4; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.matrix != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.color != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.frustumProjection != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + buf.writeByte(this.shape.getValue()); + if (this.color != null) { + this.color.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeFloatLE(this.time); + buf.writeByte(this.fade ? 1 : 0); + int matrixOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int frustumProjectionOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.matrix != null) { + buf.setIntLE(matrixOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.matrix.length > 4096000) { + throw ProtocolException.arrayTooLong("Matrix", this.matrix.length, 4096000); + } + + VarInt.write(buf, this.matrix.length); + + for (float item : this.matrix) { + buf.writeFloatLE(item); + } + } else { + buf.setIntLE(matrixOffsetSlot, -1); + } + + if (this.frustumProjection != null) { + buf.setIntLE(frustumProjectionOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.frustumProjection.length > 4096000) { + throw ProtocolException.arrayTooLong("FrustumProjection", this.frustumProjection.length, 4096000); + } + + VarInt.write(buf, this.frustumProjection.length); + + for (float item : this.frustumProjection) { + buf.writeFloatLE(item); + } + } else { + buf.setIntLE(frustumProjectionOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 27; + if (this.matrix != null) { + size += VarInt.size(this.matrix.length) + this.matrix.length * 4; + } + + if (this.frustumProjection != null) { + size += VarInt.size(this.frustumProjection.length) + this.frustumProjection.length * 4; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 27) { + return ValidationResult.error("Buffer too small: expected at least 27 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int matrixOffset = buffer.getIntLE(offset + 19); + if (matrixOffset < 0) { + return ValidationResult.error("Invalid offset for Matrix"); + } + + int pos = offset + 27 + matrixOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Matrix"); + } + + int matrixCount = VarInt.peek(buffer, pos); + if (matrixCount < 0) { + return ValidationResult.error("Invalid array count for Matrix"); + } + + if (matrixCount > 4096000) { + return ValidationResult.error("Matrix exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += matrixCount * 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Matrix"); + } + } + + if ((nullBits & 4) != 0) { + int frustumProjectionOffset = buffer.getIntLE(offset + 23); + if (frustumProjectionOffset < 0) { + return ValidationResult.error("Invalid offset for FrustumProjection"); + } + + int posx = offset + 27 + frustumProjectionOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for FrustumProjection"); + } + + int frustumProjectionCount = VarInt.peek(buffer, posx); + if (frustumProjectionCount < 0) { + return ValidationResult.error("Invalid array count for FrustumProjection"); + } + + if (frustumProjectionCount > 4096000) { + return ValidationResult.error("FrustumProjection exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += frustumProjectionCount * 4; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading FrustumProjection"); + } + } + + return ValidationResult.OK; + } + } + + public DisplayDebug clone() { + DisplayDebug copy = new DisplayDebug(); + copy.shape = this.shape; + copy.matrix = this.matrix != null ? Arrays.copyOf(this.matrix, this.matrix.length) : null; + copy.color = this.color != null ? this.color.clone() : null; + copy.time = this.time; + copy.fade = this.fade; + copy.frustumProjection = this.frustumProjection != null ? Arrays.copyOf(this.frustumProjection, this.frustumProjection.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof DisplayDebug other) + ? false + : Objects.equals(this.shape, other.shape) + && Arrays.equals(this.matrix, other.matrix) + && Objects.equals(this.color, other.color) + && this.time == other.time + && this.fade == other.fade + && Arrays.equals(this.frustumProjection, other.frustumProjection); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.shape); + result = 31 * result + Arrays.hashCode(this.matrix); + result = 31 * result + Objects.hashCode(this.color); + result = 31 * result + Float.hashCode(this.time); + result = 31 * result + Boolean.hashCode(this.fade); + return 31 * result + Arrays.hashCode(this.frustumProjection); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/JoinWorld.java b/src/com/hypixel/hytale/protocol/packets/player/JoinWorld.java new file mode 100644 index 0000000..126d5e3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/JoinWorld.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class JoinWorld implements Packet { + public static final int PACKET_ID = 104; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 18; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 18; + public static final int MAX_SIZE = 18; + public boolean clearWorld; + public boolean fadeInOut; + @Nonnull + public UUID worldUuid = new UUID(0L, 0L); + + @Override + public int getId() { + return 104; + } + + public JoinWorld() { + } + + public JoinWorld(boolean clearWorld, boolean fadeInOut, @Nonnull UUID worldUuid) { + this.clearWorld = clearWorld; + this.fadeInOut = fadeInOut; + this.worldUuid = worldUuid; + } + + public JoinWorld(@Nonnull JoinWorld other) { + this.clearWorld = other.clearWorld; + this.fadeInOut = other.fadeInOut; + this.worldUuid = other.worldUuid; + } + + @Nonnull + public static JoinWorld deserialize(@Nonnull ByteBuf buf, int offset) { + JoinWorld obj = new JoinWorld(); + obj.clearWorld = buf.getByte(offset + 0) != 0; + obj.fadeInOut = buf.getByte(offset + 1) != 0; + obj.worldUuid = PacketIO.readUUID(buf, offset + 2); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 18; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.clearWorld ? 1 : 0); + buf.writeByte(this.fadeInOut ? 1 : 0); + PacketIO.writeUUID(buf, this.worldUuid); + } + + @Override + public int computeSize() { + return 18; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 18 ? ValidationResult.error("Buffer too small: expected at least 18 bytes") : ValidationResult.OK; + } + + public JoinWorld clone() { + JoinWorld copy = new JoinWorld(); + copy.clearWorld = this.clearWorld; + copy.fadeInOut = this.fadeInOut; + copy.worldUuid = this.worldUuid; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof JoinWorld other) + ? false + : this.clearWorld == other.clearWorld && this.fadeInOut == other.fadeInOut && Objects.equals(this.worldUuid, other.worldUuid); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.clearWorld, this.fadeInOut, this.worldUuid); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/LoadHotbar.java b/src/com/hypixel/hytale/protocol/packets/player/LoadHotbar.java new file mode 100644 index 0000000..2244078 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/LoadHotbar.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class LoadHotbar implements Packet { + public static final int PACKET_ID = 106; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public byte inventoryRow; + + @Override + public int getId() { + return 106; + } + + public LoadHotbar() { + } + + public LoadHotbar(byte inventoryRow) { + this.inventoryRow = inventoryRow; + } + + public LoadHotbar(@Nonnull LoadHotbar other) { + this.inventoryRow = other.inventoryRow; + } + + @Nonnull + public static LoadHotbar deserialize(@Nonnull ByteBuf buf, int offset) { + LoadHotbar obj = new LoadHotbar(); + obj.inventoryRow = buf.getByte(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.inventoryRow); + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public LoadHotbar clone() { + LoadHotbar copy = new LoadHotbar(); + copy.inventoryRow = this.inventoryRow; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof LoadHotbar other ? this.inventoryRow == other.inventoryRow : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.inventoryRow); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/MouseInteraction.java b/src/com/hypixel/hytale/protocol/packets/player/MouseInteraction.java new file mode 100644 index 0000000..78f2354 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/MouseInteraction.java @@ -0,0 +1,306 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.MouseButtonEvent; +import com.hypixel.hytale.protocol.MouseMotionEvent; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.Vector2f; +import com.hypixel.hytale.protocol.WorldInteraction; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MouseInteraction implements Packet { + public static final int PACKET_ID = 111; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 44; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 52; + public static final int MAX_SIZE = 20480071; + public long clientTimestamp; + public int activeSlot; + @Nullable + public String itemInHandId; + @Nullable + public Vector2f screenPoint; + @Nullable + public MouseButtonEvent mouseButton; + @Nullable + public MouseMotionEvent mouseMotion; + @Nullable + public WorldInteraction worldInteraction; + + @Override + public int getId() { + return 111; + } + + public MouseInteraction() { + } + + public MouseInteraction( + long clientTimestamp, + int activeSlot, + @Nullable String itemInHandId, + @Nullable Vector2f screenPoint, + @Nullable MouseButtonEvent mouseButton, + @Nullable MouseMotionEvent mouseMotion, + @Nullable WorldInteraction worldInteraction + ) { + this.clientTimestamp = clientTimestamp; + this.activeSlot = activeSlot; + this.itemInHandId = itemInHandId; + this.screenPoint = screenPoint; + this.mouseButton = mouseButton; + this.mouseMotion = mouseMotion; + this.worldInteraction = worldInteraction; + } + + public MouseInteraction(@Nonnull MouseInteraction other) { + this.clientTimestamp = other.clientTimestamp; + this.activeSlot = other.activeSlot; + this.itemInHandId = other.itemInHandId; + this.screenPoint = other.screenPoint; + this.mouseButton = other.mouseButton; + this.mouseMotion = other.mouseMotion; + this.worldInteraction = other.worldInteraction; + } + + @Nonnull + public static MouseInteraction deserialize(@Nonnull ByteBuf buf, int offset) { + MouseInteraction obj = new MouseInteraction(); + byte nullBits = buf.getByte(offset); + obj.clientTimestamp = buf.getLongLE(offset + 1); + obj.activeSlot = buf.getIntLE(offset + 9); + if ((nullBits & 2) != 0) { + obj.screenPoint = Vector2f.deserialize(buf, offset + 13); + } + + if ((nullBits & 4) != 0) { + obj.mouseButton = MouseButtonEvent.deserialize(buf, offset + 21); + } + + if ((nullBits & 16) != 0) { + obj.worldInteraction = WorldInteraction.deserialize(buf, offset + 24); + } + + if ((nullBits & 1) != 0) { + int varPos0 = offset + 52 + buf.getIntLE(offset + 44); + int itemInHandIdLen = VarInt.peek(buf, varPos0); + if (itemInHandIdLen < 0) { + throw ProtocolException.negativeLength("ItemInHandId", itemInHandIdLen); + } + + if (itemInHandIdLen > 4096000) { + throw ProtocolException.stringTooLong("ItemInHandId", itemInHandIdLen, 4096000); + } + + obj.itemInHandId = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 8) != 0) { + int varPos1 = offset + 52 + buf.getIntLE(offset + 48); + obj.mouseMotion = MouseMotionEvent.deserialize(buf, varPos1); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 52; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 44); + int pos0 = offset + 52 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 8) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 48); + int pos1 = offset + 52 + fieldOffset1; + pos1 += MouseMotionEvent.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.itemInHandId != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.screenPoint != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.mouseButton != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.mouseMotion != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.worldInteraction != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + buf.writeLongLE(this.clientTimestamp); + buf.writeIntLE(this.activeSlot); + if (this.screenPoint != null) { + this.screenPoint.serialize(buf); + } else { + buf.writeZero(8); + } + + if (this.mouseButton != null) { + this.mouseButton.serialize(buf); + } else { + buf.writeZero(3); + } + + if (this.worldInteraction != null) { + this.worldInteraction.serialize(buf); + } else { + buf.writeZero(20); + } + + int itemInHandIdOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int mouseMotionOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.itemInHandId != null) { + buf.setIntLE(itemInHandIdOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.itemInHandId, 4096000); + } else { + buf.setIntLE(itemInHandIdOffsetSlot, -1); + } + + if (this.mouseMotion != null) { + buf.setIntLE(mouseMotionOffsetSlot, buf.writerIndex() - varBlockStart); + this.mouseMotion.serialize(buf); + } else { + buf.setIntLE(mouseMotionOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 52; + if (this.itemInHandId != null) { + size += PacketIO.stringSize(this.itemInHandId); + } + + if (this.mouseMotion != null) { + size += this.mouseMotion.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 52) { + return ValidationResult.error("Buffer too small: expected at least 52 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int itemInHandIdOffset = buffer.getIntLE(offset + 44); + if (itemInHandIdOffset < 0) { + return ValidationResult.error("Invalid offset for ItemInHandId"); + } + + int pos = offset + 52 + itemInHandIdOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ItemInHandId"); + } + + int itemInHandIdLen = VarInt.peek(buffer, pos); + if (itemInHandIdLen < 0) { + return ValidationResult.error("Invalid string length for ItemInHandId"); + } + + if (itemInHandIdLen > 4096000) { + return ValidationResult.error("ItemInHandId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += itemInHandIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ItemInHandId"); + } + } + + if ((nullBits & 8) != 0) { + int mouseMotionOffset = buffer.getIntLE(offset + 48); + if (mouseMotionOffset < 0) { + return ValidationResult.error("Invalid offset for MouseMotion"); + } + + int posx = offset + 52 + mouseMotionOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MouseMotion"); + } + + ValidationResult mouseMotionResult = MouseMotionEvent.validateStructure(buffer, posx); + if (!mouseMotionResult.isValid()) { + return ValidationResult.error("Invalid MouseMotion: " + mouseMotionResult.error()); + } + + posx += MouseMotionEvent.computeBytesConsumed(buffer, posx); + } + + return ValidationResult.OK; + } + } + + public MouseInteraction clone() { + MouseInteraction copy = new MouseInteraction(); + copy.clientTimestamp = this.clientTimestamp; + copy.activeSlot = this.activeSlot; + copy.itemInHandId = this.itemInHandId; + copy.screenPoint = this.screenPoint != null ? this.screenPoint.clone() : null; + copy.mouseButton = this.mouseButton != null ? this.mouseButton.clone() : null; + copy.mouseMotion = this.mouseMotion != null ? this.mouseMotion.clone() : null; + copy.worldInteraction = this.worldInteraction != null ? this.worldInteraction.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof MouseInteraction other) + ? false + : this.clientTimestamp == other.clientTimestamp + && this.activeSlot == other.activeSlot + && Objects.equals(this.itemInHandId, other.itemInHandId) + && Objects.equals(this.screenPoint, other.screenPoint) + && Objects.equals(this.mouseButton, other.mouseButton) + && Objects.equals(this.mouseMotion, other.mouseMotion) + && Objects.equals(this.worldInteraction, other.worldInteraction); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.clientTimestamp, this.activeSlot, this.itemInHandId, this.screenPoint, this.mouseButton, this.mouseMotion, this.worldInteraction); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/RemoveMapMarker.java b/src/com/hypixel/hytale/protocol/packets/player/RemoveMapMarker.java new file mode 100644 index 0000000..7111984 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/RemoveMapMarker.java @@ -0,0 +1,143 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RemoveMapMarker implements Packet { + public static final int PACKET_ID = 119; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 16384006; + @Nullable + public String markerId; + + @Override + public int getId() { + return 119; + } + + public RemoveMapMarker() { + } + + public RemoveMapMarker(@Nullable String markerId) { + this.markerId = markerId; + } + + public RemoveMapMarker(@Nonnull RemoveMapMarker other) { + this.markerId = other.markerId; + } + + @Nonnull + public static RemoveMapMarker deserialize(@Nonnull ByteBuf buf, int offset) { + RemoveMapMarker obj = new RemoveMapMarker(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int markerIdLen = VarInt.peek(buf, pos); + if (markerIdLen < 0) { + throw ProtocolException.negativeLength("MarkerId", markerIdLen); + } + + if (markerIdLen > 4096000) { + throw ProtocolException.stringTooLong("MarkerId", markerIdLen, 4096000); + } + + int markerIdVarLen = VarInt.length(buf, pos); + obj.markerId = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += markerIdVarLen + markerIdLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.markerId != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.markerId != null) { + PacketIO.writeVarString(buf, this.markerId, 4096000); + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.markerId != null) { + size += PacketIO.stringSize(this.markerId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int markerIdLen = VarInt.peek(buffer, pos); + if (markerIdLen < 0) { + return ValidationResult.error("Invalid string length for MarkerId"); + } + + if (markerIdLen > 4096000) { + return ValidationResult.error("MarkerId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += markerIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading MarkerId"); + } + } + + return ValidationResult.OK; + } + } + + public RemoveMapMarker clone() { + RemoveMapMarker copy = new RemoveMapMarker(); + copy.markerId = this.markerId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof RemoveMapMarker other ? Objects.equals(this.markerId, other.markerId) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.markerId); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/ReticleEvent.java b/src/com/hypixel/hytale/protocol/packets/player/ReticleEvent.java new file mode 100644 index 0000000..1864e3d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/ReticleEvent.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class ReticleEvent implements Packet { + public static final int PACKET_ID = 113; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 4; + public int eventIndex; + + @Override + public int getId() { + return 113; + } + + public ReticleEvent() { + } + + public ReticleEvent(int eventIndex) { + this.eventIndex = eventIndex; + } + + public ReticleEvent(@Nonnull ReticleEvent other) { + this.eventIndex = other.eventIndex; + } + + @Nonnull + public static ReticleEvent deserialize(@Nonnull ByteBuf buf, int offset) { + ReticleEvent obj = new ReticleEvent(); + obj.eventIndex = buf.getIntLE(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 4; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.eventIndex); + } + + @Override + public int computeSize() { + return 4; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 4 ? ValidationResult.error("Buffer too small: expected at least 4 bytes") : ValidationResult.OK; + } + + public ReticleEvent clone() { + ReticleEvent copy = new ReticleEvent(); + copy.eventIndex = this.eventIndex; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof ReticleEvent other ? this.eventIndex == other.eventIndex : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.eventIndex); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/SaveHotbar.java b/src/com/hypixel/hytale/protocol/packets/player/SaveHotbar.java new file mode 100644 index 0000000..cec3b57 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/SaveHotbar.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SaveHotbar implements Packet { + public static final int PACKET_ID = 107; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public byte inventoryRow; + + @Override + public int getId() { + return 107; + } + + public SaveHotbar() { + } + + public SaveHotbar(byte inventoryRow) { + this.inventoryRow = inventoryRow; + } + + public SaveHotbar(@Nonnull SaveHotbar other) { + this.inventoryRow = other.inventoryRow; + } + + @Nonnull + public static SaveHotbar deserialize(@Nonnull ByteBuf buf, int offset) { + SaveHotbar obj = new SaveHotbar(); + obj.inventoryRow = buf.getByte(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.inventoryRow); + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public SaveHotbar clone() { + SaveHotbar copy = new SaveHotbar(); + copy.inventoryRow = this.inventoryRow; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof SaveHotbar other ? this.inventoryRow == other.inventoryRow : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.inventoryRow); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/SetBlockPlacementOverride.java b/src/com/hypixel/hytale/protocol/packets/player/SetBlockPlacementOverride.java new file mode 100644 index 0000000..1371266 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/SetBlockPlacementOverride.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SetBlockPlacementOverride implements Packet { + public static final int PACKET_ID = 103; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean enabled; + + @Override + public int getId() { + return 103; + } + + public SetBlockPlacementOverride() { + } + + public SetBlockPlacementOverride(boolean enabled) { + this.enabled = enabled; + } + + public SetBlockPlacementOverride(@Nonnull SetBlockPlacementOverride other) { + this.enabled = other.enabled; + } + + @Nonnull + public static SetBlockPlacementOverride deserialize(@Nonnull ByteBuf buf, int offset) { + SetBlockPlacementOverride obj = new SetBlockPlacementOverride(); + obj.enabled = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.enabled ? 1 : 0); + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public SetBlockPlacementOverride clone() { + SetBlockPlacementOverride copy = new SetBlockPlacementOverride(); + copy.enabled = this.enabled; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof SetBlockPlacementOverride other ? this.enabled == other.enabled : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.enabled); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/SetClientId.java b/src/com/hypixel/hytale/protocol/packets/player/SetClientId.java new file mode 100644 index 0000000..eca8b0b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/SetClientId.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SetClientId implements Packet { + public static final int PACKET_ID = 100; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 4; + public int clientId; + + @Override + public int getId() { + return 100; + } + + public SetClientId() { + } + + public SetClientId(int clientId) { + this.clientId = clientId; + } + + public SetClientId(@Nonnull SetClientId other) { + this.clientId = other.clientId; + } + + @Nonnull + public static SetClientId deserialize(@Nonnull ByteBuf buf, int offset) { + SetClientId obj = new SetClientId(); + obj.clientId = buf.getIntLE(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 4; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.clientId); + } + + @Override + public int computeSize() { + return 4; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 4 ? ValidationResult.error("Buffer too small: expected at least 4 bytes") : ValidationResult.OK; + } + + public SetClientId clone() { + SetClientId copy = new SetClientId(); + copy.clientId = this.clientId; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof SetClientId other ? this.clientId == other.clientId : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.clientId); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/SetGameMode.java b/src/com/hypixel/hytale/protocol/packets/player/SetGameMode.java new file mode 100644 index 0000000..9f86db9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/SetGameMode.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SetGameMode implements Packet { + public static final int PACKET_ID = 101; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + @Nonnull + public GameMode gameMode = GameMode.Adventure; + + @Override + public int getId() { + return 101; + } + + public SetGameMode() { + } + + public SetGameMode(@Nonnull GameMode gameMode) { + this.gameMode = gameMode; + } + + public SetGameMode(@Nonnull SetGameMode other) { + this.gameMode = other.gameMode; + } + + @Nonnull + public static SetGameMode deserialize(@Nonnull ByteBuf buf, int offset) { + SetGameMode obj = new SetGameMode(); + obj.gameMode = GameMode.fromValue(buf.getByte(offset + 0)); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.gameMode.getValue()); + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public SetGameMode clone() { + SetGameMode copy = new SetGameMode(); + copy.gameMode = this.gameMode; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof SetGameMode other ? Objects.equals(this.gameMode, other.gameMode) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.gameMode); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/SetMovementStates.java b/src/com/hypixel/hytale/protocol/packets/player/SetMovementStates.java new file mode 100644 index 0000000..c4de0af --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/SetMovementStates.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.SavedMovementStates; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SetMovementStates implements Packet { + public static final int PACKET_ID = 102; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 2; + @Nullable + public SavedMovementStates movementStates; + + @Override + public int getId() { + return 102; + } + + public SetMovementStates() { + } + + public SetMovementStates(@Nullable SavedMovementStates movementStates) { + this.movementStates = movementStates; + } + + public SetMovementStates(@Nonnull SetMovementStates other) { + this.movementStates = other.movementStates; + } + + @Nonnull + public static SetMovementStates deserialize(@Nonnull ByteBuf buf, int offset) { + SetMovementStates obj = new SetMovementStates(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.movementStates = SavedMovementStates.deserialize(buf, offset + 1); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 2; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.movementStates != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.movementStates != null) { + this.movementStates.serialize(buf); + } else { + buf.writeZero(1); + } + } + + @Override + public int computeSize() { + return 2; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 2 ? ValidationResult.error("Buffer too small: expected at least 2 bytes") : ValidationResult.OK; + } + + public SetMovementStates clone() { + SetMovementStates copy = new SetMovementStates(); + copy.movementStates = this.movementStates != null ? this.movementStates.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof SetMovementStates other ? Objects.equals(this.movementStates, other.movementStates) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.movementStates); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/SyncPlayerPreferences.java b/src/com/hypixel/hytale/protocol/packets/player/SyncPlayerPreferences.java new file mode 100644 index 0000000..d0619ff --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/SyncPlayerPreferences.java @@ -0,0 +1,154 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.PickupLocation; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SyncPlayerPreferences implements Packet { + public static final int PACKET_ID = 116; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public boolean showEntityMarkers; + @Nonnull + public PickupLocation armorItemsPreferredPickupLocation = PickupLocation.Hotbar; + @Nonnull + public PickupLocation weaponAndToolItemsPreferredPickupLocation = PickupLocation.Hotbar; + @Nonnull + public PickupLocation usableItemsItemsPreferredPickupLocation = PickupLocation.Hotbar; + @Nonnull + public PickupLocation solidBlockItemsPreferredPickupLocation = PickupLocation.Hotbar; + @Nonnull + public PickupLocation miscItemsPreferredPickupLocation = PickupLocation.Hotbar; + public boolean allowNPCDetection; + public boolean respondToHit; + + @Override + public int getId() { + return 116; + } + + public SyncPlayerPreferences() { + } + + public SyncPlayerPreferences( + boolean showEntityMarkers, + @Nonnull PickupLocation armorItemsPreferredPickupLocation, + @Nonnull PickupLocation weaponAndToolItemsPreferredPickupLocation, + @Nonnull PickupLocation usableItemsItemsPreferredPickupLocation, + @Nonnull PickupLocation solidBlockItemsPreferredPickupLocation, + @Nonnull PickupLocation miscItemsPreferredPickupLocation, + boolean allowNPCDetection, + boolean respondToHit + ) { + this.showEntityMarkers = showEntityMarkers; + this.armorItemsPreferredPickupLocation = armorItemsPreferredPickupLocation; + this.weaponAndToolItemsPreferredPickupLocation = weaponAndToolItemsPreferredPickupLocation; + this.usableItemsItemsPreferredPickupLocation = usableItemsItemsPreferredPickupLocation; + this.solidBlockItemsPreferredPickupLocation = solidBlockItemsPreferredPickupLocation; + this.miscItemsPreferredPickupLocation = miscItemsPreferredPickupLocation; + this.allowNPCDetection = allowNPCDetection; + this.respondToHit = respondToHit; + } + + public SyncPlayerPreferences(@Nonnull SyncPlayerPreferences other) { + this.showEntityMarkers = other.showEntityMarkers; + this.armorItemsPreferredPickupLocation = other.armorItemsPreferredPickupLocation; + this.weaponAndToolItemsPreferredPickupLocation = other.weaponAndToolItemsPreferredPickupLocation; + this.usableItemsItemsPreferredPickupLocation = other.usableItemsItemsPreferredPickupLocation; + this.solidBlockItemsPreferredPickupLocation = other.solidBlockItemsPreferredPickupLocation; + this.miscItemsPreferredPickupLocation = other.miscItemsPreferredPickupLocation; + this.allowNPCDetection = other.allowNPCDetection; + this.respondToHit = other.respondToHit; + } + + @Nonnull + public static SyncPlayerPreferences deserialize(@Nonnull ByteBuf buf, int offset) { + SyncPlayerPreferences obj = new SyncPlayerPreferences(); + obj.showEntityMarkers = buf.getByte(offset + 0) != 0; + obj.armorItemsPreferredPickupLocation = PickupLocation.fromValue(buf.getByte(offset + 1)); + obj.weaponAndToolItemsPreferredPickupLocation = PickupLocation.fromValue(buf.getByte(offset + 2)); + obj.usableItemsItemsPreferredPickupLocation = PickupLocation.fromValue(buf.getByte(offset + 3)); + obj.solidBlockItemsPreferredPickupLocation = PickupLocation.fromValue(buf.getByte(offset + 4)); + obj.miscItemsPreferredPickupLocation = PickupLocation.fromValue(buf.getByte(offset + 5)); + obj.allowNPCDetection = buf.getByte(offset + 6) != 0; + obj.respondToHit = buf.getByte(offset + 7) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.showEntityMarkers ? 1 : 0); + buf.writeByte(this.armorItemsPreferredPickupLocation.getValue()); + buf.writeByte(this.weaponAndToolItemsPreferredPickupLocation.getValue()); + buf.writeByte(this.usableItemsItemsPreferredPickupLocation.getValue()); + buf.writeByte(this.solidBlockItemsPreferredPickupLocation.getValue()); + buf.writeByte(this.miscItemsPreferredPickupLocation.getValue()); + buf.writeByte(this.allowNPCDetection ? 1 : 0); + buf.writeByte(this.respondToHit ? 1 : 0); + } + + @Override + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public SyncPlayerPreferences clone() { + SyncPlayerPreferences copy = new SyncPlayerPreferences(); + copy.showEntityMarkers = this.showEntityMarkers; + copy.armorItemsPreferredPickupLocation = this.armorItemsPreferredPickupLocation; + copy.weaponAndToolItemsPreferredPickupLocation = this.weaponAndToolItemsPreferredPickupLocation; + copy.usableItemsItemsPreferredPickupLocation = this.usableItemsItemsPreferredPickupLocation; + copy.solidBlockItemsPreferredPickupLocation = this.solidBlockItemsPreferredPickupLocation; + copy.miscItemsPreferredPickupLocation = this.miscItemsPreferredPickupLocation; + copy.allowNPCDetection = this.allowNPCDetection; + copy.respondToHit = this.respondToHit; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SyncPlayerPreferences other) + ? false + : this.showEntityMarkers == other.showEntityMarkers + && Objects.equals(this.armorItemsPreferredPickupLocation, other.armorItemsPreferredPickupLocation) + && Objects.equals(this.weaponAndToolItemsPreferredPickupLocation, other.weaponAndToolItemsPreferredPickupLocation) + && Objects.equals(this.usableItemsItemsPreferredPickupLocation, other.usableItemsItemsPreferredPickupLocation) + && Objects.equals(this.solidBlockItemsPreferredPickupLocation, other.solidBlockItemsPreferredPickupLocation) + && Objects.equals(this.miscItemsPreferredPickupLocation, other.miscItemsPreferredPickupLocation) + && this.allowNPCDetection == other.allowNPCDetection + && this.respondToHit == other.respondToHit; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.showEntityMarkers, + this.armorItemsPreferredPickupLocation, + this.weaponAndToolItemsPreferredPickupLocation, + this.usableItemsItemsPreferredPickupLocation, + this.solidBlockItemsPreferredPickupLocation, + this.miscItemsPreferredPickupLocation, + this.allowNPCDetection, + this.respondToHit + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/UpdateMemoriesFeatureStatus.java b/src/com/hypixel/hytale/protocol/packets/player/UpdateMemoriesFeatureStatus.java new file mode 100644 index 0000000..f993158 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/UpdateMemoriesFeatureStatus.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class UpdateMemoriesFeatureStatus implements Packet { + public static final int PACKET_ID = 118; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean isFeatureUnlocked; + + @Override + public int getId() { + return 118; + } + + public UpdateMemoriesFeatureStatus() { + } + + public UpdateMemoriesFeatureStatus(boolean isFeatureUnlocked) { + this.isFeatureUnlocked = isFeatureUnlocked; + } + + public UpdateMemoriesFeatureStatus(@Nonnull UpdateMemoriesFeatureStatus other) { + this.isFeatureUnlocked = other.isFeatureUnlocked; + } + + @Nonnull + public static UpdateMemoriesFeatureStatus deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateMemoriesFeatureStatus obj = new UpdateMemoriesFeatureStatus(); + obj.isFeatureUnlocked = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.isFeatureUnlocked ? 1 : 0); + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public UpdateMemoriesFeatureStatus clone() { + UpdateMemoriesFeatureStatus copy = new UpdateMemoriesFeatureStatus(); + copy.isFeatureUnlocked = this.isFeatureUnlocked; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof UpdateMemoriesFeatureStatus other ? this.isFeatureUnlocked == other.isFeatureUnlocked : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.isFeatureUnlocked); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/player/UpdateMovementSettings.java b/src/com/hypixel/hytale/protocol/packets/player/UpdateMovementSettings.java new file mode 100644 index 0000000..591d1bc --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/player/UpdateMovementSettings.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.protocol.packets.player; + +import com.hypixel.hytale.protocol.MovementSettings; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateMovementSettings implements Packet { + public static final int PACKET_ID = 110; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 252; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 252; + public static final int MAX_SIZE = 252; + @Nullable + public MovementSettings movementSettings; + + @Override + public int getId() { + return 110; + } + + public UpdateMovementSettings() { + } + + public UpdateMovementSettings(@Nullable MovementSettings movementSettings) { + this.movementSettings = movementSettings; + } + + public UpdateMovementSettings(@Nonnull UpdateMovementSettings other) { + this.movementSettings = other.movementSettings; + } + + @Nonnull + public static UpdateMovementSettings deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateMovementSettings obj = new UpdateMovementSettings(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.movementSettings = MovementSettings.deserialize(buf, offset + 1); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 252; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.movementSettings != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.movementSettings != null) { + this.movementSettings.serialize(buf); + } else { + buf.writeZero(251); + } + } + + @Override + public int computeSize() { + return 252; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 252 ? ValidationResult.error("Buffer too small: expected at least 252 bytes") : ValidationResult.OK; + } + + public UpdateMovementSettings clone() { + UpdateMovementSettings copy = new UpdateMovementSettings(); + copy.movementSettings = this.movementSettings != null ? this.movementSettings.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof UpdateMovementSettings other ? Objects.equals(this.movementSettings, other.movementSettings) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.movementSettings); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/serveraccess/Access.java b/src/com/hypixel/hytale/protocol/packets/serveraccess/Access.java new file mode 100644 index 0000000..15c1d77 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/serveraccess/Access.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol.packets.serveraccess; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum Access { + Private(0), + LAN(1), + Friend(2), + Open(3); + + public static final Access[] VALUES = values(); + private final int value; + + private Access(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static Access fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("Access", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/serveraccess/RequestServerAccess.java b/src/com/hypixel/hytale/protocol/packets/serveraccess/RequestServerAccess.java new file mode 100644 index 0000000..7585823 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/serveraccess/RequestServerAccess.java @@ -0,0 +1,86 @@ +package com.hypixel.hytale.protocol.packets.serveraccess; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class RequestServerAccess implements Packet { + public static final int PACKET_ID = 250; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 3; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 3; + public static final int MAX_SIZE = 3; + @Nonnull + public Access access = Access.Private; + public short externalPort; + + @Override + public int getId() { + return 250; + } + + public RequestServerAccess() { + } + + public RequestServerAccess(@Nonnull Access access, short externalPort) { + this.access = access; + this.externalPort = externalPort; + } + + public RequestServerAccess(@Nonnull RequestServerAccess other) { + this.access = other.access; + this.externalPort = other.externalPort; + } + + @Nonnull + public static RequestServerAccess deserialize(@Nonnull ByteBuf buf, int offset) { + RequestServerAccess obj = new RequestServerAccess(); + obj.access = Access.fromValue(buf.getByte(offset + 0)); + obj.externalPort = buf.getShortLE(offset + 1); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 3; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.access.getValue()); + buf.writeShortLE(this.externalPort); + } + + @Override + public int computeSize() { + return 3; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 3 ? ValidationResult.error("Buffer too small: expected at least 3 bytes") : ValidationResult.OK; + } + + public RequestServerAccess clone() { + RequestServerAccess copy = new RequestServerAccess(); + copy.access = this.access; + copy.externalPort = this.externalPort; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof RequestServerAccess other) ? false : Objects.equals(this.access, other.access) && this.externalPort == other.externalPort; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.access, this.externalPort); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/serveraccess/SetServerAccess.java b/src/com/hypixel/hytale/protocol/packets/serveraccess/SetServerAccess.java new file mode 100644 index 0000000..c3195ce --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/serveraccess/SetServerAccess.java @@ -0,0 +1,150 @@ +package com.hypixel.hytale.protocol.packets.serveraccess; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SetServerAccess implements Packet { + public static final int PACKET_ID = 252; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 16384007; + @Nonnull + public Access access = Access.Private; + @Nullable + public String password; + + @Override + public int getId() { + return 252; + } + + public SetServerAccess() { + } + + public SetServerAccess(@Nonnull Access access, @Nullable String password) { + this.access = access; + this.password = password; + } + + public SetServerAccess(@Nonnull SetServerAccess other) { + this.access = other.access; + this.password = other.password; + } + + @Nonnull + public static SetServerAccess deserialize(@Nonnull ByteBuf buf, int offset) { + SetServerAccess obj = new SetServerAccess(); + byte nullBits = buf.getByte(offset); + obj.access = Access.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int passwordLen = VarInt.peek(buf, pos); + if (passwordLen < 0) { + throw ProtocolException.negativeLength("Password", passwordLen); + } + + if (passwordLen > 4096000) { + throw ProtocolException.stringTooLong("Password", passwordLen, 4096000); + } + + int passwordVarLen = VarInt.length(buf, pos); + obj.password = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += passwordVarLen + passwordLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.password != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.access.getValue()); + if (this.password != null) { + PacketIO.writeVarString(buf, this.password, 4096000); + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.password != null) { + size += PacketIO.stringSize(this.password); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int passwordLen = VarInt.peek(buffer, pos); + if (passwordLen < 0) { + return ValidationResult.error("Invalid string length for Password"); + } + + if (passwordLen > 4096000) { + return ValidationResult.error("Password exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += passwordLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Password"); + } + } + + return ValidationResult.OK; + } + } + + public SetServerAccess clone() { + SetServerAccess copy = new SetServerAccess(); + copy.access = this.access; + copy.password = this.password; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SetServerAccess other) ? false : Objects.equals(this.access, other.access) && Objects.equals(this.password, other.password); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.access, this.password); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/serveraccess/UpdateServerAccess.java b/src/com/hypixel/hytale/protocol/packets/serveraccess/UpdateServerAccess.java new file mode 100644 index 0000000..8be67eb --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/serveraccess/UpdateServerAccess.java @@ -0,0 +1,187 @@ +package com.hypixel.hytale.protocol.packets.serveraccess; + +import com.hypixel.hytale.protocol.HostAddress; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateServerAccess implements Packet { + public static final int PACKET_ID = 251; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 2; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 2; + public static final int MAX_SIZE = 1677721600; + @Nonnull + public Access access = Access.Private; + @Nullable + public HostAddress[] hosts; + + @Override + public int getId() { + return 251; + } + + public UpdateServerAccess() { + } + + public UpdateServerAccess(@Nonnull Access access, @Nullable HostAddress[] hosts) { + this.access = access; + this.hosts = hosts; + } + + public UpdateServerAccess(@Nonnull UpdateServerAccess other) { + this.access = other.access; + this.hosts = other.hosts; + } + + @Nonnull + public static UpdateServerAccess deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateServerAccess obj = new UpdateServerAccess(); + byte nullBits = buf.getByte(offset); + obj.access = Access.fromValue(buf.getByte(offset + 1)); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int hostsCount = VarInt.peek(buf, pos); + if (hostsCount < 0) { + throw ProtocolException.negativeLength("Hosts", hostsCount); + } + + if (hostsCount > 4096000) { + throw ProtocolException.arrayTooLong("Hosts", hostsCount, 4096000); + } + + int hostsVarLen = VarInt.size(hostsCount); + if (pos + hostsVarLen + hostsCount * 2L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Hosts", pos + hostsVarLen + hostsCount * 2, buf.readableBytes()); + } + + pos += hostsVarLen; + obj.hosts = new HostAddress[hostsCount]; + + for (int i = 0; i < hostsCount; i++) { + obj.hosts[i] = HostAddress.deserialize(buf, pos); + pos += HostAddress.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += HostAddress.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.hosts != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.access.getValue()); + if (this.hosts != null) { + if (this.hosts.length > 4096000) { + throw ProtocolException.arrayTooLong("Hosts", this.hosts.length, 4096000); + } + + VarInt.write(buf, this.hosts.length); + + for (HostAddress item : this.hosts) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 2; + if (this.hosts != null) { + int hostsSize = 0; + + for (HostAddress elem : this.hosts) { + hostsSize += elem.computeSize(); + } + + size += VarInt.size(this.hosts.length) + hostsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 2) { + return ValidationResult.error("Buffer too small: expected at least 2 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 2; + if ((nullBits & 1) != 0) { + int hostsCount = VarInt.peek(buffer, pos); + if (hostsCount < 0) { + return ValidationResult.error("Invalid array count for Hosts"); + } + + if (hostsCount > 4096000) { + return ValidationResult.error("Hosts exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < hostsCount; i++) { + ValidationResult structResult = HostAddress.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid HostAddress in Hosts[" + i + "]: " + structResult.error()); + } + + pos += HostAddress.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateServerAccess clone() { + UpdateServerAccess copy = new UpdateServerAccess(); + copy.access = this.access; + copy.hosts = this.hosts != null ? Arrays.stream(this.hosts).map(e -> e.clone()).toArray(HostAddress[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateServerAccess other) + ? false + : Objects.equals(this.access, other.access) && Arrays.equals((Object[])this.hosts, (Object[])other.hosts); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.access); + return 31 * result + Arrays.hashCode((Object[])this.hosts); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/setup/AssetFinalize.java b/src/com/hypixel/hytale/protocol/packets/setup/AssetFinalize.java new file mode 100644 index 0000000..084d6f9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/setup/AssetFinalize.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.protocol.packets.setup; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class AssetFinalize implements Packet { + public static final int PACKET_ID = 26; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public AssetFinalize() { + } + + @Override + public int getId() { + return 26; + } + + @Nonnull + public static AssetFinalize deserialize(@Nonnull ByteBuf buf, int offset) { + return new AssetFinalize(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public AssetFinalize clone() { + return new AssetFinalize(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof AssetFinalize other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/setup/AssetInitialize.java b/src/com/hypixel/hytale/protocol/packets/setup/AssetInitialize.java new file mode 100644 index 0000000..9fe4b2e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/setup/AssetInitialize.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.protocol.packets.setup; + +import com.hypixel.hytale.protocol.Asset; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class AssetInitialize implements Packet { + public static final int PACKET_ID = 24; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 2121; + @Nonnull + public Asset asset = new Asset(); + public int size; + + @Override + public int getId() { + return 24; + } + + public AssetInitialize() { + } + + public AssetInitialize(@Nonnull Asset asset, int size) { + this.asset = asset; + this.size = size; + } + + public AssetInitialize(@Nonnull AssetInitialize other) { + this.asset = other.asset; + this.size = other.size; + } + + @Nonnull + public static AssetInitialize deserialize(@Nonnull ByteBuf buf, int offset) { + AssetInitialize obj = new AssetInitialize(); + obj.size = buf.getIntLE(offset + 0); + int pos = offset + 4; + obj.asset = Asset.deserialize(buf, pos); + pos += Asset.computeBytesConsumed(buf, pos); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + int pos = offset + 4; + pos += Asset.computeBytesConsumed(buf, pos); + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.size); + this.asset.serialize(buf); + } + + @Override + public int computeSize() { + int size = 4; + return size + this.asset.computeSize(); + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 4) { + return ValidationResult.error("Buffer too small: expected at least 4 bytes"); + } else { + int pos = offset + 4; + ValidationResult assetResult = Asset.validateStructure(buffer, pos); + if (!assetResult.isValid()) { + return ValidationResult.error("Invalid Asset: " + assetResult.error()); + } else { + pos += Asset.computeBytesConsumed(buffer, pos); + return ValidationResult.OK; + } + } + } + + public AssetInitialize clone() { + AssetInitialize copy = new AssetInitialize(); + copy.asset = this.asset.clone(); + copy.size = this.size; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof AssetInitialize other) ? false : Objects.equals(this.asset, other.asset) && this.size == other.size; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.asset, this.size); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/setup/AssetPart.java b/src/com/hypixel/hytale/protocol/packets/setup/AssetPart.java new file mode 100644 index 0000000..a523baf --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/setup/AssetPart.java @@ -0,0 +1,161 @@ +package com.hypixel.hytale.protocol.packets.setup; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetPart implements Packet { + public static final int PACKET_ID = 25; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 4096006; + @Nullable + public byte[] part; + + @Override + public int getId() { + return 25; + } + + public AssetPart() { + } + + public AssetPart(@Nullable byte[] part) { + this.part = part; + } + + public AssetPart(@Nonnull AssetPart other) { + this.part = other.part; + } + + @Nonnull + public static AssetPart deserialize(@Nonnull ByteBuf buf, int offset) { + AssetPart obj = new AssetPart(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int partCount = VarInt.peek(buf, pos); + if (partCount < 0) { + throw ProtocolException.negativeLength("Part", partCount); + } + + if (partCount > 4096000) { + throw ProtocolException.arrayTooLong("Part", partCount, 4096000); + } + + int partVarLen = VarInt.size(partCount); + if (pos + partVarLen + partCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Part", pos + partVarLen + partCount * 1, buf.readableBytes()); + } + + pos += partVarLen; + obj.part = new byte[partCount]; + + for (int i = 0; i < partCount; i++) { + obj.part[i] = buf.getByte(pos + i * 1); + } + + pos += partCount * 1; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 1; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.part != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.part != null) { + if (this.part.length > 4096000) { + throw ProtocolException.arrayTooLong("Part", this.part.length, 4096000); + } + + VarInt.write(buf, this.part.length); + + for (byte item : this.part) { + buf.writeByte(item); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.part != null) { + size += VarInt.size(this.part.length) + this.part.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int partCount = VarInt.peek(buffer, pos); + if (partCount < 0) { + return ValidationResult.error("Invalid array count for Part"); + } + + if (partCount > 4096000) { + return ValidationResult.error("Part exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += partCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Part"); + } + } + + return ValidationResult.OK; + } + } + + public AssetPart clone() { + AssetPart copy = new AssetPart(); + copy.part = this.part != null ? Arrays.copyOf(this.part, this.part.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof AssetPart other ? Arrays.equals(this.part, other.part) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode(this.part); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/setup/ClientFeature.java b/src/com/hypixel/hytale/protocol/packets/setup/ClientFeature.java new file mode 100644 index 0000000..bd48869 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/setup/ClientFeature.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.protocol.packets.setup; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum ClientFeature { + SplitVelocity(0), + Mantling(1), + SprintForce(2), + CrouchSlide(3), + SafetyRoll(4), + DisplayHealthBars(5), + DisplayCombatText(6); + + public static final ClientFeature[] VALUES = values(); + private final int value; + + private ClientFeature(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static ClientFeature fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("ClientFeature", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/setup/PlayerOptions.java b/src/com/hypixel/hytale/protocol/packets/setup/PlayerOptions.java new file mode 100644 index 0000000..0329d83 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/setup/PlayerOptions.java @@ -0,0 +1,122 @@ +package com.hypixel.hytale.protocol.packets.setup; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.PlayerSkin; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerOptions implements Packet { + public static final int PACKET_ID = 33; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 327680184; + @Nullable + public PlayerSkin skin; + + @Override + public int getId() { + return 33; + } + + public PlayerOptions() { + } + + public PlayerOptions(@Nullable PlayerSkin skin) { + this.skin = skin; + } + + public PlayerOptions(@Nonnull PlayerOptions other) { + this.skin = other.skin; + } + + @Nonnull + public static PlayerOptions deserialize(@Nonnull ByteBuf buf, int offset) { + PlayerOptions obj = new PlayerOptions(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + obj.skin = PlayerSkin.deserialize(buf, pos); + pos += PlayerSkin.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + pos += PlayerSkin.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.skin != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.skin != null) { + this.skin.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.skin != null) { + size += this.skin.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + ValidationResult skinResult = PlayerSkin.validateStructure(buffer, pos); + if (!skinResult.isValid()) { + return ValidationResult.error("Invalid Skin: " + skinResult.error()); + } + + pos += PlayerSkin.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public PlayerOptions clone() { + PlayerOptions copy = new PlayerOptions(); + copy.skin = this.skin != null ? this.skin.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof PlayerOptions other ? Objects.equals(this.skin, other.skin) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.skin); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/setup/RemoveAssets.java b/src/com/hypixel/hytale/protocol/packets/setup/RemoveAssets.java new file mode 100644 index 0000000..29319ac --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/setup/RemoveAssets.java @@ -0,0 +1,176 @@ +package com.hypixel.hytale.protocol.packets.setup; + +import com.hypixel.hytale.protocol.Asset; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RemoveAssets implements Packet { + public static final int PACKET_ID = 27; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public Asset[] asset; + + @Override + public int getId() { + return 27; + } + + public RemoveAssets() { + } + + public RemoveAssets(@Nullable Asset[] asset) { + this.asset = asset; + } + + public RemoveAssets(@Nonnull RemoveAssets other) { + this.asset = other.asset; + } + + @Nonnull + public static RemoveAssets deserialize(@Nonnull ByteBuf buf, int offset) { + RemoveAssets obj = new RemoveAssets(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int assetCount = VarInt.peek(buf, pos); + if (assetCount < 0) { + throw ProtocolException.negativeLength("Asset", assetCount); + } + + if (assetCount > 4096000) { + throw ProtocolException.arrayTooLong("Asset", assetCount, 4096000); + } + + int assetVarLen = VarInt.size(assetCount); + if (pos + assetVarLen + assetCount * 64L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Asset", pos + assetVarLen + assetCount * 64, buf.readableBytes()); + } + + pos += assetVarLen; + obj.asset = new Asset[assetCount]; + + for (int i = 0; i < assetCount; i++) { + obj.asset[i] = Asset.deserialize(buf, pos); + pos += Asset.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += Asset.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.asset != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.asset != null) { + if (this.asset.length > 4096000) { + throw ProtocolException.arrayTooLong("Asset", this.asset.length, 4096000); + } + + VarInt.write(buf, this.asset.length); + + for (Asset item : this.asset) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.asset != null) { + int assetSize = 0; + + for (Asset elem : this.asset) { + assetSize += elem.computeSize(); + } + + size += VarInt.size(this.asset.length) + assetSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int assetCount = VarInt.peek(buffer, pos); + if (assetCount < 0) { + return ValidationResult.error("Invalid array count for Asset"); + } + + if (assetCount > 4096000) { + return ValidationResult.error("Asset exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < assetCount; i++) { + ValidationResult structResult = Asset.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid Asset in Asset[" + i + "]: " + structResult.error()); + } + + pos += Asset.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public RemoveAssets clone() { + RemoveAssets copy = new RemoveAssets(); + copy.asset = this.asset != null ? Arrays.stream(this.asset).map(e -> e.clone()).toArray(Asset[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof RemoveAssets other ? Arrays.equals((Object[])this.asset, (Object[])other.asset) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.asset); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/setup/RequestAssets.java b/src/com/hypixel/hytale/protocol/packets/setup/RequestAssets.java new file mode 100644 index 0000000..2f92d5c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/setup/RequestAssets.java @@ -0,0 +1,176 @@ +package com.hypixel.hytale.protocol.packets.setup; + +import com.hypixel.hytale.protocol.Asset; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RequestAssets implements Packet { + public static final int PACKET_ID = 23; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public Asset[] assets; + + @Override + public int getId() { + return 23; + } + + public RequestAssets() { + } + + public RequestAssets(@Nullable Asset[] assets) { + this.assets = assets; + } + + public RequestAssets(@Nonnull RequestAssets other) { + this.assets = other.assets; + } + + @Nonnull + public static RequestAssets deserialize(@Nonnull ByteBuf buf, int offset) { + RequestAssets obj = new RequestAssets(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int assetsCount = VarInt.peek(buf, pos); + if (assetsCount < 0) { + throw ProtocolException.negativeLength("Assets", assetsCount); + } + + if (assetsCount > 4096000) { + throw ProtocolException.arrayTooLong("Assets", assetsCount, 4096000); + } + + int assetsVarLen = VarInt.size(assetsCount); + if (pos + assetsVarLen + assetsCount * 64L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Assets", pos + assetsVarLen + assetsCount * 64, buf.readableBytes()); + } + + pos += assetsVarLen; + obj.assets = new Asset[assetsCount]; + + for (int i = 0; i < assetsCount; i++) { + obj.assets[i] = Asset.deserialize(buf, pos); + pos += Asset.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += Asset.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.assets != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.assets != null) { + if (this.assets.length > 4096000) { + throw ProtocolException.arrayTooLong("Assets", this.assets.length, 4096000); + } + + VarInt.write(buf, this.assets.length); + + for (Asset item : this.assets) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.assets != null) { + int assetsSize = 0; + + for (Asset elem : this.assets) { + assetsSize += elem.computeSize(); + } + + size += VarInt.size(this.assets.length) + assetsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int assetsCount = VarInt.peek(buffer, pos); + if (assetsCount < 0) { + return ValidationResult.error("Invalid array count for Assets"); + } + + if (assetsCount > 4096000) { + return ValidationResult.error("Assets exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < assetsCount; i++) { + ValidationResult structResult = Asset.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid Asset in Assets[" + i + "]: " + structResult.error()); + } + + pos += Asset.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public RequestAssets clone() { + RequestAssets copy = new RequestAssets(); + copy.assets = this.assets != null ? Arrays.stream(this.assets).map(e -> e.clone()).toArray(Asset[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof RequestAssets other ? Arrays.equals((Object[])this.assets, (Object[])other.assets) : false; + } + } + + @Override + public int hashCode() { + int result = 1; + return 31 * result + Arrays.hashCode((Object[])this.assets); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/setup/RequestCommonAssetsRebuild.java b/src/com/hypixel/hytale/protocol/packets/setup/RequestCommonAssetsRebuild.java new file mode 100644 index 0000000..ad23429 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/setup/RequestCommonAssetsRebuild.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.protocol.packets.setup; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class RequestCommonAssetsRebuild implements Packet { + public static final int PACKET_ID = 28; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public RequestCommonAssetsRebuild() { + } + + @Override + public int getId() { + return 28; + } + + @Nonnull + public static RequestCommonAssetsRebuild deserialize(@Nonnull ByteBuf buf, int offset) { + return new RequestCommonAssetsRebuild(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public RequestCommonAssetsRebuild clone() { + return new RequestCommonAssetsRebuild(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof RequestCommonAssetsRebuild other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/setup/ServerTags.java b/src/com/hypixel/hytale/protocol/packets/setup/ServerTags.java new file mode 100644 index 0000000..cd11961 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/setup/ServerTags.java @@ -0,0 +1,204 @@ +package com.hypixel.hytale.protocol.packets.setup; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ServerTags implements Packet { + public static final int PACKET_ID = 34; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1677721600; + @Nullable + public Map tags; + + @Override + public int getId() { + return 34; + } + + public ServerTags() { + } + + public ServerTags(@Nullable Map tags) { + this.tags = tags; + } + + public ServerTags(@Nonnull ServerTags other) { + this.tags = other.tags; + } + + @Nonnull + public static ServerTags deserialize(@Nonnull ByteBuf buf, int offset) { + ServerTags obj = new ServerTags(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int tagsCount = VarInt.peek(buf, pos); + if (tagsCount < 0) { + throw ProtocolException.negativeLength("Tags", tagsCount); + } + + if (tagsCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Tags", tagsCount, 4096000); + } + + pos += VarInt.size(tagsCount); + obj.tags = new HashMap<>(tagsCount); + + for (int i = 0; i < tagsCount; i++) { + int keyLen = VarInt.peek(buf, pos); + if (keyLen < 0) { + throw ProtocolException.negativeLength("key", keyLen); + } + + if (keyLen > 4096000) { + throw ProtocolException.stringTooLong("key", keyLen, 4096000); + } + + int keyVarLen = VarInt.length(buf, pos); + String key = PacketIO.readVarString(buf, pos); + pos += keyVarLen + keyLen; + int val = buf.getIntLE(pos); + pos += 4; + if (obj.tags.put(key, val) != null) { + throw ProtocolException.duplicateKey("tags", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + pos += 4; + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.tags != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.tags != null) { + if (this.tags.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Tags", this.tags.size(), 4096000); + } + + VarInt.write(buf, this.tags.size()); + + for (Entry e : this.tags.entrySet()) { + PacketIO.writeVarString(buf, e.getKey(), 4096000); + buf.writeIntLE(e.getValue()); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.tags != null) { + int tagsSize = 0; + + for (Entry kvp : this.tags.entrySet()) { + tagsSize += PacketIO.stringSize(kvp.getKey()) + 4; + } + + size += VarInt.size(this.tags.size()) + tagsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int tagsCount = VarInt.peek(buffer, pos); + if (tagsCount < 0) { + return ValidationResult.error("Invalid dictionary count for Tags"); + } + + if (tagsCount > 4096000) { + return ValidationResult.error("Tags exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < tagsCount; i++) { + int keyLen = VarInt.peek(buffer, pos); + if (keyLen < 0) { + return ValidationResult.error("Invalid string length for key"); + } + + if (keyLen > 4096000) { + return ValidationResult.error("key exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += keyLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public ServerTags clone() { + ServerTags copy = new ServerTags(); + copy.tags = this.tags != null ? new HashMap<>(this.tags) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof ServerTags other ? Objects.equals(this.tags, other.tags) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.tags); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/setup/SetTimeDilation.java b/src/com/hypixel/hytale/protocol/packets/setup/SetTimeDilation.java new file mode 100644 index 0000000..01f7b9b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/setup/SetTimeDilation.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.setup; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SetTimeDilation implements Packet { + public static final int PACKET_ID = 30; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 4; + public float timeDilation; + + @Override + public int getId() { + return 30; + } + + public SetTimeDilation() { + } + + public SetTimeDilation(float timeDilation) { + this.timeDilation = timeDilation; + } + + public SetTimeDilation(@Nonnull SetTimeDilation other) { + this.timeDilation = other.timeDilation; + } + + @Nonnull + public static SetTimeDilation deserialize(@Nonnull ByteBuf buf, int offset) { + SetTimeDilation obj = new SetTimeDilation(); + obj.timeDilation = buf.getFloatLE(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 4; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.timeDilation); + } + + @Override + public int computeSize() { + return 4; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 4 ? ValidationResult.error("Buffer too small: expected at least 4 bytes") : ValidationResult.OK; + } + + public SetTimeDilation clone() { + SetTimeDilation copy = new SetTimeDilation(); + copy.timeDilation = this.timeDilation; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof SetTimeDilation other ? this.timeDilation == other.timeDilation : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.timeDilation); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/setup/SetUpdateRate.java b/src/com/hypixel/hytale/protocol/packets/setup/SetUpdateRate.java new file mode 100644 index 0000000..b50015f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/setup/SetUpdateRate.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.setup; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SetUpdateRate implements Packet { + public static final int PACKET_ID = 29; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 4; + public int updatesPerSecond; + + @Override + public int getId() { + return 29; + } + + public SetUpdateRate() { + } + + public SetUpdateRate(int updatesPerSecond) { + this.updatesPerSecond = updatesPerSecond; + } + + public SetUpdateRate(@Nonnull SetUpdateRate other) { + this.updatesPerSecond = other.updatesPerSecond; + } + + @Nonnull + public static SetUpdateRate deserialize(@Nonnull ByteBuf buf, int offset) { + SetUpdateRate obj = new SetUpdateRate(); + obj.updatesPerSecond = buf.getIntLE(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 4; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.updatesPerSecond); + } + + @Override + public int computeSize() { + return 4; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 4 ? ValidationResult.error("Buffer too small: expected at least 4 bytes") : ValidationResult.OK; + } + + public SetUpdateRate clone() { + SetUpdateRate copy = new SetUpdateRate(); + copy.updatesPerSecond = this.updatesPerSecond; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof SetUpdateRate other ? this.updatesPerSecond == other.updatesPerSecond : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.updatesPerSecond); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/setup/UpdateFeatures.java b/src/com/hypixel/hytale/protocol/packets/setup/UpdateFeatures.java new file mode 100644 index 0000000..31a66e1 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/setup/UpdateFeatures.java @@ -0,0 +1,171 @@ +package com.hypixel.hytale.protocol.packets.setup; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateFeatures implements Packet { + public static final int PACKET_ID = 31; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 8192006; + @Nullable + public Map features; + + @Override + public int getId() { + return 31; + } + + public UpdateFeatures() { + } + + public UpdateFeatures(@Nullable Map features) { + this.features = features; + } + + public UpdateFeatures(@Nonnull UpdateFeatures other) { + this.features = other.features; + } + + @Nonnull + public static UpdateFeatures deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateFeatures obj = new UpdateFeatures(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int featuresCount = VarInt.peek(buf, pos); + if (featuresCount < 0) { + throw ProtocolException.negativeLength("Features", featuresCount); + } + + if (featuresCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("Features", featuresCount, 4096000); + } + + pos += VarInt.size(featuresCount); + obj.features = new HashMap<>(featuresCount); + + for (int i = 0; i < featuresCount; i++) { + ClientFeature key = ClientFeature.fromValue(buf.getByte(pos)); + pos++; + boolean val = buf.getByte(pos) != 0; + pos++; + if (obj.features.put(key, val) != null) { + throw ProtocolException.duplicateKey("features", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos++; + pos++; + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.features != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.features != null) { + if (this.features.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("Features", this.features.size(), 4096000); + } + + VarInt.write(buf, this.features.size()); + + for (Entry e : this.features.entrySet()) { + buf.writeByte(e.getKey().getValue()); + buf.writeByte(e.getValue() ? 1 : 0); + } + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.features != null) { + size += VarInt.size(this.features.size()) + this.features.size() * 2; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int featuresCount = VarInt.peek(buffer, pos); + if (featuresCount < 0) { + return ValidationResult.error("Invalid dictionary count for Features"); + } + + if (featuresCount > 4096000) { + return ValidationResult.error("Features exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < featuresCount; i++) { + pos++; + if (++pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading value"); + } + } + } + + return ValidationResult.OK; + } + } + + public UpdateFeatures clone() { + UpdateFeatures copy = new UpdateFeatures(); + copy.features = this.features != null ? new HashMap<>(this.features) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof UpdateFeatures other ? Objects.equals(this.features, other.features) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.features); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/setup/ViewRadius.java b/src/com/hypixel/hytale/protocol/packets/setup/ViewRadius.java new file mode 100644 index 0000000..2f576b0 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/setup/ViewRadius.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.setup; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class ViewRadius implements Packet { + public static final int PACKET_ID = 32; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 4; + public int value; + + @Override + public int getId() { + return 32; + } + + public ViewRadius() { + } + + public ViewRadius(int value) { + this.value = value; + } + + public ViewRadius(@Nonnull ViewRadius other) { + this.value = other.value; + } + + @Nonnull + public static ViewRadius deserialize(@Nonnull ByteBuf buf, int offset) { + ViewRadius obj = new ViewRadius(); + obj.value = buf.getIntLE(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 4; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.value); + } + + @Override + public int computeSize() { + return 4; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 4 ? ValidationResult.error("Buffer too small: expected at least 4 bytes") : ValidationResult.OK; + } + + public ViewRadius clone() { + ViewRadius copy = new ViewRadius(); + copy.value = this.value; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof ViewRadius other ? this.value == other.value : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.value); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/setup/WorldLoadFinished.java b/src/com/hypixel/hytale/protocol/packets/setup/WorldLoadFinished.java new file mode 100644 index 0000000..efb18c9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/setup/WorldLoadFinished.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.protocol.packets.setup; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class WorldLoadFinished implements Packet { + public static final int PACKET_ID = 22; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public WorldLoadFinished() { + } + + @Override + public int getId() { + return 22; + } + + @Nonnull + public static WorldLoadFinished deserialize(@Nonnull ByteBuf buf, int offset) { + return new WorldLoadFinished(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public WorldLoadFinished clone() { + return new WorldLoadFinished(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof WorldLoadFinished other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/setup/WorldLoadProgress.java b/src/com/hypixel/hytale/protocol/packets/setup/WorldLoadProgress.java new file mode 100644 index 0000000..044f540 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/setup/WorldLoadProgress.java @@ -0,0 +1,159 @@ +package com.hypixel.hytale.protocol.packets.setup; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WorldLoadProgress implements Packet { + public static final int PACKET_ID = 21; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 16384014; + @Nullable + public String status; + public int percentComplete; + public int percentCompleteSubitem; + + @Override + public int getId() { + return 21; + } + + public WorldLoadProgress() { + } + + public WorldLoadProgress(@Nullable String status, int percentComplete, int percentCompleteSubitem) { + this.status = status; + this.percentComplete = percentComplete; + this.percentCompleteSubitem = percentCompleteSubitem; + } + + public WorldLoadProgress(@Nonnull WorldLoadProgress other) { + this.status = other.status; + this.percentComplete = other.percentComplete; + this.percentCompleteSubitem = other.percentCompleteSubitem; + } + + @Nonnull + public static WorldLoadProgress deserialize(@Nonnull ByteBuf buf, int offset) { + WorldLoadProgress obj = new WorldLoadProgress(); + byte nullBits = buf.getByte(offset); + obj.percentComplete = buf.getIntLE(offset + 1); + obj.percentCompleteSubitem = buf.getIntLE(offset + 5); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int statusLen = VarInt.peek(buf, pos); + if (statusLen < 0) { + throw ProtocolException.negativeLength("Status", statusLen); + } + + if (statusLen > 4096000) { + throw ProtocolException.stringTooLong("Status", statusLen, 4096000); + } + + int statusVarLen = VarInt.length(buf, pos); + obj.status = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += statusVarLen + statusLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.status != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.percentComplete); + buf.writeIntLE(this.percentCompleteSubitem); + if (this.status != null) { + PacketIO.writeVarString(buf, this.status, 4096000); + } + } + + @Override + public int computeSize() { + int size = 9; + if (this.status != null) { + size += PacketIO.stringSize(this.status); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int statusLen = VarInt.peek(buffer, pos); + if (statusLen < 0) { + return ValidationResult.error("Invalid string length for Status"); + } + + if (statusLen > 4096000) { + return ValidationResult.error("Status exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += statusLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Status"); + } + } + + return ValidationResult.OK; + } + } + + public WorldLoadProgress clone() { + WorldLoadProgress copy = new WorldLoadProgress(); + copy.status = this.status; + copy.percentComplete = this.percentComplete; + copy.percentCompleteSubitem = this.percentCompleteSubitem; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof WorldLoadProgress other) + ? false + : Objects.equals(this.status, other.status) + && this.percentComplete == other.percentComplete + && this.percentCompleteSubitem == other.percentCompleteSubitem; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.status, this.percentComplete, this.percentCompleteSubitem); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/setup/WorldSettings.java b/src/com/hypixel/hytale/protocol/packets/setup/WorldSettings.java new file mode 100644 index 0000000..e830d2f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/setup/WorldSettings.java @@ -0,0 +1,185 @@ +package com.hypixel.hytale.protocol.packets.setup; + +import com.hypixel.hytale.protocol.Asset; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WorldSettings implements Packet { + public static final int PACKET_ID = 20; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 1677721600; + public int worldHeight; + @Nullable + public Asset[] requiredAssets; + + @Override + public int getId() { + return 20; + } + + public WorldSettings() { + } + + public WorldSettings(int worldHeight, @Nullable Asset[] requiredAssets) { + this.worldHeight = worldHeight; + this.requiredAssets = requiredAssets; + } + + public WorldSettings(@Nonnull WorldSettings other) { + this.worldHeight = other.worldHeight; + this.requiredAssets = other.requiredAssets; + } + + @Nonnull + public static WorldSettings deserialize(@Nonnull ByteBuf buf, int offset) { + WorldSettings obj = new WorldSettings(); + byte nullBits = buf.getByte(offset); + obj.worldHeight = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int requiredAssetsCount = VarInt.peek(buf, pos); + if (requiredAssetsCount < 0) { + throw ProtocolException.negativeLength("RequiredAssets", requiredAssetsCount); + } + + if (requiredAssetsCount > 4096000) { + throw ProtocolException.arrayTooLong("RequiredAssets", requiredAssetsCount, 4096000); + } + + int requiredAssetsVarLen = VarInt.size(requiredAssetsCount); + if (pos + requiredAssetsVarLen + requiredAssetsCount * 64L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("RequiredAssets", pos + requiredAssetsVarLen + requiredAssetsCount * 64, buf.readableBytes()); + } + + pos += requiredAssetsVarLen; + obj.requiredAssets = new Asset[requiredAssetsCount]; + + for (int i = 0; i < requiredAssetsCount; i++) { + obj.requiredAssets[i] = Asset.deserialize(buf, pos); + pos += Asset.computeBytesConsumed(buf, pos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += Asset.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.requiredAssets != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.worldHeight); + if (this.requiredAssets != null) { + if (this.requiredAssets.length > 4096000) { + throw ProtocolException.arrayTooLong("RequiredAssets", this.requiredAssets.length, 4096000); + } + + VarInt.write(buf, this.requiredAssets.length); + + for (Asset item : this.requiredAssets) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 5; + if (this.requiredAssets != null) { + int requiredAssetsSize = 0; + + for (Asset elem : this.requiredAssets) { + requiredAssetsSize += elem.computeSize(); + } + + size += VarInt.size(this.requiredAssets.length) + requiredAssetsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int requiredAssetsCount = VarInt.peek(buffer, pos); + if (requiredAssetsCount < 0) { + return ValidationResult.error("Invalid array count for RequiredAssets"); + } + + if (requiredAssetsCount > 4096000) { + return ValidationResult.error("RequiredAssets exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < requiredAssetsCount; i++) { + ValidationResult structResult = Asset.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid Asset in RequiredAssets[" + i + "]: " + structResult.error()); + } + + pos += Asset.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public WorldSettings clone() { + WorldSettings copy = new WorldSettings(); + copy.worldHeight = this.worldHeight; + copy.requiredAssets = this.requiredAssets != null ? Arrays.stream(this.requiredAssets).map(e -> e.clone()).toArray(Asset[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof WorldSettings other) + ? false + : this.worldHeight == other.worldHeight && Arrays.equals((Object[])this.requiredAssets, (Object[])other.requiredAssets); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.worldHeight); + return 31 * result + Arrays.hashCode((Object[])this.requiredAssets); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/window/CancelCraftingAction.java b/src/com/hypixel/hytale/protocol/packets/window/CancelCraftingAction.java new file mode 100644 index 0000000..28c1a65 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/window/CancelCraftingAction.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.protocol.packets.window; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class CancelCraftingAction extends WindowAction { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public CancelCraftingAction() { + } + + @Nonnull + public static CancelCraftingAction deserialize(@Nonnull ByteBuf buf, int offset) { + return new CancelCraftingAction(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public CancelCraftingAction clone() { + return new CancelCraftingAction(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof CancelCraftingAction other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/window/ChangeBlockAction.java b/src/com/hypixel/hytale/protocol/packets/window/ChangeBlockAction.java new file mode 100644 index 0000000..7b88b0a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/window/ChangeBlockAction.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.protocol.packets.window; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class ChangeBlockAction extends WindowAction { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean down; + + public ChangeBlockAction() { + } + + public ChangeBlockAction(boolean down) { + this.down = down; + } + + public ChangeBlockAction(@Nonnull ChangeBlockAction other) { + this.down = other.down; + } + + @Nonnull + public static ChangeBlockAction deserialize(@Nonnull ByteBuf buf, int offset) { + ChangeBlockAction obj = new ChangeBlockAction(); + obj.down = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + buf.writeByte(this.down ? 1 : 0); + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public ChangeBlockAction clone() { + ChangeBlockAction copy = new ChangeBlockAction(); + copy.down = this.down; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof ChangeBlockAction other ? this.down == other.down : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.down); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/window/ClientOpenWindow.java b/src/com/hypixel/hytale/protocol/packets/window/ClientOpenWindow.java new file mode 100644 index 0000000..e8f0aa0 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/window/ClientOpenWindow.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.protocol.packets.window; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class ClientOpenWindow implements Packet { + public static final int PACKET_ID = 204; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + @Nonnull + public WindowType type = WindowType.Container; + + @Override + public int getId() { + return 204; + } + + public ClientOpenWindow() { + } + + public ClientOpenWindow(@Nonnull WindowType type) { + this.type = type; + } + + public ClientOpenWindow(@Nonnull ClientOpenWindow other) { + this.type = other.type; + } + + @Nonnull + public static ClientOpenWindow deserialize(@Nonnull ByteBuf buf, int offset) { + ClientOpenWindow obj = new ClientOpenWindow(); + obj.type = WindowType.fromValue(buf.getByte(offset + 0)); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.type.getValue()); + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public ClientOpenWindow clone() { + ClientOpenWindow copy = new ClientOpenWindow(); + copy.type = this.type; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof ClientOpenWindow other ? Objects.equals(this.type, other.type) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.type); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/window/CloseWindow.java b/src/com/hypixel/hytale/protocol/packets/window/CloseWindow.java new file mode 100644 index 0000000..f9664a5 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/window/CloseWindow.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.window; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class CloseWindow implements Packet { + public static final int PACKET_ID = 202; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 4; + public int id; + + @Override + public int getId() { + return 202; + } + + public CloseWindow() { + } + + public CloseWindow(int id) { + this.id = id; + } + + public CloseWindow(@Nonnull CloseWindow other) { + this.id = other.id; + } + + @Nonnull + public static CloseWindow deserialize(@Nonnull ByteBuf buf, int offset) { + CloseWindow obj = new CloseWindow(); + obj.id = buf.getIntLE(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 4; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.id); + } + + @Override + public int computeSize() { + return 4; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 4 ? ValidationResult.error("Buffer too small: expected at least 4 bytes") : ValidationResult.OK; + } + + public CloseWindow clone() { + CloseWindow copy = new CloseWindow(); + copy.id = this.id; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof CloseWindow other ? this.id == other.id : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/window/CraftItemAction.java b/src/com/hypixel/hytale/protocol/packets/window/CraftItemAction.java new file mode 100644 index 0000000..657b5c3 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/window/CraftItemAction.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.protocol.packets.window; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class CraftItemAction extends WindowAction { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public CraftItemAction() { + } + + @Nonnull + public static CraftItemAction deserialize(@Nonnull ByteBuf buf, int offset) { + return new CraftItemAction(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public CraftItemAction clone() { + return new CraftItemAction(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof CraftItemAction other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/window/CraftRecipeAction.java b/src/com/hypixel/hytale/protocol/packets/window/CraftRecipeAction.java new file mode 100644 index 0000000..a844952 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/window/CraftRecipeAction.java @@ -0,0 +1,144 @@ +package com.hypixel.hytale.protocol.packets.window; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CraftRecipeAction extends WindowAction { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 5; + public static final int MAX_SIZE = 16384010; + @Nullable + public String recipeId; + public int quantity; + + public CraftRecipeAction() { + } + + public CraftRecipeAction(@Nullable String recipeId, int quantity) { + this.recipeId = recipeId; + this.quantity = quantity; + } + + public CraftRecipeAction(@Nonnull CraftRecipeAction other) { + this.recipeId = other.recipeId; + this.quantity = other.quantity; + } + + @Nonnull + public static CraftRecipeAction deserialize(@Nonnull ByteBuf buf, int offset) { + CraftRecipeAction obj = new CraftRecipeAction(); + byte nullBits = buf.getByte(offset); + obj.quantity = buf.getIntLE(offset + 1); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int recipeIdLen = VarInt.peek(buf, pos); + if (recipeIdLen < 0) { + throw ProtocolException.negativeLength("RecipeId", recipeIdLen); + } + + if (recipeIdLen > 4096000) { + throw ProtocolException.stringTooLong("RecipeId", recipeIdLen, 4096000); + } + + int recipeIdVarLen = VarInt.length(buf, pos); + obj.recipeId = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += recipeIdVarLen + recipeIdLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.recipeId != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.quantity); + if (this.recipeId != null) { + PacketIO.writeVarString(buf, this.recipeId, 4096000); + } + + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 5; + if (this.recipeId != null) { + size += PacketIO.stringSize(this.recipeId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 5) { + return ValidationResult.error("Buffer too small: expected at least 5 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 5; + if ((nullBits & 1) != 0) { + int recipeIdLen = VarInt.peek(buffer, pos); + if (recipeIdLen < 0) { + return ValidationResult.error("Invalid string length for RecipeId"); + } + + if (recipeIdLen > 4096000) { + return ValidationResult.error("RecipeId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += recipeIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading RecipeId"); + } + } + + return ValidationResult.OK; + } + } + + public CraftRecipeAction clone() { + CraftRecipeAction copy = new CraftRecipeAction(); + copy.recipeId = this.recipeId; + copy.quantity = this.quantity; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof CraftRecipeAction other) ? false : Objects.equals(this.recipeId, other.recipeId) && this.quantity == other.quantity; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.recipeId, this.quantity); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/window/OpenWindow.java b/src/com/hypixel/hytale/protocol/packets/window/OpenWindow.java new file mode 100644 index 0000000..72cb683 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/window/OpenWindow.java @@ -0,0 +1,295 @@ +package com.hypixel.hytale.protocol.packets.window; + +import com.hypixel.hytale.protocol.ExtraResources; +import com.hypixel.hytale.protocol.InventorySection; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class OpenWindow implements Packet { + public static final int PACKET_ID = 200; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 6; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 18; + public static final int MAX_SIZE = 1677721600; + public int id; + @Nonnull + public WindowType windowType = WindowType.Container; + @Nullable + public String windowData; + @Nullable + public InventorySection inventory; + @Nullable + public ExtraResources extraResources; + + @Override + public int getId() { + return 200; + } + + public OpenWindow() { + } + + public OpenWindow( + int id, @Nonnull WindowType windowType, @Nullable String windowData, @Nullable InventorySection inventory, @Nullable ExtraResources extraResources + ) { + this.id = id; + this.windowType = windowType; + this.windowData = windowData; + this.inventory = inventory; + this.extraResources = extraResources; + } + + public OpenWindow(@Nonnull OpenWindow other) { + this.id = other.id; + this.windowType = other.windowType; + this.windowData = other.windowData; + this.inventory = other.inventory; + this.extraResources = other.extraResources; + } + + @Nonnull + public static OpenWindow deserialize(@Nonnull ByteBuf buf, int offset) { + OpenWindow obj = new OpenWindow(); + byte nullBits = buf.getByte(offset); + obj.id = buf.getIntLE(offset + 1); + obj.windowType = WindowType.fromValue(buf.getByte(offset + 5)); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 18 + buf.getIntLE(offset + 6); + int windowDataLen = VarInt.peek(buf, varPos0); + if (windowDataLen < 0) { + throw ProtocolException.negativeLength("WindowData", windowDataLen); + } + + if (windowDataLen > 4096000) { + throw ProtocolException.stringTooLong("WindowData", windowDataLen, 4096000); + } + + obj.windowData = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 18 + buf.getIntLE(offset + 10); + obj.inventory = InventorySection.deserialize(buf, varPos1); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 18 + buf.getIntLE(offset + 14); + obj.extraResources = ExtraResources.deserialize(buf, varPos2); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 18; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 6); + int pos0 = offset + 18 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 10); + int pos1 = offset + 18 + fieldOffset1; + pos1 += InventorySection.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 14); + int pos2 = offset + 18 + fieldOffset2; + pos2 += ExtraResources.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.windowData != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.inventory != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.extraResources != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.id); + buf.writeByte(this.windowType.getValue()); + int windowDataOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int inventoryOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int extraResourcesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.windowData != null) { + buf.setIntLE(windowDataOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.windowData, 4096000); + } else { + buf.setIntLE(windowDataOffsetSlot, -1); + } + + if (this.inventory != null) { + buf.setIntLE(inventoryOffsetSlot, buf.writerIndex() - varBlockStart); + this.inventory.serialize(buf); + } else { + buf.setIntLE(inventoryOffsetSlot, -1); + } + + if (this.extraResources != null) { + buf.setIntLE(extraResourcesOffsetSlot, buf.writerIndex() - varBlockStart); + this.extraResources.serialize(buf); + } else { + buf.setIntLE(extraResourcesOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 18; + if (this.windowData != null) { + size += PacketIO.stringSize(this.windowData); + } + + if (this.inventory != null) { + size += this.inventory.computeSize(); + } + + if (this.extraResources != null) { + size += this.extraResources.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 18) { + return ValidationResult.error("Buffer too small: expected at least 18 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int windowDataOffset = buffer.getIntLE(offset + 6); + if (windowDataOffset < 0) { + return ValidationResult.error("Invalid offset for WindowData"); + } + + int pos = offset + 18 + windowDataOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for WindowData"); + } + + int windowDataLen = VarInt.peek(buffer, pos); + if (windowDataLen < 0) { + return ValidationResult.error("Invalid string length for WindowData"); + } + + if (windowDataLen > 4096000) { + return ValidationResult.error("WindowData exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += windowDataLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading WindowData"); + } + } + + if ((nullBits & 2) != 0) { + int inventoryOffset = buffer.getIntLE(offset + 10); + if (inventoryOffset < 0) { + return ValidationResult.error("Invalid offset for Inventory"); + } + + int posx = offset + 18 + inventoryOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Inventory"); + } + + ValidationResult inventoryResult = InventorySection.validateStructure(buffer, posx); + if (!inventoryResult.isValid()) { + return ValidationResult.error("Invalid Inventory: " + inventoryResult.error()); + } + + posx += InventorySection.computeBytesConsumed(buffer, posx); + } + + if ((nullBits & 4) != 0) { + int extraResourcesOffset = buffer.getIntLE(offset + 14); + if (extraResourcesOffset < 0) { + return ValidationResult.error("Invalid offset for ExtraResources"); + } + + int posxx = offset + 18 + extraResourcesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ExtraResources"); + } + + ValidationResult extraResourcesResult = ExtraResources.validateStructure(buffer, posxx); + if (!extraResourcesResult.isValid()) { + return ValidationResult.error("Invalid ExtraResources: " + extraResourcesResult.error()); + } + + posxx += ExtraResources.computeBytesConsumed(buffer, posxx); + } + + return ValidationResult.OK; + } + } + + public OpenWindow clone() { + OpenWindow copy = new OpenWindow(); + copy.id = this.id; + copy.windowType = this.windowType; + copy.windowData = this.windowData; + copy.inventory = this.inventory != null ? this.inventory.clone() : null; + copy.extraResources = this.extraResources != null ? this.extraResources.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof OpenWindow other) + ? false + : this.id == other.id + && Objects.equals(this.windowType, other.windowType) + && Objects.equals(this.windowData, other.windowData) + && Objects.equals(this.inventory, other.inventory) + && Objects.equals(this.extraResources, other.extraResources); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.windowType, this.windowData, this.inventory, this.extraResources); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/window/SelectSlotAction.java b/src/com/hypixel/hytale/protocol/packets/window/SelectSlotAction.java new file mode 100644 index 0000000..4df6967 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/window/SelectSlotAction.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.protocol.packets.window; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SelectSlotAction extends WindowAction { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 4; + public int slot; + + public SelectSlotAction() { + } + + public SelectSlotAction(int slot) { + this.slot = slot; + } + + public SelectSlotAction(@Nonnull SelectSlotAction other) { + this.slot = other.slot; + } + + @Nonnull + public static SelectSlotAction deserialize(@Nonnull ByteBuf buf, int offset) { + SelectSlotAction obj = new SelectSlotAction(); + obj.slot = buf.getIntLE(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 4; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + buf.writeIntLE(this.slot); + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + return 4; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 4 ? ValidationResult.error("Buffer too small: expected at least 4 bytes") : ValidationResult.OK; + } + + public SelectSlotAction clone() { + SelectSlotAction copy = new SelectSlotAction(); + copy.slot = this.slot; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof SelectSlotAction other ? this.slot == other.slot : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.slot); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/window/SendWindowAction.java b/src/com/hypixel/hytale/protocol/packets/window/SendWindowAction.java new file mode 100644 index 0000000..beb244d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/window/SendWindowAction.java @@ -0,0 +1,102 @@ +package com.hypixel.hytale.protocol.packets.window; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SendWindowAction implements Packet { + public static final int PACKET_ID = 203; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 32768027; + public int id; + @Nonnull + public WindowAction action; + + @Override + public int getId() { + return 203; + } + + public SendWindowAction() { + } + + public SendWindowAction(int id, @Nonnull WindowAction action) { + this.id = id; + this.action = action; + } + + public SendWindowAction(@Nonnull SendWindowAction other) { + this.id = other.id; + this.action = other.action; + } + + @Nonnull + public static SendWindowAction deserialize(@Nonnull ByteBuf buf, int offset) { + SendWindowAction obj = new SendWindowAction(); + obj.id = buf.getIntLE(offset + 0); + int pos = offset + 4; + obj.action = WindowAction.deserialize(buf, pos); + pos += WindowAction.computeBytesConsumed(buf, pos); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + int pos = offset + 4; + pos += WindowAction.computeBytesConsumed(buf, pos); + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.id); + this.action.serializeWithTypeId(buf); + } + + @Override + public int computeSize() { + int size = 4; + return size + this.action.computeSizeWithTypeId(); + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 4) { + return ValidationResult.error("Buffer too small: expected at least 4 bytes"); + } else { + int pos = offset + 4; + ValidationResult actionResult = WindowAction.validateStructure(buffer, pos); + if (!actionResult.isValid()) { + return ValidationResult.error("Invalid Action: " + actionResult.error()); + } else { + pos += WindowAction.computeBytesConsumed(buffer, pos); + return ValidationResult.OK; + } + } + } + + public SendWindowAction clone() { + SendWindowAction copy = new SendWindowAction(); + copy.id = this.id; + copy.action = this.action; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SendWindowAction other) ? false : this.id == other.id && Objects.equals(this.action, other.action); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.action); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/window/SetActiveAction.java b/src/com/hypixel/hytale/protocol/packets/window/SetActiveAction.java new file mode 100644 index 0000000..fc40ef4 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/window/SetActiveAction.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.protocol.packets.window; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SetActiveAction extends WindowAction { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean state; + + public SetActiveAction() { + } + + public SetActiveAction(boolean state) { + this.state = state; + } + + public SetActiveAction(@Nonnull SetActiveAction other) { + this.state = other.state; + } + + @Nonnull + public static SetActiveAction deserialize(@Nonnull ByteBuf buf, int offset) { + SetActiveAction obj = new SetActiveAction(); + obj.state = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + buf.writeByte(this.state ? 1 : 0); + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public SetActiveAction clone() { + SetActiveAction copy = new SetActiveAction(); + copy.state = this.state; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof SetActiveAction other ? this.state == other.state : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.state); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/window/SortItemsAction.java b/src/com/hypixel/hytale/protocol/packets/window/SortItemsAction.java new file mode 100644 index 0000000..20b1096 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/window/SortItemsAction.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.protocol.packets.window; + +import com.hypixel.hytale.protocol.SortType; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SortItemsAction extends WindowAction { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + @Nonnull + public SortType sortType = SortType.Name; + + public SortItemsAction() { + } + + public SortItemsAction(@Nonnull SortType sortType) { + this.sortType = sortType; + } + + public SortItemsAction(@Nonnull SortItemsAction other) { + this.sortType = other.sortType; + } + + @Nonnull + public static SortItemsAction deserialize(@Nonnull ByteBuf buf, int offset) { + SortItemsAction obj = new SortItemsAction(); + obj.sortType = SortType.fromValue(buf.getByte(offset + 0)); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + buf.writeByte(this.sortType.getValue()); + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public SortItemsAction clone() { + SortItemsAction copy = new SortItemsAction(); + copy.sortType = this.sortType; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof SortItemsAction other ? Objects.equals(this.sortType, other.sortType) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.sortType); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/window/TierUpgradeAction.java b/src/com/hypixel/hytale/protocol/packets/window/TierUpgradeAction.java new file mode 100644 index 0000000..3df1f56 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/window/TierUpgradeAction.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.protocol.packets.window; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class TierUpgradeAction extends WindowAction { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public TierUpgradeAction() { + } + + @Nonnull + public static TierUpgradeAction deserialize(@Nonnull ByteBuf buf, int offset) { + return new TierUpgradeAction(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public TierUpgradeAction clone() { + return new TierUpgradeAction(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof TierUpgradeAction other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/window/UpdateCategoryAction.java b/src/com/hypixel/hytale/protocol/packets/window/UpdateCategoryAction.java new file mode 100644 index 0000000..0cc379c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/window/UpdateCategoryAction.java @@ -0,0 +1,174 @@ +package com.hypixel.hytale.protocol.packets.window; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class UpdateCategoryAction extends WindowAction { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 32768018; + @Nonnull + public String category = ""; + @Nonnull + public String itemCategory = ""; + + public UpdateCategoryAction() { + } + + public UpdateCategoryAction(@Nonnull String category, @Nonnull String itemCategory) { + this.category = category; + this.itemCategory = itemCategory; + } + + public UpdateCategoryAction(@Nonnull UpdateCategoryAction other) { + this.category = other.category; + this.itemCategory = other.itemCategory; + } + + @Nonnull + public static UpdateCategoryAction deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateCategoryAction obj = new UpdateCategoryAction(); + int varPos0 = offset + 8 + buf.getIntLE(offset + 0); + int categoryLen = VarInt.peek(buf, varPos0); + if (categoryLen < 0) { + throw ProtocolException.negativeLength("Category", categoryLen); + } else if (categoryLen > 4096000) { + throw ProtocolException.stringTooLong("Category", categoryLen, 4096000); + } else { + obj.category = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + varPos0 = offset + 8 + buf.getIntLE(offset + 4); + categoryLen = VarInt.peek(buf, varPos0); + if (categoryLen < 0) { + throw ProtocolException.negativeLength("ItemCategory", categoryLen); + } else if (categoryLen > 4096000) { + throw ProtocolException.stringTooLong("ItemCategory", categoryLen, 4096000); + } else { + obj.itemCategory = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + return obj; + } + } + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + int maxEnd = 8; + int fieldOffset0 = buf.getIntLE(offset + 0); + int pos0 = offset + 8 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + + fieldOffset0 = buf.getIntLE(offset + 4); + pos0 = offset + 8 + fieldOffset0; + sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + + return maxEnd; + } + + @Override + public int serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + int categoryOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int itemCategoryOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + buf.setIntLE(categoryOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.category, 4096000); + buf.setIntLE(itemCategoryOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.itemCategory, 4096000); + return buf.writerIndex() - startPos; + } + + @Override + public int computeSize() { + int size = 8; + size += PacketIO.stringSize(this.category); + return size + PacketIO.stringSize(this.itemCategory); + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 8) { + return ValidationResult.error("Buffer too small: expected at least 8 bytes"); + } else { + int categoryOffset = buffer.getIntLE(offset + 0); + if (categoryOffset < 0) { + return ValidationResult.error("Invalid offset for Category"); + } else { + int pos = offset + 8 + categoryOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Category"); + } else { + int categoryLen = VarInt.peek(buffer, pos); + if (categoryLen < 0) { + return ValidationResult.error("Invalid string length for Category"); + } else if (categoryLen > 4096000) { + return ValidationResult.error("Category exceeds max length 4096000"); + } else { + pos += VarInt.length(buffer, pos); + pos += categoryLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Category"); + } else { + categoryOffset = buffer.getIntLE(offset + 4); + if (categoryOffset < 0) { + return ValidationResult.error("Invalid offset for ItemCategory"); + } else { + pos = offset + 8 + categoryOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ItemCategory"); + } else { + categoryLen = VarInt.peek(buffer, pos); + if (categoryLen < 0) { + return ValidationResult.error("Invalid string length for ItemCategory"); + } else if (categoryLen > 4096000) { + return ValidationResult.error("ItemCategory exceeds max length 4096000"); + } else { + pos += VarInt.length(buffer, pos); + pos += categoryLen; + return pos > buffer.writerIndex() ? ValidationResult.error("Buffer overflow reading ItemCategory") : ValidationResult.OK; + } + } + } + } + } + } + } + } + } + + public UpdateCategoryAction clone() { + UpdateCategoryAction copy = new UpdateCategoryAction(); + copy.category = this.category; + copy.itemCategory = this.itemCategory; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateCategoryAction other) + ? false + : Objects.equals(this.category, other.category) && Objects.equals(this.itemCategory, other.itemCategory); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.category, this.itemCategory); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/window/UpdateWindow.java b/src/com/hypixel/hytale/protocol/packets/window/UpdateWindow.java new file mode 100644 index 0000000..78f666b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/window/UpdateWindow.java @@ -0,0 +1,285 @@ +package com.hypixel.hytale.protocol.packets.window; + +import com.hypixel.hytale.protocol.ExtraResources; +import com.hypixel.hytale.protocol.InventorySection; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateWindow implements Packet { + public static final int PACKET_ID = 201; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 5; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 17; + public static final int MAX_SIZE = 1677721600; + public int id; + @Nullable + public String windowData; + @Nullable + public InventorySection inventory; + @Nullable + public ExtraResources extraResources; + + @Override + public int getId() { + return 201; + } + + public UpdateWindow() { + } + + public UpdateWindow(int id, @Nullable String windowData, @Nullable InventorySection inventory, @Nullable ExtraResources extraResources) { + this.id = id; + this.windowData = windowData; + this.inventory = inventory; + this.extraResources = extraResources; + } + + public UpdateWindow(@Nonnull UpdateWindow other) { + this.id = other.id; + this.windowData = other.windowData; + this.inventory = other.inventory; + this.extraResources = other.extraResources; + } + + @Nonnull + public static UpdateWindow deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateWindow obj = new UpdateWindow(); + byte nullBits = buf.getByte(offset); + obj.id = buf.getIntLE(offset + 1); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 17 + buf.getIntLE(offset + 5); + int windowDataLen = VarInt.peek(buf, varPos0); + if (windowDataLen < 0) { + throw ProtocolException.negativeLength("WindowData", windowDataLen); + } + + if (windowDataLen > 4096000) { + throw ProtocolException.stringTooLong("WindowData", windowDataLen, 4096000); + } + + obj.windowData = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 17 + buf.getIntLE(offset + 9); + obj.inventory = InventorySection.deserialize(buf, varPos1); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 17 + buf.getIntLE(offset + 13); + obj.extraResources = ExtraResources.deserialize(buf, varPos2); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 17; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 5); + int pos0 = offset + 17 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 9); + int pos1 = offset + 17 + fieldOffset1; + pos1 += InventorySection.computeBytesConsumed(buf, pos1); + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 13); + int pos2 = offset + 17 + fieldOffset2; + pos2 += ExtraResources.computeBytesConsumed(buf, pos2); + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.windowData != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.inventory != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.extraResources != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.id); + int windowDataOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int inventoryOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int extraResourcesOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.windowData != null) { + buf.setIntLE(windowDataOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.windowData, 4096000); + } else { + buf.setIntLE(windowDataOffsetSlot, -1); + } + + if (this.inventory != null) { + buf.setIntLE(inventoryOffsetSlot, buf.writerIndex() - varBlockStart); + this.inventory.serialize(buf); + } else { + buf.setIntLE(inventoryOffsetSlot, -1); + } + + if (this.extraResources != null) { + buf.setIntLE(extraResourcesOffsetSlot, buf.writerIndex() - varBlockStart); + this.extraResources.serialize(buf); + } else { + buf.setIntLE(extraResourcesOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 17; + if (this.windowData != null) { + size += PacketIO.stringSize(this.windowData); + } + + if (this.inventory != null) { + size += this.inventory.computeSize(); + } + + if (this.extraResources != null) { + size += this.extraResources.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 17) { + return ValidationResult.error("Buffer too small: expected at least 17 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int windowDataOffset = buffer.getIntLE(offset + 5); + if (windowDataOffset < 0) { + return ValidationResult.error("Invalid offset for WindowData"); + } + + int pos = offset + 17 + windowDataOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for WindowData"); + } + + int windowDataLen = VarInt.peek(buffer, pos); + if (windowDataLen < 0) { + return ValidationResult.error("Invalid string length for WindowData"); + } + + if (windowDataLen > 4096000) { + return ValidationResult.error("WindowData exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += windowDataLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading WindowData"); + } + } + + if ((nullBits & 2) != 0) { + int inventoryOffset = buffer.getIntLE(offset + 9); + if (inventoryOffset < 0) { + return ValidationResult.error("Invalid offset for Inventory"); + } + + int posx = offset + 17 + inventoryOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Inventory"); + } + + ValidationResult inventoryResult = InventorySection.validateStructure(buffer, posx); + if (!inventoryResult.isValid()) { + return ValidationResult.error("Invalid Inventory: " + inventoryResult.error()); + } + + posx += InventorySection.computeBytesConsumed(buffer, posx); + } + + if ((nullBits & 4) != 0) { + int extraResourcesOffset = buffer.getIntLE(offset + 13); + if (extraResourcesOffset < 0) { + return ValidationResult.error("Invalid offset for ExtraResources"); + } + + int posxx = offset + 17 + extraResourcesOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ExtraResources"); + } + + ValidationResult extraResourcesResult = ExtraResources.validateStructure(buffer, posxx); + if (!extraResourcesResult.isValid()) { + return ValidationResult.error("Invalid ExtraResources: " + extraResourcesResult.error()); + } + + posxx += ExtraResources.computeBytesConsumed(buffer, posxx); + } + + return ValidationResult.OK; + } + } + + public UpdateWindow clone() { + UpdateWindow copy = new UpdateWindow(); + copy.id = this.id; + copy.windowData = this.windowData; + copy.inventory = this.inventory != null ? this.inventory.clone() : null; + copy.extraResources = this.extraResources != null ? this.extraResources.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateWindow other) + ? false + : this.id == other.id + && Objects.equals(this.windowData, other.windowData) + && Objects.equals(this.inventory, other.inventory) + && Objects.equals(this.extraResources, other.extraResources); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.windowData, this.inventory, this.extraResources); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/window/WindowAction.java b/src/com/hypixel/hytale/protocol/packets/window/WindowAction.java new file mode 100644 index 0000000..8b9c3bc --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/window/WindowAction.java @@ -0,0 +1,108 @@ +package com.hypixel.hytale.protocol.packets.window; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public abstract class WindowAction { + public static final int MAX_SIZE = 32768023; + + public WindowAction() { + } + + @Nonnull + public static WindowAction deserialize(@Nonnull ByteBuf buf, int offset) { + int typeId = VarInt.peek(buf, offset); + int typeIdLen = VarInt.length(buf, offset); + + return (WindowAction)(switch (typeId) { + case 0 -> CraftRecipeAction.deserialize(buf, offset + typeIdLen); + case 1 -> TierUpgradeAction.deserialize(buf, offset + typeIdLen); + case 2 -> SelectSlotAction.deserialize(buf, offset + typeIdLen); + case 3 -> ChangeBlockAction.deserialize(buf, offset + typeIdLen); + case 4 -> SetActiveAction.deserialize(buf, offset + typeIdLen); + case 5 -> CraftItemAction.deserialize(buf, offset + typeIdLen); + case 6 -> UpdateCategoryAction.deserialize(buf, offset + typeIdLen); + case 7 -> CancelCraftingAction.deserialize(buf, offset + typeIdLen); + case 8 -> SortItemsAction.deserialize(buf, offset + typeIdLen); + default -> throw ProtocolException.unknownPolymorphicType("WindowAction", typeId); + }); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + int typeId = VarInt.peek(buf, offset); + int typeIdLen = VarInt.length(buf, offset); + + return typeIdLen + switch (typeId) { + case 0 -> CraftRecipeAction.computeBytesConsumed(buf, offset + typeIdLen); + case 1 -> TierUpgradeAction.computeBytesConsumed(buf, offset + typeIdLen); + case 2 -> SelectSlotAction.computeBytesConsumed(buf, offset + typeIdLen); + case 3 -> ChangeBlockAction.computeBytesConsumed(buf, offset + typeIdLen); + case 4 -> SetActiveAction.computeBytesConsumed(buf, offset + typeIdLen); + case 5 -> CraftItemAction.computeBytesConsumed(buf, offset + typeIdLen); + case 6 -> UpdateCategoryAction.computeBytesConsumed(buf, offset + typeIdLen); + case 7 -> CancelCraftingAction.computeBytesConsumed(buf, offset + typeIdLen); + case 8 -> SortItemsAction.computeBytesConsumed(buf, offset + typeIdLen); + default -> throw ProtocolException.unknownPolymorphicType("WindowAction", typeId); + }; + } + + public int getTypeId() { + if (this instanceof CraftRecipeAction sub) { + return 0; + } else if (this instanceof TierUpgradeAction sub) { + return 1; + } else if (this instanceof SelectSlotAction sub) { + return 2; + } else if (this instanceof ChangeBlockAction sub) { + return 3; + } else if (this instanceof SetActiveAction sub) { + return 4; + } else if (this instanceof CraftItemAction sub) { + return 5; + } else if (this instanceof UpdateCategoryAction sub) { + return 6; + } else if (this instanceof CancelCraftingAction sub) { + return 7; + } else if (this instanceof SortItemsAction sub) { + return 8; + } else { + throw new IllegalStateException("Unknown subtype: " + this.getClass().getName()); + } + } + + public abstract int serialize(@Nonnull ByteBuf var1); + + public abstract int computeSize(); + + public int serializeWithTypeId(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + VarInt.write(buf, this.getTypeId()); + this.serialize(buf); + return buf.writerIndex() - startPos; + } + + public int computeSizeWithTypeId() { + return VarInt.size(this.getTypeId()) + this.computeSize(); + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + int typeId = VarInt.peek(buffer, offset); + int typeIdLen = VarInt.length(buffer, offset); + + return switch (typeId) { + case 0 -> CraftRecipeAction.validateStructure(buffer, offset + typeIdLen); + case 1 -> TierUpgradeAction.validateStructure(buffer, offset + typeIdLen); + case 2 -> SelectSlotAction.validateStructure(buffer, offset + typeIdLen); + case 3 -> ChangeBlockAction.validateStructure(buffer, offset + typeIdLen); + case 4 -> SetActiveAction.validateStructure(buffer, offset + typeIdLen); + case 5 -> CraftItemAction.validateStructure(buffer, offset + typeIdLen); + case 6 -> UpdateCategoryAction.validateStructure(buffer, offset + typeIdLen); + case 7 -> CancelCraftingAction.validateStructure(buffer, offset + typeIdLen); + case 8 -> SortItemsAction.validateStructure(buffer, offset + typeIdLen); + default -> ValidationResult.error("Unknown polymorphic type ID " + typeId + " for WindowAction"); + }; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/window/WindowType.java b/src/com/hypixel/hytale/protocol/packets/window/WindowType.java new file mode 100644 index 0000000..5802eea --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/window/WindowType.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.protocol.packets.window; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum WindowType { + Container(0), + PocketCrafting(1), + BasicCrafting(2), + DiagramCrafting(3), + StructuralCrafting(4), + Processing(5), + Memories(6); + + public static final WindowType[] VALUES = values(); + private final int value; + + private WindowType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static WindowType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("WindowType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/ClearEditorTimeOverride.java b/src/com/hypixel/hytale/protocol/packets/world/ClearEditorTimeOverride.java new file mode 100644 index 0000000..ae5b30b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/ClearEditorTimeOverride.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class ClearEditorTimeOverride implements Packet { + public static final int PACKET_ID = 148; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public ClearEditorTimeOverride() { + } + + @Override + public int getId() { + return 148; + } + + @Nonnull + public static ClearEditorTimeOverride deserialize(@Nonnull ByteBuf buf, int offset) { + return new ClearEditorTimeOverride(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public ClearEditorTimeOverride clone() { + return new ClearEditorTimeOverride(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof ClearEditorTimeOverride other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/PaletteType.java b/src/com/hypixel/hytale/protocol/packets/world/PaletteType.java new file mode 100644 index 0000000..5852264 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/PaletteType.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum PaletteType { + Empty(0), + HalfByte(1), + Byte(2), + Short(3); + + public static final PaletteType[] VALUES = values(); + private final int value; + + private PaletteType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static PaletteType fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("PaletteType", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/PlaySoundEvent2D.java b/src/com/hypixel/hytale/protocol/packets/world/PlaySoundEvent2D.java new file mode 100644 index 0000000..87c9733 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/PlaySoundEvent2D.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class PlaySoundEvent2D implements Packet { + public static final int PACKET_ID = 154; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 13; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 13; + public int soundEventIndex; + @Nonnull + public SoundCategory category = SoundCategory.Music; + public float volumeModifier; + public float pitchModifier; + + @Override + public int getId() { + return 154; + } + + public PlaySoundEvent2D() { + } + + public PlaySoundEvent2D(int soundEventIndex, @Nonnull SoundCategory category, float volumeModifier, float pitchModifier) { + this.soundEventIndex = soundEventIndex; + this.category = category; + this.volumeModifier = volumeModifier; + this.pitchModifier = pitchModifier; + } + + public PlaySoundEvent2D(@Nonnull PlaySoundEvent2D other) { + this.soundEventIndex = other.soundEventIndex; + this.category = other.category; + this.volumeModifier = other.volumeModifier; + this.pitchModifier = other.pitchModifier; + } + + @Nonnull + public static PlaySoundEvent2D deserialize(@Nonnull ByteBuf buf, int offset) { + PlaySoundEvent2D obj = new PlaySoundEvent2D(); + obj.soundEventIndex = buf.getIntLE(offset + 0); + obj.category = SoundCategory.fromValue(buf.getByte(offset + 4)); + obj.volumeModifier = buf.getFloatLE(offset + 5); + obj.pitchModifier = buf.getFloatLE(offset + 9); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 13; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.soundEventIndex); + buf.writeByte(this.category.getValue()); + buf.writeFloatLE(this.volumeModifier); + buf.writeFloatLE(this.pitchModifier); + } + + @Override + public int computeSize() { + return 13; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 13 ? ValidationResult.error("Buffer too small: expected at least 13 bytes") : ValidationResult.OK; + } + + public PlaySoundEvent2D clone() { + PlaySoundEvent2D copy = new PlaySoundEvent2D(); + copy.soundEventIndex = this.soundEventIndex; + copy.category = this.category; + copy.volumeModifier = this.volumeModifier; + copy.pitchModifier = this.pitchModifier; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof PlaySoundEvent2D other) + ? false + : this.soundEventIndex == other.soundEventIndex + && Objects.equals(this.category, other.category) + && this.volumeModifier == other.volumeModifier + && this.pitchModifier == other.pitchModifier; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.soundEventIndex, this.category, this.volumeModifier, this.pitchModifier); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/PlaySoundEvent3D.java b/src/com/hypixel/hytale/protocol/packets/world/PlaySoundEvent3D.java new file mode 100644 index 0000000..1c233ea --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/PlaySoundEvent3D.java @@ -0,0 +1,129 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.Position; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlaySoundEvent3D implements Packet { + public static final int PACKET_ID = 155; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 38; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 38; + public static final int MAX_SIZE = 38; + public int soundEventIndex; + @Nonnull + public SoundCategory category = SoundCategory.Music; + @Nullable + public Position position; + public float volumeModifier; + public float pitchModifier; + + @Override + public int getId() { + return 155; + } + + public PlaySoundEvent3D() { + } + + public PlaySoundEvent3D(int soundEventIndex, @Nonnull SoundCategory category, @Nullable Position position, float volumeModifier, float pitchModifier) { + this.soundEventIndex = soundEventIndex; + this.category = category; + this.position = position; + this.volumeModifier = volumeModifier; + this.pitchModifier = pitchModifier; + } + + public PlaySoundEvent3D(@Nonnull PlaySoundEvent3D other) { + this.soundEventIndex = other.soundEventIndex; + this.category = other.category; + this.position = other.position; + this.volumeModifier = other.volumeModifier; + this.pitchModifier = other.pitchModifier; + } + + @Nonnull + public static PlaySoundEvent3D deserialize(@Nonnull ByteBuf buf, int offset) { + PlaySoundEvent3D obj = new PlaySoundEvent3D(); + byte nullBits = buf.getByte(offset); + obj.soundEventIndex = buf.getIntLE(offset + 1); + obj.category = SoundCategory.fromValue(buf.getByte(offset + 5)); + if ((nullBits & 1) != 0) { + obj.position = Position.deserialize(buf, offset + 6); + } + + obj.volumeModifier = buf.getFloatLE(offset + 30); + obj.pitchModifier = buf.getFloatLE(offset + 34); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 38; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.position != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.soundEventIndex); + buf.writeByte(this.category.getValue()); + if (this.position != null) { + this.position.serialize(buf); + } else { + buf.writeZero(24); + } + + buf.writeFloatLE(this.volumeModifier); + buf.writeFloatLE(this.pitchModifier); + } + + @Override + public int computeSize() { + return 38; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 38 ? ValidationResult.error("Buffer too small: expected at least 38 bytes") : ValidationResult.OK; + } + + public PlaySoundEvent3D clone() { + PlaySoundEvent3D copy = new PlaySoundEvent3D(); + copy.soundEventIndex = this.soundEventIndex; + copy.category = this.category; + copy.position = this.position != null ? this.position.clone() : null; + copy.volumeModifier = this.volumeModifier; + copy.pitchModifier = this.pitchModifier; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof PlaySoundEvent3D other) + ? false + : this.soundEventIndex == other.soundEventIndex + && Objects.equals(this.category, other.category) + && Objects.equals(this.position, other.position) + && this.volumeModifier == other.volumeModifier + && this.pitchModifier == other.pitchModifier; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.soundEventIndex, this.category, this.position, this.volumeModifier, this.pitchModifier); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/PlaySoundEventEntity.java b/src/com/hypixel/hytale/protocol/packets/world/PlaySoundEventEntity.java new file mode 100644 index 0000000..6d8d3b9 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/PlaySoundEventEntity.java @@ -0,0 +1,102 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class PlaySoundEventEntity implements Packet { + public static final int PACKET_ID = 156; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 16; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 16; + public static final int MAX_SIZE = 16; + public int soundEventIndex; + public int networkId; + public float volumeModifier; + public float pitchModifier; + + @Override + public int getId() { + return 156; + } + + public PlaySoundEventEntity() { + } + + public PlaySoundEventEntity(int soundEventIndex, int networkId, float volumeModifier, float pitchModifier) { + this.soundEventIndex = soundEventIndex; + this.networkId = networkId; + this.volumeModifier = volumeModifier; + this.pitchModifier = pitchModifier; + } + + public PlaySoundEventEntity(@Nonnull PlaySoundEventEntity other) { + this.soundEventIndex = other.soundEventIndex; + this.networkId = other.networkId; + this.volumeModifier = other.volumeModifier; + this.pitchModifier = other.pitchModifier; + } + + @Nonnull + public static PlaySoundEventEntity deserialize(@Nonnull ByteBuf buf, int offset) { + PlaySoundEventEntity obj = new PlaySoundEventEntity(); + obj.soundEventIndex = buf.getIntLE(offset + 0); + obj.networkId = buf.getIntLE(offset + 4); + obj.volumeModifier = buf.getFloatLE(offset + 8); + obj.pitchModifier = buf.getFloatLE(offset + 12); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 16; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.soundEventIndex); + buf.writeIntLE(this.networkId); + buf.writeFloatLE(this.volumeModifier); + buf.writeFloatLE(this.pitchModifier); + } + + @Override + public int computeSize() { + return 16; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 16 ? ValidationResult.error("Buffer too small: expected at least 16 bytes") : ValidationResult.OK; + } + + public PlaySoundEventEntity clone() { + PlaySoundEventEntity copy = new PlaySoundEventEntity(); + copy.soundEventIndex = this.soundEventIndex; + copy.networkId = this.networkId; + copy.volumeModifier = this.volumeModifier; + copy.pitchModifier = this.pitchModifier; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof PlaySoundEventEntity other) + ? false + : this.soundEventIndex == other.soundEventIndex + && this.networkId == other.networkId + && this.volumeModifier == other.volumeModifier + && this.pitchModifier == other.pitchModifier; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.soundEventIndex, this.networkId, this.volumeModifier, this.pitchModifier); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/RotationAxis.java b/src/com/hypixel/hytale/protocol/packets/world/RotationAxis.java new file mode 100644 index 0000000..5cff0b0 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/RotationAxis.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum RotationAxis { + X(0), + Y(1), + Z(2); + + public static final RotationAxis[] VALUES = values(); + private final int value; + + private RotationAxis(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static RotationAxis fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("RotationAxis", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/RotationDirection.java b/src/com/hypixel/hytale/protocol/packets/world/RotationDirection.java new file mode 100644 index 0000000..e381f6a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/RotationDirection.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.io.ProtocolException; + +public enum RotationDirection { + Positive(0), + Negative(1); + + public static final RotationDirection[] VALUES = values(); + private final int value; + + private RotationDirection(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static RotationDirection fromValue(int value) { + if (value >= 0 && value < VALUES.length) { + return VALUES[value]; + } else { + throw ProtocolException.invalidEnumValue("RotationDirection", value); + } + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/ServerSetBlock.java b/src/com/hypixel/hytale/protocol/packets/world/ServerSetBlock.java new file mode 100644 index 0000000..7189861 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/ServerSetBlock.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class ServerSetBlock implements Packet { + public static final int PACKET_ID = 140; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 19; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 19; + public static final int MAX_SIZE = 19; + public int x; + public int y; + public int z; + public int blockId; + public short filler; + public byte rotation; + + @Override + public int getId() { + return 140; + } + + public ServerSetBlock() { + } + + public ServerSetBlock(int x, int y, int z, int blockId, short filler, byte rotation) { + this.x = x; + this.y = y; + this.z = z; + this.blockId = blockId; + this.filler = filler; + this.rotation = rotation; + } + + public ServerSetBlock(@Nonnull ServerSetBlock other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.blockId = other.blockId; + this.filler = other.filler; + this.rotation = other.rotation; + } + + @Nonnull + public static ServerSetBlock deserialize(@Nonnull ByteBuf buf, int offset) { + ServerSetBlock obj = new ServerSetBlock(); + obj.x = buf.getIntLE(offset + 0); + obj.y = buf.getIntLE(offset + 4); + obj.z = buf.getIntLE(offset + 8); + obj.blockId = buf.getIntLE(offset + 12); + obj.filler = buf.getShortLE(offset + 16); + obj.rotation = buf.getByte(offset + 18); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 19; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.x); + buf.writeIntLE(this.y); + buf.writeIntLE(this.z); + buf.writeIntLE(this.blockId); + buf.writeShortLE(this.filler); + buf.writeByte(this.rotation); + } + + @Override + public int computeSize() { + return 19; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 19 ? ValidationResult.error("Buffer too small: expected at least 19 bytes") : ValidationResult.OK; + } + + public ServerSetBlock clone() { + ServerSetBlock copy = new ServerSetBlock(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + copy.blockId = this.blockId; + copy.filler = this.filler; + copy.rotation = this.rotation; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ServerSetBlock other) + ? false + : this.x == other.x + && this.y == other.y + && this.z == other.z + && this.blockId == other.blockId + && this.filler == other.filler + && this.rotation == other.rotation; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y, this.z, this.blockId, this.filler, this.rotation); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/ServerSetBlocks.java b/src/com/hypixel/hytale/protocol/packets/world/ServerSetBlocks.java new file mode 100644 index 0000000..8a27087 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/ServerSetBlocks.java @@ -0,0 +1,157 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class ServerSetBlocks implements Packet { + public static final int PACKET_ID = 141; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 12; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 12; + public static final int MAX_SIZE = 36864017; + public int x; + public int y; + public int z; + @Nonnull + public SetBlockCmd[] cmds = new SetBlockCmd[0]; + + @Override + public int getId() { + return 141; + } + + public ServerSetBlocks() { + } + + public ServerSetBlocks(int x, int y, int z, @Nonnull SetBlockCmd[] cmds) { + this.x = x; + this.y = y; + this.z = z; + this.cmds = cmds; + } + + public ServerSetBlocks(@Nonnull ServerSetBlocks other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.cmds = other.cmds; + } + + @Nonnull + public static ServerSetBlocks deserialize(@Nonnull ByteBuf buf, int offset) { + ServerSetBlocks obj = new ServerSetBlocks(); + obj.x = buf.getIntLE(offset + 0); + obj.y = buf.getIntLE(offset + 4); + obj.z = buf.getIntLE(offset + 8); + int pos = offset + 12; + int cmdsCount = VarInt.peek(buf, pos); + if (cmdsCount < 0) { + throw ProtocolException.negativeLength("Cmds", cmdsCount); + } else if (cmdsCount > 4096000) { + throw ProtocolException.arrayTooLong("Cmds", cmdsCount, 4096000); + } else { + int cmdsVarLen = VarInt.size(cmdsCount); + if (pos + cmdsVarLen + cmdsCount * 9L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Cmds", pos + cmdsVarLen + cmdsCount * 9, buf.readableBytes()); + } else { + pos += cmdsVarLen; + obj.cmds = new SetBlockCmd[cmdsCount]; + + for (int i = 0; i < cmdsCount; i++) { + obj.cmds[i] = SetBlockCmd.deserialize(buf, pos); + pos += SetBlockCmd.computeBytesConsumed(buf, pos); + } + + return obj; + } + } + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + int pos = offset + 12; + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += SetBlockCmd.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.x); + buf.writeIntLE(this.y); + buf.writeIntLE(this.z); + if (this.cmds.length > 4096000) { + throw ProtocolException.arrayTooLong("Cmds", this.cmds.length, 4096000); + } else { + VarInt.write(buf, this.cmds.length); + + for (SetBlockCmd item : this.cmds) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 12; + return size + VarInt.size(this.cmds.length) + this.cmds.length * 9; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 12) { + return ValidationResult.error("Buffer too small: expected at least 12 bytes"); + } else { + int pos = offset + 12; + int cmdsCount = VarInt.peek(buffer, pos); + if (cmdsCount < 0) { + return ValidationResult.error("Invalid array count for Cmds"); + } else if (cmdsCount > 4096000) { + return ValidationResult.error("Cmds exceeds max length 4096000"); + } else { + pos += VarInt.length(buffer, pos); + pos += cmdsCount * 9; + return pos > buffer.writerIndex() ? ValidationResult.error("Buffer overflow reading Cmds") : ValidationResult.OK; + } + } + } + + public ServerSetBlocks clone() { + ServerSetBlocks copy = new ServerSetBlocks(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + copy.cmds = Arrays.stream(this.cmds).map(e -> e.clone()).toArray(SetBlockCmd[]::new); + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ServerSetBlocks other) + ? false + : this.x == other.x && this.y == other.y && this.z == other.z && Arrays.equals((Object[])this.cmds, (Object[])other.cmds); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.x); + result = 31 * result + Integer.hashCode(this.y); + result = 31 * result + Integer.hashCode(this.z); + return 31 * result + Arrays.hashCode((Object[])this.cmds); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/ServerSetFluid.java b/src/com/hypixel/hytale/protocol/packets/world/ServerSetFluid.java new file mode 100644 index 0000000..71add37 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/ServerSetFluid.java @@ -0,0 +1,105 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class ServerSetFluid implements Packet { + public static final int PACKET_ID = 142; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 17; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 17; + public static final int MAX_SIZE = 17; + public int x; + public int y; + public int z; + public int fluidId; + public byte fluidLevel; + + @Override + public int getId() { + return 142; + } + + public ServerSetFluid() { + } + + public ServerSetFluid(int x, int y, int z, int fluidId, byte fluidLevel) { + this.x = x; + this.y = y; + this.z = z; + this.fluidId = fluidId; + this.fluidLevel = fluidLevel; + } + + public ServerSetFluid(@Nonnull ServerSetFluid other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.fluidId = other.fluidId; + this.fluidLevel = other.fluidLevel; + } + + @Nonnull + public static ServerSetFluid deserialize(@Nonnull ByteBuf buf, int offset) { + ServerSetFluid obj = new ServerSetFluid(); + obj.x = buf.getIntLE(offset + 0); + obj.y = buf.getIntLE(offset + 4); + obj.z = buf.getIntLE(offset + 8); + obj.fluidId = buf.getIntLE(offset + 12); + obj.fluidLevel = buf.getByte(offset + 16); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 17; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.x); + buf.writeIntLE(this.y); + buf.writeIntLE(this.z); + buf.writeIntLE(this.fluidId); + buf.writeByte(this.fluidLevel); + } + + @Override + public int computeSize() { + return 17; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 17 ? ValidationResult.error("Buffer too small: expected at least 17 bytes") : ValidationResult.OK; + } + + public ServerSetFluid clone() { + ServerSetFluid copy = new ServerSetFluid(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + copy.fluidId = this.fluidId; + copy.fluidLevel = this.fluidLevel; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ServerSetFluid other) + ? false + : this.x == other.x && this.y == other.y && this.z == other.z && this.fluidId == other.fluidId && this.fluidLevel == other.fluidLevel; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y, this.z, this.fluidId, this.fluidLevel); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/ServerSetFluids.java b/src/com/hypixel/hytale/protocol/packets/world/ServerSetFluids.java new file mode 100644 index 0000000..f9d070d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/ServerSetFluids.java @@ -0,0 +1,157 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class ServerSetFluids implements Packet { + public static final int PACKET_ID = 143; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 12; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 12; + public static final int MAX_SIZE = 28672017; + public int x; + public int y; + public int z; + @Nonnull + public SetFluidCmd[] cmds = new SetFluidCmd[0]; + + @Override + public int getId() { + return 143; + } + + public ServerSetFluids() { + } + + public ServerSetFluids(int x, int y, int z, @Nonnull SetFluidCmd[] cmds) { + this.x = x; + this.y = y; + this.z = z; + this.cmds = cmds; + } + + public ServerSetFluids(@Nonnull ServerSetFluids other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.cmds = other.cmds; + } + + @Nonnull + public static ServerSetFluids deserialize(@Nonnull ByteBuf buf, int offset) { + ServerSetFluids obj = new ServerSetFluids(); + obj.x = buf.getIntLE(offset + 0); + obj.y = buf.getIntLE(offset + 4); + obj.z = buf.getIntLE(offset + 8); + int pos = offset + 12; + int cmdsCount = VarInt.peek(buf, pos); + if (cmdsCount < 0) { + throw ProtocolException.negativeLength("Cmds", cmdsCount); + } else if (cmdsCount > 4096000) { + throw ProtocolException.arrayTooLong("Cmds", cmdsCount, 4096000); + } else { + int cmdsVarLen = VarInt.size(cmdsCount); + if (pos + cmdsVarLen + cmdsCount * 7L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Cmds", pos + cmdsVarLen + cmdsCount * 7, buf.readableBytes()); + } else { + pos += cmdsVarLen; + obj.cmds = new SetFluidCmd[cmdsCount]; + + for (int i = 0; i < cmdsCount; i++) { + obj.cmds[i] = SetFluidCmd.deserialize(buf, pos); + pos += SetFluidCmd.computeBytesConsumed(buf, pos); + } + + return obj; + } + } + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + int pos = offset + 12; + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < arrLen; i++) { + pos += SetFluidCmd.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.x); + buf.writeIntLE(this.y); + buf.writeIntLE(this.z); + if (this.cmds.length > 4096000) { + throw ProtocolException.arrayTooLong("Cmds", this.cmds.length, 4096000); + } else { + VarInt.write(buf, this.cmds.length); + + for (SetFluidCmd item : this.cmds) { + item.serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 12; + return size + VarInt.size(this.cmds.length) + this.cmds.length * 7; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 12) { + return ValidationResult.error("Buffer too small: expected at least 12 bytes"); + } else { + int pos = offset + 12; + int cmdsCount = VarInt.peek(buffer, pos); + if (cmdsCount < 0) { + return ValidationResult.error("Invalid array count for Cmds"); + } else if (cmdsCount > 4096000) { + return ValidationResult.error("Cmds exceeds max length 4096000"); + } else { + pos += VarInt.length(buffer, pos); + pos += cmdsCount * 7; + return pos > buffer.writerIndex() ? ValidationResult.error("Buffer overflow reading Cmds") : ValidationResult.OK; + } + } + } + + public ServerSetFluids clone() { + ServerSetFluids copy = new ServerSetFluids(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + copy.cmds = Arrays.stream(this.cmds).map(e -> e.clone()).toArray(SetFluidCmd[]::new); + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ServerSetFluids other) + ? false + : this.x == other.x && this.y == other.y && this.z == other.z && Arrays.equals((Object[])this.cmds, (Object[])other.cmds); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.x); + result = 31 * result + Integer.hashCode(this.y); + result = 31 * result + Integer.hashCode(this.z); + return 31 * result + Arrays.hashCode((Object[])this.cmds); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/ServerSetPaused.java b/src/com/hypixel/hytale/protocol/packets/world/ServerSetPaused.java new file mode 100644 index 0000000..a6a8fb5 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/ServerSetPaused.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class ServerSetPaused implements Packet { + public static final int PACKET_ID = 159; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean paused; + + @Override + public int getId() { + return 159; + } + + public ServerSetPaused() { + } + + public ServerSetPaused(boolean paused) { + this.paused = paused; + } + + public ServerSetPaused(@Nonnull ServerSetPaused other) { + this.paused = other.paused; + } + + @Nonnull + public static ServerSetPaused deserialize(@Nonnull ByteBuf buf, int offset) { + ServerSetPaused obj = new ServerSetPaused(); + obj.paused = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.paused ? 1 : 0); + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public ServerSetPaused clone() { + ServerSetPaused copy = new ServerSetPaused(); + copy.paused = this.paused; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof ServerSetPaused other ? this.paused == other.paused : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.paused); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/SetBlockCmd.java b/src/com/hypixel/hytale/protocol/packets/world/SetBlockCmd.java new file mode 100644 index 0000000..a784eca --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/SetBlockCmd.java @@ -0,0 +1,89 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SetBlockCmd { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 9; + public short index; + public int blockId; + public short filler; + public byte rotation; + + public SetBlockCmd() { + } + + public SetBlockCmd(short index, int blockId, short filler, byte rotation) { + this.index = index; + this.blockId = blockId; + this.filler = filler; + this.rotation = rotation; + } + + public SetBlockCmd(@Nonnull SetBlockCmd other) { + this.index = other.index; + this.blockId = other.blockId; + this.filler = other.filler; + this.rotation = other.rotation; + } + + @Nonnull + public static SetBlockCmd deserialize(@Nonnull ByteBuf buf, int offset) { + SetBlockCmd obj = new SetBlockCmd(); + obj.index = buf.getShortLE(offset + 0); + obj.blockId = buf.getIntLE(offset + 2); + obj.filler = buf.getShortLE(offset + 6); + obj.rotation = buf.getByte(offset + 8); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 9; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeShortLE(this.index); + buf.writeIntLE(this.blockId); + buf.writeShortLE(this.filler); + buf.writeByte(this.rotation); + } + + public int computeSize() { + return 9; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 9 ? ValidationResult.error("Buffer too small: expected at least 9 bytes") : ValidationResult.OK; + } + + public SetBlockCmd clone() { + SetBlockCmd copy = new SetBlockCmd(); + copy.index = this.index; + copy.blockId = this.blockId; + copy.filler = this.filler; + copy.rotation = this.rotation; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SetBlockCmd other) + ? false + : this.index == other.index && this.blockId == other.blockId && this.filler == other.filler && this.rotation == other.rotation; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.index, this.blockId, this.filler, this.rotation); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/SetChunk.java b/src/com/hypixel/hytale/protocol/packets/world/SetChunk.java new file mode 100644 index 0000000..6827591 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/SetChunk.java @@ -0,0 +1,389 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SetChunk implements Packet { + public static final int PACKET_ID = 131; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 13; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 25; + public static final int MAX_SIZE = 12288040; + public int x; + public int y; + public int z; + @Nullable + public byte[] localLight; + @Nullable + public byte[] globalLight; + @Nullable + public byte[] data; + + @Override + public int getId() { + return 131; + } + + public SetChunk() { + } + + public SetChunk(int x, int y, int z, @Nullable byte[] localLight, @Nullable byte[] globalLight, @Nullable byte[] data) { + this.x = x; + this.y = y; + this.z = z; + this.localLight = localLight; + this.globalLight = globalLight; + this.data = data; + } + + public SetChunk(@Nonnull SetChunk other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.localLight = other.localLight; + this.globalLight = other.globalLight; + this.data = other.data; + } + + @Nonnull + public static SetChunk deserialize(@Nonnull ByteBuf buf, int offset) { + SetChunk obj = new SetChunk(); + byte nullBits = buf.getByte(offset); + obj.x = buf.getIntLE(offset + 1); + obj.y = buf.getIntLE(offset + 5); + obj.z = buf.getIntLE(offset + 9); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 25 + buf.getIntLE(offset + 13); + int localLightCount = VarInt.peek(buf, varPos0); + if (localLightCount < 0) { + throw ProtocolException.negativeLength("LocalLight", localLightCount); + } + + if (localLightCount > 4096000) { + throw ProtocolException.arrayTooLong("LocalLight", localLightCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + localLightCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("LocalLight", varPos0 + varIntLen + localLightCount * 1, buf.readableBytes()); + } + + obj.localLight = new byte[localLightCount]; + + for (int i = 0; i < localLightCount; i++) { + obj.localLight[i] = buf.getByte(varPos0 + varIntLen + i * 1); + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 25 + buf.getIntLE(offset + 17); + int globalLightCount = VarInt.peek(buf, varPos1); + if (globalLightCount < 0) { + throw ProtocolException.negativeLength("GlobalLight", globalLightCount); + } + + if (globalLightCount > 4096000) { + throw ProtocolException.arrayTooLong("GlobalLight", globalLightCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + globalLightCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("GlobalLight", varPos1 + varIntLen + globalLightCount * 1, buf.readableBytes()); + } + + obj.globalLight = new byte[globalLightCount]; + + for (int i = 0; i < globalLightCount; i++) { + obj.globalLight[i] = buf.getByte(varPos1 + varIntLen + i * 1); + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 25 + buf.getIntLE(offset + 21); + int dataCount = VarInt.peek(buf, varPos2); + if (dataCount < 0) { + throw ProtocolException.negativeLength("Data", dataCount); + } + + if (dataCount > 4096000) { + throw ProtocolException.arrayTooLong("Data", dataCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + dataCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Data", varPos2 + varIntLen + dataCount * 1, buf.readableBytes()); + } + + obj.data = new byte[dataCount]; + + for (int i = 0; i < dataCount; i++) { + obj.data[i] = buf.getByte(varPos2 + varIntLen + i * 1); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 25; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 13); + int pos0 = offset + 25 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + arrLen * 1; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 17); + int pos1 = offset + 25 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + arrLen * 1; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 21); + int pos2 = offset + 25 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + arrLen * 1; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.localLight != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.globalLight != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.data != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.x); + buf.writeIntLE(this.y); + buf.writeIntLE(this.z); + int localLightOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int globalLightOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int dataOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.localLight != null) { + buf.setIntLE(localLightOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.localLight.length > 4096000) { + throw ProtocolException.arrayTooLong("LocalLight", this.localLight.length, 4096000); + } + + VarInt.write(buf, this.localLight.length); + + for (byte item : this.localLight) { + buf.writeByte(item); + } + } else { + buf.setIntLE(localLightOffsetSlot, -1); + } + + if (this.globalLight != null) { + buf.setIntLE(globalLightOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.globalLight.length > 4096000) { + throw ProtocolException.arrayTooLong("GlobalLight", this.globalLight.length, 4096000); + } + + VarInt.write(buf, this.globalLight.length); + + for (byte item : this.globalLight) { + buf.writeByte(item); + } + } else { + buf.setIntLE(globalLightOffsetSlot, -1); + } + + if (this.data != null) { + buf.setIntLE(dataOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.data.length > 4096000) { + throw ProtocolException.arrayTooLong("Data", this.data.length, 4096000); + } + + VarInt.write(buf, this.data.length); + + for (byte item : this.data) { + buf.writeByte(item); + } + } else { + buf.setIntLE(dataOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 25; + if (this.localLight != null) { + size += VarInt.size(this.localLight.length) + this.localLight.length * 1; + } + + if (this.globalLight != null) { + size += VarInt.size(this.globalLight.length) + this.globalLight.length * 1; + } + + if (this.data != null) { + size += VarInt.size(this.data.length) + this.data.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 25) { + return ValidationResult.error("Buffer too small: expected at least 25 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int localLightOffset = buffer.getIntLE(offset + 13); + if (localLightOffset < 0) { + return ValidationResult.error("Invalid offset for LocalLight"); + } + + int pos = offset + 25 + localLightOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for LocalLight"); + } + + int localLightCount = VarInt.peek(buffer, pos); + if (localLightCount < 0) { + return ValidationResult.error("Invalid array count for LocalLight"); + } + + if (localLightCount > 4096000) { + return ValidationResult.error("LocalLight exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += localLightCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading LocalLight"); + } + } + + if ((nullBits & 2) != 0) { + int globalLightOffset = buffer.getIntLE(offset + 17); + if (globalLightOffset < 0) { + return ValidationResult.error("Invalid offset for GlobalLight"); + } + + int posx = offset + 25 + globalLightOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for GlobalLight"); + } + + int globalLightCount = VarInt.peek(buffer, posx); + if (globalLightCount < 0) { + return ValidationResult.error("Invalid array count for GlobalLight"); + } + + if (globalLightCount > 4096000) { + return ValidationResult.error("GlobalLight exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += globalLightCount * 1; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading GlobalLight"); + } + } + + if ((nullBits & 4) != 0) { + int dataOffset = buffer.getIntLE(offset + 21); + if (dataOffset < 0) { + return ValidationResult.error("Invalid offset for Data"); + } + + int posxx = offset + 25 + dataOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Data"); + } + + int dataCount = VarInt.peek(buffer, posxx); + if (dataCount < 0) { + return ValidationResult.error("Invalid array count for Data"); + } + + if (dataCount > 4096000) { + return ValidationResult.error("Data exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += dataCount * 1; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Data"); + } + } + + return ValidationResult.OK; + } + } + + public SetChunk clone() { + SetChunk copy = new SetChunk(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + copy.localLight = this.localLight != null ? Arrays.copyOf(this.localLight, this.localLight.length) : null; + copy.globalLight = this.globalLight != null ? Arrays.copyOf(this.globalLight, this.globalLight.length) : null; + copy.data = this.data != null ? Arrays.copyOf(this.data, this.data.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SetChunk other) + ? false + : this.x == other.x + && this.y == other.y + && this.z == other.z + && Arrays.equals(this.localLight, other.localLight) + && Arrays.equals(this.globalLight, other.globalLight) + && Arrays.equals(this.data, other.data); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.x); + result = 31 * result + Integer.hashCode(this.y); + result = 31 * result + Integer.hashCode(this.z); + result = 31 * result + Arrays.hashCode(this.localLight); + result = 31 * result + Arrays.hashCode(this.globalLight); + return 31 * result + Arrays.hashCode(this.data); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/SetChunkEnvironments.java b/src/com/hypixel/hytale/protocol/packets/world/SetChunkEnvironments.java new file mode 100644 index 0000000..e8e4365 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/SetChunkEnvironments.java @@ -0,0 +1,177 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SetChunkEnvironments implements Packet { + public static final int PACKET_ID = 134; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 4096014; + public int x; + public int z; + @Nullable + public byte[] environments; + + @Override + public int getId() { + return 134; + } + + public SetChunkEnvironments() { + } + + public SetChunkEnvironments(int x, int z, @Nullable byte[] environments) { + this.x = x; + this.z = z; + this.environments = environments; + } + + public SetChunkEnvironments(@Nonnull SetChunkEnvironments other) { + this.x = other.x; + this.z = other.z; + this.environments = other.environments; + } + + @Nonnull + public static SetChunkEnvironments deserialize(@Nonnull ByteBuf buf, int offset) { + SetChunkEnvironments obj = new SetChunkEnvironments(); + byte nullBits = buf.getByte(offset); + obj.x = buf.getIntLE(offset + 1); + obj.z = buf.getIntLE(offset + 5); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int environmentsCount = VarInt.peek(buf, pos); + if (environmentsCount < 0) { + throw ProtocolException.negativeLength("Environments", environmentsCount); + } + + if (environmentsCount > 4096000) { + throw ProtocolException.arrayTooLong("Environments", environmentsCount, 4096000); + } + + int environmentsVarLen = VarInt.size(environmentsCount); + if (pos + environmentsVarLen + environmentsCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Environments", pos + environmentsVarLen + environmentsCount * 1, buf.readableBytes()); + } + + pos += environmentsVarLen; + obj.environments = new byte[environmentsCount]; + + for (int i = 0; i < environmentsCount; i++) { + obj.environments[i] = buf.getByte(pos + i * 1); + } + + pos += environmentsCount * 1; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 1; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.environments != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.x); + buf.writeIntLE(this.z); + if (this.environments != null) { + if (this.environments.length > 4096000) { + throw ProtocolException.arrayTooLong("Environments", this.environments.length, 4096000); + } + + VarInt.write(buf, this.environments.length); + + for (byte item : this.environments) { + buf.writeByte(item); + } + } + } + + @Override + public int computeSize() { + int size = 9; + if (this.environments != null) { + size += VarInt.size(this.environments.length) + this.environments.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int environmentsCount = VarInt.peek(buffer, pos); + if (environmentsCount < 0) { + return ValidationResult.error("Invalid array count for Environments"); + } + + if (environmentsCount > 4096000) { + return ValidationResult.error("Environments exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += environmentsCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Environments"); + } + } + + return ValidationResult.OK; + } + } + + public SetChunkEnvironments clone() { + SetChunkEnvironments copy = new SetChunkEnvironments(); + copy.x = this.x; + copy.z = this.z; + copy.environments = this.environments != null ? Arrays.copyOf(this.environments, this.environments.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SetChunkEnvironments other) + ? false + : this.x == other.x && this.z == other.z && Arrays.equals(this.environments, other.environments); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.x); + result = 31 * result + Integer.hashCode(this.z); + return 31 * result + Arrays.hashCode(this.environments); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/SetChunkHeightmap.java b/src/com/hypixel/hytale/protocol/packets/world/SetChunkHeightmap.java new file mode 100644 index 0000000..e83f20b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/SetChunkHeightmap.java @@ -0,0 +1,175 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SetChunkHeightmap implements Packet { + public static final int PACKET_ID = 132; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 4096014; + public int x; + public int z; + @Nullable + public byte[] heightmap; + + @Override + public int getId() { + return 132; + } + + public SetChunkHeightmap() { + } + + public SetChunkHeightmap(int x, int z, @Nullable byte[] heightmap) { + this.x = x; + this.z = z; + this.heightmap = heightmap; + } + + public SetChunkHeightmap(@Nonnull SetChunkHeightmap other) { + this.x = other.x; + this.z = other.z; + this.heightmap = other.heightmap; + } + + @Nonnull + public static SetChunkHeightmap deserialize(@Nonnull ByteBuf buf, int offset) { + SetChunkHeightmap obj = new SetChunkHeightmap(); + byte nullBits = buf.getByte(offset); + obj.x = buf.getIntLE(offset + 1); + obj.z = buf.getIntLE(offset + 5); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int heightmapCount = VarInt.peek(buf, pos); + if (heightmapCount < 0) { + throw ProtocolException.negativeLength("Heightmap", heightmapCount); + } + + if (heightmapCount > 4096000) { + throw ProtocolException.arrayTooLong("Heightmap", heightmapCount, 4096000); + } + + int heightmapVarLen = VarInt.size(heightmapCount); + if (pos + heightmapVarLen + heightmapCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Heightmap", pos + heightmapVarLen + heightmapCount * 1, buf.readableBytes()); + } + + pos += heightmapVarLen; + obj.heightmap = new byte[heightmapCount]; + + for (int i = 0; i < heightmapCount; i++) { + obj.heightmap[i] = buf.getByte(pos + i * 1); + } + + pos += heightmapCount * 1; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 1; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.heightmap != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.x); + buf.writeIntLE(this.z); + if (this.heightmap != null) { + if (this.heightmap.length > 4096000) { + throw ProtocolException.arrayTooLong("Heightmap", this.heightmap.length, 4096000); + } + + VarInt.write(buf, this.heightmap.length); + + for (byte item : this.heightmap) { + buf.writeByte(item); + } + } + } + + @Override + public int computeSize() { + int size = 9; + if (this.heightmap != null) { + size += VarInt.size(this.heightmap.length) + this.heightmap.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int heightmapCount = VarInt.peek(buffer, pos); + if (heightmapCount < 0) { + return ValidationResult.error("Invalid array count for Heightmap"); + } + + if (heightmapCount > 4096000) { + return ValidationResult.error("Heightmap exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += heightmapCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Heightmap"); + } + } + + return ValidationResult.OK; + } + } + + public SetChunkHeightmap clone() { + SetChunkHeightmap copy = new SetChunkHeightmap(); + copy.x = this.x; + copy.z = this.z; + copy.heightmap = this.heightmap != null ? Arrays.copyOf(this.heightmap, this.heightmap.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SetChunkHeightmap other) ? false : this.x == other.x && this.z == other.z && Arrays.equals(this.heightmap, other.heightmap); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.x); + result = 31 * result + Integer.hashCode(this.z); + return 31 * result + Arrays.hashCode(this.heightmap); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/SetChunkTintmap.java b/src/com/hypixel/hytale/protocol/packets/world/SetChunkTintmap.java new file mode 100644 index 0000000..920fef1 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/SetChunkTintmap.java @@ -0,0 +1,175 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SetChunkTintmap implements Packet { + public static final int PACKET_ID = 133; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 4096014; + public int x; + public int z; + @Nullable + public byte[] tintmap; + + @Override + public int getId() { + return 133; + } + + public SetChunkTintmap() { + } + + public SetChunkTintmap(int x, int z, @Nullable byte[] tintmap) { + this.x = x; + this.z = z; + this.tintmap = tintmap; + } + + public SetChunkTintmap(@Nonnull SetChunkTintmap other) { + this.x = other.x; + this.z = other.z; + this.tintmap = other.tintmap; + } + + @Nonnull + public static SetChunkTintmap deserialize(@Nonnull ByteBuf buf, int offset) { + SetChunkTintmap obj = new SetChunkTintmap(); + byte nullBits = buf.getByte(offset); + obj.x = buf.getIntLE(offset + 1); + obj.z = buf.getIntLE(offset + 5); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int tintmapCount = VarInt.peek(buf, pos); + if (tintmapCount < 0) { + throw ProtocolException.negativeLength("Tintmap", tintmapCount); + } + + if (tintmapCount > 4096000) { + throw ProtocolException.arrayTooLong("Tintmap", tintmapCount, 4096000); + } + + int tintmapVarLen = VarInt.size(tintmapCount); + if (pos + tintmapVarLen + tintmapCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Tintmap", pos + tintmapVarLen + tintmapCount * 1, buf.readableBytes()); + } + + pos += tintmapVarLen; + obj.tintmap = new byte[tintmapCount]; + + for (int i = 0; i < tintmapCount; i++) { + obj.tintmap[i] = buf.getByte(pos + i * 1); + } + + pos += tintmapCount * 1; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 1; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.tintmap != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.x); + buf.writeIntLE(this.z); + if (this.tintmap != null) { + if (this.tintmap.length > 4096000) { + throw ProtocolException.arrayTooLong("Tintmap", this.tintmap.length, 4096000); + } + + VarInt.write(buf, this.tintmap.length); + + for (byte item : this.tintmap) { + buf.writeByte(item); + } + } + } + + @Override + public int computeSize() { + int size = 9; + if (this.tintmap != null) { + size += VarInt.size(this.tintmap.length) + this.tintmap.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int tintmapCount = VarInt.peek(buffer, pos); + if (tintmapCount < 0) { + return ValidationResult.error("Invalid array count for Tintmap"); + } + + if (tintmapCount > 4096000) { + return ValidationResult.error("Tintmap exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += tintmapCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Tintmap"); + } + } + + return ValidationResult.OK; + } + } + + public SetChunkTintmap clone() { + SetChunkTintmap copy = new SetChunkTintmap(); + copy.x = this.x; + copy.z = this.z; + copy.tintmap = this.tintmap != null ? Arrays.copyOf(this.tintmap, this.tintmap.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SetChunkTintmap other) ? false : this.x == other.x && this.z == other.z && Arrays.equals(this.tintmap, other.tintmap); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.x); + result = 31 * result + Integer.hashCode(this.z); + return 31 * result + Arrays.hashCode(this.tintmap); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/SetFluidCmd.java b/src/com/hypixel/hytale/protocol/packets/world/SetFluidCmd.java new file mode 100644 index 0000000..252cd78 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/SetFluidCmd.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SetFluidCmd { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 7; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 7; + public static final int MAX_SIZE = 7; + public short index; + public int fluidId; + public byte fluidLevel; + + public SetFluidCmd() { + } + + public SetFluidCmd(short index, int fluidId, byte fluidLevel) { + this.index = index; + this.fluidId = fluidId; + this.fluidLevel = fluidLevel; + } + + public SetFluidCmd(@Nonnull SetFluidCmd other) { + this.index = other.index; + this.fluidId = other.fluidId; + this.fluidLevel = other.fluidLevel; + } + + @Nonnull + public static SetFluidCmd deserialize(@Nonnull ByteBuf buf, int offset) { + SetFluidCmd obj = new SetFluidCmd(); + obj.index = buf.getShortLE(offset + 0); + obj.fluidId = buf.getIntLE(offset + 2); + obj.fluidLevel = buf.getByte(offset + 6); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 7; + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeShortLE(this.index); + buf.writeIntLE(this.fluidId); + buf.writeByte(this.fluidLevel); + } + + public int computeSize() { + return 7; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 7 ? ValidationResult.error("Buffer too small: expected at least 7 bytes") : ValidationResult.OK; + } + + public SetFluidCmd clone() { + SetFluidCmd copy = new SetFluidCmd(); + copy.index = this.index; + copy.fluidId = this.fluidId; + copy.fluidLevel = this.fluidLevel; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SetFluidCmd other) ? false : this.index == other.index && this.fluidId == other.fluidId && this.fluidLevel == other.fluidLevel; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.index, this.fluidId, this.fluidLevel); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/SetFluids.java b/src/com/hypixel/hytale/protocol/packets/world/SetFluids.java new file mode 100644 index 0000000..4d7b347 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/SetFluids.java @@ -0,0 +1,182 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SetFluids implements Packet { + public static final int PACKET_ID = 136; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 13; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 4096018; + public int x; + public int y; + public int z; + @Nullable + public byte[] data; + + @Override + public int getId() { + return 136; + } + + public SetFluids() { + } + + public SetFluids(int x, int y, int z, @Nullable byte[] data) { + this.x = x; + this.y = y; + this.z = z; + this.data = data; + } + + public SetFluids(@Nonnull SetFluids other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.data = other.data; + } + + @Nonnull + public static SetFluids deserialize(@Nonnull ByteBuf buf, int offset) { + SetFluids obj = new SetFluids(); + byte nullBits = buf.getByte(offset); + obj.x = buf.getIntLE(offset + 1); + obj.y = buf.getIntLE(offset + 5); + obj.z = buf.getIntLE(offset + 9); + int pos = offset + 13; + if ((nullBits & 1) != 0) { + int dataCount = VarInt.peek(buf, pos); + if (dataCount < 0) { + throw ProtocolException.negativeLength("Data", dataCount); + } + + if (dataCount > 4096000) { + throw ProtocolException.arrayTooLong("Data", dataCount, 4096000); + } + + int dataVarLen = VarInt.size(dataCount); + if (pos + dataVarLen + dataCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Data", pos + dataVarLen + dataCount * 1, buf.readableBytes()); + } + + pos += dataVarLen; + obj.data = new byte[dataCount]; + + for (int i = 0; i < dataCount; i++) { + obj.data[i] = buf.getByte(pos + i * 1); + } + + pos += dataCount * 1; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 13; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 1; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.data != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.x); + buf.writeIntLE(this.y); + buf.writeIntLE(this.z); + if (this.data != null) { + if (this.data.length > 4096000) { + throw ProtocolException.arrayTooLong("Data", this.data.length, 4096000); + } + + VarInt.write(buf, this.data.length); + + for (byte item : this.data) { + buf.writeByte(item); + } + } + } + + @Override + public int computeSize() { + int size = 13; + if (this.data != null) { + size += VarInt.size(this.data.length) + this.data.length * 1; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 13; + if ((nullBits & 1) != 0) { + int dataCount = VarInt.peek(buffer, pos); + if (dataCount < 0) { + return ValidationResult.error("Invalid array count for Data"); + } + + if (dataCount > 4096000) { + return ValidationResult.error("Data exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += dataCount * 1; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Data"); + } + } + + return ValidationResult.OK; + } + } + + public SetFluids clone() { + SetFluids copy = new SetFluids(); + copy.x = this.x; + copy.y = this.y; + copy.z = this.z; + copy.data = this.data != null ? Arrays.copyOf(this.data, this.data.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SetFluids other) ? false : this.x == other.x && this.y == other.y && this.z == other.z && Arrays.equals(this.data, other.data); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.x); + result = 31 * result + Integer.hashCode(this.y); + result = 31 * result + Integer.hashCode(this.z); + return 31 * result + Arrays.hashCode(this.data); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/SetPaused.java b/src/com/hypixel/hytale/protocol/packets/world/SetPaused.java new file mode 100644 index 0000000..5eeef8c --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/SetPaused.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SetPaused implements Packet { + public static final int PACKET_ID = 158; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean paused; + + @Override + public int getId() { + return 158; + } + + public SetPaused() { + } + + public SetPaused(boolean paused) { + this.paused = paused; + } + + public SetPaused(@Nonnull SetPaused other) { + this.paused = other.paused; + } + + @Nonnull + public static SetPaused deserialize(@Nonnull ByteBuf buf, int offset) { + SetPaused obj = new SetPaused(); + obj.paused = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.paused ? 1 : 0); + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public SetPaused clone() { + SetPaused copy = new SetPaused(); + copy.paused = this.paused; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof SetPaused other ? this.paused == other.paused : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.paused); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/SleepClock.java b/src/com/hypixel/hytale/protocol/packets/world/SleepClock.java new file mode 100644 index 0000000..a219a68 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/SleepClock.java @@ -0,0 +1,123 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.InstantData; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SleepClock { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 33; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 33; + public static final int MAX_SIZE = 33; + @Nullable + public InstantData startGametime; + @Nullable + public InstantData targetGametime; + public float progress; + public float durationSeconds; + + public SleepClock() { + } + + public SleepClock(@Nullable InstantData startGametime, @Nullable InstantData targetGametime, float progress, float durationSeconds) { + this.startGametime = startGametime; + this.targetGametime = targetGametime; + this.progress = progress; + this.durationSeconds = durationSeconds; + } + + public SleepClock(@Nonnull SleepClock other) { + this.startGametime = other.startGametime; + this.targetGametime = other.targetGametime; + this.progress = other.progress; + this.durationSeconds = other.durationSeconds; + } + + @Nonnull + public static SleepClock deserialize(@Nonnull ByteBuf buf, int offset) { + SleepClock obj = new SleepClock(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.startGametime = InstantData.deserialize(buf, offset + 1); + } + + if ((nullBits & 2) != 0) { + obj.targetGametime = InstantData.deserialize(buf, offset + 13); + } + + obj.progress = buf.getFloatLE(offset + 25); + obj.durationSeconds = buf.getFloatLE(offset + 29); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 33; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.startGametime != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.targetGametime != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + if (this.startGametime != null) { + this.startGametime.serialize(buf); + } else { + buf.writeZero(12); + } + + if (this.targetGametime != null) { + this.targetGametime.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeFloatLE(this.progress); + buf.writeFloatLE(this.durationSeconds); + } + + public int computeSize() { + return 33; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 33 ? ValidationResult.error("Buffer too small: expected at least 33 bytes") : ValidationResult.OK; + } + + public SleepClock clone() { + SleepClock copy = new SleepClock(); + copy.startGametime = this.startGametime != null ? this.startGametime.clone() : null; + copy.targetGametime = this.targetGametime != null ? this.targetGametime.clone() : null; + copy.progress = this.progress; + copy.durationSeconds = this.durationSeconds; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SleepClock other) + ? false + : Objects.equals(this.startGametime, other.startGametime) + && Objects.equals(this.targetGametime, other.targetGametime) + && this.progress == other.progress + && this.durationSeconds == other.durationSeconds; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.startGametime, this.targetGametime, this.progress, this.durationSeconds); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/SleepMultiplayer.java b/src/com/hypixel/hytale/protocol/packets/world/SleepMultiplayer.java new file mode 100644 index 0000000..1fd9a53 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/SleepMultiplayer.java @@ -0,0 +1,171 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SleepMultiplayer { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 65536014; + public int sleepersCount; + public int awakeCount; + @Nullable + public UUID[] awakeSample; + + public SleepMultiplayer() { + } + + public SleepMultiplayer(int sleepersCount, int awakeCount, @Nullable UUID[] awakeSample) { + this.sleepersCount = sleepersCount; + this.awakeCount = awakeCount; + this.awakeSample = awakeSample; + } + + public SleepMultiplayer(@Nonnull SleepMultiplayer other) { + this.sleepersCount = other.sleepersCount; + this.awakeCount = other.awakeCount; + this.awakeSample = other.awakeSample; + } + + @Nonnull + public static SleepMultiplayer deserialize(@Nonnull ByteBuf buf, int offset) { + SleepMultiplayer obj = new SleepMultiplayer(); + byte nullBits = buf.getByte(offset); + obj.sleepersCount = buf.getIntLE(offset + 1); + obj.awakeCount = buf.getIntLE(offset + 5); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int awakeSampleCount = VarInt.peek(buf, pos); + if (awakeSampleCount < 0) { + throw ProtocolException.negativeLength("AwakeSample", awakeSampleCount); + } + + if (awakeSampleCount > 4096000) { + throw ProtocolException.arrayTooLong("AwakeSample", awakeSampleCount, 4096000); + } + + int awakeSampleVarLen = VarInt.size(awakeSampleCount); + if (pos + awakeSampleVarLen + awakeSampleCount * 16L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("AwakeSample", pos + awakeSampleVarLen + awakeSampleCount * 16, buf.readableBytes()); + } + + pos += awakeSampleVarLen; + obj.awakeSample = new UUID[awakeSampleCount]; + + for (int i = 0; i < awakeSampleCount; i++) { + obj.awakeSample[i] = PacketIO.readUUID(buf, pos + i * 16); + } + + pos += awakeSampleCount * 16; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 16; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.awakeSample != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.sleepersCount); + buf.writeIntLE(this.awakeCount); + if (this.awakeSample != null) { + if (this.awakeSample.length > 4096000) { + throw ProtocolException.arrayTooLong("AwakeSample", this.awakeSample.length, 4096000); + } + + VarInt.write(buf, this.awakeSample.length); + + for (UUID item : this.awakeSample) { + PacketIO.writeUUID(buf, item); + } + } + } + + public int computeSize() { + int size = 9; + if (this.awakeSample != null) { + size += VarInt.size(this.awakeSample.length) + this.awakeSample.length * 16; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int awakeSampleCount = VarInt.peek(buffer, pos); + if (awakeSampleCount < 0) { + return ValidationResult.error("Invalid array count for AwakeSample"); + } + + if (awakeSampleCount > 4096000) { + return ValidationResult.error("AwakeSample exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += awakeSampleCount * 16; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading AwakeSample"); + } + } + + return ValidationResult.OK; + } + } + + public SleepMultiplayer clone() { + SleepMultiplayer copy = new SleepMultiplayer(); + copy.sleepersCount = this.sleepersCount; + copy.awakeCount = this.awakeCount; + copy.awakeSample = this.awakeSample != null ? Arrays.copyOf(this.awakeSample, this.awakeSample.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SleepMultiplayer other) + ? false + : this.sleepersCount == other.sleepersCount + && this.awakeCount == other.awakeCount + && Arrays.equals((Object[])this.awakeSample, (Object[])other.awakeSample); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.sleepersCount); + result = 31 * result + Integer.hashCode(this.awakeCount); + return 31 * result + Arrays.hashCode((Object[])this.awakeSample); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/SpawnBlockParticleSystem.java b/src/com/hypixel/hytale/protocol/packets/world/SpawnBlockParticleSystem.java new file mode 100644 index 0000000..622393f --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/SpawnBlockParticleSystem.java @@ -0,0 +1,112 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.BlockParticleEvent; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.Position; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SpawnBlockParticleSystem implements Packet { + public static final int PACKET_ID = 153; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 30; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 30; + public static final int MAX_SIZE = 30; + public int blockId; + @Nonnull + public BlockParticleEvent particleType = BlockParticleEvent.Walk; + @Nullable + public Position position; + + @Override + public int getId() { + return 153; + } + + public SpawnBlockParticleSystem() { + } + + public SpawnBlockParticleSystem(int blockId, @Nonnull BlockParticleEvent particleType, @Nullable Position position) { + this.blockId = blockId; + this.particleType = particleType; + this.position = position; + } + + public SpawnBlockParticleSystem(@Nonnull SpawnBlockParticleSystem other) { + this.blockId = other.blockId; + this.particleType = other.particleType; + this.position = other.position; + } + + @Nonnull + public static SpawnBlockParticleSystem deserialize(@Nonnull ByteBuf buf, int offset) { + SpawnBlockParticleSystem obj = new SpawnBlockParticleSystem(); + byte nullBits = buf.getByte(offset); + obj.blockId = buf.getIntLE(offset + 1); + obj.particleType = BlockParticleEvent.fromValue(buf.getByte(offset + 5)); + if ((nullBits & 1) != 0) { + obj.position = Position.deserialize(buf, offset + 6); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 30; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.position != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.blockId); + buf.writeByte(this.particleType.getValue()); + if (this.position != null) { + this.position.serialize(buf); + } else { + buf.writeZero(24); + } + } + + @Override + public int computeSize() { + return 30; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 30 ? ValidationResult.error("Buffer too small: expected at least 30 bytes") : ValidationResult.OK; + } + + public SpawnBlockParticleSystem clone() { + SpawnBlockParticleSystem copy = new SpawnBlockParticleSystem(); + copy.blockId = this.blockId; + copy.particleType = this.particleType; + copy.position = this.position != null ? this.position.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SpawnBlockParticleSystem other) + ? false + : this.blockId == other.blockId && Objects.equals(this.particleType, other.particleType) && Objects.equals(this.position, other.position); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.blockId, this.particleType, this.position); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/SpawnParticleSystem.java b/src/com/hypixel/hytale/protocol/packets/world/SpawnParticleSystem.java new file mode 100644 index 0000000..7f2430b --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/SpawnParticleSystem.java @@ -0,0 +1,215 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.Position; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SpawnParticleSystem implements Packet { + public static final int PACKET_ID = 152; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 44; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 44; + public static final int MAX_SIZE = 16384049; + @Nullable + public String particleSystemId; + @Nullable + public Position position; + @Nullable + public Direction rotation; + public float scale; + @Nullable + public Color color; + + @Override + public int getId() { + return 152; + } + + public SpawnParticleSystem() { + } + + public SpawnParticleSystem(@Nullable String particleSystemId, @Nullable Position position, @Nullable Direction rotation, float scale, @Nullable Color color) { + this.particleSystemId = particleSystemId; + this.position = position; + this.rotation = rotation; + this.scale = scale; + this.color = color; + } + + public SpawnParticleSystem(@Nonnull SpawnParticleSystem other) { + this.particleSystemId = other.particleSystemId; + this.position = other.position; + this.rotation = other.rotation; + this.scale = other.scale; + this.color = other.color; + } + + @Nonnull + public static SpawnParticleSystem deserialize(@Nonnull ByteBuf buf, int offset) { + SpawnParticleSystem obj = new SpawnParticleSystem(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 2) != 0) { + obj.position = Position.deserialize(buf, offset + 1); + } + + if ((nullBits & 4) != 0) { + obj.rotation = Direction.deserialize(buf, offset + 25); + } + + obj.scale = buf.getFloatLE(offset + 37); + if ((nullBits & 8) != 0) { + obj.color = Color.deserialize(buf, offset + 41); + } + + int pos = offset + 44; + if ((nullBits & 1) != 0) { + int particleSystemIdLen = VarInt.peek(buf, pos); + if (particleSystemIdLen < 0) { + throw ProtocolException.negativeLength("ParticleSystemId", particleSystemIdLen); + } + + if (particleSystemIdLen > 4096000) { + throw ProtocolException.stringTooLong("ParticleSystemId", particleSystemIdLen, 4096000); + } + + int particleSystemIdVarLen = VarInt.length(buf, pos); + obj.particleSystemId = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += particleSystemIdVarLen + particleSystemIdLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 44; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.particleSystemId != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.position != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.rotation != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.color != null) { + nullBits = (byte)(nullBits | 8); + } + + buf.writeByte(nullBits); + if (this.position != null) { + this.position.serialize(buf); + } else { + buf.writeZero(24); + } + + if (this.rotation != null) { + this.rotation.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeFloatLE(this.scale); + if (this.color != null) { + this.color.serialize(buf); + } else { + buf.writeZero(3); + } + + if (this.particleSystemId != null) { + PacketIO.writeVarString(buf, this.particleSystemId, 4096000); + } + } + + @Override + public int computeSize() { + int size = 44; + if (this.particleSystemId != null) { + size += PacketIO.stringSize(this.particleSystemId); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 44) { + return ValidationResult.error("Buffer too small: expected at least 44 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 44; + if ((nullBits & 1) != 0) { + int particleSystemIdLen = VarInt.peek(buffer, pos); + if (particleSystemIdLen < 0) { + return ValidationResult.error("Invalid string length for ParticleSystemId"); + } + + if (particleSystemIdLen > 4096000) { + return ValidationResult.error("ParticleSystemId exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += particleSystemIdLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ParticleSystemId"); + } + } + + return ValidationResult.OK; + } + } + + public SpawnParticleSystem clone() { + SpawnParticleSystem copy = new SpawnParticleSystem(); + copy.particleSystemId = this.particleSystemId; + copy.position = this.position != null ? this.position.clone() : null; + copy.rotation = this.rotation != null ? this.rotation.clone() : null; + copy.scale = this.scale; + copy.color = this.color != null ? this.color.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof SpawnParticleSystem other) + ? false + : Objects.equals(this.particleSystemId, other.particleSystemId) + && Objects.equals(this.position, other.position) + && Objects.equals(this.rotation, other.rotation) + && this.scale == other.scale + && Objects.equals(this.color, other.color); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.particleSystemId, this.position, this.rotation, this.scale, this.color); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/UnloadChunk.java b/src/com/hypixel/hytale/protocol/packets/world/UnloadChunk.java new file mode 100644 index 0000000..aefff6d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/UnloadChunk.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class UnloadChunk implements Packet { + public static final int PACKET_ID = 135; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public int chunkX; + public int chunkZ; + + @Override + public int getId() { + return 135; + } + + public UnloadChunk() { + } + + public UnloadChunk(int chunkX, int chunkZ) { + this.chunkX = chunkX; + this.chunkZ = chunkZ; + } + + public UnloadChunk(@Nonnull UnloadChunk other) { + this.chunkX = other.chunkX; + this.chunkZ = other.chunkZ; + } + + @Nonnull + public static UnloadChunk deserialize(@Nonnull ByteBuf buf, int offset) { + UnloadChunk obj = new UnloadChunk(); + obj.chunkX = buf.getIntLE(offset + 0); + obj.chunkZ = buf.getIntLE(offset + 4); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.chunkX); + buf.writeIntLE(this.chunkZ); + } + + @Override + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public UnloadChunk clone() { + UnloadChunk copy = new UnloadChunk(); + copy.chunkX = this.chunkX; + copy.chunkZ = this.chunkZ; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UnloadChunk other) ? false : this.chunkX == other.chunkX && this.chunkZ == other.chunkZ; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.chunkX, this.chunkZ); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/UpdateBlockDamage.java b/src/com/hypixel/hytale/protocol/packets/world/UpdateBlockDamage.java new file mode 100644 index 0000000..a58cefe --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/UpdateBlockDamage.java @@ -0,0 +1,111 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateBlockDamage implements Packet { + public static final int PACKET_ID = 144; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 21; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 21; + public static final int MAX_SIZE = 21; + @Nullable + public BlockPosition blockPosition; + public float damage; + public float delta; + + @Override + public int getId() { + return 144; + } + + public UpdateBlockDamage() { + } + + public UpdateBlockDamage(@Nullable BlockPosition blockPosition, float damage, float delta) { + this.blockPosition = blockPosition; + this.damage = damage; + this.delta = delta; + } + + public UpdateBlockDamage(@Nonnull UpdateBlockDamage other) { + this.blockPosition = other.blockPosition; + this.damage = other.damage; + this.delta = other.delta; + } + + @Nonnull + public static UpdateBlockDamage deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateBlockDamage obj = new UpdateBlockDamage(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.blockPosition = BlockPosition.deserialize(buf, offset + 1); + } + + obj.damage = buf.getFloatLE(offset + 13); + obj.delta = buf.getFloatLE(offset + 17); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 21; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.blockPosition != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.blockPosition != null) { + this.blockPosition.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeFloatLE(this.damage); + buf.writeFloatLE(this.delta); + } + + @Override + public int computeSize() { + return 21; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 21 ? ValidationResult.error("Buffer too small: expected at least 21 bytes") : ValidationResult.OK; + } + + public UpdateBlockDamage clone() { + UpdateBlockDamage copy = new UpdateBlockDamage(); + copy.blockPosition = this.blockPosition != null ? this.blockPosition.clone() : null; + copy.damage = this.damage; + copy.delta = this.delta; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateBlockDamage other) + ? false + : Objects.equals(this.blockPosition, other.blockPosition) && this.damage == other.damage && this.delta == other.delta; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.blockPosition, this.damage, this.delta); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/UpdateEditorTimeOverride.java b/src/com/hypixel/hytale/protocol/packets/world/UpdateEditorTimeOverride.java new file mode 100644 index 0000000..36cd54e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/UpdateEditorTimeOverride.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.InstantData; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateEditorTimeOverride implements Packet { + public static final int PACKET_ID = 147; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 14; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 14; + public static final int MAX_SIZE = 14; + @Nullable + public InstantData gameTime; + public boolean paused; + + @Override + public int getId() { + return 147; + } + + public UpdateEditorTimeOverride() { + } + + public UpdateEditorTimeOverride(@Nullable InstantData gameTime, boolean paused) { + this.gameTime = gameTime; + this.paused = paused; + } + + public UpdateEditorTimeOverride(@Nonnull UpdateEditorTimeOverride other) { + this.gameTime = other.gameTime; + this.paused = other.paused; + } + + @Nonnull + public static UpdateEditorTimeOverride deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateEditorTimeOverride obj = new UpdateEditorTimeOverride(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.gameTime = InstantData.deserialize(buf, offset + 1); + } + + obj.paused = buf.getByte(offset + 13) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 14; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.gameTime != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.gameTime != null) { + this.gameTime.serialize(buf); + } else { + buf.writeZero(12); + } + + buf.writeByte(this.paused ? 1 : 0); + } + + @Override + public int computeSize() { + return 14; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 14 ? ValidationResult.error("Buffer too small: expected at least 14 bytes") : ValidationResult.OK; + } + + public UpdateEditorTimeOverride clone() { + UpdateEditorTimeOverride copy = new UpdateEditorTimeOverride(); + copy.gameTime = this.gameTime != null ? this.gameTime.clone() : null; + copy.paused = this.paused; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateEditorTimeOverride other) ? false : Objects.equals(this.gameTime, other.gameTime) && this.paused == other.paused; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.gameTime, this.paused); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/UpdateEditorWeatherOverride.java b/src/com/hypixel/hytale/protocol/packets/world/UpdateEditorWeatherOverride.java new file mode 100644 index 0000000..407dcfd --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/UpdateEditorWeatherOverride.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class UpdateEditorWeatherOverride implements Packet { + public static final int PACKET_ID = 150; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 4; + public int weatherIndex; + + @Override + public int getId() { + return 150; + } + + public UpdateEditorWeatherOverride() { + } + + public UpdateEditorWeatherOverride(int weatherIndex) { + this.weatherIndex = weatherIndex; + } + + public UpdateEditorWeatherOverride(@Nonnull UpdateEditorWeatherOverride other) { + this.weatherIndex = other.weatherIndex; + } + + @Nonnull + public static UpdateEditorWeatherOverride deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateEditorWeatherOverride obj = new UpdateEditorWeatherOverride(); + obj.weatherIndex = buf.getIntLE(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 4; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.weatherIndex); + } + + @Override + public int computeSize() { + return 4; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 4 ? ValidationResult.error("Buffer too small: expected at least 4 bytes") : ValidationResult.OK; + } + + public UpdateEditorWeatherOverride clone() { + UpdateEditorWeatherOverride copy = new UpdateEditorWeatherOverride(); + copy.weatherIndex = this.weatherIndex; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof UpdateEditorWeatherOverride other ? this.weatherIndex == other.weatherIndex : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.weatherIndex); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/UpdateEnvironmentMusic.java b/src/com/hypixel/hytale/protocol/packets/world/UpdateEnvironmentMusic.java new file mode 100644 index 0000000..67fc618 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/UpdateEnvironmentMusic.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class UpdateEnvironmentMusic implements Packet { + public static final int PACKET_ID = 151; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 4; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 4; + public static final int MAX_SIZE = 4; + public int environmentIndex; + + @Override + public int getId() { + return 151; + } + + public UpdateEnvironmentMusic() { + } + + public UpdateEnvironmentMusic(int environmentIndex) { + this.environmentIndex = environmentIndex; + } + + public UpdateEnvironmentMusic(@Nonnull UpdateEnvironmentMusic other) { + this.environmentIndex = other.environmentIndex; + } + + @Nonnull + public static UpdateEnvironmentMusic deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateEnvironmentMusic obj = new UpdateEnvironmentMusic(); + obj.environmentIndex = buf.getIntLE(offset + 0); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 4; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.environmentIndex); + } + + @Override + public int computeSize() { + return 4; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 4 ? ValidationResult.error("Buffer too small: expected at least 4 bytes") : ValidationResult.OK; + } + + public UpdateEnvironmentMusic clone() { + UpdateEnvironmentMusic copy = new UpdateEnvironmentMusic(); + copy.environmentIndex = this.environmentIndex; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof UpdateEnvironmentMusic other ? this.environmentIndex == other.environmentIndex : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.environmentIndex); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/UpdatePostFxSettings.java b/src/com/hypixel/hytale/protocol/packets/world/UpdatePostFxSettings.java new file mode 100644 index 0000000..4259333 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/UpdatePostFxSettings.java @@ -0,0 +1,109 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class UpdatePostFxSettings implements Packet { + public static final int PACKET_ID = 361; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 20; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 20; + public static final int MAX_SIZE = 20; + public float globalIntensity; + public float power; + public float sunshaftScale; + public float sunIntensity; + public float sunshaftIntensity; + + @Override + public int getId() { + return 361; + } + + public UpdatePostFxSettings() { + } + + public UpdatePostFxSettings(float globalIntensity, float power, float sunshaftScale, float sunIntensity, float sunshaftIntensity) { + this.globalIntensity = globalIntensity; + this.power = power; + this.sunshaftScale = sunshaftScale; + this.sunIntensity = sunIntensity; + this.sunshaftIntensity = sunshaftIntensity; + } + + public UpdatePostFxSettings(@Nonnull UpdatePostFxSettings other) { + this.globalIntensity = other.globalIntensity; + this.power = other.power; + this.sunshaftScale = other.sunshaftScale; + this.sunIntensity = other.sunIntensity; + this.sunshaftIntensity = other.sunshaftIntensity; + } + + @Nonnull + public static UpdatePostFxSettings deserialize(@Nonnull ByteBuf buf, int offset) { + UpdatePostFxSettings obj = new UpdatePostFxSettings(); + obj.globalIntensity = buf.getFloatLE(offset + 0); + obj.power = buf.getFloatLE(offset + 4); + obj.sunshaftScale = buf.getFloatLE(offset + 8); + obj.sunIntensity = buf.getFloatLE(offset + 12); + obj.sunshaftIntensity = buf.getFloatLE(offset + 16); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 20; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.globalIntensity); + buf.writeFloatLE(this.power); + buf.writeFloatLE(this.sunshaftScale); + buf.writeFloatLE(this.sunIntensity); + buf.writeFloatLE(this.sunshaftIntensity); + } + + @Override + public int computeSize() { + return 20; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 20 ? ValidationResult.error("Buffer too small: expected at least 20 bytes") : ValidationResult.OK; + } + + public UpdatePostFxSettings clone() { + UpdatePostFxSettings copy = new UpdatePostFxSettings(); + copy.globalIntensity = this.globalIntensity; + copy.power = this.power; + copy.sunshaftScale = this.sunshaftScale; + copy.sunIntensity = this.sunIntensity; + copy.sunshaftIntensity = this.sunshaftIntensity; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdatePostFxSettings other) + ? false + : this.globalIntensity == other.globalIntensity + && this.power == other.power + && this.sunshaftScale == other.sunshaftScale + && this.sunIntensity == other.sunIntensity + && this.sunshaftIntensity == other.sunshaftIntensity; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.globalIntensity, this.power, this.sunshaftScale, this.sunIntensity, this.sunshaftIntensity); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/UpdateSleepState.java b/src/com/hypixel/hytale/protocol/packets/world/UpdateSleepState.java new file mode 100644 index 0000000..40a554e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/UpdateSleepState.java @@ -0,0 +1,157 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateSleepState implements Packet { + public static final int PACKET_ID = 157; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 36; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 36; + public static final int MAX_SIZE = 65536050; + public boolean grayFade; + public boolean sleepUi; + @Nullable + public SleepClock clock; + @Nullable + public SleepMultiplayer multiplayer; + + @Override + public int getId() { + return 157; + } + + public UpdateSleepState() { + } + + public UpdateSleepState(boolean grayFade, boolean sleepUi, @Nullable SleepClock clock, @Nullable SleepMultiplayer multiplayer) { + this.grayFade = grayFade; + this.sleepUi = sleepUi; + this.clock = clock; + this.multiplayer = multiplayer; + } + + public UpdateSleepState(@Nonnull UpdateSleepState other) { + this.grayFade = other.grayFade; + this.sleepUi = other.sleepUi; + this.clock = other.clock; + this.multiplayer = other.multiplayer; + } + + @Nonnull + public static UpdateSleepState deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateSleepState obj = new UpdateSleepState(); + byte nullBits = buf.getByte(offset); + obj.grayFade = buf.getByte(offset + 1) != 0; + obj.sleepUi = buf.getByte(offset + 2) != 0; + if ((nullBits & 1) != 0) { + obj.clock = SleepClock.deserialize(buf, offset + 3); + } + + int pos = offset + 36; + if ((nullBits & 2) != 0) { + obj.multiplayer = SleepMultiplayer.deserialize(buf, pos); + pos += SleepMultiplayer.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 36; + if ((nullBits & 2) != 0) { + pos += SleepMultiplayer.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.clock != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.multiplayer != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeByte(this.grayFade ? 1 : 0); + buf.writeByte(this.sleepUi ? 1 : 0); + if (this.clock != null) { + this.clock.serialize(buf); + } else { + buf.writeZero(33); + } + + if (this.multiplayer != null) { + this.multiplayer.serialize(buf); + } + } + + @Override + public int computeSize() { + int size = 36; + if (this.multiplayer != null) { + size += this.multiplayer.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 36) { + return ValidationResult.error("Buffer too small: expected at least 36 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 36; + if ((nullBits & 2) != 0) { + ValidationResult multiplayerResult = SleepMultiplayer.validateStructure(buffer, pos); + if (!multiplayerResult.isValid()) { + return ValidationResult.error("Invalid Multiplayer: " + multiplayerResult.error()); + } + + pos += SleepMultiplayer.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public UpdateSleepState clone() { + UpdateSleepState copy = new UpdateSleepState(); + copy.grayFade = this.grayFade; + copy.sleepUi = this.sleepUi; + copy.clock = this.clock != null ? this.clock.clone() : null; + copy.multiplayer = this.multiplayer != null ? this.multiplayer.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateSleepState other) + ? false + : this.grayFade == other.grayFade + && this.sleepUi == other.sleepUi + && Objects.equals(this.clock, other.clock) + && Objects.equals(this.multiplayer, other.multiplayer); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.grayFade, this.sleepUi, this.clock, this.multiplayer); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/UpdateSunSettings.java b/src/com/hypixel/hytale/protocol/packets/world/UpdateSunSettings.java new file mode 100644 index 0000000..4589224 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/UpdateSunSettings.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class UpdateSunSettings implements Packet { + public static final int PACKET_ID = 360; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public float heightPercentage; + public float angleRadians; + + @Override + public int getId() { + return 360; + } + + public UpdateSunSettings() { + } + + public UpdateSunSettings(float heightPercentage, float angleRadians) { + this.heightPercentage = heightPercentage; + this.angleRadians = angleRadians; + } + + public UpdateSunSettings(@Nonnull UpdateSunSettings other) { + this.heightPercentage = other.heightPercentage; + this.angleRadians = other.angleRadians; + } + + @Nonnull + public static UpdateSunSettings deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateSunSettings obj = new UpdateSunSettings(); + obj.heightPercentage = buf.getFloatLE(offset + 0); + obj.angleRadians = buf.getFloatLE(offset + 4); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloatLE(this.heightPercentage); + buf.writeFloatLE(this.angleRadians); + } + + @Override + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public UpdateSunSettings clone() { + UpdateSunSettings copy = new UpdateSunSettings(); + copy.heightPercentage = this.heightPercentage; + copy.angleRadians = this.angleRadians; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateSunSettings other) ? false : this.heightPercentage == other.heightPercentage && this.angleRadians == other.angleRadians; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.heightPercentage, this.angleRadians); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/UpdateTime.java b/src/com/hypixel/hytale/protocol/packets/world/UpdateTime.java new file mode 100644 index 0000000..e3311fe --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/UpdateTime.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.InstantData; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateTime implements Packet { + public static final int PACKET_ID = 146; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 13; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 13; + @Nullable + public InstantData gameTime; + + @Override + public int getId() { + return 146; + } + + public UpdateTime() { + } + + public UpdateTime(@Nullable InstantData gameTime) { + this.gameTime = gameTime; + } + + public UpdateTime(@Nonnull UpdateTime other) { + this.gameTime = other.gameTime; + } + + @Nonnull + public static UpdateTime deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateTime obj = new UpdateTime(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + obj.gameTime = InstantData.deserialize(buf, offset + 1); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 13; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.gameTime != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.gameTime != null) { + this.gameTime.serialize(buf); + } else { + buf.writeZero(12); + } + } + + @Override + public int computeSize() { + return 13; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 13 ? ValidationResult.error("Buffer too small: expected at least 13 bytes") : ValidationResult.OK; + } + + public UpdateTime clone() { + UpdateTime copy = new UpdateTime(); + copy.gameTime = this.gameTime != null ? this.gameTime.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof UpdateTime other ? Objects.equals(this.gameTime, other.gameTime) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.gameTime); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/UpdateTimeSettings.java b/src/com/hypixel/hytale/protocol/packets/world/UpdateTimeSettings.java new file mode 100644 index 0000000..3dc6b57 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/UpdateTimeSettings.java @@ -0,0 +1,102 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class UpdateTimeSettings implements Packet { + public static final int PACKET_ID = 145; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 10; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 10; + public static final int MAX_SIZE = 10; + public int daytimeDurationSeconds; + public int nighttimeDurationSeconds; + public byte totalMoonPhases; + public boolean timePaused; + + @Override + public int getId() { + return 145; + } + + public UpdateTimeSettings() { + } + + public UpdateTimeSettings(int daytimeDurationSeconds, int nighttimeDurationSeconds, byte totalMoonPhases, boolean timePaused) { + this.daytimeDurationSeconds = daytimeDurationSeconds; + this.nighttimeDurationSeconds = nighttimeDurationSeconds; + this.totalMoonPhases = totalMoonPhases; + this.timePaused = timePaused; + } + + public UpdateTimeSettings(@Nonnull UpdateTimeSettings other) { + this.daytimeDurationSeconds = other.daytimeDurationSeconds; + this.nighttimeDurationSeconds = other.nighttimeDurationSeconds; + this.totalMoonPhases = other.totalMoonPhases; + this.timePaused = other.timePaused; + } + + @Nonnull + public static UpdateTimeSettings deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateTimeSettings obj = new UpdateTimeSettings(); + obj.daytimeDurationSeconds = buf.getIntLE(offset + 0); + obj.nighttimeDurationSeconds = buf.getIntLE(offset + 4); + obj.totalMoonPhases = buf.getByte(offset + 8); + obj.timePaused = buf.getByte(offset + 9) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 10; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.daytimeDurationSeconds); + buf.writeIntLE(this.nighttimeDurationSeconds); + buf.writeByte(this.totalMoonPhases); + buf.writeByte(this.timePaused ? 1 : 0); + } + + @Override + public int computeSize() { + return 10; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 10 ? ValidationResult.error("Buffer too small: expected at least 10 bytes") : ValidationResult.OK; + } + + public UpdateTimeSettings clone() { + UpdateTimeSettings copy = new UpdateTimeSettings(); + copy.daytimeDurationSeconds = this.daytimeDurationSeconds; + copy.nighttimeDurationSeconds = this.nighttimeDurationSeconds; + copy.totalMoonPhases = this.totalMoonPhases; + copy.timePaused = this.timePaused; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateTimeSettings other) + ? false + : this.daytimeDurationSeconds == other.daytimeDurationSeconds + && this.nighttimeDurationSeconds == other.nighttimeDurationSeconds + && this.totalMoonPhases == other.totalMoonPhases + && this.timePaused == other.timePaused; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.daytimeDurationSeconds, this.nighttimeDurationSeconds, this.totalMoonPhases, this.timePaused); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/world/UpdateWeather.java b/src/com/hypixel/hytale/protocol/packets/world/UpdateWeather.java new file mode 100644 index 0000000..5e83a19 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/world/UpdateWeather.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.protocol.packets.world; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class UpdateWeather implements Packet { + public static final int PACKET_ID = 149; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public int weatherIndex; + public float transitionSeconds; + + @Override + public int getId() { + return 149; + } + + public UpdateWeather() { + } + + public UpdateWeather(int weatherIndex, float transitionSeconds) { + this.weatherIndex = weatherIndex; + this.transitionSeconds = transitionSeconds; + } + + public UpdateWeather(@Nonnull UpdateWeather other) { + this.weatherIndex = other.weatherIndex; + this.transitionSeconds = other.transitionSeconds; + } + + @Nonnull + public static UpdateWeather deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateWeather obj = new UpdateWeather(); + obj.weatherIndex = buf.getIntLE(offset + 0); + obj.transitionSeconds = buf.getFloatLE(offset + 4); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.weatherIndex); + buf.writeFloatLE(this.transitionSeconds); + } + + @Override + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public UpdateWeather clone() { + UpdateWeather copy = new UpdateWeather(); + copy.weatherIndex = this.weatherIndex; + copy.transitionSeconds = this.transitionSeconds; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateWeather other) ? false : this.weatherIndex == other.weatherIndex && this.transitionSeconds == other.transitionSeconds; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.weatherIndex, this.transitionSeconds); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/worldmap/BiomeData.java b/src/com/hypixel/hytale/protocol/packets/worldmap/BiomeData.java new file mode 100644 index 0000000..14cbf58 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/worldmap/BiomeData.java @@ -0,0 +1,242 @@ +package com.hypixel.hytale.protocol.packets.worldmap; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BiomeData { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 17; + public static final int MAX_SIZE = 32768027; + public int zoneId; + @Nullable + public String zoneName; + @Nullable + public String biomeName; + public int biomeColor; + + public BiomeData() { + } + + public BiomeData(int zoneId, @Nullable String zoneName, @Nullable String biomeName, int biomeColor) { + this.zoneId = zoneId; + this.zoneName = zoneName; + this.biomeName = biomeName; + this.biomeColor = biomeColor; + } + + public BiomeData(@Nonnull BiomeData other) { + this.zoneId = other.zoneId; + this.zoneName = other.zoneName; + this.biomeName = other.biomeName; + this.biomeColor = other.biomeColor; + } + + @Nonnull + public static BiomeData deserialize(@Nonnull ByteBuf buf, int offset) { + BiomeData obj = new BiomeData(); + byte nullBits = buf.getByte(offset); + obj.zoneId = buf.getIntLE(offset + 1); + obj.biomeColor = buf.getIntLE(offset + 5); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 17 + buf.getIntLE(offset + 9); + int zoneNameLen = VarInt.peek(buf, varPos0); + if (zoneNameLen < 0) { + throw ProtocolException.negativeLength("ZoneName", zoneNameLen); + } + + if (zoneNameLen > 4096000) { + throw ProtocolException.stringTooLong("ZoneName", zoneNameLen, 4096000); + } + + obj.zoneName = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 17 + buf.getIntLE(offset + 13); + int biomeNameLen = VarInt.peek(buf, varPos1); + if (biomeNameLen < 0) { + throw ProtocolException.negativeLength("BiomeName", biomeNameLen); + } + + if (biomeNameLen > 4096000) { + throw ProtocolException.stringTooLong("BiomeName", biomeNameLen, 4096000); + } + + obj.biomeName = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 17; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 9); + int pos0 = offset + 17 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 13); + int pos1 = offset + 17 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.zoneName != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.biomeName != null) { + nullBits = (byte)(nullBits | 2); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.zoneId); + buf.writeIntLE(this.biomeColor); + int zoneNameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int biomeNameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.zoneName != null) { + buf.setIntLE(zoneNameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.zoneName, 4096000); + } else { + buf.setIntLE(zoneNameOffsetSlot, -1); + } + + if (this.biomeName != null) { + buf.setIntLE(biomeNameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.biomeName, 4096000); + } else { + buf.setIntLE(biomeNameOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 17; + if (this.zoneName != null) { + size += PacketIO.stringSize(this.zoneName); + } + + if (this.biomeName != null) { + size += PacketIO.stringSize(this.biomeName); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 17) { + return ValidationResult.error("Buffer too small: expected at least 17 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int zoneNameOffset = buffer.getIntLE(offset + 9); + if (zoneNameOffset < 0) { + return ValidationResult.error("Invalid offset for ZoneName"); + } + + int pos = offset + 17 + zoneNameOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ZoneName"); + } + + int zoneNameLen = VarInt.peek(buffer, pos); + if (zoneNameLen < 0) { + return ValidationResult.error("Invalid string length for ZoneName"); + } + + if (zoneNameLen > 4096000) { + return ValidationResult.error("ZoneName exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += zoneNameLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading ZoneName"); + } + } + + if ((nullBits & 2) != 0) { + int biomeNameOffset = buffer.getIntLE(offset + 13); + if (biomeNameOffset < 0) { + return ValidationResult.error("Invalid offset for BiomeName"); + } + + int posx = offset + 17 + biomeNameOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for BiomeName"); + } + + int biomeNameLen = VarInt.peek(buffer, posx); + if (biomeNameLen < 0) { + return ValidationResult.error("Invalid string length for BiomeName"); + } + + if (biomeNameLen > 4096000) { + return ValidationResult.error("BiomeName exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += biomeNameLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading BiomeName"); + } + } + + return ValidationResult.OK; + } + } + + public BiomeData clone() { + BiomeData copy = new BiomeData(); + copy.zoneId = this.zoneId; + copy.zoneName = this.zoneName; + copy.biomeName = this.biomeName; + copy.biomeColor = this.biomeColor; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof BiomeData other) + ? false + : this.zoneId == other.zoneId + && Objects.equals(this.zoneName, other.zoneName) + && Objects.equals(this.biomeName, other.biomeName) + && this.biomeColor == other.biomeColor; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.zoneId, this.zoneName, this.biomeName, this.biomeColor); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/worldmap/ClearWorldMap.java b/src/com/hypixel/hytale/protocol/packets/worldmap/ClearWorldMap.java new file mode 100644 index 0000000..916016d --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/worldmap/ClearWorldMap.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.protocol.packets.worldmap; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class ClearWorldMap implements Packet { + public static final int PACKET_ID = 242; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 0; + public static final int MAX_SIZE = 0; + + public ClearWorldMap() { + } + + @Override + public int getId() { + return 242; + } + + @Nonnull + public static ClearWorldMap deserialize(@Nonnull ByteBuf buf, int offset) { + return new ClearWorldMap(); + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 0; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + } + + @Override + public int computeSize() { + return 0; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 0 ? ValidationResult.error("Buffer too small: expected at least 0 bytes") : ValidationResult.OK; + } + + public ClearWorldMap clone() { + return new ClearWorldMap(); + } + + @Override + public boolean equals(Object obj) { + return this == obj ? true : obj instanceof ClearWorldMap other; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/worldmap/ContextMenuItem.java b/src/com/hypixel/hytale/protocol/packets/worldmap/ContextMenuItem.java new file mode 100644 index 0000000..4830f50 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/worldmap/ContextMenuItem.java @@ -0,0 +1,169 @@ +package com.hypixel.hytale.protocol.packets.worldmap; + +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class ContextMenuItem { + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 0; + public static final int VARIABLE_FIELD_COUNT = 2; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 32768018; + @Nonnull + public String name = ""; + @Nonnull + public String command = ""; + + public ContextMenuItem() { + } + + public ContextMenuItem(@Nonnull String name, @Nonnull String command) { + this.name = name; + this.command = command; + } + + public ContextMenuItem(@Nonnull ContextMenuItem other) { + this.name = other.name; + this.command = other.command; + } + + @Nonnull + public static ContextMenuItem deserialize(@Nonnull ByteBuf buf, int offset) { + ContextMenuItem obj = new ContextMenuItem(); + int varPos0 = offset + 8 + buf.getIntLE(offset + 0); + int nameLen = VarInt.peek(buf, varPos0); + if (nameLen < 0) { + throw ProtocolException.negativeLength("Name", nameLen); + } else if (nameLen > 4096000) { + throw ProtocolException.stringTooLong("Name", nameLen, 4096000); + } else { + obj.name = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + varPos0 = offset + 8 + buf.getIntLE(offset + 4); + nameLen = VarInt.peek(buf, varPos0); + if (nameLen < 0) { + throw ProtocolException.negativeLength("Command", nameLen); + } else if (nameLen > 4096000) { + throw ProtocolException.stringTooLong("Command", nameLen, 4096000); + } else { + obj.command = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + return obj; + } + } + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + int maxEnd = 8; + int fieldOffset0 = buf.getIntLE(offset + 0); + int pos0 = offset + 8 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + + fieldOffset0 = buf.getIntLE(offset + 4); + pos0 = offset + 8 + fieldOffset0; + sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + int nameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int commandOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + buf.setIntLE(nameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.name, 4096000); + buf.setIntLE(commandOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.command, 4096000); + } + + public int computeSize() { + int size = 8; + size += PacketIO.stringSize(this.name); + return size + PacketIO.stringSize(this.command); + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 8) { + return ValidationResult.error("Buffer too small: expected at least 8 bytes"); + } else { + int nameOffset = buffer.getIntLE(offset + 0); + if (nameOffset < 0) { + return ValidationResult.error("Invalid offset for Name"); + } else { + int pos = offset + 8 + nameOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Name"); + } else { + int nameLen = VarInt.peek(buffer, pos); + if (nameLen < 0) { + return ValidationResult.error("Invalid string length for Name"); + } else if (nameLen > 4096000) { + return ValidationResult.error("Name exceeds max length 4096000"); + } else { + pos += VarInt.length(buffer, pos); + pos += nameLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Name"); + } else { + nameOffset = buffer.getIntLE(offset + 4); + if (nameOffset < 0) { + return ValidationResult.error("Invalid offset for Command"); + } else { + pos = offset + 8 + nameOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Command"); + } else { + nameLen = VarInt.peek(buffer, pos); + if (nameLen < 0) { + return ValidationResult.error("Invalid string length for Command"); + } else if (nameLen > 4096000) { + return ValidationResult.error("Command exceeds max length 4096000"); + } else { + pos += VarInt.length(buffer, pos); + pos += nameLen; + return pos > buffer.writerIndex() ? ValidationResult.error("Buffer overflow reading Command") : ValidationResult.OK; + } + } + } + } + } + } + } + } + } + + public ContextMenuItem clone() { + ContextMenuItem copy = new ContextMenuItem(); + copy.name = this.name; + copy.command = this.command; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof ContextMenuItem other) ? false : Objects.equals(this.name, other.name) && Objects.equals(this.command, other.command); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.name, this.command); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/worldmap/MapChunk.java b/src/com/hypixel/hytale/protocol/packets/worldmap/MapChunk.java new file mode 100644 index 0000000..09be102 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/worldmap/MapChunk.java @@ -0,0 +1,125 @@ +package com.hypixel.hytale.protocol.packets.worldmap; + +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MapChunk { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 16384023; + public int chunkX; + public int chunkZ; + @Nullable + public MapImage image; + + public MapChunk() { + } + + public MapChunk(int chunkX, int chunkZ, @Nullable MapImage image) { + this.chunkX = chunkX; + this.chunkZ = chunkZ; + this.image = image; + } + + public MapChunk(@Nonnull MapChunk other) { + this.chunkX = other.chunkX; + this.chunkZ = other.chunkZ; + this.image = other.image; + } + + @Nonnull + public static MapChunk deserialize(@Nonnull ByteBuf buf, int offset) { + MapChunk obj = new MapChunk(); + byte nullBits = buf.getByte(offset); + obj.chunkX = buf.getIntLE(offset + 1); + obj.chunkZ = buf.getIntLE(offset + 5); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + obj.image = MapImage.deserialize(buf, pos); + pos += MapImage.computeBytesConsumed(buf, pos); + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + pos += MapImage.computeBytesConsumed(buf, pos); + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.image != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.chunkX); + buf.writeIntLE(this.chunkZ); + if (this.image != null) { + this.image.serialize(buf); + } + } + + public int computeSize() { + int size = 9; + if (this.image != null) { + size += this.image.computeSize(); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + ValidationResult imageResult = MapImage.validateStructure(buffer, pos); + if (!imageResult.isValid()) { + return ValidationResult.error("Invalid Image: " + imageResult.error()); + } + + pos += MapImage.computeBytesConsumed(buffer, pos); + } + + return ValidationResult.OK; + } + } + + public MapChunk clone() { + MapChunk copy = new MapChunk(); + copy.chunkX = this.chunkX; + copy.chunkZ = this.chunkZ; + copy.image = this.image != null ? this.image.clone() : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof MapChunk other) + ? false + : this.chunkX == other.chunkX && this.chunkZ == other.chunkZ && Objects.equals(this.image, other.image); + } + } + + @Override + public int hashCode() { + return Objects.hash(this.chunkX, this.chunkZ, this.image); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/worldmap/MapImage.java b/src/com/hypixel/hytale/protocol/packets/worldmap/MapImage.java new file mode 100644 index 0000000..82633b0 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/worldmap/MapImage.java @@ -0,0 +1,165 @@ +package com.hypixel.hytale.protocol.packets.worldmap; + +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MapImage { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 9; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 9; + public static final int MAX_SIZE = 16384014; + public int width; + public int height; + @Nullable + public int[] data; + + public MapImage() { + } + + public MapImage(int width, int height, @Nullable int[] data) { + this.width = width; + this.height = height; + this.data = data; + } + + public MapImage(@Nonnull MapImage other) { + this.width = other.width; + this.height = other.height; + this.data = other.data; + } + + @Nonnull + public static MapImage deserialize(@Nonnull ByteBuf buf, int offset) { + MapImage obj = new MapImage(); + byte nullBits = buf.getByte(offset); + obj.width = buf.getIntLE(offset + 1); + obj.height = buf.getIntLE(offset + 5); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int dataCount = VarInt.peek(buf, pos); + if (dataCount < 0) { + throw ProtocolException.negativeLength("Data", dataCount); + } + + if (dataCount > 4096000) { + throw ProtocolException.arrayTooLong("Data", dataCount, 4096000); + } + + int dataVarLen = VarInt.size(dataCount); + if (pos + dataVarLen + dataCount * 4L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Data", pos + dataVarLen + dataCount * 4, buf.readableBytes()); + } + + pos += dataVarLen; + obj.data = new int[dataCount]; + + for (int i = 0; i < dataCount; i++) { + obj.data[i] = buf.getIntLE(pos + i * 4); + } + + pos += dataCount * 4; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int arrLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + arrLen * 4; + } + + return pos - offset; + } + + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.data != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeIntLE(this.width); + buf.writeIntLE(this.height); + if (this.data != null) { + if (this.data.length > 4096000) { + throw ProtocolException.arrayTooLong("Data", this.data.length, 4096000); + } + + VarInt.write(buf, this.data.length); + + for (int item : this.data) { + buf.writeIntLE(item); + } + } + } + + public int computeSize() { + int size = 9; + if (this.data != null) { + size += VarInt.size(this.data.length) + this.data.length * 4; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 9) { + return ValidationResult.error("Buffer too small: expected at least 9 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 9; + if ((nullBits & 1) != 0) { + int dataCount = VarInt.peek(buffer, pos); + if (dataCount < 0) { + return ValidationResult.error("Invalid array count for Data"); + } + + if (dataCount > 4096000) { + return ValidationResult.error("Data exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += dataCount * 4; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Data"); + } + } + + return ValidationResult.OK; + } + } + + public MapImage clone() { + MapImage copy = new MapImage(); + copy.width = this.width; + copy.height = this.height; + copy.data = this.data != null ? Arrays.copyOf(this.data, this.data.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof MapImage other) ? false : this.width == other.width && this.height == other.height && Arrays.equals(this.data, other.data); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Integer.hashCode(this.width); + result = 31 * result + Integer.hashCode(this.height); + return 31 * result + Arrays.hashCode(this.data); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/worldmap/MapMarker.java b/src/com/hypixel/hytale/protocol/packets/worldmap/MapMarker.java new file mode 100644 index 0000000..7b9abbc --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/worldmap/MapMarker.java @@ -0,0 +1,440 @@ +package com.hypixel.hytale.protocol.packets.worldmap; + +import com.hypixel.hytale.protocol.Transform; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MapMarker { + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 38; + public static final int VARIABLE_FIELD_COUNT = 4; + public static final int VARIABLE_BLOCK_START = 54; + public static final int MAX_SIZE = 1677721600; + @Nullable + public String id; + @Nullable + public String name; + @Nullable + public String markerImage; + @Nullable + public Transform transform; + @Nullable + public ContextMenuItem[] contextMenuItems; + + public MapMarker() { + } + + public MapMarker( + @Nullable String id, @Nullable String name, @Nullable String markerImage, @Nullable Transform transform, @Nullable ContextMenuItem[] contextMenuItems + ) { + this.id = id; + this.name = name; + this.markerImage = markerImage; + this.transform = transform; + this.contextMenuItems = contextMenuItems; + } + + public MapMarker(@Nonnull MapMarker other) { + this.id = other.id; + this.name = other.name; + this.markerImage = other.markerImage; + this.transform = other.transform; + this.contextMenuItems = other.contextMenuItems; + } + + @Nonnull + public static MapMarker deserialize(@Nonnull ByteBuf buf, int offset) { + MapMarker obj = new MapMarker(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 8) != 0) { + obj.transform = Transform.deserialize(buf, offset + 1); + } + + if ((nullBits & 1) != 0) { + int varPos0 = offset + 54 + buf.getIntLE(offset + 38); + int idLen = VarInt.peek(buf, varPos0); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + obj.id = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8); + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 54 + buf.getIntLE(offset + 42); + int nameLen = VarInt.peek(buf, varPos1); + if (nameLen < 0) { + throw ProtocolException.negativeLength("Name", nameLen); + } + + if (nameLen > 4096000) { + throw ProtocolException.stringTooLong("Name", nameLen, 4096000); + } + + obj.name = PacketIO.readVarString(buf, varPos1, PacketIO.UTF8); + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 54 + buf.getIntLE(offset + 46); + int markerImageLen = VarInt.peek(buf, varPos2); + if (markerImageLen < 0) { + throw ProtocolException.negativeLength("MarkerImage", markerImageLen); + } + + if (markerImageLen > 4096000) { + throw ProtocolException.stringTooLong("MarkerImage", markerImageLen, 4096000); + } + + obj.markerImage = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8); + } + + if ((nullBits & 16) != 0) { + int varPos3 = offset + 54 + buf.getIntLE(offset + 50); + int contextMenuItemsCount = VarInt.peek(buf, varPos3); + if (contextMenuItemsCount < 0) { + throw ProtocolException.negativeLength("ContextMenuItems", contextMenuItemsCount); + } + + if (contextMenuItemsCount > 4096000) { + throw ProtocolException.arrayTooLong("ContextMenuItems", contextMenuItemsCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos3); + if (varPos3 + varIntLen + contextMenuItemsCount * 0L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("ContextMenuItems", varPos3 + varIntLen + contextMenuItemsCount * 0, buf.readableBytes()); + } + + obj.contextMenuItems = new ContextMenuItem[contextMenuItemsCount]; + int elemPos = varPos3 + varIntLen; + + for (int i = 0; i < contextMenuItemsCount; i++) { + obj.contextMenuItems[i] = ContextMenuItem.deserialize(buf, elemPos); + elemPos += ContextMenuItem.computeBytesConsumed(buf, elemPos); + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 54; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 38); + int pos0 = offset + 54 + fieldOffset0; + int sl = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0) + sl; + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 42); + int pos1 = offset + 54 + fieldOffset1; + int sl = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1) + sl; + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 46); + int pos2 = offset + 54 + fieldOffset2; + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + if ((nullBits & 16) != 0) { + int fieldOffset3 = buf.getIntLE(offset + 50); + int pos3 = offset + 54 + fieldOffset3; + int arrLen = VarInt.peek(buf, pos3); + pos3 += VarInt.length(buf, pos3); + + for (int i = 0; i < arrLen; i++) { + pos3 += ContextMenuItem.computeBytesConsumed(buf, pos3); + } + + if (pos3 - offset > maxEnd) { + maxEnd = pos3 - offset; + } + } + + return maxEnd; + } + + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.name != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.markerImage != null) { + nullBits = (byte)(nullBits | 4); + } + + if (this.transform != null) { + nullBits = (byte)(nullBits | 8); + } + + if (this.contextMenuItems != null) { + nullBits = (byte)(nullBits | 16); + } + + buf.writeByte(nullBits); + if (this.transform != null) { + this.transform.serialize(buf); + } else { + buf.writeZero(37); + } + + int idOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int nameOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int markerImageOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int contextMenuItemsOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.id != null) { + buf.setIntLE(idOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.id, 4096000); + } else { + buf.setIntLE(idOffsetSlot, -1); + } + + if (this.name != null) { + buf.setIntLE(nameOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.name, 4096000); + } else { + buf.setIntLE(nameOffsetSlot, -1); + } + + if (this.markerImage != null) { + buf.setIntLE(markerImageOffsetSlot, buf.writerIndex() - varBlockStart); + PacketIO.writeVarString(buf, this.markerImage, 4096000); + } else { + buf.setIntLE(markerImageOffsetSlot, -1); + } + + if (this.contextMenuItems != null) { + buf.setIntLE(contextMenuItemsOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.contextMenuItems.length > 4096000) { + throw ProtocolException.arrayTooLong("ContextMenuItems", this.contextMenuItems.length, 4096000); + } + + VarInt.write(buf, this.contextMenuItems.length); + + for (ContextMenuItem item : this.contextMenuItems) { + item.serialize(buf); + } + } else { + buf.setIntLE(contextMenuItemsOffsetSlot, -1); + } + } + + public int computeSize() { + int size = 54; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + if (this.name != null) { + size += PacketIO.stringSize(this.name); + } + + if (this.markerImage != null) { + size += PacketIO.stringSize(this.markerImage); + } + + if (this.contextMenuItems != null) { + int contextMenuItemsSize = 0; + + for (ContextMenuItem elem : this.contextMenuItems) { + contextMenuItemsSize += elem.computeSize(); + } + + size += VarInt.size(this.contextMenuItems.length) + contextMenuItemsSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 54) { + return ValidationResult.error("Buffer too small: expected at least 54 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int idOffset = buffer.getIntLE(offset + 38); + if (idOffset < 0) { + return ValidationResult.error("Invalid offset for Id"); + } + + int pos = offset + 54 + idOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Id"); + } + + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + if ((nullBits & 2) != 0) { + int nameOffset = buffer.getIntLE(offset + 42); + if (nameOffset < 0) { + return ValidationResult.error("Invalid offset for Name"); + } + + int posx = offset + 54 + nameOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Name"); + } + + int nameLen = VarInt.peek(buffer, posx); + if (nameLen < 0) { + return ValidationResult.error("Invalid string length for Name"); + } + + if (nameLen > 4096000) { + return ValidationResult.error("Name exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + posx += nameLen; + if (posx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Name"); + } + } + + if ((nullBits & 4) != 0) { + int markerImageOffset = buffer.getIntLE(offset + 46); + if (markerImageOffset < 0) { + return ValidationResult.error("Invalid offset for MarkerImage"); + } + + int posxx = offset + 54 + markerImageOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for MarkerImage"); + } + + int markerImageLen = VarInt.peek(buffer, posxx); + if (markerImageLen < 0) { + return ValidationResult.error("Invalid string length for MarkerImage"); + } + + if (markerImageLen > 4096000) { + return ValidationResult.error("MarkerImage exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += markerImageLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading MarkerImage"); + } + } + + if ((nullBits & 16) != 0) { + int contextMenuItemsOffset = buffer.getIntLE(offset + 50); + if (contextMenuItemsOffset < 0) { + return ValidationResult.error("Invalid offset for ContextMenuItems"); + } + + int posxxx = offset + 54 + contextMenuItemsOffset; + if (posxxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for ContextMenuItems"); + } + + int contextMenuItemsCount = VarInt.peek(buffer, posxxx); + if (contextMenuItemsCount < 0) { + return ValidationResult.error("Invalid array count for ContextMenuItems"); + } + + if (contextMenuItemsCount > 4096000) { + return ValidationResult.error("ContextMenuItems exceeds max length 4096000"); + } + + posxxx += VarInt.length(buffer, posxxx); + + for (int i = 0; i < contextMenuItemsCount; i++) { + ValidationResult structResult = ContextMenuItem.validateStructure(buffer, posxxx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid ContextMenuItem in ContextMenuItems[" + i + "]: " + structResult.error()); + } + + posxxx += ContextMenuItem.computeBytesConsumed(buffer, posxxx); + } + } + + return ValidationResult.OK; + } + } + + public MapMarker clone() { + MapMarker copy = new MapMarker(); + copy.id = this.id; + copy.name = this.name; + copy.markerImage = this.markerImage; + copy.transform = this.transform != null ? this.transform.clone() : null; + copy.contextMenuItems = this.contextMenuItems != null ? Arrays.stream(this.contextMenuItems).map(e -> e.clone()).toArray(ContextMenuItem[]::new) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof MapMarker other) + ? false + : Objects.equals(this.id, other.id) + && Objects.equals(this.name, other.name) + && Objects.equals(this.markerImage, other.markerImage) + && Objects.equals(this.transform, other.transform) + && Arrays.equals((Object[])this.contextMenuItems, (Object[])other.contextMenuItems); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.id); + result = 31 * result + Objects.hashCode(this.name); + result = 31 * result + Objects.hashCode(this.markerImage); + result = 31 * result + Objects.hashCode(this.transform); + return 31 * result + Arrays.hashCode((Object[])this.contextMenuItems); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/worldmap/TeleportToWorldMapMarker.java b/src/com/hypixel/hytale/protocol/packets/worldmap/TeleportToWorldMapMarker.java new file mode 100644 index 0000000..e7d44ac --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/worldmap/TeleportToWorldMapMarker.java @@ -0,0 +1,143 @@ +package com.hypixel.hytale.protocol.packets.worldmap; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TeleportToWorldMapMarker implements Packet { + public static final int PACKET_ID = 244; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 16384006; + @Nullable + public String id; + + @Override + public int getId() { + return 244; + } + + public TeleportToWorldMapMarker() { + } + + public TeleportToWorldMapMarker(@Nullable String id) { + this.id = id; + } + + public TeleportToWorldMapMarker(@Nonnull TeleportToWorldMapMarker other) { + this.id = other.id; + } + + @Nonnull + public static TeleportToWorldMapMarker deserialize(@Nonnull ByteBuf buf, int offset) { + TeleportToWorldMapMarker obj = new TeleportToWorldMapMarker(); + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int idLen = VarInt.peek(buf, pos); + if (idLen < 0) { + throw ProtocolException.negativeLength("Id", idLen); + } + + if (idLen > 4096000) { + throw ProtocolException.stringTooLong("Id", idLen, 4096000); + } + + int idVarLen = VarInt.length(buf, pos); + obj.id = PacketIO.readVarString(buf, pos, PacketIO.UTF8); + pos += idVarLen + idLen; + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int sl = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos) + sl; + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.id != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + if (this.id != null) { + PacketIO.writeVarString(buf, this.id, 4096000); + } + } + + @Override + public int computeSize() { + int size = 1; + if (this.id != null) { + size += PacketIO.stringSize(this.id); + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 1) { + return ValidationResult.error("Buffer too small: expected at least 1 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 1; + if ((nullBits & 1) != 0) { + int idLen = VarInt.peek(buffer, pos); + if (idLen < 0) { + return ValidationResult.error("Invalid string length for Id"); + } + + if (idLen > 4096000) { + return ValidationResult.error("Id exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + pos += idLen; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading Id"); + } + } + + return ValidationResult.OK; + } + } + + public TeleportToWorldMapMarker clone() { + TeleportToWorldMapMarker copy = new TeleportToWorldMapMarker(); + copy.id = this.id; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof TeleportToWorldMapMarker other ? Objects.equals(this.id, other.id) : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.id); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/worldmap/TeleportToWorldMapPosition.java b/src/com/hypixel/hytale/protocol/packets/worldmap/TeleportToWorldMapPosition.java new file mode 100644 index 0000000..8490f9a --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/worldmap/TeleportToWorldMapPosition.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.protocol.packets.worldmap; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class TeleportToWorldMapPosition implements Packet { + public static final int PACKET_ID = 245; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 8; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 8; + public static final int MAX_SIZE = 8; + public int x; + public int y; + + @Override + public int getId() { + return 245; + } + + public TeleportToWorldMapPosition() { + } + + public TeleportToWorldMapPosition(int x, int y) { + this.x = x; + this.y = y; + } + + public TeleportToWorldMapPosition(@Nonnull TeleportToWorldMapPosition other) { + this.x = other.x; + this.y = other.y; + } + + @Nonnull + public static TeleportToWorldMapPosition deserialize(@Nonnull ByteBuf buf, int offset) { + TeleportToWorldMapPosition obj = new TeleportToWorldMapPosition(); + obj.x = buf.getIntLE(offset + 0); + obj.y = buf.getIntLE(offset + 4); + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 8; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeIntLE(this.x); + buf.writeIntLE(this.y); + } + + @Override + public int computeSize() { + return 8; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 8 ? ValidationResult.error("Buffer too small: expected at least 8 bytes") : ValidationResult.OK; + } + + public TeleportToWorldMapPosition clone() { + TeleportToWorldMapPosition copy = new TeleportToWorldMapPosition(); + copy.x = this.x; + copy.y = this.y; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof TeleportToWorldMapPosition other) ? false : this.x == other.x && this.y == other.y; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/worldmap/UpdateWorldMap.java b/src/com/hypixel/hytale/protocol/packets/worldmap/UpdateWorldMap.java new file mode 100644 index 0000000..207d775 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/worldmap/UpdateWorldMap.java @@ -0,0 +1,435 @@ +package com.hypixel.hytale.protocol.packets.worldmap; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketIO; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateWorldMap implements Packet { + public static final int PACKET_ID = 241; + public static final boolean IS_COMPRESSED = true; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 3; + public static final int VARIABLE_BLOCK_START = 13; + public static final int MAX_SIZE = 1677721600; + @Nullable + public MapChunk[] chunks; + @Nullable + public MapMarker[] addedMarkers; + @Nullable + public String[] removedMarkers; + + @Override + public int getId() { + return 241; + } + + public UpdateWorldMap() { + } + + public UpdateWorldMap(@Nullable MapChunk[] chunks, @Nullable MapMarker[] addedMarkers, @Nullable String[] removedMarkers) { + this.chunks = chunks; + this.addedMarkers = addedMarkers; + this.removedMarkers = removedMarkers; + } + + public UpdateWorldMap(@Nonnull UpdateWorldMap other) { + this.chunks = other.chunks; + this.addedMarkers = other.addedMarkers; + this.removedMarkers = other.removedMarkers; + } + + @Nonnull + public static UpdateWorldMap deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateWorldMap obj = new UpdateWorldMap(); + byte nullBits = buf.getByte(offset); + if ((nullBits & 1) != 0) { + int varPos0 = offset + 13 + buf.getIntLE(offset + 1); + int chunksCount = VarInt.peek(buf, varPos0); + if (chunksCount < 0) { + throw ProtocolException.negativeLength("Chunks", chunksCount); + } + + if (chunksCount > 4096000) { + throw ProtocolException.arrayTooLong("Chunks", chunksCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos0); + if (varPos0 + varIntLen + chunksCount * 9L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("Chunks", varPos0 + varIntLen + chunksCount * 9, buf.readableBytes()); + } + + obj.chunks = new MapChunk[chunksCount]; + int elemPos = varPos0 + varIntLen; + + for (int i = 0; i < chunksCount; i++) { + obj.chunks[i] = MapChunk.deserialize(buf, elemPos); + elemPos += MapChunk.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 2) != 0) { + int varPos1 = offset + 13 + buf.getIntLE(offset + 5); + int addedMarkersCount = VarInt.peek(buf, varPos1); + if (addedMarkersCount < 0) { + throw ProtocolException.negativeLength("AddedMarkers", addedMarkersCount); + } + + if (addedMarkersCount > 4096000) { + throw ProtocolException.arrayTooLong("AddedMarkers", addedMarkersCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos1); + if (varPos1 + varIntLen + addedMarkersCount * 38L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("AddedMarkers", varPos1 + varIntLen + addedMarkersCount * 38, buf.readableBytes()); + } + + obj.addedMarkers = new MapMarker[addedMarkersCount]; + int elemPos = varPos1 + varIntLen; + + for (int i = 0; i < addedMarkersCount; i++) { + obj.addedMarkers[i] = MapMarker.deserialize(buf, elemPos); + elemPos += MapMarker.computeBytesConsumed(buf, elemPos); + } + } + + if ((nullBits & 4) != 0) { + int varPos2 = offset + 13 + buf.getIntLE(offset + 9); + int removedMarkersCount = VarInt.peek(buf, varPos2); + if (removedMarkersCount < 0) { + throw ProtocolException.negativeLength("RemovedMarkers", removedMarkersCount); + } + + if (removedMarkersCount > 4096000) { + throw ProtocolException.arrayTooLong("RemovedMarkers", removedMarkersCount, 4096000); + } + + int varIntLen = VarInt.length(buf, varPos2); + if (varPos2 + varIntLen + removedMarkersCount * 1L > buf.readableBytes()) { + throw ProtocolException.bufferTooSmall("RemovedMarkers", varPos2 + varIntLen + removedMarkersCount * 1, buf.readableBytes()); + } + + obj.removedMarkers = new String[removedMarkersCount]; + int elemPos = varPos2 + varIntLen; + + for (int i = 0; i < removedMarkersCount; i++) { + int strLen = VarInt.peek(buf, elemPos); + if (strLen < 0) { + throw ProtocolException.negativeLength("removedMarkers[" + i + "]", strLen); + } + + if (strLen > 4096000) { + throw ProtocolException.stringTooLong("removedMarkers[" + i + "]", strLen, 4096000); + } + + int strVarLen = VarInt.length(buf, elemPos); + obj.removedMarkers[i] = PacketIO.readVarString(buf, elemPos); + elemPos += strVarLen + strLen; + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int maxEnd = 13; + if ((nullBits & 1) != 0) { + int fieldOffset0 = buf.getIntLE(offset + 1); + int pos0 = offset + 13 + fieldOffset0; + int arrLen = VarInt.peek(buf, pos0); + pos0 += VarInt.length(buf, pos0); + + for (int i = 0; i < arrLen; i++) { + pos0 += MapChunk.computeBytesConsumed(buf, pos0); + } + + if (pos0 - offset > maxEnd) { + maxEnd = pos0 - offset; + } + } + + if ((nullBits & 2) != 0) { + int fieldOffset1 = buf.getIntLE(offset + 5); + int pos1 = offset + 13 + fieldOffset1; + int arrLen = VarInt.peek(buf, pos1); + pos1 += VarInt.length(buf, pos1); + + for (int i = 0; i < arrLen; i++) { + pos1 += MapMarker.computeBytesConsumed(buf, pos1); + } + + if (pos1 - offset > maxEnd) { + maxEnd = pos1 - offset; + } + } + + if ((nullBits & 4) != 0) { + int fieldOffset2 = buf.getIntLE(offset + 9); + int pos2 = offset + 13 + fieldOffset2; + int arrLen = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2); + + for (int i = 0; i < arrLen; i++) { + int sl = VarInt.peek(buf, pos2); + pos2 += VarInt.length(buf, pos2) + sl; + } + + if (pos2 - offset > maxEnd) { + maxEnd = pos2 - offset; + } + } + + return maxEnd; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + int startPos = buf.writerIndex(); + byte nullBits = 0; + if (this.chunks != null) { + nullBits = (byte)(nullBits | 1); + } + + if (this.addedMarkers != null) { + nullBits = (byte)(nullBits | 2); + } + + if (this.removedMarkers != null) { + nullBits = (byte)(nullBits | 4); + } + + buf.writeByte(nullBits); + int chunksOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int addedMarkersOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int removedMarkersOffsetSlot = buf.writerIndex(); + buf.writeIntLE(0); + int varBlockStart = buf.writerIndex(); + if (this.chunks != null) { + buf.setIntLE(chunksOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.chunks.length > 4096000) { + throw ProtocolException.arrayTooLong("Chunks", this.chunks.length, 4096000); + } + + VarInt.write(buf, this.chunks.length); + + for (MapChunk item : this.chunks) { + item.serialize(buf); + } + } else { + buf.setIntLE(chunksOffsetSlot, -1); + } + + if (this.addedMarkers != null) { + buf.setIntLE(addedMarkersOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.addedMarkers.length > 4096000) { + throw ProtocolException.arrayTooLong("AddedMarkers", this.addedMarkers.length, 4096000); + } + + VarInt.write(buf, this.addedMarkers.length); + + for (MapMarker item : this.addedMarkers) { + item.serialize(buf); + } + } else { + buf.setIntLE(addedMarkersOffsetSlot, -1); + } + + if (this.removedMarkers != null) { + buf.setIntLE(removedMarkersOffsetSlot, buf.writerIndex() - varBlockStart); + if (this.removedMarkers.length > 4096000) { + throw ProtocolException.arrayTooLong("RemovedMarkers", this.removedMarkers.length, 4096000); + } + + VarInt.write(buf, this.removedMarkers.length); + + for (String item : this.removedMarkers) { + PacketIO.writeVarString(buf, item, 4096000); + } + } else { + buf.setIntLE(removedMarkersOffsetSlot, -1); + } + } + + @Override + public int computeSize() { + int size = 13; + if (this.chunks != null) { + int chunksSize = 0; + + for (MapChunk elem : this.chunks) { + chunksSize += elem.computeSize(); + } + + size += VarInt.size(this.chunks.length) + chunksSize; + } + + if (this.addedMarkers != null) { + int addedMarkersSize = 0; + + for (MapMarker elem : this.addedMarkers) { + addedMarkersSize += elem.computeSize(); + } + + size += VarInt.size(this.addedMarkers.length) + addedMarkersSize; + } + + if (this.removedMarkers != null) { + int removedMarkersSize = 0; + + for (String elem : this.removedMarkers) { + removedMarkersSize += PacketIO.stringSize(elem); + } + + size += VarInt.size(this.removedMarkers.length) + removedMarkersSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 13) { + return ValidationResult.error("Buffer too small: expected at least 13 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + if ((nullBits & 1) != 0) { + int chunksOffset = buffer.getIntLE(offset + 1); + if (chunksOffset < 0) { + return ValidationResult.error("Invalid offset for Chunks"); + } + + int pos = offset + 13 + chunksOffset; + if (pos >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for Chunks"); + } + + int chunksCount = VarInt.peek(buffer, pos); + if (chunksCount < 0) { + return ValidationResult.error("Invalid array count for Chunks"); + } + + if (chunksCount > 4096000) { + return ValidationResult.error("Chunks exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < chunksCount; i++) { + ValidationResult structResult = MapChunk.validateStructure(buffer, pos); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid MapChunk in Chunks[" + i + "]: " + structResult.error()); + } + + pos += MapChunk.computeBytesConsumed(buffer, pos); + } + } + + if ((nullBits & 2) != 0) { + int addedMarkersOffset = buffer.getIntLE(offset + 5); + if (addedMarkersOffset < 0) { + return ValidationResult.error("Invalid offset for AddedMarkers"); + } + + int posx = offset + 13 + addedMarkersOffset; + if (posx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for AddedMarkers"); + } + + int addedMarkersCount = VarInt.peek(buffer, posx); + if (addedMarkersCount < 0) { + return ValidationResult.error("Invalid array count for AddedMarkers"); + } + + if (addedMarkersCount > 4096000) { + return ValidationResult.error("AddedMarkers exceeds max length 4096000"); + } + + posx += VarInt.length(buffer, posx); + + for (int i = 0; i < addedMarkersCount; i++) { + ValidationResult structResult = MapMarker.validateStructure(buffer, posx); + if (!structResult.isValid()) { + return ValidationResult.error("Invalid MapMarker in AddedMarkers[" + i + "]: " + structResult.error()); + } + + posx += MapMarker.computeBytesConsumed(buffer, posx); + } + } + + if ((nullBits & 4) != 0) { + int removedMarkersOffset = buffer.getIntLE(offset + 9); + if (removedMarkersOffset < 0) { + return ValidationResult.error("Invalid offset for RemovedMarkers"); + } + + int posxx = offset + 13 + removedMarkersOffset; + if (posxx >= buffer.writerIndex()) { + return ValidationResult.error("Offset out of bounds for RemovedMarkers"); + } + + int removedMarkersCount = VarInt.peek(buffer, posxx); + if (removedMarkersCount < 0) { + return ValidationResult.error("Invalid array count for RemovedMarkers"); + } + + if (removedMarkersCount > 4096000) { + return ValidationResult.error("RemovedMarkers exceeds max length 4096000"); + } + + posxx += VarInt.length(buffer, posxx); + + for (int i = 0; i < removedMarkersCount; i++) { + int strLen = VarInt.peek(buffer, posxx); + if (strLen < 0) { + return ValidationResult.error("Invalid string length in RemovedMarkers"); + } + + posxx += VarInt.length(buffer, posxx); + posxx += strLen; + if (posxx > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading string in RemovedMarkers"); + } + } + } + + return ValidationResult.OK; + } + } + + public UpdateWorldMap clone() { + UpdateWorldMap copy = new UpdateWorldMap(); + copy.chunks = this.chunks != null ? Arrays.stream(this.chunks).map(e -> e.clone()).toArray(MapChunk[]::new) : null; + copy.addedMarkers = this.addedMarkers != null ? Arrays.stream(this.addedMarkers).map(e -> e.clone()).toArray(MapMarker[]::new) : null; + copy.removedMarkers = this.removedMarkers != null ? Arrays.copyOf(this.removedMarkers, this.removedMarkers.length) : null; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateWorldMap other) + ? false + : Arrays.equals((Object[])this.chunks, (Object[])other.chunks) + && Arrays.equals((Object[])this.addedMarkers, (Object[])other.addedMarkers) + && Arrays.equals((Object[])this.removedMarkers, (Object[])other.removedMarkers); + } + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Arrays.hashCode((Object[])this.chunks); + result = 31 * result + Arrays.hashCode((Object[])this.addedMarkers); + return 31 * result + Arrays.hashCode((Object[])this.removedMarkers); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/worldmap/UpdateWorldMapSettings.java b/src/com/hypixel/hytale/protocol/packets/worldmap/UpdateWorldMapSettings.java new file mode 100644 index 0000000..65c0d3e --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/worldmap/UpdateWorldMapSettings.java @@ -0,0 +1,242 @@ +package com.hypixel.hytale.protocol.packets.worldmap; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ProtocolException; +import com.hypixel.hytale.protocol.io.ValidationResult; +import com.hypixel.hytale.protocol.io.VarInt; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateWorldMapSettings implements Packet { + public static final int PACKET_ID = 240; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 1; + public static final int FIXED_BLOCK_SIZE = 16; + public static final int VARIABLE_FIELD_COUNT = 1; + public static final int VARIABLE_BLOCK_START = 16; + public static final int MAX_SIZE = 1677721600; + public boolean enabled = true; + @Nullable + public Map biomeDataMap; + public boolean allowTeleportToCoordinates; + public boolean allowTeleportToMarkers; + public float defaultScale = 32.0F; + public float minScale = 2.0F; + public float maxScale = 256.0F; + + @Override + public int getId() { + return 240; + } + + public UpdateWorldMapSettings() { + } + + public UpdateWorldMapSettings( + boolean enabled, + @Nullable Map biomeDataMap, + boolean allowTeleportToCoordinates, + boolean allowTeleportToMarkers, + float defaultScale, + float minScale, + float maxScale + ) { + this.enabled = enabled; + this.biomeDataMap = biomeDataMap; + this.allowTeleportToCoordinates = allowTeleportToCoordinates; + this.allowTeleportToMarkers = allowTeleportToMarkers; + this.defaultScale = defaultScale; + this.minScale = minScale; + this.maxScale = maxScale; + } + + public UpdateWorldMapSettings(@Nonnull UpdateWorldMapSettings other) { + this.enabled = other.enabled; + this.biomeDataMap = other.biomeDataMap; + this.allowTeleportToCoordinates = other.allowTeleportToCoordinates; + this.allowTeleportToMarkers = other.allowTeleportToMarkers; + this.defaultScale = other.defaultScale; + this.minScale = other.minScale; + this.maxScale = other.maxScale; + } + + @Nonnull + public static UpdateWorldMapSettings deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateWorldMapSettings obj = new UpdateWorldMapSettings(); + byte nullBits = buf.getByte(offset); + obj.enabled = buf.getByte(offset + 1) != 0; + obj.allowTeleportToCoordinates = buf.getByte(offset + 2) != 0; + obj.allowTeleportToMarkers = buf.getByte(offset + 3) != 0; + obj.defaultScale = buf.getFloatLE(offset + 4); + obj.minScale = buf.getFloatLE(offset + 8); + obj.maxScale = buf.getFloatLE(offset + 12); + int pos = offset + 16; + if ((nullBits & 1) != 0) { + int biomeDataMapCount = VarInt.peek(buf, pos); + if (biomeDataMapCount < 0) { + throw ProtocolException.negativeLength("BiomeDataMap", biomeDataMapCount); + } + + if (biomeDataMapCount > 4096000) { + throw ProtocolException.dictionaryTooLarge("BiomeDataMap", biomeDataMapCount, 4096000); + } + + pos += VarInt.size(biomeDataMapCount); + obj.biomeDataMap = new HashMap<>(biomeDataMapCount); + + for (int i = 0; i < biomeDataMapCount; i++) { + short key = buf.getShortLE(pos); + pos += 2; + BiomeData val = BiomeData.deserialize(buf, pos); + pos += BiomeData.computeBytesConsumed(buf, pos); + if (obj.biomeDataMap.put(key, val) != null) { + throw ProtocolException.duplicateKey("biomeDataMap", key); + } + } + } + + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + byte nullBits = buf.getByte(offset); + int pos = offset + 16; + if ((nullBits & 1) != 0) { + int dictLen = VarInt.peek(buf, pos); + pos += VarInt.length(buf, pos); + + for (int i = 0; i < dictLen; i++) { + pos += 2; + pos += BiomeData.computeBytesConsumed(buf, pos); + } + } + + return pos - offset; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + byte nullBits = 0; + if (this.biomeDataMap != null) { + nullBits = (byte)(nullBits | 1); + } + + buf.writeByte(nullBits); + buf.writeByte(this.enabled ? 1 : 0); + buf.writeByte(this.allowTeleportToCoordinates ? 1 : 0); + buf.writeByte(this.allowTeleportToMarkers ? 1 : 0); + buf.writeFloatLE(this.defaultScale); + buf.writeFloatLE(this.minScale); + buf.writeFloatLE(this.maxScale); + if (this.biomeDataMap != null) { + if (this.biomeDataMap.size() > 4096000) { + throw ProtocolException.dictionaryTooLarge("BiomeDataMap", this.biomeDataMap.size(), 4096000); + } + + VarInt.write(buf, this.biomeDataMap.size()); + + for (Entry e : this.biomeDataMap.entrySet()) { + buf.writeShortLE(e.getKey()); + e.getValue().serialize(buf); + } + } + } + + @Override + public int computeSize() { + int size = 16; + if (this.biomeDataMap != null) { + int biomeDataMapSize = 0; + + for (Entry kvp : this.biomeDataMap.entrySet()) { + biomeDataMapSize += 2 + kvp.getValue().computeSize(); + } + + size += VarInt.size(this.biomeDataMap.size()) + biomeDataMapSize; + } + + return size; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + if (buffer.readableBytes() - offset < 16) { + return ValidationResult.error("Buffer too small: expected at least 16 bytes"); + } else { + byte nullBits = buffer.getByte(offset); + int pos = offset + 16; + if ((nullBits & 1) != 0) { + int biomeDataMapCount = VarInt.peek(buffer, pos); + if (biomeDataMapCount < 0) { + return ValidationResult.error("Invalid dictionary count for BiomeDataMap"); + } + + if (biomeDataMapCount > 4096000) { + return ValidationResult.error("BiomeDataMap exceeds max length 4096000"); + } + + pos += VarInt.length(buffer, pos); + + for (int i = 0; i < biomeDataMapCount; i++) { + pos += 2; + if (pos > buffer.writerIndex()) { + return ValidationResult.error("Buffer overflow reading key"); + } + + pos += BiomeData.computeBytesConsumed(buffer, pos); + } + } + + return ValidationResult.OK; + } + } + + public UpdateWorldMapSettings clone() { + UpdateWorldMapSettings copy = new UpdateWorldMapSettings(); + copy.enabled = this.enabled; + if (this.biomeDataMap != null) { + Map m = new HashMap<>(); + + for (Entry e : this.biomeDataMap.entrySet()) { + m.put(e.getKey(), e.getValue().clone()); + } + + copy.biomeDataMap = m; + } + + copy.allowTeleportToCoordinates = this.allowTeleportToCoordinates; + copy.allowTeleportToMarkers = this.allowTeleportToMarkers; + copy.defaultScale = this.defaultScale; + copy.minScale = this.minScale; + copy.maxScale = this.maxScale; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return !(obj instanceof UpdateWorldMapSettings other) + ? false + : this.enabled == other.enabled + && Objects.equals(this.biomeDataMap, other.biomeDataMap) + && this.allowTeleportToCoordinates == other.allowTeleportToCoordinates + && this.allowTeleportToMarkers == other.allowTeleportToMarkers + && this.defaultScale == other.defaultScale + && this.minScale == other.minScale + && this.maxScale == other.maxScale; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.enabled, this.biomeDataMap, this.allowTeleportToCoordinates, this.allowTeleportToMarkers, this.defaultScale, this.minScale, this.maxScale + ); + } +} diff --git a/src/com/hypixel/hytale/protocol/packets/worldmap/UpdateWorldMapVisible.java b/src/com/hypixel/hytale/protocol/packets/worldmap/UpdateWorldMapVisible.java new file mode 100644 index 0000000..4d52dc6 --- /dev/null +++ b/src/com/hypixel/hytale/protocol/packets/worldmap/UpdateWorldMapVisible.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.protocol.packets.worldmap; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.ValidationResult; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class UpdateWorldMapVisible implements Packet { + public static final int PACKET_ID = 243; + public static final boolean IS_COMPRESSED = false; + public static final int NULLABLE_BIT_FIELD_SIZE = 0; + public static final int FIXED_BLOCK_SIZE = 1; + public static final int VARIABLE_FIELD_COUNT = 0; + public static final int VARIABLE_BLOCK_START = 1; + public static final int MAX_SIZE = 1; + public boolean visible; + + @Override + public int getId() { + return 243; + } + + public UpdateWorldMapVisible() { + } + + public UpdateWorldMapVisible(boolean visible) { + this.visible = visible; + } + + public UpdateWorldMapVisible(@Nonnull UpdateWorldMapVisible other) { + this.visible = other.visible; + } + + @Nonnull + public static UpdateWorldMapVisible deserialize(@Nonnull ByteBuf buf, int offset) { + UpdateWorldMapVisible obj = new UpdateWorldMapVisible(); + obj.visible = buf.getByte(offset + 0) != 0; + return obj; + } + + public static int computeBytesConsumed(@Nonnull ByteBuf buf, int offset) { + return 1; + } + + @Override + public void serialize(@Nonnull ByteBuf buf) { + buf.writeByte(this.visible ? 1 : 0); + } + + @Override + public int computeSize() { + return 1; + } + + public static ValidationResult validateStructure(@Nonnull ByteBuf buffer, int offset) { + return buffer.readableBytes() - offset < 1 ? ValidationResult.error("Buffer too small: expected at least 1 bytes") : ValidationResult.OK; + } + + public UpdateWorldMapVisible clone() { + UpdateWorldMapVisible copy = new UpdateWorldMapVisible(); + copy.visible = this.visible; + return copy; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return obj instanceof UpdateWorldMapVisible other ? this.visible == other.visible : false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.visible); + } +} diff --git a/src/com/hypixel/hytale/registry/Registration.java b/src/com/hypixel/hytale/registry/Registration.java new file mode 100644 index 0000000..0dc43ce --- /dev/null +++ b/src/com/hypixel/hytale/registry/Registration.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.registry; + +import java.util.function.BooleanSupplier; +import javax.annotation.Nonnull; + +public class Registration { + @Nonnull + protected final BooleanSupplier isEnabled; + @Nonnull + protected final Runnable unregister; + private boolean registered = true; + + public Registration(@Nonnull BooleanSupplier isEnabled, @Nonnull Runnable unregister) { + this.isEnabled = isEnabled; + this.unregister = unregister; + } + + public void unregister() { + if (this.registered && this.isEnabled.getAsBoolean()) { + this.unregister.run(); + } + + this.registered = false; + } + + public boolean isRegistered() { + return this.registered && this.isEnabled.getAsBoolean(); + } + + @Nonnull + @Override + public String toString() { + return "Registration{isEnabled=" + this.isEnabled + ", unregister=" + this.unregister + ", registered=" + this.registered + "}"; + } +} diff --git a/src/com/hypixel/hytale/registry/Registry.java b/src/com/hypixel/hytale/registry/Registry.java new file mode 100644 index 0000000..ffe4281 --- /dev/null +++ b/src/com/hypixel/hytale/registry/Registry.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.registry; + +import com.hypixel.hytale.function.consumer.BooleanConsumer; +import java.util.Collections; +import java.util.List; +import java.util.function.BooleanSupplier; +import javax.annotation.Nonnull; + +public abstract class Registry { + @Nonnull + private final BooleanSupplier precondition; + private final String preconditionMessage; + private final Registry.RegistrationWrapFunction wrappingFunction; + @Nonnull + private final List registrations; + @Nonnull + private final List unmodifiableRegistrations; + private boolean enabled = true; + + protected Registry( + @Nonnull List registrations, + @Nonnull BooleanSupplier precondition, + String preconditionMessage, + @Nonnull Registry.RegistrationWrapFunction wrappingFunction + ) { + this.registrations = registrations; + this.unmodifiableRegistrations = Collections.unmodifiableList(registrations); + this.precondition = precondition; + this.preconditionMessage = preconditionMessage; + this.wrappingFunction = wrappingFunction; + } + + protected void checkPrecondition() { + if (!this.precondition.getAsBoolean()) { + throw new IllegalStateException(this.preconditionMessage); + } + } + + public boolean isEnabled() { + return this.enabled; + } + + public void enable() { + this.enabled = true; + } + + public void shutdown() { + this.enabled = false; + } + + public T register(@Nonnull T registration) { + if (!this.enabled) { + registration.unregister(); + throw new IllegalStateException("Registry is not enabled!"); + } else { + BooleanConsumer reg = v -> registration.unregister(); + this.registrations.add(reg); + return this.wrappingFunction.wrap(registration, () -> this.enabled || registration.isRegistered(), () -> { + this.registrations.remove(reg); + registration.unregister(); + }); + } + } + + @Nonnull + public List getRegistrations() { + return this.unmodifiableRegistrations; + } + + @Nonnull + @Override + public String toString() { + return "Registry{registrations.size()=" + this.registrations.size() + "}"; + } + + public interface RegistrationWrapFunction { + T wrap(T var1, BooleanSupplier var2, Runnable var3); + } +} diff --git a/src/com/hypixel/hytale/server/core/Constants.java b/src/com/hypixel/hytale/server/core/Constants.java new file mode 100644 index 0000000..89b14a0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/Constants.java @@ -0,0 +1,108 @@ +package com.hypixel.hytale.server.core; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.asset.common.CommonAssetModule; +import com.hypixel.hytale.server.core.blocktype.BlockTypeModule; +import com.hypixel.hytale.server.core.console.ConsoleModule; +import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule; +import com.hypixel.hytale.server.core.io.ServerManager; +import com.hypixel.hytale.server.core.modules.LegacyModule; +import com.hypixel.hytale.server.core.modules.accesscontrol.AccessControlModule; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.blockhealth.BlockHealthModule; +import com.hypixel.hytale.server.core.modules.blockset.BlockSetModule; +import com.hypixel.hytale.server.core.modules.camera.FlyCameraModule; +import com.hypixel.hytale.server.core.modules.collision.CollisionModule; +import com.hypixel.hytale.server.core.modules.debug.DebugPlugin; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageModule; +import com.hypixel.hytale.server.core.modules.entity.stamina.StaminaModule; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.modules.entityui.EntityUIModule; +import com.hypixel.hytale.server.core.modules.i18n.I18nModule; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.item.ItemModule; +import com.hypixel.hytale.server.core.modules.migrations.MigrationModule; +import com.hypixel.hytale.server.core.modules.prefabspawner.PrefabSpawnerModule; +import com.hypixel.hytale.server.core.modules.projectile.ProjectileModule; +import com.hypixel.hytale.server.core.modules.serverplayerlist.ServerPlayerListModule; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import com.hypixel.hytale.server.core.modules.splitvelocity.SplitVelocity; +import com.hypixel.hytale.server.core.modules.time.TimeModule; +import com.hypixel.hytale.server.core.permissions.PermissionsModule; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.connectedblocks.ConnectedBlocksModule; +import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule; +import java.nio.file.Files; +import java.nio.file.Path; +import javax.annotation.Nonnull; +import joptsimple.OptionSet; + +public final class Constants { + private static final OptionSet OPTION_SET = Options.getOptionSet(); + public static final boolean DEBUG = true; + public static final boolean SINGLEPLAYER = OPTION_SET.has(Options.SINGLEPLAYER); + public static final boolean ALLOWS_SELF_OP_COMMAND = OPTION_SET.has(Options.ALLOW_SELF_OP_COMMAND); + public static final boolean FRESH_UNIVERSE = checkFreshUniverse(); + public static final boolean FORCE_NETWORK_FLUSH = OPTION_SET.valueOf(Options.FORCE_NETWORK_FLUSH); + public static final Path UNIVERSE_PATH = getUniversePath(); + @Nonnull + public static final PluginManifest[] CORE_PLUGINS = new PluginManifest[]{ + ConsoleModule.MANIFEST, + PermissionsModule.MANIFEST, + FlyCameraModule.MANIFEST, + AssetModule.MANIFEST, + CommonAssetModule.MANIFEST, + CosmeticsModule.MANIFEST, + ServerManager.MANIFEST, + I18nModule.MANIFEST, + ItemModule.MANIFEST, + BlockTypeModule.MANIFEST, + LegacyModule.MANIFEST, + BlockModule.MANIFEST, + BlockStateModule.MANIFEST, + CollisionModule.MANIFEST, + BlockSetModule.MANIFEST, + MigrationModule.MANIFEST, + BlockHealthModule.MANIFEST, + PrefabSpawnerModule.MANIFEST, + TimeModule.MANIFEST, + InteractionModule.MANIFEST, + EntityModule.MANIFEST, + EntityStatsModule.MANIFEST, + EntityUIModule.MANIFEST, + DamageModule.MANIFEST, + SplitVelocity.MANIFEST, + StaminaModule.MANIFEST, + DebugPlugin.MANIFEST, + ProjectileModule.MANIFEST, + ServerPlayerListModule.MANIFEST, + AccessControlModule.MANIFEST, + SingleplayerModule.MANIFEST, + Universe.MANIFEST, + ConnectedBlocksModule.MANIFEST + }; + + public Constants() { + } + + public static void init() { + } + + private static boolean checkFreshUniverse() { + Path universePath = getUniversePath(); + if (!Files.exists(universePath)) { + return true; + } else if (!Files.exists(universePath.resolve("players"))) { + return true; + } else { + Path worlds = universePath.resolve("worlds"); + return !Files.exists(worlds); + } + } + + private static Path getUniversePath() { + return OPTION_SET.has(Options.UNIVERSE) ? OPTION_SET.valueOf(Options.UNIVERSE) : Path.of("universe"); + } +} diff --git a/src/com/hypixel/hytale/server/core/HytaleServer.java b/src/com/hypixel/hytale/server/core/HytaleServer.java new file mode 100644 index 0000000..f1566c3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/HytaleServer.java @@ -0,0 +1,491 @@ +package com.hypixel.hytale.server.core; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.common.thread.HytaleForkJoinThreadFactory; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.common.util.GCUtil; +import com.hypixel.hytale.common.util.HardwareUtil; +import com.hypixel.hytale.common.util.NetworkUtil; +import com.hypixel.hytale.common.util.java.ManifestUtil; +import com.hypixel.hytale.event.EventBus; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.backend.HytaleLogManager; +import com.hypixel.hytale.logger.backend.HytaleLoggerBackend; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; +import com.hypixel.hytale.metrics.JVMMetrics; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.plugin.early.EarlyPluginLoader; +import com.hypixel.hytale.server.core.asset.AssetRegistryLoader; +import com.hypixel.hytale.server.core.asset.LoadAssetEvent; +import com.hypixel.hytale.server.core.auth.ServerAuthManager; +import com.hypixel.hytale.server.core.command.system.CommandManager; +import com.hypixel.hytale.server.core.console.ConsoleSender; +import com.hypixel.hytale.server.core.event.events.BootEvent; +import com.hypixel.hytale.server.core.event.events.ShutdownEvent; +import com.hypixel.hytale.server.core.io.ServerManager; +import com.hypixel.hytale.server.core.io.netty.NettyUtil; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import com.hypixel.hytale.server.core.plugin.PluginBase; +import com.hypixel.hytale.server.core.plugin.PluginClassLoader; +import com.hypixel.hytale.server.core.plugin.PluginManager; +import com.hypixel.hytale.server.core.plugin.PluginState; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.datastore.DataStoreProvider; +import com.hypixel.hytale.server.core.universe.datastore.DiskDataStoreProvider; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.util.concurrent.ThreadUtil; +import io.netty.handler.codec.quic.Quic; +import io.sentry.Sentry; +import io.sentry.SentryOptions; +import io.sentry.protocol.Contexts; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.time.Instant; +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import joptsimple.OptionSet; + +public class HytaleServer { + public static final int DEFAULT_PORT = 5520; + public static final ScheduledExecutorService SCHEDULED_EXECUTOR = Executors.newSingleThreadScheduledExecutor(ThreadUtil.daemon("Scheduler")); + @Nonnull + public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() + .register("Time", server -> Instant.now(), Codec.INSTANT) + .register("Boot", server -> server.boot, Codec.INSTANT) + .register("BootStart", server -> server.bootStart, Codec.LONG) + .register("Booting", server -> server.booting.get(), Codec.BOOLEAN) + .register("ShutdownReason", server -> { + ShutdownReason reason = server.shutdown.get(); + return reason == null ? null : reason.toString(); + }, Codec.STRING) + .register("PluginManager", HytaleServer::getPluginManager, PluginManager.METRICS_REGISTRY) + .register("Config", HytaleServer::getConfig, HytaleServerConfig.CODEC) + .register("JVM", JVMMetrics.METRICS_REGISTRY); + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static HytaleServer instance; + private final Semaphore aliveLock = new Semaphore(0); + private final AtomicBoolean booting = new AtomicBoolean(false); + private final AtomicBoolean booted = new AtomicBoolean(false); + private final AtomicReference shutdown = new AtomicReference<>(); + private final EventBus eventBus = new EventBus(Options.getOptionSet().has(Options.EVENT_DEBUG)); + private final PluginManager pluginManager = new PluginManager(); + private final CommandManager commandManager = new CommandManager(); + @Nonnull + private final HytaleServerConfig hytaleServerConfig; + private final Instant boot; + private final long bootStart; + private int pluginsProgress; + + public HytaleServer() throws IOException { + instance = this; + Quic.ensureAvailability(); + HytaleLoggerBackend.setIndent(25); + ThreadUtil.forceTimeHighResolution(); + ThreadUtil.createKeepAliveThread(this.aliveLock); + this.boot = Instant.now(); + this.bootStart = System.nanoTime(); + LOGGER.at(Level.INFO).log("Starting HytaleServer"); + Constants.init(); + DataStoreProvider.CODEC.register("Disk", DiskDataStoreProvider.class, DiskDataStoreProvider.CODEC); + LOGGER.at(Level.INFO).log("Loading config..."); + this.hytaleServerConfig = HytaleServerConfig.load(); + HytaleLoggerBackend.reloadLogLevels(); + System.setProperty("java.util.concurrent.ForkJoinPool.common.threadFactory", HytaleForkJoinThreadFactory.class.getName()); + OptionSet optionSet = Options.getOptionSet(); + LOGGER.at(Level.INFO).log("Authentication mode: %s", optionSet.valueOf(Options.AUTH_MODE)); + ServerAuthManager.getInstance().initialize(); + if (EarlyPluginLoader.hasTransformers()) { + HytaleLogger.getLogger().at(Level.INFO).log("Early plugins loaded!! Disabling Sentry!!"); + } else if (!optionSet.has(Options.DISABLE_SENTRY)) { + LOGGER.at(Level.INFO).log("Enabling Sentry"); + SentryOptions options = new SentryOptions(); + options.setDsn("https://6043a13c7b5c45b5c834b6d896fb378e@sentry.hytale.com/4"); + options.setRelease(ManifestUtil.getImplementationVersion()); + options.setDist(ManifestUtil.getImplementationRevisionId()); + options.setEnvironment("release"); + options.setTag("patchline", ManifestUtil.getPatchline()); + options.setServerName(NetworkUtil.getHostName()); + options.setBeforeSend((event, hint) -> { + Throwable throwable = event.getThrowable(); + if (PluginClassLoader.isFromThirdPartyPlugin(throwable)) { + return null; + } else { + Contexts contexts = event.getContexts(); + HashMap serverContext = new HashMap<>(); + serverContext.put("name", this.getServerName()); + serverContext.put("max-players", this.getConfig().getMaxPlayers()); + ServerManager serverManager = ServerManager.get(); + if (serverManager != null) { + serverContext.put("listeners", serverManager.getListeners().stream().map(Object::toString).toList()); + } + + contexts.put("server", serverContext); + Universe universe = Universe.get(); + if (universe != null) { + HashMap universeContext = new HashMap<>(); + universeContext.put("path", universe.getPath().toString()); + universeContext.put("player-count", universe.getPlayerCount()); + universeContext.put("worlds", universe.getWorlds().keySet().stream().toList()); + contexts.put("universe", universeContext); + } + + HashMap pluginsContext = new HashMap<>(); + + for (PluginBase plugin : this.pluginManager.getPlugins()) { + PluginManifest manifestx = plugin.getManifest(); + HashMap pluginInfo = new HashMap<>(); + pluginInfo.put("version", manifestx.getVersion().toString()); + pluginInfo.put("state", plugin.getState().name()); + pluginsContext.put(plugin.getIdentifier().toString(), pluginInfo); + } + + contexts.put("plugins", pluginsContext); + return event; + } + }); + Sentry.init(options); + Sentry.configureScope( + scope -> { + UUID hardwareUUID = HardwareUtil.getUUID(); + if (hardwareUUID != null) { + scope.setContexts("hardware", Map.of("uuid", hardwareUUID.toString())); + } + + scope.setContexts( + "build", + Map.of( + "version", + String.valueOf(ManifestUtil.getImplementationVersion()), + "revision-id", + String.valueOf(ManifestUtil.getImplementationRevisionId()), + "patchline", + String.valueOf(ManifestUtil.getPatchline()), + "environment", + "release" + ) + ); + if (Constants.SINGLEPLAYER) { + scope.setContexts( + "singleplayer", Map.of("owner-uuid", String.valueOf(SingleplayerModule.getUuid()), "owner-name", SingleplayerModule.getUsername()) + ); + } + } + ); + HytaleLogger.getLogger().setSentryClient(Sentry.getCurrentScopes()); + } + + NettyUtil.init(); + float sin = TrigMathUtil.sin(0.0F); + float atan2 = TrigMathUtil.atan2(0.0F, 0.0F); + Thread shutdownHook = new Thread(() -> { + if (this.shutdown.getAndSet(ShutdownReason.SIGINT) == null) { + this.shutdown0(ShutdownReason.SIGINT); + } + }, "ShutdownHook"); + shutdownHook.setDaemon(false); + Runtime.getRuntime().addShutdownHook(shutdownHook); + AssetRegistryLoader.init(); + + for (PluginManifest manifest : Constants.CORE_PLUGINS) { + this.pluginManager.registerCorePlugin(manifest); + } + + GCUtil.register(info -> { + Universe universe = Universe.get(); + if (universe != null) { + for (World world : universe.getWorlds().values()) { + world.markGCHasRun(); + } + } + }); + this.boot(); + } + + @Nonnull + public EventBus getEventBus() { + return this.eventBus; + } + + @Nonnull + public PluginManager getPluginManager() { + return this.pluginManager; + } + + @Nonnull + public CommandManager getCommandManager() { + return this.commandManager; + } + + @Nonnull + public HytaleServerConfig getConfig() { + return this.hytaleServerConfig; + } + + private void boot() { + if (!this.booting.getAndSet(true)) { + LOGGER.at(Level.INFO) + .log("Booting up HytaleServer - Version: " + ManifestUtil.getImplementationVersion() + ", Revision: " + ManifestUtil.getImplementationRevisionId()); + + try { + this.pluginsProgress = 0; + this.sendSingleplayerProgress(); + if (this.isShuttingDown()) { + return; + } + + LOGGER.at(Level.INFO).log("Setup phase..."); + this.commandManager.registerCommands(); + this.pluginManager.setup(); + ServerAuthManager.getInstance().initializeCredentialStore(); + LOGGER.at(Level.INFO).log("Setup phase completed! Boot time %s", FormatUtil.nanosToString(System.nanoTime() - this.bootStart)); + if (this.isShuttingDown()) { + return; + } + + LoadAssetEvent loadAssetEvent = get() + .getEventBus() + .dispatchFor(LoadAssetEvent.class) + .dispatch(new LoadAssetEvent(this.bootStart)); + if (this.isShuttingDown()) { + return; + } + + if (loadAssetEvent.isShouldShutdown()) { + List reasons = loadAssetEvent.getReasons(); + String join = String.join(", ", reasons); + LOGGER.at(Level.SEVERE).log("Asset validation FAILED with %d reason(s): %s", reasons.size(), join); + this.shutdownServer(ShutdownReason.VALIDATE_ERROR.withMessage(join)); + return; + } + + if (Options.getOptionSet().has(Options.SHUTDOWN_AFTER_VALIDATE)) { + LOGGER.at(Level.INFO).log("Asset validation passed"); + this.shutdownServer(ShutdownReason.SHUTDOWN); + return; + } + + this.pluginsProgress = 0; + this.sendSingleplayerProgress(); + if (this.isShuttingDown()) { + return; + } + + LOGGER.at(Level.INFO).log("Starting plugin manager..."); + this.pluginManager.start(); + LOGGER.at(Level.INFO).log("Plugin manager started! Startup time so far: %s", FormatUtil.nanosToString(System.nanoTime() - this.bootStart)); + if (this.isShuttingDown()) { + return; + } + + this.sendSingleplayerSignal("-=|Enabled|0"); + } catch (Throwable var5) { + LOGGER.at(Level.SEVERE).withCause(var5).log("Failed to boot HytaleServer!"); + Throwable t = var5; + + while (t.getCause() != null) { + t = t.getCause(); + } + + this.shutdownServer(ShutdownReason.CRASH.withMessage("Failed to start server! " + t.getMessage())); + } + + if (this.hytaleServerConfig.consumeHasChanged()) { + HytaleServerConfig.save(this.hytaleServerConfig).join(); + } + + SCHEDULED_EXECUTOR.scheduleWithFixedDelay(() -> { + try { + if (this.hytaleServerConfig.consumeHasChanged()) { + HytaleServerConfig.save(this.hytaleServerConfig).join(); + } + } catch (Exception var2x) { + LOGGER.at(Level.SEVERE).withCause(var2x).log("Failed to save server config!"); + } + }, 1L, 1L, TimeUnit.MINUTES); + LOGGER.at(Level.INFO).log("Getting Hytale Universe ready..."); + Universe.get().getUniverseReady().join(); + LOGGER.at(Level.INFO).log("Universe ready!"); + List tags = new ObjectArrayList<>(); + if (Constants.SINGLEPLAYER) { + tags.add("Singleplayer"); + } else { + tags.add("Multiplayer"); + } + + if (Constants.FRESH_UNIVERSE) { + tags.add("Fresh Universe"); + } + + this.booted.set(true); + ServerManager.get().waitForBindComplete(); + this.eventBus.dispatch(BootEvent.class); + List bootCommands = Options.getOptionSet().valuesOf(Options.BOOT_COMMAND); + if (!bootCommands.isEmpty()) { + CommandManager.get().handleCommands(ConsoleSender.INSTANCE, new ArrayDeque<>(bootCommands)).join(); + } + + String border = "\u001b[0;32m==============================================================================================="; + LOGGER.at(Level.INFO).log("\u001b[0;32m==============================================================================================="); + LOGGER.at(Level.INFO) + .log( + "%s Hytale Server Booted! [%s] took %s", + "\u001b[0;32m", + String.join(", ", tags), + FormatUtil.nanosToString(System.nanoTime() - this.bootStart) + ); + LOGGER.at(Level.INFO).log("\u001b[0;32m==============================================================================================="); + ServerAuthManager authManager = ServerAuthManager.getInstance(); + if (!authManager.isSingleplayer() && authManager.getAuthMode() == ServerAuthManager.AuthMode.NONE) { + LOGGER.at(Level.WARNING).log("%sNo server tokens configured. Use /auth login to authenticate.", "\u001b[0;31m"); + } + + this.sendSingleplayerSignal(">> Singleplayer Ready <<"); + } + } + + public void shutdownServer() { + this.shutdownServer(ShutdownReason.SHUTDOWN); + } + + public void shutdownServer(@Nonnull ShutdownReason reason) { + Objects.requireNonNull(reason, "Server shutdown reason can't be null!"); + if (this.shutdown.getAndSet(reason) == null) { + if (reason.getMessage() != null) { + this.sendSingleplayerSignal("-=|Shutdown|" + reason.getMessage()); + } + + Thread shutdownThread = new Thread(() -> this.shutdown0(reason), "ShutdownThread"); + shutdownThread.setDaemon(false); + shutdownThread.start(); + } + } + + void shutdown0(@Nonnull ShutdownReason reason) { + LOGGER.at(Level.INFO).log("Shutdown triggered!!!"); + + try { + LOGGER.at(Level.INFO).log("Shutting down... %d '%s'", reason.getExitCode(), reason.getMessage()); + this.eventBus.dispatch(ShutdownEvent.class); + this.pluginManager.shutdown(); + this.commandManager.shutdown(); + this.eventBus.shutdown(); + ServerAuthManager.getInstance().shutdown(); + LOGGER.at(Level.INFO).log("Saving config..."); + if (this.hytaleServerConfig.consumeHasChanged()) { + HytaleServerConfig.save(this.hytaleServerConfig).join(); + } + + LOGGER.at(Level.INFO).log("Shutdown completed!"); + } catch (Throwable var3) { + LOGGER.at(Level.SEVERE).withCause(var3).log("Exception while shutting down:"); + } + + this.aliveLock.release(); + HytaleLogManager.resetFinally(); + SCHEDULED_EXECUTOR.schedule(() -> { + LOGGER.at(Level.SEVERE).log("Forcing shutdown!"); + Runtime.getRuntime().halt(reason.getExitCode()); + }, 3L, TimeUnit.SECONDS); + if (reason != ShutdownReason.SIGINT) { + System.exit(reason.getExitCode()); + } + } + + public void doneSetup(PluginBase plugin) { + this.pluginsProgress++; + this.sendSingleplayerProgress(); + } + + public void doneStart(PluginBase plugin) { + this.pluginsProgress++; + this.sendSingleplayerProgress(); + } + + public void doneStop(PluginBase plugin) { + this.pluginsProgress--; + this.sendSingleplayerProgress(); + } + + public void sendSingleplayerProgress() { + List plugins = this.pluginManager.getPlugins(); + if (this.shutdown.get() != null) { + this.sendSingleplayerSignal("-=|Shutdown Modules|" + MathUtil.round((double)(plugins.size() - this.pluginsProgress) / plugins.size(), 2) * 100.0); + } else if (this.pluginManager.getState() == PluginState.SETUP) { + this.sendSingleplayerSignal("-=|Setup|" + MathUtil.round((double)this.pluginsProgress / plugins.size(), 2) * 100.0); + } else if (this.pluginManager.getState() == PluginState.START) { + this.sendSingleplayerSignal("-=|Starting|" + MathUtil.round((double)this.pluginsProgress / plugins.size(), 2) * 100.0); + } + } + + public String getServerName() { + return this.getConfig().getServerName(); + } + + public boolean isBooting() { + return this.booting.get(); + } + + public boolean isBooted() { + return this.booted.get(); + } + + public boolean isShuttingDown() { + return this.shutdown.get() != null; + } + + @Nonnull + public Instant getBoot() { + return this.boot; + } + + public long getBootStart() { + return this.bootStart; + } + + @Nullable + public ShutdownReason getShutdownReason() { + return this.shutdown.get(); + } + + private void sendSingleplayerSignal(String message) { + if (Constants.SINGLEPLAYER) { + HytaleLoggerBackend.rawLog(message); + } + } + + public void reportSingleplayerStatus(String message) { + if (Constants.SINGLEPLAYER) { + HytaleLoggerBackend.rawLog("-=|" + message + "|0"); + } + } + + public void reportSaveProgress(@Nonnull World world, int saved, int total) { + if (this.isShuttingDown()) { + double progress = MathUtil.round((double)saved / total, 2) * 100.0; + if (Constants.SINGLEPLAYER) { + this.sendSingleplayerSignal("-=|Saving world " + world.getName() + " chunks|" + progress); + } else if (total < 10 || saved % (total / 10) == 0) { + world.getLogger().at(Level.INFO).log("Saving chunks: %.0f%%", progress); + } + } + } + + public static HytaleServer get() { + return instance; + } +} diff --git a/src/com/hypixel/hytale/server/core/HytaleServerConfig.java b/src/com/hypixel/hytale/server/core/HytaleServerConfig.java new file mode 100644 index 0000000..bb7beec --- /dev/null +++ b/src/com/hypixel/hytale/server/core/HytaleServerConfig.java @@ -0,0 +1,685 @@ +package com.hypixel.hytale.server.core; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.DocumentContainingCodec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.codecs.map.ObjectMapCodec; +import com.hypixel.hytale.codec.lookup.Priority; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.common.plugin.PluginIdentifier; +import com.hypixel.hytale.common.semver.SemverRange; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.auth.AuthCredentialStoreProvider; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.universe.playerdata.DefaultPlayerStorageProvider; +import com.hypixel.hytale.server.core.universe.playerdata.DiskPlayerStorageProvider; +import com.hypixel.hytale.server.core.universe.playerdata.PlayerStorageProvider; +import com.hypixel.hytale.server.core.util.BsonUtil; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public class HytaleServerConfig { + public static final int VERSION = 3; + public static final int DEFAULT_MAX_VIEW_RADIUS = 32; + @Nonnull + public static final Path PATH = Path.of("config.json"); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(HytaleServerConfig.class, HytaleServerConfig::new) + .versioned() + .codecVersion(3) + .append(new KeyedCodec<>("ServerName", Codec.STRING), (o, s) -> o.serverName = s, o -> o.serverName) + .add() + .append(new KeyedCodec<>("MOTD", Codec.STRING), (o, s) -> o.motd = s, o -> o.motd) + .add() + .append(new KeyedCodec<>("Password", Codec.STRING), (o, s) -> o.password = s, o -> o.password) + .add() + .append(new KeyedCodec<>("MaxPlayers", Codec.INTEGER), (o, i) -> o.maxPlayers = i, o -> o.maxPlayers) + .add() + .append(new KeyedCodec<>("MaxViewRadius", Codec.INTEGER), (o, i) -> o.maxViewRadius = i, o -> o.maxViewRadius) + .add() + .append(new KeyedCodec<>("LocalCompressionEnabled", Codec.BOOLEAN), (o, i) -> o.localCompressionEnabled = i, o -> o.localCompressionEnabled) + .add() + .append(new KeyedCodec<>("Defaults", HytaleServerConfig.Defaults.CODEC), (o, obj) -> o.defaults = obj, o -> o.defaults) + .add() + .append( + new KeyedCodec<>("ConnectionTimeouts", HytaleServerConfig.ConnectionTimeouts.CODEC), (o, m) -> o.connectionTimeouts = m, o -> o.connectionTimeouts + ) + .add() + .append(new KeyedCodec<>("RateLimit", HytaleServerConfig.RateLimitConfig.CODEC), (o, m) -> o.rateLimitConfig = m, o -> o.rateLimitConfig) + .add() + .append(new KeyedCodec<>("Modules", new MapCodec<>(HytaleServerConfig.Module.CODEC, ConcurrentHashMap::new, false)), (o, m) -> { + o.modules = m; + o.unmodifiableModules = Collections.unmodifiableMap(m); + }, o -> o.modules) + .add() + .append(new KeyedCodec<>("LogLevels", new MapCodec<>(Codec.LOG_LEVEL, ConcurrentHashMap::new, false)), (o, m) -> { + o.logLevels = m; + o.unmodifiableLogLevels = Collections.unmodifiableMap(o.logLevels); + }, o -> o.logLevels) + .add() + .append( + new KeyedCodec<>( + "Plugins", + new ObjectMapCodec<>( + HytaleServerConfig.ModConfig.CODEC, Object2ObjectOpenHashMap::new, PluginIdentifier::toString, PluginIdentifier::fromString, false + ) + ), + (o, i) -> o.legacyPluginConfig = i, + o -> null + ) + .setVersionRange(0, 2) + .add() + .append( + new KeyedCodec<>( + "Mods", + new ObjectMapCodec<>(HytaleServerConfig.ModConfig.CODEC, ConcurrentHashMap::new, PluginIdentifier::toString, PluginIdentifier::fromString, false) + ), + (o, i) -> o.modConfig = i, + o -> o.modConfig + ) + .add() + .append( + new KeyedCodec<>("DisplayTmpTagsInStrings", Codec.BOOLEAN), + (o, displayTmpTagsInStrings) -> o.displayTmpTagsInStrings = displayTmpTagsInStrings, + o -> o.displayTmpTagsInStrings + ) + .add() + .append(new KeyedCodec<>("PlayerStorage", PlayerStorageProvider.CODEC), (o, obj) -> o.playerStorageProvider = obj, o -> o.playerStorageProvider) + .add() + .append(new KeyedCodec<>("AuthCredentialStore", Codec.BSON_DOCUMENT), (o, value) -> o.authCredentialStoreConfig = value, o -> o.authCredentialStoreConfig) + .add() + .afterDecode(config -> { + config.defaults.hytaleServerConfig = config; + config.connectionTimeouts.hytaleServerConfig = config; + config.rateLimitConfig.hytaleServerConfig = config; + config.modules.values().forEach(m -> m.setHytaleServerConfig(config)); + if (config.legacyPluginConfig != null && !config.legacyPluginConfig.isEmpty()) { + for (Entry entry : config.legacyPluginConfig.entrySet()) { + config.modConfig.putIfAbsent(entry.getKey(), entry.getValue()); + } + + config.legacyPluginConfig = null; + config.markChanged(); + } + }) + .build(); + @Nonnull + private final transient AtomicBoolean hasChanged = new AtomicBoolean(); + private String serverName = "Hytale Server"; + private String motd = ""; + private String password = ""; + private int maxPlayers = 100; + private int maxViewRadius = 32; + private boolean localCompressionEnabled; + @Nonnull + private HytaleServerConfig.Defaults defaults = new HytaleServerConfig.Defaults(this); + @Nonnull + private HytaleServerConfig.ConnectionTimeouts connectionTimeouts = new HytaleServerConfig.ConnectionTimeouts(this); + @Nonnull + private HytaleServerConfig.RateLimitConfig rateLimitConfig = new HytaleServerConfig.RateLimitConfig(this); + @Nonnull + private Map modules = new ConcurrentHashMap<>(); + @Nonnull + private Map logLevels = Collections.emptyMap(); + @Nullable + private transient Map legacyPluginConfig; + @Nonnull + private Map modConfig = new ConcurrentHashMap<>(); + @Nonnull + private Map unmodifiableModules = Collections.unmodifiableMap(this.modules); + @Nonnull + private Map unmodifiableLogLevels = Collections.unmodifiableMap(this.logLevels); + @Nonnull + private PlayerStorageProvider playerStorageProvider = PlayerStorageProvider.CODEC.getDefault(); + @Nullable + private BsonDocument authCredentialStoreConfig = null; + @Nullable + private transient AuthCredentialStoreProvider authCredentialStoreProvider = null; + private boolean displayTmpTagsInStrings; + + public HytaleServerConfig() { + } + + public String getServerName() { + return this.serverName; + } + + public void setServerName(@Nonnull String serverName) { + this.serverName = serverName; + this.markChanged(); + } + + public String getMotd() { + return this.motd; + } + + public void setMotd(@Nonnull String motd) { + this.motd = motd; + this.markChanged(); + } + + public String getPassword() { + return this.password; + } + + public void setPassword(@Nonnull String password) { + this.password = password; + this.markChanged(); + } + + public boolean isDisplayTmpTagsInStrings() { + return this.displayTmpTagsInStrings; + } + + public void setDisplayTmpTagsInStrings(boolean displayTmpTagsInStrings) { + this.displayTmpTagsInStrings = displayTmpTagsInStrings; + } + + public int getMaxPlayers() { + return this.maxPlayers; + } + + public void setMaxPlayers(int maxPlayers) { + this.maxPlayers = maxPlayers; + this.markChanged(); + } + + public int getMaxViewRadius() { + return this.maxViewRadius; + } + + public void setMaxViewRadius(int maxViewRadius) { + this.maxViewRadius = maxViewRadius; + this.markChanged(); + } + + public boolean isLocalCompressionEnabled() { + return this.localCompressionEnabled; + } + + public void setLocalCompressionEnabled(boolean localCompression) { + this.localCompressionEnabled = localCompression; + this.markChanged(); + } + + @Nonnull + public HytaleServerConfig.Defaults getDefaults() { + return this.defaults; + } + + public void setDefaults(@Nonnull HytaleServerConfig.Defaults defaults) { + this.defaults = defaults; + this.markChanged(); + } + + @Nonnull + public HytaleServerConfig.ConnectionTimeouts getConnectionTimeouts() { + return this.connectionTimeouts; + } + + public void setConnectionTimeouts(@Nonnull HytaleServerConfig.ConnectionTimeouts connectionTimeouts) { + this.connectionTimeouts = connectionTimeouts; + this.markChanged(); + } + + @Nonnull + public HytaleServerConfig.RateLimitConfig getRateLimitConfig() { + return this.rateLimitConfig; + } + + public void setRateLimitConfig(@Nonnull HytaleServerConfig.RateLimitConfig rateLimitConfig) { + this.rateLimitConfig = rateLimitConfig; + this.markChanged(); + } + + @Nonnull + public Map getModules() { + return this.unmodifiableModules; + } + + @Nonnull + public HytaleServerConfig.Module getModule(String moduleName) { + return this.modules.computeIfAbsent(moduleName, k -> new HytaleServerConfig.Module(this)); + } + + public void setModules(@Nonnull Map modules) { + this.modules = modules; + this.markChanged(); + } + + @Nonnull + public Map getLogLevels() { + return this.unmodifiableLogLevels; + } + + public void setLogLevels(@Nonnull Map logLevels) { + this.logLevels = logLevels; + this.markChanged(); + } + + @Nonnull + public Map getModConfig() { + return this.modConfig; + } + + public void setModConfig(@Nonnull Map modConfig) { + this.modConfig = modConfig; + this.markChanged(); + } + + @Nonnull + public PlayerStorageProvider getPlayerStorageProvider() { + return this.playerStorageProvider; + } + + public void setPlayerStorageProvider(@Nonnull PlayerStorageProvider playerStorageProvider) { + this.playerStorageProvider = playerStorageProvider; + this.markChanged(); + } + + @Nonnull + public AuthCredentialStoreProvider getAuthCredentialStoreProvider() { + if (this.authCredentialStoreProvider != null) { + return this.authCredentialStoreProvider; + } else { + if (this.authCredentialStoreConfig != null) { + this.authCredentialStoreProvider = AuthCredentialStoreProvider.CODEC.decode(this.authCredentialStoreConfig); + } else { + this.authCredentialStoreProvider = AuthCredentialStoreProvider.CODEC.getDefault(); + } + + return this.authCredentialStoreProvider; + } + } + + public void setAuthCredentialStoreProvider(@Nonnull AuthCredentialStoreProvider provider) { + this.authCredentialStoreProvider = provider; + this.authCredentialStoreConfig = (BsonDocument)AuthCredentialStoreProvider.CODEC.encode(provider); + this.markChanged(); + } + + public void removeModule(@Nonnull String module) { + this.modules.remove(module); + this.markChanged(); + } + + public void markChanged() { + this.hasChanged.set(true); + } + + public boolean consumeHasChanged() { + return this.hasChanged.getAndSet(false); + } + + @Nonnull + public static HytaleServerConfig load() { + return load(PATH); + } + + @Nonnull + public static HytaleServerConfig load(@Nonnull Path path) { + if (!Files.isRegularFile(path)) { + HytaleServerConfig hytaleServerConfig = new HytaleServerConfig(); + if (!Options.getOptionSet().has(Options.BARE)) { + save(hytaleServerConfig).join(); + } + + return hytaleServerConfig; + } else { + try { + HytaleServerConfig config = RawJsonReader.readSyncWithBak(path, CODEC, HytaleLogger.getLogger()); + if (config == null) { + throw new RuntimeException("Failed to load server config from " + path); + } else { + return config; + } + } catch (Exception var2) { + throw new RuntimeException("Failed to read server config!", var2); + } + } + } + + @Nonnull + public static CompletableFuture save(@Nonnull HytaleServerConfig hytaleServerConfig) { + return save(PATH, hytaleServerConfig); + } + + @Nonnull + public static CompletableFuture save(@Nonnull Path path, @Nonnull HytaleServerConfig hytaleServerConfig) { + BsonDocument document = CODEC.encode(hytaleServerConfig, ExtraInfo.THREAD_LOCAL.get()).asDocument(); + return BsonUtil.writeDocument(path, document); + } + + static { + PlayerStorageProvider.CODEC.register(Priority.DEFAULT, "Hytale", DefaultPlayerStorageProvider.class, DefaultPlayerStorageProvider.CODEC); + PlayerStorageProvider.CODEC.register("Disk", DiskPlayerStorageProvider.class, DiskPlayerStorageProvider.CODEC); + HytaleServerConfig.Module.BUILDER_CODEC_BUILDER + .addField( + new KeyedCodec<>("Modules", new MapCodec<>(HytaleServerConfig.Module.CODEC, ConcurrentHashMap::new, false)), + (o, m) -> o.modules = m, + o -> o.modules + ); + } + + public static class ConnectionTimeouts { + public static final Duration DEFAULT_INITIAL_TIMEOUT = Duration.of(10L, ChronoUnit.SECONDS); + public static final Duration DEFAULT_AUTH_TIMEOUT = Duration.of(30L, ChronoUnit.SECONDS); + public static final Duration DEFAULT_PLAY_TIMEOUT = Duration.of(1L, ChronoUnit.MINUTES); + public static final Codec CODEC = BuilderCodec.builder( + HytaleServerConfig.ConnectionTimeouts.class, HytaleServerConfig.ConnectionTimeouts::new + ) + .addField(new KeyedCodec<>("InitialTimeout", Codec.DURATION), (o, d) -> o.initialTimeout = d, o -> o.initialTimeout) + .addField(new KeyedCodec<>("AuthTimeout", Codec.DURATION), (o, d) -> o.authTimeout = d, o -> o.authTimeout) + .addField(new KeyedCodec<>("PlayTimeout", Codec.DURATION), (o, d) -> o.playTimeout = d, o -> o.playTimeout) + .addField( + new KeyedCodec<>("JoinTimeouts", new MapCodec<>(Codec.DURATION, ConcurrentHashMap::new, false)), (o, m) -> o.joinTimeouts = m, o -> o.joinTimeouts + ) + .build(); + private Duration initialTimeout; + private Duration authTimeout; + private Duration playTimeout; + private Map joinTimeouts = new ConcurrentHashMap<>(); + @Nonnull + private Map unmodifiableJoinTimeouts = Collections.unmodifiableMap(this.joinTimeouts); + private transient HytaleServerConfig hytaleServerConfig; + + public ConnectionTimeouts() { + } + + public ConnectionTimeouts(HytaleServerConfig hytaleServerConfig) { + this.hytaleServerConfig = hytaleServerConfig; + } + + public Duration getInitialTimeout() { + return this.initialTimeout != null ? this.initialTimeout : DEFAULT_INITIAL_TIMEOUT; + } + + public void setInitialTimeout(Duration initialTimeout) { + this.initialTimeout = initialTimeout; + this.hytaleServerConfig.markChanged(); + } + + public Duration getAuthTimeout() { + return this.authTimeout != null ? this.authTimeout : DEFAULT_AUTH_TIMEOUT; + } + + public void setAuthTimeout(Duration authTimeout) { + this.authTimeout = authTimeout; + this.hytaleServerConfig.markChanged(); + } + + public Duration getPlayTimeout() { + return this.playTimeout != null ? this.playTimeout : DEFAULT_PLAY_TIMEOUT; + } + + public void setPlayTimeout(Duration playTimeout) { + this.playTimeout = playTimeout; + this.hytaleServerConfig.markChanged(); + } + + @Nonnull + public Map getJoinTimeouts() { + return this.unmodifiableJoinTimeouts; + } + + public void setJoinTimeouts(Map joinTimeouts) { + this.joinTimeouts = joinTimeouts; + this.hytaleServerConfig.markChanged(); + } + } + + public static class Defaults { + public static final KeyedCodec WORLD = new KeyedCodec<>("World", Codec.STRING); + public static final KeyedCodec GAMEMODE = new KeyedCodec<>("GameMode", ProtocolCodecs.GAMEMODE_LEGACY); + public static final BuilderCodec CODEC = BuilderCodec.builder( + HytaleServerConfig.Defaults.class, HytaleServerConfig.Defaults::new + ) + .addField(WORLD, (o, i) -> o.world = i, o -> o.world) + .addField(GAMEMODE, (o, s) -> o.gameMode = s, o -> o.gameMode) + .build(); + private transient HytaleServerConfig hytaleServerConfig; + private String world = "default"; + private GameMode gameMode = GameMode.Adventure; + + private Defaults() { + } + + private Defaults(HytaleServerConfig hytaleServerConfig) { + this.hytaleServerConfig = hytaleServerConfig; + } + + public String getWorld() { + return this.world; + } + + public void setWorld(String world) { + this.world = world; + this.hytaleServerConfig.markChanged(); + } + + public GameMode getGameMode() { + return this.gameMode; + } + + public void setGameMode(GameMode gameMode) { + this.gameMode = gameMode; + this.hytaleServerConfig.markChanged(); + } + } + + public static class ModConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder( + HytaleServerConfig.ModConfig.class, HytaleServerConfig.ModConfig::new + ) + .append(new KeyedCodec<>("Enabled", Codec.BOOLEAN), (modConfig, enabled) -> modConfig.enabled = enabled, modConfig -> modConfig.enabled) + .add() + .append( + new KeyedCodec<>("RequiredVersion", SemverRange.CODEC), + (modConfig, semverRange) -> modConfig.requiredVersion = semverRange, + modConfig -> modConfig.requiredVersion + ) + .add() + .build(); + @Nullable + private Boolean enabled; + @Nullable + private SemverRange requiredVersion; + + public ModConfig() { + } + + @Nullable + public Boolean getEnabled() { + return this.enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + @Nullable + public SemverRange getRequiredVersion() { + return this.requiredVersion; + } + + public void setRequiredVersion(SemverRange requiredVersion) { + this.requiredVersion = requiredVersion; + } + + public static void setBoot(HytaleServerConfig serverConfig, PluginIdentifier identifier, boolean enabled) { + serverConfig.getModConfig().computeIfAbsent(identifier, id -> new HytaleServerConfig.ModConfig()).enabled = enabled; + } + } + + public static class Module { + @Nonnull + protected static BuilderCodec.Builder BUILDER_CODEC_BUILDER = BuilderCodec.builder( + HytaleServerConfig.Module.class, HytaleServerConfig.Module::new + ) + .addField(new KeyedCodec<>("Enabled", Codec.BOOLEAN), (o, i) -> o.enabled = i, o -> o.enabled); + @Nonnull + protected static BuilderCodec BUILDER_CODEC = BUILDER_CODEC_BUILDER.build(); + @Nonnull + public static final DocumentContainingCodec CODEC = new DocumentContainingCodec<>( + BUILDER_CODEC, (o, i) -> o.document = i, o -> o.document + ); + private transient HytaleServerConfig hytaleServerConfig; + private Boolean enabled; + @Nonnull + private Map modules = new ConcurrentHashMap<>(); + @Nonnull + private BsonDocument document = new BsonDocument(); + + private Module() { + } + + private Module(@Nonnull HytaleServerConfig hytaleServerConfig) { + this.hytaleServerConfig = hytaleServerConfig; + } + + public boolean isEnabled(boolean def) { + return this.enabled != null ? this.enabled : def; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + this.hytaleServerConfig.markChanged(); + } + + public Boolean getEnabled() { + return this.enabled; + } + + @Nonnull + public Map getModules() { + return Collections.unmodifiableMap(this.modules); + } + + @Nonnull + public HytaleServerConfig.Module getModule(@Nonnull String moduleName) { + return this.modules.computeIfAbsent(moduleName, k -> new HytaleServerConfig.Module(this.hytaleServerConfig)); + } + + public void setModules(@Nonnull Map modules) { + this.modules = modules; + this.hytaleServerConfig.markChanged(); + } + + @Nonnull + public BsonDocument getDocument() { + return this.document; + } + + @Nullable + public T decode(@Nonnull Codec codec) { + return codec.decode(this.document); + } + + public void encode(@Nonnull Codec codec, @Nonnull T t) { + this.document = codec.encode(t).asDocument(); + } + + @Nonnull + public Optional getData(@Nonnull KeyedCodec keyedCodec) { + return keyedCodec.get(this.document); + } + + @Nullable + public T getDataOrNull(@Nonnull KeyedCodec keyedCodec) { + return keyedCodec.getOrNull(this.document); + } + + public T getDataNow(@Nonnull KeyedCodec keyedCodec) { + return keyedCodec.getNow(this.document); + } + + public void put(@Nonnull KeyedCodec keyedCodec, T t) { + keyedCodec.put(this.document, t); + this.hytaleServerConfig.markChanged(); + } + + public void setDocument(@Nonnull BsonDocument document) { + this.document = document; + this.hytaleServerConfig.markChanged(); + } + + void setHytaleServerConfig(@Nonnull HytaleServerConfig hytaleServerConfig) { + this.hytaleServerConfig = hytaleServerConfig; + this.modules.values().forEach(module -> module.setHytaleServerConfig(hytaleServerConfig)); + } + } + + public static class RateLimitConfig { + public static final int DEFAULT_PACKETS_PER_SECOND = 2000; + public static final int DEFAULT_BURST_CAPACITY = 500; + public static final Codec CODEC = BuilderCodec.builder( + HytaleServerConfig.RateLimitConfig.class, HytaleServerConfig.RateLimitConfig::new + ) + .addField(new KeyedCodec<>("Enabled", Codec.BOOLEAN), (o, b) -> o.enabled = b, o -> o.enabled) + .addField(new KeyedCodec<>("PacketsPerSecond", Codec.INTEGER), (o, i) -> o.packetsPerSecond = i, o -> o.packetsPerSecond) + .addField(new KeyedCodec<>("BurstCapacity", Codec.INTEGER), (o, i) -> o.burstCapacity = i, o -> o.burstCapacity) + .build(); + private Boolean enabled; + private Integer packetsPerSecond; + private Integer burstCapacity; + transient HytaleServerConfig hytaleServerConfig; + + public RateLimitConfig() { + } + + public RateLimitConfig(HytaleServerConfig hytaleServerConfig) { + this.hytaleServerConfig = hytaleServerConfig; + } + + public boolean isEnabled() { + return this.enabled != null ? this.enabled : true; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + if (this.hytaleServerConfig != null) { + this.hytaleServerConfig.markChanged(); + } + } + + public int getPacketsPerSecond() { + return this.packetsPerSecond != null ? this.packetsPerSecond : 2000; + } + + public void setPacketsPerSecond(int packetsPerSecond) { + this.packetsPerSecond = packetsPerSecond; + if (this.hytaleServerConfig != null) { + this.hytaleServerConfig.markChanged(); + } + } + + public int getBurstCapacity() { + return this.burstCapacity != null ? this.burstCapacity : 500; + } + + public void setBurstCapacity(int burstCapacity) { + this.burstCapacity = burstCapacity; + if (this.hytaleServerConfig != null) { + this.hytaleServerConfig.markChanged(); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/Message.java b/src/com/hypixel/hytale/server/core/Message.java new file mode 100644 index 0000000..ba1b415 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/Message.java @@ -0,0 +1,508 @@ +package com.hypixel.hytale.server.core; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.EmptyExtraInfo; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.function.FunctionCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.BooleanSchema; +import com.hypixel.hytale.codec.schema.config.IntegerSchema; +import com.hypixel.hytale.codec.schema.config.NullSchema; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.protocol.BoolParamValue; +import com.hypixel.hytale.protocol.DoubleParamValue; +import com.hypixel.hytale.protocol.FormattedMessage; +import com.hypixel.hytale.protocol.IntParamValue; +import com.hypixel.hytale.protocol.LongParamValue; +import com.hypixel.hytale.protocol.MaybeBool; +import com.hypixel.hytale.protocol.ParamValue; +import com.hypixel.hytale.protocol.StringParamValue; +import com.hypixel.hytale.server.core.asset.util.ColorParseUtil; +import com.hypixel.hytale.server.core.modules.i18n.I18nModule; +import com.hypixel.hytale.server.core.util.MessageUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.awt.Color; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonBoolean; +import org.bson.BsonDouble; +import org.bson.BsonInt32; +import org.bson.BsonInt64; +import org.bson.BsonNull; +import org.bson.BsonString; +import org.bson.BsonValue; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class Message { + private static final BuilderCodec.Builder MESSAGE_CODEC_BUILDER = BuilderCodec.builder(FormattedMessage.class, FormattedMessage::new); + private static final BuilderCodec MESSAGE_CODEC = MESSAGE_CODEC_BUILDER.build(); + private static final Codec PARAM_CODEC = new Message.ParamValueCodec(); + private static final Codec MAYBE_BOOL_CODEC = new Message.MaybeBoolCodec(); + public static final FunctionCodec CODEC = new FunctionCodec<>(MESSAGE_CODEC, Message::new, Message::getFormattedMessage); + private final FormattedMessage message; + + protected Message(@Nonnull String message, boolean i18n) { + this(); + if (i18n) { + this.message.messageId = message; + } else { + this.message.rawText = message; + } + } + + protected Message() { + this.message = new FormattedMessage(); + } + + public Message(@Nonnull FormattedMessage message) { + this.message = message; + } + + @Nonnull + public Message param(@Nonnull String key, @Nonnull String value) { + if (this.message.params == null) { + this.message.params = new HashMap<>(); + } + + StringParamValue val = new StringParamValue(); + val.value = value; + this.message.params.put(key, val); + return this; + } + + @Nonnull + public Message param(@Nonnull String key, boolean value) { + if (this.message.params == null) { + this.message.params = new HashMap<>(); + } + + BoolParamValue val = new BoolParamValue(); + val.value = value; + this.message.params.put(key, val); + return this; + } + + @Nonnull + public Message param(@Nonnull String key, double value) { + if (this.message.params == null) { + this.message.params = new HashMap<>(); + } + + DoubleParamValue val = new DoubleParamValue(); + val.value = value; + this.message.params.put(key, val); + return this; + } + + @Nonnull + public Message param(@Nonnull String key, int value) { + if (this.message.params == null) { + this.message.params = new HashMap<>(); + } + + IntParamValue val = new IntParamValue(); + val.value = value; + this.message.params.put(key, val); + return this; + } + + @Nonnull + public Message param(@Nonnull String key, long value) { + if (this.message.params == null) { + this.message.params = new HashMap<>(); + } + + LongParamValue val = new LongParamValue(); + val.value = value; + this.message.params.put(key, val); + return this; + } + + @Nonnull + public Message param(@Nonnull String key, float value) { + if (this.message.params == null) { + this.message.params = new HashMap<>(); + } + + DoubleParamValue val = new DoubleParamValue(); + val.value = value; + this.message.params.put(key, val); + return this; + } + + @Nonnull + public Message param(@Nonnull String key, @Nonnull Message formattedMessage) { + if (this.message.messageParams == null) { + this.message.messageParams = new HashMap<>(); + } + + this.message.messageParams.put(key, formattedMessage.message); + return this; + } + + @Nonnull + public Message bold(boolean bold) { + this.message.bold = bold ? MaybeBool.True : MaybeBool.False; + return this; + } + + @Nonnull + public Message italic(boolean italic) { + this.message.italic = italic ? MaybeBool.True : MaybeBool.False; + return this; + } + + @Nonnull + public Message monospace(boolean monospace) { + this.message.monospace = monospace ? MaybeBool.True : MaybeBool.False; + return this; + } + + @Nonnull + public Message color(@Nonnull String color) { + this.message.color = color; + return this; + } + + @Nonnull + public Message color(@Nonnull Color color) { + this.message.color = ColorParseUtil.colorToHex(color); + return this; + } + + @Nonnull + public Message link(@Nonnull String url) { + this.message.link = url; + return this; + } + + @Nonnull + public Message insert(@Nonnull Message formattedMessage) { + this.message.children = ArrayUtil.append(this.message.children, formattedMessage.message); + return this; + } + + @Nonnull + public Message insert(@Nonnull String message) { + return this.insert(raw(message)); + } + + @Nonnull + public Message insertAll(@Nonnull Message... formattedMessages) { + int offset = 0; + if (this.message.children != null) { + offset = this.message.children.length; + this.message.children = Arrays.copyOf(this.message.children, this.message.children.length + formattedMessages.length); + } else { + this.message.children = new FormattedMessage[formattedMessages.length]; + } + + for (Message formattedMessage : formattedMessages) { + this.message.children[offset++] = formattedMessage.message; + } + + return this; + } + + @Nonnull + public Message insertAll(@Nonnull List formattedMessages) { + int offset = 0; + if (this.message.children != null) { + offset = this.message.children.length; + this.message.children = Arrays.copyOf(this.message.children, this.message.children.length + formattedMessages.size()); + } else { + this.message.children = new FormattedMessage[formattedMessages.size()]; + } + + for (Message formattedMessage : formattedMessages) { + this.message.children[offset++] = formattedMessage.message; + } + + return this; + } + + @Nullable + public String getRawText() { + return this.message.rawText; + } + + @Nullable + public String getMessageId() { + return this.message.messageId; + } + + @Nullable + public String getColor() { + return this.message.color; + } + + @Nonnull + public List getChildren() { + if (this.message.children == null) { + return Collections.emptyList(); + } else { + List children = new ObjectArrayList<>(); + + for (FormattedMessage value : this.message.children) { + children.add(new Message(value)); + } + + return children; + } + } + + @Nonnull + public String getAnsiMessage() { + String rawText = this.getRawText(); + if (rawText != null) { + return rawText; + } else { + String messageId = this.getMessageId(); + if (messageId == null) { + return ""; + } else { + String message = I18nModule.get().getMessage("en-US", messageId); + if (message != null) { + return MessageUtil.formatText(message, this.message.params, this.message.messageParams); + } else { + StringBuilder rawMessage = new StringBuilder(messageId); + if (this.message.params != null) { + rawMessage.append(this.message.params); + } + + if (this.message.messageParams != null) { + for (Entry p : this.message.messageParams.entrySet()) { + rawMessage.append(p.getValue()).append("=").append(new Message(p.getValue()).getAnsiMessage()); + } + } + + return rawMessage.toString(); + } + } + } + } + + public FormattedMessage getFormattedMessage() { + return this.message; + } + + @Override + public String toString() { + return this.message.toString(); + } + + @Nonnull + public static Message empty() { + return new Message(); + } + + @Nonnull + public static Message translation(@Nonnull String messageId) { + return new Message(messageId, true); + } + + @Nonnull + public static Message raw(@Nonnull String message) { + return new Message(message, false); + } + + @Nonnull + public static Message parse(@Nonnull String message) { + try { + return CODEC.decodeJson(new RawJsonReader(message.toCharArray()), EmptyExtraInfo.EMPTY); + } catch (IOException var2) { + throw SneakyThrow.sneakyThrow(var2); + } + } + + @Nonnull + public static Message join(@Nonnull Message... messages) { + return new Message().insertAll(messages); + } + + static { + MESSAGE_CODEC_BUILDER.appendInherited(new KeyedCodec<>("RawText", Codec.STRING), (o, v) -> o.rawText = v, o -> o.rawText, (o, p) -> o.rawText = p.rawText) + .add() + .appendInherited(new KeyedCodec<>("MessageId", Codec.STRING), (o, v) -> o.messageId = v, o -> o.messageId, (o, p) -> o.messageId = p.messageId) + .add() + .appendInherited( + new KeyedCodec<>("Params", new MapCodec<>(PARAM_CODEC, HashMap::new)), (o, v) -> o.params = v, o -> o.params, (o, p) -> o.params = p.params + ) + .add() + .appendInherited( + new KeyedCodec<>("MessageParams", new MapCodec<>(MESSAGE_CODEC, HashMap::new)), + (o, v) -> o.messageParams = v, + o -> o.messageParams, + (o, p) -> o.messageParams = p.messageParams + ) + .add() + .appendInherited( + new KeyedCodec<>("Children", new ArrayCodec<>(MESSAGE_CODEC, FormattedMessage[]::new)), + (o, v) -> o.children = v, + o -> o.children, + (o, p) -> o.children = p.children + ) + .add() + .appendInherited(new KeyedCodec<>("Bold", MAYBE_BOOL_CODEC), (o, v) -> o.bold = v != null ? v : MaybeBool.Null, o -> o.bold, (o, p) -> o.bold = p.bold) + .add() + .appendInherited( + new KeyedCodec<>("Italic", MAYBE_BOOL_CODEC), (o, v) -> o.italic = v != null ? v : MaybeBool.Null, o -> o.italic, (o, p) -> o.italic = p.italic + ) + .add() + .appendInherited( + new KeyedCodec<>("Monospace", MAYBE_BOOL_CODEC), + (o, v) -> o.monospace = v != null ? v : MaybeBool.Null, + o -> o.monospace, + (o, p) -> o.monospace = p.monospace + ) + .add() + .appendInherited( + new KeyedCodec<>("Underline", MAYBE_BOOL_CODEC), + (o, v) -> o.underlined = v != null ? v : MaybeBool.Null, + o -> o.underlined, + (o, p) -> o.underlined = p.underlined + ) + .add() + .appendInherited(new KeyedCodec<>("Color", Codec.STRING), (o, v) -> o.color = v, o -> o.color, (o, p) -> o.color = p.color) + .add() + .appendInherited(new KeyedCodec<>("Link", Codec.STRING), (o, v) -> o.link = v, o -> o.link, (o, p) -> o.link = p.link) + .add(); + } + + private static class MaybeBoolCodec implements Codec { + private MaybeBoolCodec() { + } + + @Nullable + public MaybeBool decode(BsonValue bsonValue, ExtraInfo extraInfo) { + if (bsonValue.isNull()) { + return MaybeBool.Null; + } else { + return bsonValue.asBoolean().getValue() ? MaybeBool.True : MaybeBool.False; + } + } + + public BsonValue encode(MaybeBool maybeBool, ExtraInfo extraInfo) { + return (BsonValue)(switch (maybeBool) { + case Null -> BsonNull.VALUE; + case False -> BsonBoolean.FALSE; + case True -> BsonBoolean.TRUE; + }); + } + + @NullableDecl + public MaybeBool decodeJson(@NonNullDecl RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + if (reader.peekFor('n')) { + if (!reader.tryConsume("null")) { + throw new IllegalArgumentException("Invalid null value"); + } else { + return MaybeBool.Null; + } + } else if (reader.peekFor('N')) { + if (!reader.tryConsume("NULL")) { + throw new IllegalArgumentException("Invalid null value"); + } else { + return MaybeBool.Null; + } + } else { + return reader.readBooleanValue() ? MaybeBool.True : MaybeBool.False; + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return Schema.anyOf(new BooleanSchema(), new NullSchema()); + } + } + + private static class ParamValueCodec implements Codec { + private ParamValueCodec() { + } + + @Nullable + public ParamValue decode(BsonValue bsonValue, ExtraInfo extraInfo) { + return (ParamValue)(switch (bsonValue.getBsonType()) { + case DOUBLE -> { + DoubleParamValue value = new DoubleParamValue(); + value.value = bsonValue.asDouble().getValue(); + yield value; + } + case STRING -> { + StringParamValue value = new StringParamValue(); + value.value = bsonValue.asString().getValue(); + yield value; + } + case BOOLEAN -> { + BoolParamValue value = new BoolParamValue(); + value.value = bsonValue.asBoolean().getValue(); + yield value; + } + case INT32 -> { + IntParamValue value = new IntParamValue(); + value.value = bsonValue.asInt32().getValue(); + yield value; + } + case INT64 -> { + LongParamValue value = new LongParamValue(); + value.value = bsonValue.asInt64().getValue(); + yield value; + } + default -> throw new IllegalArgumentException("Unsupported bson type: " + bsonValue.getBsonType()); + }); + } + + public BsonValue encode(ParamValue paramValue, ExtraInfo extraInfo) { + return (BsonValue)(switch (paramValue) { + case StringParamValue s -> new BsonString(s.value); + case BoolParamValue b -> BsonBoolean.valueOf(b.value); + case DoubleParamValue d -> new BsonDouble(d.value); + case IntParamValue i -> new BsonInt32(i.value); + case LongParamValue l -> new BsonInt64(l.value); + default -> throw new IllegalArgumentException("Unknown ParamValue type: " + paramValue.getClass()); + }); + } + + @Nullable + public ParamValue decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + if (reader.peekFor('"')) { + StringParamValue param = new StringParamValue(); + param.value = reader.readString(); + return param; + } else if (!reader.peekFor('f') && !reader.peekFor('t')) { + double value = reader.readDoubleValue(); + DoubleParamValue param = new DoubleParamValue(); + param.value = value; + return param; + } else if (!reader.tryConsume("false")) { + throw new IllegalArgumentException("Invalid boolean value"); + } else { + BoolParamValue param = new BoolParamValue(); + param.value = reader.readBooleanValue(); + return param; + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return Schema.anyOf(new BooleanSchema(), new NumberSchema(), new IntegerSchema(), new StringSchema()); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/NameMatching.java b/src/com/hypixel/hytale/server/core/NameMatching.java new file mode 100644 index 0000000..5cbd285 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/NameMatching.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.server.core; + +import java.util.Collection; +import java.util.Comparator; +import java.util.function.BiPredicate; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public enum NameMatching { + EXACT((s1, s2) -> s1.equals(s2) ? 0 : Integer.MIN_VALUE, String::equals), + EXACT_IGNORE_CASE((s1, s2) -> s1.equalsIgnoreCase(s2) ? 0 : Integer.MIN_VALUE, String::equalsIgnoreCase), + STARTS_WITH((s1, s2) -> s1.startsWith(s2) ? s1.length() - s2.length() : Integer.MIN_VALUE, String::equals), + STARTS_WITH_IGNORE_CASE((s1, s2) -> s1.toLowerCase().startsWith(s2.toLowerCase()) ? s1.length() - s2.length() : Integer.MIN_VALUE, String::equalsIgnoreCase); + + @Nonnull + public static NameMatching DEFAULT = STARTS_WITH_IGNORE_CASE; + private final Comparator comparator; + private final BiPredicate equality; + + private NameMatching(Comparator comparator, BiPredicate equality) { + this.comparator = comparator; + this.equality = equality; + } + + public Comparator getComparator() { + return this.comparator; + } + + @Nullable + public T find(@Nonnull Collection players, String value, @Nonnull Function getter) { + return find(players, value, getter, this.comparator, this.equality); + } + + @Nullable + public static T find( + @Nonnull Collection players, + String value, + @Nonnull Function getter, + @Nonnull Comparator comparator, + @Nonnull BiPredicate equality + ) { + T closest = null; + int highestScore = Integer.MIN_VALUE; + + for (T player : players) { + String name = getter.apply(player); + if (equality.test(name, value)) { + return player; + } + + int comparison = comparator.compare(name, value); + if (comparison > highestScore) { + highestScore = comparison; + closest = player; + } + } + + return highestScore == Integer.MIN_VALUE ? null : closest; + } +} diff --git a/src/com/hypixel/hytale/server/core/Options.java b/src/com/hypixel/hytale/server/core/Options.java new file mode 100644 index 0000000..851251e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/Options.java @@ -0,0 +1,389 @@ +package com.hypixel.hytale.server.core; + +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.common.util.java.ManifestUtil; +import com.hypixel.hytale.logger.backend.HytaleLoggerBackend; +import com.hypixel.hytale.server.core.io.transport.TransportType; +import com.hypixel.hytale.server.core.universe.world.ValidationOption; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.Map.Entry; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import joptsimple.OptionParser; +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import joptsimple.ValueConversionException; +import joptsimple.ValueConverter; + +public class Options { + public static final OptionParser PARSER = new OptionParser(); + public static final OptionSpec HELP = PARSER.accepts("help", "Print's this message.").forHelp(); + public static final OptionSpec VERSION = PARSER.accepts("version", "Prints version information."); + public static final OptionSpec BARE = PARSER.accepts( + "bare", + "Runs the server bare. For example without loading worlds, binding to ports or creating directories. (Note: Plugins will still be loaded which may not respect this flag)" + ); + public static final OptionSpec> LOG_LEVELS = PARSER.accepts("log", "Sets the logger level.") + .withRequiredArg() + .withValuesSeparatedBy(',') + .withValuesConvertedBy(new Options.LevelValueConverter()); + public static final OptionSpec BIND = PARSER.acceptsAll(List.of("b", "bind"), "Port to listen on") + .withRequiredArg() + .withValuesSeparatedBy(',') + .withValuesConvertedBy(new Options.SocketAddressValueConverter()) + .defaultsTo(new InetSocketAddress(5520)); + public static final OptionSpec TRANSPORT = PARSER.acceptsAll(List.of("t", "transport"), "Transport type") + .withRequiredArg() + .ofType(TransportType.class) + .defaultsTo(TransportType.QUIC); + public static final OptionSpec DISABLE_CPB_BUILD = PARSER.accepts("disable-cpb-build", "Disables building of compact prefab buffers"); + public static final OptionSpec PREFAB_CACHE_DIRECTORY = PARSER.accepts("prefab-cache", "Prefab cache directory for immutable assets") + .withRequiredArg() + .withValuesConvertedBy(new Options.PathConverter(Options.PathConverter.PathType.ANY)); + public static final OptionSpec ASSET_DIRECTORY = PARSER.acceptsAll(List.of("assets"), "Asset directory") + .withRequiredArg() + .withValuesConvertedBy(new Options.PathConverter(Options.PathConverter.PathType.DIR_OR_ZIP)) + .defaultsTo(Paths.get("../HytaleAssets")); + public static final OptionSpec MODS_DIRECTORIES = PARSER.acceptsAll(List.of("mods"), "Additional mods directories") + .withRequiredArg() + .withValuesSeparatedBy(',') + .withValuesConvertedBy(new Options.PathConverter(Options.PathConverter.PathType.DIR)); + public static final OptionSpec ACCEPT_EARLY_PLUGINS = PARSER.accepts( + "accept-early-plugins", "You acknowledge that loading early plugins is unsupported and may cause stability issues." + ); + public static final OptionSpec EARLY_PLUGIN_DIRECTORIES = PARSER.accepts("early-plugins", "Additional early plugin directories to load from") + .withRequiredArg() + .withValuesSeparatedBy(',') + .withValuesConvertedBy(new Options.PathConverter(Options.PathConverter.PathType.DIR)); + public static final OptionSpec VALIDATE_ASSETS = PARSER.accepts( + "validate-assets", "Causes the server to exit with an error code if any assets are invalid." + ); + public static final OptionSpec VALIDATE_PREFABS = PARSER.accepts( + "validate-prefabs", "Causes the server to exit with an error code if any prefabs are invalid." + ) + .withOptionalArg() + .withValuesSeparatedBy(',') + .ofType(ValidationOption.class); + public static final OptionSpec VALIDATE_WORLD_GEN = PARSER.accepts( + "validate-world-gen", "Causes the server to exit with an error code if default world gen is invalid." + ); + public static final OptionSpec SHUTDOWN_AFTER_VALIDATE = PARSER.accepts( + "shutdown-after-validate", "Automatically shutdown the server after asset and/or prefab validation." + ); + public static final OptionSpec GENERATE_SCHEMA = PARSER.accepts( + "generate-schema", "Causes the server generate schema, save it into the assets directory and then exit" + ); + public static final OptionSpec WORLD_GEN_DIRECTORY = PARSER.accepts("world-gen", "World gen directory") + .withRequiredArg() + .withValuesConvertedBy(new Options.PathConverter(Options.PathConverter.PathType.DIR)); + public static final OptionSpec DISABLE_FILE_WATCHER = PARSER.accepts("disable-file-watcher"); + public static final OptionSpec DISABLE_SENTRY = PARSER.accepts("disable-sentry"); + public static final OptionSpec DISABLE_ASSET_COMPARE = PARSER.accepts("disable-asset-compare"); + public static final OptionSpec BACKUP = PARSER.accepts("backup"); + public static final OptionSpec BACKUP_FREQUENCY_MINUTES = PARSER.accepts("backup-frequency").withRequiredArg().ofType(Integer.class).defaultsTo(30); + public static final OptionSpec BACKUP_DIRECTORY = PARSER.accepts("backup-dir") + .requiredIf(BACKUP) + .withRequiredArg() + .withValuesConvertedBy(new Options.PathConverter(Options.PathConverter.PathType.DIR)); + public static final OptionSpec BACKUP_MAX_COUNT = PARSER.accepts("backup-max-count").withRequiredArg().ofType(Integer.class).defaultsTo(5); + public static final OptionSpec SINGLEPLAYER = PARSER.accepts("singleplayer"); + public static final OptionSpec OWNER_NAME = PARSER.accepts("owner-name").withRequiredArg(); + public static final OptionSpec OWNER_UUID = PARSER.accepts("owner-uuid").withRequiredArg().withValuesConvertedBy(new Options.UUIDConverter()); + public static final OptionSpec CLIENT_PID = PARSER.accepts("client-pid").withRequiredArg().ofType(Integer.class); + public static final OptionSpec UNIVERSE = PARSER.accepts("universe") + .withRequiredArg() + .withValuesConvertedBy(new Options.PathConverter(Options.PathConverter.PathType.DIR)); + public static final OptionSpec EVENT_DEBUG = PARSER.accepts("event-debug"); + public static final OptionSpec FORCE_NETWORK_FLUSH = PARSER.accepts("force-network-flush").withRequiredArg().ofType(Boolean.class).defaultsTo(true); + public static final OptionSpec> MIGRATIONS = PARSER.accepts("migrations", "The migrations to run") + .withRequiredArg() + .withValuesConvertedBy(new Options.StringToPathMapConverter()); + public static final OptionSpec MIGRATE_WORLDS = PARSER.accepts("migrate-worlds", "Worlds to migrate") + .availableIf(MIGRATIONS) + .withRequiredArg() + .withValuesSeparatedBy(','); + public static final OptionSpec BOOT_COMMAND = PARSER.accepts( + "boot-command", "Runs command on boot. If multiple commands are provided they are executed synchronously in order." + ) + .withRequiredArg() + .withValuesSeparatedBy(','); + public static final String ALLOW_SELF_OP_COMMAND_STRING = "allow-op"; + public static final OptionSpec ALLOW_SELF_OP_COMMAND = PARSER.accepts("allow-op"); + public static final OptionSpec AUTH_MODE = PARSER.accepts("auth-mode", "Authentication mode") + .withRequiredArg() + .withValuesConvertedBy(new Options.AuthModeConverter()) + .defaultsTo(Options.AuthMode.AUTHENTICATED); + public static final OptionSpec SESSION_TOKEN = PARSER.accepts("session-token", "Session token for Session Service API") + .withRequiredArg() + .ofType(String.class); + public static final OptionSpec IDENTITY_TOKEN = PARSER.accepts("identity-token", "Identity token (JWT)").withRequiredArg().ofType(String.class); + private static OptionSet optionSet; + + public Options() { + } + + public static OptionSet getOptionSet() { + return optionSet; + } + + public static T getOrDefault(OptionSpec optionSpec, @Nonnull OptionSet optionSet, T def) { + return !optionSet.has(optionSpec) ? def : optionSet.valueOf(optionSpec); + } + + public static boolean parse(String[] args) throws IOException { + optionSet = PARSER.parse(args); + if (optionSet.has(HELP)) { + PARSER.printHelpOn(System.out); + return true; + } else if (optionSet.has(VERSION)) { + String version = ManifestUtil.getImplementationVersion(); + String patchline = ManifestUtil.getPatchline(); + String environment = "release"; + if ("release".equals(patchline)) { + System.out.println("HytaleServer v" + version + " (" + patchline + ")"); + } else { + System.out.println("HytaleServer v" + version + " (" + patchline + ", " + environment + ")"); + } + + return true; + } else { + List nonOptionArguments = optionSet.nonOptionArguments(); + if (!nonOptionArguments.isEmpty()) { + System.err.println("Unknown arguments: " + nonOptionArguments); + System.exit(1); + return true; + } else { + if (optionSet.has(LOG_LEVELS)) { + HytaleLoggerBackend.loadLevels(optionSet.valuesOf(LOG_LEVELS)); + } else if (optionSet.has(SHUTDOWN_AFTER_VALIDATE)) { + HytaleLoggerBackend.loadLevels(List.of(Map.entry("", Level.WARNING))); + } + + return false; + } + } + } + + public static enum AuthMode { + AUTHENTICATED, + OFFLINE, + INSECURE; + + private AuthMode() { + } + } + + private static class AuthModeConverter implements ValueConverter { + private AuthModeConverter() { + } + + public Options.AuthMode convert(String value) { + return Options.AuthMode.valueOf(value.toUpperCase()); + } + + @Override + public Class valueType() { + return Options.AuthMode.class; + } + + @Override + public String valuePattern() { + return "authenticated|offline|insecure"; + } + } + + public static class LevelValueConverter implements ValueConverter> { + private static final Entry ENTRY = Map.entry("", Level.ALL); + + public LevelValueConverter() { + } + + @Nonnull + public Entry convert(@Nonnull String value) { + if (!value.contains(":")) { + return Map.entry("", Level.parse(value.toUpperCase())); + } else { + String[] split = value.split(":"); + return Map.entry(split[0], Level.parse(split[1].toUpperCase())); + } + } + + @Nonnull + @Override + public Class> valueType() { + return (Class>)ENTRY.getClass(); + } + + @Nullable + @Override + public String valuePattern() { + return null; + } + } + + public static class PathConverter implements ValueConverter { + private final Options.PathConverter.PathType pathType; + + public PathConverter(Options.PathConverter.PathType pathType) { + this.pathType = pathType; + } + + @Nonnull + public Path convert(@Nonnull String s) { + try { + Path path = PathUtil.get(s); + if (Files.exists(path)) { + switch (this.pathType) { + case FILE: + if (!Files.isRegularFile(path)) { + throw new ValueConversionException("Path must be a file!"); + } + break; + case DIR: + if (!Files.isDirectory(path)) { + throw new ValueConversionException("Path must be a directory!"); + } + break; + case DIR_OR_ZIP: + if (!Files.isDirectory(path) && (!Files.exists(path) || !path.getFileName().toString().endsWith(".zip"))) { + throw new ValueConversionException("Path must be a directory or zip!"); + } + } + } + + return path; + } catch (InvalidPathException var3) { + throw new ValueConversionException("Failed to parse '" + s + "' to path!", var3); + } + } + + @Nonnull + @Override + public Class valueType() { + return Path.class; + } + + @Nullable + @Override + public String valuePattern() { + return null; + } + + public static enum PathType { + FILE, + DIR, + DIR_OR_ZIP, + ANY; + + private PathType() { + } + } + } + + public static class SocketAddressValueConverter implements ValueConverter { + public SocketAddressValueConverter() { + } + + @Nonnull + public InetSocketAddress convert(@Nonnull String value) { + if (value.contains(":")) { + String[] split = value.split(":"); + return new InetSocketAddress(split[0], Integer.parseInt(split[1])); + } else { + try { + return new InetSocketAddress(Integer.parseInt(value)); + } catch (NumberFormatException var3) { + return new InetSocketAddress(value, 5520); + } + } + } + + @Nonnull + @Override + public Class valueType() { + return InetSocketAddress.class; + } + + @Nullable + @Override + public String valuePattern() { + return null; + } + } + + public static class StringToPathMapConverter implements ValueConverter> { + private static final Map MAP = new Object2ObjectOpenHashMap<>(); + + public StringToPathMapConverter() { + } + + @Nonnull + public Map convert(@Nonnull String value) { + HashMap map = new HashMap<>(); + String[] strings = value.split(","); + + for (String string : strings) { + String[] split = string.split("="); + if (split.length == 2) { + if (map.containsKey(split[0])) { + throw new ValueConversionException("String '" + split[0] + "' has already been specified!"); + } + + Path path = PathUtil.get(split[1]); + if (!Files.exists(path)) { + throw new ValueConversionException("No file found for '" + split[1] + "'!"); + } + + map.put(split[0], path); + } + } + + return map; + } + + @Nonnull + @Override + public Class> valueType() { + return (Class>)MAP.getClass(); + } + + @Nullable + @Override + public String valuePattern() { + return null; + } + } + + public static class UUIDConverter implements ValueConverter { + public UUIDConverter() { + } + + @Nonnull + public UUID convert(@Nonnull String s) { + return UUID.fromString(s); + } + + @Nonnull + @Override + public Class valueType() { + return UUID.class; + } + + @Nullable + @Override + public String valuePattern() { + return null; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/ShutdownReason.java b/src/com/hypixel/hytale/server/core/ShutdownReason.java new file mode 100644 index 0000000..b931ad4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/ShutdownReason.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.core; + +import javax.annotation.Nonnull; + +public class ShutdownReason { + public static final ShutdownReason SIGINT = new ShutdownReason(130); + public static final ShutdownReason SHUTDOWN = new ShutdownReason(0); + public static final ShutdownReason CRASH = new ShutdownReason(1); + public static final ShutdownReason AUTH_FAILED = new ShutdownReason(2); + public static final ShutdownReason WORLD_GEN = new ShutdownReason(3); + public static final ShutdownReason CLIENT_GONE = new ShutdownReason(4); + public static final ShutdownReason MISSING_REQUIRED_PLUGIN = new ShutdownReason(5); + public static final ShutdownReason VALIDATE_ERROR = new ShutdownReason(6); + private final int exitCode; + private final String message; + + public ShutdownReason(int exitCode) { + this(exitCode, null); + } + + public ShutdownReason(int exitCode, String message) { + this.exitCode = exitCode; + this.message = message; + } + + public int getExitCode() { + return this.exitCode; + } + + public String getMessage() { + return this.message; + } + + @Nonnull + public ShutdownReason withMessage(String message) { + return new ShutdownReason(this.exitCode, message); + } + + @Nonnull + @Override + public String toString() { + return "ShutdownReason{exitCode=" + this.exitCode + ", message='" + this.message + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/AssetModule.java b/src/com/hypixel/hytale/server/core/asset/AssetModule.java new file mode 100644 index 0000000..598a4b4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/AssetModule.java @@ -0,0 +1,457 @@ +package com.hypixel.hytale.server.core.asset; + +import com.hypixel.hytale.assetstore.AssetLoadResult; +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.event.RegisterAssetStoreEvent; +import com.hypixel.hytale.assetstore.event.RemoveAssetStoreEvent; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.common.plugin.PluginIdentifier; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.event.EventPriority; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.HytaleServerConfig; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.asset.monitor.AssetMonitor; +import com.hypixel.hytale.server.core.asset.type.gameplay.respawn.HomeOrSpawnPoint; +import com.hypixel.hytale.server.core.asset.type.gameplay.respawn.RespawnController; +import com.hypixel.hytale.server.core.asset.type.gameplay.respawn.WorldSpawnPoint; +import com.hypixel.hytale.server.core.asset.type.item.DroplistCommand; +import com.hypixel.hytale.server.core.event.events.BootEvent; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.plugin.PluginManager; +import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen; +import com.hypixel.hytale.server.core.universe.world.worldgen.ValidatableWorldGen; +import com.hypixel.hytale.server.core.universe.world.worldgen.WorldGenLoadException; +import com.hypixel.hytale.server.core.universe.world.worldgen.provider.IWorldGenProvider; +import com.hypixel.hytale.server.core.universe.world.worldmap.IWorldMap; +import com.hypixel.hytale.server.core.universe.world.worldmap.provider.IWorldMapProvider; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(AssetModule.class).build(); + private static AssetModule instance; + @Nullable + private AssetMonitor assetMonitor; + @Nonnull + private final List assetPacks = new CopyOnWriteArrayList<>(); + private boolean hasLoaded = false; + private final List> pendingAssetStores = new CopyOnWriteArrayList<>(); + + public static AssetModule get() { + return instance; + } + + public AssetModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + if (Options.getOptionSet().has(Options.DISABLE_FILE_WATCHER)) { + this.getLogger().at(Level.WARNING).log("Not running asset watcher because --disable-file-watcher was set"); + } else { + try { + this.assetMonitor = new AssetMonitor(); + this.getLogger().at(Level.INFO).log("Asset monitor enabled!"); + } catch (IOException var4) { + this.getLogger().at(Level.SEVERE).withCause(var4).log("Failed to create asset monitor!"); + } + } + + for (Path path : Options.getOptionSet().valuesOf(Options.ASSET_DIRECTORY)) { + this.loadAndRegisterPack(path); + } + + this.loadPacksFromDirectory(PluginManager.MODS_PATH); + + for (Path modsPath : Options.getOptionSet().valuesOf(Options.MODS_DIRECTORIES)) { + this.loadPacksFromDirectory(modsPath); + } + + this.getEventRegistry().register((short)-16, LoadAssetEvent.class, event -> { + if (this.hasLoaded) { + throw new IllegalStateException("LoadAssetEvent has already been dispatched"); + } else { + AssetRegistry.ASSET_LOCK.writeLock().lock(); + + try { + this.hasLoaded = true; + AssetRegistryLoader.preLoadAssets(event); + + for (AssetPack pack : this.assetPacks) { + AssetRegistryLoader.loadAssets(event, pack); + } + } finally { + AssetRegistry.ASSET_LOCK.writeLock().unlock(); + } + } + }); + this.getEventRegistry().register((short)-16, AssetPackRegisterEvent.class, event -> AssetRegistryLoader.loadAssets(null, event.getAssetPack())); + this.getEventRegistry().register(AssetPackUnregisterEvent.class, event -> { + for (AssetStore assetStore : AssetRegistry.getStoreMap().values()) { + assetStore.removeAssetPack(event.getAssetPack().getName()); + } + }); + this.getEventRegistry().register(LoadAssetEvent.class, AssetModule::validateWorldGen); + this.getEventRegistry().register(EventPriority.FIRST, LoadAssetEvent.class, SneakyThrow.sneakyConsumer(AssetRegistryLoader::writeSchemas)); + this.getEventRegistry().register(RegisterAssetStoreEvent.class, this::onNewStore); + this.getEventRegistry().register(RemoveAssetStoreEvent.class, this::onRemoveStore); + this.getEventRegistry().registerGlobal(BootEvent.class, event -> { + StringBuilder sb = new StringBuilder("Total Loaded Assets: "); + AssetStore[] assetStores = AssetRegistry.getStoreMap().values().toArray(AssetStore[]::new); + Arrays.sort(assetStores, Comparator.comparingInt(o -> o.getAssetMap().getAssetCount())); + + for (int i = assetStores.length - 1; i >= 0; i--) { + AssetStore assetStore = assetStores[i]; + String simpleName = assetStore.getAssetClass().getSimpleName(); + int assetCount = assetStore.getAssetMap().getAssetCount(); + sb.append(simpleName).append(": ").append(assetCount).append(", "); + } + + sb.setLength(sb.length() - 2); + this.getLogger().at(Level.INFO).log(sb.toString()); + }); + RespawnController.CODEC.register("HomeOrSpawnPoint", HomeOrSpawnPoint.class, HomeOrSpawnPoint.CODEC); + RespawnController.CODEC.register("WorldSpawnPoint", WorldSpawnPoint.class, WorldSpawnPoint.CODEC); + this.getCommandRegistry().registerCommand(new DroplistCommand()); + } + + @Override + protected void shutdown() { + if (this.assetMonitor != null) { + this.assetMonitor.shutdown(); + this.assetMonitor = null; + } + + for (AssetPack pack : this.assetPacks) { + if (pack.getFileSystem() != null) { + try { + pack.getFileSystem().close(); + } catch (IOException var4) { + this.getLogger().at(Level.WARNING).withCause(var4).log("Failed to close asset pack filesystem: %s", pack.getName()); + } + } + } + + this.assetPacks.clear(); + } + + @Nonnull + public AssetPack getBaseAssetPack() { + return this.assetPacks.getFirst(); + } + + @Nonnull + public List getAssetPacks() { + return this.assetPacks; + } + + @Nullable + public AssetMonitor getAssetMonitor() { + return this.assetMonitor; + } + + @Nullable + public AssetPack findAssetPackForPath(Path path) { + path = path.toAbsolutePath().normalize(); + + for (AssetPack pack : this.assetPacks) { + if (path.startsWith(pack.getRoot())) { + return pack; + } + } + + return null; + } + + public boolean isAssetPathImmutable(@Nonnull Path path) { + AssetPack pack = this.findAssetPackForPath(path); + return pack != null && pack.isImmutable(); + } + + @Nullable + private PluginManifest loadPackManifest(Path packPath) throws IOException { + if (packPath.getFileName().toString().toLowerCase().endsWith(".zip")) { + try (FileSystem fs = FileSystems.newFileSystem(packPath, (ClassLoader)null)) { + Path manifestPath = fs.getPath("manifest.json"); + if (Files.exists(manifestPath)) { + try (BufferedReader reader = Files.newBufferedReader(manifestPath, StandardCharsets.UTF_8)) { + char[] buffer = RawJsonReader.READ_BUFFER.get(); + RawJsonReader rawJsonReader = new RawJsonReader(reader, buffer); + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + PluginManifest manifest = PluginManifest.CODEC.decodeJson(rawJsonReader, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(this.getLogger()); + return manifest; + } + } + + return null; + } + } else if (Files.isDirectory(packPath)) { + Path manifestPath = packPath.resolve("manifest.json"); + if (Files.exists(manifestPath)) { + PluginManifest manifest; + try (FileReader reader = new FileReader(manifestPath.toFile(), StandardCharsets.UTF_8)) { + char[] buffer = RawJsonReader.READ_BUFFER.get(); + RawJsonReader rawJsonReader = new RawJsonReader(reader, buffer); + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + PluginManifest manifestx = PluginManifest.CODEC.decodeJson(rawJsonReader, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(this.getLogger()); + manifest = manifestx; + } + + return manifest; + } + } + + return null; + } + + private void loadPacksFromDirectory(Path modsPath) { + if (Files.isDirectory(modsPath)) { + this.getLogger().at(Level.INFO).log("Loading packs from directory: %s", modsPath); + + try (DirectoryStream stream = Files.newDirectoryStream(modsPath)) { + for (Path packPath : stream) { + if (packPath.getFileName() != null && !packPath.getFileName().toString().toLowerCase().endsWith(".jar")) { + this.loadAndRegisterPack(packPath); + } + } + } catch (IOException var7) { + this.getLogger().at(Level.SEVERE).withCause(var7).log("Failed to load mods from: %s", modsPath); + } + } + } + + private void loadAndRegisterPack(Path packPath) { + PluginManifest manifest; + try { + manifest = this.loadPackManifest(packPath); + if (manifest == null) { + this.getLogger().at(Level.WARNING).log("Skipping pack at %s: missing or invalid manifest.json", packPath.getFileName()); + return; + } + } catch (Exception var7) { + this.getLogger().at(Level.WARNING).withCause(var7).log("Failed to load manifest for pack at %s", packPath); + return; + } + + PluginIdentifier packIdentifier = new PluginIdentifier(manifest); + HytaleServerConfig.ModConfig modConfig = HytaleServer.get().getConfig().getModConfig().get(packIdentifier); + boolean enabled = modConfig == null || modConfig.getEnabled() == null || modConfig.getEnabled(); + String packId = packIdentifier.toString(); + if (enabled) { + this.registerPack(packId, packPath, manifest); + this.getLogger().at(Level.INFO).log("Loaded pack: %s from %s", packId, packPath.getFileName()); + } else { + this.getLogger().at(Level.INFO).log("Skipped disabled pack: %s", packId); + } + } + + public void registerPack(@Nonnull String name, @Nonnull Path path, @Nonnull PluginManifest manifest) { + Path absolutePath = path.toAbsolutePath().normalize(); + Path packLocation = absolutePath; + FileSystem fileSystem = null; + boolean isImmutable = false; + String lowerFileName = absolutePath.getFileName().toString().toLowerCase(); + if (!lowerFileName.endsWith(".zip") && !lowerFileName.endsWith(".jar")) { + isImmutable = Files.isRegularFile(absolutePath.resolve("CommonAssetsIndex.hashes")); + } else { + try { + fileSystem = FileSystems.newFileSystem(absolutePath, (ClassLoader)null); + absolutePath = fileSystem.getPath("").toAbsolutePath().normalize(); + isImmutable = true; + } catch (IOException var13) { + throw SneakyThrow.sneakyThrow(var13); + } + } + + AssetPack pack = new AssetPack(packLocation, name, absolutePath, fileSystem, isImmutable, manifest); + this.assetPacks.add(pack); + AssetRegistry.ASSET_LOCK.writeLock().lock(); + + try { + if (this.hasLoaded) { + HytaleServer.get().getEventBus().dispatchFor(AssetPackRegisterEvent.class).dispatch(new AssetPackRegisterEvent(pack)); + return; + } + } finally { + AssetRegistry.ASSET_LOCK.writeLock().unlock(); + } + } + + public void unregisterPack(@Nonnull String name) { + AssetPack pack = this.getAssetPack(name); + if (pack == null) { + this.getLogger().at(Level.WARNING).log("Tried to unregister non-existent asset pack: %s", name); + } else { + this.assetPacks.remove(pack); + if (pack.getFileSystem() != null) { + try { + pack.getFileSystem().close(); + } catch (IOException var8) { + throw SneakyThrow.sneakyThrow(var8); + } + } + + AssetRegistry.ASSET_LOCK.writeLock().lock(); + + try { + HytaleServer.get() + .getEventBus() + .dispatchFor(AssetPackUnregisterEvent.class) + .dispatch(new AssetPackUnregisterEvent(pack)); + } finally { + AssetRegistry.ASSET_LOCK.writeLock().unlock(); + } + } + } + + public AssetPack getAssetPack(@Nonnull String name) { + for (AssetPack pack : this.assetPacks) { + if (name.equals(pack.getName())) { + return pack; + } + } + + return null; + } + + private void onRemoveStore(@Nonnull RemoveAssetStoreEvent event) { + AssetStore>, ? extends AssetMap>> assetStore = (AssetStore>, ? extends AssetMap>>)event.getAssetStore(); + String path = assetStore.getPath(); + if (path != null) { + for (AssetPack pack : this.assetPacks) { + if (!pack.isImmutable()) { + Path assetsPath = pack.getRoot().resolve("Server").resolve(path); + if (Files.isDirectory(assetsPath)) { + assetStore.removeFileMonitor(assetsPath); + } + } + } + } + } + + private void onNewStore(@Nonnull RegisterAssetStoreEvent event) { + if (AssetRegistry.HAS_INIT) { + this.pendingAssetStores.add(event.getAssetStore()); + } + } + + public void initPendingStores() { + for (int i = 0; i < this.pendingAssetStores.size(); i++) { + this.initStore(this.pendingAssetStores.get(i)); + } + + this.pendingAssetStores.clear(); + } + + private void initStore(@Nonnull AssetStore assetStore) { + AssetRegistry.ASSET_LOCK.writeLock().lock(); + + try { + List preAddedAssets = assetStore.getPreAddedAssets(); + if (preAddedAssets != null && !preAddedAssets.isEmpty()) { + AssetLoadResult loadResult = assetStore.loadAssets("Hytale:Hytale", preAddedAssets); + if (loadResult.hasFailed()) { + throw new RuntimeException("Failed to load asset store: " + assetStore.getAssetClass()); + } + } + + for (AssetPack pack : this.assetPacks) { + Path serverAssetDirectory = pack.getRoot().resolve("Server"); + String path = assetStore.getPath(); + if (path != null) { + Path assetsPath = serverAssetDirectory.resolve(path); + if (Files.isDirectory(assetsPath)) { + AssetLoadResult>> loadResult = (AssetLoadResult>>)assetStore.loadAssetsFromDirectory( + pack.getName(), assetsPath + ); + if (loadResult.hasFailed()) { + throw new RuntimeException("Failed to load asset store: " + assetStore.getAssetClass()); + } + } else { + this.getLogger() + .at(Level.SEVERE) + .log("Path for %s isn't a directory or doesn't exist: %s", assetStore.getAssetClass().getSimpleName(), assetsPath); + } + } + + assetStore.validateCodecDefaults(); + if (path != null) { + Path assetsPath = serverAssetDirectory.resolve(path); + if (Files.isDirectory(assetsPath)) { + assetStore.addFileMonitor(pack.getName(), assetsPath); + } + } + } + } catch (IOException var12) { + throw SneakyThrow.sneakyThrow(var12); + } finally { + AssetRegistry.ASSET_LOCK.writeLock().unlock(); + } + } + + private static void validateWorldGen(@Nonnull LoadAssetEvent event) { + if (Options.getOptionSet().has(Options.VALIDATE_WORLD_GEN)) { + long start = System.nanoTime(); + + try { + IWorldGenProvider provider = IWorldGenProvider.CODEC.getDefault(); + IWorldGen generator = provider.getGenerator(); + generator.getDefaultSpawnProvider(0); + if (generator instanceof ValidatableWorldGen) { + boolean valid = ((ValidatableWorldGen)generator).validate(); + if (!valid) { + event.failed(true, "failed to validate world gen"); + } + } + + if (generator instanceof IWorldMapProvider worldMapProvider) { + IWorldMap worldMap = worldMapProvider.getGenerator(null); + worldMap.getWorldMapSettings(); + } + } catch (WorldGenLoadException var7) { + HytaleLogger.getLogger().at(Level.SEVERE).withCause(var7).log("Failed to load default world gen!"); + HytaleLogger.getLogger().at(Level.SEVERE).log("\n" + var7.getTraceMessage("\n")); + event.failed(true, "failed to validate world gen: " + var7.getTraceMessage(" -> ")); + } catch (Throwable var8) { + HytaleLogger.getLogger().at(Level.SEVERE).withCause(var8).log("Failed to load default world gen!"); + event.failed(true, "failed to validate world gen"); + } + + HytaleLogger.getLogger() + .at(Level.INFO) + .log( + "Validate world gen phase completed! Boot time %s, Took %s", + FormatUtil.nanosToString(System.nanoTime() - event.getBootStart()), + FormatUtil.nanosToString(System.nanoTime() - start) + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/AssetNotifications.java b/src/com/hypixel/hytale/server/core/asset/AssetNotifications.java new file mode 100644 index 0000000..a3a1744 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/AssetNotifications.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.server.core.asset; + +public class AssetNotifications { + public static final String ASSET_ADDED_MESSAGE_KEY = "server.general.assetstore.reloadAssets"; + public static final String ASSET_DELETED_MESSAGE_KEY = "server.general.assetstore.removedAssets"; + public static final String ASSET_RELOADED_MESSAGE_KEY = "server.general.assetstore.reloadAssets"; + public static final String ASSET_SECONDARY_GENERIC_MESSAGE_KEY = "server.general.assetstore.removedAssetsSecondaryGeneric"; + public static final String ASSET_ADDED_ICON = "Icons/AssetNotifications/IconCheckmark.png"; + public static final String ASSET_DELETED_ICON = "Icons/AssetNotifications/Trash.png"; + public static final String ASSET_RELOADED_ICON = "Icons/AssetNotifications/AssetReloaded.png"; + public static final String ASSET_ADDED_COLOR = "#06EE92"; + public static final String ASSET_DELETED_COLOR = "#FF3874"; + public static final String ASSET_RELOADED_COLOR = "#A7AfA7"; + + public AssetNotifications() { + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/AssetPackRegisterEvent.java b/src/com/hypixel/hytale/server/core/asset/AssetPackRegisterEvent.java new file mode 100644 index 0000000..e74e420 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/AssetPackRegisterEvent.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.server.core.asset; + +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.event.IEvent; + +public class AssetPackRegisterEvent implements IEvent { + private final AssetPack assetPack; + + public AssetPackRegisterEvent(AssetPack assetPack) { + this.assetPack = assetPack; + } + + public AssetPack getAssetPack() { + return this.assetPack; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/AssetPackUnregisterEvent.java b/src/com/hypixel/hytale/server/core/asset/AssetPackUnregisterEvent.java new file mode 100644 index 0000000..c762deb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/AssetPackUnregisterEvent.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.server.core.asset; + +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.event.IEvent; + +public class AssetPackUnregisterEvent implements IEvent { + private final AssetPack assetPack; + + public AssetPackUnregisterEvent(AssetPack assetPack) { + this.assetPack = assetPack; + } + + public AssetPack getAssetPack() { + return this.assetPack; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/AssetRegistryLoader.java b/src/com/hypixel/hytale/server/core/asset/AssetRegistryLoader.java new file mode 100644 index 0000000..2af4cce --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/AssetRegistryLoader.java @@ -0,0 +1,1046 @@ +package com.hypixel.hytale.server.core.asset; + +import com.hypixel.hytale.assetstore.AssetLoadResult; +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetCodec; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.iterator.AssetStoreIterator; +import com.hypixel.hytale.assetstore.iterator.CircularDependencyException; +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.EmptyExtraInfo; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.ShutdownReason; +import com.hypixel.hytale.server.core.asset.type.ambiencefx.AmbienceFXPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.ambiencefx.config.AmbienceFX; +import com.hypixel.hytale.server.core.asset.type.audiocategory.AudioCategoryPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.audiocategory.config.AudioCategory; +import com.hypixel.hytale.server.core.asset.type.blockbreakingdecal.BlockBreakingDecalPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.blockbreakingdecal.config.BlockBreakingDecal; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxesPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.blockparticle.BlockParticleSetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.blockparticle.config.BlockParticleSet; +import com.hypixel.hytale.server.core.asset.type.blockset.BlockSetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet; +import com.hypixel.hytale.server.core.asset.type.blocksound.BlockSoundSetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.blocksound.config.BlockSoundSet; +import com.hypixel.hytale.server.core.asset.type.blocktype.BlockGroupPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.blocktype.BlockTypePacketGenerator; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockMigration; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BlockTypeListAsset; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.PrefabListAsset; +import com.hypixel.hytale.server.core.asset.type.camera.CameraEffect; +import com.hypixel.hytale.server.core.asset.type.entityeffect.EntityEffectPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.asset.type.environment.EnvironmentPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.core.asset.type.equalizereffect.EqualizerEffectPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.equalizereffect.config.EqualizerEffect; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.asset.type.fluid.FluidTypePacketGenerator; +import com.hypixel.hytale.server.core.asset.type.fluidfx.FluidFXPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.fluidfx.config.FluidFX; +import com.hypixel.hytale.server.core.asset.type.gamemode.GameModeType; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.item.FieldcraftCategoryPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.item.ItemCategoryPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.item.ResourceTypePacketGenerator; +import com.hypixel.hytale.server.core.asset.type.item.config.BlockGroup; +import com.hypixel.hytale.server.core.asset.type.item.config.BuilderToolItemReferenceAsset; +import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; +import com.hypixel.hytale.server.core.asset.type.item.config.FieldcraftCategory; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemCategory; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemQuality; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemReticleConfig; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemToolSpec; +import com.hypixel.hytale.server.core.asset.type.item.config.ResourceType; +import com.hypixel.hytale.server.core.asset.type.itemanimation.ItemPlayerAnimationsPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.itemanimation.config.ItemPlayerAnimations; +import com.hypixel.hytale.server.core.asset.type.itemsound.ItemSoundSetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.itemsound.config.ItemSoundSet; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.asset.type.modelvfx.ModelVFXPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.modelvfx.config.ModelVFX; +import com.hypixel.hytale.server.core.asset.type.particle.ParticleSpawnerPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.particle.ParticleSystemPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSpawner; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; +import com.hypixel.hytale.server.core.asset.type.portalworld.PortalType; +import com.hypixel.hytale.server.core.asset.type.projectile.config.Projectile; +import com.hypixel.hytale.server.core.asset.type.responsecurve.config.ExponentialResponseCurve; +import com.hypixel.hytale.server.core.asset.type.responsecurve.config.ResponseCurve; +import com.hypixel.hytale.server.core.asset.type.reverbeffect.ReverbEffectPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.reverbeffect.config.ReverbEffect; +import com.hypixel.hytale.server.core.asset.type.soundevent.SoundEventPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundset.SoundSetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.soundset.config.SoundSet; +import com.hypixel.hytale.server.core.asset.type.tagpattern.TagPatternPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.tagpattern.config.AndPatternOp; +import com.hypixel.hytale.server.core.asset.type.tagpattern.config.EqualsTagOp; +import com.hypixel.hytale.server.core.asset.type.tagpattern.config.NotPatternOp; +import com.hypixel.hytale.server.core.asset.type.tagpattern.config.OrPatternOp; +import com.hypixel.hytale.server.core.asset.type.tagpattern.config.TagPattern; +import com.hypixel.hytale.server.core.asset.type.trail.TrailPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.trail.config.Trail; +import com.hypixel.hytale.server.core.asset.type.weather.WeatherPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import com.hypixel.hytale.server.core.asset.type.wordlist.WordList; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entity.hitboxcollision.HitboxCollisionConfig; +import com.hypixel.hytale.server.core.modules.entity.repulsion.RepulsionConfig; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.interaction.interaction.UnarmedInteractions; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.item.CraftingRecipePacketGenerator; +import com.hypixel.hytale.server.core.modules.item.ItemPacketGenerator; +import com.hypixel.hytale.server.core.modules.item.ItemQualityPacketGenerator; +import com.hypixel.hytale.server.core.modules.item.ItemReticleConfigPacketGenerator; +import com.hypixel.hytale.server.core.modules.projectile.config.ProjectileConfig; +import com.hypixel.hytale.server.core.modules.projectile.config.ProjectileConfigPacketGenerator; +import com.hypixel.hytale.server.core.universe.world.connectedblocks.CustomConnectedBlockTemplateAsset; +import com.hypixel.hytale.server.core.util.BsonUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonArray; +import org.bson.BsonDocument; +import org.bson.BsonInt32; +import org.bson.BsonString; +import org.bson.BsonValue; + +public class AssetRegistryLoader { + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + + public AssetRegistryLoader() { + } + + public static void init() { + } + + public static void preLoadAssets(@Nonnull LoadAssetEvent event) { + try { + preLoadAssets0(event); + } catch (Throwable var2) { + event.failed(true, "failed to validate assets"); + throw SneakyThrow.sneakyThrow(var2); + } + } + + public static void loadAssets(@Nullable LoadAssetEvent event, @Nonnull AssetPack assetPack) { + AssetRegistry.ASSET_LOCK.writeLock().lock(); + + try { + loadAssets0(event, assetPack); + AssetRegistry.HAS_INIT = true; + } catch (Throwable var6) { + if (event != null) { + event.failed(true, "failed to validate assets"); + } + + throw SneakyThrow.sneakyThrow(var6); + } finally { + AssetRegistry.ASSET_LOCK.writeLock().unlock(); + } + } + + private static void preLoadAssets0(@Nonnull LoadAssetEvent event) { + AssetStore.DISABLE_DYNAMIC_DEPENDENCIES = true; + Collection> values = AssetRegistry.getStoreMap().values(); + LOGGER.at(Level.INFO).log("Loading %s asset stores...", values.size()); + + for (AssetStore assetStore : values) { + assetStore.simplifyLoadBeforeDependencies(); + } + + boolean failedToLoadAsset = false; + LOGGER.at(Level.INFO).log("Pre-adding assets..."); + AssetStoreIterator iterator = new AssetStoreIterator(values); + + label71: { + try { + while (iterator.hasNext()) { + if (HytaleServer.get().isShuttingDown()) { + LOGGER.at(Level.INFO).log("Aborted asset loading due to server shutdown!"); + break label71; + } + + AssetStore>, ? extends AssetMap>> assetStore = (AssetStore>, ? extends AssetMap>>)iterator.next(); + if (assetStore == null) { + throw new CircularDependencyException(values, iterator); + } + + long start = System.nanoTime(); + Class>> assetClass = assetStore.getAssetClass(); + + try { + List preAddedAssets = assetStore.getPreAddedAssets(); + if (preAddedAssets != null && !preAddedAssets.isEmpty()) { + AssetLoadResult loadResult = assetStore.loadAssets("Hytale:Hytale", preAddedAssets); + failedToLoadAsset |= loadResult.hasFailed(); + } + } catch (Exception var14) { + failedToLoadAsset = true; + long end = System.nanoTime(); + long diff = end - start; + if (iterator.isBeingWaitedFor(assetStore)) { + throw new RuntimeException( + String.format("Failed to pre-add %s took %s", assetClass.getSimpleName(), FormatUtil.nanosToString(diff)), var14 + ); + } + + LOGGER.at(Level.SEVERE).withCause(var14).log("Failed to pre-add %s took %s", assetClass.getSimpleName(), FormatUtil.nanosToString(diff)); + } + } + } catch (Throwable var15) { + try { + iterator.close(); + } catch (Throwable var13) { + var15.addSuppressed(var13); + } + + throw var15; + } + + iterator.close(); + if (failedToLoadAsset) { + event.failed(Options.getOptionSet().has(Options.VALIDATE_ASSETS), "failed to validate internal assets"); + } + + return; + } + + iterator.close(); + } + + private static void loadAssets0(@Nullable LoadAssetEvent event, @Nonnull AssetPack assetPack) { + AssetStore.DISABLE_DYNAMIC_DEPENDENCIES = true; + Path serverAssetDirectory = assetPack.getRoot().resolve("Server"); + HytaleLogger.getLogger().at(Level.INFO).log("Loading assets from: %s", serverAssetDirectory); + long startAll = System.nanoTime(); + boolean failedToLoadAsset = false; + LOGGER.at(Level.INFO).log("Loading assets from %s", serverAssetDirectory); + Collection> values = AssetRegistry.getStoreMap().values(); + + try (AssetStoreIterator iterator = new AssetStoreIterator(values)) { + while (iterator.hasNext()) { + if (HytaleServer.get().isShuttingDown()) { + LOGGER.at(Level.INFO).log("Aborted asset loading due to server shutdown!"); + return; + } + + AssetStore>, ? extends AssetMap>> assetStore = (AssetStore>, ? extends AssetMap>>)iterator.next(); + if (assetStore == null) { + throw new CircularDependencyException(values, iterator); + } + + long start = System.nanoTime(); + Class>> assetClass = assetStore.getAssetClass(); + + try { + String path = assetStore.getPath(); + if (path != null) { + Path assetsPath = serverAssetDirectory.resolve(path); + if (Files.isDirectory(assetsPath)) { + AssetLoadResult>> loadResult = assetStore.loadAssetsFromDirectory( + assetPack.getName(), assetsPath + ); + failedToLoadAsset |= loadResult.hasFailed(); + } + } + } catch (Exception var18) { + failedToLoadAsset = true; + long end = System.nanoTime(); + long diff = end - start; + if (iterator.isBeingWaitedFor(assetStore)) { + throw new RuntimeException( + String.format("Failed to load %s from path '%s' took %s", assetClass.getSimpleName(), assetStore.getPath(), FormatUtil.nanosToString(diff)), + var18 + ); + } + + LOGGER.at(Level.SEVERE) + .withCause(var18) + .log("Failed to load %s from path '%s' took %s", assetClass.getSimpleName(), assetStore.getPath(), FormatUtil.nanosToString(diff)); + } + } + } + + for (AssetStore assetStore : values) { + if (assetPack.getName().equals("Hytale:Hytale")) { + assetStore.validateCodecDefaults(); + } + + String path = assetStore.getPath(); + if (path != null) { + Path assetsPath = serverAssetDirectory.resolve(path); + if (Files.isDirectory(assetsPath) && !assetPack.isImmutable()) { + assetStore.addFileMonitor(assetPack.getName(), assetsPath); + } + } + } + + long endAll = System.nanoTime(); + long diffAll = endAll - startAll; + LOGGER.at(Level.INFO).log("Took %s to load all assets", FormatUtil.nanosToString(diffAll)); + if (failedToLoadAsset && event != null) { + event.failed(Options.getOptionSet().has(Options.VALIDATE_ASSETS), "failed to validate assets"); + } + } + + public static void sendAssets(@Nonnull PacketHandler packetHandler) { + Consumer packetConsumer = packetHandler::write; + Consumer singlePacketConsumer = packetHandler::write; + AssetRegistry.ASSET_LOCK.writeLock().lock(); + + try { + HytaleAssetStore.SETUP_PACKET_CONSUMERS.add(singlePacketConsumer); + } finally { + AssetRegistry.ASSET_LOCK.writeLock().unlock(); + } + + try { + for (AssetStore assetStore : AssetRegistry.getStoreMap().values()) { + ((HytaleAssetStore)assetStore).sendAssets(packetConsumer); + } + } finally { + AssetRegistry.ASSET_LOCK.writeLock().lock(); + + try { + HytaleAssetStore.SETUP_PACKET_CONSUMERS.remove(singlePacketConsumer); + } finally { + AssetRegistry.ASSET_LOCK.writeLock().unlock(); + } + } + } + + @Nonnull + public static Map generateSchemas(@Nonnull SchemaContext context, @Nonnull BsonDocument vsCodeConfig) { + AssetStore[] values = AssetRegistry.getStoreMap().values().toArray(AssetStore[]::new); + Arrays.sort(values, Comparator.comparing(storex -> storex.getAssetClass().getSimpleName())); + BsonArray vsCodeSchemas = new BsonArray(); + BsonDocument vsCodeFiles = new BsonDocument(); + vsCodeConfig.put("json.schemas", vsCodeSchemas); + vsCodeConfig.put("files.associations", vsCodeFiles); + vsCodeConfig.put("editor.tabSize", new BsonInt32(2)); + + for (AssetStore store : values) { + Class assetClass = store.getAssetClass(); + String name = assetClass.getSimpleName(); + AssetCodec codec = store.getCodec(); + context.addFileReference(name + ".json", codec); + } + + HashMap schemas = new HashMap<>(); + + for (AssetStore store : values) { + Class assetClass = store.getAssetClass(); + String path = store.getPath(); + String name = assetClass.getSimpleName(); + AssetCodec codec = store.getCodec(); + Schema schema = codec.toSchema(context); + if (codec instanceof AssetCodecMapCodec) { + schema.setTitle(name); + } + + schema.setId(name + ".json"); + Schema.HytaleMetadata hytale = schema.getHytale(); + hytale.setPath(path); + hytale.setExtension(store.getExtension()); + Class idProvider = store.getIdProvider(); + if (idProvider != null) { + hytale.setIdProvider(idProvider.getSimpleName()); + } + + List preload = store.getPreAddedAssets(); + if (preload != null && !preload.isEmpty()) { + String[] internal = new String[preload.size()]; + + for (int i = 0; i < preload.size(); i++) { + Object p = preload.get(i); + Object k = store.getKeyFunction().apply(p); + internal[i] = k.toString(); + } + + hytale.setInternalKeys(internal); + } + + BsonDocument config = new BsonDocument(); + config.put( + "fileMatch", + new BsonArray( + List.of(new BsonString("/Server/" + path + "/*" + store.getExtension()), new BsonString("/Server/" + path + "/**/*" + store.getExtension())) + ) + ); + config.put("url", new BsonString("./Schema/" + name + ".json")); + vsCodeSchemas.add((BsonValue)config); + if (!store.getExtension().equals(".json")) { + vsCodeFiles.put("*" + store.getExtension(), new BsonString("json")); + } + + schemas.put(name + ".json", schema); + } + + HytaleServer.get() + .getEventBus() + .dispatchFor(GenerateSchemaEvent.class) + .dispatch(new GenerateSchemaEvent(schemas, context, vsCodeConfig)); + Schema definitions = new Schema(); + definitions.setDefinitions(context.getDefinitions()); + definitions.setId("common.json"); + schemas.put("common.json", definitions); + Schema otherDefinitions = new Schema(); + otherDefinitions.setDefinitions(context.getOtherDefinitions()); + otherDefinitions.setId("other.json"); + schemas.put("other.json", otherDefinitions); + return schemas; + } + + public static void writeSchemas(LoadAssetEvent event) { + if (Options.getOptionSet().has(Options.GENERATE_SCHEMA)) { + try { + AssetPack pack = AssetModule.get().getBaseAssetPack(); + if (pack.isImmutable()) { + LOGGER.at(Level.SEVERE).log("Not generating schema due launcher assets"); + HytaleServer.get().shutdownServer(ShutdownReason.VALIDATE_ERROR.withMessage("Not generating scheme due launcher assets")); + return; + } + + BsonDocument vsCodeConfig = new BsonDocument(); + Path assetDirectory = pack.getRoot(); + Path schemaDir = assetDirectory.resolve("Schema"); + Files.createDirectories(schemaDir); + + try (Stream stream = Files.walk(schemaDir, 1)) { + stream.filter(v -> v.toString().endsWith(".json")).forEach(SneakyThrow.sneakyConsumer(Files::delete)); + } + + SchemaContext context = new SchemaContext(); + Map schemas = generateSchemas(context, vsCodeConfig); + + for (Entry schema : schemas.entrySet()) { + BsonUtil.writeDocument(schemaDir.resolve(schema.getKey()), Schema.CODEC.encode(schema.getValue(), EmptyExtraInfo.EMPTY).asDocument(), false) + .join(); + } + + Files.createDirectories(assetDirectory.resolve(".vscode")); + BsonUtil.writeDocument(assetDirectory.resolve(".vscode/settings.json"), vsCodeConfig, false).join(); + } catch (Throwable var11) { + LOGGER.at(Level.SEVERE).withCause(var11).log("Schema generation failed"); + HytaleServer.get().shutdownServer(ShutdownReason.CRASH.withMessage("Schema generation failed")); + return; + } + + HytaleServer.get().shutdownServer(ShutdownReason.SHUTDOWN.withMessage("Schema generated")); + } + } + + static { + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + AmbienceFX.class, new IndexedAssetMap() + ) + .setPath("Audio/AmbienceFX")) + .setCodec(AmbienceFX.CODEC)) + .setKeyFunction(AmbienceFX::getId)) + .setReplaceOnRemove(AmbienceFX::new)) + .setPacketGenerator(new AmbienceFXPacketGenerator()) + .loadsAfter( + Weather.class, + Environment.class, + FluidFX.class, + SoundEvent.class, + BlockSoundSet.class, + TagPattern.class, + AudioCategory.class, + ReverbEffect.class, + EqualizerEffect.class + )) + .preLoadAssets(Collections.singletonList(AmbienceFX.EMPTY))) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + BlockBoundingBoxes.class, new IndexedLookupTableAssetMap<>(BlockBoundingBoxes[]::new) + ) + .setPath("Item/Block/Hitboxes")) + .setCodec(BlockBoundingBoxes.CODEC)) + .setKeyFunction(BlockBoundingBoxes::getId)) + .setReplaceOnRemove(BlockBoundingBoxes::getUnitBoxFor)) + .setPacketGenerator(new BlockBoundingBoxesPacketGenerator()) + .preLoadAssets(Collections.singletonList(BlockBoundingBoxes.UNIT_BOX))) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + BlockSet.class, new IndexedLookupTableAssetMap<>(BlockSet[]::new) + ) + .setPath("Item/Block/Sets")) + .setCodec(BlockSet.CODEC)) + .setKeyFunction(BlockSet::getId)) + .setReplaceOnRemove(BlockSet::new)) + .setPacketGenerator(new BlockSetPacketGenerator()) + .loadsBefore(Item.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + BlockSoundSet.class, new IndexedLookupTableAssetMap<>(BlockSoundSet[]::new) + ) + .setPath("Item/Block/Sounds")) + .setCodec(BlockSoundSet.CODEC)) + .setKeyFunction(BlockSoundSet::getId)) + .setReplaceOnRemove(BlockSoundSet::new)) + .setPacketGenerator(new BlockSoundSetPacketGenerator()) + .loadsAfter(SoundEvent.class)) + .preLoadAssets(Collections.singletonList(BlockSoundSet.EMPTY_BLOCK_SOUND_SET))) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ItemSoundSet.class, new IndexedLookupTableAssetMap<>(ItemSoundSet[]::new) + ) + .setPath("Audio/ItemSounds")) + .setCodec(ItemSoundSet.CODEC)) + .setKeyFunction(ItemSoundSet::getId)) + .setReplaceOnRemove(ItemSoundSet::new)) + .setPacketGenerator(new ItemSoundSetPacketGenerator()) + .loadsAfter(SoundEvent.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + BlockParticleSet.class, new DefaultAssetMap() + ) + .setPath("Item/Block/Particles")) + .setCodec(BlockParticleSet.CODEC)) + .setKeyFunction(BlockParticleSet::getId)) + .setPacketGenerator(new BlockParticleSetPacketGenerator()) + .loadsAfter(ParticleSystem.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + BlockBreakingDecal.class, new DefaultAssetMap() + ) + .setPath("Item/Block/BreakingDecals")) + .setCodec(BlockBreakingDecal.CODEC)) + .setKeyFunction(BlockBreakingDecal::getId)) + .setPacketGenerator(new BlockBreakingDecalPacketGenerator()) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + Integer.class, BlockMigration.class, new DefaultAssetMap() + ) + .setPath("Item/Block/Migrations")) + .setCodec(BlockMigration.CODEC)) + .setKeyFunction(BlockMigration::getId)) + .build() + ); + BlockTypeAssetMap blockTypeAssetMap = new BlockTypeAssetMap<>(BlockType[]::new, BlockType::getGroup); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + BlockType.class, blockTypeAssetMap + ) + .setPath("Item/Block/Blocks")) + .setCodec(BlockType.CODEC)) + .setKeyFunction(BlockType::getId)) + .setPacketGenerator(new BlockTypePacketGenerator()) + .loadsAfter( + BlockBoundingBoxes.class, + BlockSoundSet.class, + SoundEvent.class, + BlockParticleSet.class, + BlockBreakingDecal.class, + CustomConnectedBlockTemplateAsset.class, + PrefabListAsset.class, + BlockTypeListAsset.class + )) + .setNotificationItemFunction(item -> new ItemStack(item, 1).toPacket()) + .setReplaceOnRemove(BlockType::getUnknownFor)) + .preLoadAssets(Arrays.asList(BlockType.EMPTY, BlockType.UNKNOWN, BlockType.DEBUG_CUBE, BlockType.DEBUG_MODEL))) + .setIdProvider(Item.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + Fluid.class, new IndexedLookupTableAssetMap<>(Fluid[]::new) + ) + .setPath("Item/Block/Fluids")) + .setCodec(Fluid.CODEC)) + .setKeyFunction(Fluid::getId)) + .setReplaceOnRemove(Fluid::getUnknownFor)) + .setPacketGenerator(new FluidTypePacketGenerator()) + .loadsAfter(FluidFX.class, BlockSoundSet.class, BlockParticleSet.class, SoundEvent.class)) + .preLoadAssets(List.of(Fluid.EMPTY, Fluid.UNKNOWN))) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ItemPlayerAnimations.class, new DefaultAssetMap() + ) + .setPath("Item/Animations")) + .setCodec(ItemPlayerAnimations.CODEC)) + .setKeyFunction(ItemPlayerAnimations::getId)) + .setPacketGenerator(new ItemPlayerAnimationsPacketGenerator()) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + Environment.class, new IndexedLookupTableAssetMap<>(Environment[]::new) + ) + .setPath("Environments")) + .setCodec(Environment.CODEC)) + .setKeyFunction(Environment::getId)) + .setReplaceOnRemove(Environment::getUnknownFor)) + .setPacketGenerator(new EnvironmentPacketGenerator()) + .loadsAfter(Weather.class, FluidFX.class, ParticleSystem.class)) + .preLoadAssets(Collections.singletonList(Environment.UNKNOWN))) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + FluidFX.class, new IndexedLookupTableAssetMap<>(FluidFX[]::new) + ) + .setPath("Item/Block/FluidFX")) + .setCodec(FluidFX.CODEC)) + .setKeyFunction(FluidFX::getId)) + .setReplaceOnRemove(FluidFX::getUnknownFor)) + .setPacketGenerator(new FluidFXPacketGenerator()) + .loadsAfter(ParticleSystem.class)) + .preLoadAssets(Collections.singletonList(FluidFX.EMPTY_FLUID_FX))) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ItemCategory.class, new DefaultAssetMap(Collections.synchronizedMap(new Object2ObjectLinkedOpenHashMap())) + ) + .setPath("Item/Category/CreativeLibrary")) + .setCodec(ItemCategory.CODEC)) + .setKeyFunction(ItemCategory::getId)) + .setPacketGenerator(new ItemCategoryPacketGenerator()) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + FieldcraftCategory.class, new DefaultAssetMap(Collections.synchronizedMap(new Object2ObjectLinkedOpenHashMap())) + ) + .setPath("Item/Category/Fieldcraft")) + .setCodec(FieldcraftCategory.CODEC)) + .setKeyFunction(FieldcraftCategory::getId)) + .setPacketGenerator(new FieldcraftCategoryPacketGenerator()) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(ItemDropList.class, new DefaultAssetMap()) + .setPath("Drops")) + .setCodec(ItemDropList.CODEC)) + .setKeyFunction(ItemDropList::getId)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(WordList.class, new DefaultAssetMap()) + .setPath("WordLists")) + .setCodec(WordList.CODEC)) + .setKeyFunction(WordList::getId)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ItemReticleConfig.class, new IndexedLookupTableAssetMap<>(ItemReticleConfig[]::new) + ) + .setPath("Item/Reticles")) + .setCodec(ItemReticleConfig.CODEC)) + .setKeyFunction(ItemReticleConfig::getId)) + .setReplaceOnRemove(ItemReticleConfig::new)) + .setPacketGenerator(new ItemReticleConfigPacketGenerator()) + .preLoadAssets(Collections.singletonList(ItemReticleConfig.DEFAULT))) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ItemToolSpec.class, new DefaultAssetMap() + ) + .setPath("Item/Unarmed/Gathering")) + .setCodec(ItemToolSpec.CODEC)) + .setKeyFunction(ItemToolSpec::getGatherType)) + .loadsAfter(SoundEvent.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(PortalType.class, new DefaultAssetMap()) + .setPath("PortalTypes")) + .setCodec(PortalType.CODEC)) + .setKeyFunction(PortalType::getId)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + Item.class, new DefaultAssetMap() + ) + .setPath("Item/Items")) + .setCodec(Item.CODEC)) + .setKeyFunction(Item::getId)) + .setPacketGenerator(new ItemPacketGenerator()) + .loadsAfter( + ItemCategory.class, + ItemPlayerAnimations.class, + UnarmedInteractions.class, + ResourceType.class, + BlockType.class, + EntityEffect.class, + ItemQuality.class, + ItemReticleConfig.class, + SoundEvent.class, + PortalType.class, + ItemSoundSet.class + )) + .setNotificationItemFunction(item -> new ItemStack(item, 1).toPacket()) + .preLoadAssets(Collections.singletonList(Item.UNKNOWN))) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + CraftingRecipe.class, new DefaultAssetMap() + ) + .setPath("Item/Recipes")) + .setCodec(CraftingRecipe.CODEC)) + .setKeyFunction(CraftingRecipe::getId)) + .setPacketGenerator(new CraftingRecipePacketGenerator()) + .loadsAfter(Item.class, BlockType.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ModelAsset.class, new DefaultAssetMap() + ) + .setPath("Models")) + .setCodec(ModelAsset.CODEC)) + .setKeyFunction(ModelAsset::getId)) + .loadsAfter(ParticleSystem.class, SoundEvent.class, Trail.class)) + .preLoadAssets(List.of(ModelAsset.DEBUG))) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ParticleSpawner.class, new DefaultAssetMap() + ) + .setPath("Particles")) + .setExtension(".particlespawner")) + .setCodec(ParticleSpawner.CODEC)) + .setKeyFunction(ParticleSpawner::getId)) + .setPacketGenerator(new ParticleSpawnerPacketGenerator()) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ParticleSystem.class, new DefaultAssetMap() + ) + .setPath("Particles")) + .setExtension(".particlesystem")) + .setCodec(ParticleSystem.CODEC)) + .setKeyFunction(ParticleSystem::getId)) + .setPacketGenerator(new ParticleSystemPacketGenerator()) + .loadsAfter(ParticleSpawner.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(Trail.class, new DefaultAssetMap()) + .setPath("Entity/Trails")) + .setCodec(Trail.CODEC)) + .setKeyFunction(Trail::getId)) + .setPacketGenerator(new TrailPacketGenerator()) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + Projectile.class, new DefaultAssetMap() + ) + .setPath("Projectiles")) + .setCodec(Projectile.CODEC)) + .setKeyFunction(Projectile::getId)) + .loadsAfter(SoundEvent.class, ModelAsset.class, ParticleSystem.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + EntityEffect.class, new IndexedLookupTableAssetMap<>(EntityEffect[]::new) + ) + .setPath("Entity/Effects")) + .setCodec(EntityEffect.CODEC)) + .setKeyFunction(EntityEffect::getId)) + .setReplaceOnRemove(EntityEffect::new)) + .setPacketGenerator(new EntityEffectPacketGenerator()) + .loadsAfter( + ModelAsset.class, ParticleSystem.class, EntityStatType.class, ModelVFX.class, DamageCause.class, CameraEffect.class, SoundEvent.class + )) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ModelVFX.class, new IndexedLookupTableAssetMap<>(ModelVFX[]::new) + ) + .setPath("Entity/ModelVFX")) + .setCodec(ModelVFX.CODEC)) + .setKeyFunction(ModelVFX::getId)) + .setReplaceOnRemove(ModelVFX::new)) + .setPacketGenerator(new ModelVFXPacketGenerator()) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + GameModeType.class, new DefaultAssetMap() + ) + .setPath("Entity/GameMode")) + .setCodec(GameModeType.CODEC)) + .setKeyFunction(GameModeType::getId)) + .loadsAfter(Interaction.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(ResourceType.class, new DefaultAssetMap()) + .setPath("Item/ResourceTypes")) + .setCodec(ResourceType.CODEC)) + .setKeyFunction(ResourceType::getId)) + .setPacketGenerator(new ResourceTypePacketGenerator()) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + Weather.class, new IndexedLookupTableAssetMap<>(Weather[]::new) + ) + .setPath("Weathers")) + .setCodec(Weather.CODEC)) + .setKeyFunction(Weather::getId)) + .setReplaceOnRemove(Weather::new)) + .setPacketGenerator(new WeatherPacketGenerator()) + .loadsAfter(ParticleSystem.class)) + .preLoadAssets(Collections.singletonList(Weather.UNKNOWN))) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + GameplayConfig.class, new DefaultAssetMap() + ) + .setPath("GameplayConfigs")) + .setCodec(GameplayConfig.CODEC)) + .setKeyFunction(GameplayConfig::getId)) + .loadsAfter( + Item.class, + SoundEvent.class, + SoundSet.class, + BlockType.class, + EntityEffect.class, + HitboxCollisionConfig.class, + DamageCause.class, + RepulsionConfig.class, + ParticleSystem.class, + AmbienceFX.class + )) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + SoundEvent.class, new IndexedLookupTableAssetMap<>(SoundEvent[]::new) + ) + .setPath("Audio/SoundEvents")) + .setCodec(SoundEvent.CODEC)) + .setKeyFunction(SoundEvent::getId)) + .setReplaceOnRemove(SoundEvent::new)) + .setPacketGenerator(new SoundEventPacketGenerator()) + .preLoadAssets(Collections.singletonList(SoundEvent.EMPTY_SOUND_EVENT))) + .loadsAfter(AudioCategory.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + SoundSet.class, new IndexedLookupTableAssetMap<>(SoundSet[]::new) + ) + .setPath("Audio/SoundSets")) + .setCodec(SoundSet.CODEC)) + .setKeyFunction(SoundSet::getId)) + .setReplaceOnRemove(SoundSet::new)) + .setPacketGenerator(new SoundSetPacketGenerator()) + .loadsAfter(SoundEvent.class)) + .preLoadAssets(Collections.singletonList(SoundSet.EMPTY_SOUND_SET))) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + AudioCategory.class, new IndexedLookupTableAssetMap<>(AudioCategory[]::new) + ) + .setPath("Audio/AudioCategories")) + .setCodec(AudioCategory.CODEC)) + .setKeyFunction(AudioCategory::getId)) + .setReplaceOnRemove(AudioCategory::new)) + .setPacketGenerator(new AudioCategoryPacketGenerator()) + .preLoadAssets(Collections.singletonList(AudioCategory.EMPTY_AUDIO_CATEGORY))) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ReverbEffect.class, new IndexedLookupTableAssetMap<>(ReverbEffect[]::new) + ) + .setPath("Audio/Reverb")) + .setCodec(ReverbEffect.CODEC)) + .setKeyFunction(ReverbEffect::getId)) + .setReplaceOnRemove(ReverbEffect::new)) + .setPacketGenerator(new ReverbEffectPacketGenerator()) + .preLoadAssets(Collections.singletonList(ReverbEffect.EMPTY_REVERB_EFFECT))) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + EqualizerEffect.class, new IndexedLookupTableAssetMap<>(EqualizerEffect[]::new) + ) + .setPath("Audio/EQ")) + .setCodec(EqualizerEffect.CODEC)) + .setKeyFunction(EqualizerEffect::getId)) + .setReplaceOnRemove(EqualizerEffect::new)) + .setPacketGenerator(new EqualizerEffectPacketGenerator()) + .preLoadAssets(Collections.singletonList(EqualizerEffect.EMPTY_EQUALIZER_EFFECT))) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ResponseCurve.class, new IndexedLookupTableAssetMap<>(ResponseCurve[]::new) + ) + .setPath("ResponseCurves")) + .setCodec(ResponseCurve.CODEC)) + .setKeyFunction(ResponseCurve::getId)) + .setReplaceOnRemove(ExponentialResponseCurve::new)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ItemQuality.class, new IndexedLookupTableAssetMap<>(ItemQuality[]::new) + ) + .setPath("Item/Qualities")) + .setCodec(ItemQuality.CODEC)) + .setKeyFunction(ItemQuality::getId)) + .setPacketGenerator(new ItemQualityPacketGenerator()) + .setReplaceOnRemove(ItemQuality::new)) + .loadsAfter(ParticleSystem.class)) + .preLoadAssets(Collections.singletonList(ItemQuality.DEFAULT_ITEM_QUALITY))) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + DamageCause.class, new IndexedLookupTableAssetMap<>(DamageCause[]::new) + ) + .setPath("Entity/Damage")) + .setCodec(DamageCause.CODEC)) + .setKeyFunction(DamageCause::getId)) + .setReplaceOnRemove(DamageCause::new)) + .loadsBefore(Item.class, Interaction.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ProjectileConfig.class, new DefaultAssetMap() + ) + .setPath("ProjectileConfigs")) + .setCodec(ProjectileConfig.CODEC)) + .setKeyFunction(ProjectileConfig::getId)) + .loadsAfter(Interaction.class, SoundEvent.class, ModelAsset.class, ParticleSystem.class)) + .setPacketGenerator(new ProjectileConfigPacketGenerator()) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + BlockGroup.class, new DefaultAssetMap() + ) + .setPath("Item/Groups")) + .setCodec(BlockGroup.CODEC)) + .setKeyFunction(BlockGroup::getId)) + .loadsAfter(BlockType.class, Item.class)) + .setPacketGenerator(new BlockGroupPacketGenerator()) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + BuilderToolItemReferenceAsset.class, new DefaultAssetMap() + ) + .setPath("Item/PlayerToolsMenuConfig")) + .setCodec(BuilderToolItemReferenceAsset.CODEC)) + .setKeyFunction(BuilderToolItemReferenceAsset::getId)) + .loadsAfter(BlockType.class, Item.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + BlockTypeListAsset.class, new DefaultAssetMap() + ) + .setPath("BlockTypeList")) + .setKeyFunction(BlockTypeListAsset::getId)) + .setCodec(BlockTypeListAsset.CODEC)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(PrefabListAsset.class, new DefaultAssetMap()) + .setPath("PrefabList")) + .setKeyFunction(PrefabListAsset::getId)) + .setCodec(PrefabListAsset.CODEC)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + CameraEffect.class, new IndexedLookupTableAssetMap<>(CameraEffect[]::new) + ) + .loadsBefore(GameplayConfig.class, Interaction.class)) + .setPath("Camera/CameraEffect")) + .setCodec(CameraEffect.CODEC)) + .setKeyFunction(CameraEffect::getId)) + .setReplaceOnRemove(CameraEffect.MissingCameraEffect::new)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + TagPattern.class, new IndexedLookupTableAssetMap<>(TagPattern[]::new) + ) + .setPath("TagPatterns")) + .setCodec(TagPattern.CODEC)) + .setKeyFunction(TagPattern::getId)) + .setReplaceOnRemove(EqualsTagOp::new)) + .setPacketGenerator(new TagPatternPacketGenerator()) + .build() + ); + TagPattern.CODEC.register("Equals", EqualsTagOp.class, EqualsTagOp.CODEC); + TagPattern.CODEC.register("And", AndPatternOp.class, AndPatternOp.CODEC); + TagPattern.CODEC.register("Or", OrPatternOp.class, OrPatternOp.CODEC); + TagPattern.CODEC.register("Not", NotPatternOp.class, NotPatternOp.CODEC); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/GenerateSchemaEvent.java b/src/com/hypixel/hytale/server/core/asset/GenerateSchemaEvent.java new file mode 100644 index 0000000..ecf1c7a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/GenerateSchemaEvent.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.core.asset; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.event.IEvent; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonArray; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.BsonValue; + +public class GenerateSchemaEvent implements IEvent { + protected final Map schemas; + protected final SchemaContext context; + protected final BsonDocument vsCodeConfig; + + public GenerateSchemaEvent(Map schemas, SchemaContext context, BsonDocument vsCodeConfig) { + this.schemas = schemas; + this.context = context; + this.vsCodeConfig = vsCodeConfig; + } + + public SchemaContext getContext() { + return this.context; + } + + public BsonDocument getVsCodeConfig() { + return this.vsCodeConfig; + } + + public void addSchemaLink(String name, @Nonnull List paths, @Nullable String extension) { + BsonDocument config = new BsonDocument(); + config.put("fileMatch", new BsonArray(paths.stream().map(v -> new BsonString("/Server/" + v)).collect(Collectors.toList()))); + config.put("url", new BsonString("./Schema/" + name + ".json")); + this.vsCodeConfig.getArray("json.schemas").add((BsonValue)config); + if (extension != null && !extension.equals(".json")) { + this.vsCodeConfig.getDocument("files.associations").put("*" + extension, new BsonString("json")); + } + } + + public void addSchema(String fileName, Schema schema) { + this.schemas.put(fileName, schema); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/HytaleAssetStore.java b/src/com/hypixel/hytale/server/core/asset/HytaleAssetStore.java new file mode 100644 index 0000000..31dc4a2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/HytaleAssetStore.java @@ -0,0 +1,325 @@ +package com.hypixel.hytale.server.core.asset; + +import com.hypixel.hytale.assetstore.AssetLoadResult; +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.event.AssetStoreMonitorEvent; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.event.EventBus; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.protocol.ItemWithAllMetadata; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.packets.interface_.NotificationStyle; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.asset.monitor.AssetMonitor; +import com.hypixel.hytale.server.core.asset.monitor.AssetMonitorHandler; +import com.hypixel.hytale.server.core.asset.monitor.EventKind; +import com.hypixel.hytale.server.core.asset.packet.AssetPacketGenerator; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.util.NotificationUtil; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.io.IOException; +import java.lang.ref.SoftReference; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.Map.Entry; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HytaleAssetStore, M extends AssetMap> extends AssetStore { + public static final ObjectList> SETUP_PACKET_CONSUMERS = new ObjectArrayList<>(); + protected final AssetPacketGenerator packetGenerator; + protected final Function notificationItemFunction; + @Nullable + protected SoftReference cachedInitPackets; + + public HytaleAssetStore(@Nonnull HytaleAssetStore.Builder builder) { + super(builder); + this.packetGenerator = builder.packetGenerator; + this.notificationItemFunction = builder.notificationItemFunction; + } + + public AssetPacketGenerator getPacketGenerator() { + return this.packetGenerator; + } + + public Function getNotificationItemFunction() { + return this.notificationItemFunction; + } + + @Nonnull + protected EventBus getEventBus() { + return HytaleServer.get().getEventBus(); + } + + @Override + public void addFileMonitor(@Nonnull String packKey, @Nonnull Path assetsPath) { + AssetMonitor assetMonitor = AssetModule.get().getAssetMonitor(); + if (assetMonitor != null) { + assetMonitor.monitorDirectoryFiles(assetsPath, new HytaleAssetStore.AssetStoreMonitorHandler(packKey)); + } + } + + @Override + public void removeFileMonitor(@Nonnull Path path) { + AssetMonitor assetMonitor = AssetModule.get().getAssetMonitor(); + if (assetMonitor != null) { + assetMonitor.removeMonitorDirectoryFiles(path, this); + } + } + + @Override + protected void handleRemoveOrUpdate(@Nullable Set toBeRemoved, @Nullable Map toBeUpdated, @Nonnull AssetUpdateQuery query) { + if (this.packetGenerator != null) { + this.cachedInitPackets = null; + Universe universe = Universe.get(); + if (universe.getPlayerCount() != 0 || !SETUP_PACKET_CONSUMERS.isEmpty()) { + if (toBeRemoved != null && !toBeRemoved.isEmpty()) { + Packet packet = this.packetGenerator.generateRemovePacket(this.assetMap, toBeRemoved, query); + universe.broadcastPacketNoCache(packet); + + for (int i = 0; i < SETUP_PACKET_CONSUMERS.size(); i++) { + Consumer c = SETUP_PACKET_CONSUMERS.get(i); + c.accept(packet); + } + } + + if (toBeUpdated != null && !toBeUpdated.isEmpty()) { + Packet packet = this.packetGenerator.generateUpdatePacket(this.assetMap, toBeUpdated, query); + universe.broadcastPacketNoCache(packet); + + for (int i = 0; i < SETUP_PACKET_CONSUMERS.size(); i++) { + Consumer c = SETUP_PACKET_CONSUMERS.get(i); + c.accept(packet); + } + } + } + } + } + + public void sendAssets(@Nonnull Consumer packetConsumer) { + if (this.packetGenerator != null) { + Packet[] packets = this.cachedInitPackets == null ? null : this.cachedInitPackets.get(); + if (packets != null) { + packetConsumer.accept(packets); + } else { + Map map = this.assetMap.getAssetMap(); + Packet packet = this.packetGenerator.generateInitPacket(this.assetMap, map); + this.cachedInitPackets = new SoftReference<>(packets = new Packet[]{packet}); + packetConsumer.accept(packets); + } + } + } + + protected void sendReloadedNotification(@Nonnull AssetLoadResult result) { + this.sendNotificationKeys( + Message.translation("server.general.assetstore.reloadAssets").param("class", this.tClass.getSimpleName()).color("#A7AfA7"), + "Icons/AssetNotifications/AssetReloaded.png", + result.getLoadedAssets().keySet() + ); + this.sendNotificationKeys( + Message.translation("server.general.assetstore.loadFailed").param("class", this.tClass.getSimpleName()), null, result.getFailedToLoadKeys() + ); + this.sendNotificationPaths( + Message.translation("server.general.assetstore.loadFailed").param("class", this.tClass.getSimpleName()), result.getFailedToLoadPaths() + ); + } + + protected void sendRemovedNotification(@Nonnull Set removedKeys) { + this.sendNotificationKeys( + Message.translation("server.general.assetstore.removedAssets").param("class", this.tClass.getSimpleName()).color("#FF3874"), + "Icons/AssetNotifications/Trash.png", + removedKeys + ); + } + + protected void sendNotificationKeys(Message primaryMessage, @Nullable String icon, @Nonnull Set keys) { + if (!keys.isEmpty()) { + if (this.notificationItemFunction != null && keys.size() < 5) { + for (K key : keys) { + ItemWithAllMetadata item = this.notificationItemFunction.apply(key); + if (item != null) { + NotificationUtil.sendNotificationToUniverse(primaryMessage, Message.raw(key.toString()), item, NotificationStyle.Default); + } else { + NotificationUtil.sendNotificationToUniverse(primaryMessage, Message.raw(key.toString()), icon, null, NotificationStyle.Default); + } + } + } else { + Message secondaryMessage = Message.translation("server.general.assetstore.removedAssetsSecondaryGeneric").param("count", keys.size()); + NotificationUtil.sendNotificationToUniverse(primaryMessage, secondaryMessage, icon, NotificationStyle.Default); + } + } + } + + protected void sendNotificationPaths(Message primaryMessage, @Nonnull Set paths) { + if (!paths.isEmpty()) { + NotificationUtil.sendNotificationToUniverse(primaryMessage, Message.raw(paths.toString()), NotificationStyle.Warning); + } + } + + @Nonnull + public static , M extends AssetMap> HytaleAssetStore.Builder builder( + Class tClass, M assetMap + ) { + return new HytaleAssetStore.Builder<>(String.class, tClass, assetMap); + } + + @Nonnull + public static , M extends AssetMap> HytaleAssetStore.Builder builder( + Class kClass, Class tClass, M assetMap + ) { + return new HytaleAssetStore.Builder<>(kClass, tClass, assetMap); + } + + static { + AssetStore.DISABLE_ASSET_COMPARE = Options.getOptionSet().has(Options.DISABLE_ASSET_COMPARE); + } + + private class AssetStoreMonitorHandler implements AssetMonitorHandler { + private final String packKey; + + public AssetStoreMonitorHandler(String packKey) { + this.packKey = packKey; + } + + @Override + public Object getKey() { + return HytaleAssetStore.this; + } + + public boolean test(Path path, EventKind eventKind) { + return !Files.isRegularFile(path) || path.getFileName().toString().endsWith(HytaleAssetStore.this.extension); + } + + public void accept(Map map) { + ObjectArrayList createdOrModifiedFilesToLoad = new ObjectArrayList<>(); + ObjectArrayList removedFilesToUnload = new ObjectArrayList<>(); + ObjectArrayList createdOrModifiedDirectories = new ObjectArrayList<>(); + ObjectArrayList removedFilesAndDirectories = new ObjectArrayList<>(); + + for (Entry entry : map.entrySet()) { + Path path = entry.getKey(); + switch ((EventKind)entry.getValue()) { + case ENTRY_CREATE: + if (Files.isDirectory(path)) { + HytaleAssetStore.this.logger.at(Level.FINEST).log("Directory Created: %s", path); + + try (Stream stream = Files.walk(path, FileUtil.DEFAULT_WALK_TREE_OPTIONS_ARRAY)) { + stream.forEach(child -> { + if (Files.isDirectory(child)) { + createdOrModifiedDirectories.add(path); + } + + if (Files.isRegularFile(child) && child.getFileName().toString().endsWith(HytaleAssetStore.this.extension)) { + createdOrModifiedFilesToLoad.add(child); + } + }); + } catch (IOException var14) { + HytaleAssetStore.this.logger.at(Level.SEVERE).withCause(var14).log("Failed to reload assets in directory: %s", path); + } + } else { + HytaleAssetStore.this.logger.at(Level.FINEST).log("File Created: %s", path); + createdOrModifiedFilesToLoad.add(path); + createdOrModifiedFilesToLoad.add(path); + } + break; + case ENTRY_DELETE: + removedFilesAndDirectories.add(path); + + for (Path value : HytaleAssetStore.this.getAssetMap().getPathMap(this.packKey).values()) { + if (PathUtil.isChildOf(path, value)) { + HytaleAssetStore.this.logger.at(Level.FINEST).log("Deleted: %s", value); + removedFilesToUnload.add(value); + } + } + break; + case ENTRY_MODIFY: + if (Files.isDirectory(path)) { + HytaleAssetStore.this.logger.at(Level.FINEST).log("Directory Modified: %s", path); + createdOrModifiedDirectories.add(path); + } else { + HytaleAssetStore.this.logger.at(Level.FINEST).log("File Modified: %s", path); + createdOrModifiedFilesToLoad.add(path); + } + break; + default: + throw new IllegalArgumentException("Unknown eventKind " + entry.getValue()); + } + } + + if (!removedFilesAndDirectories.isEmpty() || !createdOrModifiedFilesToLoad.isEmpty() || !createdOrModifiedDirectories.isEmpty()) { + IEventDispatcher dispatchFor = HytaleAssetStore.this.getEventBus() + .dispatchFor(AssetStoreMonitorEvent.class); + if (dispatchFor.hasListener()) { + dispatchFor.dispatch( + new AssetStoreMonitorEvent( + this.packKey, + HytaleAssetStore.this, + createdOrModifiedFilesToLoad, + removedFilesToUnload, + createdOrModifiedDirectories, + removedFilesAndDirectories + ) + ); + } + } + + if (!createdOrModifiedFilesToLoad.isEmpty()) { + HytaleAssetStore.this.logger.at(Level.INFO).log("Reloading assets: %s", createdOrModifiedFilesToLoad); + long start = System.nanoTime(); + AssetLoadResult result = HytaleAssetStore.this.loadAssetsFromPaths(this.packKey, createdOrModifiedFilesToLoad, AssetUpdateQuery.DEFAULT, true); + HytaleAssetStore.this.logger + .at(Level.INFO) + .log("Took %s to reload assets: %s", FormatUtil.nanosToString(System.nanoTime() - start), createdOrModifiedFilesToLoad); + HytaleAssetStore.this.sendReloadedNotification(result); + } + + if (!removedFilesToUnload.isEmpty()) { + HytaleAssetStore.this.logger.at(Level.INFO).log("Removing deleted assets: %s", removedFilesToUnload); + Set removedKeys = HytaleAssetStore.this.removeAssetWithPaths(this.packKey, removedFilesToUnload); + HytaleAssetStore.this.sendRemovedNotification(removedKeys); + } + } + } + + public static class Builder, M extends AssetMap> + extends AssetStore.Builder> { + protected AssetPacketGenerator packetGenerator; + protected Function notificationItemFunction; + + public Builder(Class kClass, Class tClass, M assetMap) { + super(kClass, tClass, assetMap); + } + + @Nonnull + public HytaleAssetStore.Builder setPacketGenerator(AssetPacketGenerator packetGenerator) { + this.packetGenerator = Objects.requireNonNull(packetGenerator, "packetGenerator can't be null!"); + return this; + } + + @Nonnull + public HytaleAssetStore.Builder setNotificationItemFunction(Function notificationItemFunction) { + this.notificationItemFunction = Objects.requireNonNull(notificationItemFunction, "notificationItemFunction can't be null!"); + return this; + } + + @Nonnull + public HytaleAssetStore build() { + return new HytaleAssetStore<>(this); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/LoadAssetEvent.java b/src/com/hypixel/hytale/server/core/asset/LoadAssetEvent.java new file mode 100644 index 0000000..fd35b5b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/LoadAssetEvent.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.core.asset; + +import com.hypixel.hytale.event.IEvent; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class LoadAssetEvent implements IEvent { + public static final short PRIORITY_LOAD_COMMON = -32; + public static final short PRIORITY_LOAD_REGISTRY = -16; + public static final short PRIORITY_LOAD_LATE = 64; + private final long bootStart; + @Nonnull + private final List reasons = new ObjectArrayList<>(); + private boolean shouldShutdown = false; + + public LoadAssetEvent(long bootStart) { + this.bootStart = bootStart; + } + + public long getBootStart() { + return this.bootStart; + } + + public boolean isShouldShutdown() { + return this.shouldShutdown; + } + + @Nonnull + public List getReasons() { + return this.reasons; + } + + public void failed(boolean shouldShutdown, String reason) { + this.shouldShutdown |= shouldShutdown; + this.reasons.add(reason); + } + + @Nonnull + @Override + public String toString() { + return "LoadAssetEvent{bootStart=" + this.bootStart + ", shouldShutdown=" + this.shouldShutdown + ", reasons=" + this.reasons + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/common/BlockyAnimationCache.java b/src/com/hypixel/hytale/server/core/asset/common/BlockyAnimationCache.java new file mode 100644 index 0000000..a77a7ae --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/common/BlockyAnimationCache.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.server.core.asset.common; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockyAnimationCache { + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final Map animations = new ConcurrentHashMap<>(); + + public BlockyAnimationCache() { + } + + @Nonnull + public static CompletableFuture get(String name) { + BlockyAnimationCache.BlockyAnimation animationData = animations.get(name); + if (animationData != null) { + return CompletableFuture.completedFuture(animationData); + } else { + CommonAsset asset = CommonAssetRegistry.getByName(name); + return asset == null ? CompletableFuture.completedFuture(null) : get0(asset); + } + } + + @Nonnull + public static CompletableFuture get(@Nonnull CommonAsset asset) { + BlockyAnimationCache.BlockyAnimation animationData = animations.get(asset.getName()); + return animationData != null ? CompletableFuture.completedFuture(animationData) : get0(asset); + } + + @Nullable + public static BlockyAnimationCache.BlockyAnimation getNow(String name) { + BlockyAnimationCache.BlockyAnimation animationData = animations.get(name); + if (animationData != null) { + return animationData; + } else { + CommonAsset asset = CommonAssetRegistry.getByName(name); + return asset == null ? null : get0(asset).join(); + } + } + + public static BlockyAnimationCache.BlockyAnimation getNow(@Nonnull CommonAsset asset) { + BlockyAnimationCache.BlockyAnimation animationData = animations.get(asset.getName()); + return animationData != null ? animationData : get0(asset).join(); + } + + @Nonnull + private static CompletableFuture get0(@Nonnull CommonAsset asset) { + String name = asset.getName(); + return CompletableFutureUtil._catch(asset.getBlob().thenApply(bytes -> { + String str = new String(bytes, StandardCharsets.UTF_8); + RawJsonReader reader = RawJsonReader.fromJsonString(str); + + try { + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + BlockyAnimationCache.BlockyAnimation newAnimationData = BlockyAnimationCache.BlockyAnimation.CODEC.decodeJson(reader, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER); + animations.put(name, newAnimationData); + return newAnimationData; + } catch (IOException var6) { + throw SneakyThrow.sneakyThrow(var6); + } + })); + } + + public static void invalidate(String name) { + animations.remove(name); + } + + public static class BlockyAnimation { + public static final BuilderCodec CODEC = BuilderCodec.builder( + BlockyAnimationCache.BlockyAnimation.class, BlockyAnimationCache.BlockyAnimation::new + ) + .addField( + new KeyedCodec<>("duration", Codec.INTEGER, true, true), + (blockyAnimation, i) -> blockyAnimation.duration = i, + blockyAnimation -> blockyAnimation.duration + ) + .build(); + public static final double FRAMES_PER_SECOND = 60.0; + private int duration; + + public BlockyAnimation() { + } + + public int getDurationFrames() { + return this.duration; + } + + public double getDurationMillis() { + return this.duration * 1000.0 / 60.0; + } + + public double getDurationSeconds() { + return this.duration / 60.0; + } + + @Nonnull + @Override + public String toString() { + return "BlockyAnimation{duration=" + this.duration + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/common/CommonAsset.java b/src/com/hypixel/hytale/server/core/asset/common/CommonAsset.java new file mode 100644 index 0000000..b7ac1ae --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/common/CommonAsset.java @@ -0,0 +1,98 @@ +package com.hypixel.hytale.server.core.asset.common; + +import com.hypixel.hytale.common.util.PatternUtil; +import com.hypixel.hytale.protocol.Asset; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.util.HashUtil; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.concurrent.CompletableFuture; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class CommonAsset implements NetworkSerializable { + public static final int HASH_LENGTH = 64; + public static final Pattern HASH_PATTERN = Pattern.compile("^[A-Fa-f0-9]{64}$"); + @Nonnull + private final String name; + @Nonnull + private final String hash; + protected transient WeakReference> blob; + protected transient SoftReference cachedPacket; + + public CommonAsset(@Nonnull String name, @Nullable byte[] bytes) { + this.name = PatternUtil.replaceBackslashWithForwardSlash(name); + this.hash = hash(bytes); + this.blob = new WeakReference<>(bytes != null ? CompletableFuture.completedFuture(bytes) : null); + } + + public CommonAsset(@Nonnull String name, @Nonnull String hash, @Nullable byte[] bytes) { + this.name = PatternUtil.replaceBackslashWithForwardSlash(name); + this.hash = hash.toLowerCase(); + this.blob = new WeakReference<>(bytes != null ? CompletableFuture.completedFuture(bytes) : null); + } + + @Nonnull + public String getName() { + return this.name; + } + + @Nonnull + public String getHash() { + return this.hash; + } + + public CompletableFuture getBlob() { + CompletableFuture future = this.blob.get(); + if (future == null) { + future = this.getBlob0(); + this.blob = new WeakReference<>(future); + } + + return future; + } + + protected abstract CompletableFuture getBlob0(); + + @Nonnull + public Asset toPacket() { + Asset cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + Asset packet = new Asset(this.hash, this.name); + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + CommonAsset asset = (CommonAsset)o; + return !this.name.equals(asset.name) ? false : this.hash.equals(asset.hash); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.name.hashCode(); + return 31 * result + this.hash.hashCode(); + } + + @Nonnull + @Override + public String toString() { + return "CommonAsset{name='" + this.name + "', hash='" + this.hash + "'}"; + } + + @Nonnull + public static String hash(byte[] bytes) { + return HashUtil.sha256(bytes); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/common/CommonAssetModule.java b/src/com/hypixel/hytale/server/core/asset/common/CommonAssetModule.java new file mode 100644 index 0000000..59a521a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/common/CommonAssetModule.java @@ -0,0 +1,725 @@ +package com.hypixel.hytale.server.core.asset.common; + +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.common.util.PatternUtil; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.function.supplier.CachedSupplier; +import com.hypixel.hytale.function.supplier.SupplierUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.protocol.Asset; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.packets.interface_.Notification; +import com.hypixel.hytale.protocol.packets.interface_.NotificationStyle; +import com.hypixel.hytale.protocol.packets.setup.AssetFinalize; +import com.hypixel.hytale.protocol.packets.setup.AssetInitialize; +import com.hypixel.hytale.protocol.packets.setup.AssetPart; +import com.hypixel.hytale.protocol.packets.setup.RemoveAssets; +import com.hypixel.hytale.protocol.packets.setup.RequestCommonAssetsRebuild; +import com.hypixel.hytale.protocol.packets.setup.WorldLoadProgress; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.asset.AssetPackRegisterEvent; +import com.hypixel.hytale.server.core.asset.AssetPackUnregisterEvent; +import com.hypixel.hytale.server.core.asset.LoadAssetEvent; +import com.hypixel.hytale.server.core.asset.common.asset.FileCommonAsset; +import com.hypixel.hytale.server.core.asset.common.events.CommonAssetMonitorEvent; +import com.hypixel.hytale.server.core.asset.common.events.SendCommonAssetsEvent; +import com.hypixel.hytale.server.core.asset.monitor.AssetMonitor; +import com.hypixel.hytale.server.core.asset.monitor.AssetMonitorHandler; +import com.hypixel.hytale.server.core.asset.monitor.EventKind; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.util.NotificationUtil; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import it.unimi.dsi.fastutil.booleans.BooleanObjectPair; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CommonAssetModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(CommonAssetModule.class).depends(AssetModule.class).build(); + private static CommonAssetModule instance; + public static final Set IGNORED_FILES = Set.of(Path.of(".DS_Store"), Path.of("Thumbs.db")); + public static final Instant TICK_TIMESTAMP_ORIGIN = Instant.parse("0001-01-01T00:00:00Z"); + public static final String ASSET_INDEX_VERSION_IDENTIFIER = "VERSION="; + public static final int ASSET_INDEX_HASHES_VERSION = 0; + public static final int ASSET_INDEX_CACHE_VERSION = 1; + public static final int MAX_FRAME = 2621440; + private final CachedSupplier assets = SupplierUtil.cache( + () -> CommonAssetRegistry.getAllAssets() + .stream() + .map(List::getLast) + .map(CommonAssetRegistry.PackAsset::asset) + .map(CommonAsset::toPacket) + .toArray(Asset[]::new) + ); + + public static CommonAssetModule get() { + return instance; + } + + public CommonAssetModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + this.getEventRegistry().register(SendCommonAssetsEvent.class, this::onSendCommonAssets); + this.getEventRegistry().register((short)-32, LoadAssetEvent.class, event -> { + for (AssetPack pack : AssetModule.get().getAssetPacks()) { + this.loadCommonAssets(pack, event.getBootStart()); + } + }); + this.getEventRegistry().register((short)-32, AssetPackRegisterEvent.class, event -> this.loadCommonAssets(event.getAssetPack(), System.nanoTime())); + this.getEventRegistry().register(AssetPackUnregisterEvent.class, event -> this.removeCommonAssets(event.getAssetPack())); + } + + private void removeCommonAssets(@Nonnull AssetPack assetPack) { + this.unregisterAssetMonitor(assetPack); + List removedAssets = new ObjectArrayList<>(); + List updatedAssets = new ObjectArrayList<>(); + + for (List assets : CommonAssetRegistry.getAllAssets()) { + for (CommonAssetRegistry.PackAsset asset : assets) { + if (asset.pack().equals(assetPack.getName())) { + BooleanObjectPair removed = CommonAssetRegistry.removeCommonAssetByName(asset.pack(), asset.asset().getName()); + if (removed != null) { + if (removed.firstBoolean()) { + updatedAssets.add(removed.second().asset()); + } else { + removedAssets.add(removed.second()); + } + } + + this.assets.invalidate(); + } + } + } + + this.sendRemoveAssets(removedAssets, false); + this.sendAssets(updatedAssets, false); + Universe.get().broadcastPacketNoCache(new RequestCommonAssetsRebuild()); + } + + public void loadCommonAssets(@Nonnull AssetPack pack, long bootTime) { + Path assetPath = pack.getRoot(); + HytaleLogger.getLogger().at(Level.INFO).log("Loading common assets from: %s", assetPath); + long start = System.nanoTime(); + if (this.readCommonAssetsIndexHashes(pack)) { + int duplicateAssetCount = CommonAssetRegistry.getDuplicateAssetCount(); + if (duplicateAssetCount > 0) { + this.getLogger().at(Level.WARNING).log("Duplicated Asset Count: %s", duplicateAssetCount); + } + + HytaleLogger.getLogger() + .at(Level.INFO) + .log( + "Loading common assets phase completed! Boot time %s, Took %s", + FormatUtil.nanosToString(System.nanoTime() - bootTime), + FormatUtil.nanosToString(System.nanoTime() - start) + ); + } else { + Path commonPath = pack.getRoot().resolve("Common"); + AssetMonitor assetMonitor = AssetModule.get().getAssetMonitor(); + if (assetMonitor != null && !pack.isImmutable() && Files.isDirectory(commonPath)) { + assetMonitor.monitorDirectoryFiles(commonPath, new CommonAssetModule.CommonAssetMonitorHandler(pack, commonPath)); + } + + this.readCommonAssetsIndexCache(pack); + + try { + this.walkFileTree(pack); + } catch (IOException var10) { + throw SneakyThrow.sneakyThrow(var10); + } + + int duplicateAssetCount = CommonAssetRegistry.getDuplicateAssetCount(); + if (duplicateAssetCount > 0) { + this.getLogger().at(Level.WARNING).log("Duplicated Asset Count: %s", duplicateAssetCount); + } + + HytaleLogger.getLogger() + .at(Level.INFO) + .log( + "Loading common assets phase completed! Boot time %s, Took %s", + FormatUtil.nanosToString(System.nanoTime() - bootTime), + FormatUtil.nanosToString(System.nanoTime() - start) + ); + Universe.get().broadcastPacketNoCache(new RequestCommonAssetsRebuild()); + } + } + + public void addCommonAsset(String pack, @Nonnull T asset) { + this.addCommonAsset(pack, asset, true); + } + + public void addCommonAsset(String pack, @Nonnull T asset, boolean log) { + CommonAssetRegistry.AddCommonAssetResult result = CommonAssetRegistry.addCommonAsset(pack, asset); + CommonAssetRegistry.PackAsset newAsset = result.getNewPackAsset(); + CommonAssetRegistry.PackAsset oldAsset = result.getPreviousNameAsset(); + if (oldAsset != null && oldAsset.asset().getHash().equals(newAsset.asset().getHash())) { + if (log) { + this.getLogger().at(Level.INFO).log("Didn't change: %s", asset.getName()); + } + } else { + if (oldAsset == null) { + if (log) { + this.getLogger().at(Level.INFO).log("Created: %s", newAsset); + } + } else if (log) { + this.getLogger().at(Level.INFO).log("Reloaded: %s - Old Hash: %s", newAsset, oldAsset.asset().getHash()); + } + + String messageId = oldAsset == null ? "server.general.assetstore.reloadAssets" : "server.general.assetstore.reloadAssets"; + String iconPath = oldAsset == null ? "Icons/AssetNotifications/IconCheckmark.png" : "Icons/AssetNotifications/AssetReloaded.png"; + String messageColor = oldAsset == null ? "#06EE92" : "#A7AfA7"; + NotificationUtil.sendNotificationToUniverse( + Message.translation(messageId).color(messageColor).param("class", "Common"), + Message.raw(newAsset.pack() + ":" + newAsset.asset().getName()), + iconPath, + NotificationStyle.Success + ); + if (result.getActiveAsset().equals(newAsset)) { + this.assets.invalidate(); + BlockyAnimationCache.invalidate(newAsset.asset().getName()); + if (Universe.get().getPlayerCount() > 0) { + this.sendAsset(newAsset.asset(), false); + } + } + } + } + + @Nullable + public Asset[] getRequiredAssets() { + return this.assets.get(); + } + + private boolean readCommonAssetsIndexHashes(@Nonnull AssetPack pack) { + Path assetPath = pack.getRoot(); + Path commonPath = assetPath.resolve("Common"); + Path assetHashFile = assetPath.resolve("CommonAssetsIndex.hashes"); + if (Files.isRegularFile(assetHashFile)) { + long loadHashesStart = System.nanoTime(); + int loadedAssetCount = 0; + + try (BufferedReader reader = Files.newBufferedReader(assetHashFile)) { + int version = 0; + int i = 0; + + while (true) { + String line = reader.readLine(); + if (line == null) { + break; + } + + if (line.startsWith("VERSION=")) { + version = Integer.parseInt(line.substring("VERSION=".length())); + this.getLogger().at(Level.FINEST).log("Version set to %d from CommonAssetsIndex.hashes:L%d '%s'", version, i, line); + if (version > 0) { + throw new IllegalArgumentException(String.format("Unsupported version %d in CommonAssetsIndex.hashes %d > %d", version, version, 0)); + } + } else { + String[] split = line.split(" ", 2); + if (split.length != 2) { + this.getLogger().at(Level.WARNING).log("Corrupt line in CommonAssetsIndex.hashes:L%d '%s'", i, line); + } else { + String hash = split[0]; + if (hash.length() != 64 && !CommonAsset.HASH_PATTERN.matcher(hash).matches()) { + this.getLogger().at(Level.WARNING).log("Corrupt line in CommonAssetsIndex.hashes:L%d '%s'", i, line); + } else { + String name = split[1]; + this.addCommonAsset(pack.getName(), new FileCommonAsset(commonPath.resolve(name), name, hash, null), false); + this.getLogger().at(Level.FINEST).log("Loaded asset info from CommonAssetsIndex.hashes:L%d '%s'", i, name); + loadedAssetCount++; + } + } + } + + i++; + } + } catch (IOException var17) { + this.getLogger().at(Level.WARNING).withCause(var17).log("Failed to load hashes from CommonAssetsIndex.hashes"); + return false; + } + + long loadHashesEnd = System.nanoTime(); + long loadHashesDiff = loadHashesEnd - loadHashesStart; + this.getLogger() + .at(Level.INFO) + .log("Took %s to load %d assets from CommonAssetsIndex.hashes file.", FormatUtil.nanosToString(loadHashesDiff), loadedAssetCount); + return true; + } else { + return false; + } + } + + private void readCommonAssetsIndexCache(@Nonnull AssetPack pack) { + Path assetPath = pack.getRoot(); + Path commonPath = assetPath.resolve("Common"); + Path assetCacheFile = assetPath.resolve("CommonAssetsIndex.cache"); + if (Files.isRegularFile(assetCacheFile)) { + long loadCacheStart = System.nanoTime(); + AtomicInteger loadedAssetCount = new AtomicInteger(); + List> futures = new ObjectArrayList<>(); + + try (BufferedReader reader = Files.newBufferedReader(assetCacheFile)) { + int version = 0; + int i = 0; + + while (true) { + String line = reader.readLine(); + if (line == null) { + break; + } + + if (line.startsWith("VERSION=")) { + version = Integer.parseInt(line.substring("VERSION=".length())); + this.getLogger().at(Level.FINEST).log("Version set to %d from CommonAssetsIndex.cache:L%d '%s'", version, i, line); + if (version > 1) { + throw new IllegalArgumentException(String.format("Unsupported version %d in CommonAssetsIndex.cache %d > %d", version, version, 1)); + } + } else { + int indexOne = line.indexOf(32); + int indexTwo = line.indexOf(32, indexOne + 1); + if (indexTwo < 0) { + this.getLogger().at(Level.WARNING).log("Corrupt line in CommonAssetsIndex.cache:L%d '%s'", i, line); + } else { + String hash = line.substring(0, indexOne); + if (hash.length() != 64 && !CommonAsset.HASH_PATTERN.matcher(hash).matches()) { + this.getLogger().at(Level.WARNING).log("Corrupt line in CommonAssetsIndex.cache:L%d '%s'", i, line); + } else { + long timestampLong = Long.parseLong(line, indexOne + 1, indexTwo, 10); + Instant timestamp; + if (version > 0) { + timestamp = Instant.ofEpochSecond(timestampLong); + } else { + long timestampMillis = timestampLong / 10000L; + timestamp = TICK_TIMESTAMP_ORIGIN.plusMillis(timestampMillis); + } + + String name = line.substring(indexTwo + 1); + Path file = commonPath.resolve(name); + int lineNumber = i; + futures.add( + CompletableFuture.supplyAsync( + () -> { + BasicFileAttributes attributes; + try { + attributes = Files.readAttributes(file, BasicFileAttributes.class); + } catch (IOException var10x) { + return null; + } + + if (!attributes.isRegularFile()) { + return null; + } else { + Instant lastModified = attributes.lastModifiedTime().toInstant().truncatedTo(ChronoUnit.SECONDS); + if (timestamp.equals(lastModified)) { + this.addCommonAsset(pack.getName(), new FileCommonAsset(file, name, hash, null), false); + this.getLogger().at(Level.FINEST).log("Loaded asset info from CommonAssetsIndex.cache:L%d '%s'", lineNumber, name); + loadedAssetCount.getAndIncrement(); + } else { + this.getLogger() + .at(Level.FINEST) + .log( + "Skipped outdated asset from CommonAssetsIndex.cache:L%d '%s', Timestamp: %s, Last Modified: %s", + lineNumber, + name, + timestamp, + lastModified + ); + } + + return null; + } + } + ) + ); + } + } + } + + i++; + } + } catch (IOException var24) { + this.getLogger().at(Level.WARNING).withCause(var24).log("Failed to load hashes from CommonAssetsIndex.cache"); + } + + CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).join(); + long loadCacheEnd = System.nanoTime(); + long loadCacheDiff = loadCacheEnd - loadCacheStart; + this.getLogger() + .at(Level.INFO) + .log("Took %s to load %d assets from CommonAssetsIndex.cache file.", FormatUtil.nanosToString(loadCacheDiff), loadedAssetCount.get()); + } + } + + private void walkFileTree(@Nonnull final AssetPack pack) throws IOException { + Path assetPath = pack.getRoot(); + Path commonPath = assetPath.resolve("Common").toAbsolutePath(); + if (Files.exists(commonPath)) { + final int commonPathSubStringIndex = commonPath.toString().length() + 1; + long walkFileTreeStart = System.nanoTime(); + final ObjectArrayList> futures = new ObjectArrayList<>(); + Files.walkFileTree(commonPath, FileUtil.DEFAULT_WALK_TREE_OPTIONS_SET, Integer.MAX_VALUE, new SimpleFileVisitor() { + @Nonnull + public FileVisitResult visitFile(@Nonnull Path path, @Nonnull BasicFileAttributes attrs) throws IOException { + if (!attrs.isRegularFile()) { + return FileVisitResult.CONTINUE; + } else { + Path fileName = path.getFileName(); + if (CommonAssetModule.IGNORED_FILES.contains(fileName)) { + String name = PatternUtil.replaceBackslashWithForwardSlash(path.toString().substring(commonPathSubStringIndex)); + CommonAssetModule.this.getLogger().at(Level.FINEST).log("Skipping ignored file at %s", name); + return FileVisitResult.CONTINUE; + } else if (fileName.toString().endsWith(".hash")) { + Files.deleteIfExists(path); + return FileVisitResult.CONTINUE; + } else { + String name = PatternUtil.replaceBackslashWithForwardSlash(path.toString().substring(commonPathSubStringIndex)); + if (CommonAssetRegistry.hasCommonAsset(pack, name)) { + return FileVisitResult.CONTINUE; + } else { + CommonAssetModule.this.getLogger().at(Level.FINER).log("Loading asset: %s", name); + futures.add(CompletableFuture.supplyAsync(SneakyThrow.sneakySupplier(() -> Files.readAllBytes(path))).thenAcceptAsync(bytes -> { + FileCommonAsset asset = new FileCommonAsset(path, name, bytes); + CommonAssetModule.this.addCommonAsset(pack.getName(), asset, false); + CommonAssetModule.this.getLogger().at(Level.FINER).log("Loaded asset: %s", asset); + }).exceptionally(throwable -> { + CommonAssetModule.this.getLogger().at(Level.FINE).withCause(throwable).log("Failed to load asset: %s", name); + throw SneakyThrow.sneakyThrow(throwable); + })); + return FileVisitResult.CONTINUE; + } + } + } + } + }); + CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).join(); + this.assets.invalidate(); + long walkFileTreeEnd = System.nanoTime(); + long walkFileTreeDiff = walkFileTreeEnd - walkFileTreeStart; + this.getLogger().at(Level.INFO).log("Took %s to walk file tree and load %d assets.", FormatUtil.nanosToString(walkFileTreeDiff), futures.size()); + } + } + + private void unregisterAssetMonitor(@Nonnull AssetPack pack) { + AssetMonitor assetMonitor = AssetModule.get().getAssetMonitor(); + if (assetMonitor != null) { + assetMonitor.removeMonitorDirectoryFiles(pack.getRoot().resolve("Common"), pack); + } + } + + private void reloadAsset(@Nonnull List> addedOrUpdatedAssets, String pack, @Nonnull Path file, @Nonnull String name) { + this.getLogger().at(Level.FINEST).log("Reloading: %s", file); + addedOrUpdatedAssets.add( + CompletableFuture.supplyAsync(SneakyThrow.sneakySupplier(() -> Files.readAllBytes(file))) + .thenAcceptAsync(bytes -> this.addCommonAsset(pack, new FileCommonAsset(file, name, bytes))) + .exceptionally(throwable -> { + if (throwable instanceof NoSuchFileException) { + throwable = new SkipSentryException(throwable); + } + + this.getLogger().at(Level.SEVERE).withCause(throwable).log("Failed to reload asset: %s", file); + return null; + }) + ); + } + + private void onSendCommonAssets(@Nonnull SendCommonAssetsEvent event) { + this.sendAssetsToPlayer(event.getPacketHandler(), event.getRequestedAssets(), true); + } + + public void sendAssetsToPlayer(@Nonnull PacketHandler packetHandler, @Nullable Asset[] requested, boolean forceRebuild) { + List toSend = new ObjectArrayList<>(); + if (requested != null) { + for (Asset toSendAsset : requested) { + CommonAsset asset = CommonAssetRegistry.getByHash(toSendAsset.hash); + Objects.requireNonNull(asset, toSendAsset.hash); + toSend.add(asset); + } + } else { + for (List asset : CommonAssetRegistry.getAllAssets()) { + toSend.add(asset.getLast().asset()); + } + } + + this.getLogger().at(Level.FINE).log("%s requested %d assets!", packetHandler.getIdentifier(), toSend.size()); + this.sendAssetsToPlayer(packetHandler, toSend, forceRebuild); + } + + public void sendAssets(@Nonnull List toSend, boolean forceRebuild) { + for (int i = 0; i < toSend.size(); i++) { + CommonAsset thisAsset = toSend.get(i); + byte[] allBytes = thisAsset.getBlob().join(); + byte[][] parts = ArrayUtil.split(allBytes, 2621440); + Packet[] packets = new Packet[2 + parts.length]; + packets[0] = new AssetInitialize(thisAsset.toPacket(), allBytes.length); + + for (int partIndex = 0; partIndex < parts.length; partIndex++) { + packets[1 + partIndex] = new AssetPart(parts[partIndex]); + } + + packets[packets.length - 1] = new AssetFinalize(); + Universe.get().broadcastPacket(packets); + } + + if (!toSend.isEmpty() && forceRebuild) { + Universe.get().broadcastPacketNoCache(new RequestCommonAssetsRebuild()); + } + } + + public void sendAssetsToPlayer(@Nonnull PacketHandler packetHandler, @Nonnull List toSend, boolean forceRebuild) { + for (int i = 0; i < toSend.size(); i++) { + int thisPercent = MathUtil.getPercentageOf(i, toSend.size()); + CommonAsset thisAsset = toSend.get(i); + byte[] allBytes = thisAsset.getBlob().join(); + byte[][] parts = ArrayUtil.split(allBytes, 2621440); + Packet[] packets = new Packet[2 + parts.length * 2]; + packets[0] = new AssetInitialize(thisAsset.toPacket(), allBytes.length); + + for (int partIndex = 0; partIndex < parts.length; partIndex++) { + packets[1 + partIndex * 2] = new WorldLoadProgress("Loading asset " + thisAsset.getName(), thisPercent, 100 * partIndex / parts.length); + packets[1 + partIndex * 2 + 1] = new AssetPart(parts[partIndex]); + } + + packets[packets.length - 1] = new AssetFinalize(); + packetHandler.write(packets); + } + + if (!toSend.isEmpty() && forceRebuild) { + packetHandler.writeNoCache(new RequestCommonAssetsRebuild()); + } + } + + public void sendAsset(@Nonnull CommonAsset asset, boolean forceRebuild) { + asset.getBlob().whenComplete((allBytes, throwable) -> { + if (throwable != null) { + this.getLogger().at(Level.WARNING).log("Failed to send asset: %s, %s", asset.getName(), asset.getHash()); + } else { + byte[][] parts = ArrayUtil.split(allBytes, 2621440); + Packet[] packets = new Packet[2 + (forceRebuild ? 1 : 0) + parts.length]; + packets[0] = new AssetInitialize(asset.toPacket(), allBytes.length); + + for (int i = 0; i < parts.length; i++) { + packets[1 + i] = new AssetPart(parts[i]); + } + + packets[1 + parts.length] = new AssetFinalize(); + if (forceRebuild) { + packets[2 + parts.length] = new RequestCommonAssetsRebuild(); + } + + Universe.get().broadcastPacket(packets); + } + }); + } + + public void sendRemoveAssets(@Nonnull List assets, boolean forceRebuild) { + int size = assets.size(); + Asset[] asset_ = new Asset[size]; + String messageRemovalKey = "server.general.assetstore.removedAssets"; + String color = "#FF3874"; + String icon = "Icons/AssetNotifications/Trash.png"; + Message message = Message.translation("server.general.assetstore.removedAssets").param("class", "Common").color("#FF3874"); + int packetCountThreshold = 5; + int packetsCount = 1 + (forceRebuild ? 1 : 0) + (assets.size() < 5 ? assets.size() : 1); + Packet[] packets = new Packet[packetsCount]; + int i = 0; + + for (CommonAssetRegistry.PackAsset asset : assets) { + asset_[i++] = asset.asset().toPacket(); + } + + if (assets.size() < 5) { + i = 0; + + for (CommonAssetRegistry.PackAsset asset : assets) { + Message assetName = Message.raw(asset.pack() + ":" + asset.asset().getName()).color("#FF3874"); + packets[i++] = new Notification( + message.getFormattedMessage(), assetName.getFormattedMessage(), "Icons/AssetNotifications/Trash.png", null, NotificationStyle.Default + ); + } + + packets[i++] = new RemoveAssets(asset_); + if (forceRebuild) { + packets[i++] = new RequestCommonAssetsRebuild(); + } + } else { + Message secondaryMessage = Message.translation("server.general.assetstore.removedAssetsSecondaryGeneric").param("count", assets.size()); + packets[0] = new Notification( + message.getFormattedMessage(), secondaryMessage.getFormattedMessage(), "Icons/AssetNotifications/Trash.png", null, NotificationStyle.Default + ); + packets[1] = new RemoveAssets(asset_); + if (forceRebuild) { + packets[2] = new RequestCommonAssetsRebuild(); + } + } + + Universe.get().broadcastPacket(packets); + } + + private class CommonAssetMonitorHandler implements AssetMonitorHandler { + private final AssetPack pack; + private final Path commonPath; + + public CommonAssetMonitorHandler(AssetPack pack, Path commonPath) { + this.pack = pack; + this.commonPath = commonPath; + } + + @Override + public Object getKey() { + return this.pack; + } + + public boolean test(Path path, EventKind eventKind) { + return !CommonAssetModule.IGNORED_FILES.contains(path.getFileName()); + } + + public void accept(Map map) { + List createdOrModifiedFilesToLoad = new ObjectArrayList<>(); + List removedFilesToUnload = new ObjectArrayList<>(); + List createdOrModifiedDirectories = new ObjectArrayList<>(); + List removedFilesAndDirectories = new ObjectArrayList<>(); + + for (Entry entry : map.entrySet()) { + Path path = entry.getKey(); + EventKind eventKind = entry.getValue(); + switch (eventKind) { + case ENTRY_CREATE: + if (Files.isDirectory(path)) { + CommonAssetModule.this.getLogger().at(Level.INFO).log("Directory Created: %s", path); + + try (Stream stream = Files.walk(path, FileUtil.DEFAULT_WALK_TREE_OPTIONS_ARRAY)) { + stream.forEach(child -> { + BasicFileAttributes attributes; + try { + attributes = Files.readAttributes(child, BasicFileAttributes.class); + } catch (IOException var6) { + return; + } + + if (attributes.isDirectory()) { + createdOrModifiedDirectories.add(path); + } else if (attributes.isRegularFile()) { + createdOrModifiedFilesToLoad.add(child); + } + }); + } catch (IOException var17) { + CommonAssetModule.this.getLogger().at(Level.SEVERE).withCause(var17).log("Failed to reload assets in directory: %s", path); + } + } else { + CommonAssetModule.this.getLogger().at(Level.INFO).log("File Created: %s", path); + createdOrModifiedFilesToLoad.add(path); + } + break; + case ENTRY_DELETE: + CommonAssetModule.this.getLogger().at(Level.INFO).log("Deleted: %s", path); + removedFilesAndDirectories.add(path); + Path relative = PathUtil.relativize(this.commonPath, path); + String name = PatternUtil.replaceBackslashWithForwardSlash(relative.toString()); + + for (CommonAsset asset : CommonAssetRegistry.getCommonAssetsStartingWith(this.pack.getName(), name)) { + removedFilesToUnload.add(this.commonPath.resolve(asset.getName())); + } + break; + case ENTRY_MODIFY: + if (Files.isDirectory(path)) { + CommonAssetModule.this.getLogger().at(Level.INFO).log("Directory Modified: %s", path); + createdOrModifiedDirectories.add(path); + } else { + CommonAssetModule.this.getLogger().at(Level.INFO).log("File Modified: %s", path); + createdOrModifiedFilesToLoad.add(path); + } + break; + default: + throw new IllegalArgumentException("Unknown eventKind " + eventKind); + } + } + + if (!removedFilesAndDirectories.isEmpty() || !createdOrModifiedFilesToLoad.isEmpty() || !createdOrModifiedDirectories.isEmpty()) { + IEventDispatcher dispatchFor = HytaleServer.get() + .getEventBus() + .dispatchFor(CommonAssetMonitorEvent.class); + if (dispatchFor.hasListener()) { + dispatchFor.dispatch( + new CommonAssetMonitorEvent( + this.pack.getName(), createdOrModifiedFilesToLoad, removedFilesToUnload, createdOrModifiedDirectories, removedFilesAndDirectories + ) + ); + } + } + + List> addedOrUpdatedAssets = new ObjectArrayList<>(); + List removedAssets = new ObjectArrayList<>(); + List updatedAssets = new ObjectArrayList<>(); + if (!removedFilesToUnload.isEmpty()) { + CommonAssetModule.this.getLogger().at(Level.INFO).log("Removing deleted assets: %s", removedFilesToUnload); + + for (Path path : removedFilesToUnload) { + Path relativePath = PathUtil.relativize(this.commonPath, path); + String name = PatternUtil.replaceBackslashWithForwardSlash(relativePath.toString()); + BooleanObjectPair removed = CommonAssetRegistry.removeCommonAssetByName(this.pack.getName(), name); + if (removed != null) { + if (removed.firstBoolean()) { + updatedAssets.add(removed.second().asset()); + } else { + removedAssets.add(removed.second()); + } + } + + CommonAssetModule.this.assets.invalidate(); + } + + CommonAssetModule.this.sendRemoveAssets(removedAssets, false); + CommonAssetModule.this.sendAssets(updatedAssets, false); + } + + if (!createdOrModifiedFilesToLoad.isEmpty()) { + CommonAssetModule.this.getLogger().at(Level.INFO).log("Reloading assets: %s", createdOrModifiedFilesToLoad); + + for (Path path : createdOrModifiedFilesToLoad) { + Path relative = PathUtil.relativize(this.commonPath, path); + String name = PatternUtil.replaceBackslashWithForwardSlash(relative.toString()); + CommonAssetModule.this.reloadAsset(addedOrUpdatedAssets, this.pack.getName(), path, name); + } + + CompletableFuture.allOf(addedOrUpdatedAssets.toArray(CompletableFuture[]::new)) + .thenAccept(v -> Universe.get().broadcastPacketNoCache(new RequestCommonAssetsRebuild())); + } else { + Universe.get().broadcastPacketNoCache(new RequestCommonAssetsRebuild()); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/common/CommonAssetRegistry.java b/src/com/hypixel/hytale/server/core/asset/common/CommonAssetRegistry.java new file mode 100644 index 0000000..414ca4c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/common/CommonAssetRegistry.java @@ -0,0 +1,251 @@ +package com.hypixel.hytale.server.core.asset.common; + +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.common.util.PatternUtil; +import it.unimi.dsi.fastutil.booleans.BooleanObjectPair; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CommonAssetRegistry { + private static final Map> assetByNameMap = new ConcurrentHashMap<>(); + private static final Map> assetByHashMap = new ConcurrentHashMap<>(); + private static final AtomicInteger duplicateAssetCount = new AtomicInteger(); + private static final Collection> unmodifiableAssetByNameMapValues = Collections.unmodifiableCollection( + assetByNameMap.values() + ); + + public CommonAssetRegistry() { + } + + public static int getDuplicateAssetCount() { + return duplicateAssetCount.get(); + } + + @Nonnull + public static Map> getDuplicatedAssets() { + Map> duplicates = new Object2ObjectOpenHashMap<>(); + + for (Entry> entry : assetByHashMap.entrySet()) { + if (entry.getValue().size() > 1) { + duplicates.put(entry.getKey(), new ObjectArrayList<>(entry.getValue())); + } + } + + return duplicates; + } + + @Nonnull + public static Collection> getAllAssets() { + return unmodifiableAssetByNameMapValues; + } + + public static void clearAllAssets() { + assetByNameMap.clear(); + assetByHashMap.clear(); + } + + @Nonnull + public static CommonAssetRegistry.AddCommonAssetResult addCommonAsset(String pack, @Nonnull CommonAsset asset) { + CommonAssetRegistry.AddCommonAssetResult result = new CommonAssetRegistry.AddCommonAssetResult(); + result.newPackAsset = new CommonAssetRegistry.PackAsset(pack, asset); + List list = assetByNameMap.computeIfAbsent(asset.getName(), v -> new CopyOnWriteArrayList<>()); + boolean added = false; + boolean addHash = true; + + for (int i = 0; i < list.size(); i++) { + CommonAssetRegistry.PackAsset e = list.get(i); + if (e.pack().equals(pack)) { + result.previousNameAsset = e; + if (i == list.size() - 1) { + assetByHashMap.get(e.asset.getHash()).remove(e); + assetByHashMap.compute(e.asset.getHash(), (k, v) -> v != null && !v.isEmpty() ? v : null); + } else { + addHash = false; + } + + list.set(i, result.newPackAsset); + added = true; + break; + } + } + + if (!added) { + if (!list.isEmpty()) { + CommonAssetRegistry.PackAsset e = list.getLast(); + assetByHashMap.get(e.asset.getHash()).remove(e); + assetByHashMap.compute(e.asset.getHash(), (k, v) -> v != null && !v.isEmpty() ? v : null); + result.previousNameAsset = e; + } + + list.add(result.newPackAsset); + } + + if (addHash) { + List commonAssets = assetByHashMap.computeIfAbsent(asset.getHash(), k -> new CopyOnWriteArrayList<>()); + if (!commonAssets.isEmpty()) { + result.previousHashAssets = commonAssets.toArray(CommonAssetRegistry.PackAsset[]::new); + } + + commonAssets.add(result.newPackAsset); + } + + if (result.previousHashAssets != null || result.previousNameAsset != null) { + result.duplicateAssetId = duplicateAssetCount.getAndIncrement(); + } + + result.activeAsset = list.getLast(); + return result; + } + + @Nullable + public static BooleanObjectPair removeCommonAssetByName(String pack, String name) { + name = PatternUtil.replaceBackslashWithForwardSlash(name); + List oldAssets = assetByNameMap.get(name); + if (oldAssets == null) { + return null; + } else { + CommonAssetRegistry.PackAsset previousCurrent = oldAssets.getLast(); + oldAssets.removeIf(v -> v.pack().equals(pack)); + assetByNameMap.compute(name, (k, v) -> v != null && !v.isEmpty() ? v : null); + if (oldAssets.isEmpty()) { + removeCommonAssetByHash0(previousCurrent); + return BooleanObjectPair.of(false, previousCurrent); + } else { + CommonAssetRegistry.PackAsset newCurrent = oldAssets.getLast(); + if (newCurrent.equals(previousCurrent)) { + return null; + } else { + removeCommonAssetByHash0(previousCurrent); + assetByHashMap.computeIfAbsent(newCurrent.asset.getHash(), v -> new CopyOnWriteArrayList<>()).add(newCurrent); + return BooleanObjectPair.of(true, newCurrent); + } + } + } + } + + @Nonnull + public static List getCommonAssetsStartingWith(String pack, String name) { + List oldAssets = new ObjectArrayList<>(); + + for (List assets : assetByNameMap.values()) { + for (CommonAssetRegistry.PackAsset asset : assets) { + if (asset.asset().getName().startsWith(name) && asset.pack().equals(pack)) { + oldAssets.add(asset.asset()); + } + } + } + + return oldAssets; + } + + public static boolean hasCommonAsset(String name) { + return assetByNameMap.containsKey(name); + } + + public static boolean hasCommonAsset(AssetPack pack, String name) { + List packAssets = assetByNameMap.get(name); + if (packAssets != null) { + for (CommonAssetRegistry.PackAsset packAsset : packAssets) { + if (packAsset.pack.equals(pack.getName())) { + return true; + } + } + } + + return false; + } + + @Nullable + public static CommonAsset getByName(String name) { + name = PatternUtil.replaceBackslashWithForwardSlash(name); + List asset = assetByNameMap.get(name); + return asset == null ? null : asset.getLast().asset(); + } + + @Nullable + public static CommonAsset getByHash(@Nonnull String hash) { + List assets = assetByHashMap.get(hash.toLowerCase()); + return assets != null && !assets.isEmpty() ? assets.getFirst().asset() : null; + } + + private static void removeCommonAssetByHash0(@Nonnull CommonAssetRegistry.PackAsset oldAsset) { + List commonAssets = assetByHashMap.get(oldAsset.asset().getHash()); + if (commonAssets != null && commonAssets.remove(oldAsset) && commonAssets.isEmpty()) { + assetByHashMap.compute(oldAsset.asset().getHash(), (key, assets) -> assets != null && !assets.isEmpty() ? assets : null); + } + } + + public static class AddCommonAssetResult { + private CommonAssetRegistry.PackAsset newPackAsset; + private CommonAssetRegistry.PackAsset previousNameAsset; + private CommonAssetRegistry.PackAsset activeAsset; + private CommonAssetRegistry.PackAsset[] previousHashAssets; + private int duplicateAssetId; + + public AddCommonAssetResult() { + } + + public CommonAssetRegistry.PackAsset getNewPackAsset() { + return this.newPackAsset; + } + + public CommonAssetRegistry.PackAsset getPreviousNameAsset() { + return this.previousNameAsset; + } + + public CommonAssetRegistry.PackAsset getActiveAsset() { + return this.activeAsset; + } + + public CommonAssetRegistry.PackAsset[] getPreviousHashAssets() { + return this.previousHashAssets; + } + + public int getDuplicateAssetId() { + return this.duplicateAssetId; + } + + @Nonnull + @Override + public String toString() { + return "AddCommonAssetResult{previousNameAsset=" + + this.previousNameAsset + + ", previousHashAssets=" + + Arrays.toString((Object[])this.previousHashAssets) + + ", duplicateAssetId=" + + this.duplicateAssetId + + "}"; + } + } + + public record PackAsset(String pack, CommonAsset asset) { + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + CommonAssetRegistry.PackAsset packAsset = (CommonAssetRegistry.PackAsset)o; + return !this.pack.equals(packAsset.pack) ? false : this.asset.equals(packAsset.asset); + } else { + return false; + } + } + + @Nonnull + @Override + public String toString() { + return "PackAsset{pack='" + this.pack + "', asset=" + this.asset + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/common/CommonAssetValidator.java b/src/com/hypixel/hytale/server/core/asset/common/CommonAssetValidator.java new file mode 100644 index 0000000..fe93928 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/common/CommonAssetValidator.java @@ -0,0 +1,118 @@ +package com.hypixel.hytale.server.core.asset.common; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import com.hypixel.hytale.codec.validation.validator.ArrayValidator; +import com.hypixel.hytale.common.util.PatternUtil; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CommonAssetValidator implements Validator { + public static final CommonAssetValidator TEXTURE_ITEM = new CommonAssetValidator("png", "Blocks", "BlockTextures", "Items", "NPC", "Resources", "VFX"); + public static final CommonAssetValidator TEXTURE_CHARACTER = new CommonAssetValidator("png", "Characters", "NPC", "Items", "VFX"); + public static final CommonAssetValidator TEXTURE_CHARACTER_ATTACHMENT = new CommonAssetValidator( + "png", "Characters", "NPC", "Items", "Cosmetics", "Items", "NPC", "Resources" + ); + public static final CommonAssetValidator TEXTURE_TRAIL = new CommonAssetValidator("png", "Trails"); + public static final CommonAssetValidator TEXTURE_SKY = new CommonAssetValidator("png", "Sky"); + public static final CommonAssetValidator TEXTURE_PARTICLES = new CommonAssetValidator("png", "Particles"); + public static final CommonAssetValidator TEXTURE_ITEM_QUALITY = new CommonAssetValidator("png", true, "UI/ItemQualities"); + public static final CommonAssetValidator ICON_RESOURCE = new CommonAssetValidator("png", "Icons/ResourceTypes"); + public static final CommonAssetValidator ICON_ITEM = new CommonAssetValidator("png", "Icons/ItemsGenerated", "Icons/Items"); + public static final CommonAssetValidator ICON_ITEM_CATEGORIES = new CommonAssetValidator("png", "Icons/ItemCategories"); + public static final CommonAssetValidator ICON_CRAFTING = new CommonAssetValidator("png", "Icons/CraftingCategories"); + public static final CommonAssetValidator ICON_ENTITY_STAT = new CommonAssetValidator("png", "Icons/EntityStats"); + public static final CommonAssetValidator ICON_MODEL = new CommonAssetValidator("png", "Icons/ModelsGenerated", "Icons/Models"); + public static final CommonAssetValidator UI_RETICLE_PART = new CommonAssetValidator("png", "UI/Reticles"); + public static final ArrayValidator UI_RETICLE_PARTS_ARRAY = new ArrayValidator<>(UI_RETICLE_PART); + public static final CommonAssetValidator UI_SCREEN_EFFECT = new CommonAssetValidator("png", "ScreenEffects"); + public static final CommonAssetValidator UI_CRAFTING_DIAGRAM = new CommonAssetValidator("svg", "CraftingDiagrams"); + public static final CommonAssetValidator MODEL_ITEM = new CommonAssetValidator("blockymodel", "Blocks", "Items", "Resources", "NPC", "VFX", "Consumable"); + public static final CommonAssetValidator MODEL_CHARACTER = new CommonAssetValidator("blockymodel", "Characters", "NPC", "Items", "VFX"); + public static final CommonAssetValidator MODEL_CHARACTER_ATTACHMENT = new CommonAssetValidator( + "blockymodel", "Characters", "NPC", "Items", "Cosmetics", "Items", "NPC", "Resources" + ); + public static final CommonAssetValidator PREFAB_LIST = new CommonAssetValidator("json", "PrefabList"); + public static final CommonAssetValidator BLOCK_LIST = new CommonAssetValidator("json", "BlockTypeList"); + public static final CommonAssetValidator ANIMATION_ITEM_CHARACTER = new CommonAssetValidator("blockyanim", "Characters", "NPC"); + public static final CommonAssetValidator ANIMATION_ITEM_BLOCK = new CommonAssetValidator( + "blockyanim", "Blocks", "Items", "Resources", "NPC", "VFX", "Consumable" + ); + public static final CommonAssetValidator ANIMATION_CHARACTER = new CommonAssetValidator("blockyanim", "Characters", "NPC", "Equipment", "VFX", "Items"); + public static final CommonAssetValidator MUSIC = new CommonAssetValidator("ogg", "Music"); + public static final CommonAssetValidator SOUNDS = new CommonAssetValidator("ogg", "Sounds"); + @Nullable + private final String[] requiredRoots; + @Nullable + private final String requiredExtension; + private final boolean isUIAsset; + + public CommonAssetValidator(String requiredExtension, boolean isUIAsset, @Nullable String... requiredRoots) { + if (requiredRoots != null) { + for (int i = 0; i < requiredRoots.length; i++) { + String req = requiredRoots[i]; + if (!req.endsWith("/")) { + requiredRoots[i] = req + "/"; + } + } + } + + this.requiredRoots = requiredRoots; + this.requiredExtension = requiredExtension; + this.isUIAsset = isUIAsset; + } + + public CommonAssetValidator(String requiredExtension, String... requiredRoots) { + this(requiredExtension, false, requiredRoots); + } + + public CommonAssetValidator() { + this.requiredRoots = null; + this.requiredExtension = null; + this.isUIAsset = true; + } + + public void accept(@Nullable String asset, @Nonnull ValidationResults results) { + if (asset != null) { + if (this.requiredRoots != null) { + boolean valid = false; + + for (String root : this.requiredRoots) { + if (asset.startsWith(root)) { + valid = true; + break; + } + } + + if (!valid) { + results.fail("Common Asset '" + asset + "' must be within the root: " + Arrays.toString((Object[])this.requiredRoots)); + } + } + + if (this.requiredExtension != null && !asset.endsWith(this.requiredExtension)) { + results.fail("Common Asset '" + asset + "' must have the extension " + this.requiredExtension); + } + + asset = PatternUtil.replaceBackslashWithForwardSlash(asset); + if (!CommonAssetRegistry.hasCommonAsset(asset)) { + if (this.isUIAsset && asset.endsWith(".png")) { + String scaled2XVersionFilename = asset.substring(0, asset.lastIndexOf(".png")) + "@2x.png"; + if (!CommonAssetRegistry.hasCommonAsset(scaled2XVersionFilename)) { + results.fail("Common Asset '" + asset + "' doesn't exist!"); + } + } else { + results.fail("Common Asset '" + asset + "' doesn't exist!"); + } + } + } + } + + @Override + public void updateSchema(SchemaContext context, @Nonnull Schema target) { + ((StringSchema)target).setHytaleCommonAsset(new StringSchema.CommonAsset(this.requiredExtension, this.isUIAsset, this.requiredRoots)); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/common/HytaleFileTypes.java b/src/com/hypixel/hytale/server/core/asset/common/HytaleFileTypes.java new file mode 100644 index 0000000..45a27c7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/common/HytaleFileTypes.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.server.core.asset.common; + +public class HytaleFileTypes { + static final String ANIMATION_FILE_TYPE = "blockyanim"; + static final String PNG_FILE_TYPE = "png"; + static final String SVG_FILE_TYPE = "svg"; + static final String MODEL_FILE_TYPE = "blockymodel"; + static final String OGG_FILE_TYPE = "ogg"; + static final String JSON_FILE_TYPE = "json"; + + public HytaleFileTypes() { + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/common/OggVorbisInfoCache.java b/src/com/hypixel/hytale/server/core/asset/common/OggVorbisInfoCache.java new file mode 100644 index 0000000..55b39a2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/common/OggVorbisInfoCache.java @@ -0,0 +1,146 @@ +package com.hypixel.hytale.server.core.asset.common; + +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class OggVorbisInfoCache { + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final Map vorbisFiles = new ConcurrentHashMap<>(); + + public OggVorbisInfoCache() { + } + + @Nonnull + public static CompletableFuture get(String name) { + OggVorbisInfoCache.OggVorbisInfo info = vorbisFiles.get(name); + if (info != null) { + return CompletableFuture.completedFuture(info); + } else { + CommonAsset asset = CommonAssetRegistry.getByName(name); + return asset == null ? CompletableFuture.completedFuture(null) : get0(asset); + } + } + + @Nonnull + public static CompletableFuture get(@Nonnull CommonAsset asset) { + OggVorbisInfoCache.OggVorbisInfo info = vorbisFiles.get(asset.getName()); + return info != null ? CompletableFuture.completedFuture(info) : get0(asset); + } + + @Nullable + public static OggVorbisInfoCache.OggVorbisInfo getNow(String name) { + OggVorbisInfoCache.OggVorbisInfo info = vorbisFiles.get(name); + if (info != null) { + return info; + } else { + CommonAsset asset = CommonAssetRegistry.getByName(name); + return asset == null ? null : get0(asset).join(); + } + } + + public static OggVorbisInfoCache.OggVorbisInfo getNow(@Nonnull CommonAsset asset) { + OggVorbisInfoCache.OggVorbisInfo info = vorbisFiles.get(asset.getName()); + return info != null ? info : get0(asset).join(); + } + + @Nonnull + private static CompletableFuture get0(@Nonnull CommonAsset asset) { + String name = asset.getName(); + return CompletableFutureUtil._catch( + asset.getBlob() + .thenApply( + bytes -> { + ByteBuf b = Unpooled.wrappedBuffer(bytes); + + OggVorbisInfoCache.OggVorbisInfo var21; + try { + int len = b.readableBytes(); + int id = -1; + int i = 0; + + for (int end = len - 7; i <= end; i++) { + i = b.indexOf(i, len - 7, (byte)1); + if (i == -1) { + break; + } + + if (b.getByte(i + 1) == 118 + && b.getByte(i + 2) == 111 + && b.getByte(i + 3) == 114 + && b.getByte(i + 4) == 98 + && b.getByte(i + 5) == 105 + && b.getByte(i + 6) == 115) { + id = i; + break; + } + } + + if (id < 0 || id + 16 > len) { + throw new IllegalArgumentException("Vorbis id header not found"); + } + + i = b.getUnsignedByte(id + 11); + int sampleRate = b.getIntLE(id + 12); + double duration = -1.0; + if (sampleRate > 0) { + for (int ix = Math.max(0, len - 14); ix >= 0; ix--) { + ix = b.indexOf(ix, 0, (byte)79); + if (ix == -1) { + break; + } + + if (b.getByte(ix + 1) == 103 && b.getByte(ix + 2) == 103 && b.getByte(ix + 3) == 83) { + int headerType = b.getUnsignedByte(ix + 5); + if ((headerType & 4) != 0) { + long granule = b.getLongLE(ix + 6); + if (granule >= 0L) { + duration = (double)granule / sampleRate; + } + break; + } + } + } + } + + OggVorbisInfoCache.OggVorbisInfo info = new OggVorbisInfoCache.OggVorbisInfo(i, sampleRate, duration); + vorbisFiles.put(name, info); + var21 = info; + } finally { + b.release(); + } + + return var21; + } + ) + ); + } + + public static void invalidate(String name) { + vorbisFiles.remove(name); + } + + public static class OggVorbisInfo { + public final int channels; + public final int sampleRate; + public final double duration; + + OggVorbisInfo(int channels, int sampleRate, double duration) { + this.channels = channels; + this.sampleRate = sampleRate; + this.duration = duration; + } + + @Nonnull + @Override + public String toString() { + return "OggVorbisInfo{channels=" + this.channels + ", sampleRate=" + this.sampleRate + ", duration=" + this.duration + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/common/PlayerCommonAssets.java b/src/com/hypixel/hytale/server/core/asset/common/PlayerCommonAssets.java new file mode 100644 index 0000000..cbc5a7a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/common/PlayerCommonAssets.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.core.asset.common; + +import com.hypixel.hytale.protocol.Asset; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerCommonAssets { + @Nonnull + private final Map assetMissing = new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map assetSent; + + public PlayerCommonAssets(@Nonnull Asset[] requiredAssets) { + for (Asset requiredAsset : requiredAssets) { + this.assetMissing.put(requiredAsset.hash, requiredAsset.name); + } + + this.assetSent = new Object2ObjectOpenHashMap<>(); + } + + public void sent(@Nullable Asset[] hashes) { + Set set = new HashSet<>(); + if (hashes != null) { + for (Asset hash : hashes) { + set.add(hash.hash); + } + } + + Iterator iterator = this.assetMissing.keySet().iterator(); + + while (iterator.hasNext()) { + String hash = iterator.next(); + if (set.contains(hash)) { + iterator.remove(); + set.remove(hash); + } + } + + if (!set.isEmpty()) { + throw new RuntimeException("Still had hashes: " + set); + } else { + this.assetSent.putAll(this.assetMissing); + this.assetMissing.clear(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/common/ResourceCommonAsset.java b/src/com/hypixel/hytale/server/core/asset/common/ResourceCommonAsset.java new file mode 100644 index 0000000..4a045c8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/common/ResourceCommonAsset.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.server.core.asset.common; + +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ResourceCommonAsset extends CommonAsset { + private final Class clazz; + private final String path; + + public ResourceCommonAsset(Class clazz, String path, @Nonnull String name, byte[] bytes) { + super(name, bytes); + this.clazz = clazz; + this.path = path; + } + + public ResourceCommonAsset(Class clazz, String path, @Nonnull String name, @Nonnull String hash, byte[] bytes) { + super(name, hash, bytes); + this.clazz = clazz; + this.path = path; + } + + public String getPath() { + return this.path; + } + + @Nonnull + @Override + public CompletableFuture getBlob0() { + try { + CompletableFuture var2; + try (InputStream stream = this.clazz.getResourceAsStream(this.path)) { + var2 = CompletableFuture.completedFuture(stream.readAllBytes()); + } + + return var2; + } catch (IOException var6) { + return CompletableFuture.failedFuture(var6); + } + } + + @Nonnull + @Override + public String toString() { + return "ResourceCommonAsset{" + super.toString() + "}"; + } + + @Nullable + public static ResourceCommonAsset of(@Nonnull Class clazz, @Nonnull String path, @Nonnull String name) { + try { + ResourceCommonAsset var5; + try (InputStream stream = clazz.getResourceAsStream(path)) { + if (stream == null) { + return null; + } + + byte[] bytes = stream.readAllBytes(); + var5 = new ResourceCommonAsset(clazz, path, name, bytes); + } + + return var5; + } catch (IOException var8) { + throw SneakyThrow.sneakyThrow(var8); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/common/SoundFileValidators.java b/src/com/hypixel/hytale/server/core/asset/common/SoundFileValidators.java new file mode 100644 index 0000000..edacb65 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/common/SoundFileValidators.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.server.core.asset.common; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SoundFileValidators { + public static final SoundFileValidators.ChannelValidator MONO = new SoundFileValidators.ChannelValidator(1); + public static final SoundFileValidators.ChannelValidator STEREO = new SoundFileValidators.ChannelValidator(2); + private static final String MONO_STRING = "Mono"; + private static final String STEREO_STRING = "Stereo"; + + public SoundFileValidators() { + } + + @Nonnull + public static String getEncoding(int channelCount) { + return switch (channelCount) { + case 1 -> "Mono"; + case 2 -> "Stereo"; + default -> throw new IllegalArgumentException("Invalid channel count: " + channelCount); + }; + } + + public static class ChannelValidator implements Validator { + private final int channelCount; + + public ChannelValidator(int channelCount) { + assert channelCount == 1 || channelCount == 2; + + this.channelCount = channelCount; + } + + public void accept(@Nullable String s, @Nonnull ValidationResults results) { + if (s != null) { + OggVorbisInfoCache.OggVorbisInfo info = OggVorbisInfoCache.getNow(s); + if (info == null) { + results.fail("No such ogg file: " + s); + } else { + if (info.channels != this.channelCount) { + results.fail( + "Sound file '" + + s + + "' is " + + SoundFileValidators.getEncoding(info.channels) + + " instead of " + + SoundFileValidators.getEncoding(this.channelCount) + ); + } + } + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/common/asset/FileCommonAsset.java b/src/com/hypixel/hytale/server/core/asset/common/asset/FileCommonAsset.java new file mode 100644 index 0000000..5a0c3f5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/common/asset/FileCommonAsset.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.server.core.asset.common.asset; + +import com.hypixel.hytale.server.core.asset.common.CommonAsset; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class FileCommonAsset extends CommonAsset { + private final Path file; + + public FileCommonAsset(Path file, @Nonnull String name, byte[] bytes) { + super(name, bytes); + this.file = file; + } + + public FileCommonAsset(Path file, @Nonnull String name, @Nonnull String hash, byte[] bytes) { + super(name, hash, bytes); + this.file = file; + } + + public Path getFile() { + return this.file; + } + + @Nonnull + @Override + public CompletableFuture getBlob0() { + return CompletableFuture.supplyAsync(SneakyThrow.sneakySupplier(() -> Files.readAllBytes(this.file))); + } + + @Nonnull + @Override + public String toString() { + return "FileCommonAsset{file=" + this.file + ", " + super.toString() + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/common/events/CommonAssetMonitorEvent.java b/src/com/hypixel/hytale/server/core/asset/common/events/CommonAssetMonitorEvent.java new file mode 100644 index 0000000..2a303fa --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/common/events/CommonAssetMonitorEvent.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.server.core.asset.common.events; + +import com.hypixel.hytale.assetstore.event.AssetMonitorEvent; +import java.nio.file.Path; +import java.util.List; + +public class CommonAssetMonitorEvent extends AssetMonitorEvent { + public CommonAssetMonitorEvent( + String assetPack, List createdOrModified, List removed, List createdOrModifiedDirectories, List removedDirectories + ) { + super(assetPack, createdOrModified, removed, createdOrModifiedDirectories, removedDirectories); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/common/events/SendCommonAssetsEvent.java b/src/com/hypixel/hytale/server/core/asset/common/events/SendCommonAssetsEvent.java new file mode 100644 index 0000000..034a20d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/common/events/SendCommonAssetsEvent.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.asset.common.events; + +import com.hypixel.hytale.event.IAsyncEvent; +import com.hypixel.hytale.protocol.Asset; +import com.hypixel.hytale.server.core.io.PacketHandler; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class SendCommonAssetsEvent implements IAsyncEvent { + private final PacketHandler packetHandler; + private final Asset[] assets; + + public SendCommonAssetsEvent(PacketHandler packetHandler, Asset[] assets) { + this.packetHandler = packetHandler; + this.assets = assets; + } + + public PacketHandler getPacketHandler() { + return this.packetHandler; + } + + public Asset[] getRequestedAssets() { + return this.assets; + } + + @Nonnull + @Override + public String toString() { + return "SendCommonAssetsEvent{packetHandler=" + this.packetHandler + ", assets=" + Arrays.toString((Object[])this.assets) + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/modifiers/MovementEffects.java b/src/com/hypixel/hytale/server/core/asset/modifiers/MovementEffects.java new file mode 100644 index 0000000..544fbaa --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/modifiers/MovementEffects.java @@ -0,0 +1,138 @@ +package com.hypixel.hytale.server.core.asset.modifiers; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class MovementEffects implements NetworkSerializable { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(MovementEffects.class, MovementEffects::new) + .appendInherited( + new KeyedCodec<>("DisableAll", Codec.BOOLEAN), + (entityEffect, s) -> entityEffect.disableAll = s, + entityEffect -> entityEffect.disableAll, + (entityEffect, parent) -> entityEffect.disableAll = parent.disableAll + ) + .documentation("Determines whether all movement input is disabled") + .add() + .appendInherited( + new KeyedCodec<>("DisableForward", Codec.BOOLEAN), + (entityEffect, s) -> entityEffect.disableForward = s, + entityEffect -> entityEffect.disableForward, + (entityEffect, parent) -> entityEffect.disableForward = parent.disableForward + ) + .documentation("Determines whether forwards movement input is disabled") + .add() + .appendInherited( + new KeyedCodec<>("DisableBackward", Codec.BOOLEAN), + (entityEffect, s) -> entityEffect.disableBackward = s, + entityEffect -> entityEffect.disableBackward, + (entityEffect, parent) -> entityEffect.disableBackward = parent.disableBackward + ) + .documentation("Determines whether backwards movement input is disabled") + .add() + .appendInherited( + new KeyedCodec<>("DisableLeft", Codec.BOOLEAN), + (entityEffect, s) -> entityEffect.disableLeft = s, + entityEffect -> entityEffect.disableLeft, + (entityEffect, parent) -> entityEffect.disableLeft = parent.disableLeft + ) + .documentation("Determines whether left-strafe movement input is disabled") + .add() + .appendInherited( + new KeyedCodec<>("DisableRight", Codec.BOOLEAN), + (entityEffect, s) -> entityEffect.disableRight = s, + entityEffect -> entityEffect.disableRight, + (entityEffect, parent) -> entityEffect.disableRight = parent.disableRight + ) + .documentation("Determines whether right-strafe movement input is disabled") + .add() + .appendInherited( + new KeyedCodec<>("DisableSprint", Codec.BOOLEAN), + (entityEffect, s) -> entityEffect.disableSprint = s, + entityEffect -> entityEffect.disableSprint, + (entityEffect, parent) -> entityEffect.disableSprint = parent.disableSprint + ) + .documentation("Determines whether sprint input is disabled") + .add() + .appendInherited( + new KeyedCodec<>("DisableJump", Codec.BOOLEAN), + (entityEffect, s) -> entityEffect.disableJump = s, + entityEffect -> entityEffect.disableJump, + (entityEffect, parent) -> entityEffect.disableJump = parent.disableJump + ) + .documentation("Determines whether jump input is disabled") + .add() + .appendInherited( + new KeyedCodec<>("DisableCrouch", Codec.BOOLEAN), + (entityEffect, s) -> entityEffect.disableCrouch = s, + entityEffect -> entityEffect.disableCrouch, + (entityEffect, parent) -> entityEffect.disableCrouch = parent.disableCrouch + ) + .documentation("Determines whether crouch input is disabled") + .add() + .afterDecode((movementEffects, extraInfo) -> { + if (movementEffects.disableAll) { + movementEffects.disableForward = true; + movementEffects.disableBackward = true; + movementEffects.disableLeft = true; + movementEffects.disableRight = true; + movementEffects.disableSprint = true; + movementEffects.disableJump = true; + movementEffects.disableCrouch = true; + } + }) + .build(); + protected boolean disableAll = false; + protected boolean disableForward = false; + protected boolean disableBackward = false; + protected boolean disableLeft = false; + protected boolean disableRight = false; + protected boolean disableSprint = false; + protected boolean disableJump = false; + protected boolean disableCrouch = false; + + protected MovementEffects() { + } + + public boolean isDisableAll() { + return this.disableAll; + } + + @Nonnull + public com.hypixel.hytale.protocol.MovementEffects toPacket() { + com.hypixel.hytale.protocol.MovementEffects packet = new com.hypixel.hytale.protocol.MovementEffects(); + packet.disableForward = this.disableForward; + packet.disableBackward = this.disableBackward; + packet.disableLeft = this.disableLeft; + packet.disableRight = this.disableRight; + packet.disableSprint = this.disableSprint; + packet.disableJump = this.disableJump; + packet.disableCrouch = this.disableCrouch; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "MovementEffects{, disableAll=" + + this.disableAll + + ", disableForward=" + + this.disableForward + + ", disableBackward=" + + this.disableBackward + + ", disableLeft=" + + this.disableLeft + + ", disableRight=" + + this.disableRight + + ", disableSprint=" + + this.disableSprint + + ", disableJump=" + + this.disableJump + + ", disableCrouch=" + + this.disableCrouch + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/monitor/AssetMonitor.java b/src/com/hypixel/hytale/server/core/asset/monitor/AssetMonitor.java new file mode 100644 index 0000000..8b81bcd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/monitor/AssetMonitor.java @@ -0,0 +1,157 @@ +package com.hypixel.hytale.server.core.asset.monitor; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.server.core.util.concurrent.ThreadUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.AccessDeniedException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class AssetMonitor { + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(ThreadUtil.daemon("AssetMonitor Thread")); + private final Map> directoryMonitors = new ConcurrentHashMap<>(); + private final Map fileChangeTasks = new ConcurrentHashMap<>(); + private final Map> directoryHandlerChangeTasks = new ConcurrentHashMap<>(); + @Nonnull + private final PathWatcherThread pathWatcherThread = new PathWatcherThread(this::onChange); + + public AssetMonitor() throws IOException { + this.pathWatcherThread.start(); + } + + public void shutdown() { + this.pathWatcherThread.shutdown(); + } + + public void monitorDirectoryFiles(@Nonnull Path path, @Nonnull AssetMonitorHandler handler) { + if (!Files.isDirectory(path)) { + throw new IllegalArgumentException(String.valueOf(path)); + } else { + try { + Path normalize = path.toAbsolutePath().normalize(); + LOGGER.at(Level.FINE).log("Monitoring Directory: %s", normalize); + this.directoryMonitors.computeIfAbsent(normalize, SneakyThrow.sneakyFunction(k -> { + this.pathWatcherThread.addPath(k); + return new ObjectArrayList(); + })).add(handler); + } catch (Exception var4) { + LOGGER.at(Level.SEVERE).withCause(new SkipSentryException(var4)).log("Failed to monitor directory: %s", path); + } + } + } + + public void removeMonitorDirectoryFiles(@Nonnull Path path, @Nonnull Object key) { + if (!Files.isDirectory(path)) { + throw new IllegalArgumentException(String.valueOf(path)); + } else { + try { + Path normalize = path.toAbsolutePath().normalize(); + LOGGER.at(Level.FINE).log("Monitoring Directory: %s", normalize); + this.directoryMonitors.computeIfAbsent(normalize, SneakyThrow.sneakyFunction(k -> { + this.pathWatcherThread.addPath(k); + return new ObjectArrayList(); + })).removeIf(v -> v.getKey().equals(key)); + } catch (Exception var4) { + LOGGER.at(Level.SEVERE).withCause(new SkipSentryException(var4)).log("Failed to monitor directory: %s", path); + } + } + } + + protected void onChange(@Nonnull Path file, EventKind eventKind) { + LOGGER.at(Level.FINER).log("onChange: %s of %s", file, eventKind); + Path path = file.toAbsolutePath().normalize(); + FileChangeTask oldTask = this.fileChangeTasks.remove(path); + if (oldTask != null) { + oldTask.cancelSchedule(); + } + + for (Map tasks : this.directoryHandlerChangeTasks.values()) { + for (DirectoryHandlerChangeTask task : tasks.values()) { + task.removePath(path); + } + } + + boolean createdOrModified = eventKind == EventKind.ENTRY_CREATE || eventKind == EventKind.ENTRY_MODIFY; + if (createdOrModified && !Files.exists(path)) { + LOGGER.at(Level.WARNING).log("The asset file '%s' was deleted before we could load/update it!", path); + } else { + try { + this.fileChangeTasks.put(path, new FileChangeTask(this, path, new PathEvent(eventKind, System.nanoTime()))); + } catch (FileNotFoundException | AccessDeniedException | NoSuchFileException var9) { + LOGGER.at(Level.WARNING).log("The asset file '%s' was deleted before we could load/update it!", path); + } catch (IOException var10) { + LOGGER.at(Level.SEVERE).withCause(var10).log("Failed to queue asset to be reloaded %s", path); + } + } + } + + public void onDelayedChange(@Nonnull Path path, @Nonnull PathEvent pathEvent) { + LOGGER.at(Level.FINER).log("onDelayedChange: %s of %s", path, pathEvent); + + for (Entry> entry : this.directoryMonitors.entrySet()) { + Path parent = entry.getKey(); + if (path.startsWith(parent)) { + Map tasks = this.directoryHandlerChangeTasks + .computeIfAbsent(parent, k -> new ConcurrentHashMap<>()); + + for (AssetMonitorHandler directoryHandler : entry.getValue()) { + try { + if (directoryHandler.test(path, pathEvent.getEventKind())) { + tasks.computeIfAbsent(directoryHandler, handler -> new DirectoryHandlerChangeTask(this, parent, handler)).addPath(path, pathEvent); + } + } catch (Exception var10) { + LOGGER.at(Level.SEVERE).withCause(var10).log("Failed to run directoryHandler.test for parent: %s, %s of %s", parent, path, pathEvent); + } + } + } + } + } + + public void removeFileChangeTask(@Nonnull FileChangeTask fileChangeTask) { + this.fileChangeTasks.remove(fileChangeTask.getPath()); + } + + public void markChanged(@Nonnull Path path) { + for (Entry> entry : this.directoryHandlerChangeTasks.entrySet()) { + Path parent = entry.getKey(); + if (path.startsWith(parent)) { + for (DirectoryHandlerChangeTask hookChangeTask : entry.getValue().values()) { + hookChangeTask.markChanged(); + } + } + } + } + + public void removeHookChangeTask(@Nonnull DirectoryHandlerChangeTask directoryHandlerChangeTask) { + AssetMonitorHandler hook = directoryHandlerChangeTask.getHandler(); + this.directoryHandlerChangeTasks.compute(directoryHandlerChangeTask.getParent(), (k, map) -> { + if (map == null) { + return null; + } else { + map.remove(hook); + return map.isEmpty() ? null : map; + } + }); + } + + @Nonnull + public static ScheduledFuture runTask(@Nonnull Runnable task, long millisDelay) { + return EXECUTOR.scheduleWithFixedDelay(task, millisDelay, millisDelay, TimeUnit.MILLISECONDS); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/monitor/AssetMonitorHandler.java b/src/com/hypixel/hytale/server/core/asset/monitor/AssetMonitorHandler.java new file mode 100644 index 0000000..a3019d6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/monitor/AssetMonitorHandler.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.core.asset.monitor; + +import java.nio.file.Path; +import java.util.Map; +import java.util.function.BiPredicate; +import java.util.function.Consumer; + +public interface AssetMonitorHandler extends BiPredicate, Consumer> { + Object getKey(); +} diff --git a/src/com/hypixel/hytale/server/core/asset/monitor/DirectoryHandlerChangeTask.java b/src/com/hypixel/hytale/server/core/asset/monitor/DirectoryHandlerChangeTask.java new file mode 100644 index 0000000..a799469 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/monitor/DirectoryHandlerChangeTask.java @@ -0,0 +1,121 @@ +package com.hypixel.hytale.server.core.asset.monitor; + +import com.hypixel.hytale.logger.HytaleLogger; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.AbstractMap.SimpleEntry; +import java.util.Map.Entry; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class DirectoryHandlerChangeTask implements Runnable { + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final long ACCUMULATION_DELAY_MILLIS = 1000L; + private final AssetMonitor assetMonitor; + private final Path parent; + private final AssetMonitorHandler handler; + @Nonnull + private final ScheduledFuture task; + private final AtomicBoolean changed = new AtomicBoolean(true); + private final Map paths = new Object2ObjectOpenHashMap<>(); + + public DirectoryHandlerChangeTask(AssetMonitor assetMonitor, Path parent, AssetMonitorHandler handler) { + this.assetMonitor = assetMonitor; + this.parent = parent; + this.handler = handler; + this.task = AssetMonitor.runTask(this, 1000L); + } + + @Override + public void run() { + if (!this.changed.getAndSet(false)) { + this.cancelSchedule(); + + try { + LOGGER.at(Level.FINER).log("run: %s", this.paths); + ObjectArrayList> entries = new ObjectArrayList<>(this.paths.size()); + + for (Entry entry : this.paths.entrySet()) { + entries.add(new SimpleEntry<>(entry.getKey(), entry.getValue())); + } + + this.paths.clear(); + entries.sort(Comparator.comparingLong(value -> value.getValue().getTimestamp())); + Set fileNames = new HashSet<>(); + Map eventPaths = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : entries) { + if (!fileNames.add(entry.getKey().getFileName().toString())) { + LOGGER.at(Level.FINER).log("run handler.accept(%s)", eventPaths); + this.handler.accept(eventPaths); + eventPaths = new Object2ObjectOpenHashMap<>(); + fileNames.clear(); + } + + eventPaths.put(entry.getKey(), entry.getValue().getEventKind()); + } + + if (!eventPaths.isEmpty()) { + LOGGER.at(Level.FINER).log("run handler.accept(%s)", eventPaths); + this.handler.accept(eventPaths); + } + } catch (Exception var6) { + LOGGER.at(Level.SEVERE).withCause(var6).log("Failed to run: %s", this); + } + } + } + + public AssetMonitor getAssetMonitor() { + return this.assetMonitor; + } + + public Path getParent() { + return this.parent; + } + + public AssetMonitorHandler getHandler() { + return this.handler; + } + + public void addPath(Path path, PathEvent pathEvent) { + LOGGER.at(Level.FINEST).log("addPath(%s, %s): %s", path, pathEvent, this); + this.paths.put(path, pathEvent); + this.changed.set(true); + } + + public void removePath(Path path) { + LOGGER.at(Level.FINEST).log("removePath(%s, %s): %s", path, this); + this.paths.remove(path); + if (this.paths.isEmpty()) { + this.cancelSchedule(); + } else { + this.changed.set(true); + } + } + + public void markChanged() { + AssetMonitor.LOGGER.at(Level.FINEST).log("markChanged(): %s", this); + this.changed.set(true); + } + + public void cancelSchedule() { + LOGGER.at(Level.FINEST).log("cancelSchedule(): %s", this); + this.assetMonitor.removeHookChangeTask(this); + if (this.task != null && !this.task.isDone()) { + this.task.cancel(false); + } + } + + @Nonnull + @Override + public String toString() { + return "DirectoryHandlerChangeTask{parent=" + this.parent + ", handler=" + this.handler + ", changed=" + this.changed + ", paths=" + this.paths + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/monitor/EventKind.java b/src/com/hypixel/hytale/server/core/asset/monitor/EventKind.java new file mode 100644 index 0000000..7dee84b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/monitor/EventKind.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.asset.monitor; + +import java.nio.file.Path; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent.Kind; +import javax.annotation.Nonnull; + +public enum EventKind { + ENTRY_CREATE, + ENTRY_DELETE, + ENTRY_MODIFY; + + private EventKind() { + } + + @Nonnull + public static EventKind parse(Kind kind) { + if (kind == StandardWatchEventKinds.ENTRY_CREATE) { + return ENTRY_CREATE; + } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { + return ENTRY_DELETE; + } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) { + return ENTRY_MODIFY; + } else { + throw new IllegalStateException("Unknown type: " + kind); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/monitor/FileChangeTask.java b/src/com/hypixel/hytale/server/core/asset/monitor/FileChangeTask.java new file mode 100644 index 0000000..feeb3d5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/monitor/FileChangeTask.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.server.core.asset.monitor; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.concurrent.ScheduledFuture; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class FileChangeTask implements Runnable { + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final long FILE_SIZE_CHECK_DELAY_MILLIS = 200L; + private final AssetMonitor assetMonitor; + @Nonnull + private final Path path; + @Nonnull + private final PathEvent pathEvent; + private final boolean createdOrModified; + @Nonnull + private final ScheduledFuture task; + private long lastSize; + + public FileChangeTask(AssetMonitor assetMonitor, @Nonnull Path path, @Nonnull PathEvent pathEvent) throws IOException { + this.assetMonitor = assetMonitor; + this.path = path; + this.pathEvent = pathEvent; + this.createdOrModified = pathEvent.getEventKind() == EventKind.ENTRY_CREATE || pathEvent.getEventKind() == EventKind.ENTRY_MODIFY; + long size = 0L; + if (this.createdOrModified) { + BasicFileAttributes fileAttributes = Files.readAttributes(path, BasicFileAttributes.class); + if (!fileAttributes.isDirectory()) { + size = fileAttributes.size(); + } + } + + this.lastSize = size; + this.task = AssetMonitor.runTask(this, 200L); + } + + public AssetMonitor getAssetMonitor() { + return this.assetMonitor; + } + + @Nonnull + public Path getPath() { + return this.path; + } + + @Nonnull + public PathEvent getPathEvent() { + return this.pathEvent; + } + + @Override + public void run() { + try { + if (this.createdOrModified) { + if (!Files.exists(this.path)) { + LOGGER.at(Level.WARNING).log("The asset file '%s' was deleted before we could load/update it!", this.path); + this.cancelSchedule(); + return; + } + + BasicFileAttributes fileAttributes = Files.readAttributes(this.path, BasicFileAttributes.class); + if (!fileAttributes.isDirectory()) { + long size = fileAttributes.size(); + if (size > this.lastSize) { + LOGGER.at(Level.FINEST).log("File increased in size: %s, %s, %d > %d", this.path, this.pathEvent, size, this.lastSize); + this.lastSize = size; + this.assetMonitor.markChanged(this.path); + return; + } + } + } + + this.cancelSchedule(); + this.assetMonitor.onDelayedChange(this.path, this.pathEvent); + } catch (FileNotFoundException | NoSuchFileException var4) { + LOGGER.at(Level.SEVERE).withCause(new SkipSentryException(var4)).log("The asset file '%s' was deleted before we could load/update it!", this.path); + } catch (Throwable var5) { + LOGGER.at(Level.SEVERE).withCause(var5).log("Failed to handle file change %s", this.path); + } + } + + public void cancelSchedule() { + LOGGER.at(Level.FINEST).log("cancelSchedule(): %s", this); + this.assetMonitor.removeFileChangeTask(this); + if (this.task != null && !this.task.isDone()) { + this.task.cancel(false); + } + } + + @Nonnull + @Override + public String toString() { + return "FileChangeTask{path=" + this.path + ", eventKind=" + this.pathEvent + ", lastSize=" + this.lastSize + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/monitor/PathEvent.java b/src/com/hypixel/hytale/server/core/asset/monitor/PathEvent.java new file mode 100644 index 0000000..c20a4a8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/monitor/PathEvent.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.server.core.asset.monitor; + +import javax.annotation.Nonnull; + +public class PathEvent { + private final EventKind eventKind; + private final long timestamp; + + public PathEvent(EventKind eventKind, long timestamp) { + this.eventKind = eventKind; + this.timestamp = timestamp; + } + + public EventKind getEventKind() { + return this.eventKind; + } + + public long getTimestamp() { + return this.timestamp; + } + + @Nonnull + @Override + public String toString() { + return "PathEvent{eventKind=" + this.eventKind + ", timestamp=" + this.timestamp + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/monitor/PathWatcherThread.java b/src/com/hypixel/hytale/server/core/asset/monitor/PathWatcherThread.java new file mode 100644 index 0000000..41a415a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/monitor/PathWatcherThread.java @@ -0,0 +1,151 @@ +package com.hypixel.hytale.server.core.asset.monitor; + +import com.hypixel.hytale.common.util.SystemUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import com.sun.nio.file.ExtendedWatchEventModifier; +import com.sun.nio.file.SensitivityWatchEventModifier; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.nio.file.WatchEvent.Kind; +import java.nio.file.WatchEvent.Modifier; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.logging.Level; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +public class PathWatcherThread implements Runnable { + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final boolean HAS_FILE_TREE_SUPPORT = SystemUtil.TYPE == SystemUtil.SystemType.WINDOWS; + public static final Kind[] WATCH_EVENT_KINDS = new Kind[]{ + StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY + }; + private final BiConsumer consumer; + @Nonnull + private final Thread thread; + private final WatchService service; + private final Map registered = new ConcurrentHashMap<>(); + + public PathWatcherThread(BiConsumer consumer) throws IOException { + this.consumer = consumer; + this.thread = new Thread(this, "PathWatcher"); + this.thread.setDaemon(true); + this.service = FileSystems.getDefault().newWatchService(); + } + + @Override + public final void run() { + try { + while (!Thread.interrupted()) { + WatchKey key = this.service.take(); + Path directory = (Path)key.watchable(); + + for (WatchEvent event : key.pollEvents()) { + Kind kind = event.kind(); + if (kind == StandardWatchEventKinds.OVERFLOW) { + LOGGER.at(Level.WARNING) + .log( + "Event Overflow, Unable to detect all file changed! This may cause server instability!! More than AbstractWatchKey.MAX_EVENT_LIST_SIZE queued events (512)" + ); + } else { + Path path = directory.resolve((Path)event.context()); + if (!HAS_FILE_TREE_SUPPORT && event.kind() == StandardWatchEventKinds.ENTRY_CREATE && Files.isDirectory(path)) { + this.addPath(path); + } + + this.consumer.accept(path, EventKind.parse((Kind)event.kind())); + } + } + + if (!key.reset()) { + break; + } + } + } catch (InterruptedException var9) { + Thread.currentThread().interrupt(); + } catch (Throwable var10) { + LOGGER.at(Level.SEVERE).withCause(var10).log("Exception occurred when polling:"); + } + + LOGGER.at(Level.INFO).log("Stopped polling for changes in assets. Server will need to be rebooted to load changes!"); + + try { + this.service.close(); + } catch (IOException var8) { + } + + this.registered.clear(); + } + + public void start() { + this.thread.start(); + } + + public void shutdown() { + this.thread.interrupt(); + + try { + this.thread.join(1000L); + + try { + this.service.close(); + } catch (IOException var2) { + } + + this.registered.clear(); + } catch (InterruptedException var3) { + Thread.currentThread().interrupt(); + } + } + + public void addPath(Path path) throws IOException { + path = path.toAbsolutePath(); + if (Files.isRegularFile(path)) { + path = path.getParent(); + } + + Path parent = path; + + do { + WatchKey keys = this.registered.get(parent); + if (keys != null) { + if (!HAS_FILE_TREE_SUPPORT) { + this.watchPath(path); + } + + return; + } + } while ((parent = parent.getParent()) != null); + + this.watchPath(path); + } + + private void watchPath(@Nonnull Path path) throws IOException { + if (HAS_FILE_TREE_SUPPORT) { + this.registered.put(path, path.register(this.service, WATCH_EVENT_KINDS, SensitivityWatchEventModifier.HIGH, ExtendedWatchEventModifier.FILE_TREE)); + LOGGER.at(Level.FINEST).log("Register path: %s", path); + } else { + Modifier[] modifiers = new Modifier[]{SensitivityWatchEventModifier.HIGH}; + + try (Stream stream = Files.walk(path, FileUtil.DEFAULT_WALK_TREE_OPTIONS_ARRAY)) { + stream.forEach(SneakyThrow.sneakyConsumer(childPath -> { + if (Files.isDirectory(childPath)) { + this.registered.put(childPath, childPath.register(this.service, WATCH_EVENT_KINDS, modifiers)); + LOGGER.at(Level.FINEST).log("Register path: %s", childPath); + } + })); + } + } + + LOGGER.at(Level.FINER).log("Watching path: %s", path); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/packet/AssetPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/packet/AssetPacketGenerator.java new file mode 100644 index 0000000..88b6024 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/packet/AssetPacketGenerator.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.server.core.asset.packet; + +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.protocol.Packet; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class AssetPacketGenerator, M extends AssetMap> { + public AssetPacketGenerator() { + } + + public abstract Packet generateInitPacket(M var1, Map var2); + + public abstract Packet generateUpdatePacket(M var1, Map var2, @Nonnull AssetUpdateQuery var3); + + @Nullable + public abstract Packet generateRemovePacket(M var1, Set var2, @Nonnull AssetUpdateQuery var3); +} diff --git a/src/com/hypixel/hytale/server/core/asset/packet/DefaultAssetPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/packet/DefaultAssetPacketGenerator.java new file mode 100644 index 0000000..cbe1cdf --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/packet/DefaultAssetPacketGenerator.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.core.asset.packet; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.protocol.Packet; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; + +public abstract class DefaultAssetPacketGenerator>> + extends SimpleAssetPacketGenerator> { + public DefaultAssetPacketGenerator() { + } + + public abstract Packet generateInitPacket(DefaultAssetMap var1, Map var2); + + public abstract Packet generateUpdatePacket(Map var1); + + @Nullable + public abstract Packet generateRemovePacket(Set var1); + + public final Packet generateUpdatePacket(DefaultAssetMap assetMap, Map loadedAssets) { + return this.generateUpdatePacket(loadedAssets); + } + + @Nullable + public final Packet generateRemovePacket(DefaultAssetMap assetMap, Set removed) { + return this.generateRemovePacket(removed); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/packet/SimpleAssetPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/packet/SimpleAssetPacketGenerator.java new file mode 100644 index 0000000..6e0afb7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/packet/SimpleAssetPacketGenerator.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.core.asset.packet; + +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.protocol.Packet; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class SimpleAssetPacketGenerator, M extends AssetMap> extends AssetPacketGenerator { + public SimpleAssetPacketGenerator() { + } + + @Override + public abstract Packet generateInitPacket(M var1, Map var2); + + @Override + public Packet generateUpdatePacket(M assetMap, Map loadedAssets, @Nonnull AssetUpdateQuery query) { + return this.generateUpdatePacket(assetMap, loadedAssets); + } + + @Override + public Packet generateRemovePacket(M assetMap, Set removed, @Nonnull AssetUpdateQuery query) { + return this.generateRemovePacket(assetMap, removed); + } + + protected abstract Packet generateUpdatePacket(M var1, Map var2); + + @Nullable + protected abstract Packet generateRemovePacket(M var1, Set var2); +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/AmbienceFXPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/AmbienceFXPacketGenerator.java new file mode 100644 index 0000000..104a1f8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/AmbienceFXPacketGenerator.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.asset.type.ambiencefx; + +import com.hypixel.hytale.assetstore.map.IndexedAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateAmbienceFX; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.ambiencefx.config.AmbienceFX; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class AmbienceFXPacketGenerator extends SimpleAssetPacketGenerator> { + public AmbienceFXPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedAssetMap assetMap, @Nonnull Map assets) { + UpdateAmbienceFX packet = new UpdateAmbienceFX(); + packet.type = UpdateType.Init; + packet.ambienceFX = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.ambienceFX.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateUpdatePacket(@Nonnull IndexedAssetMap assetMap, @Nonnull Map loadedAssets) { + UpdateAmbienceFX packet = new UpdateAmbienceFX(); + packet.type = UpdateType.AddOrUpdate; + packet.ambienceFX = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.ambienceFX.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateRemovePacket(@Nonnull IndexedAssetMap assetMap, @Nonnull Set removed) { + UpdateAmbienceFX packet = new UpdateAmbienceFX(); + packet.type = UpdateType.Remove; + packet.ambienceFX = new Object2ObjectOpenHashMap<>(); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.ambienceFX.put(index, null); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFX.java b/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFX.java new file mode 100644 index 0000000..cffb8c6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFX.java @@ -0,0 +1,244 @@ +package com.hypixel.hytale.server.core.asset.type.ambiencefx.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDefaultCollapsedState; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorSectionStart; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.server.core.asset.type.audiocategory.config.AudioCategory; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AmbienceFX implements JsonAssetWithMap>, NetworkSerializable { + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(AmbienceFX::getAssetStore)); + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + AmbienceFX.class, + AmbienceFX::new, + Codec.STRING, + (ambienceFX, k) -> ambienceFX.id = k, + ambienceFX -> ambienceFX.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("Conditions", AmbienceFXConditions.CODEC), + (ambienceFX, l) -> ambienceFX.conditions = l, + ambienceFX -> ambienceFX.conditions, + (ambienceFX, parent) -> ambienceFX.conditions = parent.conditions + ) + .metadata(new UIEditorSectionStart("Conditions")) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("Sounds", new ArrayCodec<>(AmbienceFXSound.CODEC, AmbienceFXSound[]::new)), + (ambienceFX, l) -> ambienceFX.sounds = l, + ambienceFX -> ambienceFX.sounds, + (ambienceFX, parent) -> ambienceFX.sounds = parent.sounds + ) + .metadata(new UIEditorSectionStart("Audio")) + .add() + .appendInherited( + new KeyedCodec<>("Music", AmbienceFXMusic.CODEC), + (ambienceFX, l) -> ambienceFX.music = l, + ambienceFX -> ambienceFX.music, + (ambienceFX, parent) -> ambienceFX.music = parent.music + ) + .add() + .appendInherited( + new KeyedCodec<>("AmbientBed", AmbienceFXAmbientBed.CODEC), + (ambienceFX, l) -> ambienceFX.ambientBed = l, + ambienceFX -> ambienceFX.ambientBed, + (ambienceFX, parent) -> ambienceFX.ambientBed = parent.ambientBed + ) + .add() + .appendInherited( + new KeyedCodec<>("SoundEffect", AmbienceFXSoundEffect.CODEC), + (ambienceFX, l) -> ambienceFX.soundEffect = l, + ambienceFX -> ambienceFX.soundEffect, + (ambienceFX, parent) -> ambienceFX.soundEffect = parent.soundEffect + ) + .add() + .appendInherited( + new KeyedCodec<>("Priority", Codec.INTEGER), + (ambienceFX, i) -> ambienceFX.priority = i, + ambienceFX -> ambienceFX.priority, + (ambienceFX, parent) -> ambienceFX.priority = parent.priority + ) + .addValidator(Validators.greaterThanOrEqual(0)) + .documentation("Priority for this AmbienceFX. Only applies to music and sound effect. Higher number means higher priority.") + .add() + .appendInherited( + new KeyedCodec<>("BlockedAmbienceFxIds", Codec.STRING_ARRAY), + (ambienceFX, s) -> ambienceFX.blockedAmbienceFxIds = s, + ambienceFX -> ambienceFX.blockedAmbienceFxIds, + (ambienceFX, parent) -> ambienceFX.blockedAmbienceFxIds = parent.blockedAmbienceFxIds + ) + .addValidatorLate(() -> VALIDATOR_CACHE.getArrayValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("AudioCategory", Codec.STRING), + (ambienceFX, s) -> ambienceFX.audioCategoryId = s, + ambienceFX -> ambienceFX.audioCategoryId, + (ambienceFX, parent) -> ambienceFX.audioCategoryId = parent.audioCategoryId + ) + .addValidator(AudioCategory.VALIDATOR_CACHE.getValidator()) + .documentation("Audio category to assign this ambienceFX to for additional property routing. Only affects ambient bed and music, not emitters.") + .add() + .afterDecode(ambienceFX -> { + if (ambienceFX.audioCategoryId != null) { + ambienceFX.audioCategoryIndex = AudioCategory.getAssetMap().getIndex(ambienceFX.audioCategoryId); + } + }) + .build(); + public static final int EMPTY_ID = 0; + public static final AmbienceFX EMPTY = new AmbienceFX() { + { + this.id = "Empty"; + } + }; + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected AmbienceFXConditions conditions; + protected AmbienceFXSound[] sounds; + protected AmbienceFXMusic music; + protected AmbienceFXAmbientBed ambientBed; + protected AmbienceFXSoundEffect soundEffect; + protected int priority = 0; + protected String[] blockedAmbienceFxIds; + @Nullable + protected String audioCategoryId = null; + protected transient int audioCategoryIndex = 0; + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(AmbienceFX.class); + } + + return ASSET_STORE; + } + + public static IndexedAssetMap getAssetMap() { + return (IndexedAssetMap)getAssetStore().getAssetMap(); + } + + public AmbienceFX(String id) { + this.id = id; + } + + protected AmbienceFX() { + } + + @Nonnull + public com.hypixel.hytale.protocol.AmbienceFX toPacket() { + com.hypixel.hytale.protocol.AmbienceFX cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.AmbienceFX packet = new com.hypixel.hytale.protocol.AmbienceFX(); + packet.id = this.id; + if (this.conditions != null) { + packet.conditions = this.conditions.toPacket(); + } + + if (this.sounds != null && this.sounds.length > 0) { + packet.sounds = ArrayUtil.copyAndMutate(this.sounds, AmbienceFXSound::toPacket, com.hypixel.hytale.protocol.AmbienceFXSound[]::new); + } + + if (this.music != null) { + packet.music = this.music.toPacket(); + } + + if (this.ambientBed != null) { + packet.ambientBed = this.ambientBed.toPacket(); + } + + if (this.soundEffect != null) { + packet.soundEffect = this.soundEffect.toPacket(); + } + + packet.priority = this.priority; + if (this.blockedAmbienceFxIds != null) { + packet.blockedAmbienceFxIndices = new int[this.blockedAmbienceFxIds.length]; + + for (int i = 0; i < this.blockedAmbienceFxIds.length; i++) { + packet.blockedAmbienceFxIndices[i] = getAssetMap().getIndex(this.blockedAmbienceFxIds[i]); + } + } + + packet.audioCategoryIndex = this.audioCategoryIndex; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + public String getId() { + return this.id; + } + + public AmbienceFXConditions getConditions() { + return this.conditions; + } + + public AmbienceFXSound[] getSounds() { + return this.sounds; + } + + public AmbienceFXMusic getMusic() { + return this.music; + } + + public AmbienceFXAmbientBed getAmbientBed() { + return this.ambientBed; + } + + public AmbienceFXSoundEffect getSoundEffect() { + return this.soundEffect; + } + + public int getPriority() { + return this.priority; + } + + public String[] getBlockedAmbienceFxIds() { + return this.blockedAmbienceFxIds; + } + + @Nonnull + @Override + public String toString() { + return "AmbienceFX{id='" + + this.id + + "', conditions=" + + this.conditions + + ", sounds=" + + Arrays.toString((Object[])this.sounds) + + ", music=" + + this.music + + ", ambientBed=" + + this.ambientBed + + ", soundEffect='" + + this.soundEffect + + ", priority=" + + this.priority + + "', blockedAmbienceFxIds=" + + Arrays.toString((Object[])this.blockedAmbienceFxIds) + + ", audioCategoryId=" + + this.audioCategoryId + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXAmbientBed.java b/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXAmbientBed.java new file mode 100644 index 0000000..e1b1625 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXAmbientBed.java @@ -0,0 +1,105 @@ +package com.hypixel.hytale.server.core.asset.type.ambiencefx.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.AudioUtil; +import com.hypixel.hytale.protocol.AmbienceTransitionSpeed; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.asset.common.SoundFileValidators; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class AmbienceFXAmbientBed implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(AmbienceFXAmbientBed.class, AmbienceFXAmbientBed::new) + .appendInherited( + new KeyedCodec<>("Track", Codec.STRING), + (ambienceFXAmbientBed, s) -> ambienceFXAmbientBed.track = s, + ambienceFXAmbientBed -> ambienceFXAmbientBed.track, + (ambienceFXAmbientBed, parent) -> ambienceFXAmbientBed.track = parent.track + ) + .addValidator(Validators.nonNull()) + .addValidator(CommonAssetValidator.SOUNDS) + .addValidator(SoundFileValidators.STEREO) + .add() + .appendInherited( + new KeyedCodec<>("Volume", Codec.FLOAT), + (ambienceFXAmbientBed, f) -> ambienceFXAmbientBed.decibels = f, + ambienceFXAmbientBed -> ambienceFXAmbientBed.decibels, + (ambienceFXAmbientBed, parent) -> ambienceFXAmbientBed.decibels = parent.decibels + ) + .metadata(new UIEditor(new UIEditor.FormattedNumber(null, " dB", null))) + .addValidator(Validators.range(-100.0F, 10.0F)) + .add() + .appendInherited( + new KeyedCodec<>("TransitionSpeed", new EnumCodec<>(AmbienceTransitionSpeed.class)), + (ambienceFXAmbientBed, e) -> ambienceFXAmbientBed.transitionSpeed = e, + ambienceFXAmbientBed -> ambienceFXAmbientBed.transitionSpeed, + (ambienceFXAmbientBed, parent) -> ambienceFXAmbientBed.transitionSpeed = parent.transitionSpeed + ) + .documentation( + "How quickly to transition to this ambient bed and fade out any ambient beds that are stopping. For fading out stopping ambient beds, faster transitions take priority. Fade-ins are already fast by default." + ) + .add() + .afterDecode(AmbienceFXAmbientBed::processConfig) + .build(); + protected String track; + protected float decibels = 0.0F; + protected transient float volume = 1.0F; + protected AmbienceTransitionSpeed transitionSpeed = AmbienceTransitionSpeed.Default; + + public AmbienceFXAmbientBed(String track, float decibels, AmbienceTransitionSpeed transitionSpeed) { + this.track = track; + this.decibels = decibels; + this.transitionSpeed = transitionSpeed; + } + + protected AmbienceFXAmbientBed() { + } + + @Nonnull + public com.hypixel.hytale.protocol.AmbienceFXAmbientBed toPacket() { + com.hypixel.hytale.protocol.AmbienceFXAmbientBed packet = new com.hypixel.hytale.protocol.AmbienceFXAmbientBed(); + packet.track = this.track; + packet.volume = this.volume; + packet.transitionSpeed = this.transitionSpeed; + return packet; + } + + public String getTrack() { + return this.track; + } + + public float getDecibels() { + return this.decibels; + } + + public float getVolume() { + return this.volume; + } + + public AmbienceTransitionSpeed getTransitionSpeed() { + return this.transitionSpeed; + } + + protected void processConfig() { + this.volume = AudioUtil.decibelsToLinearGain(this.decibels); + } + + @Nonnull + @Override + public String toString() { + return "AmbienceFXAmbientBed{track='" + + this.track + + "', decibels=" + + this.decibels + + ", volume=" + + this.volume + + ", transitionSpeed=" + + this.transitionSpeed + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXBlockSoundSet.java b/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXBlockSoundSet.java new file mode 100644 index 0000000..90d7fcc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXBlockSoundSet.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.server.core.asset.type.ambiencefx.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.Rangef; +import com.hypixel.hytale.server.core.asset.type.blocksound.config.BlockSoundSet; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class AmbienceFXBlockSoundSet implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(AmbienceFXBlockSoundSet.class, AmbienceFXBlockSoundSet::new) + .append( + new KeyedCodec<>("BlockSoundSetId", Codec.STRING), + (ambienceFXBlockSoundSet, s) -> ambienceFXBlockSoundSet.blockSoundSetId = s, + ambienceFXBlockSoundSet -> ambienceFXBlockSoundSet.blockSoundSetId + ) + .addValidator(Validators.nonNull()) + .addValidator(BlockSoundSet.VALIDATOR_CACHE.getValidator()) + .add() + .addField( + new KeyedCodec<>("Percent", ProtocolCodecs.RANGEF), + (ambienceFXBlockSoundSet, o) -> ambienceFXBlockSoundSet.percent = o, + ambienceFXBlockSoundSet -> ambienceFXBlockSoundSet.percent + ) + .afterDecode(AmbienceFXBlockSoundSet::processConfig) + .build(); + public static final Rangef DEFAULT_PERCENT = new Rangef(0.0F, 0.0F); + protected String blockSoundSetId; + protected transient int blockSoundSetIndex; + protected Rangef percent = DEFAULT_PERCENT; + + public AmbienceFXBlockSoundSet(String blockSoundSetId, Rangef percent) { + this.blockSoundSetId = blockSoundSetId; + this.percent = percent; + } + + protected AmbienceFXBlockSoundSet() { + } + + @Nonnull + public com.hypixel.hytale.protocol.AmbienceFXBlockSoundSet toPacket() { + com.hypixel.hytale.protocol.AmbienceFXBlockSoundSet packet = new com.hypixel.hytale.protocol.AmbienceFXBlockSoundSet(); + packet.blockSoundSetIndex = this.blockSoundSetIndex; + packet.percent = this.percent; + return packet; + } + + public String getBlockSoundSetId() { + return this.blockSoundSetId; + } + + public Rangef getPercent() { + return this.percent; + } + + protected void processConfig() { + this.blockSoundSetIndex = BlockSoundSet.getAssetMap().getIndex(this.blockSoundSetId); + } + + @Nonnull + @Override + public String toString() { + return "AmbienceFXBlockSoundSet{blockSoundSetId='" + + this.blockSoundSetId + + "'blockSoundSetIndex='" + + this.blockSoundSetIndex + + "', percent=" + + this.percent + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXConditions.java b/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXConditions.java new file mode 100644 index 0000000..d9d2363 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXConditions.java @@ -0,0 +1,363 @@ +package com.hypixel.hytale.server.core.asset.type.ambiencefx.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.protocol.Range; +import com.hypixel.hytale.protocol.Rangeb; +import com.hypixel.hytale.protocol.Rangef; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.core.asset.type.fluidfx.config.FluidFX; +import com.hypixel.hytale.server.core.asset.type.tagpattern.config.TagPattern; +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class AmbienceFXConditions implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(AmbienceFXConditions.class, AmbienceFXConditions::new) + .appendInherited( + new KeyedCodec<>("Never", Codec.BOOLEAN), + (ambienceFXConditions, l) -> ambienceFXConditions.never = l, + ambienceFXConditions -> ambienceFXConditions.never, + (ambienceFXConditions, parent) -> ambienceFXConditions.never = parent.never + ) + .documentation("If true, this Ambience will never conditionally trigger (but can be set server-side, for example).") + .add() + .appendInherited( + new KeyedCodec<>("EnvironmentIds", Codec.STRING_ARRAY), + (ambienceFXConditions, l) -> ambienceFXConditions.environmentIds = l, + ambienceFXConditions -> ambienceFXConditions.environmentIds, + (ambienceFXConditions, parent) -> ambienceFXConditions.environmentIds = parent.environmentIds + ) + .addValidator(Environment.VALIDATOR_CACHE.getArrayValidator()) + .add() + .appendInherited( + new KeyedCodec<>("EnvironmentTagPattern", TagPattern.CHILD_ASSET_CODEC), + (ambienceFxConditions, t) -> ambienceFxConditions.environmentTagPattern = t, + ambienceFXConditions -> ambienceFXConditions.environmentTagPattern, + (ambienceFXConditions, parent) -> ambienceFXConditions.environmentTagPattern = parent.environmentTagPattern + ) + .addValidator(TagPattern.VALIDATOR_CACHE.getValidator()) + .documentation("A tag pattern to use for matching environments.") + .add() + .appendInherited( + new KeyedCodec<>("WeatherTagPattern", TagPattern.CHILD_ASSET_CODEC), + (ambienceFxConditions, t) -> ambienceFxConditions.weatherTagPattern = t, + ambienceFXConditions -> ambienceFXConditions.weatherTagPattern, + (ambienceFXConditions, parent) -> ambienceFXConditions.weatherTagPattern = parent.weatherTagPattern + ) + .addValidator(TagPattern.VALIDATOR_CACHE.getValidator()) + .documentation("A tag pattern to use for matching weathers.") + .add() + .appendInherited( + new KeyedCodec<>("WeatherIds", Codec.STRING_ARRAY), + (ambienceFXConditions, l) -> ambienceFXConditions.weatherIds = l, + ambienceFXConditions -> ambienceFXConditions.weatherIds, + (ambienceFXConditions, parent) -> ambienceFXConditions.weatherIds = parent.weatherIds + ) + .addValidator(Weather.VALIDATOR_CACHE.getArrayValidator()) + .add() + .appendInherited( + new KeyedCodec<>("FluidFXIds", Codec.STRING_ARRAY), + (ambienceFXConditions, l) -> ambienceFXConditions.fluidFXIds = l, + ambienceFXConditions -> ambienceFXConditions.fluidFXIds, + (ambienceFXConditions, parent) -> ambienceFXConditions.fluidFXIds = parent.fluidFXIds + ) + .addValidator(FluidFX.VALIDATOR_CACHE.getArrayValidator()) + .add() + .appendInherited( + new KeyedCodec<>("SurroundingBlockSoundSets", new ArrayCodec<>(AmbienceFXBlockSoundSet.CODEC, AmbienceFXBlockSoundSet[]::new)), + (ambienceFXConditions, l) -> ambienceFXConditions.surroundingBlockSoundSets = l, + ambienceFXConditions -> ambienceFXConditions.surroundingBlockSoundSets, + (ambienceFXConditions, parent) -> ambienceFXConditions.surroundingBlockSoundSets = parent.surroundingBlockSoundSets + ) + .add() + .appendInherited( + new KeyedCodec<>("Altitude", ProtocolCodecs.RANGE), + (ambienceFXBlockEnvironment, o) -> ambienceFXBlockEnvironment.altitude = o, + ambienceFXBlockEnvironment -> ambienceFXBlockEnvironment.altitude, + (ambienceFXConditions, parent) -> ambienceFXConditions.altitude = parent.altitude + ) + .add() + .appendInherited( + new KeyedCodec<>("Walls", ProtocolCodecs.RANGEB), + (ambienceFXBlockEnvironment, o) -> ambienceFXBlockEnvironment.walls = o, + ambienceFXBlockEnvironment -> ambienceFXBlockEnvironment.walls, + (ambienceFXConditions, parent) -> ambienceFXConditions.walls = parent.walls + ) + .add() + .appendInherited( + new KeyedCodec<>("Roof", Codec.BOOLEAN), + (ambienceFXConditions, aBoolean) -> ambienceFXConditions.roof = aBoolean, + ambienceFXConditions -> ambienceFXConditions.roof, + (ambienceFXConditions, parent) -> ambienceFXConditions.roof = parent.roof + ) + .add() + .appendInherited( + new KeyedCodec<>("RoofMaterialTagPattern", TagPattern.CHILD_ASSET_CODEC), + (ambienceFxConditions, t) -> ambienceFxConditions.roofMaterialTagPattern = t, + ambienceFXConditions -> ambienceFXConditions.roofMaterialTagPattern, + (ambienceFXConditions, parent) -> ambienceFXConditions.roofMaterialTagPattern = parent.roofMaterialTagPattern + ) + .addValidator(TagPattern.VALIDATOR_CACHE.getValidator()) + .documentation("A tag pattern to use for matching roof material. If Roof is not required, will only be matched if a roof is present.") + .add() + .appendInherited( + new KeyedCodec<>("Floor", Codec.BOOLEAN), + (ambienceFXConditions, aBoolean) -> ambienceFXConditions.floor = aBoolean, + ambienceFXConditions -> ambienceFXConditions.floor, + (ambienceFXConditions, parent) -> ambienceFXConditions.floor = parent.floor + ) + .add() + .appendInherited( + new KeyedCodec<>("SunLightLevel", ProtocolCodecs.RANGEB), + (ambienceFXBlockEnvironment, o) -> ambienceFXBlockEnvironment.sunLightLevel = o, + ambienceFXBlockEnvironment -> ambienceFXBlockEnvironment.sunLightLevel, + (ambienceFXConditions, parent) -> ambienceFXConditions.sunLightLevel = parent.sunLightLevel + ) + .add() + .appendInherited( + new KeyedCodec<>("TorchLightLevel", ProtocolCodecs.RANGEB), + (ambienceFXBlockEnvironment, o) -> ambienceFXBlockEnvironment.torchLightLevel = o, + ambienceFXBlockEnvironment -> ambienceFXBlockEnvironment.torchLightLevel, + (ambienceFXConditions, parent) -> ambienceFXConditions.torchLightLevel = parent.torchLightLevel + ) + .add() + .appendInherited( + new KeyedCodec<>("GlobalLightLevel", ProtocolCodecs.RANGEB), + (ambienceFXBlockEnvironment, o) -> ambienceFXBlockEnvironment.globalLightLevel = o, + ambienceFXBlockEnvironment -> ambienceFXBlockEnvironment.globalLightLevel, + (ambienceFXConditions, parent) -> ambienceFXConditions.globalLightLevel = parent.globalLightLevel + ) + .add() + .appendInherited( + new KeyedCodec<>("DayTime", ProtocolCodecs.RANGEF), + (ambienceFXBlockEnvironment, o) -> ambienceFXBlockEnvironment.dayTime = o, + ambienceFXBlockEnvironment -> ambienceFXBlockEnvironment.dayTime, + (ambienceFXConditions, parent) -> ambienceFXConditions.dayTime = parent.dayTime + ) + .add() + .afterDecode(AmbienceFXConditions::processConfig) + .build(); + public static final Range DEFAULT_ALTITUDE = new Range(0, 512); + public static final Rangeb DEFAULT_WALLS = new Rangeb((byte)0, (byte)4); + public static final Rangeb DEFAULT_LIGHT_LEVEL = new Rangeb((byte)0, (byte)15); + public static final Rangef DEFAULT_DAY_TIME = new Rangef(0.0F, 24.0F); + protected boolean never; + protected String[] environmentIds; + protected transient int[] environmentIndices; + protected String[] weatherIds; + protected transient int[] weatherIndices; + protected String environmentTagPattern; + protected String weatherTagPattern; + protected String[] fluidFXIds; + protected transient int[] fluidFXIndices; + protected AmbienceFXBlockSoundSet[] surroundingBlockSoundSets; + protected Range altitude = DEFAULT_ALTITUDE; + protected Rangeb walls = DEFAULT_WALLS; + protected boolean roof; + protected String roofMaterialTagPattern; + protected boolean floor; + protected Rangeb sunLightLevel = DEFAULT_LIGHT_LEVEL; + protected Rangeb torchLightLevel = DEFAULT_LIGHT_LEVEL; + protected Rangeb globalLightLevel = DEFAULT_LIGHT_LEVEL; + protected Rangef dayTime = DEFAULT_DAY_TIME; + + protected AmbienceFXConditions() { + } + + @Nonnull + public com.hypixel.hytale.protocol.AmbienceFXConditions toPacket() { + com.hypixel.hytale.protocol.AmbienceFXConditions packet = new com.hypixel.hytale.protocol.AmbienceFXConditions(); + packet.never = this.never; + if (this.environmentIndices != null && this.environmentIndices.length > 0) { + packet.environmentIndices = this.environmentIndices; + } + + if (this.environmentTagPattern != null) { + packet.environmentTagPatternIndex = TagPattern.getAssetMap().getIndex(this.environmentTagPattern); + } else { + packet.environmentTagPatternIndex = -1; + } + + if (this.weatherIndices != null && this.weatherIndices.length > 0) { + packet.weatherIndices = this.weatherIndices; + } + + if (this.weatherTagPattern != null) { + packet.weatherTagPatternIndex = TagPattern.getAssetMap().getIndex(this.weatherTagPattern); + } else { + packet.weatherTagPatternIndex = -1; + } + + if (this.fluidFXIndices != null) { + packet.fluidFXIndices = this.fluidFXIndices; + } + + if (this.surroundingBlockSoundSets != null && this.surroundingBlockSoundSets.length > 0) { + packet.surroundingBlockSoundSets = ArrayUtil.copyAndMutate( + this.surroundingBlockSoundSets, AmbienceFXBlockSoundSet::toPacket, com.hypixel.hytale.protocol.AmbienceFXBlockSoundSet[]::new + ); + } + + packet.altitude = this.altitude; + packet.walls = this.walls; + packet.roof = this.roof; + if (this.roofMaterialTagPattern != null) { + packet.roofMaterialTagPatternIndex = TagPattern.getAssetMap().getIndex(this.roofMaterialTagPattern); + } else { + packet.roofMaterialTagPatternIndex = -1; + } + + packet.floor = this.floor; + packet.sunLightLevel = this.sunLightLevel; + packet.torchLightLevel = this.torchLightLevel; + packet.globalLightLevel = this.globalLightLevel; + packet.dayTime = this.dayTime; + return packet; + } + + public boolean isNever() { + return this.never; + } + + public String[] getEnvironmentIds() { + return this.environmentIds; + } + + public int[] getEnvironmentIndices() { + return this.environmentIndices; + } + + public String[] getWeatherIds() { + return this.weatherIds; + } + + public int[] getWeatherIndices() { + return this.weatherIndices; + } + + public String[] getFluidFXIds() { + return this.fluidFXIds; + } + + public int[] getFluidFXIndices() { + return this.fluidFXIndices; + } + + public AmbienceFXBlockSoundSet[] getSurroundingBlockSoundSets() { + return this.surroundingBlockSoundSets; + } + + public Range getAltitude() { + return this.altitude; + } + + public Rangeb getWalls() { + return this.walls; + } + + public boolean getRoof() { + return this.roof; + } + + public boolean getFloor() { + return this.floor; + } + + public Rangeb getSunLightLevel() { + return this.sunLightLevel; + } + + public Rangeb getTorchLightLevel() { + return this.torchLightLevel; + } + + public Rangeb getGlobalLightLevel() { + return this.globalLightLevel; + } + + public Rangef getDayTime() { + return this.dayTime; + } + + public boolean isRoof() { + return this.roof; + } + + public boolean isFloor() { + return this.floor; + } + + protected void processConfig() { + if (this.environmentIds != null) { + this.environmentIndices = new int[this.environmentIds.length]; + + for (int i = 0; i < this.environmentIds.length; i++) { + this.environmentIndices[i] = Environment.getAssetMap().getIndex(this.environmentIds[i]); + } + } + + if (this.weatherIds != null) { + this.weatherIndices = new int[this.weatherIds.length]; + + for (int i = 0; i < this.weatherIds.length; i++) { + this.weatherIndices[i] = Weather.getAssetMap().getIndex(this.weatherIds[i]); + } + } + + if (this.fluidFXIds != null) { + this.fluidFXIndices = new int[this.fluidFXIds.length]; + + for (int i = 0; i < this.fluidFXIds.length; i++) { + this.fluidFXIndices[i] = FluidFX.getAssetMap().getIndex(this.fluidFXIds[i]); + } + } + } + + @Nonnull + @Override + public String toString() { + return "AmbienceFXConditions{,never=" + + this.never + + ",environmentIds=" + + Arrays.toString((Object[])this.environmentIds) + + ", environmentIndices=" + + Arrays.toString(this.environmentIndices) + + ", environmentTagPattern=" + + this.environmentTagPattern + + ", weatherIds=" + + Arrays.toString((Object[])this.weatherIds) + + ", weatherIndices=" + + Arrays.toString(this.weatherIndices) + + ", fluidFXIds=" + + Arrays.toString((Object[])this.fluidFXIds) + + ", fluidFXIndices=" + + Arrays.toString(this.fluidFXIndices) + + ", surroundingBlockSoundSets=" + + Arrays.toString((Object[])this.surroundingBlockSoundSets) + + ", altitude=" + + this.altitude + + ", walls=" + + this.walls + + ", roof=" + + this.roof + + ", roofMaterialTagPattern=" + + this.roofMaterialTagPattern + + ", floor=" + + this.floor + + ", sunLightLevel=" + + this.sunLightLevel + + ", torchLightLevel=" + + this.torchLightLevel + + ", globalLightLevel=" + + this.globalLightLevel + + ", dayTime=" + + this.dayTime + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXMusic.java b/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXMusic.java new file mode 100644 index 0000000..aba903a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXMusic.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.server.core.asset.type.ambiencefx.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.codec.validation.validator.ArrayValidator; +import com.hypixel.hytale.common.util.AudioUtil; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class AmbienceFXMusic implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(AmbienceFXMusic.class, AmbienceFXMusic::new) + .appendInherited( + new KeyedCodec<>("Tracks", Codec.STRING_ARRAY), + (ambienceFXMusic, strings) -> ambienceFXMusic.tracks = strings, + ambienceFXMusic -> ambienceFXMusic.tracks, + (ambienceFXMusic, parent) -> ambienceFXMusic.tracks = parent.tracks + ) + .addValidator(Validators.nonEmptyArray()) + .addValidator(new ArrayValidator<>(CommonAssetValidator.MUSIC)) + .add() + .appendInherited( + new KeyedCodec<>("Volume", Codec.FLOAT), + (ambienceFXMusic, f) -> ambienceFXMusic.decibels = f, + ambienceFXMusic -> ambienceFXMusic.decibels, + (ambienceFXMusic, parent) -> ambienceFXMusic.decibels = parent.decibels + ) + .metadata(new UIEditor(new UIEditor.FormattedNumber(null, " dB", null))) + .addValidator(Validators.range(-100.0F, 10.0F)) + .add() + .afterDecode(AmbienceFXMusic::processConfig) + .build(); + protected String[] tracks; + protected float decibels = 0.0F; + protected transient float volume = 1.0F; + + public AmbienceFXMusic(String[] tracks, float decibels) { + this.tracks = tracks; + this.decibels = decibels; + } + + protected AmbienceFXMusic() { + } + + @Nonnull + public com.hypixel.hytale.protocol.AmbienceFXMusic toPacket() { + com.hypixel.hytale.protocol.AmbienceFXMusic packet = new com.hypixel.hytale.protocol.AmbienceFXMusic(); + if (this.tracks != null && this.tracks.length > 0) { + packet.tracks = this.tracks; + } + + packet.volume = this.volume; + return packet; + } + + public String[] getTracks() { + return this.tracks; + } + + public float getDecibels() { + return this.decibels; + } + + public float getVolume() { + return this.volume; + } + + protected void processConfig() { + this.volume = AudioUtil.decibelsToLinearGain(this.decibels); + } + + @Nonnull + @Override + public String toString() { + return "AmbienceFXMusic{tracks=" + Arrays.toString((Object[])this.tracks) + ", decibels=" + this.decibels + ", volume=" + this.volume + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXSound.java b/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXSound.java new file mode 100644 index 0000000..4c90672 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXSound.java @@ -0,0 +1,149 @@ +package com.hypixel.hytale.server.core.asset.type.ambiencefx.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.AmbienceFXAltitude; +import com.hypixel.hytale.protocol.AmbienceFXSoundPlay3D; +import com.hypixel.hytale.protocol.Range; +import com.hypixel.hytale.protocol.Rangef; +import com.hypixel.hytale.server.core.asset.type.blocksound.config.BlockSoundSet; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class AmbienceFXSound implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(AmbienceFXSound.class, AmbienceFXSound::new) + .append( + new KeyedCodec<>("SoundEventId", Codec.STRING), + (ambienceFXSound, o) -> ambienceFXSound.soundEventId = o, + ambienceFXSound -> ambienceFXSound.soundEventId + ) + .addValidator(Validators.nonNull()) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .add() + .addField( + new KeyedCodec<>("Play3D", new EnumCodec<>(AmbienceFXSoundPlay3D.class)), + (ambienceFXSound, o) -> ambienceFXSound.play3D = o, + ambienceFXSound -> ambienceFXSound.play3D + ) + .append( + new KeyedCodec<>("BlockSoundSetId", Codec.STRING), + (ambienceFXSound, o) -> ambienceFXSound.blockSoundSetId = o, + ambienceFXSound -> ambienceFXSound.blockSoundSetId + ) + .addValidator(BlockSoundSet.VALIDATOR_CACHE.getValidator()) + .add() + .addField( + new KeyedCodec<>("Altitude", new EnumCodec<>(AmbienceFXAltitude.class)), + (ambienceFXSound, o) -> ambienceFXSound.altitude = o, + ambienceFXSound -> ambienceFXSound.altitude + ) + .addField( + new KeyedCodec<>("Frequency", ProtocolCodecs.RANGEF), + (ambienceFXSound, o) -> ambienceFXSound.frequency = o, + ambienceFXSound -> ambienceFXSound.frequency + ) + .addField(new KeyedCodec<>("Radius", ProtocolCodecs.RANGE), (ambienceFXSound, o) -> ambienceFXSound.radius = o, ambienceFXSound -> ambienceFXSound.radius) + .afterDecode(AmbienceFXSound::processConfig) + .build(); + public static final Rangef DEFAULT_FREQUENCY = new Rangef(1.0F, 10.0F); + public static final Range DEFAULT_RADIUS = new Range(0, 24); + protected String soundEventId; + protected transient int soundEventIndex; + protected AmbienceFXSoundPlay3D play3D = AmbienceFXSoundPlay3D.Random; + protected String blockSoundSetId; + protected transient int blockSoundSetIndex; + protected AmbienceFXAltitude altitude = AmbienceFXAltitude.Normal; + protected Rangef frequency = DEFAULT_FREQUENCY; + protected Range radius = DEFAULT_RADIUS; + + public AmbienceFXSound( + String soundEventId, AmbienceFXSoundPlay3D play3D, String blockSoundSetId, AmbienceFXAltitude altitude, Rangef frequency, Range radius + ) { + this.soundEventId = soundEventId; + this.play3D = play3D; + this.blockSoundSetId = blockSoundSetId; + this.altitude = altitude; + this.frequency = frequency; + this.radius = radius; + } + + protected AmbienceFXSound() { + } + + @Nonnull + public com.hypixel.hytale.protocol.AmbienceFXSound toPacket() { + com.hypixel.hytale.protocol.AmbienceFXSound packet = new com.hypixel.hytale.protocol.AmbienceFXSound(); + packet.soundEventIndex = this.soundEventIndex; + packet.play3D = this.play3D; + packet.blockSoundSetIndex = this.blockSoundSetIndex; + packet.altitude = this.altitude; + packet.frequency = this.frequency; + packet.radius = this.radius; + return packet; + } + + public String getSoundEventId() { + return this.soundEventId; + } + + public int getSoundEventIndex() { + return this.soundEventIndex; + } + + public AmbienceFXSoundPlay3D getPlay3D() { + return this.play3D; + } + + public String getBlockSoundSetId() { + return this.blockSoundSetId; + } + + public AmbienceFXAltitude getAltitude() { + return this.altitude; + } + + public Rangef getFrequency() { + return this.frequency; + } + + public Range getRadius() { + return this.radius; + } + + protected void processConfig() { + if (this.soundEventId != null) { + this.soundEventIndex = SoundEvent.getAssetMap().getIndex(this.soundEventId); + } + + if (this.blockSoundSetId != null) { + this.blockSoundSetIndex = BlockSoundSet.getAssetMap().getIndex(this.blockSoundSetId); + } + } + + @Nonnull + @Override + public String toString() { + return "AmbienceFXSound{soundEventId='" + + this.soundEventId + + "', soundEventIndex=" + + this.soundEventIndex + + ", play3D=" + + this.play3D + + ", blockSoundSetId='" + + this.blockSoundSetId + + "', blockSoundSetIndex=" + + this.blockSoundSetIndex + + ", altitude=" + + this.altitude + + ", frequency=" + + this.frequency + + ", radius=" + + this.radius + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXSoundEffect.java b/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXSoundEffect.java new file mode 100644 index 0000000..91348ef --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/ambiencefx/config/AmbienceFXSoundEffect.java @@ -0,0 +1,108 @@ +package com.hypixel.hytale.server.core.asset.type.ambiencefx.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.asset.type.equalizereffect.config.EqualizerEffect; +import com.hypixel.hytale.server.core.asset.type.reverbeffect.config.ReverbEffect; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AmbienceFXSoundEffect implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(AmbienceFXSoundEffect.class, AmbienceFXSoundEffect::new) + .appendInherited( + new KeyedCodec<>("ReverbEffectId", Codec.STRING), + (ambienceFXSoundEffect, s) -> ambienceFXSoundEffect.reverbEffectId = s, + ambienceFXSoundEffect -> ambienceFXSoundEffect.reverbEffectId, + (ambienceFXSoundEffect, parent) -> ambienceFXSoundEffect.reverbEffectId = parent.reverbEffectId + ) + .addValidator(ReverbEffect.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("EqualizerEffectId", Codec.STRING), + (ambienceFXSoundEffect, s) -> ambienceFXSoundEffect.equalizerEffectId = s, + ambienceFXSoundEffect -> ambienceFXSoundEffect.equalizerEffectId, + (ambienceFXSoundEffect, parent) -> ambienceFXSoundEffect.equalizerEffectId = parent.equalizerEffectId + ) + .addValidator(EqualizerEffect.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("IsInstant", Codec.BOOLEAN), + (ambienceFXSoundEffect, b) -> ambienceFXSoundEffect.isInstant = b, + ambienceFXSoundEffect -> ambienceFXSoundEffect.isInstant, + (ambienceFXSoundEffect, parent) -> ambienceFXSoundEffect.isInstant = parent.isInstant + ) + .add() + .afterDecode(AmbienceFXSoundEffect::processConfig) + .build(); + @Nullable + protected String reverbEffectId; + protected transient int reverbEffectIndex = 0; + @Nullable + protected String equalizerEffectId; + protected transient int equalizerEffectIndex = 0; + protected boolean isInstant = false; + + public AmbienceFXSoundEffect(@Nullable String reverbEffectId, @Nullable String equalizerEffectId, boolean isInstant) { + this.reverbEffectId = reverbEffectId; + this.equalizerEffectId = equalizerEffectId; + this.isInstant = isInstant; + } + + protected AmbienceFXSoundEffect() { + } + + protected void processConfig() { + if (this.reverbEffectId != null) { + this.reverbEffectIndex = ReverbEffect.getAssetMap().getIndex(this.reverbEffectId); + } + + if (this.equalizerEffectId != null) { + this.equalizerEffectIndex = EqualizerEffect.getAssetMap().getIndex(this.equalizerEffectId); + } + } + + @Nonnull + public com.hypixel.hytale.protocol.AmbienceFXSoundEffect toPacket() { + com.hypixel.hytale.protocol.AmbienceFXSoundEffect packet = new com.hypixel.hytale.protocol.AmbienceFXSoundEffect(); + packet.reverbEffectIndex = this.reverbEffectIndex; + packet.equalizerEffectIndex = this.equalizerEffectIndex; + packet.isInstant = this.isInstant; + return packet; + } + + @Nullable + public String getReverbEffectId() { + return this.reverbEffectId; + } + + public int getReverbEffectIndex() { + return this.reverbEffectIndex; + } + + @Nullable + public String getEqualizerEffectId() { + return this.equalizerEffectId; + } + + public int getEqualizerEffectIndex() { + return this.equalizerEffectIndex; + } + + public boolean isInstant() { + return this.isInstant; + } + + @Nonnull + @Override + public String toString() { + return "AmbienceFXSoundEffect{reverbEffectId='" + + this.reverbEffectId + + "', equalizerEffectId='" + + this.equalizerEffectId + + "', isInstant=" + + this.isInstant + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/attitude/Attitude.java b/src/com/hypixel/hytale/server/core/asset/type/attitude/Attitude.java new file mode 100644 index 0000000..46ec26c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/attitude/Attitude.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.core.asset.type.attitude; + +import com.hypixel.hytale.codec.codecs.EnumCodec; +import java.util.function.Supplier; + +public enum Attitude implements Supplier { + IGNORE("is ignoring the target"), + HOSTILE("is hostile towards the target"), + NEUTRAL("is neutral towards the target"), + FRIENDLY("is friendly towards the target"), + REVERED("reveres the target"); + + public static final EnumCodec CODEC = new EnumCodec<>(Attitude.class); + public static final Attitude[] VALUES = values(); + private final String description; + + private Attitude(String description) { + this.description = description; + } + + public String get() { + return this.description; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/audiocategory/AudioCategoryPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/audiocategory/AudioCategoryPacketGenerator.java new file mode 100644 index 0000000..7819733 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/audiocategory/AudioCategoryPacketGenerator.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.asset.type.audiocategory; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateAudioCategories; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.audiocategory.config.AudioCategory; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class AudioCategoryPacketGenerator extends SimpleAssetPacketGenerator> { + public AudioCategoryPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + UpdateAudioCategories packet = new UpdateAudioCategories(); + packet.type = UpdateType.Init; + packet.categories = new Int2ObjectOpenHashMap<>(assets.size()); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.categories.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateUpdatePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets) { + UpdateAudioCategories packet = new UpdateAudioCategories(); + packet.type = UpdateType.AddOrUpdate; + packet.categories = new Int2ObjectOpenHashMap<>(loadedAssets.size()); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.categories.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateRemovePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed) { + UpdateAudioCategories packet = new UpdateAudioCategories(); + packet.type = UpdateType.Remove; + packet.categories = new Int2ObjectOpenHashMap<>(removed.size()); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.categories.put(index, null); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/audiocategory/config/AudioCategory.java b/src/com/hypixel/hytale/server/core/asset/type/audiocategory/config/AudioCategory.java new file mode 100644 index 0000000..800fc61 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/audiocategory/config/AudioCategory.java @@ -0,0 +1,113 @@ +package com.hypixel.hytale.server.core.asset.type.audiocategory.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.AudioUtil; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import javax.annotation.Nonnull; + +public class AudioCategory + implements JsonAssetWithMap>, + NetworkSerializable { + public static final int EMPTY_ID = 0; + public static final String EMPTY = "EMPTY"; + public static final AudioCategory EMPTY_AUDIO_CATEGORY = new AudioCategory("EMPTY"); + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + AudioCategory.class, AudioCategory::new, Codec.STRING, (t, k) -> t.id = k, t -> t.id, (asset, data) -> asset.data = data, asset -> asset.data + ) + .documentation( + "An asset used to define an audio category. Can be used to adjust the volume of all sound events that reference a given category. Note: When using an inheritance structure, these categories act a bit like an audio bus where the category's volume is combined with the volumes further up in the hierarchy. e.g. if the category's volume is 4dB and the parent is -2dB, the final volume will be 2dB." + ) + .appendInherited( + new KeyedCodec<>("Volume", Codec.FLOAT), + (category, f) -> category.volume = AudioUtil.decibelsToLinearGain(f), + category -> AudioUtil.linearGainToDecibels(category.volume), + (category, parent) -> category.volume = parent.volume + ) + .metadata(new UIEditor(new UIEditor.FormattedNumber(null, " dB", null))) + .addValidator(Validators.range(-100.0F, 10.0F)) + .documentation("Volume adjustment for the audio category in decibels.") + .add() + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(AudioCategory::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected float volume = AudioUtil.decibelsToLinearGain(0.0F); + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(AudioCategory.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public AudioCategory(String id) { + this.id = id; + } + + protected AudioCategory() { + } + + public String getId() { + return this.id; + } + + public float getVolume() { + return this.volume; + } + + @Nonnull + @Override + public String toString() { + return "AudioCategory{id='" + this.id + "', volume=" + this.volume + "}"; + } + + @Nonnull + public com.hypixel.hytale.protocol.AudioCategory toPacket() { + com.hypixel.hytale.protocol.AudioCategory cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.AudioCategory packet = new com.hypixel.hytale.protocol.AudioCategory(); + packet.id = this.id; + packet.volume = this.volume; + AssetExtraInfo.Data parentData = this.data; + + while (parentData != null) { + String parentKey = ASSET_STORE.transformKey(parentData.getParentKey()); + if (parentKey == null) { + break; + } + + AudioCategory parent = (AudioCategory)((IndexedLookupTableAssetMap)ASSET_STORE.getAssetMap()).getAsset(parentKey); + if (parent == null) { + break; + } + + packet.volume = packet.volume * parent.volume; + parentData = parent.data; + } + + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blockbreakingdecal/BlockBreakingDecalPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/blockbreakingdecal/BlockBreakingDecalPacketGenerator.java new file mode 100644 index 0000000..6802f28 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blockbreakingdecal/BlockBreakingDecalPacketGenerator.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.server.core.asset.type.blockbreakingdecal; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateBlockBreakingDecals; +import com.hypixel.hytale.server.core.asset.packet.DefaultAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.blockbreakingdecal.config.BlockBreakingDecal; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockBreakingDecalPacketGenerator extends DefaultAssetPacketGenerator { + public BlockBreakingDecalPacketGenerator() { + } + + @Nonnull + @Override + public Packet generateInitPacket(@Nonnull DefaultAssetMap assetMap, Map assets) { + UpdateBlockBreakingDecals packet = new UpdateBlockBreakingDecals(); + packet.type = UpdateType.Init; + packet.blockBreakingDecals = assetMap.getAssetMap().entrySet().stream().collect(Collectors.toMap(Entry::getKey, entry -> entry.getValue().toPacket())); + return packet; + } + + @Nonnull + @Override + public Packet generateUpdatePacket(@Nonnull Map loadedAssets) { + UpdateBlockBreakingDecals packet = new UpdateBlockBreakingDecals(); + packet.type = UpdateType.AddOrUpdate; + packet.blockBreakingDecals = loadedAssets.entrySet().stream().collect(Collectors.toMap(Entry::getKey, entry -> entry.getValue().toPacket())); + return packet; + } + + @Nullable + @Override + public Packet generateRemovePacket(@Nonnull Set removed) { + UpdateBlockBreakingDecals packet = new UpdateBlockBreakingDecals(); + packet.type = UpdateType.Remove; + packet.blockBreakingDecals = new Object2ObjectOpenHashMap<>(); + + for (String string : removed) { + packet.blockBreakingDecals.put(string, null); + } + + return null; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blockbreakingdecal/config/BlockBreakingDecal.java b/src/com/hypixel/hytale/server/core/asset/type/blockbreakingdecal/config/BlockBreakingDecal.java new file mode 100644 index 0000000..504964c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blockbreakingdecal/config/BlockBreakingDecal.java @@ -0,0 +1,72 @@ +package com.hypixel.hytale.server.core.asset.type.blockbreakingdecal.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.AssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.validator.ArrayValidator; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class BlockBreakingDecal + implements JsonAssetWithMap>, + NetworkSerializable { + private static final String[] DEFAULT_STAGE_TEXTURE_LIST = new String[0]; + public static final AssetCodec CODEC = AssetBuilderCodec.builder( + BlockBreakingDecal.class, + BlockBreakingDecal::new, + Codec.STRING, + (t, k) -> t.id = k, + t -> t.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .append( + new KeyedCodec<>("StageTextures", Codec.STRING_ARRAY), (blockSet, strings) -> blockSet.stageTextures = strings, blockSet -> blockSet.stageTextures + ) + .addValidator(new ArrayValidator<>(CommonAssetValidator.TEXTURE_ITEM)) + .add() + .build(); + private static AssetStore> ASSET_STORE; + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(BlockBreakingDecal::getAssetStore)); + private String id; + private AssetExtraInfo.Data data; + private String[] stageTextures = DEFAULT_STAGE_TEXTURE_LIST; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(BlockBreakingDecal.class); + } + + return ASSET_STORE; + } + + protected BlockBreakingDecal() { + } + + @Nonnull + public com.hypixel.hytale.protocol.BlockBreakingDecal toPacket() { + com.hypixel.hytale.protocol.BlockBreakingDecal packet = new com.hypixel.hytale.protocol.BlockBreakingDecal(); + packet.stageTextures = this.stageTextures; + return packet; + } + + public String getId() { + return this.id; + } + + @Nonnull + @Override + public String toString() { + return "BlockBreakingDecal{id='" + this.id + "', data=" + this.data + ", stageTextures=" + Arrays.toString((Object[])this.stageTextures) + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blockhitbox/BlockBoundingBoxes.java b/src/com/hypixel/hytale/server/core/asset/type/blockhitbox/BlockBoundingBoxes.java new file mode 100644 index 0000000..b2d3351 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blockhitbox/BlockBoundingBoxes.java @@ -0,0 +1,346 @@ +package com.hypixel.hytale.server.core.asset.type.blockhitbox; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.protocol.Hitbox; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.io.NetworkSerializers; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockBoundingBoxes implements JsonAssetWithMap>, NetworkSerializable { + private static final int ROTATIONS = Rotation.VALUES.length * Rotation.VALUES.length * Rotation.VALUES.length; + public static final int DEFAULT_ID = 0; + public static final String DEFAULT = "Full"; + public static final BlockBoundingBoxes UNIT_BOX = new BlockBoundingBoxes("Full", 0.0, 1.0); + public static final double UNIT_BOX_MAXIMUM_EXTENT; + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + BlockBoundingBoxes.class, + BlockBoundingBoxes::new, + Codec.STRING, + (blockBoundingBoxes, s) -> blockBoundingBoxes.id = s, + blockBoundingBoxes -> blockBoundingBoxes.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("Boxes", new ArrayCodec<>(Box.CODEC, Box[]::new), true), + (blockBoundingBoxes, boxes) -> blockBoundingBoxes.baseDetailBoxes = boxes, + blockBoundingBoxes -> blockBoundingBoxes.baseDetailBoxes, + (blockBoundingBoxes, parent) -> blockBoundingBoxes.baseDetailBoxes = parent.baseDetailBoxes + ) + .addValidator(Validators.nonEmptyArray()) + .add() + .afterDecode(BlockBoundingBoxes::processConfig) + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(BlockBoundingBoxes::getAssetStore)); + public static final Hitbox[] EMPTY_HITBOXES = new Hitbox[0]; + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected Box[] baseDetailBoxes; + @Nonnull + private final transient BlockBoundingBoxes.RotatedVariantBoxes[] variants = new BlockBoundingBoxes.RotatedVariantBoxes[ROTATIONS]; + protected transient boolean protrudesUnitBox; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(BlockBoundingBoxes.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public BlockBoundingBoxes() { + } + + public BlockBoundingBoxes(String id, double min, double max) { + this.id = id; + this.baseDetailBoxes = new Box[]{new Box().setMinMax(min, max)}; + this.processConfig(); + } + + public String getId() { + return this.id; + } + + public boolean protrudesUnitBox() { + return this.protrudesUnitBox; + } + + protected void processConfig() { + if (this.baseDetailBoxes != null) { + this.protrudesUnitBox = false; + + for (RotationTuple tuple : RotationTuple.VALUES) { + Rotation yaw = tuple.yaw(); + Rotation pitch = tuple.pitch(); + Rotation roll = tuple.roll(); + BlockBoundingBoxes.RotatedVariantBoxes variant = getRotated(this, yaw, pitch, roll); + this.variants[RotationTuple.index(yaw, pitch, roll)] = variant; + this.protrudesUnitBox = this.protrudesUnitBox + | ( + variant.boundingBox.min.x < 0.0 + || variant.boundingBox.max.x > 1.0 + || variant.boundingBox.min.y < 0.0 + || variant.boundingBox.max.y > 1.0 + || variant.boundingBox.min.z < 0.0 + || variant.boundingBox.max.z > 1.0 + ); + } + } + } + + public BlockBoundingBoxes.RotatedVariantBoxes get(@Nonnull Rotation yaw, @Nonnull Rotation pitch, @Nonnull Rotation roll) { + return this.variants[RotationTuple.index(yaw, pitch, roll)]; + } + + public BlockBoundingBoxes.RotatedVariantBoxes get(int index) { + return this.variants[index]; + } + + public Hitbox[] toPacket() { + if (this.baseDetailBoxes == null) { + return EMPTY_HITBOXES; + } else { + Hitbox[] arr = new Hitbox[this.baseDetailBoxes.length]; + + for (int i = 0; i < this.baseDetailBoxes.length; i++) { + arr[i] = NetworkSerializers.BOX.toPacket(this.baseDetailBoxes[i]); + } + + return arr; + } + } + + @Nonnull + @Override + public String toString() { + return "BlockBoundingBoxes{data=" + + this.data + + ", id='" + + this.id + + "', baseDetailBoxes=" + + Arrays.toString((Object[])this.baseDetailBoxes) + + ", variants=" + + Arrays.toString((Object[])this.variants) + + ", protrudesUnitBox=" + + this.protrudesUnitBox + + "}"; + } + + @Nonnull + public static BlockBoundingBoxes getUnitBoxFor(String id) { + return new BlockBoundingBoxes(id, 0.0, 1.0); + } + + @Nonnull + private static BlockBoundingBoxes.RotatedVariantBoxes getRotated( + @Nonnull BlockBoundingBoxes boxes, @Nonnull Rotation rotationYaw, @Nonnull Rotation rotationPitch, @Nonnull Rotation rotationRoll + ) { + Box[] detailBoxes = boxes.baseDetailBoxes; + Box[] newDetailBoxes = new Box[detailBoxes.length]; + + for (int i = 0; i < detailBoxes.length; i++) { + newDetailBoxes[i] = rotate(new Box(detailBoxes[i]), rotationYaw, rotationPitch, rotationRoll); + } + + return new BlockBoundingBoxes.RotatedVariantBoxes(newDetailBoxes); + } + + @Nonnull + protected static Box rotate(@Nonnull Box box, @Nonnull Rotation rotationYaw, @Nonnull Rotation rotationPitch, @Nonnull Rotation rotationRoll) { + switch (rotationRoll) { + case None: + default: + break; + case Ninety: + rotate90Z(box); + break; + case OneEighty: + rotate180Z(box); + break; + case TwoSeventy: + rotate270Z(box); + } + + switch (rotationPitch) { + case None: + default: + break; + case Ninety: + rotate90X(box); + break; + case OneEighty: + rotate180X(box); + break; + case TwoSeventy: + rotate270X(box); + } + + switch (rotationYaw) { + case None: + default: + break; + case Ninety: + rotate90Y(box); + break; + case OneEighty: + rotate180Y(box); + break; + case TwoSeventy: + rotate270Y(box); + } + + box.normalize(); + return box; + } + + private static void rotate90X(@Nonnull Box box) { + double t = box.min.z; + box.min.z = box.min.y; + box.min.y = 1.0 - t; + t = box.max.z; + box.max.z = box.max.y; + box.max.y = 1.0 - t; + } + + private static void rotate180X(@Nonnull Box box) { + box.min.z = 1.0 - box.min.z; + box.min.y = 1.0 - box.min.y; + box.max.z = 1.0 - box.max.z; + box.max.y = 1.0 - box.max.y; + } + + private static void rotate270X(@Nonnull Box box) { + double t = box.min.z; + box.min.z = 1.0 - box.min.y; + box.min.y = t; + t = box.max.z; + box.max.z = 1.0 - box.max.y; + box.max.y = t; + } + + private static void rotate90Y(@Nonnull Box box) { + double t = box.min.x; + box.min.x = box.min.z; + box.min.z = 1.0 - t; + t = box.max.x; + box.max.x = box.max.z; + box.max.z = 1.0 - t; + } + + private static void rotate180Y(@Nonnull Box box) { + box.min.x = 1.0 - box.min.x; + box.min.z = 1.0 - box.min.z; + box.max.x = 1.0 - box.max.x; + box.max.z = 1.0 - box.max.z; + } + + private static void rotate270Y(@Nonnull Box box) { + double t = box.min.x; + box.min.x = 1.0 - box.min.z; + box.min.z = t; + t = box.max.x; + box.max.x = 1.0 - box.max.z; + box.max.z = t; + } + + private static void rotate90Z(@Nonnull Box box) { + double t = box.min.x; + box.min.x = box.min.y; + box.min.y = 1.0 - t; + t = box.max.x; + box.max.x = box.max.y; + box.max.y = 1.0 - t; + } + + private static void rotate180Z(@Nonnull Box box) { + box.min.x = 1.0 - box.min.x; + box.min.y = 1.0 - box.min.y; + box.max.x = 1.0 - box.max.x; + box.max.y = 1.0 - box.max.y; + } + + private static void rotate270Z(@Nonnull Box box) { + double t = box.min.x; + box.min.x = 1.0 - box.min.y; + box.min.y = t; + t = box.max.x; + box.max.x = 1.0 - box.max.y; + box.max.y = t; + } + + static { + UNIT_BOX_MAXIMUM_EXTENT = UNIT_BOX.variants[0].getBoundingBox().getMaximumExtent(); + } + + public static class RotatedVariantBoxes { + protected Box boundingBox; + protected Box[] detailBoxes; + + public RotatedVariantBoxes(@Nullable Box[] boxes) { + if (boxes == null || boxes.length == 0) { + boxes = new Box[]{new Box().setMinMax(0.0, 1.0)}; + } + + if (boxes.length == 1) { + this.boundingBox = boxes[0]; + } else { + Box box = this.boundingBox = new Box(); + + for (int i = 0; i < boxes.length; i++) { + box.union(boxes[i]); + } + } + + this.detailBoxes = boxes; + } + + @Nonnull + public Box getBoundingBox() { + return this.boundingBox; + } + + @Nonnull + public Box[] getDetailBoxes() { + return this.detailBoxes; + } + + public boolean hasDetailBoxes() { + return this.detailBoxes.length > 1; + } + + public boolean containsPosition(double x, double y, double z) { + if (this.hasDetailBoxes()) { + for (Box detailBox : this.detailBoxes) { + if (detailBox.containsPosition(x, y, z)) { + return true; + } + } + + return false; + } else { + return this.boundingBox.containsPosition(x, y, z); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blockhitbox/BlockBoundingBoxesPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/blockhitbox/BlockBoundingBoxesPacketGenerator.java new file mode 100644 index 0000000..7fb2270 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blockhitbox/BlockBoundingBoxesPacketGenerator.java @@ -0,0 +1,82 @@ +package com.hypixel.hytale.server.core.asset.type.blockhitbox; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Hitbox; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateBlockHitboxes; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class BlockBoundingBoxesPacketGenerator + extends SimpleAssetPacketGenerator> { + public BlockBoundingBoxesPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + UpdateBlockHitboxes packet = new UpdateBlockHitboxes(); + packet.type = UpdateType.Init; + Map hitboxes = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + hitboxes.put(index, entry.getValue().toPacket()); + } + + packet.blockBaseHitboxes = hitboxes; + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateUpdatePacket( + @Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets + ) { + UpdateBlockHitboxes packet = new UpdateBlockHitboxes(); + packet.type = UpdateType.AddOrUpdate; + Map hitboxes = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + hitboxes.put(index, entry.getValue().toPacket()); + } + + packet.blockBaseHitboxes = hitboxes; + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateRemovePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed) { + UpdateBlockHitboxes packet = new UpdateBlockHitboxes(); + packet.type = UpdateType.Remove; + Map hitboxes = new Int2ObjectOpenHashMap<>(); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + hitboxes.put(index, null); + } + + packet.blockBaseHitboxes = hitboxes; + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blockparticle/BlockParticleSetPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/blockparticle/BlockParticleSetPacketGenerator.java new file mode 100644 index 0000000..93ae518 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blockparticle/BlockParticleSetPacketGenerator.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.server.core.asset.type.blockparticle; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateBlockParticleSets; +import com.hypixel.hytale.server.core.asset.packet.DefaultAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.blockparticle.config.BlockParticleSet; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class BlockParticleSetPacketGenerator extends DefaultAssetPacketGenerator { + public BlockParticleSetPacketGenerator() { + } + + @Nonnull + @Override + public Packet generateInitPacket(DefaultAssetMap assetMap, @Nonnull Map assets) { + UpdateBlockParticleSets packet = new UpdateBlockParticleSets(); + packet.type = UpdateType.Init; + packet.blockParticleSets = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + packet.blockParticleSets.put(entry.getKey(), entry.getValue().toPacket()); + } + + return packet; + } + + @Nonnull + @Override + public Packet generateUpdatePacket(@Nonnull Map loadedAssets) { + UpdateBlockParticleSets packet = new UpdateBlockParticleSets(); + packet.type = UpdateType.AddOrUpdate; + packet.blockParticleSets = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + packet.blockParticleSets.put(entry.getKey(), entry.getValue().toPacket()); + } + + return packet; + } + + @Nonnull + @Override + public Packet generateRemovePacket(@Nonnull Set removed) { + UpdateBlockParticleSets packet = new UpdateBlockParticleSets(); + packet.type = UpdateType.Remove; + packet.blockParticleSets = new Object2ObjectOpenHashMap<>(); + + for (String key : removed) { + packet.blockParticleSets.put(key, null); + } + + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blockparticle/config/BlockParticleSet.java b/src/com/hypixel/hytale/server/core/asset/type/blockparticle/config/BlockParticleSet.java new file mode 100644 index 0000000..5a81dc6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blockparticle/config/BlockParticleSet.java @@ -0,0 +1,179 @@ +package com.hypixel.hytale.server.core.asset.type.blockparticle.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDefaultCollapsedState; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.BlockParticleEvent; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import java.util.Map; +import javax.annotation.Nonnull; + +public class BlockParticleSet + implements JsonAssetWithMap>, + NetworkSerializable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + BlockParticleSet.class, + BlockParticleSet::new, + Codec.STRING, + (blockParticleSet, s) -> blockParticleSet.id = s, + blockParticleSet -> blockParticleSet.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .documentation("Particle Systems that can be spawned in relation to block events.") + .appendInherited( + new KeyedCodec<>("Color", ProtocolCodecs.COLOR), + (blockParticleSet, s) -> blockParticleSet.color = s, + blockParticleSet -> blockParticleSet.color, + (blockParticleSet, parent) -> blockParticleSet.color = parent.color + ) + .documentation("The colour used if none was specified in the particle settings.") + .add() + .appendInherited( + new KeyedCodec<>("Scale", Codec.FLOAT), + (blockParticleSet, f) -> blockParticleSet.scale = f, + blockParticleSet -> blockParticleSet.scale, + (blockParticleSet, parent) -> blockParticleSet.scale = parent.scale + ) + .documentation("The scale of the particle system.") + .add() + .appendInherited( + new KeyedCodec<>("PositionOffset", ProtocolCodecs.VECTOR3F), + (blockParticleSet, s) -> blockParticleSet.positionOffset = s, + blockParticleSet -> blockParticleSet.positionOffset, + (blockParticleSet, parent) -> blockParticleSet.positionOffset = parent.positionOffset + ) + .documentation("The position offset from the spawn position.") + .add() + .appendInherited( + new KeyedCodec<>("RotationOffset", ProtocolCodecs.DIRECTION), + (blockParticleSet, s) -> blockParticleSet.rotationOffset = s, + blockParticleSet -> blockParticleSet.rotationOffset, + (blockParticleSet, parent) -> blockParticleSet.rotationOffset = parent.rotationOffset + ) + .documentation("The rotation offset from the spawn rotation.") + .add() + .>appendInherited( + new KeyedCodec<>("Particles", new EnumMapCodec<>(BlockParticleEvent.class, Codec.STRING)), + (blockParticleSet, s) -> blockParticleSet.particleSystemIds = s, + blockParticleSet -> blockParticleSet.particleSystemIds, + (blockParticleSet, parent) -> blockParticleSet.particleSystemIds = parent.particleSystemIds + ) + .addValidator(Validators.nonNull()) + .addValidator(ParticleSystem.VALIDATOR_CACHE.getMapValueValidator()) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .build(); + private static AssetStore> ASSET_STORE; + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(BlockParticleSet::getAssetStore)); + protected AssetExtraInfo.Data data; + protected String id; + protected Color color; + protected float scale = 1.0F; + protected Vector3f positionOffset; + protected Direction rotationOffset; + protected Map particleSystemIds; + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(BlockParticleSet.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public BlockParticleSet( + String id, Color color, float scale, Vector3f positionOffset, Direction rotationOffset, Map particleSystemIds + ) { + this.id = id; + this.color = color; + this.scale = scale; + this.positionOffset = positionOffset; + this.rotationOffset = rotationOffset; + this.particleSystemIds = particleSystemIds; + } + + protected BlockParticleSet() { + } + + @Nonnull + public com.hypixel.hytale.protocol.BlockParticleSet toPacket() { + com.hypixel.hytale.protocol.BlockParticleSet cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.BlockParticleSet packet = new com.hypixel.hytale.protocol.BlockParticleSet(); + packet.id = this.id; + packet.color = this.color; + packet.scale = this.scale; + packet.positionOffset = this.positionOffset; + packet.rotationOffset = this.rotationOffset; + packet.particleSystemIds = this.particleSystemIds; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + public String getId() { + return this.id; + } + + public Color getColor() { + return this.color; + } + + public float getScale() { + return this.scale; + } + + public Vector3f getPositionOffset() { + return this.positionOffset; + } + + public Direction getRotationOffset() { + return this.rotationOffset; + } + + public Map getParticleSystemIds() { + return this.particleSystemIds; + } + + @Nonnull + @Override + public String toString() { + return "BlockParticleSet{id='" + + this.id + + "', color=" + + this.color + + ", scale=" + + this.scale + + ", positionOffset=" + + this.positionOffset + + ", rotationOffset=" + + this.rotationOffset + + ", particleSystemIds=" + + this.particleSystemIds + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blockset/BlockSetPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/blockset/BlockSetPacketGenerator.java new file mode 100644 index 0000000..1a3ffe2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blockset/BlockSetPacketGenerator.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.server.core.asset.type.blockset; + +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateBlockSets; +import com.hypixel.hytale.server.core.asset.packet.AssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockSetPacketGenerator extends AssetPacketGenerator> { + public BlockSetPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, Map assets) { + UpdateBlockSets packet = new UpdateBlockSets(); + packet.type = UpdateType.Init; + packet.blockSets = assetMap.getAssetMap().entrySet().stream().collect(Collectors.toMap(Entry::getKey, entry -> entry.getValue().toPacket())); + return packet; + } + + @Nonnull + public Packet generateUpdatePacket( + IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets, @Nonnull AssetUpdateQuery query + ) { + UpdateBlockSets packet = new UpdateBlockSets(); + packet.type = UpdateType.AddOrUpdate; + packet.blockSets = loadedAssets.entrySet().stream().collect(Collectors.toMap(Entry::getKey, entry -> entry.getValue().toPacket())); + return packet; + } + + @Nullable + public Packet generateRemovePacket(IndexedLookupTableAssetMap assetMap, @Nonnull Set removed, @Nonnull AssetUpdateQuery query) { + UpdateBlockSets packet = new UpdateBlockSets(); + packet.type = UpdateType.Remove; + packet.blockSets = new Object2ObjectOpenHashMap<>(); + + for (String string : removed) { + packet.blockSets.put(string, null); + } + + return null; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blockset/config/BlockSet.java b/src/com/hypixel/hytale/server/core/asset/type/blockset/config/BlockSet.java new file mode 100644 index 0000000..824b449 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blockset/config/BlockSet.java @@ -0,0 +1,214 @@ +package com.hypixel.hytale.server.core.asset.type.blockset.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorSectionStart; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.blockset.BlockSetModule; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.Arrays; +import javax.annotation.Nonnull; + +@Deprecated(forRemoval = true) +public class BlockSet + implements JsonAssetWithMap>, + NetworkSerializable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + BlockSet.class, BlockSet::new, Codec.STRING, (t, k) -> t.id = k, t -> t.id, (asset, data) -> asset.data = data, asset -> asset.data + ) + .append(new KeyedCodec<>("Parent", Codec.STRING), (blockSet, b) -> blockSet.parent = b, blockSet -> blockSet.parent) + .metadata(new UIEditorSectionStart("General")) + .add() + .addField(new KeyedCodec<>("IncludeAll", Codec.BOOLEAN), (blockSet, b) -> blockSet.includeAll = b, blockSet -> blockSet.includeAll) + .addField( + new KeyedCodec<>("IncludeBlockTypes", Codec.STRING_ARRAY), + (blockSet, strings) -> blockSet.includeBlockTypes = strings, + blockSet -> blockSet.includeBlockTypes + ) + .addField( + new KeyedCodec<>("ExcludeBlockTypes", Codec.STRING_ARRAY), + (blockSet, strings) -> blockSet.excludeBlockTypes = strings, + blockSet -> blockSet.excludeBlockTypes + ) + .addField( + new KeyedCodec<>("IncludeBlockGroups", Codec.STRING_ARRAY), + (blockSet, strings) -> blockSet.includeBlockGroups = strings, + blockSet -> blockSet.includeBlockGroups + ) + .addField( + new KeyedCodec<>("ExcludeBlockGroups", Codec.STRING_ARRAY), + (blockSet, strings) -> blockSet.excludeBlockGroups = strings, + blockSet -> blockSet.excludeBlockGroups + ) + .addField( + new KeyedCodec<>("IncludeHitboxTypes", Codec.STRING_ARRAY), + (blockSet, strings) -> blockSet.includeHitboxTypes = strings, + blockSet -> blockSet.includeHitboxTypes + ) + .addField( + new KeyedCodec<>("ExcludeHitboxTypes", Codec.STRING_ARRAY), + (blockSet, strings) -> blockSet.excludeHitboxTypes = strings, + blockSet -> blockSet.excludeHitboxTypes + ) + .addField( + new KeyedCodec<>("IncludeCategories", new ArrayCodec<>(Codec.STRING_ARRAY, String[][]::new)), + (blockSet, strings) -> blockSet.includeCategories = strings, + blockSet -> blockSet.includeCategories + ) + .addField( + new KeyedCodec<>("ExcludeCategories", new ArrayCodec<>(Codec.STRING_ARRAY, String[][]::new)), + (blockSet, strings) -> blockSet.excludeCategories = strings, + blockSet -> blockSet.excludeCategories + ) + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(BlockSet::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected String parent; + protected boolean includeAll; + protected String[] includeBlockTypes; + protected String[] excludeBlockTypes; + protected String[] includeBlockGroups; + protected String[] excludeBlockGroups; + protected String[] includeHitboxTypes; + protected String[] excludeHitboxTypes; + protected String[][] includeCategories; + protected String[][] excludeCategories; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(BlockSet.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public BlockSet(String id) { + this.id = id; + } + + public BlockSet( + String id, + String parent, + boolean includeAll, + String[] includeBlockTypes, + String[] excludeBlockTypes, + String[] includeBlockGroups, + String[] excludeBlockGroups, + String[] includeHitboxTypes, + String[] excludeHitboxTypes, + String[][] includeCategories, + String[][] excludeCategories + ) { + this.id = id; + this.parent = parent; + this.includeAll = includeAll; + this.includeBlockTypes = includeBlockTypes; + this.excludeBlockTypes = excludeBlockTypes; + this.includeBlockGroups = includeBlockGroups; + this.excludeBlockGroups = excludeBlockGroups; + this.includeHitboxTypes = includeHitboxTypes; + this.excludeHitboxTypes = excludeHitboxTypes; + this.includeCategories = includeCategories; + this.excludeCategories = excludeCategories; + } + + protected BlockSet() { + } + + public String getId() { + return this.id; + } + + public String getParent() { + return this.parent; + } + + public boolean isIncludeAll() { + return this.includeAll; + } + + public String[] getIncludeBlockTypes() { + return this.includeBlockTypes; + } + + public String[] getExcludeBlockTypes() { + return this.excludeBlockTypes; + } + + public String[] getIncludeBlockGroups() { + return this.includeBlockGroups; + } + + public String[] getExcludeBlockGroups() { + return this.excludeBlockGroups; + } + + public String[] getIncludeHitboxTypes() { + return this.includeHitboxTypes; + } + + public String[] getExcludeHitboxTypes() { + return this.excludeHitboxTypes; + } + + public String[][] getIncludeCategories() { + return this.includeCategories; + } + + public String[][] getExcludeCategories() { + return this.excludeCategories; + } + + @Nonnull + @Override + public String toString() { + return "BlockSet{name='" + + this.id + + "', parent='" + + this.parent + + "', includeAll=" + + this.includeAll + + ", includeBlockTypes=" + + Arrays.toString((Object[])this.includeBlockTypes) + + ", excludeBlockTypes=" + + Arrays.toString((Object[])this.excludeBlockTypes) + + ", includeBlockGroups=" + + Arrays.toString((Object[])this.includeBlockGroups) + + ", excludeBlockGroups=" + + Arrays.toString((Object[])this.excludeBlockGroups) + + ", includeHitboxTypes=" + + Arrays.toString((Object[])this.includeHitboxTypes) + + ", excludeHitboxTypes=" + + Arrays.toString((Object[])this.excludeHitboxTypes) + + ", includeCategories=" + + Arrays.deepToString(this.includeCategories) + + ", excludeCategories=" + + Arrays.deepToString(this.excludeCategories) + + "}"; + } + + @Nonnull + public com.hypixel.hytale.protocol.BlockSet toPacket() { + com.hypixel.hytale.protocol.BlockSet packet = new com.hypixel.hytale.protocol.BlockSet(); + int index = getAssetMap().getIndex(this.id); + IntSet allBlocks = BlockSetModule.getInstance().getBlockSets().get(index); + packet.name = this.id; + packet.blocks = allBlocks.toIntArray(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocksound/BlockSoundSetPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/blocksound/BlockSoundSetPacketGenerator.java new file mode 100644 index 0000000..47f6bee --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocksound/BlockSoundSetPacketGenerator.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.asset.type.blocksound; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateBlockSoundSets; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.blocksound.config.BlockSoundSet; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class BlockSoundSetPacketGenerator extends SimpleAssetPacketGenerator> { + public BlockSoundSetPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + UpdateBlockSoundSets packet = new UpdateBlockSoundSets(); + packet.type = UpdateType.Init; + packet.blockSoundSets = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.blockSoundSets.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateUpdatePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets) { + UpdateBlockSoundSets packet = new UpdateBlockSoundSets(); + packet.type = UpdateType.AddOrUpdate; + packet.blockSoundSets = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.blockSoundSets.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateRemovePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed) { + UpdateBlockSoundSets packet = new UpdateBlockSoundSets(); + packet.type = UpdateType.Remove; + packet.blockSoundSets = new Int2ObjectOpenHashMap<>(); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.blockSoundSets.put(index, null); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocksound/config/BlockSoundSet.java b/src/com/hypixel/hytale/server/core/asset/type/blocksound/config/BlockSoundSet.java new file mode 100644 index 0000000..65ffbef --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocksound/config/BlockSoundSet.java @@ -0,0 +1,156 @@ +package com.hypixel.hytale.server.core.asset.type.blocksound.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDefaultCollapsedState; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.range.FloatRange; +import com.hypixel.hytale.protocol.BlockSoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.validator.SoundEventValidators; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntMaps; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.lang.ref.SoftReference; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class BlockSoundSet + implements JsonAssetWithMap>, + NetworkSerializable { + public static final int EMPTY_ID = 0; + public static final String EMPTY = "EMPTY"; + private static final FloatRange DEFAULT_MOVE_IN_REPEAT_RANGE = new FloatRange(0.5F, 1.5F); + public static final BlockSoundSet EMPTY_BLOCK_SOUND_SET = new BlockSoundSet("EMPTY"); + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + BlockSoundSet.class, + BlockSoundSet::new, + Codec.STRING, + (blockSounds, s) -> blockSounds.id = s, + blockSounds -> blockSounds.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("SoundEvents", new EnumMapCodec<>(BlockSoundEvent.class, Codec.STRING)), + (blockParticleSet, s) -> blockParticleSet.soundEventIds = s, + blockParticleSet -> blockParticleSet.soundEventIds, + (blockParticleSet, parent) -> blockParticleSet.soundEventIds = parent.soundEventIds + ) + .addValidator(Validators.nonNull()) + .addValidator(SoundEvent.VALIDATOR_CACHE.getMapValueValidator()) + .addValidator(SoundEventValidators.MONO_VALIDATOR_CACHE.getMapValueValidator()) + .addValidator(SoundEventValidators.ONESHOT_VALIDATOR_CACHE.getMapValueValidator()) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("MoveInRepeatRange", FloatRange.CODEC), + (blockSounds, f) -> blockSounds.moveInRepeatRange = f, + blockSounds -> blockSounds.moveInRepeatRange, + (blockSounds, parent) -> blockSounds.moveInRepeatRange = parent.moveInRepeatRange + ) + .add() + .afterDecode(BlockSoundSet::processConfig) + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(BlockSoundSet::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected Map soundEventIds = Collections.emptyMap(); + protected transient Object2IntMap soundEventIndices = Object2IntMaps.emptyMap(); + protected FloatRange moveInRepeatRange = DEFAULT_MOVE_IN_REPEAT_RANGE; + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(BlockSoundSet.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public BlockSoundSet(String id, Map soundEventIds) { + this.id = id; + this.soundEventIds = soundEventIds; + } + + public BlockSoundSet(String id) { + this.id = id; + } + + protected BlockSoundSet() { + } + + @Nonnull + public com.hypixel.hytale.protocol.BlockSoundSet toPacket() { + com.hypixel.hytale.protocol.BlockSoundSet cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.BlockSoundSet packet = new com.hypixel.hytale.protocol.BlockSoundSet(); + packet.id = this.id; + packet.soundEventIndices = this.soundEventIndices; + packet.moveInRepeatRange = new com.hypixel.hytale.protocol.FloatRange( + this.moveInRepeatRange.getInclusiveMin(), this.moveInRepeatRange.getInclusiveMax() + ); + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + public String getId() { + return this.id; + } + + public Map getSoundEventIds() { + return this.soundEventIds; + } + + public Object2IntMap getSoundEventIndices() { + return this.soundEventIndices; + } + + public FloatRange getMoveInRepeatRange() { + return this.moveInRepeatRange; + } + + protected void processConfig() { + if (!this.soundEventIds.isEmpty()) { + this.soundEventIndices = new Object2IntOpenHashMap<>(); + + for (Entry entry : this.soundEventIds.entrySet()) { + this.soundEventIndices.put(entry.getKey(), SoundEvent.getAssetMap().getIndex(entry.getValue())); + } + } + } + + @Nonnull + @Override + public String toString() { + return "BlockSoundSet{id='" + + this.id + + "', soundEventIds=" + + this.soundEventIds + + ", soundEventIndices=" + + this.soundEventIndices + + ", moveInRepeatRange=" + + this.moveInRepeatRange + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktick/BlockTickManager.java b/src/com/hypixel/hytale/server/core/asset/type/blocktick/BlockTickManager.java new file mode 100644 index 0000000..e524688 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktick/BlockTickManager.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.core.asset.type.blocktick; + +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nonnull; + +@Deprecated(forRemoval = true) +public final class BlockTickManager { + private static final AtomicReference BLOCK_TICK_PROVIDER = new AtomicReference<>(IBlockTickProvider.NONE); + + private BlockTickManager() { + } + + public static void setBlockTickProvider(@Nonnull IBlockTickProvider provider) { + BLOCK_TICK_PROVIDER.set(provider); + } + + @Nonnull + public static IBlockTickProvider getBlockTickProvider() { + return BLOCK_TICK_PROVIDER.get(); + } + + public static boolean hasBlockTickProvider() { + return getBlockTickProvider() != IBlockTickProvider.NONE; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktick/BlockTickStrategy.java b/src/com/hypixel/hytale/server/core/asset/type/blocktick/BlockTickStrategy.java new file mode 100644 index 0000000..2bd383f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktick/BlockTickStrategy.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.asset.type.blocktick; + +public enum BlockTickStrategy { + CONTINUE, + IGNORED, + SLEEP, + WAIT_FOR_ADJACENT_CHUNK_LOAD; + + private BlockTickStrategy() { + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktick/IBlockTickProvider.java b/src/com/hypixel/hytale/server/core/asset/type/blocktick/IBlockTickProvider.java new file mode 100644 index 0000000..6b708b9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktick/IBlockTickProvider.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.core.asset.type.blocktick; + +import com.hypixel.hytale.server.core.asset.type.blocktick.config.TickProcedure; +import javax.annotation.Nullable; + +@FunctionalInterface +public interface IBlockTickProvider { + IBlockTickProvider NONE = blockId -> null; + + @Nullable + TickProcedure getTickProcedure(int var1); +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktick/config/TickProcedure.java b/src/com/hypixel/hytale/server/core/asset/type/blocktick/config/TickProcedure.java new file mode 100644 index 0000000..0955da0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktick/config/TickProcedure.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.core.asset.type.blocktick.config; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import java.util.SplittableRandom; + +public abstract class TickProcedure { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(TickProcedure.class).build(); + protected static final SplittableRandom BASE_RANDOM = new SplittableRandom(); + protected static final ThreadLocal RANDOM = ThreadLocal.withInitial(BASE_RANDOM::split); + + public TickProcedure() { + } + + protected SplittableRandom getRandom() { + return RANDOM.get(); + } + + public abstract BlockTickStrategy onTick(World var1, WorldChunk var2, int var3, int var4, int var5, int var6); +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/BlockGroupPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/BlockGroupPacketGenerator.java new file mode 100644 index 0000000..5311274 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/BlockGroupPacketGenerator.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateBlockGroups; +import com.hypixel.hytale.server.core.asset.packet.DefaultAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.item.config.BlockGroup; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockGroupPacketGenerator extends DefaultAssetPacketGenerator { + public BlockGroupPacketGenerator() { + } + + @Nonnull + @Override + public Packet generateInitPacket(@Nonnull DefaultAssetMap assetMap, Map assets) { + UpdateBlockGroups packet = new UpdateBlockGroups(); + packet.type = UpdateType.Init; + packet.groups = assetMap.getAssetMap().entrySet().stream().collect(Collectors.toMap(Entry::getKey, entry -> entry.getValue().toPacket())); + return packet; + } + + @Nonnull + @Override + public Packet generateUpdatePacket(@Nonnull Map loadedAssets) { + UpdateBlockGroups packet = new UpdateBlockGroups(); + packet.type = UpdateType.AddOrUpdate; + packet.groups = loadedAssets.entrySet().stream().collect(Collectors.toMap(Entry::getKey, entry -> entry.getValue().toPacket())); + return packet; + } + + @Nullable + @Override + public Packet generateRemovePacket(@Nonnull Set removed) { + UpdateBlockGroups packet = new UpdateBlockGroups(); + packet.type = UpdateType.Remove; + packet.groups = new Object2ObjectOpenHashMap<>(); + + for (String string : removed) { + packet.groups.put(string, null); + } + + return null; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/BlockTypePacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/BlockTypePacketGenerator.java new file mode 100644 index 0000000..57d686e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/BlockTypePacketGenerator.java @@ -0,0 +1,99 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype; + +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.protocol.CachedPacket; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateBlockTypes; +import com.hypixel.hytale.server.core.asset.packet.AssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class BlockTypePacketGenerator extends AssetPacketGenerator> { + public BlockTypePacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull BlockTypeAssetMap assetMap, @Nonnull Map assets) { + UpdateBlockTypes packet = new UpdateBlockTypes(); + packet.type = UpdateType.Init; + Map blockTypes = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + blockTypes.put(index, entry.getValue().toPacket()); + } + + packet.blockTypes = blockTypes; + packet.maxId = assetMap.getNextIndex(); + packet.updateBlockTextures = true; + packet.updateModels = true; + packet.updateModelTextures = true; + packet.updateMapGeometry = true; + return packet; + } + + @Nonnull + public Packet generateUpdatePacket( + @Nonnull BlockTypeAssetMap assetMap, @Nonnull Map loadedAssets, @Nonnull AssetUpdateQuery query + ) { + UpdateBlockTypes packet = new UpdateBlockTypes(); + packet.type = UpdateType.AddOrUpdate; + Map blockTypes = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + blockTypes.put(index, entry.getValue().toPacket()); + } + + packet.blockTypes = blockTypes; + packet.maxId = assetMap.getNextIndex(); + AssetUpdateQuery.RebuildCache rebuildCache = query.getRebuildCache(); + packet.updateBlockTextures = rebuildCache.isBlockTextures(); + packet.updateModelTextures = rebuildCache.isModelTextures(); + packet.updateModels = rebuildCache.isModels(); + packet.updateMapGeometry = rebuildCache.isMapGeometry(); + return CachedPacket.cache(packet); + } + + @Nonnull + public Packet generateRemovePacket(@Nonnull BlockTypeAssetMap assetMap, @Nonnull Set removed, @Nonnull AssetUpdateQuery query) { + UpdateBlockTypes packet = new UpdateBlockTypes(); + packet.type = UpdateType.Remove; + Map blockTypes = new Int2ObjectOpenHashMap<>(); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + com.hypixel.hytale.protocol.BlockType blockType = new com.hypixel.hytale.protocol.BlockType(); + blockType.name = key; + blockTypes.put(index, blockType); + } + + packet.blockTypes = blockTypes; + AssetUpdateQuery.RebuildCache rebuildCache = query.getRebuildCache(); + packet.updateBlockTextures = rebuildCache.isBlockTextures(); + packet.updateModels = rebuildCache.isModels(); + packet.updateModelTextures = rebuildCache.isModelTextures(); + packet.updateMapGeometry = rebuildCache.isMapGeometry(); + return CachedPacket.cache(packet); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockBreakingDropType.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockBreakingDropType.java new file mode 100644 index 0000000..6d54558 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockBreakingDropType.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.BlockBreaking; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class BlockBreakingDropType implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockBreakingDropType.class, BlockBreakingDropType::new) + .append(new KeyedCodec<>("GatherType", Codec.STRING), (blockBreaking, s) -> blockBreaking.gatherType = s, blockBreaking -> blockBreaking.gatherType) + .add() + .append(new KeyedCodec<>("Quality", Codec.INTEGER), (blockBreaking, s) -> blockBreaking.quality = s, blockBreaking -> blockBreaking.quality) + .add() + .append(new KeyedCodec<>("ItemId", Codec.STRING), (blockBreaking, s) -> blockBreaking.itemId = s, blockBreaking -> blockBreaking.itemId) + .addValidatorLate(() -> Item.VALIDATOR_CACHE.getValidator().late()) + .add() + .append(new KeyedCodec<>("Quantity", Codec.INTEGER), (blockBreaking, s) -> blockBreaking.quantity = s, blockBreaking -> blockBreaking.quantity) + .add() + .append( + new KeyedCodec<>("DropList", new ContainedAssetCodec<>(ItemDropList.class, ItemDropList.CODEC)), + (blockBreaking, s) -> blockBreaking.dropListId = s, + blockBreaking -> blockBreaking.dropListId + ) + .addValidatorLate(() -> ItemDropList.VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + protected String gatherType; + protected int quality; + protected String itemId; + protected String dropListId; + protected int quantity = 1; + + public BlockBreakingDropType(String gatherType, int quality, int quantity, String itemId, String dropListId) { + this.gatherType = gatherType; + this.quality = quality; + this.quantity = quantity; + this.itemId = itemId; + this.dropListId = dropListId; + } + + protected BlockBreakingDropType() { + } + + @Nonnull + public BlockBreaking toPacket() { + BlockBreaking packet = new BlockBreaking(); + packet.gatherType = this.gatherType; + packet.quality = this.quality; + packet.quantity = this.quantity; + if (this.itemId != null) { + packet.itemId = this.itemId.toString(); + } + + packet.dropListId = this.dropListId; + return packet; + } + + public String getGatherType() { + return this.gatherType; + } + + public int getQuality() { + return this.quality; + } + + public int getQuantity() { + return this.quantity; + } + + public String getItemId() { + return this.itemId; + } + + public String getDropListId() { + return this.dropListId; + } + + @Nonnull + public BlockBreakingDropType withoutDrops() { + return new BlockBreakingDropType(this.gatherType, 0, 0, null, null); + } + + @Nonnull + @Override + public String toString() { + return "BlockBreakingDropType{gatherType='" + + this.gatherType + + "', quality=" + + this.quality + + ", quantity=" + + this.quantity + + ", itemId=" + + this.itemId + + ", dropListId='" + + this.dropListId + + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockFace.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockFace.java new file mode 100644 index 0000000..7fca7e3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockFace.java @@ -0,0 +1,235 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockNeighbor; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public enum BlockFace { + UP(BlockFace.FaceConnectionType.FLIP, BlockNeighbor.Up, Vector3i.UP), + DOWN(BlockFace.FaceConnectionType.FLIP, BlockNeighbor.Down, Vector3i.DOWN), + NORTH(BlockFace.FaceConnectionType.FLIP, BlockNeighbor.North, Vector3i.NORTH), + EAST(BlockFace.FaceConnectionType.FLIP, BlockNeighbor.East, Vector3i.EAST), + SOUTH(BlockFace.FaceConnectionType.FLIP, BlockNeighbor.South, Vector3i.SOUTH), + WEST(BlockFace.FaceConnectionType.FLIP, BlockNeighbor.West, Vector3i.WEST), + UP_NORTH(BlockFace.FaceConnectionType.ROTATE_X, BlockNeighbor.UpNorth, UP, NORTH), + UP_SOUTH(BlockFace.FaceConnectionType.ROTATE_X, BlockNeighbor.UpSouth, UP, SOUTH), + UP_EAST(BlockFace.FaceConnectionType.ROTATE_Z, BlockNeighbor.UpEast, UP, EAST), + UP_WEST(BlockFace.FaceConnectionType.ROTATE_Z, BlockNeighbor.UpWest, UP, WEST), + DOWN_NORTH(BlockFace.FaceConnectionType.ROTATE_X, BlockNeighbor.DownNorth, DOWN, NORTH), + DOWN_SOUTH(BlockFace.FaceConnectionType.ROTATE_X, BlockNeighbor.DownSouth, DOWN, SOUTH), + DOWN_EAST(BlockFace.FaceConnectionType.ROTATE_Z, BlockNeighbor.DownEast, DOWN, EAST), + DOWN_WEST(BlockFace.FaceConnectionType.ROTATE_Z, BlockNeighbor.DownWest, DOWN, WEST), + NORTH_EAST(BlockFace.FaceConnectionType.ROTATE_Y, BlockNeighbor.NorthEast, NORTH, EAST), + SOUTH_EAST(BlockFace.FaceConnectionType.ROTATE_Y, BlockNeighbor.SouthEast, SOUTH, EAST), + SOUTH_WEST(BlockFace.FaceConnectionType.ROTATE_Y, BlockNeighbor.SouthWest, SOUTH, WEST), + NORTH_WEST(BlockFace.FaceConnectionType.ROTATE_Y, BlockNeighbor.NorthWest, NORTH, WEST), + UP_NORTH_EAST(BlockFace.FaceConnectionType.ROTATE_ALL, BlockNeighbor.UpNorthEast, UP, NORTH, EAST), + UP_SOUTH_EAST(BlockFace.FaceConnectionType.ROTATE_ALL, BlockNeighbor.UpSouthEast, UP, SOUTH, EAST), + UP_SOUTH_WEST(BlockFace.FaceConnectionType.ROTATE_ALL, BlockNeighbor.UpSouthWest, UP, SOUTH, WEST), + UP_NORTH_WEST(BlockFace.FaceConnectionType.ROTATE_ALL, BlockNeighbor.UpNorthWest, UP, NORTH, WEST), + DOWN_NORTH_EAST(BlockFace.FaceConnectionType.ROTATE_ALL, BlockNeighbor.DownNorthEast, DOWN, NORTH, EAST), + DOWN_SOUTH_EAST(BlockFace.FaceConnectionType.ROTATE_ALL, BlockNeighbor.DownSouthEast, DOWN, SOUTH, EAST), + DOWN_SOUTH_WEST(BlockFace.FaceConnectionType.ROTATE_ALL, BlockNeighbor.DownSouthWest, DOWN, SOUTH, WEST), + DOWN_NORTH_WEST(BlockFace.FaceConnectionType.ROTATE_ALL, BlockNeighbor.DownNorthWest, DOWN, NORTH, WEST); + + public static final EnumCodec CODEC = new EnumCodec<>(BlockFace.class); + public static final BlockFace[] VALUES = values(); + @Nonnull + private static final Map DIRECTION_MAP = new Object2ObjectOpenHashMap<>(); + private final BlockFace.FaceConnectionType faceConnectionType; + @Nonnull + private final BlockFace[] components; + private final Vector3i direction; + private final BlockNeighbor blockNeighbor; + private BlockFace[] connectingFaces; + private Vector3i[] connectingFaceOffsets; + + private BlockFace(BlockFace.FaceConnectionType faceConnectionType, BlockNeighbor blockNeighbor, Vector3i direction) { + this.faceConnectionType = faceConnectionType; + this.direction = direction; + this.blockNeighbor = blockNeighbor; + this.components = new BlockFace[0]; + } + + private BlockFace(BlockFace.FaceConnectionType faceConnectionType, BlockNeighbor blockNeighbor, @Nonnull BlockFace... components) { + this.faceConnectionType = faceConnectionType; + this.components = components; + + for (BlockFace component : components) { + if (component.components.length > 0) { + throw new IllegalArgumentException("Only the base BlockFace's can be used as components to make other block faces"); + } + } + + this.direction = new Vector3i(); + + for (BlockFace componentx : components) { + this.direction.add(componentx.direction); + } + + this.blockNeighbor = blockNeighbor; + } + + public BlockFace.FaceConnectionType getFaceConnectionType() { + return this.faceConnectionType; + } + + @Nonnull + public BlockFace[] getComponents() { + return this.components; + } + + public Vector3i getDirection() { + return this.direction; + } + + public BlockFace[] getConnectingFaces() { + return this.connectingFaces; + } + + public Vector3i[] getConnectingFaceOffsets() { + return this.connectingFaceOffsets; + } + + @Nonnull + private BlockFace[] getConnectingFaces0() { + switch (this.faceConnectionType) { + case FLIP: + return new BlockFace[]{flip(this)}; + case ROTATE_X: { + BlockFace[] blockFaces = new BlockFace[3]; + blockFaces[0] = rotate(this, Rotation.Ninety, Rotation.None, Rotation.None); + blockFaces[1] = rotate(this, Rotation.OneEighty, Rotation.None, Rotation.None); + blockFaces[2] = rotate(this, Rotation.TwoSeventy, Rotation.None, Rotation.None); + return blockFaces; + } + case ROTATE_Y: { + BlockFace[] blockFaces = new BlockFace[3]; + blockFaces[0] = rotate(this, Rotation.None, Rotation.Ninety, Rotation.None); + blockFaces[1] = rotate(this, Rotation.None, Rotation.OneEighty, Rotation.None); + blockFaces[2] = rotate(this, Rotation.None, Rotation.TwoSeventy, Rotation.None); + return blockFaces; + } + case ROTATE_Z: { + BlockFace[] blockFaces = new BlockFace[3]; + blockFaces[0] = rotate(this, Rotation.None, Rotation.None, Rotation.Ninety); + blockFaces[1] = rotate(this, Rotation.None, Rotation.None, Rotation.OneEighty); + blockFaces[2] = rotate(this, Rotation.None, Rotation.None, Rotation.TwoSeventy); + return blockFaces; + } + case ROTATE_ALL: + return new BlockFace[]{ + rotate(this, Rotation.Ninety, Rotation.None, Rotation.None), + rotate(this, Rotation.OneEighty, Rotation.None, Rotation.None), + rotate(this, Rotation.None, Rotation.Ninety, Rotation.None), + rotate(this, Rotation.None, Rotation.OneEighty, Rotation.None), + rotate(this, Rotation.None, Rotation.TwoSeventy, Rotation.None), + rotate(this, Rotation.None, Rotation.None, Rotation.OneEighty), + flip(this) + }; + default: + throw new IllegalArgumentException("Unknown FaceConnectionType " + this.faceConnectionType); + } + } + + @Nonnull + private Vector3i directionTo(@Nonnull BlockFace connectingFace) { + Vector3i vector3i = new Vector3i(); + if (this.direction.getX() == -connectingFace.direction.getX()) { + vector3i.setX(this.direction.getX()); + } + + if (this.direction.getY() == -connectingFace.direction.getY()) { + vector3i.setY(this.direction.getY()); + } + + if (this.direction.getZ() == -connectingFace.direction.getZ()) { + vector3i.setZ(this.direction.getZ()); + } + + return vector3i; + } + + public static BlockFace lookup(Vector3i direction) { + return DIRECTION_MAP.get(direction); + } + + public static BlockFace rotate(@Nonnull BlockFace blockFace, @Nonnull Rotation rotationYaw, @Nonnull Rotation rotationPitch) { + Vector3i rotate = Rotation.rotate(blockFace.direction, rotationYaw, rotationPitch); + return lookup(rotate); + } + + public static BlockFace rotate(@Nonnull BlockFace blockFace, @Nonnull Rotation rotationX, @Nonnull Rotation rotationY, @Nonnull Rotation rotationZ) { + Vector3i rotate = Rotation.rotate(blockFace.direction, rotationX, rotationY, rotationZ); + return lookup(rotate); + } + + public static BlockFace flip(@Nonnull BlockFace blockFace) { + Vector3i flipped = blockFace.direction.clone().scale(-1); + return lookup(flipped); + } + + public BlockNeighbor toProtocolBlockNeighbor() { + return this.blockNeighbor; + } + + @Nullable + public static BlockFace fromProtocolFace(@Nonnull com.hypixel.hytale.protocol.BlockFace face) { + return switch (face) { + case Up -> UP; + case Down -> DOWN; + case North -> NORTH; + case South -> SOUTH; + case East -> EAST; + case West -> WEST; + case None -> null; + }; + } + + @Nonnull + public static com.hypixel.hytale.protocol.BlockFace toProtocolFace(@Nullable BlockFace face) { + if (face == null) { + return com.hypixel.hytale.protocol.BlockFace.None; + } else { + return switch (face) { + case UP -> com.hypixel.hytale.protocol.BlockFace.Up; + case DOWN -> com.hypixel.hytale.protocol.BlockFace.Down; + case NORTH -> com.hypixel.hytale.protocol.BlockFace.North; + case EAST -> com.hypixel.hytale.protocol.BlockFace.East; + case SOUTH -> com.hypixel.hytale.protocol.BlockFace.South; + case WEST -> com.hypixel.hytale.protocol.BlockFace.West; + default -> throw new IllegalArgumentException("Invalid BlockFace"); + }; + } + } + + static { + for (BlockFace blockFace : VALUES) { + DIRECTION_MAP.put(blockFace.direction, blockFace); + } + + for (BlockFace blockFace : VALUES) { + blockFace.connectingFaces = blockFace.getConnectingFaces0(); + blockFace.connectingFaceOffsets = new Vector3i[blockFace.connectingFaces.length]; + + for (int i = 0; i < blockFace.connectingFaces.length; i++) { + blockFace.connectingFaceOffsets[i] = blockFace.directionTo(blockFace.connectingFaces[i]); + } + } + } + + static enum FaceConnectionType { + FLIP, + ROTATE_X, + ROTATE_Y, + ROTATE_Z, + ROTATE_ALL; + + private FaceConnectionType() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockFaceSupport.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockFaceSupport.java new file mode 100644 index 0000000..a4f340a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockFaceSupport.java @@ -0,0 +1,87 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class BlockFaceSupport implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockFaceSupport.class, BlockFaceSupport::new) + .append(new KeyedCodec<>("FaceType", Codec.STRING), (blockFaceSupport, s) -> blockFaceSupport.faceType = s, blockFaceSupport -> blockFaceSupport.faceType) + .add() + .documentation("Can be any string. Compared with FaceType in \"Support\". A LOT of blocks use 'Full'.") + .append( + new KeyedCodec<>("Filler", new ArrayCodec<>(Vector3i.CODEC, Vector3i[]::new)), + (blockFaceSupport, o) -> blockFaceSupport.filler = o, + blockFaceSupport -> blockFaceSupport.filler + ) + .add() + .build(); + public static final BlockFaceSupport ALL = new BlockFaceSupport(); + public static final String FULL_SUPPORTING_FACE = "Full"; + protected String faceType = "Full"; + protected Vector3i[] filler; + + public BlockFaceSupport() { + } + + public BlockFaceSupport(String faceType, Vector3i[] filler) { + this.faceType = faceType; + this.filler = filler; + } + + public String getFaceType() { + return this.faceType; + } + + public Vector3i[] getFiller() { + return this.filler; + } + + public boolean providesSupportFromFiller(Vector3i filler) { + return this.filler == null || ArrayUtil.contains(this.filler, filler); + } + + @Nonnull + @Override + public String toString() { + return "BlockFaceSupport{faceType='" + this.faceType + "', filler=" + Arrays.toString((Object[])this.filler) + "}"; + } + + @Nonnull + public static BlockFaceSupport rotate( + @Nonnull BlockFaceSupport original, @Nonnull Rotation rotationYaw, @Nonnull Rotation rotationPitch, @Nonnull Rotation roll + ) { + if (original == ALL) { + return ALL; + } else { + Vector3i[] rotatedFiller = ArrayUtil.copyAndMutate( + original.filler, vector -> Rotation.rotate(vector, rotationYaw, rotationPitch, roll), Vector3i[]::new + ); + return new BlockFaceSupport(original.faceType, rotatedFiller); + } + } + + @Nonnull + public com.hypixel.hytale.protocol.BlockFaceSupport toPacket() { + com.hypixel.hytale.protocol.BlockFaceSupport protocolBlockFaceSupport = new com.hypixel.hytale.protocol.BlockFaceSupport(); + protocolBlockFaceSupport.faceType = this.faceType; + if (this.filler != null) { + com.hypixel.hytale.protocol.Vector3i[] filler = new com.hypixel.hytale.protocol.Vector3i[this.filler.length]; + + for (int j = 0; j < this.filler.length; j++) { + Vector3i fillerVector = this.filler[j]; + filler[j] = new com.hypixel.hytale.protocol.Vector3i(fillerVector.x, fillerVector.y, fillerVector.z); + } + + protocolBlockFaceSupport.filler = filler; + } + + return protocolBlockFaceSupport; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockFlipType.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockFlipType.java new file mode 100644 index 0000000..52d6d4c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockFlipType.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.math.Axis; +import javax.annotation.Nonnull; + +public enum BlockFlipType { + ORTHOGONAL, + SYMMETRIC; + + private BlockFlipType() { + } + + public Rotation flipYaw(@Nonnull Rotation rotation, Axis axis) { + if (axis == Axis.Y) { + return rotation; + } else { + switch (this) { + case ORTHOGONAL: + int multiplier = axis == rotation.getAxisOfAlignment() ? -1 : 1; + int index = rotation.ordinal() + multiplier + Rotation.VALUES.length; + index %= Rotation.VALUES.length; + return Rotation.VALUES[index]; + case SYMMETRIC: + if (rotation.getAxisOfAlignment() == axis) { + return rotation.add(Rotation.OneEighty); + } + + return rotation; + default: + throw new UnsupportedOperationException(); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockGathering.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockGathering.java new file mode 100644 index 0000000..3e3498e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockGathering.java @@ -0,0 +1,180 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.schema.metadata.AllowEmptyObject; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Collections; +import java.util.Map; +import javax.annotation.Nonnull; + +public class BlockGathering implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockGathering.class, BlockGathering::new) + .append( + new KeyedCodec<>("Breaking", BlockBreakingDropType.CODEC), + (blockGathering, o) -> blockGathering.breaking = o, + blockGathering -> blockGathering.breaking + ) + .metadata(AllowEmptyObject.INSTANCE) + .add() + .append( + new KeyedCodec<>("Harvest", HarvestingDropType.CODEC), (blockGathering, o) -> blockGathering.harvest = o, blockGathering -> blockGathering.harvest + ) + .metadata(AllowEmptyObject.INSTANCE) + .add() + .append( + new KeyedCodec<>("Soft", SoftBlockDropType.CODEC), (blockGathering, o) -> blockGathering.soft = o, blockGathering -> blockGathering.soft + ) + .metadata(AllowEmptyObject.INSTANCE) + .add() + .append( + new KeyedCodec<>("Physics", PhysicsDropType.CODEC), (blockGathering, o) -> blockGathering.physics = o, blockGathering -> blockGathering.physics + ) + .metadata(AllowEmptyObject.INSTANCE) + .add() + .append( + new KeyedCodec<>("Tools", new ArrayCodec<>(BlockGathering.BlockToolData.CODEC, BlockGathering.BlockToolData[]::new)), + (blockGathering, o) -> blockGathering.toolDataRaw = o, + blockGathering -> blockGathering.toolDataRaw + ) + .metadata(AllowEmptyObject.INSTANCE) + .add() + .appendInherited( + new KeyedCodec<>("UseDefaultDropWhenPlaced", Codec.BOOLEAN), + (o, v) -> o.useDefaultDropWhenPlaced = v, + o -> o.useDefaultDropWhenPlaced, + (o, p) -> o.useDefaultDropWhenPlaced = p.useDefaultDropWhenPlaced + ) + .documentation("If this is set then player placed blocks will use the default drop behaviour instead of using the droplists.") + .add() + .afterDecode(g -> { + if (g.toolDataRaw != null) { + g.toolData = new Object2ObjectOpenHashMap<>(); + + for (BlockGathering.BlockToolData t : g.toolDataRaw) { + g.toolData.put(t.getTypeId(), t); + } + } + }) + .build(); + protected BlockBreakingDropType breaking; + protected HarvestingDropType harvest; + protected SoftBlockDropType soft; + protected PhysicsDropType physics; + protected BlockGathering.BlockToolData[] toolDataRaw; + @Nonnull + protected Map toolData = Collections.emptyMap(); + protected boolean useDefaultDropWhenPlaced = false; + + protected BlockGathering() { + } + + @Nonnull + public com.hypixel.hytale.protocol.BlockGathering toPacket() { + com.hypixel.hytale.protocol.BlockGathering packet = new com.hypixel.hytale.protocol.BlockGathering(); + if (this.breaking != null) { + packet.breaking = this.breaking.toPacket(); + } + + if (this.harvest != null) { + packet.harvest = this.harvest.toPacket(); + } + + if (this.soft != null) { + packet.soft = this.soft.toPacket(); + } + + return packet; + } + + public BlockBreakingDropType getBreaking() { + return this.breaking; + } + + public HarvestingDropType getHarvest() { + return this.harvest; + } + + public SoftBlockDropType getSoft() { + return this.soft; + } + + public boolean isHarvestable() { + return this.harvest != null; + } + + public boolean isSoft() { + return this.soft != null; + } + + public PhysicsDropType getPhysics() { + return this.physics; + } + + public boolean shouldUseDefaultDropWhenPlaced() { + return this.useDefaultDropWhenPlaced; + } + + @Nonnull + @Override + public String toString() { + return "BlockGathering{breaking=" + this.breaking + ", harvest=" + this.harvest + ", harvest=" + this.soft + "}"; + } + + @Nonnull + public Map getToolData() { + return this.toolData; + } + + public static class BlockToolData { + public static final BuilderCodec CODEC = BuilderCodec.builder( + BlockGathering.BlockToolData.class, BlockGathering.BlockToolData::new + ) + .append(new KeyedCodec<>("Type", Codec.STRING), (toolData, o) -> toolData.typeId = o, toolData -> toolData.typeId) + .metadata(AllowEmptyObject.INSTANCE) + .add() + .append(new KeyedCodec<>("State", Codec.STRING), (toolData, o) -> toolData.stateId = o, toolData -> toolData.stateId) + .metadata(AllowEmptyObject.INSTANCE) + .add() + .append(new KeyedCodec<>("ItemId", Codec.STRING), (toolData, s) -> toolData.itemId = s, toolData -> toolData.itemId) + .addValidatorLate(() -> Item.VALIDATOR_CACHE.getValidator().late()) + .add() + .append( + new KeyedCodec<>("DropList", new ContainedAssetCodec<>(ItemDropList.class, ItemDropList.CODEC)), + (toolData, s) -> toolData.dropListId = s, + toolData -> toolData.dropListId + ) + .addValidatorLate(() -> ItemDropList.VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + protected String typeId; + protected String stateId; + protected String itemId; + protected String dropListId; + + public BlockToolData() { + } + + public String getTypeId() { + return this.typeId; + } + + public String getStateId() { + return this.stateId; + } + + public String getItemId() { + return this.itemId; + } + + public String getDropListId() { + return this.dropListId; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockMigration.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockMigration.java new file mode 100644 index 0000000..150d858 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockMigration.java @@ -0,0 +1,98 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.StringIntegerCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class BlockMigration implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + BlockMigration.class, + BlockMigration::new, + new StringIntegerCodec(), + (blockMigration, i) -> blockMigration.id = i, + blockMigration -> blockMigration.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .addField( + new KeyedCodec<>("DirectMigrations", new MapCodec<>(Codec.STRING, HashMap::new)), + (blockMigration, document) -> blockMigration.directMigrations = document, + blockMigration -> blockMigration.directMigrations + ) + .addField( + new KeyedCodec<>("NameMigrations", new MapCodec<>(Codec.STRING, HashMap::new)), + (blockMigration, document) -> blockMigration.nameMigrations = document, + blockMigration -> blockMigration.nameMigrations + ) + .build(); + private static DefaultAssetMap ASSET_MAP; + protected AssetExtraInfo.Data data; + protected int id; + protected Map directMigrations = Collections.emptyMap(); + protected Map nameMigrations = Collections.emptyMap(); + + public static DefaultAssetMap getAssetMap() { + if (ASSET_MAP == null) { + ASSET_MAP = (DefaultAssetMap)AssetRegistry.getAssetStore(BlockMigration.class).getAssetMap(); + } + + return ASSET_MAP; + } + + public BlockMigration(int id, Map directMigrations, Map nameMigrations) { + this.id = id; + this.directMigrations = directMigrations; + this.nameMigrations = nameMigrations; + } + + protected BlockMigration() { + } + + @Nonnull + public Integer getId() { + return this.id; + } + + @Nonnull + public String getMigration(@Nonnull String blockTypeKey) { + String direct = this.directMigrations.get(blockTypeKey); + if (direct != null) { + return direct; + } else { + String name = this.nameMigrations.get(blockTypeKey); + return name != null ? name : blockTypeKey; + } + } + + public String getDirectMigration(String blockTypeKey) { + return this.directMigrations.getOrDefault(blockTypeKey, blockTypeKey); + } + + public String getNameMigration(@Nonnull String blockTypeKey) { + return this.nameMigrations.getOrDefault(blockTypeKey, blockTypeKey); + } + + public Map getDirectMigrations() { + return this.directMigrations; + } + + public Map getNameMigrations() { + return this.nameMigrations; + } + + @Nonnull + @Override + public String toString() { + return "BlockMigration{id='" + this.id + "', directMigrations=" + this.directMigrations + ", nameMigrations=" + this.nameMigrations + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockMovementSettings.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockMovementSettings.java new file mode 100644 index 0000000..70699e8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockMovementSettings.java @@ -0,0 +1,207 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class BlockMovementSettings implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockMovementSettings.class, BlockMovementSettings::new) + .append( + new KeyedCodec<>("IsClimbable", Codec.BOOLEAN), + (blockMovementSettings, o) -> blockMovementSettings.isClimbable = o, + blockMovementSettings -> blockMovementSettings.isClimbable + ) + .add() + .append( + new KeyedCodec<>("IsBouncy", Codec.BOOLEAN), + (blockMovementSettings, o) -> blockMovementSettings.isBouncy = o, + blockMovementSettings -> blockMovementSettings.isBouncy + ) + .add() + .append( + new KeyedCodec<>("BounceVelocity", Codec.FLOAT), + (blockMovementSettings, o) -> blockMovementSettings.bounceVelocity = o, + blockMovementSettings -> blockMovementSettings.bounceVelocity + ) + .add() + .append( + new KeyedCodec<>("ClimbUpSpeedMultiplier", Codec.FLOAT), + (blockMovementSettings, o) -> blockMovementSettings.climbUpSpeedMultiplier = o, + blockMovementSettings -> blockMovementSettings.climbUpSpeedMultiplier + ) + .add() + .append( + new KeyedCodec<>("ClimbDownSpeedMultiplier", Codec.FLOAT), + (blockMovementSettings, o) -> blockMovementSettings.climbDownSpeedMultiplier = o, + blockMovementSettings -> blockMovementSettings.climbDownSpeedMultiplier + ) + .add() + .append( + new KeyedCodec<>("ClimbLateralSpeedMultiplier", Codec.FLOAT), + (blockMovementSettings, o) -> blockMovementSettings.climbLateralSpeedMultiplier = o, + blockMovementSettings -> blockMovementSettings.climbLateralSpeedMultiplier + ) + .add() + .append( + new KeyedCodec<>("Drag", Codec.FLOAT), + (blockMovementSettings, o) -> blockMovementSettings.drag = o, + blockMovementSettings -> blockMovementSettings.drag + ) + .add() + .append( + new KeyedCodec<>("Friction", Codec.FLOAT), + (blockMovementSettings, o) -> blockMovementSettings.friction = o, + blockMovementSettings -> blockMovementSettings.friction + ) + .add() + .append( + new KeyedCodec<>("TerminalVelocityModifier", Codec.FLOAT), + (blockMovementSettings, o) -> blockMovementSettings.terminalVelocityModifier = o, + blockMovementSettings -> blockMovementSettings.terminalVelocityModifier + ) + .add() + .append( + new KeyedCodec<>("HorizontalSpeedMultiplier", Codec.FLOAT), + (blockMovementSettings, o) -> blockMovementSettings.horizontalSpeedMultiplier = o, + blockMovementSettings -> blockMovementSettings.horizontalSpeedMultiplier + ) + .add() + .append( + new KeyedCodec<>("JumpForceMultiplier", Codec.FLOAT), + (blockMovementSettings, o) -> blockMovementSettings.jumpForceMultiplier = o, + blockMovementSettings -> blockMovementSettings.jumpForceMultiplier + ) + .add() + .build(); + private boolean isClimbable; + private boolean isBouncy; + private float bounceVelocity; + private float drag = 0.82F; + private float friction = 0.18F; + private float climbUpSpeedMultiplier = 1.0F; + private float climbDownSpeedMultiplier = 1.0F; + private float climbLateralSpeedMultiplier = 1.0F; + private float terminalVelocityModifier = 1.0F; + private float horizontalSpeedMultiplier = 1.0F; + private float jumpForceMultiplier = 1.0F; + + public BlockMovementSettings( + boolean isClimbable, + boolean isBouncy, + float bounceVelocity, + float drag, + float friction, + float climbUpSpeed, + float climbDownSpeed, + float climbLateralSpeedMultiplier, + float terminalVelocityModifier, + float horizontalSpeedMultiplier, + float jumpForceMultiplier + ) { + this.isClimbable = isClimbable; + this.isBouncy = isBouncy; + this.bounceVelocity = bounceVelocity; + this.climbUpSpeedMultiplier = climbUpSpeed; + this.climbDownSpeedMultiplier = climbDownSpeed; + this.climbLateralSpeedMultiplier = climbLateralSpeedMultiplier; + this.drag = drag; + this.friction = friction; + this.terminalVelocityModifier = terminalVelocityModifier; + this.horizontalSpeedMultiplier = horizontalSpeedMultiplier; + this.jumpForceMultiplier = jumpForceMultiplier; + } + + protected BlockMovementSettings() { + } + + @Nonnull + public com.hypixel.hytale.protocol.BlockMovementSettings toPacket() { + com.hypixel.hytale.protocol.BlockMovementSettings packet = new com.hypixel.hytale.protocol.BlockMovementSettings(); + packet.isClimbable = this.isClimbable; + packet.isBouncy = this.isBouncy; + packet.bounceVelocity = this.bounceVelocity; + packet.climbUpSpeedMultiplier = this.climbUpSpeedMultiplier; + packet.climbDownSpeedMultiplier = this.climbDownSpeedMultiplier; + packet.climbLateralSpeedMultiplier = this.climbLateralSpeedMultiplier; + packet.drag = this.drag; + packet.friction = this.friction; + packet.terminalVelocityModifier = this.terminalVelocityModifier; + packet.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + packet.jumpForceMultiplier = this.jumpForceMultiplier; + return packet; + } + + public boolean isClimbable() { + return this.isClimbable; + } + + public boolean isBouncy() { + return this.isBouncy; + } + + public float getBounceVelocity() { + return this.bounceVelocity; + } + + public float getDrag() { + return this.drag; + } + + public float getFriction() { + return this.friction; + } + + public float getClimbUpSpeedMultiplier() { + return this.climbUpSpeedMultiplier; + } + + public float getClimbDownSpeedMultiplier() { + return this.climbDownSpeedMultiplier; + } + + public float getClimbLateralSpeedMultiplier() { + return this.climbLateralSpeedMultiplier; + } + + public float getTerminalVelocityModifier() { + return this.terminalVelocityModifier; + } + + public float getHorizontalSpeedMultiplier() { + return this.horizontalSpeedMultiplier; + } + + public float jumpForceMultiplier() { + return this.jumpForceMultiplier; + } + + @Nonnull + @Override + public String toString() { + return "BlockMovementSettings{isClimbable=" + + this.isClimbable + + "isBouncy=" + + this.isBouncy + + "bounceSpeed=" + + this.bounceVelocity + + ", climbUpSpeedMultiplier=" + + this.climbUpSpeedMultiplier + + ", climbDownSpeedMultiplier=" + + this.climbDownSpeedMultiplier + + ", climbLateralSpeedMultiplier=" + + this.climbLateralSpeedMultiplier + + ", drag=" + + this.drag + + ", friction=" + + this.friction + + ", terminalVelocityModifier=" + + this.terminalVelocityModifier + + ", horizontalSpeedMultiplier=" + + this.horizontalSpeedMultiplier + + ", jumpForceMultiplier=" + + this.jumpForceMultiplier + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings.java new file mode 100644 index 0000000..8a421a7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings.java @@ -0,0 +1,252 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class BlockPlacementSettings implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockPlacementSettings.class, BlockPlacementSettings::new) + .append( + new KeyedCodec<>("AllowRotationKey", Codec.BOOLEAN), + (placementSettings, o) -> placementSettings.allowRotationKey = o, + placementSettings -> placementSettings.allowRotationKey + ) + .add() + .append( + new KeyedCodec<>("PlaceInEmptyBlocks", Codec.BOOLEAN), + (placementSettings, o) -> placementSettings.placeInEmptyBlocks = o, + placementSettings -> placementSettings.placeInEmptyBlocks + ) + .documentation("If this block is allowed to be placed inside other blocks with an Empty Material (destroying them).") + .add() + .append( + new KeyedCodec<>("RotationMode", BlockPlacementSettings.RotationMode.CODEC), + (placementSettings, o) -> placementSettings.rotationMode = o, + placementSettings -> placementSettings.rotationMode + ) + .documentation("The mode determining the rotation of this block when placed.") + .add() + .append( + new KeyedCodec<>("BlockPreviewVisibility", BlockPlacementSettings.BlockPreviewVisibility.CODEC), + (placementSettings, o) -> placementSettings.previewVisibility = o, + placementSettings -> placementSettings.previewVisibility + ) + .documentation("An override for the block preview visibility") + .add() + .append( + new KeyedCodec<>("WallPlacementOverrideBlockId", Codec.STRING), + (placementSettings, o) -> placementSettings.wallPlacementOverrideBlockId = o, + placementSettings -> placementSettings.wallPlacementOverrideBlockId + ) + .add() + .append( + new KeyedCodec<>("FloorPlacementOverrideBlockId", Codec.STRING), + (placementSettings, o) -> placementSettings.floorPlacementOverrideBlockId = o, + placementSettings -> placementSettings.floorPlacementOverrideBlockId + ) + .add() + .append( + new KeyedCodec<>("CeilingPlacementOverrideBlockId", Codec.STRING), + (placementSettings, o) -> placementSettings.ceilingPlacementOverrideBlockId = o, + placementSettings -> placementSettings.ceilingPlacementOverrideBlockId + ) + .add() + .build(); + protected String wallPlacementOverrideBlockId; + protected String floorPlacementOverrideBlockId; + protected String ceilingPlacementOverrideBlockId; + private boolean allowRotationKey = true; + private boolean placeInEmptyBlocks; + private BlockPlacementSettings.BlockPreviewVisibility previewVisibility = BlockPlacementSettings.BlockPreviewVisibility.DEFAULT; + private BlockPlacementSettings.RotationMode rotationMode = BlockPlacementSettings.RotationMode.DEFAULT; + + protected BlockPlacementSettings() { + } + + @Nonnull + public com.hypixel.hytale.protocol.BlockPlacementSettings toPacket() { + // $VF: Couldn't be decompiled + // Please report this to the Vineflower issue tracker, at https://github.com/Vineflower/vineflower/issues with a copy of the class file (if you have the rights to distribute it!) + // java.lang.IllegalStateException: Invalid switch case set: [[const(null)], [const(0)], [const(1)], [const(2)], [null]] for selector of type Lcom/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings$BlockPreviewVisibility; + // at org.jetbrains.java.decompiler.modules.decompiler.exps.SwitchHeadExprent.checkExprTypeBounds(SwitchHeadExprent.java:66) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor.checkTypeExpr(VarTypeProcessor.java:140) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor.checkTypeExprent(VarTypeProcessor.java:126) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor.lambda$processVarTypes$2(VarTypeProcessor.java:114) + // at org.jetbrains.java.decompiler.modules.decompiler.flow.DirectGraph.iterateExprents(DirectGraph.java:107) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor.processVarTypes(VarTypeProcessor.java:114) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor.calculateVarTypes(VarTypeProcessor.java:44) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionsProcessor.setVarVersions(VarVersionsProcessor.java:68) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarProcessor.setVarVersions(VarProcessor.java:47) + // at org.jetbrains.java.decompiler.main.rels.MethodProcessor.codeToJava(MethodProcessor.java:302) + // + // Bytecode: + // 000: new com/hypixel/hytale/protocol/BlockPlacementSettings + // 003: dup + // 004: invokespecial com/hypixel/hytale/protocol/BlockPlacementSettings. ()V + // 007: astore 1 + // 008: aload 1 + // 009: aload 0 + // 00a: getfield com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings.allowRotationKey Z + // 00d: putfield com/hypixel/hytale/protocol/BlockPlacementSettings.allowRotationKey Z + // 010: aload 1 + // 011: aload 0 + // 012: getfield com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings.placeInEmptyBlocks Z + // 015: putfield com/hypixel/hytale/protocol/BlockPlacementSettings.placeInEmptyBlocks Z + // 018: aload 1 + // 019: aload 0 + // 01a: getfield com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings.previewVisibility Lcom/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings$BlockPreviewVisibility; + // 01d: astore 2 + // 01e: bipush 0 + // 01f: istore 3 + // 020: aload 2 + // 021: iload 3 + // 022: invokedynamic typeSwitch (Ljava/lang/Object;I)I bsm=java/lang/runtime/SwitchBootstraps.typeSwitch (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; args=[ null.invoke Ljava/lang/Enum$EnumDesc;, null.invoke Ljava/lang/Enum$EnumDesc;, null.invoke Ljava/lang/Enum$EnumDesc; ] + // 027: tableswitch 29 -1 2 39 45 51 57 + // 044: new java/lang/MatchException + // 047: dup + // 048: aconst_null + // 049: aconst_null + // 04a: invokespecial java/lang/MatchException. (Ljava/lang/String;Ljava/lang/Throwable;)V + // 04d: athrow + // 04e: getstatic com/hypixel/hytale/protocol/BlockPreviewVisibility.Default Lcom/hypixel/hytale/protocol/BlockPreviewVisibility; + // 051: goto 063 + // 054: getstatic com/hypixel/hytale/protocol/BlockPreviewVisibility.Default Lcom/hypixel/hytale/protocol/BlockPreviewVisibility; + // 057: goto 063 + // 05a: getstatic com/hypixel/hytale/protocol/BlockPreviewVisibility.AlwaysHidden Lcom/hypixel/hytale/protocol/BlockPreviewVisibility; + // 05d: goto 063 + // 060: getstatic com/hypixel/hytale/protocol/BlockPreviewVisibility.AlwaysVisible Lcom/hypixel/hytale/protocol/BlockPreviewVisibility; + // 063: putfield com/hypixel/hytale/protocol/BlockPlacementSettings.previewVisibility Lcom/hypixel/hytale/protocol/BlockPreviewVisibility; + // 066: aload 1 + // 067: aload 0 + // 068: getfield com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings.rotationMode Lcom/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings$RotationMode; + // 06b: astore 2 + // 06c: bipush 0 + // 06d: istore 3 + // 06e: aload 2 + // 06f: iload 3 + // 070: invokedynamic typeSwitch (Ljava/lang/Object;I)I bsm=java/lang/runtime/SwitchBootstraps.typeSwitch (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; args=[ null.invoke Ljava/lang/Enum$EnumDesc;, null.invoke Ljava/lang/Enum$EnumDesc;, null.invoke Ljava/lang/Enum$EnumDesc;, null.invoke Ljava/lang/Enum$EnumDesc; ] + // 075: tableswitch 35 -1 3 45 51 57 63 69 + // 098: new java/lang/MatchException + // 09b: dup + // 09c: aconst_null + // 09d: aconst_null + // 09e: invokespecial java/lang/MatchException. (Ljava/lang/String;Ljava/lang/Throwable;)V + // 0a1: athrow + // 0a2: getstatic com/hypixel/hytale/protocol/BlockPlacementRotationMode.Default Lcom/hypixel/hytale/protocol/BlockPlacementRotationMode; + // 0a5: goto 0bd + // 0a8: getstatic com/hypixel/hytale/protocol/BlockPlacementRotationMode.Default Lcom/hypixel/hytale/protocol/BlockPlacementRotationMode; + // 0ab: goto 0bd + // 0ae: getstatic com/hypixel/hytale/protocol/BlockPlacementRotationMode.FacingPlayer Lcom/hypixel/hytale/protocol/BlockPlacementRotationMode; + // 0b1: goto 0bd + // 0b4: getstatic com/hypixel/hytale/protocol/BlockPlacementRotationMode.StairFacingPlayer Lcom/hypixel/hytale/protocol/BlockPlacementRotationMode; + // 0b7: goto 0bd + // 0ba: getstatic com/hypixel/hytale/protocol/BlockPlacementRotationMode.BlockNormal Lcom/hypixel/hytale/protocol/BlockPlacementRotationMode; + // 0bd: putfield com/hypixel/hytale/protocol/BlockPlacementSettings.rotationMode Lcom/hypixel/hytale/protocol/BlockPlacementRotationMode; + // 0c0: aload 1 + // 0c1: aload 0 + // 0c2: getfield com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings.wallPlacementOverrideBlockId Ljava/lang/String; + // 0c5: ifnonnull 0cc + // 0c8: bipush -1 + // 0c9: goto 0d6 + // 0cc: invokestatic com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockType.getAssetMap ()Lcom/hypixel/hytale/assetstore/map/BlockTypeAssetMap; + // 0cf: aload 0 + // 0d0: getfield com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings.wallPlacementOverrideBlockId Ljava/lang/String; + // 0d3: invokevirtual com/hypixel/hytale/assetstore/map/BlockTypeAssetMap.getIndex (Ljava/lang/Object;)I + // 0d6: putfield com/hypixel/hytale/protocol/BlockPlacementSettings.wallPlacementOverrideBlockId I + // 0d9: aload 1 + // 0da: getfield com/hypixel/hytale/protocol/BlockPlacementSettings.wallPlacementOverrideBlockId I + // 0dd: ldc -2147483648 + // 0df: if_icmpne 0f3 + // 0e2: new java/lang/IllegalArgumentException + // 0e5: dup + // 0e6: aload 0 + // 0e7: getfield com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings.wallPlacementOverrideBlockId Ljava/lang/String; + // 0ea: invokedynamic makeConcatWithConstants (Ljava/lang/String;)Ljava/lang/String; bsm=java/lang/invoke/StringConcatFactory.makeConcatWithConstants (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; args=[ "Unknown key! \u0001" ] + // 0ef: invokespecial java/lang/IllegalArgumentException. (Ljava/lang/String;)V + // 0f2: athrow + // 0f3: aload 1 + // 0f4: aload 0 + // 0f5: getfield com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings.floorPlacementOverrideBlockId Ljava/lang/String; + // 0f8: ifnonnull 0ff + // 0fb: bipush -1 + // 0fc: goto 109 + // 0ff: invokestatic com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockType.getAssetMap ()Lcom/hypixel/hytale/assetstore/map/BlockTypeAssetMap; + // 102: aload 0 + // 103: getfield com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings.floorPlacementOverrideBlockId Ljava/lang/String; + // 106: invokevirtual com/hypixel/hytale/assetstore/map/BlockTypeAssetMap.getIndex (Ljava/lang/Object;)I + // 109: putfield com/hypixel/hytale/protocol/BlockPlacementSettings.floorPlacementOverrideBlockId I + // 10c: aload 1 + // 10d: getfield com/hypixel/hytale/protocol/BlockPlacementSettings.floorPlacementOverrideBlockId I + // 110: ldc -2147483648 + // 112: if_icmpne 126 + // 115: new java/lang/IllegalArgumentException + // 118: dup + // 119: aload 0 + // 11a: getfield com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings.floorPlacementOverrideBlockId Ljava/lang/String; + // 11d: invokedynamic makeConcatWithConstants (Ljava/lang/String;)Ljava/lang/String; bsm=java/lang/invoke/StringConcatFactory.makeConcatWithConstants (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; args=[ "Unknown key! \u0001" ] + // 122: invokespecial java/lang/IllegalArgumentException. (Ljava/lang/String;)V + // 125: athrow + // 126: aload 1 + // 127: aload 0 + // 128: getfield com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings.ceilingPlacementOverrideBlockId Ljava/lang/String; + // 12b: ifnonnull 132 + // 12e: bipush -1 + // 12f: goto 13c + // 132: invokestatic com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockType.getAssetMap ()Lcom/hypixel/hytale/assetstore/map/BlockTypeAssetMap; + // 135: aload 0 + // 136: getfield com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings.ceilingPlacementOverrideBlockId Ljava/lang/String; + // 139: invokevirtual com/hypixel/hytale/assetstore/map/BlockTypeAssetMap.getIndex (Ljava/lang/Object;)I + // 13c: putfield com/hypixel/hytale/protocol/BlockPlacementSettings.ceilingPlacementOverrideBlockId I + // 13f: aload 1 + // 140: getfield com/hypixel/hytale/protocol/BlockPlacementSettings.ceilingPlacementOverrideBlockId I + // 143: ldc -2147483648 + // 145: if_icmpne 159 + // 148: new java/lang/IllegalArgumentException + // 14b: dup + // 14c: aload 0 + // 14d: getfield com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockPlacementSettings.ceilingPlacementOverrideBlockId Ljava/lang/String; + // 150: invokedynamic makeConcatWithConstants (Ljava/lang/String;)Ljava/lang/String; bsm=java/lang/invoke/StringConcatFactory.makeConcatWithConstants (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; args=[ "Unknown key! \u0001" ] + // 155: invokespecial java/lang/IllegalArgumentException. (Ljava/lang/String;)V + // 158: athrow + // 159: aload 1 + // 15a: areturn + } + + public String getWallPlacementOverrideBlockId() { + return this.wallPlacementOverrideBlockId; + } + + public String getFloorPlacementOverrideBlockId() { + return this.floorPlacementOverrideBlockId; + } + + public String getCeilingPlacementOverrideBlockId() { + return this.ceilingPlacementOverrideBlockId; + } + + public static enum BlockPreviewVisibility { + ALWAYS_VISIBLE, + ALWAYS_HIDDEN, + DEFAULT; + + public static final EnumCodec CODEC = new EnumCodec<>(BlockPlacementSettings.BlockPreviewVisibility.class); + + private BlockPreviewVisibility() { + } + } + + public static enum RotationMode { + FACING_PLAYER, + BLOCK_NORMAL, + STAIR_FACING_PLAYER, + DEFAULT; + + public static final EnumCodec CODEC = new EnumCodec<>(BlockPlacementSettings.RotationMode.class); + + private RotationMode() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockSupportsRequiredForType.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockSupportsRequiredForType.java new file mode 100644 index 0000000..cea900a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockSupportsRequiredForType.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +public enum BlockSupportsRequiredForType { + Any, + All; + + private BlockSupportsRequiredForType() { + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockType.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockType.java new file mode 100644 index 0000000..eda5706 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockType.java @@ -0,0 +1,2056 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.codec.codecs.map.MergedEnumMapCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDefaultCollapsedState; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorSectionStart; +import com.hypixel.hytale.codec.schema.metadata.ui.UIPropertyTitle; +import com.hypixel.hytale.codec.schema.metadata.ui.UIRebuildCaches; +import com.hypixel.hytale.codec.store.StoredCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.common.util.MapUtil; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.BenchType; +import com.hypixel.hytale.protocol.BlockFlags; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.protocol.BlockNeighbor; +import com.hypixel.hytale.protocol.BlockTextures; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.ColorLight; +import com.hypixel.hytale.protocol.DrawType; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.ModelTexture; +import com.hypixel.hytale.protocol.Opacity; +import com.hypixel.hytale.protocol.RailConfig; +import com.hypixel.hytale.protocol.RailPoint; +import com.hypixel.hytale.protocol.RandomRotation; +import com.hypixel.hytale.protocol.ShaderType; +import com.hypixel.hytale.protocol.ShadingMode; +import com.hypixel.hytale.protocol.Tint; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.asset.type.blockbreakingdecal.config.BlockBreakingDecal; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blockparticle.config.BlockParticleSet; +import com.hypixel.hytale.server.core.asset.type.blocksound.config.BlockSoundSet; +import com.hypixel.hytale.server.core.asset.type.blocktick.config.TickProcedure; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.Bench; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.FarmingData; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.mountpoints.RotatedMountPointsArray; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BlockTypeListAsset; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.PrefabListAsset; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelParticle; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.validator.SoundEventValidators; +import com.hypixel.hytale.server.core.asset.util.ColorParseUtil; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.InteractionTypeUtils; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.universe.world.chunk.section.palette.ISectionPalette; +import com.hypixel.hytale.server.core.universe.world.connectedblocks.ConnectedBlockRuleSet; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.util.io.ByteBufUtil; +import io.netty.buffer.ByteBuf; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.lang.ref.SoftReference; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.ToIntFunction; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockType implements JsonAssetWithMap>, NetworkSerializable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + BlockType.class, BlockType::new, Codec.STRING, (t, k) -> t.id = k, t -> t.id, (asset, data) -> asset.data = data, asset -> asset.data + ) + .documentation("The definition for a block in the game. Can only be defined within an **Item** and not standalone.") + .appendInherited( + new KeyedCodec<>("Group", Codec.STRING), + (blockType, o) -> blockType.group = o, + blockType -> blockType.group, + (blockType, parent) -> blockType.group = parent.group + ) + .documentation( + "Sets the group for this block. Used by **BlockSets**.\n\nA group of _\"@Tech\"_ will prevent physics from being automatically applied to the block." + ) + .metadata(new UIEditor(new UIEditor.TextField("BlockGroups"))) + .add() + .appendInherited( + new KeyedCodec<>("BlockListAssetId", Codec.STRING), + (blockType, blockListAssetId) -> blockType.blockListAssetId = blockListAssetId, + blockType -> blockType.blockListAssetId, + (blockType, parent) -> blockType.blockListAssetId = parent.blockListAssetId + ) + .addValidator(BlockTypeListAsset.VALIDATOR_CACHE.getValidator()) + .documentation("The name of a BlockList asset, for use in builder tool brushes") + .add() + .appendInherited( + new KeyedCodec<>("PrefabListAssetId", Codec.STRING), + (blockType, prefabListAssetId) -> blockType.prefabListAssetId = prefabListAssetId, + blockType -> blockType.prefabListAssetId, + (blockType, parent) -> blockType.prefabListAssetId = parent.prefabListAssetId + ) + .addValidator(PrefabListAsset.VALIDATOR_CACHE.getValidator()) + .documentation("The name of a PrefabList asset, for use in builder tool brushes") + .add() + .appendInherited( + new KeyedCodec<>("DrawType", new EnumCodec<>(DrawType.class)), + (blockType, o) -> blockType.drawType = o, + blockType -> blockType.drawType, + (blockType, parent) -> blockType.drawType = parent.drawType + ) + .addValidator(Validators.nonNull()) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS, UIRebuildCaches.ClientCache.BLOCK_TEXTURES, UIRebuildCaches.ClientCache.MODEL_TEXTURES)) + .metadata(new UIEditorSectionStart("Rendering")) + .add() + .appendInherited( + new KeyedCodec<>("Textures", new ArrayCodec<>(BlockTypeTextures.CODEC, BlockTypeTextures[]::new)), + (blockType, o) -> blockType.textures = o, + blockType -> blockType.textures, + (blockType, parent) -> blockType.textures = parent.textures + ) + .metadata(new UIPropertyTitle("Block Textures")) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS, UIRebuildCaches.ClientCache.BLOCK_TEXTURES)) + .add() + .appendInherited( + new KeyedCodec<>("TextureSideMask", Codec.STRING), + (blockType, o) -> blockType.textureSideMask = o, + blockType -> blockType.textureSideMask, + (blockType, parent) -> blockType.textureSideMask = parent.textureSideMask + ) + .addValidator(CommonAssetValidator.TEXTURE_ITEM) + .metadata(new UIPropertyTitle("Block Texture Side Mask")) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS, UIRebuildCaches.ClientCache.BLOCK_TEXTURES)) + .add() + .appendInherited( + new KeyedCodec<>("CubeShadingMode", new EnumCodec<>(ShadingMode.class)), + (blockType, o) -> blockType.cubeShadingMode = o, + blockType -> blockType.cubeShadingMode, + (blockType, parent) -> blockType.cubeShadingMode = parent.cubeShadingMode + ) + .addValidator(Validators.nonNull()) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .add() + .appendInherited( + new KeyedCodec<>("CustomModelTexture", new ArrayCodec<>(CustomModelTexture.CODEC, CustomModelTexture[]::new)), + (blockType, o) -> blockType.customModelTexture = o, + blockType -> blockType.customModelTexture, + (blockType, parent) -> blockType.customModelTexture = parent.customModelTexture + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS, UIRebuildCaches.ClientCache.BLOCK_TEXTURES)) + .metadata(new UIPropertyTitle("Block Model Textures")) + .add() + .appendInherited( + new KeyedCodec<>("CustomModel", Codec.STRING), + (blockType, o) -> blockType.customModel = o, + blockType -> blockType.customModel, + (blockType, parent) -> blockType.customModel = parent.customModel + ) + .addValidator(CommonAssetValidator.MODEL_ITEM) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .metadata(new UIPropertyTitle("Block Model")) + .add() + .appendInherited( + new KeyedCodec<>("BlockBreakingDecalId", Codec.STRING), + (blockType, s) -> blockType.blockBreakingDecalId = s, + blockType -> blockType.blockBreakingDecalId, + (blockType, parent) -> blockType.blockBreakingDecalId = parent.blockBreakingDecalId + ) + .documentation("The block breaking decal defined here defines the decal asset that should be overlaid when this block is damaged") + .addValidator(BlockBreakingDecal.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("Material", new EnumCodec<>(BlockMaterial.class)), + (blockType, o) -> blockType.material = o, + blockType -> blockType.material, + (blockType, parent) -> blockType.material = parent.material + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Opacity", new EnumCodec<>(Opacity.class)), + (blockType, o) -> blockType.opacity = o, + blockType -> blockType.opacity, + (blockType, parent) -> blockType.opacity = parent.opacity + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("RequiresAlphaBlending", Codec.BOOLEAN), + (blockType, o) -> blockType.requiresAlphaBlending = o, + blockType -> blockType.requiresAlphaBlending, + (blockType, parent) -> blockType.requiresAlphaBlending = parent.requiresAlphaBlending + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .add() + .appendInherited( + new KeyedCodec<>("CustomModelScale", Codec.FLOAT), + (blockType, o) -> blockType.customModelScale = o, + blockType -> blockType.customModelScale, + (blockType, parent) -> blockType.customModelScale = parent.customModelScale + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .add() + .appendInherited( + new KeyedCodec<>("CustomModelAnimation", Codec.STRING), + (blockType, o) -> blockType.customModelAnimation = o, + blockType -> blockType.customModelAnimation, + (blockType, parent) -> blockType.customModelAnimation = parent.customModelAnimation + ) + .addValidator(CommonAssetValidator.ANIMATION_ITEM_BLOCK) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .metadata(new UIPropertyTitle("Block Model Animation")) + .add() + .appendInherited( + new KeyedCodec<>("Light", ProtocolCodecs.COLOR_LIGHT), + (blockType, o) -> blockType.light = o, + blockType -> blockType.light, + (blockType, parent) -> blockType.light = parent.light + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .metadata(new UIPropertyTitle("Block Light")) + .add() + .appendInherited( + new KeyedCodec<>("TickProcedure", TickProcedure.CODEC), + (blockType, v) -> blockType.tickProcedure = v, + blockType -> blockType.tickProcedure, + (blockType, parent) -> blockType.tickProcedure = parent.tickProcedure + ) + .add() + .appendInherited( + new KeyedCodec<>("ConnectedBlockRuleSet", ConnectedBlockRuleSet.CODEC), + (blockType, connectedBlockRuleSet) -> blockType.connectedBlockRuleSet = connectedBlockRuleSet, + blockType -> blockType.connectedBlockRuleSet, + (blockType, parent) -> blockType.connectedBlockRuleSet = parent.connectedBlockRuleSet + ) + .add() + .appendInherited( + new KeyedCodec<>("Effect", new ArrayCodec<>(new EnumCodec<>(ShaderType.class), ShaderType[]::new)), + (blockType, o) -> blockType.effect = o, + blockType -> blockType.effect, + (blockType, parent) -> blockType.effect = parent.effect + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .add() + .appendInherited( + new KeyedCodec<>("TransitionTexture", Codec.STRING), + (blockType, o) -> blockType.transitionTexture = o, + blockType -> blockType.transitionTexture, + (blockType, parent) -> blockType.transitionTexture = parent.transitionTexture + ) + .addValidator(CommonAssetValidator.TEXTURE_ITEM) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS, UIRebuildCaches.ClientCache.BLOCK_TEXTURES)) + .add() + .appendInherited( + new KeyedCodec<>("TransitionToGroups", new ArrayCodec<>(Codec.STRING, String[]::new).metadata(new UIEditor(new UIEditor.TextField("BlockGroups")))), + (blockType, o) -> blockType.transitionToGroups = o, + blockType -> blockType.transitionToGroups, + (blockType, parent) -> blockType.transitionToGroups = parent.transitionToGroups + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .add() + .appendInherited( + new KeyedCodec<>("TransitionToTag", Codec.STRING), + (blockType, o) -> blockType.transitionToTag = o, + blockType -> blockType.transitionToTag, + (blockType, parent) -> blockType.transitionToTag = parent.transitionToTag + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .add() + .appendInherited( + new KeyedCodec<>("BlockParticleSetId", Codec.STRING), + (blockType, s) -> blockType.blockParticleSetId = s, + blockType -> blockType.blockParticleSetId, + (blockType, parent) -> blockType.blockParticleSetId = parent.blockParticleSetId + ) + .documentation( + "The block particle set defined here defines which particles should be spawned when an entity interacts with this block (like when stepping on it for example" + ) + .addValidator(BlockParticleSet.VALIDATOR_CACHE.getValidator()) + .metadata(new UIEditorSectionStart("Particles")) + .add() + .appendInherited( + new KeyedCodec<>("ParticleColor", ProtocolCodecs.COLOR), + (blockType, s) -> blockType.particleColor = s, + blockType -> blockType.particleColor, + (blockType, parent) -> blockType.particleColor = parent.particleColor + ) + .add() + .appendInherited( + new KeyedCodec<>("Particles", ModelParticle.ARRAY_CODEC), + (blockType, s) -> blockType.particles = s, + blockType -> blockType.particles, + (blockType, parent) -> blockType.particles = parent.particles + ) + .documentation("The particles defined here will be spawned on top of blocks of this type placed in the world.") + .metadata(new UIPropertyTitle("Block Particles")) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .addValidator(Validators.nonNullArrayElements()) + .add() + .appendInherited( + new KeyedCodec<>("RandomRotation", new EnumCodec<>(RandomRotation.class)), + (blockType, o) -> blockType.randomRotation = o, + blockType -> blockType.randomRotation, + (blockType, parent) -> blockType.randomRotation = parent.randomRotation + ) + .metadata(new UIEditorSectionStart("Rotation")) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("VariantRotation", new EnumCodec<>(VariantRotation.class)), + (blockType, o) -> blockType.variantRotation = o, + blockType -> blockType.variantRotation, + (blockType, parent) -> blockType.variantRotation = parent.variantRotation + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("FlipType", new EnumCodec<>(BlockFlipType.class)), + (blockType, o) -> blockType.flipType = o, + blockType -> blockType.flipType, + (blockType, parent) -> blockType.flipType = parent.flipType + ) + .add() + .appendInherited( + new KeyedCodec<>("RotationYawPlacementOffset", new EnumCodec<>(Rotation.class)), + (blockType, o) -> blockType.rotationYawPlacementOffset = o, + blockType -> blockType.rotationYawPlacementOffset, + (blockType, parent) -> blockType.rotationYawPlacementOffset = parent.rotationYawPlacementOffset + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Seats", RotatedMountPointsArray.CODEC), + (blockType, o) -> blockType.seats = o, + blockType -> blockType.seats, + (blockType, parent) -> blockType.seats = parent.seats + ) + .metadata(new UIEditorSectionStart("Behaviour")) + .documentation("The details of the seats on this block.") + .add() + .appendInherited( + new KeyedCodec<>("Beds", RotatedMountPointsArray.CODEC), + (blockType, o) -> blockType.beds = o, + blockType -> blockType.beds, + (blockType, parent) -> blockType.beds = parent.beds + ) + .documentation("The details of the beds for this block.") + .add() + .appendInherited( + new KeyedCodec<>("MovementSettings", BlockMovementSettings.CODEC), + (blockType, o) -> blockType.movementSettings = o, + blockType -> blockType.movementSettings, + (blockType, parent) -> blockType.movementSettings = parent.movementSettings + ) + .add() + .appendInherited( + new KeyedCodec<>( + "Flags", + BuilderCodec.builder(BlockFlags.class, BlockFlags::new) + .appendInherited( + new KeyedCodec<>("IsUsable", Codec.BOOLEAN), + (blockFlags, b) -> blockFlags.isUsable = b, + blockFlags -> blockFlags.isUsable, + (blockFlags, parent) -> blockFlags.isUsable = parent.isUsable + ) + .add() + .appendInherited( + new KeyedCodec<>("IsStackable", Codec.BOOLEAN), + (blockFlags, b) -> blockFlags.isStackable = b, + blockFlags -> blockFlags.isStackable, + (blockFlags, parent) -> blockFlags.isStackable = parent.isStackable + ) + .add() + .build() + ), + (blockType, o) -> blockType.flags = o, + blockType -> blockType.flags, + (blockType, parent) -> blockType.flags = new BlockFlags(parent.flags) + ) + .add() + .appendInherited( + new KeyedCodec<>("Bench", Bench.CODEC), + (blockType, s) -> blockType.bench = s, + blockType -> blockType.bench, + (blockType, parent) -> blockType.bench = parent.bench + ) + .add() + .appendInherited( + new KeyedCodec<>("Gathering", BlockGathering.CODEC), + (blockType, s) -> blockType.gathering = s, + blockType -> blockType.gathering, + (blockType, parent) -> blockType.gathering = parent.gathering + ) + .add() + .appendInherited( + new KeyedCodec<>("PlacementSettings", BlockPlacementSettings.CODEC), + (blockType, s) -> blockType.placementSettings = s, + blockType -> blockType.placementSettings, + (blockType, parent) -> blockType.placementSettings = parent.placementSettings + ) + .add() + .appendInherited( + new KeyedCodec<>("Farming", FarmingData.CODEC), + (blockType, farming) -> blockType.farming = farming, + blockType -> blockType.farming, + (blockType, parent) -> blockType.farming = parent.farming + ) + .add() + .appendInherited( + new KeyedCodec<>("IsDoor", Codec.BOOLEAN), + (blockType, s) -> blockType.isDoor = s, + blockType -> blockType.isDoor, + (blockType, parent) -> blockType.isDoor = parent.isDoor + ) + .add() + .appendInherited( + new KeyedCodec<>("AllowsMultipleUsers", Codec.BOOLEAN), + (blockType, b) -> blockType.allowsMultipleUsers = b, + blockType -> blockType.allowsMultipleUsers, + (blockType, parent) -> blockType.allowsMultipleUsers = parent.allowsMultipleUsers + ) + .add() + .appendInherited( + new KeyedCodec<>("HitboxType", Codec.STRING), + (blockType, o) -> blockType.hitboxType = o, + blockType -> blockType.hitboxType, + (blockType, parent) -> blockType.hitboxType = parent.hitboxType + ) + .addValidator(BlockBoundingBoxes.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("InteractionHitboxType", Codec.STRING), + (blockType, o) -> blockType.interactionHitboxType = o, + blockType -> blockType.interactionHitboxType, + (blockType, parent) -> blockType.interactionHitboxType = parent.interactionHitboxType + ) + .addValidator(BlockBoundingBoxes.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("InteractionHint", Codec.STRING), + (blockType, s) -> blockType.interactionHint = s, + blockType -> blockType.interactionHint, + (blockType, parent) -> blockType.interactionHint = parent.interactionHint + ) + .documentation( + "This property allows to specify custom text that will be displayed underneath the crosshair when the player aims at this block. The value of this property should be a reference to a translation. *{key}* will be replaced with the interaction input binding." + ) + .add() + .appendInherited( + new KeyedCodec<>("DamageToEntities", Codec.INTEGER), + (blockType, s) -> blockType.damageToEntities = s, + blockType -> blockType.damageToEntities, + (blockType, parent) -> blockType.damageToEntities = parent.damageToEntities + ) + .add() + .>appendInherited( + new KeyedCodec<>("Interactions", new EnumMapCodec<>(InteractionType.class, RootInteraction.CHILD_ASSET_CODEC)), + (item, v) -> item.interactions = MapUtil.combineUnmodifiable(item.interactions, v, () -> new EnumMap<>(InteractionType.class)), + item -> item.interactions, + (item, parent) -> item.interactions = parent.interactions + ) + .addValidator(RootInteraction.VALIDATOR_CACHE.getMapValueValidator()) + .metadata(new UIEditorSectionStart("Interactions")) + .add() + .appendInherited( + new KeyedCodec<>("BlockSoundSetId", Codec.STRING), + (blockType, o) -> blockType.blockSoundSetId = o, + blockType -> blockType.blockSoundSetId, + (blockType, parent) -> blockType.blockSoundSetId = parent.blockSoundSetId + ) + .documentation("Sets the **BlockSoundSet** that will be used for this block for various events e.g. placement, breaking") + .addValidator(BlockSoundSet.VALIDATOR_CACHE.getValidator()) + .metadata(new UIEditorSectionStart("Sounds")) + .add() + .appendInherited( + new KeyedCodec<>("AmbientSoundEventId", Codec.STRING), + (blockType, s) -> blockType.ambientSoundEventId = s, + blockType -> blockType.ambientSoundEventId, + (blockType, parent) -> blockType.ambientSoundEventId = parent.ambientSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .addValidator(SoundEventValidators.LOOPING) + .documentation("A looping ambient sound event that emits from this block when placed in the world or held in-hand.") + .add() + .appendInherited( + new KeyedCodec<>("InteractionSoundEventId", Codec.STRING), + (blockType, s) -> blockType.interactionSoundEventId = s, + blockType -> blockType.interactionSoundEventId, + (blockType, parent) -> blockType.interactionSoundEventId = parent.interactionSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .addValidator(SoundEventValidators.ONESHOT) + .documentation("A oneshot sound event that plays upon interaction with this block.") + .add() + .appendInherited( + new KeyedCodec<>("Looping", Codec.BOOLEAN), + (blockType, s) -> blockType.isLooping = s, + blockType -> blockType.isLooping, + (blockType, parent) -> blockType.isLooping = parent.isLooping + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .add() + .appendInherited( + new KeyedCodec<>("SupportDropType", SupportDropType.CODEC), + (blockType, o) -> blockType.supportDropType = o, + blockType -> blockType.supportDropType, + (blockType, parent) -> blockType.supportDropType = parent.supportDropType + ) + .metadata(new UIEditorSectionStart("Support")) + .add() + .appendInherited( + new KeyedCodec<>("MaxSupportDistance", Codec.INTEGER), + (blockType, i) -> blockType.maxSupportDistance = i, + blockType -> blockType.maxSupportDistance, + (blockType, parent) -> blockType.maxSupportDistance = parent.maxSupportDistance + ) + .addValidator(Validators.range(0, 14)) + .add() + .appendInherited( + new KeyedCodec<>("SupportsRequiredFor", new EnumCodec<>(BlockSupportsRequiredForType.class)), + (blockType, o) -> blockType.blockSupportsRequiredFor = o, + blockType -> blockType.blockSupportsRequiredFor, + (blockType, parent) -> blockType.blockSupportsRequiredFor = parent.blockSupportsRequiredFor + ) + .addValidator(Validators.nonNull()) + .add() + .>appendInherited( + new KeyedCodec<>( + "Support", + new MergedEnumMapCodec<>( + BlockFace.class, + MergedBlockFaces.class, + MergedBlockFaces::getComponents, + ArrayUtil::combine, + new ArrayCodec<>(RequiredBlockFaceSupport.CODEC, RequiredBlockFaceSupport[]::new) + ) + ), + (blockType, o) -> blockType.support = o, + blockType -> blockType.support, + (blockType, parent) -> blockType.support = parent.support + ) + .addValidator(RequiredBlockFaceSupportValidator.INSTANCE) + .documentation( + "A set of \"Required Support\" conditions. If met, the block won't fall off from block physics checks.\n*If this field is empty the block is automatically considered supported.*\n" + ) + .add() + .appendInherited( + new KeyedCodec<>( + "Supporting", + new MergedEnumMapCodec<>( + BlockFace.class, + MergedBlockFaces.class, + MergedBlockFaces::getComponents, + ArrayUtil::combine, + new ArrayCodec<>(BlockFaceSupport.CODEC, BlockFaceSupport[]::new) + ) + ), + (blockType, o) -> blockType.supporting = o, + blockType -> blockType.supporting, + (blockType, parent) -> blockType.supporting = parent.supporting + ) + .add() + .documentation("The counter-party to \"Support\". This block offers supporting faces which can match the face requirements of adjacent/nearby blocks.") + .appendInherited( + new KeyedCodec<>("IgnoreSupportWhenPlaced", Codec.BOOLEAN), + (o, i) -> o.ignoreSupportWhenPlaced = i, + o -> o.ignoreSupportWhenPlaced, + (o, p) -> o.ignoreSupportWhenPlaced = p.ignoreSupportWhenPlaced + ) + .documentation("Whether when this block is placed by a player that the support requirements should be ignored.") + .add() + .append( + new KeyedCodec<>("Aliases", new ArrayCodec<>(Codec.STRING, String[]::new)), (blockType, o) -> blockType.aliases = o, blockType -> blockType.aliases + ) + .documentation("Specifies the alternatives names (aliases) for a block type for use in command matching") + .add() + .append(new KeyedCodec<>("Tint", ProtocolCodecs.COLOR_ARRAY), (blockType, o) -> { + blockType.tintUp = o; + blockType.tintDown = o; + blockType.tintNorth = o; + blockType.tintSouth = o; + blockType.tintWest = o; + blockType.tintEast = o; + }, blockType -> null) + .metadata(new UIEditorSectionStart("Tint")) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MAP_GEOMETRY)) + .add() + .appendInherited( + new KeyedCodec<>("TintUp", ProtocolCodecs.COLOR_ARRAY), + (blockType, o) -> blockType.tintUp = o, + blockType -> blockType.tintUp, + (blockType, parent) -> blockType.tintUp = parent.tintUp + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MAP_GEOMETRY)) + .add() + .appendInherited( + new KeyedCodec<>("TintDown", ProtocolCodecs.COLOR_ARRAY), + (blockType, o) -> blockType.tintDown = o, + blockType -> blockType.tintDown, + (blockType, parent) -> blockType.tintDown = parent.tintDown + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MAP_GEOMETRY)) + .add() + .appendInherited( + new KeyedCodec<>("TintNorth", ProtocolCodecs.COLOR_ARRAY), + (blockType, o) -> blockType.tintNorth = o, + blockType -> blockType.tintNorth, + (blockType, parent) -> blockType.tintNorth = parent.tintNorth + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MAP_GEOMETRY)) + .add() + .appendInherited( + new KeyedCodec<>("TintSouth", ProtocolCodecs.COLOR_ARRAY), + (blockType, o) -> blockType.tintSouth = o, + blockType -> blockType.tintSouth, + (blockType, parent) -> blockType.tintSouth = parent.tintSouth + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MAP_GEOMETRY)) + .add() + .appendInherited( + new KeyedCodec<>("TintWest", ProtocolCodecs.COLOR_ARRAY), + (blockType, o) -> blockType.tintWest = o, + blockType -> blockType.tintWest, + (blockType, parent) -> blockType.tintWest = parent.tintWest + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MAP_GEOMETRY)) + .add() + .appendInherited( + new KeyedCodec<>("TintEast", ProtocolCodecs.COLOR_ARRAY), + (blockType, o) -> blockType.tintEast = o, + blockType -> blockType.tintEast, + (blockType, parent) -> blockType.tintEast = parent.tintEast + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MAP_GEOMETRY)) + .add() + .append(new KeyedCodec<>("BiomeTint", Codec.INTEGER), (blockType, o) -> { + blockType.biomeTintUp = o; + blockType.biomeTintDown = o; + blockType.biomeTintNorth = o; + blockType.biomeTintSouth = o; + blockType.biomeTintWest = o; + blockType.biomeTintEast = o; + }, blockType -> null) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MAP_GEOMETRY)) + .add() + .appendInherited( + new KeyedCodec<>("BiomeTintUp", Codec.INTEGER), + (blockType, o) -> blockType.biomeTintUp = o, + blockType -> blockType.biomeTintUp, + (blockType, parent) -> blockType.biomeTintUp = parent.biomeTintUp + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MAP_GEOMETRY)) + .add() + .appendInherited( + new KeyedCodec<>("BiomeTintDown", Codec.INTEGER), + (blockType, o) -> blockType.biomeTintDown = o, + blockType -> blockType.biomeTintDown, + (blockType, parent) -> blockType.biomeTintDown = parent.biomeTintDown + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MAP_GEOMETRY)) + .add() + .appendInherited( + new KeyedCodec<>("BiomeTintNorth", Codec.INTEGER), + (blockType, o) -> blockType.biomeTintNorth = o, + blockType -> blockType.biomeTintNorth, + (blockType, parent) -> blockType.biomeTintNorth = parent.biomeTintNorth + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MAP_GEOMETRY)) + .add() + .appendInherited( + new KeyedCodec<>("BiomeTintSouth", Codec.INTEGER), + (blockType, o) -> blockType.biomeTintSouth = o, + blockType -> blockType.biomeTintSouth, + (blockType, parent) -> blockType.biomeTintSouth = parent.biomeTintSouth + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MAP_GEOMETRY)) + .add() + .appendInherited( + new KeyedCodec<>("BiomeTintWest", Codec.INTEGER), + (blockType, o) -> blockType.biomeTintWest = o, + blockType -> blockType.biomeTintWest, + (blockType, parent) -> blockType.biomeTintWest = parent.biomeTintWest + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MAP_GEOMETRY)) + .add() + .appendInherited( + new KeyedCodec<>("BiomeTintEast", Codec.INTEGER), + (blockType, o) -> blockType.biomeTintEast = o, + blockType -> blockType.biomeTintEast, + (blockType, parent) -> blockType.biomeTintEast = parent.biomeTintEast + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MAP_GEOMETRY)) + .add() + .appendInherited(new KeyedCodec<>("State", StateData.CODEC), (blockType, s) -> { + s.copyFrom(blockType.state); + blockType.state = s; + }, blockType -> blockType.state, (blockType, parent) -> blockType.state = parent.state) + .metadata(new UIEditorSectionStart("State")) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .>appendInherited( + new KeyedCodec<>("BlockEntity", new StoredCodec<>(ChunkStore.HOLDER_CODEC_KEY)), + (blockType, s) -> blockType.blockEntity = s, + blockType -> blockType.blockEntity, + (blockType, parent) -> blockType.blockEntity = parent.blockEntity + ) + .metadata(new UIEditorSectionStart("Components")) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("Rail", ProtocolCodecs.RAIL_CONFIG_CODEC), (o, v) -> o.railConfig = v, o -> o.railConfig, (o, p) -> o.railConfig = p.railConfig + ) + .add() + .afterDecode(BlockType::processConfig) + .build(); + public static final String[] EMPTY_ALIAS_LIST = new String[0]; + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(BlockType::getAssetStore)); + public static final String UNKNOWN_TEXTURE = "BlockTextures/Unknown.png"; + public static final ModelTexture[] UNKNOWN_CUSTOM_MODEL_TEXTURE = new ModelTexture[]{new ModelTexture("BlockTextures/Unknown.png", 1.0F)}; + public static final BlockTextures[] UNKNOWN_BLOCK_TEXTURES = new BlockTextures[]{ + new BlockTextures( + "BlockTextures/Unknown.png", + "BlockTextures/Unknown.png", + "BlockTextures/Unknown.png", + "BlockTextures/Unknown.png", + "BlockTextures/Unknown.png", + "BlockTextures/Unknown.png", + 1.0F + ) + }; + public static final Map REQUIRED_BOTTOM_FACE_SUPPORT = Collections.unmodifiableMap( + new EnumMap(BlockFace.class) { + { + this.put(BlockFace.DOWN, new RequiredBlockFaceSupport[]{new RequiredBlockFaceSupport("Full")}); + } + } + ); + public static final BlockFaceSupport[] BLOCK_FACE_SUPPORT_ALL_ARRAY = new BlockFaceSupport[]{BlockFaceSupport.ALL}; + public static final Map ALL_SUPPORTING_FACES = Collections.unmodifiableMap( + new EnumMap(BlockFace.class) { + { + for (BlockFace blockFace : BlockFace.VALUES) { + this.put(blockFace, BlockType.BLOCK_FACE_SUPPORT_ALL_ARRAY); + } + } + } + ); + public static final ShaderType[] DEFAULT_SHADER_EFFECTS = new ShaderType[]{ShaderType.None}; + public static final BlockType DEFAULT_BLOCK_TYPE = new BlockType(); + public static final ISectionPalette.KeySerializer KEY_SERIALIZER = (buf, id) -> { + String key = getAssetMap().getAssetOrDefault(id, BlockType.UNKNOWN).getId(); + ByteBufUtil.writeUTF(buf, key); + }; + public static final ToIntFunction KEY_DESERIALIZER = byteBuf -> { + String blockType = ByteBufUtil.readUTF(byteBuf); + return getBlockIdOrUnknown(blockType, "Failed to find block '%s' in chunk section!", blockType); + }; + public static final String EMPTY_KEY = "Empty"; + public static final String UNKNOWN_KEY = "Unknown"; + public static final String DEBUG_CUBE_KEY = "Debug_Cube"; + public static final String DEBUG_MODEL_KEY = "Debug_Model"; + public static final int EMPTY_ID = 0; + public static final BlockType EMPTY = new BlockType("Empty") { + { + this.drawType = DrawType.Empty; + this.material = BlockMaterial.Empty; + this.opacity = Opacity.Transparent; + this.group = "Air"; + this.support = Collections.emptyMap(); + this.processConfig(); + } + }; + public static final int UNKNOWN_ID = 1; + public static final BlockType UNKNOWN = new BlockType("Unknown") { + { + this.unknown = true; + this.drawType = DrawType.Cube; + this.material = BlockMaterial.Solid; + this.processConfig(); + } + }; + public static final int DEBUG_CUBE_ID = 2; + public static final BlockType DEBUG_CUBE = new BlockType("Debug_Cube") { + { + this.drawType = DrawType.Cube; + this.material = BlockMaterial.Solid; + this.variantRotation = VariantRotation.Debug; + this.textures = new BlockTypeTextures[]{ + new BlockTypeTextures( + "BlockTextures/_Debug/Up.png", + "BlockTextures/_Debug/Down.png", + "BlockTextures/_Debug/North.png", + "BlockTextures/_Debug/South.png", + "BlockTextures/_Debug/East.png", + "BlockTextures/_Debug/West.png", + 1 + ) + }; + this.processConfig(); + } + }; + public static final int DEBUG_MODEL_ID = 3; + public static final BlockType DEBUG_MODEL = new BlockType("Debug_Model") { + { + this.drawType = DrawType.Model; + this.material = BlockMaterial.Empty; + this.variantRotation = VariantRotation.Debug; + this.customModel = "Blocks/_Debug/Model.blockymodel"; + this.customModelTexture = new CustomModelTexture[]{new CustomModelTexture("Blocks/_Debug/Texture.png", 1)}; + this.processConfig(); + } + }; + public static final String TECHNICAL_BLOCK_GROUP = "@Tech"; + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected boolean unknown; + protected String group; + protected String blockListAssetId; + protected String prefabListAssetId; + protected String blockSoundSetId = "EMPTY"; + protected transient int blockSoundSetIndex = 0; + protected ModelParticle[] particles; + protected String blockParticleSetId; + protected String blockBreakingDecalId; + protected Color particleColor; + protected TickProcedure tickProcedure; + protected ShaderType[] effect; + protected BlockTypeTextures[] textures; + protected String textureSideMask; + @Nonnull + protected ShadingMode cubeShadingMode = ShadingMode.Standard; + @Nullable + protected String customModel; + @Nullable + protected CustomModelTexture[] customModelTexture; + protected float customModelScale = 1.0F; + protected String customModelAnimation; + @Nonnull + protected DrawType drawType = DrawType.Cube; + @Nonnull + protected BlockMaterial material = BlockMaterial.Empty; + @Nonnull + protected Opacity opacity = Opacity.Solid; + protected boolean requiresAlphaBlending; + protected Color[] tintUp; + protected Color[] tintDown; + protected Color[] tintNorth; + protected Color[] tintSouth; + protected Color[] tintWest; + protected Color[] tintEast; + protected int biomeTintUp; + protected int biomeTintDown; + protected int biomeTintNorth; + protected int biomeTintSouth; + protected int biomeTintWest; + protected int biomeTintEast; + @Nonnull + protected BlockSupportsRequiredForType blockSupportsRequiredFor = BlockSupportsRequiredForType.All; + @Nonnull + protected RandomRotation randomRotation = RandomRotation.None; + @Nonnull + protected VariantRotation variantRotation = VariantRotation.None; + protected BlockFlipType flipType = BlockFlipType.SYMMETRIC; + @Nonnull + protected Rotation rotationYawPlacementOffset = Rotation.None; + @Nullable + protected RotatedMountPointsArray seats; + @Nullable + protected RotatedMountPointsArray beds; + protected String transitionTexture; + protected String[] transitionToGroups; + protected String transitionToTag; + protected String hitboxType = "Full"; + protected transient int hitboxTypeIndex = 0; + @Nullable + protected String interactionHitboxType; + protected transient int interactionHitboxTypeIndex = Integer.MIN_VALUE; + protected ColorLight light; + protected BlockMovementSettings movementSettings = new BlockMovementSettings(); + protected BlockFlags flags = new BlockFlags(false, true); + protected String interactionHint; + protected boolean isTrigger; + @Deprecated + protected boolean isDoor; + protected int damageToEntities; + protected boolean allowsMultipleUsers = true; + @Nullable + protected ConnectedBlockRuleSet connectedBlockRuleSet; + protected Bench bench; + protected BlockGathering gathering; + protected BlockPlacementSettings placementSettings; + protected StateData state; + protected String ambientSoundEventId; + protected transient int ambientSoundEventIndex; + protected String interactionSoundEventId; + protected transient int interactionSoundEventIndex; + protected boolean isLooping; + protected Holder blockEntity; + protected FarmingData farming; + protected SupportDropType supportDropType = SupportDropType.BREAK; + protected int maxSupportDistance; + @Nullable + protected Map support; + @Nullable + protected transient Map[] rotatedSupport; + @Nullable + protected Map supporting; + @Nullable + protected transient Map[] rotatedSupporting; + protected boolean ignoreSupportWhenPlaced; + protected Map interactions = Collections.emptyMap(); + @Nullable + protected RailConfig railConfig; + @Nullable + protected RailConfig[] rotatedRailConfig; + protected String[] aliases = EMPTY_ALIAS_LIST; + @Nullable + private transient String defaultStateKey; + @Nullable + private transient SoftReference cachedPacket; + + @Nullable + public static BlockType fromString(@Nonnull String input) { + return getAssetMap().getAsset(input); + } + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(BlockType.class); + } + + return ASSET_STORE; + } + + public static BlockTypeAssetMap getAssetMap() { + return (BlockTypeAssetMap)getAssetStore().getAssetMap(); + } + + public BlockType() { + } + + public BlockType(String id) { + this.id = id; + } + + public BlockType(@Nonnull BlockType other) { + this.data = other.data; + this.id = other.id; + this.unknown = other.unknown; + this.group = other.group; + this.blockSoundSetId = other.blockSoundSetId; + this.blockSoundSetIndex = other.blockSoundSetIndex; + this.particles = other.particles; + this.blockParticleSetId = other.blockParticleSetId; + this.blockBreakingDecalId = other.blockBreakingDecalId; + this.particleColor = other.particleColor; + this.tickProcedure = other.tickProcedure; + this.effect = other.effect; + this.textures = other.textures; + this.textureSideMask = other.textureSideMask; + this.customModelTexture = other.customModelTexture; + this.drawType = other.drawType; + this.material = other.material; + this.opacity = other.opacity; + this.requiresAlphaBlending = other.requiresAlphaBlending; + this.customModel = other.customModel; + this.customModelScale = other.customModelScale; + this.customModelAnimation = other.customModelAnimation; + this.tintUp = other.tintUp; + this.tintDown = other.tintDown; + this.tintNorth = other.tintNorth; + this.tintSouth = other.tintSouth; + this.tintWest = other.tintWest; + this.tintEast = other.tintEast; + this.biomeTintUp = other.biomeTintUp; + this.biomeTintDown = other.biomeTintDown; + this.biomeTintNorth = other.biomeTintNorth; + this.biomeTintSouth = other.biomeTintSouth; + this.biomeTintWest = other.biomeTintWest; + this.biomeTintEast = other.biomeTintEast; + this.randomRotation = other.randomRotation; + this.variantRotation = other.variantRotation; + this.flipType = other.flipType; + this.rotationYawPlacementOffset = other.rotationYawPlacementOffset; + this.seats = other.seats; + this.transitionTexture = other.transitionTexture; + this.transitionToGroups = other.transitionToGroups; + this.transitionToTag = other.transitionToTag; + this.hitboxType = other.hitboxType; + this.hitboxTypeIndex = other.hitboxTypeIndex; + this.interactionHitboxType = other.interactionHitboxType; + this.interactionHitboxTypeIndex = other.interactionHitboxTypeIndex; + this.light = other.light; + this.movementSettings = other.movementSettings; + this.flags = other.flags; + this.interactionHint = other.interactionHint; + this.isTrigger = other.isTrigger; + this.damageToEntities = other.damageToEntities; + this.bench = other.bench; + this.gathering = other.gathering; + this.placementSettings = other.placementSettings; + this.state = other.state; + this.blockEntity = other.blockEntity; + this.farming = other.farming; + this.supportDropType = other.supportDropType; + this.maxSupportDistance = other.maxSupportDistance; + this.support = other.support; + this.supporting = other.supporting; + this.cubeShadingMode = other.cubeShadingMode; + this.allowsMultipleUsers = other.allowsMultipleUsers; + this.interactions = other.interactions; + this.ambientSoundEventId = other.ambientSoundEventId; + this.ambientSoundEventIndex = other.ambientSoundEventIndex; + this.interactionSoundEventId = other.interactionSoundEventId; + this.interactionSoundEventIndex = other.interactionSoundEventIndex; + this.isLooping = other.isLooping; + this.isDoor = other.isDoor; + this.blockSupportsRequiredFor = other.blockSupportsRequiredFor; + this.connectedBlockRuleSet = other.connectedBlockRuleSet; + this.railConfig = other.railConfig; + this.ignoreSupportWhenPlaced = other.ignoreSupportWhenPlaced; + } + + @Nonnull + public com.hypixel.hytale.protocol.BlockType toPacket() { + com.hypixel.hytale.protocol.BlockType cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + BlockTypeAssetMap blockTypeAssetMap = getAssetMap(); + com.hypixel.hytale.protocol.BlockType packet = new com.hypixel.hytale.protocol.BlockType(); + packet.name = this.id; + Item item = this.getItem(); + if (item != null) { + packet.item = item.getId(); + } + + packet.unknown = this.unknown; + if (this.group != null) { + packet.group = getAssetMap().getGroupId(this.group); + } + + packet.blockSoundSetIndex = this.blockSoundSetIndex; + packet.blockParticleSetId = this.blockParticleSetId; + packet.blockBreakingDecalId = this.blockBreakingDecalId; + packet.particleColor = this.particleColor; + if (this.support != null) { + Object2ObjectOpenHashMap supportMap = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : this.support.entrySet()) { + RequiredBlockFaceSupport[] supports = entry.getValue(); + com.hypixel.hytale.protocol.RequiredBlockFaceSupport[] protocolSupports = new com.hypixel.hytale.protocol.RequiredBlockFaceSupport[supports.length]; + + for (int i = 0; i < supports.length; i++) { + protocolSupports[i] = supports[i].toPacket(); + } + + supportMap.put(entry.getKey().toProtocolBlockNeighbor(), protocolSupports); + } + + packet.support = supportMap; + } + + if (this.supporting != null) { + Object2ObjectOpenHashMap supportingMap = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : this.supporting.entrySet()) { + BlockFaceSupport[] blockFaceSupports = entry.getValue(); + com.hypixel.hytale.protocol.BlockFaceSupport[] protocolBlockFaceSupports = new com.hypixel.hytale.protocol.BlockFaceSupport[blockFaceSupports.length]; + + for (int i = 0; i < blockFaceSupports.length; i++) { + protocolBlockFaceSupports[i] = blockFaceSupports[i].toPacket(); + } + + supportingMap.put(entry.getKey().toProtocolBlockNeighbor(), protocolBlockFaceSupports); + } + + packet.supporting = supportingMap; + } + packet.blockSupportsRequiredFor = switch (this.blockSupportsRequiredFor) { + case Any -> com.hypixel.hytale.protocol.BlockSupportsRequiredForType.Any; + case All -> com.hypixel.hytale.protocol.BlockSupportsRequiredForType.All; + }; + packet.maxSupportDistance = this.maxSupportDistance; + if (this.effect != null && this.effect.length > 0) { + packet.shaderEffect = this.effect; + } else { + packet.shaderEffect = DEFAULT_SHADER_EFFECTS; + } + + if (this.textures != null && this.textures.length > 0) { + int totalWeight = 0; + + for (BlockTypeTextures texture : this.textures) { + totalWeight = (int)(totalWeight + texture.getWeight()); + } + + BlockTextures[] texturePackets = new BlockTextures[this.textures.length]; + + for (int i = 0; i < this.textures.length; i++) { + texturePackets[i] = this.textures[i].toPacket(totalWeight); + } + + packet.cubeTextures = texturePackets; + } else { + packet.cubeTextures = UNKNOWN_BLOCK_TEXTURES; + } + + packet.cubeSideMaskTexture = this.textureSideMask; + packet.cubeShadingMode = this.cubeShadingMode; + if (this.customModelTexture != null && this.customModelTexture.length > 0) { + int totalWeight = 0; + + for (CustomModelTexture modelTexture : this.customModelTexture) { + totalWeight += modelTexture.getWeight(); + } + + ModelTexture[] texturePackets = new ModelTexture[this.customModelTexture.length]; + + for (int i = 0; i < this.customModelTexture.length; i++) { + texturePackets[i] = this.customModelTexture[i].toPacket(totalWeight); + } + + packet.modelTexture = texturePackets; + } else { + packet.modelTexture = UNKNOWN_CUSTOM_MODEL_TEXTURE; + } + + packet.drawType = this.drawType; + packet.requiresAlphaBlending = this.requiresAlphaBlending; + if (this.customModel != null) { + packet.model = this.customModel; + } + + packet.modelScale = this.customModelScale; + if (this.customModelAnimation != null) { + packet.modelAnimation = this.customModelAnimation; + } + + packet.tint = new Tint(); + if (this.tintUp != null && this.tintUp.length > 0) { + packet.tint.top = ColorParseUtil.colorToARGBInt(this.tintUp[0]); + } else { + packet.tint.top = -1; + } + + if (this.tintDown != null && this.tintDown.length > 0) { + packet.tint.bottom = ColorParseUtil.colorToARGBInt(this.tintDown[0]); + } else { + packet.tint.bottom = -1; + } + + if (this.tintNorth != null && this.tintNorth.length > 0) { + packet.tint.back = ColorParseUtil.colorToARGBInt(this.tintNorth[0]); + } else { + packet.tint.back = -1; + } + + if (this.tintSouth != null && this.tintSouth.length > 0) { + packet.tint.front = ColorParseUtil.colorToARGBInt(this.tintSouth[0]); + } else { + packet.tint.front = -1; + } + + if (this.tintWest != null && this.tintWest.length > 0) { + packet.tint.left = ColorParseUtil.colorToARGBInt(this.tintWest[0]); + } else { + packet.tint.left = -1; + } + + if (this.tintEast != null && this.tintEast.length > 0) { + packet.tint.right = ColorParseUtil.colorToARGBInt(this.tintEast[0]); + } else { + packet.tint.right = -1; + } + + packet.biomeTint = new Tint(this.biomeTintUp, this.biomeTintDown, this.biomeTintSouth, this.biomeTintNorth, this.biomeTintWest, this.biomeTintEast); + packet.variantRotation = this.variantRotation.toPacket(); + packet.randomRotation = this.randomRotation; + packet.rotationYawPlacementOffset = this.rotationYawPlacementOffset.toPacket(); + packet.opacity = this.opacity; + if (this.transitionTexture != null) { + packet.transitionTexture = this.transitionTexture; + } + + if (this.transitionToGroups != null && this.transitionToGroups.length > 0) { + int[] arr = new int[this.transitionToGroups.length]; + + for (int i = 0; i < this.transitionToGroups.length; i++) { + arr[i] = blockTypeAssetMap.getGroupId(this.transitionToGroups[i]); + } + + packet.transitionToGroups = arr; + } + + if (this.transitionToTag != null) { + packet.transitionToTag = AssetRegistry.getOrCreateTagIndex(this.transitionToTag); + } else { + packet.transitionToTag = Integer.MIN_VALUE; + } + + packet.material = this.material; + packet.hitbox = this.hitboxTypeIndex; + packet.interactionHitbox = this.interactionHitboxTypeIndex; + packet.light = this.light; + packet.movementSettings = this.movementSettings.toPacket(); + packet.flags = this.flags; + packet.interactionHint = this.interactionHint; + if (this.gathering != null) { + packet.gathering = this.gathering.toPacket(); + } + + if (this.placementSettings != null) { + packet.placementSettings = this.placementSettings.toPacket(); + } + + packet.looping = this.isLooping; + packet.ambientSoundEventIndex = this.ambientSoundEventIndex; + if (this.particles != null && this.particles.length > 0) { + packet.particles = new com.hypixel.hytale.protocol.ModelParticle[this.particles.length]; + + for (int i = 0; i < this.particles.length; i++) { + packet.particles[i] = this.particles[i].toPacket(); + } + } + + Object2IntOpenHashMap interactionMap = new Object2IntOpenHashMap<>(); + + for (Entry e : this.interactions.entrySet()) { + interactionMap.put(e.getKey(), RootInteraction.getRootInteractionIdOrUnknown(e.getValue())); + } + + packet.interactions = interactionMap; + if (this.state != null) { + packet.states = this.state.toPacket(this); + String def = this.getBlockKeyForState("default"); + if (def != null) { + packet.states.put("default", getAssetMap().getIndex(def)); + } + } + + if (this.data != null) { + IntSet tags = this.data.getExpandedTagIndexes(); + if (tags != null) { + packet.tagIndexes = tags.toIntArray(); + } + } + + packet.rail = this.railConfig; + packet.ignoreSupportWhenPlaced = this.ignoreSupportWhenPlaced; + if (this.bench != null) { + packet.bench = this.bench.toPacket(); + } + + if (this.connectedBlockRuleSet != null) { + packet.connectedBlockRuleSet = this.connectedBlockRuleSet.toPacket(blockTypeAssetMap); + } + + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + public String getId() { + return this.id; + } + + public AssetExtraInfo.Data getData() { + return this.data; + } + + @Nullable + public Item getItem() { + if (this.data == null) { + return null; + } else { + String itemKey = this.data.getContainerKey(Item.class); + return itemKey == null ? null : Item.getAssetMap().getAsset(itemKey); + } + } + + public boolean isState() { + return this.getStateForBlock(this.id) != null; + } + + @Nullable + public BlockType getBlockForState(@Nonnull String state) { + String key = this.getBlockKeyForState(state); + return key == null ? null : getAssetMap().getAsset(key); + } + + @Nullable + public String getBlockKeyForState(@Nonnull String state) { + String key; + if (state.equals("default")) { + key = this.getDefaultStateKey(); + } else { + key = this.state != null ? this.state.getBlockForState(state) : null; + } + + return key; + } + + public String getDefaultStateKey() { + if (this.defaultStateKey == null) { + this.defaultStateKey = this.data.getContainerKey(BlockType.class); + } + + return this.defaultStateKey; + } + + @Nullable + public String getStateForBlock(@Nonnull BlockType blockType) { + return this.getStateForBlock(blockType.getId()); + } + + @Nullable + public String getStateForBlock(@Nonnull String blockTypeKey) { + return this.state != null ? this.state.getStateForBlock(blockTypeKey) : null; + } + + public boolean isUnknown() { + return this.unknown; + } + + public String getGroup() { + return this.group; + } + + public String getBlockSoundSetId() { + return this.blockSoundSetId; + } + + public int getBlockSoundSetIndex() { + return this.blockSoundSetIndex; + } + + public ModelParticle[] getParticles() { + return this.particles; + } + + public String getBlockParticleSetId() { + return this.blockParticleSetId; + } + + public String getBlockBreakingDecalId() { + return this.blockBreakingDecalId; + } + + public Color getParticleColor() { + return this.particleColor; + } + + public TickProcedure getTickProcedure() { + return this.tickProcedure; + } + + public ShaderType[] getEffect() { + return this.effect; + } + + public BlockTypeTextures[] getTextures() { + return this.textures; + } + + public String getTextureSideMask() { + return this.textureSideMask; + } + + @Nullable + public CustomModelTexture[] getCustomModelTexture() { + return this.customModelTexture; + } + + public DrawType getDrawType() { + return this.drawType; + } + + public BlockMaterial getMaterial() { + return this.material; + } + + public Opacity getOpacity() { + return this.opacity; + } + + @Nullable + public String getCustomModel() { + return this.customModel; + } + + public float getCustomModelScale() { + return this.customModelScale; + } + + public String getCustomModelAnimation() { + return this.customModelAnimation; + } + + public Color[] getTintUp() { + return this.tintUp; + } + + public Color[] getTintDown() { + return this.tintDown; + } + + public Color[] getTintNorth() { + return this.tintNorth; + } + + public Color[] getTintSouth() { + return this.tintSouth; + } + + public Color[] getTintWest() { + return this.tintWest; + } + + public Color[] getTintEast() { + return this.tintEast; + } + + public int getBiomeTintUp() { + return this.biomeTintUp; + } + + public int getBiomeTintDown() { + return this.biomeTintDown; + } + + public int getBiomeTintNorth() { + return this.biomeTintNorth; + } + + public int getBiomeTintSouth() { + return this.biomeTintSouth; + } + + public int getBiomeTintWest() { + return this.biomeTintWest; + } + + public int getBiomeTintEast() { + return this.biomeTintEast; + } + + @Nullable + public ConnectedBlockRuleSet getConnectedBlockRuleSet() { + return this.connectedBlockRuleSet; + } + + public BlockSupportsRequiredForType getBlockSupportsRequiredFor() { + return this.blockSupportsRequiredFor; + } + + public RandomRotation getRandomRotation() { + return this.randomRotation; + } + + @Nonnull + public VariantRotation getVariantRotation() { + return this.variantRotation; + } + + public BlockFlipType getFlipType() { + return this.flipType; + } + + public Rotation getRotationYawPlacementOffset() { + return this.rotationYawPlacementOffset; + } + + @Nullable + public RotatedMountPointsArray getSeats() { + return this.seats; + } + + @Nullable + public RotatedMountPointsArray getBeds() { + return this.beds; + } + + public String getTransitionTexture() { + return this.transitionTexture; + } + + public String[] getTransitionToGroups() { + return this.transitionToGroups; + } + + public String getBlockListAssetId() { + return this.blockListAssetId; + } + + public String getPrefabListAssetId() { + return this.prefabListAssetId; + } + + public String getHitboxType() { + return this.hitboxType; + } + + public int getHitboxTypeIndex() { + return this.hitboxTypeIndex; + } + + @Nullable + public String getInteractionHitboxType() { + return this.interactionHitboxType; + } + + public int getInteractionHitboxTypeIndex() { + return this.interactionHitboxTypeIndex; + } + + public ColorLight getLight() { + return this.light; + } + + public BlockMovementSettings getMovementSettings() { + return this.movementSettings; + } + + public BlockFlags getFlags() { + return this.flags; + } + + public String getInteractionHint() { + return this.interactionHint; + } + + public boolean isTrigger() { + return this.isTrigger; + } + + public int getDamageToEntities() { + return this.damageToEntities; + } + + public Bench getBench() { + return this.bench; + } + + public BlockGathering getGathering() { + return this.gathering; + } + + public BlockPlacementSettings getPlacementSettings() { + return this.placementSettings; + } + + public StateData getState() { + return this.state; + } + + public Holder getBlockEntity() { + return this.blockEntity; + } + + public String getAmbientSoundEventId() { + return this.ambientSoundEventId; + } + + public int getAmbientSoundEventIndex() { + return this.ambientSoundEventIndex; + } + + public String getInteractionSoundEventId() { + return this.interactionSoundEventId; + } + + public int getInteractionSoundEventIndex() { + return this.interactionSoundEventIndex; + } + + public boolean isLooping() { + return this.isLooping; + } + + public FarmingData getFarming() { + return this.farming; + } + + public SupportDropType getSupportDropType() { + return this.supportDropType; + } + + public int getMaxSupportDistance() { + return this.maxSupportDistance; + } + + public boolean isFullySupportive() { + return this.supporting == ALL_SUPPORTING_FACES; + } + + @Nullable + public Map getSupport(int rotationIndex) { + if (this.support != null && rotationIndex != 0) { + if (this.rotatedSupport == null) { + this.rotatedSupport = new Map[RotationTuple.VALUES.length]; + } + + Map rotatedSupportArray = this.rotatedSupport[rotationIndex]; + if (rotatedSupportArray == null) { + RotationTuple rotation = RotationTuple.get(rotationIndex); + Map> rotatedSupport = new EnumMap<>(BlockFace.class); + + for (Entry entry : this.support.entrySet()) { + BlockFace blockFace = entry.getKey(); + RequiredBlockFaceSupport[] requiredBlockFaceSupports = entry.getValue(); + BlockFace rotatedBlockFace = BlockFace.rotate(blockFace, rotation.yaw(), rotation.pitch(), rotation.roll()); + + for (RequiredBlockFaceSupport requiredBlockFaceSupport : requiredBlockFaceSupports) { + if (requiredBlockFaceSupport.isRotated()) { + RequiredBlockFaceSupport rotatedRequiredBlockFaceSupport = RequiredBlockFaceSupport.rotate( + requiredBlockFaceSupport, rotation.yaw(), rotation.pitch(), rotation.roll() + ); + rotatedSupport.computeIfAbsent(rotatedBlockFace, k -> new ObjectArrayList<>()).add(rotatedRequiredBlockFaceSupport); + } else { + rotatedSupport.computeIfAbsent(blockFace, k -> new ObjectArrayList<>()).add(requiredBlockFaceSupport); + } + } + } + + rotatedSupportArray = new EnumMap<>(BlockFace.class); + + for (Entry> entry : rotatedSupport.entrySet()) { + rotatedSupportArray.put(entry.getKey(), entry.getValue().toArray(RequiredBlockFaceSupport[]::new)); + } + + this.rotatedSupport[rotationIndex] = rotatedSupportArray; + } + + return rotatedSupportArray; + } else { + return this.support; + } + } + + @Nullable + public Map getSupporting(int rotationIndex) { + if (this.supporting != null && rotationIndex != 0) { + if (this.rotatedSupporting == null) { + this.rotatedSupporting = new Map[RotationTuple.VALUES.length]; + } + + Map rotatedSupportingArray = this.rotatedSupporting[rotationIndex]; + if (rotatedSupportingArray == null) { + RotationTuple rotation = RotationTuple.get(rotationIndex); + if (this.isFullySupportive()) { + rotatedSupportingArray = ALL_SUPPORTING_FACES; + } else { + Map> rotatedSupporting = new EnumMap<>(BlockFace.class); + + for (Entry entry : this.supporting.entrySet()) { + BlockFace blockFace = entry.getKey(); + BlockFaceSupport[] blockFaceSupports = entry.getValue(); + BlockFace rotatedBlockFace = BlockFace.rotate(blockFace, rotation.yaw(), rotation.pitch(), rotation.roll()); + + for (BlockFaceSupport blockFaceSupport : blockFaceSupports) { + BlockFaceSupport rotatedBlockFaceSupport = BlockFaceSupport.rotate(blockFaceSupport, rotation.yaw(), rotation.pitch(), rotation.roll()); + rotatedSupporting.computeIfAbsent(rotatedBlockFace, k -> new ObjectArrayList<>()).add(rotatedBlockFaceSupport); + } + } + + rotatedSupportingArray = new EnumMap<>(BlockFace.class); + + for (Entry> entry : rotatedSupporting.entrySet()) { + rotatedSupportingArray.put(entry.getKey(), entry.getValue().toArray(BlockFaceSupport[]::new)); + } + } + + this.rotatedSupporting[rotationIndex] = rotatedSupportingArray; + } + + return rotatedSupportingArray; + } else { + return this.supporting; + } + } + + public boolean hasSupport() { + return this.support != null && !this.support.isEmpty() || this.maxSupportDistance > 0; + } + + public boolean isAllowsMultipleUsers() { + return this.allowsMultipleUsers; + } + + public Map getInteractions() { + return this.interactions; + } + + @Nullable + public RailConfig getRailConfig(int rotationIndex) { + if (this.railConfig != null && rotationIndex != 0) { + if (this.rotatedRailConfig == null) { + this.rotatedRailConfig = new RailConfig[RotationTuple.VALUES.length]; + } + + RailConfig rotatedRail = this.rotatedRailConfig[rotationIndex]; + if (rotatedRail == null) { + RotationTuple rotation = RotationTuple.get(rotationIndex); + rotatedRail = new RailConfig(this.railConfig); + + for (RailPoint p : rotatedRail.points) { + Vector3f hyPoint = new Vector3f(p.point.x - 0.5F, p.point.y - 0.5F, p.point.z - 0.5F); + hyPoint = Rotation.rotate(hyPoint, rotation.yaw(), rotation.pitch(), rotation.roll()); + p.point.x = hyPoint.x + 0.5F; + p.point.y = hyPoint.y + 0.5F; + p.point.z = hyPoint.z + 0.5F; + Vector3f hyNormal = new Vector3f(p.normal.x, p.normal.y, p.normal.z); + hyNormal = Rotation.rotate(hyNormal, rotation.yaw(), rotation.pitch(), rotation.roll()); + p.normal.x = hyNormal.x; + p.normal.y = hyNormal.y; + p.normal.z = hyNormal.z; + } + + this.rotatedRailConfig[rotationIndex] = rotatedRail; + } + + return rotatedRail; + } else { + return this.railConfig; + } + } + + @Deprecated + public boolean isDoor() { + return this.isDoor; + } + + public boolean shouldIgnoreSupportWhenPlaced() { + return this.ignoreSupportWhenPlaced; + } + + public boolean canBePlacedAsDeco() { + return this.ignoreSupportWhenPlaced || this.gathering != null && this.gathering.shouldUseDefaultDropWhenPlaced(); + } + + protected void processConfig() { + if (this.bench != null) { + if (this.state == null && this.bench.getType() == BenchType.Processing) { + this.state = new StateData("processingBench"); + } + + this.flags.isUsable = true; + if (this.interactionHint == null) { + this.interactionHint = "server.interactionHints.open"; + } + } else if (this.state == null || !"container".equalsIgnoreCase(this.state.getId()) && !"Door".equalsIgnoreCase(this.state.getId())) { + if (this.gathering != null && this.gathering.isHarvestable()) { + this.flags.isUsable = true; + if (this.interactionHint == null) { + this.interactionHint = "server.interactionHints.gather"; + } + } else if (this.seats != null && this.seats.size() > 0 && this.interactionHint == null) { + this.interactionHint = "server.interactionHints.sit"; + } + } else { + this.flags.isUsable = true; + if (this.interactionHint == null) { + this.interactionHint = "server.interactionHints.open"; + } + } + + if (this.interactions.containsKey(InteractionType.Use)) { + this.flags.isUsable = true; + if (this.interactionHint == null) { + this.interactionHint = "server.interactionHints.generic"; + } + } + + if (this.flags.isUsable && this.interactionHint == null) { + this.interactionHint = "server.interactionHints.generic"; + } + + if (this.ambientSoundEventId != null) { + this.ambientSoundEventIndex = SoundEvent.getAssetMap().getIndex(this.ambientSoundEventId); + } + + if (this.interactionSoundEventId != null) { + this.interactionSoundEventIndex = SoundEvent.getAssetMap().getIndex(this.interactionSoundEventId); + } + + if (this.support == null && this.material == BlockMaterial.Empty && !"@Tech".equals(this.group)) { + this.support = REQUIRED_BOTTOM_FACE_SUPPORT; + } + + if (this.supporting == null + && (this.drawType == DrawType.Cube || this.drawType == DrawType.CubeWithModel || this.drawType == DrawType.GizmoCube) + && this.material == BlockMaterial.Solid) { + this.supporting = ALL_SUPPORTING_FACES; + } else if (this.supporting == null) { + this.supporting = Collections.emptyMap(); + } + + this.hitboxTypeIndex = this.hitboxType.equals("Full") ? 0 : BlockBoundingBoxes.getAssetMap().getIndex(this.hitboxType); + if (this.hitboxTypeIndex == Integer.MIN_VALUE) { + HytaleLogger.getLogger().at(Level.WARNING).log("Unknown hitbox '%s' for block '%s', using default", this.hitboxType, this.getId()); + this.hitboxTypeIndex = 0; + } + + if (this.interactionHitboxType != null) { + this.interactionHitboxTypeIndex = this.interactionHitboxType.equals("Full") + ? 0 + : BlockBoundingBoxes.getAssetMap().getIndex(this.interactionHitboxType); + if (this.interactionHitboxTypeIndex == Integer.MIN_VALUE) { + HytaleLogger.getLogger() + .at(Level.WARNING) + .log("Unknown interaction hitbox '%s' for block '%s', using collision hitbox", this.interactionHitboxType, this.getId()); + this.interactionHitboxTypeIndex = this.hitboxTypeIndex; + } + } + + this.blockSoundSetIndex = this.blockSoundSetId.equals("EMPTY") ? 0 : BlockSoundSet.getAssetMap().getIndex(this.blockSoundSetId); + if (this.blockSoundSetIndex == Integer.MIN_VALUE) { + HytaleLogger.getLogger().at(Level.WARNING).log("Unknown block sound set '%s' for block '%s', using empty", this.blockSoundSetId, this.getId()); + this.blockSoundSetIndex = 0; + } + + for (InteractionType type : this.interactions.keySet()) { + if (InteractionTypeUtils.isCollisionType(type)) { + this.isTrigger = true; + break; + } + } + + if (this.bench != null && !this.interactions.containsKey(InteractionType.Use)) { + Map interactions = this.interactions.isEmpty() ? new EnumMap<>(InteractionType.class) : new EnumMap<>(this.interactions); + RootInteraction rootInteraction = this.bench.getRootInteraction(); + if (rootInteraction != null) { + interactions.put(InteractionType.Use, rootInteraction.getId()); + } + + this.interactions = Collections.unmodifiableMap(interactions); + } + } + + @Nonnull + public static BlockType getUnknownFor(String blockTypeKey) { + return UNKNOWN.clone(blockTypeKey); + } + + public void getBlockCenter(int rotationIndex, @Nonnull Vector3d outCenter) { + BlockBoundingBoxes hitboxAsset = BlockBoundingBoxes.getAssetMap().getAsset(this.hitboxTypeIndex); + if (hitboxAsset == null) { + throw new IllegalStateException("Unknown hitbox: " + this.hitboxType); + } else { + BlockBoundingBoxes.RotatedVariantBoxes rotatedHitbox = hitboxAsset.get(rotationIndex); + Box boundingBox = rotatedHitbox.getBoundingBox(); + outCenter.assign(boundingBox.middleX(), boundingBox.middleY(), boundingBox.middleZ()); + } + } + + @Nonnull + @Override + public String toString() { + return "BlockType{id=" + + this.id + + ", unknown=" + + this.unknown + + ", group='" + + this.group + + "', blockSoundSetId='" + + this.blockSoundSetId + + "', blockSoundSetIndex=" + + this.blockSoundSetIndex + + ", particles=" + + Arrays.toString((Object[])this.particles) + + ", blockParticleSetId='" + + this.blockParticleSetId + + "', blockBreakingDecalId='" + + this.blockBreakingDecalId + + "', particleColor=" + + this.particleColor + + ", effect=" + + Arrays.toString((Object[])this.effect) + + ", textures=" + + Arrays.toString((Object[])this.textures) + + ", textureSideMask='" + + this.textureSideMask + + "', cubeShadingMode=" + + this.cubeShadingMode + + ", customModel='" + + this.customModel + + "', customModelTexture=" + + Arrays.toString((Object[])this.customModelTexture) + + ", customModelScale=" + + this.customModelScale + + ", customModelAnimation='" + + this.customModelAnimation + + "', drawType=" + + this.drawType + + ", material=" + + this.material + + ", opacity=" + + this.opacity + + ", requiresAlphaBlending=" + + this.requiresAlphaBlending + + ", tickProcedure" + + this.tickProcedure + + ", tintUp=" + + Arrays.toString((Object[])this.tintUp) + + ", tintDown=" + + Arrays.toString((Object[])this.tintDown) + + ", tintNorth=" + + Arrays.toString((Object[])this.tintNorth) + + ", tintSouth=" + + Arrays.toString((Object[])this.tintSouth) + + ", tintWest=" + + Arrays.toString((Object[])this.tintWest) + + ", tintEast=" + + Arrays.toString((Object[])this.tintEast) + + ", biomeTintUp=" + + this.biomeTintUp + + ", biomeTintDown=" + + this.biomeTintDown + + ", biomeTintNorth=" + + this.biomeTintNorth + + ", biomeTintSouth=" + + this.biomeTintSouth + + ", biomeTintWest=" + + this.biomeTintWest + + ", biomeTintEast=" + + this.biomeTintEast + + ", randomRotation=" + + this.randomRotation + + ", variantRotation=" + + this.variantRotation + + ", flipType=" + + this.flipType + + ", rotationYawPlacementOffset=" + + this.rotationYawPlacementOffset + + ", transitionTexture='" + + this.transitionTexture + + "', transitionToGroups=" + + Arrays.toString((Object[])this.transitionToGroups) + + ", hitboxType='" + + this.hitboxType + + "', hitboxTypeIndex=" + + this.hitboxTypeIndex + + ", interactionHitboxType='" + + this.interactionHitboxType + + "', interactionHitboxTypeIndex=" + + this.interactionHitboxTypeIndex + + ", light=" + + this.light + + ", movementSettings=" + + this.movementSettings + + ", flags=" + + this.flags + + ", interactionHint='" + + this.interactionHint + + "', isTrigger=" + + this.isTrigger + + ", damageToEntities=" + + this.damageToEntities + + ", allowsMultipleUsers=" + + this.allowsMultipleUsers + + ", bench=" + + this.bench + + ", gathering=" + + this.gathering + + ", placementSettings=" + + this.placementSettings + + ", state=" + + this.state + + ", ambientSoundEventId='" + + this.ambientSoundEventId + + "', ambientSoundEventIndex='" + + this.ambientSoundEventIndex + + "', interactionSoundEventId='" + + this.interactionSoundEventId + + "', interactionSoundEventIndex='" + + this.interactionSoundEventIndex + + "', isLooping=" + + this.isLooping + + ", farming=" + + this.farming + + ", supportDropType=" + + this.supportDropType + + ", maxSupportDistance=" + + this.maxSupportDistance + + ", support=" + + this.support + + ", supporting=" + + this.supporting + + ", interactions=" + + this.interactions + + ", railConfig=" + + this.railConfig + + "}"; + } + + @Nonnull + public BlockType clone(String newKey) { + if (this.id != null && this.id.equals(newKey)) { + return this; + } else { + BlockType blockType = new BlockType(this); + blockType.id = newKey; + blockType.cachedPacket = null; + return blockType; + } + } + + public static int getBlockIdOrUnknown(@Nonnull String blockTypeKey, String message, Object... params) { + return getBlockIdOrUnknown(getAssetMap(), blockTypeKey, message, params); + } + + public static int getBlockIdOrUnknown(@Nonnull BlockTypeAssetMap assetMap, @Nonnull String blockTypeKey, String message, Object... params) { + int blockId = assetMap.getIndex(blockTypeKey); + if (blockId == Integer.MIN_VALUE) { + HytaleLogger.getLogger().at(Level.WARNING).logVarargs(message, params); + AssetRegistry.getAssetStore(BlockType.class) + .loadAssets("Hytale:Hytale", Collections.singletonList(getUnknownFor(blockTypeKey)), AssetUpdateQuery.DEFAULT_NO_REBUILD); + int index = assetMap.getIndex(blockTypeKey); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + blockTypeKey); + } + + blockId = index; + } + + return blockId; + } + + static { + StateData.addDefinitions(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockTypeTextures.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockTypeTextures.java new file mode 100644 index 0000000..8bc091a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/BlockTypeTextures.java @@ -0,0 +1,159 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.BlockTextures; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import javax.annotation.Nonnull; + +public class BlockTypeTextures { + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockTypeTextures.class, BlockTypeTextures::new) + .append(new KeyedCodec<>("All", Codec.STRING), (blockType, o) -> { + blockType.up = o; + blockType.down = o; + blockType.north = o; + blockType.south = o; + blockType.west = o; + blockType.east = o; + }, blockType -> null) + .addValidator(CommonAssetValidator.TEXTURE_ITEM) + .add() + .append(new KeyedCodec<>("Sides", Codec.STRING), (blockType, o) -> { + blockType.north = o; + blockType.south = o; + blockType.west = o; + blockType.east = o; + }, blockType -> null) + .addValidator(CommonAssetValidator.TEXTURE_ITEM) + .add() + .append(new KeyedCodec<>("UpDown", Codec.STRING), (blockType, o) -> { + blockType.up = o; + blockType.down = o; + }, blockType -> null) + .addValidator(CommonAssetValidator.TEXTURE_ITEM) + .add() + .appendInherited( + new KeyedCodec<>("Up", Codec.STRING), (blockType, o) -> blockType.up = o, blockType -> blockType.up, (blockType, parent) -> blockType.up = parent.up + ) + .addValidator(CommonAssetValidator.TEXTURE_ITEM) + .add() + .appendInherited( + new KeyedCodec<>("Down", Codec.STRING), + (blockType, o) -> blockType.down = o, + blockType -> blockType.down, + (blockType, parent) -> blockType.down = parent.down + ) + .addValidator(CommonAssetValidator.TEXTURE_ITEM) + .add() + .appendInherited( + new KeyedCodec<>("North", Codec.STRING), + (blockType, o) -> blockType.north = o, + blockType -> blockType.north, + (blockType, parent) -> blockType.north = parent.north + ) + .addValidator(CommonAssetValidator.TEXTURE_ITEM) + .add() + .appendInherited( + new KeyedCodec<>("South", Codec.STRING), + (blockType, o) -> blockType.south = o, + blockType -> blockType.south, + (blockType, parent) -> blockType.south = parent.south + ) + .addValidator(CommonAssetValidator.TEXTURE_ITEM) + .add() + .appendInherited( + new KeyedCodec<>("West", Codec.STRING), + (blockType, o) -> blockType.west = o, + blockType -> blockType.west, + (blockType, parent) -> blockType.west = parent.west + ) + .addValidator(CommonAssetValidator.TEXTURE_ITEM) + .add() + .appendInherited( + new KeyedCodec<>("East", Codec.STRING), + (blockType, o) -> blockType.east = o, + blockType -> blockType.east, + (blockType, parent) -> blockType.east = parent.east + ) + .addValidator(CommonAssetValidator.TEXTURE_ITEM) + .add() + .appendInherited( + new KeyedCodec<>("Weight", Codec.INTEGER), + (blockType, o) -> blockType.weight = o, + blockType -> blockType.weight, + (blockType, parent) -> blockType.weight = parent.weight + ) + .add() + .build(); + protected String up = "BlockTextures/Unknown.png"; + protected String down = "BlockTextures/Unknown.png"; + protected String north = "BlockTextures/Unknown.png"; + protected String south = "BlockTextures/Unknown.png"; + protected String east = "BlockTextures/Unknown.png"; + protected String west = "BlockTextures/Unknown.png"; + protected int weight = 1; + + public BlockTypeTextures() { + } + + public BlockTypeTextures(String all) { + this.up = all; + this.down = all; + this.north = all; + this.south = all; + this.east = all; + this.west = all; + } + + public BlockTypeTextures(String up, String down, String north, String south, String east, String west, int weight) { + this.up = up; + this.down = down; + this.north = north; + this.south = south; + this.east = east; + this.west = west; + this.weight = weight; + } + + public String getUp() { + return this.up; + } + + public String getDown() { + return this.down; + } + + public String getNorth() { + return this.north; + } + + public String getSouth() { + return this.south; + } + + public String getEast() { + return this.east; + } + + public String getWest() { + return this.west; + } + + public float getWeight() { + return this.weight; + } + + @Nonnull + public BlockTextures toPacket(float totalWeight) { + BlockTextures packet = new BlockTextures(); + packet.top = this.up; + packet.bottom = this.down; + packet.front = this.south; + packet.back = this.north; + packet.left = this.west; + packet.right = this.east; + packet.weight = this.weight / totalWeight; + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/CustomModelTexture.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/CustomModelTexture.java new file mode 100644 index 0000000..537c83b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/CustomModelTexture.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.ModelTexture; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import javax.annotation.Nonnull; + +public class CustomModelTexture { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder(CustomModelTexture.class, CustomModelTexture::new) + .append( + new KeyedCodec<>("Texture", Codec.STRING), (customModelTexture, s) -> customModelTexture.texture = s, customModelTexture -> customModelTexture.texture + ) + .addValidator(CommonAssetValidator.TEXTURE_ITEM) + .add() + .addField( + new KeyedCodec<>("Weight", Codec.INTEGER), (customModelTexture, i) -> customModelTexture.weight = i, customModelTexture -> customModelTexture.weight + ) + .build(); + private String texture; + private int weight; + + public CustomModelTexture() { + } + + public CustomModelTexture(String texture, int weight) { + this.texture = texture; + this.weight = weight; + } + + public String getTexture() { + return this.texture; + } + + public int getWeight() { + return this.weight; + } + + @Nonnull + public ModelTexture toPacket(float totalWight) { + return new ModelTexture(this.texture, this.weight / totalWight); + } + + @Nonnull + @Override + public String toString() { + return "CustomModelTexture{texture='" + this.texture + "', weight=" + this.weight + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/HarvestingDropType.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/HarvestingDropType.java new file mode 100644 index 0000000..8152c44 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/HarvestingDropType.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.Harvesting; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HarvestingDropType implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(HarvestingDropType.class, HarvestingDropType::new) + .append(new KeyedCodec<>("ItemId", Codec.STRING), (harvesting, o) -> harvesting.itemId = o, harvesting -> harvesting.itemId) + .addValidatorLate(() -> Item.VALIDATOR_CACHE.getValidator().late()) + .add() + .append( + new KeyedCodec<>("DropList", new ContainedAssetCodec<>(ItemDropList.class, ItemDropList.CODEC)), + (harvesting, o) -> harvesting.dropListId = o, + harvesting -> harvesting.dropListId + ) + .addValidatorLate(() -> ItemDropList.VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + protected String itemId; + protected String dropListId; + + public HarvestingDropType(String itemId, String dropListId) { + this.itemId = itemId; + this.dropListId = dropListId; + } + + protected HarvestingDropType() { + } + + @Nonnull + public Harvesting toPacket() { + Harvesting packet = new Harvesting(); + if (this.itemId != null) { + packet.itemId = this.itemId.toString(); + } + + packet.dropListId = this.dropListId; + return packet; + } + + public String getItemId() { + return this.itemId; + } + + public String getDropListId() { + return this.dropListId; + } + + @Nullable + public HarvestingDropType withoutDrops() { + return null; + } + + @Nonnull + @Override + public String toString() { + return "HarvestingDropType{itemId=" + this.itemId + ", dropListId='" + this.dropListId + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/MergedBlockFaces.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/MergedBlockFaces.java new file mode 100644 index 0000000..0296bb9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/MergedBlockFaces.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.codec.codecs.EnumCodec; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public enum MergedBlockFaces { + ALL(BlockFace.VALUES), + BLOCK_SIDES(BlockFace.UP, BlockFace.DOWN, BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST), + CARDINAL_DIRECTIONS(BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST), + HORIZONTAL( + BlockFace.NORTH, BlockFace.NORTH_EAST, BlockFace.EAST, BlockFace.SOUTH_EAST, BlockFace.SOUTH, BlockFace.SOUTH_WEST, BlockFace.WEST, BlockFace.NORTH_WEST + ), + UP_CARDINAL_DIRECTIONS(BlockFace.UP_NORTH, BlockFace.UP_EAST, BlockFace.UP_SOUTH, BlockFace.UP_WEST), + DOWN_CARDINAL_DIRECTIONS(BlockFace.DOWN_NORTH, BlockFace.DOWN_EAST, BlockFace.DOWN_SOUTH, BlockFace.DOWN_WEST); + + @Nonnull + public static EnumCodec CODEC = new EnumCodec<>(MergedBlockFaces.class); + private final BlockFace[] components; + + private MergedBlockFaces(BlockFace... components) { + this.components = components; + } + + public BlockFace[] getComponents() { + return this.components; + } + + @Nonnull + @Override + public String toString() { + return "MergedBlockFaces{components=" + Arrays.toString((Object[])this.components) + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/PhysicsDropType.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/PhysicsDropType.java new file mode 100644 index 0000000..a3a9069 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/PhysicsDropType.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PhysicsDropType { + public static final BuilderCodec CODEC = BuilderCodec.builder(PhysicsDropType.class, PhysicsDropType::new) + .append(new KeyedCodec<>("ItemId", Codec.STRING), (softBlock, o) -> softBlock.itemId = o, softBlock -> softBlock.itemId) + .addValidatorLate(() -> Item.VALIDATOR_CACHE.getValidator().late()) + .add() + .append( + new KeyedCodec<>("DropList", new ContainedAssetCodec<>(ItemDropList.class, ItemDropList.CODEC)), + (softBlock, o) -> softBlock.dropListId = o, + softBlock -> softBlock.dropListId + ) + .addValidatorLate(() -> ItemDropList.VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + protected String itemId; + protected String dropListId; + + public PhysicsDropType(String itemId, String dropListId) { + this.itemId = itemId; + this.dropListId = dropListId; + } + + protected PhysicsDropType() { + } + + public String getItemId() { + return this.itemId; + } + + public String getDropListId() { + return this.dropListId; + } + + @Nullable + public PhysicsDropType withoutDrops() { + return null; + } + + @Nonnull + @Override + public String toString() { + return "PhysicsDropType{itemId=" + this.itemId + ", dropListId='" + this.dropListId + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/RequiredBlockFaceSupport.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/RequiredBlockFaceSupport.java new file mode 100644 index 0000000..60d7387 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/RequiredBlockFaceSupport.java @@ -0,0 +1,294 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.SupportMatch; +import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class RequiredBlockFaceSupport implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(RequiredBlockFaceSupport.class, RequiredBlockFaceSupport::new) + .append(new KeyedCodec<>("FaceType", Codec.STRING), (blockFaceSupport, o) -> blockFaceSupport.faceType = o, blockFaceSupport -> blockFaceSupport.faceType) + .documentation("Can be any string. Compared with FaceType in \"Supporting\" of other blocks. A LOT of blocks use 'Full'.") + .add() + .addField( + new KeyedCodec<>("SelfFaceType", Codec.STRING), + (blockFaceSupport, o) -> blockFaceSupport.selfFaceType = o, + blockFaceSupport -> blockFaceSupport.selfFaceType + ) + .addField( + new KeyedCodec<>("BlockSetId", Codec.STRING), + (blockFaceSupport, o) -> blockFaceSupport.blockSetId = o, + blockFaceSupport -> blockFaceSupport.blockSetId + ) + .addField( + new KeyedCodec<>("BlockTypeId", Codec.STRING), + (blockFaceSupport, o) -> blockFaceSupport.blockTypeId = o, + blockFaceSupport -> blockFaceSupport.blockTypeId + ) + .addField(new KeyedCodec<>("FluidId", Codec.STRING), (blockFaceSupport, o) -> blockFaceSupport.fluidId = o, blockFaceSupport -> blockFaceSupport.fluidId) + .addField( + new KeyedCodec<>("MatchSelf", RequiredBlockFaceSupport.Match.CODEC), + (requiredBlockFaceSupport, b) -> requiredBlockFaceSupport.matchSelf = b, + requiredBlockFaceSupport -> requiredBlockFaceSupport.matchSelf + ) + .addField( + new KeyedCodec<>("Support", RequiredBlockFaceSupport.Match.CODEC), + (requiredBlockFaceSupport, b) -> requiredBlockFaceSupport.support = b, + requiredBlockFaceSupport -> requiredBlockFaceSupport.support + ) + .addField( + new KeyedCodec<>("AllowSupportPropagation", Codec.BOOLEAN), + (requiredBlockFaceSupport, b) -> requiredBlockFaceSupport.allowSupportPropagation = b, + requiredBlockFaceSupport -> requiredBlockFaceSupport.allowSupportPropagation + ) + .addField( + new KeyedCodec<>("Rotate", Codec.BOOLEAN), + (requiredBlockFaceSupport, b) -> requiredBlockFaceSupport.rotate = b, + requiredBlockFaceSupport -> requiredBlockFaceSupport.rotate + ) + .addField( + new KeyedCodec<>("Filler", new ArrayCodec<>(Vector3i.CODEC, Vector3i[]::new)), + (blockFaceSupport, o) -> blockFaceSupport.filler = o, + blockFaceSupport -> blockFaceSupport.filler + ) + .append( + new KeyedCodec<>("TagId", Codec.STRING), + (requiredBlockFaceSupport, s) -> requiredBlockFaceSupport.tagId = s, + requiredBlockFaceSupport -> requiredBlockFaceSupport.tagId + ) + .add() + .afterDecode(blockFaceSupport -> { + if (blockFaceSupport.blockSetId != null) { + int index = BlockSet.getAssetMap().getIndex(blockFaceSupport.blockSetId); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + blockFaceSupport.blockSetId); + } + + blockFaceSupport.blockSetIndex = index; + } + + if (blockFaceSupport.tagId != null) { + blockFaceSupport.tagIndex = AssetRegistry.getOrCreateTagIndex(blockFaceSupport.tagId); + } + }) + .build(); + protected String faceType; + protected String selfFaceType; + protected String blockSetId; + protected int blockSetIndex = Integer.MIN_VALUE; + protected String blockTypeId; + protected String fluidId; + protected RequiredBlockFaceSupport.Match matchSelf = RequiredBlockFaceSupport.Match.IGNORED; + protected String tagId; + protected int tagIndex = Integer.MIN_VALUE; + protected RequiredBlockFaceSupport.Match support = RequiredBlockFaceSupport.Match.REQUIRED; + protected boolean allowSupportPropagation = true; + protected boolean rotate = true; + protected Vector3i[] filler; + + public RequiredBlockFaceSupport() { + } + + public RequiredBlockFaceSupport(String faceType) { + this.faceType = faceType; + } + + public RequiredBlockFaceSupport( + String faceType, + String selfFaceType, + String blockSetId, + String blockTypeId, + String fluidId, + RequiredBlockFaceSupport.Match matchSelf, + RequiredBlockFaceSupport.Match support, + boolean allowSupportPropagation, + boolean rotate, + Vector3i[] filler, + String tagId, + int tagIndex + ) { + this.faceType = faceType; + this.selfFaceType = selfFaceType; + this.blockSetId = blockSetId; + this.blockTypeId = blockTypeId; + this.fluidId = fluidId; + this.matchSelf = matchSelf; + this.support = support; + this.allowSupportPropagation = allowSupportPropagation; + this.rotate = rotate; + this.filler = filler; + this.tagId = tagId; + this.tagIndex = tagIndex; + } + + public String getFaceType() { + return this.faceType; + } + + public String getSelfFaceType() { + return this.selfFaceType; + } + + public String getBlockSetId() { + return this.blockSetId; + } + + public int getBlockSetIndex() { + return this.blockSetIndex; + } + + public String getBlockTypeId() { + return this.blockTypeId; + } + + public String getFluidId() { + return this.fluidId; + } + + public RequiredBlockFaceSupport.Match getMatchSelf() { + return this.matchSelf; + } + + public RequiredBlockFaceSupport.Match getSupport() { + return this.support; + } + + public boolean allowsSupportPropagation() { + return this.allowSupportPropagation; + } + + public boolean isRotated() { + return this.rotate; + } + + public Vector3i[] getFiller() { + return this.filler; + } + + public boolean isAppliedToFiller(Vector3i filler) { + return this.filler == null || ArrayUtil.contains(this.filler, filler); + } + + public String getTagId() { + return this.tagId; + } + + public int getTagIndex() { + return this.tagIndex; + } + + @Nonnull + @Override + public String toString() { + return "RequiredBlockFaceSupport{faceType='" + + this.faceType + + "', selfFaceType='" + + this.selfFaceType + + "', blockSetId='" + + this.blockSetId + + "', blockTypeId=" + + this.blockTypeId + + ", fluidId=" + + this.fluidId + + ", matchSelf=" + + this.matchSelf + + ", support=" + + this.support + + ", allowSupportPropagation=" + + this.allowSupportPropagation + + ", rotate=" + + this.rotate + + ", filler=" + + Arrays.toString((Object[])this.filler) + + ", tagId=" + + this.tagId + + "}"; + } + + @Nonnull + public static RequiredBlockFaceSupport rotate( + @Nonnull RequiredBlockFaceSupport original, @Nonnull Rotation rotationYaw, @Nonnull Rotation rotationPitch, @Nonnull Rotation roll + ) { + Vector3i[] rotatedFiller = ArrayUtil.copyAndMutate(original.filler, vector -> Rotation.rotate(vector, rotationYaw, rotationPitch, roll), Vector3i[]::new); + return new RequiredBlockFaceSupport( + original.faceType, + original.selfFaceType, + original.blockSetId, + original.blockTypeId, + original.fluidId, + original.matchSelf, + original.support, + original.allowSupportPropagation, + original.rotate, + rotatedFiller, + original.tagId, + original.tagIndex + ); + } + + @Nonnull + public com.hypixel.hytale.protocol.RequiredBlockFaceSupport toPacket() { + com.hypixel.hytale.protocol.RequiredBlockFaceSupport packet = new com.hypixel.hytale.protocol.RequiredBlockFaceSupport(); + packet.faceType = this.faceType; + packet.selfFaceType = this.selfFaceType; + packet.tagIndex = this.tagIndex; + packet.blockTypeId = this.blockTypeId == null ? -1 : BlockType.getAssetMap().getIndex(this.blockTypeId); + if (packet.blockTypeId == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + this.blockTypeId); + } else { + packet.fluidId = this.fluidId == null ? -1 : Fluid.getAssetMap().getIndex(this.fluidId); + if (packet.fluidId == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + this.fluidId); + } else { + packet.allowSupportPropagation = this.allowSupportPropagation; + packet.rotate = this.rotate; + packet.blockSetId = this.blockSetId; + + packet.support = switch (this.support) { + case IGNORED -> SupportMatch.Ignored; + case REQUIRED -> SupportMatch.Required; + case DISALLOWED -> SupportMatch.Disallowed; + }; + + packet.matchSelf = switch (this.matchSelf) { + case IGNORED -> SupportMatch.Ignored; + case REQUIRED -> SupportMatch.Required; + case DISALLOWED -> SupportMatch.Disallowed; + }; + if (this.filler != null) { + com.hypixel.hytale.protocol.Vector3i[] filler = new com.hypixel.hytale.protocol.Vector3i[this.filler.length]; + + for (int j = 0; j < this.filler.length; j++) { + Vector3i fillerVector = this.filler[j]; + filler[j] = new com.hypixel.hytale.protocol.Vector3i(fillerVector.x, fillerVector.y, fillerVector.z); + } + + packet.filler = filler; + } + + return packet; + } + } + } + + public static enum Match { + IGNORED, + REQUIRED, + DISALLOWED; + + public static final EnumCodec CODEC = new EnumCodec<>(RequiredBlockFaceSupport.Match.class); + + private Match() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/RequiredBlockFaceSupportValidator.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/RequiredBlockFaceSupportValidator.java new file mode 100644 index 0000000..7f40738 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/RequiredBlockFaceSupportValidator.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.codec.validation.ValidationResults; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +class RequiredBlockFaceSupportValidator implements LegacyValidator> { + static final RequiredBlockFaceSupportValidator INSTANCE = new RequiredBlockFaceSupportValidator(); + + RequiredBlockFaceSupportValidator() { + } + + public void accept(@Nullable Map support, @Nonnull ValidationResults results) { + if (support != null) { + for (Entry entry : support.entrySet()) { + BlockFace blockFace = entry.getKey(); + RequiredBlockFaceSupport[] requiredBlockFaceSupports = entry.getValue(); + + for (int i = 0; i < requiredBlockFaceSupports.length; i++) { + RequiredBlockFaceSupport blockFaceSupport = requiredBlockFaceSupports[i]; + if (blockFaceSupport == null) { + results.fail("Value for 'Support." + blockFace + "[" + i + "]' can't be null!"); + } else { + boolean noRequirements = blockFaceSupport.getFaceType() == null + && blockFaceSupport.getBlockSetId() == null + && blockFaceSupport.getBlockTypeId() == null + && blockFaceSupport.getFluidId() == null + && blockFaceSupport.getMatchSelf() == RequiredBlockFaceSupport.Match.IGNORED + && blockFaceSupport.getTagId() == null; + if (blockFaceSupport.getSupport() != RequiredBlockFaceSupport.Match.IGNORED && noRequirements) { + results.warn("Value for 'Support." + blockFace + "[" + i + "]' doesn't define any requirements and should be removed!"); + } + + if (blockFaceSupport.getSupport() == RequiredBlockFaceSupport.Match.IGNORED && !blockFaceSupport.allowsSupportPropagation()) { + results.warn("Value for 'Support." + blockFace + "[" + i + "]' doesn't allow support or support propagation so should be removed!"); + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/Rotation.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/Rotation.java new file mode 100644 index 0000000..8cdacac --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/Rotation.java @@ -0,0 +1,290 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public enum Rotation implements NetworkSerializable { + None(0, com.hypixel.hytale.protocol.Rotation.None, Axis.Z, Vector3i.NEG_Z), + Ninety(90, com.hypixel.hytale.protocol.Rotation.Ninety, Axis.X, Vector3i.NEG_X), + OneEighty(180, com.hypixel.hytale.protocol.Rotation.OneEighty, Axis.Z, Vector3i.POS_Z), + TwoSeventy(270, com.hypixel.hytale.protocol.Rotation.TwoSeventy, Axis.X, Vector3i.POS_X); + + public static final Rotation[] VALUES = values(); + public static final Rotation[] NORMAL = new Rotation[]{None, Ninety, OneEighty, TwoSeventy}; + public static final Codec CODEC = new EnumCodec<>(Rotation.class); + private final int degrees; + private final com.hypixel.hytale.protocol.Rotation packet; + private final Axis axisOfAlignment; + private final Vector3i axisDirection; + + private Rotation(int degrees, com.hypixel.hytale.protocol.Rotation packet, Axis axisOfAlignment, Vector3i axisDirection) { + this.degrees = degrees; + this.packet = packet; + this.axisOfAlignment = axisOfAlignment; + this.axisDirection = axisDirection; + } + + public com.hypixel.hytale.protocol.Rotation toPacket() { + return this.packet; + } + + public int getDegrees() { + return this.degrees; + } + + public double getRadians() { + return Math.toRadians(this.degrees); + } + + public Axis getAxisOfAlignment() { + return this.axisOfAlignment; + } + + public Vector3i getAxisDirection() { + return this.axisDirection; + } + + @Nonnull + public Rotation flip() { + return this.add(OneEighty); + } + + @Nonnull + public Rotation flip(Axis axis) { + return this.axisOfAlignment != axis ? this : this.flip(); + } + + @Nonnull + public Rotation subtract(@Nullable Rotation rotation) { + return rotation == null ? this : ofDegrees(this.degrees - rotation.degrees); + } + + @Nonnull + public Rotation add(@Nullable Rotation rotation) { + return rotation == null ? this : ofDegrees(this.degrees + rotation.degrees); + } + + public static Rotation add(@Nullable Rotation a, Rotation b) { + return a == null ? b : a.add(b); + } + + @Nonnull + public Vector3i rotatePitch(@Nonnull Vector3i in, @Nonnull Vector3i out) { + return this.rotateX(in, out); + } + + @Nonnull + public Vector3f rotatePitch(@Nonnull Vector3f in, @Nonnull Vector3f out) { + return this.rotateX(in, out); + } + + public int rotateX(int filler) { + return switch (this) { + case None -> filler; + case Ninety -> FillerBlockUtil.pack(FillerBlockUtil.unpackX(filler), -FillerBlockUtil.unpackZ(filler), FillerBlockUtil.unpackY(filler)); + case OneEighty -> FillerBlockUtil.pack(FillerBlockUtil.unpackX(filler), -FillerBlockUtil.unpackY(filler), -FillerBlockUtil.unpackZ(filler)); + case TwoSeventy -> FillerBlockUtil.pack(FillerBlockUtil.unpackX(filler), FillerBlockUtil.unpackZ(filler), -FillerBlockUtil.unpackY(filler)); + }; + } + + @Nonnull + public Vector3i rotateX(@Nonnull Vector3i in, @Nonnull Vector3i out) { + return switch (this) { + case None -> out.assign(in); + case Ninety -> out.assign(in.x, -in.z, in.y); + case OneEighty -> out.assign(in.x, -in.y, -in.z); + case TwoSeventy -> out.assign(in.x, in.z, -in.y); + }; + } + + @Nonnull + public Vector3f rotateX(@Nonnull Vector3f in, @Nonnull Vector3f out) { + return switch (this) { + case None -> out.assign(in); + case Ninety -> out.assign(in.x, -in.z, in.y); + case OneEighty -> out.assign(in.x, -in.y, -in.z); + case TwoSeventy -> out.assign(in.x, in.z, -in.y); + }; + } + + @Nonnull + public Vector3d rotateX(@Nonnull Vector3d in, @Nonnull Vector3d out) { + return switch (this) { + case None -> out.assign(in); + case Ninety -> out.assign(in.x, -in.z, in.y); + case OneEighty -> out.assign(in.x, -in.y, -in.z); + case TwoSeventy -> out.assign(in.x, in.z, -in.y); + }; + } + + @Nonnull + public Vector3i rotateYaw(@Nonnull Vector3i in, @Nonnull Vector3i out) { + return this.rotateY(in, out); + } + + @Nonnull + public Vector3f rotateYaw(@Nonnull Vector3f in, @Nonnull Vector3f out) { + return this.rotateY(in, out); + } + + public int rotateY(int filler) { + return switch (this) { + case None -> filler; + case Ninety -> FillerBlockUtil.pack(FillerBlockUtil.unpackZ(filler), FillerBlockUtil.unpackY(filler), -FillerBlockUtil.unpackX(filler)); + case OneEighty -> FillerBlockUtil.pack(-FillerBlockUtil.unpackX(filler), FillerBlockUtil.unpackY(filler), -FillerBlockUtil.unpackZ(filler)); + case TwoSeventy -> FillerBlockUtil.pack(-FillerBlockUtil.unpackZ(filler), FillerBlockUtil.unpackY(filler), FillerBlockUtil.unpackX(filler)); + }; + } + + @Nonnull + public Vector3i rotateY(@Nonnull Vector3i in, @Nonnull Vector3i out) { + return switch (this) { + case None -> out.assign(in); + case Ninety -> out.assign(in.z, in.y, -in.x); + case OneEighty -> out.assign(-in.x, in.y, -in.z); + case TwoSeventy -> out.assign(-in.z, in.y, in.x); + }; + } + + @Nonnull + public Vector3f rotateY(@Nonnull Vector3f in, @Nonnull Vector3f out) { + return switch (this) { + case None -> out.assign(in); + case Ninety -> out.assign(in.z, in.y, -in.x); + case OneEighty -> out.assign(-in.x, in.y, -in.z); + case TwoSeventy -> out.assign(-in.z, in.y, in.x); + }; + } + + @Nonnull + public Vector3d rotateY(@Nonnull Vector3d in, @Nonnull Vector3d out) { + return switch (this) { + case None -> out.assign(in); + case Ninety -> out.assign(in.z, in.y, -in.x); + case OneEighty -> out.assign(-in.x, in.y, -in.z); + case TwoSeventy -> out.assign(-in.z, in.y, in.x); + }; + } + + @Nonnull + private Vector3i rotateRoll(@Nonnull Vector3i in, @Nonnull Vector3i out) { + return this.rotateZ(in, out); + } + + @Nonnull + private Vector3f rotateRoll(@Nonnull Vector3f in, @Nonnull Vector3f out) { + return this.rotateZ(in, out); + } + + public int rotateZ(int filler) { + return switch (this) { + case None -> filler; + case Ninety -> FillerBlockUtil.pack(-FillerBlockUtil.unpackY(filler), FillerBlockUtil.unpackX(filler), FillerBlockUtil.unpackZ(filler)); + case OneEighty -> FillerBlockUtil.pack(-FillerBlockUtil.unpackX(filler), -FillerBlockUtil.unpackY(filler), FillerBlockUtil.unpackZ(filler)); + case TwoSeventy -> FillerBlockUtil.pack(FillerBlockUtil.unpackY(filler), -FillerBlockUtil.unpackX(filler), FillerBlockUtil.unpackZ(filler)); + }; + } + + @Nonnull + public Vector3i rotateZ(@Nonnull Vector3i in, @Nonnull Vector3i out) { + return switch (this) { + case None -> out.assign(in); + case Ninety -> out.assign(-in.y, in.x, in.z); + case OneEighty -> out.assign(-in.x, -in.y, in.z); + case TwoSeventy -> out.assign(in.y, -in.x, in.z); + }; + } + + @Nonnull + public Vector3f rotateZ(@Nonnull Vector3f in, @Nonnull Vector3f out) { + return switch (this) { + case None -> out.assign(in); + case Ninety -> out.assign(-in.y, in.x, in.z); + case OneEighty -> out.assign(-in.x, -in.y, in.z); + case TwoSeventy -> out.assign(in.y, -in.x, in.z); + }; + } + + @Nonnull + public Vector3d rotateZ(@Nonnull Vector3d in, @Nonnull Vector3d out) { + return switch (this) { + case None -> out.assign(in); + case Ninety -> out.assign(-in.y, in.x, in.z); + case OneEighty -> out.assign(-in.x, -in.y, in.z); + case TwoSeventy -> out.assign(in.y, -in.x, in.z); + }; + } + + @Nonnull + public static Rotation ofDegrees(int degrees) { + degrees = Math.floorMod(degrees, 360); + + return switch (degrees) { + case 0 -> None; + case 90 -> Ninety; + case 180 -> OneEighty; + case 270 -> TwoSeventy; + default -> throw new IllegalArgumentException("Rotation degrees value " + degrees + " cannot be mapped to type Rotation"); + }; + } + + public static Rotation closestOfDegrees(float degrees) { + if (degrees < 0.0F) { + degrees = degrees % 360.0F + 360.0F; + } + + return VALUES[MathUtil.fastRound(degrees / 90.0F) % VALUES.length]; + } + + @Nonnull + public static Rotation valueOf(@Nonnull com.hypixel.hytale.protocol.Rotation packet) { + return switch (packet) { + case None -> None; + case Ninety -> Ninety; + case OneEighty -> OneEighty; + case TwoSeventy -> TwoSeventy; + }; + } + + @Nonnull + public static Vector3i rotate(@Nonnull Vector3i vector3i, @Nonnull Rotation rotationYaw, @Nonnull Rotation rotationPitch) { + Vector3i rotated = vector3i.clone(); + rotationPitch.rotatePitch(rotated, rotated); + rotationYaw.rotateYaw(rotated, rotated); + return rotated; + } + + @Nonnull + public static Vector3i rotate(@Nonnull Vector3i vector3i, @Nonnull Rotation rotationYaw, @Nonnull Rotation rotationPitch, @Nonnull Rotation rotationRoll) { + Vector3i rotated = rotate(vector3i, rotationYaw, rotationPitch); + rotationRoll.rotateRoll(rotated, rotated); + return rotated; + } + + @Nonnull + public static Vector3f rotate(@Nonnull Vector3f vector3f, @Nonnull Rotation rotationYaw, @Nonnull Rotation rotationPitch, @Nonnull Rotation rotationRoll) { + Vector3f rotated = vector3f.clone(); + rotationPitch.rotatePitch(rotated, rotated); + rotationYaw.rotateYaw(rotated, rotated); + rotationRoll.rotateRoll(rotated, rotated); + return rotated; + } + + @Nonnull + public static Vector3d rotate(@Nonnull Vector3d vector3d, @Nonnull Rotation rotationYaw, @Nonnull Rotation rotationPitch, @Nonnull Rotation rotationRoll) { + Vector3d rotated = vector3d.clone(); + rotationPitch.rotateX(rotated, rotated); + rotationYaw.rotateY(rotated, rotated); + rotationRoll.rotateZ(rotated, rotated); + return rotated; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/RotationTuple.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/RotationTuple.java new file mode 100644 index 0000000..c116da5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/RotationTuple.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public record RotationTuple(int index, Rotation yaw, Rotation pitch, Rotation roll) { + public static final RotationTuple[] EMPTY_ARRAY = new RotationTuple[0]; + public static final RotationTuple NONE = new RotationTuple(0, Rotation.None, Rotation.None, Rotation.None); + public static final int NONE_INDEX = 0; + @Nonnull + public static final RotationTuple[] VALUES; + + public static RotationTuple of(@Nonnull Rotation yaw, @Nonnull Rotation pitch, @Nonnull Rotation roll) { + return VALUES[index(yaw, pitch, roll)]; + } + + public static RotationTuple of(@Nonnull Rotation yaw, @Nonnull Rotation pitch) { + return VALUES[index(yaw, pitch, Rotation.None)]; + } + + public static int index(@Nonnull Rotation yaw, @Nonnull Rotation pitch, @Nonnull Rotation roll) { + return roll.ordinal() * Rotation.VALUES.length * Rotation.VALUES.length + pitch.ordinal() * Rotation.VALUES.length + yaw.ordinal(); + } + + public static RotationTuple get(int index) { + return VALUES[index]; + } + + public static RotationTuple getRotation(@Nonnull RotationTuple[] rotations, @Nonnull RotationTuple pair, @Nonnull Rotation rotation) { + int index = 0; + + for (int i = 0; i < rotations.length; i++) { + RotationTuple rotationPair = rotations[i]; + if (pair.equals(rotationPair)) { + index = i; + break; + } + } + + return rotations[(index + rotation.ordinal()) % Rotation.VALUES.length]; + } + + @Nonnull + public Vector3d rotate(@Nonnull Vector3d vector) { + return Rotation.rotate(vector, this.yaw, this.pitch, this.roll); + } + + static { + RotationTuple[] arr = new RotationTuple[Rotation.VALUES.length * Rotation.VALUES.length * Rotation.VALUES.length]; + arr[0] = NONE; + + for (Rotation roll : Rotation.VALUES) { + for (Rotation pitch : Rotation.VALUES) { + for (Rotation yaw : Rotation.VALUES) { + if (yaw != Rotation.None || pitch != Rotation.None || roll != Rotation.None) { + int index = index(yaw, pitch, roll); + arr[index] = new RotationTuple(index, yaw, pitch, roll); + } + } + } + } + + VALUES = arr; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/SoftBlockDropType.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/SoftBlockDropType.java new file mode 100644 index 0000000..392b174 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/SoftBlockDropType.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.SoftBlock; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class SoftBlockDropType implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(SoftBlockDropType.class, SoftBlockDropType::new) + .append(new KeyedCodec<>("ItemId", Codec.STRING), (softBlock, o) -> softBlock.itemId = o, softBlock -> softBlock.itemId) + .addValidatorLate(() -> Item.VALIDATOR_CACHE.getValidator().late()) + .add() + .append( + new KeyedCodec<>("DropList", new ContainedAssetCodec<>(ItemDropList.class, ItemDropList.CODEC)), + (softBlock, o) -> softBlock.dropListId = o, + softBlock -> softBlock.dropListId + ) + .addValidatorLate(() -> ItemDropList.VALIDATOR_CACHE.getValidator().late()) + .add() + .addField( + new KeyedCodec<>("IsWeaponBreakable", Codec.BOOLEAN), + (blockGathering, o) -> blockGathering.isWeaponBreakable = o, + blockGathering -> blockGathering.isWeaponBreakable + ) + .build(); + protected String itemId; + protected String dropListId; + protected boolean isWeaponBreakable = true; + + public SoftBlockDropType(String itemId, String dropListId, boolean isWeaponBreakable) { + this.itemId = itemId; + this.dropListId = dropListId; + this.isWeaponBreakable = isWeaponBreakable; + } + + protected SoftBlockDropType() { + } + + @Nonnull + public SoftBlock toPacket() { + SoftBlock packet = new SoftBlock(); + if (this.itemId != null) { + packet.itemId = this.itemId.toString(); + } + + packet.dropListId = this.dropListId; + packet.isWeaponBreakable = this.isWeaponBreakable; + return packet; + } + + public String getItemId() { + return this.itemId; + } + + public String getDropListId() { + return this.dropListId; + } + + public boolean isWeaponBreakable() { + return this.isWeaponBreakable; + } + + @Nonnull + public SoftBlockDropType withoutDrops() { + return new SoftBlockDropType(null, null, this.isWeaponBreakable); + } + + @Nonnull + @Override + public String toString() { + return "SoftBlockDropType{itemId=" + this.itemId + ", dropListId='" + this.dropListId + "', isWeaponBreakable='" + this.isWeaponBreakable + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/StateData.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/StateData.java new file mode 100644 index 0000000..f3fc00f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/StateData.java @@ -0,0 +1,115 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.codec.lookup.Priority; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StateData { + public static final String NULL_STATE_ID = "default"; + public static final BuilderCodec.Builder DEFAULT_CODEC_BUILDER = BuilderCodec.builder(StateData.class, StateData::new) + .appendInherited(new KeyedCodec<>("Id", Codec.STRING), (stateData, s) -> stateData.id = s, stateData -> stateData.id, (o, p) -> o.id = p.id) + .add() + .afterDecode((stateData, extraInfo) -> { + if (stateData.stateToBlock != null) { + Map map = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : stateData.stateToBlock.entrySet()) { + map.put(entry.getValue(), entry.getKey()); + } + + stateData.blockToState = Collections.unmodifiableMap(map); + } + }); + public static final BuilderCodec DEFAULT_CODEC = DEFAULT_CODEC_BUILDER.build(); + public static final CodecMapCodec CODEC = new CodecMapCodec(true) + .register(Priority.DEFAULT, "StateData", StateData.class, DEFAULT_CODEC); + private String id; + private Map stateToBlock; + private Map blockToState; + + protected StateData() { + } + + public StateData(String id) { + this.id = id; + } + + @Nullable + public String getId() { + return this.id; + } + + @Nullable + public String getBlockForState(String state) { + return this.stateToBlock == null ? null : this.stateToBlock.get(state); + } + + @Nullable + public String getStateForBlock(String blockTypeKey) { + return this.blockToState == null ? null : this.blockToState.get(blockTypeKey); + } + + @Nullable + public Map toPacket(@Nonnull BlockType current) { + if (this.stateToBlock == null) { + return null; + } else { + Map data = new Object2IntOpenHashMap<>(); + + for (String state : this.stateToBlock.keySet()) { + String key = current.getBlockKeyForState(state); + int index = BlockType.getAssetMap().getIndex(key); + if (index != Integer.MIN_VALUE) { + data.put(state, index); + } + } + + return data; + } + } + + @Nonnull + @Override + public String toString() { + return "StateData{id='" + this.id + "', stateToBlock='" + this.stateToBlock + "'}"; + } + + public void copyFrom(@Nullable StateData state) { + if (state != null && this.stateToBlock == null) { + this.stateToBlock = state.stateToBlock; + this.blockToState = state.blockToState; + } + } + + static void addDefinitions() { + DEFAULT_CODEC_BUILDER.addField( + new KeyedCodec<>( + "Definitions", + new MapCodec( + new ContainedAssetCodec<>(BlockType.class, BlockType.CODEC, ContainedAssetCodec.Mode.INJECT_PARENT, StateData::generateBlockKey), HashMap::new + ) + ), + (stateData, m) -> stateData.stateToBlock = m, + stateData -> stateData.stateToBlock + ); + } + + @Nonnull + private static String generateBlockKey(@Nonnull AssetExtraInfo extraInfo) { + String key = extraInfo.getKey(); + return "*" + key + "_" + extraInfo.peekKey('_'); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/SupportDropType.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/SupportDropType.java new file mode 100644 index 0000000..87576cc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/SupportDropType.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.codec.codecs.EnumCodec; + +public enum SupportDropType { + BREAK, + DESTROY; + + public static final EnumCodec CODEC = new EnumCodec<>(SupportDropType.class); + + private SupportDropType() { + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/VariantRotation.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/VariantRotation.java new file mode 100644 index 0000000..89f69b8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/VariantRotation.java @@ -0,0 +1,264 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config; + +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.function.BiFunction; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public enum VariantRotation implements NetworkSerializable { + None( + com.hypixel.hytale.protocol.VariantRotation.None, + RotationTuple.EMPTY_ARRAY, + pair -> RotationTuple.NONE, + (pair, rotation) -> RotationTuple.NONE, + (pair, rotation) -> RotationTuple.NONE + ), + Wall( + com.hypixel.hytale.protocol.VariantRotation.Wall, + new RotationTuple[]{RotationTuple.of(Rotation.Ninety, Rotation.None)}, + pair -> pair.yaw() != Rotation.Ninety && pair.yaw() != Rotation.TwoSeventy + ? RotationTuple.of(Rotation.None, Rotation.None) + : RotationTuple.of(Rotation.Ninety, Rotation.None), + (pair, rotation) -> pair, + (pair, rotation) -> pair + ), + UpDown( + com.hypixel.hytale.protocol.VariantRotation.UpDown, + new RotationTuple[]{RotationTuple.of(Rotation.None, Rotation.OneEighty)}, + pair -> pair.pitch() == Rotation.OneEighty ? RotationTuple.of(Rotation.None, Rotation.OneEighty) : RotationTuple.of(Rotation.None, Rotation.None), + (pair, rotation) -> RotationTuple.of(pair.yaw(), pair.pitch().add(rotation)), + (pair, rotation) -> pair.pitch().add(rotation) == Rotation.OneEighty + ? RotationTuple.of(pair.yaw(), Rotation.OneEighty) + : RotationTuple.of(pair.yaw(), Rotation.None) + ), + Pipe( + com.hypixel.hytale.protocol.VariantRotation.Pipe, + new RotationTuple[]{RotationTuple.of(Rotation.None, Rotation.Ninety), RotationTuple.of(Rotation.Ninety, Rotation.Ninety)}, + pair -> pair.pitch() != Rotation.Ninety && pair.pitch() != Rotation.TwoSeventy + ? RotationTuple.of(Rotation.None, validatePipe(pair.pitch())) + : RotationTuple.of(validatePipe(pair.yaw()), validatePipe(pair.pitch())), + (pair, rotation) -> RotationTuple.of(pair.yaw(), pair.pitch().add(rotation)), + (pair, rotation) -> { + if (pair.yaw() == Rotation.None && pair.pitch() == Rotation.Ninety) { + return pair; + } else { + return switch (pair.yaw().add(rotation)) { + case None, OneEighty -> RotationTuple.of(Rotation.None, Rotation.None); + case Ninety, TwoSeventy -> RotationTuple.of(Rotation.Ninety, Rotation.Ninety); + }; + } + } + ), + DoublePipe( + com.hypixel.hytale.protocol.VariantRotation.DoublePipe, + new RotationTuple[]{ + RotationTuple.of(Rotation.None, Rotation.Ninety), + RotationTuple.of(Rotation.Ninety, Rotation.Ninety), + RotationTuple.of(Rotation.OneEighty, Rotation.Ninety), + RotationTuple.of(Rotation.TwoSeventy, Rotation.Ninety), + RotationTuple.of(Rotation.None, Rotation.OneEighty) + }, + pair -> { + return switch (pair.pitch()) { + case OneEighty -> RotationTuple.of(Rotation.None, Rotation.OneEighty); + case Ninety -> pair; + case TwoSeventy -> RotationTuple.of(pair.yaw().flip(), Rotation.Ninety); + default -> RotationTuple.NONE; + }; + }, + (pair, rotation) -> (pair.yaw() == Rotation.Ninety || pair.yaw() == Rotation.TwoSeventy) && pair.pitch() == Rotation.Ninety + ? pair + : RotationTuple.getRotation( + new RotationTuple[]{ + RotationTuple.NONE, + RotationTuple.of(Rotation.None, Rotation.Ninety), + RotationTuple.of(Rotation.None, Rotation.OneEighty), + RotationTuple.of(Rotation.OneEighty, Rotation.Ninety) + }, + pair, + rotation + ), + (pair, rotation) -> pair.yaw() != Rotation.None || pair.pitch() != Rotation.Ninety && pair.pitch() != Rotation.TwoSeventy + ? RotationTuple.getRotation( + new RotationTuple[]{ + RotationTuple.NONE, + RotationTuple.of(Rotation.Ninety, Rotation.Ninety), + RotationTuple.of(Rotation.None, Rotation.OneEighty), + RotationTuple.of(Rotation.TwoSeventy, Rotation.Ninety) + }, + pair, + rotation + ) + : pair + ), + NESW( + com.hypixel.hytale.protocol.VariantRotation.NESW, + new RotationTuple[]{ + RotationTuple.of(Rotation.Ninety, Rotation.None), + RotationTuple.of(Rotation.OneEighty, Rotation.None), + RotationTuple.of(Rotation.TwoSeventy, Rotation.None) + }, + pair -> RotationTuple.of(pair.yaw(), Rotation.None), + (pair, rotation) -> pair, + (pair, rotation) -> pair + ), + UpDownNESW( + com.hypixel.hytale.protocol.VariantRotation.UpDownNESW, + new RotationTuple[]{ + RotationTuple.of(Rotation.Ninety, Rotation.None), + RotationTuple.of(Rotation.OneEighty, Rotation.None), + RotationTuple.of(Rotation.TwoSeventy, Rotation.None), + RotationTuple.of(Rotation.None, Rotation.OneEighty), + RotationTuple.of(Rotation.Ninety, Rotation.OneEighty), + RotationTuple.of(Rotation.OneEighty, Rotation.OneEighty), + RotationTuple.of(Rotation.TwoSeventy, Rotation.OneEighty) + }, + pair -> pair.pitch() == Rotation.OneEighty ? RotationTuple.of(pair.yaw(), Rotation.OneEighty) : RotationTuple.of(pair.yaw(), Rotation.None), + (pair, rotation) -> RotationTuple.of(pair.yaw(), pair.pitch().add(rotation)), + (pair, rotation) -> pair.pitch().add(rotation) == Rotation.OneEighty ? RotationTuple.of(pair.yaw(), Rotation.OneEighty) : pair + ), + Debug( + com.hypixel.hytale.protocol.VariantRotation.UpDownNESW, + new RotationTuple[]{ + RotationTuple.of(Rotation.Ninety, Rotation.None), + RotationTuple.of(Rotation.OneEighty, Rotation.None), + RotationTuple.of(Rotation.TwoSeventy, Rotation.None), + RotationTuple.of(Rotation.None, Rotation.Ninety), + RotationTuple.of(Rotation.Ninety, Rotation.Ninety), + RotationTuple.of(Rotation.OneEighty, Rotation.Ninety), + RotationTuple.of(Rotation.TwoSeventy, Rotation.Ninety), + RotationTuple.of(Rotation.None, Rotation.OneEighty), + RotationTuple.of(Rotation.Ninety, Rotation.OneEighty), + RotationTuple.of(Rotation.OneEighty, Rotation.OneEighty), + RotationTuple.of(Rotation.TwoSeventy, Rotation.OneEighty), + RotationTuple.of(Rotation.None, Rotation.TwoSeventy), + RotationTuple.of(Rotation.Ninety, Rotation.TwoSeventy), + RotationTuple.of(Rotation.OneEighty, Rotation.TwoSeventy), + RotationTuple.of(Rotation.TwoSeventy, Rotation.TwoSeventy) + }, + Function.identity(), + (pair, rotation) -> RotationTuple.of(pair.yaw(), pair.pitch().add(rotation)), + (pair, rotation) -> pair + ), + All( + com.hypixel.hytale.protocol.VariantRotation.All, + new RotationTuple[]{ + RotationTuple.of(Rotation.None, Rotation.None, Rotation.Ninety), + RotationTuple.of(Rotation.None, Rotation.None, Rotation.OneEighty), + RotationTuple.of(Rotation.None, Rotation.None, Rotation.TwoSeventy), + RotationTuple.of(Rotation.None, Rotation.Ninety, Rotation.None), + RotationTuple.of(Rotation.None, Rotation.Ninety, Rotation.Ninety), + RotationTuple.of(Rotation.None, Rotation.Ninety, Rotation.OneEighty), + RotationTuple.of(Rotation.None, Rotation.Ninety, Rotation.TwoSeventy), + RotationTuple.of(Rotation.None, Rotation.OneEighty, Rotation.None), + RotationTuple.of(Rotation.None, Rotation.OneEighty, Rotation.Ninety), + RotationTuple.of(Rotation.None, Rotation.OneEighty, Rotation.OneEighty), + RotationTuple.of(Rotation.None, Rotation.OneEighty, Rotation.TwoSeventy), + RotationTuple.of(Rotation.None, Rotation.TwoSeventy, Rotation.None), + RotationTuple.of(Rotation.None, Rotation.TwoSeventy, Rotation.Ninety), + RotationTuple.of(Rotation.None, Rotation.TwoSeventy, Rotation.OneEighty), + RotationTuple.of(Rotation.None, Rotation.TwoSeventy, Rotation.TwoSeventy), + RotationTuple.of(Rotation.Ninety, Rotation.None, Rotation.None), + RotationTuple.of(Rotation.Ninety, Rotation.None, Rotation.Ninety), + RotationTuple.of(Rotation.Ninety, Rotation.None, Rotation.OneEighty), + RotationTuple.of(Rotation.Ninety, Rotation.None, Rotation.TwoSeventy), + RotationTuple.of(Rotation.Ninety, Rotation.Ninety, Rotation.None), + RotationTuple.of(Rotation.Ninety, Rotation.Ninety, Rotation.Ninety), + RotationTuple.of(Rotation.Ninety, Rotation.Ninety, Rotation.OneEighty), + RotationTuple.of(Rotation.Ninety, Rotation.Ninety, Rotation.TwoSeventy), + RotationTuple.of(Rotation.Ninety, Rotation.OneEighty, Rotation.None), + RotationTuple.of(Rotation.Ninety, Rotation.OneEighty, Rotation.Ninety), + RotationTuple.of(Rotation.Ninety, Rotation.OneEighty, Rotation.OneEighty), + RotationTuple.of(Rotation.Ninety, Rotation.OneEighty, Rotation.TwoSeventy), + RotationTuple.of(Rotation.Ninety, Rotation.TwoSeventy, Rotation.None), + RotationTuple.of(Rotation.Ninety, Rotation.TwoSeventy, Rotation.Ninety), + RotationTuple.of(Rotation.Ninety, Rotation.TwoSeventy, Rotation.OneEighty), + RotationTuple.of(Rotation.Ninety, Rotation.TwoSeventy, Rotation.TwoSeventy), + RotationTuple.of(Rotation.OneEighty, Rotation.None, Rotation.None), + RotationTuple.of(Rotation.OneEighty, Rotation.None, Rotation.Ninety), + RotationTuple.of(Rotation.OneEighty, Rotation.None, Rotation.OneEighty), + RotationTuple.of(Rotation.OneEighty, Rotation.None, Rotation.TwoSeventy), + RotationTuple.of(Rotation.OneEighty, Rotation.Ninety, Rotation.None), + RotationTuple.of(Rotation.OneEighty, Rotation.Ninety, Rotation.Ninety), + RotationTuple.of(Rotation.OneEighty, Rotation.Ninety, Rotation.OneEighty), + RotationTuple.of(Rotation.OneEighty, Rotation.Ninety, Rotation.TwoSeventy), + RotationTuple.of(Rotation.OneEighty, Rotation.OneEighty, Rotation.None), + RotationTuple.of(Rotation.OneEighty, Rotation.OneEighty, Rotation.Ninety), + RotationTuple.of(Rotation.OneEighty, Rotation.OneEighty, Rotation.OneEighty), + RotationTuple.of(Rotation.OneEighty, Rotation.OneEighty, Rotation.TwoSeventy), + RotationTuple.of(Rotation.OneEighty, Rotation.TwoSeventy, Rotation.None), + RotationTuple.of(Rotation.OneEighty, Rotation.TwoSeventy, Rotation.Ninety), + RotationTuple.of(Rotation.OneEighty, Rotation.TwoSeventy, Rotation.OneEighty), + RotationTuple.of(Rotation.OneEighty, Rotation.TwoSeventy, Rotation.TwoSeventy), + RotationTuple.of(Rotation.TwoSeventy, Rotation.None, Rotation.None), + RotationTuple.of(Rotation.TwoSeventy, Rotation.None, Rotation.Ninety), + RotationTuple.of(Rotation.TwoSeventy, Rotation.None, Rotation.OneEighty), + RotationTuple.of(Rotation.TwoSeventy, Rotation.None, Rotation.TwoSeventy), + RotationTuple.of(Rotation.TwoSeventy, Rotation.Ninety, Rotation.None), + RotationTuple.of(Rotation.TwoSeventy, Rotation.Ninety, Rotation.Ninety), + RotationTuple.of(Rotation.TwoSeventy, Rotation.Ninety, Rotation.OneEighty), + RotationTuple.of(Rotation.TwoSeventy, Rotation.Ninety, Rotation.TwoSeventy), + RotationTuple.of(Rotation.TwoSeventy, Rotation.OneEighty, Rotation.None), + RotationTuple.of(Rotation.TwoSeventy, Rotation.OneEighty, Rotation.Ninety), + RotationTuple.of(Rotation.TwoSeventy, Rotation.OneEighty, Rotation.OneEighty), + RotationTuple.of(Rotation.TwoSeventy, Rotation.OneEighty, Rotation.TwoSeventy), + RotationTuple.of(Rotation.TwoSeventy, Rotation.TwoSeventy, Rotation.None), + RotationTuple.of(Rotation.TwoSeventy, Rotation.TwoSeventy, Rotation.Ninety), + RotationTuple.of(Rotation.TwoSeventy, Rotation.TwoSeventy, Rotation.OneEighty), + RotationTuple.of(Rotation.TwoSeventy, Rotation.TwoSeventy, Rotation.TwoSeventy) + }, + Function.identity(), + (pair, rotation) -> RotationTuple.of(pair.yaw(), pair.pitch().add(rotation)), + (pair, rotation) -> pair + ); + + public static final VariantRotation[] EMPTY_ARRAY = new VariantRotation[0]; + private final com.hypixel.hytale.protocol.VariantRotation protocolType; + private final RotationTuple[] rotations; + private final Function verify; + private final BiFunction rotateX; + private final BiFunction rotateZ; + + @Nonnull + private static Rotation validatePipe(@Nonnull Rotation yaw) { + return switch (yaw) { + case None, Ninety -> yaw; + case OneEighty -> Rotation.None; + case TwoSeventy -> Rotation.Ninety; + }; + } + + private VariantRotation( + com.hypixel.hytale.protocol.VariantRotation protocolType, + RotationTuple[] rotations, + Function verify, + BiFunction rotateX, + BiFunction rotateZ + ) { + this.protocolType = protocolType; + this.rotations = rotations; + this.verify = verify; + this.rotateX = rotateX; + this.rotateZ = rotateZ; + } + + public RotationTuple[] getRotations() { + return this.rotations; + } + + public RotationTuple rotateX(RotationTuple pair, Rotation rotation) { + return this.rotateX.apply(pair, rotation); + } + + public RotationTuple rotateZ(RotationTuple pair, Rotation rotation) { + return this.rotateZ.apply(pair, rotation); + } + + public RotationTuple verify(RotationTuple pair) { + return this.verify.apply(pair); + } + + public com.hypixel.hytale.protocol.VariantRotation toPacket() { + return this.protocolType; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/Bench.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/Bench.java new file mode 100644 index 0000000..1db0180 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/Bench.java @@ -0,0 +1,346 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config.bench; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.lookup.ObjectCodecMapCodec; +import com.hypixel.hytale.protocol.BenchType; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.validator.SoundEventValidators; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.Map; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class Bench implements NetworkSerializable { + public static final ObjectCodecMapCodec CODEC = new ObjectCodecMapCodec<>("Type", new EnumCodec<>(BenchType.class)); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(Bench.class) + .addField(new KeyedCodec<>("Id", Codec.STRING), (bench, s) -> bench.id = s, bench -> bench.id) + .addField(new KeyedCodec<>("DescriptiveLabel", Codec.STRING), (bench, s) -> bench.descriptiveLabel = s, bench -> bench.descriptiveLabel) + .appendInherited( + new KeyedCodec<>("TierLevels", new ArrayCodec<>(BenchTierLevel.CODEC, BenchTierLevel[]::new)), + (bench, u) -> bench.tierLevels = u, + bench -> bench.tierLevels, + (bench, parent) -> bench.tierLevels = parent.tierLevels + ) + .add() + .append( + new KeyedCodec<>("LocalOpenSoundEventId", Codec.STRING), (bench, s) -> bench.localOpenSoundEventId = s, bench -> bench.localOpenSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.ONESHOT) + .add() + .append( + new KeyedCodec<>("LocalCloseSoundEventId", Codec.STRING), (bench, s) -> bench.localCloseSoundEventId = s, bench -> bench.localCloseSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.ONESHOT) + .add() + .append( + new KeyedCodec<>("CompletedSoundEventId", Codec.STRING), (bench, s) -> bench.completedSoundEventId = s, bench -> bench.completedSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .addValidator(SoundEventValidators.ONESHOT) + .add() + .append(new KeyedCodec<>("FailedSoundEventId", Codec.STRING), (bench, s) -> bench.failedSoundEventId = s, bench -> bench.failedSoundEventId) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .addValidator(SoundEventValidators.ONESHOT) + .add() + .append( + new KeyedCodec<>("BenchUpgradeSoundEventId", Codec.STRING), (bench, s) -> bench.benchUpgradeSoundEventId = s, bench -> bench.benchUpgradeSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .addValidator(SoundEventValidators.ONESHOT) + .add() + .append( + new KeyedCodec<>("BenchUpgradeCompletedSoundEventId", Codec.STRING), + (bench, s) -> bench.benchUpgradeCompletedSoundEventId = s, + bench -> bench.benchUpgradeCompletedSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .addValidator(SoundEventValidators.ONESHOT) + .add() + .afterDecode(bench -> { + bench.type = CODEC.getIdFor((Class)bench.getClass()); + if (bench.localOpenSoundEventId != null) { + bench.localOpenSoundEventIndex = SoundEvent.getAssetMap().getIndex(bench.localOpenSoundEventId); + } + + if (bench.localCloseSoundEventId != null) { + bench.localCloseSoundEventIndex = SoundEvent.getAssetMap().getIndex(bench.localCloseSoundEventId); + } + + if (bench.completedSoundEventId != null) { + bench.completedSoundEventIndex = SoundEvent.getAssetMap().getIndex(bench.completedSoundEventId); + } + + if (bench.benchUpgradeSoundEventId != null) { + bench.benchUpgradeSoundEventIndex = SoundEvent.getAssetMap().getIndex(bench.benchUpgradeSoundEventId); + } + + if (bench.benchUpgradeCompletedSoundEventId != null) { + bench.benchUpgradeCompletedSoundEventIndex = SoundEvent.getAssetMap().getIndex(bench.benchUpgradeCompletedSoundEventId); + } + + if (bench.failedSoundEventId != null) { + bench.failedSoundEventIndex = SoundEvent.getAssetMap().getIndex(bench.failedSoundEventId); + } + }) + .build(); + @Deprecated(forRemoval = true) + protected static final Map BENCH_INTERACTIONS = new EnumMap<>(BenchType.class); + @Nonnull + protected BenchType type = BenchType.Crafting; + protected String id; + protected String descriptiveLabel; + protected BenchTierLevel[] tierLevels; + @Nullable + protected String localOpenSoundEventId = null; + protected transient int localOpenSoundEventIndex = 0; + @Nullable + protected String localCloseSoundEventId = null; + protected transient int localCloseSoundEventIndex = 0; + @Nullable + protected String completedSoundEventId = null; + protected transient int completedSoundEventIndex = 0; + @Nullable + protected String failedSoundEventId = null; + protected transient int failedSoundEventIndex = 0; + @Nullable + protected String benchUpgradeSoundEventId = null; + protected transient int benchUpgradeSoundEventIndex = 0; + @Nullable + protected String benchUpgradeCompletedSoundEventId = null; + protected transient int benchUpgradeCompletedSoundEventIndex = 0; + + public Bench() { + } + + public BenchType getType() { + return this.type; + } + + public String getId() { + return this.id; + } + + public String getDescriptiveLabel() { + return this.descriptiveLabel; + } + + public BenchTierLevel getTierLevel(int tierLevel) { + return this.tierLevels != null && tierLevel >= 1 && tierLevel <= this.tierLevels.length ? this.tierLevels[tierLevel - 1] : null; + } + + public BenchUpgradeRequirement getUpgradeRequirement(int tierLevel) { + BenchTierLevel currentTierLevel = this.getTierLevel(tierLevel); + return currentTierLevel == null ? null : currentTierLevel.upgradeRequirement; + } + + @Nullable + public String getLocalOpenSoundEventId() { + return this.localOpenSoundEventId; + } + + public int getLocalOpenSoundEventIndex() { + return this.localOpenSoundEventIndex; + } + + @Nullable + public String getLocalCloseSoundEventId() { + return this.localCloseSoundEventId; + } + + public int getLocalCloseSoundEventIndex() { + return this.localCloseSoundEventIndex; + } + + @Nullable + public String getCompletedSoundEventId() { + return this.completedSoundEventId; + } + + public int getCompletedSoundEventIndex() { + return this.completedSoundEventIndex; + } + + @Nullable + public String getFailedSoundEventId() { + return this.failedSoundEventId; + } + + public int getFailedSoundEventIndex() { + return this.failedSoundEventIndex; + } + + @Nullable + public String getBenchUpgradeSoundEventId() { + return this.benchUpgradeSoundEventId; + } + + public int getBenchUpgradeSoundEventIndex() { + return this.benchUpgradeSoundEventIndex; + } + + @Nullable + public String getBenchUpgradeCompletedSoundEventId() { + return this.benchUpgradeCompletedSoundEventId; + } + + public int getBenchUpgradeCompletedSoundEventIndex() { + return this.benchUpgradeCompletedSoundEventIndex; + } + + @Nullable + public RootInteraction getRootInteraction() { + return BENCH_INTERACTIONS.get(this.type); + } + + public com.hypixel.hytale.protocol.Bench toPacket() { + com.hypixel.hytale.protocol.Bench packet = new com.hypixel.hytale.protocol.Bench(); + if (this.tierLevels != null && this.tierLevels.length > 0) { + packet.benchTierLevels = new com.hypixel.hytale.protocol.BenchTierLevel[this.tierLevels.length]; + + for (int i = 0; i < this.tierLevels.length; i++) { + packet.benchTierLevels[i] = this.tierLevels[i].toPacket(); + } + } + + return packet; + } + + @Override + public boolean equals(Object o) { + if (o != null && this.getClass() == o.getClass()) { + Bench bench = (Bench)o; + return this.localOpenSoundEventIndex == bench.localOpenSoundEventIndex + && this.localCloseSoundEventIndex == bench.localCloseSoundEventIndex + && this.completedSoundEventIndex == bench.completedSoundEventIndex + && this.benchUpgradeSoundEventIndex == bench.benchUpgradeSoundEventIndex + && this.benchUpgradeCompletedSoundEventIndex == bench.benchUpgradeCompletedSoundEventIndex + && this.type == bench.type + && Objects.equals(this.id, bench.id) + && Objects.equals(this.descriptiveLabel, bench.descriptiveLabel) + && Objects.deepEquals(this.tierLevels, bench.tierLevels) + && Objects.equals(this.localOpenSoundEventId, bench.localOpenSoundEventId) + && Objects.equals(this.localCloseSoundEventId, bench.localCloseSoundEventId) + && Objects.equals(this.completedSoundEventId, bench.completedSoundEventId) + && Objects.equals(this.failedSoundEventId, bench.failedSoundEventId) + && Objects.equals(this.benchUpgradeSoundEventId, bench.benchUpgradeSoundEventId) + && Objects.equals(this.benchUpgradeCompletedSoundEventId, bench.benchUpgradeCompletedSoundEventId); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.type, + this.id, + this.descriptiveLabel, + Arrays.hashCode((Object[])this.tierLevels), + this.localOpenSoundEventId, + this.localOpenSoundEventIndex, + this.localCloseSoundEventId, + this.localCloseSoundEventIndex, + this.completedSoundEventId, + this.completedSoundEventIndex, + this.failedSoundEventId, + this.failedSoundEventIndex, + this.benchUpgradeSoundEventId, + this.benchUpgradeSoundEventIndex, + this.benchUpgradeCompletedSoundEventId, + this.benchUpgradeCompletedSoundEventIndex + ); + } + + @Override + public String toString() { + return "Bench{type=" + + this.type + + ", id='" + + this.id + + "', descriptiveLabel='" + + this.descriptiveLabel + + "', tierLevels=" + + Arrays.toString((Object[])this.tierLevels) + + ", localOpenSoundEventId='" + + this.localOpenSoundEventId + + "', localOpenSoundEventIndex=" + + this.localOpenSoundEventIndex + + ", localCloseSoundEventId='" + + this.localCloseSoundEventId + + "', localCloseSoundEventIndex=" + + this.localCloseSoundEventIndex + + ", completedSoundEventId='" + + this.completedSoundEventId + + "', completedSoundEventIndex=" + + this.completedSoundEventIndex + + ", failedSoundEventId='" + + this.failedSoundEventId + + "', failedSoundEventIndex=" + + this.failedSoundEventIndex + + ", benchUpgradeSoundEventId='" + + this.benchUpgradeSoundEventId + + "', benchUpgradeSoundEventIndex=" + + this.benchUpgradeSoundEventIndex + + ", benchUpgradeCompletedSoundEventId='" + + this.benchUpgradeCompletedSoundEventId + + "', benchUpgradeCompletedSoundEventIndex=" + + this.benchUpgradeCompletedSoundEventIndex + + "}"; + } + + @Deprecated(forRemoval = true) + public static void registerRootInteraction(BenchType benchType, RootInteraction interaction) { + BENCH_INTERACTIONS.put(benchType, interaction); + } + + public static class BenchSlot { + public static final BuilderCodec CODEC = BuilderCodec.builder(Bench.BenchSlot.class, Bench.BenchSlot::new) + .addField(new KeyedCodec<>("Icon", Codec.STRING), (benchSlot, s) -> benchSlot.icon = s, benchSlot -> benchSlot.icon) + .build(); + protected String icon; + + protected BenchSlot() { + } + + public String getIcon() { + return this.icon; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Bench.BenchSlot benchSlot = (Bench.BenchSlot)o; + return this.icon != null ? this.icon.equals(benchSlot.icon) : benchSlot.icon == null; + } else { + return false; + } + } + + @Override + public int hashCode() { + return this.icon != null ? this.icon.hashCode() : 0; + } + + @Nonnull + @Override + public String toString() { + return "BenchSlot{icon='" + this.icon + "'}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/BenchTierLevel.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/BenchTierLevel.java new file mode 100644 index 0000000..ec93156 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/BenchTierLevel.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config.bench; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.io.NetworkSerializable; + +public class BenchTierLevel implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(BenchTierLevel.class, BenchTierLevel::new) + .append(new KeyedCodec<>("UpgradeRequirement", BenchUpgradeRequirement.CODEC), (tier, d) -> tier.upgradeRequirement = d, tier -> tier.upgradeRequirement) + .add() + .append( + new KeyedCodec<>("CraftingTimeReductionModifier", Codec.DOUBLE), + (tier, d) -> tier.craftingTimeReductionModifier = d.floatValue(), + tier -> (double)tier.craftingTimeReductionModifier + ) + .addValidator(Validators.range(0.0, 1.0)) + .add() + .append(new KeyedCodec<>("ExtraInputSlot", Codec.INTEGER), (tier, d) -> tier.extraInputSlot = d, tier -> tier.extraInputSlot) + .addValidator(Validators.min(0)) + .add() + .append(new KeyedCodec<>("ExtraOutputSlot", Codec.INTEGER), (tier, d) -> tier.extraOutputSlot = d, tier -> tier.extraOutputSlot) + .addValidator(Validators.min(0)) + .add() + .build(); + protected BenchUpgradeRequirement upgradeRequirement; + protected float craftingTimeReductionModifier; + protected int extraInputSlot; + protected int extraOutputSlot; + + public BenchTierLevel(BenchUpgradeRequirement upgradeRequirement, float craftingTimeReductionModifier, int extraInputSlot, int extraOutputSlot) { + this.upgradeRequirement = upgradeRequirement; + this.craftingTimeReductionModifier = craftingTimeReductionModifier; + this.extraInputSlot = extraInputSlot; + this.extraOutputSlot = extraOutputSlot; + } + + protected BenchTierLevel() { + } + + public float getCraftingTimeReductionModifier() { + return this.craftingTimeReductionModifier; + } + + public BenchUpgradeRequirement getUpgradeRequirement() { + return this.upgradeRequirement; + } + + public int getExtraInputSlot() { + return this.extraInputSlot; + } + + public int getExtraOutputSlot() { + return this.extraOutputSlot; + } + + public com.hypixel.hytale.protocol.BenchTierLevel toPacket() { + com.hypixel.hytale.protocol.BenchTierLevel packet = new com.hypixel.hytale.protocol.BenchTierLevel(); + if (this.upgradeRequirement != null) { + packet.benchUpgradeRequirement = this.upgradeRequirement.toPacket(); + } + + packet.craftingTimeReductionModifier = this.craftingTimeReductionModifier; + packet.extraInputSlot = this.extraInputSlot; + packet.extraOutputSlot = this.extraOutputSlot; + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/BenchUpgradeRequirement.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/BenchUpgradeRequirement.java new file mode 100644 index 0000000..408afc6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/BenchUpgradeRequirement.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config.bench; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.inventory.MaterialQuantity; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.Arrays; + +public class BenchUpgradeRequirement implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(BenchUpgradeRequirement.class, BenchUpgradeRequirement::new) + .append( + new KeyedCodec<>("Material", new ArrayCodec<>(MaterialQuantity.CODEC, MaterialQuantity[]::new)), + (benchUpgradeRequirement, objects) -> benchUpgradeRequirement.input = objects, + benchUpgradeRequirement -> benchUpgradeRequirement.input + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("TimeSeconds", Codec.DOUBLE), + (benchUpgradeRequirement, d) -> benchUpgradeRequirement.timeSeconds = d.floatValue(), + benchUpgradeRequirement -> (double)benchUpgradeRequirement.timeSeconds + ) + .addValidator(Validators.min(0.0)) + .add() + .build(); + protected MaterialQuantity[] input; + protected float timeSeconds; + + public BenchUpgradeRequirement(MaterialQuantity[] input, float timeSeconds) { + this.input = input; + this.timeSeconds = timeSeconds; + } + + protected BenchUpgradeRequirement() { + } + + public MaterialQuantity[] getInput() { + return this.input; + } + + public float getTimeSeconds() { + return this.timeSeconds; + } + + @Override + public String toString() { + return "BenchUpgradeRequirement{input=" + Arrays.toString((Object[])this.input) + ", timeSeconds=" + this.timeSeconds + "}"; + } + + public com.hypixel.hytale.protocol.BenchUpgradeRequirement toPacket() { + com.hypixel.hytale.protocol.BenchUpgradeRequirement packet = new com.hypixel.hytale.protocol.BenchUpgradeRequirement(); + if (this.input != null && this.input.length > 0) { + packet.material = new com.hypixel.hytale.protocol.MaterialQuantity[this.input.length]; + + for (int i = 0; i < this.input.length; i++) { + packet.material[i] = this.input[i].toPacket(); + } + + packet.timeSeconds = this.timeSeconds; + } + + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/CraftingBench.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/CraftingBench.java new file mode 100644 index 0000000..7d82120 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/CraftingBench.java @@ -0,0 +1,202 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config.bench; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CraftingBench extends Bench { + public static final BuilderCodec CODEC = BuilderCodec.builder(CraftingBench.class, CraftingBench::new, Bench.BASE_CODEC) + .append( + new KeyedCodec<>("Categories", new ArrayCodec<>(CraftingBench.BenchCategory.CODEC, CraftingBench.BenchCategory[]::new)), + (bench, s) -> bench.categories = s, + bench -> bench.categories + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + protected CraftingBench.BenchCategory[] categories; + + public CraftingBench() { + } + + public CraftingBench.BenchCategory[] getCategories() { + return this.categories; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o == null || this.getClass() != o.getClass()) { + return false; + } else if (!super.equals(o)) { + return false; + } else { + CraftingBench that = (CraftingBench)o; + return Arrays.equals((Object[])this.categories, (Object[])that.categories); + } + } + + @Override + public int hashCode() { + int result = super.hashCode(); + return 31 * result + Arrays.hashCode((Object[])this.categories); + } + + public static class BenchCategory { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CraftingBench.BenchCategory.class, CraftingBench.BenchCategory::new + ) + .addField(new KeyedCodec<>("Id", Codec.STRING), (benchCategory, s) -> benchCategory.id = s, benchCategory -> benchCategory.id) + .append(new KeyedCodec<>("Name", Codec.STRING), (benchCategory, s) -> benchCategory.name = s, benchCategory -> benchCategory.name) + .metadata(new UIEditor(new UIEditor.LocalizationKeyField("server.benchCategories.{id}"))) + .add() + .append(new KeyedCodec<>("Icon", Codec.STRING), (benchCategory, s) -> benchCategory.icon = s, benchCategory -> benchCategory.icon) + .addValidator(CommonAssetValidator.ICON_CRAFTING) + .add() + .addField( + new KeyedCodec<>("ItemCategories", new ArrayCodec<>(CraftingBench.BenchItemCategory.CODEC, CraftingBench.BenchItemCategory[]::new)), + (benchCategory, s) -> benchCategory.itemCategories = s, + benchCategory -> benchCategory.itemCategories + ) + .build(); + protected String id; + protected String name; + protected String icon; + protected CraftingBench.BenchItemCategory[] itemCategories; + + public BenchCategory(String id, String name, String icon, CraftingBench.BenchItemCategory[] itemCategories) { + this.id = id; + this.name = name; + this.icon = icon; + this.itemCategories = itemCategories; + } + + protected BenchCategory() { + } + + public String getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public String getIcon() { + return this.icon; + } + + public CraftingBench.BenchItemCategory[] getItemCategories() { + return this.itemCategories; + } + + @Nonnull + @Override + public String toString() { + return "BenchCategory{id='" + + this.id + + "', name='" + + this.name + + "', icon='" + + this.icon + + "', itemCategories='" + + Arrays.toString((Object[])this.itemCategories) + + "'}"; + } + } + + public static class BenchItemCategory { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CraftingBench.BenchItemCategory.class, CraftingBench.BenchItemCategory::new + ) + .addField(new KeyedCodec<>("Id", Codec.STRING), (benchItemCategory, s) -> benchItemCategory.id = s, benchItemCategory -> benchItemCategory.id) + .addField(new KeyedCodec<>("Name", Codec.STRING), (benchItemCategory, s) -> benchItemCategory.name = s, benchItemCategory -> benchItemCategory.name) + .append( + new KeyedCodec<>("Icon", Codec.STRING), (benchItemCategory, s) -> benchItemCategory.icon = s, benchItemCategory -> benchItemCategory.icon + ) + .addValidator(CommonAssetValidator.ICON_CRAFTING) + .add() + .append( + new KeyedCodec<>("Diagram", Codec.STRING), (benchItemCategory, s) -> benchItemCategory.diagram = s, benchItemCategory -> benchItemCategory.diagram + ) + .addValidator(CommonAssetValidator.UI_CRAFTING_DIAGRAM) + .add() + .addField( + new KeyedCodec<>("Slots", Codec.INTEGER), (benchItemCategory, s) -> benchItemCategory.slots = s, benchItemCategory -> benchItemCategory.slots + ) + .addField( + new KeyedCodec<>("SpecialSlot", Codec.BOOLEAN), + (benchItemCategory, s) -> benchItemCategory.specialSlot = s, + benchItemCategory -> benchItemCategory.specialSlot + ) + .build(); + protected String id; + protected String name; + protected String icon; + protected String diagram; + protected int slots = 1; + protected boolean specialSlot = true; + + public BenchItemCategory(String id, String name, String icon, String diagram, int slots, boolean specialSlot) { + this.id = id; + this.name = name; + this.icon = icon; + this.diagram = diagram; + this.slots = slots; + this.specialSlot = specialSlot; + } + + protected BenchItemCategory() { + } + + public String getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public String getIcon() { + return this.icon; + } + + public String getDiagram() { + return this.diagram; + } + + public int getSlots() { + return this.slots; + } + + public boolean isSpecialSlot() { + return this.specialSlot; + } + + @Nonnull + @Override + public String toString() { + return "BenchItemCategory{id='" + + this.id + + "', name='" + + this.name + + "', icon='" + + this.icon + + "', diagram='" + + this.diagram + + "', slots='" + + this.slots + + "', specialSlot='" + + this.specialSlot + + "'}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/DiagramCraftingBench.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/DiagramCraftingBench.java new file mode 100644 index 0000000..210b36d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/DiagramCraftingBench.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config.bench; + +import com.hypixel.hytale.codec.builder.BuilderCodec; + +public class DiagramCraftingBench extends CraftingBench { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DiagramCraftingBench.class, DiagramCraftingBench::new, CraftingBench.CODEC + ) + .build(); + + public DiagramCraftingBench() { + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/ProcessingBench.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/ProcessingBench.java new file mode 100644 index 0000000..23c48ff --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/ProcessingBench.java @@ -0,0 +1,291 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config.bench; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.validator.SoundEventValidators; +import com.hypixel.hytale.server.core.inventory.MaterialQuantity; +import java.util.Arrays; +import java.util.Locale; +import java.util.Objects; +import javax.annotation.Nullable; + +public class ProcessingBench extends Bench { + public static final BuilderCodec CODEC = BuilderCodec.builder(ProcessingBench.class, ProcessingBench::new, Bench.BASE_CODEC) + .append( + new KeyedCodec<>("Input", new ArrayCodec<>(ProcessingBench.ProcessingSlot.CODEC, ProcessingBench.ProcessingSlot[]::new)), + (bench, s) -> bench.input = s, + bench -> bench.input + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("Fuel", new ArrayCodec<>(ProcessingBench.ProcessingSlot.CODEC, ProcessingBench.ProcessingSlot[]::new)), + (bench, s) -> bench.fuel = s, + bench -> bench.fuel + ) + .add() + .append(new KeyedCodec<>("MaxFuel", Codec.INTEGER), (bench, s) -> bench.maxFuel = s, bench -> bench.maxFuel) + .add() + .append(new KeyedCodec<>("FuelDropItemId", Codec.STRING), (bench, s) -> bench.fuelDropItemId = s, bench -> bench.fuelDropItemId) + .add() + .append(new KeyedCodec<>("OutputSlotsCount", Codec.INTEGER), (bench, s) -> bench.outputSlotsCount = s, bench -> bench.outputSlotsCount) + .addValidator(Validators.greaterThanOrEqual(1)) + .add() + .append(new KeyedCodec<>("ExtraOutput", ProcessingBench.ExtraOutput.CODEC), (bench, s) -> bench.extraOutput = s, bench -> bench.extraOutput) + .add() + .append(new KeyedCodec<>("AllowNoInputProcessing", Codec.BOOLEAN), (bench, s) -> bench.allowNoInputProcessing = s, bench -> bench.allowNoInputProcessing) + .add() + .append(new KeyedCodec<>("IconItem", Codec.STRING), (bench, s) -> bench.iconItem = s, bench -> bench.iconItem) + .add() + .append(new KeyedCodec<>("Icon", Codec.STRING), (bench, s) -> bench.icon = s, bench -> bench.icon) + .add() + .append(new KeyedCodec<>("IconName", Codec.STRING), (bench, s) -> bench.iconName = s, bench -> bench.iconName) + .add() + .append(new KeyedCodec<>("IconId", Codec.STRING), (bench, s) -> bench.iconId = s, bench -> bench.iconId) + .add() + .append(new KeyedCodec<>("EndSoundEventId", Codec.STRING), (bench, s) -> bench.endSoundEventId = s, bench -> bench.endSoundEventId) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .addValidator(SoundEventValidators.ONESHOT) + .add() + .afterDecode(p -> { + if (p.icon != null) { + if (p.iconId == null) { + String name = p.icon.substring(0, p.icon.indexOf(46)); + p.iconId = name.toLowerCase(Locale.ROOT); + } + + if (p.iconName == null) { + String name = p.icon.substring(0, p.icon.indexOf(46)); + p.iconName = name.toLowerCase(Locale.ROOT); + } + } + + if (p.endSoundEventId != null) { + p.endSoundEventIndex = SoundEvent.getAssetMap().getIndex(p.endSoundEventId); + } + }) + .build(); + protected ProcessingBench.ProcessingSlot[] input; + protected ProcessingBench.ProcessingSlot[] fuel; + protected boolean allowNoInputProcessing; + protected ProcessingBench.ExtraOutput extraOutput; + protected int maxFuel = -1; + protected String fuelDropItemId; + protected int outputSlotsCount = 1; + protected String iconItem; + protected String icon; + protected String iconName; + protected String iconId; + @Nullable + protected String endSoundEventId = null; + protected transient int endSoundEventIndex = 0; + + public ProcessingBench() { + } + + public String getIconItem() { + return this.iconItem; + } + + public String getIcon() { + return this.icon; + } + + public String getIconName() { + return this.iconName; + } + + public String getIconId() { + return this.iconId; + } + + public ProcessingBench.ProcessingSlot[] getInput(int tierLevel) { + if (this.tierLevels == null) { + return this.input; + } else if (tierLevel > this.tierLevels.length) { + return this.input; + } else { + ProcessingBench.ProcessingSlot[] result = new ProcessingBench.ProcessingSlot[this.input.length + this.tierLevels[tierLevel - 1].extraInputSlot]; + Arrays.fill(result, this.input[0]); + return result; + } + } + + public ProcessingBench.ProcessingSlot[] getFuel() { + return this.fuel; + } + + public int getMaxFuel() { + return this.maxFuel; + } + + public String getFuelDropItemId() { + return this.fuelDropItemId; + } + + public int getOutputSlotsCount(int tierLevel) { + if (this.tierLevels == null) { + return this.outputSlotsCount; + } else { + return tierLevel > this.tierLevels.length ? this.outputSlotsCount : this.outputSlotsCount + this.tierLevels[tierLevel - 1].extraOutputSlot; + } + } + + public ProcessingBench.ExtraOutput getExtraOutput() { + return this.extraOutput; + } + + @Nullable + public String getEndSoundEventId() { + return this.endSoundEventId; + } + + public int getEndSoundEventIndex() { + return this.endSoundEventIndex; + } + + public boolean shouldAllowNoInputProcessing() { + return this.allowNoInputProcessing; + } + + @Override + public boolean equals(Object o) { + if (o == null || this.getClass() != o.getClass()) { + return false; + } else if (!super.equals(o)) { + return false; + } else { + ProcessingBench that = (ProcessingBench)o; + return this.allowNoInputProcessing == that.allowNoInputProcessing + && this.maxFuel == that.maxFuel + && this.outputSlotsCount == that.outputSlotsCount + && this.endSoundEventIndex == that.endSoundEventIndex + && Arrays.equals((Object[])this.input, (Object[])that.input) + && Arrays.equals((Object[])this.fuel, (Object[])that.fuel) + && Objects.equals(this.extraOutput, that.extraOutput) + && Objects.equals(this.fuelDropItemId, that.fuelDropItemId) + && Objects.equals(this.iconItem, that.iconItem) + && Objects.equals(this.icon, that.icon) + && Objects.equals(this.iconName, that.iconName) + && Objects.equals(this.iconId, that.iconId) + && Objects.equals(this.endSoundEventId, that.endSoundEventId); + } + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + Arrays.hashCode((Object[])this.input); + result = 31 * result + Arrays.hashCode((Object[])this.fuel); + result = 31 * result + Boolean.hashCode(this.allowNoInputProcessing); + result = 31 * result + Objects.hashCode(this.extraOutput); + result = 31 * result + this.maxFuel; + result = 31 * result + Objects.hashCode(this.fuelDropItemId); + result = 31 * result + this.outputSlotsCount; + result = 31 * result + Objects.hashCode(this.iconItem); + result = 31 * result + Objects.hashCode(this.icon); + result = 31 * result + Objects.hashCode(this.iconName); + result = 31 * result + Objects.hashCode(this.iconId); + result = 31 * result + Objects.hashCode(this.endSoundEventId); + return 31 * result + this.endSoundEventIndex; + } + + public static class ExtraOutput { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ProcessingBench.ExtraOutput.class, ProcessingBench.ExtraOutput::new + ) + .append(new KeyedCodec<>("Outputs", new ArrayCodec<>(MaterialQuantity.CODEC, MaterialQuantity[]::new)), (o, i) -> o.outputs = i, o -> o.outputs) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("PerFuelItemsConsumed", Codec.INTEGER), (o, i) -> o.perFuelItemsConsumed = i, o -> o.perFuelItemsConsumed) + .addValidator(Validators.greaterThanOrEqual(1)) + .add() + .append( + new KeyedCodec<>("IgnoredFuelSources", new ArrayCodec<>(MaterialQuantity.CODEC, MaterialQuantity[]::new)), + (o, i) -> o.ignoredFuelSources = i, + o -> o.ignoredFuelSources + ) + .add() + .build(); + private MaterialQuantity[] outputs; + private int perFuelItemsConsumed = 1; + private MaterialQuantity[] ignoredFuelSources; + + public ExtraOutput() { + } + + public MaterialQuantity[] getOutputs() { + return this.outputs; + } + + public int getPerFuelItemsConsumed() { + return this.perFuelItemsConsumed; + } + + public boolean isIgnoredFuelSource(Item id) { + if (this.ignoredFuelSources == null) { + return false; + } else { + for (MaterialQuantity mat : this.ignoredFuelSources) { + if (mat.getItemId() != null && mat.getItemId().equals(id.getBlockId())) { + return true; + } + } + + return false; + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + ProcessingBench.ExtraOutput that = (ProcessingBench.ExtraOutput)o; + return this.perFuelItemsConsumed != that.perFuelItemsConsumed ? false : Arrays.equals((Object[])this.outputs, (Object[])that.outputs); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = Arrays.hashCode((Object[])this.outputs); + return 31 * result + this.perFuelItemsConsumed; + } + } + + public static class ProcessingSlot extends Bench.BenchSlot { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ProcessingBench.ProcessingSlot.class, ProcessingBench.ProcessingSlot::new, Bench.BenchSlot.CODEC + ) + .append(new KeyedCodec<>("ResourceTypeId", Codec.STRING), (benchSlot, s) -> benchSlot.resourceTypeId = s, benchSlot -> benchSlot.resourceTypeId) + .add() + .append( + new KeyedCodec<>("FilterValidIngredients", Codec.BOOLEAN), + (benchSlot, b) -> benchSlot.filterValidIngredients = b, + benchSlot -> benchSlot.filterValidIngredients + ) + .add() + .build(); + protected boolean filterValidIngredients; + protected String resourceTypeId; + + public ProcessingSlot() { + } + + public String getResourceTypeId() { + return this.resourceTypeId; + } + + public boolean shouldFilterValidIngredients() { + return this.filterValidIngredients; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/StructuralCraftingBench.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/StructuralCraftingBench.java new file mode 100644 index 0000000..22fb053 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/bench/StructuralCraftingBench.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config.bench; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.Collections; +import javax.annotation.Nonnull; + +public class StructuralCraftingBench extends Bench { + public static final BuilderCodec CODEC = BuilderCodec.builder( + StructuralCraftingBench.class, StructuralCraftingBench::new, Bench.BASE_CODEC + ) + .append( + new KeyedCodec<>("Categories", new ArrayCodec<>(Codec.STRING, String[]::new)), + (bench, categories) -> bench.sortedCategories = categories, + bench -> bench.sortedCategories + ) + .add() + .append( + new KeyedCodec<>("HeaderCategories", new ArrayCodec<>(Codec.STRING, String[]::new)), + (bench, headerCategories) -> bench.headerCategories = headerCategories, + bench -> bench.headerCategories + ) + .add() + .append( + new KeyedCodec<>("AlwaysShowInventoryHints", Codec.BOOLEAN), + (bench, alwaysShowInventoryHints) -> bench.alwaysShowInventoryHints = alwaysShowInventoryHints, + bench -> bench.alwaysShowInventoryHints + ) + .add() + .append( + new KeyedCodec<>("AllowBlockGroupCycling", Codec.BOOLEAN), + (bench, allowBlockGroupCycling) -> bench.allowBlockGroupCycling = allowBlockGroupCycling, + bench -> bench.allowBlockGroupCycling + ) + .add() + .afterDecode(StructuralCraftingBench::processConfig) + .build(); + private String[] headerCategories; + private ObjectOpenHashSet headerCategoryMap; + private String[] sortedCategories; + private Object2IntMap categoryToIndexMap; + private boolean allowBlockGroupCycling; + private boolean alwaysShowInventoryHints; + + public StructuralCraftingBench() { + } + + private void processConfig() { + if (this.headerCategories != null) { + this.headerCategoryMap = new ObjectOpenHashSet<>(); + Collections.addAll(this.headerCategoryMap, this.headerCategories); + } + + if (this.sortedCategories != null) { + this.categoryToIndexMap = new Object2IntOpenHashMap<>(); + + for (int i = 0; i < this.sortedCategories.length; i++) { + this.categoryToIndexMap.put(this.sortedCategories[i], i); + } + } + } + + public boolean isHeaderCategory(@Nonnull String category) { + return this.headerCategoryMap != null && this.headerCategoryMap.contains(category); + } + + public int getCategoryIndex(@Nonnull String category) { + return this.categoryToIndexMap.getOrDefault(category, Integer.MAX_VALUE); + } + + public boolean shouldAllowBlockGroupCycling() { + return this.allowBlockGroupCycling; + } + + public boolean shouldAlwaysShowInventoryHints() { + return this.alwaysShowInventoryHints; + } + + @Override + public String toString() { + return "StructuralCraftingBench{}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/farming/FarmingData.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/farming/FarmingData.java new file mode 100644 index 0000000..f1083f0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/farming/FarmingData.java @@ -0,0 +1,132 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config.farming; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.Rangef; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FarmingData { + @Nonnull + public static Codec CODEC = BuilderCodec.builder(FarmingData.class, FarmingData::new) + .append( + new KeyedCodec<>("Stages", new MapCodec<>(new ArrayCodec<>(FarmingStageData.CODEC, FarmingStageData[]::new), HashMap::new)), + (farming, stages) -> farming.stages = stages, + farming -> farming.stages + ) + .add() + .append( + new KeyedCodec<>("StartingStageSet", Codec.STRING), (farming, starting) -> farming.startingStageSet = starting, farming -> farming.startingStageSet + ) + .add() + .append( + new KeyedCodec<>("StageSetAfterHarvest", Codec.STRING), (farming, set) -> farming.stageSetAfterHarvest = set, farming -> farming.stageSetAfterHarvest + ) + .add() + .append( + new KeyedCodec<>("ActiveGrowthModifiers", GrowthModifierAsset.CHILD_ASSET_CODEC_ARRAY), + (farming, modifiers) -> farming.growthModifiers = modifiers, + farming -> farming.growthModifiers + ) + .add() + .appendInherited( + new KeyedCodec<>("SoilConfig", FarmingData.SoilConfig.CODEC), (o, v) -> o.soilConfig = v, o -> o.soilConfig, (o, p) -> o.soilConfig = p.soilConfig + ) + .add() + .afterDecode(farmingData -> { + if (farmingData != null && farmingData.getStages() != null) { + if (!farmingData.getStages().containsKey(farmingData.startingStageSet)) { + throw new IllegalArgumentException("Invalid StartingStageSet " + farmingData.startingStageSet); + } + + if (farmingData.stageSetAfterHarvest != null && !farmingData.getStages().containsKey(farmingData.stageSetAfterHarvest)) { + throw new IllegalArgumentException("Invalid StageSetAfterHarvest " + farmingData.startingStageSet); + } + } + }) + .build(); + protected Map stages; + protected String startingStageSet = "Default"; + protected String stageSetAfterHarvest; + protected String[] growthModifiers; + @Nullable + protected FarmingData.SoilConfig soilConfig; + + public FarmingData() { + } + + @Nullable + public Map getStages() { + return this.stages; + } + + @Nullable + public String getStartingStageSet() { + return this.startingStageSet; + } + + public String getStageSetAfterHarvest() { + return this.stageSetAfterHarvest; + } + + public String[] getGrowthModifiers() { + return this.growthModifiers; + } + + @Nullable + public FarmingData.SoilConfig getSoilConfig() { + return this.soilConfig; + } + + @Nonnull + @Override + public String toString() { + return "FarmingData{stages=" + + this.stages + + ", startingStageSet='" + + this.startingStageSet + + "', stageSetAfterHarvest='" + + this.stageSetAfterHarvest + + "', growthModifiers=" + + Arrays.toString((Object[])this.growthModifiers) + + "}"; + } + + public static class SoilConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder(FarmingData.SoilConfig.class, FarmingData.SoilConfig::new) + .appendInherited( + new KeyedCodec<>("TargetBlock", Codec.STRING), (o, v) -> o.targetBlock = v, o -> o.targetBlock, (o, p) -> o.targetBlock = p.targetBlock + ) + .addValidatorLate(() -> BlockType.VALIDATOR_CACHE.getValidator().late()) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Lifetime", ProtocolCodecs.RANGEF), (o, v) -> o.lifetime = v, o -> o.lifetime, (o, p) -> o.lifetime = p.lifetime + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + protected String targetBlock; + protected Rangef lifetime; + + public SoilConfig() { + } + + public String getTargetBlock() { + return this.targetBlock; + } + + public Rangef getLifetime() { + return this.lifetime; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/farming/FarmingStageData.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/farming/FarmingStageData.java new file mode 100644 index 0000000..d3ac3ab --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/farming/FarmingStageData.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config.farming; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.protocol.Rangef; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.validator.SoundEventValidators; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class FarmingStageData { + @Nonnull + public static CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + @Nonnull + public static BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(FarmingStageData.class) + .append( + new KeyedCodec<>("Duration", ProtocolCodecs.RANGEF), + (farmingStage, duration) -> farmingStage.duration = duration, + farmingStage -> farmingStage.duration + ) + .add() + .append( + new KeyedCodec<>("SoundEventId", Codec.STRING), (farmingStage, sound) -> farmingStage.soundEventId = sound, farmingStage -> farmingStage.soundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .addValidator(SoundEventValidators.ONESHOT) + .add() + .afterDecode(farmingStage -> { + if (farmingStage.soundEventId != null) { + farmingStage.soundEventIndex = SoundEvent.getAssetMap().getIndex(farmingStage.soundEventId); + } + }) + .build(); + protected Rangef duration; + @Nullable + protected String soundEventId = null; + protected transient int soundEventIndex = 0; + + public FarmingStageData() { + } + + @Nullable + public Rangef getDuration() { + return this.duration; + } + + @Nullable + public String getSoundEventId() { + return this.soundEventId; + } + + public int getSoundEventIndex() { + return this.soundEventIndex; + } + + public boolean implementsShouldStop() { + return false; + } + + public boolean shouldStop(ComponentAccessor commandBuffer, Ref sectionRef, Ref blockRef, int x, int y, int z) { + return false; + } + + public void apply( + ComponentAccessor commandBuffer, + Ref sectionRef, + Ref blockRef, + int x, + int y, + int z, + @Nullable FarmingStageData previousStage + ) { + ChunkSection section = commandBuffer.getComponent(sectionRef, ChunkSection.getComponentType()); + int worldX = ChunkUtil.worldCoordFromLocalCoord(section.getX(), x); + int worldY = ChunkUtil.worldCoordFromLocalCoord(section.getY(), y); + int worldZ = ChunkUtil.worldCoordFromLocalCoord(section.getZ(), z); + SoundUtil.playSoundEvent3d( + this.soundEventIndex, SoundCategory.SFX, worldX, worldY, worldZ, commandBuffer.getExternalData().getWorld().getEntityStore().getStore() + ); + if (previousStage != null) { + previousStage.remove(commandBuffer, sectionRef, blockRef, x, y, z); + } + } + + public void remove(ComponentAccessor commandBuffer, Ref sectionRef, Ref blockRef, int x, int y, int z) { + } + + @Nonnull + @Override + public String toString() { + return "FarmingStageData{duration=" + this.duration + ", soundEventId='" + this.soundEventId + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/farming/GrowthModifierAsset.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/farming/GrowthModifierAsset.java new file mode 100644 index 0000000..219ba4f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/farming/GrowthModifierAsset.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config.farming; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; + +public abstract class GrowthModifierAsset implements JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(GrowthModifierAsset.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(GrowthModifierAsset.class) + .appendInherited( + new KeyedCodec<>("Modifier", Codec.DOUBLE), + (asset, modifier) -> asset.modifier = modifier, + asset -> asset.modifier, + (asset, parent) -> asset.modifier = parent.modifier + ) + .add() + .build(); + private static AssetStore> ASSET_STORE; + private AssetExtraInfo.Data data; + protected String id; + protected double modifier; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(GrowthModifierAsset.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public GrowthModifierAsset() { + } + + public GrowthModifierAsset(String id) { + this.id = id; + } + + public String getId() { + return this.id; + } + + public double getModifier() { + return this.modifier; + } + + public double getCurrentGrowthMultiplier( + CommandBuffer commandBuffer, Ref sectionRef, Ref blockRef, int x, int y, int z, boolean initialTick + ) { + return this.modifier; + } + + @Nonnull + @Override + public String toString() { + return "GrowthModifierAsset{id='" + this.id + "', modifier=" + this.modifier + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/mountpoints/BlockMountPoint.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/mountpoints/BlockMountPoint.java new file mode 100644 index 0000000..c0eafc8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/mountpoints/BlockMountPoint.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config.mountpoints; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import javax.annotation.Nonnull; + +public class BlockMountPoint { + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockMountPoint.class, BlockMountPoint::new) + .appendInherited(new KeyedCodec<>("Offset", Vector3f.CODEC), (seat, i) -> seat.offset = i, seat -> seat.offset, (seat, p) -> seat.offset = p.offset) + .documentation("Relative offset from the block center (the point at .5,.5,.5 in world). Forward on a chair is 0,0,0.3") + .add() + .appendInherited( + new KeyedCodec<>("Yaw", Codec.DOUBLE), + (seat, o) -> seat.yawOffSetDegrees = o.floatValue(), + seat -> (double)seat.yawOffSetDegrees, + (seat, p) -> seat.yawOffSetDegrees = p.yawOffSetDegrees + ) + .documentation("Offset for the model sitting on this seat in DEGREES") + .add() + .build(); + public static final BlockMountPoint[] EMPTY_ARRAY = new BlockMountPoint[0]; + private Vector3f offset; + private float yawOffSetDegrees; + + public BlockMountPoint() { + this(new Vector3f(), 0.0F); + } + + public BlockMountPoint(Vector3f offset, float yawOffSetDegrees) { + this.offset = offset; + this.yawOffSetDegrees = yawOffSetDegrees; + } + + public Vector3f getOffset() { + return this.offset; + } + + public float getYawOffSetDegrees() { + return this.yawOffSetDegrees; + } + + @Nonnull + public BlockMountPoint rotate(@Nonnull Rotation yaw, @Nonnull Rotation pitch, @Nonnull Rotation roll) { + Vector3f rotatedOffset = Rotation.rotate(this.offset, yaw, pitch, roll); + return new BlockMountPoint(rotatedOffset, this.yawOffSetDegrees); + } + + @Nonnull + public Vector3f computeWorldSpacePosition(@Nonnull Vector3i blockLoc) { + return blockLoc.toVector3f().add(0.5F, 0.5F, 0.5F).add(this.offset.x, this.offset.y, this.offset.z); + } + + @Nonnull + public Vector3f computeRotationEuler(@Nonnull int rotationIndex) { + RotationTuple rotationTuple = RotationTuple.get(rotationIndex); + Vector3f rotation = new Vector3f( + (float)rotationTuple.pitch().getRadians(), (float)rotationTuple.yaw().getRadians(), (float)rotationTuple.roll().getRadians() + ); + rotation.addYaw((float) Math.PI); + rotation.addYaw((float)Math.toRadians(this.yawOffSetDegrees)); + return rotation; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/mountpoints/RotatedMountPointsArray.java b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/mountpoints/RotatedMountPointsArray.java new file mode 100644 index 0000000..1d00bf2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/blocktype/config/mountpoints/RotatedMountPointsArray.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.asset.type.blocktype.config.mountpoints; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.function.FunctionCodec; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nullable; + +public class RotatedMountPointsArray { + private static final ArrayCodec CHILD = new ArrayCodec<>(BlockMountPoint.CODEC, BlockMountPoint[]::new); + public static final Codec CODEC = new FunctionCodec<>(CHILD, RotatedMountPointsArray::new, RotatedMountPointsArray::getRaw); + private BlockMountPoint[] raw; + private transient BlockMountPoint[][] rotated; + + public RotatedMountPointsArray() { + } + + public RotatedMountPointsArray(BlockMountPoint[] raw) { + this.raw = raw; + } + + public int size() { + return this.raw == null ? 0 : this.raw.length; + } + + public BlockMountPoint[] getRaw() { + return this.raw; + } + + @Nullable + public BlockMountPoint[] getRotated(int rotationIndex) { + if (this.raw != null && rotationIndex != 0) { + if (this.rotated == null) { + this.rotated = new BlockMountPoint[RotationTuple.VALUES.length][]; + } + + BlockMountPoint[] value = this.rotated[rotationIndex]; + if (value == null) { + RotationTuple rotation = RotationTuple.get(rotationIndex); + List list = new ObjectArrayList<>(); + + for (BlockMountPoint mountPoint : this.raw) { + BlockMountPoint rotated = mountPoint.rotate(rotation.yaw(), rotation.pitch(), rotation.roll()); + list.add(rotated); + } + + value = list.toArray(BlockMountPoint[]::new); + this.rotated[rotationIndex] = value; + } + + return value; + } else { + return this.raw; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/BlockTypeListAsset.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/BlockTypeListAsset.java new file mode 100644 index 0000000..028fcf5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/BlockTypeListAsset.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.common.map.WeightedMap; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import java.util.Collections; +import java.util.HashSet; +import javax.annotation.Nonnull; + +public class BlockTypeListAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + BlockTypeListAsset.class, + BlockTypeListAsset::new, + Codec.STRING, + (builder, id) -> builder.id = id, + builder -> builder.id, + (builder, data) -> builder.data = data, + builder -> builder.data + ) + .append(new KeyedCodec<>("Blocks", new ArrayCodec<>(Codec.STRING, String[]::new), true), (builder, blockTypeKeys) -> { + if (blockTypeKeys != null) { + Collections.addAll(builder.blockTypeKeys, blockTypeKeys); + } + }, builder -> builder.blockTypeKeys.toArray(String[]::new)) + .add() + .afterDecode(blockTypeListAsset -> { + if (blockTypeListAsset.blockTypeKeys != null) { + WeightedMap.Builder weightedMapBuilder = WeightedMap.builder(ArrayUtil.EMPTY_STRING_ARRAY); + + for (String blockTypeKey : blockTypeListAsset.blockTypeKeys) { + weightedMapBuilder.put(blockTypeKey, 1.0); + } + + blockTypeListAsset.blockPattern = new BlockPattern(weightedMapBuilder.build()); + } + }) + .build(); + private static AssetStore> ASSET_STORE; + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(BlockTypeListAsset::getAssetStore)); + private String id; + private final HashSet blockTypeKeys = new HashSet<>(); + private BlockPattern blockPattern; + private AssetExtraInfo.Data data; + + public BlockTypeListAsset() { + } + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(BlockTypeListAsset.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public BlockPattern getBlockPattern() { + return this.blockPattern; + } + + @Nonnull + public HashSet getBlockTypeKeys() { + return this.blockTypeKeys; + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/BrushData.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/BrushData.java new file mode 100644 index 0000000..7ffd0f0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/BrushData.java @@ -0,0 +1,800 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.protocol.Rotation; +import com.hypixel.hytale.protocol.packets.buildertools.BrushAxis; +import com.hypixel.hytale.protocol.packets.buildertools.BrushOrigin; +import com.hypixel.hytale.protocol.packets.buildertools.BrushShape; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolBlockArg; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolBrushData; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolStringArg; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.BlockArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.BoolArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.BrushAxisArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.BrushOriginArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.BrushRotationArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.BrushShapeArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.IntArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.MaskArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.StringArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.ToolArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.ToolArgException; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockMask; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import java.util.Arrays; +import java.util.Objects; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; + +public class BrushData implements NetworkSerializable { + public static final String WIDTH_KEY = "Width"; + public static final String HEIGHT_KEY = "Height"; + public static final String SHAPE_KEY = "Shape"; + public static final String THICKNESS_KEY = "Thickness"; + public static final String CAPPED_KEY = "Capped"; + public static final String ORIGIN_KEY = "Origin"; + public static final String ORIGIN_ROTATION_KEY = "OriginRotation"; + public static final String ROTATION_AXIS_KEY = "RotationAxis"; + public static final String ROTATION_ANGLE_KEY = "RotationAngle"; + public static final String MIRROR_AXIS_KEY = "MirrorAxis"; + public static final String MATERIAL_KEY = "Material"; + public static final String FAVORITE_MATERIALS_KEY = "FavoriteMaterials"; + public static final String MASK_KEY = "Mask"; + public static final String MASK_ABOVE_KEY = "MaskAbove"; + public static final String MASK_NOT_KEY = "MaskNot"; + public static final String MASK_BELOW_KEY = "MaskBelow"; + public static final String MASK_ADJACENT_KEY = "MaskAdjacent"; + public static final String MASK_NEIGHBOR_KEY = "MaskNeighbor"; + public static final String MASK_COMMANDS_KEY = "MaskCommands"; + public static final String USE_MASK_COMMANDS_KEY = "UseMaskCommands"; + public static final String INVERT_MASK_KEY = "InvertMask"; + private static final String WIDTH_DOC = "The width of the brush shape"; + private static final String HEIGHT_DOC = "The height of the brush shape"; + private static final String THICKNESS_DOC = "The number of blocks thick the walls of the brush shape should be"; + private static final String CAPPED_DOC = "Controls whether the end(s) of hollow brush shapes are closed or open"; + private static final String SHAPE_DOC = "The brush shape"; + private static final String ORIGIN_DOC = "The origin of the brush shape"; + private static final String ORIGIN_ROTATION_DOC = "Toggles the vertical offset for shapes rotated about the x/z axis"; + private static final String ROTATION_AXIS_DOC = "The axis that the brush shape should rotate around"; + private static final String ROTATION_ANGLE_DOC = "The angle that the brush shape should be rotated by"; + private static final String MIRROR_AXIS_DOC = "The axis that the brush shape should mirror in"; + private static final String MATERIAL_DOC = "The material to apply when the brush is used"; + private static final String FAVORITE_MATERIALS_DOC = "Materials available for quick selection.\n\nWhen a material is selected from here, it is set on the Material key."; + private static final String MASK_DOC = "Limits the selection to blocks matching materials in this mask"; + private static final String MASK_ABOVE_DOC = "Limits the selection to blocks above ones matching materials in this mask"; + private static final String MASK_NOT_DOC = "Limits the selection to any blocks except ones matching materials in this mask"; + private static final String MASK_BELOW_DOC = "Limits the selection to blocks below ones matching materials in this mask"; + private static final String MASK_ADJACENT_DOC = "Limits the selection to blocks horizontally adjacent to ones matching materials in this mask"; + private static final String MASK_NEIGHBOR_DOC = "Limits the selection to blocks neighboring (in any direction) ones matching materials in this mask"; + private static final String MASK_COMMANDS_DOC = "Custom mask commands to apply to the brush, based on /gmask syntax"; + private static final String USE_MASK_COMMANDS_DOC = "Specifies whether to use the block selector mask values or custom mask commands"; + private static final String INVERT_MASK_DOC = "When enabled, inverts the entire combined mask result"; + public static final int DEFAULT_WIDTH = 5; + public static final int DEFAULT_HEIGHT = 5; + public static final BrushData DEFAULT = new BrushData(); + public static final int DEFAULT_FAVORITE_MATERIALS_CAPACITY = 5; + private static final Pattern NEWLINES_PATTERN = Pattern.compile("\\r?\\n"); + public static final BuilderCodec CODEC = BuilderCodec.builder(BrushData.class, BrushData::new) + .append(new KeyedCodec<>("Width", IntArg.CODEC), (brushData, o) -> brushData.width = o, brushData -> brushData.width) + .addValidator(Validators.nonNull()) + .documentation("The width of the brush shape") + .add() + .append(new KeyedCodec<>("Height", IntArg.CODEC), (brushData, o) -> brushData.height = o, brushData -> brushData.height) + .addValidator(Validators.nonNull()) + .documentation("The height of the brush shape") + .add() + .append(new KeyedCodec<>("Thickness", IntArg.CODEC), (data, o) -> data.thickness = o, data -> data.thickness) + .addValidator(Validators.nonNull()) + .documentation("The number of blocks thick the walls of the brush shape should be") + .add() + .append(new KeyedCodec<>("Capped", BoolArg.CODEC), (data, o) -> data.capped = o, data -> data.capped) + .addValidator(Validators.nonNull()) + .documentation("Controls whether the end(s) of hollow brush shapes are closed or open") + .add() + .append(new KeyedCodec<>("Shape", BrushShapeArg.CODEC), (brushData, o) -> brushData.shape = o, brushData -> brushData.shape) + .addValidator(Validators.nonNull()) + .documentation("The brush shape") + .add() + .append(new KeyedCodec<>("Origin", BrushOriginArg.CODEC), (brushData, o) -> brushData.origin = o, brushData -> brushData.origin) + .addValidator(Validators.nonNull()) + .documentation("The origin of the brush shape") + .add() + .append(new KeyedCodec<>("OriginRotation", BoolArg.CODEC), (data, o) -> data.originRotation = o, data -> data.originRotation) + .addValidator(Validators.nonNull()) + .documentation("Toggles the vertical offset for shapes rotated about the x/z axis") + .add() + .append(new KeyedCodec<>("RotationAxis", BrushAxisArg.CODEC), (data, o) -> data.rotationAxis = o, data -> data.rotationAxis) + .addValidator(Validators.nonNull()) + .documentation("The axis that the brush shape should rotate around") + .add() + .append(new KeyedCodec<>("RotationAngle", BrushRotationArg.CODEC), (data, o) -> data.rotationAngle = o, data -> data.rotationAngle) + .addValidator(Validators.nonNull()) + .documentation("The angle that the brush shape should be rotated by") + .add() + .append(new KeyedCodec<>("MirrorAxis", BrushAxisArg.CODEC), (data, o) -> data.mirrorAxis = o, data -> data.mirrorAxis) + .addValidator(Validators.nonNull()) + .documentation("The axis that the brush shape should mirror in") + .add() + .append(new KeyedCodec<>("Material", BlockArg.CODEC), (brushData, o) -> brushData.material = o, brushData -> brushData.material) + .addValidator(Validators.nonNull()) + .documentation("The material to apply when the brush is used") + .add() + .append( + new KeyedCodec<>("FavoriteMaterials", new ArrayCodec<>(BlockArg.CODEC, BlockArg[]::new)), + (brushData, o) -> brushData.favoriteMaterials = o, + brushData -> brushData.favoriteMaterials + ) + .documentation("Materials available for quick selection.\n\nWhen a material is selected from here, it is set on the Material key.") + .add() + .append(new KeyedCodec<>("Mask", MaskArg.CODEC), (brushData, o) -> brushData.mask = o, brushData -> brushData.mask) + .documentation("Limits the selection to blocks matching materials in this mask") + .add() + .append(new KeyedCodec<>("MaskAbove", MaskArg.CODEC), (brushData, o) -> brushData.maskAbove = o, brushData -> brushData.maskAbove) + .documentation("Limits the selection to blocks above ones matching materials in this mask") + .add() + .append(new KeyedCodec<>("MaskNot", MaskArg.CODEC), (brushData, o) -> brushData.maskNot = o, brushData -> brushData.maskNot) + .documentation("Limits the selection to any blocks except ones matching materials in this mask") + .add() + .append(new KeyedCodec<>("MaskBelow", MaskArg.CODEC), (brushData, o) -> brushData.maskBelow = o, brushData -> brushData.maskBelow) + .documentation("Limits the selection to blocks below ones matching materials in this mask") + .add() + .append(new KeyedCodec<>("MaskAdjacent", MaskArg.CODEC), (brushData, o) -> brushData.maskAdjacent = o, brushData -> brushData.maskAdjacent) + .documentation("Limits the selection to blocks horizontally adjacent to ones matching materials in this mask") + .add() + .append(new KeyedCodec<>("MaskNeighbor", MaskArg.CODEC), (brushData, o) -> brushData.maskNeighbor = o, brushData -> brushData.maskNeighbor) + .documentation("Limits the selection to blocks neighboring (in any direction) ones matching materials in this mask") + .add() + .append( + new KeyedCodec<>("MaskCommands", new ArrayCodec<>(StringArg.CODEC, StringArg[]::new)), + (brushData, o) -> brushData.maskCommands = o, + brushData -> brushData.maskCommands + ) + .documentation("Custom mask commands to apply to the brush, based on /gmask syntax") + .add() + .append( + new KeyedCodec<>("UseMaskCommands", BoolArg.CODEC), (brushData, o) -> brushData.useMaskCommands = o, brushData -> brushData.useMaskCommands + ) + .documentation("Specifies whether to use the block selector mask values or custom mask commands") + .add() + .append(new KeyedCodec<>("InvertMask", BoolArg.CODEC), (brushData, o) -> brushData.invertMask = o, brushData -> brushData.invertMask) + .documentation("When enabled, inverts the entire combined mask result") + .add() + .build(); + protected IntArg width = new IntArg(5, 1, 100); + protected IntArg height = new IntArg(5, 1, 100); + protected IntArg thickness = new IntArg(0, 0, 100); + protected BoolArg capped = new BoolArg(false); + protected BrushShapeArg shape = new BrushShapeArg(BrushShape.Cube); + protected BrushOriginArg origin = new BrushOriginArg(BrushOrigin.Center); + protected BoolArg originRotation = new BoolArg(false); + protected BrushAxisArg rotationAxis = new BrushAxisArg(BrushAxis.None); + protected BrushRotationArg rotationAngle = new BrushRotationArg(Rotation.None); + protected BrushAxisArg mirrorAxis = new BrushAxisArg(BrushAxis.None); + protected BlockArg material = new BlockArg(BlockPattern.EMPTY, true); + protected BlockArg[] favoriteMaterials = BlockArg.EMPTY_ARRAY; + protected MaskArg mask = MaskArg.EMPTY; + protected MaskArg maskAbove = MaskArg.EMPTY; + protected MaskArg maskNot = MaskArg.EMPTY; + protected MaskArg maskBelow = MaskArg.EMPTY; + protected MaskArg maskAdjacent = MaskArg.EMPTY; + protected MaskArg maskNeighbor = MaskArg.EMPTY; + protected StringArg[] maskCommands = StringArg.EMPTY_ARRAY; + protected BoolArg useMaskCommands = new BoolArg(false); + protected BoolArg invertMask = new BoolArg(false); + + protected BrushData() { + } + + public BrushData( + IntArg width, + IntArg height, + IntArg thickness, + BoolArg capped, + BrushShapeArg shape, + BrushOriginArg origin, + BoolArg originRotation, + BrushAxisArg rotationAxis, + BrushRotationArg rotationAngle, + BrushAxisArg mirrorAxis, + BlockArg material, + BlockArg[] favoriteMaterials, + MaskArg mask, + MaskArg maskAbove, + MaskArg maskNot, + MaskArg maskBelow, + MaskArg maskAdjacent, + MaskArg maskNeighbor, + StringArg[] maskCommands, + BoolArg useMaskCommands + ) { + this.width = width; + this.height = height; + this.thickness = thickness; + this.capped = capped; + this.shape = shape; + this.origin = origin; + this.originRotation = originRotation; + this.rotationAxis = rotationAxis; + this.rotationAngle = rotationAngle; + this.mirrorAxis = mirrorAxis; + this.material = material; + this.favoriteMaterials = favoriteMaterials; + this.mask = mask; + this.maskAbove = maskAbove; + this.maskNot = maskNot; + this.maskBelow = maskBelow; + this.maskAdjacent = maskAdjacent; + this.maskNeighbor = maskNeighbor; + this.maskCommands = maskCommands; + this.useMaskCommands = useMaskCommands; + } + + public IntArg getWidth() { + return this.width; + } + + public IntArg getHeight() { + return this.height; + } + + public IntArg getThickness() { + return this.thickness; + } + + public BoolArg getCapped() { + return this.capped; + } + + public BrushShapeArg getShape() { + return this.shape; + } + + public BrushOriginArg getOrigin() { + return this.origin; + } + + public BoolArg getOriginRotation() { + return this.originRotation; + } + + public BrushAxisArg getRotationAxis() { + return this.rotationAxis; + } + + public BrushRotationArg getRotationAngle() { + return this.rotationAngle; + } + + public BrushAxisArg getMirrorAxis() { + return this.mirrorAxis; + } + + public BlockArg getMaterial() { + return this.material; + } + + public BlockArg[] getFavoriteMaterials() { + return this.favoriteMaterials; + } + + public MaskArg getMask() { + return this.mask; + } + + public MaskArg getMaskAbove() { + return this.maskAbove; + } + + public MaskArg getMaskNot() { + return this.maskNot; + } + + public MaskArg getMaskBelow() { + return this.maskBelow; + } + + public MaskArg getMaskAdjacent() { + return this.maskAdjacent; + } + + public MaskArg getMaskNeighbor() { + return this.maskNeighbor; + } + + public StringArg[] getMaskCommands() { + return this.maskCommands; + } + + public BoolArg getUseMaskCommands() { + return this.useMaskCommands; + } + + public BoolArg getInvertMask() { + return this.invertMask; + } + + public void updateArgValue(@Nonnull BrushData.Values brush, @Nonnull String id, @Nonnull String value) throws ToolArgException { + switch (id) { + case "Height": + brush.height = this.height.fromString(value); + break; + case "Width": + brush.width = this.width.fromString(value); + break; + case "Thickness": + brush.thickness = this.thickness.fromString(value); + break; + case "Capped": + brush.capped = this.capped.fromString(value); + break; + case "Shape": + brush.shape = this.shape.fromString(value); + break; + case "Origin": + brush.origin = this.origin.fromString(value); + break; + case "OriginRotation": + brush.originRotation = this.originRotation.fromString(value); + break; + case "RotationAxis": + brush.rotationAxis = this.rotationAxis.fromString(value); + break; + case "RotationAngle": + brush.rotationAngle = this.rotationAngle.fromString(value); + break; + case "MirrorAxis": + brush.mirrorAxis = this.mirrorAxis.fromString(value); + break; + case "Material": + brush.material = this.material.fromString(value); + break; + case "FavoriteMaterials": + brush.favoriteMaterials = value.isEmpty() + ? BlockPattern.EMPTY_ARRAY + : Arrays.stream(value.split(",")).limit(5L).map(BlockPattern::parse).toArray(BlockPattern[]::new); + break; + case "Mask": + brush.mask = this.mask.fromString(value); + break; + case "MaskAbove": + brush.maskAbove = this.maskAbove.fromString(value); + break; + case "MaskNot": + brush.maskNot = this.maskNot.fromString(value); + break; + case "MaskBelow": + brush.maskBelow = this.maskBelow.fromString(value); + break; + case "MaskAdjacent": + brush.maskAdjacent = this.maskAdjacent.fromString(value); + break; + case "MaskNeighbor": + brush.maskNeighbor = this.maskNeighbor.fromString(value); + break; + case "MaskCommands": + brush.maskCommands = value.isEmpty() ? ArrayUtil.EMPTY_STRING_ARRAY : NEWLINES_PATTERN.split(value); + break; + case "UseMaskCommands": + brush.useMaskCommands = this.useMaskCommands.fromString(value); + break; + case "InvertMask": + brush.invertMask = this.invertMask.fromString(value); + break; + default: + throw new ToolArgException(Message.translation("server.builderTools.toolUnknownArg").param("arg", id)); + } + } + + @Nonnull + public BuilderToolBrushData toPacket() { + BuilderToolBrushData packet = new BuilderToolBrushData(); + packet.width = this.width.toIntArgPacket(); + packet.height = this.height.toIntArgPacket(); + packet.thickness = this.thickness.toIntArgPacket(); + packet.capped = this.capped.toBoolArgPacket(); + packet.shape = this.shape.toBrushShapeArgPacket(); + packet.origin = this.origin.toBrushOriginArgPacket(); + packet.originRotation = this.originRotation.toBoolArgPacket(); + packet.rotationAxis = this.rotationAxis.toBrushAxisArgPacket(); + packet.rotationAngle = this.rotationAngle.toRotationArgPacket(); + packet.mirrorAxis = this.mirrorAxis.toBrushAxisArgPacket(); + packet.material = this.material.toBlockArgPacket(); + packet.favoriteMaterials = Arrays.stream(this.favoriteMaterials) + .filter(Objects::nonNull) + .map(BlockArg::toBlockArgPacket) + .toArray(BuilderToolBlockArg[]::new); + packet.mask = this.mask.toMaskArgPacket(); + packet.maskAbove = this.maskAbove.toMaskArgPacket(); + packet.maskNot = this.maskNot.toMaskArgPacket(); + packet.maskBelow = this.maskBelow.toMaskArgPacket(); + packet.maskAdjacent = this.maskAdjacent.toMaskArgPacket(); + packet.maskNeighbor = this.maskNeighbor.toMaskArgPacket(); + packet.maskCommands = Arrays.stream(this.maskCommands).filter(Objects::nonNull).map(StringArg::toStringArgPacket).toArray(BuilderToolStringArg[]::new); + packet.useMaskCommands = this.useMaskCommands.toBoolArgPacket(); + packet.invertMask = this.invertMask.toBoolArgPacket(); + return packet; + } + + @Nonnull + @Override + public String toString() { + return "BrushData{width=" + + this.width + + ", height=" + + this.height + + ", thickness=" + + this.thickness + + ", capped=" + + this.capped + + ", shape=" + + this.shape + + ", origin=" + + this.origin + + ", originRotation=" + + this.originRotation + + ", rotationAxis=" + + this.rotationAxis + + ", rotationAngle=" + + this.rotationAngle + + ", mirrorAxis=" + + this.mirrorAxis + + ", material=" + + this.material + + ", favoriteMaterials=" + + Arrays.toString((Object[])this.favoriteMaterials) + + ", mask=" + + this.mask + + ", maskAbove=" + + this.maskAbove + + ", maskNot=" + + this.maskNot + + ", maskBelow=" + + this.maskBelow + + ", maskAdjacent=" + + this.maskAdjacent + + ", maskNeighbor=" + + this.maskNeighbor + + ", maskCommands=" + + Arrays.toString((Object[])this.maskCommands) + + ", useMaskCommands=" + + this.useMaskCommands + + ", invertMask=" + + this.invertMask + + "}"; + } + + public static class Values { + public static final Codec CODEC = BuilderCodec.builder(BrushData.Values.class, BrushData.Values::new) + .append(new KeyedCodec<>("Width", Codec.INTEGER), (brushData, o) -> brushData.width = o, brushData -> brushData.width) + .addValidator(Validators.greaterThan(0)) + .documentation("The width of the brush shape") + .add() + .append(new KeyedCodec<>("Height", Codec.INTEGER), (brushData, o) -> brushData.height = o, brushData -> brushData.height) + .addValidator(Validators.greaterThan(0)) + .documentation("The height of the brush shape") + .add() + .append(new KeyedCodec<>("Thickness", Codec.INTEGER), (data, o) -> data.thickness = o, data -> data.thickness) + .addValidator(Validators.range(0, 100)) + .documentation("The number of blocks thick the walls of the brush shape should be") + .add() + .append(new KeyedCodec<>("Capped", Codec.BOOLEAN), (data, o) -> data.capped = o, data -> data.capped) + .addValidator(Validators.nonNull()) + .documentation("Controls whether the end(s) of hollow brush shapes are closed or open") + .add() + .append(new KeyedCodec<>("Shape", BrushShapeArg.BRUSH_SHAPE_CODEC), (brushData, o) -> brushData.shape = o, brushData -> brushData.shape) + .addValidator(Validators.nonNull()) + .documentation("The brush shape") + .add() + .append( + new KeyedCodec<>("Origin", BrushOriginArg.BRUSH_ORIGIN_CODEC), (brushData, o) -> brushData.origin = o, brushData -> brushData.origin + ) + .addValidator(Validators.nonNull()) + .documentation("The origin of the brush shape") + .add() + .append(new KeyedCodec<>("OriginRotation", Codec.BOOLEAN), (data, o) -> data.originRotation = o, data -> data.originRotation) + .addValidator(Validators.nonNull()) + .documentation("Toggles the vertical offset for shapes rotated about the x/z axis") + .add() + .append(new KeyedCodec<>("RotationAxis", BrushAxisArg.BRUSH_AXIS_CODEC), (data, o) -> data.rotationAxis = o, data -> data.rotationAxis) + .addValidator(Validators.nonNull()) + .documentation("The axis that the brush shape should rotate around") + .add() + .append(new KeyedCodec<>("RotationAngle", BrushRotationArg.ROTATION_CODEC), (data, o) -> data.rotationAngle = o, data -> data.rotationAngle) + .addValidator(Validators.nonNull()) + .documentation("The angle that the brush shape should be rotated by") + .add() + .append(new KeyedCodec<>("MirrorAxis", BrushAxisArg.BRUSH_AXIS_CODEC), (data, o) -> data.mirrorAxis = o, data -> data.mirrorAxis) + .addValidator(Validators.nonNull()) + .documentation("The axis that the brush shape should mirror in") + .add() + .append(new KeyedCodec<>("Material", BlockPattern.CODEC), (brushData, o) -> brushData.material = o, brushData -> brushData.material) + .addValidator(Validators.nonNull()) + .documentation("The material to apply when the brush is used") + .add() + .append( + new KeyedCodec<>("FavoriteMaterials", new ArrayCodec<>(BlockPattern.CODEC, BlockPattern[]::new)), + (brushData, o) -> brushData.favoriteMaterials = o, + brushData -> brushData.favoriteMaterials + ) + .addValidator(Validators.arraySizeRange(0, 5)) + .documentation("Materials available for quick selection.\n\nWhen a material is selected from here, it is set on the Material key.") + .add() + .append(new KeyedCodec<>("Mask", BlockMask.CODEC), (brushData, o) -> brushData.mask = o, brushData -> brushData.mask) + .documentation("Limits the selection to blocks matching materials in this mask") + .add() + .append(new KeyedCodec<>("MaskAbove", BlockMask.CODEC), (brushData, o) -> brushData.maskAbove = o, brushData -> brushData.maskAbove) + .documentation("Limits the selection to blocks above ones matching materials in this mask") + .add() + .append(new KeyedCodec<>("MaskNot", BlockMask.CODEC), (brushData, o) -> brushData.maskNot = o, brushData -> brushData.maskNot) + .documentation("Limits the selection to any blocks except ones matching materials in this mask") + .add() + .append(new KeyedCodec<>("MaskBelow", BlockMask.CODEC), (brushData, o) -> brushData.maskBelow = o, brushData -> brushData.maskBelow) + .documentation("Limits the selection to blocks below ones matching materials in this mask") + .add() + .append( + new KeyedCodec<>("MaskAdjacent", BlockMask.CODEC), (brushData, o) -> brushData.maskAdjacent = o, brushData -> brushData.maskAdjacent + ) + .documentation("Limits the selection to blocks horizontally adjacent to ones matching materials in this mask") + .add() + .append( + new KeyedCodec<>("MaskNeighbor", BlockMask.CODEC), (brushData, o) -> brushData.maskNeighbor = o, brushData -> brushData.maskNeighbor + ) + .documentation("Limits the selection to blocks neighboring (in any direction) ones matching materials in this mask") + .add() + .append( + new KeyedCodec<>("MaskCommands", Codec.STRING_ARRAY), (brushData, o) -> brushData.maskCommands = o, brushData -> brushData.maskCommands + ) + .documentation("Custom mask commands to apply to the brush, based on /gmask syntax") + .add() + .append( + new KeyedCodec<>("UseMaskCommands", Codec.BOOLEAN), (brushData, o) -> brushData.useMaskCommands = o, brushData -> brushData.useMaskCommands + ) + .documentation("Specifies whether to use the block selector mask values or custom mask commands") + .add() + .append(new KeyedCodec<>("InvertMask", Codec.BOOLEAN), (brushData, o) -> brushData.invertMask = o, brushData -> brushData.invertMask) + .documentation("When enabled, inverts the entire combined mask result") + .add() + .build(); + private int width; + private int height; + private int thickness; + private boolean capped; + private BrushShape shape; + private BrushOrigin origin; + private boolean originRotation; + private BrushAxis rotationAxis; + private Rotation rotationAngle; + private BrushAxis mirrorAxis; + private BlockPattern material; + private BlockPattern[] favoriteMaterials; + private BlockMask mask; + private BlockMask maskAbove; + private BlockMask maskNot; + private BlockMask maskBelow; + private BlockMask maskAdjacent; + private BlockMask maskNeighbor; + private String[] maskCommands; + private boolean useMaskCommands; + private boolean invertMask; + + protected Values() { + this(BrushData.DEFAULT); + } + + public Values(@Nonnull BrushData brushData) { + this.width = brushData.width.getValue(); + this.height = brushData.height.getValue(); + this.thickness = brushData.thickness.getValue(); + this.capped = brushData.capped.getValue(); + this.shape = brushData.shape.getValue(); + this.origin = brushData.origin.getValue(); + this.originRotation = brushData.originRotation.getValue(); + this.rotationAxis = brushData.rotationAxis.getValue(); + this.rotationAngle = brushData.rotationAngle.getValue(); + this.mirrorAxis = brushData.mirrorAxis.getValue(); + this.material = brushData.material.getValue(); + this.favoriteMaterials = brushData.favoriteMaterials.length == 0 + ? BlockPattern.EMPTY_ARRAY + : Arrays.stream(brushData.favoriteMaterials).limit(5L).map(ToolArg::getValue).toArray(BlockPattern[]::new); + this.mask = brushData.mask.getValue(); + this.maskAbove = brushData.maskAbove.getValue(); + this.maskNot = brushData.maskNot.getValue(); + this.maskBelow = brushData.maskBelow.getValue(); + this.maskAdjacent = brushData.maskAdjacent.getValue(); + this.maskNeighbor = brushData.maskNeighbor.getValue(); + this.maskCommands = brushData.maskCommands.length == 0 + ? ArrayUtil.EMPTY_STRING_ARRAY + : Arrays.stream(brushData.maskCommands).map(ToolArg::getValue).toArray(String[]::new); + this.useMaskCommands = brushData.useMaskCommands.getValue(); + this.invertMask = brushData.invertMask.getValue(); + } + + public Values( + int width, + int height, + int thickness, + boolean capped, + BrushShape shape, + BrushOrigin origin, + boolean originRotation, + BrushAxis rotationAxis, + Rotation rotationAngle, + BrushAxis mirrorAxis, + BlockPattern material, + BlockPattern[] favoriteMaterials, + BlockMask mask, + BlockMask maskAbove, + BlockMask maskNot, + BlockMask maskBelow, + BlockMask maskAdjacent, + BlockMask maskNeighbor, + String[] maskCommands, + boolean useMaskCommands + ) { + this.width = width; + this.height = height; + this.thickness = thickness; + this.capped = capped; + this.shape = shape; + this.origin = origin; + this.originRotation = originRotation; + this.rotationAxis = rotationAxis; + this.rotationAngle = rotationAngle; + this.mirrorAxis = mirrorAxis; + this.material = material; + this.favoriteMaterials = favoriteMaterials; + this.mask = mask; + this.maskAbove = maskAbove; + this.maskNot = maskNot; + this.maskBelow = maskBelow; + this.maskAdjacent = maskAdjacent; + this.maskNeighbor = maskNeighbor; + this.maskCommands = maskCommands; + this.useMaskCommands = useMaskCommands; + } + + public int getWidth() { + return this.width; + } + + public int getHeight() { + return this.height; + } + + public int getThickness() { + return this.thickness; + } + + public boolean isCapped() { + return this.capped; + } + + public BrushShape getShape() { + return this.shape; + } + + public BrushOrigin getOrigin() { + return this.origin; + } + + public boolean getOriginRotation() { + return this.originRotation; + } + + public BrushAxis getRotationAxis() { + return this.rotationAxis; + } + + public Rotation getRotationAngle() { + return this.rotationAngle; + } + + public BrushAxis getMirrorAxis() { + return this.mirrorAxis; + } + + public BlockPattern getMaterial() { + return this.material; + } + + public BlockPattern[] getFavoriteMaterials() { + return this.favoriteMaterials; + } + + public BlockMask getMask() { + return this.mask; + } + + public BlockMask getMaskAbove() { + return this.maskAbove; + } + + public BlockMask getMaskNot() { + return this.maskNot; + } + + public BlockMask getMaskBelow() { + return this.maskBelow; + } + + public BlockMask getMaskAdjacent() { + return this.maskAdjacent; + } + + public BlockMask getMaskNeighbor() { + return this.maskNeighbor; + } + + public String[] getMaskCommands() { + return this.maskCommands; + } + + @Nonnull + public BlockMask[] getParsedMaskCommands() { + return Arrays.stream(this.getMaskCommands()).map(m -> m.split(" ")).map(BlockMask::parse).toArray(BlockMask[]::new); + } + + public boolean shouldUseMaskCommands() { + return this.useMaskCommands; + } + + public boolean shouldInvertMask() { + return this.invertMask; + } + + @Nonnull + @Override + public String toString() { + return "Values{width=" + + this.width + + ", height=" + + this.height + + ", thickness=" + + this.thickness + + ", capped=" + + this.capped + + ", shape=" + + this.shape + + ", origin=" + + this.origin + + ", originRotation=" + + this.originRotation + + ", rotationAxis=" + + this.rotationAxis + + ", rotationAngle=" + + this.rotationAngle + + ", mirrorAxis=" + + this.mirrorAxis + + ", material=" + + this.material + + ", favoriteMaterials=" + + Arrays.toString((Object[])this.favoriteMaterials) + + ", mask=" + + this.mask + + ", maskAbove=" + + this.maskAbove + + ", maskNot=" + + this.maskNot + + ", maskBelow=" + + this.maskBelow + + ", maskAdjacent=" + + this.maskAdjacent + + ", maskNeighbor=" + + this.maskNeighbor + + ", maskCommands=" + + Arrays.toString((Object[])this.maskCommands) + + ", useMaskCommands=" + + this.useMaskCommands + + ", invertMask=" + + this.invertMask + + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/BuilderTool.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/BuilderTool.java new file mode 100644 index 0000000..bf97f36 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/BuilderTool.java @@ -0,0 +1,251 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.lookup.MapProvidedMapCodec; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArg; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArgGroup; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolState; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.ToolArg; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.args.ToolArgException; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.lang.ref.SoftReference; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public class BuilderTool implements JsonAssetWithMap>, NetworkSerializable { + public static final String TOOL_DATA_KEY = "ToolData"; + public static final KeyedCodec BRUSH_DATA_KEY_CODEC = new KeyedCodec<>("BrushData", BrushData.Values.CODEC); + public static final BuilderTool DEFAULT = new BuilderTool(); + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + BuilderTool.class, BuilderTool::new, Codec.STRING, (t, k) -> t.id = k, t -> t.id, (asset, data) -> asset.data = data, asset -> asset.data + ) + .addField(new KeyedCodec<>("Id", Codec.STRING), (builderTool, o) -> builderTool.id = o, builderTool -> builderTool.id) + .addField(new KeyedCodec<>("IsBrush", Codec.BOOLEAN), (builderTool, o) -> builderTool.isBrush = o, builderTool -> builderTool.isBrush) + .addField( + new KeyedCodec<>("BrushConfigurationCommand", Codec.STRING), + (builderTool, o) -> builderTool.brushConfigurationCommand = o, + builderTool -> builderTool.brushConfigurationCommand + ) + .addField( + new KeyedCodec<>("Args", new MapCodec<>(ToolArg.CODEC, HashMap::new)), (builderTool, s) -> builderTool.args = s, builderTool -> builderTool.args + ) + .addField(new KeyedCodec<>("BrushData", BrushData.CODEC), (builderTool, o) -> builderTool.brushData = o, builderTool -> builderTool.brushData) + .afterDecode(builderTool -> { + if (!builderTool.args.isEmpty()) { + builderTool.argsCodec = new MapProvidedMapCodec<>(builderTool.args, ToolArg::getCodec, HashMap::new); + } + }) + .build(); + private static DefaultAssetMap ASSET_MAP; + protected AssetExtraInfo.Data data; + protected String id; + protected boolean isBrush; + protected String brushConfigurationCommand; + protected BrushData brushData = BrushData.DEFAULT; + protected Map args = Collections.emptyMap(); + protected Map defaultToolArgs; + private MapProvidedMapCodec argsCodec; + private SoftReference cachedPacket; + + public BuilderTool() { + } + + public static DefaultAssetMap getAssetMap() { + if (ASSET_MAP == null) { + ASSET_MAP = (DefaultAssetMap)AssetRegistry.getAssetStore(BuilderTool.class).getAssetMap(); + } + + return ASSET_MAP; + } + + @Nullable + public static BuilderTool getActiveBuilderTool(@Nonnull Player player) { + ItemStack activeItemStack = player.getInventory().getItemInHand(); + if (activeItemStack == null) { + return null; + } else { + Item item = activeItemStack.getItem(); + BuilderToolData builderToolData = item.getBuilderToolData(); + return builderToolData == null ? null : builderToolData.getTools()[0]; + } + } + + public String getId() { + return this.id; + } + + public String getBrushConfigurationCommand() { + return this.brushConfigurationCommand; + } + + public boolean isBrush() { + return this.isBrush; + } + + public BrushData getBrushData() { + return this.brushData; + } + + public Map getArgs() { + return this.args; + } + + public MapProvidedMapCodec getArgsCodec() { + return this.argsCodec; + } + + @Nonnull + private Map getDefaultToolArgs(@Nonnull ItemStack itemStack) { + BuilderTool builderToolAsset = itemStack.getItem().getBuilderToolData().getTools()[0]; + Map map = new Object2ObjectOpenHashMap<>(builderToolAsset.args.size()); + + for (Entry entry : builderToolAsset.args.entrySet()) { + map.put(entry.getKey(), entry.getValue().getValue()); + } + + return map; + } + + @Nonnull + private BrushData.Values getDefaultBrushArgs(@Nonnull ItemStack itemStack) { + BuilderTool builderToolAsset = itemStack.getItem().getBuilderToolData().getTools()[0]; + return new BrushData.Values(builderToolAsset.brushData); + } + + @Nonnull + public BuilderTool.ArgData getItemArgData(@Nonnull ItemStack itemStack) { + Map toolArgs = null; + if (!this.args.isEmpty()) { + Map toolData = itemStack.getFromMetadataOrNull("ToolData", this.argsCodec); + toolArgs = toolData == null ? this.getDefaultToolArgs(itemStack) : toolData; + } + + BrushData.Values brushArgs = null; + if (this.isBrush) { + BrushData.Values brushData = itemStack.getFromMetadataOrNull(BRUSH_DATA_KEY_CODEC); + brushArgs = brushData == null ? this.getDefaultBrushArgs(itemStack) : brushData; + } + + return new BuilderTool.ArgData(toolArgs, brushArgs); + } + + @Nonnull + public ItemStack createItemStack(@Nonnull String itemId, int quantity, @Nonnull BuilderTool.ArgData argData) { + BsonDocument meta = new BsonDocument(); + if (argData.tool() != null) { + meta.put("ToolData", this.argsCodec.encode(argData.tool())); + } + + if (this.isBrush) { + BRUSH_DATA_KEY_CODEC.put(meta, argData.brush); + } + + return new ItemStack(itemId, quantity, meta); + } + + @Nonnull + public ItemStack updateArgMetadata(@Nonnull ItemStack itemStack, BuilderToolArgGroup group, @Nonnull String id, @Nullable String value) throws ToolArgException { + BuilderTool.ArgData argData = this.getItemArgData(itemStack); + if (group == BuilderToolArgGroup.Brush) { + this.brushData.updateArgValue(argData.brush, id, value); + } else { + ToolArg arg = this.args.get(id); + if (arg == null) { + throw new ToolArgException(Message.translation("server.builderTools.toolUnknownArg").param("arg", id)); + } + + if (value == null) { + if (arg.isRequired()) { + throw new ToolArgException(Message.translation("server.builderTools.toolArgMissing").param("arg", id)); + } + + argData = BuilderTool.ArgData.removeToolArg(argData, id); + } else { + Object newValue = arg.fromString(value); + argData = BuilderTool.ArgData.setToolArg(argData, id, newValue); + } + } + + return this.createItemStack(itemStack.getItemId(), itemStack.getQuantity(), argData); + } + + @Nonnull + public BuilderToolState toPacket() { + BuilderToolState cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + BuilderToolState packet = new BuilderToolState(); + packet.id = this.id; + packet.isBrush = this.isBrush; + if (this.brushData != null) { + packet.brushData = this.brushData.toPacket(); + } + + Map map = new Object2ObjectOpenHashMap<>(this.args.size()); + + for (Entry entry : this.args.entrySet()) { + map.put(entry.getKey(), entry.getValue().toPacket()); + } + + packet.args = map; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + @Nonnull + @Override + public String toString() { + return "BuilderTool{id='" + this.id + "', isBrush=" + this.isBrush + ", brushData=" + this.brushData + ", args=" + this.args + "}"; + } + + public record ArgData(@Nullable Map tool, @Nullable BrushData.Values brush) { + @Nonnull + public static BuilderTool.ArgData setToolArg(@Nonnull BuilderTool.ArgData argData, String argId, Object value) { + Map tool = argData.tool(); + if (tool == null) { + return argData; + } else { + Object2ObjectOpenHashMap newToolArgs = new Object2ObjectOpenHashMap<>(tool); + newToolArgs.put(argId, value); + return new BuilderTool.ArgData(newToolArgs, argData.brush()); + } + } + + @Nonnull + public static BuilderTool.ArgData removeToolArg(@Nonnull BuilderTool.ArgData argData, String argId) { + Map tool = argData.tool(); + if (tool == null) { + return argData; + } else { + Object2ObjectOpenHashMap newToolArgs = new Object2ObjectOpenHashMap<>(tool); + newToolArgs.remove(argId); + return new BuilderTool.ArgData(newToolArgs, argData.brush()); + } + } + + @Nonnull + @Override + public String toString() { + return "ArgData{tool=" + this.tool + ", brush=" + this.brush + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/BuilderToolData.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/BuilderToolData.java new file mode 100644 index 0000000..b10aefa --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/BuilderToolData.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.ItemBuilderToolData; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolState; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class BuilderToolData implements NetworkSerializable { + public static final BuilderToolData DEFAULT = new BuilderToolData(); + public static final BuilderCodec CODEC = BuilderCodec.builder(BuilderToolData.class, BuilderToolData::new) + .addField( + new KeyedCodec<>("UI", new ArrayCodec<>(Codec.STRING, String[]::new)), + (builderToolData, o) -> builderToolData.ui = o, + builderToolData -> builderToolData.ui + ) + .append( + new KeyedCodec<>("Tools", new ArrayCodec<>(BuilderTool.CODEC, BuilderTool[]::new)), + (builderToolData, o) -> builderToolData.tools = o, + builderToolData -> builderToolData.tools + ) + .addValidator(Validators.nonEmptyArray()) + .add() + .build(); + protected String[] ui; + protected BuilderTool[] tools; + + public BuilderToolData() { + } + + public BuilderToolData(String[] ui, BuilderTool[] tools) { + this.ui = ui; + this.tools = tools; + } + + public String[] getUi() { + return this.ui; + } + + public BuilderTool[] getTools() { + return this.tools; + } + + @Nonnull + public ItemBuilderToolData toPacket() { + ItemBuilderToolData packet = new ItemBuilderToolData(); + packet.ui = this.ui; + packet.tools = new BuilderToolState[this.tools.length]; + + for (int i = 0; i < this.tools.length; i++) { + packet.tools[i] = this.tools[i].toPacket(); + } + + return packet; + } + + @Nonnull + @Override + public String toString() { + return "BuilderToolData{ui=" + Arrays.toString((Object[])this.ui) + ", tools=" + Arrays.toString((Object[])this.tools) + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/PrefabListAsset.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/PrefabListAsset.java new file mode 100644 index 0000000..da2eb22 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/PrefabListAsset.java @@ -0,0 +1,177 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.server.core.prefab.PrefabStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabListAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + PrefabListAsset.class, + PrefabListAsset::new, + Codec.STRING, + (builder, id) -> builder.id = id, + builder -> builder.id, + (builder, data) -> builder.data = data, + builder -> builder.data + ) + .append( + new KeyedCodec<>("Prefabs", new ArrayCodec<>(PrefabListAsset.PrefabReference.CODEC, PrefabListAsset.PrefabReference[]::new), true), + (builder, prefabPaths) -> builder.prefabReferences = prefabPaths, + builder -> builder.prefabReferences + ) + .add() + .afterDecode(PrefabListAsset::convertPrefabReferencesToPrefabPaths) + .build(); + private static AssetStore> ASSET_STORE; + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(PrefabListAsset::getAssetStore)); + private String id; + private Path[] prefabPaths; + private PrefabListAsset.PrefabReference[] prefabReferences; + private AssetExtraInfo.Data data; + + public PrefabListAsset() { + } + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(PrefabListAsset.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + private void convertPrefabReferencesToPrefabPaths() { + if (this.prefabReferences != null) { + ObjectArrayList paths = new ObjectArrayList<>(); + + for (PrefabListAsset.PrefabReference prefabReference : this.prefabReferences) { + paths.addAll(prefabReference.prefabPaths); + } + + this.prefabPaths = paths.toArray(Path[]::new); + } + } + + public Path[] getPrefabPaths() { + return this.prefabPaths; + } + + @Nonnull + public PrefabListAsset.PrefabReference[] getPrefabReferences() { + return this.prefabReferences != null ? this.prefabReferences : new PrefabListAsset.PrefabReference[0]; + } + + @Nullable + public Path getRandomPrefab() { + return this.prefabPaths.length == 0 ? null : this.prefabPaths[ThreadLocalRandom.current().nextInt(this.prefabPaths.length)]; + } + + public String getId() { + return this.id; + } + + public static class PrefabReference { + public static final BuilderCodec CODEC = BuilderCodec.builder( + PrefabListAsset.PrefabReference.class, PrefabListAsset.PrefabReference::new + ) + .append( + new KeyedCodec<>("RootDirectory", new EnumCodec<>(PrefabListAsset.PrefabRootDirectory.class), true), + (prefabReference, rootDirectory) -> prefabReference.rootDirectory = rootDirectory, + prefabReference -> prefabReference.rootDirectory + ) + .add() + .append( + new KeyedCodec<>("Path", Codec.STRING, true), + (prefabReference, unprocessedPrefabPath) -> prefabReference.unprocessedPrefabPath = unprocessedPrefabPath, + prefabReference -> prefabReference.unprocessedPrefabPath + ) + .add() + .append( + new KeyedCodec<>("Recursive", Codec.BOOLEAN, false), + (prefabReference, recursive) -> prefabReference.recursive = recursive, + prefabReference -> prefabReference.recursive + ) + .add() + .afterDecode(PrefabListAsset.PrefabReference::processPrefabPath) + .build(); + public PrefabListAsset.PrefabRootDirectory rootDirectory; + public String unprocessedPrefabPath; + public boolean recursive = false; + @Nonnull + public List prefabPaths = new ObjectArrayList<>(); + + public PrefabReference() { + } + + public void processPrefabPath() { + if (this.unprocessedPrefabPath != null) { + this.unprocessedPrefabPath = this.unprocessedPrefabPath.replace('\\', '/'); + if (this.unprocessedPrefabPath.endsWith("/")) { + try (Stream walk = Files.walk(this.rootDirectory.getPrefabPath().resolve(this.unprocessedPrefabPath), this.recursive ? 5 : 1)) { + walk.filter(x$0 -> Files.isRegularFile(x$0)).filter(path -> path.toString().endsWith(".prefab.json")).forEach(this.prefabPaths::add); + } catch (IOException var6) { + PrefabListAsset.getAssetStore() + .getLogger() + .at(Level.SEVERE) + .withCause(var6) + .log("Failed to process prefab path: %s", this.unprocessedPrefabPath); + } + } else { + if (!this.unprocessedPrefabPath.endsWith(".prefab.json")) { + this.unprocessedPrefabPath = this.unprocessedPrefabPath + ".prefab.json"; + } + + this.prefabPaths.add(this.rootDirectory.getPrefabPath().resolve(this.unprocessedPrefabPath)); + } + } + } + } + + public static enum PrefabRootDirectory { + Server(() -> PrefabStore.get().getServerPrefabsPath(), "server.commands.editprefab.ui.rootDirectory.server"), + Asset(() -> PrefabStore.get().getAssetPrefabsPath(), "server.commands.editprefab.ui.rootDirectory.asset"), + Worldgen(() -> PrefabStore.get().getWorldGenPrefabsPath(), "server.commands.editprefab.ui.rootDirectory.worldGen"); + + private final Supplier prefabPath; + private final String localizationString; + + private PrefabRootDirectory(Supplier prefabPath, String localizationString) { + this.prefabPath = prefabPath; + this.localizationString = localizationString; + } + + public Path getPrefabPath() { + return this.prefabPath.get(); + } + + public String getLocalizationString() { + return this.localizationString; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BlockArg.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BlockArg.java new file mode 100644 index 0000000..6664afb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BlockArg.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config.args; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArg; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArgType; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolBlockArg; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import javax.annotation.Nonnull; + +public class BlockArg extends ToolArg { + public static final BlockArg[] EMPTY_ARRAY = new BlockArg[0]; + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockArg.class, BlockArg::new, ToolArg.DEFAULT_CODEC) + .addField(new KeyedCodec<>("Default", BlockPattern.CODEC), (blockArg, d) -> blockArg.value = d, blockArg -> blockArg.value) + .addField(new KeyedCodec<>("AllowPattern", Codec.BOOLEAN), (blockArg, d) -> blockArg.allowPattern = d, blockArg -> blockArg.allowPattern) + .build(); + protected boolean allowPattern; + + public BlockArg() { + } + + public BlockArg(BlockPattern value, boolean allowPattern) { + this.value = value; + this.allowPattern = allowPattern; + } + + @Nonnull + @Override + public Codec getCodec() { + return BlockPattern.CODEC; + } + + @Nonnull + public BlockPattern fromString(@Nonnull String str) { + return BlockPattern.parse(str); + } + + @Nonnull + public BuilderToolBlockArg toBlockArgPacket() { + return new BuilderToolBlockArg(this.value.toString(), this.allowPattern); + } + + @Override + protected void setupPacket(@Nonnull BuilderToolArg packet) { + packet.argType = BuilderToolArgType.Block; + packet.blockArg = this.toBlockArgPacket(); + } + + @Nonnull + @Override + public String toString() { + return "BlockArg{allowPattern=" + this.allowPattern + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BoolArg.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BoolArg.java new file mode 100644 index 0000000..6fb01d1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BoolArg.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config.args; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArg; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArgType; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolBoolArg; +import javax.annotation.Nonnull; + +public class BoolArg extends ToolArg { + public static final BuilderCodec CODEC = BuilderCodec.builder(BoolArg.class, BoolArg::new, ToolArg.DEFAULT_CODEC) + .addField(new KeyedCodec<>("Default", Codec.BOOLEAN), (boolArg, d) -> boolArg.value = d, boolArg -> boolArg.value) + .build(); + + public BoolArg() { + } + + public BoolArg(boolean value) { + this.value = value; + } + + @Nonnull + @Override + public Codec getCodec() { + return Codec.BOOLEAN; + } + + @Nonnull + public Boolean fromString(@Nonnull String str) { + return Boolean.valueOf(str); + } + + @Nonnull + public BuilderToolBoolArg toBoolArgPacket() { + return new BuilderToolBoolArg(this.value); + } + + @Override + protected void setupPacket(@Nonnull BuilderToolArg packet) { + packet.argType = BuilderToolArgType.Bool; + packet.boolArg = this.toBoolArgPacket(); + } + + @Nonnull + @Override + public String toString() { + return "BoolArg{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BrushAxisArg.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BrushAxisArg.java new file mode 100644 index 0000000..7a505c6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BrushAxisArg.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config.args; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.packets.buildertools.BrushAxis; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArg; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArgType; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolBrushAxisArg; +import javax.annotation.Nonnull; + +public class BrushAxisArg extends ToolArg { + public static final Codec BRUSH_AXIS_CODEC = new EnumCodec<>(BrushAxis.class); + public static final BuilderCodec CODEC = BuilderCodec.builder(BrushAxisArg.class, BrushAxisArg::new, ToolArg.DEFAULT_CODEC) + .append(new KeyedCodec<>("Default", BRUSH_AXIS_CODEC), (arg, o) -> arg.value = o, arg -> arg.value) + .documentation("Represents the type of axis to be used when performing transformations on brushes") + .addValidator(Validators.nonNull()) + .add() + .build(); + + public BrushAxisArg() { + } + + public BrushAxisArg(BrushAxis value) { + this.value = value; + } + + @Nonnull + @Override + public Codec getCodec() { + return BRUSH_AXIS_CODEC; + } + + @Nonnull + public BrushAxis fromString(@Nonnull String str) throws ToolArgException { + return BrushAxis.valueOf(str); + } + + @Nonnull + public BuilderToolBrushAxisArg toBrushAxisArgPacket() { + return new BuilderToolBrushAxisArg(this.value); + } + + @Override + protected void setupPacket(@Nonnull BuilderToolArg packet) { + packet.argType = BuilderToolArgType.BrushAxis; + packet.brushAxisArg = this.toBrushAxisArgPacket(); + } + + @Nonnull + @Override + public String toString() { + return "BrushAxisArg{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BrushOriginArg.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BrushOriginArg.java new file mode 100644 index 0000000..195d353 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BrushOriginArg.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config.args; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.protocol.packets.buildertools.BrushOrigin; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArg; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArgType; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolBrushOriginArg; +import javax.annotation.Nonnull; + +public class BrushOriginArg extends ToolArg { + public static final EnumCodec BRUSH_ORIGIN_CODEC = new EnumCodec<>(BrushOrigin.class); + public static final BuilderCodec CODEC = BuilderCodec.builder(BrushOriginArg.class, BrushOriginArg::new, ToolArg.DEFAULT_CODEC) + .addField(new KeyedCodec<>("Default", BRUSH_ORIGIN_CODEC), (originArg, o) -> originArg.value = o, originArg -> originArg.value) + .build(); + + public BrushOriginArg() { + } + + public BrushOriginArg(BrushOrigin value) { + this.value = value; + } + + @Nonnull + @Override + public Codec getCodec() { + return BRUSH_ORIGIN_CODEC; + } + + @Nonnull + public BrushOrigin fromString(@Nonnull String str) { + return BrushOrigin.valueOf(str); + } + + @Nonnull + public BuilderToolBrushOriginArg toBrushOriginArgPacket() { + return new BuilderToolBrushOriginArg(this.value); + } + + @Override + protected void setupPacket(@Nonnull BuilderToolArg packet) { + packet.argType = BuilderToolArgType.BrushOrigin; + packet.brushOriginArg = this.toBrushOriginArgPacket(); + } + + @Nonnull + @Override + public String toString() { + return "BrushOriginArg{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BrushRotationArg.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BrushRotationArg.java new file mode 100644 index 0000000..30c07c1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BrushRotationArg.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config.args; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.Rotation; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArg; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArgType; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolRotationArg; +import javax.annotation.Nonnull; + +public class BrushRotationArg extends ToolArg { + public static final Codec ROTATION_CODEC = new EnumCodec<>(Rotation.class); + public static final BuilderCodec CODEC = BuilderCodec.builder(BrushRotationArg.class, BrushRotationArg::new, ToolArg.DEFAULT_CODEC) + .append(new KeyedCodec<>("Default", ROTATION_CODEC), (arg, o) -> arg.value = o, arg -> arg.value) + .documentation("Represents the amount of rotation to be applied to a brush shape") + .addValidator(Validators.nonNull()) + .add() + .build(); + + public BrushRotationArg() { + } + + public BrushRotationArg(Rotation value) { + this.value = value; + } + + @Nonnull + @Override + public Codec getCodec() { + return ROTATION_CODEC; + } + + @Nonnull + public Rotation fromString(@Nonnull String str) throws ToolArgException { + return Rotation.valueOf(str); + } + + @Nonnull + public BuilderToolRotationArg toRotationArgPacket() { + return new BuilderToolRotationArg(this.value); + } + + @Override + protected void setupPacket(@Nonnull BuilderToolArg packet) { + packet.argType = BuilderToolArgType.Rotation; + packet.rotationArg = this.toRotationArgPacket(); + } + + @Nonnull + @Override + public String toString() { + return "BrushRotationArg{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BrushShapeArg.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BrushShapeArg.java new file mode 100644 index 0000000..9e090e2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/BrushShapeArg.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config.args; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.protocol.packets.buildertools.BrushShape; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArg; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArgType; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolBrushShapeArg; +import javax.annotation.Nonnull; + +public class BrushShapeArg extends ToolArg { + public static final EnumCodec BRUSH_SHAPE_CODEC = new EnumCodec<>(BrushShape.class); + public static final BuilderCodec CODEC = BuilderCodec.builder(BrushShapeArg.class, BrushShapeArg::new, ToolArg.DEFAULT_CODEC) + .addField(new KeyedCodec<>("Default", BRUSH_SHAPE_CODEC), (shapeArg, o) -> shapeArg.value = o, shapeArg -> shapeArg.value) + .build(); + + public BrushShapeArg() { + } + + public BrushShapeArg(BrushShape value) { + this.value = value; + } + + @Nonnull + @Override + public Codec getCodec() { + return BRUSH_SHAPE_CODEC; + } + + @Nonnull + public BrushShape fromString(@Nonnull String str) { + return BrushShape.valueOf(str); + } + + @Nonnull + public BuilderToolBrushShapeArg toBrushShapeArgPacket() { + return new BuilderToolBrushShapeArg(this.value); + } + + @Override + protected void setupPacket(@Nonnull BuilderToolArg packet) { + packet.argType = BuilderToolArgType.BrushShape; + packet.brushShapeArg = this.toBrushShapeArgPacket(); + } + + @Nonnull + @Override + public String toString() { + return "BrushShapeArg{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/FloatArg.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/FloatArg.java new file mode 100644 index 0000000..948ea05 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/FloatArg.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config.args; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArg; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArgType; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolFloatArg; +import com.hypixel.hytale.server.core.Message; +import javax.annotation.Nonnull; + +public class FloatArg extends ToolArg { + public static final BuilderCodec CODEC = BuilderCodec.builder(FloatArg.class, FloatArg::new, ToolArg.DEFAULT_CODEC) + .addField(new KeyedCodec<>("Default", Codec.DOUBLE), (floatArg, o) -> floatArg.value = o.floatValue(), floatArg -> (double)floatArg.value.floatValue()) + .addField(new KeyedCodec<>("Min", Codec.DOUBLE), (floatArg, o) -> floatArg.min = o.floatValue(), floatArg -> (double)floatArg.min) + .addField(new KeyedCodec<>("Max", Codec.DOUBLE), (floatArg, o) -> floatArg.max = o.floatValue(), floatArg -> (double)floatArg.max) + .build(); + protected float min; + protected float max; + + public FloatArg() { + this.value = 0.0F; + } + + public FloatArg(float value, float min, float max) { + this.value = value; + this.min = min; + this.max = max; + } + + public float getMin() { + return this.min; + } + + public float getMax() { + return this.max; + } + + @Nonnull + @Override + public Codec getCodec() { + return Codec.FLOAT; + } + + @Nonnull + public Float fromString(@Nonnull String str) throws ToolArgException { + float value = Float.parseFloat(str); + if (!(value < this.min) && !(value > this.max)) { + return value; + } else { + throw new ToolArgException( + Message.translation("server.builderTools.toolArgRangeError").param("value", value).param("min", this.min).param("max", this.max) + ); + } + } + + @Nonnull + public BuilderToolFloatArg toFloatArgPacket() { + return new BuilderToolFloatArg(this.value, this.min, this.max); + } + + @Override + protected void setupPacket(@Nonnull BuilderToolArg packet) { + packet.argType = BuilderToolArgType.Float; + packet.floatArg = this.toFloatArgPacket(); + } + + @Nonnull + @Override + public String toString() { + return "FloatArg{min=" + this.min + ", max=" + this.max + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/IntArg.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/IntArg.java new file mode 100644 index 0000000..95873cc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/IntArg.java @@ -0,0 +1,72 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config.args; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArg; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArgType; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolIntArg; +import com.hypixel.hytale.server.core.Message; +import javax.annotation.Nonnull; + +public class IntArg extends ToolArg { + public static final BuilderCodec CODEC = BuilderCodec.builder(IntArg.class, IntArg::new, ToolArg.DEFAULT_CODEC) + .addField(new KeyedCodec<>("Default", Codec.INTEGER), (intArg, d) -> intArg.value = d, intArg -> intArg.value) + .addField(new KeyedCodec<>("Min", Codec.INTEGER), (intArg, d) -> intArg.min = d, intArg -> intArg.min) + .addField(new KeyedCodec<>("Max", Codec.INTEGER), (intArg, d) -> intArg.max = d, intArg -> intArg.max) + .build(); + protected int min; + protected int max; + + public IntArg() { + } + + public IntArg(int value, int min, int max) { + this.value = value; + this.min = min; + this.max = max; + } + + @Nonnull + @Override + public Codec getCodec() { + return Codec.INTEGER; + } + + public int getMin() { + return this.min; + } + + public int getMax() { + return this.max; + } + + @Nonnull + public Integer fromString(@Nonnull String str) throws ToolArgException { + int value = Integer.parseInt(str); + if (value >= this.min && value <= this.max) { + return value; + } else { + throw new ToolArgException( + Message.translation("server.builderTools.toolArgRangeError").param("value", value).param("min", this.min).param("max", this.max) + ); + } + } + + @Nonnull + public BuilderToolIntArg toIntArgPacket() { + return new BuilderToolIntArg(this.value, this.min, this.max); + } + + @Override + protected void setupPacket(@Nonnull BuilderToolArg packet) { + packet.argType = BuilderToolArgType.Int; + packet.intArg = this.toIntArgPacket(); + } + + @Nonnull + @Override + public String toString() { + return "IntArg{min=" + this.min + ", max=" + this.max + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/MaskArg.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/MaskArg.java new file mode 100644 index 0000000..c6311ea --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/MaskArg.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config.args; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArg; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArgType; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolMaskArg; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockMask; +import javax.annotation.Nonnull; + +public class MaskArg extends ToolArg { + public static final MaskArg EMPTY = new MaskArg(BlockMask.EMPTY, false); + public static final BuilderCodec CODEC = BuilderCodec.builder(MaskArg.class, MaskArg::new, ToolArg.DEFAULT_CODEC) + .addField(new KeyedCodec<>("Default", BlockMask.CODEC), (maskArg, d) -> maskArg.value = d, maskArg -> maskArg.value) + .build(); + + public MaskArg() { + } + + public MaskArg(BlockMask value) { + this.value = value; + } + + public MaskArg(BlockMask value, boolean required) { + this.value = value; + this.required = required; + } + + @Nonnull + @Override + public Codec getCodec() { + return BlockMask.CODEC; + } + + @Nonnull + public BlockMask fromString(@Nonnull String str) { + return BlockMask.parse(str); + } + + @Nonnull + public BuilderToolMaskArg toMaskArgPacket() { + return new BuilderToolMaskArg(this.value.toString()); + } + + @Override + protected void setupPacket(@Nonnull BuilderToolArg packet) { + packet.argType = BuilderToolArgType.Mask; + packet.maskArg = this.toMaskArgPacket(); + } + + @Nonnull + @Override + public String toString() { + return "MaskArg{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/OptionArg.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/OptionArg.java new file mode 100644 index 0000000..ee01ce4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/OptionArg.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config.args; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArg; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArgType; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolOptionArg; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class OptionArg extends ToolArg { + public static final BuilderCodec CODEC = BuilderCodec.builder(OptionArg.class, OptionArg::new, ToolArg.DEFAULT_CODEC) + .addField(new KeyedCodec<>("Default", Codec.STRING), (optionArg, o) -> optionArg.value = o, optionArg -> optionArg.value) + .addField(new KeyedCodec<>("Options", Codec.STRING_ARRAY), (optionArg, o) -> optionArg.options = o, optionArg -> optionArg.options) + .build(); + protected String[] options; + + public OptionArg() { + } + + public OptionArg(String value, String[] options) { + this.value = value; + this.options = options; + } + + @Nonnull + @Override + public Codec getCodec() { + return Codec.STRING; + } + + @Nonnull + public String fromString(@Nonnull String str) { + for (String option : this.options) { + if (str.equalsIgnoreCase(option)) { + return option; + } + } + + try { + int index = Integer.parseInt(str); + if (index >= 0 && index < this.options.length) { + return this.options[index]; + } + } catch (NumberFormatException var6) { + } + + throw new IllegalArgumentException(); + } + + @Nonnull + public BuilderToolOptionArg toOptionArgPacket() { + return new BuilderToolOptionArg(this.value, this.options); + } + + @Override + protected void setupPacket(@Nonnull BuilderToolArg packet) { + packet.argType = BuilderToolArgType.Option; + packet.optionArg = this.toOptionArgPacket(); + } + + @Nonnull + @Override + public String toString() { + return "OptionArg{options=" + Arrays.toString((Object[])this.options) + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/StringArg.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/StringArg.java new file mode 100644 index 0000000..5f99e38 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/StringArg.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config.args; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArg; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArgType; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolStringArg; +import javax.annotation.Nonnull; + +public class StringArg extends ToolArg { + public static final StringArg[] EMPTY_ARRAY = new StringArg[0]; + public static final BuilderCodec CODEC = BuilderCodec.builder(StringArg.class, StringArg::new, ToolArg.DEFAULT_CODEC) + .addField(new KeyedCodec<>("Default", Codec.STRING), (stringArg, d) -> stringArg.value = d, stringArg -> stringArg.value) + .build(); + + public StringArg() { + } + + public StringArg(String value) { + this.value = value; + } + + @Nonnull + @Override + public Codec getCodec() { + return Codec.STRING; + } + + @Nonnull + public String fromString(@Nonnull String str) { + return str; + } + + @Nonnull + public BuilderToolStringArg toStringArgPacket() { + return new BuilderToolStringArg(this.value); + } + + @Override + protected void setupPacket(@Nonnull BuilderToolArg packet) { + packet.argType = BuilderToolArgType.String; + packet.stringArg = this.toStringArgPacket(); + } + + @Nonnull + @Override + public String toString() { + return "StringArg{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/ToolArg.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/ToolArg.java new file mode 100644 index 0000000..7015a3a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/ToolArg.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config.args; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolArg; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public abstract class ToolArg implements NetworkSerializable { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final BuilderCodec DEFAULT_CODEC = BuilderCodec.abstractBuilder(ToolArg.class) + .addField(new KeyedCodec<>("Required", Codec.BOOLEAN), (shapeArg, o) -> shapeArg.required = o, shapeArg -> shapeArg.required) + .build(); + protected boolean required = true; + protected T value; + + public ToolArg() { + } + + public T getValue() { + return this.value; + } + + public boolean isRequired() { + return this.required; + } + + public abstract Codec getCodec(); + + @Nonnull + public abstract T fromString(@Nonnull String var1) throws ToolArgException; + + protected abstract void setupPacket(BuilderToolArg var1); + + @Nonnull + public BuilderToolArg toPacket() { + BuilderToolArg packet = new BuilderToolArg(); + packet.required = this.required; + this.setupPacket(packet); + return packet; + } + + @Nonnull + @Override + public String toString() { + return "ToolArg{required=" + this.required + ", value=" + this.value + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/ToolArgException.java b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/ToolArgException.java new file mode 100644 index 0000000..9a146c7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/buildertool/config/args/ToolArgException.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.core.asset.type.buildertool.config.args; + +import com.hypixel.hytale.server.core.Message; +import javax.annotation.Nonnull; + +public class ToolArgException extends Exception { + @Nonnull + private final Message translationMessage; + + public ToolArgException(@Nonnull Message translationMessage) { + super(translationMessage.toString()); + this.translationMessage = translationMessage; + } + + public ToolArgException(@Nonnull Message translationMessage, Throwable cause) { + super(translationMessage.toString(), cause); + this.translationMessage = translationMessage; + } + + @Nonnull + public Message getTranslationMessage() { + return this.translationMessage; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/camera/CameraEffect.java b/src/com/hypixel/hytale/server/core/asset/type/camera/CameraEffect.java new file mode 100644 index 0000000..1904055 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/camera/CameraEffect.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.server.core.asset.type.camera; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.protocol.packets.camera.CameraShakeEffect; +import javax.annotation.Nonnull; + +public abstract class CameraEffect implements JsonAssetWithMap> { + @Nonnull + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + @Nonnull + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(CameraEffect.class, CODEC); + @Nonnull + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(CameraEffect::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected String id; + protected AssetExtraInfo.Data data; + + public CameraEffect() { + } + + @Nonnull + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(CameraEffect.class); + } + + return ASSET_STORE; + } + + @Nonnull + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public String getId() { + return this.id; + } + + public abstract CameraShakeEffect createCameraShakePacket(); + + public abstract CameraShakeEffect createCameraShakePacket(float var1); + + public static class MissingCameraEffect extends CameraEffect { + public MissingCameraEffect(@Nonnull String id) { + } + + @Nonnull + @Override + public CameraShakeEffect createCameraShakePacket() { + return new CameraShakeEffect(); + } + + @Nonnull + @Override + public CameraShakeEffect createCameraShakePacket(float intensityContext) { + return new CameraShakeEffect(); + } + + @Nonnull + @Override + public String toString() { + return "MissingShakeEffect{}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/entityeffect/EntityEffectPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/entityeffect/EntityEffectPacketGenerator.java new file mode 100644 index 0000000..2a82054 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/entityeffect/EntityEffectPacketGenerator.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.asset.type.entityeffect; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateEntityEffects; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class EntityEffectPacketGenerator extends SimpleAssetPacketGenerator> { + public EntityEffectPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + UpdateEntityEffects packet = new UpdateEntityEffects(); + packet.type = UpdateType.Init; + packet.entityEffects = new Int2ObjectOpenHashMap<>(assets.size()); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.entityEffects.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + protected Packet generateUpdatePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets) { + UpdateEntityEffects packet = new UpdateEntityEffects(); + packet.type = UpdateType.AddOrUpdate; + packet.entityEffects = new Int2ObjectOpenHashMap<>(loadedAssets.size()); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.entityEffects.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + protected Packet generateRemovePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed) { + UpdateEntityEffects packet = new UpdateEntityEffects(); + packet.type = UpdateType.Remove; + packet.entityEffects = new Int2ObjectOpenHashMap<>(removed.size()); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.entityEffects.put(index, null); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/AbilityEffects.java b/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/AbilityEffects.java new file mode 100644 index 0000000..5e932f8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/AbilityEffects.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.core.asset.type.entityeffect.config; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import java.util.EnumSet; +import java.util.Set; +import javax.annotation.Nonnull; + +public class AbilityEffects implements NetworkSerializable { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(AbilityEffects.class, AbilityEffects::new) + .appendInherited( + new KeyedCodec<>("Disabled", InteractionModule.INTERACTION_TYPE_SET_CODEC), + (entityEffect, s) -> entityEffect.disabled = s, + entityEffect -> entityEffect.disabled, + (entityEffect, parent) -> entityEffect.disabled = parent.disabled + ) + .documentation("A collection of interaction types to become disabled while the entity effect affiliated with this ability effect is active") + .add() + .build(); + protected Set disabled; + + public AbilityEffects(@Nonnull Set disabled) { + this.disabled = EnumSet.copyOf(disabled); + } + + protected AbilityEffects() { + } + + @Nonnull + public com.hypixel.hytale.protocol.AbilityEffects toPacket() { + com.hypixel.hytale.protocol.AbilityEffects packet = new com.hypixel.hytale.protocol.AbilityEffects(); + packet.disabled = this.disabled == null ? null : this.disabled.toArray(InteractionType[]::new); + return packet; + } + + @Nonnull + @Override + public String toString() { + return "AbilityEffects{, disabled=" + this.disabled + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/ApplicationEffects.java b/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/ApplicationEffects.java new file mode 100644 index 0000000..692577d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/ApplicationEffects.java @@ -0,0 +1,250 @@ +package com.hypixel.hytale.server.core.asset.type.entityeffect.config; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.asset.modifiers.MovementEffects; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelParticle; +import com.hypixel.hytale.server.core.asset.type.modelvfx.config.ModelVFX; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.validator.SoundEventValidators; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class ApplicationEffects implements NetworkSerializable { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(ApplicationEffects.class, ApplicationEffects::new) + .appendInherited( + new KeyedCodec<>("EntityBottomTint", ProtocolCodecs.COLOR), + (entityEffect, s) -> entityEffect.entityBottomTint = s, + entityEffect -> entityEffect.entityBottomTint, + (entityEffect, parent) -> entityEffect.entityBottomTint = parent.entityBottomTint + ) + .add() + .appendInherited( + new KeyedCodec<>("EntityTopTint", ProtocolCodecs.COLOR), + (entityEffect, s) -> entityEffect.entityTopTint = s, + entityEffect -> entityEffect.entityTopTint, + (entityEffect, parent) -> entityEffect.entityTopTint = parent.entityTopTint + ) + .add() + .appendInherited( + new KeyedCodec<>("EntityAnimationId", Codec.STRING), + (entityEffect, s) -> entityEffect.entityAnimationId = s, + entityEffect -> entityEffect.entityAnimationId, + (entityEffect, parent) -> entityEffect.entityAnimationId = parent.entityAnimationId + ) + .add() + .appendInherited( + new KeyedCodec<>("Particles", ModelParticle.ARRAY_CODEC), + (entityEffect, s) -> entityEffect.particles = s, + entityEffect -> entityEffect.particles, + (entityEffect, parent) -> entityEffect.particles = parent.particles + ) + .add() + .appendInherited( + new KeyedCodec<>("FirstPersonParticles", ModelParticle.ARRAY_CODEC), + (entityEffect, s) -> entityEffect.firstPersonParticles = s, + entityEffect -> entityEffect.firstPersonParticles, + (entityEffect, parent) -> entityEffect.firstPersonParticles = parent.firstPersonParticles + ) + .add() + .appendInherited( + new KeyedCodec<>("ScreenEffect", Codec.STRING), + (entityEffect, s) -> entityEffect.screenEffect = s, + entityEffect -> entityEffect.screenEffect, + (entityEffect, parent) -> entityEffect.screenEffect = parent.screenEffect + ) + .addValidator(CommonAssetValidator.UI_SCREEN_EFFECT) + .add() + .appendInherited( + new KeyedCodec<>("HorizontalSpeedMultiplier", Codec.DOUBLE), + (entityEffect, s) -> entityEffect.horizontalSpeedMultiplier = s.floatValue(), + entityEffect -> (double)entityEffect.horizontalSpeedMultiplier, + (entityEffect, parent) -> entityEffect.horizontalSpeedMultiplier = parent.horizontalSpeedMultiplier + ) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .appendInherited( + new KeyedCodec<>("KnockbackMultiplier", Codec.DOUBLE), + (entityEffect, s) -> entityEffect.knockbackMultiplier = s.floatValue(), + entityEffect -> (double)entityEffect.knockbackMultiplier, + (entityEffect, parent) -> entityEffect.knockbackMultiplier = parent.knockbackMultiplier + ) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .appendInherited( + new KeyedCodec<>("LocalSoundEventId", Codec.STRING), + (entityEffect, s) -> entityEffect.soundEventIdLocal = s, + entityEffect -> entityEffect.soundEventIdLocal, + (entityEffect, parent) -> entityEffect.soundEventIdLocal = parent.soundEventIdLocal + ) + .documentation("Local sound event played to the affected player") + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("WorldSoundEventId", Codec.STRING), + (entityEffect, s) -> entityEffect.soundEventIdWorld = s, + entityEffect -> entityEffect.soundEventIdWorld, + (entityEffect, parent) -> entityEffect.soundEventIdWorld = parent.soundEventIdWorld + ) + .documentation("World sound event played to surrounding players") + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .add() + .afterDecode(ApplicationEffects::processConfig) + .appendInherited( + new KeyedCodec<>("ModelVFXId", Codec.STRING), + (entityEffect, s) -> entityEffect.modelVFXId = s, + entityEffect -> entityEffect.modelVFXId, + (entityEffect, parent) -> entityEffect.modelVFXId = parent.modelVFXId + ) + .addValidator(ModelVFX.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("MovementEffects", MovementEffects.CODEC), + (entityEffect, s) -> entityEffect.movementEffects = s, + entityEffect -> entityEffect.movementEffects, + (entityEffect, parent) -> entityEffect.movementEffects = parent.movementEffects + ) + .add() + .appendInherited( + new KeyedCodec<>("AbilityEffects", AbilityEffects.CODEC), + (entityEffect, s) -> entityEffect.abilityEffects = s, + entityEffect -> entityEffect.abilityEffects, + (entityEffect, parent) -> entityEffect.abilityEffects = parent.abilityEffects + ) + .documentation("Handles any effects applied that are affiliated with abilities") + .add() + .appendInherited( + new KeyedCodec<>("MouseSensitivityAdjustmentTarget", Codec.FLOAT), + (interaction, doubles) -> interaction.mouseSensitivityAdjustmentTarget = doubles, + interaction -> interaction.mouseSensitivityAdjustmentTarget, + (interaction, parent) -> interaction.mouseSensitivityAdjustmentTarget = parent.mouseSensitivityAdjustmentTarget + ) + .documentation("What is the target modifier to apply to mouse sensitivity while this interaction is active.") + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .appendInherited( + new KeyedCodec<>("MouseSensitivityAdjustmentDuration", Codec.FLOAT), + (interaction, doubles) -> interaction.mouseSensitivityAdjustmentDuration = doubles, + interaction -> interaction.mouseSensitivityAdjustmentDuration, + (interaction, parent) -> interaction.mouseSensitivityAdjustmentDuration = parent.mouseSensitivityAdjustmentDuration + ) + .documentation("Override the global linear modifier adjustment with this as the time to go from 1.0 to 0.0.") + .add() + .build(); + protected Color entityBottomTint; + protected Color entityTopTint; + protected String entityAnimationId; + protected ModelParticle[] particles; + protected ModelParticle[] firstPersonParticles; + protected String screenEffect; + protected float horizontalSpeedMultiplier = 1.0F; + protected float knockbackMultiplier = 1.0F; + protected String soundEventIdLocal; + protected transient int soundEventIndexLocal = 0; + protected String soundEventIdWorld; + protected transient int soundEventIndexWorld = 0; + protected String modelVFXId; + protected MovementEffects movementEffects; + protected AbilityEffects abilityEffects; + private float mouseSensitivityAdjustmentTarget; + private float mouseSensitivityAdjustmentDuration; + + protected ApplicationEffects() { + } + + @Nonnull + public com.hypixel.hytale.protocol.ApplicationEffects toPacket() { + com.hypixel.hytale.protocol.ApplicationEffects packet = new com.hypixel.hytale.protocol.ApplicationEffects(); + packet.entityBottomTint = this.entityBottomTint; + packet.entityTopTint = this.entityTopTint; + packet.entityAnimationId = this.entityAnimationId; + if (this.particles != null && this.particles.length > 0) { + packet.particles = new com.hypixel.hytale.protocol.ModelParticle[this.particles.length]; + + for (int i = 0; i < this.particles.length; i++) { + packet.particles[i] = this.particles[i].toPacket(); + } + } + + if (this.firstPersonParticles != null && this.firstPersonParticles.length > 0) { + packet.firstPersonParticles = new com.hypixel.hytale.protocol.ModelParticle[this.firstPersonParticles.length]; + + for (int i = 0; i < this.firstPersonParticles.length; i++) { + packet.firstPersonParticles[i] = this.firstPersonParticles[i].toPacket(); + } + } + + packet.screenEffect = this.screenEffect; + packet.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + packet.soundEventIndexLocal = this.soundEventIndexLocal != 0 ? this.soundEventIndexLocal : this.soundEventIndexWorld; + packet.soundEventIndexWorld = this.soundEventIndexWorld; + packet.modelVFXId = this.modelVFXId; + if (this.movementEffects != null) { + packet.movementEffects = this.movementEffects.toPacket(); + } + + if (this.abilityEffects != null) { + packet.abilityEffects = this.abilityEffects.toPacket(); + } + + packet.mouseSensitivityAdjustmentDuration = this.mouseSensitivityAdjustmentDuration; + packet.mouseSensitivityAdjustmentTarget = this.mouseSensitivityAdjustmentTarget; + return packet; + } + + public float getHorizontalSpeedMultiplier() { + return this.horizontalSpeedMultiplier; + } + + public float getKnockbackMultiplier() { + return this.knockbackMultiplier; + } + + protected void processConfig() { + IndexedLookupTableAssetMap soundEventAssetMap = SoundEvent.getAssetMap(); + if (this.soundEventIdLocal != null) { + this.soundEventIndexLocal = soundEventAssetMap.getIndex(this.soundEventIdLocal); + } + + if (this.soundEventIdWorld != null) { + this.soundEventIndexWorld = soundEventAssetMap.getIndex(this.soundEventIdWorld); + } + } + + @Nonnull + @Override + public String toString() { + return "ApplicationEffects{entityBottomTint=" + + this.entityBottomTint + + ", entityTopTint=" + + this.entityTopTint + + ", entityAnimationId='" + + this.entityAnimationId + + "', particles=" + + Arrays.toString((Object[])this.particles) + + ", firstPersonParticles=" + + Arrays.toString((Object[])this.firstPersonParticles) + + ", screenEffect='" + + this.screenEffect + + "', horizontalSpeedMultiplier=" + + this.horizontalSpeedMultiplier + + ", soundEventIndexLocal=" + + this.soundEventIndexLocal + + ", soundEventIndexWorld=" + + this.soundEventIndexWorld + + ", movementEffects=" + + this.movementEffects + + ", abilityEffects=" + + this.abilityEffects + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/EntityEffect.java b/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/EntityEffect.java new file mode 100644 index 0000000..8caca2d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/EntityEffect.java @@ -0,0 +1,503 @@ +package com.hypixel.hytale.server.core.asset.type.entityeffect.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.codecs.map.Object2FloatMapCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.ValueType; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemArmor; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.StaticModifier; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.DamageCalculator; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.DamageEffects; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; +import java.lang.ref.SoftReference; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityEffect + implements JsonAssetWithMap>, + NetworkSerializable { + @Nonnull + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + EntityEffect.class, + EntityEffect::new, + Codec.STRING, + (entityEffect, s) -> entityEffect.id = s, + entityEffect -> entityEffect.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("Name", Codec.STRING), + (entityEffect, s) -> entityEffect.name = s, + entityEffect -> entityEffect.name, + (entityEffect, parent) -> entityEffect.name = parent.name + ) + .documentation("The name of this entity effect that will be displayed in the UI. This must be a localization key.") + .add() + .appendInherited( + new KeyedCodec<>("ApplicationEffects", ApplicationEffects.CODEC), + (entityEffect, s) -> entityEffect.applicationEffects = s, + entityEffect -> entityEffect.applicationEffects, + (entityEffect, parent) -> entityEffect.applicationEffects = parent.applicationEffects + ) + .add() + .appendInherited( + new KeyedCodec<>("WorldRemovalSoundEventId", Codec.STRING), + (entityEffect, s) -> entityEffect.worldRemovalSoundEventId = s, + entityEffect -> entityEffect.worldRemovalSoundEventId, + (entityEffect, parent) -> entityEffect.worldRemovalSoundEventId = parent.worldRemovalSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("LocalRemovalSoundEventId", Codec.STRING), + (entityEffect, s) -> entityEffect.localRemovalSoundEventId = s, + entityEffect -> entityEffect.localRemovalSoundEventId, + (entityEffect, parent) -> entityEffect.localRemovalSoundEventId = parent.localRemovalSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("DamageCalculator", DamageCalculator.CODEC), + (entityEffect, s) -> entityEffect.damageCalculator = s, + entityEffect -> entityEffect.damageCalculator, + (entityEffect, parent) -> entityEffect.damageCalculator = parent.damageCalculator + ) + .add() + .appendInherited( + new KeyedCodec<>("DamageCalculatorCooldown", Codec.FLOAT), + (entityEffect, s) -> entityEffect.damageCalculatorCooldown = s, + entityEffect -> entityEffect.damageCalculatorCooldown, + (entityEffect, parent) -> entityEffect.damageCalculatorCooldown = parent.damageCalculatorCooldown + ) + .addValidator(Validators.greaterThanOrEqual(0.0F)) + .add() + .appendInherited( + new KeyedCodec<>("DamageEffects", DamageEffects.CODEC), + (entityEffect, s) -> entityEffect.damageEffects = s, + entityEffect -> entityEffect.damageEffects, + (entityEffect, parent) -> entityEffect.damageEffects = parent.damageEffects + ) + .add() + .appendInherited( + new KeyedCodec<>("StatModifierEffects", DamageEffects.CODEC), + (entityEffect, s) -> entityEffect.statModifierEffects = s, + entityEffect -> entityEffect.statModifierEffects, + (entityEffect, parent) -> entityEffect.statModifierEffects = parent.statModifierEffects + ) + .documentation("Effects to play when stat modifiers are applied and updated.") + .add() + .appendInherited( + new KeyedCodec<>("ModelOverride", ModelOverride.CODEC), + (entityEffect, o) -> entityEffect.modelOverride = o, + entityEffect -> entityEffect.modelOverride, + (entityEffect, parent) -> entityEffect.modelOverride = parent.modelOverride + ) + .add() + .appendInherited( + new KeyedCodec<>("ModelChange", Codec.STRING), + (entityEffect, s) -> entityEffect.modelChange = s, + entityEffect -> entityEffect.modelChange, + (entityEffect, parent) -> entityEffect.modelChange = parent.modelChange + ) + .addValidator(ModelAsset.VALIDATOR_CACHE.getValidator()) + .documentation("A model to change the affected entity's appearance to.") + .add() + .append( + new KeyedCodec<>("RawStatModifiers", new MapCodec<>(new ArrayCodec<>(StaticModifier.CODEC, StaticModifier[]::new), HashMap::new)), + (entityEffect, map) -> entityEffect.rawStatModifiers = map, + entityEffect -> entityEffect.rawStatModifiers + ) + .addValidator(EntityStatType.VALIDATOR_CACHE.getMapKeyValidator().late()) + .add() + .>appendInherited( + new KeyedCodec<>("StatModifiers", new Object2FloatMapCodec<>(Codec.STRING, Object2FloatOpenHashMap::new)), + (entityEffect, o) -> entityEffect.unknownEntityStats = o, + entityEffect -> entityEffect.unknownEntityStats, + (entityEffect, parent) -> entityEffect.entityStats = parent.entityStats + ) + .addValidator(EntityStatType.VALIDATOR_CACHE.getMapKeyValidator()) + .documentation("Modifiers to apply to EntityStats.") + .add() + .appendInherited( + new KeyedCodec<>("ValueType", new EnumCodec<>(ValueType.class)), + (entityEffect, valueType) -> entityEffect.valueType = valueType, + entityEffect -> entityEffect.valueType, + (entityEffect, parent) -> entityEffect.valueType = parent.valueType + ) + .documentation( + "Enum to specify if the StatModifiers must be considered as absolute values or percent. Default value is Absolute. When using ValueType.Absolute, '100' matches the max value." + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Duration", Codec.FLOAT), + (entityEffect, o) -> entityEffect.duration = o, + entityEffect -> entityEffect.duration, + (entityEffect, parent) -> entityEffect.duration = parent.duration + ) + .documentation("Value used by default unless specified otherwise in the method's call.") + .add() + .appendInherited( + new KeyedCodec<>("OverlapBehavior", OverlapBehavior.CODEC), + (entityEffect, s) -> entityEffect.overlapBehavior = s, + entityEffect -> entityEffect.overlapBehavior, + (entityEffect, parent) -> entityEffect.overlapBehavior = parent.overlapBehavior + ) + .documentation("Value used by default unless specified otherwise in the method's call.") + .add() + .appendInherited( + new KeyedCodec<>("Infinite", Codec.BOOLEAN), + (entityEffect, aBoolean) -> entityEffect.infinite = aBoolean, + entityEffect -> entityEffect.infinite, + (entityEffect, parent) -> entityEffect.infinite = parent.infinite + ) + .documentation("Value used by default unless specified otherwise in the method's call.") + .add() + .appendInherited( + new KeyedCodec<>("Debuff", Codec.BOOLEAN), + (entityEffect, aBoolean) -> entityEffect.debuff = aBoolean, + entityEffect -> entityEffect.debuff, + (entityEffect, parent) -> entityEffect.debuff = parent.debuff + ) + .documentation("Value used by default unless specified otherwise in the method's call.") + .add() + .appendInherited( + new KeyedCodec<>("Locale", Codec.STRING), + (entityEffect, aString) -> entityEffect.locale = aString, + entityEffect -> entityEffect.locale, + (entityEffect, parent) -> entityEffect.locale = parent.locale + ) + .documentation("An optional translation key, used to display the damage cause upon death.") + .add() + .appendInherited( + new KeyedCodec<>("StatusEffectIcon", Codec.STRING), + (entityEffect, aString) -> entityEffect.statusEffectIcon = aString, + entityEffect -> entityEffect.statusEffectIcon, + (entityEffect, parent) -> entityEffect.statusEffectIcon = parent.statusEffectIcon + ) + .documentation("Value used by default unless specified otherwise in the method's call.") + .add() + .appendInherited( + new KeyedCodec<>("RemovalBehavior", RemovalBehavior.CODEC), + (entityEffect, removalBehavior) -> entityEffect.removalBehavior = removalBehavior, + entityEffect -> entityEffect.removalBehavior, + (entityEffect, parent) -> entityEffect.removalBehavior = parent.removalBehavior + ) + .documentation("Value used by default unless specified otherwise in the method's call.") + .add() + .appendInherited( + new KeyedCodec<>("Invulnerable", Codec.BOOLEAN), + (entityEffect, aBoolean) -> entityEffect.invulnerable = aBoolean, + entityEffect -> entityEffect.invulnerable, + (entityEffect, parent) -> entityEffect.invulnerable = parent.invulnerable + ) + .documentation("Determines whether this effect applies the invulnerable component to the entity whilst active.") + .add() + .appendInherited( + new KeyedCodec<>("DamageResistance", new MapCodec<>(new ArrayCodec<>(StaticModifier.CODEC, StaticModifier[]::new), HashMap::new)), + (entityEffect, map) -> entityEffect.damageResistanceValuesRaw = map, + entityEffect -> entityEffect.damageResistanceValuesRaw, + (entityEffect, parent) -> entityEffect.damageResistanceValuesRaw = parent.damageResistanceValuesRaw + ) + .addValidator(DamageCause.VALIDATOR_CACHE.getMapKeyValidator()) + .add() + .afterDecode(entityEffect -> { + entityEffect.entityStats = EntityStatsModule.resolveEntityStats(entityEffect.unknownEntityStats); + entityEffect.statModifiers = EntityStatsModule.resolveEntityStats(entityEffect.rawStatModifiers); + if (entityEffect.damageResistanceValuesRaw != null && !entityEffect.damageResistanceValuesRaw.isEmpty()) { + entityEffect.damageResistanceValues = ItemArmor.convertStringKeyToDamageCause(entityEffect.damageResistanceValuesRaw); + } + + if (entityEffect.worldRemovalSoundEventId != null) { + entityEffect.worldRemovalSoundEventIndex = SoundEvent.getAssetMap().getIndex(entityEffect.worldRemovalSoundEventId); + } + + if (entityEffect.localRemovalSoundEventId != null) { + entityEffect.localRemovalSoundEventIndex = SoundEvent.getAssetMap().getIndex(entityEffect.localRemovalSoundEventId); + } + }) + .build(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(EntityEffect.class, CODEC); + @Nullable + private static AssetStore> STORE; + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(EntityEffect::getAssetStore)); + protected AssetExtraInfo.Data data; + protected String id; + @Nullable + protected String name; + @Nullable + protected ApplicationEffects applicationEffects; + @Nullable + protected String worldRemovalSoundEventId; + protected transient int worldRemovalSoundEventIndex = 0; + @Nullable + protected String localRemovalSoundEventId; + protected transient int localRemovalSoundEventIndex = 0; + @Nullable + protected DamageCalculator damageCalculator; + protected float damageCalculatorCooldown; + @Nullable + protected DamageEffects damageEffects; + @Nullable + protected DamageEffects statModifierEffects; + @Nullable + protected ModelOverride modelOverride; + @Nullable + protected String modelChange; + @Nullable + protected Object2FloatMap unknownEntityStats; + @Nullable + protected Int2FloatMap entityStats; + @Nonnull + protected ValueType valueType = ValueType.Absolute; + protected float duration = 0.0F; + @Nonnull + protected OverlapBehavior overlapBehavior = OverlapBehavior.IGNORE; + @Nonnull + protected RemovalBehavior removalBehavior = RemovalBehavior.COMPLETE; + protected boolean infinite; + protected boolean debuff; + @Nullable + protected String statusEffectIcon; + @Nullable + protected String locale; + protected boolean invulnerable = false; + @Nullable + protected Map rawStatModifiers; + @Nullable + protected Int2ObjectMap statModifiers; + @Nullable + protected Map damageResistanceValuesRaw; + @Nullable + protected Map damageResistanceValues; + @Nullable + private SoftReference cachedPacket; + + @Nonnull + public static AssetStore> getAssetStore() { + if (STORE == null) { + STORE = AssetRegistry.getAssetStore(EntityEffect.class); + } + + return STORE; + } + + @Nonnull + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public EntityEffect(@Nonnull String id) { + this.id = id; + } + + protected EntityEffect() { + } + + @Nullable + public String getName() { + return this.name; + } + + @Nullable + public Int2ObjectMap getStatModifiers() { + return this.statModifiers; + } + + public String getId() { + return this.id; + } + + @Nullable + public ApplicationEffects getApplicationEffects() { + return this.applicationEffects; + } + + @Nullable + public DamageCalculator getDamageCalculator() { + return this.damageCalculator; + } + + public float getDamageCalculatorCooldown() { + return this.damageCalculatorCooldown; + } + + @Nullable + public DamageEffects getDamageEffects() { + return this.damageEffects; + } + + @Nullable + public DamageEffects getStatModifierEffects() { + return this.statModifierEffects; + } + + @Nullable + public ModelOverride getModelOverride() { + return this.modelOverride; + } + + @Nullable + public String getModelChange() { + return this.modelChange; + } + + @Nullable + public Int2FloatMap getEntityStats() { + return this.entityStats; + } + + public float getDuration() { + return this.duration; + } + + @Nonnull + public OverlapBehavior getOverlapBehavior() { + return this.overlapBehavior; + } + + public boolean isInfinite() { + return this.infinite; + } + + public boolean isDebuff() { + return this.debuff; + } + + @Nullable + public String getStatusEffectIcon() { + return this.statusEffectIcon; + } + + @Nullable + public String getLocale() { + return this.locale; + } + + @Nonnull + public RemovalBehavior getRemovalBehavior() { + return this.removalBehavior; + } + + @Nonnull + public ValueType getValueType() { + return this.valueType; + } + + public boolean isInvulnerable() { + return this.invulnerable; + } + + @Nullable + public Map getDamageResistanceValues() { + return this.damageResistanceValues; + } + + @Nonnull + public com.hypixel.hytale.protocol.EntityEffect toPacket() { + com.hypixel.hytale.protocol.EntityEffect cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.EntityEffect packet = new com.hypixel.hytale.protocol.EntityEffect(); + packet.id = this.id; + packet.name = this.name; + if (this.applicationEffects != null) { + packet.applicationEffects = this.applicationEffects.toPacket(); + } + + packet.worldRemovalSoundEventIndex = this.worldRemovalSoundEventIndex; + packet.localRemovalSoundEventIndex = this.localRemovalSoundEventIndex != 0 ? this.localRemovalSoundEventIndex : this.worldRemovalSoundEventIndex; + if (this.modelOverride != null) { + packet.modelOverride = this.modelOverride.toPacket(); + } + + packet.duration = this.duration; + packet.infinite = this.infinite; + packet.debuff = this.debuff; + packet.statusEffectIcon = this.statusEffectIcon; + + packet.overlapBehavior = switch (this.overlapBehavior) { + case EXTEND -> com.hypixel.hytale.protocol.OverlapBehavior.Extend; + case OVERWRITE -> com.hypixel.hytale.protocol.OverlapBehavior.Overwrite; + case IGNORE -> com.hypixel.hytale.protocol.OverlapBehavior.Ignore; + }; + packet.damageCalculatorCooldown = this.damageCalculatorCooldown; + packet.statModifiers = this.entityStats; + packet.valueType = this.valueType; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + @Nonnull + @Override + public String toString() { + return "EntityEffect{id='" + + this.id + + "', name=" + + this.name + + ", applicationEffects=" + + this.applicationEffects + + ", damageCalculator=" + + this.damageCalculator + + ", damageCalculatorCooldown=" + + this.damageCalculatorCooldown + + ", damageEffects=" + + this.damageEffects + + ", modelOverride=" + + this.modelOverride + + ", modelChange='" + + this.modelChange + + "', unknownEntityStats=" + + this.unknownEntityStats + + ", entityStats=" + + this.entityStats + + ", valueType=" + + this.valueType + + ", duration=" + + this.duration + + ", overlapBehavior=" + + this.overlapBehavior + + ", infinite=" + + this.infinite + + ", debuff=" + + this.debuff + + ", statusEffectIcon=" + + this.statusEffectIcon + + ", locale=" + + this.locale + + ", removalBehavior=" + + this.removalBehavior + + ", invulnerable=" + + this.invulnerable + + ", damageResistanceValues=" + + this.damageResistanceValues + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/ModelOverride.java b/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/ModelOverride.java new file mode 100644 index 0000000..d6463e6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/ModelOverride.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.server.core.asset.type.entityeffect.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.protocol.AnimationSet; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ModelOverride implements NetworkSerializable { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(ModelOverride.class, ModelOverride::new) + .appendInherited( + new KeyedCodec<>("Model", Codec.STRING, true), + (modelOverride, s) -> modelOverride.model = s, + modelOverride -> modelOverride.model, + (modelOverride, parent) -> modelOverride.model = parent.model + ) + .addValidator(CommonAssetValidator.MODEL_CHARACTER) + .add() + .appendInherited( + new KeyedCodec<>("Texture", Codec.STRING, true), + (modelOverride, s) -> modelOverride.texture = s, + modelOverride -> modelOverride.texture, + (modelOverride, parent) -> modelOverride.texture = parent.texture + ) + .addValidator(CommonAssetValidator.TEXTURE_CHARACTER) + .add() + .appendInherited( + new KeyedCodec<>("AnimationSets", new MapCodec<>(ModelAsset.AnimationSet.CODEC, HashMap::new)), + (modelOverride, m) -> modelOverride.animationSetMap = m, + modelOverride -> modelOverride.animationSetMap, + (modelOverride, parent) -> modelOverride.animationSetMap = parent.animationSetMap + ) + .add() + .build(); + @Nullable + protected String model; + @Nullable + protected String texture; + @Nonnull + protected Map animationSetMap = Collections.emptyMap(); + + protected ModelOverride() { + } + + @Nonnull + public com.hypixel.hytale.protocol.ModelOverride toPacket() { + com.hypixel.hytale.protocol.ModelOverride packet = new com.hypixel.hytale.protocol.ModelOverride(); + packet.model = this.model; + packet.texture = this.texture; + if (!this.animationSetMap.isEmpty()) { + Map map = new Object2ObjectOpenHashMap<>(this.animationSetMap.size()); + + for (Entry entry : this.animationSetMap.entrySet()) { + map.put(entry.getKey(), entry.getValue().toPacket(entry.getKey())); + } + + packet.animationSets = map; + } + + return packet; + } + + @Nonnull + @Override + public String toString() { + return "ModelOverride{model='" + this.model + "', texture='" + this.texture + "', animationSetMap=" + this.animationSetMap + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/OverlapBehavior.java b/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/OverlapBehavior.java new file mode 100644 index 0000000..f194280 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/OverlapBehavior.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.server.core.asset.type.entityeffect.config; + +import com.hypixel.hytale.codec.codecs.EnumCodec; +import javax.annotation.Nonnull; + +public enum OverlapBehavior { + EXTEND, + OVERWRITE, + IGNORE; + + @Nonnull + public static final EnumCodec CODEC = new EnumCodec<>(OverlapBehavior.class); + + private OverlapBehavior() { + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/RemovalBehavior.java b/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/RemovalBehavior.java new file mode 100644 index 0000000..e44862a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/entityeffect/config/RemovalBehavior.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.server.core.asset.type.entityeffect.config; + +import com.hypixel.hytale.codec.codecs.EnumCodec; +import javax.annotation.Nonnull; + +public enum RemovalBehavior { + COMPLETE, + INFINITE, + DURATION; + + @Nonnull + public static final EnumCodec CODEC = new EnumCodec<>(RemovalBehavior.class); + + private RemovalBehavior() { + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/environment/EnvironmentPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/environment/EnvironmentPacketGenerator.java new file mode 100644 index 0000000..4b209ef --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/environment/EnvironmentPacketGenerator.java @@ -0,0 +1,89 @@ +package com.hypixel.hytale.server.core.asset.type.environment; + +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateEnvironments; +import com.hypixel.hytale.server.core.asset.packet.AssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class EnvironmentPacketGenerator extends AssetPacketGenerator> { + public EnvironmentPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + Map assetsFromMap = assetMap.getAssetMap(); + if (assets.size() != assetsFromMap.size()) { + throw new UnsupportedOperationException("Environments can not handle partial init packets!!!"); + } else { + UpdateEnvironments packet = new UpdateEnvironments(); + packet.type = UpdateType.Init; + packet.environments = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.environments.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + packet.rebuildMapGeometry = true; + return packet; + } + } + + @Nonnull + public Packet generateUpdatePacket( + @Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets, @Nonnull AssetUpdateQuery query + ) { + UpdateEnvironments packet = new UpdateEnvironments(); + packet.type = UpdateType.AddOrUpdate; + packet.environments = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.environments.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + packet.rebuildMapGeometry = query.getRebuildCache().isMapGeometry(); + return packet; + } + + @Nonnull + public Packet generateRemovePacket( + @Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed, @Nonnull AssetUpdateQuery query + ) { + UpdateEnvironments packet = new UpdateEnvironments(); + packet.type = UpdateType.Remove; + packet.environments = new Int2ObjectOpenHashMap<>(); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.environments.put(index, null); + } + + packet.rebuildMapGeometry = query.getRebuildCache().isMapGeometry(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/environment/config/Environment.java b/src/com/hypixel/hytale/server/core/asset/type/environment/config/Environment.java new file mode 100644 index 0000000..4a6a32a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/environment/config/Environment.java @@ -0,0 +1,288 @@ +package com.hypixel.hytale.server.core.asset.type.environment.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.map.Int2ObjectMapCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDefaultCollapsedState; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorFeatures; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorSectionStart; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.codec.validation.validator.MapKeyValidator; +import com.hypixel.hytale.common.map.IWeightedMap; +import com.hypixel.hytale.common.map.WeightedMap; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.WorldEnvironment; +import com.hypixel.hytale.server.core.asset.type.fluidfx.config.FluidFX; +import com.hypixel.hytale.server.core.asset.type.fluidfx.config.FluidParticle; +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.codec.WeightedMapCodec; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.lang.ref.SoftReference; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Environment implements JsonAssetWithMap>, NetworkSerializable { + public static final int HOURS_PER_DAY = (int)ChronoUnit.DAYS.getDuration().toHours(); + public static final int MAX_KEY_HOUR = HOURS_PER_DAY - 1; + public static final Integer[] HOURS = new Integer[HOURS_PER_DAY]; + @Nonnull + private static final IWeightedMap DEFAULT_WEATHER_FORECAST; + public static final AssetBuilderCodec CODEC; + public static final ValidatorCache VALIDATOR_CACHE; + private static AssetStore> ASSET_STORE; + public static final int UNKNOWN_ID = 0; + public static final Environment UNKNOWN; + protected AssetExtraInfo.Data data; + protected String id; + protected Color waterTint; + protected Map fluidParticles = Collections.emptyMap(); + protected Int2ObjectMap> weatherForecasts; + protected double spawnDensity; + protected boolean blockModificationAllowed = true; + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(Environment.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public Environment( + String id, Color waterTint, Map fluidParticles, Int2ObjectMap> weatherForecasts, double spawnDensity + ) { + this.id = id; + this.waterTint = waterTint; + this.fluidParticles = fluidParticles; + this.weatherForecasts = weatherForecasts; + this.spawnDensity = spawnDensity; + } + + protected Environment() { + } + + public String getId() { + return this.id; + } + + public Color getWaterTint() { + return this.waterTint; + } + + public Map getFluidParticles() { + return this.fluidParticles; + } + + public Int2ObjectMap> getWeatherForecasts() { + return this.weatherForecasts; + } + + public IWeightedMap getWeatherForecast(int hour) { + if (hour < 0 || hour > MAX_KEY_HOUR) { + throw new IllegalArgumentException("hour must be in range of 0 to " + MAX_KEY_HOUR); + } else { + return this.weatherForecasts == null ? DEFAULT_WEATHER_FORECAST : this.weatherForecasts.getOrDefault(hour, DEFAULT_WEATHER_FORECAST); + } + } + + public double getSpawnDensity() { + return this.spawnDensity; + } + + public boolean isBlockModificationAllowed() { + return this.blockModificationAllowed; + } + + @Nonnull + public WorldEnvironment toPacket() { + WorldEnvironment cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + WorldEnvironment packet = new WorldEnvironment(); + packet.id = this.id; + if (this.waterTint != null) { + packet.waterTint = this.waterTint; + } + + if (!this.fluidParticles.isEmpty()) { + Map map = new Int2ObjectOpenHashMap<>(this.fluidParticles.size()); + + for (Entry entry : this.fluidParticles.entrySet()) { + int index = FluidFX.getAssetMap().getIndex(entry.getKey()); + if (index != Integer.MIN_VALUE) { + map.put(index, entry.getValue().toPacket()); + } + } + + packet.fluidParticles = map; + } + + if (this.data != null) { + IntSet tags = this.data.getExpandedTagIndexes(); + packet.tagIndexes = tags.toIntArray(); + } + + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Environment that = (Environment)o; + return this.id.equals(that.id); + } else { + return false; + } + } + + @Override + public int hashCode() { + return this.id.hashCode(); + } + + @Nonnull + @Override + public String toString() { + return "Environment{id='" + + this.id + + "', waterTint='" + + this.waterTint + + "', fluidParticles='" + + this.fluidParticles + + "', weatherForecasts=" + + this.weatherForecasts + + ", spawnDensity=" + + this.spawnDensity + + ", packet=" + + this.cachedPacket + + "}"; + } + + @Nonnull + public static Environment getUnknownFor(final String unknownId) { + return new Environment() { + { + this.id = unknownId; + this.waterTint = new Color((byte)10, (byte)51, (byte)85); + this.spawnDensity = 0.175; + } + }; + } + + public static int getIndexOrUnknown(String id, String message, Object... params) { + int environmentIndex = getAssetMap().getIndex(id); + if (environmentIndex == Integer.MIN_VALUE) { + HytaleLogger.getLogger().at(Level.WARNING).logVarargs(message, params); + getAssetStore().loadAssets("Hytale:Hytale", Collections.singletonList(getUnknownFor(id))); + int index = getAssetMap().getIndex(id); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + id); + } + + environmentIndex = index; + } + + return environmentIndex; + } + + static { + WeightedMap.Builder mapBuilder = WeightedMap.builder(WeatherForecast.EMPTY_ARRAY); + mapBuilder.put(new WeatherForecast(Weather.UNKNOWN.getId(), 1.0), 1.0); + DEFAULT_WEATHER_FORECAST = mapBuilder.build(); + + for (int i = 0; i < HOURS_PER_DAY; i++) { + HOURS[i] = i; + } + + CODEC = AssetBuilderCodec.builder( + Environment.class, + Environment::new, + Codec.STRING, + (environment, s) -> environment.id = s, + environment -> environment.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .metadata(new UIEditorFeatures(UIEditorFeatures.EditorFeature.WEATHER_DAYTIME_BAR)) + .appendInherited( + new KeyedCodec<>("WaterTint", ProtocolCodecs.COLOR), + (environment, s) -> environment.waterTint = s, + environment -> environment.waterTint, + (environment, parent) -> environment.waterTint = parent.waterTint + ) + .add() + .appendInherited( + new KeyedCodec<>("FluidParticles", new MapCodec<>(FluidParticle.CODEC, HashMap::new)), + (environment, s) -> environment.fluidParticles = s, + environment -> environment.fluidParticles, + (environment, parent) -> environment.fluidParticles = parent.fluidParticles + ) + .addValidator(FluidFX.VALIDATOR_CACHE.getMapKeyValidator()) + .add() + .appendInherited( + new KeyedCodec<>("SpawnDensity", Codec.DOUBLE), + (environment, d) -> environment.spawnDensity = d, + environment -> environment.spawnDensity, + (environment, parent) -> environment.spawnDensity = parent.spawnDensity + ) + .add() + .appendInherited( + new KeyedCodec<>("BlockModificationAllowed", Codec.BOOLEAN), + (environment, b) -> environment.blockModificationAllowed = b, + environment -> environment.blockModificationAllowed, + (environment, parent) -> environment.blockModificationAllowed = parent.blockModificationAllowed + ) + .add() + .>>appendInherited( + new KeyedCodec<>( + "WeatherForecasts", + new Int2ObjectMapCodec<>(new WeightedMapCodec<>(WeatherForecast.CODEC, WeatherForecast.EMPTY_ARRAY), Int2ObjectOpenHashMap::new), + true + ), + (environment, l) -> environment.weatherForecasts = l, + environment -> environment.weatherForecasts, + (environment, parent) -> environment.weatherForecasts = parent.weatherForecasts + ) + .addValidator(Validators.nonNull()) + .addValidator(new MapKeyValidator<>(Validators.range(0, MAX_KEY_HOUR))) + .addValidator(Validators.requiredMapKeysValidator(HOURS)) + .metadata(new UIEditor(UIEditor.WEIGHTED_TIMELINE)) + .metadata(new UIEditorSectionStart("Weather")) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .build(); + VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(Environment::getAssetStore)); + UNKNOWN = getUnknownFor("Unknown"); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/environment/config/WeatherForecast.java b/src/com/hypixel/hytale/server/core/asset/type/environment/config/WeatherForecast.java new file mode 100644 index 0000000..e697917 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/environment/config/WeatherForecast.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.server.core.asset.type.environment.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.map.IWeightedElement; +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import javax.annotation.Nonnull; + +public class WeatherForecast implements IWeightedElement { + public static final BuilderCodec CODEC = BuilderCodec.builder(WeatherForecast.class, WeatherForecast::new) + .append(new KeyedCodec<>("WeatherId", Codec.STRING), (weatherForecast, s) -> weatherForecast.weatherId = s, weatherForecast -> weatherForecast.weatherId) + .addValidator(Validators.nonNull()) + .addValidator(Weather.VALIDATOR_CACHE.getValidator()) + .add() + .addField(new KeyedCodec<>("Weight", Codec.DOUBLE, true), (spawn, s) -> spawn.weight = s, spawn -> spawn.weight) + .afterDecode(WeatherForecast::processConfig) + .build(); + public static final WeatherForecast[] EMPTY_ARRAY = new WeatherForecast[0]; + protected String weatherId; + protected transient int weatherIndex; + protected double weight; + + public WeatherForecast(String weatherId, double weight) { + this.weatherId = weatherId; + this.weight = weight; + } + + protected WeatherForecast() { + } + + public String getWeatherId() { + return this.weatherId; + } + + public int getWeatherIndex() { + return this.weatherIndex; + } + + protected void processConfig() { + this.weatherIndex = Weather.getAssetMap().getIndex(this.weatherId); + } + + @Nonnull + @Override + public String toString() { + return "WeatherForecast{weatherId='" + this.weatherId + "', weatherIndex=" + this.weatherIndex + ", weight=" + this.weight + "}"; + } + + @Override + public double getWeight() { + return this.weight; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/equalizereffect/EqualizerEffectPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/equalizereffect/EqualizerEffectPacketGenerator.java new file mode 100644 index 0000000..1510243 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/equalizereffect/EqualizerEffectPacketGenerator.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.asset.type.equalizereffect; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateEqualizerEffects; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.equalizereffect.config.EqualizerEffect; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class EqualizerEffectPacketGenerator extends SimpleAssetPacketGenerator> { + public EqualizerEffectPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + UpdateEqualizerEffects packet = new UpdateEqualizerEffects(); + packet.type = UpdateType.Init; + packet.effects = new Int2ObjectOpenHashMap<>(assets.size()); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.effects.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateUpdatePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets) { + UpdateEqualizerEffects packet = new UpdateEqualizerEffects(); + packet.type = UpdateType.AddOrUpdate; + packet.effects = new Int2ObjectOpenHashMap<>(loadedAssets.size()); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.effects.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateRemovePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed) { + UpdateEqualizerEffects packet = new UpdateEqualizerEffects(); + packet.type = UpdateType.Remove; + packet.effects = new Int2ObjectOpenHashMap<>(removed.size()); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.effects.put(index, null); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/equalizereffect/config/EqualizerEffect.java b/src/com/hypixel/hytale/server/core/asset/type/equalizereffect/config/EqualizerEffect.java new file mode 100644 index 0000000..cf404e4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/equalizereffect/config/EqualizerEffect.java @@ -0,0 +1,259 @@ +package com.hypixel.hytale.server.core.asset.type.equalizereffect.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorPreview; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.AudioUtil; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import javax.annotation.Nonnull; + +public class EqualizerEffect + implements JsonAssetWithMap>, + NetworkSerializable { + public static final int EMPTY_ID = 0; + public static final String EMPTY = "EMPTY"; + public static final EqualizerEffect EMPTY_EQUALIZER_EFFECT = new EqualizerEffect("EMPTY"); + public static final float MIN_GAIN_DB = -18.0F; + public static final float MAX_GAIN_DB = 18.0F; + public static final float MIN_WIDTH = 0.01F; + public static final float MAX_WIDTH = 1.0F; + public static final float LOW_FREQ_MIN = 50.0F; + public static final float LOW_FREQ_MAX = 800.0F; + public static final float LOW_MID_FREQ_MIN = 200.0F; + public static final float LOW_MID_FREQ_MAX = 3000.0F; + public static final float HIGH_MID_FREQ_MIN = 1000.0F; + public static final float HIGH_MID_FREQ_MAX = 8000.0F; + public static final float HIGH_FREQ_MIN = 4000.0F; + public static final float HIGH_FREQ_MAX = 16000.0F; + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + EqualizerEffect.class, EqualizerEffect::new, Codec.STRING, (t, k) -> t.id = k, t -> t.id, (asset, data) -> asset.data = data, asset -> asset.data + ) + .documentation("An asset used to define a 4-band equalizer audio effect.") + .metadata(new UIEditorPreview(UIEditorPreview.PreviewType.EQUALIZER_EFFECT)) + .appendInherited( + new KeyedCodec<>("LowGain", Codec.FLOAT), + (eq, f) -> eq.lowGain = AudioUtil.decibelsToLinearGain(f), + eq -> AudioUtil.linearGainToDecibels(eq.lowGain), + (eq, parent) -> eq.lowGain = parent.lowGain + ) + .metadata(new UIEditor(new UIEditor.FormattedNumber(null, " dB", null))) + .addValidator(Validators.range(-18.0F, 18.0F)) + .documentation("Low band gain in decibels.") + .add() + .appendInherited( + new KeyedCodec<>("LowCutOff", Codec.FLOAT), (eq, f) -> eq.lowCutOff = f, eq -> eq.lowCutOff, (eq, parent) -> eq.lowCutOff = parent.lowCutOff + ) + .addValidator(Validators.range(50.0F, 800.0F)) + .documentation("Low band cutoff frequency in Hz.") + .add() + .appendInherited( + new KeyedCodec<>("LowMidGain", Codec.FLOAT), + (eq, f) -> eq.lowMidGain = AudioUtil.decibelsToLinearGain(f), + eq -> AudioUtil.linearGainToDecibels(eq.lowMidGain), + (eq, parent) -> eq.lowMidGain = parent.lowMidGain + ) + .metadata(new UIEditor(new UIEditor.FormattedNumber(null, " dB", null))) + .addValidator(Validators.range(-18.0F, 18.0F)) + .documentation("Low-mid band gain in decibels.") + .add() + .appendInherited( + new KeyedCodec<>("LowMidCenter", Codec.FLOAT), + (eq, f) -> eq.lowMidCenter = f, + eq -> eq.lowMidCenter, + (eq, parent) -> eq.lowMidCenter = parent.lowMidCenter + ) + .addValidator(Validators.range(200.0F, 3000.0F)) + .documentation("Low-mid band center frequency in Hz.") + .add() + .appendInherited( + new KeyedCodec<>("LowMidWidth", Codec.FLOAT), (eq, f) -> eq.lowMidWidth = f, eq -> eq.lowMidWidth, (eq, parent) -> eq.lowMidWidth = parent.lowMidWidth + ) + .addValidator(Validators.range(0.01F, 1.0F)) + .documentation("Low-mid band width.") + .add() + .appendInherited( + new KeyedCodec<>("HighMidGain", Codec.FLOAT), + (eq, f) -> eq.highMidGain = AudioUtil.decibelsToLinearGain(f), + eq -> AudioUtil.linearGainToDecibels(eq.highMidGain), + (eq, parent) -> eq.highMidGain = parent.highMidGain + ) + .metadata(new UIEditor(new UIEditor.FormattedNumber(null, " dB", null))) + .addValidator(Validators.range(-18.0F, 18.0F)) + .documentation("High-mid band gain in decibels.") + .add() + .appendInherited( + new KeyedCodec<>("HighMidCenter", Codec.FLOAT), + (eq, f) -> eq.highMidCenter = f, + eq -> eq.highMidCenter, + (eq, parent) -> eq.highMidCenter = parent.highMidCenter + ) + .addValidator(Validators.range(1000.0F, 8000.0F)) + .documentation("High-mid band center frequency in Hz.") + .add() + .appendInherited( + new KeyedCodec<>("HighMidWidth", Codec.FLOAT), + (eq, f) -> eq.highMidWidth = f, + eq -> eq.highMidWidth, + (eq, parent) -> eq.highMidWidth = parent.highMidWidth + ) + .addValidator(Validators.range(0.01F, 1.0F)) + .documentation("High-mid band width.") + .add() + .appendInherited( + new KeyedCodec<>("HighGain", Codec.FLOAT), + (eq, f) -> eq.highGain = AudioUtil.decibelsToLinearGain(f), + eq -> AudioUtil.linearGainToDecibels(eq.highGain), + (eq, parent) -> eq.highGain = parent.highGain + ) + .metadata(new UIEditor(new UIEditor.FormattedNumber(null, " dB", null))) + .addValidator(Validators.range(-18.0F, 18.0F)) + .documentation("High band gain in decibels.") + .add() + .appendInherited( + new KeyedCodec<>("HighCutOff", Codec.FLOAT), (eq, f) -> eq.highCutOff = f, eq -> eq.highCutOff, (eq, parent) -> eq.highCutOff = parent.highCutOff + ) + .addValidator(Validators.range(4000.0F, 16000.0F)) + .documentation("High band cutoff frequency in Hz.") + .add() + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(EqualizerEffect::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected float lowGain = 1.0F; + protected float lowCutOff = 200.0F; + protected float lowMidGain = 1.0F; + protected float lowMidCenter = 500.0F; + protected float lowMidWidth = 1.0F; + protected float highMidGain = 1.0F; + protected float highMidCenter = 3000.0F; + protected float highMidWidth = 1.0F; + protected float highGain = 1.0F; + protected float highCutOff = 6000.0F; + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(EqualizerEffect.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public EqualizerEffect(String id) { + this.id = id; + } + + protected EqualizerEffect() { + } + + public String getId() { + return this.id; + } + + public float getLowGain() { + return this.lowGain; + } + + public float getLowCutOff() { + return this.lowCutOff; + } + + public float getLowMidGain() { + return this.lowMidGain; + } + + public float getLowMidCenter() { + return this.lowMidCenter; + } + + public float getLowMidWidth() { + return this.lowMidWidth; + } + + public float getHighMidGain() { + return this.highMidGain; + } + + public float getHighMidCenter() { + return this.highMidCenter; + } + + public float getHighMidWidth() { + return this.highMidWidth; + } + + public float getHighGain() { + return this.highGain; + } + + public float getHighCutOff() { + return this.highCutOff; + } + + @Nonnull + @Override + public String toString() { + return "EqualizerEffect{id='" + + this.id + + "', lowGain=" + + this.lowGain + + ", lowCutOff=" + + this.lowCutOff + + ", lowMidGain=" + + this.lowMidGain + + ", lowMidCenter=" + + this.lowMidCenter + + ", lowMidWidth=" + + this.lowMidWidth + + ", highMidGain=" + + this.highMidGain + + ", highMidCenter=" + + this.highMidCenter + + ", highMidWidth=" + + this.highMidWidth + + ", highGain=" + + this.highGain + + ", highCutOff=" + + this.highCutOff + + "}"; + } + + @Nonnull + public com.hypixel.hytale.protocol.EqualizerEffect toPacket() { + com.hypixel.hytale.protocol.EqualizerEffect cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.EqualizerEffect packet = new com.hypixel.hytale.protocol.EqualizerEffect(); + packet.id = this.id; + packet.lowGain = this.lowGain; + packet.lowCutOff = this.lowCutOff; + packet.lowMidGain = this.lowMidGain; + packet.lowMidCenter = this.lowMidCenter; + packet.lowMidWidth = this.lowMidWidth; + packet.highMidGain = this.highMidGain; + packet.highMidCenter = this.highMidCenter; + packet.highMidWidth = this.highMidWidth; + packet.highGain = this.highGain; + packet.highCutOff = this.highCutOff; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/fluid/DefaultFluidTicker.java b/src/com/hypixel/hytale/server/core/asset/type/fluid/DefaultFluidTicker.java new file mode 100644 index 0000000..5ac48cc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/fluid/DefaultFluidTicker.java @@ -0,0 +1,280 @@ +package com.hypixel.hytale.server.core.asset.type.fluid; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.common.util.MapUtil; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DefaultFluidTicker extends FluidTicker { + public static final BuilderCodec CODEC = BuilderCodec.builder(DefaultFluidTicker.class, DefaultFluidTicker::new, BASE_CODEC) + .appendInherited( + new KeyedCodec<>("SpreadFluid", Codec.STRING), + (ticker, o) -> ticker.spreadFluid = o, + ticker -> ticker.spreadFluid, + (ticker, parent) -> ticker.spreadFluid = parent.spreadFluid + ) + .addValidator(Fluid.VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("Collisions", new MapCodec<>(DefaultFluidTicker.FluidCollisionConfig.CODEC, HashMap::new)), + (ticker, o) -> ticker.rawCollisionMap = MapUtil.combineUnmodifiable(ticker.rawCollisionMap, o), + ticker -> ticker.rawCollisionMap, + (ticker, parent) -> ticker.rawCollisionMap = parent.rawCollisionMap + ) + .documentation("Defines what happens when this fluid tries to spread into another fluid") + .add() + .build(); + private static final int MAX_DROP_DISTANCE = 5; + public static final DefaultFluidTicker INSTANCE = new DefaultFluidTicker(); + private String spreadFluid; + private int spreadFluidId; + private Map rawCollisionMap = Collections.emptyMap(); + @Nullable + private transient Int2ObjectMap collisionMap = null; + + public DefaultFluidTicker() { + } + + @Nonnull + @Override + protected BlockTickStrategy spread( + @Nonnull World world, + long tick, + @Nonnull FluidTicker.Accessor accessor, + @Nonnull FluidSection fluidSection, + BlockSection blockSection, + @Nonnull Fluid fluid, + int fluidId, + byte fluidLevel, + int worldX, + int worldY, + int worldZ + ) { + if (worldY == 0) { + return BlockTickStrategy.SLEEP; + } else { + BlockTypeAssetMap blockMap = BlockType.getAssetMap(); + IndexedLookupTableAssetMap fluidMap = Fluid.getAssetMap(); + boolean isDifferentSectionBelow = fluidSection.getY() != ChunkUtil.chunkCoordinate(worldY - 1); + FluidSection fluidSectionBelow = isDifferentSectionBelow ? accessor.getFluidSectionByBlock(worldX, worldY - 1, worldZ) : fluidSection; + BlockSection blockSectionBelow = isDifferentSectionBelow ? accessor.getBlockSectionByBlock(worldX, worldY - 1, worldZ) : blockSection; + if (fluidSectionBelow != null && blockSectionBelow != null) { + int fluidBelowId = fluidSectionBelow.getFluidId(worldX, worldY - 1, worldZ); + Fluid fluidBelow = fluidMap.getAsset(fluidBelowId); + byte fluidLevelBelow = fluidSectionBelow.getFluidLevel(worldX, worldY - 1, worldZ); + int spreadFluidId = this.getSpreadFluidId(fluidId); + int blockIdBelow = blockSectionBelow.get(worldX, worldY - 1, worldZ); + BlockType blockBelow = blockMap.getAsset(blockIdBelow); + if (!isSolid(blockBelow)) { + DefaultFluidTicker.FluidCollisionConfig config = this.getCollisionMap().get(fluidBelowId); + if (config != null && !executeCollision(world, accessor, fluidSectionBelow, blockSectionBelow, config, worldX, worldY - 1, worldZ)) { + return BlockTickStrategy.CONTINUE; + } else { + if (fluidBelowId == 0 && !isSolid(blockBelow) || fluidBelowId == spreadFluidId && fluidLevelBelow < fluidBelow.getMaxFluidLevel()) { + int spreadId = this.getSpreadFluidId(fluidId); + Fluid spreadFluid = fluidMap.getAsset(spreadId); + boolean changed = fluidSectionBelow.setFluid(worldX, worldY - 1, worldZ, spreadId, (byte)spreadFluid.getMaxFluidLevel()); + if (changed) { + blockSectionBelow.setTicking(worldX, worldY - 1, worldZ, true); + } + } + + return BlockTickStrategy.SLEEP; + } + } else { + if (fluidBelowId == 0) { + if (fluidLevel == 1 && fluid.getMaxFluidLevel() != 1) { + return BlockTickStrategy.SLEEP; + } + + int offsets = this.getSpreadOffsets(blockMap, accessor, fluidSection, blockSection, worldX, worldY, worldZ, ORTO_OFFSETS, fluidId, 5); + if (offsets == 2147483646) { + return BlockTickStrategy.WAIT_FOR_ADJACENT_CHUNK_LOAD; + } + + int childFillLevel = fluidLevel - 1; + if (spreadFluidId != fluidId) { + childFillLevel = Fluid.getAssetMap().getAsset(spreadFluidId).getMaxFluidLevel() - 1; + } + + BlockType sourceBlock = blockMap.getAsset(blockSection.get(worldX, worldY, worldZ)); + int sourceRotationIndex = blockSection.getRotationIndex(worldX, worldY, worldZ); + int sourceFiller = blockSection.getFiller(worldX, worldY, worldZ); + + for (int i = 0; i < ORTO_OFFSETS.length; i++) { + if (offsets == 0 || (offsets & 1 << i) != 0) { + Vector2i offset = ORTO_OFFSETS[i]; + int x = offset.x; + int z = offset.y; + int blockX = worldX + x; + int blockZ = worldZ + z; + if (!blocksFluidFrom(sourceBlock, sourceRotationIndex, -x, -z, sourceFiller)) { + boolean isDifferentSection = !ChunkUtil.isSameChunkSection(worldX, worldY, worldZ, blockX, worldY, blockZ); + FluidSection otherFluidSection = isDifferentSection ? accessor.getFluidSectionByBlock(blockX, worldY, blockZ) : fluidSection; + BlockSection otherBlockSection = isDifferentSection ? accessor.getBlockSectionByBlock(blockX, worldY, blockZ) : blockSection; + if (otherFluidSection == null || otherBlockSection == null) { + return BlockTickStrategy.WAIT_FOR_ADJACENT_CHUNK_LOAD; + } + + BlockType block = blockMap.getAsset(otherBlockSection.get(blockX, worldY, blockZ)); + int rotationIndex = otherBlockSection.getRotationIndex(blockX, worldY, blockZ); + int destFiller = otherBlockSection.getFiller(blockX, worldY, blockZ); + if (!blocksFluidFrom(block, rotationIndex, x, z, destFiller)) { + int otherFluidId = otherFluidSection.getFluidId(blockX, worldY, blockZ); + if (otherFluidId != 0 && otherFluidId != spreadFluidId) { + DefaultFluidTicker.FluidCollisionConfig config = this.getCollisionMap().get(otherFluidId); + if (config == null || executeCollision(world, accessor, otherFluidSection, otherBlockSection, config, blockX, worldY, blockZ)) { + continue; + } + } + + byte fillLevel = otherFluidSection.getFluidLevel(blockX, worldY, blockZ); + if (otherFluidId != spreadFluidId || fillLevel < childFillLevel) { + if (childFillLevel == 0) { + otherFluidSection.setFluid(blockX, worldY, blockZ, 0, (byte)0); + } else { + otherFluidSection.setFluid(blockX, worldY, blockZ, spreadFluidId, (byte)childFillLevel); + otherBlockSection.setTicking(blockX, worldY, blockZ, true); + } + } + } + } + } + } + } + + return BlockTickStrategy.SLEEP; + } + } else { + return BlockTickStrategy.SLEEP; + } + } + } + + private static boolean executeCollision( + @Nonnull World world, + @Nonnull FluidTicker.Accessor accessor, + @Nonnull FluidSection fluidSection, + BlockSection blockSection, + @Nonnull DefaultFluidTicker.FluidCollisionConfig config, + int blockX, + int blockY, + int blockZ + ) { + int blockToPlace = config.getBlockToPlaceIndex(); + if (blockToPlace != Integer.MIN_VALUE) { + accessor.setBlock(blockX, blockY, blockZ, blockToPlace); + setTickingSurrounding(accessor, blockSection, blockX, blockY, blockZ); + fluidSection.setFluid(blockX, blockY, blockZ, 0, (byte)0); + } + + int soundEvent = config.getSoundEventIndex(); + if (soundEvent != Integer.MIN_VALUE) { + world.execute(() -> SoundUtil.playSoundEvent3d(soundEvent, SoundCategory.SFX, blockX, blockY, blockZ, world.getEntityStore().getStore())); + } + + return !config.placeFluid; + } + + @Override + public boolean isSelfFluid(int selfFluidId, int otherFluidId) { + return super.isSelfFluid(selfFluidId, otherFluidId) || otherFluidId == this.getSpreadFluidId(selfFluidId); + } + + private int getSpreadFluidId(int fluidId) { + if (this.spreadFluidId == 0) { + if (this.spreadFluid != null) { + this.spreadFluidId = Fluid.getAssetMap().getIndex(this.spreadFluid); + } else { + this.spreadFluidId = Integer.MIN_VALUE; + } + } + + return this.spreadFluidId == Integer.MIN_VALUE ? fluidId : this.spreadFluidId; + } + + @Nonnull + public Int2ObjectMap getCollisionMap() { + if (this.collisionMap == null) { + Int2ObjectOpenHashMap collisionMap = new Int2ObjectOpenHashMap<>(this.rawCollisionMap.size()); + + for (Entry entry : this.rawCollisionMap.entrySet()) { + int index = Fluid.getAssetMap().getIndex(entry.getKey()); + if (index != Integer.MIN_VALUE) { + collisionMap.put(index, entry.getValue()); + } + } + + this.collisionMap = collisionMap; + } + + return this.collisionMap; + } + + public static class FluidCollisionConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DefaultFluidTicker.FluidCollisionConfig.class, DefaultFluidTicker.FluidCollisionConfig::new + ) + .appendInherited( + new KeyedCodec<>("BlockToPlace", Codec.STRING), (o, v) -> o.blockToPlace = v, o -> o.blockToPlace, (o, p) -> o.blockToPlace = p.blockToPlace + ) + .documentation("The block to place when a collision occurs") + .add() + .appendInherited( + new KeyedCodec<>("SoundEvent", Codec.STRING), (o, v) -> o.soundEvent = v, o -> o.soundEvent, (o, p) -> o.soundEvent = p.soundEvent + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("PlaceFluid", Codec.BOOLEAN), (o, v) -> o.placeFluid = v, o -> o.placeFluid, (o, p) -> o.placeFluid = p.placeFluid + ) + .documentation("Whether to still place the fluid on collision") + .add() + .build(); + private String blockToPlace; + private int blockToPlaceIndex = Integer.MIN_VALUE; + public boolean placeFluid = false; + private String soundEvent; + private int soundEventIndex = Integer.MIN_VALUE; + + public FluidCollisionConfig() { + } + + public int getBlockToPlaceIndex() { + if (this.blockToPlaceIndex == Integer.MIN_VALUE && this.blockToPlace != null) { + this.blockToPlaceIndex = BlockType.getBlockIdOrUnknown(this.blockToPlace, "Unknown block type %s", this.blockToPlace); + } + + return this.blockToPlaceIndex; + } + + public int getSoundEventIndex() { + if (this.soundEventIndex == Integer.MIN_VALUE && this.soundEvent != null) { + this.soundEventIndex = SoundEvent.getAssetMap().getIndex(this.soundEvent); + } + + return this.soundEventIndex; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/fluid/FiniteFluidTicker.java b/src/com/hypixel/hytale/server/core/asset/type/fluid/FiniteFluidTicker.java new file mode 100644 index 0000000..56ab11d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/fluid/FiniteFluidTicker.java @@ -0,0 +1,383 @@ +package com.hypixel.hytale.server.core.asset.type.fluid; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FiniteFluidTicker extends FluidTicker { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder(FiniteFluidTicker.class, FiniteFluidTicker::new, FluidTicker.BASE_CODEC).build(); + @Nonnull + private static final Vector2i[] DIAG_OFFSETS = new Vector2i[]{new Vector2i(-1, -1), new Vector2i(1, 1), new Vector2i(1, -1), new Vector2i(-1, 1)}; + private static final int MAX_DROP_DISTANCE = 2; + @Nonnull + private static final List> OFFSETS_LISTS = new ObjectArrayList<>(); + private static final int RANDOM_VARIANTS = 16; + + public FiniteFluidTicker() { + } + + @Nonnull + @Override + protected FluidTicker.AliveStatus isAlive( + @Nonnull FluidTicker.Accessor accessor, + @Nonnull FluidSection fluidSection, + @Nonnull BlockSection blockSection, + Fluid fluid, + int fluidId, + byte fluidLevel, + int worldX, + int worldY, + int worldZ + ) { + return FluidTicker.AliveStatus.ALIVE; + } + + @Nonnull + @Override + protected BlockTickStrategy spread( + World world, + long tick, + @Nonnull FluidTicker.Accessor accessor, + @Nonnull FluidSection fluidSection, + BlockSection blockSection, + @Nonnull Fluid fluid, + int fluidId, + byte fluidLevel, + int worldX, + int worldY, + int worldZ + ) { + if (worldY == 0) { + return BlockTickStrategy.SLEEP; + } else { + boolean isDifferentSectionBelow = fluidSection.getY() != ChunkUtil.chunkCoordinate(worldY - 1); + FluidSection belowFluidSection = isDifferentSectionBelow ? accessor.getFluidSectionByBlock(worldX, worldY - 1, worldZ) : fluidSection; + BlockSection belowBlockSection = isDifferentSectionBelow ? accessor.getBlockSectionByBlock(worldX, worldY - 1, worldZ) : blockSection; + if (belowFluidSection != null && belowBlockSection != null) { + int bottomFluidId = belowFluidSection.getFluidId(worldX, worldY - 1, worldZ); + byte bottomFluidLevel = belowFluidSection.getFluidLevel(worldX, worldY - 1, worldZ); + return this.spreadDownwards( + accessor, + fluidSection, + blockSection, + belowFluidSection, + belowBlockSection, + worldX, + worldY, + worldZ, + fluid, + fluidId, + fluidLevel, + bottomFluidId, + bottomFluidLevel + ) + ? BlockTickStrategy.CONTINUE + : this.spreadSideways(tick, accessor, fluidSection, blockSection, worldX, worldY, worldZ, fluid, fluidId, fluidLevel); + } else { + return BlockTickStrategy.SLEEP; + } + } + } + + private boolean spreadDownwards( + @Nonnull FluidTicker.Accessor accessor, + @Nonnull FluidSection fluidSection, + BlockSection blockSection, + @Nonnull FluidSection belowFluidSection, + @Nonnull BlockSection belowBlockSection, + int worldX, + int worldY, + int worldZ, + @Nonnull Fluid fluid, + int fluidId, + byte fluidLevel, + int bottomFluidId, + byte bottomFluidLevel + ) { + if (fluidId != bottomFluidId && bottomFluidId != 0) { + return false; + } else if (isSolid(BlockType.getAssetMap().getAsset(belowBlockSection.get(worldX, worldY - 1, worldZ)))) { + return false; + } else { + int topY = this.getTopY(accessor, fluidSection, worldX, worldY, worldZ, fluid, fluidId); + boolean isTopDifferent = ChunkUtil.chunkCoordinate(topY) != fluidSection.getY(); + FluidSection topFluidSection = isTopDifferent ? accessor.getFluidSectionByBlock(worldX, topY, worldZ) : fluidSection; + BlockSection topBlockSection = isTopDifferent ? accessor.getBlockSectionByBlock(worldX, topY, worldZ) : blockSection; + int topBlockLevel = topFluidSection.getFluidLevel(worldX, topY, worldZ); + int transferLevel = Math.min(topBlockLevel, fluid.getMaxFluidLevel() - bottomFluidLevel); + if (transferLevel == 0) { + return false; + } else { + int newBottomLevel = bottomFluidId == 0 ? transferLevel : bottomFluidLevel + transferLevel; + belowFluidSection.setFluid(worldX, worldY - 1, worldZ, fluidId, (byte)newBottomLevel); + setTickingSurrounding(accessor, belowBlockSection, worldX, worldY - 1, worldZ); + boolean updated; + if (transferLevel == topBlockLevel) { + updated = topFluidSection.setFluid(worldX, topY, worldZ, 0, (byte)0); + } else { + updated = topFluidSection.setFluid(worldX, topY, worldZ, fluidId, (byte)(topBlockLevel - transferLevel)); + } + + setTickingSurrounding(accessor, topBlockSection, worldX, topY, worldZ); + return updated; + } + } + } + + @Nonnull + private BlockTickStrategy spreadSideways( + long tick, + @Nonnull FluidTicker.Accessor accessor, + @Nonnull FluidSection fluidSection, + BlockSection blockSection, + int worldX, + int worldY, + int worldZ, + @Nonnull Fluid fluid, + int fluidId, + byte fluidLevel + ) { + if (fluidLevel == 1) { + return BlockTickStrategy.SLEEP; + } else { + int newLevel = fluidLevel; + BlockTypeAssetMap blockTypeMap = BlockType.getAssetMap(); + long hash = HashUtil.rehash(worldX, worldY, worldZ, 4032035379L); + int index = OFFSETS_LISTS.size() + (int)((hash + tick) % OFFSETS_LISTS.size()); + List offsetsList = OFFSETS_LISTS.get(index % OFFSETS_LISTS.size()); + + for (int idx = 0; idx < offsetsList.size(); idx++) { + Vector2i[] offsetArray = offsetsList.get(idx); + int offsets = this.getSpreadOffsets(blockTypeMap, accessor, fluidSection, blockSection, worldX, worldY, worldZ, offsetArray, fluidId, 2); + boolean spreadDownhill = offsets != 0; + + for (int i = 0; i < offsetArray.length && newLevel != 1; i++) { + if (!spreadDownhill || (offsets & 1 << i) != 0) { + Vector2i offset = offsetArray[i]; + FiniteFluidTicker.SpreadOutcome spreadOutcome = this.spreadToOffset( + accessor, fluidSection, blockSection, offset, worldX, worldY, worldZ, fluid, fluidId, fluidLevel + ); + if (spreadOutcome != null) { + switch (spreadOutcome) { + case SUCCESS: + newLevel--; + break; + case UNLOADED_CHUNK: + return BlockTickStrategy.WAIT_FOR_ADJACENT_CHUNK_LOAD; + } + } + } + } + + if (spreadDownhill) { + break; + } + } + + if (newLevel == fluidLevel) { + return BlockTickStrategy.SLEEP; + } else { + return !this.drainFromTopBlock(accessor, fluidSection, blockSection, worldX, worldY, worldZ, fluid, fluidId, (byte)(fluidLevel - newLevel)) + ? BlockTickStrategy.WAIT_FOR_ADJACENT_CHUNK_LOAD + : BlockTickStrategy.CONTINUE; + } + } + } + + @Nullable + private FiniteFluidTicker.SpreadOutcome spreadToOffset( + @Nonnull FluidTicker.Accessor accessor, + FluidSection fluidSection, + BlockSection blockSection, + @Nonnull Vector2i offset, + int worldX, + int worldY, + int worldZ, + Fluid fluid, + int fluidId, + byte fluidLevel + ) { + if (!isOffsetConnected(accessor, blockSection, offset, worldX, worldY, worldZ)) { + return null; + } else { + int x = offset.getX(); + int z = offset.getY(); + int blockX = worldX + x; + int blockZ = worldZ + z; + boolean isDifferentSection = !ChunkUtil.isSameChunkSection(worldX, worldY, worldZ, blockX, worldY, blockZ); + FluidSection otherFluidSection = isDifferentSection ? accessor.getFluidSectionByBlock(blockX, worldY, blockZ) : fluidSection; + BlockSection otherBlockSection = isDifferentSection ? accessor.getBlockSectionByBlock(blockX, worldY, blockZ) : blockSection; + if (otherFluidSection != null && otherBlockSection != null) { + int blockId = otherBlockSection.get(blockX, worldY, blockZ); + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + if (isSolid(blockType)) { + return null; + } else { + int adjacentFluidId = otherFluidSection.getFluidId(blockX, worldY, blockZ); + byte adjacentFluidLevel = otherFluidSection.getFluidLevel(blockX, worldY, blockZ); + int newAdjacentFillLevel = 1; + if (adjacentFluidId == 0 || adjacentFluidId == fluidId && adjacentFluidLevel < fluidLevel - 1) { + if (adjacentFluidId == fluidId) { + newAdjacentFillLevel = adjacentFluidLevel + 1; + } + + if (otherFluidSection.setFluid(blockX, worldY, blockZ, fluidId, (byte)newAdjacentFillLevel)) { + setTickingSurrounding(accessor, otherBlockSection, blockX, worldY, blockZ); + return FiniteFluidTicker.SpreadOutcome.SUCCESS; + } + } + + return null; + } + } else { + return FiniteFluidTicker.SpreadOutcome.UNLOADED_CHUNK; + } + } + } + + private boolean drainFromTopBlock( + @Nonnull FluidTicker.Accessor accessor, + @Nonnull FluidSection fluidSection, + BlockSection blockSection, + int worldX, + int worldY, + int worldZ, + @Nonnull Fluid fluid, + int fluidId, + byte drainLevels + ) { + int topY = this.getTopY(accessor, fluidSection, worldX, worldY, worldZ, fluid, fluidId); + boolean isDifferentSection = fluidSection.getY() != ChunkUtil.chunkCoordinate(topY); + FluidSection topFluidSection = isDifferentSection ? accessor.getFluidSectionByBlock(worldX, topY, worldZ) : fluidSection; + BlockSection topBlockSection = isDifferentSection ? accessor.getBlockSectionByBlock(worldX, topY, worldZ) : blockSection; + if (topFluidSection != null && topBlockSection != null) { + byte topBlockFillLevels = topFluidSection.getFluidLevel(worldX, topY, worldZ); + if (topBlockFillLevels > drainLevels) { + setTickingSurrounding(accessor, topBlockSection, worldX, topY, worldZ); + return topFluidSection.setFluid(worldX, topY, worldZ, fluidId, (byte)(topBlockFillLevels - drainLevels)); + } else if (topBlockFillLevels == drainLevels) { + setTickingSurrounding(accessor, topBlockSection, worldX, topY, worldZ); + return topFluidSection.setFluid(worldX, topY, worldZ, 0, (byte)0); + } else { + int nextY = topY; + boolean updated = true; + FluidSection nextFluidSection = topFluidSection; + + for (BlockSection nextBlockSection = topBlockSection; drainLevels > 0; nextY--) { + boolean isDifferent = ChunkUtil.chunkCoordinate(nextY) != nextFluidSection.getY(); + if (isDifferent) { + nextFluidSection = accessor.getFluidSectionByBlock(worldX, nextY, worldZ); + nextBlockSection = accessor.getBlockSectionByBlock(worldX, nextY, worldZ); + if (nextFluidSection == null || nextBlockSection == null) { + return false; + } + } + + int nextFluidLevel = nextFluidSection.getFluidLevel(worldX, nextY, worldZ); + int transferLevels = Math.min(nextFluidLevel, drainLevels); + if (transferLevels == nextFluidLevel) { + updated &= nextFluidSection.setFluid(worldX, nextY, worldZ, 0, (byte)0); + } else { + updated &= nextFluidSection.setFluid(worldX, nextY, worldZ, fluidId, (byte)nextFluidLevel); + setTickingSurrounding(accessor, nextBlockSection, worldX, nextY, worldZ); + } + + drainLevels -= (byte)transferLevels; + } + + return updated; + } + } else { + return false; + } + } + + private int getTopY( + @Nonnull FluidTicker.Accessor accessor, @Nonnull FluidSection fluidSection, int worldX, int worldY, int worldZ, @Nonnull Fluid fluid, int fluidId + ) { + int topY = worldY; + FluidSection aboveFluidSection = fluidSection.getY() != ChunkUtil.chunkCoordinate(worldY + 1) + ? accessor.getFluidSectionByBlock(worldX, worldY + 1, worldZ) + : fluidSection; + + while (true) { + if (fluidSection.getY() != ChunkUtil.chunkCoordinate(topY)) { + fluidSection = accessor.getFluidSectionByBlock(worldX, topY, worldZ); + } + + if (aboveFluidSection.getY() != ChunkUtil.chunkCoordinate(topY + 1)) { + aboveFluidSection = accessor.getFluidSectionByBlock(worldX, topY + 1, worldZ); + } + + if (fluidSection.getFluidLevel(worldX, topY, worldZ) != fluid.getMaxFluidLevel() || aboveFluidSection.getFluidId(worldX, topY + 1, worldZ) != fluidId) { + return topY; + } + + topY++; + } + } + + private static boolean isOffsetConnected( + @Nonnull FluidTicker.Accessor accessor, BlockSection blockSection, @Nonnull Vector2i offset, int worldX, int worldY, int worldZ + ) { + int x = offset.getX(); + int z = offset.getY(); + if (x != 0 && z != 0) { + BlockSection section1 = ChunkUtil.isSameChunkSection(worldX, worldY, worldZ, worldX + x, worldY, worldZ) + ? blockSection + : accessor.getBlockSection(worldX + x, worldY, worldZ); + BlockSection section2 = ChunkUtil.isSameChunkSection(worldX, worldY, worldZ, worldX, worldY, worldZ + z) + ? blockSection + : accessor.getBlockSection(worldX, worldY, worldZ + z); + if (section1 != null && section2 != null) { + int block1 = section1.get(worldX + x, worldY, worldZ); + int block2 = section2.get(worldX, worldY, worldZ + z); + return block1 == 0 || block2 == 0 || !isSolid(BlockType.getAssetMap().getAsset(block1)) || !isSolid(BlockType.getAssetMap().getAsset(block2)); + } else { + return false; + } + } else { + return true; + } + } + + static { + List offsets = List.of(ORTO_OFFSETS, DIAG_OFFSETS); + Random random = new Random(51966L); + + for (int i = 0; i < 16; i++) { + ObjectArrayList offsetLists = new ObjectArrayList<>(); + + for (Vector2i[] offset : offsets) { + List offsetArray = Arrays.asList(offset); + Collections.shuffle(offsetArray, random); + offsetLists.add(offsetArray.toArray(Vector2i[]::new)); + } + + OFFSETS_LISTS.add(offsetLists); + } + } + + private static enum SpreadOutcome { + SUCCESS, + UNLOADED_CHUNK; + + private SpreadOutcome() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/fluid/Fluid.java b/src/com/hypixel/hytale/server/core/asset/type/fluid/Fluid.java new file mode 100644 index 0000000..e4db086 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/fluid/Fluid.java @@ -0,0 +1,485 @@ +package com.hypixel.hytale.server.core.asset.type.fluid; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorSectionStart; +import com.hypixel.hytale.codec.schema.metadata.ui.UIPropertyTitle; +import com.hypixel.hytale.codec.schema.metadata.ui.UIRebuildCaches; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.BlockTextures; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.ColorLight; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.Opacity; +import com.hypixel.hytale.protocol.ShaderType; +import com.hypixel.hytale.server.core.asset.type.blockparticle.config.BlockParticleSet; +import com.hypixel.hytale.server.core.asset.type.blocksound.config.BlockSoundSet; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockTypeTextures; +import com.hypixel.hytale.server.core.asset.type.fluidfx.config.FluidFX; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.InteractionTypeUtils; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.universe.world.chunk.section.palette.ISectionPalette; +import com.hypixel.hytale.server.core.util.io.ByteBufUtil; +import io.netty.buffer.ByteBuf; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.Collections; +import java.util.Map; +import java.util.function.ToIntFunction; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Fluid implements JsonAssetWithMap>, NetworkSerializable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + Fluid.class, Fluid::new, Codec.STRING, (t, k) -> t.id = k, t -> t.id, (asset, data) -> asset.data = data, asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("MaxFluidLevel", Codec.INTEGER), + (asset, v) -> asset.maxFluidLevel = v, + asset -> asset.maxFluidLevel, + (asset, parent) -> asset.maxFluidLevel = parent.maxFluidLevel + ) + .addValidator(Validators.range(0, 15)) + .add() + .appendInherited( + new KeyedCodec<>("Textures", new ArrayCodec<>(BlockTypeTextures.CODEC, BlockTypeTextures[]::new)), + (fluid, o) -> fluid.textures = o, + fluid -> fluid.textures, + (fluid, parent) -> fluid.textures = parent.textures + ) + .metadata(new UIPropertyTitle("Block Textures")) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS, UIRebuildCaches.ClientCache.BLOCK_TEXTURES)) + .add() + .appendInherited( + new KeyedCodec<>("Effect", new ArrayCodec<>(new EnumCodec<>(ShaderType.class), ShaderType[]::new)), + (fluid, o) -> fluid.effect = o, + fluid -> fluid.effect, + (fluid, parent) -> fluid.effect = parent.effect + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .add() + .appendInherited( + new KeyedCodec<>("Opacity", new EnumCodec<>(Opacity.class)), + (fluid, o) -> fluid.opacity = o, + fluid -> fluid.opacity, + (fluid, parent) -> fluid.opacity = parent.opacity + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("RequiresAlphaBlending", Codec.BOOLEAN), + (fluid, o) -> fluid.requiresAlphaBlending = o, + fluid -> fluid.requiresAlphaBlending, + (fluid, parent) -> fluid.requiresAlphaBlending = parent.requiresAlphaBlending + ) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .add() + .appendInherited( + new KeyedCodec<>("FluidFXId", Codec.STRING), + (fluid, o) -> fluid.fluidFXId = o, + fluid -> fluid.fluidFXId, + (fluid, parent) -> fluid.fluidFXId = parent.fluidFXId + ) + .addValidator(FluidFX.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("Ticker", FluidTicker.CODEC), (fluid, o) -> fluid.ticker = o, fluid -> fluid.ticker, (fluid, parent) -> fluid.ticker = parent.ticker + ) + .add() + .appendInherited( + new KeyedCodec<>("Light", ProtocolCodecs.COLOR_LIGHT), + (fluid, o) -> fluid.light = o, + fluid -> fluid.light, + (fluid, parent) -> fluid.light = parent.light + ) + .add() + .appendInherited( + new KeyedCodec<>("DamageToEntities", Codec.INTEGER), + (fluid, s) -> fluid.damageToEntities = s, + fluid -> fluid.damageToEntities, + (fluid, parent) -> fluid.damageToEntities = parent.damageToEntities + ) + .add() + .appendInherited( + new KeyedCodec<>("BlockParticleSetId", Codec.STRING), + (fluid, s) -> fluid.blockParticleSetId = s, + fluid -> fluid.blockParticleSetId, + (fluid, parent) -> fluid.blockParticleSetId = parent.blockParticleSetId + ) + .documentation( + "The block particle set defined here defines which particles should be spawned when an entity interacts with this block (like when stepping on it for example" + ) + .addValidator(BlockParticleSet.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("ParticleColor", ProtocolCodecs.COLOR), + (fluid, s) -> fluid.particleColor = s, + fluid -> fluid.particleColor, + (fluid, parent) -> fluid.particleColor = parent.particleColor + ) + .add() + .appendInherited( + new KeyedCodec<>("BlockSoundSetId", Codec.STRING), + (fluid, o) -> fluid.blockSoundSetId = o, + fluid -> fluid.blockSoundSetId, + (fluid, parent) -> fluid.blockSoundSetId = parent.blockSoundSetId + ) + .documentation("Sets the **BlockSoundSet** that will be used for this block for various events e.g. placement, breaking") + .addValidator(BlockSoundSet.VALIDATOR_CACHE.getValidator()) + .add() + .>appendInherited( + new KeyedCodec<>("Interactions", new EnumMapCodec<>(InteractionType.class, RootInteraction.CHILD_ASSET_CODEC)), + (item, v) -> item.interactions = v, + item -> item.interactions, + (item, parent) -> item.interactions = parent.interactions + ) + .addValidator(RootInteraction.VALIDATOR_CACHE.getMapValueValidator()) + .metadata(new UIEditorSectionStart("Interactions")) + .add() + .afterDecode(Fluid::processConfig) + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(Fluid::getAssetStore)); + public static final String UNKNOWN_TEXTURE = "BlockTextures/Unknown.png"; + public static final BlockTextures[] UNKNOWN_BLOCK_TEXTURES = new BlockTextures[]{ + new BlockTextures( + "BlockTextures/Unknown.png", + "BlockTextures/Unknown.png", + "BlockTextures/Unknown.png", + "BlockTextures/Unknown.png", + "BlockTextures/Unknown.png", + "BlockTextures/Unknown.png", + 1.0F + ) + }; + public static final ShaderType[] DEFAULT_SHADER_EFFECTS = new ShaderType[]{ShaderType.None}; + public static final ISectionPalette.KeySerializer KEY_SERIALIZER = (buf, id) -> { + String key = getAssetMap().getAssetOrDefault(id, Fluid.UNKNOWN).getId(); + ByteBufUtil.writeUTF(buf, key); + }; + public static final ToIntFunction KEY_DESERIALIZER = byteBuf -> { + String fluid = ByteBufUtil.readUTF(byteBuf); + return getFluidIdOrUnknown(fluid, "Failed to find fluid '%s' in chunk section!", fluid); + }; + public static final int EMPTY_ID = 0; + public static final String EMPTY_KEY = "Empty"; + public static final Fluid EMPTY = new Fluid("Empty") { + { + this.processConfig(); + } + }; + public static final int UNKNOWN_ID = 1; + public static final Fluid UNKNOWN = new Fluid("Unknown") { + { + this.unknown = true; + this.processConfig(); + } + }; + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected boolean unknown; + private int maxFluidLevel = 8; + private BlockTypeTextures[] textures; + private ShaderType[] effect; + @Nonnull + private Opacity opacity = Opacity.Solid; + private boolean requiresAlphaBlending = true; + private String fluidFXId = "Empty"; + protected transient int fluidFXIndex = 0; + private FluidTicker ticker = DefaultFluidTicker.INSTANCE; + protected int damageToEntities; + protected ColorLight light; + protected Color particleColor; + protected String blockSoundSetId = "EMPTY"; + protected transient int blockSoundSetIndex = 0; + public String blockParticleSetId; + protected Map interactions = Collections.emptyMap(); + protected transient boolean isTrigger; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(Fluid.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public Fluid() { + } + + public Fluid(String id) { + this.id = id; + } + + public Fluid(@Nonnull Fluid other) { + this.data = other.data; + this.id = other.id; + this.unknown = other.unknown; + this.maxFluidLevel = other.maxFluidLevel; + this.textures = other.textures; + this.effect = other.effect; + this.opacity = other.opacity; + this.requiresAlphaBlending = other.requiresAlphaBlending; + this.fluidFXId = other.fluidFXId; + this.damageToEntities = other.damageToEntities; + this.light = other.light; + this.particleColor = other.particleColor; + this.blockSoundSetId = other.blockSoundSetId; + this.interactions = other.interactions; + this.isTrigger = other.isTrigger; + this.processConfig(); + } + + public AssetExtraInfo.Data getData() { + return this.data; + } + + public String getId() { + return this.id; + } + + public boolean isUnknown() { + return this.unknown; + } + + public int getMaxFluidLevel() { + return this.maxFluidLevel; + } + + public FluidTicker getTicker() { + return this.ticker; + } + + public int getDamageToEntities() { + return this.damageToEntities; + } + + public String getFluidFXId() { + return this.fluidFXId; + } + + public int getFluidFXIndex() { + return this.fluidFXIndex; + } + + public ColorLight getLight() { + return this.light; + } + + public Color getParticleColor() { + return this.particleColor; + } + + public boolean isTrigger() { + return this.isTrigger; + } + + public Map getInteractions() { + return this.interactions; + } + + protected void processConfig() { + this.fluidFXIndex = this.fluidFXId.equals("Empty") ? 0 : FluidFX.getAssetMap().getIndex(this.fluidFXId); + this.blockSoundSetIndex = this.blockSoundSetId.equals("EMPTY") ? 0 : BlockSoundSet.getAssetMap().getIndex(this.blockSoundSetId); + + for (InteractionType type : this.interactions.keySet()) { + if (InteractionTypeUtils.isCollisionType(type)) { + this.isTrigger = true; + break; + } + } + } + + @Nonnull + public static Fluid getUnknownFor(String key) { + return UNKNOWN.clone(key); + } + + @Nonnull + public Fluid clone(String newKey) { + if (this.id != null && this.id.equals(newKey)) { + return this; + } else { + Fluid fluid = new Fluid(this); + fluid.id = newKey; + return fluid; + } + } + + public static int getFluidIdOrUnknown(String key, String message, Object... params) { + return getFluidIdOrUnknown(getAssetMap(), key, message, params); + } + + public static int getFluidIdOrUnknown(@Nonnull IndexedLookupTableAssetMap assetMap, String key, String message, Object... params) { + int fluidId = assetMap.getIndex(key); + if (fluidId == Integer.MIN_VALUE) { + HytaleLogger.getLogger().at(Level.WARNING).logVarargs(message, params); + AssetRegistry.getAssetStore(Fluid.class).loadAssets("Hytale:Hytale", Collections.singletonList(getUnknownFor(key))); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + fluidId = index; + } + + return fluidId; + } + + @Nonnull + public com.hypixel.hytale.protocol.Fluid toPacket() { + com.hypixel.hytale.protocol.Fluid packet = new com.hypixel.hytale.protocol.Fluid(); + packet.id = this.id; + packet.maxFluidLevel = this.maxFluidLevel; + packet.fluidFXIndex = this.fluidFXIndex; + packet.opacity = this.opacity; + packet.light = this.light; + if (this.effect != null && this.effect.length > 0) { + packet.shaderEffect = this.effect; + } else { + packet.shaderEffect = DEFAULT_SHADER_EFFECTS; + } + + if (this.textures != null && this.textures.length > 0) { + int totalWeight = 0; + + for (BlockTypeTextures texture : this.textures) { + totalWeight = (int)(totalWeight + texture.getWeight()); + } + + BlockTextures[] texturePackets = new BlockTextures[this.textures.length]; + + for (int i = 0; i < this.textures.length; i++) { + texturePackets[i] = this.textures[i].toPacket(totalWeight); + } + + packet.cubeTextures = texturePackets; + } else { + packet.cubeTextures = UNKNOWN_BLOCK_TEXTURES; + } + + packet.requiresAlphaBlending = this.requiresAlphaBlending; + packet.blockSoundSetIndex = this.blockSoundSetIndex; + packet.blockParticleSetId = this.blockParticleSetId; + packet.particleColor = this.particleColor; + packet.fluidFXIndex = this.fluidFXIndex; + if (this.data != null) { + IntSet tags = this.data.getExpandedTagIndexes(); + if (tags != null) { + packet.tagIndexes = tags.toIntArray(); + } + } + + return packet; + } + + @Nullable + @Deprecated(forRemoval = true) + public static String convertLegacyName(@Nonnull String fluidName, byte level) { + return switch (fluidName) { + case "Fluid_Water" -> level == 0 ? "Water_Source" : "Water"; + case "Fluid_Water_Test" -> "Water_Finite"; + case "Fluid_Lava" -> level == 0 ? "Lava_Source" : "Lava"; + case "Fluid_Tar" -> level == 0 ? "Tar_Source" : "Tar"; + case "Fluid_Slime" -> level == 0 ? "Slime_Source" : "Slime"; + case "Fluid_Poison" -> level == 0 ? "Poison_Source" : "Poison"; + default -> null; + }; + } + + @Nullable + @Deprecated(forRemoval = true) + public static Fluid.ConversionResult convertBlockToFluid(@Nonnull String blockTypeStr) { + int fluidPos = blockTypeStr.indexOf("|Fluid="); + if (fluidPos != -1) { + int fluidNameEnd = blockTypeStr.indexOf(124, fluidPos + 2); + String fluidName; + if (fluidNameEnd != -1) { + fluidName = blockTypeStr.substring(fluidPos + "|Fluid=".length(), fluidNameEnd); + blockTypeStr = blockTypeStr.substring(0, fluidPos) + blockTypeStr.substring(fluidNameEnd); + } else { + fluidName = blockTypeStr.substring(fluidPos + "|Fluid=".length()); + blockTypeStr = blockTypeStr.substring(0, fluidPos); + } + + int fluidLevelStart = blockTypeStr.indexOf("|FluidLevel="); + byte fluidLevel; + if (fluidLevelStart != -1) { + int fluidLevelEnd = blockTypeStr.indexOf(124, fluidLevelStart + 2); + String fluidLevelStr; + if (fluidLevelEnd != -1) { + fluidLevelStr = blockTypeStr.substring(fluidLevelStart + "|FluidLevel=".length(), fluidLevelEnd); + blockTypeStr = blockTypeStr.substring(0, fluidLevelStart) + blockTypeStr.substring(fluidLevelEnd); + } else { + fluidLevelStr = blockTypeStr.substring(fluidLevelStart + "|FluidLevel=".length()); + blockTypeStr = blockTypeStr.substring(0, fluidLevelStart); + } + + fluidLevel = Byte.parseByte(fluidLevelStr); + } else { + fluidLevel = 0; + } + + fluidName = convertLegacyName(fluidName, fluidLevel); + int fluidId = getFluidIdOrUnknown(fluidName, "Failed to find fluid '%s'", fluidName); + fluidLevel = fluidLevel == 0 ? (byte)getAssetMap().getAsset(fluidId).getMaxFluidLevel() : fluidLevel; + return new Fluid.ConversionResult(blockTypeStr, fluidId, fluidLevel); + } else if (blockTypeStr.startsWith("Fluid_")) { + int fluidLevelStart = blockTypeStr.indexOf("|FluidLevel="); + byte fluidLevel; + if (fluidLevelStart != -1) { + int fluidLevelEnd = blockTypeStr.indexOf(124, fluidLevelStart + 2); + String fluidLevelStr; + if (fluidLevelEnd != -1) { + fluidLevelStr = blockTypeStr.substring(fluidLevelStart + "|FluidLevel=".length(), fluidLevelEnd); + blockTypeStr = blockTypeStr.substring(0, fluidLevelStart) + blockTypeStr.substring(fluidLevelEnd); + } else { + fluidLevelStr = blockTypeStr.substring(fluidLevelStart + "|FluidLevel=".length()); + blockTypeStr = blockTypeStr.substring(0, fluidLevelStart); + } + + fluidLevel = Byte.parseByte(fluidLevelStr); + } else { + fluidLevel = 0; + } + + String newFluidName = convertLegacyName(blockTypeStr, fluidLevel); + int fluidId = getFluidIdOrUnknown(newFluidName, "Failed to find fluid '%s'", newFluidName); + fluidLevel = fluidLevel == 0 ? (byte)getAssetMap().getAsset(fluidId).getMaxFluidLevel() : fluidLevel; + return new Fluid.ConversionResult(null, fluidId, fluidLevel); + } else { + return null; + } + } + + @Deprecated(forRemoval = true) + public static class ConversionResult { + public String blockTypeStr; + public int fluidId; + public byte fluidLevel; + + public ConversionResult(String blockTypeStr, int fluidId, byte fluidLevel) { + this.blockTypeStr = blockTypeStr; + this.fluidId = fluidId; + this.fluidLevel = fluidLevel; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/fluid/FluidTicker.java b/src/com/hypixel/hytale/server/core/asset/type/fluid/FluidTicker.java new file mode 100644 index 0000000..d93d468 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/fluid/FluidTicker.java @@ -0,0 +1,569 @@ +package com.hypixel.hytale.server.core.asset.type.fluid; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.protocol.DrawType; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.AbstractCachedAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class FluidTicker { + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(FluidTicker.class) + .appendInherited( + new KeyedCodec<>("FlowRate", Codec.FLOAT), + (ticker, r) -> ticker.flowRate = r, + ticker -> ticker.flowRate, + (ticker, parent) -> ticker.flowRate = parent.flowRate + ) + .documentation("The tick frequency for this fluid type, in seconds") + .addValidator(Validators.greaterThan(0.0F)) + .add() + .appendInherited( + new KeyedCodec<>("CanDemote", Codec.BOOLEAN), + (ticker, o) -> ticker.canDemote = o, + ticker -> ticker.canDemote, + (ticker, parent) -> ticker.canDemote = parent.canDemote + ) + .documentation("If false then the fluid will stay at its level") + .add() + .appendInherited( + new KeyedCodec<>("SupportedBy", Codec.STRING), + (ticker, o) -> ticker.supportedBy = o, + ticker -> ticker.supportedBy, + (ticker, parent) -> ticker.supportedBy = parent.supportedBy + ) + .add() + .build(); + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type", true); + protected static final Vector2i[] ORTO_OFFSETS = new Vector2i[]{new Vector2i(-1, 0), new Vector2i(1, 0), new Vector2i(0, -1), new Vector2i(0, 1)}; + protected static final int SPREAD_NO_PATH = Integer.MAX_VALUE; + protected static final int SPREAD_NO_CHUNK = 2147483646; + protected static final int OFFSET_DROP_NONE = 0; + public static final int FLUID_BLOCK_DISTANCE = 5; + private static final double FULL_DIMENSION_THRESHOLD = 0.9; + private static final double PARTIAL_DIMENSION_THRESHOLD = 0.6; + private static final double FACE_BLOCK_THRESHOLD = 0.1; + private float flowRate = 0.5F; + private boolean canDemote = true; + private String supportedBy; + private transient int supportedById = 0; + + public FluidTicker() { + } + + public int getSupportedById() { + if (this.supportedById == 0) { + if (this.supportedBy != null) { + this.supportedById = Fluid.getAssetMap().getIndex(this.supportedBy); + } else { + this.supportedById = Integer.MIN_VALUE; + } + } + + return this.supportedById; + } + + public BlockTickStrategy tick( + @Nonnull CommandBuffer commandBuffer, + @Nonnull FluidTicker.CachedAccessor cachedAccessor, + @Nonnull FluidSection fluidSection, + @Nonnull BlockSection blockSection, + @Nonnull Fluid fluid, + int fluidId, + int worldX, + int worldY, + int worldZ + ) { + World world = commandBuffer.getExternalData().getWorld(); + long hash = HashUtil.rehash(worldX, worldY, worldZ, 4030921250L); + long tick = commandBuffer.getExternalData().getWorld().getTick(); + int flowRateLimitTicks = Math.round(this.flowRate * world.getTps()); + return (hash + tick) % flowRateLimitTicks != 0L + ? BlockTickStrategy.CONTINUE + : this.process(world, tick, cachedAccessor, fluidSection, blockSection, fluid, fluidId, worldX, worldY, worldZ); + } + + public BlockTickStrategy process( + World world, + long tick, + @Nonnull FluidTicker.Accessor accessor, + @Nonnull FluidSection fluidSection, + @Nonnull BlockSection blockSection, + @Nonnull Fluid fluid, + int fluidId, + int worldX, + int worldY, + int worldZ + ) { + byte fluidLevel = fluidSection.getFluidLevel(worldX, worldY, worldZ); + int block = blockSection.get(worldX, worldY, worldZ); + if (isFullySolid(BlockType.getAssetMap().getAsset(block))) { + fluidSection.setFluid(worldX, worldY, worldZ, 0, (byte)0); + setTickingSurrounding(accessor, blockSection, worldX, worldY, worldZ); + return BlockTickStrategy.SLEEP; + } else { + switch (this.isAlive(accessor, fluidSection, blockSection, fluid, fluidId, fluidLevel, worldX, worldY, worldZ)) { + case ALIVE: + return this.spread(world, tick, accessor, fluidSection, blockSection, fluid, fluidId, fluidLevel, worldX, worldY, worldZ); + case DEMOTE: + if (fluidLevel == 1) { + fluidSection.setFluid(worldX, worldY, worldZ, 0, (byte)0); + setTickingSurrounding(accessor, blockSection, worldX, worldY, worldZ); + return BlockTickStrategy.SLEEP; + } + + fluidSection.setFluid(worldX, worldY, worldZ, fluidId, (byte)((fluidLevel == 0 ? fluid.getMaxFluidLevel() : fluidLevel) - 1)); + setTickingSurrounding(accessor, blockSection, worldX, worldY, worldZ); + return BlockTickStrategy.SLEEP; + case WAIT_FOR_ADJACENT_CHUNK: + return BlockTickStrategy.WAIT_FOR_ADJACENT_CHUNK_LOAD; + default: + return BlockTickStrategy.SLEEP; + } + } + } + + @Nonnull + protected FluidTicker.AliveStatus isAlive( + @Nonnull FluidTicker.Accessor accessor, + @Nonnull FluidSection fluidSection, + @Nonnull BlockSection blockSection, + Fluid fluid, + int fluidId, + byte fluidLevel, + int worldX, + int worldY, + int worldZ + ) { + if (!this.canDemote) { + return FluidTicker.AliveStatus.ALIVE; + } else { + int supportId = this.getSupportedById(); + BlockTypeAssetMap blockMap = BlockType.getAssetMap(); + FluidSection aboveFluidSection = ChunkUtil.chunkCoordinate(worldY + 1) == fluidSection.getY() + ? fluidSection + : accessor.getFluidSectionByBlock(worldX, worldY + 1, worldZ); + if (aboveFluidSection == null) { + return FluidTicker.AliveStatus.WAIT_FOR_ADJACENT_CHUNK; + } else { + int fluidAbove = aboveFluidSection.getFluidId(worldX, worldY + 1, worldZ); + if (fluidAbove != fluidId && fluidAbove != supportId) { + BlockType thisBlock = blockMap.getAsset(blockSection.get(worldX, worldY, worldZ)); + int thisRotation = blockSection.getRotationIndex(worldX, worldY, worldZ); + int thisFiller = blockSection.getFiller(worldX, worldY, worldZ); + boolean chunkNotLoaded = false; + + for (Vector2i offset : ORTO_OFFSETS) { + int x = offset.x; + int z = offset.y; + int blockX = x + worldX; + int blockZ = z + worldZ; + boolean isDifferentSection = !ChunkUtil.isSameChunkSection(worldX, worldY, worldZ, blockX, worldY, blockZ); + FluidSection otherFluidSection = isDifferentSection ? accessor.getFluidSectionByBlock(blockX, worldY, blockZ) : fluidSection; + BlockSection otherBlockSection = isDifferentSection ? accessor.getBlockSectionByBlock(blockX, worldY, blockZ) : blockSection; + if (otherFluidSection != null && otherBlockSection != null) { + int otherFluid = otherFluidSection.getFluidId(blockX, worldY, blockZ); + if (supportId != Integer.MIN_VALUE && otherFluid == supportId) { + BlockType sourceBlock = blockMap.getAsset(otherBlockSection.get(blockX, worldY, blockZ)); + if (sourceBlock != null) { + int sourceRotation = otherBlockSection.getRotationIndex(blockX, worldY, blockZ); + int sourceFiller = otherBlockSection.getFiller(blockX, worldY, blockZ); + if (!blocksFluidFrom(sourceBlock, sourceRotation, x, z, sourceFiller) + && !blocksFluidFrom(thisBlock, thisRotation, -x, -z, thisFiller)) { + return FluidTicker.AliveStatus.ALIVE; + } + } + } else { + byte otherFluidLevel = otherFluidSection.getFluidLevel(blockX, worldY, blockZ); + if (otherFluid != 0 && otherFluid == fluidId && otherFluidLevel > fluidLevel) { + BlockType sourceBlock = blockMap.getAsset(otherBlockSection.get(blockX, worldY, blockZ)); + if (sourceBlock != null) { + int sourceRotation = otherBlockSection.getRotationIndex(blockX, worldY, blockZ); + int sourceFiller = otherBlockSection.getFiller(blockX, worldY, blockZ); + if (!blocksFluidFrom(sourceBlock, sourceRotation, x, z, sourceFiller) + && !blocksFluidFrom(thisBlock, thisRotation, -x, -z, thisFiller)) { + return FluidTicker.AliveStatus.ALIVE; + } + } + } + } + } else { + chunkNotLoaded = true; + } + } + + return chunkNotLoaded ? FluidTicker.AliveStatus.WAIT_FOR_ADJACENT_CHUNK : FluidTicker.AliveStatus.DEMOTE; + } else { + return FluidTicker.AliveStatus.ALIVE; + } + } + } + } + + protected abstract BlockTickStrategy spread( + World var1, long var2, FluidTicker.Accessor var4, FluidSection var5, BlockSection var6, Fluid var7, int var8, byte var9, int var10, int var11, int var12 + ); + + public static void setTickingSurrounding(@Nonnull FluidTicker.Accessor accessor, BlockSection blockSection, int worldX, int worldY, int worldZ) { + for (int y = -1; y <= 1; y++) { + for (int z = -1; z <= 1; z++) { + for (int x = -1; x <= 1; x++) { + int bx = worldX + x; + int by = worldY + y; + int bz = worldZ + z; + BlockSection chunk = ChunkUtil.isSameChunkSection(worldX, worldY, worldZ, bx, by, bz) + ? blockSection + : accessor.getBlockSectionByBlock(bx, by, bz); + if (chunk != null) { + chunk.setTicking(bx, by, bz, true); + } + } + } + } + } + + protected int getSpreadOffsets( + @Nonnull BlockTypeAssetMap blockMap, + @Nonnull FluidTicker.Accessor accessor, + @Nonnull FluidSection fluidSection, + BlockSection blockSection, + int worldX, + int worldY, + int worldZ, + @Nonnull Vector2i[] offsetArray, + int fluidId, + int maxDropDistance + ) { + int shortestDistanceToDrop = Integer.MAX_VALUE; + int offsets = 0; + if (worldY <= 0) { + return offsets; + } else { + for (int i = 0; i < offsetArray.length; i++) { + Vector2i offset = offsetArray[i]; + int distance = this.distanceToDrop(blockMap, accessor, fluidSection, blockSection, worldX, worldY, worldZ, offset, fluidId, maxDropDistance); + if (distance == 2147483646) { + return 2147483646; + } + + if (distance < shortestDistanceToDrop) { + offsets = 0; + shortestDistanceToDrop = distance; + } + + if (distance <= shortestDistanceToDrop && distance != Integer.MAX_VALUE) { + offsets |= 1 << i; + } + } + + return offsets; + } + } + + protected int distanceToDrop( + @Nonnull BlockTypeAssetMap blockMap, + @Nonnull FluidTicker.Accessor accessor, + @Nonnull FluidSection fluidSection, + BlockSection blockSection, + int worldX, + int worldY, + int worldZ, + @Nonnull Vector2i offset, + int fluidId, + int maxDropDistance + ) { + int ox = offset.x; + int oz = offset.y; + int yMinus1 = worldY - 1; + FluidSection belowFluidSection = fluidSection; + BlockSection belowBlockSection = blockSection; + boolean isBelowDifferent = fluidSection.getY() != ChunkUtil.chunkCoordinate(yMinus1); + if (isBelowDifferent) { + belowFluidSection = accessor.getFluidSectionByBlock(worldX, yMinus1, worldZ); + belowBlockSection = accessor.getBlockSectionByBlock(worldX, yMinus1, worldZ); + } + + int curX = worldX; + int curZ = worldZ; + + for (int i = 1; i < maxDropDistance; i++) { + int blockX = worldX + ox * i; + int blockZ = worldZ + oz * i; + if (!ChunkUtil.isSameChunk(curX, curZ, blockX, blockZ)) { + curX = blockX; + curZ = blockZ; + fluidSection = accessor.getFluidSectionByBlock(blockX, worldY, blockZ); + blockSection = accessor.getBlockSectionByBlock(blockX, worldY, blockZ); + if (isBelowDifferent) { + belowFluidSection = accessor.getFluidSectionByBlock(worldX, yMinus1, worldZ); + belowBlockSection = accessor.getBlockSectionByBlock(worldX, yMinus1, worldZ); + } else { + belowFluidSection = fluidSection; + belowBlockSection = blockSection; + } + } + + if (fluidSection == null || blockSection == null || belowFluidSection == null || belowBlockSection == null) { + return 2147483646; + } + + int otherFluidId = fluidSection.getFluidId(blockX, worldY, blockZ); + BlockType block = blockMap.getAsset(blockSection.get(blockX, worldY, blockZ)); + if (otherFluidId != 0 && !this.isSelfFluid(fluidId, otherFluidId) || otherFluidId == 0 && isSolid(block)) { + break; + } + + BlockType belowBlock = blockMap.getAsset(belowBlockSection.get(blockX, yMinus1, blockZ)); + if (!isSolid(belowBlock)) { + return i; + } + } + + return Integer.MAX_VALUE; + } + + public static boolean isFullySolid(@Nonnull BlockType blockType) { + DrawType drawType = blockType.getDrawType(); + return blockType.getMaterial() == BlockMaterial.Solid && (drawType == DrawType.Cube || drawType == DrawType.CubeWithModel); + } + + public static boolean isSolid(@Nonnull BlockType blockType) { + DrawType drawType = blockType.getDrawType(); + return drawType == DrawType.Cube || drawType == DrawType.CubeWithModel; + } + + public static boolean blocksFluidFrom(@Nonnull BlockType blockType, int rotationIndex, int offsetX, int offsetZ) { + return blocksFluidFrom(blockType, rotationIndex, offsetX, offsetZ, 0); + } + + public static boolean blocksFluidFrom(@Nonnull BlockType blockType, int rotationIndex, int offsetX, int offsetZ, int filler) { + if (blockType.getMaterial() != BlockMaterial.Solid) { + return false; + } else if (isFullySolid(blockType)) { + return true; + } else { + int hitboxIndex = blockType.getHitboxTypeIndex(); + BlockBoundingBoxes hitboxAsset = BlockBoundingBoxes.getAssetMap().getAsset(hitboxIndex); + if (hitboxAsset == null) { + return true; + } else { + BlockBoundingBoxes.RotatedVariantBoxes rotatedHitbox = hitboxAsset.get(rotationIndex); + Box boundingBox = rotatedHitbox.getBoundingBox(); + if (!hitboxAsset.protrudesUnitBox() && filler == 0) { + double width = boundingBox.max.x - boundingBox.min.x; + double height = boundingBox.max.y - boundingBox.min.y; + double depth = boundingBox.max.z - boundingBox.min.z; + boolean isTall = height > 0.9; + if (!isTall) { + return false; + } else { + boolean isFullDepth = depth > 0.9; + boolean isPartialWidth = width < 0.6; + if (!isPartialWidth || !isFullDepth) { + boolean isFullWidth = width > 0.9; + boolean isPartialDepth = depth < 0.6; + if (!isFullWidth || !isPartialDepth) { + Box[] detailBoxes = rotatedHitbox.getDetailBoxes(); + if (detailBoxes.length > 1) { + return boxesBlockFace(detailBoxes, offsetX, offsetZ); + } else { + double faceCoverage = 0.0; + if (offsetX > 0 && boundingBox.min.x < 0.1) { + faceCoverage = height * depth; + } else if (offsetX < 0 && boundingBox.max.x > 0.9) { + faceCoverage = height * depth; + } else if (offsetZ > 0 && boundingBox.min.z < 0.1) { + faceCoverage = height * width; + } else if (offsetZ < 0 && boundingBox.max.z > 0.9) { + faceCoverage = height * width; + } + + return faceCoverage > 0.9; + } + } else if (offsetZ == 0) { + return false; + } else { + return offsetZ > 0 ? boundingBox.min.z < 0.1 : boundingBox.max.z > 0.9; + } + } else if (offsetX == 0) { + return false; + } else { + return offsetX > 0 ? boundingBox.min.x < 0.1 : boundingBox.max.x > 0.9; + } + } + } else { + int fillerX = FillerBlockUtil.unpackX(filler); + int fillerY = FillerBlockUtil.unpackY(filler); + int fillerZ = FillerBlockUtil.unpackZ(filler); + Box[] detailBoxes = rotatedHitbox.getDetailBoxes(); + double maxCrossSectionCoverage = 0.0; + + for (Box box : detailBoxes) { + double clampedMinX = Math.max(box.min.x, (double)fillerX); + double clampedMaxX = Math.min(box.max.x, (double)(fillerX + 1)); + double clampedMinY = Math.max(box.min.y, (double)fillerY); + double clampedMaxY = Math.min(box.max.y, (double)(fillerY + 1)); + double clampedMinZ = Math.max(box.min.z, (double)fillerZ); + double clampedMaxZ = Math.min(box.max.z, (double)(fillerZ + 1)); + if (!(clampedMaxX <= clampedMinX) && !(clampedMaxY <= clampedMinY) && !(clampedMaxZ <= clampedMinZ)) { + double boxWidth = clampedMaxX - clampedMinX; + double boxHeight = clampedMaxY - clampedMinY; + double boxDepth = clampedMaxZ - clampedMinZ; + double crossSectionCoverage = 0.0; + if (offsetX != 0) { + crossSectionCoverage = boxHeight * boxDepth; + } else if (offsetZ != 0) { + crossSectionCoverage = boxHeight * boxWidth; + } + + maxCrossSectionCoverage = Math.max(maxCrossSectionCoverage, crossSectionCoverage); + } + } + + return maxCrossSectionCoverage > 0.9; + } + } + } + } + + private static boolean boxesBlockFace(Box[] boxes, int offsetX, int offsetZ) { + double totalArea = 0.0; + + for (Box box : boxes) { + double areaOnFace = 0.0; + if (offsetX > 0 && box.min.x < 0.1) { + double height = box.max.y - box.min.y; + double depth = box.max.z - box.min.z; + areaOnFace = height * depth; + } else if (offsetX < 0 && box.max.x > 0.9) { + double height = box.max.y - box.min.y; + double depth = box.max.z - box.min.z; + areaOnFace = height * depth; + } else if (offsetZ > 0 && box.min.z < 0.1) { + double height = box.max.y - box.min.y; + double width = box.max.x - box.min.x; + areaOnFace = height * width; + } else if (offsetZ < 0 && box.max.z > 0.9) { + double height = box.max.y - box.min.y; + double width = box.max.x - box.min.x; + areaOnFace = height * width; + } + + totalArea += areaOnFace; + } + + return totalArea > 0.9; + } + + public boolean isSelfFluid(int selfFluidId, int otherFluidId) { + return selfFluidId == otherFluidId || otherFluidId == this.getSupportedById(); + } + + public boolean canDemote() { + return this.canDemote; + } + + public interface Accessor { + @Nullable + FluidSection getFluidSection(int var1, int var2, int var3); + + @Nullable + default FluidSection getFluidSectionByBlock(int bx, int by, int bz) { + return this.getFluidSection(ChunkUtil.chunkCoordinate(bx), ChunkUtil.chunkCoordinate(by), ChunkUtil.chunkCoordinate(bz)); + } + + @Nullable + BlockSection getBlockSection(int var1, int var2, int var3); + + @Nullable + default BlockSection getBlockSectionByBlock(int bx, int by, int bz) { + return this.getBlockSection(ChunkUtil.chunkCoordinate(bx), ChunkUtil.chunkCoordinate(by), ChunkUtil.chunkCoordinate(bz)); + } + + @Deprecated(forRemoval = true) + void setBlock(int var1, int var2, int var3, int var4); + } + + protected static enum AliveStatus { + ALIVE, + DEMOTE, + WAIT_FOR_ADJACENT_CHUNK; + + private AliveStatus() { + } + } + + public static class CachedAccessor extends AbstractCachedAccessor implements FluidTicker.Accessor { + private static final ThreadLocal THREAD_LOCAL = ThreadLocal.withInitial(FluidTicker.CachedAccessor::new); + private static final int FLUID_COMPONENT = 0; + private static final int BLOCK_COMPONENT = 1; + private CommandBuffer commandBuffer; + public FluidSection selfFluidSection; + public BlockSection selfBlockSection; + + protected CachedAccessor() { + super(2); + } + + @Nonnull + public static FluidTicker.CachedAccessor of(CommandBuffer commandBuffer, @Nonnull FluidSection section, BlockSection blockSection, int radius) { + FluidTicker.CachedAccessor accessor = THREAD_LOCAL.get(); + accessor.init(commandBuffer, section, blockSection, radius); + accessor.insertSectionComponent(0, section, section.getX(), section.getY(), section.getZ()); + accessor.insertSectionComponent(1, blockSection, section.getX(), section.getY(), section.getZ()); + return accessor; + } + + private void init(CommandBuffer commandBuffer, @Nonnull FluidSection section, BlockSection blockSection, int radius) { + this.init(commandBuffer, section.getX(), section.getY(), section.getZ(), radius); + this.commandBuffer = commandBuffer; + this.selfFluidSection = section; + this.selfBlockSection = blockSection; + } + + @Override + public FluidSection getFluidSection(int cx, int cy, int cz) { + return this.getComponentSection(cx, cy, cz, 0, FluidSection.getComponentType()); + } + + @Override + public BlockSection getBlockSection(int cx, int cy, int cz) { + return this.getComponentSection(cx, cy, cz, 1, BlockSection.getComponentType()); + } + + @Override + public void setBlock(int x, int y, int z, int blockId) { + Ref chunk = this.getChunk(ChunkUtil.chunkCoordinate(x), ChunkUtil.chunkCoordinate(z)); + if (chunk != null && chunk.isValid()) { + this.commandBuffer.run(store -> { + if (chunk.isValid()) { + WorldChunk wc = store.getComponent(chunk, WorldChunk.getComponentType()); + if (wc != null) { + wc.setBlock(x, y, z, blockId); + } + } + }); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/fluid/FluidTypePacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/fluid/FluidTypePacketGenerator.java new file mode 100644 index 0000000..d0322e3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/fluid/FluidTypePacketGenerator.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.server.core.asset.type.fluid; + +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateFluids; +import com.hypixel.hytale.server.core.asset.packet.AssetPacketGenerator; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class FluidTypePacketGenerator extends AssetPacketGenerator> { + public FluidTypePacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + UpdateFluids packet = new UpdateFluids(); + packet.type = UpdateType.Init; + HashMap fluidTypes = new HashMap<>(); + + for (Entry entry : assets.entrySet()) { + int index = assetMap.getIndex(entry.getKey()); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key: " + entry.getKey()); + } + + fluidTypes.put(index, entry.getValue().toPacket()); + } + + packet.fluids = fluidTypes; + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateUpdatePacket( + @Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets, @Nonnull AssetUpdateQuery query + ) { + UpdateFluids packet = new UpdateFluids(); + packet.type = UpdateType.AddOrUpdate; + HashMap fluidTypes = new HashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + int index = assetMap.getIndex(entry.getKey()); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key: " + entry.getKey()); + } + + fluidTypes.put(index, entry.getValue().toPacket()); + } + + packet.fluids = fluidTypes; + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateRemovePacket( + @Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed, @Nonnull AssetUpdateQuery query + ) { + UpdateFluids packet = new UpdateFluids(); + packet.type = UpdateType.Remove; + HashMap fluidTypes = new HashMap<>(); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key: " + key); + } + + Fluid fluid = new Fluid(key); + fluidTypes.put(index, fluid.toPacket()); + } + + packet.fluids = fluidTypes; + packet.maxId = assetMap.getNextIndex(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/fluidfx/FluidFXPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/fluidfx/FluidFXPacketGenerator.java new file mode 100644 index 0000000..d064693 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/fluidfx/FluidFXPacketGenerator.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.asset.type.fluidfx; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateFluidFX; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.fluidfx.config.FluidFX; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class FluidFXPacketGenerator extends SimpleAssetPacketGenerator> { + public FluidFXPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + UpdateFluidFX packet = new UpdateFluidFX(); + packet.type = UpdateType.Init; + packet.fluidFX = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.fluidFX.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateUpdatePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets) { + UpdateFluidFX packet = new UpdateFluidFX(); + packet.type = UpdateType.AddOrUpdate; + packet.fluidFX = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.fluidFX.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateRemovePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed) { + UpdateFluidFX packet = new UpdateFluidFX(); + packet.type = UpdateType.Remove; + packet.fluidFX = new Int2ObjectOpenHashMap<>(); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.fluidFX.put(index, null); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/fluidfx/config/FluidFX.java b/src/com/hypixel/hytale/server/core/asset/type/fluidfx/config/FluidFX.java new file mode 100644 index 0000000..3b37de1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/fluidfx/config/FluidFX.java @@ -0,0 +1,362 @@ +package com.hypixel.hytale.server.core.asset.type.fluidfx.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.FluidFXMovementSettings; +import com.hypixel.hytale.protocol.FluidFog; +import com.hypixel.hytale.protocol.NearFar; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FluidFX implements JsonAssetWithMap>, NetworkSerializable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + FluidFX.class, + FluidFX::new, + Codec.STRING, + (fluidFX, s) -> fluidFX.id = s, + fluidFX -> fluidFX.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("Fog", new EnumCodec<>(FluidFog.class)), + (fluidFX, fluidFog) -> fluidFX.fog = fluidFog, + fluidFX -> fluidFX.fog, + (fluidFX, parent) -> fluidFX.fog = parent.fog + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("FogColor", ProtocolCodecs.COLOR), + (fluidFX, o) -> fluidFX.fogColor = o, + fluidFX -> fluidFX.fogColor, + (fluidFX, parent) -> fluidFX.fogColor = parent.fogColor + ) + .add() + .appendInherited(new KeyedCodec<>("FogDistance", Codec.DOUBLE_ARRAY), (weather, o) -> { + weather.fogDistance = new float[2]; + weather.fogDistance[0] = (float)o[0]; + weather.fogDistance[1] = (float)o[1]; + }, weather -> new double[]{weather.fogDistance[0], weather.fogDistance[1]}, (weather, parent) -> weather.fogDistance = parent.fogDistance) + .addValidator(Validators.doubleArraySize(2)) + .add() + .appendInherited( + new KeyedCodec<>("FogDepthStart", Codec.DOUBLE), + (fluidFX, s) -> fluidFX.fogDepthStart = s.floatValue(), + fluidFX -> (double)fluidFX.fogDepthStart, + (fluidFX, parent) -> fluidFX.fogDepthStart = parent.fogDepthStart + ) + .add() + .appendInherited( + new KeyedCodec<>("FogDepthFalloff", Codec.DOUBLE), + (fluidFX, s) -> fluidFX.fogDepthFalloff = s.floatValue(), + fluidFX -> (double)fluidFX.fogDepthFalloff, + (fluidFX, parent) -> fluidFX.fogDepthFalloff = parent.fogDepthFalloff + ) + .add() + .appendInherited( + new KeyedCodec<>("ColorsSaturation", Codec.DOUBLE), + (fluidFX, s) -> fluidFX.colorsSaturation = s.floatValue(), + fluidFX -> (double)fluidFX.colorsSaturation, + (fluidFX, parent) -> fluidFX.colorsSaturation = parent.colorsSaturation + ) + .add() + .appendInherited( + new KeyedCodec<>("ColorsFilter", Codec.DOUBLE_ARRAY), + (weather, o) -> { + weather.colorsFilter = new float[3]; + weather.colorsFilter[0] = (float)o[0]; + weather.colorsFilter[1] = (float)o[1]; + weather.colorsFilter[2] = (float)o[2]; + }, + weather -> new double[]{weather.colorsFilter[0], weather.colorsFilter[1], weather.colorsFilter[2]}, + (weather, parent) -> weather.colorsFilter = parent.colorsFilter + ) + .addValidator(Validators.doubleArraySize(3)) + .add() + .appendInherited( + new KeyedCodec<>("DistortionAmplitude", Codec.DOUBLE), + (fluidFX, s) -> fluidFX.distortionAmplitude = s.floatValue(), + fluidFX -> (double)fluidFX.distortionAmplitude, + (fluidFX, parent) -> fluidFX.distortionAmplitude = parent.distortionAmplitude + ) + .add() + .appendInherited( + new KeyedCodec<>("DistortionFrequency", Codec.DOUBLE), + (fluidFX, s) -> fluidFX.distortionFrequency = s.floatValue(), + fluidFX -> (double)fluidFX.distortionFrequency, + (fluidFX, parent) -> fluidFX.distortionFrequency = parent.distortionFrequency + ) + .add() + .appendInherited( + new KeyedCodec<>("Particle", FluidParticle.CODEC), + (fluidFX, s) -> fluidFX.particle = s, + fluidFX -> fluidFX.particle, + (fluidFX, parent) -> fluidFX.particle = parent.particle + ) + .add() + .appendInherited( + new KeyedCodec<>( + "MovementSettings", + BuilderCodec.builder(FluidFXMovementSettings.class, FluidFXMovementSettings::new) + .append( + new KeyedCodec<>("SwimUpSpeed", Codec.DOUBLE), + (movementSettings, val) -> movementSettings.swimUpSpeed = val.floatValue(), + movementSettings -> (double)movementSettings.swimUpSpeed + ) + .add() + .append( + new KeyedCodec<>("SwimDownSpeed", Codec.DOUBLE), + (movementSettings, val) -> movementSettings.swimDownSpeed = val.floatValue(), + movementSettings -> (double)movementSettings.swimDownSpeed + ) + .add() + .append( + new KeyedCodec<>("SinkSpeed", Codec.DOUBLE), + (movementSettings, val) -> movementSettings.sinkSpeed = val.floatValue(), + movementSettings -> (double)movementSettings.sinkSpeed + ) + .add() + .append( + new KeyedCodec<>("HorizontalSpeedMultiplier", Codec.DOUBLE), + (movementSettings, val) -> movementSettings.horizontalSpeedMultiplier = val.floatValue(), + movementSettings -> (double)movementSettings.horizontalSpeedMultiplier + ) + .add() + .append( + new KeyedCodec<>("FieldOfViewMultiplier", Codec.DOUBLE), + (movementSettings, val) -> movementSettings.fieldOfViewMultiplier = val.floatValue(), + movementSettings -> (double)movementSettings.fieldOfViewMultiplier + ) + .add() + .append( + new KeyedCodec<>("EntryVelocityMultiplier", Codec.DOUBLE), + (movementSettings, val) -> movementSettings.entryVelocityMultiplier = val.floatValue(), + movementSettings -> (double)movementSettings.entryVelocityMultiplier + ) + .add() + .build() + ), + (fluidFX, movementSettings) -> fluidFX.movementSettings = movementSettings, + fluidFX -> fluidFX.movementSettings, + (fluidFX, parent) -> fluidFX.movementSettings = parent.movementSettings + ) + .add() + .build(); + public static final Color DEFAULT_FOG_COLOR = new Color((byte)-1, (byte)-1, (byte)-1); + public static final float[] DEFAULT_FOG_DISTANCE = new float[]{0.0F, 32.0F}; + public static final float[] DEFAULT_COLORS_FILTER = new float[]{1.0F, 1.0F, 1.0F}; + public static final int EMPTY_ID = 0; + public static final String EMPTY = "Empty"; + public static final FluidFX EMPTY_FLUID_FX = getUnknownFor("Empty"); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(FluidFX::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + @Nonnull + protected FluidFog fog = FluidFog.Color; + protected Color fogColor = DEFAULT_FOG_COLOR; + protected float[] fogDistance = DEFAULT_FOG_DISTANCE; + protected float fogDepthStart = 40.0F; + protected float fogDepthFalloff = 10.0F; + protected float colorsSaturation = 1.0F; + protected float[] colorsFilter = DEFAULT_COLORS_FILTER; + protected float distortionAmplitude = 8.0F; + protected float distortionFrequency = 4.0F; + protected FluidParticle particle; + protected FluidFXMovementSettings movementSettings; + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(FluidFX.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public FluidFX( + String id, + FluidFog fog, + Color fogColor, + float[] fogDistance, + float fogDepthStart, + float fogDepthFalloff, + float colorsSaturation, + float[] colorsFilter, + float distortionAmplitude, + float distortionFrequency, + FluidParticle particle, + FluidFXMovementSettings movementSettings + ) { + this.id = id; + this.fog = fog; + this.fogColor = fogColor; + this.fogDistance = fogDistance; + this.fogDepthStart = fogDepthStart; + this.fogDepthFalloff = fogDepthFalloff; + this.colorsSaturation = colorsSaturation; + this.colorsFilter = colorsFilter; + this.distortionAmplitude = distortionAmplitude; + this.distortionFrequency = distortionFrequency; + this.particle = particle; + this.movementSettings = movementSettings; + } + + public FluidFX(String id) { + this.id = id; + } + + protected FluidFX() { + } + + @Nonnull + public com.hypixel.hytale.protocol.FluidFX toPacket() { + com.hypixel.hytale.protocol.FluidFX cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.FluidFX packet = new com.hypixel.hytale.protocol.FluidFX(); + packet.id = this.id; + packet.fogMode = this.fog; + packet.fogColor = this.fogColor; + packet.fogDistance = new NearFar(this.fogDistance[0], this.fogDistance[1]); + packet.fogDepthStart = this.fogDepthStart; + packet.fogDepthFalloff = this.fogDepthFalloff; + packet.colorFilter = new Color((byte)(this.colorsFilter[0] * 255.0F), (byte)(this.colorsFilter[1] * 255.0F), (byte)(this.colorsFilter[2] * 255.0F)); + packet.colorSaturation = this.colorsSaturation; + packet.distortionAmplitude = this.distortionAmplitude; + packet.distortionFrequency = this.distortionFrequency; + if (this.particle != null) { + packet.particle = this.particle.toPacket(); + } + + if (this.movementSettings != null) { + packet.movementSettings = this.movementSettings; + } + + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + public String getId() { + return this.id; + } + + public FluidFog getFog() { + return this.fog; + } + + public Color getFogColor() { + return this.fogColor; + } + + public float[] getFogDistance() { + return this.fogDistance; + } + + public float getColorsSaturation() { + return this.colorsSaturation; + } + + public float[] getColorsFilter() { + return this.colorsFilter; + } + + public float getDistortionAmplitude() { + return this.distortionAmplitude; + } + + public float getDistortionFrequency() { + return this.distortionFrequency; + } + + public float getFogDepthStart() { + return this.fogDepthStart; + } + + public float getFogDepthFalloff() { + return this.fogDepthFalloff; + } + + public FluidParticle getParticle() { + return this.particle; + } + + public FluidFXMovementSettings getMovementSettings() { + return this.movementSettings; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + FluidFX fluidFX = (FluidFX)o; + return this.id != null ? this.id.equals(fluidFX.id) : fluidFX.id == null; + } else { + return false; + } + } + + @Override + public int hashCode() { + return this.id != null ? this.id.hashCode() : 0; + } + + @Nonnull + @Override + public String toString() { + return "FluidFX{id='" + + this.id + + "', fog=" + + this.fog + + ", fogColor='" + + this.fogColor + + "', fogDistance=" + + Arrays.toString(this.fogDistance) + + ", fogDepthStart=" + + this.fogDepthStart + + ", fogDepthFalloff=" + + this.fogDepthFalloff + + ", colorsSaturation=" + + this.colorsSaturation + + ", colorsFilter=" + + Arrays.toString(this.colorsFilter) + + ", distortionAmplitude=" + + this.distortionAmplitude + + ", distortionFrequency=" + + this.distortionFrequency + + ", particle=" + + this.particle + + ", movementSettings=" + + this.movementSettings + + "}"; + } + + @Nonnull + public static FluidFX getUnknownFor(String unknownId) { + return new FluidFX(unknownId); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/fluidfx/config/FluidParticle.java b/src/com/hypixel/hytale/server/core/asset/type/fluidfx/config/FluidParticle.java new file mode 100644 index 0000000..992b231 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/fluidfx/config/FluidParticle.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.server.core.asset.type.fluidfx.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import javax.annotation.Nonnull; + +public class FluidParticle implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(FluidParticle.class, FluidParticle::new) + .documentation("Particle System that can be spawned in relation to a fluid.") + .append(new KeyedCodec<>("SystemId", Codec.STRING), (particle, s) -> particle.systemId = s, particle -> particle.systemId) + .documentation("The id of the particle system.") + .addValidator(Validators.nonNull()) + .addValidator(ParticleSystem.VALIDATOR_CACHE.getValidator()) + .add() + .append(new KeyedCodec<>("Color", ProtocolCodecs.COLOR), (particle, o) -> particle.color = o, particle -> particle.color) + .documentation("The colour used if none was specified in the particle settings.") + .add() + .append(new KeyedCodec<>("Scale", Codec.FLOAT), (particle, f) -> particle.scale = f, particle -> particle.scale) + .documentation("The scale of the particle system.") + .add() + .build(); + protected String systemId; + protected Color color; + protected float scale = 1.0F; + private SoftReference cachedPacket; + + public FluidParticle(String systemId, Color color, float scale) { + this.systemId = systemId; + this.color = color; + this.scale = scale; + } + + protected FluidParticle() { + } + + public String getSystemId() { + return this.systemId; + } + + public Color getColor() { + return this.color; + } + + public float getScale() { + return this.scale; + } + + @Nonnull + public com.hypixel.hytale.protocol.FluidParticle toPacket() { + com.hypixel.hytale.protocol.FluidParticle cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.FluidParticle packet = new com.hypixel.hytale.protocol.FluidParticle(); + packet.systemId = this.systemId; + packet.color = this.color; + packet.scale = this.scale; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + @Nonnull + @Override + public String toString() { + return "FluidParticle{systemId='" + this.systemId + "', color=" + this.color + ", scale=" + this.scale + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gamemode/GameModeType.java b/src/com/hypixel/hytale/server/core/asset/type/gamemode/GameModeType.java new file mode 100644 index 0000000..4d5dbbe --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gamemode/GameModeType.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.server.core.asset.type.gamemode; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class GameModeType implements JsonAssetWithMap> { + @Nonnull + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + GameModeType.class, + GameModeType::new, + Codec.STRING, + (gmType, s) -> gmType.id = s, + gmType -> gmType.id, + (gmType, data) -> gmType.data = data, + gmType -> gmType.data + ) + .append(new KeyedCodec<>("PermissionGroups", Codec.STRING_ARRAY), (gmType, o) -> gmType.permissionGroups = o, gmType -> gmType.permissionGroups) + .add() + .append( + new KeyedCodec<>("InteractionsOnEnter", RootInteraction.CHILD_ASSET_CODEC), + (gmType, interactions) -> gmType.interactionsOnEnter = interactions, + gmType -> gmType.interactionsOnEnter + ) + .addValidatorLate(() -> RootInteraction.VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + private static AssetStore> ASSET_STORE; + @Nonnull + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(GameModeType::getAssetStore)); + protected AssetExtraInfo.Data data; + protected String id; + private String[] permissionGroups; + private String interactionsOnEnter; + + @Nonnull + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(GameModeType.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + @Nonnull + public static GameModeType fromGameMode(@Nonnull GameMode gameMode) { + GameModeType type = (GameModeType)((DefaultAssetMap)getAssetStore().getAssetMap()).getAsset(gameMode.name()); + return type == null ? new GameModeType() : type; + } + + protected GameModeType() { + } + + @Nullable + public String getInteractionsOnEnter() { + return this.interactionsOnEnter; + } + + @Nonnull + public String[] getPermissionGroups() { + return this.permissionGroups == null ? ArrayUtil.EMPTY_STRING_ARRAY : this.permissionGroups; + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/BrokenPenalties.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/BrokenPenalties.java new file mode 100644 index 0000000..ca07c1f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/BrokenPenalties.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class BrokenPenalties { + public static final BrokenPenalties DEFAULT = new BrokenPenalties(); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(BrokenPenalties.class, BrokenPenalties::new) + .addField(new KeyedCodec<>("Tool", Codec.DOUBLE), (o, i) -> o.tool = i, o -> o.tool) + .addField(new KeyedCodec<>("Armor", Codec.DOUBLE), (o, i) -> o.armor = i, o -> o.armor) + .addField(new KeyedCodec<>("Weapon", Codec.DOUBLE), (o, i) -> o.weapon = i, o -> o.weapon) + .build(); + private Double tool; + private Double armor; + private Double weapon; + + public BrokenPenalties() { + } + + public double getTool(double defaultValue) { + return this.tool == null ? defaultValue : this.tool; + } + + public double getArmor(double defaultValue) { + return this.armor == null ? defaultValue : this.armor; + } + + public double getWeapon(double defaultValue) { + return this.weapon == null ? defaultValue : this.weapon; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/CameraEffectsConfig.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/CameraEffectsConfig.java new file mode 100644 index 0000000..568f892 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/CameraEffectsConfig.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.server.core.asset.type.camera.CameraEffect; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntMaps; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class CameraEffectsConfig { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(CameraEffectsConfig.class, CameraEffectsConfig::new) + .appendInherited( + new KeyedCodec<>("DamageEffects", new MapCodec<>(CameraEffect.CHILD_ASSET_CODEC, HashMap::new)), + (config, damageEffectIds) -> config.damageEffectIds = damageEffectIds, + config -> config.damageEffectIds, + (config, parent) -> config.damageEffectIds = parent.damageEffectIds + ) + .addValidator(DamageCause.VALIDATOR_CACHE.getMapKeyValidator()) + .addValidator(CameraEffect.VALIDATOR_CACHE.getMapValueValidator()) + .documentation("The default damage camera effects") + .add() + .afterDecode(config -> { + if (config.damageEffectIds != null) { + config.damageEffectIndices = new Int2IntOpenHashMap(); + + for (Entry entry : config.damageEffectIds.entrySet()) { + int key = DamageCause.getAssetMap().getIndex(entry.getKey()); + int effectIndex = CameraEffect.getAssetMap().getIndex(entry.getValue()); + config.damageEffectIndices.put(key, effectIndex); + } + } + }) + .build(); + protected Map damageEffectIds; + @Nonnull + protected transient Int2IntMap damageEffectIndices = Int2IntMaps.EMPTY_MAP; + + public CameraEffectsConfig() { + } + + public int getCameraEffectIndex(int damageCauseIndex) { + return this.damageEffectIndices.getOrDefault(damageCauseIndex, Integer.MIN_VALUE); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/CombatConfig.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/CombatConfig.java new file mode 100644 index 0000000..0c1cf39 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/CombatConfig.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import java.time.Duration; +import javax.annotation.Nonnull; + +public class CombatConfig { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(CombatConfig.class, CombatConfig::new) + .appendInherited( + new KeyedCodec<>("OutOfCombatDelaySeconds", Codec.DURATION_SECONDS), + (combatConfig, v) -> combatConfig.outOfCombatDelay = v, + combatConfig -> combatConfig.outOfCombatDelay, + (combatConfig, parent) -> combatConfig.outOfCombatDelay = parent.outOfCombatDelay + ) + .documentation("Delay before an entity is considered out of combat. Expressed in seconds.") + .addValidator(Validators.greaterThanOrEqual(Duration.ZERO)) + .add() + .appendInherited( + new KeyedCodec<>("StaminaBrokenEffectId", Codec.STRING), + (combatConfig, s) -> combatConfig.staminaBrokenEffectId = s, + combatConfig -> combatConfig.staminaBrokenEffectId, + (combatConfig, parent) -> combatConfig.staminaBrokenEffectId = parent.staminaBrokenEffectId + ) + .documentation("The id of the EntityEffect to apply upon stamina being depleted due to damage.") + .addValidator(Validators.nonNull()) + .addValidator(EntityEffect.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("DisplayHealthBars", Codec.BOOLEAN), + (combatConfig, v) -> combatConfig.displayHealthBars = v, + combatConfig -> combatConfig.displayHealthBars, + (combatConfig, parent) -> combatConfig.displayHealthBars = parent.displayHealthBars + ) + .documentation("Whether to display health bars above entities. Clients can still disable this in their settings.") + .add() + .appendInherited( + new KeyedCodec<>("DisplayCombatText", Codec.BOOLEAN), + (combatConfig, v) -> combatConfig.displayCombatText = v, + combatConfig -> combatConfig.displayCombatText, + (combatConfig, parent) -> combatConfig.displayCombatText = parent.displayCombatText + ) + .documentation("Whether to display combat text (damage numbers) on entities. Clients can still disable this in their settings.") + .add() + .afterDecode(combatConfig -> combatConfig.staminaBrokenEffectIndex = EntityEffect.getAssetMap().getIndex(combatConfig.staminaBrokenEffectId)) + .appendInherited( + new KeyedCodec<>("DisableNPCIncomingDamage", Codec.BOOLEAN), + (combatConfig, v) -> combatConfig.disableNpcIncomingDamage = v, + combatConfig -> combatConfig.disableNpcIncomingDamage, + (combatConfig, parent) -> combatConfig.disableNpcIncomingDamage = parent.disableNpcIncomingDamage + ) + .documentation("Whether NPCs can take damage.") + .add() + .appendInherited( + new KeyedCodec<>("DisablePlayerIncomingDamage", Codec.BOOLEAN), + (combatConfig, v) -> combatConfig.disablePlayerIncomingDamage = v, + combatConfig -> combatConfig.disablePlayerIncomingDamage, + (combatConfig, parent) -> combatConfig.disablePlayerIncomingDamage = parent.disablePlayerIncomingDamage + ) + .documentation("Whether players can take damage.") + .add() + .build(); + @Nonnull + protected Duration outOfCombatDelay = Duration.ofMillis(5000L); + protected String staminaBrokenEffectId = "Stamina_Broken"; + private int staminaBrokenEffectIndex; + protected boolean displayHealthBars = true; + protected boolean displayCombatText = true; + protected boolean disableNpcIncomingDamage = false; + protected boolean disablePlayerIncomingDamage = false; + + public CombatConfig() { + this.staminaBrokenEffectIndex = EntityEffect.getAssetMap().getIndex(this.staminaBrokenEffectId); + } + + @Nonnull + public Duration getOutOfCombatDelay() { + return this.outOfCombatDelay; + } + + public int getStaminaBrokenEffectIndex() { + return this.staminaBrokenEffectIndex; + } + + public boolean isDisplayHealthBars() { + return this.displayHealthBars; + } + + public boolean isDisplayCombatText() { + return this.displayCombatText; + } + + public boolean isNpcIncomingDamageDisabled() { + return this.disableNpcIncomingDamage; + } + + public boolean isPlayerIncomingDamageDisabled() { + return this.disablePlayerIncomingDamage; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/CraftingConfig.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/CraftingConfig.java new file mode 100644 index 0000000..66c92ef --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/CraftingConfig.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class CraftingConfig { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(CraftingConfig.class, CraftingConfig::new) + .appendInherited( + new KeyedCodec<>("BenchMaterialChestHorizontalSearchRadius", Codec.INTEGER), + (gameplayConfig, o) -> gameplayConfig.benchMaterialHorizontalChestSearchRadius = o, + gameplayConfig -> gameplayConfig.benchMaterialHorizontalChestSearchRadius, + (gameplayConfig, parent) -> gameplayConfig.benchMaterialHorizontalChestSearchRadius = parent.benchMaterialHorizontalChestSearchRadius + ) + .addValidator(Validators.range(0, 7)) + .documentation("The horizontal radius of search around a bench to use materials from the chests") + .add() + .appendInherited( + new KeyedCodec<>("BenchMaterialChestVerticalSearchRadius", Codec.INTEGER), + (gameplayConfig, o) -> gameplayConfig.benchMaterialVerticalChestSearchRadius = o, + gameplayConfig -> gameplayConfig.benchMaterialVerticalChestSearchRadius, + (gameplayConfig, parent) -> gameplayConfig.benchMaterialVerticalChestSearchRadius = parent.benchMaterialVerticalChestSearchRadius + ) + .addValidator(Validators.range(0, 7)) + .documentation("The vertical radius of search around a bench to use materials from the chests") + .add() + .appendInherited( + new KeyedCodec<>("BenchMaterialChestLimit", Codec.INTEGER), + (gameplayConfig, o) -> gameplayConfig.benchMaterialChestLimit = o, + gameplayConfig -> gameplayConfig.benchMaterialChestLimit, + (gameplayConfig, parent) -> gameplayConfig.benchMaterialChestLimit = parent.benchMaterialChestLimit + ) + .addValidator(Validators.range(0, 200)) + .documentation("The maximum number of chests a crafting bench will draw materials from") + .add() + .build(); + protected int benchMaterialHorizontalChestSearchRadius = 7; + protected int benchMaterialVerticalChestSearchRadius = 3; + protected int benchMaterialChestLimit = 100; + + public CraftingConfig() { + } + + public int getBenchMaterialHorizontalChestSearchRadius() { + return this.benchMaterialHorizontalChestSearchRadius; + } + + public int getBenchMaterialVerticalChestSearchRadius() { + return this.benchMaterialVerticalChestSearchRadius; + } + + public int getBenchMaterialChestLimit() { + return this.benchMaterialChestLimit; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/DeathConfig.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/DeathConfig.java new file mode 100644 index 0000000..06409e3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/DeathConfig.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.asset.type.gameplay.respawn.HomeOrSpawnPoint; +import com.hypixel.hytale.server.core.asset.type.gameplay.respawn.RespawnController; +import javax.annotation.Nonnull; + +public class DeathConfig { + public static final EnumCodec LOSS_MODE_CODEC = new EnumCodec<>(DeathConfig.ItemsLossMode.class); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(DeathConfig.class, DeathConfig::new) + .appendInherited( + new KeyedCodec<>("RespawnController", RespawnController.CODEC), + (o, i) -> o.respawnController = i, + o -> o.respawnController, + (o, p) -> o.respawnController = p.respawnController + ) + .addValidator(Validators.nonNull()) + .documentation("The respawn controller that determines where the player respawns.") + .add() + .appendInherited( + new KeyedCodec<>("ItemsLossMode", LOSS_MODE_CODEC), + (deathConfig, o) -> deathConfig.itemsLossMode = o, + deathConfig -> deathConfig.itemsLossMode, + (o, p) -> o.itemsLossMode = p.itemsLossMode + ) + .documentation("The mode used to compute what the entity will lose upon death.") + .add() + .appendInherited( + new KeyedCodec<>("ItemsAmountLossPercentage", Codec.DOUBLE), + (deathConfig, aDouble) -> deathConfig.itemsAmountLossPercentage = aDouble, + deathConfig -> deathConfig.itemsAmountLossPercentage, + (deathConfig, parent) -> deathConfig.itemsAmountLossPercentage = parent.itemsAmountLossPercentage + ) + .addValidator(Validators.range(0.0, 100.0)) + .documentation( + "The amount (in %) of items lost for an ItemStack upon death (applied to the entire inventory). Used ONLY if `ItemsLossMode` is set to 'Configured` and applied to items that have `DropOnDeath` set to `true`." + ) + .add() + .appendInherited( + new KeyedCodec<>("ItemsDurabilityLossPercentage", Codec.DOUBLE), + (deathConfig, aDouble) -> deathConfig.itemsDurabilityLossPercentage = aDouble, + deathConfig -> deathConfig.itemsDurabilityLossPercentage, + (deathConfig, parent) -> deathConfig.itemsDurabilityLossPercentage = parent.itemsDurabilityLossPercentage + ) + .addValidator(Validators.range(0.0, 100.0)) + .documentation("The amount of durability (in %) items lose upon death (applied to the entire inventory).") + .add() + .build(); + @Nonnull + protected RespawnController respawnController = HomeOrSpawnPoint.INSTANCE; + protected DeathConfig.ItemsLossMode itemsLossMode = DeathConfig.ItemsLossMode.NONE; + protected double itemsAmountLossPercentage = 10.0; + protected double itemsDurabilityLossPercentage = 10.0; + + public DeathConfig() { + } + + @Nonnull + public RespawnController getRespawnController() { + return this.respawnController; + } + + public DeathConfig.ItemsLossMode getItemsLossMode() { + return this.itemsLossMode; + } + + public double getItemsAmountLossPercentage() { + return this.itemsAmountLossPercentage; + } + + public double getItemsDurabilityLossPercentage() { + return this.itemsDurabilityLossPercentage; + } + + public static enum ItemsLossMode { + NONE, + ALL, + CONFIGURED; + + private ItemsLossMode() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/GameplayConfig.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/GameplayConfig.java new file mode 100644 index 0000000..d5fc6bc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/GameplayConfig.java @@ -0,0 +1,267 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.lookup.MapKeyMapCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemEntityConfig; +import com.hypixel.hytale.server.core.asset.type.soundset.config.SoundSet; +import javax.annotation.Nonnull; + +public class GameplayConfig implements JsonAssetWithMap> { + public static final String DEFAULT_ID = "Default"; + public static final GameplayConfig DEFAULT = new GameplayConfig(); + @Nonnull + public static final MapKeyMapCodec PLUGIN_CODEC = new MapKeyMapCodec<>(true); + @Nonnull + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + GameplayConfig.class, + GameplayConfig::new, + Codec.STRING, + (gameplayConfig, s) -> gameplayConfig.id = s, + GameplayConfig::getId, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("Gathering", GatheringConfig.CODEC), + (gameplayConfig, o) -> gameplayConfig.gatheringConfig = o, + gameplayConfig -> gameplayConfig.gatheringConfig, + (gameplayConfig, parent) -> gameplayConfig.gatheringConfig = parent.gatheringConfig + ) + .add() + .appendInherited( + new KeyedCodec<>("World", WorldConfig.CODEC), + (gameplayConfig, o) -> gameplayConfig.worldConfig = o, + gameplayConfig -> gameplayConfig.worldConfig, + (gameplayConfig, parent) -> gameplayConfig.worldConfig = parent.worldConfig + ) + .add() + .appendInherited( + new KeyedCodec<>("WorldMap", WorldMapConfig.CODEC), + (gameplayConfig, o) -> gameplayConfig.worldMapConfig = o, + gameplayConfig -> gameplayConfig.worldMapConfig, + (gameplayConfig, parent) -> gameplayConfig.worldMapConfig = parent.worldMapConfig + ) + .add() + .appendInherited( + new KeyedCodec<>("Death", DeathConfig.CODEC), + (gameplayConfig, o) -> gameplayConfig.deathConfig = o, + gameplayConfig -> gameplayConfig.deathConfig, + (gameplayConfig, parent) -> gameplayConfig.deathConfig = parent.deathConfig + ) + .add() + .appendInherited( + new KeyedCodec<>("Respawn", RespawnConfig.CODEC), + (gameplayConfig, o) -> gameplayConfig.respawnConfig = o, + gameplayConfig -> gameplayConfig.respawnConfig, + (gameplayConfig, parent) -> gameplayConfig.respawnConfig = parent.respawnConfig + ) + .add() + .appendInherited( + new KeyedCodec<>("ShowItemPickupNotifications", Codec.BOOLEAN), + (gameplayConfig, showItemPickupNotifications) -> gameplayConfig.showItemPickupNotifications = showItemPickupNotifications, + gameplayConfig -> gameplayConfig.showItemPickupNotifications, + (gameplayConfig, parent) -> gameplayConfig.showItemPickupNotifications = parent.showItemPickupNotifications + ) + .add() + .appendInherited( + new KeyedCodec<>("ItemDurability", ItemDurabilityConfig.CODEC), + (gameplayConfig, o) -> gameplayConfig.itemDurabilityConfig = o, + gameplayConfig -> gameplayConfig.itemDurabilityConfig, + (gameplayConfig, parent) -> gameplayConfig.itemDurabilityConfig = parent.itemDurabilityConfig + ) + .add() + .appendInherited( + new KeyedCodec<>("ItemEntity", ItemEntityConfig.CODEC), + (gameplayConfig, itemEntityConfig) -> gameplayConfig.itemEntityConfig = itemEntityConfig, + gameplayConfig -> gameplayConfig.itemEntityConfig, + (gameplayConfig, parent) -> gameplayConfig.itemEntityConfig = parent.itemEntityConfig + ) + .add() + .appendInherited( + new KeyedCodec<>("Combat", CombatConfig.CODEC), + (gameplayConfig, combatConfig) -> gameplayConfig.combatConfig = combatConfig, + gameplayConfig -> gameplayConfig.combatConfig, + (gameplayConfig, parent) -> gameplayConfig.combatConfig = parent.combatConfig + ) + .add() + .>appendInherited(new KeyedCodec<>("Plugin", PLUGIN_CODEC), (o, i) -> { + if (o.pluginConfig.isEmpty()) { + o.pluginConfig = i; + } else { + MapKeyMapCodec.TypeMap temp = o.pluginConfig; + o.pluginConfig = new MapKeyMapCodec.TypeMap<>(PLUGIN_CODEC); + o.pluginConfig.putAll(temp); + o.pluginConfig.putAll(i); + } + }, o -> o.pluginConfig, (o, p) -> o.pluginConfig = p.pluginConfig) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Player", PlayerConfig.CODEC), + (gameplayConfig, playerConfig) -> gameplayConfig.playerConfig = playerConfig, + gameplayConfig -> gameplayConfig.playerConfig, + (gameplayConfig, parent) -> gameplayConfig.playerConfig = parent.playerConfig + ) + .add() + .appendInherited( + new KeyedCodec<>("CameraEffects", CameraEffectsConfig.CODEC), + (o, i) -> o.cameraEffectsConfig = i, + o -> o.cameraEffectsConfig, + (o, p) -> o.cameraEffectsConfig = p.cameraEffectsConfig + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("CreativePlaySoundSet", SoundSet.CHILD_ASSET_CODEC), + (o, i) -> o.creativePlaySoundSet = i, + o -> o.creativePlaySoundSet, + (o, p) -> o.creativePlaySoundSet = p.creativePlaySoundSet + ) + .addValidator(SoundSet.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("Crafting", CraftingConfig.CODEC), + (gameplayConfig, o) -> gameplayConfig.craftingConfig = o, + gameplayConfig -> gameplayConfig.craftingConfig, + (gameplayConfig, parent) -> gameplayConfig.craftingConfig = parent.craftingConfig + ) + .add() + .appendInherited(new KeyedCodec<>("Spawn", SpawnConfig.CODEC), (o, v) -> o.spawnConfig = v, o -> o.spawnConfig, (o, p) -> o.spawnConfig = p.spawnConfig) + .add() + .appendInherited( + new KeyedCodec<>("MaxEnvironmentalNPCSpawns", Codec.INTEGER), + (o, v) -> o.maxEnvironmentalNPCSpawns = v, + o -> o.maxEnvironmentalNPCSpawns, + (o, p) -> o.maxEnvironmentalNPCSpawns = p.maxEnvironmentalNPCSpawns + ) + .documentation("The absolute maximum number of environmental NPC spawns. < 0 for infinite.") + .add() + .afterDecode(GameplayConfig::processConfig) + .build(); + private static AssetStore> ASSET_STORE; + @Nonnull + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(GameplayConfig::getAssetStore)); + protected AssetExtraInfo.Data data; + protected String id; + protected GatheringConfig gatheringConfig = new GatheringConfig(); + protected WorldConfig worldConfig = new WorldConfig(); + protected WorldMapConfig worldMapConfig = new WorldMapConfig(); + protected DeathConfig deathConfig = new DeathConfig(); + protected ItemDurabilityConfig itemDurabilityConfig = new ItemDurabilityConfig(); + protected ItemEntityConfig itemEntityConfig = new ItemEntityConfig(); + protected RespawnConfig respawnConfig = new RespawnConfig(); + protected CombatConfig combatConfig = new CombatConfig(); + protected MapKeyMapCodec.TypeMap pluginConfig = MapKeyMapCodec.TypeMap.empty(); + protected PlayerConfig playerConfig = new PlayerConfig(); + protected CameraEffectsConfig cameraEffectsConfig = new CameraEffectsConfig(); + protected CraftingConfig craftingConfig = new CraftingConfig(); + protected SpawnConfig spawnConfig = new SpawnConfig(); + protected String creativePlaySoundSet; + protected boolean showItemPickupNotifications = true; + protected transient int creativePlaySoundSetIndex; + protected int maxEnvironmentalNPCSpawns = 500; + + public GameplayConfig() { + } + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(GameplayConfig.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public GatheringConfig getGatheringConfig() { + return this.gatheringConfig; + } + + public WorldConfig getWorldConfig() { + return this.worldConfig; + } + + public WorldMapConfig getWorldMapConfig() { + return this.worldMapConfig; + } + + public DeathConfig getDeathConfig() { + return this.deathConfig; + } + + public boolean getShowItemPickupNotifications() { + return this.showItemPickupNotifications; + } + + public ItemDurabilityConfig getItemDurabilityConfig() { + return this.itemDurabilityConfig; + } + + public ItemEntityConfig getItemEntityConfig() { + return this.itemEntityConfig; + } + + public RespawnConfig getRespawnConfig() { + return this.respawnConfig; + } + + public CombatConfig getCombatConfig() { + return this.combatConfig; + } + + public MapKeyMapCodec.TypeMap getPluginConfig() { + return this.pluginConfig; + } + + public PlayerConfig getPlayerConfig() { + return this.playerConfig; + } + + public CameraEffectsConfig getCameraEffectsConfig() { + return this.cameraEffectsConfig; + } + + public String getCreativePlaySoundSet() { + return this.creativePlaySoundSet; + } + + public int getCreativePlaySoundSetIndex() { + return this.creativePlaySoundSetIndex; + } + + public CraftingConfig getCraftingConfig() { + return this.craftingConfig; + } + + public int getMaxEnvironmentalNPCSpawns() { + return this.maxEnvironmentalNPCSpawns; + } + + @Nonnull + public SpawnConfig getSpawnConfig() { + return this.spawnConfig; + } + + protected void processConfig() { + if (this.creativePlaySoundSet != null) { + this.creativePlaySoundSetIndex = SoundSet.getAssetMap().getIndex(this.creativePlaySoundSet); + } + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/GatheringConfig.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/GatheringConfig.java new file mode 100644 index 0000000..b58e8ec --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/GatheringConfig.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class GatheringConfig { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(GatheringConfig.class, GatheringConfig::new) + .appendInherited( + new KeyedCodec<>("UnbreakableBlock", GatheringEffectsConfig.CODEC), + (gameplayConfig, o) -> gameplayConfig.unbreakableBlockConfig = o, + gameplayConfig -> gameplayConfig.unbreakableBlockConfig, + (gameplayConfig, parent) -> gameplayConfig.unbreakableBlockConfig = parent.unbreakableBlockConfig + ) + .add() + .appendInherited( + new KeyedCodec<>("IncorrectTool", GatheringEffectsConfig.CODEC), + (gameplayConfig, o) -> gameplayConfig.incorrectToolConfig = o, + gameplayConfig -> gameplayConfig.incorrectToolConfig, + (gameplayConfig, parent) -> gameplayConfig.incorrectToolConfig = parent.incorrectToolConfig + ) + .add() + .build(); + @Nonnull + protected GatheringEffectsConfig unbreakableBlockConfig = new GatheringEffectsConfig(); + @Nonnull + protected GatheringEffectsConfig incorrectToolConfig = new GatheringEffectsConfig(); + + public GatheringConfig() { + } + + @Nonnull + public GatheringEffectsConfig getUnbreakableBlockConfig() { + return this.unbreakableBlockConfig; + } + + @Nonnull + public GatheringEffectsConfig getIncorrectToolConfig() { + return this.incorrectToolConfig; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/GatheringEffectsConfig.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/GatheringEffectsConfig.java new file mode 100644 index 0000000..73fe746 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/GatheringEffectsConfig.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import javax.annotation.Nonnull; + +public class GatheringEffectsConfig { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(GatheringEffectsConfig.class, GatheringEffectsConfig::new) + .append( + new KeyedCodec<>("ParticleSystemId", Codec.STRING), + (unbreakableBlockConfig, o) -> unbreakableBlockConfig.particleSystemId = o, + unbreakableBlockConfig -> unbreakableBlockConfig.particleSystemId + ) + .addValidator(ParticleSystem.VALIDATOR_CACHE.getValidator()) + .add() + .append( + new KeyedCodec<>("SoundEventId", Codec.STRING), + (unbreakableBlockConfig, o) -> unbreakableBlockConfig.soundEventId = o, + unbreakableBlockConfig -> unbreakableBlockConfig.soundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .add() + .afterDecode(GatheringEffectsConfig::processConfig) + .build(); + protected String particleSystemId; + protected String soundEventId; + protected transient int soundEventIndex; + + public GatheringEffectsConfig() { + } + + public String getParticleSystemId() { + return this.particleSystemId; + } + + public String getSoundEventId() { + return this.soundEventId; + } + + public int getSoundEventIndex() { + return this.soundEventIndex; + } + + protected void processConfig() { + if (this.soundEventId != null) { + this.soundEventIndex = SoundEvent.getAssetMap().getIndex(this.soundEventId); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/ItemDurabilityConfig.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/ItemDurabilityConfig.java new file mode 100644 index 0000000..9f89d4a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/ItemDurabilityConfig.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class ItemDurabilityConfig { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(ItemDurabilityConfig.class, ItemDurabilityConfig::new) + .appendInherited( + new KeyedCodec<>("BrokenPenalties", BrokenPenalties.CODEC), + (itemDurabilityConfig, itemTypeDoubleMap) -> itemDurabilityConfig.brokenPenalties = itemTypeDoubleMap, + itemDurabilityConfig -> itemDurabilityConfig.brokenPenalties, + (o, p) -> o.brokenPenalties = p.brokenPenalties + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + @Nonnull + protected BrokenPenalties brokenPenalties = BrokenPenalties.DEFAULT; + + public ItemDurabilityConfig() { + } + + @Nonnull + public BrokenPenalties getBrokenPenalties() { + return this.brokenPenalties; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/PlayerConfig.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/PlayerConfig.java new file mode 100644 index 0000000..68ed8ee --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/PlayerConfig.java @@ -0,0 +1,92 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementConfig; +import com.hypixel.hytale.server.core.modules.entity.hitboxcollision.HitboxCollisionConfig; +import com.hypixel.hytale.server.core.modules.entity.repulsion.RepulsionConfig; +import javax.annotation.Nonnull; + +public class PlayerConfig { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(PlayerConfig.class, PlayerConfig::new) + .appendInherited( + new KeyedCodec<>("HitboxCollisionConfig", Codec.STRING), + (playerConfig, s) -> playerConfig.hitboxCollisionConfigId = s, + playerConfig -> playerConfig.hitboxCollisionConfigId, + (playerConfig, parent) -> playerConfig.hitboxCollisionConfigId = parent.hitboxCollisionConfigId + ) + .documentation("The HitboxCollision config to apply to all players.") + .addValidator(HitboxCollisionConfig.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("RepulsionConfig", Codec.STRING), + (playerConfig, s) -> playerConfig.repulsionConfigId = s, + playerConfig -> playerConfig.repulsionConfigId, + (playerConfig, parent) -> playerConfig.repulsionConfigId = parent.repulsionConfigId + ) + .documentation("The Repulsion to apply to all players.") + .addValidator(RepulsionConfig.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("MovementConfig", Codec.STRING), + (playerConfig, s) -> playerConfig.movementConfigId = s, + playerConfig -> playerConfig.movementConfigId, + (playerConfig, parent) -> playerConfig.movementConfigId = parent.movementConfigId + ) + .addValidator(MovementConfig.VALIDATOR_CACHE.getValidator()) + .documentation("The maximum number of simultaneous deployable entities players are allowed to own.") + .add() + .appendInherited( + new KeyedCodec<>("MaxDeployableEntities", Codec.INTEGER), + (playerConfig, s) -> playerConfig.maxDeployableEntities = s, + playerConfig -> playerConfig.maxDeployableEntities, + (playerConfig, parent) -> playerConfig.maxDeployableEntities = parent.maxDeployableEntities + ) + .add() + .afterDecode(playerConfig -> { + if (playerConfig.hitboxCollisionConfigId != null) { + playerConfig.hitboxCollisionConfigIndex = HitboxCollisionConfig.getAssetMap().getIndexOrDefault(playerConfig.hitboxCollisionConfigId, -1); + } + + if (playerConfig.repulsionConfigId != null) { + playerConfig.repulsionConfigIndex = RepulsionConfig.getAssetMap().getIndexOrDefault(playerConfig.repulsionConfigId, -1); + } + + if (playerConfig.movementConfigId != null) { + playerConfig.movementConfigIndex = MovementConfig.getAssetMap().getIndexOrDefault(playerConfig.movementConfigId, 0); + } + }) + .build(); + protected String hitboxCollisionConfigId; + protected String repulsionConfigId; + protected String movementConfigId = "BuiltinDefault"; + protected int hitboxCollisionConfigIndex = -1; + protected int repulsionConfigIndex = -1; + protected int movementConfigIndex = 0; + protected int maxDeployableEntities = -1; + + public PlayerConfig() { + } + + public int getHitboxCollisionConfigIndex() { + return this.hitboxCollisionConfigIndex; + } + + public int getRepulsionConfigIndex() { + return this.repulsionConfigIndex; + } + + public int getMovementConfigIndex() { + return this.movementConfigIndex; + } + + public String getMovementConfigId() { + return this.movementConfigId; + } + + public int getMaxDeployableEntities() { + return this.maxDeployableEntities; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/RespawnConfig.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/RespawnConfig.java new file mode 100644 index 0000000..bbf8628 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/RespawnConfig.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class RespawnConfig { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder(RespawnConfig.class, RespawnConfig::new) + .append( + new KeyedCodec<>("RadiusLimitRespawnPoint", Codec.INTEGER), + (worldConfig, integer) -> worldConfig.radiusLimitRespawnPoint = integer, + worldConfig -> worldConfig.radiusLimitRespawnPoint + ) + .addValidator(Validators.greaterThan(0)) + .add() + .append( + new KeyedCodec<>("MaxRespawnPointsPerPlayer", Codec.INTEGER), + (worldConfig, integer) -> worldConfig.maxRespawnPointsPerPlayer = integer, + worldConfig -> worldConfig.maxRespawnPointsPerPlayer + ) + .addValidator(Validators.greaterThan(0)) + .add() + .build(); + protected int radiusLimitRespawnPoint = 500; + protected int maxRespawnPointsPerPlayer = 3; + + public RespawnConfig() { + } + + public int getRadiusLimitRespawnPoint() { + return this.radiusLimitRespawnPoint; + } + + public int getMaxRespawnPointsPerPlayer() { + return this.maxRespawnPointsPerPlayer; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/SleepConfig.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/SleepConfig.java new file mode 100644 index 0000000..868033f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/SleepConfig.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.LocalTime; +import javax.annotation.Nullable; + +public class SleepConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder(SleepConfig.class, SleepConfig::new) + .append(new KeyedCodec<>("WakeUpHour", Codec.FLOAT), (sleepConfig, i) -> sleepConfig.wakeUpHour = i, o -> o.wakeUpHour) + .documentation("The in-game hour at which players naturally wake up from sleep.") + .add() + .append( + new KeyedCodec<>("AllowedSleepHoursRange", Codec.DOUBLE_ARRAY), + (sleepConfig, i) -> sleepConfig.allowedSleepHoursRange = i, + o -> o.allowedSleepHoursRange + ) + .addValidator(Validators.doubleArraySize(2)) + .documentation("The in-game hours during which players can sleep to skip to the WakeUpHour. If missing, there is no restriction.") + .add() + .build(); + public static final SleepConfig DEFAULT = new SleepConfig(); + private float wakeUpHour = 5.5F; + private double[] allowedSleepHoursRange; + + public SleepConfig() { + } + + public float getWakeUpHour() { + return this.wakeUpHour; + } + + @Nullable + public double[] getAllowedSleepHoursRange() { + return this.allowedSleepHoursRange; + } + + @Nullable + public LocalTime getSleepStartTime() { + if (this.allowedSleepHoursRange == null) { + return null; + } else { + double sleepStartHour = this.allowedSleepHoursRange[0]; + int hour = (int)sleepStartHour; + int minute = (int)((sleepStartHour - hour) * 60.0); + return LocalTime.of(hour, minute); + } + } + + public boolean isWithinSleepHoursRange(LocalDateTime gameTime) { + if (this.allowedSleepHoursRange == null) { + return true; + } else { + float hour = getFractionalHourOfDay(gameTime); + double min = this.allowedSleepHoursRange[0]; + double max = this.allowedSleepHoursRange[1]; + return (hour - min + 24.0) % 24.0 <= (max - min + 24.0) % 24.0; + } + } + + public Duration computeDurationUntilSleep(LocalDateTime now) { + if (this.allowedSleepHoursRange == null) { + return Duration.ZERO; + } else { + float currentHour = getFractionalHourOfDay(now); + double sleepStartHour = this.allowedSleepHoursRange[0]; + double hoursUntilSleep = (sleepStartHour - currentHour + 24.0) % 24.0; + long seconds = (long)(hoursUntilSleep * 3600.0); + return Duration.ofSeconds(seconds); + } + } + + private static float getFractionalHourOfDay(LocalDateTime dateTime) { + return dateTime.getHour() + dateTime.getMinute() / 60.0F + dateTime.getSecond() / 3600.0F; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/SpawnConfig.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/SpawnConfig.java new file mode 100644 index 0000000..d043cb2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/SpawnConfig.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.server.core.asset.type.particle.config.WorldParticle; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SpawnConfig { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(SpawnConfig.class, SpawnConfig::new) + .appendInherited( + new KeyedCodec<>("FirstSpawnParticles", new ArrayCodec<>(WorldParticle.CODEC, WorldParticle[]::new)), + (o, v) -> o.firstSpawnParticles = v, + o -> o.firstSpawnParticles, + (o, p) -> o.firstSpawnParticles = p.firstSpawnParticles + ) + .add() + .appendInherited( + new KeyedCodec<>("SpawnParticles", new ArrayCodec<>(WorldParticle.CODEC, WorldParticle[]::new)), + (o, v) -> o.spawnParticles = v, + o -> o.spawnParticles, + (o, p) -> o.spawnParticles = p.spawnParticles + ) + .add() + .build(); + protected WorldParticle[] firstSpawnParticles; + protected WorldParticle[] spawnParticles; + + public SpawnConfig() { + } + + @Nullable + public WorldParticle[] getFirstSpawnParticles() { + return this.firstSpawnParticles; + } + + @Nullable + public WorldParticle[] getSpawnParticles() { + return this.spawnParticles; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/WorldConfig.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/WorldConfig.java new file mode 100644 index 0000000..1f99299 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/WorldConfig.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class WorldConfig { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(WorldConfig.class, WorldConfig::new) + .append( + new KeyedCodec<>("AllowBlockBreaking", Codec.BOOLEAN), + (worldConfig, o) -> worldConfig.allowBlockBreaking = o, + worldConfig -> worldConfig.allowBlockBreaking + ) + .add() + .append( + new KeyedCodec<>("AllowBlockGathering", Codec.BOOLEAN), + (worldConfig, o) -> worldConfig.allowBlockGathering = o, + worldConfig -> worldConfig.allowBlockGathering + ) + .add() + .append( + new KeyedCodec<>("AllowBlockPlacement", Codec.BOOLEAN), + (worldConfig, o) -> worldConfig.allowBlockPlacement = o, + worldConfig -> worldConfig.allowBlockPlacement + ) + .add() + .append( + new KeyedCodec<>("BlockPlacementFragilityTimer", Codec.DOUBLE), + (worldConfig, d) -> worldConfig.blockPlacementFragilityTimer = d.floatValue(), + worldConfig -> (double)worldConfig.blockPlacementFragilityTimer + ) + .documentation("The timer, in seconds, that blocks have after placement during which they are fragile and can be broken instantly") + .add() + .append( + new KeyedCodec<>("DaytimeDurationSeconds", Codec.INTEGER), + (worldConfig, i) -> worldConfig.daytimeDurationSeconds = i, + worldConfig -> worldConfig.daytimeDurationSeconds + ) + .documentation("The number of real-world seconds it takes for the day to pass (from sunrise to sunset)") + .add() + .append( + new KeyedCodec<>("NighttimeDurationSeconds", Codec.INTEGER), + (worldConfig, i) -> worldConfig.nighttimeDurationSeconds = i, + worldConfig -> worldConfig.nighttimeDurationSeconds + ) + .documentation("The number of real-world seconds it takes for the night to pass (from sunset to sunrise)") + .add() + .append(new KeyedCodec<>("TotalMoonPhases", Codec.INTEGER), (worldConfig, i) -> worldConfig.totalMoonPhases = i, o -> o.totalMoonPhases) + .add() + .append( + new KeyedCodec<>("Sleep", SleepConfig.CODEC), (worldConfig, sleepConfig) -> worldConfig.sleepConfig = sleepConfig, o -> o.sleepConfig + ) + .documentation("Configurations related to sleeping in this world (in beds)") + .add() + .build(); + public static final int DEFAULT_TOTAL_DAY_DURATION_SECONDS = 2880; + public static final int DEFAULT_DAYTIME_DURATION_SECONDS = 1728; + public static final int DEFAULT_NIGHTTIME_DURATION_SECONDS = 1728; + protected boolean allowBlockBreaking = true; + protected boolean allowBlockGathering = true; + protected boolean allowBlockPlacement = true; + protected int daytimeDurationSeconds = 1728; + protected int nighttimeDurationSeconds = 1728; + private int totalMoonPhases = 5; + protected float blockPlacementFragilityTimer; + private SleepConfig sleepConfig = SleepConfig.DEFAULT; + + public WorldConfig() { + } + + public boolean isBlockBreakingAllowed() { + return this.allowBlockBreaking; + } + + public boolean isBlockGatheringAllowed() { + return this.allowBlockGathering; + } + + public boolean isBlockPlacementAllowed() { + return this.allowBlockPlacement; + } + + public int getDaytimeDurationSeconds() { + return this.daytimeDurationSeconds; + } + + public int getNighttimeDurationSeconds() { + return this.nighttimeDurationSeconds; + } + + public int getTotalMoonPhases() { + return this.totalMoonPhases; + } + + public float getBlockPlacementFragilityTimer() { + return this.blockPlacementFragilityTimer; + } + + public SleepConfig getSleepConfig() { + return this.sleepConfig; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/WorldMapConfig.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/WorldMapConfig.java new file mode 100644 index 0000000..c142c81 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/WorldMapConfig.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class WorldMapConfig { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(WorldMapConfig.class, WorldMapConfig::new) + .addField( + new KeyedCodec<>("DisplaySpawn", Codec.BOOLEAN), (worldMapConfig, o) -> worldMapConfig.displaySpawn = o, worldMapConfig -> worldMapConfig.displaySpawn + ) + .addField( + new KeyedCodec<>("DisplayHome", Codec.BOOLEAN), (worldMapConfig, o) -> worldMapConfig.displayHome = o, worldMapConfig -> worldMapConfig.displayHome + ) + .addField( + new KeyedCodec<>("DisplayWarps", Codec.BOOLEAN), (worldMapConfig, o) -> worldMapConfig.displayWarps = o, worldMapConfig -> worldMapConfig.displayWarps + ) + .addField( + new KeyedCodec<>("DisplayDeathMarker", Codec.BOOLEAN), + (worldMapConfig, o) -> worldMapConfig.displayDeathMarker = o, + worldMapConfig -> worldMapConfig.displayDeathMarker + ) + .addField( + new KeyedCodec<>("DisplayPlayers", Codec.BOOLEAN), + (worldMapConfig, o) -> worldMapConfig.displayPlayers = o, + worldMapConfig -> worldMapConfig.displayPlayers + ) + .build(); + protected boolean displaySpawn = true; + protected boolean displayHome = true; + protected boolean displayWarps = true; + protected boolean displayDeathMarker = true; + protected boolean displayPlayers = true; + + public WorldMapConfig() { + } + + public boolean isDisplaySpawn() { + return this.displaySpawn; + } + + public boolean isDisplayHome() { + return this.displayHome; + } + + public boolean isDisplayWarps() { + return this.displayWarps; + } + + public boolean isDisplayDeathMarker() { + return this.displayDeathMarker; + } + + public boolean isDisplayPlayers() { + return this.displayPlayers; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/respawn/HomeOrSpawnPoint.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/respawn/HomeOrSpawnPoint.java new file mode 100644 index 0000000..a467dc4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/respawn/HomeOrSpawnPoint.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay.respawn; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class HomeOrSpawnPoint implements RespawnController { + @Nonnull + public static final HomeOrSpawnPoint INSTANCE = new HomeOrSpawnPoint(); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(HomeOrSpawnPoint.class, () -> INSTANCE).build(); + + public HomeOrSpawnPoint() { + } + + @Override + public void respawnPlayer(@Nonnull World world, @Nonnull Ref playerReference, @Nonnull CommandBuffer commandBuffer) { + Transform homePosition = Player.getRespawnPosition(playerReference, world.getName(), commandBuffer); + commandBuffer.addComponent(playerReference, Teleport.getComponentType(), new Teleport(null, homePosition)); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/respawn/RespawnController.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/respawn/RespawnController.java new file mode 100644 index 0000000..f386f02 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/respawn/RespawnController.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay.respawn; + +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public interface RespawnController { + @Nonnull + CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + + void respawnPlayer(@Nonnull World var1, @Nonnull Ref var2, @Nonnull CommandBuffer var3); +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/gameplay/respawn/WorldSpawnPoint.java b/src/com/hypixel/hytale/server/core/asset/type/gameplay/respawn/WorldSpawnPoint.java new file mode 100644 index 0000000..c8f4179 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/gameplay/respawn/WorldSpawnPoint.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.core.asset.type.gameplay.respawn; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class WorldSpawnPoint implements RespawnController { + public static final WorldSpawnPoint INSTANCE = new WorldSpawnPoint(); + public static final BuilderCodec CODEC = BuilderCodec.builder(WorldSpawnPoint.class, () -> INSTANCE).build(); + + public WorldSpawnPoint() { + } + + @Override + public void respawnPlayer(@NonNullDecl World world, @NonNullDecl Ref playerReference, @NonNullDecl CommandBuffer commandBuffer) { + ISpawnProvider spawnProvider = world.getWorldConfig().getSpawnProvider(); + Transform spawnPoint = spawnProvider.getSpawnPoint(playerReference, commandBuffer); + commandBuffer.addComponent(playerReference, Teleport.getComponentType(), new Teleport(null, spawnPoint)); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/DroplistCommand.java b/src/com/hypixel/hytale/server/core/asset/type/item/DroplistCommand.java new file mode 100644 index 0000000..81cfc1d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/DroplistCommand.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.asset.type.item; + +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.item.ItemModule; +import java.util.LinkedHashMap; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class DroplistCommand extends CommandBase { + @Nonnull + private final RequiredArg itemDroplistArg = this.withRequiredArg("droplist", "server.commands.droplist.set.droplist.desc", ArgTypes.STRING); + private final OptionalArg countArg = this.withOptionalArg("count", "server.commands.droplist.set.count.desc", ArgTypes.INTEGER) + .addValidator(Validators.greaterThan(0)); + + public DroplistCommand() { + super("droplist", "server.commands.droplist.desc"); + } + + @Override + protected void executeSync(@NonNullDecl CommandContext context) { + String droplistId = this.itemDroplistArg.get(context); + ItemDropList itemDropList = ItemDropList.getAssetMap().getAsset(droplistId); + if (itemDropList == null) { + context.sendMessage(Message.translation("server.commands.droplist.notFound").param("droplistId", droplistId)); + } else { + int count = this.countArg.provided(context) ? this.countArg.get(context) : 1; + LinkedHashMap accumulatedDrops = new LinkedHashMap<>(); + + for (int i = 0; i < count; i++) { + for (ItemStack itemStack : ItemModule.get().getRandomItemDrops(droplistId)) { + accumulatedDrops.merge(itemStack.getItemId(), itemStack.getQuantity(), Integer::sum); + } + } + + if (accumulatedDrops.isEmpty()) { + context.sendMessage(Message.translation("server.commands.droplist.empty").param("droplistId", droplistId)); + } else { + context.sendMessage(Message.translation("server.commands.droplist.result").param("droplistId", droplistId)); + + for (Entry entry : accumulatedDrops.entrySet()) { + Message message = Message.translation("server.commands.droplist.result.item") + .param("itemName", entry.getKey()) + .param("itemQuantity", entry.getValue()); + context.sendMessage(message); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/FieldcraftCategoryPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/item/FieldcraftCategoryPacketGenerator.java new file mode 100644 index 0000000..0b497fa --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/FieldcraftCategoryPacketGenerator.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.core.asset.type.item; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.protocol.ItemCategory; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateFieldcraftCategories; +import com.hypixel.hytale.server.core.asset.packet.DefaultAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.item.config.FieldcraftCategory; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; + +public class FieldcraftCategoryPacketGenerator extends DefaultAssetPacketGenerator { + public FieldcraftCategoryPacketGenerator() { + } + + @Nonnull + @Override + public Packet generateInitPacket(@Nonnull DefaultAssetMap assetMap, @Nonnull Map assets) { + Map assetsFromMap = assetMap.getAssetMap(); + if (assets.size() != assetsFromMap.size()) { + throw new UnsupportedOperationException("Item categories can not handle partial init packets!!!"); + } else { + UpdateFieldcraftCategories packet = new UpdateFieldcraftCategories(); + packet.type = UpdateType.Init; + ItemCategory[] arr = new ItemCategory[assets.size()]; + int i = 0; + + for (FieldcraftCategory itemCategory : assets.values()) { + arr[i++] = itemCategory.toPacket(); + } + + packet.itemCategories = arr; + return packet; + } + } + + @Nonnull + @Override + public Packet generateUpdatePacket(@Nonnull Map assets) { + UpdateFieldcraftCategories packet = new UpdateFieldcraftCategories(); + packet.type = UpdateType.AddOrUpdate; + ItemCategory[] arr = new ItemCategory[assets.size()]; + int i = 0; + + for (FieldcraftCategory itemCategory : assets.values()) { + arr[i++] = itemCategory.toPacket(); + } + + packet.itemCategories = arr; + return packet; + } + + @Override + public Packet generateRemovePacket(Set removed) { + throw new IllegalArgumentException("We don't support removing item categories at this time!"); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/ItemCategoryPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/item/ItemCategoryPacketGenerator.java new file mode 100644 index 0000000..7a669db --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/ItemCategoryPacketGenerator.java @@ -0,0 +1,71 @@ +package com.hypixel.hytale.server.core.asset.type.item; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateItemCategories; +import com.hypixel.hytale.server.core.asset.packet.DefaultAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemCategory; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; + +public class ItemCategoryPacketGenerator extends DefaultAssetPacketGenerator { + public ItemCategoryPacketGenerator() { + } + + @Nonnull + @Override + public Packet generateInitPacket(@Nonnull DefaultAssetMap assetMap, @Nonnull Map assets) { + Map assetsFromMap = assetMap.getAssetMap(); + if (assets.size() != assetsFromMap.size()) { + throw new UnsupportedOperationException("Item categories can not handle partial init packets!!!"); + } else { + UpdateItemCategories packet = new UpdateItemCategories(); + packet.type = UpdateType.Init; + com.hypixel.hytale.protocol.ItemCategory[] arr = new com.hypixel.hytale.protocol.ItemCategory[assets.size()]; + int i = 0; + + for (ItemCategory itemCategory : assets.values()) { + arr[i++] = itemCategory.toPacket(); + } + + packet.itemCategories = arr; + return packet; + } + } + + @Nonnull + @Override + public Packet generateUpdatePacket(@Nonnull Map assets) { + UpdateItemCategories packet = new UpdateItemCategories(); + packet.type = UpdateType.AddOrUpdate; + com.hypixel.hytale.protocol.ItemCategory[] arr = new com.hypixel.hytale.protocol.ItemCategory[assets.size()]; + int i = 0; + + for (ItemCategory itemCategory : assets.values()) { + arr[i++] = itemCategory.toPacket(); + } + + packet.itemCategories = arr; + return packet; + } + + @Nonnull + @Override + public Packet generateRemovePacket(@Nonnull Set removed) { + UpdateItemCategories packet = new UpdateItemCategories(); + packet.type = UpdateType.Remove; + com.hypixel.hytale.protocol.ItemCategory[] arr = new com.hypixel.hytale.protocol.ItemCategory[removed.size()]; + int i = 0; + + for (String id : removed) { + com.hypixel.hytale.protocol.ItemCategory itemCategory = new com.hypixel.hytale.protocol.ItemCategory(); + itemCategory.id = id; + arr[i++] = itemCategory; + } + + packet.itemCategories = arr; + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/ResourceTypePacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/item/ResourceTypePacketGenerator.java new file mode 100644 index 0000000..665ed97 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/ResourceTypePacketGenerator.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.server.core.asset.type.item; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateResourceTypes; +import com.hypixel.hytale.server.core.asset.packet.DefaultAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.item.config.ResourceType; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class ResourceTypePacketGenerator extends DefaultAssetPacketGenerator { + public ResourceTypePacketGenerator() { + } + + @Nonnull + @Override + public Packet generateInitPacket(@Nonnull DefaultAssetMap assetMap, @Nonnull Map assets) { + Map assetsFromMap = assetMap.getAssetMap(); + if (assets.size() != assetsFromMap.size()) { + throw new UnsupportedOperationException("Resource types can not handle partial init packets!!!"); + } else { + UpdateResourceTypes packet = new UpdateResourceTypes(); + packet.type = UpdateType.Init; + packet.resourceTypes = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + packet.resourceTypes.put(entry.getKey(), entry.getValue().toPacket()); + } + + return packet; + } + } + + @Nonnull + @Override + public Packet generateUpdatePacket(@Nonnull Map loadedAssets) { + UpdateResourceTypes packet = new UpdateResourceTypes(); + packet.type = UpdateType.AddOrUpdate; + packet.resourceTypes = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + packet.resourceTypes.put(entry.getKey(), entry.getValue().toPacket()); + } + + return packet; + } + + @Nonnull + @Override + public Packet generateRemovePacket(@Nonnull Set removed) { + UpdateResourceTypes packet = new UpdateResourceTypes(); + packet.type = UpdateType.Remove; + packet.resourceTypes = new Object2ObjectOpenHashMap<>(); + + for (String key : removed) { + packet.resourceTypes.put(key, null); + } + + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/AssetIconProperties.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/AssetIconProperties.java new file mode 100644 index 0000000..161275c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/AssetIconProperties.java @@ -0,0 +1,71 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.Vector2f; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetIconProperties implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(AssetIconProperties.class, AssetIconProperties::new) + .addField(new KeyedCodec<>("Scale", Codec.DOUBLE), (props, scale) -> props.scale = scale.floatValue(), props -> (double)props.scale) + .addField( + new KeyedCodec<>("Translation", Vector2d.AS_ARRAY_CODEC), + (props, translation) -> props.translation = translation == null ? null : new Vector2f((float)translation.getX(), (float)translation.getY()), + props -> props.translation == null ? null : new Vector2d(props.translation.x, props.translation.y) + ) + .addField( + new KeyedCodec<>("Rotation", Vector3d.AS_ARRAY_CODEC), + (props, rot) -> props.rotation = rot == null ? null : new Vector3f((float)rot.getX(), (float)rot.getY(), (float)rot.getZ()), + props -> props.rotation == null ? null : new Vector3d(props.rotation.x, props.rotation.y, props.rotation.z) + ) + .build(); + private float scale; + @Nullable + private Vector2f translation; + @Nullable + private Vector3f rotation; + + AssetIconProperties() { + } + + public AssetIconProperties(float scale, Vector2f translation, Vector3f rotation) { + this.scale = scale; + this.translation = translation; + this.rotation = rotation; + } + + public float getScale() { + return this.scale; + } + + @Nullable + public Vector2f getTranslation() { + return this.translation; + } + + @Nullable + public Vector3f getRotation() { + return this.rotation; + } + + @Nonnull + public com.hypixel.hytale.protocol.AssetIconProperties toPacket() { + com.hypixel.hytale.protocol.AssetIconProperties packet = new com.hypixel.hytale.protocol.AssetIconProperties(); + packet.scale = this.scale; + packet.translation = this.translation; + packet.rotation = this.rotation; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "AssetIconProperties{scale=" + this.scale + ", translation=" + this.translation + ", rotation=" + this.rotation + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/BlockGroup.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/BlockGroup.java new file mode 100644 index 0000000..111e415 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/BlockGroup.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.AssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@Deprecated +public class BlockGroup implements JsonAssetWithMap>, NetworkSerializable { + private static final String[] DEFAULT_BLOCK_LIST = new String[0]; + public static final AssetCodec CODEC = AssetBuilderCodec.builder( + BlockGroup.class, BlockGroup::new, Codec.STRING, (t, k) -> t.id = k, t -> t.id, (asset, data) -> asset.data = data, asset -> asset.data + ) + .addField(new KeyedCodec<>("Blocks", Codec.STRING_ARRAY), (blockSet, strings) -> blockSet.blocks = strings, blockSet -> blockSet.blocks) + .build(); + private String id; + private AssetExtraInfo.Data data; + private String[] blocks = DEFAULT_BLOCK_LIST; + + public BlockGroup() { + } + + @Nullable + public static BlockGroup findItemGroup(@Nonnull Item item) { + String blockId = item.getBlockId(); + if (blockId == null) { + return null; + } else { + for (BlockGroup group : ((DefaultAssetMap)AssetRegistry.getAssetStore(BlockGroup.class).getAssetMap()).getAssetMap().values()) { + if (ArrayUtil.contains(group.blocks, blockId)) { + return group; + } + } + + return null; + } + } + + public String getId() { + return this.id; + } + + public String get(int index) { + return this.blocks[index]; + } + + public int size() { + return this.blocks.length; + } + + public int getIndex(@Nonnull Item item) { + String id = item.getBlockId(); + return ArrayUtil.indexOf(this.blocks, id); + } + + @Nonnull + public com.hypixel.hytale.protocol.BlockGroup toPacket() { + com.hypixel.hytale.protocol.BlockGroup packet = new com.hypixel.hytale.protocol.BlockGroup(); + packet.names = this.blocks; + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/BlockSelectorToolData.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/BlockSelectorToolData.java new file mode 100644 index 0000000..ffad4c2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/BlockSelectorToolData.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class BlockSelectorToolData implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockSelectorToolData.class, BlockSelectorToolData::new) + .append(new KeyedCodec<>("DurabilityLossOnUse", Codec.DOUBLE), (data, x) -> data.durabilityLossOnUse = x.floatValue(), data -> data.durabilityLossOnUse) + .add() + .build(); + protected double durabilityLossOnUse; + + protected BlockSelectorToolData() { + } + + @Nonnull + public com.hypixel.hytale.protocol.BlockSelectorToolData toPacket() { + com.hypixel.hytale.protocol.BlockSelectorToolData packet = new com.hypixel.hytale.protocol.BlockSelectorToolData(); + packet.durabilityLossOnUse = (float)this.durabilityLossOnUse; + return packet; + } + + public double getDurabilityLossOnUse() { + return this.durabilityLossOnUse; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/BuilderToolItemReferenceAsset.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/BuilderToolItemReferenceAsset.java new file mode 100644 index 0000000..6c05623 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/BuilderToolItemReferenceAsset.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.AssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import javax.annotation.Nonnull; + +public class BuilderToolItemReferenceAsset implements JsonAssetWithMap> { + private static AssetStore> ASSET_STORE; + @Nonnull + public static final AssetCodec CODEC = AssetBuilderCodec.builder( + BuilderToolItemReferenceAsset.class, + BuilderToolItemReferenceAsset::new, + Codec.STRING, + (t, k) -> t.id = k, + t -> t.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .append(new KeyedCodec<>("BuilderToolItems", new ArrayCodec<>(Codec.STRING, String[]::new)), (i, itemIds) -> i.itemIds = itemIds, i -> i.itemIds) + .add() + .build(); + private String id; + protected String[] itemIds; + private AssetExtraInfo.Data data; + + public BuilderToolItemReferenceAsset() { + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(BuilderToolItemReferenceAsset.class); + } + + return ASSET_STORE; + } + + public String[] getItems() { + return this.itemIds; + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/CraftingRecipe.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/CraftingRecipe.java new file mode 100644 index 0000000..a4231d6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/CraftingRecipe.java @@ -0,0 +1,292 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.protocol.BenchRequirement; +import com.hypixel.hytale.protocol.BenchType; +import com.hypixel.hytale.server.core.inventory.MaterialQuantity; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class CraftingRecipe implements JsonAssetWithMap> { + public static final String FIELDCRAFT_REQUIREMENT = "Fieldcraft"; + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + CraftingRecipe.class, + CraftingRecipe::new, + Codec.STRING, + (recipe, blockTypeKey) -> recipe.id = blockTypeKey, + recipe -> recipe.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .append( + new KeyedCodec<>("Input", new ArrayCodec<>(MaterialQuantity.CODEC, MaterialQuantity[]::new)), + (craftingRecipe, objects) -> craftingRecipe.input = objects, + craftingRecipe -> craftingRecipe.input + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("Output", new ArrayCodec<>(MaterialQuantity.CODEC, MaterialQuantity[]::new)), + (craftingRecipe, objects) -> craftingRecipe.outputs = objects, + craftingRecipe -> craftingRecipe.outputs + ) + .add() + .append( + new KeyedCodec<>("PrimaryOutput", MaterialQuantity.CODEC), + (craftingRecipe, objects) -> craftingRecipe.primaryOutput = objects, + craftingRecipe -> craftingRecipe.primaryOutput + ) + .add() + .append( + new KeyedCodec<>("OutputQuantity", Codec.INTEGER), + (craftingRecipe, quantity) -> craftingRecipe.primaryOutputQuantity = quantity, + craftingRecipe -> craftingRecipe.primaryOutputQuantity + ) + .add() + .append( + new KeyedCodec<>( + "BenchRequirement", + new ArrayCodec<>( + BuilderCodec.builder(BenchRequirement.class, BenchRequirement::new) + .append( + new KeyedCodec<>("Type", new EnumCodec<>(BenchType.class)), + (benchRequirement, benchType) -> benchRequirement.type = benchType, + benchRequirement -> benchRequirement.type + ) + .add() + .append(new KeyedCodec<>("Id", Codec.STRING), (benchRequirement, s) -> benchRequirement.id = s, benchRequirement -> benchRequirement.id) + .add() + .append( + new KeyedCodec<>("Categories", Codec.STRING_ARRAY), + (benchRequirement, s) -> benchRequirement.categories = s, + benchRequirement -> benchRequirement.categories + ) + .add() + .appendInherited( + new KeyedCodec<>("RequiredTierLevel", Codec.INTEGER), + (benchRequirement, s) -> benchRequirement.requiredTierLevel = s, + benchRequirement -> benchRequirement.requiredTierLevel, + (benchRequirement, parent) -> benchRequirement.requiredTierLevel = parent.requiredTierLevel + ) + .add() + .build(), + BenchRequirement[]::new + ) + ), + (craftingRecipe, objects) -> craftingRecipe.benchRequirement = objects, + craftingRecipe -> craftingRecipe.benchRequirement + ) + .add() + .append( + new KeyedCodec<>("TimeSeconds", Codec.DOUBLE), + (craftingRecipe, d) -> craftingRecipe.timeSeconds = d.floatValue(), + craftingRecipe -> (double)craftingRecipe.timeSeconds + ) + .addValidator(Validators.min(0.0)) + .add() + .append( + new KeyedCodec<>("KnowledgeRequired", Codec.BOOLEAN), + (craftingRecipe, b) -> craftingRecipe.knowledgeRequired = b, + craftingRecipe -> craftingRecipe.knowledgeRequired + ) + .add() + .append( + new KeyedCodec<>("RequiredMemoriesLevel", Codec.INTEGER), + (craftingRecipe, integer) -> craftingRecipe.requiredMemoriesLevel = integer, + craftingRecipe -> craftingRecipe.requiredMemoriesLevel + ) + .documentation("The level of Memories starts from 1, meaning a recipe with a RequiredMemoriesLevel set at 1 will always be available to players.") + .addValidator(Validators.greaterThanOrEqual(1)) + .add() + .validator((craftingRecipe, results) -> { + BenchRequirement[] benchRequirements = craftingRecipe.getBenchRequirement(); + if (benchRequirements != null) { + for (BenchRequirement benchRequirement : benchRequirements) { + if (craftingRecipe.isKnowledgeRequired() && benchRequirement.type != BenchType.Crafting && benchRequirement.type != BenchType.DiagramCrafting) { + results.fail("KnowledgeRequired in recipe can't be set for non crafting recipes"); + } + + if (benchRequirement.type == BenchType.DiagramCrafting && craftingRecipe.getOutputs() != null && craftingRecipe.getOutputs().length > 1) { + results.fail("DiagramCrafting in recipe can only have 1 output"); + } + + if ("Fieldcraft".equals(benchRequirement.id) && craftingRecipe.getTimeSeconds() > 0.0F) { + results.warn(String.format("Bench Requirement in recipe for '%s' should not have a delay (TimeSeconds) set!", "Fieldcraft")); + } + } + } + }) + .afterDecode(CraftingRecipe::processConfig) + .build(); + private static final MaterialQuantity[] EMPTY_OUTPUT = new MaterialQuantity[0]; + private static AssetStore> ASSET_STORE; + private AssetExtraInfo.Data data; + protected String id; + protected MaterialQuantity[] input; + protected MaterialQuantity[] outputs = EMPTY_OUTPUT; + protected MaterialQuantity primaryOutput; + protected int primaryOutputQuantity = 1; + protected BenchRequirement[] benchRequirement; + protected float timeSeconds; + protected boolean knowledgeRequired; + protected int requiredMemoriesLevel = 1; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(CraftingRecipe.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public CraftingRecipe( + MaterialQuantity[] input, + MaterialQuantity primaryOutput, + MaterialQuantity[] outputs, + int outputQuantity, + BenchRequirement[] benchRequirement, + float timeSeconds, + boolean knowledgeRequired, + int requiredMemoriesLevel + ) { + this.input = input; + this.primaryOutput = primaryOutput; + this.outputs = outputs; + this.primaryOutputQuantity = outputQuantity; + this.benchRequirement = benchRequirement; + this.timeSeconds = timeSeconds; + this.knowledgeRequired = knowledgeRequired; + this.requiredMemoriesLevel = requiredMemoriesLevel; + } + + public CraftingRecipe(CraftingRecipe other) { + this.input = other.input; + this.primaryOutput = other.primaryOutput; + this.outputs = other.outputs; + this.primaryOutputQuantity = other.primaryOutputQuantity; + this.benchRequirement = other.benchRequirement; + this.timeSeconds = other.timeSeconds; + this.knowledgeRequired = other.knowledgeRequired; + this.requiredMemoriesLevel = other.requiredMemoriesLevel; + } + + protected CraftingRecipe() { + } + + public static String generateIdFromItemRecipe(Item item, int i) { + return item.id + "_Recipe_Generated_" + i; + } + + @Nonnull + public com.hypixel.hytale.protocol.CraftingRecipe toPacket(String id) { + com.hypixel.hytale.protocol.CraftingRecipe packet = new com.hypixel.hytale.protocol.CraftingRecipe(); + packet.id = id; + if (this.input != null && this.input.length > 0) { + packet.inputs = ArrayUtil.copyAndMutate(this.input, MaterialQuantity::toPacket, com.hypixel.hytale.protocol.MaterialQuantity[]::new); + } + + packet.primaryOutput = this.primaryOutput.toPacket(); + if (this.outputs != null && this.outputs.length > 0) { + packet.outputs = ArrayUtil.copyAndMutate(this.outputs, MaterialQuantity::toPacket, com.hypixel.hytale.protocol.MaterialQuantity[]::new); + } + + if (this.benchRequirement != null && this.benchRequirement.length > 0) { + packet.benchRequirement = this.benchRequirement; + } + + packet.knowledgeRequired = this.knowledgeRequired; + packet.timeSeconds = this.timeSeconds; + packet.requiredMemoriesLevel = this.requiredMemoriesLevel; + return packet; + } + + private void processConfig() { + if ((this.outputs == null || this.outputs.length == 0) && this.primaryOutput != null) { + this.outputs = new MaterialQuantity[]{this.primaryOutput}; + } + } + + public MaterialQuantity[] getInput() { + return this.input; + } + + public MaterialQuantity[] getOutputs() { + return this.outputs; + } + + public BenchRequirement[] getBenchRequirement() { + return this.benchRequirement; + } + + public float getTimeSeconds() { + return this.timeSeconds; + } + + public boolean isKnowledgeRequired() { + return this.knowledgeRequired; + } + + public int getRequiredMemoriesLevel() { + return this.requiredMemoriesLevel; + } + + public MaterialQuantity getPrimaryOutput() { + return this.primaryOutput; + } + + public boolean isRestrictedByBenchTierLevel(String benchId, int tierLevel) { + if (this.benchRequirement == null) { + return false; + } else { + for (BenchRequirement b : this.benchRequirement) { + if (benchId.equals(b.id) && tierLevel < b.requiredTierLevel) { + return true; + } + } + + return false; + } + } + + @Nonnull + @Override + public String toString() { + return "CraftingRecipe{input=" + + Arrays.toString((Object[])this.input) + + ", extraOutputs=" + + Arrays.toString((Object[])this.outputs) + + ", primaryOutput=" + + this.primaryOutput + + ", outputQuantity=" + + this.primaryOutputQuantity + + ", benchRequirement=" + + Arrays.toString((Object[])this.benchRequirement) + + ", timeSeconds=" + + this.timeSeconds + + ", knowledgeRequired=" + + this.knowledgeRequired + + ", requiredMemoriesLevel=" + + this.requiredMemoriesLevel + + "}"; + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/FieldcraftCategory.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/FieldcraftCategory.java new file mode 100644 index 0000000..c207a70 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/FieldcraftCategory.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import javax.annotation.Nonnull; + +public class FieldcraftCategory + implements JsonAssetWithMap>, + NetworkSerializable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + FieldcraftCategory.class, + FieldcraftCategory::new, + Codec.STRING, + (itemCategory, k) -> itemCategory.id = k, + itemCategory -> itemCategory.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .addField(new KeyedCodec<>("Name", Codec.STRING), (itemCategory, s) -> itemCategory.name = s, itemCategory -> itemCategory.name) + .append(new KeyedCodec<>("Icon", Codec.STRING), (itemCategory, s) -> itemCategory.icon = s, itemCategory -> itemCategory.icon) + .addValidator(CommonAssetValidator.ICON_CRAFTING) + .add() + .addField(new KeyedCodec<>("Order", Codec.INTEGER), (itemCategory, s) -> itemCategory.order = s, itemCategory -> itemCategory.order) + .build(); + private static DefaultAssetMap ASSET_MAP; + protected AssetExtraInfo.Data data; + protected String id; + protected String name; + protected String icon; + protected int order; + private SoftReference cachedPacket; + + public static DefaultAssetMap getAssetMap() { + if (ASSET_MAP == null) { + ASSET_MAP = (DefaultAssetMap)AssetRegistry.getAssetStore(FieldcraftCategory.class).getAssetMap(); + } + + return ASSET_MAP; + } + + protected FieldcraftCategory() { + } + + @Nonnull + public com.hypixel.hytale.protocol.ItemCategory toPacket() { + com.hypixel.hytale.protocol.ItemCategory cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.ItemCategory packet = new com.hypixel.hytale.protocol.ItemCategory(); + packet.id = this.id; + packet.icon = this.icon; + packet.name = this.name; + packet.order = this.order; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + public String getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public String getIcon() { + return this.icon; + } + + @Nonnull + @Override + public String toString() { + return "FieldcraftCategory{id='" + this.id + "', name='" + this.name + "', icon='" + this.icon + "', order=" + this.order + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/Item.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/Item.java new file mode 100644 index 0000000..fa2b921 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/Item.java @@ -0,0 +1,1135 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.AssetCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.schema.metadata.AllowEmptyObject; +import com.hypixel.hytale.codec.schema.metadata.ui.UIButton; +import com.hypixel.hytale.codec.schema.metadata.ui.UICreateButtons; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDefaultCollapsedState; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDisplayMode; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorPreview; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorSectionStart; +import com.hypixel.hytale.codec.schema.metadata.ui.UIPropertyTitle; +import com.hypixel.hytale.codec.schema.metadata.ui.UIRebuildCaches; +import com.hypixel.hytale.codec.schema.metadata.ui.UISidebarButtons; +import com.hypixel.hytale.codec.schema.metadata.ui.UITypeIcon; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.MapUtil; +import com.hypixel.hytale.protocol.ColorLight; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.ItemBase; +import com.hypixel.hytale.protocol.ItemResourceType; +import com.hypixel.hytale.protocol.ModelTrail; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BuilderToolData; +import com.hypixel.hytale.server.core.asset.type.itemanimation.config.ItemPlayerAnimations; +import com.hypixel.hytale.server.core.asset.type.itemsound.config.ItemSoundSet; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelParticle; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.inventory.MaterialQuantity; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.interaction.interaction.UnarmedInteractions; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.InteractionConfiguration; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.ChangeActiveSlotInteraction; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.lang.ref.SoftReference; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Item implements JsonAssetWithMap>, NetworkSerializable { + private static final AssetBuilderCodec.Builder CODEC_BUILDER = AssetBuilderCodec.builder( + Item.class, + Item::new, + Codec.STRING, + (item, blockTypeKey) -> item.id = blockTypeKey, + item -> item.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .metadata(new UIEditorPreview(UIEditorPreview.PreviewType.ITEM)) + .metadata(new UITypeIcon("Item.png")) + .metadata( + new UIRebuildCaches( + false, + UIRebuildCaches.ClientCache.MODELS, + UIRebuildCaches.ClientCache.BLOCK_TEXTURES, + UIRebuildCaches.ClientCache.MODEL_TEXTURES, + UIRebuildCaches.ClientCache.MAP_GEOMETRY, + UIRebuildCaches.ClientCache.ITEM_ICONS + ) + ) + .metadata(new UISidebarButtons(new UIButton("server.assetEditor.buttons.equipItem", "EquipItem"))) + .metadata(new UICreateButtons(new UIButton("server.assetEditor.buttons.createAndEquipItem", "EquipItem"))) + .appendInherited(new KeyedCodec<>("Icon", Codec.STRING), (item, s) -> item.icon = s, item -> item.icon, (item, parent) -> item.icon = parent.icon) + .addValidator(CommonAssetValidator.ICON_ITEM) + .metadata(new UIEditor(new UIEditor.Icon("Icons/ItemsGenerated/{assetId}.png", 64, 64))) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.ITEM_ICONS)) + .add() + .appendInherited( + new KeyedCodec<>("Categories", new ArrayCodec<>(Codec.STRING, String[]::new).metadata(new UIEditor(new UIEditor.Dropdown("ItemCategories")))), + (item, s) -> item.categories = s, + item -> item.categories, + (item, parent) -> item.categories = parent.categories + ) + .addValidatorLate(() -> ItemCategory.VALIDATOR_CACHE.getArrayValidator().late()) + .documentation("A list of categories this item will be shown in on the creative library menu.") + .add() + .appendInherited( + new KeyedCodec<>("IconProperties", AssetIconProperties.CODEC), + (item, s) -> item.iconProperties = s, + item -> item.iconProperties, + (item, parent) -> item.iconProperties = parent.iconProperties + ) + .metadata(UIDisplayMode.HIDDEN) + .add() + .appendInherited( + new KeyedCodec<>("TranslationProperties", ItemTranslationProperties.CODEC), + (item, s) -> item.translationProperties = s, + item -> item.translationProperties, + (item, parent) -> item.translationProperties = parent.translationProperties + ) + .documentation("The translation properties for this item asset.") + .add() + .appendInherited( + new KeyedCodec<>("ItemLevel", Codec.INTEGER), + (item, s) -> item.itemLevel = s, + item -> item.itemLevel, + (item, parent) -> item.itemLevel = parent.itemLevel + ) + .add() + .appendInherited( + new KeyedCodec<>("Reticle", new ContainedAssetCodec<>(ItemReticleConfig.class, ItemReticleConfig.CODEC)), + (item, s) -> item.reticleId = s, + item -> item.reticleId, + (item, parent) -> item.reticleId = parent.reticleId + ) + .addValidator(ItemReticleConfig.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("MaxStack", Codec.INTEGER), (item, s) -> item.maxStack = s, item -> item.maxStack, (item, parent) -> item.maxStack = parent.maxStack + ) + .metadata(new UIPropertyTitle("Max Stack Size")) + .documentation("The maximum amount this item can be stacked in the inventory") + .addValidator(Validators.greaterThan(0)) + .add() + .append(new KeyedCodec<>("Quality", Codec.STRING), (item, s) -> item.qualityId = s, item -> item.qualityId) + .addValidator(ItemQuality.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("ItemEntity", ItemEntityConfig.CODEC), + (item, itemEntityConfig) -> item.itemEntityConfig = itemEntityConfig, + item -> item.itemEntityConfig, + (item, parent) -> item.itemEntityConfig = parent.itemEntityConfig + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("SoundEventId", Codec.STRING), + (item, s) -> item.soundEventId = s, + item -> item.soundEventId, + (item, parent) -> item.soundEventId = parent.soundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("ItemSoundSetId", Codec.STRING), + (item, s) -> item.itemSoundSetId = s, + item -> item.itemSoundSetId, + (item, parent) -> item.itemSoundSetId = parent.itemSoundSetId + ) + .addValidator(Validators.nonNull()) + .addValidator(ItemSoundSet.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited(new KeyedCodec<>("Set", Codec.STRING), (item, s) -> item.set = s, item -> item.set, (item, parent) -> item.set = parent.set) + .add() + .appendInherited( + new KeyedCodec<>("Model", Codec.STRING), (item, s) -> item.model = s, item -> item.model, (item, parent) -> item.model = parent.model + ) + .addValidator(CommonAssetValidator.MODEL_ITEM) + .metadata(new UIEditorSectionStart("Rendering")) + .metadata(new UIRebuildCaches(false, UIRebuildCaches.ClientCache.MODELS)) + .metadata(new UIPropertyTitle("Item Model")) + .documentation("The model used for rendering this item. If this is a block, BlockType.Model should be used instead.") + .add() + .appendInherited( + new KeyedCodec<>("Scale", Codec.DOUBLE), + (item, s) -> item.scale = s.floatValue(), + item -> (double)item.scale, + (item, parent) -> item.scale = parent.scale + ) + .metadata(new UIPropertyTitle("Item Scale")) + .add() + .appendInherited( + new KeyedCodec<>("Texture", Codec.STRING), (item, s) -> item.texture = s, item -> item.texture, (item, parent) -> item.texture = parent.texture + ) + .addValidator(CommonAssetValidator.TEXTURE_ITEM) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .metadata(new UIPropertyTitle("Item Texture")) + .documentation("The texture used for rendering this item. If this is a block, block specific properties should be used instead.") + .add() + .appendInherited( + new KeyedCodec<>("Animation", Codec.STRING), + (item, s) -> item.animation = s, + item -> item.animation, + (item, parent) -> item.animation = parent.animation + ) + .addValidator(CommonAssetValidator.ANIMATION_ITEM_BLOCK) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .metadata(new UIPropertyTitle("Item Animation")) + .documentation("The animation used for rendering this item. If this is a block, block specific properties should be used instead.") + .add() + .appendInherited( + new KeyedCodec<>("UsePlayerAnimations", Codec.BOOLEAN), + (item, s) -> item.usePlayerAnimations = s, + item -> item.usePlayerAnimations, + (item, parent) -> item.usePlayerAnimations = parent.usePlayerAnimations + ) + .add() + .appendInherited( + new KeyedCodec<>("PlayerAnimationsId", ItemPlayerAnimations.CHILD_CODEC), + (item, s) -> item.playerAnimationsId = s, + item -> item.playerAnimationsId, + (item, parent) -> item.playerAnimationsId = parent.playerAnimationsId + ) + .addValidator(Validators.nonNull()) + .addValidator(ItemPlayerAnimations.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("DroppedItemAnimation", Codec.STRING), + (item, animation) -> item.droppedItemAnimation = animation, + item -> item.droppedItemAnimation, + (item, parent) -> item.droppedItemAnimation = parent.droppedItemAnimation + ) + .addValidator(CommonAssetValidator.ANIMATION_ITEM_BLOCK) + .add() + .appendInherited( + new KeyedCodec<>("Particles", ModelParticle.ARRAY_CODEC), + (item, s) -> item.particles = s, + item -> item.particles, + (item, parent) -> item.particles = parent.particles + ) + .metadata(new UIPropertyTitle("Item Particles")) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .documentation("The particles played for this item. If this is a block, block specific properties should be used instead.") + .add() + .appendInherited( + new KeyedCodec<>("FirstPersonParticles", ModelParticle.ARRAY_CODEC), + (item, s) -> item.firstPersonParticles = s, + item -> item.firstPersonParticles, + (item, parent) -> item.firstPersonParticles = parent.firstPersonParticles + ) + .metadata(new UIPropertyTitle("Item First Person Particles")) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .documentation("The particles played for this item when in first person. If this is a block, block specific properties should be used instead.") + .add() + .appendInherited( + new KeyedCodec<>("Trails", ModelAsset.MODEL_TRAIL_ARRAY_CODEC), + (item, s) -> item.trails = s, + item -> item.trails, + (item, parent) -> item.trails = parent.trails + ) + .metadata(new UIPropertyTitle("Item Trails")) + .documentation("The trail attached to this item") + .add() + .appendInherited( + new KeyedCodec<>("Light", ProtocolCodecs.COLOR_LIGHT), (item, o) -> item.light = o, item -> item.light, (item, parent) -> item.light = parent.light + ) + .metadata(new UIPropertyTitle("Item Light")) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.MODELS)) + .documentation("The light this item is emitting when being held or dropped. For block light, see Block properties") + .add() + .append(new KeyedCodec<>("Recipe", CraftingRecipe.CODEC), (item, s) -> item.recipeToGenerate = s, item -> item.recipeToGenerate) + .metadata(new UIEditorSectionStart("Crafting")) + .add() + .appendInherited( + new KeyedCodec<>( + "ResourceTypes", + new ArrayCodec<>( + BuilderCodec.builder(ItemResourceType.class, ItemResourceType::new) + .append(new KeyedCodec<>("Id", Codec.STRING), (itemResourceType, s) -> itemResourceType.id = s, itemResourceType -> itemResourceType.id) + .addValidator(ResourceType.VALIDATOR_CACHE.getValidator()) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("Quantity", Codec.INTEGER), + (itemResourceType, s) -> itemResourceType.quantity = s, + itemResourceType -> itemResourceType.quantity + ) + .addValidator(Validators.greaterThan(0)) + .add() + .build(), + ItemResourceType[]::new + ) + ), + (item, s) -> item.resourceTypes = s, + item -> item.resourceTypes, + (item, parent) -> item.resourceTypes = parent.resourceTypes + ) + .add() + .appendInherited( + new KeyedCodec<>("Tool", ItemTool.CODEC), (item, s) -> item.tool = s, item -> item.tool, (item, parent) -> item.tool = parent.tool + ) + .metadata(new UIEditorSectionStart("Functionality")) + .add() + .appendInherited( + new KeyedCodec<>("BlockSelectorTool", BlockSelectorToolData.CODEC), + (item, s) -> item.blockSelectorToolData = s, + item -> item.blockSelectorToolData, + (item, parent) -> item.blockSelectorToolData = parent.blockSelectorToolData + ) + .add() + .appendInherited( + new KeyedCodec<>("BuilderTool", BuilderToolData.CODEC), + (item, s) -> item.builderToolData = s, + item -> item.builderToolData, + (item, parent) -> item.builderToolData = parent.builderToolData + ) + .add() + .appendInherited( + new KeyedCodec<>("Weapon", ItemWeapon.CODEC), (item, s) -> item.weapon = s, item -> item.weapon, (item, parent) -> item.weapon = parent.weapon + ) + .metadata(AllowEmptyObject.INSTANCE) + .add() + .appendInherited(new KeyedCodec<>("Armor", ItemArmor.CODEC), (item, s) -> item.armor = s, item -> item.armor, (item, parent) -> item.armor = parent.armor) + .add() + .appendInherited( + new KeyedCodec<>("Glider", ItemGlider.CODEC), (item, s) -> item.glider = s, item -> item.glider, (item, parent) -> item.glider = parent.glider + ) + .add() + .appendInherited( + new KeyedCodec<>("Utility", ItemUtility.CODEC), (item, s) -> item.utility = s, item -> item.utility, (item, parent) -> item.utility = parent.utility + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("PortalKey", PortalKey.CODEC), + (item, s) -> item.portalKey = s, + item -> item.portalKey, + (item, parent) -> item.portalKey = parent.portalKey + ) + .add() + .appendInherited( + new KeyedCodec<>("Container", ItemStackContainerConfig.CODEC), + (item, s) -> item.itemStackContainerConfig = s, + item -> item.itemStackContainerConfig, + (item, parent) -> item.itemStackContainerConfig = parent.itemStackContainerConfig + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Consumable", Codec.BOOLEAN), + (item, s) -> item.consumable = s, + item -> item.consumable, + (item, parent) -> item.consumable = parent.consumable + ) + .add() + .appendInherited( + new KeyedCodec<>("Variant", Codec.BOOLEAN), (item, b) -> item.variant = b, item -> item.variant, (item, parent) -> item.variant = parent.variant + ) + .documentation( + "Whether this item is a variant of another. Typically this is only the case for connected blocks. If this item is marked as a variant, then we filter it out of the item library menu by default, unless the player chooses to display variants." + ) + .add() + .appendInherited( + new KeyedCodec<>("MaxDurability", Codec.DOUBLE), + (item, s) -> item.maxDurability = s, + item -> item.maxDurability, + (item, parent) -> item.maxDurability = parent.maxDurability + ) + .add() + .appendInherited( + new KeyedCodec<>("FuelQuality", Codec.DOUBLE), + (item, s) -> item.fuelQuality = s, + item -> item.fuelQuality, + (item, parent) -> item.fuelQuality = parent.fuelQuality + ) + .add() + .appendInherited( + new KeyedCodec<>("DurabilityLossOnHit", Codec.DOUBLE), + (item, s) -> item.durabilityLossOnHit = s, + item -> item.durabilityLossOnHit, + (item, parent) -> item.durabilityLossOnHit = parent.durabilityLossOnHit + ) + .add() + .appendInherited( + new KeyedCodec<>("BlockType", new ContainedAssetCodec<>(BlockType.class, BlockType.CODEC, ContainedAssetCodec.Mode.INHERIT_ID_AND_PARENT)), + (item, s) -> item.hasBlockType = true, + item -> item.blockId, + (item, parent) -> item.blockId = parent.blockId + ) + .metadata(new UIEditorSectionStart("Block")) + .metadata( + new UIRebuildCaches(false, UIRebuildCaches.ClientCache.MODELS, UIRebuildCaches.ClientCache.BLOCK_TEXTURES, UIRebuildCaches.ClientCache.MODEL_TEXTURES) + ) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .>appendInherited( + new KeyedCodec<>("Interactions", new EnumMapCodec<>(InteractionType.class, RootInteraction.CHILD_ASSET_CODEC)), + (item, v) -> item.interactions = MapUtil.combineUnmodifiable(item.interactions, v, () -> new EnumMap<>(InteractionType.class)), + item -> item.interactions, + (item, parent) -> item.interactions = parent.interactions + ) + .addValidator(RootInteraction.VALIDATOR_CACHE.getMapValueValidator()) + .metadata(new UIEditorSectionStart("Interactions")) + .add() + .appendInherited( + new KeyedCodec<>("InteractionConfig", InteractionConfiguration.CODEC), + (item, v) -> item.interactionConfig = v, + item -> item.interactionConfig, + (item, parent) -> item.interactionConfig = parent.interactionConfig + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("InteractionVars", new MapCodec<>(RootInteraction.CHILD_ASSET_CODEC, HashMap::new)), + (item, v) -> item.interactionVars = MapUtil.combineUnmodifiable(item.interactionVars, v), + item -> item.interactionVars, + (item, parent) -> item.interactionVars = parent.interactionVars + ) + .addValidator(RootInteraction.VALIDATOR_CACHE.getMapValueValidator()) + .add() + .appendInherited( + new KeyedCodec<>( + "ItemAppearanceConditions", new MapCodec<>(new ArrayCodec<>(ItemAppearanceCondition.CODEC, ItemAppearanceCondition[]::new), HashMap::new) + ), + (item, stringMap) -> item.itemAppearanceConditions = stringMap, + item -> item.itemAppearanceConditions, + (item, parent) -> item.itemAppearanceConditions = parent.itemAppearanceConditions + ) + .documentation("Define per EntityStat an array of ItemAppearanceCondition. Only a single condition will be applied to the item at the same time.") + .addValidator(EntityStatType.VALIDATOR_CACHE.getMapKeyValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("DisplayEntityStatsHUD", Codec.STRING_ARRAY), + (item, strings) -> item.rawDisplayEntityStatsHUD = strings, + item -> item.rawDisplayEntityStatsHUD, + (item, parent) -> item.rawDisplayEntityStatsHUD = parent.rawDisplayEntityStatsHUD + ) + .documentation("Used to indicate to the client whether an EntityStat HUD UI needs to be displayed") + .add() + .appendInherited( + new KeyedCodec<>("PullbackConfig", ItemPullbackConfig.CODEC), + (item, s) -> item.pullbackConfig = s, + item -> item.pullbackConfig, + (item, parent) -> item.pullbackConfig = parent.pullbackConfig + ) + .documentation("Overrides the offset of first person arms when close to obstacles") + .add() + .appendInherited( + new KeyedCodec<>("ClipsGeometry", Codec.BOOLEAN), + (item, s) -> item.clipsGeometry = s, + item -> item.clipsGeometry, + (item, parent) -> item.clipsGeometry = parent.clipsGeometry + ) + .add() + .appendInherited( + new KeyedCodec<>("RenderDeployablePreview", Codec.BOOLEAN), + (item, s) -> item.renderDeployablePreview = s, + item -> item.renderDeployablePreview, + (item, parent) -> item.renderDeployablePreview = parent.renderDeployablePreview + ) + .add() + .appendInherited( + new KeyedCodec<>("DropOnDeath", Codec.BOOLEAN), + (item, aBoolean) -> item.dropOnDeath = aBoolean, + item -> item.dropOnDeath, + (item, parent) -> item.dropOnDeath = parent.dropOnDeath + ) + .add() + .afterDecode(Item::processConfig); + public static final AssetCodec CODEC = CODEC_BUILDER.build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(Item::getAssetStore)); + private static AssetStore> ASSET_STORE; + public static final String UNKNOWN_TEXTURE = "Items/Unknown.png"; + public static final Item UNKNOWN = new Item("Unknown") { + { + this.playerAnimationsId = "Item"; + this.model = "Items/CreativeTools/EditorTool.blockymodel"; + this.texture = "Items/Unknown.png"; + this.maxStack = 100; + this.itemEntityConfig = ItemEntityConfig.DEFAULT; + this.interactionConfig = InteractionConfiguration.DEFAULT; + this.interactions = Map.of(InteractionType.SwapFrom, ChangeActiveSlotInteraction.DEFAULT_ROOT.getId()); + } + }; + protected AssetExtraInfo.Data data; + protected String id; + protected String icon; + protected AssetIconProperties iconProperties; + protected ItemTranslationProperties translationProperties; + protected String reticleId; + protected int reticleIndex = 0; + protected int itemLevel; + protected int maxStack = -1; + protected String qualityId; + protected int qualityIndex = 0; + protected CraftingRecipe recipeToGenerate; + protected String blockId; + protected boolean hasBlockType; + protected boolean consumable; + protected boolean variant; + protected ItemTool tool; + protected BlockSelectorToolData blockSelectorToolData; + protected BuilderToolData builderToolData; + protected ItemWeapon weapon; + protected ItemArmor armor; + protected ItemGlider glider; + protected ItemUtility utility = ItemUtility.DEFAULT; + protected ItemStackContainerConfig itemStackContainerConfig = ItemStackContainerConfig.DEFAULT; + protected PortalKey portalKey; + protected String playerAnimationsId = "Default"; + protected boolean usePlayerAnimations = false; + protected String model; + protected float scale = 1.0F; + protected String texture = "Items/Unknown.png"; + protected String animation; + protected String[] categories; + protected String set; + protected String soundEventId; + protected transient int soundEventIndex; + protected String itemSoundSetId = "ISS_Default"; + protected transient int itemSoundSetIndex; + protected ModelParticle[] particles; + protected ModelParticle[] firstPersonParticles; + protected ModelTrail[] trails; + protected ColorLight light; + protected ItemResourceType[] resourceTypes; + protected Map stateToBlock; + protected Map blockToState; + protected Map interactions = Collections.emptyMap(); + protected Map interactionVars = Collections.emptyMap(); + protected InteractionConfiguration interactionConfig; + protected ItemEntityConfig itemEntityConfig; + protected String droppedItemAnimation; + protected double maxDurability; + protected double fuelQuality = 1.0; + protected double durabilityLossOnHit; + protected Map itemAppearanceConditions; + protected String[] rawDisplayEntityStatsHUD; + @Nullable + protected int[] displayEntityStatsHUD; + protected ItemPullbackConfig pullbackConfig; + protected boolean clipsGeometry; + protected boolean renderDeployablePreview; + protected boolean dropOnDeath; + private transient SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(Item.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + protected Item() { + } + + public Item(String id) { + this.id = id; + } + + public Item(@Nonnull Item other) { + this.data = other.data; + this.id = other.id; + this.icon = other.icon; + this.iconProperties = other.iconProperties; + this.translationProperties = other.translationProperties; + this.reticleId = other.reticleId; + this.itemLevel = other.itemLevel; + this.maxStack = other.maxStack; + this.qualityId = other.qualityId; + this.recipeToGenerate = other.recipeToGenerate; + this.consumable = other.consumable; + this.variant = other.variant; + this.playerAnimationsId = other.playerAnimationsId; + this.usePlayerAnimations = other.usePlayerAnimations; + this.model = other.model; + this.scale = other.scale; + this.texture = other.texture; + this.animation = other.animation; + this.tool = other.tool; + this.blockSelectorToolData = other.blockSelectorToolData; + this.builderToolData = other.builderToolData; + this.weapon = other.weapon; + this.armor = other.armor; + this.utility = other.utility; + this.portalKey = other.portalKey; + this.categories = other.categories; + this.set = other.set; + this.soundEventId = other.soundEventId; + this.soundEventIndex = other.soundEventIndex; + this.itemSoundSetId = other.itemSoundSetId; + this.itemSoundSetIndex = other.itemSoundSetIndex; + this.particles = other.particles; + this.firstPersonParticles = other.firstPersonParticles; + this.trails = other.trails; + this.light = other.light; + this.resourceTypes = other.resourceTypes; + this.interactions = other.interactions; + this.interactionVars = other.interactionVars; + this.interactionConfig = other.interactionConfig; + this.droppedItemAnimation = other.droppedItemAnimation; + this.itemEntityConfig = other.itemEntityConfig; + this.stateToBlock = other.stateToBlock; + this.blockId = other.blockId; + this.hasBlockType = other.hasBlockType; + this.displayEntityStatsHUD = other.displayEntityStatsHUD; + this.pullbackConfig = other.pullbackConfig; + this.clipsGeometry = other.clipsGeometry; + this.renderDeployablePreview = other.renderDeployablePreview; + this.dropOnDeath = other.dropOnDeath; + } + + @Nonnull + public ItemBase toPacket() { + ItemBase cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + ItemBase packet = new ItemBase(); + packet.id = this.id; + if (this.icon != null) { + packet.icon = this.icon; + } + + if (this.iconProperties != null) { + packet.iconProperties = this.iconProperties.toPacket(); + } + + if (this.translationProperties != null) { + packet.translationProperties = this.translationProperties.toPacket(); + } + + if (this.model != null) { + packet.model = this.model; + } + + packet.scale = this.scale; + if (this.texture != null) { + packet.texture = this.texture; + } + + if (this.animation != null) { + packet.animation = this.animation; + } + + packet.playerAnimationsId = this.playerAnimationsId; + packet.usePlayerAnimations = this.usePlayerAnimations; + packet.reticleIndex = this.reticleIndex; + packet.maxStack = this.maxStack; + packet.itemLevel = this.itemLevel; + packet.qualityIndex = this.qualityIndex; + if (this.blockId != null) { + packet.blockId = BlockType.getAssetMap().getIndexOrDefault(this.blockId, 1); + if (packet.blockId == 0) { + throw new IllegalArgumentException("Block Id Can't be 0"); + } + } + + packet.consumable = this.consumable; + packet.variant = this.variant; + if (this.tool != null) { + packet.tool = this.tool.toPacket(); + } + + if (this.blockSelectorToolData != null) { + packet.blockSelectorTool = this.blockSelectorToolData.toPacket(); + } + + if (this.builderToolData != null) { + packet.builderToolData = this.builderToolData.toPacket(); + } + + if (this.weapon != null) { + packet.weapon = this.weapon.toPacket(); + } + + if (this.armor != null) { + packet.armor = this.armor.toPacket(); + } + + if (this.glider != null) { + packet.gliderConfig = this.glider.toPacket(); + } + + if (this.utility != null) { + packet.utility = this.utility.toPacket(); + } + + if (this.categories != null && this.categories.length > 0) { + packet.categories = this.categories; + } + + if (this.set != null) { + packet.set = this.set; + } + + packet.soundEventIndex = this.soundEventIndex; + packet.itemSoundSetIndex = this.itemSoundSetIndex; + if (this.particles != null && this.particles.length > 0) { + packet.particles = new com.hypixel.hytale.protocol.ModelParticle[this.particles.length]; + + for (int i = 0; i < this.particles.length; i++) { + packet.particles[i] = this.particles[i].toPacket(); + } + } + + if (this.firstPersonParticles != null && this.firstPersonParticles.length > 0) { + packet.firstPersonParticles = new com.hypixel.hytale.protocol.ModelParticle[this.firstPersonParticles.length]; + + for (int i = 0; i < this.firstPersonParticles.length; i++) { + packet.firstPersonParticles[i] = this.firstPersonParticles[i].toPacket(); + } + } + + if (this.trails != null && this.trails.length > 0) { + packet.trails = this.trails; + } + + if (this.light != null) { + packet.light = this.light; + } + + if (this.resourceTypes != null && this.resourceTypes.length > 0) { + packet.resourceTypes = this.resourceTypes; + } + + Object2IntOpenHashMap interactionsIntMap = new Object2IntOpenHashMap<>(); + + for (Entry e : this.interactions.entrySet()) { + interactionsIntMap.put(e.getKey(), RootInteraction.getRootInteractionIdOrUnknown(e.getValue())); + } + + packet.interactions = interactionsIntMap; + Object2IntOpenHashMap interactionVarsIntMap = new Object2IntOpenHashMap<>(); + + for (Entry e : this.interactionVars.entrySet()) { + interactionVarsIntMap.put(e.getKey(), RootInteraction.getRootInteractionIdOrUnknown(e.getValue())); + } + + packet.interactionVars = interactionVarsIntMap; + packet.interactionConfig = this.interactionConfig.toPacket(); + packet.durability = this.getMaxDurability(); + packet.itemEntity = this.itemEntityConfig.toPacket(); + if (this.droppedItemAnimation != null) { + packet.droppedItemAnimation = this.droppedItemAnimation; + } + + if (this.itemAppearanceConditions != null) { + HashMap map = new HashMap<>(); + + for (Entry entry : this.itemAppearanceConditions.entrySet()) { + ItemAppearanceCondition[] conditions = entry.getValue(); + com.hypixel.hytale.protocol.ItemAppearanceCondition[] protocolConditions = new com.hypixel.hytale.protocol.ItemAppearanceCondition[conditions.length]; + + for (int i = 0; i < conditions.length; i++) { + protocolConditions[i] = conditions[i].toPacket(); + } + + map.put(EntityStatType.getAssetMap().getIndex(entry.getKey()), protocolConditions); + } + + packet.itemAppearanceConditions = map; + } + + if (this.data != null) { + IntSet expandedTagIndexes = this.data.getExpandedTagIndexes(); + if (expandedTagIndexes != null) { + packet.tagIndexes = expandedTagIndexes.toIntArray(); + } + } + + packet.displayEntityStatsHUD = this.displayEntityStatsHUD; + if (this.pullbackConfig != null) { + packet.pullbackConfig = this.pullbackConfig.toPacket(); + } + + packet.clipsGeometry = this.clipsGeometry; + packet.renderDeployablePreview = this.renderDeployablePreview; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + @Nullable + public String getItemIdForState(String state) { + return this.stateToBlock != null ? this.stateToBlock.get(state) : null; + } + + @Nullable + public Item getItemForState(String state) { + String id = this.getItemIdForState(state); + return id == null ? null : getAssetMap().getAsset(id); + } + + public boolean isState() { + return this.getStateForItem(this.id) != null; + } + + @Nullable + public String getStateForItem(@Nonnull Item item) { + return this.getStateForItem(item.getId()); + } + + @Nullable + public String getStateForItem(String key) { + return this.blockToState != null ? this.blockToState.get(key) : null; + } + + public AssetExtraInfo.Data getData() { + return this.data; + } + + public String getId() { + return this.id; + } + + public String getBlockId() { + return this.blockId; + } + + @Nonnull + public String getTranslationKey() { + if (this.translationProperties != null) { + String nameTranslation = this.translationProperties.getName(); + if (nameTranslation != null) { + return nameTranslation; + } + } + + return "server.items." + this.id + ".name"; + } + + @Nonnull + public String getDescriptionTranslationKey() { + if (this.translationProperties != null) { + String descriptionTranslation = this.translationProperties.getDescription(); + if (descriptionTranslation != null) { + return descriptionTranslation; + } + } + + return "server.items." + this.id + ".description"; + } + + public String getModel() { + return this.model; + } + + public String getTexture() { + return this.texture; + } + + public boolean isConsumable() { + return this.consumable; + } + + public boolean isVariant() { + return this.variant; + } + + public boolean getUsePlayerAnimations() { + return this.usePlayerAnimations; + } + + public String getPlayerAnimationsId() { + return this.playerAnimationsId; + } + + public String getIcon() { + return this.icon; + } + + public AssetIconProperties getIconProperties() { + return this.iconProperties; + } + + public ItemTranslationProperties getTranslationProperties() { + return this.translationProperties; + } + + public float getScale() { + return this.scale; + } + + public String getReticleId() { + return this.reticleId; + } + + public int getItemLevel() { + return this.itemLevel; + } + + public int getMaxStack() { + return this.maxStack; + } + + public int getQualityIndex() { + return this.qualityIndex; + } + + public ItemTool getTool() { + return this.tool; + } + + public BlockSelectorToolData getBlockSelectorToolData() { + return this.blockSelectorToolData; + } + + public BuilderToolData getBuilderToolData() { + return this.builderToolData; + } + + public ItemArmor getArmor() { + return this.armor; + } + + public ItemGlider getGlider() { + return this.glider; + } + + @Nonnull + public ItemUtility getUtility() { + return this.utility; + } + + @Nullable + public PortalKey getPortalKey() { + return this.portalKey; + } + + @Nonnull + public ItemStackContainerConfig getItemStackContainerConfig() { + return this.itemStackContainerConfig; + } + + public String[] getCategories() { + return this.categories; + } + + public String getSoundEventId() { + return this.soundEventId; + } + + public int getSoundEventIndex() { + return this.soundEventIndex; + } + + public boolean hasBlockType() { + return this.blockId != null; + } + + public ItemWeapon getWeapon() { + return this.weapon; + } + + public ItemResourceType[] getResourceTypes() { + return this.resourceTypes; + } + + public double getMaxDurability() { + return this.maxDurability; + } + + public ColorLight getLight() { + return this.light; + } + + public Map getInteractions() { + return this.interactions; + } + + public Map getInteractionVars() { + return this.interactionVars; + } + + public ItemEntityConfig getItemEntityConfig() { + return this.itemEntityConfig; + } + + public String getDroppedItemAnimation() { + return this.droppedItemAnimation; + } + + public double getDurabilityLossOnHit() { + return this.durabilityLossOnHit; + } + + public int[] getDisplayEntityStatsHUD() { + return this.displayEntityStatsHUD; + } + + public ItemPullbackConfig getPullbackConfig() { + return this.pullbackConfig; + } + + public boolean getClipsGeometry() { + return this.clipsGeometry; + } + + public boolean getRenderDeployablePreview() { + return this.renderDeployablePreview; + } + + public double getFuelQuality() { + return this.fuelQuality; + } + + public InteractionConfiguration getInteractionConfig() { + return this.interactionConfig; + } + + public int getItemSoundSetIndex() { + return this.itemSoundSetIndex; + } + + public void collectRecipesToGenerate(Collection recipes) { + if (this.recipeToGenerate != null) { + recipes.add(this.recipeToGenerate); + } + } + + public boolean hasRecipesToGenerate() { + return this.recipeToGenerate != null; + } + + public boolean dropsOnDeath() { + return this.dropOnDeath; + } + + protected void processConfig() { + if (this.hasBlockType) { + this.blockId = this.id; + } + + if (this.maxStack == -1) { + if (this.tool == null && this.weapon == null && this.armor == null && this.builderToolData == null && this.blockSelectorToolData == null) { + this.maxStack = 100; + } else { + this.maxStack = 1; + } + } + + Map interactions = this.interactions.isEmpty() ? new EnumMap<>(InteractionType.class) : new EnumMap<>(this.interactions); + DefaultAssetMap unarmedInteractionsAssetMap = UnarmedInteractions.getAssetMap(); + UnarmedInteractions fallbackInteractions = this.playerAnimationsId != null ? unarmedInteractionsAssetMap.getAsset(this.playerAnimationsId) : null; + if (fallbackInteractions != null) { + for (Entry entry : fallbackInteractions.getInteractions().entrySet()) { + interactions.putIfAbsent(entry.getKey(), entry.getValue()); + } + } + + UnarmedInteractions defaultUnarmedInteractions = unarmedInteractionsAssetMap.getAsset("Empty"); + if (defaultUnarmedInteractions != null) { + for (Entry entry : defaultUnarmedInteractions.getInteractions().entrySet()) { + interactions.putIfAbsent(entry.getKey(), entry.getValue()); + } + } + + this.interactions = Collections.unmodifiableMap(interactions); + if (this.reticleId != null) { + this.reticleIndex = ItemReticleConfig.getAssetMap().getIndexOrDefault(this.reticleId, 0); + } + + IndexedLookupTableAssetMap itemQualityAssetMap = ItemQuality.getAssetMap(); + if (this.qualityId != null) { + this.qualityIndex = itemQualityAssetMap.getIndexOrDefault(this.qualityId, 0); + ItemQuality itemQuality = itemQualityAssetMap.getAsset(this.qualityIndex); + if (this.itemEntityConfig == null && itemQuality != null) { + this.itemEntityConfig = itemQuality.getItemEntityConfig(); + } + } + + if (this.itemEntityConfig == null) { + if (this.blockId != null) { + this.itemEntityConfig = ItemEntityConfig.DEFAULT_BLOCK; + } else { + this.itemEntityConfig = ItemEntityConfig.DEFAULT; + } + } + + if (this.interactionConfig == null) { + if (this.weapon != null) { + this.interactionConfig = InteractionConfiguration.DEFAULT_WEAPON; + } else { + this.interactionConfig = InteractionConfiguration.DEFAULT; + } + } + + if (this.soundEventId != null) { + this.soundEventIndex = SoundEvent.getAssetMap().getIndex(this.soundEventId); + } + + this.itemSoundSetIndex = ItemSoundSet.getAssetMap().getIndex(this.itemSoundSetId); + if (this.stateToBlock != null) { + Map map = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : this.stateToBlock.entrySet()) { + map.put(entry.getValue(), entry.getKey()); + } + + this.blockToState = Collections.unmodifiableMap(map); + } + + if (this.recipeToGenerate != null) { + CraftingRecipe recipe = this.recipeToGenerate; + CraftingRecipe newRecipe = new CraftingRecipe(recipe); + MaterialQuantity primaryOutput = new MaterialQuantity(this.id, null, null, newRecipe.primaryOutputQuantity, null); + if (newRecipe.outputs == null || newRecipe.outputs.length == 0) { + newRecipe.outputs = new MaterialQuantity[]{primaryOutput}; + } + + newRecipe.primaryOutput = primaryOutput; + newRecipe.id = CraftingRecipe.generateIdFromItemRecipe(this, 0); + this.recipeToGenerate = newRecipe; + } + + this.displayEntityStatsHUD = EntityStatsModule.resolveEntityStats(this.rawDisplayEntityStatsHUD); + } + + static { + CODEC_BUILDER.appendInherited( + new KeyedCodec<>("State", new MapCodec(new ContainedAssetCodec<>(Item.class, CODEC, ContainedAssetCodec.Mode.INJECT_PARENT), HashMap::new)), + (item, m) -> item.stateToBlock = m, + item -> item.stateToBlock, + (item, parent) -> item.stateToBlock = parent.stateToBlock + ) + .metadata(new UIEditorSectionStart("State")) + .add(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemAppearanceCondition.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemAppearanceCondition.java new file mode 100644 index 0000000..f04c806 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemAppearanceCondition.java @@ -0,0 +1,209 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.range.FloatRange; +import com.hypixel.hytale.protocol.ValueType; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelParticle; +import com.hypixel.hytale.server.core.asset.type.modelvfx.config.ModelVFX; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.validator.SoundEventValidators; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class ItemAppearanceCondition implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(ItemAppearanceCondition.class, ItemAppearanceCondition::new) + .append( + new KeyedCodec<>("Particles", ModelParticle.ARRAY_CODEC), + (itemAppearanceCondition, modelParticles) -> itemAppearanceCondition.particles = modelParticles, + itemAppearanceCondition -> itemAppearanceCondition.particles + ) + .add() + .append( + new KeyedCodec<>("FirstPersonParticles", ModelParticle.ARRAY_CODEC), + (itemAppearanceCondition, modelParticles) -> itemAppearanceCondition.firstPersonParticles = modelParticles, + itemAppearanceCondition -> itemAppearanceCondition.firstPersonParticles + ) + .add() + .append( + new KeyedCodec<>("Model", Codec.STRING), + (itemAppearanceCondition, s) -> itemAppearanceCondition.model = s, + itemAppearanceCondition -> itemAppearanceCondition.model + ) + .addValidator(CommonAssetValidator.MODEL_CHARACTER) + .add() + .append( + new KeyedCodec<>("Texture", Codec.STRING), + (itemAppearanceCondition, s) -> itemAppearanceCondition.texture = s, + itemAppearanceCondition -> itemAppearanceCondition.texture + ) + .addValidator(CommonAssetValidator.TEXTURE_CHARACTER) + .add() + .append( + new KeyedCodec<>("ModelVFXId", Codec.STRING), + (itemAppearanceCondition, s) -> itemAppearanceCondition.modelVFXId = s, + itemAppearanceCondition -> itemAppearanceCondition.modelVFXId + ) + .addValidator(ModelVFX.VALIDATOR_CACHE.getValidator()) + .add() + .append( + new KeyedCodec<>("WorldSoundEventId", Codec.STRING), + (activationEffects, s) -> activationEffects.worldSoundEventId = s, + activationEffects -> activationEffects.worldSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .addValidator(SoundEventValidators.LOOPING) + .documentation("3D sound to play in the world when applying this condition.") + .add() + .append( + new KeyedCodec<>("LocalSoundEventId", Codec.STRING), + (activationEffects, s) -> activationEffects.localSoundEventId = s, + activationEffects -> activationEffects.localSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.LOOPING) + .documentation("Local sound to play for the owner of this condition.") + .add() + .append( + new KeyedCodec<>("Condition", FloatRange.CODEC), + (itemAppearanceCondition, intRange) -> itemAppearanceCondition.condition = intRange, + itemAppearanceCondition -> itemAppearanceCondition.condition + ) + .documentation("An array of 2 floats to define when the condition is active. 'Infinite' and '-Infinite' can be used to define bounds.") + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("ConditionValueType", new EnumCodec<>(ValueType.class)), + (itemAppearanceCondition, conditionValueType) -> itemAppearanceCondition.conditionValueType = conditionValueType, + itemAppearanceCondition -> itemAppearanceCondition.conditionValueType + ) + .documentation( + "Enum to specify if the condition range must be considered as absolute values or percent. Default value is Absolute. When using ValueType.Absolute, '100' matches the max value." + ) + .addValidator(Validators.nonNull()) + .add() + .afterDecode(condition -> { + if (condition.worldSoundEventId != null) { + condition.worldSoundEventIndex = SoundEvent.getAssetMap().getIndex(condition.worldSoundEventId); + } + + if (condition.localSoundEventId != null) { + condition.localSoundEventIndex = SoundEvent.getAssetMap().getIndex(condition.localSoundEventId); + } + }) + .build(); + protected ModelParticle[] particles; + protected ModelParticle[] firstPersonParticles; + protected String worldSoundEventId; + protected transient int worldSoundEventIndex = 0; + protected String localSoundEventId; + protected transient int localSoundEventIndex = 0; + protected String model; + protected String texture; + protected FloatRange condition; + @Nonnull + protected ValueType conditionValueType = ValueType.Absolute; + protected String modelVFXId; + + public ItemAppearanceCondition() { + } + + public ModelParticle[] getParticles() { + return this.particles; + } + + public String getModel() { + return this.model; + } + + public String getTexture() { + return this.texture; + } + + public FloatRange getCondition() { + return this.condition; + } + + public ValueType getConditionValueType() { + return this.conditionValueType; + } + + public String getModelVFXId() { + return this.modelVFXId; + } + + public String getWorldSoundEventId() { + return this.worldSoundEventId; + } + + public int getWorldSoundEventIndex() { + return this.worldSoundEventIndex; + } + + public String getLocalSoundEventId() { + return this.localSoundEventId; + } + + public int getLocalSoundEventIndex() { + return this.localSoundEventIndex; + } + + @Nonnull + public com.hypixel.hytale.protocol.ItemAppearanceCondition toPacket() { + com.hypixel.hytale.protocol.ItemAppearanceCondition packet = new com.hypixel.hytale.protocol.ItemAppearanceCondition(); + if (this.particles != null && this.particles.length > 0) { + packet.particles = new com.hypixel.hytale.protocol.ModelParticle[this.particles.length]; + + for (int i = 0; i < this.particles.length; i++) { + packet.particles[i] = this.particles[i].toPacket(); + } + } + + if (this.firstPersonParticles != null && this.firstPersonParticles.length > 0) { + packet.firstPersonParticles = new com.hypixel.hytale.protocol.ModelParticle[this.firstPersonParticles.length]; + + for (int i = 0; i < this.firstPersonParticles.length; i++) { + packet.firstPersonParticles[i] = this.firstPersonParticles[i].toPacket(); + } + } + + packet.model = this.model; + packet.texture = this.texture; + packet.condition = new com.hypixel.hytale.protocol.FloatRange(this.condition.getInclusiveMin(), this.condition.getInclusiveMax()); + packet.conditionValueType = this.conditionValueType; + packet.modelVFXId = this.modelVFXId; + packet.localSoundEventId = this.localSoundEventIndex != 0 ? this.localSoundEventIndex : this.worldSoundEventIndex; + packet.worldSoundEventId = this.worldSoundEventIndex; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "ItemAppearanceCondition{particles=" + + Arrays.toString((Object[])this.particles) + + ", firstPersonParticles=" + + Arrays.toString((Object[])this.firstPersonParticles) + + ", worldSoundEventId='" + + this.worldSoundEventId + + "', localSoundEventId='" + + this.localSoundEventId + + "', model='" + + this.model + + "', texture='" + + this.texture + + "', condition=" + + this.condition + + ", conditionValueType=" + + this.conditionValueType + + ", modelVFXId=" + + this.modelVFXId + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemArmor.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemArmor.java new file mode 100644 index 0000000..47152f1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemArmor.java @@ -0,0 +1,392 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.Cosmetic; +import com.hypixel.hytale.protocol.ItemArmorSlot; +import com.hypixel.hytale.protocol.Modifier; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.modules.entitystats.RegeneratingValue; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.StaticModifier; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.DamageClass; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemArmor implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(ItemArmor.class, ItemArmor::new) + .append( + new KeyedCodec<>("ArmorSlot", new EnumCodec<>(ItemArmorSlot.class)), + (itemArmor, itemArmorSlot) -> itemArmor.armorSlot = itemArmorSlot, + itemArmor -> itemArmor.armorSlot + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("DamageResistance", new MapCodec<>(new ArrayCodec<>(StaticModifier.CODEC, StaticModifier[]::new), HashMap::new)), + (itemArmor, map) -> itemArmor.damageResistanceValuesRaw = map, + itemArmor -> itemArmor.damageResistanceValuesRaw + ) + .addValidator(DamageCause.VALIDATOR_CACHE.getMapKeyValidator()) + .add() + .append( + new KeyedCodec<>("DamageEnhancement", new MapCodec<>(new ArrayCodec<>(StaticModifier.CODEC, StaticModifier[]::new), HashMap::new)), + (itemArmor, map) -> itemArmor.damageEnhancementValuesRaw = map, + itemArmor -> itemArmor.damageEnhancementValuesRaw + ) + .addValidator(DamageCause.VALIDATOR_CACHE.getMapKeyValidator()) + .add() + .>appendInherited( + new KeyedCodec<>("DamageClassEnhancement", new EnumMapCodec<>(DamageClass.class, new ArrayCodec<>(StaticModifier.CODEC, StaticModifier[]::new))), + (o, v) -> o.damageClassEnhancement = v, + o -> o.damageClassEnhancement, + (o, p) -> o.damageClassEnhancement = p.damageClassEnhancement + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("KnockbackResistances", new MapCodec<>(Codec.FLOAT, HashMap::new)), + (itemArmor, map) -> itemArmor.knockbackResistancesRaw = map, + itemArmor -> itemArmor.knockbackResistancesRaw + ) + .addValidator(DamageCause.VALIDATOR_CACHE.getMapKeyValidator()) + .add() + .append( + new KeyedCodec<>("KnockbackEnhancements", new MapCodec<>(Codec.FLOAT, HashMap::new)), + (itemArmor, map) -> itemArmor.knockbackEnhancementsRaw = map, + itemArmor -> itemArmor.knockbackEnhancementsRaw + ) + .addValidator(DamageCause.VALIDATOR_CACHE.getMapKeyValidator()) + .add() + .append( + new KeyedCodec<>("Regenerating", new MapCodec<>(new ArrayCodec<>(EntityStatType.Regenerating.CODEC, EntityStatType.Regenerating[]::new), HashMap::new)), + (itemArmor, map) -> itemArmor.regenerating = map, + itemArmor -> itemArmor.regenerating + ) + .addValidator(EntityStatType.VALIDATOR_CACHE.getMapKeyValidator()) + .add() + .append( + new KeyedCodec<>("BaseDamageResistance", Codec.DOUBLE), + (itemArmor, d) -> itemArmor.baseDamageResistance = d, + itemArmor -> itemArmor.baseDamageResistance + ) + .add() + .append( + new KeyedCodec<>("StatModifiers", new MapCodec<>(new ArrayCodec<>(StaticModifier.CODEC, StaticModifier[]::new), HashMap::new)), + (itemArmor, map) -> itemArmor.rawStatModifiers = map, + itemArmor -> itemArmor.rawStatModifiers + ) + .add() + .append( + new KeyedCodec<>("InteractionModifiers", new MapCodec<>(new MapCodec<>(StaticModifier.CODEC, HashMap::new), HashMap::new)), + (itemArmor, map) -> itemArmor.interactionModifiersRaw = map, + itemArmor -> itemArmor.interactionModifiersRaw + ) + .add() + .append( + new KeyedCodec<>("CosmeticsToHide", new ArrayCodec<>(new EnumCodec<>(Cosmetic.class), Cosmetic[]::new)), + (item, s) -> item.cosmeticsToHide = s, + item -> item.cosmeticsToHide + ) + .add() + .afterDecode(item -> processConfig(item)) + .build(); + @Nonnull + protected ItemArmorSlot armorSlot = ItemArmorSlot.Head; + @Nullable + protected Map damageResistanceValuesRaw; + @Nullable + protected Map damageResistanceValues; + @Nullable + protected Map damageEnhancementValuesRaw; + @Nullable + protected Map damageEnhancementValues; + protected double baseDamageResistance; + @Nullable + protected Map rawStatModifiers; + @Nullable + protected Int2ObjectMap statModifiers; + protected Cosmetic[] cosmeticsToHide; + @Nullable + protected Map regenerating; + @Nullable + protected Int2ObjectMap> regeneratingValues; + @Nullable + protected Map knockbackResistancesRaw; + @Nullable + protected Map knockbackResistances; + @Nullable + protected Map knockbackEnhancementsRaw; + @Nullable + protected Map knockbackEnhancements; + @Nullable + protected Map> interactionModifiersRaw; + @Nullable + protected Map> interactionModifiers; + @Nonnull + protected Map damageClassEnhancement = Collections.emptyMap(); + + public ItemArmor(ItemArmorSlot armorSlot, double baseDamageResistance, @Nullable Int2ObjectMap statModifiers, Cosmetic[] cosmeticsToHide) { + this.armorSlot = armorSlot; + this.baseDamageResistance = baseDamageResistance; + this.statModifiers = statModifiers; + this.cosmeticsToHide = cosmeticsToHide; + } + + protected ItemArmor() { + } + + @Nonnull + public com.hypixel.hytale.protocol.ItemArmor toPacket() { + com.hypixel.hytale.protocol.ItemArmor packet = new com.hypixel.hytale.protocol.ItemArmor(); + packet.armorSlot = this.armorSlot; + packet.cosmeticsToHide = this.cosmeticsToHide; + packet.statModifiers = EntityStatMap.toPacket(this.statModifiers); + packet.baseDamageResistance = this.baseDamageResistance; + if (this.damageResistanceValues != null && !this.damageResistanceValues.isEmpty()) { + Map damageResistanceMap = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : this.damageResistanceValues.entrySet()) { + if (entry.getKey() != null && entry.getValue() != null) { + Modifier[] modifiers = new Modifier[((StaticModifier[])entry.getValue()).length]; + + for (int i = 0; i < ((StaticModifier[])entry.getValue()).length; i++) { + modifiers[i] = entry.getValue()[i].toPacket(); + } + + damageResistanceMap.put(entry.getKey().getId(), modifiers); + } + } + + packet.damageResistance = damageResistanceMap.isEmpty() ? null : damageResistanceMap; + } + + if (this.damageClassEnhancement != null && !this.damageClassEnhancement.isEmpty()) { + Map damageClassEnhancementMap = new Object2ObjectOpenHashMap<>(); + + for (Entry entryx : this.damageClassEnhancement.entrySet()) { + if (entryx.getKey() != null && entryx.getValue() != null) { + Modifier[] modifiers = new Modifier[((StaticModifier[])entryx.getValue()).length]; + + for (int i = 0; i < ((StaticModifier[])entryx.getValue()).length; i++) { + modifiers[i] = entryx.getValue()[i].toPacket(); + } + + damageClassEnhancementMap.put(entryx.getKey().name().toLowerCase(), modifiers); + } + } + + packet.damageClassEnhancement = damageClassEnhancementMap.isEmpty() ? null : damageClassEnhancementMap; + } + + if (this.damageEnhancementValues != null && !this.damageEnhancementValues.isEmpty()) { + Map damageEnhancementMap = new Object2ObjectOpenHashMap<>(); + + for (Entry entryxx : this.damageEnhancementValues.entrySet()) { + if (entryxx.getKey() != null && entryxx.getValue() != null) { + Modifier[] modifiers = new Modifier[((StaticModifier[])entryxx.getValue()).length]; + + for (int i = 0; i < ((StaticModifier[])entryxx.getValue()).length; i++) { + modifiers[i] = entryxx.getValue()[i].toPacket(); + } + + damageEnhancementMap.put(entryxx.getKey().getId(), modifiers); + } + } + + packet.damageEnhancement = damageEnhancementMap.isEmpty() ? null : damageEnhancementMap; + } + + return packet; + } + + public ItemArmorSlot getArmorSlot() { + return this.armorSlot; + } + + public double getBaseDamageResistance() { + return this.baseDamageResistance; + } + + @Nullable + public Int2ObjectMap> getRegeneratingValues() { + return this.regeneratingValues; + } + + @Nullable + public Int2ObjectMap getStatModifiers() { + return this.statModifiers; + } + + @Nullable + public Map getDamageResistanceValues() { + return this.damageResistanceValues; + } + + @Nullable + public Map getDamageEnhancementValues() { + return this.damageEnhancementValues; + } + + @Nonnull + public Map getDamageClassEnhancement() { + return this.damageClassEnhancement; + } + + @Nullable + public Map getKnockbackEnhancements() { + return this.knockbackEnhancements; + } + + @Nullable + public Map getKnockbackResistances() { + return this.knockbackResistances; + } + + @Nullable + public Int2ObjectMap getInteractionModifier(String Key) { + return this.interactionModifiers == null ? null : this.interactionModifiers.get(Key); + } + + private static void processConfig(@Nonnull ItemArmor item) { + processStatModifiers(item); + processRegenModifiers(item); + processInteractionModifiers(item); + item.damageResistanceValues = convertStringKeyToDamageCause(item.damageResistanceValuesRaw); + item.damageEnhancementValues = convertStringKeyToDamageCause(item.damageEnhancementValuesRaw); + item.knockbackResistances = convertStringKeyToDamageCause(item.knockbackResistancesRaw); + item.knockbackEnhancements = convertStringKeyToDamageCause(item.knockbackEnhancementsRaw); + } + + private static void processStatModifiers(@Nonnull ItemArmor item) { + item.statModifiers = EntityStatsModule.resolveEntityStats(item.rawStatModifiers); + } + + private static void processRegenModifiers(@Nonnull ItemArmor item) { + if (item.regenerating != null) { + Int2ObjectMap> values = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : item.regenerating.entrySet()) { + int index = EntityStatType.getAssetMap().getIndex(entry.getKey()); + if (index != Integer.MIN_VALUE) { + EntityStatType.Regenerating[] entryValue = entry.getValue(); + List operatingEntry = values.computeIfAbsent(index, ArrayList::new); + + for (EntityStatType.Regenerating regen : entryValue) { + operatingEntry.add(new RegeneratingValue(regen)); + } + } + } + + item.regeneratingValues = values; + } + } + + private static void processInteractionModifiers(@Nonnull ItemArmor item) { + if (item.interactionModifiersRaw != null) { + Map> values = new Object2ObjectOpenHashMap<>(); + + for (Entry> entry : item.interactionModifiersRaw.entrySet()) { + String key = entry.getKey(); + + for (Entry stat : entry.getValue().entrySet()) { + int index = EntityStatType.getAssetMap().getIndex(stat.getKey()); + if (index != Integer.MIN_VALUE) { + StaticModifier statValue = stat.getValue(); + Int2ObjectMap statModMap = values.computeIfAbsent(key, k -> new Int2ObjectOpenHashMap<>()); + if (statModMap.get(index) == null) { + statModMap.put(index, new StaticModifier(statValue.getTarget(), statValue.getCalculationType(), statValue.getAmount())); + } else { + HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + LOGGER.at(Level.SEVERE) + .log( + "ItemArmor::processInteractionModifiers - Interaction Mod %s / %s has multiple entries on the same object %s", + key, + stat.getKey(), + item.armorSlot.name() + ); + } + } + } + } + + item.interactionModifiers = values; + } + } + + public static Map convertStringKeyToDamageCause(@Nullable Map rawData) { + Map values = new Object2ObjectOpenHashMap<>(); + if (rawData == null) { + return null; + } else { + for (Entry entry : rawData.entrySet()) { + DamageCause cause = DamageCause.getAssetMap().getAsset(entry.getKey()); + if (cause != null) { + values.put(cause, entry.getValue()); + } + } + + return values; + } + } + + @Nonnull + @Override + public String toString() { + return "ItemArmor{armorSlot=" + + this.armorSlot + + ", damageResistanceValues=" + + this.damageResistanceValues + + ", damageEnhancementValues=" + + this.damageEnhancementValues + + ", baseDamageResistance=" + + this.baseDamageResistance + + ", rawStatModifiers=" + + this.rawStatModifiers + + ", statModifiers=" + + this.statModifiers + + ", cosmeticsToHide=" + + Arrays.toString((Object[])this.cosmeticsToHide) + + ", regenerating=" + + this.regenerating + + ", regeneratingValues=" + + this.regeneratingValues + + ", knockbackResistances=" + + this.knockbackResistances + + ", knockbackEnhancements=" + + this.knockbackEnhancements + + ", interactionModifiersRaw=" + + this.interactionModifiersRaw + + ", interactionModifiers=" + + this.interactionModifiers + + "}"; + } + + public static enum InteractionModifierId { + Dodge; + + private InteractionModifierId() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemCategory.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemCategory.java new file mode 100644 index 0000000..ce7fc98 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemCategory.java @@ -0,0 +1,161 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.protocol.ItemGridInfoDisplayMode; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import java.util.Arrays; +import java.util.Comparator; +import javax.annotation.Nonnull; + +public class ItemCategory + implements JsonAssetWithMap>, + NetworkSerializable { + private static final AssetBuilderCodec.Builder CODEC_BUILDER = AssetBuilderCodec.builder( + ItemCategory.class, + ItemCategory::new, + Codec.STRING, + (itemCategory, k) -> itemCategory.id = k, + itemCategory -> itemCategory.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .addField(new KeyedCodec<>("Id", Codec.STRING), (itemCategory, s) -> itemCategory.id = s, itemCategory -> itemCategory.id) + .addField(new KeyedCodec<>("Name", Codec.STRING), (itemCategory, s) -> itemCategory.name = s, itemCategory -> itemCategory.name) + .append(new KeyedCodec<>("Icon", Codec.STRING), (itemCategory, s) -> itemCategory.icon = s, itemCategory -> itemCategory.icon) + .addValidator(CommonAssetValidator.ICON_ITEM_CATEGORIES) + .add() + .append( + new KeyedCodec<>("InfoDisplayMode", new EnumCodec<>(ItemGridInfoDisplayMode.class), false, true), + (itemCategory, s) -> itemCategory.infoDisplayMode = s, + itemCategory -> itemCategory.infoDisplayMode + ) + .addValidator(Validators.nonNull()) + .add() + .addField(new KeyedCodec<>("Order", Codec.INTEGER), (itemCategory, s) -> itemCategory.order = s, itemCategory -> itemCategory.order) + .afterDecode(itemCategory -> { + if (itemCategory.children != null) { + Arrays.sort(itemCategory.children, Comparator.comparingInt(value -> value.order)); + } + }); + public static final AssetBuilderCodec CODEC = CODEC_BUILDER.build(); + private static AssetStore> ASSET_STORE; + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ItemCategory::getAssetStore)); + protected AssetExtraInfo.Data data; + protected String id; + protected String name; + protected String icon; + protected int order; + @Nonnull + protected ItemGridInfoDisplayMode infoDisplayMode = ItemGridInfoDisplayMode.Tooltip; + protected ItemCategory[] children; + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ItemCategory.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public ItemCategory(String id, String name, String icon, ItemGridInfoDisplayMode infoDisplayMode, ItemCategory[] children) { + this.id = id; + this.name = name; + this.icon = icon; + this.infoDisplayMode = infoDisplayMode; + this.children = children; + } + + protected ItemCategory() { + } + + @Nonnull + public com.hypixel.hytale.protocol.ItemCategory toPacket() { + com.hypixel.hytale.protocol.ItemCategory cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.ItemCategory packet = new com.hypixel.hytale.protocol.ItemCategory(); + packet.id = this.id; + packet.name = this.name; + packet.icon = this.icon; + packet.order = this.order; + packet.infoDisplayMode = this.infoDisplayMode; + if (this.children != null && this.children.length > 0) { + packet.children = ArrayUtil.copyAndMutate(this.children, ItemCategory::toPacket, com.hypixel.hytale.protocol.ItemCategory[]::new); + } + + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + public String getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public String getIcon() { + return this.icon; + } + + public int getOrder() { + return this.order; + } + + public ItemGridInfoDisplayMode getInfoDisplayMode() { + return this.infoDisplayMode; + } + + public ItemCategory[] getChildren() { + return this.children; + } + + @Nonnull + @Override + public String toString() { + return "ItemCategory{id='" + + this.id + + "', name='" + + this.name + + "', icon='" + + this.icon + + "', order=" + + this.order + + ", infoDisplayMode='" + + this.infoDisplayMode + + "', children=" + + Arrays.toString((Object[])this.children) + + "}"; + } + + static { + CODEC_BUILDER.addField( + new KeyedCodec<>("Children", new ArrayCodec<>(CODEC, ItemCategory[]::new)), + (itemCategory, l) -> itemCategory.children = l, + itemCategory -> itemCategory.children + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemDrop.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemDrop.java new file mode 100644 index 0000000..65777a6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemDrop.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.codec.validation.validator.RangeRefValidator; +import java.util.Random; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public class ItemDrop { + public static final BuilderCodec CODEC = BuilderCodec.builder(ItemDrop.class, ItemDrop::new) + .append(new KeyedCodec<>("ItemId", Codec.STRING), (itemDrop, s) -> itemDrop.itemId = s, itemDrop -> itemDrop.itemId) + .addValidatorLate(() -> Item.VALIDATOR_CACHE.getValidator().late()) + .add() + .addField(new KeyedCodec<>("Metadata", Codec.BSON_DOCUMENT), (itemDrop, document) -> itemDrop.metadata = document, itemDrop -> itemDrop.metadata) + .append(new KeyedCodec<>("QuantityMin", Codec.INTEGER), (itemDrop, i) -> itemDrop.quantityMin = i, itemDrop -> itemDrop.quantityMin) + .addValidator(new RangeRefValidator<>(null, "1/QuantityMax", true)) + .add() + .append(new KeyedCodec<>("QuantityMax", Codec.INTEGER), (itemDrop, i) -> itemDrop.quantityMax = i, itemDrop -> itemDrop.quantityMax) + .addValidator(Validators.greaterThan(0)) + .add() + .build(); + protected String itemId; + protected BsonDocument metadata; + protected int quantityMin = 1; + protected int quantityMax = 1; + + public ItemDrop(String itemId, BsonDocument metadata, int quantityMin, int quantityMax) { + this.itemId = itemId; + this.metadata = metadata; + this.quantityMin = quantityMin; + this.quantityMax = quantityMax; + } + + protected ItemDrop() { + } + + public String getItemId() { + return this.itemId; + } + + @Nullable + public BsonDocument getMetadata() { + return this.metadata == null ? null : this.metadata.clone(); + } + + public int getQuantityMin() { + return this.quantityMin; + } + + public int getQuantityMax() { + return this.quantityMax; + } + + public int getRandomQuantity(@Nonnull Random random) { + return random.nextInt(Math.max(this.quantityMax - this.quantityMin + 1, 1)) + this.quantityMin; + } + + @Nonnull + @Override + public String toString() { + return "ItemDrop{itemId='" + + this.itemId + + "', metadata=" + + this.metadata + + ", quantityMin=" + + this.quantityMin + + ", quantityMax=" + + this.quantityMax + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemDropList.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemDropList.java new file mode 100644 index 0000000..69a52a2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemDropList.java @@ -0,0 +1,86 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.server.core.asset.type.item.config.container.ItemDropContainer; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class ItemDropList implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ItemDropList.class, + ItemDropList::new, + Codec.STRING, + (itemDropList, s) -> itemDropList.id = s, + itemDropList -> itemDropList.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("Container", ItemDropContainer.CODEC), + (itemDropList, o) -> itemDropList.container = o, + itemDropList -> itemDropList.container, + (itemDropList, parent) -> itemDropList.container = parent.container + ) + .add() + .validator((asset, results) -> { + ItemDropContainer container = asset.getContainer(); + if (container != null) { + List allDrops = container.getAllDrops(new ObjectArrayList<>()); + if (allDrops.isEmpty()) { + results.fail("Container must have something to drop!"); + } + } + }) + .build(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(ItemDropList.class, CODEC); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ItemDropList::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected ItemDropContainer container; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ItemDropList.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public ItemDropList(String id, ItemDropContainer container) { + this.id = id; + this.container = container; + } + + protected ItemDropList() { + } + + public String getId() { + return this.id; + } + + public ItemDropContainer getContainer() { + return this.container; + } + + @Nonnull + @Override + public String toString() { + return "ItemDropList{id='" + this.id + "', container=" + this.container + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemEntityConfig.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemEntityConfig.java new file mode 100644 index 0000000..443a97c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemEntityConfig.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.physics.component.PhysicsValues; +import javax.annotation.Nonnull; + +public class ItemEntityConfig implements NetworkSerializable { + public static final String DEFAULT_PARTICLE_SYSTEM_ID = "Item"; + public static final ItemEntityConfig DEFAULT = new ItemEntityConfig("Item", null, true); + public static final ItemEntityConfig DEFAULT_BLOCK = new ItemEntityConfig(null, null, true); + public static final BuilderCodec CODEC = BuilderCodec.builder(ItemEntityConfig.class, ItemEntityConfig::new) + .appendInherited( + new KeyedCodec<>("Physics", PhysicsValues.CODEC), + (itemEntityConfig, physicsValues) -> itemEntityConfig.physicsValues = physicsValues, + itemEntityConfig -> itemEntityConfig.physicsValues, + (o, p) -> o.physicsValues = p.physicsValues + ) + .add() + .appendInherited( + new KeyedCodec<>("PickupRadius", Codec.FLOAT), + (itemEntityConfig, box) -> itemEntityConfig.pickupRadius = box, + itemEntityConfig -> itemEntityConfig.pickupRadius, + (o, p) -> o.pickupRadius = p.pickupRadius + ) + .add() + .appendInherited( + new KeyedCodec<>("Lifetime", Codec.FLOAT), + (itemEntityConfig, v) -> itemEntityConfig.ttl = v, + itemEntityConfig -> itemEntityConfig.ttl, + (o, p) -> o.ttl = p.ttl + ) + .add() + .appendInherited( + new KeyedCodec<>("ParticleSystemId", Codec.STRING), + (o, i) -> o.particleSystemId = i, + o -> o.particleSystemId, + (o, p) -> o.particleSystemId = p.particleSystemId + ) + .addValidator(ParticleSystem.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("ParticleColor", ProtocolCodecs.COLOR), + (o, i) -> o.particleColor = i, + o -> o.particleColor, + (o, p) -> o.particleColor = p.particleColor + ) + .add() + .appendInherited( + new KeyedCodec<>("ShowItemParticles", Codec.BOOLEAN), + (o, i) -> o.showItemParticles = i, + o -> o.showItemParticles, + (o, p) -> o.showItemParticles = p.showItemParticles + ) + .add() + .build(); + protected PhysicsValues physicsValues = new PhysicsValues(5.0, 0.5, false); + protected float pickupRadius = 1.75F; + protected Float ttl; + protected String particleSystemId = "Item"; + protected Color particleColor; + protected boolean showItemParticles = true; + + public ItemEntityConfig() { + } + + public ItemEntityConfig(String particleSystemId, Color particleColor, boolean showItemParticles) { + this.particleSystemId = particleSystemId; + this.particleColor = particleColor; + this.showItemParticles = showItemParticles; + } + + public PhysicsValues getPhysicsValues() { + return this.physicsValues; + } + + public float getPickupRadius() { + return this.pickupRadius; + } + + public Float getTtl() { + return this.ttl; + } + + @Nonnull + public com.hypixel.hytale.protocol.ItemEntityConfig toPacket() { + com.hypixel.hytale.protocol.ItemEntityConfig packet = new com.hypixel.hytale.protocol.ItemEntityConfig(); + packet.particleSystemId = this.particleSystemId; + packet.particleColor = this.particleColor; + packet.showItemParticles = this.showItemParticles; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "ItemEntityConfig{physicsValues=" + this.physicsValues + ", pickupRadius=" + this.pickupRadius + ", ttl=" + this.ttl + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemGlider.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemGlider.java new file mode 100644 index 0000000..9ee1214 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemGlider.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.io.NetworkSerializable; + +public class ItemGlider implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(ItemGlider.class, ItemGlider::new) + .appendInherited( + new KeyedCodec<>("TerminalVelocity", Codec.FLOAT), + (o, i) -> o.terminalVelocity = i, + o -> o.terminalVelocity, + (o, p) -> o.terminalVelocity = p.terminalVelocity + ) + .documentation("The maximum speed the player can fall while gliding.") + .add() + .appendInherited( + new KeyedCodec<>("FallSpeedMultiplier", Codec.FLOAT), + (o, i) -> o.fallSpeedMultiplier = i, + o -> o.fallSpeedMultiplier, + (o, p) -> o.fallSpeedMultiplier = p.fallSpeedMultiplier + ) + .documentation("The rate at which the fall speed is incremented.") + .add() + .appendInherited( + new KeyedCodec<>("HorizontalSpeedMultiplier", Codec.FLOAT), + (o, i) -> o.horizontalSpeedMultiplier = i, + o -> o.horizontalSpeedMultiplier, + (o, p) -> o.horizontalSpeedMultiplier = p.horizontalSpeedMultiplier + ) + .documentation("The rate at which the horizontal move speed is incremented.") + .add() + .appendInherited(new KeyedCodec<>("Speed", Codec.FLOAT), (o, i) -> o.speed = i, o -> o.speed, (o, p) -> o.speed = p.speed) + .documentation("The horizontal movement speed of the glider.") + .add() + .build(); + protected float terminalVelocity; + protected float fallSpeedMultiplier; + protected float horizontalSpeedMultiplier; + protected float speed; + + public ItemGlider() { + } + + public float getTerminalVelocity() { + return this.terminalVelocity; + } + + public float getFallSpeedMultiplier() { + return this.fallSpeedMultiplier; + } + + public float getHorizontalSpeedMultiplier() { + return this.horizontalSpeedMultiplier; + } + + public float getSpeed() { + return this.speed; + } + + public com.hypixel.hytale.protocol.ItemGlider toPacket() { + com.hypixel.hytale.protocol.ItemGlider packet = new com.hypixel.hytale.protocol.ItemGlider(); + packet.terminalVelocity = this.terminalVelocity; + packet.fallSpeedMultiplier = this.fallSpeedMultiplier; + packet.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + packet.speed = this.speed; + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemPullbackConfig.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemPullbackConfig.java new file mode 100644 index 0000000..32d55c3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemPullbackConfig.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.ItemPullbackConfiguration; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemPullbackConfig implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(ItemPullbackConfig.class, ItemPullbackConfig::new) + .append( + new KeyedCodec<>("LeftOffsetOverride", Vector3d.AS_ARRAY_CODEC), + (pullbackConfig, offOverride) -> pullbackConfig.leftOffsetOverride = offOverride == null + ? null + : new Vector3f((float)offOverride.getX(), (float)offOverride.getY(), (float)offOverride.getZ()), + pullbackConfig -> pullbackConfig.leftOffsetOverride == null + ? null + : new Vector3d(pullbackConfig.leftOffsetOverride.x, pullbackConfig.leftOffsetOverride.y, pullbackConfig.leftOffsetOverride.z) + ) + .add() + .append( + new KeyedCodec<>("LeftRotationOverride", Vector3d.AS_ARRAY_CODEC), + (pullbackConfig, rotOverride) -> pullbackConfig.leftRotationOverride = rotOverride == null + ? null + : new Vector3f((float)rotOverride.getX(), (float)rotOverride.getY(), (float)rotOverride.getZ()), + pullbackConfig -> pullbackConfig.leftRotationOverride == null + ? null + : new Vector3d(pullbackConfig.leftRotationOverride.x, pullbackConfig.leftRotationOverride.y, pullbackConfig.leftRotationOverride.z) + ) + .add() + .append( + new KeyedCodec<>("RightOffsetOverride", Vector3d.AS_ARRAY_CODEC), + (pullbackConfig, offOverride) -> pullbackConfig.rightOffsetOverride = offOverride == null + ? null + : new Vector3f((float)offOverride.getX(), (float)offOverride.getY(), (float)offOverride.getZ()), + pullbackConfig -> pullbackConfig.rightOffsetOverride == null + ? null + : new Vector3d(pullbackConfig.rightOffsetOverride.x, pullbackConfig.rightOffsetOverride.y, pullbackConfig.rightOffsetOverride.z) + ) + .add() + .append( + new KeyedCodec<>("RightRotationOverride", Vector3d.AS_ARRAY_CODEC), + (pullbackConfig, rotOverride) -> pullbackConfig.rightRotationOverride = rotOverride == null + ? null + : new Vector3f((float)rotOverride.getX(), (float)rotOverride.getY(), (float)rotOverride.getZ()), + pullbackConfig -> pullbackConfig.rightRotationOverride == null + ? null + : new Vector3d(pullbackConfig.rightRotationOverride.x, pullbackConfig.rightRotationOverride.y, pullbackConfig.rightRotationOverride.z) + ) + .add() + .build(); + @Nullable + protected Vector3f leftOffsetOverride; + @Nullable + protected Vector3f leftRotationOverride; + @Nullable + protected Vector3f rightOffsetOverride; + @Nullable + protected Vector3f rightRotationOverride; + + ItemPullbackConfig() { + } + + public ItemPullbackConfig(Vector3f leftOffsetOverride, Vector3f leftRotationOverride, Vector3f rightOffsetOverride, Vector3f rightRotationOverride) { + this.leftOffsetOverride = leftOffsetOverride; + this.leftRotationOverride = leftRotationOverride; + this.rightOffsetOverride = rightOffsetOverride; + this.rightRotationOverride = rightRotationOverride; + } + + @Nonnull + public ItemPullbackConfiguration toPacket() { + ItemPullbackConfiguration packet = new ItemPullbackConfiguration(); + packet.leftOffsetOverride = this.leftOffsetOverride; + packet.leftRotationOverride = this.leftRotationOverride; + packet.rightOffsetOverride = this.rightOffsetOverride; + packet.rightRotationOverride = this.rightRotationOverride; + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemQuality.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemQuality.java new file mode 100644 index 0000000..01368bd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemQuality.java @@ -0,0 +1,322 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.asset.util.ColorParseUtil; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import javax.annotation.Nonnull; + +public class ItemQuality + implements JsonAssetWithMap>, + NetworkSerializable { + @Nonnull + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ItemQuality.class, + ItemQuality::new, + Codec.STRING, + (itemQuality, s) -> itemQuality.id = s, + ItemQuality::getId, + (itemQuality, data) -> itemQuality.data = data, + itemQuality -> itemQuality.data + ) + .append( + new KeyedCodec<>("QualityValue", Codec.INTEGER), (itemQuality, integer) -> itemQuality.qualityValue = integer, itemQuality -> itemQuality.qualityValue + ) + .documentation("Define the value of the quality to order them, 0 being the lowest quality.") + .add() + .append( + new KeyedCodec<>("ItemTooltipTexture", Codec.STRING), + (itemQuality, s) -> itemQuality.itemTooltipTexture = s, + itemQuality -> itemQuality.itemTooltipTexture + ) + .documentation("The path to the texture of the item tooltip. It has to be located in Common/Items/Qualities.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyString()) + .addValidator(CommonAssetValidator.TEXTURE_ITEM_QUALITY) + .add() + .append( + new KeyedCodec<>("ItemTooltipArrowTexture", Codec.STRING), + (itemQuality, s) -> itemQuality.itemTooltipArrowTexture = s, + itemQuality -> itemQuality.itemTooltipArrowTexture + ) + .documentation("The path to the texture of the item tooltip arrow. It has to be located in Common/Items/Qualities.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyString()) + .addValidator(CommonAssetValidator.TEXTURE_ITEM_QUALITY) + .add() + .append(new KeyedCodec<>("SlotTexture", Codec.STRING), (itemQuality, s) -> itemQuality.slotTexture = s, itemQuality -> itemQuality.slotTexture) + .documentation("The path to the texture of the item slot. It has to be located in Common/Items/Qualities.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyString()) + .addValidator(CommonAssetValidator.TEXTURE_ITEM_QUALITY) + .add() + .append( + new KeyedCodec<>("BlockSlotTexture", Codec.STRING), (itemQuality, s) -> itemQuality.blockSlotTexture = s, itemQuality -> itemQuality.blockSlotTexture + ) + .documentation("The path to the texture of the item slot, if it has an associated block type. It has to be located in Common/Items/Qualities.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyString()) + .addValidator(CommonAssetValidator.TEXTURE_ITEM_QUALITY) + .add() + .append( + new KeyedCodec<>("SpecialSlotTexture", Codec.STRING), + (itemQuality, s) -> itemQuality.specialSlotTexture = s, + itemQuality -> itemQuality.specialSlotTexture + ) + .documentation( + "The path to the texture of the item slot used when RenderSpecialSlot is true and the item is consumable or usable. It has to be located in Common/Items/Qualities." + ) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyString()) + .addValidator(CommonAssetValidator.TEXTURE_ITEM_QUALITY) + .add() + .append(new KeyedCodec<>("TextColor", ProtocolCodecs.COLOR), (itemQuality, s) -> itemQuality.textColor = s, itemQuality -> itemQuality.textColor) + .documentation("The color that'll be used to display the text of the item in the inventory.") + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("LocalizationKey", Codec.STRING), (itemQuality, s) -> itemQuality.localizationKey = s, itemQuality -> itemQuality.localizationKey + ) + .documentation("The localization key for the item quality name.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyString()) + .metadata(new UIEditor(new UIEditor.LocalizationKeyField("qualities.{assetId}", true))) + .add() + .append( + new KeyedCodec<>("VisibleQualityLabel", Codec.BOOLEAN), + (itemQuality, aBoolean) -> itemQuality.visibleQualityLabel = aBoolean, + itemQuality -> itemQuality.visibleQualityLabel + ) + .documentation("To specify the quality label should be displayed in the tooltip.") + .add() + .append( + new KeyedCodec<>("RenderSpecialSlot", Codec.BOOLEAN), + (itemQuality, aBoolean) -> itemQuality.renderSpecialSlot = aBoolean, + itemQuality -> itemQuality.renderSpecialSlot + ) + .documentation("To specify if we display a special slot texture if the item is a consumable or usable.") + .add() + .append( + new KeyedCodec<>("ItemEntityConfig", ItemEntityConfig.CODEC), + (itemQuality, itemEntityConfig) -> itemQuality.itemEntityConfig = itemEntityConfig, + itemQuality -> itemQuality.itemEntityConfig + ) + .documentation( + "Provides an ItemEntityConfig used for all items with their item quality set to this asset unless overridden by an ItemEntityConfig defined on the item itself." + ) + .add() + .append( + new KeyedCodec<>("HideFromSearch", Codec.BOOLEAN), + (itemQuality, aBoolean) -> itemQuality.hideFromSearch = aBoolean, + itemQuality -> itemQuality.hideFromSearch + ) + .documentation("Whether this item is hidden from typical public search, like the creative library") + .add() + .build(); + public static final int DEFAULT_INDEX = 0; + public static final String DEFAULT_ID = "Default"; + @Nonnull + public static final ItemQuality DEFAULT_ITEM_QUALITY = new ItemQuality("Default") { + { + this.qualityValue = -1; + this.itemTooltipTexture = "UI/ItemQualities/Tooltips/ItemTooltipDefault.png"; + this.itemTooltipArrowTexture = "UI/ItemQualities/Tooltips/ItemTooltipDefaultArrow.png"; + this.slotTexture = "UI/ItemQualities/Slots/SlotDefault.png"; + this.blockSlotTexture = "UI/ItemQualities/Slots/SlotDefault.png"; + this.specialSlotTexture = "UI/ItemQualities/Slots/SpecialSlotDefault.png"; + this.textColor = ColorParseUtil.hexStringToColor("#c9d2dd"); + this.localizationKey = "server.general.qualities.Default"; + this.hideFromSearch = false; + } + }; + @Nonnull + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ItemQuality::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected int qualityValue; + protected String itemTooltipTexture; + protected String itemTooltipArrowTexture; + protected String slotTexture; + protected String blockSlotTexture; + protected String specialSlotTexture; + protected Color textColor; + protected String localizationKey; + protected boolean visibleQualityLabel; + protected boolean renderSpecialSlot; + protected ItemEntityConfig itemEntityConfig; + protected boolean hideFromSearch = false; + private transient SoftReference cachedPacket; + + @Nonnull + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ItemQuality.class); + } + + return ASSET_STORE; + } + + @Nonnull + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public ItemQuality( + String id, + int qualityValue, + String itemTooltipTexture, + String itemTooltipArrowTexture, + String slotTexture, + String blockSlotTexture, + String specialSlotTexture, + Color textColor, + String localizationKey, + boolean visibleQualityLabel, + boolean renderSpecialSlot, + boolean hideFromSearch, + ItemEntityConfig itemEntityConfig + ) { + this.id = id; + this.qualityValue = qualityValue; + this.itemTooltipTexture = itemTooltipTexture; + this.itemTooltipArrowTexture = itemTooltipArrowTexture; + this.slotTexture = slotTexture; + this.blockSlotTexture = blockSlotTexture; + this.specialSlotTexture = specialSlotTexture; + this.textColor = textColor; + this.localizationKey = localizationKey; + this.visibleQualityLabel = visibleQualityLabel; + this.renderSpecialSlot = renderSpecialSlot; + this.hideFromSearch = hideFromSearch; + this.itemEntityConfig = itemEntityConfig; + } + + public ItemQuality(@Nonnull String id) { + this.id = id; + } + + protected ItemQuality() { + } + + public String getId() { + return this.id; + } + + public int getQualityValue() { + return this.qualityValue; + } + + public String getItemTooltipTexture() { + return this.itemTooltipTexture; + } + + public String getItemTooltipArrowTexture() { + return this.itemTooltipArrowTexture; + } + + public String getSlotTexture() { + return this.slotTexture; + } + + public String getBlockSlotTexture() { + return this.blockSlotTexture; + } + + public String getSpecialSlotTexture() { + return this.specialSlotTexture; + } + + public Color getTextColor() { + return this.textColor; + } + + public String getLocalizationKey() { + return this.localizationKey; + } + + public boolean isVisibleQualityLabel() { + return this.visibleQualityLabel; + } + + public boolean isRenderSpecialSlot() { + return this.renderSpecialSlot; + } + + public boolean isHiddenFromSearch() { + return this.hideFromSearch; + } + + public ItemEntityConfig getItemEntityConfig() { + return this.itemEntityConfig; + } + + @Nonnull + public com.hypixel.hytale.protocol.ItemQuality toPacket() { + com.hypixel.hytale.protocol.ItemQuality cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.ItemQuality packet = new com.hypixel.hytale.protocol.ItemQuality(); + packet.id = this.id; + packet.itemTooltipTexture = this.itemTooltipTexture; + packet.itemTooltipArrowTexture = this.itemTooltipArrowTexture; + packet.slotTexture = this.slotTexture; + packet.blockSlotTexture = this.blockSlotTexture; + packet.specialSlotTexture = this.specialSlotTexture; + packet.textColor = this.textColor; + packet.localizationKey = this.localizationKey; + packet.visibleQualityLabel = this.visibleQualityLabel; + packet.renderSpecialSlot = this.renderSpecialSlot; + packet.hideFromSearch = this.hideFromSearch; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + @Nonnull + @Override + public String toString() { + return "ItemQuality{id='" + + this.id + + "', qualityValue=" + + this.qualityValue + + ", itemTooltipTexture='" + + this.itemTooltipTexture + + "', itemTooltipArrowTexture='" + + this.itemTooltipArrowTexture + + "', slotTexture='" + + this.slotTexture + + "', blockSlotTexture='" + + this.blockSlotTexture + + "', specialSlotTexture='" + + this.specialSlotTexture + + "', textColor='" + + this.textColor + + "', localizationKey='" + + this.localizationKey + + "', visibleQualityLabel=" + + this.visibleQualityLabel + + ", renderSpecialSlot=" + + this.renderSpecialSlot + + ", itemEntityConfig=" + + this.itemEntityConfig + + ", hideFromSearch=" + + this.hideFromSearch + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemReticleConfig.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemReticleConfig.java new file mode 100644 index 0000000..97736fc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemReticleConfig.java @@ -0,0 +1,244 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.ItemReticleClientEvent; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; +import java.lang.ref.SoftReference; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class ItemReticleConfig + implements JsonAssetWithMap>, + NetworkSerializable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ItemReticleConfig.class, + ItemReticleConfig::new, + Codec.STRING, + (itemReticleConfig, s) -> itemReticleConfig.id = s, + itemReticleConfig -> itemReticleConfig.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("Base", new ArrayCodec<>(Codec.STRING, String[]::new)), + (itemReticleConfig, o) -> itemReticleConfig.base = o, + itemReticleConfig -> itemReticleConfig.base, + (itemReticleConfig, parent) -> itemReticleConfig.base = parent.base + ) + .documentation("Paths to the parts that should be displayed for the base reticle.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyArray()) + .addValidator(CommonAssetValidator.UI_RETICLE_PARTS_ARRAY) + .add() + .appendInherited( + new KeyedCodec<>("ServerEvents", new MapCodec<>(ItemReticleConfig.ItemReticleWithDuration.CODEC, HashMap::new)), + (itemReticleConfig, o) -> itemReticleConfig.serverEvents = o, + itemReticleConfig -> itemReticleConfig.serverEvents, + (itemReticleConfig, parent) -> itemReticleConfig.serverEvents = parent.serverEvents + ) + .documentation("A map of reticle configurations for server-side events.") + .add() + .>appendInherited( + new KeyedCodec<>("ClientEvents", new EnumMapCodec<>(ItemReticleClientEvent.class, ItemReticleConfig.ItemReticle.CODEC)), + (itemReticleConfig, o) -> itemReticleConfig.clientEvents = o, + itemReticleConfig -> itemReticleConfig.clientEvents, + (itemReticleConfig, parent) -> itemReticleConfig.clientEvents = parent.clientEvents + ) + .documentation("A map of reticle configurations for client-side events.") + .add() + .afterDecode(ItemReticleConfig::processConfig) + .build(); + public static final int DEFAULT_INDEX = 0; + public static final String DEFAULT_ID = "Default"; + public static final ItemReticleConfig DEFAULT = new ItemReticleConfig("Default") { + { + this.base = new String[]{"UI/Reticles/Default.png"}; + } + }; + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ItemReticleConfig::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected String[] base; + protected Map serverEvents; + protected Int2ObjectMap indexedServerEvents; + protected Map clientEvents; + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ItemReticleConfig.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + protected ItemReticleConfig() { + } + + public ItemReticleConfig(String id) { + this.id = id; + } + + protected void processConfig() { + if (this.serverEvents != null && !this.serverEvents.isEmpty()) { + this.indexedServerEvents = new Int2ObjectOpenHashMap<>(); + this.serverEvents.forEach((event, config) -> this.indexedServerEvents.put(AssetRegistry.getOrCreateTagIndex(event), config)); + } + } + + public String getId() { + return this.id; + } + + @Nonnull + public com.hypixel.hytale.protocol.ItemReticleConfig toPacket() { + com.hypixel.hytale.protocol.ItemReticleConfig cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.ItemReticleConfig packet = new com.hypixel.hytale.protocol.ItemReticleConfig(); + packet.base = this.base; + if (this.indexedServerEvents != null) { + packet.serverEvents = new Int2ObjectOpenHashMap<>(); + + for (Entry event : this.indexedServerEvents.int2ObjectEntrySet()) { + packet.serverEvents.put(event.getIntKey(), event.getValue().toPacket()); + } + } + + if (this.clientEvents != null) { + packet.clientEvents = new EnumMap<>(ItemReticleClientEvent.class); + + for (java.util.Map.Entry event : this.clientEvents.entrySet()) { + packet.clientEvents.put(event.getKey(), event.getValue().toPacket()); + } + } + + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + @Nonnull + @Override + public String toString() { + return "ItemReticleConfig{id='" + + this.id + + "', base=" + + Arrays.toString((Object[])this.base) + + ", serverEvents=" + + this.serverEvents + + ", indexedServerEvents=" + + this.indexedServerEvents + + ", clientEvents=" + + this.clientEvents + + "}"; + } + + static class ItemReticle implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ItemReticleConfig.ItemReticle.class, ItemReticleConfig.ItemReticle::new + ) + .append(new KeyedCodec<>("HideBase", Codec.BOOLEAN), (itemReticle, o) -> itemReticle.hideBase = o, itemReticle -> itemReticle.hideBase) + .documentation("Specifies whether the base reticle should be hidden while the configured parts are shown.") + .add() + .append( + new KeyedCodec<>("Parts", new ArrayCodec<>(Codec.STRING, String[]::new)), + (itemReticle, o) -> itemReticle.parts = o, + itemReticle -> itemReticle.parts + ) + .documentation("A list of reticle parts that should be displayed for this configuration.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyArray()) + .addValidator(CommonAssetValidator.UI_RETICLE_PARTS_ARRAY) + .add() + .build(); + protected boolean hideBase; + protected String[] parts; + + public ItemReticle(boolean hideBase, String[] parts) { + this.hideBase = hideBase; + this.parts = parts; + } + + public ItemReticle() { + } + + @Nonnull + public com.hypixel.hytale.protocol.ItemReticle toPacket() { + com.hypixel.hytale.protocol.ItemReticle packet = new com.hypixel.hytale.protocol.ItemReticle(); + packet.hideBase = this.hideBase; + packet.parts = this.parts; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "ItemReticle{, hideBase=" + this.hideBase + ", parts=" + Arrays.toString((Object[])this.parts) + "}"; + } + } + + static class ItemReticleWithDuration extends ItemReticleConfig.ItemReticle { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ItemReticleConfig.ItemReticleWithDuration.class, ItemReticleConfig.ItemReticleWithDuration::new, ItemReticleConfig.ItemReticle.CODEC + ) + .append(new KeyedCodec<>("Duration", Codec.FLOAT), (itemReticle, value) -> itemReticle.duration = value, itemReticle -> itemReticle.duration) + .documentation("The duration (in seconds) this reticle configuration should be displayed for.") + .addValidator(Validators.greaterThan(0.0F)) + .add() + .build(); + protected float duration = 0.25F; + + public ItemReticleWithDuration(boolean hideBase, String[] parts, float duration) { + this.hideBase = hideBase; + this.parts = parts; + this.duration = duration; + } + + public ItemReticleWithDuration() { + } + + @Nonnull + @Override + public com.hypixel.hytale.protocol.ItemReticle toPacket() { + com.hypixel.hytale.protocol.ItemReticle packet = new com.hypixel.hytale.protocol.ItemReticle(); + packet.hideBase = this.hideBase; + packet.parts = this.parts; + packet.duration = this.duration; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "ItemReticleWithDuration{, duration=" + this.duration + "}" + super.toString(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemStackContainerConfig.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemStackContainerConfig.java new file mode 100644 index 0000000..bbf7cf3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemStackContainerConfig.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterType; +import javax.annotation.Nonnull; + +public class ItemStackContainerConfig { + public static final ItemStackContainerConfig DEFAULT = new ItemStackContainerConfig(); + public static final BuilderCodec CODEC = BuilderCodec.builder(ItemStackContainerConfig.class, ItemStackContainerConfig::new) + .append(new KeyedCodec<>("Capacity", Codec.SHORT), (itemTool, s) -> itemTool.capacity = s, itemTool -> itemTool.capacity) + .add() + .append(new KeyedCodec<>("GlobalFilter", FilterType.CODEC), (itemTool, s) -> itemTool.globalFilter = s, itemTool -> itemTool.globalFilter) + .add() + .append(new KeyedCodec<>("ItemTag", Codec.STRING), (materialQuantity, s) -> materialQuantity.tag = s, materialQuantity -> materialQuantity.tag) + .add() + .afterDecode((config, extraInfo) -> { + if (config.tag != null) { + config.tagIndex = AssetRegistry.getOrCreateTagIndex(config.tag); + } + }) + .build(); + protected short capacity = 0; + protected FilterType globalFilter = FilterType.ALLOW_ALL; + protected String tag; + protected volatile int tagIndex = Integer.MIN_VALUE; + + public ItemStackContainerConfig() { + } + + public short getCapacity() { + return this.capacity; + } + + public FilterType getGlobalFilter() { + return this.globalFilter; + } + + public int getTagIndex() { + return this.tagIndex; + } + + @Nonnull + @Override + public String toString() { + return "ItemStackContainerConfig{capacity=" + this.capacity + ", globalFilter=" + this.globalFilter + ", tag='" + this.tag + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemTool.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemTool.java new file mode 100644 index 0000000..f4c6386 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemTool.java @@ -0,0 +1,216 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.validator.SoundEventValidators; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemTool implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(ItemTool.class, ItemTool::new) + .addField( + new KeyedCodec<>("Specs", new ArrayCodec<>(ItemToolSpec.CODEC, ItemToolSpec[]::new)), (itemTool, s) -> itemTool.specs = s, itemTool -> itemTool.specs + ) + .addField(new KeyedCodec<>("Speed", Codec.DOUBLE), (itemTool, d) -> itemTool.speed = d.floatValue(), itemTool -> (double)itemTool.speed) + .addField( + new KeyedCodec<>("DurabilityLossBlockTypes", new ArrayCodec<>(ItemTool.DurabilityLossBlockTypes.CODEC, ItemTool.DurabilityLossBlockTypes[]::new)), + (item, s) -> item.durabilityLossBlockTypes = s, + item -> item.durabilityLossBlockTypes + ) + .appendInherited( + new KeyedCodec<>("HitSoundLayer", Codec.STRING), + (item, s) -> item.hitSoundLayerId = s, + item -> item.hitSoundLayerId, + (item, parent) -> item.hitSoundLayerId = parent.hitSoundLayerId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .documentation("Sound to play in addition to the block breaking sound when hitting a block this tool is designed to break.") + .add() + .appendInherited( + new KeyedCodec<>("IncorrectMaterialSoundLayer", Codec.STRING), + (item, s) -> item.incorrectMaterialSoundLayerId = s, + item -> item.incorrectMaterialSoundLayerId, + (item, parent) -> item.incorrectMaterialSoundLayerId = parent.incorrectMaterialSoundLayerId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .documentation("Sound to play in addition to the block breaking sound when hitting a block this tool cannot break.") + .add() + .afterDecode(ItemTool::processConfig) + .build(); + protected ItemToolSpec[] specs; + protected float speed; + protected ItemTool.DurabilityLossBlockTypes[] durabilityLossBlockTypes; + @Nullable + protected String hitSoundLayerId = null; + protected transient int hitSoundLayerIndex = 0; + @Nullable + protected String incorrectMaterialSoundLayerId = null; + protected transient int incorrectMaterialSoundLayerIndex = 0; + + public ItemTool(ItemToolSpec[] specs, float speed, ItemTool.DurabilityLossBlockTypes[] durabilityLossBlockTypes) { + this.specs = specs; + this.speed = speed; + this.durabilityLossBlockTypes = durabilityLossBlockTypes; + } + + protected ItemTool() { + } + + protected void processConfig() { + if (this.hitSoundLayerId != null) { + this.hitSoundLayerIndex = SoundEvent.getAssetMap().getIndex(this.hitSoundLayerId); + } + + if (this.incorrectMaterialSoundLayerId != null) { + this.incorrectMaterialSoundLayerIndex = SoundEvent.getAssetMap().getIndex(this.incorrectMaterialSoundLayerId); + } + } + + @Nonnull + public com.hypixel.hytale.protocol.ItemTool toPacket() { + com.hypixel.hytale.protocol.ItemTool packet = new com.hypixel.hytale.protocol.ItemTool(); + if (this.specs != null && this.specs.length > 0) { + packet.specs = ArrayUtil.copyAndMutate(this.specs, ItemToolSpec::toPacket, com.hypixel.hytale.protocol.ItemToolSpec[]::new); + } + + packet.speed = this.speed; + return packet; + } + + public ItemToolSpec[] getSpecs() { + return this.specs; + } + + public float getSpeed() { + return this.speed; + } + + public ItemTool.DurabilityLossBlockTypes[] getDurabilityLossBlockTypes() { + return this.durabilityLossBlockTypes; + } + + public int getHitSoundLayerIndex() { + return this.hitSoundLayerIndex; + } + + public int getIncorrectMaterialSoundLayerIndex() { + return this.incorrectMaterialSoundLayerIndex; + } + + @Nonnull + @Override + public String toString() { + return "ItemTool{specs=" + + Arrays.toString((Object[])this.specs) + + ", speed=" + + this.speed + + ", durabilityLossBlockTypes=" + + Arrays.toString((Object[])this.durabilityLossBlockTypes) + + ", hitSoundLayerId='" + + this.hitSoundLayerId + + "', hitSoundLayerIndex=" + + this.hitSoundLayerIndex + + ", incorrectMaterialSoundLayerId='" + + this.incorrectMaterialSoundLayerId + + "', incorrectMaterialSoundLayerIndex=" + + this.incorrectMaterialSoundLayerIndex + + "}"; + } + + public static class DurabilityLossBlockTypes { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ItemTool.DurabilityLossBlockTypes.class, ItemTool.DurabilityLossBlockTypes::new + ) + .addField(new KeyedCodec<>("BlockTypes", new ArrayCodec<>(Codec.STRING, String[]::new)), (item, s) -> item.blockTypes = s, item -> item.blockTypes) + .addField(new KeyedCodec<>("BlockSets", Codec.STRING_ARRAY), (item, s) -> item.blockSets = s, item -> item.blockSets) + .addField(new KeyedCodec<>("DurabilityLossOnHit", Codec.DOUBLE), (item, s) -> item.durabilityLossOnHit = s, item -> item.durabilityLossOnHit) + .afterDecode(item -> { + if (item.blockSets != null) { + item.blockSetIndexes = new int[item.blockSets.length]; + + for (int i = 0; i < item.blockSets.length; i++) { + String blockSet = item.blockSets[i]; + int index = BlockSet.getAssetMap().getIndex(blockSet); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + blockSet); + } + + item.blockSetIndexes[i] = index; + } + } + }) + .build(); + protected String[] blockTypes; + protected String[] blockSets; + protected double durabilityLossOnHit; + protected int[] blockTypeIndexes; + protected int[] blockSetIndexes; + + protected DurabilityLossBlockTypes() { + } + + public DurabilityLossBlockTypes(String[] blockTypes, String[] blockSets, double durabilityLossOnHit) { + this.blockTypes = blockTypes; + this.blockSets = blockSets; + this.durabilityLossOnHit = durabilityLossOnHit; + } + + public String[] getBlockTypes() { + return this.blockTypes; + } + + public String[] getBlockSets() { + return this.blockSets; + } + + public double getDurabilityLossOnHit() { + return this.durabilityLossOnHit; + } + + public int[] getBlockTypeIndexes() { + if (this.blockTypes != null && this.blockTypeIndexes == null) { + int[] blockTypeIndexes = new int[this.blockTypes.length]; + + for (int i = 0; i < this.blockTypes.length; i++) { + String key = this.blockTypes[i]; + int index = BlockType.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + blockTypeIndexes[i] = index; + } + + this.blockTypeIndexes = blockTypeIndexes; + } + + return this.blockTypeIndexes; + } + + public int[] getBlockSetIndexes() { + return this.blockSetIndexes; + } + + @Nonnull + @Override + public String toString() { + return "DurabilityLossBlockTypes{blockTypes=" + + Arrays.toString((Object[])this.blockTypes) + + ", blockSets=" + + Arrays.toString((Object[])this.blockSets) + + ", durabilityLossOnHit=" + + this.durabilityLossOnHit + + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemToolSpec.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemToolSpec.java new file mode 100644 index 0000000..1036d88 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemToolSpec.java @@ -0,0 +1,144 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.AssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.validator.SoundEventValidators; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemToolSpec + implements JsonAssetWithMap>, + NetworkSerializable { + public static final AssetCodec CODEC = AssetBuilderCodec.builder( + ItemToolSpec.class, + ItemToolSpec::new, + Codec.STRING, + (itemToolSpec, s) -> itemToolSpec.gatherType = s, + itemToolSpec -> itemToolSpec.gatherType, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .addField(new KeyedCodec<>("GatherType", Codec.STRING), (itemToolSpec, s) -> itemToolSpec.gatherType = s, itemToolSpec -> itemToolSpec.gatherType) + .addField(new KeyedCodec<>("Power", Codec.DOUBLE), (itemToolSpec, d) -> itemToolSpec.power = d.floatValue(), itemToolSpec -> (double)itemToolSpec.power) + .addField(new KeyedCodec<>("Quality", Codec.INTEGER), (itemToolSpec, i) -> itemToolSpec.quality = i, itemToolSpec -> itemToolSpec.quality) + .addField(new KeyedCodec<>("IsIncorrect", Codec.BOOLEAN), (itemToolSpec, i) -> itemToolSpec.incorrect = i, itemToolSpec -> itemToolSpec.incorrect) + .appendInherited( + new KeyedCodec<>("HitSoundLayer", Codec.STRING), + (spec, s) -> spec.hitSoundLayerId = s, + spec -> spec.hitSoundLayerId, + (spec, parent) -> spec.hitSoundLayerId = parent.hitSoundLayerId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .documentation("Sound to play in addition to the block breaking sound when hitting this block type.") + .add() + .afterDecode(ItemToolSpec::processConfig) + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ItemToolSpec::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String gatherType; + protected float power; + protected int quality; + protected boolean incorrect; + @Nullable + protected String hitSoundLayerId = null; + protected transient int hitSoundLayerIndex = 0; + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ItemToolSpec.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public ItemToolSpec(String gatherType, float power, int quality) { + this.gatherType = gatherType; + this.power = power; + this.quality = quality; + } + + protected ItemToolSpec() { + } + + protected void processConfig() { + if (this.hitSoundLayerId != null) { + this.hitSoundLayerIndex = SoundEvent.getAssetMap().getIndex(this.hitSoundLayerId); + } + } + + @Nonnull + public com.hypixel.hytale.protocol.ItemToolSpec toPacket() { + com.hypixel.hytale.protocol.ItemToolSpec cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.ItemToolSpec packet = new com.hypixel.hytale.protocol.ItemToolSpec(); + packet.gatherType = this.gatherType; + packet.power = this.power; + packet.quality = this.quality; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + public String getId() { + return this.gatherType; + } + + public String getGatherType() { + return this.gatherType; + } + + public float getPower() { + return this.power; + } + + public int getQuality() { + return this.quality; + } + + public boolean isIncorrect() { + return this.incorrect; + } + + public int getHitSoundLayerIndex() { + return this.hitSoundLayerIndex; + } + + @Nonnull + @Override + public String toString() { + return "ItemToolSpec{gatherType='" + + this.gatherType + + "', power=" + + this.power + + ", quality=" + + this.quality + + ", incorrect=" + + this.incorrect + + ", hitSoundLayerId='" + + this.hitSoundLayerId + + "', hitSoundLayerIndex=" + + this.hitSoundLayerIndex + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemTranslationProperties.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemTranslationProperties.java new file mode 100644 index 0000000..81bddb0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemTranslationProperties.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemTranslationProperties implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(ItemTranslationProperties.class, ItemTranslationProperties::new) + .appendInherited(new KeyedCodec<>("Name", Codec.STRING), (data, s) -> data.name = s, data -> data.name, (o, p) -> o.name = p.name) + .documentation("The translation key for the name of this item.") + .metadata(new UIEditor(new UIEditor.LocalizationKeyField("server.items.{assetId}.name", true))) + .add() + .appendInherited( + new KeyedCodec<>("Description", Codec.STRING), (data, s) -> data.description = s, data -> data.description, (o, p) -> o.description = p.description + ) + .documentation("The translation key for the description of this item.") + .metadata(new UIEditor(new UIEditor.LocalizationKeyField("server.items.{assetId}.description"))) + .add() + .build(); + @Nullable + private String name; + @Nullable + private String description; + + ItemTranslationProperties() { + } + + public ItemTranslationProperties(@Nonnull String name, @Nonnull String description) { + this.name = name; + this.description = description; + } + + @Nullable + public String getName() { + return this.name; + } + + @Nullable + public String getDescription() { + return this.description; + } + + @Nonnull + public com.hypixel.hytale.protocol.ItemTranslationProperties toPacket() { + com.hypixel.hytale.protocol.ItemTranslationProperties packet = new com.hypixel.hytale.protocol.ItemTranslationProperties(); + packet.name = this.name; + packet.description = this.description; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "ItemTranslationProperties{name=" + this.name + ", description=" + this.description + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemUtility.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemUtility.java new file mode 100644 index 0000000..3b65999 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemUtility.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.StaticModifier; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemUtility implements NetworkSerializable { + public static final ItemUtility DEFAULT = new ItemUtility(); + public static final BuilderCodec CODEC = BuilderCodec.builder(ItemUtility.class, ItemUtility::new) + .append(new KeyedCodec<>("Usable", Codec.BOOLEAN), (o, v) -> o.usable = v, o -> o.usable) + .add() + .append(new KeyedCodec<>("Compatible", Codec.BOOLEAN), (o, v) -> o.compatible = v, o -> o.compatible) + .add() + .append( + new KeyedCodec<>("StatModifiers", new MapCodec<>(new ArrayCodec<>(StaticModifier.CODEC, StaticModifier[]::new), HashMap::new)), + (itemArmor, map) -> itemArmor.rawStatModifiers = map, + itemArmor -> itemArmor.rawStatModifiers + ) + .addValidator(EntityStatType.VALIDATOR_CACHE.getMapKeyValidator().late()) + .add() + .append( + new KeyedCodec<>("EntityStatsToClear", Codec.STRING_ARRAY), + (itemUtility, strings) -> itemUtility.rawEntityStatsToClear = strings, + itemUtility -> itemUtility.rawEntityStatsToClear + ) + .add() + .afterDecode(item -> { + item.statModifiers = EntityStatsModule.resolveEntityStats(item.rawStatModifiers); + item.entityStatsToClear = EntityStatsModule.resolveEntityStats(item.rawEntityStatsToClear); + }) + .build(); + protected boolean usable; + protected boolean compatible; + @Nullable + protected Map rawStatModifiers; + @Nullable + protected Int2ObjectMap statModifiers; + protected String[] rawEntityStatsToClear; + @Nullable + protected int[] entityStatsToClear; + + public ItemUtility() { + } + + public boolean isUsable() { + return this.usable; + } + + public boolean isCompatible() { + return this.compatible; + } + + @Nullable + public Int2ObjectMap getStatModifiers() { + return this.statModifiers; + } + + public int[] getEntityStatsToClear() { + return this.entityStatsToClear; + } + + @Nonnull + public com.hypixel.hytale.protocol.ItemUtility toPacket() { + return new com.hypixel.hytale.protocol.ItemUtility(this.usable, this.compatible, this.entityStatsToClear, EntityStatMap.toPacket(this.statModifiers)); + } + + @Nonnull + @Override + public String toString() { + return "ItemUtility{usable=" + + this.usable + + ", compatible=" + + this.compatible + + ", rawStatModifiers=" + + this.rawStatModifiers + + ", statModifiers=" + + this.statModifiers + + ", rawEntityStatsToClear=" + + Arrays.toString((Object[])this.rawEntityStatsToClear) + + ", entityStatsToClear=" + + Arrays.toString(this.entityStatsToClear) + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemWeapon.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemWeapon.java new file mode 100644 index 0000000..aaf6114 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/ItemWeapon.java @@ -0,0 +1,87 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.StaticModifier; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemWeapon implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(ItemWeapon.class, ItemWeapon::new) + .append( + new KeyedCodec<>("StatModifiers", new MapCodec<>(new ArrayCodec<>(StaticModifier.CODEC, StaticModifier[]::new), HashMap::new)), + (itemArmor, map) -> itemArmor.rawStatModifiers = map, + itemArmor -> itemArmor.rawStatModifiers + ) + .addValidator(EntityStatType.VALIDATOR_CACHE.getMapKeyValidator().late()) + .add() + .append( + new KeyedCodec<>("EntityStatsToClear", Codec.STRING_ARRAY), + (itemWeapon, strings) -> itemWeapon.rawEntityStatsToClear = strings, + itemWeapon -> itemWeapon.rawEntityStatsToClear + ) + .add() + .append( + new KeyedCodec<>("RenderDualWielded", Codec.BOOLEAN), + (itemWeapon, value) -> itemWeapon.renderDualWielded = value, + itemWeapon -> itemWeapon.renderDualWielded + ) + .add() + .afterDecode(item -> { + item.statModifiers = EntityStatsModule.resolveEntityStats(item.rawStatModifiers); + item.entityStatsToClear = EntityStatsModule.resolveEntityStats(item.rawEntityStatsToClear); + }) + .build(); + @Nullable + protected Map rawStatModifiers; + @Nullable + protected Int2ObjectMap statModifiers; + protected String[] rawEntityStatsToClear; + @Nullable + protected int[] entityStatsToClear; + protected boolean renderDualWielded; + + public ItemWeapon() { + } + + @Nullable + public Int2ObjectMap getStatModifiers() { + return this.statModifiers; + } + + public int[] getEntityStatsToClear() { + return this.entityStatsToClear; + } + + @Nonnull + public com.hypixel.hytale.protocol.ItemWeapon toPacket() { + return new com.hypixel.hytale.protocol.ItemWeapon(this.entityStatsToClear, EntityStatMap.toPacket(this.statModifiers), this.renderDualWielded); + } + + @Nonnull + @Override + public String toString() { + return "ItemWeapon{rawStatModifiers=" + + this.rawStatModifiers + + ", statModifiers=" + + this.statModifiers + + ", rawEntityStatsToClear=" + + Arrays.toString((Object[])this.rawEntityStatsToClear) + + ", entityStatsToClear=" + + Arrays.toString(this.entityStatsToClear) + + ", renderDualWielded=" + + this.renderDualWielded + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/PortalKey.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/PortalKey.java new file mode 100644 index 0000000..0bfc46d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/PortalKey.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.asset.type.portalworld.PortalType; + +public class PortalKey { + public static final BuilderCodec CODEC = BuilderCodec.builder(PortalKey.class, PortalKey::new) + .appendInherited( + new KeyedCodec<>("PortalType", Codec.STRING), + (portalKey, o) -> portalKey.portalTypeId = o, + portalKey -> portalKey.portalTypeId, + (portalKey, parent) -> portalKey.portalTypeId = parent.portalTypeId + ) + .documentation("The ID of of the PortalType that this key opens.") + .addValidator(Validators.nonNull()) + .addValidator(PortalType.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("TimeLimitSeconds", Codec.INTEGER), + (portalKey, o) -> portalKey.timeLimitSeconds = o, + portalKey -> portalKey.timeLimitSeconds, + (portalKey, parent) -> portalKey.timeLimitSeconds = parent.timeLimitSeconds + ) + .add() + .build(); + private String portalTypeId; + private int timeLimitSeconds = -1; + + public PortalKey() { + } + + public String getPortalTypeId() { + return this.portalTypeId; + } + + public int getTimeLimitSeconds() { + return this.timeLimitSeconds; + } + + @Override + public String toString() { + return "PortalKey{instanceId='" + this.portalTypeId + "', timeLimitSeconds=" + this.timeLimitSeconds + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/ResourceType.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/ResourceType.java new file mode 100644 index 0000000..be500c0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/ResourceType.java @@ -0,0 +1,117 @@ +package com.hypixel.hytale.server.core.asset.type.item.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import javax.annotation.Nonnull; + +public class ResourceType + implements JsonAssetWithMap>, + NetworkSerializable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ResourceType.class, + ResourceType::new, + Codec.STRING, + (resourceType, k) -> resourceType.id = k, + resourceType -> resourceType.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("Name", Codec.STRING), + (resourceType, s) -> resourceType.name = s, + resourceType -> resourceType.name, + (resourceType, parent) -> resourceType.name = parent.name + ) + .add() + .appendInherited( + new KeyedCodec<>("Description", Codec.STRING), + (resourceType, s) -> resourceType.description = s, + resourceType -> resourceType.description, + (resourceType, parent) -> resourceType.description = parent.description + ) + .add() + .appendInherited( + new KeyedCodec<>("Icon", Codec.STRING), + (resourceType, s) -> resourceType.icon = s, + resourceType -> resourceType.icon, + (resourceType, parent) -> resourceType.icon = parent.icon + ) + .addValidator(CommonAssetValidator.ICON_RESOURCE) + .add() + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ResourceType::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected String name; + protected String description; + protected String icon; + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ResourceType.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public ResourceType(String id, String name, String description, String icon) { + this.id = id; + this.name = name; + this.description = description; + this.icon = icon; + } + + protected ResourceType() { + } + + public String getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public String getDescription() { + return this.description; + } + + public String getIcon() { + return this.icon; + } + + @Nonnull + public com.hypixel.hytale.protocol.ResourceType toPacket() { + com.hypixel.hytale.protocol.ResourceType cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.ResourceType packet = new com.hypixel.hytale.protocol.ResourceType(this.id, this.icon); + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + @Nonnull + @Override + public String toString() { + return "ResourceType{id='" + this.id + "', name='" + this.name + "', description='" + this.description + "', icon='" + this.icon + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/container/ChoiceItemDropContainer.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/container/ChoiceItemDropContainer.java new file mode 100644 index 0000000..7f57f19 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/container/ChoiceItemDropContainer.java @@ -0,0 +1,71 @@ +package com.hypixel.hytale.server.core.asset.type.item.config.container; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.map.IWeightedMap; +import com.hypixel.hytale.common.map.WeightedMap; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDrop; +import com.hypixel.hytale.server.core.codec.WeightedMapCodec; +import java.util.List; +import java.util.Set; +import java.util.function.DoubleSupplier; +import javax.annotation.Nonnull; + +public class ChoiceItemDropContainer extends ItemDropContainer { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ChoiceItemDropContainer.class, ChoiceItemDropContainer::new, ItemDropContainer.DEFAULT_CODEC + ) + .addField( + new KeyedCodec<>("Containers", new WeightedMapCodec<>(ItemDropContainer.CODEC, ItemDropContainer.EMPTY_ARRAY)), + (choiceItemDropContainer, o) -> choiceItemDropContainer.containers = o, + choiceItemDropContainer -> choiceItemDropContainer.containers + ) + .addField( + new KeyedCodec<>("RollsMin", Codec.INTEGER), + (choiceItemDropContainer, i) -> choiceItemDropContainer.rollsMin = i, + choiceItemDropContainer -> choiceItemDropContainer.rollsMin + ) + .addField( + new KeyedCodec<>("RollsMax", Codec.INTEGER), + (choiceItemDropContainer, i) -> choiceItemDropContainer.rollsMax = i, + choiceItemDropContainer -> choiceItemDropContainer.rollsMax + ) + .build(); + protected IWeightedMap containers; + protected int rollsMin = 1; + protected int rollsMax = 1; + + public ChoiceItemDropContainer(ItemDropContainer[] containers, double chance) { + super(chance); + this.containers = WeightedMap.builder(ItemDropContainer.EMPTY_ARRAY).putAll(containers, ItemDropContainer::getWeight).build(); + } + + public ChoiceItemDropContainer() { + } + + @Override + protected void populateDrops(List drops, DoubleSupplier chanceProvider, Set droplistReferences) { + int count = this.rollsMin + (int)(chanceProvider.getAsDouble() * (this.rollsMax - this.rollsMin + 1)); + + for (int i = 0; i < count; i++) { + ItemDropContainer drop = this.containers.get(chanceProvider); + drop.populateDrops(drops, chanceProvider, droplistReferences); + } + } + + @Override + public List getAllDrops(List list) { + for (ItemDropContainer container : this.containers.internalKeys()) { + container.getAllDrops(list); + } + + return list; + } + + @Nonnull + @Override + public String toString() { + return "ChoiceItemDropContainer{weight=" + this.weight + ", containers=" + this.containers + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/container/DroplistItemDropContainer.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/container/DroplistItemDropContainer.java new file mode 100644 index 0000000..a799805 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/container/DroplistItemDropContainer.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.core.asset.type.item.config.container; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDrop; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList; +import java.util.List; +import java.util.Set; +import java.util.function.DoubleSupplier; + +public class DroplistItemDropContainer extends ItemDropContainer { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DroplistItemDropContainer.class, DroplistItemDropContainer::new, ItemDropContainer.DEFAULT_CODEC + ) + .append( + new KeyedCodec<>("DroplistId", Codec.STRING), + (droplistItemDropContainer, s) -> droplistItemDropContainer.droplistId = s, + droplistItemDropContainer -> droplistItemDropContainer.droplistId + ) + .addValidator(Validators.nonNull()) + .addValidatorLate(() -> ItemDropList.VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + String droplistId; + + public DroplistItemDropContainer() { + } + + @Override + protected void populateDrops(List drops, DoubleSupplier chanceProvider, Set droplistReferences) { + if (droplistReferences.add(this.droplistId)) { + ItemDropList droplist = ItemDropList.getAssetMap().getAsset(this.droplistId); + if (droplist != null) { + droplist.getContainer().populateDrops(drops, chanceProvider, droplistReferences); + } + } + } + + @Override + public List getAllDrops(List list) { + ItemDropList droplist = ItemDropList.getAssetMap().getAsset(this.droplistId); + if (droplist == null) { + return list; + } else { + droplist.getContainer().getAllDrops(list); + return list; + } + } + + @Override + public String toString() { + return "DroplistItemDropContainer{droplistId='" + this.droplistId + "', weight=" + this.weight + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/container/EmptyItemDropContainer.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/container/EmptyItemDropContainer.java new file mode 100644 index 0000000..2703348 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/container/EmptyItemDropContainer.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.server.core.asset.type.item.config.container; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDrop; +import java.util.List; +import java.util.Set; +import java.util.function.DoubleSupplier; +import javax.annotation.Nonnull; + +public class EmptyItemDropContainer extends ItemDropContainer { + public static final BuilderCodec CODEC = BuilderCodec.builder( + EmptyItemDropContainer.class, EmptyItemDropContainer::new, ItemDropContainer.DEFAULT_CODEC + ) + .build(); + + public EmptyItemDropContainer() { + } + + @Override + protected void populateDrops(List drops, @Nonnull DoubleSupplier chanceProvider, Set droplistReferences) { + } + + @Override + public List getAllDrops(List list) { + return list; + } + + @Override + public String toString() { + return "EmptyItemDropContainer{}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/container/ItemDropContainer.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/container/ItemDropContainer.java new file mode 100644 index 0000000..1a5b395 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/container/ItemDropContainer.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.server.core.asset.type.item.config.container; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.common.map.IWeightedElement; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDrop; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.DoubleSupplier; + +public abstract class ItemDropContainer implements IWeightedElement { + public static final BuilderCodec DEFAULT_CODEC = BuilderCodec.abstractBuilder(ItemDropContainer.class) + .addField( + new KeyedCodec<>("Weight", Codec.DOUBLE), + (itemDropContainer, aDouble) -> itemDropContainer.weight = aDouble, + itemDropContainer -> itemDropContainer.weight + ) + .build(); + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final ItemDropContainer[] EMPTY_ARRAY = new ItemDropContainer[0]; + protected double weight = 100.0; + + public ItemDropContainer(double weight) { + this.weight = weight; + } + + protected ItemDropContainer() { + } + + @Override + public double getWeight() { + return this.weight; + } + + public void populateDrops(List drops, DoubleSupplier chanceProvider, String droplistId) { + Set droplistReferences = new HashSet<>(); + droplistReferences.add(droplistId); + this.populateDrops(drops, chanceProvider, droplistReferences); + } + + protected abstract void populateDrops(List var1, DoubleSupplier var2, Set var3); + + public abstract List getAllDrops(List var1); + + static { + CODEC.register("Multiple", MultipleItemDropContainer.class, MultipleItemDropContainer.CODEC); + CODEC.register("Choice", ChoiceItemDropContainer.class, ChoiceItemDropContainer.CODEC); + CODEC.register("Single", SingleItemDropContainer.class, SingleItemDropContainer.CODEC); + CODEC.register("Droplist", DroplistItemDropContainer.class, DroplistItemDropContainer.CODEC); + CODEC.register("Empty", EmptyItemDropContainer.class, EmptyItemDropContainer.CODEC); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/container/MultipleItemDropContainer.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/container/MultipleItemDropContainer.java new file mode 100644 index 0000000..ab6e3a4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/container/MultipleItemDropContainer.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.server.core.asset.type.item.config.container; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDrop; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.function.DoubleSupplier; +import javax.annotation.Nonnull; + +public class MultipleItemDropContainer extends ItemDropContainer { + public static final BuilderCodec CODEC = BuilderCodec.builder( + MultipleItemDropContainer.class, MultipleItemDropContainer::new, ItemDropContainer.DEFAULT_CODEC + ) + .addField( + new KeyedCodec<>("MinCount", Codec.INTEGER), + (multipleItemDropContainer, integer) -> multipleItemDropContainer.minCount = integer, + multipleItemDropContainer -> multipleItemDropContainer.minCount + ) + .addField( + new KeyedCodec<>("MaxCount", Codec.INTEGER), + (multipleItemDropContainer, integer) -> multipleItemDropContainer.maxCount = integer, + multipleItemDropContainer -> multipleItemDropContainer.maxCount + ) + .addField( + new KeyedCodec<>("Containers", new ArrayCodec<>(ItemDropContainer.CODEC, ItemDropContainer[]::new)), + (multipleItemDropContainer, o) -> multipleItemDropContainer.containers = o, + multipleItemDropContainer -> multipleItemDropContainer.containers + ) + .build(); + protected ItemDropContainer[] containers; + protected int minCount = 1; + protected int maxCount = 1; + + public MultipleItemDropContainer(ItemDropContainer[] containers, double chance, int minCount, int maxCount) { + super(chance); + this.containers = containers; + this.minCount = minCount; + this.maxCount = maxCount; + } + + public MultipleItemDropContainer() { + } + + @Override + protected void populateDrops(List drops, @Nonnull DoubleSupplier chanceProvider, Set droplistReferences) { + int count = (int)MathUtil.fastRound(chanceProvider.getAsDouble() * (this.maxCount - this.minCount) + this.minCount); + + for (int i = 0; i < count; i++) { + for (ItemDropContainer container : this.containers) { + if (container.getWeight() >= chanceProvider.getAsDouble() * 100.0) { + container.populateDrops(drops, chanceProvider, droplistReferences); + } + } + } + } + + @Override + public List getAllDrops(List list) { + for (ItemDropContainer container : this.containers) { + container.getAllDrops(list); + } + + return list; + } + + @Nonnull + @Override + public String toString() { + return "MultipleItemDropContainer{weight=" + this.weight + ", containers=" + Arrays.toString((Object[])this.containers) + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/container/SingleItemDropContainer.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/container/SingleItemDropContainer.java new file mode 100644 index 0000000..caa7ae7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/container/SingleItemDropContainer.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.server.core.asset.type.item.config.container; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDrop; +import java.util.List; +import java.util.Set; +import java.util.function.DoubleSupplier; +import javax.annotation.Nonnull; + +public class SingleItemDropContainer extends ItemDropContainer { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SingleItemDropContainer.class, SingleItemDropContainer::new, ItemDropContainer.DEFAULT_CODEC + ) + .append( + new KeyedCodec<>("Item", ItemDrop.CODEC), + (singleItemDropContainer, itemDrop) -> singleItemDropContainer.drop = itemDrop, + singleItemDropContainer -> singleItemDropContainer.drop + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + @Nonnull + protected ItemDrop drop; + + public SingleItemDropContainer(@Nonnull ItemDrop drop, double chance) { + super(chance); + this.drop = drop; + } + + protected SingleItemDropContainer() { + } + + @Nonnull + public ItemDrop getDrop() { + return this.drop; + } + + @Override + protected void populateDrops(@Nonnull List drops, DoubleSupplier chanceProvider, Set droplistReferences) { + drops.add(this.drop); + } + + @Nonnull + @Override + public List getAllDrops(@Nonnull List list) { + list.add(this.drop); + return list; + } + + @Nonnull + @Override + public String toString() { + return "SingleItemDropContainer{drop=" + this.drop + ", weight=" + this.weight + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/item/config/metadata/AdventureMetadata.java b/src/com/hypixel/hytale/server/core/asset/type/item/config/metadata/AdventureMetadata.java new file mode 100644 index 0000000..5775902 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/item/config/metadata/AdventureMetadata.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.asset.type.item.config.metadata; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; + +public class AdventureMetadata { + public static final String KEY = "Adventure"; + public static final BuilderCodec CODEC = BuilderCodec.builder(AdventureMetadata.class, AdventureMetadata::new) + .appendInherited( + new KeyedCodec<>("Cursed", Codec.BOOLEAN), + (meta, s) -> meta.cursed = s, + meta -> meta.cursed ? Boolean.TRUE : null, + (meta, parent) -> meta.cursed = parent.cursed + ) + .add() + .build(); + public static final KeyedCodec KEYED_CODEC = new KeyedCodec<>("Adventure", CODEC); + private boolean cursed; + + public AdventureMetadata() { + } + + public boolean isCursed() { + return this.cursed; + } + + public void setCursed(boolean cursed) { + this.cursed = cursed; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/itemanimation/ItemPlayerAnimationsPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/itemanimation/ItemPlayerAnimationsPacketGenerator.java new file mode 100644 index 0000000..8a7b823 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/itemanimation/ItemPlayerAnimationsPacketGenerator.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.server.core.asset.type.itemanimation; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateItemPlayerAnimations; +import com.hypixel.hytale.server.core.asset.packet.DefaultAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.itemanimation.config.ItemPlayerAnimations; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; + +public class ItemPlayerAnimationsPacketGenerator extends DefaultAssetPacketGenerator { + public ItemPlayerAnimationsPacketGenerator() { + } + + @Nonnull + @Override + public Packet generateInitPacket(DefaultAssetMap assetMap, @Nonnull Map assets) { + UpdateItemPlayerAnimations packet = new UpdateItemPlayerAnimations(); + packet.type = UpdateType.Init; + packet.itemPlayerAnimations = new Object2ObjectOpenHashMap<>(assets.size()); + + for (ItemPlayerAnimations itemPlayerAnimation : assets.values()) { + packet.itemPlayerAnimations.put(itemPlayerAnimation.getId(), itemPlayerAnimation.toPacket()); + } + + return packet; + } + + @Nonnull + @Override + public Packet generateUpdatePacket(@Nonnull Map loadedAssets) { + UpdateItemPlayerAnimations packet = new UpdateItemPlayerAnimations(); + packet.type = UpdateType.AddOrUpdate; + packet.itemPlayerAnimations = new Object2ObjectOpenHashMap<>(loadedAssets.size()); + + for (ItemPlayerAnimations itemPlayerAnimation : loadedAssets.values()) { + packet.itemPlayerAnimations.put(itemPlayerAnimation.getId(), itemPlayerAnimation.toPacket()); + } + + return packet; + } + + @Nonnull + @Override + public Packet generateRemovePacket(@Nonnull Set removed) { + UpdateItemPlayerAnimations packet = new UpdateItemPlayerAnimations(); + packet.type = UpdateType.Remove; + packet.itemPlayerAnimations = new Object2ObjectOpenHashMap<>(removed.size()); + + for (String key : removed) { + com.hypixel.hytale.protocol.ItemPlayerAnimations itemPlayerAnimation = new com.hypixel.hytale.protocol.ItemPlayerAnimations(); + itemPlayerAnimation.id = key; + packet.itemPlayerAnimations.put(key, itemPlayerAnimation); + } + + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/itemanimation/config/ItemPlayerAnimations.java b/src/com/hypixel/hytale/server/core/asset/type/itemanimation/config/ItemPlayerAnimations.java new file mode 100644 index 0000000..d5c81b8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/itemanimation/config/ItemPlayerAnimations.java @@ -0,0 +1,215 @@ +package com.hypixel.hytale.server.core.asset.type.itemanimation.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.MapUtil; +import com.hypixel.hytale.protocol.ItemAnimation; +import com.hypixel.hytale.protocol.WiggleWeights; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemPullbackConfig; +import com.hypixel.hytale.server.core.asset.type.model.config.camera.CameraSettings; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class ItemPlayerAnimations + implements JsonAssetWithMap>, + NetworkSerializable { + public static final String DEFAULT_ID = "Default"; + public static final BuilderCodec WIGGLE_WEIGHTS_CODEC = BuilderCodec.builder(WiggleWeights.class, WiggleWeights::new) + .addField(new KeyedCodec<>("X", Codec.DOUBLE), (wiggleWeight, d) -> wiggleWeight.x = d.floatValue(), wiggleWeight -> (double)wiggleWeight.x) + .addField( + new KeyedCodec<>("XDeceleration", Codec.DOUBLE), + (wiggleWeight, d) -> wiggleWeight.xDeceleration = d.floatValue(), + wiggleWeight -> (double)wiggleWeight.xDeceleration + ) + .addField(new KeyedCodec<>("Y", Codec.DOUBLE), (wiggleWeight, d) -> wiggleWeight.y = d.floatValue(), wiggleWeight -> (double)wiggleWeight.y) + .addField( + new KeyedCodec<>("YDeceleration", Codec.DOUBLE), + (wiggleWeight, d) -> wiggleWeight.yDeceleration = d.floatValue(), + wiggleWeight -> (double)wiggleWeight.yDeceleration + ) + .addField(new KeyedCodec<>("Z", Codec.DOUBLE), (wiggleWeight, d) -> wiggleWeight.z = d.floatValue(), wiggleWeight -> (double)wiggleWeight.z) + .addField( + new KeyedCodec<>("ZDeceleration", Codec.DOUBLE), + (wiggleWeight, d) -> wiggleWeight.zDeceleration = d.floatValue(), + wiggleWeight -> (double)wiggleWeight.zDeceleration + ) + .addField(new KeyedCodec<>("Roll", Codec.DOUBLE), (wiggleWeight, d) -> wiggleWeight.roll = d.floatValue(), wiggleWeight -> (double)wiggleWeight.roll) + .addField( + new KeyedCodec<>("RollDeceleration", Codec.DOUBLE), + (wiggleWeight, d) -> wiggleWeight.rollDeceleration = d.floatValue(), + wiggleWeight -> (double)wiggleWeight.rollDeceleration + ) + .addField(new KeyedCodec<>("Pitch", Codec.DOUBLE), (wiggleWeight, d) -> wiggleWeight.pitch = d.floatValue(), wiggleWeight -> (double)wiggleWeight.pitch) + .addField( + new KeyedCodec<>("PitchDeceleration", Codec.DOUBLE), + (wiggleWeight, d) -> wiggleWeight.pitchDeceleration = d.floatValue(), + wiggleWeight -> (double)wiggleWeight.pitchDeceleration + ) + .build(); + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ItemPlayerAnimations.class, + ItemPlayerAnimations::new, + Codec.STRING, + (t, k) -> t.id = k, + t -> t.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("Animations", new MapCodec<>(ProtocolCodecs.ITEM_ANIMATION_CODEC, HashMap::new)), + (itemPlayerAnimations, map) -> itemPlayerAnimations.animations = MapUtil.combineUnmodifiable(itemPlayerAnimations.animations, map), + itemPlayerAnimations -> itemPlayerAnimations.animations, + (itemPlayerAnimations, parent) -> itemPlayerAnimations.animations = parent.animations + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("WiggleWeights", WIGGLE_WEIGHTS_CODEC), + (itemPlayerAnimations, map) -> itemPlayerAnimations.wiggleWeights = map, + itemPlayerAnimations -> itemPlayerAnimations.wiggleWeights, + (itemPlayerAnimations, parent) -> itemPlayerAnimations.wiggleWeights = parent.wiggleWeights + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Camera", CameraSettings.CODEC), + (itemPlayerAnimations, o) -> itemPlayerAnimations.camera = o, + itemPlayerAnimations -> itemPlayerAnimations.camera, + (itemPlayerAnimations, parent) -> itemPlayerAnimations.camera = parent.camera + ) + .add() + .appendInherited( + new KeyedCodec<>("PullbackConfig", ItemPullbackConfig.CODEC), + (itemPlayerAnimations, s) -> itemPlayerAnimations.pullbackConfig = s, + itemPlayerAnimations -> itemPlayerAnimations.pullbackConfig, + (itemPlayerAnimations, parent) -> itemPlayerAnimations.pullbackConfig = parent.pullbackConfig + ) + .documentation("Overrides the offset of first person arms when close to obstacles") + .add() + .appendInherited( + new KeyedCodec<>("UseFirstPersonOverrides", Codec.BOOLEAN), + (itemPlayerAnimations, s) -> itemPlayerAnimations.useFirstPersonOverrides = s, + itemPlayerAnimations -> itemPlayerAnimations.useFirstPersonOverrides, + (itemPlayerAnimations, parent) -> itemPlayerAnimations.useFirstPersonOverrides = parent.useFirstPersonOverrides + ) + .documentation("Determines whether or not to use FirstPersonOverride animations within ItemAnimations") + .add() + .build(); + public static final Codec CHILD_CODEC = new ContainedAssetCodec<>(ItemPlayerAnimations.class, CODEC); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ItemPlayerAnimations::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected Map animations = Collections.emptyMap(); + protected WiggleWeights wiggleWeights; + protected CameraSettings camera; + protected ItemPullbackConfig pullbackConfig; + protected boolean useFirstPersonOverrides; + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ItemPlayerAnimations.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public ItemPlayerAnimations( + String id, + Map animations, + WiggleWeights wiggleWeights, + CameraSettings camera, + ItemPullbackConfig pullbackConfig, + boolean useFirstPersonOverrides + ) { + this.id = id; + this.animations = animations; + this.wiggleWeights = wiggleWeights; + this.camera = camera; + this.pullbackConfig = pullbackConfig; + this.useFirstPersonOverrides = useFirstPersonOverrides; + } + + protected ItemPlayerAnimations() { + } + + public String getId() { + return this.id; + } + + public Map getAnimations() { + return this.animations; + } + + public WiggleWeights getWiggleWeights() { + return this.wiggleWeights; + } + + public CameraSettings getCamera() { + return this.camera; + } + + @Nonnull + public com.hypixel.hytale.protocol.ItemPlayerAnimations toPacket() { + com.hypixel.hytale.protocol.ItemPlayerAnimations cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.ItemPlayerAnimations packet = new com.hypixel.hytale.protocol.ItemPlayerAnimations(); + packet.id = this.id; + packet.animations = this.animations; + packet.wiggleWeights = this.wiggleWeights; + if (this.camera != null) { + packet.camera = this.camera.toPacket(); + } + + if (this.pullbackConfig != null) { + packet.pullbackConfig = this.pullbackConfig.toPacket(); + } + + packet.useFirstPersonOverride = this.useFirstPersonOverrides; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + @Nonnull + @Override + public String toString() { + return "ItemPlayerAnimations{id='" + + this.id + + "', animations=" + + this.animations + + ", wiggleWeights=" + + this.wiggleWeights + + ", camera=" + + this.camera + + ", pullbackConfig=" + + this.pullbackConfig + + ", useFirstPersonOverrides=" + + this.useFirstPersonOverrides + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/itemsound/ItemSoundSetPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/itemsound/ItemSoundSetPacketGenerator.java new file mode 100644 index 0000000..95fde9d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/itemsound/ItemSoundSetPacketGenerator.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.asset.type.itemsound; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateItemSoundSets; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.itemsound.config.ItemSoundSet; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class ItemSoundSetPacketGenerator extends SimpleAssetPacketGenerator> { + public ItemSoundSetPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + UpdateItemSoundSets packet = new UpdateItemSoundSets(); + packet.type = UpdateType.Init; + packet.itemSoundSets = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.itemSoundSets.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateUpdatePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets) { + UpdateItemSoundSets packet = new UpdateItemSoundSets(); + packet.type = UpdateType.AddOrUpdate; + packet.itemSoundSets = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.itemSoundSets.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateRemovePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed) { + UpdateItemSoundSets packet = new UpdateItemSoundSets(); + packet.type = UpdateType.Remove; + packet.itemSoundSets = new Int2ObjectOpenHashMap<>(); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.itemSoundSets.put(index, null); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/itemsound/config/ItemSoundSet.java b/src/com/hypixel/hytale/server/core/asset/type/itemsound/config/ItemSoundSet.java new file mode 100644 index 0000000..8eff766 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/itemsound/config/ItemSoundSet.java @@ -0,0 +1,128 @@ +package com.hypixel.hytale.server.core.asset.type.itemsound.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDefaultCollapsedState; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.ItemSoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.validator.SoundEventValidators; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntMaps; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.lang.ref.SoftReference; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class ItemSoundSet + implements JsonAssetWithMap>, + NetworkSerializable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ItemSoundSet.class, + ItemSoundSet::new, + Codec.STRING, + (itemSounds, s) -> itemSounds.id = s, + itemSounds -> itemSounds.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("SoundEvents", new EnumMapCodec<>(ItemSoundEvent.class, Codec.STRING)), + (itemParticleSet, s) -> itemParticleSet.soundEventIds = s, + itemParticleSet -> itemParticleSet.soundEventIds, + (itemParticleSet, parent) -> itemParticleSet.soundEventIds = parent.soundEventIds + ) + .addValidator(Validators.nonNull()) + .addValidator(SoundEvent.VALIDATOR_CACHE.getMapValueValidator()) + .addValidator(SoundEventValidators.STEREO_VALIDATOR_CACHE.getMapValueValidator()) + .addValidator(SoundEventValidators.ONESHOT_VALIDATOR_CACHE.getMapValueValidator()) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .afterDecode(ItemSoundSet::processConfig) + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ItemSoundSet::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected Map soundEventIds = Collections.emptyMap(); + protected transient Object2IntMap soundEventIndices = Object2IntMaps.emptyMap(); + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ItemSoundSet.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public ItemSoundSet(String id, Map soundEventIds) { + this.id = id; + this.soundEventIds = soundEventIds; + } + + public ItemSoundSet(String id) { + this.id = id; + } + + protected ItemSoundSet() { + } + + @Nonnull + public com.hypixel.hytale.protocol.ItemSoundSet toPacket() { + com.hypixel.hytale.protocol.ItemSoundSet cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.ItemSoundSet packet = new com.hypixel.hytale.protocol.ItemSoundSet(); + packet.id = this.id; + packet.soundEventIndices = this.soundEventIndices; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + public String getId() { + return this.id; + } + + public Map getSoundEventIds() { + return this.soundEventIds; + } + + public Object2IntMap getSoundEventIndices() { + return this.soundEventIndices; + } + + protected void processConfig() { + if (!this.soundEventIds.isEmpty()) { + this.soundEventIndices = new Object2IntOpenHashMap<>(); + + for (Entry entry : this.soundEventIds.entrySet()) { + this.soundEventIndices.put(entry.getKey(), SoundEvent.getAssetMap().getIndex(entry.getValue())); + } + } + } + + @Nonnull + @Override + public String toString() { + return "ItemSoundSet{id='" + this.id + "', soundEventIds=" + this.soundEventIds + ", soundEventIndices=" + this.soundEventIndices + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/model/config/DetailBox.java b/src/com/hypixel/hytale/server/core/asset/type/model/config/DetailBox.java new file mode 100644 index 0000000..4c1f035 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/model/config/DetailBox.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.core.asset.type.model.config; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.io.NetworkSerializers; +import javax.annotation.Nonnull; + +public class DetailBox implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(DetailBox.class, DetailBox::new) + .appendInherited(new KeyedCodec<>("Offset", Vector3d.CODEC), (o, i) -> o.offset = i, o -> o.offset, (o, p) -> o.offset = p.offset) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Box", Box.CODEC), (o, i) -> o.box = i, o -> o.box, (o, p) -> o.box = p.box) + .addValidator(Validators.nonNull()) + .add() + .build(); + protected Vector3d offset = Vector3d.ZERO; + protected Box box = Box.UNIT; + + public DetailBox() { + } + + public DetailBox(Vector3d offset, Box box) { + this.offset = offset; + this.box = box; + } + + public DetailBox(DetailBox other) { + this.offset.assign(other.offset); + this.box.assign(other.box); + } + + public Vector3d getOffset() { + return this.offset; + } + + public Box getBox() { + return this.box; + } + + public DetailBox scaled(float scale) { + return new DetailBox(this.offset.clone().scale(scale), this.box.clone().scale(scale)); + } + + @Nonnull + public com.hypixel.hytale.protocol.DetailBox toPacket() { + return new com.hypixel.hytale.protocol.DetailBox( + new Vector3f((float)this.offset.x, (float)this.offset.y, (float)this.offset.z), NetworkSerializers.BOX.toPacket(this.box) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/model/config/Model.java b/src/com/hypixel/hytale/server/core/asset/type/model/config/Model.java new file mode 100644 index 0000000..ce216c2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/model/config/Model.java @@ -0,0 +1,712 @@ +package com.hypixel.hytale.server.core.asset.type.model.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.protocol.AnimationSet; +import com.hypixel.hytale.protocol.ColorLight; +import com.hypixel.hytale.protocol.ModelTrail; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.protocol.Phobia; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.asset.type.model.config.camera.CameraSettings; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.io.NetworkSerializers; +import com.hypixel.hytale.server.core.modules.physics.component.PhysicsValues; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.lang.ref.SoftReference; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Model implements NetworkSerializable { + public static final String UNKNOWN_TEXTURE = "textures/Unknown.png"; + private final String modelAssetId; + private final float scale; + private final Map randomAttachmentIds; + private final ModelAttachment[] attachments; + @Nullable + private final Box boundingBox; + @Nullable + private final Box crouchBoundingBox; + private final String model; + private final String texture; + private final String gradientSet; + private final String gradientId; + private final float eyeHeight; + private final float crouchOffset; + private final Map animationSetMap; + private final CameraSettings camera; + private final ColorLight light; + private final ModelParticle[] particles; + private final ModelTrail[] trails; + private final PhysicsValues physicsValues; + private final Map detailBoxes; + private final Phobia phobia; + private final String phobiaModelAssetId; + private transient SoftReference cachedPacket; + + public Model( + String modelAssetId, + float scale, + Map randomAttachmentIds, + ModelAttachment[] attachments, + @Nullable Box boundingBox, + String model, + String texture, + String gradientSet, + String gradientId, + float eyeHeight, + float crouchOffset, + Map animationSetMap, + CameraSettings camera, + ColorLight light, + ModelParticle[] particles, + ModelTrail[] trails, + PhysicsValues physicsValues, + Map detailBoxes, + Phobia phobia, + String phobiaModelAssetId + ) { + this.modelAssetId = modelAssetId; + this.scale = scale; + this.randomAttachmentIds = randomAttachmentIds; + this.attachments = attachments; + this.boundingBox = boundingBox; + this.model = model; + this.texture = texture; + this.gradientSet = gradientSet; + this.gradientId = gradientId; + this.eyeHeight = eyeHeight; + this.crouchOffset = crouchOffset; + this.animationSetMap = animationSetMap; + this.camera = camera; + this.light = light; + this.particles = particles; + this.trails = trails; + this.physicsValues = physicsValues; + this.detailBoxes = detailBoxes; + this.crouchBoundingBox = boundingBox == null ? null : new Box(boundingBox.min.clone(), boundingBox.max.clone().add(0.0, crouchOffset, 0.0)); + this.phobia = phobia; + this.phobiaModelAssetId = phobiaModelAssetId; + } + + public Model(@Nonnull Model other) { + this.modelAssetId = other.modelAssetId; + this.scale = other.scale; + this.randomAttachmentIds = other.randomAttachmentIds; + this.attachments = other.attachments; + this.boundingBox = other.boundingBox; + this.model = other.model; + this.texture = other.texture; + this.gradientSet = other.gradientSet; + this.gradientId = other.gradientId; + this.eyeHeight = other.eyeHeight; + this.crouchOffset = other.crouchOffset; + this.animationSetMap = other.animationSetMap; + this.camera = other.camera; + this.light = other.light; + this.particles = other.particles; + this.trails = other.trails; + this.physicsValues = other.physicsValues; + this.crouchBoundingBox = other.crouchBoundingBox; + this.detailBoxes = other.detailBoxes; + this.phobia = other.phobia; + this.phobiaModelAssetId = other.phobiaModelAssetId; + } + + @Nonnull + public com.hypixel.hytale.protocol.Model toPacket() { + com.hypixel.hytale.protocol.Model cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.Model packet = new com.hypixel.hytale.protocol.Model(); + if (this.modelAssetId != null) { + packet.assetId = this.modelAssetId; + } + + if (this.model != null) { + packet.path = this.model; + } + + if (this.texture != null) { + packet.texture = this.texture; + } else if (this.model == null) { + packet.texture = "textures/Unknown.png"; + } else { + packet.texture = this.model.replace(".blockymodel", ".png"); + } + + packet.gradientSet = this.gradientSet; + packet.gradientId = this.gradientId; + if (this.scale > 0.0F) { + packet.scale = this.scale; + } + + if (this.eyeHeight > 0.0F) { + packet.eyeHeight = this.eyeHeight; + } + + if (this.crouchOffset != 0.0F) { + packet.crouchOffset = this.crouchOffset; + } + + if (this.animationSetMap != null) { + Map map = new Object2ObjectOpenHashMap<>(this.animationSetMap.size()); + + for (Entry entry : this.animationSetMap.entrySet()) { + map.put(entry.getKey(), entry.getValue().toPacket(entry.getKey())); + } + + packet.animationSets = map; + } + + if (this.attachments != null && this.attachments.length > 0) { + packet.attachments = new com.hypixel.hytale.protocol.ModelAttachment[this.attachments.length]; + + for (int i = 0; i < this.attachments.length; i++) { + packet.attachments[i] = this.attachments[i].toPacket(); + } + } + + if (this.boundingBox != null) { + packet.hitbox = NetworkSerializers.BOX.toPacket(this.boundingBox); + } + + packet.light = this.light; + if (this.particles != null && this.particles.length > 0) { + packet.particles = new com.hypixel.hytale.protocol.ModelParticle[this.particles.length]; + + for (int i = 0; i < this.particles.length; i++) { + packet.particles[i] = this.particles[i].toPacket(); + } + } + + packet.trails = this.trails; + if (this.camera != null) { + packet.camera = this.camera.toPacket(); + } + + if (this.detailBoxes != null) { + Map map = packet.detailBoxes = new Object2ObjectOpenHashMap<>(this.detailBoxes.size()); + + for (Entry entry : this.detailBoxes.entrySet()) { + map.put(entry.getKey(), Arrays.stream(entry.getValue()).map(NetworkSerializable::toPacket).toArray(com.hypixel.hytale.protocol.DetailBox[]::new)); + } + } + + if (this.phobia != Phobia.None && this.phobiaModelAssetId != null) { + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(this.phobiaModelAssetId); + if (modelAsset != null) { + Model model = createUnitScaleModel(modelAsset, this.boundingBox); + packet.phobiaModel = model.toPacket(); + packet.phobia = this.phobia; + } + } + + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + public String getModelAssetId() { + return this.modelAssetId; + } + + public float getScale() { + return this.scale; + } + + public Map getRandomAttachmentIds() { + return this.randomAttachmentIds; + } + + public ModelAttachment[] getAttachments() { + return this.attachments; + } + + @Nullable + public Box getBoundingBox(@Nullable MovementStates movementStates) { + if (movementStates == null) { + return this.boundingBox; + } else { + return !movementStates.crouching && !movementStates.forcedCrouching ? this.boundingBox : this.crouchBoundingBox; + } + } + + @Nullable + public Box getBoundingBox() { + return this.boundingBox; + } + + @Nullable + public Box getCrouchBoundingBox() { + return this.crouchBoundingBox; + } + + public String getModel() { + return this.model; + } + + public String getTexture() { + return this.texture; + } + + public String getGradientSet() { + return this.gradientSet; + } + + public String getGradientId() { + return this.gradientId; + } + + public float getEyeHeight() { + return this.eyeHeight; + } + + public float getCrouchOffset() { + return this.crouchOffset; + } + + public Map getAnimationSetMap() { + return this.animationSetMap != null ? this.animationSetMap : Collections.emptyMap(); + } + + @Nullable + public String getFirstBoundAnimationId(@Nullable String id, @Nullable String fallbackId) { + if (id != null && this.animationSetMap.containsKey(id)) { + return id; + } else { + return fallbackId != null && this.animationSetMap.containsKey(fallbackId) ? fallbackId : null; + } + } + + @Nullable + public String getFirstBoundAnimationId(@Nonnull String... preferenceOrder) { + for (String animationId : preferenceOrder) { + if (animationId != null && this.animationSetMap.containsKey(animationId)) { + return animationId; + } + } + + return null; + } + + public CameraSettings getCamera() { + return this.camera; + } + + public ColorLight getLight() { + return this.light; + } + + public ModelParticle[] getParticles() { + return this.particles; + } + + public ModelTrail[] getTrails() { + return this.trails; + } + + public PhysicsValues getPhysicsValues() { + return this.physicsValues; + } + + public Map getDetailBoxes() { + return this.detailBoxes; + } + + public Phobia getPhobia() { + return this.phobia; + } + + public String getPhobiaModelAssetId() { + return this.phobiaModelAssetId; + } + + @Nonnull + public Model.ModelReference toReference() { + return "Player".equals(this.modelAssetId) + ? Model.ModelReference.DEFAULT_PLAYER_MODEL + : new Model.ModelReference(this.modelAssetId, this.scale, this.randomAttachmentIds, this.animationSetMap == null); + } + + public float getEyeHeight(@Nullable Ref ref, @Nullable ComponentAccessor componentAccessor) { + if (ref != null && componentAccessor != null && ref.isValid()) { + MovementStatesComponent movementStatesComponent = componentAccessor.getComponent(ref, MovementStatesComponent.getComponentType()); + if (movementStatesComponent == null) { + return this.getEyeHeight(); + } else { + MovementStates movementStates = movementStatesComponent.getMovementStates(); + return movementStates.crouching ? this.getEyeHeight() + this.getCrouchOffset() : this.getEyeHeight(); + } + } else { + return this.getEyeHeight(); + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Model model1 = (Model)o; + if (Float.compare(model1.scale, this.scale) != 0) { + return false; + } else if (Float.compare(model1.eyeHeight, this.eyeHeight) != 0) { + return false; + } else if (Float.compare(model1.crouchOffset, this.crouchOffset) != 0) { + return false; + } else if (!Objects.equals(this.modelAssetId, model1.modelAssetId)) { + return false; + } else if (!Objects.equals(this.randomAttachmentIds, model1.randomAttachmentIds)) { + return false; + } else if (!Arrays.equals((Object[])this.attachments, (Object[])model1.attachments)) { + return false; + } else if (!Objects.equals(this.boundingBox, model1.boundingBox)) { + return false; + } else if (!Objects.equals(this.model, model1.model)) { + return false; + } else if (!Objects.equals(this.texture, model1.texture)) { + return false; + } else if (!Objects.equals(this.gradientSet, model1.gradientSet)) { + return false; + } else if (!Objects.equals(this.gradientId, model1.gradientId)) { + return false; + } else if (!Objects.equals(this.animationSetMap, model1.animationSetMap)) { + return false; + } else if (!Objects.equals(this.camera, model1.camera)) { + return false; + } else if (!Objects.equals(this.light, model1.light)) { + return false; + } else if (!Arrays.equals((Object[])this.particles, (Object[])model1.particles)) { + return false; + } else { + return !Arrays.equals((Object[])this.trails, (Object[])model1.trails) ? false : Objects.equals(this.physicsValues, model1.physicsValues); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.modelAssetId != null ? this.modelAssetId.hashCode() : 0; + result = 31 * result + (this.scale != 0.0F ? Float.floatToIntBits(this.scale) : 0); + result = 31 * result + (this.randomAttachmentIds != null ? this.randomAttachmentIds.hashCode() : 0); + result = 31 * result + Arrays.hashCode((Object[])this.attachments); + result = 31 * result + (this.boundingBox != null ? this.boundingBox.hashCode() : 0); + result = 31 * result + (this.model != null ? this.model.hashCode() : 0); + result = 31 * result + (this.texture != null ? this.texture.hashCode() : 0); + result = 31 * result + (this.gradientSet != null ? this.gradientSet.hashCode() : 0); + result = 31 * result + (this.gradientId != null ? this.gradientId.hashCode() : 0); + result = 31 * result + (this.eyeHeight != 0.0F ? Float.floatToIntBits(this.eyeHeight) : 0); + result = 31 * result + (this.crouchOffset != 0.0F ? Float.floatToIntBits(this.crouchOffset) : 0); + result = 31 * result + (this.animationSetMap != null ? this.animationSetMap.hashCode() : 0); + result = 31 * result + (this.camera != null ? this.camera.hashCode() : 0); + result = 31 * result + (this.light != null ? this.light.hashCode() : 0); + result = 31 * result + Arrays.hashCode((Object[])this.particles); + result = 31 * result + Arrays.hashCode((Object[])this.trails); + return 31 * result + (this.physicsValues != null ? this.physicsValues.hashCode() : 0); + } + + @Override + public String toString() { + return "Model{modelAssetId='" + + this.modelAssetId + + "', scale=" + + this.scale + + ", randomAttachmentIds=" + + this.randomAttachmentIds + + ", attachments=" + + Arrays.toString((Object[])this.attachments) + + ", boundingBox=" + + this.boundingBox + + ", crouchBoundingBox=" + + this.crouchBoundingBox + + ", model='" + + this.model + + "', texture='" + + this.texture + + "', gradientSet='" + + this.gradientSet + + "', gradientId='" + + this.gradientId + + "', eyeHeight=" + + this.eyeHeight + + ", crouchOffset=" + + this.crouchOffset + + ", animationSetMap=" + + this.animationSetMap + + ", camera=" + + this.camera + + ", light=" + + this.light + + ", particles=" + + Arrays.toString((Object[])this.particles) + + ", trails=" + + Arrays.toString((Object[])this.trails) + + ", physicsValues=" + + this.physicsValues + + ", detailBoxes=" + + this.detailBoxes + + ", phobia=" + + this.phobia + + ", phobiaModelAssetId='" + + this.phobiaModelAssetId + + "'}"; + } + + @Nonnull + public static Model createRandomScaleModel(@Nonnull ModelAsset modelAsset) { + return createScaledModel(modelAsset, modelAsset.generateRandomScale()); + } + + @Nonnull + public static Model createStaticScaledModel(@Nonnull ModelAsset modelAsset, float scale) { + return createScaledModel(modelAsset, scale, modelAsset.generateRandomAttachmentIds(), null, true); + } + + @Nonnull + public static Model createUnitScaleModel(@Nonnull ModelAsset modelAsset) { + return createScaledModel(modelAsset, 1.0F, null); + } + + @Nonnull + public static Model createUnitScaleModel(@Nonnull ModelAsset modelAsset, @Nullable Box boundingBox) { + return createScaledModel(modelAsset, 1.0F, null, boundingBox); + } + + @Nonnull + public static Model createScaledModel(@Nonnull ModelAsset modelAsset, float scale) { + return createScaledModel(modelAsset, scale, modelAsset.generateRandomAttachmentIds()); + } + + @Nonnull + public static Model createScaledModel(@Nonnull ModelAsset modelAsset, float scale, @Nullable Map randomAttachmentIds) { + return createScaledModel(modelAsset, scale, randomAttachmentIds, null, false); + } + + @Nonnull + public static Model createScaledModel( + @Nonnull ModelAsset modelAsset, float scale, @Nullable Map randomAttachmentIds, @Nullable Box overrideBoundingBox + ) { + return createScaledModel(modelAsset, scale, randomAttachmentIds, overrideBoundingBox, false); + } + + @Nonnull + public static Model createScaledModel( + @Nonnull ModelAsset modelAsset, float scale, @Nullable Map randomAttachmentIds, @Nullable Box overrideBoundingBox, boolean staticModel + ) { + Objects.requireNonNull(modelAsset, "ModelAsset can't be null"); + if (scale <= 0.0F) { + throw new IllegalArgumentException("Scale must be greater than 0"); + } else { + Box boundingBox = overrideBoundingBox != null ? overrideBoundingBox : modelAsset.getBoundingBox(); + Map detailBoxes = modelAsset.getDetailBoxes(); + float eyeHeight = modelAsset.getEyeHeight(); + float crouchOffset = modelAsset.getCrouchOffset(); + CameraSettings camera = modelAsset.getCamera(); + PhysicsValues physicsValues = modelAsset.getPhysicsValues(); + ModelParticle[] particles = modelAsset.getParticles(); + ModelTrail[] trails = modelAsset.getTrails(); + if (scale != 1.0F) { + boundingBox = boundingBox.clone().scale(scale); + if (detailBoxes != null) { + HashMap scaledDetailBoxes = new HashMap<>(detailBoxes.size()); + + for (Entry entry : detailBoxes.entrySet()) { + scaledDetailBoxes.put(entry.getKey(), Arrays.stream(entry.getValue()).map(v -> v.scaled(scale)).toArray(DetailBox[]::new)); + } + + detailBoxes = scaledDetailBoxes; + } + + eyeHeight *= scale; + crouchOffset *= scale; + if (camera != null) { + camera = camera.clone().scale(scale); + } + + if (physicsValues != null) { + physicsValues = new PhysicsValues(physicsValues); + physicsValues.scale(scale); + } + + if (particles != null) { + ModelParticle[] scaledParticules = new ModelParticle[particles.length]; + + for (int i = 0; i < particles.length; i++) { + scaledParticules[i] = particles[i].clone().scale(scale); + } + + particles = scaledParticules; + } + + if (trails != null) { + ModelTrail[] scaledTrails = new ModelTrail[trails.length]; + + for (int i = 0; i < trails.length; i++) { + ModelTrail trail = trails[i]; + ModelTrail scaledTrail = new ModelTrail(trail); + if (trail.positionOffset != null) { + scaledTrail.positionOffset = new Vector3f(); + scaledTrail.positionOffset.x = trail.positionOffset.x * scale; + scaledTrail.positionOffset.y = trail.positionOffset.y * scale; + scaledTrail.positionOffset.z = trail.positionOffset.z * scale; + } + + scaledTrails[i] = scaledTrail; + } + + trails = scaledTrails; + } + } + + ModelAttachment[] attachments = modelAsset.getAttachments(randomAttachmentIds); + Map animationSetMap = staticModel ? null : modelAsset.getAnimationSetMap(); + return new Model( + modelAsset.getId(), + scale, + randomAttachmentIds, + attachments, + boundingBox, + modelAsset.getModel(), + modelAsset.getTexture(), + modelAsset.getGradientSet(), + modelAsset.getGradientId(), + eyeHeight, + crouchOffset, + animationSetMap, + camera, + modelAsset.getLight(), + particles, + trails, + physicsValues, + detailBoxes, + modelAsset.getPhobia(), + modelAsset.getPhobiaModelAssetId() + ); + } + } + + public static class ModelReference { + public static final BuilderCodec CODEC = BuilderCodec.builder(Model.ModelReference.class, Model.ModelReference::new) + .addField(new KeyedCodec<>("Id", Codec.STRING), (modelReference, s) -> modelReference.modelAssetId = s, modelReference -> modelReference.modelAssetId) + .addField( + new KeyedCodec<>("Scale", Codec.DOUBLE), + (modelReference, aDouble) -> modelReference.scale = aDouble.floatValue(), + modelReference -> (double)modelReference.scale + ) + .addField( + new KeyedCodec<>("RandomAttachments", MapCodec.STRING_HASH_MAP_CODEC), + (modelReference, stringStringMap) -> modelReference.randomAttachmentIds = stringStringMap, + modelReference -> modelReference.randomAttachmentIds + ) + .addField( + new KeyedCodec<>("Static", Codec.BOOLEAN), (modelReference, b) -> modelReference.staticModel = b, modelReference -> modelReference.staticModel + ) + .build(); + public static final Model.ModelReference DEFAULT_PLAYER_MODEL = new Model.ModelReference("Player", -1.0F, null, false); + private String modelAssetId; + private float scale; + private Map randomAttachmentIds; + private boolean staticModel; + + public ModelReference(String modelAssetId, float scale, Map randomAttachmentIds) { + this(modelAssetId, scale, randomAttachmentIds, false); + } + + public ModelReference(String modelAssetId, float scale, Map randomAttachmentIds, boolean staticModel) { + this.modelAssetId = modelAssetId; + this.scale = scale; + this.randomAttachmentIds = randomAttachmentIds; + this.staticModel = staticModel; + } + + protected ModelReference() { + } + + public String getModelAssetId() { + return this.modelAssetId; + } + + public float getScale() { + return this.scale; + } + + public Map getRandomAttachmentIds() { + return this.randomAttachmentIds; + } + + public boolean isStaticModel() { + return this.staticModel; + } + + @Nullable + public Model toModel() { + if (this.modelAssetId == null) { + return null; + } else { + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(this.modelAssetId); + if (modelAsset == null) { + modelAsset = ModelAsset.DEBUG; + } + + return Model.createScaledModel(modelAsset, this.scale, this.randomAttachmentIds, null, this.staticModel); + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Model.ModelReference that = (Model.ModelReference)o; + if (Float.compare(that.scale, this.scale) != 0) { + return false; + } else if (this.staticModel != that.staticModel) { + return false; + } else { + return !Objects.equals(this.modelAssetId, that.modelAssetId) ? false : Objects.equals(this.randomAttachmentIds, that.randomAttachmentIds); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.modelAssetId != null ? this.modelAssetId.hashCode() : 0; + result = 31 * result + (this.scale != 0.0F ? Float.floatToIntBits(this.scale) : 0); + result = 31 * result + (this.randomAttachmentIds != null ? this.randomAttachmentIds.hashCode() : 0); + return 31 * result + (this.staticModel ? 1 : 0); + } + + @Nonnull + @Override + public String toString() { + return "ModelReference{modelAssetId='" + + this.modelAssetId + + "', scale=" + + this.scale + + ", randomAttachmentIds=" + + this.randomAttachmentIds + + ", staticModel=" + + this.staticModel + + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/model/config/ModelAsset.java b/src/com/hypixel/hytale/server/core/asset/type/model/config/ModelAsset.java new file mode 100644 index 0000000..a7b0fa3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/model/config/ModelAsset.java @@ -0,0 +1,717 @@ +package com.hypixel.hytale.server.core.asset.type.model.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.array.IntArrayCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIButton; +import com.hypixel.hytale.codec.schema.metadata.ui.UICreateButtons; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDefaultCollapsedState; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDisplayMode; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorPreview; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorSectionStart; +import com.hypixel.hytale.codec.schema.metadata.ui.UIRebuildCaches; +import com.hypixel.hytale.codec.schema.metadata.ui.UISidebarButtons; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.codec.validation.validator.IntArrayValidator; +import com.hypixel.hytale.common.map.IWeightedMap; +import com.hypixel.hytale.common.map.WeightedMap; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.common.util.MapUtil; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.ColorLight; +import com.hypixel.hytale.protocol.EntityPart; +import com.hypixel.hytale.protocol.ModelTrail; +import com.hypixel.hytale.protocol.Phobia; +import com.hypixel.hytale.protocol.Rangef; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.asset.type.item.config.AssetIconProperties; +import com.hypixel.hytale.server.core.asset.type.model.config.camera.CameraAxis; +import com.hypixel.hytale.server.core.asset.type.model.config.camera.CameraSettings; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.validator.SoundEventValidators; +import com.hypixel.hytale.server.core.asset.type.trail.config.Trail; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.modules.physics.component.PhysicsValues; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ModelAsset implements JsonAssetWithMap> { + public static final BuilderCodec MODEL_TRAIL_CODEC = BuilderCodec.builder(ModelTrail.class, ModelTrail::new) + .append(new KeyedCodec<>("TrailId", new ContainedAssetCodec<>(Trail.class, Trail.CODEC)), (trail, s) -> trail.trailId = s, trail -> trail.trailId) + .addValidator(Trail.VALIDATOR_CACHE.getValidator()) + .add() + .addField( + new KeyedCodec<>("TargetEntityPart", new EnumCodec<>(EntityPart.class)), (trail, s) -> trail.targetEntityPart = s, trail -> trail.targetEntityPart + ) + .addField(new KeyedCodec<>("TargetNodeName", Codec.STRING), (trail, s) -> trail.targetNodeName = s, trail -> trail.targetNodeName) + .addField(new KeyedCodec<>("PositionOffset", ProtocolCodecs.VECTOR3F), (trail, s) -> trail.positionOffset = s, trail -> trail.positionOffset) + .addField(new KeyedCodec<>("RotationOffset", ProtocolCodecs.DIRECTION), (trail, s) -> trail.rotationOffset = s, trail -> trail.rotationOffset) + .addField(new KeyedCodec<>("FixedRotation", Codec.BOOLEAN), (trail, s) -> trail.fixedRotation = s, trail -> trail.fixedRotation) + .build(); + public static final ArrayCodec MODEL_TRAIL_ARRAY_CODEC = new ArrayCodec<>(MODEL_TRAIL_CODEC, ModelTrail[]::new); + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ModelAsset.class, + ModelAsset::new, + Codec.STRING, + (modelAsset, s) -> modelAsset.id = s, + modelAsset -> modelAsset.id, + (modelAsset, data) -> modelAsset.extraData = data, + modelAsset -> modelAsset.extraData + ) + .metadata(new UIEditorPreview(UIEditorPreview.PreviewType.MODEL)) + .metadata( + new UISidebarButtons( + new UIButton("server.assetEditor.buttons.resetModel", "ResetModel"), new UIButton("server.assetEditor.buttons.useModel", "UseModel") + ) + ) + .metadata(new UICreateButtons(new UIButton("server.assetEditor.buttons.createAndUseModel", "UseModel"))) + .appendInherited( + new KeyedCodec<>("Model", Codec.STRING), (model, s) -> model.model = s, model -> model.model, (model, parent) -> model.model = parent.model + ) + .addValidator(CommonAssetValidator.MODEL_CHARACTER) + .add() + .appendInherited( + new KeyedCodec<>("Texture", Codec.STRING), (model, s) -> model.texture = s, model -> model.texture, (model, parent) -> model.texture = parent.texture + ) + .addValidator(CommonAssetValidator.TEXTURE_CHARACTER) + .add() + .appendInherited( + new KeyedCodec<>("GradientSet", Codec.STRING), + (model, s) -> model.gradientSet = s, + model -> model.gradientSet, + (model, parent) -> model.gradientSet = parent.gradientSet + ) + .metadata(new UIEditor(new UIEditor.Dropdown("GradientSets"))) + .add() + .appendInherited( + new KeyedCodec<>("GradientId", Codec.STRING), + (model, s) -> model.gradientId = s, + model -> model.gradientId, + (model, parent) -> model.gradientId = parent.gradientId + ) + .metadata(new UIEditor(new UIEditor.Dropdown("GradientIds"))) + .add() + .appendInherited(new KeyedCodec<>("Icon", Codec.STRING), (item, s) -> item.icon = s, item -> item.icon, (item, parent) -> item.icon = parent.icon) + .addValidator(CommonAssetValidator.ICON_MODEL) + .metadata(new UIEditor(new UIEditor.Icon("Icons/ModelsGenerated/{assetId}.png", 128, 128))) + .metadata(new UIRebuildCaches(UIRebuildCaches.ClientCache.ITEM_ICONS)) + .add() + .appendInherited( + new KeyedCodec<>("IconProperties", AssetIconProperties.CODEC), + (item, s) -> item.iconProperties = s, + item -> item.iconProperties, + (item, parent) -> item.iconProperties = parent.iconProperties + ) + .metadata(UIDisplayMode.HIDDEN) + .add() + .appendInherited( + new KeyedCodec<>("Light", ProtocolCodecs.COLOR_LIGHT), + (model, l) -> model.light = l, + model -> model.light, + (model, parent) -> model.light = parent.light + ) + .add() + .appendInherited( + new KeyedCodec<>("PhysicsValues", PhysicsValues.CODEC), + (model, l) -> model.physicsValues = l, + model -> model.physicsValues, + (model, parent) -> model.physicsValues = parent.physicsValues + ) + .add() + .appendInherited( + new KeyedCodec<>("MinScale", Codec.DOUBLE), + (modelAsset, d) -> modelAsset.minScale = d.floatValue(), + modelAsset -> (double)modelAsset.minScale, + (modelAsset, parent) -> modelAsset.minScale = parent.minScale + ) + .metadata(new UIEditorSectionStart("Hitbox")) + .add() + .appendInherited( + new KeyedCodec<>("MaxScale", Codec.DOUBLE), + (modelAsset, d) -> modelAsset.maxScale = d.floatValue(), + modelAsset -> (double)modelAsset.maxScale, + (modelAsset, parent) -> modelAsset.maxScale = parent.maxScale + ) + .add() + .appendInherited( + new KeyedCodec<>("EyeHeight", Codec.DOUBLE), + (model, d) -> model.eyeHeight = d.floatValue(), + model -> (double)model.eyeHeight, + (model, parent) -> model.eyeHeight = parent.eyeHeight + ) + .add() + .appendInherited( + new KeyedCodec<>("HitBox", Box.CODEC), + (model, o) -> model.boundingBox = o, + model -> model.boundingBox, + (model, parent) -> model.boundingBox = parent.boundingBox + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("DetailBoxes", new MapCodec<>(new ArrayCodec<>(DetailBox.CODEC, DetailBox[]::new), HashMap::new)), + (o, i) -> o.detailBoxes = i, + o -> o.detailBoxes, + (o, p) -> o.detailBoxes = p.detailBoxes + ) + .add() + .appendInherited( + new KeyedCodec<>("CrouchOffset", Codec.DOUBLE), + (model, d) -> model.crouchOffset = d.floatValue(), + model -> (double)model.crouchOffset, + (model, parent) -> model.crouchOffset = parent.crouchOffset + ) + .metadata(new UIEditorSectionStart("Camera")) + .add() + .appendInherited( + new KeyedCodec<>("Camera", CameraSettings.CODEC), + (model, o) -> model.camera = o, + model -> model.camera, + (model, parent) -> model.camera = parent.camera + ) + .add() + .appendInherited( + new KeyedCodec<>("DefaultAttachments", new ArrayCodec<>(ModelAttachment.CODEC, ModelAttachment[]::new)), + (modelAsset, l) -> modelAsset.defaultAttachments = l, + modelAsset -> modelAsset.defaultAttachments, + (modelAsset, parent) -> modelAsset.defaultAttachments = parent.defaultAttachments + ) + .metadata(new UIEditorSectionStart("Attachments")) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("RandomAttachmentSets", new MapCodec<>(new MapCodec<>(ModelAttachment.CODEC, HashMap::new), HashMap::new)), + (modelAsset, l) -> modelAsset.randomAttachmentSets = l, + modelAsset -> modelAsset.randomAttachmentSets, + (modelAsset, parent) -> modelAsset.randomAttachmentSets = parent.randomAttachmentSets + ) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("AnimationSets", new MapCodec<>(ModelAsset.AnimationSet.CODEC, HashMap::new)), + (model, m) -> model.animationSetMap = MapUtil.combineUnmodifiable(model.animationSetMap, m), + model -> model.animationSetMap, + (model, parent) -> model.animationSetMap = parent.animationSetMap + ) + .metadata(new UIEditorSectionStart("Animations")) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("Particles", ModelParticle.ARRAY_CODEC), + (model, l) -> model.particles = l, + model -> model.particles, + (model, parent) -> model.particles = parent.particles + ) + .metadata(new UIEditorSectionStart("Physics")) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("Trails", MODEL_TRAIL_ARRAY_CODEC), + (model, l) -> model.trails = l, + model -> model.trails, + (model, parent) -> model.trails = parent.trails + ) + .metadata(new UIEditorSectionStart("Trails")) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("Phobia", new EnumCodec<>(Phobia.class)), + (modelAsset, phobia) -> modelAsset.phobia = phobia, + modelAsset -> modelAsset.phobia, + (modelAsset, parent) -> modelAsset.phobia = parent.phobia + ) + .addValidator(Validators.nonNull()) + .documentation("Enum used to specify if the NPC is part of a phobia (e.g. spider for arachnophobia).") + .add() + .appendInherited( + new KeyedCodec<>("PhobiaModelAssetId", Codec.STRING), + (modelAsset, s) -> modelAsset.phobiaModelAssetId = s, + modelAsset -> modelAsset.phobiaModelAssetId, + (modelAsset, parent) -> modelAsset.phobiaModelAssetId = parent.phobiaModelAssetId + ) + .documentation("The model to use if the player has the setting with the matching phobia toggled on.") + .addValidatorLate(() -> ModelAsset.VALIDATOR_CACHE.getValidator().late()) + .add() + .afterDecode(modelAsset -> { + if (modelAsset.randomAttachmentSets != null && !modelAsset.randomAttachmentSets.isEmpty()) { + Map> weightedRandomAttachmentSets = new Object2ObjectOpenHashMap<>(); + + for (Entry> entry : modelAsset.randomAttachmentSets.entrySet()) { + WeightedMap.Builder builder = WeightedMap.builder(ArrayUtil.EMPTY_STRING_ARRAY); + + for (Entry attachmentEntry : entry.getValue().entrySet()) { + builder.put(attachmentEntry.getKey(), attachmentEntry.getValue().weight); + } + + weightedRandomAttachmentSets.put(entry.getKey(), builder.build()); + } + + modelAsset.weightedRandomAttachmentSets = weightedRandomAttachmentSets; + } + }) + .build(); + public static final ModelAsset DEBUG = new ModelAsset() { + { + this.id = "Debug"; + this.model = "Blocks/_Debug/Model.blockymodel"; + this.texture = "Characters/_Debug/Texture.png"; + this.camera = new CameraSettings(null, CameraAxis.STATIC_HEAD, CameraAxis.STATIC_HEAD); + this.boundingBox = new Box(new Vector3d(0.0, 0.0, 0.0), new Vector3d(1.0, 1.0, 1.0)); + this.minScale = 1.0F; + this.maxScale = 1.0F; + } + }; + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ModelAsset::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data extraData; + protected String id; + protected String model; + protected String texture; + protected String gradientSet; + protected String gradientId; + protected float eyeHeight; + protected float crouchOffset; + protected Map animationSetMap = Collections.emptyMap(); + protected CameraSettings camera; + protected Box boundingBox; + protected ColorLight light; + protected ModelParticle[] particles; + protected ModelTrail[] trails; + protected PhysicsValues physicsValues = new PhysicsValues(68.0, 0.5, false); + protected ModelAttachment[] defaultAttachments; + protected Map> randomAttachmentSets; + protected float minScale = 0.95F; + protected float maxScale = 1.05F; + protected String icon; + protected AssetIconProperties iconProperties; + protected Map detailBoxes = Collections.emptyMap(); + protected Map> weightedRandomAttachmentSets; + @Nonnull + protected Phobia phobia = Phobia.None; + protected String phobiaModelAssetId; + + public ModelAsset() { + } + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ModelAsset.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public String getId() { + return this.id; + } + + public String getModel() { + return this.model; + } + + public String getTexture() { + return this.texture; + } + + public String getGradientId() { + return this.gradientId; + } + + public String getGradientSet() { + return this.gradientSet; + } + + public float getEyeHeight() { + return this.eyeHeight; + } + + public float getCrouchOffset() { + return this.crouchOffset; + } + + public Map getAnimationSetMap() { + return this.animationSetMap; + } + + public CameraSettings getCamera() { + return this.camera; + } + + @Nonnull + public Box getBoundingBox() { + return this.boundingBox; + } + + public ColorLight getLight() { + return this.light; + } + + public ModelParticle[] getParticles() { + return this.particles; + } + + public ModelTrail[] getTrails() { + return this.trails; + } + + public PhysicsValues getPhysicsValues() { + return this.physicsValues; + } + + public ModelAttachment[] getDefaultAttachments() { + return this.defaultAttachments; + } + + public Map> getRandomAttachmentSets() { + return this.randomAttachmentSets; + } + + public float getMinScale() { + return this.minScale; + } + + public float getMaxScale() { + return this.maxScale; + } + + public AssetIconProperties getIconProperties() { + return this.iconProperties; + } + + public String getIcon() { + return this.icon; + } + + public float generateRandomScale() { + return MathUtil.randomFloat(this.minScale, this.maxScale); + } + + @Nullable + public Map generateRandomAttachmentIds() { + if (this.weightedRandomAttachmentSets == null) { + return null; + } else { + ThreadLocalRandom random = ThreadLocalRandom.current(); + Map randomAttachmentIds = new Object2ObjectOpenHashMap<>(); + + for (Entry> entry : this.weightedRandomAttachmentSets.entrySet()) { + String attachmentSetId = entry.getKey(); + String attachmentId = entry.getValue().get(random); + if (attachmentId != null) { + randomAttachmentIds.put(attachmentSetId, attachmentId); + } + } + + return randomAttachmentIds; + } + } + + public ModelAttachment[] getAttachments(@Nullable Map randomAttachmentIds) { + if (randomAttachmentIds != null && !randomAttachmentIds.isEmpty() && this.randomAttachmentSets != null) { + List attachments = new ObjectArrayList<>( + (this.defaultAttachments == null ? 0 : this.defaultAttachments.length) + randomAttachmentIds.size() + ); + if (this.defaultAttachments != null) { + Collections.addAll(attachments, this.defaultAttachments); + } + + for (Entry entry : randomAttachmentIds.entrySet()) { + Map attachmentSet = this.randomAttachmentSets.get(entry.getKey()); + if (attachmentSet != null) { + ModelAttachment attachment = attachmentSet.get(entry.getValue()); + if (attachment != null && attachment.getModel() != null && attachment.getTexture() != null) { + attachments.add(attachment); + } + } + } + + return attachments.toArray(ModelAttachment[]::new); + } else { + return this.defaultAttachments; + } + } + + public Map getDetailBoxes() { + return this.detailBoxes; + } + + public Phobia getPhobia() { + return this.phobia; + } + + public String getPhobiaModelAssetId() { + return this.phobiaModelAssetId; + } + + @Override + public String toString() { + return "ModelAsset{id='" + + this.id + + "', model='" + + this.model + + "', texture='" + + this.texture + + "', gradientSet='" + + this.gradientSet + + "', gradientId='" + + this.gradientId + + "', eyeHeight=" + + this.eyeHeight + + ", crouchOffset=" + + this.crouchOffset + + ", animationSetMap=" + + this.animationSetMap + + ", camera=" + + this.camera + + ", boundingBox=" + + this.boundingBox + + ", light=" + + this.light + + ", particles=" + + Arrays.toString((Object[])this.particles) + + ", trails=" + + Arrays.toString((Object[])this.trails) + + ", physicsValues=" + + this.physicsValues + + ", defaultAttachments=" + + Arrays.toString((Object[])this.defaultAttachments) + + ", randomAttachmentSets=" + + this.randomAttachmentSets + + ", minScale=" + + this.minScale + + ", maxScale=" + + this.maxScale + + ", icon='" + + this.icon + + "', iconProperties=" + + this.iconProperties + + ", detailBoxes=" + + this.detailBoxes + + ", weightedRandomAttachmentSets=" + + this.weightedRandomAttachmentSets + + ", phobia=" + + this.phobia + + ", phobiaModelAssetId='" + + this.phobiaModelAssetId + + "'}"; + } + + public static class Animation { + public static final BuilderCodec CODEC = BuilderCodec.builder(ModelAsset.Animation.class, ModelAsset.Animation::new) + .append(new KeyedCodec<>("Animation", Codec.STRING), (animation, s) -> animation.animation = s, animation -> animation.animation) + .addValidator(Validators.nonNull()) + .addValidator(CommonAssetValidator.ANIMATION_CHARACTER) + .add() + .append(new KeyedCodec<>("Speed", Codec.DOUBLE), (animation, s) -> animation.speed = s.floatValue(), animation -> (double)animation.speed) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append( + new KeyedCodec<>("BlendingDuration", Codec.DOUBLE), + (animation, s) -> animation.blendingDuration = s.floatValue(), + animation -> (double)animation.blendingDuration + ) + .addValidator(Validators.min(0.0)) + .add() + .addField(new KeyedCodec<>("Looping", Codec.BOOLEAN), (animation, s) -> animation.looping = s, animation -> animation.looping) + .addField( + new KeyedCodec<>("Weight", Codec.DOUBLE), (animation, aDouble) -> animation.weight = aDouble.floatValue(), animation -> (double)animation.weight + ) + .append( + new KeyedCodec<>("FootstepIntervals", Codec.INT_ARRAY), (animation, a) -> animation.footstepIntervals = a, animation -> animation.footstepIntervals + ) + .documentation( + "The intervals (in percentage of the animation duration) at which footsteps are supposed to occur. Only relevant for movement animations (used for timing footstep sound effects)." + ) + .addValidator(new IntArrayValidator(Validators.range(0, 100))) + .add() + .append(new KeyedCodec<>("SoundEventId", Codec.STRING), (animation, s) -> animation.soundEventId = s, animation -> animation.soundEventId) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .add() + .append( + new KeyedCodec<>("PassiveLoopCount", Codec.INTEGER), + (animation, integer) -> animation.passiveLoopCount = integer, + animation -> animation.passiveLoopCount + ) + .addValidator(Validators.greaterThan(0)) + .add() + .afterDecode(ModelAsset.Animation::processConfig) + .build(); + protected String animation; + protected float speed = 1.0F; + protected float blendingDuration = 0.2F; + protected boolean looping = true; + protected float weight = 1.0F; + protected int[] footstepIntervals = IntArrayCodec.EMPTY_INT_ARRAY; + protected String soundEventId; + protected transient int soundEventIndex; + protected int passiveLoopCount = 1; + + public Animation( + String id, String animation, float speed, float blendingDuration, boolean looping, float weight, int[] footstepIntervals, String soundEventId + ) { + this.animation = animation; + this.speed = speed; + this.blendingDuration = blendingDuration; + this.looping = looping; + this.weight = weight; + this.footstepIntervals = footstepIntervals; + this.soundEventId = soundEventId; + } + + protected Animation() { + } + + public String getAnimation() { + return this.animation; + } + + public float getSpeed() { + return this.speed; + } + + public float getBlendingDuration() { + return this.blendingDuration; + } + + public boolean isLooping() { + return this.looping; + } + + public double getWeight() { + return this.weight; + } + + public String getSoundEventId() { + return this.soundEventId; + } + + public int getSoundEventIndex() { + return this.soundEventIndex; + } + + @Nonnull + public com.hypixel.hytale.protocol.Animation toPacket() { + com.hypixel.hytale.protocol.Animation packet = new com.hypixel.hytale.protocol.Animation(); + packet.name = this.animation; + packet.speed = this.speed; + packet.blendingDuration = this.blendingDuration; + packet.looping = this.looping; + packet.weight = this.weight; + packet.footstepIntervals = this.footstepIntervals; + packet.soundEventIndex = this.soundEventIndex; + packet.passiveLoopCount = this.passiveLoopCount; + return packet; + } + + protected void processConfig() { + if (this.soundEventId != null) { + this.soundEventIndex = SoundEvent.getAssetMap().getIndex(this.soundEventId); + } + } + + @Nonnull + @Override + public String toString() { + return "Animation{animation='" + + this.animation + + "', speed=" + + this.speed + + ", blendingDuration=" + + this.blendingDuration + + ", looping=" + + this.looping + + ", weight=" + + this.weight + + ", footstepIntervals=" + + Arrays.toString(this.footstepIntervals) + + ", soundEventId='" + + this.soundEventId + + "', soundEventIndex=" + + this.soundEventIndex + + ", passiveLoopCount=" + + this.passiveLoopCount + + "}"; + } + } + + public static class AnimationSet { + public static final BuilderCodec CODEC = BuilderCodec.builder(ModelAsset.AnimationSet.class, ModelAsset.AnimationSet::new) + .append( + new KeyedCodec<>("Animations", new ArrayCodec<>(ModelAsset.Animation.CODEC, ModelAsset.Animation[]::new)), + (animationSet, animations) -> animationSet.animations = animations, + animationSet -> animationSet.animations + ) + .addValidator(Validators.nonEmptyArray()) + .add() + .addField( + new KeyedCodec<>("NextAnimationDelay", ProtocolCodecs.RANGEF), + (animationSet, rangef) -> animationSet.nextAnimationDelay = rangef, + animationSet -> animationSet.nextAnimationDelay + ) + .build(); + public static final Rangef DEFAULT_NEXT_ANIMATION_DELAY = new Rangef(2.0F, 10.0F); + protected ModelAsset.Animation[] animations; + protected Rangef nextAnimationDelay = DEFAULT_NEXT_ANIMATION_DELAY; + + public AnimationSet(ModelAsset.Animation[] animations, Rangef nextAnimationDelay) { + this.animations = animations; + this.nextAnimationDelay = nextAnimationDelay; + } + + public AnimationSet() { + } + + public ModelAsset.Animation[] getAnimations() { + return this.animations; + } + + public Rangef getNextAnimationDelay() { + return this.nextAnimationDelay; + } + + @Nonnull + public com.hypixel.hytale.protocol.AnimationSet toPacket(String id) { + com.hypixel.hytale.protocol.AnimationSet packet = new com.hypixel.hytale.protocol.AnimationSet(); + packet.id = id; + packet.animations = ArrayUtil.copyAndMutate(this.animations, ModelAsset.Animation::toPacket, com.hypixel.hytale.protocol.Animation[]::new); + packet.nextAnimationDelay = this.nextAnimationDelay; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "AnimationSet{animations=" + Arrays.toString((Object[])this.animations) + ", nextAnimationDelay=" + this.nextAnimationDelay + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/model/config/ModelAttachment.java b/src/com/hypixel/hytale/server/core/asset/type/model/config/ModelAttachment.java new file mode 100644 index 0000000..ad7e549 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/model/config/ModelAttachment.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.server.core.asset.type.model.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.cosmetics.CosmeticAssetValidator; +import com.hypixel.hytale.server.core.cosmetics.CosmeticType; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class ModelAttachment implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(ModelAttachment.class, ModelAttachment::new) + .append(new KeyedCodec<>("Model", Codec.STRING), (modelAttachment, s) -> modelAttachment.model = s, modelAttachment -> modelAttachment.model) + .addValidator(CommonAssetValidator.MODEL_CHARACTER_ATTACHMENT) + .add() + .append( + new KeyedCodec<>("Texture", Codec.STRING), (modelAttachment, s) -> modelAttachment.texture = s, modelAttachment -> modelAttachment.texture + ) + .addValidator(CommonAssetValidator.TEXTURE_CHARACTER_ATTACHMENT) + .add() + .append( + new KeyedCodec<>("GradientSet", Codec.STRING), (modelAttachment, s) -> modelAttachment.gradientSet = s, modelAttachment -> modelAttachment.gradientSet + ) + .metadata(new UIEditor(new UIEditor.Dropdown("GradientSets"))) + .addValidator(new CosmeticAssetValidator(CosmeticType.GRADIENT_SETS)) + .add() + .append( + new KeyedCodec<>("GradientId", Codec.STRING), (modelAttachment, s) -> modelAttachment.gradientId = s, modelAttachment -> modelAttachment.gradientId + ) + .metadata(new UIEditor(new UIEditor.Dropdown("GradientIds"))) + .add() + .addField( + new KeyedCodec<>("Weight", Codec.DOUBLE), (modelAttachment, aDouble) -> modelAttachment.weight = aDouble, modelAttachment -> modelAttachment.weight + ) + .build(); + protected String model; + protected String texture; + protected String gradientSet; + protected String gradientId; + protected double weight = 1.0; + + public ModelAttachment(String model, String texture, String gradientSet, String gradientId, double weight) { + this.model = model; + this.texture = texture; + this.gradientSet = gradientSet; + this.gradientId = gradientId; + this.weight = weight; + } + + protected ModelAttachment() { + } + + public String getModel() { + return this.model; + } + + public String getTexture() { + return this.texture; + } + + public String getGradientId() { + return this.gradientId; + } + + public String getGradientSet() { + return this.gradientSet; + } + + public double getWeight() { + return this.weight; + } + + @Nonnull + public com.hypixel.hytale.protocol.ModelAttachment toPacket() { + com.hypixel.hytale.protocol.ModelAttachment packet = new com.hypixel.hytale.protocol.ModelAttachment(); + packet.model = this.model; + packet.texture = this.texture; + packet.gradientSet = this.gradientSet; + packet.gradientId = this.gradientId; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "ModelAttachment{model='" + + this.model + + "', texture='" + + this.texture + + "', gradientSet='" + + this.gradientSet + + "', gradientId='" + + this.gradientId + + "', weight=" + + this.weight + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/model/config/ModelParticle.java b/src/com/hypixel/hytale/server/core/asset/type/model/config/ModelParticle.java new file mode 100644 index 0000000..e479456 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/model/config/ModelParticle.java @@ -0,0 +1,177 @@ +package com.hypixel.hytale.server.core.asset.type.model.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.EntityPart; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class ModelParticle implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(ModelParticle.class, ModelParticle::new) + .append(new KeyedCodec<>("SystemId", Codec.STRING), (particle, s) -> particle.systemId = s, particle -> particle.systemId) + .addValidator(Validators.nonNull()) + .addValidator(ParticleSystem.VALIDATOR_CACHE.getValidator()) + .add() + .append( + new KeyedCodec<>("TargetEntityPart", new EnumCodec<>(EntityPart.class)), + (particle, o) -> particle.targetEntityPart = o, + particle -> particle.targetEntityPart + ) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("TargetNodeName", Codec.STRING), (particle, s) -> particle.targetNodeName = s, particle -> particle.targetNodeName) + .add() + .append(new KeyedCodec<>("Color", ProtocolCodecs.COLOR), (particle, o) -> particle.color = o, particle -> particle.color) + .add() + .append(new KeyedCodec<>("Scale", Codec.DOUBLE), (particle, o) -> particle.scale = o.floatValue(), particle -> (double)particle.scale) + .addValidator(Validators.greaterThan(0.0)) + .add() + .append(new KeyedCodec<>("PositionOffset", ProtocolCodecs.VECTOR3F), (particle, s) -> particle.positionOffset = s, particle -> particle.positionOffset) + .add() + .append(new KeyedCodec<>("RotationOffset", ProtocolCodecs.DIRECTION), (particle, s) -> particle.rotationOffset = s, particle -> particle.rotationOffset) + .add() + .append( + new KeyedCodec<>("DetachedFromModel", Codec.BOOLEAN), + (modelParticle, aBoolean) -> modelParticle.detachedFromModel = aBoolean, + modelParticle -> modelParticle.detachedFromModel + ) + .documentation("To indicate if the spawned particle should be attached to the model and follow it, or spawn in world space.") + .add() + .build(); + public static final ArrayCodec ARRAY_CODEC = new ArrayCodec<>(CODEC, ModelParticle[]::new); + protected String systemId; + @Nonnull + protected EntityPart targetEntityPart = EntityPart.Self; + protected String targetNodeName; + protected Color color; + protected float scale = 1.0F; + protected Vector3f positionOffset; + protected Direction rotationOffset; + protected boolean detachedFromModel; + + public ModelParticle( + String systemId, + EntityPart targetEntityPart, + String targetNodeName, + Color color, + float scale, + Vector3f positionOffset, + Direction rotationOffset, + boolean detachedFromModel + ) { + this.systemId = systemId; + this.targetEntityPart = targetEntityPart; + this.targetNodeName = targetNodeName; + this.color = color; + this.scale = scale; + this.positionOffset = positionOffset; + this.rotationOffset = rotationOffset; + this.detachedFromModel = detachedFromModel; + } + + public ModelParticle(ModelParticle other) { + this.systemId = other.systemId; + this.targetEntityPart = other.targetEntityPart; + this.targetNodeName = other.targetNodeName; + this.color = other.color; + this.scale = other.scale; + this.positionOffset = other.positionOffset; + this.rotationOffset = other.rotationOffset; + this.detachedFromModel = other.detachedFromModel; + } + + protected ModelParticle() { + } + + @Nonnull + public com.hypixel.hytale.protocol.ModelParticle toPacket() { + com.hypixel.hytale.protocol.ModelParticle packet = new com.hypixel.hytale.protocol.ModelParticle(); + packet.systemId = this.systemId; + packet.targetEntityPart = this.targetEntityPart; + packet.targetNodeName = this.targetNodeName; + packet.color = this.color; + packet.scale = this.scale; + packet.positionOffset = this.positionOffset; + packet.rotationOffset = this.rotationOffset; + packet.detachedFromModel = this.detachedFromModel; + return packet; + } + + public String getSystemId() { + return this.systemId; + } + + public EntityPart getTargetEntityPart() { + return this.targetEntityPart; + } + + public String getTargetNodeName() { + return this.targetNodeName; + } + + public Color getColor() { + return this.color; + } + + public float getScale() { + return this.scale; + } + + public Vector3f getPositionOffset() { + return this.positionOffset; + } + + public Direction getRotationOffset() { + return this.rotationOffset; + } + + public boolean isDetachedFromModel() { + return this.detachedFromModel; + } + + public ModelParticle scale(float scale) { + this.scale *= scale; + if (this.positionOffset != null) { + this.positionOffset.x *= scale; + this.positionOffset.y *= scale; + this.positionOffset.z *= scale; + } + + return this; + } + + @Nonnull + @Override + public String toString() { + return "ModelParticle{systemId='" + + this.systemId + + "', targetEntityPart=" + + this.targetEntityPart + + ", targetNodeName='" + + this.targetNodeName + + "', color=" + + this.color + + ", scale=" + + this.scale + + ", positionOffset=" + + this.positionOffset + + ", rotationOffset=" + + this.rotationOffset + + ", detachedFromModel=" + + this.detachedFromModel + + "}"; + } + + public ModelParticle clone() { + return new ModelParticle(this); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/model/config/camera/CameraAxis.java b/src/com/hypixel/hytale/server/core/asset/type/model/config/camera/CameraAxis.java new file mode 100644 index 0000000..16b0fec --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/model/config/camera/CameraAxis.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.server.core.asset.type.model.config.camera; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.CameraNode; +import com.hypixel.hytale.protocol.Rangef; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class CameraAxis implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(CameraAxis.class, CameraAxis::new) + .append(new KeyedCodec<>("AngleRange", ProtocolCodecs.RANGEF), (cameraAxis, s) -> cameraAxis.angleRange = s, cameraAxis -> cameraAxis.angleRange) + .add() + .append( + new KeyedCodec<>("TargetNodes", new ArrayCodec<>(new EnumCodec<>(CameraNode.class), CameraNode[]::new)), + (cameraAxis, s) -> cameraAxis.targetNodes = s, + cameraAxis -> cameraAxis.targetNodes + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + public static final CameraAxis STATIC_HEAD = new CameraAxis(new Rangef(0.0F, 0.0F), new CameraNode[]{CameraNode.Head}); + protected Rangef angleRange; + protected CameraNode[] targetNodes; + + protected CameraAxis() { + } + + public CameraAxis(Rangef angleRange, CameraNode[] targetNodes) { + this.angleRange = angleRange; + this.targetNodes = targetNodes; + } + + @Nonnull + public com.hypixel.hytale.protocol.CameraAxis toPacket() { + com.hypixel.hytale.protocol.CameraAxis packet = new com.hypixel.hytale.protocol.CameraAxis(); + packet.angleRange = this.angleRange; + packet.targetNodes = this.targetNodes; + return packet; + } + + public Rangef getAngleRange() { + return this.angleRange; + } + + public CameraNode[] getTargetNodes() { + return this.targetNodes; + } + + @Nonnull + @Override + public String toString() { + return "CameraAxis{angleRange=" + this.angleRange + ", targetNodes=" + Arrays.toString((Object[])this.targetNodes) + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/model/config/camera/CameraSettings.java b/src/com/hypixel/hytale/server/core/asset/type/model/config/camera/CameraSettings.java new file mode 100644 index 0000000..2e41c56 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/model/config/camera/CameraSettings.java @@ -0,0 +1,87 @@ +package com.hypixel.hytale.server.core.asset.type.model.config.camera; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CameraSettings implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(CameraSettings.class, CameraSettings::new) + .addField( + new KeyedCodec<>("PositionOffset", ProtocolCodecs.VECTOR3F), + (cameraSettings, s) -> cameraSettings.positionOffset = s, + cameraSettings -> cameraSettings.positionOffset + ) + .addField(new KeyedCodec<>("Yaw", CameraAxis.CODEC), (cameraSettings, s) -> cameraSettings.yaw = s, cameraSettings -> cameraSettings.yaw) + .addField(new KeyedCodec<>("Pitch", CameraAxis.CODEC), (cameraSettings, s) -> cameraSettings.pitch = s, cameraSettings -> cameraSettings.pitch) + .build(); + @Nullable + protected Vector3f positionOffset; + protected CameraAxis yaw; + protected CameraAxis pitch; + + protected CameraSettings() { + } + + public CameraSettings(Vector3f positionOffset, CameraAxis yaw, CameraAxis pitch) { + this.positionOffset = positionOffset; + this.yaw = yaw; + this.pitch = pitch; + } + + public CameraSettings(CameraSettings other) { + this.positionOffset = other.positionOffset != null ? new Vector3f(other.positionOffset) : null; + this.yaw = other.yaw; + this.pitch = other.pitch; + } + + @Nonnull + public com.hypixel.hytale.protocol.CameraSettings toPacket() { + com.hypixel.hytale.protocol.CameraSettings packet = new com.hypixel.hytale.protocol.CameraSettings(); + packet.positionOffset = this.positionOffset; + if (this.yaw != null) { + packet.yaw = this.yaw.toPacket(); + } + + if (this.pitch != null) { + packet.pitch = this.pitch.toPacket(); + } + + return packet; + } + + public Vector3f getPositionOffset() { + return this.positionOffset; + } + + public CameraAxis getYaw() { + return this.yaw; + } + + public CameraAxis getPitch() { + return this.pitch; + } + + public CameraSettings scale(float scale) { + if (this.positionOffset != null) { + this.positionOffset.x *= scale; + this.positionOffset.y *= scale; + this.positionOffset.z *= scale; + } + + return this; + } + + @Nonnull + @Override + public String toString() { + return "CameraSettings{positionOffset=" + this.positionOffset + ", yaw=" + this.yaw + ", pitch=" + this.pitch + "}"; + } + + public CameraSettings clone() { + return new CameraSettings(this); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/modelvfx/ModelVFXPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/modelvfx/ModelVFXPacketGenerator.java new file mode 100644 index 0000000..96c3f11 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/modelvfx/ModelVFXPacketGenerator.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.asset.type.modelvfx; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateModelvfxs; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.modelvfx.config.ModelVFX; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class ModelVFXPacketGenerator extends SimpleAssetPacketGenerator> { + public ModelVFXPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + UpdateModelvfxs packet = new UpdateModelvfxs(); + packet.type = UpdateType.Init; + packet.modelVFXs = new Int2ObjectOpenHashMap<>(assets.size()); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.modelVFXs.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + protected Packet generateUpdatePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets) { + UpdateModelvfxs packet = new UpdateModelvfxs(); + packet.type = UpdateType.AddOrUpdate; + packet.modelVFXs = new Int2ObjectOpenHashMap<>(loadedAssets.size()); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.modelVFXs.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + protected Packet generateRemovePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed) { + UpdateModelvfxs packet = new UpdateModelvfxs(); + packet.type = UpdateType.Remove; + packet.modelVFXs = new Int2ObjectOpenHashMap<>(removed.size()); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.modelVFXs.put(index, null); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/modelvfx/config/ModelVFX.java b/src/com/hypixel/hytale/server/core/asset/type/modelvfx/config/ModelVFX.java new file mode 100644 index 0000000..51e44e0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/modelvfx/config/ModelVFX.java @@ -0,0 +1,283 @@ +package com.hypixel.hytale.server.core.asset.type.modelvfx.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.CurveType; +import com.hypixel.hytale.protocol.EffectDirection; +import com.hypixel.hytale.protocol.LoopOption; +import com.hypixel.hytale.protocol.SwitchTo; +import com.hypixel.hytale.protocol.Vector2f; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class ModelVFX + implements JsonAssetWithMap>, + NetworkSerializable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ModelVFX.class, + ModelVFX::new, + Codec.STRING, + (modelVFX, s) -> modelVFX.id = s, + modelVFX -> modelVFX.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .append(new KeyedCodec<>("SwitchTo", new EnumCodec<>(SwitchTo.class)), (modelVFX, s) -> modelVFX.switchTo = s, modelVFX -> modelVFX.switchTo) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("EffectDirection", new EnumCodec<>(EffectDirection.class)), + (modelVFX, s) -> modelVFX.effectDirection = s, + modelVFX -> modelVFX.effectDirection + ) + .addValidator(Validators.nonNull()) + .add() + .addField(new KeyedCodec<>("AnimationDuration", Codec.FLOAT), (modelVFX, d) -> modelVFX.animationDuration = d, modelVFX -> modelVFX.animationDuration) + .addField(new KeyedCodec<>("AnimationRange", ProtocolCodecs.VECTOR2F), (modelVFX, d) -> modelVFX.animationRange = d, modelVFX -> modelVFX.animationRange) + .append( + new KeyedCodec<>("LoopOption", new EnumCodec<>(LoopOption.class)), (modelVFX, s) -> modelVFX.loopOption = s, modelVFX -> modelVFX.loopOption + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("CurveType", new EnumCodec<>(CurveType.class)), (modelVFX, s) -> modelVFX.curveType = s, modelVFX -> modelVFX.curveType + ) + .addValidator(Validators.nonNull()) + .add() + .addField(new KeyedCodec<>("HighlightColor", ProtocolCodecs.COLOR), (modelVFX, o) -> modelVFX.highlightColor = o, modelVFX -> modelVFX.highlightColor) + .addField(new KeyedCodec<>("HighlightThickness", Codec.FLOAT), (modelVFX, d) -> modelVFX.highlightThickness = d, modelVFX -> modelVFX.highlightThickness) + .addField( + new KeyedCodec<>("UseBloomOnHighlight", Codec.BOOLEAN), (modelVFX, b) -> modelVFX.useBloomOnHighlight = b, modelVFX -> modelVFX.useBloomOnHighlight + ) + .addField( + new KeyedCodec<>("UseProgessiveHighlight", Codec.BOOLEAN), + (modelVFX, b) -> modelVFX.useProgressiveHighlight = b, + modelVFX -> modelVFX.useProgressiveHighlight + ) + .addField(new KeyedCodec<>("NoiseScale", ProtocolCodecs.VECTOR2F), (modelVFX, d) -> modelVFX.noiseScale = d, modelVFX -> modelVFX.noiseScale) + .addField( + new KeyedCodec<>("NoiseScrollSpeed", ProtocolCodecs.VECTOR2F), (modelVFX, d) -> modelVFX.noiseScrollSpeed = d, modelVFX -> modelVFX.noiseScrollSpeed + ) + .addField(new KeyedCodec<>("PostColor", ProtocolCodecs.COLOR), (modelVFX, o) -> modelVFX.postColor = o, modelVFX -> modelVFX.postColor) + .append(new KeyedCodec<>("PostColorOpacity", Codec.FLOAT), (modelVFX, d) -> modelVFX.postColorOpacity = d, modelVFX -> modelVFX.postColorOpacity) + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .build(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(ModelVFX.class, CODEC); + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + private static AssetStore> STORE; + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ModelVFX::getAssetStore)); + protected AssetExtraInfo.Data data; + protected String id; + @Nonnull + private SwitchTo switchTo = SwitchTo.Disappear; + @Nonnull + private EffectDirection effectDirection = EffectDirection.None; + private float animationDuration; + private Vector2f animationRange = new Vector2f(0.0F, 1.0F); + @Nonnull + private LoopOption loopOption = LoopOption.PlayOnce; + @Nonnull + private CurveType curveType = CurveType.Linear; + private Color highlightColor = new Color((byte)-1, (byte)-1, (byte)-1); + private float highlightThickness; + private boolean useBloomOnHighlight; + private boolean useProgressiveHighlight; + private Vector2f noiseScale = new Vector2f(50.0F, 50.0F); + private Vector2f noiseScrollSpeed; + private Color postColor = new Color((byte)-1, (byte)-1, (byte)-1); + private float postColorOpacity = 1.0F; + + public static AssetStore> getAssetStore() { + if (STORE == null) { + STORE = AssetRegistry.getAssetStore(ModelVFX.class); + } + + return STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public ModelVFX( + String id, + SwitchTo switchTo, + EffectDirection effectDirection, + float animationDuration, + Vector2f animationRange, + LoopOption loopOption, + CurveType curveType, + Color highlightColor, + float highlightThickness, + boolean useBloomOnHighlight, + boolean useProgressiveHighlight, + Vector2f noiseScale, + Vector2f noiseScrollSpeed, + Color postColor, + float postColorOpacity + ) { + this.id = id; + this.switchTo = switchTo; + this.effectDirection = effectDirection; + this.animationDuration = animationDuration; + this.animationRange = animationRange; + this.loopOption = loopOption; + this.curveType = curveType; + this.highlightColor = highlightColor; + this.highlightThickness = highlightThickness; + this.useBloomOnHighlight = useBloomOnHighlight; + this.useProgressiveHighlight = useProgressiveHighlight; + this.noiseScale = noiseScale; + this.noiseScrollSpeed = noiseScrollSpeed; + this.postColor = postColor; + this.postColorOpacity = postColorOpacity; + } + + public ModelVFX(String id) { + this.id = id; + } + + protected ModelVFX() { + } + + @Nonnull + public com.hypixel.hytale.protocol.ModelVFX toPacket() { + com.hypixel.hytale.protocol.ModelVFX packet = new com.hypixel.hytale.protocol.ModelVFX(); + packet.id = this.id; + packet.switchTo = this.switchTo; + packet.effectDirection = this.effectDirection; + packet.animationDuration = this.animationDuration; + if (this.animationRange != null) { + packet.animationRange = this.animationRange; + } + + packet.loopOption = this.loopOption; + packet.curveType = this.curveType; + packet.highlightColor = this.highlightColor; + packet.useBloomOnHighlight = this.useBloomOnHighlight; + packet.useProgessiveHighlight = this.useProgressiveHighlight; + packet.highlightThickness = this.highlightThickness; + if (this.noiseScale != null) { + packet.noiseScale = this.noiseScale; + } + + if (this.noiseScrollSpeed != null) { + packet.noiseScrollSpeed = this.noiseScrollSpeed; + } + + packet.postColor = this.postColor; + packet.postColorOpacity = this.postColorOpacity; + return packet; + } + + public String getId() { + return this.id; + } + + public SwitchTo getSwitchTo() { + return this.switchTo; + } + + public EffectDirection getEffectDirection() { + return this.effectDirection; + } + + public float getAnimationDuration() { + return this.animationDuration; + } + + public Vector2f getAnimationRange() { + return this.animationRange; + } + + public LoopOption getLoopOption() { + return this.loopOption; + } + + public CurveType getCurveType() { + return this.curveType; + } + + public Color getHighlightColor() { + return this.highlightColor; + } + + public boolean useBloomOnHighlight() { + return this.useBloomOnHighlight; + } + + public boolean useProgessiveHighlight() { + return this.useProgressiveHighlight; + } + + public float getHighlightThickness() { + return this.highlightThickness; + } + + public Vector2f getNoiseScale() { + return this.noiseScale; + } + + public Vector2f getNoiseScrollSpeed() { + return this.noiseScrollSpeed; + } + + public Color getPostColor() { + return this.postColor; + } + + public float getPostColorOpacity() { + return this.postColorOpacity; + } + + @Nonnull + @Override + public String toString() { + return "ModelVFX{id='" + + this.id + + "'SwitchTo=" + + this.switchTo + + ", effectDirection=" + + this.effectDirection + + ", animationDuration=" + + this.animationDuration + + ", animationRange=" + + this.animationRange + + ", loopOption=" + + this.loopOption + + ", curveType=" + + this.curveType + + ", highlightColor='" + + this.highlightColor + + "', useBloomOnHighlight=" + + this.useBloomOnHighlight + + ", useProgressiveHighlight=" + + this.useProgressiveHighlight + + ", highlightThickness=" + + this.highlightThickness + + ", noiseScale=" + + this.noiseScale + + ", noiseScrollSpeed" + + this.noiseScrollSpeed + + ", postColor='" + + this.postColor + + "', postColorOpacity" + + this.postColorOpacity + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/particle/ParticleSpawnerPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/particle/ParticleSpawnerPacketGenerator.java new file mode 100644 index 0000000..5219809 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/particle/ParticleSpawnerPacketGenerator.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.server.core.asset.type.particle; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateParticleSpawners; +import com.hypixel.hytale.server.core.asset.packet.DefaultAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSpawner; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class ParticleSpawnerPacketGenerator extends DefaultAssetPacketGenerator { + public ParticleSpawnerPacketGenerator() { + } + + @Nonnull + @Override + public Packet generateInitPacket(DefaultAssetMap assetMap, @Nonnull Map assets) { + UpdateParticleSpawners packet = new UpdateParticleSpawners(); + packet.type = UpdateType.Init; + packet.particleSpawners = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + packet.particleSpawners.put(entry.getKey(), entry.getValue().toPacket()); + } + + return packet; + } + + @Nonnull + @Override + public Packet generateUpdatePacket(@Nonnull Map loadedAssets) { + UpdateParticleSpawners packet = new UpdateParticleSpawners(); + packet.type = UpdateType.AddOrUpdate; + packet.particleSpawners = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + packet.particleSpawners.put(entry.getKey(), entry.getValue().toPacket()); + } + + return packet; + } + + @Nonnull + @Override + public Packet generateRemovePacket(@Nonnull Set removed) { + UpdateParticleSpawners packet = new UpdateParticleSpawners(); + packet.type = UpdateType.Remove; + packet.removedParticleSpawners = removed.toArray(String[]::new); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/particle/ParticleSystemPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/particle/ParticleSystemPacketGenerator.java new file mode 100644 index 0000000..e869720 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/particle/ParticleSystemPacketGenerator.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.server.core.asset.type.particle; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateParticleSystems; +import com.hypixel.hytale.server.core.asset.packet.DefaultAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class ParticleSystemPacketGenerator extends DefaultAssetPacketGenerator { + public ParticleSystemPacketGenerator() { + } + + @Nonnull + @Override + public Packet generateInitPacket(DefaultAssetMap assetMap, @Nonnull Map assets) { + UpdateParticleSystems packet = new UpdateParticleSystems(); + packet.type = UpdateType.Init; + packet.particleSystems = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + packet.particleSystems.put(entry.getKey(), entry.getValue().toPacket()); + } + + return packet; + } + + @Nonnull + @Override + public Packet generateUpdatePacket(@Nonnull Map loadedAssets) { + UpdateParticleSystems packet = new UpdateParticleSystems(); + packet.type = UpdateType.AddOrUpdate; + packet.particleSystems = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + packet.particleSystems.put(entry.getKey(), entry.getValue().toPacket()); + } + + return packet; + } + + @Nonnull + @Override + public Packet generateRemovePacket(@Nonnull Set removed) { + UpdateParticleSystems packet = new UpdateParticleSystems(); + packet.type = UpdateType.Remove; + packet.removedParticleSystems = removed.toArray(String[]::new); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/particle/commands/ParticleCommand.java b/src/com/hypixel/hytale/server/core/asset/type/particle/commands/ParticleCommand.java new file mode 100644 index 0000000..c433837 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/particle/commands/ParticleCommand.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.core.asset.type.particle.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class ParticleCommand extends AbstractCommandCollection { + public ParticleCommand() { + super("particle", "server.commands.particle.desc"); + this.addSubCommand(new ParticleSpawnCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/particle/commands/ParticleSpawnCommand.java b/src/com/hypixel/hytale/server/core/asset/type/particle/commands/ParticleSpawnCommand.java new file mode 100644 index 0000000..563d359 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/particle/commands/ParticleSpawnCommand.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.asset.type.particle.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; +import com.hypixel.hytale.server.core.asset.type.particle.pages.ParticleSpawnPage; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.ParticleUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ParticleSpawnCommand extends AbstractTargetPlayerCommand { + @Nonnull + private final RequiredArg particleSystemArg = this.withRequiredArg( + "particle", "server.commands.particle.spawn.particle.desc", ArgTypes.PARTICLE_SYSTEM + ); + + public ParticleSpawnCommand() { + super("spawn", "server.commands.particle.spawn.desc"); + this.addUsageVariant(new ParticleSpawnCommand.ParticleSpawnPageCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + ParticleSystem particleSystem = this.particleSystemArg.get(context); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get().getPlayerSpatialResourceType()); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(position, 75.0, results); + ParticleUtil.spawnParticleEffect(particleSystem.getId(), position, transformComponent.getRotation(), results, store); + } + + private static class ParticleSpawnPageCommand extends AbstractTargetPlayerCommand { + public ParticleSpawnPageCommand() { + super("server.commands.particle.spawn.page.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getPageManager().openCustomPage(ref, store, new ParticleSpawnPage(playerRef)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/particle/config/Particle.java b/src/com/hypixel/hytale/server/core/asset/type/particle/config/Particle.java new file mode 100644 index 0000000..43e3e72 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/particle/config/Particle.java @@ -0,0 +1,224 @@ +package com.hypixel.hytale.server.core.asset.type.particle.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.map.Int2ObjectMapCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDefaultCollapsedState; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorSectionStart; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.codec.validation.validator.MapKeyValidator; +import com.hypixel.hytale.protocol.ParticleScaleRatioConstraint; +import com.hypixel.hytale.protocol.ParticleUVOption; +import com.hypixel.hytale.protocol.Size; +import com.hypixel.hytale.protocol.SoftParticle; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; +import javax.annotation.Nonnull; + +public class Particle implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(Particle.class, Particle::new) + .append(new KeyedCodec<>("Texture", Codec.STRING), (particle, s) -> particle.texture = s, particle -> particle.texture) + .addValidator(Validators.nonNull()) + .addValidator(CommonAssetValidator.TEXTURE_PARTICLES) + .metadata(new UIEditorSectionStart("Material")) + .add() + .addField(new KeyedCodec<>("FrameSize", ProtocolCodecs.SIZE), (particle, o) -> particle.frameSize = o, particle -> particle.frameSize) + .append( + new KeyedCodec<>("SoftParticles", new EnumCodec<>(SoftParticle.class)), (particle, o) -> particle.softParticle = o, particle -> particle.softParticle + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("SoftParticlesFadeFactor", Codec.FLOAT), + (particle, f) -> particle.softParticlesFadeFactor = f, + particle -> particle.softParticlesFadeFactor + ) + .addValidator(Validators.range(0.1F, 2.0F)) + .add() + .appendInherited( + new KeyedCodec<>("UseSpriteBlending", Codec.BOOLEAN), + (particle, s) -> particle.useSpriteBlending = s, + particle -> particle.useSpriteBlending, + (particle, parent) -> particle.useSpriteBlending = parent.useSpriteBlending + ) + .add() + .>append( + new KeyedCodec<>("Animation", new Int2ObjectMapCodec<>(ParticleAnimationFrame.CODEC, Int2ObjectOpenHashMap::new)), + (particle, o) -> particle.animation = o, + particle -> particle.animation + ) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyMap()) + .addValidator(new MapKeyValidator<>(Validators.range(0, 100))) + .metadata(new UIEditorSectionStart("Animation")) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .append( + new KeyedCodec<>("CollisionAnimationFrame", ParticleAnimationFrame.CODEC), + (particle, o) -> particle.collisionAnimationFrame = o, + particle -> particle.collisionAnimationFrame + ) + .add() + .append( + new KeyedCodec<>("UVOption", new EnumCodec<>(ParticleUVOption.class)), (particle, o) -> particle.uvOption = o, particle -> particle.uvOption + ) + .addValidator(Validators.nonNull()) + .metadata(new UIEditorSectionStart("Initial Frame")) + .add() + .append( + new KeyedCodec<>("ScaleRatioConstraint", new EnumCodec<>(ParticleScaleRatioConstraint.class)), + (particle, o) -> particle.scaleRatioConstraint = o, + particle -> particle.scaleRatioConstraint + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("InitialAnimationFrame", ParticleAnimationFrame.CODEC), + (particle, o) -> particle.initialAnimationFrame = o, + particle -> particle.initialAnimationFrame + ) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .build(); + protected String texture; + protected Size frameSize; + @Nonnull + protected ParticleUVOption uvOption = ParticleUVOption.None; + @Nonnull + protected ParticleScaleRatioConstraint scaleRatioConstraint = ParticleScaleRatioConstraint.OneToOne; + @Nonnull + protected SoftParticle softParticle = SoftParticle.Enable; + protected float softParticlesFadeFactor = 1.0F; + protected boolean useSpriteBlending; + protected ParticleAnimationFrame initialAnimationFrame; + protected ParticleAnimationFrame collisionAnimationFrame; + protected Int2ObjectMap animation; + + public Particle( + String texture, + Size frameSize, + ParticleUVOption uvOption, + ParticleScaleRatioConstraint scaleRatioConstraint, + SoftParticle softParticle, + float softParticlesFadeFactor, + boolean useSpriteBlending, + ParticleAnimationFrame initialAnimationFrame, + ParticleAnimationFrame collisionAnimationFrame, + Int2ObjectMap animation + ) { + this.texture = texture; + this.frameSize = frameSize; + this.uvOption = uvOption; + this.scaleRatioConstraint = scaleRatioConstraint; + this.softParticle = softParticle; + this.softParticlesFadeFactor = softParticlesFadeFactor; + this.useSpriteBlending = useSpriteBlending; + this.initialAnimationFrame = initialAnimationFrame; + this.collisionAnimationFrame = collisionAnimationFrame; + this.animation = animation; + } + + protected Particle() { + } + + public String getTexture() { + return this.texture; + } + + public Size getFrameSize() { + return this.frameSize; + } + + public ParticleUVOption getUvOption() { + return this.uvOption; + } + + public ParticleScaleRatioConstraint getScaleRatioConstraint() { + return this.scaleRatioConstraint; + } + + public SoftParticle getSoftParticle() { + return this.softParticle; + } + + public float getSoftParticlesFadeFactor() { + return this.softParticlesFadeFactor; + } + + public boolean isUseSpriteBlending() { + return this.useSpriteBlending; + } + + public ParticleAnimationFrame getInitialAnimationFrame() { + return this.initialAnimationFrame; + } + + public ParticleAnimationFrame getCollisionAnimationFrame() { + return this.collisionAnimationFrame; + } + + public Int2ObjectMap getAnimation() { + return this.animation; + } + + @Nonnull + public com.hypixel.hytale.protocol.Particle toPacket() { + com.hypixel.hytale.protocol.Particle packet = new com.hypixel.hytale.protocol.Particle(); + packet.texturePath = this.texture; + packet.frameSize = this.frameSize; + packet.uvOption = this.uvOption; + packet.scaleRatioConstraint = this.scaleRatioConstraint; + packet.softParticles = this.softParticle; + packet.softParticlesFadeFactor = this.softParticlesFadeFactor; + packet.useSpriteBlending = this.useSpriteBlending; + if (this.initialAnimationFrame != null) { + packet.initialAnimationFrame = this.initialAnimationFrame.toPacket(); + } + + if (this.collisionAnimationFrame != null) { + packet.collisionAnimationFrame = this.collisionAnimationFrame.toPacket(); + } + + if (this.animation != null) { + packet.animationFrames = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : this.animation.int2ObjectEntrySet()) { + packet.animationFrames.put(entry.getIntKey(), entry.getValue().toPacket()); + } + } + + return packet; + } + + @Nonnull + @Override + public String toString() { + return "Particle{texture='" + + this.texture + + "', frameSize=" + + this.frameSize + + ", uvOption=" + + this.uvOption + + ", scaleRatioConstraint=" + + this.scaleRatioConstraint + + ", softParticle=" + + this.softParticle + + ", softParticlesFadeFactor=" + + this.softParticlesFadeFactor + + ", useSpriteBlending=" + + this.useSpriteBlending + + ", initialAnimationFrame=" + + this.initialAnimationFrame + + ", collisionAnimationFrame=" + + this.collisionAnimationFrame + + ", animation=" + + this.animation + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleAnimationFrame.java b/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleAnimationFrame.java new file mode 100644 index 0000000..78ab840 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleAnimationFrame.java @@ -0,0 +1,99 @@ +package com.hypixel.hytale.server.core.asset.type.particle.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.Range; +import com.hypixel.hytale.protocol.RangeVector2f; +import com.hypixel.hytale.protocol.RangeVector3f; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class ParticleAnimationFrame implements NetworkSerializable { + public static final int UNASSIGNED_OPACITY = -1; + public static final BuilderCodec CODEC = BuilderCodec.builder(ParticleAnimationFrame.class, ParticleAnimationFrame::new) + .addField( + new KeyedCodec<>("FrameIndex", ProtocolCodecs.RANGE), + (animationFrame, s) -> animationFrame.frameIndex = s, + animationFrame -> animationFrame.frameIndex + ) + .addField( + new KeyedCodec<>("Scale", ProtocolCodecs.RANGE_VECTOR2F), (animationFrame, o) -> animationFrame.scale = o, animationFrame -> animationFrame.scale + ) + .addField( + new KeyedCodec<>("Rotation", ProtocolCodecs.RANGE_VECTOR3F), + (animationFrame, o) -> animationFrame.rotation = o, + animationFrame -> animationFrame.rotation + ) + .addField(new KeyedCodec<>("Color", ProtocolCodecs.COLOR), (animationFrame, o) -> animationFrame.color = o, animationFrame -> animationFrame.color) + .append(new KeyedCodec<>("Opacity", Codec.FLOAT), (animationFrame, f) -> animationFrame.opacity = f, animationFrame -> animationFrame.opacity) + .addValidator(Validators.or(Validators.range(0.0F, 1.0F), Validators.equal(-1.0F))) + .add() + .build(); + protected Range frameIndex; + protected RangeVector2f scale; + protected RangeVector3f rotation; + protected Color color; + protected float opacity = -1.0F; + + public ParticleAnimationFrame(Range frameIndex, RangeVector2f scale, RangeVector3f rotation, Color color, float opacity) { + this.frameIndex = frameIndex; + this.scale = scale; + this.rotation = rotation; + this.color = color; + this.opacity = opacity; + } + + protected ParticleAnimationFrame() { + } + + public Range getFrameIndex() { + return this.frameIndex; + } + + public RangeVector2f getScale() { + return this.scale; + } + + public RangeVector3f getRotation() { + return this.rotation; + } + + public Color getColor() { + return this.color; + } + + public float getOpacity() { + return this.opacity; + } + + @Nonnull + public com.hypixel.hytale.protocol.ParticleAnimationFrame toPacket() { + com.hypixel.hytale.protocol.ParticleAnimationFrame packet = new com.hypixel.hytale.protocol.ParticleAnimationFrame(); + packet.frameIndex = this.frameIndex; + packet.scale = this.scale; + packet.rotation = this.rotation; + packet.color = this.color; + packet.opacity = this.opacity; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "ParticleAnimationFrame{frameIndex=" + + this.frameIndex + + ", scale=" + + this.scale + + ", rotation=" + + this.rotation + + ", color=" + + this.color + + ", opacity=" + + this.opacity + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleAttractor.java b/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleAttractor.java new file mode 100644 index 0000000..9d2f660 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleAttractor.java @@ -0,0 +1,212 @@ +package com.hypixel.hytale.server.core.asset.type.particle.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class ParticleAttractor implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(ParticleAttractor.class, ParticleAttractor::new) + .addField( + new KeyedCodec<>("Position", ProtocolCodecs.VECTOR3F), + (particleAttractor, o) -> particleAttractor.position = o, + particleAttractor -> particleAttractor.position + ) + .addField( + new KeyedCodec<>("RadialAxis", ProtocolCodecs.VECTOR3F), + (particleAttractor, o) -> particleAttractor.radialAxis = o, + particleAttractor -> particleAttractor.radialAxis + ) + .append( + new KeyedCodec<>("TrailPositionMultiplier", Codec.FLOAT), + (particleAttractor, f) -> particleAttractor.trailPositionMultiplier = f, + particleAttractor -> particleAttractor.trailPositionMultiplier + ) + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .addField(new KeyedCodec<>("Radius", Codec.FLOAT), (particleAttractor, f) -> particleAttractor.radius = f, particleAttractor -> particleAttractor.radius) + .addField( + new KeyedCodec<>("RadialAcceleration", Codec.FLOAT), + (particleAttractor, f) -> particleAttractor.radialAcceleration = f, + particleAttractor -> particleAttractor.radialAcceleration + ) + .addField( + new KeyedCodec<>("RadialTangentAcceleration", Codec.FLOAT), + (particleAttractor, f) -> particleAttractor.radialTangentAcceleration = f, + particleAttractor -> particleAttractor.radialTangentAcceleration + ) + .addField( + new KeyedCodec<>("LinearAcceleration", ProtocolCodecs.VECTOR3F), + (particleAttractor, o) -> particleAttractor.linearAcceleration = o, + particleAttractor -> particleAttractor.linearAcceleration + ) + .addField( + new KeyedCodec<>("RadialImpulse", Codec.FLOAT), + (particleAttractor, f) -> particleAttractor.radialImpulse = f, + particleAttractor -> particleAttractor.radialImpulse + ) + .addField( + new KeyedCodec<>("RadialTangentImpulse", Codec.FLOAT), + (particleAttractor, f) -> particleAttractor.radialTangentImpulse = f, + particleAttractor -> particleAttractor.radialTangentImpulse + ) + .addField( + new KeyedCodec<>("LinearImpulse", ProtocolCodecs.VECTOR3F), + (particleAttractor, o) -> particleAttractor.linearImpulse = o, + particleAttractor -> particleAttractor.linearImpulse + ) + .addField( + new KeyedCodec<>("DampingMultiplier", ProtocolCodecs.VECTOR3F), + (particleAttractor, o) -> particleAttractor.dampingMultiplier = o, + particleAttractor -> particleAttractor.dampingMultiplier + ) + .build(); + protected Vector3f position; + protected Vector3f radialAxis; + protected float trailPositionMultiplier; + protected float radius; + protected float radialAcceleration; + protected float radialTangentAcceleration; + protected Vector3f linearAcceleration; + protected float radialImpulse; + protected float radialTangentImpulse; + protected Vector3f linearImpulse; + protected Vector3f dampingMultiplier; + + public ParticleAttractor( + Vector3f position, + Vector3f radialAxis, + float trailPositionMultiplier, + float radius, + float radialAcceleration, + float radialTangentAcceleration, + Vector3f linearAcceleration, + float radialImpulse, + float radialTangentImpulse, + Vector3f linearImpulse, + Vector3f dampingMultiplier + ) { + this.position = position; + this.radialAxis = radialAxis; + this.trailPositionMultiplier = trailPositionMultiplier; + this.radius = radius; + this.radialAcceleration = radialAcceleration; + this.radialTangentAcceleration = radialTangentAcceleration; + this.linearAcceleration = linearAcceleration; + this.radialImpulse = radialImpulse; + this.radialTangentImpulse = radialTangentImpulse; + this.linearImpulse = linearImpulse; + this.dampingMultiplier = dampingMultiplier; + } + + protected ParticleAttractor() { + } + + public Vector3f getPosition() { + return this.position; + } + + public Vector3f getRadialAxis() { + return this.radialAxis; + } + + public float getTrailPositionMultiplier() { + return this.trailPositionMultiplier; + } + + public float getRadius() { + return this.radius; + } + + public float getRadialAcceleration() { + return this.radialAcceleration; + } + + public float getRadialTangentAcceleration() { + return this.radialTangentAcceleration; + } + + public Vector3f getLinearAcceleration() { + return this.linearAcceleration; + } + + public float getRadialImpulse() { + return this.radialImpulse; + } + + public float getRadialTangentImpulse() { + return this.radialTangentImpulse; + } + + public Vector3f getLinearImpulse() { + return this.linearImpulse; + } + + public Vector3f getDampingMultiplier() { + return this.dampingMultiplier; + } + + @Nonnull + public com.hypixel.hytale.protocol.ParticleAttractor toPacket() { + com.hypixel.hytale.protocol.ParticleAttractor packet = new com.hypixel.hytale.protocol.ParticleAttractor(); + if (this.position != null) { + packet.position = this.position; + } + + if (this.radialAxis != null) { + packet.radialAxis = this.radialAxis; + } + + packet.trailPositionMultiplier = this.trailPositionMultiplier; + packet.radius = this.radius; + packet.radialAcceleration = this.radialAcceleration; + packet.radialTangentAcceleration = this.radialTangentAcceleration; + if (this.linearAcceleration != null) { + packet.linearAcceleration = this.linearAcceleration; + } + + packet.radialImpulse = this.radialImpulse; + packet.radialTangentImpulse = this.radialTangentImpulse; + if (this.linearImpulse != null) { + packet.linearImpulse = this.linearImpulse; + } + + if (this.dampingMultiplier != null) { + packet.dampingMultiplier = this.dampingMultiplier; + } + + return packet; + } + + @Nonnull + @Override + public String toString() { + return "ParticleAttractor{position=" + + this.position + + ", radialAxis=" + + this.radialAxis + + ", trailPositionMultiplier=" + + this.trailPositionMultiplier + + ", radius=" + + this.radius + + ", radialAcceleration=" + + this.radialAcceleration + + ", radialTangentAcceleration=" + + this.radialTangentAcceleration + + ", linearAcceleration=" + + this.linearAcceleration + + ", radialImpulse=" + + this.radialImpulse + + ", radialTangentImpulse=" + + this.radialTangentImpulse + + ", linearImpulse=" + + this.linearImpulse + + ", dampingMultiplier=" + + this.dampingMultiplier + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleCollision.java b/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleCollision.java new file mode 100644 index 0000000..8625359 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleCollision.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.server.core.asset.type.particle.config; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.ParticleCollisionAction; +import com.hypixel.hytale.protocol.ParticleCollisionBlockType; +import com.hypixel.hytale.protocol.ParticleRotationInfluence; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class ParticleCollision implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(ParticleCollision.class, ParticleCollision::new) + .append( + new KeyedCodec<>("BlockType", new EnumCodec<>(ParticleCollisionBlockType.class)), + (particleCollision, o) -> particleCollision.blockType = o, + particleCollision -> particleCollision.blockType + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("Action", new EnumCodec<>(ParticleCollisionAction.class)), + (particleCollision, o) -> particleCollision.action = o, + particleCollision -> particleCollision.action + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("ParticleRotationInfluence", new EnumCodec<>(ParticleRotationInfluence.class)), + (particleCollision, o) -> particleCollision.particleRotationInfluence = o, + particleCollision -> particleCollision.particleRotationInfluence + ) + .add() + .build(); + @Nonnull + private ParticleCollisionBlockType blockType = ParticleCollisionBlockType.None; + @Nonnull + private ParticleCollisionAction action = ParticleCollisionAction.Expire; + private ParticleRotationInfluence particleRotationInfluence; + + public ParticleCollision(ParticleCollisionBlockType blockType, ParticleCollisionAction action, ParticleRotationInfluence particleRotationInfluence) { + this.blockType = blockType; + this.action = action; + this.particleRotationInfluence = particleRotationInfluence; + } + + protected ParticleCollision() { + } + + public ParticleCollisionBlockType getParticleMapCollision() { + return this.blockType; + } + + public ParticleCollisionAction getType() { + return this.action; + } + + public ParticleRotationInfluence getParticleRotationInfluence() { + return this.particleRotationInfluence; + } + + @Nonnull + public com.hypixel.hytale.protocol.ParticleCollision toPacket() { + com.hypixel.hytale.protocol.ParticleCollision packet = new com.hypixel.hytale.protocol.ParticleCollision(); + packet.blockType = this.blockType; + packet.action = this.action; + packet.particleRotationInfluence = this.particleRotationInfluence != null ? this.particleRotationInfluence : ParticleRotationInfluence.None; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "ParticleCollision{blockType=" + + this.blockType + + ", action=" + + this.action + + ", particleRotationInfluence=" + + this.particleRotationInfluence + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleSpawner.java b/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleSpawner.java new file mode 100644 index 0000000..8027584 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleSpawner.java @@ -0,0 +1,582 @@ +package com.hypixel.hytale.server.core.asset.type.particle.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDefaultCollapsedState; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorSectionStart; +import com.hypixel.hytale.codec.schema.metadata.ui.UITypeIcon; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.protocol.EmitShape; +import com.hypixel.hytale.protocol.FXRenderMode; +import com.hypixel.hytale.protocol.InitialVelocity; +import com.hypixel.hytale.protocol.IntersectionHighlight; +import com.hypixel.hytale.protocol.ParticleRotationInfluence; +import com.hypixel.hytale.protocol.Range; +import com.hypixel.hytale.protocol.RangeVector3f; +import com.hypixel.hytale.protocol.Rangef; +import com.hypixel.hytale.protocol.UVMotion; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class ParticleSpawner + implements JsonAssetWithMap>, + NetworkSerializable { + public static final String PARTICLE_PATH = "Particles/"; + public static final String PARTICLE_EXTENSION = ".particle"; + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ParticleSpawner.class, + ParticleSpawner::new, + Codec.STRING, + (particleSpawner, s) -> particleSpawner.id = s, + particleSpawner -> particleSpawner.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .metadata(new UITypeIcon("ParticleSpawner.png")) + .appendInherited( + new KeyedCodec<>("Shape", new EnumCodec<>(EmitShape.class)), + (particleSpawner, s) -> particleSpawner.shape = s, + particleSpawner -> particleSpawner.shape, + (particleSpawner, parent) -> particleSpawner.shape = parent.shape + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("EmitOffset", ProtocolCodecs.RANGE_VECTOR3F), + (particleSpawner, s) -> particleSpawner.emitOffset = s, + particleSpawner -> particleSpawner.emitOffset, + (particleSpawner, parent) -> particleSpawner.emitOffset = parent.emitOffset + ) + .add() + .appendInherited( + new KeyedCodec<>("UseEmitDirection", Codec.BOOLEAN), + (particleSpawner, b) -> particleSpawner.useEmitDirection = b, + particleSpawner -> particleSpawner.useEmitDirection, + (particleSpawner, parent) -> particleSpawner.useEmitDirection = parent.useEmitDirection + ) + .documentation("Use spawn position to determine direction. Overrides pitch/yaw in InitialVelocity.") + .add() + .appendInherited( + new KeyedCodec<>("TotalParticles", ProtocolCodecs.RANGE), + (particleSpawner, s) -> particleSpawner.totalParticles = s, + particleSpawner -> particleSpawner.totalParticles, + (particleSpawner, parent) -> particleSpawner.totalParticles = parent.totalParticles + ) + .add() + .appendInherited( + new KeyedCodec<>("LifeSpan", Codec.FLOAT), + (particleSpawner, f) -> particleSpawner.lifeSpan = f, + particleSpawner -> particleSpawner.lifeSpan, + (particleSpawner, parent) -> particleSpawner.lifeSpan = parent.lifeSpan + ) + .add() + .appendInherited( + new KeyedCodec<>("MaxConcurrentParticles", Codec.INTEGER), + (particleSpawner, s) -> particleSpawner.maxConcurrentParticles = s, + particleSpawner -> particleSpawner.maxConcurrentParticles, + (particleSpawner, parent) -> particleSpawner.maxConcurrentParticles = parent.maxConcurrentParticles + ) + .add() + .appendInherited( + new KeyedCodec<>("ParticleLifeSpan", ProtocolCodecs.RANGEF), + (particleSpawner, s) -> particleSpawner.particleLifeSpan = s, + particleSpawner -> particleSpawner.particleLifeSpan, + (particleSpawner, parent) -> particleSpawner.particleLifeSpan = parent.particleLifeSpan + ) + .add() + .appendInherited( + new KeyedCodec<>("SpawnRate", ProtocolCodecs.RANGEF), + (particleSpawner, s) -> particleSpawner.spawnRate = s, + particleSpawner -> particleSpawner.spawnRate, + (particleSpawner, parent) -> particleSpawner.spawnRate = parent.spawnRate + ) + .add() + .appendInherited( + new KeyedCodec<>("SpawnBurst", Codec.BOOLEAN), + (particleSpawner, b) -> particleSpawner.spawnBurst = b, + particleSpawner -> particleSpawner.spawnBurst, + (particleSpawner, parent) -> particleSpawner.spawnRate = parent.spawnRate + ) + .add() + .append( + new KeyedCodec<>("WaveDelay", ProtocolCodecs.RANGEF), + (particleSpawner, b) -> particleSpawner.waveDelay = b, + particleSpawner -> particleSpawner.waveDelay + ) + .add() + .appendInherited( + new KeyedCodec<>("InitialVelocity", ProtocolCodecs.INITIAL_VELOCITY), + (particleSpawner, s) -> particleSpawner.initialVelocity = s, + particleSpawner -> particleSpawner.initialVelocity, + (particleSpawner, parent) -> particleSpawner.initialVelocity = parent.initialVelocity + ) + .add() + .appendInherited( + new KeyedCodec<>("ParticleRotationInfluence", new EnumCodec<>(ParticleRotationInfluence.class)), + (particleSpawner, s) -> particleSpawner.particleRotationInfluence = s, + particleSpawner -> particleSpawner.particleRotationInfluence, + (particleSpawner, parent) -> particleSpawner.particleRotationInfluence = parent.particleRotationInfluence + ) + .addValidator(Validators.nonNull()) + .metadata(new UIEditorSectionStart("Motion")) + .add() + .appendInherited( + new KeyedCodec<>("ParticleRotateWithSpawner", Codec.BOOLEAN), + (particleSpawner, s) -> particleSpawner.particleRotateWithSpawner = s, + particleSpawner -> particleSpawner.particleRotateWithSpawner, + (particleSpawner, parent) -> particleSpawner.particleRotateWithSpawner = parent.particleRotateWithSpawner + ) + .add() + .appendInherited( + new KeyedCodec<>("TrailSpawnerPositionMultiplier", Codec.FLOAT), + (particleSpawner, f) -> particleSpawner.trailSpawnerPositionMultiplier = f, + particleSpawner -> particleSpawner.trailSpawnerPositionMultiplier, + (particleSpawner, parent) -> particleSpawner.trailSpawnerPositionMultiplier = parent.trailSpawnerPositionMultiplier + ) + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .appendInherited( + new KeyedCodec<>("TrailSpawnerRotationMultiplier", Codec.FLOAT), + (particleSpawner, f) -> particleSpawner.trailSpawnerRotationMultiplier = f, + particleSpawner -> particleSpawner.trailSpawnerRotationMultiplier, + (particleSpawner, parent) -> particleSpawner.trailSpawnerRotationMultiplier = parent.trailSpawnerRotationMultiplier + ) + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .append( + new KeyedCodec<>("VelocityStretchMultiplier", Codec.FLOAT), + (particleSpawner, f) -> particleSpawner.velocityStretchMultiplier = f, + particleSpawner -> particleSpawner.velocityStretchMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("Attractors", new ArrayCodec<>(ParticleAttractor.CODEC, ParticleAttractor[]::new)), + (particleSpawner, o) -> particleSpawner.attractors = o, + particleSpawner -> particleSpawner.attractors, + (particleSpawner, parent) -> particleSpawner.attractors = parent.attractors + ) + .metadata(new UIEditorSectionStart("Attractors")) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("RenderMode", new EnumCodec<>(FXRenderMode.class)), + (particleSpawner, s) -> particleSpawner.renderMode = s, + particleSpawner -> particleSpawner.renderMode, + (particleSpawner, parent) -> particleSpawner.renderMode = parent.renderMode + ) + .addValidator(Validators.nonNull()) + .metadata(new UIEditorSectionStart("Material")) + .add() + .appendInherited( + new KeyedCodec<>("LightInfluence", Codec.FLOAT), + (particleSpawner, f) -> particleSpawner.lightInfluence = f, + particleSpawner -> particleSpawner.lightInfluence, + (particleSpawner, parent) -> particleSpawner.lightInfluence = parent.lightInfluence + ) + .add() + .appendInherited( + new KeyedCodec<>("IntersectionHighlight", ProtocolCodecs.INTERSECTION_HIGHLIGHT), + (particleSpawner, s) -> particleSpawner.intersectionHighlight = s, + particleSpawner -> particleSpawner.intersectionHighlight, + (particleSpawner, parent) -> particleSpawner.intersectionHighlight = parent.intersectionHighlight + ) + .add() + .appendInherited( + new KeyedCodec<>("LinearFiltering", Codec.BOOLEAN), + (particleSpawner, s) -> particleSpawner.linearFiltering = s, + particleSpawner -> particleSpawner.linearFiltering, + (particleSpawner, parent) -> particleSpawner.linearFiltering = parent.linearFiltering + ) + .add() + .appendInherited( + new KeyedCodec<>("UVMotion", ProtocolCodecs.UV_MOTION), + (particleSpawner, s) -> particleSpawner.uvMotion = s, + particleSpawner -> particleSpawner.uvMotion, + (particleSpawner, parent) -> particleSpawner.uvMotion = parent.uvMotion + ) + .add() + .append( + new KeyedCodec<>("CameraOffset", Codec.FLOAT), + (particleSpawner, f) -> particleSpawner.cameraOffset = f, + particleSpawner -> particleSpawner.cameraOffset + ) + .addValidator(Validators.range(-10.0F, 10.0F)) + .add() + .appendInherited( + new KeyedCodec<>("ParticleCollision", ParticleCollision.CODEC), + (particleSpawner, s) -> particleSpawner.particleCollision = s, + particleSpawner -> particleSpawner.particleCollision, + (particleSpawner, parent) -> particleSpawner.particleCollision = parent.particleCollision + ) + .metadata(new UIEditorSectionStart("Collision")) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("IsLowRes", Codec.BOOLEAN), + (particleSpawner, s) -> particleSpawner.isLowRes = s, + particleSpawner -> particleSpawner.isLowRes, + (particleSpawner, parent) -> particleSpawner.isLowRes = parent.isLowRes + ) + .metadata(new UIEditorSectionStart("Optimization")) + .add() + .appendInherited( + new KeyedCodec<>("Particle", Particle.CODEC), + (particleSpawner, o) -> particleSpawner.particle = o, + particleSpawner -> particleSpawner.particle, + (particleSpawner, parent) -> particleSpawner.particle = parent.particle + ) + .addValidator(Validators.nonNull()) + .metadata(new UIEditorSectionStart("Particle")) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ParticleSpawner::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected Particle particle; + @Nonnull + protected FXRenderMode renderMode = FXRenderMode.BlendLinear; + @Nonnull + protected EmitShape shape = EmitShape.Sphere; + protected RangeVector3f emitOffset; + protected boolean useEmitDirection; + protected float cameraOffset; + @Nonnull + protected ParticleRotationInfluence particleRotationInfluence = ParticleRotationInfluence.None; + protected boolean particleRotateWithSpawner; + protected boolean isLowRes; + protected float trailSpawnerPositionMultiplier; + protected float trailSpawnerRotationMultiplier; + protected ParticleCollision particleCollision; + protected float lightInfluence; + protected boolean linearFiltering; + protected Range totalParticles; + protected float lifeSpan; + protected int maxConcurrentParticles; + protected Rangef particleLifeSpan; + protected Rangef spawnRate; + protected boolean spawnBurst; + protected Rangef waveDelay; + protected InitialVelocity initialVelocity; + protected float velocityStretchMultiplier; + protected UVMotion uvMotion; + protected ParticleAttractor[] attractors; + protected IntersectionHighlight intersectionHighlight; + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ParticleSpawner.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public ParticleSpawner( + String id, + Particle particle, + FXRenderMode renderMode, + EmitShape shape, + RangeVector3f emitOffset, + boolean useEmitDirection, + float cameraOffset, + ParticleRotationInfluence particleRotationInfluence, + boolean particleRotateWithSpawner, + boolean isLowRes, + float trailSpawnerPositionMultiplier, + float trailSpawnerRotationMultiplier, + ParticleCollision particleCollision, + float lightInfluence, + boolean linearFiltering, + Range totalParticles, + float lifeSpan, + int maxConcurrentParticles, + Rangef particleLifeSpan, + Rangef spawnRate, + boolean spawnBurst, + Rangef waveDelay, + InitialVelocity initialVelocity, + float velocityStretchMultiplier, + UVMotion uvMotion, + ParticleAttractor[] attractors, + IntersectionHighlight intersectionHighlight + ) { + this.id = id; + this.particle = particle; + this.renderMode = renderMode; + this.shape = shape; + this.emitOffset = emitOffset; + this.useEmitDirection = useEmitDirection; + this.cameraOffset = cameraOffset; + this.particleRotationInfluence = particleRotationInfluence; + this.particleRotateWithSpawner = particleRotateWithSpawner; + this.isLowRes = isLowRes; + this.trailSpawnerPositionMultiplier = trailSpawnerPositionMultiplier; + this.trailSpawnerRotationMultiplier = trailSpawnerRotationMultiplier; + this.particleCollision = particleCollision; + this.lightInfluence = lightInfluence; + this.linearFiltering = linearFiltering; + this.totalParticles = totalParticles; + this.lifeSpan = lifeSpan; + this.maxConcurrentParticles = maxConcurrentParticles; + this.particleLifeSpan = particleLifeSpan; + this.spawnRate = spawnRate; + this.spawnBurst = spawnBurst; + this.waveDelay = waveDelay; + this.initialVelocity = initialVelocity; + this.velocityStretchMultiplier = velocityStretchMultiplier; + this.uvMotion = uvMotion; + this.attractors = attractors; + this.intersectionHighlight = intersectionHighlight; + } + + protected ParticleSpawner() { + } + + @Nonnull + public com.hypixel.hytale.protocol.ParticleSpawner toPacket() { + com.hypixel.hytale.protocol.ParticleSpawner cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.ParticleSpawner packet = new com.hypixel.hytale.protocol.ParticleSpawner(); + packet.id = this.id; + if (this.particle != null) { + packet.particle = this.particle.toPacket(); + } + + packet.shape = this.shape; + packet.renderMode = this.renderMode; + packet.emitOffset = this.emitOffset; + packet.useEmitDirection = this.useEmitDirection; + packet.cameraOffset = this.cameraOffset; + packet.particleRotationInfluence = this.particleRotationInfluence; + packet.particleRotateWithSpawner = this.particleRotateWithSpawner; + packet.isLowRes = this.isLowRes; + packet.trailSpawnerPositionMultiplier = this.trailSpawnerPositionMultiplier; + packet.trailSpawnerRotationMultiplier = this.trailSpawnerRotationMultiplier; + if (this.particleCollision != null) { + packet.particleCollision = this.particleCollision.toPacket(); + if (this.particleCollision.getParticleRotationInfluence() == null) { + packet.particleCollision.particleRotationInfluence = this.particleRotationInfluence; + } + } + + packet.lightInfluence = this.lightInfluence; + packet.linearFiltering = this.linearFiltering; + packet.totalParticles = this.totalParticles; + packet.lifeSpan = this.lifeSpan; + packet.maxConcurrentParticles = this.maxConcurrentParticles; + if (this.particleLifeSpan != null) { + packet.particleLifeSpan = this.particleLifeSpan; + } + + if (this.spawnRate != null) { + packet.spawnRate = this.spawnRate; + } + + packet.spawnBurst = this.spawnBurst; + if (this.waveDelay != null) { + packet.waveDelay = this.waveDelay; + } + + packet.initialVelocity = this.initialVelocity; + packet.velocityStretchMultiplier = this.velocityStretchMultiplier; + packet.uvMotion = this.uvMotion; + if (this.attractors != null && this.attractors.length > 0) { + packet.attractors = ArrayUtil.copyAndMutate(this.attractors, ParticleAttractor::toPacket, com.hypixel.hytale.protocol.ParticleAttractor[]::new); + } + + packet.intersectionHighlight = this.intersectionHighlight; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + public String getId() { + return this.id; + } + + public Particle getParticle() { + return this.particle; + } + + public FXRenderMode getRenderMode() { + return this.renderMode; + } + + public EmitShape getShape() { + return this.shape; + } + + public RangeVector3f getEmitOffset() { + return this.emitOffset; + } + + public boolean getUseEmitDirection() { + return this.useEmitDirection; + } + + public float getCameraOffset() { + return this.cameraOffset; + } + + public ParticleRotationInfluence getParticleRotationInfluence() { + return this.particleRotationInfluence; + } + + public boolean isParticleRotateWithSpawner() { + return this.particleRotateWithSpawner; + } + + public boolean isLowRes() { + return this.isLowRes; + } + + public float getTrailSpawnerPositionMultiplier() { + return this.trailSpawnerPositionMultiplier; + } + + public float getTrailSpawnerRotationMultiplier() { + return this.trailSpawnerRotationMultiplier; + } + + public ParticleCollision getParticleCollision() { + return this.particleCollision; + } + + public float getLightInfluence() { + return this.lightInfluence; + } + + public boolean isLinearFiltering() { + return this.linearFiltering; + } + + public Range getTotalParticles() { + return this.totalParticles; + } + + public float getLifeSpan() { + return this.lifeSpan; + } + + public int getMaxConcurrentParticles() { + return this.maxConcurrentParticles; + } + + public Rangef getParticleLifeSpan() { + return this.particleLifeSpan; + } + + public Rangef getSpawnRate() { + return this.spawnRate; + } + + public boolean isSpawnBurst() { + return this.spawnBurst; + } + + public Rangef getWaveDelay() { + return this.waveDelay; + } + + public InitialVelocity getInitialVelocity() { + return this.initialVelocity; + } + + public float getVelocityStretchMultiplier() { + return this.velocityStretchMultiplier; + } + + public UVMotion getUVMotion() { + return this.uvMotion; + } + + public ParticleAttractor[] getAttractors() { + return this.attractors; + } + + public IntersectionHighlight getIntersectionHighlight() { + return this.intersectionHighlight; + } + + @Nonnull + @Override + public String toString() { + return "ParticleSpawner{id='" + + this.id + + "', particle='" + + this.particle + + ", renderMode=" + + this.renderMode + + ", shape=" + + this.shape + + ", emitOffset=" + + this.emitOffset + + ", useEmitDirection=" + + this.useEmitDirection + + ", cameraOffset=" + + this.cameraOffset + + ", particleRotationInfluence=" + + this.particleRotationInfluence + + ", particleRotateWithSpawner=" + + this.particleRotateWithSpawner + + ", isLowRes=" + + this.isLowRes + + ", trailSpawnerPositionMultiplier=" + + this.trailSpawnerPositionMultiplier + + ", trailSpawnerRotationMultiplier=" + + this.trailSpawnerRotationMultiplier + + ", particleCollision=" + + this.particleCollision + + ", lightInfluence=" + + this.lightInfluence + + ", linearFiltering=" + + this.linearFiltering + + ", totalParticles=" + + this.totalParticles + + ", lifeSpan=" + + this.lifeSpan + + ", maxConcurrentParticles=" + + this.maxConcurrentParticles + + ", particleLifeSpan=" + + this.particleLifeSpan + + ", spawnRate=" + + this.spawnRate + + ", spawnBurst=" + + this.spawnBurst + + ", waveDelay=" + + this.waveDelay + + ", initialVelocity=" + + this.initialVelocity + + ", velocityStretchMultiplier=" + + this.velocityStretchMultiplier + + ", uvMotion=" + + this.uvMotion + + ", attractors=" + + Arrays.toString((Object[])this.attractors) + + ", intersectionHighlight" + + this.intersectionHighlight + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleSpawnerGroup.java b/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleSpawnerGroup.java new file mode 100644 index 0000000..04a7a8f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleSpawnerGroup.java @@ -0,0 +1,247 @@ +package com.hypixel.hytale.server.core.asset.type.particle.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.InitialVelocity; +import com.hypixel.hytale.protocol.RangeVector3f; +import com.hypixel.hytale.protocol.Rangef; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class ParticleSpawnerGroup implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(ParticleSpawnerGroup.class, ParticleSpawnerGroup::new) + .append( + new KeyedCodec<>("SpawnerId", Codec.STRING), + (particleSpawnerGroup, s) -> particleSpawnerGroup.spawnerId = s, + particleSpawnerGroup -> particleSpawnerGroup.spawnerId + ) + .addValidator(ParticleSpawner.VALIDATOR_CACHE.getValidator()) + .add() + .addField( + new KeyedCodec<>("PositionOffset", ProtocolCodecs.VECTOR3F), + (particleSpawnerGroup, o) -> particleSpawnerGroup.positionOffset = o, + particleSpawnerGroup -> particleSpawnerGroup.positionOffset + ) + .addField( + new KeyedCodec<>("RotationOffset", ProtocolCodecs.DIRECTION), + (particleSpawnerGroup, o) -> particleSpawnerGroup.rotationOffset = o, + particleSpawnerGroup -> particleSpawnerGroup.rotationOffset + ) + .addField( + new KeyedCodec<>("FixedRotation", Codec.BOOLEAN), + (particleSpawnerGroup, b) -> particleSpawnerGroup.fixedRotation = b, + particleSpawnerGroup -> particleSpawnerGroup.fixedRotation + ) + .addField( + new KeyedCodec<>("SpawnRate", ProtocolCodecs.RANGEF), + (particleSpawnerGroup, b) -> particleSpawnerGroup.spawnRate = b, + particleSpawnerGroup -> particleSpawnerGroup.spawnRate + ) + .addField( + new KeyedCodec<>("LifeSpan", ProtocolCodecs.RANGEF), + (particleSpawnerGroup, b) -> particleSpawnerGroup.lifeSpan = b, + particleSpawnerGroup -> particleSpawnerGroup.lifeSpan + ) + .addField( + new KeyedCodec<>("StartDelay", Codec.FLOAT), (particleSpawner, f) -> particleSpawner.startDelay = f, particleSpawner -> particleSpawner.startDelay + ) + .addField( + new KeyedCodec<>("WaveDelay", ProtocolCodecs.RANGEF), + (particleSpawnerGroup, b) -> particleSpawnerGroup.waveDelay = b, + particleSpawnerGroup -> particleSpawnerGroup.waveDelay + ) + .addField( + new KeyedCodec<>("TotalSpawners", Codec.INTEGER), + (particleSpawnerGroup, i) -> particleSpawnerGroup.totalSpawners = i, + particleSpawnerGroup -> particleSpawnerGroup.totalSpawners + ) + .addField( + new KeyedCodec<>("MaxConcurrent", Codec.INTEGER), + (particleSpawnerGroup, i) -> particleSpawnerGroup.maxConcurrent = i, + particleSpawnerGroup -> particleSpawnerGroup.maxConcurrent + ) + .addField( + new KeyedCodec<>("InitialVelocity", ProtocolCodecs.INITIAL_VELOCITY), + (particleSpawnerGroup, o) -> particleSpawnerGroup.initialVelocity = o, + particleSpawnerGroup -> particleSpawnerGroup.initialVelocity + ) + .addField( + new KeyedCodec<>("EmitOffset", ProtocolCodecs.RANGE_VECTOR3F), + (particleSpawnerGroup, o) -> particleSpawnerGroup.emitOffset = o, + particleSpawnerGroup -> particleSpawnerGroup.emitOffset + ) + .addField( + new KeyedCodec<>("Attractors", new ArrayCodec<>(ParticleAttractor.CODEC, ParticleAttractor[]::new)), + (particleSpawnerGroup, o) -> particleSpawnerGroup.attractors = o, + particleSpawnerGroup -> particleSpawnerGroup.attractors + ) + .build(); + protected String spawnerId; + protected Vector3f positionOffset; + protected Direction rotationOffset; + protected boolean fixedRotation; + protected Rangef spawnRate; + protected Rangef lifeSpan; + protected float startDelay; + protected Rangef waveDelay; + protected int totalSpawners = 1; + protected int maxConcurrent; + protected InitialVelocity initialVelocity; + protected RangeVector3f emitOffset; + protected ParticleAttractor[] attractors; + + public ParticleSpawnerGroup( + String spawnerId, + Vector3f positionOffset, + Direction rotationOffset, + boolean fixedRotation, + Rangef spawnRate, + Rangef lifeSpan, + float startDelay, + Rangef waveDelay, + int totalSpawners, + int maxConcurrent, + InitialVelocity initialVelocity, + RangeVector3f emitOffset, + ParticleAttractor[] attractors + ) { + this.spawnerId = spawnerId; + this.positionOffset = positionOffset; + this.rotationOffset = rotationOffset; + this.fixedRotation = fixedRotation; + this.spawnRate = spawnRate; + this.startDelay = startDelay; + this.lifeSpan = lifeSpan; + this.waveDelay = waveDelay; + this.totalSpawners = totalSpawners; + this.maxConcurrent = maxConcurrent; + this.initialVelocity = initialVelocity; + this.emitOffset = emitOffset; + this.attractors = attractors; + } + + protected ParticleSpawnerGroup() { + } + + @Nonnull + public com.hypixel.hytale.protocol.ParticleSpawnerGroup toPacket() { + com.hypixel.hytale.protocol.ParticleSpawnerGroup packet = new com.hypixel.hytale.protocol.ParticleSpawnerGroup(); + packet.spawnerId = this.spawnerId; + packet.positionOffset = this.positionOffset; + packet.rotationOffset = this.rotationOffset; + if (this.spawnRate != null) { + packet.spawnRate = this.spawnRate; + } + + if (this.lifeSpan != null) { + packet.lifeSpan = this.lifeSpan; + } + + if (this.waveDelay != null) { + packet.waveDelay = this.waveDelay; + } + + packet.startDelay = this.startDelay; + packet.maxConcurrent = this.maxConcurrent; + packet.totalSpawners = this.totalSpawners; + packet.initialVelocity = this.initialVelocity; + packet.emitOffset = this.emitOffset; + if (this.attractors != null && this.attractors.length > 0) { + packet.attractors = ArrayUtil.copyAndMutate(this.attractors, ParticleAttractor::toPacket, com.hypixel.hytale.protocol.ParticleAttractor[]::new); + } + + packet.fixedRotation = this.fixedRotation; + return packet; + } + + public String getSpawnerId() { + return this.spawnerId; + } + + public Vector3f getPositionOffset() { + return this.positionOffset; + } + + public Direction getRotationOffset() { + return this.rotationOffset; + } + + public boolean isFixedRotation() { + return this.fixedRotation; + } + + public Rangef getSpawnRate() { + return this.spawnRate; + } + + public Rangef getLifeSpan() { + return this.lifeSpan; + } + + public float getStartDelay() { + return this.startDelay; + } + + public Rangef getWaveDelay() { + return this.waveDelay; + } + + public int getTotalSpawners() { + return this.totalSpawners; + } + + public int getMaxConcurrent() { + return this.maxConcurrent; + } + + public InitialVelocity getInitialVelocity() { + return this.initialVelocity; + } + + public RangeVector3f getEmitOffset() { + return this.emitOffset; + } + + public ParticleAttractor[] getAttractors() { + return this.attractors; + } + + @Nonnull + @Override + public String toString() { + return "ParticleSpawnerGroup{spawnerId='" + + this.spawnerId + + "', positionOffset=" + + this.positionOffset + + ", rotationOffset=" + + this.rotationOffset + + ", fixedRotation=" + + this.fixedRotation + + ", spawnRate=" + + this.spawnRate + + ", lifeSpan=" + + this.lifeSpan + + ", startDelay=" + + this.startDelay + + ", waveDelay=" + + this.waveDelay + + ", totalSpawners=" + + this.totalSpawners + + ", maxConcurrent=" + + this.maxConcurrent + + ", initialVelocity=" + + this.initialVelocity + + ", emitOffset=" + + this.emitOffset + + ", attractors=" + + Arrays.toString((Object[])this.attractors) + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleSystem.java b/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleSystem.java new file mode 100644 index 0000000..530e7cf --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/particle/config/ParticleSystem.java @@ -0,0 +1,171 @@ +package com.hypixel.hytale.server.core.asset.type.particle.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDefaultCollapsedState; +import com.hypixel.hytale.codec.schema.metadata.ui.UITypeIcon; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class ParticleSystem + implements JsonAssetWithMap>, + NetworkSerializable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ParticleSystem.class, + ParticleSystem::new, + Codec.STRING, + (particleSystem, s) -> particleSystem.id = s, + particleSystem -> particleSystem.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .metadata(new UITypeIcon("ParticleSystem.png")) + .appendInherited( + new KeyedCodec<>("Spawners", new ArrayCodec<>(ParticleSpawnerGroup.CODEC, ParticleSpawnerGroup[]::new)), + (particleSystem, o) -> particleSystem.spawners = o, + particleSystem -> particleSystem.spawners, + (particleSystem, parent) -> particleSystem.spawners = parent.spawners + ) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .addValidator(Validators.nonEmptyArray()) + .add() + .appendInherited( + new KeyedCodec<>("LifeSpan", Codec.FLOAT), + (particleSystem, f) -> particleSystem.lifeSpan = f, + particleSystem -> particleSystem.lifeSpan, + (particleSystem, parent) -> particleSystem.lifeSpan = parent.lifeSpan + ) + .add() + .appendInherited( + new KeyedCodec<>("CullDistance", Codec.FLOAT), + (particleSystem, f) -> particleSystem.cullDistance = f, + particleSystem -> particleSystem.cullDistance, + (particleSystem, parent) -> particleSystem.cullDistance = parent.cullDistance + ) + .add() + .appendInherited( + new KeyedCodec<>("BoundingRadius", Codec.FLOAT), + (particleSystem, f) -> particleSystem.boundingRadius = f, + particleSystem -> particleSystem.boundingRadius, + (particleSystem, parent) -> particleSystem.boundingRadius = parent.boundingRadius + ) + .add() + .appendInherited( + new KeyedCodec<>("IsImportant", Codec.BOOLEAN), + (particleSystem, b) -> particleSystem.isImportant = b, + particleSystem -> particleSystem.isImportant, + (particleSystem, parent) -> particleSystem.isImportant = parent.isImportant + ) + .add() + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ParticleSystem::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected float lifeSpan; + protected ParticleSpawnerGroup[] spawners; + protected float cullDistance; + protected float boundingRadius; + protected boolean isImportant; + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ParticleSystem.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public ParticleSystem(String id, float lifeSpan, ParticleSpawnerGroup[] spawners, float cullDistance, float boundingRadius, boolean isImportant) { + this.id = id; + this.lifeSpan = lifeSpan; + this.spawners = spawners; + this.cullDistance = cullDistance; + this.boundingRadius = boundingRadius; + this.isImportant = isImportant; + } + + protected ParticleSystem() { + } + + @Nonnull + public com.hypixel.hytale.protocol.ParticleSystem toPacket() { + com.hypixel.hytale.protocol.ParticleSystem cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.ParticleSystem packet = new com.hypixel.hytale.protocol.ParticleSystem(); + packet.id = this.id; + packet.lifeSpan = this.lifeSpan; + if (this.spawners != null && this.spawners.length > 0) { + packet.spawners = ArrayUtil.copyAndMutate(this.spawners, ParticleSpawnerGroup::toPacket, com.hypixel.hytale.protocol.ParticleSpawnerGroup[]::new); + } + + packet.cullDistance = this.cullDistance; + packet.boundingRadius = this.boundingRadius; + packet.isImportant = this.isImportant; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + public String getId() { + return this.id; + } + + public float getLifeSpan() { + return this.lifeSpan; + } + + public ParticleSpawnerGroup[] getSpawners() { + return this.spawners; + } + + public float getCullDistance() { + return this.cullDistance; + } + + public float getBoundingRadius() { + return this.boundingRadius; + } + + public boolean isImportant() { + return this.isImportant; + } + + @Nonnull + @Override + public String toString() { + return "ParticleSystem{id='" + + this.id + + "', lifeSpan=" + + this.lifeSpan + + ", spawners=" + + Arrays.toString((Object[])this.spawners) + + ", cullDistance=" + + this.cullDistance + + ", boundingRadius=" + + this.boundingRadius + + ", isImportant=" + + this.isImportant + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/particle/config/WorldParticle.java b/src/com/hypixel/hytale/server/core/asset/type/particle/config/WorldParticle.java new file mode 100644 index 0000000..88baa9f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/particle/config/WorldParticle.java @@ -0,0 +1,109 @@ +package com.hypixel.hytale.server.core.asset.type.particle.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class WorldParticle implements NetworkSerializable { + public static final String SYSTEM_ID_DOC = "The id of the particle system."; + public static final String COLOR_DOC = "The colour used if none was specified in the particle settings."; + public static final String SCALE_DOC = "The scale of the particle system."; + public static final String POSITION_OFFSET_DOC = "The position offset from the spawn position."; + public static final String ROTATION_OFFSET_DOC = "The rotation offset from the spawn rotation."; + public static final BuilderCodec CODEC = BuilderCodec.builder(WorldParticle.class, WorldParticle::new) + .documentation("Particle System that can be spawned in the world.") + .append(new KeyedCodec<>("SystemId", Codec.STRING), (particle, s) -> particle.systemId = s, particle -> particle.systemId) + .documentation("The id of the particle system.") + .addValidator(Validators.nonNull()) + .addValidator(ParticleSystem.VALIDATOR_CACHE.getValidator()) + .add() + .append(new KeyedCodec<>("Color", ProtocolCodecs.COLOR), (particle, o) -> particle.color = o, particle -> particle.color) + .documentation("The colour used if none was specified in the particle settings.") + .add() + .append(new KeyedCodec<>("Scale", Codec.FLOAT), (particle, f) -> particle.scale = f, particle -> particle.scale) + .documentation("The scale of the particle system.") + .add() + .append( + new KeyedCodec<>("PositionOffset", ProtocolCodecs.VECTOR3F), (particle, s) -> particle.positionOffset = s, particle -> particle.positionOffset + ) + .documentation("The position offset from the spawn position.") + .add() + .append( + new KeyedCodec<>("RotationOffset", ProtocolCodecs.DIRECTION), (particle, s) -> particle.rotationOffset = s, particle -> particle.rotationOffset + ) + .documentation("The rotation offset from the spawn rotation.") + .add() + .build(); + public static final ArrayCodec ARRAY_CODEC = new ArrayCodec<>(CODEC, WorldParticle[]::new); + protected String systemId; + protected Color color; + protected float scale = 1.0F; + protected Vector3f positionOffset; + protected Direction rotationOffset; + + public WorldParticle(String systemId, Color color, float scale, Vector3f positionOffset, Direction rotationOffset) { + this.systemId = systemId; + this.color = color; + this.scale = scale; + this.positionOffset = positionOffset; + this.rotationOffset = rotationOffset; + } + + protected WorldParticle() { + } + + public String getSystemId() { + return this.systemId; + } + + public Color getColor() { + return this.color; + } + + public float getScale() { + return this.scale; + } + + public Vector3f getPositionOffset() { + return this.positionOffset; + } + + public Direction getRotationOffset() { + return this.rotationOffset; + } + + @Nonnull + public com.hypixel.hytale.protocol.WorldParticle toPacket() { + com.hypixel.hytale.protocol.WorldParticle packet = new com.hypixel.hytale.protocol.WorldParticle(); + packet.systemId = this.systemId; + packet.color = this.color; + packet.scale = this.scale; + packet.positionOffset = this.positionOffset; + packet.rotationOffset = this.rotationOffset; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "WorldParticle{systemId='" + + this.systemId + + "', color=" + + this.color + + ", scale=" + + this.scale + + ", positionOffset=" + + this.positionOffset + + ", rotationOffset=" + + this.rotationOffset + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/particle/pages/ParticleSpawnPage.java b/src/com/hypixel/hytale/server/core/asset/type/particle/pages/ParticleSpawnPage.java new file mode 100644 index 0000000..0b91f35 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/particle/pages/ParticleSpawnPage.java @@ -0,0 +1,275 @@ +package com.hypixel.hytale.server.core.asset.type.particle.pages; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.StringCompareUtil; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.NonSerialized; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.Rangef; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSpawnerGroup; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.ui.Value; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.ParticleUtil; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ParticleSpawnPage extends InteractiveCustomUIPage { + private static final String COMMON_TEXT_BUTTON_DOCUMENT = "Common/TextButton.ui"; + private static final Value BUTTON_LABEL_STYLE = Value.ref("Common/TextButton.ui", "LabelStyle"); + private static final Value BUTTON_LABEL_STYLE_SELECTED = Value.ref("Common/TextButton.ui", "SelectedLabelStyle"); + @Nonnull + private String searchQuery = ""; + private List particleSystemIds; + @Nullable + private String selectedParticleSystemId; + @Nullable + private Ref particleSystemPreview; + private Vector3d position; + private Vector3f rotation; + + public ParticleSpawnPage(@Nonnull PlayerRef playerRef) { + super(playerRef, CustomPageLifetime.CanDismiss, ParticleSpawnPage.ParticleSpawnPageEventData.CODEC); + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/ParticleSpawnPage.ui"); + eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#SearchInput", EventData.of("@SearchQuery", "#SearchInput.Value"), false); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, + "#RotationOffset", + new EventData().append("Action", "UpdateRotationOffset").append("@RotationOffset", "#RotationOffset.Value"), + false + ); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#Spawn", new EventData().append("Action", "Spawn"), false); + this.buildParticleList(ref, commandBuilder, eventBuilder, store); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull ParticleSpawnPage.ParticleSpawnPageEventData data) { + if (data.searchQuery != null) { + this.searchQuery = data.searchQuery.trim().toLowerCase(); + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.buildParticleList(ref, commandBuilder, eventBuilder, store); + this.sendUpdate(commandBuilder, eventBuilder, false); + } else { + String var8 = data.action; + switch (var8) { + case "Select": + if (data.particleSystemId != null) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + this.selectParticleSystem(ref, store, data.particleSystemId, commandBuilder); + this.sendUpdate(commandBuilder, null, false); + } + break; + case "UpdateRotationOffset": + if (this.particleSystemPreview.isValid()) { + TransformComponent transform = store.getComponent(this.particleSystemPreview, TransformComponent.getComponentType()); + transform.getRotation().setYaw(this.rotation.getYaw() + (float)Math.toRadians(data.rotationOffset)); + HeadRotation headRotation = store.getComponent(this.particleSystemPreview, HeadRotation.getComponentType()); + if (headRotation != null) { + headRotation.getRotation().setYaw(this.rotation.getYaw() + (float)Math.toRadians(data.rotationOffset)); + } + } + break; + case "Spawn": + if (this.selectedParticleSystemId != null) { + if (this.particleSystemPreview.isValid()) { + store.removeEntity(this.particleSystemPreview, RemoveReason.REMOVE); + } + + SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get().getPlayerSpatialResourceType()); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(this.position, 75.0, results); + ParticleUtil.spawnParticleEffect(this.selectedParticleSystemId, this.position, this.rotation, results, store); + } + } + } + } + + @Override + public void onDismiss(@Nonnull Ref ref, @Nonnull Store store) { + if (this.particleSystemPreview.isValid()) { + store.removeEntity(this.particleSystemPreview, RemoveReason.REMOVE); + } + } + + private void buildParticleList( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.clear("#ParticleSystemList"); + Set roleTemplateNames = ParticleSystem.getAssetMap().getAssetMap().keySet(); + if (!this.searchQuery.isEmpty()) { + Object2IntMap map = new Object2IntOpenHashMap<>(roleTemplateNames.size()); + + for (String value : roleTemplateNames) { + int fuzzyDistance = StringCompareUtil.getFuzzyDistance(value, this.searchQuery, Locale.ENGLISH); + if (fuzzyDistance > 0) { + map.put(value, fuzzyDistance); + } + } + + this.particleSystemIds = map.keySet() + .stream() + .sorted() + .sorted(Comparator.comparingInt(map::getInt).reversed()) + .limit(20L) + .collect(Collectors.toList()); + } else { + this.particleSystemIds = roleTemplateNames.stream().sorted(String::compareTo).collect(Collectors.toList()); + } + + int i = 0; + + for (int bound = this.particleSystemIds.size(); i < bound; i++) { + String id = this.particleSystemIds.get(i); + String selector = "#ParticleSystemList[" + i + "]"; + commandBuilder.append("#ParticleSystemList", "Common/TextButton.ui"); + commandBuilder.set(selector + " #Button.Text", id); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, selector + " #Button", new EventData().append("Action", "Select").append("ParticleSystemId", id), false + ); + } + + if (!this.particleSystemIds.isEmpty()) { + if (!this.particleSystemIds.contains(this.selectedParticleSystemId)) { + this.selectParticleSystem(ref, store, this.particleSystemIds.getFirst(), commandBuilder); + } else if (this.selectedParticleSystemId != null) { + this.selectParticleSystem(ref, store, this.selectedParticleSystemId, commandBuilder); + } + } + } + + private void selectParticleSystem( + @Nonnull Ref ref, @Nonnull Store store, @Nonnull String particleSystemId, @Nonnull UICommandBuilder commandBuilder + ) { + if (this.selectedParticleSystemId != null && this.particleSystemIds.contains(this.selectedParticleSystemId)) { + commandBuilder.set("#ParticleSystemList[" + this.particleSystemIds.indexOf(this.selectedParticleSystemId) + "] #Button.Style", BUTTON_LABEL_STYLE); + } + + commandBuilder.set("#ParticleSystemList[" + this.particleSystemIds.indexOf(particleSystemId) + "] #Button.Style", BUTTON_LABEL_STYLE_SELECTED); + commandBuilder.set("#ParticleSystemName.Text", particleSystemId); + ParticleSystem particleSystem = ParticleSystem.getAssetMap().getAsset(particleSystemId); + float lifeSpan = particleSystem.getLifeSpan(); + commandBuilder.set("#SystemLifespan.Text", lifeSpan <= 0.0F ? "Infinite" : String.valueOf(lifeSpan)); + float lifeSpanMin = Float.MAX_VALUE; + float lifeSpanMax = -1.0F; + + for (ParticleSpawnerGroup spawner : particleSystem.getSpawners()) { + Rangef groupLifespawn = spawner.getLifeSpan(); + if (groupLifespawn != null) { + lifeSpanMin = Math.min(lifeSpanMin, groupLifespawn.min); + lifeSpanMax = Math.max(lifeSpanMax, groupLifespawn.max); + } else { + lifeSpanMin = -1.0F; + lifeSpanMax = Float.MAX_VALUE; + } + } + + commandBuilder.set( + "#ParticleGroupLifespan.Text", + String.format( + "%s - %s", + lifeSpanMin < 0.0F ? "Unset" : String.valueOf(lifeSpanMin), + lifeSpanMax >= Float.MAX_VALUE ? "Infinite" : (lifeSpanMax < 0.0F ? "Unset" : String.valueOf(lifeSpanMax)) + ) + ); + this.selectedParticleSystemId = particleSystemId; + if (this.particleSystemPreview != null && this.particleSystemPreview.isValid()) { + if (this.particleSystemPreview != null && this.particleSystemPreview.isValid()) { + } + } else { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3d playerPosition = transformComponent.getPosition(); + Vector3f headRotation = headRotationComponent.getRotation(); + Vector3d previewPosition = TargetUtil.getTargetLocation(ref, 8.0, store); + if (previewPosition == null) { + previewPosition = playerPosition.clone().add(Transform.getDirection(headRotation.getPitch(), headRotation.getYaw()).scale(4.0)); + } + + Vector3d targetGround = TargetUtil.getTargetLocation( + store.getExternalData().getWorld(), blockId -> blockId != 0, previewPosition.x, previewPosition.y, previewPosition.z, 0.0, -1.0, 0.0, 8.0 + ); + if (targetGround != null) { + previewPosition = targetGround; + } + + previewPosition.add(0.0, particleSystem.getBoundingRadius(), 0.0); + Vector3d relativePos = playerPosition.clone().subtract(previewPosition); + relativePos.setY(0.0); + Vector3f previewRotation = Vector3f.lookAt(relativePos); + this.position = previewPosition; + this.rotation = previewRotation; + Holder holder = store.getRegistry().newHolder(); + holder.addComponent(EntityStore.REGISTRY.getNonSerializedComponentType(), NonSerialized.get()); + holder.addComponent(NetworkId.getComponentType(), new NetworkId(store.getExternalData().takeNextNetworkId())); + holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(previewPosition, previewRotation)); + this.particleSystemPreview = store.addEntity(holder, AddReason.SPAWN); + } + } + + public static class ParticleSpawnPageEventData { + static final String KEY_ACTION = "Action"; + static final String KEY_PARTICLE_SYSTEM_ID = "ParticleSystemId"; + static final String KEY_SEARCH_QUERY = "@SearchQuery"; + static final String KEY_ROTATION_OFFSET = "@RotationOffset"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + ParticleSpawnPage.ParticleSpawnPageEventData.class, ParticleSpawnPage.ParticleSpawnPageEventData::new + ) + .append(new KeyedCodec<>("Action", Codec.STRING), (entry, s) -> entry.action = s, entry -> entry.action) + .add() + .append(new KeyedCodec<>("ParticleSystemId", Codec.STRING), (entry, s) -> entry.particleSystemId = s, entry -> entry.particleSystemId) + .add() + .append(new KeyedCodec<>("@SearchQuery", Codec.STRING), (entry, s) -> entry.searchQuery = s, entry -> entry.searchQuery) + .add() + .append(new KeyedCodec<>("@RotationOffset", Codec.FLOAT), (entry, s) -> entry.rotationOffset = s, entry -> entry.rotationOffset) + .add() + .build(); + private String particleSystemId; + private String action; + private String searchQuery; + private float rotationOffset; + + public ParticleSpawnPageEventData() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/portalworld/PillTag.java b/src/com/hypixel/hytale/server/core/asset/type/portalworld/PillTag.java new file mode 100644 index 0000000..c88b7bd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/portalworld/PillTag.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.core.asset.type.portalworld; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; + +public class PillTag { + public static final BuilderCodec CODEC = BuilderCodec.builder(PillTag.class, PillTag::new) + .appendInherited( + new KeyedCodec<>("TranslationKey", Codec.STRING), + (pillTag, o) -> pillTag.translationKey = o, + pillTag -> pillTag.translationKey, + (pillTag, parent) -> pillTag.translationKey = parent.translationKey + ) + .documentation("The translation key for the text of this tag.") + .add() + .appendInherited( + new KeyedCodec<>("Color", ProtocolCodecs.COLOR), + (pillTag, o) -> pillTag.color = o, + pillTag -> pillTag.color, + (pillTag, parent) -> pillTag.color = parent.color + ) + .add() + .build(); + public static final PillTag[] EMPTY_LIST = new PillTag[0]; + private String translationKey; + private Color color; + + public PillTag() { + } + + public String getTranslationKey() { + return this.translationKey; + } + + public Message getMessage() { + return Message.translation(this.translationKey); + } + + public Color getColor() { + return this.color; + } + + @Override + public String toString() { + return "PillTag{translationKey='" + this.translationKey + "', color=" + this.color + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/portalworld/PortalDescription.java b/src/com/hypixel/hytale/server/core/asset/type/portalworld/PortalDescription.java new file mode 100644 index 0000000..6aa774e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/portalworld/PortalDescription.java @@ -0,0 +1,95 @@ +package com.hypixel.hytale.server.core.asset.type.portalworld; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class PortalDescription { + public static final BuilderCodec CODEC = BuilderCodec.builder(PortalDescription.class, PortalDescription::new) + .append(new KeyedCodec<>("DisplayName", Codec.STRING), (portalType, o) -> portalType.displayNameKey = o, portalType -> portalType.displayNameKey) + .documentation("The translation key for the name of this portal.") + .add() + .append(new KeyedCodec<>("FlavorText", Codec.STRING), (portalType, o) -> portalType.flavorTextKey = o, portalType -> portalType.flavorTextKey) + .documentation("The translation key for the description of this portal.") + .add() + .append(new KeyedCodec<>("ThemeColor", ProtocolCodecs.COLOR), (portalType, o) -> portalType.themeColor = o, portalType -> portalType.themeColor) + .documentation("What color do you associate with this portal? May be used in many places.") + .add() + .append( + new KeyedCodec<>("DescriptionTags", new ArrayCodec<>(PillTag.CODEC, PillTag[]::new)), + (portalType, o) -> portalType.pillTags = o, + portalType -> portalType.pillTags + ) + .documentation("Purely cosmetic list of tags for the UI.") + .add() + .append( + new KeyedCodec<>("Objectives", Codec.STRING_ARRAY), (portalType, o) -> portalType.objectivesKeys = o, portalType -> portalType.objectivesKeys + ) + .documentation("List of translation keys for the objectives in this portal.") + .add() + .append(new KeyedCodec<>("Tips", Codec.STRING_ARRAY), (portalType, o) -> portalType.wisdomKeys = o, portalType -> portalType.wisdomKeys) + .documentation("List of translation keys for the tips/wisdom offered for this portal.") + .add() + .append( + new KeyedCodec<>("SplashImage", Codec.STRING), (portalType, o) -> portalType.splashImageFilename = o, portalType -> portalType.splashImageFilename + ) + .documentation( + "The filename of the splash image for this portal. Your best bet to find the folder is to search for an existing portal's image in assets. Screenshots taken 60 fov." + ) + .add() + .build(); + private String displayNameKey; + private String flavorTextKey; + private Color themeColor; + private PillTag[] pillTags; + private String[] objectivesKeys; + private String[] wisdomKeys; + private String splashImageFilename; + + public PortalDescription() { + } + + public String getDisplayNameKey() { + return this.displayNameKey; + } + + public Message getDisplayName() { + return Message.translation(this.displayNameKey); + } + + public String getFlavorTextKey() { + return this.flavorTextKey; + } + + public Message getFlavorText() { + return Message.translation(this.flavorTextKey); + } + + public Color getThemeColor() { + return this.themeColor; + } + + public List getPillTags() { + return this.pillTags == null ? Collections.emptyList() : Arrays.asList(this.pillTags); + } + + public String[] getObjectivesKeys() { + return this.objectivesKeys == null ? ArrayUtil.EMPTY_STRING_ARRAY : this.objectivesKeys; + } + + public String[] getWisdomKeys() { + return this.wisdomKeys == null ? ArrayUtil.EMPTY_STRING_ARRAY : this.wisdomKeys; + } + + public String getSplashImageFilename() { + return this.splashImageFilename; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/portalworld/PortalSpawn.java b/src/com/hypixel/hytale/server/core/asset/type/portalworld/PortalSpawn.java new file mode 100644 index 0000000..5fc2115 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/portalworld/PortalSpawn.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.core.asset.type.portalworld; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3i; + +public class PortalSpawn { + public static final BuilderCodec CODEC = BuilderCodec.builder(PortalSpawn.class, PortalSpawn::new) + .append(new KeyedCodec<>("Y", Codec.INTEGER), (spawn, o) -> spawn.checkSpawnY = o, spawn -> spawn.checkSpawnY) + .documentation("The Y height where to start looking for X,Z candidate.") + .add() + .append(new KeyedCodec<>("ScanHeight", Codec.INTEGER), (spawn, o) -> spawn.scanHeight = o, spawn -> spawn.scanHeight) + .documentation("How many blocks to scan downwards after picking a X,Y,Z candidate.") + .add() + .append(new KeyedCodec<>("MinRadius", Codec.INTEGER), (spawn, o) -> spawn.minRadius = o, spawn -> spawn.minRadius) + .documentation("Picks a random X,Z point around center at [MinRadius]-[MaxRadius] radius to find chunks.") + .add() + .append(new KeyedCodec<>("MaxRadius", Codec.INTEGER), (spawn, o) -> spawn.maxRadius = o, spawn -> spawn.maxRadius) + .documentation("Picks a random X,Z point around center at [MinRadius]-[MaxRadius] radius to find chunks.") + .add() + .append(new KeyedCodec<>("Center", Vector3i.CODEC), (spawn, o) -> spawn.center = o, spawn -> spawn.center) + .documentation("Picks a random X,Z point around [Center] at Radius radius.") + .add() + .append(new KeyedCodec<>("ChunkDartThrows", Codec.INTEGER), (spawn, o) -> spawn.chunkDartThrows = o, spawn -> spawn.chunkDartThrows) + .documentation("How many attempts at picking a spawn.") + .add() + .append(new KeyedCodec<>("ChecksPerChunk", Codec.INTEGER), (spawn, o) -> spawn.checksPerChunk = o, spawn -> spawn.checksPerChunk) + .documentation("For every chunk, how many random location checks are done within the chunk.") + .add() + .build(); + private Vector3i center = Vector3i.ZERO.clone(); + private int scanHeight = 16; + private int checkSpawnY; + private int minRadius; + private int maxRadius; + private int chunkDartThrows = 20; + private int checksPerChunk = 5; + + public PortalSpawn() { + } + + public Vector3i getCenter() { + return this.center; + } + + public int getCheckSpawnY() { + return this.checkSpawnY; + } + + public int getScanHeight() { + return this.scanHeight; + } + + public int getMinRadius() { + return this.minRadius; + } + + public int getMaxRadius() { + return this.maxRadius; + } + + public int getChunkDartThrows() { + return this.chunkDartThrows; + } + + public int getChecksPerChunk() { + return this.checksPerChunk; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/portalworld/PortalType.java b/src/com/hypixel/hytale/server/core/asset/type/portalworld/PortalType.java new file mode 100644 index 0000000..5a1d98f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/portalworld/PortalType.java @@ -0,0 +1,123 @@ +package com.hypixel.hytale.server.core.asset.type.portalworld; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import java.util.Collections; +import java.util.Set; +import javax.annotation.Nullable; + +public class PortalType implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + PortalType.class, + PortalType::new, + Codec.STRING, + (portalType, s) -> portalType.id = s, + portalType -> portalType.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .append(new KeyedCodec<>("InstanceId", Codec.STRING), (portalType, o) -> portalType.instanceId = o, portalType -> portalType.instanceId) + .documentation("The instance id (folder name in Assets/Server/Instances) that this instance loads.") + .add() + .append( + new KeyedCodec<>("Description", PortalDescription.CODEC), (portalType, o) -> portalType.description = o, portalType -> portalType.description + ) + .documentation("The qualitative description of the portal.") + .add() + .append( + new KeyedCodec<>("PlayerSpawn", PortalSpawn.CODEC), (portalType, o) -> portalType.portalSpawn = o, portalType -> portalType.portalSpawn + ) + .documentation("If set, overrides the Instance's SpawnProvider with the portal's spawning system.") + .add() + .append( + new KeyedCodec<>("CursedItems", Codec.STRING_ARRAY), + (portalType, o) -> portalType.cursedItems = o == null ? Collections.emptySet() : Set.of(o), + portalType -> portalType.cursedItems.toArray(String[]::new) + ) + .documentation("Which item drops are cursed when spawned in this portal.") + .add() + .append( + new KeyedCodec<>("VoidInvasionEnabled", Codec.BOOLEAN), + (portalType, o) -> portalType.voidInvasionEnabled = o, + portalType -> portalType.voidInvasionEnabled + ) + .documentation("Whether the void invasion is enabled for this portal.") + .add() + .append(new KeyedCodec<>("GameplayConfig", Codec.STRING), (config, o) -> config.gameplayConfig = o, config -> config.gameplayConfig) + .documentation("The ID of the GameplayConfig asset for worlds spawned with this portal type.") + .add() + .build(); + private static AssetStore> ASSET_STORE; + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(PortalType::getAssetStore)); + private AssetExtraInfo.Data data; + private String id; + private String instanceId; + private PortalDescription description; + private String gameplayConfig = "Portal"; + private boolean voidInvasionEnabled = false; + private PortalSpawn portalSpawn; + private Set cursedItems = Collections.emptySet(); + + public PortalType() { + } + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(PortalType.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public String getId() { + return this.id; + } + + public String getInstanceId() { + return this.instanceId; + } + + public Message getDisplayName() { + return this.description.getDisplayName(); + } + + public PortalDescription getDescription() { + return this.description; + } + + @Nullable + public PortalSpawn getPortalSpawn() { + return this.portalSpawn; + } + + public Set getCursedItems() { + return this.cursedItems; + } + + public String getGameplayConfigId() { + return this.gameplayConfig; + } + + public boolean isVoidInvasionEnabled() { + return this.voidInvasionEnabled; + } + + @Nullable + public GameplayConfig getGameplayConfig() { + return GameplayConfig.getAssetMap().getAsset(this.gameplayConfig); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/projectile/config/Projectile.java b/src/com/hypixel/hytale/server/core/asset/type/projectile/config/Projectile.java new file mode 100644 index 0000000..0657ef4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/projectile/config/Projectile.java @@ -0,0 +1,821 @@ +package com.hypixel.hytale.server.core.asset.type.projectile.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.asset.type.particle.config.WorldParticle; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.validator.SoundEventValidators; +import com.hypixel.hytale.server.core.entity.ExplosionConfig; +import com.hypixel.hytale.server.core.modules.physics.SimplePhysicsProvider; +import com.hypixel.hytale.server.core.modules.projectile.config.BallisticData; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@Deprecated +public class Projectile implements JsonAssetWithMap>, BallisticData { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + Projectile.class, + Projectile::new, + Codec.STRING, + (projectile, s) -> projectile.id = s, + projectile -> projectile.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("Appearance", Codec.STRING), + (projectile, s) -> projectile.appearance = s, + projectile -> projectile.appearance, + (projectile, parent) -> projectile.appearance = parent.appearance + ) + .addValidator(ModelAsset.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("Radius", Codec.DOUBLE), + (projectile, s) -> projectile.radius = s, + projectile -> projectile.radius, + (projectile, parent) -> projectile.radius = parent.radius + ) + .add() + .appendInherited( + new KeyedCodec<>("Height", Codec.DOUBLE), + (projectile, s) -> projectile.height = s, + projectile -> projectile.height, + (projectile, parent) -> projectile.height = parent.height + ) + .add() + .appendInherited( + new KeyedCodec<>("VerticalCenterShot", Codec.DOUBLE), + (projectile, s) -> projectile.verticalCenterShot = s, + projectile -> projectile.verticalCenterShot, + (projectile, parent) -> projectile.verticalCenterShot = parent.verticalCenterShot + ) + .add() + .appendInherited( + new KeyedCodec<>("HorizontalCenterShot", Codec.DOUBLE), + (projectile, s) -> projectile.horizontalCenterShot = s, + projectile -> projectile.horizontalCenterShot, + (projectile, parent) -> projectile.horizontalCenterShot = parent.horizontalCenterShot + ) + .add() + .appendInherited( + new KeyedCodec<>("DepthShot", Codec.DOUBLE), + (projectile, s) -> projectile.depthShot = s, + projectile -> projectile.depthShot, + (projectile, parent) -> projectile.depthShot = parent.depthShot + ) + .add() + .appendInherited( + new KeyedCodec<>("PitchAdjustShot", Codec.BOOLEAN), + (projectile, s) -> projectile.pitchAdjustShot = s, + projectile -> projectile.pitchAdjustShot, + (projectile, parent) -> projectile.pitchAdjustShot = parent.pitchAdjustShot + ) + .add() + .appendInherited( + new KeyedCodec<>("MuzzleVelocity", Codec.DOUBLE), + (projectile, s) -> projectile.muzzleVelocity = s, + projectile -> projectile.muzzleVelocity, + (projectile, parent) -> projectile.muzzleVelocity = parent.muzzleVelocity + ) + .add() + .appendInherited( + new KeyedCodec<>("TerminalVelocity", Codec.DOUBLE), + (projectile, s) -> projectile.terminalVelocity = s, + projectile -> projectile.terminalVelocity, + (projectile, parent) -> projectile.terminalVelocity = parent.terminalVelocity + ) + .add() + .appendInherited( + new KeyedCodec<>("Gravity", Codec.DOUBLE), + (projectile, s) -> projectile.gravity = s, + projectile -> projectile.gravity, + (projectile, parent) -> projectile.gravity = parent.gravity + ) + .add() + .appendInherited( + new KeyedCodec<>("Bounciness", Codec.DOUBLE), + (projectile, s) -> projectile.bounciness = s, + projectile -> projectile.bounciness, + (projectile, parent) -> projectile.bounciness = parent.bounciness + ) + .add() + .appendInherited( + new KeyedCodec<>("ImpactSlowdown", Codec.DOUBLE), + (projectile, s) -> projectile.impactSlowdown = s, + projectile -> projectile.impactSlowdown, + (projectile, parent) -> projectile.impactSlowdown = parent.impactSlowdown + ) + .add() + .appendInherited( + new KeyedCodec<>("SticksVertically", Codec.BOOLEAN), + (projectile, s) -> projectile.sticksVertically = s, + projectile -> projectile.sticksVertically, + (projectile, parent) -> projectile.sticksVertically = parent.sticksVertically + ) + .add() + .appendInherited( + new KeyedCodec<>("ComputeYaw", Codec.BOOLEAN), + (projectile, s) -> projectile.computeYaw = s, + projectile -> projectile.computeYaw, + (projectile, parent) -> projectile.computeYaw = parent.computeYaw + ) + .add() + .appendInherited( + new KeyedCodec<>("ComputePitch", Codec.BOOLEAN), + (projectile, s) -> projectile.computePitch = s, + projectile -> projectile.computePitch, + (projectile, parent) -> projectile.computePitch = parent.computePitch + ) + .add() + .appendInherited( + new KeyedCodec<>("ComputeRoll", Codec.BOOLEAN), + (projectile, s) -> projectile.computeRoll = s, + projectile -> projectile.computeRoll, + (projectile, parent) -> projectile.computeRoll = parent.computeRoll + ) + .add() + .appendInherited( + new KeyedCodec<>("TimeToLive", Codec.DOUBLE), + (projectile, s) -> projectile.timeToLive = s, + projectile -> projectile.timeToLive, + (projectile, parent) -> projectile.timeToLive = parent.timeToLive + ) + .add() + .appendInherited( + new KeyedCodec<>("BounceSoundEventId", Codec.STRING), + (projectile, s) -> projectile.bounceSoundEventId = s, + projectile -> projectile.bounceSoundEventId, + (projectile, parent) -> projectile.bounceSoundEventId = parent.bounceSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .add() + .appendInherited( + new KeyedCodec<>("BounceParticles", WorldParticle.CODEC), + (projectile, s) -> projectile.bounceParticles = s, + projectile -> projectile.bounceParticles, + (projectile, parent) -> projectile.bounceParticles = parent.bounceParticles + ) + .add() + .appendInherited( + new KeyedCodec<>("HitSoundEventId", Codec.STRING), + (projectile, s) -> projectile.hitSoundEventId = s, + projectile -> projectile.hitSoundEventId, + (projectile, parent) -> projectile.hitSoundEventId = parent.hitSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .add() + .appendInherited( + new KeyedCodec<>("HitParticles", WorldParticle.CODEC), + (projectile, s) -> projectile.hitParticles = s, + projectile -> projectile.hitParticles, + (projectile, parent) -> projectile.hitParticles = parent.hitParticles + ) + .add() + .appendInherited( + new KeyedCodec<>("Damage", Codec.INTEGER), + (projectile, s) -> projectile.damage = s, + projectile -> projectile.damage, + (projectile, parent) -> projectile.damage = parent.damage + ) + .add() + .appendInherited( + new KeyedCodec<>("DeadTime", Codec.DOUBLE), + (projectile, s) -> projectile.deadTime = s, + projectile -> projectile.deadTime, + (projectile, parent) -> projectile.deadTime = parent.deadTime + ) + .add() + .appendInherited( + new KeyedCodec<>("MissSoundEventId", Codec.STRING), + (projectile, s) -> projectile.missSoundEventId = s, + projectile -> projectile.missSoundEventId, + (projectile, parent) -> projectile.missSoundEventId = parent.missSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .add() + .appendInherited( + new KeyedCodec<>("MissParticles", WorldParticle.CODEC), + (projectile, s) -> projectile.missParticles = s, + projectile -> projectile.missParticles, + (projectile, parent) -> projectile.missParticles = parent.missParticles + ) + .add() + .appendInherited( + new KeyedCodec<>("DeadTimeMiss", Codec.DOUBLE), + (projectile, s) -> projectile.deadTimeMiss = s, + projectile -> projectile.deadTimeMiss, + (projectile, parent) -> projectile.deadTimeMiss = parent.deadTimeMiss + ) + .add() + .appendInherited( + new KeyedCodec<>("DeathSoundEventId", Codec.STRING), + (projectile, s) -> projectile.deathSoundEventId = s, + projectile -> projectile.deathSoundEventId, + (projectile, parent) -> projectile.deathSoundEventId = parent.deathSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .add() + .appendInherited( + new KeyedCodec<>("DeathParticles", WorldParticle.CODEC), + (projectile, s) -> projectile.deathParticles = s, + projectile -> projectile.deathParticles, + (projectile, parent) -> projectile.deathParticles = parent.deathParticles + ) + .add() + .appendInherited( + new KeyedCodec<>("DeathEffectsOnHit", Codec.BOOLEAN), + (projectile, b) -> projectile.deathEffectsOnHit = b, + projectile -> projectile.deathEffectsOnHit, + (projectile, parent) -> projectile.deathEffectsOnHit = parent.deathEffectsOnHit + ) + .add() + .appendInherited( + new KeyedCodec<>("ExplosionConfig", ExplosionConfig.CODEC), + (projectile, s) -> projectile.explosionConfig = s, + projectile -> projectile.explosionConfig, + (projectile, parent) -> projectile.explosionConfig = parent.explosionConfig + ) + .documentation("The explosion config associated with this projectile") + .add() + .appendInherited( + new KeyedCodec<>("Density", Codec.DOUBLE), + (projectile, s) -> projectile.density = s, + projectile -> projectile.density, + (projectile, parent) -> projectile.density = parent.density + ) + .add() + .appendInherited( + new KeyedCodec<>("WaterTerminalVelocityMultiplier", Codec.DOUBLE), + (projectile, s) -> projectile.waterTerminalVelocityMultiplier = s, + projectile -> projectile.waterTerminalVelocityMultiplier, + (projectile, parent) -> projectile.waterTerminalVelocityMultiplier = parent.waterTerminalVelocityMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("WaterHitImpulseLoss", Codec.DOUBLE), + (projectile, s) -> projectile.waterHitImpulseLoss = s, + projectile -> projectile.waterHitImpulseLoss, + (projectile, parent) -> projectile.waterHitImpulseLoss = parent.waterHitImpulseLoss + ) + .add() + .appendInherited( + new KeyedCodec<>("DampingRotation", Codec.DOUBLE), + (projectile, s) -> projectile.dampingRotation = s, + projectile -> projectile.dampingRotation, + (projectile, parent) -> projectile.dampingRotation = parent.dampingRotation + ) + .add() + .appendInherited( + new KeyedCodec<>("RotationSpeedVelocityRatio", Codec.DOUBLE), + (projectile, s) -> projectile.rotationSpeedVelocityRatio = s, + projectile -> projectile.rotationSpeedVelocityRatio, + (projectile, parent) -> projectile.rotationSpeedVelocityRatio = parent.rotationSpeedVelocityRatio + ) + .add() + .appendInherited( + new KeyedCodec<>("SwimmingDampingFactor", Codec.DOUBLE), + (projectile, s) -> projectile.swimmingDampingFactor = s, + projectile -> projectile.swimmingDampingFactor, + (projectile, parent) -> projectile.swimmingDampingFactor = parent.swimmingDampingFactor + ) + .add() + .appendInherited( + new KeyedCodec<>("RotationMode", new EnumCodec<>(SimplePhysicsProvider.ROTATION_MODE.class)), + (projectile, type) -> projectile.rotationMode = type, + projectile -> projectile.rotationMode, + (projectile, parent) -> projectile.rotationMode = parent.rotationMode + ) + .add() + .afterDecode(Projectile::processConfig) + .build(); + private static AssetStore> ASSET_STORE; + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(Projectile::getAssetStore)); + protected AssetExtraInfo.Data data; + protected String id; + protected String appearance; + protected double radius; + protected double height; + protected double verticalCenterShot; + protected double horizontalCenterShot; + protected double depthShot; + protected boolean pitchAdjustShot; + protected double muzzleVelocity; + protected double terminalVelocity; + protected double gravity; + protected double bounciness; + protected double impactSlowdown; + protected boolean sticksVertically; + protected boolean computeYaw = true; + protected boolean computePitch = true; + protected boolean computeRoll = true; + protected SimplePhysicsProvider.ROTATION_MODE rotationMode = SimplePhysicsProvider.ROTATION_MODE.Velocity; + protected double timeToLive; + protected String bounceSoundEventId; + protected transient int bounceSoundEventIndex; + @Nullable + protected WorldParticle bounceParticles; + protected String hitSoundEventId; + protected transient int hitSoundEventIndex; + @Nullable + protected WorldParticle hitParticles; + protected int damage; + protected double deadTime; + protected String missSoundEventId; + protected transient int missSoundEventIndex; + protected WorldParticle missParticles; + protected double deadTimeMiss = 10.0; + protected String deathSoundEventId; + protected transient int deathSoundEventIndex; + @Nullable + protected WorldParticle deathParticles; + protected boolean deathEffectsOnHit; + @Nullable + protected ExplosionConfig explosionConfig; + double density = 2000.0; + double waterTerminalVelocityMultiplier = 1.0; + double waterHitImpulseLoss = 0.0; + double dampingRotation = 0.0; + double rotationSpeedVelocityRatio = 2.0; + double swimmingDampingFactor = 1.0; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(Projectile.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + protected Projectile() { + } + + public String getId() { + return this.id; + } + + public String getAppearance() { + return this.appearance; + } + + public double getRadius() { + return this.radius; + } + + public double getHeight() { + return this.height; + } + + @Override + public double getVerticalCenterShot() { + return this.verticalCenterShot; + } + + public double getHorizontalCenterShot() { + return this.horizontalCenterShot; + } + + @Override + public double getDepthShot() { + return this.depthShot; + } + + @Override + public boolean isPitchAdjustShot() { + return this.pitchAdjustShot; + } + + public boolean isSticksVertically() { + return this.sticksVertically; + } + + @Override + public double getMuzzleVelocity() { + return this.muzzleVelocity; + } + + public double getTerminalVelocity() { + return this.terminalVelocity; + } + + @Override + public double getGravity() { + return this.gravity; + } + + public double getBounciness() { + return this.bounciness; + } + + public double getImpactSlowdown() { + return this.impactSlowdown; + } + + public double getTimeToLive() { + return this.timeToLive; + } + + public int getDamage() { + return this.damage; + } + + public double getDeadTime() { + return this.deadTime; + } + + public double getDeadTimeMiss() { + return this.deadTimeMiss; + } + + public String getBounceSoundEventId() { + return this.bounceSoundEventId; + } + + public int getBounceSoundEventIndex() { + return this.bounceSoundEventIndex; + } + + public String getHitSoundEventId() { + return this.hitSoundEventId; + } + + public int getHitSoundEventIndex() { + return this.hitSoundEventIndex; + } + + public String getMissSoundEventId() { + return this.missSoundEventId; + } + + public int getMissSoundEventIndex() { + return this.missSoundEventIndex; + } + + public String getDeathSoundEventId() { + return this.deathSoundEventId; + } + + public int getDeathSoundEventIndex() { + return this.deathSoundEventIndex; + } + + @Nullable + public WorldParticle getBounceParticles() { + return this.bounceParticles; + } + + @Nullable + public WorldParticle getMissParticles() { + return this.missParticles; + } + + @Nullable + public WorldParticle getDeathParticles() { + return this.deathParticles; + } + + @Nullable + public WorldParticle getHitParticles() { + return this.hitParticles; + } + + public boolean isDeathEffectsOnHit() { + return this.deathEffectsOnHit; + } + + public boolean isComputeYaw() { + return this.computeYaw; + } + + public boolean isComputePitch() { + return this.computePitch; + } + + public boolean isComputeRoll() { + return this.computeRoll; + } + + public SimplePhysicsProvider.ROTATION_MODE getRotationMode() { + return this.rotationMode; + } + + public double getDensity() { + return this.density; + } + + public double getWaterTerminalVelocityMultiplier() { + return this.waterTerminalVelocityMultiplier; + } + + public double getWaterHitImpulseLoss() { + return this.waterHitImpulseLoss; + } + + public double getDampingRotation() { + return this.dampingRotation; + } + + public double getRotationSpeedVelocityRatio() { + return this.rotationSpeedVelocityRatio; + } + + public double getSwimmingDampingFactor() { + return this.swimmingDampingFactor; + } + + protected void processConfig() { + if (this.bounceSoundEventId != null) { + this.bounceSoundEventIndex = SoundEvent.getAssetMap().getIndex(this.bounceSoundEventId); + } + + if (this.hitSoundEventId != null) { + this.hitSoundEventIndex = SoundEvent.getAssetMap().getIndex(this.hitSoundEventId); + } + + if (this.missSoundEventId != null) { + this.missSoundEventIndex = SoundEvent.getAssetMap().getIndex(this.missSoundEventId); + } + + if (this.deathSoundEventId != null) { + this.deathSoundEventIndex = SoundEvent.getAssetMap().getIndex(this.deathSoundEventId); + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Projectile that = (Projectile)o; + if (Double.compare(that.radius, this.radius) != 0) { + return false; + } else if (Double.compare(that.height, this.height) != 0) { + return false; + } else if (Double.compare(that.verticalCenterShot, this.verticalCenterShot) != 0) { + return false; + } else if (Double.compare(that.horizontalCenterShot, this.horizontalCenterShot) != 0) { + return false; + } else if (Double.compare(that.depthShot, this.depthShot) != 0) { + return false; + } else if (this.pitchAdjustShot != that.pitchAdjustShot) { + return false; + } else if (Double.compare(that.muzzleVelocity, this.muzzleVelocity) != 0) { + return false; + } else if (Double.compare(that.terminalVelocity, this.terminalVelocity) != 0) { + return false; + } else if (Double.compare(that.gravity, this.gravity) != 0) { + return false; + } else if (Double.compare(that.bounciness, this.bounciness) != 0) { + return false; + } else if (Double.compare(that.impactSlowdown, this.impactSlowdown) != 0) { + return false; + } else if (this.sticksVertically != that.sticksVertically) { + return false; + } else if (this.computeYaw != that.computeYaw) { + return false; + } else if (this.computePitch != that.computePitch) { + return false; + } else if (this.computeRoll != that.computeRoll) { + return false; + } else if (Double.compare(that.timeToLive, this.timeToLive) != 0) { + return false; + } else if (this.bounceSoundEventIndex != that.bounceSoundEventIndex) { + return false; + } else if (this.hitSoundEventIndex != that.hitSoundEventIndex) { + return false; + } else if (this.damage != that.damage) { + return false; + } else if (Double.compare(that.deadTime, this.deadTime) != 0) { + return false; + } else if (this.missSoundEventIndex != that.missSoundEventIndex) { + return false; + } else if (Double.compare(that.deadTimeMiss, this.deadTimeMiss) != 0) { + return false; + } else if (this.deathSoundEventIndex != that.deathSoundEventIndex) { + return false; + } else if (this.deathEffectsOnHit != that.deathEffectsOnHit) { + return false; + } else if (this.explosionConfig != that.explosionConfig) { + return false; + } else if (Double.compare(that.density, this.density) != 0) { + return false; + } else if (Double.compare(that.waterTerminalVelocityMultiplier, this.waterTerminalVelocityMultiplier) != 0) { + return false; + } else if (Double.compare(that.waterHitImpulseLoss, this.waterHitImpulseLoss) != 0) { + return false; + } else if (Double.compare(that.dampingRotation, this.dampingRotation) != 0) { + return false; + } else if (Double.compare(that.rotationSpeedVelocityRatio, this.rotationSpeedVelocityRatio) != 0) { + return false; + } else if (Double.compare(that.swimmingDampingFactor, this.swimmingDampingFactor) != 0) { + return false; + } else if (!this.id.equals(that.id)) { + return false; + } else if (this.appearance != null ? this.appearance.equals(that.appearance) : that.appearance == null) { + if (this.rotationMode != that.rotationMode) { + return false; + } else if (this.bounceSoundEventId != null ? this.bounceSoundEventId.equals(that.bounceSoundEventId) : that.bounceSoundEventId == null) { + if (this.bounceParticles != null ? this.bounceParticles.equals(that.bounceParticles) : that.bounceParticles == null) { + if (this.hitSoundEventId != null ? this.hitSoundEventId.equals(that.hitSoundEventId) : that.hitSoundEventId == null) { + if (this.hitParticles != null ? this.hitParticles.equals(that.hitParticles) : that.hitParticles == null) { + if (this.missSoundEventId != null ? this.missSoundEventId.equals(that.missSoundEventId) : that.missSoundEventId == null) { + if (this.missParticles != null ? this.missParticles.equals(that.missParticles) : that.missParticles == null) { + if (this.deathSoundEventId != null ? this.deathSoundEventId.equals(that.deathSoundEventId) : that.deathSoundEventId == null) { + return this.deathParticles != null ? !this.deathParticles.equals(that.deathParticles) : that.deathParticles != null; + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.id.hashCode(); + result = 31 * result + (this.appearance != null ? this.appearance.hashCode() : 0); + long temp = Double.doubleToLongBits(this.radius); + result = 31 * result + (int)(temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.height); + result = 31 * result + (int)(temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.verticalCenterShot); + result = 31 * result + (int)(temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.horizontalCenterShot); + result = 31 * result + (int)(temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.depthShot); + result = 31 * result + (int)(temp ^ temp >>> 32); + result = 31 * result + (this.pitchAdjustShot ? 1 : 0); + temp = Double.doubleToLongBits(this.muzzleVelocity); + result = 31 * result + (int)(temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.terminalVelocity); + result = 31 * result + (int)(temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.gravity); + result = 31 * result + (int)(temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.bounciness); + result = 31 * result + (int)(temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.impactSlowdown); + result = 31 * result + (int)(temp ^ temp >>> 32); + result = 31 * result + (this.sticksVertically ? 1 : 0); + result = 31 * result + (this.computeYaw ? 1 : 0); + result = 31 * result + (this.computePitch ? 1 : 0); + result = 31 * result + (this.computeRoll ? 1 : 0); + result = 31 * result + (this.rotationMode != null ? this.rotationMode.hashCode() : 0); + temp = Double.doubleToLongBits(this.timeToLive); + result = 31 * result + (int)(temp ^ temp >>> 32); + result = 31 * result + (this.bounceSoundEventId != null ? this.bounceSoundEventId.hashCode() : 0); + result = 31 * result + this.bounceSoundEventIndex; + result = 31 * result + (this.bounceParticles != null ? this.bounceParticles.hashCode() : 0); + result = 31 * result + (this.hitSoundEventId != null ? this.hitSoundEventId.hashCode() : 0); + result = 31 * result + this.hitSoundEventIndex; + result = 31 * result + (this.hitParticles != null ? this.hitParticles.hashCode() : 0); + result = 31 * result + this.damage; + temp = Double.doubleToLongBits(this.deadTime); + result = 31 * result + (int)(temp ^ temp >>> 32); + result = 31 * result + (this.missSoundEventId != null ? this.missSoundEventId.hashCode() : 0); + result = 31 * result + this.missSoundEventIndex; + result = 31 * result + (this.missParticles != null ? this.missParticles.hashCode() : 0); + temp = Double.doubleToLongBits(this.deadTimeMiss); + result = 31 * result + (int)(temp ^ temp >>> 32); + result = 31 * result + (this.deathSoundEventId != null ? this.deathSoundEventId.hashCode() : 0); + result = 31 * result + this.deathSoundEventIndex; + result = 31 * result + (this.deathParticles != null ? this.deathParticles.hashCode() : 0); + result = 31 * result + (this.deathEffectsOnHit ? 1 : 0); + result = 31 * result + (int)(temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.density); + result = 31 * result + (int)(temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.waterTerminalVelocityMultiplier); + result = 31 * result + (int)(temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.waterHitImpulseLoss); + result = 31 * result + (int)(temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.dampingRotation); + result = 31 * result + (int)(temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.rotationSpeedVelocityRatio); + result = 31 * result + (int)(temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.swimmingDampingFactor); + return 31 * result + (int)(temp ^ temp >>> 32); + } + + @Nonnull + @Override + public String toString() { + return "Projectile{id='" + + this.id + + "', appearance='" + + this.appearance + + "', radius=" + + this.radius + + ", height=" + + this.height + + ", verticalCenterShot=" + + this.verticalCenterShot + + ", horizontalCenterShot=" + + this.horizontalCenterShot + + ", depthShot=" + + this.depthShot + + ", pitchAdjustShot=" + + this.pitchAdjustShot + + ", muzzleVelocity=" + + this.muzzleVelocity + + ", terminalVelocity=" + + this.terminalVelocity + + ", gravity=" + + this.gravity + + ", bounciness=" + + this.bounciness + + ", impactSlowdown=" + + this.impactSlowdown + + ", sticksVertically=" + + this.sticksVertically + + ", computeYaw=" + + this.computeYaw + + ", computePitch=" + + this.computePitch + + ", computeRoll=" + + this.computeRoll + + ", rotationMode=" + + this.rotationMode + + ", timeToLive=" + + this.timeToLive + + ", bounceSoundEventId='" + + this.bounceSoundEventId + + "', bounceParticles='" + + this.bounceParticles + + "', hitSoundEventId='" + + this.hitSoundEventId + + "', hitParticles='" + + this.hitParticles + + "', damage=" + + this.damage + + ", deadTime=" + + this.deadTime + + ", missSoundEventId='" + + this.missSoundEventId + + "', missParticles='" + + this.missParticles + + "', deadTimeMiss=" + + this.deadTimeMiss + + ", deathSoundEventId='" + + this.deathSoundEventId + + "', deathParticles='" + + this.deathParticles + + "', deathEffectsOnHit=" + + this.deathEffectsOnHit + + ", density=" + + this.density + + ", waterTerminalVelocityMultiplier=" + + this.waterTerminalVelocityMultiplier + + ", waterHitImpulseLoss=" + + this.waterHitImpulseLoss + + ", dampingRotation=" + + this.dampingRotation + + ", rotationSpeedVelocityRatio=" + + this.rotationSpeedVelocityRatio + + ", swimmingDampingFactor=" + + this.swimmingDampingFactor + + "}"; + } + + @Nullable + public ExplosionConfig getExplosionConfig() { + return this.explosionConfig; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/responsecurve/ScaledResponseCurve.java b/src/com/hypixel/hytale/server/core/asset/type/responsecurve/ScaledResponseCurve.java new file mode 100644 index 0000000..24524ca --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/responsecurve/ScaledResponseCurve.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.server.core.asset.type.responsecurve; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.lookup.Priority; +import javax.annotation.Nonnull; + +public abstract class ScaledResponseCurve implements JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data, true + ) + .register(Priority.DEFAULT, "Default", ScaledXResponseCurve.class, ScaledXResponseCurve.CODEC); + protected AssetExtraInfo.Data data; + protected String id; + + public ScaledResponseCurve(String id) { + this.id = id; + } + + protected ScaledResponseCurve() { + } + + public abstract double computeY(double var1); + + public String getId() { + return this.id; + } + + @Nonnull + @Override + public String toString() { + return "ScaledResponseCurve{data=" + this.data + ", id='" + this.id + "'}"; + } + + static { + CODEC.register("Switch", ScaledSwitchResponseCurve.class, ScaledSwitchResponseCurve.CODEC); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/responsecurve/ScaledSwitchResponseCurve.java b/src/com/hypixel/hytale/server/core/asset/type/responsecurve/ScaledSwitchResponseCurve.java new file mode 100644 index 0000000..fe27132 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/responsecurve/ScaledSwitchResponseCurve.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.core.asset.type.responsecurve; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class ScaledSwitchResponseCurve extends ScaledResponseCurve { + public static final BuilderCodec CODEC = BuilderCodec.builder(ScaledSwitchResponseCurve.class, ScaledSwitchResponseCurve::new) + .documentation( + "A special type of scaled response curve which returns the initial state value before the defined switch point and the final state value after reaching it." + ) + .append(new KeyedCodec<>("InitialState", Codec.DOUBLE), (curve, d) -> curve.initialState = d, curve -> curve.initialState) + .addValidator(Validators.range(0.0, 1.0)) + .documentation("The y value to return before the switch point.") + .add() + .append(new KeyedCodec<>("FinalState", Codec.DOUBLE), (curve, d) -> curve.finalState = d, curve -> curve.finalState) + .addValidator(Validators.range(0.0, 1.0)) + .documentation("The y value to return at and beyond the switch point.") + .add() + .append(new KeyedCodec<>("SwitchPoint", Codec.DOUBLE), (curve, d) -> curve.switchPoint = d, curve -> curve.switchPoint) + .addValidator(Validators.nonNull()) + .documentation("The value at which to switch from the initial state to the final state.") + .add() + .build(); + protected double initialState = 0.0; + protected double finalState = 1.0; + protected double switchPoint; + + protected ScaledSwitchResponseCurve() { + } + + @Override + public double computeY(double x) { + return x < this.switchPoint ? this.initialState : this.finalState; + } + + @Nonnull + @Override + public String toString() { + return "ScaledSwitchResponseCurve{initialState=" + + this.initialState + + ", finalState=" + + this.finalState + + ", switchPoint=" + + this.switchPoint + + "}" + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/responsecurve/ScaledXResponseCurve.java b/src/com/hypixel/hytale/server/core/asset/type/responsecurve/ScaledXResponseCurve.java new file mode 100644 index 0000000..4fbb1fd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/responsecurve/ScaledXResponseCurve.java @@ -0,0 +1,72 @@ +package com.hypixel.hytale.server.core.asset.type.responsecurve; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.asset.type.responsecurve.config.ResponseCurve; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class ScaledXResponseCurve extends ScaledResponseCurve { + public static final BuilderCodec CODEC = BuilderCodec.builder(ScaledXResponseCurve.class, ScaledXResponseCurve::new) + .documentation("A response curve scaled only on the x axis.") + .append(new KeyedCodec<>("ResponseCurve", Codec.STRING), (curve, s) -> curve.responseCurve = s, curve -> curve.responseCurve) + .documentation("The response curve to scale") + .addValidator(Validators.nonNull()) + .addValidator(ResponseCurve.VALIDATOR_CACHE.getValidator()) + .add() + .append(new KeyedCodec<>("XRange", Codec.DOUBLE_ARRAY), (curve, o) -> curve.xRange = o, curve -> new double[]{curve.xRange[0], curve.xRange[1]}) + .documentation("The range to map the x axis to. e.g. [ 0, 10 ]") + .addValidator(Validators.doubleArraySize(2)) + .addValidator(Validators.monotonicSequentialDoubleArrayValidator()) + .add() + .afterDecode(curve -> { + if (curve.responseCurve != null) { + int index = ResponseCurve.getAssetMap().getIndex(curve.responseCurve); + curve.responseCurveReference = new ResponseCurve.Reference(index, ResponseCurve.getAssetMap().getAsset(index)); + } + }) + .build(); + public static final double[] DEFAULT_RANGE = new double[]{0.0, 1.0}; + protected String responseCurve; + protected ResponseCurve.Reference responseCurveReference; + protected double[] xRange = DEFAULT_RANGE; + + public ScaledXResponseCurve(String responseCurve, double[] xRange) { + this.responseCurve = responseCurve; + this.xRange = xRange; + } + + protected ScaledXResponseCurve() { + } + + public String getResponseCurve() { + return this.responseCurve; + } + + public double[] getXRange() { + return this.xRange; + } + + @Override + public double computeY(double x) { + return MathUtil.clamp(this.computeNormalisedY(x), 0.0, 1.0); + } + + protected double computeNormalisedY(double x) { + ResponseCurve curve = this.responseCurveReference.get(); + double minX = this.xRange[0]; + double maxX = this.xRange[1]; + x = MathUtil.clamp(x, minX, maxX); + double normalisedX = (x - minX) / (maxX - minX); + return curve.computeY(normalisedX); + } + + @Nonnull + @Override + public String toString() { + return "ScaledXResponseCurve{responseCurve=" + this.responseCurve + ", xRange=" + Arrays.toString(this.xRange) + "}" + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/responsecurve/ScaledXYResponseCurve.java b/src/com/hypixel/hytale/server/core/asset/type/responsecurve/ScaledXYResponseCurve.java new file mode 100644 index 0000000..df3de44 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/responsecurve/ScaledXYResponseCurve.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.core.asset.type.responsecurve; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.util.MathUtil; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class ScaledXYResponseCurve extends ScaledXResponseCurve { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ScaledXYResponseCurve.class, ScaledXYResponseCurve::new, ScaledXResponseCurve.CODEC + ) + .documentation("A response curve that is scaled on both the x and y axes.") + .append(new KeyedCodec<>("YRange", Codec.DOUBLE_ARRAY), (curve, o) -> curve.yRange = o, curve -> new double[]{curve.yRange[0], curve.yRange[1]}) + .documentation("The range to map the y axis to. e.g. [ 0, 10 ]") + .addValidator(Validators.doubleArraySize(2)) + .addValidator(Validators.monotonicSequentialDoubleArrayValidator()) + .add() + .build(); + protected double[] yRange = DEFAULT_RANGE; + + public ScaledXYResponseCurve(String responseCurve, double[] xRange, double[] yRange) { + super(responseCurve, xRange); + this.yRange = yRange; + } + + protected ScaledXYResponseCurve() { + } + + public double[] getYRange() { + return this.yRange; + } + + @Override + public double computeY(double x) { + double normalisedY = this.computeNormalisedY(x); + double minY = this.yRange[0]; + double maxY = this.yRange[1]; + return MathUtil.clamp(minY + (maxY - minY) * normalisedY, minY, maxY); + } + + @Nonnull + @Override + public String toString() { + return "ScaledXYResponseCurve{yRange=" + Arrays.toString(this.yRange) + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/responsecurve/config/ExponentialResponseCurve.java b/src/com/hypixel/hytale/server/core/asset/type/responsecurve/config/ExponentialResponseCurve.java new file mode 100644 index 0000000..b887007 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/responsecurve/config/ExponentialResponseCurve.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.server.core.asset.type.responsecurve.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class ExponentialResponseCurve extends ResponseCurve { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ExponentialResponseCurve.class, ExponentialResponseCurve::new, BASE_CODEC + ) + .documentation("An response curve which changes at an exponential rate.") + .appendInherited( + new KeyedCodec<>("Slope", Codec.DOUBLE), (curve, d) -> curve.slope = d, curve -> curve.slope, (curve, parent) -> curve.slope = parent.slope + ) + .documentation("The slope of the curve.") + .add() + .appendInherited( + new KeyedCodec<>("Exponent", Codec.DOUBLE), + (curve, d) -> curve.exponent = d, + curve -> curve.exponent, + (curve, parent) -> curve.exponent = parent.exponent + ) + .documentation("The exponent used to generate this curve. 1 is linear, 2 results in a quadratic parabola, and 3 in a cubic curve, etc.") + .add() + .appendInherited( + new KeyedCodec<>("HorizontalShift", Codec.DOUBLE), + (curve, d) -> curve.horizontalShift = d, + curve -> curve.horizontalShift, + (curve, parent) -> curve.horizontalShift = parent.horizontalShift + ) + .documentation("The horizontal shift to apply to the curve. This decides how far the curve is shifted left or right along the x axis.") + .addValidator(Validators.range(-1.0, 1.0)) + .add() + .appendInherited( + new KeyedCodec<>("VerticalShift", Codec.DOUBLE), + (curve, d) -> curve.verticalShift = d, + curve -> curve.verticalShift, + (curve, parent) -> curve.verticalShift = parent.verticalShift + ) + .documentation("The vertical shift to apply to the curve. This decides how far the curve is shifted up or down along the y axis.") + .addValidator(Validators.range(-1.0, 1.0)) + .add() + .build(); + protected double slope = 1.0; + protected double exponent = 1.0; + protected double horizontalShift; + protected double verticalShift; + + public ExponentialResponseCurve(double slope, double exponent, double horizontalShift, double verticalShift) { + this.slope = slope; + this.exponent = exponent; + this.horizontalShift = horizontalShift; + this.verticalShift = verticalShift; + } + + public ExponentialResponseCurve(String id) { + super(id); + } + + protected ExponentialResponseCurve() { + } + + @Override + public double computeY(double x) { + if (!(x < 0.0) && !(x > 1.0)) { + return this.slope * Math.pow(x - this.horizontalShift, this.exponent) + this.verticalShift; + } else { + throw new IllegalArgumentException("X must be between 0.0 and 1.0"); + } + } + + public double getSlope() { + return this.slope; + } + + public double getExponent() { + return this.exponent; + } + + public double getHorizontalShift() { + return this.horizontalShift; + } + + public double getVerticalShift() { + return this.verticalShift; + } + + @Nonnull + @Override + public String toString() { + return "ExponentialResponseCurve{slope=" + + this.slope + + ", exponent=" + + this.exponent + + ", horizontalShift=" + + this.horizontalShift + + ", verticalShift=" + + this.verticalShift + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/responsecurve/config/LogisticResponseCurve.java b/src/com/hypixel/hytale/server/core/asset/type/responsecurve/config/LogisticResponseCurve.java new file mode 100644 index 0000000..a9c6033 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/responsecurve/config/LogisticResponseCurve.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.server.core.asset.type.responsecurve.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class LogisticResponseCurve extends ResponseCurve { + public static final BuilderCodec CODEC = BuilderCodec.builder(LogisticResponseCurve.class, LogisticResponseCurve::new, BASE_CODEC) + .documentation("A response curve with a logistic rate of change.") + .appendInherited( + new KeyedCodec<>("RateOfChange", Codec.DOUBLE), + (curve, d) -> curve.rateOfChange = d, + curve -> curve.rateOfChange, + (curve, parent) -> curve.rateOfChange = parent.rateOfChange + ) + .documentation("The rate of change of the curve - similar to **Slope** in the exponential curve.") + .add() + .appendInherited( + new KeyedCodec<>("Ceiling", Codec.DOUBLE), (curve, d) -> curve.ceiling = d, curve -> curve.ceiling, (curve, parent) -> curve.ceiling = parent.ceiling + ) + .documentation( + "The total height of the curve between its two plateaus. Using a negative value with vertical offsets allows the curve to act as a diminishing factor" + ) + .add() + .appendInherited( + new KeyedCodec<>("HorizontalShift", Codec.DOUBLE), + (curve, d) -> curve.horizontalShift = d, + curve -> curve.horizontalShift, + (curve, parent) -> curve.horizontalShift = parent.horizontalShift + ) + .documentation("The horizontal shift to apply to the curve. This decides how far the curve is shifted left or right along the x axis.") + .add() + .appendInherited( + new KeyedCodec<>("VerticalShift", Codec.DOUBLE), + (curve, d) -> curve.verticalShift = d, + curve -> curve.verticalShift, + (curve, parent) -> curve.verticalShift = parent.verticalShift + ) + .documentation("The vertical shift to apply to the curve. This decides how far the curve is shifted up or down along the y axis.") + .add() + .build(); + protected double rateOfChange = 1.0; + protected double ceiling = 1.0; + protected double horizontalShift = 0.5; + protected double verticalShift = 0.0; + + public LogisticResponseCurve(double rateOfChange, double ceiling, double horizontalShift, double verticalShift) { + this.rateOfChange = rateOfChange; + this.ceiling = ceiling; + this.horizontalShift = horizontalShift; + this.verticalShift = verticalShift; + } + + protected LogisticResponseCurve() { + } + + @Override + public double computeY(double x) { + if (!(x < 0.0) && !(x > 1.0)) { + return this.ceiling / (1.0 + Math.pow(100.0, 2.0 * this.rateOfChange * (this.horizontalShift - x))) + this.verticalShift; + } else { + throw new IllegalArgumentException("X must be between 0.0 and 1.0"); + } + } + + public double getRateOfChange() { + return this.rateOfChange; + } + + public double getCeiling() { + return this.ceiling; + } + + public double getHorizontalShift() { + return this.horizontalShift; + } + + public double getVerticalShift() { + return this.verticalShift; + } + + @Nonnull + @Override + public String toString() { + return "LogisticResponseCurve{rateOfChange=" + + this.rateOfChange + + ", ceiling=" + + this.ceiling + + ", horizontalShift=" + + this.horizontalShift + + ", verticalShift=" + + this.verticalShift + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/responsecurve/config/ResponseCurve.java b/src/com/hypixel/hytale/server/core/asset/type/responsecurve/config/ResponseCurve.java new file mode 100644 index 0000000..7a895d8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/responsecurve/config/ResponseCurve.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.server.core.asset.type.responsecurve.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import java.lang.ref.WeakReference; +import javax.annotation.Nonnull; + +public abstract class ResponseCurve implements JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.extraData = data, t -> t.extraData + ); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(ResponseCurve.class) + .afterDecode(responseCurve -> responseCurve.reference = new WeakReference<>(responseCurve)) + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ResponseCurve::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data extraData; + protected String id; + protected WeakReference reference; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ResponseCurve.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public ResponseCurve(String id) { + this.id = id; + } + + protected ResponseCurve() { + } + + public String getId() { + return this.id; + } + + public WeakReference getReference() { + return this.reference; + } + + public abstract double computeY(double var1); + + @Nonnull + @Override + public String toString() { + return "ResponseCurve{id='" + this.id + "'}"; + } + + static { + CODEC.register("Exponential", ExponentialResponseCurve.class, ExponentialResponseCurve.CODEC); + CODEC.register("Logistic", LogisticResponseCurve.class, LogisticResponseCurve.CODEC); + CODEC.register("SineWave", SineWaveResponseCurve.class, SineWaveResponseCurve.CODEC); + } + + public static class Reference { + private int index; + private WeakReference reference; + + public Reference(int index, @Nonnull ResponseCurve responseCurve) { + this.index = index; + this.reference = responseCurve.getReference(); + } + + @Nonnull + public ResponseCurve get() { + ResponseCurve responseCurve = this.reference.get(); + if (responseCurve == null) { + responseCurve = ResponseCurve.getAssetMap().getAsset(this.index); + this.reference = responseCurve.getReference(); + } + + return responseCurve; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/responsecurve/config/SineWaveResponseCurve.java b/src/com/hypixel/hytale/server/core/asset/type/responsecurve/config/SineWaveResponseCurve.java new file mode 100644 index 0000000..8d21b79 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/responsecurve/config/SineWaveResponseCurve.java @@ -0,0 +1,95 @@ +package com.hypixel.hytale.server.core.asset.type.responsecurve.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.util.TrigMathUtil; +import javax.annotation.Nonnull; + +public class SineWaveResponseCurve extends ResponseCurve { + public static final BuilderCodec CODEC = BuilderCodec.builder(SineWaveResponseCurve.class, SineWaveResponseCurve::new, BASE_CODEC) + .documentation("A response curve with a sine wave shape.") + .appendInherited( + new KeyedCodec<>("Amplitude", Codec.DOUBLE), + (curve, d) -> curve.amplitude = d, + curve -> curve.amplitude, + (curve, parent) -> curve.amplitude = parent.amplitude + ) + .documentation("The vertical distance between the horizontal axis and the max/min value of the function.") + .add() + .appendInherited( + new KeyedCodec<>("Frequency", Codec.DOUBLE), + (curve, d) -> curve.frequency = d, + curve -> curve.frequency, + (curve, parent) -> curve.frequency = parent.frequency + ) + .documentation("The frequency of the sine wave's repetition (e.g. set to 1, the full pattern will appear once in the 0-1 range, twice with 2, etc).") + .add() + .appendInherited( + new KeyedCodec<>("HorizontalShift", Codec.DOUBLE), + (curve, d) -> curve.horizontalShift = d, + curve -> curve.horizontalShift, + (curve, parent) -> curve.horizontalShift = parent.horizontalShift + ) + .documentation("The horizontal shift to apply to the curve,. This decides how far the curve is shifted left or right along the x axis.") + .addValidator(Validators.range(-1.0, 1.0)) + .add() + .appendInherited( + new KeyedCodec<>("VerticalShift", Codec.DOUBLE), + (curve, d) -> curve.verticalShift = d, + curve -> curve.verticalShift, + (curve, parent) -> curve.verticalShift = parent.verticalShift + ) + .documentation("The vertical shift to apply to the curve. This decides how far the curve is shifted up or down along the y axis.") + .addValidator(Validators.range(-1.0, 1.0)) + .add() + .build(); + protected double amplitude = 1.0; + protected double frequency = 0.5; + protected double horizontalShift; + protected double verticalShift; + + protected SineWaveResponseCurve() { + } + + @Override + public double computeY(double x) { + if (!(x < 0.0) && !(x > 1.0)) { + return this.amplitude * TrigMathUtil.sin((float) (Math.PI * 2) * this.frequency * x + this.horizontalShift) + this.verticalShift; + } else { + throw new IllegalArgumentException("X must be between 0.0 and 1.0"); + } + } + + public double getAmplitude() { + return this.amplitude; + } + + public double getFrequency() { + return this.frequency; + } + + public double getHorizontalShift() { + return this.horizontalShift; + } + + public double getVerticalShift() { + return this.verticalShift; + } + + @Nonnull + @Override + public String toString() { + return "SineWaveResponseCurve{amplitude=" + + this.amplitude + + ", frequency=" + + this.frequency + + ", horizontalShift=" + + this.horizontalShift + + ", verticalShift=" + + this.verticalShift + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/reverbeffect/ReverbEffectPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/reverbeffect/ReverbEffectPacketGenerator.java new file mode 100644 index 0000000..100d212 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/reverbeffect/ReverbEffectPacketGenerator.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.asset.type.reverbeffect; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateReverbEffects; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.reverbeffect.config.ReverbEffect; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class ReverbEffectPacketGenerator extends SimpleAssetPacketGenerator> { + public ReverbEffectPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + UpdateReverbEffects packet = new UpdateReverbEffects(); + packet.type = UpdateType.Init; + packet.effects = new Int2ObjectOpenHashMap<>(assets.size()); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.effects.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateUpdatePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets) { + UpdateReverbEffects packet = new UpdateReverbEffects(); + packet.type = UpdateType.AddOrUpdate; + packet.effects = new Int2ObjectOpenHashMap<>(loadedAssets.size()); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.effects.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateRemovePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed) { + UpdateReverbEffects packet = new UpdateReverbEffects(); + packet.type = UpdateType.Remove; + packet.effects = new Int2ObjectOpenHashMap<>(removed.size()); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.effects.put(index, null); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/reverbeffect/config/ReverbEffect.java b/src/com/hypixel/hytale/server/core/asset/type/reverbeffect/config/ReverbEffect.java new file mode 100644 index 0000000..ed3b3d7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/reverbeffect/config/ReverbEffect.java @@ -0,0 +1,325 @@ +package com.hypixel.hytale.server.core.asset.type.reverbeffect.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorPreview; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.AudioUtil; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import javax.annotation.Nonnull; + +public class ReverbEffect + implements JsonAssetWithMap>, + NetworkSerializable { + public static final int EMPTY_ID = 0; + public static final String EMPTY = "EMPTY"; + public static final ReverbEffect EMPTY_REVERB_EFFECT = new ReverbEffect("EMPTY"); + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ReverbEffect.class, ReverbEffect::new, Codec.STRING, (t, k) -> t.id = k, t -> t.id, (asset, data) -> asset.data = data, asset -> asset.data + ) + .documentation("An asset used to define a reverb audio effect.") + .metadata(new UIEditorPreview(UIEditorPreview.PreviewType.REVERB_EFFECT)) + .appendInherited( + new KeyedCodec<>("DryGain", Codec.FLOAT), + (reverb, f) -> reverb.dryGain = AudioUtil.decibelsToLinearGain(f), + reverb -> AudioUtil.linearGainToDecibels(reverb.dryGain), + (reverb, parent) -> reverb.dryGain = parent.dryGain + ) + .metadata(new UIEditor(new UIEditor.FormattedNumber(null, " dB", null))) + .addValidator(Validators.range(-100.0F, 10.0F)) + .documentation("Dry signal gain adjustment in decibels.") + .add() + .appendInherited( + new KeyedCodec<>("ModalDensity", Codec.FLOAT), + (reverb, f) -> reverb.modalDensity = f, + reverb -> reverb.modalDensity, + (reverb, parent) -> reverb.modalDensity = parent.modalDensity + ) + .addValidator(Validators.range(0.0F, 1.0F)) + .documentation("Modal density of the reverb.") + .add() + .appendInherited( + new KeyedCodec<>("Diffusion", Codec.FLOAT), + (reverb, f) -> reverb.diffusion = f, + reverb -> reverb.diffusion, + (reverb, parent) -> reverb.diffusion = parent.diffusion + ) + .addValidator(Validators.range(0.0F, 1.0F)) + .documentation("Diffusion of the reverb reflections.") + .add() + .appendInherited( + new KeyedCodec<>("Gain", Codec.FLOAT), + (reverb, f) -> reverb.gain = AudioUtil.decibelsToLinearGain(f), + reverb -> AudioUtil.linearGainToDecibels(reverb.gain), + (reverb, parent) -> reverb.gain = parent.gain + ) + .metadata(new UIEditor(new UIEditor.FormattedNumber(null, " dB", null))) + .addValidator(Validators.range(-100.0F, 0.0F)) + .documentation("Overall reverb gain in decibels.") + .add() + .appendInherited( + new KeyedCodec<>("HighFrequencyGain", Codec.FLOAT), + (reverb, f) -> reverb.highFrequencyGain = AudioUtil.decibelsToLinearGain(f), + reverb -> AudioUtil.linearGainToDecibels(reverb.highFrequencyGain), + (reverb, parent) -> reverb.highFrequencyGain = parent.highFrequencyGain + ) + .metadata(new UIEditor(new UIEditor.FormattedNumber(null, " dB", null))) + .addValidator(Validators.range(-100.0F, 0.0F)) + .documentation("High frequency gain in decibels.") + .add() + .appendInherited( + new KeyedCodec<>("DecayTime", Codec.FLOAT), + (reverb, f) -> reverb.decayTime = f, + reverb -> reverb.decayTime, + (reverb, parent) -> reverb.decayTime = parent.decayTime + ) + .addValidator(Validators.range(0.1F, 20.0F)) + .documentation("Decay time in seconds.") + .add() + .appendInherited( + new KeyedCodec<>("HighFrequencyDecayRatio", Codec.FLOAT), + (reverb, f) -> reverb.highFrequencyDecayRatio = f, + reverb -> reverb.highFrequencyDecayRatio, + (reverb, parent) -> reverb.highFrequencyDecayRatio = parent.highFrequencyDecayRatio + ) + .addValidator(Validators.range(0.1F, 2.0F)) + .documentation("High frequency decay ratio.") + .add() + .appendInherited( + new KeyedCodec<>("ReflectionGain", Codec.FLOAT), + (reverb, f) -> reverb.reflectionGain = AudioUtil.decibelsToLinearGain(f), + reverb -> AudioUtil.linearGainToDecibels(reverb.reflectionGain), + (reverb, parent) -> reverb.reflectionGain = parent.reflectionGain + ) + .metadata(new UIEditor(new UIEditor.FormattedNumber(null, " dB", null))) + .addValidator(Validators.range(-100.0F, 10.0F)) + .documentation("Early reflections gain in decibels.") + .add() + .appendInherited( + new KeyedCodec<>("ReflectionDelay", Codec.FLOAT), + (reverb, f) -> reverb.reflectionDelay = f, + reverb -> reverb.reflectionDelay, + (reverb, parent) -> reverb.reflectionDelay = parent.reflectionDelay + ) + .addValidator(Validators.range(0.0F, 0.3F)) + .documentation("Early reflections delay in seconds.") + .add() + .appendInherited( + new KeyedCodec<>("LateReverbGain", Codec.FLOAT), + (reverb, f) -> reverb.lateReverbGain = AudioUtil.decibelsToLinearGain(f), + reverb -> AudioUtil.linearGainToDecibels(reverb.lateReverbGain), + (reverb, parent) -> reverb.lateReverbGain = parent.lateReverbGain + ) + .metadata(new UIEditor(new UIEditor.FormattedNumber(null, " dB", null))) + .addValidator(Validators.range(-100.0F, 20.0F)) + .documentation("Late reverb gain in decibels.") + .add() + .appendInherited( + new KeyedCodec<>("LateReverbDelay", Codec.FLOAT), + (reverb, f) -> reverb.lateReverbDelay = f, + reverb -> reverb.lateReverbDelay, + (reverb, parent) -> reverb.lateReverbDelay = parent.lateReverbDelay + ) + .addValidator(Validators.range(0.0F, 0.1F)) + .documentation("Late reverb delay in seconds.") + .add() + .appendInherited( + new KeyedCodec<>("RoomRolloffFactor", Codec.FLOAT), + (reverb, f) -> reverb.roomRolloffFactor = f, + reverb -> reverb.roomRolloffFactor, + (reverb, parent) -> reverb.roomRolloffFactor = parent.roomRolloffFactor + ) + .addValidator(Validators.range(0.0F, 10.0F)) + .documentation("Room rolloff factor.") + .add() + .appendInherited( + new KeyedCodec<>("AirAbsorbptionHighFrequencyGain", Codec.FLOAT), + (reverb, f) -> reverb.airAbsorptionHighFrequencyGain = AudioUtil.decibelsToLinearGain(f), + reverb -> AudioUtil.linearGainToDecibels(reverb.airAbsorptionHighFrequencyGain), + (reverb, parent) -> reverb.airAbsorptionHighFrequencyGain = parent.airAbsorptionHighFrequencyGain + ) + .metadata(new UIEditor(new UIEditor.FormattedNumber(null, " dB", null))) + .addValidator(Validators.range(-1.0F, 0.0F)) + .documentation("Air absorption high frequency gain in decibels.") + .add() + .appendInherited( + new KeyedCodec<>("LimitDecayHighFrequency", Codec.BOOLEAN), + (reverb, b) -> reverb.limitDecayHighFrequency = b, + reverb -> reverb.limitDecayHighFrequency, + (reverb, parent) -> reverb.limitDecayHighFrequency = parent.limitDecayHighFrequency + ) + .documentation("Whether to limit high frequency decay time.") + .add() + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ReverbEffect::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected float dryGain = AudioUtil.decibelsToLinearGain(0.0F); + protected float modalDensity = 1.0F; + protected float diffusion = 1.0F; + protected float gain = AudioUtil.decibelsToLinearGain(-10.0F); + protected float highFrequencyGain = AudioUtil.decibelsToLinearGain(-1.0F); + protected float decayTime = 1.49F; + protected float highFrequencyDecayRatio = 0.83F; + protected float reflectionGain = AudioUtil.decibelsToLinearGain(-26.0F); + protected float reflectionDelay = 0.007F; + protected float lateReverbGain = AudioUtil.decibelsToLinearGain(2.0F); + protected float lateReverbDelay = 0.011F; + protected float roomRolloffFactor = 0.0F; + protected float airAbsorptionHighFrequencyGain = AudioUtil.decibelsToLinearGain(-0.05F); + protected boolean limitDecayHighFrequency = true; + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ReverbEffect.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public ReverbEffect(String id) { + this.id = id; + } + + protected ReverbEffect() { + } + + public String getId() { + return this.id; + } + + public float getDryGain() { + return this.dryGain; + } + + public float getModalDensity() { + return this.modalDensity; + } + + public float getDiffusion() { + return this.diffusion; + } + + public float getGain() { + return this.gain; + } + + public float getHighFrequencyGain() { + return this.highFrequencyGain; + } + + public float getDecayTime() { + return this.decayTime; + } + + public float getHighFrequencyDecayRatio() { + return this.highFrequencyDecayRatio; + } + + public float getReflectionGain() { + return this.reflectionGain; + } + + public float getReflectionDelay() { + return this.reflectionDelay; + } + + public float getLateReverbGain() { + return this.lateReverbGain; + } + + public float getLateReverbDelay() { + return this.lateReverbDelay; + } + + public float getRoomRolloffFactor() { + return this.roomRolloffFactor; + } + + public float getAirAbsorptionHighFrequencyGain() { + return this.airAbsorptionHighFrequencyGain; + } + + public boolean isLimitDecayHighFrequency() { + return this.limitDecayHighFrequency; + } + + @Nonnull + @Override + public String toString() { + return "ReverbEffect{id='" + + this.id + + "', dryGain=" + + this.dryGain + + ", modalDensity=" + + this.modalDensity + + ", diffusion=" + + this.diffusion + + ", gain=" + + this.gain + + ", highFrequencyGain=" + + this.highFrequencyGain + + ", decayTime=" + + this.decayTime + + ", highFrequencyDecayRatio=" + + this.highFrequencyDecayRatio + + ", reflectionGain=" + + this.reflectionGain + + ", reflectionDelay=" + + this.reflectionDelay + + ", lateReverbGain=" + + this.lateReverbGain + + ", lateReverbDelay=" + + this.lateReverbDelay + + ", roomRolloffFactor=" + + this.roomRolloffFactor + + ", airAbsorptionHighFrequencyGain=" + + this.airAbsorptionHighFrequencyGain + + ", limitDecayHighFrequency=" + + this.limitDecayHighFrequency + + "}"; + } + + @Nonnull + public com.hypixel.hytale.protocol.ReverbEffect toPacket() { + com.hypixel.hytale.protocol.ReverbEffect cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.ReverbEffect packet = new com.hypixel.hytale.protocol.ReverbEffect(); + packet.id = this.id; + packet.dryGain = this.dryGain; + packet.modalDensity = this.modalDensity; + packet.diffusion = this.diffusion; + packet.gain = this.gain; + packet.highFrequencyGain = this.highFrequencyGain; + packet.decayTime = this.decayTime; + packet.highFrequencyDecayRatio = this.highFrequencyDecayRatio; + packet.reflectionGain = this.reflectionGain; + packet.reflectionDelay = this.reflectionDelay; + packet.lateReverbGain = this.lateReverbGain; + packet.lateReverbDelay = this.lateReverbDelay; + packet.roomRolloffFactor = this.roomRolloffFactor; + packet.airAbsorptionHighFrequencyGain = this.airAbsorptionHighFrequencyGain; + packet.limitDecayHighFrequency = this.limitDecayHighFrequency; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/soundevent/SoundEventPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/soundevent/SoundEventPacketGenerator.java new file mode 100644 index 0000000..8daa71e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/soundevent/SoundEventPacketGenerator.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.asset.type.soundevent; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateSoundEvents; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class SoundEventPacketGenerator extends SimpleAssetPacketGenerator> { + public SoundEventPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + UpdateSoundEvents packet = new UpdateSoundEvents(); + packet.type = UpdateType.Init; + packet.soundEvents = new Int2ObjectOpenHashMap<>(assets.size()); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.soundEvents.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateUpdatePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets) { + UpdateSoundEvents packet = new UpdateSoundEvents(); + packet.type = UpdateType.AddOrUpdate; + packet.soundEvents = new Int2ObjectOpenHashMap<>(loadedAssets.size()); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.soundEvents.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateRemovePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed) { + UpdateSoundEvents packet = new UpdateSoundEvents(); + packet.type = UpdateType.Remove; + packet.soundEvents = new Int2ObjectOpenHashMap<>(removed.size()); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.soundEvents.put(index, null); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/soundevent/config/SoundEvent.java b/src/com/hypixel/hytale/server/core/asset/type/soundevent/config/SoundEvent.java new file mode 100644 index 0000000..d681965 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/soundevent/config/SoundEvent.java @@ -0,0 +1,313 @@ +package com.hypixel.hytale.server.core.asset.type.soundevent.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.AudioUtil; +import com.hypixel.hytale.server.core.asset.type.audiocategory.config.AudioCategory; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SoundEvent + implements JsonAssetWithMap>, + NetworkSerializable { + public static final int EMPTY_ID = 0; + public static final String EMPTY = "EMPTY"; + public static final SoundEvent EMPTY_SOUND_EVENT = new SoundEvent("EMPTY"); + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + SoundEvent.class, SoundEvent::new, Codec.STRING, (t, k) -> t.id = k, t -> t.id, (asset, data) -> asset.data = data, asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("Volume", Codec.FLOAT), + (soundEvent, f) -> soundEvent.volume = AudioUtil.decibelsToLinearGain(f), + soundEvent -> AudioUtil.linearGainToDecibels(soundEvent.volume), + (soundEvent, parent) -> soundEvent.volume = parent.volume + ) + .metadata(new UIEditor(new UIEditor.FormattedNumber(null, " dB", null))) + .addValidator(Validators.range(-100.0F, 10.0F)) + .documentation("Volume adjustment of the sound event in decibels.") + .add() + .appendInherited( + new KeyedCodec<>("Pitch", Codec.FLOAT), + (soundEvent, f) -> soundEvent.pitch = AudioUtil.semitonesToLinearPitch(f), + soundEvent -> AudioUtil.linearPitchToSemitones(soundEvent.pitch), + (soundEvent, parent) -> soundEvent.pitch = parent.pitch + ) + .addValidator(Validators.range(-12.0F, 12.0F)) + .documentation("Pitch adjustment of the sound event in semitones.") + .add() + .appendInherited( + new KeyedCodec<>("MusicDuckingVolume", Codec.FLOAT), + (soundEvent, f) -> soundEvent.musicDuckingVolume = AudioUtil.decibelsToLinearGain(f), + soundEvent -> AudioUtil.linearGainToDecibels(soundEvent.musicDuckingVolume), + (soundEvent, parent) -> soundEvent.musicDuckingVolume = parent.musicDuckingVolume + ) + .addValidator(Validators.range(-100.0F, 0.0F)) + .documentation("Amount to duck music volume when playing in decibels.") + .add() + .appendInherited( + new KeyedCodec<>("AmbientDuckingVolume", Codec.FLOAT), + (soundEvent, f) -> soundEvent.ambientDuckingVolume = AudioUtil.decibelsToLinearGain(f), + soundEvent -> AudioUtil.linearGainToDecibels(soundEvent.ambientDuckingVolume), + (soundEvent, parent) -> soundEvent.ambientDuckingVolume = parent.ambientDuckingVolume + ) + .addValidator(Validators.range(-100.0F, 0.0F)) + .documentation("Amount to duck ambient sounds when playing in decibels.") + .add() + .appendInherited( + new KeyedCodec<>("StartAttenuationDistance", Codec.FLOAT), + (soundEvent, f) -> soundEvent.startAttenuationDistance = f, + soundEvent -> soundEvent.startAttenuationDistance, + (soundEvent, parent) -> soundEvent.startAttenuationDistance = parent.startAttenuationDistance + ) + .documentation("Distance at which to begin attenuation in blocks.") + .add() + .appendInherited( + new KeyedCodec<>("MaxDistance", Codec.FLOAT), + (soundEvent, f) -> soundEvent.maxDistance = f, + soundEvent -> soundEvent.maxDistance, + (soundEvent, parent) -> soundEvent.maxDistance = parent.maxDistance + ) + .documentation("Maximum distance at which this sound event can be heard in blocks (i.e. the distance at which it's attenuated to zero).") + .add() + .appendInherited( + new KeyedCodec<>("MaxInstance", Codec.INTEGER), + (soundEvent, i) -> soundEvent.maxInstance = i, + soundEvent -> soundEvent.maxInstance, + (soundEvent, parent) -> soundEvent.maxInstance = parent.maxInstance + ) + .addValidator(Validators.range(1, 100)) + .documentation("Max concurrent number of instances of this sound event.") + .add() + .appendInherited( + new KeyedCodec<>("PreventSoundInterruption", Codec.BOOLEAN), + (soundEvent, b) -> soundEvent.preventSoundInterruption = b, + soundEvent -> soundEvent.preventSoundInterruption, + (soundEvent, parent) -> soundEvent.preventSoundInterruption = parent.preventSoundInterruption + ) + .documentation("Whether to prevent interruption of this sound event.") + .add() + .appendInherited( + new KeyedCodec<>("Layers", new ArrayCodec<>(SoundEventLayer.CODEC, SoundEventLayer[]::new)), + (soundEvent, objects) -> soundEvent.layers = objects, + soundEvent -> soundEvent.layers, + (soundEvent, parent) -> soundEvent.layers = parent.layers + ) + .addValidator(Validators.nonEmptyArray()) + .documentation("The layered sounds that make up this sound event.") + .add() + .appendInherited( + new KeyedCodec<>("AudioCategory", Codec.STRING), + (soundEvent, s) -> soundEvent.audioCategoryId = s, + soundEvent -> soundEvent.audioCategoryId, + (soundEvent, parent) -> soundEvent.audioCategoryId = parent.audioCategoryId + ) + .addValidator(AudioCategory.VALIDATOR_CACHE.getValidator()) + .documentation("Audio category to assign this sound event to for additional property routing.") + .add() + .afterDecode(SoundEvent::processConfig) + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(SoundEvent::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected transient float volume = 1.0F; + protected transient float pitch = 1.0F; + protected transient float musicDuckingVolume = 1.0F; + protected transient float ambientDuckingVolume = 1.0F; + protected float startAttenuationDistance = 2.0F; + protected float maxDistance = 16.0F; + protected int maxInstance = 50; + protected boolean preventSoundInterruption = false; + protected SoundEventLayer[] layers; + @Nullable + protected String audioCategoryId = null; + protected transient int audioCategoryIndex = 0; + protected transient int highestNumberOfChannels = 0; + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(SoundEvent.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + protected void processConfig() { + if (this.audioCategoryId != null) { + this.audioCategoryIndex = AudioCategory.getAssetMap().getIndex(this.audioCategoryId); + } + + if (this.layers != null) { + for (SoundEventLayer layer : this.layers) { + if (layer.highestNumberOfChannels > this.highestNumberOfChannels) { + this.highestNumberOfChannels = layer.highestNumberOfChannels; + } + } + } + } + + public SoundEvent( + String id, + float volume, + float pitch, + float musicDuckingVolume, + float ambientDuckingVolume, + float startAttenuationDistance, + float maxDistance, + int maxInstance, + boolean preventSoundInterruption, + SoundEventLayer[] layers + ) { + this.id = id; + this.volume = volume; + this.pitch = pitch; + this.musicDuckingVolume = musicDuckingVolume; + this.ambientDuckingVolume = ambientDuckingVolume; + this.startAttenuationDistance = startAttenuationDistance; + this.maxDistance = maxDistance; + this.maxInstance = maxInstance; + this.preventSoundInterruption = preventSoundInterruption; + this.layers = layers; + } + + public SoundEvent(String id) { + this.id = id; + } + + protected SoundEvent() { + } + + public String getId() { + return this.id; + } + + public float getVolume() { + return this.volume; + } + + public float getPitch() { + return this.pitch; + } + + public float getMusicDuckingVolume() { + return this.musicDuckingVolume; + } + + public float getAmbientDuckingVolume() { + return this.ambientDuckingVolume; + } + + public float getStartAttenuationDistance() { + return this.startAttenuationDistance; + } + + public float getMaxDistance() { + return this.maxDistance; + } + + public int getMaxInstance() { + return this.maxInstance; + } + + public boolean getPreventSoundInterruption() { + return this.preventSoundInterruption; + } + + public SoundEventLayer[] getLayers() { + return this.layers; + } + + @Nullable + public String getAudioCategoryId() { + return this.audioCategoryId; + } + + public int getAudioCategoryIndex() { + return this.audioCategoryIndex; + } + + public int getHighestNumberOfChannels() { + return this.highestNumberOfChannels; + } + + @Nonnull + @Override + public String toString() { + return "SoundEvent{id='" + + this.id + + "', volume=" + + this.volume + + ", pitch=" + + this.pitch + + ", musicDuckingVolume=" + + this.musicDuckingVolume + + ", ambientDuckingVolume=" + + this.ambientDuckingVolume + + ", startAttenuationDistance=" + + this.startAttenuationDistance + + ", maxDistance=" + + this.maxDistance + + ", maxInstance=" + + this.maxInstance + + ", preventSoundInterruption=" + + this.preventSoundInterruption + + ", layers=" + + Arrays.toString((Object[])this.layers) + + ", audioCategoryId='" + + this.audioCategoryId + + "', audioCategoryIndex=" + + this.audioCategoryIndex + + ", highestNumberOfChannels=" + + this.highestNumberOfChannels + + "}"; + } + + @Nonnull + public com.hypixel.hytale.protocol.SoundEvent toPacket() { + com.hypixel.hytale.protocol.SoundEvent cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.SoundEvent packet = new com.hypixel.hytale.protocol.SoundEvent(); + packet.id = this.id; + packet.volume = this.volume; + packet.pitch = this.pitch; + packet.musicDuckingVolume = this.musicDuckingVolume; + packet.ambientDuckingVolume = this.ambientDuckingVolume; + packet.startAttenuationDistance = this.startAttenuationDistance; + packet.maxDistance = this.maxDistance; + packet.maxInstance = this.maxInstance; + packet.preventSoundInterruption = this.preventSoundInterruption; + packet.audioCategory = this.audioCategoryIndex; + if (this.layers != null && this.layers.length > 0) { + packet.layers = new com.hypixel.hytale.protocol.SoundEventLayer[this.layers.length]; + + for (int i = 0; i < this.layers.length; i++) { + packet.layers[i] = this.layers[i].toPacket(); + } + } + + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/soundevent/config/SoundEventLayer.java b/src/com/hypixel/hytale/server/core/asset/type/soundevent/config/SoundEventLayer.java new file mode 100644 index 0000000..7894a7d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/soundevent/config/SoundEventLayer.java @@ -0,0 +1,290 @@ +package com.hypixel.hytale.server.core.asset.type.soundevent.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.codec.validation.validator.ArrayValidator; +import com.hypixel.hytale.common.util.AudioUtil; +import com.hypixel.hytale.protocol.SoundEventLayerRandomSettings; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.asset.common.OggVorbisInfoCache; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class SoundEventLayer implements NetworkSerializable { + public static final Codec CODEC = BuilderCodec.builder(SoundEventLayer.class, SoundEventLayer::new) + .append( + new KeyedCodec<>("Volume", Codec.FLOAT), + (soundEventLayer, f) -> soundEventLayer.volume = AudioUtil.decibelsToLinearGain(f), + soundEventLayer -> AudioUtil.linearGainToDecibels(soundEventLayer.volume) + ) + .metadata(new UIEditor(new UIEditor.FormattedNumber(null, " dB", null))) + .addValidator(Validators.range(-100.0F, 10.0F)) + .documentation("Volume offset for this layer in decibels.") + .add() + .append( + new KeyedCodec<>("StartDelay", Codec.FLOAT), (soundEventLayer, f) -> soundEventLayer.startDelay = f, soundEventLayer -> soundEventLayer.startDelay + ) + .documentation("A delay in seconds from when the sound event starts after which this layer should begin.") + .add() + .append( + new KeyedCodec<>("Looping", Codec.BOOLEAN), (soundEventLayer, b) -> soundEventLayer.looping = b, soundEventLayer -> soundEventLayer.looping + ) + .documentation("Whether this layer loops.") + .add() + .append( + new KeyedCodec<>("Probability", Codec.INTEGER), + (soundEventLayer, i) -> soundEventLayer.probability = i, + soundEventLayer -> soundEventLayer.probability + ) + .documentation("The probability of this layer being played when the sound event is triggered in percentage.") + .add() + .append( + new KeyedCodec<>("ProbabilityRerollDelay", Codec.FLOAT), + (soundEventLayer, f) -> soundEventLayer.probabilityRerollDelay = f, + soundEventLayer -> soundEventLayer.probabilityRerollDelay + ) + .documentation("A delay in seconds before the probability of this layer playing can be rerolled to see if it will now play (or not play) again.") + .add() + .append( + new KeyedCodec<>("RandomSettings", SoundEventLayer.RandomSettings.CODEC), + (soundEventLayer, o) -> soundEventLayer.randomSettings = o, + soundEventLayer -> soundEventLayer.randomSettings + ) + .documentation("Randomization settings for parameters of this layer.") + .add() + .append( + new KeyedCodec<>("Files", Codec.STRING_ARRAY), (soundEventLayer, s) -> soundEventLayer.files = s, soundEventLayer -> soundEventLayer.files + ) + .addValidator(Validators.nonEmptyArray()) + .addValidator(new ArrayValidator<>(CommonAssetValidator.SOUNDS)) + .documentation("The list of possible sound files for this layer. One will be chosen at random.") + .add() + .append( + new KeyedCodec<>("RoundRobinHistorySize", Codec.INTEGER), + (soundEventLayer, i) -> soundEventLayer.roundRobinHistorySize = i, + soundEventLayer -> soundEventLayer.roundRobinHistorySize + ) + .addValidator(Validators.range(0, 32)) + .documentation("The same sound file will not repeat within this many plays. 0 disables round-robin behavior.") + .add() + .afterDecode(layer -> { + if (layer.files != null) { + for (String file : layer.files) { + OggVorbisInfoCache.OggVorbisInfo info = OggVorbisInfoCache.getNow(file); + if (info != null && info.channels > layer.highestNumberOfChannels) { + layer.highestNumberOfChannels = info.channels; + } + } + } + }) + .build(); + protected transient float volume = 1.0F; + protected float startDelay = 0.0F; + protected boolean looping = false; + protected int probability = 100; + protected float probabilityRerollDelay = 1.0F; + protected SoundEventLayer.RandomSettings randomSettings = SoundEventLayer.RandomSettings.DEFAULT; + protected String[] files; + protected int roundRobinHistorySize = 0; + protected transient int highestNumberOfChannels = 0; + + public SoundEventLayer( + float volume, + float startDelay, + boolean looping, + int probability, + float probabilityRerollDelay, + SoundEventLayer.RandomSettings randomSettings, + String[] files, + int roundRobinHistorySize + ) { + this.volume = volume; + this.startDelay = startDelay; + this.looping = looping; + this.probability = probability; + this.probabilityRerollDelay = probabilityRerollDelay; + this.randomSettings = randomSettings; + this.files = files; + this.roundRobinHistorySize = roundRobinHistorySize; + } + + protected SoundEventLayer() { + } + + public float getVolume() { + return this.volume; + } + + public float getStartDelay() { + return this.startDelay; + } + + public boolean isLooping() { + return this.looping; + } + + public int getProbability() { + return this.probability; + } + + public float getProbabilityRerollDelay() { + return this.probabilityRerollDelay; + } + + public SoundEventLayer.RandomSettings getRandomSettings() { + return this.randomSettings; + } + + public String[] getFiles() { + return this.files; + } + + public int getRoundRobinHistorySize() { + return this.roundRobinHistorySize; + } + + public int getHighestNumberOfChannels() { + return this.highestNumberOfChannels; + } + + @Nonnull + public com.hypixel.hytale.protocol.SoundEventLayer toPacket() { + com.hypixel.hytale.protocol.SoundEventLayer packet = new com.hypixel.hytale.protocol.SoundEventLayer(); + packet.volume = this.volume; + packet.startDelay = this.startDelay; + packet.looping = this.looping; + packet.probability = this.probability; + packet.probabilityRerollDelay = this.probabilityRerollDelay; + packet.randomSettings = new SoundEventLayerRandomSettings(); + packet.randomSettings.minVolume = this.randomSettings.minVolume; + packet.randomSettings.maxVolume = this.randomSettings.maxVolume; + packet.randomSettings.minPitch = this.randomSettings.minPitch; + packet.randomSettings.maxPitch = this.randomSettings.maxPitch; + packet.randomSettings.maxStartOffset = this.randomSettings.maxStartOffset; + packet.files = this.files; + packet.roundRobinHistorySize = this.roundRobinHistorySize; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "SoundEventLayer{, volume=" + + this.volume + + ", startDelay=" + + this.startDelay + + ", looping=" + + this.looping + + ", probability=" + + this.probability + + ", probabilityRerollDelay=" + + this.probabilityRerollDelay + + ", randomSettings=" + + this.randomSettings + + ", files=" + + Arrays.toString((Object[])this.files) + + ", roundRobinHistorySize=" + + this.roundRobinHistorySize + + ", highestNumberOfChannels=" + + this.highestNumberOfChannels + + "}"; + } + + public static class RandomSettings { + public static final Codec CODEC = BuilderCodec.builder( + SoundEventLayer.RandomSettings.class, SoundEventLayer.RandomSettings::new + ) + .append( + new KeyedCodec<>("MinVolume", Codec.FLOAT), + (soundEventLayer, f) -> soundEventLayer.minVolume = AudioUtil.decibelsToLinearGain(f), + soundEventLayer -> AudioUtil.linearGainToDecibels(soundEventLayer.minVolume) + ) + .addValidator(Validators.range(-100.0F, 0.0F)) + .documentation("Minimum additional random volume offset in decibels.") + .add() + .append( + new KeyedCodec<>("MaxVolume", Codec.FLOAT), + (soundEventLayer, f) -> soundEventLayer.maxVolume = AudioUtil.decibelsToLinearGain(f), + soundEventLayer -> AudioUtil.linearGainToDecibels(soundEventLayer.maxVolume) + ) + .addValidator(Validators.range(0.0F, 10.0F)) + .documentation("Maximum additional random volume offset in decibels.") + .add() + .append( + new KeyedCodec<>("MinPitch", Codec.FLOAT), + (soundEventLayer, f) -> soundEventLayer.minPitch = AudioUtil.semitonesToLinearPitch(f), + soundEventLayer -> AudioUtil.linearPitchToSemitones(soundEventLayer.minPitch) + ) + .addValidator(Validators.range(-12.0F, 0.0F)) + .documentation("Minimum additional random pitch offset in semitones.") + .add() + .append( + new KeyedCodec<>("MaxPitch", Codec.FLOAT), + (soundEventLayer, f) -> soundEventLayer.maxPitch = AudioUtil.semitonesToLinearPitch(f), + soundEventLayer -> AudioUtil.linearPitchToSemitones(soundEventLayer.maxPitch) + ) + .addValidator(Validators.range(0.0F, 12.0F)) + .documentation("Maximum additional random pitch offset in semitones.") + .add() + .append( + new KeyedCodec<>("MaxStartOffset", Codec.FLOAT), + (soundEventLayer, f) -> soundEventLayer.maxStartOffset = f, + soundEventLayer -> soundEventLayer.maxStartOffset + ) + .addValidator(Validators.range(0.0F, Float.MAX_VALUE)) + .documentation( + "Maximum amount by which to offset the start of this sound event (e.g. start up to x seconds into the sound). This should only really be used for looping sounds to prevent phasing issues." + ) + .add() + .build(); + public static final SoundEventLayer.RandomSettings DEFAULT = new SoundEventLayer.RandomSettings(); + protected transient float minVolume = 1.0F; + protected transient float maxVolume = 1.0F; + protected transient float minPitch = 1.0F; + protected transient float maxPitch = 1.0F; + protected float maxStartOffset; + + public RandomSettings() { + } + + public float getMinVolume() { + return this.minVolume; + } + + public float getMaxVolume() { + return this.maxVolume; + } + + public float getMinPitch() { + return this.minPitch; + } + + public float getMaxPitch() { + return this.maxPitch; + } + + public float getMaxStartOffset() { + return this.maxStartOffset; + } + + @Nonnull + @Override + public String toString() { + return "RandomSettings{, minVolume=" + + this.minVolume + + ", maxVolume=" + + this.maxVolume + + ", minPitch=" + + this.minPitch + + ", maxPitch=" + + this.maxPitch + + ", maxStartOffset=" + + this.maxStartOffset + + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/soundevent/validator/SoundEventValidators.java b/src/com/hypixel/hytale/server/core/asset/type/soundevent/validator/SoundEventValidators.java new file mode 100644 index 0000000..ba33bc1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/soundevent/validator/SoundEventValidators.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.server.core.asset.type.soundevent.validator; + +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.server.core.asset.common.SoundFileValidators; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEventLayer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SoundEventValidators { + public static final SoundEventValidators.LoopValidator LOOPING = new SoundEventValidators.LoopValidator(true); + public static final SoundEventValidators.LoopValidator ONESHOT = new SoundEventValidators.LoopValidator(false); + public static final SoundEventValidators.ChannelValidator MONO = new SoundEventValidators.ChannelValidator(1); + public static final SoundEventValidators.ChannelValidator STEREO = new SoundEventValidators.ChannelValidator(2); + public static final ValidatorCache MONO_VALIDATOR_CACHE = new ValidatorCache<>(MONO); + public static final ValidatorCache STEREO_VALIDATOR_CACHE = new ValidatorCache<>(STEREO); + public static final ValidatorCache ONESHOT_VALIDATOR_CACHE = new ValidatorCache<>(ONESHOT); + + public SoundEventValidators() { + } + + public static class ChannelValidator implements Validator { + private final int channelCount; + + public ChannelValidator(int channelCount) { + assert channelCount == 1 || channelCount == 2; + + this.channelCount = channelCount; + } + + public void accept(@Nullable String s, @Nonnull ValidationResults results) { + if (s != null) { + SoundEvent soundEvent = SoundEvent.getAssetMap().getAsset(s); + if (soundEvent == null) { + results.fail("Sound event with name '" + s + "' does not exist"); + } else { + if (soundEvent.getHighestNumberOfChannels() != this.channelCount) { + results.fail( + "Sound event with name '" + + s + + "' is " + + SoundFileValidators.getEncoding(soundEvent.getHighestNumberOfChannels()) + + " instead of " + + SoundFileValidators.getEncoding(this.channelCount) + ); + } + } + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + } + } + + public static class LoopValidator implements Validator { + private final boolean looping; + + private LoopValidator(boolean looping) { + this.looping = looping; + } + + public void accept(@Nullable String s, @Nonnull ValidationResults results) { + if (s != null) { + SoundEvent soundEvent = SoundEvent.getAssetMap().getAsset(s); + if (soundEvent == null) { + results.fail("Sound event with name '" + s + "' does not exist"); + } else if (soundEvent.getLayers() != null) { + if (this.looping) { + for (SoundEventLayer layer : soundEvent.getLayers()) { + if (layer.isLooping()) { + return; + } + } + + results.fail("Sound event with name '" + s + "' does not have a looping layer"); + } else { + for (SoundEventLayer layerx : soundEvent.getLayers()) { + if (layerx.isLooping()) { + results.fail("Sound event with name '" + s + "' has a looping layer and is not a oneshot sound"); + return; + } + } + } + } + } + } + + @Override + public void updateSchema(SchemaContext context, Schema target) { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/soundset/SoundSetPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/soundset/SoundSetPacketGenerator.java new file mode 100644 index 0000000..90f4178 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/soundset/SoundSetPacketGenerator.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.asset.type.soundset; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateSoundSets; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.soundset.config.SoundSet; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class SoundSetPacketGenerator extends SimpleAssetPacketGenerator> { + public SoundSetPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + UpdateSoundSets packet = new UpdateSoundSets(); + packet.type = UpdateType.Init; + packet.soundSets = new Int2ObjectOpenHashMap<>(assets.size()); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.soundSets.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateUpdatePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets) { + UpdateSoundSets packet = new UpdateSoundSets(); + packet.type = UpdateType.AddOrUpdate; + packet.soundSets = new Int2ObjectOpenHashMap<>(loadedAssets.size()); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.soundSets.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateRemovePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed) { + UpdateSoundSets packet = new UpdateSoundSets(); + packet.type = UpdateType.Remove; + packet.soundSets = new Int2ObjectOpenHashMap<>(removed.size()); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.soundSets.put(index, null); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/soundset/config/SoundSet.java b/src/com/hypixel/hytale/server/core/asset/type/soundset/config/SoundSet.java new file mode 100644 index 0000000..0a675e1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/soundset/config/SoundSet.java @@ -0,0 +1,145 @@ +package com.hypixel.hytale.server.core.asset.type.soundset.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDefaultCollapsedState; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntMaps; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.lang.ref.SoftReference; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class SoundSet + implements JsonAssetWithMap>, + NetworkSerializable { + public static final int EMPTY_ID = 0; + public static final String EMPTY = "EMPTY"; + public static final SoundSet EMPTY_SOUND_SET = new SoundSet("EMPTY"); + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + SoundSet.class, SoundSet::new, Codec.STRING, (t, k) -> t.id = k, t -> t.id, (asset, data) -> asset.data = data, asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("SoundEvents", MapCodec.STRING_HASH_MAP_CODEC), + (soundSet, s) -> soundSet.soundEventIds = s, + soundSet -> soundSet.soundEventIds, + (soundSet, parent) -> soundSet.soundEventIds = parent.soundEventIds + ) + .addValidator(Validators.nonNull()) + .addValidator(SoundEvent.VALIDATOR_CACHE.getMapValueValidator()) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("Category", new EnumCodec<>(SoundCategory.class)), + (soundSet, s) -> soundSet.category = s, + soundSet -> soundSet.category, + (soundSet, parent) -> soundSet.category = parent.category + ) + .addValidator(Validators.nonNull()) + .add() + .afterDecode(SoundSet::processConfig) + .build(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(SoundSet.class, CODEC); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(SoundSet::getAssetStore)); + private static AssetStore> ASSET_STORE; + protected AssetExtraInfo.Data data; + protected String id; + protected Map soundEventIds = Collections.emptyMap(); + protected transient Object2IntMap soundEventIndices = Object2IntMaps.emptyMap(); + @Nonnull + protected SoundCategory category = SoundCategory.SFX; + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(SoundSet.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public SoundSet(String id, Map soundEventIds, SoundCategory category) { + this.id = id; + this.soundEventIds = soundEventIds; + this.category = category; + } + + public SoundSet(String id) { + this.id = id; + } + + protected SoundSet() { + } + + public String getId() { + return this.id; + } + + public Map getSoundEventIds() { + return this.soundEventIds; + } + + public Object2IntMap getSoundEventIndices() { + return this.soundEventIndices; + } + + protected void processConfig() { + if (!this.soundEventIds.isEmpty()) { + this.soundEventIndices = new Object2IntOpenHashMap<>(); + + for (Entry entry : this.soundEventIds.entrySet()) { + this.soundEventIndices.put(entry.getKey(), SoundEvent.getAssetMap().getIndex(entry.getValue())); + } + } + } + + @Nonnull + @Override + public String toString() { + return "SoundSet{id='" + + this.id + + "', soundEventIds=" + + this.soundEventIds + + ", soundEventIndices=" + + this.soundEventIndices + + ", category=" + + this.category + + "}"; + } + + @Nonnull + public com.hypixel.hytale.protocol.SoundSet toPacket() { + com.hypixel.hytale.protocol.SoundSet cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.SoundSet packet = new com.hypixel.hytale.protocol.SoundSet(); + packet.id = this.id; + packet.sounds = this.soundEventIndices; + packet.category = this.category; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/tagpattern/TagPatternPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/tagpattern/TagPatternPacketGenerator.java new file mode 100644 index 0000000..4ea2480 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/tagpattern/TagPatternPacketGenerator.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.asset.type.tagpattern; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateTagPatterns; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.tagpattern.config.TagPattern; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class TagPatternPacketGenerator extends SimpleAssetPacketGenerator> { + public TagPatternPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + UpdateTagPatterns packet = new UpdateTagPatterns(); + packet.type = UpdateType.Init; + packet.patterns = new Int2ObjectOpenHashMap<>(assets.size()); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.patterns.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateUpdatePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets) { + UpdateTagPatterns packet = new UpdateTagPatterns(); + packet.type = UpdateType.AddOrUpdate; + packet.patterns = new Int2ObjectOpenHashMap<>(loadedAssets.size()); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.patterns.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateRemovePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed) { + UpdateTagPatterns packet = new UpdateTagPatterns(); + packet.type = UpdateType.Remove; + packet.patterns = new Int2ObjectOpenHashMap<>(removed.size()); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.patterns.put(index, null); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/AndPatternOp.java b/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/AndPatternOp.java new file mode 100644 index 0000000..5292dd0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/AndPatternOp.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.core.asset.type.tagpattern.config; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.TagPatternType; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.lang.ref.SoftReference; +import javax.annotation.Nonnull; + +public class AndPatternOp extends MultiplePatternOp { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder(AndPatternOp.class, AndPatternOp::new, MultiplePatternOp.CODEC).build(); + + public AndPatternOp() { + } + + @Override + public boolean test(Int2ObjectMap tags) { + for (int i = 0; i < this.patterns.length; i++) { + if (!this.patterns[i].test(tags)) { + return false; + } + } + + return true; + } + + @Override + public com.hypixel.hytale.protocol.TagPattern toPacket() { + com.hypixel.hytale.protocol.TagPattern cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.TagPattern packet = super.toPacket(); + packet.type = TagPatternType.And; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + @Nonnull + @Override + public String toString() { + return "AndPatternOp{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/EqualsTagOp.java b/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/EqualsTagOp.java new file mode 100644 index 0000000..7fb57e4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/EqualsTagOp.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.core.asset.type.tagpattern.config; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.TagPatternType; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.lang.ref.SoftReference; +import javax.annotation.Nonnull; + +public class EqualsTagOp extends TagPattern { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder(EqualsTagOp.class, EqualsTagOp::new, TagPattern.BASE_CODEC) + .append(new KeyedCodec<>("Tag", Codec.STRING), (singleTagOp, s) -> singleTagOp.tag = s, singleTagOp -> singleTagOp.tag) + .addValidator(Validators.nonNull()) + .add() + .afterDecode(singleTagOp -> { + if (singleTagOp.tag != null) { + singleTagOp.tagIndex = AssetRegistry.getOrCreateTagIndex(singleTagOp.tag); + } + }) + .build(); + protected String tag; + protected int tagIndex; + + public EqualsTagOp(String tag) { + this.tag = tag; + } + + protected EqualsTagOp() { + } + + @Override + public boolean test(@Nonnull Int2ObjectMap tags) { + return tags.containsKey(this.tagIndex); + } + + public com.hypixel.hytale.protocol.TagPattern toPacket() { + com.hypixel.hytale.protocol.TagPattern cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.TagPattern packet = new com.hypixel.hytale.protocol.TagPattern(); + packet.type = TagPatternType.Equals; + packet.tagIndex = this.tagIndex; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + @Nonnull + @Override + public String toString() { + return "EqualsTagOp{tag='" + this.tag + "'} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/MultiplePatternOp.java b/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/MultiplePatternOp.java new file mode 100644 index 0000000..5ccf844 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/MultiplePatternOp.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.server.core.asset.type.tagpattern.config; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public abstract class MultiplePatternOp extends TagPattern { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.abstractBuilder(MultiplePatternOp.class, TagPattern.BASE_CODEC) + .append( + new KeyedCodec<>("Patterns", new ArrayCodec<>(TagPattern.CODEC, TagPattern[]::new)), + (tagPattern, tagPatterns) -> tagPattern.patterns = tagPatterns, + tagPattern -> tagPattern.patterns + ) + .addValidator(Validators.nonEmptyArray()) + .add() + .build(); + protected TagPattern[] patterns; + + public MultiplePatternOp() { + } + + public com.hypixel.hytale.protocol.TagPattern toPacket() { + com.hypixel.hytale.protocol.TagPattern packet = new com.hypixel.hytale.protocol.TagPattern(); + packet.operands = new com.hypixel.hytale.protocol.TagPattern[this.patterns.length]; + + for (int i = 0; i < this.patterns.length; i++) { + packet.operands[i] = this.patterns[i].toPacket(); + } + + return packet; + } + + @Nonnull + @Override + public String toString() { + return "MultiplePatternOp{patterns=" + Arrays.toString((Object[])this.patterns) + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/NotPatternOp.java b/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/NotPatternOp.java new file mode 100644 index 0000000..e9fbda5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/NotPatternOp.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.core.asset.type.tagpattern.config; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.TagPatternType; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.lang.ref.SoftReference; +import javax.annotation.Nonnull; + +public class NotPatternOp extends TagPattern { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder(NotPatternOp.class, NotPatternOp::new, TagPattern.BASE_CODEC) + .append(new KeyedCodec<>("Pattern", TagPattern.CODEC), (singleTagOp, s) -> singleTagOp.pattern = s, singleTagOp -> singleTagOp.pattern) + .addValidator(Validators.nonNull()) + .add() + .build(); + protected TagPattern pattern; + + public NotPatternOp() { + } + + @Override + public boolean test(Int2ObjectMap tags) { + return !this.pattern.test(tags); + } + + public com.hypixel.hytale.protocol.TagPattern toPacket() { + com.hypixel.hytale.protocol.TagPattern cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.TagPattern packet = new com.hypixel.hytale.protocol.TagPattern(); + packet.type = TagPatternType.Not; + packet.not = this.pattern.toPacket(); + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + @Nonnull + @Override + public String toString() { + return "NotPatternOp{pattern=" + this.pattern + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/OrPatternOp.java b/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/OrPatternOp.java new file mode 100644 index 0000000..09599b8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/OrPatternOp.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.core.asset.type.tagpattern.config; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.TagPatternType; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.lang.ref.SoftReference; +import javax.annotation.Nonnull; + +public class OrPatternOp extends MultiplePatternOp { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder(OrPatternOp.class, OrPatternOp::new, MultiplePatternOp.CODEC).build(); + + public OrPatternOp() { + } + + @Override + public boolean test(Int2ObjectMap tags) { + for (int i = 0; i < this.patterns.length; i++) { + if (this.patterns[i].test(tags)) { + return true; + } + } + + return false; + } + + @Override + public com.hypixel.hytale.protocol.TagPattern toPacket() { + com.hypixel.hytale.protocol.TagPattern cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.TagPattern packet = super.toPacket(); + packet.type = TagPatternType.Or; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + @Nonnull + @Override + public String toString() { + return "OrPatternOp{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/TagPattern.java b/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/TagPattern.java new file mode 100644 index 0000000..9b4f352 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/tagpattern/config/TagPattern.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.server.core.asset.type.tagpattern.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.lang.ref.SoftReference; +import javax.annotation.Nonnull; + +public abstract class TagPattern + implements JsonAssetWithMap>, + NetworkSerializable { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + "Op", Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(TagPattern.class).build(); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(TagPattern.class, CODEC); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(TagPattern::getAssetStore)); + private static AssetStore> ASSET_STORE; + private AssetExtraInfo.Data data; + protected String id; + protected SoftReference cachedPacket; + + public TagPattern() { + } + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(TagPattern.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public String getId() { + return this.id; + } + + public abstract boolean test(Int2ObjectMap var1); + + @Nonnull + @Override + public String toString() { + return "TagPattern{id='" + this.id + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/trail/TrailPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/trail/TrailPacketGenerator.java new file mode 100644 index 0000000..83db34f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/trail/TrailPacketGenerator.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.server.core.asset.type.trail; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateTrails; +import com.hypixel.hytale.server.core.asset.packet.DefaultAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.trail.config.Trail; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class TrailPacketGenerator extends DefaultAssetPacketGenerator { + public TrailPacketGenerator() { + } + + @Nonnull + @Override + public Packet generateInitPacket(DefaultAssetMap assetMap, @Nonnull Map assets) { + UpdateTrails packet = new UpdateTrails(); + packet.type = UpdateType.Init; + packet.trails = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + packet.trails.put(entry.getKey(), entry.getValue().toPacket()); + } + + return packet; + } + + @Nonnull + @Override + public Packet generateUpdatePacket(@Nonnull Map loadedAssets) { + UpdateTrails packet = new UpdateTrails(); + packet.type = UpdateType.AddOrUpdate; + packet.trails = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + packet.trails.put(entry.getKey(), entry.getValue().toPacket()); + } + + return packet; + } + + @Nonnull + @Override + public Packet generateRemovePacket(@Nonnull Set removed) { + UpdateTrails packet = new UpdateTrails(); + packet.type = UpdateType.Remove; + packet.trails = new Object2ObjectOpenHashMap<>(); + + for (String key : removed) { + packet.trails.put(key, null); + } + + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/trail/config/Animation.java b/src/com/hypixel/hytale/server/core/asset/type/trail/config/Animation.java new file mode 100644 index 0000000..2fa463e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/trail/config/Animation.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.core.asset.type.trail.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.protocol.Range; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import javax.annotation.Nonnull; + +public class Animation { + public static final BuilderCodec CODEC = BuilderCodec.builder(Animation.class, Animation::new) + .appendInherited( + new KeyedCodec<>("FrameSize", Vector2i.CODEC), + (animation, b) -> animation.frameSize = b, + animation -> animation.frameSize, + (animation, parent) -> animation.frameSize = parent.frameSize + ) + .add() + .appendInherited( + new KeyedCodec<>("FrameRange", ProtocolCodecs.RANGE), + (animation, b) -> animation.frameRange = b, + animation -> animation.frameRange, + (animation, parent) -> animation.frameRange = parent.frameRange + ) + .add() + .appendInherited( + new KeyedCodec<>("FrameLifeSpan", Codec.INTEGER), + (animation, i) -> animation.frameLifeSpan = i, + animation -> animation.frameLifeSpan, + (animation, parent) -> animation.frameLifeSpan = parent.frameLifeSpan + ) + .add() + .build(); + private Vector2i frameSize; + private Range frameRange; + private int frameLifeSpan; + + public Animation() { + } + + public Vector2i getFrameSize() { + return this.frameSize; + } + + public Range getFrameRange() { + return this.frameRange; + } + + public int getFrameLifeSpan() { + return this.frameLifeSpan; + } + + @Nonnull + @Override + public String toString() { + return "Animation{frameSize=" + this.frameSize + ", frameRange=" + this.frameRange + ", frameLifeSpan=" + this.frameLifeSpan + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/trail/config/Edge.java b/src/com/hypixel/hytale/server/core/asset/type/trail/config/Edge.java new file mode 100644 index 0000000..850b67b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/trail/config/Edge.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.core.asset.type.trail.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.ColorAlpha; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import javax.annotation.Nonnull; + +public class Edge { + public static final BuilderCodec CODEC = BuilderCodec.builder(Edge.class, Edge::new) + .appendInherited( + new KeyedCodec<>("Width", Codec.DOUBLE), + (edge, d) -> edge.width = d.floatValue(), + edge -> (double)edge.width, + (edge, parent) -> edge.width = parent.width + ) + .add() + .appendInherited( + new KeyedCodec<>("Color", ProtocolCodecs.COLOR_AlPHA), (edge, o) -> edge.color = o, edge -> edge.color, (edge, parent) -> edge.color = parent.color + ) + .add() + .build(); + private float width; + private ColorAlpha color = new ColorAlpha((byte)-1, (byte)-1, (byte)-1, (byte)-1); + + public Edge() { + } + + @Nonnull + public com.hypixel.hytale.protocol.Edge toPacket() { + com.hypixel.hytale.protocol.Edge packet = new com.hypixel.hytale.protocol.Edge(); + packet.color = this.color; + packet.width = this.width; + return packet; + } + + public float getWidth() { + return this.width; + } + + public ColorAlpha getColor() { + return this.color; + } + + @Nonnull + @Override + public String toString() { + return "Edge{width=" + this.width + ", color='" + this.color + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/trail/config/Trail.java b/src/com/hypixel/hytale/server/core/asset/type/trail/config/Trail.java new file mode 100644 index 0000000..109999b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/trail/config/Trail.java @@ -0,0 +1,265 @@ +package com.hypixel.hytale.server.core.asset.type.trail.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDefaultCollapsedState; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.protocol.FXRenderMode; +import com.hypixel.hytale.protocol.IntersectionHighlight; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import javax.annotation.Nonnull; + +public class Trail implements JsonAssetWithMap>, NetworkSerializable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + Trail.class, Trail::new, Codec.STRING, (trail, s) -> trail.id = s, trail -> trail.id, (asset, data) -> asset.data = data, asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("TexturePath", Codec.STRING), + (trail, s) -> trail.texture = s, + trail -> trail.texture, + (trail, parent) -> trail.texture = parent.texture + ) + .addValidator(Validators.nonNull()) + .addValidator(CommonAssetValidator.TEXTURE_TRAIL) + .add() + .appendInherited( + new KeyedCodec<>("LifeSpan", Codec.INTEGER), + (trail, i) -> trail.lifeSpan = i, + trail -> trail.lifeSpan, + (trail, parent) -> trail.lifeSpan = parent.lifeSpan + ) + .add() + .appendInherited( + new KeyedCodec<>("Roll", Codec.DOUBLE), + (trail, d) -> trail.roll = d.floatValue(), + trail -> (double)trail.roll, + (trail, parent) -> trail.roll = parent.roll + ) + .add() + .appendInherited( + new KeyedCodec<>("Start", Edge.CODEC), (trail, o) -> trail.start = o, trail -> trail.start, (trail, parent) -> trail.start = parent.start + ) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited(new KeyedCodec<>("End", Edge.CODEC), (trail, o) -> trail.end = o, trail -> trail.end, (trail, parent) -> trail.end = parent.end) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("LightInfluence", Codec.DOUBLE), + (trail, d) -> trail.lightInfluence = d.floatValue(), + trail -> (double)trail.lightInfluence, + (trail, parent) -> trail.lightInfluence = parent.lightInfluence + ) + .addValidator(Validators.range(0.0, 1.0)) + .add() + .appendInherited( + new KeyedCodec<>("RenderMode", new EnumCodec<>(FXRenderMode.class)), + (trail, s) -> trail.renderMode = s, + trail -> trail.renderMode, + (trail, parent) -> trail.renderMode = parent.renderMode + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("IntersectionHighlight", ProtocolCodecs.INTERSECTION_HIGHLIGHT), + (trail, s) -> trail.intersectionHighlight = s, + trail -> trail.intersectionHighlight, + (trail, parent) -> trail.intersectionHighlight = parent.intersectionHighlight + ) + .add() + .appendInherited( + new KeyedCodec<>("Smooth", Codec.BOOLEAN), (trail, b) -> trail.smooth = b, trail -> trail.smooth, (trail, parent) -> trail.smooth = parent.smooth + ) + .add() + .appendInherited( + new KeyedCodec<>("Animation", Animation.CODEC), + (trail, b) -> trail.animation = b, + trail -> trail.animation, + (trail, parent) -> trail.animation = parent.animation + ) + .add() + .build(); + private static AssetStore> ASSET_STORE; + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(Trail::getAssetStore)); + protected AssetExtraInfo.Data data; + protected String id; + protected String texture; + @Nonnull + protected FXRenderMode renderMode = FXRenderMode.BlendLinear; + protected IntersectionHighlight intersectionHighlight; + protected int lifeSpan; + protected float roll; + protected float lightInfluence; + protected boolean smooth; + protected Edge start; + protected Edge end; + protected Animation animation; + protected SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(Trail.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public Trail( + String id, + String texture, + FXRenderMode renderMode, + IntersectionHighlight intersectionHighlight, + int lifeSpan, + float roll, + float lightInfluence, + boolean smooth, + Edge start, + Edge end, + Animation animation + ) { + this.id = id; + this.texture = texture; + this.renderMode = renderMode; + this.intersectionHighlight = intersectionHighlight; + this.lifeSpan = lifeSpan; + this.roll = roll; + this.lightInfluence = lightInfluence; + this.smooth = smooth; + this.start = start; + this.end = end; + this.animation = animation; + } + + protected Trail() { + } + + @Nonnull + public com.hypixel.hytale.protocol.Trail toPacket() { + com.hypixel.hytale.protocol.Trail cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.Trail packet = new com.hypixel.hytale.protocol.Trail(); + packet.id = this.id; + packet.texture = this.texture; + packet.lifeSpan = this.lifeSpan; + packet.roll = this.roll; + packet.lightInfluence = this.lightInfluence; + packet.renderMode = this.renderMode; + packet.intersectionHighlight = this.intersectionHighlight; + packet.smooth = this.smooth; + if (this.start != null) { + packet.start = this.start.toPacket(); + } + + if (this.end != null) { + packet.end = this.end.toPacket(); + } + + if (this.animation != null) { + Vector2i frameSize = this.animation.getFrameSize(); + if (frameSize != null) { + packet.frameSize = new com.hypixel.hytale.protocol.Vector2i(frameSize.getX(), frameSize.getY()); + } + + if (this.animation.getFrameRange() != null) { + packet.frameRange = this.animation.getFrameRange(); + } + + packet.frameLifeSpan = this.animation.getFrameLifeSpan(); + } + + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + public String getId() { + return this.id; + } + + public String getTexture() { + return this.texture; + } + + public FXRenderMode getRenderMode() { + return this.renderMode; + } + + public IntersectionHighlight getIntersectionHighlight() { + return this.intersectionHighlight; + } + + public int getLifeSpan() { + return this.lifeSpan; + } + + public float getRoll() { + return this.roll; + } + + public float getLightInfluence() { + return this.lightInfluence; + } + + public boolean isSmooth() { + return this.smooth; + } + + public Edge getStart() { + return this.start; + } + + public Edge getEnd() { + return this.end; + } + + public Animation getAnimation() { + return this.animation; + } + + @Nonnull + @Override + public String toString() { + return "Trail{id='" + + this.id + + "', texture='" + + this.texture + + "', renderMode=" + + this.renderMode + + ", intersectionHighlight=" + + this.intersectionHighlight + + ", lifeSpan=" + + this.lifeSpan + + ", roll=" + + this.roll + + ", lightInfluence=" + + this.lightInfluence + + ", smooth=" + + this.smooth + + ", start=" + + this.start + + ", end=" + + this.end + + ", animation=" + + this.animation + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/weather/WeatherPacketGenerator.java b/src/com/hypixel/hytale/server/core/asset/type/weather/WeatherPacketGenerator.java new file mode 100644 index 0000000..20035d9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/weather/WeatherPacketGenerator.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.asset.type.weather; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateWeathers; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class WeatherPacketGenerator extends SimpleAssetPacketGenerator> { + public WeatherPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + UpdateWeathers packet = new UpdateWeathers(); + packet.type = UpdateType.Init; + packet.weathers = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.weathers.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateUpdatePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets) { + UpdateWeathers packet = new UpdateWeathers(); + packet.type = UpdateType.AddOrUpdate; + packet.weathers = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.weathers.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateRemovePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed) { + UpdateWeathers packet = new UpdateWeathers(); + packet.type = UpdateType.Remove; + packet.weathers = new Int2ObjectOpenHashMap<>(); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.weathers.put(index, null); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/weather/config/Cloud.java b/src/com/hypixel/hytale/server/core/asset/type/weather/config/Cloud.java new file mode 100644 index 0000000..be039d2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/weather/config/Cloud.java @@ -0,0 +1,72 @@ +package com.hypixel.hytale.server.core.asset.type.weather.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class Cloud implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(Cloud.class, Cloud::new) + .append(new KeyedCodec<>("Texture", Codec.STRING), (cloud, s) -> cloud.texture = s, Cloud::getTexture) + .addValidator(CommonAssetValidator.TEXTURE_SKY) + .add() + .append( + new KeyedCodec<>("Colors", new ArrayCodec<>(TimeColorAlpha.CODEC, TimeColorAlpha[]::new)), (cloud, s) -> cloud.colors = s, Cloud::getColors + ) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .add() + .append(new KeyedCodec<>("Speeds", new ArrayCodec<>(TimeFloat.CODEC, TimeFloat[]::new)), (cloud, s) -> cloud.speeds = s, Cloud::getSpeeds) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .add() + .build(); + protected String texture; + protected TimeColorAlpha[] colors; + protected TimeFloat[] speeds; + + public Cloud(String texture, TimeColorAlpha[] colors, TimeFloat[] speeds) { + this.texture = texture; + this.colors = colors; + this.speeds = speeds; + } + + protected Cloud() { + } + + @Nonnull + public com.hypixel.hytale.protocol.Cloud toPacket() { + com.hypixel.hytale.protocol.Cloud packet = new com.hypixel.hytale.protocol.Cloud(); + packet.texture = this.texture; + packet.colors = Weather.toColorAlphaMap(this.colors); + packet.speeds = Weather.toFloatMap(this.speeds); + return packet; + } + + public String getTexture() { + return this.texture; + } + + public TimeColorAlpha[] getColors() { + return this.colors; + } + + public TimeFloat[] getSpeeds() { + return this.speeds; + } + + @Nonnull + @Override + public String toString() { + return "Cloud{texture='" + + this.texture + + "', colors=" + + Arrays.toString((Object[])this.colors) + + ", speeds=" + + Arrays.toString((Object[])this.speeds) + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/weather/config/DayTexture.java b/src/com/hypixel/hytale/server/core/asset/type/weather/config/DayTexture.java new file mode 100644 index 0000000..c4b246f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/weather/config/DayTexture.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.asset.type.weather.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import javax.annotation.Nonnull; + +public class DayTexture { + public static final BuilderCodec CODEC = BuilderCodec.builder(DayTexture.class, DayTexture::new) + .addField(new KeyedCodec<>("Day", Codec.INTEGER), (dayTexture, i) -> dayTexture.day = i, DayTexture::getDay) + .append(new KeyedCodec<>("Texture", Codec.STRING), (dayTexture, s) -> dayTexture.texture = s, DayTexture::getTexture) + .addValidator(CommonAssetValidator.TEXTURE_SKY) + .add() + .build(); + protected int day; + protected String texture; + + public DayTexture(int day, String texture) { + this.day = day; + this.texture = texture; + } + + protected DayTexture() { + } + + public int getDay() { + return this.day; + } + + public String getTexture() { + return this.texture; + } + + @Nonnull + @Override + public String toString() { + return "DayTexture{day=" + this.day + ", texture='" + this.texture + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/weather/config/FogOptions.java b/src/com/hypixel/hytale/server/core/asset/type/weather/config/FogOptions.java new file mode 100644 index 0000000..350ce75 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/weather/config/FogOptions.java @@ -0,0 +1,101 @@ +package com.hypixel.hytale.server.core.asset.type.weather.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nullable; + +public class FogOptions { + public static final BuilderCodec CODEC = BuilderCodec.builder(FogOptions.class, FogOptions::new) + .appendInherited( + new KeyedCodec<>("IgnoreFogLimits", Codec.BOOLEAN), + (opt, s) -> opt.ignoreFogLimits = s, + opt -> opt.ignoreFogLimits, + (opt, parent) -> opt.ignoreFogLimits = parent.ignoreFogLimits + ) + .documentation("The client has a default minimum AND maximum for \"FogFar\". Toggling this on will allow your FogDistance[1] to bypass those limits.") + .add() + .appendInherited( + new KeyedCodec<>("EffectiveViewDistanceMultiplier", Codec.FLOAT), + (opt, s) -> opt.effectiveViewDistanceMultiplier = s, + opt -> opt.effectiveViewDistanceMultiplier, + (opt, parent) -> opt.effectiveViewDistanceMultiplier = parent.effectiveViewDistanceMultiplier + ) + .documentation( + "The client's default cap for FogDistance[1] (aka FogFar) is the effective view distance, meaning the farthest viewable chunk. This value (defaults 1.0) multiplies that cap. For example with high fog density, you can afford a fog multiplier of 1.3 as the cutoff of unloaded chunks may still be hidden." + ) + .add() + .appendInherited( + new KeyedCodec<>("FogHeightCameraFixed", Codec.FLOAT), + (opt, s) -> opt.fogHeightCameraFixed = s, + opt -> opt.fogHeightCameraFixed, + (opt, parent) -> opt.fogHeightCameraFixed = parent.fogHeightCameraFixed + ) + .documentation( + "By default, the client has e^(-FogHeightFalloff * ~Camera.Y) height-based fog. This adds significant fog near Camera.Y = 0. By setting this value (between 0.0 and 1.0), the Exp function is bypassed and there will be a fixed fog for height in the fog shader." + ) + .add() + .appendInherited( + new KeyedCodec<>("FogHeightCameraOffset", Codec.FLOAT), + (opt, s) -> opt.fogHeightCameraOffset = s, + opt -> opt.fogHeightCameraOffset, + (opt, parent) -> opt.fogHeightCameraOffset = parent.fogHeightCameraOffset + ) + .documentation( + "By default, the client has e^(-FogHeightFalloff * ~Camera.Y) height-based fog. This adds significant fog near Camera.Y = 0. The FogHeightCameraOffset is added to the Camera.Y." + ) + .add() + .build(); + private boolean ignoreFogLimits = false; + private float effectiveViewDistanceMultiplier = 1.0F; + private Float fogHeightCameraFixed = null; + private float fogHeightCameraOffset = 0.0F; + + public FogOptions() { + } + + public boolean isIgnoreFogLimits() { + return this.ignoreFogLimits; + } + + public float getEffectiveViewDistanceMultiplier() { + return this.effectiveViewDistanceMultiplier; + } + + @Nullable + public Float getFogHeightCameraFixed() { + return this.fogHeightCameraFixed; + } + + public float getFogHeightCameraOffset() { + return this.fogHeightCameraOffset; + } + + public com.hypixel.hytale.protocol.FogOptions toPacket() { + com.hypixel.hytale.protocol.FogOptions proto = new com.hypixel.hytale.protocol.FogOptions(); + proto.ignoreFogLimits = this.ignoreFogLimits; + proto.effectiveViewDistanceMultiplier = this.effectiveViewDistanceMultiplier; + if (this.fogHeightCameraFixed == null) { + proto.fogHeightCameraOverriden = false; + } else { + proto.fogHeightCameraOverriden = true; + proto.fogHeightCameraFixed = this.fogHeightCameraFixed; + } + + proto.fogHeightCameraOffset = this.fogHeightCameraOffset; + return proto; + } + + @Override + public String toString() { + return "FogOptions{ignoreFogLimits=" + + this.ignoreFogLimits + + ", effectiveViewDistanceMultiplier=" + + this.effectiveViewDistanceMultiplier + + ", fogHeightCameraFixed=" + + this.fogHeightCameraFixed + + ", fogHeightCameraOffset=" + + this.fogHeightCameraOffset + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/weather/config/TimeColor.java b/src/com/hypixel/hytale/server/core/asset/type/weather/config/TimeColor.java new file mode 100644 index 0000000..f3ae2f0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/weather/config/TimeColor.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.core.asset.type.weather.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import javax.annotation.Nonnull; + +public class TimeColor { + public static final BuilderCodec CODEC = BuilderCodec.builder(TimeColor.class, TimeColor::new) + .append(new KeyedCodec<>("Hour", Codec.DOUBLE), (timeColor, i) -> timeColor.hour = i.floatValue(), timeColor -> (double)timeColor.getHour()) + .addValidator(Validators.range(0.0, 24.0)) + .add() + .addField(new KeyedCodec<>("Color", ProtocolCodecs.COLOR), (timeColor, o) -> timeColor.color = o, TimeColor::getColor) + .build(); + public static final ArrayCodec ARRAY_CODEC = new ArrayCodec<>(CODEC, TimeColor[]::new); + protected float hour; + protected Color color; + + public TimeColor(float hour, Color color) { + this.hour = hour; + this.color = color; + } + + protected TimeColor() { + } + + public float getHour() { + return this.hour; + } + + public Color getColor() { + return this.color; + } + + @Nonnull + @Override + public String toString() { + return "TimeColor{hour=" + this.hour + ", color='" + this.color + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/weather/config/TimeColorAlpha.java b/src/com/hypixel/hytale/server/core/asset/type/weather/config/TimeColorAlpha.java new file mode 100644 index 0000000..f1635e8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/weather/config/TimeColorAlpha.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.core.asset.type.weather.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.ColorAlpha; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import javax.annotation.Nonnull; + +public class TimeColorAlpha { + public static final BuilderCodec CODEC = BuilderCodec.builder(TimeColorAlpha.class, TimeColorAlpha::new) + .append( + new KeyedCodec<>("Hour", Codec.DOUBLE), + (timeColorAlpha, i) -> timeColorAlpha.hour = i.floatValue(), + timeColorAlpha -> (double)timeColorAlpha.getHour() + ) + .addValidator(Validators.range(0.0, 24.0)) + .add() + .addField(new KeyedCodec<>("Color", ProtocolCodecs.COLOR_AlPHA), (timeColorAlpha, o) -> timeColorAlpha.color = o, TimeColorAlpha::getColor) + .build(); + public static final ArrayCodec ARRAY_CODEC = new ArrayCodec<>(CODEC, TimeColorAlpha[]::new); + protected float hour; + protected ColorAlpha color; + + public TimeColorAlpha(float hour, ColorAlpha color) { + this.hour = hour; + this.color = color; + } + + protected TimeColorAlpha() { + } + + public float getHour() { + return this.hour; + } + + public ColorAlpha getColor() { + return this.color; + } + + @Nonnull + @Override + public String toString() { + return "TimeColorAlpha{hour=" + this.hour + ", color='" + this.color + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/weather/config/TimeFloat.java b/src/com/hypixel/hytale/server/core/asset/type/weather/config/TimeFloat.java new file mode 100644 index 0000000..524a376 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/weather/config/TimeFloat.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.asset.type.weather.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class TimeFloat { + public static final BuilderCodec CODEC = BuilderCodec.builder(TimeFloat.class, TimeFloat::new) + .append(new KeyedCodec<>("Hour", Codec.DOUBLE), (timeFloat, i) -> timeFloat.hour = i.floatValue(), timeFloat -> (double)timeFloat.getHour()) + .addValidator(Validators.range(0.0, 24.0)) + .add() + .addField(new KeyedCodec<>("Value", Codec.DOUBLE), (timeFloat, d) -> timeFloat.value = d.floatValue(), timeFloat -> (double)timeFloat.value) + .build(); + protected float hour; + protected float value; + + public TimeFloat(float hour, float value) { + this.hour = hour; + this.value = value; + } + + protected TimeFloat() { + } + + public float getHour() { + return this.hour; + } + + public float getValue() { + return this.value; + } + + @Nonnull + @Override + public String toString() { + return "TimeFloat{hour=" + this.hour + ", value='" + this.value + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/weather/config/Weather.java b/src/com/hypixel/hytale/server/core/asset/type/weather/config/Weather.java new file mode 100644 index 0000000..1b05bf5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/weather/config/Weather.java @@ -0,0 +1,673 @@ +package com.hypixel.hytale.server.core.asset.type.weather.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDefaultCollapsedState; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorFeatures; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditorSectionStart; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.ColorAlpha; +import com.hypixel.hytale.protocol.NearFar; +import com.hypixel.hytale.protocol.WeatherParticle; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.lang.ref.SoftReference; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class Weather implements JsonAssetWithMap>, NetworkSerializable { + public static final BuilderCodec PARTICLE_CODEC = BuilderCodec.builder(WeatherParticle.class, WeatherParticle::new) + .documentation("Particle System that can be spawned in relation to a weather.") + .append(new KeyedCodec<>("SystemId", Codec.STRING), (particle, s) -> particle.systemId = s, particle -> particle.systemId) + .addValidator(Validators.nonNull()) + .addValidator(ParticleSystem.VALIDATOR_CACHE.getValidator()) + .add() + .append(new KeyedCodec<>("Color", ProtocolCodecs.COLOR), (particle, o) -> particle.color = o, particle -> particle.color) + .documentation("The colour used if none was specified in the particle settings.") + .add() + .append(new KeyedCodec<>("Scale", Codec.FLOAT), (particle, f) -> particle.scale = f, particle -> particle.scale) + .documentation("The scale of the particle system.") + .add() + .append(new KeyedCodec<>("OvergroundOnly", Codec.BOOLEAN), (particle, s) -> particle.isOvergroundOnly = s, particle -> particle.isOvergroundOnly) + .documentation("Sets if the particles can only spawn above the columns highest blocks.") + .add() + .append( + new KeyedCodec<>("PositionOffsetMultiplier", Codec.FLOAT), + (particle, f) -> particle.positionOffsetMultiplier = f, + particle -> particle.positionOffsetMultiplier + ) + .documentation("The amount the system will move ahead of the camera.") + .add() + .build(); + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + Weather.class, + Weather::new, + Codec.STRING, + (weather, s) -> weather.id = s, + weather -> weather.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .metadata(new UIEditorFeatures(UIEditorFeatures.EditorFeature.WEATHER_DAYTIME_BAR, UIEditorFeatures.EditorFeature.WEATHER_PREVIEW_LOCAL)) + .appendInherited( + new KeyedCodec<>("Stars", Codec.STRING), + (weather, o) -> weather.stars = o, + weather -> weather.stars, + (weather, parent) -> weather.stars = parent.stars + ) + .addValidator(CommonAssetValidator.TEXTURE_SKY) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("ScreenEffect", Codec.STRING), + (weather, o) -> weather.screenEffect = o, + weather -> weather.screenEffect, + (weather, parent) -> weather.screenEffect = parent.screenEffect + ) + .addValidator(CommonAssetValidator.UI_SCREEN_EFFECT) + .add() + .appendInherited(new KeyedCodec<>("FogDistance", Codec.DOUBLE_ARRAY), (weather, o) -> { + weather.fogDistance = new float[2]; + weather.fogDistance[0] = (float)o[0]; + weather.fogDistance[1] = (float)o[1]; + }, weather -> new double[]{weather.fogDistance[0], weather.fogDistance[1]}, (weather, parent) -> weather.fogDistance = parent.fogDistance) + .addValidator(Validators.nonNull()) + .addValidator(Validators.doubleArraySize(2)) + .addValidator(Validators.monotonicSequentialDoubleArrayValidator()) + .documentation( + "Array of strictly two values. First is FogNear, which is expected to be negative. Second is FogFar. FogNear determines how foggy it is at the player's position, while FogFar determines the range at which FogDensities starts ramping up." + ) + .add() + .appendInherited( + new KeyedCodec<>("FogOptions", FogOptions.CODEC), + (weather, o) -> weather.fogOptions = o, + weather -> weather.fogOptions, + (weather, parent) -> weather.fogOptions = parent.fogOptions + ) + .documentation("Optional extra information about the fog for this Weather") + .add() + .appendInherited( + new KeyedCodec<>("Particle", PARTICLE_CODEC), + (weather, o) -> weather.particle = o, + weather -> weather.particle, + (weather, parent) -> weather.particle = parent.particle + ) + .add() + .appendInherited( + new KeyedCodec<>("ScreenEffectColors", TimeColorAlpha.ARRAY_CODEC), + (weather, o) -> weather.screenEffectColors = o, + weather -> weather.screenEffectColors, + (weather, parent) -> weather.screenEffectColors = parent.screenEffectColors + ) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("SunlightDampingMultipliers", new ArrayCodec<>(TimeFloat.CODEC, TimeFloat[]::new)), + (weather, o) -> weather.sunlightDampingMultiplier = o, + weather -> weather.sunlightDampingMultiplier, + (weather, parent) -> weather.sunlightDampingMultiplier = parent.sunlightDampingMultiplier + ) + .metadata(new UIEditorSectionStart("Colors")) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("SunlightColors", TimeColor.ARRAY_CODEC), + (weather, o) -> weather.sunlightColors = o, + weather -> weather.sunlightColors, + (weather, parent) -> weather.sunlightColors = parent.sunlightColors + ) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("SunColors", TimeColor.ARRAY_CODEC), + (weather, o) -> weather.sunColors = o, + weather -> weather.sunColors, + (weather, parent) -> weather.sunColors = parent.sunColors + ) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("MoonColors", TimeColorAlpha.ARRAY_CODEC), + (weather, o) -> weather.moonColors = o, + weather -> weather.moonColors, + (weather, parent) -> weather.moonColors = parent.moonColors + ) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("SunGlowColors", TimeColorAlpha.ARRAY_CODEC), + (weather, o) -> weather.sunGlowColors = o, + weather -> weather.sunGlowColors, + (weather, parent) -> weather.sunGlowColors = parent.sunGlowColors + ) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("MoonGlowColors", TimeColorAlpha.ARRAY_CODEC), + (weather, o) -> weather.moonGlowColors = o, + weather -> weather.moonGlowColors, + (weather, parent) -> weather.moonGlowColors = parent.moonGlowColors + ) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("SunScales", new ArrayCodec<>(TimeFloat.CODEC, TimeFloat[]::new)), + (weather, o) -> weather.sunScales = o, + weather -> weather.sunScales, + (weather, parent) -> weather.sunScales = parent.sunScales + ) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("MoonScales", new ArrayCodec<>(TimeFloat.CODEC, TimeFloat[]::new)), + (weather, o) -> weather.moonScales = o, + weather -> weather.moonScales, + (weather, parent) -> weather.moonScales = parent.moonScales + ) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("SkyTopColors", TimeColorAlpha.ARRAY_CODEC), + (weather, o) -> weather.skyTopColors = o, + weather -> weather.skyTopColors, + (weather, parent) -> weather.skyTopColors = parent.skyTopColors + ) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("SkyBottomColors", TimeColorAlpha.ARRAY_CODEC), + (weather, o) -> weather.skyBottomColors = o, + weather -> weather.skyBottomColors, + (weather, parent) -> weather.skyBottomColors = parent.skyBottomColors + ) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("SkySunsetColors", TimeColorAlpha.ARRAY_CODEC), + (weather, o) -> weather.skySunsetColors = o, + weather -> weather.skySunsetColors, + (weather, parent) -> weather.skySunsetColors = parent.skySunsetColors + ) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("FogColors", TimeColor.ARRAY_CODEC), + (weather, o) -> weather.fogColors = o, + weather -> weather.fogColors, + (weather, parent) -> weather.fogColors = parent.fogColors + ) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("FogHeightFalloffs", new ArrayCodec<>(TimeFloat.CODEC, TimeFloat[]::new)), + (weather, o) -> weather.fogHeightFalloffs = o, + weather -> weather.fogHeightFalloffs, + (weather, parent) -> weather.fogHeightFalloffs = parent.fogHeightFalloffs + ) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("FogDensities", new ArrayCodec<>(TimeFloat.CODEC, TimeFloat[]::new)), + (weather, o) -> weather.fogDensities = o, + weather -> weather.fogDensities, + (weather, parent) -> weather.fogDensities = parent.fogDensities + ) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("WaterTints", TimeColor.ARRAY_CODEC), + (weather, o) -> weather.waterTints = o, + weather -> weather.waterTints, + (weather, parent) -> weather.waterTints = parent.waterTints + ) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("ColorFilters", TimeColor.ARRAY_CODEC), + (weather, o) -> weather.colorFilters = o, + weather -> weather.colorFilters, + (weather, parent) -> weather.colorFilters = parent.colorFilters + ) + .metadata(new UIEditor(UIEditor.TIMELINE)) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("Moons", new ArrayCodec<>(DayTexture.CODEC, DayTexture[]::new)), + (weather, o) -> weather.moons = o, + weather -> weather.moons, + (weather, parent) -> weather.moons = parent.moons + ) + .metadata(new UIEditorSectionStart("Moons")) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .appendInherited( + new KeyedCodec<>("Clouds", new ArrayCodec<>(Cloud.CODEC, Cloud[]::new)), + (weather, o) -> weather.clouds = o, + weather -> weather.clouds, + (weather, parent) -> weather.clouds = parent.clouds + ) + .metadata(new UIEditorSectionStart("Clouds")) + .metadata(UIDefaultCollapsedState.UNCOLLAPSED) + .add() + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(Weather::getAssetStore)); + private static AssetStore> ASSET_STORE; + public static final float[] DEFAULT_FOG_DISTANCE = new float[]{-96.0F, 1024.0F}; + public static final int UNKNOWN_ID = 0; + public static final Weather UNKNOWN = new Weather("Unknown"); + protected AssetExtraInfo.Data data; + protected String id; + protected DayTexture[] moons; + protected Cloud[] clouds; + protected TimeFloat[] sunlightDampingMultiplier; + protected TimeColor[] sunlightColors; + protected TimeColor[] sunColors; + protected TimeColorAlpha[] moonColors; + protected TimeColorAlpha[] sunGlowColors; + protected TimeColorAlpha[] moonGlowColors; + protected TimeFloat[] sunScales; + protected TimeFloat[] moonScales; + protected TimeColorAlpha[] skyTopColors; + protected TimeColorAlpha[] skyBottomColors; + protected TimeColorAlpha[] skySunsetColors; + protected TimeColor[] fogColors; + protected TimeFloat[] fogHeightFalloffs; + protected TimeFloat[] fogDensities; + protected TimeColor[] waterTints; + protected float[] fogDistance = DEFAULT_FOG_DISTANCE; + protected FogOptions fogOptions; + protected String screenEffect; + protected TimeColorAlpha[] screenEffectColors; + protected TimeColor[] colorFilters; + protected String stars; + protected WeatherParticle particle; + private SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(Weather.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public Weather( + String id, + DayTexture[] moons, + Cloud[] clouds, + TimeFloat[] sunlightDampingMultiplier, + TimeColor[] sunlightColors, + TimeColor[] sunColors, + TimeColorAlpha[] moonColors, + TimeColorAlpha[] sunGlowColors, + TimeColorAlpha[] moonGlowColors, + TimeFloat[] sunScales, + TimeFloat[] moonScales, + TimeColorAlpha[] skyTopColors, + TimeColorAlpha[] skyBottomColors, + TimeColorAlpha[] skySunsetColors, + TimeColor[] fogColors, + TimeFloat[] fogHeightFalloffs, + TimeFloat[] fogDensities, + TimeColor[] waterTints, + float[] fogDistance, + FogOptions fogOptions, + String screenEffect, + TimeColorAlpha[] screenEffectColors, + TimeColor[] colorFilters, + String stars, + WeatherParticle particle + ) { + this.id = id; + this.moons = moons; + this.clouds = clouds; + this.sunlightDampingMultiplier = sunlightDampingMultiplier; + this.sunlightColors = sunlightColors; + this.sunColors = sunColors; + this.moonColors = moonColors; + this.sunGlowColors = sunGlowColors; + this.moonGlowColors = moonGlowColors; + this.sunScales = sunScales; + this.moonScales = moonScales; + this.skyTopColors = skyTopColors; + this.skyBottomColors = skyBottomColors; + this.skySunsetColors = skySunsetColors; + this.fogColors = fogColors; + this.fogHeightFalloffs = fogHeightFalloffs; + this.fogDensities = fogDensities; + this.waterTints = waterTints; + this.fogDistance = fogDistance; + this.fogOptions = fogOptions; + this.screenEffect = screenEffect; + this.screenEffectColors = screenEffectColors; + this.colorFilters = colorFilters; + this.stars = stars; + this.particle = particle; + } + + public Weather(String id) { + this.id = id; + } + + protected Weather() { + } + + @Nonnull + public com.hypixel.hytale.protocol.Weather toPacket() { + com.hypixel.hytale.protocol.Weather cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.Weather packet = new com.hypixel.hytale.protocol.Weather(); + packet.id = this.id; + if (this.moons != null && this.moons.length > 0) { + packet.moons = toStringMap(this.moons); + } + + if (this.clouds != null && this.clouds.length > 0) { + packet.clouds = ArrayUtil.copyAndMutate(this.clouds, Cloud::toPacket, com.hypixel.hytale.protocol.Cloud[]::new); + } + + if (this.sunlightDampingMultiplier != null && this.sunlightDampingMultiplier.length > 0) { + packet.sunlightDampingMultiplier = toFloatMap(this.sunlightDampingMultiplier); + } + + if (this.sunlightColors != null && this.sunlightColors.length > 0) { + packet.sunlightColors = toColorMap(this.sunlightColors); + } + + if (this.sunColors != null && this.sunColors.length > 0) { + packet.sunColors = toColorMap(this.sunColors); + } + + if (this.sunGlowColors != null && this.sunGlowColors.length > 0) { + packet.sunGlowColors = toColorAlphaMap(this.sunGlowColors); + } + + if (this.sunScales != null && this.sunScales.length > 0) { + packet.sunScales = toFloatMap(this.sunScales); + } + + if (this.moonColors != null && this.moonColors.length > 0) { + packet.moonColors = toColorAlphaMap(this.moonColors); + } + + if (this.moonGlowColors != null && this.moonGlowColors.length > 0) { + packet.moonGlowColors = toColorAlphaMap(this.moonGlowColors); + } + + if (this.moonScales != null && this.moonScales.length > 0) { + packet.moonScales = toFloatMap(this.moonScales); + } + + if (this.skyTopColors != null && this.skyTopColors.length > 0) { + packet.skyTopColors = toColorAlphaMap(this.skyTopColors); + } + + if (this.skyBottomColors != null && this.skyBottomColors.length > 0) { + packet.skyBottomColors = toColorAlphaMap(this.skyBottomColors); + } + + if (this.skySunsetColors != null && this.skySunsetColors.length > 0) { + packet.skySunsetColors = toColorAlphaMap(this.skySunsetColors); + } + + if (this.fogColors != null && this.fogColors.length > 0) { + packet.fogColors = toColorMap(this.fogColors); + } + + if (this.fogHeightFalloffs != null && this.fogHeightFalloffs.length > 0) { + packet.fogHeightFalloffs = toFloatMap(this.fogHeightFalloffs); + } + + if (this.fogDensities != null && this.fogDensities.length > 0) { + packet.fogDensities = toFloatMap(this.fogDensities); + } + + packet.screenEffect = this.screenEffect; + if (this.screenEffectColors != null && this.screenEffectColors.length > 0) { + packet.screenEffectColors = toColorAlphaMap(this.screenEffectColors); + } + + if (this.colorFilters != null && this.colorFilters.length > 0) { + packet.colorFilters = toColorMap(this.colorFilters); + } + + if (this.waterTints != null && this.waterTints.length > 0) { + packet.waterTints = toColorMap(this.waterTints); + } + + if (this.fogOptions != null) { + packet.fogOptions = this.fogOptions.toPacket(); + } + + packet.fog = new NearFar(this.fogDistance[0], this.fogDistance[1]); + packet.stars = this.stars; + if (this.particle != null) { + packet.particle = this.particle; + } + + if (this.data != null) { + IntSet tags = this.data.getExpandedTagIndexes(); + packet.tagIndexes = tags.toIntArray(); + } + + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + public String getId() { + return this.id; + } + + public DayTexture[] getMoons() { + return this.moons; + } + + public Cloud[] getClouds() { + return this.clouds; + } + + public TimeFloat[] getSunlightDampingMultiplier() { + return this.sunlightDampingMultiplier; + } + + public TimeColor[] getSunlightColors() { + return this.sunlightColors; + } + + public TimeColor[] getSunColors() { + return this.sunColors; + } + + public TimeColorAlpha[] getMoonColors() { + return this.moonColors; + } + + public TimeColorAlpha[] getSunGlowColors() { + return this.sunGlowColors; + } + + public TimeColorAlpha[] getMoonGlowColors() { + return this.moonGlowColors; + } + + public TimeFloat[] getSunScales() { + return this.sunScales; + } + + public TimeFloat[] getMoonScales() { + return this.moonScales; + } + + public TimeColorAlpha[] getSkyTopColors() { + return this.skyTopColors; + } + + public TimeColorAlpha[] getSkyBottomColors() { + return this.skyBottomColors; + } + + public TimeColorAlpha[] getSkySunsetColors() { + return this.skySunsetColors; + } + + public TimeColor[] getFogColors() { + return this.fogColors; + } + + public TimeFloat[] getFogHeightFalloffs() { + return this.fogHeightFalloffs; + } + + public TimeFloat[] getFogDensities() { + return this.fogDensities; + } + + public TimeColor[] getWaterTints() { + return this.waterTints; + } + + public float[] getFogDistance() { + return this.fogDistance; + } + + public FogOptions getFogOptions() { + return this.fogOptions; + } + + public String getScreenEffect() { + return this.screenEffect; + } + + public TimeColorAlpha[] getScreenEffectColors() { + return this.screenEffectColors; + } + + public TimeColor[] getColorFilters() { + return this.colorFilters; + } + + public String getStars() { + return this.stars; + } + + public WeatherParticle getParticle() { + return this.particle; + } + + @Nonnull + @Override + public String toString() { + return "Weather{id='" + + this.id + + "', moons=" + + Arrays.toString((Object[])this.moons) + + ", clouds=" + + Arrays.toString((Object[])this.clouds) + + ", sunlightDampingMultiplier=" + + Arrays.toString((Object[])this.sunlightDampingMultiplier) + + ", sunlightColors=" + + Arrays.toString((Object[])this.sunlightColors) + + ", sunColors=" + + Arrays.toString((Object[])this.sunColors) + + ", sunGlowColors=" + + Arrays.toString((Object[])this.sunGlowColors) + + ", sunScales=" + + Arrays.toString((Object[])this.sunScales) + + ", moonColors=" + + Arrays.toString((Object[])this.moonColors) + + ", moonGlowColors=" + + Arrays.toString((Object[])this.moonGlowColors) + + ", moonScales=" + + Arrays.toString((Object[])this.moonScales) + + ", skyTopColors=" + + Arrays.toString((Object[])this.skyTopColors) + + ", skyBottomColors=" + + Arrays.toString((Object[])this.skyBottomColors) + + ", skySunsetColors=" + + Arrays.toString((Object[])this.skySunsetColors) + + ", fogColors=" + + Arrays.toString((Object[])this.fogColors) + + ", fogHeightFalloffs=" + + Arrays.toString((Object[])this.fogHeightFalloffs) + + ", fogDensities=" + + Arrays.toString((Object[])this.fogDensities) + + ", fogDistance=" + + Arrays.toString(this.fogDistance) + + ", fogOptions=" + + this.fogOptions + + ", screenEffect=" + + this.screenEffect + + ", screenEffectColors=" + + Arrays.toString((Object[])this.screenEffectColors) + + ", colorFilters=" + + Arrays.toString((Object[])this.colorFilters) + + ", waterTints=" + + Arrays.toString((Object[])this.waterTints) + + ", stars=" + + this.stars + + ", particle=" + + this.particle + + "}"; + } + + @Nonnull + public static Map toStringMap(@Nonnull DayTexture[] dayTexture) { + return Arrays.stream(dayTexture).collect(Collectors.toMap(DayTexture::getDay, DayTexture::getTexture)); + } + + @Nonnull + public static Map toFloatMap(@Nonnull TimeFloat[] timeFloat) { + return Arrays.stream(timeFloat).collect(Collectors.toMap(TimeFloat::getHour, TimeFloat::getValue)); + } + + @Nonnull + public static Map toColorMap(@Nonnull TimeColor[] timeColor) { + return Arrays.stream(timeColor).collect(Collectors.toMap(TimeColor::getHour, TimeColor::getColor)); + } + + @Nonnull + public static Map toColorAlphaMap(@Nonnull TimeColorAlpha[] timeColorAlpha) { + return Arrays.stream(timeColorAlpha).collect(Collectors.toMap(TimeColorAlpha::getHour, TimeColorAlpha::getColor)); + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/type/wordlist/WordList.java b/src/com/hypixel/hytale/server/core/asset/type/wordlist/WordList.java new file mode 100644 index 0000000..b58d29e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/type/wordlist/WordList.java @@ -0,0 +1,145 @@ +package com.hypixel.hytale.server.core.asset.type.wordlist; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.server.core.modules.i18n.I18nModule; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WordList implements JsonAssetWithMap> { + private static final String WORDLISTS_TRANSLATION_FILE = "wordlists"; + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + WordList.class, + WordList::new, + Codec.STRING, + (wordList, s) -> wordList.id = s, + wordList -> wordList.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("TranslationKeys", Codec.STRING_ARRAY), + (wordList, o) -> wordList.translationKeys = o, + wordList -> wordList.translationKeys, + (wordList, parent) -> wordList.translationKeys = parent.translationKeys + ) + .documentation( + "The list of word message keys. Need to be added in Assets/Server/Languages/wordlists.lang. For example if the WordList asset file is 'animals' and you write 'cow' here, it will refer to 'animals.cow' (full path is 'wordlists.animals.cow')" + ) + .add() + .afterDecode(WordList::processConfig) + .build(); + private static AssetStore> ASSET_STORE; + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(WordList::getAssetStore)); + private static final WordList EMPTY = new WordList(); + protected AssetExtraInfo.Data data; + protected String id; + protected String[] translationKeys; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(WordList.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + public static WordList getWordList(@Nullable String assetKey) { + if (assetKey != null && !assetKey.isEmpty()) { + WordList wordList = getAssetMap().getAsset(assetKey); + return wordList == null ? EMPTY : wordList; + } else { + return EMPTY; + } + } + + protected WordList() { + } + + public String getId() { + return this.id; + } + + protected void processConfig() { + if (this.translationKeys != null) { + String idLower = this.id.toLowerCase(); + String[] remappedTranslationKeys = new String[this.translationKeys.length]; + + for (int i = 0; i < this.translationKeys.length; i++) { + remappedTranslationKeys[i] = "wordlists." + idLower + "." + this.translationKeys[i]; + } + + this.translationKeys = remappedTranslationKeys; + } + } + + @Nullable + public String pickDefaultLanguage(@Nonnull Random random, @Nonnull Set alreadyUsedTranslated) { + String translationKey = this.pickTranslationKey(random, alreadyUsedTranslated, "en-US"); + return translationKey == null ? null : I18nModule.get().getMessage("en-US", translationKey); + } + + @Nullable + public String pickTranslationKey(@Nonnull Random random, @Nonnull Set alreadyUsedTranslated, String languageForAlreadyUsed) { + List available = toKeysListMinusTranslated(this.translationKeys, alreadyUsedTranslated, languageForAlreadyUsed); + return available.isEmpty() ? null : available.get(random.nextInt(available.size())); + } + + @Nonnull + private static List toListMinusSet(@Nullable T[] array, @Nonnull Set set) { + if (array != null && array.length != 0) { + List result = new ObjectArrayList<>(array.length); + + for (T elem : array) { + if (!set.contains(elem)) { + result.add(elem); + } + } + + return result; + } else { + return Collections.emptyList(); + } + } + + @Nonnull + private static List toKeysListMinusTranslated(@Nullable String[] translationKeys, @Nonnull Set alreadyUsedTranslated, String language) { + if (translationKeys != null && translationKeys.length != 0) { + List result = new ObjectArrayList<>(translationKeys.length); + + for (String translationKey : translationKeys) { + String translated = I18nModule.get().getMessage(language, translationKey); + if (translated != null && !alreadyUsedTranslated.contains(translated)) { + result.add(translationKey); + } + } + + return result; + } else { + return Collections.emptyList(); + } + } + + @Nonnull + @Override + public String toString() { + return "WordList{id='" + this.id + "', translationKeys=" + this.translationKeys + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/asset/util/ColorParseUtil.java b/src/com/hypixel/hytale/server/core/asset/util/ColorParseUtil.java new file mode 100644 index 0000000..27d46c8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/asset/util/ColorParseUtil.java @@ -0,0 +1,384 @@ +package com.hypixel.hytale.server.core.asset.util; + +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.ColorAlpha; +import com.hypixel.hytale.protocol.ColorLight; +import java.io.IOException; +import java.util.Objects; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ColorParseUtil { + public static final Pattern HEX_COLOR_PATTERN = Pattern.compile("^\\s*#([0-9a-fA-F]{3}){1,2}\\s*$"); + public static final Pattern HEX_ALPHA_COLOR_PATTERN = Pattern.compile("^\\s*#([0-9a-fA-F]{4}){1,2}\\s*$"); + public static final Pattern RGB_COLOR_PATTERN = Pattern.compile("^\\s*rgb\\((\\s*[0-9]{1,3}\\s*,){2}\\s*[0-9]{1,3}\\s*\\)\\s*$"); + public static final Pattern RGBA_COLOR_PATTERN = Pattern.compile("^\\s*rgba\\((\\s*[0-9]{1,3}\\s*,){3}\\s*[0,1](.[0-9]*)?\\s*\\)\\s*$"); + public static final Pattern RGBA_HEX_COLOR_PATTERN = Pattern.compile("^\\s*rgba\\(\\s*#([0-9a-fA-F]{3}){1,2}\\s*,\\s*[0,1](.[0-9]*)?\\s*\\)\\s*$"); + + public ColorParseUtil() { + } + + @Nullable + public static ColorAlpha readColorAlpha(@Nonnull RawJsonReader reader) throws IOException { + reader.consumeWhiteSpace(); + + return switch (reader.peek()) { + case 35 -> readHexStringToColorAlpha(reader); + case 114 -> readRgbaStringToColorAlpha(reader); + default -> null; + }; + } + + @Nullable + public static ColorAlpha parseColorAlpha(@Nonnull String stringValue) { + if (HEX_ALPHA_COLOR_PATTERN.matcher(stringValue).matches()) { + return hexStringToColorAlpha(stringValue); + } else if (RGBA_HEX_COLOR_PATTERN.matcher(stringValue).matches()) { + return rgbaHexStringToColor(stringValue); + } else { + return RGBA_COLOR_PATTERN.matcher(stringValue).matches() ? rgbaDecimalStringToColor(stringValue) : null; + } + } + + @Nullable + public static Color readColor(@Nonnull RawJsonReader reader) throws IOException { + reader.consumeWhiteSpace(); + + return switch (reader.peek()) { + case 35 -> readHexStringToColor(reader); + case 114 -> readRgbStringToColor(reader); + default -> null; + }; + } + + @Nullable + public static Color parseColor(@Nonnull String stringValue) { + if (HEX_COLOR_PATTERN.matcher(stringValue).matches()) { + return hexStringToColor(stringValue); + } else { + return RGB_COLOR_PATTERN.matcher(stringValue).matches() ? rgbStringToColor(stringValue) : null; + } + } + + @Nonnull + public static Color readHexStringToColor(@Nonnull RawJsonReader reader) throws IOException { + int rgba = readHexAlphaStringToRGBAInt(reader); + return new Color((byte)(rgba >> 24 & 0xFF), (byte)(rgba >> 16 & 0xFF), (byte)(rgba >> 8 & 0xFF)); + } + + @Nonnull + public static Color hexStringToColor(String color) { + int rgba = hexAlphaStringToRGBAInt(color); + return new Color((byte)(rgba >> 24 & 0xFF), (byte)(rgba >> 16 & 0xFF), (byte)(rgba >> 8 & 0xFF)); + } + + @Nonnull + public static ColorAlpha readHexStringToColorAlpha(@Nonnull RawJsonReader reader) throws IOException { + int rgba = readHexAlphaStringToRGBAInt(reader); + return new ColorAlpha((byte)(rgba & 0xFF), (byte)(rgba >> 24 & 0xFF), (byte)(rgba >> 16 & 0xFF), (byte)(rgba >> 8 & 0xFF)); + } + + @Nonnull + public static ColorAlpha hexStringToColorAlpha(String color) { + int rgba = hexAlphaStringToRGBAInt(color); + return new ColorAlpha((byte)(rgba & 0xFF), (byte)(rgba >> 24 & 0xFF), (byte)(rgba >> 16 & 0xFF), (byte)(rgba >> 8 & 0xFF)); + } + + public static int readHexAlphaStringToRGBAInt(@Nonnull RawJsonReader reader) throws IOException { + reader.consumeWhiteSpace(); + reader.expect('#'); + reader.mark(); + + try { + int value = reader.readIntValue(16); + int size = reader.getMarkDistance(); + switch (size) { + case 3: + value <<= 4; + value |= 15; + case 4: + int red = value >> 12 & 15; + int green = value >> 8 & 15; + int blue = value >> 4 & 15; + int alpha = value & 15; + return red << 28 | red << 24 | green << 20 | green << 16 | blue << 12 | blue << 8 | alpha << 4 | alpha; + case 5: + case 7: + default: + throw new IllegalArgumentException("Invalid hex color size: " + size); + case 6: + return value << 8 | 0xFF; + case 8: + return value; + } + } finally { + reader.unmark(); + reader.consumeWhiteSpace(); + } + } + + public static int hexAlphaStringToRGBAInt(String color) { + Objects.requireNonNull(color, "Color must not be null"); + color = color.trim(); + if (!color.isEmpty() && color.charAt(0) == '#') { + color = color.substring(1); + int value = (int)Long.parseLong(color, 16); + switch (color.length()) { + case 3: + value <<= 4; + value |= 15; + case 4: + int red = value >> 12 & 15; + int green = value >> 8 & 15; + int blue = value >> 4 & 15; + int alpha = value & 15; + return red << 28 | red << 24 | green << 20 | green << 16 | blue << 12 | blue << 8 | alpha << 4 | alpha; + case 5: + case 7: + default: + throw new IllegalArgumentException("Invalid hex color format: '" + color + "'"); + case 6: + return value << 8 | 0xFF; + case 8: + return value; + } + } else { + throw new IllegalArgumentException("Hex color must start with '#'"); + } + } + + public static int readHexStringToRGBInt(@Nonnull RawJsonReader reader) throws IOException { + return readHexAlphaStringToRGBAInt(reader) >>> 8; + } + + public static int hexStringToRGBInt(String color) { + return hexAlphaStringToRGBAInt(color) >>> 8; + } + + @Nonnull + public static String colorToHexString(@Nullable Color color) { + return color == null ? "#FFFFFF" : toHexString(color.red, color.green, color.blue); + } + + @Nonnull + public static String colorToHexAlphaString(@Nullable ColorAlpha color) { + return color == null ? "#FFFFFFFF" : toHexAlphaString(color.red, color.green, color.blue, color.alpha); + } + + @Nonnull + public static Color readRgbStringToColor(@Nonnull RawJsonReader reader) throws IOException { + reader.consumeWhiteSpace(); + reader.expect("rgb(", 0); + reader.consumeWhiteSpace(); + byte red = reader.readByteValue(); + reader.consumeWhiteSpace(); + reader.expect(','); + reader.consumeWhiteSpace(); + byte green = reader.readByteValue(); + reader.consumeWhiteSpace(); + reader.expect(','); + reader.consumeWhiteSpace(); + byte blue = reader.readByteValue(); + reader.consumeWhiteSpace(); + reader.expect(')'); + reader.consumeWhiteSpace(); + return new Color(red, green, blue); + } + + @Nonnull + public static Color rgbStringToColor(String color) { + Objects.requireNonNull(color, "Color must not be null"); + color = color.trim(); + if (color.startsWith("rgb(") && color.charAt(color.length() - 1) == ')') { + color = color.substring(4, color.length() - 1); + String[] channels = color.split(","); + int channelLength = channels.length; + if (channelLength != 3) { + throw new IllegalArgumentException("rgb() but contain all 3 channels; r, g and b"); + } else { + byte red = (byte)Integer.parseInt(channels[0].trim()); + byte green = (byte)Integer.parseInt(channels[1].trim()); + byte blue = (byte)Integer.parseInt(channels[2].trim()); + return new Color(red, green, blue); + } + } else { + throw new IllegalArgumentException("Color must start with 'rgb(' and end with ')'"); + } + } + + @Nonnull + public static ColorAlpha readRgbaStringToColorAlpha(@Nonnull RawJsonReader reader) throws IOException { + reader.consumeWhiteSpace(); + reader.expect("rgba(", 0); + reader.consumeWhiteSpace(); + return reader.peek() == 35 ? readRgbaHexStringToColor(reader, false) : readRgbaDecimalStringToColor(reader, false); + } + + @Nonnull + public static ColorAlpha readRgbaDecimalStringToColor(@Nonnull RawJsonReader reader) throws IOException { + return readRgbaDecimalStringToColor(reader, true); + } + + @Nonnull + public static ColorAlpha readRgbaDecimalStringToColor(@Nonnull RawJsonReader reader, boolean readStart) throws IOException { + if (readStart) { + reader.consumeWhiteSpace(); + reader.expect("rgba(", 0); + reader.consumeWhiteSpace(); + } + + byte red = reader.readByteValue(); + reader.consumeWhiteSpace(); + reader.expect(','); + reader.consumeWhiteSpace(); + byte green = reader.readByteValue(); + reader.consumeWhiteSpace(); + reader.expect(','); + reader.consumeWhiteSpace(); + byte blue = reader.readByteValue(); + reader.consumeWhiteSpace(); + reader.expect(','); + reader.consumeWhiteSpace(); + byte alpha = (byte)MathUtil.clamp(reader.readFloatValue() * 255.0F, 0.0F, 255.0F); + reader.consumeWhiteSpace(); + reader.expect(')'); + reader.consumeWhiteSpace(); + return new ColorAlpha(alpha, red, green, blue); + } + + @Nonnull + public static ColorAlpha rgbaDecimalStringToColor(String color) { + Objects.requireNonNull(color, "Color must not be null"); + color = color.trim(); + if (color.startsWith("rgba(") && color.charAt(color.length() - 1) == ')') { + color = color.substring(5, color.length() - 1); + String[] channels = color.split(","); + int channelLength = channels.length; + if (channelLength != 4) { + throw new IllegalArgumentException("rgba() but contain all 4 channels; r, g, b and a"); + } else { + byte red = (byte)MathUtil.clamp(Integer.parseInt(channels[0].trim()), 0, 255); + byte green = (byte)MathUtil.clamp(Integer.parseInt(channels[1].trim()), 0, 255); + byte blue = (byte)MathUtil.clamp(Integer.parseInt(channels[2].trim()), 0, 255); + byte alpha = (byte)MathUtil.clamp(Float.parseFloat(channels[3]) * 255.0F, 0.0F, 255.0F); + return new ColorAlpha(alpha, red, green, blue); + } + } else { + throw new IllegalArgumentException("Color must start with 'rgba(' and end with ')'"); + } + } + + @Nonnull + public static ColorAlpha readRgbaHexStringToColor(@Nonnull RawJsonReader reader) throws IOException { + return readRgbaHexStringToColor(reader, true); + } + + @Nonnull + public static ColorAlpha readRgbaHexStringToColor(@Nonnull RawJsonReader reader, boolean readStart) throws IOException { + if (readStart) { + reader.consumeWhiteSpace(); + reader.expect("rgba(", 0); + reader.consumeWhiteSpace(); + } + + long val = readHexAlphaStringToRGBAInt(reader); + reader.consumeWhiteSpace(); + reader.expect(','); + reader.consumeWhiteSpace(); + byte alpha = (byte)MathUtil.clamp(reader.readFloatValue() * 255.0F, 0.0F, 255.0F); + reader.consumeWhiteSpace(); + reader.expect(')'); + reader.consumeWhiteSpace(); + return new ColorAlpha(alpha, (byte)(val >> 24 & 255L), (byte)(val >> 16 & 255L), (byte)(val >> 8 & 255L)); + } + + @Nonnull + public static ColorAlpha rgbaHexStringToColor(String color) { + Objects.requireNonNull(color, "Color must not be null"); + color = color.trim(); + if (color.startsWith("rgba(") && color.charAt(color.length() - 1) == ')') { + color = color.substring(5, color.length() - 1); + String[] channels = color.split(","); + int channelLength = channels.length; + if (channelLength != 2) { + throw new IllegalArgumentException("rgba() but contain both #rgb and a"); + } else { + long val = hexAlphaStringToRGBAInt(channels[0].trim()); + byte alpha = (byte)MathUtil.clamp(Float.parseFloat(channels[1]) * 255.0F, 0.0F, 255.0F); + return new ColorAlpha(alpha, (byte)(val >> 24 & 255L), (byte)(val >> 16 & 255L), (byte)(val >> 8 & 255L)); + } + } else { + throw new IllegalArgumentException("Color must start with 'rgba(' and end with ')'"); + } + } + + @Nonnull + public static String colorToHex(@Nullable java.awt.Color color) { + if (color == null) { + return "#FFFFFF"; + } else { + int argb = color.getRGB(); + int rgb = argb & 16777215; + return toHexString(rgb); + } + } + + @Nonnull + public static String colorToHexAlpha(@Nullable java.awt.Color color) { + if (color == null) { + return "#FFFFFFFF"; + } else { + int argb = color.getRGB(); + int alpha = argb >> 24 & 0xFF; + int rgb = argb & 16777215; + int rgba = rgb << 8 | alpha; + return toHexAlphaString(rgba); + } + } + + public static int colorToARGBInt(@Nullable Color color) { + return color == null ? -1 : 0xFF000000 | (color.red & 0xFF) << 16 | (color.green & 0xFF) << 8 | color.blue & 0xFF; + } + + public static void hexStringToColorLightDirect(@Nonnull ColorLight colorLight, @Nonnull String color) { + if (color.length() == 4) { + colorLight.red = Byte.parseByte(color.substring(1, 2), 16); + colorLight.green = Byte.parseByte(color.substring(2, 3), 16); + colorLight.blue = Byte.parseByte(color.substring(3, 4), 16); + } else { + colorLight.red = (byte)(Integer.parseInt(color.substring(1, 3), 16) / 17); + colorLight.green = (byte)(Integer.parseInt(color.substring(3, 5), 16) / 17); + colorLight.blue = (byte)(Integer.parseInt(color.substring(5, 7), 16) / 17); + } + } + + @Nonnull + public static String colorLightToHexString(@Nonnull ColorLight colorLight) { + return toHexString((byte)(colorLight.red * 17), (byte)(colorLight.green * 17), (byte)(colorLight.blue * 17)); + } + + @Nonnull + public static String toHexString(byte red, byte green, byte blue) { + return toHexString((red & 255) << 16 | (green & 255) << 8 | blue & 255); + } + + @Nonnull + public static String toHexString(int rgb) { + String hexString = Integer.toHexString(rgb); + return "#" + "0".repeat(6 - hexString.length()) + hexString; + } + + @Nonnull + public static String toHexAlphaString(byte red, byte green, byte blue, byte alpha) { + return toHexAlphaString((red & 255) << 24 | (green & 255) << 16 | (blue & 255) << 8 | alpha & 255); + } + + @Nonnull + public static String toHexAlphaString(int rgba) { + String hexString = Integer.toHexString(rgba); + return "#" + "0".repeat(8 - hexString.length()) + hexString; + } +} diff --git a/src/com/hypixel/hytale/server/core/auth/AuthConfig.java b/src/com/hypixel/hytale/server/core/auth/AuthConfig.java new file mode 100644 index 0000000..4a74f5d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/AuthConfig.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.core.auth; + +import com.hypixel.hytale.common.util.java.ManifestUtil; +import javax.annotation.Nonnull; + +public class AuthConfig { + public static final String USER_AGENT = "HytaleServer/" + ManifestUtil.getImplementationVersion(); + public static final String OAUTH_AUTH_URL = "https://oauth.accounts.hytale.com/oauth2/auth"; + public static final String OAUTH_TOKEN_URL = "https://oauth.accounts.hytale.com/oauth2/token"; + public static final String DEVICE_AUTH_URL = "https://oauth.accounts.hytale.com/oauth2/device/auth"; + public static final String CONSENT_REDIRECT_URL = "https://accounts.hytale.com/consent/client"; + public static final String SESSION_SERVICE_URL = "https://sessions.hytale.com"; + public static final String ACCOUNT_DATA_URL = "https://account-data.hytale.com"; + public static final String BUILD_ENVIRONMENT = "release"; + public static final String CLIENT_ID = "hytale-server"; + public static final String[] SCOPES = new String[]{"openid", "offline", "auth:server"}; + public static final String SCOPE_CLIENT = "hytale:client"; + public static final String SCOPE_SERVER = "hytale:server"; + public static final String SCOPE_EDITOR = "hytale:editor"; + public static final int HTTP_TIMEOUT_SECONDS = 10; + public static final int DEVICE_POLL_INTERVAL_SECONDS = 5; + public static final String ENV_SERVER_AUDIENCE = "HYTALE_SERVER_AUDIENCE"; + public static final String ENV_SERVER_IDENTITY_TOKEN = "HYTALE_SERVER_IDENTITY_TOKEN"; + public static final String ENV_SERVER_SESSION_TOKEN = "HYTALE_SERVER_SESSION_TOKEN"; + private static final String SERVER_AUDIENCE_OVERRIDE = System.getenv("HYTALE_SERVER_AUDIENCE"); + + @Nonnull + public static String getServerAudience() { + return SERVER_AUDIENCE_OVERRIDE != null ? SERVER_AUDIENCE_OVERRIDE : ServerAuthManager.getInstance().getServerSessionId().toString(); + } + + private AuthConfig() { + } +} diff --git a/src/com/hypixel/hytale/server/core/auth/AuthConfigGenerated.java b/src/com/hypixel/hytale/server/core/auth/AuthConfigGenerated.java new file mode 100644 index 0000000..90eac9a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/AuthConfigGenerated.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.server.core.auth; + +final class AuthConfigGenerated { + static final String DOMAIN = "hytale.com"; + static final String ENVIRONMENT = "release"; + + private AuthConfigGenerated() { + } +} diff --git a/src/com/hypixel/hytale/server/core/auth/AuthCredentialStoreProvider.java b/src/com/hypixel/hytale/server/core/auth/AuthCredentialStoreProvider.java new file mode 100644 index 0000000..7cf1b14 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/AuthCredentialStoreProvider.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.auth; + +import com.hypixel.hytale.codec.lookup.BuilderCodecMapCodec; +import javax.annotation.Nonnull; + +public interface AuthCredentialStoreProvider { + BuilderCodecMapCodec CODEC = new BuilderCodecMapCodec<>("Type", true); + + @Nonnull + IAuthCredentialStore createStore(); +} diff --git a/src/com/hypixel/hytale/server/core/auth/CertificateUtil.java b/src/com/hypixel/hytale/server/core/auth/CertificateUtil.java new file mode 100644 index 0000000..7fdc32d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/CertificateUtil.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.server.core.auth; + +import com.hypixel.hytale.logger.HytaleLogger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.Base64; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CertificateUtil { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + + public CertificateUtil() { + } + + @Nullable + public static String computeCertificateFingerprint(@Nonnull X509Certificate certificate) { + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + byte[] certBytes = certificate.getEncoded(); + byte[] hash = sha256.digest(certBytes); + return base64UrlEncode(hash); + } catch (NoSuchAlgorithmException var4) { + LOGGER.at(Level.SEVERE).withCause(var4).log("SHA-256 algorithm not available"); + return null; + } catch (CertificateEncodingException var5) { + LOGGER.at(Level.WARNING).withCause(var5).log("Failed to encode certificate"); + return null; + } + } + + public static boolean validateCertificateBinding(@Nullable String jwtFingerprint, @Nullable X509Certificate clientCert) { + if (jwtFingerprint == null || jwtFingerprint.isEmpty()) { + LOGGER.at(Level.WARNING).log("JWT missing certificate fingerprint (cnf.x5t#S256) - rejecting token"); + return false; + } else if (clientCert == null) { + LOGGER.at(Level.WARNING).log("No client certificate present in mTLS connection - rejecting token"); + return false; + } else { + String actualFingerprint = computeCertificateFingerprint(clientCert); + if (actualFingerprint == null) { + LOGGER.at(Level.WARNING).log("Failed to compute client certificate fingerprint"); + return false; + } else { + boolean matches = timingSafeEquals(jwtFingerprint, actualFingerprint); + if (!matches) { + LOGGER.at(Level.WARNING).log("Certificate fingerprint mismatch! JWT: %s, Actual: %s", jwtFingerprint, actualFingerprint); + } else { + LOGGER.at(Level.INFO).log("Certificate binding validated successfully"); + } + + return matches; + } + } + } + + public static boolean timingSafeEquals(String a, String b) { + if (a != null && b != null) { + byte[] aBytes = a.getBytes(StandardCharsets.UTF_8); + byte[] bBytes = b.getBytes(StandardCharsets.UTF_8); + return MessageDigest.isEqual(aBytes, bBytes); + } else { + return a == b; + } + } + + private static String base64UrlEncode(byte[] input) { + String base64 = Base64.getEncoder().encodeToString(input); + return base64.replace('+', '-').replace('/', '_').replace("=", ""); + } +} diff --git a/src/com/hypixel/hytale/server/core/auth/DefaultAuthCredentialStore.java b/src/com/hypixel/hytale/server/core/auth/DefaultAuthCredentialStore.java new file mode 100644 index 0000000..9b3eb93 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/DefaultAuthCredentialStore.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.server.core.auth; + +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DefaultAuthCredentialStore implements IAuthCredentialStore { + private IAuthCredentialStore.OAuthTokens tokens = new IAuthCredentialStore.OAuthTokens(null, null, null); + @Nullable + private UUID profile; + + public DefaultAuthCredentialStore() { + } + + @Override + public void setTokens(@Nonnull IAuthCredentialStore.OAuthTokens tokens) { + this.tokens = tokens; + } + + @Nonnull + @Override + public IAuthCredentialStore.OAuthTokens getTokens() { + return this.tokens; + } + + @Override + public void setProfile(@Nullable UUID uuid) { + this.profile = uuid; + } + + @Nullable + @Override + public UUID getProfile() { + return this.profile; + } + + @Override + public void clear() { + this.tokens = new IAuthCredentialStore.OAuthTokens(null, null, null); + this.profile = null; + } +} diff --git a/src/com/hypixel/hytale/server/core/auth/EncryptedAuthCredentialStore.java b/src/com/hypixel/hytale/server/core/auth/EncryptedAuthCredentialStore.java new file mode 100644 index 0000000..2ae8aa5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/EncryptedAuthCredentialStore.java @@ -0,0 +1,229 @@ +package com.hypixel.hytale.server.core.auth; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.HardwareUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.util.BsonUtil; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.SecureRandom; +import java.time.Instant; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import org.bson.BsonDocument; + +public class EncryptedAuthCredentialStore implements IAuthCredentialStore { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final String ALGORITHM = "AES/GCM/NoPadding"; + private static final int GCM_IV_LENGTH = 12; + private static final int GCM_TAG_LENGTH = 128; + private static final int KEY_LENGTH = 256; + private static final int PBKDF2_ITERATIONS = 100000; + private static final byte[] SALT = "HytaleAuthCredentialStore".getBytes(StandardCharsets.UTF_8); + private static final BuilderCodec CREDENTIALS_CODEC = BuilderCodec.builder( + EncryptedAuthCredentialStore.StoredCredentials.class, EncryptedAuthCredentialStore.StoredCredentials::new + ) + .append(new KeyedCodec<>("AccessToken", Codec.STRING), (o, v) -> o.accessToken = v, o -> o.accessToken) + .add() + .append(new KeyedCodec<>("RefreshToken", Codec.STRING), (o, v) -> o.refreshToken = v, o -> o.refreshToken) + .add() + .append(new KeyedCodec<>("ExpiresAt", Codec.INSTANT), (o, v) -> o.expiresAt = v, o -> o.expiresAt) + .add() + .append(new KeyedCodec<>("ProfileUuid", Codec.UUID_STRING), (o, v) -> o.profileUuid = v, o -> o.profileUuid) + .add() + .build(); + private final Path path; + @Nullable + private final SecretKey encryptionKey; + private IAuthCredentialStore.OAuthTokens tokens = new IAuthCredentialStore.OAuthTokens(null, null, null); + @Nullable + private UUID profile; + + public EncryptedAuthCredentialStore(@Nonnull Path path) { + this.path = path; + this.encryptionKey = deriveKey(); + if (this.encryptionKey == null) { + LOGGER.at(Level.WARNING).log("Cannot derive encryption key - encrypted storage will not persist credentials"); + } else { + this.load(); + } + } + + @Nullable + private static SecretKey deriveKey() { + UUID hardwareId = HardwareUtil.getUUID(); + if (hardwareId == null) { + return null; + } else { + try { + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + PBEKeySpec spec = new PBEKeySpec(hardwareId.toString().toCharArray(), SALT, 100000, 256); + SecretKey tmp = factory.generateSecret(spec); + return new SecretKeySpec(tmp.getEncoded(), "AES"); + } catch (Exception var4) { + LOGGER.at(Level.WARNING).withCause(var4).log("Failed to derive encryption key"); + return null; + } + } + } + + private void load() { + if (this.encryptionKey != null && Files.exists(this.path)) { + try { + byte[] encrypted = Files.readAllBytes(this.path); + byte[] decrypted = this.decrypt(encrypted); + if (decrypted == null) { + LOGGER.at(Level.WARNING).log("Failed to decrypt credentials from %s - file may be corrupted or from different hardware", this.path); + return; + } + + BsonDocument doc = BsonUtil.readFromBytes(decrypted); + if (doc == null) { + LOGGER.at(Level.WARNING).log("Failed to parse credentials from %s", this.path); + return; + } + + EncryptedAuthCredentialStore.StoredCredentials stored = CREDENTIALS_CODEC.decode(doc); + if (stored != null) { + this.tokens = new IAuthCredentialStore.OAuthTokens(stored.accessToken, stored.refreshToken, stored.expiresAt); + this.profile = stored.profileUuid; + } + + LOGGER.at(Level.INFO).log("Loaded encrypted credentials from %s", this.path); + } catch (Exception var5) { + LOGGER.at(Level.WARNING).withCause(var5).log("Failed to load encrypted credentials from %s", this.path); + } + } + } + + private void save() { + if (this.encryptionKey == null) { + LOGGER.at(Level.WARNING).log("Cannot save credentials - no encryption key available"); + } else { + try { + EncryptedAuthCredentialStore.StoredCredentials stored = new EncryptedAuthCredentialStore.StoredCredentials(); + stored.accessToken = this.tokens.accessToken(); + stored.refreshToken = this.tokens.refreshToken(); + stored.expiresAt = this.tokens.accessTokenExpiresAt(); + stored.profileUuid = this.profile; + BsonDocument doc = (BsonDocument)CREDENTIALS_CODEC.encode(stored); + byte[] plaintext = BsonUtil.writeToBytes(doc); + byte[] encrypted = this.encrypt(plaintext); + if (encrypted == null) { + LOGGER.at(Level.SEVERE).log("Failed to encrypt credentials"); + return; + } + + Files.write(this.path, encrypted); + } catch (IOException var5) { + LOGGER.at(Level.SEVERE).withCause(var5).log("Failed to save encrypted credentials to %s", this.path); + } + } + } + + @Nullable + private byte[] encrypt(@Nonnull byte[] plaintext) { + if (this.encryptionKey == null) { + return null; + } else { + try { + byte[] iv = new byte[12]; + new SecureRandom().nextBytes(iv); + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(1, this.encryptionKey, new GCMParameterSpec(128, iv)); + byte[] ciphertext = cipher.doFinal(plaintext); + ByteBuffer result = ByteBuffer.allocate(iv.length + ciphertext.length); + result.put(iv); + result.put(ciphertext); + return result.array(); + } catch (Exception var6) { + LOGGER.at(Level.SEVERE).withCause(var6).log("Encryption failed"); + return null; + } + } + } + + @Nullable + private byte[] decrypt(@Nonnull byte[] encrypted) { + if (this.encryptionKey != null && encrypted.length >= 12) { + try { + ByteBuffer buffer = ByteBuffer.wrap(encrypted); + byte[] iv = new byte[12]; + buffer.get(iv); + byte[] ciphertext = new byte[buffer.remaining()]; + buffer.get(ciphertext); + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(2, this.encryptionKey, new GCMParameterSpec(128, iv)); + return cipher.doFinal(ciphertext); + } catch (Exception var6) { + LOGGER.at(Level.WARNING).withCause(var6).log("Decryption failed"); + return null; + } + } else { + return null; + } + } + + @Override + public void setTokens(@Nonnull IAuthCredentialStore.OAuthTokens tokens) { + this.tokens = tokens; + this.save(); + } + + @Nonnull + @Override + public IAuthCredentialStore.OAuthTokens getTokens() { + return this.tokens; + } + + @Override + public void setProfile(@Nullable UUID uuid) { + this.profile = uuid; + this.save(); + } + + @Nullable + @Override + public UUID getProfile() { + return this.profile; + } + + @Override + public void clear() { + this.tokens = new IAuthCredentialStore.OAuthTokens(null, null, null); + this.profile = null; + + try { + Files.deleteIfExists(this.path); + } catch (IOException var2) { + LOGGER.at(Level.WARNING).withCause(var2).log("Failed to delete encrypted credentials file %s", this.path); + } + } + + private static class StoredCredentials { + @Nullable + String accessToken; + @Nullable + String refreshToken; + @Nullable + Instant expiresAt; + @Nullable + UUID profileUuid; + + private StoredCredentials() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/auth/EncryptedAuthCredentialStoreProvider.java b/src/com/hypixel/hytale/server/core/auth/EncryptedAuthCredentialStoreProvider.java new file mode 100644 index 0000000..dd78035 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/EncryptedAuthCredentialStoreProvider.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.core.auth; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class EncryptedAuthCredentialStoreProvider implements AuthCredentialStoreProvider { + public static final String ID = "Encrypted"; + public static final String DEFAULT_PATH = "auth.enc"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + EncryptedAuthCredentialStoreProvider.class, EncryptedAuthCredentialStoreProvider::new + ) + .append(new KeyedCodec<>("Path", Codec.STRING), (o, p) -> o.path = p, o -> o.path) + .add() + .build(); + private String path = "auth.enc"; + + public EncryptedAuthCredentialStoreProvider() { + } + + @Nonnull + @Override + public IAuthCredentialStore createStore() { + return new EncryptedAuthCredentialStore(Path.of(this.path)); + } + + @Nonnull + @Override + public String toString() { + return "EncryptedAuthCredentialStoreProvider{path='" + this.path + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/auth/IAuthCredentialStore.java b/src/com/hypixel/hytale/server/core/auth/IAuthCredentialStore.java new file mode 100644 index 0000000..988c43d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/IAuthCredentialStore.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.core.auth; + +import java.time.Instant; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface IAuthCredentialStore { + void setTokens(@Nonnull IAuthCredentialStore.OAuthTokens var1); + + @Nonnull + IAuthCredentialStore.OAuthTokens getTokens(); + + void setProfile(@Nullable UUID var1); + + @Nullable + UUID getProfile(); + + void clear(); + + public record OAuthTokens(@Nullable String accessToken, @Nullable String refreshToken, @Nullable Instant accessTokenExpiresAt) { + public boolean isValid() { + return this.refreshToken != null; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/auth/JWTValidator.java b/src/com/hypixel/hytale/server/core/auth/JWTValidator.java new file mode 100644 index 0000000..5f63504 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/JWTValidator.java @@ -0,0 +1,451 @@ +package com.hypixel.hytale.server.core.auth; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.crypto.Ed25519Verifier; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.OctetKeyPair; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import java.security.cert.X509Certificate; +import java.text.ParseException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class JWTValidator { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final JWSAlgorithm SUPPORTED_ALGORITHM = JWSAlgorithm.EdDSA; + private final SessionServiceClient sessionServiceClient; + private final String expectedIssuer; + private final String expectedAudience; + private volatile JWKSet cachedJwkSet; + private volatile long jwksCacheExpiry; + private final long jwksCacheDurationMs = TimeUnit.HOURS.toMillis(1L); + private final ReentrantLock jwksFetchLock = new ReentrantLock(); + private volatile CompletableFuture pendingFetch = null; + + public JWTValidator(@Nonnull SessionServiceClient sessionServiceClient, @Nonnull String expectedIssuer, @Nonnull String expectedAudience) { + this.sessionServiceClient = sessionServiceClient; + this.expectedIssuer = expectedIssuer; + this.expectedAudience = expectedAudience; + } + + @Nullable + public JWTValidator.JWTClaims validateToken(@Nonnull String accessToken, @Nullable X509Certificate clientCert) { + if (accessToken.isEmpty()) { + LOGGER.at(Level.WARNING).log("Access token is empty"); + return null; + } else { + try { + SignedJWT signedJWT = SignedJWT.parse(accessToken); + JWSAlgorithm algorithm = signedJWT.getHeader().getAlgorithm(); + if (!SUPPORTED_ALGORITHM.equals(algorithm)) { + LOGGER.at(Level.WARNING).log("Unsupported JWT algorithm: %s (expected EdDSA)", algorithm); + return null; + } else if (!this.verifySignatureWithRetry(signedJWT)) { + LOGGER.at(Level.WARNING).log("JWT signature verification failed"); + return null; + } else { + JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet(); + JWTValidator.JWTClaims claims = new JWTValidator.JWTClaims(); + claims.issuer = claimsSet.getIssuer(); + claims.audience = claimsSet.getAudience() != null && !claimsSet.getAudience().isEmpty() ? claimsSet.getAudience().get(0) : null; + claims.subject = claimsSet.getSubject(); + claims.username = claimsSet.getStringClaim("username"); + claims.ipAddress = claimsSet.getStringClaim("ip"); + claims.issuedAt = claimsSet.getIssueTime() != null ? claimsSet.getIssueTime().toInstant().getEpochSecond() : null; + claims.expiresAt = claimsSet.getExpirationTime() != null ? claimsSet.getExpirationTime().toInstant().getEpochSecond() : null; + claims.notBefore = claimsSet.getNotBeforeTime() != null ? claimsSet.getNotBeforeTime().toInstant().getEpochSecond() : null; + Map cnfClaim = claimsSet.getJSONObjectClaim("cnf"); + if (cnfClaim != null) { + claims.certificateFingerprint = (String)cnfClaim.get("x5t#S256"); + } + + if (!this.expectedIssuer.equals(claims.issuer)) { + LOGGER.at(Level.WARNING).log("Invalid issuer: expected %s, got %s", this.expectedIssuer, claims.issuer); + return null; + } else if (!this.expectedAudience.equals(claims.audience)) { + LOGGER.at(Level.WARNING).log("Invalid audience: expected %s, got %s", this.expectedAudience, claims.audience); + return null; + } else { + long nowSeconds = Instant.now().getEpochSecond(); + long clockSkewSeconds = 60L; + if (claims.expiresAt != null && nowSeconds >= claims.expiresAt + clockSkewSeconds) { + LOGGER.at(Level.WARNING).log("Token expired (exp: %d, now: %d)", claims.expiresAt, nowSeconds); + return null; + } else if (claims.notBefore != null && nowSeconds < claims.notBefore - clockSkewSeconds) { + LOGGER.at(Level.WARNING).log("Token not yet valid (nbf: %d, now: %d)", claims.notBefore, nowSeconds); + return null; + } else if (!CertificateUtil.validateCertificateBinding(claims.certificateFingerprint, clientCert)) { + LOGGER.at(Level.WARNING).log("Certificate binding validation failed"); + return null; + } else { + LOGGER.at(Level.INFO).log("JWT validated successfully for user %s (UUID: %s)", claims.username, claims.subject); + return claims; + } + } + } + } catch (ParseException var12) { + LOGGER.at(Level.WARNING).withCause(var12).log("Failed to parse JWT"); + return null; + } catch (Exception var13) { + LOGGER.at(Level.WARNING).withCause(var13).log("JWT validation error"); + return null; + } + } + } + + private boolean verifySignature(SignedJWT signedJWT, JWKSet jwkSet) { + try { + String keyId = signedJWT.getHeader().getKeyID(); + OctetKeyPair ed25519Key = null; + + for (JWK jwk : jwkSet.getKeys()) { + if (jwk instanceof OctetKeyPair okp && (keyId == null || keyId.equals(jwk.getKeyID()))) { + ed25519Key = okp; + break; + } + } + + if (ed25519Key == null) { + LOGGER.at(Level.WARNING).log("No Ed25519 key found for kid=%s", keyId); + return false; + } else { + Ed25519Verifier verifier = new Ed25519Verifier(ed25519Key); + boolean valid = signedJWT.verify(verifier); + if (valid) { + LOGGER.at(Level.FINE).log("JWT signature verified with key kid=%s", keyId); + } + + return valid; + } + } catch (Exception var8) { + LOGGER.at(Level.WARNING).withCause(var8).log("JWT signature verification failed"); + return false; + } + } + + @Nullable + private JWKSet getJwkSet() { + return this.getJwkSet(false); + } + + @Nullable + private JWKSet getJwkSet(boolean forceRefresh) { + long now = System.currentTimeMillis(); + if (!forceRefresh && this.cachedJwkSet != null && now < this.jwksCacheExpiry) { + return this.cachedJwkSet; + } else { + this.jwksFetchLock.lock(); + + JWKSet var5; + try { + if (!forceRefresh && this.cachedJwkSet != null && now < this.jwksCacheExpiry) { + return this.cachedJwkSet; + } + + CompletableFuture existing = this.pendingFetch; + if (existing == null || existing.isDone()) { + if (forceRefresh) { + LOGGER.at(Level.INFO).log("Force refreshing JWKS cache (key rotation or verification failure)"); + } + + this.pendingFetch = CompletableFuture.supplyAsync(this::fetchJwksFromService); + return this.pendingFetch.join(); + } + + this.jwksFetchLock.unlock(); + + try { + var5 = existing.join(); + } finally { + this.jwksFetchLock.lock(); + } + } finally { + this.jwksFetchLock.unlock(); + } + + return var5; + } + } + + @Nullable + private JWKSet fetchJwksFromService() { + SessionServiceClient.JwksResponse jwksResponse = this.sessionServiceClient.getJwks(); + if (jwksResponse != null && jwksResponse.keys != null && jwksResponse.keys.length != 0) { + try { + ArrayList jwkList = new ArrayList<>(); + + for (SessionServiceClient.JwkKey key : jwksResponse.keys) { + JWK jwk = this.convertToJWK(key); + if (jwk != null) { + jwkList.add(jwk); + } + } + + if (jwkList.isEmpty()) { + LOGGER.at(Level.WARNING).log("No valid JWKs found in JWKS response"); + return this.cachedJwkSet; + } else { + JWKSet newSet = new JWKSet(jwkList); + this.cachedJwkSet = newSet; + this.jwksCacheExpiry = System.currentTimeMillis() + this.jwksCacheDurationMs; + LOGGER.at(Level.INFO).log("JWKS loaded with %d keys", jwkList.size()); + return newSet; + } + } catch (Exception var8) { + LOGGER.at(Level.WARNING).withCause(var8).log("Failed to parse JWKS"); + return this.cachedJwkSet; + } + } else { + LOGGER.at(Level.WARNING).log("Failed to fetch JWKS or no keys available"); + return this.cachedJwkSet; + } + } + + private boolean verifySignatureWithRetry(SignedJWT signedJWT) { + JWKSet jwkSet = this.getJwkSet(); + if (jwkSet == null) { + return false; + } else if (this.verifySignature(signedJWT, jwkSet)) { + return true; + } else { + LOGGER.at(Level.INFO).log("Signature verification failed with cached JWKS, retrying with fresh keys"); + JWKSet freshJwkSet = this.getJwkSet(true); + return freshJwkSet != null && freshJwkSet != jwkSet ? this.verifySignature(signedJWT, freshJwkSet) : false; + } + } + + @Nullable + private JWK convertToJWK(SessionServiceClient.JwkKey key) { + if (!"OKP".equals(key.kty)) { + LOGGER.at(Level.WARNING).log("Unsupported key type: %s (expected OKP)", key.kty); + return null; + } else { + try { + String json = String.format("{\"kty\":\"OKP\",\"crv\":\"%s\",\"x\":\"%s\",\"kid\":\"%s\",\"alg\":\"EdDSA\"}", key.crv, key.x, key.kid); + return JWK.parse(json); + } catch (Exception var3) { + LOGGER.at(Level.WARNING).withCause(var3).log("Failed to parse Ed25519 key"); + return null; + } + } + } + + public void invalidateJwksCache() { + this.jwksFetchLock.lock(); + + try { + this.cachedJwkSet = null; + this.jwksCacheExpiry = 0L; + this.pendingFetch = null; + } finally { + this.jwksFetchLock.unlock(); + } + } + + @Nullable + public JWTValidator.IdentityTokenClaims validateIdentityToken(@Nonnull String identityToken) { + if (identityToken.isEmpty()) { + LOGGER.at(Level.WARNING).log("Identity token is empty"); + return null; + } else { + try { + SignedJWT signedJWT = SignedJWT.parse(identityToken); + JWSAlgorithm algorithm = signedJWT.getHeader().getAlgorithm(); + if (!SUPPORTED_ALGORITHM.equals(algorithm)) { + LOGGER.at(Level.WARNING).log("Unsupported identity token algorithm: %s (expected EdDSA)", algorithm); + return null; + } else if (!this.verifySignatureWithRetry(signedJWT)) { + LOGGER.at(Level.WARNING).log("Identity token signature verification failed"); + return null; + } else { + JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet(); + JWTValidator.IdentityTokenClaims claims = new JWTValidator.IdentityTokenClaims(); + claims.issuer = claimsSet.getIssuer(); + claims.subject = claimsSet.getSubject(); + claims.username = claimsSet.getStringClaim("username"); + claims.issuedAt = claimsSet.getIssueTime() != null ? claimsSet.getIssueTime().toInstant().getEpochSecond() : null; + claims.expiresAt = claimsSet.getExpirationTime() != null ? claimsSet.getExpirationTime().toInstant().getEpochSecond() : null; + claims.notBefore = claimsSet.getNotBeforeTime() != null ? claimsSet.getNotBeforeTime().toInstant().getEpochSecond() : null; + claims.scope = claimsSet.getStringClaim("scope"); + if (!this.expectedIssuer.equals(claims.issuer)) { + LOGGER.at(Level.WARNING).log("Invalid identity token issuer: expected %s, got %s", this.expectedIssuer, claims.issuer); + return null; + } else { + long nowSeconds = Instant.now().getEpochSecond(); + long clockSkewSeconds = 60L; + if (claims.expiresAt == null) { + LOGGER.at(Level.WARNING).log("Identity token missing expiration claim"); + return null; + } else if (nowSeconds >= claims.expiresAt + clockSkewSeconds) { + LOGGER.at(Level.WARNING).log("Identity token expired (exp: %d, now: %d)", claims.expiresAt, nowSeconds); + return null; + } else if (claims.notBefore != null && nowSeconds < claims.notBefore - clockSkewSeconds) { + LOGGER.at(Level.WARNING).log("Identity token not yet valid (nbf: %d, now: %d)", claims.notBefore, nowSeconds); + return null; + } else if (claims.issuedAt != null && claims.issuedAt > nowSeconds + clockSkewSeconds) { + LOGGER.at(Level.WARNING).log("Identity token issued in the future (iat: %d, now: %d)", claims.issuedAt, nowSeconds); + return null; + } else if (claims.getSubjectAsUUID() == null) { + LOGGER.at(Level.WARNING).log("Identity token has invalid or missing subject UUID"); + return null; + } else { + LOGGER.at(Level.INFO).log("Identity token validated successfully for user %s (UUID: %s)", claims.username, claims.subject); + return claims; + } + } + } + } catch (ParseException var10) { + LOGGER.at(Level.WARNING).withCause(var10).log("Failed to parse identity token"); + return null; + } catch (Exception var11) { + LOGGER.at(Level.WARNING).withCause(var11).log("Identity token validation error"); + return null; + } + } + } + + @Nullable + public JWTValidator.SessionTokenClaims validateSessionToken(@Nonnull String sessionToken) { + if (sessionToken.isEmpty()) { + LOGGER.at(Level.WARNING).log("Session token is empty"); + return null; + } else { + try { + SignedJWT signedJWT = SignedJWT.parse(sessionToken); + JWSAlgorithm algorithm = signedJWT.getHeader().getAlgorithm(); + if (!SUPPORTED_ALGORITHM.equals(algorithm)) { + LOGGER.at(Level.WARNING).log("Unsupported session token algorithm: %s (expected EdDSA)", algorithm); + return null; + } else if (!this.verifySignatureWithRetry(signedJWT)) { + LOGGER.at(Level.WARNING).log("Session token signature verification failed"); + return null; + } else { + JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet(); + JWTValidator.SessionTokenClaims claims = new JWTValidator.SessionTokenClaims(); + claims.issuer = claimsSet.getIssuer(); + claims.subject = claimsSet.getSubject(); + claims.issuedAt = claimsSet.getIssueTime() != null ? claimsSet.getIssueTime().toInstant().getEpochSecond() : null; + claims.expiresAt = claimsSet.getExpirationTime() != null ? claimsSet.getExpirationTime().toInstant().getEpochSecond() : null; + claims.notBefore = claimsSet.getNotBeforeTime() != null ? claimsSet.getNotBeforeTime().toInstant().getEpochSecond() : null; + if (!this.expectedIssuer.equals(claims.issuer)) { + LOGGER.at(Level.WARNING).log("Invalid session token issuer: expected %s, got %s", this.expectedIssuer, claims.issuer); + return null; + } else { + long nowSeconds = Instant.now().getEpochSecond(); + long clockSkewSeconds = 60L; + if (claims.expiresAt == null) { + LOGGER.at(Level.WARNING).log("Session token missing expiration claim"); + return null; + } else if (nowSeconds >= claims.expiresAt + clockSkewSeconds) { + LOGGER.at(Level.WARNING).log("Session token expired (exp: %d, now: %d)", claims.expiresAt, nowSeconds); + return null; + } else if (claims.notBefore != null && nowSeconds < claims.notBefore - clockSkewSeconds) { + LOGGER.at(Level.WARNING).log("Session token not yet valid (nbf: %d, now: %d)", claims.notBefore, nowSeconds); + return null; + } else { + LOGGER.at(Level.INFO).log("Session token validated successfully"); + return claims; + } + } + } + } catch (ParseException var10) { + LOGGER.at(Level.WARNING).withCause(var10).log("Failed to parse session token"); + return null; + } catch (Exception var11) { + LOGGER.at(Level.WARNING).withCause(var11).log("Session token validation error"); + return null; + } + } + } + + public static class IdentityTokenClaims { + public String issuer; + public String subject; + public String username; + public Long issuedAt; + public Long expiresAt; + public Long notBefore; + public String scope; + + public IdentityTokenClaims() { + } + + @Nullable + public UUID getSubjectAsUUID() { + if (this.subject == null) { + return null; + } else { + try { + return UUID.fromString(this.subject); + } catch (IllegalArgumentException var2) { + return null; + } + } + } + + @Nonnull + public String[] getScopes() { + return this.scope != null && !this.scope.isEmpty() ? this.scope.split(" ") : new String[0]; + } + + public boolean hasScope(@Nonnull String targetScope) { + for (String s : this.getScopes()) { + if (s.equals(targetScope)) { + return true; + } + } + + return false; + } + } + + public static class JWTClaims { + public String issuer; + public String audience; + public String subject; + public String username; + public String ipAddress; + public Long issuedAt; + public Long expiresAt; + public Long notBefore; + public String certificateFingerprint; + + public JWTClaims() { + } + + @Nullable + public UUID getSubjectAsUUID() { + if (this.subject == null) { + return null; + } else { + try { + return UUID.fromString(this.subject); + } catch (IllegalArgumentException var2) { + return null; + } + } + } + } + + public static class SessionTokenClaims { + public String issuer; + public String subject; + public Long issuedAt; + public Long expiresAt; + public Long notBefore; + + public SessionTokenClaims() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/auth/MemoryAuthCredentialStoreProvider.java b/src/com/hypixel/hytale/server/core/auth/MemoryAuthCredentialStoreProvider.java new file mode 100644 index 0000000..ed4d4c3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/MemoryAuthCredentialStoreProvider.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.server.core.auth; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class MemoryAuthCredentialStoreProvider implements AuthCredentialStoreProvider { + public static final String ID = "Memory"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + MemoryAuthCredentialStoreProvider.class, MemoryAuthCredentialStoreProvider::new + ) + .build(); + + public MemoryAuthCredentialStoreProvider() { + } + + @Nonnull + @Override + public IAuthCredentialStore createStore() { + return new DefaultAuthCredentialStore(); + } + + @Nonnull + @Override + public String toString() { + return "MemoryAuthCredentialStoreProvider{}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/auth/PlayerAuthentication.java b/src/com/hypixel/hytale/server/core/auth/PlayerAuthentication.java new file mode 100644 index 0000000..e0cdbc5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/PlayerAuthentication.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.core.auth; + +import com.hypixel.hytale.protocol.HostAddress; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerAuthentication { + public static final int MAX_REFERRAL_DATA_SIZE = 4096; + private UUID uuid; + private String username; + private byte[] referralData; + private HostAddress referralSource; + + public PlayerAuthentication() { + } + + public PlayerAuthentication(@Nonnull UUID uuid, @Nonnull String username) { + this.uuid = uuid; + this.username = username; + } + + @Nonnull + public String getUsername() { + if (this.username == null) { + throw new UnsupportedOperationException("Username not set - incomplete authentication"); + } else { + return this.username; + } + } + + @Nonnull + public UUID getUuid() { + if (this.uuid == null) { + throw new UnsupportedOperationException("UUID not set - incomplete authentication"); + } else { + return this.uuid; + } + } + + public void setUsername(@Nonnull String username) { + this.username = username; + } + + public void setUuid(@Nonnull UUID uuid) { + this.uuid = uuid; + } + + @Nullable + public byte[] getReferralData() { + return this.referralData; + } + + public void setReferralData(@Nullable byte[] referralData) { + if (referralData != null && referralData.length > 4096) { + throw new IllegalArgumentException("Referral data exceeds maximum size of 4096 bytes (got " + referralData.length + ")"); + } else { + this.referralData = referralData; + } + } + + @Nullable + public HostAddress getReferralSource() { + return this.referralSource; + } + + public void setReferralSource(@Nullable HostAddress referralSource) { + this.referralSource = referralSource; + } +} diff --git a/src/com/hypixel/hytale/server/core/auth/ServerAuthManager.java b/src/com/hypixel/hytale/server/core/auth/ServerAuthManager.java new file mode 100644 index 0000000..141be5b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/ServerAuthManager.java @@ -0,0 +1,799 @@ +package com.hypixel.hytale.server.core.auth; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.hypixel.hytale.codec.lookup.Priority; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.auth.oauth.OAuthBrowserFlow; +import com.hypixel.hytale.server.core.auth.oauth.OAuthClient; +import com.hypixel.hytale.server.core.auth.oauth.OAuthDeviceFlow; +import java.nio.charset.StandardCharsets; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.util.Base64; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import joptsimple.OptionSet; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class ServerAuthManager { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final int REFRESH_BUFFER_SECONDS = 300; + private static volatile ServerAuthManager instance; + private volatile ServerAuthManager.AuthMode authMode = ServerAuthManager.AuthMode.NONE; + private volatile Instant tokenExpiry; + private final AtomicReference gameSession = new AtomicReference<>(); + private final AtomicReference credentialStore = new AtomicReference<>(new DefaultAuthCredentialStore()); + private final Map availableProfiles = new ConcurrentHashMap<>(); + private final AtomicReference serverCertificate = new AtomicReference<>(); + private final UUID serverSessionId = UUID.randomUUID(); + private volatile boolean isSingleplayer; + private OAuthClient oauthClient; + private volatile SessionServiceClient sessionServiceClient; + private final ScheduledExecutorService refreshScheduler = Executors.newSingleThreadScheduledExecutor(r -> { + Thread t = new Thread(r, "TokenRefresh"); + t.setDaemon(true); + return t; + }); + private ScheduledFuture refreshTask; + private Runnable cancelActiveFlow; + private volatile SessionServiceClient.GameProfile[] pendingProfiles; + private volatile ServerAuthManager.AuthMode pendingAuthMode; + + private ServerAuthManager() { + } + + public void initialize() { + OptionSet optionSet = Options.getOptionSet(); + if (optionSet == null) { + LOGGER.at(Level.WARNING).log("Options not parsed, cannot initialize ServerAuthManager"); + } else { + this.oauthClient = new OAuthClient(); + this.isSingleplayer = optionSet.has(Options.SINGLEPLAYER); + if (this.isSingleplayer && optionSet.has(Options.OWNER_UUID)) { + SessionServiceClient.GameProfile ownerProfile = new SessionServiceClient.GameProfile(); + ownerProfile.uuid = optionSet.valueOf(Options.OWNER_UUID); + ownerProfile.username = optionSet.has(Options.OWNER_NAME) ? optionSet.valueOf(Options.OWNER_NAME) : null; + this.credentialStore.get().setProfile(ownerProfile.uuid); + LOGGER.at(Level.INFO).log("Singleplayer mode, owner: %s (%s)", ownerProfile.username, ownerProfile.uuid); + } + + boolean hasCliTokens = false; + String sessionTokenValue = null; + String identityTokenValue = null; + if (optionSet.has(Options.SESSION_TOKEN)) { + sessionTokenValue = optionSet.valueOf(Options.SESSION_TOKEN); + LOGGER.at(Level.INFO).log("Session token loaded from CLI"); + } else { + String envToken = System.getenv("HYTALE_SERVER_SESSION_TOKEN"); + if (envToken != null && !envToken.isEmpty()) { + sessionTokenValue = envToken; + LOGGER.at(Level.INFO).log("Session token loaded from environment"); + } + } + + if (optionSet.has(Options.IDENTITY_TOKEN)) { + identityTokenValue = optionSet.valueOf(Options.IDENTITY_TOKEN); + LOGGER.at(Level.INFO).log("Identity token loaded from CLI"); + } else { + String envTokenx = System.getenv("HYTALE_SERVER_IDENTITY_TOKEN"); + if (envTokenx != null && !envTokenx.isEmpty()) { + identityTokenValue = envTokenx; + LOGGER.at(Level.INFO).log("Identity token loaded from environment"); + } + } + + if (sessionTokenValue != null || identityTokenValue != null) { + if (this.validateInitialTokens(sessionTokenValue, identityTokenValue)) { + SessionServiceClient.GameSessionResponse session = new SessionServiceClient.GameSessionResponse(); + session.sessionToken = sessionTokenValue; + session.identityToken = identityTokenValue; + this.gameSession.set(session); + hasCliTokens = true; + } else { + LOGGER.at(Level.WARNING).log("Token validation failed. Server starting unauthenticated. Use /auth login to authenticate."); + } + } + + if (hasCliTokens) { + if (this.isSingleplayer) { + this.authMode = ServerAuthManager.AuthMode.SINGLEPLAYER; + LOGGER.at(Level.INFO).log("Auth mode: SINGLEPLAYER"); + } else { + this.authMode = ServerAuthManager.AuthMode.EXTERNAL_SESSION; + LOGGER.at(Level.INFO).log("Auth mode: EXTERNAL_SESSION"); + } + + this.parseAndScheduleRefresh(); + } else { + LOGGER.at(Level.INFO).log("No server tokens configured. Use /auth login to authenticate, or provide tokens via CLI/environment."); + } + + LOGGER.at(Level.INFO).log("Server session ID: %s", this.serverSessionId); + LOGGER.at(Level.FINE) + .log( + "ServerAuthManager initialized - session token: %s, identity token: %s, auth mode: %s", + this.hasSessionToken() ? "present" : "missing", + this.hasIdentityToken() ? "present" : "missing", + this.authMode + ); + } + } + + public void initializeCredentialStore() { + AuthCredentialStoreProvider provider = HytaleServer.get().getConfig().getAuthCredentialStoreProvider(); + this.credentialStore.set(provider.createStore()); + LOGGER.at(Level.INFO) + .log("Auth credential store: %s", AuthCredentialStoreProvider.CODEC.getIdFor((Class)provider.getClass())); + IAuthCredentialStore store = this.credentialStore.get(); + IAuthCredentialStore.OAuthTokens tokens = store.getTokens(); + if (tokens.isValid()) { + LOGGER.at(Level.INFO).log("Found stored credentials, attempting to restore session..."); + ServerAuthManager.AuthResult result = this.createGameSessionFromOAuth(ServerAuthManager.AuthMode.OAUTH_STORE); + if (result == ServerAuthManager.AuthResult.SUCCESS) { + LOGGER.at(Level.INFO).log("Session restored from stored credentials"); + } else if (result == ServerAuthManager.AuthResult.PENDING_PROFILE_SELECTION) { + LOGGER.at(Level.INFO).log("Session restored but profile selection required - use /auth select"); + } else { + LOGGER.at(Level.WARNING).log("Failed to restore session from stored credentials"); + } + } + } + + public static ServerAuthManager getInstance() { + if (instance == null) { + synchronized (ServerAuthManager.class) { + if (instance == null) { + instance = new ServerAuthManager(); + } + } + } + + return instance; + } + + @Nullable + public SessionServiceClient.GameSessionResponse getGameSession() { + return this.gameSession.get(); + } + + @Nullable + public String getIdentityToken() { + SessionServiceClient.GameSessionResponse session = this.gameSession.get(); + return session != null ? session.identityToken : null; + } + + @Nullable + public String getSessionToken() { + SessionServiceClient.GameSessionResponse session = this.gameSession.get(); + return session != null ? session.sessionToken : null; + } + + public void setGameSession(@Nonnull SessionServiceClient.GameSessionResponse session) { + this.gameSession.set(session); + LOGGER.at(Level.FINE).log("Game session updated"); + } + + public boolean hasIdentityToken() { + SessionServiceClient.GameSessionResponse session = this.gameSession.get(); + return session != null && session.identityToken != null; + } + + public boolean hasSessionToken() { + SessionServiceClient.GameSessionResponse session = this.gameSession.get(); + return session != null && session.sessionToken != null; + } + + public void setServerCertificate(@Nonnull X509Certificate certificate) { + this.serverCertificate.set(certificate); + LOGGER.at(Level.INFO).log("Server certificate set: %s", certificate.getSubjectX500Principal()); + } + + @Nullable + public X509Certificate getServerCertificate() { + return this.serverCertificate.get(); + } + + @Nullable + public String getServerCertificateFingerprint() { + X509Certificate cert = this.serverCertificate.get(); + return cert == null ? null : CertificateUtil.computeCertificateFingerprint(cert); + } + + @Nonnull + public UUID getServerSessionId() { + return this.serverSessionId; + } + + public CompletableFuture startFlowAsync(@Nonnull OAuthBrowserFlow flow) { + if (this.isSingleplayer) { + return CompletableFuture.completedFuture(ServerAuthManager.AuthResult.FAILED); + } else { + this.cancelActiveFlow(); + this.cancelActiveFlow = this.oauthClient.startFlow(flow); + return flow.getFuture() + .thenApply( + res -> { + switch (res) { + case SUCCESS: + IAuthCredentialStore store = this.credentialStore.get(); + OAuthClient.TokenResponse tokens = flow.getTokenResponse(); + store.setTokens( + new IAuthCredentialStore.OAuthTokens(tokens.accessToken(), tokens.refreshToken(), Instant.now().plusSeconds(tokens.expiresIn())) + ); + return this.createGameSessionFromOAuth(ServerAuthManager.AuthMode.OAUTH_BROWSER); + case FAILED: + LOGGER.at(Level.WARNING).log("OAuth browser flow failed: %s", flow.getErrorMessage()); + return ServerAuthManager.AuthResult.FAILED; + default: + LOGGER.at(Level.WARNING).log("OAuth browser flow completed with unexpected result: %v", res); + return ServerAuthManager.AuthResult.FAILED; + } + } + ); + } + } + + public CompletableFuture startFlowAsync(OAuthDeviceFlow flow) { + if (this.isSingleplayer) { + return CompletableFuture.completedFuture(ServerAuthManager.AuthResult.FAILED); + } else { + this.cancelActiveFlow(); + this.cancelActiveFlow = this.oauthClient.startFlow(flow); + return flow.getFuture() + .thenApply( + res -> { + switch (res) { + case SUCCESS: + IAuthCredentialStore store = this.credentialStore.get(); + OAuthClient.TokenResponse tokens = flow.getTokenResponse(); + store.setTokens( + new IAuthCredentialStore.OAuthTokens(tokens.accessToken(), tokens.refreshToken(), Instant.now().plusSeconds(tokens.expiresIn())) + ); + return this.createGameSessionFromOAuth(ServerAuthManager.AuthMode.OAUTH_DEVICE); + case FAILED: + LOGGER.at(Level.WARNING).log("OAuth device flow failed: %s", flow.getErrorMessage()); + return ServerAuthManager.AuthResult.FAILED; + default: + LOGGER.at(Level.WARNING).log("OAuth device flow completed with unexpected result: %v", res); + return ServerAuthManager.AuthResult.FAILED; + } + } + ); + } + } + + public CompletableFuture registerCredentialStore(IAuthCredentialStore store) { + if (this.isSingleplayer) { + return CompletableFuture.completedFuture(ServerAuthManager.AuthResult.FAILED); + } else if (this.hasSessionToken() && this.hasIdentityToken()) { + return CompletableFuture.completedFuture(ServerAuthManager.AuthResult.FAILED); + } else { + this.credentialStore.set(store); + return CompletableFuture.completedFuture(this.createGameSessionFromOAuth(ServerAuthManager.AuthMode.OAUTH_STORE)); + } + } + + public void swapCredentialStoreProvider(@Nonnull AuthCredentialStoreProvider provider) { + IAuthCredentialStore oldStore = this.credentialStore.get(); + IAuthCredentialStore newStore = provider.createStore(); + IAuthCredentialStore.OAuthTokens tokens = oldStore.getTokens(); + if (tokens.isValid()) { + newStore.setTokens(tokens); + } + + UUID profile = oldStore.getProfile(); + if (profile != null) { + newStore.setProfile(profile); + } + + this.credentialStore.set(newStore); + LOGGER.at(Level.INFO).log("Swapped credential store to: %s", provider.getClass().getSimpleName()); + } + + private ServerAuthManager.AuthResult createGameSessionFromOAuth(ServerAuthManager.AuthMode mode) { + if (!this.refreshOAuthTokens()) { + LOGGER.at(Level.WARNING).log("No valid OAuth tokens to create game session"); + return ServerAuthManager.AuthResult.FAILED; + } else { + IAuthCredentialStore store = this.credentialStore.get(); + String accessToken = store.getTokens().accessToken(); + if (accessToken == null) { + LOGGER.at(Level.WARNING).log("No access token in credential store"); + return ServerAuthManager.AuthResult.FAILED; + } else { + if (this.sessionServiceClient == null) { + this.sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com"); + } + + SessionServiceClient.GameProfile[] profiles = this.sessionServiceClient.getGameProfiles(accessToken); + if (profiles != null && profiles.length != 0) { + this.availableProfiles.clear(); + + for (SessionServiceClient.GameProfile profile : profiles) { + this.availableProfiles.put(profile.uuid, profile); + } + + SessionServiceClient.GameProfile profile = this.tryAutoSelectProfile(profiles); + if (profile != null) { + return this.completeAuthWithProfile(profile, mode) ? ServerAuthManager.AuthResult.SUCCESS : ServerAuthManager.AuthResult.FAILED; + } else { + this.pendingProfiles = profiles; + this.pendingAuthMode = mode; + this.cancelActiveFlow = null; + LOGGER.at(Level.INFO).log("Multiple profiles available. Use '/auth select ' to choose:"); + + for (int i = 0; i < profiles.length; i++) { + LOGGER.at(Level.INFO).log(" [%d] %s (%s)", i + 1, profiles[i].username, profiles[i].uuid); + } + + return ServerAuthManager.AuthResult.PENDING_PROFILE_SELECTION; + } + } else { + LOGGER.at(Level.WARNING).log("No game profiles found for this account"); + return ServerAuthManager.AuthResult.FAILED; + } + } + } + } + + private boolean refreshOAuthTokens() { + return this.refreshOAuthTokens(false); + } + + private boolean refreshOAuthTokens(boolean force) { + IAuthCredentialStore store = this.credentialStore.get(); + IAuthCredentialStore.OAuthTokens tokens = store.getTokens(); + Instant expiresAt = tokens.accessTokenExpiresAt(); + if (!force && expiresAt != null && !expiresAt.isBefore(Instant.now().plusSeconds(300L))) { + return true; + } else { + String refreshToken = tokens.refreshToken(); + if (refreshToken == null) { + LOGGER.at(Level.WARNING).log("No refresh token present to refresh OAuth tokens"); + return false; + } else { + LOGGER.at(Level.INFO).log("Refreshing OAuth tokens..."); + OAuthClient.TokenResponse newTokens = this.oauthClient.refreshTokens(refreshToken); + if (newTokens != null && newTokens.isSuccess()) { + store.setTokens( + new IAuthCredentialStore.OAuthTokens(newTokens.accessToken(), newTokens.refreshToken(), Instant.now().plusSeconds(newTokens.expiresIn())) + ); + return true; + } else { + LOGGER.at(Level.WARNING).log("OAuth token refresh failed"); + return false; + } + } + } + } + + @Nullable + private SessionServiceClient.GameProfile tryAutoSelectProfile(SessionServiceClient.GameProfile[] profiles) { + OptionSet optionSet = Options.getOptionSet(); + if (optionSet != null && optionSet.has(Options.OWNER_UUID)) { + UUID requestedUuid = optionSet.valueOf(Options.OWNER_UUID); + + for (SessionServiceClient.GameProfile profile : profiles) { + if (profile.uuid.equals(requestedUuid)) { + LOGGER.at(Level.INFO).log("Selected profile from --owner-uuid: %s (%s)", profile.username, profile.uuid); + return profile; + } + } + + LOGGER.at(Level.WARNING).log("Specified --owner-uuid %s not found in available profiles", requestedUuid); + return null; + } else if (profiles.length == 1) { + LOGGER.at(Level.INFO).log("Auto-selected profile: %s (%s)", profiles[0].username, profiles[0].uuid); + return profiles[0]; + } else { + UUID profileUuid = this.credentialStore.get().getProfile(); + if (profileUuid != null) { + for (SessionServiceClient.GameProfile profilex : profiles) { + if (profilex.uuid.equals(profileUuid)) { + LOGGER.at(Level.INFO).log("Auto-selected profile from stroage: %s (%s)", profilex.username, profilex.uuid); + return profilex; + } + } + } + + return null; + } + } + + private boolean completeAuthWithProfile(SessionServiceClient.GameProfile profile, ServerAuthManager.AuthMode mode) { + SessionServiceClient.GameSessionResponse newSession = this.createGameSession(profile.uuid); + if (newSession == null) { + LOGGER.at(Level.WARNING).log("Failed to create game session"); + return false; + } else { + this.gameSession.set(newSession); + this.authMode = mode; + this.cancelActiveFlow = null; + this.pendingProfiles = null; + this.pendingAuthMode = null; + Instant expiresAtInstant = newSession.getExpiresAtInstant(); + if (expiresAtInstant != null) { + this.tokenExpiry = expiresAtInstant; + long secondsUntilExpiry = expiresAtInstant.getEpochSecond() - Instant.now().getEpochSecond(); + if (secondsUntilExpiry > 300L) { + this.scheduleRefresh((int)secondsUntilExpiry); + } + } + + LOGGER.at(Level.INFO).log("Authentication successful! Mode: %s", mode); + return true; + } + } + + @Nullable + public SessionServiceClient.GameProfile[] getPendingProfiles() { + return this.pendingProfiles; + } + + public boolean hasPendingProfiles() { + return this.pendingProfiles != null && this.pendingProfiles.length > 0; + } + + public boolean selectPendingProfile(int index) { + SessionServiceClient.GameProfile[] profiles = this.pendingProfiles; + ServerAuthManager.AuthMode mode = this.pendingAuthMode; + if (profiles == null || profiles.length == 0) { + LOGGER.at(Level.WARNING).log("No pending profiles to select"); + return false; + } else if (index >= 1 && index <= profiles.length) { + SessionServiceClient.GameProfile selected = profiles[index - 1]; + LOGGER.at(Level.INFO).log("Selected profile: %s (%s)", selected.username, selected.uuid); + return this.completeAuthWithProfile(selected, mode != null ? mode : ServerAuthManager.AuthMode.OAUTH_BROWSER); + } else { + LOGGER.at(Level.WARNING).log("Invalid profile index: %d (valid range: 1-%d)", index, profiles.length); + return false; + } + } + + public boolean selectPendingProfileByUsername(String username) { + SessionServiceClient.GameProfile[] profiles = this.pendingProfiles; + ServerAuthManager.AuthMode mode = this.pendingAuthMode; + if (profiles != null && profiles.length != 0) { + for (SessionServiceClient.GameProfile profile : profiles) { + if (profile.username != null && profile.username.equalsIgnoreCase(username)) { + LOGGER.at(Level.INFO).log("Selected profile: %s (%s)", profile.username, profile.uuid); + return this.completeAuthWithProfile(profile, mode != null ? mode : ServerAuthManager.AuthMode.OAUTH_BROWSER); + } + } + + LOGGER.at(Level.WARNING).log("No profile found with username: %s", username); + return false; + } else { + LOGGER.at(Level.WARNING).log("No pending profiles to select"); + return false; + } + } + + public void clearPendingProfiles() { + this.pendingProfiles = null; + this.pendingAuthMode = null; + } + + public boolean cancelActiveFlow() { + if (this.cancelActiveFlow != null) { + this.cancelActiveFlow.run(); + this.cancelActiveFlow = null; + return true; + } else { + return false; + } + } + + private boolean validateInitialTokens(@Nullable String sessionToken, @Nullable String identityToken) { + if (sessionToken == null && identityToken == null) { + return false; + } else { + if (this.sessionServiceClient == null) { + this.sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com"); + } + + JWTValidator validator = new JWTValidator(this.sessionServiceClient, "https://sessions.hytale.com", ""); + boolean valid = true; + if (identityToken != null) { + JWTValidator.IdentityTokenClaims claims = validator.validateIdentityToken(identityToken); + if (claims == null) { + LOGGER.at(Level.WARNING).log("Identity token validation failed"); + valid = false; + } else if (!claims.hasScope("hytale:server")) { + LOGGER.at(Level.WARNING).log("Identity token missing required scope: expected %s, got %s", "hytale:server", claims.scope); + valid = false; + } else { + LOGGER.at(Level.INFO).log("Identity token validated for %s (%s)", claims.username, claims.subject); + } + } + + if (sessionToken != null) { + JWTValidator.SessionTokenClaims claims = validator.validateSessionToken(sessionToken); + if (claims == null) { + LOGGER.at(Level.WARNING).log("Session token validation failed"); + valid = false; + } else { + LOGGER.at(Level.INFO).log("Session token validated"); + } + } + + return valid; + } + } + + private void parseAndScheduleRefresh() { + SessionServiceClient.GameSessionResponse session = this.gameSession.get(); + if (session != null) { + Instant expiry = session.getExpiresAtInstant(); + if (expiry != null) { + this.tokenExpiry = expiry; + long secondsUntilExpiry = expiry.getEpochSecond() - Instant.now().getEpochSecond(); + if (secondsUntilExpiry > 300L) { + this.scheduleRefresh((int)secondsUntilExpiry); + } + + return; + } + } + + String idToken = this.getIdentityToken(); + if (idToken != null) { + try { + String[] parts = idToken.split("\\."); + if (parts.length != 3) { + return; + } + + String payload = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); + JsonObject json = JsonParser.parseString(payload).getAsJsonObject(); + if (json.has("exp")) { + long exp = json.get("exp").getAsLong(); + this.tokenExpiry = Instant.ofEpochSecond(exp); + long secondsUntilExpiry = exp - Instant.now().getEpochSecond(); + if (secondsUntilExpiry > 300L) { + this.scheduleRefresh((int)secondsUntilExpiry); + } + } + } catch (Exception var10) { + LOGGER.at(Level.WARNING).withCause(var10).log("Failed to parse token expiry"); + } + } + } + + private void scheduleRefresh(int expiresInSeconds) { + if (this.refreshTask != null) { + this.refreshTask.cancel(false); + } + + long refreshDelay = Math.max(expiresInSeconds - 300, 60); + LOGGER.at(Level.INFO).log("Token refresh scheduled in %d seconds", refreshDelay); + this.refreshTask = this.refreshScheduler.schedule(this::doRefresh, refreshDelay, TimeUnit.SECONDS); + } + + private void doRefresh() { + String currentSessionToken = this.getSessionToken(); + if (currentSessionToken == null || !this.refreshGameSession(currentSessionToken)) { + LOGGER.at(Level.INFO).log("Game session refresh failed, attempting OAuth refresh..."); + if (!this.refreshGameSessionViaOAuth()) { + LOGGER.at(Level.WARNING).log("All refresh attempts failed. Server may lose authentication."); + } + } + } + + private boolean refreshGameSession(String currentSessionToken) { + LOGGER.at(Level.INFO).log("Refreshing game session with Session Service..."); + if (this.sessionServiceClient == null) { + this.sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com"); + } + + try { + SessionServiceClient.GameSessionResponse response = this.sessionServiceClient.refreshSessionAsync(currentSessionToken).join(); + if (response != null) { + this.gameSession.set(response); + Instant expiresAtInstant = response.getExpiresAtInstant(); + if (expiresAtInstant != null) { + this.tokenExpiry = expiresAtInstant; + long secondsUntilExpiry = expiresAtInstant.getEpochSecond() - Instant.now().getEpochSecond(); + if (secondsUntilExpiry > 300L) { + this.scheduleRefresh((int)secondsUntilExpiry); + } + } + + LOGGER.at(Level.INFO).log("Game session refresh successful"); + return true; + } + } catch (Exception var6) { + LOGGER.at(Level.WARNING).log("Session Service refresh failed: %s", var6.getMessage()); + } + + return false; + } + + private boolean refreshGameSessionViaOAuth() { + boolean supported = switch (this.authMode) { + case OAUTH_BROWSER, OAUTH_DEVICE, OAUTH_STORE -> true; + default -> false; + }; + if (!supported) { + LOGGER.at(Level.WARNING).log("Refresh via OAuth not supported for current Auth Mode"); + return false; + } else { + UUID currentProfile = this.credentialStore.get().getProfile(); + if (currentProfile == null) { + LOGGER.at(Level.WARNING).log("No current profile, cannot refresh game session"); + return false; + } else { + SessionServiceClient.GameSessionResponse newSession = this.createGameSession(currentProfile); + if (newSession == null) { + LOGGER.at(Level.WARNING).log("Failed to create new game session"); + return false; + } else { + this.gameSession.set(newSession); + Instant expiresAtInstant = newSession.getExpiresAtInstant(); + if (expiresAtInstant != null) { + this.tokenExpiry = expiresAtInstant; + long secondsUntilExpiry = expiresAtInstant.getEpochSecond() - Instant.now().getEpochSecond(); + if (secondsUntilExpiry > 300L) { + this.scheduleRefresh((int)secondsUntilExpiry); + } + } + + LOGGER.at(Level.INFO).log("New game session created via OAuth refresh"); + return true; + } + } + } + } + + @NullableDecl + private SessionServiceClient.GameSessionResponse createGameSession(UUID profileUuid) { + if (this.sessionServiceClient == null) { + this.sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com"); + } + + if (!this.refreshOAuthTokens()) { + LOGGER.at(Level.WARNING).log("OAuth token refresh for game session creation failed"); + return null; + } else { + IAuthCredentialStore store = this.credentialStore.get(); + String accessToken = store.getTokens().accessToken(); + SessionServiceClient.GameSessionResponse result = this.sessionServiceClient.createGameSession(accessToken, profileUuid); + if (result == null) { + LOGGER.at(Level.WARNING).log("Trying force refresh of OAuth tokens because game session creation failed"); + if (!this.refreshOAuthTokens(true)) { + LOGGER.at(Level.WARNING).log("Force refresh failed"); + return null; + } + + result = this.sessionServiceClient.createGameSession(accessToken, profileUuid); + if (result == null) { + LOGGER.at(Level.WARNING).log("Game session creation with force refreshed tokens failed"); + return null; + } + } + + store.setProfile(profileUuid); + return result; + } + } + + public void logout() { + this.cancelActiveFlow(); + if (this.refreshTask != null) { + this.refreshTask.cancel(false); + this.refreshTask = null; + } + + this.gameSession.set(null); + this.credentialStore.get().clear(); + this.availableProfiles.clear(); + this.pendingProfiles = null; + this.pendingAuthMode = null; + this.tokenExpiry = null; + this.authMode = ServerAuthManager.AuthMode.NONE; + LOGGER.at(Level.INFO).log("Server logged out"); + } + + public ServerAuthManager.AuthMode getAuthMode() { + return this.authMode; + } + + public boolean isSingleplayer() { + return this.isSingleplayer; + } + + public boolean isOwner(@Nullable UUID playerUuid) { + UUID profileUuid = this.credentialStore.get().getProfile(); + return profileUuid != null && profileUuid.equals(playerUuid); + } + + @Nullable + public SessionServiceClient.GameProfile getSelectedProfile() { + UUID profileUuid = this.credentialStore.get().getProfile(); + return profileUuid == null ? null : this.availableProfiles.get(profileUuid); + } + + @Nullable + public Instant getTokenExpiry() { + return this.tokenExpiry; + } + + public String getAuthStatus() { + StringBuilder sb = new StringBuilder(); + sb.append(this.authMode.name()); + if (this.hasSessionToken() && this.hasIdentityToken()) { + sb.append(" (authenticated)"); + } else if (!this.hasSessionToken() && !this.hasIdentityToken()) { + sb.append(" (no tokens)"); + } else { + sb.append(" (partial)"); + } + + if (this.tokenExpiry != null) { + long secondsRemaining = this.tokenExpiry.getEpochSecond() - Instant.now().getEpochSecond(); + if (secondsRemaining > 0L) { + sb.append(String.format(" [expires in %dm %ds]", secondsRemaining / 60L, secondsRemaining % 60L)); + } else { + sb.append(" [EXPIRED]"); + } + } + + return sb.toString(); + } + + public void shutdown() { + this.cancelActiveFlow(); + if (this.refreshTask != null) { + this.refreshTask.cancel(false); + } + + this.refreshScheduler.shutdown(); + String currentSessionToken = this.getSessionToken(); + if (currentSessionToken != null && !currentSessionToken.isEmpty()) { + if (this.sessionServiceClient == null) { + this.sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com"); + } + + this.sessionServiceClient.terminateSession(currentSessionToken); + } + } + + static { + AuthCredentialStoreProvider.CODEC.register(Priority.DEFAULT, "Memory", MemoryAuthCredentialStoreProvider.class, MemoryAuthCredentialStoreProvider.CODEC); + AuthCredentialStoreProvider.CODEC.register("Encrypted", EncryptedAuthCredentialStoreProvider.class, EncryptedAuthCredentialStoreProvider.CODEC); + } + + public static enum AuthMode { + NONE, + SINGLEPLAYER, + EXTERNAL_SESSION, + OAUTH_BROWSER, + OAUTH_DEVICE, + OAUTH_STORE; + + private AuthMode() { + } + } + + public static enum AuthResult { + SUCCESS, + PENDING_PROFILE_SELECTION, + FAILED; + + private AuthResult() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/auth/SessionServiceClient.java b/src/com/hypixel/hytale/server/core/auth/SessionServiceClient.java new file mode 100644 index 0000000..e6d244d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/SessionServiceClient.java @@ -0,0 +1,481 @@ +package com.hypixel.hytale.server.core.auth; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.EmptyExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.logger.HytaleLogger; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse.BodyHandlers; +import java.time.Duration; +import java.time.Instant; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SessionServiceClient { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(5L); + private final HttpClient httpClient; + private final String sessionServiceUrl; + + public SessionServiceClient(@Nonnull String sessionServiceUrl) { + if (sessionServiceUrl != null && !sessionServiceUrl.isEmpty()) { + this.sessionServiceUrl = sessionServiceUrl.endsWith("/") ? sessionServiceUrl.substring(0, sessionServiceUrl.length() - 1) : sessionServiceUrl; + this.httpClient = HttpClient.newBuilder().connectTimeout(REQUEST_TIMEOUT).build(); + LOGGER.at(Level.INFO).log("Session Service client initialized for: %s", this.sessionServiceUrl); + } else { + throw new IllegalArgumentException("Session Service URL cannot be null or empty"); + } + } + + public CompletableFuture requestAuthorizationGrantAsync(@Nonnull String identityToken, @Nonnull String serverAudience, @Nonnull String bearerToken) { + return CompletableFuture.supplyAsync( + () -> { + try { + String jsonBody = String.format("{\"identityToken\":\"%s\",\"aud\":\"%s\"}", escapeJsonString(identityToken), escapeJsonString(serverAudience)); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(this.sessionServiceUrl + "/server-join/auth-grant")) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .header("Authorization", "Bearer " + bearerToken) + .header("User-Agent", AuthConfig.USER_AGENT) + .timeout(REQUEST_TIMEOUT) + .POST(BodyPublishers.ofString(jsonBody)) + .build(); + LOGGER.at(Level.INFO).log("Requesting authorization grant with identity token, aud='%s'", serverAudience); + HttpResponse response = this.httpClient.send(request, BodyHandlers.ofString()); + if (response.statusCode() != 200) { + LOGGER.at(Level.WARNING).log("Failed to request authorization grant: HTTP %d - %s", response.statusCode(), response.body()); + return null; + } else { + SessionServiceClient.AuthGrantResponse authGrantResponse = SessionServiceClient.AuthGrantResponse.CODEC + .decodeJson(new RawJsonReader(response.body().toCharArray()), EmptyExtraInfo.EMPTY); + if (authGrantResponse != null && authGrantResponse.authorizationGrant != null) { + LOGGER.at(Level.INFO).log("Successfully obtained authorization grant"); + return authGrantResponse.authorizationGrant; + } else { + LOGGER.at(Level.WARNING).log("Session Service response missing authorizationGrant field"); + return null; + } + } + } catch (IOException var8) { + LOGGER.at(Level.WARNING).log("IO error while requesting authorization grant: %s", var8.getMessage()); + return null; + } catch (InterruptedException var9) { + LOGGER.at(Level.WARNING).log("Request interrupted while obtaining authorization grant"); + Thread.currentThread().interrupt(); + return null; + } catch (Exception var10) { + LOGGER.at(Level.WARNING).log("Unexpected error requesting authorization grant: %s", var10.getMessage()); + return null; + } + } + ); + } + + public CompletableFuture exchangeAuthGrantForTokenAsync( + @Nonnull String authorizationGrant, @Nonnull String x509Fingerprint, @Nonnull String bearerToken + ) { + return CompletableFuture.supplyAsync( + () -> { + try { + String jsonBody = String.format( + "{\"authorizationGrant\":\"%s\",\"x509Fingerprint\":\"%s\"}", escapeJsonString(authorizationGrant), escapeJsonString(x509Fingerprint) + ); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(this.sessionServiceUrl + "/server-join/auth-token")) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .header("Authorization", "Bearer " + bearerToken) + .header("User-Agent", AuthConfig.USER_AGENT) + .timeout(REQUEST_TIMEOUT) + .POST(BodyPublishers.ofString(jsonBody)) + .build(); + LOGGER.at(Level.INFO).log("Exchanging authorization grant for access token"); + LOGGER.at(Level.FINE).log("Using bearer token (first 20 chars): %s...", bearerToken.length() > 20 ? bearerToken.substring(0, 20) : bearerToken); + LOGGER.at(Level.FINE).log("Request body: %s", jsonBody); + HttpResponse response = this.httpClient.send(request, BodyHandlers.ofString()); + if (response.statusCode() != 200) { + LOGGER.at(Level.WARNING).log("Failed to exchange auth grant: HTTP %d - %s", response.statusCode(), response.body()); + return null; + } else { + SessionServiceClient.AccessTokenResponse tokenResponse = SessionServiceClient.AccessTokenResponse.CODEC + .decodeJson(new RawJsonReader(response.body().toCharArray()), EmptyExtraInfo.EMPTY); + if (tokenResponse != null && tokenResponse.accessToken != null) { + LOGGER.at(Level.INFO).log("Successfully obtained access token"); + return tokenResponse.accessToken; + } else { + LOGGER.at(Level.WARNING).log("Session Service response missing accessToken field"); + return null; + } + } + } catch (IOException var8) { + LOGGER.at(Level.WARNING).log("IO error while exchanging auth grant: %s", var8.getMessage()); + return null; + } catch (InterruptedException var9) { + LOGGER.at(Level.WARNING).log("Request interrupted while exchanging auth grant"); + Thread.currentThread().interrupt(); + return null; + } catch (Exception var10) { + LOGGER.at(Level.WARNING).log("Unexpected error exchanging auth grant: %s", var10.getMessage()); + return null; + } + } + ); + } + + @Nullable + public SessionServiceClient.JwksResponse getJwks() { + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(this.sessionServiceUrl + "/.well-known/jwks.json")) + .header("Accept", "application/json") + .header("User-Agent", AuthConfig.USER_AGENT) + .timeout(REQUEST_TIMEOUT) + .GET() + .build(); + LOGGER.at(Level.FINE).log("Fetching JWKS from Session Service"); + HttpResponse response = this.httpClient.send(request, BodyHandlers.ofString()); + if (response.statusCode() != 200) { + LOGGER.at(Level.WARNING).log("Failed to fetch JWKS: HTTP %d - %s", response.statusCode(), response.body()); + return null; + } else { + SessionServiceClient.JwksResponse jwks = SessionServiceClient.JwksResponse.CODEC + .decodeJson(new RawJsonReader(response.body().toCharArray()), EmptyExtraInfo.EMPTY); + if (jwks != null && jwks.keys != null && jwks.keys.length != 0) { + LOGGER.at(Level.INFO).log("Successfully fetched JWKS with %d keys", jwks.keys.length); + return jwks; + } else { + LOGGER.at(Level.WARNING).log("Session Service returned invalid JWKS (no keys)"); + return null; + } + } + } catch (IOException var4) { + LOGGER.at(Level.WARNING).log("IO error while fetching JWKS: %s", var4.getMessage()); + return null; + } catch (InterruptedException var5) { + LOGGER.at(Level.WARNING).log("Request interrupted while fetching JWKS"); + Thread.currentThread().interrupt(); + return null; + } catch (Exception var6) { + LOGGER.at(Level.WARNING).log("Unexpected error fetching JWKS: %s", var6.getMessage()); + return null; + } + } + + @Nullable + public SessionServiceClient.GameProfile[] getGameProfiles(@Nonnull String oauthAccessToken) { + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://account-data.hytale.com/my-account/get-profiles")) + .header("Accept", "application/json") + .header("Authorization", "Bearer " + oauthAccessToken) + .header("User-Agent", AuthConfig.USER_AGENT) + .timeout(REQUEST_TIMEOUT) + .GET() + .build(); + LOGGER.at(Level.INFO).log("Fetching game profiles..."); + HttpResponse response = this.httpClient.send(request, BodyHandlers.ofString()); + if (response.statusCode() != 200) { + LOGGER.at(Level.WARNING).log("Failed to fetch profiles: HTTP %d - %s", response.statusCode(), response.body()); + return null; + } else { + SessionServiceClient.LauncherDataResponse data = SessionServiceClient.LauncherDataResponse.CODEC + .decodeJson(new RawJsonReader(response.body().toCharArray()), EmptyExtraInfo.EMPTY); + if (data != null && data.profiles != null) { + LOGGER.at(Level.INFO).log("Found %d game profile(s)", data.profiles.length); + return data.profiles; + } else { + LOGGER.at(Level.WARNING).log("Account Data returned invalid response"); + return null; + } + } + } catch (IOException var5) { + LOGGER.at(Level.WARNING).log("IO error while fetching profiles: %s", var5.getMessage()); + return null; + } catch (InterruptedException var6) { + LOGGER.at(Level.WARNING).log("Request interrupted while fetching profiles"); + Thread.currentThread().interrupt(); + return null; + } catch (Exception var7) { + LOGGER.at(Level.WARNING).log("Unexpected error fetching profiles: %s", var7.getMessage()); + return null; + } + } + + public SessionServiceClient.GameSessionResponse createGameSession(@Nonnull String oauthAccessToken, @Nonnull UUID profileUuid) { + try { + String body = String.format("{\"uuid\":\"%s\"}", profileUuid.toString()); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(this.sessionServiceUrl + "/game-session/new")) + .header("Content-Type", "application/json") + .header("Authorization", "Bearer " + oauthAccessToken) + .header("User-Agent", AuthConfig.USER_AGENT) + .timeout(REQUEST_TIMEOUT) + .POST(BodyPublishers.ofString(body)) + .build(); + LOGGER.at(Level.INFO).log("Creating game session..."); + HttpResponse response = this.httpClient.send(request, BodyHandlers.ofString()); + if (response.statusCode() != 200 && response.statusCode() != 201) { + LOGGER.at(Level.WARNING).log("Failed to create game session: HTTP %d - %s", response.statusCode(), response.body()); + return null; + } else { + SessionServiceClient.GameSessionResponse sessionResponse = SessionServiceClient.GameSessionResponse.CODEC + .decodeJson(new RawJsonReader(response.body().toCharArray()), EmptyExtraInfo.EMPTY); + if (sessionResponse != null && sessionResponse.identityToken != null) { + LOGGER.at(Level.INFO).log("Successfully created game session"); + return sessionResponse; + } else { + LOGGER.at(Level.WARNING).log("Session Service returned invalid response"); + return null; + } + } + } catch (IOException var7) { + LOGGER.at(Level.WARNING).log("IO error while creating session: %s", var7.getMessage()); + return null; + } catch (InterruptedException var8) { + LOGGER.at(Level.WARNING).log("Request interrupted while creating session"); + Thread.currentThread().interrupt(); + return null; + } catch (Exception var9) { + LOGGER.at(Level.WARNING).log("Unexpected error creating session: %s", var9.getMessage()); + return null; + } + } + + public CompletableFuture refreshSessionAsync(@Nonnull String sessionToken) { + return CompletableFuture.supplyAsync( + () -> { + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(this.sessionServiceUrl + "/game-session/refresh")) + .header("Accept", "application/json") + .header("Authorization", "Bearer " + sessionToken) + .header("User-Agent", AuthConfig.USER_AGENT) + .timeout(REQUEST_TIMEOUT) + .POST(BodyPublishers.noBody()) + .build(); + LOGGER.at(Level.INFO).log("Refreshing game session..."); + HttpResponse response = this.httpClient.send(request, BodyHandlers.ofString()); + if (response.statusCode() != 200) { + LOGGER.at(Level.WARNING).log("Failed to refresh session: HTTP %d - %s", response.statusCode(), response.body()); + return null; + } else { + SessionServiceClient.GameSessionResponse sessionResponse = SessionServiceClient.GameSessionResponse.CODEC + .decodeJson(new RawJsonReader(response.body().toCharArray()), EmptyExtraInfo.EMPTY); + if (sessionResponse != null && sessionResponse.identityToken != null) { + LOGGER.at(Level.INFO).log("Successfully refreshed game session"); + return sessionResponse; + } else { + LOGGER.at(Level.WARNING).log("Session Service returned invalid response (missing identity token)"); + return null; + } + } + } catch (IOException var5) { + LOGGER.at(Level.WARNING).log("IO error while refreshing session: %s", var5.getMessage()); + return null; + } catch (InterruptedException var6) { + LOGGER.at(Level.WARNING).log("Request interrupted while refreshing session"); + Thread.currentThread().interrupt(); + return null; + } catch (Exception var7) { + LOGGER.at(Level.WARNING).log("Unexpected error refreshing session: %s", var7.getMessage()); + return null; + } + } + ); + } + + public void terminateSession(@Nonnull String sessionToken) { + if (sessionToken != null && !sessionToken.isEmpty()) { + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(this.sessionServiceUrl + "/game-session")) + .header("Authorization", "Bearer " + sessionToken) + .header("User-Agent", AuthConfig.USER_AGENT) + .timeout(REQUEST_TIMEOUT) + .DELETE() + .build(); + LOGGER.at(Level.INFO).log("Terminating game session..."); + HttpResponse response = this.httpClient.send(request, BodyHandlers.ofString()); + if (response.statusCode() != 200 && response.statusCode() != 204) { + LOGGER.at(Level.WARNING).log("Failed to terminate session: HTTP %d - %s", response.statusCode(), response.body()); + } else { + LOGGER.at(Level.INFO).log("Game session terminated"); + } + } catch (IOException var4) { + LOGGER.at(Level.WARNING).log("IO error while terminating session: %s", var4.getMessage()); + } catch (InterruptedException var5) { + LOGGER.at(Level.WARNING).log("Request interrupted while terminating session"); + Thread.currentThread().interrupt(); + } catch (Exception var6) { + LOGGER.at(Level.WARNING).log("Error terminating session: %s", var6.getMessage()); + } + } + } + + private static String escapeJsonString(String value) { + return value == null ? "" : value.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t"); + } + + private static KeyedCodec externalKey(String key, Codec codec) { + return new KeyedCodec<>(key, codec, false, true); + } + + public static class AccessTokenResponse { + public String accessToken; + public static final BuilderCodec CODEC = BuilderCodec.builder( + SessionServiceClient.AccessTokenResponse.class, SessionServiceClient.AccessTokenResponse::new + ) + .append(SessionServiceClient.externalKey("accessToken", Codec.STRING), (r, v) -> r.accessToken = v, r -> r.accessToken) + .add() + .build(); + + public AccessTokenResponse() { + } + } + + public static class AuthGrantResponse { + public String authorizationGrant; + public static final BuilderCodec CODEC = BuilderCodec.builder( + SessionServiceClient.AuthGrantResponse.class, SessionServiceClient.AuthGrantResponse::new + ) + .append(SessionServiceClient.externalKey("authorizationGrant", Codec.STRING), (r, v) -> r.authorizationGrant = v, r -> r.authorizationGrant) + .add() + .build(); + + public AuthGrantResponse() { + } + } + + public static class GameProfile { + public UUID uuid; + public String username; + public static final BuilderCodec CODEC = BuilderCodec.builder( + SessionServiceClient.GameProfile.class, SessionServiceClient.GameProfile::new + ) + .append(SessionServiceClient.externalKey("uuid", Codec.UUID_STRING), (p, v) -> p.uuid = v, p -> p.uuid) + .add() + .append(SessionServiceClient.externalKey("username", Codec.STRING), (p, v) -> p.username = v, p -> p.username) + .add() + .build(); + + public GameProfile() { + } + } + + public static class GameSessionResponse { + public String sessionToken; + public String identityToken; + public String expiresAt; + public static final BuilderCodec CODEC = BuilderCodec.builder( + SessionServiceClient.GameSessionResponse.class, SessionServiceClient.GameSessionResponse::new + ) + .append(SessionServiceClient.externalKey("sessionToken", Codec.STRING), (r, v) -> r.sessionToken = v, r -> r.sessionToken) + .add() + .append(SessionServiceClient.externalKey("identityToken", Codec.STRING), (r, v) -> r.identityToken = v, r -> r.identityToken) + .add() + .append(SessionServiceClient.externalKey("expiresAt", Codec.STRING), (r, v) -> r.expiresAt = v, r -> r.expiresAt) + .add() + .build(); + + public GameSessionResponse() { + } + + public Instant getExpiresAtInstant() { + if (this.expiresAt == null) { + return null; + } else { + try { + return Instant.parse(this.expiresAt); + } catch (Exception var2) { + return null; + } + } + } + } + + public static class JwkKey { + public String kty; + public String alg; + public String use; + public String kid; + public String crv; + public String x; + public String y; + public String n; + public String e; + public static final BuilderCodec CODEC = BuilderCodec.builder( + SessionServiceClient.JwkKey.class, SessionServiceClient.JwkKey::new + ) + .append(SessionServiceClient.externalKey("kty", Codec.STRING), (k, v) -> k.kty = v, k -> k.kty) + .add() + .append(SessionServiceClient.externalKey("alg", Codec.STRING), (k, v) -> k.alg = v, k -> k.alg) + .add() + .append(SessionServiceClient.externalKey("use", Codec.STRING), (k, v) -> k.use = v, k -> k.use) + .add() + .append(SessionServiceClient.externalKey("kid", Codec.STRING), (k, v) -> k.kid = v, k -> k.kid) + .add() + .append(SessionServiceClient.externalKey("crv", Codec.STRING), (k, v) -> k.crv = v, k -> k.crv) + .add() + .append(SessionServiceClient.externalKey("x", Codec.STRING), (k, v) -> k.x = v, k -> k.x) + .add() + .append(SessionServiceClient.externalKey("y", Codec.STRING), (k, v) -> k.y = v, k -> k.y) + .add() + .append(SessionServiceClient.externalKey("n", Codec.STRING), (k, v) -> k.n = v, k -> k.n) + .add() + .append(SessionServiceClient.externalKey("e", Codec.STRING), (k, v) -> k.e = v, k -> k.e) + .add() + .build(); + + public JwkKey() { + } + } + + public static class JwksResponse { + public SessionServiceClient.JwkKey[] keys; + public static final BuilderCodec CODEC = BuilderCodec.builder( + SessionServiceClient.JwksResponse.class, SessionServiceClient.JwksResponse::new + ) + .append( + SessionServiceClient.externalKey("keys", new ArrayCodec<>(SessionServiceClient.JwkKey.CODEC, SessionServiceClient.JwkKey[]::new)), + (r, v) -> r.keys = v, + r -> r.keys + ) + .add() + .build(); + + public JwksResponse() { + } + } + + public static class LauncherDataResponse { + public UUID owner; + public SessionServiceClient.GameProfile[] profiles; + public static final BuilderCodec CODEC = BuilderCodec.builder( + SessionServiceClient.LauncherDataResponse.class, SessionServiceClient.LauncherDataResponse::new + ) + .append(SessionServiceClient.externalKey("owner", Codec.UUID_STRING), (r, v) -> r.owner = v, r -> r.owner) + .add() + .append( + SessionServiceClient.externalKey("profiles", new ArrayCodec<>(SessionServiceClient.GameProfile.CODEC, SessionServiceClient.GameProfile[]::new)), + (r, v) -> r.profiles = v, + r -> r.profiles + ) + .add() + .build(); + + public LauncherDataResponse() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/auth/oauth/OAuthBrowserFlow.java b/src/com/hypixel/hytale/server/core/auth/oauth/OAuthBrowserFlow.java new file mode 100644 index 0000000..3348903 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/oauth/OAuthBrowserFlow.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.server.core.auth.oauth; + +public abstract class OAuthBrowserFlow extends OAuthFlow { + public OAuthBrowserFlow() { + } + + public abstract void onFlowInfo(String var1); +} diff --git a/src/com/hypixel/hytale/server/core/auth/oauth/OAuthClient.java b/src/com/hypixel/hytale/server/core/auth/oauth/OAuthClient.java new file mode 100644 index 0000000..f6e7443 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/oauth/OAuthClient.java @@ -0,0 +1,402 @@ +package com.hypixel.hytale.server.core.auth.oauth; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.auth.AuthConfig; +import com.sun.net.httpserver.HttpServer; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse.BodyHandlers; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.time.Duration; +import java.util.Base64; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class OAuthClient { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final SecureRandom RANDOM = new SecureRandom(); + private final HttpClient httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10L)).build(); + + public OAuthClient() { + } + + public Runnable startFlow(@Nonnull OAuthBrowserFlow flow) { + AtomicBoolean cancelled = new AtomicBoolean(false); + CompletableFuture.runAsync( + () -> { + HttpServer server = null; + + try { + String csrfState = this.generateRandomString(32); + String codeVerifier = this.generateRandomString(64); + String codeChallenge = this.generateCodeChallenge(codeVerifier); + server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); + int port = server.getAddress().getPort(); + String encodedState = this.encodeStateWithPort(csrfState, port); + String redirectUri = "https://accounts.hytale.com/consent/client"; + CompletableFuture authCodeFuture = new CompletableFuture<>(); + server.createContext( + "/", + exchange -> { + try { + String query = exchange.getRequestURI().getQuery(); + String code = this.extractParam(query, "code"); + String returnedEncodedState = this.extractParam(query, "state"); + String response; + int statusCode; + if (returnedEncodedState == null || !returnedEncodedState.equals(csrfState)) { + response = buildHtmlPage( + false, + "Authentication Failed", + "Authentication Failed", + "Something went wrong during authentication. Please close this window and try again.", + "Invalid state parameter" + ); + statusCode = 400; + authCodeFuture.completeExceptionally(new Exception("Invalid state")); + } else if (code != null && !code.isEmpty()) { + response = buildHtmlPage( + true, + "Authentication Successful", + "Authentication Successful", + "You have been logged in successfully. You can now close this window and return to the server.", + null + ); + statusCode = 200; + authCodeFuture.complete(code); + } else { + String error = this.extractParam(query, "error"); + String errorMsg = error != null ? error : "No code received"; + response = buildHtmlPage( + false, + "Authentication Failed", + "Authentication Failed", + "Something went wrong during authentication. Please close this window and try again.", + errorMsg + ); + statusCode = 400; + authCodeFuture.completeExceptionally(new Exception(errorMsg)); + } + + exchange.sendResponseHeaders(statusCode, response.length()); + + try (OutputStream os = exchange.getResponseBody()) { + os.write(response.getBytes(StandardCharsets.UTF_8)); + } + } catch (Exception var20x) { + LOGGER.at(Level.WARNING).withCause(var20x).log("Error handling OAuth callback"); + } finally { + HytaleServer.SCHEDULED_EXECUTOR.schedule(() -> server.stop(0), 1L, TimeUnit.SECONDS); + } + } + ); + server.setExecutor(null); + server.start(); + String authUrl = this.buildAuthUrl(encodedState, codeChallenge, redirectUri); + flow.onFlowInfo(authUrl); + String authCode = authCodeFuture.get(5L, TimeUnit.MINUTES); + if (cancelled.get()) { + flow.onFailure("Authentication cancelled"); + return; + } + + OAuthClient.TokenResponse oauthTokens = this.exchangeCodeForTokens(authCode, codeVerifier, redirectUri); + if (oauthTokens != null) { + flow.onSuccess(oauthTokens); + return; + } + + flow.onFailure("Failed to exchange authorization code for tokens"); + } catch (Exception var19) { + LOGGER.at(Level.WARNING).withCause(var19).log("OAuth browser flow failed"); + if (!cancelled.get()) { + flow.onFailure(var19.getMessage()); + } + + return; + } finally { + if (server != null) { + server.stop(0); + } + } + } + ); + return () -> cancelled.set(true); + } + + public Runnable startFlow(OAuthDeviceFlow flow) { + AtomicBoolean cancelled = new AtomicBoolean(false); + CompletableFuture.runAsync(() -> { + try { + OAuthClient.DeviceAuthResponse deviceAuth = this.requestDeviceAuthorization(); + if (deviceAuth == null) { + flow.onFailure("Failed to start device authorization"); + return; + } + + flow.onFlowInfo(deviceAuth.userCode(), deviceAuth.verificationUri(), deviceAuth.verificationUriComplete(), deviceAuth.expiresIn()); + int pollInterval = Math.max(deviceAuth.interval, 5); + long deadline = System.currentTimeMillis() + deviceAuth.expiresIn * 1000L; + + while (System.currentTimeMillis() < deadline && !cancelled.get()) { + Thread.sleep(pollInterval * 1000L); + OAuthClient.TokenResponse tokens = this.pollDeviceToken(deviceAuth.deviceCode); + if (tokens != null) { + if (tokens.error == null) { + flow.onSuccess(tokens); + return; + } + + if (!"authorization_pending".equals(tokens.error)) { + if (!"slow_down".equals(tokens.error)) { + flow.onFailure("Device authorization failed: " + tokens.error); + return; + } + + pollInterval += 5; + } + } + } + + if (cancelled.get()) { + flow.onFailure("Authentication cancelled"); + } else { + flow.onFailure("Device authorization expired"); + } + } catch (Exception var8) { + LOGGER.at(Level.WARNING).withCause(var8).log("OAuth device flow failed"); + if (!cancelled.get()) { + flow.onFailure(var8.getMessage()); + } + } + }); + return () -> cancelled.set(true); + } + + @Nullable + public OAuthClient.TokenResponse refreshTokens(@Nonnull String refreshToken) { + try { + String body = "grant_type=refresh_token&client_id=" + + URLEncoder.encode("hytale-server", StandardCharsets.UTF_8) + + "&refresh_token=" + + URLEncoder.encode(refreshToken, StandardCharsets.UTF_8); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://oauth.accounts.hytale.com/oauth2/token")) + .header("Content-Type", "application/x-www-form-urlencoded") + .header("User-Agent", AuthConfig.USER_AGENT) + .POST(BodyPublishers.ofString(body)) + .build(); + HttpResponse response = this.httpClient.send(request, BodyHandlers.ofString()); + if (response.statusCode() != 200) { + LOGGER.at(Level.WARNING).log("Token refresh failed: HTTP %d - %s", response.statusCode(), response.body()); + return null; + } else { + return this.parseTokenResponse(response.body()); + } + } catch (Exception var5) { + LOGGER.at(Level.WARNING).withCause(var5).log("Token refresh failed"); + return null; + } + } + + private String buildAuthUrl(String state, String codeChallenge, String redirectUri) { + return "https://oauth.accounts.hytale.com/oauth2/auth?response_type=code&client_id=" + + URLEncoder.encode("hytale-server", StandardCharsets.UTF_8) + + "&redirect_uri=" + + URLEncoder.encode(redirectUri, StandardCharsets.UTF_8) + + "&scope=" + + URLEncoder.encode(String.join(" ", AuthConfig.SCOPES), StandardCharsets.UTF_8) + + "&state=" + + URLEncoder.encode(state, StandardCharsets.UTF_8) + + "&code_challenge=" + + URLEncoder.encode(codeChallenge, StandardCharsets.UTF_8) + + "&code_challenge_method=S256"; + } + + @Nullable + private OAuthClient.TokenResponse exchangeCodeForTokens(String code, String codeVerifier, String redirectUri) { + try { + String body = "grant_type=authorization_code&client_id=" + + URLEncoder.encode("hytale-server", StandardCharsets.UTF_8) + + "&code=" + + URLEncoder.encode(code, StandardCharsets.UTF_8) + + "&redirect_uri=" + + URLEncoder.encode(redirectUri, StandardCharsets.UTF_8) + + "&code_verifier=" + + URLEncoder.encode(codeVerifier, StandardCharsets.UTF_8); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://oauth.accounts.hytale.com/oauth2/token")) + .header("Content-Type", "application/x-www-form-urlencoded") + .header("User-Agent", AuthConfig.USER_AGENT) + .POST(BodyPublishers.ofString(body)) + .build(); + HttpResponse response = this.httpClient.send(request, BodyHandlers.ofString()); + if (response.statusCode() != 200) { + LOGGER.at(Level.WARNING).log("Token exchange failed: HTTP %d - %s", response.statusCode(), response.body()); + return null; + } else { + return this.parseTokenResponse(response.body()); + } + } catch (Exception var7) { + LOGGER.at(Level.WARNING).withCause(var7).log("Token exchange failed"); + return null; + } + } + + @Nullable + private OAuthClient.DeviceAuthResponse requestDeviceAuthorization() { + try { + String body = "client_id=" + + URLEncoder.encode("hytale-server", StandardCharsets.UTF_8) + + "&scope=" + + URLEncoder.encode(String.join(" ", AuthConfig.SCOPES), StandardCharsets.UTF_8); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://oauth.accounts.hytale.com/oauth2/device/auth")) + .header("Content-Type", "application/x-www-form-urlencoded") + .header("User-Agent", AuthConfig.USER_AGENT) + .POST(BodyPublishers.ofString(body)) + .build(); + HttpResponse response = this.httpClient.send(request, BodyHandlers.ofString()); + if (response.statusCode() != 200) { + LOGGER.at(Level.WARNING).log("Device authorization request failed: HTTP %d - %s", response.statusCode(), response.body()); + return null; + } else { + return this.parseDeviceAuthResponse(response.body()); + } + } catch (Exception var4) { + LOGGER.at(Level.WARNING).withCause(var4).log("Device authorization request failed"); + return null; + } + } + + @Nullable + private OAuthClient.TokenResponse pollDeviceToken(String deviceCode) { + try { + String body = "grant_type=urn:ietf:params:oauth:grant-type:device_code&client_id=" + + URLEncoder.encode("hytale-server", StandardCharsets.UTF_8) + + "&device_code=" + + URLEncoder.encode(deviceCode, StandardCharsets.UTF_8); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://oauth.accounts.hytale.com/oauth2/token")) + .header("Content-Type", "application/x-www-form-urlencoded") + .header("User-Agent", AuthConfig.USER_AGENT) + .POST(BodyPublishers.ofString(body)) + .build(); + HttpResponse response = this.httpClient.send(request, BodyHandlers.ofString()); + if (response.statusCode() == 400) { + return this.parseTokenResponse(response.body()); + } else if (response.statusCode() != 200) { + LOGGER.at(Level.WARNING).log("Device token poll failed: HTTP %d - %s", response.statusCode(), response.body()); + return null; + } else { + return this.parseTokenResponse(response.body()); + } + } catch (Exception var5) { + LOGGER.at(Level.WARNING).withCause(var5).log("Device token poll failed"); + return null; + } + } + + private String generateRandomString(int length) { + byte[] bytes = new byte[length]; + RANDOM.nextBytes(bytes); + return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes).substring(0, length); + } + + private String generateCodeChallenge(String verifier) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(verifier.getBytes(StandardCharsets.US_ASCII)); + return Base64.getUrlEncoder().withoutPadding().encodeToString(hash); + } catch (Exception var4) { + throw new RuntimeException("Failed to generate code challenge", var4); + } + } + + private String extractParam(String query, String name) { + if (query == null) { + return null; + } else { + Pattern pattern = Pattern.compile(name + "=([^&]*)"); + Matcher matcher = pattern.matcher(query); + return matcher.find() ? URLDecoder.decode(matcher.group(1), StandardCharsets.UTF_8) : null; + } + } + + private String encodeStateWithPort(String state, int port) { + String json = String.format("{\"state\":\"%s\",\"port\":\"%d\"}", state, port); + return Base64.getUrlEncoder().withoutPadding().encodeToString(json.getBytes(StandardCharsets.UTF_8)); + } + + private OAuthClient.TokenResponse parseTokenResponse(String json) { + JsonObject obj = JsonParser.parseString(json).getAsJsonObject(); + return new OAuthClient.TokenResponse( + getJsonString(obj, "access_token"), + getJsonString(obj, "refresh_token"), + getJsonString(obj, "id_token"), + getJsonString(obj, "error"), + getJsonInt(obj, "expires_in", 0) + ); + } + + private OAuthClient.DeviceAuthResponse parseDeviceAuthResponse(String json) { + JsonObject obj = JsonParser.parseString(json).getAsJsonObject(); + return new OAuthClient.DeviceAuthResponse( + getJsonString(obj, "device_code"), + getJsonString(obj, "user_code"), + getJsonString(obj, "verification_uri"), + getJsonString(obj, "verification_uri_complete"), + getJsonInt(obj, "expires_in", 600), + getJsonInt(obj, "interval", 5) + ); + } + + @Nullable + private static String getJsonString(JsonObject obj, String key) { + JsonElement elem = obj.get(key); + return elem != null && elem.isJsonPrimitive() ? elem.getAsString() : null; + } + + private static int getJsonInt(JsonObject obj, String key, int defaultValue) { + JsonElement elem = obj.get(key); + return elem != null && elem.isJsonPrimitive() ? elem.getAsInt() : defaultValue; + } + + private static String buildHtmlPage(boolean success, String title, String heading, String message, @Nullable String errorDetail) { + String detail = errorDetail != null && !errorDetail.isEmpty() ? "
" + errorDetail + "
" : ""; + String iconClass = success ? "icon-success" : "icon-error"; + String iconSvg = success + ? "" + : ""; + return "\n\n\n \n \n %s - Hytale\n \n \n \n \n\n
%s

%s

%s

%s
\n\n" + .formatted(title, iconClass, iconSvg, heading, message, detail); + } + + public record DeviceAuthResponse(String deviceCode, String userCode, String verificationUri, String verificationUriComplete, int expiresIn, int interval) { + } + + public record TokenResponse(@Nullable String accessToken, @Nullable String refreshToken, @Nullable String idToken, @Nullable String error, int expiresIn) { + public boolean isSuccess() { + return this.error == null && this.accessToken != null; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/auth/oauth/OAuthDeviceFlow.java b/src/com/hypixel/hytale/server/core/auth/oauth/OAuthDeviceFlow.java new file mode 100644 index 0000000..87cfb0c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/oauth/OAuthDeviceFlow.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.server.core.auth.oauth; + +public abstract class OAuthDeviceFlow extends OAuthFlow { + public OAuthDeviceFlow() { + } + + public abstract void onFlowInfo(String var1, String var2, String var3, int var4); +} diff --git a/src/com/hypixel/hytale/server/core/auth/oauth/OAuthFlow.java b/src/com/hypixel/hytale/server/core/auth/oauth/OAuthFlow.java new file mode 100644 index 0000000..d9b7738 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/oauth/OAuthFlow.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.core.auth.oauth; + +import java.util.concurrent.CompletableFuture; + +abstract class OAuthFlow { + private OAuthClient.TokenResponse tokenResponse = null; + private final CompletableFuture future = new CompletableFuture<>(); + private OAuthResult result = OAuthResult.UNKNOWN; + private String errorMessage = null; + + OAuthFlow() { + } + + final void onSuccess(OAuthClient.TokenResponse tokenResponse) { + if (!this.future.isDone()) { + this.tokenResponse = tokenResponse; + this.result = OAuthResult.SUCCESS; + this.future.complete(this.result); + } + } + + final void onFailure(String errorMessage) { + if (!this.future.isDone()) { + this.errorMessage = errorMessage; + this.result = OAuthResult.FAILED; + } + } + + public OAuthClient.TokenResponse getTokenResponse() { + return this.tokenResponse; + } + + public OAuthResult getResult() { + return this.result; + } + + public String getErrorMessage() { + return this.errorMessage; + } + + public CompletableFuture getFuture() { + return this.future; + } +} diff --git a/src/com/hypixel/hytale/server/core/auth/oauth/OAuthResult.java b/src/com/hypixel/hytale/server/core/auth/oauth/OAuthResult.java new file mode 100644 index 0000000..c1f26da --- /dev/null +++ b/src/com/hypixel/hytale/server/core/auth/oauth/OAuthResult.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.core.auth.oauth; + +public enum OAuthResult { + UNKNOWN, + SUCCESS, + FAILED; + + private OAuthResult() { + } +} diff --git a/src/com/hypixel/hytale/server/core/blocktype/BlockTypeModule.java b/src/com/hypixel/hytale/server/core/blocktype/BlockTypeModule.java new file mode 100644 index 0000000..9c133ab --- /dev/null +++ b/src/com/hypixel/hytale/server/core/blocktype/BlockTypeModule.java @@ -0,0 +1,521 @@ +package com.hypixel.hytale.server.core.blocktype; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.DisableProcessingAssert; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.RootDependency; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BenchType; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.StateData; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.Bench; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.CraftingBench; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.DiagramCraftingBench; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.ProcessingBench; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.StructuralCraftingBench; +import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; +import com.hypixel.hytale.server.core.modules.LegacyModule; +import com.hypixel.hytale.server.core.modules.item.ItemModule; +import com.hypixel.hytale.server.core.modules.migrations.ChunkColumnMigrationSystem; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.accessor.BlockAccessor; +import com.hypixel.hytale.server.core.universe.world.accessor.ChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.accessor.LocalCachedChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkFlag; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.systems.ChunkSystems; +import com.hypixel.hytale.server.core.universe.world.events.ChunkPreLoadProcessEvent; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import java.util.Arrays; +import java.util.Set; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockTypeModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(BlockTypeModule.class).depends(ItemModule.class).depends(LegacyModule.class).build(); + public static final int SET_BLOCK_SETTINGS = 157; + public static final String DEBUG_CUBE_TEXTURE_UP = "BlockTextures/_Debug/Up.png"; + public static final String DEBUG_CUBE_TEXTURE_DOWN = "BlockTextures/_Debug/Down.png"; + public static final String DEBUG_CUBE_TEXTURE_NORTH = "BlockTextures/_Debug/North.png"; + public static final String DEBUG_CUBE_TEXTURE_SOUTH = "BlockTextures/_Debug/South.png"; + public static final String DEBUG_CUBE_TEXTURE_EAST = "BlockTextures/_Debug/East.png"; + public static final String DEBUG_CUBE_TEXTURE_WEST = "BlockTextures/_Debug/West.png"; + public static final String DEBUG_MODEL_MODEL = "Blocks/_Debug/Model.blockymodel"; + public static final String DEBUG_MODEL_BLOCK_TEXTURE = "Blocks/_Debug/Texture.png"; + public static final String DEBUG_MODEL_ENTITY_TEXTURE = "Characters/_Debug/Texture.png"; + private static final ThreadLocal TEMP_BLOCKS = ThreadLocal.withInitial(() -> new BlockType[327680]); + private static BlockTypeModule instance; + private ComponentType blockPhysicsComponentType; + + public static BlockTypeModule get() { + return instance; + } + + public BlockTypeModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + Bench.CODEC.register(BenchType.Crafting, CraftingBench.class, CraftingBench.CODEC); + Bench.CODEC.register(BenchType.Processing, ProcessingBench.class, ProcessingBench.CODEC); + Bench.CODEC.register(BenchType.DiagramCrafting, DiagramCraftingBench.class, DiagramCraftingBench.CODEC); + Bench.CODEC.register(BenchType.StructuralCrafting, StructuralCraftingBench.class, StructuralCraftingBench.CODEC); + this.blockPhysicsComponentType = this.getChunkStoreRegistry().registerComponent(BlockPhysics.class, "BlockPhysics", BlockPhysics.CODEC); + this.getChunkStoreRegistry().registerSystem(new BlockTypeModule.MigrateLegacySections()); + } + + public ComponentType getBlockPhysicsComponentType() { + return this.blockPhysicsComponentType; + } + + @Deprecated + private static void onChunkPreLoadProcessEnsureBlockState(@Nonnull ChunkPreLoadProcessEvent event) { + if (event.isNewlyGenerated()) { + BlockTypeAssetMap blockTypeAssetMap = BlockType.getAssetMap(); + Holder holder = event.getHolder(); + WorldChunk chunk = event.getChunk(); + ChunkColumn column = holder.getComponent(ChunkColumn.getComponentType()); + if (column != null) { + Holder[] sections = column.getSectionHolders(); + if (sections != null) { + for (int sectionIndex = 0; sectionIndex < 10; sectionIndex++) { + BlockSection section = sections[sectionIndex].ensureAndGetComponent(BlockSection.getComponentType()); + if (!section.isSolidAir()) { + int sectionYBlock = sectionIndex << 5; + + for (int sectionY = 0; sectionY < 32; sectionY++) { + int y = sectionYBlock | sectionY; + + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + int blockId = section.get(x, y, z); + BlockType blockType = blockTypeAssetMap.getAsset(blockId); + if (blockType != null && !blockType.isUnknown() && section.getFiller(x, y, z) == 0) { + StateData state = blockType.getState(); + if (state != null && state.getId() != null && chunk.getState(x, y, z) == null) { + Vector3i position = new Vector3i(x, y, z); + BlockState blockState = BlockStateModule.get().createBlockState(state.getId(), chunk, position, blockType); + if (blockState != null) { + chunk.setState(x, y, z, blockState); + } + } + } + } + } + } + } + } + } + } + } + } + + private static void onChunkPreLoadProcess(@Nonnull ChunkPreLoadProcessEvent event) { + if (event.isNewlyGenerated()) { + WorldChunk chunk = event.getChunk(); + BlockChunk blockChunk = chunk.getBlockChunk(); + Holder holder = event.getHolder(); + ChunkColumn column = holder.getComponent(ChunkColumn.getComponentType()); + if (column != null) { + Holder[] sections = column.getSectionHolders(); + if (sections != null) { + BlockType[] tempBlocks = TEMP_BLOCKS.get(); + Arrays.fill(tempBlocks, null); + + for (int sectionIndex = 0; sectionIndex < 10; sectionIndex++) { + BlockSection section = sections[sectionIndex].ensureAndGetComponent(BlockSection.getComponentType()); + if (!section.isSolidAir() && !(section.getMaximumHitboxExtent() <= 0.0)) { + onChunksectionPreLoadProcess(chunk, section, sectionIndex, tempBlocks); + } + } + } + } + } + } + + private static void onChunksectionPreLoadProcess(@Nonnull WorldChunk chunk, @Nonnull BlockSection section, int sectionIndex, @Nonnull BlockType[] blocks) { + int sectionYBlock = sectionIndex << 5; + BlockTypeAssetMap blockTypeAssetMap = BlockType.getAssetMap(); + IndexedLookupTableAssetMap hitboxAssetMap = BlockBoundingBoxes.getAssetMap(); + + for (int y = 0; y < 32; y++) { + int finalY = sectionYBlock | y; + + for (int x = 0; x < 32; x++) { + int finalX = x; + + for (int z = 0; z < 32; z++) { + int finalZ = z; + BlockType blockType = getBlockType(blockTypeAssetMap, blocks, section, finalX, finalY, finalZ, true); + if (blockType != null) { + int rotation = section.getRotationIndex(x, y, z); + int filler = section.getFiller(x, y, z); + if (filler != 0) { + int blockX = finalX - FillerBlockUtil.unpackX(filler); + if (blockX >= 0 && blockX < 32) { + int blockY = finalY - FillerBlockUtil.unpackY(filler); + if (blockY >= 0 && blockY < 320) { + int blockZ = finalZ - FillerBlockUtil.unpackZ(filler); + if (blockZ >= 0 && blockZ < 32) { + BlockType originBlockType = getBlockType(blockTypeAssetMap, blocks, section, blockX, blockY, blockZ, false); + if (originBlockType != null) { + String blockTypeKey = blockType.getId(); + if (blockType.isUnknown() || !blockTypeKey.equals(originBlockType.getId())) { + chunk.breakBlock(finalX, finalY, finalZ, 157); + } + } + } + } + } + } else { + int blockId = blockTypeAssetMap.getIndex(blockType.getId()); + FillerBlockUtil.forEachFillerBlock(hitboxAssetMap.getAsset(blockType.getHitboxTypeIndex()).get(rotation), (x1, y1, z1) -> { + if (x1 != 0 || y1 != 0 || z1 != 0) { + int blockX = finalX + x1; + if (blockX >= 0 && blockX < 32) { + int blockYx = finalY + y1; + if (blockYx >= 0 && blockYx < 320) { + int blockZx = finalZ + z1; + if (blockZx >= 0 && blockZx < 32) { + BlockType neighbourBlockType = getBlockType(blockTypeAssetMap, blocks, section, blockX, blockYx, blockZx, false); + if (neighbourBlockType != null && neighbourBlockType.getMaterial() != BlockMaterial.Solid) { + int newFiller = FillerBlockUtil.pack(x1, y1, z1); + chunk.setBlock(blockX, blockYx, blockZx, blockId, blockType, rotation, newFiller, 157); + } + } + } + } + } + }); + } + } + } + } + } + } + + @Nullable + private static BlockType getBlockType( + @Nonnull BlockTypeAssetMap blockTypeAssetMap, + @Nonnull BlockType[] blocks, + @Nonnull BlockSection section, + int blockX, + int blockY, + int blockZ, + boolean skipEmpty + ) { + int indexBlock = ChunkUtil.indexBlockInColumn(blockX, blockY, blockZ); + BlockType blockType = blocks[indexBlock]; + if (blockType == null) { + int blockId = section.get(blockX, blockY, blockZ); + if (blockId == 0) { + blocks[indexBlock] = BlockType.EMPTY; + return skipEmpty ? null : BlockType.EMPTY; + } else { + return blocks[indexBlock] = blockTypeAssetMap.getAsset(blockId); + } + } else { + return skipEmpty && "Empty".equals(blockType.getId()) ? null : blockType; + } + } + + public static void breakOrSetFillerBlocks( + @Nonnull BlockTypeAssetMap blockTypeAssetMap, + @Nonnull IndexedLookupTableAssetMap hitboxAssetMap, + @Nonnull ChunkAccessor accessor, + @Nonnull BlockAccessor chunk, + int finalX, + int finalY, + int finalZ, + @Nonnull BlockType blockType, + int rotation + ) { + int filler = chunk.getFiller(finalX, finalY, finalZ); + if (filler != 0) { + if (!isFillerValid(blockTypeAssetMap, accessor, chunk, blockType, filler, finalX, finalY, finalZ)) { + chunk.breakBlock(finalX, finalY, finalZ, 157); + } else { + int originX = finalX - FillerBlockUtil.unpackX(filler); + int originY = finalY - FillerBlockUtil.unpackY(filler); + int originZ = finalZ - FillerBlockUtil.unpackZ(filler); + setFillerBlocks(blockTypeAssetMap, hitboxAssetMap, accessor, chunk, originX, originY, originZ, blockType, rotation); + } + } else { + setFillerBlocks(blockTypeAssetMap, hitboxAssetMap, accessor, chunk, finalX, finalY, finalZ, blockType, rotation); + } + } + + @Nullable + private static BlockType getOriginBlockType( + @Nonnull BlockTypeAssetMap blockTypeAssetMap, + @Nonnull ChunkAccessor accessor, + @Nonnull BlockAccessor section, + int originX, + int originY, + int originZ + ) { + if (originX >= 0 && originX < 32 && originY >= 0 && originY < 320 && originZ >= 0 && originZ < 32) { + int originBlockId = section.getBlock(originX, originY, originZ); + return blockTypeAssetMap.getAsset(originBlockId); + } else { + int worldX = (section.getX() << 5) + originX; + int worldZ = (section.getZ() << 5) + originZ; + BlockAccessor fillerOriginChunk = accessor.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(worldX, worldZ)); + if (fillerOriginChunk != null) { + int originBlockId = fillerOriginChunk.getBlock(originX, originY, originZ); + return blockTypeAssetMap.getAsset(originBlockId); + } else { + get() + .getLogger() + .at(Level.WARNING) + .log("Blocking chunk load when trying to get origin block for filler! Origin: %s, %s, %s", originX, originY, originZ); + fillerOriginChunk = accessor.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(worldX, worldZ)); + int originBlockId = fillerOriginChunk.getBlock(originX, originY, originZ); + return blockTypeAssetMap.getAsset(originBlockId); + } + } + } + + private static void setFillerBlocks( + @Nonnull BlockTypeAssetMap blockTypeAssetMap, + @Nonnull IndexedLookupTableAssetMap hitboxAssetMap, + @Nonnull ChunkAccessor accessor, + @Nonnull BlockAccessor chunk, + int finalX, + int finalY, + int finalZ, + @Nonnull BlockType originBlockType, + int rotation + ) { + int originBlockId = blockTypeAssetMap.getIndex(originBlockType.getId()); + FillerBlockUtil.forEachFillerBlock( + hitboxAssetMap.getAsset(originBlockType.getHitboxTypeIndex()).get(rotation), + (x1, y1, z1) -> { + if (x1 != 0 || y1 != 0 || z1 != 0) { + int blockX = finalX + x1; + int blockY = finalY + y1; + int blockZ = finalZ + z1; + if (blockX >= 0 && blockX < 32 && blockY >= 0 && blockY < 320 && blockZ >= 0 && blockZ < 32) { + int blockId = chunk.getBlock(blockX, blockY, blockZ); + int currentRotation = chunk.getRotationIndex(blockX, blockY, blockZ); + int currentFiller = chunk.getFiller(blockX, blockY, blockZ); + BlockType blockType = blockTypeAssetMap.getAsset(blockId); + if ((currentFiller == 0 || !isFillerValid(blockTypeAssetMap, accessor, chunk, blockType, currentFiller, blockX, blockY, blockZ)) + && blockType.getMaterial() != BlockMaterial.Solid) { + int filler = FillerBlockUtil.pack(x1, y1, z1); + chunk.setBlock(blockX, blockY, blockZ, originBlockId, originBlockType, currentRotation, filler, 157); + } + } else { + int worldX = (chunk.getX() << 5) + blockX; + int worldZ = (chunk.getZ() << 5) + blockZ; + BlockAccessor neighbourChunk = accessor.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(worldX, worldZ)); + if (neighbourChunk != null) { + int blockId = neighbourChunk.getBlock(blockX, blockY, blockZ); + int currentRotation = neighbourChunk.getRotationIndex(blockX, blockY, blockZ); + int currentFiller = neighbourChunk.getFiller(blockX, blockY, blockZ); + BlockType blockType = blockTypeAssetMap.getAsset(blockId); + if ((currentFiller == 0 || !isFillerValid(blockTypeAssetMap, accessor, chunk, blockType, currentFiller, blockX, blockY, blockZ)) + && blockType.getMaterial() != BlockMaterial.Solid) { + int filler = FillerBlockUtil.pack(x1, y1, z1); + neighbourChunk.setBlock(blockX, blockY, blockZ, originBlockId, originBlockType, currentRotation, filler, 157); + } + } + } + } + } + ); + } + + private static boolean isFillerValid( + @Nonnull BlockTypeAssetMap blockTypeAssetMap, + @Nonnull ChunkAccessor accessor, + @Nonnull BlockAccessor chunk, + @Nonnull BlockType blockType, + int filler, + int x, + int y, + int z + ) { + int originX = x - FillerBlockUtil.unpackX(filler); + int originY = y - FillerBlockUtil.unpackY(filler); + int originZ = z - FillerBlockUtil.unpackZ(filler); + BlockType originBlockType = getOriginBlockType(blockTypeAssetMap, accessor, chunk, originX, originY, originZ); + if (blockType.isUnknown()) { + return false; + } else { + String blockTypeKey = blockType.getId(); + return blockTypeKey.equals(originBlockType.getId()); + } + } + + @Deprecated + private static class FixFillerBlocksSystem extends RefSystem implements DisableProcessingAssert { + private static final ComponentType COMPONENT_TYPE = WorldChunk.getComponentType(); + + private FixFillerBlocksSystem() { + } + + @Override + public Query getQuery() { + return COMPONENT_TYPE; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + WorldChunk chunk = store.getComponent(ref, COMPONENT_TYPE); + if (chunk.is(ChunkFlag.NEWLY_GENERATED)) { + World world = store.getExternalData().getWorld(); + world.execute(() -> fixFillerFor(world, chunk)); + } + } + + public static void fixFillerFor(@Nonnull World world, @Nonnull WorldChunk chunk) { + BlockChunk blockChunk = chunk.getBlockChunk(); + BlockTypeAssetMap blockTypeAssetMap = BlockType.getAssetMap(); + IndexedLookupTableAssetMap hitboxAssetMap = BlockBoundingBoxes.getAssetMap(); + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atChunk(world, chunk, 1); + + for (int x = -1; x < 2; x++) { + for (int z = -1; z < 2; z++) { + if (x != 0 || z != 0) { + WorldChunk chunkIfInMemory = world.getChunkIfInMemory(ChunkUtil.indexChunk(x + chunk.getX(), z + chunk.getZ())); + if (chunkIfInMemory != null) { + accessor.overwrite(chunkIfInMemory); + } + } + } + } + + for (int sectionIndex = 0; sectionIndex < 10; sectionIndex++) { + BlockSection section = blockChunk.getSectionAtIndex(sectionIndex); + boolean skipInsideSection = section.getMaximumHitboxExtent() <= 0.0; + int sectionYBlock = sectionIndex << 5; + + for (int yInSection = 0; yInSection < 32; yInSection++) { + int y = sectionYBlock | yInSection; + + for (int x = -1; x < 33; x++) { + for (int zx = -1; zx < 33; zx++) { + if (x < 1 || x >= 31 || y < 1 || y >= 319 || zx < 1 || zx >= 31) { + if (x < 0 || x >= 32 || y < 0 || y >= 320 || zx < 0 || zx >= 32) { + int worldX = (chunk.getX() << 5) + x; + int worldZ = (chunk.getZ() << 5) + zx; + WorldChunk neighbourChunk = accessor.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(worldX, worldZ)); + if (neighbourChunk != null) { + BlockSection neighbourSection = neighbourChunk.getBlockChunk().getSectionAtBlockY(y); + if (!(neighbourSection.getMaximumHitboxExtent() <= 0.0)) { + int blockId = neighbourSection.get(x, y, zx); + if (blockId != 0) { + BlockType blockType = blockTypeAssetMap.getAsset(blockId); + int rotation = neighbourSection.getRotationIndex(x, y, zx); + BlockTypeModule.breakOrSetFillerBlocks(blockTypeAssetMap, hitboxAssetMap, accessor, chunk, x, y, zx, blockType, rotation); + } + } + } + } else if (!skipInsideSection) { + int blockId = section.get(x, y, zx); + if (blockId != 0) { + BlockType blockType = blockTypeAssetMap.getAsset(blockId); + int rotation = section.getRotationIndex(x, y, zx); + BlockTypeModule.breakOrSetFillerBlocks(blockTypeAssetMap, hitboxAssetMap, accessor, chunk, x, y, zx, blockType, rotation); + } + } + } + } + } + } + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + } + + @Deprecated(forRemoval = true) + private static class MigrateLegacySections extends ChunkColumnMigrationSystem { + private final Query QUERY = Query.and(ChunkColumn.getComponentType(), BlockChunk.getComponentType()); + private final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.BEFORE, LegacyModule.MigrateLegacySections.class), + new SystemDependency<>(Order.AFTER, ChunkSystems.OnNewChunk.class), + RootDependency.first() + ); + + private MigrateLegacySections() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + ChunkColumn column = holder.getComponent(ChunkColumn.getComponentType()); + + assert column != null; + + BlockChunk blockChunk = holder.getComponent(BlockChunk.getComponentType()); + + assert blockChunk != null; + + Holder[] sections = column.getSectionHolders(); + BlockSection[] legacySections = blockChunk.getMigratedSections(); + if (legacySections != null) { + for (int i = 0; i < sections.length; i++) { + Holder section = sections[i]; + BlockSection paletteSection = legacySections[i]; + if (section != null && paletteSection != null) { + BlockPhysics phys = paletteSection.takeMigratedDecoBlocks(); + if (phys != null) { + section.putComponent(BlockPhysics.getComponentType(), phys); + blockChunk.markNeedsSaving(); + } + } + } + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.DEPENDENCIES; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/blocktype/component/BlockPhysics.java b/src/com/hypixel/hytale/server/core/blocktype/component/BlockPhysics.java new file mode 100644 index 0000000..ce0e3d5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/blocktype/component/BlockPhysics.java @@ -0,0 +1,220 @@ +package com.hypixel.hytale.server.core.blocktype.component; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.BitUtil; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.blocktype.BlockTypeModule; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.util.io.ByteBufUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; +import java.util.Arrays; +import java.util.concurrent.locks.StampedLock; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockPhysics implements Component { + public static final int VERSION = 0; + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockPhysics.class, BlockPhysics::new) + .versioned() + .codecVersion(0) + .append(new KeyedCodec<>("Data", Codec.BYTE_ARRAY), BlockPhysics::deserialize, BlockPhysics::serialize) + .add() + .build(); + public static final int SUPPORT_DATA_SIZE = 16384; + public static final int IS_DECO_VALUE = 15; + public static final int NULL_SUPPORT = 0; + private final StampedLock lock = new StampedLock(); + @Nullable + private byte[] supportData = null; + private int nonZeroCount = 0; + + public BlockPhysics() { + } + + public static ComponentType getComponentType() { + return BlockTypeModule.get().getBlockPhysicsComponentType(); + } + + public boolean set(int index, int support) { + long stamp = this.lock.writeLock(); + + try { + support &= 15; + if (this.supportData == null) { + if (support == 0) { + return false; + } + + this.supportData = new byte[16384]; + } + + byte previousValue = BitUtil.getAndSetNibble(this.supportData, index, (byte)support); + if (previousValue != support) { + if (previousValue == 0) { + this.nonZeroCount++; + } else if (support == 0) { + this.nonZeroCount--; + if (this.nonZeroCount <= 0) { + this.supportData = null; + } + } + + return true; + } else { + return false; + } + } finally { + this.lock.unlockWrite(stamp); + } + } + + public boolean set(int x, int y, int z, int support) { + return this.set(ChunkUtil.indexBlock(x, y, z), support); + } + + public int get(int index) { + long stamp = this.lock.readLock(); + + byte var4; + try { + if (this.supportData != null) { + return BitUtil.getNibble(this.supportData, index); + } + + var4 = 0; + } finally { + this.lock.unlockRead(stamp); + } + + return var4; + } + + public int get(int x, int y, int z) { + return this.get(ChunkUtil.indexBlock(x, y, z)); + } + + public boolean isDeco(int x, int y, int z) { + return this.isDeco(ChunkUtil.indexBlock(x, y, z)); + } + + public boolean isDeco(int index) { + return this.get(index) == 15; + } + + @Nonnull + @Override + public Component clone() { + BlockPhysics decoBlocks = new BlockPhysics(); + if (this.supportData != null) { + decoBlocks.supportData = Arrays.copyOf(this.supportData, this.supportData.length); + decoBlocks.nonZeroCount = this.nonZeroCount; + } + + return decoBlocks; + } + + private byte[] serialize(ExtraInfo extraInfo) { + ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(); + + try { + buf.writeBoolean(this.supportData != null); + if (this.supportData != null) { + buf.writeBytes(this.supportData); + } + + return ByteBufUtil.getBytesRelease(buf); + } catch (Throwable var4) { + buf.release(); + throw SneakyThrow.sneakyThrow(var4); + } + } + + private void deserialize(@Nonnull byte[] bytes, ExtraInfo extraInfo) { + ByteBuf buf = Unpooled.wrappedBuffer(bytes); + if (buf.readBoolean()) { + this.supportData = new byte[16384]; + buf.readBytes(this.supportData); + this.nonZeroCount = 0; + + for (int i = 0; i < 16384; i++) { + byte v = this.supportData[i]; + if ((v & 15) != 0) { + this.nonZeroCount++; + } + + if ((v & 240) != 0) { + this.nonZeroCount++; + } + } + } else { + this.supportData = null; + this.nonZeroCount = 0; + } + } + + public static void clear(@Nonnull Store store, @Nonnull Ref section, int x, int y, int z) { + BlockPhysics blockPhysics = store.getComponent(section, getComponentType()); + if (blockPhysics != null) { + blockPhysics.set(ChunkUtil.indexBlock(x, y, z), 0); + } + } + + public static void clear(@Nonnull Holder section, int x, int y, int z) { + BlockPhysics blockPhysics = section.getComponent(getComponentType()); + if (blockPhysics != null) { + blockPhysics.set(ChunkUtil.indexBlock(x, y, z), 0); + } + } + + public static void reset(@Nonnull Store store, @Nonnull Ref section, int x, int y, int z) { + BlockPhysics blockPhysics = store.getComponent(section, getComponentType()); + if (blockPhysics == null) { + blockPhysics = store.ensureAndGetComponent(section, getComponentType()); + } + + blockPhysics.set(ChunkUtil.indexBlock(x, y, z), 0); + } + + public static void reset(@Nonnull Holder section, int x, int y, int z) { + setSupportValue(section, x, y, z, 0); + } + + public static void markDeco(@Nonnull ComponentAccessor store, @Nonnull Ref section, int x, int y, int z) { + BlockPhysics blockPhysics = store.getComponent(section, getComponentType()); + if (blockPhysics == null) { + blockPhysics = store.ensureAndGetComponent(section, getComponentType()); + } + + blockPhysics.set(ChunkUtil.indexBlock(x, y, z), 15); + } + + public static void setSupportValue(@Nonnull Store store, @Nonnull Ref section, int x, int y, int z, int value) { + BlockPhysics blockPhysics = store.getComponent(section, getComponentType()); + if (blockPhysics == null) { + blockPhysics = store.ensureAndGetComponent(section, getComponentType()); + } + + blockPhysics.set(ChunkUtil.indexBlock(x, y, z), value); + } + + public static void setSupportValue(@Nonnull Holder section, int x, int y, int z, int value) { + BlockPhysics blockPhysics = section.getComponent(getComponentType()); + if (blockPhysics == null) { + blockPhysics = section.ensureAndGetComponent(getComponentType()); + } + + blockPhysics.set(ChunkUtil.indexBlock(x, y, z), value); + } +} diff --git a/src/com/hypixel/hytale/server/core/client/ClientFeatureHandler.java b/src/com/hypixel/hytale/server/core/client/ClientFeatureHandler.java new file mode 100644 index 0000000..a43f801 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/client/ClientFeatureHandler.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.core.client; + +import com.hypixel.hytale.protocol.packets.setup.ClientFeature; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import javax.annotation.Nonnull; + +public class ClientFeatureHandler { + public ClientFeatureHandler() { + } + + public static void register(@Nonnull ClientFeature feature) { + for (World world : Universe.get().getWorlds().values()) { + world.registerFeature(feature, true); + } + } + + public static void unregister(@Nonnull ClientFeature feature) { + for (World world : Universe.get().getWorlds().values()) { + world.registerFeature(feature, false); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/codec/BoolDoublePairCodec.java b/src/com/hypixel/hytale/server/core/codec/BoolDoublePairCodec.java new file mode 100644 index 0000000..fd70e04 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/codec/BoolDoublePairCodec.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.core.codec; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.common.tuple.BoolDoublePair; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import org.bson.BsonString; +import org.bson.BsonValue; + +public class BoolDoublePairCodec implements Codec { + private static final Pattern PATTERN = Pattern.compile("~?[0-9]*"); + + public BoolDoublePairCodec() { + } + + @Nonnull + public BoolDoublePair decode(BsonValue bsonValue, ExtraInfo extraInfo) { + if (bsonValue instanceof BsonString) { + String str = bsonValue.asString().getValue(); + if (str.charAt(0) == '~') { + return str.length() == 1 ? BoolDoublePair.of(true, 0.0) : BoolDoublePair.of(true, Double.parseDouble(str.substring(1))); + } else { + return BoolDoublePair.of(false, Double.parseDouble(str)); + } + } else { + return BoolDoublePair.of(false, bsonValue.asDouble().getValue()); + } + } + + @Nonnull + public BsonValue encode(@Nonnull BoolDoublePair pair, ExtraInfo extraInfo) { + return new BsonString((pair.getLeft() ? "~" : "") + pair.getRight()); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + StringSchema s = new StringSchema(); + s.setPattern(PATTERN); + return Schema.anyOf(new NumberSchema(), s); + } +} diff --git a/src/com/hypixel/hytale/server/core/codec/LayerEntryCodec.java b/src/com/hypixel/hytale/server/core/codec/LayerEntryCodec.java new file mode 100644 index 0000000..3683532 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/codec/LayerEntryCodec.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.core.codec; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class LayerEntryCodec { + public static final BuilderCodec CODEC = BuilderCodec.builder(LayerEntryCodec.class, LayerEntryCodec::new) + .append(new KeyedCodec<>("Left", Codec.INTEGER), (entry, depth) -> entry.depth = depth, entry -> entry.depth) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Right", Codec.STRING), (entry, material) -> entry.material = material, entry -> entry.material) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("UseToolArg", Codec.BOOLEAN), (entry, useToolArg) -> entry.useToolArg = useToolArg != null && useToolArg, entry -> entry.useToolArg + ) + .add() + .build(); + private Integer depth; + private String material; + private boolean useToolArg = false; + + public LayerEntryCodec() { + } + + public LayerEntryCodec(Integer depth, String material, boolean useToolArg) { + this.depth = depth; + this.material = material; + this.useToolArg = useToolArg; + } + + @Nonnull + public Integer getDepth() { + return this.depth; + } + + @Nonnull + public String getMaterial() { + return this.material; + } + + public boolean isUseToolArg() { + return this.useToolArg; + } +} diff --git a/src/com/hypixel/hytale/server/core/codec/PairCodec.java b/src/com/hypixel/hytale/server/core/codec/PairCodec.java new file mode 100644 index 0000000..cd5d809 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/codec/PairCodec.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.server.core.codec; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import it.unimi.dsi.fastutil.Pair; +import javax.annotation.Nonnull; + +public class PairCodec { + public PairCodec() { + } + + public static class IntegerPair { + public static final BuilderCodec CODEC = BuilderCodec.builder(PairCodec.IntegerPair.class, PairCodec.IntegerPair::new) + .append(new KeyedCodec<>("Left", Codec.INTEGER), (pair, left) -> pair.left = left, pair -> pair.left) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Right", Codec.INTEGER), (pair, right) -> pair.right = right, pair -> pair.right) + .addValidator(Validators.nonNull()) + .add() + .build(); + private Integer left; + private Integer right; + + public IntegerPair() { + } + + public IntegerPair(Integer left, Integer right) { + this.left = left; + this.right = right; + } + + @Nonnull + public Pair toPair() { + return Pair.of(this.left, this.right); + } + + @Nonnull + public static PairCodec.IntegerPair fromPair(@Nonnull Pair pair) { + return new PairCodec.IntegerPair(pair.left(), pair.right()); + } + + public Integer getLeft() { + return this.left; + } + + public Integer getRight() { + return this.right; + } + } + + public static class IntegerStringPair { + public static final BuilderCodec CODEC = BuilderCodec.builder( + PairCodec.IntegerStringPair.class, PairCodec.IntegerStringPair::new + ) + .append(new KeyedCodec<>("Left", Codec.INTEGER), (pair, left) -> pair.left = left, pair -> pair.left) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Right", Codec.STRING), (pair, right) -> pair.right = right, pair -> pair.right) + .addValidator(Validators.nonNull()) + .add() + .build(); + private Integer left; + private String right; + + public IntegerStringPair() { + } + + public IntegerStringPair(Integer left, String right) { + this.left = left; + this.right = right; + } + + @Nonnull + public Pair toPair() { + return Pair.of(this.left, this.right); + } + + @Nonnull + public static PairCodec.IntegerStringPair fromPair(@Nonnull Pair pair) { + return new PairCodec.IntegerStringPair(pair.left(), pair.right()); + } + + public Integer getLeft() { + return this.left; + } + + public String getRight() { + return this.right; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/codec/ProtocolCodecs.java b/src/com/hypixel/hytale/server/core/codec/ProtocolCodecs.java new file mode 100644 index 0000000..e5eeafb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/codec/ProtocolCodecs.java @@ -0,0 +1,284 @@ +package com.hypixel.hytale.server.core.codec; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.schema.metadata.HytaleType; +import com.hypixel.hytale.codec.schema.metadata.ui.UIDisplayMode; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.protocol.AccumulationMode; +import com.hypixel.hytale.protocol.ChangeStatBehaviour; +import com.hypixel.hytale.protocol.ChangeVelocityType; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.ColorLight; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.EasingType; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.InitialVelocity; +import com.hypixel.hytale.protocol.IntersectionHighlight; +import com.hypixel.hytale.protocol.ItemAnimation; +import com.hypixel.hytale.protocol.RailConfig; +import com.hypixel.hytale.protocol.RailPoint; +import com.hypixel.hytale.protocol.Range; +import com.hypixel.hytale.protocol.RangeVector2f; +import com.hypixel.hytale.protocol.RangeVector3f; +import com.hypixel.hytale.protocol.Rangeb; +import com.hypixel.hytale.protocol.Rangef; +import com.hypixel.hytale.protocol.SavedMovementStates; +import com.hypixel.hytale.protocol.Size; +import com.hypixel.hytale.protocol.UVMotion; +import com.hypixel.hytale.protocol.UVMotionCurveType; +import com.hypixel.hytale.protocol.Vector2f; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.protocol.packets.worldmap.ContextMenuItem; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.asset.common.BlockyAnimationCache; +import com.hypixel.hytale.server.core.asset.common.CommonAssetValidator; +import com.hypixel.hytale.server.core.asset.util.ColorParseUtil; +import com.hypixel.hytale.server.core.codec.protocol.ColorAlphaCodec; +import com.hypixel.hytale.server.core.codec.protocol.ColorCodec; +import com.hypixel.hytale.server.core.util.PositionUtil; + +public final class ProtocolCodecs { + public static final BuilderCodec DIRECTION = BuilderCodec.builder(Direction.class, Direction::new) + .metadata(UIDisplayMode.COMPACT) + .appendInherited(new KeyedCodec<>("Yaw", Codec.FLOAT), (o, i) -> o.yaw = i, o -> o.yaw, (o, p) -> o.yaw = p.yaw) + .add() + .appendInherited(new KeyedCodec<>("Pitch", Codec.FLOAT), (o, i) -> o.pitch = i, o -> o.pitch, (o, p) -> o.pitch = p.pitch) + .add() + .appendInherited(new KeyedCodec<>("Roll", Codec.FLOAT), (o, i) -> o.roll = i, o -> o.roll, (o, p) -> o.roll = p.roll) + .add() + .build(); + public static final BuilderCodec VECTOR2F = BuilderCodec.builder(Vector2f.class, Vector2f::new) + .metadata(UIDisplayMode.COMPACT) + .appendInherited(new KeyedCodec<>("X", Codec.FLOAT), (o, i) -> o.x = i, o -> o.x, (o, p) -> o.x = p.x) + .add() + .appendInherited(new KeyedCodec<>("Y", Codec.FLOAT), (o, i) -> o.y = i, o -> o.y, (o, p) -> o.y = p.y) + .add() + .build(); + public static final BuilderCodec VECTOR3F = BuilderCodec.builder(Vector3f.class, Vector3f::new) + .metadata(UIDisplayMode.COMPACT) + .appendInherited(new KeyedCodec<>("X", Codec.FLOAT), (o, i) -> o.x = i, o -> o.x, (o, p) -> o.x = p.x) + .add() + .appendInherited(new KeyedCodec<>("Y", Codec.FLOAT), (o, i) -> o.y = i, o -> o.y, (o, p) -> o.y = p.y) + .add() + .appendInherited(new KeyedCodec<>("Z", Codec.FLOAT), (o, i) -> o.z = i, o -> o.z, (o, p) -> o.z = p.z) + .add() + .build(); + public static final BuilderCodec COLOR_LIGHT = BuilderCodec.builder(ColorLight.class, ColorLight::new) + .appendInherited(new KeyedCodec<>("Color", Codec.STRING), ColorParseUtil::hexStringToColorLightDirect, ColorParseUtil::colorLightToHexString, (o, p) -> { + o.red = p.red; + o.green = p.green; + o.blue = p.blue; + }) + .metadata(new HytaleType("ColorShort")) + .add() + .appendInherited(new KeyedCodec<>("Radius", Codec.BYTE), (o, i) -> o.radius = i, o -> o.radius, (o, p) -> o.radius = p.radius) + .add() + .build(); + public static final ColorCodec COLOR = new ColorCodec(); + public static final ArrayCodec COLOR_ARRAY = new ArrayCodec<>(COLOR, Color[]::new); + public static final ColorAlphaCodec COLOR_AlPHA = new ColorAlphaCodec(); + public static final EnumCodec GAMEMODE = new EnumCodec<>(GameMode.class) + .documentKey(GameMode.Creative, "Makes the player invulnerable and grants them the ability to fly.") + .documentKey(GameMode.Adventure, "The normal gamemode for players playing the game."); + public static final EnumCodec GAMEMODE_LEGACY = new EnumCodec<>(GameMode.class, EnumCodec.EnumStyle.LEGACY); + public static final BuilderCodec SIZE = BuilderCodec.builder(Size.class, Size::new) + .metadata(UIDisplayMode.COMPACT) + .addField(new KeyedCodec<>("Width", Codec.INTEGER), (size, i) -> size.width = i, size -> size.width) + .addField(new KeyedCodec<>("Height", Codec.INTEGER), (size, i) -> size.height = i, size -> size.height) + .build(); + public static final BuilderCodec RANGE = BuilderCodec.builder(Range.class, Range::new) + .metadata(UIDisplayMode.COMPACT) + .addField(new KeyedCodec<>("Min", Codec.INTEGER), (rangeb, i) -> rangeb.min = i, rangeb -> rangeb.min) + .addField(new KeyedCodec<>("Max", Codec.INTEGER), (rangeb, i) -> rangeb.max = i, rangeb -> rangeb.max) + .build(); + public static final BuilderCodec RANGEB = BuilderCodec.builder(Rangeb.class, Rangeb::new) + .metadata(UIDisplayMode.COMPACT) + .addField(new KeyedCodec<>("Min", Codec.BYTE), (rangeb, i) -> rangeb.min = i, rangeb -> rangeb.min) + .addField(new KeyedCodec<>("Max", Codec.BYTE), (rangeb, i) -> rangeb.max = i, rangeb -> rangeb.max) + .build(); + public static final BuilderCodec RANGEF = BuilderCodec.builder(Rangef.class, Rangef::new) + .metadata(UIDisplayMode.COMPACT) + .addField(new KeyedCodec<>("Min", Codec.DOUBLE), (rangef, d) -> rangef.min = d.floatValue(), rangeb -> (double)rangeb.min) + .addField(new KeyedCodec<>("Max", Codec.DOUBLE), (rangef, d) -> rangef.max = d.floatValue(), rangeb -> (double)rangeb.max) + .build(); + public static final BuilderCodec RANGE_VECTOR2F = BuilderCodec.builder(RangeVector2f.class, RangeVector2f::new) + .addField(new KeyedCodec<>("X", RANGEF), (rangeVector2f, d) -> rangeVector2f.x = d, rangeVector2f -> rangeVector2f.x) + .addField(new KeyedCodec<>("Y", RANGEF), (rangeVector2f, d) -> rangeVector2f.y = d, rangeVector2f -> rangeVector2f.y) + .build(); + public static final BuilderCodec RANGE_VECTOR3F = BuilderCodec.builder(RangeVector3f.class, RangeVector3f::new) + .addField(new KeyedCodec<>("X", RANGEF), (rangeVector3f, d) -> rangeVector3f.x = d, rangeVector3f -> rangeVector3f.x) + .addField(new KeyedCodec<>("Y", RANGEF), (rangeVector3f, d) -> rangeVector3f.y = d, rangeVector3f -> rangeVector3f.y) + .addField(new KeyedCodec<>("Z", RANGEF), (rangeVector3f, d) -> rangeVector3f.z = d, rangeVector3f -> rangeVector3f.z) + .build(); + public static final BuilderCodec INITIAL_VELOCITY = BuilderCodec.builder(InitialVelocity.class, InitialVelocity::new) + .addField(new KeyedCodec<>("Yaw", RANGEF), (rangeVector3f, d) -> rangeVector3f.yaw = d, rangeVector3f -> rangeVector3f.yaw) + .addField(new KeyedCodec<>("Pitch", RANGEF), (rangeVector3f, d) -> rangeVector3f.pitch = d, rangeVector3f -> rangeVector3f.pitch) + .addField(new KeyedCodec<>("Speed", RANGEF), (rangeVector3f, d) -> rangeVector3f.speed = d, rangeVector3f -> rangeVector3f.speed) + .build(); + public static final BuilderCodec UV_MOTION = BuilderCodec.builder(UVMotion.class, UVMotion::new) + .append(new KeyedCodec<>("Texture", Codec.STRING), (uvMotion, s) -> uvMotion.texture = s, uvMotion -> uvMotion.texture) + .addValidator(CommonAssetValidator.TEXTURE_PARTICLES) + .add() + .append(new KeyedCodec<>("AddRandomUVOffset", Codec.BOOLEAN), (uvMotion, b) -> uvMotion.addRandomUVOffset = b, uvMotion -> uvMotion.addRandomUVOffset) + .add() + .append(new KeyedCodec<>("SpeedX", Codec.DOUBLE), (uvMotion, s) -> uvMotion.speedX = s.floatValue(), uvMotion -> (double)uvMotion.speedX) + .addValidator(Validators.range(-10.0, 10.0)) + .add() + .append(new KeyedCodec<>("SpeedY", Codec.DOUBLE), (uvMotion, s) -> uvMotion.speedY = s.floatValue(), uvMotion -> (double)uvMotion.speedY) + .addValidator(Validators.range(-10.0, 10.0)) + .add() + .append(new KeyedCodec<>("Strength", Codec.DOUBLE), (uvMotion, s) -> uvMotion.strength = s.floatValue(), uvMotion -> (double)uvMotion.strength) + .addValidator(Validators.range(0.0, 50.0)) + .add() + .append( + new KeyedCodec<>("StrengthCurveType", new EnumCodec<>(UVMotionCurveType.class)), + (uvMotion, s) -> uvMotion.strengthCurveType = s, + uvMotion -> uvMotion.strengthCurveType + ) + .add() + .append(new KeyedCodec<>("Scale", Codec.DOUBLE), (uvMotion, s) -> uvMotion.scale = s.floatValue(), uvMotion -> (double)uvMotion.scale) + .addValidator(Validators.range(0.0, 10.0)) + .add() + .build(); + public static final BuilderCodec INTERSECTION_HIGHLIGHT = BuilderCodec.builder( + IntersectionHighlight.class, IntersectionHighlight::new + ) + .append( + new KeyedCodec<>("HighlightThreshold", Codec.FLOAT), + (intersectionHighlight, s) -> intersectionHighlight.highlightThreshold = s, + intersectionHighlight -> intersectionHighlight.highlightThreshold + ) + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .addField( + new KeyedCodec<>("HighlightColor", COLOR), + (intersectionHighlight, s) -> intersectionHighlight.highlightColor = s, + intersectionHighlight -> intersectionHighlight.highlightColor + ) + .build(); + public static final BuilderCodec SAVED_MOVEMENT_STATES = BuilderCodec.builder(SavedMovementStates.class, SavedMovementStates::new) + .addField(new KeyedCodec<>("Flying", Codec.BOOLEAN), (movementStates, flying) -> movementStates.flying = flying, movementStates -> movementStates.flying) + .build(); + public static final BuilderCodec CONTEXT_MENU_ITEM = BuilderCodec.builder(ContextMenuItem.class, ContextMenuItem::new) + .addField(new KeyedCodec<>("Name", Codec.STRING), (item, s) -> item.name = s, item -> item.name) + .addField(new KeyedCodec<>("Command", Codec.STRING), (item, s) -> item.command = s, item -> item.command) + .build(); + public static final ArrayCodec CONTEXT_MENU_ITEM_ARRAY = new ArrayCodec<>(CONTEXT_MENU_ITEM, ContextMenuItem[]::new); + public static final BuilderCodec MARKER = BuilderCodec.builder(MapMarker.class, MapMarker::new) + .addField(new KeyedCodec<>("Id", Codec.STRING), (marker, s) -> marker.id = s, marker -> marker.id) + .addField(new KeyedCodec<>("Name", Codec.STRING), (marker, s) -> marker.name = s, marker -> marker.name) + .addField(new KeyedCodec<>("Image", Codec.STRING), (marker, s) -> marker.markerImage = s, marker -> marker.markerImage) + .append( + new KeyedCodec<>("Transform", Transform.CODEC), + (marker, s) -> marker.transform = PositionUtil.toTransformPacket(s), + marker -> PositionUtil.toTransform(marker.transform) + ) + .addValidator(Validators.nonNull()) + .add() + .addField( + new KeyedCodec<>("ContextMenuItems", CONTEXT_MENU_ITEM_ARRAY), (marker, items) -> marker.contextMenuItems = items, marker -> marker.contextMenuItems + ) + .build(); + public static final ArrayCodec MARKER_ARRAY = new ArrayCodec<>(MARKER, MapMarker[]::new); + public static final BuilderCodec ITEM_ANIMATION_CODEC = BuilderCodec.builder(ItemAnimation.class, ItemAnimation::new) + .append(new KeyedCodec<>("ThirdPerson", Codec.STRING), (itemAnimation, s) -> itemAnimation.thirdPerson = s, itemAnimation -> itemAnimation.thirdPerson) + .addValidator(CommonAssetValidator.ANIMATION_ITEM_CHARACTER) + .add() + .append( + new KeyedCodec<>("ThirdPersonMoving", Codec.STRING), + (itemAnimation, s) -> itemAnimation.thirdPersonMoving = s, + itemAnimation -> itemAnimation.thirdPersonMoving + ) + .addValidator(CommonAssetValidator.ANIMATION_ITEM_CHARACTER) + .add() + .append( + new KeyedCodec<>("ThirdPersonFace", Codec.STRING), + (itemAnimation, s) -> itemAnimation.thirdPersonFace = s, + itemAnimation -> itemAnimation.thirdPersonFace + ) + .addValidator(CommonAssetValidator.ANIMATION_ITEM_CHARACTER) + .add() + .append( + new KeyedCodec<>("FirstPerson", Codec.STRING), (itemAnimation, s) -> itemAnimation.firstPerson = s, itemAnimation -> itemAnimation.firstPerson + ) + .addValidator(CommonAssetValidator.ANIMATION_ITEM_CHARACTER) + .add() + .append( + new KeyedCodec<>("FirstPersonOverride", Codec.STRING), + (itemAnimation, s) -> itemAnimation.firstPersonOverride = s, + itemAnimation -> itemAnimation.firstPersonOverride + ) + .addValidator(CommonAssetValidator.ANIMATION_ITEM_CHARACTER) + .add() + .addField( + new KeyedCodec<>("KeepPreviousFirstPersonAnimation", Codec.BOOLEAN), + (itemAnimation, s) -> itemAnimation.keepPreviousFirstPersonAnimation = s, + itemAnimation -> itemAnimation.keepPreviousFirstPersonAnimation + ) + .addField( + new KeyedCodec<>("Speed", Codec.DOUBLE), (itemAnimation, s) -> itemAnimation.speed = s.floatValue(), itemAnimation -> (double)itemAnimation.speed + ) + .addField( + new KeyedCodec<>("BlendingDuration", Codec.DOUBLE), + (itemAnimation, s) -> itemAnimation.blendingDuration = s.floatValue(), + itemAnimation -> (double)itemAnimation.blendingDuration + ) + .addField(new KeyedCodec<>("Looping", Codec.BOOLEAN), (itemAnimation, s) -> itemAnimation.looping = s, itemAnimation -> itemAnimation.looping) + .addField( + new KeyedCodec<>("ClipsGeometry", Codec.BOOLEAN), (itemAnimation, s) -> itemAnimation.clipsGeometry = s, itemAnimation -> itemAnimation.clipsGeometry + ) + .afterDecode(itemAnimation -> { + if (itemAnimation.firstPerson != null) { + BlockyAnimationCache.get(itemAnimation.firstPerson); + } + + if (itemAnimation.firstPersonOverride != null) { + BlockyAnimationCache.get(itemAnimation.firstPersonOverride); + } + }) + .build(); + public static final EnumCodec CHANGE_STAT_BEHAVIOUR_CODEC = new EnumCodec<>(ChangeStatBehaviour.class) + .documentKey(ChangeStatBehaviour.Add, "Adds the value to the stat") + .documentKey(ChangeStatBehaviour.Set, "Changes the stat to the given value"); + public static final EnumCodec ACCUMULATION_MODE_CODEC = new EnumCodec<>(AccumulationMode.class) + .documentKey(AccumulationMode.Set, "Set the current value to the new one") + .documentKey(AccumulationMode.Sum, "Add the new value to the current one") + .documentKey(AccumulationMode.Average, "Average the new value with current one"); + public static final EnumCodec EASING_TYPE_CODEC = new EnumCodec<>(EasingType.class); + public static final EnumCodec CHANGE_VELOCITY_TYPE_CODEC = new EnumCodec<>(ChangeVelocityType.class) + .documentKey(ChangeVelocityType.Add, "Adds the velocity to any existing velocity") + .documentKey(ChangeVelocityType.Set, "Changes the velocity to the given value. Overriding existing values."); + public static final BuilderCodec RAIL_POINT_CODEC = BuilderCodec.builder(RailPoint.class, RailPoint::new) + .appendInherited(new KeyedCodec<>("Point", VECTOR3F), (o, v) -> o.point = v, o -> o.point, (o, p) -> o.point = p.point) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Normal", VECTOR3F), (o, v) -> o.normal = v, o -> o.normal, (o, p) -> o.normal = p.normal) + .addValidator(Validators.nonNull()) + .add() + .afterDecode(o -> { + if (o.normal != null) { + com.hypixel.hytale.math.vector.Vector3f v = new com.hypixel.hytale.math.vector.Vector3f(o.normal.x, o.normal.y, o.normal.z); + v = v.normalize(); + o.normal.x = v.x; + o.normal.y = v.y; + o.normal.z = v.z; + } + }) + .build(); + public static final BuilderCodec RAIL_CONFIG_CODEC = BuilderCodec.builder(RailConfig.class, RailConfig::new) + .appendInherited( + new KeyedCodec<>("Points", new ArrayCodec<>(RAIL_POINT_CODEC, RailPoint[]::new)), (o, v) -> o.points = v, o -> o.points, (o, p) -> o.points = p.points + ) + .addValidator(Validators.nonNull()) + .addValidator(Validators.arraySizeRange(2, 16)) + .add() + .build(); + + public ProtocolCodecs() { + } +} diff --git a/src/com/hypixel/hytale/server/core/codec/ShapeCodecs.java b/src/com/hypixel/hytale/server/core/codec/ShapeCodecs.java new file mode 100644 index 0000000..7b94f82 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/codec/ShapeCodecs.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.core.codec; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.shape.Cylinder; +import com.hypixel.hytale.math.shape.Ellipsoid; +import com.hypixel.hytale.math.shape.OriginShape; +import com.hypixel.hytale.math.shape.Shape; +import com.hypixel.hytale.math.vector.Vector3d; + +public class ShapeCodecs { + public static final CodecMapCodec SHAPE = new CodecMapCodec<>(); + public static final BuilderCodec BOX = BuilderCodec.builder(Box.class, Box::new) + .addField(new KeyedCodec<>("Min", Vector3d.CODEC), (shape, min) -> shape.min.assign(min), shape -> shape.min) + .addField(new KeyedCodec<>("Max", Vector3d.CODEC), (shape, max) -> shape.max.assign(max), shape -> shape.max) + .build(); + public static final BuilderCodec ELLIPSOID = BuilderCodec.builder(Ellipsoid.class, Ellipsoid::new) + .addField(new KeyedCodec<>("RadiusX", Codec.DOUBLE), (shape, radius) -> shape.radiusX = radius, shape -> shape.radiusX) + .addField(new KeyedCodec<>("RadiusY", Codec.DOUBLE), (shape, radius) -> shape.radiusY = radius, shape -> shape.radiusY) + .addField(new KeyedCodec<>("RadiusZ", Codec.DOUBLE), (shape, radius) -> shape.radiusZ = radius, shape -> shape.radiusZ) + .addField(new KeyedCodec<>("Radius", Codec.DOUBLE), Ellipsoid::assign, shape -> null) + .build(); + public static final BuilderCodec CYLINDER = BuilderCodec.builder(Cylinder.class, Cylinder::new) + .addField(new KeyedCodec<>("Height", Codec.DOUBLE), (shape, height) -> shape.height = height, shape -> shape.height) + .addField(new KeyedCodec<>("RadiusX", Codec.DOUBLE), (shape, radiusX) -> shape.radiusX = radiusX, shape -> shape.radiusX) + .addField(new KeyedCodec<>("RadiusZ", Codec.DOUBLE), (shape, radiusZ) -> shape.radiusZ = radiusZ, shape -> shape.radiusZ) + .addField(new KeyedCodec<>("Radius", Codec.DOUBLE), Cylinder::assign, shape -> null) + .build(); + public static final BuilderCodec> ORIGIN_SHAPE = BuilderCodec.builder(OriginShape.class, OriginShape::new) + .addField(new KeyedCodec<>("Origin", Vector3d.CODEC), (shape, origin) -> shape.origin.assign(origin), shape -> shape.origin) + .addField(new KeyedCodec<>("Shape", SHAPE), (shape, childShape) -> shape.shape = (S)childShape, shape -> shape.shape) + .build(); + + public ShapeCodecs() { + } + + static { + SHAPE.register("Box", Box.class, BOX); + SHAPE.register("Ellipsoid", Ellipsoid.class, ELLIPSOID); + SHAPE.register("Cylinder", Cylinder.class, CYLINDER); + SHAPE.register("OriginShape", OriginShape.class, ORIGIN_SHAPE); + } +} diff --git a/src/com/hypixel/hytale/server/core/codec/WeightedMapCodec.java b/src/com/hypixel/hytale/server/core/codec/WeightedMapCodec.java new file mode 100644 index 0000000..8f5b8c2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/codec/WeightedMapCodec.java @@ -0,0 +1,101 @@ +package com.hypixel.hytale.server.core.codec; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.WrappedCodec; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.common.map.IWeightedElement; +import com.hypixel.hytale.common.map.IWeightedMap; +import com.hypixel.hytale.common.map.WeightedMap; +import java.io.IOException; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonValue; + +public class WeightedMapCodec implements Codec>, WrappedCodec { + private final Codec codec; + private final T[] emptyKeys; + + public WeightedMapCodec(Codec codec, T[] emptyKeys) { + this.codec = codec; + this.emptyKeys = emptyKeys; + } + + @Override + public Codec getChildCodec() { + return this.codec; + } + + public IWeightedMap decode(@Nonnull BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { + BsonArray array = bsonValue.asArray(); + WeightedMap.Builder mapBuilder = WeightedMap.builder(this.emptyKeys); + mapBuilder.ensureCapacity(array.size()); + + for (int i = 0; i < array.size(); i++) { + BsonValue value = array.get(i); + extraInfo.pushIntKey(i); + + try { + T element = this.codec.decode(value, extraInfo); + mapBuilder.put(element, element.getWeight()); + } catch (Exception var11) { + throw new CodecException("Failed to decode", value, extraInfo, var11); + } finally { + extraInfo.popKey(); + } + } + + return mapBuilder.build(); + } + + @Nonnull + public BsonValue encode(@Nonnull IWeightedMap map, ExtraInfo extraInfo) { + BsonArray array = new BsonArray(); + map.forEach(element -> array.add(this.codec.encode(element, extraInfo))); + return array; + } + + public IWeightedMap decodeJson(@Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { + reader.expect('['); + reader.consumeWhiteSpace(); + WeightedMap.Builder mapBuilder = WeightedMap.builder(this.emptyKeys); + if (reader.tryConsume(']')) { + return mapBuilder.build(); + } else { + int i = 0; + + while (true) { + extraInfo.pushIntKey(i, reader); + + try { + T element = this.codec.decodeJson(reader, extraInfo); + mapBuilder.put(element, element.getWeight()); + } catch (Exception var9) { + throw new CodecException("Failed to decode", reader, extraInfo, var9); + } finally { + extraInfo.popKey(); + } + + reader.consumeWhiteSpace(); + if (reader.tryConsumeOrExpect(']', ',')) { + return mapBuilder.build(); + } + + reader.consumeWhiteSpace(); + } + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ArraySchema s = new ArraySchema(); + s.setTitle("WeightedMap"); + s.setItem(context.refDefinition(this.codec)); + return s; + } +} diff --git a/src/com/hypixel/hytale/server/core/codec/protocol/ColorAlphaCodec.java b/src/com/hypixel/hytale/server/core/codec/protocol/ColorAlphaCodec.java new file mode 100644 index 0000000..bc38dc6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/codec/protocol/ColorAlphaCodec.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.server.core.codec.protocol; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.protocol.ColorAlpha; +import com.hypixel.hytale.server.core.asset.util.ColorParseUtil; +import java.io.IOException; +import javax.annotation.Nonnull; +import org.bson.BsonString; +import org.bson.BsonValue; + +public class ColorAlphaCodec implements Codec { + public ColorAlphaCodec() { + } + + @Nonnull + public BsonValue encode(ColorAlpha colorAlpha, ExtraInfo extraInfo) { + return new BsonString(ColorParseUtil.colorToHexAlphaString(colorAlpha)); + } + + @Nonnull + public ColorAlpha decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + ColorAlpha colorAlpha = ColorParseUtil.parseColorAlpha(bsonValue.asString().getValue()); + if (colorAlpha != null) { + return colorAlpha; + } else { + throw new CodecException("Invalid color format, expected: #RGBA, #RRGGBBAA, rgba(#RGB,A), rgba(#RRGGBB,A) or rgba(R,G,B,A)"); + } + } + + @Nonnull + public ColorAlpha decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + reader.expect('"'); + ColorAlpha colorAlpha = ColorParseUtil.readColorAlpha(reader); + reader.expect('"'); + if (colorAlpha != null) { + return colorAlpha; + } else { + throw new CodecException("Invalid color format, expected: #RGBA, #RRGGBBAA, rgba(#RGB,A), rgba(#RRGGBB,A) or rgba(R,G,B,A)"); + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + StringSchema hex = new StringSchema(); + hex.setPattern(ColorParseUtil.HEX_COLOR_PATTERN); + StringSchema hexAlpha = new StringSchema(); + hexAlpha.setPattern(ColorParseUtil.HEX_ALPHA_COLOR_PATTERN); + StringSchema rgbaHex = new StringSchema(); + rgbaHex.setPattern(ColorParseUtil.RGBA_HEX_COLOR_PATTERN); + StringSchema rgba = new StringSchema(); + rgba.setPattern(ColorParseUtil.RGBA_COLOR_PATTERN); + Schema s = Schema.anyOf(hex, hexAlpha, rgbaHex, rgba); + s.setTitle("Color RGBA"); + s.getHytale().setType("ColorAlpha"); + return s; + } +} diff --git a/src/com/hypixel/hytale/server/core/codec/protocol/ColorCodec.java b/src/com/hypixel/hytale/server/core/codec/protocol/ColorCodec.java new file mode 100644 index 0000000..33000b7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/codec/protocol/ColorCodec.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.server.core.codec.protocol; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.server.core.asset.util.ColorParseUtil; +import java.io.IOException; +import javax.annotation.Nonnull; +import org.bson.BsonString; +import org.bson.BsonValue; + +public class ColorCodec implements Codec { + public ColorCodec() { + } + + @Nonnull + public BsonValue encode(Color color, ExtraInfo extraInfo) { + return new BsonString(ColorParseUtil.colorToHexString(color)); + } + + @Nonnull + public Color decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + Color color = ColorParseUtil.parseColor(bsonValue.asString().getValue()); + if (color != null) { + return color; + } else { + throw new CodecException("Invalid color format, expected: #RGB, #RRGGBB or rgb(R,G,B)"); + } + } + + @Nonnull + public Color decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + reader.expect('"'); + Color color = ColorParseUtil.readColor(reader); + reader.expect('"'); + if (color != null) { + return color; + } else { + throw new CodecException("Invalid color format, expected: #RGB, #RRGGBB or rgb(R,G,B)"); + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + StringSchema hex = new StringSchema(); + hex.setPattern(ColorParseUtil.HEX_COLOR_PATTERN); + StringSchema rgb = new StringSchema(); + rgb.setPattern(ColorParseUtil.RGB_COLOR_PATTERN); + Schema s = Schema.anyOf(hex, rgb); + s.setTitle("Color RGB"); + s.getHytale().setType("Color"); + return s; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/AssetTagsCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/AssetTagsCommand.java new file mode 100644 index 0000000..6c02155 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/AssetTagsCommand.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.core.command.commands.debug; + +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.map.AssetMapWithIndexes; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import java.util.Set; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class AssetTagsCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_ASSETS_TAGS_TAG_NOT_FOUND = Message.translation("server.commands.assets.tags.tagNotFound"); + @Nonnull + private final RequiredArg classArg = this.withRequiredArg("class", "server.commands.assets.tags.class.desc", ArgTypes.STRING); + @Nonnull + private final RequiredArg tagArg = this.withRequiredArg("tag", "server.commands.assets.tags.tag.desc", ArgTypes.STRING); + + public AssetTagsCommand() { + super("tags", "server.commands.assets.tags.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + String assetClass = this.classArg.get(context); + String tag = this.tagArg.get(context); + int tagIndex = AssetRegistry.getTagIndex(tag); + if (tagIndex == Integer.MIN_VALUE) { + context.sendMessage(MESSAGE_COMMANDS_ASSETS_TAGS_TAG_NOT_FOUND.param("tag", tag)); + } else { + for (Entry, AssetStore> entry : AssetRegistry.getStoreMap().entrySet()) { + String simpleName = entry.getKey().getSimpleName(); + if (simpleName.equalsIgnoreCase(assetClass)) { + context.sendMessage(Message.translation("server.commands.assets.tags.assetsOfTypeWithTag").param("type", simpleName).param("tag", tag)); + AssetMap assetMap = entry.getValue().getAssetMap(); + Set keysForTag = assetMap.getKeysForTag(tagIndex).stream().map(Object::toString).map(Message::raw).collect(Collectors.toSet()); + context.sendMessage(MessageFormat.list(Message.translation("server.commands.assets.tags.assetKeys"), keysForTag)); + if (assetMap instanceof AssetMapWithIndexes assetMapWithIndexes) { + Set indexesForTag = assetMapWithIndexes.getIndexesForTag(tagIndex) + .intStream() + .mapToObj(Integer::toString) + .map(Message::raw) + .collect(Collectors.toSet()); + context.sendMessage(MessageFormat.list(Message.translation("server.commands.assets.tags.assetIndexes"), indexesForTag)); + } + break; + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/AssetsCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/AssetsCommand.java new file mode 100644 index 0000000..09d1e29 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/AssetsCommand.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.server.core.command.commands.debug; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class AssetsCommand extends AbstractCommandCollection { + public AssetsCommand() { + super("assets", "server.commands.assets.desc"); + this.addSubCommand(new AssetTagsCommand()); + this.addSubCommand(new AssetsDuplicatesCommand()); + this.addSubCommand(new AssetsCommand.AssetLongestAssetNameCommand()); + } + + public static class AssetLongestAssetNameCommand extends AbstractAsyncCommand { + public AssetLongestAssetNameCommand() { + super("longest", ""); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + return CompletableFuture.runAsync( + () -> { + for (Entry, AssetStore> e : AssetRegistry.getStoreMap().entrySet()) { + String longestName = ""; + + for (Object asset : e.getValue().getAssetMap().getAssetMap().keySet()) { + String name = e.getValue().transformKey(asset).toString(); + if (name.length() > longestName.length()) { + longestName = name; + } + } + + context.sendMessage( + Message.raw("Longest asset name for " + e.getKey().getSimpleName() + ": " + longestName + " (" + longestName.length() + " characters)") + ); + } + } + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/AssetsDuplicatesCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/AssetsDuplicatesCommand.java new file mode 100644 index 0000000..f5d3af0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/AssetsDuplicatesCommand.java @@ -0,0 +1,101 @@ +package com.hypixel.hytale.server.core.command.commands.debug; + +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.common.CommonAsset; +import com.hypixel.hytale.server.core.asset.common.CommonAssetRegistry; +import com.hypixel.hytale.server.core.asset.common.asset.FileCommonAsset; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class AssetsDuplicatesCommand extends AbstractAsyncCommand { + @Nonnull + private final FlagArg reverseFlag = this.withFlagArg("reverse", "server.commands.assets.duplicates.reverse.desc"); + + public AssetsDuplicatesCommand() { + super("duplicates", "server.commands.assets.duplicates.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + boolean reverse = this.reverseFlag.get(context); + List> futures = new ObjectArrayList<>(); + List duplicates = new ObjectArrayList<>(); + + for (Entry> entry : CommonAssetRegistry.getDuplicatedAssets().entrySet()) { + AssetsDuplicatesCommand.DuplicatedAssetInfo duplicateInfo = new AssetsDuplicatesCommand.DuplicatedAssetInfo(entry.getKey(), entry.getValue()); + duplicates.add(duplicateInfo); + futures.add(duplicateInfo.calculateTotalSize()); + } + + return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)) + .thenAccept( + aVoid -> { + duplicates.sort( + reverse ? AssetsDuplicatesCommand.DuplicatedAssetInfo.COMPARATOR_REVERSE : AssetsDuplicatesCommand.DuplicatedAssetInfo.COMPARATOR + ); + long totalWastedSpace = 0L; + + for (AssetsDuplicatesCommand.DuplicatedAssetInfo duplicateInfox : duplicates) { + Message header = Message.translation("server.commands.assets.duplicates.header") + .param("hash", duplicateInfox.hash) + .param("wastedBytes", FormatUtil.bytesToString(duplicateInfox.wastedSpace)); + Set duplicateAssets = duplicateInfox.assets + .stream() + .map(a -> a.pack() + ":" + a.asset().getName()) + .map(Message::raw) + .collect(Collectors.toSet()); + context.sendMessage(MessageFormat.list(header, duplicateAssets)); + totalWastedSpace += duplicateInfox.wastedSpace; + } + + context.sendMessage( + Message.translation("server.commands.assets.duplicates.total").param("wastedBytes", FormatUtil.bytesToString(totalWastedSpace)) + ); + } + ); + } + + public static class DuplicatedAssetInfo { + @Nonnull + public static final Comparator COMPARATOR = Comparator.comparingLong(o -> o.wastedSpace); + @Nonnull + public static final Comparator COMPARATOR_REVERSE = Collections.reverseOrder(COMPARATOR); + @Nonnull + final String hash; + @Nonnull + final List assets; + long wastedSpace; + + public DuplicatedAssetInfo(@Nonnull String hash, @Nonnull List assets) { + this.hash = hash; + this.assets = assets; + } + + @Nonnull + public CompletableFuture calculateTotalSize() { + CommonAsset commonAsset = this.assets.getFirst().asset(); + if (commonAsset instanceof FileCommonAsset fileCommonAsset) { + Path path = fileCommonAsset.getFile(); + return CompletableFuture.runAsync(SneakyThrow.sneakyRunnable(() -> this.wastedSpace = Files.size(path) * (this.assets.size() - 1))); + } else { + return commonAsset.getBlob().thenAccept(bytes -> this.wastedSpace = (long)bytes.length * (this.assets.size() - 1)); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/DebugPlayerPositionCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/DebugPlayerPositionCommand.java new file mode 100644 index 0000000..dd970c0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/DebugPlayerPositionCommand.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.server.core.command.commands.debug; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.PendingTeleport; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class DebugPlayerPositionCommand extends AbstractPlayerCommand { + public DebugPlayerPositionCommand() { + super("debugplayerposition", "server.commands.debugplayerposition.desc"); + } + + @Override + protected void execute( + @NonNullDecl CommandContext context, + @NonNullDecl Store store, + @NonNullDecl Ref ref, + @NonNullDecl PlayerRef playerRef, + @NonNullDecl World world + ) { + Transform transform = store.getComponent(ref, TransformComponent.getComponentType()).getTransform(); + Vector3f headRotation = store.getComponent(ref, HeadRotation.getComponentType()).getRotation(); + Teleport teleport = store.getComponent(ref, Teleport.getComponentType()); + PendingTeleport pendingTeleport = store.getComponent(ref, PendingTeleport.getComponentType()); + String teleportFmt = teleport == null ? "none" : fmtPos(teleport.getPosition()); + String pendingTeleportFmt = pendingTeleport == null ? "none" : fmtPos(pendingTeleport.getPosition()); + Message message = Message.translation("server.commands.debugplayerposition.result") + .param("bodyPosition", fmtPos(transform.getPosition())) + .param("bodyRotation", fmtRot(transform.getRotation())) + .param("headRotation", fmtRot(headRotation)) + .param("teleport", teleportFmt) + .param("pendingTeleport", pendingTeleportFmt); + playerRef.sendMessage(message); + Vector3f blue = new Vector3f(0.137F, 0.867F, 0.882F); + DebugUtils.addSphere(world, transform.getPosition(), blue, 0.5, 30.0F); + playerRef.sendMessage(Message.translation("server.commands.debugplayerposition.notify").color("#23DDE1")); + } + + private static String fmtPos(Vector3d vector) { + String fmt = "%.1f"; + return String.format("%.1f", vector.getX()) + ", " + String.format("%.1f", vector.getY()) + ", " + String.format("%.1f", vector.getZ()); + } + + private static String fmtRot(Vector3f vector) { + return "Pitch=" + fmtDegrees(vector.getPitch()) + ", Yaw=" + fmtDegrees(vector.getYaw()) + ", Roll=" + fmtDegrees(vector.getRoll()); + } + + private static String fmtDegrees(float radians) { + return String.format("%.1f", Math.toDegrees(radians)) + "\u00b0"; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/HitDetectionCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/HitDetectionCommand.java new file mode 100644 index 0000000..1db5a3a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/HitDetectionCommand.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.server.core.command.commands.debug; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.SelectInteraction; +import javax.annotation.Nonnull; + +public class HitDetectionCommand extends CommandBase { + public HitDetectionCommand() { + super("hitdetection", "server.commands.hitdetection.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + SelectInteraction.SHOW_VISUAL_DEBUG = !SelectInteraction.SHOW_VISUAL_DEBUG; + context.sendMessage(Message.translation("server.commands.hitdetection.toggled").param("debug", SelectInteraction.SHOW_VISUAL_DEBUG)); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/HudManagerTestCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/HudManagerTestCommand.java new file mode 100644 index 0000000..c2b5270 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/HudManagerTestCommand.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.server.core.command.commands.debug; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.HudComponent; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.hud.HudManager; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HudManagerTestCommand extends AbstractTargetPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_HUD_TEST_SHOWN_SELF = Message.translation("server.commands.hudtest.shown.self"); + @Nonnull + private static final Message MESSAGE_COMMANDS_HUT_TEST_HIDDEN_SELF = Message.translation("server.commands.hudtest.hidden.self"); + @Nonnull + private final FlagArg resetHudFlag = this.withFlagArg("reset", "server.commands.hudtest.reset.desc"); + + public HudManagerTestCommand() { + super("hudtest", "server.commands.hudtest.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + HudManager hudManager = playerComponent.getHudManager(); + boolean isTargetingOther = !ref.equals(sourceRef); + if (this.resetHudFlag.provided(context)) { + hudManager.showHudComponents(playerRef, HudComponent.Hotbar); + if (isTargetingOther) { + context.sendMessage(Message.translation("server.commands.hudtest.shown.other").param("username", playerRefComponent.getUsername())); + } else { + context.sendMessage(MESSAGE_COMMANDS_HUD_TEST_SHOWN_SELF); + } + } else { + hudManager.hideHudComponents(playerRef, HudComponent.Hotbar); + if (isTargetingOther) { + context.sendMessage(Message.translation("server.commands.hudtest.hidden.other").param("username", playerRefComponent.getUsername())); + } else { + context.sendMessage(MESSAGE_COMMANDS_HUT_TEST_HIDDEN_SELF); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/LogCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/LogCommand.java new file mode 100644 index 0000000..784efdc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/LogCommand.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.server.core.command.commands.debug; + +import com.hypixel.hytale.logger.backend.HytaleLoggerBackend; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.SingleArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Arrays; +import java.util.Map; +import java.util.logging.Level; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class LogCommand extends CommandBase { + @Nonnull + private static final Level[] STANDARD_LEVELS = new Level[]{ + Level.OFF, Level.SEVERE, Level.WARNING, Level.INFO, Level.CONFIG, Level.FINE, Level.FINER, Level.FINEST, Level.ALL + }; + @Nonnull + private static final String LEVELS_STRING = Arrays.stream(STANDARD_LEVELS).map(Level::getName).collect(Collectors.joining(", ")); + @Nonnull + private static final SingleArgumentType LOG_LEVEL = new SingleArgumentType( + "server.commands.parsing.argtype.logLevel.name", + Message.translation("server.commands.parsing.argtype.logLevel.usage").param("levels", LEVELS_STRING), + Arrays.stream(STANDARD_LEVELS).map(Level::getName).toArray(String[]::new) + ) { + @Nonnull + public Level parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + try { + return Level.parse(input.toUpperCase()); + } catch (IllegalArgumentException var4) { + parseResult.fail(Message.translation("server.commands.log.invalidLevel").param("input", input).param("level", Level.INFO.getName())); + return Level.INFO; + } + } + }; + @Nonnull + private final RequiredArg loggerArg = this.withRequiredArg("logger", "server.commands.log.logger.desc", ArgTypes.STRING); + @Nonnull + private final OptionalArg levelArg = this.withOptionalArg("level", "server.commands.log.level.desc", LOG_LEVEL); + @Nonnull + private final FlagArg saveFlag = this.withFlagArg("save", "server.commands.log.save.desc"); + @Nonnull + private final FlagArg resetFlag = this.withFlagArg("reset", "server.commands.log.reset.desc"); + + public LogCommand() { + super("log", "server.commands.log.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + String loggerName = this.loggerArg.get(context); + HytaleLoggerBackend logger; + if (loggerName.equalsIgnoreCase("global")) { + loggerName = "global"; + logger = HytaleLoggerBackend.getLogger(); + } else { + logger = HytaleLoggerBackend.getLogger(loggerName); + } + + if (this.levelArg.provided(context)) { + Level level = this.levelArg.get(context); + logger.setLevel(level); + boolean saved = false; + if (this.saveFlag.get(context)) { + Map logLevels = new Object2ObjectOpenHashMap<>(HytaleServer.get().getConfig().getLogLevels()); + logLevels.put(logger.getLoggerName(), level); + HytaleServer.get().getConfig().setLogLevels(logLevels); + saved = true; + } + + context.sendMessage( + Message.translation("server.commands.log.setLogger") + .param("name", loggerName) + .param("level", level.getName()) + .param("saved", saved ? " and saved to config!!" : "") + ); + } else { + if (this.resetFlag.get(context)) { + Map logLevels = new Object2ObjectOpenHashMap<>(HytaleServer.get().getConfig().getLogLevels()); + logLevels.remove(logger.getLoggerName()); + HytaleServer.get().getConfig().setLogLevels(logLevels); + context.sendMessage(Message.translation("server.commands.log.removedLogger").param("name", loggerName)); + } + + context.sendMessage(Message.translation("server.commands.log.setLoggerNoSave").param("name", loggerName).param("level", logger.getLevel().getName())); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/MessageTranslationTestCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/MessageTranslationTestCommand.java new file mode 100644 index 0000000..914faa9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/MessageTranslationTestCommand.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.server.core.command.commands.debug; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import javax.annotation.Nonnull; + +public class MessageTranslationTestCommand extends CommandBase { + public MessageTranslationTestCommand() { + super("messagetest", "Test sending messages with nested translated parameter messages"); + this.addAliases("msgtest"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + Message param = Message.translation("server.commands.message.container") + .param("content", Message.translation("server.commands.message.example").param("random", 25)); + context.sender().sendMessage(param); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/PIDCheckCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/PIDCheckCommand.java new file mode 100644 index 0000000..b2f2d2a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/PIDCheckCommand.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.core.command.commands.debug; + +import com.hypixel.hytale.server.core.Constants; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.util.ProcessUtil; +import javax.annotation.Nonnull; + +public class PIDCheckCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_PID_CHECK_SINGLEPLAYER_ONLY = Message.translation("server.commands.pidcheck.singlePlayerOnly"); + @Nonnull + private final FlagArg singleplayerFlag = this.withFlagArg("singleplayer", "server.commands.pidcheck.singleplayer.desc"); + @Nonnull + private final OptionalArg pidArg = this.withOptionalArg("pid", "server.commands.pidcheck.pid.desc", ArgTypes.INTEGER); + + public PIDCheckCommand() { + super("pidcheck", "server.commands.pidcheck.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + if (this.singleplayerFlag.get(context)) { + if (!Constants.SINGLEPLAYER) { + context.sendMessage(MESSAGE_COMMANDS_PID_CHECK_SINGLEPLAYER_ONLY); + } else { + int pid = Options.getOptionSet().valueOf(Options.CLIENT_PID); + Message runningMessage = Message.translation( + ProcessUtil.isProcessRunning(pid) ? "server.commands.pidcheck.isRunning" : "server.commands.pidcheck.isNotRunning" + ); + context.sendMessage(Message.translation("server.commands.pidcheck.clientPIDRunning").param("pid", pid).param("running", runningMessage)); + } + } else if (!this.pidArg.provided(context)) { + context.sendMessage(Message.translation("server.commands.pidcheck.pidRequired")); + } else { + int pid = this.pidArg.get(context); + Message runningMessage = Message.translation( + ProcessUtil.isProcessRunning(pid) ? "server.commands.pidcheck.isRunning" : "server.commands.pidcheck.isNotRunning" + ); + context.sendMessage(Message.translation("server.commands.pidcheck.PIDRunning").param("pid", pid).param("running", runningMessage)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/PacketStatsCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/PacketStatsCommand.java new file mode 100644 index 0000000..bc6aecb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/PacketStatsCommand.java @@ -0,0 +1,149 @@ +package com.hypixel.hytale.server.core.command.commands.debug; + +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.io.PacketStatsRecorder; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PacketStatsCommand extends AbstractTargetPlayerCommand { + @Nonnull + private final RequiredArg packetArg = this.withRequiredArg("packet", "server.commands.packetStats.packet.desc", ArgTypes.STRING); + + public PacketStatsCommand() { + super("packetstats", "server.commands.packetStats.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + String packetName = this.packetArg.get(context); + PacketHandler packetHandler = playerRef.getPacketHandler(); + PacketStatsRecorder recorder = packetHandler.getPacketStatsRecorder(); + if (recorder == null) { + context.sendMessage(Message.translation("server.commands.packetStats.notAvailable")); + } else { + PacketStatsRecorder.PacketStatsEntry entry = findEntry(recorder, packetName); + if (entry == null) { + context.sendMessage(Message.translation("server.commands.packetStats.notFound").param("name", packetName)); + context.sendMessage( + Message.translation("server.general.failed.didYouMean") + .param("choices", StringUtil.sortByFuzzyDistance(packetName, getEntryNames(recorder), CommandUtil.RECOMMEND_COUNT).toString()) + ); + } else { + Message sentTotal = Message.translation("server.commands.packetStats.row") + .param("count", entry.getSentCount()) + .param( + "size", + FormatUtil.bytesToString(entry.getSentCompressedTotal()) + + " (" + + FormatUtil.bytesToString(entry.getSentUncompressedTotal()) + + " uncompressed)" + ) + .param("avg", FormatUtil.bytesToString((long)entry.getSentCompressedAvg())) + .param("min", FormatUtil.bytesToString(entry.getSentCompressedMin())) + .param("max", FormatUtil.bytesToString(entry.getSentCompressedMax())); + PacketStatsRecorder.RecentStats sentRecently = entry.getSentRecently(); + int sentRecentlyCount = sentRecently.count(); + Message sentRecent = Message.translation("server.commands.packetStats.row") + .param("count", sentRecentlyCount) + .param( + "size", + FormatUtil.bytesToString(sentRecently.compressedTotal()) + + " (" + + FormatUtil.bytesToString(sentRecently.uncompressedTotal()) + + " uncompressed)" + ) + .param("avg", FormatUtil.bytesToString(sentRecentlyCount > 0 ? sentRecently.compressedTotal() / sentRecentlyCount : 0L)) + .param("min", FormatUtil.bytesToString(sentRecently.compressedMin())) + .param("max", FormatUtil.bytesToString(sentRecently.compressedMax())); + Message receivedTotal = Message.translation("server.commands.packetStats.row") + .param("count", entry.getReceivedCount()) + .param( + "size", + FormatUtil.bytesToString(entry.getReceivedCompressedTotal()) + + " (" + + FormatUtil.bytesToString(entry.getReceivedUncompressedTotal()) + + " uncompressed)" + ) + .param("avg", FormatUtil.bytesToString((long)entry.getReceivedCompressedAvg())) + .param("min", FormatUtil.bytesToString(entry.getReceivedCompressedMin())) + .param("max", FormatUtil.bytesToString(entry.getReceivedCompressedMax())); + PacketStatsRecorder.RecentStats receivedRecently = entry.getReceivedRecently(); + int receivedRecentlyCount = receivedRecently.count(); + Message receivedRecent = Message.translation("server.commands.packetStats.row") + .param("count", receivedRecentlyCount) + .param( + "size", + FormatUtil.bytesToString(receivedRecently.compressedTotal()) + + " (" + + FormatUtil.bytesToString(receivedRecently.uncompressedTotal()) + + " uncompressed)" + ) + .param("avg", FormatUtil.bytesToString(receivedRecentlyCount > 0 ? receivedRecently.compressedTotal() / receivedRecentlyCount : 0L)) + .param("min", FormatUtil.bytesToString(receivedRecently.compressedMin())) + .param("max", FormatUtil.bytesToString(receivedRecently.compressedMax())); + context.sendMessage( + Message.translation("server.commands.packetStats.stats") + .param("name", entry.getName()) + .param("id", entry.getPacketId()) + .param("sentTotal", sentTotal) + .param("sentRecent", sentRecent) + .param("receivedTotal", receivedTotal) + .param("receivedRecent", receivedRecent) + ); + } + } + } + + @Nullable + private static PacketStatsRecorder.PacketStatsEntry findEntry(PacketStatsRecorder recorder, String name) { + for (int i = 0; i < 512; i++) { + PacketStatsRecorder.PacketStatsEntry entry = recorder.getEntry(i); + if (entry.hasData()) { + String entryName = entry.getName(); + if (entryName != null && name.equalsIgnoreCase(entryName)) { + return entry; + } + } + } + + return null; + } + + private static List getEntryNames(PacketStatsRecorder recorder) { + ObjectArrayList list = new ObjectArrayList<>(); + + for (int i = 0; i < 512; i++) { + PacketStatsRecorder.PacketStatsEntry entry = recorder.getEntry(i); + if (entry.hasData()) { + String name = entry.getName(); + if (name != null) { + list.add(name); + } + } + } + + return list; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/PingCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/PingCommand.java new file mode 100644 index 0000000..11ca792 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/PingCommand.java @@ -0,0 +1,183 @@ +package com.hypixel.hytale.server.core.command.commands.debug; + +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.metrics.metric.HistoricMetric; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.packets.connection.PongType; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PingCommand extends AbstractTargetPlayerCommand { + @Nonnull + private final FlagArg detailFlag = this.withFlagArg("detail", "server.commands.ping.detail.desc"); + + public PingCommand() { + super("ping", "server.commands.ping.desc"); + this.setPermissionGroup(GameMode.Adventure); + this.addSubCommand(new PingCommand.Clear()); + this.addSubCommand(new PingCommand.Graph()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + if (this.detailFlag.provided(context)) { + this.sendDetailedMessage(context, playerRef); + } else { + this.sendShortMessage(context, playerRef); + } + } + + private void sendDetailedMessage(@Nonnull CommandContext context, @Nonnull PlayerRef playerRef) { + Message msg = Message.join(Message.raw(playerRef.getUsername()), Message.raw(" ping:")); + + for (PongType pingType : PongType.values()) { + PacketHandler.PingInfo pingInfo = playerRef.getPacketHandler().getPingInfo(pingType); + HistoricMetric historicMetric = pingInfo.getPingMetricSet(); + long[] periods = historicMetric.getPeriodsNanos(); + msg.insert(Message.raw("\n" + pingType.name() + ":\n")); + + for (int i = 0; i < periods.length; i++) { + String length = FormatUtil.timeUnitToString(periods[i], TimeUnit.NANOSECONDS, true); + double average = historicMetric.getAverage(i); + long max = historicMetric.calculateMax(i); + long min = historicMetric.calculateMin(i); + String value = FormatUtil.simpleTimeUnitFormat(min, average, max, PacketHandler.PingInfo.TIME_UNIT, TimeUnit.MILLISECONDS, 3); + msg.insert(Message.raw(" (" + length + "): " + " ".repeat(Math.max(0, 24 - value.length())) + value + "\n")); + } + + msg.insert(Message.raw(" Queue: " + FormatUtil.simpleFormat(pingInfo.getPacketQueueMetric()))); + } + + context.sendMessage(msg); + } + + private void sendShortMessage(@Nonnull CommandContext context, @Nonnull PlayerRef playerRef) { + String length = FormatUtil.timeUnitToString(1L, TimeUnit.SECONDS, true); + Message msg = Message.join(Message.raw(playerRef.getUsername()), Message.raw(" ping (" + length + "):")); + + for (PongType pingType : PongType.values()) { + HistoricMetric historicMetric = playerRef.getPacketHandler().getPingInfo(pingType).getPingMetricSet(); + double average = historicMetric.getAverage(0); + long max = historicMetric.calculateMax(0); + long min = historicMetric.calculateMin(0); + String value = FormatUtil.simpleTimeUnitFormat(min, average, max, PacketHandler.PingInfo.TIME_UNIT, TimeUnit.MILLISECONDS, 3); + msg.insert(Message.raw("\n" + pingType.name() + ":" + " ".repeat(Math.max(0, 24 - value.length())) + value)); + } + + context.sendMessage(msg); + } + + private static class Clear extends AbstractTargetPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_PING_HISTORY_CLEARED = Message.translation("server.commands.ping.historyCleared"); + + public Clear() { + super("clear", "server.commands.ping.clear.desc"); + this.setPermissionGroup(GameMode.Adventure); + this.addAliases("reset"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + for (PongType pingType : PongType.values()) { + playerRef.getPacketHandler().getPingInfo(pingType).clear(); + } + + context.sendMessage(MESSAGE_COMMANDS_PING_HISTORY_CLEARED); + } + } + + private static class Graph extends AbstractTargetPlayerCommand { + @Nonnull + private final DefaultArg widthArg = this.withDefaultArg( + "width", "server.commands.ping.graph.width.desc", ArgTypes.INTEGER, 100, "server.commands.ping.graph.width.default" + ); + @Nonnull + private final DefaultArg heightArg = this.withDefaultArg( + "height", "server.commands.ping.graph.height.desc", ArgTypes.INTEGER, 10, "server.commands.ping.graph.height.default" + ); + + public Graph() { + super("graph", "server.commands.ping.graph.desc"); + this.setPermissionGroup(GameMode.Adventure); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + int width = this.widthArg.get(context); + int height = this.heightArg.get(context); + long startNanos = System.nanoTime(); + Message message = Message.empty(); + + for (PongType pingType : PongType.values()) { + message.insert(pingType + ":\n"); + PacketHandler.PingInfo pingInfo = playerRef.getPacketHandler().getPingInfo(pingType); + HistoricMetric pingMetricSet = pingInfo.getPingMetricSet(); + long[] periods = pingMetricSet.getPeriodsNanos(); + + for (int i = 0; i < periods.length; i++) { + long period = periods[i]; + long max = pingMetricSet.calculateMax(i); + long min = pingMetricSet.calculateMin(i); + long[] historyTimestamps = pingMetricSet.getTimestamps(i); + long[] historyValues = pingMetricSet.getValues(i); + String historyLengthFormatted = FormatUtil.timeUnitToString(period, TimeUnit.NANOSECONDS, true); + message.insert(Message.translation("server.commands.ping.graph.period").param("time", historyLengthFormatted)); + StringBuilder sb = new StringBuilder(); + StringUtil.generateGraph( + sb, + width, + height, + startNanos - period, + startNanos, + min, + max, + value -> FormatUtil.timeUnitToString(MathUtil.fastCeil(value), PacketHandler.PingInfo.TIME_UNIT), + historyTimestamps.length, + ii -> historyTimestamps[ii], + ii -> historyValues[ii] + ); + message.insert(sb.toString()); + } + } + + context.sendMessage(message); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/ShowBuilderToolsHudCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/ShowBuilderToolsHudCommand.java new file mode 100644 index 0000000..9d4a8c4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/ShowBuilderToolsHudCommand.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.server.core.command.commands.debug; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.packets.interface_.HudComponent; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.hud.HudManager; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ShowBuilderToolsHudCommand extends AbstractPlayerCommand { + @Nonnull + private final FlagArg hideArg = this.withFlagArg("hide", "server.commands.builderToolsLegend.hide.desc"); + + public ShowBuilderToolsHudCommand() { + super("builderToolsLegend", "server.commands.builderToolsLegend.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + HudManager hudManager = playerComponent.getHudManager(); + if (this.hideArg.provided(context)) { + hudManager.hideHudComponents(playerRef, HudComponent.BuilderToolsLegend); + hudManager.showHudComponents(playerRef, HudComponent.BuilderToolsMaterialSlotSelector); + } else { + hudManager.showHudComponents(playerRef, HudComponent.BuilderToolsLegend); + hudManager.showHudComponents(playerRef, HudComponent.BuilderToolsMaterialSlotSelector); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/StopNetworkChunkSendingCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/StopNetworkChunkSendingCommand.java new file mode 100644 index 0000000..e6104ed --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/StopNetworkChunkSendingCommand.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.core.command.commands.debug; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class StopNetworkChunkSendingCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg sendNetworkChunksArg = this.withRequiredArg( + "sendNetworkChunks", "Whether chunks should be sent over the network to yourself", ArgTypes.BOOLEAN + ); + + public StopNetworkChunkSendingCommand() { + super("networkChunkSending", "Stop sending chunks over the network"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + ChunkTracker chunkTrackerComponent = store.getComponent(ref, ChunkTracker.getComponentType()); + if (chunkTrackerComponent == null) { + playerRef.sendMessage(Message.translation("server.commands.networkChunkSending.noComponent")); + } else { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + chunkTrackerComponent.setReadyForChunks(this.sendNetworkChunksArg.get(context)); + playerRef.sendMessage( + Message.translation("server.commands.networkChunkSending.set") + .param("username", playerRefComponent.getUsername()) + .param("enabled", this.sendNetworkChunksArg.get(context)) + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/TagPatternCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/TagPatternCommand.java new file mode 100644 index 0000000..1331135 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/TagPatternCommand.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.server.core.command.commands.debug; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.tagpattern.config.TagPattern; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.AssetArgumentType; +import com.hypixel.hytale.server.core.command.system.arguments.types.SingleArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import javax.annotation.Nonnull; + +public class TagPatternCommand extends CommandBase { + @Nonnull + private static final SingleArgumentType TAG_PATTERN_ARG_TYPE = new AssetArgumentType( + "server.commands.parsing.argtype.asset.tagpattern.name", TagPattern.class, "server.commands.parsing.argtype.asset.tagpattern.usage" + ); + @Nonnull + private final RequiredArg tagPatternArg = this.withRequiredArg("tagPattern", "server.commands.tagpattern.tagPattern.desc", TAG_PATTERN_ARG_TYPE); + @Nonnull + private final RequiredArg blockTypeArg = this.withRequiredArg("blockType", "server.commands.tagpattern.blockType.desc", ArgTypes.BLOCK_TYPE_ASSET); + + public TagPatternCommand() { + super("tagpattern", "server.commands.tagpattern.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + TagPattern tagPattern = this.tagPatternArg.get(context); + BlockType blockType = this.blockTypeArg.get(context); + boolean result = tagPattern.test(blockType.getData().getTags()); + context.sendMessage( + (result ? Message.translation("server.commands.tagpattern.matches") : Message.translation("server.commands.tagpattern.noMatches")) + .param("blocktype", blockType.getId()) + .param("pattern", tagPattern.getId()) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/VersionCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/VersionCommand.java new file mode 100644 index 0000000..5694b45 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/VersionCommand.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.server.core.command.commands.debug; + +import com.hypixel.hytale.common.util.java.ManifestUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import javax.annotation.Nonnull; + +public class VersionCommand extends CommandBase { + private static final Message MESSAGE_RESPONSE = Message.translation("server.commands.version.response"); + private static final Message MESSAGE_RESPONSE_WITH_ENV = Message.translation("server.commands.version.response.withEnvironment"); + + public VersionCommand() { + super("version", "Displays version information about the currently running server"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + String version = ManifestUtil.getImplementationVersion(); + String patchline = ManifestUtil.getPatchline(); + if ("release".equals(patchline)) { + context.sendMessage(MESSAGE_RESPONSE.param("version", version).param("patchline", patchline)); + } else { + context.sendMessage(MESSAGE_RESPONSE_WITH_ENV.param("version", version).param("patchline", patchline).param("environment", "release")); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/component/hitboxcollision/HitboxCollisionAddCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/component/hitboxcollision/HitboxCollisionAddCommand.java new file mode 100644 index 0000000..86f4595 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/component/hitboxcollision/HitboxCollisionAddCommand.java @@ -0,0 +1,98 @@ +package com.hypixel.hytale.server.core.command.commands.debug.component.hitboxcollision; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.modules.entity.hitboxcollision.HitboxCollision; +import com.hypixel.hytale.server.core.modules.entity.hitboxcollision.HitboxCollisionConfig; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HitboxCollisionAddCommand extends AbstractCommandCollection { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_TARGET_NOT_IN_WORLD = Message.translation("server.commands.errors.targetNotInWorld"); + + public HitboxCollisionAddCommand() { + super("add", "server.commands.hitboxcollision.add.desc"); + this.addSubCommand(new HitboxCollisionAddCommand.HitboxCollisionAddEntityCommand()); + this.addSubCommand(new HitboxCollisionAddCommand.HitboxCollisionAddSelfCommand()); + } + + public static class HitboxCollisionAddEntityCommand extends AbstractWorldCommand { + @Nonnull + public static final Message MESSAGE_COMMANDS_HIT_BOX_COLLISION_ADD_ALREADY_ADDED = Message.translation("server.commands.hitboxcollision.add.alreadyAdded"); + @Nonnull + public static final Message MESSAGE_COMMANDS_HIT_BOX_COLLISION_ADD_SUCCESS = Message.translation("server.commands.hitboxcollision.add.success"); + @Nonnull + private final RequiredArg hitboxCollisionConfigArg = this.withRequiredArg( + "hitboxCollisionConfig", "server.commands.hitboxcollision.add.hitboxCollisionConfig.desc", ArgTypes.HITBOX_COLLISION_CONFIG + ); + @Nonnull + private final EntityWrappedArg entityArg = this.withOptionalArg("entity", "server.commands.hitboxcollision.add.entity.desc", ArgTypes.ENTITY_ID); + + public HitboxCollisionAddEntityCommand() { + super("entity", "server.commands.hitboxcollision.add.entity.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Ref entityRef = this.entityArg.get(store, context); + if (entityRef != null && entityRef.isValid()) { + HitboxCollisionConfig hitboxCollisionConfig = this.hitboxCollisionConfigArg.get(context); + if (store.getArchetype(entityRef).contains(HitboxCollision.getComponentType())) { + context.sendMessage(MESSAGE_COMMANDS_HIT_BOX_COLLISION_ADD_ALREADY_ADDED); + } else { + store.addComponent(entityRef, HitboxCollision.getComponentType(), new HitboxCollision(hitboxCollisionConfig)); + context.sendMessage(MESSAGE_COMMANDS_HIT_BOX_COLLISION_ADD_SUCCESS); + } + } else { + context.sendMessage(HitboxCollisionAddCommand.MESSAGE_COMMANDS_ERRORS_TARGET_NOT_IN_WORLD); + } + } + } + + public static class HitboxCollisionAddSelfCommand extends AbstractTargetPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_HIT_BOX_COLLISION_ADD_ALREADY_ADDED = Message.translation( + "server.commands.hitboxcollision.add.alreadyAdded" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_HIT_BOX_COLLISION_ADD_SUCCESS = Message.translation("server.commands.hitboxcollision.add.success"); + @Nonnull + private final RequiredArg hitboxCollisionConfigArg = this.withRequiredArg( + "hitboxCollisionConfig", "server.commands.hitboxcollision.add.hitboxCollisionConfig.desc", ArgTypes.HITBOX_COLLISION_CONFIG + ); + + public HitboxCollisionAddSelfCommand() { + super("self", "server.commands.hitboxcollision.add.self.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + if (store.getArchetype(ref).contains(HitboxCollision.getComponentType())) { + context.sendMessage(MESSAGE_COMMANDS_HIT_BOX_COLLISION_ADD_ALREADY_ADDED); + } else { + HitboxCollisionConfig hitboxCollisionConfig = this.hitboxCollisionConfigArg.get(context); + store.addComponent(ref, HitboxCollision.getComponentType(), new HitboxCollision(hitboxCollisionConfig)); + context.sendMessage(MESSAGE_COMMANDS_HIT_BOX_COLLISION_ADD_SUCCESS); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/component/hitboxcollision/HitboxCollisionCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/component/hitboxcollision/HitboxCollisionCommand.java new file mode 100644 index 0000000..fa241af --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/component/hitboxcollision/HitboxCollisionCommand.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.command.commands.debug.component.hitboxcollision; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class HitboxCollisionCommand extends AbstractCommandCollection { + public HitboxCollisionCommand() { + super("hitboxcollision", "server.commands.hitboxcollision.desc"); + this.addSubCommand(new HitboxCollisionAddCommand()); + this.addSubCommand(new HitboxCollisionRemoveCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/component/hitboxcollision/HitboxCollisionRemoveCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/component/hitboxcollision/HitboxCollisionRemoveCommand.java new file mode 100644 index 0000000..88fe843 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/component/hitboxcollision/HitboxCollisionRemoveCommand.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.server.core.command.commands.debug.component.hitboxcollision; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.modules.entity.hitboxcollision.HitboxCollision; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class HitboxCollisionRemoveCommand extends AbstractCommandCollection { + private static final Message MESSAGE_COMMANDS_ERRORS_TARGET_NOT_IN_WORLD = Message.translation("server.commands.errors.targetNotInWorld"); + + public HitboxCollisionRemoveCommand() { + super("remove", "server.commands.hitboxcollision.remove.desc"); + this.addSubCommand(new HitboxCollisionRemoveCommand.HitboxCollisionRemoveEntityCommand()); + this.addSubCommand(new HitboxCollisionRemoveCommand.HitboxCollisionRemoveSelfCommand()); + } + + public static class HitboxCollisionRemoveEntityCommand extends AbstractWorldCommand { + @Nonnull + private final EntityWrappedArg entityArg = this.withRequiredArg("entity", "server.commands.hitboxcollision.remove.entity.desc", ArgTypes.ENTITY_ID); + + public HitboxCollisionRemoveEntityCommand() { + super("entity", "server.commands.hitboxcollision.remove.entity.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Ref entityRef = this.entityArg.get(store, context); + if (entityRef != null && entityRef.isValid()) { + store.tryRemoveComponent(entityRef, HitboxCollision.getComponentType()); + context.sendMessage(Message.translation("server.commands.hitboxcollision.remove.success")); + } else { + context.sendMessage(HitboxCollisionRemoveCommand.MESSAGE_COMMANDS_ERRORS_TARGET_NOT_IN_WORLD); + } + } + } + + public static class HitboxCollisionRemoveSelfCommand extends AbstractPlayerCommand { + public HitboxCollisionRemoveSelfCommand() { + super("self", "server.commands.hitboxcollision.remove.self.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + store.tryRemoveComponent(ref, HitboxCollision.getComponentType()); + context.sendMessage(Message.translation("server.commands.hitboxcollision.remove.success")); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/component/repulsion/RepulsionAddCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/component/repulsion/RepulsionAddCommand.java new file mode 100644 index 0000000..fcfde07 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/component/repulsion/RepulsionAddCommand.java @@ -0,0 +1,95 @@ +package com.hypixel.hytale.server.core.command.commands.debug.component.repulsion; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.modules.entity.repulsion.Repulsion; +import com.hypixel.hytale.server.core.modules.entity.repulsion.RepulsionConfig; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RepulsionAddCommand extends AbstractCommandCollection { + private static final Message MESSAGE_COMMANDS_ERRORS_TARGET_NOT_IN_WORLD = Message.translation("server.commands.errors.targetNotInWorld"); + + public RepulsionAddCommand() { + super("add", "server.commands.repulsion.add.desc"); + this.addSubCommand(new RepulsionAddCommand.RepulsionAddEntityCommand()); + this.addSubCommand(new RepulsionAddCommand.RepulsionAddSelfCommand()); + } + + public static class RepulsionAddEntityCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_REPULSION_ADD_ALREADY_ADDED = Message.translation("server.commands.repulsion.add.alreadyAdded"); + @Nonnull + private static final Message COMMANDS_REPULSION_ADD_SUCCESS = Message.translation("server.commands.repulsion.add.success"); + @Nonnull + private final RequiredArg repulsionConfigArg = this.withRequiredArg( + "repulsionConfig", "server.commands.repulsion.add.repulsionConfig.desc", ArgTypes.REPULSION_CONFIG + ); + @Nonnull + private final EntityWrappedArg entityArg = this.withRequiredArg("entity", "server.commands.repulsion.add.entity.desc", ArgTypes.ENTITY_ID); + + public RepulsionAddEntityCommand() { + super("entity", "server.commands.repulsion.add.entity.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Ref entityRef = this.entityArg.get(store, context); + if (entityRef != null && entityRef.isValid()) { + RepulsionConfig repulsionConfig = this.repulsionConfigArg.get(context); + if (store.getArchetype(entityRef).contains(Repulsion.getComponentType())) { + context.sendMessage(MESSAGE_COMMANDS_REPULSION_ADD_ALREADY_ADDED); + } else { + store.addComponent(entityRef, Repulsion.getComponentType(), new Repulsion(repulsionConfig)); + context.sendMessage(COMMANDS_REPULSION_ADD_SUCCESS); + } + } else { + context.sendMessage(RepulsionAddCommand.MESSAGE_COMMANDS_ERRORS_TARGET_NOT_IN_WORLD); + } + } + } + + public static class RepulsionAddSelfCommand extends AbstractTargetPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_REPULSION_ADD_ALREADY_ADDED = Message.translation("server.commands.repulsion.add.alreadyAdded"); + @Nonnull + private static final Message MESSAGE_COMMANDS_REPULSION_ADD_SUCCESS = Message.translation("server.commands.repulsion.add.success"); + @Nonnull + private final RequiredArg repulsionConfigArg = this.withRequiredArg( + "repulsionConfig", "server.commands.repulsion.add.repulsionConfig.desc", ArgTypes.REPULSION_CONFIG + ); + + public RepulsionAddSelfCommand() { + super("self", "server.commands.repulsion.add.self.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + if (store.getArchetype(ref).contains(Repulsion.getComponentType())) { + context.sendMessage(MESSAGE_COMMANDS_REPULSION_ADD_ALREADY_ADDED); + } else { + RepulsionConfig repulsionConfig = this.repulsionConfigArg.get(context); + store.addComponent(ref, Repulsion.getComponentType(), new Repulsion(repulsionConfig)); + context.sendMessage(MESSAGE_COMMANDS_REPULSION_ADD_SUCCESS); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/component/repulsion/RepulsionCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/component/repulsion/RepulsionCommand.java new file mode 100644 index 0000000..7a57e43 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/component/repulsion/RepulsionCommand.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.command.commands.debug.component.repulsion; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class RepulsionCommand extends AbstractCommandCollection { + public RepulsionCommand() { + super("repulsion", "server.commands.repulsion.desc"); + this.addSubCommand(new RepulsionAddCommand()); + this.addSubCommand(new RepulsionRemoveCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/component/repulsion/RepulsionRemoveCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/component/repulsion/RepulsionRemoveCommand.java new file mode 100644 index 0000000..e207b8a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/component/repulsion/RepulsionRemoveCommand.java @@ -0,0 +1,72 @@ +package com.hypixel.hytale.server.core.command.commands.debug.component.repulsion; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.modules.entity.repulsion.Repulsion; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RepulsionRemoveCommand extends AbstractCommandCollection { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_TARGET_NOT_IN_WORLD = Message.translation("server.commands.errors.targetNotInWorld"); + + public RepulsionRemoveCommand() { + super("remove", "server.commands.repulsion.remove.desc"); + this.addSubCommand(new RepulsionRemoveCommand.RepulsionRemoveEntityCommand()); + this.addSubCommand(new RepulsionRemoveCommand.RepulsionRemoveSelfCommand()); + } + + public static class RepulsionRemoveEntityCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_REPULSION_REMOVE_SUCCESS = Message.translation("server.commands.repulsion.remove.success"); + @Nonnull + private final EntityWrappedArg entityArg = this.withRequiredArg("entity", "server.commands.repulsion.remove.entity.desc", ArgTypes.ENTITY_ID); + + public RepulsionRemoveEntityCommand() { + super("entity", "server.commands.repulsion.remove.entity.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Ref entityRef = this.entityArg.get(store, context); + if (entityRef != null && entityRef.isValid()) { + store.tryRemoveComponent(entityRef, Repulsion.getComponentType()); + context.sendMessage(MESSAGE_COMMANDS_REPULSION_REMOVE_SUCCESS); + } else { + context.sendMessage(RepulsionRemoveCommand.MESSAGE_COMMANDS_ERRORS_TARGET_NOT_IN_WORLD); + } + } + } + + public static class RepulsionRemoveSelfCommand extends AbstractTargetPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_REPULSION_REMOVE_SUCCESS = Message.translation("server.commands.repulsion.remove.success"); + + public RepulsionRemoveSelfCommand() { + super("self", "server.commands.repulsion.remove.self.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + store.tryRemoveComponent(ref, Repulsion.getComponentType()); + context.sendMessage(MESSAGE_COMMANDS_REPULSION_REMOVE_SUCCESS); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/packs/PacksCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/packs/PacksCommand.java new file mode 100644 index 0000000..d91244d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/packs/PacksCommand.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.core.command.commands.debug.packs; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class PacksCommand extends AbstractCommandCollection { + public PacksCommand() { + super("packs", "server.commands.packs.desc"); + this.addSubCommand(new PacksListCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/packs/PacksListCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/packs/PacksListCommand.java new file mode 100644 index 0000000..2623dca --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/packs/PacksListCommand.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.core.command.commands.debug.packs; + +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Comparator; +import java.util.List; +import javax.annotation.Nonnull; + +public class PacksListCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_PACKS_NOT_INITIALIZED = Message.translation("server.commands.packs.notInitialized"); + @Nonnull + private static final Message MESSAGE_PACKS_NONE_LOADED = Message.translation("server.commands.packs.noneLoaded"); + + public PacksListCommand() { + super("list", "server.commands.packs.list.desc"); + this.addAliases("ls"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + AssetModule assetModule = AssetModule.get(); + if (assetModule == null) { + context.sendMessage(MESSAGE_PACKS_NOT_INITIALIZED); + } else { + List assetPacks = assetModule.getAssetPacks(); + if (assetPacks.isEmpty()) { + context.sendMessage(MESSAGE_PACKS_NONE_LOADED); + } else { + ObjectArrayList packs = new ObjectArrayList<>(); + assetPacks.stream() + .sorted(Comparator.comparing(AssetPack::getName, String.CASE_INSENSITIVE_ORDER)) + .map(PacksListCommand::formatPack) + .forEach(packs::add); + context.sendMessage(MessageFormat.list(Message.translation("server.commands.packs.listHeader"), packs)); + } + } + } + + @Nonnull + private static Message formatPack(@Nonnull AssetPack pack) { + String name = pack.getName(); + String root = pack.getRoot() != null ? pack.getRoot().toString() : ""; + return Message.translation("server.commands.packs.listEntry").param("name", name).param("root", root); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerCommand.java new file mode 100644 index 0000000..c91b3eb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerCommand.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.core.command.commands.debug.server; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class ServerCommand extends AbstractCommandCollection { + public ServerCommand() { + super("server", "server.commands.server.desc"); + this.addSubCommand(new ServerStatsCommand()); + this.addSubCommand(new ServerGCCommand()); + this.addSubCommand(new ServerDumpCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerDumpCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerDumpCommand.java new file mode 100644 index 0000000..33b45aa --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerDumpCommand.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.server.core.command.commands.debug.server; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.util.DumpUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import java.io.IOException; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class ServerDumpCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_SERVER_DUMP_DUMPING_STATE = Message.translation("server.commands.server.dump.dumpingState"); + @Nonnull + private final FlagArg jsonFlag = this.withFlagArg("json", "server.commands.server.dump.json.desc"); + + public ServerDumpCommand() { + super("dump", "server.commands.server.dump.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + context.sendMessage(MESSAGE_COMMANDS_SERVER_DUMP_DUMPING_STATE); + if (this.jsonFlag.provided(context)) { + try { + Path path = DumpUtil.dumpToJson(); + context.sendMessage(Message.translation("server.commands.server.dump.finished").param("filepath", path.toAbsolutePath().toString())); + } catch (IOException var3) { + context.sendMessage(Message.translation("server.commands.server.dump.error").param("error", var3.getMessage())); + throw SneakyThrow.sneakyThrow(var3); + } + } else { + Path file = DumpUtil.dump(false, false); + context.sendMessage(Message.translation("server.commands.server.dump.finished").param("filepath", file.toAbsolutePath().toString())); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerGCCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerGCCommand.java new file mode 100644 index 0000000..6c94a94 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerGCCommand.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.core.command.commands.debug.server; + +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import javax.annotation.Nonnull; + +public class ServerGCCommand extends CommandBase { + public ServerGCCommand() { + super("gc", "server.commands.server.gc.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + long before = Runtime.getRuntime().freeMemory(); + System.gc(); + long after = Runtime.getRuntime().freeMemory(); + long freedBytes = before - after; + if (freedBytes >= 0L) { + context.sendMessage(Message.translation("server.commands.server.gc.forcedgc").param("bytes", FormatUtil.bytesToString(freedBytes))); + } else { + context.sendMessage(Message.translation("server.commands.server.gc.forcedgc.increased").param("bytes", FormatUtil.bytesToString(-freedBytes))); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerStatsCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerStatsCommand.java new file mode 100644 index 0000000..6e5b65e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerStatsCommand.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.core.command.commands.debug.server; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class ServerStatsCommand extends AbstractCommandCollection { + public ServerStatsCommand() { + super("stats", "server.commands.server.stats.desc"); + this.addSubCommand(new ServerStatsCpuCommand()); + this.addSubCommand(new ServerStatsMemoryCommand()); + this.addSubCommand(new ServerStatsGcCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerStatsCpuCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerStatsCpuCommand.java new file mode 100644 index 0000000..9296eb8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerStatsCpuCommand.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.server.core.command.commands.debug.server; + +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; +import java.lang.management.RuntimeMXBean; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + +public class ServerStatsCpuCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_SERVER_STATS_CPU_FULL_USAGE_INFO = Message.translation("server.commands.server.stats.cpu.fullUsageInfo"); + @Nonnull + private static final Message MESSAGE_COMMANDS_SERVER_STATS_FULL_INFO_UNAVAILABLE = Message.translation("server.commands.server.stats.fullInfoUnavailable"); + @Nonnull + private static final Message MESSAGE_COMMANDS_SERVER_STATS_CPU_USAGE_INFO = Message.translation("server.commands.server.stats.cpu.usageInfo"); + + public ServerStatsCpuCommand() { + super("cpu", "server.commands.server.stats.cpu.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); + OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean(); + if (operatingSystemMXBean instanceof com.sun.management.OperatingSystemMXBean sunOSBean) { + context.sendMessage( + MESSAGE_COMMANDS_SERVER_STATS_CPU_FULL_USAGE_INFO.param("systemLoad", sunOSBean.getSystemCpuLoad()) + .param("processLoad", sunOSBean.getProcessCpuLoad()) + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_SERVER_STATS_FULL_INFO_UNAVAILABLE); + } + + context.sendMessage( + MESSAGE_COMMANDS_SERVER_STATS_CPU_USAGE_INFO.param("loadAverage", operatingSystemMXBean.getSystemLoadAverage()) + .param("processUptime", FormatUtil.timeUnitToString(runtimeMXBean.getUptime(), TimeUnit.MILLISECONDS)) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerStatsGcCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerStatsGcCommand.java new file mode 100644 index 0000000..c531302 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerStatsGcCommand.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.core.command.commands.debug.server; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class ServerStatsGcCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_SERVER_STATS_GC_USAGE_INFO = Message.translation("server.commands.server.stats.gc.usageInfo"); + + public ServerStatsGcCommand() { + super("gc", "server.commands.server.stats.gc.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + for (GarbageCollectorMXBean garbageCollectorMXBean : ManagementFactory.getGarbageCollectorMXBeans()) { + context.sendMessage( + MESSAGE_COMMANDS_SERVER_STATS_GC_USAGE_INFO.param("name", garbageCollectorMXBean.getName()) + .param("poolNames", Arrays.toString((Object[])garbageCollectorMXBean.getMemoryPoolNames())) + .param("collectionCount", garbageCollectorMXBean.getCollectionCount()) + .param("collectionTime", garbageCollectorMXBean.getCollectionTime()) + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerStatsMemoryCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerStatsMemoryCommand.java new file mode 100644 index 0000000..db39587 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/server/ServerStatsMemoryCommand.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.core.command.commands.debug.server; + +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.sun.management.OperatingSystemMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryUsage; +import javax.annotation.Nonnull; + +public class ServerStatsMemoryCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_SERVER_STATS_MEMORY_FULL_USAGE_INFO = Message.translation("server.commands.server.stats.memory.fullUsageInfo"); + @Nonnull + private static final Message MESSAGE_COMMANDS_SERVER_STATS_FULL_INFO_UNAVAILABLE = Message.translation("server.commands.server.stats.fullInfoUnavailable"); + @Nonnull + private static final Message MESSAGE_COMMANDS_SERVER_STATS_MEMORY_USAGE_INFO = Message.translation("server.commands.server.stats.memory.usageInfo"); + + public ServerStatsMemoryCommand() { + super("memory", "server.commands.server.stats.memory.desc"); + this.addAliases("mem"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + if (ManagementFactory.getOperatingSystemMXBean() instanceof OperatingSystemMXBean sunOSBean) { + context.sendMessage( + MESSAGE_COMMANDS_SERVER_STATS_MEMORY_FULL_USAGE_INFO.param("totalPhysicalMemory", FormatUtil.bytesToString(sunOSBean.getTotalPhysicalMemorySize())) + .param("freePhysicalMemory", FormatUtil.bytesToString(sunOSBean.getFreePhysicalMemorySize())) + .param("totalSwapMemory", FormatUtil.bytesToString(sunOSBean.getTotalSwapSpaceSize())) + .param("freeSwapMemory", FormatUtil.bytesToString(sunOSBean.getFreeSwapSpaceSize())) + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_SERVER_STATS_FULL_INFO_UNAVAILABLE); + } + + MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + context.sendMessage( + MESSAGE_COMMANDS_SERVER_STATS_MEMORY_USAGE_INFO.param("heapMemoryUsage", formatMemoryUsage(memoryMXBean.getHeapMemoryUsage())) + .param("nonHeapMemoryUsage", formatMemoryUsage(memoryMXBean.getNonHeapMemoryUsage())) + .param("objectsPendingFinalizationCount", memoryMXBean.getObjectPendingFinalizationCount()) + ); + } + + @Nonnull + private static Message formatMemoryUsage(@Nonnull MemoryUsage memoryUsage) { + return Message.translation("server.commands.server.stats.memory.usage") + .param("init", FormatUtil.bytesToString(memoryUsage.getInit())) + .param("used", FormatUtil.bytesToString(memoryUsage.getUsed())) + .param("committed", FormatUtil.bytesToString(memoryUsage.getCommitted())) + .param("max", FormatUtil.bytesToString(memoryUsage.getMax())) + .param("free", FormatUtil.bytesToString(memoryUsage.getMax() - memoryUsage.getCommitted())); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/stresstest/Bot.java b/src/com/hypixel/hytale/server/core/command/commands/debug/stresstest/Bot.java new file mode 100644 index 0000000..5394531 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/stresstest/Bot.java @@ -0,0 +1,313 @@ +package com.hypixel.hytale.server.core.command.commands.debug.stresstest; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.Asset; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.EntityUpdate; +import com.hypixel.hytale.protocol.InstantData; +import com.hypixel.hytale.protocol.ModelTransform; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.Position; +import com.hypixel.hytale.protocol.TeleportAck; +import com.hypixel.hytale.protocol.io.netty.PacketDecoder; +import com.hypixel.hytale.protocol.io.netty.PacketEncoder; +import com.hypixel.hytale.protocol.packets.connection.ClientType; +import com.hypixel.hytale.protocol.packets.connection.Connect; +import com.hypixel.hytale.protocol.packets.connection.Disconnect; +import com.hypixel.hytale.protocol.packets.connection.DisconnectType; +import com.hypixel.hytale.protocol.packets.connection.Ping; +import com.hypixel.hytale.protocol.packets.connection.Pong; +import com.hypixel.hytale.protocol.packets.connection.PongType; +import com.hypixel.hytale.protocol.packets.entities.EntityUpdates; +import com.hypixel.hytale.protocol.packets.player.ClientMovement; +import com.hypixel.hytale.protocol.packets.player.ClientReady; +import com.hypixel.hytale.protocol.packets.player.ClientTeleport; +import com.hypixel.hytale.protocol.packets.player.SetClientId; +import com.hypixel.hytale.protocol.packets.setup.PlayerOptions; +import com.hypixel.hytale.protocol.packets.setup.RequestAssets; +import com.hypixel.hytale.protocol.packets.setup.ViewRadius; +import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule; +import com.hypixel.hytale.server.core.io.ServerManager; +import com.hypixel.hytale.server.core.io.netty.NettyUtil; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.util.PositionUtil; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollSocketChannel; +import io.netty.channel.kqueue.KQueue; +import io.netty.channel.kqueue.KQueueSocketChannel; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Bot extends SimpleChannelInboundHandler { + private static final ScheduledExecutorService EXECUTOR = Executors.newScheduledThreadPool(8); + private static final EventLoopGroup WORKER_GROUP = NettyUtil.getEventLoopGroup(8, "BotWorkerGroup"); + public static final Asset[] EMPTY_ASSET_ARRAY = new Asset[0]; + @Nonnull + private final HytaleLogger logger; + private final String name; + @Nonnull + private final BotConfig config; + @Nonnull + private final ScheduledFuture tickFuture; + private final ObjectArrayFIFOQueue pingPackets = new ObjectArrayFIFOQueue<>(); + private final MovementStates movementStates = new MovementStates(); + @Nullable + private SocketChannel channel; + private int id = -1; + private Vector3d pos; + private final Vector3f rotation = new Vector3f(); + private final Vector3d destination = new Vector3d(); + private final Vector3d temp = new Vector3d(); + private final Vector3f targetRotation = new Vector3f(); + + public Bot(String name, @Nonnull BotConfig config, int tickStepNanos) throws InterruptedException, SocketException { + this.logger = HytaleLogger.get(name); + this.name = name; + this.config = config; + this.destination.assign(config.spawn.getPosition()); + this.destination.y = ThreadLocalRandom.current().nextDouble(config.flyYHeight.getX(), config.flyYHeight.getY()); + InetSocketAddress address = ServerManager.get().getLocalOrPublicAddress(); + this.logger.at(Level.INFO).log("Booting Bot! Connecting to %s", address); + new Bootstrap() + .group(WORKER_GROUP) + .channel(Epoll.isAvailable() ? EpollSocketChannel.class : (KQueue.isAvailable() ? KQueueSocketChannel.class : NioSocketChannel.class)) + .option(ChannelOption.SO_REUSEADDR, Boolean.TRUE) + .handler(new ChannelInitializer() { + protected void initChannel(@Nonnull SocketChannel channel) { + Bot.this.channel = channel; + channel.pipeline().addLast("packetDecoder", new PacketDecoder()); + channel.pipeline().addLast("packetEncoder", new PacketEncoder()); + if (NettyUtil.PACKET_LOGGER.getLevel() != Level.OFF) { + channel.pipeline().addLast("logger", NettyUtil.LOGGER); + } + + channel.pipeline().addLast("handler", Bot.this); + } + }) + .connect(address) + .sync(); + float dt = (float)(tickStepNanos / 1.0E9); + this.tickFuture = EXECUTOR.scheduleAtFixedRate(() -> { + if (this.channel != null) { + try { + this.tick(dt); + } catch (Throwable var4x) { + this.logger.at(Level.SEVERE).withCause(var4x).log("Exception ticking %s", name); + } + } + }, tickStepNanos, tickStepNanos, TimeUnit.NANOSECONDS); + } + + public void shutdown() { + this.tickFuture.cancel(false); + if (this.channel != null && !this.channel.isShutdown()) { + try { + this.channel.shutdown().await(1L, TimeUnit.SECONDS); + this.channel = null; + } catch (InterruptedException var2) { + var2.printStackTrace(); + } + } + } + + public void tick(float dt) { + while (!this.pingPackets.isEmpty()) { + Ping packet = this.pingPackets.dequeue(); + this.channel.write(new Pong(packet.id, WorldTimeResource.instantToInstantData(Instant.now()), PongType.Tick, (short)0)); + } + + if (this.pos == null) { + this.channel.flush(); + } else { + double movementDistance = this.config.flySpeed * dt; + if (this.pos.distanceSquaredTo(this.destination) <= movementDistance * movementDistance) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + double randX = random.nextDouble(-this.config.radius, this.config.radius); + double randY = random.nextDouble(this.config.flyYHeight.getX(), this.config.flyYHeight.getY()); + double randZ = random.nextDouble(-this.config.radius, this.config.radius); + this.destination.assign(this.config.spawn.getPosition()); + this.destination.y = randY; + this.destination.add(randX, 0.0, randZ); + } + + this.temp.assign(this.destination).subtract(this.pos); + Vector3f.lookAt(this.temp, this.targetRotation); + Vector3f.lerpAngle(this.rotation, this.targetRotation, 0.3F, this.rotation); + this.temp.normalize(); + this.temp.scale(movementDistance); + this.pos.add(this.temp); + this.movementStates.flying = true; + this.channel.writeAndFlush(this.createMovementPacket()); + } + } + + @Override + public void channelActive(@Nonnull ChannelHandlerContext ctx) { + UUID uuid = UUID.nameUUIDFromBytes(("BOT|" + this.name).getBytes(StandardCharsets.UTF_8)); + ctx.writeAndFlush( + new Connect("6708f121966c1c443f4b0eb525b2f81d0a8dc61f5003a692a8fa157e5e02cea9", ClientType.Game, "en", null, uuid, this.name, null, null) + ); + this.logger.at(Level.INFO).log("Connected!"); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + this.logger.at(Level.INFO).log("Disconnected!"); + this.shutdown(); + StressTestStartCommand.BOTS.remove(this); + } + + @Override + public void exceptionCaught(@Nonnull ChannelHandlerContext ctx, @Nonnull Throwable cause) { + this.logger.at(Level.WARNING).withCause(cause).log("Got exception from netty pipeline"); + if (ctx.channel().isWritable()) { + ctx.channel().writeAndFlush(new Disconnect(cause.getMessage(), DisconnectType.Crash)).addListener(ChannelFutureListener.CLOSE); + } else { + ctx.channel().close(); + } + + this.shutdown(); + StressTestStartCommand.BOTS.remove(this); + } + + public void channelRead0(@Nonnull ChannelHandlerContext ctx, @Nonnull Packet packet) { + switch (packet.getId()) { + case 1: + this.logger.at(Level.INFO).log("Disconnected for: %s %s", ((Disconnect)packet).reason, ((Disconnect)packet).type); + ctx.close(); + break; + case 2: + Ping ping = (Ping)packet; + InstantData instantData = WorldTimeResource.instantToInstantData(Instant.now()); + ctx.write(new Pong(ping.id, instantData, PongType.Raw, (short)0)); + ctx.writeAndFlush(new Pong(ping.id, instantData, PongType.Direct, (short)0)); + this.pingPackets.enqueue(ping); + break; + case 20: + ctx.write(new RequestAssets(EMPTY_ASSET_ARRAY)); + ctx.write(new ViewRadius(this.config.viewRadius)); + ctx.writeAndFlush(new PlayerOptions(CosmeticsModule.get().generateRandomSkin(ThreadLocalRandom.current()))); + break; + case 100: + this.id = ((SetClientId)packet).clientId; + break; + case 104: + ctx.writeAndFlush(new ClientReady(true, this.id != -1)); + break; + case 109: + ClientTeleport clientTeleport = (ClientTeleport)packet; + ModelTransform modelTransform = clientTeleport.modelTransform; + if (modelTransform == null) { + return; + } + + this.updateModelTransform(modelTransform); + this.logger.at(Level.INFO).log("TP: %s (sending ack for teleportId: %s)", this.pos, clientTeleport.teleportId); + ClientMovement movement = this.createMovementPacket(); + movement.teleportAck = new TeleportAck(clientTeleport.teleportId); + ctx.writeAndFlush(movement); + break; + case 161: + EntityUpdates entityUpdates = (EntityUpdates)packet; + EntityUpdate entry = findEntityUpdate(entityUpdates, this.id); + if (entry == null) { + return; + } + + for (ComponentUpdate update : entry.updates) { + if (update.type == ComponentUpdateType.Transform) { + this.updateModelTransform(update.transform); + break; + } + } + } + } + + public void updateModelTransform(@Nonnull ModelTransform modelTransform) { + Position position = modelTransform.position; + if (position != null) { + if (this.pos == null) { + this.pos = new Vector3d(); + } + + this.pos.assign(position.x, position.y, position.z); + } + + Direction lookOrientation = modelTransform.lookOrientation; + if (lookOrientation != null) { + this.updateRotation(lookOrientation); + } + } + + public void updateRotation(@Nonnull Direction lookOrientation) { + if (!Float.isNaN(lookOrientation.yaw)) { + this.rotation.setYaw(lookOrientation.yaw); + } + + if (!Float.isNaN(lookOrientation.pitch)) { + this.rotation.setPitch(lookOrientation.pitch); + } + + if (!Float.isNaN(lookOrientation.roll)) { + this.rotation.setRoll(lookOrientation.roll); + } + } + + @Nonnull + public ClientMovement createMovementPacket() { + ClientMovement movement = new ClientMovement(); + movement.absolutePosition = PositionUtil.toPositionPacket(this.pos); + movement.lookOrientation = PositionUtil.toDirectionPacket(this.rotation); + movement.bodyOrientation = PositionUtil.toDirectionPacket(this.rotation); + movement.bodyOrientation.pitch = 0.0F; + movement.movementStates = this.movementStates; + return movement; + } + + @Nonnull + @Override + public String toString() { + return "Bot{name='" + this.name + "', id=" + this.id + "}"; + } + + @Nullable + public static EntityUpdate findEntityUpdate(@Nonnull EntityUpdates bulkList, int id) { + if (bulkList.updates == null) { + return null; + } else { + for (EntityUpdate otherEntry : bulkList.updates) { + if (otherEntry.networkId == id) { + return otherEntry; + } + } + + return null; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/stresstest/BotConfig.java b/src/com/hypixel/hytale/server/core/command/commands/debug/stresstest/BotConfig.java new file mode 100644 index 0000000..c66b72f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/stresstest/BotConfig.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.server.core.command.commands.debug.stresstest; + +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector2d; + +public class BotConfig { + public final double radius; + public final Vector2d flyYHeight; + public final double flySpeed; + public final Transform spawn; + public final int viewRadius; + + public BotConfig(double radius, Vector2d flyYHeight, double flySpeed, Transform spawn, int viewRadius) { + this.radius = radius; + this.flyYHeight = flyYHeight; + this.flySpeed = flySpeed; + this.spawn = spawn; + this.viewRadius = viewRadius; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/stresstest/StressTestCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/stresstest/StressTestCommand.java new file mode 100644 index 0000000..ceefaa6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/stresstest/StressTestCommand.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.command.commands.debug.stresstest; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class StressTestCommand extends AbstractCommandCollection { + public StressTestCommand() { + super("stresstest", "server.commands.stresstest.desc"); + this.addSubCommand(new StressTestStartCommand()); + this.addSubCommand(new StressTestStopCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/stresstest/StressTestStartCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/stresstest/StressTestStartCommand.java new file mode 100644 index 0000000..0ed78e5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/stresstest/StressTestStartCommand.java @@ -0,0 +1,360 @@ +package com.hypixel.hytale.server.core.command.commands.debug.stresstest; + +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.event.EventRegistration; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.metrics.metric.HistoricMetric; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.ShutdownReason; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncWorldCommand; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.event.events.player.AddPlayerToWorldEvent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.net.SocketException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StressTestStartCommand extends AbstractAsyncWorldCommand { + @Nonnull + protected static final AtomicReference STATE = new AtomicReference<>( + StressTestStartCommand.StressTestState.NOT_RUNNING + ); + @Nonnull + private static final String NAME_PREFIX = "bot-"; + @Nonnull + public static final List BOTS = Collections.synchronizedList(new ObjectArrayList<>()); + @Nonnull + private static final Message MESSAGE_COMMANDS_STRESS_TEST_ALREADY_STARTED = Message.translation("server.commands.stresstest.alreadyStarted"); + @Nonnull + private static final Message MESSAGE_COMMANDS_STRESS_TEST_STARTED = Message.translation("server.commands.stresstest.started"); + @Nullable + static StressTestStartCommand.DumpType DUMP_TYPE; + @Nullable + static Path DATE_PATH; + @Nullable + static EventRegistration EVENT_REGISTRATION; + @Nullable + static ScheduledFuture STRESS_TEST_BOT_TASK; + @Nullable + static ScheduledFuture STRESS_TEST_DUMP_TASK; + @Nonnull + private final OptionalArg nameArg = this.withOptionalArg("name", "server.commands.stresstest.start.name.desc", ArgTypes.STRING); + @Nonnull + private final DefaultArg initCountArg = this.withDefaultArg( + "initcount", "server.commands.stresstest.start.initcount.desc", ArgTypes.INTEGER, 0, "server.commands.stresstest.start.initcount.default" + ) + .addValidator(Validators.greaterThanOrEqual(0)); + @Nonnull + private final DefaultArg intervalArg = this.withDefaultArg( + "interval", "server.commands.stresstest.start.interval.desc", ArgTypes.DOUBLE, 30.0, "server.commands.stresstest.start.interval.default" + ) + .addValidator(Validators.greaterThan(0.0)); + @Nonnull + private final DefaultArg dumptypeArg = this.withDefaultArg( + "dumptype", + "server.commands.stresstest.start.dumptype.desc", + ArgTypes.forEnum("server.commands.parsing.argtype.enum.name", StressTestStartCommand.DumpType.class), + StressTestStartCommand.DumpType.INTERVAL, + "server.commands.stresstest.start.dumptype.default" + ); + @Nonnull + private final DefaultArg dumpintervalArg = this.withDefaultArg( + "dumpinterval", "server.commands.stresstest.start.dumpinterval.desc", ArgTypes.DOUBLE, 300.0, "server.commands.stresstest.start.dumpinterval.default" + ) + .addValidator(Validators.greaterThan(0.0)); + @Nonnull + private final OptionalArg thresholdArg = this.withOptionalArg("threshold", "server.commands.stresstest.start.threshold.desc", ArgTypes.DOUBLE) + .addValidator(Validators.greaterThan(0.0)); + @Nonnull + private final DefaultArg percentileArg = this.withDefaultArg( + "percentile", "server.commands.stresstest.start.percentile.desc", ArgTypes.DOUBLE, 0.95, "server.commands.stresstest.start.percentile.default" + ) + .addValidator(Validators.range(0.0, 1.0)); + @Nonnull + private final DefaultArg viewRadiusArg = this.withDefaultArg( + "viewradius", "server.commands.stresstest.start.viewradius.desc", ArgTypes.INTEGER, 192, "server.commands.stresstest.start.viewradius.default" + ) + .addValidator(Validators.greaterThanOrEqual(32)); + @Nonnull + private final DefaultArg radiusArg = this.withDefaultArg( + "radius", "server.commands.stresstest.start.radius.desc", ArgTypes.DOUBLE, 384.0, "server.commands.stresstest.start.radius.default" + ) + .addValidator(Validators.greaterThan(0.0)); + @Nonnull + private final DefaultArg yheightArg = this.withDefaultArg( + "yheight", "server.commands.stresstest.start.yheight.desc", ArgTypes.DOUBLE, 125.0, "server.commands.stresstest.start.yheight.default" + ); + @Nonnull + private final OptionalArg yheightMaxArg = this.withOptionalArg("yheightmax", "server.commands.stresstest.start.yheightmax.desc", ArgTypes.DOUBLE); + @Nonnull + private final DefaultArg flySpeedArg = this.withDefaultArg( + "flySpeed", "server.commands.stresstest.start.flySpeed.desc", ArgTypes.DOUBLE, 8.0, "server.commands.stresstest.start.flySpeed.default" + ) + .addValidator(Validators.greaterThan(0.0)); + @Nonnull + private final FlagArg shutdownFlag = this.withFlagArg("shutdown", "server.commands.stresstest.start.shutdown.desc"); + + public StressTestStartCommand() { + super("start", "server.commands.stresstest.start.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context, @Nonnull World world) { + ISpawnProvider spawnProvider = world.getWorldConfig().getSpawnProvider(); + Transform spawn = spawnProvider.getSpawnPoints()[0]; + String name = this.nameArg.provided(context) ? this.nameArg.get(context) : null; + if (name != null && !StringUtil.isAlphaNumericHyphenUnderscoreString(name)) { + context.sendMessage(Message.translation("server.commands.stresstest.invalidName").param("name", name)); + return CompletableFuture.completedFuture(null); + } else { + int viewRadius = this.viewRadiusArg.get(context); + double radius = this.radiusArg.get(context); + double yheight = this.yheightArg.get(context); + double yheightMax = this.yheightMaxArg.provided(context) ? this.yheightMaxArg.get(context) : yheight + 10.0; + double flySpeed = this.flySpeedArg.get(context); + BotConfig config = new BotConfig(radius, new Vector2d(yheight, yheightMax), flySpeed, spawn, viewRadius); + int initCount = this.initCountArg.get(context); + double interval = this.intervalArg.get(context); + StressTestStartCommand.DumpType dumpType = this.dumptypeArg.get(context); + double dumpInterval = this.dumpintervalArg.get(context); + int thresholdNanos = this.thresholdArg.provided(context) ? MathUtil.ceil(this.thresholdArg.get(context) * 1000000.0) : world.getTickStepNanos(); + double percentile = MathUtil.round(this.percentileArg.get(context), 4); + boolean shutdown = this.shutdownFlag.get(context); + if (!STATE.compareAndSet(StressTestStartCommand.StressTestState.NOT_RUNNING, StressTestStartCommand.StressTestState.RUNNING)) { + context.sendMessage(MESSAGE_COMMANDS_STRESS_TEST_ALREADY_STARTED); + return CompletableFuture.completedFuture(null); + } else { + try { + start(name, world, config, initCount, interval, dumpType, dumpInterval, thresholdNanos, percentile, shutdown); + } catch (IOException var27) { + throw SneakyThrow.sneakyThrow(var27); + } + + context.sendMessage(MESSAGE_COMMANDS_STRESS_TEST_STARTED); + return CompletableFuture.completedFuture(null); + } + } + } + + private static void start( + @Nullable String name, + @Nonnull World world, + @Nonnull BotConfig config, + int initCount, + double interval, + StressTestStartCommand.DumpType dumpType, + double dumpInterval, + long thresholdNanos, + double percentile, + boolean shutdown + ) throws IOException { + EVENT_REGISTRATION = HytaleServer.get().getEventBus().register(AddPlayerToWorldEvent.class, world.getName(), event -> { + Holder holder = event.getHolder(); + PlayerRef playerRefComponent = holder.getComponent(PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + if (playerRefComponent.getUsername().startsWith("bot-")) { + InteractionManager manager = holder.getComponent(InteractionModule.get().getInteractionManagerComponent()); + holder.putComponent(TransformComponent.getComponentType(), new TransformComponent(config.spawn.getPosition(), config.spawn.getRotation())); + manager.setHasRemoteClient(false); + } + }); + DUMP_TYPE = dumpType; + DATE_PATH = MetricsRegistry.createDatePath(Paths.get("stress-test-dumps"), null, name != null ? "_" + name : null); + if (!Files.exists(DATE_PATH)) { + Files.createDirectories(DATE_PATH); + } + + String percentileDisplay = StringUtil.trimEnd(Double.toString(MathUtil.round(percentile * 100.0, 2)), ".0"); + Path resultsPath = DATE_PATH.resolve("results.csv"); + Files.writeString(resultsPath, "avg,min,p25,p50,p75,max,p" + percentileDisplay + ",bots\n", StandardOpenOption.CREATE_NEW); + int tickStepNanos = world.getTickStepNanos(); + AtomicInteger counter = new AtomicInteger(); + + for (int i = 0; i < initCount; i++) { + try { + BOTS.add(new Bot("bot-" + counter.getAndIncrement(), config, tickStepNanos)); + } catch (SocketException | InterruptedException var20) { + Thread.currentThread().interrupt(); + throw SneakyThrow.sneakyThrow(var20); + } + } + + if (DUMP_TYPE == StressTestStartCommand.DumpType.INTERVAL) { + int dumpIntervalMillis = MathUtil.ceil(dumpInterval * 1000.0); + STRESS_TEST_DUMP_TASK = HytaleServer.SCHEDULED_EXECUTOR.scheduleAtFixedRate(() -> { + try { + Path path = MetricsRegistry.createDumpPath(DATE_PATH, ".dump.json"); + HytaleServer.METRICS_REGISTRY.dumpToJson(path, HytaleServer.get()); + } catch (IOException var1x) { + HytaleLogger.getLogger().at(Level.SEVERE).withCause(var1x).log("Failed to save dump!"); + } + }, dumpIntervalMillis, dumpIntervalMillis, TimeUnit.MILLISECONDS); + } + + int intervalMillis = MathUtil.ceil(interval * 1000.0); + STRESS_TEST_BOT_TASK = HytaleServer.SCHEDULED_EXECUTOR + .scheduleWithFixedDelay( + () -> CompletableFuture.runAsync( + SneakyThrow.sneakyRunnable( + () -> { + if (DUMP_TYPE == StressTestStartCommand.DumpType.NEW_BOT) { + Path path = MetricsRegistry.createDumpPath(DATE_PATH, ".dump.json"); + HytaleServer.METRICS_REGISTRY.dumpToJson(path, HytaleServer.get()); + } + + HistoricMetric historicMetric = world.getBufferedTickLengthMetricSet(); + int periodIndex = 1; + double avg = historicMetric.getAverage(periodIndex); + long min = historicMetric.calculateMin(periodIndex); + long max = historicMetric.calculateMax(periodIndex); + long[] values = historicMetric.getValues(1); + Arrays.sort(values); + double p25 = MathUtil.percentile(values, 0.25); + double p50 = MathUtil.percentile(values, 0.5); + double p75 = MathUtil.percentile(values, 0.75); + int bots = BOTS.size(); + double p = MathUtil.percentile(values, percentile); + Files.writeString( + resultsPath, avg + "," + min + "," + p25 + "," + p50 + "," + p75 + "," + max + "," + p + "," + bots + "\n", StandardOpenOption.APPEND + ); + HytaleLogger.getLogger() + .at(Level.INFO) + .log( + "avg: %s, min: %s, p25: %s, p50: %s, p75: %s, max: %s, p%s: %s, bots: %s", + FormatUtil.timeUnitToString(MathUtil.ceil(avg), TimeUnit.NANOSECONDS), + FormatUtil.timeUnitToString(min, TimeUnit.NANOSECONDS), + FormatUtil.timeUnitToString(MathUtil.ceil(p25), TimeUnit.NANOSECONDS), + FormatUtil.timeUnitToString(MathUtil.ceil(p50), TimeUnit.NANOSECONDS), + FormatUtil.timeUnitToString(MathUtil.ceil(p75), TimeUnit.NANOSECONDS), + FormatUtil.timeUnitToString(max, TimeUnit.NANOSECONDS), + percentileDisplay, + FormatUtil.timeUnitToString(MathUtil.ceil(p), TimeUnit.NANOSECONDS), + bots + ); + if (p > thresholdNanos) { + HytaleLogger.getLogger() + .at(Level.INFO) + .log( + "Stopped stress test due to p%s > threshold: %s > %s", + percentileDisplay, + FormatUtil.timeUnitToString(MathUtil.ceil(p), TimeUnit.NANOSECONDS), + FormatUtil.timeUnitToString(thresholdNanos, TimeUnit.NANOSECONDS) + ); + if (STATE.compareAndSet(StressTestStartCommand.StressTestState.RUNNING, StressTestStartCommand.StressTestState.STOPPING)) { + stop(); + } + + if (shutdown) { + HytaleServer.get().shutdownServer(ShutdownReason.SHUTDOWN.withMessage("Stress test finished!")); + } + } else { + BOTS.add(new Bot("bot-" + counter.getAndIncrement(), config, tickStepNanos)); + } + } + ) + ), + intervalMillis, + intervalMillis, + TimeUnit.MILLISECONDS + ); + HytaleLogger.getLogger() + .at(Level.INFO) + .log( + "Started stress test! Bot add interval %s with threshold %s for p%s" + (shutdown ? " and will shutdown when finished" : ""), + FormatUtil.timeUnitToString(intervalMillis, TimeUnit.MILLISECONDS), + FormatUtil.timeUnitToString(thresholdNanos, TimeUnit.NANOSECONDS), + percentileDisplay + ); + } + + static void stop() { + if (DUMP_TYPE == StressTestStartCommand.DumpType.INTERVAL || DUMP_TYPE == StressTestStartCommand.DumpType.FINISH) { + try { + Path path = MetricsRegistry.createDumpPath(DATE_PATH, ".dump.json"); + HytaleServer.METRICS_REGISTRY.dumpToJson(path, HytaleServer.get()); + } catch (IOException var1) { + throw SneakyThrow.sneakyThrow(var1); + } + } + + if (STRESS_TEST_BOT_TASK != null) { + STRESS_TEST_BOT_TASK.cancel(false); + STRESS_TEST_BOT_TASK = null; + } + + if (STRESS_TEST_DUMP_TASK != null) { + STRESS_TEST_DUMP_TASK.cancel(false); + STRESS_TEST_DUMP_TASK = null; + } + + BOTS.removeIf(bot -> { + bot.shutdown(); + return true; + }); + if (EVENT_REGISTRATION != null) { + EVENT_REGISTRATION.unregister(); + EVENT_REGISTRATION = null; + } + + DATE_PATH = null; + DUMP_TYPE = null; + STATE.compareAndSet(StressTestStartCommand.StressTestState.STOPPING, StressTestStartCommand.StressTestState.NOT_RUNNING); + HytaleLogger.getLogger().at(Level.INFO).log("Stopped stress test!"); + } + + static enum DumpType { + NEW_BOT, + INTERVAL, + FINISH, + NEVER; + + private DumpType() { + } + } + + static enum StressTestState { + NOT_RUNNING, + RUNNING, + STOPPING; + + private StressTestState() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/debug/stresstest/StressTestStopCommand.java b/src/com/hypixel/hytale/server/core/command/commands/debug/stresstest/StressTestStopCommand.java new file mode 100644 index 0000000..ddd6edb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/debug/stresstest/StressTestStopCommand.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.command.commands.debug.stresstest; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class StressTestStopCommand extends AbstractAsyncCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_STRESS_TEST_NOT_STARTED = Message.translation("server.commands.stresstest.notStarted"); + @Nonnull + private static final Message MESSAGE_COMMANDS_STRESS_TEST_STOPPED = Message.translation("server.commands.stresstest.stopped"); + + public StressTestStopCommand() { + super("stop", "server.commands.stresstest.stop.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + if (!StressTestStartCommand.STATE.compareAndSet(StressTestStartCommand.StressTestState.RUNNING, StressTestStartCommand.StressTestState.STOPPING)) { + context.sendMessage(MESSAGE_COMMANDS_STRESS_TEST_NOT_STARTED); + return CompletableFuture.completedFuture(null); + } else { + StressTestStartCommand.stop(); + context.sendMessage(MESSAGE_COMMANDS_STRESS_TEST_STOPPED); + return CompletableFuture.completedFuture(null); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/DamageCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/DamageCommand.java new file mode 100644 index 0000000..a4e52d4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/DamageCommand.java @@ -0,0 +1,107 @@ +package com.hypixel.hytale.server.core.command.commands.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class DamageCommand extends AbstractPlayerCommand { + @Nonnull + private final OptionalArg amountArg = this.withOptionalArg("amount", "server.commands.damage.arg.amount.desc", ArgTypes.DOUBLE); + @Nonnull + private final FlagArg silentArg = this.withFlagArg("silent", "server.commands.damage.arg.silent.desc"); + + public DamageCommand() { + super("damage", "server.commands.damage.desc"); + this.addAliases("hurt"); + this.requirePermission(HytalePermissions.fromCommand("damage.self")); + this.addUsageVariant(new DamageCommand.DamageOtherCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + double amount = this.amountArg.provided(context) ? this.amountArg.get(context) : 1.0; + boolean silent = this.silentArg.get(context); + Damage.CommandSource damageSource = new Damage.CommandSource(context.sender(), this.getName()); + Damage damage = new Damage(damageSource, DamageCause.COMMAND, (float)amount); + DamageSystems.executeDamage(ref, store, damage); + if (!silent) { + String damageFmt = String.format("%.1f", amount); + context.sendMessage(Message.translation("server.commands.damage.dealt.self").param("damage", damageFmt)); + if (world.getGameplayConfig().getCombatConfig().isPlayerIncomingDamageDisabled()) { + context.sendMessage(Message.translation("server.commands.damage.disabled").color("#ffc800")); + } + } + } + + private static class DamageOtherCommand extends CommandBase { + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + @Nonnull + private final OptionalArg amountArg = this.withOptionalArg("amount", "server.commands.damage.arg.amount.desc", ArgTypes.DOUBLE); + @Nonnull + private final FlagArg silentArg = this.withFlagArg("silent", "server.commands.damage.arg.silent.desc"); + + DamageOtherCommand() { + super("server.commands.damage.other.desc"); + this.requirePermission(HytalePermissions.fromCommand("damage.other")); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef targetPlayerRef = this.playerArg.get(context); + Ref ref = targetPlayerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + double amount = this.amountArg.provided(context) ? this.amountArg.get(context) : 1.0; + boolean silent = this.silentArg.get(context); + Damage.CommandSource damageSource = new Damage.CommandSource(context.sender(), "damage"); + Damage damage = new Damage(damageSource, DamageCause.COMMAND, (float)amount); + DamageSystems.executeDamage(ref, store, damage); + if (!silent) { + String damageFmt = String.format("%.1f", amount); + context.sendMessage( + Message.translation("server.commands.damage.dealt").param("damage", damageFmt).param("victim", playerRefComponent.getUsername()) + ); + if (world.getGameplayConfig().getCombatConfig().isPlayerIncomingDamageDisabled()) { + context.sendMessage(Message.translation("server.commands.damage.disabled").color("#ffc800")); + } + } + } + } + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/GameModeCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/GameModeCommand.java new file mode 100644 index 0000000..d60cdc0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/GameModeCommand.java @@ -0,0 +1,101 @@ +package com.hypixel.hytale.server.core.command.commands.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class GameModeCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_GAMEMODE_ALREADY_IN_MODE_SELF = Message.translation("server.commands.gamemode.alreadyInMode.self"); + @Nonnull + private final RequiredArg gameModeArg = this.withRequiredArg("gamemode", "server.commands.gamemode.gamemode.desc", ArgTypes.GAME_MODE); + + public GameModeCommand() { + super("gamemode", "server.commands.gamemode.desc"); + this.addAliases("gm"); + this.requirePermission(HytalePermissions.fromCommand("gamemode.self")); + this.addUsageVariant(new GameModeCommand.GameModeOtherCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + GameMode gameMode = this.gameModeArg.get(context); + if (playerComponent.getGameMode() == gameMode) { + context.sendMessage(MESSAGE_COMMANDS_GAMEMODE_ALREADY_IN_MODE_SELF); + } else { + Player.setGameMode(ref, gameMode, store); + Message gameModeMessage = Message.translation("server.general.gamemodes." + gameMode.name().toLowerCase()); + context.sendMessage(Message.translation("server.commands.gamemode.success.self").param("mode", gameModeMessage)); + } + } + + private static class GameModeOtherCommand extends CommandBase { + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private final RequiredArg gameModeArg = this.withRequiredArg("gamemode", "server.commands.gamemode.gamemode.desc", ArgTypes.GAME_MODE); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + + GameModeOtherCommand() { + super("server.commands.gamemode.other.desc"); + this.requirePermission(HytalePermissions.fromCommand("gamemode.other")); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef targetPlayerRef = this.playerArg.get(context); + Ref ref = targetPlayerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + GameMode gameMode = this.gameModeArg.get(context); + if (playerComponent.getGameMode() == gameMode) { + context.sendMessage( + Message.translation("server.commands.gamemode.alreadyInMode.other").param("username", playerRefComponent.getUsername()) + ); + } else { + Player.setGameMode(ref, gameMode, store); + Message gameModeMessage = Message.translation("server.general.gamemodes." + gameMode.name().toLowerCase()); + context.sendMessage( + Message.translation("server.commands.gamemode.success.other") + .param("mode", gameModeMessage) + .param("username", playerRefComponent.getUsername()) + ); + } + } + } + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/HideCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/HideCommand.java new file mode 100644 index 0000000..3969b9a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/HideCommand.java @@ -0,0 +1,268 @@ +package com.hypixel.hytale.server.core.command.commands.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Collection; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class HideCommand extends AbstractCommandCollection { + public HideCommand() { + super("hide", "server.commands.hide.desc"); + this.addUsageVariant(new HideCommand.HidePlayerCommand()); + this.addSubCommand(new HideCommand.ShowPlayerCommand()); + this.addSubCommand(new HideCommand.HideAllCommand()); + this.addSubCommand(new HideCommand.ShowAllCommand()); + } + + static class HideAllCommand extends CommandBase { + HideAllCommand() { + super("all", "server.commands.hide.all.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + Universe.get().getWorlds().forEach((name, world) -> world.execute(() -> { + Collection playerRefs = world.getPlayerRefs(); + + for (PlayerRef playerRef1 : playerRefs) { + Ref ref1 = playerRef1.getReference(); + if (ref1 != null && ref1.isValid()) { + Store store1 = ref1.getStore(); + Player playerComponent1 = store1.getComponent(ref1, Player.getComponentType()); + if (playerComponent1 != null) { + for (PlayerRef playerRef2 : playerRefs) { + if (!playerRef1.equals(playerRef2)) { + Ref ref2 = playerRef2.getReference(); + if (ref2 != null && ref2.isValid()) { + UUIDComponent uuidComponent = store1.getComponent(ref2, UUIDComponent.getComponentType()); + if (uuidComponent != null) { + playerRef1.getHiddenPlayersManager().hidePlayer(uuidComponent.getUuid()); + } + } + } + } + } + } + } + })); + context.sendMessage(Message.translation("server.commands.hide.allHiddenFromAll")); + } + } + + static class HidePlayerCommand extends AbstractAsyncCommand { + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + @Nonnull + private final OptionalArg targetArg = this.withOptionalArg("target", "server.commands.hide.target.desc", ArgTypes.PLAYER_REF); + + HidePlayerCommand() { + super("server.commands.hide.player.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + PlayerRef playerRef = this.playerArg.get(context); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + return this.runAsync( + context, + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + } else { + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + if (uuidComponent == null) { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + } else { + UUID playerUuid = uuidComponent.getUuid(); + if (this.targetArg.provided(context)) { + PlayerRef targetPlayerRef = this.targetArg.get(context); + Ref targetRef = targetPlayerRef.getReference(); + if (targetRef == null || !targetRef.isValid()) { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + return; + } + + if (targetRef.equals(ref)) { + context.sendMessage(Message.translation("server.commands.hide.cantHideFromSelf")); + return; + } + + Store targetStore = targetRef.getStore(); + Player targetPlayerComponent = targetStore.getComponent(targetRef, Player.getComponentType()); + if (targetPlayerComponent == null) { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + return; + } + + targetPlayerRef.getHiddenPlayersManager().hidePlayer(playerUuid); + context.sendMessage( + Message.translation("server.commands.hide.hiddenFrom") + .param("username", playerRef.getUsername()) + .param("targetUsername", targetPlayerRef.getUsername()) + ); + } else { + Universe.get().getWorlds().forEach((name, w) -> w.execute(() -> { + for (PlayerRef targetPlayerRefx : w.getPlayerRefs()) { + Ref targetRefx = targetPlayerRefx.getReference(); + if (targetRefx != null && targetRefx.isValid() && !targetRefx.equals(ref)) { + Store targetStorex = targetRefx.getStore(); + Player targetPlayerComponentx = targetStorex.getComponent(targetRefx, Player.getComponentType()); + if (targetPlayerComponentx != null) { + targetPlayerRefx.getHiddenPlayersManager().hidePlayer(playerUuid); + } + } + } + })); + context.sendMessage(Message.translation("server.commands.hide.hiddenFromAll").param("username", playerRef.getUsername())); + } + } + } + }, + world + ); + } else { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + return CompletableFuture.completedFuture(null); + } + } + } + + static class ShowAllCommand extends CommandBase { + ShowAllCommand() { + super("showall", "server.commands.hide.showAll.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + Universe.get().getWorlds().forEach((name, world) -> world.execute(() -> { + Collection playerRefs = world.getPlayerRefs(); + + for (PlayerRef playerRef1 : playerRefs) { + Ref ref1 = playerRef1.getReference(); + if (ref1 != null && ref1.isValid()) { + Store store1 = ref1.getStore(); + Player playerComponent1 = store1.getComponent(ref1, Player.getComponentType()); + if (playerComponent1 != null) { + for (PlayerRef playerRef2 : playerRefs) { + if (!playerRef1.equals(playerRef2)) { + Ref ref2 = playerRef2.getReference(); + if (ref2 != null && ref2.isValid()) { + UUIDComponent uuidComponent = store1.getComponent(ref2, UUIDComponent.getComponentType()); + if (uuidComponent != null) { + playerRef1.getHiddenPlayersManager().showPlayer(uuidComponent.getUuid()); + } + } + } + } + } + } + } + })); + context.sendMessage(Message.translation("server.commands.hide.allShownToAll")); + } + } + + static class ShowPlayerCommand extends AbstractAsyncCommand { + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + @Nonnull + private final OptionalArg targetArg = this.withOptionalArg("target", "server.commands.hide.target.desc", ArgTypes.PLAYER_REF); + + ShowPlayerCommand() { + super("show", "server.commands.hide.showPlayer.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + PlayerRef playerRef = this.playerArg.get(context); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + return this.runAsync( + context, + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + } else { + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + if (uuidComponent == null) { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + } else { + UUID playerUuid = uuidComponent.getUuid(); + if (this.targetArg.provided(context)) { + PlayerRef targetPlayerRef = this.targetArg.get(context); + Ref targetRef = targetPlayerRef.getReference(); + if (targetRef == null || !targetRef.isValid()) { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + return; + } + + if (targetRef.equals(ref)) { + context.sendMessage(Message.translation("server.commands.hide.cantHideFromSelf")); + return; + } + + Store targetStore = targetRef.getStore(); + Player targetPlayerComponent = targetStore.getComponent(targetRef, Player.getComponentType()); + if (targetPlayerComponent == null) { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + return; + } + + targetPlayerRef.getHiddenPlayersManager().showPlayer(playerUuid); + context.sendMessage( + Message.translation("server.commands.hide.shownTo") + .param("username", playerRef.getUsername()) + .param("targetUsername", targetPlayerRef.getUsername()) + ); + } else { + Universe.get().getWorlds().forEach((name, w) -> w.execute(() -> { + for (PlayerRef targetPlayerRefx : w.getPlayerRefs()) { + Ref targetRefx = targetPlayerRefx.getReference(); + if (targetRefx != null && targetRefx.isValid() && !targetRefx.equals(ref)) { + Store targetStorex = targetRefx.getStore(); + Player targetPlayerComponentx = targetStorex.getComponent(targetRefx, Player.getComponentType()); + if (targetPlayerComponentx != null) { + targetPlayerRefx.getHiddenPlayersManager().showPlayer(playerUuid); + } + } + } + })); + context.sendMessage(Message.translation("server.commands.hide.shownToAll").param("username", playerRef.getUsername())); + } + } + } + }, + world + ); + } else { + context.sendMessage(Message.translation("server.commands.errors.playerNotInWorld")); + return CompletableFuture.completedFuture(null); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/KillCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/KillCommand.java new file mode 100644 index 0000000..71b67da --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/KillCommand.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.server.core.command.commands.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class KillCommand extends AbstractPlayerCommand { + public KillCommand() { + super("kill", "server.commands.kill.desc"); + this.requirePermission(HytalePermissions.fromCommand("kill.self")); + this.addUsageVariant(new KillCommand.KillOtherCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Damage.CommandSource damageSource = new Damage.CommandSource(context.sender(), this.getName()); + DeathComponent.tryAddComponent(store, ref, new Damage(damageSource, DamageCause.COMMAND, 2.1474836E9F)); + context.sendMessage(Message.translation("server.commands.kill.success.self")); + } + + private static class KillOtherCommand extends CommandBase { + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + + KillOtherCommand() { + super("server.commands.kill.other.desc"); + this.requirePermission(HytalePermissions.fromCommand("kill.other")); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef targetPlayerRef = this.playerArg.get(context); + Ref ref = targetPlayerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + Damage.CommandSource damageSource = new Damage.CommandSource(context.sender(), "kill"); + DeathComponent.tryAddComponent(store, ref, new Damage(damageSource, DamageCause.COMMAND, 2.1474836E9F)); + context.sendMessage(Message.translation("server.commands.kill.success.other").param("username", playerRefComponent.getUsername())); + } + }); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/PlayerCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/PlayerCommand.java new file mode 100644 index 0000000..ee01401 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/PlayerCommand.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.server.core.command.commands.player; + +import com.hypixel.hytale.server.core.command.commands.player.camera.PlayerCameraSubCommand; +import com.hypixel.hytale.server.core.command.commands.player.effect.PlayerEffectSubCommand; +import com.hypixel.hytale.server.core.command.commands.player.stats.PlayerStatsSubCommand; +import com.hypixel.hytale.server.core.command.commands.player.viewradius.PlayerViewRadiusSubCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class PlayerCommand extends AbstractCommandCollection { + public PlayerCommand() { + super("player", "server.commands.player.desc"); + this.addSubCommand(new PlayerResetCommand()); + this.addSubCommand(new PlayerStatsSubCommand()); + this.addSubCommand(new PlayerEffectSubCommand()); + this.addSubCommand(new PlayerRespawnCommand()); + this.addSubCommand(new PlayerCameraSubCommand()); + this.addSubCommand(new PlayerViewRadiusSubCommand()); + this.addSubCommand(new PlayerZoneCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/PlayerResetCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/PlayerResetCommand.java new file mode 100644 index 0000000..19cf31f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/PlayerResetCommand.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.server.core.command.commands.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerResetCommand extends AbstractTargetPlayerCommand { + public PlayerResetCommand() { + super("reset", "server.commands.player.reset.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + Universe.get().resetPlayer(playerRef); + context.sendMessage(Message.translation("server.commands.reset.playerReset").param("uuid", uuidComponent.getUuid().toString())); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/PlayerRespawnCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/PlayerRespawnCommand.java new file mode 100644 index 0000000..703e476 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/PlayerRespawnCommand.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.server.core.command.commands.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PlayerRespawnCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_PLAYER_RESPAWN_SUCCESS_SELF = Message.translation("server.commands.player.respawn.success.self"); + + public PlayerRespawnCommand() { + super("respawn", "server.commands.player.respawn.desc"); + this.addUsageVariant(new PlayerRespawnCommand.PlayerRespawnOtherCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + store.tryRemoveComponent(ref, DeathComponent.getComponentType()); + context.sendMessage(MESSAGE_COMMANDS_PLAYER_RESPAWN_SUCCESS_SELF); + } + + private static class PlayerRespawnOtherCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + + PlayerRespawnOtherCommand() { + super("server.commands.player.respawn.other.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef playerRef = this.playerArg.get(context); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + store.tryRemoveComponent(ref, DeathComponent.getComponentType()); + context.sendMessage(Message.translation("server.commands.player.respawn.success.other").param("username", playerRef.getUsername())); + } + }); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/PlayerZoneCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/PlayerZoneCommand.java new file mode 100644 index 0000000..7831ac8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/PlayerZoneCommand.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.server.core.command.commands.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerZoneCommand extends AbstractTargetPlayerCommand { + @Nonnull + private static final Message MESSAGE_NO_DATA = Message.translation("server.commands.player.zone.noData"); + + public PlayerZoneCommand() { + super("zone", "server.commands.player.zone.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + WorldMapTracker worldMapTracker = playerComponent.getWorldMapTracker(); + WorldMapTracker.ZoneDiscoveryInfo currentZone = worldMapTracker.getCurrentZone(); + String currentBiome = worldMapTracker.getCurrentBiomeName(); + if (currentZone != null && currentBiome != null) { + context.sendMessage( + Message.translation("server.commands.player.zone.currentZone") + .param("zone", Message.translation(String.format("server.map.region.%s", currentZone.regionName()))) + ); + context.sendMessage(Message.translation("server.commands.player.zone.currentBiome").param("biome", currentBiome)); + } else { + context.sendMessage(MESSAGE_NO_DATA); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/ReferCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/ReferCommand.java new file mode 100644 index 0000000..e0705af --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/ReferCommand.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.server.core.command.commands.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ReferCommand extends AbstractTargetPlayerCommand { + @Nonnull + private final RequiredArg hostArg = this.withRequiredArg("host", "Target server hostname or IP", ArgTypes.STRING); + @Nonnull + private final RequiredArg portArg = this.withRequiredArg("port", "Target server port", ArgTypes.INTEGER); + + public ReferCommand() { + super("refer", "Refer a player to another server for testing"); + this.addAliases("transfer"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + String host = this.hostArg.get(context); + int port = this.portArg.get(context); + boolean isTargetingOther = !ref.equals(sourceRef); + if (isTargetingOther) { + CommandUtil.requirePermission(context.sender(), HytalePermissions.fromCommand("refer.other")); + } else { + CommandUtil.requirePermission(context.sender(), HytalePermissions.fromCommand("refer.self")); + } + + if (port > 0 && port <= 65535) { + byte[] testData = "Test referral".getBytes(); + + try { + playerRef.referToServer(host, port, testData); + if (isTargetingOther) { + context.sendMessage( + Message.translation("server.commands.refer.success.other").param("username", playerRef.getUsername()).param("host", host).param("port", port) + ); + } else { + context.sendMessage(Message.translation("server.commands.refer.success.self").param("host", host).param("port", port)); + } + } catch (Exception var12) { + context.sendMessage(Message.translation("server.commands.refer.failed").param("error", var12.getMessage())); + } + } else { + context.sendMessage(Message.translation("server.commands.refer.invalidPort")); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/SudoCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/SudoCommand.java new file mode 100644 index 0000000..8aaaf79 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/SudoCommand.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.server.core.command.commands.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.NameMatching; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandManager; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class SudoCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_SU_INVALID_USAGE = Message.translation("server.commands.sudo.invalidusage"); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.sudo.player.desc", ArgTypes.STRING); + + public SudoCommand() { + super("sudo", "server.commands.sudo.desc"); + this.addAliases("su"); + this.setAllowsExtraArguments(true); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + String playerName = this.playerArg.get(context); + String inputString = context.getInputString(); + String rawArgs = CommandUtil.stripCommandName(inputString); + int commandIndex = rawArgs.indexOf(32); + if (commandIndex == -1) { + context.sendMessage(MESSAGE_COMMANDS_SU_INVALID_USAGE); + } else { + String commandToExecute = rawArgs.substring(commandIndex + 1).trim(); + if (commandToExecute.isEmpty()) { + context.sendMessage(MESSAGE_COMMANDS_SU_INVALID_USAGE); + } else { + if (commandToExecute.charAt(0) == '/') { + commandToExecute = commandToExecute.substring(1); + } + + List players; + if (playerName.equals("*")) { + players = Universe.get().getPlayers(); + } else { + PlayerRef player = Universe.get().getPlayer(playerName, NameMatching.DEFAULT); + if (player == null) { + context.sendMessage(Message.translation("server.commands.errors.noSuchPlayer").param("username", playerName)); + return; + } + + players = new ObjectArrayList<>(); + players.add(player); + } + + if (players.isEmpty()) { + context.sendMessage(Message.translation("server.commands.errors.noSuchPlayer").param("username", playerName)); + } else { + String finalCommand = commandToExecute; + + for (PlayerRef player : players) { + Ref ref = player.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + CommandManager.get().handleCommand(playerComponent, finalCommand); + }); + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/ToggleBlockPlacementOverrideCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/ToggleBlockPlacementOverrideCommand.java new file mode 100644 index 0000000..aa38278 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/ToggleBlockPlacementOverrideCommand.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.core.command.commands.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ToggleBlockPlacementOverrideCommand extends AbstractPlayerCommand { + public ToggleBlockPlacementOverrideCommand() { + super("toggleBlockPlacementOverride", "server.commands.toggleBlockPlacementOverride.desc"); + this.addAliases("tbpo", "togglePlacement"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.setOverrideBlockPlacementRestrictions(ref, !playerComponent.isOverrideBlockPlacementRestrictions(), store); + context.sendMessage( + Message.translation( + "server.commands.toggleBlockPlacementOverride." + (playerComponent.isOverrideBlockPlacementRestrictions() ? "enabled" : "disabled") + ) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/WhereAmICommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/WhereAmICommand.java new file mode 100644 index 0000000..0b57886 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/WhereAmICommand.java @@ -0,0 +1,128 @@ +package com.hypixel.hytale.server.core.command.commands.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WhereAmICommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_WHERE_AM_I_CHUNK_NOT_LOADED = Message.translation("server.commands.whereami.chunkNotLoaded"); + + public WhereAmICommand() { + super("whereami", "server.commands.whereami.desc"); + this.setPermissionGroup(GameMode.Creative); + this.requirePermission(HytalePermissions.fromCommand("whereami.self")); + this.addUsageVariant(new WhereAmICommand.WhereAmIOtherCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + sendLocationInfo(context, store, ref, world, null); + } + + private static void sendLocationInfo( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull World world, @Nullable String targetUsername + ) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3d position = transformComponent.getPosition(); + Vector3f headRotation = headRotationComponent.getRotation(); + Vector3i axisDirection = headRotationComponent.getAxisDirection(); + Axis axis = headRotationComponent.getAxis(); + Vector3d direction = headRotationComponent.getDirection(); + int chunkX = MathUtil.floor(position.getX()) >> 5; + int chunkY = MathUtil.floor(position.getY()) >> 5; + int chunkZ = MathUtil.floor(position.getZ()) >> 5; + long chunkIndex = ChunkUtil.indexChunk(chunkX, chunkZ); + WorldChunk playerChunk = world.getChunkIfInMemory(chunkIndex); + String headerKey = targetUsername != null ? "server.commands.whereami.header.other" : "server.commands.whereami.header"; + Message message = Message.translation(headerKey) + .param("username", targetUsername) + .param("world", world.getName()) + .param("chunkX", chunkX) + .param("chunkY", chunkY) + .param("chunkZ", chunkZ) + .param("posX", position.getX()) + .param("posY", position.getY()) + .param("posZ", position.getZ()) + .param("yaw", headRotation.getYaw()) + .param("pitch", headRotation.getPitch()) + .param("roll", headRotation.getRoll()) + .param("direction", direction.toString()) + .param("axisDirection", axisDirection.toString()) + .param("axis", axis.toString()); + if (playerChunk == null) { + message.insert(MESSAGE_COMMANDS_WHERE_AM_I_CHUNK_NOT_LOADED); + } else { + message.insert(Message.translation("server.commands.whereami.needsSaving").param("needsSaving", Boolean.toString(playerChunk.getNeedsSaving()))); + } + + context.sendMessage(message); + } + + private static class WhereAmIOtherCommand extends CommandBase { + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + + WhereAmIOtherCommand() { + super("server.commands.whereami.other.desc"); + this.setPermissionGroup(GameMode.Creative); + this.requirePermission(HytalePermissions.fromCommand("whereami.other")); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef targetPlayerRef = this.playerArg.get(context); + Ref ref = targetPlayerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + WhereAmICommand.sendLocationInfo(context, store, ref, world, playerRefComponent.getUsername()); + } + }); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/WhoAmICommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/WhoAmICommand.java new file mode 100644 index 0000000..145ec21 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/WhoAmICommand.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.server.core.command.commands.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WhoAmICommand extends AbstractPlayerCommand { + public static final String UUID_ALIAS = "uuid"; + + public WhoAmICommand() { + super("whoami", "server.commands.whoami.desc"); + this.setPermissionGroup(GameMode.Adventure); + this.addAliases("uuid"); + this.addUsageVariant(new WhoAmICommand.WhoAmIOtherCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + sendPlayerInfo(context, playerRef); + } + + private static void sendPlayerInfo(@Nonnull CommandContext context, @Nonnull PlayerRef playerRef) { + Message message = Message.translation("server.commands.whoami.header") + .param("uuid", playerRef.getUuid().toString()) + .param("username", playerRef.getUsername()) + .param("language", playerRef.getLanguage()); + context.sendMessage(message); + } + + private static class WhoAmIOtherCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + + WhoAmIOtherCommand() { + super("server.commands.whoami.other.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef playerRef = this.playerArg.get(context); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + WhoAmICommand.sendPlayerInfo(context, playerRef); + } + }); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/camera/CameraDemo.java b/src/com/hypixel/hytale/server/core/command/commands/player/camera/CameraDemo.java new file mode 100644 index 0000000..eebdc16 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/camera/CameraDemo.java @@ -0,0 +1,165 @@ +package com.hypixel.hytale.server.core.command.commands.player.camera; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.EventRegistry; +import com.hypixel.hytale.math.iterator.BlockIterator; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.ClientCameraView; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.MouseButtonState; +import com.hypixel.hytale.protocol.MouseButtonType; +import com.hypixel.hytale.protocol.MouseInputType; +import com.hypixel.hytale.protocol.MovementForceRotationType; +import com.hypixel.hytale.protocol.PositionDistanceOffsetType; +import com.hypixel.hytale.protocol.RotationType; +import com.hypixel.hytale.protocol.ServerCameraSettings; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.protocol.packets.camera.SetServerCamera; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.entities.player.CameraManager; +import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerInteractEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerMouseButtonEvent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.CopyOnWriteArrayList; +import javax.annotation.Nonnull; + +public class CameraDemo { + public static final CameraDemo INSTANCE = new CameraDemo(); + private final EventRegistry eventRegistry = new EventRegistry( + new CopyOnWriteArrayList<>(), () -> this.isActive, "CameraDemo is not active!", HytaleServer.get().getEventBus() + ); + private final ServerCameraSettings cameraSettings = createServerCameraSettings(); + private boolean isActive; + + public CameraDemo() { + } + + public void activate() { + if (!this.isActive) { + this.eventRegistry.enable(); + this.isActive = true; + this.eventRegistry.register(PlayerConnectEvent.class, event -> this.onAddNewPlayer(event.getPlayerRef())); + this.eventRegistry.register(PlayerMouseButtonEvent.class, this::onPlayerMouseButton); + this.eventRegistry.registerGlobal(PlayerInteractEvent.class, event -> event.setCancelled(true)); + Universe.get().getPlayers().forEach(this::onAddNewPlayer); + } + } + + public void deactivate() { + if (this.isActive) { + this.eventRegistry.shutdown(); + Universe.get().getPlayers().forEach(p -> { + CameraManager cameraManager = p.getComponent(CameraManager.getComponentType()); + if (cameraManager != null) { + cameraManager.resetCamera(p); + } + }); + this.isActive = false; + } + } + + private void onAddNewPlayer(@Nonnull PlayerRef player) { + player.getPacketHandler().writeNoCache(new SetServerCamera(ClientCameraView.Custom, true, this.cameraSettings)); + } + + private void onPlayerMouseButton(@Nonnull PlayerMouseButtonEvent event) { + if (event.getMouseButton().state == MouseButtonState.Released) { + Ref ref = event.getPlayerRef(); + if (ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + Vector3i targetBlock = event.getTargetBlock(); + CameraManager cameraManagerComponent = store.getComponent(ref, CameraManager.getComponentType()); + + assert cameraManagerComponent != null; + + Vector3i lastTargetBlock = cameraManagerComponent.getLastMouseButtonPressedPosition(event.getMouseButton().mouseButtonType); + if (event.getMouseButton().mouseButtonType == MouseButtonType.Middle) { + if (event.getItemInHand() != null && event.getItemInHand().hasBlockType() && targetBlock != null) { + String key = event.getItemInHand().getId(); + int blockId = BlockType.getAssetMap().getIndex(key); + if (blockId == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + if (!lastTargetBlock.equals(targetBlock)) { + BlockIterator.iterateFromTo(lastTargetBlock, targetBlock, (xx, y, zx, px, py, pz, qx, qy, qz) -> { + world.getChunk(ChunkUtil.indexChunkFromBlock(xx, zx)).setBlock(xx, y, zx, blockId); + return true; + }); + } else { + int x = targetBlock.getX(); + int z = targetBlock.getZ(); + world.getChunk(ChunkUtil.indexChunkFromBlock(x, z)).setBlock(x, targetBlock.getY(), z, blockId); + } + } + } else if (event.getMouseButton().mouseButtonType == MouseButtonType.Right) { + if (!lastTargetBlock.equals(targetBlock)) { + BlockIterator.iterateFromTo(lastTargetBlock, targetBlock, (xx, y, zx, px, py, pz, qx, qy, qz) -> { + world.getChunk(ChunkUtil.indexChunkFromBlock(xx, zx)).setBlock(xx, y, zx, 0); + return true; + }); + } else { + int x = targetBlock.getX(); + int z = targetBlock.getZ(); + world.getChunk(ChunkUtil.indexChunkFromBlock(x, z)).setBlock(x, targetBlock.getY(), z, 0); + } + } else if (event.getMouseButton().mouseButtonType == MouseButtonType.Left + && event.getItemInHand() != null + && event.getItemInHand().hasBlockType() + && targetBlock != null) { + String keyx = event.getItemInHand().getId(); + int blockIdx = BlockType.getAssetMap().getIndex(keyx); + if (blockIdx == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + keyx); + } + + if (!lastTargetBlock.equals(targetBlock)) { + BlockIterator.iterateFromTo( + lastTargetBlock.getX(), + lastTargetBlock.getY() + 1, + lastTargetBlock.getZ(), + targetBlock.getX(), + targetBlock.getY() + 1, + targetBlock.getZ(), + (xx, y, zx, px, py, pz, qx, qy, qz) -> { + world.getChunk(ChunkUtil.indexChunkFromBlock(xx, zx)).setBlock(xx, y, zx, blockId); + return true; + } + ); + } else { + int x = targetBlock.getX(); + int z = targetBlock.getZ(); + world.getChunk(ChunkUtil.indexChunkFromBlock(x, z)).setBlock(x, targetBlock.getY() + 1, z, blockIdx); + } + } + } + } + } + + @Nonnull + private static ServerCameraSettings createServerCameraSettings() { + ServerCameraSettings cameraSettings = new ServerCameraSettings(); + cameraSettings.positionLerpSpeed = 0.2F; + cameraSettings.rotationLerpSpeed = 0.2F; + cameraSettings.distance = 20.0F; + cameraSettings.displayCursor = true; + cameraSettings.sendMouseMotion = true; + cameraSettings.isFirstPerson = false; + cameraSettings.movementForceRotationType = MovementForceRotationType.Custom; + cameraSettings.eyeOffset = true; + cameraSettings.positionDistanceOffsetType = PositionDistanceOffsetType.DistanceOffset; + cameraSettings.rotationType = RotationType.Custom; + cameraSettings.rotation = new Direction(0.0F, (float) (-Math.PI / 2), 0.0F); + cameraSettings.mouseInputType = MouseInputType.LookAtPlane; + cameraSettings.planeNormal = new Vector3f(0.0F, 1.0F, 0.0F); + return cameraSettings; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraDemoActivateCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraDemoActivateCommand.java new file mode 100644 index 0000000..21f364c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraDemoActivateCommand.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.core.command.commands.player.camera; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerCameraDemoActivateCommand extends AbstractTargetPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CAMERA_DEMO_ENABLED = Message.translation("server.commands.camera.demo.enabled"); + + public PlayerCameraDemoActivateCommand() { + super("activate", "server.commands.camera.demo.activate.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + CameraDemo.INSTANCE.activate(); + context.sendMessage(MESSAGE_COMMANDS_CAMERA_DEMO_ENABLED); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraDemoDeactivateCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraDemoDeactivateCommand.java new file mode 100644 index 0000000..7c2e9ed --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraDemoDeactivateCommand.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.core.command.commands.player.camera; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerCameraDemoDeactivateCommand extends AbstractTargetPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CAMERA_DEMO_DISABLED = Message.translation("server.commands.camera.demo.disabled"); + + public PlayerCameraDemoDeactivateCommand() { + super("deactivate", "server.commands.camera.demo.deactivate.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + CameraDemo.INSTANCE.deactivate(); + context.sendMessage(MESSAGE_COMMANDS_CAMERA_DEMO_DISABLED); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraDemoSubCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraDemoSubCommand.java new file mode 100644 index 0000000..ceffe22 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraDemoSubCommand.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.command.commands.player.camera; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class PlayerCameraDemoSubCommand extends AbstractCommandCollection { + public PlayerCameraDemoSubCommand() { + super("demo", "server.commands.camera.demo.desc"); + this.addSubCommand(new PlayerCameraDemoActivateCommand()); + this.addSubCommand(new PlayerCameraDemoDeactivateCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraResetCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraResetCommand.java new file mode 100644 index 0000000..3bff120 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraResetCommand.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.server.core.command.commands.player.camera; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.ClientCameraView; +import com.hypixel.hytale.protocol.packets.camera.SetServerCamera; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerCameraResetCommand extends AbstractTargetPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CAMERA_RESET_SUCCESS = Message.translation("server.commands.camera.reset.success"); + + public PlayerCameraResetCommand() { + super("reset", "server.commands.camera.reset.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + playerRef.getPacketHandler().writeNoCache(new SetServerCamera(ClientCameraView.Custom, false, null)); + context.sendMessage(MESSAGE_COMMANDS_CAMERA_RESET_SUCCESS); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraSideScrollerCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraSideScrollerCommand.java new file mode 100644 index 0000000..d878fc4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraSideScrollerCommand.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.server.core.command.commands.player.camera; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.ClientCameraView; +import com.hypixel.hytale.protocol.MouseInputType; +import com.hypixel.hytale.protocol.MovementForceRotationType; +import com.hypixel.hytale.protocol.PositionDistanceOffsetType; +import com.hypixel.hytale.protocol.RotationType; +import com.hypixel.hytale.protocol.ServerCameraSettings; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.protocol.packets.camera.SetServerCamera; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerCameraSideScrollerCommand extends AbstractTargetPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CAMERA_SIDESCROLLER_SUCCESS = Message.translation("server.commands.camera.sidescroller.success"); + + public PlayerCameraSideScrollerCommand() { + super("sidescroller", "server.commands.camera.sidescroller.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + ServerCameraSettings cameraSettings = new ServerCameraSettings(); + cameraSettings.positionLerpSpeed = 0.2F; + cameraSettings.rotationLerpSpeed = 0.2F; + cameraSettings.distance = 15.0F; + cameraSettings.displayCursor = true; + cameraSettings.isFirstPerson = false; + cameraSettings.movementForceRotationType = MovementForceRotationType.Custom; + cameraSettings.movementMultiplier = new Vector3f(1.0F, 1.0F, 0.0F); + cameraSettings.eyeOffset = true; + cameraSettings.positionDistanceOffsetType = PositionDistanceOffsetType.DistanceOffset; + cameraSettings.rotationType = RotationType.Custom; + cameraSettings.mouseInputType = MouseInputType.LookAtPlane; + cameraSettings.planeNormal = new Vector3f(0.0F, 0.0F, 1.0F); + playerRef.getPacketHandler().writeNoCache(new SetServerCamera(ClientCameraView.Custom, true, cameraSettings)); + context.sendMessage(MESSAGE_COMMANDS_CAMERA_SIDESCROLLER_SUCCESS); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraSubCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraSubCommand.java new file mode 100644 index 0000000..9de9156 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraSubCommand.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.server.core.command.commands.player.camera; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class PlayerCameraSubCommand extends AbstractCommandCollection { + public PlayerCameraSubCommand() { + super("camera", "server.commands.camera.desc"); + this.addSubCommand(new PlayerCameraResetCommand()); + this.addSubCommand(new PlayerCameraTopdownCommand()); + this.addSubCommand(new PlayerCameraSideScrollerCommand()); + this.addSubCommand(new PlayerCameraDemoSubCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraTopdownCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraTopdownCommand.java new file mode 100644 index 0000000..4fa5bf2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/camera/PlayerCameraTopdownCommand.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.core.command.commands.player.camera; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.ClientCameraView; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.MouseInputType; +import com.hypixel.hytale.protocol.MovementForceRotationType; +import com.hypixel.hytale.protocol.PositionDistanceOffsetType; +import com.hypixel.hytale.protocol.RotationType; +import com.hypixel.hytale.protocol.ServerCameraSettings; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.protocol.packets.camera.SetServerCamera; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerCameraTopdownCommand extends AbstractTargetPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CAMERA_TOPDOWN_SUCCESS = Message.translation("server.commands.camera.topdown.success"); + + public PlayerCameraTopdownCommand() { + super("topdown", "server.commands.camera.topdown.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + ServerCameraSettings cameraSettings = new ServerCameraSettings(); + cameraSettings.positionLerpSpeed = 0.2F; + cameraSettings.rotationLerpSpeed = 0.2F; + cameraSettings.distance = 20.0F; + cameraSettings.displayCursor = true; + cameraSettings.isFirstPerson = false; + cameraSettings.movementForceRotationType = MovementForceRotationType.Custom; + cameraSettings.eyeOffset = true; + cameraSettings.positionDistanceOffsetType = PositionDistanceOffsetType.DistanceOffset; + cameraSettings.rotationType = RotationType.Custom; + cameraSettings.rotation = new Direction(0.0F, (float) (-Math.PI / 2), 0.0F); + cameraSettings.mouseInputType = MouseInputType.LookAtPlane; + cameraSettings.planeNormal = new Vector3f(0.0F, 1.0F, 0.0F); + playerRef.getPacketHandler().writeNoCache(new SetServerCamera(ClientCameraView.Custom, true, cameraSettings)); + context.sendMessage(MESSAGE_COMMANDS_CAMERA_TOPDOWN_SUCCESS); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/effect/PlayerEffectApplyCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/effect/PlayerEffectApplyCommand.java new file mode 100644 index 0000000..1a5c665 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/effect/PlayerEffectApplyCommand.java @@ -0,0 +1,111 @@ +package com.hypixel.hytale.server.core.command.commands.player.effect; + +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.OverlapBehavior; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PlayerEffectApplyCommand extends AbstractPlayerCommand { + private static final float DEFAULT_DURATION = 100.0F; + private static final Message MESSAGE_EFFECT_APPLIED_SELF = Message.translation("server.commands.player.effect.apply.success.self"); + @Nonnull + private final RequiredArg effectArg = this.withRequiredArg("effect", "server.commands.player.effect.apply.effect.desc", ArgTypes.EFFECT_ASSET); + @Nonnull + private final DefaultArg durationArg = this.withDefaultArg( + "duration", "server.commands.player.effect.apply.duration.desc", ArgTypes.FLOAT, 100.0F, "server.commands.entity.effect.duration" + ) + .addValidator(Validators.greaterThan(0.0F)); + + public PlayerEffectApplyCommand() { + super("apply", "server.commands.player.effect.apply.desc"); + this.requirePermission(HytalePermissions.fromCommand("player.effect.apply.self")); + this.addUsageVariant(new PlayerEffectApplyCommand.PlayerEffectApplyOtherCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + EffectControllerComponent effectControllerComponent = store.getComponent(ref, EffectControllerComponent.getComponentType()); + + assert effectControllerComponent != null; + + EntityEffect effect = this.effectArg.get(context); + Float duration = this.durationArg.get(context); + effectControllerComponent.addEffect(ref, effect, duration, OverlapBehavior.OVERWRITE, store); + context.sendMessage(MESSAGE_EFFECT_APPLIED_SELF.param("effect", effect.getId()).param("duration", duration)); + } + + private static class PlayerEffectApplyOtherCommand extends CommandBase { + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + private static final Message MESSAGE_EFFECT_APPLIED_OTHER = Message.translation("server.commands.player.effect.apply.success.other"); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + @Nonnull + private final RequiredArg effectArg = this.withRequiredArg( + "effect", "server.commands.player.effect.apply.effect.desc", ArgTypes.EFFECT_ASSET + ); + @Nonnull + private final DefaultArg durationArg = this.withDefaultArg( + "duration", "server.commands.player.effect.apply.duration.desc", ArgTypes.FLOAT, 100.0F, "server.commands.entity.effect.duration" + ) + .addValidator(Validators.greaterThan(0.0F)); + + PlayerEffectApplyOtherCommand() { + super("server.commands.player.effect.apply.other.desc"); + this.requirePermission(HytalePermissions.fromCommand("player.effect.apply.other")); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef targetPlayerRef = this.playerArg.get(context); + Ref ref = targetPlayerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + EffectControllerComponent effectControllerComponent = store.getComponent(ref, EffectControllerComponent.getComponentType()); + + assert effectControllerComponent != null; + + EntityEffect effect = this.effectArg.get(context); + Float duration = this.durationArg.get(context); + effectControllerComponent.addEffect(ref, effect, duration, OverlapBehavior.OVERWRITE, store); + context.sendMessage( + MESSAGE_EFFECT_APPLIED_OTHER.param("username", playerRefComponent.getUsername()) + .param("effect", effect.getId()) + .param("duration", duration) + ); + } + } + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/effect/PlayerEffectClearCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/effect/PlayerEffectClearCommand.java new file mode 100644 index 0000000..b64ff00 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/effect/PlayerEffectClearCommand.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.server.core.command.commands.player.effect; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PlayerEffectClearCommand extends AbstractPlayerCommand { + private static final Message MESSAGE_EFFECTS_CLEARED_SELF = Message.translation("server.commands.player.effect.clear.success.self"); + + public PlayerEffectClearCommand() { + super("clear", "server.commands.player.effect.clear.desc"); + this.requirePermission(HytalePermissions.fromCommand("player.effect.clear.self")); + this.addUsageVariant(new PlayerEffectClearCommand.PlayerEffectClearOtherCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + EffectControllerComponent effectControllerComponent = store.getComponent(ref, EffectControllerComponent.getComponentType()); + + assert effectControllerComponent != null; + + effectControllerComponent.clearEffects(ref, store); + context.sendMessage(MESSAGE_EFFECTS_CLEARED_SELF); + } + + private static class PlayerEffectClearOtherCommand extends CommandBase { + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + private static final Message MESSAGE_EFFECTS_CLEARED_OTHER = Message.translation("server.commands.player.effect.clear.success.other"); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + + PlayerEffectClearOtherCommand() { + super("server.commands.player.effect.clear.other.desc"); + this.requirePermission(HytalePermissions.fromCommand("player.effect.clear.other")); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef targetPlayerRef = this.playerArg.get(context); + Ref ref = targetPlayerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + EffectControllerComponent effectControllerComponent = store.getComponent(ref, EffectControllerComponent.getComponentType()); + + assert effectControllerComponent != null; + + effectControllerComponent.clearEffects(ref, store); + context.sendMessage(MESSAGE_EFFECTS_CLEARED_OTHER.param("username", playerRefComponent.getUsername())); + } + }); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/effect/PlayerEffectSubCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/effect/PlayerEffectSubCommand.java new file mode 100644 index 0000000..2e7486b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/effect/PlayerEffectSubCommand.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.command.commands.player.effect; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class PlayerEffectSubCommand extends AbstractCommandCollection { + public PlayerEffectSubCommand() { + super("effect", "server.commands.player.effect.desc"); + this.addSubCommand(new PlayerEffectApplyCommand()); + this.addSubCommand(new PlayerEffectClearCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/inventory/GiveArmorCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/inventory/GiveArmorCommand.java new file mode 100644 index 0000000..38863ac --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/inventory/GiveArmorCommand.java @@ -0,0 +1,133 @@ +package com.hypixel.hytale.server.core.command.commands.player.inventory; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.NameMatching; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.awt.Color; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class GiveArmorCommand extends AbstractAsyncCommand { + private static final String PREFIX = "Armor_"; + @Nonnull + private static final Message MESSAGE_COMMANDS_GIVEARMOR_SUCCESS = Message.translation("server.commands.givearmor.success"); + @Nonnull + private final OptionalArg playerArg = this.withOptionalArg("player", "server.commands.givearmor.player.desc", ArgTypes.STRING); + @Nonnull + private final RequiredArg searchStringArg = this.withRequiredArg("search", "server.commands.givearmor.search.desc", ArgTypes.STRING); + @Nonnull + private final FlagArg setFlag = this.withFlagArg("set", "server.commands.givearmor.set.desc"); + + public GiveArmorCommand() { + super("armor", "server.commands.givearmor.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + Collection> targets; + if (this.playerArg.provided(context)) { + String playerInput = this.playerArg.get(context); + if ("*".equals(playerInput)) { + targets = new ObjectArrayList<>(); + + for (PlayerRef player : Universe.get().getPlayers()) { + targets.add(player.getReference()); + } + } else { + PlayerRef player = Universe.get().getPlayer(playerInput, NameMatching.DEFAULT); + if (player == null) { + context.sendMessage(Message.translation("server.commands.errors.noSuchPlayer").param("username", playerInput)); + return CompletableFuture.completedFuture(null); + } + + targets = Collections.singletonList(player.getReference()); + } + } else { + if (!context.isPlayer()) { + context.sendMessage(Message.translation("server.commands.errors.playerOrArg").param("option", "player")); + return CompletableFuture.completedFuture(null); + } + + targets = Collections.singletonList(context.senderAsPlayerRef()); + } + + if (targets.isEmpty()) { + context.sendMessage(Message.translation("server.commands.errors.noSuchPlayer").param("username", "*")); + return CompletableFuture.completedFuture(null); + } else { + String searchString = this.searchStringArg.get(context); + List armor = Item.getAssetMap() + .getAssetMap() + .keySet() + .stream() + .filter(blockTypeKey -> blockTypeKey.startsWith("Armor_") && blockTypeKey.indexOf(searchString, "Armor_".length()) == "Armor_".length()) + .map(ItemStack::new) + .collect(Collectors.toList()); + if (armor.isEmpty()) { + context.sendMessage(Message.translation("server.commands.givearmor.typeNotFound").param("type", searchString).color(Color.RED)); + return CompletableFuture.completedFuture(null); + } else { + Map>> playersByWorld = new Object2ObjectOpenHashMap<>(); + + for (Ref targetRef : targets) { + if (targetRef != null && targetRef.isValid()) { + Store store = targetRef.getStore(); + World world = store.getExternalData().getWorld(); + playersByWorld.computeIfAbsent(world, k -> new ObjectArrayList<>()).add(targetRef); + } + } + + ObjectArrayList> futures = new ObjectArrayList<>(); + boolean shouldClear = this.setFlag.provided(context); + + for (Entry>> entry : playersByWorld.entrySet()) { + World world = entry.getKey(); + List> worldPlayers = entry.getValue(); + CompletableFuture future = this.runAsync(context, () -> { + for (Ref playerRef : worldPlayers) { + if (playerRef != null && playerRef.isValid()) { + Store storex = playerRef.getStore(); + Player targetPlayerComponent = storex.getComponent(playerRef, Player.getComponentType()); + if (targetPlayerComponent != null) { + ItemContainer armorInventory = targetPlayerComponent.getInventory().getArmor(); + if (shouldClear) { + armorInventory.clear(); + } + + armorInventory.addItemStacks(armor); + } + } + } + }, world); + futures.add(future); + } + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenRun(() -> context.sendMessage(MESSAGE_COMMANDS_GIVEARMOR_SUCCESS)); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/inventory/GiveCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/inventory/GiveCommand.java new file mode 100644 index 0000000..e7c758c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/inventory/GiveCommand.java @@ -0,0 +1,153 @@ +package com.hypixel.hytale.server.core.command.commands.player.inventory; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; + +public class GiveCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_GIVE_RECEIVED = Message.translation("server.commands.give.received"); + @Nonnull + private static final Message MESSAGE_COMMANDS_GIVE_INSUFFICIENT_INV_SPACE = Message.translation("server.commands.give.insufficientInvSpace"); + @Nonnull + private static final Message MESSAGE_COMMANDS_GIVE_INVALID_METADATA = Message.translation("server.commands.give.invalidMetadata"); + @Nonnull + private final RequiredArg itemArg = this.withRequiredArg("item", "server.commands.give.item.desc", ArgTypes.ITEM_ASSET); + @Nonnull + private final DefaultArg quantityArg = this.withDefaultArg("quantity", "server.commands.give.quantity.desc", ArgTypes.INTEGER, 1, "1"); + @Nonnull + private final OptionalArg metadataArg = this.withOptionalArg("metadata", "server.commands.give.metadata.desc", ArgTypes.STRING); + + public GiveCommand() { + super("give", "server.commands.give.desc"); + this.requirePermission(HytalePermissions.fromCommand("give.self")); + this.addUsageVariant(new GiveCommand.GiveOtherCommand()); + this.addSubCommand(new GiveArmorCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Item item = this.itemArg.get(context); + Integer quantity = this.quantityArg.get(context); + BsonDocument metadata = null; + if (this.metadataArg.provided(context)) { + String metadataStr = this.metadataArg.get(context); + + try { + metadata = BsonDocument.parse(metadataStr); + } catch (Exception var13) { + context.sendMessage(MESSAGE_COMMANDS_GIVE_INVALID_METADATA.param("error", var13.getMessage())); + return; + } + } + + ItemStackTransaction transaction = playerComponent.getInventory().getCombinedHotbarFirst().addItemStack(new ItemStack(item.getId(), quantity, metadata)); + ItemStack remainder = transaction.getRemainder(); + Message itemNameMessage = Message.translation(item.getTranslationKey()); + if (remainder != null && !remainder.isEmpty()) { + context.sendMessage(MESSAGE_COMMANDS_GIVE_INSUFFICIENT_INV_SPACE.param("quantity", quantity).param("item", itemNameMessage)); + } else { + context.sendMessage(MESSAGE_COMMANDS_GIVE_RECEIVED.param("quantity", quantity).param("item", itemNameMessage)); + } + } + + private static class GiveOtherCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private static final Message MESSAGE_COMMANDS_GIVE_GAVE = Message.translation("server.commands.give.gave"); + @Nonnull + private static final Message MESSAGE_COMMANDS_GIVE_INSUFFICIENT_INV_SPACE = Message.translation("server.commands.give.insufficientInvSpace"); + @Nonnull + private static final Message MESSAGE_COMMANDS_GIVE_INVALID_METADATA = Message.translation("server.commands.give.invalidMetadata"); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + @Nonnull + private final RequiredArg itemArg = this.withRequiredArg("item", "server.commands.give.item.desc", ArgTypes.ITEM_ASSET); + @Nonnull + private final DefaultArg quantityArg = this.withDefaultArg("quantity", "server.commands.give.quantity.desc", ArgTypes.INTEGER, 1, "1"); + @Nonnull + private final OptionalArg metadataArg = this.withOptionalArg("metadata", "server.commands.give.metadata.desc", ArgTypes.STRING); + + GiveOtherCommand() { + super("server.commands.give.other.desc"); + this.requirePermission(HytalePermissions.fromCommand("give.other")); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef targetPlayerRef = this.playerArg.get(context); + Ref ref = targetPlayerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + Item item = this.itemArg.get(context); + Integer quantity = this.quantityArg.get(context); + BsonDocument metadata = null; + if (this.metadataArg.provided(context)) { + String metadataStr = this.metadataArg.get(context); + + try { + metadata = BsonDocument.parse(metadataStr); + } catch (Exception var13) { + context.sendMessage(MESSAGE_COMMANDS_GIVE_INVALID_METADATA.param("error", var13.getMessage())); + return; + } + } + + ItemStackTransaction transaction = playerComponent.getInventory() + .getCombinedHotbarFirst() + .addItemStack(new ItemStack(item.getId(), quantity, metadata)); + ItemStack remainder = transaction.getRemainder(); + Message itemNameMessage = Message.translation(item.getTranslationKey()); + if (remainder != null && !remainder.isEmpty()) { + context.sendMessage(MESSAGE_COMMANDS_GIVE_INSUFFICIENT_INV_SPACE.param("quantity", quantity).param("item", itemNameMessage)); + } else { + context.sendMessage( + MESSAGE_COMMANDS_GIVE_GAVE.param("targetUsername", targetPlayerRef.getUsername()) + .param("quantity", quantity) + .param("item", itemNameMessage) + ); + } + } + } + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/inventory/InventoryBackpackCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/inventory/InventoryBackpackCommand.java new file mode 100644 index 0000000..4f0d093 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/inventory/InventoryBackpackCommand.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.server.core.command.commands.player.inventory; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.ItemUtils; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import javax.annotation.Nonnull; + +public class InventoryBackpackCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_INVENTORY_BACKPACK_SIZE = Message.translation("server.commands.inventory.backpack.size"); + @Nonnull + private static final Message MESSAGE_COMMANDS_INVENTORY_BACKPACK_RESIZED = Message.translation("server.commands.inventory.backpack.resized"); + @Nonnull + private final OptionalArg sizeArg = this.withOptionalArg("size", "server.commands.inventorybackpack.size.desc", ArgTypes.INTEGER); + + public InventoryBackpackCommand() { + super("backpack", "server.commands.inventorybackpack.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Inventory inventory = playerComponent.getInventory(); + if (!this.sizeArg.provided(context)) { + context.sendMessage(MESSAGE_COMMANDS_INVENTORY_BACKPACK_SIZE.param("capacity", inventory.getBackpack().getCapacity())); + } else { + short capacity = this.sizeArg.get(context).shortValue(); + ObjectArrayList remainder = new ObjectArrayList<>(); + inventory.resizeBackpack(capacity, remainder); + + for (ItemStack item : remainder) { + ItemUtils.dropItem(ref, item, store); + } + + context.sendMessage( + MESSAGE_COMMANDS_INVENTORY_BACKPACK_RESIZED.param("capacity", inventory.getBackpack().getCapacity()).param("dropped", remainder.size()) + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/inventory/InventoryClearCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/inventory/InventoryClearCommand.java new file mode 100644 index 0000000..5477123 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/inventory/InventoryClearCommand.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.core.command.commands.player.inventory; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class InventoryClearCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CLEARINV_SUCCESS = Message.translation("server.commands.clearinv.success"); + + public InventoryClearCommand() { + super("clear", "server.commands.inventoryclear.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getInventory().clear(); + context.sendMessage(MESSAGE_COMMANDS_CLEARINV_SUCCESS); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/inventory/InventoryCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/inventory/InventoryCommand.java new file mode 100644 index 0000000..df06904 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/inventory/InventoryCommand.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.server.core.command.commands.player.inventory; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class InventoryCommand extends AbstractCommandCollection { + public InventoryCommand() { + super("inventory", "server.commands.inventory.desc"); + this.addAliases("inv"); + this.addSubCommand(new InventoryClearCommand()); + this.addSubCommand(new InventorySeeCommand()); + this.addSubCommand(new InventoryItemCommand()); + this.addSubCommand(new InventoryBackpackCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/inventory/InventoryItemCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/inventory/InventoryItemCommand.java new file mode 100644 index 0000000..858496a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/inventory/InventoryItemCommand.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.core.command.commands.player.inventory; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.windows.ContainerWindow; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.container.ItemStackItemContainer; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class InventoryItemCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_INVENTORY_ITEM_NO_ITEM_IN_HAND = Message.translation("server.commands.inventory.item.noItemInHand"); + @Nonnull + private static final Message MESSAGE_COMMANDS_INVENTORY_ITEM_NO_CONTAINER_ON_ITEM = Message.translation("server.commands.inventory.item.noContainerOnItem"); + + public InventoryItemCommand() { + super("item", "server.commands.inventoryitem.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Inventory inventory = playerComponent.getInventory(); + ItemContainer hotbar = inventory.getHotbar(); + byte activeHotbarSlot = inventory.getActiveHotbarSlot(); + ItemStack activeHotbarItem = inventory.getActiveHotbarItem(); + if (ItemStack.isEmpty(activeHotbarItem)) { + context.sendMessage(MESSAGE_COMMANDS_INVENTORY_ITEM_NO_ITEM_IN_HAND); + } else { + ItemStackItemContainer backpackInventory = ItemStackItemContainer.getContainer(hotbar, activeHotbarSlot); + if (backpackInventory != null && backpackInventory.getCapacity() != 0) { + playerComponent.getPageManager().setPageWithWindows(ref, store, Page.Bench, true, new ContainerWindow(backpackInventory)); + } else { + context.sendMessage(MESSAGE_COMMANDS_INVENTORY_ITEM_NO_CONTAINER_ON_ITEM); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/inventory/InventorySeeCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/inventory/InventorySeeCommand.java new file mode 100644 index 0000000..fc2b7f3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/inventory/InventorySeeCommand.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.server.core.command.commands.player.inventory; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.windows.ContainerWindow; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.inventory.container.DelegateItemContainer; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterType; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class InventorySeeCommand extends AbstractPlayerCommand { + public static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private final RequiredArg targetPlayerArg = this.withRequiredArg("player", "server.commands.inventorysee.player.desc", ArgTypes.PLAYER_REF); + + public InventorySeeCommand() { + super("see", "server.commands.inventorysee.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef targetPlayerRef = this.targetPlayerArg.get(context); + Ref targetRef = targetPlayerRef.getReference(); + if (targetRef != null && targetRef.isValid()) { + Store targetStore = targetRef.getStore(); + World targetWorld = targetStore.getExternalData().getWorld(); + targetWorld.execute(() -> { + Player targetPlayerComponent = targetStore.getComponent(targetRef, Player.getComponentType()); + if (targetPlayerComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + CombinedItemContainer targetInventory = targetPlayerComponent.getInventory().getCombinedHotbarFirst(); + ItemContainer targetItemContainer = targetInventory; + if (!context.sender().hasPermission(HytalePermissions.fromCommand("invsee", "modify"))) { + DelegateItemContainer delegateItemContainer = new DelegateItemContainer<>(targetInventory); + delegateItemContainer.setGlobalFilter(FilterType.DENY_ALL); + targetItemContainer = delegateItemContainer; + } + + playerComponent.getPageManager().setPageWithWindows(ref, store, Page.Bench, true, new ContainerWindow(targetItemContainer)); + } + }); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/inventory/ItemStateCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/inventory/ItemStateCommand.java new file mode 100644 index 0000000..5f9735d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/inventory/ItemStateCommand.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.core.command.commands.player.inventory; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ItemStateCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ITEMSTATE_NO_ITEM = Message.translation("server.commands.itemstate.noItem"); + @Nonnull + private final RequiredArg stateArg = this.withRequiredArg("state", "server.commands.itemstate.state.desc", ArgTypes.STRING); + + public ItemStateCommand() { + super("itemstate", "server.commands.itemstate.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Inventory inventory = playerComponent.getInventory(); + byte activeHotbarSlot = inventory.getActiveHotbarSlot(); + if (activeHotbarSlot == -1) { + context.sendMessage(MESSAGE_COMMANDS_ITEMSTATE_NO_ITEM); + } else { + ItemContainer hotbar = inventory.getHotbar(); + ItemStack item = hotbar.getItemStack(activeHotbarSlot); + if (item == null) { + context.sendMessage(MESSAGE_COMMANDS_ITEMSTATE_NO_ITEM); + } else { + String state = this.stateArg.get(context); + hotbar.setItemStackForSlot(activeHotbarSlot, item.withState(state)); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsAddCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsAddCommand.java new file mode 100644 index 0000000..039b2ca --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsAddCommand.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.command.commands.player.stats; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.commands.world.entity.stats.EntityStatsAddCommand; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Collections; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerStatsAddCommand extends AbstractTargetPlayerCommand { + @Nonnull + private final RequiredArg entityStatNameArg = this.withRequiredArg("statName", "server.commands.player.stats.add.statName.desc", ArgTypes.STRING); + @Nonnull + private final RequiredArg statAmountArg = this.withRequiredArg("statAmount", "server.commands.player.stats.add.statAmount.desc", ArgTypes.INTEGER); + + public PlayerStatsAddCommand() { + super("add", "server.commands.player.stats.add.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + int statAmount = this.statAmountArg.get(context); + String entityStat = this.entityStatNameArg.get(context); + EntityStatsAddCommand.addEntityStat(context, Collections.singletonList(ref), statAmount, entityStat, store); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsDumpCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsDumpCommand.java new file mode 100644 index 0000000..ab962ec --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsDumpCommand.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.command.commands.player.stats; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.commands.world.entity.stats.EntityStatsDumpCommand; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Collections; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerStatsDumpCommand extends AbstractTargetPlayerCommand { + public PlayerStatsDumpCommand() { + super("dump", "server.commands.player.stats.dump.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + EntityStatsDumpCommand.dumpEntityStatsData(context, Collections.singletonList(playerRef.getReference()), store); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsGetCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsGetCommand.java new file mode 100644 index 0000000..118be4b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsGetCommand.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.server.core.command.commands.player.stats; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.commands.world.entity.stats.EntityStatsGetCommand; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Collections; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerStatsGetCommand extends AbstractTargetPlayerCommand { + @Nonnull + private final RequiredArg entityStatNameArg = this.withRequiredArg("statName", "server.commands.player.stats.get.statName.desc", ArgTypes.STRING); + + public PlayerStatsGetCommand() { + super("get", "server.commands.player.stats.get.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + String entityStat = this.entityStatNameArg.get(context); + EntityStatsGetCommand.getEntityStat(context, Collections.singletonList(playerRef.getReference()), entityStat, store); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsResetCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsResetCommand.java new file mode 100644 index 0000000..e66e4ed --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsResetCommand.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.server.core.command.commands.player.stats; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.commands.world.entity.stats.EntityStatsResetCommand; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Collections; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerStatsResetCommand extends AbstractTargetPlayerCommand { + @Nonnull + private final RequiredArg entityStatNameArg = this.withRequiredArg("statName", "server.commands.player.stats.reset.statName.desc", ArgTypes.STRING); + + public PlayerStatsResetCommand() { + super("reset", "server.commands.player.stats.reset.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + String entityStat = this.entityStatNameArg.get(context); + EntityStatsResetCommand.resetEntityStat(context, Collections.singletonList(playerRef.getReference()), entityStat, store); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsSetCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsSetCommand.java new file mode 100644 index 0000000..5df743f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsSetCommand.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.command.commands.player.stats; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.commands.world.entity.stats.EntityStatsSetCommand; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Collections; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerStatsSetCommand extends AbstractTargetPlayerCommand { + @Nonnull + private final RequiredArg entityStatNameArg = this.withRequiredArg("statName", "server.commands.player.stats.set.statName.desc", ArgTypes.STRING); + @Nonnull + private final RequiredArg statValueArg = this.withRequiredArg("statValue", "server.commands.player.stats.set.statValue.desc", ArgTypes.INTEGER); + + public PlayerStatsSetCommand() { + super("set", "server.commands.player.stats.set.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + int newStatValue = this.statValueArg.get(context); + String entityStat = this.entityStatNameArg.get(context); + EntityStatsSetCommand.setEntityStat(context, Collections.singletonList(playerRef.getReference()), newStatValue, entityStat, store); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsSetToMaxCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsSetToMaxCommand.java new file mode 100644 index 0000000..7088c48 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsSetToMaxCommand.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.server.core.command.commands.player.stats; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.commands.world.entity.stats.EntityStatsSetToMaxCommand; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Collections; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerStatsSetToMaxCommand extends AbstractTargetPlayerCommand { + @Nonnull + private final RequiredArg entityStatNameArg = this.withRequiredArg( + "statName", "server.commands.player.stats.settomax.statName.desc", ArgTypes.STRING + ); + + public PlayerStatsSetToMaxCommand() { + super("settomax", "server.commands.player.stats.settomax.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + String entityStat = this.entityStatNameArg.get(context); + EntityStatsSetToMaxCommand.setEntityStatMax(context, Collections.singletonList(ref), entityStat, store); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsSubCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsSubCommand.java new file mode 100644 index 0000000..5b9d03f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/stats/PlayerStatsSubCommand.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.server.core.command.commands.player.stats; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class PlayerStatsSubCommand extends AbstractCommandCollection { + public PlayerStatsSubCommand() { + super("stats", "server.commands.player.stats.desc"); + this.addAliases("stat"); + this.addSubCommand(new PlayerStatsAddCommand()); + this.addSubCommand(new PlayerStatsGetCommand()); + this.addSubCommand(new PlayerStatsSetCommand()); + this.addSubCommand(new PlayerStatsSetToMaxCommand()); + this.addSubCommand(new PlayerStatsDumpCommand()); + this.addSubCommand(new PlayerStatsResetCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/viewradius/PlayerViewRadiusGetCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/viewradius/PlayerViewRadiusGetCommand.java new file mode 100644 index 0000000..b2a3f60 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/viewradius/PlayerViewRadiusGetCommand.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.core.command.commands.player.viewradius; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerViewRadiusGetCommand extends AbstractTargetPlayerCommand { + public PlayerViewRadiusGetCommand() { + super("get", "server.commands.player.viewradius.get.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + EntityTrackerSystems.EntityViewer entityViewerComponent = store.getComponent(ref, EntityTrackerSystems.EntityViewer.getComponentType()); + + assert entityViewerComponent != null; + + int viewRadiusChunks = playerComponent.getViewRadius(); + int clientViewRadiusChunks = playerComponent.getClientViewRadius(); + int viewRadiusBlocks = entityViewerComponent.viewRadiusBlocks; + context.sendMessage( + Message.translation("server.commands.player.viewradius.info") + .param("radius", viewRadiusChunks) + .param("clientRadius", clientViewRadiusChunks) + .param("radiusBlocks", viewRadiusBlocks) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/viewradius/PlayerViewRadiusSetCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/viewradius/PlayerViewRadiusSetCommand.java new file mode 100644 index 0000000..15443d2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/viewradius/PlayerViewRadiusSetCommand.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.server.core.command.commands.player.viewradius; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.setup.ViewRadius; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerViewRadiusSetCommand extends AbstractTargetPlayerCommand { + @Nonnull + private final RequiredArg radiusArg = this.withRequiredArg("radius", "server.commands.player.viewradius.set.radius.desc", ArgTypes.STRING); + @Nonnull + private final FlagArg blocksArg = this.withFlagArg("blocks", "server.commands.player.viewradius.set.blocks.desc"); + @Nonnull + private final FlagArg bypassArg = this.withFlagArg("bypass", "server.commands.player.viewradius.set.bypass.desc"); + + public PlayerViewRadiusSetCommand() { + super("set", "server.commands.player.viewradius.set.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + EntityTrackerSystems.EntityViewer entityViewerComponent = store.getComponent(ref, EntityTrackerSystems.EntityViewer.getComponentType()); + + assert entityViewerComponent != null; + + String radiusInput = this.radiusArg.get(context); + boolean measureInBlocks = this.blocksArg.get(context); + boolean bypass = this.bypassArg.get(context); + int viewRadiusChunks; + if ("default".equalsIgnoreCase(radiusInput)) { + viewRadiusChunks = 32; + } else { + try { + int value = Integer.parseInt(radiusInput); + viewRadiusChunks = measureInBlocks ? (int)Math.ceil(value / 32.0F) : value; + } catch (NumberFormatException var15) { + context.sendMessage(Message.translation("server.commands.player.viewradius.set.invalidNumber").param("value", radiusInput)); + return; + } + } + + int maxViewRadius = HytaleServer.get().getConfig().getMaxViewRadius(); + if (viewRadiusChunks > maxViewRadius && !bypass) { + context.sendMessage(Message.translation("server.commands.player.viewradius.set.noHigherThan").param("radius", maxViewRadius)); + } else { + int viewRadiusBlocks = viewRadiusChunks * 32; + playerComponent.setClientViewRadius(viewRadiusChunks); + entityViewerComponent.viewRadiusBlocks = viewRadiusBlocks; + playerRef.getPacketHandler().writeNoCache(new ViewRadius(viewRadiusBlocks)); + context.sendMessage( + Message.translation("server.commands.player.viewradius.set.success").param("radius", viewRadiusChunks).param("radiusBlocks", viewRadiusBlocks) + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/player/viewradius/PlayerViewRadiusSubCommand.java b/src/com/hypixel/hytale/server/core/command/commands/player/viewradius/PlayerViewRadiusSubCommand.java new file mode 100644 index 0000000..38ad7ab --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/player/viewradius/PlayerViewRadiusSubCommand.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.command.commands.player.viewradius; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class PlayerViewRadiusSubCommand extends AbstractCommandCollection { + public PlayerViewRadiusSubCommand() { + super("viewradius", "server.commands.player.viewradius.desc"); + this.addSubCommand(new PlayerViewRadiusGetCommand()); + this.addSubCommand(new PlayerViewRadiusSetCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/server/KickCommand.java b/src/com/hypixel/hytale/server/core/command/commands/server/KickCommand.java new file mode 100644 index 0000000..3ec8406 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/server/KickCommand.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.server.core.command.commands.server; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import javax.annotation.Nonnull; + +public class KickCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_KICK_SUCCESS = Message.translation("server.commands.kick.success"); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.kick.desc", ArgTypes.PLAYER_REF); + + public KickCommand() { + super("kick", "server.commands.kick.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PlayerRef playerToKick = this.playerArg.get(context); + playerToKick.getPacketHandler().disconnect("You were kicked."); + context.sendMessage(MESSAGE_COMMANDS_KICK_SUCCESS.param("username", playerToKick.getUsername())); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/server/MaxPlayersCommand.java b/src/com/hypixel/hytale/server/core/command/commands/server/MaxPlayersCommand.java new file mode 100644 index 0000000..0478a8e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/server/MaxPlayersCommand.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.core.command.commands.server; + +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import javax.annotation.Nonnull; + +public class MaxPlayersCommand extends CommandBase { + @Nonnull + private final OptionalArg amountArg = this.withOptionalArg("amount", "server.commands.maxplayers.amount.desc", ArgTypes.INTEGER); + + public MaxPlayersCommand() { + super("maxplayers", "server.commands.maxplayers.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + if (this.amountArg.provided(context)) { + int maxPlayers = this.amountArg.get(context); + HytaleServer.get().getConfig().setMaxPlayers(maxPlayers); + context.sendMessage(Message.translation("server.commands.maxplayers.set").param("maxPlayers", maxPlayers)); + } else { + int maxPlayers = HytaleServer.get().getConfig().getMaxPlayers(); + context.sendMessage(Message.translation("server.commands.maxplayers.get").param("maxPlayers", maxPlayers)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/server/StopCommand.java b/src/com/hypixel/hytale/server/core/command/commands/server/StopCommand.java new file mode 100644 index 0000000..6a50b21 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/server/StopCommand.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.command.commands.server; + +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.ShutdownReason; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import javax.annotation.Nonnull; + +public class StopCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_STOP_SUCCESS = Message.translation("server.commands.stop.success"); + @Nonnull + private final FlagArg crashFlag = this.withFlagArg("crash", "server.commands.stop.crash.desc"); + + public StopCommand() { + super("stop", "server.commands.stop.desc"); + this.addAliases("shutdown"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + context.sendMessage(MESSAGE_COMMANDS_STOP_SUCCESS); + if (this.crashFlag.provided(context)) { + HytaleServer.get().shutdownServer(ShutdownReason.CRASH); + } else { + HytaleServer.get().shutdownServer(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/server/WhoCommand.java b/src/com/hypixel/hytale/server/core/command/commands/server/WhoCommand.java new file mode 100644 index 0000000..f92e8cb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/server/WhoCommand.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.server.core.command.commands.server; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.DisplayNameComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class WhoCommand extends AbstractAsyncCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_WHO_PLAYER_WITH_DISPLAY_NAME = Message.translation("server.commands.who.playerWithDisplayName"); + + public WhoCommand() { + super("who", "server.commands.who.desc"); + this.setPermissionGroup(GameMode.Adventure); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + Map worlds = Universe.get().getWorlds(); + ObjectArrayList> futures = new ObjectArrayList<>(); + + for (Entry entry : worlds.entrySet()) { + World world = entry.getValue(); + CompletableFuture future = this.runAsync( + context, + () -> { + Store store = world.getEntityStore().getStore(); + Collection playerRefs = world.getPlayerRefs(); + List playerMessages = new ObjectArrayList<>(); + + for (PlayerRef playerRef : playerRefs) { + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + DisplayNameComponent displayNameComponent = store.getComponent(ref, DisplayNameComponent.getComponentType()); + + assert displayNameComponent != null; + + Message displayName = displayNameComponent.getDisplayName(); + if (displayName != null) { + playerMessages.add( + MESSAGE_COMMANDS_WHO_PLAYER_WITH_DISPLAY_NAME.param("displayName", displayName).param("username", playerRef.getUsername()) + ); + } else { + playerMessages.add(Message.raw(playerRef.getUsername())); + } + } + } + } + + context.sendMessage(MessageFormat.list(Message.raw(world.getName()), playerMessages)); + }, + world + ); + futures.add(future); + } + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthCancelCommand.java b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthCancelCommand.java new file mode 100644 index 0000000..0171df7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthCancelCommand.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.core.command.commands.server.auth; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.auth.ServerAuthManager; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import java.awt.Color; +import javax.annotation.Nonnull; + +public class AuthCancelCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_SINGLEPLAYER = Message.translation("server.commands.auth.cancel.singleplayer").color(Color.RED); + @Nonnull + private static final Message MESSAGE_SUCCESS = Message.translation("server.commands.auth.cancel.success").color(Color.YELLOW); + @Nonnull + private static final Message MESSAGE_NOTHING = Message.translation("server.commands.auth.cancel.nothing").color(Color.YELLOW); + + public AuthCancelCommand() { + super("cancel", "server.commands.auth.cancel.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + ServerAuthManager authManager = ServerAuthManager.getInstance(); + if (authManager.isSingleplayer()) { + context.sendMessage(MESSAGE_SINGLEPLAYER); + } else { + if (authManager.cancelActiveFlow()) { + context.sendMessage(MESSAGE_SUCCESS); + } else { + context.sendMessage(MESSAGE_NOTHING); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthCommand.java b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthCommand.java new file mode 100644 index 0000000..9b70d99 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthCommand.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.server.core.command.commands.server.auth; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class AuthCommand extends AbstractCommandCollection { + public AuthCommand() { + super("auth", "server.commands.auth.desc"); + this.addSubCommand(new AuthStatusCommand()); + this.addSubCommand(new AuthLoginCommand()); + this.addSubCommand(new AuthSelectCommand()); + this.addSubCommand(new AuthLogoutCommand()); + this.addSubCommand(new AuthCancelCommand()); + this.addSubCommand(new AuthPersistenceCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthLoginBrowserCommand.java b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthLoginBrowserCommand.java new file mode 100644 index 0000000..eaf790d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthLoginBrowserCommand.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.server.core.command.commands.server.auth; + +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.auth.AuthCredentialStoreProvider; +import com.hypixel.hytale.server.core.auth.MemoryAuthCredentialStoreProvider; +import com.hypixel.hytale.server.core.auth.ServerAuthManager; +import com.hypixel.hytale.server.core.auth.SessionServiceClient; +import com.hypixel.hytale.server.core.auth.oauth.OAuthBrowserFlow; +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import java.awt.Color; +import java.awt.Desktop; +import java.awt.Desktop.Action; +import java.net.URI; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class AuthLoginBrowserCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_SINGLEPLAYER = Message.translation("server.commands.auth.login.singleplayer").color(Color.RED); + @Nonnull + private static final Message MESSAGE_ALREADY_AUTHENTICATED = Message.translation("server.commands.auth.login.alreadyAuthenticated").color(Color.YELLOW); + @Nonnull + private static final Message MESSAGE_STARTING = Message.translation("server.commands.auth.login.browser.starting").color(Color.YELLOW); + @Nonnull + private static final Message MESSAGE_SUCCESS = Message.translation("server.commands.auth.login.browser.success").color(Color.GREEN); + @Nonnull + private static final Message MESSAGE_FAILED = Message.translation("server.commands.auth.login.browser.failed").color(Color.RED); + @Nonnull + private static final Message MESSAGE_PENDING = Message.translation("server.commands.auth.login.pending").color(Color.YELLOW); + @Nonnull + private static final Message MESSAGE_PERSISTENCE_MEMORY = Message.translation("server.commands.auth.login.persistence.memory").color(Color.ORANGE); + @Nonnull + private static final Message MESSAGE_PERSISTENCE_SAVED = Message.translation("server.commands.auth.login.persistence.saved").color(Color.GREEN); + + public AuthLoginBrowserCommand() { + super("browser", "server.commands.auth.login.browser.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + ServerAuthManager authManager = ServerAuthManager.getInstance(); + if (authManager.isSingleplayer()) { + context.sendMessage(MESSAGE_SINGLEPLAYER); + } else if (authManager.hasSessionToken() && authManager.hasIdentityToken()) { + context.sendMessage(MESSAGE_ALREADY_AUTHENTICATED); + } else { + context.sendMessage(MESSAGE_STARTING); + authManager.startFlowAsync(new AuthLoginBrowserCommand.AuthFlow()).thenAccept(result -> { + switch (result) { + case SUCCESS: + context.sendMessage(MESSAGE_SUCCESS); + sendPersistenceFeedback(context); + break; + case PENDING_PROFILE_SELECTION: + context.sendMessage(MESSAGE_PENDING); + SessionServiceClient.GameProfile[] profiles = authManager.getPendingProfiles(); + if (profiles != null) { + AuthSelectCommand.sendProfileList(context, profiles); + } + break; + case FAILED: + context.sendMessage(MESSAGE_FAILED); + } + }); + } + } + + static void sendPersistenceFeedback(@Nonnull CommandContext context) { + AuthCredentialStoreProvider provider = HytaleServer.get().getConfig().getAuthCredentialStoreProvider(); + if (provider instanceof MemoryAuthCredentialStoreProvider) { + String availableTypes = String.join(", ", AuthCredentialStoreProvider.CODEC.getRegisteredIds()); + context.sendMessage(MESSAGE_PERSISTENCE_MEMORY.param("types", availableTypes)); + } else { + String typeName = AuthCredentialStoreProvider.CODEC.getIdFor((Class)provider.getClass()); + context.sendMessage(MESSAGE_PERSISTENCE_SAVED.param("type", typeName)); + } + } + + private static class AuthFlow extends OAuthBrowserFlow { + private AuthFlow() { + } + + @Override + public void onFlowInfo(String authUrl) { + AbstractCommand.LOGGER.at(Level.INFO).log("Starting OAuth browser flow..."); + AbstractCommand.LOGGER.at(Level.INFO).log("==================================================================="); + AbstractCommand.LOGGER.at(Level.INFO).log("Please open this URL in your browser to authenticate:"); + AbstractCommand.LOGGER.at(Level.INFO).log("%s", authUrl); + AbstractCommand.LOGGER.at(Level.INFO).log("==================================================================="); + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Action.BROWSE)) { + try { + Desktop.getDesktop().browse(new URI(authUrl)); + AbstractCommand.LOGGER.at(Level.INFO).log("Browser opened automatically."); + } catch (Exception var3) { + AbstractCommand.LOGGER.at(Level.INFO).log("Could not open browser automatically. Please open the URL manually."); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthLoginCommand.java b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthLoginCommand.java new file mode 100644 index 0000000..7ee89cb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthLoginCommand.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.command.commands.server.auth; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class AuthLoginCommand extends AbstractCommandCollection { + public AuthLoginCommand() { + super("login", "server.commands.auth.login.desc"); + this.addSubCommand(new AuthLoginBrowserCommand()); + this.addSubCommand(new AuthLoginDeviceCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthLoginDeviceCommand.java b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthLoginDeviceCommand.java new file mode 100644 index 0000000..8b194b5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthLoginDeviceCommand.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.server.core.command.commands.server.auth; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.auth.ServerAuthManager; +import com.hypixel.hytale.server.core.auth.SessionServiceClient; +import com.hypixel.hytale.server.core.auth.oauth.OAuthDeviceFlow; +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import java.awt.Color; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class AuthLoginDeviceCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_SINGLEPLAYER = Message.translation("server.commands.auth.login.singleplayer").color(Color.RED); + @Nonnull + private static final Message MESSAGE_ALREADY_AUTHENTICATED = Message.translation("server.commands.auth.login.alreadyAuthenticated").color(Color.YELLOW); + @Nonnull + private static final Message MESSAGE_STARTING = Message.translation("server.commands.auth.login.device.starting").color(Color.YELLOW); + @Nonnull + private static final Message MESSAGE_SUCCESS = Message.translation("server.commands.auth.login.device.success").color(Color.GREEN); + @Nonnull + private static final Message MESSAGE_FAILED = Message.translation("server.commands.auth.login.device.failed").color(Color.RED); + @Nonnull + private static final Message MESSAGE_PENDING = Message.translation("server.commands.auth.login.pending").color(Color.YELLOW); + + public AuthLoginDeviceCommand() { + super("device", "server.commands.auth.login.device.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + ServerAuthManager authManager = ServerAuthManager.getInstance(); + if (authManager.isSingleplayer()) { + context.sendMessage(MESSAGE_SINGLEPLAYER); + } else if (authManager.hasSessionToken() && authManager.hasIdentityToken()) { + context.sendMessage(MESSAGE_ALREADY_AUTHENTICATED); + } else { + context.sendMessage(MESSAGE_STARTING); + authManager.startFlowAsync(new AuthLoginDeviceCommand.AuthFlow()).thenAccept(result -> { + switch (result) { + case SUCCESS: + context.sendMessage(MESSAGE_SUCCESS); + AuthLoginBrowserCommand.sendPersistenceFeedback(context); + break; + case PENDING_PROFILE_SELECTION: + context.sendMessage(MESSAGE_PENDING); + SessionServiceClient.GameProfile[] profiles = authManager.getPendingProfiles(); + if (profiles != null) { + AuthSelectCommand.sendProfileList(context, profiles); + } + break; + case FAILED: + context.sendMessage(MESSAGE_FAILED); + } + }); + } + } + + private static class AuthFlow extends OAuthDeviceFlow { + private AuthFlow() { + } + + @Override + public void onFlowInfo(String userCode, String verificationUri, String verificationUriComplete, int expiresIn) { + AbstractCommand.LOGGER.at(Level.INFO).log("==================================================================="); + AbstractCommand.LOGGER.at(Level.INFO).log("DEVICE AUTHORIZATION"); + AbstractCommand.LOGGER.at(Level.INFO).log("==================================================================="); + AbstractCommand.LOGGER.at(Level.INFO).log("Visit: %s", verificationUri); + AbstractCommand.LOGGER.at(Level.INFO).log("Enter code: %s", userCode); + if (verificationUriComplete != null) { + AbstractCommand.LOGGER.at(Level.INFO).log("Or visit: %s", verificationUriComplete); + } + + AbstractCommand.LOGGER.at(Level.INFO).log("==================================================================="); + AbstractCommand.LOGGER.at(Level.INFO).log("Waiting for authorization (expires in %d seconds)...", expiresIn); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthLogoutCommand.java b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthLogoutCommand.java new file mode 100644 index 0000000..81fc6b7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthLogoutCommand.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.core.command.commands.server.auth; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.auth.ServerAuthManager; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import java.awt.Color; +import javax.annotation.Nonnull; + +public class AuthLogoutCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_SINGLEPLAYER = Message.translation("server.commands.auth.logout.singleplayer").color(Color.RED); + @Nonnull + private static final Message MESSAGE_NOT_AUTHENTICATED = Message.translation("server.commands.auth.logout.notAuthenticated").color(Color.YELLOW); + @Nonnull + private static final Message MESSAGE_SUCCESS = Message.translation("server.commands.auth.logout.success").color(Color.GREEN); + + public AuthLogoutCommand() { + super("logout", "server.commands.auth.logout.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + ServerAuthManager authManager = ServerAuthManager.getInstance(); + if (authManager.isSingleplayer()) { + context.sendMessage(MESSAGE_SINGLEPLAYER); + } else if (!authManager.hasIdentityToken() && !authManager.hasSessionToken()) { + context.sendMessage(MESSAGE_NOT_AUTHENTICATED); + } else { + ServerAuthManager.AuthMode previousMode = authManager.getAuthMode(); + authManager.logout(); + context.sendMessage(MESSAGE_SUCCESS.param("previousMode", previousMode.name())); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthPersistenceCommand.java b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthPersistenceCommand.java new file mode 100644 index 0000000..1aaabf4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthPersistenceCommand.java @@ -0,0 +1,72 @@ +package com.hypixel.hytale.server.core.command.commands.server.auth; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.auth.AuthCredentialStoreProvider; +import com.hypixel.hytale.server.core.auth.ServerAuthManager; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import java.awt.Color; +import javax.annotation.Nonnull; + +public class AuthPersistenceCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_SINGLEPLAYER = Message.translation("server.commands.auth.persistence.singleplayer").color(Color.RED); + @Nonnull + private static final Message MESSAGE_CURRENT = Message.translation("server.commands.auth.persistence.current").color(Color.YELLOW); + @Nonnull + private static final Message MESSAGE_AVAILABLE = Message.translation("server.commands.auth.persistence.available").color(Color.GRAY); + @Nonnull + private static final Message MESSAGE_CHANGED = Message.translation("server.commands.auth.persistence.changed").color(Color.GREEN); + @Nonnull + private static final Message MESSAGE_UNKNOWN_TYPE = Message.translation("server.commands.auth.persistence.unknownType").color(Color.RED); + + public AuthPersistenceCommand() { + super("persistence", "server.commands.auth.persistence.desc"); + this.addUsageVariant(new AuthPersistenceCommand.SetPersistenceVariant()); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + if (ServerAuthManager.getInstance().isSingleplayer()) { + context.sendMessage(MESSAGE_SINGLEPLAYER); + } else { + AuthCredentialStoreProvider provider = HytaleServer.get().getConfig().getAuthCredentialStoreProvider(); + String typeName = AuthCredentialStoreProvider.CODEC.getIdFor((Class)provider.getClass()); + context.sendMessage(MESSAGE_CURRENT.param("type", typeName)); + String availableTypes = String.join(", ", AuthCredentialStoreProvider.CODEC.getRegisteredIds()); + context.sendMessage(MESSAGE_AVAILABLE.param("types", availableTypes)); + } + } + + private static class SetPersistenceVariant extends CommandBase { + @Nonnull + private final RequiredArg typeArg = this.withRequiredArg("type", "server.commands.auth.persistence.type.desc", ArgTypes.STRING); + + SetPersistenceVariant() { + super("server.commands.auth.persistence.variant.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + ServerAuthManager authManager = ServerAuthManager.getInstance(); + if (authManager.isSingleplayer()) { + context.sendMessage(AuthPersistenceCommand.MESSAGE_SINGLEPLAYER); + } else { + String typeName = this.typeArg.get(context); + BuilderCodec codec = AuthCredentialStoreProvider.CODEC.getCodecFor(typeName); + if (codec == null) { + context.sendMessage(AuthPersistenceCommand.MESSAGE_UNKNOWN_TYPE.param("type", typeName)); + } else { + AuthCredentialStoreProvider newProvider = codec.getDefaultValue(); + HytaleServer.get().getConfig().setAuthCredentialStoreProvider(newProvider); + authManager.swapCredentialStoreProvider(newProvider); + context.sendMessage(AuthPersistenceCommand.MESSAGE_CHANGED.param("type", typeName)); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthSelectCommand.java b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthSelectCommand.java new file mode 100644 index 0000000..3d21764 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthSelectCommand.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.server.core.command.commands.server.auth; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.auth.ServerAuthManager; +import com.hypixel.hytale.server.core.auth.SessionServiceClient; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import java.awt.Color; +import javax.annotation.Nonnull; + +public class AuthSelectCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_NO_PENDING = Message.translation("server.commands.auth.select.noPending").color(Color.YELLOW); + @Nonnull + private static final Message MESSAGE_SUCCESS = Message.translation("server.commands.auth.select.success").color(Color.GREEN); + @Nonnull + private static final Message MESSAGE_FAILED = Message.translation("server.commands.auth.select.failed").color(Color.RED); + @Nonnull + private static final Message MESSAGE_AVAILABLE_PROFILES = Message.translation("server.commands.auth.select.availableProfiles").color(Color.YELLOW); + @Nonnull + private static final Message MESSAGE_USAGE = Message.translation("server.commands.auth.select.usage").color(Color.GRAY); + + public AuthSelectCommand() { + super("select", "server.commands.auth.select.desc"); + this.addUsageVariant(new AuthSelectCommand.SelectProfileVariant()); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + ServerAuthManager authManager = ServerAuthManager.getInstance(); + if (!authManager.hasPendingProfiles()) { + context.sendMessage(MESSAGE_NO_PENDING); + } else { + SessionServiceClient.GameProfile[] profiles = authManager.getPendingProfiles(); + if (profiles != null) { + context.sendMessage(MESSAGE_AVAILABLE_PROFILES); + sendProfileList(context, profiles); + context.sendMessage(MESSAGE_USAGE); + } + } + } + + static void sendProfileList(@Nonnull CommandContext context, @Nonnull SessionServiceClient.GameProfile[] profiles) { + for (int i = 0; i < profiles.length; i++) { + context.sendMessage( + Message.translation("server.commands.auth.select.profileItem") + .param("index", i + 1) + .param("username", profiles[i].username) + .param("uuid", profiles[i].uuid.toString()) + ); + } + } + + private static class SelectProfileVariant extends CommandBase { + @Nonnull + private final RequiredArg profileArg = this.withRequiredArg("profile", "server.commands.auth.select.profile.desc", ArgTypes.STRING); + + SelectProfileVariant() { + super("server.commands.auth.select.variant.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + ServerAuthManager authManager = ServerAuthManager.getInstance(); + if (!authManager.hasPendingProfiles()) { + context.sendMessage(AuthSelectCommand.MESSAGE_NO_PENDING); + } else { + String selection = this.profileArg.get(context); + + boolean success; + try { + int index = Integer.parseInt(selection); + success = authManager.selectPendingProfile(index); + } catch (NumberFormatException var6) { + success = authManager.selectPendingProfileByUsername(selection); + } + + if (success) { + context.sendMessage(AuthSelectCommand.MESSAGE_SUCCESS); + } else { + context.sendMessage(AuthSelectCommand.MESSAGE_FAILED); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthStatusCommand.java b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthStatusCommand.java new file mode 100644 index 0000000..c3a08c0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/server/auth/AuthStatusCommand.java @@ -0,0 +1,121 @@ +package com.hypixel.hytale.server.core.command.commands.server.auth; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.auth.ServerAuthManager; +import com.hypixel.hytale.server.core.auth.SessionServiceClient; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import java.awt.Color; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class AuthStatusCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_STATUS_HEADER = Message.translation("server.commands.auth.status.format"); + @Nonnull + private static final Message MESSAGE_STATUS_CONNECTION_MODE_AUTHENTICATED = Message.translation("server.commands.auth.status.connectionMode.authenticated") + .color(Color.GREEN); + @Nonnull + private static final Message MESSAGE_STATUS_CONNECTION_MODE_OFFLINE = Message.translation("server.commands.auth.status.connectionMode.offline") + .color(Color.YELLOW); + @Nonnull + private static final Message MESSAGE_STATUS_CONNECTION_MODE_INSECURE = Message.translation("server.commands.auth.status.connectionMode.insecure") + .color(Color.ORANGE); + @Nonnull + private static final Message MESSAGE_STATUS_MODE_NONE = Message.translation("server.commands.auth.status.mode.none").color(Color.RED); + @Nonnull + private static final Message MESSAGE_STATUS_MODE_SINGLEPLAYER = Message.translation("server.commands.auth.status.mode.singleplayer").color(Color.GREEN); + @Nonnull + private static final Message MESSAGE_STATUS_MODE_EXTERNAL = Message.translation("server.commands.auth.status.mode.external").color(Color.GREEN); + @Nonnull + private static final Message MESSAGE_STATUS_MODE_OAUTH_BROWSER = Message.translation("server.commands.auth.status.mode.oauthBrowser").color(Color.GREEN); + @Nonnull + private static final Message MESSAGE_STATUS_MODE_OAUTH_DEVICE = Message.translation("server.commands.auth.status.mode.oauthDevice").color(Color.GREEN); + @Nonnull + private static final Message MESSAGE_STATUS_MODE_OAUTH_STORE = Message.translation("server.commands.auth.status.mode.oauthStore").color(Color.GREEN); + @Nonnull + private static final Message MESSAGE_STATUS_TOKEN_PRESENT = Message.translation("server.commands.auth.status.tokenPresent").color(Color.GREEN); + @Nonnull + private static final Message MESSAGE_STATUS_TOKEN_MISSING = Message.translation("server.commands.auth.status.tokenMissing").color(Color.RED); + @Nonnull + private static final Message MESSAGE_STATUS_HELP = Message.translation("server.commands.auth.status.help").color(Color.YELLOW); + @Nonnull + private static final Message MESSAGE_STATUS_PENDING = Message.translation("server.commands.auth.status.pending").color(Color.YELLOW); + + public AuthStatusCommand() { + super("status", "server.commands.auth.status.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + ServerAuthManager authManager = ServerAuthManager.getInstance(); + + Message connectionModeMessage = switch ((Options.AuthMode)Options.getOptionSet().valueOf(Options.AUTH_MODE)) { + case AUTHENTICATED -> MESSAGE_STATUS_CONNECTION_MODE_AUTHENTICATED; + case OFFLINE -> MESSAGE_STATUS_CONNECTION_MODE_OFFLINE; + case INSECURE -> MESSAGE_STATUS_CONNECTION_MODE_INSECURE; + }; + ServerAuthManager.AuthMode mode = authManager.getAuthMode(); + + Message modeMessage = switch (mode) { + case NONE -> MESSAGE_STATUS_MODE_NONE; + case SINGLEPLAYER -> MESSAGE_STATUS_MODE_SINGLEPLAYER; + case EXTERNAL_SESSION -> MESSAGE_STATUS_MODE_EXTERNAL; + case OAUTH_BROWSER -> MESSAGE_STATUS_MODE_OAUTH_BROWSER; + case OAUTH_DEVICE -> MESSAGE_STATUS_MODE_OAUTH_DEVICE; + case OAUTH_STORE -> MESSAGE_STATUS_MODE_OAUTH_STORE; + }; + String profileInfo = ""; + SessionServiceClient.GameProfile profile = authManager.getSelectedProfile(); + if (profile != null) { + String name = profile.username != null ? profile.username : "Unknown"; + profileInfo = name + " (" + profile.uuid + ")"; + } + + Message sessionTokenStatus = authManager.hasSessionToken() ? MESSAGE_STATUS_TOKEN_PRESENT : MESSAGE_STATUS_TOKEN_MISSING; + Message identityTokenStatus = authManager.hasIdentityToken() ? MESSAGE_STATUS_TOKEN_PRESENT : MESSAGE_STATUS_TOKEN_MISSING; + String expiryStatus = ""; + Instant expiry = authManager.getTokenExpiry(); + if (expiry != null) { + long secondsRemaining = expiry.getEpochSecond() - Instant.now().getEpochSecond(); + if (secondsRemaining > 0L) { + long hours = secondsRemaining / 3600L; + long minutes = secondsRemaining % 3600L / 60L; + long seconds = secondsRemaining % 60L; + expiryStatus = String.format("%02d:%02d:%02d remaining", hours, minutes, seconds); + } else { + expiryStatus = "EXPIRED"; + } + } + + String certificateStatus; + if (authManager.getServerCertificate() != null) { + String fingerprint = authManager.getServerCertificateFingerprint(); + certificateStatus = fingerprint != null ? fingerprint.substring(0, 16) + "..." : "Unknown"; + } else { + certificateStatus = "Not loaded"; + } + + context.sendMessage( + MESSAGE_STATUS_HEADER.param("connectionMode", connectionModeMessage) + .param("tokenMode", modeMessage) + .param("profile", profileInfo) + .param("sessionToken", sessionTokenStatus) + .param("identityToken", identityTokenStatus) + .param("expiry", expiryStatus) + .param("certificate", certificateStatus) + ); + if (mode == ServerAuthManager.AuthMode.NONE && !authManager.isSingleplayer()) { + if (authManager.hasPendingProfiles()) { + context.sendMessage(MESSAGE_STATUS_PENDING); + SessionServiceClient.GameProfile[] profiles = authManager.getPendingProfiles(); + if (profiles != null) { + AuthSelectCommand.sendProfileList(context, profiles); + } + } else { + context.sendMessage(MESSAGE_STATUS_HELP); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/BackupCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/BackupCommand.java new file mode 100644 index 0000000..6c45f64 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/BackupCommand.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.command.commands.utility; + +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.universe.Universe; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class BackupCommand extends AbstractAsyncCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_WAIT_FOR_BOOT = Message.translation("server.commands.errors.waitForBoot"); + @Nonnull + private static final Message MESSAGE_COMMANDS_BACKUP_NOT_CONFIGURED = Message.translation("server.commands.backup.notConfigured"); + @Nonnull + private static final Message MESSAGE_COMMANDS_BACKUP_STARTING = Message.translation("server.commands.backup.starting"); + @Nonnull + private static final Message MESSAGE_COMMANDS_BACKUP_COMPLETE = Message.translation("server.commands.backup.complete"); + + public BackupCommand() { + super("backup", "server.commands.backup.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + if (!HytaleServer.get().isBooted()) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_WAIT_FOR_BOOT); + return CompletableFuture.completedFuture(null); + } else if (!Options.getOptionSet().has(Options.BACKUP_DIRECTORY)) { + context.sendMessage(MESSAGE_COMMANDS_BACKUP_NOT_CONFIGURED); + return CompletableFuture.completedFuture(null); + } else { + context.sendMessage(MESSAGE_COMMANDS_BACKUP_STARTING); + return Universe.get().runBackup().thenAccept(aVoid -> context.sendMessage(MESSAGE_COMMANDS_BACKUP_COMPLETE)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/ConvertPrefabsCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/ConvertPrefabsCommand.java new file mode 100644 index 0000000..32693d0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/ConvertPrefabsCommand.java @@ -0,0 +1,320 @@ +package com.hypixel.hytale.server.core.command.commands.utility; + +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.prefab.PrefabStore; +import com.hypixel.hytale.server.core.prefab.config.SelectionPrefabSerializer; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.storage.provider.EmptyChunkStorageProvider; +import com.hypixel.hytale.server.core.universe.world.storage.resources.EmptyResourceStorageProvider; +import com.hypixel.hytale.server.core.universe.world.worldgen.provider.DummyWorldGenProvider; +import com.hypixel.hytale.server.core.util.BsonUtil; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public class ConvertPrefabsCommand extends AbstractAsyncCommand { + private static final String UNABLE_TO_LOAD_MODEL = "Unable to load entity with model "; + private static final String FAILED_TO_FIND_BLOCK = "Failed to find block "; + private static final int BATCH_SIZE = 10; + private static final long DELAY_BETWEEN_BATCHES_MS = 50L; + @Nonnull + private static final Message MESSAGE_COMMANDS_CONVERT_PREFABS_FAILED = Message.translation("server.commands.convertprefabs.failed"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CONVERT_PREFABS_DEFAULT_WORLD_NULL = Message.translation("server.commands.convertprefabs.defaultWorldNull"); + @Nonnull + private final FlagArg blocksFlag = this.withFlagArg("blocks", "server.commands.convertprefabs.blocks.desc"); + @Nonnull + private final FlagArg fillerFlag = this.withFlagArg("filler", "server.commands.convertprefabs.filler.desc"); + @Nonnull + private final FlagArg relativeFlag = this.withFlagArg("relative", "server.commands.convertprefabs.relative.desc"); + @Nonnull + private final FlagArg entitiesFlag = this.withFlagArg("entities", "server.commands.convertprefabs.entities.desc"); + private final FlagArg destructiveFlag = this.withFlagArg("destructive", "server.commands.convertprefabs.destructive.desc"); + @Nonnull + private final OptionalArg pathArg = this.withOptionalArg("path", "server.commands.convertprefabs.path.desc", ArgTypes.STRING); + @Nonnull + private final DefaultArg storeArg = this.withDefaultArg( + "store", "server.commands.convertprefabs.store.desc", ArgTypes.STRING, "asset", "server.commands.convertprefabs.store.defaultDesc" + ); + + public ConvertPrefabsCommand() { + super("convertprefabs", "server.commands.convertprefabs.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + boolean blocks = this.blocksFlag.get(context); + boolean filler = this.fillerFlag.get(context); + boolean relative = this.relativeFlag.get(context); + boolean entities = this.entitiesFlag.get(context); + boolean destructive = this.destructiveFlag.get(context); + World defaultWorld = Universe.get().getDefaultWorld(); + if (defaultWorld == null) { + context.sendMessage(MESSAGE_COMMANDS_CONVERT_PREFABS_DEFAULT_WORLD_NULL); + return CompletableFuture.completedFuture(null); + } else { + defaultWorld.getChunk(ChunkUtil.indexChunk(0, 0)); + List failed = new ObjectArrayList<>(); + List skipped = new ObjectArrayList<>(); + String storeOption = this.storeArg.get(context); + if (this.pathArg.provided(context)) { + Path assetPath = Paths.get(this.pathArg.get(context)); + return this.convertPath(assetPath, blocks, filler, relative, entities, destructive, failed, skipped).thenApply(_v -> { + this.sendCompletionMessages(context, assetPath, failed, skipped); + return null; + }); + } else { + return switch (storeOption) { + case "server" -> { + Path assetPath = PrefabStore.get().getServerPrefabsPath(); + yield this.convertPath(assetPath, blocks, filler, relative, entities, destructive, failed, skipped).thenApply(_v -> { + this.sendCompletionMessages(context, assetPath, failed, skipped); + return null; + }); + } + case "asset" -> { + Path assetPath = PrefabStore.get().getAssetPrefabsPath(); + yield this.convertPath(assetPath, blocks, filler, relative, entities, destructive, failed, skipped).thenApply(_v -> { + this.sendCompletionMessages(context, assetPath, failed, skipped); + return null; + }); + } + case "worldgen" -> { + Path assetPath = PrefabStore.get().getWorldGenPrefabsPath(); + yield this.convertPath(assetPath, blocks, filler, relative, entities, destructive, failed, skipped).thenApply(_v -> { + this.sendCompletionMessages(context, assetPath, failed, skipped); + return null; + }); + } + case "all" -> { + Path assetPath = Path.of(""); + yield this.convertPath(PrefabStore.get().getWorldGenPrefabsPath(), blocks, filler, relative, entities, destructive, failed, skipped) + .thenCompose( + _v -> this.convertPath(PrefabStore.get().getServerPrefabsPath(), blocks, filler, relative, entities, destructive, failed, skipped) + ) + .thenCompose( + _v -> this.convertPath(PrefabStore.get().getAssetPrefabsPath(), blocks, filler, relative, entities, destructive, failed, skipped) + ) + .thenApply(_v -> { + this.sendCompletionMessages(context, assetPath, failed, skipped); + return null; + }); + } + default -> { + context.sendMessage(Message.translation("server.commands.convertprefabs.invalidStore").param("store", storeOption)); + yield CompletableFuture.completedFuture(null); + } + }; + } + } + } + + private void sendCompletionMessages(@Nonnull CommandContext context, @Nonnull Path assetPath, @Nonnull List failed, @Nonnull List skipped) { + if (!skipped.isEmpty()) { + Message header = Message.translation("server.commands.convertprefabs.skipped"); + context.sendMessage(MessageFormat.list(header, skipped.stream().map(Message::raw).collect(Collectors.toSet()))); + } + + if (!failed.isEmpty()) { + context.sendMessage(MessageFormat.list(MESSAGE_COMMANDS_CONVERT_PREFABS_FAILED, failed.stream().map(Message::raw).collect(Collectors.toSet()))); + } + + context.sendMessage(Message.translation("server.commands.prefabConvertionDone").param("path", assetPath.toString())); + } + + @Nonnull + private CompletableFuture convertPath( + @Nonnull Path assetPath, + boolean blocks, + boolean filler, + boolean relative, + boolean entities, + boolean destructive, + @Nonnull List failed, + @Nonnull List skipped + ) { + if (!Files.exists(assetPath)) { + return CompletableFuture.completedFuture(null); + } else { + CompletableFuture conversionWorldFuture; + if (!entities && !blocks) { + conversionWorldFuture = null; + } else { + Universe universe = Universe.get(); + WorldConfig config = new WorldConfig(); + config.setWorldGenProvider(new DummyWorldGenProvider()); + config.setChunkStorageProvider(EmptyChunkStorageProvider.INSTANCE); + config.setResourceStorageProvider(EmptyResourceStorageProvider.INSTANCE); + + try { + conversionWorldFuture = universe.makeWorld("ConvertPrefabs-" + UUID.randomUUID(), Files.createTempDirectory("convertprefab"), config); + } catch (IOException var14) { + throw SneakyThrow.sneakyThrow(var14); + } + } + + try { + CompletableFuture e; + try (Stream stream = Files.walk(assetPath, FileUtil.DEFAULT_WALK_TREE_OPTIONS_ARRAY)) { + List prefabPaths = stream.filter(path -> Files.isRegularFile(path) && path.toString().endsWith(".prefab.json")) + .collect(Collectors.toList()); + if (prefabPaths.isEmpty()) { + if (conversionWorldFuture != null) { + conversionWorldFuture.thenAccept(world -> Universe.get().removeWorld(world.getName())); + } + + return CompletableFuture.completedFuture(null); + } + + e = this.processPrefabsInBatches(prefabPaths, blocks, filler, relative, entities, destructive, conversionWorldFuture, failed, skipped) + .thenApply(_v -> { + if (conversionWorldFuture != null) { + conversionWorldFuture.thenAccept(world -> Universe.get().removeWorld(world.getName())); + } + + return null; + }); + } + + return e; + } catch (IOException var16) { + throw SneakyThrow.sneakyThrow(var16); + } + } + } + + @Nonnull + private CompletableFuture processPrefabsInBatches( + @Nonnull List prefabPaths, + boolean blocks, + boolean filler, + boolean relative, + boolean entities, + boolean destructive, + @Nullable CompletableFuture conversionWorldFuture, + @Nonnull List failed, + @Nonnull List skipped + ) { + CompletableFuture result = CompletableFuture.completedFuture(null); + + for (int i = 0; i < prefabPaths.size(); i += 10) { + int batchEnd = Math.min(i + 10, prefabPaths.size()); + List batch = prefabPaths.subList(i, batchEnd); + int batchIndex = i / 10; + if (batchIndex > 0) { + result = result.thenCompose(_v -> CompletableFuture.runAsync(() -> {}, CompletableFuture.delayedExecutor(50L, TimeUnit.MILLISECONDS))); + } + + CompletableFuture[] batchFutures = batch.stream() + .map(path -> this.processPrefab(path, blocks, filler, relative, entities, destructive, conversionWorldFuture, failed, skipped)) + .toArray(CompletableFuture[]::new); + result = result.thenCompose(_v -> CompletableFuture.allOf(batchFutures)); + } + + return result; + } + + @Nonnull + private CompletableFuture processPrefab( + @Nonnull Path path, + boolean blocks, + boolean filler, + boolean relative, + boolean entities, + boolean destructive, + @Nullable CompletableFuture conversionWorldFuture, + @Nonnull List failed, + @Nonnull List skipped + ) { + return BsonUtil.readDocument(path, false) + .thenApply(document -> { + BlockSelection prefab = SelectionPrefabSerializer.deserialize(document); + if (filler) { + prefab.tryFixFiller(destructive); + } + + if (relative) { + prefab = prefab.relativize(); + } + + return prefab; + }) + .thenCompose(prefab -> entities && conversionWorldFuture != null ? conversionWorldFuture.thenCompose(world -> CompletableFuture.runAsync(() -> { + try { + prefab.reserializeEntities(world.getEntityStore().getStore(), destructive); + } catch (IOException var4x) { + throw SneakyThrow.sneakyThrow(var4x); + } + }, world)).thenApply(_v -> prefab) : CompletableFuture.completedFuture(prefab)) + .thenCompose( + prefab -> blocks && conversionWorldFuture != null + ? conversionWorldFuture.thenCompose( + world -> CompletableFuture.runAsync(() -> prefab.reserializeBlockStates(world.getChunkStore(), destructive), world) + ) + .thenApply(_v -> prefab) + : CompletableFuture.completedFuture(prefab) + ) + .thenCompose(prefab -> { + BsonDocument newDocument = SelectionPrefabSerializer.serialize(prefab); + return BsonUtil.writeDocument(path, newDocument, false); + }) + .exceptionally( + throwable -> { + String message = throwable.getCause() != null ? throwable.getCause().getMessage() : null; + if (message != null) { + if (message.contains("Failed to find block ")) { + if (message.substring("Failed to find block ".length()).contains("%")) { + skipped.add("Skipped prefab " + path + " because it contains block % chance."); + return null; + } + + failed.add("Failed to update " + path + " because " + message); + return null; + } + + if (message.contains("Unable to load entity with model ")) { + failed.add("Failed to update " + path + " because " + message); + return null; + } + } + + failed.add( + "Failed to update " + + path + + " because " + + (message != null ? message : (throwable.getCause() != null ? throwable.getCause().getClass() : throwable.getClass())) + ); + if (throwable.getCause() != null) { + new Exception("Failed to update " + path, throwable.getCause()).printStackTrace(); + } + + return null; + } + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/EventTitleCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/EventTitleCommand.java new file mode 100644 index 0000000..02b830a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/EventTitleCommand.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.server.core.command.commands.utility; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.util.EventTitleUtil; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; + +public class EventTitleCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_EVENT_TITLE_TITLE_REQUIRED = Message.translation("server.commands.eventtitle.titleRequired"); + @Nonnull + private final FlagArg majorFlag = this.withFlagArg("major", "server.commands.eventtitle.major.desc"); + @Nonnull + private final DefaultArg secondaryTitleArg = this.withDefaultArg( + "secondary", "server.commands.eventtitle.secondary.desc", ArgTypes.STRING, "Event", "server.commands.eventtitle.secondary.defaultDesc" + ); + @Nonnull + private final OptionalArg primaryTitleArg = this.withOptionalArg("title", "server.commands.eventtitle.title.desc", ArgTypes.STRING); + + public EventTitleCommand() { + super("eventtitle", "server.commands.eventtitle.desc"); + this.setAllowsExtraArguments(true); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + String primaryTitleText; + if (this.primaryTitleArg.provided(context)) { + primaryTitleText = this.primaryTitleArg.get(context).replace("\"", ""); + } else { + String inputString = context.getInputString(); + String rawArgs = CommandUtil.stripCommandName(inputString); + if (rawArgs.trim().isEmpty()) { + context.sendMessage(MESSAGE_COMMANDS_EVENT_TITLE_TITLE_REQUIRED); + return; + } + + primaryTitleText = this.extractTitleFromRawInput(rawArgs, context); + if (primaryTitleText.trim().isEmpty()) { + context.sendMessage(MESSAGE_COMMANDS_EVENT_TITLE_TITLE_REQUIRED); + return; + } + } + + Message primaryTitle = Message.raw(primaryTitleText); + Message secondaryTitle = Message.raw(this.secondaryTitleArg.get(context)); + boolean isMajor = this.majorFlag.get(context); + + for (World world : Universe.get().getWorlds().values()) { + for (PlayerRef playerRef : world.getPlayerRefs()) { + EventTitleUtil.showEventTitleToPlayer(playerRef, primaryTitle, secondaryTitle, isMajor); + } + } + } + + @Nonnull + private String extractTitleFromRawInput(@Nonnull String rawArgs, @Nonnull CommandContext context) { + String titleText = rawArgs.trim(); + if (this.majorFlag.get(context)) { + titleText = titleText.replaceAll("--major\\b", "").trim(); + } + + if (this.secondaryTitleArg.provided(context)) { + String secondaryValue = this.secondaryTitleArg.get(context); + titleText = titleText.replaceAll("--secondary\\s*=\\s*" + Pattern.quote(secondaryValue), ""); + titleText = titleText.replaceAll("--secondary\\s+" + Pattern.quote(secondaryValue), ""); + } else { + titleText = titleText.replaceAll("--secondary\\s*=\\s*[^\\s]+", ""); + titleText = titleText.replaceAll("--secondary\\s+[^\\s]+", ""); + } + + return titleText.trim(); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/NotifyCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/NotifyCommand.java new file mode 100644 index 0000000..0b4736e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/NotifyCommand.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.server.core.command.commands.utility; + +import com.hypixel.hytale.protocol.packets.interface_.NotificationStyle; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.util.NotificationUtil; +import javax.annotation.Nonnull; + +public class NotifyCommand extends CommandBase { + public NotifyCommand() { + super("notify", "server.commands.notify.desc"); + this.setAllowsExtraArguments(true); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + String inputString = context.getInputString(); + String rawArgs = CommandUtil.stripCommandName(inputString).trim(); + if (rawArgs.isEmpty()) { + context.sendMessage(Message.translation("server.commands.parsing.error.wrongNumberRequiredParameters").param("expected", 1).param("actual", 0)); + } else { + String[] args = rawArgs.split("\\s+"); + if (args.length == 0) { + context.sendMessage(Message.translation("server.commands.parsing.error.wrongNumberRequiredParameters").param("expected", 1).param("actual", 0)); + } else { + NotificationStyle style = NotificationStyle.Default; + int messageStartIndex = 0; + if (args.length >= 2) { + String firstArg = args[0]; + if (!firstArg.startsWith("{")) { + try { + style = NotificationStyle.valueOf(firstArg.toUpperCase()); + messageStartIndex = 1; + } catch (IllegalArgumentException var12) { + } + } + } + + StringBuilder messageBuilder = new StringBuilder(); + + for (int i = messageStartIndex; i < args.length; i++) { + if (i > messageStartIndex) { + messageBuilder.append(' '); + } + + messageBuilder.append(args[i]); + } + + String messageString = messageBuilder.toString(); + Message message; + if (messageString.startsWith("{")) { + try { + message = Message.parse(messageString); + } catch (IllegalArgumentException var11) { + context.sendMessage(Message.raw("Invalid formatted message: " + var11.getMessage())); + return; + } + } else { + message = Message.raw(messageString); + } + + Message senderName = Message.raw(context.sender().getDisplayName()); + NotificationUtil.sendNotificationToUniverse(message, senderName, "announcement", null, style); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/StashCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/StashCommand.java new file mode 100644 index 0000000..0f91d5b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/StashCommand.java @@ -0,0 +1,110 @@ +package com.hypixel.hytale.server.core.command.commands.utility; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.meta.state.ItemContainerState; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import com.hypixel.hytale.server.core.util.TargetUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StashCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_CHUNK_NOT_LOADED = Message.translation("server.commands.errors.chunkNotLoaded"); + @Nonnull + private static final Message MESSAGE_COMMANDS_STASH_DROP_LIST_SET = Message.translation("server.commands.stash.droplistSet"); + @Nonnull + private static final Message MESSAGE_COMMANDS_STASH_NO_DROP_LIST = Message.translation("server.commands.stash.noDroplist"); + @Nonnull + private static final Message MESSAGE_GENERAL_BLOCK_TARGET_NOT_IN_RANGE = Message.translation("server.general.blockTargetNotInRange"); + private static final int DISTANCE_MAX = 10; + @Nonnull + private final OptionalArg setArg = this.withOptionalArg("set", "server.commands.stash.setDroplist.desc", ArgTypes.STRING); + + public StashCommand() { + super("stash", "server.commands.stash.getDroplist.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + ItemContainerState itemContainerState = this.getItemContainerState(ref, world, context, store); + if (itemContainerState != null) { + if (this.setArg.provided(context)) { + String dropList = this.setArg.get(context); + itemContainerState.setDroplist(dropList); + context.sendMessage(MESSAGE_COMMANDS_STASH_DROP_LIST_SET); + } else { + String droplist = itemContainerState.getDroplist(); + if (droplist != null) { + context.sendMessage(Message.translation("server.commands.stash.currentDroplist").param("droplist", droplist)); + } else { + context.sendMessage(MESSAGE_COMMANDS_STASH_NO_DROP_LIST); + } + } + } + } + + @Nullable + private ItemContainerState getItemContainerState( + @Nonnull Ref ref, @Nonnull World world, @Nonnull CommandContext context, @Nonnull ComponentAccessor componentAccessor + ) { + Vector3i block = TargetUtil.getTargetBlock(ref, 10.0, componentAccessor); + if (block == null) { + context.sendMessage(MESSAGE_GENERAL_BLOCK_TARGET_NOT_IN_RANGE); + return null; + } else { + ChunkStore chunkStore = world.getChunkStore(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(block.x, block.z); + Ref chunkRef = chunkStore.getChunkReference(chunkIndex); + if (chunkRef != null && chunkRef.isValid()) { + Store chunkStoreStore = chunkStore.getStore(); + BlockChunk blockChunkComponent = chunkStoreStore.getComponent(chunkRef, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + WorldChunk worldChunkComponent = chunkStoreStore.getComponent(chunkRef, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + BlockSection section = blockChunkComponent.getSectionAtBlockY(block.y); + int filler = section.getFiller(block.x, block.y, block.z); + if (filler != 0) { + block.x = block.x - FillerBlockUtil.unpackX(filler); + block.y = block.y - FillerBlockUtil.unpackY(filler); + block.z = block.z - FillerBlockUtil.unpackZ(filler); + } + + BlockState state = worldChunkComponent.getState(block.x, block.y, block.z); + if (!(state instanceof ItemContainerState)) { + context.sendMessage(Message.translation("server.general.containerNotFound").param("block", block.toString())); + return null; + } else { + return (ItemContainerState)state; + } + } else { + int chunkX = ChunkUtil.chunkCoordinate(block.x); + int chunkZ = ChunkUtil.chunkCoordinate(block.z); + context.sendMessage(MESSAGE_COMMANDS_ERRORS_CHUNK_NOT_LOADED.param("chunkX", chunkX).param("chunkZ", chunkZ).param("world", world.getName())); + return null; + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/ValidateCPBCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/ValidateCPBCommand.java new file mode 100644 index 0000000..ca7656a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/ValidateCPBCommand.java @@ -0,0 +1,87 @@ +package com.hypixel.hytale.server.core.command.commands.utility; + +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.prefab.selection.buffer.BsonPrefabBufferDeserializer; +import com.hypixel.hytale.server.core.util.BsonUtil; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +public class ValidateCPBCommand extends AbstractAsyncCommand { + private static final String UNABLE_TO_LOAD_MODEL = "Unable to load entity with model "; + private static final String FAILED_TO_FIND_BLOCK = "Failed to find block "; + @Nonnull + private final OptionalArg pathArg = this.withOptionalArg("path", "server.commands.validatecpb.path.desc", ArgTypes.STRING); + + public ValidateCPBCommand() { + super("validatecpb", "server.commands.validatecpb.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + if (this.pathArg.provided(context)) { + String path = this.pathArg.get(context); + return CompletableFuture.runAsync(() -> convertPrefabs(context, PathUtil.get(path))); + } else { + return CompletableFuture.runAsync(() -> { + for (AssetPack pack : AssetModule.get().getAssetPacks()) { + convertPrefabs(context, pack.getRoot()); + } + }); + } + } + + private static void convertPrefabs(@Nonnull CommandContext context, @Nonnull Path assetPath) { + List failed = new ObjectArrayList<>(); + + try (Stream stream = Files.walk(assetPath, FileUtil.DEFAULT_WALK_TREE_OPTIONS_ARRAY)) { + CompletableFuture[] futures = stream.filter(path -> Files.isRegularFile(path) && path.toString().endsWith(".prefab.json")) + .map(path -> BsonUtil.readDocument(path, false).thenAccept(document -> { + BsonPrefabBufferDeserializer.INSTANCE.deserialize(path, document); + context.sendMessage(Message.translation("server.general.loadedPrefab").param("name", path.toString())); + }).exceptionally(throwable -> { + String message = throwable.getCause().getMessage(); + if (message != null) { + if (message.contains("Failed to find block ")) { + failed.add("Failed to load " + path + " because " + message); + return null; + } + + if (message.contains("Unable to load entity with model ")) { + failed.add("Failed to load " + path + " because " + message); + return null; + } + } + + failed.add("Failed to load " + path + " because " + (message != null ? message : throwable.getCause().getClass())); + new Exception("Failed to load " + path, throwable.getCause()).printStackTrace(); + return null; + })) + .toArray(CompletableFuture[]::new); + CompletableFuture.allOf(futures).join(); + } catch (IOException var8) { + throw SneakyThrow.sneakyThrow(var8); + } + + if (!failed.isEmpty()) { + context.sendMessage(Message.translation("server.commands.validatecpb.failed").param("failed", failed.toString())); + } + + context.sendMessage(Message.translation("server.commands.prefabConvertionDone").param("path", assetPath.toString())); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/git/UpdateAssetsCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/git/UpdateAssetsCommand.java new file mode 100644 index 0000000..8f4fb09 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/git/UpdateAssetsCommand.java @@ -0,0 +1,127 @@ +package com.hypixel.hytale.server.core.command.commands.utility.git; + +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.util.AssetUtil; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class UpdateAssetsCommand extends AbstractCommandCollection { + public UpdateAssetsCommand() { + super("assets", "server.commands.update.assets.desc"); + this.addSubCommand(new UpdateAssetsCommand.UpdateAssetsStatusCommand()); + this.addSubCommand(new UpdateAssetsCommand.UpdateAssetsResetCommand()); + this.addSubCommand(new UpdateAssetsCommand.UpdateAssetsPullCommand()); + } + + private abstract static class UpdateAssetsGitCommand extends AbstractAsyncCommand { + protected UpdateAssetsGitCommand(@Nonnull String name, @Nonnull String description) { + super(name, description); + } + + @Nonnull + protected abstract String[] getCommand(@Nonnull Path var1); + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + return CompletableFuture.runAsync(() -> { + Path assetPath = AssetUtil.getHytaleAssetsPath(); + Path gitPath = null; + if (Files.exists(assetPath.resolve(".git"))) { + gitPath = assetPath; + } else { + Path parent = PathUtil.getParent(assetPath.toAbsolutePath()); + if (Files.exists(parent.resolve(".git"))) { + gitPath = parent; + } + } + + if (gitPath == null) { + context.sendMessage(Message.translation("server.general.pathNotGitRepo").param("path", assetPath.toString())); + } else { + String[] processCommand = this.getCommand(gitPath); + String commandDisplay = String.join(" ", processCommand); + + try { + context.sendMessage(Message.translation("server.commands.update.running").param("cmd", commandDisplay)); + Process process = new ProcessBuilder(processCommand).directory(gitPath.toFile()).start(); + + try { + process.waitFor(); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)); + + String line; + while ((line = reader.readLine()) != null) { + context.sendMessage(Message.translation("server.commands.update.runningStdOut").param("cmd", commandDisplay).param("line", line)); + } + + reader = new BufferedReader(new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8)); + + while ((line = reader.readLine()) != null) { + context.sendMessage(Message.translation("server.commands.update.runningStdErr").param("cmd", commandDisplay).param("line", line)); + } + + context.sendMessage(Message.translation("server.commands.update.done").param("cmd", commandDisplay)); + } catch (InterruptedException var9) { + Thread.currentThread().interrupt(); + } + } catch (IOException var10) { + context.sendMessage(Message.translation("server.commands.update.failed").param("cmd", commandDisplay).param("msg", var10.getMessage())); + } + } + }); + } + } + + private static class UpdateAssetsPullCommand extends UpdateAssetsCommand.UpdateAssetsGitCommand { + public UpdateAssetsPullCommand() { + super("pull", "server.commands.update.assets.pull.desc"); + } + + @Nonnull + @Override + protected String[] getCommand(@Nonnull Path gitPath) { + Path script = gitPath.resolve("../../updateAssets.sh"); + if (Files.exists(script)) { + Path relative = gitPath.relativize(script); + return new String[]{"sh", relative.toString()}; + } else { + return new String[]{"git", "pull"}; + } + } + } + + private static class UpdateAssetsResetCommand extends UpdateAssetsCommand.UpdateAssetsGitCommand { + public UpdateAssetsResetCommand() { + super("reset", "server.commands.update.assets.reset.desc"); + } + + @Nonnull + @Override + protected String[] getCommand(@Nonnull Path gitPath) { + return new String[]{"git", "reset", "--hard", "head"}; + } + } + + private static class UpdateAssetsStatusCommand extends UpdateAssetsCommand.UpdateAssetsGitCommand { + public UpdateAssetsStatusCommand() { + super("status", "server.commands.update.assets.status.desc"); + } + + @Nonnull + @Override + protected String[] getCommand(@Nonnull Path gitPath) { + return new String[]{"git", "status"}; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/git/UpdateCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/git/UpdateCommand.java new file mode 100644 index 0000000..2f4583a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/git/UpdateCommand.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.command.commands.utility.git; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class UpdateCommand extends AbstractCommandCollection { + public UpdateCommand() { + super("update", "server.commands.update.desc"); + this.addSubCommand(new UpdateAssetsCommand()); + this.addSubCommand(new UpdatePrefabsCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/git/UpdatePrefabsCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/git/UpdatePrefabsCommand.java new file mode 100644 index 0000000..fc6e62a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/git/UpdatePrefabsCommand.java @@ -0,0 +1,170 @@ +package com.hypixel.hytale.server.core.command.commands.utility.git; + +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.prefab.PrefabStore; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class UpdatePrefabsCommand extends AbstractCommandCollection { + public UpdatePrefabsCommand() { + super("prefabs", "server.commands.update.prefabs.desc"); + this.addSubCommand(new UpdatePrefabsCommand.UpdatePrefabsStatusCommand()); + this.addSubCommand(new UpdatePrefabsCommand.UpdatePrefabsCommitCommand()); + this.addSubCommand(new UpdatePrefabsCommand.UpdatePrefabsPullCommand()); + this.addSubCommand(new UpdatePrefabsCommand.UpdatePrefabsPushCommand()); + this.addSubCommand(new UpdatePrefabsCommand.UpdatePrefabsAllCommand()); + } + + private static class UpdatePrefabsAllCommand extends UpdatePrefabsCommand.UpdatePrefabsGitCommand { + public UpdatePrefabsAllCommand() { + super("all", "server.commands.update.prefabs.all.desc"); + } + + @Nonnull + @Override + protected String[][] getCommands(@Nonnull String senderDisplayName) { + return new String[][]{ + {"git", "submodule", "foreach", "git", "add", "--all", "."}, + {"git", "submodule", "foreach", "git", "commit", "-am", "\"Update prefabs by " + senderDisplayName + "\""}, + {"git", "submodule", "foreach", "git", "pull"}, + {"git", "submodule", "foreach", "git", "push"}, + {"git", "add", "--all", "."}, + {"git", "commit", "-am", "\"Update prefabs by " + senderDisplayName + "\""}, + {"git", "pull"}, + {"git", "push"} + }; + } + } + + private static class UpdatePrefabsCommitCommand extends UpdatePrefabsCommand.UpdatePrefabsGitCommand { + public UpdatePrefabsCommitCommand() { + super("commit", "server.commands.update.prefabs.commit.desc"); + } + + @Nonnull + @Override + protected String[][] getCommands(@Nonnull String senderDisplayName) { + return new String[][]{ + {"git", "add", "--all", "."}, + {"git", "commit", "-am", "\"Update prefabs by " + senderDisplayName + "\""}, + {"git", "submodule", "foreach", "git", "add", "--all", "."}, + {"git", "submodule", "foreach", "git", "commit", "-am", "\"Update prefabs by " + senderDisplayName + "\""} + }; + } + } + + private abstract static class UpdatePrefabsGitCommand extends AbstractAsyncCommand { + protected UpdatePrefabsGitCommand(@Nonnull String name, @Nonnull String description) { + super(name, description); + } + + @Nonnull + protected abstract String[][] getCommands(@Nonnull String var1); + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + return CompletableFuture.runAsync( + () -> { + Path prefabsPath = PrefabStore.get().getServerPrefabsPath(); + Path gitPath = null; + if (Files.isDirectory(prefabsPath.resolve(".git"))) { + gitPath = prefabsPath; + } else { + Path parent = PathUtil.getParent(prefabsPath); + if (Files.isDirectory(parent.resolve(".git"))) { + gitPath = parent; + } + } + + if (gitPath == null) { + context.sendMessage(Message.translation("server.general.pathNotGitRepo").param("path", prefabsPath.toString())); + } else { + String senderDisplayName = context.sender().getDisplayName(); + String[][] cmds = this.getCommands(senderDisplayName); + + for (String[] processCommand : cmds) { + try { + String commandDisplay = String.join(" ", processCommand); + context.sendMessage(Message.translation("server.commands.update.runningCmd").param("cmd", commandDisplay)); + Process process = new ProcessBuilder(processCommand).directory(gitPath.toFile()).start(); + + try { + process.waitFor(); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)); + + String line; + while ((line = reader.readLine()) != null) { + context.sendMessage(Message.translation("server.commands.update.runningStdOut").param("cmd", commandDisplay).param("line", line)); + } + + reader = new BufferedReader(new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8)); + + while ((line = reader.readLine()) != null) { + context.sendMessage(Message.translation("server.commands.update.runningStdErr").param("cmd", commandDisplay).param("line", line)); + } + + context.sendMessage(Message.translation("server.commands.update.done").param("cmd", commandDisplay)); + } catch (InterruptedException var14) { + Thread.currentThread().interrupt(); + break; + } + } catch (IOException var15) { + context.sendMessage( + Message.translation("server.commands.update.failed").param("cmd", String.join(" ", processCommand)).param("msg", var15.getMessage()) + ); + break; + } + } + } + } + ); + } + } + + private static class UpdatePrefabsPullCommand extends UpdatePrefabsCommand.UpdatePrefabsGitCommand { + public UpdatePrefabsPullCommand() { + super("pull", "server.commands.update.prefabs.pull.desc"); + } + + @Nonnull + @Override + protected String[][] getCommands(@Nonnull String senderDisplayName) { + return new String[][]{{"git", "pull"}, {"git", "submodule", "foreach", "git", "pull"}}; + } + } + + private static class UpdatePrefabsPushCommand extends UpdatePrefabsCommand.UpdatePrefabsGitCommand { + public UpdatePrefabsPushCommand() { + super("push", "server.commands.update.prefabs.push.desc"); + } + + @Nonnull + @Override + protected String[][] getCommands(@Nonnull String senderDisplayName) { + return new String[][]{{"git", "push", "origin", "master"}, {"git", "submodule", "foreach", "git", "push"}}; + } + } + + private static class UpdatePrefabsStatusCommand extends UpdatePrefabsCommand.UpdatePrefabsGitCommand { + public UpdatePrefabsStatusCommand() { + super("status", "server.commands.update.prefabs.status.desc"); + } + + @Nonnull + @Override + protected String[][] getCommands(@Nonnull String senderDisplayName) { + return new String[][]{{"git", "status"}, {"git", "submodule", "foreach", "git", "status"}}; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/help/HelpCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/help/HelpCommand.java new file mode 100644 index 0000000..fe6c4e9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/help/HelpCommand.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.server.core.command.commands.utility.help; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandManager; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.command.system.pages.CommandListPage; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HelpCommand extends AbstractAsyncCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + + public HelpCommand() { + super("help", "server.commands.help.desc"); + this.addAliases("?"); + this.setPermissionGroup(GameMode.Adventure); + this.addUsageVariant(new HelpCommand.HelpCommandVariant()); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + return openHelpUI(context, null); + } + + @Nonnull + static CompletableFuture openHelpUI(@Nonnull CommandContext context, @Nullable String initialCommand) { + if (context.isPlayer()) { + Ref playerRef = context.senderAsPlayerRef(); + if (playerRef != null && playerRef.isValid()) { + Store store = playerRef.getStore(); + World world = store.getExternalData().getWorld(); + String resolvedCommand = resolveCommandName(initialCommand); + return CompletableFuture.runAsync(() -> { + Player playerComponent = store.getComponent(playerRef, Player.getComponentType()); + PlayerRef playerRefComponent = store.getComponent(playerRef, PlayerRef.getComponentType()); + if (playerComponent != null && playerRefComponent != null) { + playerComponent.getPageManager().openCustomPage(playerRef, store, new CommandListPage(playerRefComponent, resolvedCommand)); + } + }, world); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + return CompletableFuture.completedFuture(null); + } + } else { + return CompletableFuture.completedFuture(null); + } + } + + @Nullable + private static String resolveCommandName(@Nullable String commandNameOrAlias) { + if (commandNameOrAlias == null) { + return null; + } else { + String lowerName = commandNameOrAlias.toLowerCase(); + Map commands = CommandManager.get().getCommandRegistration(); + if (commands.containsKey(lowerName)) { + return lowerName; + } else { + for (Entry entry : commands.entrySet()) { + Set aliases = entry.getValue().getAliases(); + if (aliases != null && aliases.contains(lowerName)) { + return entry.getKey(); + } + } + + return lowerName; + } + } + } + + private static class HelpCommandVariant extends AbstractAsyncCommand { + @Nonnull + private final RequiredArg commandArg = this.withRequiredArg("command", "server.commands.help.command.name.desc", ArgTypes.STRING); + + HelpCommandVariant() { + super("server.commands.help.command.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + String commandName = this.commandArg.get(context); + return HelpCommand.openHelpUI(context, commandName); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingCalculationCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingCalculationCommand.java new file mode 100644 index 0000000..017d692 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingCalculationCommand.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.server.core.command.commands.utility.lighting; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.lighting.ChunkLightingManager; +import com.hypixel.hytale.server.core.universe.world.lighting.FloodLightCalculation; +import com.hypixel.hytale.server.core.universe.world.lighting.FullBrightLightCalculation; +import com.hypixel.hytale.server.core.universe.world.lighting.LightCalculation; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class LightingCalculationCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_INVALIDATE_LIGHTING = Message.translation("server.commands.invalidatedlighting"); + @Nonnull + private static final Message MESSAGE_COMMANDS_LIGHTING_CALCULATION_ALREADY_FULLBRIGHT = Message.translation( + "server.commands.lightingcalculation.alreadyFullBright" + ); + @Nonnull + private final RequiredArg calculationTypeArg = this.withRequiredArg( + "light-calculation", + "server.commands.lightingcalculation.calculation.desc", + ArgTypes.forEnum("server.commands.parsing.argtype.enum.name", LightingCalculationCommand.LightCalculationType.class) + ); + @Nonnull + private final FlagArg invalidateFlag = this.withFlagArg("invalidate", "server.commands.lightingcalculation.invalidate.desc"); + + public LightingCalculationCommand() { + super("calculation", "server.commands.lightingcalculation.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + LightingCalculationCommand.LightCalculationType type = this.calculationTypeArg.get(context); + ChunkLightingManager chunkLighting = world.getChunkLighting(); + switch (type) { + case FLOOD: + chunkLighting.setLightCalculation(new FloodLightCalculation(chunkLighting)); + context.sendMessage(Message.translation("server.commands.lightingcalculation.setCalculation").param("calculation", "Flood")); + break; + case FULLBRIGHT: + LightCalculation lightCalculation = chunkLighting.getLightCalculation(); + if (lightCalculation instanceof FullBrightLightCalculation) { + context.sendMessage(MESSAGE_COMMANDS_LIGHTING_CALCULATION_ALREADY_FULLBRIGHT); + return; + } + + chunkLighting.setLightCalculation(new FullBrightLightCalculation(chunkLighting, lightCalculation)); + context.sendMessage( + Message.translation("server.commands.lightcalculation.setCalculationWithDelegate") + .param("delegate", lightCalculation.getClass().getSimpleName()) + ); + } + + if (this.invalidateFlag.get(context)) { + chunkLighting.invalidateLoadedChunks(); + context.sendMessage(MESSAGE_COMMANDS_INVALIDATE_LIGHTING); + } + } + + private static enum LightCalculationType { + FLOOD, + FULLBRIGHT; + + private LightCalculationType() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingCommand.java new file mode 100644 index 0000000..553b16a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingCommand.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.server.core.command.commands.utility.lighting; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class LightingCommand extends AbstractCommandCollection { + public LightingCommand() { + super("lighting", "server.commands.lighting.desc"); + this.addAliases("light"); + this.addSubCommand(new LightingCalculationCommand()); + this.addSubCommand(new LightingGetCommand()); + this.addSubCommand(new LightingInvalidateCommand()); + this.addSubCommand(new LightingInfoCommand()); + this.addSubCommand(new LightingSendCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingGetCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingGetCommand.java new file mode 100644 index 0000000..f9759de --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingGetCommand.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.server.core.command.commands.utility.lighting; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeIntPosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkLightData; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class LightingGetCommand extends AbstractWorldCommand { + @Nonnull + private final RequiredArg positionArg = this.withRequiredArg( + "x y z", "server.commands.light.get.position.desc", ArgTypes.RELATIVE_BLOCK_POSITION + ); + @Nonnull + private final FlagArg hexFlag = this.withFlagArg("hex", "server.commands.light.get.hex.desc"); + + public LightingGetCommand() { + super("get", "server.commands.light.get.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + ChunkStore chunkStore = world.getChunkStore(); + Vector3i position = this.positionArg.get(context).getBlockPosition(context, store); + int x = position.x; + int y = position.y; + int z = position.z; + long chunkIndex = ChunkUtil.indexChunkFromBlock(x, z); + Ref chunkReference = chunkStore.getChunkReference(chunkIndex); + if (chunkReference != null && chunkReference.isValid()) { + BlockChunk blockChunkComponent = chunkStore.getStore().getComponent(chunkReference, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + BlockSection section = blockChunkComponent.getSectionAtBlockY(y); + short lightValue = section.getGlobalLight().getLightRaw(x, y, z); + byte redLight = ChunkLightData.getLightValue(lightValue, 0); + byte greenLight = ChunkLightData.getLightValue(lightValue, 1); + byte blueLight = ChunkLightData.getLightValue(lightValue, 2); + byte skyLight = ChunkLightData.getLightValue(lightValue, 3); + boolean displayHex = this.hexFlag.get(context); + Message messageToSend = Message.translation("server.commands.light.get").param("x", x).param("y", y).param("z", z).param("worldName", world.getName()); + if (displayHex) { + String hexString = Integer.toHexString(lightValue); + messageToSend.insert("#" + "0".repeat(8 - hexString.length()) + hexString); + } else { + messageToSend.insert( + Message.translation("server.commands.light.value") + .param("red", (int)redLight) + .param("green", (int)greenLight) + .param("blue", (int)blueLight) + .param("sky", (int)skyLight) + ); + } + + context.sendMessage(messageToSend); + } else { + Message errorMessage = Message.translation("server.commands.errors.chunkNotLoaded") + .param("chunkX", ChunkUtil.chunkCoordinate(x)) + .param("chunkZ", ChunkUtil.chunkCoordinate(z)) + .param("world", world.getName()); + context.sendMessage(errorMessage); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingInfoCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingInfoCommand.java new file mode 100644 index 0000000..be54270 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingInfoCommand.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.server.core.command.commands.utility.lighting; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.lighting.ChunkLightingManager; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nonnull; + +public class LightingInfoCommand extends AbstractWorldCommand { + @Nonnull + private final FlagArg detailFlag = this.withFlagArg("detail", "server.commands.lighting.info.detail.desc"); + + public LightingInfoCommand() { + super("info", "server.commands.lighting.info.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Store chunkStoreStore = world.getChunkStore().getStore(); + ChunkLightingManager chunkLighting = world.getChunkLighting(); + context.sendMessage( + Message.translation("server.commands.lighting.info.summary") + .param("queueSize", chunkLighting.getQueueSize()) + .param("lightCalculation", chunkLighting.getLightCalculation().getClass().getSimpleName()) + ); + if (this.detailFlag.get(context)) { + AtomicInteger total = new AtomicInteger(); + AtomicInteger localLightCount = new AtomicInteger(); + AtomicInteger globalLightCount = new AtomicInteger(); + chunkStoreStore.forEachEntityParallel(WorldChunk.getComponentType(), (index, archetypeChunk, commandBuffer) -> { + int hasLocalCount = 0; + int hasGlobalCount = 0; + BlockChunk blockChunkComponent = archetypeChunk.getComponent(index, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + for (int y = 0; y < 10; y++) { + BlockSection section = blockChunkComponent.getSectionAtBlockY(y); + if (section.hasLocalLight()) { + hasLocalCount++; + } + + if (section.hasGlobalLight()) { + hasGlobalCount++; + } + } + + total.getAndAdd(10); + localLightCount.getAndAdd(hasLocalCount); + globalLightCount.getAndAdd(hasGlobalCount); + }); + context.sendMessage( + Message.translation("server.commands.lighting.info.chunkDetails") + .param("totalChunkSections", total.get()) + .param("chunksWithLocalLight", localLightCount.get()) + .param("chunksWithGlobalLight", globalLightCount.get()) + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingInvalidateCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingInvalidateCommand.java new file mode 100644 index 0000000..47c2b0b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingInvalidateCommand.java @@ -0,0 +1,86 @@ +package com.hypixel.hytale.server.core.command.commands.utility.lighting; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.lighting.ChunkLightingManager; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class LightingInvalidateCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private static final Message MESSAGE_COMMANDS_INVALIDATED_LIGHTING = Message.translation("server.commands.invalidatedlighting"); + @Nonnull + private final FlagArg oneFlag = this.withFlagArg("one", "server.commands.invalidatelighting.one.desc"); + + public LightingInvalidateCommand() { + super("invalidate", "server.commands.invalidatelighting.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + ChunkLightingManager chunkLighting = world.getChunkLighting(); + ChunkStore chunkStore = world.getChunkStore(); + if (this.oneFlag.get(context)) { + if (!context.isPlayer()) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + return; + } + + Ref ref = context.senderAsPlayerRef(); + if (ref == null || !ref.isValid()) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + return; + } + + Store entityStore = ref.getStore(); + TransformComponent transformComponent = entityStore.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + long chunkIndex = ChunkUtil.indexChunkFromBlock((int)position.getX(), (int)position.getZ()); + int chunkX = ChunkUtil.xOfChunkIndex(chunkIndex); + int chunkZ = ChunkUtil.zOfChunkIndex(chunkIndex); + Ref chunkReference = chunkStore.getChunkReference(chunkIndex); + if (chunkReference == null || !chunkReference.isValid()) { + Message errorMessage = Message.translation("server.commands.errors.chunkNotLoaded") + .param("chunkX", chunkX) + .param("chunkZ", chunkZ) + .param("world", world.getName()); + context.sendMessage(errorMessage); + return; + } + + Store chunkStoreStore = chunkStore.getStore(); + BlockChunk blockChunkComponent = chunkStoreStore.getComponent(chunkReference, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + int chunkY = MathUtil.floor(position.getY()) >> 5; + BlockSection section = blockChunkComponent.getSectionAtBlockY(chunkY); + section.invalidateLocalLight(); + blockChunkComponent.invalidateChunkSection(chunkY); + Vector3i chunkPosition = new Vector3i(blockChunkComponent.getX(), chunkY, blockChunkComponent.getZ()); + chunkLighting.addToQueue(chunkPosition); + context.sendMessage(Message.translation("server.commands.invalidatelighting.success").param("chunkPosition", chunkPosition.toString())); + } else { + chunkLighting.invalidateLoadedChunks(); + context.sendMessage(MESSAGE_COMMANDS_INVALIDATED_LIGHTING); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingSendCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingSendCommand.java new file mode 100644 index 0000000..1884233 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingSendCommand.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.server.core.command.commands.utility.lighting; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; + +public class LightingSendCommand extends AbstractCommandCollection { + public LightingSendCommand() { + super("send", "server.commands.sendlighting.desc"); + this.addSubCommand(new LightingSendCommand.LightingSendLocalCommand()); + this.addSubCommand(new LightingSendCommand.LightingSendGlobalCommand()); + } + + private static class LightingSendGlobalCommand extends LightingSendToggleCommand { + public LightingSendGlobalCommand() { + super( + "global", + "server.commands.sendlighting.global.desc", + "server.commands.sendlighting.global.enabled.desc", + "server.commands.sendlighting.globalLightingStatus", + () -> BlockChunk.SEND_GLOBAL_LIGHTING_DATA, + value -> BlockChunk.SEND_GLOBAL_LIGHTING_DATA = value + ); + } + } + + private static class LightingSendLocalCommand extends LightingSendToggleCommand { + public LightingSendLocalCommand() { + super( + "local", + "server.commands.sendlighting.local.desc", + "server.commands.sendlighting.local.enabled.desc", + "server.commands.sendlighting.localLightingStatus", + () -> BlockChunk.SEND_LOCAL_LIGHTING_DATA, + value -> BlockChunk.SEND_LOCAL_LIGHTING_DATA = value + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingSendToggleCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingSendToggleCommand.java new file mode 100644 index 0000000..c667c5e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/lighting/LightingSendToggleCommand.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.core.command.commands.utility.lighting; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import java.util.Objects; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +abstract class LightingSendToggleCommand extends AbstractWorldCommand { + @Nonnull + private final String statusTranslationKey; + @Nonnull + private final BooleanSupplier getter; + @Nonnull + private final Consumer setter; + @Nonnull + private final OptionalArg enabledArg; + + protected LightingSendToggleCommand( + @Nonnull String name, + @Nonnull String description, + @Nonnull String enabledDesc, + @Nonnull String statusTranslationKey, + @Nonnull BooleanSupplier getter, + @Nonnull Consumer setter + ) { + super(name, description); + this.statusTranslationKey = statusTranslationKey; + this.getter = getter; + this.setter = setter; + this.enabledArg = this.withOptionalArg("enabled", enabledDesc, ArgTypes.BOOLEAN); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Boolean enabled = this.enabledArg.provided(context) ? this.enabledArg.get(context) : null; + Boolean newValue = Objects.requireNonNullElseGet(enabled, () -> !this.getter.getAsBoolean()); + this.setter.accept(newValue); + context.sendMessage(Message.translation(this.statusTranslationKey).param("status", MessageFormat.enabled(newValue))); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/metacommands/CommandsCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/metacommands/CommandsCommand.java new file mode 100644 index 0000000..4f71c6e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/metacommands/CommandsCommand.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.core.command.commands.utility.metacommands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class CommandsCommand extends AbstractCommandCollection { + public CommandsCommand() { + super("commands", "server.commands.meta.desc"); + this.addSubCommand(new DumpCommandsCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/metacommands/DumpCommandsCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/metacommands/DumpCommandsCommand.java new file mode 100644 index 0000000..a6822a6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/metacommands/DumpCommandsCommand.java @@ -0,0 +1,89 @@ +package com.hypixel.hytale.server.core.command.commands.utility.metacommands; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandManager; +import com.hypixel.hytale.server.core.command.system.CommandOwner; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DumpCommandsCommand extends CommandBase { + public DumpCommandsCommand() { + super("dump", "server.commands.meta.dump.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + JsonObject outputJson = new JsonObject(); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + List modernDefs = this.gatherCommandDefs(); + outputJson.add("modern", gson.toJsonTree(modernDefs)); + CompletableFuture.runAsync(() -> { + try { + String outputStr = gson.toJson((JsonElement)outputJson); + Path path = Paths.get("dumps/commands.dump.json"); + Files.createDirectories(path.getParent()); + Files.writeString(path, outputStr); + context.sendMessage(Message.translation("server.commands.meta.dump.success").param("file", path.toAbsolutePath().toString())); + } catch (Throwable var5) { + throw new RuntimeException(var5); + } + }).exceptionally(t -> { + context.sendMessage(Message.translation("server.commands.meta.dump.error")); + HytaleLogger.getLogger().at(Level.SEVERE).withCause(t).log("Couldn't write command dump"); + return null; + }); + } + + private List gatherCommandDefs() { + Map registrations = CommandManager.get().getCommandRegistration(); + List defs = new ObjectArrayList<>(registrations.size() * 2); + registrations.forEach((name, command) -> this.extractCommand(command, defs)); + return defs; + } + + private void extractCommand(@Nonnull AbstractCommand command, @Nonnull List defs) { + String outputName = "/" + command.getFullyQualifiedName(); + String className = command.getClass().getName(); + String owner = this.formatNullable(command.getOwner(), CommandOwner::getName); + String ownerClass = this.formatNullable(command.getOwner(), o -> o.getClass().getName()); + String permission = this.formatPermission(command.getPermission()); + List permissionGroups = command.getPermissionGroups(); + defs.add(new DumpCommandsCommand.CommandDef(outputName, className, owner, ownerClass, permission, permissionGroups)); + + for (AbstractCommand subCommand : command.getSubCommands().values()) { + this.extractCommand(subCommand, defs); + } + } + + private String formatNullable(@Nullable T something, Function func) { + try { + return something == null ? "NULL" : func.apply(something); + } catch (Throwable var4) { + return "ERROR"; + } + } + + private String formatPermission(@Nullable String permission) { + return permission == null ? "NULL" : permission; + } + + private record CommandDef(String name, String className, String owner, String ownerClass, String permission, List permissionGroups) { + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/net/NetworkCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/net/NetworkCommand.java new file mode 100644 index 0000000..bc0cd27 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/net/NetworkCommand.java @@ -0,0 +1,117 @@ +package com.hypixel.hytale.server.core.command.commands.utility.net; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.knockback.KnockbackSystems; +import com.hypixel.hytale.server.core.io.netty.LatencySimulationHandler; +import com.hypixel.hytale.server.core.modules.entity.player.KnockbackPredictionSystems; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import io.netty.channel.Channel; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NetworkCommand extends AbstractCommandCollection { + public NetworkCommand() { + super("network", "server.commands.network.desc"); + this.addAliases("net"); + this.addSubCommand(new NetworkCommand.LatencySimulationCommand()); + this.addSubCommand(new NetworkCommand.ServerKnockbackCommand()); + this.addSubCommand(new NetworkCommand.DebugKnockbackCommand()); + } + + static class DebugKnockbackCommand extends CommandBase { + DebugKnockbackCommand() { + super("debugknockback", "server.commands.network.debugknockback.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + KnockbackPredictionSystems.DEBUG_KNOCKBACK_POSITION = !KnockbackPredictionSystems.DEBUG_KNOCKBACK_POSITION; + context.sendMessage( + Message.translation("server.commands.network.knockbackDebugEnabled").param("enabled", KnockbackPredictionSystems.DEBUG_KNOCKBACK_POSITION) + ); + } + } + + public static class LatencySimulationCommand extends AbstractCommandCollection { + public LatencySimulationCommand() { + super("latencysimulation", "server.commands.latencySimulation.desc"); + this.addAliases("latsim"); + this.addSubCommand(new NetworkCommand.LatencySimulationCommand.Set()); + this.addSubCommand(new NetworkCommand.LatencySimulationCommand.Reset()); + } + + static class Reset extends AbstractTargetPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_LATENCY_SIMULATION_RESET_SUCCESS = Message.translation("server.commands.latencySimulation.reset.success"); + + Reset() { + super("reset", "server.commands.latencySimulation.reset.desc"); + this.addAliases("clear"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + Channel channel = playerRef.getPacketHandler().getChannel(); + LatencySimulationHandler.setLatency(channel, 0L, TimeUnit.MILLISECONDS); + context.sendMessage(MESSAGE_COMMANDS_LATENCY_SIMULATION_RESET_SUCCESS); + } + } + + static class Set extends AbstractTargetPlayerCommand { + @Nonnull + private final RequiredArg delayArg = this.withRequiredArg("delay", "server.commands.latencySimulation.set.delay.desc", ArgTypes.INTEGER); + + Set() { + super("set", "server.commands.latencySimulation.set.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + int delay = this.delayArg.get(context); + Channel channel = playerRef.getPacketHandler().getChannel(); + LatencySimulationHandler.setLatency(channel, delay, TimeUnit.MILLISECONDS); + context.sendMessage(Message.translation("server.commands.latencySimulation.set.success").param("millis", delay)); + } + } + } + + static class ServerKnockbackCommand extends CommandBase { + ServerKnockbackCommand() { + super("serverknockback", "server.commands.network.serverknockback.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + KnockbackSystems.ApplyPlayerKnockback.DO_SERVER_PREDICTION = !KnockbackSystems.ApplyPlayerKnockback.DO_SERVER_PREDICTION; + context.sendMessage( + Message.translation("server.commands.network.knockbackServerPredictionEnabled") + .param("enabled", KnockbackSystems.ApplyPlayerKnockback.DO_SERVER_PREDICTION) + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/sleep/SleepCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/sleep/SleepCommand.java new file mode 100644 index 0000000..56ee9c5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/sleep/SleepCommand.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.command.commands.utility.sleep; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class SleepCommand extends AbstractCommandCollection { + public SleepCommand() { + super("sleep", "server.commands.sleep.desc"); + this.addSubCommand(new SleepOffsetCommand()); + this.addSubCommand(new SleepTestCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/sleep/SleepOffsetCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/sleep/SleepOffsetCommand.java new file mode 100644 index 0000000..3aa2fcb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/sleep/SleepOffsetCommand.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.server.core.command.commands.utility.sleep; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.util.thread.TickingThread; +import javax.annotation.Nonnull; + +public class SleepOffsetCommand extends CommandBase { + @Nonnull + private final FlagArg percentFlag = this.withFlagArg("percent", "server.commands.sleepoffset.percent.desc"); + @Nonnull + private final OptionalArg offsetArg = this.withOptionalArg("offset", "server.commands.sleepoffset.offset.desc", ArgTypes.INTEGER); + + public SleepOffsetCommand() { + super("offset", "server.commands.sleepoffset.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + if (this.offsetArg.provided(context)) { + float oldValue = (float)TickingThread.SLEEP_OFFSET; + int newValue = this.offsetArg.get(context); + TickingThread.SLEEP_OFFSET = newValue; + if (this.percentFlag.get(context)) { + context.sendMessage(Message.translation("server.commands.sleepoffset.setPercent").param("newValue", newValue).param("oldValue", oldValue)); + } else { + context.sendMessage(Message.translation("server.commands.sleepoffset.set").param("newValue", newValue).param("oldValue", oldValue)); + } + } else { + float value = (float)TickingThread.SLEEP_OFFSET; + if (this.percentFlag.get(context)) { + context.sendMessage(Message.translation("server.commands.sleepoffset.getPercent").param("value", value)); + } else { + context.sendMessage(Message.translation("server.commands.sleepoffset.getOffset").param("value", value)); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/sleep/SleepTestCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/sleep/SleepTestCommand.java new file mode 100644 index 0000000..cded6fb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/sleep/SleepTestCommand.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.server.core.command.commands.utility.sleep; + +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.metrics.metric.Metric; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + +public class SleepTestCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_SLEEP_TEST_INTERRUPTED = Message.translation("server.commands.sleeptest.interrupted"); + @Nonnull + private final DefaultArg sleepArg = this.withDefaultArg( + "sleep", "server.commands.sleeptest.sleep.desc", ArgTypes.INTEGER, 10, "server.commands.sleeptest.sleep.defaultDesc" + ); + @Nonnull + private final DefaultArg countArg = this.withDefaultArg( + "count", "server.commands.sleeptest.count.desc", ArgTypes.INTEGER, 1000, "server.commands.sleeptest.count.defaultDesc" + ); + + public SleepTestCommand() { + super("test", "server.commands.sleeptest.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + int sleep = this.sleepArg.get(context); + int count = this.countArg.get(context); + CompletableFuture.runAsync( + () -> { + context.sendMessage( + Message.translation("server.commands.sleeptest.starting") + .param("count", count) + .param("sleep", sleep) + .param("ms", FormatUtil.timeUnitToString((long)count * sleep, TimeUnit.MILLISECONDS)) + ); + + try { + Metric metricDelta = new Metric(); + Metric metricOffset = new Metric(); + + for (int i = 0; i < count; i++) { + long before = System.nanoTime(); + Thread.sleep(sleep); + long after = System.nanoTime(); + long delta = after - before; + metricDelta.add(delta); + long offset = delta - sleep * 1000000L; + metricOffset.add(offset); + } + + context.sendMessage( + Message.translation("server.commands.sleeptest.test") + .param("deltaMin", metricDelta.getMin()) + .param("deltaMax", metricDelta.getMax()) + .param("deltaAvg", metricDelta.getAverage()) + .param("deltaTime", FormatUtil.nanosToString((long)metricDelta.getAverage())) + .param("offsetMin", metricOffset.getMin()) + .param("offsetMax", metricOffset.getMax()) + .param("offsetAvg", metricOffset.getAverage()) + .param("offsetTime", FormatUtil.nanosToString((long)metricOffset.getAverage())) + ); + } catch (InterruptedException var14) { + context.sendMessage(MESSAGE_COMMANDS_SLEEP_TEST_INTERRUPTED); + Thread.currentThread().interrupt(); + } + } + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/sound/SoundCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/sound/SoundCommand.java new file mode 100644 index 0000000..b28fb4d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/sound/SoundCommand.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.core.command.commands.utility.sound; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.audio.PlaySoundPage; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SoundCommand extends AbstractPlayerCommand { + public SoundCommand() { + super("sound", "server.commands.sound.desc"); + this.addSubCommand(new SoundPlay2DCommand()); + this.addSubCommand(new SoundPlay3DCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerComponent.getPageManager().openCustomPage(ref, store, new PlaySoundPage(playerRefComponent)); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/sound/SoundPlay2DCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/sound/SoundPlay2DCommand.java new file mode 100644 index 0000000..207d5bd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/sound/SoundPlay2DCommand.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.core.command.commands.utility.sound; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SoundPlay2DCommand extends AbstractTargetPlayerCommand { + @Nonnull + private final RequiredArg soundEventArg = this.withRequiredArg("sound", "server.commands.sound.play.sound.desc", ArgTypes.SOUND_EVENT_ASSET); + @Nonnull + private final DefaultArg categoryArg = this.withDefaultArg( + "category", "server.commands.sound.category.desc", ArgTypes.SOUND_CATEGORY, SoundCategory.SFX, "server.commands.sound.category.default" + ); + @Nonnull + private final FlagArg allFlag = this.withFlagArg("all", "server.commands.sound.all.desc"); + + public SoundPlay2DCommand() { + super("2d", "server.commands.sound.2d.desc"); + this.addAliases("play"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + SoundEvent soundEvent = this.soundEventArg.get(context); + SoundCategory soundCategory = this.categoryArg.get(context); + int soundEventIndex = SoundEvent.getAssetMap().getIndex(soundEvent.getId()); + if (this.allFlag.provided(context)) { + SoundUtil.playSoundEvent2d(soundEventIndex, soundCategory, store); + } else { + SoundUtil.playSoundEvent2d(ref, soundEventIndex, soundCategory, store); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/sound/SoundPlay3DCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/sound/SoundPlay3DCommand.java new file mode 100644 index 0000000..3030cb2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/sound/SoundPlay3DCommand.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.server.core.command.commands.utility.sound; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeVector3i; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SoundPlay3DCommand extends AbstractTargetPlayerCommand { + @Nonnull + private final RequiredArg soundEventArg = this.withRequiredArg("sound", "server.commands.sound.play3d.sound.desc", ArgTypes.SOUND_EVENT_ASSET); + @Nonnull + private final DefaultArg categoryArg = this.withDefaultArg( + "category", "server.commands.sound.category.desc", ArgTypes.SOUND_CATEGORY, SoundCategory.SFX, "server.commands.sound.category.default" + ); + @Nonnull + private final RequiredArg positionArg = this.withRequiredArg( + "position", "server.commands.sound.play3d.position.desc", ArgTypes.RELATIVE_VECTOR3I + ); + @Nonnull + private final FlagArg allFlag = this.withFlagArg("all", "server.commands.sound.all.desc"); + + public SoundPlay3DCommand() { + super("3d", "server.commands.sound.3d.desc"); + this.addAliases("play3d"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + SoundEvent soundEvent = this.soundEventArg.get(context); + SoundCategory soundCategory = this.categoryArg.get(context); + RelativeVector3i relativePosition = this.positionArg.get(context); + int soundEventIndex = SoundEvent.getAssetMap().getIndex(soundEvent.getId()); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d basePosition = transformComponent.getPosition(); + Vector3i blockPosition = relativePosition.resolve(MathUtil.floor(basePosition.x), MathUtil.floor(basePosition.y), MathUtil.floor(basePosition.z)); + if (this.allFlag.provided(context)) { + SoundUtil.playSoundEvent3d(soundEventIndex, soundCategory, blockPosition.x, blockPosition.y, blockPosition.z, world.getEntityStore().getStore()); + } else { + SoundUtil.playSoundEvent3dToPlayer(ref, soundEventIndex, soundCategory, blockPosition.x, blockPosition.y, blockPosition.z, store); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapClearMarkersCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapClearMarkersCommand.java new file mode 100644 index 0000000..e7c2593 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapClearMarkersCommand.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.core.command.commands.utility.worldmap; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerWorldData; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WorldMapClearMarkersCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_MAP_MARKERS_CLEARED = Message.translation("server.commands.worldmap.markersCleared"); + + public WorldMapClearMarkersCommand() { + super("clearmarkers", "server.commands.worldmap.clearmarkers.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerWorldData perWorldData = playerComponent.getPlayerConfigData().getPerWorldData(world.getName()); + perWorldData.setWorldMapMarkers(null); + context.sendMessage(MESSAGE_COMMANDS_WORLD_MAP_MARKERS_CLEARED); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapCommand.java new file mode 100644 index 0000000..5169240 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapCommand.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.server.core.command.commands.utility.worldmap; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class WorldMapCommand extends AbstractCommandCollection { + public WorldMapCommand() { + super("worldmap", "server.commands.worldmap.desc"); + this.addAliases("map"); + this.addSubCommand(new WorldMapReloadCommand()); + this.addSubCommand(new WorldMapDiscoverCommand()); + this.addSubCommand(new WorldMapUndiscoverCommand()); + this.addSubCommand(new WorldMapClearMarkersCommand()); + this.addSubCommand(new WorldMapViewRadiusSubCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapDiscoverCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapDiscoverCommand.java new file mode 100644 index 0000000..386002f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapDiscoverCommand.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.server.core.command.commands.utility.worldmap; + +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.worldmap.BiomeData; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class WorldMapDiscoverCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_MAP_ALL_ZONES_DISCOVERED = Message.translation("server.commands.worldmap.allZonesDiscovered"); + @Nonnull + private final OptionalArg zoneArg = this.withOptionalArg("zone", "server.commands.worldmap.zone.desc", ArgTypes.STRING); + + public WorldMapDiscoverCommand() { + super("discover", "server.commands.worldmap.discover.desc"); + this.addAliases("disc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Map biomeDataMap = world.getWorldMapManager().getWorldMapSettings().getSettingsPacket().biomeDataMap; + if (biomeDataMap != null) { + Set zoneNames = biomeDataMap.values().stream().map(biomeData -> biomeData.zoneName).collect(Collectors.toSet()); + if (!this.zoneArg.provided(context)) { + context.sendMessage(Message.translation("server.commands.worldmap.zoneNames").param("zoneNames", zoneNames.toString())); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + zoneNames.removeAll(playerComponent.getPlayerConfigData().getDiscoveredZones()); + context.sendMessage(Message.translation("server.commands.worldmap.zonesNotDiscovered").param("zoneNames", zoneNames.toString())); + } else { + String zoneName = this.zoneArg.get(context); + if (zoneName.equalsIgnoreCase("all")) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getWorldMapTracker().discoverZones(world, zoneNames); + context.sendMessage(MESSAGE_COMMANDS_WORLD_MAP_ALL_ZONES_DISCOVERED); + } else if (!zoneNames.contains(zoneName)) { + context.sendMessage(Message.translation("server.commands.worldmap.zoneNotFound").param("zoneName", zoneName)); + context.sendMessage( + Message.translation("server.general.failed.didYouMean") + .param("choices", StringUtil.sortByFuzzyDistance(zoneName, zoneNames, CommandUtil.RECOMMEND_COUNT).toString()) + ); + } else { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + boolean added = playerComponent.getWorldMapTracker().discoverZone(world, zoneName); + if (added) { + context.sendMessage(Message.translation("server.commands.worldmap.zoneDiscovered").param("zoneName", zoneName)); + } else { + context.sendMessage(Message.translation("server.commands.worldmap.zoneAlreadyDiscovered").param("zoneName", zoneName)); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapReloadCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapReloadCommand.java new file mode 100644 index 0000000..677d11c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapReloadCommand.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.core.command.commands.utility.worldmap; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.worldmap.IWorldMap; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapLoadException; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class WorldMapReloadCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_MAP_CLEAR_IMAGES = Message.translation("server.commands.worldmap.clearimages"); + + public WorldMapReloadCommand() { + super("reload", "server.commands.worldmap.reload.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + try { + IWorldMap worldMap = world.getWorldConfig().getWorldMapProvider().getGenerator(world); + world.getWorldMapManager().setGenerator(worldMap); + context.sendMessage(MESSAGE_COMMANDS_WORLD_MAP_CLEAR_IMAGES); + } catch (WorldMapLoadException var5) { + HytaleLogger.getLogger().at(Level.SEVERE).log("Failed to reload world map for world " + world.getName(), var5); + context.sendMessage(Message.translation("server.commands.worldmap.reloadFailed").param("error", var5.getMessage())); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapUndiscoverCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapUndiscoverCommand.java new file mode 100644 index 0000000..53f137d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapUndiscoverCommand.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.server.core.command.commands.utility.worldmap; + +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.worldmap.BiomeData; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class WorldMapUndiscoverCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_MAP_ALL_ZONES_REMOVED_FROM_DISCOVERED = Message.translation( + "server.commands.worldmap.allZonesRemovedFromDiscovered" + ); + @Nonnull + private final OptionalArg zoneArg = this.withOptionalArg("zone", "server.commands.worldmap.zone.desc", ArgTypes.STRING); + + public WorldMapUndiscoverCommand() { + super("undiscover", "server.commands.worldmap.undiscover.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Map biomeDataMap = world.getWorldMapManager().getWorldMapSettings().getSettingsPacket().biomeDataMap; + if (biomeDataMap != null) { + Set zoneNames = biomeDataMap.values().stream().map(biomeData -> biomeData.zoneName).collect(Collectors.toSet()); + if (!this.zoneArg.provided(context)) { + context.sendMessage(Message.translation("server.commands.worldmap.zoneNames").param("zoneNames", zoneNames.toString())); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + context.sendMessage( + Message.translation("server.commands.worldmap.zonesDiscovered") + .param("zoneNames", playerComponent.getPlayerConfigData().getDiscoveredZones().toString()) + ); + } + } else { + String zoneName = this.zoneArg.get(context); + if ("all".equalsIgnoreCase(zoneName)) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + playerComponent.getWorldMapTracker().undiscoverZones(world, zoneNames); + context.sendMessage(MESSAGE_COMMANDS_WORLD_MAP_ALL_ZONES_REMOVED_FROM_DISCOVERED); + } + } else if (!zoneNames.contains(zoneName)) { + context.sendMessage(Message.translation("server.commands.worldmap.zoneNotFound").param("zoneName", zoneName)); + context.sendMessage( + Message.translation("server.general.failed.didYouMean") + .param("choices", StringUtil.sortByFuzzyDistance(zoneName, zoneNames, CommandUtil.RECOMMEND_COUNT).toString()) + ); + } else { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + boolean removed = playerComponent.getWorldMapTracker().undiscoverZone(world, zoneName); + if (removed) { + context.sendMessage(Message.translation("server.commands.worldmap.zoneRemovedFromDiscovered").param("zoneName", zoneName)); + } else { + context.sendMessage(Message.translation("server.commands.worldmap.zoneNotDiscoveredYet").param("zoneName", zoneName)); + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapViewRadiusGetCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapViewRadiusGetCommand.java new file mode 100644 index 0000000..7be75ea --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapViewRadiusGetCommand.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.core.command.commands.utility.worldmap; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WorldMapViewRadiusGetCommand extends AbstractTargetPlayerCommand { + public WorldMapViewRadiusGetCommand() { + super("get", "server.commands.worldmap.viewradius.get.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + WorldMapTracker worldMapTracker = playerComponent.getWorldMapTracker(); + Integer override = worldMapTracker.getViewRadiusOverride(); + int effectiveRadius = worldMapTracker.getEffectiveViewRadius(world); + if (override != null) { + context.sendMessage( + Message.translation("server.commands.worldmap.viewradius.get.withOverride").param("radius", effectiveRadius).param("override", override) + ); + } else { + context.sendMessage(Message.translation("server.commands.worldmap.viewradius.get.noOverride").param("radius", effectiveRadius)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapViewRadiusRemoveCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapViewRadiusRemoveCommand.java new file mode 100644 index 0000000..486a3e1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapViewRadiusRemoveCommand.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.server.core.command.commands.utility.worldmap; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WorldMapViewRadiusRemoveCommand extends AbstractTargetPlayerCommand { + public WorldMapViewRadiusRemoveCommand() { + super("remove", "server.commands.worldmap.viewradius.remove.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + WorldMapTracker worldMapTracker = playerComponent.getWorldMapTracker(); + if (worldMapTracker.getViewRadiusOverride() == null) { + context.sendMessage(Message.translation("server.commands.worldmap.viewradius.remove.noOverride")); + } else { + worldMapTracker.setViewRadiusOverride(null); + context.sendMessage(Message.translation("server.commands.worldmap.viewradius.remove.success")); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapViewRadiusSetCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapViewRadiusSetCommand.java new file mode 100644 index 0000000..d6c5659 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapViewRadiusSetCommand.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.core.command.commands.utility.worldmap; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WorldMapViewRadiusSetCommand extends AbstractTargetPlayerCommand { + @Nonnull + private final RequiredArg radiusArg = this.withRequiredArg("radius", "server.commands.worldmap.viewradius.set.radius.desc", ArgTypes.INTEGER); + @Nonnull + private final FlagArg bypassArg = this.withFlagArg("bypass", "server.commands.worldmap.viewradius.set.bypass.desc") + .setPermission("server.commands.worldmap.viewradius.set.bypass"); + + public WorldMapViewRadiusSetCommand() { + super("set", "server.commands.worldmap.viewradius.set.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + int viewRadius = this.radiusArg.get(context); + boolean bypass = this.bypassArg.get(context); + if (viewRadius < 0) { + context.sendMessage(Message.translation("server.commands.worldmap.viewradius.set.mustBePositive")); + } else if (viewRadius > 512 && !bypass) { + context.sendMessage(Message.translation("server.commands.worldmap.viewradius.set.noHigherThan").param("radius", 512)); + } else { + playerComponent.getWorldMapTracker().setViewRadiusOverride(viewRadius); + context.sendMessage(Message.translation("server.commands.worldmap.viewradius.set.success").param("radius", viewRadius)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapViewRadiusSubCommand.java b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapViewRadiusSubCommand.java new file mode 100644 index 0000000..3a0a145 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/utility/worldmap/WorldMapViewRadiusSubCommand.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.core.command.commands.utility.worldmap; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class WorldMapViewRadiusSubCommand extends AbstractCommandCollection { + public WorldMapViewRadiusSubCommand() { + super("viewradius", "server.commands.worldmap.viewradius.desc"); + this.addSubCommand(new WorldMapViewRadiusGetCommand()); + this.addSubCommand(new WorldMapViewRadiusSetCommand()); + this.addSubCommand(new WorldMapViewRadiusRemoveCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/SpawnBlockCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/SpawnBlockCommand.java new file mode 100644 index 0000000..ae72410 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/SpawnBlockCommand.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.core.command.commands.world; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeDoublePosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.BlockEntity; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SpawnBlockCommand extends AbstractWorldCommand { + @Nonnull + private final RequiredArg blockArg = this.withRequiredArg("block", "server.commands.spawnblock.arg.block.desc", ArgTypes.BLOCK_TYPE_KEY); + @Nonnull + private final RequiredArg positionArg = this.withRequiredArg( + "position", "server.commands.spawnblock.arg.position.desc", ArgTypes.RELATIVE_POSITION + ); + @Nonnull + private final DefaultArg rotationArg = this.withDefaultArg( + "rotation", "server.commands.spawnblock.arg.rotation.desc", ArgTypes.ROTATION, Vector3f.FORWARD, "server.commands.spawnblock.arg.rotation.desc" + ); + + public SpawnBlockCommand() { + super("spawnblock", "server.commands.spawnblock.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + String blockTypeKey = context.get(this.blockArg); + Vector3d position = context.get(this.positionArg).getRelativePosition(context, world, store); + Vector3f rotation = this.rotationArg.get(context); + TimeResource timeResource = world.getEntityStore().getStore().getResource(TimeResource.getResourceType()); + Holder blockEntityHolder = BlockEntity.assembleDefaultBlockEntity(timeResource, blockTypeKey, position); + TransformComponent transformComponent = blockEntityHolder.ensureAndGetComponent(TransformComponent.getComponentType()); + transformComponent.setPosition(position); + transformComponent.setRotation(rotation); + UUIDComponent uuidComponent = blockEntityHolder.getComponent(UUIDComponent.getComponentType()); + String entityIdString = uuidComponent == null ? "None" : uuidComponent.getUuid().toString(); + world.getEntityStore().getStore().addEntity(blockEntityHolder, AddReason.SPAWN); + context.sendMessage(Message.translation("server.commands.spawnblock.success").param("id", entityIdString)); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkCommand.java new file mode 100644 index 0000000..8e3c133 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkCommand.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.core.command.commands.world.chunk; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class ChunkCommand extends AbstractCommandCollection { + public ChunkCommand() { + super("chunk", "server.commands.chunk.desc"); + this.addAliases("chunks"); + this.addSubCommand(new ChunkFixHeightMapCommand()); + this.addSubCommand(new ChunkForceTickCommand()); + this.addSubCommand(new ChunkInfoCommand()); + this.addSubCommand(new ChunkLightingCommand()); + this.addSubCommand(new ChunkLoadCommand()); + this.addSubCommand(new ChunkLoadedCommand()); + this.addSubCommand(new ChunkMarkSaveCommand()); + this.addSubCommand(new ChunkMaxSendRateCommand()); + this.addSubCommand(new ChunkRegenerateCommand()); + this.addSubCommand(new ChunkResendCommand()); + this.addSubCommand(new ChunkTintCommand()); + this.addSubCommand(new ChunkTrackerCommand()); + this.addSubCommand(new ChunkUnloadCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkFixHeightMapCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkFixHeightMapCommand.java new file mode 100644 index 0000000..ea6f40a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkFixHeightMapCommand.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.server.core.command.commands.world.chunk; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeChunkPosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.lighting.ChunkLightingManager; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + +public class ChunkFixHeightMapCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_CHUNK_NOT_LOADED = Message.translation("server.commands.errors.chunkNotLoaded"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_FIXHEIGHTMAP_STARTED = Message.translation("server.commands.chunk.fixHeightMap.started"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_FIXHEIGHTMAP_DONE = Message.translation("server.commands.chunk.fixHeightMap.done"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_FIXHEIGHTMAP_INVALIDATING_LIGHTING = Message.translation( + "server.commands.chunk.fixHeightMap.invalidatingLighting" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_FIXHEIGHTMAP_WAITING_FOR_LIGHTING = Message.translation( + "server.commands.chunk.fixHeightMap.waitingForLighting" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_FIXHEIGHTMAP_LIGHTING_FINISHED = Message.translation( + "server.commands.chunk.fixHeightMap.lightingFinished" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_FIXHEIGHTMAP_LIGHTING_ERROR = Message.translation("server.commands.chunk.fixHeightMap.lightingError"); + @Nonnull + private final RequiredArg chunkPosArg = this.withRequiredArg( + "x z", "server.commands.chunk.fixheight.position.desc", ArgTypes.RELATIVE_CHUNK_POSITION + ); + + public ChunkFixHeightMapCommand() { + super("fixheight", "server.commands.chunk.fixheight.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + RelativeChunkPosition chunkPosition = this.chunkPosArg.get(context); + Vector2i position = chunkPosition.getChunkPosition(context, store); + fixHeightMap(context, world, position.x, position.y); + } + + private static void fixHeightMap(@Nonnull CommandContext context, @Nonnull World world, int chunkX, int chunkZ) { + ChunkLightingManager chunkLighting = world.getChunkLighting(); + ChunkStore chunkStore = world.getChunkStore(); + Store chunkStoreStore = chunkStore.getStore(); + long chunkIndex = ChunkUtil.indexChunk(chunkX, chunkZ); + Ref chunkRef = chunkStore.getChunkReference(chunkIndex); + if (chunkRef != null && chunkRef.isValid()) { + WorldChunk worldChunkComponent = chunkStoreStore.getComponent(chunkRef, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + BlockChunk blockChunkComponent = chunkStoreStore.getComponent(chunkRef, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + context.sendMessage(MESSAGE_COMMANDS_CHUNK_FIXHEIGHTMAP_STARTED); + blockChunkComponent.updateHeightmap(); + context.sendMessage(MESSAGE_COMMANDS_CHUNK_FIXHEIGHTMAP_DONE); + context.sendMessage(MESSAGE_COMMANDS_CHUNK_FIXHEIGHTMAP_INVALIDATING_LIGHTING); + + for (int chunkSectionY = 0; chunkSectionY < 10; chunkSectionY++) { + blockChunkComponent.getSectionAtIndex(chunkSectionY).invalidateLocalLight(); + } + + chunkLighting.invalidateLightInChunk(worldChunkComponent); + context.sendMessage(MESSAGE_COMMANDS_CHUNK_FIXHEIGHTMAP_DONE); + context.sendMessage(MESSAGE_COMMANDS_CHUNK_FIXHEIGHTMAP_WAITING_FOR_LIGHTING.param("x", chunkX).param("z", chunkZ)); + int[] count = new int[]{0}; + ScheduledFuture[] scheduledFuture = new ScheduledFuture[1]; + scheduledFuture[0] = HytaleServer.SCHEDULED_EXECUTOR.scheduleWithFixedDelay(() -> { + if (chunkLighting.isQueued(chunkX, chunkZ)) { + if (count[0]++ > 60) { + scheduledFuture[0].cancel(false); + context.sendMessage(MESSAGE_COMMANDS_CHUNK_FIXHEIGHTMAP_LIGHTING_ERROR.param("x", chunkX).param("z", chunkZ)); + } + } else { + world.getNotificationHandler().updateChunk(chunkIndex); + context.sendMessage(MESSAGE_COMMANDS_CHUNK_FIXHEIGHTMAP_LIGHTING_FINISHED.param("x", chunkX).param("z", chunkZ)); + scheduledFuture[0].cancel(false); + } + }, 1L, 1L, TimeUnit.SECONDS); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_CHUNK_NOT_LOADED.param("chunkX", chunkX).param("chunkZ", chunkZ).param("world", world.getName())); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkForceTickCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkForceTickCommand.java new file mode 100644 index 0000000..74a0d0e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkForceTickCommand.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.core.command.commands.world.chunk; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeChunkPosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ChunkForceTickCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_CHUNK_NOT_LOADED = Message.translation("server.commands.errors.chunkNotLoaded"); + @Nonnull + private static final Message MESSAGE_COMMANDS_FORCECHUNKTICK_CHUNK_LOAD_USAGE = Message.translation("server.commands.forcechunktick.chunkLoadUsage"); + @Nonnull + private static final Message MESSAGE_COMMANDS_FORCECHUNKTICK_BLOCKS_IN_CHUNK_TICK = Message.translation("server.commands.forcechunktick.blocksInChunkTick"); + @Nonnull + private final RequiredArg chunkPosArg = this.withRequiredArg( + "x z", "server.commands.chunk.forcetick.position.desc", ArgTypes.RELATIVE_CHUNK_POSITION + ); + + public ChunkForceTickCommand() { + super("forcetick", "server.commands.chunk.forcetick.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + RelativeChunkPosition chunkPosition = this.chunkPosArg.get(context); + Vector2i position = chunkPosition.getChunkPosition(context, store); + ChunkStore chunkStore = world.getChunkStore(); + Store chunkStoreStore = chunkStore.getStore(); + long chunkIndex = ChunkUtil.indexChunk(position.x, position.y); + Ref chunkRef = chunkStore.getChunkReference(chunkIndex); + if (chunkRef != null && chunkRef.isValid()) { + BlockChunk blockChunkComponent = chunkStoreStore.getComponent(chunkRef, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + for (int x = 0; x < 32; x++) { + for (int y = 0; y < 320; y++) { + for (int z = 0; z < 32; z++) { + blockChunkComponent.setTicking(x, y, z, true); + } + } + } + + context.sendMessage(MESSAGE_COMMANDS_FORCECHUNKTICK_BLOCKS_IN_CHUNK_TICK.param("chunkX", position.x).param("chunkZ", position.y)); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_CHUNK_NOT_LOADED.param("chunkX", position.x).param("chunkZ", position.y).param("world", world.getName())); + context.sendMessage(MESSAGE_COMMANDS_FORCECHUNKTICK_CHUNK_LOAD_USAGE.param("chunkX", position.x).param("chunkZ", position.y)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkInfoCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkInfoCommand.java new file mode 100644 index 0000000..469211f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkInfoCommand.java @@ -0,0 +1,101 @@ +package com.hypixel.hytale.server.core.command.commands.world.chunk; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeChunkPosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkFlag; +import com.hypixel.hytale.server.core.universe.world.chunk.EntityChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ChunkInfoCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_GENERAL_CHUNK_NOT_LOADED = Message.translation("server.general.chunkNotLoaded"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNKINFO_LOAD_USAGE = Message.translation("server.commands.chunkinfo.load.usage"); + @Nonnull + private final RequiredArg chunkPosArg = this.withRequiredArg( + "x z", "server.commands.chunk.info.position.desc", ArgTypes.RELATIVE_CHUNK_POSITION + ); + + public ChunkInfoCommand() { + super("info", "server.commands.chunk.info.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + RelativeChunkPosition chunkPosition = this.chunkPosArg.get(context); + Vector2i position = chunkPosition.getChunkPosition(context, store); + ChunkStore chunkStore = world.getChunkStore(); + Store chunkStoreStore = chunkStore.getStore(); + long chunkIndex = ChunkUtil.indexChunk(position.x, position.y); + Ref chunkRef = chunkStore.getChunkReference(chunkIndex); + if (chunkRef != null && chunkRef.isValid()) { + WorldChunk worldChunkComponent = chunkStoreStore.getComponent(chunkRef, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + BlockChunk blockChunkComponent = chunkStoreStore.getComponent(chunkRef, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + Message msg = Message.translation("server.commands.chunkinfo.chunk") + .param("chunkX", position.x) + .param("chunkZ", position.y) + .param("startInit", worldChunkComponent.is(ChunkFlag.START_INIT)) + .param("init", worldChunkComponent.is(ChunkFlag.INIT)) + .param("newlyGenerated", worldChunkComponent.is(ChunkFlag.NEWLY_GENERATED)) + .param("onDisk", worldChunkComponent.is(ChunkFlag.ON_DISK)) + .param("ticking", worldChunkComponent.is(ChunkFlag.TICKING)) + .param("keepLoaded", worldChunkComponent.shouldKeepLoaded()) + .param("saving", worldChunkComponent.getNeedsSaving()) + .param("savingChunk", blockChunkComponent.getNeedsSaving()); + + for (int i = 0; i < 10; i++) { + BlockSection section = blockChunkComponent.getSectionAtIndex(i); + msg.insert(Message.translation("server.commands.chunkinfo.section").param("index", i)); + if (section instanceof BlockSection) { + msg.insert(Message.translation("server.commands.chunkinfo.dataType").param("data", section.getChunkSection().getClass().getSimpleName())); + } + + msg.insert( + Message.translation("server.commands.chunkinfo.sectionInfo") + .param("ticking", section.hasTicking()) + .param("solidAir", section.isSolidAir()) + .param("count", section.count()) + .param("counts", section.valueCounts().toString()) + ); + } + + BlockComponentChunk blockStateChunk = chunkStoreStore.getComponent(chunkRef, BlockComponentChunk.getComponentType()); + EntityChunk entityChunk = chunkStoreStore.getComponent(chunkRef, EntityChunk.getComponentType()); + if (blockStateChunk != null && entityChunk != null) { + msg.insert( + Message.translation("server.commands.chunkinfo.blockStateChunk") + .param("saving", blockStateChunk.getNeedsSaving()) + .param("countStates", blockStateChunk.getEntityHolders().size() + blockStateChunk.getEntityReferences().size()) + .param("savingEntity", entityChunk.getNeedsSaving()) + .param("countEntities", entityChunk.getEntityHolders().size() + entityChunk.getEntityReferences().size()) + ); + } + + context.sendMessage(msg); + } else { + context.sendMessage(MESSAGE_GENERAL_CHUNK_NOT_LOADED.param("chunkX", position.x).param("chunkZ", position.y)); + context.sendMessage(MESSAGE_COMMANDS_CHUNKINFO_LOAD_USAGE.param("chunkX", position.x).param("chunkZ", position.y)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkLightingCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkLightingCommand.java new file mode 100644 index 0000000..4945bc9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkLightingCommand.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.core.command.commands.world.chunk; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeIntPosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class ChunkLightingCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_CHUNK_NOT_LOADED = Message.translation("server.commands.errors.chunkNotLoaded"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNKINFO_LOAD_USAGE = Message.translation("server.commands.chunkinfo.load.usage"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNKINFO_SERIALIZED = Message.translation("server.commands.chunkinfo.serialized"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNKINFO_SERIALIZED_FAILED = Message.translation("server.commands.chunkinfo.serialized.failed"); + @Nonnull + private final RequiredArg positionArg = this.withRequiredArg( + "x y z", "server.commands.chunk.lighting.position.desc", ArgTypes.RELATIVE_BLOCK_POSITION + ); + + public ChunkLightingCommand() { + super("lighting", "server.commands.chunklighting.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Vector3i position = this.positionArg.get(context).getBlockPosition(context, store); + ChunkStore chunkStore = world.getChunkStore(); + Store chunkStoreStore = chunkStore.getStore(); + Vector2i chunkPos = new Vector2i(ChunkUtil.chunkCoordinate(position.getX()), ChunkUtil.chunkCoordinate(position.getZ())); + long chunkIndex = ChunkUtil.indexChunk(chunkPos.x, chunkPos.y); + Ref chunkRef = chunkStore.getChunkReference(chunkIndex); + if (chunkRef != null && chunkRef.isValid()) { + BlockChunk blockChunkComponent = chunkStoreStore.getComponent(chunkRef, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + try { + BlockSection section = blockChunkComponent.getSectionAtBlockY(position.y); + String s = section.getLocalLight().octreeToString(); + HytaleLogger.getLogger().at(Level.INFO).log("Chunk light output for (%d, %d, %d): %s", position.x, position.y, position.z, s); + context.sendMessage(MESSAGE_COMMANDS_CHUNKINFO_SERIALIZED); + } catch (Throwable var14) { + HytaleLogger.getLogger().at(Level.SEVERE).log("Failed to print chunk:", var14); + context.sendMessage(MESSAGE_COMMANDS_CHUNKINFO_SERIALIZED_FAILED); + } + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_CHUNK_NOT_LOADED.param("chunkX", chunkPos.x).param("chunkZ", chunkPos.y).param("world", world.getName())); + context.sendMessage(MESSAGE_COMMANDS_CHUNKINFO_LOAD_USAGE.param("chunkX", chunkPos.x).param("chunkZ", chunkPos.y)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkLoadCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkLoadCommand.java new file mode 100644 index 0000000..6c24077 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkLoadCommand.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.core.command.commands.world.chunk; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeChunkPosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ChunkLoadCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_LOAD_ALREADY_LOADED = Message.translation("server.commands.chunk.load.alreadyLoaded"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_LOAD_LOADING = Message.translation("server.commands.chunk.load.loading"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_LOAD_LOADED = Message.translation("server.commands.chunk.load.loaded"); + @Nonnull + private final RequiredArg chunkPosArg = this.withRequiredArg( + "x z", "server.commands.chunk.load.position.desc", ArgTypes.RELATIVE_CHUNK_POSITION + ); + @Nonnull + private final FlagArg markDirtyArg = this.withFlagArg("markdirty", "server.commands.chunk.load.markdirty.desc"); + + public ChunkLoadCommand() { + super("load", "server.commands.chunk.load.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + RelativeChunkPosition chunkPosition = this.chunkPosArg.get(context); + Vector2i position = chunkPosition.getChunkPosition(context, store); + ChunkStore chunkStore = world.getChunkStore(); + long chunkIndex = ChunkUtil.indexChunk(position.x, position.y); + Ref chunkRef = chunkStore.getChunkReference(chunkIndex); + if (chunkRef != null && chunkRef.isValid()) { + context.sendMessage( + MESSAGE_COMMANDS_CHUNK_LOAD_ALREADY_LOADED.param("chunkX", position.x).param("chunkZ", position.y).param("worldName", world.getName()) + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_CHUNK_LOAD_LOADING.param("chunkX", position.x).param("chunkZ", position.y).param("worldName", world.getName())); + world.getChunkAsync(position.x, position.y).thenAccept(worldChunk -> world.execute(() -> { + if (this.markDirtyArg.provided(context)) { + worldChunk.markNeedsSaving(); + } + + context.sendMessage(MESSAGE_COMMANDS_CHUNK_LOAD_LOADED.param("chunkX", position.x).param("chunkZ", position.y).param("worldName", world.getName())); + })); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkLoadedCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkLoadedCommand.java new file mode 100644 index 0000000..edb59da --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkLoadedCommand.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.core.command.commands.world.chunk; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChunkLoadedCommand extends AbstractTargetPlayerCommand { + public ChunkLoadedCommand() { + super("loaded", "server.commands.chunk.loaded.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + ChunkTracker chunkTracker = store.getComponent(ref, ChunkTracker.getComponentType()); + + assert chunkTracker != null; + + context.sendMessage(chunkTracker.getLoadedChunksMessage()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkMarkSaveCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkMarkSaveCommand.java new file mode 100644 index 0000000..e19529b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkMarkSaveCommand.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.core.command.commands.world.chunk; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeChunkPosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ChunkMarkSaveCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_MARKSAVE_MARKING_ALREADY_LOADED = Message.translation( + "server.commands.chunk.marksave.markingAlreadyLoaded" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_LOAD_LOADING = Message.translation("server.commands.chunk.load.loading"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_MARKSAVE_LOADED = Message.translation("server.commands.chunk.marksave.loaded"); + @Nonnull + private final RequiredArg chunkPosArg = this.withRequiredArg( + "x z", "server.commands.chunk.marksave.position.desc", ArgTypes.RELATIVE_CHUNK_POSITION + ); + + public ChunkMarkSaveCommand() { + super("marksave", "server.commands.chunk.marksave.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + RelativeChunkPosition chunkPosition = this.chunkPosArg.get(context); + Vector2i position = chunkPosition.getChunkPosition(context, store); + ChunkStore chunkStore = world.getChunkStore(); + Store chunkStoreStore = chunkStore.getStore(); + long chunkIndex = ChunkUtil.indexChunk(position.x, position.y); + Ref chunkRef = chunkStore.getChunkReference(chunkIndex); + if (chunkRef != null && chunkRef.isValid()) { + WorldChunk worldChunkComponent = chunkStoreStore.getComponent(chunkRef, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + worldChunkComponent.markNeedsSaving(); + context.sendMessage( + MESSAGE_COMMANDS_CHUNK_MARKSAVE_MARKING_ALREADY_LOADED.param("x", position.x).param("z", position.y).param("worldName", world.getName()) + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_CHUNK_LOAD_LOADING.param("chunkX", position.x).param("chunkZ", position.y).param("worldName", world.getName())); + world.getChunkAsync(position.x, position.y).thenAccept(worldChunk -> world.execute(() -> { + worldChunk.markNeedsSaving(); + context.sendMessage(MESSAGE_COMMANDS_CHUNK_MARKSAVE_LOADED.param("x", position.x).param("z", position.y).param("worldName", world.getName())); + })); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkMaxSendRateCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkMaxSendRateCommand.java new file mode 100644 index 0000000..3d2bef7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkMaxSendRateCommand.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.server.core.command.commands.world.chunk; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChunkMaxSendRateCommand extends AbstractTargetPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_MAXSENDRATE_SEC_SET = Message.translation("server.commands.chunk.maxsendrate.sec.set"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_MAXSENDRATE_TICK_SET = Message.translation("server.commands.chunk.maxsendrate.tick.set"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_MAXSENDRATE_SUMMARY = Message.translation("server.commands.chunk.maxsendrate.summary"); + @Nonnull + private final OptionalArg secArg = this.withOptionalArg("sec", "server.commands.chunk.maxsendrate.sec.desc", ArgTypes.INTEGER); + @Nonnull + private final OptionalArg tickArg = this.withOptionalArg("tick", "server.commands.chunk.maxsendrate.tick.desc", ArgTypes.INTEGER); + + public ChunkMaxSendRateCommand() { + super("maxsendrate", "server.commands.chunk.maxsendrate.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + ChunkTracker chunkTracker = store.getComponent(ref, ChunkTracker.getComponentType()); + + assert chunkTracker != null; + + if (this.secArg.provided(context)) { + int sec = this.secArg.get(context); + chunkTracker.setMaxChunksPerSecond(sec); + context.sendMessage(MESSAGE_COMMANDS_CHUNK_MAXSENDRATE_SEC_SET.param("value", sec)); + } + + if (this.tickArg.provided(context)) { + int tick = this.tickArg.get(context); + chunkTracker.setMaxChunksPerTick(tick); + context.sendMessage(MESSAGE_COMMANDS_CHUNK_MAXSENDRATE_TICK_SET.param("value", tick)); + } + + context.sendMessage( + MESSAGE_COMMANDS_CHUNK_MAXSENDRATE_SUMMARY.param("perSecond", chunkTracker.getMaxChunksPerSecond()) + .param("perTick", chunkTracker.getMaxChunksPerTick()) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkRegenerateCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkRegenerateCommand.java new file mode 100644 index 0000000..059e1b8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkRegenerateCommand.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.core.command.commands.world.chunk; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeChunkPosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ChunkRegenerateCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_REGENERATE_SUCCESS = Message.translation("server.commands.chunk.regenerate.success"); + @Nonnull + private final RequiredArg chunkPosArg = this.withRequiredArg( + "x z", "server.commands.chunk.regenerate.position.desc", ArgTypes.RELATIVE_CHUNK_POSITION + ); + + public ChunkRegenerateCommand() { + super("regenerate", "server.commands.chunk.regenerate.desc", true); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Vector2i chunkPosition = this.chunkPosArg.get(context).getChunkPosition(context, store); + long chunkIndex = ChunkUtil.indexChunk(chunkPosition.x, chunkPosition.y); + ChunkStore chunkStore = world.getChunkStore(); + chunkStore.getChunkReferenceAsync(chunkIndex, 9) + .thenAccept( + chunkRef -> world.execute( + () -> context.sendMessage( + MESSAGE_COMMANDS_CHUNK_REGENERATE_SUCCESS.param("chunkX", chunkPosition.x) + .param("chunkZ", chunkPosition.y) + .param("worldName", world.getName()) + ) + ) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkResendCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkResendCommand.java new file mode 100644 index 0000000..19cfd47 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkResendCommand.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.server.core.command.commands.world.chunk; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.iterator.SpiralIterator; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChunkResendCommand extends AbstractTargetPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_RESEND_UNLOADED_ALL = Message.translation("server.commands.chunk.resend.unloadedAll"); + @Nonnull + private final FlagArg clearCacheArg = this.withFlagArg("clearcache", "server.commands.chunk.resend.clearcache.desc"); + + public ChunkResendCommand() { + super("resend", "server.commands.chunk.resend.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + ChunkTracker chunkTrackerComponent = store.getComponent(ref, ChunkTracker.getComponentType()); + + assert chunkTrackerComponent != null; + + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + int chunkX = MathUtil.floor(position.getX()) >> 5; + int chunkZ = MathUtil.floor(position.getZ()) >> 5; + if (this.clearCacheArg.provided(context)) { + ChunkStore chunkStore = world.getChunkStore(); + Store chunkStoreStore = chunkStore.getStore(); + SpiralIterator iterator = new SpiralIterator(chunkX, chunkZ, playerComponent.getViewRadius()); + + while (iterator.hasNext()) { + long chunkIndex = iterator.next(); + Ref chunkRef = chunkStore.getChunkReference(chunkIndex); + if (chunkRef != null && chunkRef.isValid()) { + BlockChunk blockChunkComponent = chunkStoreStore.getComponent(chunkRef, BlockChunk.getComponentType()); + if (blockChunkComponent != null) { + for (int y = 0; y < 10; y++) { + blockChunkComponent.invalidateChunkSection(y); + } + } + } + } + } + + chunkTrackerComponent.unloadAll(playerRef); + context.sendMessage(MESSAGE_COMMANDS_CHUNK_RESEND_UNLOADED_ALL); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkTintCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkTintCommand.java new file mode 100644 index 0000000..cd90081 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkTintCommand.java @@ -0,0 +1,356 @@ +package com.hypixel.hytale.server.core.command.commands.world.chunk; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandManager; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.accessor.ChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.accessor.LocalCachedChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.Long2IntMap.Entry; +import javax.annotation.Nonnull; + +public class ChunkTintCommand extends AbstractPlayerCommand { + private static final int BLUR_RADIUS = 5; + private static final double BLUR_SIGMA = 1.5; + @Nonnull + private static final Message MESSAGE_GENERAL_CHUNK_NOT_LOADED = Message.translation("server.general.chunkNotLoaded"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_TINT_SUCCESS = Message.translation("server.commands.chunk.tint.success"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_TINT_SUCCESS_BLUR = Message.translation("server.commands.chunk.tint.success.blur"); + @Nonnull + private final RequiredArg colorArg = this.withRequiredArg("color", "server.commands.chunk.tint.color.desc", ArgTypes.COLOR); + @Nonnull + private final DefaultArg radiusArg = this.withDefaultArg( + "radius", "server.commands.chunk.tint.radius.desc", ArgTypes.INTEGER, 5, "server.commands.chunk.tint.radius.default" + ) + .addValidator(Validators.greaterThan(0)); + @Nonnull + private final DefaultArg sigmaArg = this.withDefaultArg( + "sigma", "server.commands.chunk.tint.sigma.desc", ArgTypes.DOUBLE, 1.5, "server.commands.chunk.tint.sigma.default" + ) + .addValidator(Validators.greaterThan(0.0)); + @Nonnull + private final FlagArg blurArg = this.withFlagArg("blur", "server.commands.chunk.tint.blur.desc"); + + public ChunkTintCommand() { + super("tint", "server.commands.chunk.tint.desc"); + this.addUsageVariant(new ChunkTintCommand.TintChunkPageCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + int color = this.colorArg.get(context); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + int chunkX = MathUtil.floor(position.getX()) >> 5; + int chunkZ = MathUtil.floor(position.getZ()) >> 5; + ChunkStore chunkStore = world.getChunkStore(); + Store chunkStoreStore = chunkStore.getStore(); + LongOpenHashSet updateChunks = new LongOpenHashSet(); + int radius = 0; + double sigma = 0.0; + long chunkIndex = ChunkUtil.indexChunk(chunkX, chunkZ); + Ref chunkRef = chunkStore.getChunkReference(chunkIndex); + if (chunkRef != null && chunkRef.isValid()) { + BlockChunk blockChunk = chunkStoreStore.getComponent(chunkRef, BlockChunk.getComponentType()); + if (blockChunk != null) { + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + blockChunk.setTint(x, z, color); + } + } + + updateChunks.add(chunkIndex); + } + + if (this.blurArg.provided(context)) { + radius = this.radiusArg.get(context); + sigma = this.sigmaArg.get(context); + double[] matrix = gaussianMatrix(sigma, radius); + int blockX = chunkX << 5; + int blockZ = chunkZ << 5; + Long2IntOpenHashMap newTintMap = new Long2IntOpenHashMap(); + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords(world, blockX, blockZ, 32 + radius * 2); + + for (int x = -radius; x <= 32 + radius; x++) { + for (int z = -radius; z <= 32 + radius; z++) { + int offsetX = blockX + x; + int offsetZ = blockZ + z; + int blurred = blur(accessor, radius, matrix, offsetX, offsetZ); + newTintMap.put(MathUtil.packLong(offsetX, offsetZ), blurred); + } + } + + for (Entry entry : newTintMap.long2IntEntrySet()) { + long key = entry.getLongKey(); + int x = MathUtil.unpackLeft(key); + int z = MathUtil.unpackRight(key); + long chunkIndexx = ChunkUtil.indexChunkFromBlock(x, z); + Ref chunkRefx = chunkStore.getChunkReference(chunkIndexx); + if (chunkRefx != null && chunkRefx.isValid()) { + BlockChunk blockChunkx = chunkStoreStore.getComponent(chunkRefx, BlockChunk.getComponentType()); + if (blockChunkx != null) { + blockChunkx.setTint(x, z, entry.getIntValue()); + updateChunks.add(chunkIndexx); + } + } + } + } + + updateChunks.forEach(chunkIndexx -> world.getNotificationHandler().updateChunk(chunkIndexx)); + if (this.blurArg.provided(context)) { + context.sendMessage( + MESSAGE_COMMANDS_CHUNK_TINT_SUCCESS_BLUR.param("chunkX", chunkX) + .param("chunkZ", chunkZ) + .param("chunksAffected", updateChunks.size()) + .param("radius", radius) + .param("sigma", sigma) + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_CHUNK_TINT_SUCCESS.param("chunkX", chunkX).param("chunkZ", chunkZ)); + } + } else { + context.sendMessage(MESSAGE_GENERAL_CHUNK_NOT_LOADED.param("chunkX", chunkX).param("chunkZ", chunkZ)); + } + } + + private static int blur(@Nonnull ChunkAccessor chunkAccessor, int radius, double[] matrix, int x, int z) { + double r = 0.0; + double g = 0.0; + double b = 0.0; + + for (int ix = -radius; ix <= radius; ix++) { + for (int iz = -radius; iz <= radius; iz++) { + double factor = matrix[gaussianIndex(radius, ix, iz)]; + int ax = x + ix; + int az = z + iz; + WorldChunk worldChunk = chunkAccessor.getChunk(ChunkUtil.indexChunkFromBlock(ax, az)); + if (worldChunk != null) { + BlockChunk blockChunk = worldChunk.getBlockChunk(); + if (blockChunk != null) { + int c = blockChunk.getTint(ax, az); + r += (c >> 16 & 0xFF) * factor; + g += (c >> 8 & 0xFF) * factor; + b += (c & 0xFF) * factor; + } + } + } + } + + return 0xFF000000 | MathUtil.floor(r) << 16 | MathUtil.floor(g) << 8 | MathUtil.floor(b); + } + + private static double gaussian2d(double sigma, double x, double y) { + return 1.0 / ((Math.PI * 2) * sigma * sigma) * Math.pow(Math.E, -(x * x + y * y) / (2.0 * sigma * sigma)); + } + + private static double[] gaussianMatrix(double sigma, int radius) { + int length = 2 * radius + 1; + double[] matrix = new double[length * length]; + + for (int x = -radius; x <= radius; x++) { + for (int y = -radius; y <= radius; y++) { + double value = gaussian2d(sigma, x, y); + matrix[gaussianIndex(radius, x, y)] = value; + } + } + + double sum = 0.0; + + for (double val : matrix) { + sum += val; + } + + for (int i = 0; i < matrix.length; i++) { + matrix[i] /= sum; + } + + return matrix; + } + + private static int gaussianIndex(int radius, int x, int y) { + x += radius; + y += radius; + return x * (2 * radius + 1) + y; + } + + public static class TintChunkPage extends InteractiveCustomUIPage { + TintChunkPage(@Nonnull PlayerRef playerRef) { + super(playerRef, CustomPageLifetime.CanDismiss, ChunkTintCommand.TintChunkPage.TintChunkPageEventData.CODEC); + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/TintChunkPage.ui"); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, + "#ColorPicker", + new EventData().append("@Color", "#ColorPicker.Value").append("Submit", ChunkTintCommand.TintChunkPage.TintChunkPageAction.ColorChanged.name()), + false + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#ApplyButton", + new EventData() + .append("@Color", "#ColorPicker.Value") + .append("@Radius", "#Radius.Value") + .append("@BlurEnabled", "#BlurEnabledContainer #CheckBox.Value") + .append("@Sigma", "#Sigma.Value") + .append("@HexColor", "#HexColor.Value") + .append("Submit", ChunkTintCommand.TintChunkPage.TintChunkPageAction.Submit.name()), + false + ); + } + + public void handleDataEvent( + @Nonnull Ref ref, @Nonnull Store store, @Nonnull ChunkTintCommand.TintChunkPage.TintChunkPageEventData data + ) { + switch (data.getAction()) { + case Submit: { + String color = data.getColor().substring(0, 7); + int radiusStr = data.getRadius(); + int sigmaStr = data.getSigma(); + if (!data.getKeyHexColor().isEmpty()) { + color = data.getKeyHexColor(); + if (!color.startsWith("#")) { + color = "#" + color; + } + } + + if (data.isBlurEnabled()) { + CommandManager.get().handleCommand(this.playerRef, "chunk tint " + color + " --blur --radius=" + radiusStr + " --sigma=" + sigmaStr); + } else { + CommandManager.get().handleCommand(this.playerRef, "chunk tint " + color); + } + break; + } + case ColorChanged: { + String color = data.getColor().substring(0, 7); + UICommandBuilder commands = new UICommandBuilder(); + commands.set("#HexColor.Value", color); + this.sendUpdate(commands); + } + } + } + + public static enum TintChunkPageAction { + Submit, + ColorChanged; + + private TintChunkPageAction() { + } + } + + public static class TintChunkPageEventData { + public static final String KEY_COLOR = "@Color"; + public static final String KEY_RADIUS = "@Radius"; + public static final String KEY_SIGMA = "@Sigma"; + public static final String KEY_BLUR_ENABLED = "@BlurEnabled"; + public static final String KEY_HEX_COLOR = "@HexColor"; + public static final String KEY_ACTION = "Submit"; + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder( + ChunkTintCommand.TintChunkPage.TintChunkPageEventData.class, ChunkTintCommand.TintChunkPage.TintChunkPageEventData::new + ) + .addField(new KeyedCodec<>("@Color", Codec.STRING), (entry, s) -> entry.color = s, entry -> entry.color) + .addField(new KeyedCodec<>("@Radius", Codec.INTEGER), (entry, s) -> entry.radius = s, entry -> entry.radius) + .addField(new KeyedCodec<>("@Sigma", Codec.INTEGER), (entry, s) -> entry.sigma = s, entry -> entry.sigma) + .addField(new KeyedCodec<>("@BlurEnabled", Codec.BOOLEAN), (entry, b) -> entry.isBlurEnabled = b, entry -> entry.isBlurEnabled) + .addField(new KeyedCodec<>("@HexColor", Codec.STRING), (entry, s) -> entry.hexColor = s, entry -> entry.hexColor) + .addField( + new KeyedCodec<>("Submit", new EnumCodec<>(ChunkTintCommand.TintChunkPage.TintChunkPageAction.class)), + (entry, s) -> entry.action = s, + entry -> entry.action + ) + .build(); + private String color; + private int radius; + private int sigma; + private String hexColor; + private boolean isBlurEnabled; + private ChunkTintCommand.TintChunkPage.TintChunkPageAction action; + + public TintChunkPageEventData() { + } + + public String getColor() { + return this.color; + } + + public int getRadius() { + return this.radius; + } + + public int getSigma() { + return this.sigma; + } + + public boolean isBlurEnabled() { + return this.isBlurEnabled; + } + + public String getKeyHexColor() { + return this.hexColor; + } + + public ChunkTintCommand.TintChunkPage.TintChunkPageAction getAction() { + return this.action; + } + } + } + + static class TintChunkPageCommand extends AbstractPlayerCommand { + TintChunkPageCommand() { + super("server.commands.chunk.tint.get"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getPageManager().openCustomPage(ref, store, new ChunkTintCommand.TintChunkPage(playerRef)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkTrackerCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkTrackerCommand.java new file mode 100644 index 0000000..2e1363b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkTrackerCommand.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.core.command.commands.world.chunk; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChunkTrackerCommand extends AbstractTargetPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_TRACKER_SUMMARY = Message.translation("server.commands.chunkTracker.summary"); + + public ChunkTrackerCommand() { + super("tracker", "server.commands.chunk.tracker.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, + @Nullable Ref sourceRef, + @Nonnull Ref ref, + @Nonnull PlayerRef playerRef, + @Nonnull World world, + @Nonnull Store store + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + ChunkTracker chunkTrackerComponent = store.getComponent(ref, ChunkTracker.getComponentType()); + + assert chunkTrackerComponent != null; + + ChunkStore chunkStore = world.getChunkStore(); + String loadedWorldChunks = Integer.toString(chunkStore.getLoadedChunksCount()); + context.sendMessage( + MESSAGE_COMMANDS_CHUNK_TRACKER_SUMMARY.param("maxChunksPerSecond", chunkTrackerComponent.getMaxChunksPerSecond()) + .param("maxChunksPerTick", chunkTrackerComponent.getMaxChunksPerTick()) + .param("minChunkLoadedRadius", chunkTrackerComponent.getMinLoadedChunksRadius()) + .param("maxHotChunkLoadedRadius", chunkTrackerComponent.getMaxHotLoadedChunksRadius()) + .param("loadedPlayerChunks", chunkTrackerComponent.getLoadedChunksCount()) + .param("loadingPlayerChunks", chunkTrackerComponent.getLoadingChunksCount()) + .param("loadedWorldChunks", loadedWorldChunks) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkUnloadCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkUnloadCommand.java new file mode 100644 index 0000000..3c0a0dd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/chunk/ChunkUnloadCommand.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.server.core.command.commands.world.chunk; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeChunkPosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ChunkUnloadCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_UNLOAD_ALREADY_UNLOADED = Message.translation("server.commands.chunk.unload.alreadyUnloaded"); + @Nonnull + private static final Message MESSAGE_COMMANDS_CHUNK_UNLOAD_SUCCESS = Message.translation("server.commands.chunk.unload.success"); + @Nonnull + private final RequiredArg chunkPosArg = this.withRequiredArg( + "x z", "server.commands.chunk.unload.position.desc", ArgTypes.RELATIVE_CHUNK_POSITION + ); + + public ChunkUnloadCommand() { + super("unload", "server.commands.chunk.unload.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + RelativeChunkPosition chunkPosition = this.chunkPosArg.get(context); + Vector2i position = chunkPosition.getChunkPosition(context, store); + ChunkStore chunkComponentStore = world.getChunkStore(); + long indexChunk = ChunkUtil.indexChunk(position.x, position.y); + Ref chunkRef = chunkComponentStore.getChunkReference(indexChunk); + if (chunkRef == null) { + context.sendMessage( + MESSAGE_COMMANDS_CHUNK_UNLOAD_ALREADY_UNLOADED.param("chunkX", position.x).param("chunkZ", position.y).param("worldName", world.getName()) + ); + } else { + chunkComponentStore.remove(chunkRef, RemoveReason.UNLOAD); + world.getNotificationHandler().updateChunk(indexChunk); + context.sendMessage(MESSAGE_COMMANDS_CHUNK_UNLOAD_SUCCESS.param("chunkX", position.x).param("chunkZ", position.y).param("worldName", world.getName())); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityCleanCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityCleanCommand.java new file mode 100644 index 0000000..3169dc6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityCleanCommand.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity; + +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EntityCleanCommand extends AbstractWorldCommand { + public EntityCleanCommand() { + super("clean", "server.commands.entity.clean.desc", true); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + store.forEachEntityParallel((index, archetypeChunk, commandBuffer) -> { + if (!archetypeChunk.getArchetype().contains(Player.getComponentType())) { + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + } + }); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityCloneCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityCloneCommand.java new file mode 100644 index 0000000..1847c09 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityCloneCommand.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity; + +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class EntityCloneCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_GENERAL_NO_ENTITY_IN_VIEW = Message.translation("server.general.noEntityInView"); + @Nonnull + private static final Message MESSAGE_COMMANDS_ENTITY_CLONE_CLONED = Message.translation("server.commands.entity.clone.cloned"); + @Nonnull + private final EntityWrappedArg entityArg = this.withOptionalArg("entity", "server.commands.entity.clone.entity.desc", ArgTypes.ENTITY_ID); + @Nonnull + private final DefaultArg countArg = this.withDefaultArg( + "count", "server.commands.entity.clone.count.desc", ArgTypes.INTEGER, 1, "server.commands.entity.clone.count.default" + ) + .addValidator(Validators.greaterThan(0)); + + public EntityCloneCommand() { + super("clone", "server.commands.entity.clone.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Ref entityRef = this.entityArg.get(store, context); + if (entityRef != null && entityRef.isValid()) { + CommandSender sender = context.sender(); + int count = this.countArg.get(context); + + for (int i = 0; i < count; i++) { + Holder copy = store.copyEntity(entityRef); + copy.replaceComponent(UUIDComponent.getComponentType(), new UUIDComponent(UUID.randomUUID())); + store.addEntity(copy, AddReason.SPAWN); + } + + if (count == 1) { + sender.sendMessage(MESSAGE_COMMANDS_ENTITY_CLONE_CLONED); + } else { + sender.sendMessage(Message.translation("server.commands.entity.clone.cloned.multiple").param("count", count)); + } + } else { + context.sendMessage(MESSAGE_GENERAL_NO_ENTITY_IN_VIEW); + } + } + + public static void cloneEntity(@Nonnull CommandSender sender, @Nonnull Ref entityReference, @Nonnull Store store) { + Holder copy = store.copyEntity(entityReference); + if (copy.getComponent(UUIDComponent.getComponentType()) != null) { + copy.replaceComponent(UUIDComponent.getComponentType(), new UUIDComponent(UUID.randomUUID())); + } + + store.addEntity(copy, AddReason.SPAWN); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityCommand.java new file mode 100644 index 0000000..ac407fc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityCommand.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity; + +import com.hypixel.hytale.server.core.command.commands.world.entity.snapshot.EntitySnapshotSubCommand; +import com.hypixel.hytale.server.core.command.commands.world.entity.stats.EntityStatsSubCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class EntityCommand extends AbstractCommandCollection { + public EntityCommand() { + super("entity", "server.commands.entity.desc"); + this.addAliases("entities"); + this.addSubCommand(new EntityCloneCommand()); + this.addSubCommand(new EntityRemoveCommand()); + this.addSubCommand(new EntityDumpCommand()); + this.addSubCommand(new EntityCleanCommand()); + this.addSubCommand(new EntityLodCommand()); + this.addSubCommand(new EntityTrackerCommand()); + this.addSubCommand(new EntityResendCommand()); + this.addSubCommand(new EntityNameplateCommand()); + this.addSubCommand(new EntityStatsSubCommand()); + this.addSubCommand(new EntitySnapshotSubCommand()); + this.addSubCommand(new EntityEffectCommand()); + this.addSubCommand(new EntityMakeInteractableCommand()); + this.addSubCommand(new EntityIntangibleCommand()); + this.addSubCommand(new EntityInvulnerableCommand()); + this.addSubCommand(new EntityHideFromAdventurePlayersCommand()); + this.addSubCommand(new EntityCountCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityCountCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityCountCommand.java new file mode 100644 index 0000000..a727b71 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityCountCommand.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EntityCountCommand extends AbstractWorldCommand { + public EntityCountCommand() { + super("count", "server.commands.entity.count.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + context.sendMessage(Message.translation("server.commands.entity.count.count").param("count", store.getEntityCount())); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityDumpCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityDumpCommand.java new file mode 100644 index 0000000..0638dd0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityDumpCommand.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.BsonUtil; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; + +public class EntityDumpCommand extends AbstractWorldCommand { + private static final Message MESSAGE_GENERAL_NO_ENTITY_IN_VIEW = Message.translation("server.general.noEntityInView"); + private static final Message MESSAGE_COMMANDS_ENTITY_DUMP_DUMP_DONE = Message.translation("server.commands.entity.dump.dumpDone"); + @Nonnull + private final EntityWrappedArg entityArg = this.withOptionalArg("entity", "server.commands.entity.dump.entity.desc", ArgTypes.ENTITY_ID); + + public EntityDumpCommand() { + super("dump", "server.commands.entity.dump.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Ref entityRef = this.entityArg.get(store, context); + if (entityRef != null && entityRef.isValid()) { + Holder holder = store.copyEntity(entityRef); + BsonDocument document = EntityStore.REGISTRY.serialize(holder); + HytaleLogger.getLogger().at(Level.INFO).log("Entity: %s\n%s\n%s", entityRef, holder, BsonUtil.toJson(document)); + context.sendMessage(MESSAGE_COMMANDS_ENTITY_DUMP_DUMP_DONE); + } else { + context.sendMessage(MESSAGE_GENERAL_NO_ENTITY_IN_VIEW); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityEffectCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityEffectCommand.java new file mode 100644 index 0000000..299467b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityEffectCommand.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity; + +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.OverlapBehavior; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetEntityCommand; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import javax.annotation.Nonnull; + +public class EntityEffectCommand extends AbstractTargetEntityCommand { + @Nonnull + private final RequiredArg effectArg = this.withRequiredArg("effect", "server.commands.entity.effect.effect.desc", ArgTypes.EFFECT_ASSET); + @Nonnull + private final DefaultArg durationArg = this.withDefaultArg( + "duration", "server.commands.entity.effect.duration.desc", ArgTypes.FLOAT, 100.0F, "server.commands.entity.effect.duration.default" + ) + .addValidator(Validators.greaterThan(0.0F)); + + public EntityEffectCommand() { + super("effect", "server.commands.entity.effect.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull ObjectList> entities, @Nonnull World world, @Nonnull Store store + ) { + EntityEffect entityEffect = this.effectArg.get(context); + float duration = this.durationArg.get(context); + + for (Ref entityRef : entities) { + EffectControllerComponent effectControllerComponent = store.getComponent(entityRef, EffectControllerComponent.getComponentType()); + if (effectControllerComponent != null) { + effectControllerComponent.addEffect(entityRef, entityEffect, duration, OverlapBehavior.OVERWRITE, store); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityHideFromAdventurePlayersCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityHideFromAdventurePlayersCommand.java new file mode 100644 index 0000000..27c2cd9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityHideFromAdventurePlayersCommand.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetEntityCommand; +import com.hypixel.hytale.server.core.modules.entity.component.HiddenFromAdventurePlayers; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import javax.annotation.Nonnull; + +public class EntityHideFromAdventurePlayersCommand extends AbstractTargetEntityCommand { + @Nonnull + private final FlagArg removeFlag = this.withFlagArg("remove", "server.commands.entity.hidefromadventureplayers.remove.desc"); + + public EntityHideFromAdventurePlayersCommand() { + super("hidefromadventureplayers", "server.commands.entity.hidefromadventureplayers.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull ObjectList> entities, @Nonnull World world, @Nonnull Store store + ) { + boolean remove = this.removeFlag.provided(context); + + for (Ref entity : entities) { + if (remove) { + store.tryRemoveComponent(entity, HiddenFromAdventurePlayers.getComponentType()); + } else { + store.ensureComponent(entity, HiddenFromAdventurePlayers.getComponentType()); + } + } + + context.sendMessage( + Message.translation("server.commands.entity.hidefromadventureplayers.success." + (remove ? "unset" : "set")).param("amount", entities.size()) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityIntangibleCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityIntangibleCommand.java new file mode 100644 index 0000000..369fe65 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityIntangibleCommand.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetEntityCommand; +import com.hypixel.hytale.server.core.modules.entity.component.Intangible; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import javax.annotation.Nonnull; + +public class EntityIntangibleCommand extends AbstractTargetEntityCommand { + @Nonnull + private final FlagArg removeFlag = this.withFlagArg("remove", "server.commands.entity.intangible.remove.desc"); + + public EntityIntangibleCommand() { + super("intangible", "server.commands.entity.intangible.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull ObjectList> entities, @Nonnull World world, @Nonnull Store store + ) { + boolean remove = this.removeFlag.provided(context); + + for (Ref entity : entities) { + if (remove) { + store.tryRemoveComponent(entity, Intangible.getComponentType()); + } else { + store.ensureComponent(entity, Intangible.getComponentType()); + } + } + + context.sendMessage(Message.translation("server.commands.entity.intangible.success." + (remove ? "unset" : "set")).param("amount", entities.size())); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityInvulnerableCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityInvulnerableCommand.java new file mode 100644 index 0000000..2a906f9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityInvulnerableCommand.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetEntityCommand; +import com.hypixel.hytale.server.core.modules.entity.component.Invulnerable; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import javax.annotation.Nonnull; + +public class EntityInvulnerableCommand extends AbstractTargetEntityCommand { + @Nonnull + private final FlagArg removeFlag = this.withFlagArg("remove", "server.commands.entity.invulnerable.remove.desc"); + + public EntityInvulnerableCommand() { + super("invulnerable", "server.commands.entity.invulnerable.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull ObjectList> entities, @Nonnull World world, @Nonnull Store store + ) { + boolean remove = this.removeFlag.provided(context); + + for (Ref entity : entities) { + if (remove) { + store.tryRemoveComponent(entity, Invulnerable.getComponentType()); + } else { + store.ensureComponent(entity, Invulnerable.getComponentType()); + } + } + + context.sendMessage(Message.translation("server.commands.entity.invulnerable.success." + (remove ? "unset" : "set")).param("amount", entities.size())); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityLodCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityLodCommand.java new file mode 100644 index 0000000..b3e2362 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityLodCommand.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.modules.entity.tracker.LegacyEntityTrackerSystems; +import javax.annotation.Nonnull; + +public class EntityLodCommand extends CommandBase { + @Nonnull + private final RequiredArg ratioArg = this.withRequiredArg("ratio", "server.commands.entity.lod.ratio.desc", ArgTypes.DOUBLE); + + public EntityLodCommand() { + super("lod", "server.commands.entity.lod.desc"); + this.addSubCommand(new EntityLodCommand.Default()); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + LegacyEntityTrackerSystems.LegacyLODCull.ENTITY_LOD_RATIO = this.ratioArg.get(context); + context.sendMessage(Message.translation("server.commands.entity.lod.ratioSet").param("ratio", LegacyEntityTrackerSystems.LegacyLODCull.ENTITY_LOD_RATIO)); + } + + static class Default extends CommandBase { + Default() { + super("default", "server.commands.entity.lod.default.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + LegacyEntityTrackerSystems.LegacyLODCull.ENTITY_LOD_RATIO = 3.5E-5; + context.sendMessage( + Message.translation("server.commands.entity.lod.ratioSet").param("ratio", LegacyEntityTrackerSystems.LegacyLODCull.ENTITY_LOD_RATIO) + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityMakeInteractableCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityMakeInteractableCommand.java new file mode 100644 index 0000000..2f23d84 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityMakeInteractableCommand.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetEntityCommand; +import com.hypixel.hytale.server.core.modules.entity.component.Interactable; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import javax.annotation.Nonnull; + +public class EntityMakeInteractableCommand extends AbstractTargetEntityCommand { + @Nonnull + private final FlagArg disableFlag = this.withFlagArg("disable", "server.commands.entity.interactable.disable.desc"); + + public EntityMakeInteractableCommand() { + super("interactable", "server.commands.entity.interactable.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull ObjectList> entities, @Nonnull World world, @Nonnull Store store + ) { + boolean disable = this.disableFlag.provided(context); + + for (Ref entity : entities) { + if (disable) { + store.tryRemoveComponent(entity, Interactable.getComponentType()); + } else { + store.ensureComponent(entity, Interactable.getComponentType()); + } + } + + context.sendMessage(Message.translation("server.commands.entity.interactable.success." + (disable ? "unset" : "set")).param("amount", entities.size())); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityNameplateCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityNameplateCommand.java new file mode 100644 index 0000000..8f0af31 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityNameplateCommand.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.entity.nameplate.Nameplate; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EntityNameplateCommand extends AbstractWorldCommand { + private static final Message MESSAGE_GENERAL_NO_ENTITY_IN_VIEW = Message.translation("server.general.noEntityInView"); + private static final Message MESSAGE_COMMANDS_ENTITY_NAMEPLATE_UPDATED = Message.translation("server.commands.entity.nameplate.updated"); + @Nonnull + private final EntityWrappedArg entityArg = this.withOptionalArg("entity", "server.commands.entity.nameplate.entity.desc", ArgTypes.ENTITY_ID); + @Nonnull + private final RequiredArg textArg = this.withRequiredArg("text", "server.commands.entity.nameplate.text.desc", ArgTypes.STRING); + + public EntityNameplateCommand() { + super("nameplate", "server.commands.entity.nameplate.desc"); + this.addUsageVariant(new EntityNameplateCommand.Remove()); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Ref entityRef = this.entityArg.get(store, context); + if (entityRef != null && entityRef.isValid()) { + String text = this.textArg.get(context); + store.ensureAndGetComponent(entityRef, Nameplate.getComponentType()).setText(text); + context.sendMessage(MESSAGE_COMMANDS_ENTITY_NAMEPLATE_UPDATED); + } else { + context.sendMessage(MESSAGE_GENERAL_NO_ENTITY_IN_VIEW); + } + } + + public static class Remove extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_GENERAL_NO_ENTITY_IN_VIEW = Message.translation("server.general.noEntityInView"); + @Nonnull + private static final Message MESSAGE_COMMANDS_ENTITY_NAMEPLATE_REMOVED = Message.translation("server.commands.entity.nameplate.removed"); + @Nonnull + private final EntityWrappedArg entityArg = this.withOptionalArg("entity", "server.commands.entity.nameplate.entity.desc", ArgTypes.ENTITY_ID); + + public Remove() { + super("server.commands.entity.nameplate.remove.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Ref entityRef = this.entityArg.get(store, context); + if (entityRef != null && entityRef.isValid()) { + store.tryRemoveComponent(entityRef, Nameplate.getComponentType()); + context.sendMessage(MESSAGE_COMMANDS_ENTITY_NAMEPLATE_REMOVED); + } else { + context.sendMessage(MESSAGE_GENERAL_NO_ENTITY_IN_VIEW); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityRemoveCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityRemoveCommand.java new file mode 100644 index 0000000..7f8d5e4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityRemoveCommand.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityRemoveCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_GENERAL_NO_ENTITY_IN_VIEW = Message.translation("server.general.noEntityInView"); + @Nonnull + private final EntityWrappedArg entityArg = this.withOptionalArg("entity", "server.commands.entity.remove.entity.desc", ArgTypes.ENTITY_ID); + @Nonnull + private final FlagArg othersFlag = this.withFlagArg("others", "server.commands.entity.remove.others.desc"); + + public EntityRemoveCommand() { + super("remove", "server.commands.entity.remove.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Ref entityRef = this.entityArg.get(store, context); + if (entityRef != null && entityRef.isValid()) { + if (this.othersFlag.provided(context)) { + store.forEachEntityParallel((index, archetypeChunk, commandBuffer) -> { + if (!archetypeChunk.getArchetype().contains(Player.getComponentType())) { + if (!archetypeChunk.getReferenceTo(index).equals(entityRef)) { + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + } + } + }); + } else { + removeEntity(context.senderAsPlayerRef(), entityRef, store); + } + } else { + context.sendMessage(MESSAGE_GENERAL_NO_ENTITY_IN_VIEW); + } + } + + public static void removeEntity( + @Nullable Ref playerRef, @Nonnull Ref entityReference, @Nonnull ComponentAccessor componentAccessor + ) { + if (playerRef != null && playerRef.isValid()) { + EntityTrackerSystems.EntityViewer entityViewer = componentAccessor.getComponent(playerRef, EntityTrackerSystems.EntityViewer.getComponentType()); + if (entityViewer != null && !entityViewer.visible.contains(entityReference)) { + PlayerRef playerRefComponent = componentAccessor.getComponent(playerRef, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerRefComponent.sendMessage(Message.translation("server.general.entity.remove.unableToRemove").param("id", entityReference.getIndex())); + return; + } + } + + componentAccessor.removeEntity(entityReference, EntityStore.REGISTRY.newHolder(), RemoveReason.REMOVE); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityResendCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityResendCommand.java new file mode 100644 index 0000000..9668d0c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityResendCommand.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EntityResendCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private static final Message MESSAGE_COMMANDS_ENTITY_RESET_NO_ENTITY_VIEWER_COMPONENT = Message.translation( + "server.commands.entity.resend.noEntityViewerComponent" + ); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.entity.resend.player.desc", ArgTypes.PLAYER_REF); + + public EntityResendCommand() { + super("resend", "server.commands.entity.resend.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + PlayerRef playerRef = this.playerArg.get(context); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + if (!EntityTrackerSystems.despawnAll(ref, store)) { + context.sendMessage(MESSAGE_COMMANDS_ENTITY_RESET_NO_ENTITY_VIEWER_COMPONENT); + } + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityTrackerCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityTrackerCommand.java new file mode 100644 index 0000000..cc58e4a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/EntityTrackerCommand.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EntityTrackerCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private static final Message MESSAGE_COMMANDS_ENTITY_TRACKER_NO_VIEWER_COMPONENT = Message.translation("server.commands.entity.tracker.noViewerComponent"); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.entity.tracker.player.desc", ArgTypes.PLAYER_REF); + + public EntityTrackerCommand() { + super("tracker", "server.commands.entity.tracker.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + PlayerRef playerRef = this.playerArg.get(context); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + EntityTrackerSystems.EntityViewer entityViewerComponent = store.getComponent(ref, EntityTrackerSystems.EntityViewer.getComponentType()); + if (entityViewerComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ENTITY_TRACKER_NO_VIEWER_COMPONENT); + } else { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + context.sendMessage( + Message.translation("server.commands.entityTracker.summary") + .param("visibleCount", entityViewerComponent.visible.size()) + .param("lodExcludedCount", entityViewerComponent.lodExcludedCount) + .param("hiddenCount", entityViewerComponent.hiddenCount) + .param("totalCount", entityViewerComponent.visible.size() + entityViewerComponent.lodExcludedCount + entityViewerComponent.hiddenCount) + .param("worldTotalCount", store.getEntityCount()) + .param("viewRadius", playerComponent.getViewRadius()) + .param("viewRadiusBlocks", entityViewerComponent.viewRadiusBlocks) + ); + } + } + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/snapshot/EntitySnapshotHistoryCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/snapshot/EntitySnapshotHistoryCommand.java new file mode 100644 index 0000000..6dbc11e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/snapshot/EntitySnapshotHistoryCommand.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity.snapshot; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.entity.EntitySnapshot; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.SnapshotBuffer; +import com.hypixel.hytale.server.core.universe.world.ParticleUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import javax.annotation.Nonnull; + +public class EntitySnapshotHistoryCommand extends AbstractWorldCommand { + public EntitySnapshotHistoryCommand() { + super("history", "server.commands.entity.snapshot.history.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + ComponentType snapshotBufferComponentType = SnapshotBuffer.getComponentType(); + store.forEachChunk(snapshotBufferComponentType, (chunk, cmdBuffer) -> { + for (int idx = 0; idx < chunk.size(); idx++) { + SnapshotBuffer snapshotBufferComponent = chunk.getComponent(idx, snapshotBufferComponentType); + + assert snapshotBufferComponent != null; + + if (!snapshotBufferComponent.isInitialized()) { + return; + } + + for (int i = snapshotBufferComponent.getOldestTickIndex(); i <= snapshotBufferComponent.getCurrentTickIndex(); i++) { + EntitySnapshot snapshot = snapshotBufferComponent.getSnapshot(i); + + assert snapshot != null; + + Vector3d pos = snapshot.getPosition(); + SpatialResource, EntityStore> playerSpatialResource = cmdBuffer.getResource(EntityModule.get().getPlayerSpatialResourceType()); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(pos, 75.0, results); + ParticleUtil.spawnParticleEffect("Example_Simple", pos.x, pos.y, pos.z, results, cmdBuffer); + } + } + }); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/snapshot/EntitySnapshotLengthCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/snapshot/EntitySnapshotLengthCommand.java new file mode 100644 index 0000000..5d8f322 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/snapshot/EntitySnapshotLengthCommand.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity.snapshot; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.modules.entity.system.SnapshotSystems; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + +public class EntitySnapshotLengthCommand extends CommandBase { + @Nonnull + private final RequiredArg lengthArg = this.withRequiredArg("length", "server.commands.entity.snapshot.length.length.desc", ArgTypes.INTEGER); + + public EntitySnapshotLengthCommand() { + super("length", "server.commands.entity.snapshot.length.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + int lengthMs = this.lengthArg.get(context); + SnapshotSystems.HISTORY_LENGTH_NS = TimeUnit.MILLISECONDS.toNanos(lengthMs); + long millis = TimeUnit.NANOSECONDS.toMillis(SnapshotSystems.HISTORY_LENGTH_NS); + context.sendMessage(Message.translation("server.commands.entity.snapshot.lengthSet").param("millis", millis)); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/snapshot/EntitySnapshotSubCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/snapshot/EntitySnapshotSubCommand.java new file mode 100644 index 0000000..b911f6b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/snapshot/EntitySnapshotSubCommand.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity.snapshot; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class EntitySnapshotSubCommand extends AbstractCommandCollection { + public EntitySnapshotSubCommand() { + super("snapshot", "server.commands.entity.snapshot.desc"); + this.addAliases("snap"); + this.addSubCommand(new EntitySnapshotLengthCommand()); + this.addSubCommand(new EntitySnapshotHistoryCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsAddCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsAddCommand.java new file mode 100644 index 0000000..5dc052a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsAddCommand.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity.stats; + +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetEntityCommand; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.List; +import javax.annotation.Nonnull; + +public class EntityStatsAddCommand extends AbstractTargetEntityCommand { + @Nonnull + private final RequiredArg entityStatNameArg = this.withRequiredArg("statName", "server.commands.entity.stats.add.statName.desc", ArgTypes.STRING); + @Nonnull + private final RequiredArg statAmountArg = this.withRequiredArg("statAmount", "server.commands.entity.stats.add.statAmount.desc", ArgTypes.INTEGER); + + public EntityStatsAddCommand() { + super("add", "server.commands.entity.stats.add.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull ObjectList> entities, @Nonnull World world, @Nonnull Store store + ) { + int statAmount = this.statAmountArg.get(context); + String entityStatName = this.entityStatNameArg.get(context); + addEntityStat(context, entities, statAmount, entityStatName, store); + } + + public static void addEntityStat( + @Nonnull CommandContext context, + @Nonnull List> entityRefs, + int statAmount, + @Nonnull String entityStatName, + @Nonnull Store store + ) { + int entityStatIndex = EntityStatType.getAssetMap().getIndex(entityStatName); + if (entityStatIndex == Integer.MIN_VALUE) { + context.sendMessage(Message.translation("server.commands.entityStats.entityStatNotFound").param("name", entityStatName)); + context.sendMessage( + Message.translation("server.general.failed.didYouMean") + .param( + "choices", + StringUtil.sortByFuzzyDistance(entityStatName, EntityStatType.getAssetMap().getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT).toString() + ) + ); + } else { + for (Ref entity : entityRefs) { + EntityStatMap entityStatMapComponent = store.getComponent(entity, EntityStatsModule.get().getEntityStatMapComponentType()); + if (entityStatMapComponent != null) { + if (entityStatMapComponent.get(entityStatIndex) == null) { + context.sendMessage(Message.translation("server.commands.entityStats.entityStatNotFound").param("name", entityStatName)); + context.sendMessage( + Message.translation("server.general.failed.didYouMean") + .param( + "choices", + StringUtil.sortByFuzzyDistance(entityStatName, EntityStatType.getAssetMap().getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT) + .toString() + ) + ); + } else { + float newValueOfStat = entityStatMapComponent.addStatValue(entityStatIndex, statAmount); + context.sendMessage(Message.translation("server.commands.entityStats.success").param("name", entityStatName).param("value", newValueOfStat)); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsDumpCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsDumpCommand.java new file mode 100644 index 0000000..33f048e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsDumpCommand.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity.stats; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetEntityCommand; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.List; +import javax.annotation.Nonnull; + +public class EntityStatsDumpCommand extends AbstractTargetEntityCommand { + public EntityStatsDumpCommand() { + super("dump", "server.commands.entity.stats.dump.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull ObjectList> entities, @Nonnull World world, @Nonnull Store store + ) { + dumpEntityStatsData(context, entities, store); + } + + public static void dumpEntityStatsData(@Nonnull CommandContext context, @Nonnull List> entities, @Nonnull Store store) { + for (Ref entity : entities) { + ComponentType component = EntityStatsModule.get().getEntityStatMapComponentType(); + EntityStatMap statMap = store.getComponent(entity, component); + if (statMap != null) { + ObjectArrayList values = new ObjectArrayList<>(statMap.size()); + + for (int i = 0; i < statMap.size(); i++) { + EntityStatValue entityStat = statMap.get(i); + values.add(Message.translation("server.commands.entityStats.value").param("name", entityStat.getId()).param("value", entityStat.get())); + } + + context.sendMessage(MessageFormat.list(null, values)); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsGetCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsGetCommand.java new file mode 100644 index 0000000..486cdcd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsGetCommand.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity.stats; + +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetEntityCommand; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.List; +import javax.annotation.Nonnull; + +public class EntityStatsGetCommand extends AbstractTargetEntityCommand { + @Nonnull + private final RequiredArg entityStatNameArg = this.withRequiredArg("statName", "server.commands.entity.stats.get.statName.desc", ArgTypes.STRING); + + public EntityStatsGetCommand() { + super("get", "server.commands.entity.stats.get.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull ObjectList> entities, @Nonnull World world, @Nonnull Store store + ) { + String entityStat = this.entityStatNameArg.get(context); + getEntityStat(context, entities, entityStat, store); + } + + public static void getEntityStat( + @Nonnull CommandContext context, @Nonnull List> entities, @Nonnull String entityStat, @Nonnull Store store + ) { + int entityStatIndex = EntityStatType.getAssetMap().getIndex(entityStat); + if (entityStatIndex == Integer.MIN_VALUE) { + context.sendMessage(Message.translation("server.commands.entityStats.entityStatNotFound").param("name", entityStat)); + context.sendMessage( + Message.translation("server.general.failed.didYouMean") + .param( + "choices", + StringUtil.sortByFuzzyDistance(entityStat, EntityStatType.getAssetMap().getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT).toString() + ) + ); + } else { + for (Ref entity : entities) { + EntityStatMap entityStatMapComponent = store.getComponent(entity, EntityStatsModule.get().getEntityStatMapComponentType()); + if (entityStatMapComponent != null) { + EntityStatValue value = entityStatMapComponent.get(entityStatIndex); + if (value == null) { + context.sendMessage(Message.translation("server.commands.entityStats.entityStatNotFound").param("name", entityStat)); + context.sendMessage( + Message.translation("server.general.failed.didYouMean") + .param( + "choices", + StringUtil.sortByFuzzyDistance(entityStat, EntityStatType.getAssetMap().getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT) + .toString() + ) + ); + } else { + context.sendMessage(Message.translation("server.commands.entityStats.value").param("name", value.getId()).param("value", value.get())); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsResetCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsResetCommand.java new file mode 100644 index 0000000..1b415e0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsResetCommand.java @@ -0,0 +1,72 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity.stats; + +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetEntityCommand; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.List; +import javax.annotation.Nonnull; + +public class EntityStatsResetCommand extends AbstractTargetEntityCommand { + @Nonnull + private final RequiredArg entityStatNameArg = this.withRequiredArg("statName", "server.commands.entity.stats.reset.statName.desc", ArgTypes.STRING); + + public EntityStatsResetCommand() { + super("reset", "server.commands.entity.stats.reset.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull ObjectList> entities, @Nonnull World world, @Nonnull Store store + ) { + String entityStat = this.entityStatNameArg.get(context); + resetEntityStat(context, entities, entityStat, store); + } + + public static void resetEntityStat( + @Nonnull CommandContext context, @Nonnull List> entities, @Nonnull String entityStat, @Nonnull Store store + ) { + int entityStatIndex = EntityStatType.getAssetMap().getIndex(entityStat); + if (entityStatIndex == Integer.MIN_VALUE) { + context.sendMessage(Message.translation("server.commands.entityStats.entityStatNotFound").param("name", entityStat)); + context.sendMessage( + Message.translation("server.general.failed.didYouMean") + .param( + "choices", + StringUtil.sortByFuzzyDistance(entityStat, EntityStatType.getAssetMap().getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT).toString() + ) + ); + } else { + for (Ref entity : entities) { + EntityStatMap entityStatMap = store.getComponent(entity, EntityStatsModule.get().getEntityStatMapComponentType()); + if (entityStatMap != null) { + if (entityStatMap.get(entityStatIndex) == null) { + context.sendMessage(Message.translation("server.commands.entityStats.entityStatNotFound").param("name", entityStat)); + context.sendMessage( + Message.translation("server.general.failed.didYouMean") + .param( + "choices", + StringUtil.sortByFuzzyDistance(entityStat, EntityStatType.getAssetMap().getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT) + .toString() + ) + ); + } else { + float valueResetTo = entityStatMap.resetStatValue(entityStatIndex); + context.sendMessage(Message.translation("server.commands.entityStats.valueReset").param("name", entityStat).param("value", valueResetTo)); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsSetCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsSetCommand.java new file mode 100644 index 0000000..15c50b6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsSetCommand.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity.stats; + +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetEntityCommand; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.List; +import javax.annotation.Nonnull; + +public class EntityStatsSetCommand extends AbstractTargetEntityCommand { + @Nonnull + private final RequiredArg entityStatNameArg = this.withRequiredArg("statName", "server.commands.entity.stats.set.statName.desc", ArgTypes.STRING); + @Nonnull + private final RequiredArg statValueArg = this.withRequiredArg("statValue", "server.commands.entity.stats.set.statValue.desc", ArgTypes.INTEGER); + + public EntityStatsSetCommand() { + super("set", "server.commands.entity.stats.set.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull ObjectList> entities, @Nonnull World world, @Nonnull Store store + ) { + int newStatValue = this.statValueArg.get(context); + String entityStatName = this.entityStatNameArg.get(context); + setEntityStat(context, entities, newStatValue, entityStatName, store); + } + + public static void setEntityStat( + @Nonnull CommandContext context, + @Nonnull List> entities, + int newStatValue, + @Nonnull String entityStatName, + @Nonnull Store store + ) { + int entityStatIndex = EntityStatType.getAssetMap().getIndex(entityStatName); + if (entityStatIndex == Integer.MIN_VALUE) { + context.sendMessage(Message.translation("server.commands.entityStats.entityStatNotFound").param("name", entityStatName)); + context.sendMessage( + Message.translation("server.general.failed.didYouMean") + .param( + "choices", + StringUtil.sortByFuzzyDistance(entityStatName, EntityStatType.getAssetMap().getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT).toString() + ) + ); + } else { + for (Ref entity : entities) { + EntityStatMap entityStatMap = store.getComponent(entity, EntityStatsModule.get().getEntityStatMapComponentType()); + if (entityStatMap != null) { + if (entityStatMap.get(entityStatIndex) == null) { + context.sendMessage(Message.translation("server.commands.entityStats.entityStatNotFound").param("name", entityStatName)); + context.sendMessage( + Message.translation("server.general.failed.didYouMean") + .param( + "choices", + StringUtil.sortByFuzzyDistance(entityStatName, EntityStatType.getAssetMap().getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT) + .toString() + ) + ); + } else { + float newValueOfStat = entityStatMap.setStatValue(entityStatIndex, newStatValue); + context.sendMessage(Message.translation("server.commands.entityStats.success").param("name", entityStatName).param("value", newValueOfStat)); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsSetToMaxCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsSetToMaxCommand.java new file mode 100644 index 0000000..5fbe52d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsSetToMaxCommand.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity.stats; + +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractTargetEntityCommand; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.List; +import javax.annotation.Nonnull; + +public class EntityStatsSetToMaxCommand extends AbstractTargetEntityCommand { + @Nonnull + private final RequiredArg entityStatNameArg = this.withRequiredArg( + "statName", "server.commands.entity.stats.settomax.statName.desc", ArgTypes.STRING + ); + + public EntityStatsSetToMaxCommand() { + super("settomax", "server.commands.entity.stats.settomax.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull ObjectList> entities, @Nonnull World world, @Nonnull Store store + ) { + String entityStatName = this.entityStatNameArg.get(context); + setEntityStatMax(context, entities, entityStatName, store); + } + + public static void setEntityStatMax( + @Nonnull CommandContext context, @Nonnull List> entities, @Nonnull String entityStatName, @Nonnull Store store + ) { + int entityStatIndex = EntityStatType.getAssetMap().getIndex(entityStatName); + if (entityStatIndex == Integer.MIN_VALUE) { + context.sendMessage(Message.translation("server.commands.entityStats.entityStatNotFound").param("name", entityStatName)); + context.sendMessage( + Message.translation("server.general.failed.didYouMean") + .param( + "choices", + StringUtil.sortByFuzzyDistance(entityStatName, EntityStatType.getAssetMap().getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT).toString() + ) + ); + } else { + for (Ref entity : entities) { + EntityStatMap entityStatMap = store.getComponent(entity, EntityStatsModule.get().getEntityStatMapComponentType()); + if (entityStatMap != null) { + EntityStatValue entityStatValue = entityStatMap.get(entityStatIndex); + if (entityStatValue == null) { + context.sendMessage(Message.translation("server.commands.entityStats.entityStatNotFound").param("name", entityStatName)); + context.sendMessage( + Message.translation("server.general.failed.didYouMean") + .param( + "choices", + StringUtil.sortByFuzzyDistance(entityStatName, EntityStatType.getAssetMap().getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT) + .toString() + ) + ); + } else { + float newValueOfStat = entityStatMap.setStatValue(entityStatIndex, entityStatValue.getMax()); + context.sendMessage(Message.translation("server.commands.entityStats.success").param("name", entityStatName).param("value", newValueOfStat)); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsSubCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsSubCommand.java new file mode 100644 index 0000000..6b2d9e2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/entity/stats/EntityStatsSubCommand.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.server.core.command.commands.world.entity.stats; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class EntityStatsSubCommand extends AbstractCommandCollection { + public EntityStatsSubCommand() { + super("stats", "server.commands.entity.stats.desc"); + this.addAliases("stat"); + this.addSubCommand(new EntityStatsDumpCommand()); + this.addSubCommand(new EntityStatsGetCommand()); + this.addSubCommand(new EntityStatsSetCommand()); + this.addSubCommand(new EntityStatsSetToMaxCommand()); + this.addSubCommand(new EntityStatsResetCommand()); + this.addSubCommand(new EntityStatsAddCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/worldgen/WorldGenBenchmarkCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/worldgen/WorldGenBenchmarkCommand.java new file mode 100644 index 0000000..51c0546 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/worldgen/WorldGenBenchmarkCommand.java @@ -0,0 +1,161 @@ +package com.hypixel.hytale.server.core.command.commands.world.worldgen; + +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.IBenchmarkableWorldGen; +import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import java.io.File; +import java.io.FileWriter; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class WorldGenBenchmarkCommand extends CommandBase { + private static final AtomicBoolean IS_RUNNING = new AtomicBoolean(false); + public static final Message MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_SAVING = Message.translation("server.commands.worldgenbenchmark.saving"); + public static final Message MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_DONE = Message.translation("server.commands.worldgenbenchmark.done"); + public static final Message MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_SAVE_FAILED = Message.translation("server.commands.worldgenbenchmark.saveFailed"); + public static final Message MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_SAVE_DONE = Message.translation("server.commands.worldgenbenchmark.saveDone"); + public static final Message MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_PROGRESS = Message.translation("server.commands.worldgenbenchmark.progress"); + public static final Message MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_STARTED = Message.translation("server.commands.worldgenbenchmark.started"); + public static final Message MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_ABORT = Message.translation("server.commands.worldgenbenchmark.abort"); + public static final Message MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_BENCHMARK_NOT_SUPPORTED = Message.translation( + "server.commands.worldgenbenchmark.benchmarkNotSupported" + ); + public static final Message MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_ALREADY_IN_PROGRESS = Message.translation( + "server.commands.worldgenbenchmark.alreadyInProgress" + ); + @Nonnull + private final OptionalArg worldArg = this.withOptionalArg("world", "server.commands.worldthread.arg.desc", ArgTypes.WORLD); + @Nonnull + private final OptionalArg seedArg = this.withOptionalArg("seed", "server.commands.worldgenbenchmark.seed.desc", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg pos1Arg = this.withRequiredArg("pos1", "server.commands.worldgenbenchmark.pos1.desc", ArgTypes.VECTOR2I); + @Nonnull + private final RequiredArg pos2Arg = this.withRequiredArg("pos2", "server.commands.worldgenbenchmark.pos2.desc", ArgTypes.VECTOR2I); + + public WorldGenBenchmarkCommand() { + super("benchmark", "server.commands.worldgenbenchmark.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + if (IS_RUNNING.get()) { + context.sendMessage(MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_ALREADY_IN_PROGRESS); + } else { + World world = this.worldArg.getProcessed(context); + String worldName = world.getName(); + int seed = this.seedArg.provided(context) ? this.seedArg.get(context) : (int)world.getWorldConfig().getSeed(); + IWorldGen worldGen = world.getChunkStore().getGenerator(); + if (!(worldGen instanceof IBenchmarkableWorldGen benchmarkableWorldGen)) { + context.sendMessage(MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_BENCHMARK_NOT_SUPPORTED); + } else { + Vector2i corner1 = this.pos1Arg.get(context); + Vector2i corner2 = this.pos2Arg.get(context); + int minX; + int maxX; + if (corner1.x < corner2.x) { + minX = ChunkUtil.chunkCoordinate(corner1.x); + maxX = ChunkUtil.chunkCoordinate(corner2.x); + } else { + minX = ChunkUtil.chunkCoordinate(corner2.x); + maxX = ChunkUtil.chunkCoordinate(corner1.x); + } + + int minZ; + int maxZ; + if (corner1.y < corner2.y) { + minZ = ChunkUtil.chunkCoordinate(corner1.y); + maxZ = ChunkUtil.chunkCoordinate(corner2.y); + } else { + minZ = ChunkUtil.chunkCoordinate(corner2.y); + maxZ = ChunkUtil.chunkCoordinate(corner1.y); + } + + LongArrayList generatingChunks = new LongArrayList(); + + for (int x = minX; x <= maxX; x++) { + for (int z = minZ; z <= maxZ; z++) { + generatingChunks.add(ChunkUtil.indexChunk(x, z)); + } + } + + if (IS_RUNNING.getAndSet(true)) { + context.sendMessage(MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_ABORT); + } else { + context.sendMessage( + MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_STARTED.param("seed", seed).param("worldName", worldName).param("size", generatingChunks.size()) + ); + benchmarkableWorldGen.getBenchmark().start(); + int chunkCount = generatingChunks.size(); + long startTime = System.nanoTime(); + new Thread( + () -> { + Set> currentChunks = new HashSet<>(); + long nextBroadcast = System.nanoTime(); + + do { + long thisTime = System.nanoTime(); + if (thisTime >= nextBroadcast) { + world.execute( + () -> world.sendMessage( + MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_PROGRESS.param( + "percent", Math.round((1.0 - (double)generatingChunks.size() / chunkCount) * 1000.0) / 10.0 + ) + ) + ); + nextBroadcast = thisTime + 5000000000L; + } + + currentChunks.removeIf(CompletableFuture::isDone); + + for (int i = currentChunks.size(); i < 20 && !generatingChunks.isEmpty(); i++) { + long index = generatingChunks.removeLong(generatingChunks.size() - 1); + CompletableFuture future = worldGen.generate( + seed, index, ChunkUtil.xOfChunkIndex(index), ChunkUtil.zOfChunkIndex(index), idx -> true + ); + currentChunks.add(future); + } + } while (!currentChunks.isEmpty()); + + String duration = FormatUtil.nanosToString(System.nanoTime() - startTime); + world.execute(() -> world.sendMessage(MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_DONE.param("duration", duration))); + world.execute(() -> world.sendMessage(MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_SAVING)); + String fileName = "quant." + System.currentTimeMillis() + "." + (maxX - minX) + "x" + (maxZ - minZ) + "." + worldName + ".txt"; + File folder = new File("quantification"); + File file = new File("quantification" + File.separator + fileName); + folder.mkdirs(); + + try (FileWriter fw = new FileWriter(file)) { + fw.write(benchmarkableWorldGen.getBenchmark().buildReport().join()); + world.execute(() -> world.sendMessage(MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_SAVE_DONE.param("fileName", fileName))); + } catch (Exception var25) { + HytaleLogger.getLogger().at(Level.SEVERE).withCause(var25).log("Failed to save worldgen benchmark report!"); + world.execute(() -> world.sendMessage(MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_SAVE_FAILED)); + } + + benchmarkableWorldGen.getBenchmark().stop(); + IS_RUNNING.set(false); + }, + "WorldGenBenchmarkCommand" + ) + .start(); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/worldgen/WorldGenCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/worldgen/WorldGenCommand.java new file mode 100644 index 0000000..2e02424 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/worldgen/WorldGenCommand.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.core.command.commands.world.worldgen; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class WorldGenCommand extends AbstractCommandCollection { + public WorldGenCommand() { + super("worldgen", "server.commands.worldgen.desc"); + this.addAliases("wg"); + this.addSubCommand(new WorldGenBenchmarkCommand()); + this.addSubCommand(new WorldGenReloadCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/commands/world/worldgen/WorldGenReloadCommand.java b/src/com/hypixel/hytale/server/core/command/commands/world/worldgen/WorldGenReloadCommand.java new file mode 100644 index 0000000..30656aa --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/commands/world/worldgen/WorldGenReloadCommand.java @@ -0,0 +1,184 @@ +package com.hypixel.hytale.server.core.command.commands.world.worldgen; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.IChunkSaver; +import com.hypixel.hytale.server.core.universe.world.storage.component.ChunkSavingSystems; +import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen; +import com.hypixel.hytale.server.core.universe.world.worldgen.WorldGenLoadException; +import com.hypixel.hytale.server.core.universe.world.worldmap.IWorldMap; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class WorldGenReloadCommand extends AbstractAsyncWorldCommand { + private static final AtomicBoolean IS_RUNNING = new AtomicBoolean(false); + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_GEN_RELOAD_STARTED = Message.translation("server.commands.worldgen.reload.started"); + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_GEN_RELOAD_COMPLETE = Message.translation("server.commands.worldgen.reload.complete"); + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_GEN_RELOAD_CHUNK_SAVING_DISABLED = Message.translation( + "server.commands.worldgen.reload.chunkSavingDisabled" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_GEN_RELOAD_DELETING_CHUNKS = Message.translation("server.commands.worldgen.reload.deletingChunks"); + @Nonnull + private static final Message MKESSAGE_COMMANDS_WORLD_GEN_RELOAD_CHUNK_SAVING_ENABLED = Message.translation( + "server.commands.worldgen.reload.chunkSavingEnabled" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_GEN_RELOAD_REGENERATING_LOADED_CHUNKS = Message.translation( + "server.commands.worldgen.reload.regeneratingLoadedChunks" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_GEN_RELOAD_CHUNK_SAVING_ENABLED = Message.translation( + "server.commands.worldgen.reload.chunkSavingEnabled" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_GEN_RELOAD_ALREADY_IN_PROGRESS = Message.translation("server.commands.worldgen.reload.alreadyInProgress"); + @Nonnull + public static final Message MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_ABORT = Message.translation("server.commands.worldgen.reload.abort"); + @Nonnull + private final FlagArg clearArg = this.withFlagArg("clear", "server.commands.worldgen.reload.clear.desc"); + + public WorldGenReloadCommand() { + super("reload", "server.commands.worldgen.reload.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context, @Nonnull World world) { + if (IS_RUNNING.get()) { + context.sendMessage(MESSAGE_COMMANDS_WORLD_GEN_RELOAD_ALREADY_IN_PROGRESS); + return CompletableFuture.completedFuture(null); + } else { + context.sendMessage(MESSAGE_COMMANDS_WORLD_GEN_RELOAD_STARTED); + WorldConfig worldConfig = world.getWorldConfig(); + ChunkStore chunkComponentStore = world.getChunkStore(); + if (IS_RUNNING.getAndSet(true)) { + context.sendMessage(MESSAGE_COMMANDS_WORLD_GEN_BENCHMARK_ABORT); + return CompletableFuture.completedFuture(null); + } else { + CompletableFuture worldMap; + try { + IWorldGen worldGen = worldConfig.getWorldGenProvider().getGenerator(); + chunkComponentStore.setGenerator(worldGen); + worldConfig.setDefaultSpawnProvider(worldGen); + worldConfig.markChanged(); + IWorldMap worldMapx = worldConfig.getWorldMapProvider().getGenerator(world); + world.getWorldMapManager().setGenerator(worldMapx); + context.sendMessage(MESSAGE_COMMANDS_WORLD_GEN_RELOAD_COMPLETE); + return this.clearArg.provided(context) ? clearChunks(context, world) : CompletableFuture.completedFuture(null); + } catch (WorldGenLoadException var11) { + context.sendMessage(Message.translation("server.commands.worldgen.reload.failed").param("error", var11.getTraceMessage("\n"))); + HytaleLogger.getLogger().at(Level.SEVERE).withCause(new SkipSentryException(var11)).log("Failed to load WorldGen!"); + return CompletableFuture.completedFuture(null); + } catch (Exception var12) { + context.sendMessage(Message.translation("server.commands.worldgen.reload.failed").param("error", var12.getMessage())); + HytaleLogger.getLogger().at(Level.SEVERE).withCause(var12).log("Exception when trying to load WorldGen!"); + worldMap = CompletableFuture.completedFuture(null); + } finally { + IS_RUNNING.set(false); + } + + return worldMap; + } + } + } + + @Nonnull + private static CompletableFuture clearChunks(@Nonnull CommandContext context, @Nonnull World world) { + ChunkStore chunkComponentStore = world.getChunkStore(); + Store componentStore = chunkComponentStore.getStore(); + ChunkSavingSystems.Data data = componentStore.getResource(ChunkStore.SAVE_RESOURCE); + data.isSaving = false; + data.clearSaveQueue(); + context.sendMessage(MESSAGE_COMMANDS_WORLD_GEN_RELOAD_CHUNK_SAVING_DISABLED); + context.sendMessage(MESSAGE_COMMANDS_WORLD_GEN_RELOAD_DELETING_CHUNKS); + IChunkSaver saver = chunkComponentStore.getSaver(); + if (saver == null) { + context.sendMessage(MKESSAGE_COMMANDS_WORLD_GEN_RELOAD_CHUNK_SAVING_ENABLED); + return CompletableFuture.completedFuture(null); + } else { + return CompletableFuture.supplyAsync(SneakyThrow.sneakySupplier(() -> { + try { + return saver.getIndexes(); + } catch (IOException var3x) { + HytaleLogger.getLogger().at(Level.SEVERE).withCause(var3x).log("Failed to get chunk indexes for clearing!"); + context.sendMessage(Message.translation("server.commands.worldgen.reload.failed").param("error", var3x.getMessage())); + throw SneakyThrow.sneakyThrow(var3x); + } + }), world) + .thenComposeAsync( + indexes -> { + AtomicInteger counter = new AtomicInteger(); + double total = indexes.size(); + ObjectArrayList> futures = new ObjectArrayList<>(); + LongIterator iterator = indexes.iterator(); + + while (iterator.hasNext()) { + long index = iterator.nextLong(); + int x = ChunkUtil.xOfChunkIndex(index); + int z = ChunkUtil.zOfChunkIndex(index); + futures.add( + saver.removeHolder(x, z) + .thenRun( + () -> { + int i = counter.getAndIncrement(); + if (i > 0 && i % 64 == 0) { + world.execute( + () -> context.sendMessage( + Message.translation("server.commands.worldgen.reload.deletingChunksProgress") + .param("progress", MathUtil.round(i * 100 / total, 2)) + ) + ); + } + } + ) + ); + } + + return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)); + }, + world + ) + .thenComposeAsync(aVoid -> { + context.sendMessage(MESSAGE_COMMANDS_WORLD_GEN_RELOAD_REGENERATING_LOADED_CHUNKS); + LongSet chunkIndexes = chunkComponentStore.getChunkIndexes(); + ObjectArrayList> regenerateFutures = new ObjectArrayList<>(); + LongIterator chunkIterator = chunkIndexes.iterator(); + + while (chunkIterator.hasNext()) { + long index = chunkIterator.nextLong(); + regenerateFutures.add(chunkComponentStore.getChunkReferenceAsync(index, 9)); + } + + return CompletableFuture.allOf(regenerateFutures.toArray(CompletableFuture[]::new)); + }, world) + .thenRunAsync(() -> { + Store chunkStore = chunkComponentStore.getStore(); + ChunkSavingSystems.Data saveData = chunkStore.getResource(ChunkStore.SAVE_RESOURCE); + saveData.isSaving = true; + context.sendMessage(MESSAGE_COMMANDS_WORLD_GEN_RELOAD_CHUNK_SAVING_ENABLED); + }, world); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/AbbreviationMap.java b/src/com/hypixel/hytale/server/core/command/system/AbbreviationMap.java new file mode 100644 index 0000000..595dae9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/AbbreviationMap.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.core.command.system; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class AbbreviationMap { + private final Map abbreviationMap; + + public AbbreviationMap(@Nonnull Map abbreviationMap) { + this.abbreviationMap = abbreviationMap; + } + + public Value get(@Nonnull String abbreviation) { + return this.abbreviationMap.get(abbreviation.toLowerCase()); + } + + @Nonnull + public static AbbreviationMap.AbbreviationMapBuilder create() { + return new AbbreviationMap.AbbreviationMapBuilder(); + } + + public static class AbbreviationMapBuilder { + private final Map keys = new Object2ObjectOpenHashMap<>(); + + public AbbreviationMapBuilder() { + } + + @Nonnull + public AbbreviationMap.AbbreviationMapBuilder put(@Nonnull String key, @Nonnull Value value) { + if (this.keys.putIfAbsent(key.toLowerCase(), value) != null) { + throw new IllegalArgumentException("Cannot have values with the same key in AbbreviationMap: " + key); + } else { + return this; + } + } + + @Nonnull + public AbbreviationMap build() { + Object2ObjectOpenHashMap abbreviationMap = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : this.keys.entrySet()) { + this.appendAbbreviation(entry.getKey(), entry.getValue(), abbreviationMap); + } + + abbreviationMap.values().removeIf(Objects::isNull); + abbreviationMap.trim(); + return new AbbreviationMap<>(Collections.unmodifiableMap(abbreviationMap)); + } + + private void appendAbbreviation(@Nonnull String key, @Nonnull Value value, @Nonnull Map map) { + map.put(key, value); + + for (int i = 1; i < key.length(); i++) { + String substring = key.substring(0, key.length() - i); + Value existingAbbreviationValue = map.get(substring); + if (existingAbbreviationValue == null) { + map.put(substring, value); + } else if (!this.keys.containsKey(substring) && !existingAbbreviationValue.equals(value)) { + map.put(substring, null); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/AbstractCommand.java b/src/com/hypixel/hytale/server/core/command/system/AbstractCommand.java new file mode 100644 index 0000000..5284916 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/AbstractCommand.java @@ -0,0 +1,891 @@ +package com.hypixel.hytale.server.core.command.system; + +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Constants; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.arguments.system.AbstractOptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.ArgWrapper; +import com.hypixel.hytale.server.core.command.system.arguments.system.Argument; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.WrappedArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgumentType; +import com.hypixel.hytale.server.core.command.system.arguments.types.ListArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.modules.i18n.I18nModule; +import com.hypixel.hytale.server.core.plugin.PluginBase; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectBooleanPair; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class AbstractCommand { + @Nonnull + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final String[] EMPTY_STRING_ARRAY = new String[0]; + @Nonnull + private static final Message MESSAGE_COMMANDS_HELP_NO_PERMISSIBLE_SUB_COMMAND = Message.translation("server.commands.help.noPermissibleSubCommand"); + @Nonnull + private static final Message MESSAGE_COMMANDS_PARSING_ERROR_NO_PERMISSION_FOR_COMMAND = Message.translation( + "server.commands.parsing.error.noPermissionForCommand" + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_PARSING_ERROR_ATTEMPTED_UNSAFE = Message.translation("server.commands.parsing.error.attemptedUnsafe"); + @Nonnull + private static final Message MESSAGE_COMMANDS_PARSING_USAGE_REQUIRES_CONFIRMATION = Message.translation("server.commands.parsing.usage.requiresConfirmation"); + @Nonnull + private static final Message MESSAGE_COMMAND_SINGLEPLAYER = Message.translation("server.commands.parsing.error.unavailableInSingleplayer"); + @Nonnull + static final String CONFIRM_ARG_TAG = "confirm"; + @Nonnull + private static final String COLOR_STRING_ARG_REQUIRED = "#C1E0FF"; + @Nonnull + private static final String COLOR_STRING_ARG_OPTIONAL = "#7E9EBC"; + private AbstractCommand parentCommand; + @Nullable + private final String name; + @Nonnull + private final Set aliases = new HashSet<>(); + private final String description; + @Nonnull + private final List> requiredArguments = new ObjectArrayList<>(); + @Nonnull + private final Map> optionalArguments = new Object2ObjectOpenHashMap<>(); + private AbbreviationMap> argumentAbbreviationMap; + @Nonnull + private final Map subCommands = new LinkedHashMap<>(); + @Nonnull + private final Map subCommandsAliases = new LinkedHashMap<>(); + @Nonnull + private final Int2ObjectMap variantCommands = new Int2ObjectOpenHashMap<>(); + @Nullable + private CommandOwner owner; + @Nullable + private String permission; + @Nullable + private List permissionGroups; + private int totalNumRequiredParameters; + private final boolean requiresConfirmation; + private boolean unavailableInSingleplayer; + private boolean allowsExtraArguments; + private boolean hasBeenRegistered; + + protected AbstractCommand(@Nullable String name, @Nullable String description, boolean requiresConfirmation) { + this.name = name == null ? null : name.toLowerCase(); + this.description = description; + this.requiresConfirmation = requiresConfirmation; + if (requiresConfirmation) { + this.registerOptionalArg(new FlagArg(this, "confirm", "")); + } + } + + protected AbstractCommand(@Nullable String name, @Nullable String description) { + this(name, description, false); + } + + protected AbstractCommand(@Nullable String description) { + this(null, description); + } + + public void setOwner(@Nonnull CommandOwner owner) { + this.owner = owner; + if (this.permission == null && this.canGeneratePermission()) { + this.permission = this.generatePermission(); + } + + for (AbstractCommand subCommand : this.subCommands.values()) { + subCommand.setOwner(owner); + } + + for (AbstractCommand variantCommand : this.variantCommands.values()) { + variantCommand.setOwner(owner); + } + } + + protected boolean canGeneratePermission() { + return true; + } + + @Nullable + protected String generatePermissionNode() { + return this.name == null ? null : this.name.toLowerCase(); + } + + @Nonnull + private String generatePermission() { + String selfNode = this.generatePermissionNode(); + if (this.parentCommand != null) { + String parentPermission = this.parentCommand.permission == null ? this.parentCommand.generatePermission() : this.parentCommand.permission; + String generatedPermission; + if (selfNode != null && !selfNode.isEmpty()) { + generatedPermission = parentPermission + "." + selfNode; + } else { + generatedPermission = parentPermission; + } + + LOGGER.atFine().log("Generated missing permission '" + generatedPermission + "'."); + return generatedPermission; + } else if (this.owner instanceof PluginBase plugin) { + return plugin.getBasePermission() + ".command." + selfNode; + } else if (this.owner instanceof CommandManager) { + return "hytale.system.command." + selfNode; + } else { + throw new IllegalArgumentException("Unknown owner type, please use PluginBase or CommandManager"); + } + } + + @Nullable + public List getPermissionGroups() { + return this.permissionGroups; + } + + protected void setPermissionGroups(@Nonnull String... groups) { + this.permissionGroups = Arrays.asList(groups); + } + + protected void setPermissionGroup(@Nullable GameMode gameMode) { + this.setPermissionGroups(gameMode == null ? null : gameMode.toString()); + } + + @Nonnull + public Map> getPermissionGroupsRecursive() { + Map> permissionsByGroup = new Object2ObjectOpenHashMap<>(); + this.putRecursivePermissionGroups(permissionsByGroup); + return permissionsByGroup; + } + + public void putRecursivePermissionGroups(@Nonnull Map> permissionsByGroup) { + List permissionGroups = this.permissionGroups; + if (permissionGroups == null && this.parentCommand != null) { + permissionGroups = this.parentCommand.permissionGroups; + } + + if (permissionGroups != null && this.permission != null) { + for (String group : permissionGroups) { + if (group != null) { + permissionsByGroup.computeIfAbsent(group, k -> new HashSet<>()).add(this.permission); + } + } + } + + for (AbstractCommand subCommand : this.subCommands.values()) { + subCommand.putRecursivePermissionGroups(permissionsByGroup); + } + } + + protected void setUnavailableInSingleplayer(boolean unavailableInSingleplayer) { + this.unavailableInSingleplayer = unavailableInSingleplayer; + } + + public void setAllowsExtraArguments(boolean allowsExtraArguments) { + this.allowsExtraArguments = allowsExtraArguments; + } + + @Nonnull + public MatchResult matches(@Nonnull String language, @Nonnull String search, int termDepth) { + return this.matches(language, search, termDepth, 0); + } + + @Nonnull + private MatchResult matches(@Nonnull String language, @Nonnull String search, int termDepth, int depth) { + if (this.name != null && this.name.contains(search)) { + return MatchResult.of(termDepth, depth, 0, this.name, search); + } else { + for (String alias : this.aliases) { + if (alias.contains(search)) { + return MatchResult.of(termDepth, depth, 1, alias, search); + } + } + + for (AbstractOptionalArg opt : this.optionalArguments.values()) { + if (opt.getName().contains(search)) { + return MatchResult.of(termDepth, depth, 3, opt.getName(), search); + } + + for (String aliasx : opt.getAliases()) { + if (aliasx.contains(search)) { + return MatchResult.of(termDepth, depth, 3, aliasx, search); + } + } + } + + for (RequiredArg opt : this.requiredArguments) { + if (opt.getName().contains(search)) { + return MatchResult.of(termDepth, depth, 3, opt.getName(), search); + } + } + + if (this.description != null) { + String descriptionMessage = I18nModule.get().getMessage(language, this.description); + if (descriptionMessage != null && descriptionMessage.contains(search)) { + return MatchResult.of(termDepth, depth, 4, descriptionMessage, search); + } + } + + for (AbstractOptionalArg optx : this.optionalArguments.values()) { + String description = optx.getDescription(); + if (description != null) { + String usageDescription = I18nModule.get().getMessage(language, description); + if (usageDescription != null && usageDescription.contains(search)) { + return MatchResult.of(termDepth, depth, 5, usageDescription, search); + } + } + } + + for (AbstractCommand subCommand : this.subCommands.values()) { + MatchResult result = subCommand.matches(language, search, termDepth, depth + 1); + if (result != MatchResult.NONE) { + return result; + } + } + + for (AbstractCommand variantCommand : this.variantCommands.values()) { + MatchResult result = variantCommand.matches(language, search, termDepth, depth + 1); + if (result != MatchResult.NONE) { + return result; + } + } + + return MatchResult.NONE; + } + } + + public void completeRegistration() throws GeneralCommandException { + this.hasBeenRegistered = true; + + for (AbstractCommand command : this.subCommands.values()) { + command.completeRegistration(); + } + + for (AbstractCommand command : this.variantCommands.values()) { + command.completeRegistration(); + } + + this.validateVariantNumberOfRequiredParameters(new ParseResult(true)); + this.validateDefaultArguments(new ParseResult(true)); + this.createOptionalArgumentAbbreviationMap(); + } + + private void createOptionalArgumentAbbreviationMap() { + AbbreviationMap.AbbreviationMapBuilder> abbreviationMapBuilder = AbbreviationMap.create(); + + for (AbstractOptionalArg abstractOptionalArg : this.optionalArguments.values()) { + abbreviationMapBuilder.put(abstractOptionalArg.getName(), abstractOptionalArg); + + for (String alias : abstractOptionalArg.getAliases()) { + abbreviationMapBuilder.put(alias, abstractOptionalArg); + } + } + + this.argumentAbbreviationMap = abbreviationMapBuilder.build(); + } + + private void validateVariantNumberOfRequiredParameters(@Nonnull ParseResult result) { + for (Entry entry : this.variantCommands.int2ObjectEntrySet()) { + if (this.totalNumRequiredParameters == entry.getValue().totalNumRequiredParameters) { + result.fail( + Message.raw( + "Command '" + + this.getFullyQualifiedName() + + "' and its variant '" + + entry.getValue().toString() + + "' both have " + + this.totalNumRequiredParameters + + " required parameters. Variants must have different numbers of required parameters." + ) + ); + return; + } + } + } + + private void validateDefaultArguments(@Nonnull ParseResult parseResult) { + for (AbstractOptionalArg value : this.optionalArguments.values()) { + if (value instanceof DefaultArg defaultArg) { + defaultArg.validateDefaultValue(parseResult); + } + } + } + + public void requirePermission(@Nonnull String permission) { + this.permission = permission; + } + + @Nullable + public String getFullyQualifiedName() { + if (this.parentCommand != null) { + return this.isVariant() ? this.parentCommand.getFullyQualifiedName() : this.parentCommand.getFullyQualifiedName() + " " + this.name; + } else { + return this.name; + } + } + + public int countParents() { + return this.parentCommand == null ? 0 : this.parentCommand.countParents() + 1; + } + + public void addAliases(@Nonnull String... aliases) { + if (this.hasBeenRegistered) { + throw new IllegalStateException("Cannot add aliases when a command has already completed registration"); + } else if (this.name == null) { + throw new IllegalStateException("Cannot add aliases to a command with no name"); + } else { + for (String alias : aliases) { + this.aliases.add(alias.toLowerCase()); + } + } + } + + public void addSubCommand(@Nonnull AbstractCommand command) { + if (this.hasBeenRegistered) { + throw new IllegalStateException("Cannot add new subcommands when a command has already completed registration"); + } else if (this.isVariant()) { + throw new IllegalStateException("Cannot add a subcommand to a variant command, can only add subcommands to named commands"); + } else if (command.name == null) { + throw new IllegalArgumentException("Cannot add a subcommand with no name"); + } else if (command.parentCommand != null) { + throw new IllegalArgumentException("Cannot re-use subcommands. Only one parent command allowed for each subcommand"); + } else { + command.parentCommand = this; + if (this.subCommands.containsKey(command.name)) { + throw new IllegalArgumentException("Cannot have multiple subcommands with the same name"); + } else if (this.subCommandsAliases.containsKey(command.name)) { + throw new IllegalArgumentException("Command has same name as existing command alias for command: " + command.name); + } else { + this.subCommands.put(command.name, command); + + for (String alias : command.aliases) { + if (this.subCommandsAliases.containsKey(alias) || this.subCommands.containsKey(alias)) { + throw new IllegalArgumentException("Cannot specify a subcommand alias with the same name as an existing command or alias: " + alias); + } + + this.subCommandsAliases.put(alias, command.name); + } + + command.hasBeenRegistered = true; + } + } + } + + public void addUsageVariant(@Nonnull AbstractCommand command) { + if (this.hasBeenRegistered) { + throw new IllegalStateException("Cannot add new variants when a command has already completed registration"); + } else if (this.isVariant()) { + throw new IllegalStateException("Cannot add a command variant to a variant command, can only add variants to named commands"); + } else if (command.name != null) { + throw new IllegalArgumentException("Cannot add a variant command with a name, use the description-only constructor"); + } else if (command.parentCommand != null) { + throw new IllegalArgumentException("Cannot re-use variant commands. Only one parent command allowed for each variant command"); + } else { + AbstractCommand variantWithSameNumRequiredParameters = this.variantCommands.put(command.totalNumRequiredParameters, command); + if (variantWithSameNumRequiredParameters != null) { + throw new IllegalArgumentException( + "You have already registered a variant command with " + + command.totalNumRequiredParameters + + " required parameters. Command's class name: " + + variantWithSameNumRequiredParameters.getClass().getName() + ); + } else { + command.parentCommand = this; + command.hasBeenRegistered = true; + } + } + } + + @Nullable + public CompletableFuture acceptCall(@Nonnull CommandSender sender, @Nonnull ParserContext parserContext, @Nonnull ParseResult parseResult) { + parserContext.convertToSubCommand(); + return this.acceptCall0(sender, parserContext, parseResult); + } + + @Nullable + private CompletableFuture acceptCall0(@Nonnull CommandSender sender, @Nonnull ParserContext parserContext, @Nonnull ParseResult parseResult) { + int numberOfPreOptionalTokens = parserContext.getNumPreOptionalTokens(); + ObjectBooleanPair> completableFutureBooleanPair = this.checkForExecutingSubcommands( + sender, parserContext, parseResult, numberOfPreOptionalTokens + ); + if (parseResult.failed()) { + return null; + } else if (completableFutureBooleanPair.rightBoolean()) { + return completableFutureBooleanPair.left(); + } else if (!this.hasPermission(sender)) { + parseResult.fail(MESSAGE_COMMANDS_PARSING_ERROR_NO_PERMISSION_FOR_COMMAND); + return null; + } else if (this instanceof AbstractCommandCollection && numberOfPreOptionalTokens != 0) { + HashSet commandNames = new HashSet<>(this.subCommands.keySet()); + commandNames.addAll(this.subCommandsAliases.keySet()); + String firstToken = parserContext.getFirstToken(); + String commandSuggestionPrefix = "\n/" + this.getFullyQualifiedName() + " "; + String suggestedCommands = commandSuggestionPrefix + String.join(commandSuggestionPrefix, StringUtil.sortByFuzzyDistance(firstToken, commandNames, 5)); + parseResult.fail( + Message.translation("server.commands.parsing.error.commandCollectionSubcommandNotFound") + .param("subcommand", firstToken) + .param("suggestions", suggestedCommands) + ); + return null; + } else if (parserContext.isHelpSpecified()) { + sender.sendMessage(this.getUsageString(sender)); + return null; + } else if (this.unavailableInSingleplayer && Constants.SINGLEPLAYER) { + parseResult.fail(MESSAGE_COMMAND_SINGLEPLAYER); + return null; + } else if (this.requiresConfirmation && !parserContext.isConfirmationSpecified()) { + parseResult.fail(MESSAGE_COMMANDS_PARSING_ERROR_ATTEMPTED_UNSAFE); + return null; + } else { + if (this.allowsExtraArguments) { + if (numberOfPreOptionalTokens < this.totalNumRequiredParameters) { + parseResult.fail( + Message.translation("server.commands.parsing.error.wrongNumberRequiredParameters") + .param("expected", this.totalNumRequiredParameters) + .param("actual", numberOfPreOptionalTokens) + .insert("\n") + .insert(Message.translation("server.commands.help.usagecolon").param("usage", this.getUsageShort(sender, true))) + .insert("\n") + .insert(Message.translation("server.commands.help.useHelpToLearnMore").param("command", this.getFullyQualifiedName())) + ); + return null; + } + } else if (this.totalNumRequiredParameters != numberOfPreOptionalTokens) { + parseResult.fail( + Message.translation("server.commands.parsing.error.wrongNumberRequiredParameters") + .param("expected", this.totalNumRequiredParameters) + .param("actual", numberOfPreOptionalTokens) + .insert("\n") + .insert(Message.translation("server.commands.help.usagecolon").param("usage", this.getUsageShort(sender, true))) + .insert("\n") + .insert(Message.translation("server.commands.help.useHelpToLearnMore").param("command", this.getFullyQualifiedName())) + ); + return null; + } + + CommandContext commandContext = new CommandContext(this, sender, parserContext.getInputString()); + this.processRequiredArguments(parserContext, parseResult, commandContext); + if (parseResult.failed()) { + return null; + } else { + this.processOptionalArguments(parserContext, parseResult, commandContext); + return parseResult.failed() ? null : this.execute(commandContext); + } + } + } + + public boolean hasPermission(@Nonnull CommandSender sender) { + String permission = this.getPermission(); + if (permission == null) { + return true; + } else if (sender.hasPermission(permission)) { + return this.parentCommand == null ? true : this.parentCommand.hasPermission(sender); + } else { + return false; + } + } + + @Nonnull + private ObjectBooleanPair> checkForExecutingSubcommands( + @Nonnull CommandSender sender, @Nonnull ParserContext parserContext, @Nonnull ParseResult parseResult, int numberOfPreOptionalTokens + ) { + if (parserContext.getNumPreOptSingleValueTokensBeforeListTokens() >= 0) { + if (!this.subCommands.isEmpty()) { + String subCommandName = parserContext.getPreOptionalSingleValueToken(0); + if (subCommandName != null) { + subCommandName = subCommandName.toLowerCase(); + } + + AbstractCommand subCommand = this.subCommands.get(subCommandName); + if (subCommand != null) { + parserContext.convertToSubCommand(); + return ObjectBooleanPair.of(subCommand.acceptCall0(sender, parserContext, parseResult), true); + } + + String alias = this.subCommandsAliases.get(subCommandName); + if (alias != null) { + parserContext.convertToSubCommand(); + return ObjectBooleanPair.of(this.subCommands.get(alias).acceptCall0(sender, parserContext, parseResult), true); + } + } + + AbstractCommand commandVariant = this.variantCommands.get(numberOfPreOptionalTokens); + if (this.totalNumRequiredParameters != numberOfPreOptionalTokens && commandVariant != null) { + return ObjectBooleanPair.of(commandVariant.acceptCall0(sender, parserContext, parseResult), true); + } + } + + return ObjectBooleanPair.of(null, false); + } + + private void processRequiredArguments(@Nonnull ParserContext parserContext, @Nonnull ParseResult parseResult, @Nonnull CommandContext commandContext) { + int currentReqArgIndex = 0; + + for (RequiredArg requiredArgument : this.requiredArguments) { + if (requiredArgument.getArgumentType().isListArgument() && parserContext.isListToken(currentReqArgIndex)) { + ParserContext.PreOptionalListContext preOptionalTokenContext = parserContext.getPreOptionalListToken(currentReqArgIndex); + currentReqArgIndex++; + commandContext.appendArgumentData(requiredArgument, preOptionalTokenContext.getTokens(), true, parseResult); + } else { + String[] argParameters = new String[requiredArgument.getArgumentType().getNumberOfParameters()]; + + for (int i = 0; i < requiredArgument.getArgumentType().getNumberOfParameters(); i++) { + if (parserContext.isListToken(currentReqArgIndex)) { + parseResult.fail(Message.translation("server.commands.parsing.error.notAList").param("name", requiredArgument.getName())); + return; + } + + argParameters[i] = parserContext.getPreOptionalSingleValueToken(currentReqArgIndex); + currentReqArgIndex++; + } + + commandContext.appendArgumentData(requiredArgument, argParameters, false, parseResult); + if (parseResult.failed()) { + return; + } + } + } + } + + private void processOptionalArguments(@Nonnull ParserContext parserContext, @Nonnull ParseResult parseResult, @Nonnull CommandContext commandContext) { + for (java.util.Map.Entry>> optionalArgContext : parserContext.getOptionalArgs()) { + AbstractOptionalArg, ?> optionalArg = (AbstractOptionalArg, ?>)this.argumentAbbreviationMap + .get(optionalArgContext.getKey()); + if (optionalArg == null) { + parseResult.fail(Message.translation("server.commands.parsing.error.couldNotFindOptionalArgName").param("input", optionalArgContext.getKey())); + return; + } + + if (optionalArg.getPermission() != null && !commandContext.sender().hasPermission(optionalArg.getPermission())) { + parseResult.fail(Message.translation("server.commands.parsing.error.noPermissionForOptional").param("argument", optionalArgContext.getKey())); + return; + } + + List> optionalArgValues = optionalArgContext.getValue(); + if (optionalArg.getArgumentType().isListArgument() && optionalArgValues.size() > 1) { + String[] optionalArgParseValues = new String[optionalArgValues.size() * optionalArgValues.getFirst().size()]; + + for (int i = 0; i < optionalArgValues.size(); i++) { + List values = optionalArgValues.get(i); + + for (int k = 0; k < values.size(); k++) { + optionalArgParseValues[i * values.size() + k] = values.get(k); + } + } + + commandContext.appendArgumentData(optionalArg, optionalArgParseValues, true, parseResult); + } else { + commandContext.appendArgumentData( + optionalArg, optionalArgValues.isEmpty() ? EMPTY_STRING_ARRAY : optionalArgValues.getFirst().toArray(EMPTY_STRING_ARRAY), false, parseResult + ); + } + } + + for (AbstractOptionalArg optionalArgx : this.optionalArguments.values()) { + optionalArgx.verifyArgumentDependencies(commandContext, parseResult); + } + } + + @Nullable + protected abstract CompletableFuture execute(@Nonnull CommandContext var1); + + @Nonnull + public Message getUsageString(@Nonnull CommandSender sender) { + Message requiredArgsMessage = Message.raw(""); + + for (RequiredArg requiredArgument : this.requiredArguments) { + requiredArgsMessage.insert(" ").insert(requiredArgument.getUsageMessageWithoutDescription()); + if (requiredArgument.getArgumentType().isListArgument()) { + requiredArgsMessage.insert("/..."); + } + } + + Message requiresConfirmationMessage = this.requiresConfirmation ? MESSAGE_COMMANDS_PARSING_USAGE_REQUIRES_CONFIRMATION : Message.raw(""); + Message requiredArgs = Message.raw(""); + boolean requiredArgsShown = false; + + for (RequiredArg requiredArgumentx : this.requiredArguments) { + requiredArgsShown = true; + requiredArgs.insert("\n ").insert(requiredArgumentx.getUsageMessage()); + } + + Message optionalArgs = Message.raw(""); + boolean optionalArgsShown = false; + Message defaultArgs = Message.raw(""); + boolean defaultArgsShown = false; + Message flagArgs = Message.raw(""); + boolean flagArgsShown = false; + + for (java.util.Map.Entry> entry : this.optionalArguments.entrySet()) { + AbstractOptionalArg, ?> arg = (AbstractOptionalArg, ?>)entry.getValue(); + if (arg.getPermission() == null || sender.hasPermission(arg.getPermission())) { + switch (arg) { + case OptionalArg ignored: + optionalArgsShown = true; + optionalArgs.insert("\n ").insert(arg.getUsageMessage()); + break; + case DefaultArg ignoredx: + defaultArgsShown = true; + defaultArgs.insert("\n ").insert(arg.getUsageMessage()); + break; + case FlagArg ignoredxx: + flagArgsShown = true; + flagArgs.insert("\n ").insert(arg.getUsageMessage()); + break; + default: + } + } + } + + if (requiredArgsShown) { + requiredArgs = Message.translation("server.commands.parsing.usage.requiredArgs").param("args", requiredArgs); + } + + if (optionalArgsShown) { + optionalArgs = Message.translation("server.commands.parsing.usage.optionalArgs").param("args", optionalArgs); + } + + if (flagArgsShown) { + flagArgs = Message.translation("server.commands.parsing.usage.flagArgs").param("args", flagArgs); + } + + if (defaultArgsShown) { + defaultArgs = Message.translation("server.commands.parsing.usage.defaultArgs").param("args", defaultArgs); + } + + Message variantsMessage = Message.raw(""); + + for (AbstractCommand value : this.variantCommands.values()) { + if (value.hasPermission(sender)) { + variantsMessage.insert( + Message.translation("server.commands.parsing.usage.subcommands") + .param("separator", Message.translation("server.commands.parsing.usage.subcommandSeparator")) + .param("usage", value.getUsageString(sender)) + ); + } + } + + Message subcommandsMessage = Message.raw(""); + + for (AbstractCommand valuex : this.subCommands.values()) { + if (valuex.hasPermission(sender)) { + subcommandsMessage.insert( + Message.translation("server.commands.parsing.usage.subcommands") + .param("separator", Message.translation("server.commands.parsing.usage.subcommandSeparator")) + .param("usage", valuex.getUsageString(sender)) + ); + } + } + + Message argTypesMessage = Message.raw("\nArgument Types:"); + HashSet> allArgumentTypes = new HashSet<>(); + + for (RequiredArg requiredArgumentx : this.requiredArguments) { + allArgumentTypes.add(requiredArgumentx.getArgumentType()); + } + + for (AbstractOptionalArg optionalArgument : this.optionalArguments.values()) { + allArgumentTypes.add(optionalArgument.getArgumentType()); + } + + for (ArgumentType argumentType : allArgumentTypes) { + argTypesMessage.insert("\n ") + .insert(argumentType.getName()) + .insert(": ") + .insert(argumentType.getArgumentUsage()) + .insert("\n Examples: ") + .insert("'") + .insert(String.join("', '", argumentType.getExamples())) + .insert("'."); + } + + return Message.translation("server.commands.parsing.usage.header") + .param("fullyQualifiedName", this.getFullyQualifiedName()) + .param("description", Message.translation(this.description)) + .param("listOfRequiredArgs", requiredArgsMessage) + .param("requiresConfirmation", requiresConfirmationMessage) + .param("requiredArgs", requiredArgs) + .param("optionalArgs", optionalArgs) + .param("defaultArgs", defaultArgs) + .param("flagArgs", flagArgs) + .param("argTypes", argTypesMessage) + .param("variants", variantsMessage) + .param("subcommands", subcommandsMessage); + } + + @Nonnull + public Message getUsageShort(@Nonnull CommandSender sender, boolean fullyQualify) { + String indent = " ".repeat(this.countParents()); + if (this.subCommands.isEmpty() && this.variantCommands.isEmpty()) { + String fullyQualifiedName = this.getFullyQualifiedName(); + Message message = Message.raw(indent) + .insert(fullyQualify ? (fullyQualifiedName != null ? fullyQualifiedName : "???") : (this.name != null ? this.name : "")); + + for (RequiredArg requiredArgument : this.requiredArguments) { + message.insert(" ").insert(requiredArgument.getUsageOneLiner().color("#C1E0FF")); + } + + for (AbstractOptionalArg optionalArgument : this.optionalArguments.values()) { + if (optionalArgument.hasPermission(sender)) { + message.insert(" ").insert(optionalArgument.getUsageOneLiner().color("#7E9EBC")); + } + } + + return message; + } else { + String prefix = this.parentCommand == null ? "/" : indent; + Message message = Message.raw(prefix).insert(this.name); + boolean anyPermissible = false; + + for (AbstractCommand variantCommand : this.variantCommands.values()) { + if (variantCommand.hasPermission(sender)) { + message.insert("\n ").insert(indent).insert(variantCommand.getUsageShort(sender, fullyQualify)); + anyPermissible = true; + } + } + + for (AbstractCommand subCommand : this.subCommands.values()) { + if (subCommand.hasPermission(sender)) { + message.insert("\n ").insert(indent).insert(subCommand.getUsageShort(sender, fullyQualify)); + anyPermissible = true; + } + } + + if (!anyPermissible) { + message.insert("\n ").insert(MESSAGE_COMMANDS_HELP_NO_PERMISSIBLE_SUB_COMMAND); + } + + return message; + } + } + + @Nonnull + private , D> R registerRequiredArg(@Nonnull R requiredArgument) { + if (this.hasBeenRegistered) { + throw new IllegalStateException("Cannot add new arguments when a command has already completed registration"); + } else if (requiredArgument.getCommandRegisteredTo().equals(this) && !this.requiredArguments.contains(requiredArgument)) { + this.totalNumRequiredParameters = this.totalNumRequiredParameters + requiredArgument.getArgumentType().getNumberOfParameters(); + this.requiredArguments.add(requiredArgument); + return requiredArgument; + } else { + throw new IllegalArgumentException("Cannot re-use arguments"); + } + } + + @Nonnull + private , D> R registerOptionalArg(@Nonnull R optionalArgument) { + if (this.hasBeenRegistered) { + throw new IllegalStateException("Cannot add new arguments when a command has already completed registration"); + } else if (optionalArgument.getCommandRegisteredTo().equals(this) && !this.optionalArguments.containsKey(optionalArgument.getName().toLowerCase())) { + this.optionalArguments.put(optionalArgument.getName().toLowerCase(), optionalArgument); + return optionalArgument; + } else { + throw new IllegalArgumentException("Cannot re-use arguments"); + } + } + + @Nonnull + public RequiredArg withRequiredArg(@Nonnull String name, @Nonnull String description, @Nonnull ArgumentType argType) { + return this.registerRequiredArg(new RequiredArg<>(this, name, description, argType)); + } + + public , D> W withRequiredArg(@Nonnull String name, @Nonnull String description, @Nonnull ArgWrapper wrapper) { + return wrapper.wrapArg(this.registerRequiredArg(new RequiredArg<>(this, name, description, wrapper.argumentType()))); + } + + @Nonnull + public RequiredArg> withListRequiredArg(@Nonnull String name, @Nonnull String description, @Nonnull ArgumentType argType) { + return this.registerRequiredArg(new RequiredArg<>(this, name, description, new ListArgumentType<>(argType))); + } + + @Nonnull + public DefaultArg withDefaultArg(String name, String description, ArgumentType argType, @Nullable D defaultValue, String defaultValueDescription) { + return this.registerOptionalArg(new DefaultArg<>(this, name, description, argType, defaultValue, defaultValueDescription)); + } + + public , D> W withDefaultArg( + @Nonnull String name, @Nonnull String description, @Nonnull ArgWrapper wrapper, D defaultValue, @Nonnull String defaultValueDescription + ) { + return wrapper.wrapArg(this.registerOptionalArg(new DefaultArg<>(this, name, description, wrapper.argumentType(), defaultValue, defaultValueDescription))); + } + + @Nonnull + public DefaultArg> withListDefaultArg( + @Nonnull String name, @Nonnull String description, @Nonnull ArgumentType argType, List defaultValue, @Nonnull String defaultValueDescription + ) { + return this.registerOptionalArg(new DefaultArg<>(this, name, description, new ListArgumentType<>(argType), defaultValue, defaultValueDescription)); + } + + @Nonnull + public OptionalArg withOptionalArg(@Nonnull String name, @Nonnull String description, @Nonnull ArgumentType argType) { + return this.registerOptionalArg(new OptionalArg<>(this, name, description, argType)); + } + + public , D> W withOptionalArg(@Nonnull String name, @Nonnull String description, @Nonnull ArgWrapper wrapper) { + return wrapper.wrapArg(this.registerOptionalArg(new OptionalArg<>(this, name, description, wrapper.argumentType()))); + } + + @Nonnull + public OptionalArg> withListOptionalArg(@Nonnull String name, @Nonnull String description, @Nonnull ArgumentType argType) { + return this.registerOptionalArg(new OptionalArg<>(this, name, description, new ListArgumentType<>(argType))); + } + + @Nonnull + public FlagArg withFlagArg(@Nonnull String name, @Nonnull String description) { + return this.registerOptionalArg(new FlagArg(this, name, description)); + } + + public boolean isVariant() { + return this.name == null; + } + + @Nullable + public String getName() { + return this.name; + } + + @Nonnull + public Set getAliases() { + return this.aliases; + } + + public String getDescription() { + return this.description; + } + + @Nullable + public CommandOwner getOwner() { + return this.owner; + } + + @Nullable + public String getPermission() { + return this.permission; + } + + @Nonnull + public Map getSubCommands() { + return this.subCommands; + } + + @Nonnull + public List> getRequiredArguments() { + return this.requiredArguments; + } + + public boolean hasBeenRegistered() { + return this.hasBeenRegistered; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/CommandContext.java b/src/com/hypixel/hytale/server/core/command/system/CommandContext.java new file mode 100644 index 0000000..9a12193 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/CommandContext.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.server.core.command.system; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.arguments.system.AbstractOptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.Argument; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.exceptions.SenderTypeException; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class CommandContext { + @Nonnull + private final AbstractCommand calledCommand; + @Nonnull + private final String inputString; + @Nonnull + private final CommandSender sender; + @Nonnull + private final Map, Object> argValues; + @Nonnull + private final Map, String[]> argInput; + + public CommandContext(@Nonnull AbstractCommand calledCommand, @Nonnull CommandSender sender, @Nonnull String inputString) { + this.calledCommand = calledCommand; + this.inputString = inputString; + this.sender = sender; + this.argValues = new Object2ObjectOpenHashMap<>(); + this.argInput = new Object2ObjectOpenHashMap<>(); + } + + void appendArgumentData(@Nonnull Argument argument, @Nonnull String[] data, boolean asListArgument, @Nonnull ParseResult parseResult) { + if (data.length == 0 && argument instanceof DefaultArg defaultArg) { + this.argValues.put(argument, defaultArg.getDefaultValue()); + } else { + int numParameters = argument.getArgumentType().getNumberOfParameters(); + if ((!asListArgument || data.length % numParameters == 0) && (asListArgument || data.length == numParameters)) { + DataType convertedValue = argument.getArgumentType().parse(data, parseResult); + if (!parseResult.failed()) { + argument.validate(convertedValue, parseResult); + if (!parseResult.failed()) { + this.argValues.put(argument, convertedValue); + this.argInput.put(argument, data); + } + } + } else { + parseResult.fail( + Message.translation("server.commands.parsing.error.wrongNumberOfParametersForArgument") + .param("argument", argument.getName()) + .param("expected", argument.getArgumentType().getNumberOfParameters()) + .param("actual", data.length) + .param("input", String.join(" ", data)) + ); + } + } + } + + public DataType get(@Nonnull Argument argument) { + if (!this.argValues.containsKey(argument) && argument instanceof AbstractOptionalArg.DefaultValueArgument defaultValueArgument) { + Object defaultValue = defaultValueArgument.getDefaultValue(); + this.argValues.put(argument, defaultValue); + return (DataType)defaultValue; + } else { + return (DataType)this.argValues.get(argument); + } + } + + public String[] getInput(@Nonnull Argument argument) { + return this.argInput.get(argument); + } + + public boolean provided(@Nonnull Argument argument) { + return this.argValues.containsKey(argument); + } + + @Nonnull + public String getInputString() { + return this.inputString; + } + + public void sendMessage(@Nonnull Message message) { + this.sender.sendMessage(message); + } + + public boolean isPlayer() { + return this.sender instanceof Player; + } + + @Nonnull + public T senderAs(@Nonnull Class senderType) { + try { + return senderType.cast(this.sender); + } catch (ClassCastException var3) { + throw new SenderTypeException(senderType); + } + } + + @Nullable + public Ref senderAsPlayerRef() { + return this.senderAs(Player.class).getReference(); + } + + @Nonnull + public CommandSender sender() { + return this.sender; + } + + @Nonnull + public AbstractCommand getCalledCommand() { + return this.calledCommand; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/CommandManager.java b/src/com/hypixel/hytale/server/core/command/system/CommandManager.java new file mode 100644 index 0000000..0274736 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/CommandManager.java @@ -0,0 +1,363 @@ +package com.hypixel.hytale.server.core.command.system; + +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.particle.commands.ParticleCommand; +import com.hypixel.hytale.server.core.command.commands.debug.AssetsCommand; +import com.hypixel.hytale.server.core.command.commands.debug.DebugPlayerPositionCommand; +import com.hypixel.hytale.server.core.command.commands.debug.HitDetectionCommand; +import com.hypixel.hytale.server.core.command.commands.debug.HudManagerTestCommand; +import com.hypixel.hytale.server.core.command.commands.debug.LogCommand; +import com.hypixel.hytale.server.core.command.commands.debug.MessageTranslationTestCommand; +import com.hypixel.hytale.server.core.command.commands.debug.PIDCheckCommand; +import com.hypixel.hytale.server.core.command.commands.debug.PacketStatsCommand; +import com.hypixel.hytale.server.core.command.commands.debug.PingCommand; +import com.hypixel.hytale.server.core.command.commands.debug.ShowBuilderToolsHudCommand; +import com.hypixel.hytale.server.core.command.commands.debug.StopNetworkChunkSendingCommand; +import com.hypixel.hytale.server.core.command.commands.debug.TagPatternCommand; +import com.hypixel.hytale.server.core.command.commands.debug.VersionCommand; +import com.hypixel.hytale.server.core.command.commands.debug.component.hitboxcollision.HitboxCollisionCommand; +import com.hypixel.hytale.server.core.command.commands.debug.component.repulsion.RepulsionCommand; +import com.hypixel.hytale.server.core.command.commands.debug.packs.PacksCommand; +import com.hypixel.hytale.server.core.command.commands.debug.server.ServerCommand; +import com.hypixel.hytale.server.core.command.commands.debug.stresstest.StressTestCommand; +import com.hypixel.hytale.server.core.command.commands.player.DamageCommand; +import com.hypixel.hytale.server.core.command.commands.player.GameModeCommand; +import com.hypixel.hytale.server.core.command.commands.player.HideCommand; +import com.hypixel.hytale.server.core.command.commands.player.KillCommand; +import com.hypixel.hytale.server.core.command.commands.player.PlayerCommand; +import com.hypixel.hytale.server.core.command.commands.player.ReferCommand; +import com.hypixel.hytale.server.core.command.commands.player.SudoCommand; +import com.hypixel.hytale.server.core.command.commands.player.ToggleBlockPlacementOverrideCommand; +import com.hypixel.hytale.server.core.command.commands.player.WhereAmICommand; +import com.hypixel.hytale.server.core.command.commands.player.WhoAmICommand; +import com.hypixel.hytale.server.core.command.commands.player.inventory.GiveCommand; +import com.hypixel.hytale.server.core.command.commands.player.inventory.InventoryCommand; +import com.hypixel.hytale.server.core.command.commands.player.inventory.ItemStateCommand; +import com.hypixel.hytale.server.core.command.commands.server.KickCommand; +import com.hypixel.hytale.server.core.command.commands.server.MaxPlayersCommand; +import com.hypixel.hytale.server.core.command.commands.server.StopCommand; +import com.hypixel.hytale.server.core.command.commands.server.WhoCommand; +import com.hypixel.hytale.server.core.command.commands.server.auth.AuthCommand; +import com.hypixel.hytale.server.core.command.commands.utility.BackupCommand; +import com.hypixel.hytale.server.core.command.commands.utility.ConvertPrefabsCommand; +import com.hypixel.hytale.server.core.command.commands.utility.EventTitleCommand; +import com.hypixel.hytale.server.core.command.commands.utility.NotifyCommand; +import com.hypixel.hytale.server.core.command.commands.utility.StashCommand; +import com.hypixel.hytale.server.core.command.commands.utility.ValidateCPBCommand; +import com.hypixel.hytale.server.core.command.commands.utility.git.UpdateCommand; +import com.hypixel.hytale.server.core.command.commands.utility.help.HelpCommand; +import com.hypixel.hytale.server.core.command.commands.utility.lighting.LightingCommand; +import com.hypixel.hytale.server.core.command.commands.utility.metacommands.CommandsCommand; +import com.hypixel.hytale.server.core.command.commands.utility.net.NetworkCommand; +import com.hypixel.hytale.server.core.command.commands.utility.sleep.SleepCommand; +import com.hypixel.hytale.server.core.command.commands.utility.sound.SoundCommand; +import com.hypixel.hytale.server.core.command.commands.utility.worldmap.WorldMapCommand; +import com.hypixel.hytale.server.core.command.commands.world.SpawnBlockCommand; +import com.hypixel.hytale.server.core.command.commands.world.chunk.ChunkCommand; +import com.hypixel.hytale.server.core.command.commands.world.entity.EntityCommand; +import com.hypixel.hytale.server.core.command.commands.world.worldgen.WorldGenCommand; +import com.hypixel.hytale.server.core.command.system.exceptions.CommandException; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Deque; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ForkJoinPool; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CommandManager implements CommandOwner { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static CommandManager instance; + private final Map commandRegistration = new Object2ObjectOpenHashMap<>(); + private final Map aliases = new Object2ObjectOpenHashMap<>(); + + public static CommandManager get() { + return instance; + } + + public CommandManager() { + instance = this; + } + + public void shutdown() { + this.aliases.clear(); + } + + @Nonnull + public Map getCommandRegistration() { + return this.commandRegistration; + } + + public void registerCommands() { + this.registerSystemCommand(new ChunkCommand()); + this.registerSystemCommand(new LogCommand()); + this.registerSystemCommand(new PIDCheckCommand()); + this.registerSystemCommand(new PingCommand()); + this.registerSystemCommand(new WorldGenCommand()); + this.registerSystemCommand(new HitDetectionCommand()); + this.registerSystemCommand(new PacketStatsCommand()); + this.registerSystemCommand(new AssetsCommand()); + this.registerSystemCommand(new PacksCommand()); + this.registerSystemCommand(new ServerCommand()); + this.registerSystemCommand(new StressTestCommand()); + this.registerSystemCommand(new HitboxCollisionCommand()); + this.registerSystemCommand(new DebugPlayerPositionCommand()); + this.registerSystemCommand(new MessageTranslationTestCommand()); + this.registerSystemCommand(new HudManagerTestCommand()); + this.registerSystemCommand(new RepulsionCommand()); + this.registerSystemCommand(new StopNetworkChunkSendingCommand()); + this.registerSystemCommand(new ShowBuilderToolsHudCommand()); + this.registerSystemCommand(new VersionCommand()); + this.registerSystemCommand(new ParticleCommand()); + this.registerSystemCommand(new TagPatternCommand()); + this.registerSystemCommand(new GameModeCommand()); + this.registerSystemCommand(new HideCommand()); + this.registerSystemCommand(new KillCommand()); + this.registerSystemCommand(new DamageCommand()); + this.registerSystemCommand(new SudoCommand()); + this.registerSystemCommand(new WhereAmICommand()); + this.registerSystemCommand(new WhoAmICommand()); + this.registerSystemCommand(new ReferCommand()); + this.registerSystemCommand(new ToggleBlockPlacementOverrideCommand()); + this.registerSystemCommand(new GiveCommand()); + this.registerSystemCommand(new InventoryCommand()); + this.registerSystemCommand(new ItemStateCommand()); + this.registerSystemCommand(new AuthCommand()); + this.registerSystemCommand(new KickCommand()); + this.registerSystemCommand(new MaxPlayersCommand()); + this.registerSystemCommand(new StopCommand()); + this.registerSystemCommand(new WhoCommand()); + this.registerSystemCommand(new BackupCommand()); + this.registerSystemCommand(new ConvertPrefabsCommand()); + this.registerSystemCommand(new HelpCommand()); + this.registerSystemCommand(new NotifyCommand()); + this.registerSystemCommand(new EventTitleCommand()); + this.registerSystemCommand(new ValidateCPBCommand()); + this.registerSystemCommand(new WorldMapCommand()); + this.registerSystemCommand(new SoundCommand()); + this.registerSystemCommand(new StashCommand()); + this.registerSystemCommand(new SpawnBlockCommand()); + this.registerSystemCommand(new EntityCommand()); + this.registerSystemCommand(new PlayerCommand()); + this.registerSystemCommand(new LightingCommand()); + this.registerSystemCommand(new SleepCommand()); + this.registerSystemCommand(new NetworkCommand()); + this.registerSystemCommand(new CommandsCommand()); + this.registerSystemCommand(new UpdateCommand()); + } + + public Map> createVirtualPermissionGroups() { + Map> permissionsByGroup = new Object2ObjectOpenHashMap<>(); + + for (AbstractCommand command : this.commandRegistration.values()) { + for (Entry> entry : command.getPermissionGroupsRecursive().entrySet()) { + Set permissionsForGroup = permissionsByGroup.computeIfAbsent(entry.getKey(), k -> new HashSet<>()); + permissionsForGroup.addAll(entry.getValue()); + } + } + + return permissionsByGroup; + } + + public void registerSystemCommand(@Nonnull AbstractCommand command) { + command.setOwner(this); + this.register(command); + } + + @Nullable + public CommandRegistration register(@Nonnull AbstractCommand command) { + String name = command.getName(); + if (name != null && !name.isEmpty()) { + this.commandRegistration.put(command.getName(), command); + + try { + command.completeRegistration(); + } catch (Exception var7) { + String errorMessage = var7.getMessage(); + if (var7 instanceof GeneralCommandException generalException) { + String messageText = generalException.getMessageText(); + if (messageText != null) { + errorMessage = messageText; + } + } + + HytaleLogger.getLogger() + .at(Level.SEVERE) + .withCause(var7) + .log("Failed to register command: %s%s", command.getName(), errorMessage != null ? " - " + errorMessage : ""); + return null; + } + + for (String alias : command.getAliases()) { + this.aliases.put(alias, name); + } + + return new CommandRegistration(command, () -> true, () -> { + AbstractCommand remove = this.commandRegistration.remove(name); + if (remove != null) { + for (String aliasx : remove.getAliases()) { + this.aliases.remove(aliasx); + } + } + }); + } else { + throw new IllegalArgumentException("Registered commands must define a name"); + } + } + + @Nonnull + public CompletableFuture handleCommand(@Nonnull PlayerRef playerRef, @Nonnull String command) { + Ref ref = playerRef.getReference(); + if (ref == null) { + return new CompletableFuture<>(); + } else { + Store store = ref.getStore(); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + return this.handleCommand(playerComponent, command); + } + } + + @Nonnull + public CompletableFuture handleCommand(@Nonnull CommandSender commandSender, @Nonnull String commandString) { + Objects.requireNonNull(commandSender, "Command sender must not be null!"); + Objects.requireNonNull(commandString, "Command must not be null!"); + CompletableFuture future = new CompletableFuture<>(); + ForkJoinPool.commonPool().execute(() -> { + Thread thread = Thread.currentThread(); + String oldName = thread.getName(); + thread.setName(oldName + " -- Running: " + commandString); + + try { + LOGGER.at(Level.FINE).log("%s sent command: %s", commandSender.getDisplayName(), commandString); + int endIndex = commandString.indexOf(32); + String commandName = (endIndex < 0 ? commandString : commandString.substring(0, endIndex)).toLowerCase(); + AbstractCommand command = this.commandRegistration.get(commandName); + if (command == null) { + String key = this.aliases.get(commandName); + if (key != null && this.commandRegistration.containsKey(key)) { + command = this.commandRegistration.get(key); + } + } + + if (command != null) { + this.runCommand(commandSender, commandString, command, future); + return; + } + + commandSender.sendMessage(Message.translation("server.modules.command.notFound").param("cmd", commandString)); + future.complete(null); + } finally { + thread.setName(oldName); + } + }); + return future; + } + + private void runCommand( + @Nonnull CommandSender commandSender, @Nonnull String commandInput, @Nonnull AbstractCommand abstractCommand, @Nonnull CompletableFuture future + ) { + try { + LOGGER.at(Level.INFO).log("%s executed command: %s", commandSender.getDisplayName(), commandInput); + ParseResult parseResult = new ParseResult(); + List tokens = Tokenizer.parseArguments(commandInput, parseResult); + if (parseResult.failed()) { + parseResult.sendMessages(commandSender); + future.complete(null); + return; + } + + ParserContext parserContext = ParserContext.of(tokens, parseResult); + if (parseResult.failed()) { + parseResult.sendMessages(commandSender); + future.complete(null); + return; + } + + CompletableFuture commandFuture = abstractCommand.acceptCall(commandSender, parserContext, parseResult); + if (parseResult.failed()) { + parseResult.sendMessages(commandSender); + future.complete(null); + return; + } + + if (commandFuture != null) { + commandFuture.whenComplete( + (aVoid, throwable) -> { + if (throwable != null) { + if (!CompletableFutureUtil.isCanceled(throwable) && !isInternalException(throwable)) { + LOGGER.at(Level.SEVERE) + .withCause(new SkipSentryException(throwable)) + .log("Failed to execute command %s for %s", commandInput, commandSender.getDisplayName()); + commandSender.sendMessage( + Message.translation("server.modules.command.error").param("cmd", commandInput).param("msg", throwable.getMessage()) + ); + } + + future.completeExceptionally(throwable); + } else { + future.complete(aVoid); + } + } + ); + } else { + future.complete(null); + } + } catch (Throwable var9) { + if (var9 instanceof CommandException commandException) { + commandException.sendTranslatedMessage(commandSender); + } else { + LOGGER.at(Level.SEVERE).withCause(var9).log("Failed to execute command %s for %s", commandInput, commandSender.getDisplayName()); + Message errorMsg = var9.getMessage() == null + ? Message.translation("server.modules.command.noProvidedExceptionMessage") + : Message.raw(var9.getMessage()); + commandSender.sendMessage(Message.translation("server.modules.command.error").param("cmd", commandInput).param("msg", errorMsg)); + } + + future.completeExceptionally(var9); + } + } + + private static boolean isInternalException(@Nonnull Throwable throwable) { + if (throwable instanceof CommandException) { + return true; + } else { + return throwable instanceof CompletionException && throwable.getCause() != null && throwable.getCause() != throwable + ? isInternalException(throwable.getCause()) + : false; + } + } + + @Nonnull + public CompletableFuture handleCommands(@Nonnull CommandSender sender, @Nonnull Deque commands) { + return this.handleCommands0(sender, commands); + } + + @Nonnull + private CompletableFuture handleCommands0(@Nonnull CommandSender sender, @Nonnull Deque commands) { + return commands.isEmpty() + ? CompletableFuture.completedFuture(null) + : this.handleCommand(sender, commands.poll()).thenCompose(aVoid -> this.handleCommands0(sender, commands)); + } + + @Nonnull + @Override + public String getName() { + return "HytaleServer"; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/CommandOwner.java b/src/com/hypixel/hytale/server/core/command/system/CommandOwner.java new file mode 100644 index 0000000..e7ae809 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/CommandOwner.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.server.core.command.system; + +public interface CommandOwner { + String getName(); +} diff --git a/src/com/hypixel/hytale/server/core/command/system/CommandRegistration.java b/src/com/hypixel/hytale/server/core/command/system/CommandRegistration.java new file mode 100644 index 0000000..26780b0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/CommandRegistration.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.server.core.command.system; + +import com.hypixel.hytale.registry.Registration; +import java.util.function.BooleanSupplier; +import javax.annotation.Nonnull; + +public class CommandRegistration extends Registration { + @Nonnull + private final AbstractCommand abstractCommand; + + public CommandRegistration(@Nonnull AbstractCommand command, @Nonnull BooleanSupplier isEnabled, @Nonnull Runnable unregister) { + super(isEnabled, unregister); + this.abstractCommand = command; + } + + public CommandRegistration(@Nonnull CommandRegistration registration, @Nonnull BooleanSupplier isEnabled, @Nonnull Runnable unregister) { + super(isEnabled, unregister); + this.abstractCommand = registration.abstractCommand; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/CommandRegistry.java b/src/com/hypixel/hytale/server/core/command/system/CommandRegistry.java new file mode 100644 index 0000000..7e5be85 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/CommandRegistry.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.core.command.system; + +import com.hypixel.hytale.function.consumer.BooleanConsumer; +import com.hypixel.hytale.registry.Registry; +import com.hypixel.hytale.server.core.plugin.PluginBase; +import java.util.List; +import java.util.function.BooleanSupplier; +import javax.annotation.Nonnull; + +public class CommandRegistry extends Registry { + private final PluginBase plugin; + + public CommandRegistry(@Nonnull List registrations, BooleanSupplier precondition, String preconditionMessage, PluginBase plugin) { + super(registrations, precondition, preconditionMessage, CommandRegistration::new); + this.plugin = plugin; + } + + public CommandRegistration registerCommand(@Nonnull AbstractCommand command) { + this.checkPrecondition(); + if (this.plugin != null) { + command.setOwner(this.plugin); + } + + return this.register(CommandManager.get().register(command)); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/CommandSender.java b/src/com/hypixel/hytale/server/core/command/system/CommandSender.java new file mode 100644 index 0000000..c86ed65 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/CommandSender.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.command.system; + +import com.hypixel.hytale.server.core.permissions.PermissionHolder; +import com.hypixel.hytale.server.core.receiver.IMessageReceiver; +import java.util.UUID; + +public interface CommandSender extends IMessageReceiver, PermissionHolder { + String getDisplayName(); + + UUID getUuid(); +} diff --git a/src/com/hypixel/hytale/server/core/command/system/CommandUtil.java b/src/com/hypixel/hytale/server/core/command/system/CommandUtil.java new file mode 100644 index 0000000..b475e13 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/CommandUtil.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.command.system; + +import com.hypixel.hytale.server.core.command.system.exceptions.NoPermissionException; +import com.hypixel.hytale.server.core.permissions.PermissionHolder; +import javax.annotation.Nonnull; + +public class CommandUtil { + public static final String CONFIRM_UNSAFE_COMMAND = "confirm"; + public static final String WORLD_OPTION = "world"; + public static final String ENTITY_OPTION = "entity"; + public static final String PLAYER_OPTION = "player"; + public static int RECOMMEND_COUNT = 5; + + public CommandUtil() { + } + + @Nonnull + public static String stripCommandName(@Nonnull String rawCommand) { + int indexOf = rawCommand.indexOf(32); + return indexOf < 0 ? rawCommand : rawCommand.substring(indexOf + 1); + } + + public static void requirePermission(@Nonnull PermissionHolder permissionHolder, @Nonnull String permission) { + if (!permissionHolder.hasPermission(permission)) { + throw new NoPermissionException(permission); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/CommandValidationResults.java b/src/com/hypixel/hytale/server/core/command/system/CommandValidationResults.java new file mode 100644 index 0000000..f497297 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/CommandValidationResults.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.server.core.command.system; + +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.server.core.Message; +import javax.annotation.Nonnull; + +public class CommandValidationResults extends ValidationResults { + public CommandValidationResults(@Nonnull ExtraInfo extraInfo) { + super(extraInfo); + } + + public void processResults(@Nonnull ParseResult parseResult) { + this._processValidationResults(); + if (this.validatorExceptions != null && !this.validatorExceptions.isEmpty()) { + StringBuilder sb = new StringBuilder(); + boolean failed = false; + + for (ValidationResults.ValidatorResultsHolder holder : this.validatorExceptions) { + for (ValidationResults.ValidationResult result : holder.results()) { + failed |= result.appendResult(sb); + } + } + + if (failed) { + parseResult.fail(Message.raw(sb.toString())); + } else { + this.validatorExceptions.clear(); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/MatchResult.java b/src/com/hypixel/hytale/server/core/command/system/MatchResult.java new file mode 100644 index 0000000..a7a99b2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/MatchResult.java @@ -0,0 +1,98 @@ +package com.hypixel.hytale.server.core.command.system; + +import com.hypixel.hytale.common.util.StringCompareUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MatchResult implements Comparable { + public static final MatchResult NONE = new MatchResult(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE); + public static final MatchResult EXACT = new MatchResult(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); + public static final int NAME = 0; + public static final int ALIAS = 1; + public static final int USAGE_ARG = 3; + public static final int DESCRIPTION = 4; + public static final int USAGE_DESCRIPTION = 5; + private final int term; + private final int depth; + private final int type; + private final int match; + + @Nonnull + public static MatchResult of(int termDepth, int depth, int type, @Nonnull String text, @Nonnull String search) { + return new MatchResult(termDepth, depth, type, StringCompareUtil.getLevenshteinDistance(text, search)); + } + + public MatchResult(int term, int depth, int type, int match) { + this.term = term; + this.depth = depth; + this.type = type; + this.match = match; + } + + public int getDepth() { + return this.depth; + } + + public int getType() { + return this.type; + } + + public int getMatch() { + return this.match; + } + + @Nonnull + public MatchResult min(@Nonnull MatchResult other) { + if (this.term < other.term) { + return this; + } else if (this.term > other.term) { + return other; + } else if (this.type < other.type) { + return this; + } else if (this.type > other.type) { + return other; + } else if (this.depth < other.depth) { + return this; + } else if (this.depth > other.depth) { + return other; + } else if (this.match < other.match) { + return this; + } else { + return this.match > other.match ? other : this; + } + } + + public int compareTo(@Nonnull MatchResult o) { + if (o.term != this.term) { + return this.term - o.term; + } else if (o.type != this.type) { + return this.type - o.type; + } else { + return o.depth != this.depth ? this.depth - o.depth : this.match - o.match; + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + MatchResult that = (MatchResult)o; + return this.depth != that.depth ? false : this.match == that.match; + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.depth; + return 31 * result + this.match; + } + + @Nonnull + @Override + public String toString() { + return "MatchResult{depth=" + this.depth + ", match=" + this.match + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/ParseResult.java b/src/com/hypixel/hytale/server/core/command/system/ParseResult.java new file mode 100644 index 0000000..693b4a4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/ParseResult.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.server.core.command.system; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ParseResult { + private boolean failed; + @Nullable + private List reasons; + private final boolean throwExceptionWhenFailed; + + public ParseResult() { + this(false); + } + + public ParseResult(boolean throwExceptionWhenFailed) { + this.throwExceptionWhenFailed = throwExceptionWhenFailed; + } + + public void fail(@Nonnull Message reason, @Nullable Message... otherMessages) { + this.failed = true; + if (this.reasons == null) { + this.reasons = new ObjectArrayList<>(); + } + + this.reasons.add(reason); + if (otherMessages != null) { + Collections.addAll(this.reasons, otherMessages); + } + + if (this.throwExceptionWhenFailed) { + StringBuilder builder = new StringBuilder(reason.getAnsiMessage()); + if (otherMessages != null) { + for (Message otherMessage : otherMessages) { + builder.append("\n").append(otherMessage.getAnsiMessage()); + } + } + + throw new GeneralCommandException(Message.raw(builder.toString())); + } + } + + public void fail(@Nonnull Message reason) { + this.fail(reason, (Message[])null); + } + + public boolean failed() { + return this.failed; + } + + public void sendMessages(@Nonnull CommandSender sender) { + if (this.reasons != null) { + for (Message reason : this.reasons) { + sender.sendMessage(reason); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/ParserContext.java b/src/com/hypixel/hytale/server/core/command/system/ParserContext.java new file mode 100644 index 0000000..33f4037 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/ParserContext.java @@ -0,0 +1,323 @@ +package com.hypixel.hytale.server.core.command.system; + +import com.hypixel.hytale.server.core.Message; +import it.unimi.dsi.fastutil.booleans.BooleanArrayList; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectSortedSet; +import java.util.HashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ParserContext { + private static final HashSet SPECIAL_TOKENS = new HashSet<>( + List.of(Tokenizer.MULTI_ARG_BEGIN, Tokenizer.MULTI_ARG_END, Tokenizer.MULTI_ARG_SEPARATOR) + ); + private static final int MAX_LIST_ITEMS = 10; + @Nonnull + private final String inputString; + @Nonnull + private final BooleanArrayList parameterForwardingMap; + @Nonnull + private final Int2ObjectMap preOptionalSingleValueTokens; + @Nonnull + private final Int2ObjectMap preOptionalListTokens; + @Nonnull + private final Object2ObjectLinkedOpenHashMap>> optionalArgs; + private String lastInsertedOptionalArgName; + private int numPreOptSingleValueTokensBeforeListTokens; + private int subCommandIndex; + private static final Pattern ARG_NAME_PATTERN = Pattern.compile("--(\\w*)"); + private static final Matcher ARG_NAME_MATCHER = ARG_NAME_PATTERN.matcher(""); + private static final Pattern ARG_NAME_AND_VALUE_PATTERN = Pattern.compile("--(\\w+)=\"*(.*)\"*"); + private static final Matcher ARG_NAME_AND_VALUE_MATCHER = ARG_NAME_AND_VALUE_PATTERN.matcher(""); + + public ParserContext(@Nonnull List tokens, @Nonnull ParseResult parseResult) { + this.inputString = String.join(" ", tokens); + this.parameterForwardingMap = new BooleanArrayList(); + this.preOptionalSingleValueTokens = new Int2ObjectOpenHashMap<>(); + this.preOptionalListTokens = new Int2ObjectOpenHashMap<>(); + this.optionalArgs = new Object2ObjectLinkedOpenHashMap<>(); + this.contextualizeTokens(tokens, parseResult); + } + + @Nonnull + public static ParserContext of(@Nonnull List tokens, @Nonnull ParseResult parseResult) { + return new ParserContext(tokens, parseResult); + } + + private void contextualizeTokens(@Nonnull List tokens, @Nonnull ParseResult parseResult) { + boolean beganParsingOptionals = false; + boolean inList = false; + boolean isSingleValueList = false; + boolean wasLastTokenASpecialValue = false; + boolean hasEnteredListBefore = false; + + for (int i = 0; i < tokens.size(); i++) { + String token = tokens.get(i); + if (inList) { + hasEnteredListBefore = true; + } + + if (SPECIAL_TOKENS.contains(token)) { + boolean isListEndingAndStartingNew = tokens.get(i - 1).equals(Tokenizer.MULTI_ARG_END) && !inList && token.equals(Tokenizer.MULTI_ARG_BEGIN); + if (wasLastTokenASpecialValue && !isListEndingAndStartingNew) { + StringBuilder stringBuilder = new StringBuilder(); + + for (int i1 = 0; i1 < tokens.size(); i1++) { + stringBuilder.append(tokens.get(i1)).append(" "); + if (i1 == i) { + stringBuilder.append(" <--- *HERE!* "); + } + } + + parseResult.fail(Message.translation("server.commands.parsing.error.cantDoublePlaceSpecialTokens"), Message.raw(stringBuilder.toString())); + return; + } + + wasLastTokenASpecialValue = true; + } else { + wasLastTokenASpecialValue = false; + } + + ARG_NAME_MATCHER.reset(token); + if (ARG_NAME_MATCHER.lookingAt()) { + beganParsingOptionals = true; + this.addNewOptionalArg(ARG_NAME_MATCHER.group(1)); + ARG_NAME_AND_VALUE_MATCHER.reset(token); + if (ARG_NAME_AND_VALUE_MATCHER.matches()) { + this.appendOptionalParameter(ARG_NAME_AND_VALUE_MATCHER.group(2), parseResult); + if (parseResult.failed()) { + return; + } + } + } else if (beganParsingOptionals) { + this.appendOptionalParameter(token, parseResult); + if (parseResult.failed()) { + return; + } + } else if (token.equals(Tokenizer.MULTI_ARG_BEGIN)) { + inList = true; + isSingleValueList = false; + this.parameterForwardingMap.add(true); + this.preOptionalListTokens.put(this.parameterForwardingMap.size() - 1, new ParserContext.PreOptionalListContext()); + } else if (token.equals(Tokenizer.MULTI_ARG_END)) { + inList = false; + } else if (inList) { + this.preOptionalListTokens.get(this.parameterForwardingMap.size() - 1).addToken(token, parseResult); + if (parseResult.failed()) { + return; + } + + if (isSingleValueList && !wasLastTokenASpecialValue && tokens.size() > i + 1 && !tokens.get(i + 1).equals(Tokenizer.MULTI_ARG_SEPARATOR)) { + inList = false; + } + } else if (tokens.size() > i + 1 && tokens.get(i + 1).equals(Tokenizer.MULTI_ARG_SEPARATOR)) { + inList = true; + isSingleValueList = true; + this.parameterForwardingMap.add(true); + this.preOptionalListTokens.put(this.parameterForwardingMap.size() - 1, new ParserContext.PreOptionalListContext().addToken(token, parseResult)); + if (parseResult.failed()) { + return; + } + } else { + if (!hasEnteredListBefore) { + this.numPreOptSingleValueTokensBeforeListTokens++; + } + + this.parameterForwardingMap.add(false); + this.preOptionalSingleValueTokens.put(this.parameterForwardingMap.size() - 1, token); + } + } + + if (inList && !isSingleValueList) { + parseResult.fail(Message.translation("server.commands.parsing.error.endCommandWithOpenList").param("listEndToken", Tokenizer.MULTI_ARG_END)); + } + } + + public void addNewOptionalArg(String name) { + name = name.toLowerCase(); + this.lastInsertedOptionalArgName = name; + this.optionalArgs.put(name, new ObjectArrayList<>()); + } + + public void appendOptionalParameter(@Nonnull String value, @Nonnull ParseResult parseResult) { + if (!this.optionalArgs.isEmpty() && this.lastInsertedOptionalArgName != null) { + List> args = this.optionalArgs.get(this.lastInsertedOptionalArgName); + if (!value.equals(Tokenizer.MULTI_ARG_BEGIN) && !value.equals(Tokenizer.MULTI_ARG_END)) { + if (value.equals(Tokenizer.MULTI_ARG_SEPARATOR)) { + args.add(new ObjectArrayList<>()); + } else if (args.isEmpty()) { + ObjectArrayList values = new ObjectArrayList<>(); + values.add(value); + args.add(values); + } else { + args.getLast().add(value); + } + } + } else { + parseResult.fail(Message.translation("server.commands.parsing.error.noOptionalParameterToAddValueTo")); + } + } + + @Nonnull + public String getInputString() { + return this.inputString; + } + + public boolean isListToken(int index) { + index += this.subCommandIndex; + return this.parameterForwardingMap.size() <= index ? false : this.parameterForwardingMap.getBoolean(index); + } + + public int getNumPreOptSingleValueTokensBeforeListTokens() { + return this.numPreOptSingleValueTokensBeforeListTokens - this.subCommandIndex; + } + + public int getNumPreOptionalTokens() { + int numPreOptionalTokens = 0; + numPreOptionalTokens += this.preOptionalSingleValueTokens.size(); + + for (ParserContext.PreOptionalListContext value : this.preOptionalListTokens.values()) { + numPreOptionalTokens += value.numTokensPerArgument; + } + + return numPreOptionalTokens - this.subCommandIndex; + } + + public String getPreOptionalSingleValueToken(int index) { + index += this.subCommandIndex; + return this.preOptionalSingleValueTokens.get(index); + } + + public ParserContext.PreOptionalListContext getPreOptionalListToken(int index) { + index += this.subCommandIndex; + return this.preOptionalListTokens.get(index); + } + + @Nullable + public String getFirstToken() { + if (this.parameterForwardingMap.size() <= this.subCommandIndex) { + return null; + } else if (this.parameterForwardingMap.getBoolean(this.subCommandIndex)) { + ParserContext.PreOptionalListContext preOptionalListContext = this.preOptionalListTokens.get(this.subCommandIndex); + return preOptionalListContext.tokens.isEmpty() ? null : preOptionalListContext.tokens.getFirst(); + } else { + return this.preOptionalSingleValueTokens.get(this.subCommandIndex); + } + } + + @Nonnull + public ObjectSortedSet>>> getOptionalArgs() { + return this.optionalArgs.entrySet(); + } + + public boolean isHelpSpecified() { + return this.optionalArgs.containsKey("help") || this.optionalArgs.containsKey("?"); + } + + public boolean isConfirmationSpecified() { + return this.optionalArgs.containsKey("confirm"); + } + + public void convertToSubCommand() { + this.subCommandIndex++; + } + + public static class PreOptionalListContext { + private final List tokens = new ObjectArrayList<>(); + private boolean hasReachedFirstMultiArgSeparator = false; + private int numTokensPerArgument = 0; + private int numTokensSinceLastSeparator = 0; + private int numberOfListItems = 0; + + public PreOptionalListContext() { + } + + @Nullable + public ParserContext.PreOptionalListContext addToken(@Nonnull String token, @Nonnull ParseResult parseResult) { + if (token.equals(Tokenizer.MULTI_ARG_SEPARATOR)) { + if (!this.hasReachedFirstMultiArgSeparator) { + this.hasReachedFirstMultiArgSeparator = true; + this.numTokensSinceLastSeparator = 0; + this.numberOfListItems++; + this.verifyNumberOfListItems(parseResult); + return this; + } else if (this.numTokensSinceLastSeparator != this.numTokensPerArgument) { + this.tokens.add(token); + parseResult.fail( + Message.translation("server.commands.parsing.error.allArgumentsInListNeedSameLength").param("error", this.getStringRepresentation(true)) + ); + return null; + } else { + this.numTokensSinceLastSeparator = 0; + this.numberOfListItems++; + this.verifyNumberOfListItems(parseResult); + return this; + } + } else { + this.numTokensSinceLastSeparator++; + if (this.numberOfListItems == 0) { + this.numTokensPerArgument++; + } + + if (this.hasReachedFirstMultiArgSeparator && this.numTokensSinceLastSeparator > this.numTokensPerArgument) { + this.tokens.add(token); + parseResult.fail( + Message.translation("server.commands.parsing.error.allArgumentsInListNeedSameLength").param("error", this.getStringRepresentation(true)) + ); + return null; + } else { + this.tokens.add(token); + return this; + } + } + } + + @Nonnull + private String getStringRepresentation(boolean asTooLongFailure) { + StringBuilder stringBuilder = new StringBuilder(Tokenizer.MULTI_ARG_BEGIN); + + for (int i = 0; i < this.tokens.size(); i++) { + if (i != 0 && i % this.numTokensPerArgument == 0 && i != this.tokens.size() - 1) { + stringBuilder.append(" ").append(Tokenizer.MULTI_ARG_SEPARATOR); + } + + stringBuilder.append(" ").append(this.tokens.get(i)); + } + + if (asTooLongFailure) { + stringBuilder.append("<-- *HERE* ... ]"); + } else { + stringBuilder.append(" ]"); + } + + return stringBuilder.toString(); + } + + public void verifyNumberOfListItems(@Nonnull ParseResult parseResult) { + if (this.numberOfListItems > 10) { + parseResult.fail(Message.translation("server.commands.parsing.error.tooManyListItems").param("amount", 10)); + } + } + + @Nonnull + public String[] getTokens() { + return this.tokens.toArray(String[]::new); + } + + public int getNumTokensPerArgument() { + return this.numTokensPerArgument; + } + + public int getNumberOfListItems() { + return this.numberOfListItems; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/Tokenizer.java b/src/com/hypixel/hytale/server/core/command/system/Tokenizer.java new file mode 100644 index 0000000..292cf39 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/Tokenizer.java @@ -0,0 +1,167 @@ +package com.hypixel.hytale.server.core.command.system; + +import com.hypixel.hytale.server.core.Message; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Tokenizer { + public static final char MULTI_ARG_SEPARATOR_CHAR = ','; + public static final char MULTI_ARG_BEGIN_CHAR = '['; + public static final char MULTI_ARG_END_CHAR = ']'; + public static final String MULTI_ARG_SEPARATOR = String.valueOf(','); + public static final String MULTI_ARG_BEGIN = String.valueOf('['); + public static final String MULTI_ARG_END = String.valueOf(']'); + private static final Message MESSAGE_COMMANDS_PARSING_ERROR_UNBALANCED_QUOTES = Message.translation("server.commands.parsing.error.unbalancedQuotes"); + + public Tokenizer() { + } + + @Nullable + public static List parseArguments(@Nonnull String input, @Nonnull ParseResult parseResult) { + List parsedTokens = new ObjectArrayList<>(); + String[] firstSplit = input.split(Pattern.quote(" "), 2); + parsedTokens.add(firstSplit[0]); + if (firstSplit.length == 1) { + return parsedTokens; + } else { + String argsStr = firstSplit[1]; + char quote = 0; + int tokenStart = 0; + boolean inList = false; + + for (int i = 0; i < argsStr.length(); i++) { + boolean extractToken; + char c = argsStr.charAt(i); + extractToken = false; + label88: + switch (c) { + case ' ': + if (quote == 0) { + if (tokenStart < i) { + parsedTokens.add(argsStr.substring(tokenStart, i)); + } + + tokenStart = i + 1; + } + break; + case '"': + if (quote == 0) { + quote = '"'; + } else if (quote == '"') { + quote = 0; + String extraction = argsStr.substring(tokenStart, i + 1); + if (!extraction.isEmpty()) { + parsedTokens.add(extraction); + } + + tokenStart = i + 1; + } + break; + case '\'': + if (quote == 0) { + quote = '\''; + } else if (quote == '\'') { + quote = 0; + String extraction = argsStr.substring(tokenStart, i + 1); + if (!extraction.isEmpty()) { + parsedTokens.add(extraction); + } + + tokenStart = i + 1; + } + break; + case ',': + if (quote == 0) { + String extraction = argsStr.substring(tokenStart, i); + if (!extraction.isEmpty()) { + parsedTokens.add(extraction); + } + + tokenStart = i; + extractToken = true; + } + break; + case '[': + if (quote == 0) { + if (inList) { + parseResult.fail(Message.translation("server.commands.parsing.error.cannotBeginListInsideList").param("index", i)); + return null; + } + + inList = true; + tokenStart = i; + extractToken = true; + } + break; + case '\\': + if (argsStr.length() <= i + 1) { + parseResult.fail(Message.translation("server.commands.parsing.error.invalidEscape").param("index", i + 1).param("input", input)); + return null; + } + + char nextCharacter = argsStr.charAt(i + 1); + switch (nextCharacter) { + case '"': + case '\'': + case ',': + case '[': + case '\\': + case ']': + argsStr = argsStr.substring(0, i) + argsStr.substring(i + 1); + i++; + break label88; + default: + parseResult.fail( + Message.translation("server.commands.parsing.error.invalidEscapeForSymbol") + .param("symbol", (int)nextCharacter) + .param("index", i + 1) + .param("input", input) + .param("command", input) + ); + return null; + } + case ']': + if (quote == 0) { + if (!inList) { + parseResult.fail(Message.translation("server.commands.parsing.error.cannotEndListWithoutStarting").param("index", i)); + return null; + } + + String extraction = argsStr.substring(tokenStart, i); + if (!extraction.isEmpty()) { + parsedTokens.add(extraction); + } + + tokenStart = i; + inList = false; + extractToken = true; + } + } + + if (extractToken) { + parsedTokens.add(argsStr.substring(tokenStart, i + 1)); + tokenStart = i + 1; + } + + if (tokenStart > argsStr.length()) { + tokenStart = argsStr.length(); + break; + } + } + + if (quote != 0) { + parseResult.fail(MESSAGE_COMMANDS_PARSING_ERROR_UNBALANCED_QUOTES); + return null; + } else { + if (tokenStart != argsStr.length()) { + parsedTokens.add(argsStr.substring(tokenStart)); + } + + return parsedTokens; + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/system/AbstractOptionalArg.java b/src/com/hypixel/hytale/server/core/command/system/arguments/system/AbstractOptionalArg.java new file mode 100644 index 0000000..602f8e7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/system/AbstractOptionalArg.java @@ -0,0 +1,224 @@ +package com.hypixel.hytale.server.core.command.system.arguments.system; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgumentType; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class AbstractOptionalArg, DataType> extends Argument { + @Nonnull + private final Set aliases = new HashSet<>(); + @Nullable + private String permission; + private Set> requiredIf; + private Set> requiredIfAbsent; + private Set> availableOnlyIfAll; + private Set> availableOnlyIfAllAbsent; + + AbstractOptionalArg( + @Nonnull AbstractCommand commandRegisteredTo, @Nonnull String name, @Nonnull String description, @Nonnull ArgumentType argumentType + ) { + super(commandRegisteredTo, name, description, argumentType); + } + + public final Arg addAliases(@Nonnull String... newAliases) { + if (this.getCommandRegisteredTo().hasBeenRegistered()) { + throw new IllegalStateException("Cannot change aliases for an argument after a command has already been registered"); + } else { + for (String newAlias : newAliases) { + this.aliases.add(newAlias.toLowerCase()); + } + + return this.getThis(); + } + } + + public Arg requiredIf(@Nonnull AbstractOptionalArg dependent, @Nullable AbstractOptionalArg... otherDependents) { + if (this.requiredIf == null) { + this.requiredIf = new HashSet<>(); + } + + if (!this.addDependencyArg(this.requiredIf, this.requiredIfAbsent, dependent, otherDependents)) { + throw new IllegalStateException("Cannot have one argument in both requiredIf and requiredIfAbsent. Argument: " + dependent.getName()); + } else { + return this.getThis(); + } + } + + public Arg requiredIf(@Nonnull AbstractOptionalArg dependent) { + return this.requiredIf(dependent, (AbstractOptionalArg[])null); + } + + public Arg requiredIfAbsent(@Nonnull AbstractOptionalArg dependent, @Nullable AbstractOptionalArg... otherDependents) { + if (this.requiredIfAbsent == null) { + this.requiredIfAbsent = new HashSet<>(); + } + + if (!this.addDependencyArg(this.requiredIfAbsent, this.requiredIf, dependent, otherDependents)) { + throw new IllegalStateException("Cannot have one argument in both requiredIf and requiredIfAbsent. Argument: " + dependent.getName()); + } else { + return this.getThis(); + } + } + + public Arg requiredIfAbsent(@Nonnull AbstractOptionalArg dependent) { + return this.requiredIfAbsent(dependent, (AbstractOptionalArg[])null); + } + + public Arg availableOnlyIfAll(@Nonnull AbstractOptionalArg dependent, @Nullable AbstractOptionalArg... otherDependents) { + if (this.availableOnlyIfAll == null) { + this.availableOnlyIfAll = new HashSet<>(); + } + + if (!this.addDependencyArg(this.availableOnlyIfAll, this.availableOnlyIfAllAbsent, dependent, otherDependents)) { + throw new IllegalStateException("Cannot have one argument in both availableIf and availableIfAbsent. Argument: " + dependent.getName()); + } else { + return this.getThis(); + } + } + + public Arg availableOnlyIfAll(@Nonnull AbstractOptionalArg dependent) { + return this.availableOnlyIfAll(dependent, (AbstractOptionalArg[])null); + } + + public Arg availableOnlyIfAllAbsent(@Nonnull AbstractOptionalArg dependent, @Nullable AbstractOptionalArg... otherDependents) { + if (this.availableOnlyIfAllAbsent == null) { + this.availableOnlyIfAllAbsent = new HashSet<>(); + } + + if (!this.addDependencyArg(this.availableOnlyIfAllAbsent, this.availableOnlyIfAll, dependent, otherDependents)) { + throw new IllegalStateException("Cannot have one argument in both availableIf and availableIfAbsent. Argument: " + dependent.getName()); + } else { + return this.getThis(); + } + } + + public Arg availableOnlyIfAllAbsent(@Nonnull AbstractOptionalArg dependent) { + return this.availableOnlyIfAllAbsent(dependent, (AbstractOptionalArg[])null); + } + + private boolean addDependencyArg( + @Nonnull Set> set, + @Nullable Set> oppositeSet, + AbstractOptionalArg dependent, + @Nullable AbstractOptionalArg... otherDependents + ) { + if (this.getCommandRegisteredTo().hasBeenRegistered()) { + throw new IllegalStateException("Cannot change argument dependencies after command has completed registration"); + } else if (oppositeSet != null && oppositeSet.contains(dependent)) { + return false; + } else { + set.add(dependent); + if (otherDependents != null) { + if (oppositeSet != null) { + for (AbstractOptionalArg otherDependent : otherDependents) { + if (oppositeSet.contains(otherDependent)) { + return false; + } + } + } + + Collections.addAll(this.requiredIfAbsent, otherDependents); + } + + return true; + } + } + + public boolean verifyArgumentDependencies(@Nonnull CommandContext context, @Nonnull ParseResult parseResult) { + boolean provided = context.provided(this); + if (!provided) { + if (this.requiredIf != null) { + for (AbstractOptionalArg arg : this.requiredIf) { + if (arg.provided(context)) { + parseResult.fail( + Message.translation("server.commands.parsing.error.optionalArgRequiredIf") + .param("required", this.getName()) + .param("requirer", arg.getName()) + ); + return false; + } + } + } + + if (this.requiredIfAbsent != null) { + for (AbstractOptionalArg argx : this.requiredIfAbsent) { + if (!argx.provided(context)) { + parseResult.fail( + Message.translation("server.commands.parsing.error.optionalArgRequiredIf") + .param("required", this.getName()) + .param("requirer", argx.getName()) + ); + return false; + } + } + } + + return true; + } else { + if (this.availableOnlyIfAll != null) { + for (AbstractOptionalArg argxx : this.availableOnlyIfAll) { + if (!argxx.provided(context)) { + parseResult.fail( + Message.translation("server.commands.parsing.error.optionalArgAvailableIf") + .param("available", this.getName()) + .param("required", this.availableOnlyIfAll.stream().map(Argument::getName).collect(Collectors.joining(", "))) + ); + return false; + } + } + } + + if (this.availableOnlyIfAllAbsent != null) { + for (AbstractOptionalArg argxxx : this.availableOnlyIfAllAbsent) { + if (argxxx.provided(context)) { + parseResult.fail( + Message.translation("server.commands.parsing.error.optionalArgAvailableIfAbsent") + .param("available", this.getName()) + .param("required", this.availableOnlyIfAllAbsent.stream().map(Argument::getName).collect(Collectors.joining(", "))) + ); + return false; + } + } + } + + return true; + } + } + + @Nonnull + public Arg setPermission(@Nonnull String permission) { + if (this.getCommandRegisteredTo().hasBeenRegistered()) { + throw new IllegalStateException("Cannot change permissions after a command has already been registered"); + } else { + this.permission = permission; + return this.getThis(); + } + } + + @Nonnull + public Set getAliases() { + return Collections.unmodifiableSet(this.aliases); + } + + @Nullable + public String getPermission() { + return this.permission; + } + + public boolean hasPermission(@Nonnull CommandSender sender) { + return this.permission == null || sender.hasPermission(this.permission); + } + + public interface DefaultValueArgument { + DataType getDefaultValue(); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/system/ArgWrapper.java b/src/com/hypixel/hytale/server/core/command/system/arguments/system/ArgWrapper.java new file mode 100644 index 0000000..0535e1c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/system/ArgWrapper.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.server.core.command.system.arguments.system; + +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgumentType; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public record ArgWrapper, BasicType>( + @Nonnull ArgumentType argumentType, @Nonnull Function, W> wrappedArgProviderFunction +) { + public W wrapArg(@Nonnull Argument argument) { + return this.wrappedArgProviderFunction.apply(argument); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/system/Argument.java b/src/com/hypixel/hytale/server/core/command/system/arguments/system/Argument.java new file mode 100644 index 0000000..31d9b22 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/system/Argument.java @@ -0,0 +1,133 @@ +package com.hypixel.hytale.server.core.command.system.arguments.system; + +import com.hypixel.hytale.codec.EmptyExtraInfo; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.CommandValidationResults; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgumentType; +import com.hypixel.hytale.server.core.command.system.suggestion.SuggestionProvider; +import com.hypixel.hytale.server.core.command.system.suggestion.SuggestionResult; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class Argument, DataType> { + @Nonnull + private final String name; + @Nullable + private final String description; + private final ArgumentType argumentType; + @Nullable + private SuggestionProvider suggestionProvider; + @Nullable + private List> validators; + @Nonnull + private final AbstractCommand commandRegisteredTo; + + Argument(@Nonnull AbstractCommand commandRegisteredTo, @Nonnull String name, @Nullable String description, @Nonnull ArgumentType argumentType) { + this.commandRegisteredTo = commandRegisteredTo; + this.name = name; + this.description = description; + this.argumentType = argumentType; + } + + public Arg addValidator(@Nonnull Validator validator) { + if (this.commandRegisteredTo.hasBeenRegistered()) { + throw new IllegalStateException("Cannot add validators after command has already completed registration"); + } else { + if (this.validators == null) { + this.validators = new ObjectArrayList<>(); + } + + this.validators.add(validator); + return this.getThis(); + } + } + + public void validate(@Nonnull DataType data, @Nonnull ParseResult parseResult) { + if (this.validators != null) { + CommandValidationResults results = new CommandValidationResults(EmptyExtraInfo.EMPTY); + + for (Validator validator : this.validators) { + validator.accept(data, (ValidationResults)results); + } + + results.processResults(parseResult); + } + } + + public boolean provided(@Nonnull CommandContext context) { + return context.provided(this); + } + + public DataType get(@Nonnull CommandContext context) { + return context.get(this); + } + + @Nonnull + protected abstract Arg getThis(); + + @Nullable + public DataType getProcessed(@Nonnull CommandContext context) { + return this.argumentType.processedGet(context.sender(), context, this); + } + + public Arg suggest(@Nonnull SuggestionProvider suggestionProvider) { + if (this.commandRegisteredTo.hasBeenRegistered()) { + throw new IllegalStateException("Cannot add a SuggestionProvider after command has already completed registration"); + } else { + this.suggestionProvider = suggestionProvider; + return this.getThis(); + } + } + + @Nonnull + public List getSuggestions(@Nonnull CommandSender sender, @Nonnull String[] textAlreadyEntered) { + SuggestionResult suggestionResult = new SuggestionResult(); + String textAlreadyEnteredAsSingleString = String.join(" ", textAlreadyEntered); + if (this.suggestionProvider != null) { + this.suggestionProvider.suggest(sender, textAlreadyEnteredAsSingleString, textAlreadyEntered.length, suggestionResult); + } + + this.argumentType.suggest(sender, textAlreadyEnteredAsSingleString, textAlreadyEntered.length, suggestionResult); + return suggestionResult.getSuggestions(); + } + + @Nonnull + public abstract Message getUsageMessage(); + + @Nonnull + public abstract Message getUsageOneLiner(); + + @Nonnull + public AbstractCommand getCommandRegisteredTo() { + return this.commandRegisteredTo; + } + + @Nonnull + public String getName() { + return this.name; + } + + @Nonnull + public ArgumentType getArgumentType() { + return this.argumentType; + } + + @Nullable + public String getDescription() { + return this.description; + } + + @Nonnull + @Override + public String toString() { + return "Argument{name='" + this.name + "', description='" + this.description + "', argumentType=" + this.argumentType + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/system/DefaultArg.java b/src/com/hypixel/hytale/server/core/command/system/arguments/system/DefaultArg.java new file mode 100644 index 0000000..025a226 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/system/DefaultArg.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.server.core.command.system.arguments.system; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgumentType; +import javax.annotation.Nonnull; + +public class DefaultArg extends AbstractOptionalArg, DataType> implements AbstractOptionalArg.DefaultValueArgument { + @Nonnull + private final DataType defaultValue; + @Nonnull + private final String defaultValueDescription; + + public DefaultArg( + @Nonnull AbstractCommand commandRegisteredTo, + @Nonnull String name, + @Nonnull String description, + @Nonnull ArgumentType argumentType, + @Nonnull DataType defaultValue, + @Nonnull String defaultValueDescription + ) { + super(commandRegisteredTo, name, description, argumentType); + this.defaultValue = defaultValue; + this.defaultValueDescription = defaultValueDescription; + } + + @Nonnull + protected DefaultArg getThis() { + return this; + } + + @Override + public final DataType getDefaultValue() { + return this.defaultValue; + } + + public void validateDefaultValue(@Nonnull ParseResult parseResult) { + this.validate(this.getDefaultValue(), parseResult); + } + + @Nonnull + @Override + public Message getUsageMessage() { + return Message.raw("--") + .insert(this.getName()) + .insert(" (") + .insert(this.getArgumentType().getName()) + .insert(":default=") + .insert(Message.translation(this.getDefaultValueDescription())) + .insert(") -> \"") + .insert(Message.translation(this.getDescription())) + .insert("\""); + } + + @Nonnull + @Override + public Message getUsageOneLiner() { + String defaultValueStr = this.defaultValue == null ? "?" : this.defaultValue.toString(); + return Message.raw("[--").insert(this.getName()).insert("=").insert(defaultValueStr).insert("]"); + } + + @Nonnull + public String getDefaultValueDescription() { + return this.defaultValueDescription; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/system/FlagArg.java b/src/com/hypixel/hytale/server/core/command/system/arguments/system/FlagArg.java new file mode 100644 index 0000000..4e649e4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/system/FlagArg.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.server.core.command.system.arguments.system; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import com.hypixel.hytale.server.core.command.system.arguments.types.BooleanFlagArgumentType; +import javax.annotation.Nonnull; + +public class FlagArg extends AbstractOptionalArg implements AbstractOptionalArg.DefaultValueArgument { + @Nonnull + private static final BooleanFlagArgumentType BOOLEAN_FLAG_ARGUMENT_TYPE = new BooleanFlagArgumentType(); + + public FlagArg(@Nonnull AbstractCommand commandRegisteredTo, @Nonnull String name, @Nonnull String description) { + super(commandRegisteredTo, name, description, BOOLEAN_FLAG_ARGUMENT_TYPE); + } + + @Nonnull + protected FlagArg getThis() { + return this; + } + + @Nonnull + public Boolean getDefaultValue() { + return Boolean.FALSE; + } + + @Nonnull + @Override + public Message getUsageMessage() { + return Message.raw("--").insert(this.getName()).insert(" -> \"").insert(Message.translation(this.getDescription())).insert("\""); + } + + @Nonnull + @Override + public Message getUsageOneLiner() { + return Message.raw("[--").insert(this.getName()).insert("]"); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/system/OptionalArg.java b/src/com/hypixel/hytale/server/core/command/system/arguments/system/OptionalArg.java new file mode 100644 index 0000000..b20681c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/system/OptionalArg.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.command.system.arguments.system; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgumentType; +import javax.annotation.Nonnull; + +public class OptionalArg extends AbstractOptionalArg, DataType> { + public OptionalArg( + @Nonnull AbstractCommand commandRegisteredTo, @Nonnull String name, @Nonnull String description, @Nonnull ArgumentType argumentType + ) { + super(commandRegisteredTo, name, description, argumentType); + if (argumentType.getNumberOfParameters() < 1) { + throw new IllegalArgumentException("Cannot create an Optional Argument with 0 parameters. If you want to have 0 parameters, use Flag Arguments"); + } + } + + @Nonnull + protected OptionalArg getThis() { + return this; + } + + @Nonnull + @Override + public Message getUsageMessage() { + return Message.raw("--") + .insert(this.getName()) + .insert(" ") + .insert(this.getArgumentType().getName()) + .insert(" -> \"") + .insert(Message.translation(this.getDescription())) + .insert("\""); + } + + @Nonnull + @Override + public Message getUsageOneLiner() { + return Message.raw("[--").insert(this.getName()).insert("=?]"); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/system/RequiredArg.java b/src/com/hypixel/hytale/server/core/command/system/arguments/system/RequiredArg.java new file mode 100644 index 0000000..9c2ab82 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/system/RequiredArg.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.core.command.system.arguments.system; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgumentType; +import javax.annotation.Nonnull; + +public class RequiredArg extends Argument, DataType> { + public RequiredArg( + @Nonnull AbstractCommand commandRegisteredTo, @Nonnull String name, @Nonnull String description, @Nonnull ArgumentType argumentType + ) { + super(commandRegisteredTo, name, description, argumentType); + if (argumentType.getNumberOfParameters() < 1) { + throw new IllegalArgumentException("Cannot create a Required Argument with 0 parameters."); + } + } + + @Nonnull + public Message getUsageMessageWithoutDescription() { + return Message.raw("<").insert(this.getName()).insert(":").insert(this.getArgumentType().getName()).insert(">"); + } + + @Nonnull + @Override + public Message getUsageMessage() { + return Message.raw(this.getName()) + .insert(" (") + .insert(this.getArgumentType().getName()) + .insert(") -> \"") + .insert(Message.translation(this.getDescription())) + .insert("\""); + } + + @Nonnull + @Override + public Message getUsageOneLiner() { + return Message.raw("<").insert(this.getName()).insert(">"); + } + + @Nonnull + protected RequiredArg getThis() { + return this; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/system/WrappedArg.java b/src/com/hypixel/hytale/server/core/command/system/arguments/system/WrappedArg.java new file mode 100644 index 0000000..7282b2f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/system/WrappedArg.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.core.command.system.arguments.system; + +import com.hypixel.hytale.server.core.command.system.CommandContext; +import javax.annotation.Nonnull; + +public abstract class WrappedArg { + @Nonnull + protected final Argument arg; + + public WrappedArg(@Nonnull Argument arg) { + this.arg = arg; + } + + public boolean provided(@Nonnull CommandContext context) { + return this.arg.provided(context); + } + + @Nonnull + public String getName() { + return this.arg.getName(); + } + + @Nonnull + public String getDescription() { + return this.arg.getDescription(); + } + + @Nonnull + public > D addAliases(@Nonnull String... aliases) { + if (this.arg instanceof AbstractOptionalArg abstractOptionalArg) { + abstractOptionalArg.addAliases(aliases); + return (D)this; + } else { + throw new UnsupportedOperationException( + "You are trying to add aliases to a wrapped arg that is wrapping a RequiredArgument. RequiredArguments do not accept aliases" + ); + } + } + + @Nonnull + public Argument getArg() { + return this.arg; + } + + protected BasicType get(@Nonnull CommandContext context) { + return this.arg.get(context); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/AbstractAssetArgumentType.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/AbstractAssetArgumentType.java new file mode 100644 index 0000000..557b4b3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/AbstractAssetArgumentType.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import java.awt.Color; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class AbstractAssetArgumentType, M extends AssetMap, AssetKeyType> + extends SingleArgumentType { + @Nonnull + private final Class dataTypeClass; + + public AbstractAssetArgumentType(@Nonnull String name, @Nonnull Class type, @Nonnull String argumentUsage) { + super(name, argumentUsage); + this.dataTypeClass = type; + } + + @Nullable + public DataType parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + M assetMap = this.getAssetMap(); + AssetKeyType assetKey = this.getAssetKey(input); + if (assetKey == null) { + parseResult.fail( + Message.translation("server.commands.notfound").param("type", this.dataTypeClass.getSimpleName()).param("id", input).color(Color.RED), + Message.translation("server.general.failed.didYouMean") + .param("choices", StringUtil.sortByFuzzyDistance(input, assetMap.getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT).toString()) + ); + return null; + } else { + DataType asset = assetMap.getAsset(assetKey); + if (asset == null) { + parseResult.fail( + Message.translation("server.commands.notfound").param("type", this.dataTypeClass.getSimpleName()).param("id", input).color(Color.RED), + Message.translation("server.general.failed.didYouMean") + .param("choices", StringUtil.sortByFuzzyDistance(input, assetMap.getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT).toString()) + ); + return null; + } else { + return asset; + } + } + } + + @Nullable + public abstract AssetKeyType getAssetKey(@Nonnull String var1); + + @Nonnull + public M getAssetMap() { + return (M)AssetRegistry.getAssetStore(this.dataTypeClass).getAssetMap(); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/ArgTypes.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/ArgTypes.java new file mode 100644 index 0000000..067886e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/ArgTypes.java @@ -0,0 +1,772 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.NameMatching; +import com.hypixel.hytale.server.core.asset.type.ambiencefx.config.AmbienceFX; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import com.hypixel.hytale.server.core.command.system.arguments.system.ArgWrapper; +import com.hypixel.hytale.server.core.command.system.arguments.system.Argument; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.command.system.suggestion.SuggestionResult; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.hitboxcollision.HitboxCollisionConfig; +import com.hypixel.hytale.server.core.modules.entity.repulsion.RepulsionConfig; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockMask; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import it.unimi.dsi.fastutil.Pair; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; +import java.util.function.BiFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class ArgTypes { + public static final SingleArgumentType BOOLEAN = new SingleArgumentType( + "server.commands.parsing.argtype.boolean.name", "server.commands.parsing.argtype.boolean.usage", "true", "false" + ) { + private static final String TRUE_STRING = "true"; + private static final String FALSE_STRING = "false"; + + @Nonnull + public Boolean parse(String input, ParseResult parseResult) { + return Boolean.parseBoolean(input); + } + + @Override + public void suggest(@Nonnull CommandSender sender, @Nonnull String textAlreadyEntered, int numParametersTyped, @Nonnull SuggestionResult result) { + textAlreadyEntered = textAlreadyEntered.toLowerCase(); + if ("false".startsWith(textAlreadyEntered)) { + result.suggest("false"); + result.suggest("true"); + } else { + result.suggest("true"); + result.suggest("false"); + } + } + }; + public static final SingleArgumentType INTEGER = new SingleArgumentType( + "server.commands.parsing.argtype.integer.name", "server.commands.parsing.argtype.integer.usage", "-27432", "-1", "0", "1", "56346" + ) { + @Nullable + public Integer parse(@Nonnull String input, ParseResult parseResult) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException var4) { + parseResult.fail(Message.translation("server.commands.parsing.argtype.integer.fail").param("input", input)); + return null; + } + } + }; + public static final SingleArgumentType STRING = new SingleArgumentType( + "server.commands.parsing.argtype.string.name", + "server.commands.parsing.argtype.string.usage", + "\"Hytale is really cool!\"", + "\"Numbers work 2!\"", + "\"If you can type it...\"" + ) { + public String parse(String input, ParseResult parseResult) { + return input; + } + }; + public static final SingleArgumentType FLOAT = new SingleArgumentType( + "server.commands.parsing.argtype.float.name", "server.commands.parsing.argtype.float.usage", "3.14159", "-2.5", "7" + ) { + @Nullable + public Float parse(@Nonnull String input, ParseResult parseResult) { + try { + return Float.parseFloat(input); + } catch (NumberFormatException var4) { + parseResult.fail(Message.translation("server.commands.parsing.argtype.float.fail").param("input", input)); + return null; + } + } + }; + public static final SingleArgumentType DOUBLE = new SingleArgumentType( + "server.commands.parsing.argtype.double.name", "server.commands.parsing.argtype.double.usage", "-3.14", "0.0", "3.141596" + ) { + @Nullable + public Double parse(@Nonnull String input, ParseResult parseResult) { + try { + return Double.parseDouble(input); + } catch (NumberFormatException var4) { + parseResult.fail(Message.translation("server.commands.parsing.argtype.double.fail").param("input", input)); + return null; + } + } + }; + public static final SingleArgumentType UUID = new SingleArgumentType( + "server.commands.parsing.argtype.uuid.name", "server.commands.parsing.argtype.uuid.usage", java.util.UUID.randomUUID().toString() + ) { + @Nullable + public UUID parse(@Nonnull String input, ParseResult parseResult) { + try { + return java.util.UUID.fromString(input); + } catch (IllegalArgumentException var4) { + parseResult.fail(Message.translation("server.commands.parsing.argtype.uuid.fail").param("input", input)); + return null; + } + } + }; + public static final SingleArgumentType PLAYER_UUID = new SingleArgumentType( + "server.commands.parsing.argtype.playerUuid.name", + "server.commands.parsing.argtype.playerUuid.usage", + java.util.UUID.randomUUID().toString(), + "john_doe", + "user123" + ) { + @Nullable + public UUID parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + try { + return java.util.UUID.fromString(input); + } catch (IllegalArgumentException var7) { + for (World world : Universe.get().getWorlds().values()) { + Collection playerRefs = world.getPlayerRefs(); + PlayerRef playerRef = NameMatching.DEFAULT.find(playerRefs, input, PlayerRef::getUsername); + if (playerRef != null) { + return playerRef.getUuid(); + } + } + + parseResult.fail(Message.translation("server.commands.parsing.argtype.playerUuid.fail").param("input", input)); + return null; + } + } + }; + public static final SingleArgumentType RELATIVE_DOUBLE_COORD = new SingleArgumentType( + "server.commands.parsing.argtype.doubleCoordinate.name", "server.commands.parsing.argtype.doubleCoordinate.usage", "5.0", "~-2.3", "0.0" + ) { + @Nullable + public Coord parse(@Nonnull String input, ParseResult parseResult) { + try { + return Coord.parse(input); + } catch (NumberFormatException var4) { + parseResult.fail(Message.translation("server.commands.parsing.argtype.doubleCoordinate.fail").param("input", input)); + return null; + } + } + }; + public static final SingleArgumentType RELATIVE_INT_COORD = new SingleArgumentType( + "server.commands.parsing.argtype.integerCoordinate.name", "server.commands.parsing.argtype.integerCoordinate.usage", "5", "~-2", "0" + ) { + @Nullable + public IntCoord parse(@Nonnull String input, ParseResult parseResult) { + try { + return IntCoord.parse(input); + } catch (NumberFormatException var4) { + parseResult.fail(Message.translation("server.commands.parsing.argtype.integerCoordinate.fail").param("input", input)); + return null; + } + } + }; + public static final SingleArgumentType RELATIVE_INTEGER = new SingleArgumentType( + "Relative Integer", "A tilde to mark an integer as relative to a base", "5", "~-2", "0" + ) { + @Nullable + public RelativeInteger parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + return RelativeInteger.parse(input, parseResult); + } + }; + public static final SingleArgumentType RELATIVE_FLOAT = new SingleArgumentType( + "server.commands.parsing.argtype.relativeFloat.name", "server.commands.parsing.argtype.relativeFloat.usage", "90.0", "~-45.5", "~" + ) { + @Nullable + public RelativeFloat parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + return RelativeFloat.parse(input, parseResult); + } + }; + public static final SingleArgumentType PLAYER_REF = new SingleArgumentType( + "server.commands.parsing.argtype.player.name", "server.commands.parsing.argtype.player.usage", "john_doe", "user123" + ) { + @Nullable + public PlayerRef parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + PlayerRef playerRef = null; + + for (World world : Universe.get().getWorlds().values()) { + Collection playerRefs = world.getPlayerRefs(); + playerRef = NameMatching.DEFAULT.find(playerRefs, input, PlayerRef::getUsername); + if (playerRef != null) { + break; + } + } + + if (playerRef == null) { + parseResult.fail(Message.translation("server.commands.errors.noSuchPlayer").param("username", input)); + return null; + } else { + return playerRef; + } + } + + @Nonnull + public PlayerRef processedGet(CommandSender sender, @Nonnull CommandContext context, Argument argument) { + PlayerRef playerRef = context.get(argument); + if (playerRef != null) { + return playerRef; + } else if (sender instanceof Player player) { + return player.getPlayerRef(); + } else { + throw new GeneralCommandException(Message.translation("server.commands.errors.playerOrArg").param("option", "player")); + } + } + }; + public static final SingleArgumentType WORLD = new SingleArgumentType( + "server.commands.parsing.argtype.world.name", "server.commands.parsing.argtype.world.usage", "default" + ) { + @Nullable + public World parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + World world = Universe.get().getWorld(input); + if (world == null) { + parseResult.fail(Message.translation("server.commands.errors.noSuchWorld").param("name", input)); + return null; + } else { + return world; + } + } + + @Nullable + public World processedGet(CommandSender sender, @Nonnull CommandContext context, @Nonnull Argument argument) { + World world = argument.get(context); + if (world != null) { + return world; + } else if (sender instanceof Player) { + return ((Player)sender).getWorld(); + } else { + Universe universe = Universe.get(); + if (universe.getWorlds().size() == 1) { + Iterator iterator = universe.getWorlds().values().iterator(); + if (iterator.hasNext()) { + return iterator.next(); + } + } + + throw new GeneralCommandException(Message.translation("server.commands.errors.playerOrArg").param("option", "world")); + } + } + }; + public static final SingleArgumentType MODEL_ASSET = new AssetArgumentType( + "server.commands.parsing.argtype.asset.model.name", ModelAsset.class, "server.commands.parsing.argtype.asset.model.usage" + ); + public static final SingleArgumentType WEATHER_ASSET = new AssetArgumentType( + "server.commands.parsing.argtype.asset.weather.name", Weather.class, "server.commands.parsing.argtype.asset.weather.usage" + ); + public static final SingleArgumentType INTERACTION_ASSET = new AssetArgumentType( + "server.commands.parsing.argtype.asset.interaction.name", Interaction.class, "server.commands.parsing.argtype.asset.interaction.usage" + ); + public static final SingleArgumentType ROOT_INTERACTION_ASSET = new AssetArgumentType( + "server.commands.parsing.argtype.asset.rootinteraction.name", RootInteraction.class, "server.commands.parsing.argtype.asset.interaction.usage" + ); + public static final SingleArgumentType EFFECT_ASSET = new AssetArgumentType( + "server.commands.parsing.argtype.asset.effect.name", EntityEffect.class, "server.commands.parsing.argtype.asset.effect.usage" + ); + public static final SingleArgumentType ENVIRONMENT_ASSET = new AssetArgumentType( + "server.commands.parsing.argtype.asset.environment.name", Environment.class, "server.commands.parsing.argtype.asset.environment.usage" + ); + public static final SingleArgumentType ITEM_ASSET = new AssetArgumentType( + "server.commands.parsing.argtype.asset.item.name", Item.class, "server.commands.parsing.argtype.asset.item.usage" + ); + public static final SingleArgumentType BLOCK_TYPE_ASSET = new AssetArgumentType( + "server.commands.parsing.argtype.asset.blocktype.name", BlockType.class, "server.commands.parsing.argtype.asset.blocktype.usage" + ); + public static final SingleArgumentType PARTICLE_SYSTEM = new AssetArgumentType( + "server.commands.parsing.argtype.asset.particlesystem.name", ParticleSystem.class, "server.commands.parsing.argtype.asset.particlesystem.usage" + ); + public static final SingleArgumentType HITBOX_COLLISION_CONFIG = new AssetArgumentType( + "server.commands.parsing.argtype.asset.hitboxcollisionconfig.name", + HitboxCollisionConfig.class, + "server.commands.parsing.argtype.asset.hitboxcollisionconfig.usage" + ); + public static final SingleArgumentType REPULSION_CONFIG = new AssetArgumentType( + "server.commands.parsing.argtype.asset.repulsionconfig.name", RepulsionConfig.class, "server.commands.parsing.argtype.asset.repulsionconfig.usage" + ); + public static final SingleArgumentType SOUND_EVENT_ASSET = new AssetArgumentType( + "server.commands.parsing.argtype.asset.soundevent.name", SoundEvent.class, "server.commands.parsing.argtype.asset.soundevent.usage" + ); + public static final SingleArgumentType AMBIENCE_FX_ASSET = new AssetArgumentType( + "server.commands.parsing.argtype.asset.ambiencefx.name", AmbienceFX.class, "server.commands.parsing.argtype.asset.ambiencefx.usage" + ); + public static final SingleArgumentType SOUND_CATEGORY = forEnum("server.commands.parsing.argtype.soundcategory.name", SoundCategory.class); + public static final ArgWrapper ENTITY_ID = new ArgWrapper<>( + UUID.withOverriddenUsage("server.commands.parsing.argtype.entityid.usage"), EntityWrappedArg::new + ); + public static final SingleArgumentType INTEGER_COMPARISON_OPERATOR = new SingleArgumentType( + "Integer Comparison Operator", "A mathematical sign for integer comparison", ">", "<", ">=", "<=", "%", "=", "!=" + ) { + @Nullable + public ArgTypes.IntegerComparisonOperator parse(String input, @Nonnull ParseResult parseResult) { + ArgTypes.IntegerComparisonOperator integerComparisonOperator = ArgTypes.IntegerComparisonOperator.getFromStringRepresentation(input); + if (integerComparisonOperator == null) { + parseResult.fail(Message.raw("Could not find an integer comparison operator for value: '" + input + "'.")); + return null; + } else { + return integerComparisonOperator; + } + } + }; + public static final SingleArgumentType INTEGER_OPERATION = new SingleArgumentType( + "Integer Operator", "A mathematical sign for performing an operation", "+", "-", "*", "/", "%", "=" + ) { + @Nullable + public ArgTypes.IntegerOperation parse(String input, @Nonnull ParseResult parseResult) { + ArgTypes.IntegerOperation integerOperation = ArgTypes.IntegerOperation.getFromStringRepresentation(input); + if (integerOperation == null) { + parseResult.fail(Message.raw("Could not find an integer operator for value: '" + input + "'.")); + return null; + } else { + return integerOperation; + } + } + }; + public static final ArgumentType> INT_RANGE = new MultiArgumentType>( + "Integer Range", "Two integers representing a minimum and maximum of a range", "-2 8", "5 5", "1 5" + ) { + private final WrappedArgumentType minValue = this.withParameter( + "min", "Minimum value, must be less than or equal to max value", ArgTypes.INTEGER + ); + private final WrappedArgumentType maxValue = this.withParameter( + "max", "Maximum value, must be greater than or equal to min value", ArgTypes.INTEGER + ); + + @Nullable + public Pair parse(@Nonnull MultiArgumentContext context, @Nonnull ParseResult parseResult) { + if (context.get(this.minValue) > context.get(this.maxValue)) { + parseResult.fail( + Message.raw( + "You cannot set the minimum value as larger than the maximum value. Min: " + + context.get(this.minValue) + + " Max: " + + context.get(this.maxValue) + ) + ); + return null; + } else { + return Pair.of(context.get(this.minValue), context.get(this.maxValue)); + } + } + }; + public static final ArgumentType RELATIVE_INT_RANGE = new MultiArgumentType( + "Integer Range", "Two integers representing a minimum and maximum of a range", "~-2 ~8", "~5 ~5", "~1 ~5" + ) { + private final WrappedArgumentType minValue = this.withParameter( + "min", "Minimum value, must be less than or equal to max value", ArgTypes.RELATIVE_INTEGER + ); + private final WrappedArgumentType maxValue = this.withParameter( + "max", "Maximum value, must be greater than or equal to min value", ArgTypes.RELATIVE_INTEGER + ); + + @Nullable + public RelativeIntegerRange parse(@Nonnull MultiArgumentContext context, @Nonnull ParseResult parseResult) { + RelativeInteger min = this.minValue.get(context); + RelativeInteger max = this.maxValue.get(context); + if (min != null && max != null) { + if (min.isRelative() != max.isRelative()) { + parseResult.fail(Message.raw("Your range must have both min and max as relative, or both as not relative. You can not mix relatives in ranges.")); + return null; + } else if (min.getRawValue() > max.getRawValue()) { + parseResult.fail( + Message.raw( + "You cannot set the minimum value as larger than the maximum value. Min: " + + context.get(this.minValue) + + " Max: " + + context.get(this.maxValue) + ) + ); + return null; + } else { + return new RelativeIntegerRange(context.get(this.minValue), context.get(this.maxValue)); + } + } else { + parseResult.fail(Message.raw("Could not parse min or max value of the range.")); + return null; + } + } + }; + public static final ArgumentType VECTOR2I = new MultiArgumentType( + "Integer Vector 2D", "Two integers, generally corresponding to x/z axis", "124 232", "5 -3", "1 1" + ) { + private final WrappedArgumentType xValue = this.withParameter("x", "X value", ArgTypes.INTEGER); + private final WrappedArgumentType zValue = this.withParameter("z", "Z value", ArgTypes.INTEGER); + + @Nonnull + public Vector2i parse(@Nonnull MultiArgumentContext context, ParseResult parseResult) { + return new Vector2i(context.get(this.xValue), context.get(this.zValue)); + } + }; + public static final ArgumentType VECTOR3I = new MultiArgumentType( + "Integer Vector", "Three integers, generally corresponding to x/y/z axis", "124 232 234", "5 0 -3", "1 1 1" + ) { + private final WrappedArgumentType xValue = this.withParameter("x", "X value", ArgTypes.INTEGER); + private final WrappedArgumentType yValue = this.withParameter("y", "Y value", ArgTypes.INTEGER); + private final WrappedArgumentType zValue = this.withParameter("z", "Z value", ArgTypes.INTEGER); + + @Nonnull + public Vector3i parse(@Nonnull MultiArgumentContext context, ParseResult parseResult) { + return new Vector3i(context.get(this.xValue), context.get(this.yValue), context.get(this.zValue)); + } + }; + public static final ArgumentType RELATIVE_VECTOR3I = new MultiArgumentType( + "Relative Integer Vector", "Three optionally relative integers, generally corresponding to x/y/z axis", "124 ~232 234", "~5 0 ~-3", "1 ~1 1" + ) { + private final WrappedArgumentType xValue = this.withParameter("x", "X value", ArgTypes.RELATIVE_INTEGER); + private final WrappedArgumentType yValue = this.withParameter("y", "Y value", ArgTypes.RELATIVE_INTEGER); + private final WrappedArgumentType zValue = this.withParameter("z", "Z value", ArgTypes.RELATIVE_INTEGER); + + @Nonnull + public RelativeVector3i parse(@Nonnull MultiArgumentContext context, ParseResult parseResult) { + return new RelativeVector3i(context.get(this.xValue), context.get(this.yValue), context.get(this.zValue)); + } + }; + public static final ArgumentType RELATIVE_BLOCK_POSITION = new MultiArgumentType( + "server.commands.parsing.argtype.relativeBlockPosition.name", + "server.commands.parsing.argtype.relativeBlockPosition.usage", + "124 232 234", + "~5 ~ ~-3", + "~ ~ ~" + ) { + private final WrappedArgumentType xValue = this.withParameter("x", "server.commands.parsing.argtype.xCoord.usage", ArgTypes.RELATIVE_INT_COORD); + private final WrappedArgumentType yValue = this.withParameter("y", "server.commands.parsing.argtype.yCoord.usage", ArgTypes.RELATIVE_INT_COORD); + private final WrappedArgumentType zValue = this.withParameter("z", "server.commands.parsing.argtype.zCoord.usage", ArgTypes.RELATIVE_INT_COORD); + + @Nonnull + public RelativeIntPosition parse(@Nonnull MultiArgumentContext context, ParseResult parseResult) { + return new RelativeIntPosition(context.get(this.xValue), context.get(this.yValue), context.get(this.zValue)); + } + }; + public static final ArgumentType RELATIVE_POSITION = new MultiArgumentType( + "server.commands.parsing.argtype.relativePosition.name", + "server.commands.parsing.argtype.relativePosition.usage", + "124.63 232.27 234.22", + "~5.5 ~ ~", + "~ ~ ~" + ) { + private final WrappedArgumentType xValue = this.withParameter("x", "server.commands.parsing.argtype.xCoord.usage", ArgTypes.RELATIVE_DOUBLE_COORD); + private final WrappedArgumentType yValue = this.withParameter("y", "server.commands.parsing.argtype.yCoord.usage", ArgTypes.RELATIVE_DOUBLE_COORD); + private final WrappedArgumentType zValue = this.withParameter("z", "server.commands.parsing.argtype.zCoord.usage", ArgTypes.RELATIVE_DOUBLE_COORD); + + @Nonnull + public RelativeDoublePosition parse(@Nonnull MultiArgumentContext context, ParseResult parseResult) { + return new RelativeDoublePosition(context.get(this.xValue), context.get(this.yValue), context.get(this.zValue)); + } + }; + public static final ArgumentType RELATIVE_CHUNK_POSITION = new MultiArgumentType( + "server.commands.parsing.argtype.relativeChunkPosition.name", "server.commands.parsing.argtype.relativeChunkPosition.usage", "5 10", "~c2 ~c-3", "~ ~" + ) { + private final WrappedArgumentType xValue = this.withParameter("x", "server.commands.parsing.argtype.xCoord.usage", ArgTypes.RELATIVE_INT_COORD); + private final WrappedArgumentType zValue = this.withParameter("z", "server.commands.parsing.argtype.zCoord.usage", ArgTypes.RELATIVE_INT_COORD); + + @Nonnull + public RelativeChunkPosition parse(@Nonnull MultiArgumentContext context, ParseResult parseResult) { + return new RelativeChunkPosition(context.get(this.xValue), context.get(this.zValue)); + } + }; + public static final ArgumentType ROTATION = new MultiArgumentType( + "server.commands.parsing.argtype.rotation.name", "server.commands.parsing.argtype.rotation.usage", "124.63 232.27 234.22" + ) { + private final WrappedArgumentType pitch = this.withParameter( + "server.commands.parsing.argtype.pitch.name", "server.commands.parsing.argtype.pitch.usage", ArgTypes.FLOAT + ); + private final WrappedArgumentType yaw = this.withParameter( + "server.commands.parsing.argtype.yaw.name", "server.commands.parsing.argtype.yaw.usage", ArgTypes.FLOAT + ); + private final WrappedArgumentType roll = this.withParameter( + "server.commands.parsing.argtype.roll.name", "server.commands.parsing.argtype.roll.usage", ArgTypes.FLOAT + ); + + @Nonnull + public Vector3f parse(@Nonnull MultiArgumentContext context, ParseResult parseResult) { + return new Vector3f(context.get(this.pitch), context.get(this.yaw), context.get(this.roll)); + } + }; + public static final SingleArgumentType BLOCK_TYPE_KEY = new SingleArgumentType("Block Type Key", "A block type", "Wood_Drywood_Planks_Half") { + @Nullable + public String parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + try { + return input; + } catch (Exception var4) { + parseResult.fail(Message.raw(var4.getMessage())); + return null; + } + } + }; + public static final ArgumentType BLOCK_ID = new ProcessedArgumentType( + "Block Id", Message.raw("A block type, converted to an int id"), BLOCK_TYPE_KEY, "Wood_Drywood_Planks_Half" + ) { + @Nonnull + public Integer processInput(String blockTypeKey) { + return BlockType.getAssetMap().getIndex(blockTypeKey); + } + }; + public static final SingleArgumentType COLOR = new SingleArgumentType( + "server.commands.parsing.argtype.color.name", "server.commands.parsing.argtype.color.usage", "#FF0000", "#00FF00FF", "16711680", "0xFF0000" + ) { + @Nullable + public Integer parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + input = input.trim(); + if (input.isEmpty()) { + parseResult.fail(Message.raw("Color cannot be empty")); + return null; + } else if (input.charAt(0) == '#') { + try { + String hexString = input.substring(1); + long value = Long.parseLong(hexString, 16); + switch (hexString.length()) { + case 3: + int r = (int)(value >> 8 & 15L); + int g = (int)(value >> 4 & 15L); + int b = (int)(value & 15L); + return 0xFF000000 | r << 20 | r << 16 | g << 12 | g << 8 | b << 4 | b; + case 4: + int r4 = (int)(value >> 12 & 15L); + int g4 = (int)(value >> 8 & 15L); + int b4 = (int)(value >> 4 & 15L); + int a4 = (int)(value & 15L); + return r4 << 28 | r4 << 24 | g4 << 20 | g4 << 16 | b4 << 12 | b4 << 8 | a4 << 4 | a4; + case 5: + case 7: + default: + parseResult.fail(Message.raw("Invalid hex color format. Expected #RGB, #RGBA, #RRGGBB, or #RRGGBBAA, got: '" + input + "'")); + return null; + case 6: + return 0xFF000000 | (int)value; + case 8: + return (int)value; + } + } catch (NumberFormatException var13) { + parseResult.fail(Message.raw("Invalid hex color: '" + input + "'. " + var13.getMessage())); + return null; + } + } else if (input.length() <= 2 || !input.startsWith("0x") && !input.startsWith("0X")) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException var15) { + parseResult.fail( + Message.raw("Invalid color format. Expected hex color (#RRGGBB), hex integer (0xFF0000), or decimal integer (16711680), got: '" + input + "'") + ); + return null; + } + } else { + try { + return Integer.parseUnsignedInt(input.substring(2), 16); + } catch (NumberFormatException var14) { + parseResult.fail(Message.raw("Invalid hex integer color: '" + input + "'. " + var14.getMessage())); + return null; + } + } + } + }; + public static final ArgumentType> WEIGHTED_BLOCK_TYPE = new MultiArgumentType>( + "Weighted Block Type", "A weight corresponding to a blocktype", "5 Empty", "20 Rock_Stone", "2 Rock_Shale" + ) { + private final WrappedArgumentType weight = this.withParameter( + "weight", "The relative weight of this entry. Think of it as a lottery ticket", ArgTypes.INTEGER + ); + private final WrappedArgumentType blockType = this.withParameter( + "blockType", "The BlockTypeKey associated with the weight", ArgTypes.BLOCK_TYPE_KEY + ); + + @Nonnull + public Pair parse(@Nonnull MultiArgumentContext context, ParseResult parseResult) { + return Pair.of(context.get(this.weight), context.get(this.blockType)); + } + }; + private static final ArgumentType WEIGHTED_BLOCK_ENTRY = new SingleArgumentType( + "Weighted Block Entry", "A block with optional weight prefix (e.g., 20%Rock_Stone or Rock_Stone)", "Rock_Stone", "20%Rock_Stone", "50%Rock_Stone|Yaw=90" + ) { + @Nullable + public String parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + if (input.isEmpty()) { + parseResult.fail(Message.raw("Block entry cannot be empty")); + return null; + } else { + return input; + } + } + }; + public static final ArgumentType BLOCK_PATTERN = new ProcessedArgumentType, BlockPattern>( + "Block Pattern", + Message.raw("A list of blocks with optional weights (e.g., [20%Rock_Stone, 80%Rock_Shale])"), + new ListArgumentType<>(WEIGHTED_BLOCK_ENTRY), + "[Rock_Stone]", + "[20%Rock_Stone, 80%Rock_Shale]", + "[50%Rock_Stone|Yaw=90, 50%Fluid_Water]" + ) { + @Nonnull + public BlockPattern processInput(@Nonnull List entries) { + String patternString = String.join(",", entries); + return BlockPattern.parse(patternString); + } + }; + private static final ArgumentType INDIVIDUAL_BLOCK_MASK = new SingleArgumentType( + "Block Mask", "Create a block mask using symbols and block names", ">Grass_Full", "!Fluid_Water", "!^Fluid_Lava", "!#" + ) { + @Nullable + public BlockMask parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + try { + return BlockMask.parse(input); + } catch (Exception var4) { + parseResult.fail(Message.raw("There was an error in the parsing of your block mask: " + input + ", please try again.")); + return null; + } + } + }; + public static final ArgumentType BLOCK_MASK = new ProcessedArgumentType, BlockMask>( + "Block Mask", + Message.raw("A list of block masks that combine together"), + new ListArgumentType<>(INDIVIDUAL_BLOCK_MASK), + "[!Fluid_Water, !^Fluid_Lava]", + "[>Grass_Full, !#]" + ) { + public BlockMask processInput(@Nonnull List masks) { + return BlockMask.combine(masks.toArray(BlockMask[]::new)); + } + }; + public static final SingleArgumentType TICK_RATE = new SingleArgumentType( + "server.commands.parsing.argtype.tickrate.name", "server.commands.parsing.argtype.tickrate.usage", "30tps", "33ms", "60", "20tps", "50ms" + ) { + @Nullable + public Integer parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + String trimmed = input.trim().toLowerCase(); + if (trimmed.isEmpty()) { + parseResult.fail(Message.translation("server.commands.parsing.argtype.tickrate.empty")); + return null; + } else { + try { + if (trimmed.endsWith("tps")) { + String value = trimmed.substring(0, trimmed.length() - 3).trim(); + return Integer.parseInt(value); + } else if (trimmed.endsWith("ms")) { + String value = trimmed.substring(0, trimmed.length() - 2).trim(); + double ms = Double.parseDouble(value); + if (ms <= 0.0) { + parseResult.fail(Message.translation("server.commands.parsing.argtype.tickrate.invalidMs").param("input", input)); + return null; + } else { + return (int)Math.round(1000.0 / ms); + } + } else { + return Integer.parseInt(trimmed); + } + } catch (NumberFormatException var7) { + parseResult.fail(Message.translation("server.commands.parsing.argtype.tickrate.fail").param("input", input)); + return null; + } + } + } + + @Override + public void suggest(@Nonnull CommandSender sender, @Nonnull String textAlreadyEntered, int numParametersTyped, @Nonnull SuggestionResult result) { + result.suggest("30tps"); + result.suggest("60tps"); + result.suggest("20tps"); + result.suggest("33ms"); + result.suggest("16ms"); + result.suggest("50ms"); + } + }; + public static final SingleArgumentType GAME_MODE = new GameModeArgumentType(); + + public ArgTypes() { + } + + @Nonnull + public static > SingleArgumentType forEnum(String name, @Nonnull Class enumType) { + return new EnumArgumentType<>(name, enumType); + } + + public static enum IntegerComparisonOperator { + GREATER_THAN((left, right) -> left > right, ">"), + GREATER_THAN_EQUAL_TO((left, right) -> left >= right, ">="), + LESS_THAN((left, right) -> left < right, "<"), + LESS_THAN_EQUAL_TO((left, right) -> left <= right, "<="), + MOD_EQUAL_ZERO((left, right) -> left % right == 0, "%"), + MOD_NOT_EQUAL_ZERO((left, right) -> left % right != 0, "!%"), + EQUAL_TO(Integer::equals, "="), + NOT_EQUAL_TO((left, right) -> !left.equals(right), "!="); + + private final BiFunction comparisonFunction; + private final String stringRepresentation; + + private IntegerComparisonOperator(BiFunction comparisonFunction, String stringRepresentation) { + this.comparisonFunction = comparisonFunction; + this.stringRepresentation = stringRepresentation; + } + + public boolean compare(int left, int right) { + return this.comparisonFunction.apply(left, right); + } + + public String getStringRepresentation() { + return this.stringRepresentation; + } + + @Nullable + public static ArgTypes.IntegerComparisonOperator getFromStringRepresentation(String stringRepresentation) { + for (ArgTypes.IntegerComparisonOperator value : values()) { + if (value.stringRepresentation.equals(stringRepresentation)) { + return value; + } + } + + return null; + } + } + + public static enum IntegerOperation { + ADD(Integer::sum, "+"), + SUBTRACT((previous, modifier) -> previous - modifier, "-"), + MULTIPLY((previous, modifier) -> previous * modifier, "*"), + DIVIDE((previous, modifier) -> previous / modifier, "/"), + MODULUS((previous, modifier) -> previous % modifier, "%"), + SET((previous, modifier) -> modifier, "="); + + @Nonnull + private final BiFunction operationFunction; + @Nonnull + private final String stringRepresentation; + + private IntegerOperation(@Nonnull final BiFunction operationFunction, @Nonnull final String stringRepresentation) { + this.operationFunction = operationFunction; + this.stringRepresentation = stringRepresentation; + } + + public int operate(int previous, int modifier) { + return this.operationFunction.apply(previous, modifier); + } + + public String getStringRepresentation() { + return this.stringRepresentation; + } + + @Nullable + public static ArgTypes.IntegerOperation getFromStringRepresentation(@Nonnull String stringRepresentation) { + for (ArgTypes.IntegerOperation value : values()) { + if (value.stringRepresentation.equals(stringRepresentation)) { + return value; + } + } + + return null; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/ArgumentType.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/ArgumentType.java new file mode 100644 index 0000000..a31d063 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/ArgumentType.java @@ -0,0 +1,91 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import com.hypixel.hytale.server.core.command.system.arguments.system.Argument; +import com.hypixel.hytale.server.core.command.system.suggestion.SuggestionProvider; +import com.hypixel.hytale.server.core.command.system.suggestion.SuggestionResult; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class ArgumentType implements SuggestionProvider { + @Nonnull + public static final String[] EMPTY_EXAMPLES = new String[0]; + private final Message name; + @Nonnull + private final Message argumentUsage; + @Nonnull + protected final String[] examples; + protected int numberOfParameters; + + protected ArgumentType(@Nonnull Message name, @Nonnull Message argumentUsage, int numberOfParameters, @Nullable String... examples) { + this.name = name; + this.argumentUsage = argumentUsage; + this.numberOfParameters = numberOfParameters; + if (numberOfParameters < 0) { + throw new IllegalArgumentException("You cannot have less than 0 parameters for a argument type"); + } else { + this.examples = examples == null ? EMPTY_EXAMPLES : examples; + } + } + + protected ArgumentType(@Nonnull String name, @Nonnull Message argumentUsage, int numberOfParameters, @Nullable String... examples) { + this(Message.translation(name), argumentUsage, numberOfParameters, examples); + } + + protected ArgumentType(String name, @Nonnull String argumentUsage, int numberOfParameters, @Nullable String... examples) { + this(Message.translation(name), Message.translation(argumentUsage), numberOfParameters, examples); + } + + @Nullable + public DataType processedGet(CommandSender sender, CommandContext context, Argument argument) { + throw new UnsupportedOperationException("This method has not yet been implemented in the subclass, please implement it or do not call it"); + } + + @Override + public void suggest(@Nonnull CommandSender sender, @Nonnull String textAlreadyEntered, int numParametersTyped, @Nonnull SuggestionResult result) { + } + + @Nullable + public abstract DataType parse(@Nonnull String[] var1, @Nonnull ParseResult var2); + + @Nonnull + public Message getArgumentUsage() { + return this.argumentUsage; + } + + public int getNumberOfParameters() { + return this.numberOfParameters; + } + + @Nonnull + public Message getName() { + return this.name; + } + + @Nonnull + public String[] getExamples() { + return this.examples; + } + + public boolean isListArgument() { + return false; + } + + @Nonnull + @Override + public String toString() { + return "ArgumentType{name='" + + this.name + + "', argumentUsage=" + + this.argumentUsage + + ", examples=" + + Arrays.toString((Object[])this.examples) + + ", numberOfParameters=" + + this.numberOfParameters + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/AssetArgumentType.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/AssetArgumentType.java new file mode 100644 index 0000000..059f903 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/AssetArgumentType.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import javax.annotation.Nonnull; + +public class AssetArgumentType, M extends AssetMap> + extends AbstractAssetArgumentType { + public AssetArgumentType(String name, Class type, @Nonnull String argumentUsage) { + super(name, type, argumentUsage); + } + + public String getAssetKey(@Nonnull String input) { + return input; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/BooleanFlagArgumentType.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/BooleanFlagArgumentType.java new file mode 100644 index 0000000..3c1e0a7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/BooleanFlagArgumentType.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.server.core.command.system.ParseResult; +import javax.annotation.Nonnull; + +public class BooleanFlagArgumentType extends ArgumentType { + public BooleanFlagArgumentType() { + super("server.commands.parsing.argtype.flag.name", "server.commands.parsing.argtype.flag.usage", 0, "None, just specify the argument for true"); + } + + @Nonnull + public Boolean parse(@Nonnull String[] input, @Nonnull ParseResult parseResult) { + return Boolean.TRUE; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/Coord.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/Coord.java new file mode 100644 index 0000000..deb11d3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/Coord.java @@ -0,0 +1,105 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import javax.annotation.Nonnull; + +public class Coord { + private final double value; + private final boolean height; + private final boolean relative; + private final boolean chunk; + + public Coord(double value, boolean height, boolean relative, boolean chunk) { + this.value = value; + this.height = height; + this.relative = relative; + this.chunk = chunk; + } + + public double getValue() { + return this.value; + } + + public boolean isNotBase() { + return !this.height && !this.relative && !this.chunk; + } + + public boolean isHeight() { + return this.height; + } + + public boolean isRelative() { + return this.relative; + } + + public boolean isChunk() { + return this.chunk; + } + + public double resolveXZ(double base) { + return this.resolve(base); + } + + public double resolveYAtWorldCoords(double base, @Nonnull World world, double x, double z) throws GeneralCommandException { + if (this.height) { + WorldChunk worldCoords = world.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(x, z)); + if (worldCoords == null) { + throw new GeneralCommandException(Message.raw("Failed to load chunk at (" + x + ", " + z + ")")); + } else { + return worldCoords.getHeight(MathUtil.floor(x), MathUtil.floor(z)) + 1 + this.resolve(0.0); + } + } else { + return this.resolve(base); + } + } + + protected double resolve(double base) { + double val = this.chunk ? this.value * 32.0 : this.value; + return this.relative ? val + base : val; + } + + @Nonnull + public static Coord parse(@Nonnull String str) { + boolean height = false; + boolean relative = false; + boolean chunk = false; + int index = 0; + + label20: + while (true) { + switch (str.charAt(index)) { + case '_': + index++; + height = true; + if (str.length() == index) { + return new Coord(0.0, true, relative, chunk); + } + break; + case 'c': + index++; + chunk = true; + if (str.length() == index) { + return new Coord(0.0, height, relative, true); + } + break label20; + case '~': + index++; + relative = true; + if (str.length() == index) { + return new Coord(0.0, height, true, chunk); + } + break; + default: + break label20; + } + } + + String rest = str.substring(index); + return new Coord(Double.parseDouble(rest), height, relative, chunk); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/EntityWrappedArg.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/EntityWrappedArg.java new file mode 100644 index 0000000..08bb7f0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/EntityWrappedArg.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.Argument; +import com.hypixel.hytale.server.core.command.system.arguments.system.WrappedArg; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityWrappedArg extends WrappedArg { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + + public EntityWrappedArg(@Nonnull Argument argument) { + super(argument); + } + + @Nullable + public Ref get(@Nonnull ComponentAccessor componentAccessor, @Nonnull CommandContext context) { + World world = componentAccessor.getExternalData().getWorld(); + Ref reference = this.getEntityDirectly(context, world); + if (reference != null) { + return reference; + } else { + Ref playerRef = context.isPlayer() ? context.senderAsPlayerRef() : null; + if (playerRef == null) { + throw new GeneralCommandException(Message.translation("server.commands.errors.playerOrArg").param("option", "entity")); + } else if (!playerRef.isValid()) { + throw new GeneralCommandException(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + Ref entityRef = TargetUtil.getTargetEntity(playerRef, componentAccessor); + if (entityRef == null) { + throw new GeneralCommandException(Message.translation("server.commands.errors.no_entity_in_view").param("option", "entity")); + } else { + return entityRef; + } + } + } + } + + @Nullable + public Ref getEntityDirectly(@Nonnull CommandContext context, @Nonnull World world) { + if (!this.provided(context)) { + return null; + } else { + UUID uuid = this.get(context); + Ref reference = world.getEntityStore().getRefFromUUID(uuid); + if (reference == null) { + throw new GeneralCommandException(Message.translation("server.commands.errors.targetNotFound").param("uuid", uuid.toString())); + } else { + return reference; + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/EnumArgumentType.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/EnumArgumentType.java new file mode 100644 index 0000000..b59b80e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/EnumArgumentType.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EnumArgumentType> extends SingleArgumentType { + @Nonnull + private final Class enumClass; + @Nonnull + private final E[] enumConstants; + @Nonnull + private final List enumNames; + + public EnumArgumentType(@Nonnull String name, @Nonnull Class enumClass) { + super(name, getArgumentUsageString(enumClass.getEnumConstants()), getExamples(enumClass.getEnumConstants())); + this.enumClass = enumClass; + this.enumConstants = enumClass.getEnumConstants(); + this.enumNames = new ObjectArrayList<>(); + + for (E enumConstant : this.enumConstants) { + this.enumNames.add(enumConstant.name()); + } + } + + @Nullable + public E parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + String inputLowerCase = input.toLowerCase(); + + for (E enumConstant : this.enumConstants) { + if (enumConstant.name().toLowerCase().equals(inputLowerCase)) { + return enumConstant; + } + } + + parseResult.fail( + Message.empty() + .insert(Message.translation("server.commands.errors.noSuchEnum").param("type", this.enumClass.getSimpleName()).param("name", input)) + .insert(Message.raw(" ")) + .insert( + Message.translation("server.general.failed.didYouMean") + .param("choices", StringUtil.sortByFuzzyDistance(inputLowerCase, this.enumNames, CommandUtil.RECOMMEND_COUNT).toString()) + ) + ); + return null; + } + + @Nonnull + public static > String getArgumentUsageString(@Nonnull X[] enumConstants) { + StringBuilder stringBuilder = new StringBuilder(); + + for (int i = 0; i < enumConstants.length; i++) { + stringBuilder.append(enumConstants[i].name().toLowerCase()); + if (i != enumConstants.length - 1) { + stringBuilder.append(", "); + } + } + + return stringBuilder.toString(); + } + + @Nonnull + public static > String[] getExamples(@Nonnull X[] enumConstants) { + String[] examples = new String[enumConstants.length]; + + for (int i = 0; i < enumConstants.length; i++) { + examples[i] = enumConstants[i].name().toLowerCase(); + } + + return examples; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/GameModeArgumentType.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/GameModeArgumentType.java new file mode 100644 index 0000000..686ae89 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/GameModeArgumentType.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class GameModeArgumentType extends SingleArgumentType { + @Nonnull + private static final Object2ObjectMap GAMEMODE_MAP = new Object2ObjectOpenHashMap<>(); + + public GameModeArgumentType() { + super("server.commands.parsing.argtype.gamemode.name", "server.commands.parsing.argtype.gamemode.usage", "adventure", "creative", "a", "c"); + } + + @Nullable + public GameMode parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + String inputLowerCase = input.toLowerCase(); + GameMode gameMode = GAMEMODE_MAP.get(inputLowerCase); + if (gameMode != null) { + return gameMode; + } else { + List validModes = StringUtil.sortByFuzzyDistance(inputLowerCase, GAMEMODE_MAP.keySet(), CommandUtil.RECOMMEND_COUNT); + parseResult.fail( + Message.translation("server.commands.parsing.argtype.gamemode.invalid").param("input", input).param("suggestions", validModes.toString()) + ); + return null; + } + } + + static { + GAMEMODE_MAP.put("adventure", GameMode.Adventure); + GAMEMODE_MAP.put("creative", GameMode.Creative); + GAMEMODE_MAP.put("a", GameMode.Adventure); + GAMEMODE_MAP.put("c", GameMode.Creative); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/IntCoord.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/IntCoord.java new file mode 100644 index 0000000..d6b9042 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/IntCoord.java @@ -0,0 +1,105 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; + +public class IntCoord { + private final int value; + private final boolean height; + private final boolean relative; + private final boolean chunk; + + public IntCoord(int value, boolean height, boolean relative, boolean chunk) { + this.value = value; + this.height = height; + this.relative = relative; + this.chunk = chunk; + } + + public int getValue() { + return this.value; + } + + public boolean isNotBase() { + return !this.height && !this.relative && !this.chunk; + } + + public boolean isHeight() { + return this.height; + } + + public boolean isRelative() { + return this.relative; + } + + public boolean isChunk() { + return this.chunk; + } + + public int resolveXZ(int base) { + return this.resolve(base); + } + + public int resolveYAtWorldCoords(int base, @Nonnull ChunkStore chunkStore, int x, int z) { + if (this.height) { + long chunkIndex = ChunkUtil.indexChunkFromBlock(x, z); + Ref chunkRef = chunkStore.getChunkReference(chunkIndex); + if (chunkRef != null && chunkRef.isValid()) { + WorldChunk worldChunkComponent = chunkStore.getStore().getComponent(chunkRef, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + return this.resolve(worldChunkComponent.getHeight(x, z) + 1); + } else { + return this.resolve(base); + } + } else { + return this.resolve(base); + } + } + + protected int resolve(int base) { + int val = this.chunk ? this.value * 32 : this.value; + return this.relative ? val + base : val; + } + + @Nonnull + public static IntCoord parse(@Nonnull String str) { + boolean height = false; + boolean relative = false; + boolean chunk = false; + int index = 0; + + while (true) { + switch (str.charAt(index)) { + case '_': + index++; + height = true; + if (str.length() == index) { + return new IntCoord(0, true, relative, chunk); + } + break; + case 'c': + index++; + chunk = true; + if (str.length() == index) { + return new IntCoord(0, height, relative, true); + } + break; + case '~': + index++; + relative = true; + if (str.length() == index) { + return new IntCoord(0, height, true, chunk); + } + break; + default: + String rest = str.substring(index); + return new IntCoord(Integer.parseInt(rest), height, relative, chunk); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/ListArgumentType.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/ListArgumentType.java new file mode 100644 index 0000000..6b7e8b3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/ListArgumentType.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ListArgumentType extends ArgumentType> { + @Nonnull + private final ArgumentType argumentType; + + public ListArgumentType(@Nonnull ArgumentType argumentType) { + super( + Message.translation("server.commands.parsing.argtype.list.name").insert(" (").insert(argumentType.getName()).insert(")"), + Message.translation("server.commands.parsing.argtype.list.usage"), + argumentType.getNumberOfParameters(), + argumentType.getExamples() + ); + this.argumentType = argumentType; + } + + @Override + public boolean isListArgument() { + return true; + } + + @Nullable + public List parse(@Nonnull String[] input, @Nonnull ParseResult parseResult) { + List returnList = new ObjectArrayList<>(); + int i = 0; + + while (i < input.length) { + returnList.add(this.argumentType.parse(Arrays.copyOfRange(input, i, i + this.argumentType.getNumberOfParameters()), parseResult)); + if (parseResult.failed()) { + return null; + } + + i += this.argumentType.getNumberOfParameters(); + } + + return returnList; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/MultiArgumentContext.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/MultiArgumentContext.java new file mode 100644 index 0000000..8d451f4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/MultiArgumentContext.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.server.core.command.system.ParseResult; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MultiArgumentContext { + @Nonnull + private final Map, Object> parsedArguments = new Object2ObjectOpenHashMap<>(); + + public MultiArgumentContext() { + } + + public void registerArgumentValues(@Nonnull ArgumentType argumentType, @Nonnull String[] values, @Nonnull ParseResult parseResult) { + this.parsedArguments.put(argumentType, argumentType.parse(values, parseResult)); + } + + @Nullable + public DataType get(@Nonnull ArgumentType argumentType) { + return (DataType)this.parsedArguments.get(argumentType); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/MultiArgumentType.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/MultiArgumentType.java new file mode 100644 index 0000000..6d615fe --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/MultiArgumentType.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.server.core.command.system.ParseResult; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import java.util.Arrays; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class MultiArgumentType extends ArgumentType { + @Nonnull + private final Map> argumentValues = new Object2ObjectLinkedOpenHashMap<>(); + + public MultiArgumentType(@Nonnull String name, @Nonnull String argumentUsage, @Nullable String... examples) { + super(name, argumentUsage, 0, examples); + } + + @Nonnull + protected WrappedArgumentType withParameter(@Nonnull String name, @Nonnull String usage, @Nonnull SingleArgumentType argumentType) { + WrappedArgumentType wrappedArgumentType = argumentType.withOverriddenUsage(usage); + name = name.toLowerCase(); + if (this.argumentValues.containsKey(name)) { + throw new IllegalArgumentException("Cannot register two MultiArgumentType parameters with the same name"); + } else { + this.argumentValues.put(name, wrappedArgumentType); + this.numberOfParameters = this.argumentValues.size(); + return wrappedArgumentType; + } + } + + @Nullable + @Override + public DataType parse(@Nonnull String[] input, @Nonnull ParseResult parseResult) { + MultiArgumentContext multiArgumentContext = new MultiArgumentContext(); + int endOfLastIndex = 0; + + for (SingleArgumentType argumentValue : this.argumentValues.values()) { + multiArgumentContext.registerArgumentValues( + argumentValue, Arrays.copyOfRange(input, endOfLastIndex, endOfLastIndex + argumentValue.numberOfParameters), parseResult + ); + if (parseResult.failed()) { + return null; + } + + endOfLastIndex += argumentValue.numberOfParameters; + } + + return this.parse(multiArgumentContext, parseResult); + } + + @Nullable + public abstract DataType parse(@Nonnull MultiArgumentContext var1, @Nonnull ParseResult var2); +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/ProcessedArgumentType.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/ProcessedArgumentType.java new file mode 100644 index 0000000..0f61971 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/ProcessedArgumentType.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class ProcessedArgumentType extends ArgumentType { + @Nonnull + private final ArgumentType inputTypeArgumentType; + + public ProcessedArgumentType(String name, Message argumentUsage, @Nonnull ArgumentType inputTypeArgumentType, @Nullable String... examples) { + super(name, argumentUsage, inputTypeArgumentType.numberOfParameters, examples); + this.inputTypeArgumentType = inputTypeArgumentType; + } + + @Nonnull + public ArgumentType getInputTypeArgumentType() { + return this.inputTypeArgumentType; + } + + @Override + public boolean isListArgument() { + return this.getInputTypeArgumentType().isListArgument(); + } + + @Nullable + @Override + public OutputType parse(@Nonnull String[] input, @Nonnull ParseResult parseResult) { + InputType parsedData = this.inputTypeArgumentType.parse(input, parseResult); + if (parseResult.failed()) { + return null; + } else { + OutputType outputType = this.processInput(parsedData); + return parseResult.failed() ? null : outputType; + } + } + + public abstract OutputType processInput(InputType var1); +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeChunkPosition.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeChunkPosition.java new file mode 100644 index 0000000..35c2eb2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeChunkPosition.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class RelativeChunkPosition { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_WORLD_UNPSPECIFIED = Message.translation("server.commands.errors.worldUnspecified"); + private final IntCoord x; + private final IntCoord z; + + public RelativeChunkPosition(@Nonnull IntCoord x, @Nonnull IntCoord z) { + this.x = x; + this.z = z; + } + + @Nonnull + public Vector2i getChunkPosition(@Nonnull CommandContext context, ComponentAccessor componentAccessor) { + Ref playerRef = context.senderAsPlayerRef(); + if (playerRef == null) { + throw new GeneralCommandException(MESSAGE_COMMANDS_ERRORS_WORLD_UNPSPECIFIED); + } else if (!playerRef.isValid()) { + throw new GeneralCommandException(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + TransformComponent transformComponent = componentAccessor.getComponent(playerRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + return this.getChunkPosition(transformComponent.getPosition()); + } + } + + @Nonnull + public Vector2i getChunkPosition(@Nonnull Vector3d base) { + int relX = this.x.isNotBase() ? this.x.getValue() : this.x.resolveXZ(MathUtil.floor(base.x)) >> 5; + int relZ = this.z.isNotBase() ? this.z.getValue() : this.z.resolveXZ(MathUtil.floor(base.z)) >> 5; + return new Vector2i(relX, relZ); + } + + public boolean isRelative() { + return this.x.isRelative() || this.z.isRelative(); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeDirection.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeDirection.java new file mode 100644 index 0000000..73533b2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeDirection.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public enum RelativeDirection { + FORWARD, + BACKWARD, + LEFT, + RIGHT, + UP, + DOWN; + + public static final SingleArgumentType ARGUMENT_TYPE = new SingleArgumentType( + "Relative Direction", + "A direction relative to the player (forward, backward, left, right, up, down)", + "forward", + "backward", + "left", + "right", + "up", + "down" + ) { + @Nullable + public RelativeDirection parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + String var3 = input.toLowerCase(); + + return switch (var3) { + case "forward", "f" -> RelativeDirection.FORWARD; + case "backward", "back", "b" -> RelativeDirection.BACKWARD; + case "left", "l" -> RelativeDirection.LEFT; + case "right", "r" -> RelativeDirection.RIGHT; + case "up", "u" -> RelativeDirection.UP; + case "down", "d" -> RelativeDirection.DOWN; + default -> { + parseResult.fail(Message.raw("Invalid direction: " + input + ". Use: forward, backward, left, right, up, down")); + yield null; + } + }; + } + }; + + private RelativeDirection() { + } + + @Nonnull + public static Vector3i toDirectionVector(@Nullable RelativeDirection direction, @Nonnull HeadRotation headRotation) { + if (direction == null) { + return headRotation.getAxisDirection(); + } else { + return switch (direction) { + case FORWARD -> headRotation.getHorizontalAxisDirection(); + case BACKWARD -> headRotation.getHorizontalAxisDirection().clone().scale(-1); + case LEFT -> rotateLeft(headRotation.getHorizontalAxisDirection()); + case RIGHT -> rotateRight(headRotation.getHorizontalAxisDirection()); + case UP -> new Vector3i(0, 1, 0); + case DOWN -> new Vector3i(0, -1, 0); + }; + } + } + + @Nonnull + public static Axis toAxis(@Nonnull RelativeDirection direction, @Nonnull HeadRotation headRotation) { + return switch (direction) { + case FORWARD, BACKWARD -> getHorizontalAxis(headRotation); + case LEFT, RIGHT -> getPerpendicularHorizontalAxis(headRotation); + case UP, DOWN -> Axis.Y; + }; + } + + @Nonnull + private static Axis getHorizontalAxis(@Nonnull HeadRotation headRotation) { + Vector3i horizontalDir = headRotation.getHorizontalAxisDirection(); + return horizontalDir.getX() != 0 ? Axis.X : Axis.Z; + } + + @Nonnull + private static Axis getPerpendicularHorizontalAxis(@Nonnull HeadRotation headRotation) { + Vector3i horizontalDir = headRotation.getHorizontalAxisDirection(); + return horizontalDir.getX() != 0 ? Axis.Z : Axis.X; + } + + @Nonnull + private static Vector3i rotateLeft(@Nonnull Vector3i dir) { + return new Vector3i(dir.z, 0, -dir.x); + } + + @Nonnull + private static Vector3i rotateRight(@Nonnull Vector3i dir) { + return new Vector3i(-dir.z, 0, dir.x); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeDoublePosition.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeDoublePosition.java new file mode 100644 index 0000000..ec50897 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeDoublePosition.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class RelativeDoublePosition { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_RELATIVE_POSITION_ARG = Message.translation("server.commands.errors.relativePositionArg"); + private final Coord x; + private final Coord y; + private final Coord z; + + public RelativeDoublePosition(@Nonnull Coord x, @Nonnull Coord y, @Nonnull Coord z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Nonnull + public Vector3d getRelativePosition(@Nonnull Vector3d base, @Nonnull World world) { + double relX = this.x.resolveXZ(base.x); + double relZ = this.z.resolveXZ(base.z); + double relY = this.y.resolveYAtWorldCoords(base.y, world, relX, relZ); + return new Vector3d(relX, relY, relZ); + } + + @Nonnull + public Vector3d getRelativePosition(@Nonnull CommandContext context, @Nonnull World world, @Nonnull ComponentAccessor componentAccessor) { + boolean relative = this.isRelative(); + if (relative && !context.isPlayer()) { + throw new GeneralCommandException(MESSAGE_COMMANDS_ERRORS_RELATIVE_POSITION_ARG); + } else { + Vector3d basePosition; + if (relative) { + Ref senderPlayerRef = context.senderAsPlayerRef(); + if (senderPlayerRef == null || !senderPlayerRef.isValid()) { + throw new GeneralCommandException(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + + TransformComponent transformComponent = componentAccessor.getComponent(senderPlayerRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + basePosition = transformComponent.getPosition(); + } else { + basePosition = Vector3d.ZERO; + } + + return this.getRelativePosition(basePosition, world); + } + } + + public boolean isRelative() { + return this.x.isRelative() || this.y.isRelative() || this.z.isRelative(); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeFloat.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeFloat.java new file mode 100644 index 0000000..dc5d8c4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeFloat.java @@ -0,0 +1,71 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RelativeFloat { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(RelativeFloat.class, RelativeFloat::new) + .append(new KeyedCodec<>("Value", Codec.FLOAT), (o, i) -> o.value = i, RelativeFloat::getRawValue) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Relative", Codec.BOOLEAN), (o, i) -> o.isRelative = i, RelativeFloat::isRelative) + .addValidator(Validators.nonNull()) + .add() + .build(); + private float value; + private boolean isRelative; + + public RelativeFloat(float value, boolean isRelative) { + this.value = value; + this.isRelative = isRelative; + } + + protected RelativeFloat() { + } + + @Nullable + public static RelativeFloat parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + boolean relative = input.contains("~"); + input = input.replaceAll(Pattern.quote("~"), ""); + + try { + float value; + if (input.isBlank()) { + value = 0.0F; + } else { + value = Float.parseFloat(input); + } + + return new RelativeFloat(value, relative); + } catch (Exception var4) { + parseResult.fail(Message.raw("Invalid float: " + input)); + return null; + } + } + + public float getRawValue() { + return this.value; + } + + public boolean isRelative() { + return this.isRelative; + } + + public float resolve(float baseValue) { + return this.isRelative ? baseValue + this.value : this.value; + } + + @Nonnull + @Override + public String toString() { + return (this.isRelative ? "~" : "") + this.value; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeIntPosition.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeIntPosition.java new file mode 100644 index 0000000..efe6c0e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeIntPosition.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class RelativeIntPosition { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_RELATIVE_POSITION_ARG = Message.translation("server.commands.errors.relativePositionArg"); + @Nonnull + private final IntCoord x; + @Nonnull + private final IntCoord y; + @Nonnull + private final IntCoord z; + + public RelativeIntPosition(@Nonnull IntCoord x, @Nonnull IntCoord y, @Nonnull IntCoord z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Nonnull + public Vector3i getBlockPosition(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d base = transformComponent.getPosition(); + World world = componentAccessor.getExternalData().getWorld(); + return this.getBlockPosition(base, world.getChunkStore()); + } + + @Nonnull + public Vector3i getBlockPosition(@Nonnull CommandContext context, @Nonnull ComponentAccessor componentAccessor) { + boolean relative = this.isRelative(); + Ref playerRef = context.isPlayer() ? context.senderAsPlayerRef() : null; + if (playerRef != null) { + if (!playerRef.isValid()) { + throw new GeneralCommandException(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + return this.getBlockPosition(playerRef, componentAccessor); + } + } else if (relative) { + throw new GeneralCommandException(MESSAGE_COMMANDS_ERRORS_RELATIVE_POSITION_ARG); + } else { + Vector3d base = Vector3d.ZERO; + World world = componentAccessor.getExternalData().getWorld(); + return this.getBlockPosition(base, world.getChunkStore()); + } + } + + @Nonnull + public Vector3i getBlockPosition(@Nonnull Vector3d base, @Nonnull ChunkStore chunkStore) { + int relX = this.x.resolveXZ(MathUtil.floor(base.x)); + int relZ = this.z.resolveXZ(MathUtil.floor(base.z)); + int relY = this.y.resolveYAtWorldCoords(MathUtil.floor(base.y), chunkStore, relX, relZ); + return new Vector3i(relX, relY, relZ); + } + + public boolean isRelative() { + return this.x.isRelative() || this.y.isRelative() || this.z.isRelative(); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeInteger.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeInteger.java new file mode 100644 index 0000000..401a17a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeInteger.java @@ -0,0 +1,71 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RelativeInteger { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(RelativeInteger.class, RelativeInteger::new) + .append(new KeyedCodec<>("Value", Codec.INTEGER), (o, i) -> o.value = i, RelativeInteger::getRawValue) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Relative", Codec.BOOLEAN), (o, i) -> o.isRelative = i, RelativeInteger::isRelative) + .addValidator(Validators.nonNull()) + .add() + .build(); + private int value; + private boolean isRelative; + + public RelativeInteger(int value, boolean isRelative) { + this.value = value; + this.isRelative = isRelative; + } + + protected RelativeInteger() { + } + + @Nullable + public static RelativeInteger parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + boolean relative = input.contains("~"); + input = input.replaceAll(Pattern.quote("~"), ""); + + try { + int value; + if (input.isBlank()) { + value = 0; + } else { + value = Integer.parseInt(input); + } + + return new RelativeInteger(value, relative); + } catch (Exception var4) { + parseResult.fail(Message.raw("Invalid integer: " + input)); + return null; + } + } + + public int getRawValue() { + return this.value; + } + + public boolean isRelative() { + return this.isRelative; + } + + public int resolve(int baseValue) { + return this.isRelative ? baseValue + this.value : this.value; + } + + @Nonnull + @Override + public String toString() { + return (this.isRelative ? "~" : "") + this.value; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeIntegerRange.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeIntegerRange.java new file mode 100644 index 0000000..bfa4e2d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeIntegerRange.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; + +public class RelativeIntegerRange { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(RelativeIntegerRange.class, RelativeIntegerRange::new) + .append(new KeyedCodec<>("Min", RelativeInteger.CODEC), (o, i) -> o.min = i, o -> o.min) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Max", RelativeInteger.CODEC), (o, i) -> o.max = i, o -> o.max) + .addValidator(Validators.nonNull()) + .add() + .build(); + private RelativeInteger min; + private RelativeInteger max; + + public RelativeIntegerRange(@Nonnull RelativeInteger min, @Nonnull RelativeInteger max) { + this.min = min; + this.max = max; + } + + protected RelativeIntegerRange() { + } + + public RelativeIntegerRange(int min, int max) { + this.min = new RelativeInteger(min, false); + this.max = new RelativeInteger(max, false); + } + + public int getNumberInRange(int base) { + return this.min.getRawValue() == this.max.getRawValue() + ? this.min.resolve(base) + : ThreadLocalRandom.current().nextInt(this.min.resolve(base), this.max.resolve(base) + 1); + } + + @Nonnull + @Override + public String toString() { + return "{ Minimum: " + this.min + ", Maximum: " + this.max + " }"; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeVector3i.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeVector3i.java new file mode 100644 index 0000000..bddb5ee --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/RelativeVector3i.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public class RelativeVector3i { + @Nonnull + public static final RelativeVector3i ZERO = new RelativeVector3i(new RelativeInteger(0, false), new RelativeInteger(0, false), new RelativeInteger(0, false)); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(RelativeVector3i.class, RelativeVector3i::new) + .append(new KeyedCodec<>("X", RelativeInteger.CODEC), (vec, val) -> vec.x = val, vec -> vec.x) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Y", RelativeInteger.CODEC), (vec, val) -> vec.y = val, vec -> vec.y) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Z", RelativeInteger.CODEC), (vec, val) -> vec.z = val, vec -> vec.z) + .addValidator(Validators.nonNull()) + .add() + .build(); + private RelativeInteger x; + private RelativeInteger y; + private RelativeInteger z; + + public RelativeVector3i(RelativeInteger x, RelativeInteger y, RelativeInteger z) { + this.x = x; + this.y = y; + this.z = z; + } + + protected RelativeVector3i() { + } + + @Nonnull + public Vector3i resolve(int xBase, int yBase, int zBase) { + return new Vector3i(this.x.resolve(xBase), this.y.resolve(yBase), this.z.resolve(zBase)); + } + + @Nonnull + public Vector3i resolve(@Nonnull Vector3i base) { + return this.resolve(base.x, base.y, base.z); + } + + @Nonnull + @Override + public String toString() { + return "{" + this.x + ", " + this.y + ", " + this.z + "}"; + } + + public boolean isRelativeX() { + return this.x.isRelative(); + } + + public boolean isRelativeY() { + return this.y.isRelative(); + } + + public boolean isRelativeZ() { + return this.z.isRelative(); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/SingleArgumentType.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/SingleArgumentType.java new file mode 100644 index 0000000..1eb5ff3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/SingleArgumentType.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class SingleArgumentType extends ArgumentType { + protected SingleArgumentType(Message name, @Nonnull String argumentUsage, @Nullable String... examples) { + super(name, Message.translation(argumentUsage), 1, examples); + } + + protected SingleArgumentType(String name, @Nonnull Message argumentUsage, @Nullable String... examples) { + super(name, argumentUsage, 1, examples); + } + + public SingleArgumentType(String name, @Nonnull String argumentUsage, @Nullable String... examples) { + super(name, argumentUsage, 1, examples); + } + + @Nullable + @Override + public DataType parse(@Nonnull String[] input, @Nonnull ParseResult parseResult) { + return this.parse(input[0], parseResult); + } + + @Nullable + public abstract DataType parse(String var1, ParseResult var2); + + @Nonnull + public WrappedArgumentType withOverriddenUsage(@Nonnull String usage, @Nullable String... examples) { + return new WrappedArgumentType(this.getName(), this, usage, examples) { + @Override + public DataType parse(String input, ParseResult parseResult) { + return this.wrappedArgumentType.parse(new String[]{input}, parseResult); + } + }; + } + + @Nonnull + public WrappedArgumentType withOverriddenUsage(@Nonnull String usage) { + return new WrappedArgumentType(this.getName(), this, usage, this.examples) { + @Override + public DataType parse(String input, ParseResult parseResult) { + return this.wrappedArgumentType.parse(new String[]{input}, parseResult); + } + }; + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/arguments/types/WrappedArgumentType.java b/src/com/hypixel/hytale/server/core/command/system/arguments/types/WrappedArgumentType.java new file mode 100644 index 0000000..9e217aa --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/arguments/types/WrappedArgumentType.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.core.command.system.arguments.types; + +import com.hypixel.hytale.server.core.Message; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class WrappedArgumentType extends SingleArgumentType { + protected final ArgumentType wrappedArgumentType; + + public WrappedArgumentType(Message name, ArgumentType wrappedArgumentType, @Nonnull String argumentUsage, @Nullable String... examples) { + super(name, argumentUsage, examples); + this.wrappedArgumentType = wrappedArgumentType; + } + + @Nonnull + @Override + public String[] getExamples() { + return Arrays.equals((Object[])this.examples, (Object[])EMPTY_EXAMPLES) ? this.wrappedArgumentType.getExamples() : this.examples; + } + + @Nullable + public DataType get(@Nonnull MultiArgumentContext context) { + return context.get(this); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractAsyncCommand.java b/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractAsyncCommand.java new file mode 100644 index 0000000..79649f0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractAsyncCommand.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.core.command.system.basecommands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import java.awt.Color; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public abstract class AbstractAsyncCommand extends AbstractCommand { + @Nonnull + private static final Message MESSAGE_MODULES_COMMAND_RUNTIME_ERROR = Message.translation("server.modules.command.runtimeError").color(Color.RED); + + public AbstractAsyncCommand(@Nonnull String name, @Nonnull String description) { + super(name, description); + } + + public AbstractAsyncCommand(@Nonnull String name, @Nonnull String description, boolean requiresConfirmation) { + super(name, description, requiresConfirmation); + } + + public AbstractAsyncCommand(@Nonnull String description) { + super(description); + } + + @Override + protected final CompletableFuture execute(@Nonnull CommandContext context) { + return this.executeAsync(context); + } + + @Nonnull + protected abstract CompletableFuture executeAsync(@Nonnull CommandContext var1); + + @Nonnull + public CompletableFuture runAsync(@Nonnull CommandContext context, @Nonnull Runnable runnable, @Nonnull Executor executor) { + return CompletableFuture.runAsync(() -> { + try { + runnable.run(); + } catch (Exception var3x) { + context.sendMessage(MESSAGE_MODULES_COMMAND_RUNTIME_ERROR); + AbstractCommand.LOGGER.at(Level.SEVERE).withCause(var3x).log("Exception while running that command:"); + } + }, executor); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractAsyncPlayerCommand.java b/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractAsyncPlayerCommand.java new file mode 100644 index 0000000..b105b85 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractAsyncPlayerCommand.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.server.core.command.system.basecommands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; + +public abstract class AbstractAsyncPlayerCommand extends AbstractAsyncCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_OR_ARG = Message.translation("server.commands.errors.playerOrArg").param("option", "player"); + + public AbstractAsyncPlayerCommand(@Nonnull String name, @Nonnull String description) { + super(name, description); + } + + public AbstractAsyncPlayerCommand(@Nonnull String name, @Nonnull String description, boolean requiresConfirmation) { + super(name, description, requiresConfirmation); + } + + public AbstractAsyncPlayerCommand(@Nonnull String description) { + super(description); + } + + @Nonnull + @Override + protected final CompletableFuture executeAsync(@Nonnull CommandContext context) { + Ref ref = context.senderAsPlayerRef(); + if (ref == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_OR_ARG); + return CompletableFuture.completedFuture(null); + } else if (!ref.isValid()) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + return CompletableFuture.completedFuture(null); + } else { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + return CompletableFuture.>supplyAsync(() -> { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + if (playerRefComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + return null; + } else { + return this.executeAsync(context, store, ref, playerRefComponent, world); + } + }, world).thenCompose(future -> (CompletionStage)(future != null ? future : CompletableFuture.completedFuture(null))); + } + } + + @Nonnull + protected abstract CompletableFuture executeAsync( + @Nonnull CommandContext var1, @Nonnull Store var2, @Nonnull Ref var3, @Nonnull PlayerRef var4, @Nonnull World var5 + ); +} diff --git a/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractAsyncWorldCommand.java b/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractAsyncWorldCommand.java new file mode 100644 index 0000000..a3faecb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractAsyncWorldCommand.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.server.core.command.system.basecommands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public abstract class AbstractAsyncWorldCommand extends AbstractAsyncCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_NO_WORLD = Message.translation("server.commands.errors.noWorld"); + @Nonnull + private final OptionalArg worldArg = this.withOptionalArg("world", "server.commands.worldthread.arg.desc", ArgTypes.WORLD); + + public AbstractAsyncWorldCommand(@Nonnull String name, @Nonnull String description) { + super(name, description); + } + + public AbstractAsyncWorldCommand(@Nonnull String name, @Nonnull String description, boolean requiresConfirmation) { + super(name, description, requiresConfirmation); + } + + public AbstractAsyncWorldCommand(@Nonnull String description) { + super(description); + } + + @Nonnull + @Override + protected final CompletableFuture executeAsync(@Nonnull CommandContext context) { + World world = this.worldArg.getProcessed(context); + if (world == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_NO_WORLD); + return CompletableFuture.completedFuture(null); + } else { + return this.executeAsync(context, world); + } + } + + @Nonnull + protected abstract CompletableFuture executeAsync(@Nonnull CommandContext var1, @Nonnull World var2); +} diff --git a/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractCommandCollection.java b/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractCommandCollection.java new file mode 100644 index 0000000..5d7efea --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractCommandCollection.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.server.core.command.system.basecommands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public abstract class AbstractCommandCollection extends AbstractAsyncCommand { + public AbstractCommandCollection(@Nonnull String name, @Nonnull String description) { + super(name, description); + } + + @Nonnull + public Message getFullUsage(@Nonnull CommandSender sender) { + return super.getUsageString(sender); + } + + @Nonnull + @Override + protected final CompletableFuture executeAsync(@Nonnull CommandContext context) { + Message message = Message.translation("server.commands.help.usage") + .insert(":") + .insert(" (") + .insert(Message.translation("server.commands.help.useHelpOnAnySubCommand")) + .insert(")") + .insert("\n") + .insert(this.getUsageString(context.sender())); + context.sender().sendMessage(message); + return CompletableFuture.completedFuture(null); + } + + @Nonnull + @Override + public Message getUsageString(@Nonnull CommandSender sender) { + return this.getUsageShort(sender, false); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractPlayerCommand.java b/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractPlayerCommand.java new file mode 100644 index 0000000..58b4933 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractPlayerCommand.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.command.system.basecommands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public abstract class AbstractPlayerCommand extends AbstractAsyncCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_OR_ARG = Message.translation("server.commands.errors.playerOrArg").param("option", "player"); + + public AbstractPlayerCommand(@Nonnull String name, @Nonnull String description) { + super(name, description); + } + + public AbstractPlayerCommand(@Nonnull String name, @Nonnull String description, boolean requiresConfirmation) { + super(name, description, requiresConfirmation); + } + + public AbstractPlayerCommand(@Nonnull String description) { + super(description); + } + + @Nonnull + @Override + protected final CompletableFuture executeAsync(@Nonnull CommandContext context) { + Ref ref = context.senderAsPlayerRef(); + if (ref == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_OR_ARG); + return CompletableFuture.completedFuture(null); + } else if (!ref.isValid()) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + return CompletableFuture.completedFuture(null); + } else { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + return this.runAsync(context, () -> { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + if (playerRefComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } else { + this.execute(context, store, ref, playerRefComponent, world); + } + }, world); + } + } + + protected abstract void execute( + @Nonnull CommandContext var1, @Nonnull Store var2, @Nonnull Ref var3, @Nonnull PlayerRef var4, @Nonnull World var5 + ); +} diff --git a/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractTargetEntityCommand.java b/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractTargetEntityCommand.java new file mode 100644 index 0000000..d252222 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractTargetEntityCommand.java @@ -0,0 +1,147 @@ +package com.hypixel.hytale.server.core.command.system.basecommands; + +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import it.unimi.dsi.fastutil.objects.ObjectLists; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public abstract class AbstractTargetEntityCommand extends AbstractAsyncCommand { + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + private static final Message MESSAGE_GENERAL_NO_ENTITY_IN_VIEW = Message.translation("server.general.noEntityInView"); + @Nonnull + private final OptionalArg worldArg = this.withOptionalArg("world", "server.commands.worldthread.arg.desc", ArgTypes.WORLD); + @Nonnull + private final OptionalArg radiusArg = this.withOptionalArg("radius", "server.commands.entity.radius.desc", ArgTypes.DOUBLE) + .addValidator(Validators.greaterThan(0.0)); + @Nonnull + private final OptionalArg playerArg = this.withOptionalArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + @Nonnull + private final EntityWrappedArg entityArg = this.withOptionalArg("entity", "server.commands.entity.entity.desc", ArgTypes.ENTITY_ID); + + public AbstractTargetEntityCommand(@Nonnull String name, @Nonnull String description) { + super(name, description); + } + + public AbstractTargetEntityCommand(@Nonnull String name, @Nonnull String description, boolean requiresConfirmation) { + super(name, description, requiresConfirmation); + } + + public AbstractTargetEntityCommand(@Nonnull String description) { + super(description); + } + + @Nonnull + @Override + protected final CompletableFuture executeAsync(@Nonnull CommandContext context) { + World world; + if (this.worldArg.provided(context)) { + world = this.worldArg.get(context); + } else { + if (!context.isPlayer()) { + context.sendMessage(Message.translation("server.commands.errors.playerOrArg").param("option", "world")); + return CompletableFuture.completedFuture(null); + } + + Ref ref = context.senderAsPlayerRef(); + if (ref == null || !ref.isValid()) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + return CompletableFuture.completedFuture(null); + } + + world = ref.getStore().getExternalData().getWorld(); + } + + Store store = world.getEntityStore().getStore(); + return this.runAsync(context, () -> { + ObjectList> entitiesToOperateOn; + if (this.radiusArg.provided(context)) { + if (!context.isPlayer()) { + context.sendMessage(Message.translation("server.commands.errors.playerOrArg").param("option", "radius")); + return; + } + + Ref playerRef = context.senderAsPlayerRef(); + if (playerRef == null || !playerRef.isValid()) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + return; + } + + TransformComponent transformComponent = store.getComponent(playerRef, TransformComponent.getComponentType()); + if (transformComponent == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + return; + } + + double radius = this.radiusArg.get(context); + Vector3d position = transformComponent.getPosition(); + entitiesToOperateOn = new ObjectArrayList<>(); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + SpatialResource, EntityStore> entitySpatialResource = store.getResource(EntityModule.get().getEntitySpatialResourceType()); + entitySpatialResource.getSpatialStructure().collect(position, radius, results); + entitiesToOperateOn.addAll(results); + SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get().getPlayerSpatialResourceType()); + playerSpatialResource.getSpatialStructure().collect(position, radius, results); + entitiesToOperateOn.addAll(results); + } else if (this.playerArg.provided(context)) { + PlayerRef targetPlayerRef = this.playerArg.get(context); + Ref targetRef = targetPlayerRef.getReference(); + if (targetRef == null || !targetRef.isValid()) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + return; + } + + entitiesToOperateOn = ObjectLists.singleton(targetRef); + } else if (this.entityArg.provided(context)) { + Ref entityRef = this.entityArg.get(store, context); + if (entityRef == null || !entityRef.isValid()) { + context.sendMessage(MESSAGE_GENERAL_NO_ENTITY_IN_VIEW); + return; + } + + entitiesToOperateOn = ObjectLists.singleton(entityRef); + } else { + if (!context.isPlayer()) { + context.sendMessage(Message.translation("server.commands.errors.playerOrArg").param("option", "entity")); + return; + } + + Ref playerRefx = context.senderAsPlayerRef(); + if (playerRefx == null || !playerRefx.isValid()) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + return; + } + + Ref entityRef = TargetUtil.getTargetEntity(playerRefx, store); + if (entityRef == null) { + context.sendMessage(MESSAGE_GENERAL_NO_ENTITY_IN_VIEW); + return; + } + + entitiesToOperateOn = ObjectLists.singleton(entityRef); + } + + this.execute(context, entitiesToOperateOn, world, store); + }, world); + } + + protected abstract void execute( + @Nonnull CommandContext var1, @Nonnull ObjectList> var2, @Nonnull World var3, @Nonnull Store var4 + ); +} diff --git a/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractTargetPlayerCommand.java b/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractTargetPlayerCommand.java new file mode 100644 index 0000000..90d4144 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractTargetPlayerCommand.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.server.core.command.system.basecommands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class AbstractTargetPlayerCommand extends AbstractAsyncCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private final OptionalArg playerArg = this.withOptionalArg("player", "server.commands.argtype.player.desc", ArgTypes.PLAYER_REF); + + public AbstractTargetPlayerCommand(@Nonnull String name, @Nonnull String description) { + super(name, description); + } + + public AbstractTargetPlayerCommand(@Nonnull String name, @Nonnull String description, boolean requiresConfirmation) { + super(name, description, requiresConfirmation); + } + + public AbstractTargetPlayerCommand(@Nonnull String description) { + super(description); + } + + @Nonnull + @Override + protected final CompletableFuture executeAsync(@Nonnull CommandContext context) { + Ref sourceRef; + if (context.isPlayer()) { + sourceRef = context.senderAsPlayerRef(); + } else { + sourceRef = null; + } + + Ref targetRef; + if (this.playerArg.provided(context)) { + targetRef = this.playerArg.get(context).getReference(); + CommandUtil.requirePermission(context.sender(), HytalePermissions.fromCommand(this.getPermission() + ".other")); + } else { + if (!context.isPlayer()) { + context.sendMessage(Message.translation("server.commands.errors.playerOrArg").param("option", "player")); + return CompletableFuture.completedFuture(null); + } + + targetRef = context.senderAsPlayerRef(); + } + + if (targetRef != null && targetRef.isValid()) { + Store targetStore = targetRef.getStore(); + World targetWorld = targetStore.getExternalData().getWorld(); + return this.runAsync(context, () -> { + PlayerRef playerRefComponent = targetStore.getComponent(targetRef, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + this.execute(context, sourceRef, targetRef, playerRefComponent, targetWorld, targetStore); + }, targetWorld); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + return CompletableFuture.completedFuture(null); + } + } + + protected abstract void execute( + @Nonnull CommandContext var1, + @Nullable Ref var2, + @Nonnull Ref var3, + @Nonnull PlayerRef var4, + @Nonnull World var5, + @Nonnull Store var6 + ); +} diff --git a/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractWorldCommand.java b/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractWorldCommand.java new file mode 100644 index 0000000..4cef490 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/basecommands/AbstractWorldCommand.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.core.command.system.basecommands; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public abstract class AbstractWorldCommand extends AbstractAsyncCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_NO_WORLD = Message.translation("server.commands.errors.noWorld"); + @Nonnull + private final OptionalArg worldArg = this.withOptionalArg("world", "server.commands.worldthread.arg.desc", ArgTypes.WORLD); + + public AbstractWorldCommand(@Nonnull String name, @Nonnull String description) { + super(name, description); + } + + public AbstractWorldCommand(@Nonnull String name, @Nonnull String description, boolean requiresConfirmation) { + super(name, description, requiresConfirmation); + } + + public AbstractWorldCommand(@Nonnull String description) { + super(description); + } + + @Nonnull + @Override + protected final CompletableFuture executeAsync(@Nonnull CommandContext context) { + World world = this.worldArg.getProcessed(context); + if (world == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_NO_WORLD); + return CompletableFuture.completedFuture(null); + } else { + Store store = world.getEntityStore().getStore(); + return this.runAsync(context, () -> this.execute(context, world, store), world); + } + } + + protected abstract void execute(@Nonnull CommandContext var1, @Nonnull World var2, @Nonnull Store var3); +} diff --git a/src/com/hypixel/hytale/server/core/command/system/basecommands/CommandBase.java b/src/com/hypixel/hytale/server/core/command/system/basecommands/CommandBase.java new file mode 100644 index 0000000..cdb9c1b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/basecommands/CommandBase.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.core.command.system.basecommands; + +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class CommandBase extends AbstractCommand { + public CommandBase(@Nonnull String name, @Nonnull String description) { + super(name, description); + } + + public CommandBase(@Nonnull String name, @Nonnull String description, boolean requiresConfirmation) { + super(name, description, requiresConfirmation); + } + + public CommandBase(@Nonnull String description) { + super(description); + } + + @Nullable + @Override + protected final CompletableFuture execute(@Nonnull CommandContext context) { + this.executeSync(context); + return null; + } + + protected abstract void executeSync(@Nonnull CommandContext var1); +} diff --git a/src/com/hypixel/hytale/server/core/command/system/exceptions/CommandException.java b/src/com/hypixel/hytale/server/core/command/system/exceptions/CommandException.java new file mode 100644 index 0000000..5d3f381 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/exceptions/CommandException.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.command.system.exceptions; + +import com.hypixel.hytale.server.core.command.system.CommandSender; +import javax.annotation.Nonnull; + +public abstract class CommandException extends RuntimeException { + public CommandException() { + } + + public abstract void sendTranslatedMessage(@Nonnull CommandSender var1); +} diff --git a/src/com/hypixel/hytale/server/core/command/system/exceptions/GeneralCommandException.java b/src/com/hypixel/hytale/server/core/command/system/exceptions/GeneralCommandException.java new file mode 100644 index 0000000..2c529e7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/exceptions/GeneralCommandException.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.command.system.exceptions; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import java.awt.Color; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class GeneralCommandException extends CommandException { + @Nonnull + private final Message message; + + public GeneralCommandException(@Nonnull Message message) { + this.message = message.color(Color.RED); + } + + @Override + public void sendTranslatedMessage(@Nonnull CommandSender sender) { + sender.sendMessage(this.message); + } + + @Nullable + public String getMessageText() { + try { + return this.message.getAnsiMessage(); + } catch (Exception var3) { + String rawText = this.message.getRawText(); + return rawText != null ? rawText : this.message.getMessageId(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/exceptions/NoPermissionException.java b/src/com/hypixel/hytale/server/core/command/system/exceptions/NoPermissionException.java new file mode 100644 index 0000000..95b5384 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/exceptions/NoPermissionException.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.server.core.command.system.exceptions; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import java.awt.Color; +import javax.annotation.Nonnull; + +public class NoPermissionException extends CommandException { + @Nonnull + private final String permission; + + public NoPermissionException(@Nonnull String permission) { + this.permission = permission; + } + + @Override + public void sendTranslatedMessage(@Nonnull CommandSender sender) { + sender.sendMessage(Message.translation("server.commands.errors.permission").param("permission", this.permission).color(Color.RED)); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/exceptions/SenderTypeException.java b/src/com/hypixel/hytale/server/core/command/system/exceptions/SenderTypeException.java new file mode 100644 index 0000000..df6f501 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/exceptions/SenderTypeException.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.server.core.command.system.exceptions; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import java.awt.Color; +import javax.annotation.Nonnull; + +public class SenderTypeException extends CommandException { + @Nonnull + private final Class senderType; + + public SenderTypeException(@Nonnull Class senderType) { + this.senderType = senderType; + } + + @Override + public void sendTranslatedMessage(@Nonnull CommandSender sender) { + sender.sendMessage(Message.translation("server.commands.errors.sender").param("sender", this.senderType.getSimpleName()).color(Color.RED)); + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/pages/CommandListPage.java b/src/com/hypixel/hytale/server/core/command/system/pages/CommandListPage.java new file mode 100644 index 0000000..5119699 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/pages/CommandListPage.java @@ -0,0 +1,734 @@ +package com.hypixel.hytale.server.core.command.system.pages; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.OpenChatWithCommand; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import com.hypixel.hytale.server.core.command.system.CommandManager; +import com.hypixel.hytale.server.core.command.system.MatchResult; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgumentType; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.ui.Value; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CommandListPage extends InteractiveCustomUIPage { + private static final Value BUTTON_LABEL_STYLE = Value.ref("Pages/BasicTextButton.ui", "LabelStyle"); + private static final Value BUTTON_LABEL_STYLE_SELECTED = Value.ref("Pages/BasicTextButton.ui", "SelectedLabelStyle"); + private final List visibleCommands = new ObjectArrayList<>(); + @Nonnull + private String searchQuery = ""; + private String selectedCommand; + private String selectedSubcommand; + private Integer selectedVariantIndex; + private final List subcommandBreadcrumb = new ObjectArrayList<>(); + @Nullable + private final String initialCommand; + + public CommandListPage(@Nonnull PlayerRef playerRef) { + this(playerRef, null); + } + + public CommandListPage(@Nonnull PlayerRef playerRef, @Nullable String initialCommand) { + super(playerRef, CustomPageLifetime.CanDismiss, CommandListPage.CommandListPageEventData.CODEC); + this.initialCommand = initialCommand; + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/CommandListPage.ui"); + eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#SearchInput", EventData.of("@SearchQuery", "#SearchInput.Value"), false); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#BackButton", EventData.of("NavigateUp", "true")); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#SendToChatButton", EventData.of("SendToChat", "true")); + this.buildCommandList(ref, commandBuilder, eventBuilder, store); + String commandToSelect = this.visibleCommands.getFirst(); + if (this.initialCommand != null && this.visibleCommands.contains(this.initialCommand)) { + commandToSelect = this.initialCommand; + } + + this.selectCommand(ref, commandToSelect, commandBuilder, eventBuilder, store); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull CommandListPage.CommandListPageEventData data) { + if (data.searchQuery != null) { + this.searchQuery = data.searchQuery.trim().toLowerCase(); + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.buildCommandList(ref, commandBuilder, eventBuilder, store); + this.sendUpdate(commandBuilder, eventBuilder, false); + } else if (data.command != null) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.selectCommand(ref, data.command, commandBuilder, eventBuilder, store); + this.sendUpdate(commandBuilder, eventBuilder, false); + } else if (data.navigateUp != null) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.navigateUp(ref, commandBuilder, eventBuilder, store); + this.sendUpdate(commandBuilder, eventBuilder, false); + } else if (data.subcommand != null) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.selectSubcommand(ref, data.subcommand, commandBuilder, eventBuilder, store); + this.sendUpdate(commandBuilder, eventBuilder, false); + } else if (data.variantIndex != null) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + + try { + int variantIdx = Integer.parseInt(data.variantIndex); + this.selectVariant(ref, variantIdx, commandBuilder, eventBuilder, store); + this.sendUpdate(commandBuilder, eventBuilder, false); + } catch (NumberFormatException var7) { + } + } else if (data.sendToChat != null) { + this.handleSendToChat(ref, store); + } + } + + private void handleSendToChat(@Nonnull Ref ref, @Nonnull Store store) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + String command = this.buildCurrentCommandString(); + playerComponent.getPageManager().setPage(ref, store, Page.None); + this.playerRef.getPacketHandler().write(new OpenChatWithCommand(command)); + } + } + + @Nonnull + private String buildCurrentCommandString() { + StringBuilder sb = new StringBuilder("/"); + sb.append(this.selectedCommand); + + for (String part : this.subcommandBreadcrumb) { + sb.append(" ").append(part); + } + + AbstractCommand currentContext = CommandManager.get().getCommandRegistration().get(this.selectedCommand); + if (currentContext != null) { + for (String part : this.subcommandBreadcrumb) { + Map subcommands = currentContext.getSubCommands(); + currentContext = subcommands.get(part); + if (currentContext == null) { + break; + } + } + + if (currentContext != null) { + for (RequiredArg arg : currentContext.getRequiredArguments()) { + sb.append(" <").append(arg.getName()).append(">"); + } + } + } + + return sb.toString(); + } + + private void buildCommandList( + @Nonnull Ref ref, + @Nonnull UICommandBuilder commandBuilder, + @Nonnull UIEventBuilder eventBuilder, + @Nonnull ComponentAccessor componentAccessor + ) { + commandBuilder.clear("#CommandList"); + Map commands = new Object2ObjectOpenHashMap<>(CommandManager.get().getCommandRegistration()); + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + commands.values().removeIf(commandx -> !commandx.hasPermission(playerComponent)); + if (this.searchQuery.isEmpty()) { + this.visibleCommands.clear(); + this.visibleCommands.addAll(commands.keySet()); + Collections.sort(this.visibleCommands); + } else { + ObjectArrayList results = new ObjectArrayList<>(); + + for (Entry entry : commands.entrySet()) { + if (entry.getValue() != null) { + results.add(new CommandListPage.SearchResult(entry.getKey(), MatchResult.EXACT)); + } + } + + String[] terms = this.searchQuery.split(" "); + + for (int termIndex = 0; termIndex < terms.length; termIndex++) { + String term = terms[termIndex]; + + for (int cmdIndex = results.size() - 1; cmdIndex >= 0; cmdIndex--) { + CommandListPage.SearchResult result = results.get(cmdIndex); + AbstractCommand command = commands.get(result.name); + MatchResult match; + if (command != null) { + match = command.matches(this.playerRef.getLanguage(), term, termIndex); + } else { + match = MatchResult.NONE; + } + + if (match == MatchResult.NONE) { + results.remove(cmdIndex); + } else { + result.match = result.match.min(match); + } + } + } + + results.sort(CommandListPage.SearchResult.COMPARATOR); + this.visibleCommands.clear(); + + for (int i = 0; i < results.size(); i++) { + this.visibleCommands.add(results.get(i).name); + } + } + + for (int i = 0; i < this.visibleCommands.size(); i++) { + String name = this.visibleCommands.get(i); + commandBuilder.append("#CommandList", "Pages/BasicTextButton.ui"); + commandBuilder.set("#CommandList[" + i + "].TextSpans", Message.raw(name)); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#CommandList[" + i + "]", EventData.of("Command", name)); + if (name.equals(this.selectedCommand)) { + commandBuilder.set("#CommandList[" + i + "].Style", BUTTON_LABEL_STYLE_SELECTED); + } + } + } + + private void selectCommand( + @Nonnull Ref ref, + @Nonnull String commandName, + @Nonnull UICommandBuilder commandBuilder, + @Nonnull UIEventBuilder eventBuilder, + @Nonnull ComponentAccessor componentAccessor + ) { + AbstractCommand command = CommandManager.get().getCommandRegistration().get(commandName); + if (command == null) { + throw new IllegalArgumentException("Unknown command: " + commandName); + } else { + commandBuilder.set("#CommandName.TextSpans", Message.raw(commandName)); + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + commandBuilder.set("#CommandDescription.TextSpans", Message.translation(command.getDescription())); + this.selectedSubcommand = null; + this.selectedVariantIndex = null; + this.subcommandBreadcrumb.clear(); + this.buildSubcommandTabs(command, playerComponent, commandBuilder, eventBuilder); + this.displayCommandInfo(command, playerComponent, commandBuilder, eventBuilder); + if (this.selectedCommand != null && this.visibleCommands.contains(this.selectedCommand)) { + commandBuilder.set("#CommandList[" + this.visibleCommands.indexOf(this.selectedCommand) + "].Style", BUTTON_LABEL_STYLE); + } + + commandBuilder.set("#CommandList[" + this.visibleCommands.indexOf(commandName) + "].Style", BUTTON_LABEL_STYLE_SELECTED); + this.selectedCommand = commandName; + } + } + + private void selectSubcommand( + @Nonnull Ref ref, + @Nonnull String subcommandName, + @Nonnull UICommandBuilder commandBuilder, + @Nonnull UIEventBuilder eventBuilder, + @Nonnull ComponentAccessor componentAccessor + ) { + AbstractCommand currentContext = CommandManager.get().getCommandRegistration().get(this.selectedCommand); + if (currentContext != null) { + for (String breadcrumbPart : this.subcommandBreadcrumb) { + Map subcommands = currentContext.getSubCommands(); + currentContext = subcommands.get(breadcrumbPart); + if (currentContext == null) { + return; + } + } + + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + Map subcommands = currentContext.getSubCommands(); + AbstractCommand subcommand = subcommands.get(subcommandName); + if (subcommand != null) { + this.subcommandBreadcrumb.add(subcommandName); + this.selectedSubcommand = subcommandName; + this.selectedVariantIndex = null; + this.updateTitleWithBreadcrumb(commandBuilder); + commandBuilder.set("#CommandDescription.TextSpans", Message.translation(subcommand.getDescription())); + this.buildSubcommandTabs(subcommand, playerComponent, commandBuilder, eventBuilder); + commandBuilder.set("#BackButton.Visible", true); + this.displayCommandInfo(subcommand, playerComponent, commandBuilder, eventBuilder); + } + } + } + + private void selectVariant( + @Nonnull Ref ref, + int variantIndex, + @Nonnull UICommandBuilder commandBuilder, + @Nonnull UIEventBuilder eventBuilder, + @Nonnull ComponentAccessor componentAccessor + ) { + AbstractCommand currentContext = CommandManager.get().getCommandRegistration().get(this.selectedCommand); + if (currentContext != null) { + for (String breadcrumbPart : this.subcommandBreadcrumb) { + Map subcommands = currentContext.getSubCommands(); + currentContext = subcommands.get(breadcrumbPart); + if (currentContext == null) { + return; + } + } + + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + try { + Field variantsField = AbstractCommand.class.getDeclaredField("variantCommands"); + variantsField.setAccessible(true); + Int2ObjectMap variants = (Int2ObjectMap)variantsField.get(currentContext); + AbstractCommand variant = variants.get(variantIndex); + if (variant == null || !variant.hasPermission(playerComponent)) { + return; + } + + this.selectedVariantIndex = variantIndex; + this.updateTitleWithVariantSuffix(commandBuilder); + commandBuilder.set("#VariantsSection.Visible", false); + commandBuilder.set("#BackButton.Visible", true); + commandBuilder.set("#CommandUsageLabel.TextSpans", this.getSimplifiedUsage(variant, playerComponent)); + this.buildParametersSection(variant, playerComponent, commandBuilder); + this.buildArgumentTypesSection(variant, playerComponent, commandBuilder); + } catch (Exception var11) { + } + } + } + + private void navigateUp( + @Nonnull Ref ref, + @Nonnull UICommandBuilder commandBuilder, + @Nonnull UIEventBuilder eventBuilder, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.selectedVariantIndex != null) { + this.selectedVariantIndex = null; + AbstractCommand currentContext = CommandManager.get().getCommandRegistration().get(this.selectedCommand); + if (currentContext != null) { + for (String breadcrumbPart : this.subcommandBreadcrumb) { + Map subcommands = currentContext.getSubCommands(); + currentContext = subcommands.get(breadcrumbPart); + if (currentContext == null) { + return; + } + } + + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + this.updateTitleWithBreadcrumb(commandBuilder); + this.displayCommandInfo(currentContext, playerComponent, commandBuilder, eventBuilder); + commandBuilder.set("#BackButton.Visible", !this.subcommandBreadcrumb.isEmpty()); + } + } else if (!this.subcommandBreadcrumb.isEmpty()) { + this.subcommandBreadcrumb.remove(this.subcommandBreadcrumb.size() - 1); + AbstractCommand currentContext = CommandManager.get().getCommandRegistration().get(this.selectedCommand); + if (currentContext != null) { + for (String breadcrumbPartx : this.subcommandBreadcrumb) { + Map subcommands = currentContext.getSubCommands(); + currentContext = subcommands.get(breadcrumbPartx); + if (currentContext == null) { + return; + } + } + + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + this.selectedSubcommand = this.subcommandBreadcrumb.isEmpty() ? null : this.subcommandBreadcrumb.get(this.subcommandBreadcrumb.size() - 1); + this.updateTitleWithBreadcrumb(commandBuilder); + this.buildSubcommandTabs(currentContext, playerComponent, commandBuilder, eventBuilder); + this.displayCommandInfo(currentContext, playerComponent, commandBuilder, eventBuilder); + } + } + } + + private void buildSubcommandTabs( + @Nonnull AbstractCommand command, @Nonnull Player playerComponent, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder + ) { + commandBuilder.clear("#SubcommandCards"); + Map subcommands = command.getSubCommands(); + if (subcommands.isEmpty()) { + commandBuilder.set("#SubcommandSection.Visible", false); + } else { + commandBuilder.set("#SubcommandSection.Visible", true); + int cardIndex = 0; + int rowIndex = 0; + int cardsInCurrentRow = 0; + + for (Entry entry : subcommands.entrySet()) { + AbstractCommand subcommand = entry.getValue(); + if (subcommand.hasPermission(playerComponent)) { + if (cardsInCurrentRow == 0) { + commandBuilder.appendInline("#SubcommandCards", "Group { LayoutMode: Left; Anchor: (Bottom: 0); }"); + } + + commandBuilder.append("#SubcommandCards[" + rowIndex + "]", "Pages/SubcommandCard.ui"); + commandBuilder.set("#SubcommandCards[" + rowIndex + "][" + cardsInCurrentRow + "] #SubcommandName.TextSpans", Message.raw(entry.getKey())); + commandBuilder.set( + "#SubcommandCards[" + rowIndex + "][" + cardsInCurrentRow + "] #SubcommandUsage.TextSpans", + this.getSimplifiedUsage(subcommand, playerComponent) + ); + commandBuilder.set( + "#SubcommandCards[" + rowIndex + "][" + cardsInCurrentRow + "] #SubcommandDescription.TextSpans", + Message.translation(subcommand.getDescription()) + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#SubcommandCards[" + rowIndex + "][" + cardsInCurrentRow + "]", + EventData.of("Subcommand", entry.getKey()) + ); + cardsInCurrentRow++; + cardIndex++; + if (cardsInCurrentRow >= 3) { + cardsInCurrentRow = 0; + rowIndex++; + } + } + } + } + + commandBuilder.set("#BackButton.Visible", !this.subcommandBreadcrumb.isEmpty()); + } + + private void updateTitleWithBreadcrumb(@Nonnull UICommandBuilder commandBuilder) { + StringBuilder titleText = new StringBuilder(this.selectedCommand); + + for (String part : this.subcommandBreadcrumb) { + titleText.append(" > ").append(part); + } + + commandBuilder.set("#CommandName.TextSpans", Message.raw(titleText.toString())); + } + + private void updateTitleWithVariantSuffix(@Nonnull UICommandBuilder commandBuilder) { + StringBuilder titleText = new StringBuilder(this.selectedCommand); + + for (String part : this.subcommandBreadcrumb) { + titleText.append(" > ").append(part); + } + + titleText.append(" [Variant]"); + commandBuilder.set("#CommandName.TextSpans", Message.raw(titleText.toString())); + } + + private void buildAliasesSection(@Nonnull AbstractCommand command, @Nonnull UICommandBuilder commandBuilder) { + Set aliases = command.getAliases(); + if (aliases != null && !aliases.isEmpty()) { + commandBuilder.set("#AliasesSection.Visible", true); + commandBuilder.set("#AliasesList.TextSpans", Message.raw(String.join(", ", aliases))); + } else { + commandBuilder.set("#AliasesSection.Visible", false); + } + } + + private void buildPermissionSection(@Nonnull AbstractCommand command, @Nonnull UICommandBuilder commandBuilder) { + String permission = command.getPermission(); + if (permission != null && !permission.isEmpty()) { + commandBuilder.set("#PermissionSection.Visible", true); + commandBuilder.set("#PermissionLabel.TextSpans", Message.raw(permission)); + } else { + commandBuilder.set("#PermissionSection.Visible", false); + } + } + + private void buildVariantsSection( + @Nonnull AbstractCommand command, @Nonnull Player playerComponent, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder + ) { + commandBuilder.clear("#VariantsList"); + + try { + Field variantsField = AbstractCommand.class.getDeclaredField("variantCommands"); + variantsField.setAccessible(true); + Int2ObjectMap variants = (Int2ObjectMap)variantsField.get(command); + if (variants.isEmpty()) { + commandBuilder.set("#VariantsSection.Visible", false); + return; + } + + commandBuilder.set("#VariantsSection.Visible", true); + int displayIndex = 0; + + for (it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry entry : variants.int2ObjectEntrySet()) { + AbstractCommand variant = entry.getValue(); + int variantIndex = entry.getIntKey(); + if (variant.hasPermission(playerComponent)) { + commandBuilder.append("#VariantsList", "Pages/VariantCard.ui"); + commandBuilder.set("#VariantsList[" + displayIndex + "] #VariantUsage.TextSpans", this.getSimplifiedUsage(variant, playerComponent)); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, "#VariantsList[" + displayIndex + "]", EventData.of("Variant", String.valueOf(variantIndex)) + ); + displayIndex++; + } + } + + if (displayIndex == 0) { + commandBuilder.set("#VariantsSection.Visible", false); + } + } catch (Exception var12) { + commandBuilder.set("#VariantsSection.Visible", false); + } + } + + private void displayCommandInfo( + @Nonnull AbstractCommand command, @Nonnull Player playerComponent, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder + ) { + this.buildVariantsSection(command, playerComponent, commandBuilder, eventBuilder); + this.buildAliasesSection(command, commandBuilder); + this.buildPermissionSection(command, commandBuilder); + commandBuilder.set("#CommandUsageLabel.TextSpans", this.getSimplifiedUsage(command, playerComponent)); + this.buildParametersSection(command, playerComponent, commandBuilder); + this.buildArgumentTypesSection(command, playerComponent, commandBuilder); + } + + private Message getSimplifiedUsage(@Nonnull AbstractCommand command, @Nonnull Player playerComponent) { + Message message = Message.raw("/").insert(command.getFullyQualifiedName()); + + try { + Field requiredArgsField = AbstractCommand.class.getDeclaredField("requiredArguments"); + requiredArgsField.setAccessible(true); + + for (RequiredArg arg : (List)requiredArgsField.get(command)) { + message.insert(" <").insert(Message.translation(arg.getName())).insert(">"); + } + } catch (Exception var9) { + } + + try { + Field optionalArgsField = AbstractCommand.class.getDeclaredField("optionalArguments"); + optionalArgsField.setAccessible(true); + Map optionalArgs = (Map)optionalArgsField.get(command); + if (!optionalArgs.isEmpty()) { + message.insert(" [").insert(Message.translation("server.customUI.commandListPage.optionsIndicator")).insert("]"); + } + } catch (Exception var8) { + } + + return message; + } + + private void buildParametersSection(@Nonnull AbstractCommand command, @Nonnull Player playerComponent, @Nonnull UICommandBuilder commandBuilder) { + commandBuilder.clear("#RequiredArgumentsList"); + commandBuilder.clear("#OptionalArgumentsList"); + commandBuilder.clear("#DefaultArgumentsList"); + commandBuilder.clear("#FlagArgumentsList"); + boolean hasAnyParameters = false; + + try { + Field requiredArgsField = AbstractCommand.class.getDeclaredField("requiredArguments"); + requiredArgsField.setAccessible(true); + List> requiredArgs = (List>)requiredArgsField.get(command); + if (!requiredArgs.isEmpty()) { + hasAnyParameters = true; + + for (int i = 0; i < requiredArgs.size(); i++) { + RequiredArg arg = requiredArgs.get(i); + commandBuilder.append("#RequiredArgumentsList", "Pages/ParameterItem.ui"); + commandBuilder.set("#RequiredArgumentsList[" + i + "] #ParamName.TextSpans", Message.raw(arg.getName())); + commandBuilder.set("#RequiredArgumentsList[" + i + "] #ParamTag.TextSpans", Message.raw("[Required]")); + commandBuilder.set( + "#RequiredArgumentsList[" + i + "] #ParamType.TextSpans", + Message.translation("server.customUI.commandListPage.paramType").param("type", arg.getArgumentType().getName()) + ); + commandBuilder.set( + "#RequiredArgumentsList[" + i + "] #ParamDescription.TextSpans", + arg.getDescription() != null + ? Message.translation(arg.getDescription()) + : Message.translation("server.customUI.commandListPage.noDescription") + ); + } + } + } catch (Exception var17) { + } + + try { + Field optionalArgsField = AbstractCommand.class.getDeclaredField("optionalArguments"); + optionalArgsField.setAccessible(true); + Map optionalArgs = (Map)optionalArgsField.get(command); + if (!optionalArgs.isEmpty()) { + hasAnyParameters = true; + } + + int optIndex = 0; + int defIndex = 0; + int flagIndex = 0; + + for (Entry entry : optionalArgs.entrySet()) { + Object arg = entry.getValue(); + if (arg instanceof OptionalArg optArg) { + if (optArg.getPermission() == null || playerComponent.hasPermission(optArg.getPermission())) { + commandBuilder.append("#OptionalArgumentsList", "Pages/ParameterItem.ui"); + commandBuilder.set( + "#OptionalArgumentsList[" + optIndex + "] #ParamName.TextSpans", Message.raw("--" + optArg.getName() + " <" + optArg.getName() + ">") + ); + commandBuilder.set("#OptionalArgumentsList[" + optIndex + "] #ParamTag.TextSpans", Message.raw("[Optional]")); + commandBuilder.set( + "#OptionalArgumentsList[" + optIndex + "] #ParamType.TextSpans", + Message.translation("server.customUI.commandListPage.paramType").param("type", optArg.getArgumentType().getName()) + ); + commandBuilder.set( + "#OptionalArgumentsList[" + optIndex + "] #ParamDescription.TextSpans", + optArg.getDescription() != null + ? Message.translation(optArg.getDescription()) + : Message.translation("server.customUI.commandListPage.noDescription") + ); + optIndex++; + } + } else if (arg instanceof DefaultArg defArg) { + if (defArg.getPermission() == null || playerComponent.hasPermission(defArg.getPermission())) { + commandBuilder.append("#DefaultArgumentsList", "Pages/ParameterItem.ui"); + commandBuilder.set( + "#DefaultArgumentsList[" + defIndex + "] #ParamName.TextSpans", Message.raw("--" + defArg.getName() + " <" + defArg.getName() + ">") + ); + commandBuilder.set("#DefaultArgumentsList[" + defIndex + "] #ParamTag.TextSpans", Message.raw("[Default]")); + commandBuilder.set( + "#DefaultArgumentsList[" + defIndex + "] #ParamType.TextSpans", + Message.translation("server.customUI.commandListPage.paramTypeDefault") + .param("type", defArg.getArgumentType().getName()) + .param("default", defArg.getDefaultValueDescription()) + ); + commandBuilder.set( + "#DefaultArgumentsList[" + defIndex + "] #ParamDescription.TextSpans", + defArg.getDescription() != null + ? Message.translation(defArg.getDescription()) + : Message.translation("server.customUI.commandListPage.noDescription") + ); + defIndex++; + } + } else if (arg instanceof FlagArg flagArg && (flagArg.getPermission() == null || playerComponent.hasPermission(flagArg.getPermission()))) { + commandBuilder.append("#FlagArgumentsList", "Pages/ParameterItem.ui"); + commandBuilder.set("#FlagArgumentsList[" + flagIndex + "] #ParamName.TextSpans", Message.raw("--" + flagArg.getName())); + commandBuilder.set("#FlagArgumentsList[" + flagIndex + "] #ParamTag.TextSpans", Message.raw("[Flag]")); + commandBuilder.set( + "#FlagArgumentsList[" + flagIndex + "] #ParamType.TextSpans", Message.translation("server.customUI.commandListPage.paramTypeFlag") + ); + commandBuilder.set( + "#FlagArgumentsList[" + flagIndex + "] #ParamDescription.TextSpans", + flagArg.getDescription() != null + ? Message.translation(flagArg.getDescription()) + : Message.translation("server.customUI.commandListPage.noDescription") + ); + flagIndex++; + } + } + } catch (Exception var16) { + } + + commandBuilder.set("#ParametersSection.Visible", hasAnyParameters); + } + + private void buildArgumentTypesSection(@Nonnull AbstractCommand command, @Nonnull Player playerComponent, @Nonnull UICommandBuilder commandBuilder) { + commandBuilder.clear("#ArgumentTypesList"); + HashSet> allArgumentTypes = new HashSet<>(); + + try { + Field requiredArgsField = AbstractCommand.class.getDeclaredField("requiredArguments"); + requiredArgsField.setAccessible(true); + + for (RequiredArg arg : (List)requiredArgsField.get(command)) { + allArgumentTypes.add(arg.getArgumentType()); + } + + Field optionalArgsField = AbstractCommand.class.getDeclaredField("optionalArguments"); + optionalArgsField.setAccessible(true); + Map optionalArgs = (Map)optionalArgsField.get(command); + + for (Object entry : optionalArgs.values()) { + if (entry instanceof OptionalArg) { + allArgumentTypes.add(((OptionalArg)entry).getArgumentType()); + } else if (entry instanceof DefaultArg) { + allArgumentTypes.add(((DefaultArg)entry).getArgumentType()); + } + } + } catch (Exception var11) { + } + + if (allArgumentTypes.isEmpty()) { + commandBuilder.set("#ArgumentTypesSection.Visible", false); + } else { + commandBuilder.set("#ArgumentTypesSection.Visible", true); + int index = 0; + + for (ArgumentType argType : allArgumentTypes) { + commandBuilder.append("#ArgumentTypesList", "Pages/ArgumentTypeItem.ui"); + commandBuilder.set("#ArgumentTypesList[" + index + "] #TypeName.TextSpans", argType.getName()); + commandBuilder.set("#ArgumentTypesList[" + index + "] #TypeDescription.TextSpans", argType.getArgumentUsage()); + String[] examples = argType.getExamples(); + if (examples != null && examples.length > 0) { + commandBuilder.set( + "#ArgumentTypesList[" + index + "] #TypeExamples.TextSpans", + Message.translation("server.customUI.commandListPage.examples").param("examples", String.join("', '", examples)) + ); + } else { + commandBuilder.set("#ArgumentTypesList[" + index + "] #TypeExamples.Visible", false); + } + + index++; + } + } + } + + public static class CommandListPageEventData { + static final String KEY_COMMAND = "Command"; + static final String KEY_SUBCOMMAND = "Subcommand"; + static final String KEY_SEARCH_QUERY = "@SearchQuery"; + static final String KEY_NAVIGATE_UP = "NavigateUp"; + static final String KEY_VARIANT = "Variant"; + static final String KEY_SEND_TO_CHAT = "SendToChat"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + CommandListPage.CommandListPageEventData.class, CommandListPage.CommandListPageEventData::new + ) + .addField(new KeyedCodec<>("Command", Codec.STRING), (entry, s) -> entry.command = s, entry -> entry.command) + .addField(new KeyedCodec<>("Subcommand", Codec.STRING), (entry, s) -> entry.subcommand = s, entry -> entry.subcommand) + .addField(new KeyedCodec<>("@SearchQuery", Codec.STRING), (entry, s) -> entry.searchQuery = s, entry -> entry.searchQuery) + .addField(new KeyedCodec<>("NavigateUp", Codec.STRING), (entry, s) -> entry.navigateUp = s, entry -> entry.navigateUp) + .addField(new KeyedCodec<>("Variant", Codec.STRING), (entry, s) -> entry.variantIndex = s, entry -> entry.variantIndex) + .addField(new KeyedCodec<>("SendToChat", Codec.STRING), (entry, s) -> entry.sendToChat = s, entry -> entry.sendToChat) + .build(); + private String command; + private String subcommand; + private String searchQuery; + private String navigateUp; + private String variantIndex; + private String sendToChat; + + public CommandListPageEventData() { + } + } + + private static class SearchResult { + public static final Comparator COMPARATOR = Comparator.comparing(o -> o.match); + private final String name; + private MatchResult match; + + public SearchResult(String name, MatchResult match) { + this.name = name; + this.match = match; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/command/system/suggestion/SuggestionProvider.java b/src/com/hypixel/hytale/server/core/command/system/suggestion/SuggestionProvider.java new file mode 100644 index 0000000..b49c6a9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/suggestion/SuggestionProvider.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.server.core.command.system.suggestion; + +import com.hypixel.hytale.server.core.command.system.CommandSender; +import javax.annotation.Nonnull; + +@FunctionalInterface +public interface SuggestionProvider { + void suggest(@Nonnull CommandSender var1, @Nonnull String var2, int var3, @Nonnull SuggestionResult var4); +} diff --git a/src/com/hypixel/hytale/server/core/command/system/suggestion/SuggestionResult.java b/src/com/hypixel/hytale/server/core/command/system/suggestion/SuggestionResult.java new file mode 100644 index 0000000..336fb90 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/command/system/suggestion/SuggestionResult.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.server.core.command.system.suggestion; + +import com.hypixel.hytale.common.util.StringCompareUtil; +import it.unimi.dsi.fastutil.ints.IntObjectPair; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public class SuggestionResult { + private static final int FUZZY_SUGGESTION_MAX_RESULTS = 5; + @Nonnull + private static final Comparator> INTEGER_STRING_PAIR_COMPARATOR = Comparator.comparingInt(IntObjectPair::leftInt); + private final List suggestions = new ObjectArrayList<>(); + + public SuggestionResult() { + } + + @Nonnull + public SuggestionResult suggest(@Nonnull String suggestion) { + this.suggestions.add(suggestion); + return this; + } + + @Nonnull + public SuggestionResult suggest(@Nonnull Function toStringFunction, @Nonnull DataType suggestion) { + return this.suggest(toStringFunction.apply(suggestion)); + } + + @Nonnull + public SuggestionResult suggest(@Nonnull Object objectToString) { + return this.suggest(objectToString.toString()); + } + + @Nonnull + public List getSuggestions() { + return this.suggestions; + } + + @Nonnull + public SuggestionResult fuzzySuggest( + @Nonnull String input, @Nonnull Collection items, @Nonnull Function toStringFunction + ) { + List> sorting = new ObjectArrayList<>(5); + int lowestStoredFuzzyValue = Integer.MIN_VALUE; + + for (DataType item : items) { + String toString = toStringFunction.apply(item); + int fuzzyValue = StringCompareUtil.getFuzzyDistance(toString, input, Locale.ENGLISH); + if (sorting.size() == 5) { + if (fuzzyValue < lowestStoredFuzzyValue) { + continue; + } + + sorting.set(0, IntObjectPair.of(fuzzyValue, toString)); + } else { + sorting.add(IntObjectPair.of(fuzzyValue, toString)); + } + + sorting.sort(INTEGER_STRING_PAIR_COMPARATOR); + lowestStoredFuzzyValue = sorting.getFirst().leftInt(); + } + + for (IntObjectPair integerStringPair : sorting) { + this.suggest(integerStringPair.right()); + } + + return this; + } +} diff --git a/src/com/hypixel/hytale/server/core/console/ConsoleModule.java b/src/com/hypixel/hytale/server/core/console/ConsoleModule.java new file mode 100644 index 0000000..b127708 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/console/ConsoleModule.java @@ -0,0 +1,121 @@ +package com.hypixel.hytale.server.core.console; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.backend.HytaleConsole; +import com.hypixel.hytale.server.core.Constants; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.ShutdownReason; +import com.hypixel.hytale.server.core.command.system.CommandManager; +import com.hypixel.hytale.server.core.console.command.SayCommand; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import java.io.IOError; +import java.io.IOException; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import org.jline.reader.EndOfFileException; +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; +import org.jline.reader.UserInterruptException; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; + +public class ConsoleModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(ConsoleModule.class).build(); + private static ConsoleModule instance; + private Terminal terminal; + private ConsoleModule.ConsoleRunnable consoleRunnable; + + public static ConsoleModule get() { + return instance; + } + + public ConsoleModule(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + instance = this; + this.getCommandRegistry().registerCommand(new SayCommand()); + + try { + TerminalBuilder builder = TerminalBuilder.builder(); + if (Constants.SINGLEPLAYER) { + builder.dumb(true); + } else { + builder.color(true); + } + + this.terminal = builder.build(); + HytaleConsole.INSTANCE.setTerminal(this.terminal.getType()); + LineReader lineReader = LineReaderBuilder.builder().terminal(this.terminal).build(); + this.consoleRunnable = new ConsoleModule.ConsoleRunnable(lineReader, ConsoleSender.INSTANCE); + this.getLogger().at(Level.INFO).log("Setup console with type: %s", this.terminal.getType()); + } catch (IOException var3) { + this.getLogger().at(Level.SEVERE).withCause(var3).log("Failed to start console reader"); + } + } + + @Override + protected void shutdown() { + this.getLogger().at(Level.INFO).log("Restoring terminal..."); + + try { + this.terminal.close(); + } catch (IOException var2) { + HytaleLogger.getLogger().at(Level.SEVERE).withCause(var2).log("Failed to restore terminal!"); + } + + this.consoleRunnable.interrupt(); + } + + public Terminal getTerminal() { + return this.terminal; + } + + private static class ConsoleRunnable implements Runnable { + private final LineReader lineReader; + private final ConsoleSender consoleSender; + @Nonnull + private final Thread consoleThread; + + public ConsoleRunnable(LineReader lineReader, ConsoleSender consoleSender) { + this.lineReader = lineReader; + this.consoleSender = consoleSender; + this.consoleThread = new Thread(this, "ConsoleThread"); + this.consoleThread.setDaemon(true); + this.consoleThread.start(); + } + + @Override + public void run() { + try { + String terminalType = this.lineReader.getTerminal().getType(); + boolean isDumb = "dumb".equals(terminalType) || "dumb-color".equals(terminalType); + + while (!this.consoleThread.isInterrupted()) { + String command = this.lineReader.readLine(isDumb ? null : "> "); + if (command == null) { + break; + } + + command = command.trim(); + if (!command.isEmpty() && command.charAt(0) == '/') { + command = command.substring(1); + } + + CommandManager.get().handleCommand(this.consoleSender, command); + } + } catch (UserInterruptException var4) { + HytaleServer.get().shutdownServer(ShutdownReason.SIGINT); + } catch (IOError | EndOfFileException var5) { + } + } + + public void interrupt() { + this.consoleThread.interrupt(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/console/ConsoleSender.java b/src/com/hypixel/hytale/server/core/console/ConsoleSender.java new file mode 100644 index 0000000..1f960ab --- /dev/null +++ b/src/com/hypixel/hytale/server/core/console/ConsoleSender.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.core.console; + +import com.hypixel.hytale.logger.backend.HytaleLoggerBackend; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.util.MessageUtil; +import java.util.UUID; +import javax.annotation.Nonnull; +import org.jline.terminal.Terminal; +import org.jline.utils.AttributedString; + +public class ConsoleSender implements CommandSender { + public static final ConsoleSender INSTANCE = new ConsoleSender(); + private final UUID uuid = new UUID(0L, 0L); + + protected ConsoleSender() { + } + + @Override + public void sendMessage(@Nonnull Message message) { + Terminal terminal = ConsoleModule.get().getTerminal(); + AttributedString attributedString = MessageUtil.toAnsiString(message); + HytaleLoggerBackend.rawLog(attributedString.toAnsi(terminal)); + } + + @Nonnull + @Override + public String getDisplayName() { + return "Console"; + } + + @Nonnull + @Override + public UUID getUuid() { + return this.uuid; + } + + @Override + public boolean hasPermission(@Nonnull String id) { + return true; + } + + @Override + public boolean hasPermission(@Nonnull String id, boolean def) { + return true; + } +} diff --git a/src/com/hypixel/hytale/server/core/console/command/SayCommand.java b/src/com/hypixel/hytale/server/core/console/command/SayCommand.java new file mode 100644 index 0000000..e8f5740 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/console/command/SayCommand.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.core.console.command; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.console.ConsoleSender; +import com.hypixel.hytale.server.core.universe.Universe; +import java.awt.Color; +import javax.annotation.Nonnull; + +public class SayCommand extends CommandBase { + @Nonnull + private static final Color SAY_COMMAND_COLOR = Color.CYAN; + + public SayCommand() { + super("say", "server.commands.say.desc"); + this.addAliases("broadcast"); + this.setAllowsExtraArguments(true); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + String rawArgs = CommandUtil.stripCommandName(context.getInputString()).trim(); + if (rawArgs.isEmpty()) { + context.sendMessage(Message.translation("server.commands.parsing.error.wrongNumberRequiredParameters").param("expected", 1).param("actual", 0)); + } else { + Message result; + if (rawArgs.charAt(0) == '{') { + try { + result = Message.parse(rawArgs).color(SAY_COMMAND_COLOR); + } catch (IllegalArgumentException var5) { + context.sendMessage(Message.raw("Failed to parse formatted message: " + var5.getMessage())); + return; + } + } else { + result = Message.translation("server.chat.broadcastMessage") + .param("username", context.sender().getDisplayName()) + .param("message", rawArgs) + .color(SAY_COMMAND_COLOR); + } + + Universe.get().getWorlds().values().forEach(world -> world.getPlayerRefs().forEach(playerRef -> playerRef.sendMessage(result))); + ConsoleSender.INSTANCE.sendMessage(result); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/cosmetics/BodyType.java b/src/com/hypixel/hytale/server/core/cosmetics/BodyType.java new file mode 100644 index 0000000..23cce87 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/cosmetics/BodyType.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.server.core.cosmetics; + +public enum BodyType { + Masculine, + Feminine; + + private BodyType() { + } +} diff --git a/src/com/hypixel/hytale/server/core/cosmetics/CosmeticAssetValidator.java b/src/com/hypixel/hytale/server/core/cosmetics/CosmeticAssetValidator.java new file mode 100644 index 0000000..44b3302 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/cosmetics/CosmeticAssetValidator.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.core.cosmetics; + +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validator; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CosmeticAssetValidator implements Validator { + private final CosmeticType type; + + public CosmeticAssetValidator(CosmeticType type) { + this.type = type; + } + + public void accept(@Nullable String asset, @Nonnull ValidationResults results) { + if (asset != null) { + CosmeticRegistry reg = CosmeticsModule.get().getRegistry(); + Map toCheck = reg.getByType(this.type); + if (!toCheck.containsKey(asset)) { + results.fail("Cosmetic Asset (" + this.type + ") '" + asset + "' doesn't exist!"); + } + } + } + + @Override + public void updateSchema(SchemaContext context, @Nonnull Schema target) { + ((StringSchema)target).setHytaleCosmeticAsset(EnumCodec.EnumStyle.LEGACY.formatCamelCase(this.type.name())); + } +} diff --git a/src/com/hypixel/hytale/server/core/cosmetics/CosmeticRegistry.java b/src/com/hypixel/hytale/server/core/cosmetics/CosmeticRegistry.java new file mode 100644 index 0000000..f3d95b0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/cosmetics/CosmeticRegistry.java @@ -0,0 +1,253 @@ +package com.hypixel.hytale.server.core.cosmetics; + +import com.hypixel.hytale.assetstore.AssetPack; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Map; +import java.util.function.Function; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class CosmeticRegistry { + public static final String MODEL = "Characters/Player.blockymodel"; + public static final String SKIN_GRADIENTSET_ID = "Skin"; + @Nonnull + private final Map emotes; + @Nonnull + private final Map eyeColors; + @Nonnull + private final Map gradientSets; + @Nonnull + private final Map bodyCharacteristics; + @Nonnull + private final Map underwear; + @Nonnull + private final Map eyebrows; + @Nonnull + private final Map ears; + @Nonnull + private final Map eyes; + @Nonnull + private final Map faces; + @Nonnull + private final Map mouths; + @Nonnull + private final Map facialHair; + @Nonnull + private final Map pants; + @Nonnull + private final Map overpants; + @Nonnull + private final Map undertops; + @Nonnull + private final Map overtops; + @Nonnull + private final Map haircuts; + @Nonnull + private final Map shoes; + @Nonnull + private final Map headAccessory; + @Nonnull + private final Map faceAccessory; + @Nonnull + private final Map earAccessory; + @Nonnull + private final Map gloves; + @Nonnull + private final Map capes; + @Nonnull + private final Map skinFeatures; + + public CosmeticRegistry(@Nonnull AssetPack pack) { + Path assetsDirectory = pack.getRoot(); + this.emotes = this.load(assetsDirectory, "Emotes.json", Emote::new); + this.eyeColors = this.load(assetsDirectory, "EyeColors.json", PlayerSkinTintColor::new); + this.gradientSets = this.load(assetsDirectory, "GradientSets.json", PlayerSkinGradientSet::new); + this.bodyCharacteristics = this.load(assetsDirectory, "BodyCharacteristics.json", PlayerSkinPart::new); + this.underwear = this.load(assetsDirectory, "Underwear.json", PlayerSkinPart::new); + this.eyes = this.load(assetsDirectory, "Eyes.json", PlayerSkinPart::new); + this.faces = this.load(assetsDirectory, "Faces.json", PlayerSkinPart::new); + this.eyebrows = this.load(assetsDirectory, "Eyebrows.json", PlayerSkinPart::new); + this.ears = this.load(assetsDirectory, "Ears.json", PlayerSkinPart::new); + this.mouths = this.load(assetsDirectory, "Mouths.json", PlayerSkinPart::new); + this.facialHair = this.load(assetsDirectory, "FacialHair.json", PlayerSkinPart::new); + this.pants = this.load(assetsDirectory, "Pants.json", PlayerSkinPart::new); + this.overpants = this.load(assetsDirectory, "Overpants.json", PlayerSkinPart::new); + this.undertops = this.load(assetsDirectory, "Undertops.json", PlayerSkinPart::new); + this.overtops = this.load(assetsDirectory, "Overtops.json", PlayerSkinPart::new); + this.haircuts = this.load(assetsDirectory, "Haircuts.json", PlayerSkinPart::new); + this.shoes = this.load(assetsDirectory, "Shoes.json", PlayerSkinPart::new); + this.headAccessory = this.load(assetsDirectory, "HeadAccessory.json", PlayerSkinPart::new); + this.faceAccessory = this.load(assetsDirectory, "FaceAccessory.json", PlayerSkinPart::new); + this.earAccessory = this.load(assetsDirectory, "EarAccessory.json", PlayerSkinPart::new); + this.gloves = this.load(assetsDirectory, "Gloves.json", PlayerSkinPart::new); + this.capes = this.load(assetsDirectory, "Capes.json", PlayerSkinPart::new); + this.skinFeatures = this.load(assetsDirectory, "SkinFeatures.json", PlayerSkinPart::new); + } + + @Nonnull + private Map load(@Nonnull Path assetsDirectory, @Nonnull String file, @Nonnull Function func) { + Map map = new Object2ObjectOpenHashMap<>(); + Path path = assetsDirectory.resolve("Cosmetics").resolve("CharacterCreator").resolve(file); + + try { + for (BsonValue bsonValue : BsonArray.parse(Files.readString(path))) { + BsonDocument doc = bsonValue.asDocument(); + map.put(doc.getString("Id").getValue(), func.apply(doc)); + } + } catch (IOException var10) { + var10.printStackTrace(); + } + + return Collections.unmodifiableMap(map); + } + + @Nonnull + public Map getEmotes() { + return this.emotes; + } + + @Nonnull + public Map getEyeColors() { + return this.eyeColors; + } + + @Nonnull + public Map getGradientSets() { + return this.gradientSets; + } + + @Nonnull + public Map getBodyCharacteristics() { + return this.bodyCharacteristics; + } + + @Nonnull + public Map getUnderwear() { + return this.underwear; + } + + @Nonnull + public Map getEyebrows() { + return this.eyebrows; + } + + @Nonnull + public Map getEars() { + return this.ears; + } + + @Nonnull + public Map getEyes() { + return this.eyes; + } + + @Nonnull + public Map getFaces() { + return this.faces; + } + + @Nonnull + public Map getMouths() { + return this.mouths; + } + + @Nonnull + public Map getFacialHairs() { + return this.facialHair; + } + + @Nonnull + public Map getPants() { + return this.pants; + } + + @Nonnull + public Map getOverpants() { + return this.overpants; + } + + @Nonnull + public Map getUndertops() { + return this.undertops; + } + + @Nonnull + public Map getOvertops() { + return this.overtops; + } + + @Nonnull + public Map getHaircuts() { + return this.haircuts; + } + + @Nonnull + public Map getShoes() { + return this.shoes; + } + + @Nonnull + public Map getHeadAccessories() { + return this.headAccessory; + } + + @Nonnull + public Map getFaceAccessories() { + return this.faceAccessory; + } + + @Nonnull + public Map getEarAccessories() { + return this.earAccessory; + } + + @Nonnull + public Map getGloves() { + return this.gloves; + } + + @Nonnull + public Map getSkinFeatures() { + return this.skinFeatures; + } + + @Nonnull + public Map getCapes() { + return this.capes; + } + + public Map getByType(@Nonnull CosmeticType type) { + return switch (type) { + case EMOTES -> this.getEmotes(); + case SKIN_TONES -> this.getGradientSets().get("Skin").getGradients(); + case EYE_COLORS -> this.getEyeColors(); + case GRADIENT_SETS -> this.getGradientSets(); + case BODY_CHARACTERISTICS -> this.getBodyCharacteristics(); + case UNDERWEAR -> this.getUnderwear(); + case EYEBROWS -> this.getEyebrows(); + case EARS -> this.getEars(); + case EYES -> this.getEyes(); + case FACE -> this.getFaces(); + case MOUTHS -> this.getMouths(); + case FACIAL_HAIR -> this.getFacialHairs(); + case PANTS -> this.getPants(); + case OVERPANTS -> this.getOverpants(); + case UNDERTOPS -> this.getUndertops(); + case OVERTOPS -> this.getOvertops(); + case HAIRCUTS -> this.getHaircuts(); + case SHOES -> this.getShoes(); + case HEAD_ACCESSORY -> this.getHeadAccessories(); + case FACE_ACCESSORY -> this.getFaceAccessories(); + case EAR_ACCESSORY -> this.getEarAccessories(); + case GLOVES -> this.getGloves(); + case CAPES -> this.getCapes(); + case SKIN_FEATURES -> this.getSkinFeatures(); + }; + } +} diff --git a/src/com/hypixel/hytale/server/core/cosmetics/CosmeticType.java b/src/com/hypixel/hytale/server/core/cosmetics/CosmeticType.java new file mode 100644 index 0000000..fff9f19 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/cosmetics/CosmeticType.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.cosmetics; + +public enum CosmeticType { + EMOTES, + SKIN_TONES, + EYE_COLORS, + GRADIENT_SETS, + BODY_CHARACTERISTICS, + UNDERWEAR, + EYEBROWS, + EARS, + EYES, + FACE, + MOUTHS, + FACIAL_HAIR, + PANTS, + OVERPANTS, + UNDERTOPS, + OVERTOPS, + HAIRCUTS, + SHOES, + HEAD_ACCESSORY, + FACE_ACCESSORY, + EAR_ACCESSORY, + GLOVES, + CAPES, + SKIN_FEATURES; + + private CosmeticType() { + } +} diff --git a/src/com/hypixel/hytale/server/core/cosmetics/CosmeticsModule.java b/src/com/hypixel/hytale/server/core/cosmetics/CosmeticsModule.java new file mode 100644 index 0000000..abd0538 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/cosmetics/CosmeticsModule.java @@ -0,0 +1,304 @@ +package com.hypixel.hytale.server.core.cosmetics; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.common.util.RandomUtil; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.asset.LoadAssetEvent; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.cosmetics.commands.EmoteCommand; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import java.util.Map; +import java.util.Random; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CosmeticsModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(CosmeticsModule.class).build(); + private static CosmeticsModule INSTANCE; + private CosmeticRegistry registry; + + public CosmeticsModule(@Nonnull JavaPluginInit init) { + super(init); + INSTANCE = this; + } + + @Override + protected void setup() { + this.registry = new CosmeticRegistry(AssetModule.get().getBaseAssetPack()); + this.getCommandRegistry().registerCommand(new EmoteCommand()); + if (Options.getOptionSet().has(Options.VALIDATE_ASSETS)) { + this.getEventRegistry().register((short)64, LoadAssetEvent.class, this::validateGeneratedSkin); + } + } + + public CosmeticRegistry getRegistry() { + return this.registry; + } + + private void validateGeneratedSkin(@Nonnull LoadAssetEvent eventType) { + for (int i = 0; i < 10; i++) { + com.hypixel.hytale.protocol.PlayerSkin skin = this.generateRandomSkin(new Random(i)); + + try { + this.validateSkin(skin); + } catch (CosmeticsModule.InvalidSkinException var5) { + eventType.failed(true, var5.getMessage()); + return; + } + } + } + + @Nullable + public Model createRandomModel(@Nonnull Random random) { + com.hypixel.hytale.protocol.PlayerSkin skin = this.generateRandomSkin(random); + return get().createModel(skin); + } + + @Nullable + public Model createModel(@Nonnull com.hypixel.hytale.protocol.PlayerSkin skin) { + return this.createModel(skin, 1.0F); + } + + @Nullable + public Model createModel(@Nonnull com.hypixel.hytale.protocol.PlayerSkin skin, float scale) { + try { + this.validateSkin(skin); + } catch (CosmeticsModule.InvalidSkinException var4) { + this.getLogger().at(Level.WARNING).withCause(var4).log("Was passed an invalid skin %s", skin); + return null; + } + + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset("Player"); + return Model.createScaledModel(modelAsset, scale, null); + } + + public void validateSkin(@Nonnull com.hypixel.hytale.protocol.PlayerSkin skin) throws CosmeticsModule.InvalidSkinException { + if (skin == null) { + throw new CosmeticsModule.InvalidSkinException("Skin can't be null!"); + } else if (skin.face == null || !this.registry.getFaces().containsKey(skin.face)) { + throw new CosmeticsModule.InvalidSkinException("Invalid face attachment!"); + } else if (skin.ears == null || !this.registry.getEars().containsKey(skin.ears)) { + throw new CosmeticsModule.InvalidSkinException("Invalid ears attachment!"); + } else if (skin.mouth == null || !this.registry.getMouths().containsKey(skin.mouth)) { + throw new CosmeticsModule.InvalidSkinException("Invalid mouth attachment!"); + } else if (!this.isValidAttachment(this.registry.getBodyCharacteristics(), skin.bodyCharacteristic, true)) { + throw new CosmeticsModule.InvalidSkinException("Invalid body characteristic!"); + } else if (!this.isValidAttachment(this.registry.getUnderwear(), skin.underwear, true)) { + throw new CosmeticsModule.InvalidSkinException("Invalid underwear attachment!"); + } else if (!this.isValidAttachment(this.registry.getEyes(), skin.eyes, true)) { + throw new CosmeticsModule.InvalidSkinException("Invalid eye attachment!"); + } else if (!this.isValidAttachment(this.registry.getSkinFeatures(), skin.skinFeature)) { + throw new CosmeticsModule.InvalidSkinException("Invalid skin feature attachment!"); + } else if (!this.isValidAttachment(this.registry.getEyebrows(), skin.eyebrows)) { + throw new CosmeticsModule.InvalidSkinException("Invalid eye brows attachment!"); + } else if (!this.isValidAttachment(this.registry.getPants(), skin.pants)) { + throw new CosmeticsModule.InvalidSkinException("Invalid pants attachment!"); + } else if (!this.isValidAttachment(this.registry.getOverpants(), skin.overpants)) { + throw new CosmeticsModule.InvalidSkinException("Invalid overpants attachment!"); + } else if (!this.isValidAttachment(this.registry.getShoes(), skin.shoes)) { + throw new CosmeticsModule.InvalidSkinException("Invalid shoes attachment!"); + } else if (!this.isValidAttachment(this.registry.getUndertops(), skin.undertop)) { + throw new CosmeticsModule.InvalidSkinException("Invalid under top attachment!"); + } else if (!this.isValidAttachment(this.registry.getOvertops(), skin.overtop)) { + throw new CosmeticsModule.InvalidSkinException("Invalid over top attachment!"); + } else if (!this.isValidAttachment(this.registry.getGloves(), skin.gloves)) { + throw new CosmeticsModule.InvalidSkinException("Invalid gloves attachment!"); + } else if (!this.isValidAttachment(this.registry.getHeadAccessories(), skin.headAccessory)) { + throw new CosmeticsModule.InvalidSkinException("Invalid head accessory attachment!"); + } else if (!this.isValidAttachment(this.registry.getFaceAccessories(), skin.faceAccessory)) { + throw new CosmeticsModule.InvalidSkinException("Invalid face accessory attachment!"); + } else if (!this.isValidAttachment(this.registry.getEarAccessories(), skin.earAccessory)) { + throw new CosmeticsModule.InvalidSkinException("Invalid ear accessory attachment!"); + } else if (!this.isValidHaircutAttachment(skin.haircut, skin.headAccessory)) { + throw new CosmeticsModule.InvalidSkinException("Invalid haircut attachment!"); + } else if (!this.isValidAttachment(this.registry.getFacialHairs(), skin.facialHair)) { + throw new CosmeticsModule.InvalidSkinException("Invalid facial accessory attachment!"); + } else if (!this.isValidAttachment(this.registry.getCapes(), skin.cape)) { + throw new CosmeticsModule.InvalidSkinException("Invalid capes attachment!"); + } + } + + private boolean isValidAttachment(@Nonnull Map map, String id) { + return this.isValidAttachment(map, id, false); + } + + private boolean isValidTexture(@Nonnull PlayerSkinPart part, String variantId, String textureId) { + if (part.getGradientSet() != null && this.registry.getGradientSets().get(part.getGradientSet()).getGradients().containsKey(textureId)) { + return true; + } else { + return part.getVariants() != null ? part.getVariants().get(variantId).getTextures().containsKey(textureId) : part.getTextures().containsKey(textureId); + } + } + + private boolean isValidAttachment(@Nonnull Map map, @Nullable String id, boolean required) { + if (id == null) { + return !required; + } else { + String[] idParts = id.split("\\."); + PlayerSkinPart skinPart = map.get(idParts[0]); + if (skinPart == null) { + return false; + } else { + String variantId = idParts.length > 2 && !idParts[2].isEmpty() ? idParts[2] : null; + return skinPart.getVariants() != null && !skinPart.getVariants().containsKey(variantId) + ? false + : this.isValidTexture(skinPart, variantId, idParts[1]); + } + } + } + + private boolean isValidHaircutAttachment(@Nullable String haircutId, @Nullable String headAccessoryId) { + if (haircutId == null) { + return true; + } else { + Map haircuts = this.registry.getHaircuts(); + String[] idParts = haircutId.split("\\."); + String haircutAssetId = idParts[0]; + String haircutAssetTextureId = idParts.length > 1 && !idParts[1].isEmpty() ? idParts[1] : null; + if (headAccessoryId != null) { + idParts = headAccessoryId.split("\\."); + String headAccessoryAssetId = idParts[0]; + PlayerSkinPart headAccessoryPart = this.registry.getHeadAccessories().get(headAccessoryAssetId); + if (headAccessoryPart != null) { + switch (headAccessoryPart.getHeadAccessoryType()) { + case HalfCovering: + PlayerSkinPart haircutPart = haircuts.get(haircutAssetId); + if (haircutPart == null) { + return false; + } + + if (haircutPart.doesRequireGenericHaircut()) { + PlayerSkinPart baseHaircutPart = haircuts.get("Generic" + haircutPart.getHairType()); + return this.isValidAttachment(haircuts, baseHaircutPart.getId() + "." + haircutAssetTextureId, false); + } + break; + case FullyCovering: + return this.isValidAttachment(haircuts, haircutId); + } + } + } + + return this.isValidAttachment(haircuts, haircutId); + } + } + + public static CosmeticsModule get() { + return INSTANCE; + } + + @Nonnull + public com.hypixel.hytale.protocol.PlayerSkin generateRandomSkin(@Nonnull Random random) { + String bodyCharacteristic = this.randomSkinPart(this.registry.getBodyCharacteristics(), true, random); + String underwear = this.randomSkinPart(this.registry.getUnderwear(), true, random); + String face = this.randomSkinPart(this.registry.getFaces(), true, false, random); + String ears = this.randomSkinPart(this.registry.getEars(), true, false, random); + String mouth = this.randomSkinPart(this.registry.getMouths(), true, false, random); + String eyes = this.randomSkinPart(this.registry.getEyes(), true, random); + String facialHair = null; + if (random.nextInt(10) > 4) { + facialHair = this.randomSkinPart(this.registry.getFacialHairs(), random); + } + + String haircut = this.randomSkinPart(this.registry.getHaircuts(), random); + String eyebrows = this.randomSkinPart(this.registry.getEyebrows(), random); + String pants = this.randomSkinPart(this.registry.getPants(), random); + String overpants = null; + String undertop = this.randomSkinPart(this.registry.getUndertops(), random); + String overtop = this.randomSkinPart(this.registry.getOvertops(), random); + String shoes = this.randomSkinPart(this.registry.getShoes(), random); + String headAccessory = null; + if (random.nextInt(10) > 8) { + headAccessory = this.randomSkinPart(this.registry.getHeadAccessories(), random); + } + + String faceAccessory = null; + if (random.nextInt(10) > 8) { + faceAccessory = this.randomSkinPart(this.registry.getFaceAccessories(), random); + } + + String earAccessory = null; + if (random.nextInt(10) > 8) { + earAccessory = this.randomSkinPart(this.registry.getEarAccessories(), random); + } + + String skinFeature = null; + if (random.nextInt(10) > 8) { + skinFeature = this.randomSkinPart(this.registry.getSkinFeatures(), random); + } + + String gloves = null; + return new com.hypixel.hytale.protocol.PlayerSkin( + bodyCharacteristic, + underwear, + face, + eyes, + ears, + mouth, + facialHair, + haircut, + eyebrows, + pants, + overpants, + undertop, + overtop, + shoes, + headAccessory, + faceAccessory, + earAccessory, + skinFeature, + gloves, + null + ); + } + + @Nullable + private String randomSkinPart(@Nonnull Map map, @Nonnull Random random) { + return this.randomSkinPart(map, false, random); + } + + @Nullable + private String randomSkinPart(@Nonnull Map map, boolean required, @Nonnull Random random) { + return this.randomSkinPart(map, required, true, random); + } + + @Nullable + private String randomSkinPart(@Nonnull Map map, boolean required, boolean color, @Nonnull Random random) { + PlayerSkinPart[] arr = map.values().toArray(PlayerSkinPart[]::new); + PlayerSkinPart part = required ? RandomUtil.selectRandom(arr, random) : RandomUtil.selectRandomOrNull(arr, random); + if (part == null) { + return null; + } else if (!color) { + return part.getId(); + } else { + String[] colors = ArrayUtil.EMPTY_STRING_ARRAY; + if (part.getGradientSet() != null) { + colors = this.registry.getGradientSets().get(part.getGradientSet()).getGradients().keySet().toArray(String[]::new); + } + + Map textures = part.getTextures(); + String variantId = null; + if (part.getVariants() != null) { + variantId = RandomUtil.selectRandom(part.getVariants().keySet().toArray(String[]::new), random); + textures = part.getVariants().get(variantId).getTextures(); + } + + if (textures != null) { + colors = ArrayUtil.combine(colors, textures.keySet().toArray(String[]::new)); + } + + String colorId = RandomUtil.selectRandom(colors, random); + return variantId == null ? part.getId() + "." + colorId : part.getId() + "." + colorId + "." + variantId; + } + } + + public static class InvalidSkinException extends Exception { + public InvalidSkinException(String message) { + super(message); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/cosmetics/Emote.java b/src/com/hypixel/hytale/server/core/cosmetics/Emote.java new file mode 100644 index 0000000..277d34e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/cosmetics/Emote.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.core.cosmetics; + +import javax.annotation.Nonnull; +import org.bson.BsonDocument; + +public class Emote { + protected String id; + protected String name; + protected String animation; + + protected Emote(@Nonnull BsonDocument bson) { + this.id = bson.getString("Id").getValue(); + this.name = bson.getString("Name").getValue(); + this.animation = bson.getString("Animation").getValue(); + } + + public String getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public String getAnimation() { + return this.animation; + } + + @Nonnull + @Override + public String toString() { + return "Emote{id='" + this.id + "', name='" + this.name + "', animation='" + this.animation + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkin.java b/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkin.java new file mode 100644 index 0000000..1c0cdec --- /dev/null +++ b/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkin.java @@ -0,0 +1,294 @@ +package com.hypixel.hytale.server.core.cosmetics; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class PlayerSkin { + private final PlayerSkin.PlayerSkinPartId bodyCharacteristic; + private final PlayerSkin.PlayerSkinPartId underwear; + private final String face; + private final String ears; + private final String mouth; + @Nullable + private final PlayerSkin.PlayerSkinPartId eyes; + @Nullable + private final PlayerSkin.PlayerSkinPartId facialHair; + @Nullable + private final PlayerSkin.PlayerSkinPartId haircut; + @Nullable + private final PlayerSkin.PlayerSkinPartId eyebrows; + @Nullable + private final PlayerSkin.PlayerSkinPartId pants; + @Nullable + private final PlayerSkin.PlayerSkinPartId overpants; + @Nullable + private final PlayerSkin.PlayerSkinPartId undertop; + @Nullable + private final PlayerSkin.PlayerSkinPartId overtop; + @Nullable + private final PlayerSkin.PlayerSkinPartId shoes; + @Nullable + private final PlayerSkin.PlayerSkinPartId headAccessory; + @Nullable + private final PlayerSkin.PlayerSkinPartId faceAccessory; + @Nullable + private final PlayerSkin.PlayerSkinPartId earAccessory; + @Nullable + private final PlayerSkin.PlayerSkinPartId skinFeature; + @Nullable + private final PlayerSkin.PlayerSkinPartId gloves; + @Nullable + private final PlayerSkin.PlayerSkinPartId cape; + + public PlayerSkin(@Nonnull BsonDocument doc) { + this.bodyCharacteristic = getId(doc, "bodyCharacteristic"); + this.underwear = getId(doc, "underwear"); + this.face = doc.getString("face").getValue(); + this.ears = doc.getString("ears").getValue(); + this.mouth = doc.getString("mouth").getValue(); + this.eyes = PlayerSkin.PlayerSkinPartId.fromString(doc.getString("eyes").getValue()); + this.facialHair = getId(doc, "facialHair"); + this.haircut = getId(doc, "haircut"); + this.eyebrows = getId(doc, "eyebrows"); + this.pants = getId(doc, "pants"); + this.overpants = getId(doc, "overpants"); + this.undertop = getId(doc, "undertop"); + this.overtop = getId(doc, "overtop"); + this.shoes = getId(doc, "shoes"); + this.headAccessory = getId(doc, "headAccessory"); + this.faceAccessory = getId(doc, "faceAccessory"); + this.earAccessory = getId(doc, "earAccessory"); + this.skinFeature = getId(doc, "skinFeature"); + this.gloves = getId(doc, "gloves"); + this.cape = getId(doc, "cape"); + } + + public PlayerSkin( + PlayerSkin.PlayerSkinPartId bodyCharacteristic, + PlayerSkin.PlayerSkinPartId underwear, + String face, + String ears, + String mouth, + PlayerSkin.PlayerSkinPartId eyes, + PlayerSkin.PlayerSkinPartId facialHair, + PlayerSkin.PlayerSkinPartId haircut, + PlayerSkin.PlayerSkinPartId eyebrows, + PlayerSkin.PlayerSkinPartId pants, + PlayerSkin.PlayerSkinPartId overpants, + PlayerSkin.PlayerSkinPartId undertop, + PlayerSkin.PlayerSkinPartId overtop, + PlayerSkin.PlayerSkinPartId shoes, + PlayerSkin.PlayerSkinPartId headAccessory, + PlayerSkin.PlayerSkinPartId faceAccessory, + PlayerSkin.PlayerSkinPartId earAccessory, + PlayerSkin.PlayerSkinPartId skinFeature, + PlayerSkin.PlayerSkinPartId gloves, + PlayerSkin.PlayerSkinPartId cape + ) { + this.bodyCharacteristic = bodyCharacteristic; + this.underwear = underwear; + this.face = face; + this.ears = ears; + this.mouth = mouth; + this.eyes = eyes; + this.facialHair = facialHair; + this.haircut = haircut; + this.eyebrows = eyebrows; + this.pants = pants; + this.overpants = overpants; + this.undertop = undertop; + this.overtop = overtop; + this.shoes = shoes; + this.headAccessory = headAccessory; + this.faceAccessory = faceAccessory; + this.earAccessory = earAccessory; + this.skinFeature = skinFeature; + this.gloves = gloves; + this.cape = cape; + } + + public PlayerSkin( + String bodyCharacteristic, + String underwear, + String face, + String ears, + String mouth, + @Nullable String eyes, + @Nullable String facialHair, + @Nullable String haircut, + @Nullable String eyebrows, + @Nullable String pants, + @Nullable String overpants, + @Nullable String undertop, + @Nullable String overtop, + @Nullable String shoes, + @Nullable String headAccessory, + @Nullable String faceAccessory, + @Nullable String earAccessory, + @Nullable String skinFeature, + @Nullable String gloves, + @Nullable String cape + ) { + this.bodyCharacteristic = bodyCharacteristic != null ? PlayerSkin.PlayerSkinPartId.fromString(bodyCharacteristic) : null; + this.underwear = underwear != null ? PlayerSkin.PlayerSkinPartId.fromString(underwear) : null; + this.face = face; + this.ears = ears; + this.mouth = mouth; + this.eyes = eyes != null ? PlayerSkin.PlayerSkinPartId.fromString(eyes) : null; + this.facialHair = facialHair != null ? PlayerSkin.PlayerSkinPartId.fromString(facialHair) : null; + this.haircut = haircut != null ? PlayerSkin.PlayerSkinPartId.fromString(haircut) : null; + this.eyebrows = eyebrows != null ? PlayerSkin.PlayerSkinPartId.fromString(eyebrows) : null; + this.pants = pants != null ? PlayerSkin.PlayerSkinPartId.fromString(pants) : null; + this.overpants = overpants != null ? PlayerSkin.PlayerSkinPartId.fromString(overpants) : null; + this.undertop = undertop != null ? PlayerSkin.PlayerSkinPartId.fromString(undertop) : null; + this.overtop = overtop != null ? PlayerSkin.PlayerSkinPartId.fromString(overtop) : null; + this.shoes = shoes != null ? PlayerSkin.PlayerSkinPartId.fromString(shoes) : null; + this.headAccessory = headAccessory != null ? PlayerSkin.PlayerSkinPartId.fromString(headAccessory) : null; + this.faceAccessory = faceAccessory != null ? PlayerSkin.PlayerSkinPartId.fromString(faceAccessory) : null; + this.earAccessory = earAccessory != null ? PlayerSkin.PlayerSkinPartId.fromString(earAccessory) : null; + this.skinFeature = skinFeature != null ? PlayerSkin.PlayerSkinPartId.fromString(skinFeature) : null; + this.gloves = gloves != null ? PlayerSkin.PlayerSkinPartId.fromString(gloves) : null; + this.cape = cape != null ? PlayerSkin.PlayerSkinPartId.fromString(cape) : null; + } + + @Nullable + private static PlayerSkin.PlayerSkinPartId getId(@Nonnull BsonDocument doc, String key) { + BsonValue bsonValue = doc.get(key); + return bsonValue != null && !bsonValue.isNull() ? PlayerSkin.PlayerSkinPartId.fromString(bsonValue.asString().getValue()) : null; + } + + public PlayerSkin.PlayerSkinPartId getBodyCharacteristic() { + return this.bodyCharacteristic; + } + + public PlayerSkin.PlayerSkinPartId getUnderwear() { + return this.underwear; + } + + public String getFace() { + return this.face; + } + + @Nullable + public PlayerSkin.PlayerSkinPartId getEyes() { + return this.eyes; + } + + @Nonnull + public String getEars() { + return this.ears; + } + + @Nonnull + public String getMouth() { + return this.mouth; + } + + @Nullable + public PlayerSkin.PlayerSkinPartId getFacialHair() { + return this.facialHair; + } + + @Nullable + public PlayerSkin.PlayerSkinPartId getHaircut() { + return this.haircut; + } + + @Nullable + public PlayerSkin.PlayerSkinPartId getEyebrows() { + return this.eyebrows; + } + + @Nullable + public PlayerSkin.PlayerSkinPartId getPants() { + return this.pants; + } + + @Nullable + public PlayerSkin.PlayerSkinPartId getOverpants() { + return this.overpants; + } + + @Nullable + public PlayerSkin.PlayerSkinPartId getUndertop() { + return this.undertop; + } + + @Nullable + public PlayerSkin.PlayerSkinPartId getOvertop() { + return this.overtop; + } + + @Nullable + public PlayerSkin.PlayerSkinPartId getShoes() { + return this.shoes; + } + + @Nullable + public PlayerSkin.PlayerSkinPartId getHeadAccessory() { + return this.headAccessory; + } + + @Nullable + public PlayerSkin.PlayerSkinPartId getFaceAccessory() { + return this.faceAccessory; + } + + @Nullable + public PlayerSkin.PlayerSkinPartId getEarAccessory() { + return this.earAccessory; + } + + @Nullable + public PlayerSkin.PlayerSkinPartId getSkinFeature() { + return this.skinFeature; + } + + @Nullable + public PlayerSkin.PlayerSkinPartId getGloves() { + return this.gloves; + } + + @Nullable + public PlayerSkin.PlayerSkinPartId getCape() { + return this.cape; + } + + public static class PlayerSkinPartId { + public final String assetId; + public final String textureId; + public final String variantId; + + public PlayerSkinPartId(String assetId, String textureId, String variantId) { + this.assetId = assetId; + this.textureId = textureId; + this.variantId = variantId; + } + + @Nonnull + public static PlayerSkin.PlayerSkinPartId fromString(@Nonnull String stringId) { + String[] idParts = stringId.split("\\."); + return new PlayerSkin.PlayerSkinPartId(idParts[0], idParts.length > 1 ? idParts[1] : null, idParts.length > 2 ? idParts[2] : null); + } + + public String getAssetId() { + return this.assetId; + } + + public String getTextureId() { + return this.textureId; + } + + public String getVariantId() { + return this.variantId; + } + + @Nonnull + @Override + public String toString() { + return "CharacterPartId{assetId='" + this.assetId + "', textureId='" + this.textureId + "', variantId='" + this.variantId + "'}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinGradient.java b/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinGradient.java new file mode 100644 index 0000000..e754811 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinGradient.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.core.cosmetics; + +import javax.annotation.Nonnull; +import org.bson.BsonDocument; + +public class PlayerSkinGradient extends PlayerSkinTintColor { + private String texture; + + protected PlayerSkinGradient(@Nonnull BsonDocument doc) { + super(doc); + if (doc.containsKey("Texture")) { + this.texture = doc.getString("Texture").getValue(); + } + } + + public String getTexture() { + return this.texture; + } + + @Nonnull + @Override + public String toString() { + return "PlayerSkinGradient{texture='" + this.texture + "', id='" + this.id + "', baseColor='" + this.baseColor + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinGradientSet.java b/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinGradientSet.java new file mode 100644 index 0000000..fec4922 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinGradientSet.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.cosmetics; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class PlayerSkinGradientSet { + private final String id; + private final Map gradients; + + protected PlayerSkinGradientSet(@Nonnull BsonDocument doc) { + this.id = doc.getString("Id").getValue(); + BsonDocument gradients = doc.getDocument("Gradients"); + this.gradients = new Object2ObjectOpenHashMap<>(); + + for (Entry gradient : gradients.entrySet()) { + this.gradients.put(gradient.getKey(), new PlayerSkinPartTexture(gradient.getValue().asDocument())); + } + } + + public String getId() { + return this.id; + } + + public Map getGradients() { + return this.gradients; + } +} diff --git a/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinPart.java b/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinPart.java new file mode 100644 index 0000000..44cb5e7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinPart.java @@ -0,0 +1,220 @@ +package com.hypixel.hytale.server.core.cosmetics; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Arrays; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class PlayerSkinPart { + private final String id; + private final String name; + private String model; + private String greyscaleTexture; + private String gradientSet; + private Map textures; + private Map variants; + private boolean isDefaultAsset; + private String[] tags; + private PlayerSkinPart.HaircutType hairType; + private boolean requiresGenericHaircut; + @Nonnull + private PlayerSkinPart.HeadAccessoryType headAccessoryType = PlayerSkinPart.HeadAccessoryType.Simple; + + protected PlayerSkinPart(@Nonnull BsonDocument doc) { + this.id = doc.getString("Id").getValue(); + this.name = doc.getString("Name").getValue(); + if (doc.containsKey("Model")) { + this.model = doc.getString("Model").getValue(); + } + + if (doc.containsKey("GradientSet")) { + this.gradientSet = doc.getString("GradientSet").getValue(); + } + + if (doc.containsKey("GreyscaleTexture")) { + this.greyscaleTexture = doc.getString("GreyscaleTexture").getValue(); + } + + if (doc.containsKey("Variants")) { + BsonDocument mapping = doc.getDocument("Variants"); + this.variants = new Object2ObjectOpenHashMap<>(); + + for (Entry set : mapping.entrySet()) { + this.variants.put(set.getKey(), new PlayerSkinPart.Variant(set.getValue().asDocument())); + } + } else if (doc.containsKey("Textures")) { + BsonDocument mapping = doc.getDocument("Textures"); + this.textures = new Object2ObjectOpenHashMap<>(); + + for (Entry set : mapping.entrySet()) { + this.textures.put(set.getKey(), new PlayerSkinPartTexture(set.getValue().asDocument())); + } + } + + if (doc.containsKey("IsDefaultAsset")) { + this.isDefaultAsset = doc.getBoolean("IsDefaultAsset").getValue(); + } + + if (doc.containsKey("Tags")) { + BsonArray bsonArray = doc.getArray("Tags"); + this.tags = new String[bsonArray.size()]; + + for (int i = 0; i < bsonArray.size(); i++) { + this.tags[i] = bsonArray.get(i).asString().getValue(); + } + } + + if (doc.containsKey("HairType")) { + this.hairType = PlayerSkinPart.HaircutType.valueOf(doc.getString("HairType").getValue()); + } + + if (doc.containsKey("RequiresGenericHaircut")) { + this.requiresGenericHaircut = doc.getBoolean("RequiresGenericHaircut").getValue(); + } + + if (doc.containsKey("HeadAccessoryType")) { + this.headAccessoryType = PlayerSkinPart.HeadAccessoryType.valueOf(doc.getString("HeadAccessoryType").getValue()); + } + } + + public String getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public String getModel() { + return this.model; + } + + public Map getTextures() { + return this.textures; + } + + public Map getVariants() { + return this.variants; + } + + public boolean isDefaultAsset() { + return this.isDefaultAsset; + } + + public String[] getTags() { + return this.tags; + } + + public PlayerSkinPart.HaircutType getHairType() { + return this.hairType; + } + + public boolean doesRequireGenericHaircut() { + return this.requiresGenericHaircut; + } + + @Nonnull + public PlayerSkinPart.HeadAccessoryType getHeadAccessoryType() { + return this.headAccessoryType; + } + + public String getGreyscaleTexture() { + return this.greyscaleTexture; + } + + public String getGradientSet() { + return this.gradientSet; + } + + @Nonnull + @Override + public String toString() { + return "PlayerSkinPart{id='" + + this.id + + "', name='" + + this.name + + "', model='" + + this.model + + "', greyscaleTexture='" + + this.greyscaleTexture + + "', gradientSet='" + + this.gradientSet + + "', textures=" + + this.textures + + ", variants=" + + this.variants + + ", isDefaultAsset=" + + this.isDefaultAsset + + ", tags=" + + Arrays.toString((Object[])this.tags) + + ", hairType=" + + this.hairType + + ", requiresGenericHaircut=" + + this.requiresGenericHaircut + + ", headAccessoryType=" + + this.headAccessoryType + + "}"; + } + + public static enum HaircutType { + Short, + Medium, + Long; + + private HaircutType() { + } + } + + public static enum HeadAccessoryType { + Simple, + HalfCovering, + FullyCovering; + + private HeadAccessoryType() { + } + } + + public static class Variant { + private final String model; + private String greyscaleTexture; + private Map textures; + + protected Variant(@Nonnull BsonDocument doc) { + this.model = doc.getString("Model").getValue(); + if (doc.containsKey("GreyscaleTexture")) { + this.greyscaleTexture = doc.getString("GreyscaleTexture").getValue(); + } + + if (doc.containsKey("Textures")) { + BsonDocument texturesDoc = doc.getDocument("Textures"); + this.textures = new Object2ObjectOpenHashMap<>(); + + for (Entry set : texturesDoc.entrySet()) { + this.textures.put(set.getKey(), new PlayerSkinPartTexture(set.getValue().asDocument())); + } + } + } + + public String getModel() { + return this.model; + } + + public String getGreyscaleTexture() { + return this.greyscaleTexture; + } + + public Map getTextures() { + return this.textures; + } + + @Nonnull + @Override + public String toString() { + return "CharacterPartVariant{model='" + this.model + "'greyscaleTexture='" + this.greyscaleTexture + "', textures=" + this.textures + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinPartTexture.java b/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinPartTexture.java new file mode 100644 index 0000000..cee2dbd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinPartTexture.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.server.core.cosmetics; + +import java.util.Arrays; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonDocument; + +public class PlayerSkinPartTexture { + private String texture; + private String[] baseColor; + + protected PlayerSkinPartTexture(@Nonnull BsonDocument doc) { + this.texture = doc.getString("Texture").getValue(); + if (doc.containsKey("BaseColor")) { + BsonArray baseColor = doc.getArray("BaseColor"); + this.baseColor = new String[baseColor.size()]; + + for (int i = 0; i < baseColor.size(); i++) { + this.baseColor[i] = baseColor.get(i).asString().getValue(); + } + } + } + + public String getTexture() { + return this.texture; + } + + public String[] getBaseColor() { + return this.baseColor; + } + + @Nonnull + @Override + public String toString() { + return "PlayerSkinPartTexture{texture='" + this.texture + "', baseColor=" + Arrays.toString((Object[])this.baseColor) + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinPartType.java b/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinPartType.java new file mode 100644 index 0000000..01478f7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinPartType.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.core.cosmetics; + +public enum PlayerSkinPartType { + Eyes, + Ears, + Mouth, + Eyebrows, + Haircut, + FacialHair, + Pants, + Overpants, + Undertops, + Overtops, + Shoes, + HeadAccessory, + FaceAccessory, + EarAccessory, + SkinFeature, + Gloves; + + private PlayerSkinPartType() { + } +} diff --git a/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinTintColor.java b/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinTintColor.java new file mode 100644 index 0000000..7cda85c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/cosmetics/PlayerSkinTintColor.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.core.cosmetics; + +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonDocument; + +public class PlayerSkinTintColor { + protected String id; + protected String[] baseColor; + + protected PlayerSkinTintColor(@Nonnull BsonDocument doc) { + this.id = doc.getString("Id").getValue(); + BsonArray baseColor = doc.getArray("BaseColor"); + this.baseColor = new String[baseColor.size()]; + + for (int i = 0; i < baseColor.size(); i++) { + this.baseColor[i] = baseColor.get(i).asString().getValue(); + } + } + + public String getId() { + return this.id; + } + + public String[] getBaseColor() { + return this.baseColor; + } + + @Nonnull + @Override + public String toString() { + return "PlayerSkinTintColor{id='" + this.id + "', baseColor='" + this.baseColor + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/cosmetics/commands/EmoteCommand.java b/src/com/hypixel/hytale/server/core/cosmetics/commands/EmoteCommand.java new file mode 100644 index 0000000..516b09e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/cosmetics/commands/EmoteCommand.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.core.cosmetics.commands; + +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.AnimationSlot; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.cosmetics.CosmeticRegistry; +import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule; +import com.hypixel.hytale.server.core.cosmetics.Emote; +import com.hypixel.hytale.server.core.entity.AnimationUtils; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import javax.annotation.Nonnull; + +public class EmoteCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg emoteArg = this.withRequiredArg("emote", "server.commands.emote.emote.desc", ArgTypes.STRING); + + public EmoteCommand() { + super("emote", "server.commands.emote.desc"); + this.setPermissionGroup(GameMode.Adventure); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + CosmeticRegistry cosmeticsRegistry = CosmeticsModule.get().getRegistry(); + Map emotes = cosmeticsRegistry.getEmotes(); + String emoteId = this.emoteArg.get(context); + Emote emote = emotes.get(emoteId); + if (emote == null) { + context.sendMessage(Message.translation("server.commands.emote.emoteNotFound").param("id", emoteId)); + context.sendMessage( + Message.translation("server.general.failed.didYouMean") + .param("choices", StringUtil.sortByFuzzyDistance(emoteId, emotes.keySet(), CommandUtil.RECOMMEND_COUNT).toString()) + ); + } else { + AnimationUtils.playAnimation(ref, AnimationSlot.Emote, null, emote.getId(), true, store); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/AnimationUtils.java b/src/com/hypixel/hytale/server/core/entity/AnimationUtils.java new file mode 100644 index 0000000..a808bf9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/AnimationUtils.java @@ -0,0 +1,120 @@ +package com.hypixel.hytale.server.core.entity; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.AnimationSlot; +import com.hypixel.hytale.protocol.packets.entities.PlayAnimation; +import com.hypixel.hytale.server.core.asset.type.itemanimation.config.ItemPlayerAnimations; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.universe.world.PlayerUtil; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AnimationUtils { + public AnimationUtils() { + } + + public static void playAnimation( + @Nonnull Ref ref, + @Nonnull AnimationSlot animationSlot, + @Nullable String animationId, + boolean sendToSelf, + @Nonnull ComponentAccessor componentAccessor + ) { + playAnimation(ref, animationSlot, null, animationId, sendToSelf, componentAccessor); + } + + public static void playAnimation( + @Nonnull Ref ref, + @Nonnull AnimationSlot animationSlot, + @Nullable String itemAnimationsId, + @Nullable String animationId, + boolean sendToSelf, + @Nonnull ComponentAccessor componentAccessor + ) { + Model model = null; + ModelComponent modelComponent = componentAccessor.getComponent(ref, ModelComponent.getComponentType()); + if (modelComponent != null) { + model = modelComponent.getModel(); + } + + if (animationSlot != AnimationSlot.Action && animationId != null && model != null && !model.getAnimationSetMap().containsKey(animationId)) { + Entity.LOGGER.at(Level.WARNING).atMostEvery(1, TimeUnit.MINUTES).log("Missing animation '%s' for Model '%s'", animationId, model.getModelAssetId()); + } else { + NetworkId networkIdComponent = componentAccessor.getComponent(ref, NetworkId.getComponentType()); + + assert networkIdComponent != null; + + PlayAnimation animationPacket = new PlayAnimation(networkIdComponent.getId(), itemAnimationsId, animationId, animationSlot); + if (sendToSelf) { + PlayerUtil.forEachPlayerThatCanSeeEntity( + ref, (playerRef, playerRefComponent, ca) -> playerRefComponent.getPacketHandler().writeNoCache(animationPacket), componentAccessor + ); + } else { + PlayerUtil.forEachPlayerThatCanSeeEntity( + ref, (playerRef, playerRefComponent, ca) -> playerRefComponent.getPacketHandler().writeNoCache(animationPacket), ref, componentAccessor + ); + } + } + } + + public static void playAnimation( + @Nonnull Ref ref, + @Nonnull AnimationSlot animationSlot, + @Nonnull String itemAnimationsId, + @Nonnull String animationId, + @Nonnull ComponentAccessor componentAccessor + ) { + playAnimation(ref, animationSlot, itemAnimationsId, animationId, false, componentAccessor); + } + + public static void playAnimation( + @Nonnull Ref ref, + @Nonnull AnimationSlot animationSlot, + @Nullable ItemPlayerAnimations itemAnimations, + @Nonnull String animationId, + @Nonnull ComponentAccessor componentAccessor + ) { + String itemAnimationsId = itemAnimations != null ? itemAnimations.getId() : null; + playAnimation(ref, animationSlot, itemAnimationsId, animationId, false, componentAccessor); + } + + public static void stopAnimation( + @Nonnull Ref ref, @Nonnull AnimationSlot animationSlot, @Nonnull ComponentAccessor componentAccessor + ) { + stopAnimation(ref, animationSlot, false, componentAccessor); + } + + public static void stopAnimation( + @Nonnull Ref ref, @Nonnull AnimationSlot animationSlot, boolean sendToSelf, @Nonnull ComponentAccessor componentAccessor + ) { + NetworkId networkIdComponent = componentAccessor.getComponent(ref, NetworkId.getComponentType()); + + assert networkIdComponent != null; + + PlayAnimation animationPacket = new PlayAnimation(networkIdComponent.getId(), null, null, animationSlot); + if (sendToSelf) { + PlayerUtil.forEachPlayerThatCanSeeEntity( + ref, (playerRef, playerRefComponent, ca) -> playerRefComponent.getPacketHandler().write(animationPacket), componentAccessor + ); + } else { + PlayerUtil.forEachPlayerThatCanSeeEntity( + ref, (playerRef, playerRefComponent, ca) -> playerRefComponent.getPacketHandler().write(animationPacket), ref, componentAccessor + ); + } + } + + public static void playAnimation( + @Nonnull Ref ref, + @Nonnull AnimationSlot animationSlot, + @Nullable String animationId, + @Nonnull ComponentAccessor componentAccessor + ) { + playAnimation(ref, animationSlot, animationId, false, componentAccessor); + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/ChainSyncStorage.java b/src/com/hypixel/hytale/server/core/entity/ChainSyncStorage.java new file mode 100644 index 0000000..c009806 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/ChainSyncStorage.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.entity; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChain; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface ChainSyncStorage { + InteractionState getClientState(); + + void setClientState(InteractionState var1); + + @Nullable + InteractionEntry getInteraction(int var1); + + void putInteractionSyncData(int var1, InteractionSyncData var2); + + void updateSyncPosition(int var1); + + boolean isSyncDataOutOfOrder(int var1); + + void syncFork(@Nonnull Ref var1, @Nonnull InteractionManager var2, @Nonnull SyncInteractionChain var3); + + void clearInteractionSyncData(int var1); +} diff --git a/src/com/hypixel/hytale/server/core/entity/Entity.java b/src/com/hypixel/hytale/server/core/entity/Entity.java new file mode 100644 index 0000000..0b37d87 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/Entity.java @@ -0,0 +1,359 @@ +package com.hypixel.hytale.server.core.entity; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.DirectDecodeCodec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.NonSerialized; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.event.events.entity.EntityRemoveEvent; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Arrays; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonString; + +public abstract class Entity implements Component { + @Nonnull + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final int VERSION = 5; + @Nonnull + public static final KeyedCodec MODEL = new KeyedCodec<>("Model", Model.ModelReference.CODEC); + @Nonnull + public static final KeyedCodec DISPLAY_NAME = new KeyedCodec<>("DisplayName", Codec.STRING); + @Nonnull + public static final KeyedCodec UUID = new KeyedCodec<>("UUID", Codec.UUID_BINARY); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.abstractBuilder(Entity.class) + .legacyVersioned() + .codecVersion(5) + .append(DISPLAY_NAME, (entity, o) -> entity.legacyDisplayName = o, entity -> entity.legacyDisplayName) + .add() + .append(UUID, (entity, o) -> entity.legacyUuid = o, entity -> entity.legacyUuid) + .add() + .build(); + public static final int UNASSIGNED_ID = -1; + protected int networkId = -1; + @Nullable + protected UUID legacyUuid; + @Nullable + protected World world; + @Nullable + protected Ref reference; + @Deprecated + private TransformComponent transformComponent; + @Deprecated(forRemoval = true) + protected String legacyDisplayName; + @Nonnull + protected final AtomicBoolean wasRemoved = new AtomicBoolean(); + @Nullable + protected Throwable removedBy; + + @Deprecated + public Entity(@Nullable World world) { + this(); + this.networkId = world != null ? world.getEntityStore().takeNextNetworkId() : -1; + this.world = world; + } + + public Entity() { + } + + @Deprecated(forRemoval = true) + public void markNeedsSave() { + if (this.transformComponent != null) { + WorldChunk chunk = this.transformComponent.getChunk(); + if (chunk != null) { + chunk.getEntityChunk().markNeedsSaving(); + } + } + } + + public void setLegacyUUID(@Nullable UUID uuid) { + this.legacyUuid = uuid; + } + + public boolean remove() { + this.world.debugAssertInTickingThread(); + if (this.wasRemoved.getAndSet(true)) { + return false; + } else { + this.removedBy = new Throwable(); + + try { + String key = this.world != null ? this.world.getName() : null; + IEventDispatcher dispatcher = HytaleServer.get().getEventBus().dispatchFor(EntityRemoveEvent.class, key); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new EntityRemoveEvent(this)); + } + + if (this.reference.isValid()) { + this.world.getEntityStore().getStore().removeEntity(this.reference, RemoveReason.REMOVE); + } + } catch (Throwable var3) { + this.wasRemoved.set(false); + } + + return true; + } + } + + public void loadIntoWorld(@Nonnull World world) { + if (this.world != null) { + throw new IllegalArgumentException("Entity is already in a world! " + this); + } else { + this.world = world; + if (this.networkId == -1) { + this.networkId = world.getEntityStore().takeNextNetworkId(); + } + } + } + + public void unloadFromWorld() { + if (this.world == null) { + throw new IllegalArgumentException("Entity is already not in a world! " + this); + } else { + this.networkId = -1; + this.world = null; + } + } + + @Deprecated(forRemoval = true) + public int getNetworkId() { + return this.networkId; + } + + @Deprecated(forRemoval = true) + public String getLegacyDisplayName() { + return this.legacyDisplayName; + } + + @Nullable + @Deprecated(forRemoval = true) + public UUID getUuid() { + return this.legacyUuid; + } + + @Deprecated(forRemoval = true) + public void setTransformComponent(TransformComponent transform) { + this.transformComponent = transform; + } + + @Deprecated(forRemoval = true) + public TransformComponent getTransformComponent() { + if (this.world == null || this.reference == null) { + throw new IllegalStateException("Called before entity was init"); + } else if (!this.world.isInThread()) { + LOGGER.at(Level.WARNING).atMostEvery(5, TimeUnit.MINUTES).withCause(new Throwable()).log("getPositionComponent called async"); + return this.transformComponent; + } else { + Store store = this.world.getEntityStore().getStore(); + TransformComponent transformComponent = store.getComponent(this.reference, TransformComponent.getComponentType()); + + assert transformComponent != null; + + return transformComponent; + } + } + + @Deprecated + public void moveTo(@Nonnull Ref ref, double locX, double locY, double locZ, @Nonnull ComponentAccessor componentAccessor) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + transformComponent.getPosition().assign(locX, locY, locZ); + } + + @Nullable + public World getWorld() { + return this.world; + } + + public boolean wasRemoved() { + return this.wasRemoved.get(); + } + + public boolean isCollidable() { + return true; + } + + @Override + public int hashCode() { + int result = this.networkId; + return 31 * result + (this.world != null ? this.world.hashCode() : 0); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Entity entity = (Entity)o; + if (this.networkId != entity.networkId) { + return false; + } else { + return this.world != null ? this.world.equals(entity.world) : entity.world == null; + } + } else { + return false; + } + } + + @Nonnull + @Override + public String toString() { + return "Entity{id=" + + this.networkId + + ", uuid=" + + this.legacyUuid + + ", reference='" + + this.reference + + "', world=" + + (this.world != null ? this.world.getName() : null) + + ", displayName='" + + this.legacyDisplayName + + "', wasRemoved='" + + this.wasRemoved + + "', removedBy='" + + (this.removedBy != null ? this.removedBy + "\n" + Arrays.toString((Object[])this.removedBy.getStackTrace()) : null) + + "'}"; + } + + public boolean isHiddenFromLivingEntity( + @Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor + ) { + return false; + } + + public void setReference(@Nonnull Ref reference) { + if (this.reference != null && this.reference.isValid()) { + throw new IllegalArgumentException("Entity already has a valid EntityReference: " + this.reference + " new reference " + reference); + } else { + this.reference = reference; + } + } + + @Nullable + public Ref getReference() { + return this.reference; + } + + @Deprecated + public void clearReference() { + this.reference = null; + } + + @Override + public Component clone() { + DirectDecodeCodec codec = EntityModule.get().getCodec((Class)this.getClass()); + Function constructor = EntityModule.get().getConstructor((Class)this.getClass()); + BsonDocument document = codec.encode(this, ExtraInfo.THREAD_LOCAL.get()).asDocument(); + document.put("EntityType", new BsonString(EntityModule.get().getIdentifier((Class)this.getClass()))); + Entity t = constructor.apply(null); + codec.decode(document, t, ExtraInfo.THREAD_LOCAL.get()); + return t; + } + + public Holder toHolder() { + if (this.reference != null && this.reference.isValid() && this.world != null) { + if (!this.world.isInThread()) { + return CompletableFuture.supplyAsync(this::toHolder, this.world).join(); + } else { + Holder holder = EntityStore.REGISTRY.newHolder(); + Store componentStore = this.world.getEntityStore().getStore(); + Archetype archetype = componentStore.getArchetype(this.reference); + + for (int i = archetype.getMinIndex(); i < archetype.length(); i++) { + ComponentType componentType = archetype.get(i); + if (componentType != null) { + Component component = componentStore.getComponent(this.reference, componentType); + + assert component != null; + + holder.addComponent(componentType, component); + } + } + + return holder; + } + } else { + Holder holder = EntityStore.REGISTRY.newHolder(); + if (this instanceof Player) { + holder.addComponent(Player.getComponentType(), (Player)this); + } else { + ComponentType componentType = EntityModule.get().getComponentType((Class)this.getClass()); + holder.addComponent(componentType, this); + } + + DirectDecodeCodec codec = EntityModule.get().getCodec((Class)this.getClass()); + if (codec == null) { + holder.addComponent(EntityStore.REGISTRY.getNonSerializedComponentType(), NonSerialized.get()); + } + + return holder; + } + } + + public static class DefaultAnimations { + @Nonnull + public static final String DEATH = "Death"; + @Nonnull + public static final String HURT = "Hurt"; + @Nonnull + public static final String DESPAWN = "Despawn"; + @Nonnull + public static final String SWIM_SUFFIX = "Swim"; + @Nonnull + public static final String FLY_SUFFIX = "Fly"; + + public DefaultAnimations() { + } + + @Nonnull + public static String[] getHurtAnimationIds(@Nonnull MovementStates movementStates, @Nonnull DamageCause damageCause) { + String animationId = damageCause.getAnimationId(); + if (movementStates.swimming) { + return new String[]{animationId + "Swim", animationId, "Hurt"}; + } else { + return movementStates.flying ? new String[]{animationId + "Fly", animationId, "Hurt"} : new String[]{animationId, "Hurt"}; + } + } + + @Nonnull + public static String[] getDeathAnimationIds(@Nonnull MovementStates movementStates, @Nonnull DamageCause damageCause) { + String animationId = damageCause.getDeathAnimationId(); + if (movementStates.swimming) { + return new String[]{animationId + "Swim", animationId, "Death"}; + } else { + return movementStates.flying ? new String[]{animationId + "Fly", animationId, "Death"} : new String[]{animationId, "Death"}; + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/EntitySnapshot.java b/src/com/hypixel/hytale/server/core/entity/EntitySnapshot.java new file mode 100644 index 0000000..b07bd51 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/EntitySnapshot.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.server.core.entity; + +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import javax.annotation.Nonnull; + +public class EntitySnapshot { + @Nonnull + private final Vector3d position = new Vector3d(); + @Nonnull + private final Vector3f bodyRotation = new Vector3f(); + + public EntitySnapshot() { + } + + public EntitySnapshot(@Nonnull Vector3d position, @Nonnull Vector3f bodyRotation) { + this.position.assign(position); + this.bodyRotation.assign(bodyRotation); + } + + public void init(@Nonnull Vector3d position, @Nonnull Vector3f bodyRotation) { + this.position.assign(position); + this.bodyRotation.assign(bodyRotation); + } + + @Nonnull + public Vector3d getPosition() { + return this.position; + } + + @Nonnull + public Vector3f getBodyRotation() { + return this.bodyRotation; + } + + @Nonnull + @Override + public String toString() { + return "EntitySnapshot{position=" + this.position + ", bodyRotation=" + this.bodyRotation + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/EntityUtils.java b/src/com/hypixel/hytale/server/core/entity/EntityUtils.java new file mode 100644 index 0000000..0684db7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/EntityUtils.java @@ -0,0 +1,112 @@ +package com.hypixel.hytale.server.core.entity; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.physics.component.PhysicsValues; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityUtils { + public EntityUtils() { + } + + @Nonnull + public static Holder toHolder(int index, @Nonnull ArchetypeChunk archetypeChunk) { + Holder holder = EntityStore.REGISTRY.newHolder(); + Archetype archetype = archetypeChunk.getArchetype(); + + for (int i = archetype.getMinIndex(); i < archetype.length(); i++) { + ComponentType componentType = archetype.get(i); + if (componentType != null) { + Component component = archetypeChunk.getComponent(index, componentType); + if (component != null) { + holder.addComponent(componentType, component); + } + } + } + + return holder; + } + + @Nullable + private static ComponentType findComponentType(@Nonnull Archetype archetype) { + return findComponentType(archetype, Entity.class); + } + + @Nullable + private static , T extends C> ComponentType findComponentType( + @Nonnull Archetype archetype, @Nonnull Class entityClass + ) { + for (int i = archetype.getMinIndex(); i < archetype.length(); i++) { + ComponentType> componentType = (ComponentType>)archetype.get( + i + ); + if (componentType != null && entityClass.isAssignableFrom(componentType.getTypeClass())) { + return (ComponentType)componentType; + } + } + + return null; + } + + @Deprecated + @Nullable + public static Entity getEntity(@Nullable Ref ref, @Nonnull ComponentAccessor componentAccessor) { + if (ref != null && ref.isValid()) { + ComponentType componentType = findComponentType(componentAccessor.getArchetype(ref)); + return componentType == null ? null : componentAccessor.getComponent(ref, componentType); + } else { + return null; + } + } + + @Nullable + @Deprecated + public static Entity getEntity(int index, @Nonnull ArchetypeChunk archetypeChunk) { + ComponentType componentType = findComponentType(archetypeChunk.getArchetype()); + return componentType == null ? null : archetypeChunk.getComponent(index, componentType); + } + + @Nullable + @Deprecated + public static Entity getEntity(@Nonnull Holder holder) { + Archetype archetype = holder.getArchetype(); + if (archetype == null) { + return null; + } else { + ComponentType componentType = findComponentType(archetype); + return componentType == null ? null : holder.getComponent(componentType); + } + } + + @Deprecated + public static boolean hasEntity(@Nonnull Archetype archetype) { + return findComponentType(archetype) != null; + } + + @Deprecated + public static boolean hasLivingEntity(@Nonnull Archetype archetype) { + return findComponentType(archetype, LivingEntity.class) != null; + } + + @Nonnull + @Deprecated + public static PhysicsValues getPhysicsValues(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + PhysicsValues physicsValuesComponent = componentAccessor.getComponent(ref, PhysicsValues.getComponentType()); + if (physicsValuesComponent != null) { + return physicsValuesComponent; + } else { + ModelComponent modelComponent = componentAccessor.getComponent(ref, ModelComponent.getComponentType()); + Model model = modelComponent != null ? modelComponent.getModel() : null; + return model != null && model.getPhysicsValues() != null ? model.getPhysicsValues() : PhysicsValues.getDefault(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/ExplosionConfig.java b/src/com/hypixel/hytale/server/core/entity/ExplosionConfig.java new file mode 100644 index 0000000..656d4f7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/ExplosionConfig.java @@ -0,0 +1,110 @@ +package com.hypixel.hytale.server.core.entity; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemTool; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.Knockback; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ExplosionConfig { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(ExplosionConfig.class, ExplosionConfig::new) + .appendInherited( + new KeyedCodec<>("DamageEntities", Codec.BOOLEAN), + (explosionConfig, b) -> explosionConfig.damageEntities = b, + explosionConfig -> explosionConfig.damageEntities, + (explosionConfig, parent) -> explosionConfig.damageEntities = parent.damageEntities + ) + .documentation("Determines whether the explosion should damage entities.") + .add() + .appendInherited( + new KeyedCodec<>("DamageBlocks", Codec.BOOLEAN), + (explosionConfig, b) -> explosionConfig.damageBlocks = b, + explosionConfig -> explosionConfig.damageBlocks, + (explosionConfig, parent) -> explosionConfig.damageBlocks = parent.damageBlocks + ) + .documentation("Determines whether the explosion should damage blocks.") + .add() + .appendInherited( + new KeyedCodec<>("BlockDamageRadius", Codec.INTEGER), + (explosionConfig, i) -> explosionConfig.blockDamageRadius = i, + explosionConfig -> explosionConfig.blockDamageRadius, + (explosionConfig, parent) -> explosionConfig.blockDamageRadius = parent.blockDamageRadius + ) + .documentation("The radius in which blocks should be damaged by the explosion.") + .add() + .appendInherited( + new KeyedCodec<>("BlockDamageFalloff", Codec.FLOAT), + (explosionConfig, f) -> explosionConfig.blockDamageFalloff = f, + explosionConfig -> explosionConfig.entityDamageFalloff, + (explosionConfig, parent) -> explosionConfig.entityDamageFalloff = parent.entityDamageFalloff + ) + .documentation("The falloff applied to the block damage.") + .add() + .appendInherited( + new KeyedCodec<>("BlockDropChance", Codec.FLOAT), + (explosionConfig, f) -> explosionConfig.blockDropChance = f, + explosionConfig -> explosionConfig.blockDropChance, + (explosionConfig, parent) -> explosionConfig.blockDropChance = parent.blockDropChance + ) + .documentation("The chance in which a block drops its loot after breaking.") + .add() + .appendInherited( + new KeyedCodec<>("EntityDamageRadius", Codec.FLOAT), + (explosionConfig, f) -> explosionConfig.entityDamageRadius = f, + explosionConfig -> explosionConfig.entityDamageRadius, + (explosionConfig, parent) -> explosionConfig.entityDamageRadius = parent.entityDamageRadius + ) + .documentation("The radius in which entities should be damaged by the explosion.") + .add() + .appendInherited( + new KeyedCodec<>("EntityDamage", Codec.FLOAT), + (explosionConfig, f) -> explosionConfig.entityDamage = f, + explosionConfig -> explosionConfig.entityDamage, + (explosionConfig, parent) -> explosionConfig.entityDamage = parent.entityDamage + ) + .documentation("The amount of damage to be applied to entities within range.") + .add() + .appendInherited( + new KeyedCodec<>("EntityDamageFalloff", Codec.FLOAT), + (explosionConfig, f) -> explosionConfig.entityDamageFalloff = f, + explosionConfig -> explosionConfig.entityDamageFalloff, + (explosionConfig, parent) -> explosionConfig.entityDamageFalloff = parent.entityDamageFalloff + ) + .documentation("The falloff applied to the entity damage.") + .add() + .appendInherited( + new KeyedCodec<>("Knockback", Knockback.CODEC), + (explosionConfig, s) -> explosionConfig.knockback = s, + explosionConfig -> explosionConfig.knockback, + (explosionConfig, parent) -> explosionConfig.knockback = parent.knockback + ) + .documentation("Determines the knockback effect applied to damaged entities.") + .add() + .appendInherited( + new KeyedCodec<>("ItemTool", ItemTool.CODEC), + (damageEffects, s) -> damageEffects.itemTool = s, + damageEffects -> damageEffects.itemTool, + (damageEffects, parent) -> damageEffects.itemTool = parent.itemTool + ) + .documentation("The item tool to reference when applying damage to blocks.") + .add() + .build(); + protected boolean damageEntities = true; + protected boolean damageBlocks = true; + protected int blockDamageRadius = 3; + protected float blockDamageFalloff = 1.0F; + protected float entityDamageRadius = 5.0F; + protected float entityDamage = 50.0F; + protected float entityDamageFalloff = 1.0F; + protected float blockDropChance = 1.0F; + @Nullable + protected Knockback knockback; + @Nullable + protected ItemTool itemTool; + + public ExplosionConfig() { + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/ExplosionUtils.java b/src/com/hypixel/hytale/server/core/entity/ExplosionUtils.java new file mode 100644 index 0000000..d516b4a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/ExplosionUtils.java @@ -0,0 +1,271 @@ +package com.hypixel.hytale.server.core.entity; + +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.block.BlockSphereUtil; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockGathering; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemTool; +import com.hypixel.hytale.server.core.entity.knockback.KnockbackComponent; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems; +import com.hypixel.hytale.server.core.modules.interaction.BlockHarvestUtils; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector.Selector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.Knockback; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ExplosionUtils { + private static final boolean DEBUG_SHAPES = false; + private static final Vector3f DEBUG_POTENTIAL_TARGET_COLOR = new Vector3f(1.0F, 1.0F, 0.0F); + private static final int DEBUG_POTENTIAL_TARGET_TIME = 5; + private static final float DEBUG_BLOCK_HIT_SCALE = 1.1F; + private static final float DEBUG_BLOCK_HIT_TIME = 2.0F; + private static final float DEBUG_BLOCK_HIT_ALPHA = 0.25F; + private static final Vector3f DEBUG_BLOCK_RADIUS_COLOR = new Vector3f(1.0F, 0.5F, 0.5F); + private static final Vector3f DEBUG_ENTITY_RADIUS_COLOR = new Vector3f(0.5F, 1.0F, 0.5F); + private static final int DEBUG_BLOCK_RADIUS_TIME = 5; + private static final int DEBUG_ENTITY_RADIUS_TIME = 5; + + public ExplosionUtils() { + } + + public static void performExplosion( + @Nonnull Damage.Source damageSource, + @Nonnull Vector3d position, + @Nonnull ExplosionConfig config, + @Nullable Ref ignoreRef, + @Nonnull CommandBuffer commandBuffer, + @Nonnull ComponentAccessor chunkStore + ) { + if (config.damageBlocks || config.damageEntities) { + Set> targetRefs = new ObjectOpenHashSet<>(); + Vector3d blockPosition = new Vector3d(Math.floor(position.x) + 0.5, Math.floor(position.y) + 0.5, Math.floor(position.z) + 0.5); + processTargetBlocks(blockPosition, config, ignoreRef, targetRefs, commandBuffer, chunkStore); + if (config.damageEntities) { + processTargetEntities(config, position, damageSource, ignoreRef, targetRefs, commandBuffer); + } + } + } + + private static void processTargetBlocks( + @Nonnull Vector3d position, + @Nonnull ExplosionConfig config, + @Nullable Ref ignoreRef, + @Nonnull Set> targetRefs, + @Nonnull CommandBuffer commandBuffer, + @Nonnull ComponentAccessor chunkStore + ) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + World world = commandBuffer.getExternalData().getWorld(); + int explosionBlockRadius = config.blockDamageRadius; + if (config.damageEntities && config.entityDamageRadius > config.blockDamageRadius) { + explosionBlockRadius = (int)config.entityDamageRadius; + } + + List> potentialTargets = new ObjectArrayList<>(); + if (config.damageEntities) { + Selector.selectNearbyEntities( + commandBuffer, position, (double)config.entityDamageRadius, potentialTargets::add, e -> ignoreRef == null || !e.equals(ignoreRef) + ); + } + + if (config.damageBlocks || !potentialTargets.isEmpty()) { + ItemTool itemTool = config.itemTool; + Set targetBlocks = new ObjectOpenHashSet<>(); + int posX = MathUtil.floor(position.x); + int posY = MathUtil.floor(position.y); + int posZ = MathUtil.floor(position.z); + BlockSphereUtil.forEachBlock(posX, posY, posZ, explosionBlockRadius, null, (x, y, z, aVoid) -> { + targetBlocks.add(new Vector3i(x, y, z)); + return true; + }); + Set avoidBlocks = new ObjectOpenHashSet<>(); + + for (Vector3i targetBlock : targetBlocks) { + Vector3d targetBlockPosition = targetBlock.toVector3d().add(0.5, 0.5, 0.5); + int setBlockSettings = 1028; + if (random.nextFloat() > config.blockDropChance) { + setBlockSettings |= 2048; + } + + double distance = position.distanceTo(targetBlockPosition); + if (!(distance <= 0.0) && !Double.isNaN(distance)) { + Vector3d direction = targetBlockPosition.clone().subtract(position); + Vector3i targetBlockPos = TargetUtil.getTargetBlock( + world, + (id, fluidId) -> isValidTargetBlock(id, config.damageBlocks), + position.x, + position.y, + position.z, + direction.x, + direction.y, + direction.z, + distance + ); + if (targetBlockPos == null) { + if (config.damageEntities) { + Vector3d entityHitPos = position.clone().add(direction); + collectPotentialTargets(targetRefs, potentialTargets, entityHitPos, position, commandBuffer); + } + } else if (!avoidBlocks.contains(targetBlockPos)) { + Vector3d targetBlockPosD = targetBlockPos.toVector3d().add(0.5, 0.5, 0.5); + if (config.damageEntities) { + collectPotentialTargets(targetRefs, potentialTargets, targetBlockPosD, position, commandBuffer); + } + + float damageDistance = (float)position.distanceTo(targetBlockPosD); + float damageScale = calculateBlockDamageScale(damageDistance, explosionBlockRadius, config.blockDamageFalloff); + long chunkIndex = ChunkUtil.indexChunkFromBlock(targetBlockPos.x, targetBlockPos.z); + Ref chunkReference = chunkStore.getExternalData().getChunkReference(chunkIndex); + if (chunkReference != null) { + boolean canDamageBlock = distance <= config.blockDamageRadius; + if (!config.damageBlocks + || canDamageBlock + && !BlockHarvestUtils.performBlockDamage( + targetBlockPos, null, itemTool, damageScale, setBlockSettings, chunkReference, commandBuffer, chunkStore + )) { + avoidBlocks.add(targetBlockPos); + } + } + } + } + } + } + } + + private static boolean isValidTargetBlock(int blockTypeId, boolean damageBlocks) { + if (blockTypeId == 0 || blockTypeId == 1) { + return false; + } else if (!damageBlocks) { + BlockType blockType = BlockType.getAssetMap().getAsset(blockTypeId); + if (blockType == null) { + return false; + } else { + BlockGathering gathering = blockType.getGathering(); + return gathering == null || !gathering.isSoft(); + } + } else { + return true; + } + } + + private static void collectPotentialTargets( + @Nonnull Set> targetRefs, + @Nonnull List> potentialTargetRefs, + @Nonnull Vector3d startPosition, + @Nonnull Vector3d endPosition, + @Nonnull CommandBuffer commandBuffer + ) { + World world = commandBuffer.getExternalData().getWorld(); + + for (Ref potentialTarget : potentialTargetRefs) { + if (processPotentialEntity(potentialTarget, startPosition, endPosition, commandBuffer) && targetRefs.add(potentialTarget)) { + } + } + } + + private static boolean processPotentialEntity( + @Nonnull Ref ref, @Nonnull Vector3d startPosition, @Nonnull Vector3d endPosition, @Nonnull CommandBuffer commandBuffer + ) { + BoundingBox boundingBoxComponent = commandBuffer.getComponent(ref, BoundingBox.getComponentType()); + if (boundingBoxComponent == null) { + return false; + } else { + TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType()); + if (transformComponent == null) { + return false; + } else { + Vector3d entityPosition = transformComponent.getPosition(); + Box boundingBox = boundingBoxComponent.getBoundingBox().clone().offset(entityPosition); + return boundingBox.intersectsLine(startPosition, endPosition); + } + } + } + + private static float calculateBlockDamageScale(float distance, float radius, float fallOff) { + if (distance >= radius) { + return 0.0F; + } else { + float normalizedDistance = distance / radius; + return 1.0F - (float)Math.pow(normalizedDistance, fallOff); + } + } + + private static void processTargetEntities( + @Nonnull ExplosionConfig config, + @Nonnull Vector3d position, + @Nonnull Damage.Source damageSource, + @Nullable Ref ignoreRef, + @Nonnull Set> targetRefs, + @Nonnull CommandBuffer commandBuffer + ) { + for (Ref targetRef : targetRefs) { + processTargetEntity(config, targetRef, position, damageSource, commandBuffer); + } + } + + private static void processTargetEntity( + @Nonnull ExplosionConfig config, + @Nonnull Ref targetRef, + @Nonnull Vector3d position, + @Nonnull Damage.Source damageSource, + @Nonnull CommandBuffer commandBuffer + ) { + float entityDamageRadius = config.entityDamageRadius; + float explosionDamage = config.entityDamage; + float explosionFalloff = config.entityDamageFalloff; + TransformComponent targetTransformComponent = commandBuffer.getComponent(targetRef, TransformComponent.getComponentType()); + + assert targetTransformComponent != null; + + Velocity targetVelocityComponent = commandBuffer.getComponent(targetRef, Velocity.getComponentType()); + + assert targetVelocityComponent != null; + + Vector3d targetPosition = targetTransformComponent.getPosition(); + Vector3d diff = targetPosition.clone().subtract(position); + double distance = diff.length(); + float damage = (float)(explosionDamage * Math.pow(1.0 - distance / entityDamageRadius, explosionFalloff)); + if (damage > 0.0F) { + DamageSystems.executeDamage(targetRef, commandBuffer, new Damage(damageSource, DamageCause.ENVIRONMENT, damage)); + } + + Knockback knockbackConfig = config.knockback; + if (knockbackConfig != null) { + ComponentType knockbackComponentType = KnockbackComponent.getComponentType(); + KnockbackComponent knockbackComponent = commandBuffer.getComponent(targetRef, knockbackComponentType); + if (knockbackComponent == null) { + knockbackComponent = new KnockbackComponent(); + commandBuffer.putComponent(targetRef, knockbackComponentType, knockbackComponent); + } + + Vector3d direction = diff.clone().normalize(); + knockbackComponent.setVelocity(knockbackConfig.calculateVector(position, (float)direction.y, targetPosition)); + knockbackComponent.setVelocityType(knockbackConfig.getVelocityType()); + knockbackComponent.setVelocityConfig(knockbackConfig.getVelocityConfig()); + knockbackComponent.setDuration(knockbackConfig.getDuration()); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/Frozen.java b/src/com/hypixel/hytale/server/core/entity/Frozen.java new file mode 100644 index 0000000..8cc56c7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/Frozen.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.entity; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public class Frozen implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(Frozen.class, Frozen::get).build(); + private static final Frozen INSTANCE = new Frozen(); + + public static ComponentType getComponentType() { + return EntityModule.get().getFrozenComponentType(); + } + + public static Frozen get() { + return INSTANCE; + } + + private Frozen() { + } + + @Override + public Component clone() { + return get(); + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/InteractionChain.java b/src/com/hypixel/hytale/server/core/entity/InteractionChain.java new file mode 100644 index 0000000..415017c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/InteractionChain.java @@ -0,0 +1,835 @@ +package com.hypixel.hytale.server.core.entity; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.ForkedChainId; +import com.hypixel.hytale.protocol.InteractionChainData; +import com.hypixel.hytale.protocol.InteractionCooldown; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChain; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.longs.Long2LongMap; +import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import java.util.List; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InteractionChain implements ChainSyncStorage { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final long NULL_FORK_ID = forkedIdToIndex(new ForkedChainId(-1, Integer.MAX_VALUE, null)); + private final InteractionType type; + private InteractionType baseType; + private final InteractionChainData chainData; + private int chainId; + private final ForkedChainId forkedChainId; + private final ForkedChainId baseForkedChainId; + private boolean predicted; + private final InteractionContext context; + @Nonnull + private final Long2ObjectMap forkedChains = new Long2ObjectOpenHashMap<>(); + @Nonnull + private final Long2ObjectMap tempForkedChainData = new Long2ObjectOpenHashMap<>(); + @Nonnull + private final Long2LongMap forkedChainsMap = new Long2LongOpenHashMap(); + @Nonnull + private final List newForks = new ObjectArrayList<>(); + @Nonnull + private final RootInteraction initialRootInteraction; + private RootInteraction rootInteraction; + private int operationCounter = 0; + @Nonnull + private final List callStack = new ObjectArrayList<>(); + private int simulatedCallStack = 0; + private final boolean requiresClient; + private int simulatedOperationCounter = 0; + private RootInteraction simulatedRootInteraction; + private int operationIndex = 0; + private int operationIndexOffset = 0; + private int clientOperationIndex = 0; + @Nonnull + private final List interactions = new ObjectArrayList<>(); + @Nonnull + private final List tempSyncData = new ObjectArrayList<>(); + private int tempSyncDataOffset = 0; + private long timestamp = System.nanoTime(); + private long waitingForServerFinished; + private long waitingForClientFinished; + private InteractionState clientState = InteractionState.NotFinished; + private InteractionState serverState = InteractionState.NotFinished; + private InteractionState finalState = InteractionState.Finished; + @Nullable + private Runnable onCompletion; + private boolean sentInitial; + private boolean desynced; + private float timeShift; + private boolean firstRun = true; + private boolean isFirstRun = true; + private boolean completed = false; + private boolean preTicked; + boolean skipChainOnClick; + + public InteractionChain( + InteractionType type, + InteractionContext context, + InteractionChainData chainData, + @Nonnull RootInteraction rootInteraction, + @Nullable Runnable onCompletion, + boolean requiresClient + ) { + this(null, null, type, context, chainData, rootInteraction, onCompletion, requiresClient); + } + + public InteractionChain( + ForkedChainId forkedChainId, + ForkedChainId baseForkedChainId, + InteractionType type, + InteractionContext context, + InteractionChainData chainData, + @Nonnull RootInteraction rootInteraction, + @Nullable Runnable onCompletion, + boolean requiresClient + ) { + this.type = this.baseType = type; + this.context = context; + this.chainData = chainData; + this.forkedChainId = forkedChainId; + this.baseForkedChainId = baseForkedChainId; + this.onCompletion = onCompletion; + this.initialRootInteraction = this.rootInteraction = this.simulatedRootInteraction = rootInteraction; + this.requiresClient = requiresClient || rootInteraction.needsRemoteSync(); + this.forkedChainsMap.defaultReturnValue(NULL_FORK_ID); + } + + public InteractionType getType() { + return this.type; + } + + public int getChainId() { + return this.chainId; + } + + public ForkedChainId getForkedChainId() { + return this.forkedChainId; + } + + public ForkedChainId getBaseForkedChainId() { + return this.baseForkedChainId; + } + + @Nonnull + public RootInteraction getInitialRootInteraction() { + return this.initialRootInteraction; + } + + public boolean isPredicted() { + return this.predicted; + } + + public InteractionContext getContext() { + return this.context; + } + + public InteractionChainData getChainData() { + return this.chainData; + } + + public InteractionState getServerState() { + return this.serverState; + } + + public boolean requiresClient() { + return this.requiresClient; + } + + public RootInteraction getRootInteraction() { + return this.rootInteraction; + } + + public RootInteraction getSimulatedRootInteraction() { + return this.simulatedRootInteraction; + } + + public int getOperationCounter() { + return this.operationCounter; + } + + public void setOperationCounter(int operationCounter) { + this.operationCounter = operationCounter; + } + + public int getSimulatedOperationCounter() { + return this.simulatedOperationCounter; + } + + public void setSimulatedOperationCounter(int simulatedOperationCounter) { + this.simulatedOperationCounter = simulatedOperationCounter; + } + + public boolean wasPreTicked() { + return this.preTicked; + } + + public void setPreTicked(boolean preTicked) { + this.preTicked = preTicked; + } + + public int getOperationIndex() { + return this.operationIndex; + } + + public void nextOperationIndex() { + this.operationIndex++; + this.clientOperationIndex++; + } + + public int getClientOperationIndex() { + return this.clientOperationIndex; + } + + @Nullable + public InteractionChain findForkedChain(@Nonnull ForkedChainId chainId, @Nullable InteractionChainData data) { + long id = forkedIdToIndex(chainId); + long altId = this.forkedChainsMap.get(id); + if (altId != NULL_FORK_ID) { + id = altId; + } + + InteractionChain chain = this.forkedChains.get(id); + if (chain == null && chainId.subIndex < 0 && data != null) { + InteractionEntry entry = this.getInteraction(chainId.entryIndex); + if (entry == null) { + return null; + } else { + int rootId = entry.getServerState().rootInteraction; + int opCounter = entry.getServerState().operationCounter; + RootInteraction root = RootInteraction.getAssetMap().getAsset(rootId); + if (root.getOperation(opCounter).getInnerOperation() instanceof Interaction interaction) { + this.context.initEntry(this, entry, null); + chain = interaction.mapForkChain(this.context, data); + this.context.deinitEntry(this, entry, null); + if (chain != null) { + this.forkedChainsMap.put(id, forkedIdToIndex(chain.getBaseForkedChainId())); + } + + return chain; + } else { + return null; + } + } + } else { + return chain; + } + } + + public InteractionChain getForkedChain(@Nonnull ForkedChainId chainId) { + long id = forkedIdToIndex(chainId); + if (chainId.subIndex < 0) { + long altId = this.forkedChainsMap.get(id); + if (altId != NULL_FORK_ID) { + id = altId; + } + } + + return this.forkedChains.get(id); + } + + public void putForkedChain(@Nonnull ForkedChainId chainId, @Nonnull InteractionChain chain) { + this.newForks.add(chain); + this.forkedChains.put(forkedIdToIndex(chainId), chain); + } + + @Nullable + public InteractionChain.TempChain getTempForkedChain(@Nonnull ForkedChainId chainId) { + InteractionEntry entry = this.getInteraction(chainId.entryIndex); + if (entry != null) { + if (chainId.subIndex < entry.getNextForkId()) { + return null; + } + } else if (chainId.entryIndex < this.operationIndexOffset) { + return null; + } + + return this.tempForkedChainData.computeIfAbsent(forkedIdToIndex(chainId), i -> new InteractionChain.TempChain()); + } + + @Nullable + InteractionChain.TempChain removeTempForkedChain(@Nonnull ForkedChainId chainId, InteractionChain forkChain) { + long id = forkedIdToIndex(chainId); + long altId = this.forkedChainsMap.get(id); + if (altId != NULL_FORK_ID) { + id = altId; + } + + InteractionChain.TempChain found = this.tempForkedChainData.remove(id); + if (found != null) { + return found; + } else { + InteractionEntry iEntry = this.context.getEntry(); + RootInteraction root = RootInteraction.getAssetMap().getAsset(iEntry.getState().rootInteraction); + if (root.getOperation(iEntry.getState().operationCounter).getInnerOperation() instanceof Interaction interaction) { + ObjectIterator> it = Long2ObjectMaps.fastIterator(this.getTempForkedChainData()); + + while (it.hasNext()) { + Entry entry = it.next(); + InteractionChain.TempChain tempChain = entry.getValue(); + if (tempChain.baseForkedChainId != null) { + int entryId = tempChain.baseForkedChainId.entryIndex; + if (entryId == iEntry.getIndex()) { + InteractionChain chain = interaction.mapForkChain(this.getContext(), tempChain.chainData); + if (chain != null) { + this.forkedChainsMap.put(forkedIdToIndex(tempChain.baseForkedChainId), forkedIdToIndex(chain.getBaseForkedChainId())); + } + + if (chain == forkChain) { + it.remove(); + return tempChain; + } + } + } + } + } + + return null; + } + } + + public boolean hasSentInitial() { + return this.sentInitial; + } + + public void setSentInitial(boolean sentInitial) { + this.sentInitial = sentInitial; + } + + public float getTimeShift() { + return this.timeShift; + } + + public void setTimeShift(float timeShift) { + this.timeShift = timeShift; + } + + public boolean consumeFirstRun() { + this.isFirstRun = this.firstRun; + this.firstRun = false; + return this.isFirstRun; + } + + public boolean isFirstRun() { + return this.isFirstRun; + } + + public void setFirstRun(boolean firstRun) { + this.isFirstRun = firstRun; + } + + public int getCallDepth() { + return this.callStack.size(); + } + + public int getSimulatedCallDepth() { + return this.simulatedCallStack; + } + + public void pushRoot(RootInteraction nextInteraction, boolean simulate) { + if (simulate) { + this.simulatedRootInteraction = nextInteraction; + this.simulatedOperationCounter = 0; + this.simulatedCallStack++; + } else { + this.callStack.add(new InteractionChain.CallState(this.rootInteraction, this.operationCounter)); + this.operationCounter = 0; + this.rootInteraction = nextInteraction; + } + } + + public void popRoot() { + InteractionChain.CallState state = this.callStack.removeLast(); + this.rootInteraction = state.rootInteraction; + this.operationCounter = state.operationCounter + 1; + this.simulatedRootInteraction = this.rootInteraction; + this.simulatedOperationCounter = this.operationCounter; + this.simulatedCallStack--; + } + + public float getTimeInSeconds() { + if (this.timestamp == 0L) { + return 0.0F; + } else { + long diff = System.nanoTime() - this.timestamp; + return (float)diff / 1.0E9F; + } + } + + public void setOnCompletion(Runnable onCompletion) { + this.onCompletion = onCompletion; + } + + void onCompletion(CooldownHandler cooldownHandler, boolean isRemote) { + if (!this.completed) { + this.completed = true; + if (this.onCompletion != null) { + this.onCompletion.run(); + this.onCompletion = null; + } + + if (isRemote) { + InteractionCooldown cooldown = this.initialRootInteraction.getCooldown(); + String cooldownId = this.initialRootInteraction.getId(); + if (cooldown != null && cooldown.cooldownId != null) { + cooldownId = cooldown.cooldownId; + } + + CooldownHandler.Cooldown cooldownTracker = cooldownHandler.getCooldown(cooldownId); + if (cooldownTracker != null) { + cooldownTracker.tick(0.016666668F); + } + } + } + } + + void updateServerState() { + if (this.serverState == InteractionState.NotFinished) { + if (this.operationCounter >= this.rootInteraction.getOperationMax()) { + this.serverState = this.finalState; + } else { + InteractionEntry entry = this.getOrCreateInteractionEntry(this.operationIndex); + + this.serverState = switch (entry.getServerState().state) { + case NotFinished, Finished -> InteractionState.NotFinished; + default -> InteractionState.Failed; + }; + } + } + } + + void updateSimulatedState() { + if (this.clientState == InteractionState.NotFinished) { + if (this.simulatedOperationCounter >= this.rootInteraction.getOperationMax()) { + this.clientState = this.finalState; + } else { + InteractionEntry entry = this.getOrCreateInteractionEntry(this.clientOperationIndex); + + this.clientState = switch (entry.getSimulationState().state) { + case NotFinished, Finished -> InteractionState.NotFinished; + default -> InteractionState.Failed; + }; + } + } + } + + @Override + public InteractionState getClientState() { + return this.clientState; + } + + @Override + public void setClientState(InteractionState state) { + this.clientState = state; + } + + @Nonnull + public InteractionEntry getOrCreateInteractionEntry(int index) { + int oIndex = index - this.operationIndexOffset; + if (oIndex < 0) { + throw new IllegalArgumentException("Trying to access removed interaction entry"); + } else { + InteractionEntry entry = oIndex < this.interactions.size() ? this.interactions.get(oIndex) : null; + if (entry == null) { + if (oIndex != this.interactions.size()) { + throw new IllegalArgumentException("Trying to add interaction entry at a weird location: " + oIndex + " " + this.interactions.size()); + } + + entry = new InteractionEntry(index, this.operationCounter, RootInteraction.getRootInteractionIdOrUnknown(this.rootInteraction.getId())); + this.interactions.add(entry); + } + + return entry; + } + } + + @Nullable + @Override + public InteractionEntry getInteraction(int index) { + index -= this.operationIndexOffset; + return index >= 0 && index < this.interactions.size() ? this.interactions.get(index) : null; + } + + public void removeInteractionEntry(@Nonnull InteractionManager interactionManager, int index) { + int oIndex = index - this.operationIndexOffset; + if (oIndex != 0) { + throw new IllegalArgumentException("Trying to remove out of order"); + } else { + InteractionEntry entry = this.interactions.remove(oIndex); + this.operationIndexOffset++; + this.tempForkedChainData.values().removeIf(fork -> { + if (fork.baseForkedChainId.entryIndex != entry.getIndex()) { + return false; + } else { + interactionManager.sendCancelPacket(this.getChainId(), fork.forkedChainId); + return true; + } + }); + } + } + + @Override + public void putInteractionSyncData(int index, InteractionSyncData data) { + index -= this.tempSyncDataOffset; + if (index < 0) { + LOGGER.at(Level.SEVERE) + .log("Attempted to store sync data at %d. Offset: %d, Size: %d", index + this.tempSyncDataOffset, this.tempSyncDataOffset, this.tempSyncData.size()); + } else if (index < this.tempSyncData.size()) { + this.tempSyncData.set(index, data); + } else if (index == this.tempSyncData.size()) { + this.tempSyncData.add(data); + } else { + LOGGER.at(Level.WARNING).log("Temp sync data sent out of order: " + index + " " + this.tempSyncData.size()); + } + } + + @Override + public void clearInteractionSyncData(int operationIndex) { + int tempIdx = operationIndex - this.tempSyncDataOffset; + if (!this.tempSyncData.isEmpty()) { + for (int end = this.tempSyncData.size() - 1; end >= tempIdx && end >= 0; end--) { + this.tempSyncData.remove(end); + } + } + + int idx = operationIndex - this.operationIndexOffset; + + for (int i = Math.max(idx, 0); i < this.interactions.size(); i++) { + this.interactions.get(i).setClientState(null); + } + } + + @Nullable + public InteractionSyncData removeInteractionSyncData(int index) { + index -= this.tempSyncDataOffset; + if (index != 0) { + return null; + } else if (this.tempSyncData.isEmpty()) { + return null; + } else if (this.tempSyncData.get(index) == null) { + return null; + } else { + this.tempSyncDataOffset++; + return this.tempSyncData.remove(index); + } + } + + @Override + public void updateSyncPosition(int index) { + if (this.tempSyncDataOffset == index) { + this.tempSyncDataOffset = index + 1; + } else if (index > this.tempSyncDataOffset) { + throw new IllegalArgumentException("Temp sync data sent out of order: " + index + " " + this.tempSyncData.size()); + } + } + + @Override + public boolean isSyncDataOutOfOrder(int index) { + return index > this.tempSyncDataOffset + this.tempSyncData.size(); + } + + @Override + public void syncFork(@Nonnull Ref ref, @Nonnull InteractionManager manager, @Nonnull SyncInteractionChain packet) { + ForkedChainId baseId = packet.forkedId; + + while (baseId.forkedId != null) { + baseId = baseId.forkedId; + } + + InteractionChain fork = this.findForkedChain(baseId, packet.data); + if (fork != null) { + manager.sync(ref, fork, packet); + } else { + InteractionChain.TempChain temp = this.getTempForkedChain(baseId); + if (temp == null) { + return; + } + + temp.setForkedChainId(packet.forkedId); + temp.setBaseForkedChainId(baseId); + temp.setChainData(packet.data); + manager.sync(ref, temp, packet); + } + } + + public void copyTempFrom(@Nonnull InteractionChain.TempChain temp) { + this.setClientState(temp.clientState); + this.tempSyncData.addAll(temp.tempSyncData); + this.getTempForkedChainData().putAll(temp.tempForkedChainData); + } + + private static long forkedIdToIndex(@Nonnull ForkedChainId chainId) { + return (long)chainId.entryIndex << 32 | chainId.subIndex & 4294967295L; + } + + public void setChainId(int chainId) { + this.chainId = chainId; + } + + public InteractionType getBaseType() { + return this.baseType; + } + + public void setBaseType(InteractionType baseType) { + this.baseType = baseType; + } + + @Nonnull + public Long2ObjectMap getForkedChains() { + return this.forkedChains; + } + + @Nonnull + public Long2ObjectMap getTempForkedChainData() { + return this.tempForkedChainData; + } + + public long getTimestamp() { + return this.timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public long getWaitingForServerFinished() { + return this.waitingForServerFinished; + } + + public void setWaitingForServerFinished(long waitingForServerFinished) { + this.waitingForServerFinished = waitingForServerFinished; + } + + public long getWaitingForClientFinished() { + return this.waitingForClientFinished; + } + + public void setWaitingForClientFinished(long waitingForClientFinished) { + this.waitingForClientFinished = waitingForClientFinished; + } + + public void setServerState(InteractionState serverState) { + this.serverState = serverState; + } + + public InteractionState getFinalState() { + return this.finalState; + } + + public void setFinalState(InteractionState finalState) { + this.finalState = finalState; + } + + void setPredicted(boolean predicted) { + this.predicted = predicted; + } + + public void flagDesync() { + this.desynced = true; + this.forkedChains.forEach((k, c) -> c.flagDesync()); + } + + public boolean isDesynced() { + return this.desynced; + } + + @Nonnull + public List getNewForks() { + return this.newForks; + } + + @Nonnull + @Override + public String toString() { + return "InteractionChain{type=" + + this.type + + ", chainData=" + + this.chainData + + ", chainId=" + + this.chainId + + ", forkedChainId=" + + this.forkedChainId + + ", predicted=" + + this.predicted + + ", context=" + + this.context + + ", forkedChains=" + + this.forkedChains + + ", tempForkedChainData=" + + this.tempForkedChainData + + ", initialRootInteraction=" + + this.initialRootInteraction + + ", rootInteraction=" + + this.rootInteraction + + ", operationCounter=" + + this.operationCounter + + ", callStack=" + + this.callStack + + ", simulatedCallStack=" + + this.simulatedCallStack + + ", requiresClient=" + + this.requiresClient + + ", simulatedOperationCounter=" + + this.simulatedOperationCounter + + ", simulatedRootInteraction=" + + this.simulatedRootInteraction + + ", operationIndex=" + + this.operationIndex + + ", operationIndexOffset=" + + this.operationIndexOffset + + ", clientOperationIndex=" + + this.clientOperationIndex + + ", interactions=" + + this.interactions + + ", tempSyncData=" + + this.tempSyncData + + ", tempSyncDataOffset=" + + this.tempSyncDataOffset + + ", timestamp=" + + this.timestamp + + ", waitingForServerFinished=" + + this.waitingForServerFinished + + ", waitingForClientFinished=" + + this.waitingForClientFinished + + ", clientState=" + + this.clientState + + ", serverState=" + + this.serverState + + ", onCompletion=" + + this.onCompletion + + ", sentInitial=" + + this.sentInitial + + ", desynced=" + + this.desynced + + ", timeShift=" + + this.timeShift + + ", firstRun=" + + this.firstRun + + ", skipChainOnClick=" + + this.skipChainOnClick + + "}"; + } + + private record CallState(RootInteraction rootInteraction, int operationCounter) { + } + + static class TempChain implements ChainSyncStorage { + final Long2ObjectMap tempForkedChainData = new Long2ObjectOpenHashMap<>(); + final List tempSyncData = new ObjectArrayList<>(); + ForkedChainId forkedChainId; + InteractionState clientState = InteractionState.NotFinished; + ForkedChainId baseForkedChainId; + InteractionChainData chainData; + + TempChain() { + } + + @Nonnull + public InteractionChain.TempChain getOrCreateTempForkedChain(@Nonnull ForkedChainId chainId) { + return this.tempForkedChainData.computeIfAbsent(InteractionChain.forkedIdToIndex(chainId), i -> new InteractionChain.TempChain()); + } + + @Override + public InteractionState getClientState() { + return this.clientState; + } + + @Override + public void setClientState(InteractionState state) { + this.clientState = state; + } + + @Nullable + @Override + public InteractionEntry getInteraction(int index) { + return null; + } + + @Override + public void putInteractionSyncData(int index, InteractionSyncData data) { + if (index < this.tempSyncData.size()) { + this.tempSyncData.set(index, data); + } else { + if (index != this.tempSyncData.size()) { + throw new IllegalArgumentException("Temp sync data sent out of order: " + index + " " + this.tempSyncData.size()); + } + + this.tempSyncData.add(data); + } + } + + @Override + public void updateSyncPosition(int index) { + } + + @Override + public boolean isSyncDataOutOfOrder(int index) { + return index > this.tempSyncData.size(); + } + + @Override + public void syncFork(@Nonnull Ref ref, @Nonnull InteractionManager manager, @Nonnull SyncInteractionChain packet) { + ForkedChainId baseId = packet.forkedId; + + while (baseId.forkedId != null) { + baseId = baseId.forkedId; + } + + InteractionChain.TempChain temp = this.getOrCreateTempForkedChain(baseId); + temp.setForkedChainId(packet.forkedId); + temp.setBaseForkedChainId(baseId); + temp.setChainData(packet.data); + manager.sync(ref, temp, packet); + } + + @Override + public void clearInteractionSyncData(int index) { + for (int end = this.tempSyncData.size() - 1; end >= index; end--) { + this.tempSyncData.remove(end); + } + } + + public InteractionChainData getChainData() { + return this.chainData; + } + + public void setChainData(InteractionChainData chainData) { + this.chainData = chainData; + } + + public ForkedChainId getBaseForkedChainId() { + return this.baseForkedChainId; + } + + public void setBaseForkedChainId(ForkedChainId baseForkedChainId) { + this.baseForkedChainId = baseForkedChainId; + } + + public void setForkedChainId(ForkedChainId forkedChainId) { + this.forkedChainId = forkedChainId; + } + + @Nonnull + @Override + public String toString() { + return "TempChain{tempForkedChainData=" + this.tempForkedChainData + ", tempSyncData=" + this.tempSyncData + ", clientState=" + this.clientState + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/InteractionContext.java b/src/com/hypixel/hytale/server/core/entity/InteractionContext.java new file mode 100644 index 0000000..bb3ac30 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/InteractionContext.java @@ -0,0 +1,571 @@ +package com.hypixel.hytale.server.core.entity; + +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector4d; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.ForkedChainId; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.InteractionChainData; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.PrioritySlot; +import com.hypixel.hytale.protocol.RootInteractionSettings; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemContext; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.meta.DynamicMetaStore; +import com.hypixel.hytale.server.core.modules.entity.component.SnapshotBuffer; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.modules.interaction.Interactions; +import com.hypixel.hytale.server.core.modules.interaction.interaction.UnarmedInteractions; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.Label; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InteractionContext { + @Nonnull + private static final Function> DEFAULT_VAR_GETTER = InteractionContext::defaultGetVars; + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final int heldItemSectionId; + @Nullable + private final ItemContainer heldItemContainer; + private final byte heldItemSlot; + @Nullable + private ItemStack heldItem; + @Nullable + private final Item originalItemType; + private Function> interactionVarsGetter = DEFAULT_VAR_GETTER; + @Nullable + private final InteractionManager interactionManager; + @Nullable + private final Ref owningEntity; + @Nullable + private final Ref runningForEntity; + @Nullable + private LivingEntity entity; + @Nullable + private InteractionChain chain; + @Nullable + private InteractionEntry entry; + @Nullable + private Label[] labels; + @Nullable + private InteractionContext.SnapshotProvider snapshotProvider; + @Nonnull + private final DynamicMetaStore metaStore; + + private InteractionContext( + @Nullable InteractionManager interactionManager, + @Nullable Ref owningEntity, + int heldItemSectionId, + @Nullable ItemContainer heldItemContainer, + byte heldItemSlot, + @Nullable ItemStack heldItem + ) { + this(interactionManager, owningEntity, owningEntity, heldItemSectionId, heldItemContainer, heldItemSlot, heldItem); + } + + private InteractionContext( + @Nullable InteractionManager interactionManager, + @Nullable Ref owningEntity, + @Nullable Ref runningForEntity, + int heldItemSectionId, + @Nullable ItemContainer heldItemContainer, + byte heldItemSlot, + @Nullable ItemStack heldItem + ) { + this.interactionManager = interactionManager; + this.owningEntity = owningEntity; + this.runningForEntity = runningForEntity; + this.heldItemSectionId = heldItemSectionId; + this.heldItemContainer = heldItemContainer; + this.heldItemSlot = heldItemSlot; + this.heldItem = heldItem; + this.originalItemType = heldItem != null ? heldItem.getItem() : null; + this.metaStore = new DynamicMetaStore<>(this, Interaction.CONTEXT_META_REGISTRY); + } + + @Nonnull + public InteractionChain fork(@Nonnull InteractionContext context, @Nonnull RootInteraction rootInteraction, boolean predicted) { + assert this.chain != null; + + return this.fork(this.chain.getType(), context, rootInteraction, predicted); + } + + @Nonnull + public InteractionChain fork(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull RootInteraction rootInteraction, boolean predicted) { + InteractionChainData data = new InteractionChainData(this.chain.getChainData()); + return this.fork(data, type, context, rootInteraction, predicted); + } + + @Nonnull + public InteractionChain fork( + @Nonnull InteractionChainData data, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nonnull RootInteraction rootInteraction, + boolean predicted + ) { + if (context == this) { + throw new IllegalArgumentException("Cannot use current context"); + } else { + Integer slot = context.metaStore.getMetaObject(Interaction.TARGET_SLOT); + if (slot == null) { + slot = this.metaStore.getMetaObject(Interaction.TARGET_SLOT); + context.metaStore.putMetaObject(Interaction.TARGET_SLOT, slot); + } + + if (slot != null) { + data.targetSlot = slot; + } + + Ref targetEntity = context.metaStore.getIfPresentMetaObject(Interaction.TARGET_ENTITY); + if (targetEntity != null && targetEntity.isValid()) { + CommandBuffer commandBuffer = this.getCommandBuffer(); + + assert commandBuffer != null; + + NetworkId networkComponent = commandBuffer.getComponent(targetEntity, NetworkId.getComponentType()); + if (networkComponent != null) { + data.entityId = networkComponent.getId(); + } + } + + Vector4d hitLocation = context.metaStore.getIfPresentMetaObject(Interaction.HIT_LOCATION); + if (hitLocation != null) { + data.hitLocation = new Vector3f((float)hitLocation.x, (float)hitLocation.y, (float)hitLocation.z); + } + + String hitDetail = context.metaStore.getIfPresentMetaObject(Interaction.HIT_DETAIL); + if (hitDetail != null) { + data.hitDetail = hitDetail; + } + + BlockPosition targetBlock = context.metaStore.getIfPresentMetaObject(Interaction.TARGET_BLOCK_RAW); + if (targetBlock != null) { + data.blockPosition = targetBlock; + } + + int index = this.chain.getChainId(); + ForkedChainId forkedChainId = this.chain.getForkedChainId(); + ForkedChainId newChainId = new ForkedChainId(this.entry.getIndex(), this.entry.nextForkId(), null); + if (forkedChainId != null) { + ForkedChainId root = forkedChainId = new ForkedChainId(forkedChainId); + + while (root.forkedId != null) { + root = root.forkedId; + } + + root.forkedId = newChainId; + } else { + forkedChainId = newChainId; + } + + InteractionChain forkChain = new InteractionChain(forkedChainId, newChainId, type, context, data, rootInteraction, null, true); + forkChain.setChainId(index); + forkChain.setBaseType(this.chain.getBaseType()); + forkChain.setPredicted(predicted); + forkChain.skipChainOnClick = this.allowSkipChainOnClick(); + forkChain.setTimeShift(this.chain.getTimeShift()); + this.chain.putForkedChain(newChainId, forkChain); + InteractionChain.TempChain tempData = this.chain.removeTempForkedChain(newChainId, forkChain); + if (tempData != null) { + LOGGER.at(Level.FINEST).log("Loading temp chain data for fork %s", newChainId); + forkChain.copyTempFrom(tempData); + } + + return forkChain; + } + } + + @Nonnull + public InteractionContext duplicate() { + InteractionContext ctx = new InteractionContext( + this.interactionManager, this.owningEntity, this.runningForEntity, this.heldItemSectionId, this.heldItemContainer, this.heldItemSlot, this.heldItem + ); + ctx.interactionVarsGetter = this.interactionVarsGetter; + ctx.metaStore.copyFrom(this.metaStore); + return ctx; + } + + @Nonnull + public Ref getEntity() { + return this.runningForEntity; + } + + @Nonnull + public Ref getOwningEntity() { + return this.owningEntity; + } + + public void execute(@Nonnull RootInteraction nextInteraction) { + this.chain.getContext().getState().enteredRootInteraction = RootInteraction.getAssetMap().getIndex(nextInteraction.getId()); + this.chain.pushRoot(nextInteraction, this.entry.isUseSimulationState()); + } + + @Nullable + public InteractionChain getChain() { + return this.chain; + } + + @Nullable + public InteractionEntry getEntry() { + return this.entry; + } + + public int getOperationCounter() { + return this.entry.isUseSimulationState() ? this.chain.getSimulatedOperationCounter() : this.chain.getOperationCounter(); + } + + public void setOperationCounter(int operationCounter) { + if (this.entry.isUseSimulationState()) { + this.chain.setSimulatedOperationCounter(operationCounter); + } else { + this.chain.setOperationCounter(operationCounter); + } + } + + public void jump(@Nonnull Label label) { + this.setOperationCounter(label.getIndex()); + } + + @Nullable + public Item getOriginalItemType() { + return this.originalItemType; + } + + public int getHeldItemSectionId() { + return this.heldItemSectionId; + } + + @Nullable + public ItemContainer getHeldItemContainer() { + return this.heldItemContainer; + } + + public byte getHeldItemSlot() { + return this.heldItemSlot; + } + + @Nullable + public ItemStack getHeldItem() { + return this.heldItem; + } + + public void setHeldItem(@Nullable ItemStack heldItem) { + this.heldItem = heldItem; + } + + @Nullable + public ItemContext createHeldItemContext() { + return this.heldItemContainer != null && this.heldItem != null ? new ItemContext(this.heldItemContainer, this.heldItemSlot, this.heldItem) : null; + } + + public Function> getInteractionVarsGetter() { + return this.interactionVarsGetter; + } + + public Map getInteractionVars() { + return this.interactionVarsGetter.apply(this); + } + + public void setInteractionVarsGetter(Function> interactionVarsGetter) { + this.interactionVarsGetter = interactionVarsGetter; + } + + public InteractionManager getInteractionManager() { + return this.interactionManager; + } + + @Nullable + public Ref getTargetEntity() { + return this.metaStore.getIfPresentMetaObject(Interaction.TARGET_ENTITY); + } + + @Nullable + public BlockPosition getTargetBlock() { + return this.metaStore.getIfPresentMetaObject(Interaction.TARGET_BLOCK); + } + + @Nonnull + public DynamicMetaStore getMetaStore() { + return this.metaStore; + } + + @Nonnull + public InteractionSyncData getState() { + return this.entry.getState(); + } + + @Nullable + public InteractionSyncData getClientState() { + return this.entry.getClientState(); + } + + @Nonnull + public InteractionSyncData getServerState() { + return this.entry.getServerState(); + } + + @Nonnull + public DynamicMetaStore getInstanceStore() { + return this.entry.getMetaStore(); + } + + public boolean allowSkipChainOnClick() { + return this.chain.skipChainOnClick; + } + + public void setLabels(Label[] labels) { + this.labels = labels; + } + + public boolean hasLabels() { + return this.labels != null; + } + + public Label getLabel(int index) { + return this.labels[index]; + } + + public EntitySnapshot getSnapshot(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + NetworkId networkIdComponent = componentAccessor.getComponent(ref, NetworkId.getComponentType()); + + assert networkIdComponent != null; + + int networkId = networkIdComponent.getId(); + if (this.snapshotProvider != null) { + return this.snapshotProvider.getSnapshot(this.getCommandBuffer(), this.runningForEntity, networkId); + } else { + SnapshotBuffer snapshotBufferComponent = componentAccessor.getComponent(ref, SnapshotBuffer.getComponentType()); + + assert snapshotBufferComponent != null; + + EntitySnapshot snapshot = snapshotBufferComponent.getSnapshot(snapshotBufferComponent.getCurrentTickIndex()); + if (snapshot != null) { + return snapshot; + } else { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + return new EntitySnapshot(transformComponent.getPosition(), transformComponent.getRotation()); + } + } + } + + public void setSnapshotProvider(@Nullable InteractionContext.SnapshotProvider snapshotProvider) { + this.snapshotProvider = snapshotProvider; + } + + public void setTimeShift(float shift) { + if (!this.entry.isUseSimulationState()) { + this.chain.setTimeShift(shift); + if (this.chain.getForkedChainId() == null) { + this.interactionManager.setGlobalTimeShift(this.chain.getType(), shift); + } + } + } + + @Nullable + public CommandBuffer getCommandBuffer() { + return this.interactionManager.commandBuffer; + } + + @Nullable + public String getRootInteractionId(@Nonnull InteractionType type) { + if (this.runningForEntity != null && this.runningForEntity.isValid()) { + Interactions interactions = this.runningForEntity.getStore().getComponent(this.runningForEntity, Interactions.getComponentType()); + if (interactions != null) { + String interactionId = interactions.getInteractionId(type); + if (interactionId != null) { + return interactionId; + } + } + } + + Item heldItem = this.originalItemType; + String interactionIds; + if (heldItem == null) { + UnarmedInteractions unarmedInteraction = UnarmedInteractions.getAssetMap().getAsset("Empty"); + interactionIds = unarmedInteraction != null ? unarmedInteraction.getInteractions().get(type) : null; + } else { + interactionIds = heldItem.getInteractions().get(type); + } + + return interactionIds; + } + + void initEntry(@Nonnull InteractionChain chain, InteractionEntry entry, @Nullable LivingEntity entity) { + CommandBuffer commandBuffer = this.getCommandBuffer(); + + assert commandBuffer != null; + + this.chain = chain; + this.entry = entry; + this.entity = entity; + this.labels = null; + Player playerComponent = null; + if (entity != null) { + playerComponent = commandBuffer.getComponent(entity.getReference(), Player.getComponentType()); + } + + GameMode gameMode = playerComponent != null ? playerComponent.getGameMode() : GameMode.Adventure; + RootInteractionSettings settings = chain.getRootInteraction().getSettings().get(gameMode); + chain.skipChainOnClick = chain.skipChainOnClick | (settings != null && settings.allowSkipChainOnClick); + } + + void deinitEntry(InteractionChain chain, InteractionEntry entry, LivingEntity entity) { + this.chain = null; + this.entry = null; + this.entity = null; + this.labels = null; + } + + @Nonnull + @Override + public String toString() { + return "InteractionContext{heldItemSectionId=" + + this.heldItemSectionId + + ", heldItemContainer=" + + this.heldItemContainer + + ", heldItemSlot=" + + this.heldItemSlot + + ", heldItem=" + + this.heldItem + + ", originalItemType=" + + this.originalItemType + + ", interactionVarsGetter=" + + this.interactionVarsGetter + + ", entity=" + + this.entity + + ", labels=" + + Arrays.toString((Object[])this.labels) + + ", snapshotProvider=" + + this.snapshotProvider + + ", metaStore=" + + this.metaStore + + "}"; + } + + @Nonnull + public static InteractionContext forProxyEntity(InteractionManager manager, @Nonnull LivingEntity entity, Ref runningForEntity) { + Inventory entityInventory = entity.getInventory(); + return new InteractionContext( + manager, + entity.getReference(), + runningForEntity, + -1, + entityInventory.getHotbar(), + entityInventory.getActiveHotbarSlot(), + entityInventory.getItemInHand() + ); + } + + @Nonnull + public static InteractionContext forInteraction( + @Nonnull InteractionManager manager, + @Nonnull Ref ref, + @Nonnull InteractionType type, + @Nonnull ComponentAccessor componentAccessor + ) { + if (type == InteractionType.Equipped) { + throw new IllegalArgumentException("Equipped interaction type requires a slot set"); + } else { + return forInteraction(manager, ref, type, 0, componentAccessor); + } + } + + @Nonnull + public static InteractionContext forInteraction( + @Nonnull InteractionManager manager, + @Nonnull Ref ref, + @Nonnull InteractionType type, + int equipSlot, + @Nonnull ComponentAccessor componentAccessor + ) { + LivingEntity entity = (LivingEntity)EntityUtils.getEntity(ref, componentAccessor); + Inventory entityInventory = entity.getInventory(); + switch (type) { + case Equipped: + return new InteractionContext( + manager, ref, -3, entityInventory.getArmor(), (byte)equipSlot, entityInventory.getArmor().getItemStack((short)equipSlot) + ); + case HeldOffhand: + return new InteractionContext( + manager, ref, -5, entityInventory.getUtility(), entityInventory.getActiveUtilitySlot(), entityInventory.getUtilityItem() + ); + case Ability1: + case Ability2: + case Ability3: + case Pick: + case Primary: + case Secondary: + if (entityInventory.usingToolsItem()) { + return new InteractionContext(manager, ref, -8, entityInventory.getTools(), entityInventory.getActiveToolsSlot(), entityInventory.getToolsItem()); + } else { + ItemStack primary = entityInventory.getItemInHand(); + ItemStack secondary = entityInventory.getUtilityItem(); + int selectedInventory = -1; + if (primary == null && secondary != null) { + selectedInventory = -5; + } else if (primary != null && secondary != null) { + int prioPrimary = primary.getItem().getInteractionConfig().getPriorityFor(type, PrioritySlot.MainHand); + int prioSecondary = secondary.getItem().getInteractionConfig().getPriorityFor(type, PrioritySlot.OffHand); + if (prioPrimary == prioSecondary) { + if (type == InteractionType.Secondary && primary.getItem().getUtility().isCompatible()) { + selectedInventory = -5; + } + } else if (prioPrimary < prioSecondary) { + selectedInventory = -5; + } + } + + if (selectedInventory == -5) { + return new InteractionContext( + manager, ref, -5, entityInventory.getUtility(), entityInventory.getActiveUtilitySlot(), entityInventory.getUtilityItem() + ); + } + + return new InteractionContext( + manager, ref, -1, entityInventory.getHotbar(), entityInventory.getActiveHotbarSlot(), entityInventory.getItemInHand() + ); + } + case Held: + default: + return new InteractionContext(manager, ref, -1, entityInventory.getHotbar(), entityInventory.getActiveHotbarSlot(), entityInventory.getItemInHand()); + } + } + + @Nonnull + public static InteractionContext withoutEntity() { + return new InteractionContext(null, null, -1, null, (byte)-1, null); + } + + @Nullable + private static Map defaultGetVars(@Nonnull InteractionContext c) { + Item item = c.originalItemType; + return item != null ? item.getInteractionVars() : null; + } + + @Deprecated + @FunctionalInterface + public interface SnapshotProvider { + EntitySnapshot getSnapshot(CommandBuffer var1, Ref var2, int var3); + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/InteractionEntry.java b/src/com/hypixel/hytale/server/core/entity/InteractionEntry.java new file mode 100644 index 0000000..4a8ad7e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/InteractionEntry.java @@ -0,0 +1,227 @@ +package com.hypixel.hytale.server.core.entity; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.server.core.meta.DynamicMetaStore; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.Operation; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InteractionEntry { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final int index; + @Nonnull + private final DynamicMetaStore metaStore; + private long timestamp; + private long simulationTimestamp; + private final InteractionSyncData serverState = new InteractionSyncData(); + private InteractionSyncData simulationState; + @Nullable + private InteractionSyncData clientState; + private long waitingForSyncData; + private long waitingForServerFinished; + private long waitingForClientFinished; + private boolean useSimulationState; + private boolean desynced; + private boolean shouldSendInitial = true; + + public InteractionEntry(int index, int counter, int rootInteraction) { + this.index = index; + this.metaStore = new DynamicMetaStore<>(null, Interaction.META_REGISTRY); + this.serverState.operationCounter = counter; + this.serverState.rootInteraction = rootInteraction; + this.serverState.state = InteractionState.NotFinished; + } + + public int getIndex() { + return this.index; + } + + public int nextForkId() { + return this.serverState.totalForks++; + } + + public int getNextForkId() { + return this.serverState.totalForks; + } + + @Nonnull + public InteractionSyncData getState() { + return this.useSimulationState ? this.getSimulationState() : this.getServerState(); + } + + public void setUseSimulationState(boolean useSimulationState) { + this.useSimulationState = useSimulationState; + } + + public float getTimeInSeconds(long tickTime) { + long timestamp = this.getTimestamp(); + if (timestamp == 0L) { + return 0.0F; + } else { + long diff = tickTime - timestamp; + return (float)diff / 1.0E9F; + } + } + + public void setTimestamp(long timestamp, float shift) { + timestamp -= (long)(shift * 1.0E9F); + if (this.useSimulationState) { + this.simulationTimestamp = timestamp; + } else { + this.timestamp = timestamp; + } + } + + public long getTimestamp() { + return this.useSimulationState ? this.simulationTimestamp : this.timestamp; + } + + public boolean isUseSimulationState() { + return this.useSimulationState; + } + + @Nullable + public InteractionSyncData getClientState() { + return this.clientState; + } + + @Nonnull + public DynamicMetaStore getMetaStore() { + return this.metaStore; + } + + public int getServerDataHashCode() { + InteractionSyncData serverData = this.getState(); + float progress = serverData.progress; + serverData.progress = (int)progress; + int hashCode = serverData.hashCode(); + serverData.progress = progress; + return hashCode; + } + + @Nonnull + public InteractionSyncData getServerState() { + return this.serverState; + } + + @Nonnull + public InteractionSyncData getSimulationState() { + if (this.simulationState == null) { + this.simulationState = new InteractionSyncData(); + this.simulationState.operationCounter = this.serverState.operationCounter; + this.simulationState.rootInteraction = this.serverState.rootInteraction; + this.simulationState.state = InteractionState.NotFinished; + } + + return this.simulationState; + } + + public boolean setClientState(@Nullable InteractionSyncData clientState) { + if (clientState != null + && (clientState.operationCounter != this.serverState.operationCounter || clientState.rootInteraction != this.serverState.rootInteraction)) { + HytaleLogger.Api ctx = LOGGER.at(Level.WARNING); + if (ctx.isEnabled()) { + RootInteraction root = RootInteraction.getAssetMap().getAsset(this.serverState.rootInteraction); + Operation op = root.getOperation(this.serverState.operationCounter); + String info; + if (op.getInnerOperation() instanceof Interaction interaction) { + info = interaction.getId() + " (" + interaction.getClass().getSimpleName() + ")"; + } else { + info = op + " (" + op.getClass().getSimpleName() + ")"; + } + + ctx.log( + "%d: Client/Server desync %d != %d, %d != %d (for %s)", + this.index, + this.serverState.operationCounter, + clientState.operationCounter, + this.serverState.rootInteraction, + clientState.rootInteraction, + info + ); + } + + return false; + } else { + this.clientState = clientState; + return true; + } + } + + public long getWaitingForSyncData() { + return this.waitingForSyncData; + } + + public void setWaitingForSyncData(long waitingForSyncData) { + this.waitingForSyncData = waitingForSyncData; + } + + public long getWaitingForServerFinished() { + return this.waitingForServerFinished; + } + + public void setWaitingForServerFinished(long waitingForServerFinished) { + this.waitingForServerFinished = waitingForServerFinished; + } + + public long getWaitingForClientFinished() { + return this.waitingForClientFinished; + } + + public void setWaitingForClientFinished(long waitingForClientFinished) { + this.waitingForClientFinished = waitingForClientFinished; + } + + public boolean consumeDesyncFlag() { + boolean flag = this.desynced; + this.desynced = false; + return flag; + } + + public void flagDesync() { + this.desynced = true; + } + + public boolean consumeSendInitial() { + boolean flag = this.shouldSendInitial; + this.shouldSendInitial = false; + return flag; + } + + @Nonnull + @Override + public String toString() { + return "InteractionEntry{index=" + + this.index + + ", metaStore=" + + this.metaStore + + ", timestamp=" + + this.timestamp + + ", getTimeInSeconds()=" + + this.getTimeInSeconds(System.nanoTime()) + + ", simulationTimestamp=" + + this.simulationTimestamp + + ", serverState=" + + this.serverState + + ", simulationState=" + + this.simulationState + + ", clientState=" + + this.clientState + + ", waitingForSyncData=" + + this.waitingForSyncData + + ", waitingForServerFinished=" + + this.waitingForServerFinished + + ", waitingForClientFinished=" + + this.waitingForClientFinished + + ", useSimulationState=" + + this.useSimulationState + + ", desynced=" + + this.desynced + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/InteractionManager.java b/src/com/hypixel/hytale/server/core/entity/InteractionManager.java new file mode 100644 index 0000000..18f8d8f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/InteractionManager.java @@ -0,0 +1,1448 @@ +package com.hypixel.hytale.server.core.entity; + +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.common.util.ListUtil; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.function.function.TriFunction; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector4d; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.ForkedChainId; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.InteractionChainData; +import com.hypixel.hytale.protocol.InteractionCooldown; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.RootInteractionSettings; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.protocol.packets.interaction.CancelInteractionChain; +import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChain; +import com.hypixel.hytale.protocol.packets.inventory.SetActiveSlot; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.io.handlers.game.GamePacketHandler; +import com.hypixel.hytale.server.core.modules.interaction.IInteractionSimulationHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.InteractionTypeUtils; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.Collector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.CollectorTag; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.Operation; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.UUIDUtil; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.Arrays; +import java.util.Deque; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InteractionManager implements Component { + public static final double MAX_REACH_DISTANCE = 8.0; + public static final float[] DEFAULT_CHARGE_TIMES = new float[]{0.0F}; + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private final Int2ObjectMap chains = new Int2ObjectOpenHashMap<>(); + @Nonnull + private final Int2ObjectMap unmodifiableChains = Int2ObjectMaps.unmodifiable(this.chains); + @Nonnull + private final CooldownHandler cooldownHandler = new CooldownHandler(); + @Nonnull + private final LivingEntity entity; + @Nullable + private final PlayerRef playerRef; + private boolean hasRemoteClient; + @Nonnull + private final IInteractionSimulationHandler interactionSimulationHandler; + @Nonnull + private final ObjectList tempSyncDataList = new ObjectArrayList<>(); + private int lastServerChainId; + private int lastClientChainId; + private long packetQueueTime; + private final float[] globalTimeShift = new float[InteractionType.VALUES.length]; + private final boolean[] globalTimeShiftDirty = new boolean[InteractionType.VALUES.length]; + private boolean timeShiftsDirty; + private final ObjectList syncPackets = new ObjectArrayList<>(); + @Nonnull + private final ObjectList chainStartQueue = new ObjectArrayList<>(); + @Nonnull + private final Predicate cachedTickChain = this::tickChain; + @Nullable + protected CommandBuffer commandBuffer; + + public InteractionManager(@Nonnull LivingEntity entity, @Nullable PlayerRef playerRef, @Nonnull IInteractionSimulationHandler simulationHandler) { + this.entity = entity; + this.playerRef = playerRef; + this.hasRemoteClient = playerRef != null; + this.interactionSimulationHandler = simulationHandler; + } + + @Nonnull + public Int2ObjectMap getChains() { + return this.unmodifiableChains; + } + + @Nonnull + public IInteractionSimulationHandler getInteractionSimulationHandler() { + return this.interactionSimulationHandler; + } + + private long getOperationTimeoutThreshold() { + if (this.playerRef != null) { + return this.playerRef.getPacketHandler().getOperationTimeoutThreshold(); + } else { + assert this.commandBuffer != null; + + World world = this.commandBuffer.getExternalData().getWorld(); + return world.getTickStepNanos() / 1000000 * 10; + } + } + + private boolean waitingForClient(@Nonnull Ref ref) { + assert this.commandBuffer != null; + + Player playerComponent = this.commandBuffer.getComponent(ref, Player.getComponentType()); + return playerComponent != null ? playerComponent.isWaitingForClientReady() : false; + } + + @Deprecated(forRemoval = true) + public void setHasRemoteClient(boolean hasRemoteClient) { + this.hasRemoteClient = hasRemoteClient; + } + + @Deprecated + public void copyFrom(@Nonnull InteractionManager interactionManager) { + this.chains.putAll(interactionManager.chains); + } + + public void tick(@Nonnull Ref ref, @Nonnull CommandBuffer commandBuffer, float dt) { + this.commandBuffer = commandBuffer; + this.clearAllGlobalTimeShift(dt); + this.cooldownHandler.tick(dt); + + for (InteractionChain interactionChain : this.chainStartQueue) { + this.executeChain0(ref, interactionChain); + } + + this.chainStartQueue.clear(); + Deque packetQueue = null; + if (this.playerRef != null) { + packetQueue = ((GamePacketHandler)this.playerRef.getPacketHandler()).getInteractionPacketQueue(); + } + + if (packetQueue != null && !packetQueue.isEmpty()) { + for (boolean first = true; this.tryConsumePacketQueue(ref, packetQueue) || first; first = false) { + if (!this.chains.isEmpty()) { + this.chains.values().removeIf(this.cachedTickChain); + } + + float cooldownDt = 0.0F; + + for (float shift : this.globalTimeShift) { + cooldownDt = Math.max(cooldownDt, shift); + } + + if (cooldownDt > 0.0F) { + this.cooldownHandler.tick(cooldownDt); + } + } + + this.commandBuffer = null; + } else { + if (!this.chains.isEmpty()) { + this.chains.values().removeIf(this.cachedTickChain); + } + + this.commandBuffer = null; + } + } + + private boolean tryConsumePacketQueue(@Nonnull Ref ref, @Nonnull Deque packetQueue) { + Iterator it = packetQueue.iterator(); + boolean finished = false; + boolean desynced = false; + int highestChainId = -1; + boolean changed = false; + + label99: + while (it.hasNext()) { + SyncInteractionChain packet = it.next(); + if (packet.desync) { + HytaleLogger.Api context = LOGGER.at(Level.FINE); + if (context.isEnabled()) { + context.log("Client packet flagged as desync"); + } + + desynced = true; + } + + InteractionChain chain = this.chains.get(packet.chainId); + if (chain != null && packet.forkedId != null) { + for (ForkedChainId id = packet.forkedId; id != null; id = id.forkedId) { + InteractionChain subChain = chain.getForkedChain(id); + if (subChain == null) { + InteractionChain.TempChain tempChain = chain.getTempForkedChain(id); + if (tempChain != null) { + tempChain.setBaseForkedChainId(id); + ForkedChainId lastId = id; + + for (ForkedChainId var17 = id.forkedId; var17 != null; var17 = var17.forkedId) { + tempChain = tempChain.getOrCreateTempForkedChain(var17); + tempChain.setBaseForkedChainId(var17); + lastId = var17; + } + + tempChain.setForkedChainId(packet.forkedId); + tempChain.setBaseForkedChainId(lastId); + tempChain.setChainData(packet.data); + this.sync(ref, tempChain, packet); + changed = true; + it.remove(); + this.packetQueueTime = 0L; + } + continue label99; + } + + chain = subChain; + } + } + + highestChainId = Math.max(highestChainId, packet.chainId); + if (chain == null && !finished) { + if (this.syncStart(ref, packet)) { + changed = true; + it.remove(); + this.packetQueueTime = 0L; + } else { + if (!this.waitingForClient(ref)) { + long queuedTime; + if (this.packetQueueTime == 0L) { + this.packetQueueTime = System.nanoTime(); + queuedTime = 0L; + } else { + queuedTime = System.nanoTime() - this.packetQueueTime; + } + + HytaleLogger.Api context = LOGGER.at(Level.FINE); + if (context.isEnabled()) { + context.log("Queued chain %d for %s", packet.chainId, FormatUtil.nanosToString(queuedTime)); + } + + if (queuedTime > TimeUnit.MILLISECONDS.toNanos(this.getOperationTimeoutThreshold())) { + this.sendCancelPacket(packet.chainId, packet.forkedId); + it.remove(); + context = LOGGER.at(Level.FINE); + if (context.isEnabled()) { + context.log("Discarding packet due to queuing for too long: %s", packet); + } + } + } + + if (!desynced) { + finished = true; + } + } + } else if (chain != null) { + this.sync(ref, chain, packet); + changed = true; + it.remove(); + this.packetQueueTime = 0L; + } else if (desynced) { + this.sendCancelPacket(packet.chainId, packet.forkedId); + it.remove(); + HytaleLogger.Api ctx = LOGGER.at(Level.FINE); + ctx.log("Discarding packet due to desync: %s", packet); + } + } + + if (desynced && !packetQueue.isEmpty()) { + HytaleLogger.Api ctx = LOGGER.at(Level.FINE); + if (ctx.isEnabled()) { + ctx.log("Discarding previous packets in queue: (before) %d", packetQueue.size()); + } + + packetQueue.removeIf(v -> { + boolean shouldRemove = this.getChain(v.chainId, v.forkedId) == null && UUIDUtil.isEmptyOrNull(v.data.proxyId) && v.initial; + if (shouldRemove) { + HytaleLogger.Api ctx1 = LOGGER.at(Level.FINE); + if (ctx1.isEnabled()) { + ctx1.log("Discarding: %s", v); + } + + this.sendCancelPacket(v.chainId, v.forkedId); + } + + return shouldRemove; + }); + ctx = LOGGER.at(Level.FINE); + if (ctx.isEnabled()) { + ctx.log("Discarded previous packets in queue: (after) %d", packetQueue.size()); + } + } + + return changed; + } + + @Nullable + private InteractionChain getChain(int chainId, @Nullable ForkedChainId forkedChainId) { + InteractionChain chain = this.chains.get(chainId); + if (chain != null && forkedChainId != null) { + for (ForkedChainId id = forkedChainId; id != null; id = id.forkedId) { + InteractionChain subChain = chain.getForkedChain(id); + if (subChain == null) { + return null; + } + + chain = subChain; + } + } + + return chain; + } + + private boolean tickChain(@Nonnull InteractionChain chain) { + if (chain.wasPreTicked()) { + chain.setPreTicked(false); + return false; + } else { + if (!this.hasRemoteClient) { + chain.updateSimulatedState(); + } + + chain.getForkedChains().values().removeIf(this.cachedTickChain); + Ref ref = this.entity.getReference(); + + assert ref != null; + + if (chain.getServerState() != InteractionState.NotFinished) { + if (chain.requiresClient() && chain.getClientState() == InteractionState.NotFinished) { + if (!this.waitingForClient(ref)) { + if (chain.getWaitingForClientFinished() == 0L) { + chain.setWaitingForClientFinished(System.nanoTime()); + } + + long waitMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - chain.getWaitingForClientFinished()); + HytaleLogger.Api context = LOGGER.at(Level.FINE); + if (context.isEnabled()) { + context.log("Server finished chain but client hasn't! %d, %s, %s", chain.getChainId(), chain, waitMillis); + } + + long threshold = this.getOperationTimeoutThreshold(); + TimeResource timeResource = this.commandBuffer.getResource(TimeResource.getResourceType()); + if (timeResource.getTimeDilationModifier() == 1.0F && waitMillis > threshold) { + this.sendCancelPacket(chain); + return chain.getForkedChains().isEmpty(); + } + } + + return false; + } else { + LOGGER.at(Level.FINE).log("Remove Chain: %d, %s", chain.getChainId(), chain); + this.handleCancelledChain(ref, chain); + chain.onCompletion(this.cooldownHandler, this.hasRemoteClient); + return chain.getForkedChains().isEmpty(); + } + } else { + int baseOpIndex = chain.getOperationIndex(); + + try { + this.doTickChain(ref, chain); + } catch (InteractionManager.ChainCancelledException var9) { + chain.setServerState(var9.state); + chain.setClientState(var9.state); + chain.updateServerState(); + if (!this.hasRemoteClient) { + chain.updateSimulatedState(); + } + + if (chain.requiresClient()) { + this.sendSyncPacket(chain, baseOpIndex, this.tempSyncDataList); + this.sendCancelPacket(chain); + } + } + + if (chain.getServerState() != InteractionState.NotFinished) { + HytaleLogger.Api contextx = LOGGER.at(Level.FINE); + if (contextx.isEnabled()) { + contextx.log("Server finished chain: %d-%s, %s in %fs", chain.getChainId(), chain.getForkedChainId(), chain, chain.getTimeInSeconds()); + } + + if (!chain.requiresClient() || chain.getClientState() != InteractionState.NotFinished) { + contextx = LOGGER.at(Level.FINE); + if (contextx.isEnabled()) { + contextx.log("Remove Chain: %d-%s, %s", chain.getChainId(), chain.getForkedChainId(), chain); + } + + this.handleCancelledChain(ref, chain); + chain.onCompletion(this.cooldownHandler, this.hasRemoteClient); + return chain.getForkedChains().isEmpty(); + } + } else if (chain.getClientState() != InteractionState.NotFinished && !this.waitingForClient(ref)) { + if (chain.getWaitingForServerFinished() == 0L) { + chain.setWaitingForServerFinished(System.nanoTime()); + } + + long waitMillisx = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - chain.getWaitingForServerFinished()); + HytaleLogger.Api contextxx = LOGGER.at(Level.FINE); + if (contextxx.isEnabled()) { + contextxx.log("Client finished chain but server hasn't! %d, %s, %s", chain.getChainId(), chain, waitMillisx); + } + + long threshold = this.getOperationTimeoutThreshold(); + if (waitMillisx > threshold) { + LOGGER.at(Level.SEVERE).log("Client finished chain earlier than server! %d, %s", chain.getChainId(), chain); + } + } + + return false; + } + } + } + + private void handleCancelledChain(@Nonnull Ref ref, @Nonnull InteractionChain chain) { + assert this.commandBuffer != null; + + RootInteraction root = chain.getRootInteraction(); + int maxOperations = root.getOperationMax(); + if (chain.getOperationCounter() < maxOperations) { + InteractionEntry entry = chain.getInteraction(chain.getOperationIndex()); + if (entry != null) { + Operation operation = root.getOperation(chain.getOperationCounter()); + if (operation == null) { + throw new IllegalStateException("Failed to find operation during simulation tick of chain '" + root.getId() + "'"); + } else { + InteractionContext context = chain.getContext(); + entry.getServerState().state = InteractionState.Failed; + if (entry.getClientState() != null) { + entry.getClientState().state = InteractionState.Failed; + } + + try { + context.initEntry(chain, entry, this.entity); + TimeResource timeResource = this.commandBuffer.getResource(TimeResource.getResourceType()); + operation.handle(ref, false, entry.getTimeInSeconds(System.nanoTime()) * timeResource.getTimeDilationModifier(), chain.getType(), context); + } finally { + context.deinitEntry(chain, entry, this.entity); + } + + chain.setOperationCounter(maxOperations); + } + } + } + } + + private void doTickChain(@Nonnull Ref ref, @Nonnull InteractionChain chain) { + ObjectList interactionData = this.tempSyncDataList; + interactionData.clear(); + RootInteraction root = chain.getRootInteraction(); + int maxOperations = root.getOperationMax(); + int currentOp = chain.getOperationCounter(); + int baseOpIndex = chain.getOperationIndex(); + int callDepth = chain.getCallDepth(); + if (chain.consumeFirstRun()) { + if (chain.getForkedChainId() == null) { + chain.setTimeShift(this.getGlobalTimeShift(chain.getType())); + } else { + InteractionChain parent = this.chains.get(chain.getChainId()); + chain.setFirstRun(parent != null && parent.isFirstRun()); + } + } else { + chain.setTimeShift(0.0F); + } + + if (!chain.getContext().getEntity().isValid()) { + throw new InteractionManager.ChainCancelledException(chain.getServerState()); + } else { + while (true) { + Operation simOp = !this.hasRemoteClient ? root.getOperation(chain.getSimulatedOperationCounter()) : null; + WaitForDataFrom simWaitFrom = simOp != null ? simOp.getWaitForDataFrom() : null; + long tickTime = System.nanoTime(); + if (!this.hasRemoteClient && simWaitFrom != WaitForDataFrom.Server) { + this.simulationTick(ref, chain, tickTime); + } + + interactionData.add(this.serverTick(ref, chain, tickTime)); + if (!chain.getContext().getEntity().isValid() + && chain.getServerState() != InteractionState.Finished + && chain.getServerState() != InteractionState.Failed) { + throw new InteractionManager.ChainCancelledException(chain.getServerState()); + } + + if (!this.hasRemoteClient && simWaitFrom == WaitForDataFrom.Server) { + this.simulationTick(ref, chain, tickTime); + } + + if (!this.hasRemoteClient) { + if (chain.getRootInteraction() != chain.getSimulatedRootInteraction()) { + throw new IllegalStateException( + "Simulation and server tick are not in sync (root interaction).\n" + + chain.getRootInteraction().getId() + + " vs " + + chain.getSimulatedRootInteraction() + ); + } + + if (chain.getOperationCounter() != chain.getSimulatedOperationCounter()) { + throw new IllegalStateException( + "Simulation and server tick are not in sync (operation position).\nRoot: " + + chain.getRootInteraction().getId() + + "\nCounter: " + + chain.getOperationCounter() + + " vs " + + chain.getSimulatedOperationCounter() + + "\nIndex: " + + chain.getOperationIndex() + ); + } + } + + if (callDepth != chain.getCallDepth()) { + callDepth = chain.getCallDepth(); + root = chain.getRootInteraction(); + maxOperations = root.getOperationMax(); + } else if (currentOp == chain.getOperationCounter()) { + break; + } + + chain.nextOperationIndex(); + currentOp = chain.getOperationCounter(); + if (currentOp >= maxOperations) { + while (callDepth > 0) { + chain.popRoot(); + callDepth = chain.getCallDepth(); + currentOp = chain.getOperationCounter(); + root = chain.getRootInteraction(); + maxOperations = root.getOperationMax(); + if (currentOp < maxOperations || callDepth == 0) { + break; + } + } + + if (callDepth == 0 && currentOp >= maxOperations) { + break; + } + } + } + + chain.updateServerState(); + if (!this.hasRemoteClient) { + chain.updateSimulatedState(); + } + + if (chain.requiresClient()) { + this.sendSyncPacket(chain, baseOpIndex, interactionData); + } + } + } + + @Nullable + private InteractionSyncData serverTick(@Nonnull Ref ref, @Nonnull InteractionChain chain, long tickTime) { + assert this.commandBuffer != null; + + RootInteraction root = chain.getRootInteraction(); + Operation operation = root.getOperation(chain.getOperationCounter()); + + assert operation != null; + + InteractionEntry entry = chain.getOrCreateInteractionEntry(chain.getOperationIndex()); + InteractionSyncData returnData = null; + boolean wasWrong = entry.consumeDesyncFlag(); + if (entry.getClientState() == null) { + wasWrong |= !entry.setClientState(chain.removeInteractionSyncData(chain.getOperationIndex())); + } + + if (wasWrong) { + returnData = entry.getServerState(); + chain.flagDesync(); + chain.clearInteractionSyncData(chain.getOperationIndex()); + } + + TimeResource timeResource = this.commandBuffer.getResource(TimeResource.getResourceType()); + float tickTimeDilation = timeResource.getTimeDilationModifier(); + if (operation.getWaitForDataFrom() != WaitForDataFrom.Client || entry.getClientState() != null) { + int serverDataHashCode = entry.getServerDataHashCode(); + InteractionContext context = chain.getContext(); + float time = entry.getTimeInSeconds(tickTime); + boolean firstRun = false; + if (entry.getTimestamp() == 0L) { + time = chain.getTimeShift(); + entry.setTimestamp(tickTime, time); + firstRun = true; + } + + time *= tickTimeDilation; + + try { + context.initEntry(chain, entry, this.entity); + operation.tick(ref, this.entity, firstRun, time, chain.getType(), context, this.cooldownHandler); + } finally { + context.deinitEntry(chain, entry, this.entity); + } + + InteractionSyncData serverData = entry.getServerState(); + if (firstRun || serverDataHashCode != entry.getServerDataHashCode()) { + returnData = serverData; + } + + try { + context.initEntry(chain, entry, this.entity); + operation.handle(ref, firstRun, time, chain.getType(), context); + } finally { + context.deinitEntry(chain, entry, this.entity); + } + + this.removeInteractionIfFinished(ref, chain, entry); + return returnData; + } else if (this.waitingForClient(ref)) { + return null; + } else { + if (entry.getWaitingForSyncData() == 0L) { + entry.setWaitingForSyncData(System.nanoTime()); + } + + long waitMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - entry.getWaitingForSyncData()); + HytaleLogger.Api contextx = LOGGER.at(Level.FINE); + if (contextx.isEnabled()) { + contextx.log("Wait for interaction clientData: %d, %s, %s", chain.getOperationIndex(), entry, waitMillis); + } + + long threshold = this.getOperationTimeoutThreshold(); + if (tickTimeDilation == 1.0F && waitMillis > threshold) { + throw new RuntimeException( + "Client took too long to send clientData! Millis: " + + waitMillis + + ", Threshold: " + + threshold + + ",\nChain: " + + chain + + ",\nEntry: " + + chain.getOperationIndex() + + ", " + + entry + + ",\nWaiting for data from: " + + operation.getWaitForDataFrom() + ); + } else { + if (entry.consumeSendInitial() || wasWrong) { + returnData = entry.getServerState(); + } + + return returnData; + } + } + } + + private void removeInteractionIfFinished(@Nonnull Ref ref, @Nonnull InteractionChain chain, @Nonnull InteractionEntry entry) { + if (chain.getOperationIndex() == entry.getIndex() && entry.getServerState().state != InteractionState.NotFinished) { + chain.setFinalState(entry.getServerState().state); + } + + if (entry.getServerState().state != InteractionState.NotFinished) { + LOGGER.at(Level.FINE).log("Server finished interaction: %d, %s", entry.getIndex(), entry); + if (!chain.requiresClient() || entry.getClientState() != null && entry.getClientState().state != InteractionState.NotFinished) { + LOGGER.at(Level.FINER).log("Remove Interaction: %d, %s", entry.getIndex(), entry); + chain.removeInteractionEntry(this, entry.getIndex()); + } + } else if (entry.getClientState() != null && entry.getClientState().state != InteractionState.NotFinished && !this.waitingForClient(ref)) { + if (entry.getWaitingForServerFinished() == 0L) { + entry.setWaitingForServerFinished(System.nanoTime()); + } + + long waitMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - entry.getWaitingForServerFinished()); + HytaleLogger.Api context = LOGGER.at(Level.FINE); + if (context.isEnabled()) { + context.log("Client finished interaction but server hasn't! %s, %d, %s, %s", entry.getClientState().state, entry.getIndex(), entry, waitMillis); + } + + long threshold = this.getOperationTimeoutThreshold(); + if (waitMillis > threshold) { + HytaleLogger.Api ctx = LOGGER.at(Level.SEVERE); + if (ctx.isEnabled()) { + ctx.log("Client finished interaction earlier than server! %d, %s", entry.getIndex(), entry); + } + } + } + } + + private void simulationTick(@Nonnull Ref ref, @Nonnull InteractionChain chain, long tickTime) { + assert this.commandBuffer != null; + + RootInteraction rootInteraction = chain.getRootInteraction(); + Operation operation = rootInteraction.getOperation(chain.getSimulatedOperationCounter()); + if (operation == null) { + throw new IllegalStateException("Failed to find operation during simulation tick of chain '" + rootInteraction.getId() + "'"); + } else { + InteractionEntry entry = chain.getOrCreateInteractionEntry(chain.getClientOperationIndex()); + InteractionContext context = chain.getContext(); + entry.setUseSimulationState(true); + + try { + context.initEntry(chain, entry, this.entity); + float time = entry.getTimeInSeconds(tickTime); + boolean firstRun = false; + if (entry.getTimestamp() == 0L) { + time = chain.getTimeShift(); + entry.setTimestamp(tickTime, time); + firstRun = true; + } + + TimeResource timeResource = this.commandBuffer.getResource(TimeResource.getResourceType()); + float tickTimeDilation = timeResource.getTimeDilationModifier(); + time *= tickTimeDilation; + operation.simulateTick(ref, this.entity, firstRun, time, chain.getType(), context, this.cooldownHandler); + } finally { + context.deinitEntry(chain, entry, this.entity); + entry.setUseSimulationState(false); + } + + if (!entry.setClientState(entry.getSimulationState())) { + throw new RuntimeException("Simulation failed"); + } else { + this.removeInteractionIfFinished(ref, chain, entry); + } + } + } + + private boolean syncStart(@Nonnull Ref ref, @Nonnull SyncInteractionChain packet) { + assert this.commandBuffer != null; + + int index = packet.chainId; + if (!packet.initial) { + if (packet.forkedId == null) { + HytaleLogger.Api ctx = LOGGER.at(Level.FINE); + if (ctx.isEnabled()) { + ctx.log("Got syncStart for %d-%s but packet wasn't the first.", index, packet.forkedId); + } + } + + return true; + } else if (packet.forkedId != null) { + HytaleLogger.Api ctx = LOGGER.at(Level.FINE); + if (ctx.isEnabled()) { + ctx.log("Can't start a forked chain from the client: %d %s", index, packet.forkedId); + } + + return true; + } else { + InteractionType type = packet.interactionType; + if (index <= 0) { + HytaleLogger.Api ctx = LOGGER.at(Level.FINE); + if (ctx.isEnabled()) { + ctx.log("Invalid client chainId! Got %d but client id's should be > 0", index); + } + + this.sendCancelPacket(index, packet.forkedId); + return true; + } else if (index <= this.lastClientChainId) { + HytaleLogger.Api ctx = LOGGER.at(Level.FINE); + if (ctx.isEnabled()) { + ctx.log("Invalid client chainId! The last clientChainId was %d but just got %d", this.lastClientChainId, index); + } + + this.sendCancelPacket(index, packet.forkedId); + return true; + } else { + UUID proxyId = packet.data.proxyId; + InteractionContext context; + if (!UUIDUtil.isEmptyOrNull(proxyId)) { + World world = this.commandBuffer.getExternalData().getWorld(); + Ref proxyTarget = world.getEntityStore().getRefFromUUID(proxyId); + if (proxyTarget == null) { + if (this.packetQueueTime != 0L + && System.nanoTime() - this.packetQueueTime > TimeUnit.MILLISECONDS.toNanos(this.getOperationTimeoutThreshold()) / 2L) { + HytaleLogger.Api ctx = LOGGER.at(Level.FINE); + if (ctx.isEnabled()) { + ctx.log("Proxy entity never spawned"); + } + + this.sendCancelPacket(index, packet.forkedId); + return true; + } + + return false; + } + + context = InteractionContext.forProxyEntity(this, this.entity, proxyTarget); + } else { + context = InteractionContext.forInteraction(this, ref, type, packet.equipSlot, this.commandBuffer); + } + + String rootInteractionId = context.getRootInteractionId(type); + if (rootInteractionId == null) { + HytaleLogger.Api ctx = LOGGER.at(Level.FINE); + if (ctx.isEnabled()) { + ctx.log("Missing root interaction: %d, %s, %s", index, this.entity.getInventory().getItemInHand(), type); + } + + this.sendCancelPacket(index, packet.forkedId); + return true; + } else { + RootInteraction rootInteraction = RootInteraction.getRootInteractionOrUnknown(rootInteractionId); + if (rootInteraction == null) { + return false; + } else if (!this.applyRules(context, packet.data, type, rootInteraction)) { + return false; + } else { + Inventory entityInventory = this.entity.getInventory(); + ItemStack itemInHand = entityInventory.getActiveHotbarItem(); + ItemStack utilityItem = entityInventory.getUtilityItem(); + String serverItemInHandId = itemInHand != null ? itemInHand.getItemId() : null; + String serverUtilityItemId = utilityItem != null ? utilityItem.getItemId() : null; + if (packet.activeHotbarSlot != entityInventory.getActiveHotbarSlot()) { + HytaleLogger.Api ctx = LOGGER.at(Level.FINE); + if (ctx.isEnabled()) { + ctx.log( + "Active slot miss match: %d, %d != %d, %s, %s, %s", + index, + entityInventory.getActiveHotbarSlot(), + packet.activeHotbarSlot, + serverItemInHandId, + packet.itemInHandId, + type + ); + } + + this.sendCancelPacket(index, packet.forkedId); + if (this.playerRef != null) { + this.playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, entityInventory.getActiveHotbarSlot())); + } + + return true; + } else if (packet.activeUtilitySlot != entityInventory.getActiveUtilitySlot()) { + HytaleLogger.Api ctxx = LOGGER.at(Level.FINE); + if (ctxx.isEnabled()) { + ctxx.log( + "Active slot miss match: %d, %d != %d, %s, %s, %s", + index, + entityInventory.getActiveUtilitySlot(), + packet.activeUtilitySlot, + serverItemInHandId, + packet.itemInHandId, + type + ); + } + + this.sendCancelPacket(index, packet.forkedId); + if (this.playerRef != null) { + this.playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-5, entityInventory.getActiveUtilitySlot())); + } + + return true; + } else if (!Objects.equals(serverItemInHandId, packet.itemInHandId)) { + HytaleLogger.Api ctxxx = LOGGER.at(Level.FINE); + if (ctxxx.isEnabled()) { + ctxxx.log("ItemInHand miss match: %d, %s, %s, %s", index, serverItemInHandId, packet.itemInHandId, type); + } + + this.sendCancelPacket(index, packet.forkedId); + return true; + } else if (!Objects.equals(serverUtilityItemId, packet.utilityItemId)) { + HytaleLogger.Api ctxxx = LOGGER.at(Level.FINE); + if (ctxxx.isEnabled()) { + ctxxx.log("UtilityItem miss match: %d, %s, %s, %s", index, serverUtilityItemId, packet.utilityItemId, type); + } + + this.sendCancelPacket(index, packet.forkedId); + return true; + } else if (this.isOnCooldown(ref, type, rootInteraction, true)) { + return false; + } else { + InteractionChain chain = this.initChain(packet.data, type, context, rootInteraction, null, true); + chain.setChainId(index); + this.sync(ref, chain, packet); + World world = this.commandBuffer.getExternalData().getWorld(); + if (packet.data.blockPosition != null) { + BlockPosition targetBlock = world.getBaseBlock(packet.data.blockPosition); + context.getMetaStore().putMetaObject(Interaction.TARGET_BLOCK, targetBlock); + context.getMetaStore().putMetaObject(Interaction.TARGET_BLOCK_RAW, packet.data.blockPosition); + } + + if (packet.data.entityId >= 0) { + EntityStore entityComponentStore = world.getEntityStore(); + Ref entityReference = entityComponentStore.getRefFromNetworkId(packet.data.entityId); + if (entityReference != null) { + context.getMetaStore().putMetaObject(Interaction.TARGET_ENTITY, entityReference); + } + } + + if (packet.data.targetSlot != Integer.MIN_VALUE) { + context.getMetaStore().putMetaObject(Interaction.TARGET_SLOT, packet.data.targetSlot); + } + + if (packet.data.hitLocation != null) { + Vector3f hit = packet.data.hitLocation; + context.getMetaStore().putMetaObject(Interaction.HIT_LOCATION, new Vector4d(hit.x, hit.y, hit.z, 1.0)); + } + + if (packet.data.hitDetail != null) { + context.getMetaStore().putMetaObject(Interaction.HIT_DETAIL, packet.data.hitDetail); + } + + this.lastClientChainId = index; + if (!this.tickChain(chain)) { + chain.setPreTicked(true); + this.chains.put(index, chain); + } + + return true; + } + } + } + } + } + } + + public void sync(@Nonnull Ref ref, @Nonnull ChainSyncStorage chainSyncStorage, @Nonnull SyncInteractionChain packet) { + assert this.commandBuffer != null; + + if (packet.newForks != null) { + for (SyncInteractionChain fork : packet.newForks) { + chainSyncStorage.syncFork(ref, this, fork); + } + } + + if (packet.interactionData == null) { + chainSyncStorage.setClientState(packet.state); + } else { + for (int i = 0; i < packet.interactionData.length; i++) { + InteractionSyncData syncData = packet.interactionData[i]; + if (syncData != null) { + int index = packet.operationBaseIndex + i; + if (!chainSyncStorage.isSyncDataOutOfOrder(index)) { + InteractionEntry interaction = chainSyncStorage.getInteraction(index); + if (interaction != null && chainSyncStorage instanceof InteractionChain interactionChain) { + if (interaction.getClientState() != null + && interaction.getClientState().state != InteractionState.NotFinished + && syncData.state == InteractionState.NotFinished + || !interaction.setClientState(syncData)) { + chainSyncStorage.clearInteractionSyncData(index); + interaction.flagDesync(); + interactionChain.flagDesync(); + return; + } + + chainSyncStorage.updateSyncPosition(index); + HytaleLogger.Api context = LOGGER.at(Level.FINEST); + if (context.isEnabled()) { + TimeResource timeResource = this.commandBuffer.getResource(TimeResource.getResourceType()); + float tickTimeDilation = timeResource.getTimeDilationModifier(); + context.log( + "%d, %d: Time (Sync) - Server: %s vs Client: %s", + packet.chainId, + index, + interaction.getTimeInSeconds(System.nanoTime()) * tickTimeDilation, + interaction.getClientState().progress + ); + } + + this.removeInteractionIfFinished(ref, interactionChain, interaction); + } else { + chainSyncStorage.putInteractionSyncData(index, syncData); + } + } + } + } + + int last = packet.operationBaseIndex + packet.interactionData.length; + chainSyncStorage.clearInteractionSyncData(last); + chainSyncStorage.setClientState(packet.state); + } + } + + public boolean canRun(@Nonnull InteractionType type, @Nonnull RootInteraction rootInteraction) { + return this.canRun(type, (short)-1, rootInteraction); + } + + public boolean canRun(@Nonnull InteractionType type, short equipSlot, @Nonnull RootInteraction rootInteraction) { + return applyRules(null, type, equipSlot, rootInteraction, this.chains, null); + } + + public boolean applyRules( + @Nonnull InteractionContext context, @Nonnull InteractionChainData data, @Nonnull InteractionType type, @Nonnull RootInteraction rootInteraction + ) { + List chainsToCancel = new ObjectArrayList<>(); + if (!applyRules(data, type, context.getHeldItemSlot(), rootInteraction, this.chains, chainsToCancel)) { + return false; + } else { + for (InteractionChain interactionChain : chainsToCancel) { + this.cancelChains(interactionChain); + } + + return true; + } + } + + public void cancelChains(@Nonnull InteractionChain chain) { + chain.setServerState(InteractionState.Failed); + chain.setClientState(InteractionState.Failed); + this.sendCancelPacket(chain); + + for (InteractionChain fork : chain.getForkedChains().values()) { + this.cancelChains(fork); + } + } + + private static boolean applyRules( + @Nullable InteractionChainData data, + @Nonnull InteractionType type, + int heldItemSlot, + @Nullable RootInteraction rootInteraction, + @Nonnull Map chains, + @Nullable List chainsToCancel + ) { + if (!chains.isEmpty() && rootInteraction != null) { + for (InteractionChain chain : chains.values()) { + if ((chain.getForkedChainId() == null || chain.isPredicted()) + && (data == null || Objects.equals(chain.getChainData().proxyId, data.proxyId)) + && (type != InteractionType.Equipped || chain.getType() != InteractionType.Equipped || chain.getContext().getHeldItemSlot() == heldItemSlot)) { + if (chain.getServerState() == InteractionState.NotFinished) { + RootInteraction currentRoot = chain.getRootInteraction(); + Operation currentOp = currentRoot.getOperation(chain.getOperationCounter()); + if (rootInteraction.getRules() + .validateInterrupts(type, rootInteraction.getData().getTags(), chain.getType(), currentRoot.getData().getTags(), currentRoot.getRules())) { + if (chainsToCancel != null) { + chainsToCancel.add(chain); + } + } else if (currentOp != null + && currentOp.getRules() != null + && rootInteraction.getRules() + .validateInterrupts(type, rootInteraction.getData().getTags(), chain.getType(), currentOp.getTags(), currentOp.getRules())) { + if (chainsToCancel != null) { + chainsToCancel.add(chain); + } + } else { + if (rootInteraction.getRules() + .validateBlocked(type, rootInteraction.getData().getTags(), chain.getType(), currentRoot.getData().getTags(), currentRoot.getRules())) { + return false; + } + + if (currentOp != null + && currentOp.getRules() != null + && rootInteraction.getRules() + .validateBlocked(type, rootInteraction.getData().getTags(), chain.getType(), currentOp.getTags(), currentOp.getRules())) { + return false; + } + } + } + + if ((chainsToCancel == null || chainsToCancel.isEmpty()) + && !applyRules(data, type, heldItemSlot, rootInteraction, chain.getForkedChains(), chainsToCancel)) { + return false; + } + } + } + + return true; + } else { + return true; + } + } + + public boolean tryStartChain( + @Nonnull Ref ref, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nonnull RootInteraction rootInteraction + ) { + InteractionChain chain = this.initChain(type, context, rootInteraction, false); + if (!this.applyRules(context, chain.getChainData(), type, rootInteraction)) { + return false; + } else { + this.executeChain(ref, commandBuffer, chain); + return true; + } + } + + public void startChain( + @Nonnull Ref ref, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nonnull RootInteraction rootInteraction + ) { + InteractionChain chain = this.initChain(type, context, rootInteraction, false); + this.executeChain(ref, commandBuffer, chain); + } + + @Nonnull + public InteractionChain initChain( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull RootInteraction rootInteraction, boolean forceRemoteSync + ) { + return this.initChain(type, context, rootInteraction, -1, null, forceRemoteSync); + } + + @Nonnull + public InteractionChain initChain( + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nonnull RootInteraction rootInteraction, + int entityId, + @Nullable BlockPosition blockPosition, + boolean forceRemoteSync + ) { + InteractionChainData data = new InteractionChainData(entityId, UUIDUtil.EMPTY_UUID, null, null, blockPosition, Integer.MIN_VALUE, null); + return this.initChain(data, type, context, rootInteraction, null, forceRemoteSync); + } + + @Nonnull + public InteractionChain initChain( + @Nonnull InteractionChainData data, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nonnull RootInteraction rootInteraction, + @Nullable Runnable onCompletion, + boolean forceRemoteSync + ) { + return new InteractionChain(type, context, data, rootInteraction, onCompletion, forceRemoteSync || !this.hasRemoteClient); + } + + public void queueExecuteChain(@Nonnull InteractionChain chain) { + this.chainStartQueue.add(chain); + } + + public void executeChain(@Nonnull Ref ref, @Nonnull CommandBuffer commandBuffer, @Nonnull InteractionChain chain) { + this.commandBuffer = commandBuffer; + this.executeChain0(ref, chain); + this.commandBuffer = null; + } + + private void executeChain0(@Nonnull Ref ref, @Nonnull InteractionChain chain) { + if (this.isOnCooldown(ref, chain.getType(), chain.getInitialRootInteraction(), false)) { + chain.setServerState(InteractionState.Failed); + chain.setClientState(InteractionState.Failed); + } else { + int index = --this.lastServerChainId; + if (index >= 0) { + index = this.lastServerChainId = -1; + } + + chain.setChainId(index); + if (!this.tickChain(chain)) { + LOGGER.at(Level.FINE).log("Add Chain: %d, %s", index, chain); + chain.setPreTicked(true); + this.chains.put(index, chain); + } + } + } + + private boolean isOnCooldown(@Nonnull Ref ref, @Nonnull InteractionType type, @Nonnull RootInteraction root, boolean remote) { + assert this.commandBuffer != null; + + InteractionCooldown cooldown = root.getCooldown(); + String cooldownId = root.getId(); + float cooldownTime = InteractionTypeUtils.getDefaultCooldown(type); + float[] cooldownChargeTimes = DEFAULT_CHARGE_TIMES; + boolean interruptRecharge = false; + if (cooldown != null) { + cooldownTime = cooldown.cooldown; + if (cooldown.chargeTimes != null && cooldown.chargeTimes.length > 0) { + cooldownChargeTimes = cooldown.chargeTimes; + } + + if (cooldown.cooldownId != null) { + cooldownId = cooldown.cooldownId; + } + + if (cooldown.interruptRecharge) { + interruptRecharge = true; + } + + if (cooldown.clickBypass && remote) { + this.cooldownHandler.resetCooldown(cooldownId, cooldownTime, cooldownChargeTimes, interruptRecharge); + return false; + } + } + + Player playerComponent = this.commandBuffer.getComponent(ref, Player.getComponentType()); + GameMode gameMode = playerComponent != null ? playerComponent.getGameMode() : GameMode.Adventure; + RootInteractionSettings settings = root.getSettings().get(gameMode); + if (settings != null && settings.allowSkipChainOnClick && remote) { + this.cooldownHandler.resetCooldown(cooldownId, cooldownTime, cooldownChargeTimes, interruptRecharge); + return false; + } else { + return this.cooldownHandler.isOnCooldown(root, cooldownId, cooldownTime, cooldownChargeTimes, interruptRecharge); + } + } + + public void tryRunHeldInteraction(@Nonnull Ref ref, @Nonnull CommandBuffer commandBuffer, @Nonnull InteractionType type) { + this.tryRunHeldInteraction(ref, commandBuffer, type, (short)-1); + } + + public void tryRunHeldInteraction( + @Nonnull Ref ref, @Nonnull CommandBuffer commandBuffer, @Nonnull InteractionType type, short equipSlot + ) { + label38: { + Inventory inventory = this.entity.getInventory(); + ItemStack itemStack; + + itemStack = switch (type) { + case Held -> inventory.getItemInHand(); + case HeldOffhand -> inventory.getUtilityItem(); + case Equipped -> { + if (equipSlot == -1) { + throw new IllegalArgumentException(); + } + + yield inventory.getArmor().getItemStack(equipSlot); + if (itemStack != null && !itemStack.isEmpty()) { + String rootId = itemStack.getItem().getInteractions().get(type); + if (rootId == null) { + return; + } else { + RootInteraction root = RootInteraction.getAssetMap().getAsset(rootId); + if (root != null && this.canRun(type, equipSlot, root)) { + InteractionContext context = InteractionContext.forInteraction(this, ref, type, equipSlot, commandBuffer); + this.startChain(ref, commandBuffer, type, context, root); + return; + } else { + return; + } + } + } else { + return; + } + } + default -> throw new IllegalArgumentException(); + }; + } + } + + public void sendSyncPacket(@Nonnull InteractionChain chain, int operationBaseIndex, @Nullable List interactionData) { + if (!chain.hasSentInitial() || interactionData != null && !ListUtil.emptyOrAllNull(interactionData) || !chain.getNewForks().isEmpty()) { + if (this.playerRef != null) { + SyncInteractionChain packet = makeSyncPacket(chain, operationBaseIndex, interactionData); + this.syncPackets.add(packet); + } + } + } + + @Nonnull + private static SyncInteractionChain makeSyncPacket( + @Nonnull InteractionChain chain, int operationBaseIndex, @Nullable List interactionData + ) { + SyncInteractionChain[] forks = null; + List newForks = chain.getNewForks(); + if (!newForks.isEmpty()) { + forks = new SyncInteractionChain[newForks.size()]; + + for (int i = 0; i < newForks.size(); i++) { + InteractionChain fc = newForks.get(i); + forks[i] = makeSyncPacket(fc, 0, null); + } + + newForks.clear(); + } + + SyncInteractionChain packet = new SyncInteractionChain( + 0, + 0, + 0, + null, + null, + null, + !chain.hasSentInitial(), + false, + chain.hasSentInitial() ? Integer.MIN_VALUE : RootInteraction.getRootInteractionIdOrUnknown(chain.getInitialRootInteraction().getId()), + chain.getType(), + chain.getContext().getHeldItemSlot(), + chain.getChainId(), + chain.getForkedChainId(), + chain.getChainData(), + chain.getServerState(), + forks, + operationBaseIndex, + interactionData == null ? null : interactionData.toArray(InteractionSyncData[]::new) + ); + chain.setSentInitial(true); + return packet; + } + + private void sendCancelPacket(@Nonnull InteractionChain chain) { + this.sendCancelPacket(chain.getChainId(), chain.getForkedChainId()); + } + + public void sendCancelPacket(int chainId, @Nonnull ForkedChainId forkedChainId) { + if (this.playerRef != null) { + this.playerRef.getPacketHandler().writeNoCache(new CancelInteractionChain(chainId, forkedChainId)); + } + } + + public void clear() { + this.forEachInteraction((chain, _i, _a) -> { + chain.setServerState(InteractionState.Failed); + chain.setClientState(InteractionState.Failed); + this.sendCancelPacket(chain); + return null; + }, null); + this.chainStartQueue.clear(); + } + + public void clearAllGlobalTimeShift(float dt) { + if (this.timeShiftsDirty) { + boolean clearFlag = true; + + for (int i = 0; i < this.globalTimeShift.length; i++) { + if (!this.globalTimeShiftDirty[i]) { + this.globalTimeShift[i] = 0.0F; + } else { + clearFlag = false; + this.globalTimeShift[i] = this.globalTimeShift[i] + dt; + } + } + + Arrays.fill(this.globalTimeShiftDirty, false); + if (clearFlag) { + this.timeShiftsDirty = false; + } + } + } + + public void setGlobalTimeShift(@Nonnull InteractionType type, float shift) { + if (shift < 0.0F) { + throw new IllegalArgumentException("Can't shift backwards"); + } else { + this.globalTimeShift[type.ordinal()] = shift; + this.globalTimeShiftDirty[type.ordinal()] = true; + this.timeShiftsDirty = true; + } + } + + public float getGlobalTimeShift(@Nonnull InteractionType type) { + return this.globalTimeShift[type.ordinal()]; + } + + public T forEachInteraction(@Nonnull TriFunction func, @Nonnull T val) { + return forEachInteraction(this.chains, func, val); + } + + private static T forEachInteraction( + @Nonnull Map chains, @Nonnull TriFunction func, @Nonnull T val + ) { + if (chains.isEmpty()) { + return val; + } else { + for (InteractionChain chain : chains.values()) { + Operation operation = chain.getRootInteraction().getOperation(chain.getOperationCounter()); + if (operation != null && operation.getInnerOperation() instanceof Interaction interaction) { + val = func.apply(chain, interaction, val); + } + + val = forEachInteraction(chain.getForkedChains(), func, val); + } + + return val; + } + } + + public void walkChain( + @Nonnull Ref ref, @Nonnull Collector collector, @Nonnull InteractionType type, @Nonnull ComponentAccessor componentAccessor + ) { + this.walkChain(ref, collector, type, null, componentAccessor); + } + + public void walkChain( + @Nonnull Ref ref, + @Nonnull Collector collector, + @Nonnull InteractionType type, + @Nullable RootInteraction rootInteraction, + @Nonnull ComponentAccessor componentAccessor + ) { + walkChain(collector, type, InteractionContext.forInteraction(this, ref, type, componentAccessor), rootInteraction); + } + + public static void walkChain( + @Nonnull Collector collector, @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable RootInteraction rootInteraction + ) { + if (rootInteraction == null) { + String rootInteractionId = context.getRootInteractionId(type); + if (rootInteractionId == null) { + throw new IllegalArgumentException("No interaction ID found for " + type + ", " + context); + } + + rootInteraction = RootInteraction.getAssetMap().getAsset(rootInteractionId); + } + + if (rootInteraction == null) { + throw new IllegalArgumentException("No interactions are defined for " + type + ", " + context); + } else { + collector.start(); + collector.into(context, null); + walkInteractions(collector, context, CollectorTag.ROOT, rootInteraction.getInteractionIds()); + collector.outof(); + collector.finished(); + } + } + + public static boolean walkInteractions( + @Nonnull Collector collector, @Nonnull InteractionContext context, @Nonnull CollectorTag tag, @Nonnull String[] interactionIds + ) { + for (String id : interactionIds) { + if (walkInteraction(collector, context, tag, id)) { + return true; + } + } + + return false; + } + + public static boolean walkInteraction(@Nonnull Collector collector, @Nonnull InteractionContext context, @Nonnull CollectorTag tag, @Nullable String id) { + if (id == null) { + return false; + } else { + Interaction interaction = Interaction.getAssetMap().getAsset(id); + if (interaction == null) { + throw new IllegalArgumentException("Failed to find interaction: " + id); + } else if (collector.collect(tag, context, interaction)) { + return true; + } else { + collector.into(context, interaction); + interaction.walk(collector, context); + collector.outof(); + return false; + } + } + } + + public ObjectList getSyncPackets() { + return this.syncPackets; + } + + @Nonnull + @Override + public Component clone() { + InteractionManager manager = new InteractionManager(this.entity, this.playerRef, this.interactionSimulationHandler); + manager.copyFrom(this); + return manager; + } + + public static class ChainCancelledException extends RuntimeException { + @Nonnull + private final InteractionState state; + + public ChainCancelledException(@Nonnull InteractionState state) { + this.state = state; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/ItemUtils.java b/src/com/hypixel/hytale/server/core/entity/ItemUtils.java new file mode 100644 index 0000000..27930ea --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/ItemUtils.java @@ -0,0 +1,158 @@ +package com.hypixel.hytale.server.core.entity; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.event.events.ecs.DropItemEvent; +import com.hypixel.hytale.server.core.event.events.ecs.InteractivelyPickupItemEvent; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemUtils { + public ItemUtils() { + } + + public static void interactivelyPickupItem( + @Nonnull Ref ref, @Nonnull ItemStack itemStack, @Nullable Vector3d origin, @Nonnull ComponentAccessor componentAccessor + ) { + LivingEntity entity = (LivingEntity)EntityUtils.getEntity(ref, componentAccessor); + InteractivelyPickupItemEvent event = new InteractivelyPickupItemEvent(itemStack); + componentAccessor.invoke(ref, event); + if (event.isCancelled()) { + dropItem(ref, itemStack, componentAccessor); + } else { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + PlayerSettings playerSettingsComponent = componentAccessor.getComponent(ref, PlayerSettings.getComponentType()); + if (playerSettingsComponent == null) { + playerSettingsComponent = PlayerSettings.defaults(); + } + + Holder pickupItemHolder = null; + Item item = itemStack.getItem(); + ItemContainer itemContainer = playerComponent.getInventory().getContainerForItemPickup(item, playerSettingsComponent); + ItemStackTransaction transaction = itemContainer.addItemStack(itemStack); + ItemStack remainder = transaction.getRemainder(); + if (remainder != null && !remainder.isEmpty()) { + int quantity = itemStack.getQuantity() - remainder.getQuantity(); + if (quantity > 0) { + ItemStack itemStackClone = itemStack.withQuantity(quantity); + playerComponent.notifyPickupItem(ref, itemStackClone, null, componentAccessor); + if (origin != null) { + pickupItemHolder = ItemComponent.generatePickedUpItem(itemStackClone, origin, componentAccessor, ref); + } + } + + dropItem(ref, remainder, componentAccessor); + } else { + playerComponent.notifyPickupItem(ref, itemStack, null, componentAccessor); + if (origin != null) { + pickupItemHolder = ItemComponent.generatePickedUpItem(itemStack, origin, componentAccessor, ref); + } + } + + if (pickupItemHolder != null) { + componentAccessor.addEntity(pickupItemHolder, AddReason.SPAWN); + } + } else { + SimpleItemContainer.addOrDropItemStack(componentAccessor, ref, entity.getInventory().getCombinedHotbarFirst(), itemStack); + } + } + } + + @Nullable + public static Ref throwItem( + @Nonnull Ref ref, @Nonnull ItemStack itemStack, float throwSpeed, @Nonnull ComponentAccessor componentAccessor + ) { + DropItemEvent.Drop event = new DropItemEvent.Drop(itemStack, throwSpeed); + componentAccessor.invoke(ref, event); + if (event.isCancelled()) { + return null; + } else { + throwSpeed = event.getThrowSpeed(); + itemStack = event.getItemStack(); + if (!itemStack.isEmpty() && itemStack.isValid()) { + HeadRotation headRotationComponent = componentAccessor.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3f rotation = headRotationComponent.getRotation(); + Vector3d direction = Transform.getDirection(rotation.getPitch(), rotation.getYaw()); + return throwItem(ref, componentAccessor, itemStack, direction, throwSpeed); + } else { + HytaleLogger.getLogger().at(Level.WARNING).log("Attempted to throw invalid item %s at %s by %s", itemStack, throwSpeed, ref.getIndex()); + return null; + } + } + } + + @Nullable + public static Ref throwItem( + @Nonnull Ref ref, + @Nonnull ComponentAccessor store, + @Nonnull ItemStack itemStack, + @Nonnull Vector3d throwDirection, + float throwSpeed + ) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + ModelComponent modelComponent = store.getComponent(ref, ModelComponent.getComponentType()); + + assert modelComponent != null; + + Vector3d throwPosition = transformComponent.getPosition().clone(); + Model model = modelComponent.getModel(); + throwPosition.add(0.0, model.getEyeHeight(ref, store), 0.0).add(throwDirection); + Holder itemEntityHolder = ItemComponent.generateItemDrop( + store, + itemStack, + throwPosition, + Vector3f.ZERO, + (float)throwDirection.x * throwSpeed, + (float)throwDirection.y * throwSpeed, + (float)throwDirection.z * throwSpeed + ); + if (itemEntityHolder == null) { + return null; + } else { + ItemComponent itemComponent = itemEntityHolder.getComponent(ItemComponent.getComponentType()); + if (itemComponent != null) { + itemComponent.setPickupDelay(1.5F); + } + + return store.addEntity(itemEntityHolder, AddReason.SPAWN); + } + } + + @Nullable + public static Ref dropItem( + @Nonnull Ref ref, @Nonnull ItemStack itemStack, @Nonnull ComponentAccessor componentAccessor + ) { + return throwItem(ref, itemStack, 1.0F, componentAccessor); + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/LivingEntity.java b/src/com/hypixel/hytale/server/core/entity/LivingEntity.java new file mode 100644 index 0000000..d8a8842 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/LivingEntity.java @@ -0,0 +1,242 @@ +package com.hypixel.hytale.server.core.entity; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.event.EventRegistration; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction; +import com.hypixel.hytale.server.core.modules.collision.WorldUtil; +import com.hypixel.hytale.server.core.modules.entity.BlockMigrationExtraInfo; +import com.hypixel.hytale.server.core.modules.entity.component.Invulnerable; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class LivingEntity extends Entity { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.abstractBuilder(LivingEntity.class, Entity.CODEC) + .append(new KeyedCodec<>("Inventory", Inventory.CODEC), (livingEntity, inventory, extraInfo) -> { + livingEntity.setInventory(inventory); + if (extraInfo instanceof BlockMigrationExtraInfo) { + livingEntity.inventory.doMigration(((BlockMigrationExtraInfo)extraInfo).getBlockMigration()); + } + }, (livingEntity, extraInfo) -> livingEntity.inventory) + .add() + .afterDecode(livingEntity -> { + if (livingEntity.inventory == null) { + livingEntity.setInventory(livingEntity.createDefaultInventory()); + } + }) + .build(); + public static final int DEFAULT_ITEM_THROW_SPEED = 6; + @Nonnull + private final StatModifiersManager statModifiersManager = new StatModifiersManager(); + private Inventory inventory; + protected double currentFallDistance; + private EventRegistration armorInventoryChangeEventRegistration; + private boolean isEquipmentNetworkOutdated; + + public LivingEntity() { + this.setInventory(this.createDefaultInventory()); + } + + public LivingEntity(@Nonnull World world) { + super(world); + this.setInventory(this.createDefaultInventory()); + } + + protected abstract Inventory createDefaultInventory(); + + public boolean canBreathe( + @Nonnull Ref ref, @Nonnull BlockMaterial breathingMaterial, int fluidId, @Nonnull ComponentAccessor componentAccessor + ) { + boolean invulnerable = componentAccessor.getArchetype(ref).contains(Invulnerable.getComponentType()); + return invulnerable || breathingMaterial == BlockMaterial.Empty && fluidId == 0; + } + + public static long getPackedMaterialAndFluidAtBreathingHeight(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + World world = componentAccessor.getExternalData().getWorld(); + Transform lookVec = TargetUtil.getLook(ref, componentAccessor); + Vector3d position = lookVec.getPosition(); + ChunkStore chunkStore = world.getChunkStore(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(position.x, position.z); + Ref chunkRef = chunkStore.getChunkReference(chunkIndex); + return chunkRef != null && chunkRef.isValid() + ? WorldUtil.getPackedMaterialAndFluidAtPosition(chunkRef, chunkStore.getStore(), position.x, position.y, position.z) + : MathUtil.packLong(BlockMaterial.Empty.ordinal(), 0); + } + + public Inventory getInventory() { + return this.inventory; + } + + @Nonnull + public Inventory setInventory(Inventory inventory) { + return this.setInventory(inventory, false); + } + + @Nonnull + public Inventory setInventory(Inventory inventory, boolean ensureCapacity) { + List remainder = ensureCapacity ? new ObjectArrayList<>() : null; + inventory = this.setInventory(inventory, ensureCapacity, remainder); + if (remainder != null && !remainder.isEmpty()) { + ListTransaction transactionList = inventory.getCombinedHotbarFirst().addItemStacks(remainder); + + for (ItemStackTransaction var6 : transactionList.getList()) { + ; + } + } + + return inventory; + } + + @Nonnull + public Inventory setInventory(Inventory inventory, boolean ensureCapacity, List remainder) { + if (this.inventory != null) { + this.inventory.unregister(); + } + + if (this.armorInventoryChangeEventRegistration != null) { + this.armorInventoryChangeEventRegistration.unregister(); + } + + if (ensureCapacity) { + inventory = Inventory.ensureCapacity(inventory, remainder); + } + + inventory.setEntity(this); + this.armorInventoryChangeEventRegistration = inventory.getArmor().registerChangeEvent(event -> this.statModifiersManager.setRecalculate(true)); + this.inventory = inventory; + return inventory; + } + + @Override + public void moveTo(@Nonnull Ref ref, double locX, double locY, double locZ, @Nonnull ComponentAccessor componentAccessor) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + MovementStatesComponent movementStatesComponent = componentAccessor.getComponent(ref, MovementStatesComponent.getComponentType()); + + assert movementStatesComponent != null; + + MovementStates movementStates = movementStatesComponent.getMovementStates(); + boolean fallDamageActive = !movementStates.inFluid && !movementStates.climbing && !movementStates.flying && !movementStates.gliding; + if (fallDamageActive) { + Vector3d position = transformComponent.getPosition(); + if (!movementStates.onGround) { + if (position.getY() > locY) { + this.currentFallDistance = this.currentFallDistance + (position.getY() - locY); + } + } else { + this.currentFallDistance = 0.0; + } + } else { + this.currentFallDistance = 0.0; + } + + super.moveTo(ref, locX, locY, locZ, componentAccessor); + } + + public boolean canDecreaseItemStackDurability(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + return false; + } + + public boolean canApplyItemStackPenalties(Ref ref, ComponentAccessor componentAccessor) { + return true; + } + + @Nullable + public ItemStackSlotTransaction decreaseItemStackDurability( + @Nonnull Ref ref, @Nullable ItemStack itemStack, int inventoryId, int slotId, @Nonnull ComponentAccessor componentAccessor + ) { + if (!this.canDecreaseItemStackDurability(ref, componentAccessor)) { + return null; + } else if (itemStack == null || itemStack.isEmpty() || itemStack.getItem() == null) { + return null; + } else if (itemStack.isBroken()) { + return null; + } else { + Item item = itemStack.getItem(); + ItemContainer section = this.inventory.getSectionById(inventoryId); + if (section == null) { + return null; + } else if (item.getArmor() != null) { + ItemStackSlotTransaction transaction = this.updateItemStackDurability( + ref, itemStack, section, slotId, -item.getDurabilityLossOnHit(), componentAccessor + ); + if (transaction.getSlotAfter().isBroken()) { + this.statModifiersManager.setRecalculate(true); + } + + return transaction; + } else { + return item.getWeapon() != null + ? this.updateItemStackDurability(ref, itemStack, section, slotId, -item.getDurabilityLossOnHit(), componentAccessor) + : null; + } + } + } + + @Nullable + public ItemStackSlotTransaction updateItemStackDurability( + @Nonnull Ref ref, + @Nonnull ItemStack itemStack, + ItemContainer container, + int slotId, + double durabilityChange, + @Nonnull ComponentAccessor componentAccessor + ) { + ItemStack updatedItemStack = itemStack.withIncreasedDurability(durabilityChange); + return container.replaceItemStackInSlot((short)slotId, itemStack, updatedItemStack); + } + + public void invalidateEquipmentNetwork() { + this.isEquipmentNetworkOutdated = true; + } + + public boolean consumeEquipmentNetworkOutdated() { + boolean temp = this.isEquipmentNetworkOutdated; + this.isEquipmentNetworkOutdated = false; + return temp; + } + + @Nonnull + public StatModifiersManager getStatModifiersManager() { + return this.statModifiersManager; + } + + public double getCurrentFallDistance() { + return this.currentFallDistance; + } + + public void setCurrentFallDistance(double currentFallDistance) { + this.currentFallDistance = currentFallDistance; + } + + @Nonnull + @Override + public String toString() { + return "LivingEntity{, " + super.toString() + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/StatModifiersManager.java b/src/com/hypixel/hytale/server/core/entity/StatModifiersManager.java new file mode 100644 index 0000000..3b64f1b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/StatModifiersManager.java @@ -0,0 +1,276 @@ +package com.hypixel.hytale.server.core.entity; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.asset.type.gameplay.BrokenPenalties; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemArmor; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.Modifier; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.StaticModifier; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntIterator; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2FloatMap.Entry; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StatModifiersManager { + @Nonnull + private final AtomicBoolean recalculate = new AtomicBoolean(); + @Nonnull + private final IntSet statsToClear = new IntOpenHashSet(); + + public StatModifiersManager() { + } + + public void setRecalculate(boolean value) { + this.recalculate.set(value); + } + + public void queueEntityStatsToClear(@Nonnull int[] entityStatsToClear) { + for (int i = 0; i < entityStatsToClear.length; i++) { + this.statsToClear.add(entityStatsToClear[i]); + } + } + + public void recalculateEntityStatModifiers( + @Nonnull Ref ref, @Nonnull EntityStatMap statMap, @Nonnull ComponentAccessor componentAccessor + ) { + if (this.recalculate.getAndSet(false)) { + if (!this.statsToClear.isEmpty()) { + IntIterator iterator = this.statsToClear.iterator(); + + while (iterator.hasNext()) { + statMap.minimizeStatValue(EntityStatMap.Predictable.SELF, iterator.nextInt()); + } + + this.statsToClear.clear(); + } + + World world = componentAccessor.getExternalData().getWorld(); + if (EntityUtils.getEntity(ref, componentAccessor) instanceof LivingEntity livingEntity) { + Inventory inventory = livingEntity.getInventory(); + Int2ObjectOpenHashMap effectModifiers = calculateEffectStatModifiers(ref, componentAccessor); + applyEffectModifiers(statMap, effectModifiers); + BrokenPenalties brokenPenalties = world.getGameplayConfig().getItemDurabilityConfig().getBrokenPenalties(); + Int2ObjectMap statModifiers = computeStatModifiers(brokenPenalties, inventory); + applyStatModifiers(statMap, statModifiers); + ItemStack itemInHand = inventory.getItemInHand(); + addItemStatModifiers(itemInHand, statMap, "*Weapon_", v -> v.getWeapon() != null ? v.getWeapon().getStatModifiers() : null); + if (itemInHand == null || itemInHand.getItem().getUtility().isCompatible()) { + addItemStatModifiers(inventory.getUtilityItem(), statMap, "*Utility_", v -> v.getUtility().getStatModifiers()); + } + } + } + } + + @Nonnull + private static Int2ObjectOpenHashMap> calculateEffectStatModifiers( + @Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor + ) { + Int2ObjectOpenHashMap> statModifiers = new Int2ObjectOpenHashMap<>(); + EffectControllerComponent effectControllerComponent = componentAccessor.getComponent(ref, EffectControllerComponent.getComponentType()); + if (effectControllerComponent == null) { + return statModifiers; + } else { + effectControllerComponent.getActiveEffects() + .forEach( + (k, v) -> { + if (v.isInfinite() || !(v.getRemainingDuration() <= 0.0F)) { + int index = v.getEntityEffectIndex(); + EntityEffect effect = EntityEffect.getAssetMap().getAsset(index); + if (effect != null && effect.getStatModifiers() != null) { + for (it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry entry : effect.getStatModifiers().int2ObjectEntrySet()) { + int entityStatType = entry.getIntKey(); + + for (StaticModifier modifier : entry.getValue()) { + float value = modifier.getAmount(); + Object2FloatMap statModifierToApply = statModifiers.computeIfAbsent( + entityStatType, x -> new Object2FloatOpenHashMap<>() + ); + statModifierToApply.mergeFloat(modifier.getCalculationType(), value, Float::sum); + } + } + } + } + } + ); + return statModifiers; + } + } + + private static void applyEffectModifiers( + @Nonnull EntityStatMap statMap, @Nonnull Int2ObjectMap> statModifiers + ) { + for (int i = 0; i < statMap.size(); i++) { + Object2FloatMap statModifiersForEntityStat = statModifiers.get(i); + if (statModifiersForEntityStat == null) { + for (StaticModifier.CalculationType calculationType : StaticModifier.CalculationType.values()) { + statMap.removeModifier(i, calculationType.createKey("Effect")); + } + } else { + for (StaticModifier.CalculationType calculationType : StaticModifier.CalculationType.values()) { + if (!statModifiersForEntityStat.containsKey(calculationType)) { + statMap.removeModifier(i, calculationType.createKey("Effect")); + } + } + + for (Entry entry : statModifiersForEntityStat.object2FloatEntrySet()) { + StaticModifier.CalculationType calculationTypex = entry.getKey(); + StaticModifier modifier = new StaticModifier(Modifier.ModifierTarget.MAX, calculationTypex, entry.getFloatValue()); + statMap.putModifier(i, calculationTypex.createKey("Effect"), modifier); + } + } + } + } + + private static void computeStatModifiers( + double brokenPenalty, + @Nonnull Int2ObjectMap> statModifiers, + @Nonnull ItemStack itemInHand, + @Nonnull Int2ObjectMap itemStatModifiers + ) { + boolean broken = itemInHand.isBroken(); + + for (it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry entry : itemStatModifiers.int2ObjectEntrySet()) { + int entityStatType = entry.getIntKey(); + + for (StaticModifier modifier : entry.getValue()) { + float value = modifier.getAmount(); + if (broken) { + value = (float)(value * brokenPenalty); + } + + Object2FloatMap statModifierToApply = statModifiers.computeIfAbsent( + entityStatType, x -> new Object2FloatOpenHashMap<>() + ); + statModifierToApply.mergeFloat(modifier.getCalculationType(), value, Float::sum); + } + } + } + + @Nonnull + private static Int2ObjectMap> computeStatModifiers( + @Nonnull BrokenPenalties brokenPenalties, @Nonnull Inventory inventory + ) { + Int2ObjectOpenHashMap> statModifiers = new Int2ObjectOpenHashMap<>(); + double armorBrokenPenalty = brokenPenalties.getArmor(0.0); + ItemContainer armorContainer = inventory.getArmor(); + + for (short i = 0; i < armorContainer.getCapacity(); i++) { + ItemStack armorItemStack = armorContainer.getItemStack(i); + if (armorItemStack != null) { + addArmorStatModifiers(armorItemStack, armorBrokenPenalty, statModifiers); + } + } + + return statModifiers; + } + + private static void addArmorStatModifiers( + @Nonnull ItemStack itemStack, double brokenPenalties, @Nonnull Int2ObjectOpenHashMap> statModifiers + ) { + if (!ItemStack.isEmpty(itemStack)) { + ItemArmor armorItem = itemStack.getItem().getArmor(); + if (armorItem != null) { + Int2ObjectMap itemStatModifiers = armorItem.getStatModifiers(); + if (itemStatModifiers != null) { + computeStatModifiers(brokenPenalties, statModifiers, itemStack, itemStatModifiers); + } + } + } + } + + private static void addItemStatModifiers( + @Nullable ItemStack itemStack, + @Nonnull EntityStatMap entityStatMap, + @Nonnull String prefix, + @Nonnull Function> toStatModifiers + ) { + if (ItemStack.isEmpty(itemStack)) { + clearAllStatModifiers(EntityStatMap.Predictable.SELF, entityStatMap, prefix, null); + } else { + Int2ObjectMap itemStatModifiers = toStatModifiers.apply(itemStack.getItem()); + if (itemStatModifiers == null) { + clearAllStatModifiers(EntityStatMap.Predictable.SELF, entityStatMap, prefix, null); + } else { + for (it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry entry : itemStatModifiers.int2ObjectEntrySet()) { + int offset = 0; + int statIndex = entry.getIntKey(); + + for (StaticModifier modifier : entry.getValue()) { + String key = prefix + offset; + offset++; + if (!(entityStatMap.getModifier(statIndex, key) instanceof StaticModifier existingStatic && existingStatic.equals(modifier))) { + entityStatMap.putModifier(EntityStatMap.Predictable.SELF, statIndex, key, modifier); + } + } + + clearStatModifiers(EntityStatMap.Predictable.SELF, entityStatMap, statIndex, prefix, offset); + } + + clearAllStatModifiers(EntityStatMap.Predictable.SELF, entityStatMap, prefix, itemStatModifiers); + } + } + } + + private static void clearAllStatModifiers( + @Nonnull EntityStatMap.Predictable predictable, + @Nonnull EntityStatMap entityStatMap, + @Nonnull String prefix, + @Nullable Int2ObjectMap excluding + ) { + for (int i = 0; i < entityStatMap.size(); i++) { + if (excluding == null || !excluding.containsKey(i)) { + clearStatModifiers(predictable, entityStatMap, i, prefix, 0); + } + } + } + + private static void clearStatModifiers( + @Nonnull EntityStatMap.Predictable predictable, @Nonnull EntityStatMap entityStatMap, int statIndex, @Nonnull String prefix, int offset + ) { + String key; + do { + key = prefix + offset; + offset++; + } while (entityStatMap.removeModifier(predictable, statIndex, key) != null); + } + + private static void applyStatModifiers(@Nonnull EntityStatMap statMap, @Nonnull Int2ObjectMap> statModifiers) { + for (int i = 0; i < statMap.size(); i++) { + Object2FloatMap statModifiersForEntityStat = statModifiers.get(i); + if (statModifiersForEntityStat == null) { + for (StaticModifier.CalculationType calculationType : StaticModifier.CalculationType.values()) { + statMap.removeModifier(i, calculationType.createKey("Armor")); + } + } else { + for (StaticModifier.CalculationType calculationType : StaticModifier.CalculationType.values()) { + if (!statModifiersForEntityStat.containsKey(calculationType)) { + statMap.removeModifier(i, calculationType.createKey("Armor")); + } + } + + for (Entry entry : statModifiersForEntityStat.object2FloatEntrySet()) { + StaticModifier.CalculationType calculationTypex = entry.getKey(); + StaticModifier modifier = new StaticModifier(Modifier.ModifierTarget.MAX, calculationTypex, entry.getFloatValue()); + statMap.putModifier(i, calculationTypex.createKey("Armor"), modifier); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/UUIDComponent.java b/src/com/hypixel/hytale/server/core/entity/UUIDComponent.java new file mode 100644 index 0000000..1391609 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/UUIDComponent.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.server.core.entity; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.UUIDUtil; +import java.util.UUID; +import javax.annotation.Nonnull; + +public final class UUIDComponent implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(UUIDComponent.class, UUIDComponent::new) + .append(new KeyedCodec<>("UUID", Codec.UUID_BINARY), (o, i) -> o.uuid = i, o -> o.uuid) + .addValidator(Validators.nonNull()) + .add() + .afterDecode(v -> { + if (v.uuid == null) { + v.uuid = UUIDUtil.generateVersion3UUID(); + } + }) + .build(); + private UUID uuid; + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getUuidComponentType(); + } + + public UUIDComponent(@Nonnull UUID uuid) { + this.uuid = uuid; + } + + private UUIDComponent() { + } + + @Nonnull + public UUID getUuid() { + return this.uuid; + } + + @Nonnull + @Override + public Component clone() { + return this; + } + + @Nonnull + public static UUIDComponent generateVersion3UUID() { + return new UUIDComponent(UUIDUtil.generateVersion3UUID()); + } + + @Nonnull + public static UUIDComponent randomUUID() { + return new UUIDComponent(UUID.randomUUID()); + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/damage/DamageDataComponent.java b/src/com/hypixel/hytale/server/core/entity/damage/DamageDataComponent.java new file mode 100644 index 0000000..dc03abe --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/damage/DamageDataComponent.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.server.core.entity.damage; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.WieldingInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DamageDataComponent implements Component { + @Nonnull + private Instant lastCombatAction = Instant.MIN; + @Nonnull + private Instant lastDamageTime = Instant.MIN; + @Nullable + private WieldingInteraction currentWielding; + @Nullable + private Instant lastChargeTime; + + public DamageDataComponent() { + } + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getDamageDataComponentType(); + } + + @Nonnull + public Instant getLastCombatAction() { + return this.lastCombatAction; + } + + public void setLastCombatAction(@Nonnull Instant lastCombatAction) { + this.lastCombatAction = lastCombatAction; + } + + @Nonnull + public Instant getLastDamageTime() { + return this.lastDamageTime; + } + + public void setLastDamageTime(@Nonnull Instant lastDamageTime) { + this.lastDamageTime = lastDamageTime; + } + + @Nullable + public Instant getLastChargeTime() { + return this.lastChargeTime; + } + + public void setLastChargeTime(@Nonnull Instant lastChargeTime) { + this.lastChargeTime = lastChargeTime; + } + + @Nullable + public WieldingInteraction getCurrentWielding() { + return this.currentWielding; + } + + public void setCurrentWielding(@Nullable WieldingInteraction currentWielding) { + this.currentWielding = currentWielding; + } + + @Nonnull + @Override + public Component clone() { + DamageDataComponent damageDataComponent = new DamageDataComponent(); + damageDataComponent.lastCombatAction = this.lastCombatAction; + damageDataComponent.lastDamageTime = this.lastDamageTime; + damageDataComponent.currentWielding = this.currentWielding; + damageDataComponent.lastChargeTime = this.lastChargeTime; + return damageDataComponent; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/damage/DamageDataSetupSystem.java b/src/com/hypixel/hytale/server/core/entity/damage/DamageDataSetupSystem.java new file mode 100644 index 0000000..33982e8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/damage/DamageDataSetupSystem.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.server.core.entity.damage; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.server.core.modules.entity.AllLegacyLivingEntityTypesQuery; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class DamageDataSetupSystem extends HolderSystem { + @Nonnull + private final ComponentType damageDataComponentType; + + public DamageDataSetupSystem(@Nonnull ComponentType damageDataComponentType) { + this.damageDataComponentType = damageDataComponentType; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(this.damageDataComponentType); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return AllLegacyLivingEntityTypesQuery.INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/effect/ActiveEntityEffect.java b/src/com/hypixel/hytale/server/core/entity/effect/ActiveEntityEffect.java new file mode 100644 index 0000000..e76bdfc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/effect/ActiveEntityEffect.java @@ -0,0 +1,281 @@ +package com.hypixel.hytale.server.core.entity.effect; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.protocol.ChangeStatBehaviour; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCalculatorSystems; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.DamageCalculator; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.DamageEffects; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import java.util.Locale; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActiveEntityEffect implements Damage.Source { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(ActiveEntityEffect.class, ActiveEntityEffect::new) + .append( + new KeyedCodec<>("EntityEffectId", Codec.STRING), (entityEffect, x) -> entityEffect.entityEffectId = x, entityEffect -> entityEffect.entityEffectId + ) + .add() + .append( + new KeyedCodec<>("InitialDuration", Codec.FLOAT), (entityEffect, x) -> entityEffect.initialDuration = x, entityEffect -> entityEffect.initialDuration + ) + .add() + .append( + new KeyedCodec<>("RemainingDuration", Codec.FLOAT), + (entityEffect, x) -> entityEffect.remainingDuration = x, + entityEffect -> entityEffect.remainingDuration + ) + .add() + .append( + new KeyedCodec<>("SinceLastDamage", Codec.FLOAT), (entityEffect, x) -> entityEffect.sinceLastDamage = x, entityEffect -> entityEffect.sinceLastDamage + ) + .add() + .append( + new KeyedCodec<>("HasBeenDamaged", Codec.BOOLEAN), (entityEffect, x) -> entityEffect.hasBeenDamaged = x, entityEffect -> entityEffect.hasBeenDamaged + ) + .add() + .append( + new KeyedCodec<>("SequentialHits", DamageCalculatorSystems.Sequence.CODEC), + (entityEffect, x) -> entityEffect.sequentialHits = x, + entityEffect -> entityEffect.sequentialHits + ) + .add() + .append(new KeyedCodec<>("Infinite", Codec.BOOLEAN), (entityEffect, aBoolean) -> entityEffect.infinite = aBoolean, entityEffect -> entityEffect.infinite) + .add() + .append(new KeyedCodec<>("Debuff", Codec.BOOLEAN), (entityEffect, aBoolean) -> entityEffect.debuff = aBoolean, entityEffect -> entityEffect.debuff) + .add() + .append( + new KeyedCodec<>("StatusEffectIcon", Codec.STRING), + (entityEffect, aString) -> entityEffect.statusEffectIcon = aString, + entityEffect -> entityEffect.statusEffectIcon + ) + .add() + .append( + new KeyedCodec<>("Invulnerable", Codec.BOOLEAN), + (entityEffect, aBoolean) -> entityEffect.invulnerable = aBoolean, + entityEffect -> entityEffect.invulnerable + ) + .add() + .build(); + private static final float DEFAULT_DURATION = 1.0F; + @Nonnull + private static final Message MESSAGE_GENERAL_DAMAGE_CAUSES_UNKNOWN = Message.translation("server.general.damageCauses.unknown"); + protected String entityEffectId; + protected int entityEffectIndex; + protected float initialDuration; + protected float remainingDuration; + protected boolean infinite; + protected boolean debuff; + @Nullable + protected String statusEffectIcon; + private float sinceLastDamage; + private boolean hasBeenDamaged; + protected boolean invulnerable; + private DamageCalculatorSystems.Sequence sequentialHits; + + public ActiveEntityEffect() { + } + + public ActiveEntityEffect( + @Nonnull String entityEffectId, + int entityEffectIndex, + float initialDuration, + float remainingDuration, + boolean infinite, + boolean debuff, + @Nullable String statusEffectIcon, + float sinceLastDamage, + boolean hasBeenDamaged, + @Nonnull DamageCalculatorSystems.Sequence sequentialHits, + boolean invulnerable + ) { + this.entityEffectId = entityEffectId; + this.entityEffectIndex = entityEffectIndex; + this.initialDuration = initialDuration; + this.remainingDuration = remainingDuration; + this.infinite = infinite; + this.debuff = debuff; + this.statusEffectIcon = statusEffectIcon; + this.sinceLastDamage = sinceLastDamage; + this.hasBeenDamaged = hasBeenDamaged; + this.sequentialHits = sequentialHits; + this.invulnerable = invulnerable; + } + + public ActiveEntityEffect( + @Nonnull String entityEffectId, int entityEffectIndex, float duration, boolean debuff, @Nullable String statusEffectIcon, boolean invulnerable + ) { + this( + entityEffectId, + entityEffectIndex, + duration, + duration, + false, + debuff, + statusEffectIcon, + 0.0F, + false, + new DamageCalculatorSystems.Sequence(), + invulnerable + ); + } + + public ActiveEntityEffect(@Nonnull String entityEffectId, int entityEffectIndex, boolean infinite, boolean invulnerable) { + this(entityEffectId, entityEffectIndex, 1.0F, 1.0F, infinite, false, "", 0.0F, false, new DamageCalculatorSystems.Sequence(), invulnerable); + } + + public void tick( + @Nonnull CommandBuffer commandBuffer, + @Nonnull Ref ref, + @Nonnull EntityEffect entityEffect, + @Nonnull EntityStatMap entityStatMapComponent, + float dt + ) { + int cyclesToRun = this.calculateCyclesToRun(entityEffect, dt); + this.tickDamage(commandBuffer, ref, entityEffect, cyclesToRun); + tickStatChanges(commandBuffer, ref, entityEffect, entityStatMapComponent, cyclesToRun); + if (!this.infinite) { + this.remainingDuration -= dt; + } + } + + private int calculateCyclesToRun(@Nonnull EntityEffect entityEffect, float dt) { + int cycles = 0; + float damageCalculatorCooldown = entityEffect.getDamageCalculatorCooldown(); + if (damageCalculatorCooldown > 0.0F) { + this.sinceLastDamage += dt; + cycles = MathUtil.fastFloor(this.sinceLastDamage / damageCalculatorCooldown); + this.sinceLastDamage %= damageCalculatorCooldown; + } else if (!this.hasBeenDamaged) { + cycles = 1; + this.hasBeenDamaged = true; + } + + return cycles; + } + + private static void tickStatChanges( + @Nonnull CommandBuffer commandBuffer, + @Nonnull Ref ref, + @Nonnull EntityEffect entityEffect, + @Nonnull EntityStatMap entityStatMapComponent, + int cyclesToRun + ) { + Int2FloatMap entityStats = entityEffect.getEntityStats(); + if (entityStats != null) { + if (cyclesToRun > 0) { + DamageEffects statModifierEffects = entityEffect.getStatModifierEffects(); + if (statModifierEffects != null) { + statModifierEffects.spawnAtEntity(commandBuffer, ref); + } + + entityStatMapComponent.processStatChanges(EntityStatMap.Predictable.ALL, entityStats, entityEffect.getValueType(), ChangeStatBehaviour.Add); + } + } + } + + private void tickDamage( + @Nonnull CommandBuffer commandBuffer, @Nonnull Ref ref, @Nonnull EntityEffect entityEffect, int cyclesToRun + ) { + DamageCalculator damageCalculator = entityEffect.getDamageCalculator(); + if (damageCalculator != null) { + if (cyclesToRun > 0) { + Object2FloatMap relativeDamage = damageCalculator.calculateDamage(this.initialDuration); + if (relativeDamage != null && !relativeDamage.isEmpty()) { + World world = commandBuffer.getExternalData().getWorld(); + DamageEffects damageEffects = entityEffect.getDamageEffects(); + Damage[] hits = DamageCalculatorSystems.queueDamageCalculator(world, relativeDamage, ref, commandBuffer, this, null); + + for (Damage damageEvent : hits) { + DamageCalculatorSystems.DamageSequence damageSequence = new DamageCalculatorSystems.DamageSequence(this.sequentialHits, damageCalculator); + damageEvent.putMetaObject(DamageCalculatorSystems.DAMAGE_SEQUENCE, damageSequence); + if (damageEffects != null) { + damageEffects.addToDamage(damageEvent); + } + + commandBuffer.invoke(ref, damageEvent); + } + } + } + } + } + + public int getEntityEffectIndex() { + return this.entityEffectIndex; + } + + public float getInitialDuration() { + return this.initialDuration; + } + + public float getRemainingDuration() { + return this.remainingDuration; + } + + public boolean isInfinite() { + return this.infinite; + } + + public boolean isDebuff() { + return this.debuff; + } + + public boolean isInvulnerable() { + return this.invulnerable; + } + + @Nonnull + @Override + public Message getDeathMessage(@Nonnull Damage info, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor) { + EntityEffect entityEffect = EntityEffect.getAssetMap().getAsset(this.entityEffectIndex); + Message damageCauseMessage; + if (entityEffect != null) { + String locale = entityEffect.getLocale(); + String reason = locale != null ? locale : entityEffect.getId().toLowerCase(Locale.ROOT); + damageCauseMessage = Message.translation("server.general.damageCauses." + reason); + } else { + damageCauseMessage = MESSAGE_GENERAL_DAMAGE_CAUSES_UNKNOWN; + } + + return Message.translation("server.general.killedBy").param("damageSource", damageCauseMessage); + } + + @Nonnull + @Override + public String toString() { + return "ActiveEntityEffect{entityEffectIndex='" + + this.entityEffectIndex + + "', initialDuration=" + + this.initialDuration + + ", remainingDuration=" + + this.remainingDuration + + ", damageCooldown=" + + this.sinceLastDamage + + ", hasBeenDamaged=" + + this.hasBeenDamaged + + ", sequentialHits=" + + this.sequentialHits + + ", infinite=" + + this.infinite + + ", debuff=" + + this.debuff + + ", statusEffectIcon=" + + this.statusEffectIcon + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/effect/EffectControllerComponent.java b/src/com/hypixel/hytale/server/core/entity/effect/EffectControllerComponent.java new file mode 100644 index 0000000..00964ba --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/effect/EffectControllerComponent.java @@ -0,0 +1,417 @@ +package com.hypixel.hytale.server.core.entity.effect; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.EffectOp; +import com.hypixel.hytale.protocol.EntityEffectUpdate; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.OverlapBehavior; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.RemovalBehavior; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.livingentity.LivingEntityEffectSystem; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSkinComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArraySet; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ObjectList; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EffectControllerComponent implements Component { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(EffectControllerComponent.class, EffectControllerComponent::new) + .append( + new KeyedCodec<>("ActiveEntityEffects", new ArrayCodec<>(ActiveEntityEffect.CODEC, ActiveEntityEffect[]::new)), + EffectControllerComponent::addActiveEntityEffects, + EffectControllerComponent::getAllActiveEntityEffects + ) + .add() + .build(); + @Nonnull + protected final Int2ObjectMap activeEffects = new Int2ObjectOpenHashMap<>(); + @Nullable + protected int[] cachedActiveEffectIndexes; + @Nonnull + protected ObjectList changes = new ObjectArrayList<>(); + protected boolean isNetworkOutdated; + @Nullable + protected Model originalModel = null; + protected int activeModelChangeEntityEffectIndex; + protected boolean isInvulnerable; + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getEffectControllerComponentType(); + } + + public EffectControllerComponent() { + } + + public EffectControllerComponent(@Nonnull EffectControllerComponent effectControllerComponent) { + this.originalModel = effectControllerComponent.originalModel; + this.activeModelChangeEntityEffectIndex = effectControllerComponent.activeModelChangeEntityEffectIndex; + this.changes.addAll(effectControllerComponent.changes); + ActiveEntityEffect[] activeEntityEffects = effectControllerComponent.getAllActiveEntityEffects(); + if (activeEntityEffects != null) { + effectControllerComponent.addActiveEntityEffects(activeEntityEffects); + } + } + + public boolean isInvulnerable() { + return this.isInvulnerable; + } + + public void setInvulnerable(boolean invulnerable) { + this.isInvulnerable = invulnerable; + } + + public boolean addEffect(@Nonnull Ref ownerRef, @Nonnull EntityEffect entityEffect, @Nonnull ComponentAccessor componentAccessor) { + int entityEffectIndex = EntityEffect.getAssetMap().getIndex(entityEffect.getId()); + return entityEffectIndex == Integer.MIN_VALUE ? false : this.addEffect(ownerRef, entityEffectIndex, entityEffect, componentAccessor); + } + + public boolean addEffect( + @Nonnull Ref ownerRef, int entityEffectIndex, @Nonnull EntityEffect entityEffect, @Nonnull ComponentAccessor componentAccessor + ) { + boolean infinite = entityEffect.isInfinite(); + float duration = entityEffect.getDuration(); + OverlapBehavior overlapBehavior = entityEffect.getOverlapBehavior(); + return infinite + ? this.addInfiniteEffect(ownerRef, entityEffectIndex, entityEffect, componentAccessor) + : this.addEffect(ownerRef, entityEffectIndex, entityEffect, duration, overlapBehavior, componentAccessor); + } + + public boolean addEffect( + @Nonnull Ref ownerRef, + @Nonnull EntityEffect entityEffect, + float duration, + @Nonnull OverlapBehavior overlapBehavior, + @Nonnull ComponentAccessor componentAccessor + ) { + int entityEffectIndex = EntityEffect.getAssetMap().getIndex(entityEffect.getId()); + return entityEffectIndex == Integer.MIN_VALUE + ? false + : this.addEffect(ownerRef, entityEffectIndex, entityEffect, duration, overlapBehavior, componentAccessor); + } + + public boolean addEffect( + @Nonnull Ref ownerRef, + int entityEffectIndex, + @Nonnull EntityEffect entityEffect, + float duration, + @Nonnull OverlapBehavior overlapBehavior, + @Nonnull ComponentAccessor componentAccessor + ) { + if (!LivingEntityEffectSystem.canApplyEffect(ownerRef, entityEffect, componentAccessor)) { + return false; + } else { + ActiveEntityEffect currentActiveEntityEffectEntry = this.activeEffects.get(entityEffectIndex); + label21: + if (currentActiveEntityEffectEntry == null) { + ActiveEntityEffect activeEntityEffectEntry = new ActiveEntityEffect( + entityEffect.getId(), entityEffectIndex, duration, entityEffect.isDebuff(), entityEffect.getStatusEffectIcon(), entityEffect.isInvulnerable() + ); + this.activeEffects.put(entityEffectIndex, activeEntityEffectEntry); + if (EntityUtils.getEntity(ownerRef, componentAccessor) instanceof LivingEntity ownerLivingEntity) { + ownerLivingEntity.getStatModifiersManager().setRecalculate(true); + } + + this.setModelChange(ownerRef, entityEffect, entityEffectIndex, componentAccessor); + this.addChange( + new EntityEffectUpdate( + EffectOp.Add, + entityEffectIndex, + activeEntityEffectEntry.remainingDuration, + false, + activeEntityEffectEntry.debuff, + activeEntityEffectEntry.statusEffectIcon + ) + ); + this.invalidateCache(); + return true; + } else if (currentActiveEntityEffectEntry.isInfinite()) { + return true; + } else { + switch (overlapBehavior) { + case EXTEND: + currentActiveEntityEffectEntry.remainingDuration += duration; + this.addChange( + new EntityEffectUpdate( + EffectOp.Add, + entityEffectIndex, + currentActiveEntityEffectEntry.remainingDuration, + false, + currentActiveEntityEffectEntry.debuff, + currentActiveEntityEffectEntry.statusEffectIcon + ) + ); + return true; + case IGNORE: + return true; + case OVERWRITE: + default: + break label21; + } + } + } + } + + public boolean addInfiniteEffect( + @Nonnull Ref ownerRef, int entityEffectIndex, @Nonnull EntityEffect entityEffect, @Nonnull ComponentAccessor componentAccessor + ) { + if (!LivingEntityEffectSystem.canApplyEffect(ownerRef, entityEffect, componentAccessor)) { + return false; + } else { + ActiveEntityEffect currentActiveEntityEffectEntry = this.activeEffects.get(entityEffectIndex); + if (currentActiveEntityEffectEntry == null) { + currentActiveEntityEffectEntry = new ActiveEntityEffect(entityEffect.getId(), entityEffectIndex, true, entityEffect.isInvulnerable()); + this.activeEffects.put(entityEffectIndex, currentActiveEntityEffectEntry); + if (EntityUtils.getEntity(ownerRef, componentAccessor) instanceof LivingEntity ownerLivingEntity) { + ownerLivingEntity.getStatModifiersManager().setRecalculate(true); + } + + this.invalidateCache(); + } else if (!currentActiveEntityEffectEntry.isInfinite()) { + currentActiveEntityEffectEntry.infinite = true; + } + + this.setModelChange(ownerRef, entityEffect, entityEffectIndex, componentAccessor); + this.addChange( + new EntityEffectUpdate( + EffectOp.Add, + entityEffectIndex, + currentActiveEntityEffectEntry.remainingDuration, + true, + currentActiveEntityEffectEntry.debuff, + currentActiveEntityEffectEntry.statusEffectIcon + ) + ); + return true; + } + } + + public void setModelChange( + @Nonnull Ref ownerRef, @Nonnull EntityEffect entityEffect, int entityEffectIndex, @Nonnull ComponentAccessor componentAccessor + ) { + if (this.originalModel == null) { + if (entityEffect.getModelChange() != null) { + ModelComponent modelComponent = componentAccessor.getComponent(ownerRef, ModelComponent.getComponentType()); + + assert modelComponent != null; + + this.originalModel = modelComponent.getModel(); + this.activeModelChangeEntityEffectIndex = entityEffectIndex; + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(entityEffect.getModelChange()); + Model scaledModel = Model.createRandomScaleModel(modelAsset); + componentAccessor.putComponent(ownerRef, ModelComponent.getComponentType(), new ModelComponent(scaledModel)); + } + } + } + + public void tryResetModelChange(@Nonnull Ref ownerRef, int activeEffectIndex, @Nonnull ComponentAccessor componentAccessor) { + if (this.originalModel != null && this.activeModelChangeEntityEffectIndex == activeEffectIndex) { + componentAccessor.putComponent(ownerRef, ModelComponent.getComponentType(), new ModelComponent(this.originalModel)); + PlayerSkinComponent playerSkinComponent = componentAccessor.getComponent(ownerRef, PlayerSkinComponent.getComponentType()); + if (playerSkinComponent != null) { + playerSkinComponent.setNetworkOutdated(); + } + + this.originalModel = null; + } + } + + public void addActiveEntityEffects(@Nonnull ActiveEntityEffect[] activeEntityEffects) { + if (activeEntityEffects.length != 0) { + for (ActiveEntityEffect activeEntityEffect : activeEntityEffects) { + int entityEffectIndex = EntityEffect.getAssetMap().getIndex(activeEntityEffect.entityEffectId); + if (entityEffectIndex != Integer.MIN_VALUE) { + activeEntityEffect.entityEffectIndex = entityEffectIndex; + this.activeEffects.put(entityEffectIndex, activeEntityEffect); + this.addChange( + new EntityEffectUpdate( + EffectOp.Add, + entityEffectIndex, + activeEntityEffect.remainingDuration, + activeEntityEffect.infinite, + activeEntityEffect.debuff, + activeEntityEffect.statusEffectIcon + ) + ); + } + } + + this.invalidateCache(); + } + } + + public void removeEffect(@Nonnull Ref ownerRef, int entityEffectIndex, @Nonnull ComponentAccessor componentAccessor) { + EntityEffect entityEffect = EntityEffect.getAssetMap().getAsset(entityEffectIndex); + if (entityEffect == null) { + throw new IllegalArgumentException(String.format("Unknown EntityEffect with index \"%s\"", entityEffectIndex)); + } else { + this.removeEffect(ownerRef, entityEffectIndex, entityEffect.getRemovalBehavior(), componentAccessor); + } + } + + public void removeEffect( + @Nonnull Ref ownerRef, + int entityEffectIndex, + @Nonnull RemovalBehavior removalBehavior, + @Nonnull ComponentAccessor componentAccessor + ) { + ActiveEntityEffect activeEffectEntry = this.activeEffects.get(entityEffectIndex); + if (activeEffectEntry != null) { + this.tryResetModelChange(ownerRef, activeEffectEntry.getEntityEffectIndex(), componentAccessor); + switch (removalBehavior) { + case COMPLETE: + this.activeEffects.remove(entityEffectIndex); + if (EntityUtils.getEntity(ownerRef, componentAccessor) instanceof LivingEntity ownerLivingEntity) { + ownerLivingEntity.getStatModifiersManager().setRecalculate(true); + } + + this.addChange(new EntityEffectUpdate(EffectOp.Remove, entityEffectIndex, 0.0F, false, false, "")); + this.invalidateCache(); + return; + case INFINITE: + activeEffectEntry.infinite = false; + break; + case DURATION: + activeEffectEntry.remainingDuration = 0.0F; + } + + if (EntityUtils.getEntity(ownerRef, componentAccessor) instanceof LivingEntity ownerLivingEntity) { + ownerLivingEntity.getStatModifiersManager().setRecalculate(true); + } + + this.addChange( + new EntityEffectUpdate( + EffectOp.Remove, + entityEffectIndex, + activeEffectEntry.remainingDuration, + activeEffectEntry.infinite, + activeEffectEntry.debuff, + activeEffectEntry.statusEffectIcon + ) + ); + } + } + + private void addChange(@Nonnull EntityEffectUpdate update) { + this.isNetworkOutdated = true; + this.changes.add(update); + } + + public void clearEffects(@Nonnull Ref ownerRef, @Nonnull ComponentAccessor componentAccessor) { + for (int effect : new IntArraySet(this.activeEffects.keySet())) { + this.removeEffect(ownerRef, effect, componentAccessor); + } + + this.invalidateCache(); + if (this.originalModel != null) { + componentAccessor.putComponent(ownerRef, ModelComponent.getComponentType(), new ModelComponent(this.originalModel)); + this.originalModel = null; + } + } + + public void invalidateCache() { + this.cachedActiveEffectIndexes = null; + } + + @Nonnull + public Int2ObjectMap getActiveEffects() { + return this.activeEffects; + } + + public int[] getActiveEffectIndexes() { + if (this.cachedActiveEffectIndexes == null) { + if (this.activeEffects.isEmpty()) { + this.cachedActiveEffectIndexes = ArrayUtil.EMPTY_INT_ARRAY; + } else { + this.cachedActiveEffectIndexes = this.activeEffects.keySet().toIntArray(); + } + } + + return this.cachedActiveEffectIndexes; + } + + public boolean consumeNetworkOutdated() { + boolean temp = this.isNetworkOutdated; + this.isNetworkOutdated = false; + return temp; + } + + @Nonnull + public EntityEffectUpdate[] consumeChanges() { + return this.changes.toArray(EntityEffectUpdate[]::new); + } + + public void clearChanges() { + this.changes.clear(); + } + + @Nonnull + public EntityEffectUpdate[] createInitUpdates() { + EntityEffectUpdate[] changeArray = new EntityEffectUpdate[this.activeEffects.size()]; + int index = 0; + ObjectIterator> iterator = Int2ObjectMaps.fastIterator(this.activeEffects); + + while (iterator.hasNext()) { + Entry entry = iterator.next(); + ActiveEntityEffect activeEntityEffectEntry = entry.getValue(); + changeArray[index++] = new EntityEffectUpdate( + EffectOp.Add, + entry.getIntKey(), + activeEntityEffectEntry.remainingDuration, + activeEntityEffectEntry.infinite, + activeEntityEffectEntry.debuff, + activeEntityEffectEntry.statusEffectIcon + ); + } + + return changeArray; + } + + @Nullable + public ActiveEntityEffect[] getAllActiveEntityEffects() { + if (this.activeEffects.isEmpty()) { + return null; + } else { + ActiveEntityEffect[] activeEntityEffects = new ActiveEntityEffect[this.activeEffects.size()]; + int index = 0; + + for (ActiveEntityEffect entityEffect : this.activeEffects.values()) { + activeEntityEffects[index] = entityEffect; + index++; + } + + return activeEntityEffects; + } + } + + @Nonnull + @Override + public String toString() { + return "EntityEffectController{, activeEffects=" + this.activeEffects + "}"; + } + + @Nonnull + public EffectControllerComponent clone() { + return new EffectControllerComponent(this); + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/BlockEntity.java b/src/com/hypixel/hytale/server/core/entity/entities/BlockEntity.java new file mode 100644 index 0000000..820e2a9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/BlockEntity.java @@ -0,0 +1,134 @@ +package com.hypixel.hytale.server.core.entity.entities; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.projectile.config.Projectile; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.modules.entity.BlockMigrationExtraInfo; +import com.hypixel.hytale.server.core.modules.entity.DespawnComponent; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.physics.SimplePhysicsProvider; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockEntity implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockEntity.class, BlockEntity::new) + .append(new KeyedCodec<>("BlockTypeKey", Codec.STRING), (blockEntity, newBlockTypeKey, extraInfo) -> { + blockEntity.blockTypeKey = newBlockTypeKey; + if (extraInfo instanceof BlockMigrationExtraInfo) { + blockEntity.blockTypeKey = ((BlockMigrationExtraInfo)extraInfo).getBlockMigration().apply(newBlockTypeKey); + } + }, (blockEntity, extraInfo) -> blockEntity.blockTypeKey) + .add() + .build(); + public static final int DEFAULT_DESPAWN_SECONDS = 120; + @Nonnull + private transient SimplePhysicsProvider simplePhysicsProvider = new SimplePhysicsProvider(); + protected String blockTypeKey; + private boolean isBlockIdNetworkOutdated; + + public static ComponentType getComponentType() { + return EntityModule.get().getBlockEntityComponentType(); + } + + protected BlockEntity() { + } + + public BlockEntity(String blockTypeKey) { + this.blockTypeKey = blockTypeKey; + } + + @Nonnull + public static Holder assembleDefaultBlockEntity(@Nonnull TimeResource time, String blockTypeKey, @Nonnull Vector3d position) { + Holder holder = EntityStore.REGISTRY.newHolder(); + holder.addComponent(getComponentType(), new BlockEntity(blockTypeKey)); + holder.addComponent(DespawnComponent.getComponentType(), DespawnComponent.despawnInSeconds(time, 120)); + holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(position.clone(), Vector3f.FORWARD)); + holder.ensureComponent(Velocity.getComponentType()); + holder.ensureComponent(UUIDComponent.getComponentType()); + return holder; + } + + @Nonnull + public SimplePhysicsProvider initPhysics(@Nonnull BoundingBox boundingBox) { + this.simplePhysicsProvider.initialize(Projectile.getAssetMap().getAsset("Projectile"), boundingBox); + this.simplePhysicsProvider.setProvideCharacterCollisions(false); + this.simplePhysicsProvider.setMoveOutOfSolid(true); + return this.simplePhysicsProvider; + } + + @Nonnull + public BoundingBox updateHitbox(@Nonnull Ref ref, @Nonnull CommandBuffer commandBuffer) { + BoundingBox boundingBoxComponent = this.createBoundingBoxComponent(); + commandBuffer.putComponent(ref, BoundingBox.getComponentType(), boundingBoxComponent); + return boundingBoxComponent; + } + + @Nullable + public BoundingBox createBoundingBoxComponent() { + if (this.blockTypeKey == null) { + return null; + } else { + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + if (assetMap == null) { + return null; + } else { + BlockType blockType = assetMap.getAsset(this.blockTypeKey); + return blockType == null + ? null + : new BoundingBox(BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex()).get(0).getBoundingBox()); + } + } + } + + public void setBlockTypeKey(String blockTypeKey, @Nonnull Ref ref, @Nonnull CommandBuffer commandBuffer) { + this.blockTypeKey = blockTypeKey; + this.isBlockIdNetworkOutdated = true; + this.updateHitbox(ref, commandBuffer); + } + + @Nonnull + public SimplePhysicsProvider getSimplePhysicsProvider() { + return this.simplePhysicsProvider; + } + + public String getBlockTypeKey() { + return this.blockTypeKey; + } + + public void addForce(float x, float y, float z) { + this.simplePhysicsProvider.addVelocity(x, y, z); + } + + public void addForce(@Nonnull Vector3d force) { + this.simplePhysicsProvider.addVelocity((float)force.x, (float)force.y, (float)force.z); + } + + public boolean consumeBlockIdNetworkOutdated() { + boolean temp = this.isBlockIdNetworkOutdated; + this.isBlockIdNetworkOutdated = false; + return temp; + } + + @Nonnull + @Override + public Component clone() { + return new BlockEntity(this.blockTypeKey); + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/Player.java b/src/com/hypixel/hytale/server/core/entity/entities/Player.java new file mode 100644 index 0000000..d50550b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/Player.java @@ -0,0 +1,868 @@ +package com.hypixel.hytale.server.core.entity.entities; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.metrics.MetricProvider; +import com.hypixel.hytale.metrics.MetricResults; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.protocol.SavedMovementStates; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.player.SetBlockPlacementOverride; +import com.hypixel.hytale.protocol.packets.player.SetGameMode; +import com.hypixel.hytale.protocol.packets.player.SetMovementStates; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.gamemode.GameModeType; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.player.CameraManager; +import com.hypixel.hytale.server.core.entity.entities.player.HotbarManager; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerRespawnPointData; +import com.hypixel.hytale.server.core.entity.entities.player.hud.HudManager; +import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementManager; +import com.hypixel.hytale.server.core.entity.entities.player.pages.PageManager; +import com.hypixel.hytale.server.core.entity.entities.player.windows.WindowManager; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.event.events.ecs.ChangeGameModeEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerReadyEvent; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.modules.collision.CollisionModule; +import com.hypixel.hytale.server.core.modules.collision.CollisionResult; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.CollisionResultComponent; +import com.hypixel.hytale.server.core.modules.entity.component.Invulnerable; +import com.hypixel.hytale.server.core.modules.entity.component.RespondToHit; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings; +import com.hypixel.hytale.server.core.modules.entity.tracker.LegacyEntityTrackerSystems; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.permissions.PermissionHolder; +import com.hypixel.hytale.server.core.permissions.PermissionsModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.NotificationUtil; +import com.hypixel.hytale.server.core.util.TempAssetIdUtil; +import it.unimi.dsi.fastutil.Pair; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Player extends LivingEntity implements CommandSender, PermissionHolder, MetricProvider { + @Nonnull + public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() + .register("Uuid", Entity::getUuid, Codec.UUID_STRING) + .register("ClientViewRadius", Player::getClientViewRadius, Codec.INTEGER); + @Nonnull + public static final KeyedCodec PLAYER_CONFIG_DATA = new KeyedCodec<>("PlayerData", PlayerConfigData.CODEC); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(Player.class, Player::new, LivingEntity.CODEC) + .append(PLAYER_CONFIG_DATA, (player, data) -> player.data = data, player -> player.data) + .add() + .append( + new KeyedCodec<>("BlockPlacementOverride", Codec.BOOLEAN), + (player, blockPlacementOverride) -> player.overrideBlockPlacementRestrictions = blockPlacementOverride, + player -> player.overrideBlockPlacementRestrictions + ) + .add() + .append( + new KeyedCodec<>("HotbarManager", HotbarManager.CODEC), + (player, hotbarManager) -> player.hotbarManager = hotbarManager, + player -> player.hotbarManager + ) + .add() + .appendInherited( + new KeyedCodec<>("GameMode", ProtocolCodecs.GAMEMODE_LEGACY), + (player, s) -> player.gameMode = s, + player -> player.gameMode, + (player, parent) -> player.gameMode = parent.gameMode + ) + .documentation("The last known game-mode of the entity.") + .add() + .build(); + public static final int DEFAULT_VIEW_RADIUS_CHUNKS = 6; + public static final long RESPAWN_INVULNERABILITY_TIME_NANOS = TimeUnit.MILLISECONDS.toNanos(3000L); + public static final long MAX_TELEPORT_INVULNERABILITY_MILLIS = 10000L; + @Deprecated(forRemoval = true) + private PlayerRef playerRef; + @Nonnull + private PlayerConfigData data = new PlayerConfigData(); + @Nonnull + private final WorldMapTracker worldMapTracker = new WorldMapTracker(this); + @Nonnull + private final WindowManager windowManager = new WindowManager(); + @Nonnull + private final PageManager pageManager = new PageManager(); + @Nonnull + private final HudManager hudManager = new HudManager(); + @Nonnull + private HotbarManager hotbarManager = new HotbarManager(); + private GameMode gameMode; + private int clientViewRadius = 6; + protected long lastSpawnTimeNanos; + private static final int MAX_VELOCITY_SAMPLE_COUNT = 2; + private static final int VELOCITY_SAMPLE_LENGTH = 12; + private static final double[][] velocitySampleWeights = new double[][]{{1.0}, {0.9, 0.1}}; + private final double[] velocitySamples = new double[12]; + private int velocitySampleCount; + private int velocitySampleIndex = 4; + private boolean overrideBlockPlacementRestrictions; + private final AtomicInteger readyId = new AtomicInteger(); + private final AtomicReference> waitingForClientReady = new AtomicReference<>(); + public boolean executeTriggers; + public boolean executeBlockDamage; + private boolean firstSpawn; + private int mountEntityId; + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getPlayerComponentType(); + } + + public Player() { + } + + public void copyFrom(@Nonnull Player oldPlayerComponent) { + this.init(this.legacyUuid, this.playerRef); + this.worldMapTracker.copyFrom(oldPlayerComponent.worldMapTracker); + this.clientViewRadius = oldPlayerComponent.clientViewRadius; + this.readyId.set(oldPlayerComponent.readyId.get()); + } + + public void init(@Nonnull UUID uuid, @Nonnull PlayerRef playerRef) { + this.legacyUuid = uuid; + this.playerRef = playerRef; + this.windowManager.init(playerRef); + this.pageManager.init(playerRef, this.windowManager); + } + + public void setNetworkId(int id) { + this.networkId = id; + } + + @Nonnull + @Override + protected Inventory createDefaultInventory() { + return new Inventory(); + } + + @Nonnull + @Override + public Inventory setInventory(Inventory inventory) { + return super.setInventory(inventory, true); + } + + @Override + public boolean remove() { + if (this.wasRemoved.getAndSet(true)) { + return false; + } else { + this.removedBy = new Throwable(); + if (this.world != null && this.world.isAlive()) { + if (this.world.isInThread()) { + Ref ref = this.playerRef.getReference(); + if (ref != null) { + Store store = ref.getStore(); + ChunkTracker tracker = store.getComponent(ref, ChunkTracker.getComponentType()); + if (tracker != null) { + tracker.clear(); + } + + this.playerRef.removeFromStore(); + } + } else { + this.world.execute(() -> { + Ref ref = this.playerRef.getReference(); + if (ref != null) { + Store storex = ref.getStore(); + ChunkTracker trackerx = storex.getComponent(ref, ChunkTracker.getComponentType()); + if (trackerx != null) { + trackerx.clear(); + } + + this.playerRef.removeFromStore(); + } + }); + } + } + + if (this.playerRef.getPacketHandler().getChannel().isActive()) { + this.playerRef.getPacketHandler().disconnect("Player removed from world!"); + LOGGER.at(Level.WARNING).withCause(this.removedBy).log("Player removed from world! %s", this); + } + + ScheduledFuture task; + if ((task = this.waitingForClientReady.getAndSet(null)) != null) { + task.cancel(false); + } + + return true; + } + } + + @Override + public void moveTo(@Nonnull Ref ref, double locX, double locY, double locZ, @Nonnull ComponentAccessor componentAccessor) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + this.addLocationChange(ref, locX - position.getX(), locY - position.getY(), locZ - position.getZ(), componentAccessor); + super.moveTo(ref, locX, locY, locZ, componentAccessor); + this.windowManager.validateWindows(); + } + + @Nonnull + public PlayerConfigData getPlayerConfigData() { + return this.data; + } + + @Override + public void markNeedsSave() { + this.data.markChanged(); + } + + @Override + public void unloadFromWorld() { + super.unloadFromWorld(); + } + + public void applyMovementStates( + @Nonnull Ref ref, + @Nonnull SavedMovementStates savedMovementStates, + @Nonnull MovementStates movementStates, + @Nonnull ComponentAccessor componentAccessor + ) { + movementStates.flying = savedMovementStates.flying; + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerRefComponent.getPacketHandler().writeNoCache(new SetMovementStates(new SavedMovementStates(movementStates.flying))); + } + + public void startClientReadyTimeout() { + ScheduledFuture task = HytaleServer.SCHEDULED_EXECUTOR.schedule(() -> this.handleClientReady(true), 10000L, TimeUnit.MILLISECONDS); + ScheduledFuture oldTask = this.waitingForClientReady.getAndSet(task); + if (oldTask != null) { + oldTask.cancel(false); + } + } + + public void handleClientReady(boolean forced) { + ScheduledFuture task; + if ((task = this.waitingForClientReady.getAndSet(null)) != null) { + task.cancel(false); + if (this.world == null) { + return; + } + + IEventDispatcher dispatcher = HytaleServer.get() + .getEventBus() + .dispatchFor(PlayerReadyEvent.class, this.world.getName()); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new PlayerReadyEvent(this.reference, this, this.readyId.getAndIncrement())); + } + } + } + + public void sendInventory() { + this.getInventory().consumeIsDirty(); + this.playerRef.getPacketHandler().write(this.getInventory().toPacket()); + } + + @Nonnull + public CompletableFuture saveConfig(@Nonnull World world, @Nonnull Holder holder) { + MovementStatesComponent movementStatesComponent = holder.getComponent(MovementStatesComponent.getComponentType()); + + assert movementStatesComponent != null; + + UUIDComponent uuidComponent = holder.getComponent(UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + this.data.getPerWorldData(world.getName()).setLastMovementStates(movementStatesComponent.getMovementStates(), false); + return Universe.get().getPlayerStorage().save(uuidComponent.getUuid(), holder); + } + + @Deprecated(forRemoval = true) + public PacketHandler getPlayerConnection() { + return this.playerRef.getPacketHandler(); + } + + @Nonnull + public WorldMapTracker getWorldMapTracker() { + return this.worldMapTracker; + } + + @Nonnull + public WindowManager getWindowManager() { + return this.windowManager; + } + + @Nonnull + public PageManager getPageManager() { + return this.pageManager; + } + + @Nonnull + public HudManager getHudManager() { + return this.hudManager; + } + + @Nonnull + public HotbarManager getHotbarManager() { + return this.hotbarManager; + } + + public boolean isFirstSpawn() { + return this.firstSpawn; + } + + public void setFirstSpawn(boolean firstSpawn) { + this.firstSpawn = firstSpawn; + } + + public void resetManagers(@Nonnull Holder holder) { + PlayerRef playerRef = this.playerRef; + LegacyEntityTrackerSystems.clear(this, holder); + this.worldMapTracker.clear(); + this.windowManager.closeAllWindows(); + this.hudManager.resetUserInterface(this.playerRef); + this.hudManager.resetHud(this.playerRef); + CameraManager cameraManagerComponent = playerRef.getComponent(CameraManager.getComponentType()); + + assert cameraManagerComponent != null; + + cameraManagerComponent.resetCamera(playerRef); + MovementManager movementManagerComponent = playerRef.getComponent(MovementManager.getComponentType()); + + assert movementManagerComponent != null; + + movementManagerComponent.applyDefaultSettings(); + movementManagerComponent.update(playerRef.getPacketHandler()); + } + + public void notifyPickupItem( + @Nonnull Ref ref, @Nonnull ItemStack itemStack, @Nullable Vector3d position, @Nonnull ComponentAccessor componentAccessor + ) { + World world = componentAccessor.getExternalData().getWorld(); + if (world.getGameplayConfig().getShowItemPickupNotifications()) { + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + Message itemNameMessage = Message.translation(itemStack.getItem().getTranslationKey()); + NotificationUtil.sendNotification( + playerRefComponent.getPacketHandler(), + Message.translation("server.general.pickedUpItem").param("item", itemNameMessage), + null, + itemStack.toPacket() + ); + } + + if (position != null) { + SoundUtil.playSoundEvent3dToPlayer(ref, TempAssetIdUtil.getSoundEventIndex("SFX_Player_Pickup_Item"), SoundCategory.UI, position, componentAccessor); + } else { + SoundUtil.playSoundEvent2d(ref, TempAssetIdUtil.getSoundEventIndex("SFX_Player_Pickup_Item"), SoundCategory.UI, componentAccessor); + } + } + + public boolean isOverrideBlockPlacementRestrictions() { + return this.overrideBlockPlacementRestrictions; + } + + public void setOverrideBlockPlacementRestrictions( + @Nonnull Ref ref, boolean overrideBlockPlacementRestrictions, @Nonnull ComponentAccessor componentAccessor + ) { + this.overrideBlockPlacementRestrictions = overrideBlockPlacementRestrictions; + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerRefComponent.getPacketHandler().writeNoCache(new SetBlockPlacementOverride(overrideBlockPlacementRestrictions)); + } + + @Override + public void sendMessage(@Nonnull Message message) { + this.playerRef.sendMessage(message); + } + + @Override + public boolean hasPermission(@Nonnull String id) { + return PermissionsModule.get().hasPermission(this.getUuid(), id); + } + + @Override + public boolean hasPermission(@Nonnull String id, boolean def) { + return PermissionsModule.get().hasPermission(this.getUuid(), id, def); + } + + public void addLocationChange( + @Nonnull Ref ref, double deltaX, double deltaY, double deltaZ, @Nonnull ComponentAccessor componentAccessor + ) { + CollisionResultComponent collisionResultComponent = componentAccessor.getComponent(ref, CollisionResultComponent.getComponentType()); + + assert collisionResultComponent != null; + + collisionResultComponent.getCollisionPositionOffset().add(deltaX, deltaY, deltaZ); + if (!collisionResultComponent.isPendingCollisionCheck()) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + collisionResultComponent.getCollisionStartPosition().assign(position); + collisionResultComponent.markPendingCollisionCheck(); + } + } + + public void configTriggerBlockProcessing(boolean triggers, boolean blockDamage, @Nonnull CollisionResultComponent collisionResultComponent) { + this.executeTriggers = triggers; + this.executeBlockDamage = blockDamage; + if (!triggers && !blockDamage) { + collisionResultComponent.getCollisionResult().disableTriggerBlocks(); + } else { + collisionResultComponent.getCollisionResult().enableTriggerBlocks(); + } + } + + public void resetVelocity(@Nonnull Velocity velocity) { + Arrays.fill(this.velocitySamples, 0.0); + this.velocitySampleIndex = 4; + this.velocitySampleCount = 0; + velocity.setZero(); + } + + public void processVelocitySample(double dt, @Nonnull Vector3d position, @Nonnull Velocity velocity) { + double x = position.x; + double y = position.y; + double z = position.z; + if (dt != 0.0) { + this.velocitySamples[this.velocitySampleIndex] = x; + this.velocitySamples[this.velocitySampleIndex + 1] = y; + this.velocitySamples[this.velocitySampleIndex + 2] = z; + this.velocitySamples[this.velocitySampleIndex + 3] = dt; + int index = this.velocitySampleIndex; + this.velocitySampleIndex += 4; + if (this.velocitySampleIndex >= 12) { + this.velocitySampleIndex = 4; + } + + if (this.velocitySampleCount < 2) { + this.velocitySampleCount++; + } + + if (this.velocitySampleCount < 2) { + velocity.setZero(); + } else { + for (int i = 0; i < 4; i++) { + this.velocitySamples[i] = 0.0; + } + + double[] weights = velocitySampleWeights[this.velocitySampleCount - 2]; + + for (int i = 0; i < this.velocitySampleCount - 1; i++) { + int previousIndex = index - 4; + if (previousIndex < 4) { + previousIndex = 8; + } + + double k = weights[i] / this.velocitySamples[index + 3]; + this.velocitySamples[0] = this.velocitySamples[0] + k * (this.velocitySamples[index] - this.velocitySamples[previousIndex]); + this.velocitySamples[1] = this.velocitySamples[1] + k * (this.velocitySamples[index + 1] - this.velocitySamples[previousIndex + 1]); + this.velocitySamples[2] = this.velocitySamples[2] + k * (this.velocitySamples[index + 2] - this.velocitySamples[previousIndex + 2]); + index = previousIndex; + } + + velocity.set(this.velocitySamples[0], this.velocitySamples[1], this.velocitySamples[2]); + } + } + } + + @Nonnull + public static Transform getRespawnPosition( + @Nonnull Ref ref, @Nonnull String worldName, @Nonnull ComponentAccessor componentAccessor + ) { + Player playerComponent = componentAccessor.getComponent(ref, getComponentType()); + + assert playerComponent != null; + + World world = componentAccessor.getExternalData().getWorld(); + PlayerConfigData playerConfigData = playerComponent.data; + PlayerRespawnPointData[] respawnPoints = playerConfigData.getPerWorldData(worldName).getRespawnPoints(); + if (respawnPoints != null && respawnPoints.length != 0) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d playerPosition = transformComponent.getPosition(); + List sortedRespawnPoints = Arrays.stream(respawnPoints).sorted((a, b) -> { + Vector3d posA = a.getRespawnPosition(); + Vector3d posB = b.getRespawnPosition(); + double distA = playerPosition.distanceSquaredTo(posA.x, playerPosition.y, posA.z); + double distB = playerPosition.distanceSquaredTo(posB.x, playerPosition.y, posB.z); + return Double.compare(distA, distB); + }).toList(); + BoundingBox playerBoundingBoxComponent = componentAccessor.getComponent(ref, BoundingBox.getComponentType()); + if (playerBoundingBoxComponent == null) { + return new Transform(sortedRespawnPoints.getFirst().getRespawnPosition()); + } else { + for (PlayerRespawnPointData respawnPoint : sortedRespawnPoints) { + Pair respawnPointResult = ensureNoCollisionAtRespawnPosition(respawnPoint, playerBoundingBoxComponent.getBoundingBox(), world); + if (respawnPointResult.left()) { + return new Transform(respawnPointResult.right(), Vector3f.ZERO); + } + + playerComponent.sendMessage(Message.translation("server.general.respawnPointObstructed").param("respawnPointName", respawnPoint.getName())); + } + + playerComponent.sendMessage(Message.translation("server.general.allRespawnPointsObstructed")); + Transform worldSpawnPoint = world.getWorldConfig().getSpawnProvider().getSpawnPoint(ref, componentAccessor); + worldSpawnPoint.setRotation(Vector3f.ZERO); + return worldSpawnPoint; + } + } else { + Transform worldSpawnPoint = world.getWorldConfig().getSpawnProvider().getSpawnPoint(ref, componentAccessor); + worldSpawnPoint.setRotation(Vector3f.ZERO); + return worldSpawnPoint; + } + } + + private static Pair ensureNoCollisionAtRespawnPosition(PlayerRespawnPointData playerRespawnPointData, Box playerHitbox, World world) { + Vector3d respawnPosition = new Vector3d(playerRespawnPointData.getRespawnPosition()); + if (CollisionModule.get().validatePosition(world, playerHitbox, respawnPosition, new CollisionResult()) != -1) { + return Pair.of(Boolean.TRUE, respawnPosition); + } else { + respawnPosition.x = playerRespawnPointData.getBlockPosition().x + 0.5F; + respawnPosition.y = playerRespawnPointData.getBlockPosition().y; + respawnPosition.z = playerRespawnPointData.getBlockPosition().z + 0.5F; + + for (int distance = 1; distance <= 2; distance++) { + for (int offset = -distance; offset <= distance; offset++) { + Vector3d newPosition = new Vector3d(respawnPosition.x + offset, respawnPosition.y, respawnPosition.z - distance); + if (CollisionModule.get().validatePosition(world, playerHitbox, newPosition, new CollisionResult()) != -1) { + return Pair.of(Boolean.TRUE, newPosition); + } + + newPosition = new Vector3d(respawnPosition.x + offset, respawnPosition.y, respawnPosition.z + distance); + if (CollisionModule.get().validatePosition(world, playerHitbox, newPosition, new CollisionResult()) != -1) { + return Pair.of(Boolean.TRUE, newPosition); + } + } + + for (int offset = -distance + 1; offset < distance; offset++) { + Vector3d newPositionx = new Vector3d(respawnPosition.x - distance, respawnPosition.y, respawnPosition.z + offset); + if (CollisionModule.get().validatePosition(world, playerHitbox, newPositionx, new CollisionResult()) != -1) { + return Pair.of(Boolean.TRUE, newPositionx); + } + + newPositionx = new Vector3d(respawnPosition.x + distance, respawnPosition.y, respawnPosition.z + offset); + if (CollisionModule.get().validatePosition(world, playerHitbox, newPositionx, new CollisionResult()) != -1) { + return Pair.of(Boolean.TRUE, newPositionx); + } + } + } + + return Pair.of(Boolean.FALSE, respawnPosition); + } + } + + public boolean hasSpawnProtection() { + return System.nanoTime() - this.lastSpawnTimeNanos <= RESPAWN_INVULNERABILITY_TIME_NANOS || this.waitingForClientReady.get() != null; + } + + public boolean isWaitingForClientReady() { + return this.waitingForClientReady.get() != null; + } + + @Override + public boolean isHiddenFromLivingEntity( + @Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor + ) { + UUIDComponent uuidComponent = componentAccessor.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + PlayerRef targetPlayerComponent = componentAccessor.getComponent(targetRef, PlayerRef.getComponentType()); + return targetPlayerComponent != null && targetPlayerComponent.getHiddenPlayersManager().isPlayerHidden(uuidComponent.getUuid()); + } + + public void setClientViewRadius(int clientViewRadius) { + this.clientViewRadius = clientViewRadius; + } + + public int getClientViewRadius() { + return this.clientViewRadius; + } + + public int getViewRadius() { + return Math.min(this.clientViewRadius, HytaleServer.get().getConfig().getMaxViewRadius()); + } + + @Override + public boolean canDecreaseItemStackDurability(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + Player playerComponent = componentAccessor.getComponent(ref, getComponentType()); + + assert playerComponent != null; + + return playerComponent.gameMode != GameMode.Creative; + } + + @Override + public boolean canApplyItemStackPenalties(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + Player playerComponent = componentAccessor.getComponent(ref, getComponentType()); + + assert playerComponent != null; + + return playerComponent.gameMode != GameMode.Creative; + } + + @Nullable + @Override + public ItemStackSlotTransaction updateItemStackDurability( + @Nonnull Ref ref, + @Nonnull ItemStack itemStack, + ItemContainer container, + int slotId, + double durabilityChange, + @Nonnull ComponentAccessor componentAccessor + ) { + ItemStackSlotTransaction transaction = super.updateItemStackDurability(ref, itemStack, container, slotId, durabilityChange, componentAccessor); + if (transaction != null && transaction.getSlotAfter().isBroken() && !itemStack.isBroken()) { + Message itemNameMessage = Message.translation(itemStack.getItem().getTranslationKey()); + this.sendMessage(Message.translation("server.general.repair.itemBroken").param("itemName", itemNameMessage).color("#ff5555")); + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + int soundEventIndex = TempAssetIdUtil.getSoundEventIndex("SFX_Item_Break"); + SoundUtil.playSoundEvent2dToPlayer(playerRefComponent, soundEventIndex, SoundCategory.SFX); + } + + return transaction; + } + + @Nonnull + @Override + public MetricResults toMetricResults() { + return METRICS_REGISTRY.toMetricResults(this); + } + + public void setLastSpawnTimeNanos(long lastSpawnTimeNanos) { + this.lastSpawnTimeNanos = lastSpawnTimeNanos; + } + + public long getSinceLastSpawnNanos() { + return System.nanoTime() - this.lastSpawnTimeNanos; + } + + @Deprecated(forRemoval = true) + public PlayerRef getPlayerRef() { + return this.playerRef; + } + + public int getMountEntityId() { + return this.mountEntityId; + } + + public void setMountEntityId(int mountEntityId) { + this.mountEntityId = mountEntityId; + } + + public GameMode getGameMode() { + return this.gameMode; + } + + public static void setGameMode(@Nonnull Ref playerRef, @Nonnull GameMode gameMode, @Nonnull ComponentAccessor componentAccessor) { + MovementManager movementManagerComponent = componentAccessor.getComponent(playerRef, MovementManager.getComponentType()); + + assert movementManagerComponent != null; + + Player playerComponent = componentAccessor.getComponent(playerRef, getComponentType()); + + assert playerComponent != null; + + GameMode oldGameMode = playerComponent.gameMode; + if (oldGameMode != gameMode) { + ChangeGameModeEvent event = new ChangeGameModeEvent(gameMode); + componentAccessor.invoke(playerRef, event); + if (event.isCancelled()) { + return; + } + + setGameModeInternal(playerRef, event.getGameMode(), movementManagerComponent, componentAccessor); + runOnSwitchToGameMode(playerRef, gameMode); + } + } + + public static void initGameMode(@Nonnull Ref playerRef, @Nonnull ComponentAccessor componentAccessor) { + MovementManager movementManagerComponent = componentAccessor.getComponent(playerRef, MovementManager.getComponentType()); + + assert movementManagerComponent != null; + + Player playerComponent = componentAccessor.getComponent(playerRef, getComponentType()); + + assert playerComponent != null; + + GameMode gameMode = playerComponent.gameMode; + if (gameMode == null) { + World world = componentAccessor.getExternalData().getWorld(); + gameMode = world.getWorldConfig().getGameMode(); + LOGGER.at(Level.INFO).log("Assigning default gamemode %s to player!", gameMode); + } + + setGameModeInternal(playerRef, gameMode, movementManagerComponent, componentAccessor); + } + + private static void setGameModeInternal( + @Nonnull Ref playerRef, + @Nonnull GameMode gameMode, + @Nonnull MovementManager movementManager, + @Nonnull ComponentAccessor componentAccessor + ) { + Player playerComponent = componentAccessor.getComponent(playerRef, getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = componentAccessor.getComponent(playerRef, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + GameMode oldGameMode = playerComponent.gameMode; + playerComponent.gameMode = gameMode; + playerRefComponent.getPacketHandler().writeNoCache(new SetGameMode(gameMode)); + if (movementManager.getDefaultSettings() != null) { + movementManager.getDefaultSettings().canFly = gameMode == GameMode.Creative; + movementManager.getSettings().canFly = gameMode == GameMode.Creative; + movementManager.update(playerRefComponent.getPacketHandler()); + } + + PermissionsModule permissionsModule = PermissionsModule.get(); + if (oldGameMode != null) { + GameModeType oldGameModeType = GameModeType.fromGameMode(oldGameMode); + + for (String group : oldGameModeType.getPermissionGroups()) { + permissionsModule.removeUserFromGroup(playerRefComponent.getUuid(), group); + } + } + + GameModeType gameModeType = GameModeType.fromGameMode(gameMode); + + for (String group : gameModeType.getPermissionGroups()) { + permissionsModule.addUserToGroup(playerRefComponent.getUuid(), group); + } + + if (gameMode == GameMode.Creative) { + componentAccessor.putComponent(playerRef, Invulnerable.getComponentType(), Invulnerable.INSTANCE); + } else { + componentAccessor.tryRemoveComponent(playerRef, Invulnerable.getComponentType()); + } + + if (gameMode == GameMode.Creative) { + PlayerSettings settings = componentAccessor.getComponent(playerRef, PlayerSettings.getComponentType()); + if (settings == null) { + settings = PlayerSettings.defaults(); + } + + if (settings.creativeSettings().respondToHit()) { + componentAccessor.putComponent(playerRef, RespondToHit.getComponentType(), RespondToHit.INSTANCE); + } else { + componentAccessor.tryRemoveComponent(playerRef, RespondToHit.getComponentType()); + } + } else { + componentAccessor.tryRemoveComponent(playerRef, RespondToHit.getComponentType()); + } + + World world = componentAccessor.getExternalData().getWorld(); + playerComponent.worldMapTracker.sendSettings(world); + } + + private static void runOnSwitchToGameMode(@Nonnull Ref ref, @Nonnull GameMode gameMode) { + Store store = ref.getStore(); + GameModeType gameModeType = GameModeType.fromGameMode(gameMode); + InteractionManager interactionManagerComponent = store.getComponent(ref, InteractionModule.get().getInteractionManagerComponent()); + if (interactionManagerComponent != null) { + String interactions = gameModeType.getInteractionsOnEnter(); + if (interactions != null) { + InteractionContext context = InteractionContext.forInteraction(interactionManagerComponent, ref, InteractionType.GameModeSwap, store); + RootInteraction rootInteraction = RootInteraction.getRootInteractionOrUnknown(interactions); + if (rootInteraction != null) { + InteractionChain chain = interactionManagerComponent.initChain(InteractionType.EntityStatEffect, context, rootInteraction, true); + interactionManagerComponent.queueExecuteChain(chain); + } + } + } + } + + @Override + public int hashCode() { + int result = super.hashCode(); + return 31 * result + (this.getUuid() != null ? this.getUuid().hashCode() : 0); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o == null || this.getClass() != o.getClass()) { + return false; + } else if (!super.equals(o)) { + return false; + } else { + Player player = (Player)o; + return this.getUuid() != null ? this.getUuid().equals(player.getUuid()) : player.getUuid() == null; + } + } + + @Nonnull + @Override + public String toString() { + return "Player{uuid=" + this.getUuid() + ", clientViewRadius='" + this.clientViewRadius + "', " + super.toString() + "}"; + } + + @Override + public String getDisplayName() { + return this.playerRef.getUsername(); + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/ProjectileComponent.java b/src/com/hypixel/hytale/server/core/entity/entities/ProjectileComponent.java new file mode 100644 index 0000000..4829d90 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/ProjectileComponent.java @@ -0,0 +1,358 @@ +package com.hypixel.hytale.server.core.entity.entities; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.server.core.asset.type.particle.config.WorldParticle; +import com.hypixel.hytale.server.core.asset.type.projectile.config.Projectile; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.ExplosionConfig; +import com.hypixel.hytale.server.core.entity.ExplosionUtils; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.modules.entity.DespawnComponent; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems; +import com.hypixel.hytale.server.core.modules.physics.SimplePhysicsProvider; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.ParticleUtil; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ProjectileComponent implements Component { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(ProjectileComponent.class, ProjectileComponent::new) + .append( + new KeyedCodec<>("ProjectileType", Codec.STRING), + (projectileEntity, projectileName) -> projectileEntity.projectileAssetName = projectileName, + projectileEntity -> projectileEntity.projectileAssetName + ) + .add() + .append( + new KeyedCodec<>("BrokenDamageModifier", Codec.FLOAT), + (projectileEntity, brokenDamageModifier) -> projectileEntity.brokenDamageModifier = brokenDamageModifier, + projectileEntity -> projectileEntity.brokenDamageModifier + ) + .add() + .append( + new KeyedCodec<>("DeadTimer", Codec.DOUBLE), + (projectileEntity, deadTimer) -> projectileEntity.deadTimer = deadTimer, + projectileEntity -> projectileEntity.deadTimer + ) + .add() + .append( + new KeyedCodec<>("CreatorUUID", Codec.UUID_STRING), + (projectileEntity, creatorUuid) -> projectileEntity.creatorUuid = creatorUuid, + projectileEntity -> projectileEntity.creatorUuid + ) + .add() + .append( + new KeyedCodec<>("HaveHit", Codec.BOOLEAN), + (projectileEntity, haveHit) -> projectileEntity.haveHit = haveHit, + projectileEntity -> projectileEntity.haveHit + ) + .add() + .append( + new KeyedCodec<>("LastBouncePosition", Vector3d.CODEC), + (projectileEntity, lastBouncePosition) -> projectileEntity.lastBouncePosition = lastBouncePosition, + projectileEntity -> projectileEntity.lastBouncePosition + ) + .add() + .append( + new KeyedCodec<>("SppImpacted", Codec.BOOLEAN), + (projectileEntity, b) -> projectileEntity.simplePhysicsProvider.setImpacted(b), + projectileEntity -> projectileEntity.simplePhysicsProvider.isImpacted() + ) + .add() + .append( + new KeyedCodec<>("SppResting", Codec.BOOLEAN), + (projectileEntity, b) -> projectileEntity.simplePhysicsProvider.setResting(b), + projectileEntity -> projectileEntity.simplePhysicsProvider.isResting() + ) + .add() + .append( + new KeyedCodec<>("SppVelocity", Vector3d.CODEC), + (projectileEntity, v) -> projectileEntity.simplePhysicsProvider.setVelocity(v), + projectileEntity -> projectileEntity.simplePhysicsProvider.getVelocity() + ) + .add() + .build(); + private static final double DEFAULT_DESPAWN_SECONDS = 60.0; + private transient SimplePhysicsProvider simplePhysicsProvider = new SimplePhysicsProvider(this::bounceHandler, this::impactHandler); + private transient String appearance = "Boy"; + @Nullable + private transient Projectile projectile; + private String projectileAssetName; + private float brokenDamageModifier = 1.0F; + private double deadTimer = -1.0; + private UUID creatorUuid; + private boolean haveHit; + private Vector3d lastBouncePosition; + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getProjectileComponentType(); + } + + private ProjectileComponent() { + } + + public ProjectileComponent(@Nonnull String projectileAssetName) { + this.projectileAssetName = projectileAssetName; + } + + @Nonnull + public static Holder assembleDefaultProjectile( + @Nonnull TimeResource time, @Nonnull String projectileAssetName, @Nonnull Vector3d position, @Nonnull Vector3f rotation + ) { + if (projectileAssetName.isEmpty()) { + throw new IllegalArgumentException("No projectile config typeName provided"); + } else { + Holder holder = EntityStore.REGISTRY.newHolder(); + ProjectileComponent projectileComponent = new ProjectileComponent(projectileAssetName); + holder.putComponent(getComponentType(), projectileComponent); + holder.putComponent(DespawnComponent.getComponentType(), DespawnComponent.despawnInMilliseconds(time, 60000L)); + holder.putComponent(TransformComponent.getComponentType(), new TransformComponent(position.clone(), rotation)); + holder.ensureComponent(Velocity.getComponentType()); + holder.ensureComponent(UUIDComponent.getComponentType()); + return holder; + } + } + + public boolean initialize() { + this.projectile = Projectile.getAssetMap().getAsset(this.projectileAssetName); + if (this.projectile == null) { + return false; + } else { + String appearance = this.projectile.getAppearance(); + if (appearance != null && !appearance.isEmpty()) { + this.appearance = appearance; + } + + return true; + } + } + + public void initializePhysics(@Nonnull BoundingBox boundingBox) { + this.simplePhysicsProvider.setProvideCharacterCollisions(true); + this.simplePhysicsProvider.initialize(this.projectile, boundingBox); + } + + public void onProjectileBounce(@Nonnull Vector3d position, @Nonnull ComponentAccessor componentAccessor) { + WorldParticle bounceParticles = this.projectile.getBounceParticles(); + if (bounceParticles != null) { + SpatialResource, EntityStore> playerSpatialResource = componentAccessor.getResource(EntityModule.get().getPlayerSpatialResourceType()); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(position, 75.0, results); + ParticleUtil.spawnParticleEffect(bounceParticles, position, results, componentAccessor); + } + + SoundUtil.playSoundEvent3d(this.projectile.getBounceSoundEventIndex(), SoundCategory.SFX, position, componentAccessor); + } + + private void onProjectileHitEvent( + @Nonnull Ref ref, @Nonnull Vector3d position, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor + ) { + WorldParticle hitParticles = this.projectile.getHitParticles(); + if (hitParticles != null) { + SpatialResource, EntityStore> playerSpatialResource = componentAccessor.getResource(EntityModule.get().getPlayerSpatialResourceType()); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(position, 75.0, results); + ParticleUtil.spawnParticleEffect(hitParticles, position, results, componentAccessor); + } + + SoundUtil.playSoundEvent3d(this.projectile.getHitSoundEventIndex(), SoundCategory.SFX, position, componentAccessor); + Entity targetEntity = EntityUtils.getEntity(targetRef, componentAccessor); + if (targetEntity instanceof LivingEntity) { + Ref shooterRef = componentAccessor.getExternalData().getRefFromUUID(this.creatorUuid); + DamageSystems.executeDamage( + targetRef, + componentAccessor, + new Damage( + new Damage.ProjectileSource(shooterRef != null ? shooterRef : ref, ref), + DamageCause.PROJECTILE, + this.projectile.getDamage() * this.brokenDamageModifier + ) + ); + this.haveHit = true; + } + + this.deadTimer = this.projectile.getDeadTime(); + } + + public boolean consumeDeadTimer(float dt) { + if (this.deadTimer < 0.0) { + return false; + } else { + this.deadTimer -= dt; + return this.deadTimer <= 0.0; + } + } + + protected void bounceHandler(@Nonnull Vector3d position, @Nonnull ComponentAccessor componentAccessor) { + if (this.lastBouncePosition == null) { + this.lastBouncePosition = new Vector3d(position); + } else { + if (!(this.lastBouncePosition.distanceSquaredTo(position) >= 0.5)) { + return; + } + + this.lastBouncePosition.assign(position); + } + + this.onProjectileBounce(position, componentAccessor); + } + + protected void impactHandler( + @Nonnull Ref ref, + @Nonnull Vector3d position, + @Nullable Ref targetRef, + @Nonnull ComponentAccessor componentAccessor + ) { + if (targetRef != null) { + this.onProjectileHitEvent(ref, position, targetRef, componentAccessor); + } else { + this.onProjectileMissEvent(position, componentAccessor); + } + } + + private void onProjectileMissEvent(@Nonnull Vector3d position, @Nonnull ComponentAccessor componentAccessor) { + WorldParticle missParticles = this.projectile.getMissParticles(); + if (missParticles != null) { + SpatialResource, EntityStore> playerSpatialResource = componentAccessor.getResource(EntityModule.get().getPlayerSpatialResourceType()); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(position, 75.0, results); + ParticleUtil.spawnParticleEffect(missParticles, position, results, componentAccessor); + } + + SoundUtil.playSoundEvent3d(this.projectile.getMissSoundEventIndex(), SoundCategory.SFX, position, componentAccessor); + this.deadTimer = this.projectile.getDeadTimeMiss(); + } + + public void onProjectileDeath(@Nonnull Ref ref, @Nonnull Vector3d position, @Nonnull CommandBuffer commandBuffer) { + EntityStore entityStore = commandBuffer.getExternalData(); + World world = entityStore.getWorld(); + ExplosionConfig explosionConfig = this.projectile.getExplosionConfig(); + if (explosionConfig != null) { + Store chunkStore = world.getChunkStore().getStore(); + Ref creatorRef = entityStore.getRefFromUUID(this.creatorUuid); + Damage.ProjectileSource damageSource = new Damage.ProjectileSource(creatorRef != null ? creatorRef : ref, ref); + ExplosionUtils.performExplosion(damageSource, position, explosionConfig, ref, commandBuffer, chunkStore); + } + + if (!this.haveHit || this.projectile.isDeathEffectsOnHit()) { + WorldParticle deathParticles = this.projectile.getDeathParticles(); + if (deathParticles != null) { + SpatialResource, EntityStore> playerSpatialResource = commandBuffer.getResource(EntityModule.get().getPlayerSpatialResourceType()); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(position, 75.0, results); + ParticleUtil.spawnParticleEffect(deathParticles, position, results, commandBuffer); + } + + SoundUtil.playSoundEvent3d(this.projectile.getDeathSoundEventIndex(), SoundCategory.SFX, position, commandBuffer); + } + } + + public void shoot(@Nonnull Holder holder, @Nonnull UUID creatorUuid, double x, double y, double z, float yaw, float pitch) { + this.creatorUuid = creatorUuid; + this.simplePhysicsProvider.setCreatorId(creatorUuid); + Vector3d direction = new Vector3d(); + computeStartOffset( + this.projectile.isPitchAdjustShot(), + this.projectile.getVerticalCenterShot(), + this.projectile.getHorizontalCenterShot(), + this.projectile.getDepthShot(), + yaw, + pitch, + direction + ); + x += direction.x; + y += direction.y; + z += direction.z; + holder.ensureAndGetComponent(TransformComponent.getComponentType()).setPosition(new Vector3d(x, y, z)); + PhysicsMath.vectorFromAngles(yaw, pitch, direction); + direction.setLength(this.projectile.getMuzzleVelocity()); + this.simplePhysicsProvider.setVelocity(direction); + } + + public static void computeStartOffset( + boolean pitchAdjust, double verticalCenterShot, double horizontalCenterShot, double depthShot, float yaw, float pitch, @Nonnull Vector3d offset + ) { + offset.assign(0.0, 0.0, 0.0); + if (depthShot != 0.0) { + PhysicsMath.vectorFromAngles(yaw, pitchAdjust ? pitch : 0.0F, offset); + offset.setLength(depthShot); + } else { + offset.assign(0.0, 0.0, 0.0); + } + + offset.add(horizontalCenterShot * -PhysicsMath.headingZ(yaw), -verticalCenterShot, horizontalCenterShot * PhysicsMath.headingX(yaw)); + } + + public boolean isOnGround() { + return this.simplePhysicsProvider.isOnGround(); + } + + @Nullable + public Projectile getProjectile() { + return this.projectile; + } + + public String getAppearance() { + return this.appearance; + } + + public String getProjectileAssetName() { + return this.projectileAssetName; + } + + public SimplePhysicsProvider getSimplePhysicsProvider() { + return this.simplePhysicsProvider; + } + + public void applyBrokenPenalty(float penalty) { + this.brokenDamageModifier = 1.0F - penalty; + } + + public ProjectileComponent(@Nonnull ProjectileComponent other) { + this.simplePhysicsProvider = other.simplePhysicsProvider; + this.projectileAssetName = other.projectileAssetName; + this.projectile = other.projectile; + this.appearance = other.appearance; + this.deadTimer = other.deadTimer; + this.creatorUuid = other.creatorUuid; + this.haveHit = other.haveHit; + this.brokenDamageModifier = other.brokenDamageModifier; + this.lastBouncePosition = other.lastBouncePosition; + } + + @Nonnull + @Override + public Component clone() { + return new ProjectileComponent(this); + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/CameraManager.java b/src/com/hypixel/hytale/server/core/entity/entities/player/CameraManager.java new file mode 100644 index 0000000..27a1980 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/CameraManager.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.server.core.entity.entities.player; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.ClientCameraView; +import com.hypixel.hytale.protocol.MouseButtonState; +import com.hypixel.hytale.protocol.MouseButtonType; +import com.hypixel.hytale.protocol.packets.camera.SetServerCamera; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.EnumMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class CameraManager implements Component { + private final Map mouseStates = new EnumMap<>(MouseButtonType.class); + private final Map mousePressedPosition = new EnumMap<>(MouseButtonType.class); + private final Map mouseReleasedPosition = new EnumMap<>(MouseButtonType.class); + private Vector2d lastScreenPoint = Vector2d.ZERO; + private Vector3i lastTargetBlock; + + public static ComponentType getComponentType() { + return EntityModule.get().getCameraManagerComponentType(); + } + + public CameraManager() { + } + + public CameraManager(@Nonnull CameraManager other) { + this(); + this.lastScreenPoint = other.lastScreenPoint; + this.lastTargetBlock = other.lastTargetBlock; + } + + public void resetCamera(@Nonnull PlayerRef ref) { + ref.getPacketHandler().writeNoCache(new SetServerCamera(ClientCameraView.Custom, false, null)); + this.mouseStates.clear(); + } + + public void handleMouseButtonState(MouseButtonType mouseButtonType, MouseButtonState state, Vector3i targetBlock) { + this.mouseStates.put(mouseButtonType, state); + if (state == MouseButtonState.Pressed) { + this.mousePressedPosition.put(mouseButtonType, targetBlock); + } + + if (state == MouseButtonState.Released) { + this.mouseReleasedPosition.put(mouseButtonType, targetBlock); + } + } + + public MouseButtonState getMouseButtonState(MouseButtonType mouseButtonType) { + return this.mouseStates.getOrDefault(mouseButtonType, MouseButtonState.Released); + } + + public Vector3i getLastMouseButtonPressedPosition(MouseButtonType mouseButtonType) { + return this.mousePressedPosition.get(mouseButtonType); + } + + public Vector3i getLastMouseButtonReleasedPosition(MouseButtonType mouseButtonType) { + return this.mouseReleasedPosition.get(mouseButtonType); + } + + public void setLastScreenPoint(Vector2d lastScreenPoint) { + this.lastScreenPoint = lastScreenPoint; + } + + public Vector2d getLastScreenPoint() { + return this.lastScreenPoint; + } + + public void setLastBlockPosition(Vector3i targetBlock) { + this.lastTargetBlock = targetBlock; + } + + public Vector3i getLastTargetBlock() { + return this.lastTargetBlock; + } + + @Nonnull + @Override + public Component clone() { + return new CameraManager(this); + } + + @Nonnull + @Override + public String toString() { + return "CameraManager{mouseStates=" + + this.mouseStates + + ", mousePressedPosition=" + + this.mousePressedPosition + + ", mouseReleasedPosition=" + + this.mouseReleasedPosition + + ", lastScreenPoint=" + + this.lastScreenPoint + + ", lastTargetBlock=" + + this.lastTargetBlock + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/HiddenPlayersManager.java b/src/com/hypixel/hytale/server/core/entity/entities/player/HiddenPlayersManager.java new file mode 100644 index 0000000..f1d3383 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/HiddenPlayersManager.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.core.entity.entities.player; + +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; + +public class HiddenPlayersManager { + @Nonnull + private final Set hiddenPlayers = ConcurrentHashMap.newKeySet(); + + public HiddenPlayersManager() { + } + + public void hidePlayer(@Nonnull UUID uuid) { + this.hiddenPlayers.add(uuid); + } + + public void showPlayer(@Nonnull UUID uuid) { + this.hiddenPlayers.remove(uuid); + } + + public boolean isPlayerHidden(@Nonnull UUID uuid) { + return this.hiddenPlayers.contains(uuid); + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/HotbarManager.java b/src/com/hypixel/hytale/server/core/entity/entities/player/HotbarManager.java new file mode 100644 index 0000000..e34788a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/HotbarManager.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.server.core.entity.entities.player; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class HotbarManager { + public static final int HOTBARS_MAX = 10; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(HotbarManager.class, HotbarManager::new) + .append( + new KeyedCodec<>("SavedHotbars", new ArrayCodec<>(ItemContainer.CODEC, ItemContainer[]::new)), + (player, savedHotbars) -> player.savedHotbars = savedHotbars, + player -> player.savedHotbars + ) + .documentation("An array of item containers that represent the saved hotbars.") + .add() + .append( + new KeyedCodec<>("CurrentHotbar", Codec.INTEGER), (player, currentHotbar) -> player.currentHotbar = currentHotbar, player -> player.currentHotbar + ) + .documentation("The current hotbar that the player has active.") + .add() + .build(); + private static final Message MESSAGE_GENERAL_HOTBAR_INVALID_SLOT = Message.translation("server.general.hotbar.invalidSlot"); + private static final Message MESSAGE_GENERAL_HOTBAR_INVALID_GAME_MODE = Message.translation("server.general.hotbar.invalidGameMode"); + @Nonnull + private ItemContainer[] savedHotbars = new ItemContainer[10]; + private int currentHotbar = 0; + private boolean currentlyLoadingHotbar; + + public HotbarManager() { + } + + public void saveHotbar(@Nonnull Ref playerRef, short hotbarIndex, @Nonnull ComponentAccessor componentAccessor) { + PlayerRef playerRefComponent = componentAccessor.getComponent(playerRef, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + if (hotbarIndex >= 0 && hotbarIndex <= 9) { + Player playerComponent = componentAccessor.getComponent(playerRef, Player.getComponentType()); + + assert playerComponent != null; + + if (!playerComponent.getGameMode().equals(GameMode.Creative)) { + playerRefComponent.sendMessage(MESSAGE_GENERAL_HOTBAR_INVALID_GAME_MODE); + } else { + this.currentlyLoadingHotbar = true; + this.savedHotbars[hotbarIndex] = playerComponent.getInventory().getHotbar().clone(); + this.currentHotbar = hotbarIndex; + this.currentlyLoadingHotbar = false; + } + } else { + playerRefComponent.sendMessage(MESSAGE_GENERAL_HOTBAR_INVALID_SLOT); + } + } + + public void loadHotbar(@Nonnull Ref playerRef, short hotbarIndex, @Nonnull ComponentAccessor componentAccessor) { + PlayerRef playerRefComponent = componentAccessor.getComponent(playerRef, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + if (hotbarIndex >= 0 && hotbarIndex <= 9) { + Player playerComponent = componentAccessor.getComponent(playerRef, Player.getComponentType()); + + assert playerComponent != null; + + if (!playerComponent.getGameMode().equals(GameMode.Creative)) { + playerRefComponent.sendMessage(MESSAGE_GENERAL_HOTBAR_INVALID_GAME_MODE); + } else { + this.currentlyLoadingHotbar = true; + ItemContainer hotbar = playerComponent.getInventory().getHotbar(); + hotbar.removeAllItemStacks(); + if (this.savedHotbars[hotbarIndex] != null) { + ItemContainer savedHotbar = this.savedHotbars[hotbarIndex].clone(); + savedHotbar.forEach(hotbar::setItemStackForSlot); + } + + this.currentHotbar = hotbarIndex; + this.currentlyLoadingHotbar = false; + playerRefComponent.sendMessage(Message.translation("server.general.hotbar.loaded").param("id", hotbarIndex + 1)); + } + } else { + playerRefComponent.sendMessage(MESSAGE_GENERAL_HOTBAR_INVALID_SLOT); + } + } + + public int getCurrentHotbarIndex() { + return this.currentHotbar; + } + + public boolean getIsCurrentlyLoadingHotbar() { + return this.currentlyLoadingHotbar; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/data/PlayerConfigData.java b/src/com/hypixel/hytale/server/core/entity/entities/player/data/PlayerConfigData.java new file mode 100644 index 0000000..6221039 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/data/PlayerConfigData.java @@ -0,0 +1,246 @@ +package com.hypixel.hytale.server.core.entity.entities.player.data; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.codecs.map.Object2IntMapCodec; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockMigration; +import com.hypixel.hytale.server.core.universe.Universe; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntMaps; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public final class PlayerConfigData { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(PlayerConfigData.class, PlayerConfigData::new) + .addField( + new KeyedCodec<>("BlockIdVersion", Codec.INTEGER), + (playerConfigData, s) -> playerConfigData.blockIdVersion = s, + playerConfigData -> playerConfigData.blockIdVersion + ) + .addField(new KeyedCodec<>("World", Codec.STRING), (playerConfigData, s) -> playerConfigData.world = s, playerConfigData -> playerConfigData.world) + .addField(new KeyedCodec<>("Preset", Codec.STRING), (playerConfigData, s) -> playerConfigData.preset = s, playerConfigData -> playerConfigData.preset) + .addField(new KeyedCodec<>("KnownRecipes", new ArrayCodec<>(Codec.STRING, String[]::new)), (playerConfigData, knownRecipes) -> { + playerConfigData.knownRecipes = Set.of(knownRecipes); + playerConfigData.unmodifiableKnownRecipes = Collections.unmodifiableSet(playerConfigData.knownRecipes); + }, playerConfigData -> playerConfigData.knownRecipes.toArray(String[]::new)) + .addField(new KeyedCodec<>("PerWorldData", new MapCodec<>(PlayerWorldData.CODEC, ConcurrentHashMap::new, false)), (playerConfigData, perWorldData) -> { + playerConfigData.perWorldData = perWorldData; + playerConfigData.unmodifiablePerWorldData = Collections.unmodifiableMap(perWorldData); + }, playerConfigData -> playerConfigData.perWorldData) + .addField(new KeyedCodec<>("DiscoveredZones", Codec.STRING_ARRAY), (playerConfigData, discoveredZones) -> { + playerConfigData.discoveredZones = Set.of(discoveredZones); + playerConfigData.unmodifiableDiscoveredZones = Collections.unmodifiableSet(playerConfigData.discoveredZones); + }, playerConfigData -> playerConfigData.discoveredZones.toArray(String[]::new)) + .addField(new KeyedCodec<>("DiscoveredInstances", new ArrayCodec<>(Codec.UUID_BINARY, UUID[]::new)), (playerConfigData, discoveredInstances) -> { + playerConfigData.discoveredInstances = Set.of(discoveredInstances); + playerConfigData.unmodifiableDiscoveredInstances = Collections.unmodifiableSet(playerConfigData.discoveredInstances); + }, playerConfigData -> playerConfigData.discoveredInstances.toArray(UUID[]::new)) + .addField( + new KeyedCodec<>("ReputationData", new Object2IntMapCodec<>(Codec.STRING, Object2IntOpenHashMap::new, false)), (playerConfigData, reputationData) -> { + playerConfigData.reputationData = reputationData; + playerConfigData.unmodifiableReputationData = Object2IntMaps.unmodifiable(reputationData); + }, playerConfigData -> playerConfigData.reputationData + ) + .addField( + new KeyedCodec<>("ActiveObjectiveUUIDs", new ArrayCodec<>(Codec.UUID_BINARY, UUID[]::new)), + (playerConfigData, objectives) -> Collections.addAll(playerConfigData.activeObjectiveUUIDs, objectives), + playerConfigData -> playerConfigData.activeObjectiveUUIDs.toArray(UUID[]::new) + ) + .afterDecode(data -> { + for (PlayerWorldData worldData : data.perWorldData.values()) { + worldData.setPlayerConfigData(data); + } + + int v = data.getBlockIdVersion(); + Map blockMigrationMap = BlockMigration.getAssetMap().getAssetMap(); + BlockMigration migration = blockMigrationMap.get(v); + + Function blockMigration; + for (blockMigration = null; migration != null; migration = blockMigrationMap.get(++v)) { + if (blockMigration == null) { + blockMigration = migration::getMigration; + } else { + blockMigration = blockMigration.andThen(migration::getMigration); + } + } + + data.setBlockIdVersion(v); + if (blockMigration != null) { + Set oldKnownRecipes = data.getKnownRecipes(); + if (!oldKnownRecipes.isEmpty()) { + Set knownRecipes = new HashSet<>(); + + for (String blockTypeKey : oldKnownRecipes) { + knownRecipes.add(blockMigration.apply(blockTypeKey)); + } + + data.setKnownRecipes(knownRecipes); + } + } + }) + .build(); + @Nonnull + private final transient AtomicBoolean hasChanged = new AtomicBoolean(); + private int blockIdVersion = 1; + private String world; + private String preset; + @Nonnull + private Set knownRecipes = new HashSet<>(); + @Nonnull + private Set unmodifiableKnownRecipes = Collections.unmodifiableSet(this.knownRecipes); + private Map perWorldData = new ConcurrentHashMap<>(); + @Nonnull + private Map unmodifiablePerWorldData = Collections.unmodifiableMap(this.perWorldData); + @Nonnull + private Set discoveredZones = new HashSet<>(); + @Nonnull + private Set unmodifiableDiscoveredZones = Collections.unmodifiableSet(this.discoveredZones); + @Nonnull + private Set discoveredInstances = new HashSet<>(); + @Nonnull + private Set unmodifiableDiscoveredInstances = Collections.unmodifiableSet(this.discoveredInstances); + private Object2IntMap reputationData = new Object2IntOpenHashMap<>(); + @Nonnull + private Object2IntMap unmodifiableReputationData = Object2IntMaps.unmodifiable(this.reputationData); + @Nonnull + private Set activeObjectiveUUIDs = ConcurrentHashMap.newKeySet(); + @Nonnull + private Set unmodifiableActiveObjectiveUUIDs = Collections.unmodifiableSet(this.activeObjectiveUUIDs); + public final Vector3d lastSavedPosition = new Vector3d(); + public final Vector3f lastSavedRotation = new Vector3f(); + + public PlayerConfigData() { + } + + public int getBlockIdVersion() { + return this.blockIdVersion; + } + + public void setBlockIdVersion(int blockIdVersion) { + this.blockIdVersion = blockIdVersion; + } + + public String getWorld() { + return this.world; + } + + public void setWorld(@Nonnull String world) { + this.world = world; + this.markChanged(); + } + + public String getPreset() { + return this.preset; + } + + public void setPreset(@Nonnull String preset) { + this.preset = preset; + this.markChanged(); + } + + @Nonnull + public Set getKnownRecipes() { + return this.unmodifiableKnownRecipes; + } + + public void setKnownRecipes(@Nonnull Set knownRecipes) { + this.knownRecipes = knownRecipes; + this.unmodifiableKnownRecipes = Collections.unmodifiableSet(knownRecipes); + this.markChanged(); + } + + @Nonnull + public Map getPerWorldData() { + return this.unmodifiablePerWorldData; + } + + @Nonnull + public PlayerWorldData getPerWorldData(@Nonnull String worldName) { + return this.perWorldData.computeIfAbsent(worldName, s -> new PlayerWorldData(this)); + } + + public void setPerWorldData(@Nonnull Map perWorldData) { + this.perWorldData = perWorldData; + this.unmodifiablePerWorldData = Collections.unmodifiableMap(perWorldData); + this.markChanged(); + } + + @Nonnull + public Set getDiscoveredZones() { + return this.unmodifiableDiscoveredZones; + } + + public void setDiscoveredZones(@Nonnull Set discoveredZones) { + this.discoveredZones = discoveredZones; + this.unmodifiableDiscoveredZones = Collections.unmodifiableSet(discoveredZones); + this.markChanged(); + } + + @Nonnull + public Set getDiscoveredInstances() { + return this.unmodifiableDiscoveredInstances; + } + + public void setDiscoveredInstances(@Nonnull Set discoveredInstances) { + this.discoveredInstances = discoveredInstances; + this.unmodifiableDiscoveredInstances = Collections.unmodifiableSet(discoveredInstances); + this.markChanged(); + } + + @Nonnull + public Object2IntMap getReputationData() { + return this.unmodifiableReputationData; + } + + public void setReputationData(@Nonnull Object2IntMap reputationData) { + this.reputationData = reputationData; + this.unmodifiableReputationData = Object2IntMaps.unmodifiable(reputationData); + this.markChanged(); + } + + @Nonnull + public Set getActiveObjectiveUUIDs() { + return this.unmodifiableActiveObjectiveUUIDs; + } + + public void setActiveObjectiveUUIDs(@Nonnull Set activeObjectiveUUIDs) { + this.activeObjectiveUUIDs.clear(); + this.activeObjectiveUUIDs.addAll(activeObjectiveUUIDs); + this.markChanged(); + } + + public void markChanged() { + this.hasChanged.set(true); + } + + public boolean consumeHasChanged() { + return this.hasChanged.getAndSet(false); + } + + public void cleanup(@Nonnull Universe universe) { + Set keySet = this.perWorldData.keySet(); + Iterator iterator = keySet.iterator(); + + while (iterator.hasNext()) { + String worldName = iterator.next(); + if (worldName.startsWith("instance-") && universe.getWorld(worldName) == null) { + iterator.remove(); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/data/PlayerDeathPositionData.java b/src/com/hypixel/hytale/server/core/entity/entities/player/data/PlayerDeathPositionData.java new file mode 100644 index 0000000..1d11a4d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/data/PlayerDeathPositionData.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.core.entity.entities.player.data; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.math.vector.Transform; +import javax.annotation.Nonnull; + +public final class PlayerDeathPositionData { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(PlayerDeathPositionData.class, PlayerDeathPositionData::new) + .append(new KeyedCodec<>("MarkerId", Codec.STRING), (data, value) -> data.markerId = value, data -> data.markerId) + .documentation("The unique ID of the associated map marker.") + .add() + .append(new KeyedCodec<>("Transform", Transform.CODEC), (data, value) -> data.transform = value, data -> data.transform) + .documentation("The transform of this death position.") + .add() + .append(new KeyedCodec<>("Day", Codec.INTEGER), (data, value) -> data.day = value, data -> data.day) + .documentation("The in-game day in which the player died.") + .add() + .build(); + @Nonnull + public static final ArrayCodec ARRAY_CODEC = new ArrayCodec<>(CODEC, PlayerDeathPositionData[]::new); + private String markerId; + private Transform transform; + private int day; + + private PlayerDeathPositionData() { + } + + public PlayerDeathPositionData(@Nonnull String markerId, @Nonnull Transform transform, int day) { + this.markerId = markerId; + this.transform = transform; + this.day = day; + } + + public String getMarkerId() { + return this.markerId; + } + + public Transform getTransform() { + return this.transform; + } + + public int getDay() { + return this.day; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/data/PlayerRespawnPointData.java b/src/com/hypixel/hytale/server/core/entity/entities/player/data/PlayerRespawnPointData.java new file mode 100644 index 0000000..6232942 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/data/PlayerRespawnPointData.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.core.entity.entities.player.data; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import javax.annotation.Nonnull; + +public final class PlayerRespawnPointData { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(PlayerRespawnPointData.class, PlayerRespawnPointData::new) + .append( + new KeyedCodec<>("BlockPosition", Vector3i.CODEC), + (respawnPointData, vector3i) -> respawnPointData.blockPosition = vector3i, + respawnPointData -> respawnPointData.blockPosition + ) + .documentation("The position of the respawn block.") + .add() + .append( + new KeyedCodec<>("RespawnPosition", Vector3d.CODEC), + (respawnPointData, vector3f) -> respawnPointData.respawnPosition = vector3f, + respawnPointData -> respawnPointData.respawnPosition + ) + .documentation("The position at which the player will respawn.") + .add() + .append(new KeyedCodec<>("Name", Codec.STRING), (respawnPointData, s) -> respawnPointData.name = s, respawnPointData -> respawnPointData.name) + .documentation("The name of the respawn point.") + .add() + .build(); + private Vector3i blockPosition; + private Vector3d respawnPosition; + private String name; + + public PlayerRespawnPointData(@Nonnull Vector3i blockPosition, @Nonnull Vector3d respawnPosition, @Nonnull String name) { + this.blockPosition = blockPosition; + this.respawnPosition = respawnPosition; + this.name = name; + } + + private PlayerRespawnPointData() { + } + + public Vector3i getBlockPosition() { + return this.blockPosition; + } + + public Vector3d getRespawnPosition() { + return this.respawnPosition; + } + + public String getName() { + return this.name; + } + + public void setName(@Nonnull String name) { + this.name = name; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/data/PlayerWorldData.java b/src/com/hypixel/hytale/server/core/entity/entities/player/data/PlayerWorldData.java new file mode 100644 index 0000000..c77fad4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/data/PlayerWorldData.java @@ -0,0 +1,156 @@ +package com.hypixel.hytale.server.core.entity.entities.player.data; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.protocol.SavedMovementStates; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class PlayerWorldData { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(PlayerWorldData.class, PlayerWorldData::new) + .append( + new KeyedCodec<>("LastPosition", Transform.CODEC), + (playerWorldData, lastPosition) -> playerWorldData.lastPosition = lastPosition, + playerWorldData -> playerWorldData.lastPosition + ) + .documentation("The last known position of the player.") + .add() + .append( + new KeyedCodec<>("LastMovementStates", ProtocolCodecs.SAVED_MOVEMENT_STATES), + (playerWorldData, lastMovementStates) -> playerWorldData.lastMovementStates = lastMovementStates, + playerWorldData -> playerWorldData.lastMovementStates + ) + .documentation("The last known movement states of the player.") + .add() + .append( + new KeyedCodec<>("WorldMapMarkers", ProtocolCodecs.MARKER_ARRAY), + (playerConfigData, objectives) -> playerConfigData.worldMapMarkers = objectives, + playerConfigData -> playerConfigData.worldMapMarkers + ) + .documentation("The world map markers of the player.") + .add() + .append( + new KeyedCodec<>("FirstSpawn", Codec.BOOLEAN), + (playerWorldData, value) -> playerWorldData.firstSpawn = value, + playerWorldData -> playerWorldData.firstSpawn + ) + .documentation("Whether this is the first spawn of the player.") + .add() + .append( + new KeyedCodec<>("RespawnPoints", new ArrayCodec<>(PlayerRespawnPointData.CODEC, PlayerRespawnPointData[]::new)), + (playerWorldData, respawnPointData) -> playerWorldData.respawnPoints = respawnPointData, + playerWorldData -> playerWorldData.respawnPoints + ) + .documentation("The respawn points of the player.") + .add() + .append( + new KeyedCodec<>("DeathPositions", new ArrayCodec<>(PlayerDeathPositionData.CODEC, PlayerDeathPositionData[]::new)), + (playerWorldData, deathPositions) -> playerWorldData.deathPositions = ObjectArrayList.wrap(deathPositions), + playerWorldData -> playerWorldData.deathPositions.toArray(PlayerDeathPositionData[]::new) + ) + .documentation("The death positions of the player in this world.") + .add() + .build(); + private static final int DEATH_POSITIONS_COUNT_MAX = 5; + private transient PlayerConfigData playerConfigData; + private Transform lastPosition; + private SavedMovementStates lastMovementStates; + private MapMarker[] worldMapMarkers; + private boolean firstSpawn = true; + @Nullable + private PlayerRespawnPointData[] respawnPoints; + @Nonnull + private List deathPositions = new ObjectArrayList<>(); + + private PlayerWorldData() { + } + + PlayerWorldData(@Nonnull PlayerConfigData playerConfigData) { + this.playerConfigData = playerConfigData; + } + + public void setPlayerConfigData(@Nonnull PlayerConfigData playerConfigData) { + this.playerConfigData = playerConfigData; + } + + public Transform getLastPosition() { + return this.lastPosition; + } + + public void setLastPosition(@Nonnull Transform lastPosition) { + this.lastPosition = lastPosition; + this.playerConfigData.markChanged(); + } + + public SavedMovementStates getLastMovementStates() { + return this.lastMovementStates; + } + + public void setLastMovementStates(@Nonnull MovementStates lastMovementStates, boolean save) { + this.setLastMovementStates_internal(lastMovementStates); + if (save) { + this.playerConfigData.markChanged(); + } + } + + private void setLastMovementStates_internal(@Nonnull MovementStates lastMovementStates) { + this.lastMovementStates = new SavedMovementStates(lastMovementStates.flying); + } + + @Nullable + public MapMarker[] getWorldMapMarkers() { + return this.worldMapMarkers; + } + + public void setWorldMapMarkers(MapMarker[] worldMapMarkers) { + this.worldMapMarkers = worldMapMarkers; + this.playerConfigData.markChanged(); + } + + public boolean isFirstSpawn() { + return this.firstSpawn; + } + + public void setFirstSpawn(boolean firstSpawn) { + this.firstSpawn = firstSpawn; + } + + @Nullable + public PlayerRespawnPointData[] getRespawnPoints() { + return this.respawnPoints; + } + + public void setRespawnPoints(@Nonnull PlayerRespawnPointData[] respawnPoints) { + this.respawnPoints = respawnPoints; + this.playerConfigData.markChanged(); + } + + @Nonnull + public List getDeathPositions() { + return this.deathPositions; + } + + public void addLastDeath(@Nonnull String markerId, @Nonnull Transform transform, int deathDay) { + this.deathPositions.add(new PlayerDeathPositionData(markerId, transform, deathDay)); + + while (this.deathPositions.size() > 5) { + this.deathPositions.removeFirst(); + } + + this.playerConfigData.markChanged(); + } + + public void removeLastDeath(@Nonnull String markerId) { + this.deathPositions.removeIf(deathPosition -> deathPosition.getMarkerId().equalsIgnoreCase(markerId)); + this.playerConfigData.markChanged(); + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/data/UniqueItemUsagesComponent.java b/src/com/hypixel/hytale/server/core/entity/entities/player/data/UniqueItemUsagesComponent.java new file mode 100644 index 0000000..e798d29 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/data/UniqueItemUsagesComponent.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.core.entity.entities.player.data; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class UniqueItemUsagesComponent implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(UniqueItemUsagesComponent.class, UniqueItemUsagesComponent::new) + .append(new KeyedCodec<>("UniqueItemUsed", new ArrayCodec<>(Codec.STRING, String[]::new)), (playerMemories, usages) -> { + if (usages != null) { + Collections.addAll(playerMemories.usedUniqueItems, usages); + } + }, playerMemories -> playerMemories.usedUniqueItems.toArray(String[]::new)) + .add() + .build(); + private final Set usedUniqueItems = new HashSet<>(); + + public UniqueItemUsagesComponent() { + } + + public static ComponentType getComponentType() { + return EntityModule.get().getUniqueItemUsagesComponentType(); + } + + @NullableDecl + @Override + public Component clone() { + UniqueItemUsagesComponent component = new UniqueItemUsagesComponent(); + component.usedUniqueItems.addAll(this.usedUniqueItems); + return component; + } + + public boolean hasUsedUniqueItem(String itemId) { + return this.usedUniqueItems.contains(itemId); + } + + public void recordUniqueItemUsage(String itemId) { + this.usedUniqueItems.add(itemId); + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/hud/CustomUIHud.java b/src/com/hypixel/hytale/server/core/entity/entities/player/hud/CustomUIHud.java new file mode 100644 index 0000000..04f3a82 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/hud/CustomUIHud.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.core.entity.entities.player.hud; + +import com.hypixel.hytale.protocol.packets.interface_.CustomHud; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import javax.annotation.Nonnull; + +public abstract class CustomUIHud { + @Nonnull + private final PlayerRef playerRef; + + public CustomUIHud(@Nonnull PlayerRef playerRef) { + this.playerRef = playerRef; + } + + public void show() { + UICommandBuilder commandBuilder = new UICommandBuilder(); + this.build(commandBuilder); + this.update(true, commandBuilder); + } + + public void update(boolean clear, @Nonnull UICommandBuilder commandBuilder) { + CustomHud customHud = new CustomHud(clear, commandBuilder.getCommands()); + this.playerRef.getPacketHandler().writeNoCache(customHud); + } + + @Nonnull + public PlayerRef getPlayerRef() { + return this.playerRef; + } + + protected abstract void build(@Nonnull UICommandBuilder var1); +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/hud/HudManager.java b/src/com/hypixel/hytale/server/core/entity/entities/player/hud/HudManager.java new file mode 100644 index 0000000..f47deb0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/hud/HudManager.java @@ -0,0 +1,128 @@ +package com.hypixel.hytale.server.core.entity.entities.player.hud; + +import com.hypixel.hytale.protocol.packets.interface_.CustomHud; +import com.hypixel.hytale.protocol.packets.interface_.HudComponent; +import com.hypixel.hytale.protocol.packets.interface_.ResetUserInterfaceState; +import com.hypixel.hytale.protocol.packets.interface_.UpdateVisibleHudComponents; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HudManager { + private static final Set DEFAULT_HUD_COMPONENTS = Set.of( + HudComponent.UtilitySlotSelector, + HudComponent.BlockVariantSelector, + HudComponent.StatusIcons, + HudComponent.Hotbar, + HudComponent.Chat, + HudComponent.Notifications, + HudComponent.KillFeed, + HudComponent.InputBindings, + HudComponent.Reticle, + HudComponent.Compass, + HudComponent.Speedometer, + HudComponent.ObjectivePanel, + HudComponent.PortalPanel, + HudComponent.EventTitle, + HudComponent.Stamina, + HudComponent.AmmoIndicator, + HudComponent.Health, + HudComponent.Mana, + HudComponent.Oxygen, + HudComponent.BuilderToolsLegend, + HudComponent.Sleep + ); + private final Set visibleHudComponents = ConcurrentHashMap.newKeySet(); + private final Set unmodifiableVisibleHudComponents = Collections.unmodifiableSet(this.visibleHudComponents); + @Nullable + private CustomUIHud customHud; + + public HudManager() { + this.visibleHudComponents.addAll(DEFAULT_HUD_COMPONENTS); + } + + public HudManager(@Nonnull HudManager other) { + this.customHud = other.customHud; + } + + @Nullable + public CustomUIHud getCustomHud() { + return this.customHud; + } + + @Nonnull + public Set getVisibleHudComponents() { + return this.unmodifiableVisibleHudComponents; + } + + public void setVisibleHudComponents(@Nonnull PlayerRef ref, HudComponent... hudComponents) { + this.visibleHudComponents.clear(); + Collections.addAll(this.visibleHudComponents, hudComponents); + this.sendVisibleHudComponents(ref.getPacketHandler()); + } + + public void setVisibleHudComponents(@Nonnull PlayerRef ref, @Nonnull Set hudComponents) { + this.visibleHudComponents.clear(); + this.visibleHudComponents.addAll(hudComponents); + this.sendVisibleHudComponents(ref.getPacketHandler()); + } + + public void showHudComponents(@Nonnull PlayerRef ref, HudComponent... hudComponents) { + Collections.addAll(this.visibleHudComponents, hudComponents); + this.sendVisibleHudComponents(ref.getPacketHandler()); + } + + public void showHudComponents(@Nonnull PlayerRef ref, @Nonnull Set hudComponents) { + this.visibleHudComponents.addAll(hudComponents); + this.sendVisibleHudComponents(ref.getPacketHandler()); + } + + public void hideHudComponents(@Nonnull PlayerRef ref, @Nonnull HudComponent... hudComponents) { + for (HudComponent hudComponent : hudComponents) { + this.visibleHudComponents.remove(hudComponent); + } + + this.sendVisibleHudComponents(ref.getPacketHandler()); + } + + public void setCustomHud(@Nonnull PlayerRef ref, @Nullable CustomUIHud hud) { + CustomUIHud oldHud = this.getCustomHud(); + if (oldHud != hud) { + this.customHud = hud; + if (hud == null) { + ref.getPacketHandler().writeNoCache(new CustomHud(true, null)); + } else { + hud.show(); + } + } + } + + public void resetHud(@Nonnull PlayerRef ref) { + this.setVisibleHudComponents(ref, DEFAULT_HUD_COMPONENTS); + this.setCustomHud(ref, null); + } + + public void resetUserInterface(@Nonnull PlayerRef ref) { + ref.getPacketHandler().writeNoCache(new ResetUserInterfaceState()); + } + + public void sendVisibleHudComponents(@Nonnull PacketHandler packetHandler) { + packetHandler.writeNoCache(new UpdateVisibleHudComponents(this.visibleHudComponents.toArray(HudComponent[]::new))); + } + + @Nonnull + @Override + public String toString() { + return "HudManager{visibleHudComponents=" + + this.visibleHudComponents + + ", unmodifiableVisibleHudComponents=" + + this.unmodifiableVisibleHudComponents + + ", customHud=" + + this.customHud + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/movement/MovementConfig.java b/src/com/hypixel/hytale/server/core/entity/entities/player/movement/MovementConfig.java new file mode 100644 index 0000000..3c85209 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/movement/MovementConfig.java @@ -0,0 +1,1141 @@ +package com.hypixel.hytale.server.core.entity.entities.player.movement; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.protocol.MovementSettings; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class MovementConfig implements JsonAssetWithMap>, NetworkSerializable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + MovementConfig.class, + MovementConfig::new, + Codec.STRING, + (movementConfig, s) -> movementConfig.id = s, + movementConfig -> movementConfig.id, + (movementConfig, data) -> movementConfig.extraData = data, + movementConfig -> movementConfig.extraData + ) + .appendInherited( + new KeyedCodec<>("VelocityResistance", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.velocityResistance = tasks, + movementConfig -> movementConfig.velocityResistance, + (movementConfig, parent) -> movementConfig.velocityResistance = parent.velocityResistance + ) + .add() + .appendInherited( + new KeyedCodec<>("JumpForce", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.jumpForce = tasks, + movementConfig -> movementConfig.jumpForce, + (movementConfig, parent) -> movementConfig.jumpForce = parent.jumpForce + ) + .add() + .appendInherited( + new KeyedCodec<>("SwimJumpForce", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.swimJumpForce = tasks, + movementConfig -> movementConfig.swimJumpForce, + (movementConfig, parent) -> movementConfig.swimJumpForce = parent.swimJumpForce + ) + .add() + .appendInherited( + new KeyedCodec<>("JumpBufferDuration", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.jumpBufferDuration = tasks, + movementConfig -> movementConfig.jumpBufferDuration, + (movementConfig, parent) -> movementConfig.jumpBufferDuration = parent.jumpBufferDuration + ) + .add() + .appendInherited( + new KeyedCodec<>("JumpBufferMaxYVelocity", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.jumpBufferMaxYVelocity = tasks, + movementConfig -> movementConfig.jumpBufferMaxYVelocity, + (movementConfig, parent) -> movementConfig.jumpBufferMaxYVelocity = parent.jumpBufferMaxYVelocity + ) + .add() + .appendInherited( + new KeyedCodec<>("Acceleration", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.acceleration = tasks, + movementConfig -> movementConfig.acceleration, + (movementConfig, parent) -> movementConfig.acceleration = parent.acceleration + ) + .add() + .appendInherited( + new KeyedCodec<>("AirDragMin", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.airDragMin = tasks, + movementConfig -> movementConfig.airDragMin, + (movementConfig, parent) -> movementConfig.airDragMin = parent.airDragMin + ) + .add() + .appendInherited( + new KeyedCodec<>("AirDragMax", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.airDragMax = tasks, + movementConfig -> movementConfig.airDragMax, + (movementConfig, parent) -> movementConfig.airDragMax = parent.airDragMax + ) + .add() + .appendInherited( + new KeyedCodec<>("AirDragMinSpeed", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.airDragMinSpeed = tasks, + movementConfig -> movementConfig.airDragMinSpeed, + (movementConfig, parent) -> movementConfig.airDragMinSpeed = parent.airDragMinSpeed + ) + .add() + .appendInherited( + new KeyedCodec<>("AirDragMaxSpeed", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.airDragMaxSpeed = tasks, + movementConfig -> movementConfig.airDragMaxSpeed, + (movementConfig, parent) -> movementConfig.airDragMaxSpeed = parent.airDragMaxSpeed + ) + .add() + .appendInherited( + new KeyedCodec<>("AirFrictionMin", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.airFrictionMin = tasks, + movementConfig -> movementConfig.airFrictionMin, + (movementConfig, parent) -> movementConfig.airFrictionMin = parent.airFrictionMin + ) + .add() + .appendInherited( + new KeyedCodec<>("AirFrictionMax", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.airFrictionMax = tasks, + movementConfig -> movementConfig.airFrictionMax, + (movementConfig, parent) -> movementConfig.airFrictionMax = parent.airFrictionMax + ) + .add() + .appendInherited( + new KeyedCodec<>("AirFrictionMinSpeed", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.airFrictionMinSpeed = tasks, + movementConfig -> movementConfig.airFrictionMinSpeed, + (movementConfig, parent) -> movementConfig.airFrictionMinSpeed = parent.airFrictionMinSpeed + ) + .add() + .appendInherited( + new KeyedCodec<>("AirFrictionMaxSpeed", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.airFrictionMaxSpeed = tasks, + movementConfig -> movementConfig.airFrictionMaxSpeed, + (movementConfig, parent) -> movementConfig.airFrictionMaxSpeed = parent.airFrictionMaxSpeed + ) + .add() + .appendInherited( + new KeyedCodec<>("AirSpeedMultiplier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.airSpeedMultiplier = tasks, + movementConfig -> movementConfig.airSpeedMultiplier, + (movementConfig, parent) -> movementConfig.airSpeedMultiplier = parent.airSpeedMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("AirControlMinSpeed", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.airControlMinSpeed = tasks, + movementConfig -> movementConfig.airControlMinSpeed, + (movementConfig, parent) -> movementConfig.airControlMinSpeed = parent.airControlMinSpeed + ) + .add() + .appendInherited( + new KeyedCodec<>("AirControlMaxSpeed", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.airControlMaxSpeed = tasks, + movementConfig -> movementConfig.airControlMaxSpeed, + (movementConfig, parent) -> movementConfig.airControlMaxSpeed = parent.airControlMaxSpeed + ) + .add() + .appendInherited( + new KeyedCodec<>("AirControlMinMultiplier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.airControlMinMultiplier = tasks, + movementConfig -> movementConfig.airControlMinMultiplier, + (movementConfig, parent) -> movementConfig.airControlMinMultiplier = parent.airControlMinMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("AirControlMaxMultiplier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.airControlMaxMultiplier = tasks, + movementConfig -> movementConfig.airControlMaxMultiplier, + (movementConfig, parent) -> movementConfig.airControlMaxMultiplier = parent.airControlMaxMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("ComboAirSpeedMultiplier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.comboAirSpeedMultiplier = tasks, + movementConfig -> movementConfig.comboAirSpeedMultiplier, + (movementConfig, parent) -> movementConfig.comboAirSpeedMultiplier = parent.comboAirSpeedMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("BaseSpeed", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.baseSpeed = tasks, + movementConfig -> movementConfig.baseSpeed, + (movementConfig, parent) -> movementConfig.baseSpeed = parent.baseSpeed + ) + .add() + .appendInherited( + new KeyedCodec<>("ClimbSpeed", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.climbSpeed = tasks, + movementConfig -> movementConfig.climbSpeed, + (movementConfig, parent) -> movementConfig.climbSpeed = parent.climbSpeed + ) + .add() + .appendInherited( + new KeyedCodec<>("ClimbSpeedLateral", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.climbSpeedLateral = tasks, + movementConfig -> movementConfig.climbSpeedLateral, + (movementConfig, parent) -> movementConfig.climbSpeedLateral = parent.climbSpeedLateral + ) + .add() + .appendInherited( + new KeyedCodec<>("ClimbUpSprintSpeed", Codec.FLOAT), + (movementConfig, aFloat) -> movementConfig.climbUpSprintSpeed = aFloat, + movementConfig -> movementConfig.climbUpSprintSpeed, + (movementConfig, parent) -> movementConfig.climbUpSprintSpeed = parent.climbUpSprintSpeed + ) + .add() + .appendInherited( + new KeyedCodec<>("ClimbDownSprintSpeed", Codec.FLOAT), + (movementConfig, aFloat) -> movementConfig.climbDownSprintSpeed = aFloat, + movementConfig -> movementConfig.climbDownSprintSpeed, + (movementConfig, parent) -> movementConfig.climbDownSprintSpeed = parent.climbDownSprintSpeed + ) + .add() + .appendInherited( + new KeyedCodec<>("HorizontalFlySpeed", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.horizontalFlySpeed = tasks, + movementConfig -> movementConfig.horizontalFlySpeed, + (movementConfig, parent) -> movementConfig.horizontalFlySpeed = parent.horizontalFlySpeed + ) + .add() + .appendInherited( + new KeyedCodec<>("VerticalFlySpeed", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.verticalFlySpeed = tasks, + movementConfig -> movementConfig.verticalFlySpeed, + (movementConfig, parent) -> movementConfig.verticalFlySpeed = parent.verticalFlySpeed + ) + .add() + .appendInherited( + new KeyedCodec<>("MaxSpeedMultiplier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.maxSpeedMultiplier = tasks, + movementConfig -> movementConfig.maxSpeedMultiplier, + (movementConfig, parent) -> movementConfig.maxSpeedMultiplier = parent.maxSpeedMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("MinSpeedMultiplier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.minSpeedMultiplier = tasks, + movementConfig -> movementConfig.minSpeedMultiplier, + (movementConfig, parent) -> movementConfig.minSpeedMultiplier = parent.minSpeedMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("WishDirectionGravityX", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.wishDirectionGravityX = tasks, + movementConfig -> movementConfig.wishDirectionGravityX, + (movementConfig, parent) -> movementConfig.wishDirectionGravityX = parent.wishDirectionGravityX + ) + .add() + .appendInherited( + new KeyedCodec<>("WishDirectionGravityY", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.wishDirectionGravityY = tasks, + movementConfig -> movementConfig.wishDirectionGravityY, + (movementConfig, parent) -> movementConfig.wishDirectionGravityY = parent.wishDirectionGravityY + ) + .add() + .appendInherited( + new KeyedCodec<>("WishDirectionWeightX", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.wishDirectionWeightX = tasks, + movementConfig -> movementConfig.wishDirectionWeightX, + (movementConfig, parent) -> movementConfig.wishDirectionWeightX = parent.wishDirectionWeightX + ) + .add() + .appendInherited( + new KeyedCodec<>("WishDirectionWeightY", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.wishDirectionWeightY = tasks, + movementConfig -> movementConfig.wishDirectionWeightY, + (movementConfig, parent) -> movementConfig.wishDirectionWeightY = parent.wishDirectionWeightY + ) + .add() + .appendInherited( + new KeyedCodec<>("CollisionExpulsionForce", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.collisionExpulsionForce = tasks, + movementConfig -> movementConfig.collisionExpulsionForce, + (movementConfig, parent) -> movementConfig.collisionExpulsionForce = parent.collisionExpulsionForce + ) + .add() + .appendInherited( + new KeyedCodec<>("ForwardWalkSpeedMultiplier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.forwardWalkSpeedMultiplier = tasks, + movementConfig -> movementConfig.forwardWalkSpeedMultiplier, + (movementConfig, parent) -> movementConfig.forwardWalkSpeedMultiplier = parent.forwardWalkSpeedMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("BackwardWalkSpeedMultiplier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.backwardWalkSpeedMultiplier = tasks, + movementConfig -> movementConfig.backwardWalkSpeedMultiplier, + (movementConfig, parent) -> movementConfig.backwardWalkSpeedMultiplier = parent.backwardWalkSpeedMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("StrafeWalkSpeedMultiplier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.strafeWalkSpeedMultiplier = tasks, + movementConfig -> movementConfig.strafeWalkSpeedMultiplier, + (movementConfig, parent) -> movementConfig.strafeWalkSpeedMultiplier = parent.strafeWalkSpeedMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("ForwardRunSpeedMultiplier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.forwardRunSpeedMultiplier = tasks, + movementConfig -> movementConfig.forwardRunSpeedMultiplier, + (movementConfig, parent) -> movementConfig.forwardRunSpeedMultiplier = parent.forwardRunSpeedMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("BackwardRunSpeedMultiplier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.backwardRunSpeedMultiplier = tasks, + movementConfig -> movementConfig.backwardRunSpeedMultiplier, + (movementConfig, parent) -> movementConfig.backwardRunSpeedMultiplier = parent.backwardRunSpeedMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("StrafeRunSpeedMultiplier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.strafeRunSpeedMultiplier = tasks, + movementConfig -> movementConfig.strafeRunSpeedMultiplier, + (movementConfig, parent) -> movementConfig.strafeRunSpeedMultiplier = parent.strafeRunSpeedMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("ForwardCrouchSpeedMultiplier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.forwardCrouchSpeedMultiplier = tasks, + movementConfig -> movementConfig.forwardCrouchSpeedMultiplier, + (movementConfig, parent) -> movementConfig.forwardCrouchSpeedMultiplier = parent.forwardCrouchSpeedMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("BackwardCrouchSpeedMultiplier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.backwardCrouchSpeedMultiplier = tasks, + movementConfig -> movementConfig.backwardCrouchSpeedMultiplier, + (movementConfig, parent) -> movementConfig.backwardCrouchSpeedMultiplier = parent.backwardCrouchSpeedMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("StrafeCrouchSpeedMultiplier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.strafeCrouchSpeedMultiplier = tasks, + movementConfig -> movementConfig.strafeCrouchSpeedMultiplier, + (movementConfig, parent) -> movementConfig.strafeCrouchSpeedMultiplier = parent.strafeCrouchSpeedMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("ForwardSprintSpeedMultiplier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.forwardSprintSpeedMultiplier = tasks, + movementConfig -> movementConfig.forwardSprintSpeedMultiplier, + (movementConfig, parent) -> movementConfig.forwardSprintSpeedMultiplier = parent.forwardSprintSpeedMultiplier + ) + .add() + .appendInherited( + new KeyedCodec<>("VariableJumpFallForce", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.variableJumpFallForce = tasks, + movementConfig -> movementConfig.variableJumpFallForce, + (movementConfig, parent) -> movementConfig.variableJumpFallForce = parent.variableJumpFallForce + ) + .add() + .appendInherited( + new KeyedCodec<>("FallEffectDuration", Codec.FLOAT), + (movementConfig, aFloat) -> movementConfig.fallEffectDuration = aFloat, + movementConfig -> movementConfig.fallEffectDuration, + (movementConfig, parent) -> movementConfig.fallEffectDuration = parent.fallEffectDuration + ) + .add() + .appendInherited( + new KeyedCodec<>("FallJumpForce", Codec.FLOAT), + (movementConfig, aFloat) -> movementConfig.fallJumpForce = aFloat, + movementConfig -> movementConfig.fallJumpForce, + (movementConfig, parent) -> movementConfig.fallJumpForce = parent.fallJumpForce + ) + .add() + .appendInherited( + new KeyedCodec<>("FallMomentumLoss", Codec.FLOAT), + (movementConfig, aFloat) -> movementConfig.fallMomentumLoss = aFloat, + movementConfig -> movementConfig.fallMomentumLoss, + (movementConfig, parent) -> movementConfig.fallMomentumLoss = parent.fallMomentumLoss + ) + .add() + .appendInherited( + new KeyedCodec<>("AutoJumpObstacleSpeedLoss", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.autoJumpObstacleSpeedLoss = tasks, + movementConfig -> movementConfig.autoJumpObstacleSpeedLoss, + (movementConfig, parent) -> movementConfig.autoJumpObstacleSpeedLoss = parent.autoJumpObstacleSpeedLoss + ) + .add() + .appendInherited( + new KeyedCodec<>("AutoJumpObstacleSprintSpeedLoss", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.autoJumpObstacleSprintSpeedLoss = tasks, + movementConfig -> movementConfig.autoJumpObstacleSprintSpeedLoss, + (movementConfig, parent) -> movementConfig.autoJumpObstacleSprintSpeedLoss = parent.autoJumpObstacleSprintSpeedLoss + ) + .add() + .appendInherited( + new KeyedCodec<>("AutoJumpObstacleEffectDuration", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.autoJumpObstacleEffectDuration = tasks, + movementConfig -> movementConfig.autoJumpObstacleEffectDuration, + (movementConfig, parent) -> movementConfig.autoJumpObstacleEffectDuration = parent.autoJumpObstacleEffectDuration + ) + .add() + .appendInherited( + new KeyedCodec<>("AutoJumpObstacleSprintEffectDuration", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.autoJumpObstacleSprintEffectDuration = tasks, + movementConfig -> movementConfig.autoJumpObstacleSprintEffectDuration, + (movementConfig, parent) -> movementConfig.autoJumpObstacleSprintEffectDuration = parent.autoJumpObstacleSprintEffectDuration + ) + .add() + .appendInherited( + new KeyedCodec<>("AutoJumpObstacleMaxAngle", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.autoJumpObstacleMaxAngle = tasks, + movementConfig -> movementConfig.autoJumpObstacleMaxAngle, + (movementConfig, parent) -> movementConfig.autoJumpObstacleMaxAngle = parent.autoJumpObstacleMaxAngle + ) + .add() + .appendInherited( + new KeyedCodec<>("AutoJumpDisableJumping", Codec.BOOLEAN), + (movementConfig, tasks) -> movementConfig.autoJumpDisableJumping = tasks, + movementConfig -> movementConfig.autoJumpDisableJumping, + (movementConfig, parent) -> movementConfig.autoJumpDisableJumping = parent.autoJumpDisableJumping + ) + .add() + .appendInherited( + new KeyedCodec<>("MinSlideEntrySpeed", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.minSlideEntrySpeed = tasks, + movementConfig -> movementConfig.minSlideEntrySpeed, + (movementConfig, parent) -> movementConfig.minSlideEntrySpeed = parent.minSlideEntrySpeed + ) + .add() + .appendInherited( + new KeyedCodec<>("SlideExitSpeed", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.slideExitSpeed = tasks, + movementConfig -> movementConfig.slideExitSpeed, + (movementConfig, parent) -> movementConfig.slideExitSpeed = parent.slideExitSpeed + ) + .add() + .appendInherited( + new KeyedCodec<>("MinFallSpeedToEngageRoll", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.minFallSpeedToEngageRoll = tasks, + movementConfig -> movementConfig.minFallSpeedToEngageRoll, + (movementConfig, parent) -> movementConfig.minFallSpeedToEngageRoll = parent.minFallSpeedToEngageRoll + ) + .add() + .appendInherited( + new KeyedCodec<>("MaxFallSpeedToEngageRoll", Codec.FLOAT), + (movementConfig, value) -> movementConfig.maxFallSpeedToEngageRoll = value, + movementConfig -> movementConfig.maxFallSpeedToEngageRoll, + (movementConfig, parent) -> movementConfig.maxFallSpeedToEngageRoll = parent.maxFallSpeedToEngageRoll + ) + .add() + .appendInherited( + new KeyedCodec<>("FallDamagePartialMitigationPercent", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.fallDamagePartialMitigationPercent = tasks, + movementConfig -> movementConfig.fallDamagePartialMitigationPercent, + (movementConfig, parent) -> movementConfig.fallDamagePartialMitigationPercent = parent.fallDamagePartialMitigationPercent + ) + .add() + .appendInherited( + new KeyedCodec<>("MaxFallSpeedRollFullMitigation", Codec.FLOAT), + (movementConfig, value) -> movementConfig.maxFallSpeedRollFullMitigation = value, + movementConfig -> movementConfig.maxFallSpeedRollFullMitigation, + (movementConfig, parent) -> movementConfig.maxFallSpeedRollFullMitigation = parent.maxFallSpeedRollFullMitigation + ) + .add() + .appendInherited( + new KeyedCodec<>("RollStartSpeedModifier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.rollStartSpeedModifier = tasks, + movementConfig -> movementConfig.rollStartSpeedModifier, + (movementConfig, parent) -> movementConfig.rollStartSpeedModifier = parent.rollStartSpeedModifier + ) + .add() + .appendInherited( + new KeyedCodec<>("RollExitSpeedModifier", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.rollExitSpeedModifier = tasks, + movementConfig -> movementConfig.rollExitSpeedModifier, + (movementConfig, parent) -> movementConfig.rollExitSpeedModifier = parent.rollExitSpeedModifier + ) + .add() + .appendInherited( + new KeyedCodec<>("RollTimeToComplete", Codec.FLOAT), + (movementConfig, tasks) -> movementConfig.rollTimeToComplete = tasks, + movementConfig -> movementConfig.rollTimeToComplete, + (movementConfig, parent) -> movementConfig.rollTimeToComplete = parent.rollTimeToComplete + ) + .add() + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(MovementConfig::getAssetStore)); + private static AssetStore> ASSET_STORE; + public static final int DEFAULT_INDEX = 0; + public static final String DEFAULT_ID = "BuiltinDefault"; + public static final MovementConfig DEFAULT_MOVEMENT = new MovementConfig("BuiltinDefault") { + { + this.velocityResistance = 0.242F; + this.jumpForce = 11.8F; + this.swimJumpForce = 10.0F; + this.jumpBufferDuration = 0.3F; + this.jumpBufferMaxYVelocity = 3.0F; + this.acceleration = 0.1F; + this.airDragMin = 0.96F; + this.airDragMax = 0.995F; + this.airDragMinSpeed = 6.0F; + this.airDragMaxSpeed = 10.0F; + this.airFrictionMin = 0.02F; + this.airFrictionMax = 0.045F; + this.airFrictionMinSpeed = 6.0F; + this.airFrictionMaxSpeed = 10.0F; + this.airSpeedMultiplier = 1.0F; + this.airControlMinSpeed = 0.0F; + this.airControlMaxSpeed = 3.0F; + this.airControlMinMultiplier = 0.0F; + this.airControlMaxMultiplier = 3.13F; + this.comboAirSpeedMultiplier = 1.05F; + this.baseSpeed = 5.5F; + this.climbSpeed = 0.035F; + this.climbSpeedLateral = 0.035F; + this.climbUpSprintSpeed = 0.5F; + this.climbDownSprintSpeed = 0.6F; + this.horizontalFlySpeed = 10.32F; + this.verticalFlySpeed = 10.32F; + this.maxSpeedMultiplier = 1000.0F; + this.minSpeedMultiplier = 0.01F; + this.wishDirectionGravityX = 0.5F; + this.wishDirectionGravityY = 0.5F; + this.wishDirectionWeightX = 0.5F; + this.wishDirectionWeightY = 0.5F; + this.collisionExpulsionForce = 0.04F; + this.forwardWalkSpeedMultiplier = 0.3F; + this.backwardWalkSpeedMultiplier = 0.3F; + this.strafeWalkSpeedMultiplier = 0.3F; + this.forwardRunSpeedMultiplier = 1.0F; + this.backwardRunSpeedMultiplier = 0.65F; + this.strafeRunSpeedMultiplier = 0.8F; + this.forwardCrouchSpeedMultiplier = 0.55F; + this.backwardCrouchSpeedMultiplier = 0.4F; + this.strafeCrouchSpeedMultiplier = 0.45F; + this.forwardSprintSpeedMultiplier = 1.65F; + this.variableJumpFallForce = 35.0F; + this.fallEffectDuration = 0.6F; + this.fallJumpForce = 7.0F; + this.fallMomentumLoss = 0.1F; + this.autoJumpObstacleSpeedLoss = 0.95F; + this.autoJumpObstacleSprintSpeedLoss = 0.75F; + this.autoJumpObstacleEffectDuration = 0.2F; + this.autoJumpObstacleSprintEffectDuration = 0.1F; + this.autoJumpObstacleMaxAngle = 45.0F; + this.autoJumpDisableJumping = true; + this.minSlideEntrySpeed = 8.5F; + this.slideExitSpeed = 2.5F; + this.minFallSpeedToEngageRoll = 21.0F; + this.maxFallSpeedToEngageRoll = 31.0F; + this.fallDamagePartialMitigationPercent = 33.0F; + this.maxFallSpeedRollFullMitigation = 25.0F; + this.rollStartSpeedModifier = 2.5F; + this.rollExitSpeedModifier = 1.5F; + this.rollTimeToComplete = 0.9F; + } + }; + protected AssetExtraInfo.Data extraData; + protected String id; + protected float velocityResistance; + protected float jumpForce; + protected float swimJumpForce; + protected float jumpBufferDuration; + protected float jumpBufferMaxYVelocity; + protected float acceleration; + protected float airDragMin; + protected float airDragMax; + protected float airDragMinSpeed; + protected float airDragMaxSpeed; + protected float airFrictionMin; + protected float airFrictionMax; + protected float airFrictionMinSpeed; + protected float airFrictionMaxSpeed; + protected float airSpeedMultiplier; + protected float airControlMinSpeed; + protected float airControlMaxSpeed; + protected float airControlMinMultiplier; + protected float airControlMaxMultiplier; + protected float comboAirSpeedMultiplier; + protected float baseSpeed; + protected float climbSpeed; + protected float climbSpeedLateral; + protected float climbUpSprintSpeed; + protected float climbDownSprintSpeed; + protected float horizontalFlySpeed; + protected float verticalFlySpeed; + protected float maxSpeedMultiplier; + protected float minSpeedMultiplier; + protected float wishDirectionGravityX; + protected float wishDirectionGravityY; + protected float wishDirectionWeightX; + protected float wishDirectionWeightY; + protected float collisionExpulsionForce; + protected float forwardWalkSpeedMultiplier; + protected float backwardWalkSpeedMultiplier; + protected float strafeWalkSpeedMultiplier; + protected float forwardRunSpeedMultiplier; + protected float backwardRunSpeedMultiplier; + protected float strafeRunSpeedMultiplier; + protected float forwardCrouchSpeedMultiplier; + protected float backwardCrouchSpeedMultiplier; + protected float strafeCrouchSpeedMultiplier; + protected float forwardSprintSpeedMultiplier; + protected float variableJumpFallForce; + protected float fallEffectDuration; + protected float fallJumpForce; + protected float fallMomentumLoss; + protected float autoJumpObstacleSpeedLoss; + protected float autoJumpObstacleSprintSpeedLoss; + protected float autoJumpObstacleEffectDuration; + protected float autoJumpObstacleSprintEffectDuration; + protected float autoJumpObstacleMaxAngle; + protected boolean autoJumpDisableJumping; + protected float minSlideEntrySpeed; + protected float slideExitSpeed; + protected float minFallSpeedToEngageRoll; + protected float maxFallSpeedToEngageRoll; + protected float fallDamagePartialMitigationPercent; + protected float maxFallSpeedRollFullMitigation; + protected float rollStartSpeedModifier; + protected float rollExitSpeedModifier; + protected float rollTimeToComplete; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(MovementConfig.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public MovementConfig(@Nonnull MovementConfig movementConfig) { + this.id = movementConfig.id; + this.velocityResistance = movementConfig.velocityResistance; + this.jumpForce = movementConfig.jumpForce; + this.swimJumpForce = movementConfig.swimJumpForce; + this.jumpBufferDuration = movementConfig.jumpBufferDuration; + this.jumpBufferMaxYVelocity = movementConfig.jumpBufferMaxYVelocity; + this.acceleration = movementConfig.acceleration; + this.airDragMin = movementConfig.airDragMin; + this.airDragMax = movementConfig.airDragMax; + this.airDragMinSpeed = movementConfig.airDragMinSpeed; + this.airDragMaxSpeed = movementConfig.airDragMaxSpeed; + this.airFrictionMin = movementConfig.airFrictionMin; + this.airFrictionMax = movementConfig.airFrictionMax; + this.airFrictionMinSpeed = movementConfig.airFrictionMinSpeed; + this.airFrictionMaxSpeed = movementConfig.airFrictionMaxSpeed; + this.airSpeedMultiplier = movementConfig.airSpeedMultiplier; + this.airControlMinSpeed = movementConfig.airControlMinSpeed; + this.airControlMaxSpeed = movementConfig.airControlMaxSpeed; + this.airControlMinMultiplier = movementConfig.airControlMinMultiplier; + this.airControlMaxMultiplier = movementConfig.airControlMaxMultiplier; + this.comboAirSpeedMultiplier = movementConfig.airSpeedMultiplier; + this.baseSpeed = movementConfig.baseSpeed; + this.climbSpeed = movementConfig.climbSpeed; + this.climbSpeedLateral = movementConfig.climbSpeedLateral; + this.climbUpSprintSpeed = movementConfig.climbUpSprintSpeed; + this.climbDownSprintSpeed = movementConfig.climbDownSprintSpeed; + this.horizontalFlySpeed = movementConfig.horizontalFlySpeed; + this.verticalFlySpeed = movementConfig.verticalFlySpeed; + this.maxSpeedMultiplier = movementConfig.maxSpeedMultiplier; + this.minSpeedMultiplier = movementConfig.minSpeedMultiplier; + this.wishDirectionGravityX = movementConfig.wishDirectionGravityX; + this.wishDirectionGravityY = movementConfig.wishDirectionGravityY; + this.wishDirectionWeightX = movementConfig.wishDirectionWeightX; + this.wishDirectionWeightY = movementConfig.wishDirectionWeightY; + this.collisionExpulsionForce = movementConfig.collisionExpulsionForce; + this.forwardWalkSpeedMultiplier = movementConfig.forwardWalkSpeedMultiplier; + this.backwardWalkSpeedMultiplier = movementConfig.backwardWalkSpeedMultiplier; + this.strafeWalkSpeedMultiplier = movementConfig.strafeWalkSpeedMultiplier; + this.forwardRunSpeedMultiplier = movementConfig.forwardRunSpeedMultiplier; + this.backwardRunSpeedMultiplier = movementConfig.backwardRunSpeedMultiplier; + this.strafeRunSpeedMultiplier = movementConfig.strafeRunSpeedMultiplier; + this.forwardCrouchSpeedMultiplier = movementConfig.forwardCrouchSpeedMultiplier; + this.backwardCrouchSpeedMultiplier = movementConfig.backwardCrouchSpeedMultiplier; + this.strafeCrouchSpeedMultiplier = movementConfig.strafeCrouchSpeedMultiplier; + this.forwardSprintSpeedMultiplier = movementConfig.forwardSprintSpeedMultiplier; + this.variableJumpFallForce = movementConfig.variableJumpFallForce; + this.autoJumpObstacleSpeedLoss = movementConfig.autoJumpObstacleSpeedLoss; + this.autoJumpObstacleSprintSpeedLoss = movementConfig.autoJumpObstacleSprintSpeedLoss; + this.autoJumpObstacleEffectDuration = movementConfig.autoJumpObstacleEffectDuration; + this.autoJumpObstacleSprintEffectDuration = movementConfig.autoJumpObstacleSprintEffectDuration; + this.autoJumpObstacleMaxAngle = movementConfig.autoJumpObstacleMaxAngle; + this.autoJumpDisableJumping = movementConfig.autoJumpDisableJumping; + this.minSlideEntrySpeed = movementConfig.minSlideEntrySpeed; + this.slideExitSpeed = movementConfig.slideExitSpeed; + this.minFallSpeedToEngageRoll = movementConfig.minFallSpeedToEngageRoll; + this.maxFallSpeedToEngageRoll = movementConfig.maxFallSpeedToEngageRoll; + this.fallDamagePartialMitigationPercent = movementConfig.fallDamagePartialMitigationPercent; + this.maxFallSpeedRollFullMitigation = movementConfig.maxFallSpeedRollFullMitigation; + this.rollStartSpeedModifier = movementConfig.rollStartSpeedModifier; + this.rollExitSpeedModifier = movementConfig.rollExitSpeedModifier; + this.rollTimeToComplete = movementConfig.rollTimeToComplete; + } + + public MovementConfig(String id) { + this.id = id; + } + + protected MovementConfig() { + } + + public String getId() { + return this.id; + } + + public AssetExtraInfo.Data getExtraData() { + return this.extraData; + } + + public float getVelocityResistance() { + return this.velocityResistance; + } + + public float getJumpForce() { + return this.jumpForce; + } + + public float getSwimJumpForce() { + return this.swimJumpForce; + } + + public float getJumpBufferDuration() { + return this.jumpBufferDuration; + } + + public float getJumpBufferMaxYVelocity() { + return this.jumpBufferMaxYVelocity; + } + + public float getAcceleration() { + return this.acceleration; + } + + public float getAirDragMin() { + return this.airDragMin; + } + + public float getAirDragMax() { + return this.airDragMax; + } + + public float getAirDragMinSpeed() { + return this.airDragMinSpeed; + } + + public float getAirDragMaxSpeed() { + return this.airDragMaxSpeed; + } + + public float getAirFrictionMin() { + return this.airFrictionMin; + } + + public float getAirFrictionMax() { + return this.airFrictionMax; + } + + public float getAirFrictionMinSpeed() { + return this.airFrictionMinSpeed; + } + + public float getAirFrictionMaxSpeed() { + return this.airFrictionMaxSpeed; + } + + public float getAirSpeedMultiplier() { + return this.airSpeedMultiplier; + } + + public float getAirControlMinSpeed() { + return this.airControlMinSpeed; + } + + public float getAirControlMaxSpeed() { + return this.airControlMaxSpeed; + } + + public float getAirControlMinMultiplier() { + return this.airControlMinMultiplier; + } + + public float getAirControlMaxMultiplier() { + return this.airControlMaxMultiplier; + } + + public float getComboAirSpeedMultiplier() { + return this.comboAirSpeedMultiplier; + } + + public float getBaseSpeed() { + return this.baseSpeed; + } + + public float getClimbSpeed() { + return this.climbSpeed; + } + + public float getClimbSpeedLateral() { + return this.climbSpeedLateral; + } + + public float getClimbUpSprintSpeed() { + return this.climbUpSprintSpeed; + } + + public float getClimbDownSprintSpeed() { + return this.climbDownSprintSpeed; + } + + public float getHorizontalFlySpeed() { + return this.horizontalFlySpeed; + } + + public float getVerticalFlySpeed() { + return this.verticalFlySpeed; + } + + public float getMaxSpeedMultiplier() { + return this.maxSpeedMultiplier; + } + + public float getMinSpeedMultiplier() { + return this.minSpeedMultiplier; + } + + public float getWishDirectionGravityX() { + return this.wishDirectionGravityX; + } + + public float getWishDirectionGravityY() { + return this.wishDirectionGravityY; + } + + public float getWishDirectionWeightX() { + return this.wishDirectionWeightX; + } + + public float getWishDirectionWeightY() { + return this.wishDirectionWeightY; + } + + public float getCollisionExpulsionForce() { + return this.collisionExpulsionForce; + } + + public float getForwardWalkSpeedMultiplier() { + return this.forwardWalkSpeedMultiplier; + } + + public float getBackwardWalkSpeedMultiplier() { + return this.backwardWalkSpeedMultiplier; + } + + public float getStrafeWalkSpeedMultiplier() { + return this.strafeWalkSpeedMultiplier; + } + + public float getForwardRunSpeedMultiplier() { + return this.forwardRunSpeedMultiplier; + } + + public float getBackwardRunSpeedMultiplier() { + return this.backwardRunSpeedMultiplier; + } + + public float getStrafeRunSpeedMultiplier() { + return this.strafeRunSpeedMultiplier; + } + + public float getForwardCrouchSpeedMultiplier() { + return this.forwardCrouchSpeedMultiplier; + } + + public float getBackwardCrouchSpeedMultiplier() { + return this.backwardCrouchSpeedMultiplier; + } + + public float getStrafeCrouchSpeedMultiplier() { + return this.strafeCrouchSpeedMultiplier; + } + + public float getForwardSprintSpeedMultiplier() { + return this.forwardSprintSpeedMultiplier; + } + + public float getVariableJumpFallForce() { + return this.variableJumpFallForce; + } + + public float getFallEffectDuration() { + return this.fallEffectDuration; + } + + public float getFallJumpForce() { + return this.fallJumpForce; + } + + public float getFallMomentumLoss() { + return this.fallMomentumLoss; + } + + public float getAutoJumpObstacleSpeedLoss() { + return this.autoJumpObstacleSpeedLoss; + } + + public float getAutoJumpObstacleSprintSpeedLoss() { + return this.autoJumpObstacleSprintSpeedLoss; + } + + public float getAutoJumpObstacleEffectDuration() { + return this.autoJumpObstacleEffectDuration; + } + + public float getAutoJumpObstacleSprintEffectDuration() { + return this.autoJumpObstacleSprintEffectDuration; + } + + public float getAutoJumpObstacleMaxAngle() { + return this.autoJumpObstacleMaxAngle; + } + + public boolean isAutoJumpDisableJumping() { + return this.autoJumpDisableJumping; + } + + public float getMinFallSpeedToEngageRoll() { + return this.minFallSpeedToEngageRoll; + } + + public float getMaxFallSpeedToEngageRoll() { + return this.maxFallSpeedToEngageRoll; + } + + public float getFallDamagePartialMitigationPercent() { + return this.fallDamagePartialMitigationPercent; + } + + public float getMaxFallSpeedRollFullMitigation() { + return this.maxFallSpeedRollFullMitigation; + } + + public float getRollStartSpeedModifier() { + return this.rollStartSpeedModifier; + } + + public float getRollExitSpeedModifier() { + return this.rollExitSpeedModifier; + } + + public float getRollTimeToComplete() { + return this.rollTimeToComplete; + } + + @Nonnull + public MovementSettings toPacket() { + MovementSettings packet = new MovementSettings(); + packet.velocityResistance = this.velocityResistance; + packet.jumpForce = this.jumpForce; + packet.swimJumpForce = this.swimJumpForce; + packet.jumpBufferDuration = this.jumpBufferDuration; + packet.jumpBufferMaxYVelocity = this.jumpBufferMaxYVelocity; + packet.acceleration = this.acceleration; + packet.airDragMin = this.airDragMin; + packet.airDragMax = this.airDragMax; + packet.airDragMinSpeed = this.airDragMinSpeed; + packet.airDragMaxSpeed = this.airDragMaxSpeed; + packet.airFrictionMin = this.airFrictionMin; + packet.airFrictionMax = this.airFrictionMax; + packet.airFrictionMinSpeed = this.airFrictionMinSpeed; + packet.airFrictionMaxSpeed = this.airFrictionMaxSpeed; + packet.airSpeedMultiplier = this.airSpeedMultiplier; + packet.airControlMinSpeed = this.airControlMinSpeed; + packet.airControlMaxSpeed = this.airControlMaxSpeed; + packet.airControlMinMultiplier = this.airControlMinMultiplier; + packet.airControlMaxMultiplier = this.airControlMaxMultiplier; + packet.comboAirSpeedMultiplier = this.airSpeedMultiplier; + packet.baseSpeed = this.baseSpeed; + packet.climbSpeed = this.climbSpeed; + packet.climbSpeedLateral = this.climbSpeedLateral; + packet.climbUpSprintSpeed = this.climbUpSprintSpeed; + packet.climbDownSprintSpeed = this.climbDownSprintSpeed; + packet.horizontalFlySpeed = this.horizontalFlySpeed; + packet.verticalFlySpeed = this.verticalFlySpeed; + packet.maxSpeedMultiplier = this.maxSpeedMultiplier; + packet.minSpeedMultiplier = this.minSpeedMultiplier; + packet.wishDirectionGravityX = this.wishDirectionGravityX; + packet.wishDirectionGravityY = this.wishDirectionGravityY; + packet.wishDirectionWeightX = this.wishDirectionWeightX; + packet.wishDirectionWeightY = this.wishDirectionWeightY; + packet.collisionExpulsionForce = this.collisionExpulsionForce; + packet.forwardWalkSpeedMultiplier = this.forwardWalkSpeedMultiplier; + packet.backwardWalkSpeedMultiplier = this.backwardWalkSpeedMultiplier; + packet.strafeWalkSpeedMultiplier = this.strafeWalkSpeedMultiplier; + packet.forwardRunSpeedMultiplier = this.forwardRunSpeedMultiplier; + packet.backwardRunSpeedMultiplier = this.backwardRunSpeedMultiplier; + packet.strafeRunSpeedMultiplier = this.strafeRunSpeedMultiplier; + packet.forwardCrouchSpeedMultiplier = this.forwardCrouchSpeedMultiplier; + packet.backwardCrouchSpeedMultiplier = this.backwardCrouchSpeedMultiplier; + packet.strafeCrouchSpeedMultiplier = this.strafeCrouchSpeedMultiplier; + packet.forwardSprintSpeedMultiplier = this.forwardSprintSpeedMultiplier; + packet.variableJumpFallForce = this.variableJumpFallForce; + packet.fallEffectDuration = this.fallEffectDuration; + packet.fallJumpForce = this.fallJumpForce; + packet.fallMomentumLoss = this.fallMomentumLoss; + packet.autoJumpObstacleSpeedLoss = this.autoJumpObstacleSpeedLoss; + packet.autoJumpObstacleSprintSpeedLoss = this.autoJumpObstacleSprintSpeedLoss; + packet.autoJumpObstacleEffectDuration = this.autoJumpObstacleEffectDuration; + packet.autoJumpObstacleSprintEffectDuration = this.autoJumpObstacleSprintEffectDuration; + packet.autoJumpObstacleMaxAngle = this.autoJumpObstacleMaxAngle; + packet.autoJumpDisableJumping = this.autoJumpDisableJumping; + packet.minSlideEntrySpeed = this.minSlideEntrySpeed; + packet.slideExitSpeed = this.slideExitSpeed; + packet.minFallSpeedToEngageRoll = this.minFallSpeedToEngageRoll; + packet.maxFallSpeedToEngageRoll = this.maxFallSpeedToEngageRoll; + packet.rollStartSpeedModifier = this.rollStartSpeedModifier; + packet.rollExitSpeedModifier = this.rollExitSpeedModifier; + packet.rollTimeToComplete = this.rollTimeToComplete; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "MovementConfig{id='" + + this.id + + "', velocityResistance=" + + this.velocityResistance + + ", jumpForce=" + + this.jumpForce + + ", swimJumpForce=" + + this.swimJumpForce + + ", jumpBufferDuration=" + + this.jumpBufferDuration + + ", jumpBufferMaxYVelocity=" + + this.jumpBufferMaxYVelocity + + ", acceleration=" + + this.acceleration + + ", airDragMin=" + + this.airDragMin + + ", airDragMax=" + + this.airDragMax + + ", airDragMinSpeed=" + + this.airDragMinSpeed + + ", airDragMaxSpeed=" + + this.airDragMaxSpeed + + ", airFrictionMin=" + + this.airFrictionMin + + ", airFrictionMax=" + + this.airFrictionMax + + ", airFrictionMinSpeed=" + + this.airFrictionMinSpeed + + ", airFrictionMaxSpeed=" + + this.airFrictionMaxSpeed + + ", airSpeedMultiplier=" + + this.airSpeedMultiplier + + ", airControlMinSpeed=" + + this.airControlMinSpeed + + ", airControlMaxSpeed=" + + this.airControlMaxSpeed + + ", airControlMinMultiplier=" + + this.airControlMinMultiplier + + ", airControlMaxMultiplier=" + + this.airControlMaxMultiplier + + ", comboAirSpeedMultiplier=" + + this.comboAirSpeedMultiplier + + ", baseSpeed=" + + this.baseSpeed + + ", climbSpeed=" + + this.climbSpeed + + ", climbSpeedLateral=" + + this.climbSpeedLateral + + ", climbUpSprintSpeed=" + + this.climbUpSprintSpeed + + ", climbDownSprintSpeed=" + + this.climbDownSprintSpeed + + ", horizontalFlySpeed=" + + this.horizontalFlySpeed + + ", verticalFlySpeed=" + + this.verticalFlySpeed + + ", maxSpeedMultiplier=" + + this.maxSpeedMultiplier + + ", minSpeedMultiplier=" + + this.minSpeedMultiplier + + ", wishDirectionGravityX=" + + this.wishDirectionGravityX + + ", wishDirectionGravityY=" + + this.wishDirectionGravityY + + ", wishDirectionWeightX=" + + this.wishDirectionWeightX + + ", wishDirectionWeightY=" + + this.wishDirectionWeightY + + ", collisionExpulsionForce=" + + this.collisionExpulsionForce + + ", forwardWalkSpeedMultiplier=" + + this.forwardWalkSpeedMultiplier + + ", backwardWalkSpeedMultiplier=" + + this.backwardWalkSpeedMultiplier + + ", strafeWalkSpeedMultiplier=" + + this.strafeWalkSpeedMultiplier + + ", forwardRunSpeedMultiplier=" + + this.forwardRunSpeedMultiplier + + ", backwardRunSpeedMultiplier=" + + this.backwardRunSpeedMultiplier + + ", strafeRunSpeedMultiplier=" + + this.strafeRunSpeedMultiplier + + ", forwardCrouchSpeedMultiplier=" + + this.forwardCrouchSpeedMultiplier + + ", backwardCrouchSpeedMultiplier=" + + this.backwardCrouchSpeedMultiplier + + ", strafeCrouchSpeedMultiplier=" + + this.strafeCrouchSpeedMultiplier + + ", forwardSprintSpeedMultiplier=" + + this.forwardSprintSpeedMultiplier + + ", variableJumpFallForce=" + + this.variableJumpFallForce + + ", fallEffectDuration=" + + this.fallEffectDuration + + ", fallJumpForce=" + + this.fallJumpForce + + ", fallMomentumLoss=" + + this.fallMomentumLoss + + ", autoJumpObstacleSpeedLoss=" + + this.autoJumpObstacleSpeedLoss + + ", autoJumpObstacleSprintSpeedLoss=" + + this.autoJumpObstacleSprintSpeedLoss + + ", autoJumpObstacleEffectDuration=" + + this.autoJumpObstacleEffectDuration + + ", autoJumpObstacleSprintEffectDuration=" + + this.autoJumpObstacleSprintEffectDuration + + ", autoJumpObstacleMaxAngle=" + + this.autoJumpObstacleMaxAngle + + ", autoJumpDisableJumping=" + + this.autoJumpDisableJumping + + ", minSlideEntrySpeed=" + + this.minSlideEntrySpeed + + ", slideExitSpeed=" + + this.slideExitSpeed + + ", minFallSpeedToEngageRoll=" + + this.minFallSpeedToEngageRoll + + ", maxFallSpeedToEngageRoll=" + + this.maxFallSpeedToEngageRoll + + ", fallDamagePartialMitigationPercent=" + + this.fallDamagePartialMitigationPercent + + ", maxFallSpeedRollFullMitigation=" + + this.maxFallSpeedRollFullMitigation + + ", rollStartSpeedModifier=" + + this.rollStartSpeedModifier + + ", rollExitSpeedModifier=" + + this.rollExitSpeedModifier + + ", rollTimeToComplete=" + + this.rollTimeToComplete + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/movement/MovementManager.java b/src/com/hypixel/hytale/server/core/entity/entities/player/movement/MovementManager.java new file mode 100644 index 0000000..be6d5b6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/movement/MovementManager.java @@ -0,0 +1,164 @@ +package com.hypixel.hytale.server.core.entity.entities.player.movement; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.MovementSettings; +import com.hypixel.hytale.protocol.packets.player.UpdateMovementSettings; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.physics.component.PhysicsValues; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.function.BiFunction; +import javax.annotation.Nonnull; + +public class MovementManager implements Component { + public static final BiFunction MASTER_DEFAULT = (physicsValues, gameMode) -> new MovementSettings() { + { + this.velocityResistance = 0.242F; + this.mass = (float)physicsValues.getMass(); + this.dragCoefficient = (float)physicsValues.getDragCoefficient(); + this.invertedGravity = physicsValues.isInvertedGravity(); + this.jumpForce = 11.8F; + this.swimJumpForce = 10.0F; + this.jumpBufferDuration = 0.3F; + this.jumpBufferMaxYVelocity = 3.0F; + this.acceleration = 0.1F; + this.airDragMin = 0.96F; + this.airDragMax = 0.995F; + this.airDragMinSpeed = 6.0F; + this.airDragMaxSpeed = 10.0F; + this.airFrictionMin = 0.02F; + this.airFrictionMax = 0.045F; + this.airFrictionMinSpeed = 6.0F; + this.airFrictionMaxSpeed = 10.0F; + this.airSpeedMultiplier = 1.0F; + this.airControlMinSpeed = 0.0F; + this.airControlMaxSpeed = 3.0F; + this.airControlMinMultiplier = 0.0F; + this.airControlMaxMultiplier = 3.13F; + this.comboAirSpeedMultiplier = 1.05F; + this.baseSpeed = 5.5F; + this.horizontalFlySpeed = 10.32F; + this.verticalFlySpeed = 10.32F; + this.climbSpeed = 0.035F; + this.climbSpeedLateral = 0.035F; + this.climbUpSprintSpeed = 0.045F; + this.climbDownSprintSpeed = 0.055F; + this.wishDirectionGravityX = 0.5F; + this.wishDirectionGravityY = 0.5F; + this.wishDirectionWeightX = 0.5F; + this.wishDirectionWeightY = 0.5F; + this.maxSpeedMultiplier = 1000.0F; + this.minSpeedMultiplier = 0.1F; + this.canFly = gameMode == GameMode.Creative; + this.collisionExpulsionForce = 0.04F; + this.forwardWalkSpeedMultiplier = 0.3F; + this.backwardWalkSpeedMultiplier = 0.3F; + this.strafeWalkSpeedMultiplier = 0.3F; + this.forwardRunSpeedMultiplier = 1.0F; + this.backwardRunSpeedMultiplier = 0.65F; + this.strafeRunSpeedMultiplier = 0.8F; + this.forwardCrouchSpeedMultiplier = 0.55F; + this.backwardCrouchSpeedMultiplier = 0.4F; + this.strafeCrouchSpeedMultiplier = 0.45F; + this.forwardSprintSpeedMultiplier = 1.65F; + this.variableJumpFallForce = 35.0F; + this.fallEffectDuration = 0.6F; + this.fallJumpForce = 7.0F; + this.fallMomentumLoss = 0.1F; + this.autoJumpObstacleEffectDuration = 0.2F; + this.autoJumpObstacleSpeedLoss = 0.95F; + this.autoJumpObstacleSprintSpeedLoss = 0.75F; + this.autoJumpObstacleSprintEffectDuration = 0.1F; + this.autoJumpObstacleMaxAngle = 45.0F; + this.autoJumpDisableJumping = true; + this.minSlideEntrySpeed = 8.5F; + this.slideExitSpeed = 2.5F; + this.minFallSpeedToEngageRoll = 21.0F; + this.maxFallSpeedToEngageRoll = 31.0F; + this.rollStartSpeedModifier = 2.5F; + this.rollExitSpeedModifier = 1.5F; + this.rollTimeToComplete = 0.9F; + } + }; + protected MovementSettings defaultSettings; + protected MovementSettings settings; + + public static ComponentType getComponentType() { + return EntityModule.get().getMovementManagerComponentType(); + } + + public MovementManager() { + } + + public MovementManager(@Nonnull MovementManager other) { + this(); + this.defaultSettings = new MovementSettings(other.defaultSettings); + this.settings = new MovementSettings(other.settings); + } + + public void resetDefaultsAndUpdate(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + this.refreshDefaultSettings(ref, componentAccessor); + this.applyDefaultSettings(); + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + this.update(playerRefComponent.getPacketHandler()); + } + + public void refreshDefaultSettings(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + World world = componentAccessor.getExternalData().getWorld(); + int movementConfigIndex = world.getGameplayConfig().getPlayerConfig().getMovementConfigIndex(); + MovementConfig movementConfig = (MovementConfig)((IndexedLookupTableAssetMap)MovementConfig.getAssetStore().getAssetMap()).getAsset(movementConfigIndex); + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + this.setDefaultSettings(movementConfig.toPacket(), EntityUtils.getPhysicsValues(ref, componentAccessor), playerComponent.getGameMode()); + } + + public void applyDefaultSettings() { + this.settings = new MovementSettings(this.defaultSettings); + } + + public void update(@Nonnull PacketHandler playerPacketHandler) { + playerPacketHandler.writeNoCache(new UpdateMovementSettings(this.getSettings())); + } + + public MovementSettings getSettings() { + return this.settings; + } + + public void setDefaultSettings(MovementSettings settings, @Nonnull PhysicsValues physicsValues, GameMode gameMode) { + this.defaultSettings = settings; + this.defaultSettings.mass = (float)physicsValues.getMass(); + this.defaultSettings.dragCoefficient = (float)physicsValues.getDragCoefficient(); + this.defaultSettings.invertedGravity = physicsValues.isInvertedGravity(); + this.defaultSettings.canFly = gameMode == GameMode.Creative; + } + + public MovementSettings getDefaultSettings() { + return this.defaultSettings; + } + + @Nonnull + @Override + public String toString() { + return "MovementManager{defaultSettings=" + this.defaultSettings + ", settings=" + this.settings + "}"; + } + + @Nonnull + @Override + public Component clone() { + return new MovementManager(this); + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/pages/BasicCustomUIPage.java b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/BasicCustomUIPage.java new file mode 100644 index 0000000..f42e6e6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/BasicCustomUIPage.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.core.entity.entities.player.pages; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public abstract class BasicCustomUIPage extends CustomUIPage { + public BasicCustomUIPage(@Nonnull PlayerRef playerRef, @Nonnull CustomPageLifetime lifetime) { + super(playerRef, lifetime); + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + this.build(commandBuilder); + } + + public abstract void build(UICommandBuilder var1); +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/pages/CustomUIPage.java b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/CustomUIPage.java new file mode 100644 index 0000000..0f9d700 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/CustomUIPage.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.server.core.entity.entities.player.pages; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.CustomPage; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class CustomUIPage { + @Nonnull + protected final PlayerRef playerRef; + @Nonnull + protected CustomPageLifetime lifetime; + + public CustomUIPage(@Nonnull PlayerRef playerRef, @Nonnull CustomPageLifetime lifetime) { + this.playerRef = playerRef; + this.lifetime = lifetime; + } + + public void setLifetime(@Nonnull CustomPageLifetime lifetime) { + this.lifetime = lifetime; + } + + @Nonnull + public CustomPageLifetime getLifetime() { + return this.lifetime; + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, String rawData) { + throw new UnsupportedOperationException("CustomUIPage doesn't support events! " + this + ": " + rawData); + } + + public abstract void build(@Nonnull Ref var1, @Nonnull UICommandBuilder var2, @Nonnull UIEventBuilder var3, @Nonnull Store var4); + + protected void rebuild() { + Ref ref = this.playerRef.getReference(); + if (ref != null) { + Store store = ref.getStore(); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.build(ref, commandBuilder, eventBuilder, ref.getStore()); + playerComponent.getPageManager() + .updateCustomPage(new CustomPage(this.getClass().getName(), false, true, this.lifetime, commandBuilder.getCommands(), eventBuilder.getEvents())); + } + } + + protected void sendUpdate() { + this.sendUpdate(null, false); + } + + protected void sendUpdate(@Nullable UICommandBuilder commandBuilder) { + this.sendUpdate(commandBuilder, false); + } + + protected void sendUpdate(@Nullable UICommandBuilder commandBuilder, boolean clear) { + Ref ref = this.playerRef.getReference(); + if (ref != null) { + Store store = ref.getStore(); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + playerComponent.getPageManager() + .updateCustomPage( + new CustomPage( + this.getClass().getName(), + false, + clear, + this.lifetime, + commandBuilder != null ? commandBuilder.getCommands() : UICommandBuilder.EMPTY_COMMAND_ARRAY, + UIEventBuilder.EMPTY_EVENT_BINDING_ARRAY + ) + ); + } + } + + protected void close() { + Ref ref = this.playerRef.getReference(); + if (ref != null) { + Store store = ref.getStore(); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + playerComponent.getPageManager().setPage(ref, store, Page.None); + } + } + + public void onDismiss(@Nonnull Ref ref, @Nonnull Store store) { + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/pages/InteractiveCustomUIPage.java b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/InteractiveCustomUIPage.java new file mode 100644 index 0000000..afcd52c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/InteractiveCustomUIPage.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.server.core.entity.entities.player.pages; + +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.packets.interface_.CustomPage; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.io.IOException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class InteractiveCustomUIPage extends CustomUIPage { + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + protected final BuilderCodec eventDataCodec; + + public InteractiveCustomUIPage(@Nonnull PlayerRef playerRef, @Nonnull CustomPageLifetime lifetime, @Nonnull BuilderCodec eventDataCodec) { + super(playerRef, lifetime); + this.eventDataCodec = eventDataCodec; + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull T data) { + } + + protected void sendUpdate(@Nullable UICommandBuilder commandBuilder, @Nullable UIEventBuilder eventBuilder, boolean clear) { + Ref ref = this.playerRef.getReference(); + if (ref != null) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + if (ref.isValid()) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getPageManager() + .updateCustomPage( + new CustomPage( + this.getClass().getName(), + false, + clear, + this.lifetime, + commandBuilder != null ? commandBuilder.getCommands() : UICommandBuilder.EMPTY_COMMAND_ARRAY, + eventBuilder != null ? eventBuilder.getEvents() : UIEventBuilder.EMPTY_EVENT_BINDING_ARRAY + ) + ); + } + } + ); + } + } + + @Override + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, String rawData) { + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + + T data; + try { + data = this.eventDataCodec.decodeJson(new RawJsonReader(rawData.toCharArray()), extraInfo); + } catch (IOException var7) { + throw new RuntimeException(var7); + } + + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER); + this.handleDataEvent(ref, store, data); + } + + @Override + protected void sendUpdate(@Nullable UICommandBuilder commandBuilder, boolean clear) { + this.sendUpdate(commandBuilder, null, clear); + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/pages/PageManager.java b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/PageManager.java new file mode 100644 index 0000000..f2ef04d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/PageManager.java @@ -0,0 +1,144 @@ +package com.hypixel.hytale.server.core.entity.entities.player.pages; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.CustomPage; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageEvent; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.protocol.packets.interface_.SetPage; +import com.hypixel.hytale.protocol.packets.window.OpenWindow; +import com.hypixel.hytale.server.core.entity.entities.player.windows.Window; +import com.hypixel.hytale.server.core.entity.entities.player.windows.WindowManager; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PageManager { + @Nullable + private WindowManager windowManager; + private PlayerRef playerRef; + @Nullable + private CustomUIPage customPage; + @Nonnull + private final AtomicInteger customPageRequiredAcknowledgments = new AtomicInteger(); + + public PageManager() { + } + + public void init(@Nonnull PlayerRef playerRef, @Nonnull WindowManager windowManager) { + this.windowManager = windowManager; + this.playerRef = playerRef; + } + + public void clearCustomPageAcknowledgements() { + this.customPageRequiredAcknowledgments.set(0); + } + + @Nullable + public CustomUIPage getCustomPage() { + return this.customPage; + } + + public void setPage(@Nonnull Ref ref, @Nonnull Store store, @Nonnull Page page) { + this.setPage(ref, store, page, false); + } + + public void setPage(@Nonnull Ref ref, @Nonnull Store store, @Nonnull Page page, boolean canCloseThroughInteraction) { + if (this.customPage != null) { + this.customPage.onDismiss(ref, store); + this.customPage = null; + this.customPageRequiredAcknowledgments.incrementAndGet(); + } + + this.playerRef.getPacketHandler().writeNoCache(new SetPage(page, canCloseThroughInteraction)); + } + + public void openCustomPage(@Nonnull Ref ref, @Nonnull Store store, @Nonnull CustomUIPage page) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + if (this.customPage != null) { + this.customPage.onDismiss(ref, ref.getStore()); + } + + page.build(ref, commandBuilder, eventBuilder, store); + this.updateCustomPage(new CustomPage(page.getClass().getName(), true, true, page.getLifetime(), commandBuilder.getCommands(), eventBuilder.getEvents())); + this.customPage = page; + } + + public boolean setPageWithWindows( + @Nonnull Ref ref, @Nonnull Store store, @Nonnull Page page, boolean canCloseThroughInteraction, @Nonnull Window... windows + ) { + if (this.windowManager == null) { + return false; + } else { + List windowPackets = this.windowManager.openWindows(windows); + if (windowPackets == null) { + return false; + } else { + this.setPage(ref, store, page, canCloseThroughInteraction); + + for (OpenWindow packet : windowPackets) { + this.playerRef.getPacketHandler().write(packet); + } + + return true; + } + } + } + + public boolean openCustomPageWithWindows( + @Nonnull Ref ref, @Nonnull Store store, @Nonnull CustomUIPage page, @Nonnull Window... windows + ) { + if (this.windowManager == null) { + return false; + } else { + List windowPackets = this.windowManager.openWindows(windows); + if (windowPackets == null) { + return false; + } else { + this.openCustomPage(ref, store, page); + + for (OpenWindow packet : windowPackets) { + this.playerRef.getPacketHandler().write(packet); + } + + return true; + } + } + } + + public void updateCustomPage(@Nonnull CustomPage page) { + this.customPageRequiredAcknowledgments.incrementAndGet(); + this.playerRef.getPacketHandler().write(page); + } + + public void handleEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull CustomPageEvent event) { + switch (event.type) { + case Dismiss: + if (this.customPage == null) { + return; + } + + this.customPage.onDismiss(ref, store); + this.customPage = null; + break; + case Data: + if (this.customPageRequiredAcknowledgments.get() != 0 || this.customPage == null) { + return; + } + + this.customPage.handleDataEvent(ref, store, event.data); + break; + case Acknowledge: + if (this.customPageRequiredAcknowledgments.decrementAndGet() < 0) { + this.customPageRequiredAcknowledgments.incrementAndGet(); + throw new IllegalArgumentException("Client sent unexpected acknowledgement"); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/pages/RespawnPage.java b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/RespawnPage.java new file mode 100644 index 0000000..a757e44 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/RespawnPage.java @@ -0,0 +1,166 @@ +package com.hypixel.hytale.server.core.entity.entities.player.pages; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.gameplay.DeathConfig; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathItemLoss; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.HashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RespawnPage extends InteractiveCustomUIPage { + @Nonnull + private static final String UI_RESPAWN_PAGE = "Pages/RespawnPage.ui"; + @Nonnull + private static final String ELEMENT_BUTTON_RESPAWN = "#RespawnButton"; + @Nonnull + private static final String ELEMENT_LABEL_DEATH_REASON_TEXT_SPANS = "#DeathReason.TextSpans"; + @Nullable + private final Message deathReason; + private final boolean displayDataOnDeathScreen; + private final DeathItemLoss deathItemLoss; + private final ItemStack[] itemsLostOnDeath; + + public RespawnPage(@Nonnull PlayerRef playerRef, @Nullable Message deathReason, boolean displayDataOnDeathScreen, DeathItemLoss deathItemLoss) { + super(playerRef, CustomPageLifetime.CantClose, RespawnPage.RespawnPageEventData.CODEC); + this.deathReason = deathReason; + this.displayDataOnDeathScreen = displayDataOnDeathScreen; + this.deathItemLoss = deathItemLoss; + this.itemsLostOnDeath = combineSimilarItemStacks(deathItemLoss.getItemsLost()); + } + + @Nullable + private static ItemStack[] combineSimilarItemStacks(@Nullable ItemStack[] itemsLostOnDeath) { + if (itemsLostOnDeath == null) { + return null; + } else { + ArrayList singleItemStacks = new ArrayList<>(); + HashMap combinedItemStacks = new HashMap<>(); + + for (ItemStack itemStack : itemsLostOnDeath) { + if (itemStack.getItem().getMaxStack() <= 1) { + singleItemStacks.add(itemStack); + } else { + String itemId = itemStack.getItemId(); + int quantity = itemStack.getQuantity(); + ItemStack combinedItemStack = combinedItemStacks.get(itemId); + if (combinedItemStack != null) { + quantity += combinedItemStack.getQuantity(); + } + + combinedItemStacks.put(itemId, itemStack.withQuantity(quantity)); + } + } + + singleItemStacks.addAll(combinedItemStacks.values()); + return singleItemStacks.toArray(ItemStack[]::new); + } + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/RespawnPage.ui"); + commandBuilder.set("#DeathReason.TextSpans", this.deathReason != null ? this.deathReason : Message.empty()); + if (!this.displayDataOnDeathScreen) { + commandBuilder.set("#DeathData.Visible", false); + } else { + DecimalFormat decimalFormat = new DecimalFormat("#.##"); + decimalFormat.setDecimalSeparatorAlwaysShown(false); + if (this.deathItemLoss.getLossMode() == DeathConfig.ItemsLossMode.NONE) { + commandBuilder.set("#ItemsLossStatus.TextSpans", Message.translation("server.general.itemsLossPrevented")); + } else { + commandBuilder.set( + "#ItemsLossStatus.TextSpans", + Message.translation("server.general.itemsDurabilityLoss") + .param("percentage", decimalFormat.format(this.deathItemLoss.getDurabilityLossPercentage())) + ); + } + + if (this.itemsLostOnDeath != null && this.itemsLostOnDeath.length != 0) { + commandBuilder.set("#ItemsAmountLoss.TextSpans", Message.translation("server.general.itemsAmountLoss")); + commandBuilder.set("#DroppedItemsContainer.Visible", true); + commandBuilder.clear("#DroppedItemsContainer"); + + for (int i = 0; i < this.itemsLostOnDeath.length; i++) { + ItemStack itemStack = this.itemsLostOnDeath[i]; + String itemSelector = "#DroppedItemsContainer[" + i + "] "; + commandBuilder.append("#DroppedItemsContainer", "Pages/DroppedItemSlot.ui"); + commandBuilder.set(itemSelector + "#ItemIcon.ItemId", itemStack.getItemId()); + commandBuilder.set(itemSelector + "#ItemIcon.Quantity", itemStack.getQuantity()); + if (itemStack.getQuantity() > 1) { + commandBuilder.set(itemSelector + "#QuantityLabel.Text", String.valueOf(itemStack.getQuantity())); + } else { + commandBuilder.set(itemSelector + "#QuantityLabel.Visible", false); + } + } + } else { + commandBuilder.set("#ItemsAmountLoss.Visible", false); + commandBuilder.set("#DroppedItemsContainer.Visible", false); + } + } + + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#RespawnButton", EventData.of("Action", "Respawn")); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull RespawnPage.RespawnPageEventData data) { + if ("Respawn".equals(data.action)) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getPageManager().setPage(ref, store, Page.None); + } + } + + @Override + public void onDismiss(@Nonnull Ref ref, @Nonnull Store store) { + super.onDismiss(ref, store); + boolean isDead = store.getArchetype(ref).contains(DeathComponent.getComponentType()); + if (isDead) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + store.tryRemoveComponent(ref, DeathComponent.getComponentType()); + } else { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerRefComponent.getPacketHandler().disconnect("Attempted to respawn while alive!"); + } + } + + public static class RespawnPageEventData { + static final String KEY_ACTION = "Action"; + static final String ACTION_RESPAWN = "Respawn"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + RespawnPage.RespawnPageEventData.class, RespawnPage.RespawnPageEventData::new + ) + .addField(new KeyedCodec<>("Action", Codec.STRING), (entry, s) -> entry.action = s, entry -> entry.action) + .build(); + private String action; + + public RespawnPageEventData() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/pages/audio/PlaySoundPage.java b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/audio/PlaySoundPage.java new file mode 100644 index 0000000..a23c60b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/audio/PlaySoundPage.java @@ -0,0 +1,186 @@ +package com.hypixel.hytale.server.core.entity.entities.player.pages.audio; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.AudioUtil; +import com.hypixel.hytale.common.util.StringCompareUtil; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.ui.Value; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlaySoundPage extends InteractiveCustomUIPage { + private static final String COMMON_TEXT_BUTTON_DOCUMENT = "Common/TextButton.ui"; + private static final Value BUTTON_LABEL_STYLE = Value.ref("Common/TextButton.ui", "LabelStyle"); + private static final Value BUTTON_LABEL_STYLE_SELECTED = Value.ref("Common/TextButton.ui", "SelectedLabelStyle"); + @Nonnull + private String searchQuery = ""; + private List soundEvents; + @Nullable + private String selectedSoundEvent; + private float volumeDecibels = 0.0F; + private float pitchSemitones = 0.0F; + + public PlaySoundPage(@Nonnull PlayerRef playerRef) { + super(playerRef, CustomPageLifetime.CanDismiss, PlaySoundPage.PlaySoundPageEventData.CODEC); + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/PlaySoundPage.ui"); + eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#SearchInput", EventData.of("@SearchQuery", "#SearchInput.Value"), false); + eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#VolumeSlider", EventData.of("@Volume", "#VolumeSlider.Value"), false); + eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#PitchSlider", EventData.of("@Pitch", "#PitchSlider.Value"), false); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#Play", new EventData().append("Type", "Play"), false); + this.buildSoundEventList(ref, commandBuilder, eventBuilder, store); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull PlaySoundPage.PlaySoundPageEventData data) { + if (data.searchQuery != null) { + this.searchQuery = data.searchQuery.trim().toLowerCase(); + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + this.buildSoundEventList(ref, commandBuilder, eventBuilder, store); + this.sendUpdate(commandBuilder, eventBuilder, false); + } else if (data.volume != null) { + this.volumeDecibels = data.volume; + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#VolumeValue.Text", String.valueOf((int)this.volumeDecibels)); + this.sendUpdate(commandBuilder, null, false); + } else if (data.pitch != null) { + this.pitchSemitones = data.pitch; + UICommandBuilder commandBuilder = new UICommandBuilder(); + commandBuilder.set("#PitchValue.Text", String.valueOf(this.pitchSemitones)); + this.sendUpdate(commandBuilder, null, false); + } else { + String var11 = data.type; + switch (var11) { + case "Select": + if (data.soundEvent != null) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + this.selectSoundEvent(ref, data.soundEvent, commandBuilder, store); + this.sendUpdate(commandBuilder, null, false); + } + break; + case "Play": + if (this.selectedSoundEvent != null) { + int index = SoundEvent.getAssetMap().getIndex(this.selectedSoundEvent); + float linearVolume = AudioUtil.decibelsToLinearGain(this.volumeDecibels); + float linearPitch = AudioUtil.semitonesToLinearPitch(this.pitchSemitones); + SoundUtil.playSoundEvent2d(ref, index, SoundCategory.SFX, linearVolume, linearPitch, store); + } + } + } + } + + private void buildSoundEventList( + @Nonnull Ref ref, + @Nonnull UICommandBuilder commandBuilder, + @Nonnull UIEventBuilder eventBuilder, + @Nonnull ComponentAccessor componentAccessor + ) { + commandBuilder.clear("#SoundList"); + int soundEventCount = SoundEvent.getAssetMap().getAssetMap().size(); + if (!this.searchQuery.isEmpty()) { + Object2IntMap map = new Object2IntOpenHashMap<>(soundEventCount); + + for (String value : SoundEvent.getAssetMap().getAssetMap().keySet()) { + int fuzzyDistance = StringCompareUtil.getFuzzyDistance(value, this.searchQuery, Locale.ENGLISH); + if (fuzzyDistance > 0) { + map.put(value, fuzzyDistance); + } + } + + this.soundEvents = map.keySet().stream().sorted().sorted(Comparator.comparingInt(map::getInt).reversed()).limit(20L).collect(Collectors.toList()); + } else { + this.soundEvents = SoundEvent.getAssetMap().getAssetMap().keySet().stream().sorted(String::compareTo).collect(Collectors.toList()); + } + + int i = 0; + + for (int bound = this.soundEvents.size(); i < bound; i++) { + String id = this.soundEvents.get(i); + String selector = "#SoundList[" + i + "]"; + commandBuilder.append("#SoundList", "Common/TextButton.ui"); + commandBuilder.set(selector + " #Button.Text", id); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, selector + " #Button", new EventData().append("Type", "Select").append("SoundEvent", id), false + ); + } + + if (!this.soundEvents.isEmpty()) { + if (!this.soundEvents.contains(this.selectedSoundEvent)) { + this.selectSoundEvent(ref, this.soundEvents.getFirst(), commandBuilder, componentAccessor); + } else if (this.selectedSoundEvent != null) { + this.selectSoundEvent(ref, this.selectedSoundEvent, commandBuilder, componentAccessor); + } + } + } + + private void selectSoundEvent( + @Nonnull Ref ref, + @Nonnull String soundEvent, + @Nonnull UICommandBuilder commandBuilder, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.selectedSoundEvent != null && this.soundEvents.contains(this.selectedSoundEvent)) { + commandBuilder.set("#SoundList[" + this.soundEvents.indexOf(this.selectedSoundEvent) + "] #Button.Style", BUTTON_LABEL_STYLE); + } + + commandBuilder.set("#SoundList[" + this.soundEvents.indexOf(soundEvent) + "] #Button.Style", BUTTON_LABEL_STYLE_SELECTED); + commandBuilder.set("#SoundName.Text", soundEvent); + this.selectedSoundEvent = soundEvent; + } + + public static class PlaySoundPageEventData { + static final String KEY_SOUND_EVENT = "SoundEvent"; + static final String KEY_TYPE = "Type"; + static final String KEY_SEARCH_QUERY = "@SearchQuery"; + static final String KEY_VOLUME = "@Volume"; + static final String KEY_PITCH = "@Pitch"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + PlaySoundPage.PlaySoundPageEventData.class, PlaySoundPage.PlaySoundPageEventData::new + ) + .append(new KeyedCodec<>("SoundEvent", Codec.STRING), (entry, s) -> entry.soundEvent = s, entry -> entry.soundEvent) + .add() + .append(new KeyedCodec<>("Type", Codec.STRING), (entry, s) -> entry.type = s, entry -> entry.type) + .add() + .append(new KeyedCodec<>("@SearchQuery", Codec.STRING), (entry, s) -> entry.searchQuery = s, entry -> entry.searchQuery) + .add() + .append(new KeyedCodec<>("@Volume", Codec.FLOAT), (entry, f) -> entry.volume = f, entry -> entry.volume) + .add() + .append(new KeyedCodec<>("@Pitch", Codec.FLOAT), (entry, f) -> entry.pitch = f, entry -> entry.pitch) + .add() + .build(); + private String soundEvent; + private String type; + private String searchQuery; + private Float volume; + private Float pitch; + + public PlaySoundPageEventData() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/pages/choices/ChoiceBasePage.java b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/choices/ChoiceBasePage.java new file mode 100644 index 0000000..4cf4f4e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/choices/ChoiceBasePage.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.server.core.entity.entities.player.pages.choices; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public abstract class ChoiceBasePage extends InteractiveCustomUIPage { + private final ChoiceElement[] elements; + private final String pageLayout; + + public ChoiceBasePage(@Nonnull PlayerRef playerRef, ChoiceElement[] elements, String pageLayout) { + super(playerRef, CustomPageLifetime.CanDismiss, ChoiceBasePage.ChoicePageEventData.CODEC); + this.elements = elements; + this.pageLayout = pageLayout; + } + + protected ChoiceElement[] getElements() { + return this.elements; + } + + protected String getPageLayout() { + return this.pageLayout; + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append(this.pageLayout); + commandBuilder.clear("#ElementList"); + if (this.elements != null && this.elements.length != 0) { + for (int i = 0; i < this.elements.length; i++) { + String selector = "#ElementList[" + i + "]"; + ChoiceElement element = this.elements[i]; + element.addButton(commandBuilder, eventBuilder, selector, this.playerRef); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, selector, EventData.of("Index", Integer.toString(i)), false); + } + } + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull ChoiceBasePage.ChoicePageEventData data) { + ChoiceElement element = this.elements[data.getIndex()]; + if (element.canFulfillRequirements(store, ref, this.playerRef)) { + ChoiceInteraction[] interactions = element.getInteractions(); + + for (ChoiceInteraction interaction : interactions) { + interaction.run(store, ref, this.playerRef); + } + } + } + + public static class ChoicePageEventData { + static final String ELEMENT_INDEX = "Index"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + ChoiceBasePage.ChoicePageEventData.class, ChoiceBasePage.ChoicePageEventData::new + ) + .append(new KeyedCodec<>("Index", Codec.STRING), (choicePageEventData, s) -> { + choicePageEventData.indexStr = s; + choicePageEventData.index = Integer.parseInt(s); + }, choicePageEventData -> choicePageEventData.indexStr) + .add() + .build(); + private String indexStr; + private int index; + + public ChoicePageEventData() { + } + + public int getIndex() { + return this.index; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/pages/choices/ChoiceElement.java b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/choices/ChoiceElement.java new file mode 100644 index 0000000..03ebe60 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/choices/ChoiceElement.java @@ -0,0 +1,109 @@ +package com.hypixel.hytale.server.core.entity.entities.player.pages.choices; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public abstract class ChoiceElement { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(ChoiceElement.class) + .append( + new KeyedCodec<>("DisplayNameKey", Codec.STRING), + (choiceElement, s) -> choiceElement.displayNameKey = s, + choiceElement -> choiceElement.displayNameKey + ) + .addValidator(Validators.nonEmptyString()) + .add() + .append( + new KeyedCodec<>("DescriptionKey", Codec.STRING), + (choiceElement, s) -> choiceElement.descriptionKey = s, + choiceElement -> choiceElement.descriptionKey + ) + .addValidator(Validators.nonEmptyString()) + .add() + .append( + new KeyedCodec<>("Interactions", new ArrayCodec<>(ChoiceInteraction.CODEC, ChoiceInteraction[]::new)), + (choiceElement, choiceInteractions) -> choiceElement.interactions = choiceInteractions, + choiceElement -> choiceElement.interactions + ) + .addValidator(Validators.nonEmptyArray()) + .add() + .append( + new KeyedCodec<>("Requirements", new ArrayCodec<>(ChoiceRequirement.CODEC, ChoiceRequirement[]::new)), + (choiceElement, choiceRequirements) -> choiceElement.requirements = choiceRequirements, + choiceElement -> choiceElement.requirements + ) + .add() + .build(); + protected String displayNameKey; + protected String descriptionKey; + protected ChoiceInteraction[] interactions; + protected ChoiceRequirement[] requirements; + + public ChoiceElement(String displayNameKey, String descriptionKey, ChoiceInteraction[] interactions, ChoiceRequirement[] requirements) { + this.displayNameKey = displayNameKey; + this.descriptionKey = descriptionKey; + this.interactions = interactions; + this.requirements = requirements; + } + + protected ChoiceElement() { + } + + public String getDisplayNameKey() { + return this.displayNameKey; + } + + public String getDescriptionKey() { + return this.descriptionKey; + } + + public ChoiceInteraction[] getInteractions() { + return this.interactions; + } + + public ChoiceRequirement[] getRequirements() { + return this.requirements; + } + + public abstract void addButton(UICommandBuilder var1, UIEventBuilder var2, String var3, PlayerRef var4); + + public boolean canFulfillRequirements(@Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef) { + if (this.requirements == null) { + return true; + } else { + for (ChoiceRequirement requirement : this.requirements) { + if (!requirement.canFulfillRequirement(store, ref, playerRef)) { + return false; + } + } + + return true; + } + } + + @Nonnull + @Override + public String toString() { + return "ChoiceElement{displayNameKey='" + + this.displayNameKey + + "', descriptionKey='" + + this.descriptionKey + + "', interactions=" + + Arrays.toString((Object[])this.interactions) + + ", requirements=" + + Arrays.toString((Object[])this.requirements) + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/pages/choices/ChoiceInteraction.java b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/choices/ChoiceInteraction.java new file mode 100644 index 0000000..80b0485 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/choices/ChoiceInteraction.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.core.entity.entities.player.pages.choices; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public abstract class ChoiceInteraction { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(ChoiceInteraction.class).build(); + + protected ChoiceInteraction() { + } + + public abstract void run(@Nonnull Store var1, @Nonnull Ref var2, @Nonnull PlayerRef var3); + + @Nonnull + @Override + public String toString() { + return "ChoiceInteraction{}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/pages/choices/ChoiceRequirement.java b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/choices/ChoiceRequirement.java new file mode 100644 index 0000000..888a7f9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/choices/ChoiceRequirement.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.core.entity.entities.player.pages.choices; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public abstract class ChoiceRequirement { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(ChoiceRequirement.class).build(); + + protected ChoiceRequirement() { + } + + public abstract boolean canFulfillRequirement(@Nonnull Store var1, @Nonnull Ref var2, @Nonnull PlayerRef var3); + + @Nonnull + @Override + public String toString() { + return "ChoiceRequirement{}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/pages/itemrepair/ItemRepairElement.java b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/itemrepair/ItemRepairElement.java new file mode 100644 index 0000000..1b67970 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/itemrepair/ItemRepairElement.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.entity.entities.player.pages.itemrepair; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceElement; +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceInteraction; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import javax.annotation.Nonnull; + +public class ItemRepairElement extends ChoiceElement { + protected ItemStack itemStack; + + public ItemRepairElement(ItemStack itemStack, RepairItemInteraction interaction) { + this.itemStack = itemStack; + this.interactions = new ChoiceInteraction[]{interaction}; + } + + @Override + public void addButton(@Nonnull UICommandBuilder commandBuilder, UIEventBuilder eventBuilder, String selector, PlayerRef playerRef) { + int durabilityPercentage = (int)Math.round(this.itemStack.getDurability() / this.itemStack.getMaxDurability() * 100.0); + commandBuilder.append("#ElementList", "Pages/ItemRepairElement.ui"); + commandBuilder.set(selector + " #Icon.ItemId", this.itemStack.getItemId().toString()); + commandBuilder.set(selector + " #Name.TextSpans", Message.translation(this.itemStack.getItem().getTranslationKey())); + commandBuilder.set(selector + " #Durability.Text", durabilityPercentage + "%"); + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/pages/itemrepair/ItemRepairPage.java b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/itemrepair/ItemRepairPage.java new file mode 100644 index 0000000..0009554 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/itemrepair/ItemRepairPage.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.server.core.entity.entities.player.pages.itemrepair; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceBasePage; +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceElement; +import com.hypixel.hytale.server.core.inventory.ItemContext; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class ItemRepairPage extends ChoiceBasePage { + public ItemRepairPage(@Nonnull PlayerRef playerRef, @Nonnull ItemContainer itemContainer, double repairPenalty, ItemContext heldItemContext) { + super(playerRef, getItemElements(itemContainer, repairPenalty, heldItemContext), "Pages/ItemRepairPage.ui"); + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + if (this.getElements().length > 0) { + super.build(ref, commandBuilder, eventBuilder, store); + } else { + commandBuilder.append(this.getPageLayout()); + commandBuilder.clear("#ElementList"); + commandBuilder.appendInline("#ElementList", "Label { Text: %customUI.itemRepairPage.noItems; Style: (Alignment: Center); }"); + } + } + + @Nonnull + protected static ChoiceElement[] getItemElements(@Nonnull ItemContainer itemContainer, double repairPenalty, ItemContext heldItemContext) { + List elements = new ObjectArrayList<>(); + + for (short slot = 0; slot < itemContainer.getCapacity(); slot++) { + ItemStack itemStack = itemContainer.getItemStack(slot); + if (!ItemStack.isEmpty(itemStack) && !itemStack.isUnbreakable() && !(itemStack.getDurability() >= itemStack.getMaxDurability())) { + ItemContext itemContext = new ItemContext(itemContainer, slot, itemStack); + elements.add(new ItemRepairElement(itemStack, new RepairItemInteraction(itemContext, repairPenalty, heldItemContext))); + } + } + + return elements.toArray(ChoiceElement[]::new); + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/pages/itemrepair/RepairItemInteraction.java b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/itemrepair/RepairItemInteraction.java new file mode 100644 index 0000000..dffaf3a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/pages/itemrepair/RepairItemInteraction.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.server.core.entity.entities.player.pages.itemrepair; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.PageManager; +import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceInteraction; +import com.hypixel.hytale.server.core.inventory.ItemContext; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TempAssetIdUtil; +import javax.annotation.Nonnull; + +public class RepairItemInteraction extends ChoiceInteraction { + protected final ItemContext itemContext; + protected final double repairPenalty; + protected final ItemContext heldItemContext; + + public RepairItemInteraction(ItemContext itemContext, double repairPenalty, ItemContext heldItemContext) { + this.itemContext = itemContext; + this.repairPenalty = repairPenalty; + this.heldItemContext = heldItemContext; + } + + @Override + public void run(@Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + PageManager pageManager = playerComponent.getPageManager(); + ItemStack itemStack = this.itemContext.getItemStack(); + double itemStackDurability = itemStack.getDurability(); + double itemStackMaxDurability = itemStack.getMaxDurability(); + double ratioAmountRepaired = 1.0 - itemStackDurability / itemStackMaxDurability; + double newMaxDurability = MathUtil.floor(itemStackMaxDurability - itemStack.getItem().getMaxDurability() * (this.repairPenalty * ratioAmountRepaired)); + if (itemStackDurability >= newMaxDurability) { + playerRef.sendMessage(Message.translation("server.general.repair.penaltyTooBig").color("#ff5555")); + pageManager.setPage(ref, store, Page.None); + } else { + if (newMaxDurability <= 10.0) { + newMaxDurability = 10.0; + playerRef.sendMessage(Message.translation("server.general.repair.tooLowDurability").color("#ff5555")); + } + + ItemContainer heldItemContainer = this.heldItemContext.getContainer(); + short heldItemSlot = this.heldItemContext.getSlot(); + ItemStack heldItemStack = this.heldItemContext.getItemStack(); + ItemStackSlotTransaction slotTransaction = heldItemContainer.removeItemStackFromSlot(heldItemSlot, heldItemStack, 1); + if (!slotTransaction.succeeded()) { + pageManager.setPage(ref, store, Page.None); + } else { + ItemStack newItemStack = itemStack.withRestoredDurability(newMaxDurability); + ItemStackSlotTransaction replaceTransaction = this.itemContext + .getContainer() + .replaceItemStackInSlot(this.itemContext.getSlot(), itemStack, newItemStack); + if (!replaceTransaction.succeeded()) { + SimpleItemContainer.addOrDropItemStack(store, ref, heldItemContainer, heldItemSlot, heldItemStack.withQuantity(1)); + pageManager.setPage(ref, store, Page.None); + } else { + Message newItemStackMessage = Message.translation(newItemStack.getItem().getTranslationKey()); + playerRef.sendMessage(Message.translation("server.general.repair.successful").param("itemName", newItemStackMessage)); + pageManager.setPage(ref, store, Page.None); + SoundUtil.playSoundEvent2d(ref, TempAssetIdUtil.getSoundEventIndex("SFX_Item_Repair"), SoundCategory.UI, store); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/windows/BlockWindow.java b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/BlockWindow.java new file mode 100644 index 0000000..a740b0d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/BlockWindow.java @@ -0,0 +1,98 @@ +package com.hypixel.hytale.server.core.entity.entities.player.windows; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.protocol.packets.window.WindowType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public abstract class BlockWindow extends Window implements ValidatedWindow { + private static final float MAX_DISTANCE = 7.0F; + protected final int x; + protected final int y; + protected final int z; + @Nonnull + protected BlockType blockType; + protected final int rotationIndex; + private double maxDistance = 7.0; + private double maxDistanceSqr = this.maxDistance * this.maxDistance; + + public BlockWindow(@Nonnull WindowType windowType, int x, int y, int z, int rotationIndex, @Nonnull BlockType blockType) { + super(windowType); + this.x = x; + this.y = y; + this.z = z; + this.rotationIndex = rotationIndex; + this.blockType = blockType; + } + + public int getX() { + return this.x; + } + + public int getY() { + return this.y; + } + + public int getZ() { + return this.z; + } + + public int getRotationIndex() { + return this.rotationIndex; + } + + @Nonnull + public BlockType getBlockType() { + return this.blockType; + } + + public void setMaxDistance(double maxDistance) { + this.maxDistance = maxDistance; + this.maxDistanceSqr = maxDistance * maxDistance; + } + + public double getMaxDistance() { + return this.maxDistance; + } + + @Override + public boolean validate() { + PlayerRef playerRef = this.getPlayerRef(); + if (playerRef == null) { + return false; + } else { + Ref ref = playerRef.getReference(); + if (ref == null) { + return false; + } else { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + if (transformComponent.getPosition().distanceSquaredTo(this.x, this.y, this.z) > this.maxDistanceSqr) { + return false; + } else { + WorldChunk worldChunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(this.x, this.z)); + if (worldChunk == null) { + return false; + } else { + BlockType currentBlockType = worldChunk.getBlockType(this.x, this.y, this.z); + if (currentBlockType == null) { + return false; + } else { + Item currentItem = currentBlockType.getItem(); + return currentItem == null ? false : currentItem.equals(this.blockType.getItem()); + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/windows/ContainerBlockWindow.java b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/ContainerBlockWindow.java new file mode 100644 index 0000000..2711d63 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/ContainerBlockWindow.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.server.core.entity.entities.player.windows; + +import com.google.gson.JsonObject; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.window.SortItemsAction; +import com.hypixel.hytale.protocol.packets.window.WindowAction; +import com.hypixel.hytale.protocol.packets.window.WindowType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.container.SortType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ContainerBlockWindow extends BlockWindow implements ItemContainerWindow { + @Nonnull + private final JsonObject windowData; + @Nonnull + private final ItemContainer itemContainer; + + public ContainerBlockWindow(int x, int y, int z, int rotationIndex, @Nonnull BlockType blockType, @Nonnull ItemContainer itemContainer) { + super(WindowType.Container, x, y, z, rotationIndex, blockType); + this.itemContainer = itemContainer; + this.windowData = new JsonObject(); + Item item = blockType.getItem(); + this.windowData.addProperty("blockItemId", item.getId()); + } + + @Nonnull + @Override + public JsonObject getData() { + return this.windowData; + } + + @Override + public boolean onOpen0() { + return true; + } + + @Override + public void onClose0() { + } + + @Nonnull + @Override + public ItemContainer getItemContainer() { + return this.itemContainer; + } + + @Override + public void handleAction(@Nonnull Ref ref, @Nonnull Store store, @Nonnull WindowAction action) { + if (action instanceof SortItemsAction sortAction) { + SortType sortType = SortType.fromPacket(sortAction.sortType); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getInventory().setSortType(sortType); + this.itemContainer.sortItems(sortType); + this.invalidate(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/windows/ContainerWindow.java b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/ContainerWindow.java new file mode 100644 index 0000000..f2ba8f1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/ContainerWindow.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.entity.entities.player.windows; + +import com.google.gson.JsonObject; +import com.hypixel.hytale.protocol.packets.window.WindowType; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import javax.annotation.Nonnull; + +public class ContainerWindow extends Window implements ItemContainerWindow { + @Nonnull + private final JsonObject windowData; + @Nonnull + private final ItemContainer itemContainer; + + public ContainerWindow(@Nonnull ItemContainer itemContainer) { + super(WindowType.Container); + this.itemContainer = itemContainer; + this.windowData = new JsonObject(); + } + + @Nonnull + @Override + public JsonObject getData() { + return this.windowData; + } + + @Override + public boolean onOpen0() { + return true; + } + + @Override + public void onClose0() { + } + + @Nonnull + @Override + public ItemContainer getItemContainer() { + return this.itemContainer; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/windows/ItemContainerWindow.java b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/ItemContainerWindow.java new file mode 100644 index 0000000..3f978a7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/ItemContainerWindow.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.server.core.entity.entities.player.windows; + +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import javax.annotation.Nonnull; + +public interface ItemContainerWindow { + @Nonnull + ItemContainer getItemContainer(); +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/windows/ItemStackContainerWindow.java b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/ItemStackContainerWindow.java new file mode 100644 index 0000000..2470889 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/ItemStackContainerWindow.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.core.entity.entities.player.windows; + +import com.google.gson.JsonObject; +import com.hypixel.hytale.event.EventRegistration; +import com.hypixel.hytale.protocol.packets.window.WindowType; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.container.ItemStackItemContainer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemStackContainerWindow extends Window implements ItemContainerWindow { + @Nonnull + private final JsonObject windowData = new JsonObject(); + @Nonnull + private final ItemStackItemContainer itemStackItemContainer; + @Nullable + private EventRegistration eventRegistration; + + public ItemStackContainerWindow(@Nonnull ItemStackItemContainer itemStackItemContainer) { + super(WindowType.Container); + this.itemStackItemContainer = itemStackItemContainer; + } + + @Nonnull + @Override + public JsonObject getData() { + return this.windowData; + } + + @Override + public boolean onOpen0() { + this.eventRegistration = this.itemStackItemContainer.getParentContainer().registerChangeEvent(event -> { + if (!this.itemStackItemContainer.isItemStackValid()) { + this.close(); + } + }); + return true; + } + + @Override + public void onClose0() { + this.eventRegistration.unregister(); + this.eventRegistration = null; + } + + @Nonnull + @Override + public ItemContainer getItemContainer() { + return this.itemStackItemContainer; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/windows/MaterialContainerWindow.java b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/MaterialContainerWindow.java new file mode 100644 index 0000000..4817ae8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/MaterialContainerWindow.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.core.entity.entities.player.windows; + +import javax.annotation.Nonnull; + +public interface MaterialContainerWindow { + @Nonnull + MaterialExtraResourcesSection getExtraResourcesSection(); + + void invalidateExtraResources(); + + boolean isValid(); +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/windows/MaterialExtraResourcesSection.java b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/MaterialExtraResourcesSection.java new file mode 100644 index 0000000..24274ef --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/MaterialExtraResourcesSection.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.entity.entities.player.windows; + +import com.hypixel.hytale.protocol.ExtraResources; +import com.hypixel.hytale.protocol.ItemQuantity; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; + +public class MaterialExtraResourcesSection { + private boolean valid; + private ItemContainer itemContainer; + private ItemQuantity[] extraMaterials; + + public MaterialExtraResourcesSection() { + } + + public void setExtraMaterials(ItemQuantity[] extraMaterials) { + this.extraMaterials = extraMaterials; + } + + public boolean isValid() { + return this.valid; + } + + public void setValid(boolean valid) { + this.valid = valid; + } + + public ExtraResources toPacket() { + ExtraResources packet = new ExtraResources(); + packet.resources = this.extraMaterials; + return packet; + } + + public ItemContainer getItemContainer() { + return this.itemContainer; + } + + public void setItemContainer(ItemContainer itemContainer) { + this.itemContainer = itemContainer; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/windows/ValidatedWindow.java b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/ValidatedWindow.java new file mode 100644 index 0000000..6885964 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/ValidatedWindow.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.server.core.entity.entities.player.windows; + +public interface ValidatedWindow { + boolean validate(); +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/windows/Window.java b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/Window.java new file mode 100644 index 0000000..f3d10e0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/Window.java @@ -0,0 +1,159 @@ +package com.hypixel.hytale.server.core.entity.entities.player.windows; + +import com.google.gson.JsonObject; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.EventPriority; +import com.hypixel.hytale.event.EventRegistration; +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.event.SyncEventBusRegistry; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.packets.window.WindowAction; +import com.hypixel.hytale.protocol.packets.window.WindowType; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class Window { + public static final Map> CLIENT_REQUESTABLE_WINDOW_TYPES = new ConcurrentHashMap<>(); + @Nonnull + protected static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + protected final SyncEventBusRegistry closeEventRegistry = new SyncEventBusRegistry<>(LOGGER, Window.WindowCloseEvent.class); + @Nonnull + protected final WindowType windowType; + @Nonnull + protected final AtomicBoolean isDirty = new AtomicBoolean(); + @Nonnull + protected final AtomicBoolean needRebuild = new AtomicBoolean(); + private int id; + @Nullable + private WindowManager manager; + @Nullable + private PlayerRef playerRef; + + public Window(@Nonnull WindowType windowType) { + this.windowType = windowType; + } + + public void init(@Nonnull PlayerRef playerRef, @Nonnull WindowManager manager) { + this.playerRef = playerRef; + this.manager = manager; + } + + @Nonnull + public abstract JsonObject getData(); + + protected abstract boolean onOpen0(); + + protected abstract void onClose0(); + + protected boolean onOpen() { + return this.onOpen0(); + } + + protected void onClose() { + try { + this.onClose0(); + } finally { + this.closeEventRegistry.dispatchFor(null).dispatch(new Window.WindowCloseEvent()); + } + } + + public void handleAction(@Nonnull Ref ref, @Nonnull Store store, @Nonnull WindowAction action) { + } + + @Nonnull + public WindowType getType() { + return this.windowType; + } + + public void setId(int id) { + this.id = id; + } + + public int getId() { + return this.id; + } + + @Nullable + public PlayerRef getPlayerRef() { + return this.playerRef; + } + + public void close() { + assert this.manager != null; + + this.manager.closeWindow(this.id); + } + + protected void invalidate() { + this.isDirty.set(true); + } + + protected void setNeedRebuild() { + this.needRebuild.set(true); + this.getData().addProperty("needRebuild", Boolean.TRUE); + } + + protected boolean consumeIsDirty() { + return this.isDirty.getAndSet(false); + } + + protected void consumeNeedRebuild() { + if (this.needRebuild.get()) { + this.getData().remove("needRebuild"); + this.needRebuild.set(false); + } + } + + @Nonnull + public EventRegistration registerCloseEvent(@Nonnull Consumer consumer) { + return this.closeEventRegistry.register((short)0, null, consumer); + } + + @Nonnull + public EventRegistration registerCloseEvent(short priority, @Nonnull Consumer consumer) { + return this.closeEventRegistry.register(priority, null, consumer); + } + + @Nonnull + public EventRegistration registerCloseEvent(@Nonnull EventPriority priority, @Nonnull Consumer consumer) { + return this.closeEventRegistry.register(priority.getValue(), null, consumer); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Window window = (Window)o; + if (this.id != window.id) { + return false; + } else { + return !Objects.equals(this.windowType, window.windowType) ? false : Objects.equals(this.playerRef, window.playerRef); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.windowType.hashCode(); + result = 31 * result + this.id; + return 31 * result + (this.playerRef != null ? this.playerRef.hashCode() : 0); + } + + public static class WindowCloseEvent implements IEvent { + public WindowCloseEvent() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/entities/player/windows/WindowManager.java b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/WindowManager.java new file mode 100644 index 0000000..27e526d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/entities/player/windows/WindowManager.java @@ -0,0 +1,246 @@ +package com.hypixel.hytale.server.core.entity.entities.player.windows; + +import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap; +import com.hypixel.hytale.event.EventPriority; +import com.hypixel.hytale.event.EventRegistration; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.ExtraResources; +import com.hypixel.hytale.protocol.InventorySection; +import com.hypixel.hytale.protocol.packets.window.CloseWindow; +import com.hypixel.hytale.protocol.packets.window.OpenWindow; +import com.hypixel.hytale.protocol.packets.window.UpdateWindow; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WindowManager { + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final AtomicInteger windowId = new AtomicInteger(1); + @Nonnull + private final Int2ObjectConcurrentHashMap windows = new Int2ObjectConcurrentHashMap<>(); + @Nonnull + private final Int2ObjectConcurrentHashMap windowChangeEvents = new Int2ObjectConcurrentHashMap<>(); + private PlayerRef playerRef; + + public WindowManager() { + } + + public void init(@Nonnull PlayerRef playerRef) { + this.playerRef = playerRef; + } + + @Nullable + public UpdateWindow clientOpenWindow(@Nonnull Window window) { + if (!Window.CLIENT_REQUESTABLE_WINDOW_TYPES.containsKey(window.getType())) { + throw new IllegalArgumentException("Client opened window must be registered in Window.CLIENT_REQUESTABLE_WINDOW_TYPES but got: " + window.getType()); + } else { + int id = 0; + Window oldWindow = this.windows.remove(0); + if (oldWindow != null) { + if (oldWindow instanceof ItemContainerWindow) { + this.windowChangeEvents.remove(oldWindow.getId()).unregister(); + } + + oldWindow.onClose(); + LOGGER.at(Level.FINE).log("%s close window %s with id %s", this.playerRef.getUuid(), oldWindow.getType(), 0); + } + + this.setWindow0(0, window); + if (!window.onOpen()) { + this.closeWindow(0); + window.setId(-1); + return null; + } else if (!window.consumeIsDirty()) { + return null; + } else { + InventorySection section = null; + if (window instanceof ItemContainerWindow) { + section = ((ItemContainerWindow)window).getItemContainer().toPacket(); + } + + ExtraResources extraResources = null; + if (window instanceof MaterialContainerWindow) { + extraResources = ((MaterialContainerWindow)window).getExtraResourcesSection().toPacket(); + } + + return new UpdateWindow(0, window.getData().toString(), section, extraResources); + } + } + } + + @Nullable + public OpenWindow openWindow(@Nonnull Window window) { + int id = this.windowId.getAndUpdate(operand -> { + operand++; + return operand > 0 ? operand : 1; + }); + this.setWindow(id, window); + if (!window.onOpen()) { + this.closeWindow(id); + window.setId(-1); + return null; + } else { + window.consumeIsDirty(); + LOGGER.at(Level.FINE).log("%s opened window %s with id %s and data %s", this.playerRef.getUuid(), window.getType(), id, window.getData()); + InventorySection section = null; + if (window instanceof ItemContainerWindow) { + section = ((ItemContainerWindow)window).getItemContainer().toPacket(); + } + + ExtraResources extraResources = null; + if (window instanceof MaterialContainerWindow) { + extraResources = ((MaterialContainerWindow)window).getExtraResourcesSection().toPacket(); + } + + return new OpenWindow(id, window.getType(), window.getData().toString(), section, extraResources); + } + } + + @Nullable + public List openWindows(@Nonnull Window... windows) { + ObjectList packets = new ObjectArrayList<>(); + + for (Window window : windows) { + OpenWindow packet = this.openWindow(window); + if (packet == null) { + for (OpenWindow addedPacket : packets) { + this.closeWindow(addedPacket.id); + } + + return null; + } + + packets.add(packet); + } + + return packets; + } + + public void setWindow(int id, @Nonnull Window window) { + if (id >= this.windowId.get()) { + throw new IllegalArgumentException("id is outside of the range, use addWindow"); + } else if (id != 0 && id != -1) { + this.setWindow0(id, window); + } else { + throw new IllegalArgumentException("id is invalid, can't be 0 or -1"); + } + } + + private void setWindow0(int id, @Nonnull Window window) { + if (this.windows.putIfAbsent(id, window) != null) { + throw new IllegalArgumentException("Window " + id + " already exists"); + } else { + window.setId(id); + window.init(this.playerRef, this); + if (window instanceof ItemContainerWindow) { + ItemContainer itemContainer = ((ItemContainerWindow)window).getItemContainer(); + this.windowChangeEvents.put(id, itemContainer.registerChangeEvent(EventPriority.LAST, e -> this.markWindowChanged(id))); + } + } + } + + @Nullable + public Window getWindow(int id) { + if (id == -1) { + throw new IllegalArgumentException("Window id -1 is invalid!"); + } else { + return this.windows.get(id); + } + } + + @Nonnull + public List getWindows() { + return new ObjectArrayList<>(this.windows.values()); + } + + public void updateWindow(@Nonnull Window window) { + InventorySection section = null; + if (window instanceof ItemContainerWindow itemContainerWindow) { + section = itemContainerWindow.getItemContainer().toPacket(); + } + + ExtraResources extraResources = null; + if (window instanceof MaterialContainerWindow materialContainerWindow && !materialContainerWindow.isValid()) { + extraResources = materialContainerWindow.getExtraResourcesSection().toPacket(); + } + + this.playerRef.getPacketHandler().writeNoCache(new UpdateWindow(window.getId(), window.getData().toString(), section, extraResources)); + window.consumeNeedRebuild(); + LOGGER.at(Level.FINER).log("%s update window %s with id %s and data %s", this.playerRef.getUuid(), window.getType(), window.getId(), window.getData()); + } + + @Nonnull + public Window closeWindow(int id) { + if (id == -1) { + throw new IllegalArgumentException("Window id -1 is invalid!"); + } else { + this.playerRef.getPacketHandler().writeNoCache(new CloseWindow(id)); + Window window = this.windows.remove(id); + if (window instanceof ItemContainerWindow) { + this.windowChangeEvents.remove(window.getId()).unregister(); + } + + if (window == null) { + throw new IllegalStateException("Window id " + id + " is invalid!"); + } else { + window.onClose(); + LOGGER.at(Level.FINE).log("%s close window %s with id %s", this.playerRef.getUuid(), window.getType(), id); + return window; + } + } + } + + public void closeAllWindows() { + for (Window window : this.windows.values()) { + window.close(); + } + } + + public void markWindowChanged(int id) { + Window window = this.getWindow(id); + if (window != null) { + window.invalidate(); + } + } + + public void updateWindows() { + this.windows.forEach((id, window, _windowManager) -> { + if (window.consumeIsDirty()) { + _windowManager.updateWindow(window); + } + }, this); + } + + public void validateWindows() { + for (Window value : this.windows.values()) { + if (value instanceof ValidatedWindow && !((ValidatedWindow)value).validate()) { + value.close(); + } + } + } + + public static void closeAndRemoveAll(@Nonnull Map windows) { + Iterator iterator = windows.values().iterator(); + + while (iterator.hasNext()) { + iterator.next().close(); + iterator.remove(); + } + } + + @Nonnull + @Override + public String toString() { + return "WindowManager{windowId=" + this.windowId + ", windows=" + this.windows + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/group/EntityGroup.java b/src/com/hypixel/hytale/server/core/entity/group/EntityGroup.java new file mode 100644 index 0000000..3ed8b13 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/group/EntityGroup.java @@ -0,0 +1,212 @@ +package com.hypixel.hytale.server.core.entity.group; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.function.consumer.IntBiObjectConsumer; +import com.hypixel.hytale.function.consumer.IntTriObjectConsumer; +import com.hypixel.hytale.function.consumer.QuadConsumer; +import com.hypixel.hytale.function.consumer.TriConsumer; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityGroup implements Component { + @Nonnull + private final Set> memberSet = new HashSet<>(); + @Nonnull + private final List> memberList = new ObjectArrayList<>(); + @Nullable + private Ref leaderRef; + private boolean dissolved; + + public EntityGroup() { + } + + public static ComponentType getComponentType() { + return EntityModule.get().getEntityGroupComponentType(); + } + + @Nullable + public Ref getLeaderRef() { + return this.leaderRef; + } + + public void setLeaderRef(@Nonnull Ref leaderRef) { + this.leaderRef = leaderRef; + } + + public void add(@Nonnull Ref reference) { + if (!this.memberSet.add(reference)) { + throw new IllegalStateException("Attempting to add entity " + reference + " that is already a member of the group!"); + } else { + this.memberList.add(reference); + } + } + + public void remove(@Nonnull Ref reference) { + if (!this.memberSet.remove(reference)) { + throw new IllegalStateException("Attempting to remove entity " + reference + " that is not a member of the group!"); + } else { + this.memberList.remove(reference); + } + } + + @Nullable + public Ref getFirst() { + return !this.memberList.isEmpty() ? this.memberList.getFirst() : null; + } + + @Nonnull + public List> getMemberList() { + return this.memberList; + } + + public int size() { + return this.memberSet.size(); + } + + public boolean isDissolved() { + return this.dissolved; + } + + public void setDissolved(boolean dissolved) { + this.dissolved = dissolved; + } + + public void clear() { + this.memberSet.clear(); + this.memberList.clear(); + this.leaderRef = null; + this.dissolved = true; + } + + public boolean isMember(Ref reference) { + return !this.dissolved && this.memberSet.contains(reference); + } + + public void forEachMemberExcludingLeader(@Nonnull TriConsumer, Ref, T> consumer, Ref sender, T arg) { + this.forEachMember(consumer, sender, arg, this.leaderRef); + } + + public void forEachMemberExcludingSelf(@Nonnull TriConsumer, Ref, T> consumer, Ref sender, T arg) { + this.forEachMember(consumer, sender, arg, sender); + } + + public void forEachMember(@Nonnull TriConsumer, Ref, T> consumer, Ref sender, T arg) { + this.forEachMember(consumer, sender, arg, null); + } + + public void forEachMember( + @Nonnull TriConsumer, Ref, T> consumer, Ref sender, T arg, Ref excludeReference + ) { + for (int i = 0; i < this.memberList.size(); i++) { + Ref member = this.memberList.get(i); + if (member.isValid() && !member.equals(excludeReference)) { + consumer.accept(member, sender, arg); + } + } + } + + public void forEachMemberExcludingLeader(@Nonnull QuadConsumer, Ref, T, V> consumer, Ref sender, T t, V v) { + this.forEachMember(consumer, sender, t, v, this.leaderRef); + } + + public void forEachMemberExcludingSelf(@Nonnull QuadConsumer, Ref, T, V> consumer, Ref sender, T t, V v) { + this.forEachMember(consumer, sender, t, v, sender); + } + + public void forEachMember(@Nonnull QuadConsumer, Ref, T, V> consumer, Ref sender, T t, V v) { + this.forEachMember(consumer, sender, t, v, null); + } + + public void forEachMember( + @Nonnull QuadConsumer, Ref, T, V> consumer, Ref sender, T t, V v, Ref excludeReference + ) { + for (int i = 0; i < this.memberList.size(); i++) { + Ref member = this.memberList.get(i); + if (member.isValid() && !member.equals(excludeReference)) { + consumer.accept(member, sender, t, v); + } + } + } + + public void forEachMemberExcludingLeader( + @Nonnull IntTriObjectConsumer, Ref, T> consumer, Ref sender, T t, int value + ) { + this.forEachMember(consumer, sender, t, value, this.leaderRef); + } + + public void forEachMemberExcludingSelf( + @Nonnull IntTriObjectConsumer, Ref, T> consumer, @Nonnull Ref sender, T t, int value + ) { + this.forEachMember(consumer, sender, t, value, sender); + } + + public void forEachMember(@Nonnull IntTriObjectConsumer, Ref, T> consumer, Ref sender, T t, int value) { + this.forEachMember(consumer, sender, t, value, null); + } + + public void forEachMember( + @Nonnull IntTriObjectConsumer, Ref, T> consumer, Ref sender, T t, int value, Ref excludeReference + ) { + for (int i = 0; i < this.memberList.size(); i++) { + Ref member = this.memberList.get(i); + if (member.isValid() && !member.equals(excludeReference)) { + consumer.accept(value, member, sender, t); + } + } + } + + public void forEachMember(@Nonnull IntBiObjectConsumer, T> consumer, T t) { + for (int i = 0; i < this.memberList.size(); i++) { + Ref member = this.memberList.get(i); + if (member.isValid()) { + consumer.accept(i, member, t); + } + } + } + + @Nullable + public Ref testMembers(@Nonnull Predicate> predicate, boolean skipLeader) { + for (int i = 0; i < this.memberList.size(); i++) { + Ref member = this.memberList.get(i); + if (member.isValid() && (!skipLeader || !member.equals(this.leaderRef)) && predicate.test(member)) { + return member; + } + } + + return null; + } + + @Nonnull + @Override + public Component clone() { + EntityGroup group = new EntityGroup(); + group.memberSet.addAll(this.memberSet); + group.memberList.addAll(this.memberList); + group.leaderRef = this.leaderRef; + group.dissolved = this.dissolved; + return group; + } + + @Nonnull + @Override + public String toString() { + return "EntityGroup{leader=" + + this.leaderRef + + ", memberSet=" + + this.memberSet + + ", memberList=" + + this.memberList + + ", dissolved=" + + this.dissolved + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/knockback/KnockbackComponent.java b/src/com/hypixel/hytale/server/core/entity/knockback/KnockbackComponent.java new file mode 100644 index 0000000..349a364 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/knockback/KnockbackComponent.java @@ -0,0 +1,102 @@ +package com.hypixel.hytale.server.core.entity.knockback; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.ChangeVelocityType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.splitvelocity.VelocityConfig; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.doubles.DoubleArrayList; +import it.unimi.dsi.fastutil.doubles.DoubleList; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class KnockbackComponent implements Component { + @Nonnull + private Vector3d velocity; + private ChangeVelocityType velocityType = ChangeVelocityType.Add; + @Nullable + private VelocityConfig velocityConfig; + @Nonnull + private DoubleList modifiers = new DoubleArrayList(); + private float duration; + private float timer; + + public KnockbackComponent() { + } + + public static ComponentType getComponentType() { + return EntityModule.get().getKnockbackComponentType(); + } + + @Nonnull + public Vector3d getVelocity() { + return this.velocity; + } + + public void setVelocity(@Nonnull Vector3d velocity) { + this.velocity = velocity; + } + + public ChangeVelocityType getVelocityType() { + return this.velocityType; + } + + public void setVelocityType(ChangeVelocityType velocityType) { + this.velocityType = velocityType; + } + + @Nullable + public VelocityConfig getVelocityConfig() { + return this.velocityConfig; + } + + public void setVelocityConfig(@Nullable VelocityConfig velocityConfig) { + this.velocityConfig = velocityConfig; + } + + public void addModifier(double modifier) { + this.modifiers.add(modifier); + } + + public void applyModifiers() { + for (int i = 0; i < this.modifiers.size(); i++) { + this.velocity.scale(this.modifiers.getDouble(i)); + } + + this.modifiers.clear(); + } + + public float getDuration() { + return this.duration; + } + + public void setDuration(float duration) { + this.duration = duration; + } + + public float getTimer() { + return this.timer; + } + + public void incrementTimer(float time) { + this.timer += time; + } + + public void setTimer(float time) { + this.timer = time; + } + + @Nonnull + @Override + public Component clone() { + KnockbackComponent component = new KnockbackComponent(); + component.velocity = this.velocity; + component.velocityType = this.velocityType; + component.velocityConfig = this.velocityConfig; + component.duration = this.duration; + component.timer = this.timer; + return component; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/knockback/KnockbackSystems.java b/src/com/hypixel/hytale/server/core/entity/knockback/KnockbackSystems.java new file mode 100644 index 0000000..1c07043 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/knockback/KnockbackSystems.java @@ -0,0 +1,160 @@ +package com.hypixel.hytale.server.core.entity.knockback; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.protocol.ChangeVelocityType; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.AllLegacyLivingEntityTypesQuery; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageModule; +import com.hypixel.hytale.server.core.modules.entity.player.KnockbackSimulation; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.modules.physics.systems.IVelocityModifyingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class KnockbackSystems { + public KnockbackSystems() { + } + + public static class ApplyKnockback extends EntityTickingSystem implements IVelocityModifyingSystem { + @Nonnull + private static final Query QUERY = Query.and( + AllLegacyLivingEntityTypesQuery.INSTANCE, KnockbackComponent.getComponentType(), Velocity.getComponentType(), Query.not(Player.getComponentType()) + ); + + public ApplyKnockback() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + KnockbackComponent knockbackComponent = archetypeChunk.getComponent(index, KnockbackComponent.getComponentType()); + + assert knockbackComponent != null; + + knockbackComponent.applyModifiers(); + Velocity velocityComponent = archetypeChunk.getComponent(index, Velocity.getComponentType()); + + assert velocityComponent != null; + + velocityComponent.addInstruction(knockbackComponent.getVelocity(), knockbackComponent.getVelocityConfig(), knockbackComponent.getVelocityType()); + if (knockbackComponent.getDuration() > 0.0F) { + knockbackComponent.incrementTimer(dt); + } + + if (knockbackComponent.getDuration() == 0.0F || knockbackComponent.getTimer() > knockbackComponent.getDuration()) { + Ref ref = archetypeChunk.getReferenceTo(index); + commandBuffer.tryRemoveComponent(ref, KnockbackComponent.getComponentType()); + } + } + } + + public static class ApplyPlayerKnockback extends EntityTickingSystem implements IVelocityModifyingSystem { + public static boolean DO_SERVER_PREDICTION = false; + @Nonnull + private static final Query QUERY = Query.and(KnockbackComponent.getComponentType(), Player.getComponentType(), Velocity.getComponentType()); + + public ApplyPlayerKnockback() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + KnockbackComponent knockbackComponent = archetypeChunk.getComponent(index, KnockbackComponent.getComponentType()); + + assert knockbackComponent != null; + + Ref ref = archetypeChunk.getReferenceTo(index); + KnockbackSimulation knockbackSimulationComponent = archetypeChunk.getComponent(index, KnockbackSimulation.getComponentType()); + if (knockbackSimulationComponent == null && DO_SERVER_PREDICTION) { + commandBuffer.addComponent(ref, KnockbackSimulation.getComponentType(), knockbackSimulationComponent = new KnockbackSimulation()); + } + + knockbackComponent.applyModifiers(); + switch (knockbackComponent.getVelocityType()) { + case Add: + if (DO_SERVER_PREDICTION) { + if (knockbackSimulationComponent != null) { + knockbackSimulationComponent.addRequestedVelocity(knockbackComponent.getVelocity()); + } + } else { + Velocity velocityComponentx = archetypeChunk.getComponent(index, Velocity.getComponentType()); + + assert velocityComponentx != null; + + velocityComponentx.addInstruction(knockbackComponent.getVelocity(), knockbackComponent.getVelocityConfig(), ChangeVelocityType.Add); + if (knockbackComponent.getDuration() > 0.0F) { + knockbackComponent.incrementTimer(dt); + } + + if (knockbackComponent.getDuration() == 0.0F || knockbackComponent.getTimer() > knockbackComponent.getDuration()) { + commandBuffer.tryRemoveComponent(ref, KnockbackComponent.getComponentType()); + } + } + break; + case Set: + if (DO_SERVER_PREDICTION) { + if (knockbackSimulationComponent != null) { + knockbackSimulationComponent.setRequestedVelocity(knockbackComponent.getVelocity()); + } + } else { + Velocity velocityComponent = archetypeChunk.getComponent(index, Velocity.getComponentType()); + + assert velocityComponent != null; + + velocityComponent.addInstruction(knockbackComponent.getVelocity(), null, ChangeVelocityType.Set); + if (knockbackComponent.getDuration() > 0.0F) { + knockbackComponent.incrementTimer(dt); + } + + if (knockbackComponent.getDuration() == 0.0F || knockbackComponent.getTimer() > knockbackComponent.getDuration()) { + commandBuffer.tryRemoveComponent(ref, KnockbackComponent.getComponentType()); + } + } + } + + if (DO_SERVER_PREDICTION && knockbackSimulationComponent != null) { + knockbackSimulationComponent.reset(); + } + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getInspectDamageGroup(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/movement/MovementStatesComponent.java b/src/com/hypixel/hytale/server/core/entity/movement/MovementStatesComponent.java new file mode 100644 index 0000000..7276e5d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/movement/MovementStatesComponent.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.core.entity.movement; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class MovementStatesComponent implements Component { + private MovementStates movementStates = new MovementStates(); + private MovementStates sentMovementStates = new MovementStates(); + + public static ComponentType getComponentType() { + return EntityModule.get().getMovementStatesComponentType(); + } + + public MovementStatesComponent() { + } + + public MovementStatesComponent(@Nonnull MovementStatesComponent other) { + this.movementStates = new MovementStates(other.movementStates); + this.sentMovementStates = new MovementStates(other.sentMovementStates); + } + + public MovementStates getMovementStates() { + return this.movementStates; + } + + public void setMovementStates(MovementStates movementStates) { + this.movementStates = movementStates; + } + + public MovementStates getSentMovementStates() { + return this.sentMovementStates; + } + + public void setSentMovementStates(MovementStates sentMovementStates) { + this.sentMovementStates = sentMovementStates; + } + + @Nonnull + @Override + public Component clone() { + return new MovementStatesComponent(this); + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/movement/MovementStatesSystems.java b/src/com/hypixel/hytale/server/core/entity/movement/MovementStatesSystems.java new file mode 100644 index 0000000..540276e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/movement/MovementStatesSystems.java @@ -0,0 +1,210 @@ +package com.hypixel.hytale.server.core.entity.movement; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.protocol.SavedMovementStates; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerWorldData; +import com.hypixel.hytale.server.core.modules.entity.AllLegacyLivingEntityTypesQuery; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MovementStatesSystems { + public MovementStatesSystems() { + } + + public static class AddSystem extends HolderSystem { + @Nonnull + private final ComponentType movementStatesComponentComponentType; + + public AddSystem(@Nonnull ComponentType movementStatesComponentComponentType) { + this.movementStatesComponentComponentType = movementStatesComponentComponentType; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(this.movementStatesComponentComponentType); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return AllLegacyLivingEntityTypesQuery.INSTANCE; + } + } + + public static class PlayerInitSystem extends RefSystem { + @Nonnull + private final Query query; + @Nonnull + private final ComponentType playerComponentType; + @Nonnull + private final ComponentType movementStatesComponentType; + + public PlayerInitSystem( + @Nonnull ComponentType playerComponentType, + @Nonnull ComponentType movementStatesComponentType + ) { + this.playerComponentType = playerComponentType; + this.movementStatesComponentType = movementStatesComponentType; + this.query = Query.and(playerComponentType, movementStatesComponentType); + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + World world = commandBuffer.getExternalData().getWorld(); + Player playerComponent = store.getComponent(ref, this.playerComponentType); + + assert playerComponent != null; + + MovementStatesComponent movementStatesComponent = store.getComponent(ref, this.movementStatesComponentType); + + assert movementStatesComponent != null; + + PlayerWorldData perWorldData = playerComponent.getPlayerConfigData().getPerWorldData(world.getName()); + SavedMovementStates movementStates = perWorldData.getLastMovementStates(); + playerComponent.applyMovementStates( + ref, movementStates != null ? movementStates : new SavedMovementStates(), movementStatesComponent.getMovementStates(), store + ); + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + } + + public static class TickingSystem extends EntityTickingSystem { + @Nonnull + private final ComponentType visibleComponentType; + @Nonnull + private final ComponentType movementStatesComponentComponentType; + @Nonnull + private final Query query; + + public TickingSystem( + @Nonnull ComponentType visibleComponentType, + @Nonnull ComponentType movementStatesComponentComponentType + ) { + this.visibleComponentType = visibleComponentType; + this.query = Query.and(movementStatesComponentComponentType, visibleComponentType); + this.movementStatesComponentComponentType = movementStatesComponentComponentType; + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.visibleComponentType); + + assert visibleComponent != null; + + MovementStatesComponent movementStatesComponent = archetypeChunk.getComponent(index, this.movementStatesComponentComponentType); + + assert movementStatesComponent != null; + + MovementStates newMovementStates = movementStatesComponent.getMovementStates(); + MovementStates sentMovementStates = movementStatesComponent.getSentMovementStates(); + if (!newMovementStates.equals(sentMovementStates)) { + copyMovementStatesFrom(newMovementStates, sentMovementStates); + queueUpdatesFor(archetypeChunk.getReferenceTo(index), visibleComponent.visibleTo, movementStatesComponent); + } else if (!visibleComponent.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), visibleComponent.newlyVisibleTo, movementStatesComponent); + } + } + + private static void queueUpdatesFor( + @Nonnull Ref ref, + @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo, + @Nonnull MovementStatesComponent movementStatesComponent + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.MovementStates; + update.movementStates = movementStatesComponent.getMovementStates(); + + for (Entry, EntityTrackerSystems.EntityViewer> entry : visibleTo.entrySet()) { + if (!ref.equals(entry.getKey())) { + entry.getValue().queueUpdate(ref, update); + } + } + } + + public static void copyMovementStatesFrom(@Nonnull MovementStates from, @Nonnull MovementStates to) { + to.idle = from.idle; + to.horizontalIdle = from.horizontalIdle; + to.jumping = from.jumping; + to.flying = from.flying; + to.walking = from.walking; + to.running = from.running; + to.sprinting = from.sprinting; + to.crouching = from.crouching; + to.forcedCrouching = from.forcedCrouching; + to.falling = from.falling; + to.climbing = from.climbing; + to.inFluid = from.inFluid; + to.swimming = from.swimming; + to.swimJumping = from.swimJumping; + to.onGround = from.onGround; + to.mantling = from.mantling; + to.sliding = from.sliding; + to.mounting = from.mounting; + to.rolling = from.rolling; + to.sitting = from.sitting; + to.gliding = from.gliding; + to.sleeping = from.sleeping; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/nameplate/Nameplate.java b/src/com/hypixel/hytale/server/core/entity/nameplate/Nameplate.java new file mode 100644 index 0000000..ba8c8c8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/nameplate/Nameplate.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.core.entity.nameplate; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class Nameplate implements Component { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(Nameplate.class, Nameplate::new) + .append(new KeyedCodec<>("Text", Codec.STRING), (nameplate, s) -> nameplate.text = s, nameplate -> nameplate.text) + .documentation("The contents to display as the nameplate text.") + .addValidator(Validators.nonNull()) + .add() + .build(); + @Nonnull + private String text = ""; + private boolean isNetworkOutdated = true; + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getNameplateComponentType(); + } + + public Nameplate() { + } + + public Nameplate(@Nonnull String text) { + this.text = text; + } + + @Nonnull + public String getText() { + return this.text; + } + + public void setText(@Nonnull String text) { + if (!this.text.equals(text)) { + this.text = text; + this.isNetworkOutdated = true; + } + } + + public boolean consumeNetworkOutdated() { + boolean temp = this.isNetworkOutdated; + this.isNetworkOutdated = false; + return temp; + } + + @Nonnull + @Override + public Component clone() { + Nameplate nameplate = new Nameplate(); + nameplate.text = this.text; + return nameplate; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/nameplate/NameplateSystems.java b/src/com/hypixel/hytale/server/core/entity/nameplate/NameplateSystems.java new file mode 100644 index 0000000..5f8dd85 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/nameplate/NameplateSystems.java @@ -0,0 +1,148 @@ +package com.hypixel.hytale.server.core.entity.nameplate; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NameplateSystems { + public NameplateSystems() { + } + + public static class EntityTrackerRemove extends RefChangeSystem { + @Nonnull + private final ComponentType componentType; + @Nonnull + private final ComponentType visibleComponentType; + @Nonnull + private final Query query; + + public EntityTrackerRemove( + @Nonnull ComponentType visibleComponentType, @Nonnull ComponentType componentType + ) { + this.visibleComponentType = visibleComponentType; + this.componentType = componentType; + this.query = Query.and(visibleComponentType, componentType); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public ComponentType componentType() { + return this.componentType; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull Nameplate component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentSet( + @Nonnull Ref ref, + Nameplate oldComponent, + @Nonnull Nameplate newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull Nameplate component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visibleComponent = store.getComponent(ref, this.visibleComponentType); + + assert visibleComponent != null; + + for (EntityTrackerSystems.EntityViewer viewer : visibleComponent.visibleTo.values()) { + viewer.queueRemove(ref, ComponentUpdateType.Nameplate); + } + } + } + + public static class EntityTrackerUpdate extends EntityTickingSystem { + @Nonnull + private final ComponentType visibleComponentType; + @Nonnull + private final ComponentType componentType; + @Nonnull + private final Query query; + + public EntityTrackerUpdate( + @Nonnull ComponentType visibleComponentType, @Nonnull ComponentType componentType + ) { + this.visibleComponentType = visibleComponentType; + this.componentType = componentType; + this.query = Query.and(visibleComponentType, componentType); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.visibleComponentType); + + assert visibleComponent != null; + + Nameplate nameplateComponent = archetypeChunk.getComponent(index, this.componentType); + + assert nameplateComponent != null; + + if (nameplateComponent.consumeNetworkOutdated()) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), nameplateComponent, visibleComponent.visibleTo); + } else if (!visibleComponent.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), nameplateComponent, visibleComponent.newlyVisibleTo); + } + } + + private static void queueUpdatesFor( + @Nonnull Ref ref, @Nonnull Nameplate nameplateComponent, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Nameplate; + update.nameplate = new com.hypixel.hytale.protocol.Nameplate(); + update.nameplate.text = nameplateComponent.getText(); + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, update); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/reference/InvalidatablePersistentRef.java b/src/com/hypixel/hytale/server/core/entity/reference/InvalidatablePersistentRef.java new file mode 100644 index 0000000..6368b3a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/reference/InvalidatablePersistentRef.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.core.entity.reference; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class InvalidatablePersistentRef extends PersistentRef { + public static final BuilderCodec CODEC = BuilderCodec.builder( + InvalidatablePersistentRef.class, InvalidatablePersistentRef::new, PersistentRef.CODEC + ) + .append(new KeyedCodec<>("RefCount", Codec.INTEGER), (instance, value) -> instance.refCount = value, instance -> instance.refCount) + .add() + .build(); + protected int refCount; + + public InvalidatablePersistentRef() { + } + + @Override + public void setEntity(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + super.setEntity(ref, componentAccessor); + PersistentRefCount refCount = componentAccessor.getComponent(ref, PersistentRefCount.getComponentType()); + if (refCount == null) { + refCount = new PersistentRefCount(); + componentAccessor.addComponent(ref, PersistentRefCount.getComponentType(), refCount); + } + + this.refCount = refCount.get(); + } + + @Override + public void clear() { + super.clear(); + this.refCount = -1; + } + + public void setRefCount(int refCount) { + this.refCount = refCount; + } + + public int getRefCount() { + return this.refCount; + } + + @Override + protected boolean validateEntityReference(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + PersistentRefCount refCount = componentAccessor.getComponent(ref, PersistentRefCount.getComponentType()); + return super.validateEntityReference(ref, componentAccessor) && refCount != null && refCount.get() == this.refCount; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/reference/PersistentRef.java b/src/com/hypixel/hytale/server/core/entity/reference/PersistentRef.java new file mode 100644 index 0000000..8b78e29 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/reference/PersistentRef.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.server.core.entity.reference; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PersistentRef { + public static final BuilderCodec CODEC = BuilderCodec.builder(PersistentRef.class, PersistentRef::new) + .append(new KeyedCodec<>("UUID", Codec.UUID_BINARY), (instance, value) -> instance.uuid = value, instance -> instance.uuid) + .add() + .build(); + @Nullable + protected UUID uuid; + @Nullable + protected Ref reference; + + public PersistentRef() { + } + + @Nullable + public UUID getUuid() { + return this.uuid; + } + + public void setUuid(UUID uuid) { + this.uuid = uuid; + this.reference = null; + } + + public void setEntity(Ref ref, UUID uuid) { + this.uuid = uuid; + this.reference = ref; + } + + public void setEntity(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + this.uuid = componentAccessor.getComponent(ref, UUIDComponent.getComponentType()).getUuid(); + this.reference = ref; + } + + public void clear() { + this.uuid = null; + this.reference = null; + } + + public boolean isValid() { + return this.uuid != null; + } + + @Nullable + public Ref getEntity(@Nonnull ComponentAccessor componentAccessor) { + if (!this.isValid()) { + return null; + } else if (this.reference != null && this.reference.isValid()) { + return this.reference; + } else { + this.reference = componentAccessor.getExternalData().getRefFromUUID(this.uuid); + if (this.reference != null && !this.validateEntityReference(this.reference, componentAccessor)) { + this.clear(); + } + + return this.reference; + } + } + + protected boolean validateEntityReference(Ref ref, ComponentAccessor componentAccessor) { + return true; + } +} diff --git a/src/com/hypixel/hytale/server/core/entity/reference/PersistentRefCount.java b/src/com/hypixel/hytale/server/core/entity/reference/PersistentRefCount.java new file mode 100644 index 0000000..a1fa6f7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/entity/reference/PersistentRefCount.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.core.entity.reference; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PersistentRefCount implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(PersistentRefCount.class, PersistentRefCount::new) + .append(new KeyedCodec<>("Count", Codec.INTEGER), (instance, value) -> instance.refCount = value, instance -> instance.refCount) + .add() + .build(); + private int refCount; + + public PersistentRefCount() { + } + + public static ComponentType getComponentType() { + return EntityModule.get().getPersistentRefCountComponentType(); + } + + public int get() { + return this.refCount; + } + + public void increment() { + if (this.refCount >= Integer.MAX_VALUE) { + this.refCount = 0; + } else { + this.refCount++; + } + } + + @Nonnull + @Override + public Component clone() { + PersistentRefCount ref = new PersistentRefCount(); + ref.refCount = this.refCount; + return ref; + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/BootEvent.java b/src/com/hypixel/hytale/server/core/event/events/BootEvent.java new file mode 100644 index 0000000..675eed3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/BootEvent.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.server.core.event.events; + +import com.hypixel.hytale.event.IEvent; +import javax.annotation.Nonnull; + +public class BootEvent implements IEvent { + public BootEvent() { + } + + @Nonnull + @Override + public String toString() { + return "BootEvent{}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/PrepareUniverseEvent.java b/src/com/hypixel/hytale/server/core/event/events/PrepareUniverseEvent.java new file mode 100644 index 0000000..a48ee93 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/PrepareUniverseEvent.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.event.events; + +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.server.core.universe.world.WorldConfigProvider; +import javax.annotation.Nonnull; + +@Deprecated +public class PrepareUniverseEvent implements IEvent { + private WorldConfigProvider worldConfigProvider; + + public PrepareUniverseEvent(WorldConfigProvider worldConfigProvider) { + this.worldConfigProvider = worldConfigProvider; + } + + public WorldConfigProvider getWorldConfigProvider() { + return this.worldConfigProvider; + } + + public void setWorldConfigProvider(WorldConfigProvider worldConfigProvider) { + this.worldConfigProvider = worldConfigProvider; + } + + @Nonnull + @Override + public String toString() { + return "PrepareUniverseEvent{worldConfigProvider=" + this.worldConfigProvider + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/ShutdownEvent.java b/src/com/hypixel/hytale/server/core/event/events/ShutdownEvent.java new file mode 100644 index 0000000..5e934e6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/ShutdownEvent.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.server.core.event.events; + +import com.hypixel.hytale.event.IEvent; +import javax.annotation.Nonnull; + +public class ShutdownEvent implements IEvent { + public static final short DISCONNECT_PLAYERS = -48; + public static final short UNBIND_LISTENERS = -40; + public static final short SHUTDOWN_WORLDS = -32; + + public ShutdownEvent() { + } + + @Nonnull + @Override + public String toString() { + return "ShutdownEvent{}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/ecs/BreakBlockEvent.java b/src/com/hypixel/hytale/server/core/event/events/ecs/BreakBlockEvent.java new file mode 100644 index 0000000..3bb81d2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/ecs/BreakBlockEvent.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.server.core.event.events.ecs; + +import com.hypixel.hytale.component.system.CancellableEcsEvent; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BreakBlockEvent extends CancellableEcsEvent { + @Nullable + private final ItemStack itemInHand; + @Nonnull + private Vector3i targetBlock; + @Nonnull + private final BlockType blockType; + + public BreakBlockEvent(@Nullable ItemStack itemInHand, @Nonnull Vector3i targetBlock, @Nonnull BlockType blockType) { + this.itemInHand = itemInHand; + this.targetBlock = targetBlock; + this.blockType = blockType; + } + + @Nullable + public ItemStack getItemInHand() { + return this.itemInHand; + } + + @Nonnull + public Vector3i getTargetBlock() { + return this.targetBlock; + } + + @Nonnull + public BlockType getBlockType() { + return this.blockType; + } + + public void setTargetBlock(@Nonnull Vector3i targetBlock) { + this.targetBlock = targetBlock; + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/ecs/ChangeGameModeEvent.java b/src/com/hypixel/hytale/server/core/event/events/ecs/ChangeGameModeEvent.java new file mode 100644 index 0000000..b7dda10 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/ecs/ChangeGameModeEvent.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.core.event.events.ecs; + +import com.hypixel.hytale.component.system.CancellableEcsEvent; +import com.hypixel.hytale.protocol.GameMode; +import javax.annotation.Nonnull; + +public class ChangeGameModeEvent extends CancellableEcsEvent { + @Nonnull + private GameMode gameMode; + + public ChangeGameModeEvent(@Nonnull GameMode gameMode) { + this.gameMode = gameMode; + } + + @Nonnull + public GameMode getGameMode() { + return this.gameMode; + } + + public void setGameMode(@Nonnull GameMode gameMode) { + this.gameMode = gameMode; + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/ecs/CraftRecipeEvent.java b/src/com/hypixel/hytale/server/core/event/events/ecs/CraftRecipeEvent.java new file mode 100644 index 0000000..7bc8c93 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/ecs/CraftRecipeEvent.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.server.core.event.events.ecs; + +import com.hypixel.hytale.component.system.CancellableEcsEvent; +import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; +import javax.annotation.Nonnull; + +public abstract class CraftRecipeEvent extends CancellableEcsEvent { + @Nonnull + private final CraftingRecipe craftedRecipe; + private final int quantity; + + public CraftRecipeEvent(@Nonnull CraftingRecipe craftedRecipe, int quantity) { + this.craftedRecipe = craftedRecipe; + this.quantity = quantity; + } + + @Nonnull + public CraftingRecipe getCraftedRecipe() { + return this.craftedRecipe; + } + + public int getQuantity() { + return this.quantity; + } + + public static final class Post extends CraftRecipeEvent { + public Post(@Nonnull CraftingRecipe craftedRecipe, int quantity) { + super(craftedRecipe, quantity); + } + } + + public static final class Pre extends CraftRecipeEvent { + public Pre(@Nonnull CraftingRecipe craftedRecipe, int quantity) { + super(craftedRecipe, quantity); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/ecs/DamageBlockEvent.java b/src/com/hypixel/hytale/server/core/event/events/ecs/DamageBlockEvent.java new file mode 100644 index 0000000..86544d2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/ecs/DamageBlockEvent.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.event.events.ecs; + +import com.hypixel.hytale.component.system.CancellableEcsEvent; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DamageBlockEvent extends CancellableEcsEvent { + @Nullable + private final ItemStack itemInHand; + @Nonnull + private Vector3i targetBlock; + @Nonnull + private final BlockType blockType; + private final float currentDamage; + private float damage; + + public DamageBlockEvent(@Nullable ItemStack itemInHand, @Nonnull Vector3i targetBlock, @Nonnull BlockType blockType, float currentDamage, float damage) { + this.itemInHand = itemInHand; + this.targetBlock = targetBlock; + this.blockType = blockType; + this.currentDamage = currentDamage; + this.damage = damage; + } + + @Nullable + public ItemStack getItemInHand() { + return this.itemInHand; + } + + @Nonnull + public Vector3i getTargetBlock() { + return this.targetBlock; + } + + public void setTargetBlock(@Nonnull Vector3i targetBlock) { + this.targetBlock = targetBlock; + } + + @Nonnull + public BlockType getBlockType() { + return this.blockType; + } + + public float getCurrentDamage() { + return this.currentDamage; + } + + public float getDamage() { + return this.damage; + } + + public void setDamage(float damage) { + this.damage = damage; + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/ecs/DiscoverZoneEvent.java b/src/com/hypixel/hytale/server/core/event/events/ecs/DiscoverZoneEvent.java new file mode 100644 index 0000000..3fe2cdf --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/ecs/DiscoverZoneEvent.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.server.core.event.events.ecs; + +import com.hypixel.hytale.component.system.EcsEvent; +import com.hypixel.hytale.component.system.ICancellableEcsEvent; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import javax.annotation.Nonnull; + +public abstract class DiscoverZoneEvent extends EcsEvent { + @Nonnull + private final WorldMapTracker.ZoneDiscoveryInfo discoveryInfo; + + public DiscoverZoneEvent(@Nonnull WorldMapTracker.ZoneDiscoveryInfo discoveryInfo) { + this.discoveryInfo = discoveryInfo; + } + + @Nonnull + public WorldMapTracker.ZoneDiscoveryInfo getDiscoveryInfo() { + return this.discoveryInfo; + } + + public static class Display extends DiscoverZoneEvent implements ICancellableEcsEvent { + private boolean cancelled = false; + + public Display(@Nonnull WorldMapTracker.ZoneDiscoveryInfo discoveryInfo) { + super(discoveryInfo); + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/ecs/DropItemEvent.java b/src/com/hypixel/hytale/server/core/event/events/ecs/DropItemEvent.java new file mode 100644 index 0000000..8b9ffca --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/ecs/DropItemEvent.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.core.event.events.ecs; + +import com.hypixel.hytale.component.system.CancellableEcsEvent; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import javax.annotation.Nonnull; + +public class DropItemEvent extends CancellableEcsEvent { + public DropItemEvent() { + } + + public static final class Drop extends DropItemEvent { + @Nonnull + private ItemStack itemStack; + private float throwSpeed; + + public Drop(@Nonnull ItemStack itemStack, float throwSpeed) { + this.itemStack = itemStack; + this.throwSpeed = throwSpeed; + } + + public void setThrowSpeed(float throwSpeed) { + this.throwSpeed = throwSpeed; + } + + public float getThrowSpeed() { + return this.throwSpeed; + } + + public void setItemStack(@Nonnull ItemStack itemStack) { + this.itemStack = itemStack; + } + + @Nonnull + public ItemStack getItemStack() { + return this.itemStack; + } + } + + public static final class PlayerRequest extends DropItemEvent { + private final int inventorySectionId; + private final short slotId; + + public PlayerRequest(int inventorySectionId, short slotId) { + this.inventorySectionId = inventorySectionId; + this.slotId = slotId; + } + + public int getInventorySectionId() { + return this.inventorySectionId; + } + + public short getSlotId() { + return this.slotId; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/ecs/InteractivelyPickupItemEvent.java b/src/com/hypixel/hytale/server/core/event/events/ecs/InteractivelyPickupItemEvent.java new file mode 100644 index 0000000..100cab0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/ecs/InteractivelyPickupItemEvent.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.core.event.events.ecs; + +import com.hypixel.hytale.component.system.CancellableEcsEvent; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import javax.annotation.Nonnull; + +public class InteractivelyPickupItemEvent extends CancellableEcsEvent { + @Nonnull + private ItemStack itemStack; + + public InteractivelyPickupItemEvent(@Nonnull ItemStack itemStack) { + this.itemStack = itemStack; + } + + @Nonnull + public ItemStack getItemStack() { + return this.itemStack; + } + + public void setItemStack(@Nonnull ItemStack itemStack) { + this.itemStack = itemStack; + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/ecs/PlaceBlockEvent.java b/src/com/hypixel/hytale/server/core/event/events/ecs/PlaceBlockEvent.java new file mode 100644 index 0000000..a1087d4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/ecs/PlaceBlockEvent.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.core.event.events.ecs; + +import com.hypixel.hytale.component.system.CancellableEcsEvent; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlaceBlockEvent extends CancellableEcsEvent { + @Nullable + private final ItemStack itemInHand; + @Nonnull + private Vector3i targetBlock; + @Nonnull + private RotationTuple rotation; + + public PlaceBlockEvent(@Nullable ItemStack itemInHand, @Nonnull Vector3i targetBlock, @Nonnull RotationTuple rotation) { + this.itemInHand = itemInHand; + this.targetBlock = targetBlock; + this.rotation = rotation; + } + + @Nullable + public ItemStack getItemInHand() { + return this.itemInHand; + } + + @Nonnull + public Vector3i getTargetBlock() { + return this.targetBlock; + } + + public void setTargetBlock(@Nonnull Vector3i targetBlock) { + Objects.requireNonNull(targetBlock, "Block can't be null"); + this.targetBlock = targetBlock; + } + + @Nonnull + public RotationTuple getRotation() { + return this.rotation; + } + + public void setRotation(@Nonnull RotationTuple rotation) { + this.rotation = rotation; + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/ecs/SwitchActiveSlotEvent.java b/src/com/hypixel/hytale/server/core/event/events/ecs/SwitchActiveSlotEvent.java new file mode 100644 index 0000000..e7c72c8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/ecs/SwitchActiveSlotEvent.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.server.core.event.events.ecs; + +import com.hypixel.hytale.component.system.CancellableEcsEvent; + +public class SwitchActiveSlotEvent extends CancellableEcsEvent { + private final int previousSlot; + private final int inventorySectionId; + private byte newSlot; + private final boolean serverRequest; + + public SwitchActiveSlotEvent(int inventorySectionId, int previousSlot, byte newSlot, boolean serverRequest) { + this.inventorySectionId = inventorySectionId; + this.previousSlot = previousSlot; + this.newSlot = newSlot; + this.serverRequest = serverRequest; + } + + public int getPreviousSlot() { + return this.previousSlot; + } + + public byte getNewSlot() { + return this.newSlot; + } + + public void setNewSlot(byte newSlot) { + this.newSlot = newSlot; + } + + public boolean isServerRequest() { + return this.serverRequest; + } + + public boolean isClientRequest() { + return !this.serverRequest; + } + + public int getInventorySectionId() { + return this.inventorySectionId; + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/ecs/UseBlockEvent.java b/src/com/hypixel/hytale/server/core/event/events/ecs/UseBlockEvent.java new file mode 100644 index 0000000..f490145 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/ecs/UseBlockEvent.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.server.core.event.events.ecs; + +import com.hypixel.hytale.component.system.EcsEvent; +import com.hypixel.hytale.component.system.ICancellableEcsEvent; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import javax.annotation.Nonnull; + +public abstract class UseBlockEvent extends EcsEvent { + @Nonnull + private final InteractionType interactionType; + @Nonnull + private final InteractionContext context; + @Nonnull + private final Vector3i targetBlock; + @Nonnull + private final BlockType blockType; + + public UseBlockEvent( + @Nonnull InteractionType interactionType, @Nonnull InteractionContext context, @Nonnull Vector3i targetBlock, @Nonnull BlockType blockType + ) { + this.interactionType = interactionType; + this.context = context; + this.targetBlock = targetBlock; + this.blockType = blockType; + } + + @Nonnull + public InteractionType getInteractionType() { + return this.interactionType; + } + + @Nonnull + public InteractionContext getContext() { + return this.context; + } + + @Nonnull + public Vector3i getTargetBlock() { + return this.targetBlock; + } + + @Nonnull + public BlockType getBlockType() { + return this.blockType; + } + + public static final class Post extends UseBlockEvent { + public Post(@Nonnull InteractionType interactionType, @Nonnull InteractionContext context, @Nonnull Vector3i targetBlock, @Nonnull BlockType blockType) { + super(interactionType, context, targetBlock, blockType); + } + } + + public static final class Pre extends UseBlockEvent implements ICancellableEcsEvent { + private boolean cancelled = false; + + public Pre(@Nonnull InteractionType interactionType, @Nonnull InteractionContext context, @Nonnull Vector3i targetBlock, @Nonnull BlockType blockType) { + super(interactionType, context, targetBlock, blockType); + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/entity/EntityEvent.java b/src/com/hypixel/hytale/server/core/event/events/entity/EntityEvent.java new file mode 100644 index 0000000..efa6cf8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/entity/EntityEvent.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.core.event.events.entity; + +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.server.core.entity.Entity; +import javax.annotation.Nonnull; + +public abstract class EntityEvent implements IEvent { + private final EntityType entity; + + public EntityEvent(EntityType entity) { + this.entity = entity; + } + + public EntityType getEntity() { + return this.entity; + } + + @Nonnull + @Override + public String toString() { + return "EntityEvent{entity=" + this.entity + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/entity/EntityRemoveEvent.java b/src/com/hypixel/hytale/server/core/event/events/entity/EntityRemoveEvent.java new file mode 100644 index 0000000..eb86c9a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/entity/EntityRemoveEvent.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.server.core.event.events.entity; + +import com.hypixel.hytale.server.core.entity.Entity; +import javax.annotation.Nonnull; + +public class EntityRemoveEvent extends EntityEvent { + public EntityRemoveEvent(Entity entity) { + super(entity); + } + + @Nonnull + @Override + public String toString() { + return "EntityRemoveEvent{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/entity/LivingEntityInventoryChangeEvent.java b/src/com/hypixel/hytale/server/core/event/events/entity/LivingEntityInventoryChangeEvent.java new file mode 100644 index 0000000..762b522 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/entity/LivingEntityInventoryChangeEvent.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.event.events.entity; + +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.transaction.Transaction; +import javax.annotation.Nonnull; + +public class LivingEntityInventoryChangeEvent extends EntityEvent { + private ItemContainer itemContainer; + private Transaction transaction; + + public LivingEntityInventoryChangeEvent(LivingEntity entity, ItemContainer itemContainer, Transaction transaction) { + super(entity); + this.itemContainer = itemContainer; + this.transaction = transaction; + } + + public ItemContainer getItemContainer() { + return this.itemContainer; + } + + public Transaction getTransaction() { + return this.transaction; + } + + @Nonnull + @Override + public String toString() { + return "LivingEntityInventoryChangeEvent{itemContainer=" + this.itemContainer + ", transaction=" + this.transaction + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/entity/LivingEntityUseBlockEvent.java b/src/com/hypixel/hytale/server/core/event/events/entity/LivingEntityUseBlockEvent.java new file mode 100644 index 0000000..8bfe0a9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/entity/LivingEntityUseBlockEvent.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.event.events.entity; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +@Deprecated(forRemoval = true) +public class LivingEntityUseBlockEvent implements IEvent { + private Ref ref; + private String blockType; + + public LivingEntityUseBlockEvent(Ref ref, String blockType) { + this.ref = ref; + this.blockType = blockType; + } + + public String getBlockType() { + return this.blockType; + } + + public Ref getRef() { + return this.ref; + } + + @Nonnull + @Override + public String toString() { + return "LivingEntityUseBlockEvent{blockType=" + this.blockType + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/permissions/GroupPermissionChangeEvent.java b/src/com/hypixel/hytale/server/core/event/events/permissions/GroupPermissionChangeEvent.java new file mode 100644 index 0000000..7f67d24 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/permissions/GroupPermissionChangeEvent.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.server.core.event.events.permissions; + +import com.hypixel.hytale.event.IEvent; +import java.util.Collections; +import java.util.Set; +import javax.annotation.Nonnull; + +public abstract class GroupPermissionChangeEvent implements IEvent { + @Nonnull + private final String groupName; + + protected GroupPermissionChangeEvent(@Nonnull String groupName) { + this.groupName = groupName; + } + + @Nonnull + public String getGroupName() { + return this.groupName; + } + + public static class Added extends GroupPermissionChangeEvent { + @Nonnull + private final Set addedPermissions; + + public Added(@Nonnull String groupName, @Nonnull Set addedPermissions) { + super(groupName); + this.addedPermissions = addedPermissions; + } + + @Nonnull + public Set getAddedPermissions() { + return Collections.unmodifiableSet(this.addedPermissions); + } + } + + public static class Removed extends GroupPermissionChangeEvent { + @Nonnull + private final Set removedPermissions; + + public Removed(@Nonnull String groupName, @Nonnull Set removedPermissions) { + super(groupName); + this.removedPermissions = removedPermissions; + } + + @Nonnull + public Set getRemovedPermissions() { + return Collections.unmodifiableSet(this.removedPermissions); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/permissions/PlayerGroupEvent.java b/src/com/hypixel/hytale/server/core/event/events/permissions/PlayerGroupEvent.java new file mode 100644 index 0000000..f65b68d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/permissions/PlayerGroupEvent.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.event.events.permissions; + +import java.util.UUID; +import javax.annotation.Nonnull; + +public class PlayerGroupEvent extends PlayerPermissionChangeEvent { + @Nonnull + private final String groupName; + + public PlayerGroupEvent(@Nonnull UUID playerUuid, @Nonnull String groupName) { + super(playerUuid); + this.groupName = groupName; + } + + @Nonnull + public String getGroupName() { + return this.groupName; + } + + public static class Added extends PlayerGroupEvent { + public Added(@Nonnull UUID playerUuid, @Nonnull String groupName) { + super(playerUuid, groupName); + } + } + + public static class Removed extends PlayerGroupEvent { + public Removed(@Nonnull UUID playerUuid, @Nonnull String groupName) { + super(playerUuid, groupName); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/permissions/PlayerPermissionChangeEvent.java b/src/com/hypixel/hytale/server/core/event/events/permissions/PlayerPermissionChangeEvent.java new file mode 100644 index 0000000..79fa4c8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/permissions/PlayerPermissionChangeEvent.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.server.core.event.events.permissions; + +import com.hypixel.hytale.event.IEvent; +import java.util.Collections; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; + +public abstract class PlayerPermissionChangeEvent implements IEvent { + @Nonnull + private final UUID playerUuid; + + protected PlayerPermissionChangeEvent(@Nonnull UUID playerUuid) { + this.playerUuid = playerUuid; + } + + @Nonnull + public UUID getPlayerUuid() { + return this.playerUuid; + } + + public static class GroupAdded extends PlayerPermissionChangeEvent { + @Nonnull + private final String groupName; + + public GroupAdded(@Nonnull UUID playerUuid, @Nonnull String groupName) { + super(playerUuid); + this.groupName = groupName; + } + + @Nonnull + public String getGroupName() { + return this.groupName; + } + } + + public static class GroupRemoved extends PlayerPermissionChangeEvent { + @Nonnull + private final String groupName; + + public GroupRemoved(@Nonnull UUID playerUuid, @Nonnull String groupName) { + super(playerUuid); + this.groupName = groupName; + } + + @Nonnull + public String getGroupName() { + return this.groupName; + } + } + + public static class PermissionsAdded extends PlayerPermissionChangeEvent { + @Nonnull + private final Set addedPermissions; + + public PermissionsAdded(@Nonnull UUID playerUuid, @Nonnull Set addedPermissions) { + super(playerUuid); + this.addedPermissions = addedPermissions; + } + + @Nonnull + public Set getAddedPermissions() { + return Collections.unmodifiableSet(this.addedPermissions); + } + } + + public static class PermissionsRemoved extends PlayerPermissionChangeEvent { + @Nonnull + private final Set removedPermissions; + + public PermissionsRemoved(@Nonnull UUID playerUuid, @Nonnull Set removedPermissions) { + super(playerUuid); + this.removedPermissions = removedPermissions; + } + + @Nonnull + public Set getRemovedPermissions() { + return Collections.unmodifiableSet(this.removedPermissions); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/player/AddPlayerToWorldEvent.java b/src/com/hypixel/hytale/server/core/event/events/player/AddPlayerToWorldEvent.java new file mode 100644 index 0000000..eeaa456 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/player/AddPlayerToWorldEvent.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.core.event.events.player; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class AddPlayerToWorldEvent implements IEvent { + @Nonnull + private final Holder holder; + @Nonnull + private final World world; + private boolean broadcastJoinMessage = true; + + public AddPlayerToWorldEvent(@Nonnull Holder holder, @Nonnull World world) { + this.holder = holder; + this.world = world; + } + + @Nonnull + public Holder getHolder() { + return this.holder; + } + + @Nonnull + public World getWorld() { + return this.world; + } + + public boolean shouldBroadcastJoinMessage() { + return this.broadcastJoinMessage; + } + + public void setBroadcastJoinMessage(boolean broadcastJoinMessage) { + this.broadcastJoinMessage = broadcastJoinMessage; + } + + @Nonnull + @Override + public String toString() { + return "AddPlayerToWorldEvent{world=" + this.world + ", broadcastJoinMessage=" + this.broadcastJoinMessage + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/player/DrainPlayerFromWorldEvent.java b/src/com/hypixel/hytale/server/core/event/events/player/DrainPlayerFromWorldEvent.java new file mode 100644 index 0000000..f2c47c9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/player/DrainPlayerFromWorldEvent.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.core.event.events.player; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class DrainPlayerFromWorldEvent implements IEvent { + private final Holder holder; + private World world; + private Transform transform; + + public DrainPlayerFromWorldEvent(Holder holder, World world, Transform transform) { + this.holder = holder; + this.world = world; + this.transform = transform; + } + + public Holder getHolder() { + return this.holder; + } + + public World getWorld() { + return this.world; + } + + public void setWorld(World world) { + this.world = world; + } + + public Transform getTransform() { + return this.transform; + } + + public void setTransform(Transform transform) { + this.transform = transform; + } + + @Nonnull + @Override + public String toString() { + return "DrainPlayerFromWorldEvent{world=" + this.world + ", transform=" + this.transform + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/player/PlayerChatEvent.java b/src/com/hypixel/hytale/server/core/event/events/player/PlayerChatEvent.java new file mode 100644 index 0000000..b12b8e7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/player/PlayerChatEvent.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.server.core.event.events.player; + +import com.hypixel.hytale.event.IAsyncEvent; +import com.hypixel.hytale.event.ICancellable; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import java.util.List; +import javax.annotation.Nonnull; + +public class PlayerChatEvent implements IAsyncEvent, ICancellable { + @Nonnull + public static final PlayerChatEvent.Formatter DEFAULT_FORMATTER = (playerRef, msg) -> Message.translation("server.chat.playerMessage") + .param("username", playerRef.getUsername()) + .param("message", msg); + @Nonnull + private PlayerRef sender; + @Nonnull + private List targets; + @Nonnull + private String content; + private PlayerChatEvent.Formatter formatter; + private boolean cancelled; + + public PlayerChatEvent(@Nonnull PlayerRef sender, @Nonnull List targets, @Nonnull String content) { + this.sender = sender; + this.targets = targets; + this.content = content; + this.formatter = DEFAULT_FORMATTER; + this.cancelled = false; + } + + @Nonnull + public PlayerRef getSender() { + return this.sender; + } + + public void setSender(@Nonnull PlayerRef sender) { + this.sender = sender; + } + + @Nonnull + public List getTargets() { + return this.targets; + } + + public void setTargets(@Nonnull List targets) { + this.targets = targets; + } + + @Nonnull + public String getContent() { + return this.content; + } + + public void setContent(@Nonnull String content) { + this.content = content; + } + + @Nonnull + public PlayerChatEvent.Formatter getFormatter() { + return this.formatter; + } + + public void setFormatter(@Nonnull PlayerChatEvent.Formatter formatter) { + this.formatter = formatter; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Nonnull + @Override + public String toString() { + return "PlayerChatEvent{message=" + + this.content + + ", targets=" + + this.targets + + ", formatter=" + + this.formatter + + ", cancelled=" + + this.cancelled + + "} " + + super.toString(); + } + + public interface Formatter { + @Nonnull + Message format(@Nonnull PlayerRef var1, @Nonnull String var2); + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/player/PlayerConnectEvent.java b/src/com/hypixel/hytale/server/core/event/events/player/PlayerConnectEvent.java new file mode 100644 index 0000000..e149d9f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/player/PlayerConnectEvent.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.server.core.event.events.player; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerConnectEvent implements IEvent { + private final Holder holder; + private final PlayerRef playerRef; + @Nullable + private World world; + + public PlayerConnectEvent(@Nonnull Holder holder, @Nonnull PlayerRef playerRef, @Nullable World world) { + this.holder = holder; + this.playerRef = playerRef; + this.world = world; + } + + public Holder getHolder() { + return this.holder; + } + + public PlayerRef getPlayerRef() { + return this.playerRef; + } + + @Nullable + @Deprecated + public Player getPlayer() { + return this.holder.getComponent(Player.getComponentType()); + } + + @Nullable + public World getWorld() { + return this.world; + } + + public void setWorld(@Nullable World world) { + this.world = world; + } + + @Nonnull + @Override + public String toString() { + return "PlayerConnectEvent{world=" + this.world + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/player/PlayerCraftEvent.java b/src/com/hypixel/hytale/server/core/event/events/player/PlayerCraftEvent.java new file mode 100644 index 0000000..a1d166e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/player/PlayerCraftEvent.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.core.event.events.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +@Deprecated(forRemoval = true) +public class PlayerCraftEvent extends PlayerEvent { + private final CraftingRecipe craftedRecipe; + private final int quantity; + + public PlayerCraftEvent(@Nonnull Ref ref, @Nonnull Player player, CraftingRecipe craftedRecipe, int quantity) { + super(ref, player); + this.craftedRecipe = craftedRecipe; + this.quantity = quantity; + } + + public CraftingRecipe getCraftedRecipe() { + return this.craftedRecipe; + } + + public int getQuantity() { + return this.quantity; + } + + @Nonnull + @Override + public String toString() { + return "PlayerCraftEvent{craftingRecipe=" + this.craftedRecipe + ", quantity=" + this.quantity + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/player/PlayerDisconnectEvent.java b/src/com/hypixel/hytale/server/core/event/events/player/PlayerDisconnectEvent.java new file mode 100644 index 0000000..3937b41 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/player/PlayerDisconnectEvent.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.server.core.event.events.player; + +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import javax.annotation.Nonnull; + +public class PlayerDisconnectEvent extends PlayerRefEvent { + public PlayerDisconnectEvent(@Nonnull PlayerRef playerRef) { + super(playerRef); + } + + @Nonnull + public PacketHandler.DisconnectReason getDisconnectReason() { + return this.playerRef.getPacketHandler().getDisconnectReason(); + } + + @Nonnull + @Override + public String toString() { + return "PlayerDisconnectEvent{playerRef=" + this.playerRef + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/player/PlayerEvent.java b/src/com/hypixel/hytale/server/core/event/events/player/PlayerEvent.java new file mode 100644 index 0000000..9780ba5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/player/PlayerEvent.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.core.event.events.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public abstract class PlayerEvent implements IEvent { + @Nonnull + private final Ref playerRef; + @Nonnull + private final Player player; + + public PlayerEvent(@Nonnull Ref playerRef, @Nonnull Player player) { + this.playerRef = playerRef; + this.player = player; + } + + @Nonnull + public Ref getPlayerRef() { + return this.playerRef; + } + + @Nonnull + public Player getPlayer() { + return this.player; + } + + @Nonnull + @Override + public String toString() { + return "PlayerEvent{player=" + this.player + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/player/PlayerInteractEvent.java b/src/com/hypixel/hytale/server/core/event/events/player/PlayerInteractEvent.java new file mode 100644 index 0000000..104062c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/player/PlayerInteractEvent.java @@ -0,0 +1,94 @@ +package com.hypixel.hytale.server.core.event.events.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.event.ICancellable; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +@Deprecated +public class PlayerInteractEvent extends PlayerEvent implements ICancellable { + private final InteractionType actionType; + private final long clientUseTime; + private final ItemStack itemInHand; + private final Vector3i targetBlock; + private final Ref targetRef; + private final Entity targetEntity; + private boolean cancelled; + + public PlayerInteractEvent( + @Nonnull Ref ref, + @Nonnull Player player, + long clientUseTime, + InteractionType actionType, + ItemStack itemInHand, + Vector3i targetBlock, + Ref targetRef, + Entity targetEntity + ) { + super(ref, player); + this.actionType = actionType; + this.clientUseTime = clientUseTime; + this.itemInHand = itemInHand; + this.targetBlock = targetBlock; + this.targetRef = targetRef; + this.targetEntity = targetEntity; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + public InteractionType getActionType() { + return this.actionType; + } + + public long getClientUseTime() { + return this.clientUseTime; + } + + public ItemStack getItemInHand() { + return this.itemInHand; + } + + public Vector3i getTargetBlock() { + return this.targetBlock; + } + + public Entity getTargetEntity() { + return this.targetEntity; + } + + public Ref getTargetRef() { + return this.targetRef; + } + + @Nonnull + @Override + public String toString() { + return "PlayerInteractEvent{actionType=" + + this.actionType + + ", clientUseTime=" + + this.clientUseTime + + ", itemInHand=" + + this.itemInHand + + ", targetBlock=" + + this.targetBlock + + ", targetEntity=" + + this.targetEntity + + ", cancelled=" + + this.cancelled + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/player/PlayerMouseButtonEvent.java b/src/com/hypixel/hytale/server/core/event/events/player/PlayerMouseButtonEvent.java new file mode 100644 index 0000000..829fba9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/player/PlayerMouseButtonEvent.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.server.core.event.events.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.event.ICancellable; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.MouseButtonEvent; +import com.hypixel.hytale.protocol.Vector2f; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PlayerMouseButtonEvent extends PlayerEvent implements ICancellable { + @Nonnull + private final PlayerRef playerRef; + private final long clientUseTime; + private final Item itemInHand; + private final Vector3i targetBlock; + private final Entity targetEntity; + private final Vector2f screenPoint; + private final MouseButtonEvent mouseButton; + private boolean cancelled; + + public PlayerMouseButtonEvent( + @Nonnull Ref ref, + @Nonnull Player player, + @Nonnull PlayerRef playerRefComponent, + long clientUseTime, + @Nonnull Item itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull Entity targetEntity, + @Nonnull Vector2f screenPoint, + @Nonnull MouseButtonEvent mouseButton + ) { + super(ref, player); + this.playerRef = playerRefComponent; + this.clientUseTime = clientUseTime; + this.itemInHand = itemInHand; + this.targetBlock = targetBlock; + this.targetEntity = targetEntity; + this.screenPoint = screenPoint; + this.mouseButton = mouseButton; + } + + @Nonnull + public PlayerRef getPlayerRefComponent() { + return this.playerRef; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + public long getClientUseTime() { + return this.clientUseTime; + } + + public Item getItemInHand() { + return this.itemInHand; + } + + public Vector3i getTargetBlock() { + return this.targetBlock; + } + + public Entity getTargetEntity() { + return this.targetEntity; + } + + public Vector2f getScreenPoint() { + return this.screenPoint; + } + + public MouseButtonEvent getMouseButton() { + return this.mouseButton; + } + + @Nonnull + @Override + public String toString() { + return "PlayerMouseButtonEvent{clientUseTime=" + + this.clientUseTime + + ", itemInHand=" + + this.itemInHand + + ", targetBlock=" + + this.targetBlock + + ", targetEntity=" + + this.targetEntity + + ", screenPoint=" + + this.screenPoint + + ", mouseButton=" + + this.mouseButton + + ", cancelled=" + + this.cancelled + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/player/PlayerMouseMotionEvent.java b/src/com/hypixel/hytale/server/core/event/events/player/PlayerMouseMotionEvent.java new file mode 100644 index 0000000..45f2843 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/player/PlayerMouseMotionEvent.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.server.core.event.events.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.event.ICancellable; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.MouseMotionEvent; +import com.hypixel.hytale.protocol.Vector2f; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PlayerMouseMotionEvent extends PlayerEvent implements ICancellable { + private final long clientUseTime; + private final Item itemInHand; + private final Vector3i targetBlock; + private final Entity targetEntity; + private final Vector2f screenPoint; + private final MouseMotionEvent mouseMotion; + private boolean cancelled; + + public PlayerMouseMotionEvent( + @Nonnull Ref ref, + @Nonnull Player player, + long clientUseTime, + Item itemInHand, + Vector3i targetBlock, + Entity targetEntity, + Vector2f screenPoint, + MouseMotionEvent mouseMotion + ) { + super(ref, player); + this.clientUseTime = clientUseTime; + this.itemInHand = itemInHand; + this.targetBlock = targetBlock; + this.targetEntity = targetEntity; + this.screenPoint = screenPoint; + this.mouseMotion = mouseMotion; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + public long getClientUseTime() { + return this.clientUseTime; + } + + public Item getItemInHand() { + return this.itemInHand; + } + + public Vector3i getTargetBlock() { + return this.targetBlock; + } + + public Entity getTargetEntity() { + return this.targetEntity; + } + + public Vector2f getScreenPoint() { + return this.screenPoint; + } + + public MouseMotionEvent getMouseMotion() { + return this.mouseMotion; + } + + @Nonnull + @Override + public String toString() { + return "PlayerMouseMotionEvent{clientUseTime=" + + this.clientUseTime + + ", itemInHand=" + + this.itemInHand + + ", targetBlock=" + + this.targetBlock + + ", targetEntity=" + + this.targetEntity + + ", screenPoint=" + + this.screenPoint + + ", mouseMotion=" + + this.mouseMotion + + ", cancelled=" + + this.cancelled + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/player/PlayerReadyEvent.java b/src/com/hypixel/hytale/server/core/event/events/player/PlayerReadyEvent.java new file mode 100644 index 0000000..f6e7b9a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/player/PlayerReadyEvent.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.core.event.events.player; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PlayerReadyEvent extends PlayerEvent { + private final int readyId; + + public PlayerReadyEvent(@Nonnull Ref ref, @Nonnull Player player, int readyId) { + super(ref, player); + this.readyId = readyId; + } + + public int getReadyId() { + return this.readyId; + } + + @Nonnull + @Override + public String toString() { + return "PlayerReadyEvent{readyId=" + this.readyId + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/player/PlayerRefEvent.java b/src/com/hypixel/hytale/server/core/event/events/player/PlayerRefEvent.java new file mode 100644 index 0000000..8b65831 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/player/PlayerRefEvent.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.core.event.events.player; + +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import javax.annotation.Nonnull; + +public abstract class PlayerRefEvent implements IEvent { + @Nonnull + final PlayerRef playerRef; + + public PlayerRefEvent(@Nonnull PlayerRef playerRef) { + this.playerRef = playerRef; + } + + @Nonnull + public PlayerRef getPlayerRef() { + return this.playerRef; + } + + @Nonnull + @Override + public String toString() { + return "PlayerRefEvent{playerRef=" + this.playerRef + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/player/PlayerSetupConnectEvent.java b/src/com/hypixel/hytale/server/core/event/events/player/PlayerSetupConnectEvent.java new file mode 100644 index 0000000..dbbc4c4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/player/PlayerSetupConnectEvent.java @@ -0,0 +1,135 @@ +package com.hypixel.hytale.server.core.event.events.player; + +import com.hypixel.hytale.event.ICancellable; +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.HostAddress; +import com.hypixel.hytale.protocol.packets.auth.ClientReferral; +import com.hypixel.hytale.server.core.auth.PlayerAuthentication; +import com.hypixel.hytale.server.core.io.PacketHandler; +import java.util.Objects; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerSetupConnectEvent implements IEvent, ICancellable { + public static final String DEFAULT_REASON = "You have been disconnected from the server!"; + private final PacketHandler packetHandler; + private final String username; + private final UUID uuid; + private final PlayerAuthentication auth; + private final byte[] referralData; + private final HostAddress referralSource; + private boolean cancelled; + private String reason; + private ClientReferral clientReferral; + + public PlayerSetupConnectEvent( + PacketHandler packetHandler, String username, UUID uuid, PlayerAuthentication auth, byte[] referralData, HostAddress referralSource + ) { + this.packetHandler = packetHandler; + this.username = username; + this.uuid = uuid; + this.auth = auth; + this.referralData = referralData; + this.referralSource = referralSource; + this.reason = "You have been disconnected from the server!"; + this.cancelled = false; + } + + public PacketHandler getPacketHandler() { + return this.packetHandler; + } + + public UUID getUuid() { + return this.uuid; + } + + public String getUsername() { + return this.username; + } + + public PlayerAuthentication getAuth() { + return this.auth; + } + + @Nullable + public byte[] getReferralData() { + return this.referralData; + } + + public boolean isReferralConnection() { + return this.referralData != null && this.referralData.length > 0; + } + + @Nullable + public HostAddress getReferralSource() { + return this.referralSource; + } + + @Nullable + public ClientReferral getClientReferral() { + return this.clientReferral; + } + + public void referToServer(@Nonnull String host, int port) { + this.referToServer(host, port, null); + } + + public void referToServer(@Nonnull String host, int port, @Nullable byte[] data) { + int MAX_REFERRAL_DATA_SIZE = 4096; + Objects.requireNonNull(host, "Host cannot be null"); + if (port > 0 && port <= 65535) { + if (data != null && data.length > 4096) { + throw new IllegalArgumentException("Referral data exceeds maximum size of 4096 bytes (got " + data.length + ")"); + } else { + HytaleLogger.getLogger() + .at(Level.INFO) + .log("Referring player %s (%s) to %s:%d with %d bytes of data", this.username, this.uuid, host, port, data != null ? data.length : 0); + this.clientReferral = new ClientReferral(new HostAddress(host, (short)port), data); + } + } else { + throw new IllegalArgumentException("Port must be between 1 and 65535"); + } + } + + public String getReason() { + return this.reason; + } + + public void setReason(String reason) { + Objects.requireNonNull(reason, "Reason can't be null"); + this.reason = reason; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Nonnull + @Override + public String toString() { + return "PlayerSetupConnectEvent{username='" + + this.username + + "', uuid=" + + this.uuid + + ", auth=" + + this.auth + + ", referralData=" + + (this.referralData != null ? this.referralData.length + " bytes" : "null") + + ", referralSource=" + + (this.referralSource != null ? this.referralSource.host + ":" + this.referralSource.port : "null") + + ", cancelled=" + + this.cancelled + + ", reason='" + + this.reason + + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/event/events/player/PlayerSetupDisconnectEvent.java b/src/com/hypixel/hytale/server/core/event/events/player/PlayerSetupDisconnectEvent.java new file mode 100644 index 0000000..f8d6759 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/event/events/player/PlayerSetupDisconnectEvent.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.core.event.events.player; + +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.server.core.auth.PlayerAuthentication; +import com.hypixel.hytale.server.core.io.PacketHandler; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class PlayerSetupDisconnectEvent implements IEvent { + private final String username; + private final UUID uuid; + private final PlayerAuthentication auth; + private final PacketHandler.DisconnectReason disconnectReason; + + public PlayerSetupDisconnectEvent(String username, UUID uuid, PlayerAuthentication auth, PacketHandler.DisconnectReason disconnectReason) { + this.username = username; + this.uuid = uuid; + this.auth = auth; + this.disconnectReason = disconnectReason; + } + + public String getUsername() { + return this.username; + } + + public UUID getUuid() { + return this.uuid; + } + + public PlayerAuthentication getAuth() { + return this.auth; + } + + public PacketHandler.DisconnectReason getDisconnectReason() { + return this.disconnectReason; + } + + @Nonnull + @Override + public String toString() { + return "PlayerSetupDisconnectEvent{username='" + + this.username + + "', uuid=" + + this.uuid + + ", auth=" + + this.auth + + ", disconnectReason=" + + this.disconnectReason + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/Inventory.java b/src/com/hypixel/hytale/server/core/inventory/Inventory.java new file mode 100644 index 0000000..730d337 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/Inventory.java @@ -0,0 +1,919 @@ +package com.hypixel.hytale.server.core.inventory; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.EventRegistration; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.protocol.InteractionChainData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.ItemArmorSlot; +import com.hypixel.hytale.protocol.PickupLocation; +import com.hypixel.hytale.protocol.SmartMoveType; +import com.hypixel.hytale.protocol.packets.inventory.UpdatePlayerInventory; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemArmor; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemUtility; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemWeapon; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.entity.StatModifiersManager; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.windows.ItemContainerWindow; +import com.hypixel.hytale.server.core.entity.entities.player.windows.Window; +import com.hypixel.hytale.server.core.event.events.entity.LivingEntityInventoryChangeEvent; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.inventory.container.EmptyItemContainer; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.container.ItemContainerUtil; +import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer; +import com.hypixel.hytale.server.core.inventory.container.SortType; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.MoveTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.SlotTransaction; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.ChangeActiveSlotInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.UUIDUtil; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Inventory implements NetworkSerializable { + public static final short DEFAULT_HOTBAR_CAPACITY = 9; + public static final short DEFAULT_UTILITY_CAPACITY = 4; + public static final short DEFAULT_TOOLS_CAPACITY = 23; + public static final short DEFAULT_ARMOR_CAPACITY = (short)ItemArmorSlot.VALUES.length; + public static final short DEFAULT_STORAGE_ROWS = 4; + public static final short DEFAULT_STORAGE_COLUMNS = 9; + public static final short DEFAULT_STORAGE_CAPACITY = 36; + public static final int HOTBAR_SECTION_ID = -1; + public static final int STORAGE_SECTION_ID = -2; + public static final int ARMOR_SECTION_ID = -3; + public static final int UTILITY_SECTION_ID = -5; + public static final int TOOLS_SECTION_ID = -8; + public static final int BACKPACK_SECTION_ID = -9; + public static final byte INACTIVE_SLOT_INDEX = -1; + public static final int VERSION = 4; + public static final BuilderCodec CODEC = BuilderCodec.builder(Inventory.class, () -> new Inventory(null)) + .versioned() + .codecVersion(4) + .append(new KeyedCodec<>("Storage", ItemContainer.CODEC), (o, i) -> o.storage = i, o -> o.storage) + .add() + .append(new KeyedCodec<>("Armor", ItemContainer.CODEC), (o, i) -> o.armor = i, o -> o.armor) + .add() + .append(new KeyedCodec<>("HotBar", ItemContainer.CODEC), (o, i) -> o.hotbar = i, o -> o.hotbar) + .add() + .append(new KeyedCodec<>("Utility", ItemContainer.CODEC), (o, i) -> o.utility = i, o -> o.utility) + .add() + .append(new KeyedCodec<>("Backpack", ItemContainer.CODEC), (o, i) -> o.backpack = i, o -> o.backpack) + .add() + .append(new KeyedCodec<>("ActiveHotbarSlot", Codec.BYTE), (o, i) -> o.activeHotbarSlot = i, o -> o.activeHotbarSlot) + .add() + .append(new KeyedCodec<>("Tool", ItemContainer.CODEC), (o, i) -> o.tools = i, o -> o.tools) + .add() + .append(new KeyedCodec<>("ActiveToolsSlot", Codec.BYTE), (o, i) -> o.activeToolsSlot = i, o -> o.activeToolsSlot) + .add() + .append(new KeyedCodec<>("ActiveUtilitySlot", Codec.BYTE), (o, i) -> o.activeUtilitySlot = i, o -> o.activeUtilitySlot) + .add() + .append(new KeyedCodec<>("SortType", new EnumCodec<>(SortType.class, EnumCodec.EnumStyle.LEGACY)), (o, i) -> o.sortType = i, o -> o.sortType) + .setVersionRange(0, 3) + .add() + .append(new KeyedCodec<>("SortType", new EnumCodec<>(SortType.class)), (o, i) -> o.sortType = i, o -> o.sortType) + .setVersionRange(4, 4) + .add() + .afterDecode(Inventory::postDecode) + .build(); + private final AtomicBoolean isDirty = new AtomicBoolean(); + private final AtomicBoolean needsSaving = new AtomicBoolean(); + private ItemContainer storage = EmptyItemContainer.INSTANCE; + private ItemContainer armor = EmptyItemContainer.INSTANCE; + private ItemContainer hotbar = EmptyItemContainer.INSTANCE; + private ItemContainer utility = EmptyItemContainer.INSTANCE; + @Deprecated + private ItemContainer tools = EmptyItemContainer.INSTANCE; + private ItemContainer backpack = EmptyItemContainer.INSTANCE; + private CombinedItemContainer combinedHotbarFirst; + private CombinedItemContainer combinedStorageFirst; + private CombinedItemContainer combinedBackpackStorageHotbar; + private CombinedItemContainer combinedStorageHotbarBackpack; + private CombinedItemContainer combinedArmorHotbarStorage; + private CombinedItemContainer combinedArmorHotbarUtilityStorage; + private CombinedItemContainer combinedHotbarUtilityConsumableStorage; + private CombinedItemContainer combinedEverything; + private byte activeHotbarSlot; + private byte activeUtilitySlot = -1; + private byte activeToolsSlot = -1; + @Nullable + private LivingEntity entity; + @Deprecated + private SortType sortType = SortType.NAME; + @Nullable + private EventRegistration armorChange; + @Nullable + private EventRegistration storageChange; + @Nullable + private EventRegistration hotbarChange; + @Nullable + private EventRegistration utilityChange; + @Nullable + private EventRegistration toolChange; + @Nullable + private EventRegistration backpackChange; + private boolean _usingToolsItem = false; + + private Inventory(Void dummy) { + } + + public Inventory() { + this((short)36, DEFAULT_ARMOR_CAPACITY, (short)9, (short)4, (short)23); + } + + public Inventory(short storageCapacity, short armorCapacity, short hotbarCapacity, short utilityCapacity, short toolCapacity) { + this( + (ItemContainer)(storageCapacity == 0 ? EmptyItemContainer.INSTANCE : new SimpleItemContainer(storageCapacity)), + (ItemContainer)(armorCapacity == 0 ? EmptyItemContainer.INSTANCE : new SimpleItemContainer(armorCapacity)), + (ItemContainer)(hotbarCapacity == 0 ? EmptyItemContainer.INSTANCE : new SimpleItemContainer(hotbarCapacity)), + (ItemContainer)(utilityCapacity == 0 ? EmptyItemContainer.INSTANCE : new SimpleItemContainer(utilityCapacity)), + (ItemContainer)(toolCapacity == 0 ? EmptyItemContainer.INSTANCE : new SimpleItemContainer(toolCapacity)), + EmptyItemContainer.INSTANCE + ); + } + + public Inventory(ItemContainer storage, ItemContainer armor, ItemContainer hotbar, ItemContainer utility, ItemContainer tools, ItemContainer backpack) { + this.storage = storage; + this.armor = ItemContainerUtil.trySetArmorFilters(armor); + this.hotbar = hotbar; + this.utility = ItemContainerUtil.trySetSlotFilters( + utility, (type, container, slot, itemStack) -> itemStack == null || itemStack.getItem().getUtility().isUsable() + ); + this.tools = tools; + this.backpack = backpack; + this.buildCombinedContains(); + this.registerChangeEvents(); + } + + protected void registerChangeEvents() { + this.storageChange = this.storage + .registerChangeEvent( + e -> { + this.markChanged(); + IEventDispatcher dispatcher = HytaleServer.get() + .getEventBus() + .dispatchFor(LivingEntityInventoryChangeEvent.class, this.entity.getWorld().getName()); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new LivingEntityInventoryChangeEvent(this.entity, e.container(), e.transaction())); + } + } + ); + this.armorChange = this.armor + .registerChangeEvent( + e -> { + this.markChanged(); + IEventDispatcher dispatcher = HytaleServer.get() + .getEventBus() + .dispatchFor(LivingEntityInventoryChangeEvent.class, this.entity.getWorld().getName()); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new LivingEntityInventoryChangeEvent(this.entity, e.container(), e.transaction())); + } + + this.entity.invalidateEquipmentNetwork(); + } + ); + this.hotbarChange = this.hotbar + .registerChangeEvent( + e -> { + this.markChanged(); + IEventDispatcher dispatcher = HytaleServer.get() + .getEventBus() + .dispatchFor(LivingEntityInventoryChangeEvent.class, this.entity.getWorld().getName()); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new LivingEntityInventoryChangeEvent(this.entity, e.container(), e.transaction())); + } + + if (this.activeHotbarSlot != -1 && this.entity != null && e.transaction().wasSlotModified(this.activeHotbarSlot)) { + if (e.transaction() instanceof SlotTransaction slot && ItemStack.isEquivalentType(slot.getSlotBefore(), slot.getSlotAfter())) { + return; + } + + StatModifiersManager statModifiersManager = this.entity.getStatModifiersManager(); + this.entity.invalidateEquipmentNetwork(); + statModifiersManager.setRecalculate(true); + ItemStack itemStack = this.getItemInHand(); + if (itemStack == null) { + return; + } + + ItemWeapon itemWeapon = itemStack.getItem().getWeapon(); + if (itemWeapon == null) { + return; + } + + int[] entityStatsToClear = itemWeapon.getEntityStatsToClear(); + if (entityStatsToClear == null) { + return; + } + + statModifiersManager.queueEntityStatsToClear(entityStatsToClear); + } + } + ); + this.utilityChange = this.utility + .registerChangeEvent( + e -> { + this.markChanged(); + IEventDispatcher dispatcher = HytaleServer.get() + .getEventBus() + .dispatchFor(LivingEntityInventoryChangeEvent.class, this.entity.getWorld().getName()); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new LivingEntityInventoryChangeEvent(this.entity, e.container(), e.transaction())); + } + + if (this.activeUtilitySlot != -1 && this.entity != null && e.transaction().wasSlotModified(this.activeUtilitySlot)) { + if (e.transaction() instanceof SlotTransaction slot && ItemStack.isEquivalentType(slot.getSlotBefore(), slot.getSlotAfter())) { + return; + } + + StatModifiersManager statModifiersManager = this.entity.getStatModifiersManager(); + this.entity.invalidateEquipmentNetwork(); + statModifiersManager.setRecalculate(true); + ItemStack itemStack = this.getUtilityItem(); + if (itemStack == null) { + return; + } + + ItemUtility itemUtility = itemStack.getItem().getUtility(); + if (itemUtility == null) { + return; + } + + int[] entityStatsToClear = itemUtility.getEntityStatsToClear(); + if (entityStatsToClear == null) { + return; + } + + statModifiersManager.queueEntityStatsToClear(entityStatsToClear); + } + } + ); + this.toolChange = this.tools + .registerChangeEvent( + e -> { + this.markChanged(); + IEventDispatcher dispatcher = HytaleServer.get() + .getEventBus() + .dispatchFor(LivingEntityInventoryChangeEvent.class, this.entity.getWorld().getName()); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new LivingEntityInventoryChangeEvent(this.entity, e.container(), e.transaction())); + } + } + ); + this.registerBackpackListener(); + } + + private void registerBackpackListener() { + this.unregisterBackpackChange(); + this.backpackChange = this.backpack + .registerChangeEvent( + e -> { + this.markChanged(); + IEventDispatcher dispatcher = HytaleServer.get() + .getEventBus() + .dispatchFor(LivingEntityInventoryChangeEvent.class, this.entity.getWorld().getName()); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new LivingEntityInventoryChangeEvent(this.entity, e.container(), e.transaction())); + } + } + ); + } + + public void unregister() { + this.entity = null; + if (this.storageChange != null) { + this.storageChange.unregister(); + this.storageChange = null; + } + + if (this.armorChange != null) { + this.armorChange.unregister(); + this.armorChange = null; + } + + if (this.hotbarChange != null) { + this.hotbarChange.unregister(); + this.hotbarChange = null; + } + + if (this.utilityChange != null) { + this.utilityChange.unregister(); + this.utilityChange = null; + } + + if (this.toolChange != null) { + this.toolChange.unregister(); + this.toolChange = null; + } + + this.unregisterBackpackChange(); + } + + private void unregisterBackpackChange() { + if (this.backpackChange != null) { + this.backpackChange.unregister(); + this.backpackChange = null; + } + } + + public void markChanged() { + this.isDirty.set(true); + this.needsSaving.set(true); + } + + public void moveItem(int fromSectionId, int fromSlotId, int quantity, int toSectionId, int toSlotId) { + ItemContainer fromContainer = this.getSectionById(fromSectionId); + if (fromContainer != null) { + ItemContainer toContainer = this.getSectionById(toSectionId); + if (toContainer != null) { + if (this.entity instanceof Player + && (toSectionId == -1 && this.activeHotbarSlot == toSlotId || fromSectionId == -1 && this.activeHotbarSlot == fromSlotId)) { + ItemStack fromItem = fromContainer.getItemStack((short)fromSlotId); + ItemStack currentItem = toContainer.getItemStack((short)toSlotId); + if (ItemStack.isStackableWith(fromItem, currentItem) || ItemStack.isSameItemType(fromItem, currentItem)) { + fromContainer.moveItemStackFromSlotToSlot((short)fromSlotId, quantity, toContainer, (short)toSlotId); + return; + } + + int interactionSlot = toSectionId == -1 && this.activeHotbarSlot == toSlotId ? toSlotId : this.activeHotbarSlot; + Ref ref = this.entity.getReference(); + Store store = ref.getStore(); + InteractionManager interactionManagerComponent = store.getComponent(ref, InteractionModule.get().getInteractionManagerComponent()); + if (interactionManagerComponent != null) { + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + InteractionContext context = InteractionContext.forInteraction(interactionManagerComponent, ref, InteractionType.SwapFrom, store); + context.getMetaStore().putMetaObject(Interaction.TARGET_SLOT, interactionSlot); + context.getMetaStore().putMetaObject(ChangeActiveSlotInteraction.PLACE_MOVED_ITEM, () -> { + fromContainer.moveItemStackFromSlotToSlot((short)fromSlotId, quantity, toContainer, (short)toSlotId); + playerRefComponent.getPacketHandler().write(this.toPacket()); + }); + String interactions = context.getRootInteractionId(InteractionType.SwapFrom); + InteractionChainData data = new InteractionChainData(-1, UUIDUtil.EMPTY_UUID, null, null, null, -interactionSlot - 1, null); + InteractionChain chain = interactionManagerComponent.initChain( + data, InteractionType.SwapFrom, context, RootInteraction.getRootInteractionOrUnknown(interactions), null, false + ); + interactionManagerComponent.queueExecuteChain(chain); + return; + } + } + + fromContainer.moveItemStackFromSlotToSlot((short)fromSlotId, quantity, toContainer, (short)toSlotId); + } + } + } + + public void smartMoveItem(int fromSectionId, int fromSlotId, int quantity, @Nonnull SmartMoveType moveType) { + ItemContainer targetContainer = this.getSectionById(fromSectionId); + if (targetContainer != null) { + switch (moveType) { + case EquipOrMergeStack: + ItemStack itemStack = targetContainer.getItemStack((short)fromSlotId); + Item item = itemStack.getItem(); + ItemArmor itemArmor = item.getArmor(); + if (itemArmor != null && fromSectionId != -3) { + targetContainer.moveItemStackFromSlotToSlot((short)fromSlotId, quantity, this.armor, (short)itemArmor.getArmorSlot().ordinal()); + return; + } + + if (this.entity instanceof Player) { + for (Window window : ((Player)this.entity).getWindowManager().getWindows()) { + if (window instanceof ItemContainerWindow) { + ((ItemContainerWindow)window).getItemContainer().combineItemStacksIntoSlot(targetContainer, (short)fromSlotId); + } + } + } + + this.combinedHotbarFirst.combineItemStacksIntoSlot(targetContainer, (short)fromSlotId); + break; + case PutInHotbarOrWindow: + if (fromSectionId >= 0) { + targetContainer.moveItemStackFromSlot((short)fromSlotId, quantity, this.combinedHotbarFirst); + return; + } + + if (this.entity instanceof Player) { + for (Window windowx : ((Player)this.entity).getWindowManager().getWindows()) { + if (windowx instanceof ItemContainerWindow) { + ItemContainer itemContainer = ((ItemContainerWindow)windowx).getItemContainer(); + MoveTransaction transaction = targetContainer.moveItemStackFromSlot((short)fromSlotId, quantity, itemContainer); + ItemStack remainder = transaction.getAddTransaction().getRemainder(); + if (ItemStack.isEmpty(remainder) || remainder.getQuantity() != quantity) { + return; + } + } + } + } + + switch (fromSectionId) { + case -2: + targetContainer.moveItemStackFromSlot((short)fromSlotId, quantity, this.hotbar); + return; + case -1: + targetContainer.moveItemStackFromSlot((short)fromSlotId, quantity, this.storage); + return; + default: + targetContainer.moveItemStackFromSlot((short)fromSlotId, quantity, this.combinedHotbarFirst); + return; + } + case PutInHotbarOrBackpack: + if (fromSectionId == -9) { + targetContainer.moveItemStackFromSlot((short)fromSlotId, quantity, this.combinedHotbarFirst); + } else { + targetContainer.moveItemStackFromSlot((short)fromSlotId, quantity, this.combinedBackpackStorageHotbar); + } + } + } + } + + @Nullable + public ListTransaction> takeAll(int inventorySectionId) { + ItemContainer sectionById = this.getSectionById(inventorySectionId); + return sectionById != null ? sectionById.moveAllItemStacksTo(this.combinedArmorHotbarStorage) : null; + } + + @Nullable + public ListTransaction> putAll(int inventorySectionId) { + ItemContainer sectionById = this.getSectionById(inventorySectionId); + return sectionById != null ? this.storage.moveAllItemStacksTo(sectionById) : null; + } + + @Nullable + public ListTransaction> quickStack(int inventorySectionId) { + ItemContainer sectionById = this.getSectionById(inventorySectionId); + return sectionById != null ? this.combinedHotbarFirst.quickStackTo(sectionById) : null; + } + + @Nonnull + public List dropAllItemStacks() { + List items = new ObjectArrayList<>(); + items.addAll(this.storage.dropAllItemStacks()); + items.addAll(this.armor.dropAllItemStacks()); + items.addAll(this.hotbar.dropAllItemStacks()); + items.addAll(this.utility.dropAllItemStacks()); + items.addAll(this.backpack.dropAllItemStacks()); + return items; + } + + public void clear() { + this.storage.clear(); + this.armor.clear(); + this.hotbar.clear(); + this.utility.clear(); + this.backpack.clear(); + } + + public ItemContainer getStorage() { + return this.storage; + } + + public ItemContainer getArmor() { + return this.armor; + } + + public ItemContainer getHotbar() { + return this.hotbar; + } + + public ItemContainer getUtility() { + return this.utility; + } + + public ItemContainer getTools() { + return this.tools; + } + + public ItemContainer getBackpack() { + return this.backpack; + } + + public void resizeBackpack(short capacity, List remainder) { + if (capacity > 0) { + this.backpack = ItemContainer.ensureContainerCapacity(this.backpack, capacity, SimpleItemContainer::new, remainder); + } else { + this.backpack = ItemContainer.copy(this.backpack, EmptyItemContainer.INSTANCE, remainder); + } + + this.buildCombinedContains(); + if (this.entity != null) { + this.registerBackpackListener(); + } + + this.markChanged(); + } + + public CombinedItemContainer getCombinedHotbarFirst() { + return this.combinedHotbarFirst; + } + + public CombinedItemContainer getCombinedStorageFirst() { + return this.combinedStorageFirst; + } + + public CombinedItemContainer getCombinedBackpackStorageHotbar() { + return this.combinedBackpackStorageHotbar; + } + + public CombinedItemContainer getCombinedArmorHotbarStorage() { + return this.combinedArmorHotbarStorage; + } + + public CombinedItemContainer getCombinedArmorHotbarUtilityStorage() { + return this.combinedArmorHotbarUtilityStorage; + } + + public CombinedItemContainer getCombinedHotbarUtilityConsumableStorage() { + return this.combinedHotbarUtilityConsumableStorage; + } + + public CombinedItemContainer getCombinedEverything() { + return this.combinedEverything; + } + + @Nonnull + public ItemContainer getContainerForItemPickup(@Nonnull Item item, PlayerSettings playerSettings) { + if (item.getArmor() != null) { + return playerSettings.armorItemsPreferredPickupLocation() == PickupLocation.Hotbar ? this.getCombinedHotbarFirst() : this.getCombinedStorageFirst(); + } else if (item.getWeapon() != null || item.getTool() != null) { + return playerSettings.weaponAndToolItemsPreferredPickupLocation() == PickupLocation.Hotbar + ? this.getCombinedHotbarFirst() + : this.getCombinedStorageFirst(); + } else if (item.getUtility().isUsable()) { + return playerSettings.usableItemsItemsPreferredPickupLocation() == PickupLocation.Hotbar + ? this.getCombinedHotbarFirst() + : this.getCombinedStorageFirst(); + } else { + BlockType blockType = item.hasBlockType() ? BlockType.getAssetMap().getAsset(item.getBlockId()) : BlockType.EMPTY; + if (blockType == null) { + blockType = BlockType.EMPTY; + } + + if (blockType.getMaterial() == BlockMaterial.Solid) { + return playerSettings.solidBlockItemsPreferredPickupLocation() == PickupLocation.Hotbar + ? this.getCombinedHotbarFirst() + : this.getCombinedStorageFirst(); + } else { + return playerSettings.miscItemsPreferredPickupLocation() == PickupLocation.Hotbar ? this.getCombinedHotbarFirst() : this.getCombinedStorageFirst(); + } + } + } + + public void setActiveSlot(int inventorySectionId, byte slot) { + int[] entityStatsToClear = null; + switch (inventorySectionId) { + case -8: + this.activeToolsSlot = slot; + break; + case -5: + this.activeUtilitySlot = slot; + ItemStack itemStack = this.getUtilityItem(); + if (itemStack != null) { + ItemUtility utility = itemStack.getItem().getUtility(); + entityStatsToClear = utility.getEntityStatsToClear(); + } + break; + case -1: + this.activeHotbarSlot = slot; + ItemStack itemStackx = this.getItemInHand(); + if (itemStackx != null) { + ItemWeapon weapon = itemStackx.getItem().getWeapon(); + if (weapon != null) { + entityStatsToClear = weapon.getEntityStatsToClear(); + } + } + break; + default: + throw new IllegalArgumentException("Inventory section with id " + inventorySectionId + " cannot select an active slot"); + } + + StatModifiersManager statModifiersManager = this.entity.getStatModifiersManager(); + this.entity.invalidateEquipmentNetwork(); + statModifiersManager.setRecalculate(true); + if (entityStatsToClear != null) { + statModifiersManager.queueEntityStatsToClear(entityStatsToClear); + } + } + + public byte getActiveSlot(int inventorySectionId) { + return switch (inventorySectionId) { + case -8 -> this.activeToolsSlot; + case -5 -> this.activeUtilitySlot; + case -1 -> this.activeHotbarSlot; + default -> throw new IllegalArgumentException("Inventory section with id " + inventorySectionId + " cannot select an active slot"); + }; + } + + public byte getActiveHotbarSlot() { + return this.activeHotbarSlot; + } + + public void setActiveHotbarSlot(byte slot) { + this.setUsingToolsItem(false); + this.setActiveSlot(-1, slot); + } + + @Nullable + public ItemStack getActiveHotbarItem() { + return this.activeHotbarSlot == -1 ? null : this.hotbar.getItemStack(this.activeHotbarSlot); + } + + @Nullable + public ItemStack getActiveToolItem() { + return this.activeToolsSlot == -1 ? null : this.tools.getItemStack(this.activeToolsSlot); + } + + @Nullable + public ItemStack getItemInHand() { + return this._usingToolsItem ? this.getActiveToolItem() : this.getActiveHotbarItem(); + } + + public byte getActiveUtilitySlot() { + return this.activeUtilitySlot; + } + + public void setActiveUtilitySlot(byte slot) { + this.setActiveSlot(-5, slot); + } + + @Nullable + public ItemStack getUtilityItem() { + return this.activeUtilitySlot == -1 ? null : this.utility.getItemStack(this.activeUtilitySlot); + } + + public byte getActiveToolsSlot() { + return this.activeToolsSlot; + } + + public void setActiveToolsSlot(byte slot) { + this.setUsingToolsItem(true); + this.setActiveSlot(-8, slot); + } + + @Nullable + public ItemStack getToolsItem() { + return this.activeToolsSlot == -1 ? null : this.tools.getItemStack(this.activeToolsSlot); + } + + @Nullable + public ItemContainer getSectionById(int id) { + if (id >= 0) { + if (this.entity instanceof Player) { + Window window = ((Player)this.entity).getWindowManager().getWindow(id); + if (window instanceof ItemContainerWindow) { + return ((ItemContainerWindow)window).getItemContainer(); + } + } + + return null; + } else { + return switch (id) { + case -9 -> this.backpack; + case -8 -> this.tools; + default -> null; + case -5 -> this.utility; + case -3 -> this.armor; + case -2 -> this.storage; + case -1 -> this.hotbar; + }; + } + } + + public boolean consumeIsDirty() { + return this.isDirty.getAndSet(false); + } + + public boolean consumeNeedsSaving() { + return this.needsSaving.getAndSet(false); + } + + public void setEntity(LivingEntity entity) { + this.entity = entity; + } + + public void sortStorage(@Nonnull SortType type) { + this.sortType = type; + this.storage.sortItems(type); + this.markChanged(); + } + + public void setSortType(SortType type) { + this.sortType = type; + this.markChanged(); + } + + public boolean containsBrokenItem() { + boolean hasBrokenItem = false; + + for (short i = 0; i < this.combinedEverything.getCapacity(); i++) { + ItemStack itemStack = this.combinedEverything.getItemStack(i); + if (!ItemStack.isEmpty(itemStack) && itemStack.isBroken()) { + hasBrokenItem = true; + } + } + + return hasBrokenItem; + } + + @Nonnull + public UpdatePlayerInventory toPacket() { + UpdatePlayerInventory packet = new UpdatePlayerInventory(); + packet.storage = this.storage.toPacket(); + packet.armor = this.armor.toPacket(); + packet.hotbar = this.hotbar.toPacket(); + packet.utility = this.utility.toPacket(); + packet.tools = this.tools.toPacket(); + packet.backpack = this.backpack.toPacket(); + packet.sortType = this.sortType.toPacket(); + return packet; + } + + public void doMigration(Function blockMigration) { + Objects.requireNonNull(blockMigration); + this.hotbar.doMigration(blockMigration); + this.storage.doMigration(blockMigration); + this.armor.doMigration(blockMigration); + this.utility.doMigration(blockMigration); + this.tools.doMigration(blockMigration); + this.backpack.doMigration(blockMigration); + } + + private void postDecode() { + this.armor = ItemContainerUtil.trySetArmorFilters(this.armor); + this.utility = ItemContainerUtil.trySetSlotFilters( + this.utility, (type, container, slot, itemStack) -> itemStack == null || itemStack.getItem().getUtility().isUsable() + ); + this.activeHotbarSlot = (byte)(this.activeHotbarSlot < this.hotbar.getCapacity() ? this.activeHotbarSlot : (this.hotbar.getCapacity() > 0 ? 0 : -1)); + this.activeUtilitySlot = this.activeUtilitySlot < this.utility.getCapacity() ? this.activeUtilitySlot : -1; + this.activeToolsSlot = this.activeToolsSlot < this.tools.getCapacity() ? this.activeToolsSlot : -1; + this.buildCombinedContains(); + this.registerChangeEvents(); + } + + private void buildCombinedContains() { + this.combinedHotbarFirst = new CombinedItemContainer(this.hotbar, this.storage); + this.combinedStorageFirst = new CombinedItemContainer(this.storage, this.hotbar); + this.combinedBackpackStorageHotbar = new CombinedItemContainer(this.backpack, this.storage, this.hotbar); + this.combinedStorageHotbarBackpack = new CombinedItemContainer(this.storage, this.hotbar, this.backpack); + this.combinedArmorHotbarStorage = new CombinedItemContainer(this.armor, this.hotbar, this.storage); + this.combinedArmorHotbarUtilityStorage = new CombinedItemContainer(this.armor, this.hotbar, this.utility, this.storage); + this.combinedHotbarUtilityConsumableStorage = new CombinedItemContainer(this.hotbar, this.utility, this.storage); + this.combinedEverything = new CombinedItemContainer(this.armor, this.hotbar, this.utility, this.storage, this.backpack); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Inventory inventory = (Inventory)o; + if (this.activeHotbarSlot != inventory.activeHotbarSlot) { + return false; + } else if (this.activeUtilitySlot != inventory.activeUtilitySlot) { + return false; + } else if (this.isDirty != inventory.isDirty) { + return false; + } else if (this.needsSaving != inventory.needsSaving) { + return false; + } else if (!Objects.equals(this.storage, inventory.storage)) { + return false; + } else if (!Objects.equals(this.armor, inventory.armor)) { + return false; + } else if (!Objects.equals(this.utility, inventory.utility)) { + return false; + } else if (!Objects.equals(this.tools, inventory.tools)) { + return false; + } else { + return !Objects.equals(this.backpack, inventory.backpack) ? false : Objects.equals(this.hotbar, inventory.hotbar); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.storage != null ? this.storage.hashCode() : 0; + result = 31 * result + (this.armor != null ? this.armor.hashCode() : 0); + result = 31 * result + (this.hotbar != null ? this.hotbar.hashCode() : 0); + result = 31 * result + (this.utility != null ? this.utility.hashCode() : 0); + result = 31 * result + (this.tools != null ? this.tools.hashCode() : 0); + result = 31 * result + (this.backpack != null ? this.backpack.hashCode() : 0); + result = 31 * result + this.activeHotbarSlot; + result = 31 * result + this.activeUtilitySlot; + result = 31 * result + this.activeToolsSlot; + result = 31 * result + this.isDirty.hashCode(); + return 31 * result + this.needsSaving.hashCode(); + } + + @Nonnull + @Override + public String toString() { + return "Inventory{, storage=" + + this.storage + + ", armor=" + + this.armor + + ", hotbar=" + + this.hotbar + + ", utility=" + + this.utility + + ", activeHotbarSlot=" + + this.activeHotbarSlot + + ", activeUtilitySlot=" + + this.activeUtilitySlot + + ", activeToolsSlot=" + + this.activeToolsSlot + + ", isDirty=" + + this.isDirty + + ", needsSaving=" + + this.needsSaving + + "}"; + } + + @Nonnull + public static Inventory ensureCapacity(@Nonnull Inventory inventory, List remainder) { + ItemContainer storage = inventory.getStorage(); + ItemContainer armor = inventory.getArmor(); + ItemContainer hotbar = inventory.getHotbar(); + ItemContainer utility = inventory.getUtility(); + ItemContainer tool = inventory.getTools(); + if (storage.getCapacity() == 36 + && armor.getCapacity() == DEFAULT_ARMOR_CAPACITY + && hotbar.getCapacity() == 9 + && utility.getCapacity() == 4 + && tool.getCapacity() == 23) { + return inventory; + } else { + ItemContainer newStorage = ItemContainer.ensureContainerCapacity(storage, (short)36, SimpleItemContainer::new, remainder); + ItemContainer newArmor = ItemContainer.ensureContainerCapacity(armor, DEFAULT_ARMOR_CAPACITY, SimpleItemContainer::new, remainder); + ItemContainer newHotbar = ItemContainer.ensureContainerCapacity(hotbar, (short)9, SimpleItemContainer::new, remainder); + ItemContainer newUtility = ItemContainer.ensureContainerCapacity(utility, (short)4, SimpleItemContainer::new, remainder); + ItemContainer newTool = ItemContainer.ensureContainerCapacity(tool, (short)23, SimpleItemContainer::new, remainder); + byte activeHotbarSlot = inventory.getActiveHotbarSlot(); + if (activeHotbarSlot > newHotbar.getCapacity()) { + activeHotbarSlot = (byte)(hotbar.getCapacity() > 0 ? 0 : -1); + } + + byte activeUtilitySlot = inventory.getActiveUtilitySlot(); + if (activeUtilitySlot > newUtility.getCapacity()) { + activeUtilitySlot = -1; + } + + byte activeToolsSlot = inventory.getActiveToolsSlot(); + if (activeToolsSlot > newTool.getCapacity()) { + activeToolsSlot = -1; + } + + inventory.unregister(); + Inventory newInventory = new Inventory(newStorage, newArmor, newHotbar, newUtility, newTool, EmptyItemContainer.INSTANCE); + newInventory.activeHotbarSlot = activeHotbarSlot; + newInventory.activeUtilitySlot = activeUtilitySlot; + newInventory.activeToolsSlot = activeToolsSlot; + newInventory.setSortType(inventory.sortType); + return newInventory; + } + } + + public void setUsingToolsItem(boolean value) { + this._usingToolsItem = value; + } + + public boolean usingToolsItem() { + return this._usingToolsItem; + } + + public static enum ItemPickupType { + PASSIVE, + INTERACTION; + + private ItemPickupType() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/ItemContext.java b/src/com/hypixel/hytale/server/core/inventory/ItemContext.java new file mode 100644 index 0000000..1a08fca --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/ItemContext.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.server.core.inventory; + +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import javax.annotation.Nonnull; + +public class ItemContext { + @Nonnull + private final ItemContainer container; + private final short slot; + @Nonnull + private final ItemStack itemStack; + + public ItemContext(@Nonnull ItemContainer container, short slot, @Nonnull ItemStack itemStack) { + this.container = container; + this.slot = slot; + this.itemStack = itemStack; + } + + @Nonnull + public ItemContainer getContainer() { + return this.container; + } + + public short getSlot() { + return this.slot; + } + + @Nonnull + public ItemStack getItemStack() { + return this.itemStack; + } + + @Nonnull + @Override + public String toString() { + return "ItemContext{container=" + this.container + ", slot=" + this.slot + ", itemStack=" + this.itemStack + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/ItemStack.java b/src/com/hypixel/hytale/server/core/inventory/ItemStack.java new file mode 100644 index 0000000..8394574 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/ItemStack.java @@ -0,0 +1,394 @@ +package com.hypixel.hytale.server.core.inventory; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.protocol.ItemQuantity; +import com.hypixel.hytale.protocol.ItemWithAllMetadata; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class ItemStack implements NetworkSerializable { + @Nonnull + public static final ItemStack[] EMPTY_ARRAY = new ItemStack[0]; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(ItemStack.class, ItemStack::new) + .append(new KeyedCodec<>("Id", Codec.STRING), (itemStack, id) -> itemStack.itemId = id, itemStack -> itemStack.itemId) + .addValidator(Validators.nonNull()) + .addValidator(Item.VALIDATOR_CACHE.getValidator().late()) + .add() + .append(new KeyedCodec<>("Quantity", Codec.INTEGER), (itemStack, quantity) -> itemStack.quantity = quantity, itemStack -> itemStack.quantity) + .addValidator(Validators.greaterThan(0)) + .add() + .append( + new KeyedCodec<>("Durability", Codec.DOUBLE), (itemStack, durability) -> itemStack.durability = durability, itemStack -> itemStack.durability + ) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append( + new KeyedCodec<>("MaxDurability", Codec.DOUBLE), + (itemStack, maxDurability) -> itemStack.maxDurability = maxDurability, + itemStack -> itemStack.maxDurability + ) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append( + new KeyedCodec<>("Metadata", Codec.BSON_DOCUMENT), (itemStack, bsonDocument) -> itemStack.metadata = bsonDocument, itemStack -> itemStack.metadata + ) + .add() + .append( + new KeyedCodec<>("OverrideDroppedItemAnimation", Codec.BOOLEAN), + (itemStack, b) -> itemStack.overrideDroppedItemAnimation = b, + itemStack -> itemStack.overrideDroppedItemAnimation + ) + .add() + .build(); + @Nonnull + public static final ItemStack EMPTY = new ItemStack() { + { + this.itemId = "Empty"; + } + }; + protected String itemId; + protected int quantity = 1; + protected double durability; + protected double maxDurability; + protected boolean overrideDroppedItemAnimation; + @Nullable + protected BsonDocument metadata; + @Nullable + private ItemWithAllMetadata cachedPacket; + + public ItemStack(@Nonnull String itemId, int quantity, @Nullable BsonDocument metadata) { + if (quantity <= 0) { + throw new IllegalArgumentException(String.format("quantity %s must be >0!", quantity)); + } else if (itemId == null) { + throw new IllegalArgumentException("itemId cannot be null!"); + } else if (itemId.equals("Empty")) { + throw new IllegalArgumentException("itemId cannot be BlockTypeKey.EMPTY!"); + } else { + this.itemId = itemId; + this.quantity = quantity; + double maxDurability = this.getItem().getMaxDurability(); + this.durability = maxDurability; + this.maxDurability = maxDurability; + this.metadata = metadata; + } + } + + public ItemStack(@Nonnull String itemId, int quantity, double durability, double maxDurability, @Nullable BsonDocument metadata) { + this(itemId, quantity, metadata); + this.durability = durability; + this.maxDurability = maxDurability; + } + + public ItemStack(@Nonnull String itemId) { + this(itemId, 1); + } + + public ItemStack(@Nonnull String itemId, int quantity) { + this(itemId, quantity, null); + } + + protected ItemStack() { + } + + @Nonnull + public String getItemId() { + return this.itemId; + } + + public int getQuantity() { + return this.quantity; + } + + @Nullable + @Deprecated + public BsonDocument getMetadata() { + return this.metadata == null ? null : this.metadata.clone(); + } + + public boolean isUnbreakable() { + return this.maxDurability <= 0.0; + } + + public boolean isBroken() { + return this.isUnbreakable() ? false : this.durability == 0.0; + } + + public double getMaxDurability() { + return this.maxDurability; + } + + public double getDurability() { + return this.durability; + } + + public boolean isEmpty() { + return this.itemId.equals("Empty"); + } + + public boolean getOverrideDroppedItemAnimation() { + return this.overrideDroppedItemAnimation; + } + + public void setOverrideDroppedItemAnimation(boolean b) { + this.overrideDroppedItemAnimation = b; + } + + @Nullable + public String getBlockKey() { + if (this.isEmpty()) { + return "Empty"; + } else { + Item item = this.getItem(); + if (item == null) { + return null; + } else { + return item.hasBlockType() ? item.getBlockId() : null; + } + } + } + + @Nonnull + public Item getItem() { + Item item = Item.getAssetMap().getAsset(this.itemId); + return item != null ? item : Item.UNKNOWN; + } + + public boolean isValid() { + return this.isEmpty() || this.getItem() != null; + } + + @Nonnull + public ItemStack withDurability(double durability) { + return new ItemStack(this.itemId, this.quantity, MathUtil.clamp(durability, 0.0, this.maxDurability), this.maxDurability, this.metadata); + } + + @Nonnull + public ItemStack withMaxDurability(double maxDurability) { + return new ItemStack(this.itemId, this.quantity, Math.min(this.durability, maxDurability), maxDurability, this.metadata); + } + + @Nonnull + public ItemStack withIncreasedDurability(double inc) { + return this.withDurability(this.durability + inc); + } + + @Nonnull + public ItemStack withRestoredDurability(double maxDurability) { + return new ItemStack(this.itemId, this.quantity, maxDurability, maxDurability, this.metadata); + } + + @Nonnull + public ItemStack withState(@Nonnull String state) { + String newItemId = this.getItem().getItemIdForState(state); + if (newItemId == null) { + throw new IllegalArgumentException("Invalid state: " + state); + } else { + return new ItemStack(newItemId, this.quantity, this.durability, this.maxDurability, this.metadata); + } + } + + @Nullable + public ItemStack withQuantity(int quantity) { + if (quantity == 0) { + return null; + } else { + return quantity == this.quantity ? this : new ItemStack(this.itemId, quantity, this.durability, this.maxDurability, this.metadata); + } + } + + @Nonnull + public ItemStack withMetadata(@Nullable BsonDocument metadata) { + return new ItemStack(this.itemId, this.quantity, this.durability, this.maxDurability, metadata); + } + + @Nonnull + public ItemStack withMetadata(@Nonnull KeyedCodec keyedCodec, @Nullable T data) { + return this.withMetadata(keyedCodec.getKey(), keyedCodec.getChildCodec(), data); + } + + @Nonnull + public ItemStack withMetadata(@Nonnull String key, @Nonnull Codec codec, @Nullable T data) { + BsonDocument clonedMeta = this.metadata == null ? new BsonDocument() : this.metadata.clone(); + if (data == null) { + clonedMeta.remove(key); + } else { + BsonValue bsonValue = codec.encode(data); + boolean empty = bsonValue.isNull() || bsonValue instanceof BsonDocument doc && doc.isEmpty(); + if (empty) { + clonedMeta.remove(key); + } else { + clonedMeta.put(key, bsonValue); + } + } + + if (clonedMeta.isEmpty()) { + clonedMeta = null; + } + + return new ItemStack(this.itemId, this.quantity, this.durability, this.maxDurability, clonedMeta); + } + + @Nonnull + public ItemStack withMetadata(@Nonnull String key, @Nullable BsonValue bsonValue) { + BsonDocument clonedMeta = this.metadata == null ? new BsonDocument() : this.metadata.clone(); + if (bsonValue != null && !bsonValue.isNull()) { + clonedMeta.put(key, bsonValue); + } else { + clonedMeta.remove(key); + } + + return new ItemStack(this.itemId, this.quantity, this.durability, this.maxDurability, clonedMeta); + } + + public ItemWithAllMetadata toPacket() { + if (this.cachedPacket != null) { + return this.cachedPacket; + } else { + ItemWithAllMetadata packet = new ItemWithAllMetadata(); + packet.itemId = this.itemId.toString(); + packet.quantity = this.quantity; + packet.durability = this.durability; + packet.maxDurability = this.maxDurability; + packet.overrideDroppedItemAnimation = this.overrideDroppedItemAnimation; + packet.metadata = this.metadata != null ? this.metadata.toJson() : null; + this.cachedPacket = packet; + return this.cachedPacket; + } + } + + public boolean isStackableWith(@Nullable ItemStack itemStack) { + if (itemStack == null) { + return false; + } else if (Double.compare(itemStack.durability, this.durability) != 0) { + return false; + } else if (Double.compare(itemStack.maxDurability, this.maxDurability) != 0) { + return false; + } else if (!this.itemId.equals(itemStack.itemId)) { + return false; + } else { + return this.metadata != null ? this.metadata.equals(itemStack.metadata) : itemStack.metadata == null; + } + } + + public boolean isEquivalentType(@Nullable ItemStack itemStack) { + if (itemStack == null) { + return false; + } else if (!this.itemId.equals(itemStack.itemId)) { + return false; + } else { + return this.metadata != null ? this.metadata.equals(itemStack.metadata) : itemStack.metadata == null; + } + } + + @Nullable + public T getFromMetadataOrNull(@Nonnull KeyedCodec keyedCodec) { + return keyedCodec.getOrNull(this.metadata); + } + + @Nullable + public T getFromMetadataOrNull(@Nonnull String key, @Nonnull Codec codec) { + BsonValue bsonValue = this.metadata == null ? null : this.metadata.get(key); + return bsonValue == null ? null : codec.decode(bsonValue); + } + + public T getFromMetadataOrDefault(@Nonnull String key, @Nonnull BuilderCodec codec) { + BsonDocument clonedMeta = this.metadata == null ? new BsonDocument() : this.metadata.clone(); + if (clonedMeta == null) { + return codec.getDefaultValue(); + } else { + BsonValue bsonValue = clonedMeta.get(key); + return bsonValue == null ? codec.getDefaultValue() : codec.decode(bsonValue); + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + ItemStack itemStack = (ItemStack)o; + if (this.quantity != itemStack.quantity) { + return false; + } else if (Double.compare(itemStack.durability, this.durability) != 0) { + return false; + } else if (Double.compare(itemStack.maxDurability, this.maxDurability) != 0) { + return false; + } else if (!this.itemId.equals(itemStack.itemId)) { + return false; + } else { + return this.metadata != null ? this.metadata.equals(itemStack.metadata) : itemStack.metadata == null; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.itemId.hashCode(); + result = 31 * result + this.quantity; + long temp = Double.doubleToLongBits(this.durability); + result = 31 * result + (int)(temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.maxDurability); + result = 31 * result + (int)(temp ^ temp >>> 32); + return 31 * result + (this.metadata != null ? this.metadata.hashCode() : 0); + } + + @Nonnull + @Override + public String toString() { + return "ItemStack{itemId=" + + this.itemId + + ", quantity=" + + this.quantity + + ", maxDurability=" + + this.maxDurability + + ", durability=" + + this.durability + + ", metadata=" + + this.metadata + + "}"; + } + + public static boolean isEmpty(@Nullable ItemStack itemFrom) { + return itemFrom == null || itemFrom.isEmpty(); + } + + public static boolean isStackableWith(@Nullable ItemStack a, ItemStack b) { + return a == b || a != null && a.isStackableWith(b); + } + + public static boolean isEquivalentType(@Nullable ItemStack a, ItemStack b) { + return a == b || a != null && a.isEquivalentType(b); + } + + public static boolean isSameItemType(@Nullable ItemStack a, @Nullable ItemStack b) { + return a == b || a != null && b != null && a.itemId.equals(b.itemId); + } + + @Nullable + public static ItemStack fromPacket(@Nullable ItemQuantity packet) { + if (packet == null) { + return null; + } else { + int quantity = packet.quantity; + return quantity <= 0 ? null : new ItemStack(packet.itemId, quantity, null); + } + } + + public static class Metadata { + public static final String BLOCK_STATE = "BlockState"; + + public Metadata() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/MaterialQuantity.java b/src/com/hypixel/hytale/server/core/inventory/MaterialQuantity.java new file mode 100644 index 0000000..5d67405 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/MaterialQuantity.java @@ -0,0 +1,174 @@ +package com.hypixel.hytale.server.core.inventory; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public class MaterialQuantity implements NetworkSerializable { + public static final MaterialQuantity[] EMPTY_ARRAY = new MaterialQuantity[0]; + public static final BuilderCodec CODEC = BuilderCodec.builder(MaterialQuantity.class, MaterialQuantity::new) + .addField( + new KeyedCodec<>("ItemId", Codec.STRING), + (craftingMaterial, blockTypeKey) -> craftingMaterial.itemId = blockTypeKey, + craftingMaterial -> craftingMaterial.itemId + ) + .addField( + new KeyedCodec<>("ResourceTypeId", Codec.STRING), + (craftingMaterial, s) -> craftingMaterial.resourceTypeId = s, + craftingMaterial -> craftingMaterial.resourceTypeId + ) + .addField(new KeyedCodec<>("ItemTag", Codec.STRING), (materialQuantity, s) -> materialQuantity.tag = s, materialQuantity -> materialQuantity.tag) + .append( + new KeyedCodec<>("Quantity", Codec.INTEGER), (craftingMaterial, s) -> craftingMaterial.quantity = s, craftingMaterial -> craftingMaterial.quantity + ) + .addValidator(Validators.greaterThan(0)) + .add() + .addField( + new KeyedCodec<>("Metadata", Codec.BSON_DOCUMENT), + (craftingMaterial, s) -> craftingMaterial.metadata = s, + craftingMaterial -> craftingMaterial.metadata + ) + .afterDecode((materialQuantity, extraInfo) -> { + if (materialQuantity.tag != null) { + materialQuantity.tagIndex = AssetRegistry.getOrCreateTagIndex(materialQuantity.tag); + } + }) + .build(); + @Nullable + protected String itemId; + @Nullable + protected String resourceTypeId; + protected String tag; + protected int tagIndex = Integer.MIN_VALUE; + protected int quantity = 1; + @Nullable + protected BsonDocument metadata; + + public MaterialQuantity(@Nullable String itemId, @Nullable String resourceTypeId, @Nullable String tag, int quantity, BsonDocument metadata) { + if (itemId == null && resourceTypeId == null && tag == null) { + throw new IllegalArgumentException("itemId, resourceTypeId and tag cannot all be null!"); + } else if (quantity <= 0) { + throw new IllegalArgumentException("quantity " + quantity + " must be >0!"); + } else { + this.itemId = itemId; + this.resourceTypeId = resourceTypeId; + this.tag = tag; + this.quantity = quantity; + this.metadata = metadata; + } + } + + protected MaterialQuantity() { + } + + @Nullable + public String getItemId() { + return this.itemId; + } + + @Nullable + public String getResourceTypeId() { + return this.resourceTypeId; + } + + public int getTagIndex() { + return this.tagIndex; + } + + public int getQuantity() { + return this.quantity; + } + + public BsonDocument getMetadata() { + return this.metadata; + } + + @Nonnull + public MaterialQuantity clone(int quantity) { + return new MaterialQuantity(this.itemId, this.resourceTypeId, this.tag, quantity, this.metadata); + } + + @Nullable + public ItemStack toItemStack() { + if (this.itemId == null) { + return null; + } else { + return this.itemId.equals("Empty") ? ItemStack.EMPTY : new ItemStack(this.itemId, this.quantity, this.metadata); + } + } + + @Nullable + public ResourceQuantity toResource() { + return this.resourceTypeId == null ? null : new ResourceQuantity(this.resourceTypeId, this.quantity); + } + + @Nonnull + public com.hypixel.hytale.protocol.MaterialQuantity toPacket() { + com.hypixel.hytale.protocol.MaterialQuantity packet = new com.hypixel.hytale.protocol.MaterialQuantity(); + if (this.itemId != null) { + packet.itemId = this.itemId.toString(); + } + + packet.itemTag = this.tagIndex; + packet.resourceTypeId = this.resourceTypeId; + packet.quantity = this.quantity; + return packet; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + MaterialQuantity that = (MaterialQuantity)o; + if (this.quantity != that.quantity) { + return false; + } else if (this.itemId != null ? this.itemId.equals(that.itemId) : that.itemId == null) { + if (this.resourceTypeId != null ? this.resourceTypeId.equals(that.resourceTypeId) : that.resourceTypeId == null) { + if (this.tag != null ? this.tag.equals(that.tag) : that.tag == null) { + return this.metadata != null ? this.metadata.equals(that.metadata) : that.metadata == null; + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.itemId != null ? this.itemId.hashCode() : 0; + result = 31 * result + (this.resourceTypeId != null ? this.resourceTypeId.hashCode() : 0); + result = 31 * result + (this.tag != null ? this.tag.hashCode() : 0); + result = 31 * result + this.quantity; + return 31 * result + (this.metadata != null ? this.metadata.hashCode() : 0); + } + + @Nonnull + @Override + public String toString() { + return "MaterialQuantity{itemId=" + + this.itemId + + ", resourceTypeId='" + + this.resourceTypeId + + "', tag='" + + this.tag + + "', quantity=" + + this.quantity + + ", metadata=" + + this.metadata + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/ResourceQuantity.java b/src/com/hypixel/hytale/server/core/inventory/ResourceQuantity.java new file mode 100644 index 0000000..144c04b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/ResourceQuantity.java @@ -0,0 +1,72 @@ +package com.hypixel.hytale.server.core.inventory; + +import com.hypixel.hytale.protocol.ItemResourceType; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ResourceQuantity { + protected String resourceId; + protected int quantity; + + public ResourceQuantity(String resourceId, int quantity) { + Objects.requireNonNull(resourceId, "resourceId cannot be null!"); + if (quantity <= 0) { + throw new IllegalArgumentException("quantity " + quantity + " must be >0!"); + } else { + this.resourceId = resourceId; + this.quantity = quantity; + } + } + + protected ResourceQuantity() { + } + + public String getResourceId() { + return this.resourceId; + } + + public int getQuantity() { + return this.quantity; + } + + @Nonnull + public ResourceQuantity clone(int quantity) { + return new ResourceQuantity(this.resourceId, quantity); + } + + @Nullable + public ItemResourceType getResourceType(@Nonnull Item item) { + return ItemContainer.getMatchingResourceType(item, this.resourceId); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + ResourceQuantity itemStack = (ResourceQuantity)o; + if (this.quantity != itemStack.quantity) { + return false; + } else { + return this.resourceId != null ? this.resourceId.equals(itemStack.resourceId) : itemStack.resourceId == null; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.resourceId != null ? this.resourceId.hashCode() : 0; + return 31 * result + this.quantity; + } + + @Nonnull + @Override + public String toString() { + return "ResourceQuantity{resourceId='" + this.resourceId + "', quantity=" + this.quantity + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/CombinedItemContainer.java b/src/com/hypixel/hytale/server/core/inventory/container/CombinedItemContainer.java new file mode 100644 index 0000000..f09a855 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/CombinedItemContainer.java @@ -0,0 +1,332 @@ +package com.hypixel.hytale.server.core.inventory.container; + +import com.hypixel.hytale.event.EventRegistration; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterActionType; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterType; +import com.hypixel.hytale.server.core.inventory.container.filter.SlotFilter; +import com.hypixel.hytale.server.core.inventory.transaction.ClearTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.Transaction; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CombinedItemContainer extends ItemContainer { + protected final ItemContainer[] containers; + + public CombinedItemContainer(ItemContainer... containers) { + this.containers = containers; + } + + public ItemContainer getContainer(int index) { + return this.containers[index]; + } + + public int getContainersSize() { + return this.containers.length; + } + + @Nullable + public ItemContainer getContainerForSlot(short slot) { + for (ItemContainer container : this.containers) { + short capacity = container.getCapacity(); + if (slot < capacity) { + return container; + } + + slot -= capacity; + } + + return null; + } + + @Override + protected V readAction(@Nonnull Supplier action) { + return this.readAction0(0, action); + } + + private V readAction0(int i, @Nonnull Supplier action) { + return i >= this.containers.length ? action.get() : this.containers[i].readAction(() -> this.readAction0(i + 1, action)); + } + + @Override + protected V readAction(@Nonnull Function action, X x) { + return this.readAction0(0, action, x); + } + + private V readAction0(int i, @Nonnull Function action, X x) { + return i >= this.containers.length ? action.apply(x) : this.containers[i].readAction(() -> this.readAction0(i + 1, action, x)); + } + + @Override + protected V writeAction(@Nonnull Supplier action) { + return this.writeAction0(0, action); + } + + private V writeAction0(int i, @Nonnull Supplier action) { + return i >= this.containers.length ? action.get() : this.containers[i].writeAction(() -> this.writeAction0(i + 1, action)); + } + + @Override + protected V writeAction(@Nonnull Function action, X x) { + return this.writeAction0(0, action, x); + } + + private V writeAction0(int i, @Nonnull Function action, X x) { + return i >= this.containers.length ? action.apply(x) : this.containers[i].writeAction(() -> this.writeAction0(i + 1, action, x)); + } + + @Nonnull + @Override + protected ClearTransaction internal_clear() { + ItemStack[] itemStacks = new ItemStack[this.getCapacity()]; + short start = 0; + + for (ItemContainer container : this.containers) { + ClearTransaction clear = container.internal_clear(); + ItemStack[] items = clear.getItems(); + + for (short slot = 0; slot < itemStacks.length; slot++) { + itemStacks[(short)(start + slot)] = items[slot]; + } + + start += container.getCapacity(); + } + + return new ClearTransaction(true, (short)0, itemStacks); + } + + @Nullable + @Override + protected ItemStack internal_getSlot(short slot) { + for (ItemContainer container : this.containers) { + short capacity = container.getCapacity(); + if (slot < capacity) { + return container.internal_getSlot(slot); + } + + slot -= capacity; + } + + return null; + } + + @Nullable + @Override + protected ItemStack internal_setSlot(short slot, ItemStack itemStack) { + if (ItemStack.isEmpty(itemStack)) { + return this.internal_removeSlot(slot); + } else { + for (ItemContainer container : this.containers) { + short capacity = container.getCapacity(); + if (slot < capacity) { + return container.internal_setSlot(slot, itemStack); + } + + slot -= capacity; + } + + return null; + } + } + + @Nullable + @Override + protected ItemStack internal_removeSlot(short slot) { + for (ItemContainer container : this.containers) { + short capacity = container.getCapacity(); + if (slot < capacity) { + return container.internal_removeSlot(slot); + } + + slot -= capacity; + } + + return null; + } + + @Override + protected boolean cantAddToSlot(short slot, ItemStack itemStack, ItemStack slotItemStack) { + for (ItemContainer container : this.containers) { + short capacity = container.getCapacity(); + if (slot < capacity) { + return container.cantAddToSlot(slot, itemStack, slotItemStack); + } + + slot -= capacity; + } + + return true; + } + + @Override + protected boolean cantRemoveFromSlot(short slot) { + for (ItemContainer container : this.containers) { + short capacity = container.getCapacity(); + if (slot < capacity) { + return container.cantRemoveFromSlot(slot); + } + + slot -= capacity; + } + + return true; + } + + @Override + protected boolean cantDropFromSlot(short slot) { + for (ItemContainer container : this.containers) { + short capacity = container.getCapacity(); + if (slot < capacity) { + return container.cantDropFromSlot(slot); + } + + slot -= capacity; + } + + return true; + } + + @Override + protected boolean cantMoveToSlot(ItemContainer fromContainer, short slotFrom) { + for (ItemContainer container : this.containers) { + boolean cantMoveToSlot = container.cantMoveToSlot(fromContainer, slotFrom); + if (cantMoveToSlot) { + return true; + } + } + + return false; + } + + @Override + public short getCapacity() { + short capacity = 0; + + for (ItemContainer container : this.containers) { + capacity += container.getCapacity(); + } + + return capacity; + } + + public CombinedItemContainer clone() { + throw new UnsupportedOperationException("clone() is not supported for CombinedItemContainer"); + } + + @Nonnull + @Override + public EventRegistration registerChangeEvent(short priority, @Nonnull Consumer consumer) { + EventRegistration thisRegistration = super.registerChangeEvent(priority, consumer); + EventRegistration[] containerRegistrations = new EventRegistration[this.containers.length]; + short start = 0; + + for (int i = 0; i < this.containers.length; i++) { + ItemContainer container = this.containers[i]; + short finalStart = start; + containerRegistrations[i] = container.internalChangeEventRegistry + .register( + priority, + null, + event -> consumer.accept(new ItemContainer.ItemContainerChangeEvent(this, event.transaction().toParent(this, finalStart, container))) + ); + start += container.getCapacity(); + } + + return EventRegistration.combine(thisRegistration, containerRegistrations); + } + + @Override + protected void sendUpdate(@Nonnull Transaction transaction) { + if (transaction.succeeded()) { + super.sendUpdate(transaction); + short start = 0; + + for (ItemContainer container : this.containers) { + Transaction containerTransaction = transaction.fromParent(this, start, container); + if (containerTransaction != null) { + if (!containerTransaction.succeeded()) { + start += container.getCapacity(); + continue; + } + + container.sendUpdate(containerTransaction); + } + + start += container.getCapacity(); + } + } + } + + @Override + public boolean containsContainer(ItemContainer itemContainer) { + if (itemContainer == this) { + return true; + } else { + for (ItemContainer container : this.containers) { + if (container.containsContainer(itemContainer)) { + return true; + } + } + + return false; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o instanceof CombinedItemContainer that) { + short capacity = this.getCapacity(); + return capacity != that.getCapacity() ? false : this.readAction(_that -> _that.readAction(_that2 -> { + for (short i = 0; i < capacity; i++) { + if (!Objects.equals(this.internal_getSlot(i), _that2.internal_getSlot(i))) { + return false; + } + } + + return true; + }, _that), that); + } else { + return false; + } + } + + @Override + public int hashCode() { + short capacity = this.getCapacity(); + int result = this.readAction(() -> { + int hash = 0; + + for (short i = 0; i < capacity; i++) { + ItemStack itemStack = this.internal_getSlot(i); + hash = 31 * hash + (itemStack != null ? itemStack.hashCode() : 0); + } + + return hash; + }); + return 31 * result + capacity; + } + + @Override + public void setGlobalFilter(FilterType globalFilter) { + throw new UnsupportedOperationException("setGlobalFilter(FilterType) is not supported in CombinedItemContainer"); + } + + @Override + public void setSlotFilter(FilterActionType actionType, short slot, SlotFilter filter) { + for (ItemContainer container : this.containers) { + short capacity = container.getCapacity(); + if (slot < capacity) { + container.setSlotFilter(actionType, slot, filter); + return; + } + + slot -= capacity; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/DelegateItemContainer.java b/src/com/hypixel/hytale/server/core/inventory/container/DelegateItemContainer.java new file mode 100644 index 0000000..981c562 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/DelegateItemContainer.java @@ -0,0 +1,199 @@ +package com.hypixel.hytale.server.core.inventory.container; + +import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap; +import com.hypixel.hytale.event.EventRegistration; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterActionType; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterType; +import com.hypixel.hytale.server.core.inventory.container.filter.SlotFilter; +import com.hypixel.hytale.server.core.inventory.transaction.ClearTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.Transaction; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DelegateItemContainer extends ItemContainer { + private T delegate; + private final Map> slotFilters = new ConcurrentHashMap<>(); + @Nonnull + private FilterType globalFilter = FilterType.ALLOW_ALL; + + public DelegateItemContainer(T delegate) { + Objects.requireNonNull(delegate, "Delegate can't be null!"); + this.delegate = delegate; + } + + public T getDelegate() { + return this.delegate; + } + + @Override + protected V readAction(Supplier action) { + return this.delegate.readAction(action); + } + + @Override + protected V readAction(Function action, X x) { + return this.delegate.readAction(action, x); + } + + @Override + protected V writeAction(Supplier action) { + return this.delegate.writeAction(action); + } + + @Override + protected V writeAction(Function action, X x) { + return this.delegate.writeAction(action, x); + } + + @Override + protected ClearTransaction internal_clear() { + return this.delegate.internal_clear(); + } + + @Override + protected ItemStack internal_getSlot(short slot) { + return this.delegate.internal_getSlot(slot); + } + + @Override + protected ItemStack internal_setSlot(short slot, ItemStack itemStack) { + return this.delegate.internal_setSlot(slot, itemStack); + } + + @Override + protected ItemStack internal_removeSlot(short slot) { + return this.delegate.internal_removeSlot(slot); + } + + @Override + protected boolean cantAddToSlot(short slot, ItemStack itemStack, ItemStack slotItemStack) { + if (!this.globalFilter.allowInput()) { + return true; + } else { + return this.testFilter(FilterActionType.ADD, slot, itemStack) ? true : this.delegate.cantAddToSlot(slot, itemStack, slotItemStack); + } + } + + @Override + protected boolean cantRemoveFromSlot(short slot) { + if (!this.globalFilter.allowOutput()) { + return true; + } else { + return this.testFilter(FilterActionType.REMOVE, slot, null) ? true : this.delegate.cantRemoveFromSlot(slot); + } + } + + @Override + protected boolean cantDropFromSlot(short slot) { + return this.testFilter(FilterActionType.DROP, slot, null) ? true : this.delegate.cantDropFromSlot(slot); + } + + @Override + protected boolean cantMoveToSlot(ItemContainer fromContainer, short slotFrom) { + return this.delegate.cantMoveToSlot(fromContainer, slotFrom); + } + + private boolean testFilter(FilterActionType actionType, short slot, ItemStack itemStack) { + Int2ObjectConcurrentHashMap map = this.slotFilters.get(actionType); + if (map == null) { + return false; + } else { + SlotFilter filter = map.get(slot); + return filter == null ? false : !filter.test(actionType, this, slot, itemStack); + } + } + + @Override + public short getCapacity() { + return this.delegate.getCapacity(); + } + + @Override + public ClearTransaction clear() { + return this.delegate.clear(); + } + + @Nonnull + public DelegateItemContainer clone() { + return new DelegateItemContainer<>(this.delegate); + } + + @Override + public boolean isEmpty() { + return this.delegate.isEmpty(); + } + + @Override + public void setGlobalFilter(@Nonnull FilterType globalFilter) { + this.globalFilter = Objects.requireNonNull(globalFilter); + } + + @Override + public void setSlotFilter(FilterActionType actionType, short slot, @Nullable SlotFilter filter) { + validateSlotIndex(slot, this.getCapacity()); + if (filter != null) { + this.slotFilters.computeIfAbsent(actionType, k -> new Int2ObjectConcurrentHashMap<>()).put(slot, filter); + } else { + this.slotFilters.computeIfPresent(actionType, (k, map) -> { + map.remove(slot); + return map.isEmpty() ? null : map; + }); + } + } + + @Nonnull + @Override + public EventRegistration registerChangeEvent(short priority, @Nonnull Consumer consumer) { + EventRegistration thisRegistration = super.registerChangeEvent(priority, consumer); + EventRegistration[] delegateRegistration = new EventRegistration[]{ + this.delegate + .internalChangeEventRegistry + .register( + priority, + null, + event -> consumer.accept(new ItemContainer.ItemContainerChangeEvent(this, event.transaction().toParent(this, (short)0, this.delegate))) + ) + }; + return EventRegistration.combine(thisRegistration, delegateRegistration); + } + + @Override + protected void sendUpdate(@Nonnull Transaction transaction) { + if (transaction.succeeded()) { + super.sendUpdate(transaction); + this.delegate.externalChangeEventRegistry.dispatchFor(null).dispatch(new ItemContainer.ItemContainerChangeEvent(this.delegate, transaction)); + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DelegateItemContainer that = (DelegateItemContainer)o; + if (this.delegate != null ? this.delegate.equals(that.delegate) : that.delegate == null) { + return (this.slotFilters != null ? this.slotFilters.equals(that.slotFilters) : that.slotFilters == null) + ? this.globalFilter == that.globalFilter + : false; + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.delegate != null ? this.delegate.hashCode() : 0; + result = 31 * result + (this.slotFilters != null ? this.slotFilters.hashCode() : 0); + return 31 * result + (this.globalFilter != null ? this.globalFilter.hashCode() : 0); + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/EmptyItemContainer.java b/src/com/hypixel/hytale/server/core/inventory/container/EmptyItemContainer.java new file mode 100644 index 0000000..5e27f94 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/EmptyItemContainer.java @@ -0,0 +1,135 @@ +package com.hypixel.hytale.server.core.inventory.container; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.event.EventRegistration; +import com.hypixel.hytale.function.consumer.ShortObjectConsumer; +import com.hypixel.hytale.protocol.ItemWithAllMetadata; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterActionType; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterType; +import com.hypixel.hytale.server.core.inventory.container.filter.SlotFilter; +import com.hypixel.hytale.server.core.inventory.transaction.ClearTransaction; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class EmptyItemContainer extends ItemContainer { + public static final EmptyItemContainer INSTANCE = new EmptyItemContainer(); + public static final BuilderCodec CODEC = BuilderCodec.builder(EmptyItemContainer.class, () -> INSTANCE).build(); + private static final EventRegistration EVENT_REGISTRATION = new EventRegistration<>( + ItemContainer.ItemContainerChangeEvent.class, () -> false, () -> {} + ); + + protected EmptyItemContainer() { + } + + @Override + public short getCapacity() { + return 0; + } + + @Nonnull + @Override + public ClearTransaction clear() { + return ClearTransaction.EMPTY; + } + + @Override + public void forEach(ShortObjectConsumer action) { + } + + @Override + protected V readAction(@Nonnull Supplier action) { + return action.get(); + } + + @Override + protected V readAction(@Nonnull Function action, X x) { + return action.apply(x); + } + + @Override + protected V writeAction(@Nonnull Supplier action) { + return action.get(); + } + + @Override + protected V writeAction(@Nonnull Function action, X x) { + return action.apply(x); + } + + @Nonnull + @Override + protected ClearTransaction internal_clear() { + return ClearTransaction.EMPTY; + } + + @Override + protected ItemStack internal_getSlot(short slot) { + throw new UnsupportedOperationException("getSlot(int) is not supported in EmptyItemContainer"); + } + + @Override + protected ItemStack internal_setSlot(short slot, ItemStack itemStack) { + throw new UnsupportedOperationException("setSlot(int, ItemStack) is not supported in EmptyItemContainer"); + } + + @Override + protected ItemStack internal_removeSlot(short slot) { + throw new UnsupportedOperationException("removeSlot(int) is not supported in EmptyItemContainer"); + } + + @Override + protected boolean cantAddToSlot(short slot, ItemStack itemStack, ItemStack slotItemStack) { + return false; + } + + @Override + protected boolean cantRemoveFromSlot(short slot) { + return false; + } + + @Override + protected boolean cantDropFromSlot(short slot) { + return false; + } + + @Override + protected boolean cantMoveToSlot(ItemContainer fromContainer, short slotFrom) { + return false; + } + + @Nonnull + @Override + public List removeAllItemStacks() { + return Collections.emptyList(); + } + + @Nonnull + @Override + public Map toProtocolMap() { + return Collections.emptyMap(); + } + + public EmptyItemContainer clone() { + return INSTANCE; + } + + @Override + public EventRegistration registerChangeEvent(short priority, Consumer consumer) { + return EVENT_REGISTRATION; + } + + @Override + public void setGlobalFilter(FilterType globalFilter) { + } + + @Override + public void setSlotFilter(FilterActionType actionType, short slot, SlotFilter filter) { + validateSlotIndex(slot, 0); + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/InternalContainerUtilItemStack.java b/src/com/hypixel/hytale/server/core/inventory/container/InternalContainerUtilItemStack.java new file mode 100644 index 0000000..cde4060 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/InternalContainerUtilItemStack.java @@ -0,0 +1,609 @@ +package com.hypixel.hytale.server.core.inventory.container; + +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.transaction.ActionType; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.SlotTransaction; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.BiPredicate; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InternalContainerUtilItemStack { + public InternalContainerUtilItemStack() { + } + + protected static int testAddToExistingSlot( + @Nonnull ItemContainer abstractItemContainer, short slot, ItemStack itemStack, int itemMaxStack, int testQuantityRemaining, boolean filter + ) { + ItemStack slotItemStack = abstractItemContainer.internal_getSlot(slot); + if (ItemStack.isEmpty(slotItemStack)) { + return testQuantityRemaining; + } else if (!slotItemStack.isStackableWith(itemStack)) { + return testQuantityRemaining; + } else if (filter && abstractItemContainer.cantAddToSlot(slot, itemStack, slotItemStack)) { + return testQuantityRemaining; + } else { + int quantity = slotItemStack.getQuantity(); + int quantityAdjustment = Math.min(itemMaxStack - quantity, testQuantityRemaining); + return testQuantityRemaining - quantityAdjustment; + } + } + + @Nonnull + protected static ItemStackSlotTransaction internal_addToExistingSlot( + @Nonnull ItemContainer container, short slot, @Nonnull ItemStack itemStack, int itemMaxStack, boolean filter + ) { + ItemStack slotItemStack = container.internal_getSlot(slot); + if (ItemStack.isEmpty(slotItemStack)) { + return new ItemStackSlotTransaction(false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, false, false, filter, true, itemStack, itemStack); + } else if (!slotItemStack.isStackableWith(itemStack)) { + return new ItemStackSlotTransaction(false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, false, false, filter, true, itemStack, itemStack); + } else if (filter && container.cantAddToSlot(slot, itemStack, slotItemStack)) { + return new ItemStackSlotTransaction(false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, false, false, filter, true, itemStack, itemStack); + } else { + int quantityRemaining = itemStack.getQuantity(); + int quantity = slotItemStack.getQuantity(); + int quantityAdjustment = Math.min(itemMaxStack - quantity, quantityRemaining); + int newQuantity = quantity + quantityAdjustment; + quantityRemaining -= quantityAdjustment; + if (quantityAdjustment <= 0) { + return new ItemStackSlotTransaction( + false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, false, false, filter, true, itemStack, itemStack + ); + } else { + ItemStack slotNew = slotItemStack.withQuantity(newQuantity); + if (newQuantity > 0) { + container.internal_setSlot(slot, slotNew); + } else { + container.internal_removeSlot(slot); + } + + ItemStack remainder = quantityRemaining != itemStack.getQuantity() ? itemStack.withQuantity(quantityRemaining) : itemStack; + return new ItemStackSlotTransaction(true, ActionType.ADD, slot, slotItemStack, slotNew, null, false, false, filter, true, itemStack, remainder); + } + } + } + + @Nonnull + protected static ItemStackSlotTransaction internal_addToEmptySlot( + @Nonnull ItemContainer container, short slot, @Nonnull ItemStack itemStack, int itemMaxStack, boolean filter + ) { + ItemStack slotItemStack = container.internal_getSlot(slot); + if (slotItemStack != null && !slotItemStack.isEmpty()) { + return new ItemStackSlotTransaction(false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, false, false, filter, false, itemStack, itemStack); + } else if (filter && container.cantAddToSlot(slot, itemStack, slotItemStack)) { + return new ItemStackSlotTransaction(false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, false, false, filter, false, itemStack, itemStack); + } else { + int quantityRemaining = itemStack.getQuantity(); + int quantityAdjustment = Math.min(itemMaxStack, quantityRemaining); + quantityRemaining -= quantityAdjustment; + ItemStack slotNew = itemStack.withQuantity(quantityAdjustment); + container.internal_setSlot(slot, slotNew); + ItemStack remainder = itemStack.getQuantity() != quantityRemaining ? itemStack.withQuantity(quantityRemaining) : itemStack; + return new ItemStackSlotTransaction(true, ActionType.ADD, slot, slotItemStack, slotNew, null, false, false, filter, false, itemStack, remainder); + } + } + + protected static int testAddToEmptySlots(@Nonnull ItemContainer container, ItemStack itemStack, int itemMaxStack, int testQuantityRemaining, boolean filter) { + for (short i = 0; i < container.getCapacity() && testQuantityRemaining > 0; i++) { + ItemStack slotItemStack = container.internal_getSlot(i); + if ((slotItemStack == null || slotItemStack.isEmpty()) && (!filter || !container.cantAddToSlot(i, itemStack, slotItemStack))) { + int quantityAdjustment = Math.min(itemMaxStack, testQuantityRemaining); + testQuantityRemaining -= quantityAdjustment; + } + } + + return testQuantityRemaining; + } + + protected static ItemStackSlotTransaction internal_addItemStackToSlot( + @Nonnull ItemContainer itemContainer, short slot, @Nonnull ItemStack itemStack, boolean allOrNothing, boolean filter + ) { + ItemContainer.validateSlotIndex(slot, itemContainer.getCapacity()); + return itemContainer.writeAction( + () -> { + int quantityRemaining = itemStack.getQuantity(); + ItemStack slotItemStack = itemContainer.internal_getSlot(slot); + if (filter && itemContainer.cantAddToSlot(slot, itemStack, slotItemStack)) { + return new ItemStackSlotTransaction( + false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, allOrNothing, false, filter, false, itemStack, itemStack + ); + } else if (slotItemStack == null) { + itemContainer.internal_setSlot(slot, itemStack); + return new ItemStackSlotTransaction(true, ActionType.ADD, slot, null, itemStack, null, allOrNothing, false, filter, false, itemStack, null); + } else { + int quantity = slotItemStack.getQuantity(); + if (!itemStack.isStackableWith(slotItemStack)) { + return new ItemStackSlotTransaction( + false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, allOrNothing, false, filter, false, itemStack, itemStack + ); + } else { + int quantityAdjustment = Math.min(slotItemStack.getItem().getMaxStack() - quantity, quantityRemaining); + int newQuantity = quantity + quantityAdjustment; + quantityRemaining -= quantityAdjustment; + if (allOrNothing && quantityRemaining > 0) { + return new ItemStackSlotTransaction( + false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, allOrNothing, false, filter, false, itemStack, itemStack + ); + } else if (quantityAdjustment <= 0) { + return new ItemStackSlotTransaction( + false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, allOrNothing, false, filter, false, itemStack, itemStack + ); + } else { + ItemStack newItemStack = slotItemStack.withQuantity(newQuantity); + itemContainer.internal_setSlot(slot, newItemStack); + ItemStack remainder = itemStack.withQuantity(quantityRemaining); + return new ItemStackSlotTransaction( + true, ActionType.ADD, slot, slotItemStack, newItemStack, null, allOrNothing, false, filter, false, itemStack, remainder + ); + } + } + } + } + ); + } + + @Nonnull + protected static ItemStackSlotTransaction internal_setItemStackForSlot(@Nonnull ItemContainer itemContainer, short slot, ItemStack itemStack, boolean filter) { + ItemContainer.validateSlotIndex(slot, itemContainer.getCapacity()); + return itemContainer.writeAction( + () -> { + ItemStack slotItemStack = itemContainer.internal_getSlot(slot); + if (filter && itemContainer.cantAddToSlot(slot, itemStack, slotItemStack)) { + return new ItemStackSlotTransaction( + false, ActionType.SET, slot, slotItemStack, slotItemStack, null, false, false, filter, false, itemStack, itemStack + ); + } else { + ItemStack oldItemStack = itemContainer.internal_setSlot(slot, itemStack); + return new ItemStackSlotTransaction(true, ActionType.SET, slot, oldItemStack, itemStack, null, false, false, filter, false, itemStack, null); + } + } + ); + } + + @Nonnull + protected static SlotTransaction internal_removeItemStackFromSlot(@Nonnull ItemContainer itemContainer, short slot, boolean filter) { + ItemContainer.validateSlotIndex(slot, itemContainer.getCapacity()); + return itemContainer.writeAction(() -> { + if (filter && itemContainer.cantRemoveFromSlot(slot)) { + ItemStack itemStack = itemContainer.internal_getSlot(slot); + return new ItemStackSlotTransaction(false, ActionType.REMOVE, slot, itemStack, itemStack, null, false, false, filter, false, null, itemStack); + } else { + ItemStack oldItemStack = itemContainer.internal_removeSlot(slot); + return new SlotTransaction(true, ActionType.REMOVE, slot, oldItemStack, null, oldItemStack, false, false, false); + } + }); + } + + protected static ItemStackSlotTransaction internal_removeItemStackFromSlot( + @Nonnull ItemContainer itemContainer, short slot, int quantityToRemove, boolean allOrNothing, boolean filter + ) { + ItemContainer.validateSlotIndex(slot, itemContainer.getCapacity()); + ItemContainer.validateQuantity(quantityToRemove); + return itemContainer.writeAction( + () -> { + if (filter && itemContainer.cantRemoveFromSlot(slot)) { + ItemStack itemStack = itemContainer.internal_getSlot(slot); + return new ItemStackSlotTransaction( + false, + ActionType.REMOVE, + slot, + itemStack, + itemStack, + null, + allOrNothing, + false, + filter, + false, + null, + itemStack.withQuantity(quantityToRemove) + ); + } else { + ItemStack slotItemStack = itemContainer.internal_getSlot(slot); + if (slotItemStack == null) { + return new ItemStackSlotTransaction(false, ActionType.REMOVE, slot, null, null, null, allOrNothing, false, filter, false, null, null); + } else { + int quantity = slotItemStack.getQuantity(); + int quantityAdjustment = Math.min(quantity, quantityToRemove); + int newQuantity = quantity - quantityAdjustment; + int quantityRemaining = quantityToRemove - quantityAdjustment; + if (allOrNothing && quantityRemaining > 0) { + return new ItemStackSlotTransaction( + false, + ActionType.REMOVE, + slot, + slotItemStack, + slotItemStack, + null, + allOrNothing, + false, + filter, + false, + null, + slotItemStack.withQuantity(quantityRemaining) + ); + } else { + ItemStack itemStack = slotItemStack.withQuantity(newQuantity); + itemContainer.internal_setSlot(slot, itemStack); + ItemStack newStack = slotItemStack.withQuantity(quantityAdjustment); + ItemStack remainder = slotItemStack.withQuantity(quantityRemaining); + return new ItemStackSlotTransaction( + true, ActionType.REMOVE, slot, slotItemStack, itemStack, newStack, allOrNothing, false, filter, false, null, remainder + ); + } + } + } + } + ); + } + + protected static ItemStackSlotTransaction internal_removeItemStackFromSlot( + @Nonnull ItemContainer itemContainer, short slot, @Nullable ItemStack itemStackToRemove, int quantityToRemove, boolean allOrNothing, boolean filter + ) { + return internal_removeItemStackFromSlot( + itemContainer, slot, itemStackToRemove, quantityToRemove, allOrNothing, filter, (a, b) -> ItemStack.isStackableWith(a, b) + ); + } + + protected static ItemStackSlotTransaction internal_removeItemStackFromSlot( + @Nonnull ItemContainer itemContainer, + short slot, + @Nullable ItemStack itemStackToRemove, + int quantityToRemove, + boolean allOrNothing, + boolean filter, + BiPredicate predicate + ) { + ItemContainer.validateSlotIndex(slot, itemContainer.getCapacity()); + ItemContainer.validateQuantity(quantityToRemove); + return itemContainer.writeAction( + () -> { + if (filter && itemContainer.cantRemoveFromSlot(slot)) { + ItemStack itemStack = itemContainer.internal_getSlot(slot); + return new ItemStackSlotTransaction( + false, ActionType.REMOVE, slot, itemStack, itemStack, null, allOrNothing, false, filter, false, itemStackToRemove, itemStackToRemove + ); + } else { + ItemStack slotItemStack = itemContainer.internal_getSlot(slot); + if ((slotItemStack != null || itemStackToRemove == null) + && (slotItemStack == null || itemStackToRemove != null) + && (slotItemStack == null || predicate.test(slotItemStack, itemStackToRemove))) { + if (slotItemStack == null) { + return new ItemStackSlotTransaction( + true, ActionType.REMOVE, slot, null, null, null, allOrNothing, false, filter, false, itemStackToRemove, itemStackToRemove + ); + } else { + int quantity = slotItemStack.getQuantity(); + int quantityAdjustment = Math.min(quantity, quantityToRemove); + int newQuantity = quantity - quantityAdjustment; + int quantityRemaining = quantityToRemove - quantityAdjustment; + if (allOrNothing && quantityRemaining > 0) { + return new ItemStackSlotTransaction( + false, + ActionType.REMOVE, + slot, + slotItemStack, + slotItemStack, + null, + allOrNothing, + false, + filter, + false, + itemStackToRemove, + itemStackToRemove + ); + } else { + ItemStack itemStack = slotItemStack.withQuantity(newQuantity); + itemContainer.internal_setSlot(slot, itemStack); + ItemStack newStack = slotItemStack.withQuantity(quantityAdjustment); + ItemStack remainder = itemStackToRemove.withQuantity(quantityRemaining); + return new ItemStackSlotTransaction( + true, ActionType.REMOVE, slot, slotItemStack, itemStack, newStack, allOrNothing, false, filter, false, itemStackToRemove, remainder + ); + } + } + } else { + return new ItemStackSlotTransaction( + false, + ActionType.REMOVE, + slot, + slotItemStack, + slotItemStack, + null, + allOrNothing, + false, + filter, + false, + itemStackToRemove, + itemStackToRemove + ); + } + } + } + ); + } + + protected static int testRemoveItemStackFromSlot( + @Nonnull ItemContainer container, short slot, ItemStack itemStack, int testQuantityRemaining, boolean filter + ) { + return testRemoveItemStackFromSlot(container, slot, itemStack, testQuantityRemaining, filter, (a, b) -> ItemStack.isStackableWith(a, b)); + } + + protected static int testRemoveItemStackFromSlot( + @Nonnull ItemContainer container, short slot, ItemStack itemStack, int testQuantityRemaining, boolean filter, BiPredicate predicate + ) { + if (filter && container.cantRemoveFromSlot(slot)) { + return testQuantityRemaining; + } else { + ItemStack slotItemStack = container.internal_getSlot(slot); + if (ItemStack.isEmpty(slotItemStack)) { + return testQuantityRemaining; + } else if (!predicate.test(slotItemStack, itemStack)) { + return testQuantityRemaining; + } else { + int quantity = slotItemStack.getQuantity(); + int quantityAdjustment = Math.min(quantity, testQuantityRemaining); + return testQuantityRemaining - quantityAdjustment; + } + } + } + + protected static ItemStackTransaction internal_addItemStack( + @Nonnull ItemContainer itemContainer, @Nonnull ItemStack itemStack, boolean allOrNothing, boolean fullStacks, boolean filter + ) { + Item item = itemStack.getItem(); + if (item == null) { + throw new IllegalArgumentException(itemStack.getItemId() + " is an invalid item!"); + } else { + int itemMaxStack = item.getMaxStack(); + return itemContainer.writeAction(() -> { + if (allOrNothing) { + int testQuantityRemaining = itemStack.getQuantity(); + if (!fullStacks) { + testQuantityRemaining = testAddToExistingItemStacks(itemContainer, itemStack, itemMaxStack, testQuantityRemaining, filter); + } + + testQuantityRemaining = testAddToEmptySlots(itemContainer, itemStack, itemMaxStack, testQuantityRemaining, filter); + if (testQuantityRemaining > 0) { + return new ItemStackTransaction(false, ActionType.ADD, itemStack, itemStack, allOrNothing, filter, Collections.emptyList()); + } + } + + List list = new ObjectArrayList<>(); + ItemStack remaining = itemStack; + if (!fullStacks) { + for (short i = 0; i < itemContainer.getCapacity() && !ItemStack.isEmpty(remaining); i++) { + ItemStackSlotTransaction transaction = internal_addToExistingSlot(itemContainer, i, remaining, itemMaxStack, filter); + list.add(transaction); + remaining = transaction.getRemainder(); + } + } + + for (short i = 0; i < itemContainer.getCapacity() && !ItemStack.isEmpty(remaining); i++) { + ItemStackSlotTransaction transaction = internal_addToEmptySlot(itemContainer, i, remaining, itemMaxStack, filter); + list.add(transaction); + remaining = transaction.getRemainder(); + } + + return new ItemStackTransaction(true, ActionType.ADD, itemStack, remaining, allOrNothing, filter, list); + }); + } + } + + protected static ListTransaction internal_addItemStacks( + @Nonnull ItemContainer itemContainer, @Nullable List itemStacks, boolean allOrNothing, boolean fullStacks, boolean filter + ) { + return itemStacks != null && !itemStacks.isEmpty() + ? itemContainer.writeAction( + () -> { + if (allOrNothing) { + for (ItemStack itemStack : itemStacks) { + int itemMaxStack = itemStack.getItem().getMaxStack(); + int testQuantityRemaining = itemStack.getQuantity(); + if (!fullStacks) { + testQuantityRemaining = testAddToExistingItemStacks(itemContainer, itemStack, itemMaxStack, testQuantityRemaining, filter); + } + + testQuantityRemaining = testAddToEmptySlots(itemContainer, itemStack, itemMaxStack, testQuantityRemaining, filter); + if (testQuantityRemaining > 0) { + return new ListTransaction<>( + false, + itemStacks.stream() + .map(i -> new ItemStackTransaction(false, ActionType.ADD, itemStack, itemStack, allOrNothing, filter, Collections.emptyList())) + .collect(Collectors.toList()) + ); + } + } + } + + List remainingItemStacks = new ObjectArrayList<>(); + + for (ItemStack itemStack : itemStacks) { + remainingItemStacks.add(internal_addItemStack(itemContainer, itemStack, allOrNothing, fullStacks, filter)); + } + + return new ListTransaction<>(true, remainingItemStacks); + } + ) + : ListTransaction.getEmptyTransaction(true); + } + + protected static ListTransaction internal_addItemStacksOrdered( + @Nonnull ItemContainer itemContainer, short offset, @Nullable List itemStacks, boolean allOrNothing, boolean filter + ) { + if (itemStacks != null && !itemStacks.isEmpty()) { + ItemContainer.validateSlotIndex(offset, itemContainer.getCapacity()); + ItemContainer.validateSlotIndex((short)(offset + itemStacks.size()), itemContainer.getCapacity()); + return itemContainer.writeAction( + () -> { + if (allOrNothing) { + for (short i = 0; i < itemStacks.size(); i++) { + short slot = (short)(offset + i); + ItemStack itemStack = itemStacks.get(i); + int itemMaxStack = itemStack.getItem().getMaxStack(); + int testQuantityRemaining = itemStack.getQuantity(); + testQuantityRemaining = testAddToExistingSlot(itemContainer, slot, itemStack, itemMaxStack, testQuantityRemaining, filter); + if (testQuantityRemaining > 0) { + List list = new ObjectArrayList<>(); + + for (short i1 = 0; i1 < itemStacks.size(); i1++) { + short islot = (short)(offset + i1); + list.add( + new ItemStackSlotTransaction( + false, ActionType.ADD, islot, null, null, null, allOrNothing, false, filter, false, itemStack, itemStack + ) + ); + } + + return new ListTransaction<>(false, list); + } + } + } + + List remainingItemStacks = new ObjectArrayList<>(); + + for (short ix = 0; ix < itemStacks.size(); ix++) { + short slot = (short)(offset + ix); + remainingItemStacks.add(internal_addItemStackToSlot(itemContainer, slot, itemStacks.get(ix), allOrNothing, filter)); + } + + return new ListTransaction<>(true, remainingItemStacks); + } + ); + } else { + return ListTransaction.getEmptyTransaction(true); + } + } + + protected static int testAddToExistingItemStacks( + @Nonnull ItemContainer container, ItemStack itemStack, int itemMaxStack, int testQuantityRemaining, boolean filter + ) { + for (short i = 0; i < container.getCapacity() && testQuantityRemaining > 0; i++) { + testQuantityRemaining = testAddToExistingSlot(container, i, itemStack, itemMaxStack, testQuantityRemaining, filter); + } + + return testQuantityRemaining; + } + + protected static ItemStackTransaction internal_removeItemStack( + @Nonnull ItemContainer itemContainer, @Nonnull ItemStack itemStack, boolean allOrNothing, boolean filter + ) { + Item item = itemStack.getItem(); + if (item == null) { + throw new IllegalArgumentException(itemStack.getItemId() + " is an invalid item!"); + } else { + return itemContainer.writeAction(() -> { + if (allOrNothing) { + int testQuantityRemaining = testRemoveItemStackFromItems(itemContainer, itemStack, itemStack.getQuantity(), filter); + if (testQuantityRemaining > 0) { + return new ItemStackTransaction(false, ActionType.REMOVE, itemStack, itemStack, allOrNothing, filter, Collections.emptyList()); + } + } + + List transactions = new ObjectArrayList<>(); + int quantityRemaining = itemStack.getQuantity(); + + for (short i = 0; i < itemContainer.getCapacity() && quantityRemaining > 0; i++) { + ItemStack slotItemStack = itemContainer.internal_getSlot(i); + if (!ItemStack.isEmpty(slotItemStack) && slotItemStack.isStackableWith(itemStack)) { + ItemStackSlotTransaction transaction = internal_removeItemStackFromSlot(itemContainer, i, quantityRemaining, false, filter); + transactions.add(transaction); + quantityRemaining = transaction.getRemainder() != null ? transaction.getRemainder().getQuantity() : 0; + } + } + + ItemStack remainder = quantityRemaining > 0 ? itemStack.withQuantity(quantityRemaining) : null; + return new ItemStackTransaction(true, ActionType.REMOVE, itemStack, remainder, allOrNothing, filter, transactions); + }); + } + } + + protected static ListTransaction internal_removeItemStacks( + @Nonnull ItemContainer itemContainer, @Nullable List itemStacks, boolean allOrNothing, boolean filter + ) { + if (itemStacks != null && !itemStacks.isEmpty()) { + for (ItemStack itemStack : itemStacks) { + Item item = itemStack.getItem(); + if (item == null) { + throw new IllegalArgumentException(itemStack.getItemId() + " is an invalid item!"); + } + } + + return itemContainer.writeAction( + () -> { + if (allOrNothing) { + for (ItemStack itemStackx : itemStacks) { + int testQuantityRemaining = testRemoveItemStackFromItems(itemContainer, itemStackx, itemStackx.getQuantity(), filter); + if (testQuantityRemaining > 0) { + return new ListTransaction<>( + false, + itemStacks.stream() + .map(i -> new ItemStackTransaction(false, ActionType.ADD, itemStackx, itemStackx, allOrNothing, filter, Collections.emptyList())) + .collect(Collectors.toList()) + ); + } + } + } + + List transactions = new ObjectArrayList<>(); + + for (short i = 0; i < itemStacks.size(); i++) { + transactions.add(internal_removeItemStack(itemContainer, itemStacks.get(i), allOrNothing, filter)); + } + + return new ListTransaction<>(true, transactions); + } + ); + } else { + return ListTransaction.getEmptyTransaction(true); + } + } + + protected static int testRemoveItemStackFromItems(@Nonnull ItemContainer container, ItemStack itemStack, int testQuantityRemaining, boolean filter) { + for (short i = 0; i < container.getCapacity() && testQuantityRemaining > 0; i++) { + if (!filter || !container.cantRemoveFromSlot(i)) { + ItemStack slotItemStack = container.internal_getSlot(i); + if (!ItemStack.isEmpty(slotItemStack) && slotItemStack.isStackableWith(itemStack)) { + int quantity = slotItemStack.getQuantity(); + int quantityAdjustment = Math.min(quantity, testQuantityRemaining); + testQuantityRemaining -= quantityAdjustment; + } + } + } + + return testQuantityRemaining; + } + + protected static TestRemoveItemSlotResult testRemoveItemStackSlotFromItems( + @Nonnull ItemContainer container, ItemStack itemStack, int testQuantityRemaining, boolean filter + ) { + return testRemoveItemStackSlotFromItems(container, itemStack, testQuantityRemaining, filter, (a, b) -> ItemStack.isStackableWith(a, b)); + } + + protected static TestRemoveItemSlotResult testRemoveItemStackSlotFromItems( + @Nonnull ItemContainer container, ItemStack itemStack, int testQuantityRemaining, boolean filter, BiPredicate predicate + ) { + TestRemoveItemSlotResult result = new TestRemoveItemSlotResult(testQuantityRemaining); + + for (short i = 0; i < container.getCapacity() && result.quantityRemaining > 0; i++) { + if (!filter || !container.cantRemoveFromSlot(i)) { + ItemStack slotItemStack = container.internal_getSlot(i); + if (!ItemStack.isEmpty(slotItemStack) && predicate.test(slotItemStack, itemStack)) { + int quantity = slotItemStack.getQuantity(); + int quantityAdjustment = Math.min(quantity, result.quantityRemaining); + result.quantityRemaining -= quantityAdjustment; + result.picked.put(i, quantityAdjustment); + } + } + } + + return result; + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/InternalContainerUtilMaterial.java b/src/com/hypixel/hytale/server/core/inventory/container/InternalContainerUtilMaterial.java new file mode 100644 index 0000000..35e1bf4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/InternalContainerUtilMaterial.java @@ -0,0 +1,242 @@ +package com.hypixel.hytale.server.core.inventory.container; + +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.MaterialQuantity; +import com.hypixel.hytale.server.core.inventory.transaction.ActionType; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.MaterialSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.MaterialTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ResourceSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.SlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.TagSlotTransaction; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InternalContainerUtilMaterial { + public InternalContainerUtilMaterial() { + } + + @Nonnull + protected static MaterialSlotTransaction internal_removeMaterialFromSlot( + @Nonnull ItemContainer itemContainer, short slot, @Nonnull MaterialQuantity material, boolean allOrNothing, boolean filter + ) { + ItemContainer.validateSlotIndex(slot, itemContainer.getCapacity()); + ItemContainer.validateQuantity(material.getQuantity()); + if (material.getItemId() != null) { + ItemStackSlotTransaction slotTransaction = InternalContainerUtilItemStack.internal_removeItemStackFromSlot( + itemContainer, slot, material.toItemStack(), material.getQuantity(), allOrNothing, filter, (a, b) -> ItemStack.isEquivalentType(a, b) + ); + return new MaterialSlotTransaction( + material, slotTransaction.getRemainder() != null ? slotTransaction.getRemainder().getQuantity() : 0, slotTransaction + ); + } else if (material.getTagIndex() != Integer.MIN_VALUE) { + TagSlotTransaction tagTransaction = InternalContainerUtilTag.internal_removeTagFromSlot( + itemContainer, slot, material.getTagIndex(), material.getQuantity(), allOrNothing, filter + ); + return new MaterialSlotTransaction(material, tagTransaction.getRemainder(), tagTransaction); + } else { + ResourceSlotTransaction resourceTransaction = InternalContainerUtilResource.internal_removeResourceFromSlot( + itemContainer, slot, material.toResource(), allOrNothing, filter + ); + return new MaterialSlotTransaction(material, resourceTransaction.getRemainder(), resourceTransaction); + } + } + + protected static MaterialTransaction internal_removeMaterial( + @Nonnull ItemContainer itemContainer, @Nonnull MaterialQuantity material, boolean allOrNothing, boolean exactAmount, boolean filter + ) { + return itemContainer.writeAction( + () -> { + if (allOrNothing || exactAmount) { + int testQuantityRemaining = testRemoveMaterialFromItems(itemContainer, material, material.getQuantity(), filter); + if (testQuantityRemaining > 0) { + return new MaterialTransaction( + false, ActionType.REMOVE, material, material.getQuantity(), allOrNothing, exactAmount, filter, Collections.emptyList() + ); + } + + if (exactAmount && testQuantityRemaining < 0) { + return new MaterialTransaction( + false, ActionType.REMOVE, material, material.getQuantity(), allOrNothing, exactAmount, filter, Collections.emptyList() + ); + } + } + + List list = new ObjectArrayList<>(); + int quantityRemaining = material.getQuantity(); + + for (short i = 0; i < itemContainer.getCapacity() && quantityRemaining > 0; i++) { + MaterialQuantity clone = material.clone(quantityRemaining); + MaterialSlotTransaction transaction = internal_removeMaterialFromSlot(itemContainer, i, clone, false, filter); + if (transaction.succeeded()) { + list.add(transaction); + quantityRemaining = transaction.getRemainder(); + } + } + + return new MaterialTransaction( + quantityRemaining != material.getQuantity(), ActionType.REMOVE, material, material.getQuantity(), allOrNothing, exactAmount, filter, list + ); + } + ); + } + + protected static ListTransaction internal_removeMaterials( + @Nonnull ItemContainer itemContainer, @Nullable List materials, boolean allOrNothing, boolean exactAmount, boolean filter + ) { + return materials != null && !materials.isEmpty() + ? itemContainer.writeAction( + () -> { + if (allOrNothing || exactAmount) { + for (MaterialQuantity material : materials) { + int testQuantityRemaining = testRemoveMaterialFromItems(itemContainer, material, material.getQuantity(), filter); + if (testQuantityRemaining > 0) { + return new ListTransaction<>( + false, + materials.stream() + .map( + remainder -> new MaterialTransaction( + false, ActionType.REMOVE, material, material.getQuantity(), allOrNothing, exactAmount, filter, Collections.emptyList() + ) + ) + .collect(Collectors.toList()) + ); + } + + if (exactAmount && testQuantityRemaining < 0) { + return new ListTransaction<>( + false, + materials.stream() + .map( + remainder -> new MaterialTransaction( + false, ActionType.REMOVE, material, material.getQuantity(), allOrNothing, exactAmount, filter, Collections.emptyList() + ) + ) + .collect(Collectors.toList()) + ); + } + } + } + + List transactions = new ObjectArrayList<>(); + + for (MaterialQuantity material : materials) { + transactions.add(internal_removeMaterial(itemContainer, material, allOrNothing, exactAmount, filter)); + } + + return new ListTransaction<>(true, transactions); + } + ) + : ListTransaction.getEmptyTransaction(true); + } + + public static int testRemoveMaterialFromItems( + @Nonnull ItemContainer container, @Nonnull MaterialQuantity material, int testQuantityRemaining, boolean filter + ) { + if (material.getItemId() != null) { + return InternalContainerUtilItemStack.testRemoveItemStackFromItems(container, material.toItemStack(), testQuantityRemaining, filter); + } else { + return material.getTagIndex() != Integer.MIN_VALUE + ? InternalContainerUtilTag.testRemoveTagFromItems(container, material.getTagIndex(), testQuantityRemaining, filter) + : InternalContainerUtilResource.testRemoveResourceFromItems(container, material.toResource(), testQuantityRemaining, filter); + } + } + + public static TestRemoveItemSlotResult getTestRemoveMaterialFromItems( + @Nonnull ItemContainer container, @Nonnull MaterialQuantity material, int testQuantityRemaining, boolean filter + ) { + if (material.getItemId() != null) { + return InternalContainerUtilItemStack.testRemoveItemStackSlotFromItems( + container, material.toItemStack(), testQuantityRemaining, filter, (a, b) -> ItemStack.isEquivalentType(a, b) + ); + } else { + return material.getTagIndex() != Integer.MIN_VALUE + ? InternalContainerUtilTag.testRemoveTagSlotFromItems(container, material.getTagIndex(), testQuantityRemaining, filter) + : InternalContainerUtilResource.testRemoveResourceSlotFromItems(container, material.toResource(), testQuantityRemaining, filter); + } + } + + protected static ListTransaction internal_removeMaterialsOrdered( + @Nonnull ItemContainer itemContainer, short offset, @Nullable List materials, boolean allOrNothing, boolean exactAmount, boolean filter + ) { + if (materials != null && !materials.isEmpty()) { + return offset + materials.size() > itemContainer.getCapacity() + ? ListTransaction.getEmptyTransaction(false) + : itemContainer.writeAction( + () -> { + if (allOrNothing || exactAmount) { + for (short i = 0; i < materials.size(); i++) { + short slot = (short)(offset + i); + MaterialQuantity material = materials.get(i); + int testQuantityRemaining = testRemoveMaterialFromSlot(itemContainer, slot, material, material.getQuantity(), filter); + if (testQuantityRemaining > 0) { + List list = new ObjectArrayList<>(); + + for (short i1 = 0; i1 < materials.size(); i1++) { + short islot = (short)(offset + i1); + list.add( + new MaterialSlotTransaction( + material, + material.getQuantity(), + new SlotTransaction(false, ActionType.REMOVE, islot, null, null, null, allOrNothing, exactAmount, filter) + ) + ); + } + + return new ListTransaction<>(false, list); + } + + if (exactAmount && testQuantityRemaining < 0) { + List list = new ObjectArrayList<>(); + + for (short i1 = 0; i1 < materials.size(); i1++) { + short islot = (short)(offset + i1); + list.add( + new MaterialSlotTransaction( + material, + material.getQuantity(), + new SlotTransaction(false, ActionType.REMOVE, islot, null, null, null, allOrNothing, exactAmount, filter) + ) + ); + } + + return new ListTransaction<>(false, list); + } + } + } + + List transactions = new ObjectArrayList<>(); + + for (short i = 0; i < materials.size(); i++) { + short slotx = (short)(offset + i); + MaterialQuantity materialx = materials.get(i); + transactions.add(internal_removeMaterialFromSlot(itemContainer, slotx, materialx, allOrNothing, filter)); + } + + return new ListTransaction<>(true, transactions); + } + ); + } else { + return ListTransaction.getEmptyTransaction(true); + } + } + + public static int testRemoveMaterialFromSlot( + @Nonnull ItemContainer container, short slot, @Nonnull MaterialQuantity material, int testQuantityRemaining, boolean filter + ) { + if (material.getItemId() != null) { + return InternalContainerUtilItemStack.testRemoveItemStackFromSlot( + container, slot, material.toItemStack(), testQuantityRemaining, filter, (a, b) -> ItemStack.isEquivalentType(a, b) + ); + } else { + return material.getTagIndex() != Integer.MIN_VALUE + ? InternalContainerUtilTag.testRemoveTagFromSlot(container, slot, material.getTagIndex(), testQuantityRemaining, filter) + : InternalContainerUtilResource.testRemoveResourceFromSlot(container, slot, material.toResource(), testQuantityRemaining, filter); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/InternalContainerUtilResource.java b/src/com/hypixel/hytale/server/core/inventory/container/InternalContainerUtilResource.java new file mode 100644 index 0000000..fd69ceb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/InternalContainerUtilResource.java @@ -0,0 +1,233 @@ +package com.hypixel.hytale.server.core.inventory.container; + +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.protocol.ItemResourceType; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.ResourceQuantity; +import com.hypixel.hytale.server.core.inventory.transaction.ActionType; +import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ResourceSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ResourceTransaction; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InternalContainerUtilResource { + public InternalContainerUtilResource() { + } + + protected static ResourceSlotTransaction internal_removeResourceFromSlot( + @Nonnull ItemContainer itemContainer, short slot, @Nonnull ResourceQuantity resource, boolean allOrNothing, boolean filter + ) { + ItemContainer.validateSlotIndex(slot, itemContainer.getCapacity()); + ItemContainer.validateQuantity(resource.getQuantity()); + return itemContainer.writeAction( + () -> { + if (filter && itemContainer.cantRemoveFromSlot(slot)) { + ItemStack itemStack = itemContainer.internal_getSlot(slot); + return new ResourceSlotTransaction( + false, ActionType.REMOVE, slot, itemStack, itemStack, null, allOrNothing, false, filter, resource, resource.getQuantity(), 0 + ); + } else { + ItemStack slotItemStack = itemContainer.internal_getSlot(slot); + if (slotItemStack == null) { + return new ResourceSlotTransaction( + false, ActionType.REMOVE, slot, null, null, null, allOrNothing, false, filter, resource, resource.getQuantity(), 0 + ); + } else { + Item slotItem = slotItemStack.getItem(); + int quantityInItems = slotItemStack.getQuantity(); + ItemResourceType resourceType = resource.getResourceType(slotItem); + if (resourceType == null) { + return new ResourceSlotTransaction( + false, ActionType.REMOVE, slot, slotItemStack, slotItemStack, null, allOrNothing, false, filter, resource, resource.getQuantity(), 0 + ); + } else { + int resourceTypeQuantity = resourceType.quantity; + int quantityRemaining = resource.getQuantity(); + int quantityInItemsRemaining = MathUtil.ceil((double)quantityRemaining / resourceTypeQuantity); + int quantityInItemsAdjustment = Math.min(quantityInItems, quantityInItemsRemaining); + int newItemStackQuantity = Math.max(quantityInItems - quantityInItemsAdjustment, 0); + int quantityAdjustment = quantityInItemsAdjustment * resourceTypeQuantity; + quantityRemaining -= quantityAdjustment; + if (allOrNothing && quantityRemaining > 0) { + return new ResourceSlotTransaction( + false, ActionType.REMOVE, slot, slotItemStack, slotItemStack, null, allOrNothing, false, filter, resource, resource.getQuantity(), 0 + ); + } else if (quantityAdjustment <= 0) { + return new ResourceSlotTransaction( + false, ActionType.REMOVE, slot, slotItemStack, slotItemStack, null, allOrNothing, false, filter, resource, resource.getQuantity(), 0 + ); + } else { + ItemStack slotNewItemStack = slotItemStack.withQuantity(newItemStackQuantity); + itemContainer.internal_setSlot(slot, slotNewItemStack); + ItemStack newStack = slotItemStack.withQuantity(quantityInItemsAdjustment); + return new ResourceSlotTransaction( + true, + ActionType.REMOVE, + slot, + slotItemStack, + slotNewItemStack, + newStack, + allOrNothing, + false, + filter, + resource, + quantityRemaining, + quantityAdjustment + ); + } + } + } + } + } + ); + } + + protected static ResourceTransaction internal_removeResource( + @Nonnull ItemContainer itemContainer, @Nonnull ResourceQuantity resource, boolean allOrNothing, boolean exactAmount, boolean filter + ) { + return itemContainer.writeAction( + () -> { + if (allOrNothing || exactAmount) { + int testQuantityRemaining = testRemoveResourceFromItems(itemContainer, resource, resource.getQuantity(), filter); + if (testQuantityRemaining > 0) { + return new ResourceTransaction( + false, ActionType.REMOVE, resource, resource.getQuantity(), 0, allOrNothing, exactAmount, filter, Collections.emptyList() + ); + } + + if (exactAmount && testQuantityRemaining < 0) { + return new ResourceTransaction( + false, ActionType.REMOVE, resource, resource.getQuantity(), 0, allOrNothing, exactAmount, filter, Collections.emptyList() + ); + } + } + + List list = new ObjectArrayList<>(); + int consumed = 0; + int quantityRemaining = resource.getQuantity(); + + for (short i = 0; i < itemContainer.getCapacity() && quantityRemaining > 0; i++) { + ResourceQuantity clone = resource.clone(quantityRemaining); + ResourceSlotTransaction transaction = internal_removeResourceFromSlot(itemContainer, i, clone, false, filter); + if (transaction.succeeded()) { + list.add(transaction); + quantityRemaining = transaction.getRemainder(); + consumed += transaction.getConsumed(); + } + } + + return new ResourceTransaction( + quantityRemaining != resource.getQuantity(), ActionType.REMOVE, resource, quantityRemaining, consumed, allOrNothing, exactAmount, filter, list + ); + } + ); + } + + protected static ListTransaction internal_removeResources( + @Nonnull ItemContainer itemContainer, @Nullable List resources, boolean allOrNothing, boolean exactAmount, boolean filter + ) { + return resources != null && !resources.isEmpty() + ? itemContainer.writeAction( + () -> { + if (allOrNothing || exactAmount) { + for (ResourceQuantity resource : resources) { + int testQuantityRemaining = testRemoveResourceFromItems(itemContainer, resource, resource.getQuantity(), filter); + if (testQuantityRemaining > 0) { + return new ListTransaction<>( + false, + resources.stream() + .map( + remainder -> new ResourceTransaction( + false, ActionType.REMOVE, resource, resource.getQuantity(), 0, allOrNothing, exactAmount, filter, Collections.emptyList() + ) + ) + .collect(Collectors.toList()) + ); + } + + if (exactAmount && testQuantityRemaining < 0) { + return new ListTransaction<>( + false, + resources.stream() + .map( + remainder -> new ResourceTransaction( + false, ActionType.REMOVE, resource, resource.getQuantity(), 0, allOrNothing, exactAmount, filter, Collections.emptyList() + ) + ) + .collect(Collectors.toList()) + ); + } + } + } + + List transactions = new ObjectArrayList<>(); + + for (ResourceQuantity resource : resources) { + transactions.add(internal_removeResource(itemContainer, resource, allOrNothing, exactAmount, filter)); + } + + return new ListTransaction<>(true, transactions); + } + ) + : ListTransaction.getEmptyTransaction(true); + } + + public static int testRemoveResourceFromItems( + @Nonnull ItemContainer container, @Nonnull ResourceQuantity resource, int testQuantityRemaining, boolean filter + ) { + for (short i = 0; i < container.getCapacity() && testQuantityRemaining > 0; i++) { + testQuantityRemaining = testRemoveResourceFromSlot(container, i, resource, testQuantityRemaining, filter); + } + + return testQuantityRemaining; + } + + public static TestRemoveItemSlotResult testRemoveResourceSlotFromItems( + @Nonnull ItemContainer container, @Nonnull ResourceQuantity resource, int testQuantityRemaining, boolean filter + ) { + TestRemoveItemSlotResult result = new TestRemoveItemSlotResult(testQuantityRemaining); + + for (short i = 0; i < container.getCapacity() && result.quantityRemaining > 0; i++) { + int newValue = testRemoveResourceFromSlot(container, i, resource, result.quantityRemaining, filter); + if (newValue != result.quantityRemaining) { + int diff = result.quantityRemaining - newValue; + result.quantityRemaining = newValue; + result.picked.put(i, diff); + } + } + + return result; + } + + public static int testRemoveResourceFromSlot( + @Nonnull ItemContainer container, short slot, @Nonnull ResourceQuantity resource, int testQuantityRemaining, boolean filter + ) { + if (filter && container.cantRemoveFromSlot(slot)) { + return testQuantityRemaining; + } else { + ItemStack slotItemStack = container.internal_getSlot(slot); + if (ItemStack.isEmpty(slotItemStack)) { + return testQuantityRemaining; + } else { + Item slotItem = slotItemStack.getItem(); + ItemResourceType resourceType = resource.getResourceType(slotItem); + if (resourceType == null) { + return testQuantityRemaining; + } else { + int resourceTypeQuantity = resourceType.quantity; + int quantityInItemsRemaining = MathUtil.ceil((double)testQuantityRemaining / resourceTypeQuantity); + int quantityInItems = slotItemStack.getQuantity(); + int quantityInItemsAdjustment = Math.min(quantityInItems, quantityInItemsRemaining); + int quantityAdjustment = quantityInItemsAdjustment * resourceTypeQuantity; + return testQuantityRemaining - quantityAdjustment; + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/InternalContainerUtilTag.java b/src/com/hypixel/hytale/server/core/inventory/container/InternalContainerUtilTag.java new file mode 100644 index 0000000..12cf63c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/InternalContainerUtilTag.java @@ -0,0 +1,146 @@ +package com.hypixel.hytale.server.core.inventory.container; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.transaction.ActionType; +import com.hypixel.hytale.server.core.inventory.transaction.TagSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.TagTransaction; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; + +public class InternalContainerUtilTag { + public InternalContainerUtilTag() { + } + + protected static TagSlotTransaction internal_removeTagFromSlot( + @Nonnull ItemContainer itemContainer, short slot, int tagIndex, int quantity, boolean allOrNothing, boolean filter + ) { + ItemContainer.validateSlotIndex(slot, itemContainer.getCapacity()); + ItemContainer.validateQuantity(quantity); + return itemContainer.writeAction( + () -> { + if (filter && itemContainer.cantRemoveFromSlot(slot)) { + ItemStack itemStack = itemContainer.internal_getSlot(slot); + return new TagSlotTransaction(false, ActionType.REMOVE, slot, itemStack, itemStack, null, allOrNothing, false, filter, tagIndex, quantity); + } else { + ItemStack slotItemStack = itemContainer.internal_getSlot(slot); + if (slotItemStack == null) { + return new TagSlotTransaction(false, ActionType.REMOVE, slot, null, null, null, allOrNothing, false, filter, tagIndex, quantity); + } else { + Item slotItem = slotItemStack.getItem(); + int quantityInItems = slotItemStack.getQuantity(); + if (slotItem.getData() != null && slotItem.getData().getExpandedTagIndexes().contains(tagIndex)) { + int quantityInItemsAdjustment = Math.min(quantityInItems, quantity); + int newItemStackQuantity = quantityInItems - quantityInItemsAdjustment; + int quantityRemaining = quantity - quantityInItemsAdjustment; + if (allOrNothing && quantityRemaining > 0) { + return new TagSlotTransaction( + false, ActionType.REMOVE, slot, slotItemStack, slotItemStack, null, allOrNothing, false, filter, tagIndex, quantity + ); + } else { + ItemStack slotNewItemStack = slotItemStack.withQuantity(newItemStackQuantity); + itemContainer.internal_setSlot(slot, slotNewItemStack); + ItemStack newStack = slotItemStack.withQuantity(quantityInItemsAdjustment); + return new TagSlotTransaction( + true, ActionType.REMOVE, slot, slotItemStack, slotNewItemStack, newStack, allOrNothing, false, filter, tagIndex, quantityRemaining + ); + } + } else { + return new TagSlotTransaction( + false, ActionType.REMOVE, slot, slotItemStack, slotItemStack, null, allOrNothing, false, filter, tagIndex, quantity + ); + } + } + } + } + ); + } + + protected static TagTransaction internal_removeTag( + @Nonnull ItemContainer itemContainer, int tagIndex, int quantity, boolean allOrNothing, boolean exactAmount, boolean filter + ) { + return itemContainer.writeAction(() -> { + if (allOrNothing || exactAmount) { + int testQuantityRemaining = testRemoveTagFromItems(itemContainer, tagIndex, quantity, filter); + if (testQuantityRemaining > 0) { + return new TagTransaction(false, ActionType.REMOVE, tagIndex, quantity, allOrNothing, exactAmount, filter, Collections.emptyList()); + } + + if (exactAmount && testQuantityRemaining < 0) { + return new TagTransaction(false, ActionType.REMOVE, tagIndex, quantity, allOrNothing, exactAmount, filter, Collections.emptyList()); + } + } + + List list = new ObjectArrayList<>(); + int quantityRemaining = quantity; + + for (short i = 0; i < itemContainer.getCapacity() && quantityRemaining > 0; i++) { + TagSlotTransaction transaction = internal_removeTagFromSlot(itemContainer, i, tagIndex, quantityRemaining, allOrNothing, filter); + list.add(transaction); + quantityRemaining = transaction.getRemainder(); + } + + return new TagTransaction(true, ActionType.REMOVE, tagIndex, quantityRemaining, allOrNothing, exactAmount, filter, list); + }); + } + + protected static int testRemoveTagFromItems(@Nonnull ItemContainer container, int tagIndex, int testQuantityRemaining, boolean filter) { + for (short i = 0; i < container.getCapacity() && testQuantityRemaining > 0; i++) { + if (!filter || !container.cantRemoveFromSlot(i)) { + ItemStack slotItemStack = container.internal_getSlot(i); + if (!ItemStack.isEmpty(slotItemStack)) { + AssetExtraInfo.Data slotItemData = slotItemStack.getItem().getData(); + if (slotItemData != null && slotItemData.getExpandedTagIndexes().contains(tagIndex)) { + int quantityInItems = slotItemStack.getQuantity(); + testQuantityRemaining -= Math.min(quantityInItems, testQuantityRemaining); + } + } + } + } + + return testQuantityRemaining; + } + + protected static TestRemoveItemSlotResult testRemoveTagSlotFromItems( + @Nonnull ItemContainer container, int tagIndex, int testQuantityRemaining, boolean filter + ) { + TestRemoveItemSlotResult result = new TestRemoveItemSlotResult(testQuantityRemaining); + + for (short i = 0; i < container.getCapacity() && result.quantityRemaining > 0; i++) { + if (!filter || !container.cantRemoveFromSlot(i)) { + ItemStack slotItemStack = container.internal_getSlot(i); + if (!ItemStack.isEmpty(slotItemStack)) { + AssetExtraInfo.Data slotItemData = slotItemStack.getItem().getData(); + if (slotItemData != null && slotItemData.getExpandedTagIndexes().contains(tagIndex)) { + int quantityInItems = slotItemStack.getQuantity(); + result.quantityRemaining = result.quantityRemaining - Math.min(quantityInItems, result.quantityRemaining); + } + } + } + } + + return result; + } + + protected static int testRemoveTagFromSlot(@Nonnull ItemContainer container, short slot, int tagIndex, int testQuantityRemaining, boolean filter) { + if (filter && container.cantRemoveFromSlot(slot)) { + return testQuantityRemaining; + } else { + ItemStack slotItemStack = container.internal_getSlot(slot); + if (ItemStack.isEmpty(slotItemStack)) { + return testQuantityRemaining; + } else { + Item slotItem = slotItemStack.getItem(); + if (!slotItem.getData().getExpandedTagIndexes().contains(tagIndex)) { + return testQuantityRemaining; + } else { + int quantityInItems = slotItemStack.getQuantity(); + return testQuantityRemaining - Math.min(quantityInItems, testQuantityRemaining); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/ItemContainer.java b/src/com/hypixel/hytale/server/core/inventory/container/ItemContainer.java new file mode 100644 index 0000000..4bbc6ff --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/ItemContainer.java @@ -0,0 +1,1419 @@ +package com.hypixel.hytale.server.core.inventory.container; + +import com.hypixel.fastutil.shorts.Short2ObjectConcurrentHashMap; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.event.EventPriority; +import com.hypixel.hytale.event.EventRegistration; +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.event.SyncEventBusRegistry; +import com.hypixel.hytale.function.consumer.ShortObjectConsumer; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.InventorySection; +import com.hypixel.hytale.protocol.ItemResourceType; +import com.hypixel.hytale.protocol.ItemWithAllMetadata; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.MaterialQuantity; +import com.hypixel.hytale.server.core.inventory.ResourceQuantity; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterActionType; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterType; +import com.hypixel.hytale.server.core.inventory.container.filter.SlotFilter; +import com.hypixel.hytale.server.core.inventory.transaction.ActionType; +import com.hypixel.hytale.server.core.inventory.transaction.ClearTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.MaterialSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.MaterialTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.MoveTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.MoveType; +import com.hypixel.hytale.server.core.inventory.transaction.ResourceSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ResourceTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.SlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.TagSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.TagTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.Transaction; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArrays; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class ItemContainer { + public static final CodecMapCodec CODEC = new CodecMapCodec<>(true); + public static final boolean DEFAULT_ADD_ALL_OR_NOTHING = false; + public static final boolean DEFAULT_REMOVE_ALL_OR_NOTHING = true; + public static final boolean DEFAULT_FULL_STACKS = false; + public static final boolean DEFAULT_EXACT_AMOUNT = true; + public static final boolean DEFAULT_FILTER = true; + protected static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + protected final SyncEventBusRegistry externalChangeEventRegistry = new SyncEventBusRegistry<>( + LOGGER, ItemContainer.ItemContainerChangeEvent.class + ); + protected final SyncEventBusRegistry internalChangeEventRegistry = new SyncEventBusRegistry<>( + LOGGER, ItemContainer.ItemContainerChangeEvent.class + ); + + public ItemContainer() { + } + + public abstract short getCapacity(); + + public abstract void setGlobalFilter(FilterType var1); + + public abstract void setSlotFilter(FilterActionType var1, short var2, SlotFilter var3); + + public abstract ItemContainer clone(); + + protected abstract V readAction(Supplier var1); + + protected abstract V readAction(Function var1, X var2); + + protected abstract V writeAction(Supplier var1); + + protected abstract V writeAction(Function var1, X var2); + + protected abstract ClearTransaction internal_clear(); + + @Nullable + protected abstract ItemStack internal_getSlot(short var1); + + @Nullable + protected abstract ItemStack internal_setSlot(short var1, ItemStack var2); + + @Nullable + protected abstract ItemStack internal_removeSlot(short var1); + + protected abstract boolean cantAddToSlot(short var1, ItemStack var2, ItemStack var3); + + protected abstract boolean cantRemoveFromSlot(short var1); + + protected abstract boolean cantDropFromSlot(short var1); + + protected abstract boolean cantMoveToSlot(ItemContainer var1, short var2); + + @Nonnull + public InventorySection toPacket() { + InventorySection packet = new InventorySection(); + packet.capacity = this.getCapacity(); + packet.items = this.toProtocolMap(); + return packet; + } + + @Nonnull + public Map toProtocolMap() { + Map map = new Int2ObjectOpenHashMap<>(); + this.forEachWithMeta((slot, itemStack, _map) -> { + if (!ItemStack.isEmpty(itemStack) && itemStack.isValid()) { + _map.put(Integer.valueOf(slot), itemStack.toPacket()); + } + }, map); + return map; + } + + public EventRegistration registerChangeEvent(@Nonnull Consumer consumer) { + return this.registerChangeEvent((short)0, consumer); + } + + public EventRegistration registerChangeEvent(@Nonnull EventPriority priority, @Nonnull Consumer consumer) { + return this.registerChangeEvent(priority.getValue(), consumer); + } + + public EventRegistration registerChangeEvent(short priority, @Nonnull Consumer consumer) { + return this.externalChangeEventRegistry.register(priority, null, consumer); + } + + public ClearTransaction clear() { + ClearTransaction transaction = this.writeAction(this::internal_clear); + this.sendUpdate(transaction); + return transaction; + } + + public boolean canAddItemStackToSlot(short slot, @Nonnull ItemStack itemStack, boolean allOrNothing, boolean filter) { + validateSlotIndex(slot, this.getCapacity()); + return this.writeAction(() -> { + int quantityRemaining = itemStack.getQuantity(); + ItemStack slotItemStack = this.internal_getSlot(slot); + if (filter && this.cantAddToSlot(slot, itemStack, slotItemStack)) { + return false; + } else if (slotItemStack == null) { + return true; + } else if (!itemStack.isStackableWith(slotItemStack)) { + return false; + } else { + int quantity = slotItemStack.getQuantity(); + int quantityAdjustment = Math.min(slotItemStack.getItem().getMaxStack() - quantity, quantityRemaining); + int newQuantityRemaining = quantityRemaining - quantityAdjustment; + return allOrNothing ? quantityRemaining <= 0 : quantityRemaining != newQuantityRemaining; + } + }); + } + + @Nonnull + public ItemStackSlotTransaction addItemStackToSlot(short slot, @Nonnull ItemStack itemStack) { + return this.addItemStackToSlot(slot, itemStack, false, true); + } + + @Nonnull + public ItemStackSlotTransaction addItemStackToSlot(short slot, @Nonnull ItemStack itemStack, boolean allOrNothing, boolean filter) { + ItemStackSlotTransaction transaction = InternalContainerUtilItemStack.internal_addItemStackToSlot(this, slot, itemStack, allOrNothing, filter); + this.sendUpdate(transaction); + return transaction; + } + + @Nonnull + public ItemStackSlotTransaction setItemStackForSlot(short slot, ItemStack itemStack) { + return this.setItemStackForSlot(slot, itemStack, true); + } + + @Nonnull + public ItemStackSlotTransaction setItemStackForSlot(short slot, ItemStack itemStack, boolean filter) { + ItemStackSlotTransaction transaction = InternalContainerUtilItemStack.internal_setItemStackForSlot(this, slot, itemStack, filter); + this.sendUpdate(transaction); + return transaction; + } + + @Nullable + public ItemStack getItemStack(short slot) { + validateSlotIndex(slot, this.getCapacity()); + return this.readAction(() -> this.internal_getSlot(slot)); + } + + @Nonnull + public ItemStackSlotTransaction replaceItemStackInSlot(short slot, ItemStack itemStackToRemove, ItemStack itemStack) { + ItemStackSlotTransaction transaction = this.internal_replaceItemStack(slot, itemStackToRemove, itemStack); + this.sendUpdate(transaction); + return transaction; + } + + public ListTransaction replaceAll(SlotReplacementFunction func) { + return this.replaceAll(func, true); + } + + private ListTransaction replaceAll(SlotReplacementFunction func, boolean ignoreEmpty) { + ListTransaction transaction = this.writeAction( + () -> { + short capacity = this.getCapacity(); + ObjectArrayList transactionsList = new ObjectArrayList<>(capacity); + + for (short slot = 0; slot < capacity; slot++) { + ItemStack existing = this.internal_getSlot(slot); + if (!ignoreEmpty || !ItemStack.isEmpty(existing)) { + ItemStack replacement = func.replace(slot, existing); + this.internal_setSlot(slot, replacement); + transactionsList.add( + new ItemStackSlotTransaction( + true, ActionType.REPLACE, slot, existing, replacement, existing, true, false, false, false, replacement, replacement + ) + ); + } + } + + return new ListTransaction<>(true, transactionsList); + } + ); + this.sendUpdate(transaction); + return transaction; + } + + protected ItemStackSlotTransaction internal_replaceItemStack(short slot, @Nullable ItemStack itemStackToRemove, ItemStack itemStack) { + validateSlotIndex(slot, this.getCapacity()); + return this.writeAction( + () -> { + ItemStack slotItemStack = this.internal_getSlot(slot); + if ((slotItemStack != null || itemStackToRemove == null) + && (slotItemStack == null || itemStackToRemove != null) + && (slotItemStack == null || itemStackToRemove.isStackableWith(slotItemStack))) { + this.internal_setSlot(slot, itemStack); + return new ItemStackSlotTransaction( + true, ActionType.REPLACE, slot, slotItemStack, itemStack, slotItemStack, true, false, false, false, itemStack, null + ); + } else { + return new ItemStackSlotTransaction( + false, ActionType.REPLACE, slot, slotItemStack, slotItemStack, null, true, false, false, false, itemStack, itemStack + ); + } + } + ); + } + + @Nonnull + public SlotTransaction removeItemStackFromSlot(short slot) { + return this.removeItemStackFromSlot(slot, true); + } + + @Nonnull + public SlotTransaction removeItemStackFromSlot(short slot, boolean filter) { + SlotTransaction transaction = InternalContainerUtilItemStack.internal_removeItemStackFromSlot(this, slot, filter); + this.sendUpdate(transaction); + return transaction; + } + + @Nonnull + public ItemStackSlotTransaction removeItemStackFromSlot(short slot, int quantityToRemove) { + return this.removeItemStackFromSlot(slot, quantityToRemove, true, true); + } + + @Nonnull + public ItemStackSlotTransaction removeItemStackFromSlot(short slot, int quantityToRemove, boolean allOrNothing, boolean filter) { + ItemStackSlotTransaction transaction = InternalContainerUtilItemStack.internal_removeItemStackFromSlot(this, slot, quantityToRemove, allOrNothing, filter); + this.sendUpdate(transaction); + return transaction; + } + + @Deprecated + public ItemStackSlotTransaction internal_removeItemStack(short slot, int quantityToRemove) { + return InternalContainerUtilItemStack.internal_removeItemStackFromSlot(this, slot, quantityToRemove, true, true); + } + + @Nonnull + public ItemStackSlotTransaction removeItemStackFromSlot(short slot, ItemStack itemStackToRemove, int quantityToRemove) { + return this.removeItemStackFromSlot(slot, itemStackToRemove, quantityToRemove, true, true); + } + + @Nonnull + public ItemStackSlotTransaction removeItemStackFromSlot(short slot, ItemStack itemStackToRemove, int quantityToRemove, boolean allOrNothing, boolean filter) { + ItemStackSlotTransaction transaction = InternalContainerUtilItemStack.internal_removeItemStackFromSlot( + this, slot, itemStackToRemove, quantityToRemove, allOrNothing, filter + ); + this.sendUpdate(transaction); + return transaction; + } + + @Nonnull + public MaterialSlotTransaction removeMaterialFromSlot(short slot, @Nonnull MaterialQuantity material) { + return this.removeMaterialFromSlot(slot, material, true, true, true); + } + + @Nonnull + public MaterialSlotTransaction removeMaterialFromSlot( + short slot, @Nonnull MaterialQuantity material, boolean allOrNothing, boolean exactAmount, boolean filter + ) { + MaterialSlotTransaction transaction = InternalContainerUtilMaterial.internal_removeMaterialFromSlot(this, slot, material, allOrNothing, filter); + this.sendUpdate(transaction); + return transaction; + } + + @Nonnull + public ResourceSlotTransaction removeResourceFromSlot(short slot, @Nonnull ResourceQuantity resource) { + return this.removeResourceFromSlot(slot, resource, true, true, true); + } + + @Nonnull + public ResourceSlotTransaction removeResourceFromSlot( + short slot, @Nonnull ResourceQuantity resource, boolean allOrNothing, boolean exactAmount, boolean filter + ) { + ResourceSlotTransaction transaction = InternalContainerUtilResource.internal_removeResourceFromSlot(this, slot, resource, allOrNothing, filter); + this.sendUpdate(transaction); + return transaction; + } + + @Nonnull + public TagSlotTransaction removeTagFromSlot(short slot, int tagIndex, int quantity) { + return this.removeTagFromSlot(slot, tagIndex, quantity, true, true); + } + + @Nonnull + public TagSlotTransaction removeTagFromSlot(short slot, int tagIndex, int quantity, boolean allOrNothing, boolean filter) { + TagSlotTransaction transaction = InternalContainerUtilTag.internal_removeTagFromSlot(this, slot, tagIndex, quantity, allOrNothing, filter); + this.sendUpdate(transaction); + return transaction; + } + + @Nonnull + public MoveTransaction moveItemStackFromSlot(short slot, @Nonnull ItemContainer containerTo) { + return this.moveItemStackFromSlot(slot, containerTo, true); + } + + @Nonnull + public MoveTransaction moveItemStackFromSlot(short slot, @Nonnull ItemContainer containerTo, boolean filter) { + return this.moveItemStackFromSlot(slot, containerTo, false, filter); + } + + @Nonnull + public MoveTransaction moveItemStackFromSlot(short slot, @Nonnull ItemContainer containerTo, boolean allOrNothing, boolean filter) { + MoveTransaction transaction = this.internal_moveItemStackFromSlot(slot, containerTo, allOrNothing, filter); + this.sendUpdate(transaction); + containerTo.sendUpdate(transaction.toInverted(this)); + return transaction; + } + + protected MoveTransaction internal_moveItemStackFromSlot( + short slot, @Nonnull ItemContainer containerTo, boolean allOrNothing, boolean filter + ) { + validateSlotIndex(slot, this.getCapacity()); + return this.writeAction(() -> containerTo.writeAction(() -> { + if (filter && this.cantRemoveFromSlot(slot)) { + return null; + } else { + ItemStack itemFrom = this.internal_removeSlot(slot); + if (ItemStack.isEmpty(itemFrom)) { + SlotTransaction slotTransaction = new SlotTransaction(false, ActionType.REMOVE, slot, null, null, null, false, false, filter); + return new MoveTransaction<>(false, slotTransaction, MoveType.MOVE_FROM_SELF, containerTo, ItemStackTransaction.FAILED_ADD); + } else { + SlotTransaction fromTransaction = new SlotTransaction(true, ActionType.REMOVE, slot, itemFrom, null, null, false, false, filter); + ItemStackTransaction addTransaction = InternalContainerUtilItemStack.internal_addItemStack(containerTo, itemFrom, allOrNothing, false, filter); + ItemStack remainder = addTransaction.getRemainder(); + if (!ItemStack.isEmpty(remainder)) { + InternalContainerUtilItemStack.internal_addItemStackToSlot(this, slot, remainder, allOrNothing, false); + } + + return new MoveTransaction<>(addTransaction.succeeded(), fromTransaction, MoveType.MOVE_FROM_SELF, containerTo, addTransaction); + } + } + })); + } + + @Nonnull + public MoveTransaction moveItemStackFromSlot(short slot, int quantity, @Nonnull ItemContainer containerTo) { + return this.moveItemStackFromSlot(slot, quantity, containerTo, false, true); + } + + @Nonnull + public MoveTransaction moveItemStackFromSlot( + short slot, int quantity, @Nonnull ItemContainer containerTo, boolean allOrNothing, boolean filter + ) { + MoveTransaction transaction = this.internal_moveItemStackFromSlot(slot, quantity, containerTo, allOrNothing, filter); + this.sendUpdate(transaction); + containerTo.sendUpdate(transaction.toInverted(this)); + return transaction; + } + + protected MoveTransaction internal_moveItemStackFromSlot( + short slot, int quantity, @Nonnull ItemContainer containerTo, boolean allOrNothing, boolean filter + ) { + validateSlotIndex(slot, this.getCapacity()); + validateQuantity(quantity); + return this.writeAction( + () -> containerTo.writeAction( + () -> { + if (filter && this.cantRemoveFromSlot(slot)) { + return null; + } else if (filter && containerTo.cantMoveToSlot(this, slot)) { + ItemStack itemStack = this.internal_getSlot(slot); + SlotTransaction slotTransaction = new SlotTransaction(false, ActionType.REMOVE, slot, itemStack, itemStack, null, false, false, filter); + return new MoveTransaction<>(false, slotTransaction, MoveType.MOVE_FROM_SELF, containerTo, ItemStackTransaction.FAILED_ADD); + } else { + ItemStackSlotTransaction fromTransaction = this.internal_removeItemStack(slot, quantity); + if (!fromTransaction.succeeded()) { + SlotTransaction slotTransaction = new SlotTransaction(false, ActionType.REMOVE, slot, null, null, null, false, false, filter); + return new MoveTransaction<>(false, slotTransaction, MoveType.MOVE_FROM_SELF, containerTo, ItemStackTransaction.FAILED_ADD); + } else { + ItemStack itemFrom = fromTransaction.getOutput(); + if (ItemStack.isEmpty(itemFrom)) { + SlotTransaction slotTransaction = new SlotTransaction(false, ActionType.REMOVE, slot, null, null, null, false, false, filter); + return new MoveTransaction<>(false, slotTransaction, MoveType.MOVE_FROM_SELF, containerTo, ItemStackTransaction.FAILED_ADD); + } else { + ItemStackTransaction addTransaction = InternalContainerUtilItemStack.internal_addItemStack( + containerTo, itemFrom, allOrNothing, false, filter + ); + ItemStack remainder = addTransaction.getRemainder(); + if (!ItemStack.isEmpty(remainder)) { + InternalContainerUtilItemStack.internal_addItemStackToSlot(this, slot, remainder, allOrNothing, false); + } + + return new MoveTransaction<>(addTransaction.succeeded(), fromTransaction, MoveType.MOVE_FROM_SELF, containerTo, addTransaction); + } + } + } + } + ) + ); + } + + @Nonnull + public ListTransaction> moveItemStackFromSlot(short slot, ItemContainer... containerTo) { + return this.moveItemStackFromSlot(slot, false, true, containerTo); + } + + @Nonnull + public ListTransaction> moveItemStackFromSlot( + short slot, boolean allOrNothing, boolean filter, @Nonnull ItemContainer... containerTo + ) { + ListTransaction> transaction = this.internal_moveItemStackFromSlot(slot, allOrNothing, filter, containerTo); + this.sendUpdate(transaction); + + for (MoveTransaction moveItemStackTransaction : transaction.getList()) { + moveItemStackTransaction.getOtherContainer().sendUpdate(moveItemStackTransaction.toInverted(this)); + } + + return transaction; + } + + @Nonnull + private ListTransaction> internal_moveItemStackFromSlot( + short slot, boolean allOrNothing, boolean filter, @Nonnull ItemContainer[] containerTo + ) { + List> transactions = new ObjectArrayList<>(); + + for (ItemContainer itemContainer : containerTo) { + MoveTransaction transaction = this.internal_moveItemStackFromSlot(slot, itemContainer, allOrNothing, filter); + transactions.add(transaction); + if (transaction.succeeded()) { + ItemStackTransaction addTransaction = transaction.getAddTransaction(); + if (ItemStack.isEmpty(addTransaction.getRemainder())) { + break; + } + } + } + + return new ListTransaction<>(!transactions.isEmpty(), transactions); + } + + @Nonnull + public ListTransaction> moveItemStackFromSlot(short slot, int quantity, ItemContainer... containerTo) { + return this.moveItemStackFromSlot(slot, quantity, false, true, containerTo); + } + + @Nonnull + public ListTransaction> moveItemStackFromSlot( + short slot, int quantity, boolean allOrNothing, boolean filter, @Nonnull ItemContainer... containerTo + ) { + ListTransaction> transaction = this.internal_moveItemStackFromSlot( + slot, quantity, allOrNothing, filter, containerTo + ); + this.sendUpdate(transaction); + + for (MoveTransaction moveItemStackTransaction : transaction.getList()) { + moveItemStackTransaction.getOtherContainer().sendUpdate(moveItemStackTransaction.toInverted(this)); + } + + return transaction; + } + + @Nonnull + private ListTransaction> internal_moveItemStackFromSlot( + short slot, int quantity, boolean allOrNothing, boolean filter, @Nonnull ItemContainer[] containerTo + ) { + List> transactions = new ObjectArrayList<>(); + + for (ItemContainer itemContainer : containerTo) { + MoveTransaction transaction = this.internal_moveItemStackFromSlot(slot, quantity, itemContainer, allOrNothing, filter); + transactions.add(transaction); + if (transaction.succeeded()) { + ItemStackTransaction addTransaction = transaction.getAddTransaction(); + if (ItemStack.isEmpty(addTransaction.getRemainder())) { + break; + } + } + } + + return new ListTransaction<>(!transactions.isEmpty(), transactions); + } + + @Nonnull + public MoveTransaction moveItemStackFromSlotToSlot(short slot, int quantity, @Nonnull ItemContainer containerTo, short slotTo) { + return this.moveItemStackFromSlotToSlot(slot, quantity, containerTo, slotTo, true); + } + + @Nonnull + public MoveTransaction moveItemStackFromSlotToSlot( + short slot, int quantity, @Nonnull ItemContainer containerTo, short slotTo, boolean filter + ) { + MoveTransaction transaction = this.internal_moveItemStackFromSlot(slot, quantity, containerTo, slotTo, filter); + this.sendUpdate(transaction); + containerTo.sendUpdate(transaction.toInverted(this)); + return transaction; + } + + protected MoveTransaction internal_moveItemStackFromSlot( + short slot, int quantity, @Nonnull ItemContainer containerTo, short slotTo, boolean filter + ) { + validateSlotIndex(slot, this.getCapacity()); + validateSlotIndex(slotTo, containerTo.getCapacity()); + validateQuantity(quantity); + return this.writeAction( + () -> containerTo.writeAction( + () -> { + if (filter && this.cantRemoveFromSlot(slot)) { + ItemStack itemStack = this.internal_getSlot(slot); + SlotTransaction slotTransaction = new SlotTransaction(false, ActionType.REMOVE, slot, itemStack, itemStack, null, false, false, filter); + return new MoveTransaction<>(false, slotTransaction, MoveType.MOVE_FROM_SELF, containerTo, SlotTransaction.FAILED_ADD); + } else if (filter && containerTo.cantMoveToSlot(this, slot)) { + ItemStack itemStack = this.internal_getSlot(slot); + SlotTransaction slotTransaction = new SlotTransaction(false, ActionType.REMOVE, slot, itemStack, itemStack, null, false, false, filter); + return new MoveTransaction<>(false, slotTransaction, MoveType.MOVE_FROM_SELF, containerTo, SlotTransaction.FAILED_ADD); + } else { + ItemStackSlotTransaction fromTransaction = this.internal_removeItemStack(slot, quantity); + if (!fromTransaction.succeeded()) { + return new MoveTransaction<>(false, fromTransaction, MoveType.MOVE_FROM_SELF, containerTo, SlotTransaction.FAILED_ADD); + } else { + ItemStack itemFrom = fromTransaction.getOutput(); + if (ItemStack.isEmpty(itemFrom)) { + return new MoveTransaction<>(true, fromTransaction, MoveType.MOVE_FROM_SELF, containerTo, SlotTransaction.FAILED_ADD); + } else { + ItemStack itemTo = containerTo.getItemStack(slotTo); + if (filter && containerTo.cantAddToSlot(slotTo, itemFrom, itemTo)) { + this.internal_setSlot(slot, fromTransaction.getSlotBefore()); + SlotTransaction slotTransaction = new SlotTransaction( + true, ActionType.REMOVE, slot, fromTransaction.getSlotBefore(), fromTransaction.getSlotAfter(), null, false, false, filter + ); + SlotTransaction addTransaction = new SlotTransaction(false, ActionType.ADD, slotTo, itemTo, itemTo, null, false, false, filter); + return new MoveTransaction<>(false, slotTransaction, MoveType.MOVE_FROM_SELF, containerTo, addTransaction); + } else if (ItemStack.isEmpty(itemTo)) { + ItemStackSlotTransaction addTransaction = InternalContainerUtilItemStack.internal_setItemStackForSlot( + containerTo, slotTo, itemFrom, filter + ); + return new MoveTransaction<>(true, fromTransaction, MoveType.MOVE_FROM_SELF, containerTo, addTransaction); + } else if (!itemFrom.isStackableWith(itemTo)) { + if (ItemStack.isEmpty(fromTransaction.getSlotAfter())) { + if (filter && this.cantAddToSlot(slot, itemTo, itemFrom)) { + this.internal_setSlot(slot, fromTransaction.getSlotBefore()); + SlotTransaction slotTransaction = new SlotTransaction( + true, ActionType.REMOVE, slot, fromTransaction.getSlotBefore(), fromTransaction.getSlotAfter(), null, false, false, filter + ); + SlotTransaction addTransaction = new SlotTransaction(false, ActionType.ADD, slotTo, itemTo, itemTo, null, false, false, filter); + return new MoveTransaction<>(false, slotTransaction, MoveType.MOVE_FROM_SELF, containerTo, addTransaction); + } else { + this.internal_setSlot(slot, itemTo); + containerTo.internal_setSlot(slotTo, itemFrom); + SlotTransaction from = new SlotTransaction(true, ActionType.REPLACE, slot, itemFrom, itemTo, null, false, false, filter); + SlotTransaction to = new SlotTransaction(true, ActionType.REPLACE, slotTo, itemTo, itemFrom, null, false, false, filter); + return new MoveTransaction<>(true, from, MoveType.MOVE_FROM_SELF, containerTo, to); + } + } else { + this.internal_setSlot(slot, fromTransaction.getSlotBefore()); + SlotTransaction slotTransaction = new SlotTransaction( + true, ActionType.REMOVE, slot, fromTransaction.getSlotBefore(), fromTransaction.getSlotAfter(), null, false, false, filter + ); + SlotTransaction addTransaction = new SlotTransaction(false, ActionType.ADD, slotTo, itemTo, itemTo, null, false, false, filter); + return new MoveTransaction<>(false, slotTransaction, MoveType.MOVE_FROM_SELF, containerTo, addTransaction); + } + } else { + int maxStack = itemFrom.getItem().getMaxStack(); + int newQuantity = itemFrom.getQuantity() + itemTo.getQuantity(); + if (newQuantity <= maxStack) { + ItemStackSlotTransaction addTransaction = InternalContainerUtilItemStack.internal_setItemStackForSlot( + containerTo, slotTo, itemTo.withQuantity(newQuantity), filter + ); + return new MoveTransaction<>(true, fromTransaction, MoveType.MOVE_FROM_SELF, containerTo, addTransaction); + } else { + ItemStackSlotTransaction addTransaction = InternalContainerUtilItemStack.internal_setItemStackForSlot( + containerTo, slotTo, itemTo.withQuantity(maxStack), filter + ); + int remainder = newQuantity - maxStack; + int quantityLeft = !ItemStack.isEmpty(fromTransaction.getSlotAfter()) ? fromTransaction.getSlotAfter().getQuantity() : 0; + this.internal_setSlot(slot, itemFrom.withQuantity(remainder + quantityLeft)); + return new MoveTransaction<>(true, fromTransaction, MoveType.MOVE_FROM_SELF, containerTo, addTransaction); + } + } + } + } + } + } + ) + ); + } + + @Nonnull + public ListTransaction> moveAllItemStacksTo(ItemContainer... containerTo) { + return this.moveAllItemStacksTo(null, containerTo); + } + + @Nonnull + public ListTransaction> moveAllItemStacksTo(Predicate itemPredicate, ItemContainer... containerTo) { + ListTransaction> transaction = this.internal_moveAllItemStacksTo(itemPredicate, containerTo); + this.sendUpdate(transaction); + + for (MoveTransaction moveItemStackTransaction : transaction.getList()) { + moveItemStackTransaction.getOtherContainer().sendUpdate(moveItemStackTransaction.toInverted(this)); + } + + return transaction; + } + + @Nonnull + protected ListTransaction> internal_moveAllItemStacksTo( + @Nullable Predicate itemPredicate, ItemContainer[] containerTo + ) { + return this.writeAction(() -> { + List> transactions = new ObjectArrayList<>(); + + for (short i = 0; i < this.getCapacity(); i++) { + if (!this.cantRemoveFromSlot(i)) { + ItemStack checkedItem = this.internal_getSlot(i); + if (!ItemStack.isEmpty(checkedItem) && (itemPredicate == null || itemPredicate.test(checkedItem))) { + transactions.addAll(this.moveItemStackFromSlot(i, containerTo).getList()); + } + } + } + + return new ListTransaction<>(true, transactions); + }); + } + + @Nonnull + public ListTransaction> quickStackTo(@Nonnull ItemContainer... containerTo) { + return this.moveAllItemStacksTo(itemStack -> { + for (ItemContainer itemContainer : containerTo) { + if (itemContainer.containsItemStacksStackableWith(itemStack)) { + return true; + } + } + + return false; + }, containerTo); + } + + @Nonnull + public ListTransaction> combineItemStacksIntoSlot(@Nonnull ItemContainer containerTo, short slotTo) { + ListTransaction> transaction = this.internal_combineItemStacksIntoSlot(containerTo, slotTo); + this.sendUpdate(transaction); + + for (MoveTransaction moveSlotTransaction : transaction.getList()) { + moveSlotTransaction.getOtherContainer().sendUpdate(moveSlotTransaction.toInverted(this)); + } + + return transaction; + } + + @Nonnull + protected ListTransaction> internal_combineItemStacksIntoSlot(@Nonnull ItemContainer containerTo, short slotTo) { + validateSlotIndex(slotTo, containerTo.getCapacity()); + return this.writeAction(() -> { + ItemStack itemStack = containerTo.internal_getSlot(slotTo); + Item item = itemStack.getItem(); + int maxStack = item.getMaxStack(); + if (!ItemStack.isEmpty(itemStack) && itemStack.getQuantity() < maxStack) { + int count = 0; + int[] quantities = new int[this.getCapacity()]; + int[] indexes = new int[this.getCapacity()]; + + for (short i = 0; i < this.getCapacity(); i++) { + if (!this.cantRemoveFromSlot(i)) { + ItemStack itemFrom = this.internal_getSlot(i); + if (itemStack != itemFrom && !ItemStack.isEmpty(itemFrom) && itemFrom.isStackableWith(itemStack)) { + indexes[count] = i; + quantities[count] = itemFrom.getQuantity(); + count++; + } + } + } + + IntArrays.quickSort(quantities, indexes, 0, count); + int quantity = itemStack.getQuantity(); + List> list = new ObjectArrayList<>(); + + for (int ai = 0; ai < count && quantity < maxStack; ai++) { + short ix = (short)indexes[ai]; + ItemStack itemFrom = this.internal_getSlot(ix); + MoveTransaction transaction = this.internal_moveItemStackFromSlot(ix, itemFrom.getQuantity(), containerTo, slotTo, true); + list.add(transaction); + quantity = !ItemStack.isEmpty(transaction.getAddTransaction().getSlotAfter()) ? transaction.getAddTransaction().getSlotAfter().getQuantity() : 0; + } + + return new ListTransaction<>(true, list); + } else { + return new ListTransaction<>(false, Collections.emptyList()); + } + }); + } + + @Nonnull + public ListTransaction> swapItems(short srcPos, @Nonnull ItemContainer containerTo, short destPos, short length) { + ListTransaction> transaction = this.internal_swapItems(srcPos, containerTo, destPos, length); + this.sendUpdate(transaction); + + for (MoveTransaction moveItemStackTransaction : transaction.getList()) { + moveItemStackTransaction.getOtherContainer().sendUpdate(moveItemStackTransaction.toInverted(this)); + } + + return transaction; + } + + @Nonnull + protected ListTransaction> internal_swapItems(short srcPos, @Nonnull ItemContainer containerTo, short destPos, short length) { + if (srcPos < 0) { + throw new IndexOutOfBoundsException("srcPos < 0"); + } else if (srcPos + length > this.getCapacity()) { + throw new IndexOutOfBoundsException("srcPos + length > capacity"); + } else if (destPos < 0) { + throw new IndexOutOfBoundsException("destPos < 0"); + } else if (destPos + length > containerTo.getCapacity()) { + throw new IndexOutOfBoundsException("destPos + length > dest.capacity"); + } else { + return this.writeAction(() -> containerTo.writeAction(() -> { + List> list = new ObjectArrayList<>(length); + + for (short slot = 0; slot < length; slot++) { + list.add(this.internal_swapItems(containerTo, (short)(srcPos + slot), (short)(destPos + slot))); + } + + return new ListTransaction<>(true, list); + })); + } + } + + @Nonnull + protected MoveTransaction internal_swapItems(@Nonnull ItemContainer containerTo, short slotFrom, short slotTo) { + ItemStack itemFrom = this.internal_removeSlot(slotFrom); + ItemStack itemTo = containerTo.internal_removeSlot(slotTo); + if (itemTo != null && !itemTo.isEmpty()) { + this.internal_setSlot(slotFrom, itemTo); + } + + if (itemFrom != null && !itemFrom.isEmpty()) { + containerTo.internal_setSlot(slotTo, itemFrom); + } + + SlotTransaction from = new SlotTransaction(true, ActionType.REPLACE, slotFrom, itemFrom, itemTo, null, false, false, false); + SlotTransaction to = new SlotTransaction(true, ActionType.REPLACE, slotTo, itemTo, itemFrom, null, false, false, false); + return new MoveTransaction<>(true, from, MoveType.MOVE_FROM_SELF, containerTo, to); + } + + public boolean canAddItemStack(@Nonnull ItemStack itemStack) { + return this.canAddItemStack(itemStack, false, true); + } + + public boolean canAddItemStack(@Nonnull ItemStack itemStack, boolean fullStacks, boolean filter) { + Item item = itemStack.getItem(); + if (item == null) { + throw new IllegalArgumentException(itemStack.getItemId() + " is an invalid item!"); + } else { + int itemMaxStack = item.getMaxStack(); + return this.readAction(() -> { + int testQuantityRemaining = itemStack.getQuantity(); + if (!fullStacks) { + testQuantityRemaining = InternalContainerUtilItemStack.testAddToExistingItemStacks(this, itemStack, itemMaxStack, testQuantityRemaining, filter); + } + + testQuantityRemaining = InternalContainerUtilItemStack.testAddToEmptySlots(this, itemStack, itemMaxStack, testQuantityRemaining, filter); + return testQuantityRemaining <= 0; + }); + } + } + + @Nonnull + public ItemStackTransaction addItemStack(@Nonnull ItemStack itemStack) { + return this.addItemStack(itemStack, false, false, true); + } + + @Nonnull + public ItemStackTransaction addItemStack(@Nonnull ItemStack itemStack, boolean allOrNothing, boolean fullStacks, boolean filter) { + ItemStackTransaction transaction = InternalContainerUtilItemStack.internal_addItemStack(this, itemStack, allOrNothing, fullStacks, filter); + this.sendUpdate(transaction); + return transaction; + } + + public boolean canAddItemStacks(List itemStacks) { + return this.canAddItemStacks(itemStacks, false, true); + } + + public boolean canAddItemStacks(@Nullable List itemStacks, boolean fullStacks, boolean filter) { + if (itemStacks != null && !itemStacks.isEmpty()) { + List tempItemDataList = new ObjectArrayList<>(itemStacks.size()); + + for (ItemStack itemStack : itemStacks) { + Item item = itemStack.getItem(); + if (item == null) { + throw new IllegalArgumentException(itemStack.getItemId() + " is an invalid item!"); + } + + tempItemDataList.add(new ItemContainer.TempItemData(itemStack, item)); + } + + return this.readAction( + () -> { + for (ItemContainer.TempItemData tempItemData : tempItemDataList) { + int itemMaxStack = tempItemData.item().getMaxStack(); + ItemStack itemStackx = tempItemData.itemStack(); + int testQuantityRemaining = itemStackx.getQuantity(); + if (!fullStacks) { + testQuantityRemaining = InternalContainerUtilItemStack.testAddToExistingItemStacks( + this, itemStackx, itemMaxStack, testQuantityRemaining, filter + ); + } + + testQuantityRemaining = InternalContainerUtilItemStack.testAddToEmptySlots(this, itemStackx, itemMaxStack, testQuantityRemaining, filter); + if (testQuantityRemaining > 0) { + return false; + } + } + + return true; + } + ); + } else { + return true; + } + } + + public ListTransaction addItemStacks(List itemStacks) { + return this.addItemStacks(itemStacks, false, false, true); + } + + public ListTransaction addItemStacks(@Nullable List itemStacks, boolean allOrNothing, boolean fullStacks, boolean filter) { + if (itemStacks != null && !itemStacks.isEmpty()) { + ListTransaction transaction = InternalContainerUtilItemStack.internal_addItemStacks( + this, itemStacks, allOrNothing, fullStacks, filter + ); + this.sendUpdate(transaction); + return transaction; + } else { + return ListTransaction.getEmptyTransaction(true); + } + } + + public ListTransaction addItemStacksOrdered(List itemStacks) { + return this.addItemStacksOrdered(itemStacks, false, true); + } + + public ListTransaction addItemStacksOrdered(short offset, List itemStacks) { + return this.addItemStacksOrdered(offset, itemStacks, false, true); + } + + public ListTransaction addItemStacksOrdered(List itemStacks, boolean allOrNothing, boolean filter) { + return this.addItemStacksOrdered((short)0, itemStacks, allOrNothing, filter); + } + + public ListTransaction addItemStacksOrdered( + short offset, @Nullable List itemStacks, boolean allOrNothing, boolean filter + ) { + if (itemStacks != null && !itemStacks.isEmpty()) { + ListTransaction transaction = InternalContainerUtilItemStack.internal_addItemStacksOrdered( + this, offset, itemStacks, allOrNothing, filter + ); + this.sendUpdate(transaction); + return transaction; + } else { + return ListTransaction.getEmptyTransaction(true); + } + } + + public boolean canRemoveItemStack(ItemStack itemStack) { + return this.canRemoveItemStack(itemStack, true, true); + } + + public boolean canRemoveItemStack(@Nullable ItemStack itemStack, boolean exactAmount, boolean filter) { + return itemStack == null ? true : this.readAction(() -> { + int testQuantityRemaining = InternalContainerUtilItemStack.testRemoveItemStackFromItems(this, itemStack, itemStack.getQuantity(), filter); + return testQuantityRemaining > 0 ? false : !exactAmount || testQuantityRemaining >= 0; + }); + } + + @Nonnull + public ItemStackTransaction removeItemStack(@Nonnull ItemStack itemStack) { + return this.removeItemStack(itemStack, true, true); + } + + @Nonnull + public ItemStackTransaction removeItemStack(@Nonnull ItemStack itemStack, boolean allOrNothing, boolean filter) { + ItemStackTransaction transaction = InternalContainerUtilItemStack.internal_removeItemStack(this, itemStack, allOrNothing, filter); + this.sendUpdate(transaction); + return transaction; + } + + public boolean canRemoveItemStacks(List itemStacks) { + return this.canRemoveItemStacks(itemStacks, true, true); + } + + public boolean canRemoveItemStacks(@Nullable List itemStacks, boolean exactAmount, boolean filter) { + return itemStacks != null && !itemStacks.isEmpty() ? this.readAction(() -> { + for (ItemStack itemStack : itemStacks) { + int testQuantityRemaining = InternalContainerUtilItemStack.testRemoveItemStackFromItems(this, itemStack, itemStack.getQuantity(), filter); + if (testQuantityRemaining > 0) { + return false; + } + + if (exactAmount && testQuantityRemaining < 0) { + return false; + } + } + + return true; + }) : true; + } + + public ListTransaction removeItemStacks(List itemStacks) { + return this.removeItemStacks(itemStacks, true, true); + } + + public ListTransaction removeItemStacks(@Nullable List itemStacks, boolean allOrNothing, boolean filter) { + if (itemStacks != null && !itemStacks.isEmpty()) { + ListTransaction transaction = InternalContainerUtilItemStack.internal_removeItemStacks(this, itemStacks, allOrNothing, filter); + this.sendUpdate(transaction); + return transaction; + } else { + return ListTransaction.getEmptyTransaction(true); + } + } + + public boolean canRemoveTag(int tagIndex, int quantity) { + return this.canRemoveTag(tagIndex, quantity, true, true); + } + + public boolean canRemoveTag(int tagIndex, int quantity, boolean exactAmount, boolean filter) { + return this.readAction(() -> { + int testQuantityRemaining = InternalContainerUtilTag.testRemoveTagFromItems(this, tagIndex, quantity, filter); + return testQuantityRemaining > 0 ? false : !exactAmount || testQuantityRemaining >= 0; + }); + } + + @Nonnull + public TagTransaction removeTag(int tagIndex, int quantity) { + return this.removeTag(tagIndex, quantity, true, true, true); + } + + @Nonnull + public TagTransaction removeTag(int tagIndex, int quantity, boolean allOrNothing, boolean exactAmount, boolean filter) { + TagTransaction transaction = InternalContainerUtilTag.internal_removeTag(this, tagIndex, quantity, allOrNothing, exactAmount, filter); + this.sendUpdate(transaction); + return transaction; + } + + public boolean canRemoveResource(ResourceQuantity resource) { + return this.canRemoveResource(resource, true, true); + } + + public boolean canRemoveResource(@Nullable ResourceQuantity resource, boolean exactAmount, boolean filter) { + return resource == null ? true : this.readAction(() -> { + int testQuantityRemaining = InternalContainerUtilResource.testRemoveResourceFromItems(this, resource, resource.getQuantity(), filter); + return testQuantityRemaining > 0 ? false : !exactAmount || testQuantityRemaining >= 0; + }); + } + + @Nonnull + public ResourceTransaction removeResource(@Nonnull ResourceQuantity resource) { + return this.removeResource(resource, true, true, true); + } + + @Nonnull + public ResourceTransaction removeResource(@Nonnull ResourceQuantity resource, boolean allOrNothing, boolean exactAmount, boolean filter) { + ResourceTransaction transaction = InternalContainerUtilResource.internal_removeResource(this, resource, allOrNothing, exactAmount, filter); + this.sendUpdate(transaction); + return transaction; + } + + public boolean canRemoveResources(List resources) { + return this.canRemoveResources(resources, true, true); + } + + public boolean canRemoveResources(@Nullable List resources, boolean exactAmount, boolean filter) { + return resources != null && !resources.isEmpty() ? this.readAction(() -> { + for (ResourceQuantity resource : resources) { + int testQuantityRemaining = InternalContainerUtilResource.testRemoveResourceFromItems(this, resource, resource.getQuantity(), filter); + if (testQuantityRemaining > 0) { + return false; + } + + if (exactAmount && testQuantityRemaining < 0) { + return false; + } + } + + return true; + }) : true; + } + + public ListTransaction removeResources(List resources) { + return this.removeResources(resources, true, true, true); + } + + public ListTransaction removeResources( + @Nullable List resources, boolean allOrNothing, boolean exactAmount, boolean filter + ) { + if (resources != null && !resources.isEmpty()) { + ListTransaction transaction = InternalContainerUtilResource.internal_removeResources( + this, resources, allOrNothing, exactAmount, filter + ); + this.sendUpdate(transaction); + return transaction; + } else { + return ListTransaction.getEmptyTransaction(true); + } + } + + public boolean canRemoveMaterial(MaterialQuantity material) { + return this.canRemoveMaterial(material, true, true); + } + + public boolean canRemoveMaterial(@Nullable MaterialQuantity material, boolean exactAmount, boolean filter) { + return material == null ? true : this.readAction(() -> { + int testQuantityRemaining = InternalContainerUtilMaterial.testRemoveMaterialFromItems(this, material, material.getQuantity(), filter); + return testQuantityRemaining > 0 ? false : !exactAmount || testQuantityRemaining >= 0; + }); + } + + @Nonnull + public MaterialTransaction removeMaterial(@Nonnull MaterialQuantity material) { + return this.removeMaterial(material, true, true, true); + } + + @Nonnull + public MaterialTransaction removeMaterial(@Nonnull MaterialQuantity material, boolean allOrNothing, boolean exactAmount, boolean filter) { + MaterialTransaction transaction = InternalContainerUtilMaterial.internal_removeMaterial(this, material, allOrNothing, exactAmount, filter); + this.sendUpdate(transaction); + return transaction; + } + + public boolean canRemoveMaterials(List materials) { + return this.canRemoveMaterials(materials, true, true); + } + + public boolean canRemoveMaterials(@Nullable List materials, boolean exactAmount, boolean filter) { + return materials != null && !materials.isEmpty() ? this.readAction(() -> { + for (MaterialQuantity material : materials) { + int testQuantityRemaining = InternalContainerUtilMaterial.testRemoveMaterialFromItems(this, material, material.getQuantity(), filter); + if (testQuantityRemaining > 0) { + return false; + } + + if (exactAmount && testQuantityRemaining < 0) { + return false; + } + } + + return true; + }) : true; + } + + public List getSlotMaterialsToRemove(@Nullable List materials, boolean exactAmount, boolean filter) { + List slotMaterials = new ObjectArrayList<>(); + return materials != null && !materials.isEmpty() ? this.readAction(() -> { + for (MaterialQuantity material : materials) { + TestRemoveItemSlotResult testResult = InternalContainerUtilMaterial.getTestRemoveMaterialFromItems(this, material, material.getQuantity(), filter); + if (testResult.quantityRemaining > 0) { + slotMaterials.clear(); + return slotMaterials; + } + + if (exactAmount && testResult.quantityRemaining < 0) { + slotMaterials.clear(); + return slotMaterials; + } + + slotMaterials.add(testResult); + } + + return slotMaterials; + }) : slotMaterials; + } + + public ListTransaction removeMaterials(List materials) { + return this.removeMaterials(materials, true, true, true); + } + + public ListTransaction removeMaterials( + @Nullable List materials, boolean allOrNothing, boolean exactAmount, boolean filter + ) { + if (materials != null && !materials.isEmpty()) { + ListTransaction transaction = InternalContainerUtilMaterial.internal_removeMaterials( + this, materials, allOrNothing, exactAmount, filter + ); + this.sendUpdate(transaction); + return transaction; + } else { + return ListTransaction.getEmptyTransaction(true); + } + } + + public ListTransaction removeMaterialsOrdered(short offset, List materials) { + return this.removeMaterialsOrdered(offset, materials, true, true, true); + } + + public ListTransaction removeMaterialsOrdered( + List materials, boolean allOrNothing, boolean exactAmount, boolean filter + ) { + return this.removeMaterialsOrdered((short)0, materials, allOrNothing, exactAmount, filter); + } + + public ListTransaction removeMaterialsOrdered( + short offset, @Nullable List materials, boolean allOrNothing, boolean exactAmount, boolean filter + ) { + if (materials != null && !materials.isEmpty()) { + if (offset + materials.size() > this.getCapacity()) { + return ListTransaction.getEmptyTransaction(false); + } else { + ListTransaction transaction = InternalContainerUtilMaterial.internal_removeMaterialsOrdered( + this, offset, materials, allOrNothing, exactAmount, filter + ); + this.sendUpdate(transaction); + return transaction; + } + } else { + return ListTransaction.getEmptyTransaction(true); + } + } + + public boolean isEmpty() { + return this.readAction(() -> { + for (short i = 0; i < this.getCapacity(); i++) { + ItemStack itemStack = this.internal_getSlot(i); + if (itemStack != null && !itemStack.isEmpty()) { + return false; + } + } + + return true; + }); + } + + public int countItemStacks(@Nonnull Predicate itemPredicate) { + return this.readAction(() -> { + int count = 0; + + for (short i = 0; i < this.getCapacity(); i++) { + ItemStack itemStack = this.internal_getSlot(i); + if (!ItemStack.isEmpty(itemStack) && itemPredicate.test(itemStack)) { + count += itemStack.getQuantity(); + } + } + + return count; + }); + } + + public boolean containsItemStacksStackableWith(@Nonnull ItemStack itemStack) { + return this.readAction(() -> { + for (short i = 0; i < this.getCapacity(); i++) { + ItemStack checked = this.internal_getSlot(i); + if (!ItemStack.isEmpty(checked) && itemStack.isStackableWith(checked)) { + return true; + } + } + + return false; + }); + } + + public void forEach(@Nonnull ShortObjectConsumer action) { + for (short i = 0; i < this.getCapacity(); i++) { + ItemStack itemStack = this.getItemStack(i); + if (!ItemStack.isEmpty(itemStack)) { + action.accept(i, itemStack); + } + } + } + + public void forEachWithMeta(@Nonnull Short2ObjectConcurrentHashMap.ShortBiObjConsumer consumer, T meta) { + for (short i = 0; i < this.getCapacity(); i++) { + ItemStack itemStack = this.getItemStack(i); + if (!ItemStack.isEmpty(itemStack)) { + consumer.accept(i, itemStack, meta); + } + } + } + + @Nonnull + public List removeAllItemStacks() { + List items = new ObjectArrayList<>(); + ListTransaction transaction = this.writeAction(() -> { + List transactions = new ObjectArrayList<>(); + + for (short i = 0; i < this.getCapacity(); i++) { + if (!this.cantRemoveFromSlot(i)) { + ItemStack itemStack = this.internal_removeSlot(i); + if (!ItemStack.isEmpty(itemStack)) { + items.add(itemStack); + transactions.add(new SlotTransaction(true, ActionType.REMOVE, i, itemStack, null, itemStack, false, false, true)); + } + } + } + + return new ListTransaction<>(true, transactions); + }); + this.sendUpdate(transaction); + return items; + } + + @Nonnull + public List dropAllItemStacks() { + return this.dropAllItemStacks(true); + } + + @Nonnull + public List dropAllItemStacks(boolean filter) { + List items = new ObjectArrayList<>(); + ListTransaction transaction = this.writeAction(() -> { + List transactions = new ObjectArrayList<>(); + + for (short i = 0; i < this.getCapacity(); i++) { + if (!filter || !this.cantDropFromSlot(i)) { + ItemStack itemStack = this.internal_removeSlot(i); + if (!ItemStack.isEmpty(itemStack)) { + items.add(itemStack); + transactions.add(new SlotTransaction(true, ActionType.REMOVE, i, itemStack, null, itemStack, false, false, true)); + } + } + } + + return new ListTransaction<>(true, transactions); + }); + this.sendUpdate(transaction); + return items; + } + + @Nonnull + public ListTransaction sortItems(@Nonnull SortType sort) { + ListTransaction transaction = this.internal_sortItems(sort); + this.sendUpdate(transaction); + return transaction; + } + + protected ListTransaction internal_sortItems(@Nonnull SortType sort) { + return this.writeAction(() -> { + ItemStack[] stacks = new ItemStack[this.getCapacity()]; + int stackOffset = 0; + + for (short i = 0; i < stacks.length; i++) { + if (!this.cantRemoveFromSlot(i)) { + ItemStack slot = this.internal_getSlot(i); + if (slot != null) { + Item item = slot.getItem(); + int maxStack = item.getMaxStack(); + int slotQuantity = slot.getQuantity(); + if (maxStack > 1) { + for (int j = 0; j < stackOffset && slotQuantity > 0; j++) { + ItemStack stack = stacks[j]; + if (slot.isStackableWith(stack)) { + int stackQuantity = stack.getQuantity(); + if (stackQuantity < maxStack) { + int adjust = Math.min(slotQuantity, maxStack - stackQuantity); + slotQuantity -= adjust; + stacks[j] = stack.withQuantity(stackQuantity + adjust); + } + } + } + } + + if (slotQuantity > 0) { + stacks[stackOffset++] = slotQuantity != slot.getQuantity() ? slot.withQuantity(slotQuantity) : slot; + } + } + } + } + + Arrays.sort(stacks, sort.getComparator()); + List transactions = new ObjectArrayList<>(stacks.length); + stackOffset = 0; + + for (short ix = 0; ix < stacks.length; ix++) { + if (!this.cantRemoveFromSlot(ix)) { + ItemStack existing = this.internal_getSlot(ix); + ItemStack replacement = stacks[stackOffset]; + if (!this.cantAddToSlot(ix, replacement, existing)) { + stackOffset++; + if (existing != replacement) { + this.internal_setSlot(ix, replacement); + transactions.add(new SlotTransaction(true, ActionType.REMOVE, ix, existing, null, replacement, false, false, true)); + } + } + } + } + + for (int ixx = stackOffset; ixx < stacks.length; ixx++) { + if (stacks[ixx] != null) { + throw new IllegalStateException("Had leftover stacks that didn't get sorted!"); + } + } + + return new ListTransaction<>(true, transactions); + }); + } + + protected void sendUpdate(@Nonnull Transaction transaction) { + if (transaction.succeeded()) { + ItemContainer.ItemContainerChangeEvent event = new ItemContainer.ItemContainerChangeEvent(this, transaction); + this.externalChangeEventRegistry.dispatchFor(null).dispatch(event); + this.internalChangeEventRegistry.dispatchFor(null).dispatch(event); + } + } + + public boolean containsContainer(ItemContainer itemContainer) { + return itemContainer == this; + } + + public void doMigration(Function blockMigration) { + this.writeAction(_blockMigration -> { + for (short i = 0; i < this.getCapacity(); i++) { + ItemStack slot = this.internal_getSlot(i); + if (!ItemStack.isEmpty(slot)) { + String oldItemId = slot.getItemId(); + String newItemId = (String)_blockMigration.apply(slot.getItemId()); + if (!oldItemId.equals(newItemId)) { + this.internal_setSlot(i, new ItemStack(newItemId, slot.getQuantity(), slot.getMetadata())); + } + } + } + + return null; + }, blockMigration); + } + + @Nullable + public static ItemResourceType getMatchingResourceType(@Nonnull Item item, @Nonnull String resourceId) { + ItemResourceType[] resourceTypes = item.getResourceTypes(); + if (resourceTypes == null) { + return null; + } else { + for (ItemResourceType resourceType : resourceTypes) { + if (resourceId.equals(resourceType.id)) { + return resourceType; + } + } + + return null; + } + } + + public static void validateQuantity(int quantity) { + if (quantity < 0) { + throw new IllegalArgumentException("Quantity is less than zero! " + quantity + " < 0"); + } + } + + public static void validateSlotIndex(short slot, int capacity) { + if (slot < 0) { + throw new IllegalArgumentException("Slot is less than zero! " + slot + " < 0"); + } else if (slot >= capacity) { + throw new IllegalArgumentException("Slot is outside capacity! " + slot + " >= " + capacity); + } + } + + @Nonnull + public static T copy(@Nonnull ItemContainer from, @Nonnull T to, @Nullable List remainder) { + from.forEach((slot, itemStack) -> { + if (slot >= to.getCapacity()) { + if (remainder != null) { + remainder.add(itemStack); + } + } else if (!ItemStack.isEmpty(itemStack)) { + to.setItemStackForSlot(slot, itemStack); + } + }); + return to; + } + + public static T ensureContainerCapacity( + @Nullable T inputContainer, short capacity, @Nonnull Short2ObjectConcurrentHashMap.ShortFunction newContainerSupplier, List remainder + ) { + if (inputContainer == null) { + return newContainerSupplier.apply(capacity); + } else { + return inputContainer.getCapacity() == capacity ? inputContainer : copy(inputContainer, newContainerSupplier.apply(capacity), remainder); + } + } + + public static ItemContainer getNewContainer(short capacity, @Nonnull Short2ObjectConcurrentHashMap.ShortFunction supplier) { + return (ItemContainer)(capacity > 0 ? supplier.apply(capacity) : EmptyItemContainer.INSTANCE); + } + + public record ItemContainerChangeEvent(ItemContainer container, Transaction transaction) implements IEvent { + @Nonnull + @Override + public String toString() { + return "ItemContainerChangeEvent{container=" + this.container + ", transaction=" + this.transaction + "}"; + } + } + + public record TempItemData(ItemStack itemStack, Item item) { + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/ItemContainerUtil.java b/src/com/hypixel/hytale/server/core/inventory/container/ItemContainerUtil.java new file mode 100644 index 0000000..e84d74f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/ItemContainerUtil.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.server.core.inventory.container; + +import com.hypixel.hytale.protocol.ItemArmorSlot; +import com.hypixel.hytale.server.core.inventory.container.filter.ArmorSlotAddFilter; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterActionType; +import com.hypixel.hytale.server.core.inventory.container.filter.NoDuplicateFilter; +import com.hypixel.hytale.server.core.inventory.container.filter.SlotFilter; + +public class ItemContainerUtil { + public ItemContainerUtil() { + } + + public static T trySetArmorFilters(T container) { + if (container instanceof SimpleItemContainer itemContainer) { + ItemArmorSlot[] itemArmorSlots = ItemArmorSlot.VALUES; + + for (short i = 0; i < itemContainer.getCapacity(); i++) { + if (i < itemArmorSlots.length) { + if (i < 5) { + itemContainer.setSlotFilter(FilterActionType.ADD, i, new ArmorSlotAddFilter(itemArmorSlots[i])); + } else { + itemContainer.setSlotFilter(FilterActionType.ADD, i, new NoDuplicateFilter(itemContainer)); + } + } else { + itemContainer.setSlotFilter(FilterActionType.ADD, i, SlotFilter.DENY); + } + } + } + + return container; + } + + public static T trySetSlotFilters(T container, SlotFilter filter) { + if (container instanceof SimpleItemContainer itemContainer) { + for (short i = 0; i < itemContainer.getCapacity(); i++) { + itemContainer.setSlotFilter(FilterActionType.ADD, i, filter); + } + } + + return container; + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/ItemStackItemContainer.java b/src/com/hypixel/hytale/server/core/inventory/container/ItemStackItemContainer.java new file mode 100644 index 0000000..3076e32 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/ItemStackItemContainer.java @@ -0,0 +1,391 @@ +package com.hypixel.hytale.server.core.inventory.container; + +import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.EmptyExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemStackContainerConfig; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterActionType; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterType; +import com.hypixel.hytale.server.core.inventory.container.filter.SlotFilter; +import com.hypixel.hytale.server.core.inventory.container.filter.TagFilter; +import com.hypixel.hytale.server.core.inventory.transaction.ClearTransaction; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public class ItemStackItemContainer extends ItemContainer { + @Nonnull + public static KeyedCodec CONTAINER_CODEC = new KeyedCodec<>("Container", Codec.BSON_DOCUMENT); + @Nonnull + public static KeyedCodec CAPACITY_CODEC = new KeyedCodec<>("Capacity", Codec.SHORT); + @Nonnull + public static KeyedCodec ITEMS_CODEC = new KeyedCodec<>("Items", new ArrayCodec<>(ItemStack.CODEC, ItemStack[]::new)); + protected final ReadWriteLock lock = new ReentrantReadWriteLock(); + protected final ItemContainer parentContainer; + protected final short itemStackSlot; + protected final ItemStack originalItemStack; + protected final short capacity; + protected ItemStack[] items; + private final Map> slotFilters = new ConcurrentHashMap<>(); + @Nonnull + private FilterType globalFilter = FilterType.ALLOW_ALL; + + private ItemStackItemContainer(ItemContainer parentContainer, short itemStackSlot, ItemStack originalItemStack, short capacity, ItemStack[] items) { + this.parentContainer = parentContainer; + this.itemStackSlot = itemStackSlot; + this.originalItemStack = originalItemStack; + this.capacity = capacity; + this.items = items; + } + + public ItemContainer getParentContainer() { + return this.parentContainer; + } + + public short getItemStackSlot() { + return this.itemStackSlot; + } + + public ItemStack getOriginalItemStack() { + return this.originalItemStack; + } + + public boolean isItemStackValid() { + ItemStack itemStack = this.parentContainer.getItemStack(this.itemStackSlot); + return ItemStack.isEmpty(itemStack) ? false : ItemStack.isSameItemType(itemStack, this.originalItemStack); + } + + @Override + public short getCapacity() { + return this.capacity; + } + + @Override + public void setGlobalFilter(@Nonnull FilterType globalFilter) { + this.globalFilter = globalFilter; + } + + @Override + public void setSlotFilter(FilterActionType actionType, short slot, @Nullable SlotFilter filter) { + validateSlotIndex(slot, this.getCapacity()); + if (filter != null) { + this.slotFilters.computeIfAbsent(actionType, k -> new Int2ObjectConcurrentHashMap<>()).put(slot, filter); + } else { + this.slotFilters.computeIfPresent(actionType, (k, map) -> { + map.remove(slot); + return map.isEmpty() ? null : map; + }); + } + } + + @Override + public ItemContainer clone() { + throw new UnsupportedOperationException("Item stack containers don't support clone"); + } + + @Override + protected V readAction(@Nonnull Supplier action) { + this.lock.readLock().lock(); + + Object var2; + try { + var2 = action.get(); + } finally { + this.lock.readLock().unlock(); + } + + return (V)var2; + } + + @Override + protected V readAction(@Nonnull Function action, X x) { + this.lock.readLock().lock(); + + Object var3; + try { + var3 = action.apply(x); + } finally { + this.lock.readLock().unlock(); + } + + return (V)var3; + } + + @Override + protected V writeAction(@Nonnull Supplier action) { + this.lock.writeLock().lock(); + + Object var2; + try { + var2 = action.get(); + } finally { + this.lock.writeLock().unlock(); + } + + return (V)var2; + } + + @Override + protected V writeAction(@Nonnull Function action, X x) { + this.lock.writeLock().lock(); + + Object var3; + try { + var3 = action.apply(x); + } finally { + this.lock.writeLock().unlock(); + } + + return (V)var3; + } + + @Override + public boolean isEmpty() { + this.lock.readLock().lock(); + + try { + if (this.items == null) { + return true; + } else { + for (short i = 0; i < this.items.length; i++) { + if (!ItemStack.isEmpty(this.items[i])) { + return false; + } + } + + return false; + } + } finally { + this.lock.readLock().unlock(); + } + } + + @Nonnull + @Override + protected ClearTransaction internal_clear() { + if (this.items == null) { + return new ClearTransaction(true, (short)0, ItemStack.EMPTY_ARRAY); + } else { + ItemStack[] oldItems = this.items; + this.items = new ItemStack[oldItems.length]; + writeToItemStack(this.parentContainer, this.itemStackSlot, this.originalItemStack, this.items); + return new ClearTransaction(true, (short)0, oldItems); + } + } + + @Nullable + @Override + protected ItemStack internal_getSlot(short slot) { + return this.items != null ? this.items[slot] : null; + } + + @Nullable + @Override + protected ItemStack internal_setSlot(short slot, ItemStack itemStack) { + if (this.items == null) { + return null; + } else if (ItemStack.isEmpty(itemStack)) { + return this.internal_removeSlot(slot); + } else { + ItemStack old = this.items[slot]; + this.items[slot] = itemStack; + writeToItemStack(this.parentContainer, this.itemStackSlot, this.originalItemStack, this.items); + return old; + } + } + + @Nullable + @Override + protected ItemStack internal_removeSlot(short slot) { + if (this.items == null) { + return null; + } else { + ItemStack old = this.items[slot]; + this.items[slot] = null; + writeToItemStack(this.parentContainer, this.itemStackSlot, this.originalItemStack, this.items); + return old; + } + } + + @Override + protected boolean cantAddToSlot(short slot, ItemStack itemStack, ItemStack slotItemStack) { + return !this.globalFilter.allowInput() ? true : this.testFilter(FilterActionType.ADD, slot, itemStack); + } + + @Override + protected boolean cantRemoveFromSlot(short slot) { + return !this.globalFilter.allowOutput() ? true : this.testFilter(FilterActionType.REMOVE, slot, null); + } + + @Override + protected boolean cantDropFromSlot(short slot) { + return this.testFilter(FilterActionType.DROP, slot, null); + } + + @Override + protected boolean cantMoveToSlot(ItemContainer fromContainer, short slotFrom) { + return fromContainer == this.parentContainer && slotFrom == this.itemStackSlot; + } + + private boolean testFilter(FilterActionType actionType, short slot, ItemStack itemStack) { + Int2ObjectConcurrentHashMap map = this.slotFilters.get(actionType); + if (map == null) { + return false; + } else { + SlotFilter filter = map.get(slot); + return filter == null ? false : !filter.test(actionType, this, slot, itemStack); + } + } + + @Nullable + @Override + public ItemStack getItemStack(short slot) { + validateSlotIndex(slot, this.getCapacity()); + this.lock.readLock().lock(); + + ItemStack var2; + try { + var2 = this.internal_getSlot(slot); + } finally { + this.lock.readLock().unlock(); + } + + return var2; + } + + public static void writeToItemStack(@Nonnull ItemContainer itemContainer, short slot, ItemStack originalItemStack, ItemStack[] items) { + if (ItemStack.isEmpty(originalItemStack)) { + throw new IllegalStateException("Item stack container is empty"); + } else { + ItemStack itemStack = itemContainer.getItemStack(slot); + if (!ItemStack.isSameItemType(itemStack, originalItemStack)) { + throw new IllegalStateException("Item stack in parent container changed!"); + } else { + BsonDocument newMetadata = itemStack.getMetadata(); + BsonDocument containerDocument = CONTAINER_CODEC.getOrNull(newMetadata, EmptyExtraInfo.EMPTY); + if (containerDocument == null) { + throw new IllegalStateException("Item stack container is empty!"); + } else { + ITEMS_CODEC.put(containerDocument, items, EmptyExtraInfo.EMPTY); + itemContainer.setItemStackForSlot(slot, itemStack.withMetadata(newMetadata)); + } + } + } + } + + @Nullable + public static ItemStackItemContainer getContainer(@Nonnull ItemContainer itemContainer, short slot) { + ItemStack itemStack = itemContainer.getItemStack(slot); + if (ItemStack.isEmpty(itemStack)) { + return null; + } else { + BsonDocument containerDocument = itemStack.getFromMetadataOrNull(CONTAINER_CODEC); + if (containerDocument == null) { + return null; + } else { + Short capacity = CAPACITY_CODEC.getOrNull(containerDocument, EmptyExtraInfo.EMPTY); + if (capacity != null && capacity > 0) { + ItemStack[] items = ITEMS_CODEC.getOrNull(containerDocument, EmptyExtraInfo.EMPTY); + if (items == null) { + items = new ItemStack[capacity]; + } + + return new ItemStackItemContainer(itemContainer, slot, itemStack, capacity, items); + } else { + return null; + } + } + } + } + + @Nonnull + public static ItemStackItemContainer makeContainerWithCapacity(@Nonnull ItemContainer itemContainer, short slot, short capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Capacity must be > 0"); + } else { + ItemStack itemStack = itemContainer.getItemStack(slot); + if (ItemStack.isEmpty(itemStack)) { + throw new IllegalArgumentException("Item stack is empty!"); + } else { + ItemStackItemContainer itemStackItemContainer = getContainer(itemContainer, slot); + if (itemStackItemContainer != null && itemStackItemContainer.getCapacity() != 0) { + throw new IllegalStateException("Item stack already has a container!"); + } else { + BsonDocument newMetadata = itemStack.getMetadata(); + if (newMetadata == null) { + newMetadata = new BsonDocument(); + } + + BsonDocument containerDocument = CONTAINER_CODEC.getOrNull(newMetadata, EmptyExtraInfo.EMPTY); + if (containerDocument == null) { + containerDocument = new BsonDocument(); + CONTAINER_CODEC.put(newMetadata, containerDocument, EmptyExtraInfo.EMPTY); + } + + CAPACITY_CODEC.put(containerDocument, capacity, EmptyExtraInfo.EMPTY); + itemContainer.setItemStackForSlot(slot, itemStack.withMetadata(newMetadata)); + return new ItemStackItemContainer(itemContainer, slot, itemStack, capacity, new ItemStack[capacity]); + } + } + } + } + + @Nullable + public static ItemStackItemContainer ensureContainer(@Nonnull ItemContainer itemContainer, short slot, short capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Capacity must be > 0"); + } else { + ItemStack itemStack = itemContainer.getItemStack(slot); + if (ItemStack.isEmpty(itemStack)) { + return null; + } else { + ItemStackItemContainer itemStackItemContainer = getContainer(itemContainer, slot); + if (itemStackItemContainer != null && itemStackItemContainer.getCapacity() != 0) { + return itemStackItemContainer; + } else { + BsonDocument newMetadata = itemStack.getMetadata(); + if (newMetadata == null) { + newMetadata = new BsonDocument(); + } + + BsonDocument containerDocument = CONTAINER_CODEC.getOrNull(newMetadata, EmptyExtraInfo.EMPTY); + if (containerDocument == null) { + containerDocument = new BsonDocument(); + CONTAINER_CODEC.put(newMetadata, containerDocument, EmptyExtraInfo.EMPTY); + } + + CAPACITY_CODEC.put(containerDocument, capacity, EmptyExtraInfo.EMPTY); + itemContainer.setItemStackForSlot(slot, itemStack.withMetadata(newMetadata)); + return new ItemStackItemContainer(itemContainer, slot, itemStack, capacity, new ItemStack[capacity]); + } + } + } + } + + @Nullable + public static ItemStackItemContainer ensureConfiguredContainer(@Nonnull ItemContainer itemContainer, short slot, @Nonnull ItemStackContainerConfig config) { + ItemStackItemContainer itemStackItemContainer = ensureContainer(itemContainer, slot, config.getCapacity()); + if (itemStackItemContainer == null) { + return null; + } else { + itemStackItemContainer.setGlobalFilter(config.getGlobalFilter()); + int tagIndex = config.getTagIndex(); + if (tagIndex != Integer.MIN_VALUE) { + for (short i = 0; i < itemStackItemContainer.getCapacity(); i++) { + itemStackItemContainer.setSlotFilter(FilterActionType.ADD, i, new TagFilter(tagIndex)); + } + } + + return itemStackItemContainer; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/SimpleItemContainer.java b/src/com/hypixel/hytale/server/core/inventory/container/SimpleItemContainer.java new file mode 100644 index 0000000..2577d70 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/SimpleItemContainer.java @@ -0,0 +1,358 @@ +package com.hypixel.hytale.server.core.inventory.container; + +import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.Short2ObjectMapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.ItemUtils; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterActionType; +import com.hypixel.hytale.server.core.inventory.container.filter.FilterType; +import com.hypixel.hytale.server.core.inventory.container.filter.SlotFilter; +import com.hypixel.hytale.server.core.inventory.transaction.ClearTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SimpleItemContainer extends ItemContainer { + public static final BuilderCodec CODEC = BuilderCodec.builder(SimpleItemContainer.class, SimpleItemContainer::new) + .append(new KeyedCodec<>("Capacity", Codec.SHORT), (o, i) -> o.capacity = i, o -> o.capacity) + .addValidator(Validators.greaterThanOrEqual((short)0)) + .add() + .append(new KeyedCodec<>("Items", new Short2ObjectMapCodec<>(ItemStack.CODEC, Short2ObjectOpenHashMap::new, false)), (o, i) -> o.items = i, o -> o.items) + .add() + .afterDecode(i -> { + if (i.items == null) { + i.items = new Short2ObjectOpenHashMap<>(i.capacity); + } + + i.items.short2ObjectEntrySet().removeIf(e -> e.getShortKey() < 0 || e.getShortKey() >= i.capacity || ItemStack.isEmpty(e.getValue())); + }) + .build(); + protected short capacity; + protected final ReadWriteLock lock = new ReentrantReadWriteLock(); + protected Short2ObjectMap items; + private final Map> slotFilters = new ConcurrentHashMap<>(); + private FilterType globalFilter = FilterType.ALLOW_ALL; + + protected SimpleItemContainer() { + } + + public SimpleItemContainer(short capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Capacity is less than or equal zero! " + capacity + " <= 0"); + } else { + this.capacity = capacity; + this.items = new Short2ObjectOpenHashMap<>(capacity); + } + } + + public SimpleItemContainer(@Nonnull SimpleItemContainer other) { + this.capacity = other.capacity; + other.lock.readLock().lock(); + + try { + this.items = new Short2ObjectOpenHashMap<>(other.items); + } finally { + other.lock.readLock().unlock(); + } + + this.slotFilters.putAll(other.slotFilters); + this.globalFilter = other.globalFilter; + } + + @Override + protected V readAction(@Nonnull Supplier action) { + this.lock.readLock().lock(); + + Object var2; + try { + var2 = action.get(); + } finally { + this.lock.readLock().unlock(); + } + + return (V)var2; + } + + @Override + protected V readAction(@Nonnull Function action, X x) { + this.lock.readLock().lock(); + + Object var3; + try { + var3 = action.apply(x); + } finally { + this.lock.readLock().unlock(); + } + + return (V)var3; + } + + @Override + protected V writeAction(@Nonnull Supplier action) { + this.lock.writeLock().lock(); + + Object var2; + try { + var2 = action.get(); + } finally { + this.lock.writeLock().unlock(); + } + + return (V)var2; + } + + @Override + protected V writeAction(@Nonnull Function action, X x) { + this.lock.writeLock().lock(); + + Object var3; + try { + var3 = action.apply(x); + } finally { + this.lock.writeLock().unlock(); + } + + return (V)var3; + } + + @Override + protected ItemStack internal_getSlot(short slot) { + return this.items.get(slot); + } + + @Override + protected ItemStack internal_setSlot(short slot, ItemStack itemStack) { + return ItemStack.isEmpty(itemStack) ? this.internal_removeSlot(slot) : this.items.put(slot, itemStack); + } + + @Override + protected ItemStack internal_removeSlot(short slot) { + return this.items.remove(slot); + } + + @Override + protected boolean cantAddToSlot(short slot, ItemStack itemStack, ItemStack slotItemStack) { + return !this.globalFilter.allowInput() ? true : this.testFilter(FilterActionType.ADD, slot, itemStack); + } + + @Override + protected boolean cantRemoveFromSlot(short slot) { + return !this.globalFilter.allowOutput() ? true : this.testFilter(FilterActionType.REMOVE, slot, null); + } + + @Override + protected boolean cantDropFromSlot(short slot) { + return this.testFilter(FilterActionType.DROP, slot, null); + } + + @Override + protected boolean cantMoveToSlot(ItemContainer fromContainer, short slotFrom) { + return false; + } + + private boolean testFilter(FilterActionType actionType, short slot, ItemStack itemStack) { + Int2ObjectConcurrentHashMap map = this.slotFilters.get(actionType); + if (map == null) { + return false; + } else { + SlotFilter filter = map.get(slot); + return filter == null ? false : !filter.test(actionType, this, slot, itemStack); + } + } + + @Override + public short getCapacity() { + return this.capacity; + } + + @Nonnull + @Override + protected ClearTransaction internal_clear() { + ItemStack[] itemStacks = new ItemStack[this.getCapacity()]; + + for (short i = 0; i < itemStacks.length; i++) { + itemStacks[i] = this.items.get(i); + } + + this.items.clear(); + return new ClearTransaction(true, (short)0, itemStacks); + } + + @Nonnull + public SimpleItemContainer clone() { + return new SimpleItemContainer(this); + } + + @Override + public boolean isEmpty() { + this.lock.readLock().lock(); + + try { + if (this.items.isEmpty()) { + return true; + } + } finally { + this.lock.readLock().unlock(); + } + + return super.isEmpty(); + } + + @Override + public void setGlobalFilter(@Nonnull FilterType globalFilter) { + this.globalFilter = Objects.requireNonNull(globalFilter); + } + + @Override + public void setSlotFilter(FilterActionType actionType, short slot, @Nullable SlotFilter filter) { + validateSlotIndex(slot, this.getCapacity()); + if (filter != null) { + this.slotFilters.computeIfAbsent(actionType, k -> new Int2ObjectConcurrentHashMap<>()).put(slot, filter); + } else { + this.slotFilters.computeIfPresent(actionType, (k, map) -> { + map.remove(slot); + return map.isEmpty() ? null : map; + }); + } + } + + @Nullable + @Override + public ItemStack getItemStack(short slot) { + validateSlotIndex(slot, this.getCapacity()); + this.lock.readLock().lock(); + + ItemStack var2; + try { + var2 = this.internal_getSlot(slot); + } finally { + this.lock.readLock().unlock(); + } + + return var2; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o instanceof SimpleItemContainer that) { + if (this.capacity != that.capacity) { + return false; + } else { + this.lock.readLock().lock(); + + boolean var3; + try { + var3 = this.items.equals(that.items); + } finally { + this.lock.readLock().unlock(); + } + + return var3; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + this.lock.readLock().lock(); + + int result; + try { + result = this.items.hashCode(); + } finally { + this.lock.readLock().unlock(); + } + + return 31 * result + this.capacity; + } + + public static ItemContainer getNewContainer(short capacity) { + return ItemContainer.getNewContainer(capacity, SimpleItemContainer::new); + } + + public static boolean addOrDropItemStack( + @Nonnull ComponentAccessor store, @Nonnull Ref ref, @Nonnull ItemContainer itemContainer, @Nonnull ItemStack itemStack + ) { + ItemStackTransaction transaction = itemContainer.addItemStack(itemStack); + ItemStack remainder = transaction.getRemainder(); + if (!ItemStack.isEmpty(remainder)) { + ItemUtils.dropItem(ref, remainder, store); + return true; + } else { + return false; + } + } + + public static boolean addOrDropItemStack( + @Nonnull ComponentAccessor store, + @Nonnull Ref ref, + @Nonnull ItemContainer itemContainer, + short slot, + @Nonnull ItemStack itemStack + ) { + ItemStackSlotTransaction transaction = itemContainer.addItemStackToSlot(slot, itemStack); + ItemStack remainder = transaction.getRemainder(); + return !ItemStack.isEmpty(remainder) ? addOrDropItemStack(store, ref, itemContainer, itemStack) : false; + } + + public static boolean addOrDropItemStacks( + @Nonnull ComponentAccessor store, @Nonnull Ref ref, @Nonnull ItemContainer itemContainer, List itemStacks + ) { + ListTransaction transaction = itemContainer.addItemStacks(itemStacks); + boolean droppedItem = false; + + for (ItemStackTransaction stackTransaction : transaction.getList()) { + ItemStack remainder = stackTransaction.getRemainder(); + if (!ItemStack.isEmpty(remainder)) { + ItemUtils.dropItem(ref, remainder, store); + droppedItem = true; + } + } + + return droppedItem; + } + + public static boolean tryAddOrderedOrDropItemStacks( + @Nonnull ComponentAccessor store, @Nonnull Ref ref, @Nonnull ItemContainer itemContainer, List itemStacks + ) { + ListTransaction transaction = itemContainer.addItemStacksOrdered(itemStacks); + List remainderItemStacks = null; + + for (ItemStackSlotTransaction stackTransaction : transaction.getList()) { + ItemStack remainder = stackTransaction.getRemainder(); + if (!ItemStack.isEmpty(remainder)) { + if (remainderItemStacks == null) { + remainderItemStacks = new ObjectArrayList<>(); + } + + remainderItemStacks.add(remainder); + } + } + + return addOrDropItemStacks(store, ref, itemContainer, remainderItemStacks); + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/SlotReplacementFunction.java b/src/com/hypixel/hytale/server/core/inventory/container/SlotReplacementFunction.java new file mode 100644 index 0000000..b7807a3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/SlotReplacementFunction.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.server.core.inventory.container; + +import com.hypixel.hytale.server.core.inventory.ItemStack; + +public interface SlotReplacementFunction { + ItemStack replace(short var1, ItemStack var2); +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/SortType.java b/src/com/hypixel/hytale/server/core/inventory/container/SortType.java new file mode 100644 index 0000000..5c5b38f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/SortType.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.server.core.inventory.container; + +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemQuality; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import java.util.Comparator; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public enum SortType { + NAME(i -> i.getItem().getTranslationKey(), false, false), + TYPE(i -> SortType.Dummy.ItemType.getType(i.getItem()), false, true), + RARITY(i -> { + int qualityIndex = i.getItem().getQualityIndex(); + ItemQuality itemQuality = ItemQuality.getAssetMap().getAsset(qualityIndex); + int itemQualityValue = (itemQuality != null ? itemQuality : ItemQuality.DEFAULT_ITEM_QUALITY).getQualityValue(); + return itemQualityValue; + }, true, true); + + @Nonnull + public static SortType[] VALUES = values(); + @Nonnull + private final Comparator comparator; + + private > SortType(@Nonnull final Function key, final boolean inverted, final boolean thenName) { + Comparator comp = comparatorFor(key); + if (inverted) { + comp = comp.reversed(); + } + + if (thenName) { + comp = comp.thenComparing(comparatorFor(i -> i.getItem().getTranslationKey())); + } + + this.comparator = Comparator.nullsLast(comp); + } + + @Nonnull + public Comparator getComparator() { + return this.comparator; + } + + @Nonnull + public com.hypixel.hytale.protocol.SortType toPacket() { + return switch (this) { + case NAME -> com.hypixel.hytale.protocol.SortType.Name; + case TYPE -> com.hypixel.hytale.protocol.SortType.Type; + case RARITY -> com.hypixel.hytale.protocol.SortType.Rarity; + }; + } + + @Nonnull + public static SortType fromPacket(@Nonnull com.hypixel.hytale.protocol.SortType sortType_) { + return switch (sortType_) { + case Type -> TYPE; + case Rarity -> RARITY; + case Name -> NAME; + }; + } + + @Nonnull + private static > Comparator comparatorFor(@Nonnull Function key) { + return (a, b) -> { + U akey = key.apply(a); + U bkey = key.apply(b); + if (akey == bkey) { + return 0; + } else if (akey == null) { + return 1; + } else { + return bkey == null ? -1 : akey.compareTo(bkey); + } + }; + } + + static class Dummy { + Dummy() { + } + + static enum ItemType { + WEAPON, + ARMOR, + TOOL, + ITEM, + SPECIAL; + + private ItemType() { + } + + @Nonnull + private static SortType.Dummy.ItemType getType(@Nonnull Item item) { + if (item.getWeapon() != null) { + return WEAPON; + } else if (item.getArmor() != null) { + return ARMOR; + } else if (item.getTool() != null) { + return TOOL; + } else { + return item.getBuilderToolData() != null ? SPECIAL : ITEM; + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/TestRemoveItemSlotResult.java b/src/com/hypixel/hytale/server/core/inventory/container/TestRemoveItemSlotResult.java new file mode 100644 index 0000000..4d06630 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/TestRemoveItemSlotResult.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.server.core.inventory.container; + +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.Map; +import java.util.Set; + +public class TestRemoveItemSlotResult { + Map picked = new Object2IntOpenHashMap<>(); + int quantityRemaining; + + public TestRemoveItemSlotResult(int testQuantityRemaining) { + this.quantityRemaining = testQuantityRemaining; + } + + public boolean hasResult() { + return !this.picked.isEmpty(); + } + + public Set getPickedSlots() { + return this.picked.keySet(); + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/filter/ArmorSlotAddFilter.java b/src/com/hypixel/hytale/server/core/inventory/container/filter/ArmorSlotAddFilter.java new file mode 100644 index 0000000..73d0c29 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/filter/ArmorSlotAddFilter.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.server.core.inventory.container.filter; + +import com.hypixel.hytale.protocol.ItemArmorSlot; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import javax.annotation.Nullable; + +public class ArmorSlotAddFilter implements ItemSlotFilter { + private final ItemArmorSlot itemArmorSlot; + + public ArmorSlotAddFilter(ItemArmorSlot itemArmorSlot) { + this.itemArmorSlot = itemArmorSlot; + } + + @Override + public boolean test(@Nullable Item item) { + return item == null || item.getArmor() != null && item.getArmor().getArmorSlot() == this.itemArmorSlot; + } + + public ItemArmorSlot getItemArmorSlot() { + return this.itemArmorSlot; + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/filter/FilterActionType.java b/src/com/hypixel/hytale/server/core/inventory/container/filter/FilterActionType.java new file mode 100644 index 0000000..f9c0270 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/filter/FilterActionType.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.core.inventory.container.filter; + +public enum FilterActionType { + ADD, + REMOVE, + DROP; + + private FilterActionType() { + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/filter/FilterType.java b/src/com/hypixel/hytale/server/core/inventory/container/filter/FilterType.java new file mode 100644 index 0000000..d8fee2f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/filter/FilterType.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.inventory.container.filter; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.codecs.EnumCodec; + +public enum FilterType { + ALLOW_INPUT_ONLY(true, false), + ALLOW_OUTPUT_ONLY(false, true), + ALLOW_ALL(true, true), + DENY_ALL(false, false); + + public static final Codec CODEC = new EnumCodec<>(FilterType.class); + private final boolean input; + private final boolean output; + + private FilterType(boolean input, boolean output) { + this.input = input; + this.output = output; + } + + public boolean allowInput() { + return this.input; + } + + public boolean allowOutput() { + return this.output; + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/filter/ItemSlotFilter.java b/src/com/hypixel/hytale/server/core/inventory/container/filter/ItemSlotFilter.java new file mode 100644 index 0000000..872bd67 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/filter/ItemSlotFilter.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.server.core.inventory.container.filter; + +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface ItemSlotFilter extends SlotFilter { + @Override + default boolean test(@Nonnull FilterActionType actionType, @Nonnull ItemContainer container, short slot, @Nullable ItemStack itemStack) { + return switch (actionType) { + case ADD -> this.test(itemStack != null ? itemStack.getItem() : null); + case REMOVE, DROP -> { + itemStack = container.getItemStack(slot); + yield this.test(itemStack != null ? itemStack.getItem() : null); + } + }; + } + + boolean test(@Nullable Item var1); +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/filter/NoDuplicateFilter.java b/src/com/hypixel/hytale/server/core/inventory/container/filter/NoDuplicateFilter.java new file mode 100644 index 0000000..d6d2be8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/filter/NoDuplicateFilter.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.core.inventory.container.filter; + +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer; +import javax.annotation.Nullable; + +public class NoDuplicateFilter implements ItemSlotFilter { + private final SimpleItemContainer container; + + public NoDuplicateFilter(SimpleItemContainer container) { + this.container = container; + } + + @Override + public boolean test(@Nullable Item item) { + if (item != null && item.getId() != null) { + for (short i = 0; i < this.container.getCapacity(); i++) { + ItemStack itemStack = this.container.getItemStack(i); + if (itemStack != null && itemStack.getItemId().equals(item.getId())) { + return false; + } + } + + return true; + } else { + return false; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/filter/ResourceFilter.java b/src/com/hypixel/hytale/server/core/inventory/container/filter/ResourceFilter.java new file mode 100644 index 0000000..d3d4dda --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/filter/ResourceFilter.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.server.core.inventory.container.filter; + +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.inventory.ResourceQuantity; +import javax.annotation.Nullable; + +public class ResourceFilter implements ItemSlotFilter { + private final ResourceQuantity resource; + + public ResourceFilter(ResourceQuantity resource) { + this.resource = resource; + } + + @Override + public boolean test(@Nullable Item item) { + return item == null || this.resource.getResourceType(item) != null; + } + + public ResourceQuantity getResource() { + return this.resource; + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/filter/SlotFilter.java b/src/com/hypixel/hytale/server/core/inventory/container/filter/SlotFilter.java new file mode 100644 index 0000000..c6a77cb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/filter/SlotFilter.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.core.inventory.container.filter; + +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import javax.annotation.Nullable; + +public interface SlotFilter { + SlotFilter ALLOW = (actionType, container, slot, itemStack) -> true; + SlotFilter DENY = (actionType, container, slot, itemStack) -> false; + + boolean test(FilterActionType var1, ItemContainer var2, short var3, @Nullable ItemStack var4); +} diff --git a/src/com/hypixel/hytale/server/core/inventory/container/filter/TagFilter.java b/src/com/hypixel/hytale/server/core/inventory/container/filter/TagFilter.java new file mode 100644 index 0000000..ad104a9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/container/filter/TagFilter.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.server.core.inventory.container.filter; + +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import javax.annotation.Nullable; + +public class TagFilter implements ItemSlotFilter { + private final int tagIndex; + + public TagFilter(int tagIndex) { + this.tagIndex = tagIndex; + } + + @Override + public boolean test(@Nullable Item item) { + return item == null || item.getData().getExpandedTagIndexes().contains(this.tagIndex); + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/transaction/ActionType.java b/src/com/hypixel/hytale/server/core/inventory/transaction/ActionType.java new file mode 100644 index 0000000..2b7f592 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/transaction/ActionType.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.core.inventory.transaction; + +public enum ActionType { + SET(true, false, true), + ADD(true, false, false), + REMOVE(false, true, false), + REPLACE(true, true, false); + + private final boolean add; + private final boolean remove; + private final boolean destroy; + + private ActionType(boolean add, boolean remove, boolean destroy) { + this.add = add; + this.remove = remove; + this.destroy = destroy; + } + + public boolean isAdd() { + return this.add; + } + + public boolean isRemove() { + return this.remove; + } + + public boolean isDestroy() { + return this.destroy; + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/transaction/ClearTransaction.java b/src/com/hypixel/hytale/server/core/inventory/transaction/ClearTransaction.java new file mode 100644 index 0000000..603e6d7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/transaction/ClearTransaction.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.server.core.inventory.transaction; + +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ClearTransaction implements Transaction { + public static final ClearTransaction EMPTY = new ClearTransaction(true, (short)0, ItemStack.EMPTY_ARRAY); + private final boolean succeeded; + private final short start; + @Nonnull + private final ItemStack[] items; + + public ClearTransaction(boolean succeeded, short start, @Nonnull ItemStack[] items) { + this.succeeded = succeeded; + this.start = start; + this.items = items; + } + + @Override + public boolean succeeded() { + return this.succeeded; + } + + @Override + public boolean wasSlotModified(short slot) { + if (!this.succeeded) { + return false; + } else { + slot = (short)(slot - this.start); + return slot >= 0 && slot < this.items.length && this.items[slot] != null && !this.items[slot].isEmpty(); + } + } + + @Nonnull + public ItemStack[] getItems() { + return this.items; + } + + @Nonnull + public ClearTransaction toParent(ItemContainer parent, short start, ItemContainer container) { + short newStart = (short)(start + this.start); + return new ClearTransaction(this.succeeded, newStart, this.items); + } + + @Nullable + public ClearTransaction fromParent(ItemContainer parent, short start, @Nonnull ItemContainer container) { + short newStart = (short)(this.start - start); + short capacity = container.getCapacity(); + if (newStart < 0) { + int from = -newStart; + return this.items.length + newStart > capacity + ? new ClearTransaction(this.succeeded, (short)0, Arrays.copyOfRange(this.items, from, from + capacity)) + : new ClearTransaction(this.succeeded, (short)0, Arrays.copyOfRange(this.items, from, this.items.length)); + } else { + return this.items.length > capacity + ? new ClearTransaction(this.succeeded, newStart, Arrays.copyOf(this.items, capacity)) + : new ClearTransaction(this.succeeded, newStart, this.items); + } + } + + @Nonnull + @Override + public String toString() { + return "ClearTransaction{items=" + Arrays.toString((Object[])this.items) + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/transaction/ItemStackSlotTransaction.java b/src/com/hypixel/hytale/server/core/inventory/transaction/ItemStackSlotTransaction.java new file mode 100644 index 0000000..b67af85 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/transaction/ItemStackSlotTransaction.java @@ -0,0 +1,101 @@ +package com.hypixel.hytale.server.core.inventory.transaction; + +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemStackSlotTransaction extends SlotTransaction { + private final boolean addToExistingSlot; + @Nullable + private final ItemStack query; + @Nullable + private final ItemStack remainder; + + public ItemStackSlotTransaction( + boolean succeeded, + @Nonnull ActionType action, + short slot, + @Nullable ItemStack slotBefore, + @Nullable ItemStack slotAfter, + @Nullable ItemStack output, + boolean allOrNothing, + boolean exactAmount, + boolean filter, + boolean addToExistingSlot, + @Nullable ItemStack query, + @Nullable ItemStack remainder + ) { + super(succeeded, action, slot, slotBefore, slotAfter, output, allOrNothing, exactAmount, filter); + this.addToExistingSlot = addToExistingSlot; + this.query = query; + this.remainder = remainder; + } + + public boolean isAddToExistingSlot() { + return this.addToExistingSlot; + } + + @Nullable + public ItemStack getQuery() { + return this.query; + } + + @Nullable + public ItemStack getRemainder() { + return this.remainder; + } + + @Nonnull + public ItemStackSlotTransaction toParent(ItemContainer parent, short start, ItemContainer container) { + short newSlot = (short)(start + this.getSlot()); + return new ItemStackSlotTransaction( + this.succeeded(), + this.getAction(), + newSlot, + this.getSlotBefore(), + this.getSlotAfter(), + this.getOutput(), + this.isAllOrNothing(), + this.isExactAmount(), + this.isFilter(), + this.addToExistingSlot, + this.query, + this.remainder + ); + } + + @Nullable + public ItemStackSlotTransaction fromParent(ItemContainer parent, short start, @Nonnull ItemContainer container) { + short newSlot = (short)(this.getSlot() - start); + return newSlot >= 0 && newSlot < container.getCapacity() + ? new ItemStackSlotTransaction( + this.succeeded(), + this.getAction(), + newSlot, + this.getSlotBefore(), + this.getSlotAfter(), + this.getOutput(), + this.isAllOrNothing(), + this.isExactAmount(), + this.isFilter(), + this.addToExistingSlot, + this.query, + this.remainder + ) + : null; + } + + @Nonnull + @Override + public String toString() { + return "ItemStackSlotTransaction{addToExistingSlot=" + + this.addToExistingSlot + + ", query=" + + this.query + + ", remainder=" + + this.remainder + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/transaction/ItemStackTransaction.java b/src/com/hypixel/hytale/server/core/inventory/transaction/ItemStackTransaction.java new file mode 100644 index 0000000..9afc759 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/transaction/ItemStackTransaction.java @@ -0,0 +1,143 @@ +package com.hypixel.hytale.server.core.inventory.transaction; + +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemStackTransaction implements Transaction { + public static final ItemStackTransaction FAILED_ADD = new ItemStackTransaction(false, ActionType.ADD, null, null, false, false, Collections.emptyList()); + private final boolean succeeded; + @Nullable + private final ActionType action; + @Nullable + private final ItemStack query; + @Nullable + private final ItemStack remainder; + private final boolean allOrNothing; + private final boolean filter; + @Nonnull + private final List slotTransactions; + + public ItemStackTransaction( + boolean succeeded, + @Nullable ActionType action, + @Nullable ItemStack query, + @Nullable ItemStack remainder, + boolean allOrNothing, + boolean filter, + @Nonnull List slotTransactions + ) { + this.succeeded = succeeded; + this.action = action; + this.query = query; + this.remainder = remainder; + this.allOrNothing = allOrNothing; + this.filter = filter; + this.slotTransactions = slotTransactions; + } + + @Override + public boolean succeeded() { + return this.succeeded; + } + + @Override + public boolean wasSlotModified(short slot) { + if (!this.succeeded) { + return false; + } else { + for (ItemStackSlotTransaction t : this.slotTransactions) { + if (t.succeeded() && t.wasSlotModified(slot)) { + return true; + } + } + + return false; + } + } + + @Nullable + public ActionType getAction() { + return this.action; + } + + @Nullable + public ItemStack getQuery() { + return this.query; + } + + @Nullable + public ItemStack getRemainder() { + return this.remainder; + } + + public boolean isAllOrNothing() { + return this.allOrNothing; + } + + public boolean isFilter() { + return this.filter; + } + + @Nonnull + public List getSlotTransactions() { + return this.slotTransactions; + } + + @Nonnull + public ItemStackTransaction toParent(ItemContainer parent, short start, ItemContainer container) { + List slotTransactions = this.slotTransactions + .stream() + .map(transaction -> transaction.toParent(parent, start, container)) + .collect(Collectors.toList()); + return new ItemStackTransaction(this.succeeded, this.action, this.query, this.remainder, this.allOrNothing, this.filter, slotTransactions); + } + + @Nullable + public ItemStackTransaction fromParent(ItemContainer parent, short start, @Nonnull ItemContainer container) { + List slotTransactions = this.slotTransactions + .stream() + .map(transactionx -> transactionx.fromParent(parent, start, container)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (slotTransactions.isEmpty()) { + return null; + } else { + boolean succeeded = false; + + for (ItemStackSlotTransaction transaction : slotTransactions) { + if (transaction.succeeded()) { + succeeded = true; + break; + } + } + + return new ItemStackTransaction(succeeded, this.action, this.query, this.remainder, this.allOrNothing, this.filter, slotTransactions); + } + } + + @Nonnull + @Override + public String toString() { + return "ItemStackTransaction{succeeded=" + + this.succeeded + + ", action=" + + this.action + + ", query=" + + this.query + + ", remainder=" + + this.remainder + + ", allOrNothing=" + + this.allOrNothing + + ", filter=" + + this.filter + + ", slotTransactions=" + + this.slotTransactions + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/transaction/ListTransaction.java b/src/com/hypixel/hytale/server/core/inventory/transaction/ListTransaction.java new file mode 100644 index 0000000..768312a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/transaction/ListTransaction.java @@ -0,0 +1,95 @@ +package com.hypixel.hytale.server.core.inventory.transaction; + +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ListTransaction implements Transaction { + public static final ListTransaction EMPTY_SUCCESSFUL_TRANSACTION = new ListTransaction(true); + public static final ListTransaction EMPTY_FAILED_TRANSACTION = new ListTransaction(false); + private final boolean succeeded; + @Nonnull + private final List list; + + public static ListTransaction getEmptyTransaction(boolean succeeded) { + return (ListTransaction)(succeeded ? EMPTY_SUCCESSFUL_TRANSACTION : EMPTY_FAILED_TRANSACTION); + } + + private ListTransaction(boolean succeeded) { + this.succeeded = succeeded; + this.list = Collections.emptyList(); + } + + public ListTransaction(boolean succeeded, @Nonnull List list) { + this.succeeded = succeeded; + this.list = Collections.unmodifiableList(list); + } + + @Override + public boolean succeeded() { + return this.succeeded; + } + + @Override + public boolean wasSlotModified(short slot) { + if (!this.succeeded) { + return false; + } else { + for (T t : this.list) { + if (t.succeeded() && t.wasSlotModified(slot)) { + return true; + } + } + + return false; + } + } + + @Nonnull + public List getList() { + return this.list; + } + + public int size() { + return this.list.size(); + } + + @Nonnull + public ListTransaction toParent(ItemContainer parent, short start, ItemContainer container) { + List list = this.list.stream().map(transaction -> transaction.toParent(parent, start, container)).collect(Collectors.toList()); + return new ListTransaction<>(this.succeeded, list); + } + + @Nullable + public ListTransaction fromParent(ItemContainer parent, short start, ItemContainer container) { + List list = this.list + .stream() + .map(transactionx -> transactionx.fromParent(parent, start, container)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (list.isEmpty()) { + return null; + } else { + boolean succeeded = false; + + for (T transaction : list) { + if (transaction.succeeded()) { + succeeded = true; + break; + } + } + + return new ListTransaction<>(succeeded, list); + } + } + + @Nonnull + @Override + public String toString() { + return "ListTransaction{succeeded=" + this.succeeded + ", list=" + this.list + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/transaction/MaterialSlotTransaction.java b/src/com/hypixel/hytale/server/core/inventory/transaction/MaterialSlotTransaction.java new file mode 100644 index 0000000..647bc2a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/transaction/MaterialSlotTransaction.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.core.inventory.transaction; + +import com.hypixel.hytale.server.core.inventory.MaterialQuantity; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MaterialSlotTransaction extends SlotTransaction { + @Nonnull + private final MaterialQuantity query; + private final int remainder; + @Nonnull + private final SlotTransaction transaction; + + public MaterialSlotTransaction(@Nonnull MaterialQuantity query, int remainder, @Nonnull SlotTransaction transaction) { + super( + transaction.succeeded(), + transaction.getAction(), + transaction.getSlot(), + transaction.getSlotBefore(), + transaction.getSlotAfter(), + transaction.getOutput(), + transaction.isAllOrNothing(), + transaction.isExactAmount(), + transaction.isFilter() + ); + this.query = query; + this.remainder = remainder; + this.transaction = transaction; + } + + @Nonnull + public MaterialQuantity getQuery() { + return this.query; + } + + public int getRemainder() { + return this.remainder; + } + + @Nonnull + public SlotTransaction getTransaction() { + return this.transaction; + } + + @Nonnull + public MaterialSlotTransaction toParent(ItemContainer parent, short start, ItemContainer container) { + return new MaterialSlotTransaction(this.query, this.remainder, this.transaction.toParent(parent, start, container)); + } + + @Nullable + public MaterialSlotTransaction fromParent(ItemContainer parent, short start, @Nonnull ItemContainer container) { + SlotTransaction newTransaction = this.transaction.fromParent(parent, start, container); + return newTransaction == null ? null : new MaterialSlotTransaction(this.query, this.remainder, newTransaction); + } + + @Nonnull + @Override + public String toString() { + return "MaterialSlotTransaction{query=" + this.query + ", remainder=" + this.remainder + ", transaction=" + this.transaction + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/transaction/MaterialTransaction.java b/src/com/hypixel/hytale/server/core/inventory/transaction/MaterialTransaction.java new file mode 100644 index 0000000..a5ce790 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/transaction/MaterialTransaction.java @@ -0,0 +1,120 @@ +package com.hypixel.hytale.server.core.inventory.transaction; + +import com.hypixel.hytale.server.core.inventory.MaterialQuantity; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MaterialTransaction extends ListTransaction { + @Nonnull + private final ActionType action; + @Nonnull + private final MaterialQuantity material; + private final int remainder; + private final boolean allOrNothing; + private final boolean exactAmount; + private final boolean filter; + + public MaterialTransaction( + boolean succeeded, + @Nonnull ActionType action, + @Nonnull MaterialQuantity material, + int remainder, + boolean allOrNothing, + boolean exactAmount, + boolean filter, + @Nonnull List slotTransactions + ) { + super(succeeded, slotTransactions); + this.action = action; + this.material = material; + this.remainder = remainder; + this.allOrNothing = allOrNothing; + this.exactAmount = exactAmount; + this.filter = filter; + } + + @Nonnull + public ActionType getAction() { + return this.action; + } + + @Nonnull + public MaterialQuantity getMaterial() { + return this.material; + } + + public int getRemainder() { + return this.remainder; + } + + public boolean isAllOrNothing() { + return this.allOrNothing; + } + + public boolean isExactAmount() { + return this.exactAmount; + } + + public boolean isFilter() { + return this.filter; + } + + @Nonnull + public MaterialTransaction toParent(ItemContainer parent, short start, ItemContainer container) { + List slotTransactions = this.getList() + .stream() + .map(transaction -> transaction.toParent(parent, start, container)) + .collect(Collectors.toList()); + return new MaterialTransaction( + this.succeeded(), this.action, this.material, this.remainder, this.allOrNothing, this.exactAmount, this.filter, slotTransactions + ); + } + + @Nullable + public MaterialTransaction fromParent(ItemContainer parent, short start, @Nonnull ItemContainer container) { + List slotTransactions = this.getList() + .stream() + .map(transactionx -> transactionx.fromParent(parent, start, container)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (slotTransactions.isEmpty()) { + return null; + } else { + boolean succeeded = false; + + for (MaterialSlotTransaction transaction : slotTransactions) { + if (transaction.succeeded()) { + succeeded = true; + break; + } + } + + return new MaterialTransaction( + succeeded, this.action, this.material, this.remainder, this.allOrNothing, this.exactAmount, this.filter, slotTransactions + ); + } + } + + @Nonnull + @Override + public String toString() { + return "MaterialTransaction{action=" + + this.action + + ", material=" + + this.material + + ", remainder=" + + this.remainder + + ", allOrNothing=" + + this.allOrNothing + + ", exactAmount=" + + this.exactAmount + + ", filter=" + + this.filter + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/transaction/MoveTransaction.java b/src/com/hypixel/hytale/server/core/inventory/transaction/MoveTransaction.java new file mode 100644 index 0000000..5f000f8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/transaction/MoveTransaction.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.server.core.inventory.transaction; + +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MoveTransaction implements Transaction { + private final boolean succeeded; + @Nonnull + private final SlotTransaction removeTransaction; + @Nonnull + private final MoveType moveType; + @Nonnull + private final ItemContainer otherContainer; + private final T addTransaction; + + public MoveTransaction( + boolean succeeded, @Nonnull SlotTransaction removeTransaction, @Nonnull MoveType moveType, @Nonnull ItemContainer otherContainer, T addTransaction + ) { + this.succeeded = succeeded; + this.removeTransaction = removeTransaction; + this.moveType = moveType; + this.otherContainer = otherContainer; + this.addTransaction = addTransaction; + } + + @Override + public boolean succeeded() { + return this.succeeded; + } + + @Nonnull + public SlotTransaction getRemoveTransaction() { + return this.removeTransaction; + } + + @Nonnull + public MoveType getMoveType() { + return this.moveType; + } + + @Nonnull + public ItemContainer getOtherContainer() { + return this.otherContainer; + } + + public T getAddTransaction() { + return this.addTransaction; + } + + @Nonnull + public MoveTransaction toInverted(@Nonnull ItemContainer itemContainer) { + return new MoveTransaction<>(this.succeeded(), this.removeTransaction, MoveType.MOVE_TO_SELF, itemContainer, this.addTransaction); + } + + @Override + public boolean wasSlotModified(short slot) { + return !this.succeeded + ? false + : this.addTransaction.succeeded() && this.addTransaction.wasSlotModified(slot) + || this.removeTransaction.succeeded() && this.removeTransaction.wasSlotModified(slot); + } + + @Nonnull + public MoveTransaction toParent(ItemContainer parent, short start, ItemContainer container) { + MoveType moveType = this.getMoveType(); + + return switch (moveType) { + case MOVE_TO_SELF -> new MoveTransaction( + this.succeeded(), this.removeTransaction, moveType, this.getOtherContainer(), (T)this.addTransaction.toParent(parent, start, container) + ); + case MOVE_FROM_SELF -> new MoveTransaction( + this.succeeded(), this.removeTransaction.toParent(parent, start, container), moveType, this.getOtherContainer(), this.addTransaction + ); + }; + } + + @Nullable + public MoveTransaction fromParent(ItemContainer parent, short start, @Nonnull ItemContainer container) { + MoveType moveType = this.getMoveType(); + switch (moveType) { + case MOVE_TO_SELF: + T newAddTransaction = (T)this.addTransaction.fromParent(parent, start, container); + if (newAddTransaction == null) { + return null; + } + + return new MoveTransaction<>(this.succeeded(), this.getRemoveTransaction(), this.getMoveType(), this.getOtherContainer(), newAddTransaction); + case MOVE_FROM_SELF: + SlotTransaction newRemoveTransaction = this.getRemoveTransaction().fromParent(parent, start, container); + if (newRemoveTransaction == null) { + return null; + } + + return new MoveTransaction<>(this.succeeded(), newRemoveTransaction, this.getMoveType(), this.getOtherContainer(), this.addTransaction); + default: + throw new IllegalStateException("Unexpected value: " + moveType); + } + } + + @Nonnull + @Override + public String toString() { + return "MoveTransaction{succeeded=" + + this.succeeded + + ", removeTransaction=" + + this.removeTransaction + + ", moveType=" + + this.moveType + + ", otherContainer=" + + this.otherContainer + + ", addTransaction=" + + this.addTransaction + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/transaction/MoveType.java b/src/com/hypixel/hytale/server/core/inventory/transaction/MoveType.java new file mode 100644 index 0000000..2532107 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/transaction/MoveType.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.server.core.inventory.transaction; + +public enum MoveType { + MOVE_TO_SELF, + MOVE_FROM_SELF; + + private MoveType() { + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/transaction/ResourceSlotTransaction.java b/src/com/hypixel/hytale/server/core/inventory/transaction/ResourceSlotTransaction.java new file mode 100644 index 0000000..c767252 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/transaction/ResourceSlotTransaction.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.server.core.inventory.transaction; + +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.ResourceQuantity; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ResourceSlotTransaction extends SlotTransaction { + @Nonnull + private final ResourceQuantity query; + private final int remainder; + private final int consumed; + + public ResourceSlotTransaction( + boolean succeeded, + @Nonnull ActionType action, + short slot, + @Nullable ItemStack slotBefore, + @Nullable ItemStack slotAfter, + @Nullable ItemStack output, + boolean allOrNothing, + boolean exactAmount, + boolean filter, + @Nonnull ResourceQuantity query, + int remainder, + int consumed + ) { + super(succeeded, action, slot, slotBefore, slotAfter, output, allOrNothing, exactAmount, filter); + this.query = query; + this.remainder = remainder; + this.consumed = consumed; + } + + @Nonnull + public ResourceQuantity getQuery() { + return this.query; + } + + public int getRemainder() { + return this.remainder; + } + + public int getConsumed() { + return this.consumed; + } + + @Nonnull + public ResourceSlotTransaction toParent(ItemContainer parent, short start, ItemContainer container) { + short newSlot = (short)(start + this.getSlot()); + return new ResourceSlotTransaction( + this.succeeded(), + this.getAction(), + newSlot, + this.getSlotBefore(), + this.getSlotAfter(), + this.getOutput(), + this.isAllOrNothing(), + this.isExactAmount(), + this.isFilter(), + this.query, + this.remainder, + this.consumed + ); + } + + @Nullable + public ResourceSlotTransaction fromParent(ItemContainer parent, short start, @Nonnull ItemContainer container) { + short newSlot = (short)(this.getSlot() - start); + return newSlot >= 0 && newSlot < container.getCapacity() + ? new ResourceSlotTransaction( + this.succeeded(), + this.getAction(), + newSlot, + this.getSlotBefore(), + this.getSlotAfter(), + this.getOutput(), + this.isAllOrNothing(), + this.isExactAmount(), + this.isFilter(), + this.query, + this.remainder, + this.consumed + ) + : null; + } + + @Nonnull + @Override + public String toString() { + return "ResourceSlotTransaction{query=" + this.query + ", remainder=" + this.remainder + ", consumed=" + this.consumed + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/transaction/ResourceTransaction.java b/src/com/hypixel/hytale/server/core/inventory/transaction/ResourceTransaction.java new file mode 100644 index 0000000..7ca1a81 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/transaction/ResourceTransaction.java @@ -0,0 +1,129 @@ +package com.hypixel.hytale.server.core.inventory.transaction; + +import com.hypixel.hytale.server.core.inventory.ResourceQuantity; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ResourceTransaction extends ListTransaction { + @Nonnull + private final ActionType action; + @Nonnull + private final ResourceQuantity resource; + private final int remainder; + private final int consumed; + private final boolean allOrNothing; + private final boolean exactAmount; + private final boolean filter; + + public ResourceTransaction( + boolean succeeded, + @Nonnull ActionType action, + @Nonnull ResourceQuantity resource, + int remainder, + int consumed, + boolean allOrNothing, + boolean exactAmount, + boolean filter, + @Nonnull List slotTransactions + ) { + super(succeeded, slotTransactions); + this.action = action; + this.resource = resource; + this.remainder = remainder; + this.consumed = consumed; + this.allOrNothing = allOrNothing; + this.exactAmount = exactAmount; + this.filter = filter; + } + + @Nonnull + public ActionType getAction() { + return this.action; + } + + @Nonnull + public ResourceQuantity getResource() { + return this.resource; + } + + public int getRemainder() { + return this.remainder; + } + + public int getConsumed() { + return this.consumed; + } + + public boolean isAllOrNothing() { + return this.allOrNothing; + } + + public boolean isExactAmount() { + return this.exactAmount; + } + + public boolean isFilter() { + return this.filter; + } + + @Nonnull + public ResourceTransaction toParent(ItemContainer parent, short start, ItemContainer container) { + List slotTransactions = this.getList() + .stream() + .map(transaction -> transaction.toParent(parent, start, container)) + .collect(Collectors.toList()); + return new ResourceTransaction( + this.succeeded(), this.action, this.resource, this.remainder, this.consumed, this.allOrNothing, this.exactAmount, this.filter, slotTransactions + ); + } + + @Nullable + public ResourceTransaction fromParent(ItemContainer parent, short start, @Nonnull ItemContainer container) { + List slotTransactions = this.getList() + .stream() + .map(transactionx -> transactionx.fromParent(parent, start, container)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (slotTransactions.isEmpty()) { + return null; + } else { + boolean succeeded = false; + + for (ResourceSlotTransaction transaction : slotTransactions) { + if (transaction.succeeded()) { + succeeded = true; + break; + } + } + + return new ResourceTransaction( + succeeded, this.action, this.resource, this.remainder, this.consumed, this.allOrNothing, this.exactAmount, this.filter, slotTransactions + ); + } + } + + @Nonnull + @Override + public String toString() { + return "ResourceTransaction{action=" + + this.action + + ", resource=" + + this.resource + + ", remainder=" + + this.remainder + + ", consumed=" + + this.consumed + + ", allOrNothing=" + + this.allOrNothing + + ", exactAmount=" + + this.exactAmount + + ", filter=" + + this.filter + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/transaction/SlotTransaction.java b/src/com/hypixel/hytale/server/core/inventory/transaction/SlotTransaction.java new file mode 100644 index 0000000..4fc5d43 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/transaction/SlotTransaction.java @@ -0,0 +1,131 @@ +package com.hypixel.hytale.server.core.inventory.transaction; + +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SlotTransaction implements Transaction { + public static final SlotTransaction FAILED_ADD = new SlotTransaction(false, ActionType.ADD, (short)-1, null, null, null, false, false, false); + private final boolean succeeded; + @Nonnull + private final ActionType action; + private final short slot; + @Nullable + private final ItemStack slotBefore; + @Nullable + private final ItemStack slotAfter; + @Nullable + private final ItemStack output; + private final boolean allOrNothing; + private final boolean exactAmount; + private final boolean filter; + + public SlotTransaction( + boolean succeeded, + @Nonnull ActionType action, + short slot, + @Nullable ItemStack slotBefore, + @Nullable ItemStack slotAfter, + @Nullable ItemStack output, + boolean allOrNothing, + boolean exactAmount, + boolean filter + ) { + this.succeeded = succeeded; + this.action = action; + this.slot = slot; + this.slotBefore = slotBefore; + this.slotAfter = slotAfter; + this.output = output; + this.allOrNothing = allOrNothing; + this.exactAmount = exactAmount; + this.filter = filter; + } + + @Override + public boolean succeeded() { + return this.succeeded; + } + + @Override + public boolean wasSlotModified(short slot) { + return !this.succeeded ? false : this.slot == slot; + } + + @Nonnull + public ActionType getAction() { + return this.action; + } + + public short getSlot() { + return this.slot; + } + + @Nullable + public ItemStack getSlotBefore() { + return this.slotBefore; + } + + @Nullable + public ItemStack getSlotAfter() { + return this.slotAfter; + } + + @Nullable + public ItemStack getOutput() { + return this.output; + } + + public boolean isAllOrNothing() { + return this.allOrNothing; + } + + public boolean isExactAmount() { + return this.exactAmount; + } + + public boolean isFilter() { + return this.filter; + } + + @Nonnull + public SlotTransaction toParent(ItemContainer parent, short start, ItemContainer container) { + short newSlot = (short)(start + this.slot); + return new SlotTransaction( + this.succeeded, this.action, newSlot, this.slotBefore, this.slotAfter, this.output, this.allOrNothing, this.exactAmount, this.filter + ); + } + + @Nullable + public SlotTransaction fromParent(ItemContainer parent, short start, @Nonnull ItemContainer container) { + short newSlot = (short)(this.slot - start); + return newSlot >= 0 && newSlot < container.getCapacity() + ? new SlotTransaction( + this.succeeded, this.action, newSlot, this.slotBefore, this.slotAfter, this.output, this.allOrNothing, this.exactAmount, this.filter + ) + : null; + } + + @Nonnull + @Override + public String toString() { + return "SlotTransaction{succeeded=" + + this.succeeded + + ", action=" + + this.action + + ", slot=" + + this.slot + + ", slotBefore=" + + this.slotBefore + + ", slotAfter=" + + this.slotAfter + + ", allOrNothing=" + + this.allOrNothing + + ", exactAmount=" + + this.exactAmount + + ", filter=" + + this.filter + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/transaction/TagSlotTransaction.java b/src/com/hypixel/hytale/server/core/inventory/transaction/TagSlotTransaction.java new file mode 100644 index 0000000..6e219e3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/transaction/TagSlotTransaction.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.server.core.inventory.transaction; + +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TagSlotTransaction extends SlotTransaction { + private final int query; + private final int remainder; + + public TagSlotTransaction( + boolean succeeded, + @Nonnull ActionType action, + short slot, + @Nullable ItemStack slotBefore, + @Nullable ItemStack slotAfter, + @Nullable ItemStack output, + boolean allOrNothing, + boolean exactAmount, + boolean filter, + @Nonnull int query, + int remainder + ) { + super(succeeded, action, slot, slotBefore, slotAfter, output, allOrNothing, exactAmount, filter); + this.query = query; + this.remainder = remainder; + } + + public int getQuery() { + return this.query; + } + + public int getRemainder() { + return this.remainder; + } + + @Nonnull + public TagSlotTransaction toParent(ItemContainer parent, short start, ItemContainer container) { + short newSlot = (short)(start + this.getSlot()); + return new TagSlotTransaction( + this.succeeded(), + this.getAction(), + newSlot, + this.getSlotBefore(), + this.getSlotAfter(), + this.getOutput(), + this.isAllOrNothing(), + this.isExactAmount(), + this.isFilter(), + this.query, + this.remainder + ); + } + + @Nullable + public TagSlotTransaction fromParent(ItemContainer parent, short start, @Nonnull ItemContainer container) { + short newSlot = (short)(this.getSlot() - start); + return newSlot >= 0 && newSlot < container.getCapacity() + ? new TagSlotTransaction( + this.succeeded(), + this.getAction(), + newSlot, + this.getSlotBefore(), + this.getSlotAfter(), + this.getOutput(), + this.isAllOrNothing(), + this.isExactAmount(), + this.isFilter(), + this.query, + this.remainder + ) + : null; + } + + @Nonnull + @Override + public String toString() { + return "TagSlotTransaction{tag=" + this.query + ", remainder=" + this.remainder + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/transaction/TagTransaction.java b/src/com/hypixel/hytale/server/core/inventory/transaction/TagTransaction.java new file mode 100644 index 0000000..61295eb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/transaction/TagTransaction.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.server.core.inventory.transaction; + +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TagTransaction extends ListTransaction { + @Nonnull + private final ActionType action; + private final int tagIndex; + private final int remainder; + private final boolean allOrNothing; + private final boolean exactAmount; + private final boolean filter; + + public TagTransaction( + boolean succeeded, + @Nonnull ActionType action, + int tagIndex, + int remainder, + boolean allOrNothing, + boolean exactAmount, + boolean filter, + @Nonnull List slotTransactions + ) { + super(succeeded, slotTransactions); + this.action = action; + this.tagIndex = tagIndex; + this.remainder = remainder; + this.allOrNothing = allOrNothing; + this.exactAmount = exactAmount; + this.filter = filter; + } + + @Nonnull + public ActionType getAction() { + return this.action; + } + + @Nonnull + public int getTagIndex() { + return this.tagIndex; + } + + public int getRemainder() { + return this.remainder; + } + + public boolean isAllOrNothing() { + return this.allOrNothing; + } + + public boolean isExactAmount() { + return this.exactAmount; + } + + public boolean isFilter() { + return this.filter; + } + + @Nonnull + public TagTransaction toParent(ItemContainer parent, short start, ItemContainer container) { + List slotTransactions = this.getList() + .stream() + .map(transaction -> transaction.toParent(parent, start, container)) + .collect(Collectors.toList()); + return new TagTransaction( + this.succeeded(), this.action, this.tagIndex, this.remainder, this.allOrNothing, this.exactAmount, this.filter, slotTransactions + ); + } + + @Nullable + public TagTransaction fromParent(ItemContainer parent, short start, @Nonnull ItemContainer container) { + List slotTransactions = this.getList() + .stream() + .map(transactionx -> transactionx.fromParent(parent, start, container)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (slotTransactions.isEmpty()) { + return null; + } else { + boolean succeeded = false; + + for (TagSlotTransaction transaction : slotTransactions) { + if (transaction.succeeded()) { + succeeded = true; + break; + } + } + + return new TagTransaction(succeeded, this.action, this.tagIndex, this.remainder, this.allOrNothing, this.exactAmount, this.filter, slotTransactions); + } + } + + @Nonnull + @Override + public String toString() { + return "TagTransaction{action=" + + this.action + + ", tag=" + + this.tagIndex + + ", remainder=" + + this.remainder + + ", allOrNothing=" + + this.allOrNothing + + ", exactAmount=" + + this.exactAmount + + ", filter=" + + this.filter + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/inventory/transaction/Transaction.java b/src/com/hypixel/hytale/server/core/inventory/transaction/Transaction.java new file mode 100644 index 0000000..7daf901 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/inventory/transaction/Transaction.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.server.core.inventory.transaction; + +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface Transaction { + boolean succeeded(); + + boolean wasSlotModified(short var1); + + @Nonnull + Transaction toParent(ItemContainer var1, short var2, ItemContainer var3); + + @Nullable + Transaction fromParent(ItemContainer var1, short var2, ItemContainer var3); +} diff --git a/src/com/hypixel/hytale/server/core/io/NetworkSerializable.java b/src/com/hypixel/hytale/server/core/io/NetworkSerializable.java new file mode 100644 index 0000000..fc94951 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/NetworkSerializable.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.server.core.io; + +public interface NetworkSerializable { + Packet toPacket(); +} diff --git a/src/com/hypixel/hytale/server/core/io/NetworkSerializer.java b/src/com/hypixel/hytale/server/core/io/NetworkSerializer.java new file mode 100644 index 0000000..982bfc7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/NetworkSerializer.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.server.core.io; + +@FunctionalInterface +public interface NetworkSerializer { + Packet toPacket(Type var1); +} diff --git a/src/com/hypixel/hytale/server/core/io/NetworkSerializers.java b/src/com/hypixel/hytale/server/core/io/NetworkSerializers.java new file mode 100644 index 0000000..02174dd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/NetworkSerializers.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.server.core.io; + +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.protocol.Hitbox; + +public interface NetworkSerializers { + NetworkSerializer BOX = t -> { + Hitbox packet = new Hitbox(); + packet.minX = (float)t.getMin().getX(); + packet.minY = (float)t.getMin().getY(); + packet.minZ = (float)t.getMin().getZ(); + packet.maxX = (float)t.getMax().getX(); + packet.maxY = (float)t.getMax().getY(); + packet.maxZ = (float)t.getMax().getZ(); + return packet; + }; +} diff --git a/src/com/hypixel/hytale/server/core/io/PacketHandler.java b/src/com/hypixel/hytale/server/core/io/PacketHandler.java new file mode 100644 index 0000000..83dcda7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/PacketHandler.java @@ -0,0 +1,553 @@ +package com.hypixel.hytale.server.core.io; + +import com.google.common.flogger.LazyArgs; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.common.util.NetworkUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.metrics.metric.HistoricMetric; +import com.hypixel.hytale.metrics.metric.Metric; +import com.hypixel.hytale.protocol.CachedPacket; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.PacketStatsRecorder; +import com.hypixel.hytale.protocol.io.netty.ProtocolUtil; +import com.hypixel.hytale.protocol.packets.connection.Disconnect; +import com.hypixel.hytale.protocol.packets.connection.DisconnectType; +import com.hypixel.hytale.protocol.packets.connection.Ping; +import com.hypixel.hytale.protocol.packets.connection.Pong; +import com.hypixel.hytale.protocol.packets.connection.PongType; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.auth.PlayerAuthentication; +import com.hypixel.hytale.server.core.io.adapter.PacketAdapters; +import com.hypixel.hytale.server.core.io.handlers.login.AuthenticationPacketHandler; +import com.hypixel.hytale.server.core.io.handlers.login.PasswordPacketHandler; +import com.hypixel.hytale.server.core.io.netty.NettyUtil; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.receiver.IPacketReceiver; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.local.LocalAddress; +import io.netty.channel.unix.DomainSocketAddress; +import io.netty.handler.codec.quic.QuicStreamChannel; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; +import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue; +import it.unimi.dsi.fastutil.ints.IntPriorityQueue; +import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; +import it.unimi.dsi.fastutil.longs.LongPriorityQueue; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.security.SecureRandom; +import java.time.Duration; +import java.time.Instant; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BooleanSupplier; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class PacketHandler implements IPacketReceiver { + public static final int MAX_PACKET_ID = 512; + private static final HytaleLogger LOGIN_TIMING_LOGGER = HytaleLogger.get("LoginTiming"); + private static final AttributeKey LOGIN_START_ATTRIBUTE_KEY = AttributeKey.newInstance("LOGIN_START"); + @Nonnull + protected final Channel channel; + @Nonnull + protected final ProtocolVersion protocolVersion; + @Nullable + protected PlayerAuthentication auth; + protected boolean queuePackets; + protected final AtomicInteger queuedPackets = new AtomicInteger(); + protected final SecureRandom pingIdRandom = new SecureRandom(); + @Nonnull + protected final PacketHandler.PingInfo[] pingInfo; + private float pingTimer; + protected boolean registered; + private ScheduledFuture timeoutTask; + @Nullable + protected Throwable clientReadyForChunksFutureStack; + @Nullable + protected CompletableFuture clientReadyForChunksFuture; + @Nonnull + protected final PacketHandler.DisconnectReason disconnectReason = new PacketHandler.DisconnectReason(); + + public PacketHandler(@Nonnull Channel channel, @Nonnull ProtocolVersion protocolVersion) { + this.channel = channel; + this.protocolVersion = protocolVersion; + this.pingInfo = new PacketHandler.PingInfo[PongType.VALUES.length]; + + for (PongType pingType : PongType.VALUES) { + this.pingInfo[pingType.ordinal()] = new PacketHandler.PingInfo(pingType); + } + } + + @Nonnull + public Channel getChannel() { + return this.channel; + } + + @Deprecated(forRemoval = true) + public void setCompressionEnabled(boolean compressionEnabled) { + HytaleLogger.getLogger().at(Level.INFO).log(this.getIdentifier() + " compression now handled by encoder"); + } + + @Deprecated(forRemoval = true) + public boolean isCompressionEnabled() { + return true; + } + + @Nonnull + public abstract String getIdentifier(); + + @Nonnull + public ProtocolVersion getProtocolVersion() { + return this.protocolVersion; + } + + public final void registered(@Nullable PacketHandler oldHandler) { + this.registered = true; + this.registered0(oldHandler); + } + + protected void registered0(@Nullable PacketHandler oldHandler) { + } + + public final void unregistered(@Nullable PacketHandler newHandler) { + this.registered = false; + this.clearTimeout(); + this.unregistered0(newHandler); + } + + protected void unregistered0(@Nullable PacketHandler newHandler) { + } + + public void handle(@Nonnull Packet packet) { + this.accept(packet); + } + + public abstract void accept(@Nonnull Packet var1); + + public void logCloseMessage() { + HytaleLogger.getLogger().at(Level.INFO).log("%s was closed.", this.getIdentifier()); + } + + public void closed(ChannelHandlerContext ctx) { + this.clearTimeout(); + } + + public void setQueuePackets(boolean queuePackets) { + this.queuePackets = queuePackets; + } + + public void tryFlush() { + if (this.queuedPackets.getAndSet(0) > 0) { + this.channel.flush(); + } + } + + public void write(@Nonnull Packet... packets) { + Packet[] cachedPackets = new Packet[packets.length]; + this.handleOutboundAndCachePackets(packets, cachedPackets); + if (this.queuePackets) { + this.channel.write(cachedPackets, this.channel.voidPromise()); + this.queuedPackets.getAndIncrement(); + } else { + this.channel.writeAndFlush(cachedPackets, this.channel.voidPromise()); + } + } + + public void write(@Nonnull Packet[] packets, @Nonnull Packet finalPacket) { + Packet[] cachedPackets = new Packet[packets.length + 1]; + this.handleOutboundAndCachePackets(packets, cachedPackets); + cachedPackets[cachedPackets.length - 1] = this.handleOutboundAndCachePacket(finalPacket); + if (this.queuePackets) { + this.channel.write(cachedPackets, this.channel.voidPromise()); + this.queuedPackets.getAndIncrement(); + } else { + this.channel.writeAndFlush(cachedPackets, this.channel.voidPromise()); + } + } + + @Override + public void write(@Nonnull Packet packet) { + this.writePacket(packet, true); + } + + @Override + public void writeNoCache(@Nonnull Packet packet) { + this.writePacket(packet, false); + } + + public void writePacket(@Nonnull Packet packet, boolean cache) { + if (!PacketAdapters.__handleOutbound(this, packet)) { + Packet toSend; + if (cache) { + toSend = this.handleOutboundAndCachePacket(packet); + } else { + toSend = packet; + } + + if (this.queuePackets) { + this.channel.write(toSend, this.channel.voidPromise()); + this.queuedPackets.getAndIncrement(); + } else { + this.channel.writeAndFlush(toSend, this.channel.voidPromise()); + } + } + } + + private void handleOutboundAndCachePackets(@Nonnull Packet[] packets, @Nonnull Packet[] cachedPackets) { + for (int i = 0; i < packets.length; i++) { + Packet packet = packets[i]; + if (!PacketAdapters.__handleOutbound(this, packet)) { + cachedPackets[i] = this.handleOutboundAndCachePacket(packet); + } + } + } + + @Nonnull + private Packet handleOutboundAndCachePacket(@Nonnull Packet packet) { + return (Packet)(packet instanceof CachedPacket ? packet : CachedPacket.cache(packet)); + } + + public void disconnect(@Nonnull String message) { + this.disconnectReason.setServerDisconnectReason(message); + HytaleLogger.getLogger().at(Level.INFO).log("Disconnecting %s with the message: %s", NettyUtil.formatRemoteAddress(this.channel), message); + this.disconnect0(message); + } + + protected void disconnect0(@Nonnull String message) { + this.channel.writeAndFlush(new Disconnect(message, DisconnectType.Disconnect)).addListener(ProtocolUtil.CLOSE_ON_COMPLETE); + } + + @Nullable + public PacketStatsRecorder getPacketStatsRecorder() { + return this.channel.attr(PacketStatsRecorder.CHANNEL_KEY).get(); + } + + @Nonnull + public PacketHandler.PingInfo getPingInfo(@Nonnull PongType pongType) { + return this.pingInfo[pongType.ordinal()]; + } + + public long getOperationTimeoutThreshold() { + double average = this.getPingInfo(PongType.Tick).getPingMetricSet().getAverage(0); + return PacketHandler.PingInfo.TIME_UNIT.toMillis(Math.round(average * 2.0)) + 3000L; + } + + public void tickPing(float dt) { + this.pingTimer -= dt; + if (this.pingTimer <= 0.0F) { + this.pingTimer = 1.0F; + this.sendPing(); + } + } + + public void sendPing() { + int id = this.pingIdRandom.nextInt(); + Instant nowInstant = Instant.now(); + long nowTimestamp = System.nanoTime(); + + for (PacketHandler.PingInfo info : this.pingInfo) { + info.recordSent(id, nowTimestamp); + } + + this.writeNoCache( + new Ping( + id, + WorldTimeResource.instantToInstantData(nowInstant), + (int)this.getPingInfo(PongType.Raw).getPingMetricSet().getLastValue(), + (int)this.getPingInfo(PongType.Direct).getPingMetricSet().getLastValue(), + (int)this.getPingInfo(PongType.Tick).getPingMetricSet().getLastValue() + ) + ); + } + + public void handlePong(@Nonnull Pong packet) { + this.pingInfo[packet.type.ordinal()].handlePacket(packet); + } + + protected void setTimeout(@Nonnull String stageId, @Nonnull BooleanSupplier meets, long def, @Nonnull TimeUnit timeUnit) { + if (this.timeoutTask != null) { + this.timeoutTask.cancel(false); + } + + if (this instanceof AuthenticationPacketHandler || !(this instanceof PasswordPacketHandler) || this.auth != null) { + logConnectionTimings(this.channel, "setTimeout-" + stageId, Level.FINEST); + Map timeouts = HytaleServer.get().getConfig().getConnectionTimeouts().getJoinTimeouts(); + long timeout; + if (timeouts.containsKey(stageId)) { + timeout = timeouts.get(stageId).toMillis(); + } else { + timeout = TimeUnit.MILLISECONDS.convert(def, timeUnit); + } + + this.timeoutTask = this.channel + .eventLoop() + .schedule( + () -> { + if (this.channel.isOpen()) { + if (!meets.getAsBoolean()) { + this.disconnect("Either you took too long to login or we took too long to process your request! Retry again in a moment."); + HytaleLogger.getLogger() + .at(Level.WARNING) + .log( + "Took longer than %s for %s to log in at stage %s! Aborting!", + FormatUtil.timeUnitToString(timeout, TimeUnit.MILLISECONDS), + this.getIdentifier(), + stageId + ); + } + } + }, + timeout, + TimeUnit.MILLISECONDS + ); + } + } + + protected void clearTimeout() { + if (this.timeoutTask != null) { + this.timeoutTask.cancel(false); + } + + if (this.clientReadyForChunksFuture != null) { + this.clientReadyForChunksFuture.cancel(true); + this.clientReadyForChunksFuture = null; + this.clientReadyForChunksFutureStack = null; + } + } + + @Nullable + public PlayerAuthentication getAuth() { + return this.auth; + } + + public boolean stillActive() { + return this.channel.isActive(); + } + + public int getQueuedPacketsCount() { + return this.queuedPackets.get(); + } + + public boolean isLocalConnection() { + SocketAddress socketAddress; + if (this.channel instanceof QuicStreamChannel quicStreamChannel) { + socketAddress = quicStreamChannel.parent().remoteSocketAddress(); + } else { + socketAddress = this.channel.remoteAddress(); + } + + if (socketAddress instanceof InetSocketAddress) { + InetAddress address = ((InetSocketAddress)socketAddress).getAddress(); + return NetworkUtil.addressMatchesAny(address, NetworkUtil.AddressType.ANY_LOCAL, NetworkUtil.AddressType.LOOPBACK); + } else { + return socketAddress instanceof DomainSocketAddress || socketAddress instanceof LocalAddress; + } + } + + public boolean isLANConnection() { + SocketAddress socketAddress; + if (this.channel instanceof QuicStreamChannel quicStreamChannel) { + socketAddress = quicStreamChannel.parent().remoteSocketAddress(); + } else { + socketAddress = this.channel.remoteAddress(); + } + + if (socketAddress instanceof InetSocketAddress) { + InetAddress address = ((InetSocketAddress)socketAddress).getAddress(); + return NetworkUtil.addressMatchesAny(address); + } else { + return socketAddress instanceof DomainSocketAddress || socketAddress instanceof LocalAddress; + } + } + + @Nonnull + public PacketHandler.DisconnectReason getDisconnectReason() { + return this.disconnectReason; + } + + public void setClientReadyForChunksFuture(@Nonnull CompletableFuture clientReadyFuture) { + if (this.clientReadyForChunksFuture != null) { + throw new IllegalStateException("Tried to hook client ready but something is already waiting for it!", this.clientReadyForChunksFutureStack); + } else { + HytaleLogger.getLogger().at(Level.WARNING).log("%s Added future for ClientReady packet?", this.getIdentifier()); + this.clientReadyForChunksFutureStack = new Throwable(); + this.clientReadyForChunksFuture = clientReadyFuture; + } + } + + @Nullable + public CompletableFuture getClientReadyForChunksFuture() { + return this.clientReadyForChunksFuture; + } + + public static void logConnectionTimings(@Nonnull Channel channel, @Nonnull String message, @Nonnull Level level) { + Attribute loginStartAttribute = channel.attr(LOGIN_START_ATTRIBUTE_KEY); + long now = System.nanoTime(); + Long before = loginStartAttribute.getAndSet(now); + if (before == null) { + LOGIN_TIMING_LOGGER.at(level).log(message); + } else { + LOGIN_TIMING_LOGGER.at(level).log("%s took %s", message, LazyArgs.lazy(() -> FormatUtil.nanosToString(now - before))); + } + } + + static { + LOGIN_TIMING_LOGGER.setLevel(Level.ALL); + } + + public static class DisconnectReason { + @Nullable + private String serverDisconnectReason; + @Nullable + private DisconnectType clientDisconnectType; + + protected DisconnectReason() { + } + + @Nullable + public String getServerDisconnectReason() { + return this.serverDisconnectReason; + } + + public void setServerDisconnectReason(String serverDisconnectReason) { + this.serverDisconnectReason = serverDisconnectReason; + this.clientDisconnectType = null; + } + + @Nullable + public DisconnectType getClientDisconnectType() { + return this.clientDisconnectType; + } + + public void setClientDisconnectType(DisconnectType clientDisconnectType) { + this.clientDisconnectType = clientDisconnectType; + this.serverDisconnectReason = null; + } + + @Nonnull + @Override + public String toString() { + return "DisconnectReason{serverDisconnectReason='" + this.serverDisconnectReason + "', clientDisconnectType=" + this.clientDisconnectType + "}"; + } + } + + public static class PingInfo { + public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() + .register("PingType", pingInfo -> pingInfo.pingType, new EnumCodec<>(PongType.class)) + .register("PingMetrics", PacketHandler.PingInfo::getPingMetricSet, HistoricMetric.METRICS_CODEC) + .register("PacketQueueMin", pingInfo -> pingInfo.packetQueueMetric.getMin(), Codec.LONG) + .register("PacketQueueAvg", pingInfo -> pingInfo.packetQueueMetric.getAverage(), Codec.DOUBLE) + .register("PacketQueueMax", pingInfo -> pingInfo.packetQueueMetric.getMax(), Codec.LONG); + public static final TimeUnit TIME_UNIT = TimeUnit.MICROSECONDS; + public static final int ONE_SECOND_INDEX = 0; + public static final int ONE_MINUTE_INDEX = 1; + public static final int FIVE_MINUTE_INDEX = 2; + public static final double PERCENTILE = 0.99F; + public static final int PING_FREQUENCY = 1; + public static final TimeUnit PING_FREQUENCY_UNIT = TimeUnit.SECONDS; + public static final int PING_FREQUENCY_MILLIS = 1000; + public static final int PING_HISTORY_MILLIS = 15000; + public static final int PING_HISTORY_LENGTH = 15; + protected final PongType pingType; + protected final Lock queueLock = new ReentrantLock(); + protected final IntPriorityQueue pingIdQueue = new IntArrayFIFOQueue(15); + protected final LongPriorityQueue pingTimestampQueue = new LongArrayFIFOQueue(15); + protected final Lock pingLock = new ReentrantLock(); + @Nonnull + protected final HistoricMetric pingMetricSet; + protected final Metric packetQueueMetric = new Metric(); + + public PingInfo(PongType pingType) { + this.pingType = pingType; + this.pingMetricSet = HistoricMetric.builder(1000L, TimeUnit.MILLISECONDS) + .addPeriod(1L, TimeUnit.SECONDS) + .addPeriod(1L, TimeUnit.MINUTES) + .addPeriod(5L, TimeUnit.MINUTES) + .build(); + } + + protected void recordSent(int id, long timestamp) { + this.queueLock.lock(); + + try { + this.pingIdQueue.enqueue(id); + this.pingTimestampQueue.enqueue(timestamp); + } finally { + this.queueLock.unlock(); + } + } + + protected void handlePacket(@Nonnull Pong packet) { + if (packet.type != this.pingType) { + throw new IllegalArgumentException("Got packet for " + packet.type + " but expected " + this.pingType); + } else { + this.queueLock.lock(); + + int nextIdToHandle; + long sentTimestamp; + try { + nextIdToHandle = this.pingIdQueue.dequeueInt(); + sentTimestamp = this.pingTimestampQueue.dequeueLong(); + } finally { + this.queueLock.unlock(); + } + + if (packet.id != nextIdToHandle) { + throw new IllegalArgumentException(String.valueOf(packet.id)); + } else { + long nanoTime = System.nanoTime(); + long pingValue = nanoTime - sentTimestamp; + if (pingValue <= 0L) { + throw new IllegalArgumentException(String.format("Ping must be received after its sent! %s", pingValue)); + } else { + this.pingLock.lock(); + + try { + this.pingMetricSet.add(nanoTime, TIME_UNIT.convert(pingValue, TimeUnit.NANOSECONDS)); + this.packetQueueMetric.add(packet.packetQueueSize); + } finally { + this.pingLock.unlock(); + } + } + } + } + } + + public PongType getPingType() { + return this.pingType; + } + + @Nonnull + public Metric getPacketQueueMetric() { + return this.packetQueueMetric; + } + + @Nonnull + public HistoricMetric getPingMetricSet() { + return this.pingMetricSet; + } + + public void clear() { + this.pingLock.lock(); + + try { + this.packetQueueMetric.clear(); + this.pingMetricSet.clear(); + } finally { + this.pingLock.unlock(); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/io/PacketStatsRecorderImpl.java b/src/com/hypixel/hytale/server/core/io/PacketStatsRecorderImpl.java new file mode 100644 index 0000000..625005c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/PacketStatsRecorderImpl.java @@ -0,0 +1,310 @@ +package com.hypixel.hytale.server.core.io; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.metrics.metric.AverageCollector; +import com.hypixel.hytale.protocol.PacketRegistry; +import com.hypixel.hytale.protocol.io.PacketStatsRecorder; +import java.util.ArrayList; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PacketStatsRecorderImpl implements PacketStatsRecorder { + public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() + .register("Packets", recorder -> { + ArrayList entries = new ArrayList<>(); + + for (int i = 0; i < 512; i++) { + PacketStatsRecorderImpl.PacketStatsEntry entry = recorder.entries[i]; + if (entry.hasData()) { + entries.add(entry); + } + } + + return entries.toArray(PacketStatsRecorderImpl.PacketStatsEntry[]::new); + }, new ArrayCodec<>(PacketStatsRecorderImpl.PacketStatsEntry.METRICS_REGISTRY, PacketStatsRecorderImpl.PacketStatsEntry[]::new)); + private final PacketStatsRecorderImpl.PacketStatsEntry[] entries = new PacketStatsRecorderImpl.PacketStatsEntry[512]; + + public PacketStatsRecorderImpl() { + for (int i = 0; i < this.entries.length; i++) { + this.entries[i] = new PacketStatsRecorderImpl.PacketStatsEntry(i); + } + } + + @Override + public void recordSend(int packetId, int uncompressedSize, int compressedSize) { + if (packetId >= 0 && packetId < this.entries.length) { + this.entries[packetId].recordSend(uncompressedSize, compressedSize); + } + } + + @Override + public void recordReceive(int packetId, int uncompressedSize, int compressedSize) { + if (packetId >= 0 && packetId < this.entries.length) { + this.entries[packetId].recordReceive(uncompressedSize, compressedSize); + } + } + + @Nonnull + public PacketStatsRecorderImpl.PacketStatsEntry getEntry(int packetId) { + return this.entries[packetId]; + } + + public static class PacketStatsEntry implements PacketStatsRecorder.PacketStatsEntry { + public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() + .register("PacketId", PacketStatsRecorderImpl.PacketStatsEntry::getPacketId, Codec.INTEGER) + .register("Name", PacketStatsRecorderImpl.PacketStatsEntry::getName, Codec.STRING) + .register("SentCount", PacketStatsRecorderImpl.PacketStatsEntry::getSentCount, Codec.INTEGER) + .register("SentUncompressedTotal", PacketStatsRecorderImpl.PacketStatsEntry::getSentUncompressedTotal, Codec.LONG) + .register("SentCompressedTotal", PacketStatsRecorderImpl.PacketStatsEntry::getSentCompressedTotal, Codec.LONG) + .register("SentUncompressedMin", PacketStatsRecorderImpl.PacketStatsEntry::getSentUncompressedMin, Codec.LONG) + .register("SentUncompressedMax", PacketStatsRecorderImpl.PacketStatsEntry::getSentUncompressedMax, Codec.LONG) + .register("SentCompressedMin", PacketStatsRecorderImpl.PacketStatsEntry::getSentCompressedMin, Codec.LONG) + .register("SentCompressedMax", PacketStatsRecorderImpl.PacketStatsEntry::getSentCompressedMax, Codec.LONG) + .register("ReceivedCount", PacketStatsRecorderImpl.PacketStatsEntry::getReceivedCount, Codec.INTEGER) + .register("ReceivedUncompressedTotal", PacketStatsRecorderImpl.PacketStatsEntry::getReceivedUncompressedTotal, Codec.LONG) + .register("ReceivedCompressedTotal", PacketStatsRecorderImpl.PacketStatsEntry::getReceivedCompressedTotal, Codec.LONG) + .register("ReceivedUncompressedMin", PacketStatsRecorderImpl.PacketStatsEntry::getReceivedUncompressedMin, Codec.LONG) + .register("ReceivedUncompressedMax", PacketStatsRecorderImpl.PacketStatsEntry::getReceivedUncompressedMax, Codec.LONG) + .register("ReceivedCompressedMin", PacketStatsRecorderImpl.PacketStatsEntry::getReceivedCompressedMin, Codec.LONG) + .register("ReceivedCompressedMax", PacketStatsRecorderImpl.PacketStatsEntry::getReceivedCompressedMax, Codec.LONG); + private final int packetId; + private final AtomicInteger sentCount = new AtomicInteger(); + private final AtomicLong sentUncompressedTotal = new AtomicLong(); + private final AtomicLong sentCompressedTotal = new AtomicLong(); + private final AtomicLong sentUncompressedMin = new AtomicLong(Long.MAX_VALUE); + private final AtomicLong sentUncompressedMax = new AtomicLong(); + private final AtomicLong sentCompressedMin = new AtomicLong(Long.MAX_VALUE); + private final AtomicLong sentCompressedMax = new AtomicLong(); + private final AverageCollector sentUncompressedAvg = new AverageCollector(); + private final AverageCollector sentCompressedAvg = new AverageCollector(); + private final Queue sentRecently = new ConcurrentLinkedQueue<>(); + private final AtomicInteger receivedCount = new AtomicInteger(); + private final AtomicLong receivedUncompressedTotal = new AtomicLong(); + private final AtomicLong receivedCompressedTotal = new AtomicLong(); + private final AtomicLong receivedUncompressedMin = new AtomicLong(Long.MAX_VALUE); + private final AtomicLong receivedUncompressedMax = new AtomicLong(); + private final AtomicLong receivedCompressedMin = new AtomicLong(Long.MAX_VALUE); + private final AtomicLong receivedCompressedMax = new AtomicLong(); + private final AverageCollector receivedUncompressedAvg = new AverageCollector(); + private final AverageCollector receivedCompressedAvg = new AverageCollector(); + private final Queue receivedRecently = new ConcurrentLinkedQueue<>(); + + public PacketStatsEntry(int packetId) { + this.packetId = packetId; + } + + void recordSend(int uncompressedSize, int compressedSize) { + this.sentCount.incrementAndGet(); + this.sentUncompressedTotal.addAndGet(uncompressedSize); + this.sentCompressedTotal.addAndGet(compressedSize); + this.sentUncompressedMin.accumulateAndGet(uncompressedSize, Math::min); + this.sentUncompressedMax.accumulateAndGet(uncompressedSize, Math::max); + this.sentCompressedMin.accumulateAndGet(compressedSize, Math::min); + this.sentCompressedMax.accumulateAndGet(compressedSize, Math::max); + this.sentUncompressedAvg.add(uncompressedSize); + this.sentCompressedAvg.add(compressedSize); + long now = System.nanoTime(); + this.sentRecently.add(new PacketStatsRecorderImpl.PacketStatsEntry.SizeRecord(now, uncompressedSize, compressedSize)); + this.pruneOld(this.sentRecently, now); + } + + void recordReceive(int uncompressedSize, int compressedSize) { + this.receivedCount.incrementAndGet(); + this.receivedUncompressedTotal.addAndGet(uncompressedSize); + this.receivedCompressedTotal.addAndGet(compressedSize); + this.receivedUncompressedMin.accumulateAndGet(uncompressedSize, Math::min); + this.receivedUncompressedMax.accumulateAndGet(uncompressedSize, Math::max); + this.receivedCompressedMin.accumulateAndGet(compressedSize, Math::min); + this.receivedCompressedMax.accumulateAndGet(compressedSize, Math::max); + this.receivedUncompressedAvg.add(uncompressedSize); + this.receivedCompressedAvg.add(compressedSize); + long now = System.nanoTime(); + this.receivedRecently.add(new PacketStatsRecorderImpl.PacketStatsEntry.SizeRecord(now, uncompressedSize, compressedSize)); + this.pruneOld(this.receivedRecently, now); + } + + private void pruneOld(Queue queue, long now) { + long cutoff = now - TimeUnit.SECONDS.toNanos(30L); + + for (PacketStatsRecorderImpl.PacketStatsEntry.SizeRecord head = queue.peek(); head != null && head.nanos < cutoff; head = queue.peek()) { + queue.poll(); + } + } + + @Override + public boolean hasData() { + return this.sentCount.get() > 0 || this.receivedCount.get() > 0; + } + + @Override + public int getPacketId() { + return this.packetId; + } + + @Nullable + @Override + public String getName() { + PacketRegistry.PacketInfo info = PacketRegistry.getById(this.packetId); + return info != null ? info.name() : null; + } + + @Override + public int getSentCount() { + return this.sentCount.get(); + } + + @Override + public long getSentUncompressedTotal() { + return this.sentUncompressedTotal.get(); + } + + @Override + public long getSentCompressedTotal() { + return this.sentCompressedTotal.get(); + } + + @Override + public long getSentUncompressedMin() { + return this.sentCount.get() > 0 ? this.sentUncompressedMin.get() : 0L; + } + + @Override + public long getSentUncompressedMax() { + return this.sentUncompressedMax.get(); + } + + @Override + public long getSentCompressedMin() { + return this.sentCount.get() > 0 ? this.sentCompressedMin.get() : 0L; + } + + @Override + public long getSentCompressedMax() { + return this.sentCompressedMax.get(); + } + + @Override + public double getSentUncompressedAvg() { + return this.sentUncompressedAvg.get(); + } + + @Override + public double getSentCompressedAvg() { + return this.sentCompressedAvg.get(); + } + + @Override + public int getReceivedCount() { + return this.receivedCount.get(); + } + + @Override + public long getReceivedUncompressedTotal() { + return this.receivedUncompressedTotal.get(); + } + + @Override + public long getReceivedCompressedTotal() { + return this.receivedCompressedTotal.get(); + } + + @Override + public long getReceivedUncompressedMin() { + return this.receivedCount.get() > 0 ? this.receivedUncompressedMin.get() : 0L; + } + + @Override + public long getReceivedUncompressedMax() { + return this.receivedUncompressedMax.get(); + } + + @Override + public long getReceivedCompressedMin() { + return this.receivedCount.get() > 0 ? this.receivedCompressedMin.get() : 0L; + } + + @Override + public long getReceivedCompressedMax() { + return this.receivedCompressedMax.get(); + } + + @Override + public double getReceivedUncompressedAvg() { + return this.receivedUncompressedAvg.get(); + } + + @Override + public double getReceivedCompressedAvg() { + return this.receivedCompressedAvg.get(); + } + + @Nonnull + @Override + public PacketStatsRecorder.RecentStats getSentRecently() { + return this.computeRecentStats(this.sentRecently); + } + + @Nonnull + @Override + public PacketStatsRecorder.RecentStats getReceivedRecently() { + return this.computeRecentStats(this.receivedRecently); + } + + private PacketStatsRecorder.RecentStats computeRecentStats(Queue queue) { + int count = 0; + long uncompressedTotal = 0L; + long compressedTotal = 0L; + int uncompressedMin = Integer.MAX_VALUE; + int uncompressedMax = 0; + int compressedMin = Integer.MAX_VALUE; + int compressedMax = 0; + + for (PacketStatsRecorderImpl.PacketStatsEntry.SizeRecord record : queue) { + count++; + uncompressedTotal += record.uncompressedSize; + compressedTotal += record.compressedSize; + uncompressedMin = Math.min(uncompressedMin, record.uncompressedSize); + uncompressedMax = Math.max(uncompressedMax, record.uncompressedSize); + compressedMin = Math.min(compressedMin, record.compressedSize); + compressedMax = Math.max(compressedMax, record.compressedSize); + } + + return count == 0 + ? PacketStatsRecorder.RecentStats.EMPTY + : new PacketStatsRecorder.RecentStats(count, uncompressedTotal, compressedTotal, uncompressedMin, uncompressedMax, compressedMin, compressedMax); + } + + public void reset() { + this.sentCount.set(0); + this.sentUncompressedTotal.set(0L); + this.sentCompressedTotal.set(0L); + this.sentUncompressedMin.set(Long.MAX_VALUE); + this.sentUncompressedMax.set(0L); + this.sentCompressedMin.set(Long.MAX_VALUE); + this.sentCompressedMax.set(0L); + this.sentUncompressedAvg.clear(); + this.sentCompressedAvg.clear(); + this.sentRecently.clear(); + this.receivedCount.set(0); + this.receivedUncompressedTotal.set(0L); + this.receivedCompressedTotal.set(0L); + this.receivedUncompressedMin.set(Long.MAX_VALUE); + this.receivedUncompressedMax.set(0L); + this.receivedCompressedMin.set(Long.MAX_VALUE); + this.receivedCompressedMax.set(0L); + this.receivedUncompressedAvg.clear(); + this.receivedCompressedAvg.clear(); + this.receivedRecently.clear(); + } + + public record SizeRecord(long nanos, int uncompressedSize, int compressedSize) { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/io/ProtocolVersion.java b/src/com/hypixel/hytale/server/core/io/ProtocolVersion.java new file mode 100644 index 0000000..60a9e27 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/ProtocolVersion.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.server.core.io; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ProtocolVersion { + private final String hash; + + public ProtocolVersion(String hash) { + this.hash = hash; + } + + public String getHash() { + return this.hash; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + ProtocolVersion that = (ProtocolVersion)o; + return this.hash != null ? this.hash.equals(that.hash) : that.hash == null; + } else { + return false; + } + } + + @Override + public int hashCode() { + return 31 * (this.hash != null ? this.hash.hashCode() : 0); + } + + @Nonnull + @Override + public String toString() { + return "ProtocolVersion{hash='" + this.hash + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/io/ServerManager.java b/src/com/hypixel/hytale/server/core/io/ServerManager.java new file mode 100644 index 0000000..ffb7717 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/ServerManager.java @@ -0,0 +1,327 @@ +package com.hypixel.hytale.server.core.io; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.common.util.NetworkUtil; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.server.core.Constants; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.event.events.ShutdownEvent; +import com.hypixel.hytale.server.core.io.commands.BindingsCommand; +import com.hypixel.hytale.server.core.io.handlers.IPacketHandler; +import com.hypixel.hytale.server.core.io.handlers.SubPacketHandler; +import com.hypixel.hytale.server.core.io.handlers.game.GamePacketHandler; +import com.hypixel.hytale.server.core.io.handlers.game.InventoryPacketHandler; +import com.hypixel.hytale.server.core.io.transport.QUICTransport; +import com.hypixel.hytale.server.core.io.transport.TCPTransport; +import com.hypixel.hytale.server.core.io.transport.Transport; +import com.hypixel.hytale.server.core.io.transport.TransportType; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ServerManager extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(ServerManager.class).build(); + @Nonnull + private static final NetworkUtil.AddressType[] NON_PUBLIC_ADDRESS_TYPES = new NetworkUtil.AddressType[]{ + NetworkUtil.AddressType.ANY_LOCAL, + NetworkUtil.AddressType.LOOPBACK, + NetworkUtil.AddressType.SITE_LOCAL, + NetworkUtil.AddressType.LINK_LOCAL, + NetworkUtil.AddressType.MULTICAST + }; + private static ServerManager instance; + @Nonnull + private final List listeners = new CopyOnWriteArrayList<>(); + @Nonnull + private final List> subPacketHandlers = new ObjectArrayList<>(); + @Nullable + private Transport transport; + @Nullable + private CompletableFuture registerFuture; + @Nullable + private CompletableFuture bootFuture; + + public static ServerManager get() { + return instance; + } + + public ServerManager(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + if (!Options.getOptionSet().has(Options.BARE)) { + this.init(); + } + } + + public void init() { + this.registerFuture = CompletableFutureUtil._catch(CompletableFuture.runAsync(SneakyThrow.sneakyRunnable(() -> { + long start = System.nanoTime(); + + this.transport = (Transport)(switch ((TransportType)Options.getOptionSet().valuesOf(Options.TRANSPORT).getFirst()) { + case TCP -> new TCPTransport(); + case QUIC -> new QUICTransport(); + }); + this.getLogger().at(Level.INFO).log("Took %s to setup transport!", FormatUtil.nanosToString(System.nanoTime() - start)); + this.registerFuture = null; + }))); + } + + @Override + protected void setup() { + this.getEventRegistry().register((short)-40, ShutdownEvent.class, event -> this.unbindAllListeners()); + get().registerSubPacketHandlers(InventoryPacketHandler::new); + this.getCommandRegistry().registerCommand(new BindingsCommand()); + } + + @Override + protected void start() { + this.bootFuture = CompletableFuture.runAsync(() -> { + CompletableFuture registerFuture = this.registerFuture; + if (registerFuture != null) { + registerFuture.getNow(null); + } + + if (!HytaleServer.get().isShuttingDown()) { + label40: + if (Options.getOptionSet().has(Options.MIGRATIONS) || Options.getOptionSet().has(Options.BARE)) { + this.bootFuture = null; + } else if (Constants.SINGLEPLAYER) { + try { + InetAddress[] localhosts = InetAddress.getAllByName("localhost"); + InetAddress[] arr$ = localhosts; + int len$ = localhosts.length; + int i$ = 0; + + while (true) { + if (i$ >= len$) { + break label40; + } + + InetAddress localhost = arr$[i$]; + this.bind(new InetSocketAddress(localhost, Options.getOptionSet().valueOf(Options.BIND).getPort())); + i$++; + } + } catch (UnknownHostException var7) { + throw SneakyThrow.sneakyThrow(var7); + } + } else { + for (InetSocketAddress address : Options.getOptionSet().valuesOf(Options.BIND)) { + this.bind(address); + } + + if (!this.listeners.isEmpty()) { + break label40; + } + + throw new IllegalArgumentException("Listeners is empty after starting ServerManager!!"); + } + } + }); + } + + @Override + protected void shutdown() { + Universe.get().disconnectAllPLayers(); + this.unbindAllListeners(); + this.transport.shutdown(); + this.transport = null; + this.getLogger().at(Level.INFO).log("Finished shutting down ServerManager..."); + } + + public void unbindAllListeners() { + for (Channel channel : this.listeners) { + this.unbind0(channel); + } + + this.listeners.clear(); + } + + @Nonnull + public List getListeners() { + return Collections.unmodifiableList(this.listeners); + } + + public boolean bind(@Nonnull InetSocketAddress address) { + if (address.getAddress().isAnyLocalAddress() && this.transport.getType() == TransportType.QUIC) { + Channel channelIpv6 = this.bind0(new InetSocketAddress(NetworkUtil.ANY_IPV6_ADDRESS, address.getPort())); + if (channelIpv6 != null) { + this.listeners.add(channelIpv6); + } + + Channel channelIpv4 = this.bind0(new InetSocketAddress(NetworkUtil.ANY_IPV4_ADDRESS, address.getPort())); + if (channelIpv4 != null) { + this.listeners.add(channelIpv4); + } + + Channel channelIpv6Localhost = this.bind0(new InetSocketAddress(NetworkUtil.LOOPBACK_IPV6_ADDRESS, address.getPort())); + if (channelIpv6Localhost != null) { + this.listeners.add(channelIpv6Localhost); + } + + return channelIpv4 != null || channelIpv6 != null; + } else { + Channel channel = this.bind0(address); + if (channel != null) { + this.listeners.add(channel); + } + + return channel != null; + } + } + + public boolean unbind(@Nonnull Channel channel) { + boolean success = this.unbind0(channel); + if (success) { + this.listeners.remove(channel); + } + + return success; + } + + @Nullable + public InetSocketAddress getLocalOrPublicAddress() throws SocketException { + for (Channel channel : this.listeners) { + if (channel.localAddress() instanceof InetSocketAddress inetSocketAddress) { + InetAddress address = inetSocketAddress.getAddress(); + if (address.isLoopbackAddress()) { + return inetSocketAddress; + } + + if (address.isAnyLocalAddress()) { + InetAddress anyNonLoopbackAddress = NetworkUtil.getFirstNonLoopbackAddress(); + if (anyNonLoopbackAddress == null) { + return null; + } + + return new InetSocketAddress(anyNonLoopbackAddress, inetSocketAddress.getPort()); + } + + return inetSocketAddress; + } + } + + return null; + } + + @Nullable + public InetSocketAddress getNonLoopbackAddress() throws SocketException { + for (Channel channel : this.listeners) { + if (channel.localAddress() instanceof InetSocketAddress inetSocketAddress) { + InetAddress address = inetSocketAddress.getAddress(); + if (!address.isLoopbackAddress()) { + if (address.isAnyLocalAddress()) { + InetAddress anyNonLoopbackAddress = NetworkUtil.getFirstNonLoopbackAddress(); + if (anyNonLoopbackAddress == null) { + return null; + } + + return new InetSocketAddress(anyNonLoopbackAddress, inetSocketAddress.getPort()); + } + + return inetSocketAddress; + } + } + } + + return null; + } + + @Nullable + public InetSocketAddress getPublicAddress() throws SocketException { + for (Channel channel : this.listeners) { + if (channel.localAddress() instanceof InetSocketAddress inetSocketAddress) { + InetAddress address = inetSocketAddress.getAddress(); + if (!address.isLoopbackAddress() && !address.isSiteLocalAddress()) { + if (address.isAnyLocalAddress()) { + InetAddress anyPublicAddress = NetworkUtil.getFirstAddressWithout(NON_PUBLIC_ADDRESS_TYPES); + if (anyPublicAddress == null) { + return null; + } + + return new InetSocketAddress(anyPublicAddress, inetSocketAddress.getPort()); + } + + return inetSocketAddress; + } + } + } + + return null; + } + + public void waitForBindComplete() { + CompletableFuture future = this.bootFuture; + if (future != null) { + future.getNow(null); + } + } + + public void registerSubPacketHandlers(@Nonnull Function supplier) { + this.subPacketHandlers.add(supplier); + } + + public void populateSubPacketHandlers(@Nonnull GamePacketHandler packetHandler) { + for (Function subPacketHandler : this.subPacketHandlers) { + packetHandler.registerSubPacketHandler(subPacketHandler.apply(packetHandler)); + } + } + + @Nullable + private Channel bind0(@Nonnull InetSocketAddress address) { + long start = System.nanoTime(); + this.getLogger().at(Level.FINE).log("Binding to %s (%s)", address, this.transport.getType()); + + try { + ChannelFuture f = this.transport.bind(address).sync(); + if (f.isSuccess()) { + Channel channel = f.channel(); + this.getLogger().at(Level.INFO).log("Listening on %s and took %s", channel.localAddress(), FormatUtil.nanosToString(System.nanoTime() - start)); + return channel; + } + + this.getLogger().at(Level.SEVERE).withCause(new SkipSentryException(f.cause())).log("Could not bind to host %s", address); + } catch (InterruptedException var6) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted when attempting to bind to host " + address, var6); + } catch (Throwable var7) { + this.getLogger().at(Level.SEVERE).withCause(new SkipSentryException(var7)).log("Failed to bind to %s", address); + } + + return null; + } + + private boolean unbind0(@Nonnull Channel channel) { + long start = System.nanoTime(); + this.getLogger().at(Level.FINE).log("Closing listener %s", channel); + + try { + channel.close().await(1L, TimeUnit.SECONDS); + this.getLogger().at(Level.INFO).log("Closed listener %s and took %s", channel, FormatUtil.nanosToString(System.nanoTime() - start)); + return true; + } catch (InterruptedException var5) { + this.getLogger().at(Level.SEVERE).withCause(var5).log("Failed to await for listener to close!"); + Thread.currentThread().interrupt(); + return false; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/io/adapter/PacketAdapters.java b/src/com/hypixel/hytale/server/core/io/adapter/PacketAdapters.java new file mode 100644 index 0000000..ac88971 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/adapter/PacketAdapters.java @@ -0,0 +1,122 @@ +package com.hypixel.hytale.server.core.io.adapter; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.io.handlers.game.GamePacketHandler; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class PacketAdapters { + private static final List inboundHandlers = new CopyOnWriteArrayList<>(); + private static final List outboundHandlers = new CopyOnWriteArrayList<>(); + + public PacketAdapters() { + } + + @Nonnull + public static PacketFilter registerInbound(@Nonnull PacketWatcher watcher) { + PacketFilter out = (packetListener, packet) -> { + watcher.accept(packetListener, packet); + return false; + }; + registerInbound(out); + return out; + } + + public static void registerInbound(PacketFilter predicate) { + inboundHandlers.add(predicate); + } + + @Nonnull + public static PacketFilter registerOutbound(@Nonnull PacketWatcher watcher) { + PacketFilter out = (packetListener, packet) -> { + watcher.accept(packetListener, packet); + return false; + }; + registerOutbound(out); + return out; + } + + public static void registerOutbound(PacketFilter predicate) { + outboundHandlers.add(predicate); + } + + @Nonnull + public static PacketFilter registerInbound(@Nonnull PlayerPacketFilter filter) { + PacketFilter out = (packetHandler, client) -> packetHandler instanceof GamePacketHandler + && filter.test(((GamePacketHandler)packetHandler).getPlayerRef(), client); + registerInbound(out); + return out; + } + + @Nonnull + public static PacketFilter registerOutbound(@Nonnull PlayerPacketFilter filter) { + PacketFilter out = (packetHandler, server) -> packetHandler instanceof GamePacketHandler + && filter.test(((GamePacketHandler)packetHandler).getPlayerRef(), server); + registerOutbound(out); + return out; + } + + @Nonnull + public static PacketFilter registerInbound(@Nonnull PlayerPacketWatcher watcher) { + PacketFilter out = (packetHandler, client) -> { + if (packetHandler instanceof GamePacketHandler) { + watcher.accept(((GamePacketHandler)packetHandler).getPlayerRef(), client); + } + + return false; + }; + registerInbound(out); + return out; + } + + @Nonnull + public static PacketFilter registerOutbound(@Nonnull PlayerPacketWatcher watcher) { + PacketFilter out = (packetHandler, server) -> { + if (packetHandler instanceof GamePacketHandler) { + watcher.accept(((GamePacketHandler)packetHandler).getPlayerRef(), server); + } + + return false; + }; + registerOutbound(out); + return out; + } + + public static void deregisterInbound(PacketFilter predicate) { + if (!inboundHandlers.remove(predicate)) { + throw new IllegalArgumentException("That handler was not registered to inbound!"); + } + } + + public static void deregisterOutbound(PacketFilter predicate) { + if (!outboundHandlers.remove(predicate)) { + throw new IllegalArgumentException("That handler was not registered to outbound!"); + } + } + + public static boolean __handleInbound(PacketHandler player, Packet packet) { + return handle(inboundHandlers, player, packet); + } + + private static boolean handle(@Nonnull List list, PacketHandler player, T packet) { + for (int i = 0; i < list.size(); i++) { + try { + if (list.get(i).test(player, packet)) { + return true; + } + } catch (Throwable var5) { + HytaleLogger.getLogger().at(Level.SEVERE).withCause(var5).log("Failed to test packet %s against %s:", packet, player); + } + } + + return false; + } + + public static boolean __handleOutbound(PacketHandler player, Packet packet) { + return handle(outboundHandlers, player, packet); + } +} diff --git a/src/com/hypixel/hytale/server/core/io/adapter/PacketFilter.java b/src/com/hypixel/hytale/server/core/io/adapter/PacketFilter.java new file mode 100644 index 0000000..40c9b0e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/adapter/PacketFilter.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.server.core.io.adapter; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.server.core.io.PacketHandler; +import java.util.function.BiPredicate; + +public interface PacketFilter extends BiPredicate { + boolean test(PacketHandler var1, Packet var2); +} diff --git a/src/com/hypixel/hytale/server/core/io/adapter/PacketWatcher.java b/src/com/hypixel/hytale/server/core/io/adapter/PacketWatcher.java new file mode 100644 index 0000000..f0ec5d9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/adapter/PacketWatcher.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.core.io.adapter; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.server.core.io.PacketHandler; +import java.util.function.BiConsumer; + +@FunctionalInterface +public interface PacketWatcher extends BiConsumer { + void accept(PacketHandler var1, Packet var2); +} diff --git a/src/com/hypixel/hytale/server/core/io/adapter/PlayerPacketFilter.java b/src/com/hypixel/hytale/server/core/io/adapter/PlayerPacketFilter.java new file mode 100644 index 0000000..407f4a4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/adapter/PlayerPacketFilter.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.server.core.io.adapter; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import java.util.function.BiPredicate; + +public interface PlayerPacketFilter extends BiPredicate { + boolean test(PlayerRef var1, Packet var2); +} diff --git a/src/com/hypixel/hytale/server/core/io/adapter/PlayerPacketWatcher.java b/src/com/hypixel/hytale/server/core/io/adapter/PlayerPacketWatcher.java new file mode 100644 index 0000000..37c9f5c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/adapter/PlayerPacketWatcher.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.server.core.io.adapter; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import java.util.function.BiConsumer; + +public interface PlayerPacketWatcher extends BiConsumer { + void accept(PlayerRef var1, Packet var2); +} diff --git a/src/com/hypixel/hytale/server/core/io/commands/BindingsCommand.java b/src/com/hypixel/hytale/server/core/io/commands/BindingsCommand.java new file mode 100644 index 0000000..9a29d3b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/commands/BindingsCommand.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.io.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.io.ServerManager; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import io.netty.channel.Channel; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class BindingsCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_IO_SERVER_MANAGER_BINDINGS = Message.translation("server.io.servermanager.bindings"); + + public BindingsCommand() { + super("bindings", "server.io.servermanager.bindings"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + List listeners = ServerManager.get().getListeners(); + context.sendMessage( + MessageFormat.list(MESSAGE_IO_SERVER_MANAGER_BINDINGS, listeners.stream().map(Channel::toString).map(Message::raw).collect(Collectors.toSet())) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/io/handlers/GenericConnectionPacketHandler.java b/src/com/hypixel/hytale/server/core/io/handlers/GenericConnectionPacketHandler.java new file mode 100644 index 0000000..0f083b8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/handlers/GenericConnectionPacketHandler.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.server.core.io.handlers; + +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.io.ProtocolVersion; +import io.netty.channel.Channel; +import javax.annotation.Nonnull; + +public abstract class GenericConnectionPacketHandler extends PacketHandler { + protected final String language; + + public GenericConnectionPacketHandler(@Nonnull Channel channel, @Nonnull ProtocolVersion protocolVersion, String language) { + super(channel, protocolVersion); + this.language = language; + } +} diff --git a/src/com/hypixel/hytale/server/core/io/handlers/GenericPacketHandler.java b/src/com/hypixel/hytale/server/core/io/handlers/GenericPacketHandler.java new file mode 100644 index 0000000..bbdedb3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/handlers/GenericPacketHandler.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.server.core.io.handlers; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.io.ProtocolVersion; +import io.netty.channel.Channel; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +public abstract class GenericPacketHandler extends PacketHandler { + private static final Consumer EMPTY_CONSUMER = packet -> {}; + @Nonnull + protected final List packetHandlers = new ObjectArrayList<>(); + private Consumer[] handlers = newHandlerArray(0); + + @Nonnull + public static Consumer[] newHandlerArray(int size) { + return new Consumer[size]; + } + + public GenericPacketHandler(@Nonnull Channel channel, @Nonnull ProtocolVersion protocolVersion) { + super(channel, protocolVersion); + } + + public void registerSubPacketHandler(SubPacketHandler subPacketHandler) { + this.packetHandlers.add(subPacketHandler); + } + + public void registerHandler(int packetId, @Nonnull Consumer handler) { + if (packetId >= this.handlers.length) { + Consumer[] newHandlers = newHandlerArray(packetId + 1); + System.arraycopy(this.handlers, 0, newHandlers, 0, this.handlers.length); + this.handlers = newHandlers; + } + + this.handlers[packetId] = handler; + } + + public void registerNoOpHandlers(@Nonnull int... packetIds) { + for (int packetId : packetIds) { + this.registerHandler(packetId, EMPTY_CONSUMER); + } + } + + @Override + public final void accept(@Nonnull Packet packet) { + int packetId = packet.getId(); + Consumer handler = this.handlers.length > packetId ? this.handlers[packetId] : null; + if (handler != null) { + try { + handler.accept(packet); + } catch (Throwable var5) { + throw new RuntimeException("Could not handle packet (" + packetId + "): " + packet, var5); + } + } else { + throw new RuntimeException("No handler is registered for (" + packetId + "): " + packet); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/io/handlers/IPacketHandler.java b/src/com/hypixel/hytale/server/core/io/handlers/IPacketHandler.java new file mode 100644 index 0000000..c8a6c3d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/handlers/IPacketHandler.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.server.core.io.handlers; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +public interface IPacketHandler { + void registerHandler(int var1, @Nonnull Consumer var2); + + void registerNoOpHandlers(int... var1); + + @Nonnull + PlayerRef getPlayerRef(); + + @Nonnull + String getIdentifier(); +} diff --git a/src/com/hypixel/hytale/server/core/io/handlers/InitialPacketHandler.java b/src/com/hypixel/hytale/server/core/io/handlers/InitialPacketHandler.java new file mode 100644 index 0000000..8164ed4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/handlers/InitialPacketHandler.java @@ -0,0 +1,238 @@ +package com.hypixel.hytale.server.core.io.handlers; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.netty.ProtocolUtil; +import com.hypixel.hytale.protocol.packets.auth.ConnectAccept; +import com.hypixel.hytale.protocol.packets.connection.ClientType; +import com.hypixel.hytale.protocol.packets.connection.Connect; +import com.hypixel.hytale.protocol.packets.connection.Disconnect; +import com.hypixel.hytale.server.core.Constants; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.io.ProtocolVersion; +import com.hypixel.hytale.server.core.io.handlers.login.AuthenticationPacketHandler; +import com.hypixel.hytale.server.core.io.handlers.login.PasswordPacketHandler; +import com.hypixel.hytale.server.core.io.netty.NettyUtil; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import com.hypixel.hytale.server.core.plugin.PluginManager; +import io.netty.channel.Channel; +import io.netty.handler.codec.quic.QuicStreamChannel; +import java.security.SecureRandom; +import java.time.Duration; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InitialPacketHandler extends PacketHandler { + private static final int MAX_REFERRAL_DATA_SIZE = 4096; + @Nullable + public static AuthenticationPacketHandler.AuthHandlerSupplier EDITOR_PACKET_HANDLER_SUPPLIER; + private boolean receivedConnect; + + public InitialPacketHandler(@Nonnull Channel channel) { + super(channel, null); + } + + @Nonnull + @Override + public String getIdentifier() { + return "{Initial(" + NettyUtil.formatRemoteAddress(this.channel) + ")}"; + } + + @Override + public void registered0(PacketHandler oldHandler) { + Duration initialTimeout = HytaleServer.get().getConfig().getConnectionTimeouts().getInitialTimeout(); + this.setTimeout("initial", () -> !this.registered, initialTimeout.toMillis(), TimeUnit.MILLISECONDS); + PacketHandler.logConnectionTimings(this.channel, "Registered", Level.FINE); + } + + @Override + public void accept(@Nonnull Packet packet) { + if (packet.getId() == 0) { + this.handle((Connect)packet); + } else if (packet.getId() == 1) { + this.handle((Disconnect)packet); + } else { + this.disconnect("Protocol error: unexpected packet " + packet.getId()); + } + } + + @Override + public void disconnect(@Nonnull String message) { + if (this.receivedConnect) { + super.disconnect(message); + } else { + HytaleLogger.getLogger().at(Level.INFO).log("Silently disconnecting %s because no Connect packet!", NettyUtil.formatRemoteAddress(this.channel)); + ProtocolUtil.closeConnection(this.channel); + } + } + + public void handle(@Nonnull Connect packet) { + this.receivedConnect = true; + this.clearTimeout(); + PacketHandler.logConnectionTimings(this.channel, "Connect", Level.FINE); + String clientProtocolHash = packet.protocolHash; + if (clientProtocolHash.length() > 64) { + this.disconnect("Invalid Protocol Hash! " + clientProtocolHash.length()); + } else { + String expectedHash = "6708f121966c1c443f4b0eb525b2f81d0a8dc61f5003a692a8fa157e5e02cea9"; + if (!clientProtocolHash.equals(expectedHash)) { + this.disconnect("Incompatible protocol!\nServer: " + expectedHash + "\nClient: " + clientProtocolHash); + } else if (HytaleServer.get().isShuttingDown()) { + this.disconnect("Server is shutting down!"); + } else if (!HytaleServer.get().isBooted()) { + this.disconnect("Server is booting up! Please try again in a moment. [" + PluginManager.get().getState() + "]"); + } else { + ProtocolVersion protocolVersion = new ProtocolVersion(clientProtocolHash); + String language = packet.language; + if (language == null) { + language = "en-US"; + } + + boolean isTcpConnection = !(this.channel instanceof QuicStreamChannel); + if (isTcpConnection) { + HytaleLogger.getLogger() + .at(Level.INFO) + .log("TCP connection from %s - only insecure auth supported", NettyUtil.formatRemoteAddress(this.channel)); + } + + if (packet.uuid == null) { + this.disconnect("Missing UUID"); + } else if (packet.username != null && !packet.username.isEmpty()) { + if (packet.referralData != null && packet.referralData.length > 4096) { + HytaleLogger.getLogger() + .at(Level.WARNING) + .log("Rejecting connection from %s - referral data too large: %d bytes (max: %d)", packet.username, packet.referralData.length, 4096); + this.disconnect("Referral data exceeds maximum size of 4096 bytes"); + } else { + boolean hasIdentityToken = packet.identityToken != null && !packet.identityToken.isEmpty(); + boolean isEditorClient = packet.clientType == ClientType.Editor; + Options.AuthMode authMode = Options.getOptionSet().valueOf(Options.AUTH_MODE); + if (hasIdentityToken && authMode == Options.AuthMode.AUTHENTICATED) { + if (isTcpConnection) { + HytaleLogger.getLogger() + .at(Level.WARNING) + .log("Rejecting authenticated connection from %s - TCP only supports insecure auth", NettyUtil.formatRemoteAddress(this.channel)); + this.disconnect("TCP connections only support insecure authentication. Use QUIC for authenticated connections."); + return; + } + + AuthenticationPacketHandler.AuthHandlerSupplier supplier = isEditorClient ? EDITOR_PACKET_HANDLER_SUPPLIER : SetupPacketHandler::new; + if (isEditorClient && supplier == null) { + this.disconnect("Editor isn't supported on this server!"); + return; + } + + HytaleLogger.getLogger() + .at(Level.INFO) + .log("Starting authenticated flow for %s (%s) from %s", packet.username, packet.uuid, NettyUtil.formatRemoteAddress(this.channel)); + NettyUtil.setChannelHandler( + this.channel, + new AuthenticationPacketHandler( + this.channel, + protocolVersion, + language, + supplier, + packet.clientType, + packet.identityToken, + packet.uuid, + packet.username, + packet.referralData, + packet.referralSource + ) + ); + } else { + if (authMode == Options.AuthMode.AUTHENTICATED) { + HytaleLogger.getLogger() + .at(Level.WARNING) + .log( + "Rejecting development connection from %s - server requires authentication (auth-mode=%s)", + NettyUtil.formatRemoteAddress(this.channel), + authMode + ); + this.disconnect("This server requires authentication!"); + return; + } + + if (authMode == Options.AuthMode.OFFLINE) { + if (!Constants.SINGLEPLAYER) { + HytaleLogger.getLogger() + .at(Level.WARNING) + .log("Rejecting connection from %s - offline mode is only valid in singleplayer", NettyUtil.formatRemoteAddress(this.channel)); + this.disconnect("Offline mode is only available in singleplayer."); + return; + } + + if (!SingleplayerModule.isOwner(null, packet.uuid)) { + HytaleLogger.getLogger() + .at(Level.WARNING) + .log( + "Rejecting connection from %s (%s) - offline mode only allows the world owner (%s)", + packet.username, + packet.uuid, + SingleplayerModule.getUuid() + ); + this.disconnect("This world is in offline mode and only the owner can connect."); + return; + } + } + + HytaleLogger.getLogger() + .at(Level.INFO) + .log("Starting development flow for %s (%s) from %s", packet.username, packet.uuid, NettyUtil.formatRemoteAddress(this.channel)); + byte[] passwordChallenge = this.generatePasswordChallengeIfNeeded(packet.uuid); + this.write(new ConnectAccept(passwordChallenge)); + PasswordPacketHandler.SetupHandlerSupplier setupSupplier = isEditorClient && EDITOR_PACKET_HANDLER_SUPPLIER != null + ? (ch, pv, lang, auth) -> EDITOR_PACKET_HANDLER_SUPPLIER.create(ch, pv, lang, auth) + : SetupPacketHandler::new; + NettyUtil.setChannelHandler( + this.channel, + new PasswordPacketHandler( + this.channel, + protocolVersion, + language, + packet.uuid, + packet.username, + packet.referralData, + packet.referralSource, + passwordChallenge, + setupSupplier + ) + ); + } + } + } else { + this.disconnect("Missing username"); + } + } + } + } + + private byte[] generatePasswordChallengeIfNeeded(UUID playerUuid) { + String password = HytaleServer.get().getConfig().getPassword(); + if (password != null && !password.isEmpty()) { + if (Constants.SINGLEPLAYER) { + UUID ownerUuid = SingleplayerModule.getUuid(); + if (ownerUuid != null && ownerUuid.equals(playerUuid)) { + return null; + } + } + + byte[] challenge = new byte[32]; + new SecureRandom().nextBytes(challenge); + return challenge; + } else { + return null; + } + } + + public void handle(@Nonnull Disconnect packet) { + this.disconnectReason.setClientDisconnectType(packet.type); + HytaleLogger.getLogger().at(Level.WARNING).log("Disconnecting %s - Sent disconnect packet???", NettyUtil.formatRemoteAddress(this.channel)); + ProtocolUtil.closeApplicationConnection(this.channel); + } +} diff --git a/src/com/hypixel/hytale/server/core/io/handlers/SetupPacketHandler.java b/src/com/hypixel/hytale/server/core/io/handlers/SetupPacketHandler.java new file mode 100644 index 0000000..c302d56 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/handlers/SetupPacketHandler.java @@ -0,0 +1,322 @@ +package com.hypixel.hytale.server.core.io.handlers; + +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.protocol.Asset; +import com.hypixel.hytale.protocol.HostAddress; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.netty.ProtocolUtil; +import com.hypixel.hytale.protocol.packets.auth.ClientReferral; +import com.hypixel.hytale.protocol.packets.connection.Disconnect; +import com.hypixel.hytale.protocol.packets.connection.DisconnectType; +import com.hypixel.hytale.protocol.packets.interface_.ServerInfo; +import com.hypixel.hytale.protocol.packets.setup.PlayerOptions; +import com.hypixel.hytale.protocol.packets.setup.RequestAssets; +import com.hypixel.hytale.protocol.packets.setup.ViewRadius; +import com.hypixel.hytale.protocol.packets.setup.WorldLoadFinished; +import com.hypixel.hytale.protocol.packets.setup.WorldLoadProgress; +import com.hypixel.hytale.protocol.packets.setup.WorldSettings; +import com.hypixel.hytale.server.core.Constants; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.HytaleServerConfig; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.AssetRegistryLoader; +import com.hypixel.hytale.server.core.asset.common.CommonAssetModule; +import com.hypixel.hytale.server.core.asset.common.PlayerCommonAssets; +import com.hypixel.hytale.server.core.asset.common.events.SendCommonAssetsEvent; +import com.hypixel.hytale.server.core.auth.PlayerAuthentication; +import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule; +import com.hypixel.hytale.server.core.event.events.player.PlayerSetupConnectEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerSetupDisconnectEvent; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.io.ProtocolVersion; +import com.hypixel.hytale.server.core.io.netty.NettyUtil; +import com.hypixel.hytale.server.core.modules.i18n.I18nModule; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.DumpUtil; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class SetupPacketHandler extends GenericConnectionPacketHandler { + private final UUID uuid; + private final String username; + private final byte[] referralData; + private final HostAddress referralSource; + private PlayerCommonAssets assets; + private boolean receivedRequest; + private int clientViewRadiusChunks = 6; + + public SetupPacketHandler(@Nonnull Channel channel, @Nonnull ProtocolVersion protocolVersion, String language, UUID uuid, String username) { + this(channel, protocolVersion, language, uuid, username, null, null); + } + + public SetupPacketHandler( + @Nonnull Channel channel, + @Nonnull ProtocolVersion protocolVersion, + String language, + UUID uuid, + String username, + byte[] referralData, + HostAddress referralSource + ) { + super(channel, protocolVersion, language); + this.uuid = uuid; + this.username = username; + this.referralData = referralData; + this.referralSource = referralSource; + this.auth = null; + if (referralData != null && referralData.length > 0) { + HytaleLogger.getLogger() + .at(Level.INFO) + .log( + "Player %s connecting with %d bytes of referral data from %s:%d (unauthenticated - plugins must validate!)", + username, + referralData.length, + referralSource != null ? referralSource.host : "unknown", + referralSource != null ? referralSource.port : 0 + ); + } + } + + public SetupPacketHandler(@Nonnull Channel channel, @Nonnull ProtocolVersion protocolVersion, String language, @Nonnull PlayerAuthentication auth) { + super(channel, protocolVersion, language); + this.uuid = auth.getUuid(); + this.username = auth.getUsername(); + this.auth = auth; + this.referralData = auth.getReferralData(); + this.referralSource = auth.getReferralSource(); + if (this.referralData != null && this.referralData.length > 0) { + HytaleLogger.getLogger() + .at(Level.INFO) + .log( + "Player %s connecting with %d bytes of referral data from %s:%d (authenticated)", + this.username, + this.referralData.length, + this.referralSource != null ? this.referralSource.host : "unknown", + this.referralSource != null ? this.referralSource.port : 0 + ); + } + } + + @Nonnull + @Override + public String getIdentifier() { + return "{Setup(" + + NettyUtil.formatRemoteAddress(this.channel) + + "), " + + this.username + + ", " + + this.uuid + + ", " + + (this.auth != null ? "SECURE" : "INSECURE") + + "}"; + } + + @Override + public void registered0(@Nonnull PacketHandler oldHandler) { + this.setTimeout("send-world-settings", () -> this.assets != null, 1L, TimeUnit.SECONDS); + PlayerSetupConnectEvent event = HytaleServer.get() + .getEventBus() + .dispatchFor(PlayerSetupConnectEvent.class) + .dispatch(new PlayerSetupConnectEvent(this, this.username, this.uuid, this.auth, this.referralData, this.referralSource)); + if (event.isCancelled()) { + this.disconnect(event.getReason()); + } else { + ClientReferral clientReferral = event.getClientReferral(); + if (clientReferral != null) { + this.writeNoCache(clientReferral); + } else { + HytaleServerConfig serverConfig = HytaleServer.get().getConfig(); + boolean enableCompression; + if (!serverConfig.isLocalCompressionEnabled()) { + enableCompression = !oldHandler.isLocalConnection(); + } else { + enableCompression = true; + } + + oldHandler.setCompressionEnabled(enableCompression); + PlayerRef otherPlayer = Universe.get().getPlayer(this.uuid); + if (otherPlayer != null) { + HytaleLogger.getLogger().at(Level.INFO).log("Found match of player %s on %s", this.uuid, otherPlayer.getUsername()); + Channel otherPlayerChannel = otherPlayer.getPacketHandler().getChannel(); + if (!NettyUtil.isFromSameOrigin(otherPlayerChannel, this.channel)) { + this.disconnect("You are already logged in on that account!"); + otherPlayer.sendMessage(Message.translation("server.io.setuppackethandler.otherLoginAttempt")); + return; + } + + Ref reference = otherPlayer.getReference(); + if (reference != null) { + World world = reference.getStore().getExternalData().getWorld(); + if (world != null) { + CompletableFuture removalFuture = new CompletableFuture<>(); + world.execute(() -> { + otherPlayer.getPacketHandler().disconnect("You logged in again with the account!"); + world.execute(() -> removalFuture.complete(null)); + }); + removalFuture.join(); + } else { + otherPlayer.getPacketHandler().disconnect("You logged in again with the account!"); + } + } + } + + PacketHandler.logConnectionTimings(this.channel, "Load Player Config", Level.FINE); + WorldSettings worldSettings = new WorldSettings(); + worldSettings.worldHeight = 320; + Asset[] requiredAssets = CommonAssetModule.get().getRequiredAssets(); + this.assets = new PlayerCommonAssets(requiredAssets); + worldSettings.requiredAssets = requiredAssets; + this.write(worldSettings); + this.write(new ServerInfo(HytaleServer.get().getServerName(), serverConfig.getMotd(), serverConfig.getMaxPlayers())); + this.setTimeout("receive-assets-request", () -> this.receivedRequest, 120L, TimeUnit.SECONDS); + } + } + } + + @Override + public void accept(@Nonnull Packet packet) { + switch (packet.getId()) { + case 1: + this.handle((Disconnect)packet); + break; + case 23: + this.handle((RequestAssets)packet); + break; + case 32: + this.handle((ViewRadius)packet); + break; + case 33: + this.handle((PlayerOptions)packet); + break; + default: + this.disconnect("Protocol error: unexpected packet " + packet.getId()); + } + } + + @Override + public void closed(ChannelHandlerContext ctx) { + super.closed(ctx); + IEventDispatcher dispatcher = HytaleServer.get() + .getEventBus() + .dispatchFor(PlayerSetupDisconnectEvent.class); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new PlayerSetupDisconnectEvent(this.username, this.uuid, this.auth, this.disconnectReason)); + } + + if (Constants.SINGLEPLAYER) { + if (Universe.get().getPlayerCount() == 0) { + HytaleLogger.getLogger().at(Level.INFO).log("No players left on singleplayer server shutting down!"); + HytaleServer.get().shutdownServer(); + } else if (SingleplayerModule.isOwner(this.auth, this.uuid)) { + HytaleLogger.getLogger().at(Level.INFO).log("Owner left the singleplayer server shutting down!"); + Universe.get().getPlayers().forEach(p -> p.getPacketHandler().disconnect(this.username + " left! Shutting down singleplayer world!")); + HytaleServer.get().shutdownServer(); + } + } + } + + public void handle(@Nonnull Disconnect packet) { + this.disconnectReason.setClientDisconnectType(packet.type); + HytaleLogger.getLogger() + .at(Level.INFO) + .log( + "%s - %s at %s left with reason: %s - %s", this.uuid, this.username, NettyUtil.formatRemoteAddress(this.channel), packet.type.name(), packet.reason + ); + ProtocolUtil.closeApplicationConnection(this.channel); + if (packet.type == DisconnectType.Crash + && Constants.SINGLEPLAYER + && (Universe.get().getPlayerCount() == 0 || SingleplayerModule.isOwner(this.auth, this.uuid))) { + DumpUtil.dump(true, false); + } + } + + public void handle(@Nonnull RequestAssets packet) { + if (this.receivedRequest) { + throw new IllegalArgumentException("Received duplicate RequestAssets!"); + } else { + this.receivedRequest = true; + PacketHandler.logConnectionTimings(this.channel, "Request Assets", Level.FINE); + CompletableFuture future = CompletableFutureUtil._catch( + HytaleServer.get() + .getEventBus() + .dispatchForAsync(SendCommonAssetsEvent.class) + .dispatch(new SendCommonAssetsEvent(this, packet.assets)) + .thenAccept(event -> { + if (this.channel.isActive()) { + PacketHandler.logConnectionTimings(this.channel, "Send Common Assets", Level.FINE); + this.assets.sent(event.getRequestedAssets()); + AssetRegistryLoader.sendAssets(this); + I18nModule.get().sendTranslations(this, this.language); + PacketHandler.logConnectionTimings(this.channel, "Send Config Assets", Level.FINE); + this.write(new WorldLoadProgress("Loading world...", 0, 0)); + this.write(new WorldLoadFinished()); + } + }) + .exceptionally(throwable -> { + if (!this.channel.isActive()) { + return null; + } else { + this.disconnect("An exception occurred while trying to login!"); + throw new RuntimeException("Exception when player was joining", throwable); + } + }) + ); + this.setTimeout("send-assets", () -> future.isDone() || !future.cancel(true), 120L, TimeUnit.SECONDS); + } + } + + public void handle(@Nonnull ViewRadius packet) { + this.clientViewRadiusChunks = MathUtil.ceil(packet.value / 32.0F); + } + + public void handle(@Nonnull PlayerOptions packet) { + if (!this.receivedRequest) { + throw new IllegalArgumentException("Hasn't received RequestAssets yet!"); + } else { + PacketHandler.logConnectionTimings(this.channel, "Player Options", Level.FINE); + if (this.channel.isActive()) { + if (packet.skin != null) { + try { + CosmeticsModule.get().validateSkin(packet.skin); + } catch (CosmeticsModule.InvalidSkinException var3) { + this.disconnect("Invalid skin! " + var3.getMessage()); + return; + } + } + + CompletableFuture future = CompletableFutureUtil._catch( + Universe.get() + .addPlayer(this.channel, this.language, this.protocolVersion, this.uuid, this.username, this.auth, this.clientViewRadiusChunks, packet.skin) + .thenAccept(player -> { + if (this.channel.isActive()) { + PacketHandler.logConnectionTimings(this.channel, "Add To Universe", Level.FINE); + this.clearTimeout(); + } + }) + .exceptionally(throwable -> { + if (!this.channel.isActive()) { + return null; + } else { + this.disconnect("An exception occurred when adding to the universe!"); + throw new RuntimeException("Exception when player adding to universe", throwable); + } + }) + ); + this.setTimeout("add-to-universe", () -> future.isDone() || !future.cancel(true), 60L, TimeUnit.SECONDS); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/io/handlers/SubPacketHandler.java b/src/com/hypixel/hytale/server/core/io/handlers/SubPacketHandler.java new file mode 100644 index 0000000..7391cb6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/handlers/SubPacketHandler.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.server.core.io.handlers; + +public interface SubPacketHandler { + void registerHandlers(); +} diff --git a/src/com/hypixel/hytale/server/core/io/handlers/game/GamePacketHandler.java b/src/com/hypixel/hytale/server/core/io/handlers/game/GamePacketHandler.java new file mode 100644 index 0000000..92dd403 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/handlers/game/GamePacketHandler.java @@ -0,0 +1,849 @@ +package com.hypixel.hytale.server.core.io.handlers.game; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockRotation; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.HostAddress; +import com.hypixel.hytale.protocol.io.netty.ProtocolUtil; +import com.hypixel.hytale.protocol.packets.camera.RequestFlyCameraMode; +import com.hypixel.hytale.protocol.packets.camera.SetFlyCameraMode; +import com.hypixel.hytale.protocol.packets.connection.Disconnect; +import com.hypixel.hytale.protocol.packets.connection.Pong; +import com.hypixel.hytale.protocol.packets.entities.MountMovement; +import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChain; +import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChains; +import com.hypixel.hytale.protocol.packets.interface_.ChatMessage; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageEvent; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.protocol.packets.interface_.SetPage; +import com.hypixel.hytale.protocol.packets.interface_.UpdateLanguage; +import com.hypixel.hytale.protocol.packets.machinima.RequestMachinimaActorModel; +import com.hypixel.hytale.protocol.packets.machinima.SetMachinimaActorModel; +import com.hypixel.hytale.protocol.packets.machinima.UpdateMachinimaScene; +import com.hypixel.hytale.protocol.packets.player.ClientMovement; +import com.hypixel.hytale.protocol.packets.player.ClientPlaceBlock; +import com.hypixel.hytale.protocol.packets.player.ClientReady; +import com.hypixel.hytale.protocol.packets.player.MouseInteraction; +import com.hypixel.hytale.protocol.packets.player.RemoveMapMarker; +import com.hypixel.hytale.protocol.packets.player.SyncPlayerPreferences; +import com.hypixel.hytale.protocol.packets.serveraccess.SetServerAccess; +import com.hypixel.hytale.protocol.packets.serveraccess.UpdateServerAccess; +import com.hypixel.hytale.protocol.packets.setup.RequestAssets; +import com.hypixel.hytale.protocol.packets.setup.ViewRadius; +import com.hypixel.hytale.protocol.packets.window.ClientOpenWindow; +import com.hypixel.hytale.protocol.packets.window.CloseWindow; +import com.hypixel.hytale.protocol.packets.window.SendWindowAction; +import com.hypixel.hytale.protocol.packets.window.UpdateWindow; +import com.hypixel.hytale.protocol.packets.world.SetPaused; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.protocol.packets.worldmap.TeleportToWorldMapMarker; +import com.hypixel.hytale.protocol.packets.worldmap.TeleportToWorldMapPosition; +import com.hypixel.hytale.protocol.packets.worldmap.UpdateWorldMapVisible; +import com.hypixel.hytale.server.core.Constants; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.HytaleServerConfig; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.NameMatching; +import com.hypixel.hytale.server.core.asset.common.CommonAssetModule; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.auth.PlayerAuthentication; +import com.hypixel.hytale.server.core.command.system.CommandManager; +import com.hypixel.hytale.server.core.console.ConsoleModule; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerWorldData; +import com.hypixel.hytale.server.core.entity.entities.player.pages.PageManager; +import com.hypixel.hytale.server.core.entity.entities.player.windows.ValidatedWindow; +import com.hypixel.hytale.server.core.entity.entities.player.windows.Window; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.event.events.player.PlayerChatEvent; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.io.ProtocolVersion; +import com.hypixel.hytale.server.core.io.ServerManager; +import com.hypixel.hytale.server.core.io.handlers.GenericPacketHandler; +import com.hypixel.hytale.server.core.io.handlers.IPacketHandler; +import com.hypixel.hytale.server.core.io.handlers.SubPacketHandler; +import com.hypixel.hytale.server.core.io.netty.NettyUtil; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerCreativeSettings; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerInput; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings; +import com.hypixel.hytale.server.core.modules.entity.teleport.PendingTeleport; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.modules.interaction.BlockPlaceUtils; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.MessageUtil; +import com.hypixel.hytale.server.core.util.PositionUtil; +import com.hypixel.hytale.server.core.util.ValidateUtil; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.Deque; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Supplier; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class GamePacketHandler extends GenericPacketHandler implements IPacketHandler { + private static final double RELATIVE_POSITION_DELTA_SCALE = 10000.0; + private PlayerRef playerRef; + @Deprecated + private Player playerComponent; + @Nonnull + private final Deque interactionPacketQueue = new ConcurrentLinkedDeque<>(); + + public GamePacketHandler(@Nonnull Channel channel, @Nonnull ProtocolVersion protocolVersion, @Nonnull PlayerAuthentication auth) { + super(channel, protocolVersion); + this.auth = auth; + ServerManager.get().populateSubPacketHandlers(this); + this.registerHandlers(); + } + + @Nonnull + public Deque getInteractionPacketQueue() { + return this.interactionPacketQueue; + } + + @Nonnull + @Override + public PlayerRef getPlayerRef() { + return this.playerRef; + } + + public void setPlayerRef(@Nonnull PlayerRef playerRef, @Nonnull Player playerComponent) { + this.playerRef = playerRef; + this.playerComponent = playerComponent; + } + + @Nonnull + @Override + public String getIdentifier() { + return "{Playing(" + + NettyUtil.formatRemoteAddress(this.channel) + + "), " + + (this.playerRef != null ? this.playerRef.getUuid() + ", " + this.playerRef.getUsername() : "null player") + + "}"; + } + + protected void registerHandlers() { + this.registerHandler(1, p -> this.handle((Disconnect)p)); + this.registerHandler(3, p -> this.handlePong((Pong)p)); + this.registerHandler(108, p -> this.handle((ClientMovement)p)); + this.registerHandler(211, p -> this.handle((ChatMessage)p)); + this.registerHandler(23, p -> this.handle((RequestAssets)p)); + this.registerHandler(219, p -> this.handle((CustomPageEvent)p)); + this.registerHandler(32, p -> this.handle((ViewRadius)p)); + this.registerHandler(232, p -> this.handle((UpdateLanguage)p)); + this.registerHandler(111, p -> this.handle((MouseInteraction)p)); + this.registerHandler(251, p -> this.handle((UpdateServerAccess)p)); + this.registerHandler(252, p -> this.handle((SetServerAccess)p)); + this.registerHandler(204, p -> this.handle((ClientOpenWindow)p)); + this.registerHandler(203, p -> this.handle((SendWindowAction)p)); + this.registerHandler(202, p -> this.handle((CloseWindow)p)); + this.registerHandler(260, p -> this.handle((RequestMachinimaActorModel)p)); + this.registerHandler(262, p -> this.handle((UpdateMachinimaScene)p)); + this.registerHandler(105, p -> this.handle((ClientReady)p)); + this.registerHandler(166, p -> this.handle((MountMovement)p)); + this.registerHandler(116, p -> this.handle((SyncPlayerPreferences)p)); + this.registerHandler(117, p -> this.handle((ClientPlaceBlock)p)); + this.registerHandler(119, p -> this.handle((RemoveMapMarker)p)); + this.registerHandler(243, p -> this.handle((UpdateWorldMapVisible)p)); + this.registerHandler(244, p -> this.handle((TeleportToWorldMapMarker)p)); + this.registerHandler(245, p -> this.handle((TeleportToWorldMapPosition)p)); + this.registerHandler(290, p -> this.handle((SyncInteractionChains)p)); + this.registerHandler(158, p -> this.handle((SetPaused)p)); + this.registerHandler(282, p -> this.handle((RequestFlyCameraMode)p)); + this.packetHandlers.forEach(SubPacketHandler::registerHandlers); + } + + @Override + public void closed(ChannelHandlerContext ctx) { + super.closed(ctx); + Universe.get().removePlayer(this.playerRef); + } + + @Override + public void disconnect(@Nonnull String message) { + this.disconnectReason.setServerDisconnectReason(message); + if (this.playerRef != null) { + HytaleLogger.getLogger() + .at(Level.INFO) + .log("Disconnecting %s at %s with the message: %s", this.playerRef.getUsername(), NettyUtil.formatRemoteAddress(this.channel), message); + this.disconnect0(message); + Universe.get().removePlayer(this.playerRef); + } else { + super.disconnect(message); + } + } + + public void handle(@Nonnull Disconnect packet) { + this.disconnectReason.setClientDisconnectType(packet.type); + HytaleLogger.getLogger() + .at(Level.INFO) + .log( + "%s - %s at %s left with reason: %s - %s", + this.playerRef.getUuid(), + this.playerRef.getUsername(), + NettyUtil.formatRemoteAddress(this.channel), + packet.type.name(), + packet.reason + ); + ProtocolUtil.closeApplicationConnection(this.channel); + } + + public void handle(@Nonnull MouseInteraction packet) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + InteractionModule.get().doMouseInteraction(ref, store, packet, playerComponent, this.playerRef); + }); + } + } + + public void handle(@Nonnull ClientMovement packet) { + if (packet.absolutePosition != null && !ValidateUtil.isSafePosition(packet.absolutePosition)) { + this.disconnect("Sent impossible position data!"); + } else if ((packet.bodyOrientation == null || ValidateUtil.isSafeDirection(packet.bodyOrientation)) + && (packet.lookOrientation == null || ValidateUtil.isSafeDirection(packet.lookOrientation))) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + if (ref.isValid()) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (!playerComponent.isWaitingForClientReady()) { + PlayerInput playerInputComponent = store.getComponent(ref, PlayerInput.getComponentType()); + if (playerInputComponent != null) { + if (packet.movementStates != null) { + playerInputComponent.queue(new PlayerInput.SetMovementStates(packet.movementStates)); + } + + if (packet.velocity != null) { + playerInputComponent.queue(new PlayerInput.SetClientVelocity(packet.velocity)); + } + + PendingTeleport pendingTeleport = store.getComponent(ref, PendingTeleport.getComponentType()); + if (pendingTeleport != null) { + if (packet.teleportAck == null) { + return; + } + + switch (pendingTeleport.validate(packet.teleportAck.teleportId, packet.absolutePosition)) { + case OK: + default: + if (!pendingTeleport.isEmpty()) { + return; + } + + store.removeComponent(ref, PendingTeleport.getComponentType()); + break; + case INVALID_ID: + this.disconnect("Incorrect teleportId"); + return; + case INVALID_POSITION: + this.disconnect("Invalid teleport"); + return; + } + } + + if (packet.mountedTo != 0) { + if (packet.mountedTo != playerInputComponent.getMountId()) { + return; + } + + if (packet.riderMovementStates != null) { + playerInputComponent.queue(new PlayerInput.SetRiderMovementStates(packet.riderMovementStates)); + } + } + + if (packet.bodyOrientation != null) { + playerInputComponent.queue(new PlayerInput.SetBody(packet.bodyOrientation)); + } + + if (packet.lookOrientation != null) { + playerInputComponent.queue(new PlayerInput.SetHead(packet.lookOrientation)); + } + + if (packet.wishMovement != null) { + playerInputComponent.queue(new PlayerInput.WishMovement(packet.wishMovement.x, packet.wishMovement.y, packet.wishMovement.z)); + } + + if (packet.absolutePosition != null) { + playerInputComponent.queue( + new PlayerInput.AbsoluteMovement(packet.absolutePosition.x, packet.absolutePosition.y, packet.absolutePosition.z) + ); + } else if (packet.relativePosition != null + && ( + packet.relativePosition.x != 0 + || packet.relativePosition.y != 0 + || packet.relativePosition.z != 0 + || packet.movementStates != null + )) { + playerInputComponent.queue( + new PlayerInput.RelativeMovement( + packet.relativePosition.x / 10000.0, packet.relativePosition.y / 10000.0, packet.relativePosition.z / 10000.0 + ) + ); + } + } + } + } + } + ); + } + } else { + this.disconnect("Sent impossible orientation data!"); + } + } + + public void handle(@Nonnull ChatMessage packet) { + if (packet.message != null && !packet.message.isEmpty()) { + String message = packet.message; + char firstChar = message.charAt(0); + if (firstChar == '/') { + CommandManager.get().handleCommand(this.playerComponent, message.substring(1)); + } else if (firstChar == '.') { + this.playerRef.sendMessage(Message.translation("server.io.gamepackethandler.localCommandDenied").param("msg", message)); + } else { + Ref ref = this.playerRef.getReference(); + if (ref == null || !ref.isValid()) { + return; + } + + UUID playerUUID = this.playerRef.getUuid(); + List targetPlayerRefs = new ObjectArrayList<>(Universe.get().getPlayers()); + targetPlayerRefs.removeIf(targetPlayerRef -> targetPlayerRef.getHiddenPlayersManager().isPlayerHidden(playerUUID)); + HytaleServer.get() + .getEventBus() + .dispatchForAsync(PlayerChatEvent.class) + .dispatch(new PlayerChatEvent(this.playerRef, targetPlayerRefs, message)) + .whenComplete( + (playerChatEvent, throwable) -> { + if (throwable != null) { + HytaleLogger.getLogger() + .at(Level.SEVERE) + .withCause(throwable) + .log("An error occurred while dispatching PlayerChatEvent for player %s", this.playerRef.getUsername()); + } else if (!playerChatEvent.isCancelled()) { + Message sentMessage = playerChatEvent.getFormatter().format(this.playerRef, playerChatEvent.getContent()); + HytaleLogger.getLogger().at(Level.INFO).log(MessageUtil.toAnsiString(sentMessage).toAnsi(ConsoleModule.get().getTerminal())); + + for (PlayerRef targetPlayerRef : playerChatEvent.getTargets()) { + targetPlayerRef.sendMessage(sentMessage); + } + } + } + ); + } + } else { + this.disconnect("Invalid chat message packet! Message was empty."); + } + } + + public void handle(@Nonnull RequestAssets packet) { + CommonAssetModule.get().sendAssetsToPlayer(this, packet.assets, true); + } + + public void handle(@Nonnull CustomPageEvent packet) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PageManager pageManager = playerComponent.getPageManager(); + pageManager.handleEvent(ref, store, packet); + }); + } else { + this.playerRef.getPacketHandler().writeNoCache(new SetPage(Page.None, true)); + } + } + + public void handle(@Nonnull ViewRadius packet) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + EntityTrackerSystems.EntityViewer entityViewerComponent = store.getComponent(ref, EntityTrackerSystems.EntityViewer.getComponentType()); + + assert entityViewerComponent != null; + + int viewRadiusChunks = MathUtil.ceil(packet.value / 32.0F); + playerComponent.setClientViewRadius(viewRadiusChunks); + entityViewerComponent.viewRadiusBlocks = playerComponent.getViewRadius() * 32; + }); + } + } + + public void handle(@Nonnull UpdateLanguage packet) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + this.playerRef.setLanguage(packet.language); + } + } + + protected void handle(@Nonnull ClientOpenWindow packet) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + Supplier supplier = Window.CLIENT_REQUESTABLE_WINDOW_TYPES.get(packet.type); + if (supplier == null) { + throw new RuntimeException("Unable to process ClientOpenWindow packet. Window type is not supported!"); + } else { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + UpdateWindow updateWindowPacket = playerComponent.getWindowManager().clientOpenWindow(supplier.get()); + if (updateWindowPacket != null) { + this.writeNoCache(updateWindowPacket); + } + }); + } + } + } + + public void handle(@Nonnull SendWindowAction packet) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Window window = playerComponent.getWindowManager().getWindow(packet.id); + if (window != null) { + if (window instanceof ValidatedWindow && !((ValidatedWindow)window).validate()) { + window.close(); + } else { + window.handleAction(this.playerRef.getReference(), store, packet.action); + } + } + }); + } + } + + public void handle(@Nonnull SyncPlayerPreferences packet) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + ComponentType componentType = EntityModule.get().getPlayerSettingsComponentType(); + store.putComponent( + ref, + componentType, + new PlayerSettings( + packet.showEntityMarkers, + packet.armorItemsPreferredPickupLocation, + packet.weaponAndToolItemsPreferredPickupLocation, + packet.usableItemsItemsPreferredPickupLocation, + packet.solidBlockItemsPreferredPickupLocation, + packet.miscItemsPreferredPickupLocation, + new PlayerCreativeSettings(packet.allowNPCDetection, packet.respondToHit) + ) + ); + } + ); + } + } + + public void handle(@Nonnull ClientPlaceBlock packet) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Inventory inventory = playerComponent.getInventory(); + Vector3i targetBlock = new Vector3i(packet.position.x, packet.position.y, packet.position.z); + BlockRotation blockRotation = new BlockRotation(packet.rotation.rotationYaw, packet.rotation.rotationPitch, packet.rotation.rotationRoll); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + if (transformComponent != null && playerComponent.getGameMode() != GameMode.Creative) { + Vector3d position = transformComponent.getPosition(); + Vector3d blockCenter = new Vector3d(targetBlock.x + 0.5, targetBlock.y + 0.5, targetBlock.z + 0.5); + if (position.distanceSquaredTo(blockCenter) > 36.0) { + return; + } + } + + Store chunkStore = world.getChunkStore().getStore(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z); + Ref chunkReference = chunkStore.getExternalData().getChunkReference(chunkIndex); + if (chunkReference != null) { + BlockChunk blockChunk = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType()); + if (blockChunk != null) { + BlockSection section = blockChunk.getSectionAtBlockY(targetBlock.y); + if (section != null) { + ItemStack itemInHand = playerComponent.getInventory().getItemInHand(); + if (itemInHand == null) { + section.invalidateBlock(targetBlock.x, targetBlock.y, targetBlock.z); + } else { + String heldBlockKey = itemInHand.getBlockKey(); + if (heldBlockKey == null) { + section.invalidateBlock(targetBlock.x, targetBlock.y, targetBlock.z); + } else { + if (packet.placedBlockId != -1) { + String clientPlacedBlockTypeKey = BlockType.getAssetMap().getAsset(packet.placedBlockId).getId(); + BlockType heldBlockType = BlockType.getAssetMap().getAsset(heldBlockKey); + if (heldBlockType != null && BlockPlaceUtils.canPlaceBlock(heldBlockType, clientPlacedBlockTypeKey)) { + heldBlockKey = clientPlacedBlockTypeKey; + } + } + + BlockPlaceUtils.placeBlock( + ref, + itemInHand, + heldBlockKey, + inventory.getHotbar(), + Vector3i.ZERO, + targetBlock, + blockRotation, + inventory, + inventory.getActiveHotbarSlot(), + playerComponent.getGameMode() != GameMode.Creative, + chunkReference, + chunkStore, + store + ); + } + } + } + } + } + } + ); + } + } + + public void handle(@Nonnull RemoveMapMarker packet) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerWorldData perWorldData = playerComponent.getPlayerConfigData().getPerWorldData(world.getName()); + perWorldData.removeLastDeath(packet.markerId); + }); + } + } + + public void handle(@Nonnull CloseWindow packet) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getWindowManager().closeWindow(packet.id); + }); + } + } + + public void handle(@Nonnull UpdateServerAccess packet) { + if (!Constants.SINGLEPLAYER) { + throw new IllegalArgumentException("UpdateServerAccess can only be used in singleplayer!"); + } else if (!SingleplayerModule.isOwner(this.playerRef)) { + throw new IllegalArgumentException("UpdateServerAccess can only be by the owner of the singleplayer world!"); + } else { + List publicAddresses = new CopyOnWriteArrayList<>(); + + for (HostAddress host : packet.hosts) { + publicAddresses.add(InetSocketAddress.createUnresolved(host.host, host.port & '\uffff')); + } + + SingleplayerModule singleplayerModule = SingleplayerModule.get(); + singleplayerModule.setPublicAddresses(publicAddresses); + singleplayerModule.updateAccess(packet.access); + } + } + + public void handle(@Nonnull SetServerAccess packet) { + if (!Constants.SINGLEPLAYER) { + throw new IllegalArgumentException("SetServerAccess can only be used in singleplayer!"); + } else if (!SingleplayerModule.isOwner(this.playerRef)) { + throw new IllegalArgumentException("SetServerAccess can only be used by the owner of the singleplayer world!"); + } else { + HytaleServerConfig config = HytaleServer.get().getConfig(); + if (config != null) { + config.setPassword(packet.password != null ? packet.password : ""); + HytaleServerConfig.save(config); + } + + SingleplayerModule.get().requestServerAccess(packet.access); + } + } + + public void handle(@Nonnull RequestMachinimaActorModel packet) { + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(packet.modelId); + this.writeNoCache(new SetMachinimaActorModel(Model.createUnitScaleModel(modelAsset).toPacket(), packet.sceneName, packet.actorName)); + } + + public void handle(@Nonnull UpdateMachinimaScene packet) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + UpdateMachinimaScene updatePacket = new UpdateMachinimaScene( + this.playerRef.getUsername(), packet.sceneName, packet.frame, packet.updateType, packet.scene + ); + if ("*".equals(packet.player)) { + for (PlayerRef otherPlayerRef : world.getPlayerRefs()) { + if (!Objects.equals(otherPlayerRef, this.playerRef)) { + otherPlayerRef.getPacketHandler().writeNoCache(updatePacket); + } + } + + this.playerRef.sendMessage(Message.translation("server.io.gamepackethandler.sceneUpdateSent")); + } else { + PlayerRef target = NameMatching.DEFAULT.find(Universe.get().getPlayers(), packet.player, PlayerRef::getUsername); + if (target != null && target.getReference().getStore().getExternalData().getWorld() == world) { + target.getPacketHandler().write(updatePacket); + this.playerRef.sendMessage(Message.translation("server.io.gamepackethander.sceneUpdateSentToPlayer").param("name", target.getUsername())); + } else { + this.playerRef.sendMessage(Message.translation("server.io.gamepackethandler.playerNotFound").param("name", packet.player)); + } + } + } + ); + } + } + + public void handle(@Nonnull ClientReady packet) { + HytaleLogger.getLogger().at(Level.WARNING).log("%s: Received %s", this.getIdentifier(), packet); + CompletableFuture future = this.clientReadyForChunksFuture; + if (packet.readyForChunks && !packet.readyForGameplay && future != null) { + this.clientReadyForChunksFutureStack = null; + this.clientReadyForChunksFuture = null; + future.completeAsync(() -> null); + } + + if (packet.readyForGameplay) { + Ref ref = this.playerRef.getReference(); + if (ref == null || !ref.isValid()) { + return; + } + + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.handleClientReady(false); + }); + } + } + + public void handle(@Nonnull UpdateWorldMapVisible packet) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getWorldMapTracker().setClientHasWorldMapVisible(packet.visible); + }); + } + } + + public void handle(@Nonnull TeleportToWorldMapMarker packet) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + WorldMapTracker worldMapTracker = playerComponent.getWorldMapTracker(); + if (!worldMapTracker.isAllowTeleportToMarkers()) { + this.disconnect("You are not allowed to use TeleportToWorldMapMarker!"); + } else { + MapMarker marker = worldMapTracker.getSentMarkers().get(packet.id); + if (marker != null) { + world.getEntityStore() + .getStore() + .addComponent( + this.playerRef.getReference(), Teleport.getComponentType(), new Teleport(null, PositionUtil.toTransform(marker.transform)) + ); + } + } + } + ); + } + } + + public void handle(@Nonnull TeleportToWorldMapPosition packet) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + WorldMapTracker worldMapTracker = playerComponent.getWorldMapTracker(); + if (!worldMapTracker.isAllowTeleportToCoordinates()) { + this.disconnect("You are not allowed to use TeleportToWorldMapMarker!"); + } else { + world.getChunkStore() + .getChunkReferenceAsync(ChunkUtil.indexChunkFromBlock(packet.x, packet.y)) + .thenAcceptAsync( + chunkRef -> { + BlockChunk blockChunk = world.getChunkStore().getStore().getComponent((Ref)chunkRef, BlockChunk.getComponentType()); + Vector3d position = new Vector3d(packet.x, blockChunk.getHeight(packet.x, packet.y) + 2, packet.y); + world.getEntityStore() + .getStore() + .addComponent(this.playerRef.getReference(), Teleport.getComponentType(), new Teleport(null, position, Vector3f.NaN)); + }, + world + ); + } + } + ); + } + } + + public void handle(@Nonnull SyncInteractionChains packet) { + Collections.addAll(this.interactionPacketQueue, packet.updates); + } + + public void handle(@Nonnull MountMovement packet) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Ref entityReference = world.getEntityStore().getRefFromNetworkId(playerComponent.getMountEntityId()); + if (entityReference != null && entityReference.isValid()) { + TransformComponent transformComponent = store.getComponent(entityReference, TransformComponent.getComponentType()); + + assert transformComponent != null; + + transformComponent.setPosition(PositionUtil.toVector3d(packet.absolutePosition)); + transformComponent.setRotation(PositionUtil.toRotation(packet.bodyOrientation)); + MovementStatesComponent movementStatesComponent = store.getComponent(entityReference, MovementStatesComponent.getComponentType()); + + assert movementStatesComponent != null; + + movementStatesComponent.setMovementStates(packet.movementStates); + } + }); + } + } + + public void handle(@Nonnull SetPaused packet) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + if (world.getPlayerCount() == 1 && Constants.SINGLEPLAYER) { + world.setPaused(packet.paused); + } + }); + } + } + + public void handle(@Nonnull RequestFlyCameraMode packet) { + Ref ref = this.playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (playerComponent.hasPermission("hytale.camera.flycam")) { + this.writeNoCache(new SetFlyCameraMode(packet.entering)); + if (packet.entering) { + this.playerRef.sendMessage(Message.translation("server.general.flyCamera.enabled")); + } else { + this.playerRef.sendMessage(Message.translation("server.general.flyCamera.disabled")); + } + } else { + this.playerRef.sendMessage(Message.translation("server.general.flyCamera.noPermission")); + } + }); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/io/handlers/game/InventoryPacketHandler.java b/src/com/hypixel/hytale/server/core/io/handlers/game/InventoryPacketHandler.java new file mode 100644 index 0000000..9b72fb6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/handlers/game/InventoryPacketHandler.java @@ -0,0 +1,490 @@ +package com.hypixel.hytale.server.core.io.handlers.game; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.ItemSoundEvent; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.inventory.DropCreativeItem; +import com.hypixel.hytale.protocol.packets.inventory.DropItemStack; +import com.hypixel.hytale.protocol.packets.inventory.InventoryAction; +import com.hypixel.hytale.protocol.packets.inventory.MoveItemStack; +import com.hypixel.hytale.protocol.packets.inventory.SetActiveSlot; +import com.hypixel.hytale.protocol.packets.inventory.SetCreativeItem; +import com.hypixel.hytale.protocol.packets.inventory.SmartGiveCreativeItem; +import com.hypixel.hytale.protocol.packets.inventory.SmartMoveItemStack; +import com.hypixel.hytale.protocol.packets.inventory.SwitchHotbarBlockSet; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.item.config.BlockGroup; +import com.hypixel.hytale.server.core.asset.type.item.config.BlockSelectorToolData; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemArmor; +import com.hypixel.hytale.server.core.asset.type.itemsound.config.ItemSoundSet; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.entity.ItemUtils; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.windows.ItemContainerWindow; +import com.hypixel.hytale.server.core.entity.entities.player.windows.Window; +import com.hypixel.hytale.server.core.event.events.ecs.DropItemEvent; +import com.hypixel.hytale.server.core.event.events.ecs.SwitchActiveSlotEvent; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.container.SortType; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.io.handlers.IPacketHandler; +import com.hypixel.hytale.server.core.io.handlers.SubPacketHandler; +import com.hypixel.hytale.server.core.modules.item.ItemModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.NotificationUtil; +import com.hypixel.hytale.server.core.util.TempAssetIdUtil; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class InventoryPacketHandler implements SubPacketHandler { + private final IPacketHandler packetHandler; + + public InventoryPacketHandler(IPacketHandler packetHandler) { + this.packetHandler = packetHandler; + } + + @Override + public void registerHandlers() { + this.packetHandler.registerHandler(171, p -> this.handle((SetCreativeItem)p)); + this.packetHandler.registerHandler(172, p -> this.handle((DropCreativeItem)p)); + this.packetHandler.registerHandler(173, p -> this.handle((SmartGiveCreativeItem)p)); + this.packetHandler.registerHandler(174, p -> this.handle((DropItemStack)p)); + this.packetHandler.registerHandler(175, p -> this.handle((MoveItemStack)p)); + this.packetHandler.registerHandler(176, p -> this.handle((SmartMoveItemStack)p)); + this.packetHandler.registerHandler(177, p -> this.handle((SetActiveSlot)p)); + this.packetHandler.registerHandler(178, p -> this.handle((SwitchHotbarBlockSet)p)); + this.packetHandler.registerHandler(179, p -> this.handle((InventoryAction)p)); + } + + public void handle(@Nonnull SetCreativeItem packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + if (playerComponent.getGameMode() != GameMode.Creative) { + NotificationUtil.sendNotification( + playerRefComponent.getPacketHandler(), Message.translation("server.general.setCreativeItem.notInCreativeMode") + ); + } else { + Inventory inventory = playerComponent.getInventory(); + int quantity = packet.item.quantity; + if (quantity > 0) { + ItemStack itemStack = ItemStack.fromPacket(packet.item); + if (packet.slotId < 0) { + ItemStackTransaction transaction = inventory.getCombinedHotbarFirst().addItemStack(itemStack); + ItemStack remainder = transaction.getRemainder(); + if (remainder != null && !remainder.isEmpty()) { + ItemUtils.dropItem(ref, remainder, store); + } + } else { + ItemContainer sectionById = inventory.getSectionById(packet.inventorySectionId); + if (packet.override) { + sectionById.setItemStackForSlot((short)packet.slotId, itemStack); + } else { + ItemStack existing = sectionById.getItemStack((short)packet.slotId); + if (existing != null && !existing.isEmpty() && existing.isStackableWith(itemStack)) { + sectionById.addItemStackToSlot((short)packet.slotId, itemStack); + } else { + sectionById.setItemStackForSlot((short)packet.slotId, itemStack); + } + } + } + } else if (packet.override) { + inventory.getSectionById(packet.inventorySectionId).setItemStackForSlot((short)packet.slotId, null); + } + } + } + ); + } + } + + public void handle(@Nonnull DropCreativeItem packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + ItemStack itemStack = ItemStack.fromPacket(packet.item); + if (itemStack != null) { + Item item = itemStack.getItem(); + if (item != Item.UNKNOWN) { + itemStack = itemStack.withQuantity(Math.min(itemStack.getQuantity(), item.getMaxStack())); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + if (playerComponent.getGameMode() != GameMode.Creative) { + NotificationUtil.sendNotification( + playerRefComponent.getPacketHandler(), Message.translation("server.general.setCreativeItem.notInCreativeMode") + ); + } else { + ItemUtils.dropItem(ref, itemStack, store); + } + } + ); + } + } + } + } + + public void handle(SwitchHotbarBlockSet packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Inventory inventory = playerComponent.getInventory(); + byte slot = inventory.getActiveHotbarSlot(); + if (slot != -1) { + ItemContainer hotbar = inventory.getHotbar(); + ItemStack stack = hotbar.getItemStack(slot); + if (stack != null && !stack.isEmpty()) { + BlockGroup set = BlockGroup.findItemGroup(stack.getItem()); + if (set != null) { + Item desiredItem = Item.getAssetMap().getAsset(packet.itemId); + if (desiredItem != null) { + int currentIndex = set.getIndex(stack.getItem()); + if (currentIndex != -1) { + int desiredIndex = set.getIndex(desiredItem); + if (desiredIndex != -1 && desiredIndex != currentIndex) { + ItemStack maxSelectorTool = null; + short maxSlot = -1; + CombinedItemContainer combinedInventory = inventory.getCombinedArmorHotbarUtilityStorage(); + + for (short i = 0; i < combinedInventory.getCapacity(); i++) { + ItemStack potentialSelector = combinedInventory.getItemStack(i); + if (!ItemStack.isEmpty(potentialSelector)) { + Item item = potentialSelector.getItem(); + BlockSelectorToolData selectorTool = item.getBlockSelectorToolData(); + if (selectorTool != null + && (maxSelectorTool == null || maxSelectorTool.getDurability() < potentialSelector.getDurability())) { + maxSelectorTool = potentialSelector; + maxSlot = i; + } + } + } + + if (maxSelectorTool != null) { + BlockSelectorToolData toolData = maxSelectorTool.getItem().getBlockSelectorToolData(); + if (playerComponent.canDecreaseItemStackDurability(ref, store) && !maxSelectorTool.isUnbreakable()) { + playerComponent.updateItemStackDurability( + ref, maxSelectorTool, combinedInventory, maxSlot, -toolData.getDurabilityLossOnUse(), store + ); + } + + ItemStack replacement = new ItemStack(set.get(desiredIndex), stack.getQuantity()); + hotbar.setItemStackForSlot(slot, replacement); + ItemSoundSet soundSet = ItemSoundSet.getAssetMap().getAsset(desiredItem.getItemSoundSetIndex()); + if (soundSet != null) { + String dragSound = soundSet.getSoundEventIds().get(ItemSoundEvent.Drop); + if (dragSound != null) { + int dragSoundIndex = SoundEvent.getAssetMap().getIndex(dragSound); + if (dragSoundIndex != 0) { + SoundUtil.playSoundEvent2d(ref, dragSoundIndex, SoundCategory.UI, store); + } + } + } + } + } + } + } + } + } + } + } + ); + } + } + + public void handle(@Nonnull SmartGiveCreativeItem packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + if (playerComponent.getGameMode() != GameMode.Creative) { + NotificationUtil.sendNotification( + playerRefComponent.getPacketHandler(), Message.translation("server.general.setCreativeItem.notInCreativeMode") + ); + } else { + Inventory inventory = playerComponent.getInventory(); + ItemStack itemStack = ItemStack.fromPacket(packet.item); + switch (packet.moveType) { + case EquipOrMergeStack: + Item item = itemStack.getItem(); + ItemArmor itemArmor = item.getArmor(); + if (itemArmor != null) { + inventory.getArmor().setItemStackForSlot((short)itemArmor.getArmorSlot().ordinal(), itemStack); + return; + } + + int quantity = itemStack.getQuantity(); + if (item.getUtility().isUsable()) { + ItemStackTransaction transaction = inventory.getUtility().addItemStack(itemStack); + ItemStack remainder = transaction.getRemainder(); + if (ItemStack.isEmpty(remainder) || remainder.getQuantity() != quantity) { + for (ItemStackSlotTransaction slotTransaction : transaction.getSlotTransactions()) { + if (slotTransaction.succeeded()) { + inventory.setActiveUtilitySlot((byte)slotTransaction.getSlot()); + } + } + } + + return; + } + break; + case PutInHotbarOrWindow: + inventory.getCombinedHotbarFirst().addItemStack(itemStack); + } + } + } + ); + } + } + + public void handle(@Nonnull DropItemStack packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute( + () -> { + DropItemEvent.PlayerRequest event = new DropItemEvent.PlayerRequest(packet.inventorySectionId, (short)packet.slotId); + store.invoke(ref, event); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Inventory inventory = playerComponent.getInventory(); + if (!event.isCancelled()) { + ItemStackSlotTransaction transaction = inventory.getSectionById(event.getInventorySectionId()) + .removeItemStackFromSlot(event.getSlotId(), packet.quantity); + ItemStack item = transaction.getOutput(); + if (item == null || item.isEmpty()) { + HytaleLogger.getLogger().at(Level.WARNING).log("%s attempted to drop an empty ItemStack!", playerRef.getUsername()); + return; + } + + String itemId = item.getItemId(); + if (!ItemModule.exists(itemId)) { + HytaleLogger.getLogger().at(Level.WARNING).log("%s attempted to drop an unregistered ItemStack! %s", playerRef.getUsername(), itemId); + return; + } + + ItemUtils.throwItem(ref, item, 6.0F, store); + SoundUtil.playSoundEvent2d(ref, TempAssetIdUtil.getSoundEventIndex("SFX_Player_Drop_Item"), SoundCategory.UI, store); + } else { + playerComponent.sendInventory(); + } + } + ); + } + } + + public void handle(@Nonnull MoveItemStack packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Inventory inventory = playerComponent.getInventory(); + inventory.moveItem(packet.fromSectionId, packet.fromSlotId, packet.quantity, packet.toSectionId, packet.toSlotId); + if (packet.toSectionId != packet.fromSectionId && packet.toSectionId == -5) { + byte newSlot = (byte)packet.toSlotId; + int inventorySectionId = packet.toSectionId; + byte currentSlot = inventory.getActiveSlot(inventorySectionId); + if (currentSlot == newSlot) { + return; + } + + SwitchActiveSlotEvent event = new SwitchActiveSlotEvent(inventorySectionId, currentSlot, newSlot, true); + store.invoke(ref, event); + if (event.isCancelled() || event.getNewSlot() == currentSlot) { + return; + } + + newSlot = event.getNewSlot(); + inventory.setActiveSlot(inventorySectionId, newSlot); + playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(inventorySectionId, newSlot)); + } + }); + } + } + + public void handle(@Nonnull SmartMoveItemStack packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Inventory inventory = playerComponent.getInventory(); + inventory.smartMoveItem(packet.fromSectionId, packet.fromSlotId, packet.quantity, packet.moveType); + }); + } + } + + public void handle(@Nonnull SetActiveSlot packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Inventory inventory = playerComponent.getInventory(); + PacketHandler packetHandler = playerRef.getPacketHandler(); + if (packet.inventorySectionId == -1) { + packetHandler.disconnect("Attempted to change the hotbar slot without an interaction"); + } else if (packet.activeSlot < -1 || packet.activeSlot >= inventory.getSectionById(packet.inventorySectionId).getCapacity()) { + packetHandler.disconnect("Attempted to set " + packet.inventorySectionId + " slot outside of range!"); + } else if (packet.activeSlot == inventory.getActiveSlot(packet.inventorySectionId)) { + packetHandler.disconnect("Attempted to set hotbar slot to already selected hotbar slot!"); + } else { + byte previousSlot = inventory.getActiveSlot(packet.inventorySectionId); + byte targetSlot = (byte)packet.activeSlot; + SwitchActiveSlotEvent event = new SwitchActiveSlotEvent(packet.inventorySectionId, previousSlot, targetSlot, false); + store.invoke(ref, event); + if (event.isCancelled()) { + targetSlot = previousSlot; + } else if (targetSlot != event.getNewSlot()) { + targetSlot = event.getNewSlot(); + } + + if (targetSlot != packet.activeSlot) { + packetHandler.writeNoCache(new SetActiveSlot(packet.inventorySectionId, targetSlot)); + } + + if (targetSlot != previousSlot) { + inventory.setActiveSlot(packet.inventorySectionId, targetSlot); + } + } + }); + } + } + + public void handle(@Nonnull InventoryAction packet) { + PlayerRef playerRef = this.packetHandler.getPlayerRef(); + Ref ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + if (packet.inventorySectionId >= 0 || packet.inventorySectionId == -9) { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Inventory inventory = playerComponent.getInventory(); + switch (packet.inventoryActionType) { + case TakeAll: + if (packet.inventorySectionId == -9) { + inventory.takeAll(packet.inventorySectionId); + return; + } + + Window window = playerComponent.getWindowManager().getWindow(packet.inventorySectionId); + if (window instanceof ItemContainerWindow) { + inventory.takeAll(packet.inventorySectionId); + } + break; + case PutAll: + if (packet.inventorySectionId == -9) { + inventory.putAll(packet.inventorySectionId); + return; + } + + Window window = playerComponent.getWindowManager().getWindow(packet.inventorySectionId); + if (window instanceof ItemContainerWindow) { + inventory.putAll(packet.inventorySectionId); + } + break; + case QuickStack: + if (packet.inventorySectionId == -9) { + inventory.quickStack(packet.inventorySectionId); + return; + } + + Window window = playerComponent.getWindowManager().getWindow(packet.inventorySectionId); + if (window instanceof ItemContainerWindow) { + inventory.quickStack(packet.inventorySectionId); + } + break; + case Sort: + SortType sortType = SortType.VALUES[packet.actionData]; + if (packet.inventorySectionId == 0) { + inventory.sortStorage(sortType); + } else { + if (packet.inventorySectionId == -9 && inventory.getBackpack() != null) { + inventory.getBackpack().sortItems(sortType); + return; + } + + if (playerComponent.getWindowManager().getWindow(packet.inventorySectionId) instanceof ItemContainerWindow itemContainerWindow) { + itemContainerWindow.getItemContainer().sortItems(sortType); + } + } + } + }); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/io/handlers/login/AuthenticationPacketHandler.java b/src/com/hypixel/hytale/server/core/io/handlers/login/AuthenticationPacketHandler.java new file mode 100644 index 0000000..aa08c7a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/handlers/login/AuthenticationPacketHandler.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.server.core.io.handlers.login; + +import com.hypixel.hytale.protocol.HostAddress; +import com.hypixel.hytale.protocol.packets.connection.ClientType; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.auth.PlayerAuthentication; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.io.ProtocolVersion; +import com.hypixel.hytale.server.core.io.netty.NettyUtil; +import com.hypixel.hytale.server.core.universe.Universe; +import io.netty.channel.Channel; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AuthenticationPacketHandler extends HandshakeHandler { + private final AuthenticationPacketHandler.AuthHandlerSupplier authHandlerSupplier; + + public AuthenticationPacketHandler( + @Nonnull Channel channel, + @Nonnull ProtocolVersion protocolVersion, + @Nonnull String language, + @Nonnull AuthenticationPacketHandler.AuthHandlerSupplier authHandlerSupplier, + @Nonnull ClientType clientType, + @Nonnull String identityToken, + @Nonnull UUID uuid, + @Nonnull String username, + @Nullable byte[] referralData, + @Nullable HostAddress referralSource + ) { + super(channel, protocolVersion, language, clientType, identityToken, uuid, username, referralData, referralSource); + this.authHandlerSupplier = authHandlerSupplier; + } + + @Nonnull + @Override + public String getIdentifier() { + return "{Authenticating(" + NettyUtil.formatRemoteAddress(this.channel) + "), authHandlerSupplier=" + this.authHandlerSupplier + "}"; + } + + @Override + public void registered0(PacketHandler oldHandler) { + int maxPlayers = HytaleServer.get().getConfig().getMaxPlayers(); + if (maxPlayers > 0 && Universe.get().getPlayerCount() >= maxPlayers) { + this.disconnect("Too many players!"); + } else { + super.registered0(oldHandler); + } + } + + @Override + protected void onAuthenticated(byte[] passwordChallenge) { + PacketHandler.logConnectionTimings(this.channel, "Authenticated", Level.FINE); + NettyUtil.setChannelHandler( + this.channel, + new PasswordPacketHandler( + this.channel, + this.protocolVersion, + this.language, + this.auth.getUuid(), + this.auth.getUsername(), + this.auth.getReferralData(), + this.auth.getReferralSource(), + passwordChallenge, + (ch, pv, lang, a) -> this.authHandlerSupplier.create(ch, pv, lang, a) + ) + ); + } + + @FunctionalInterface + public interface AuthHandlerSupplier { + PacketHandler create(Channel var1, ProtocolVersion var2, String var3, PlayerAuthentication var4); + } +} diff --git a/src/com/hypixel/hytale/server/core/io/handlers/login/HandshakeHandler.java b/src/com/hypixel/hytale/server/core/io/handlers/login/HandshakeHandler.java new file mode 100644 index 0000000..3ee44cc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/handlers/login/HandshakeHandler.java @@ -0,0 +1,410 @@ +package com.hypixel.hytale.server.core.io.handlers.login; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.HostAddress; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.netty.ProtocolUtil; +import com.hypixel.hytale.protocol.packets.auth.AuthGrant; +import com.hypixel.hytale.protocol.packets.auth.AuthToken; +import com.hypixel.hytale.protocol.packets.auth.ServerAuthToken; +import com.hypixel.hytale.protocol.packets.connection.ClientType; +import com.hypixel.hytale.protocol.packets.connection.Disconnect; +import com.hypixel.hytale.server.core.Constants; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.auth.AuthConfig; +import com.hypixel.hytale.server.core.auth.JWTValidator; +import com.hypixel.hytale.server.core.auth.PlayerAuthentication; +import com.hypixel.hytale.server.core.auth.ServerAuthManager; +import com.hypixel.hytale.server.core.auth.SessionServiceClient; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.io.ProtocolVersion; +import com.hypixel.hytale.server.core.io.handlers.GenericConnectionPacketHandler; +import com.hypixel.hytale.server.core.io.netty.NettyUtil; +import com.hypixel.hytale.server.core.io.transport.QUICTransport; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import io.netty.channel.Channel; +import io.netty.handler.timeout.ReadTimeoutHandler; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class HandshakeHandler extends GenericConnectionPacketHandler { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static volatile SessionServiceClient sessionServiceClient; + private static volatile JWTValidator jwtValidator; + private volatile HandshakeHandler.AuthState authState = HandshakeHandler.AuthState.REQUESTING_AUTH_GRANT; + private volatile boolean authTokenPacketReceived = false; + private volatile String authenticatedUsername; + private static final int AUTH_GRANT_TIMEOUT_SECONDS = 30; + private static final int AUTH_TOKEN_TIMEOUT_SECONDS = 30; + private static final int SERVER_TOKEN_EXCHANGE_TIMEOUT_SECONDS = 15; + private final ClientType clientType; + private final String identityToken; + private final UUID playerUuid; + private final String username; + private final byte[] referralData; + private final HostAddress referralSource; + + public HandshakeHandler( + @Nonnull Channel channel, + @Nonnull ProtocolVersion protocolVersion, + @Nonnull String language, + @Nonnull ClientType clientType, + @Nonnull String identityToken, + @Nonnull UUID playerUuid, + @Nonnull String username, + @Nullable byte[] referralData, + @Nullable HostAddress referralSource + ) { + super(channel, protocolVersion, language); + this.clientType = clientType; + this.identityToken = identityToken; + this.playerUuid = playerUuid; + this.username = username; + this.referralData = referralData; + this.referralSource = referralSource; + } + + private static SessionServiceClient getSessionServiceClient() { + if (sessionServiceClient == null) { + synchronized (HandshakeHandler.class) { + if (sessionServiceClient == null) { + sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com"); + } + } + } + + return sessionServiceClient; + } + + private static JWTValidator getJwtValidator() { + if (jwtValidator == null) { + synchronized (HandshakeHandler.class) { + if (jwtValidator == null) { + jwtValidator = new JWTValidator(getSessionServiceClient(), "https://sessions.hytale.com", AuthConfig.getServerAudience()); + } + } + } + + return jwtValidator; + } + + @Override + public void accept(@Nonnull Packet packet) { + switch (packet.getId()) { + case 1: + this.handle((Disconnect)packet); + break; + case 12: + this.handle((AuthToken)packet); + break; + default: + this.disconnect("Protocol error: unexpected packet " + packet.getId()); + } + } + + @Override + public void registered0(PacketHandler oldHandler) { + Duration authTimeout = HytaleServer.get().getConfig().getConnectionTimeouts().getAuthTimeout(); + this.channel.pipeline().replace("timeOut", "timeOut", new ReadTimeoutHandler(authTimeout.toMillis(), TimeUnit.MILLISECONDS)); + JWTValidator.IdentityTokenClaims identityClaims = getJwtValidator().validateIdentityToken(this.identityToken); + if (identityClaims == null) { + LOGGER.at(Level.WARNING).log("Identity token validation failed for %s from %s", this.username, NettyUtil.formatRemoteAddress(this.channel)); + this.disconnect("Invalid or expired identity token"); + } else { + UUID tokenUuid = identityClaims.getSubjectAsUUID(); + if (tokenUuid != null && tokenUuid.equals(this.playerUuid)) { + String requiredScope = this.clientType == ClientType.Editor ? "hytale:editor" : "hytale:client"; + if (!identityClaims.hasScope(requiredScope)) { + LOGGER.at(Level.WARNING) + .log( + "Identity token missing required scope for %s from %s (clientType: %s, required: %s, actual: %s)", + this.username, + NettyUtil.formatRemoteAddress(this.channel), + this.clientType, + requiredScope, + identityClaims.scope + ); + this.disconnect("Invalid identity token: missing " + requiredScope + " scope"); + } else { + LOGGER.at(Level.INFO) + .log( + "Identity token validated for %s (UUID: %s, scope: %s) from %s, requesting auth grant", + this.username, + this.playerUuid, + identityClaims.scope, + NettyUtil.formatRemoteAddress(this.channel) + ); + this.setTimeout("auth-grant-timeout", () -> this.authState != HandshakeHandler.AuthState.REQUESTING_AUTH_GRANT, 30L, TimeUnit.SECONDS); + this.requestAuthGrant(); + } + } else { + LOGGER.at(Level.WARNING) + .log( + "Identity token UUID mismatch for %s from %s (expected: %s, got: %s)", + this.username, + NettyUtil.formatRemoteAddress(this.channel), + this.playerUuid, + tokenUuid + ); + this.disconnect("Invalid identity token: UUID mismatch"); + } + } + } + + private void requestAuthGrant() { + String serverSessionToken = ServerAuthManager.getInstance().getSessionToken(); + if (serverSessionToken != null && !serverSessionToken.isEmpty()) { + getSessionServiceClient() + .requestAuthorizationGrantAsync(this.identityToken, AuthConfig.getServerAudience(), serverSessionToken) + .thenAccept( + authGrant -> { + if (this.channel.isActive()) { + if (authGrant == null) { + this.channel.eventLoop().execute(() -> this.disconnect("Failed to obtain authorization grant from session service")); + } else { + String serverIdentityToken = ServerAuthManager.getInstance().getIdentityToken(); + if (serverIdentityToken != null && !serverIdentityToken.isEmpty()) { + this.channel + .eventLoop() + .execute( + () -> { + if (this.channel.isActive()) { + if (this.authState != HandshakeHandler.AuthState.REQUESTING_AUTH_GRANT) { + LOGGER.at(Level.WARNING).log("State changed during auth grant request, current state: %s", this.authState); + } else { + this.clearTimeout(); + LOGGER.at(Level.INFO) + .log( + "Sending AuthGrant to %s (with server identity: %s)", + NettyUtil.formatRemoteAddress(this.channel), + !serverIdentityToken.isEmpty() + ); + this.write(new AuthGrant(authGrant, serverIdentityToken)); + this.authState = HandshakeHandler.AuthState.AWAITING_AUTH_TOKEN; + this.setTimeout( + "auth-token-timeout", + () -> this.authState != HandshakeHandler.AuthState.AWAITING_AUTH_TOKEN, + 30L, + TimeUnit.SECONDS + ); + } + } + } + ); + } else { + LOGGER.at(Level.SEVERE).log("Server identity token not available - cannot complete mutual authentication"); + this.channel.eventLoop().execute(() -> this.disconnect("Server authentication unavailable - please try again later")); + } + } + } + } + ) + .exceptionally(ex -> { + LOGGER.at(Level.WARNING).withCause(ex).log("Error requesting auth grant"); + this.channel.eventLoop().execute(() -> this.disconnect("Authentication error: " + ex.getMessage())); + return null; + }); + } else { + LOGGER.at(Level.SEVERE).log("Server session token not available - cannot request auth grant"); + this.disconnect("Server authentication unavailable - please try again later"); + } + } + + public void handle(@Nonnull Disconnect packet) { + this.disconnectReason.setClientDisconnectType(packet.type); + LOGGER.at(Level.INFO) + .log( + "%s (%s) at %s left with reason: %s - %s", + this.playerUuid, + this.username, + NettyUtil.formatRemoteAddress(this.channel), + packet.type.name(), + packet.reason + ); + ProtocolUtil.closeApplicationConnection(this.channel); + } + + public void handle(@Nonnull AuthToken packet) { + if (this.authState != HandshakeHandler.AuthState.AWAITING_AUTH_TOKEN) { + LOGGER.at(Level.WARNING).log("Received unexpected AuthToken packet in state %s from %s", this.authState, NettyUtil.formatRemoteAddress(this.channel)); + this.disconnect("Protocol error: unexpected AuthToken packet"); + } else if (this.authTokenPacketReceived) { + LOGGER.at(Level.WARNING).log("Received duplicate AuthToken packet from %s", NettyUtil.formatRemoteAddress(this.channel)); + this.disconnect("Protocol error: duplicate AuthToken packet"); + } else { + this.authTokenPacketReceived = true; + this.authState = HandshakeHandler.AuthState.PROCESSING_AUTH_TOKEN; + this.clearTimeout(); + String accessToken = packet.accessToken; + if (accessToken != null && !accessToken.isEmpty()) { + String serverAuthGrant = packet.serverAuthorizationGrant; + X509Certificate clientCert = this.channel.attr(QUICTransport.CLIENT_CERTIFICATE_ATTR).get(); + LOGGER.at(Level.INFO) + .log( + "Received AuthToken from %s, validating JWT (mTLS cert present: %s, server auth grant: %s)", + NettyUtil.formatRemoteAddress(this.channel), + clientCert != null, + serverAuthGrant != null && !serverAuthGrant.isEmpty() + ); + JWTValidator.JWTClaims claims = getJwtValidator().validateToken(accessToken, clientCert); + if (claims == null) { + LOGGER.at(Level.WARNING).log("JWT validation failed for %s", NettyUtil.formatRemoteAddress(this.channel)); + this.disconnect("Invalid access token"); + } else { + UUID tokenUuid = claims.getSubjectAsUUID(); + String tokenUsername = claims.username; + if (tokenUuid == null || !tokenUuid.equals(this.playerUuid)) { + LOGGER.at(Level.WARNING) + .log("JWT UUID mismatch for %s (expected: %s, got: %s)", NettyUtil.formatRemoteAddress(this.channel), this.playerUuid, tokenUuid); + this.disconnect("Invalid token claims: UUID mismatch"); + } else if (tokenUsername == null || tokenUsername.isEmpty()) { + LOGGER.at(Level.WARNING).log("JWT missing username for %s", NettyUtil.formatRemoteAddress(this.channel)); + this.disconnect("Invalid token claims: missing username"); + } else if (!tokenUsername.equals(this.username)) { + LOGGER.at(Level.WARNING) + .log("JWT username mismatch for %s (expected: %s, got: %s)", NettyUtil.formatRemoteAddress(this.channel), this.username, tokenUsername); + this.disconnect("Invalid token claims: username mismatch"); + } else { + this.authenticatedUsername = tokenUsername; + if (serverAuthGrant != null && !serverAuthGrant.isEmpty()) { + this.authState = HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN; + this.setTimeout( + "server-token-exchange-timeout", () -> this.authState != HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN, 15L, TimeUnit.SECONDS + ); + this.exchangeServerAuthGrant(serverAuthGrant); + } else { + LOGGER.at(Level.WARNING).log("Client did not provide server auth grant for mutual authentication"); + this.disconnect("Mutual authentication required - please update your client"); + } + } + } + } else { + LOGGER.at(Level.WARNING).log("Received AuthToken packet with empty access token from %s", NettyUtil.formatRemoteAddress(this.channel)); + this.disconnect("Invalid access token"); + } + } + } + + private void exchangeServerAuthGrant(@Nonnull String serverAuthGrant) { + ServerAuthManager serverAuthManager = ServerAuthManager.getInstance(); + String serverCertFingerprint = serverAuthManager.getServerCertificateFingerprint(); + if (serverCertFingerprint == null) { + LOGGER.at(Level.SEVERE).log("Server certificate fingerprint not available for mutual auth"); + this.disconnect("Server authentication unavailable - please try again later"); + } else { + String serverSessionToken = serverAuthManager.getSessionToken(); + LOGGER.at(Level.FINE) + .log("Server session token available: %s, identity token available: %s", serverSessionToken != null, serverAuthManager.getIdentityToken() != null); + if (serverSessionToken == null) { + LOGGER.at(Level.SEVERE).log("Server session token not available for auth grant exchange"); + LOGGER.at(Level.FINE) + .log( + "Auth mode: %s, has session token: %s, has identity token: %s", + serverAuthManager.getAuthStatus(), + serverAuthManager.hasSessionToken(), + serverAuthManager.hasIdentityToken() + ); + this.disconnect("Server authentication unavailable - please try again later"); + } else { + LOGGER.at(Level.FINE) + .log("Using session token (first 20 chars): %s...", serverSessionToken.length() > 20 ? serverSessionToken.substring(0, 20) : serverSessionToken); + getSessionServiceClient() + .exchangeAuthGrantForTokenAsync(serverAuthGrant, serverCertFingerprint, serverSessionToken) + .thenAccept( + serverAccessToken -> { + if (this.channel.isActive()) { + this.channel + .eventLoop() + .execute( + () -> { + if (this.channel.isActive()) { + if (this.authState != HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN) { + LOGGER.at(Level.WARNING).log("State changed during server token exchange, current state: %s", this.authState); + } else if (serverAccessToken == null) { + LOGGER.at(Level.SEVERE).log("Failed to exchange server auth grant for access token"); + this.disconnect("Server authentication failed - please try again later"); + } else { + byte[] passwordChallenge = this.generatePasswordChallengeIfNeeded(); + LOGGER.at(Level.INFO) + .log( + "Sending ServerAuthToken to %s (with password challenge: %s)", + NettyUtil.formatRemoteAddress(this.channel), + passwordChallenge != null + ); + this.write(new ServerAuthToken(serverAccessToken, passwordChallenge)); + this.completeAuthentication(passwordChallenge); + } + } + } + ); + } + } + ) + .exceptionally(ex -> { + LOGGER.at(Level.WARNING).withCause(ex).log("Error exchanging server auth grant"); + this.channel.eventLoop().execute(() -> { + if (this.authState == HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN) { + byte[] passwordChallenge = this.generatePasswordChallengeIfNeeded(); + this.completeAuthentication(passwordChallenge); + } + }); + return null; + }); + } + } + } + + private byte[] generatePasswordChallengeIfNeeded() { + String password = HytaleServer.get().getConfig().getPassword(); + if (password != null && !password.isEmpty()) { + if (Constants.SINGLEPLAYER) { + UUID ownerUuid = SingleplayerModule.getUuid(); + if (ownerUuid != null && ownerUuid.equals(this.playerUuid)) { + return null; + } + } + + byte[] challenge = new byte[32]; + new SecureRandom().nextBytes(challenge); + return challenge; + } else { + return null; + } + } + + private void completeAuthentication(byte[] passwordChallenge) { + this.auth = new PlayerAuthentication(this.playerUuid, this.authenticatedUsername); + if (this.referralData != null) { + this.auth.setReferralData(this.referralData); + } + + if (this.referralSource != null) { + this.auth.setReferralSource(this.referralSource); + } + + this.authState = HandshakeHandler.AuthState.AUTHENTICATED; + this.clearTimeout(); + LOGGER.at(Level.INFO) + .log("Mutual authentication complete for %s (%s) from %s", this.authenticatedUsername, this.playerUuid, NettyUtil.formatRemoteAddress(this.channel)); + this.onAuthenticated(passwordChallenge); + } + + protected abstract void onAuthenticated(byte[] var1); + + private static enum AuthState { + REQUESTING_AUTH_GRANT, + AWAITING_AUTH_TOKEN, + PROCESSING_AUTH_TOKEN, + EXCHANGING_SERVER_TOKEN, + AUTHENTICATED; + + private AuthState() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/io/handlers/login/PasswordPacketHandler.java b/src/com/hypixel/hytale/server/core/io/handlers/login/PasswordPacketHandler.java new file mode 100644 index 0000000..89ff40c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/handlers/login/PasswordPacketHandler.java @@ -0,0 +1,192 @@ +package com.hypixel.hytale.server.core.io.handlers.login; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.HostAddress; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.io.netty.ProtocolUtil; +import com.hypixel.hytale.protocol.packets.auth.PasswordAccepted; +import com.hypixel.hytale.protocol.packets.auth.PasswordRejected; +import com.hypixel.hytale.protocol.packets.auth.PasswordResponse; +import com.hypixel.hytale.protocol.packets.connection.Disconnect; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.auth.PlayerAuthentication; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.io.ProtocolVersion; +import com.hypixel.hytale.server.core.io.handlers.GenericConnectionPacketHandler; +import com.hypixel.hytale.server.core.io.netty.NettyUtil; +import io.netty.channel.Channel; +import io.netty.handler.timeout.ReadTimeoutHandler; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.time.Duration; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PasswordPacketHandler extends GenericConnectionPacketHandler { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final int PASSWORD_TIMEOUT_SECONDS = 30; + private static final int MAX_PASSWORD_ATTEMPTS = 3; + private static final int CHALLENGE_LENGTH = 32; + private final UUID playerUuid; + private final String username; + private final byte[] referralData; + private final HostAddress referralSource; + private byte[] passwordChallenge; + private final PasswordPacketHandler.SetupHandlerSupplier setupHandlerSupplier; + private int attemptsRemaining = 3; + + public PasswordPacketHandler( + @Nonnull Channel channel, + @Nonnull ProtocolVersion protocolVersion, + @Nonnull String language, + @Nonnull UUID playerUuid, + @Nonnull String username, + @Nullable byte[] referralData, + @Nullable HostAddress referralSource, + @Nullable byte[] passwordChallenge, + @Nonnull PasswordPacketHandler.SetupHandlerSupplier setupHandlerSupplier + ) { + super(channel, protocolVersion, language); + this.playerUuid = playerUuid; + this.username = username; + this.referralData = referralData; + this.referralSource = referralSource; + this.passwordChallenge = passwordChallenge; + this.setupHandlerSupplier = setupHandlerSupplier; + } + + @Nonnull + @Override + public String getIdentifier() { + return "{Password(" + NettyUtil.formatRemoteAddress(this.channel) + "), " + this.username + "}"; + } + + @Override + public void registered0(PacketHandler oldHandler) { + Duration playTimeout = HytaleServer.get().getConfig().getConnectionTimeouts().getPlayTimeout(); + this.channel.pipeline().replace("timeOut", "timeOut", new ReadTimeoutHandler(playTimeout.toMillis(), TimeUnit.MILLISECONDS)); + if (this.passwordChallenge != null && this.passwordChallenge.length != 0) { + LOGGER.at(Level.FINE).log("Waiting for password response from %s", this.username); + this.setTimeout("password-timeout", () -> !this.registered, 30L, TimeUnit.SECONDS); + } else { + LOGGER.at(Level.FINE).log("No password required for %s, proceeding to setup", this.username); + this.proceedToSetup(); + } + } + + @Override + public void accept(@Nonnull Packet packet) { + switch (packet.getId()) { + case 1: + this.handle((Disconnect)packet); + break; + case 15: + this.handle((PasswordResponse)packet); + break; + default: + this.disconnect("Protocol error: unexpected packet " + packet.getId()); + } + } + + public void handle(@Nonnull Disconnect packet) { + this.disconnectReason.setClientDisconnectType(packet.type); + LOGGER.at(Level.INFO) + .log( + "%s (%s) at %s left with reason: %s - %s", + this.playerUuid, + this.username, + NettyUtil.formatRemoteAddress(this.channel), + packet.type.name(), + packet.reason + ); + ProtocolUtil.closeApplicationConnection(this.channel); + } + + public void handle(@Nonnull PasswordResponse packet) { + this.clearTimeout(); + if (this.passwordChallenge != null && this.passwordChallenge.length != 0) { + byte[] clientHash = packet.hash; + if (clientHash != null && clientHash.length != 0) { + String password = HytaleServer.get().getConfig().getPassword(); + if (password != null && !password.isEmpty()) { + byte[] expectedHash = computePasswordHash(this.passwordChallenge, password); + if (expectedHash == null) { + LOGGER.at(Level.SEVERE).log("Failed to compute password hash"); + this.disconnect("Server error"); + } else if (!MessageDigest.isEqual(expectedHash, clientHash)) { + this.attemptsRemaining--; + LOGGER.at(Level.WARNING) + .log( + "Invalid password from %s (%s), %d attempts remaining", + this.username, + NettyUtil.formatRemoteAddress(this.channel), + this.attemptsRemaining + ); + if (this.attemptsRemaining <= 0) { + this.disconnect("Too many failed password attempts"); + } else { + this.passwordChallenge = generateChallenge(); + this.write(new PasswordRejected(this.passwordChallenge, this.attemptsRemaining)); + this.setTimeout("password-timeout", () -> !this.registered, 30L, TimeUnit.SECONDS); + } + } else { + LOGGER.at(Level.INFO).log("Password accepted for %s (%s)", this.username, this.playerUuid); + this.write(new PasswordAccepted()); + this.proceedToSetup(); + } + } else { + LOGGER.at(Level.SEVERE).log("Password validation failed - no password configured but challenge was sent"); + this.disconnect("Server configuration error"); + } + } else { + LOGGER.at(Level.WARNING).log("Received empty password hash from %s", NettyUtil.formatRemoteAddress(this.channel)); + this.disconnect("Invalid password response"); + } + } else { + LOGGER.at(Level.WARNING).log("Received unexpected PasswordResponse from %s - no password required", NettyUtil.formatRemoteAddress(this.channel)); + this.disconnect("Protocol error: unexpected PasswordResponse"); + } + } + + private static byte[] generateChallenge() { + byte[] challenge = new byte[32]; + new SecureRandom().nextBytes(challenge); + return challenge; + } + + private void proceedToSetup() { + this.auth = new PlayerAuthentication(this.playerUuid, this.username); + if (this.referralData != null) { + this.auth.setReferralData(this.referralData); + } + + if (this.referralSource != null) { + this.auth.setReferralSource(this.referralSource); + } + + LOGGER.at(Level.INFO).log("Connection complete for %s (%s), transitioning to setup", this.username, this.playerUuid); + NettyUtil.setChannelHandler(this.channel, this.setupHandlerSupplier.create(this.channel, this.protocolVersion, this.language, this.auth)); + } + + @Nullable + private static byte[] computePasswordHash(byte[] challenge, String password) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + digest.update(challenge); + digest.update(password.getBytes(StandardCharsets.UTF_8)); + return digest.digest(); + } catch (NoSuchAlgorithmException var3) { + return null; + } + } + + @FunctionalInterface + public interface SetupHandlerSupplier { + PacketHandler create(Channel var1, ProtocolVersion var2, String var3, PlayerAuthentication var4); + } +} diff --git a/src/com/hypixel/hytale/server/core/io/netty/HytaleChannelInitializer.java b/src/com/hypixel/hytale/server/core/io/netty/HytaleChannelInitializer.java new file mode 100644 index 0000000..c6d2758 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/netty/HytaleChannelInitializer.java @@ -0,0 +1,148 @@ +package com.hypixel.hytale.server.core.io.netty; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.io.PacketStatsRecorder; +import com.hypixel.hytale.protocol.io.netty.PacketDecoder; +import com.hypixel.hytale.protocol.io.netty.PacketEncoder; +import com.hypixel.hytale.protocol.io.netty.ProtocolUtil; +import com.hypixel.hytale.protocol.packets.connection.Disconnect; +import com.hypixel.hytale.protocol.packets.connection.DisconnectType; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.HytaleServerConfig; +import com.hypixel.hytale.server.core.io.PacketStatsRecorderImpl; +import com.hypixel.hytale.server.core.io.handlers.InitialPacketHandler; +import com.hypixel.hytale.server.core.io.transport.QUICTransport; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.handler.codec.quic.QuicChannel; +import io.netty.handler.codec.quic.QuicStreamChannel; +import io.netty.handler.timeout.ReadTimeoutException; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.handler.timeout.TimeoutException; +import io.netty.handler.timeout.WriteTimeoutException; +import java.io.IOException; +import java.nio.channels.ClosedChannelException; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class HytaleChannelInitializer extends ChannelInitializer { + public HytaleChannelInitializer() { + } + + @Override + protected void initChannel(Channel channel) { + if (channel instanceof QuicStreamChannel quicStreamChannel) { + HytaleLogger.getLogger().at(Level.INFO).log("Received stream %s to %s", NettyUtil.formatRemoteAddress(channel), NettyUtil.formatLocalAddress(channel)); + QuicChannel parentChannel = quicStreamChannel.parent(); + X509Certificate clientCert = parentChannel.attr(QUICTransport.CLIENT_CERTIFICATE_ATTR).get(); + if (clientCert != null) { + channel.attr(QUICTransport.CLIENT_CERTIFICATE_ATTR).set(clientCert); + HytaleLogger.getLogger().at(Level.FINE).log("Copied client certificate to stream: %s", clientCert.getSubjectX500Principal().getName()); + } + } else { + HytaleLogger.getLogger() + .at(Level.INFO) + .log("Received connection from %s to %s", NettyUtil.formatRemoteAddress(channel), NettyUtil.formatLocalAddress(channel)); + } + + PacketStatsRecorderImpl statsRecorder = new PacketStatsRecorderImpl(); + channel.attr(PacketStatsRecorder.CHANNEL_KEY).set(statsRecorder); + Duration initialTimeout = HytaleServer.get().getConfig().getConnectionTimeouts().getInitialTimeout(); + channel.pipeline().addLast("timeOut", new ReadTimeoutHandler(initialTimeout.toMillis(), TimeUnit.MILLISECONDS)); + channel.pipeline().addLast("packetDecoder", new PacketDecoder()); + HytaleServerConfig.RateLimitConfig rateLimitConfig = HytaleServer.get().getConfig().getRateLimitConfig(); + if (rateLimitConfig.isEnabled()) { + channel.pipeline().addLast("rateLimit", new RateLimitHandler(rateLimitConfig.getBurstCapacity(), rateLimitConfig.getPacketsPerSecond())); + } + + channel.pipeline().addLast("packetEncoder", new PacketEncoder()); + channel.pipeline().addLast("packetArrayEncoder", NettyUtil.PACKET_ARRAY_ENCODER_INSTANCE); + if (NettyUtil.PACKET_LOGGER.getLevel() != Level.OFF) { + channel.pipeline().addLast("logger", NettyUtil.LOGGER); + } + + InitialPacketHandler playerConnection = new InitialPacketHandler(channel); + channel.pipeline().addLast("handler", new PlayerChannelHandler(playerConnection)); + channel.pipeline().addLast(new HytaleChannelInitializer.ExceptionHandler()); + playerConnection.registered(null); + } + + @Override + public void exceptionCaught(@Nonnull ChannelHandlerContext ctx, Throwable cause) { + HytaleLogger.getLogger().at(Level.WARNING).withCause(cause).log("Got exception from netty pipeline in HytaleChannelInitializer!"); + if (ctx.channel().isWritable()) { + ctx.channel().writeAndFlush(new Disconnect("Internal server error!", DisconnectType.Crash)).addListener(ProtocolUtil.CLOSE_ON_COMPLETE); + } else { + ProtocolUtil.closeApplicationConnection(ctx.channel()); + } + } + + @Override + public void channelInactive(@Nonnull ChannelHandlerContext ctx) throws Exception { + ProtocolUtil.closeApplicationConnection(ctx.channel()); + super.channelInactive(ctx); + } + + private static class ExceptionHandler extends ChannelInboundHandlerAdapter { + private final AtomicBoolean handled = new AtomicBoolean(); + + private ExceptionHandler() { + } + + @Override + public void exceptionCaught(@Nonnull ChannelHandlerContext ctx, Throwable cause) { + if (!(cause instanceof ClosedChannelException)) { + ChannelHandler handler = ctx.pipeline().get("handler"); + String identifier; + if (handler instanceof PlayerChannelHandler) { + identifier = ((PlayerChannelHandler)handler).getHandler().getIdentifier(); + } else { + identifier = NettyUtil.formatRemoteAddress(ctx.channel()); + } + + if (this.handled.getAndSet(true)) { + if (cause instanceof IOException && cause.getMessage() != null) { + String var8 = cause.getMessage(); + switch (var8) { + case "Broken pipe": + case "Connection reset by peer": + case "An existing connection was forcibly closed by the remote host": + return; + } + } + + HytaleLogger.getLogger().at(Level.WARNING).withCause(cause).log("Already handled exception in ExceptionHandler but got another!"); + } else if (cause instanceof TimeoutException) { + boolean readTimeout = cause instanceof ReadTimeoutException; + boolean writeTimeout = cause instanceof WriteTimeoutException; + String msg = readTimeout ? "Read timeout for %s" : (writeTimeout ? "Write timeout for %s" : "Connection timeout for %s"); + HytaleLogger.getLogger().at(Level.INFO).log(msg, identifier); + NettyUtil.CONNECTION_EXCEPTION_LOGGER.at(Level.FINE).withCause(cause).log(msg, identifier); + if (ctx.channel().isWritable()) { + ctx.channel() + .writeAndFlush( + new Disconnect(readTimeout ? "Read timeout" : (writeTimeout ? "Write timeout" : "Connection timeout"), DisconnectType.Crash) + ) + .addListener(ProtocolUtil.CLOSE_ON_COMPLETE); + } else { + ProtocolUtil.closeApplicationConnection(ctx.channel()); + } + } else { + HytaleLogger.getLogger().at(Level.SEVERE).withCause(cause).log("Got exception from netty pipeline in ExceptionHandler: %s", cause.getMessage()); + if (ctx.channel().isWritable()) { + ctx.channel().writeAndFlush(new Disconnect("Internal server error!", DisconnectType.Crash)).addListener(ProtocolUtil.CLOSE_ON_COMPLETE); + } else { + ProtocolUtil.closeApplicationConnection(ctx.channel()); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/io/netty/LatencySimulationHandler.java b/src/com/hypixel/hytale/server/core/io/netty/LatencySimulationHandler.java new file mode 100644 index 0000000..96738d8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/netty/LatencySimulationHandler.java @@ -0,0 +1,142 @@ +package com.hypixel.hytale.server.core.io.netty; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelPromise; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Comparator; +import java.util.concurrent.DelayQueue; +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nonnull; + +public class LatencySimulationHandler extends ChannelDuplexHandler { + public static final String PIPELINE_KEY = "latencySimulator"; + private static final AtomicInteger counter = new AtomicInteger(); + private final DelayQueue delayedQueue = new DelayQueue<>(); + @Nonnull + private final Thread taskThread; + private final long delayNanos; + + public LatencySimulationHandler(long delay, @Nonnull TimeUnit unit) { + this.delayNanos = unit.toNanos(delay); + this.taskThread = new Thread(() -> { + try { + while (!Thread.interrupted()) { + LatencySimulationHandler.DelayedHandler handler = this.delayedQueue.take(); + handler.ctx.executor().execute(handler); + } + } catch (InterruptedException var2) { + Thread.currentThread().interrupt(); + } + }, "latency-simulator-" + counter.getAndIncrement()); + this.taskThread.setDaemon(true); + this.taskThread.start(); + } + + @Override + public void read(ChannelHandlerContext ctx) throws Exception { + this.delayedQueue.offer(new LatencySimulationHandler.DelayedRead(ctx, System.nanoTime() + this.delayNanos)); + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + this.delayedQueue.offer(new LatencySimulationHandler.DelayedWrite(ctx, System.nanoTime() + this.delayNanos, msg, promise)); + } + + @Override + public void flush(ChannelHandlerContext ctx) { + this.delayedQueue.offer(new LatencySimulationHandler.DelayedFlush(ctx, System.nanoTime() + this.delayNanos)); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + super.handlerRemoved(ctx); + this.taskThread.interrupt(); + ObjectArrayList list = new ObjectArrayList<>(this.delayedQueue); + list.sort(Comparator.comparingLong(value -> value.executeAtNanos)); + + for (LatencySimulationHandler.DelayedHandler handler : list) { + handler.run(); + } + } + + @Override + public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { + super.close(ctx, promise); + this.taskThread.interrupt(); + } + + public static void setLatency(@Nonnull Channel channel, long delay, @Nonnull TimeUnit unit) { + ChannelPipeline pipeline = channel.pipeline(); + if (pipeline.get("latencySimulator") == null) { + if (delay > 0L) { + pipeline.addAfter("packetArrayEncoder", "latencySimulator", new LatencySimulationHandler(delay, unit)); + } + } else if (delay <= 0L) { + pipeline.remove("latencySimulator"); + } else { + pipeline.replace("latencySimulator", "latencySimulator", new LatencySimulationHandler(delay, unit)); + } + } + + private static class DelayedFlush extends LatencySimulationHandler.DelayedHandler { + public DelayedFlush(ChannelHandlerContext ctx, long executeAtNanos) { + super(ctx, executeAtNanos); + } + + @Override + public void run() { + this.ctx.flush(); + } + } + + private abstract static class DelayedHandler implements Delayed, Runnable { + protected final ChannelHandlerContext ctx; + protected final long executeAtNanos; + + protected DelayedHandler(ChannelHandlerContext ctx, long executeAtNanos) { + this.ctx = ctx; + this.executeAtNanos = executeAtNanos; + } + + @Override + public long getDelay(@Nonnull TimeUnit unit) { + return unit.convert(this.executeAtNanos - System.nanoTime(), TimeUnit.NANOSECONDS); + } + + public int compareTo(@Nonnull Delayed o) { + return Long.compare(this.executeAtNanos, ((LatencySimulationHandler.DelayedHandler)o).executeAtNanos); + } + } + + private static class DelayedRead extends LatencySimulationHandler.DelayedHandler { + private DelayedRead(ChannelHandlerContext ctx, long executeAtNanos) { + super(ctx, executeAtNanos); + } + + @Override + public void run() { + this.ctx.read(); + } + } + + private static class DelayedWrite extends LatencySimulationHandler.DelayedHandler { + private final Object msg; + private final ChannelPromise promise; + + public DelayedWrite(ChannelHandlerContext ctx, long executeAtNanos, Object msg, ChannelPromise promise) { + super(ctx, executeAtNanos); + this.msg = msg; + this.promise = promise; + } + + @Override + public void run() { + this.ctx.write(this.msg, this.promise); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/io/netty/NettyUtil.java b/src/com/hypixel/hytale/server/core/io/netty/NettyUtil.java new file mode 100644 index 0000000..e6392ff --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/netty/NettyUtil.java @@ -0,0 +1,246 @@ +package com.hypixel.hytale.server.core.io.netty; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.backend.HytaleLoggerBackend; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.util.concurrent.ThreadUtil; +import io.netty.channel.Channel; +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelFactory; +import io.netty.channel.ChannelHandler; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.ServerChannel; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollDatagramChannel; +import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.epoll.EpollServerSocketChannel; +import io.netty.channel.kqueue.KQueue; +import io.netty.channel.kqueue.KQueueDatagramChannel; +import io.netty.channel.kqueue.KQueueEventLoopGroup; +import io.netty.channel.kqueue.KQueueServerSocketChannel; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.DatagramChannel; +import io.netty.channel.socket.SocketProtocolFamily; +import io.netty.channel.socket.nio.NioDatagramChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.quic.QuicChannel; +import io.netty.handler.codec.quic.QuicStreamChannel; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import io.netty.util.internal.ObjectUtil; +import io.netty.util.internal.StringUtil; +import io.netty.util.internal.SystemPropertyUtil; +import java.lang.reflect.Constructor; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Objects; +import java.util.concurrent.ThreadFactory; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NettyUtil { + public static final HytaleLogger CONNECTION_EXCEPTION_LOGGER = HytaleLogger.get("ConnectionExceptionLogging"); + public static final HytaleLogger PACKET_LOGGER = HytaleLogger.get("PacketLogging"); + public static final String PACKET_DECODER = "packetDecoder"; + public static final String PACKET_ARRAY_ENCODER = "packetArrayEncoder"; + public static final PacketArrayEncoder PACKET_ARRAY_ENCODER_INSTANCE = new PacketArrayEncoder(); + public static final String PACKET_ENCODER = "packetEncoder"; + public static final String LOGGER_KEY = "logger"; + public static final LoggingHandler LOGGER = new LoggingHandler("PacketLogging", LogLevel.INFO); + public static final String HANDLER = "handler"; + public static final String TIMEOUT_HANDLER = "timeOut"; + public static final String RATE_LIMIT = "rateLimit"; + + public NettyUtil() { + } + + public static void init() { + } + + private static void injectLogger(@Nonnull Channel channel) { + if (channel.pipeline().get("logger") == null) { + channel.pipeline().addAfter("packetArrayEncoder", "logger", LOGGER); + } + } + + private static void uninjectLogger(@Nonnull Channel channel) { + channel.pipeline().remove("logger"); + } + + public static void setChannelHandler(@Nonnull Channel channel, @Nonnull PacketHandler packetHandler) { + ChannelHandler oldHandler = channel.pipeline().replace("handler", "handler", new PlayerChannelHandler(packetHandler)); + PacketHandler oldPlayerConnection = null; + if (oldHandler instanceof PlayerChannelHandler) { + oldPlayerConnection = ((PlayerChannelHandler)oldHandler).getHandler(); + oldPlayerConnection.unregistered(packetHandler); + } + + packetHandler.registered(oldPlayerConnection); + } + + @Nonnull + public static EventLoopGroup getEventLoopGroup(String name) { + return getEventLoopGroup(0, name); + } + + @Nonnull + public static EventLoopGroup getEventLoopGroup(int nThreads, String name) { + if (nThreads == 0) { + nThreads = Math.max(1, SystemPropertyUtil.getInt("server.io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2)); + } + + ThreadFactory factory = ThreadUtil.daemonCounted(name + " - %d"); + if (Epoll.isAvailable()) { + return new EpollEventLoopGroup(nThreads, factory); + } else { + return (EventLoopGroup)(KQueue.isAvailable() ? new KQueueEventLoopGroup(nThreads, factory) : new NioEventLoopGroup(nThreads, factory)); + } + } + + @Nonnull + public static Class getServerChannel() { + if (Epoll.isAvailable()) { + return EpollServerSocketChannel.class; + } else { + return KQueue.isAvailable() ? KQueueServerSocketChannel.class : NioServerSocketChannel.class; + } + } + + @Nonnull + public static NettyUtil.ReflectiveChannelFactory getDatagramChannelFactory(SocketProtocolFamily family) { + if (Epoll.isAvailable()) { + return new NettyUtil.ReflectiveChannelFactory<>(EpollDatagramChannel.class, family); + } else { + return KQueue.isAvailable() + ? new NettyUtil.ReflectiveChannelFactory<>(KQueueDatagramChannel.class, family) + : new NettyUtil.ReflectiveChannelFactory<>(NioDatagramChannel.class, family); + } + } + + public static String formatRemoteAddress(Channel channel) { + if (channel instanceof QuicChannel quicChannel) { + return quicChannel.remoteAddress() + " (" + quicChannel.remoteSocketAddress() + ")"; + } else { + return channel instanceof QuicStreamChannel quicStreamChannel + ? quicStreamChannel.parent().localAddress() + + " (" + + quicStreamChannel.parent().remoteSocketAddress() + + ", streamId=" + + quicStreamChannel.remoteAddress().streamId() + + ")" + : channel.remoteAddress().toString(); + } + } + + public static String formatLocalAddress(Channel channel) { + if (channel instanceof QuicChannel quicChannel) { + return quicChannel.localAddress() + " (" + quicChannel.localSocketAddress() + ")"; + } else { + return channel instanceof QuicStreamChannel quicStreamChannel + ? quicStreamChannel.parent().localAddress() + + " (" + + quicStreamChannel.parent().localSocketAddress() + + ", streamId=" + + quicStreamChannel.localAddress().streamId() + + ")" + : channel.localAddress().toString(); + } + } + + @Nullable + public static SocketAddress getRemoteSocketAddress(Channel channel) { + if (channel instanceof QuicChannel quicChannel) { + return quicChannel.remoteSocketAddress(); + } else { + return channel instanceof QuicStreamChannel quicStreamChannel ? quicStreamChannel.parent().remoteSocketAddress() : channel.remoteAddress(); + } + } + + public static boolean isFromSameOrigin(Channel channel1, Channel channel2) { + SocketAddress remoteSocketAddress1 = getRemoteSocketAddress(channel1); + SocketAddress remoteSocketAddress2 = getRemoteSocketAddress(channel2); + if (remoteSocketAddress1 == null || remoteSocketAddress2 == null) { + return false; + } else if (Objects.equals(remoteSocketAddress1, remoteSocketAddress2)) { + return true; + } else if (!remoteSocketAddress1.getClass().equals(remoteSocketAddress2.getClass())) { + return false; + } else if (!( + remoteSocketAddress1 instanceof InetSocketAddress remoteInetSocketAddress1 + && remoteSocketAddress2 instanceof InetSocketAddress remoteInetSocketAddress2 + )) { + return false; + } else { + return remoteInetSocketAddress1.getAddress().isLoopbackAddress() && remoteInetSocketAddress2.getAddress().isLoopbackAddress() + ? true + : remoteInetSocketAddress1.getAddress().equals(remoteInetSocketAddress2.getAddress()); + } + } + + static { + HytaleLoggerBackend loggerBackend = HytaleLoggerBackend.getLogger(PACKET_LOGGER.getName()); + loggerBackend.setOnLevelChange((oldLevel, newLevel) -> { + Universe universe = Universe.get(); + if (universe != null) { + if (newLevel == Level.OFF) { + for (PlayerRef p : universe.getPlayers()) { + uninjectLogger(p.getPacketHandler().getChannel()); + } + } else { + for (PlayerRef p : universe.getPlayers()) { + injectLogger(p.getPacketHandler().getChannel()); + } + } + } + }); + PACKET_LOGGER.setLevel(Level.OFF); + loggerBackend.loadLogLevel(); + CONNECTION_EXCEPTION_LOGGER.setLevel(Level.ALL); + } + + public static class ReflectiveChannelFactory implements ChannelFactory { + @Nonnull + private final Constructor constructor; + private final SocketProtocolFamily family; + + public ReflectiveChannelFactory(@Nonnull Class clazz, SocketProtocolFamily family) { + ObjectUtil.checkNotNull(clazz, "clazz"); + + try { + this.constructor = clazz.getConstructor(SocketProtocolFamily.class); + this.family = family; + } catch (NoSuchMethodException var4) { + throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) + " does not have a public non-arg constructor", var4); + } + } + + @Nonnull + @Override + public T newChannel() { + try { + return (T)this.constructor.newInstance(this.family); + } catch (Throwable var2) { + throw new ChannelException("Unable to create Channel from class " + this.constructor.getDeclaringClass(), var2); + } + } + + @Nonnull + public String getSimpleName() { + return StringUtil.simpleClassName(this.constructor.getDeclaringClass()) + "(" + this.family + ")"; + } + + @Nonnull + @Override + public String toString() { + return StringUtil.simpleClassName(io.netty.channel.ReflectiveChannelFactory.class) + + "(" + + StringUtil.simpleClassName(this.constructor.getDeclaringClass()) + + ".class, " + + this.family + + ")"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/io/netty/PacketArrayEncoder.java b/src/com/hypixel/hytale/server/core/io/netty/PacketArrayEncoder.java new file mode 100644 index 0000000..b7aa3ef --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/netty/PacketArrayEncoder.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.server.core.io.netty; + +import com.hypixel.hytale.protocol.Packet; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.handler.codec.MessageToMessageEncoder; +import java.util.List; +import javax.annotation.Nonnull; + +@Sharable +public class PacketArrayEncoder extends MessageToMessageEncoder { + public PacketArrayEncoder() { + } + + protected void encode(ChannelHandlerContext ctx, @Nonnull Packet[] packets, @Nonnull List out) { + for (Packet packet : packets) { + if (packet != null) { + out.add(packet); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/io/netty/PlayerChannelHandler.java b/src/com/hypixel/hytale/server/core/io/netty/PlayerChannelHandler.java new file mode 100644 index 0000000..5f662f2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/netty/PlayerChannelHandler.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.core.io.netty; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.io.adapter.PacketAdapters; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +public class PlayerChannelHandler extends ChannelInboundHandlerAdapter { + private final PacketHandler handler; + + public PlayerChannelHandler(PacketHandler handler) { + this.handler = handler; + } + + public PacketHandler getHandler() { + return this.handler; + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + this.handler.logCloseMessage(); + this.handler.closed(ctx); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + if (ctx.channel().isActive()) { + Packet packet = (Packet)msg; + if (!PacketAdapters.__handleInbound(this.handler, packet)) { + this.handler.handle(packet); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/io/netty/RateLimitHandler.java b/src/com/hypixel/hytale/server/core/io/netty/RateLimitHandler.java new file mode 100644 index 0000000..27a7ef6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/netty/RateLimitHandler.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.core.io.netty; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.io.netty.ProtocolUtil; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import java.util.logging.Level; + +public class RateLimitHandler extends ChannelInboundHandlerAdapter { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final int maxTokens; + private final int refillRate; + private int tokens; + private long lastRefillTime; + + public RateLimitHandler(int maxTokens, int refillRate) { + this.maxTokens = maxTokens; + this.refillRate = refillRate; + this.tokens = maxTokens; + this.lastRefillTime = System.nanoTime(); + } + + private void refillTokens() { + long now = System.nanoTime(); + long elapsedNanos = now - this.lastRefillTime; + long tokensToAdd = elapsedNanos * this.refillRate / 1000000000L; + if (tokensToAdd > 0L) { + this.tokens = (int)Math.min((long)this.maxTokens, this.tokens + tokensToAdd); + this.lastRefillTime = now; + } + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + this.refillTokens(); + if (this.tokens > 0) { + this.tokens--; + ctx.fireChannelRead(msg); + } else { + LOGGER.at(Level.WARNING).log("Rate limit exceeded for %s, disconnecting", NettyUtil.formatRemoteAddress(ctx.channel())); + ProtocolUtil.closeApplicationConnection(ctx.channel(), 1); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/io/transport/QUICTransport.java b/src/com/hypixel/hytale/server/core/io/transport/QUICTransport.java new file mode 100644 index 0000000..5495079 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/transport/QUICTransport.java @@ -0,0 +1,218 @@ +package com.hypixel.hytale.server.core.io.transport; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.io.netty.ProtocolUtil; +import com.hypixel.hytale.protocol.packets.connection.Disconnect; +import com.hypixel.hytale.protocol.packets.connection.DisconnectType; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.auth.CertificateUtil; +import com.hypixel.hytale.server.core.auth.ServerAuthManager; +import com.hypixel.hytale.server.core.io.netty.HytaleChannelInitializer; +import com.hypixel.hytale.server.core.io.netty.NettyUtil; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.socket.DatagramChannel; +import io.netty.channel.socket.SocketProtocolFamily; +import io.netty.channel.socket.nio.NioChannelOption; +import io.netty.handler.codec.quic.InsecureQuicTokenHandler; +import io.netty.handler.codec.quic.QuicChannel; +import io.netty.handler.codec.quic.QuicServerCodecBuilder; +import io.netty.handler.codec.quic.QuicSslContext; +import io.netty.handler.codec.quic.QuicSslContextBuilder; +import io.netty.handler.ssl.ClientAuth; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import io.netty.handler.ssl.util.SelfSignedCertificate; +import io.netty.util.AttributeKey; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetSocketAddress; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.net.ssl.SSLEngine; +import jdk.net.ExtendedSocketOptions; + +public class QUICTransport implements Transport { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final AttributeKey CLIENT_CERTIFICATE_ATTR = AttributeKey.valueOf("CLIENT_CERTIFICATE"); + @Nonnull + private final EventLoopGroup workerGroup = NettyUtil.getEventLoopGroup("ServerWorkerGroup"); + private final Bootstrap bootstrapIpv4; + private final Bootstrap bootstrapIpv6; + + public QUICTransport() throws InterruptedException { + SelfSignedCertificate ssc = null; + + try { + ssc = new SelfSignedCertificate("localhost"); + } catch (CertificateException var5) { + throw new RuntimeException(var5); + } + + ServerAuthManager.getInstance().setServerCertificate(ssc.cert()); + LOGGER.at(Level.INFO).log("Server certificate registered for mutual auth, fingerprint: %s", CertificateUtil.computeCertificateFingerprint(ssc.cert())); + QuicSslContext sslContext = QuicSslContextBuilder.forServer(ssc.key(), null, ssc.cert()) + .applicationProtocols("hytale/1") + .earlyData(false) + .clientAuth(ClientAuth.REQUIRE) + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .build(); + NettyUtil.ReflectiveChannelFactory channelFactoryIpv4 = NettyUtil.getDatagramChannelFactory(SocketProtocolFamily.INET); + LOGGER.at(Level.INFO).log("Using IPv4 Datagram Channel: %s...", channelFactoryIpv4.getSimpleName()); + this.bootstrapIpv4 = new Bootstrap() + .group(this.workerGroup) + .channelFactory(channelFactoryIpv4) + .option(ChannelOption.SO_REUSEADDR, true) + .option(NioChannelOption.of(ExtendedSocketOptions.IP_DONTFRAGMENT), true) + .handler(new QUICTransport.QuicChannelInboundHandlerAdapter(sslContext)) + .validate(); + NettyUtil.ReflectiveChannelFactory channelFactoryIpv6 = NettyUtil.getDatagramChannelFactory(SocketProtocolFamily.INET6); + LOGGER.at(Level.INFO).log("Using IPv6 Datagram Channel: %s...", channelFactoryIpv6.getSimpleName()); + this.bootstrapIpv6 = new Bootstrap() + .group(this.workerGroup) + .channelFactory(channelFactoryIpv6) + .option(ChannelOption.SO_REUSEADDR, true) + .option(NioChannelOption.of(ExtendedSocketOptions.IP_DONTFRAGMENT), true) + .handler(new QUICTransport.QuicChannelInboundHandlerAdapter(sslContext)) + .validate(); + this.bootstrapIpv4.register().sync(); + this.bootstrapIpv6.register().sync(); + } + + @Nonnull + @Override + public TransportType getType() { + return TransportType.QUIC; + } + + @Override + public ChannelFuture bind(@Nonnull InetSocketAddress address) throws InterruptedException { + if (address.getAddress() instanceof Inet4Address) { + return this.bootstrapIpv4.bind(address).sync(); + } else if (address.getAddress() instanceof Inet6Address) { + return this.bootstrapIpv6.bind(address).sync(); + } else { + throw new UnsupportedOperationException("Unsupported address type: " + address.getAddress().getClass()); + } + } + + @Override + public void shutdown() { + LOGGER.at(Level.INFO).log("Shutting down workerGroup..."); + + try { + this.workerGroup.shutdownGracefully(0L, 1L, TimeUnit.SECONDS).await(1L, TimeUnit.SECONDS); + } catch (InterruptedException var2) { + LOGGER.at(Level.SEVERE).withCause(var2).log("Failed to await for listener to close!"); + Thread.currentThread().interrupt(); + } + } + + private static class QuicChannelInboundHandlerAdapter extends ChannelInboundHandlerAdapter { + private final QuicSslContext sslContext; + + public QuicChannelInboundHandlerAdapter(QuicSslContext sslContext) { + this.sslContext = sslContext; + } + + @Override + public boolean isSharable() { + return true; + } + + @Override + public void channelActive(@Nonnull ChannelHandlerContext ctx) throws Exception { + Duration playTimeout = HytaleServer.get().getConfig().getConnectionTimeouts().getPlayTimeout(); + ChannelHandler quicHandler = new QuicServerCodecBuilder() + .sslContext(this.sslContext) + .tokenHandler(InsecureQuicTokenHandler.INSTANCE) + .maxIdleTimeout(playTimeout.toMillis(), TimeUnit.MILLISECONDS) + .ackDelayExponent(3L) + .initialMaxData(524288L) + .initialMaxStreamDataUnidirectional(0L) + .initialMaxStreamsUnidirectional(0L) + .initialMaxStreamDataBidirectionalLocal(131072L) + .initialMaxStreamDataBidirectionalRemote(131072L) + .initialMaxStreamsBidirectional(1L) + .handler( + new ChannelInboundHandlerAdapter() { + @Override + public boolean isSharable() { + return true; + } + + @Override + public void channelActive(@Nonnull ChannelHandlerContext ctx) throws Exception { + QuicChannel channel = (QuicChannel)ctx.channel(); + QUICTransport.LOGGER + .at(Level.INFO) + .log("Received connection from %s to %s", NettyUtil.formatRemoteAddress(channel), NettyUtil.formatLocalAddress(channel)); + X509Certificate clientCert = QuicChannelInboundHandlerAdapter.this.extractClientCertificate(channel); + if (clientCert == null) { + QUICTransport.LOGGER + .at(Level.WARNING) + .log("Connection rejected: no client certificate from %s", NettyUtil.formatRemoteAddress(channel)); + ProtocolUtil.closeConnection(channel); + } else { + channel.attr(QUICTransport.CLIENT_CERTIFICATE_ATTR).set(clientCert); + QUICTransport.LOGGER.at(Level.FINE).log("Client certificate: %s", clientCert.getSubjectX500Principal().getName()); + } + } + + @Override + public void channelInactive(@Nonnull ChannelHandlerContext ctx) { + ((QuicChannel)ctx.channel()).collectStats().addListener(f -> { + if (f.isSuccess()) { + QUICTransport.LOGGER.at(Level.INFO).log("Connection closed: %s", f.getNow()); + } + }); + } + + @Override + public void exceptionCaught(@Nonnull ChannelHandlerContext ctx, Throwable cause) { + QUICTransport.LOGGER.at(Level.WARNING).withCause(cause).log("Got exception from netty pipeline in ChannelInitializer!"); + Channel channel = ctx.channel(); + if (channel.isWritable()) { + channel.writeAndFlush(new Disconnect("Internal server error!", DisconnectType.Crash)).addListener(ProtocolUtil.CLOSE_ON_COMPLETE); + } else { + ProtocolUtil.closeApplicationConnection(channel); + } + } + } + ) + .streamHandler(new HytaleChannelInitializer()) + .build(); + ctx.channel().pipeline().addLast(quicHandler); + } + + @Nullable + private X509Certificate extractClientCertificate(QuicChannel channel) { + try { + SSLEngine sslEngine = channel.sslEngine(); + if (sslEngine == null) { + return null; + } + + Certificate[] peerCerts = sslEngine.getSession().getPeerCertificates(); + if (peerCerts != null && peerCerts.length > 0 && peerCerts[0] instanceof X509Certificate) { + return (X509Certificate)peerCerts[0]; + } + } catch (Exception var4) { + QUICTransport.LOGGER.at(Level.FINEST).log("No peer certificate available: %s", var4.getMessage()); + } + + return null; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/io/transport/TCPTransport.java b/src/com/hypixel/hytale/server/core/io/transport/TCPTransport.java new file mode 100644 index 0000000..65ff39a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/transport/TCPTransport.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.core.io.transport; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.io.netty.HytaleChannelInitializer; +import com.hypixel.hytale.server.core.io.netty.NettyUtil; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.ServerChannel; +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class TCPTransport implements Transport { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private final EventLoopGroup bossGroup = NettyUtil.getEventLoopGroup(1, "ServerBossGroup"); + @Nonnull + private final EventLoopGroup workerGroup = NettyUtil.getEventLoopGroup("ServerWorkerGroup"); + private final ServerBootstrap bootstrap; + + public TCPTransport() throws InterruptedException { + Class serverChannel = NettyUtil.getServerChannel(); + LOGGER.at(Level.INFO).log("Using Server Channel: %s...", serverChannel.getSimpleName()); + this.bootstrap = new ServerBootstrap() + .group(this.bossGroup, this.workerGroup) + .channel(serverChannel) + .option(ChannelOption.SO_BACKLOG, 256) + .option(ChannelOption.SO_REUSEADDR, true) + .childHandler(new HytaleChannelInitializer()) + .validate(); + this.bootstrap.register().sync(); + } + + @Nonnull + @Override + public TransportType getType() { + return TransportType.TCP; + } + + @Override + public ChannelFuture bind(InetSocketAddress address) throws InterruptedException { + return this.bootstrap.bind(address).sync(); + } + + @Override + public void shutdown() { + LOGGER.at(Level.INFO).log("Shutting down bossGroup..."); + + try { + this.bossGroup.shutdownGracefully(0L, 1L, TimeUnit.SECONDS).await(1L, TimeUnit.SECONDS); + } catch (InterruptedException var3) { + LOGGER.at(Level.SEVERE).withCause(var3).log("Failed to await for listener to close!"); + Thread.currentThread().interrupt(); + } + + LOGGER.at(Level.INFO).log("Shutting down workerGroup..."); + + try { + this.workerGroup.shutdownGracefully(0L, 1L, TimeUnit.SECONDS).await(1L, TimeUnit.SECONDS); + } catch (InterruptedException var2) { + LOGGER.at(Level.SEVERE).withCause(var2).log("Failed to await for listener to close!"); + Thread.currentThread().interrupt(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/io/transport/Transport.java b/src/com/hypixel/hytale/server/core/io/transport/Transport.java new file mode 100644 index 0000000..03b91e3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/transport/Transport.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.core.io.transport; + +import io.netty.channel.ChannelFuture; +import java.net.InetSocketAddress; + +public interface Transport { + TransportType getType(); + + ChannelFuture bind(InetSocketAddress var1) throws InterruptedException; + + void shutdown(); +} diff --git a/src/com/hypixel/hytale/server/core/io/transport/TransportType.java b/src/com/hypixel/hytale/server/core/io/transport/TransportType.java new file mode 100644 index 0000000..9fbbd85 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/io/transport/TransportType.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.server.core.io.transport; + +public enum TransportType { + TCP, + QUIC; + + private TransportType() { + } +} diff --git a/src/com/hypixel/hytale/server/core/meta/AbstractMetaStore.java b/src/com/hypixel/hytale/server/core/meta/AbstractMetaStore.java new file mode 100644 index 0000000..c59645e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/meta/AbstractMetaStore.java @@ -0,0 +1,140 @@ +package com.hypixel.hytale.server.core.meta; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.DirectDecodeCodec; +import com.hypixel.hytale.codec.ExtraInfo; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.Map.Entry; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public abstract class AbstractMetaStore implements IMetaStoreImpl { + protected final K parent; + protected final IMetaRegistry registry; + @Nonnull + private final BsonDocument unknownValues; + @Nonnull + private final IntSet notUnknownKeys; + @Nullable + private BsonDocument cachedEncoded; + private boolean dirty; + private boolean bypassEncodedCache; + + public AbstractMetaStore(K parent, IMetaRegistry registry, boolean bypassEncodedCache) { + this.parent = parent; + this.registry = registry; + this.unknownValues = new BsonDocument(); + this.notUnknownKeys = new IntOpenHashSet(); + this.dirty = false; + this.bypassEncodedCache = bypassEncodedCache; + } + + protected abstract T get0(MetaKey var1); + + @Nonnull + @Override + public IMetaStoreImpl getMetaStore() { + return this; + } + + @Override + public IMetaRegistry getRegistry() { + return this.registry; + } + + @Override + public void forEachUnknownEntry(BiConsumer consumer) { + this.unknownValues.forEach(consumer); + } + + @Override + public final void markMetaStoreDirty() { + this.dirty = true; + this.cachedEncoded = null; + } + + @Override + public final boolean consumeMetaStoreDirty() { + boolean previous = this.dirty; + this.dirty = false; + return previous; + } + + protected T decodeOrNewMetaObject(MetaKey key) { + return key instanceof PersistentMetaKey && this.tryDecodeUnknownKey((PersistentMetaKey)key) + ? this.get0(key) + : this.registry.newMetaObject(key, this.parent); + } + + protected boolean tryDecodeUnknownKey(@Nonnull PersistentMetaKey key) { + if (!this.notUnknownKeys.add(key.getId())) { + return false; + } else { + BsonValue value = this.unknownValues.get(key.getKey()); + if (value != null) { + Codec codec = key.getCodec(); + T obj; + if (codec instanceof DirectDecodeCodec) { + obj = this.registry.newMetaObject(key, this.parent); + ((DirectDecodeCodec)codec).decode(value, obj, null); + } else { + obj = codec.decode(value, null); + } + + this.unknownValues.remove(key.getKey()); + this.putMetaObject(key, obj); + return true; + } else { + return false; + } + } + } + + @Nonnull + @Override + public BsonDocument encode(final ExtraInfo extraInfo) { + if (!this.bypassEncodedCache && this.cachedEncoded != null) { + return this.cachedEncoded; + } else { + final BsonDocument document = new BsonDocument(); + document.putAll(this.unknownValues); + this.getRegistry().forEachMetaEntry(this, new IMetaRegistry.MetaEntryConsumer() { + @Override + public void accept(MetaKey key, T value) { + if (key instanceof PersistentMetaKey persistentKey) { + document.put(persistentKey.getKey(), persistentKey.getCodec().encode(value, extraInfo)); + } + } + }); + if (!this.bypassEncodedCache) { + this.cachedEncoded = document; + } + + return document; + } + } + + @Override + public void decode(@Nonnull BsonDocument document, ExtraInfo extraInfo) { + if (!Codec.isNullBsonValue(document)) { + for (Entry entry : document.entrySet()) { + String key = entry.getKey(); + BsonValue value = entry.getValue(); + PersistentMetaKey metaKey = this.getRegistry().getMetaKeyForCodecKey(key); + if (metaKey == null) { + this.unknownValues.put(key, value); + } else if (metaKey.getCodec() instanceof DirectDecodeCodec) { + Object obj = this.registry.newMetaObject(metaKey, this.parent); + ((DirectDecodeCodec)metaKey.getCodec()).decode(value, obj, extraInfo); + this.putMetaObject(metaKey, obj); + } else { + this.putMetaObject(metaKey, metaKey.getCodec().decode(value, extraInfo)); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/meta/ArrayMetaStore.java b/src/com/hypixel/hytale/server/core/meta/ArrayMetaStore.java new file mode 100644 index 0000000..02e091c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/meta/ArrayMetaStore.java @@ -0,0 +1,114 @@ +package com.hypixel.hytale.server.core.meta; + +import com.hypixel.hytale.common.util.ArrayUtil; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ArrayMetaStore extends AbstractMetaStore { + private static final Object NO_ENTRY = new Object(); + private Object[] array = ArrayUtil.emptyArray(); + + public ArrayMetaStore(K parent, IMetaRegistry registry) { + this(parent, registry, false); + } + + public ArrayMetaStore(K parent, IMetaRegistry registry, boolean bypassEncodedCache) { + super(parent, registry, bypassEncodedCache); + } + + @Override + protected T get0(@Nonnull MetaKey key) { + return (T)this.array[key.getId()]; + } + + @Override + public T getMetaObject(@Nonnull MetaKey key) { + int id = key.getId(); + if (id >= this.array.length) { + T obj = this.decodeOrNewMetaObject(key); + this.resizeArray(obj, id); + return obj; + } else { + T obj = this.get0(key); + if (obj == NO_ENTRY) { + this.array[id] = obj = this.decodeOrNewMetaObject(key); + } + + return obj; + } + } + + @Nullable + @Override + public T getIfPresentMetaObject(@Nonnull MetaKey key) { + if (key.getId() >= this.array.length) { + return null; + } else { + T oldObj = this.get0(key); + return oldObj != NO_ENTRY ? oldObj : null; + } + } + + @Nullable + @Override + public T putMetaObject(@Nonnull MetaKey key, T obj) { + this.markMetaStoreDirty(); + int id = key.getId(); + if (id >= this.array.length) { + this.resizeArray(obj, id); + return null; + } else { + T oldObj = (T)this.array[id]; + this.array[id] = obj; + return oldObj != NO_ENTRY ? oldObj : null; + } + } + + @Nullable + @Override + public T removeMetaObject(@Nonnull MetaKey key) { + if (key.getId() >= this.array.length) { + return null; + } else { + this.markMetaStoreDirty(); + T oldObj = (T)this.array[key.getId()]; + this.array[key.getId()] = NO_ENTRY; + return oldObj != NO_ENTRY ? oldObj : null; + } + } + + @Nullable + @Override + public T removeSerializedMetaObject(@Nonnull MetaKey key) { + if (key.getId() >= this.array.length && key instanceof PersistentMetaKey) { + this.tryDecodeUnknownKey((PersistentMetaKey)key); + } + + return this.removeMetaObject(key); + } + + @Override + public boolean hasMetaObject(@Nonnull MetaKey key) { + int id = key.getId(); + return id >= this.array.length ? false : this.array[id] != NO_ENTRY; + } + + @Override + public void forEachMetaObject(@Nonnull IMetaStore.MetaEntryConsumer consumer) { + for (int i = 0; i < this.array.length; i++) { + Object o = this.array[i]; + if (o != NO_ENTRY) { + consumer.accept(i, o); + } + } + } + + private void resizeArray(T obj, int id) { + Object[] arr = new Object[id + 1]; + Arrays.fill(arr, this.array.length, arr.length, NO_ENTRY); + System.arraycopy(this.array, 0, arr, 0, this.array.length); + arr[id] = obj; + this.array = arr; + } +} diff --git a/src/com/hypixel/hytale/server/core/meta/DynamicMetaStore.java b/src/com/hypixel/hytale/server/core/meta/DynamicMetaStore.java new file mode 100644 index 0000000..e1fc23a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/meta/DynamicMetaStore.java @@ -0,0 +1,91 @@ +package com.hypixel.hytale.server.core.meta; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DynamicMetaStore extends AbstractMetaStore { + @Nonnull + private final Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); + + public DynamicMetaStore(K parent, IMetaRegistry registry) { + this(parent, registry, false); + } + + public DynamicMetaStore(K parent, IMetaRegistry registry, boolean bypassEncodedCache) { + super(parent, registry, bypassEncodedCache); + } + + @Override + protected T get0(@Nonnull MetaKey key) { + return (T)this.map.get(key.getId()); + } + + @Override + public T getMetaObject(@Nonnull MetaKey key) { + T o = this.get0(key); + if (o == null) { + this.map.put(key.getId(), o = this.decodeOrNewMetaObject(key)); + } + + return o; + } + + @Override + public T getIfPresentMetaObject(@Nonnull MetaKey key) { + return this.get0(key); + } + + @Override + public T putMetaObject(@Nonnull MetaKey key, T obj) { + this.markMetaStoreDirty(); + return (T)this.map.put(key.getId(), obj); + } + + @Override + public T removeMetaObject(@Nonnull MetaKey key) { + this.markMetaStoreDirty(); + return (T)this.map.remove(key.getId()); + } + + @Nullable + @Override + public T removeSerializedMetaObject(MetaKey key) { + this.markMetaStoreDirty(); + if (key instanceof PersistentMetaKey) { + this.tryDecodeUnknownKey((PersistentMetaKey)key); + } + + return this.removeMetaObject(key); + } + + @Override + public boolean hasMetaObject(@Nonnull MetaKey key) { + return this.map.containsKey(key.getId()); + } + + @Override + public void forEachMetaObject(@Nonnull IMetaStore.MetaEntryConsumer consumer) { + for (Entry entry : this.map.int2ObjectEntrySet()) { + consumer.accept(entry.getIntKey(), entry.getValue()); + } + } + + @Nonnull + public DynamicMetaStore clone(K parent) { + DynamicMetaStore clone = new DynamicMetaStore<>(parent, this.registry); + clone.map.putAll(this.map); + return clone; + } + + public void copyFrom(@Nonnull DynamicMetaStore other) { + this.markMetaStoreDirty(); + if (this.registry != other.registry) { + throw new IllegalArgumentException("Wrong registry used in `copyFrom`."); + } else { + this.map.putAll(other.map); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/meta/IMetaRegistry.java b/src/com/hypixel/hytale/server/core/meta/IMetaRegistry.java new file mode 100644 index 0000000..7df8851 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/meta/IMetaRegistry.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.core.meta; + +import com.hypixel.hytale.codec.Codec; +import java.util.function.Function; +import javax.annotation.Nullable; + +public interface IMetaRegistry { + T newMetaObject(MetaKey var1, K var2); + + void forEachMetaEntry(IMetaStore var1, IMetaRegistry.MetaEntryConsumer var2); + + @Nullable + PersistentMetaKey getMetaKeyForCodecKey(String var1); + + MetaKey registerMetaObject(Function var1, boolean var2, String var3, Codec var4); + + default MetaKey registerMetaObject(Function supplier, String keyName, Codec codec) { + return this.registerMetaObject(supplier, true, keyName, codec); + } + + default MetaKey registerMetaObject(Function supplier) { + return this.registerMetaObject(supplier, false, null, null); + } + + default MetaKey registerMetaObject() { + return this.registerMetaObject(parent -> null); + } + + @FunctionalInterface + public interface MetaEntryConsumer { + void accept(MetaKey var1, T var2); + } +} diff --git a/src/com/hypixel/hytale/server/core/meta/IMetaStore.java b/src/com/hypixel/hytale/server/core/meta/IMetaStore.java new file mode 100644 index 0000000..0f8b09f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/meta/IMetaStore.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.server.core.meta; + +import javax.annotation.Nullable; + +public interface IMetaStore { + IMetaStoreImpl getMetaStore(); + + default T getMetaObject(MetaKey key) { + return this.getMetaStore().getMetaObject(key); + } + + @Nullable + default T getIfPresentMetaObject(MetaKey key) { + return this.getMetaStore().getIfPresentMetaObject(key); + } + + @Nullable + default T putMetaObject(MetaKey key, T obj) { + return this.getMetaStore().putMetaObject(key, obj); + } + + @Nullable + default T removeMetaObject(MetaKey key) { + return this.getMetaStore().removeMetaObject(key); + } + + @Nullable + default T removeSerializedMetaObject(MetaKey key) { + return this.getMetaStore().removeSerializedMetaObject(key); + } + + default boolean hasMetaObject(MetaKey key) { + return this.getMetaStore().hasMetaObject(key); + } + + default void forEachMetaObject(IMetaStore.MetaEntryConsumer consumer) { + this.getMetaStore().forEachMetaObject(consumer); + } + + default void markMetaStoreDirty() { + this.getMetaStore().markMetaStoreDirty(); + } + + default boolean consumeMetaStoreDirty() { + return this.getMetaStore().consumeMetaStoreDirty(); + } + + @FunctionalInterface + public interface MetaEntryConsumer { + void accept(int var1, T var2); + } +} diff --git a/src/com/hypixel/hytale/server/core/meta/IMetaStoreImpl.java b/src/com/hypixel/hytale/server/core/meta/IMetaStoreImpl.java new file mode 100644 index 0000000..d335d5f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/meta/IMetaStoreImpl.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.server.core.meta; + +import com.hypixel.hytale.codec.ExtraInfo; +import java.util.function.BiConsumer; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public interface IMetaStoreImpl extends IMetaStore { + IMetaRegistry getRegistry(); + + void decode(BsonDocument var1, ExtraInfo var2); + + BsonDocument encode(ExtraInfo var1); + + void forEachUnknownEntry(BiConsumer var1); +} diff --git a/src/com/hypixel/hytale/server/core/meta/MetaKey.java b/src/com/hypixel/hytale/server/core/meta/MetaKey.java new file mode 100644 index 0000000..8d6c9af --- /dev/null +++ b/src/com/hypixel/hytale/server/core/meta/MetaKey.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.server.core.meta; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MetaKey { + private final int id; + + MetaKey(int id) { + this.id = id; + } + + public int getId() { + return this.id; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + MetaKey metaKey = (MetaKey)o; + return this.id == metaKey.id; + } else { + return false; + } + } + + @Override + public int hashCode() { + return this.id; + } + + @Nonnull + @Override + public String toString() { + return "MetaKey{id=" + this.id + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/meta/MetaRegistry.java b/src/com/hypixel/hytale/server/core/meta/MetaRegistry.java new file mode 100644 index 0000000..b62f657 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/meta/MetaRegistry.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.server.core.meta; + +import com.hypixel.hytale.codec.Codec; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MetaRegistry implements IMetaRegistry { + private final Map parameterMapping = new Object2ObjectOpenHashMap<>(); + private final List suppliers = new ObjectArrayList<>(); + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + public MetaRegistry() { + } + + @Override + public MetaKey registerMetaObject(Function function, boolean persistent, String keyName, @Nonnull Codec codec) { + this.lock.writeLock().lock(); + + MetaKey var8; + try { + if (persistent && codec == null) { + throw new IllegalStateException("Codec cannot be null if persistence is enabled."); + } + + int metaId = this.suppliers.size(); + MetaKey key; + if (persistent) { + key = new PersistentMetaKey<>(metaId, keyName, codec); + } else { + key = new MetaKey<>(metaId); + } + + MetaRegistry.MetaRegistryEntry metaEntry = new MetaRegistry.MetaRegistryEntry<>(function, key); + this.suppliers.add(metaEntry); + if (persistent) { + if (this.parameterMapping.containsKey(keyName)) { + throw new IllegalStateException("Codec key is already registered. Given: " + keyName); + } + + this.parameterMapping.put(keyName, metaEntry); + } + + var8 = metaEntry.getKey(); + } finally { + this.lock.writeLock().unlock(); + } + + return var8; + } + + @Override + public T newMetaObject(@Nonnull MetaKey key, K parent) { + this.lock.readLock().lock(); + + Object var3; + try { + var3 = this.suppliers.get(key.getId()).getFunction().apply(parent); + } finally { + this.lock.readLock().unlock(); + } + + return (T)var3; + } + + @Override + public void forEachMetaEntry(@Nonnull IMetaStore store, @Nonnull final IMetaRegistry.MetaEntryConsumer consumer) { + store.forEachMetaObject(new IMetaStore.MetaEntryConsumer() { + @Override + public void accept(int id, T value) { + MetaRegistry.MetaRegistryEntry entry = MetaRegistry.this.suppliers.get(id); + consumer.accept(entry.getKey(), value); + } + }); + } + + @Nullable + @Override + public PersistentMetaKey getMetaKeyForCodecKey(String codecKey) { + MetaRegistry.MetaRegistryEntry entry = this.parameterMapping.get(codecKey); + return entry == null ? null : (PersistentMetaKey)entry.getKey(); + } + + private class MetaRegistryEntry { + private final Function function; + private final MetaKey key; + + public MetaRegistryEntry(Function function, MetaKey key) { + this.function = function; + this.key = key; + } + + public Function getFunction() { + return this.function; + } + + public MetaKey getKey() { + return this.key; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/meta/PersistentMetaKey.java b/src/com/hypixel/hytale/server/core/meta/PersistentMetaKey.java new file mode 100644 index 0000000..b212b1a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/meta/PersistentMetaKey.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.core.meta; + +import com.hypixel.hytale.codec.Codec; +import javax.annotation.Nonnull; + +public class PersistentMetaKey extends MetaKey { + private final String key; + private final Codec codec; + + PersistentMetaKey(int id, String key, Codec codec) { + super(id); + this.key = key; + this.codec = codec; + } + + public String getKey() { + return this.key; + } + + public Codec getCodec() { + return this.codec; + } + + @Nonnull + @Override + public String toString() { + return "PersistentMetaKey{key=" + this.key + "codec=" + this.codec + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/LegacyModule.java b/src/com/hypixel/hytale/server/core/modules/LegacyModule.java new file mode 100644 index 0000000..e7c2333 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/LegacyModule.java @@ -0,0 +1,277 @@ +package com.hypixel.hytale.server.core.modules; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.store.StoredCodec; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.RootDependency; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.modules.migrations.ChunkColumnMigrationSystem; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.EntityChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.environment.EnvironmentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.blockpositions.BlockPositionProvider; +import com.hypixel.hytale.server.core.universe.world.chunk.systems.ChunkSystems; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Set; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class LegacyModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(LegacyModule.class).build(); + private static LegacyModule instance; + private ComponentType worldChunkComponentType; + private ComponentType blockChunkComponentType; + private ComponentType entityChunkComponentType; + private ComponentType blockComponentChunkComponentType; + private ComponentType environmentChunkComponentType; + private ComponentType chunkColumnComponentType; + private ComponentType chunkSectionComponentType; + private ComponentType blockSectionComponentType; + private ComponentType fluidSectionComponentType; + private ComponentType blockPositionProviderComponentType; + + public static LegacyModule get() { + return instance; + } + + public LegacyModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + this.worldChunkComponentType = this.getChunkStoreRegistry().registerComponent(WorldChunk.class, "WorldChunk", WorldChunk.CODEC); + this.blockChunkComponentType = this.getChunkStoreRegistry().registerComponent(BlockChunk.class, "BlockChunk", BlockChunk.CODEC); + this.entityChunkComponentType = this.getChunkStoreRegistry().registerComponent(EntityChunk.class, "EntityChunk", EntityChunk.CODEC); + this.blockComponentChunkComponentType = this.getChunkStoreRegistry() + .registerComponent(BlockComponentChunk.class, "BlockComponentChunk", BlockComponentChunk.CODEC); + this.environmentChunkComponentType = this.getChunkStoreRegistry().registerComponent(EnvironmentChunk.class, "EnvironmentChunk", EnvironmentChunk.CODEC); + this.chunkColumnComponentType = this.getChunkStoreRegistry().registerComponent(ChunkColumn.class, "ChunkColumn", ChunkColumn.CODEC); + this.chunkSectionComponentType = this.getChunkStoreRegistry().registerComponent(ChunkSection.class, "ChunkSection", ChunkSection.CODEC); + this.blockSectionComponentType = this.getChunkStoreRegistry().registerComponent(BlockSection.class, "Block", BlockSection.CODEC); + this.fluidSectionComponentType = this.getChunkStoreRegistry().registerComponent(FluidSection.class, "Fluid", FluidSection.CODEC); + this.blockPositionProviderComponentType = this.getChunkStoreRegistry().registerComponent(BlockPositionProvider.class, () -> { + throw new UnsupportedOperationException("BlockPositionProvider cannot be constructed"); + }); + this.getChunkStoreRegistry().registerSystem(new ChunkSystems.OnNewChunk()); + this.getChunkStoreRegistry().registerSystem(new ChunkSystems.OnChunkLoad()); + this.getChunkStoreRegistry().registerSystem(new ChunkSystems.OnNonTicking()); + this.getChunkStoreRegistry().registerSystem(new ChunkSystems.EnsureBlockSection()); + this.getChunkStoreRegistry().registerSystem(new LegacyModule.MigrateLegacySections()); + this.getChunkStoreRegistry().registerSystem(new ChunkSystems.LoadBlockSection()); + this.getChunkStoreRegistry().registerSystem(new ChunkSystems.ReplicateChanges()); + this.getChunkStoreRegistry().registerSystem(new BlockChunk.LoadBlockChunkPacketSystem(this.blockChunkComponentType)); + this.getChunkStoreRegistry().registerSystem(new EntityChunk.EntityChunkLoadingSystem()); + this.getChunkStoreRegistry().registerSystem(new BlockComponentChunk.BlockComponentChunkLoadingSystem()); + this.getChunkStoreRegistry().registerSystem(new BlockComponentChunk.LoadBlockComponentPacketSystem(this.blockComponentChunkComponentType)); + this.getChunkStoreRegistry().registerSystem(new BlockComponentChunk.UnloadBlockComponentPacketSystem(this.blockComponentChunkComponentType)); + ComponentType legacyBlockStateComponentType = this.getChunkStoreRegistry() + .registerComponent(LegacyModule.LegacyBlockStateChunk.class, "BlockStateChunk", LegacyModule.LegacyBlockStateChunk.CODEC, true); + this.getChunkStoreRegistry() + .registerSystem(new LegacyModule.MigrateLegacyBlockStateChunkSystem(legacyBlockStateComponentType, this.blockComponentChunkComponentType)); + } + + public ComponentType getWorldChunkComponentType() { + return this.worldChunkComponentType; + } + + public ComponentType getBlockChunkComponentType() { + return this.blockChunkComponentType; + } + + public ComponentType getEntityChunkComponentType() { + return this.entityChunkComponentType; + } + + public ComponentType getBlockComponentChunkComponentType() { + return this.blockComponentChunkComponentType; + } + + public ComponentType getEnvironmentChunkComponentType() { + return this.environmentChunkComponentType; + } + + public ComponentType getChunkColumnComponentType() { + return this.chunkColumnComponentType; + } + + public ComponentType getChunkSectionComponentType() { + return this.chunkSectionComponentType; + } + + public ComponentType getBlockSectionComponentType() { + return this.blockSectionComponentType; + } + + public ComponentType getFluidSectionComponentType() { + return this.fluidSectionComponentType; + } + + public ComponentType getBlockPositionProviderComponentType() { + return this.blockPositionProviderComponentType; + } + + private static class LegacyBlockStateChunk implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder( + LegacyModule.LegacyBlockStateChunk.class, LegacyModule.LegacyBlockStateChunk::new + ) + .addField( + new KeyedCodec<>("States", new ArrayCodec<>(new StoredCodec<>(ChunkStore.HOLDER_CODEC_KEY), Holder[]::new)), + (entityChunk, array) -> entityChunk.holders = array, + entityChunk -> { + throw new UnsupportedOperationException("Serialise is not allowed for BlockStateChunk"); + } + ) + .build(); + public Holder[] holders; + + public LegacyBlockStateChunk() { + } + + public LegacyBlockStateChunk(Holder[] holders) { + this.holders = holders; + } + + @Nonnull + @Override + public Component clone() { + Holder[] newHolders = new Holder[this.holders.length]; + + for (int i = 0; i < this.holders.length; i++) { + newHolders[i] = this.holders[i].clone(); + } + + return new LegacyModule.LegacyBlockStateChunk(newHolders); + } + } + + private static class MigrateLegacyBlockStateChunkSystem extends ChunkColumnMigrationSystem { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final ComponentType legacyComponentType; + private final ComponentType componentType; + private final Archetype archetype; + + public MigrateLegacyBlockStateChunkSystem( + ComponentType legacyComponentType, ComponentType componentType + ) { + this.legacyComponentType = legacyComponentType; + this.componentType = componentType; + this.archetype = Archetype.of(legacyComponentType, WorldChunk.getComponentType()); + } + + @Override + public Query getQuery() { + return this.archetype; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + LegacyModule.LegacyBlockStateChunk component = holder.getComponent(this.legacyComponentType); + + assert component != null; + + holder.removeComponent(this.legacyComponentType); + Int2ObjectOpenHashMap> holders = new Int2ObjectOpenHashMap<>(); + + for (Holder blockComponentHolder : component.holders) { + BlockState blockState = BlockState.getBlockState(blockComponentHolder); + Vector3i position = blockState.__internal_getPosition(); + if (position == null) { + LOGGER.at(Level.SEVERE).log("Skipping migration for BlockState with null position!", blockComponentHolder); + } else { + holders.put(blockState.getIndex(), blockComponentHolder); + } + } + + BlockComponentChunk blockComponentChunk = new BlockComponentChunk(holders, new Int2ObjectOpenHashMap<>()); + holder.addComponent(this.componentType, blockComponentChunk); + holder.getComponent(WorldChunk.getComponentType()).setBlockComponentChunk(blockComponentChunk); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.firstSet(); + } + } + + @Deprecated(forRemoval = true) + public static class MigrateLegacySections extends ChunkColumnMigrationSystem { + private final Query QUERY = Query.and(ChunkColumn.getComponentType(), BlockChunk.getComponentType()); + private final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, ChunkSystems.OnNewChunk.class), RootDependency.first() + ); + + public MigrateLegacySections() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + ChunkColumn column = holder.getComponent(ChunkColumn.getComponentType()); + + assert column != null; + + BlockChunk blockChunk = holder.getComponent(BlockChunk.getComponentType()); + + assert blockChunk != null; + + Holder[] sections = column.getSectionHolders(); + BlockSection[] migratedSections = blockChunk.takeMigratedSections(); + if (migratedSections != null) { + for (int i = 0; i < sections.length; i++) { + Holder section = sections[i]; + BlockSection blockSection = migratedSections[i]; + if (section != null && blockSection != null) { + section.putComponent(BlockSection.getComponentType(), blockSection); + blockChunk.markNeedsSaving(); + } + } + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.DEPENDENCIES; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/AccessControlModule.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/AccessControlModule.java new file mode 100644 index 0000000..991545d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/AccessControlModule.java @@ -0,0 +1,113 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol; + +import com.google.gson.JsonObject; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.server.core.event.events.player.PlayerSetupConnectEvent; +import com.hypixel.hytale.server.core.modules.accesscontrol.ban.Ban; +import com.hypixel.hytale.server.core.modules.accesscontrol.ban.BanParser; +import com.hypixel.hytale.server.core.modules.accesscontrol.ban.InfiniteBan; +import com.hypixel.hytale.server.core.modules.accesscontrol.ban.TimedBan; +import com.hypixel.hytale.server.core.modules.accesscontrol.commands.BanCommand; +import com.hypixel.hytale.server.core.modules.accesscontrol.commands.UnbanCommand; +import com.hypixel.hytale.server.core.modules.accesscontrol.commands.WhitelistCommand; +import com.hypixel.hytale.server.core.modules.accesscontrol.provider.AccessProvider; +import com.hypixel.hytale.server.core.modules.accesscontrol.provider.HytaleBanProvider; +import com.hypixel.hytale.server.core.modules.accesscontrol.provider.HytaleWhitelistProvider; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import javax.annotation.Nonnull; + +public class AccessControlModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(AccessControlModule.class).build(); + private static AccessControlModule instance; + private final HytaleWhitelistProvider whitelistProvider = new HytaleWhitelistProvider(); + private final HytaleBanProvider banProvider = new HytaleBanProvider(); + private final List providerRegistry = new CopyOnWriteArrayList() { + { + this.add(AccessControlModule.this.whitelistProvider); + this.add(AccessControlModule.this.banProvider); + } + }; + private final Map parsers = new ConcurrentHashMap<>(); + + public static AccessControlModule get() { + return instance; + } + + public AccessControlModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + this.getCommandRegistry().registerCommand(new BanCommand(this.banProvider)); + this.getCommandRegistry().registerCommand(new UnbanCommand(this.banProvider)); + this.getCommandRegistry().registerCommand(new WhitelistCommand(this.whitelistProvider)); + this.registerBanParser("timed", TimedBan::fromJsonObject); + this.registerBanParser("infinite", InfiniteBan::fromJsonObject); + this.getEventRegistry().register(PlayerSetupConnectEvent.class, event -> { + CompletableFuture> completableFuture = this.getDisconnectReason(event.getUuid()); + Optional disconnectReason = completableFuture.join(); + if (disconnectReason.isPresent()) { + event.setReason(disconnectReason.get()); + event.setCancelled(true); + } + }); + } + + @Override + protected void start() { + this.whitelistProvider.syncLoad(); + this.banProvider.syncLoad(); + } + + @Override + protected void shutdown() { + this.whitelistProvider.syncSave(); + this.banProvider.syncSave(); + } + + public void registerBanParser(String type, BanParser banParser) { + BanParser currentParser = this.parsers.get(type); + if (currentParser != null) { + throw new IllegalArgumentException("Type \"" + type + "\" is already registered by " + currentParser.getClass()); + } else { + this.parsers.put(type, banParser); + } + } + + public void registerAccessProvider(AccessProvider provider) { + this.providerRegistry.add(provider); + } + + public Ban parseBan(String type, JsonObject object) { + BanParser parser = this.parsers.get(type); + if (parser == null) { + throw new IllegalArgumentException("No BanParser for type: " + type); + } else { + return parser.parse(object); + } + } + + @Nonnull + private CompletableFuture> getDisconnectReason(UUID uuid) { + return this.providerRegistry + .stream() + .map(p -> p.getDisconnectReason(uuid)) + .reduce(CompletableFuture.completedFuture(Optional.empty()), (a, b) -> a.thenCombine(b, (aMessage, bMessage) -> { + if (aMessage.isPresent()) { + return (Optional)aMessage; + } else { + return (Optional)(bMessage.isPresent() ? bMessage : Optional.empty()); + } + })); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/ban/AbstractBan.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/ban/AbstractBan.java new file mode 100644 index 0000000..4606d70 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/ban/AbstractBan.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.ban; + +import com.google.gson.JsonObject; +import java.time.Instant; +import java.util.Optional; +import java.util.UUID; +import javax.annotation.Nonnull; + +abstract class AbstractBan implements Ban { + protected final UUID target; + protected final UUID by; + protected final Instant timestamp; + @Nonnull + protected final Optional reason; + + public AbstractBan(UUID target, UUID by, Instant timestamp, String reason) { + this.target = target; + this.by = by; + this.timestamp = timestamp; + this.reason = Optional.ofNullable(reason); + } + + @Override + public UUID getTarget() { + return this.target; + } + + @Override + public UUID getBy() { + return this.by; + } + + @Override + public Instant getTimestamp() { + return this.timestamp; + } + + @Nonnull + @Override + public Optional getReason() { + return this.reason; + } + + @Nonnull + @Override + public JsonObject toJsonObject() { + JsonObject object = new JsonObject(); + object.addProperty("type", this.getType()); + object.addProperty("target", this.target.toString()); + object.addProperty("by", this.by.toString()); + object.addProperty("timestamp", this.timestamp.toEpochMilli()); + this.reason.ifPresent(s -> object.addProperty("reason", s)); + return object; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/ban/Ban.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/ban/Ban.java new file mode 100644 index 0000000..345982d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/ban/Ban.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.ban; + +import com.google.gson.JsonObject; +import com.hypixel.hytale.server.core.modules.accesscontrol.provider.AccessProvider; +import java.time.Instant; +import java.util.Optional; +import java.util.UUID; + +public interface Ban extends AccessProvider { + UUID getTarget(); + + UUID getBy(); + + Instant getTimestamp(); + + boolean isInEffect(); + + Optional getReason(); + + String getType(); + + JsonObject toJsonObject(); +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/ban/BanParser.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/ban/BanParser.java new file mode 100644 index 0000000..3743a56 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/ban/BanParser.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.ban; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +@FunctionalInterface +public interface BanParser { + Ban parse(JsonObject var1) throws JsonParseException; +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/ban/InfiniteBan.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/ban/InfiniteBan.java new file mode 100644 index 0000000..7a4dbc2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/ban/InfiniteBan.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.ban; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import java.time.Instant; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class InfiniteBan extends AbstractBan { + @Nonnull + public static InfiniteBan fromJsonObject(@Nonnull JsonObject object) throws JsonParseException { + try { + UUID target = UUID.fromString(object.get("target").getAsString()); + UUID by = UUID.fromString(object.get("by").getAsString()); + Instant timestamp = Instant.ofEpochMilli(object.get("timestamp").getAsLong()); + String reason = null; + if (object.has("reason")) { + reason = object.get("reason").getAsString(); + } + + return new InfiniteBan(target, by, timestamp, reason); + } catch (Throwable var5) { + throw new JsonParseException(var5); + } + } + + public InfiniteBan(UUID target, UUID by, Instant timestamp, String reason) { + super(target, by, timestamp, reason); + } + + @Override + public boolean isInEffect() { + return true; + } + + @Nonnull + @Override + public String getType() { + return "infinite"; + } + + @Nonnull + @Override + public CompletableFuture> getDisconnectReason(UUID uuid) { + StringBuilder message = new StringBuilder("You are permanently banned!"); + this.reason.ifPresent(s -> message.append(" Reason: ").append(s)); + return CompletableFuture.completedFuture(Optional.of(message.toString())); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/ban/TimedBan.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/ban/TimedBan.java new file mode 100644 index 0000000..be023da --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/ban/TimedBan.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.ban; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.hypixel.hytale.common.util.StringUtil; +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class TimedBan extends AbstractBan { + private final Instant expiresOn; + + @Nonnull + public static TimedBan fromJsonObject(@Nonnull JsonObject object) throws JsonParseException { + try { + UUID target = UUID.fromString(object.get("target").getAsString()); + UUID by = UUID.fromString(object.get("by").getAsString()); + Instant timestamp = Instant.ofEpochMilli(object.get("timestamp").getAsLong()); + Instant expiresOn = Instant.ofEpochMilli(object.get("expiresOn").getAsLong()); + String reason = null; + if (object.has("reason")) { + reason = object.get("reason").getAsString(); + } + + return new TimedBan(target, by, timestamp, expiresOn, reason); + } catch (Throwable var6) { + throw new JsonParseException(var6); + } + } + + public TimedBan(UUID target, UUID by, Instant timestamp, Instant expiresOn, String reason) { + super(target, by, timestamp, reason); + this.expiresOn = expiresOn; + } + + @Override + public boolean isInEffect() { + return this.expiresOn.isAfter(Instant.now()); + } + + @Nonnull + @Override + public String getType() { + return "timed"; + } + + public Instant getExpiresOn() { + return this.expiresOn; + } + + @Nonnull + @Override + public CompletableFuture> getDisconnectReason(UUID uuid) { + Duration timeRemaining = Duration.between(Instant.now(), this.expiresOn); + StringBuilder message = new StringBuilder("You are temporarily banned for ").append(StringUtil.humanizeTime(timeRemaining)).append('!'); + this.reason.ifPresent(s -> message.append(" Reason: ").append(s)); + return CompletableFuture.completedFuture(Optional.of(message.toString())); + } + + @Nonnull + @Override + public JsonObject toJsonObject() { + JsonObject object = super.toJsonObject(); + object.addProperty("expiresOn", this.expiresOn.toEpochMilli()); + return object; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/BanCommand.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/BanCommand.java new file mode 100644 index 0000000..464871d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/BanCommand.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.modules.accesscontrol.ban.InfiniteBan; +import com.hypixel.hytale.server.core.modules.accesscontrol.provider.HytaleBanProvider; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.util.AuthUtil; +import java.time.Instant; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class BanCommand extends AbstractAsyncCommand { + @Nonnull + private final HytaleBanProvider banProvider; + @Nonnull + private final RequiredArg usernameArg = this.withRequiredArg("username", "server.commands.ban.username.desc", ArgTypes.STRING); + @Nonnull + private final OptionalArg reasonArg = this.withOptionalArg("reason", "server.commands.ban.reason.desc", ArgTypes.STRING); + + public BanCommand(@Nonnull HytaleBanProvider banProvider) { + super("ban", "server.commands.ban.desc"); + this.setUnavailableInSingleplayer(true); + this.banProvider = banProvider; + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + String username = this.usernameArg.get(context); + String rawInput = context.getInputString(); + int usernameIndex = rawInput.indexOf(username); + String reason; + if (usernameIndex != -1 && usernameIndex + username.length() < rawInput.length()) { + String afterUsername = rawInput.substring(usernameIndex + username.length()).trim(); + reason = afterUsername.isEmpty() ? "No reason." : afterUsername; + } else { + reason = "No reason."; + } + + return AuthUtil.lookupUuid(username).thenCompose(uuid -> { + if (this.banProvider.hasBan(uuid)) { + context.sendMessage(Message.translation("server.modules.ban.alreadyBanned").param("name", username)); + return CompletableFuture.completedFuture(null); + } else { + InfiniteBan ban = new InfiniteBan(uuid, context.sender().getUuid(), Instant.now(), reason); + this.banProvider.modify(banMap -> { + banMap.put(uuid, ban); + return true; + }); + PlayerRef player = Universe.get().getPlayer(uuid); + if (player != null) { + CompletableFuture> disconnectReason = ban.getDisconnectReason(uuid); + return disconnectReason.whenComplete((string, disconnectEx) -> { + Optional optional = (Optional)string; + if (disconnectEx != null) { + context.sendMessage(Message.translation("server.modules.ban.failedDisconnectReason").param("name", username)); + disconnectEx.printStackTrace(); + } + + if (string == null || !string.isPresent()) { + optional = Optional.of("Failed to get disconnect reason."); + } + + player.getPacketHandler().disconnect(optional.get()); + context.sendMessage(Message.translation("server.modules.ban.bannedWithReason").param("name", username).param("reason", reason)); + }).thenApply(v -> null); + } else { + context.sendMessage(Message.translation("server.modules.ban.bannedWithReason").param("name", username).param("reason", reason)); + return CompletableFuture.completedFuture(null); + } + } + }); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/UnbanCommand.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/UnbanCommand.java new file mode 100644 index 0000000..15ded9b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/UnbanCommand.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.modules.accesscontrol.provider.HytaleBanProvider; +import com.hypixel.hytale.server.core.util.AuthUtil; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class UnbanCommand extends AbstractAsyncCommand { + @Nonnull + private final HytaleBanProvider banProvider; + @Nonnull + private final RequiredArg usernameArg = this.withRequiredArg("username", "server.commands.unban.username.desc", ArgTypes.STRING); + + public UnbanCommand(@Nonnull HytaleBanProvider banProvider) { + super("unban", "server.commands.unban.desc"); + this.setUnavailableInSingleplayer(true); + this.banProvider = banProvider; + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + String username = this.usernameArg.get(context); + return AuthUtil.lookupUuid(username).thenAccept(uuid -> { + if (!this.banProvider.hasBan(uuid)) { + context.sendMessage(Message.translation("server.modules.unban.playerNotBanned").param("name", username)); + } else { + this.banProvider.modify(map -> map.remove(uuid) != null); + context.sendMessage(Message.translation("server.modules.unban.success").param("name", username)); + } + }).exceptionally(ex -> { + context.sendMessage(Message.translation("server.modules.ban.lookupFailed").param("name", username)); + ex.printStackTrace(); + return null; + }); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistAddCommand.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistAddCommand.java new file mode 100644 index 0000000..42ebbff --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistAddCommand.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.modules.accesscontrol.provider.HytaleWhitelistProvider; +import com.hypixel.hytale.server.core.util.AuthUtil; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class WhitelistAddCommand extends AbstractAsyncCommand { + @Nonnull + private final HytaleWhitelistProvider whitelistProvider; + @Nonnull + private final RequiredArg usernameArg = this.withRequiredArg("username", "server.commands.whitelist.add.username.desc", ArgTypes.STRING); + + public WhitelistAddCommand(@Nonnull HytaleWhitelistProvider whitelistProvider) { + super("add", "server.commands.whitelist.add.desc"); + this.whitelistProvider = whitelistProvider; + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + String username = this.usernameArg.get(context); + return AuthUtil.lookupUuid(username).thenAccept(uuid -> { + if (this.whitelistProvider.modify(list -> list.add(uuid))) { + context.sendMessage(Message.translation("server.modules.whitelist.addSuccess").param("name", username)); + } else { + context.sendMessage(Message.translation("server.modules.whitelist.alreadyWhitelisted").param("name", username)); + } + }).exceptionally(ex -> { + context.sendMessage(Message.translation("server.modules.ban.lookupFailed").param("name", username)); + ex.printStackTrace(); + return null; + }); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistClearCommand.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistClearCommand.java new file mode 100644 index 0000000..112908e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistClearCommand.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.modules.accesscontrol.provider.HytaleWhitelistProvider; +import javax.annotation.Nonnull; + +public class WhitelistClearCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_MODULES_WHITELIST_CLEARED = Message.translation("server.modules.whitelist.cleared"); + @Nonnull + private final HytaleWhitelistProvider whitelistProvider; + + public WhitelistClearCommand(@Nonnull HytaleWhitelistProvider whitelistProvider) { + super("clear", "server.commands.whitelist.clear.desc"); + this.whitelistProvider = whitelistProvider; + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + this.whitelistProvider.modify(list -> { + list.clear(); + return true; + }); + context.sendMessage(MESSAGE_MODULES_WHITELIST_CLEARED); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistCommand.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistCommand.java new file mode 100644 index 0000000..b19223b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistCommand.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.modules.accesscontrol.provider.HytaleWhitelistProvider; +import javax.annotation.Nonnull; + +public class WhitelistCommand extends AbstractCommandCollection { + public WhitelistCommand(@Nonnull HytaleWhitelistProvider whitelistProvider) { + super("whitelist", "server.commands.whitelist.desc"); + this.addSubCommand(new WhitelistAddCommand(whitelistProvider)); + this.addSubCommand(new WhitelistRemoveCommand(whitelistProvider)); + this.addSubCommand(new WhitelistEnableCommand(whitelistProvider)); + this.addSubCommand(new WhitelistDisableCommand(whitelistProvider)); + this.addSubCommand(new WhitelistClearCommand(whitelistProvider)); + this.addSubCommand(new WhitelistStatusCommand(whitelistProvider)); + this.addSubCommand(new WhitelistListCommand(whitelistProvider)); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistDisableCommand.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistDisableCommand.java new file mode 100644 index 0000000..3795f9e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistDisableCommand.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.modules.accesscontrol.provider.HytaleWhitelistProvider; +import javax.annotation.Nonnull; + +public class WhitelistDisableCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_MODULES_WHITELIST_DISABLED = Message.translation("server.modules.whitelist.disabled"); + @Nonnull + private static final Message MESSAGE_MODULES_WHITELIST_ALREADY_DISABLED = Message.translation("server.modules.whitelist.alreadyDisabled"); + @Nonnull + private final HytaleWhitelistProvider whitelistProvider; + + public WhitelistDisableCommand(@Nonnull HytaleWhitelistProvider whitelistProvider) { + super("disable", "server.commands.whitelist.disable.desc"); + this.addAliases("off"); + this.setUnavailableInSingleplayer(true); + this.whitelistProvider = whitelistProvider; + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + if (this.whitelistProvider.isEnabled()) { + this.whitelistProvider.setEnabled(false); + context.sendMessage(MESSAGE_MODULES_WHITELIST_DISABLED); + } else { + context.sendMessage(MESSAGE_MODULES_WHITELIST_ALREADY_DISABLED); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistEnableCommand.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistEnableCommand.java new file mode 100644 index 0000000..4add8c0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistEnableCommand.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.modules.accesscontrol.provider.HytaleWhitelistProvider; +import javax.annotation.Nonnull; + +public class WhitelistEnableCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_MODULES_WHITELIST_ALREADY_ENABLED = Message.translation("server.modules.whitelist.alreadyEnabled"); + @Nonnull + private static final Message MESSAGE_MODULES_WHITELIST_ENABLED = Message.translation("server.modules.whitelist.enabled"); + @Nonnull + private final HytaleWhitelistProvider whitelistProvider; + + public WhitelistEnableCommand(@Nonnull HytaleWhitelistProvider whitelistProvider) { + super("enable", "server.commands.whitelist.enable.desc"); + this.addAliases("on"); + this.setUnavailableInSingleplayer(true); + this.whitelistProvider = whitelistProvider; + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + if (this.whitelistProvider.isEnabled()) { + context.sendMessage(MESSAGE_MODULES_WHITELIST_ALREADY_ENABLED); + } else { + this.whitelistProvider.setEnabled(true); + context.sendMessage(MESSAGE_MODULES_WHITELIST_ENABLED); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistListCommand.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistListCommand.java new file mode 100644 index 0000000..0828499 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistListCommand.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.modules.accesscontrol.provider.HytaleWhitelistProvider; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class WhitelistListCommand extends CommandBase { + @Nonnull + private final HytaleWhitelistProvider whitelistProvider; + + public WhitelistListCommand(@Nonnull HytaleWhitelistProvider whitelistProvider) { + super("list", "server.commands.whitelist.list.desc"); + this.whitelistProvider = whitelistProvider; + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + Set whitelist = this.whitelistProvider.getList(); + if (!whitelist.isEmpty() && whitelist.size() <= 10) { + context.sendMessage(Message.translation("server.modules.whitelist.list").param("whitelist", whitelist.toString())); + } else { + context.sendMessage(Message.translation("server.modules.whitelist.size").param("size", whitelist.size())); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistRemoveCommand.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistRemoveCommand.java new file mode 100644 index 0000000..d82caba --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistRemoveCommand.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.modules.accesscontrol.provider.HytaleWhitelistProvider; +import com.hypixel.hytale.server.core.util.AuthUtil; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class WhitelistRemoveCommand extends AbstractAsyncCommand { + @Nonnull + private final HytaleWhitelistProvider whitelistProvider; + @Nonnull + private final RequiredArg usernameArg = this.withRequiredArg("username", "server.commands.whitelist.remove.username.desc", ArgTypes.STRING); + + public WhitelistRemoveCommand(@Nonnull HytaleWhitelistProvider whitelistProvider) { + super("remove", "server.commands.whitelist.remove.desc"); + this.whitelistProvider = whitelistProvider; + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + String username = this.usernameArg.get(context); + return AuthUtil.lookupUuid(username).thenAccept(uuid -> { + if (this.whitelistProvider.modify(list -> list.remove(uuid))) { + context.sendMessage(Message.translation("server.modules.whitelist.removalSuccess").param("uuid", uuid.toString())); + } else { + context.sendMessage(Message.translation("server.modules.whitelist.uuidNotWhitelisted").param("uuid", uuid.toString())); + } + }).exceptionally(ex -> { + context.sendMessage(Message.translation("server.modules.ban.lookupFailed").param("name", username)); + ex.printStackTrace(); + return null; + }); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistStatusCommand.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistStatusCommand.java new file mode 100644 index 0000000..a7ff067 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/commands/WhitelistStatusCommand.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.modules.accesscontrol.provider.HytaleWhitelistProvider; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import javax.annotation.Nonnull; + +public class WhitelistStatusCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_MODULES_WHITELIST_STATUS = Message.translation("server.modules.whitelist.status"); + @Nonnull + private final HytaleWhitelistProvider whitelistProvider; + + public WhitelistStatusCommand(@Nonnull HytaleWhitelistProvider whitelistProvider) { + super("status", "server.commands.whitelist.status.desc"); + this.whitelistProvider = whitelistProvider; + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + context.sendMessage(MESSAGE_MODULES_WHITELIST_STATUS.param("status", MessageFormat.enabled(this.whitelistProvider.isEnabled()))); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/provider/AccessProvider.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/provider/AccessProvider.java new file mode 100644 index 0000000..dbe2b6d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/provider/AccessProvider.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.provider; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public interface AccessProvider { + @Nonnull + CompletableFuture> getDisconnectReason(UUID var1); +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/provider/ClientDelegatingProvider.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/provider/ClientDelegatingProvider.java new file mode 100644 index 0000000..15bab13 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/provider/ClientDelegatingProvider.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.provider; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class ClientDelegatingProvider implements AccessProvider { + public ClientDelegatingProvider() { + } + + @Nonnull + @Override + public CompletableFuture> getDisconnectReason(UUID uuid) { + return CompletableFuture.completedFuture(Optional.empty()); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/provider/HytaleBanProvider.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/provider/HytaleBanProvider.java new file mode 100644 index 0000000..b401dc2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/provider/HytaleBanProvider.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.provider; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonWriter; +import com.hypixel.hytale.server.core.modules.accesscontrol.AccessControlModule; +import com.hypixel.hytale.server.core.modules.accesscontrol.ban.Ban; +import com.hypixel.hytale.server.core.util.io.BlockingDiskFile; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public class HytaleBanProvider extends BlockingDiskFile implements AccessProvider { + private final Map bans = new Object2ObjectOpenHashMap<>(); + + public HytaleBanProvider() { + super(Paths.get("bans.json")); + } + + @Nonnull + @Override + public CompletableFuture> getDisconnectReason(UUID uuid) { + Ban ban = this.bans.get(uuid); + if (ban != null && !ban.isInEffect()) { + this.bans.remove(uuid); + ban = null; + } + + return ban != null ? ban.getDisconnectReason(uuid) : CompletableFuture.completedFuture(Optional.empty()); + } + + @Override + protected void read(@Nonnull BufferedReader fileReader) { + JsonParser.parseReader(fileReader).getAsJsonArray().forEach(entry -> { + JsonObject jsonObject = entry.getAsJsonObject(); + + try { + Ban ban = AccessControlModule.get().parseBan(jsonObject.get("type").getAsString(), jsonObject); + Objects.requireNonNull(ban.getBy(), "Ban has null getBy"); + Objects.requireNonNull(ban.getTarget(), "Ban has null getTarget"); + if (ban.isInEffect()) { + this.bans.put(ban.getTarget(), ban); + } + } catch (Exception var4) { + throw new RuntimeException("Failed to parse ban!", var4); + } + }); + } + + @Override + protected void write(@Nonnull BufferedWriter fileWriter) throws IOException { + JsonArray array = new JsonArray(); + this.bans.forEach((key, value) -> array.add(value.toJsonObject())); + fileWriter.write(array.toString()); + } + + @Override + protected void create(@Nonnull BufferedWriter fileWriter) throws IOException { + try (JsonWriter jsonWriter = new JsonWriter(fileWriter)) { + jsonWriter.beginArray().endArray(); + } + } + + public boolean hasBan(UUID uuid) { + this.fileLock.readLock().lock(); + + boolean var2; + try { + var2 = this.bans.containsKey(uuid); + } finally { + this.fileLock.readLock().unlock(); + } + + return var2; + } + + public boolean modify(@Nonnull Function, Boolean> function) { + this.fileLock.writeLock().lock(); + + boolean modified; + try { + modified = function.apply(this.bans); + } finally { + this.fileLock.writeLock().unlock(); + } + + if (modified) { + this.syncSave(); + } + + return modified; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/accesscontrol/provider/HytaleWhitelistProvider.java b/src/com/hypixel/hytale/server/core/modules/accesscontrol/provider/HytaleWhitelistProvider.java new file mode 100644 index 0000000..d45c7b3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/accesscontrol/provider/HytaleWhitelistProvider.java @@ -0,0 +1,136 @@ +package com.hypixel.hytale.server.core.modules.accesscontrol.provider; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonWriter; +import com.hypixel.hytale.server.core.util.io.BlockingDiskFile; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public class HytaleWhitelistProvider extends BlockingDiskFile implements AccessProvider { + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final Set whitelist = new HashSet<>(); + private boolean isEnabled; + + public HytaleWhitelistProvider() { + super(Paths.get("whitelist.json")); + } + + @Override + protected void read(@Nonnull BufferedReader fileReader) { + if (JsonParser.parseReader(fileReader) instanceof JsonObject jsonObject) { + this.isEnabled = jsonObject.get("enabled").getAsBoolean(); + jsonObject.get("list").getAsJsonArray().forEach(entry -> this.whitelist.add(UUID.fromString(entry.getAsString()))); + } else { + throw new JsonParseException("element is not JsonObject!"); + } + } + + @Override + protected void write(@Nonnull BufferedWriter fileWriter) throws IOException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("enabled", this.isEnabled); + JsonArray jsonArray = new JsonArray(); + + for (UUID uuid : this.whitelist) { + jsonArray.add(uuid.toString()); + } + + jsonObject.add("list", jsonArray); + fileWriter.write(jsonObject.toString()); + } + + @Override + protected void create(@Nonnull BufferedWriter fileWriter) throws IOException { + try (JsonWriter jsonWriter = new JsonWriter(fileWriter)) { + jsonWriter.beginObject().name("enabled").value(false).name("list").beginArray().endArray().endObject(); + } + } + + @Nonnull + @Override + public CompletableFuture> getDisconnectReason(UUID uuid) { + this.lock.readLock().lock(); + + CompletableFuture var2; + try { + if (!this.isEnabled || this.whitelist.contains(uuid)) { + return CompletableFuture.completedFuture(Optional.empty()); + } + + var2 = CompletableFuture.completedFuture(Optional.of("You are not whitelisted!")); + } finally { + this.lock.readLock().unlock(); + } + + return var2; + } + + public void setEnabled(boolean isEnabled) { + this.lock.writeLock().lock(); + + try { + this.isEnabled = isEnabled; + } finally { + this.lock.writeLock().unlock(); + } + } + + public boolean modify(@Nonnull Function, Boolean> consumer) { + this.lock.writeLock().lock(); + + boolean result; + try { + result = consumer.apply(this.whitelist); + } finally { + this.lock.writeLock().unlock(); + } + + if (result) { + this.syncSave(); + } + + return result; + } + + @Nonnull + public Set getList() { + this.lock.readLock().lock(); + + Set var1; + try { + var1 = Collections.unmodifiableSet(this.whitelist); + } finally { + this.lock.readLock().unlock(); + } + + return var1; + } + + public boolean isEnabled() { + this.lock.readLock().lock(); + + boolean var1; + try { + var1 = this.isEnabled; + } finally { + this.lock.readLock().unlock(); + } + + return var1; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/block/BlockModule.java b/src/com/hypixel/hytale/server/core/modules/block/BlockModule.java new file mode 100644 index 0000000..f711d8e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/block/BlockModule.java @@ -0,0 +1,386 @@ +package com.hypixel.hytale.server.core.modules.block; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemType; +import com.hypixel.hytale.component.data.unknown.UnknownComponents; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.event.EventPriority; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.StateData; +import com.hypixel.hytale.server.core.modules.LegacyModule; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.events.AddWorldEvent; +import com.hypixel.hytale.server.core.universe.world.events.ChunkPreLoadProcessEvent; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule; +import com.hypixel.hytale.server.core.universe.world.meta.state.BlockMapMarker; +import com.hypixel.hytale.server.core.universe.world.meta.state.BlockMapMarkersResource; +import com.hypixel.hytale.server.core.universe.world.meta.state.LaunchPad; +import com.hypixel.hytale.server.core.universe.world.meta.state.RespawnBlock; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(BlockModule.class).depends(LegacyModule.class).build(); + private static BlockModule instance; + private SystemType migrationSystemType; + private ComponentType launchPadComponentType; + private ComponentType respawnBlockComponentType; + private ComponentType blockMapMarkerComponentType; + private ResourceType blockMapMarkersResourceType; + private ComponentType blockStateInfoComponentType; + private ResourceType blockStateInfoNeedRebuildResourceType; + + public static BlockModule get() { + return instance; + } + + public BlockModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + this.migrationSystemType = this.getChunkStoreRegistry().registerSystemType(BlockModule.MigrationSystem.class); + this.blockStateInfoComponentType = this.getChunkStoreRegistry().registerComponent(BlockModule.BlockStateInfo.class, () -> { + throw new UnsupportedOperationException(); + }); + this.getChunkStoreRegistry().registerSystem(new BlockModule.BlockStateInfoRefSystem(this.blockStateInfoComponentType)); + this.launchPadComponentType = this.getChunkStoreRegistry().registerComponent(LaunchPad.class, "LaunchPad", LaunchPad.CODEC); + this.getChunkStoreRegistry().registerSystem(new BlockModule.MigrateLaunchPad()); + this.respawnBlockComponentType = this.getChunkStoreRegistry().registerComponent(RespawnBlock.class, "RespawnBlock", RespawnBlock.CODEC); + this.getChunkStoreRegistry().registerSystem(new RespawnBlock.OnRemove()); + this.blockMapMarkerComponentType = this.getChunkStoreRegistry().registerComponent(BlockMapMarker.class, "BlockMapMarker", BlockMapMarker.CODEC); + this.blockMapMarkersResourceType = this.getChunkStoreRegistry() + .registerResource(BlockMapMarkersResource.class, "BlockMapMarkers", BlockMapMarkersResource.CODEC); + this.getChunkStoreRegistry().registerSystem(new BlockMapMarker.OnAddRemove()); + this.getEventRegistry() + .registerGlobal( + AddWorldEvent.class, + event -> event.getWorld().getWorldMapManager().getMarkerProviders().put("blockMapMarkers", BlockMapMarker.MarkerProvider.INSTANCE) + ); + this.blockStateInfoNeedRebuildResourceType = this.getChunkStoreRegistry() + .registerResource(BlockModule.BlockStateInfoNeedRebuild.class, BlockModule.BlockStateInfoNeedRebuild::new); + this.getEventRegistry().registerGlobal(EventPriority.EARLY, ChunkPreLoadProcessEvent.class, BlockModule::onChunkPreLoadProcessEnsureBlockEntity); + } + + @Deprecated + public static Ref ensureBlockEntity(WorldChunk chunk, int x, int y, int z) { + Ref blockRef = chunk.getBlockComponentEntity(x, y, z); + if (blockRef != null) { + return blockRef; + } else { + BlockType blockType = chunk.getBlockType(x, y, z); + if (blockType == null) { + return null; + } else if (blockType.getBlockEntity() != null) { + Holder data = blockType.getBlockEntity().clone(); + data.putComponent( + BlockModule.BlockStateInfo.getComponentType(), new BlockModule.BlockStateInfo(ChunkUtil.indexBlockInColumn(x, y, z), chunk.getReference()) + ); + return chunk.getWorld().getChunkStore().getStore().addEntity(data, AddReason.SPAWN); + } else { + BlockState state = BlockState.ensureState(chunk, x, y, z); + return state != null ? state.getReference() : null; + } + } + } + + private static void onChunkPreLoadProcessEnsureBlockEntity(@Nonnull ChunkPreLoadProcessEvent event) { + if (event.isNewlyGenerated()) { + BlockTypeAssetMap blockTypeAssetMap = BlockType.getAssetMap(); + Holder holder = event.getHolder(); + WorldChunk chunk = event.getChunk(); + ChunkColumn column = holder.getComponent(ChunkColumn.getComponentType()); + if (column != null) { + Holder[] sections = column.getSectionHolders(); + if (sections != null) { + BlockComponentChunk blockComponentModule = holder.getComponent(BlockComponentChunk.getComponentType()); + + for (int sectionIndex = 0; sectionIndex < 10; sectionIndex++) { + BlockSection section = sections[sectionIndex].ensureAndGetComponent(BlockSection.getComponentType()); + if (!section.isSolidAir()) { + int sectionYBlock = sectionIndex << 5; + + for (int sectionY = 0; sectionY < 32; sectionY++) { + int y = sectionYBlock | sectionY; + + for (int z = 0; z < 32; z++) { + for (int x = 0; x < 32; x++) { + int blockId = section.get(x, y, z); + BlockType blockType = blockTypeAssetMap.getAsset(blockId); + if (blockType != null && !blockType.isUnknown() && section.getFiller(x, y, z) == 0) { + int index = ChunkUtil.indexBlockInColumn(x, y, z); + if (blockType.getBlockEntity() != null) { + if (blockComponentModule.getEntityHolder(index) != null) { + continue; + } + + blockComponentModule.addEntityHolder(index, blockType.getBlockEntity().clone()); + } + + StateData state = blockType.getState(); + if (state != null && state.getId() != null && blockComponentModule.getEntityHolder(index) == null) { + Vector3i position = new Vector3i(x, y, z); + BlockState blockState = BlockStateModule.get().createBlockState(state.getId(), chunk, position, blockType); + if (blockState != null) { + blockComponentModule.addEntityHolder(index, blockState.toHolder()); + } + } + } + } + } + } + } + } + } + } + } + } + + public SystemType getMigrationSystemType() { + return this.migrationSystemType; + } + + public ComponentType getBlockStateInfoComponentType() { + return this.blockStateInfoComponentType; + } + + public ComponentType getLaunchPadComponentType() { + return this.launchPadComponentType; + } + + public ComponentType getRespawnBlockComponentType() { + return this.respawnBlockComponentType; + } + + public ComponentType getBlockMapMarkerComponentType() { + return this.blockMapMarkerComponentType; + } + + public ResourceType getBlockMapMarkersResourceType() { + return this.blockMapMarkersResourceType; + } + + public ResourceType getBlockStateInfoNeedRebuildResourceType() { + return this.blockStateInfoNeedRebuildResourceType; + } + + @Nullable + public static Ref getBlockEntity(@Nonnull World world, int x, int y, int z) { + ChunkStore chunkStore = world.getChunkStore(); + Ref chunkRef = chunkStore.getChunkReference(ChunkUtil.indexChunkFromBlock(x, z)); + if (chunkRef == null) { + return null; + } else { + BlockComponentChunk blockComponentChunk = chunkStore.getStore().getComponent(chunkRef, BlockComponentChunk.getComponentType()); + if (blockComponentChunk == null) { + return null; + } else { + int blockIndex = ChunkUtil.indexBlockInColumn(x, y, z); + Ref blockRef = blockComponentChunk.getEntityReference(blockIndex); + return blockRef != null && blockRef.isValid() ? blockRef : null; + } + } + } + + @Nullable + public > T getComponent(ComponentType componentType, World world, int x, int y, int z) { + Store chunkStore = world.getChunkStore().getStore(); + Ref chunkRef = world.getChunkStore().getChunkReference(ChunkUtil.indexChunkFromBlock(x, z)); + BlockComponentChunk blockComponentChunk = chunkStore.getComponent(chunkRef, BlockComponentChunk.getComponentType()); + if (blockComponentChunk == null) { + return null; + } else { + int blockIndex = ChunkUtil.indexBlockInColumn(x, y, z); + Ref blockRef = blockComponentChunk.getEntityReference(blockIndex); + return blockRef != null && blockRef.isValid() ? chunkStore.getComponent(blockRef, componentType) : null; + } + } + + public static class BlockStateInfo implements Component { + private final int index; + @Nonnull + private final Ref chunkRef; + + public static ComponentType getComponentType() { + return BlockModule.get().getBlockStateInfoComponentType(); + } + + public BlockStateInfo(int index, @Nonnull Ref chunkRef) { + Objects.requireNonNull(chunkRef); + this.index = index; + this.chunkRef = chunkRef; + } + + public int getIndex() { + return this.index; + } + + @Nonnull + public Ref getChunkRef() { + return this.chunkRef; + } + + public void markNeedsSaving() { + if (this.chunkRef != null && this.chunkRef.isValid()) { + BlockComponentChunk blockComponentChunk = this.chunkRef.getStore().getComponent(this.chunkRef, BlockComponentChunk.getComponentType()); + if (blockComponentChunk != null) { + blockComponentChunk.markNeedsSaving(); + } + } + } + + @Nonnull + @Override + public Component clone() { + return new BlockModule.BlockStateInfo(this.index, this.chunkRef); + } + } + + public static class BlockStateInfoNeedRebuild implements Resource { + private boolean needRebuild; + + public static ResourceType getResourceType() { + return BlockModule.get().getBlockStateInfoNeedRebuildResourceType(); + } + + public BlockStateInfoNeedRebuild() { + this.needRebuild = false; + } + + public BlockStateInfoNeedRebuild(boolean needRebuild) { + this.needRebuild = needRebuild; + } + + public boolean invalidateAndReturnIfNeedRebuild() { + if (this.needRebuild) { + this.needRebuild = false; + return true; + } else { + return false; + } + } + + public void markAsNeedRebuild() { + this.needRebuild = true; + } + + @Override + public Resource clone() { + return new BlockModule.BlockStateInfoNeedRebuild(this.needRebuild); + } + } + + public static class BlockStateInfoRefSystem extends RefSystem { + private final ComponentType componentType; + + public BlockStateInfoRefSystem(ComponentType componentType) { + this.componentType = componentType; + } + + @Override + public Query getQuery() { + return this.componentType; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + BlockModule.BlockStateInfo blockState = commandBuffer.getComponent(ref, this.componentType); + Ref chunk = blockState.chunkRef; + if (chunk != null) { + BlockComponentChunk blockComponentChunk = commandBuffer.getComponent(chunk, BlockComponentChunk.getComponentType()); + switch (reason) { + case SPAWN: + blockComponentChunk.addEntityReference(blockState.getIndex(), ref); + break; + case LOAD: + blockComponentChunk.loadEntityReference(blockState.getIndex(), ref); + } + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + BlockModule.BlockStateInfo blockState = commandBuffer.getComponent(ref, this.componentType); + Ref chunk = blockState.chunkRef; + if (chunk != null) { + BlockComponentChunk blockComponentChunk = commandBuffer.getComponent(chunk, BlockComponentChunk.getComponentType()); + switch (reason) { + case REMOVE: + blockComponentChunk.removeEntityReference(blockState.getIndex(), ref); + break; + case UNLOAD: + blockComponentChunk.unloadEntityReference(blockState.getIndex(), ref); + } + } + } + + @Nonnull + @Override + public String toString() { + return "BlockStateInfoRefSystem{componentType=" + this.componentType + "}"; + } + } + + @Deprecated(forRemoval = true) + public static class MigrateLaunchPad extends BlockModule.MigrationSystem { + public MigrateLaunchPad() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + UnknownComponents unknown = holder.getComponent(ChunkStore.REGISTRY.getUnknownComponentType()); + + assert unknown != null; + + LaunchPad launchPad = unknown.removeComponent("launchPad", LaunchPad.CODEC); + if (launchPad != null) { + holder.putComponent(LaunchPad.getComponentType(), launchPad); + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nullable + @Override + public Query getQuery() { + return ChunkStore.REGISTRY.getUnknownComponentType(); + } + } + + public abstract static class MigrationSystem extends HolderSystem { + public MigrationSystem() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/block/system/ItemContainerStateSpatialSystem.java b/src/com/hypixel/hytale/server/core/modules/block/system/ItemContainerStateSpatialSystem.java new file mode 100644 index 0000000..075c3d0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/block/system/ItemContainerStateSpatialSystem.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.core.modules.block.system; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.spatial.SpatialSystem; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule; +import com.hypixel.hytale.server.core.universe.world.meta.state.ItemContainerState; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemContainerStateSpatialSystem extends SpatialSystem { + @Nonnull + public static final Query QUERY = (Query)Objects.requireNonNull(BlockStateModule.get().getComponentType(ItemContainerState.class)); + + public ItemContainerStateSpatialSystem(ResourceType, ChunkStore>> resourceType) { + super(resourceType); + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + if (store.getResource(BlockModule.BlockStateInfoNeedRebuild.getResourceType()).invalidateAndReturnIfNeedRebuild()) { + super.tick(dt, systemIndex, store); + } + } + + @Override + public Vector3d getPosition(@Nonnull ArchetypeChunk archetypeChunk, int index) { + BlockModule.BlockStateInfo blockInfo = archetypeChunk.getComponent(index, BlockModule.BlockStateInfo.getComponentType()); + Ref chunkRef = blockInfo.getChunkRef(); + if (chunkRef != null && chunkRef.isValid()) { + BlockChunk blockChunk = chunkRef.getStore().getComponent(chunkRef, BlockChunk.getComponentType()); + int worldX = blockChunk.getX() << 5 | ChunkUtil.xFromBlockInColumn(blockInfo.getIndex()); + int worldY = ChunkUtil.yFromBlockInColumn(blockInfo.getIndex()); + int worldZ = blockChunk.getZ() << 5 | ChunkUtil.zFromBlockInColumn(blockInfo.getIndex()); + return new Vector3d(worldX, worldY, worldZ); + } else { + return null; + } + } + + @Nullable + @Override + public Query getQuery() { + return QUERY; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/blockhealth/BlockHealth.java b/src/com/hypixel/hytale/server/core/modules/blockhealth/BlockHealth.java new file mode 100644 index 0000000..fcbc5de --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/blockhealth/BlockHealth.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.server.core.modules.blockhealth; + +import com.hypixel.hytale.math.util.MathUtil; +import io.netty.buffer.ByteBuf; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class BlockHealth implements Cloneable { + public static final BlockHealth NO_DAMAGE_INSTANCE = new BlockHealth(1.0F, Instant.MIN) { + @Override + public void setHealth(float health) { + throw new UnsupportedOperationException("NO_DAMAGE_INSTANCE is immutable!"); + } + + @Override + public void setLastDamageGameTime(Instant lastDamageGameTime) { + throw new UnsupportedOperationException("NO_DAMAGE_INSTANCE is immutable!"); + } + }; + private float health; + private Instant lastDamageGameTime; + + public BlockHealth() { + this(1.0F, Instant.MIN); + } + + public BlockHealth(float health, Instant lastDamageGameTime) { + this.health = health; + this.lastDamageGameTime = lastDamageGameTime; + } + + public float getHealth() { + return this.health; + } + + public void setHealth(float health) { + this.health = health; + } + + public Instant getLastDamageGameTime() { + return this.lastDamageGameTime; + } + + public void setLastDamageGameTime(Instant lastDamageGameTime) { + this.lastDamageGameTime = lastDamageGameTime; + } + + public boolean isDestroyed() { + return MathUtil.closeToZero(this.health) || this.health < 0.0F; + } + + public boolean isFullHealth() { + return this.health >= 1.0; + } + + public void deserialize(@Nonnull ByteBuf buf, byte version) { + this.health = buf.readFloat(); + this.lastDamageGameTime = Instant.ofEpochMilli(buf.readLong()); + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloat(this.health); + buf.writeLong(this.lastDamageGameTime.toEpochMilli()); + } + + @Nonnull + protected BlockHealth clone() { + return new BlockHealth(this.health, this.lastDamageGameTime); + } + + @Nonnull + @Override + public String toString() { + return "BlockHealth{health=" + this.health + ", lastDamageGameTime=" + this.lastDamageGameTime + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/blockhealth/BlockHealthChunk.java b/src/com/hypixel/hytale/server/core/modules/blockhealth/BlockHealthChunk.java new file mode 100644 index 0000000..44e9b5b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/blockhealth/BlockHealthChunk.java @@ -0,0 +1,194 @@ +package com.hypixel.hytale.server.core.modules.blockhealth; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.packets.world.UpdateBlockDamage; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.util.io.ByteBufUtil; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import java.util.function.Predicate; +import javax.annotation.Nonnull; + +public class BlockHealthChunk implements Component { + private static final byte SERIALIZATION_VERSION = 2; + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockHealthChunk.class, BlockHealthChunk::new) + .append(new KeyedCodec<>("Data", Codec.BYTE_ARRAY), BlockHealthChunk::deserialize, BlockHealthChunk::serialize) + .documentation("Binary data representing the state of this BlockHealthChunk") + .add() + .append(new KeyedCodec<>("LastRepairGameTime", Codec.INSTANT), (o, l) -> o.lastRepairGameTime = l, o -> o.lastRepairGameTime) + .documentation("The last tick of the world this BlockHealthChunk processed.") + .add() + .build(); + private final Map blockHealthMap = new Object2ObjectOpenHashMap<>(0); + private final Map blockFragilityMap = new Object2ObjectOpenHashMap<>(0); + private Instant lastRepairGameTime; + + public BlockHealthChunk() { + } + + public Instant getLastRepairGameTime() { + return this.lastRepairGameTime; + } + + public void setLastRepairGameTime(Instant lastRepairGameTime) { + this.lastRepairGameTime = lastRepairGameTime; + } + + @Nonnull + public Map getBlockHealthMap() { + return this.blockHealthMap; + } + + @Nonnull + public Map getBlockFragilityMap() { + return this.blockFragilityMap; + } + + @Nonnull + public BlockHealth damageBlock(Instant currentUptime, @Nonnull World world, @Nonnull Vector3i block, float health) { + BlockHealth blockHealth = this.blockHealthMap.compute(block, (key, value) -> { + if (value == null) { + value = new BlockHealth(); + } + + value.setHealth(value.getHealth() - health); + value.setLastDamageGameTime(currentUptime); + return (BlockHealth)(value.getHealth() < 1.0 ? value : null); + }); + if (blockHealth != null && !blockHealth.isDestroyed()) { + Predicate filter = player -> true; + world.getNotificationHandler().updateBlockDamage(block.getX(), block.getY(), block.getZ(), blockHealth.getHealth(), -health, filter); + } + + return Objects.requireNonNullElse(blockHealth, BlockHealth.NO_DAMAGE_INSTANCE); + } + + @Nonnull + public BlockHealth repairBlock(@Nonnull World world, @Nonnull Vector3i block, float progress) { + BlockHealth blockHealth = Objects.requireNonNullElse(this.blockHealthMap.computeIfPresent(block, (key, value) -> { + value.setHealth(value.getHealth() + progress); + return (BlockHealth)(value.getHealth() > 1.0 ? value : null); + }), BlockHealth.NO_DAMAGE_INSTANCE); + world.getNotificationHandler().updateBlockDamage(block.getX(), block.getY(), block.getZ(), blockHealth.getHealth(), progress); + return blockHealth; + } + + public void removeBlock(@Nonnull World world, @Nonnull Vector3i block) { + if (this.blockHealthMap.remove(block) != null) { + world.getNotificationHandler().updateBlockDamage(block.getX(), block.getY(), block.getZ(), BlockHealth.NO_DAMAGE_INSTANCE.getHealth(), 0.0F); + } + } + + public void makeBlockFragile(Vector3i blockLocation, float fragileDuration) { + this.blockFragilityMap.compute(blockLocation, (key, value) -> { + if (value == null) { + value = new FragileBlock(fragileDuration); + } + + value.setDurationSeconds(fragileDuration); + return (FragileBlock)(value.getDurationSeconds() <= 0.0 ? null : value); + }); + } + + public boolean isBlockFragile(Vector3i block) { + return this.blockFragilityMap.get(block) != null; + } + + public float getBlockHealth(Vector3i block) { + return this.blockHealthMap.getOrDefault(block, BlockHealth.NO_DAMAGE_INSTANCE).getHealth(); + } + + public void createBlockDamagePackets(@Nonnull List list) { + for (Entry entry : this.blockHealthMap.entrySet()) { + Vector3i block = entry.getKey(); + BlockPosition blockPosition = new BlockPosition(block.getX(), block.getY(), block.getZ()); + list.add(new UpdateBlockDamage(blockPosition, entry.getValue().getHealth(), 0.0F)); + } + } + + @Nonnull + public BlockHealthChunk clone() { + BlockHealthChunk copy = new BlockHealthChunk(); + copy.lastRepairGameTime = this.lastRepairGameTime; + + for (Entry entry : this.blockHealthMap.entrySet()) { + copy.blockHealthMap.put(entry.getKey(), entry.getValue().clone()); + } + + for (Entry entry : this.blockFragilityMap.entrySet()) { + copy.blockFragilityMap.put(entry.getKey(), entry.getValue().clone()); + } + + return copy; + } + + public void deserialize(@Nonnull byte[] data) { + this.blockHealthMap.clear(); + ByteBuf buf = Unpooled.wrappedBuffer(data); + byte version = buf.readByte(); + int healthEntries = buf.readInt(); + + for (int i = 0; i < healthEntries; i++) { + int x = buf.readInt(); + int y = buf.readInt(); + int z = buf.readInt(); + BlockHealth bh = new BlockHealth(); + bh.deserialize(buf, version); + this.blockHealthMap.put(new Vector3i(x, y, z), bh); + } + + if (version > 1) { + int fragilityEntries = buf.readInt(); + + for (int i = 0; i < fragilityEntries; i++) { + int x = buf.readInt(); + int y = buf.readInt(); + int z = buf.readInt(); + FragileBlock fragileBlock = new FragileBlock(); + fragileBlock.deserialize(buf, version); + this.blockFragilityMap.put(new Vector3i(x, y, z), fragileBlock); + } + } + } + + public byte[] serialize() { + ByteBuf buf = Unpooled.buffer(); + buf.writeByte(2); + buf.writeInt(this.blockHealthMap.size()); + + for (Entry entry : this.blockHealthMap.entrySet()) { + Vector3i vec = entry.getKey(); + buf.writeInt(vec.x); + buf.writeInt(vec.y); + buf.writeInt(vec.z); + BlockHealth bh = entry.getValue(); + bh.serialize(buf); + } + + buf.writeInt(this.blockFragilityMap.size()); + + for (Entry entry : this.blockFragilityMap.entrySet()) { + Vector3i vec = entry.getKey(); + buf.writeInt(vec.x); + buf.writeInt(vec.y); + buf.writeInt(vec.z); + entry.getValue().serialize(buf); + } + + return ByteBufUtil.getBytesRelease(buf); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/blockhealth/BlockHealthModule.java b/src/com/hypixel/hytale/server/core/modules/blockhealth/BlockHealthModule.java new file mode 100644 index 0000000..3cdfb61 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/blockhealth/BlockHealthModule.java @@ -0,0 +1,287 @@ +package com.hypixel.hytale.server.core.modules.blockhealth; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.EntityEventSystem; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.packets.world.UpdateBlockDamage; +import com.hypixel.hytale.server.core.asset.type.gameplay.WorldConfig; +import com.hypixel.hytale.server.core.event.events.ecs.PlaceBlockEvent; +import com.hypixel.hytale.server.core.modules.LegacyModule; +import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; +import com.hypixel.hytale.server.core.modules.time.TimeModule; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.time.Instant; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockHealthModule extends JavaPlugin { + @Nonnull + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(BlockHealthModule.class) + .depends(LegacyModule.class) + .depends(TimeModule.class) + .build(); + private static final long SECONDS_UNTIL_REGENERATION = 5L; + private static final float HEALING_PER_SECOND = 0.1F; + private static BlockHealthModule instance; + private ComponentType blockHealthChunkComponentType; + + public BlockHealthModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + public static BlockHealthModule get() { + return instance; + } + + @Override + protected void setup() { + ComponentRegistryProxy chunkStoreRegistry = this.getChunkStoreRegistry(); + this.blockHealthChunkComponentType = chunkStoreRegistry.registerComponent(BlockHealthChunk.class, "BlockHealthChunk", BlockHealthChunk.CODEC); + this.getEntityStoreRegistry().registerSystem(new BlockHealthModule.PlaceBlockEventSystem()); + chunkStoreRegistry.registerSystem(new BlockHealthModule.EnsureBlockHealthSystem(this.blockHealthChunkComponentType)); + chunkStoreRegistry.registerSystem(new BlockHealthModule.BlockHealthSystem(this.blockHealthChunkComponentType)); + chunkStoreRegistry.registerSystem(new BlockHealthModule.BlockHealthPacketSystem(this.blockHealthChunkComponentType)); + } + + public ComponentType getBlockHealthChunkComponentType() { + return this.blockHealthChunkComponentType; + } + + private static class BlockHealthPacketSystem extends ChunkStore.LoadPacketDataQuerySystem { + @Nonnull + private final ComponentType blockHealthCunkComponentType; + @Nonnull + private final Archetype archetype; + + public BlockHealthPacketSystem(@Nonnull ComponentType blockHealthChunkComponentType) { + this.blockHealthCunkComponentType = blockHealthChunkComponentType; + this.archetype = Archetype.of(blockHealthChunkComponentType, WorldChunk.getComponentType()); + } + + @Nonnull + @Override + public Query getQuery() { + return this.archetype; + } + + @Override + public boolean isParallel() { + return true; + } + + public void fetch( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + Store store, + CommandBuffer commandBuffer, + PlayerRef player, + @Nonnull List results + ) { + BlockHealthChunk blockHealthChunkComponent = archetypeChunk.getComponent(index, this.blockHealthCunkComponentType); + + assert blockHealthChunkComponent != null; + + blockHealthChunkComponent.createBlockDamagePackets(results); + } + } + + private static class BlockHealthSystem extends EntityTickingSystem { + @Nonnull + private final ComponentType blockHealthComponentChunkType; + @Nonnull + private final ResourceType timeResourceType; + private final Archetype archetype; + + public BlockHealthSystem(@Nonnull ComponentType blockHealthComponentChunkType) { + this.blockHealthComponentChunkType = blockHealthComponentChunkType; + this.timeResourceType = TimeResource.getResourceType(); + this.archetype = Archetype.of(blockHealthComponentChunkType, WorldChunk.getComponentType()); + } + + @Override + public Query getQuery() { + return this.archetype; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + BlockHealthChunk blockHealthChunkComponent = archetypeChunk.getComponent(index, this.blockHealthComponentChunkType); + + assert blockHealthChunkComponent != null; + + World world = store.getExternalData().getWorld(); + Store entityStore = world.getEntityStore().getStore(); + TimeResource uptime = world.getEntityStore().getStore().getResource(this.timeResourceType); + Instant currentGameTime = uptime.getNow(); + Instant lastRepairGameTime = blockHealthChunkComponent.getLastRepairGameTime(); + blockHealthChunkComponent.setLastRepairGameTime(currentGameTime); + if (lastRepairGameTime != null) { + Map blockFragilityMap = blockHealthChunkComponent.getBlockFragilityMap(); + if (!blockFragilityMap.isEmpty()) { + float deltaSeconds = (float)(currentGameTime.toEpochMilli() - lastRepairGameTime.toEpochMilli()) / 1000.0F; + Iterator> iterator = blockFragilityMap.entrySet().iterator(); + + while (iterator.hasNext()) { + Entry entry = iterator.next(); + FragileBlock fragileBlock = entry.getValue(); + float newDuration = fragileBlock.getDurationSeconds() - deltaSeconds; + if (newDuration <= 0.0F) { + iterator.remove(); + } else { + fragileBlock.setDurationSeconds(newDuration); + } + } + } + + Map blockHealthMap = blockHealthChunkComponent.getBlockHealthMap(); + if (!blockHealthMap.isEmpty()) { + WorldChunk chunk = archetypeChunk.getComponent(index, WorldChunk.getComponentType()); + + assert chunk != null; + + Collection allPlayers = world.getPlayerRefs(); + ObjectArrayList visibleTo = new ObjectArrayList<>(allPlayers.size()); + + for (PlayerRef playerRef : allPlayers) { + Ref playerReference = playerRef.getReference(); + if (playerReference != null && playerReference.isValid()) { + ChunkTracker chunkTrackerComponent = entityStore.getComponent(playerReference, ChunkTracker.getComponentType()); + + assert chunkTrackerComponent != null; + + if (chunkTrackerComponent.isLoaded(chunk.getIndex())) { + visibleTo.add(playerRef); + } + } + } + + float deltaSeconds = (float)(currentGameTime.toEpochMilli() - lastRepairGameTime.toEpochMilli()) / 1000.0F; + Iterator> iterator = blockHealthMap.entrySet().iterator(); + + while (iterator.hasNext()) { + Entry entry = iterator.next(); + Vector3i position = entry.getKey(); + BlockHealth blockHealth = entry.getValue(); + Instant startRegenerating = blockHealth.getLastDamageGameTime().plusSeconds(5L); + if (!currentGameTime.isBefore(startRegenerating)) { + float healthDelta = 0.1F * deltaSeconds; + float health = blockHealth.getHealth() + healthDelta; + if (health < 1.0F) { + blockHealth.setHealth(health); + } else { + iterator.remove(); + health = BlockHealth.NO_DAMAGE_INSTANCE.getHealth(); + healthDelta = health - blockHealth.getHealth(); + } + + UpdateBlockDamage packet = new UpdateBlockDamage(new BlockPosition(position.getX(), position.getY(), position.getZ()), health, healthDelta); + + for (int i = 0; i < visibleTo.size(); i++) { + visibleTo.get(i).getPacketHandler().writeNoCache(packet); + } + } + } + } + } + } + } + + private static class EnsureBlockHealthSystem extends HolderSystem { + @Nonnull + private final ComponentType blockHealthChunkComponentType; + + public EnsureBlockHealthSystem(@Nonnull ComponentType blockHealthChunkComponentType) { + this.blockHealthChunkComponentType = blockHealthChunkComponentType; + } + + @Override + public Query getQuery() { + return WorldChunk.getComponentType(); + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(this.blockHealthChunkComponentType); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + } + + public static class PlaceBlockEventSystem extends EntityEventSystem { + @Nonnull + private static final ComponentType BLOCK_HEALTH_CHUNK_COMPONENT_TYPE = BlockHealthModule.get() + .getBlockHealthChunkComponentType(); + + public PlaceBlockEventSystem() { + super(PlaceBlockEvent.class); + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull PlaceBlockEvent event + ) { + World world = commandBuffer.getExternalData().getWorld(); + Vector3i blockLocation = event.getTargetBlock(); + ChunkStore chunkComponentStore = world.getChunkStore(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(blockLocation.x, blockLocation.z); + Ref chunkReference = chunkComponentStore.getChunkReference(chunkIndex); + if (chunkReference != null) { + BlockHealthChunk blockHealthComponent = chunkComponentStore.getStore().getComponent(chunkReference, BLOCK_HEALTH_CHUNK_COMPONENT_TYPE); + + assert blockHealthComponent != null; + + WorldConfig worldGameplayConfig = world.getGameplayConfig().getWorldConfig(); + float blockPlacementFragilityTimer = worldGameplayConfig.getBlockPlacementFragilityTimer(); + blockHealthComponent.makeBlockFragile(blockLocation, blockPlacementFragilityTimer); + } + } + + @Nullable + @Override + public Query getQuery() { + return Archetype.empty(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/blockhealth/FragileBlock.java b/src/com/hypixel/hytale/server/core/modules/blockhealth/FragileBlock.java new file mode 100644 index 0000000..bdca718 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/blockhealth/FragileBlock.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.server.core.modules.blockhealth; + +import io.netty.buffer.ByteBuf; +import javax.annotation.Nonnull; + +public class FragileBlock implements Cloneable { + private float durationSeconds; + + public FragileBlock(float durationSeconds) { + this.durationSeconds = durationSeconds; + } + + public FragileBlock() { + } + + public float getDurationSeconds() { + return this.durationSeconds; + } + + public void setDurationSeconds(float durationSeconds) { + this.durationSeconds = durationSeconds; + } + + public void deserialize(@Nonnull ByteBuf buf, byte version) { + this.durationSeconds = buf.readFloat(); + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeFloat(this.durationSeconds); + } + + @Nonnull + protected FragileBlock clone() { + return new FragileBlock(this.durationSeconds); + } + + @Nonnull + @Override + public String toString() { + return "FragileBlock{durationSeconds=" + this.durationSeconds + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/blockset/BlockSetLookupTable.java b/src/com/hypixel/hytale/server/core/modules/blockset/BlockSetLookupTable.java new file mode 100644 index 0000000..6f87305 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/blockset/BlockSetLookupTable.java @@ -0,0 +1,132 @@ +package com.hypixel.hytale.server.core.modules.blockset; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.IntSets; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class BlockSetLookupTable { + @Nonnull + private final Object2ObjectMap blockNameIdMap; + @Nonnull + private final Object2ObjectMap groupNameIdMap; + @Nonnull + private final Object2ObjectMap hitboxNameIdMap; + @Nonnull + private final Object2ObjectMap categoryIdMap; + + public BlockSetLookupTable(@Nonnull Map blockTypeMap) { + Object2ObjectMap blockNameIdMap = new Object2ObjectOpenHashMap<>(); + Object2ObjectMap groupNameIdMap = new Object2ObjectOpenHashMap<>(); + Object2ObjectMap hitboxNameIdMap = new Object2ObjectOpenHashMap<>(); + Object2ObjectMap categoryIdMap = new Object2ObjectOpenHashMap<>(); + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + blockTypeMap.keySet().forEach(blockName -> { + int index = assetMap.getIndex(blockName); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + blockName); + } else { + blockNameIdMap.computeIfAbsent(blockName, s -> new IntOpenHashSet()).add(index); + } + }); + blockTypeMap.forEach((blockTypeKey, blockType) -> { + String group = blockType.getGroup(); + if (group != null && !group.isEmpty()) { + int index = assetMap.getIndex(blockTypeKey); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + blockTypeKey); + } + + groupNameIdMap.computeIfAbsent(group, s -> new IntOpenHashSet()).add(index); + } + + String hitboxType = blockType.getHitboxType(); + if (hitboxType != null && !hitboxType.isEmpty()) { + int index = hitboxType.indexOf(124); + if (index != 0) { + if (index > 0) { + hitboxType = hitboxType.substring(0, index); + } + + int index1 = assetMap.getIndex(blockTypeKey); + if (index1 == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + blockTypeKey); + } + + hitboxNameIdMap.computeIfAbsent(hitboxType, s -> new IntOpenHashSet()).add(index1); + } + } + + String name = blockType.getId(); + Item item = Item.getAssetMap().getAsset(name); + if (item != null) { + String[] categories = item.getCategories(); + if (categories != null) { + for (String category : categories) { + int index = assetMap.getIndex(blockTypeKey); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + blockTypeKey); + } + + categoryIdMap.computeIfAbsent(category, s -> new IntOpenHashSet()).add(index); + } + } + } + }); + blockNameIdMap.replaceAll((s, tIntSet) -> { + ((IntOpenHashSet)tIntSet).trim(); + return IntSets.unmodifiable(tIntSet); + }); + groupNameIdMap.replaceAll((s, tIntSet) -> { + ((IntOpenHashSet)tIntSet).trim(); + return IntSets.unmodifiable(tIntSet); + }); + hitboxNameIdMap.replaceAll((s, tIntSet) -> { + ((IntOpenHashSet)tIntSet).trim(); + return IntSets.unmodifiable(tIntSet); + }); + categoryIdMap.replaceAll((s, tIntSet) -> { + ((IntOpenHashSet)tIntSet).trim(); + return IntSets.unmodifiable(tIntSet); + }); + this.blockNameIdMap = Object2ObjectMaps.unmodifiable(blockNameIdMap); + this.groupNameIdMap = Object2ObjectMaps.unmodifiable(groupNameIdMap); + this.hitboxNameIdMap = Object2ObjectMaps.unmodifiable(hitboxNameIdMap); + this.categoryIdMap = Object2ObjectMaps.unmodifiable(categoryIdMap); + } + + public void addAll(@Nonnull IntSet result) { + this.blockNameIdMap.values().forEach(result::addAll); + } + + @Nonnull + public Object2ObjectMap getBlockNameIdMap() { + return this.blockNameIdMap; + } + + @Nonnull + public Object2ObjectMap getGroupNameIdMap() { + return this.groupNameIdMap; + } + + @Nonnull + public Object2ObjectMap getHitboxNameIdMap() { + return this.hitboxNameIdMap; + } + + @Nonnull + public Object2ObjectMap getCategoryIdMap() { + return this.categoryIdMap; + } + + public boolean isEmpty() { + return this.blockNameIdMap.isEmpty() && this.groupNameIdMap.isEmpty() && this.hitboxNameIdMap.isEmpty() && this.categoryIdMap.isEmpty(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/blockset/BlockSetModule.java b/src/com/hypixel/hytale/server/core/modules/blockset/BlockSetModule.java new file mode 100644 index 0000000..edb473f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/blockset/BlockSetModule.java @@ -0,0 +1,226 @@ +package com.hypixel.hytale.server.core.modules.blockset; + +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.modules.blockset.commands.BlockSetCommand; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.Map; +import java.util.function.Consumer; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@Deprecated(forRemoval = true) +public class BlockSetModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(BlockSetModule.class).build(); + private static BlockSetModule INSTANCE; + @Nonnull + private Int2ObjectMap flattenedBlockSets = new Int2ObjectOpenHashMap<>(); + @Nonnull + private Int2ObjectMap unmodifiableFlattenedBlockSets = Int2ObjectMaps.unmodifiable(this.flattenedBlockSets); + private BlockSetLookupTable blockSetLookupTable; + + public BlockSetModule(@Nonnull JavaPluginInit module) { + super(module); + INSTANCE = this; + } + + @Override + protected void setup() { + this.getCommandRegistry().registerCommand(new BlockSetCommand(this)); + this.getEventRegistry().register(LoadedAssetsEvent.class, BlockType.class, this::onBlockTypesChanged); + this.getEventRegistry().register(LoadedAssetsEvent.class, BlockSet.class, this::onBlockSetsChanged); + } + + private void onBlockTypesChanged(@Nonnull LoadedAssetsEvent> event) { + this.blockSetLookupTable = new BlockSetLookupTable(((BlockTypeAssetMap)event.getAssetMap()).getAssetMap()); + this.flattenedBlockSets = this.flattenBlockSets(this.blockSetLookupTable); + this.unmodifiableFlattenedBlockSets = Int2ObjectMaps.unmodifiable(this.flattenedBlockSets); + } + + private void onBlockSetsChanged(LoadedAssetsEvent> event) { + this.blockSetLookupTable = new BlockSetLookupTable(BlockType.getAssetMap().getAssetMap()); + this.flattenedBlockSets = this.flattenBlockSets(this.blockSetLookupTable); + this.unmodifiableFlattenedBlockSets = Int2ObjectMaps.unmodifiable(this.flattenedBlockSets); + } + + @Nonnull + private Int2ObjectMap flattenBlockSets(@Nonnull BlockSetLookupTable lookupTable) { + Int2ObjectOpenHashMap flattenedSets = new Int2ObjectOpenHashMap<>(); + if (!lookupTable.isEmpty()) { + BlockSet.getAssetMap().getAssetMap().forEach((s, blockSet) -> { + int index = BlockSet.getAssetMap().getIndex(s); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + s); + } else { + IntSet tIntSet = flattenedSets.get(index); + if (tIntSet == null) { + IntOpenHashSet set = this.createSet(blockSet, lookupTable, flattenedSets); + set.trim(); + flattenedSets.put(index, set); + } + } + }); + } + + return Int2ObjectMaps.unmodifiable(flattenedSets); + } + + @Nonnull + private IntOpenHashSet createSet(@Nonnull BlockSet blockSet, @Nonnull BlockSetLookupTable lookupTable, @Nonnull Int2ObjectMap flattenedSets) { + IntOpenHashSet result = new IntOpenHashSet(); + String parent = blockSet.getParent(); + if (parent != null && !parent.isEmpty()) { + int parentIndex = BlockSet.getAssetMap().getIndex(parent); + if (parentIndex == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + parent); + } + + result.addAll(flattenedSets.computeIfAbsent(parentIndex, s -> { + IntOpenHashSet set = this.createSet(parent, lookupTable, flattenedSets); + set.trim(); + return set; + })); + } + + if (blockSet.isIncludeAll()) { + lookupTable.addAll(result); + } + + this.consume(blockSet.getIncludeBlockTypes(), lookupTable.getBlockNameIdMap(), "block name", result::addAll); + this.consume(blockSet.getIncludeBlockGroups(), lookupTable.getGroupNameIdMap(), "group name", result::addAll); + this.consume(blockSet.getIncludeHitboxTypes(), lookupTable.getHitboxNameIdMap(), "hitbox name", result::addAll); + this.consume(blockSet.getExcludeBlockTypes(), lookupTable.getBlockNameIdMap(), "block name", result::removeAll); + this.consume(blockSet.getExcludeBlockGroups(), lookupTable.getGroupNameIdMap(), "group name", result::removeAll); + this.consume(blockSet.getExcludeHitboxTypes(), lookupTable.getHitboxNameIdMap(), "hitbox name", result::removeAll); + this.consume(blockSet.getIncludeCategories(), lookupTable, result::addAll); + this.consume(blockSet.getExcludeCategories(), lookupTable, result::removeAll); + return result; + } + + private void consume(@Nullable String[] values, @Nonnull Map map, String typeString, @Nonnull Consumer addAll) { + if (values != null) { + for (String s : values) { + this.consumeEntry(s, addAll, map, typeString); + } + } + } + + private void consume(@Nullable String[][] values, @Nonnull BlockSetLookupTable lookupTable, @Nonnull Consumer addAll) { + if (values != null) { + for (String[] s : values) { + this.consumeCategory(s, addAll, lookupTable); + } + } + } + + @Nonnull + private IntOpenHashSet createSet(String name, @Nonnull BlockSetLookupTable lookupTable, @Nonnull Int2ObjectMap flattenedSets) { + Map blockSets = BlockSet.getAssetMap().getAssetMap(); + BlockSet blockSet = blockSets.get(name); + if (blockSet == null) { + this.getLogger().at(Level.WARNING).log("Creating block sets: Failed to find block set '%s'", name); + return new IntOpenHashSet(); + } else { + return this.createSet(blockSet, lookupTable, flattenedSets); + } + } + + private void consumeCategory(@Nullable String[] categories, @Nonnull Consumer predicate, @Nonnull BlockSetLookupTable lookupTable) { + if (categories != null && categories.length != 0) { + Map categoryIdMap = lookupTable.getCategoryIdMap(); + IntSet catSet = categoryIdMap.get(categories[0]); + if (catSet == null) { + this.getLogger().at(Level.WARNING).log("Creating block sets: '%s' does not match any block category", categories[0]); + } else if (categories.length == 1) { + predicate.accept(catSet); + } else { + IntSet andSet = new IntOpenHashSet(catSet); + + for (int i = 1; i < categories.length; i++) { + catSet = categoryIdMap.get(categories[i]); + if (catSet == null) { + this.getLogger().at(Level.WARNING).log("Creating block sets: '%s' does not match any block category", categories[i]); + return; + } + + andSet.removeAll(catSet); + if (andSet.isEmpty()) { + return; + } + } + + predicate.accept(andSet); + } + } + } + + private void consumeEntry(@Nonnull String name, @Nonnull Consumer predicate, @Nonnull Map nameIdMap, String typeString) { + if (StringUtil.isGlobPattern(name)) { + boolean[] found = new boolean[]{false}; + nameIdMap.forEach((s, tIntSet) -> { + if (StringUtil.isGlobMatching(name, s)) { + predicate.accept(tIntSet); + found[0] = true; + } + }); + if (!found[0]) { + this.getLogger().at(Level.FINE).log("Creating block sets: '%s' does not match any %s", name, typeString); + } + } else { + IntSet ids = nameIdMap.get(name); + if (ids == null) { + this.getLogger().at(Level.WARNING).log("Creating block sets: Failed to find %s '%s'", typeString, name); + } else { + predicate.accept(ids); + } + } + } + + @Nonnull + public Int2ObjectMap getBlockSets() { + return this.unmodifiableFlattenedBlockSets; + } + + public boolean blockInSet(int set, int blockId) { + IntSet s = this.flattenedBlockSets.get(set); + return s != null && s.contains(blockId); + } + + public boolean blockInSet(int set, @Nullable BlockType blockType) { + return blockType != null && this.blockInSet(set, blockType.getId()); + } + + public boolean blockInSet(int set, @Nullable String blockTypeKey) { + if (blockTypeKey == null) { + return false; + } else { + IntSet s = this.flattenedBlockSets.get(set); + if (s == null) { + return false; + } else { + int index = BlockType.getAssetMap().getIndex(blockTypeKey); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + blockTypeKey); + } else { + return s.contains(index); + } + } + } + } + + public static BlockSetModule getInstance() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/blockset/commands/BlockSetCommand.java b/src/com/hypixel/hytale/server/core/modules/blockset/commands/BlockSetCommand.java new file mode 100644 index 0000000..2d467d4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/blockset/commands/BlockSetCommand.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.core.modules.blockset.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.modules.blockset.BlockSetModule; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class BlockSetCommand extends CommandBase { + @Nonnull + private final BlockSetModule blockSetModule; + @Nonnull + private final OptionalArg blockSetArg = this.withOptionalArg("blockset", "server.commands.blockset.blockset.desc", ArgTypes.STRING); + + public BlockSetCommand(@Nonnull BlockSetModule blockSetModule) { + super("blockset", "server.commands.blockset.desc"); + this.blockSetModule = blockSetModule; + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + if (!this.blockSetArg.provided(context)) { + Set blockSetKeys = BlockSet.getAssetMap().getAssetMap().keySet().stream().map(Message::raw).collect(Collectors.toSet()); + context.sendMessage(MessageFormat.list(null, blockSetKeys)); + } else { + String blockSetName = this.blockSetArg.get(context); + int index = BlockSet.getAssetMap().getIndex(blockSetName); + if (index == Integer.MIN_VALUE) { + context.sendMessage(Message.translation("server.modules.blockset.setNotFound").param("name", blockSetName)); + } else { + IntSet set = this.blockSetModule.getBlockSets().get(index); + if (set == null) { + context.sendMessage(Message.translation("server.modules.blockset.setNotFound").param("name", blockSetName)); + } else { + List names = new ObjectArrayList<>(); + set.forEach(i -> names.add(Message.raw(BlockType.getAssetMap().getAsset(i).getId().toString()))); + names.sort(null); + context.sendMessage(MessageFormat.list(null, names)); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/camera/FlyCameraModule.java b/src/com/hypixel/hytale/server/core/modules/camera/FlyCameraModule.java new file mode 100644 index 0000000..9cc65ba --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/camera/FlyCameraModule.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.server.core.modules.camera; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.event.EventBus; +import com.hypixel.hytale.protocol.packets.camera.SetFlyCameraMode; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.event.events.permissions.GroupPermissionChangeEvent; +import com.hypixel.hytale.server.core.event.events.permissions.PlayerGroupEvent; +import com.hypixel.hytale.server.core.event.events.permissions.PlayerPermissionChangeEvent; +import com.hypixel.hytale.server.core.permissions.PermissionsModule; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class FlyCameraModule extends JavaPlugin { + @Nonnull + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(FlyCameraModule.class).depends(PermissionsModule.class).build(); + + public FlyCameraModule(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + EventBus eventBus = HytaleServer.get().getEventBus(); + eventBus.register(PlayerPermissionChangeEvent.PermissionsRemoved.class, this::handlePlayerPermissionsRemoved); + eventBus.register(PlayerGroupEvent.Removed.class, this::handlePlayerGroupRemoved); + eventBus.register(GroupPermissionChangeEvent.Removed.class, this::handleGroupPermissionsRemoved); + } + + private void handlePlayerPermissionsRemoved(@Nonnull PlayerPermissionChangeEvent.PermissionsRemoved event) { + if (PermissionsModule.hasPermission(event.getRemovedPermissions(), "hytale.camera.flycam") == Boolean.TRUE) { + this.checkAndEnforceFlyCameraPermission(event.getPlayerUuid()); + } + } + + private void handlePlayerGroupRemoved(@Nonnull PlayerGroupEvent.Removed event) { + this.checkAndEnforceFlyCameraPermission(event.getPlayerUuid()); + } + + private void handleGroupPermissionsRemoved(@Nonnull GroupPermissionChangeEvent.Removed event) { + if (PermissionsModule.hasPermission(event.getRemovedPermissions(), "hytale.camera.flycam") == Boolean.TRUE) { + String groupName = event.getGroupName(); + PermissionsModule permissionsModule = PermissionsModule.get(); + + for (PlayerRef playerRef : Universe.get().getPlayers()) { + UUID uuid = playerRef.getUuid(); + Set groups = permissionsModule.getGroupsForUser(uuid); + if (groups.contains(groupName)) { + this.checkAndEnforceFlyCameraPermission(uuid); + } + } + } + } + + private void checkAndEnforceFlyCameraPermission(@Nonnull UUID uuid) { + PlayerRef playerRef = Universe.get().getPlayer(uuid); + if (playerRef != null) { + boolean hasPermission = PermissionsModule.get().hasPermission(uuid, "hytale.camera.flycam"); + if (!hasPermission) { + playerRef.getPacketHandler().writeNoCache(new SetFlyCameraMode(false)); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/BasicCollisionData.java b/src/com/hypixel/hytale/server/core/modules/collision/BasicCollisionData.java new file mode 100644 index 0000000..e86ba66 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/BasicCollisionData.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.math.vector.Vector3d; +import java.util.Comparator; +import javax.annotation.Nonnull; + +public class BasicCollisionData { + public static final Comparator COLLISION_START_COMPARATOR = Comparator.comparingDouble(a -> a.collisionStart); + public final Vector3d collisionPoint = new Vector3d(); + public double collisionStart; + + public BasicCollisionData() { + } + + public void setStart(@Nonnull Vector3d point, double start) { + this.collisionPoint.assign(point); + this.collisionStart = start; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/BlockCollisionData.java b/src/com/hypixel/hytale/server/core/modules/collision/BlockCollisionData.java new file mode 100644 index 0000000..22420e6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/BlockCollisionData.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockCollisionData extends BoxCollisionData { + public int x; + public int y; + public int z; + public int blockId; + public int rotation; + @Nullable + public BlockType blockType; + @Nullable + public BlockMaterial blockMaterial; + public int detailBoxIndex; + public boolean willDamage; + public int fluidId; + @Nullable + public Fluid fluid; + public boolean touching; + public boolean overlapping; + + public BlockCollisionData() { + } + + public void setBlockData(@Nonnull CollisionConfig collisionConfig) { + this.x = collisionConfig.blockX; + this.y = collisionConfig.blockY; + this.z = collisionConfig.blockZ; + this.blockId = collisionConfig.blockId; + this.rotation = collisionConfig.rotation; + this.blockType = collisionConfig.blockType; + this.blockMaterial = collisionConfig.blockMaterial; + this.willDamage = (collisionConfig.blockMaterialMask & 16) != 0; + this.fluidId = collisionConfig.fluidId; + this.fluid = collisionConfig.fluid; + } + + public void setDetailBoxIndex(int detailBoxIndex) { + this.detailBoxIndex = detailBoxIndex; + } + + public void setTouchingOverlapping(boolean touching, boolean overlapping) { + this.touching = touching; + this.overlapping = overlapping; + } + + public void clear() { + this.blockType = null; + this.blockMaterial = null; + } + + @Nonnull + @Override + public String toString() { + return "BlockCollisionData{x=" + + this.x + + ", y=" + + this.y + + ", z=" + + this.z + + ", blockId=" + + this.blockId + + ", blockType=" + + this.blockType + + ", blockMaterial=" + + this.blockMaterial + + ", collisionPoint=" + + this.collisionPoint + + ", collisionNormal=" + + this.collisionNormal + + ", collisionStart=" + + this.collisionStart + + ", collisionEnd=" + + this.collisionEnd + + ", detailBoxIndex=" + + this.detailBoxIndex + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/BlockCollisionProvider.java b/src/com/hypixel/hytale/server/core/modules/collision/BlockCollisionProvider.java new file mode 100644 index 0000000..c9a2685 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/BlockCollisionProvider.java @@ -0,0 +1,385 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.math.iterator.BoxBlockIterator; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.universe.world.World; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockCollisionProvider implements BoxBlockIterator.BoxIterationConsumer { + protected final BoxBlockIntersectionEvaluator boxBlockIntersectionEvaluator = new BoxBlockIntersectionEvaluator(); + protected final MovingBoxBoxCollisionEvaluator movingBoxBoxCollisionEvaluator = new MovingBoxBoxCollisionEvaluator(); + protected final BlockDataProvider blockData = new BlockDataProvider(); + protected final Box fluidBox = new Box(Box.UNIT); + protected final CollisionTracker damageTracker = new CollisionTracker(); + protected final CollisionTracker triggerTracker = new CollisionTracker(); + protected final BlockTracker collisionTracker = new BlockTracker(); + protected int requestedCollisionMaterials = 4; + protected boolean reportOverlaps; + @Nullable + protected IBlockCollisionConsumer collisionConsumer; + @Nullable + protected IBlockTracker activeTriggers; + @Nullable + protected Vector3d motion; + protected double relativeStopDistance; + protected IBlockCollisionConsumer.Result collisionState; + + public BlockCollisionProvider() { + } + + public void setRequestedCollisionMaterials(int requestedCollisionMaterials) { + this.requestedCollisionMaterials = requestedCollisionMaterials; + } + + public void setReportOverlaps(boolean reportOverlaps) { + this.reportOverlaps = reportOverlaps; + this.movingBoxBoxCollisionEvaluator.setComputeOverlaps(reportOverlaps); + } + + @Override + public boolean next() { + return this.onSliceFinished(); + } + + @Override + public boolean accept(long x, long y, long z) { + return this.processBlockDynamic((int)x, (int)y, (int)z); + } + + public void cast( + @Nonnull World world, + @Nonnull Box collider, + @Nonnull Vector3d pos, + @Nonnull Vector3d v, + @Nonnull IBlockCollisionConsumer collisionConsumer, + @Nonnull IBlockTracker activeTriggers, + double collisionStop + ) { + if (!CollisionModule.get().isDisabled()) { + this.collisionConsumer = collisionConsumer; + this.activeTriggers = activeTriggers; + this.motion = v; + this.blockData.initialize(world); + boolean isFarDistance = !CollisionModule.isBelowMovementThreshold(v); + if (isFarDistance) { + this.castIterative(collider, pos, v, collisionStop); + } else { + this.castShortDistance(collider, pos, v); + } + + collisionConsumer.onCollisionFinished(); + this.blockData.cleanup(); + this.triggerTracker.reset(); + this.damageTracker.reset(); + this.collisionConsumer = null; + this.activeTriggers = null; + this.motion = null; + } + } + + protected void castShortDistance(@Nonnull Box collider, @Nonnull Vector3d pos, @Nonnull Vector3d v) { + this.boxBlockIntersectionEvaluator.setBox(collider, pos).offsetPosition(v); + collider.forEachBlock(pos.x + v.x, pos.y + v.y, pos.z + v.z, 1.0E-5, this, (x, y, z, _this) -> _this.processBlockStatic(x, y, z)); + this.generateTriggerExit(); + } + + protected boolean processBlockStatic(int x, int y, int z) { + this.blockData.read(x, y, z); + BlockBoundingBoxes boundingBoxes = this.blockData.getBlockBoundingBoxes(); + int blockX = this.blockData.originX(x); + int blockY = this.blockData.originY(y); + int blockZ = this.blockData.originZ(z); + boolean trigger = this.blockData.isTrigger() && !this.triggerTracker.isTracked(blockX, blockY, blockZ); + int damage = this.blockData.getBlockDamage(); + boolean canCollide = this.canCollide(); + Box[] boxes = boundingBoxes.get(this.blockData.rotation).getDetailBoxes(); + this.boxBlockIntersectionEvaluator.setDamageAndSubmerged(damage, false); + if (this.blockData.getBlockType().getMaterial() != BlockMaterial.Empty + || this.blockData.getBlockType().getMaterial() == BlockMaterial.Empty && this.blockData.getFluidId() == 0) { + if (damage != 0 && boundingBoxes.protrudesUnitBox()) { + if (this.damageTracker.isTracked(blockX, blockY, blockZ)) { + damage = 0; + } else { + this.damageTracker.trackNew(blockX, blockY, blockZ); + } + } + + if (canCollide && boundingBoxes.protrudesUnitBox()) { + if (this.collisionTracker.isTracked(blockX, blockY, blockZ)) { + canCollide = false; + } else { + this.collisionTracker.trackNew(blockX, blockY, blockZ); + } + } + + for (int i = 0; (canCollide || trigger || damage > 0) && i < boxes.length; i++) { + Box box = boxes[i]; + if (!CollisionMath.isDisjoint(this.boxBlockIntersectionEvaluator.intersectBoxComputeTouch(box, blockX, blockY, blockZ))) { + if (canCollide || this.boxBlockIntersectionEvaluator.isOverlapping() && this.reportOverlaps) { + this.collisionConsumer.onCollision(blockX, blockY, blockZ, this.motion, this.boxBlockIntersectionEvaluator, this.blockData, box); + canCollide = false; + } + + if (trigger) { + if (!this.activeTriggers.isTracked(blockX, blockY, blockZ)) { + this.activeTriggers.trackNew(blockX, blockY, blockZ); + } + + this.triggerTracker.trackNew(blockX, blockY, blockZ); + trigger = false; + } + + if (damage != 0) { + this.collisionConsumer.onCollisionDamage(blockX, blockY, blockZ, this.motion, this.boxBlockIntersectionEvaluator, this.blockData); + damage = 0; + } + } + } + + Fluid fluid = this.blockData.getFluid(); + if (fluid != null && this.blockData.getFluidId() != 0) { + this.processBlockStaticFluid(x, y, z, fluid, true); + } + + return true; + } else { + if (trigger) { + this.boxBlockIntersectionEvaluator.setDamageAndSubmerged(damage, false); + + for (Box box : boxes) { + if (!CollisionMath.isDisjoint(this.boxBlockIntersectionEvaluator.intersectBoxComputeTouch(box, blockX, blockY, blockZ))) { + this.triggerTracker.trackNew(blockX, blockY, blockZ); + break; + } + } + } + + this.processBlockStaticFluid(x, y, z, this.blockData.getFluid(), false); + return true; + } + } + + protected void processBlockStaticFluid(int x, int y, int z, @Nonnull Fluid fluid, boolean submergeFluid) { + boolean processDamage = fluid.getDamageToEntities() != 0; + boolean processCollision = this.canCollide(2); + if (processDamage || processCollision) { + this.fluidBox.max.y = this.blockData.getFillHeight(); + if (!CollisionMath.isDisjoint(this.boxBlockIntersectionEvaluator.intersectBoxComputeTouch(this.fluidBox, x, y, z))) { + this.boxBlockIntersectionEvaluator.setDamageAndSubmerged(fluid.getDamageToEntities(), submergeFluid); + if (processCollision) { + this.collisionConsumer.onCollision(x, y, z, this.motion, this.boxBlockIntersectionEvaluator, this.blockData, this.fluidBox); + } + + if (processDamage) { + this.collisionConsumer.onCollisionDamage(x, y, z, this.motion, this.boxBlockIntersectionEvaluator, this.blockData); + } + } + } + } + + protected boolean canCollide() { + return this.canCollide(this.blockData.getCollisionMaterials()); + } + + protected boolean canCollide(int collisionMaterials) { + return (collisionMaterials & this.requestedCollisionMaterials) != 0; + } + + protected void castIterative(@Nonnull Box collider, @Nonnull Vector3d pos, @Nonnull Vector3d v, double collisionStop) { + this.relativeStopDistance = MathUtil.clamp(collisionStop, 0.0, 1.0); + this.collisionState = IBlockCollisionConsumer.Result.CONTINUE; + this.movingBoxBoxCollisionEvaluator.setCollider(collider).setMove(pos, v); + collider.forEachBlock(pos, 1.0E-5, this, (xx, yx, zx, _this) -> _this.processBlockDynamic(xx, yx, zx)); + BoxBlockIterator.iterate(collider, pos, v, v.length(), this); + int count = this.damageTracker.getCount(); + + for (int i = 0; i < count; i++) { + BlockContactData collision = this.damageTracker.getContactData(i); + if (collision.getCollisionStart() <= this.relativeStopDistance) { + Vector3i position = this.damageTracker.getPosition(i); + this.collisionConsumer.onCollisionDamage(position.x, position.y, position.z, this.motion, collision, this.damageTracker.getBlockData(i)); + } + } + + this.generateTriggerExit(); + count = this.triggerTracker.getCount(); + + for (int ix = 0; ix < count; ix++) { + BlockContactData collision = this.triggerTracker.getContactData(ix); + if (collision.getCollisionStart() <= this.relativeStopDistance) { + Vector3i position = this.triggerTracker.getPosition(ix); + int x = position.x; + int y = position.y; + int z = position.z; + if (!this.activeTriggers.isTracked(x, y, z)) { + this.activeTriggers.trackNew(x, y, z); + } + } + } + } + + protected boolean onSliceFinished() { + IBlockCollisionConsumer.Result result = this.collisionConsumer.onCollisionSliceFinished(); + if (result != null && this.collisionState.ordinal() < result.ordinal()) { + this.collisionState = result; + } + + return this.collisionState == IBlockCollisionConsumer.Result.CONTINUE; + } + + protected boolean processBlockDynamic(int x, int y, int z) { + this.blockData.read(x, y, z); + int blockX = this.blockData.originX(x); + int blockY = this.blockData.originY(y); + int blockZ = this.blockData.originZ(z); + BlockBoundingBoxes boundingBoxes = this.blockData.getBlockBoundingBoxes(); + Box[] boxes = boundingBoxes.get(this.blockData.rotation).getDetailBoxes(); + boolean canCollide = this.canCollide(); + int damage = this.blockData.getBlockDamage(); + boolean trigger = this.blockData.isTrigger(); + this.movingBoxBoxCollisionEvaluator.setDamageAndSubmerged(damage, false); + BlockContactData triggerCollisionData = null; + BlockContactData damageCollisionData = null; + if (trigger) { + triggerCollisionData = this.triggerTracker.getContactData(blockX, blockY, blockZ); + if (triggerCollisionData != null) { + trigger = false; + } + } + + if (damage != 0 && boundingBoxes.protrudesUnitBox()) { + damageCollisionData = this.damageTracker.getContactData(blockX, blockY, blockZ); + if (damageCollisionData != null) { + damage = 0; + } + } + + if (this.blockData.getBlockType().getMaterial() != BlockMaterial.Empty + || this.blockData.getBlockType().getMaterial() == BlockMaterial.Empty && this.blockData.getFluidId() == 0) { + for (Box box : boxes) { + if (this.movingBoxBoxCollisionEvaluator.isBoundingBoxColliding(box, blockX, blockY, blockZ)) { + if (this.movingBoxBoxCollisionEvaluator.getCollisionStart() > this.relativeStopDistance) { + if (this.movingBoxBoxCollisionEvaluator.isOverlapping() && this.reportOverlaps) { + IBlockCollisionConsumer.Result result = this.collisionConsumer + .onCollision(blockX, blockY, blockZ, this.motion, this.movingBoxBoxCollisionEvaluator, this.blockData, box); + this.updateStopDistance(result); + } + } else { + if (canCollide || this.movingBoxBoxCollisionEvaluator.isOverlapping() && this.reportOverlaps) { + IBlockCollisionConsumer.Result result = this.collisionConsumer + .onCollision(blockX, blockY, blockZ, this.motion, this.movingBoxBoxCollisionEvaluator, this.blockData, box); + this.updateStopDistance(result); + } + + if (trigger) { + triggerCollisionData = this.processTriggerDynamic(blockX, blockY, blockZ, triggerCollisionData); + } + + if (damage != 0) { + damageCollisionData = this.processDamageDynamic(blockX, blockY, blockZ, damageCollisionData); + } + } + } + } + + Fluid fluid = this.blockData.getFluid(); + if (fluid != null && this.blockData.getFluidId() != 0) { + this.processBlockDynamicFluid(x, y, z, fluid, damageCollisionData, true); + } + + return this.collisionState != IBlockCollisionConsumer.Result.STOP_NOW; + } else { + if (trigger) { + for (Box boxx : boxes) { + if (this.movingBoxBoxCollisionEvaluator.isBoundingBoxColliding(boxx, blockX, blockY, blockZ) + && this.movingBoxBoxCollisionEvaluator.getCollisionStart() <= this.relativeStopDistance) { + triggerCollisionData = this.processTriggerDynamic(blockX, blockY, blockZ, triggerCollisionData); + } + } + } + + this.processBlockDynamicFluid(x, y, z, this.blockData.getFluid(), damageCollisionData, false); + return this.collisionState != IBlockCollisionConsumer.Result.STOP_NOW; + } + } + + protected void processBlockDynamicFluid(int x, int y, int z, @Nonnull Fluid fluid, BlockContactData damageCollisionData, boolean isSubmergeFluid) { + boolean processDamage = fluid.getDamageToEntities() != 0; + boolean processCollision = this.canCollide(2); + if (processDamage || processCollision) { + this.fluidBox.max.y = this.blockData.getFillHeight(); + if (this.movingBoxBoxCollisionEvaluator.isBoundingBoxColliding(this.fluidBox, x, y, z) + && this.movingBoxBoxCollisionEvaluator.getCollisionStart() <= this.relativeStopDistance) { + this.movingBoxBoxCollisionEvaluator.setDamageAndSubmerged(fluid.getDamageToEntities(), isSubmergeFluid); + if (processCollision) { + IBlockCollisionConsumer.Result result = this.collisionConsumer + .onCollision(x, y, z, this.motion, this.movingBoxBoxCollisionEvaluator, this.blockData, this.fluidBox); + this.updateStopDistance(result); + } + + if (processDamage) { + this.processDamageDynamic(x, y, z, damageCollisionData); + } + } + } + } + + @Nonnull + protected BlockContactData processTriggerDynamic(int blockX, int blockY, int blockZ, @Nullable BlockContactData collisionData) { + if (collisionData == null) { + return this.triggerTracker.trackNew(blockX, blockY, blockZ, this.movingBoxBoxCollisionEvaluator, this.blockData); + } else { + double collisionEnd = Math.max(collisionData.collisionEnd, this.movingBoxBoxCollisionEvaluator.getCollisionEnd()); + if (this.movingBoxBoxCollisionEvaluator.getCollisionStart() < collisionData.collisionStart) { + collisionData.assign(this.movingBoxBoxCollisionEvaluator); + } + + collisionData.collisionEnd = collisionEnd; + return collisionData; + } + } + + @Nonnull + protected BlockContactData processDamageDynamic(int blockX, int blockY, int blockZ, @Nullable BlockContactData collisionData) { + IBlockCollisionConsumer.Result result = this.collisionConsumer + .probeCollisionDamage(blockX, blockY, blockZ, this.motion, this.movingBoxBoxCollisionEvaluator, this.blockData); + this.updateStopDistance(result); + if (collisionData == null) { + return this.damageTracker.trackNew(blockX, blockY, blockZ, this.movingBoxBoxCollisionEvaluator, this.blockData); + } else { + if (this.movingBoxBoxCollisionEvaluator.getCollisionStart() < collisionData.collisionStart) { + collisionData.assign(this.movingBoxBoxCollisionEvaluator); + } + + return collisionData; + } + } + + protected void updateStopDistance(@Nullable IBlockCollisionConsumer.Result result) { + if (result != null && result != IBlockCollisionConsumer.Result.CONTINUE) { + if (this.movingBoxBoxCollisionEvaluator.collisionStart < this.relativeStopDistance) { + this.relativeStopDistance = this.movingBoxBoxCollisionEvaluator.collisionStart; + } + + if (result.ordinal() > this.collisionState.ordinal()) { + this.collisionState = result; + } + } + } + + protected void generateTriggerExit() { + for (int i = this.activeTriggers.getCount() - 1; i >= 0; i--) { + Vector3i p = this.activeTriggers.getPosition(i); + if (!this.triggerTracker.isTracked(p.x, p.y, p.z)) { + this.activeTriggers.untrack(p.x, p.y, p.z); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/BlockContactData.java b/src/com/hypixel/hytale/server/core/modules/collision/BlockContactData.java new file mode 100644 index 0000000..702b27b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/BlockContactData.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class BlockContactData { + protected final Vector3d collisionNormal = new Vector3d(); + protected final Vector3d collisionPoint = new Vector3d(); + protected double collisionStart; + protected double collisionEnd; + protected boolean onGround; + protected int damage; + protected boolean isSubmergeFluid; + protected boolean overlapping; + + public BlockContactData() { + } + + public void clear() { + } + + public void assign(@Nonnull BlockContactData other) { + this.assign(other, other.damage, other.isSubmergeFluid); + } + + public void assign(@Nonnull BlockContactData other, int damage, boolean isSubmergedFluid) { + this.collisionNormal.assign(other.collisionNormal); + this.collisionPoint.assign(other.collisionPoint); + this.collisionStart = other.collisionStart; + this.collisionEnd = other.collisionEnd; + this.onGround = other.onGround; + this.overlapping = other.overlapping; + this.setDamageAndSubmerged(damage, isSubmergedFluid); + } + + public void setDamageAndSubmerged(int damage, boolean isSubmerge) { + this.damage = damage; + this.isSubmergeFluid = isSubmerge; + } + + @Nonnull + public Vector3d getCollisionNormal() { + return this.collisionNormal; + } + + @Nonnull + public Vector3d getCollisionPoint() { + return this.collisionPoint; + } + + public double getCollisionStart() { + return this.collisionStart; + } + + public double getCollisionEnd() { + return this.collisionEnd; + } + + public boolean isOverlapping() { + return this.overlapping; + } + + public boolean isOnGround() { + return this.onGround; + } + + public int getDamage() { + return this.damage; + } + + public boolean isSubmergeFluid() { + return this.isSubmergeFluid; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/BlockData.java b/src/com/hypixel/hytale/server/core/modules/collision/BlockData.java new file mode 100644 index 0000000..a527e23 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/BlockData.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockData { + protected int blockId; + @Nullable + protected BlockType blockType; + @Nullable + protected String blockTypeKey; + protected int rotation; + protected int filler; + protected int fluidId; + @Nullable + protected Fluid fluid; + @Nullable + protected String fluidKey; + protected double fillHeight; + protected int collisionMaterials; + @Nullable + protected BlockBoundingBoxes blockBoundingBoxes; + + public BlockData() { + } + + public void assign(@Nonnull BlockData other) { + this.blockId = other.blockId; + this.blockType = other.blockType; + this.blockTypeKey = other.blockTypeKey; + this.rotation = other.rotation; + this.filler = other.filler; + this.fluidId = other.fluidId; + this.fluid = other.fluid; + this.fluidKey = other.fluidKey; + this.fillHeight = other.fillHeight; + this.collisionMaterials = other.collisionMaterials; + this.blockBoundingBoxes = other.blockBoundingBoxes; + } + + public void clear() { + this.blockId = Integer.MIN_VALUE; + this.blockType = null; + this.blockTypeKey = null; + this.rotation = 0; + this.filler = 0; + this.fluidId = Integer.MIN_VALUE; + this.fluid = null; + this.fluidKey = null; + this.blockBoundingBoxes = null; + } + + public boolean isFiller() { + return this.filler != 0; + } + + public int originX(int x) { + return x - FillerBlockUtil.unpackX(this.filler); + } + + public int originY(int y) { + return y - FillerBlockUtil.unpackY(this.filler); + } + + public int originZ(int z) { + return z - FillerBlockUtil.unpackZ(this.filler); + } + + public double getFillHeight() { + return this.fillHeight; + } + + public boolean isTrigger() { + return this.blockType.isTrigger(); + } + + public int getBlockDamage() { + return Math.max(this.blockType.getDamageToEntities(), this.fluid.getDamageToEntities()); + } + + public int getSubmergeDamage() { + Fluid fluid = this.getFluid(); + return fluid == null ? 0 : fluid.getDamageToEntities(); + } + + public int getCollisionMaterials() { + return this.collisionMaterials; + } + + @Nullable + public BlockBoundingBoxes getBlockBoundingBoxes() { + if (this.blockBoundingBoxes == null) { + this.blockBoundingBoxes = BlockBoundingBoxes.getAssetMap().getAsset(this.blockType.getHitboxTypeIndex()); + } + + return this.blockBoundingBoxes; + } + + @Nullable + public BlockType getBlockType() { + return this.blockType; + } + + public int getFluidId() { + return this.fluidId; + } + + @Nullable + public Fluid getFluid() { + return this.fluid; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/BlockDataProvider.java b/src/com/hypixel/hytale/server/core/modules/collision/BlockDataProvider.java new file mode 100644 index 0000000..109d460 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/BlockDataProvider.java @@ -0,0 +1,233 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockDataProvider extends BlockData { + protected static int FULL_LEVEL = 8; + protected final int INVALID_CHUNK_SECTION_INDEX = Integer.MIN_VALUE; + @Nullable + protected World world; + @Nullable + protected WorldChunk chunk; + protected int chunkSectionIndex; + @Nullable + protected BlockSection chunkSection; + protected int chunkX; + protected int chunkY; + protected int chunkZ; + @Nullable + protected Ref chunkSectionRef; + + public BlockDataProvider() { + } + + public void initialize(World world) { + this.world = world; + this.blockId = Integer.MIN_VALUE; + this.cleanup0(); + } + + public void cleanup() { + this.world = null; + this.cleanup0(); + } + + public void read(int x, int y, int z) { + int newBlockId = this.readBlockId(x, y, z); + int fluidId = this.readFluidId(x, y, z); + if (this.blockId != newBlockId || this.fluidId != fluidId) { + if (newBlockId == 0 && fluidId == 0) { + this.setBlock(0, BlockType.EMPTY, 0, 1); + this.fluidId = 0; + this.fluid = Fluid.EMPTY; + this.fluidKey = "Empty"; + this.fillHeight = 0.0; + } else if (newBlockId == 1) { + this.setBlock(1, BlockType.UNKNOWN, 0, 4); + this.fluidId = 0; + this.fluid = Fluid.EMPTY; + this.fluidKey = "Empty"; + this.fillHeight = 0.0; + } else { + this.blockId = newBlockId; + this.blockType = BlockType.getAssetMap().getAsset(newBlockId); + if (this.blockType.isUnknown()) { + this.setBlock(newBlockId, this.blockType, 0, 4); + this.fluidId = 0; + this.fluid = Fluid.EMPTY; + this.fluidKey = "Empty"; + this.fillHeight = 0.0; + } else { + Fluid fluid = Fluid.getAssetMap().getAsset(fluidId); + byte fluidLevel = this.readFluidLevel(x, y, z); + this.blockTypeKey = this.blockType.getId(); + this.filler = this.readFiller(x, y, z); + this.rotation = this.readRotation(x, y, z); + if (this.blockType.getMaterial() == BlockMaterial.Solid) { + this.collisionMaterials = 4; + if (this.blockType.getHitboxTypeIndex() != 0) { + this.collisionMaterials = this.collisionMaterials + materialFromFillLevel(fluid, fluidLevel); + } + } else { + this.collisionMaterials = materialFromFillLevel(fluid, fluidLevel); + } + + this.fluidId = fluidId; + this.fluid = fluid; + this.fluidKey = fluid.getId(); + this.fillHeight = fluidId != 0 ? (double)fluidLevel / fluid.getMaxFluidLevel() : 0.0; + this.blockBoundingBoxes = null; + } + } + } + } + + protected int readBlockId(int x, int y, int z) { + int chunkX = ChunkUtil.chunkCoordinate(x); + int chunkZ = ChunkUtil.chunkCoordinate(z); + if (this.chunk == null || this.chunk.getX() != chunkX || chunkZ != this.chunk.getZ()) { + this.chunk = this.world.getChunkIfInMemory(ChunkUtil.indexChunk(chunkX, chunkZ)); + this.chunkSectionIndex = Integer.MIN_VALUE; + this.chunkSection = null; + } + + if (this.chunk == null) { + return 1; + } else { + int sectionIndex = ChunkUtil.indexSection(y); + if (this.chunkSection == null || this.chunkSectionIndex != sectionIndex) { + this.chunkSectionIndex = sectionIndex; + this.chunkSection = sectionIndex >= 0 && this.chunkSectionIndex < 10 ? this.chunk.getBlockChunk().getSectionAtIndex(sectionIndex) : null; + } + + return this.chunkSection == null ? 0 : this.chunkSection.get(x, y, z); + } + } + + protected int readRotation(int x, int y, int z) { + int chunkX = ChunkUtil.chunkCoordinate(x); + int chunkZ = ChunkUtil.chunkCoordinate(z); + if (this.chunk == null || this.chunk.getX() != chunkX || chunkZ != this.chunk.getZ()) { + this.chunk = this.world.getChunkIfInMemory(ChunkUtil.indexChunk(chunkX, chunkZ)); + this.chunkSectionIndex = Integer.MIN_VALUE; + this.chunkSection = null; + } + + if (this.chunk == null) { + return 0; + } else { + int sectionIndex = ChunkUtil.indexSection(y); + if (this.chunkSection == null || this.chunkSectionIndex != sectionIndex) { + this.chunkSectionIndex = sectionIndex; + this.chunkSection = sectionIndex >= 0 && this.chunkSectionIndex < 10 ? this.chunk.getBlockChunk().getSectionAtIndex(sectionIndex) : null; + } + + return this.chunkSection == null ? 0 : this.chunkSection.getRotationIndex(x, y, z); + } + } + + protected int readFiller(int x, int y, int z) { + int chunkX = ChunkUtil.chunkCoordinate(x); + int chunkZ = ChunkUtil.chunkCoordinate(z); + if (this.chunk == null || this.chunk.getX() != chunkX || chunkZ != this.chunk.getZ()) { + this.chunk = this.world.getChunkIfInMemory(ChunkUtil.indexChunk(chunkX, chunkZ)); + this.chunkSectionIndex = Integer.MIN_VALUE; + this.chunkSection = null; + } + + if (this.chunk == null) { + return 0; + } else { + int sectionIndex = ChunkUtil.indexSection(y); + if (this.chunkSection == null || this.chunkSectionIndex != sectionIndex) { + this.chunkSectionIndex = sectionIndex; + this.chunkSection = sectionIndex >= 0 && this.chunkSectionIndex < 10 ? this.chunk.getBlockChunk().getSectionAtIndex(sectionIndex) : null; + } + + return this.chunkSection == null ? 0 : this.chunkSection.getFiller(x, y, z); + } + } + + protected int readFluidId(int x, int y, int z) { + int chunkX = ChunkUtil.chunkCoordinate(x); + int chunkY = ChunkUtil.chunkCoordinate(y); + int chunkZ = ChunkUtil.chunkCoordinate(z); + if (this.chunkSectionRef == null || !this.chunkSectionRef.isValid() || this.chunkX != chunkX || this.chunkY != chunkY || this.chunkZ != chunkZ) { + this.chunkSectionRef = this.world.getChunkStore().getChunkSectionReference(chunkX, chunkY, chunkZ); + this.chunkX = chunkX; + this.chunkY = chunkY; + this.chunkZ = chunkZ; + } + + if (this.chunkSectionRef == null) { + return 1; + } else { + FluidSection fluidSection = this.world.getChunkStore().getStore().getComponent(this.chunkSectionRef, FluidSection.getComponentType()); + return fluidSection == null ? 1 : fluidSection.getFluidId(x, y, z); + } + } + + protected byte readFluidLevel(int x, int y, int z) { + int chunkX = ChunkUtil.chunkCoordinate(x); + int chunkY = ChunkUtil.chunkCoordinate(y); + int chunkZ = ChunkUtil.chunkCoordinate(z); + if (this.chunkSectionRef == null || !this.chunkSectionRef.isValid() || this.chunkX != chunkX || this.chunkY != chunkY || this.chunkZ != chunkZ) { + this.chunkSectionRef = this.world.getChunkStore().getChunkSectionReference(chunkX, chunkY, chunkZ); + this.chunkX = chunkX; + this.chunkY = chunkY; + this.chunkZ = chunkZ; + } + + if (this.chunkSectionRef == null) { + return 0; + } else { + FluidSection fluidSection = this.world.getChunkStore().getStore().getComponent(this.chunkSectionRef, FluidSection.getComponentType()); + return fluidSection == null ? 0 : fluidSection.getFluidLevel(x, y, z); + } + } + + protected void setBlock(int id, @Nonnull BlockType type, int rotation, int material, BlockBoundingBoxes box) { + this.blockId = id; + this.rotation = rotation; + this.blockType = type; + this.blockTypeKey = type.getId(); + this.collisionMaterials = material; + this.blockBoundingBoxes = box; + this.fillHeight = 0.0; + } + + protected void setBlock(int id, @Nonnull BlockType type, int rotation, int material) { + this.setBlock(id, type, rotation, material, BlockBoundingBoxes.UNIT_BOX); + } + + protected void cleanup0() { + this.chunk = null; + this.chunkSectionIndex = Integer.MIN_VALUE; + this.chunkSection = null; + this.blockType = null; + this.blockTypeKey = null; + this.blockBoundingBoxes = null; + this.fluid = null; + this.fluidKey = null; + } + + protected static int materialFromFillLevel(@Nonnull Fluid fluid, byte level) { + if (level == 0) { + return 1; + } else { + return level == fluid.getMaxFluidLevel() ? 2 : 3; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/BlockTracker.java b/src/com/hypixel/hytale/server/core/modules/collision/BlockTracker.java new file mode 100644 index 0000000..d2bd73e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/BlockTracker.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.math.vector.Vector3i; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class BlockTracker implements IBlockTracker { + public static final int NOT_FOUND = -1; + protected static final int ALLOC_SIZE = 4; + @Nonnull + protected Vector3i[] positions = new Vector3i[4]; + protected int count; + + public BlockTracker() { + for (int i = 0; i < this.positions.length; i++) { + this.positions[i] = new Vector3i(); + } + } + + @Override + public Vector3i getPosition(int index) { + return this.positions[index]; + } + + @Override + public int getCount() { + return this.count; + } + + public void reset() { + this.count = 0; + } + + @Override + public boolean track(int x, int y, int z) { + if (this.isTracked(x, y, z)) { + return true; + } else { + this.trackNew(x, y, z); + return false; + } + } + + @Override + public void trackNew(int x, int y, int z) { + if (this.count >= this.positions.length) { + this.alloc(); + } + + Vector3i v = this.positions[this.count++]; + v.x = x; + v.y = y; + v.z = z; + } + + @Override + public boolean isTracked(int x, int y, int z) { + return this.getIndex(x, y, z) >= 0; + } + + @Override + public void untrack(int x, int y, int z) { + int index = this.getIndex(x, y, z); + if (index >= 0) { + this.untrack(index); + } + } + + public void untrack(int index) { + if (this.count <= 0) { + throw new IllegalStateException("Calling untrack on empty tracker"); + } else { + this.count--; + if (this.count != 0) { + Vector3i v = this.positions[index]; + System.arraycopy(this.positions, index + 1, this.positions, index, this.count - index); + this.positions[this.count] = v; + } + } + } + + public int getIndex(int x, int y, int z) { + for (int i = this.count - 1; i >= 0; i--) { + Vector3i v = this.positions[i]; + if (v.x == x && v.y == y && v.z == z) { + return i; + } + } + + return -1; + } + + protected void alloc() { + this.positions = Arrays.copyOf(this.positions, this.positions.length + 4); + + for (int i = this.count; i < this.positions.length; i++) { + this.positions[i] = new Vector3i(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/BoxBlockIntersectionEvaluator.java b/src/com/hypixel/hytale/server/core/modules/collision/BoxBlockIntersectionEvaluator.java new file mode 100644 index 0000000..d337c88 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/BoxBlockIntersectionEvaluator.java @@ -0,0 +1,149 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class BoxBlockIntersectionEvaluator extends BlockContactData implements IBlockCollisionEvaluator { + @Nonnull + protected Box box = new Box(); + protected Vector3d worldUp = Vector3d.UP; + protected boolean touchCeil; + protected int resultCode; + + public BoxBlockIntersectionEvaluator() { + this.setStartEnd(0.0, 1.0); + } + + @Override + public void setCollisionData(@Nonnull BlockCollisionData data, @Nonnull CollisionConfig collisionConfig, int hitboxIndex) { + data.setStart(this.collisionPoint, this.collisionStart); + data.setEnd(this.collisionEnd, this.collisionNormal); + data.setBlockData(collisionConfig); + data.setDetailBoxIndex(hitboxIndex); + data.setTouchingOverlapping(CollisionMath.isTouching(this.resultCode), CollisionMath.isOverlapping(this.resultCode)); + } + + public Vector3d getWorldUp() { + return this.worldUp; + } + + public void setWorldUp(Vector3d worldUp) { + this.worldUp = worldUp; + } + + @Nonnull + public BoxBlockIntersectionEvaluator setBox(@Nonnull Box box) { + this.box.assign(box); + return this; + } + + @Nonnull + public BoxBlockIntersectionEvaluator expandBox(double radius) { + this.box.expand(radius); + return this; + } + + @Nonnull + public BoxBlockIntersectionEvaluator setPosition(@Nonnull Vector3d pos) { + this.collisionPoint.assign(pos); + return this; + } + + @Nonnull + public BoxBlockIntersectionEvaluator setBox(@Nonnull Box box, @Nonnull Vector3d pos) { + return this.setBox(box).setPosition(pos); + } + + @Nonnull + public BoxBlockIntersectionEvaluator offsetPosition(@Nonnull Vector3d offset) { + this.collisionPoint.add(offset); + return this; + } + + @Nonnull + public BoxBlockIntersectionEvaluator setStartEnd(double start, double end) { + this.collisionStart = start; + this.collisionEnd = end; + return this; + } + + public int intersectBox(@Nonnull Box otherBox, double x, double y, double z) { + return CollisionMath.intersectAABBs(this.collisionPoint.x, this.collisionPoint.y, this.collisionPoint.z, this.box, x, y, z, otherBox); + } + + public int intersectBoxComputeTouch(@Nonnull Box otherBox, double x, double y, double z) { + int code = CollisionMath.intersectAABBs(this.collisionPoint.x, this.collisionPoint.y, this.collisionPoint.z, this.box, x, y, z, otherBox); + this.resultCode = code; + this.onGround = false; + this.touchCeil = false; + this.collisionNormal.assign(0.0, 0.0, 0.0); + this.overlapping = CollisionMath.isOverlapping(this.resultCode); + if ((code & 7) != 0) { + if (this.worldUp.y != 0.0) { + if ((code & 2) != 0) { + this.collisionNormal.assign(0.0, y + otherBox.min.y < this.collisionPoint.y + this.box.min.y ? 1.0 : -1.0, 0.0); + this.onGround = this.collisionNormal.y == this.worldUp.y; + this.touchCeil = !this.onGround; + } else if ((code & 1) != 0) { + this.collisionNormal.assign(x + otherBox.min.x < this.collisionPoint.x + this.box.min.x ? 1.0 : -1.0, 0.0, 0.0); + } else { + this.collisionNormal.assign(0.0, 0.0, z + otherBox.min.z < this.collisionPoint.z + this.box.min.z ? 1.0 : -1.0); + } + } else if (this.worldUp.x != 0.0) { + if ((code & 1) != 0) { + this.collisionNormal.assign(x + otherBox.min.x < this.collisionPoint.x + this.box.min.x ? 1.0 : -1.0, 0.0, 0.0); + this.onGround = this.collisionNormal.x == this.worldUp.x; + this.touchCeil = !this.onGround; + } else if ((code & 2) != 0) { + this.collisionNormal.assign(0.0, y + otherBox.min.y < this.collisionPoint.y + this.box.min.y ? 1.0 : -1.0, 0.0); + } else { + this.collisionNormal.assign(0.0, 0.0, z + otherBox.min.z < this.collisionPoint.z + this.box.min.z ? 1.0 : -1.0); + } + } else if ((code & 4) != 0) { + this.collisionNormal.assign(0.0, 0.0, z + otherBox.min.z < this.collisionPoint.z + this.box.min.z ? 1.0 : -1.0); + this.onGround = this.collisionNormal.z == this.worldUp.z; + this.touchCeil = !this.onGround; + } else if ((code & 2) != 0) { + this.collisionNormal.assign(0.0, y + otherBox.min.y < this.collisionPoint.y + this.box.min.y ? 1.0 : -1.0, 0.0); + } else { + this.collisionNormal.assign(x + otherBox.min.x < this.collisionPoint.x + this.box.min.x ? 1.0 : -1.0, 0.0, 0.0); + } + } + + return code; + } + + public int intersectBoxComputeOnGround(@Nonnull Box otherBox, double x, double y, double z) { + int code = CollisionMath.intersectAABBs(this.collisionPoint.x, this.collisionPoint.y, this.collisionPoint.z, this.box, x, y, z, otherBox); + this.resultCode = code; + this.onGround = false; + this.touchCeil = false; + if ((code & 7) != 0) { + if (this.worldUp.y != 0.0 && (code & 2) != 0) { + this.onGround = (y + otherBox.min.y - this.collisionPoint.y - this.box.min.y) * this.worldUp.y < 0.0; + this.touchCeil = !this.onGround; + } else if (this.worldUp.x != 0.0 && (code & 1) != 0) { + this.onGround = (x + otherBox.min.x - this.collisionPoint.x - this.box.min.x) * this.worldUp.x < 0.0; + this.touchCeil = !this.onGround; + } else if (this.worldUp.z != 0.0 && (code & 4) != 0) { + this.onGround = (z + otherBox.min.z - this.collisionPoint.z - this.box.min.z) * this.worldUp.z < 0.0; + this.touchCeil = !this.onGround; + } + } + + return code; + } + + public boolean isBoxIntersecting(@Nonnull Box otherBox, double x, double y, double z) { + return !CollisionMath.isDisjoint(this.intersectBoxComputeTouch(otherBox, x, y, z)); + } + + public boolean isTouching() { + return CollisionMath.isTouching(this.resultCode); + } + + public boolean touchesCeil() { + return this.touchCeil; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/BoxCollisionData.java b/src/com/hypixel/hytale/server/core/modules/collision/BoxCollisionData.java new file mode 100644 index 0000000..ebd4db6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/BoxCollisionData.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class BoxCollisionData extends BasicCollisionData { + public double collisionEnd; + public final Vector3d collisionNormal = new Vector3d(); + + public BoxCollisionData() { + } + + public void setEnd(double collisionEnd, @Nonnull Vector3d collisionNormal) { + this.collisionEnd = collisionEnd; + this.collisionNormal.assign(collisionNormal); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/CharacterCollisionData.java b/src/com/hypixel/hytale/server/core/modules/collision/CharacterCollisionData.java new file mode 100644 index 0000000..e06a5cc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/CharacterCollisionData.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class CharacterCollisionData extends BasicCollisionData { + public Ref entityReference; + public boolean isPlayer; + + public CharacterCollisionData() { + } + + public void assign(@Nonnull Vector3d collisionPoint, double collisionVectorScale, Ref entityReference, boolean isPlayer) { + this.collisionPoint.assign(collisionPoint); + this.collisionStart = collisionVectorScale; + this.entityReference = entityReference; + this.isPlayer = isPlayer; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/CollisionConfig.java b/src/com/hypixel/hytale/server/core/modules/collision/CollisionConfig.java new file mode 100644 index 0000000..06fd4b0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/CollisionConfig.java @@ -0,0 +1,308 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CollisionConfig { + public static final int MATERIAL_EMPTY = 1; + public static final int MATERIAL_FLUID = 2; + public static final int MATERIAL_SOLID = 4; + public static final int MATERIAL_SUBMERGED = 8; + public static final int MATERIAL_DAMAGE = 16; + public static final int MATERIAL_SET_NONE = 0; + public static final int MATERIAL_SET_ANY = 15; + private static final int INVALID_CHUNK_SECTION_INDEX = Integer.MIN_VALUE; + public int blockId; + @Nullable + public BlockType blockType; + @Nullable + public BlockMaterial blockMaterial; + public int rotation; + public int blockX; + public int blockY; + public int blockZ; + private int boundingBoxOffsetX; + private int boundingBoxOffsetY; + private int boundingBoxOffsetZ; + private BlockBoundingBoxes.RotatedVariantBoxes boundingBoxes; + @Nullable + private WorldChunk chunk; + private int chunkSectionIndex; + @Nullable + private BlockSection chunkSection; + private int chunkX; + private int chunkY; + private int chunkZ; + @Nullable + private Ref chunkSectionRef; + @Nullable + public Fluid fluid; + public int fluidId; + public byte fluidLevel; + @Nonnull + private Box blockBox = new Box(); + private World world; + private int blockMaterialCollisionMask; + public int blockMaterialMask; + public boolean blockCanCollide; + public boolean blockCanTrigger; + public boolean blockCanTriggerPartial; + public boolean checkTriggerBlocks = true; + public boolean checkDamageBlocks = true; + public Predicate canCollide; + public boolean dumpInvalidBlocks; + @Nullable + public Object extraData1; + @Nullable + public Object extraData2; + + public CollisionConfig() { + } + + public int getDetailCount() { + return this.boundingBoxes.getDetailBoxes().length; + } + + @Nonnull + public Box getBoundingBox() { + this.blockBox.assign(this.boundingBoxes.getBoundingBox()); + if (this.blockId == 0 && this.fluidId != 0 && this.fluid != null) { + this.blockBox.max.y -= 0.03125; + this.blockBox.max.y = this.blockBox.max.y * ((double)this.fluidLevel / this.fluid.getMaxFluidLevel()); + } + + return this.blockBox; + } + + @Nonnull + public Box getBoundingBox(int i) { + this.blockBox.assign(this.boundingBoxes.getDetailBoxes()[i]); + return this.blockBox; + } + + public int getBoundingBoxOffsetX() { + return this.boundingBoxOffsetX; + } + + public int getBoundingBoxOffsetY() { + return this.boundingBoxOffsetY; + } + + public int getBoundingBoxOffsetZ() { + return this.boundingBoxOffsetZ; + } + + public void setCollisionByMaterial(int collidingMaterials) { + this.blockMaterialCollisionMask = this.blockMaterialCollisionMask & -16 | collidingMaterials & 15; + } + + public int getCollisionByMaterial() { + return this.blockMaterialCollisionMask & 15; + } + + public boolean isCollidingWithDamageBlocks() { + return (this.blockMaterialCollisionMask & 16) != 0; + } + + public boolean setCollideWithDamageBlocks(boolean damageColliding) { + boolean oldState = this.isCollidingWithDamageBlocks(); + if (damageColliding) { + this.blockMaterialCollisionMask |= 16; + } else { + this.blockMaterialCollisionMask &= -17; + } + + return oldState; + } + + public Predicate getBlockCollisionPredicate() { + return this.canCollide; + } + + public void setDefaultCollisionBehaviour() { + this.setCollisionByMaterial(4); + this.setCollideWithDamageBlocks(true); + this.setDefaultBlockCollisionPredicate(); + } + + public void setDefaultBlockCollisionPredicate() { + this.canCollide = collisionConfig -> (collisionConfig.blockMaterialMask & collisionConfig.blockMaterialCollisionMask) != 0; + } + + public boolean isCheckTriggerBlocks() { + return this.checkTriggerBlocks; + } + + public void setCheckTriggerBlocks(boolean checkTriggerBlocks) { + this.checkTriggerBlocks = checkTriggerBlocks; + } + + public boolean isCheckDamageBlocks() { + return this.checkDamageBlocks; + } + + public void setCheckDamageBlocks(boolean checkDamageBlocks) { + this.checkDamageBlocks = checkDamageBlocks; + } + + public void setWorld(World world) { + if (this.world != world) { + this.chunk = null; + this.chunkSectionRef = null; + this.chunkSection = null; + this.chunkSectionIndex = Integer.MIN_VALUE; + } + + this.world = world; + this.blockId = Integer.MIN_VALUE; + this.blockX = Integer.MIN_VALUE; + this.blockY = Integer.MIN_VALUE; + this.blockZ = Integer.MIN_VALUE; + } + + public boolean canCollide(int x, int y, int z) { + this.blockX = x; + this.blockY = y; + this.blockZ = z; + int chunkX = ChunkUtil.chunkCoordinate(x); + int chunkY = ChunkUtil.chunkCoordinate(y); + int chunkZ = ChunkUtil.chunkCoordinate(z); + if (this.chunk == null || this.chunk.getX() != chunkX || chunkZ != this.chunk.getZ()) { + this.chunk = this.world.getChunkIfInMemory(ChunkUtil.indexChunk(chunkX, chunkZ)); + this.chunkSectionIndex = Integer.MIN_VALUE; + this.chunkSection = null; + } + + if (this.chunkSectionRef == null || !this.chunkSectionRef.isValid() || this.chunkX != chunkX || this.chunkY != chunkY || this.chunkZ != chunkZ) { + this.chunkSectionRef = this.world.getChunkStore().getChunkSectionReference(chunkX, chunkY, chunkZ); + this.chunkX = chunkX; + this.chunkY = chunkY; + this.chunkZ = chunkZ; + } + + this.blockCanTrigger = false; + if (this.chunk != null && this.chunkSectionRef != null) { + int sectionIndex = ChunkUtil.indexSection(y); + if (this.chunkSection == null || this.chunkSectionIndex != sectionIndex) { + this.chunkSectionIndex = sectionIndex; + if (sectionIndex >= 0 && this.chunkSectionIndex < 10) { + this.chunkSection = this.chunk.getBlockChunk().getSectionAtIndex(sectionIndex); + } else { + this.chunkSection = null; + } + } + + if (this.chunkSection == null) { + this.blockType = BlockType.EMPTY; + this.blockMaterial = BlockMaterial.Empty; + this.fluid = null; + this.fluidId = Integer.MIN_VALUE; + this.boundingBoxes = BlockBoundingBoxes.UNIT_BOX.get(Rotation.None, Rotation.None, Rotation.None); + this.blockMaterialMask = 1; + this.blockCanCollide = (this.blockMaterialCollisionMask & this.blockMaterialMask) != 0; + this.blockId = 0; + return this.blockCanCollide; + } else { + int newBlockId = this.chunkSection.get(x, y, z); + BlockType newBlockType = BlockType.getAssetMap().getAsset(newBlockId); + FluidSection fluidSection = this.chunkSectionRef.getStore().getComponent(this.chunkSectionRef, FluidSection.getComponentType()); + byte newFluidLevel = 0; + int newFluidId; + Fluid newFluid; + if (fluidSection != null) { + newFluidId = fluidSection.getFluidId(this.blockX, this.blockY, this.blockZ); + newFluid = Fluid.getAssetMap().getAsset(newFluidId); + newFluidLevel = fluidSection.getFluidLevel(this.blockX, this.blockY, this.blockZ); + } else { + newFluidId = Integer.MIN_VALUE; + newFluid = null; + } + + int filler = this.chunkSection.getFiller(x, y, z); + if (!newBlockType.isUnknown() && filler != 0) { + this.boundingBoxOffsetX = -FillerBlockUtil.unpackX(filler); + this.boundingBoxOffsetY = -FillerBlockUtil.unpackY(filler); + this.boundingBoxOffsetZ = -FillerBlockUtil.unpackZ(filler); + } else { + this.boundingBoxOffsetX = 0; + this.boundingBoxOffsetY = 0; + this.boundingBoxOffsetZ = 0; + } + + int newRotation = this.chunkSection.getRotationIndex(x, y, z); + if (newBlockId == this.blockId && this.rotation == newRotation && this.fluidId == newFluidId && this.fluidLevel == newFluidLevel) { + this.blockCanTrigger = this.blockCanTriggerPartial || this.checkTriggerBlocks && (newBlockType.isTrigger() || newFluid.isTrigger()); + return this.blockCanCollide || this.blockCanTrigger; + } else { + this.blockId = newBlockId; + this.blockType = newBlockType; + this.rotation = newRotation; + this.fluidId = newFluidId; + this.fluid = newFluid; + this.fluidLevel = newFluidLevel; + boolean blockWillDamage = this.checkDamageBlocks && (this.blockType.getDamageToEntities() > 0 || this.fluid.getDamageToEntities() > 0); + this.blockCanTrigger = blockWillDamage || this.checkTriggerBlocks && (this.blockType.isTrigger() || newFluid.isTrigger()); + this.blockMaterialMask = blockWillDamage ? 16 : 0; + if ((this.blockId == 0 || this.blockType == BlockType.EMPTY) && this.fluidId == 0) { + this.blockMaterial = BlockMaterial.Empty; + this.boundingBoxes = BlockBoundingBoxes.UNIT_BOX.get(Rotation.None, Rotation.None, Rotation.None); + this.blockMaterialMask |= 1; + this.blockCanCollide = (this.blockMaterialMask & this.blockMaterialCollisionMask) != 0; + return this.blockCanCollide || this.blockCanTrigger; + } else { + this.blockMaterial = this.blockType.getMaterial(); + this.boundingBoxes = BlockBoundingBoxes.getAssetMap().getAsset(newBlockType.getHitboxTypeIndex()).get(this.rotation); + if (this.blockMaterial == BlockMaterial.Empty) { + this.blockMaterialMask = this.blockMaterialMask | (this.fluidId != 0 ? 2 : 1); + this.blockCanCollide = (this.blockMaterialMask & this.blockMaterialCollisionMask) != 0; + return this.blockCanCollide || this.blockCanTrigger; + } else { + if (this.boundingBoxes == null) { + this.boundingBoxes = BlockBoundingBoxes.UNIT_BOX.get(Rotation.None, Rotation.None, Rotation.None); + } + + this.blockMaterialMask = this.blockMaterialMask | (this.fluidId == 0 ? 4 : 14); + this.blockCanCollide = this.canCollide.test(this); + return this.blockCanCollide || this.blockCanTrigger; + } + } + } + } + } else { + this.blockType = null; + this.blockMaterial = null; + this.fluid = null; + this.fluidId = Integer.MIN_VALUE; + this.boundingBoxes = BlockBoundingBoxes.UNIT_BOX.get(Rotation.None, Rotation.None, Rotation.None); + this.blockMaterialMask = 0; + this.blockCanCollide = true; + this.blockId = Integer.MIN_VALUE; + return true; + } + } + + public void clear() { + this.chunk = null; + this.chunkSectionIndex = Integer.MIN_VALUE; + this.chunkSection = null; + this.setWorld(null); + this.dumpInvalidBlocks = false; + this.extraData1 = null; + this.extraData2 = null; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/CollisionDataArray.java b/src/com/hypixel/hytale/server/core/modules/collision/CollisionDataArray.java new file mode 100644 index 0000000..65c25c5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/CollisionDataArray.java @@ -0,0 +1,101 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CollisionDataArray { + @Nonnull + private final List array = new ObjectArrayList<>(); + private final Supplier supplier; + private final Consumer dispose; + private final List freeList; + private int head; + + public CollisionDataArray(Supplier supplier, Consumer dispose, List freeList) { + Objects.requireNonNull(supplier, "Must provide supplier for CollisionDataArray"); + this.supplier = supplier; + this.dispose = dispose; + this.freeList = freeList; + this.head = 0; + } + + public int getCount() { + return this.array.size() - this.head; + } + + public T alloc() { + T result; + if (this.freeList.isEmpty()) { + result = this.supplier.get(); + } else { + int last = this.freeList.size() - 1; + result = this.freeList.get(last); + this.freeList.remove(last); + } + + this.array.add(result); + return result; + } + + public void reset() { + int count = this.array.size(); + if (count > 0) { + if (this.dispose != null) { + for (int i = 0; i < count; i++) { + T value = this.array.get(i); + this.dispose.accept(value); + this.freeList.add(value); + } + } else { + for (int i = 0; i < count; i++) { + T value = this.array.get(i); + this.freeList.add(value); + } + } + + this.array.clear(); + this.head = 0; + } + } + + @Nullable + public T getFirst() { + return this.head < this.array.size() ? this.array.get(this.head) : null; + } + + @Nullable + public T forgetFirst() { + this.head++; + return this.getFirst(); + } + + public boolean isEmpty() { + return this.array.isEmpty(); + } + + public void sort(Comparator comparator) { + this.array.sort(comparator); + } + + public void remove(int l) { + int index = this.head + l; + if (index < this.array.size()) { + this.freeList.add(this.array.get(index)); + this.array.remove(index); + } + } + + public int size() { + return this.array.size() - this.head; + } + + public T get(int i) { + return this.array.get(this.head + i); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/CollisionFilter.java b/src/com/hypixel/hytale/server/core/modules/collision/CollisionFilter.java new file mode 100644 index 0000000..a88b119 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/CollisionFilter.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.server.core.modules.collision; + +@FunctionalInterface +public interface CollisionFilter { + boolean test(T var1, int var2, D var3, CollisionConfig var4); +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/CollisionMaterial.java b/src/com/hypixel/hytale/server/core/modules/collision/CollisionMaterial.java new file mode 100644 index 0000000..2d30c48 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/CollisionMaterial.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.server.core.modules.collision; + +public class CollisionMaterial { + public static final int MATERIAL_EMPTY = 1; + public static final int MATERIAL_FLUID = 2; + public static final int MATERIAL_SOLID = 4; + public static final int MATERIAL_SUBMERGED = 8; + public static final int MATERIAL_SET_ANY = 15; + public static final int MATERIAL_DAMAGE = 16; + public static final int MATERIAL_SET_NONE = 0; + + public CollisionMaterial() { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/CollisionMath.java b/src/com/hypixel/hytale/server/core/modules/collision/CollisionMath.java new file mode 100644 index 0000000..a47225f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/CollisionMath.java @@ -0,0 +1,195 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class CollisionMath { + public static final ThreadLocal MIN_MAX = ThreadLocal.withInitial(Vector2d::new); + public static final int DISJOINT = 0; + public static final int TOUCH_X = 1; + public static final int TOUCH_Y = 2; + public static final int TOUCH_Z = 4; + public static final int TOUCH_ANY = 7; + public static final int OVERLAP_X = 8; + public static final int OVERLAP_Y = 16; + public static final int OVERLAP_Z = 32; + public static final int OVERLAP_ANY = 56; + public static final int OVERLAP_ALL = 56; + + public CollisionMath() { + throw new IllegalStateException("CollisionMath can't be instantiated"); + } + + public static boolean intersectVectorAABB( + @Nonnull Vector3d pos, @Nonnull Vector3d vec, double x, double y, double z, @Nonnull Box box, @Nonnull Vector2d minMax + ) { + return intersectRayAABB(pos, vec, x, y, z, box, minMax) && minMax.x <= 1.0; + } + + public static boolean intersectRayAABB( + @Nonnull Vector3d pos, @Nonnull Vector3d ray, double x, double y, double z, @Nonnull Box box, @Nonnull Vector2d minMax + ) { + minMax.x = 0.0; + minMax.y = Double.MAX_VALUE; + Vector3d min = box.getMin(); + Vector3d max = box.getMax(); + return intersect1D(pos.x, ray.getX(), x + min.x, x + max.x, minMax) + && intersect1D(pos.y, ray.getY(), y + min.y, y + max.y, minMax) + && intersect1D(pos.z, ray.getZ(), z + min.z, z + max.z, minMax) + && minMax.x >= 0.0; + } + + public static double intersectRayAABB(@Nonnull Vector3d pos, @Nonnull Vector3d ray, double x, double y, double z, @Nonnull Box box) { + Vector2d minMax = MIN_MAX.get(); + return intersectRayAABB(pos, ray, x, y, z, box, minMax) ? minMax.x : -Double.MAX_VALUE; + } + + public static boolean intersectVectorAABB( + @Nonnull Vector3d pos, @Nonnull Vector3d vec, double x, double y, double z, double radius, double height, @Nonnull Vector2d minMax + ) { + return intersectRayAABB(pos, vec, x, y, z, radius, height, minMax) && minMax.x <= 1.0; + } + + public static boolean intersectRayAABB( + @Nonnull Vector3d pos, @Nonnull Vector3d ray, double x, double y, double z, double radius, double height, @Nonnull Vector2d minMax + ) { + minMax.x = 0.0; + minMax.y = Double.MAX_VALUE; + return intersect1D(pos.x, ray.getX(), x - radius, x + radius, minMax) + && intersect1D(pos.y, ray.getY(), y, y + height, minMax) + && intersect1D(pos.z, ray.getZ(), z - radius, z + radius, minMax) + && minMax.x >= 0.0; + } + + public static boolean intersectSweptAABBs( + @Nonnull Vector3d posP, @Nonnull Vector3d vP, @Nonnull Box p, @Nonnull Vector3d posQ, @Nonnull Box q, @Nonnull Vector2d minMax, @Nonnull Box temp + ) { + return intersectSweptAABBs(posP, vP, p, posQ.x, posQ.y, posQ.z, q, minMax, temp); + } + + public static boolean intersectSweptAABBs( + @Nonnull Vector3d posP, + @Nonnull Vector3d vP, + @Nonnull Box p, + double qx, + double qy, + double qz, + @Nonnull Box q, + @Nonnull Vector2d minMax, + @Nonnull Box temp + ) { + temp.assign(q).minkowskiSum(p); + return intersectVectorAABB(posP, vP, qx, qy, qz, temp, minMax); + } + + public static boolean intersect1D(double p, double s, double min, double max, @Nonnull Vector2d minMax) { + if (!(Math.abs(s) < 1.0E-5)) { + double t1 = (min - p) / s; + double t2 = (max - p) / s; + if (t2 >= t1) { + if (t1 > minMax.x) { + minMax.x = t1; + } + + if (t2 < minMax.y) { + minMax.y = t2; + } + } else { + if (t2 > minMax.x) { + minMax.x = t2; + } + + if (t1 < minMax.y) { + minMax.y = t1; + } + } + + return minMax.x <= minMax.y; + } else { + return !(p < min) && !(p > max); + } + } + + public static boolean isDisjoint(int code) { + return code == 0; + } + + public static boolean isOverlapping(int code) { + return code == 56; + } + + public static boolean isTouching(int code) { + return (code & 7) != 0; + } + + public static int intersectAABBs(@Nonnull Vector3d p, @Nonnull Box bbP, @Nonnull Vector3d q, @Nonnull Box bbQ) { + return intersectAABBs(p.x, p.y, p.z, bbP, q.x, q.y, q.z, bbQ); + } + + public static int intersectAABBs(double px, double py, double pz, @Nonnull Box bbP, double qx, double qy, double qz, @Nonnull Box bbQ) { + int x = intersect1D(px, bbP.min.x, bbP.max.x, qx, bbQ.min.x, bbQ.max.x); + if (x == 0) { + return 0; + } else { + x &= 9; + int y = intersect1D(py, bbP.min.y, bbP.max.y, qy, bbQ.min.y, bbQ.max.y); + if (y == 0) { + return 0; + } else { + y &= 18; + int z = intersect1D(pz, bbP.min.z, bbP.max.z, qz, bbQ.min.z, bbQ.max.z); + if (z == 0) { + return 0; + } else { + z &= 36; + return x | y | z; + } + } + } + } + + public static int intersect1D(double p, double pMin, double pMax, double q, double qMin, double qMax) { + return intersect1D(p, pMin, pMax, q, qMin, qMax, 1.0E-5); + } + + public static int intersectAABBs(double px, double py, double pz, @Nonnull Box bbP, double qx, double qy, double qz, @Nonnull Box bbQ, double thickness) { + int x = intersect1D(px, bbP.min.x, bbP.max.x, qx, bbQ.min.x, bbQ.max.x, thickness); + if (x == 0) { + return 0; + } else { + x &= 9; + int y = intersect1D(py, bbP.min.y, bbP.max.y, qy, bbQ.min.y, bbQ.max.y, thickness); + if (y == 0) { + return 0; + } else { + y &= 18; + int z = intersect1D(pz, bbP.min.z, bbP.max.z, qz, bbQ.min.z, bbQ.max.z, thickness); + if (z == 0) { + return 0; + } else { + z &= 36; + return x | y | z; + } + } + } + } + + public static int intersect1D(double p, double pMin, double pMax, double q, double qMin, double qMax, double thickness) { + double offset = q - p; + double dist = pMin - qMax - offset; + if (dist > thickness) { + return 0; + } else if (dist > -thickness) { + return 7; + } else { + dist = qMin - pMax + offset; + if (dist > thickness) { + return 0; + } else { + return dist > -thickness ? 7 : 56; + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/CollisionModule.java b/src/com/hypixel/hytale/server/core/modules/collision/CollisionModule.java new file mode 100644 index 0000000..413d047 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/CollisionModule.java @@ -0,0 +1,616 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.spatial.KDTree; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.ProjectileComponent; +import com.hypixel.hytale.server.core.modules.collision.commands.HitboxCommand; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.projectile.component.Projectile; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.Config; +import java.util.List; +import java.util.function.Predicate; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class CollisionModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(CollisionModule.class).build(); + public static final int VALIDATE_INVALID = -1; + public static final int VALIDATE_OK = 0; + public static final int VALIDATE_ON_GROUND = 1; + public static final int VALIDATE_TOUCH_CEIL = 2; + private static CollisionModule instance; + private ResourceType, EntityStore>> tangiableEntitySpatialComponent; + private double extentMax; + private double minimumThickness; + private final Config config = this.withConfig("CollisionModule", CollisionModuleConfig.CODEC); + + public static CollisionModule get() { + return instance; + } + + public CollisionModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + public CollisionModuleConfig getConfig() { + return this.config.get(); + } + + @Override + protected void setup() { + this.getCommandRegistry().registerCommand(new HitboxCommand()); + this.getEventRegistry().register(LoadedAssetsEvent.class, BlockBoundingBoxes.class, this::onLoadedAssetsEvent); + this.tangiableEntitySpatialComponent = this.getEntityStoreRegistry().registerSpatialResource(() -> new KDTree<>(Ref::isValid)); + } + + public ResourceType, EntityStore>> getTangiableEntitySpatialComponent() { + return this.tangiableEntitySpatialComponent; + } + + private void onLoadedAssetsEvent(@Nonnull LoadedAssetsEvent> event) { + if (event.isInitial()) { + this.extentMax = 0.0; + this.minimumThickness = Double.MAX_VALUE; + } + + for (BlockBoundingBoxes box : event.getLoadedAssets().values()) { + this.handleLoadedHitbox(box); + } + + CollisionModuleConfig config = this.config.get(); + if (config.hasMinimumThickness()) { + this.minimumThickness = config.getMinimumThickness(); + } + + this.getLogger().at(Level.INFO).log("Block extents for CollisionSystem is Max=" + this.extentMax + ", Min=" + this.minimumThickness); + } + + private void handleLoadedHitbox(@Nonnull BlockBoundingBoxes box) { + BlockBoundingBoxes.RotatedVariantBoxes defaultBox = box.get(Rotation.None, Rotation.None, Rotation.None); + double maximumExtent = defaultBox.getBoundingBox().getMaximumExtent(); + double blockExtent = 0.0; + if (maximumExtent > blockExtent) { + blockExtent = maximumExtent; + } + + if (blockExtent > 1.0) { + this.getLogger() + .at(Level.FINE) + .log("Block Hitbox %s protrudes more than 1 unit (%s units) out of standard block and degrades performance", box.getId(), blockExtent); + } + + if (blockExtent > this.extentMax) { + this.extentMax = blockExtent; + } + + double thickness; + if (defaultBox.hasDetailBoxes()) { + thickness = Double.MAX_VALUE; + + for (Box boundingBox : defaultBox.getDetailBoxes()) { + thickness = Math.min(thickness, boundingBox.getThickness()); + } + } else { + thickness = defaultBox.getBoundingBox().getThickness(); + } + + if (thickness < 0.0) { + this.getLogger().at(Level.SEVERE).log("Hitbox for " + box.getId() + " has a negative size!"); + } else { + if (thickness < this.minimumThickness) { + this.minimumThickness = thickness; + } + } + } + + public static boolean findCollisions( + @Nonnull Box collider, + @Nonnull Vector3d pos, + @Nonnull Vector3d v, + @Nonnull CollisionResult result, + @Nonnull ComponentAccessor componentAccessor + ) { + return findCollisions(collider, pos, v, true, result, componentAccessor); + } + + public static boolean findCollisions( + @Nonnull Box collider, + @Nonnull Vector3d pos, + @Nonnull Vector3d v, + boolean stopOnCollisionFound, + @Nonnull CollisionResult result, + @Nonnull ComponentAccessor componentAccessor + ) { + World world = componentAccessor.getExternalData().getWorld(); + result.reset(); + boolean isFarDistance = !isBelowMovementThreshold(v); + if (isFarDistance) { + findBlockCollisionsIterative(world, collider, pos, v, stopOnCollisionFound, result); + } else { + findBlockCollisionsShortDistance(world, collider, pos, v, result); + } + + if (result.isCheckingForCharacterCollisions()) { + findCharacterCollisions(pos, v, result, componentAccessor); + } + + result.process(); + return isFarDistance; + } + + public static void findBlockCollisionsIterative( + @Nonnull World world, @Nonnull Box collider, @Nonnull Vector3d pos, @Nonnull Vector3d v, boolean stopOnCollisionFound, @Nonnull CollisionResult result + ) { + if (result.shouldLog()) { + result.getLogger() + .at(Level.INFO) + .log( + ">>>>>> Start findBlockCollisionIterative collider=[%s] pos=%s dir=%s", collider, Vector3d.formatShortString(pos), Vector3d.formatShortString(v) + ); + } + + CollisionConfig coll = result.getConfig(); + coll.setWorld(world); + result.getMovingBoxBoxCollision().setCollider(collider).setMove(pos, v); + if (result.shouldLog()) { + result.getLogger().at(Level.INFO).log(">>>>>> Start collider=[%s] + offset[%s]", collider, v); + } + + result.acquireCollisionModule(); + collider.forEachBlock(pos, 1.0E-5, result, (x, y, z, aResult) -> aResult.accept(x, y, z)); + if (result.shouldLog()) { + result.getLogger().at(Level.INFO).log(">>>> line collider=[%s] dir=%s len=%s", collider, Vector3d.formatShortString(v), v.length()); + } + + result.iterateBlocks(collider, pos, v, v.length(), stopOnCollisionFound); + coll.clear(); + } + + public static void findCharacterCollisions( + @Nonnull Vector3d pos, @Nonnull Vector3d v, @Nonnull CollisionResult result, @Nonnull ComponentAccessor componentAccessor + ) { + if (!isBelowMovementThreshold(v)) { + Vector3d coll = new Vector3d(); + Vector2d minMax = new Vector2d(); + List collisionEntities = result.getCollisionEntities(); + + for (int i = 0; i < collisionEntities.size(); i++) { + Entity entity = collisionEntities.get(i); + Ref ref = entity.getReference(); + + assert ref != null; + + Archetype archetype = componentAccessor.getArchetype(ref); + boolean isProjectile = archetype.contains(Projectile.getComponentType()) || archetype.contains(ProjectileComponent.getComponentType()); + if (!isProjectile) { + if (archetype.contains(DeathComponent.getComponentType())) { + return; + } + + TransformComponent entityTransformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert entityTransformComponent != null; + + BoundingBox entityBoundingBoxComponent = componentAccessor.getComponent(ref, BoundingBox.getComponentType()); + + assert entityBoundingBoxComponent != null; + + Vector3d position = entityTransformComponent.getPosition(); + Box boundingBox = entityBoundingBoxComponent.getBoundingBox(); + if (boundingBox != null && CollisionMath.intersectVectorAABB(pos, v, position.getX(), position.getY(), position.getZ(), boundingBox, minMax)) { + coll.assign(pos).addScaled(v, minMax.x); + result.allocCharacterCollision().assign(coll, minMax.x, entity.getReference(), entity instanceof Player); + } + } + } + } + } + + public static void findBlockCollisionsShortDistance( + @Nonnull World world, @Nonnull Box collider, @Nonnull Vector3d pos, @Nonnull Vector3d v, @Nonnull CollisionResult result + ) { + result.reset(); + result.getConfig().setWorld(world); + result.getConfig().extraData1 = pos; + BoxBlockIntersectionEvaluator boxBlockIntersectionEvaluator = result.getBoxBlockIntersection(); + boxBlockIntersectionEvaluator.setBox(collider, pos).offsetPosition(v); + collider.forEachBlock( + pos.x + v.x, + pos.y + v.y, + pos.z + v.z, + 1.0E-5, + result, + (x, y, z, aResult) -> { + CollisionConfig coll = aResult.getConfig(); + if (!coll.canCollide(x, y, z)) { + if (aResult.shouldLog()) { + String name = coll.blockType != null ? coll.blockType.getId().toString() : "null"; + aResult.getLogger().at(Level.INFO).log("-- Short: Ignoring block at %s/%s/%s blockType=%s", x, y, z, name); + } + + return true; + } else { + Vector3d _pos = (Vector3d)coll.extraData1; + if (coll.blockId == Integer.MIN_VALUE) { + addImmediateCollision(_pos, aResult, coll, 0); + if (aResult.shouldLog()) { + aResult.getLogger().at(Level.INFO).log("-- Short: Stopping with invalid block at %s/%s/%s blockType=", x, y, z); + } + + return true; + } else { + int boundingBoxX = x + coll.getBoundingBoxOffsetX(); + int boundingBoxY = y + coll.getBoundingBoxOffsetY(); + int boundingBoxZ = z + coll.getBoundingBoxOffsetZ(); + int numDetails = coll.getDetailCount(); + BoxBlockIntersectionEvaluator blockBox = aResult.getBoxBlockIntersection(); + int code = blockBox.intersectBoxComputeTouch(coll.getBoundingBox(), boundingBoxX, boundingBoxY, boundingBoxZ); + boolean haveCollision = !CollisionMath.isDisjoint(code); + if (aResult.shouldLog()) { + String name = coll.blockType != null ? coll.blockType.getId().toString() : "null"; + aResult.getLogger() + .at(Level.INFO) + .log( + "?? Block Test at %s/%s/%s numDet=%d haveColl=%s overlap=%s blockType=%s", + x, + y, + z, + numDetails, + haveCollision, + aResult.getBoxBlockIntersection().isOverlapping(), + name + ); + } + + if (numDetails <= 1) { + processCollision(aResult, _pos, blockBox, haveCollision, 0); + } else { + for (int i = 0; i < numDetails; i++) { + code = blockBox.intersectBoxComputeTouch(coll.getBoundingBox(i), boundingBoxX, boundingBoxY, boundingBoxZ); + haveCollision = !CollisionMath.isDisjoint(code); + processCollision(aResult, _pos, blockBox, haveCollision, i); + } + } + + return true; + } + } + } + ); + result.getConfig().clear(); + } + + protected static void processCollision( + @Nonnull CollisionResult result, + @Nonnull Vector3d pos, + @Nonnull BoxBlockIntersectionEvaluator boxBlockIntersectionEvaluator, + boolean haveCollision, + int hitboxIndex + ) { + CollisionConfig coll = result.getConfig(); + Predicate isWalkable = coll.getBlockCollisionPredicate(); + if (result.shouldLog()) { + result.getLogger() + .at(Level.INFO) + .log( + "?? Short: Further testing block haveCol=%s hitBoxIndex=%s onGround=%s touching=%s canCollide=%s canTrigger=%s", + haveCollision, + hitboxIndex, + boxBlockIntersectionEvaluator.isOnGround(), + boxBlockIntersectionEvaluator.isTouching(), + coll.blockCanCollide, + coll.blockCanTrigger + ); + } + + if (boxBlockIntersectionEvaluator.isOnGround() && coll.blockCanCollide) { + haveCollision = coll.blockType == null || !isWalkable.test(coll); + if (!haveCollision) { + result.addSlide(boxBlockIntersectionEvaluator, hitboxIndex); + if (coll.blockCanTrigger) { + result.addTrigger(boxBlockIntersectionEvaluator, hitboxIndex); + } + + if (result.shouldLog()) { + result.getLogger() + .at(Level.INFO) + .log( + "++ Short: Sliding block start=%s end=%s normal=%s", + boxBlockIntersectionEvaluator.getCollisionStart(), + boxBlockIntersectionEvaluator.getCollisionEnd(), + Vector3d.formatShortString(boxBlockIntersectionEvaluator.getCollisionNormal()) + ); + } + + return; + } + + if (result.shouldLog()) { + result.getLogger() + .at(Level.INFO) + .log( + "?? Short: Sliding block is unwalkable start=%s end=%s normal=%s", + boxBlockIntersectionEvaluator.getCollisionStart(), + boxBlockIntersectionEvaluator.getCollisionEnd(), + Vector3d.formatShortString(boxBlockIntersectionEvaluator.getCollisionNormal()) + ); + } + } + + if (haveCollision && coll.blockCanCollide) { + addImmediateCollision(pos, result, coll, hitboxIndex); + if (result.shouldLog()) { + result.getLogger() + .at(Level.INFO) + .log( + "++ Short: Collision with block start=%s end=%s normal=%s", + boxBlockIntersectionEvaluator.getCollisionStart(), + boxBlockIntersectionEvaluator.getCollisionEnd(), + Vector3d.formatShortString(boxBlockIntersectionEvaluator.getCollisionNormal()) + ); + } + } + + if (coll.blockCanTrigger && (haveCollision || boxBlockIntersectionEvaluator.isTouching())) { + if (result.shouldLog()) { + result.getLogger() + .at(Level.INFO) + .log( + "++ Short: Trigger block start=%s end=%s normal=%s", + boxBlockIntersectionEvaluator.getCollisionStart(), + boxBlockIntersectionEvaluator.getCollisionEnd(), + Vector3d.formatShortString(boxBlockIntersectionEvaluator.getCollisionNormal()) + ); + } + + result.addTrigger(boxBlockIntersectionEvaluator, hitboxIndex); + } + } + + public void findIntersections( + @Nonnull World world, @Nonnull Box collider, @Nonnull Vector3d pos, @Nonnull CollisionResult result, boolean triggerBlocks, boolean intersections + ) { + if (!this.isDisabled()) { + result.reset(); + result.getConfig().setWorld(world); + result.getConfig().extraData1 = triggerBlocks; + result.getConfig().extraData2 = intersections; + result.getBoxBlockIntersection().setBox(collider, pos).expandBox(0.01); + result.getBoxBlockIntersection().box.forEachBlock(pos, 1.0E-5, result, (x, y, z, aResult) -> { + CollisionConfig coll = aResult.getConfig(); + if (!coll.canCollide(x, y, z)) { + return true; + } else { + int boundingBoxX = x + coll.getBoundingBoxOffsetX(); + int boundingBoxY = y + coll.getBoundingBoxOffsetY(); + int boundingBoxZ = z + coll.getBoundingBoxOffsetZ(); + if (!aResult.getBoxBlockIntersection().isBoxIntersecting(coll.getBoundingBox(), boundingBoxX, boundingBoxY, boundingBoxZ)) { + return true; + } else { + boolean _triggerBlocks = (Boolean)coll.extraData1; + boolean _intersections = (Boolean)coll.extraData2; + int numDetails = coll.getDetailCount(); + if (numDetails <= 1) { + if (_triggerBlocks && coll.blockCanTrigger) { + aResult.addTrigger(aResult.getBoxBlockIntersection(), 0); + } + + if (_intersections) { + aResult.addCollision(aResult.getBoxBlockIntersection(), 0); + } + } else { + for (int i = 0; i < numDetails; i++) { + if (aResult.getBoxBlockIntersection().isBoxIntersecting(coll.getBoundingBox(i), boundingBoxX, boundingBoxY, boundingBoxZ)) { + if (_triggerBlocks && coll.blockCanTrigger) { + aResult.addTrigger(aResult.getBoxBlockIntersection(), i); + } + + if (_intersections) { + aResult.addCollision(aResult.getBoxBlockIntersection(), i); + } + } + } + } + + return true; + } + } + }); + result.getConfig().clear(); + } + } + + public int validatePosition(@Nonnull World world, @Nonnull Box collider, @Nonnull Vector3d pos, @Nonnull CollisionResult result) { + return this.isDisabled() + ? 0 + : this.validatePosition(world, collider, pos, null, (_this, collisionCode, collision, collisionConfig) -> true, false, result); + } + + public int validatePosition( + @Nonnull World world, + @Nonnull Box collider, + @Nonnull Vector3d pos, + int invalidBlockMaterials, + T t, + @Nonnull CollisionFilter predicate, + @Nonnull CollisionResult result + ) { + if (this.isDisabled()) { + return 0; + } else { + int savedCollisionState = result.getCollisionByMaterial(); + result.setCollisionByMaterial(invalidBlockMaterials); + int code = this.validatePosition(world, collider, pos, t, predicate, (invalidBlockMaterials & 16) == 0, result); + result.setCollisionByMaterial(savedCollisionState); + return code; + } + } + + private int validatePosition( + @Nonnull World world, + @Nonnull Box collider, + @Nonnull Vector3d pos, + T t, + @Nonnull CollisionFilter predicate, + boolean disableDamageBlocks, + @Nonnull CollisionResult result + ) { + CollisionModuleConfig config = this.config.get(); + result.getConfig().setWorld(world); + result.getConfig().dumpInvalidBlocks = config.isDumpInvalidBlocks(); + result.getConfig().extraData1 = t; + result.getConfig().extraData2 = predicate; + result.getBoxBlockIntersection().setBox(collider, pos); + boolean saveCheckTriggerState = result.isCheckingTriggerBlocks(); + boolean saveCheckDamageBlock = result.isCheckingDamageBlocks(); + result.disableTriggerBlocks(); + if (disableDamageBlocks) { + result.disableDamageBlocks(); + } + + result.validate = 0; + collider.forEachBlock(pos, 1.0E-5, result, (x, y, z, aResult) -> { + CollisionConfig coll = aResult.getConfig(); + if (!coll.canCollide(x, y, z)) { + return true; + } else { + BoxBlockIntersectionEvaluator boxBlockIntersection = aResult.getBoxBlockIntersection(); + int boundingBoxX = x + coll.getBoundingBoxOffsetX(); + int boundingBoxY = y + coll.getBoundingBoxOffsetY(); + int boundingBoxZ = z + coll.getBoundingBoxOffsetZ(); + int code = boxBlockIntersection.intersectBoxComputeOnGround(coll.getBoundingBox(), boundingBoxX, boundingBoxY, boundingBoxZ); + if (coll.blockId == Integer.MIN_VALUE) { + if (CollisionMath.isOverlapping(code)) { + aResult.validate = -1; + return false; + } else { + return true; + } + } else if (CollisionMath.isDisjoint(code)) { + return true; + } else { + Box _collider = boxBlockIntersection.box; + Vector3d _pos = boxBlockIntersection.collisionPoint; + Object _t = coll.extraData1; + CollisionFilter _predicate = (CollisionFilter)coll.extraData2; + int numDetails = coll.getDetailCount(); + if (numDetails <= 1) { + if (!_predicate.test(_t, code, boxBlockIntersection, coll)) { + return true; + } + + if (CollisionMath.isOverlapping(code)) { + if (coll.dumpInvalidBlocks) { + logOverlap(_pos, _collider, coll, coll.getBoundingBox(), x, y, z, 0, code); + } + + aResult.validate = -1; + return false; + } + + if (boxBlockIntersection.isOnGround()) { + aResult.validate |= 1; + } + + if (boxBlockIntersection.touchesCeil()) { + aResult.validate |= 2; + } + } else { + for (int i = 0; i < numDetails; i++) { + code = boxBlockIntersection.intersectBoxComputeOnGround(coll.getBoundingBox(i), boundingBoxX, boundingBoxY, boundingBoxZ); + if (!CollisionMath.isDisjoint(code) && _predicate.test(_t, code, boxBlockIntersection, coll)) { + if (CollisionMath.isOverlapping(code)) { + if (coll.dumpInvalidBlocks) { + logOverlap(_pos, _collider, coll, coll.getBoundingBox(i), x, y, z, i, code); + } + + aResult.validate = -1; + return false; + } + + if (boxBlockIntersection.isOnGround()) { + aResult.validate |= 1; + } + + if (boxBlockIntersection.touchesCeil()) { + aResult.validate |= 2; + } + } + } + } + + return true; + } + } + }); + if (saveCheckTriggerState) { + result.enableTriggerBlocks(); + } + + if (saveCheckDamageBlock) { + result.enableDamageBlocks(); + } + + result.getConfig().clear(); + return result.validate; + } + + private static void addImmediateCollision(@Nonnull Vector3d pos, @Nonnull CollisionResult result, @Nonnull CollisionConfig coll, int i) { + BlockCollisionData data = result.newCollision(); + data.setStart(pos, 0.0); + data.setEnd(1.0, result.getBoxBlockIntersection().getCollisionNormal()); + data.setBlockData(coll); + data.setDetailBoxIndex(i); + data.setTouchingOverlapping(false, true); + } + + public static boolean isBelowMovementThreshold(@Nonnull Vector3d v) { + return v.squaredLength() < 1.0000000000000002E-10; + } + + private static void logOverlap( + @Nonnull Vector3d pos, @Nonnull Box collider, @Nonnull CollisionConfig coll, @Nonnull Box hitBox, int x, int y, int z, int index, int intersectType + ) { + get() + .getLogger() + .at(Level.WARNING) + .log( + "Overlapping blocks - code=%s%s%s index=%s pos=%s loc=%s/%s/%s id=%s mat=%s name=%s box=%s hitbox=%s|%s", + (intersectType & 8) != 0 ? "X" : "", + (intersectType & 16) != 0 ? "Y" : "", + (intersectType & 32) != 0 ? "Z" : "", + index, + Vector3d.formatShortString(pos), + x + coll.getBoundingBoxOffsetX(), + y + coll.getBoundingBoxOffsetY(), + z + coll.getBoundingBoxOffsetZ(), + coll.blockId, + coll.blockMaterial != null ? coll.blockMaterial.name() : "none", + coll.blockType != null ? coll.blockType.getId() : "none", + collider, + Vector3d.formatShortString(hitBox.min), + Vector3d.formatShortString(hitBox.max) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/CollisionModuleConfig.java b/src/com/hypixel/hytale/server/core/modules/collision/CollisionModuleConfig.java new file mode 100644 index 0000000..7d92651 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/CollisionModuleConfig.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nullable; + +public class CollisionModuleConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder(CollisionModuleConfig.class, CollisionModuleConfig::new) + .addField(new KeyedCodec<>("ExtentMax", Codec.DOUBLE), (config, d) -> config.extentMax = d, config -> config.extentMax) + .addField(new KeyedCodec<>("DumpInvalidBlocks", Codec.BOOLEAN), (config, b) -> config.dumpInvalidBlocks = b, config -> config.dumpInvalidBlocks) + .addField(new KeyedCodec<>("MinimumThickness", Codec.DOUBLE), (config, d) -> config.minimumThickness = d, config -> config.minimumThickness) + .build(); + public static final double MOVEMENT_THRESHOLD = 1.0E-5; + public static final double MOVEMENT_THRESHOLD_SQUARED = 1.0000000000000002E-10; + public static final double EXTENT = 1.0E-5; + private double extentMax = 0.0; + private boolean dumpInvalidBlocks = false; + @Nullable + private Double minimumThickness = null; + + public CollisionModuleConfig() { + } + + public double getExtentMax() { + return this.extentMax; + } + + public void setExtentMax(double extentMax) { + this.extentMax = extentMax; + } + + public boolean isDumpInvalidBlocks() { + return this.dumpInvalidBlocks; + } + + public void setDumpInvalidBlocks(boolean dumpInvalidBlocks) { + this.dumpInvalidBlocks = dumpInvalidBlocks; + } + + public double getMinimumThickness() { + return this.minimumThickness; + } + + public void setMinimumThickness(double minimumThickness) { + this.minimumThickness = minimumThickness; + } + + public boolean hasMinimumThickness() { + return this.minimumThickness != null; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/CollisionResult.java b/src/com/hypixel/hytale/server/core/modules/collision/CollisionResult.java new file mode 100644 index 0000000..f709ba4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/CollisionResult.java @@ -0,0 +1,607 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.block.BlockUtil; +import com.hypixel.hytale.math.iterator.BoxBlockIterator; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.Predicate; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CollisionResult implements BoxBlockIterator.BoxIterationConsumer { + public static final Comparator BLOCK_COLLISION_DATA_COMPARATOR = Comparator.comparingDouble(a -> a.collisionStart) + .thenComparingDouble(a -> a.collisionEnd); + @Nonnull + private final CollisionConfig collisionConfig; + @Nonnull + private final CollisionDataArray blockCollisions; + @Nonnull + private final CollisionDataArray blockSlides; + @Nonnull + private final CollisionDataArray blockTriggers; + @Nonnull + private final CollisionDataArray characterCollisions; + @Nonnull + private final MovingBoxBoxCollisionEvaluator movingBoxBoxCollision; + @Nonnull + private final BoxBlockIntersectionEvaluator boxBlockIntersection; + public List collisionEntities; + private boolean continueAfterCollision = true; + private boolean haveNoCollision = true; + private HytaleLogger logger; + public double slideStart; + public double slideEnd; + public boolean isSliding; + public int validate; + private boolean checkForCharacterCollisions; + private int walkableMaterialMask; + public Predicate isNonWalkable; + private LongSet lastTriggers = new LongOpenHashSet(); + private LongSet newTriggers = new LongOpenHashSet(); + + public CollisionResult() { + this(true, false); + } + + public CollisionResult(boolean enableSlides, boolean enableCharacters) { + ObjectArrayList blockCollisionDataFreePool = new ObjectArrayList<>(); + ObjectArrayList characterCollisionDataFreePool = new ObjectArrayList<>(); + this.blockCollisions = new CollisionDataArray<>(BlockCollisionData::new, BlockCollisionData::clear, blockCollisionDataFreePool); + this.blockSlides = new CollisionDataArray<>(BlockCollisionData::new, BlockCollisionData::clear, blockCollisionDataFreePool); + this.blockTriggers = new CollisionDataArray<>(BlockCollisionData::new, BlockCollisionData::clear, blockCollisionDataFreePool); + this.characterCollisions = new CollisionDataArray<>(CharacterCollisionData::new, null, characterCollisionDataFreePool); + this.collisionConfig = new CollisionConfig(); + this.collisionConfig.setDefaultCollisionBehaviour(); + this.movingBoxBoxCollision = new MovingBoxBoxCollisionEvaluator(); + this.movingBoxBoxCollision.setCheckForOnGround(enableSlides); + this.boxBlockIntersection = new BoxBlockIntersectionEvaluator(); + this.checkForCharacterCollisions = enableCharacters; + this.setDefaultWalkableBehaviour(); + } + + @Nonnull + public CollisionConfig getConfig() { + return this.collisionConfig; + } + + public List getCollisionEntities() { + return this.collisionEntities; + } + + public void setCollisionEntities(List collisionEntities) { + this.collisionEntities = collisionEntities; + } + + @Nonnull + public BoxBlockIntersectionEvaluator getBoxBlockIntersection() { + return this.boxBlockIntersection; + } + + @Nonnull + public MovingBoxBoxCollisionEvaluator getMovingBoxBoxCollision() { + return this.movingBoxBoxCollision; + } + + public CharacterCollisionData allocCharacterCollision() { + return this.characterCollisions.alloc(); + } + + public void addCollision(@Nonnull IBlockCollisionEvaluator blockCollisionEvaluator, int index) { + if (!(blockCollisionEvaluator.getCollisionStart() > 1.0)) { + blockCollisionEvaluator.setCollisionData(this.newCollision(), this.collisionConfig, index); + } + } + + public BlockCollisionData newCollision() { + return this.blockCollisions.alloc(); + } + + public void addSlide(@Nonnull IBlockCollisionEvaluator blockCollisionEvaluator, int index) { + if (!(blockCollisionEvaluator.getCollisionStart() > 1.0)) { + blockCollisionEvaluator.setCollisionData(this.newSlide(), this.collisionConfig, index); + } + } + + public BlockCollisionData newSlide() { + return this.blockSlides.alloc(); + } + + public void addTrigger(@Nonnull IBlockCollisionEvaluator blockCollisionEvaluator, int index) { + if (!(blockCollisionEvaluator.getCollisionStart() > 1.0)) { + blockCollisionEvaluator.setCollisionData(this.newTrigger(), this.collisionConfig, index); + } + } + + public BlockCollisionData newTrigger() { + return this.blockTriggers.alloc(); + } + + public void reset() { + this.blockCollisions.reset(); + this.blockSlides.reset(); + this.blockTriggers.reset(); + this.characterCollisions.reset(); + } + + public void process() { + this.blockCollisions.sort(BasicCollisionData.COLLISION_START_COMPARATOR); + this.blockTriggers.sort(BasicCollisionData.COLLISION_START_COMPARATOR); + this.characterCollisions.sort(BasicCollisionData.COLLISION_START_COMPARATOR); + if (this.blockSlides.getCount() > 0) { + this.blockSlides.sort(BLOCK_COLLISION_DATA_COMPARATOR); + BlockCollisionData slide = this.blockSlides.get(0); + this.slideStart = slide.collisionStart; + this.slideEnd = slide.collisionEnd; + + for (int i = 1; i < this.blockSlides.getCount(); i++) { + slide = this.blockSlides.get(i); + if (slide.collisionStart <= this.slideEnd && slide.collisionEnd > this.slideEnd) { + this.slideEnd = slide.collisionEnd; + } + } + + this.isSliding = this.slideStart <= 0.0; + if (this.slideEnd > 1.0) { + this.slideEnd = 1.0; + } + } else { + this.isSliding = false; + } + } + + public int getBlockCollisionCount() { + return this.blockCollisions.getCount(); + } + + public BlockCollisionData getBlockCollision(int i) { + return this.blockCollisions.get(i); + } + + @Nullable + public BlockCollisionData getFirstBlockCollision() { + return this.blockCollisions.getFirst(); + } + + @Nullable + public BlockCollisionData forgetFirstBlockCollision() { + return this.blockCollisions.forgetFirst(); + } + + public int getCharacterCollisionCount() { + return this.characterCollisions.getCount(); + } + + @Nullable + public CharacterCollisionData getFirstCharacterCollision() { + return this.characterCollisions.getFirst(); + } + + @Nullable + public CharacterCollisionData forgetFirstCharacterCollision() { + return this.characterCollisions.forgetFirst(); + } + + public void pruneTriggerBlocks(double distance) { + for (int l = this.blockTriggers.size() - 1; l >= 0; l--) { + BlockCollisionData blockCollisionData = this.blockTriggers.get(l); + if (blockCollisionData.collisionStart <= distance) { + break; + } + + this.blockTriggers.remove(l); + } + } + + @Nonnull + public CollisionDataArray getTriggerBlocks() { + return this.blockTriggers; + } + + public int defaultTriggerBlocksProcessing( + @Nonnull InteractionManager manager, + @Nonnull Entity entity, + @Nonnull Ref ref, + boolean executeTriggers, + @Nonnull ComponentAccessor componentAccessor + ) { + World world = componentAccessor.getExternalData().getWorld(); + LongSet temp = this.lastTriggers; + this.lastTriggers = this.newTriggers; + this.newTriggers = temp; + this.newTriggers.clear(); + int damageToEntity = 0; + CollisionDataArray triggerBlocks = this.getTriggerBlocks(); + int i = 0; + + for (int size = triggerBlocks.size(); i < size; i++) { + BlockCollisionData triggerCollision = triggerBlocks.get(i); + if (triggerCollision.blockType != null) { + int damageToEntities = Math.max(triggerCollision.blockType.getDamageToEntities(), triggerCollision.fluid.getDamageToEntities()); + if (damageToEntities > damageToEntity) { + damageToEntity = damageToEntities; + } + } + + if (executeTriggers && entity instanceof LivingEntity) { + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(triggerCollision.x, triggerCollision.z)); + if (chunk != null) { + BlockType blockType = chunk.getBlockType(triggerCollision.x, triggerCollision.y, triggerCollision.z); + Fluid fluidType = Fluid.getAssetMap().getAsset(chunk.getFluidId(triggerCollision.x, triggerCollision.y, triggerCollision.z)); + String interactionsEnter = blockType.getInteractions().get(InteractionType.CollisionEnter); + if (interactionsEnter == null) { + interactionsEnter = fluidType.getInteractions().get(InteractionType.CollisionEnter); + } + + String interactions = blockType.getInteractions().get(InteractionType.Collision); + if (interactions == null) { + interactions = fluidType.getInteractions().get(InteractionType.Collision); + } + + if (interactionsEnter != null || interactions != null) { + int filler = chunk.getFiller(triggerCollision.x, triggerCollision.y, triggerCollision.z); + int x = triggerCollision.x; + int y = triggerCollision.y; + int z = triggerCollision.z; + String blockTypeId = blockType.getId(); + if (filler != 0) { + x -= FillerBlockUtil.unpackX(filler); + y -= FillerBlockUtil.unpackY(filler); + z -= FillerBlockUtil.unpackZ(filler); + } + + long index = BlockUtil.pack(x, y, z); + if (this.newTriggers.add(index)) { + BlockPosition pos = new BlockPosition(x, y, z); + if (!this.lastTriggers.remove(index) && interactionsEnter != null) { + this.doCollisionInteraction(manager, InteractionType.CollisionEnter, ref, interactionsEnter, pos, componentAccessor); + } + + if (interactions != null) { + this.doCollisionInteraction(manager, InteractionType.Collision, ref, interactions, pos, componentAccessor); + } + } + } + } + } + } + + if (executeTriggers && entity instanceof LivingEntity && !this.lastTriggers.isEmpty()) { + for (Long old : this.lastTriggers) { + int xx = BlockUtil.unpackX(old); + int yx = BlockUtil.unpackY(old); + int zx = BlockUtil.unpackZ(old); + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(xx, zx)); + if (chunk != null) { + BlockType blockTypex = chunk.getBlockType(xx, yx, zx); + Fluid fluidTypex = Fluid.getAssetMap().getAsset(chunk.getFluidId(xx, yx, zx)); + String interactionsx = blockTypex.getInteractions().get(InteractionType.CollisionLeave); + if (interactionsx == null) { + interactionsx = fluidTypex.getInteractions().get(InteractionType.CollisionLeave); + } + + if (interactionsx != null) { + this.doCollisionInteraction(manager, InteractionType.CollisionLeave, ref, interactionsx, new BlockPosition(xx, yx, zx), componentAccessor); + } + } + } + } + + return damageToEntity; + } + + private void doCollisionInteraction( + @Nonnull InteractionManager manager, + @Nonnull InteractionType type, + @Nonnull Ref ref, + @Nonnull String interactions, + @Nonnull BlockPosition pos, + @Nonnull ComponentAccessor componentAccessor + ) { + RootInteraction root = RootInteraction.getRootInteractionOrUnknown(interactions); + World world = componentAccessor.getExternalData().getWorld(); + InteractionContext context = InteractionContext.forInteraction(manager, ref, type, componentAccessor); + context.getMetaStore().putMetaObject(Interaction.TARGET_BLOCK_RAW, pos); + context.getMetaStore().putMetaObject(Interaction.TARGET_BLOCK, world.getBaseBlock(pos)); + InteractionChain chain = manager.initChain(type, context, root, -1, pos, false); + manager.queueExecuteChain(chain); + } + + @Override + public boolean next() { + return this.continueAfterCollision || this.haveNoCollision; + } + + @Override + public boolean accept(long x, long y, long z) { + if (this.collisionConfig.canCollide((int)x, (int)y, (int)z)) { + x += this.collisionConfig.getBoundingBoxOffsetX(); + y += this.collisionConfig.getBoundingBoxOffsetY(); + z += this.collisionConfig.getBoundingBoxOffsetZ(); + int numDetails = this.collisionConfig.getDetailCount(); + boolean haveCollision = this.movingBoxBoxCollision.isBoundingBoxColliding(this.collisionConfig.getBoundingBox(), x, y, z); + if (this.logger != null) { + Object arg7 = this.collisionConfig.blockType != null ? this.collisionConfig.blockType.getId() : "null"; + this.logger + .at(Level.INFO) + .log( + "?? Block Test at %s/%s/%s numDet=%d haveColl=%s overlap=%s blockType=%s", + x, + y, + z, + numDetails, + haveCollision, + this.movingBoxBoxCollision.isOverlapping(), + arg7 + ); + } + + if (numDetails <= 1) { + this.processCollisionResult(haveCollision, 0); + } else if (haveCollision || this.movingBoxBoxCollision.isOverlapping() || this.movingBoxBoxCollision.isTouching()) { + for (int i = 0; i < numDetails; i++) { + haveCollision = this.movingBoxBoxCollision.isBoundingBoxColliding(this.collisionConfig.getBoundingBox(i), x, y, z); + this.processCollisionResult(haveCollision, i); + } + } + } else if (this.logger != null) { + Object arg4 = this.collisionConfig.blockType != null ? this.collisionConfig.blockType.getId() : "null"; + this.logger.at(Level.INFO).log("-- Ignoring block at %s/%s/%s blockType=%s", x, y, z, arg4); + } + + return true; + } + + private void processCollisionResult(boolean haveCollision, int hitboxIndex) { + if (this.logger != null) { + this.logger + .at(Level.INFO) + .log( + "?? Further testing block haveCol=%s hitBoxIndex=%s onGround=%s touching=%s canCollide=%s canTrigger=%s", + haveCollision, + hitboxIndex, + this.movingBoxBoxCollision.isOnGround(), + this.movingBoxBoxCollision.isTouching(), + this.collisionConfig.blockCanCollide, + this.collisionConfig.blockCanTrigger + ); + } + + if (this.collisionConfig.blockCanCollide) { + boolean isNoSlideCollision = true; + if (this.movingBoxBoxCollision.onGround) { + haveCollision = this.collisionConfig.blockType == null || this.isNonWalkable.test(this.collisionConfig); + if (!haveCollision) { + this.addSlide(this.movingBoxBoxCollision, hitboxIndex); + if (this.collisionConfig.blockCanTrigger) { + this.addTrigger(this.movingBoxBoxCollision, hitboxIndex); + } + + if (this.logger != null) { + this.logger + .at(Level.INFO) + .log( + "++ Sliding block start=%s end=%s normal=%s", + this.movingBoxBoxCollision.getCollisionStart(), + this.movingBoxBoxCollision.getCollisionEnd(), + Vector3d.formatShortString(this.movingBoxBoxCollision.getCollisionNormal()) + ); + } + + return; + } + + isNoSlideCollision = false; + if (this.logger != null) { + this.logger + .at(Level.INFO) + .log( + "?? Sliding block is unwalkable start=%s end=%s normal=%s", + this.movingBoxBoxCollision.getCollisionStart(), + this.movingBoxBoxCollision.getCollisionEnd(), + Vector3d.formatShortString(this.movingBoxBoxCollision.getCollisionNormal()) + ); + } + } + + if (haveCollision) { + this.addCollision(this.movingBoxBoxCollision, hitboxIndex); + if (isNoSlideCollision) { + this.haveNoCollision = false; + } + + if (this.logger != null) { + this.logger + .at(Level.INFO) + .log( + "++ Collision with block start=%s end=%s normal=%s", + this.movingBoxBoxCollision.collisionStart, + this.movingBoxBoxCollision.collisionEnd, + Vector3d.formatShortString(this.movingBoxBoxCollision.collisionNormal) + ); + } + } + } + + if (this.collisionConfig.blockCanTrigger && (haveCollision || this.movingBoxBoxCollision.isTouching())) { + if (this.logger != null) { + this.logger + .at(Level.INFO) + .log( + "++ Trigger block start=%s end=%s normal=%s", + this.movingBoxBoxCollision.getCollisionStart(), + this.movingBoxBoxCollision.getCollisionEnd(), + Vector3d.formatShortString(this.movingBoxBoxCollision.getCollisionNormal()) + ); + } + + this.addTrigger(this.movingBoxBoxCollision, hitboxIndex); + } + } + + public void iterateBlocks(@Nonnull Box collider, @Nonnull Vector3d pos, @Nonnull Vector3d direction, double length, boolean stopOnCollisionFound) { + this.continueAfterCollision = !stopOnCollisionFound; + BoxBlockIterator.iterate(collider, pos, direction, length, this); + } + + public void acquireCollisionModule() { + this.haveNoCollision = true; + } + + public void disableSlides() { + this.movingBoxBoxCollision.setCheckForOnGround(false); + } + + public void enableSlides() { + this.movingBoxBoxCollision.setCheckForOnGround(true); + } + + public void disableCharacterCollisions() { + this.checkForCharacterCollisions = false; + } + + public void enableCharacterCollsions() { + this.checkForCharacterCollisions = true; + } + + public boolean isCheckingForCharacterCollisions() { + return this.checkForCharacterCollisions; + } + + public void enableTriggerBlocks() { + this.collisionConfig.setCheckTriggerBlocks(true); + } + + public void disableTriggerBlocks() { + this.collisionConfig.setCheckTriggerBlocks(false); + } + + public boolean isCheckingTriggerBlocks() { + return this.collisionConfig.isCheckTriggerBlocks(); + } + + public void enableDamageBlocks() { + this.collisionConfig.setCheckDamageBlocks(true); + } + + public void disableDamageBlocks() { + this.collisionConfig.setCheckDamageBlocks(false); + } + + public boolean isCheckingDamageBlocks() { + return this.collisionConfig.isCheckDamageBlocks(); + } + + public boolean setDamageBlocking(boolean blocking) { + boolean oldState = this.collisionConfig.setCollideWithDamageBlocks(blocking); + this.updateDamageWalkableFlag(); + return oldState; + } + + public boolean isDamageBlocking() { + return this.collisionConfig.isCollidingWithDamageBlocks(); + } + + public void setCollisionByMaterial(int collidingMaterials) { + this.collisionConfig.setCollisionByMaterial(collidingMaterials); + } + + public void setCollisionByMaterial(int collidingMaterials, int walkableMaterials) { + this.collisionConfig.setCollisionByMaterial(collidingMaterials); + this.setWalkableByMaterial(walkableMaterials); + } + + public int getCollisionByMaterial() { + return this.collisionConfig.getCollisionByMaterial(); + } + + public void setDefaultCollisionBehaviour() { + this.collisionConfig.setDefaultCollisionBehaviour(); + } + + public void setDefaultBlockCollisionPredicate() { + this.collisionConfig.setDefaultBlockCollisionPredicate(); + } + + public void setDefaultNonWalkablePredicate() { + this.isNonWalkable = collisionConfig -> { + int matches = collisionConfig.blockMaterialMask & this.walkableMaterialMask; + return matches == 0 || (matches & 16) != 0; + }; + } + + public void setNonWalkablePredicate(Predicate classifier) { + this.isNonWalkable = classifier; + } + + public void setWalkableByMaterial(int walkableMaterial) { + this.walkableMaterialMask = 15 & walkableMaterial; + this.updateDamageWalkableFlag(); + } + + protected void updateDamageWalkableFlag() { + if (this.collisionConfig.isCollidingWithDamageBlocks()) { + this.walkableMaterialMask |= 16; + } else { + this.walkableMaterialMask &= -17; + } + } + + public void setDefaultWalkableBehaviour() { + this.setDefaultNonWalkablePredicate(); + this.setWalkableByMaterial(5); + } + + public void setDefaultPlayerSettings() { + this.enableSlides(); + this.disableCharacterCollisions(); + this.setDefaultNonWalkablePredicate(); + this.setDefaultBlockCollisionPredicate(); + this.setCollisionByMaterial(4); + this.setWalkableByMaterial(15); + } + + public boolean isComputeOverlaps() { + return this.movingBoxBoxCollision.isComputeOverlaps(); + } + + public void setComputeOverlaps(boolean computeOverlaps) { + this.movingBoxBoxCollision.setComputeOverlaps(computeOverlaps); + } + + public HytaleLogger getLogger() { + return this.logger; + } + + public boolean shouldLog() { + return this.logger != null; + } + + public void setLogger(HytaleLogger logger) { + this.logger = logger; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/CollisionTracker.java b/src/com/hypixel/hytale/server/core/modules/collision/CollisionTracker.java new file mode 100644 index 0000000..643e28b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/CollisionTracker.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CollisionTracker extends BlockTracker { + @Nonnull + protected BlockData[] blockData = new BlockData[4]; + @Nonnull + protected BlockContactData[] contactData = new BlockContactData[4]; + + public CollisionTracker() { + for (int i = 0; i < 4; i++) { + this.blockData[i] = new BlockData(); + this.contactData[i] = new BlockContactData(); + } + } + + public BlockData getBlockData(int index) { + return this.blockData[index]; + } + + public BlockContactData getContactData(int index) { + return this.contactData[index]; + } + + @Override + public void reset() { + super.reset(); + + for (int i = 0; i < this.count; i++) { + this.blockData[i].clear(); + this.contactData[i].clear(); + } + } + + public boolean track(int x, int y, int z, @Nonnull BlockContactData contactData, @Nonnull BlockData blockData) { + if (this.isTracked(x, y, z)) { + return true; + } else { + this.trackNew(x, y, z, contactData, blockData); + return false; + } + } + + @Nonnull + public BlockContactData trackNew(int x, int y, int z, @Nonnull BlockContactData contactData, @Nonnull BlockData blockData) { + super.trackNew(x, y, z); + this.blockData[this.count - 1].assign(blockData); + BlockContactData data = this.contactData[this.count - 1]; + data.assign(contactData); + return data; + } + + @Override + public void untrack(int index) { + super.untrack(index); + if (this.count == 0) { + this.blockData[0].clear(); + this.contactData[0].clear(); + } else { + int length = this.count - index; + BlockData block = this.blockData[index]; + block.clear(); + System.arraycopy(this.blockData, index + 1, this.blockData, index, length); + this.blockData[this.count] = block; + BlockContactData coll = this.contactData[index]; + coll.clear(); + System.arraycopy(this.contactData, index + 1, this.contactData, index, length); + this.contactData[this.count] = coll; + } + } + + @Nullable + public BlockContactData getContactData(int x, int y, int z) { + int index = this.getIndex(x, y, z); + return index == -1 ? null : this.contactData[index]; + } + + @Override + protected void alloc() { + super.alloc(); + int newLength = this.blockData.length + 4; + this.blockData = Arrays.copyOf(this.blockData, newLength); + this.contactData = Arrays.copyOf(this.contactData, newLength); + + for (int i = this.count; i < newLength; i++) { + this.blockData[i] = new BlockData(); + this.contactData[i] = new BlockContactData(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/EntityCollisionProvider.java b/src/com/hypixel/hytale/server/core/modules/collision/EntityCollisionProvider.java new file mode 100644 index 0000000..c3868e6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/EntityCollisionProvider.java @@ -0,0 +1,209 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.entities.ProjectileComponent; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.projectile.component.Projectile; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityCollisionProvider { + protected static final int ALLOC_SIZE = 4; + protected static final double EXTRA_DISTANCE = 8.0; + protected EntityContactData[] contacts; + protected EntityContactData[] sortBuffer; + protected int count; + protected final Vector2d minMax = new Vector2d(); + protected final Vector3d collisionPosition = new Vector3d(); + protected final Box tempBox = new Box(); + protected double nearestCollisionStart; + @Nullable + protected Vector3d position; + @Nullable + protected Vector3d direction; + @Nullable + protected Box boundingBox; + @Nullable + protected BiPredicate, ComponentAccessor> entityFilter; + @Nullable + protected Ref ignoreSelf; + @Nullable + protected Ref ignoreOther; + + public EntityCollisionProvider() { + this.contacts = new EntityContactData[4]; + this.sortBuffer = new EntityContactData[4]; + + for (int i = 0; i < this.contacts.length; i++) { + this.contacts[i] = new EntityContactData(); + } + } + + public int getCount() { + return this.count; + } + + @Nonnull + public EntityContactData getContact(int index) { + return this.contacts[index]; + } + + public void clear() { + for (int i = 0; i < this.count; i++) { + this.contacts[i].clear(); + } + + this.count = 0; + } + + public double computeNearest( + @Nonnull Box entityBoundingBox, + @Nonnull Vector3d pos, + @Nonnull Vector3d dir, + @Nullable Ref ignoreSelf, + @Nullable Ref ignore, + @Nonnull ComponentAccessor componentAccessor + ) { + return this.computeNearest( + pos, dir, entityBoundingBox, dir.length() + 8.0, EntityCollisionProvider::defaultEntityFilter, ignoreSelf, ignore, componentAccessor + ); + } + + public double computeNearest( + @Nonnull Vector3d pos, + @Nonnull Vector3d dir, + @Nonnull Box boundingBox, + double radius, + @Nonnull BiPredicate, ComponentAccessor> entityFilter, + @Nullable Ref ignoreSelf, + @Nullable Ref ignoreOther, + @Nonnull ComponentAccessor componentAccessor + ) { + this.ignoreSelf = ignoreSelf; + this.ignoreOther = ignoreOther; + this.nearestCollisionStart = Double.MAX_VALUE; + this.entityFilter = entityFilter; + this.iterateEntitiesInSphere( + pos, + dir, + boundingBox, + radius, + (ref, _this) -> acceptNearestIgnore(ref, _this, componentAccessor), + (ref, _this1) -> acceptNearestIgnore(ref, _this1, componentAccessor), + componentAccessor + ); + if (this.count == 0) { + this.nearestCollisionStart = -Double.MAX_VALUE; + } + + this.clearRefs(); + this.ignoreSelf = null; + this.ignoreOther = null; + return this.nearestCollisionStart; + } + + protected void iterateEntitiesInSphere( + @Nonnull Vector3d pos, + @Nonnull Vector3d dir, + @Nonnull Box boundingBox, + double radius, + @Nonnull BiConsumer, EntityCollisionProvider> consumer, + @Nonnull BiConsumer, EntityCollisionProvider> consumerPlayer, + @Nonnull ComponentAccessor componentAccessor + ) { + this.position = pos; + this.direction = dir; + this.boundingBox = boundingBox; + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + SpatialResource, EntityStore> entitySpatialResource = componentAccessor.getResource(EntityModule.get().getEntitySpatialResourceType()); + entitySpatialResource.getSpatialStructure().collect(pos, radius, results); + + for (Ref ref : results) { + if (ref.isValid()) { + consumer.accept(ref, this); + } + } + + results.clear(); + SpatialResource, EntityStore> playerSpatialResource = componentAccessor.getResource(EntityModule.get().getPlayerSpatialResourceType()); + playerSpatialResource.getSpatialStructure().collect(pos, radius, results); + + for (Ref refx : results) { + if (refx.isValid()) { + consumerPlayer.accept(refx, this); + } + } + } + + protected void setContact(@Nonnull Entity entity) { + this.collisionPosition.assign(this.position).addScaled(this.direction, this.minMax.x); + this.contacts[0].assign(this.collisionPosition, this.minMax.x, this.minMax.y, entity.getReference(), null); + this.count = 1; + } + + protected boolean isColliding(@Nonnull Ref ref, @Nonnull Vector2d minMax, @Nonnull ComponentAccessor componentAccessor) { + BoundingBox boundingBoxComponent = componentAccessor.getComponent(ref, BoundingBox.getComponentType()); + if (boundingBoxComponent == null) { + return false; + } else { + Box boundingBox = boundingBoxComponent.getBoundingBox(); + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + return CollisionMath.intersectSweptAABBs(this.position, this.direction, this.boundingBox, position, boundingBox, minMax, this.tempBox) + && minMax.x <= 1.0; + } + } + + protected void clearRefs() { + this.position = null; + this.direction = null; + this.boundingBox = null; + this.entityFilter = null; + } + + public static boolean defaultEntityFilter(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + Archetype archetype = componentAccessor.getArchetype(ref); + boolean isProjectile = archetype.contains(Projectile.getComponentType()) || archetype.contains(ProjectileComponent.getComponentType()); + if (isProjectile) { + return false; + } else { + boolean isDead = archetype.contains(DeathComponent.getComponentType()); + return isDead ? false : ref.isValid(); + } + } + + protected static void acceptNearestIgnore( + @Nonnull Ref ref, @Nonnull EntityCollisionProvider collisionProvider, @Nonnull ComponentAccessor componentAccessor + ) { + Entity entity = EntityUtils.getEntity(ref, componentAccessor); + if (entity != null) { + if (entity.isCollidable() + && !ref.equals(collisionProvider.ignoreSelf) + && !ref.equals(collisionProvider.ignoreOther) + && (collisionProvider.entityFilter == null || collisionProvider.entityFilter.test(ref, componentAccessor)) + && collisionProvider.isColliding(ref, collisionProvider.minMax, componentAccessor) + && collisionProvider.minMax.x < collisionProvider.nearestCollisionStart) { + collisionProvider.nearestCollisionStart = collisionProvider.minMax.x; + collisionProvider.setContact(entity); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/EntityContactData.java b/src/com/hypixel/hytale/server/core/modules/collision/EntityContactData.java new file mode 100644 index 0000000..4a321b7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/EntityContactData.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityContactData { + protected final Vector3d collisionPoint = new Vector3d(); + protected double collisionStart; + protected double collisionEnd; + @Nullable + protected Ref entityReference; + protected String collisionDetailName; + + public EntityContactData() { + } + + @Nonnull + public Vector3d getCollisionPoint() { + return this.collisionPoint; + } + + public double getCollisionStart() { + return this.collisionStart; + } + + public double getCollisionEnd() { + return this.collisionEnd; + } + + @Nullable + public Ref getEntityReference() { + return this.entityReference; + } + + public String getCollisionDetailName() { + return this.collisionDetailName; + } + + public void assign(@Nonnull Vector3d position, double start, double end, Ref entity, String collisionDetailName) { + this.collisionPoint.assign(position); + this.collisionStart = start; + this.collisionEnd = end; + this.entityReference = entity; + this.collisionDetailName = collisionDetailName; + } + + public void clear() { + this.entityReference = null; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/EntityRefCollisionProvider.java b/src/com/hypixel/hytale/server/core/modules/collision/EntityRefCollisionProvider.java new file mode 100644 index 0000000..39e26a0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/EntityRefCollisionProvider.java @@ -0,0 +1,213 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.function.consumer.TriConsumer; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.asset.type.model.config.DetailBox; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.projectile.component.Projectile; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Map.Entry; +import java.util.function.BiPredicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityRefCollisionProvider { + protected static final int ALLOC_SIZE = 4; + protected static final double EXTRA_DISTANCE = 8.0; + protected EntityContactData[] contacts; + protected EntityContactData[] sortBuffer; + protected int count; + protected final Vector2d minMax = new Vector2d(); + protected final Vector3d collisionPosition = new Vector3d(); + protected final Box tempBox = new Box(); + protected double nearestCollisionStart; + @Nullable + protected Vector3d position; + @Nullable + protected Vector3d direction; + @Nullable + protected Box boundingBox; + @Nullable + protected BiPredicate, CommandBuffer> entityFilter; + @Nullable + protected Ref ignoreSelf; + @Nullable + protected Ref ignoreOther; + @Nonnull + protected List> tmpResults = new ObjectArrayList<>(); + @Nonnull + protected Vector3d tmpVector = new Vector3d(); + @Nullable + protected String hitDetail; + + public EntityRefCollisionProvider() { + this.contacts = new EntityContactData[4]; + this.sortBuffer = new EntityContactData[4]; + + for (int i = 0; i < this.contacts.length; i++) { + this.contacts[i] = new EntityContactData(); + } + } + + public int getCount() { + return this.count; + } + + @Nonnull + public EntityContactData getContact(int i) { + return this.contacts[i]; + } + + public void clear() { + for (int i = 0; i < this.count; i++) { + this.contacts[i].clear(); + } + + this.count = 0; + } + + public double computeNearest( + @Nonnull CommandBuffer commandBuffer, + @Nonnull Box entityBoundingBox, + @Nonnull Vector3d pos, + @Nonnull Vector3d dir, + @Nullable Ref ignoreSelf, + @Nullable Ref ignore + ) { + return this.computeNearest( + commandBuffer, pos, dir, entityBoundingBox, dir.length() + 8.0, EntityRefCollisionProvider::defaultEntityFilter, ignoreSelf, ignore + ); + } + + public double computeNearest( + @Nonnull CommandBuffer commandBuffer, + @Nonnull Vector3d pos, + @Nonnull Vector3d dir, + @Nonnull Box boundingBox, + double radius, + @Nonnull BiPredicate, CommandBuffer> entityFilter, + @Nullable Ref ignoreSelf, + @Nullable Ref ignoreOther + ) { + this.ignoreSelf = ignoreSelf; + this.ignoreOther = ignoreOther; + this.nearestCollisionStart = Double.MAX_VALUE; + this.entityFilter = entityFilter; + this.iterateEntitiesInSphere(commandBuffer, pos, dir, boundingBox, radius, EntityRefCollisionProvider::acceptNearestIgnore); + if (this.count == 0) { + this.nearestCollisionStart = -Double.MAX_VALUE; + } + + this.clearRefs(); + this.ignoreSelf = null; + this.ignoreOther = null; + return this.nearestCollisionStart; + } + + protected void iterateEntitiesInSphere( + @Nonnull CommandBuffer commandBuffer, + @Nonnull Vector3d pos, + @Nonnull Vector3d dir, + @Nonnull Box boundingBox, + double radius, + @Nonnull TriConsumer, CommandBuffer> consumer + ) { + this.position = pos; + this.direction = dir; + this.boundingBox = boundingBox; + SpatialResource, EntityStore> spatial = commandBuffer.getResource(CollisionModule.get().getTangiableEntitySpatialComponent()); + this.tmpResults.clear(); + spatial.getSpatialStructure().collect(pos, radius, this.tmpResults); + + for (Ref result : this.tmpResults) { + consumer.accept(this, result, commandBuffer); + } + } + + protected void setContact(@Nonnull Ref ref, @Nonnull String detailName) { + this.collisionPosition.assign(this.position).addScaled(this.direction, this.minMax.x); + this.contacts[0].assign(this.collisionPosition, this.minMax.x, this.minMax.y, ref, detailName); + this.count = 1; + } + + protected boolean isColliding(@Nonnull Ref ref, @Nonnull Vector2d minMax, @Nonnull CommandBuffer commandBuffer) { + BoundingBox boundingBoxComponent = commandBuffer.getComponent(ref, BoundingBox.getComponentType()); + + assert boundingBoxComponent != null; + + TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Box entityBoundingBox = boundingBoxComponent.getBoundingBox(); + if (boundingBoxComponent.getDetailBoxes() != null && !boundingBoxComponent.getDetailBoxes().isEmpty()) { + for (Entry e : boundingBoxComponent.getDetailBoxes().entrySet()) { + for (DetailBox v : e.getValue()) { + this.tmpVector.assign(v.getOffset()); + this.tmpVector.rotateY(transformComponent.getRotation().getYaw()); + this.tmpVector.add(transformComponent.getPosition()); + if (CollisionMath.intersectSweptAABBs(this.position, this.direction, this.boundingBox, this.tmpVector, v.getBox(), minMax, this.tempBox) + && minMax.x <= 1.0) { + this.hitDetail = e.getKey(); + return true; + } + } + } + + this.hitDetail = null; + return false; + } else { + this.hitDetail = null; + return CollisionMath.intersectSweptAABBs( + this.position, this.direction, this.boundingBox, transformComponent.getPosition(), entityBoundingBox, minMax, this.tempBox + ) + && minMax.x <= 1.0; + } + } + + protected void clearRefs() { + this.position = null; + this.direction = null; + this.boundingBox = null; + this.entityFilter = null; + } + + public static boolean defaultEntityFilter(@Nonnull Ref entity, @Nonnull CommandBuffer commandBuffer) { + if (!entity.isValid()) { + return false; + } else { + Archetype archetype = commandBuffer.getArchetype(entity); + if (archetype.contains(Projectile.getComponentType())) { + return false; + } else if (archetype.contains(DeathComponent.getComponentType())) { + return false; + } else { + Entity legacy = EntityUtils.getEntity(entity, commandBuffer); + return legacy == null || legacy.isCollidable(); + } + } + } + + protected void acceptNearestIgnore(@Nonnull Ref entity, @Nonnull CommandBuffer commandBuffer) { + if (this.entityFilter.test(entity, commandBuffer) + && !entity.equals(this.ignoreSelf) + && !entity.equals(this.ignoreOther) + && this.isColliding(entity, this.minMax, commandBuffer) + && this.minMax.x < this.nearestCollisionStart) { + this.nearestCollisionStart = this.minMax.x; + this.setContact(entity, this.hitDetail); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/IBlockCollisionConsumer.java b/src/com/hypixel/hytale/server/core/modules/collision/IBlockCollisionConsumer.java new file mode 100644 index 0000000..e5d0a59 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/IBlockCollisionConsumer.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector3d; + +public interface IBlockCollisionConsumer { + IBlockCollisionConsumer.Result onCollision(int var1, int var2, int var3, Vector3d var4, BlockContactData var5, BlockData var6, Box var7); + + IBlockCollisionConsumer.Result probeCollisionDamage(int var1, int var2, int var3, Vector3d var4, BlockContactData var5, BlockData var6); + + void onCollisionDamage(int var1, int var2, int var3, Vector3d var4, BlockContactData var5, BlockData var6); + + IBlockCollisionConsumer.Result onCollisionSliceFinished(); + + void onCollisionFinished(); + + public static enum Result { + CONTINUE, + STOP, + STOP_NOW; + + private Result() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/IBlockCollisionEvaluator.java b/src/com/hypixel/hytale/server/core/modules/collision/IBlockCollisionEvaluator.java new file mode 100644 index 0000000..be507f2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/IBlockCollisionEvaluator.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.server.core.modules.collision; + +public interface IBlockCollisionEvaluator { + double getCollisionStart(); + + void setCollisionData(BlockCollisionData var1, CollisionConfig var2, int var3); +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/IBlockTracker.java b/src/com/hypixel/hytale/server/core/modules/collision/IBlockTracker.java new file mode 100644 index 0000000..ba341a8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/IBlockTracker.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.math.vector.Vector3i; + +public interface IBlockTracker { + Vector3i getPosition(int var1); + + int getCount(); + + boolean track(int var1, int var2, int var3); + + void trackNew(int var1, int var2, int var3); + + boolean isTracked(int var1, int var2, int var3); + + void untrack(int var1, int var2, int var3); +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/MovingBoxBoxCollisionEvaluator.java b/src/com/hypixel/hytale/server/core/modules/collision/MovingBoxBoxCollisionEvaluator.java new file mode 100644 index 0000000..93613cc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/MovingBoxBoxCollisionEvaluator.java @@ -0,0 +1,254 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class MovingBoxBoxCollisionEvaluator extends BlockContactData implements IBlockCollisionEvaluator { + protected boolean touching; + protected Box collider; + @Nonnull + protected final Vector3d pos; + @Nonnull + protected final Vector3d v; + protected boolean checkForOnGround = true; + private boolean computeOverlaps; + @Nonnull + protected final MovingBoxBoxCollisionEvaluator.Collision1D cX; + @Nonnull + protected final MovingBoxBoxCollisionEvaluator.Collision1D cY; + @Nonnull + protected final MovingBoxBoxCollisionEvaluator.Collision1D cZ; + + public MovingBoxBoxCollisionEvaluator() { + this.pos = new Vector3d(); + this.v = new Vector3d(); + this.cX = new MovingBoxBoxCollisionEvaluator.Collision1D(); + this.cY = new MovingBoxBoxCollisionEvaluator.Collision1D(); + this.cZ = new MovingBoxBoxCollisionEvaluator.Collision1D(); + } + + @Override + public double getCollisionStart() { + return this.collisionStart; + } + + @Override + public void setCollisionData(@Nonnull BlockCollisionData data, @Nonnull CollisionConfig collisionConfig, int hitboxIndex) { + data.setStart(this.collisionPoint, this.collisionStart); + data.setEnd(this.collisionEnd, this.collisionNormal); + data.setBlockData(collisionConfig); + data.setDetailBoxIndex(hitboxIndex); + data.setTouchingOverlapping(this.touching, this.isOverlapping()); + } + + public boolean isCheckForOnGround() { + return this.checkForOnGround; + } + + public void setCheckForOnGround(boolean checkForOnGround) { + this.checkForOnGround = checkForOnGround; + } + + public boolean isComputeOverlaps() { + return this.computeOverlaps; + } + + public void setComputeOverlaps(boolean computeOverlaps) { + this.computeOverlaps = computeOverlaps; + } + + @Nonnull + public MovingBoxBoxCollisionEvaluator setCollider(Box collider) { + this.collider = collider; + return this; + } + + @Nonnull + public MovingBoxBoxCollisionEvaluator setMove(@Nonnull Vector3d pos, @Nonnull Vector3d v) { + this.pos.assign(pos); + this.v.assign(v); + this.cX.v = v.x; + this.cY.v = v.y; + this.cZ.v = v.z; + return this; + } + + public boolean isBoundingBoxColliding(@Nonnull Box blockBoundingBox, double x, double y, double z) { + this.cX.p = this.pos.x - x; + this.cY.p = this.pos.y - y; + this.cZ.p = this.pos.z - z; + this.onGround = false; + this.touching = false; + this.overlapping = false; + if (!this.cX.isColliding(blockBoundingBox.getMin().x - this.collider.getMax().x, blockBoundingBox.getMax().x - this.collider.getMin().x)) { + return false; + } else if (!this.cY.isColliding(blockBoundingBox.getMin().y - this.collider.getMax().y, blockBoundingBox.getMax().y - this.collider.getMin().y)) { + return false; + } else if (!this.cZ.isColliding(blockBoundingBox.getMin().z - this.collider.getMax().z, blockBoundingBox.getMax().z - this.collider.getMin().z)) { + return false; + } else if (this.cX.kind == 1 && this.cY.kind == 1 && this.cZ.kind == 1) { + this.overlapping = true; + if (!this.computeOverlaps) { + return false; + } else { + this.collisionStart = 0.0; + this.collisionEnd = Double.MAX_VALUE; + this.collisionNormal.assign(0.0, 0.0, 0.0); + if (this.cX.tLeave < this.collisionEnd) { + this.collisionEnd = this.cX.tLeave; + this.collisionNormal.assign(this.cX.normal, 0.0, 0.0); + } + + if (this.cY.tLeave < this.collisionEnd) { + this.collisionEnd = this.cY.tLeave; + this.collisionNormal.assign(0.0, this.cY.normal, 0.0); + } + + if (this.cZ.tLeave < this.collisionEnd) { + this.collisionEnd = this.cZ.tLeave; + this.collisionNormal.assign(0.0, 0.0, this.cZ.normal); + } + + return true; + } + } else { + this.collisionStart = -Double.MAX_VALUE; + this.collisionEnd = Double.MAX_VALUE; + if (this.cX.kind == 0) { + this.collisionNormal.assign(this.cX.normal, 0.0, 0.0); + this.collisionStart = this.cX.tEnter; + } + + if (this.cY.kind == 0 && this.cY.tEnter > this.collisionStart) { + this.collisionNormal.assign(0.0, this.cY.normal, 0.0); + this.collisionStart = this.cY.tEnter; + } + + if (this.cZ.kind == 0 && this.cZ.tEnter > this.collisionStart) { + this.collisionNormal.assign(0.0, 0.0, this.cZ.normal); + this.collisionStart = this.cZ.tEnter; + } + + if (!(this.collisionStart > -Double.MAX_VALUE)) { + if (this.checkForOnGround && this.cY.kind == 3) { + this.collisionStart = MathUtil.maxValue(this.cX.tEnter, this.cY.tEnter, this.cZ.tEnter); + this.collisionEnd = MathUtil.minValue(this.cX.tLeave, this.cY.tLeave, this.cZ.tLeave); + this.collisionPoint.assign(this.pos); + this.collisionPoint.addScaled(this.v, this.collisionStart); + this.collisionNormal.assign(0.0, this.cY.normal, 0.0); + this.onGround = true; + this.touching = true; + } + + return false; + } else { + this.collisionEnd = MathUtil.minValue(this.cX.tLeave, this.cY.tLeave, this.cZ.tLeave); + if (this.collisionStart > this.collisionEnd) { + return false; + } else { + this.collisionPoint.assign(this.pos); + this.collisionPoint.addScaled(this.v, this.collisionStart); + if (this.checkForOnGround && this.cY.kind == 3) { + this.collisionNormal.assign(0.0, this.cY.normal, 0.0); + this.onGround = true; + this.touching = true; + return false; + } else { + this.touching = this.cX.kind >= 2 || this.cY.kind >= 2 || this.cZ.kind >= 2; + return !this.touching; + } + } + } + } + } + + public boolean isTouching() { + return this.touching; + } + + public void setCollisionEnd(double collisionEnd) { + this.collisionEnd = collisionEnd; + } + + private static class Collision1D { + protected static final int COLLISION_OUTSIDE = 0; + protected static final int COLLISION_INSIDE = 1; + protected static final int COLLISION_TOUCH_MIN = 2; + protected static final int COLLISION_TOUCH_MAX = 3; + public double p; + public double v; + public double min; + public double max; + public double tEnter; + public double tLeave; + public double normal; + public int kind; + public boolean touching; + + private Collision1D() { + } + + boolean isColliding(double min, double max) { + this.min = min; + this.max = max; + this.tEnter = -Double.MAX_VALUE; + this.tLeave = Double.MAX_VALUE; + this.normal = 0.0; + this.touching = false; + double dist = min - this.p; + if (dist >= -1.0E-5) { + if (this.v < dist - 1.0E-5) { + return false; + } else { + this.normal = -1.0; + this.computeTouchOrOutside(max, dist, 2); + return true; + } + } else { + dist = max - this.p; + if (dist <= 1.0E-5) { + if (this.v > dist + 1.0E-5) { + return false; + } else { + this.normal = 1.0; + this.computeTouchOrOutside(min, dist, 3); + return true; + } + } else { + this.tEnter = 0.0; + if (this.v < 0.0) { + this.tLeave = this.clampPos((min - this.p) / this.v); + this.normal = 1.0; + } else if (this.v > 0.0) { + this.tLeave = this.clampPos((max - this.p) / this.v); + this.normal = -1.0; + } + + this.kind = 1; + return true; + } + } + } + + private void computeTouchOrOutside(double border, double dist, int touchCode) { + if (this.v != 0.0) { + this.tEnter = MathUtil.clamp(dist / this.v, 0.0, 1.0); + if (this.tEnter != 0.0 && this.tEnter < 1.0E-8) { + this.tEnter = 0.0; + } + + this.tLeave = this.clampPos((border - this.p) / this.v); + this.kind = 0; + } else { + this.tEnter = 0.0; + this.kind = touchCode; + } + } + + private double clampPos(double v) { + return v >= 0.0 ? v : 0.0; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/TangiableEntitySpatialSystem.java b/src/com/hypixel/hytale/server/core/modules/collision/TangiableEntitySpatialSystem.java new file mode 100644 index 0000000..b8f89ac --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/TangiableEntitySpatialSystem.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.spatial.SpatialSystem; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.Intangible; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class TangiableEntitySpatialSystem extends SpatialSystem { + private static final Query QUERY = Query.and( + TransformComponent.getComponentType(), BoundingBox.getComponentType(), Query.not(Intangible.getComponentType()) + ); + + public TangiableEntitySpatialSystem(ResourceType, EntityStore>> resourceType) { + super(resourceType); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + super.tick(dt, systemIndex, store); + } + + @Nonnull + @Override + public Vector3d getPosition(@Nonnull ArchetypeChunk archetypeChunk, int index) { + return archetypeChunk.getComponent(index, TransformComponent.getComponentType()).getPosition(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/WorldUtil.java b/src/com/hypixel/hytale/server/core/modules/collision/WorldUtil.java new file mode 100644 index 0000000..6322bd3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/WorldUtil.java @@ -0,0 +1,311 @@ +package com.hypixel.hytale.server.core.modules.collision; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import javax.annotation.Nonnull; + +public final class WorldUtil { + public WorldUtil() { + } + + public static boolean isFluidOnlyBlock(@Nonnull BlockType blockType, int fluidId) { + return blockType.getMaterial() == BlockMaterial.Empty && fluidId != 0; + } + + public static boolean isSolidOnlyBlock(@Nonnull BlockType blockType, int fluidId) { + return blockType.getMaterial() == BlockMaterial.Solid && fluidId == 0; + } + + public static boolean isEmptyOnlyBlock(@Nonnull BlockType blockType, int fluidId) { + return blockType.getMaterial() == BlockMaterial.Empty && fluidId == 0; + } + + public static int getFluidIdAtPosition(@Nonnull ComponentAccessor chunkStore, @Nonnull ChunkColumn chunkColumnComponent, int x, int y, int z) { + if (y >= 0 && y < 320) { + Ref sectionRef = chunkColumnComponent.getSection(ChunkUtil.chunkCoordinate(y)); + if (sectionRef != null && sectionRef.isValid()) { + FluidSection fluidSectionComponent = chunkStore.getComponent(sectionRef, FluidSection.getComponentType()); + return fluidSectionComponent == null ? 0 : fluidSectionComponent.getFluidId(x, y, z); + } else { + return 0; + } + } else { + return 0; + } + } + + public static long getPackedMaterialAndFluidAtPosition( + @Nonnull Ref chunkRef, @Nonnull ComponentAccessor chunkStore, double x, double y, double z + ) { + if (!(y < 0.0) && !(y >= 320.0)) { + int blockX = MathUtil.floor(x); + int blockY = MathUtil.floor(y); + int blockZ = MathUtil.floor(z); + ChunkColumn chunkColumnComponent = chunkStore.getComponent(chunkRef, ChunkColumn.getComponentType()); + if (chunkColumnComponent == null) { + return MathUtil.packLong(BlockMaterial.Empty.ordinal(), 0); + } else { + BlockChunk blockChunkComponent = chunkStore.getComponent(chunkRef, BlockChunk.getComponentType()); + if (blockChunkComponent == null) { + return MathUtil.packLong(BlockMaterial.Empty.ordinal(), 0); + } else { + BlockSection blockSection = blockChunkComponent.getSectionAtBlockY(blockY); + int fluidId = 0; + Ref sectionRef = chunkColumnComponent.getSection(ChunkUtil.chunkCoordinate(y)); + if (sectionRef != null && sectionRef.isValid()) { + FluidSection fluidSectionComponent = chunkStore.getComponent(sectionRef, FluidSection.getComponentType()); + if (fluidSectionComponent != null) { + fluidId = fluidSectionComponent.getFluidId(blockX, blockY, blockZ); + if (fluidId != 0) { + Fluid fluid = Fluid.getAssetMap().getAsset(fluidId); + if (fluid != null) { + double yTest = y - blockY; + if (yTest > (double)fluidSectionComponent.getFluidLevel(blockX, blockY, blockZ) / fluid.getMaxFluidLevel()) { + fluidId = 0; + } + } + } + } + } + + int blockId = blockSection.get(blockX, blockY, blockZ); + if (blockId == 0) { + return MathUtil.packLong(BlockMaterial.Empty.ordinal(), fluidId); + } else { + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + if (blockType != null && !blockType.isUnknown()) { + double relativeY = y - blockY; + String blockTypeKey = blockType.getId(); + BlockType blockTypeAsset = BlockType.getAssetMap().getAsset(blockTypeKey); + if (blockTypeAsset == null) { + return MathUtil.packLong(BlockMaterial.Empty.ordinal(), fluidId); + } else { + BlockMaterial blockTypeMaterial = blockType.getMaterial(); + int filler = blockSection.getFiller(blockX, blockY, blockZ); + int rotation = blockSection.getRotationIndex(blockX, blockY, blockZ); + if (filler != 0 && blockTypeAsset.getMaterial() == BlockMaterial.Solid) { + BlockBoundingBoxes boundingBoxes = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex()); + if (boundingBoxes == null) { + return MathUtil.packLong(BlockMaterial.Empty.ordinal(), fluidId); + } + + BlockBoundingBoxes.RotatedVariantBoxes rotatedBoxes = boundingBoxes.get(rotation); + int fillerX = FillerBlockUtil.unpackX(filler); + int fillerY = FillerBlockUtil.unpackY(filler); + int fillerZ = FillerBlockUtil.unpackZ(filler); + if (rotatedBoxes.containsPosition(x - blockX + fillerX, relativeY + fillerY, z - blockZ + fillerZ)) { + return MathUtil.packLong(BlockMaterial.Solid.ordinal(), fluidId); + } + } else if (blockTypeMaterial == BlockMaterial.Solid) { + BlockBoundingBoxes boundingBoxesx = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex()); + if (boundingBoxesx == null) { + return MathUtil.packLong(BlockMaterial.Empty.ordinal(), fluidId); + } + + BlockBoundingBoxes.RotatedVariantBoxes rotatedBoxes = boundingBoxesx.get(rotation); + if (rotatedBoxes.containsPosition(x - blockX, relativeY, z - blockZ)) { + return MathUtil.packLong(BlockMaterial.Solid.ordinal(), fluidId); + } + } + + return MathUtil.packLong(BlockMaterial.Empty.ordinal(), fluidId); + } + } else { + return MathUtil.packLong(BlockMaterial.Empty.ordinal(), fluidId); + } + } + } + } + } else { + return MathUtil.packLong(BlockMaterial.Empty.ordinal(), 0); + } + } + + public static int findFluidBlock( + @Nonnull ComponentAccessor chunkStore, + @Nonnull ChunkColumn chunkColumnComponent, + @Nonnull BlockChunk blockChunkComponent, + int x, + int y, + int z, + boolean allowBubble + ) { + if (y >= 0 && y < 320) { + if (getFluidIdAtPosition(chunkStore, chunkColumnComponent, x, y++, z) != 0) { + return y; + } else if (y != 320 && allowBubble) { + BlockSection blockSection = blockChunkComponent.getSectionAtBlockY(y); + int blockId = blockSection.get(x, y++, z); + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + BlockMaterial materialLowerBlock = blockType != null ? blockType.getMaterial() : BlockMaterial.Empty; + if (getFluidIdAtPosition(chunkStore, chunkColumnComponent, x, y++, z) != 0) { + return y; + } else if (materialLowerBlock == BlockMaterial.Solid && y != 320) { + return getFluidIdAtPosition(chunkStore, chunkColumnComponent, x, y++, z) != 0 ? y : -1; + } else { + return -1; + } + } else { + return -1; + } + } else { + return -1; + } + } + + public static int getWaterLevel( + @Nonnull ComponentAccessor chunkStore, + @Nonnull ChunkColumn chunkColumnComponent, + @Nonnull BlockChunk blockChunkComponent, + int x, + int z, + int startY + ) { + startY = findFluidBlock(chunkStore, chunkColumnComponent, blockChunkComponent, x, startY, z, true); + if (startY == -1) { + return -1; + } else { + while (startY + 1 < 320) { + BlockSection blockSection = blockChunkComponent.getSectionAtBlockY(startY + 1); + int blockId = blockSection.get(x, startY + 1, z); + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + if (blockType == null) { + break; + } + + int fluidId = getFluidIdAtPosition(chunkStore, chunkColumnComponent, x, startY + 1, z); + if (!isFluidOnlyBlock(blockType, fluidId)) { + break; + } + + startY++; + } + + return startY; + } + } + + public static int findFarthestEmptySpaceBelow( + @Nonnull ComponentAccessor chunkStore, + @Nonnull ChunkColumn chunkColumnComponent, + @Nonnull BlockChunk blockChunkComponent, + int x, + int y, + int z, + int yFail + ) { + if (y < 0) { + return yFail; + } else { + if (y >= 320) { + y = 319; + } + + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + int indexSection = ChunkUtil.indexSection(y); + + while (indexSection >= 0) { + Ref sectionRef = chunkColumnComponent.getSection(indexSection); + FluidSection fluidSectionComponent = chunkStore.getComponent(sectionRef, FluidSection.getComponentType()); + BlockSection chunkSection = blockChunkComponent.getSectionAtIndex(indexSection); + if (!chunkSection.isSolidAir() || fluidSectionComponent == null || !fluidSectionComponent.isEmpty()) { + int yBottom = 32 * indexSection--; + + while (y >= yBottom) { + int blockId = chunkSection.get(x, y--, z); + int fluidId = fluidSectionComponent != null ? fluidSectionComponent.getFluidId(x, y, z) : 0; + if (blockId != 0 || fluidId == 0) { + BlockType blockType = assetMap.getAsset(blockId); + if (blockType != null && !blockType.isUnknown()) { + int filler = chunkSection.getFiller(x, y, z); + if (filler == 0 && isEmptyOnlyBlock(blockType, fluidId)) { + continue; + } + + return y + 2; + } + + return y + 2; + } + } + } else { + y = 32 * indexSection - 1; + if (y <= 0) { + return 0; + } + + indexSection--; + } + } + + return 0; + } + } + + public static int findFarthestEmptySpaceAbove( + @Nonnull ComponentAccessor chunkStore, + @Nonnull ChunkColumn chunkColumnComponent, + @Nonnull BlockChunk blockChunkComponent, + int x, + int y, + int z, + int yFail + ) { + if (y >= 320) { + return Integer.MAX_VALUE; + } else if (y < 0) { + return yFail; + } else { + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + int sectionCount = blockChunkComponent.getSectionCount(); + int indexSection = ChunkUtil.indexSection(y); + + while (indexSection < sectionCount) { + Ref sectionRef = chunkColumnComponent.getSection(indexSection); + FluidSection fluidSectionComponent = chunkStore.getComponent(sectionRef, FluidSection.getComponentType()); + BlockSection chunkSection = blockChunkComponent.getSectionAtIndex(indexSection); + if (!chunkSection.isSolidAir() || fluidSectionComponent == null || !fluidSectionComponent.isEmpty()) { + int yTop = 32 * ++indexSection; + + while (y < yTop) { + int blockId = chunkSection.get(x, y++, z); + int fluidId = fluidSectionComponent != null ? fluidSectionComponent.getFluidId(x, y, z) : 0; + if (blockId != 0 || fluidId != 0) { + BlockType blockType = assetMap.getAsset(blockId); + if (blockType != null && !blockType.isUnknown()) { + int filler = chunkSection.getFiller(x, y, z); + if (filler == 0 && isEmptyOnlyBlock(blockType, fluidId)) { + continue; + } + + return y - 1; + } + + return y - 1; + } + } + } else { + y = 32 * ++indexSection; + if (y >= 320) { + return 319; + } + } + } + + return Integer.MAX_VALUE; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/collision/commands/HitboxCommand.java b/src/com/hypixel/hytale/server/core/modules/collision/commands/HitboxCommand.java new file mode 100644 index 0000000..abd16fb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/collision/commands/HitboxCommand.java @@ -0,0 +1,113 @@ +package com.hypixel.hytale.server.core.modules.collision.commands; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class HitboxCommand extends AbstractCommandCollection { + public HitboxCommand() { + super("hitbox", "server.commands.hitbox.desc"); + this.addSubCommand(new HitboxCommand.HitboxExtentsCommand()); + this.addUsageVariant(new HitboxCommand.HitboxGetCommand()); + } + + @Nonnull + private static Message formatBox(@Nonnull Box box) { + return Message.translation("server.commands.hitbox.box") + .param("minX", box.min.x) + .param("minY", box.min.y) + .param("minZ", box.min.z) + .param("maxX", box.max.x) + .param("maxY", box.max.y) + .param("maxZ", box.max.z); + } + + private static class HitboxExtentsCommand extends CommandBase { + @Nonnull + private final OptionalArg thresholdArg = this.withOptionalArg("threshold", "server.commands.hitbox.extents.threshold.desc", ArgTypes.DOUBLE); + + public HitboxExtentsCommand() { + super("extents", "server.commands.hitbox.extents.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + BlockTypeAssetMap blockTypeAssetMap = BlockType.getAssetMap(); + IndexedLookupTableAssetMap boundingBoxAssetMap = BlockBoundingBoxes.getAssetMap(); + int totalNumberOfFillerBlocks = 0; + double threshold = this.thresholdArg.provided(context) ? this.thresholdArg.get(context) : 0.5; + + for (BlockType blockType : blockTypeAssetMap.getAssetMap().values()) { + Box boundingBox = boundingBoxAssetMap.getAsset(blockType.getHitboxTypeIndex()).get(0).getBoundingBox(); + double width = boundingBox.width(); + double height = boundingBox.height(); + double depth = boundingBox.depth(); + int blockWidth = Math.max(MathUtil.floor(width), 1); + int blockHeight = Math.max(MathUtil.floor(height), 1); + int blockDepth = Math.max(MathUtil.floor(depth), 1); + if (width - blockWidth > threshold) { + blockWidth++; + } + + if (height - blockHeight > threshold) { + blockHeight++; + } + + if (depth - blockDepth > threshold) { + blockDepth++; + } + + int numberOfBlocks = blockWidth * blockHeight * blockDepth; + int numberOfFillerBlocks = numberOfBlocks - 1; + totalNumberOfFillerBlocks += numberOfFillerBlocks; + } + + context.sendMessage( + Message.translation("server.commands.hitbox.extentsThresholdNeeded").param("threshold", threshold).param("nb", totalNumberOfFillerBlocks) + ); + } + } + + private static class HitboxGetCommand extends CommandBase { + @Nonnull + private final RequiredArg hitboxArg = this.withRequiredArg("hitbox", "server.commands.hitbox.hitbox.desc", ArgTypes.STRING); + + public HitboxGetCommand() { + super("server.commands.hitbox.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + String name = this.hitboxArg.get(context); + BlockBoundingBoxes boundingBox = BlockBoundingBoxes.getAssetMap().getAsset(name); + if (boundingBox != null) { + BlockBoundingBoxes.RotatedVariantBoxes rotated = boundingBox.get(Rotation.None, Rotation.None, Rotation.None); + context.sendMessage(Message.translation("server.commands.hitbox.boundingBox").param("box", HitboxCommand.formatBox(rotated.getBoundingBox()))); + Box[] details = rotated.getDetailBoxes(); + if (details.length > 0) { + Message header = Message.translation("server.commands.hitbox.details.header"); + Set detailMessages = Arrays.stream(details).map(HitboxCommand::formatBox).collect(Collectors.toSet()); + context.sendMessage(MessageFormat.list(header, detailMessages)); + } + } else { + context.sendMessage(Message.translation("server.commands.hitbox.notFound").param("name", name)); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/debug/DebugPlugin.java b/src/com/hypixel/hytale/server/core/modules/debug/DebugPlugin.java new file mode 100644 index 0000000..49fa4d6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/debug/DebugPlugin.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.core.modules.debug; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.server.core.modules.debug.commands.DebugCommand; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DebugPlugin extends JavaPlugin { + @Nonnull + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(DebugPlugin.class).build(); + @Nullable + private static DebugPlugin instance; + + @Nullable + public static DebugPlugin get() { + return instance; + } + + public DebugPlugin(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + this.getCommandRegistry().registerCommand(new DebugCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/debug/DebugUtils.java b/src/com/hypixel/hytale/server/core/modules/debug/DebugUtils.java new file mode 100644 index 0000000..0f51a0b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/debug/DebugUtils.java @@ -0,0 +1,135 @@ +package com.hypixel.hytale.server.core.modules.debug; + +import com.hypixel.hytale.math.matrix.Matrix4d; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.DebugShape; +import com.hypixel.hytale.protocol.packets.player.ClearDebugShapes; +import com.hypixel.hytale.protocol.packets.player.DisplayDebug; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems; +import com.hypixel.hytale.server.core.modules.splitvelocity.SplitVelocity; +import com.hypixel.hytale.server.core.modules.splitvelocity.VelocityConfig; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.Random; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DebugUtils { + public static boolean DISPLAY_FORCES = false; + + public DebugUtils() { + } + + public static void add(@Nonnull World world, @Nonnull DebugShape shape, @Nonnull Matrix4d matrix, @Nonnull Vector3f color, float time, boolean fade) { + DisplayDebug packet = new DisplayDebug(shape, matrix.asFloatData(), new com.hypixel.hytale.protocol.Vector3f(color.x, color.y, color.z), time, fade, null); + + for (PlayerRef playerRef : world.getPlayerRefs()) { + playerRef.getPacketHandler().write(packet); + } + } + + public static void addFrustum( + @Nonnull World world, @Nonnull Matrix4d matrix, @Nonnull Matrix4d frustumProjection, @Nonnull Vector3f color, float time, boolean fade + ) { + DisplayDebug packet = new DisplayDebug( + DebugShape.Frustum, + matrix.asFloatData(), + new com.hypixel.hytale.protocol.Vector3f(color.x, color.y, color.z), + time, + fade, + frustumProjection.asFloatData() + ); + + for (PlayerRef playerRef : world.getPlayerRefs()) { + playerRef.getPacketHandler().write(packet); + } + } + + public static void clear(@Nonnull World world) { + ClearDebugShapes packet = new ClearDebugShapes(); + + for (PlayerRef playerRef : world.getPlayerRefs()) { + playerRef.getPacketHandler().write(packet); + } + } + + public static void addArrow(@Nonnull World world, @Nonnull Matrix4d baseMatrix, @Nonnull Vector3f color, double length, float time, boolean fade) { + double adjustedLength = length - 0.3; + if (adjustedLength > 0.0) { + Matrix4d matrix = new Matrix4d(baseMatrix); + matrix.translate(0.0, adjustedLength * 0.5, 0.0); + matrix.scale(0.1F, adjustedLength, 0.1F); + add(world, DebugShape.Cylinder, matrix, color, time, fade); + } + + Matrix4d matrix = new Matrix4d(baseMatrix); + matrix.translate(0.0, adjustedLength + 0.15, 0.0); + matrix.scale(0.3F, 0.3F, 0.3F); + add(world, DebugShape.Cone, matrix, color, time, fade); + } + + public static void addSphere(@Nonnull World world, @Nonnull Vector3d pos, @Nonnull Vector3f color, double scale, float time) { + Matrix4d matrix = makeMatrix(pos, scale); + add(world, DebugShape.Sphere, matrix, color, time, true); + } + + public static void addCone(@Nonnull World world, @Nonnull Vector3d pos, @Nonnull Vector3f color, double scale, float time) { + Matrix4d matrix = makeMatrix(pos, scale); + add(world, DebugShape.Cone, matrix, color, time, true); + } + + public static void addCube(@Nonnull World world, @Nonnull Vector3d pos, @Nonnull Vector3f color, double scale, float time) { + Matrix4d matrix = makeMatrix(pos, scale); + add(world, DebugShape.Cube, matrix, color, time, true); + } + + public static void addCylinder(@Nonnull World world, @Nonnull Vector3d pos, @Nonnull Vector3f color, double scale, float time) { + Matrix4d matrix = makeMatrix(pos, scale); + add(world, DebugShape.Cylinder, matrix, color, time, true); + } + + public static void addArrow(@Nonnull World world, @Nonnull Vector3d position, @Nonnull Vector3d direction, @Nonnull Vector3f color, float time, boolean fade) { + Vector3d directionClone = direction.clone(); + Matrix4d tmp = new Matrix4d(); + Matrix4d matrix = new Matrix4d(); + matrix.identity(); + matrix.translate(position); + double angleY = Math.atan2(directionClone.z, directionClone.x); + matrix.rotateAxis(angleY + (Math.PI / 2), 0.0, 1.0, 0.0, tmp); + double angleX = Math.atan2(Math.sqrt(directionClone.x * directionClone.x + directionClone.z * directionClone.z), directionClone.y); + matrix.rotateAxis(angleX, 1.0, 0.0, 0.0, tmp); + addArrow(world, matrix, color, directionClone.length(), time, fade); + } + + public static void addForce(@Nonnull World world, @Nonnull Vector3d position, @Nonnull Vector3d force, @Nullable VelocityConfig velocityConfig) { + if (DISPLAY_FORCES) { + Vector3d forceClone = force.clone(); + if (velocityConfig == null || SplitVelocity.SHOULD_MODIFY_VELOCITY) { + forceClone.x = forceClone.x / DamageSystems.HackKnockbackValues.PLAYER_KNOCKBACK_SCALE; + forceClone.z = forceClone.z / DamageSystems.HackKnockbackValues.PLAYER_KNOCKBACK_SCALE; + } + + Matrix4d tmp = new Matrix4d(); + Matrix4d matrix = new Matrix4d(); + matrix.identity(); + matrix.translate(position); + double angleY = Math.atan2(forceClone.z, forceClone.x); + matrix.rotateAxis(angleY + (Math.PI / 2), 0.0, 1.0, 0.0, tmp); + double angleX = Math.atan2(Math.sqrt(forceClone.x * forceClone.x + forceClone.z * forceClone.z), forceClone.y); + matrix.rotateAxis(angleX, 1.0, 0.0, 0.0, tmp); + Random random = new Random(); + Vector3f color = new Vector3f(random.nextFloat(), random.nextFloat(), random.nextFloat()); + addArrow(world, matrix, color, forceClone.length(), 10.0F, true); + } + } + + @Nonnull + private static Matrix4d makeMatrix(@Nonnull Vector3d pos, double scale) { + Matrix4d matrix = new Matrix4d(); + matrix.identity(); + matrix.translate(pos); + matrix.scale(scale, scale, scale); + return matrix; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugCommand.java b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugCommand.java new file mode 100644 index 0000000..fc33d5a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugCommand.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.core.modules.debug.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class DebugCommand extends AbstractCommandCollection { + public DebugCommand() { + super("debug", "server.commands.debug.desc"); + this.addSubCommand(new DebugShapeSubCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeArrowCommand.java b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeArrowCommand.java new file mode 100644 index 0000000..7b202a6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeArrowCommand.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.core.modules.debug.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.matrix.Matrix4d; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; + +public class DebugShapeArrowCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_DEBUG_SHAPE_ARROW_SUCCESS = Message.translation("server.commands.debug.shape.arrow.success"); + + public DebugShapeArrowCommand() { + super("arrow", "server.commands.debug.shape.arrow.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d pos = transformComponent.getPosition(); + ModelComponent modelComponent = store.getComponent(ref, ModelComponent.getComponentType()); + + assert modelComponent != null; + + Model model = modelComponent.getModel(); + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3f headRotation = headRotationComponent.getRotation(); + float lookYaw = headRotation.getYaw(); + float lookPitch = headRotation.getPitch(); + Matrix4d tmp = new Matrix4d(); + float eyeHeight = model != null ? model.getEyeHeight(ref, store) : 0.0F; + ThreadLocalRandom random = ThreadLocalRandom.current(); + Vector3f color = new Vector3f(random.nextFloat(), random.nextFloat(), random.nextFloat()); + Matrix4d matrix = new Matrix4d(); + matrix.identity(); + matrix.translate(pos.x, pos.y + eyeHeight, pos.z); + matrix.rotateAxis(-lookYaw, 0.0, 1.0, 0.0, tmp); + matrix.rotateAxis((Math.PI / 2) - lookPitch, 1.0, 0.0, 0.0, tmp); + DebugUtils.addArrow(world, matrix, color, 1.0, 30.0F, true); + context.sendMessage(MESSAGE_COMMANDS_DEBUG_SHAPE_ARROW_SUCCESS); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeClearCommand.java b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeClearCommand.java new file mode 100644 index 0000000..7a46c7b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeClearCommand.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.core.modules.debug.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class DebugShapeClearCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_DEBUG_SHAPE_CLEAR_SUCCESS = Message.translation("server.commands.debug.shape.clear.success"); + + public DebugShapeClearCommand() { + super("clear", "server.commands.debug.shape.clear.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + DebugUtils.clear(world); + context.sendMessage(MESSAGE_COMMANDS_DEBUG_SHAPE_CLEAR_SUCCESS); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeConeCommand.java b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeConeCommand.java new file mode 100644 index 0000000..4812f28 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeConeCommand.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.modules.debug.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; + +public class DebugShapeConeCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_DEBUG_SHAPE_CONE_SUCCESS = Message.translation("server.commands.debug.shape.cone.success"); + + public DebugShapeConeCommand() { + super("cone", "server.commands.debug.shape.cone.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + ThreadLocalRandom random = ThreadLocalRandom.current(); + Vector3f color = new Vector3f(random.nextFloat(), random.nextFloat(), random.nextFloat()); + DebugUtils.addCone(world, position, color, 2.0, 30.0F); + context.sendMessage(MESSAGE_COMMANDS_DEBUG_SHAPE_CONE_SUCCESS); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeCubeCommand.java b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeCubeCommand.java new file mode 100644 index 0000000..2cca191 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeCubeCommand.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.modules.debug.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; + +public class DebugShapeCubeCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_DEBUG_SHAPE_CUBE_SUCCESS = Message.translation("server.commands.debug.shape.cube.success"); + + public DebugShapeCubeCommand() { + super("cube", "server.commands.debug.shape.cube.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + ThreadLocalRandom random = ThreadLocalRandom.current(); + Vector3f color = new Vector3f(random.nextFloat(), random.nextFloat(), random.nextFloat()); + DebugUtils.addCube(world, position, color, 2.0, 30.0F); + context.sendMessage(MESSAGE_COMMANDS_DEBUG_SHAPE_CUBE_SUCCESS); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeCylinderCommand.java b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeCylinderCommand.java new file mode 100644 index 0000000..63c2b97 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeCylinderCommand.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.modules.debug.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; + +public class DebugShapeCylinderCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_DEBUG_SHAPE_CYLINDER_SUCCESS = Message.translation("server.commands.debug.shape.cylinder.success"); + + public DebugShapeCylinderCommand() { + super("cylinder", "server.commands.debug.shape.cylinder.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + ThreadLocalRandom random = ThreadLocalRandom.current(); + Vector3f color = new Vector3f(random.nextFloat(), random.nextFloat(), random.nextFloat()); + DebugUtils.addCylinder(world, position, color, 2.0, 30.0F); + context.sendMessage(MESSAGE_COMMANDS_DEBUG_SHAPE_CYLINDER_SUCCESS); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeShowForceCommand.java b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeShowForceCommand.java new file mode 100644 index 0000000..3e2e658 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeShowForceCommand.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.server.core.modules.debug.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import javax.annotation.Nonnull; + +public class DebugShapeShowForceCommand extends CommandBase { + public DebugShapeShowForceCommand() { + super("showforce", "server.commands.debug.shape.showforce.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + DebugUtils.DISPLAY_FORCES = !DebugUtils.DISPLAY_FORCES; + context.sendMessage(Message.raw("Display forces: " + (DebugUtils.DISPLAY_FORCES ? "enabled" : "disabled"))); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeSphereCommand.java b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeSphereCommand.java new file mode 100644 index 0000000..c3df097 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeSphereCommand.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.modules.debug.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; + +public class DebugShapeSphereCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_DEBUG_SHAPE_SPHERE_SUCCESS = Message.translation("server.commands.debug.shape.sphere.success"); + + public DebugShapeSphereCommand() { + super("sphere", "server.commands.debug.shape.sphere.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + ThreadLocalRandom random = ThreadLocalRandom.current(); + Vector3f color = new Vector3f(random.nextFloat(), random.nextFloat(), random.nextFloat()); + DebugUtils.addSphere(world, position, color, 2.0, 30.0F); + context.sendMessage(MESSAGE_COMMANDS_DEBUG_SHAPE_SPHERE_SUCCESS); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeSubCommand.java b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeSubCommand.java new file mode 100644 index 0000000..ef882d7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/debug/commands/DebugShapeSubCommand.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.server.core.modules.debug.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class DebugShapeSubCommand extends AbstractCommandCollection { + public DebugShapeSubCommand() { + super("shape", "server.commands.debug.shape.desc"); + this.addSubCommand(new DebugShapeSphereCommand()); + this.addSubCommand(new DebugShapeCubeCommand()); + this.addSubCommand(new DebugShapeCylinderCommand()); + this.addSubCommand(new DebugShapeConeCommand()); + this.addSubCommand(new DebugShapeArrowCommand()); + this.addSubCommand(new DebugShapeShowForceCommand()); + this.addSubCommand(new DebugShapeClearCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/AllLegacyEntityTypesQuery.java b/src/com/hypixel/hytale/server/core/modules/entity/AllLegacyEntityTypesQuery.java new file mode 100644 index 0000000..3797b79 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/AllLegacyEntityTypesQuery.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.core.modules.entity; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +@Deprecated +public class AllLegacyEntityTypesQuery implements Query { + public static final AllLegacyEntityTypesQuery INSTANCE = new AllLegacyEntityTypesQuery(); + + public AllLegacyEntityTypesQuery() { + } + + @Override + public boolean test(@Nonnull Archetype archetype) { + return EntityUtils.hasEntity(archetype); + } + + @Override + public boolean requiresComponentType(ComponentType componentType) { + return false; + } + + @Override + public void validateRegistry(ComponentRegistry registry) { + } + + @Override + public void validate() { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/AllLegacyLivingEntityTypesQuery.java b/src/com/hypixel/hytale/server/core/modules/entity/AllLegacyLivingEntityTypesQuery.java new file mode 100644 index 0000000..5613608 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/AllLegacyLivingEntityTypesQuery.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.server.core.modules.entity; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +@Deprecated +public class AllLegacyLivingEntityTypesQuery implements Query { + @Nonnull + public static final AllLegacyLivingEntityTypesQuery INSTANCE = new AllLegacyLivingEntityTypesQuery(); + + public AllLegacyLivingEntityTypesQuery() { + } + + @Override + public boolean test(@Nonnull Archetype archetype) { + return EntityUtils.hasLivingEntity(archetype); + } + + @Override + public boolean requiresComponentType(ComponentType componentType) { + return false; + } + + @Override + public void validateRegistry(ComponentRegistry registry) { + } + + @Override + public void validate() { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/BlockEntitySystems.java b/src/com/hypixel/hytale/server/core/modules/entity/BlockEntitySystems.java new file mode 100644 index 0000000..d714837 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/BlockEntitySystems.java @@ -0,0 +1,203 @@ +package com.hypixel.hytale.server.core.modules.entity; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.DisableProcessingAssert; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.entities.BlockEntity; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.EntityScaleComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.modules.physics.SimplePhysicsProvider; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockEntitySystems { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + + public BlockEntitySystems() { + } + + public static class BlockEntitySetupSystem extends HolderSystem { + private final ComponentType blockEntityComponentType; + + public BlockEntitySetupSystem(ComponentType blockEntityComponentType) { + this.blockEntityComponentType = blockEntityComponentType; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + if (!holder.getArchetype().contains(NetworkId.getComponentType())) { + holder.addComponent(NetworkId.getComponentType(), new NetworkId(store.getExternalData().takeNextNetworkId())); + } + + BlockEntity blockEntityComponent = holder.getComponent(this.blockEntityComponentType); + BoundingBox boundingBoxComponent = blockEntityComponent.createBoundingBoxComponent(); + if (boundingBoxComponent == null) { + BlockEntitySystems.LOGGER + .at(Level.SEVERE) + .log("Bounding box could not be initialized properly, defaulting to 1x1x1 dimensions for Block Entity bounding box"); + boundingBoxComponent = new BoundingBox(Box.horizontallyCentered(1.0, 1.0, 1.0)); + } + + holder.putComponent(BoundingBox.getComponentType(), boundingBoxComponent); + SimplePhysicsProvider simplePhysicsProvider = blockEntityComponent.initPhysics(boundingBoxComponent); + simplePhysicsProvider.setMoveOutOfSolid(false); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Override + public Query getQuery() { + return this.blockEntityComponentType; + } + } + + public static class BlockEntityTrackerSystem extends EntityTickingSystem { + private final ComponentType visibleComponentType; + private final ComponentType blockEntityComponentType; + @Nonnull + private final Query query; + + public BlockEntityTrackerSystem( + ComponentType visibleComponentType, ComponentType blockEntityComponentType + ) { + this.visibleComponentType = visibleComponentType; + this.blockEntityComponentType = blockEntityComponentType; + this.query = Query.and(visibleComponentType, blockEntityComponentType); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visible = archetypeChunk.getComponent(index, this.visibleComponentType); + BlockEntity blockEntity = archetypeChunk.getComponent(index, this.blockEntityComponentType); + + assert blockEntity != null; + + float entityScale = 2.0F; + boolean scaleOutdated = false; + EntityScaleComponent entityScaleComponent = archetypeChunk.getComponent(index, EntityScaleComponent.getComponentType()); + if (entityScaleComponent != null) { + entityScale = entityScaleComponent.getScale(); + scaleOutdated = entityScaleComponent.consumeNetworkOutdated(); + } + + boolean blockIdOutdated = blockEntity.consumeBlockIdNetworkOutdated(); + if (blockIdOutdated || scaleOutdated) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), blockEntity, visible.visibleTo, entityScale); + } else if (!visible.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), blockEntity, visible.newlyVisibleTo, entityScale); + } + } + + private static void queueUpdatesFor( + Ref ref, @Nonnull BlockEntity entity, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo, float entityScale + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Block; + String key = entity.getBlockTypeKey(); + int index = BlockType.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } else { + update.blockId = index; + update.entityScale = entityScale; + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, update); + } + } + } + } + + public static class Ticking extends EntityTickingSystem implements DisableProcessingAssert { + private final ComponentType transformComponentType = TransformComponent.getComponentType(); + private final ComponentType blockEntityComponentType; + private final Archetype archetype; + + public Ticking(@Nonnull ComponentType blockEntityComponentType) { + this.blockEntityComponentType = blockEntityComponentType; + this.archetype = Archetype.of(this.transformComponentType, blockEntityComponentType, Velocity.getComponentType()); + } + + @Override + public Query getQuery() { + return this.archetype; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + TransformComponent transformComponent = archetypeChunk.getComponent(index, this.transformComponentType); + + assert transformComponent != null; + + BlockEntity blockEntityComponent = archetypeChunk.getComponent(index, this.blockEntityComponentType); + + assert blockEntityComponent != null; + + Velocity velocityComponent = archetypeChunk.getComponent(index, Velocity.getComponentType()); + + assert velocityComponent != null; + + try { + blockEntityComponent.getSimplePhysicsProvider() + .tick(dt, velocityComponent, store.getExternalData().getWorld(), transformComponent, archetypeChunk.getReferenceTo(index), commandBuffer); + } catch (Throwable var10) { + BlockEntitySystems.LOGGER.at(Level.SEVERE).withCause(var10).log("Exception while ticking entity. Removing entity %s", blockEntityComponent); + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/BlockMigrationExtraInfo.java b/src/com/hypixel/hytale/server/core/modules/entity/BlockMigrationExtraInfo.java new file mode 100644 index 0000000..c0117d4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/BlockMigrationExtraInfo.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.server.core.modules.entity; + +import com.hypixel.hytale.codec.ExtraInfo; +import java.util.function.Function; + +public class BlockMigrationExtraInfo extends ExtraInfo { + private final Function blockMigration; + + public BlockMigrationExtraInfo(int version, Function blockMigration) { + super(version); + this.blockMigration = blockMigration; + } + + public Function getBlockMigration() { + return this.blockMigration; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/DespawnComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/DespawnComponent.java new file mode 100644 index 0000000..a10c62f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/DespawnComponent.java @@ -0,0 +1,92 @@ +package com.hypixel.hytale.server.core.modules.entity; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Duration; +import java.time.Instant; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DespawnComponent implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(DespawnComponent.class, DespawnComponent::new) + .append( + new KeyedCodec<>("Despawn", Codec.INSTANT), + (despawnComponent, instant) -> despawnComponent.timeToDespawnAt = instant, + despawnComponent -> despawnComponent.timeToDespawnAt + ) + .add() + .build(); + @Nullable + private Instant timeToDespawnAt; + + public static ComponentType getComponentType() { + return EntityModule.get().getDespawnComponentType(); + } + + public DespawnComponent() { + this(null); + } + + public DespawnComponent(@Nullable Instant timeToDespawnAt) { + this.timeToDespawnAt = timeToDespawnAt; + } + + public void setDespawn(Instant timeToDespawnAt) { + this.timeToDespawnAt = timeToDespawnAt; + } + + public void setDespawnTo(@Nonnull Instant from, float additionalSeconds) { + this.timeToDespawnAt = from.plusNanos((long)(additionalSeconds * 1.0E9F)); + } + + @Nullable + public Instant getDespawn() { + return this.timeToDespawnAt; + } + + @Nonnull + public static DespawnComponent despawnInSeconds(@Nonnull TimeResource time, int seconds) { + return new DespawnComponent(time.getNow().plus(Duration.ofSeconds(seconds))); + } + + @Nonnull + public static DespawnComponent despawnInSeconds(@Nonnull TimeResource time, float seconds) { + return new DespawnComponent(time.getNow().plusNanos((long)(seconds * 1.0E9F))); + } + + @Nonnull + public static DespawnComponent despawnInMilliseconds(@Nonnull TimeResource time, long milliseconds) { + return new DespawnComponent(time.getNow().plus(Duration.ofMillis(milliseconds))); + } + + public static void trySetDespawn( + @Nonnull CommandBuffer commandBuffer, + @Nonnull TimeResource timeResource, + @Nonnull Ref ref, + @Nullable DespawnComponent despawnComponent, + @Nullable Float newLifetime + ) { + if (despawnComponent != null) { + if (newLifetime != null) { + despawnComponent.setDespawnTo(timeResource.getNow(), newLifetime); + } else { + commandBuffer.removeComponent(ref, getComponentType()); + } + } else { + commandBuffer.putComponent(ref, getComponentType(), despawnInSeconds(timeResource, newLifetime)); + } + } + + @Nonnull + @Override + public Component clone() { + return new DespawnComponent(this.timeToDespawnAt); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/DespawnSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/DespawnSystem.java new file mode 100644 index 0000000..f6d6fd9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/DespawnSystem.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.core.modules.entity; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.server.core.modules.entity.component.Interactable; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class DespawnSystem extends EntityTickingSystem { + private final ComponentType despawnComponentType; + @Nonnull + private final Query query; + + public DespawnSystem(ComponentType despawnComponentType) { + this.despawnComponentType = despawnComponentType; + this.query = Query.and(despawnComponentType, Query.not(Interactable.getComponentType())); + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + DespawnComponent despawn = archetypeChunk.getComponent(index, this.despawnComponentType); + Instant despawnInstant = despawn.getDespawn(); + TimeResource timeResource = store.getResource(TimeResource.getResourceType()); + if (timeResource.getNow().isAfter(despawnInstant)) { + Ref entityRef = archetypeChunk.getReferenceTo(index); + commandBuffer.removeEntity(entityRef, RemoveReason.REMOVE); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/EntityModule.java b/src/com/hypixel/hytale/server/core/modules/entity/EntityModule.java new file mode 100644 index 0000000..d184616 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/EntityModule.java @@ -0,0 +1,1361 @@ +package com.hypixel.hytale.server.core.modules.entity; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.codec.DirectDecodeCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.SystemType; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.RootDependency; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.KDTree; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.ISystem; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.packets.player.UpdateMovementSettings; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.Frozen; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.damage.DamageDataComponent; +import com.hypixel.hytale.server.core.entity.damage.DamageDataSetupSystem; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.entity.entities.BlockEntity; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.ProjectileComponent; +import com.hypixel.hytale.server.core.entity.entities.player.CameraManager; +import com.hypixel.hytale.server.core.entity.entities.player.HotbarManager; +import com.hypixel.hytale.server.core.entity.entities.player.data.UniqueItemUsagesComponent; +import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementConfig; +import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementManager; +import com.hypixel.hytale.server.core.entity.group.EntityGroup; +import com.hypixel.hytale.server.core.entity.knockback.KnockbackComponent; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesSystems; +import com.hypixel.hytale.server.core.entity.nameplate.Nameplate; +import com.hypixel.hytale.server.core.entity.nameplate.NameplateSystems; +import com.hypixel.hytale.server.core.entity.reference.PersistentRefCount; +import com.hypixel.hytale.server.core.event.events.entity.LivingEntityInventoryChangeEvent; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.modules.collision.CollisionModule; +import com.hypixel.hytale.server.core.modules.collision.TangiableEntitySpatialSystem; +import com.hypixel.hytale.server.core.modules.entity.component.ActiveAnimationComponent; +import com.hypixel.hytale.server.core.modules.entity.component.AudioComponent; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.CollisionResultComponent; +import com.hypixel.hytale.server.core.modules.entity.component.DisplayNameComponent; +import com.hypixel.hytale.server.core.modules.entity.component.DynamicLight; +import com.hypixel.hytale.server.core.modules.entity.component.EntityScaleComponent; +import com.hypixel.hytale.server.core.modules.entity.component.FromPrefab; +import com.hypixel.hytale.server.core.modules.entity.component.FromWorldGen; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.HiddenFromAdventurePlayers; +import com.hypixel.hytale.server.core.modules.entity.component.Intangible; +import com.hypixel.hytale.server.core.modules.entity.component.Interactable; +import com.hypixel.hytale.server.core.modules.entity.component.Invulnerable; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.MovementAudioComponent; +import com.hypixel.hytale.server.core.modules.entity.component.NewSpawnComponent; +import com.hypixel.hytale.server.core.modules.entity.component.PersistentDynamicLight; +import com.hypixel.hytale.server.core.modules.entity.component.PersistentModel; +import com.hypixel.hytale.server.core.modules.entity.component.PositionDataComponent; +import com.hypixel.hytale.server.core.modules.entity.component.PropComponent; +import com.hypixel.hytale.server.core.modules.entity.component.RespondToHit; +import com.hypixel.hytale.server.core.modules.entity.component.RotateObjectComponent; +import com.hypixel.hytale.server.core.modules.entity.component.SnapshotBuffer; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.component.WorldGenId; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entity.dynamiclight.DynamicLightSystems; +import com.hypixel.hytale.server.core.modules.entity.hitboxcollision.HitboxCollision; +import com.hypixel.hytale.server.core.modules.entity.hitboxcollision.HitboxCollisionConfig; +import com.hypixel.hytale.server.core.modules.entity.hitboxcollision.HitboxCollisionConfigPacketGenerator; +import com.hypixel.hytale.server.core.modules.entity.hitboxcollision.HitboxCollisionSystems; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.modules.entity.item.ItemMergeSystem; +import com.hypixel.hytale.server.core.modules.entity.item.ItemPhysicsComponent; +import com.hypixel.hytale.server.core.modules.entity.item.ItemPhysicsSystem; +import com.hypixel.hytale.server.core.modules.entity.item.ItemPrePhysicsSystem; +import com.hypixel.hytale.server.core.modules.entity.item.ItemSystems; +import com.hypixel.hytale.server.core.modules.entity.item.PickupItemComponent; +import com.hypixel.hytale.server.core.modules.entity.item.PickupItemSystem; +import com.hypixel.hytale.server.core.modules.entity.item.PreventItemMerging; +import com.hypixel.hytale.server.core.modules.entity.item.PreventPickup; +import com.hypixel.hytale.server.core.modules.entity.livingentity.LivingEntityEffectClearChangesSystem; +import com.hypixel.hytale.server.core.modules.entity.livingentity.LivingEntityEffectSystem; +import com.hypixel.hytale.server.core.modules.entity.player.ApplyRandomSkinPersistedComponent; +import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; +import com.hypixel.hytale.server.core.modules.entity.player.KnockbackPredictionSystems; +import com.hypixel.hytale.server.core.modules.entity.player.KnockbackSimulation; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerCameraAddSystem; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerChunkTrackerSystems; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerHudManagerSystems; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerInput; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerItemEntityPickupSystem; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerMovementManagerSystems; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerProcessMovementSystem; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSavingSystems; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSendInventorySystem; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSkinComponent; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSystems; +import com.hypixel.hytale.server.core.modules.entity.repulsion.Repulsion; +import com.hypixel.hytale.server.core.modules.entity.repulsion.RepulsionConfig; +import com.hypixel.hytale.server.core.modules.entity.repulsion.RepulsionConfigPacketGenerator; +import com.hypixel.hytale.server.core.modules.entity.repulsion.RepulsionSystems; +import com.hypixel.hytale.server.core.modules.entity.system.AudioSystems; +import com.hypixel.hytale.server.core.modules.entity.system.EntityInteractableSystems; +import com.hypixel.hytale.server.core.modules.entity.system.EntitySpatialSystem; +import com.hypixel.hytale.server.core.modules.entity.system.EntitySystems; +import com.hypixel.hytale.server.core.modules.entity.system.HideEntitySystems; +import com.hypixel.hytale.server.core.modules.entity.system.IntangibleSystems; +import com.hypixel.hytale.server.core.modules.entity.system.InvulnerableSystems; +import com.hypixel.hytale.server.core.modules.entity.system.ItemSpatialSystem; +import com.hypixel.hytale.server.core.modules.entity.system.ModelSystems; +import com.hypixel.hytale.server.core.modules.entity.system.NetworkSendableSpatialSystem; +import com.hypixel.hytale.server.core.modules.entity.system.PlayerCollisionResultAddSystem; +import com.hypixel.hytale.server.core.modules.entity.system.PlayerSpatialSystem; +import com.hypixel.hytale.server.core.modules.entity.system.RespondToHitSystems; +import com.hypixel.hytale.server.core.modules.entity.system.RotateObjectSystem; +import com.hypixel.hytale.server.core.modules.entity.system.SnapshotSystems; +import com.hypixel.hytale.server.core.modules.entity.system.TransformSystems; +import com.hypixel.hytale.server.core.modules.entity.system.UpdateEntitySeedSystem; +import com.hypixel.hytale.server.core.modules.entity.system.UpdateLocationSystems; +import com.hypixel.hytale.server.core.modules.entity.teleport.PendingTeleport; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.modules.entity.teleport.TeleportSystems; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.modules.entity.tracker.LegacyEntityTrackerSystems; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.modules.physics.component.PhysicsValues; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.modules.physics.systems.GenericVelocityInstructionSystem; +import com.hypixel.hytale.server.core.modules.physics.systems.IVelocityModifyingSystem; +import com.hypixel.hytale.server.core.modules.physics.systems.PhysicsValuesAddSystem; +import com.hypixel.hytale.server.core.modules.physics.systems.VelocitySystems; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.plugin.PluginState; +import com.hypixel.hytale.server.core.prefab.PrefabCopyableComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.system.PlayerVelocityInstructionSystem; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(EntityModule.class).depends(Universe.class).depends(CollisionModule.class).build(); + public static final String[] LEGACY_ENTITY_CLASS_NAMES = new String[]{ + "SpawnSuppressor", "Block", "LegacySpawnBeacon", "PatrolPathMarker", "Player", "SpawnBeacon", "SpawnMarker" + }; + public static final String MOUNT_MOVEMENT_SETTINGS_ASSET_ID = "Mount"; + private static EntityModule instance; + private final Map> idMap = new ConcurrentHashMap<>(); + private final Map, String> classIdMap = new ConcurrentHashMap<>(); + private final Map, Function> classMap = new ConcurrentHashMap<>(); + private final Map, DirectDecodeCodec> codecMap = new ConcurrentHashMap<>(); + @Deprecated + private final Map, ComponentType> classToComponentType = new ConcurrentHashMap<>(); + private ComponentType uuidComponentType; + private ComponentType transformComponentType; + private ComponentType headRotationComponentType; + private ComponentType networkIdComponentType; + private ComponentType entityScaleComponentType; + private ComponentType playerComponentType; + private ComponentType movementManagerComponentType; + private ComponentType cameraManagerComponentType; + private ComponentType frozenComponentType; + private ComponentType collisionResultComponentType; + private ComponentType chunkTrackerComponentType; + private ComponentType projectileComponentType; + private ComponentType blockEntityComponentType; + private ComponentType effectControllerComponentType; + private ComponentType rotateObjectComponentType; + private ComponentType modelComponentType; + private ComponentType persistentModelComponentType; + private ComponentType propComponentType; + private ComponentType boundingBoxComponentType; + private ComponentType playerSkinComponentType; + private ResourceType, EntityStore>> playerSpatialResourceType; + private ResourceType, EntityStore>> entitySpatialResourceType; + private ResourceType, EntityStore>> itemSpatialResourceType; + private ResourceType, EntityStore>> networkSendableSpatialResourceType; + private ComponentType displayNameComponentType; + private ComponentType entityGroupComponentType; + private ComponentType movementStatesComponentType; + private ComponentType damageDataComponentType; + private ComponentType knockbackComponentType; + private ComponentType despawnComponentComponentType; + private ComponentType entityViewerComponentType; + private ComponentType visibleComponentType; + private ResourceType snapshotWorldInfoResourceType; + private ComponentType snapshotBufferComponentType; + private ComponentType persistentRefCountComponentType; + private ComponentType velocityComponentType; + private ComponentType physicsValuesComponentType; + private ComponentType fromPrefabComponentType; + private ComponentType fromWorldGenComponentType; + private ComponentType worldGenIdComponentType; + private ComponentType interactableComponentType; + private ComponentType intangibleComponentType; + private ComponentType preventPickupComponentType; + private ComponentType invulnerableComponentType; + private ComponentType respondToHitComponentType; + private ResourceType interactableQueueResourceType; + private ResourceType intangibleQueueResourceType; + private ResourceType invulnerableQueueResourceType; + private ResourceType respondToHitQueueResourceType; + private ComponentType hiddenFromAdventurePlayerComponentType; + private ComponentType nameplateComponentType; + private ComponentType hitboxCollisionComponentType; + private ComponentType repulsionComponentType; + private ComponentType teleportComponentType; + private ComponentType pendingTeleportComponentType; + private ComponentType applyRandomSkinPersistedComponent; + private SystemGroup preClearMarkersGroup; + private ComponentType playerInputComponentType; + private ComponentType knockbackSimulationComponentType; + private ComponentType playerSettingsComponentType; + private SystemType migrationSystemType; + private SystemType> velocityModifyingSystemType; + private ComponentType audioComponentType; + private ComponentType movementAudioComponentType; + private ComponentType positionDataComponentType; + private ComponentType activeAnimationComponentType; + private ComponentType newSpawnComponentType; + private ComponentType itemComponentType; + private ComponentType pickupItemComponentType; + private ComponentType preventItemMergingType; + private ComponentType itemPhysicsComponentType; + private ComponentType dynamicLightComponentType; + private ComponentType persistentDynamicLightComponentType; + private ComponentType prefabCopyableComponentType; + private ComponentType uniqueItemUsagesComponentType; + + public static EntityModule get() { + return instance; + } + + public EntityModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + this.getEventRegistry().registerGlobal(LivingEntityInventoryChangeEvent.class, event -> { + Ref entityRef = event.getEntity().getReference(); + if (entityRef != null && entityRef.isValid()) { + Store store = entityRef.getStore(); + World world = store.getExternalData().getWorld(); + world.execute(() -> { + if (entityRef.isValid()) { + Player playerComponent = store.getComponent(entityRef, Player.getComponentType()); + if (playerComponent != null) { + HotbarManager hotbarManager = playerComponent.getHotbarManager(); + if (!hotbarManager.getIsCurrentlyLoadingHotbar()) { + if (playerComponent.getGameMode().equals(GameMode.Creative)) { + if (event.getItemContainer().equals(playerComponent.getInventory().getHotbar())) { + hotbarManager.saveHotbar(entityRef, (short)hotbarManager.getCurrentHotbarIndex(), store); + } + } + } + } + } + }); + } + }); + ComponentRegistryProxy entityStoreRegistry = this.getEntityStoreRegistry(); + this.physicsValuesComponentType = entityStoreRegistry.registerComponent(PhysicsValues.class, PhysicsValues::new); + this.velocityComponentType = entityStoreRegistry.registerComponent(Velocity.class, "Velocity", Velocity.CODEC); + this.migrationSystemType = entityStoreRegistry.registerSystemType(EntityModule.MigrationSystem.class); + this.velocityModifyingSystemType = entityStoreRegistry.registerSystemType(IVelocityModifyingSystem.class); + this.boundingBoxComponentType = entityStoreRegistry.registerComponent(BoundingBox.class, BoundingBox::new); + this.entityScaleComponentType = entityStoreRegistry.registerComponent(EntityScaleComponent.class, "EntityScale", EntityScaleComponent.CODEC); + this.transformComponentType = entityStoreRegistry.registerComponent(TransformComponent.class, "Transform", TransformComponent.CODEC); + this.headRotationComponentType = entityStoreRegistry.registerComponent(HeadRotation.class, "HeadRotation", HeadRotation.CODEC); + this.uuidComponentType = entityStoreRegistry.registerComponent(UUIDComponent.class, "UUID", UUIDComponent.CODEC); + this.collisionResultComponentType = entityStoreRegistry.registerComponent(CollisionResultComponent.class, CollisionResultComponent::new); + this.networkIdComponentType = entityStoreRegistry.registerComponent(NetworkId.class, () -> { + throw new UnsupportedOperationException("Not implemented"); + }); + this.rotateObjectComponentType = entityStoreRegistry.registerComponent(RotateObjectComponent.class, "RotateObject", RotateObjectComponent.CODEC); + this.effectControllerComponentType = entityStoreRegistry.registerComponent( + EffectControllerComponent.class, "EffectController", EffectControllerComponent.CODEC + ); + this.interactableComponentType = entityStoreRegistry.registerComponent(Interactable.class, "Interactable", Interactable.CODEC); + this.intangibleComponentType = entityStoreRegistry.registerComponent(Intangible.class, "Intangible", Intangible.CODEC); + this.preventPickupComponentType = entityStoreRegistry.registerComponent(PreventPickup.class, "PreventPickup", PreventPickup.CODEC); + this.invulnerableComponentType = entityStoreRegistry.registerComponent(Invulnerable.class, "Invulnerable", Invulnerable.CODEC); + this.respondToHitComponentType = entityStoreRegistry.registerComponent(RespondToHit.class, "RespondToHit", RespondToHit.CODEC); + this.applyRandomSkinPersistedComponent = entityStoreRegistry.registerComponent( + ApplyRandomSkinPersistedComponent.class, "ApplyRandomSkinPersisted", ApplyRandomSkinPersistedComponent.CODEC + ); + this.audioComponentType = entityStoreRegistry.registerComponent(AudioComponent.class, AudioComponent::new); + this.movementAudioComponentType = entityStoreRegistry.registerComponent(MovementAudioComponent.class, MovementAudioComponent::new); + this.positionDataComponentType = entityStoreRegistry.registerComponent(PositionDataComponent.class, PositionDataComponent::new); + this.activeAnimationComponentType = entityStoreRegistry.registerComponent(ActiveAnimationComponent.class, ActiveAnimationComponent::new); + this.newSpawnComponentType = entityStoreRegistry.registerComponent(NewSpawnComponent.class, () -> { + throw new UnsupportedOperationException("Not implemented"); + }); + entityStoreRegistry.registerSystem(new EntityStore.NetworkIdSystem()); + entityStoreRegistry.registerSystem(new EntityStore.UUIDSystem()); + entityStoreRegistry.registerSystem(new VelocitySystems.AddSystem(this.velocityComponentType)); + entityStoreRegistry.registerSystem(new TangiableEntitySpatialSystem(CollisionModule.get().getTangiableEntitySpatialComponent())); + SystemGroup _trackerGroup = EntityTrackerSystems.FIND_VISIBLE_ENTITIES_GROUP; + this.visibleComponentType = entityStoreRegistry.registerComponent(EntityTrackerSystems.Visible.class, EntityTrackerSystems.Visible::new); + entityStoreRegistry.registerSystem(new TransformSystems.EntityTrackerUpdate()); + this.blockEntityComponentType = entityStoreRegistry.registerComponent(BlockEntity.class, "BlockEntity", BlockEntity.CODEC); + this.projectileComponentType = entityStoreRegistry.registerComponent(ProjectileComponent.class, "ProjectileComponent", ProjectileComponent.CODEC); + entityStoreRegistry.registerSystem(new BlockEntitySystems.Ticking(this.blockEntityComponentType)); + entityStoreRegistry.registerSystem(new BlockEntitySystems.BlockEntitySetupSystem(this.blockEntityComponentType)); + entityStoreRegistry.registerSystem(new LegacyProjectileSystems.OnAddRefSystem()); + entityStoreRegistry.registerSystem(new LegacyProjectileSystems.OnAddHolderSystem()); + entityStoreRegistry.registerSystem( + new LegacyProjectileSystems.TickingSystem( + this.projectileComponentType, this.transformComponentType, this.velocityComponentType, this.boundingBoxComponentType + ) + ); + entityStoreRegistry.registerSystem(new RotateObjectSystem(this.transformComponentType, this.rotateObjectComponentType)); + this.snapshotWorldInfoResourceType = entityStoreRegistry.registerResource(SnapshotSystems.SnapshotWorldInfo.class, SnapshotSystems.SnapshotWorldInfo::new); + this.snapshotBufferComponentType = entityStoreRegistry.registerComponent(SnapshotBuffer.class, SnapshotBuffer::new); + entityStoreRegistry.registerSystem(new SnapshotSystems.Add()); + entityStoreRegistry.registerSystem(new SnapshotSystems.Resize()); + entityStoreRegistry.registerSystem(new SnapshotSystems.Capture()); + entityStoreRegistry.registerSystem(new UpdateEntitySeedSystem()); + entityStoreRegistry.registerSystem(new EntityModule.LegacyTransformSystem()); + entityStoreRegistry.registerSystem(new EntityModule.LegacyUUIDSystem()); + entityStoreRegistry.registerSystem(new EntityModule.LegacyUUIDUpdateSystem()); + entityStoreRegistry.registerSystem(new EntitySystems.UnloadEntityFromChunk()); + this.teleportComponentType = entityStoreRegistry.registerComponent(Teleport.class, () -> { + throw new UnsupportedOperationException("Teleport must be created directly"); + }); + this.pendingTeleportComponentType = entityStoreRegistry.registerComponent(PendingTeleport.class, PendingTeleport::new); + this.playerComponentType = entityStoreRegistry.registerComponent(Player.class, "Player", Player.CODEC); + this.frozenComponentType = entityStoreRegistry.registerComponent(Frozen.class, "Frozen", Frozen.CODEC); + entityStoreRegistry.registerSystem(new PlayerCollisionResultAddSystem(this.playerComponentType, this.collisionResultComponentType)); + this.playerSettingsComponentType = entityStoreRegistry.registerComponent(PlayerSettings.class, PlayerSettings::defaults); + this.movementStatesComponentType = entityStoreRegistry.registerComponent(MovementStatesComponent.class, MovementStatesComponent::new); + entityStoreRegistry.registerSystem(new MovementStatesSystems.AddSystem(this.movementStatesComponentType)); + entityStoreRegistry.registerSystem(new MovementStatesSystems.PlayerInitSystem(this.playerComponentType, this.movementStatesComponentType)); + entityStoreRegistry.registerSystem(new TeleportSystems.MoveSystem()); + entityStoreRegistry.registerSystem(new TeleportSystems.PlayerMoveSystem()); + entityStoreRegistry.registerSystem(new TeleportSystems.PlayerMoveCompleteSystem()); + this.modelComponentType = entityStoreRegistry.registerComponent(ModelComponent.class, () -> { + throw new UnsupportedOperationException(); + }); + this.persistentModelComponentType = entityStoreRegistry.registerComponent(PersistentModel.class, "Model", PersistentModel.CODEC); + this.propComponentType = entityStoreRegistry.registerComponent(PropComponent.class, "Prop", PropComponent.CODEC); + entityStoreRegistry.registerSystem(new EntityModule.LegacyEntityHolderSystem<>(this.playerComponentType), true); + entityStoreRegistry.registerSystem(new EntityModule.LegacyEntityRefSystem<>(this.playerComponentType), true); + this.playerInputComponentType = entityStoreRegistry.registerComponent(PlayerInput.class, PlayerInput::new); + this.knockbackSimulationComponentType = entityStoreRegistry.registerComponent(KnockbackSimulation.class, () -> { + throw new UnsupportedOperationException(); + }); + this.movementManagerComponentType = entityStoreRegistry.registerComponent(MovementManager.class, MovementManager::new); + entityStoreRegistry.registerSystem(new PlayerSystems.PlayerSpawnedSystem()); + entityStoreRegistry.registerSystem(new PlayerSystems.PlayerAddedSystem(this.movementManagerComponentType)); + entityStoreRegistry.registerSystem(new PlayerSystems.EnsurePlayerInput()); + entityStoreRegistry.registerSystem(new PlayerSystems.EnsureEffectControllerSystem()); + entityStoreRegistry.registerSystem(new PlayerSystems.PlayerRemovedSystem()); + entityStoreRegistry.registerSystem(new PlayerSystems.ProcessPlayerInput()); + entityStoreRegistry.registerSystem(new PlayerSystems.UpdatePlayerRef()); + entityStoreRegistry.registerSystem(new PlayerSystems.BlockPausedMovementSystem()); + entityStoreRegistry.registerSystem(new PlayerSystems.KillFeedKillerEventSystem()); + entityStoreRegistry.registerSystem(new PlayerSystems.KillFeedDecedentEventSystem()); + entityStoreRegistry.registerSystem(new KnockbackPredictionSystems.InitKnockback()); + entityStoreRegistry.registerSystem(new KnockbackPredictionSystems.CaptureKnockbackInput()); + entityStoreRegistry.registerSystem(new KnockbackPredictionSystems.SimulateKnockback()); + entityStoreRegistry.registerSystem(new KnockbackPredictionSystems.ClearOnTeleport()); + entityStoreRegistry.registerSystem(new KnockbackPredictionSystems.ClearOnRemove()); + this.preClearMarkersGroup = entityStoreRegistry.registerSystemGroup(); + this.playerSkinComponentType = entityStoreRegistry.registerComponent(PlayerSkinComponent.class, () -> { + throw new UnsupportedOperationException("Not implemented"); + }); + this.displayNameComponentType = entityStoreRegistry.registerComponent(DisplayNameComponent.class, "DisplayName", DisplayNameComponent.CODEC); + this.fromPrefabComponentType = entityStoreRegistry.registerComponent(FromPrefab.class, "FromPrefab", FromPrefab.CODEC); + entityStoreRegistry.registerSystem(new EntitySystems.ClearFromPrefabMarker(this.fromPrefabComponentType, this.preClearMarkersGroup)); + this.hiddenFromAdventurePlayerComponentType = entityStoreRegistry.registerComponent( + HiddenFromAdventurePlayers.class, "HiddenFromAdventurePlayer", HiddenFromAdventurePlayers.CODEC + ); + entityStoreRegistry.registerSystem(new PlayerMovementManagerSystems.AssignmentSystem()); + entityStoreRegistry.registerSystem(new PlayerMovementManagerSystems.PostAssignmentSystem()); + this.cameraManagerComponentType = entityStoreRegistry.registerComponent(CameraManager.class, CameraManager::new); + entityStoreRegistry.registerSystem(new PlayerCameraAddSystem()); + entityStoreRegistry.registerSystem(new PlayerHudManagerSystems.InitializeSystem()); + this.fromWorldGenComponentType = entityStoreRegistry.registerComponent(FromWorldGen.class, "FromWorldGen", FromWorldGen.CODEC); + entityStoreRegistry.registerSystem(new EntitySystems.ClearFromWorldGenMarker(this.fromWorldGenComponentType, this.preClearMarkersGroup)); + this.worldGenIdComponentType = entityStoreRegistry.registerComponent(WorldGenId.class, "WorldGenId", WorldGenId.CODEC); + entityStoreRegistry.registerSystem( + new EntitySystems.OnLoadFromExternal(this.fromPrefabComponentType, this.fromWorldGenComponentType, this.preClearMarkersGroup) + ); + this.playerSpatialResourceType = entityStoreRegistry.registerSpatialResource(() -> new KDTree<>(Ref::isValid)); + entityStoreRegistry.registerSystem(new PlayerSpatialSystem(this.playerSpatialResourceType)); + this.entitySpatialResourceType = entityStoreRegistry.registerSpatialResource(() -> new KDTree<>(Ref::isValid)); + entityStoreRegistry.registerSystem(new EntitySpatialSystem(this.entitySpatialResourceType)); + this.despawnComponentComponentType = entityStoreRegistry.registerComponent(DespawnComponent.class, "Despawn", DespawnComponent.CODEC); + this.dynamicLightComponentType = entityStoreRegistry.registerComponent(DynamicLight.class, DynamicLight::new); + this.persistentDynamicLightComponentType = entityStoreRegistry.registerComponent( + PersistentDynamicLight.class, "DynamicLight", PersistentDynamicLight.CODEC + ); + this.preventItemMergingType = entityStoreRegistry.registerComponent(PreventItemMerging.class, "PreventItemMerging", PreventItemMerging.CODEC); + this.itemComponentType = entityStoreRegistry.registerComponent(ItemComponent.class, "Item", ItemComponent.CODEC); + this.itemPhysicsComponentType = entityStoreRegistry.registerComponent(ItemPhysicsComponent.class, ItemPhysicsComponent::new); + entityStoreRegistry.registerSystem(new ItemSystems.EnsureRequiredComponents()); + entityStoreRegistry.registerSystem(new ItemSystems.TrackerSystem(this.visibleComponentType)); + this.prefabCopyableComponentType = entityStoreRegistry.registerComponent(PrefabCopyableComponent.class, PrefabCopyableComponent::get); + this.pickupItemComponentType = entityStoreRegistry.registerComponent(PickupItemComponent.class, "PickupItem", PickupItemComponent.CODEC); + entityStoreRegistry.registerSystem(new DespawnSystem(this.despawnComponentComponentType)); + this.itemSpatialResourceType = entityStoreRegistry.registerSpatialResource(() -> new KDTree<>(Ref::isValid)); + entityStoreRegistry.registerSystem(new ItemSpatialSystem(this.itemSpatialResourceType)); + entityStoreRegistry.registerSystem(new ItemMergeSystem(this.itemComponentType, this.interactableComponentType, this.itemSpatialResourceType)); + entityStoreRegistry.registerSystem(new PlayerItemEntityPickupSystem(this.itemComponentType, this.playerComponentType, this.playerSpatialResourceType)); + entityStoreRegistry.registerSystem( + new ItemPrePhysicsSystem( + this.itemComponentType, this.boundingBoxComponentType, this.velocityComponentType, this.transformComponentType, this.physicsValuesComponentType + ) + ); + entityStoreRegistry.registerSystem(new ItemPhysicsSystem(this.itemPhysicsComponentType, this.velocityComponentType, this.boundingBoxComponentType)); + entityStoreRegistry.registerSystem(new PickupItemSystem(this.pickupItemComponentType, this.transformComponentType)); + entityStoreRegistry.registerSystem(new LivingEntityEffectSystem()); + entityStoreRegistry.registerSystem( + new PlayerProcessMovementSystem(this.playerComponentType, this.velocityComponentType, this.collisionResultComponentType) + ); + this.chunkTrackerComponentType = entityStoreRegistry.registerComponent(ChunkTracker.class, ChunkTracker::new); + entityStoreRegistry.registerSystem(new PlayerChunkTrackerSystems.AddSystem()); + entityStoreRegistry.registerSystem(new PlayerChunkTrackerSystems.UpdateSystem()); + entityStoreRegistry.registerSystem(new PlayerSystems.NameplateRefSystem()); + entityStoreRegistry.registerSystem(new PlayerSystems.NameplateRefChangeSystem()); + this.entityViewerComponentType = entityStoreRegistry.registerComponent(EntityTrackerSystems.EntityViewer.class, () -> { + throw new UnsupportedOperationException("not supported"); + }); + entityStoreRegistry.registerSystem(new EntityTrackerSystems.ClearEntityViewers(this.entityViewerComponentType)); + entityStoreRegistry.registerSystem(new EntityTrackerSystems.ClearPreviouslyVisible(this.visibleComponentType)); + entityStoreRegistry.registerSystem(new EntityTrackerSystems.EnsureVisibleComponent(this.entityViewerComponentType, this.visibleComponentType)); + entityStoreRegistry.registerSystem(new EntityTrackerSystems.AddToVisible(this.entityViewerComponentType, this.visibleComponentType)); + entityStoreRegistry.registerSystem(new EntityTrackerSystems.RemoveEmptyVisibleComponent(this.visibleComponentType)); + entityStoreRegistry.registerSystem(new EntityTrackerSystems.RemoveVisibleComponent(this.visibleComponentType)); + entityStoreRegistry.registerSystem(new EntityTrackerSystems.SendPackets(this.entityViewerComponentType)); + entityStoreRegistry.registerSystem(new MovementStatesSystems.TickingSystem(this.visibleComponentType, this.movementStatesComponentType)); + this.networkSendableSpatialResourceType = entityStoreRegistry.registerSpatialResource(() -> new KDTree<>(Ref::isValid)); + entityStoreRegistry.registerSystem(new NetworkSendableSpatialSystem(this.networkSendableSpatialResourceType)); + entityStoreRegistry.registerSystem(new EntityTrackerSystems.CollectVisible(this.entityViewerComponentType)); + entityStoreRegistry.registerSystem(new LegacyEntityTrackerSystems.LegacyLODCull(this.entityViewerComponentType)); + entityStoreRegistry.registerSystem(new LegacyEntityTrackerSystems.LegacyHideFromEntity(this.entityViewerComponentType)); + entityStoreRegistry.registerSystem(new LegacyEntityTrackerSystems.LegacyEntityModel(this.visibleComponentType)); + entityStoreRegistry.registerSystem(new LegacyEntityTrackerSystems.LegacyEntitySkin(this.visibleComponentType, this.playerSkinComponentType)); + entityStoreRegistry.registerSystem(new BlockEntitySystems.BlockEntityTrackerSystem(this.visibleComponentType, this.blockEntityComponentType)); + entityStoreRegistry.registerSystem(new LegacyEntityTrackerSystems.LegacyEquipment(this.visibleComponentType)); + entityStoreRegistry.registerSystem(new EntityTrackerSystems.EffectControllerSystem(this.visibleComponentType, this.effectControllerComponentType)); + entityStoreRegistry.registerSystem(new EntitySystems.DynamicLightTracker(this.visibleComponentType)); + entityStoreRegistry.registerSystem(new DynamicLightSystems.Setup()); + entityStoreRegistry.registerSystem(new DynamicLightSystems.EntityTrackerRemove(this.visibleComponentType)); + entityStoreRegistry.registerSystem(new LivingEntityEffectClearChangesSystem()); + entityStoreRegistry.registerSystem(new PlayerSendInventorySystem(this.playerComponentType)); + entityStoreRegistry.registerSystem(new PlayerSavingSystems.WorldRemovedSystem(this.playerComponentType)); + entityStoreRegistry.registerSystem(new PlayerSavingSystems.TickingSystem(this.playerComponentType)); + this.entityGroupComponentType = entityStoreRegistry.registerComponent(EntityGroup.class, () -> { + throw new UnsupportedOperationException("Not implemented"); + }); + this.damageDataComponentType = entityStoreRegistry.registerComponent(DamageDataComponent.class, DamageDataComponent::new); + entityStoreRegistry.registerSystem(new DamageDataSetupSystem(this.damageDataComponentType)); + this.knockbackComponentType = entityStoreRegistry.registerComponent(KnockbackComponent.class, KnockbackComponent::new); + entityStoreRegistry.registerSystem(new UpdateLocationSystems.SpawnSystem()); + entityStoreRegistry.registerSystem(new UpdateLocationSystems.TickingSystem()); + this.persistentRefCountComponentType = entityStoreRegistry.registerComponent(PersistentRefCount.class, "RefId", PersistentRefCount.CODEC); + this.nameplateComponentType = entityStoreRegistry.registerComponent(Nameplate.class, "Nameplate", Nameplate.CODEC); + entityStoreRegistry.registerSystem(new NameplateSystems.EntityTrackerUpdate(this.visibleComponentType, this.nameplateComponentType)); + entityStoreRegistry.registerSystem(new NameplateSystems.EntityTrackerRemove(this.visibleComponentType, this.nameplateComponentType)); + this.interactableQueueResourceType = entityStoreRegistry.registerResource( + EntityInteractableSystems.QueueResource.class, EntityInteractableSystems.QueueResource::new + ); + entityStoreRegistry.registerSystem(new EntityInteractableSystems.EntityTrackerUpdate(this.visibleComponentType)); + entityStoreRegistry.registerSystem(new EntityInteractableSystems.EntityTrackerAddAndRemove(this.visibleComponentType)); + this.intangibleQueueResourceType = entityStoreRegistry.registerResource(IntangibleSystems.QueueResource.class, IntangibleSystems.QueueResource::new); + entityStoreRegistry.registerSystem(new IntangibleSystems.EntityTrackerUpdate(this.visibleComponentType)); + entityStoreRegistry.registerSystem(new IntangibleSystems.EntityTrackerAddAndRemove(this.visibleComponentType)); + entityStoreRegistry.registerSystem(new EntityModule.TangibleMigrationSystem(ProjectileComponent.getComponentType()), true); + this.invulnerableQueueResourceType = entityStoreRegistry.registerResource(InvulnerableSystems.QueueResource.class, InvulnerableSystems.QueueResource::new); + entityStoreRegistry.registerSystem(new InvulnerableSystems.EntityTrackerUpdate(this.visibleComponentType)); + entityStoreRegistry.registerSystem(new InvulnerableSystems.EntityTrackerAddAndRemove(this.visibleComponentType)); + this.respondToHitQueueResourceType = entityStoreRegistry.registerResource(RespondToHitSystems.QueueResource.class, RespondToHitSystems.QueueResource::new); + entityStoreRegistry.registerSystem(new RespondToHitSystems.EntityTrackerUpdate(this.visibleComponentType)); + entityStoreRegistry.registerSystem(new RespondToHitSystems.EntityTrackerAddAndRemove(this.visibleComponentType)); + entityStoreRegistry.registerSystem(new RespondToHitSystems.OnPlayerSettingsChange()); + entityStoreRegistry.registerSystem(new AudioSystems.EntityTrackerUpdate()); + entityStoreRegistry.registerSystem(new AudioSystems.TickMovementAudio()); + entityStoreRegistry.registerSystem(new ModelSystems.SetRenderedModel()); + entityStoreRegistry.registerSystem(new ModelSystems.AssignNetworkIdToProps()); + entityStoreRegistry.registerSystem(new ModelSystems.EnsurePropsPrefabCopyable()); + entityStoreRegistry.registerSystem(new ModelSystems.ApplyRandomSkin()); + entityStoreRegistry.registerSystem(new ModelSystems.ModelSpawned()); + entityStoreRegistry.registerSystem(new ModelSystems.PlayerConnect()); + entityStoreRegistry.registerSystem(new ModelSystems.ModelChange()); + entityStoreRegistry.registerSystem(new ModelSystems.UpdateBoundingBox()); + entityStoreRegistry.registerSystem(new ModelSystems.UpdateCrouchingBoundingBox()); + entityStoreRegistry.registerSystem(new ModelSystems.PlayerUpdateMovementManager()); + entityStoreRegistry.registerSystem(new ModelSystems.AnimationEntityTrackerUpdate()); + entityStoreRegistry.registerSystem(new EntitySystems.NewSpawnEntityTrackerUpdate()); + entityStoreRegistry.registerSystem(new HideEntitySystems.AdventurePlayerSystem()); + entityStoreRegistry.registerSystem(new TransformSystems.OnRemove()); + entityStoreRegistry.registerSystem(new PhysicsValuesAddSystem(this.physicsValuesComponentType)); + entityStoreRegistry.registerSystem(new GenericVelocityInstructionSystem()); + entityStoreRegistry.registerSystem(new PlayerVelocityInstructionSystem()); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + HitboxCollisionConfig.class, new IndexedLookupTableAssetMap<>(HitboxCollisionConfig[]::new) + ) + .setPath("Entity/HitboxCollision")) + .setCodec(HitboxCollisionConfig.CODEC)) + .setKeyFunction(HitboxCollisionConfig::getId)) + .setPacketGenerator(new HitboxCollisionConfigPacketGenerator()) + .setReplaceOnRemove(HitboxCollisionConfig::new)) + .build() + ); + this.hitboxCollisionComponentType = entityStoreRegistry.registerComponent(HitboxCollision.class, "HitboxCollision", HitboxCollision.CODEC); + entityStoreRegistry.registerSystem(new HitboxCollisionSystems.Setup(this.hitboxCollisionComponentType, this.playerComponentType)); + entityStoreRegistry.registerSystem(new HitboxCollisionSystems.EntityTrackerUpdate(this.visibleComponentType, this.hitboxCollisionComponentType)); + entityStoreRegistry.registerSystem(new HitboxCollisionSystems.EntityTrackerRemove(this.visibleComponentType, this.hitboxCollisionComponentType)); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + RepulsionConfig.class, new IndexedLookupTableAssetMap<>(RepulsionConfig[]::new) + ) + .setPath("Entity/Repulsion")) + .setCodec(RepulsionConfig.CODEC)) + .setKeyFunction(RepulsionConfig::getId)) + .setPacketGenerator(new RepulsionConfigPacketGenerator()) + .setReplaceOnRemove(RepulsionConfig::new)) + .build() + ); + this.repulsionComponentType = entityStoreRegistry.registerComponent(Repulsion.class, "Repulsion", Repulsion.CODEC); + entityStoreRegistry.registerSystem(new RepulsionSystems.PlayerSetup(this.repulsionComponentType, this.playerComponentType)); + entityStoreRegistry.registerSystem(new RepulsionSystems.EntityTrackerUpdate(this.visibleComponentType, this.repulsionComponentType)); + entityStoreRegistry.registerSystem(new RepulsionSystems.EntityTrackerRemove(this.visibleComponentType, this.repulsionComponentType)); + entityStoreRegistry.registerSystem( + new RepulsionSystems.RepulsionTicker(this.repulsionComponentType, this.transformComponentType, this.entitySpatialResourceType) + ); + entityStoreRegistry.registerSystem(new EntitySystems.NewSpawnTick()); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + MovementConfig.class, new IndexedLookupTableAssetMap<>(MovementConfig[]::new) + ) + .setPath("Entity/MovementConfig")) + .setCodec(MovementConfig.CODEC)) + .setKeyFunction(MovementConfig::getId)) + .setReplaceOnRemove(MovementConfig::new)) + .loadsBefore(GameplayConfig.class)) + .preLoadAssets(Collections.singletonList(MovementConfig.DEFAULT_MOVEMENT))) + .build() + ); + this.getEventRegistry().register(LoadedAssetsEvent.class, MovementConfig.class, this::onMovementConfigLoadedAssetsEvent); + this.getEventRegistry().register(LoadedAssetsEvent.class, GameplayConfig.class, this::onGameplayConfigLoadedAssetsEvent); + this.uniqueItemUsagesComponentType = entityStoreRegistry.registerComponent( + UniqueItemUsagesComponent.class, "UniqueItemUsages", UniqueItemUsagesComponent.CODEC + ); + entityStoreRegistry.registerSystem(new PlayerSystems.EnsureUniqueItemUsagesSystem()); + } + + @Override + protected void start() { + DamageCause.PHYSICAL = DamageCause.getAssetMap().getAsset("Physical"); + DamageCause.PROJECTILE = DamageCause.getAssetMap().getAsset("Projectile"); + DamageCause.COMMAND = DamageCause.getAssetMap().getAsset("Command"); + DamageCause.DROWNING = DamageCause.getAssetMap().getAsset("Drowning"); + DamageCause.ENVIRONMENT = DamageCause.getAssetMap().getAsset("Environment"); + DamageCause.FALL = DamageCause.getAssetMap().getAsset("Fall"); + DamageCause.OUT_OF_WORLD = DamageCause.getAssetMap().getAsset("OutOfWorld"); + DamageCause.SUFFOCATION = DamageCause.getAssetMap().getAsset("Suffocation"); + if (DamageCause.PHYSICAL == null + || DamageCause.PROJECTILE == null + || DamageCause.COMMAND == null + || DamageCause.DROWNING == null + || DamageCause.ENVIRONMENT == null + || DamageCause.FALL == null + || DamageCause.OUT_OF_WORLD == null + || DamageCause.SUFFOCATION == null) { + throw new IllegalStateException("Missing default DamageCause assets"); + } + } + + public SystemType getMigrationSystemType() { + return this.migrationSystemType; + } + + public SystemType> getVelocityModifyingSystemType() { + return this.velocityModifyingSystemType; + } + + public ComponentType getPlayerComponentType() { + return this.playerComponentType; + } + + public ComponentType getFrozenComponentType() { + return this.frozenComponentType; + } + + public ComponentType getChunkTrackerComponentType() { + return this.chunkTrackerComponentType; + } + + public ComponentType getPlayerSkinComponentType() { + return this.playerSkinComponentType; + } + + public ComponentType getDisplayNameComponentType() { + return this.displayNameComponentType; + } + + public ComponentType getApplyRandomSkinPersistedComponent() { + return this.applyRandomSkinPersistedComponent; + } + + public ComponentType getEntityGroupComponentType() { + return this.entityGroupComponentType; + } + + public ResourceType, EntityStore>> getPlayerSpatialResourceType() { + return this.playerSpatialResourceType; + } + + public ResourceType, EntityStore>> getItemSpatialResourceType() { + return this.itemSpatialResourceType; + } + + public ResourceType, EntityStore>> getNetworkSendableSpatialResourceType() { + return this.networkSendableSpatialResourceType; + } + + public ComponentType getCollisionResultComponentType() { + return this.collisionResultComponentType; + } + + public ComponentType getEntityViewerComponentType() { + return this.entityViewerComponentType; + } + + public ComponentType getVisibleComponentType() { + return this.visibleComponentType; + } + + public ComponentType getDamageDataComponentType() { + return this.damageDataComponentType; + } + + public ComponentType getKnockbackComponentType() { + return this.knockbackComponentType; + } + + public ComponentType getDespawnComponentType() { + return this.despawnComponentComponentType; + } + + public ResourceType getSnapshotWorldInfoResourceType() { + return this.snapshotWorldInfoResourceType; + } + + public ComponentType getSnapshotBufferComponentType() { + return this.snapshotBufferComponentType; + } + + public ComponentType getInteractableComponentType() { + return this.interactableComponentType; + } + + public ComponentType getIntangibleComponentType() { + return this.intangibleComponentType; + } + + public ComponentType getPreventPickupComponentType() { + return this.preventPickupComponentType; + } + + public ComponentType getInvulnerableComponentType() { + return this.invulnerableComponentType; + } + + public ComponentType getRespondToHitComponentType() { + return this.respondToHitComponentType; + } + + public ResourceType getInteractableQueueResourceType() { + return this.interactableQueueResourceType; + } + + public ResourceType getIntangibleQueueResourceType() { + return this.intangibleQueueResourceType; + } + + public ResourceType getInvulnerableQueueResourceType() { + return this.invulnerableQueueResourceType; + } + + public ResourceType getRespondToHitQueueResourceType() { + return this.respondToHitQueueResourceType; + } + + public ComponentType getHiddenFromAdventurePlayerComponentType() { + return this.hiddenFromAdventurePlayerComponentType; + } + + public ComponentType getFromPrefabComponentType() { + return this.fromPrefabComponentType; + } + + public ComponentType getFromWorldGenComponentType() { + return this.fromWorldGenComponentType; + } + + public ComponentType getWorldGenIdComponentType() { + return this.worldGenIdComponentType; + } + + public ComponentType getMovementManagerComponentType() { + return this.movementManagerComponentType; + } + + public ComponentType getNameplateComponentType() { + return this.nameplateComponentType; + } + + public SystemGroup getPreClearMarkersGroup() { + return this.preClearMarkersGroup; + } + + public ComponentType getPersistentRefCountComponentType() { + return this.persistentRefCountComponentType; + } + + public ComponentType getTransformComponentType() { + return this.transformComponentType; + } + + public ComponentType getHeadRotationComponentType() { + return this.headRotationComponentType; + } + + public ComponentType getNetworkIdComponentType() { + return this.networkIdComponentType; + } + + public ComponentType getEffectControllerComponentType() { + return this.effectControllerComponentType; + } + + public ComponentType getMovementStatesComponentType() { + return this.movementStatesComponentType; + } + + public ComponentType getBlockEntityComponentType() { + return this.blockEntityComponentType; + } + + public ComponentType getEntityScaleComponentType() { + return this.entityScaleComponentType; + } + + public ComponentType getCameraManagerComponentType() { + return this.cameraManagerComponentType; + } + + public ComponentType getUuidComponentType() { + return this.uuidComponentType; + } + + public ComponentType getPlayerInputComponentType() { + return this.playerInputComponentType; + } + + public ComponentType getKnockbackSimulationComponentType() { + return this.knockbackSimulationComponentType; + } + + public ComponentType getTeleportComponentType() { + return this.teleportComponentType; + } + + public ComponentType getProjectileComponentType() { + return this.projectileComponentType; + } + + public ComponentType getPendingTeleportComponentType() { + return this.pendingTeleportComponentType; + } + + public ComponentType getModelComponentType() { + return this.modelComponentType; + } + + public ComponentType getPersistentModelComponentType() { + return this.persistentModelComponentType; + } + + public ComponentType getPropComponentType() { + return this.propComponentType; + } + + public ComponentType getBoundingBoxComponentType() { + return this.boundingBoxComponentType; + } + + public ComponentType getHitboxCollisionComponentType() { + return this.hitboxCollisionComponentType; + } + + public ComponentType getVelocityComponentType() { + return this.velocityComponentType; + } + + public ComponentType getPhysicsValuesComponentType() { + return this.physicsValuesComponentType; + } + + public ComponentType getRepulsionComponentType() { + return this.repulsionComponentType; + } + + public ResourceType, EntityStore>> getEntitySpatialResourceType() { + return this.entitySpatialResourceType; + } + + public ComponentType getItemComponentType() { + return this.itemComponentType; + } + + public ComponentType getPickupItemComponentType() { + return this.pickupItemComponentType; + } + + public ComponentType getPreventItemMergingType() { + return this.preventItemMergingType; + } + + public ComponentType getItemPhysicsComponentType() { + return this.itemPhysicsComponentType; + } + + public ComponentType getDynamicLightComponentType() { + return this.dynamicLightComponentType; + } + + public ComponentType getPersistentDynamicLightComponentType() { + return this.persistentDynamicLightComponentType; + } + + public ComponentType getPrefabCopyableComponentType() { + return this.prefabCopyableComponentType; + } + + public ComponentType getRotateObjectComponentType() { + return this.rotateObjectComponentType; + } + + public ComponentType getNewSpawnComponentType() { + return this.newSpawnComponentType; + } + + private void onMovementConfigLoadedAssetsEvent(@Nonnull LoadedAssetsEvent> event) { + Universe.get() + .getWorlds() + .forEach( + (s, world) -> world.execute( + () -> { + Store store = world.getEntityStore().getStore(); + store.forEachEntityParallel( + MovementManager.getComponentType(), + (index, archetypeChunk, commandBuffer) -> { + String gameplayMovementConfigId = world.getGameplayConfig().getPlayerConfig().getMovementConfigId(); + + for (MovementConfig movementConfig : event.getLoadedAssets().values()) { + Ref ref = archetypeChunk.getReferenceTo(index); + Player playerComponent = archetypeChunk.getComponent(index, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = archetypeChunk.getComponent(index, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + PacketHandler packetHandler = playerRefComponent.getPacketHandler(); + if (movementConfig.getId().equals("Mount") && playerComponent.getMountEntityId() > 0) { + packetHandler.writeNoCache(new UpdateMovementSettings(movementConfig.toPacket())); + return; + } + + if (gameplayMovementConfigId.equals(movementConfig.getId())) { + MovementManager movementManagerComponent = archetypeChunk.getComponent(index, MovementManager.getComponentType()); + + assert movementManagerComponent != null; + + movementManagerComponent.setDefaultSettings( + movementConfig.toPacket(), EntityUtils.getPhysicsValues(ref, store), playerComponent.getGameMode() + ); + movementManagerComponent.applyDefaultSettings(); + movementManagerComponent.update(packetHandler); + } + } + } + ); + } + ) + ); + } + + private void onGameplayConfigLoadedAssetsEvent(LoadedAssetsEvent> event) { + Universe.get() + .getWorlds() + .forEach( + (s, world) -> world.execute( + () -> { + Store store = world.getEntityStore().getStore(); + store.forEachEntityParallel( + MovementManager.getComponentType(), + (index, archetypeChunk, commandBuffer) -> { + Ref ref = archetypeChunk.getReferenceTo(index); + int gameplayMovementConfigIndex = world.getGameplayConfig().getPlayerConfig().getMovementConfigIndex(); + MovementManager movementManagerComponent = archetypeChunk.getComponent(index, MovementManager.getComponentType()); + + assert movementManagerComponent != null; + + Player playerComponent = archetypeChunk.getComponent(index, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = archetypeChunk.getComponent(index, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + MovementConfig movConfig = (MovementConfig)((IndexedLookupTableAssetMap)MovementConfig.getAssetStore().getAssetMap()) + .getAsset(gameplayMovementConfigIndex); + movementManagerComponent.setDefaultSettings( + movConfig.toPacket(), EntityUtils.getPhysicsValues(ref, store), playerComponent.getGameMode() + ); + movementManagerComponent.applyDefaultSettings(); + PacketHandler packetHandler = playerRefComponent.getPacketHandler(); + movementManagerComponent.update(packetHandler); + } + ); + } + ) + ); + } + + public ComponentType getAudioComponentType() { + return this.audioComponentType; + } + + public ComponentType getMovementAudioComponentType() { + return this.movementAudioComponentType; + } + + public ComponentType getPositionDataComponentType() { + return this.positionDataComponentType; + } + + public ComponentType getPlayerSettingsComponentType() { + return this.playerSettingsComponentType; + } + + public ComponentType getUniqueItemUsagesComponentType() { + return this.uniqueItemUsagesComponentType; + } + + public ComponentType getActiveAnimationComponentType() { + return this.activeAnimationComponentType; + } + + @Nullable + public EntityRegistration registerEntity( + @Nonnull String id, @Nonnull Class clazz, Function entityConstructor, @Nullable DirectDecodeCodec codec + ) { + if (this.isDisabled()) { + return null; + } else { + this.idMap.put(id, clazz); + this.classIdMap.put(clazz, id); + this.classMap.put(clazz, entityConstructor); + if (codec != null) { + this.codecMap.put(clazz, codec); + } + + ComponentType componentType; + if (codec != null) { + componentType = this.getEntityStoreRegistry().registerComponent(clazz, id, (BuilderCodec)codec, true); + } else { + componentType = this.getEntityStoreRegistry().registerComponent(clazz, () -> { + throw new UnsupportedOperationException("Not implemented!"); + }); + } + + this.classToComponentType.put(clazz, componentType); + this.getEntityStoreRegistry().registerSystem(new EntityModule.LegacyEntityHolderSystem<>(componentType), true); + this.getEntityStoreRegistry().registerSystem(new EntityModule.LegacyEntityRefSystem<>(componentType), true); + return new EntityRegistration(clazz, () -> this.getState() == PluginState.ENABLED, () -> this.unregisterEntity(clazz)); + } + } + + private void unregisterEntity(Class clazz) { + if (!HytaleServer.get().isShuttingDown()) { + String id = this.classIdMap.remove(clazz); + this.idMap.remove(id); + this.classMap.remove(clazz); + this.codecMap.remove(clazz); + ComponentType componentType = this.classToComponentType.remove(clazz); + EntityStore.REGISTRY.unregisterComponent(componentType); + } + } + + @Nullable + public Function getConstructor(@Nullable Class entityClass) { + if (this.isDisabled()) { + return null; + } else { + return (Function)(entityClass == null ? null : this.classMap.get(entityClass)); + } + } + + @Nullable + public DirectDecodeCodec getCodec(@Nullable Class entityClass) { + if (this.isDisabled()) { + return null; + } else { + return (DirectDecodeCodec)(entityClass == null ? null : this.codecMap.get(entityClass)); + } + } + + @Nullable + public Class getClass(@Nullable String name) { + if (this.isDisabled()) { + return null; + } else { + return name == null ? null : this.idMap.get(name); + } + } + + @Nullable + public String getIdentifier(@Nullable Class entityClass) { + if (this.isDisabled()) { + return null; + } else { + return entityClass == null ? null : this.classIdMap.get(entityClass); + } + } + + @Nullable + public ComponentType getComponentType(@Nullable Class entityClass) { + if (this.isDisabled()) { + return null; + } else if (entityClass == null) { + return null; + } else if (entityClass.equals(Player.class)) { + throw new IllegalArgumentException("Get player component type via #getPlayerComponentType()"); + } else { + return (ComponentType)this.classToComponentType.get(entityClass); + } + } + + public boolean isKnown(@Nullable Entity entity) { + return entity != null && this.getConstructor(entity.getClass()) != null; + } + + @Deprecated(forRemoval = true) + public static class HiddenFromPlayerMigrationSystem extends EntityModule.MigrationSystem { + private final ComponentType hiddenFromAdventurePlayersComponentType = HiddenFromAdventurePlayers.getComponentType(); + @Nonnull + private final Query query; + + public HiddenFromPlayerMigrationSystem(Query query) { + this.query = Query.and(query, Query.not(this.hiddenFromAdventurePlayersComponentType)); + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(this.hiddenFromAdventurePlayersComponentType); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + } + + public static class LegacyEntityHolderSystem extends HolderSystem { + private final ComponentType componentType; + + public LegacyEntityHolderSystem(ComponentType componentType) { + this.componentType = componentType; + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.firstSet(); + } + + @Override + public Query getQuery() { + return this.componentType; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + T entityComponent = holder.getComponent(this.componentType); + + assert entityComponent != null; + + entityComponent.loadIntoWorld(store.getExternalData().getWorld()); + holder.putComponent(NetworkId.getComponentType(), new NetworkId(entityComponent.getNetworkId())); + if (holder.getComponent(Player.getComponentType()) != null) { + String displayName = entityComponent.getLegacyDisplayName(); + if (displayName != null && holder.getComponent(DisplayNameComponent.getComponentType()) == null) { + holder.putComponent(DisplayNameComponent.getComponentType(), new DisplayNameComponent(Message.raw(displayName))); + } + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + T entity = holder.getComponent(this.componentType); + switch (reason) { + case REMOVE: + if (!entity.wasRemoved()) { + entity.remove(); + entity.unloadFromWorld(); + } + break; + case UNLOAD: + entity.unloadFromWorld(); + entity.clearReference(); + } + } + + @Nonnull + @Override + public String toString() { + return "LegacyEntityHolderSystem{componentType=" + this.componentType + "}"; + } + } + + public static class LegacyEntityRefSystem extends RefSystem { + private final ComponentType componentType; + + public LegacyEntityRefSystem(ComponentType componentType) { + this.componentType = componentType; + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.firstSet(); + } + + @Override + public Query getQuery() { + return this.componentType; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + T entityComponent = store.getComponent(ref, this.componentType); + + assert entityComponent != null; + + entityComponent.setReference(ref); + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Nonnull + @Override + public String toString() { + return "LegacyEntityRefSystem{componentType=" + this.componentType + "}"; + } + } + + public static class LegacyTransformSystem extends EntityModule.MigrationSystem { + public LegacyTransformSystem() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + TransformComponent transformComponent = holder.getComponent(TransformComponent.getComponentType()); + Objects.requireNonNull(transformComponent); + Entity entity = EntityUtils.getEntity(holder); + entity.setTransformComponent(transformComponent); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.firstSet(); + } + + @Nonnull + @Override + public Query getQuery() { + return AllLegacyEntityTypesQuery.INSTANCE; + } + } + + public static class LegacyUUIDSystem extends EntityModule.MigrationSystem { + private final Set> dependencies = Set.of( + new SystemDependency<>(Order.BEFORE, EntityStore.UUIDSystem.class), RootDependency.first() + ); + + public LegacyUUIDSystem() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + UUIDComponent uuid = holder.getComponent(UUIDComponent.getComponentType()); + Entity entity = EntityUtils.getEntity(holder); + if (uuid == null) { + UUID legacyUuid = entity.getUuid(); + if (legacyUuid != null) { + holder.addComponent(UUIDComponent.getComponentType(), new UUIDComponent(legacyUuid)); + } + } else { + entity.setLegacyUUID(uuid.getUuid()); + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Nonnull + @Override + public Query getQuery() { + return AllLegacyEntityTypesQuery.INSTANCE; + } + } + + public static class LegacyUUIDUpdateSystem extends RefChangeSystem { + public LegacyUUIDUpdateSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return AllLegacyEntityTypesQuery.INSTANCE; + } + + @Nonnull + @Override + public ComponentType componentType() { + return UUIDComponent.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull UUIDComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + EntityUtils.getEntity(ref, store).setLegacyUUID(component.getUuid()); + } + + public void onComponentSet( + @Nonnull Ref ref, + UUIDComponent oldComponent, + @Nonnull UUIDComponent newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityUtils.getEntity(ref, store).setLegacyUUID(newComponent.getUuid()); + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull UUIDComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + EntityUtils.getEntity(ref, store).setLegacyUUID(null); + } + } + + public abstract static class MigrationSystem extends HolderSystem { + public MigrationSystem() { + } + } + + @Deprecated(forRemoval = true) + public static class TangibleMigrationSystem extends EntityModule.MigrationSystem { + private final ComponentType intangibleComponentType = Intangible.getComponentType(); + @Nonnull + private final Query query; + + public TangibleMigrationSystem(Query query) { + this.query = Query.and(query, Query.not(this.intangibleComponentType)); + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(this.intangibleComponentType); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + } + + public static enum Type { + PLAYERS, + ALL; + + private Type() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/EntityRegistration.java b/src/com/hypixel/hytale/server/core/modules/entity/EntityRegistration.java new file mode 100644 index 0000000..e0de609 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/EntityRegistration.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.core.modules.entity; + +import com.hypixel.hytale.registry.Registration; +import com.hypixel.hytale.server.core.entity.Entity; +import java.util.function.BooleanSupplier; +import javax.annotation.Nonnull; + +public class EntityRegistration extends Registration { + private final Class entityClass; + + public EntityRegistration(Class entityClass, BooleanSupplier isEnabled, Runnable unregister) { + super(isEnabled, unregister); + this.entityClass = entityClass; + } + + public EntityRegistration(@Nonnull EntityRegistration registration, BooleanSupplier isEnabled, Runnable unregister) { + super(isEnabled, unregister); + this.entityClass = registration.entityClass; + } + + public Class getEntityClass() { + return this.entityClass; + } + + @Nonnull + @Override + public String toString() { + return "EntityRegistration{entityClass=" + this.entityClass + ", " + super.toString() + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/EntityRegistry.java b/src/com/hypixel/hytale/server/core/modules/entity/EntityRegistry.java new file mode 100644 index 0000000..dedff46 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/EntityRegistry.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.core.modules.entity; + +import com.hypixel.hytale.codec.DirectDecodeCodec; +import com.hypixel.hytale.function.consumer.BooleanConsumer; +import com.hypixel.hytale.registry.Registry; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.List; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityRegistry extends Registry { + public EntityRegistry(@Nonnull List registrations, BooleanSupplier precondition, String preconditionMessage) { + super(registrations, precondition, preconditionMessage, EntityRegistration::new); + } + + @Nullable + public EntityRegistration registerEntity( + @Nonnull String key, @Nonnull Class clazz, Function constructor, DirectDecodeCodec codec + ) { + this.checkPrecondition(); + return this.register(EntityModule.get().registerEntity(key, clazz, constructor, codec)); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/LegacyProjectileSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/LegacyProjectileSystems.java new file mode 100644 index 0000000..fe22a3c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/LegacyProjectileSystems.java @@ -0,0 +1,199 @@ +package com.hypixel.hytale.server.core.modules.entity; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.DisableProcessingAssert; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.asset.type.projectile.config.Projectile; +import com.hypixel.hytale.server.core.entity.entities.ProjectileComponent; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.PersistentModel; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class LegacyProjectileSystems { + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + + public LegacyProjectileSystems() { + } + + public static class OnAddHolderSystem extends HolderSystem { + @Nonnull + private static final ComponentType PROJECTILE_COMPONENT_TYPE = ProjectileComponent.getComponentType(); + + public OnAddHolderSystem() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + if (!holder.getArchetype().contains(NetworkId.getComponentType())) { + holder.addComponent(NetworkId.getComponentType(), new NetworkId(store.getExternalData().takeNextNetworkId())); + } + + ProjectileComponent projectileComponent = holder.getComponent(PROJECTILE_COMPONENT_TYPE); + + assert projectileComponent != null; + + projectileComponent.initialize(); + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(projectileComponent.getAppearance()); + BoundingBox boundingBox; + if (modelAsset != null) { + Model model = Model.createUnitScaleModel(modelAsset); + holder.putComponent(ModelComponent.getComponentType(), new ModelComponent(model)); + holder.putComponent(PersistentModel.getComponentType(), new PersistentModel(model.toReference())); + boundingBox = new BoundingBox(model.getBoundingBox()); + } else { + Projectile projectileAsset = projectileComponent.getProjectile(); + if (projectileAsset != null) { + boundingBox = new BoundingBox(Box.horizontallyCentered(projectileAsset.getRadius(), projectileAsset.getHeight(), projectileAsset.getRadius())); + } else { + boundingBox = new BoundingBox(Box.horizontallyCentered(0.25, 0.25, 0.25)); + } + } + + holder.putComponent(BoundingBox.getComponentType(), boundingBox); + projectileComponent.initializePhysics(boundingBox); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Override + public Query getQuery() { + return PROJECTILE_COMPONENT_TYPE; + } + } + + public static class OnAddRefSystem extends RefSystem { + @Nonnull + private static final ComponentType PROJECTILE_COMPONENT_TYPE = ProjectileComponent.getComponentType(); + + public OnAddRefSystem() { + } + + @Override + public Query getQuery() { + return PROJECTILE_COMPONENT_TYPE; + } + + @Override + public void onEntityAdded( + @NonNullDecl Ref ref, + @NonNullDecl AddReason reason, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + ProjectileComponent projectileComponent = commandBuffer.getComponent(ref, PROJECTILE_COMPONENT_TYPE); + + assert projectileComponent != null; + + if (projectileComponent.getProjectile() == null) { + LegacyProjectileSystems.LOGGER.at(Level.WARNING).log("Removing projectile entity %s as it failed to initialize correctly!", ref); + commandBuffer.removeEntity(ref, RemoveReason.REMOVE); + } + } + + @Override + public void onEntityRemove( + @NonNullDecl Ref ref, + @NonNullDecl RemoveReason reason, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + } + } + + public static class TickingSystem extends EntityTickingSystem implements DisableProcessingAssert { + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private final ComponentType transformComponentType; + @Nonnull + private final ComponentType velocityComponentType; + @Nonnull + private final ComponentType boundingBoxComponentType; + @Nonnull + private final ComponentType projectileComponentType; + @Nonnull + private final Archetype archetype; + + public TickingSystem( + @Nonnull ComponentType projectileComponentType, + @Nonnull ComponentType transformComponentType, + @Nonnull ComponentType velocityComponentType, + @Nonnull ComponentType boundingBoxComponentType + ) { + this.projectileComponentType = projectileComponentType; + this.velocityComponentType = velocityComponentType; + this.boundingBoxComponentType = boundingBoxComponentType; + this.transformComponentType = transformComponentType; + this.archetype = Archetype.of(projectileComponentType, transformComponentType, velocityComponentType, boundingBoxComponentType); + } + + @Override + public Query getQuery() { + return this.archetype; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + TransformComponent transformComponent = archetypeChunk.getComponent(index, this.transformComponentType); + + assert transformComponent != null; + + ProjectileComponent projectileComponent = archetypeChunk.getComponent(index, this.projectileComponentType); + + assert projectileComponent != null; + + Velocity velocityComponent = archetypeChunk.getComponent(index, this.velocityComponentType); + + assert velocityComponent != null; + + BoundingBox boundingBox = archetypeChunk.getComponent(index, this.boundingBoxComponentType); + Ref ref = archetypeChunk.getReferenceTo(index); + + try { + if (projectileComponent.consumeDeadTimer(dt)) { + projectileComponent.onProjectileDeath(ref, transformComponent.getPosition(), commandBuffer); + commandBuffer.removeEntity(ref, RemoveReason.REMOVE); + return; + } + + World world = commandBuffer.getExternalData().getWorld(); + projectileComponent.getSimplePhysicsProvider().tick(dt, velocityComponent, world, transformComponent, ref, commandBuffer); + } catch (Throwable var12) { + LOGGER.at(Level.SEVERE).withCause(var12).log("Exception while ticking entity! Removing!! %s", projectileComponent); + commandBuffer.removeEntity(ref, RemoveReason.REMOVE); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/ActiveAnimationComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/component/ActiveAnimationComponent.java new file mode 100644 index 0000000..edbb1d8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/ActiveAnimationComponent.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.protocol.AnimationSlot; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActiveAnimationComponent implements Component { + private final String[] activeAnimations = new String[AnimationSlot.VALUES.length]; + private boolean isNetworkOutdated = false; + + public static ComponentType getComponentType() { + return EntityModule.get().getActiveAnimationComponentType(); + } + + public ActiveAnimationComponent() { + } + + public ActiveAnimationComponent(String[] activeAnimations) { + System.arraycopy(activeAnimations, 0, this.activeAnimations, 0, activeAnimations.length); + } + + public String[] getActiveAnimations() { + return this.activeAnimations; + } + + public void setPlayingAnimation(AnimationSlot slot, @Nullable String animation) { + if (this.activeAnimations[slot.ordinal()] == null || !this.activeAnimations[slot.ordinal()].equals(animation)) { + this.activeAnimations[slot.ordinal()] = animation; + } + } + + public boolean consumeNetworkOutdated() { + boolean temp = this.isNetworkOutdated; + this.isNetworkOutdated = false; + return temp; + } + + @Nonnull + @Override + public Component clone() { + return new ActiveAnimationComponent(this.activeAnimations); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/AudioComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/component/AudioComponent.java new file mode 100644 index 0000000..826df1e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/AudioComponent.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import javax.annotation.Nonnull; + +public class AudioComponent implements Component { + private IntList soundEventIds = new IntArrayList(); + private boolean isNetworkOutdated = true; + + public static ComponentType getComponentType() { + return EntityModule.get().getAudioComponentType(); + } + + public AudioComponent() { + } + + public AudioComponent(IntList soundEventIds) { + this.soundEventIds = soundEventIds; + } + + public int[] getSoundEventIds() { + return this.soundEventIds.toIntArray(); + } + + public void addSound(int soundIndex) { + this.soundEventIds.add(soundIndex); + this.isNetworkOutdated = true; + } + + public boolean consumeNetworkOutdated() { + boolean temp = this.isNetworkOutdated; + this.isNetworkOutdated = false; + return temp; + } + + @Nonnull + @Override + public Component clone() { + return new AudioComponent(new IntArrayList(this.soundEventIds)); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/BoundingBox.java b/src/com/hypixel/hytale/server/core/modules/entity/component/BoundingBox.java new file mode 100644 index 0000000..3b1168b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/BoundingBox.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.server.core.asset.type.model.config.DetailBox; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import javax.annotation.Nonnull; + +public class BoundingBox implements Component { + private final Box boundingBox = new Box(); + protected Map detailBoxes; + + public static ComponentType getComponentType() { + return EntityModule.get().getBoundingBoxComponentType(); + } + + public BoundingBox() { + } + + public BoundingBox(@Nonnull Box boundingBox) { + this.setBoundingBox(boundingBox); + } + + @Nonnull + public Box getBoundingBox() { + return this.boundingBox; + } + + public void setBoundingBox(@Nonnull Box boundingBox) { + this.boundingBox.assign(boundingBox); + } + + public Map getDetailBoxes() { + return this.detailBoxes; + } + + public void setDetailBoxes(Map detailBoxes) { + this.detailBoxes = detailBoxes; + } + + @Nonnull + @Override + public Component clone() { + return new BoundingBox(this.boundingBox); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/CollisionResultComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/component/CollisionResultComponent.java new file mode 100644 index 0000000..8d6cb2c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/CollisionResultComponent.java @@ -0,0 +1,82 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.collision.CollisionResult; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class CollisionResultComponent implements Component { + private final CollisionResult collisionResult; + private final Vector3d collisionStartPosition; + private final Vector3d collisionPositionOffset; + private final Vector3d collisionStartPositionCopy; + private final Vector3d collisionPositionOffsetCopy; + private boolean pendingCollisionCheck; + + public static ComponentType getComponentType() { + return EntityModule.get().getCollisionResultComponentType(); + } + + public CollisionResultComponent() { + this.collisionResult = new CollisionResult(false, false); + this.collisionStartPosition = new Vector3d(); + this.collisionPositionOffset = new Vector3d(); + this.collisionStartPositionCopy = new Vector3d(); + this.collisionPositionOffsetCopy = new Vector3d(); + } + + public CollisionResultComponent(@Nonnull CollisionResultComponent other) { + this.collisionResult = other.collisionResult; + this.collisionStartPosition = other.collisionStartPosition; + this.collisionPositionOffset = other.collisionPositionOffset; + this.collisionStartPositionCopy = other.collisionStartPositionCopy; + this.collisionPositionOffsetCopy = other.collisionPositionOffsetCopy; + this.pendingCollisionCheck = other.pendingCollisionCheck; + } + + public CollisionResult getCollisionResult() { + return this.collisionResult; + } + + public Vector3d getCollisionStartPosition() { + return this.collisionStartPosition; + } + + public Vector3d getCollisionPositionOffset() { + return this.collisionPositionOffset; + } + + public Vector3d getCollisionStartPositionCopy() { + return this.collisionStartPositionCopy; + } + + public Vector3d getCollisionPositionOffsetCopy() { + return this.collisionPositionOffsetCopy; + } + + public boolean isPendingCollisionCheck() { + return this.pendingCollisionCheck; + } + + public void markPendingCollisionCheck() { + this.pendingCollisionCheck = true; + } + + public void consumePendingCollisionCheck() { + this.pendingCollisionCheck = false; + } + + public void resetLocationChange() { + this.collisionPositionOffset.assign(Vector3d.ZERO); + this.pendingCollisionCheck = false; + } + + @Nonnull + @Override + public Component clone() { + return new CollisionResultComponent(this); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/DisplayNameComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/component/DisplayNameComponent.java new file mode 100644 index 0000000..0504da3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/DisplayNameComponent.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class DisplayNameComponent implements Component { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(DisplayNameComponent.class, DisplayNameComponent::new) + .appendInherited(new KeyedCodec<>("DisplayName", Message.CODEC), (e, s) -> e.displayName = s, e -> e.displayName, (e, p) -> e.displayName = p.displayName) + .documentation("The value of the display name.") + .add() + .build(); + @Nullable + private Message displayName; + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getDisplayNameComponentType(); + } + + public DisplayNameComponent() { + } + + public DisplayNameComponent(@Nullable Message displayName) { + this.displayName = displayName; + } + + @Nullable + public Message getDisplayName() { + return this.displayName; + } + + @NullableDecl + @Override + public Component clone() { + return new DisplayNameComponent(this.displayName); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/DynamicLight.java b/src/com/hypixel/hytale/server/core/modules/entity/component/DynamicLight.java new file mode 100644 index 0000000..c6f2661 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/DynamicLight.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.protocol.ColorLight; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class DynamicLight implements Component { + private ColorLight colorLight = new ColorLight(); + private boolean isNetworkOutdated = true; + + public static ComponentType getComponentType() { + return EntityModule.get().getDynamicLightComponentType(); + } + + public DynamicLight() { + } + + public DynamicLight(ColorLight colorLight) { + this.colorLight = colorLight; + } + + public ColorLight getColorLight() { + return this.colorLight; + } + + public void setColorLight(ColorLight colorLight) { + this.colorLight = colorLight; + this.isNetworkOutdated = true; + } + + public boolean consumeNetworkOutdated() { + boolean temp = this.isNetworkOutdated; + this.isNetworkOutdated = false; + return temp; + } + + @Nonnull + @Override + public Component clone() { + return new DynamicLight(new ColorLight(this.colorLight)); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/EntityScaleComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/component/EntityScaleComponent.java new file mode 100644 index 0000000..a9d85fd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/EntityScaleComponent.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EntityScaleComponent implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(EntityScaleComponent.class, EntityScaleComponent::new) + .addField(new KeyedCodec<>("Scale", Codec.FLOAT), (o, scale) -> o.scale = scale, o -> o.scale) + .build(); + private float scale = 1.0F; + private boolean isNetworkOutdated = true; + + public static ComponentType getComponentType() { + return EntityModule.get().getEntityScaleComponentType(); + } + + private EntityScaleComponent() { + } + + public EntityScaleComponent(float scale) { + this.scale = scale; + } + + public float getScale() { + return this.scale; + } + + public void setScale(float scale) { + this.scale = scale; + this.isNetworkOutdated = true; + } + + public boolean consumeNetworkOutdated() { + boolean temp = this.isNetworkOutdated; + this.isNetworkOutdated = false; + return temp; + } + + @Nonnull + @Override + public Component clone() { + return new EntityScaleComponent(this.scale); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/FromPrefab.java b/src/com/hypixel/hytale/server/core/modules/entity/component/FromPrefab.java new file mode 100644 index 0000000..e7fed71 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/FromPrefab.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public class FromPrefab implements Component { + public static final FromPrefab INSTANCE = new FromPrefab(); + public static final BuilderCodec CODEC = BuilderCodec.builder(FromPrefab.class, () -> INSTANCE).build(); + + public static ComponentType getComponentType() { + return EntityModule.get().getFromPrefabComponentType(); + } + + private FromPrefab() { + } + + @Override + public Component clone() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/FromWorldGen.java b/src/com/hypixel/hytale/server/core/modules/entity/component/FromWorldGen.java new file mode 100644 index 0000000..0d1d1a2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/FromWorldGen.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class FromWorldGen implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(FromWorldGen.class, FromWorldGen::new) + .append(new KeyedCodec<>("WorldGenId", Codec.INTEGER), (fromWorldGen, i) -> fromWorldGen.worldGenId = i, fromWorldGen -> fromWorldGen.worldGenId) + .add() + .build(); + private int worldGenId; + + public static ComponentType getComponentType() { + return EntityModule.get().getFromWorldGenComponentType(); + } + + private FromWorldGen() { + } + + public FromWorldGen(int worldGenId) { + this.worldGenId = worldGenId; + } + + public int getWorldGenId() { + return this.worldGenId; + } + + @Nonnull + @Override + public Component clone() { + return new FromWorldGen(this.worldGenId); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/HeadRotation.java b/src/com/hypixel/hytale/server/core/modules/entity/component/HeadRotation.java new file mode 100644 index 0000000..1a09b01 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/HeadRotation.java @@ -0,0 +1,119 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.util.TrigMathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class HeadRotation implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(HeadRotation.class, HeadRotation::new) + .append(new KeyedCodec<>("Rotation", Vector3f.ROTATION), (o, i) -> o.rotation.assign(i), o -> o.rotation) + .add() + .build(); + private final Vector3f rotation = new Vector3f(); + + public static ComponentType getComponentType() { + return EntityModule.get().getHeadRotationComponentType(); + } + + public HeadRotation() { + } + + public HeadRotation(@Nonnull Vector3f rotation) { + this.rotation.assign(rotation); + } + + @Nonnull + public Vector3f getRotation() { + return this.rotation; + } + + public void setRotation(@Nonnull Vector3f rotation) { + this.rotation.assign(rotation); + } + + public Vector3d getDirection() { + return getDirection(this.rotation.getPitch(), this.rotation.getYaw(), new Vector3d()); + } + + @Nonnull + public Vector3i getAxisDirection() { + return getAxisDirection(this.rotation.getPitch(), this.rotation.getYaw(), new Vector3i()); + } + + @Nonnull + public Vector3i getAxisDirection(@Nonnull Vector3i result) { + return getAxisDirection(this.rotation.getPitch(), this.rotation.getYaw(), result); + } + + @Nonnull + public Vector3i getHorizontalAxisDirection() { + return getAxisDirection(0.0F, this.rotation.getYaw(), new Vector3i()); + } + + @Nonnull + public Axis getAxis() { + Vector3i axisDirection = this.getAxisDirection(); + if (axisDirection.getX() != 0) { + return Axis.X; + } else { + return axisDirection.getY() != 0 ? Axis.Y : Axis.Z; + } + } + + @Nonnull + public static Vector3i getAxisDirection(float pitch, float yaw, @Nonnull Vector3i result) { + if (Float.isNaN(pitch)) { + throw new IllegalStateException("Pitch can't be NaN"); + } else if (Float.isNaN(yaw)) { + throw new IllegalStateException("Yaw can't be NaN"); + } else { + double len = TrigMathUtil.cos(pitch); + double x = len * -TrigMathUtil.sin(yaw); + double y = TrigMathUtil.sin(pitch); + double z = len * -TrigMathUtil.cos(yaw); + return result.assign((int)Math.round(x), (int)Math.round(y), (int)Math.round(z)); + } + } + + @Nonnull + private static Vector3d getDirection(float pitch, float yaw, @Nonnull Vector3d result) { + if (Float.isNaN(pitch)) { + throw new IllegalStateException("Pitch can't be NaN"); + } else if (Float.isNaN(yaw)) { + throw new IllegalStateException("Yaw can't be NaN"); + } else { + return result.assign(yaw, pitch); + } + } + + public void teleportRotation(@Nonnull Vector3f rotation) { + float yaw = rotation.getYaw(); + if (!Float.isNaN(yaw)) { + this.rotation.setYaw(yaw); + } + + float pitch = rotation.getPitch(); + if (!Float.isNaN(pitch)) { + this.rotation.setPitch(pitch); + } + + float roll = rotation.getRoll(); + if (!Float.isNaN(roll)) { + this.rotation.setRoll(roll); + } + } + + @Nonnull + public HeadRotation clone() { + return new HeadRotation(this.rotation); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/HiddenFromAdventurePlayers.java b/src/com/hypixel/hytale/server/core/modules/entity/component/HiddenFromAdventurePlayers.java new file mode 100644 index 0000000..aa22675 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/HiddenFromAdventurePlayers.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public class HiddenFromAdventurePlayers implements Component { + public static final HiddenFromAdventurePlayers INSTANCE = new HiddenFromAdventurePlayers(); + public static final BuilderCodec CODEC = BuilderCodec.builder(HiddenFromAdventurePlayers.class, () -> INSTANCE).build(); + + public static ComponentType getComponentType() { + return EntityModule.get().getHiddenFromAdventurePlayerComponentType(); + } + + private HiddenFromAdventurePlayers() { + } + + @Override + public Component clone() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/Intangible.java b/src/com/hypixel/hytale/server/core/modules/entity/component/Intangible.java new file mode 100644 index 0000000..9538f10 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/Intangible.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public class Intangible implements Component { + public static final Intangible INSTANCE = new Intangible(); + public static final BuilderCodec CODEC = BuilderCodec.builder(Intangible.class, () -> INSTANCE).build(); + + public static ComponentType getComponentType() { + return EntityModule.get().getIntangibleComponentType(); + } + + private Intangible() { + } + + @Override + public Component clone() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/Interactable.java b/src/com/hypixel/hytale/server/core/modules/entity/component/Interactable.java new file mode 100644 index 0000000..81f7a82 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/Interactable.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class Interactable implements Component { + @Nonnull + public static final Interactable INSTANCE = new Interactable(); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(Interactable.class, () -> INSTANCE).build(); + + public static ComponentType getComponentType() { + return EntityModule.get().getInteractableComponentType(); + } + + private Interactable() { + } + + @Override + public Component clone() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/Invulnerable.java b/src/com/hypixel/hytale/server/core/modules/entity/component/Invulnerable.java new file mode 100644 index 0000000..63669dc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/Invulnerable.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public class Invulnerable implements Component { + public static final Invulnerable INSTANCE = new Invulnerable(); + public static final BuilderCodec CODEC = BuilderCodec.builder(Invulnerable.class, () -> INSTANCE).build(); + + public static ComponentType getComponentType() { + return EntityModule.get().getInvulnerableComponentType(); + } + + private Invulnerable() { + } + + @Override + public Component clone() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/ModelComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/component/ModelComponent.java new file mode 100644 index 0000000..3154a14 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/ModelComponent.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ModelComponent implements Component { + private final Model model; + private boolean isNetworkOutdated = true; + + public static ComponentType getComponentType() { + return EntityModule.get().getModelComponentType(); + } + + public ModelComponent(Model model) { + this.model = model; + } + + public Model getModel() { + return this.model; + } + + public boolean consumeNetworkOutdated() { + boolean temp = this.isNetworkOutdated; + this.isNetworkOutdated = false; + return temp; + } + + @Nonnull + @Override + public Component clone() { + return new ModelComponent(this.model); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/MovementAudioComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/component/MovementAudioComponent.java new file mode 100644 index 0000000..68d9878 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/MovementAudioComponent.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.function.Predicate; +import javax.annotation.Nonnull; + +public class MovementAudioComponent implements Component { + public static float NO_REPEAT = -1.0F; + private final MovementAudioComponent.ShouldHearPredicate shouldHearPredicate = new MovementAudioComponent.ShouldHearPredicate(); + private int lastInsideBlockTypeId = 0; + private float nextMoveInRepeat = NO_REPEAT; + + public static ComponentType getComponentType() { + return EntityModule.get().getMovementAudioComponentType(); + } + + public MovementAudioComponent() { + } + + @Nonnull + public MovementAudioComponent.ShouldHearPredicate getShouldHearPredicate(Ref ref) { + this.shouldHearPredicate.owner = ref; + return this.shouldHearPredicate; + } + + public int getLastInsideBlockTypeId() { + return this.lastInsideBlockTypeId; + } + + public void setLastInsideBlockTypeId(int lastInsideBlockTypeId) { + this.lastInsideBlockTypeId = lastInsideBlockTypeId; + } + + public boolean canMoveInRepeat() { + return this.nextMoveInRepeat != NO_REPEAT; + } + + public boolean tickMoveInRepeat(float dt) { + return (this.nextMoveInRepeat -= dt) <= 0.0F; + } + + public void setNextMoveInRepeat(float nextMoveInRepeat) { + this.nextMoveInRepeat = nextMoveInRepeat; + } + + @Nonnull + @Override + public Component clone() { + return new MovementAudioComponent(); + } + + public static class ShouldHearPredicate implements Predicate> { + protected Ref owner; + + public ShouldHearPredicate() { + } + + public boolean test(@Nonnull Ref targetRef) { + return !this.owner.equals(targetRef); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/NewSpawnComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/component/NewSpawnComponent.java new file mode 100644 index 0000000..ec6d5c7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/NewSpawnComponent.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public class NewSpawnComponent implements Component { + private float newSpawnWindow; + + public static ComponentType getComponentType() { + return EntityModule.get().getNewSpawnComponentType(); + } + + public NewSpawnComponent(float newSpawnWindow) { + this.newSpawnWindow = newSpawnWindow; + } + + public boolean newSpawnWindowPassed(float dt) { + return (this.newSpawnWindow -= dt) <= 0.0F; + } + + @Override + public Component clone() { + return new NewSpawnComponent(this.newSpawnWindow); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/PersistentDynamicLight.java b/src/com/hypixel/hytale/server/core/modules/entity/component/PersistentDynamicLight.java new file mode 100644 index 0000000..331c30f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/PersistentDynamicLight.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.protocol.ColorLight; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PersistentDynamicLight implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(PersistentDynamicLight.class, PersistentDynamicLight::new) + .addField(new KeyedCodec<>("Light", ProtocolCodecs.COLOR_LIGHT), (o, light) -> o.colorLight = light, o -> o.colorLight) + .build(); + private ColorLight colorLight; + + public static ComponentType getComponentType() { + return EntityModule.get().getPersistentDynamicLightComponentType(); + } + + private PersistentDynamicLight() { + } + + public PersistentDynamicLight(ColorLight colorLight) { + this.colorLight = colorLight; + } + + public ColorLight getColorLight() { + return this.colorLight; + } + + public void setColorLight(ColorLight colorLight) { + this.colorLight = colorLight; + } + + @Nonnull + @Override + public Component clone() { + return new PersistentDynamicLight(new ColorLight(this.colorLight)); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/PersistentModel.java b/src/com/hypixel/hytale/server/core/modules/entity/component/PersistentModel.java new file mode 100644 index 0000000..08ba3ac --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/PersistentModel.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PersistentModel implements Component { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(PersistentModel.class, PersistentModel::new) + .append(new KeyedCodec<>("Model", Model.ModelReference.CODEC), (entity, model) -> entity.modelReference = model, entity -> entity.modelReference) + .add() + .build(); + private Model.ModelReference modelReference; + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getPersistentModelComponentType(); + } + + private PersistentModel() { + } + + public PersistentModel(@Nonnull Model.ModelReference modelReference) { + this.modelReference = modelReference; + } + + @Nonnull + public Model.ModelReference getModelReference() { + return this.modelReference; + } + + public void setModelReference(@Nonnull Model.ModelReference modelReference) { + this.modelReference = modelReference; + } + + @Nonnull + @Override + public Component clone() { + PersistentModel modelComponent = new PersistentModel(); + modelComponent.modelReference = this.modelReference; + return modelComponent; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/PositionDataComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/component/PositionDataComponent.java new file mode 100644 index 0000000..9de831d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/PositionDataComponent.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PositionDataComponent implements Component { + private int insideBlockTypeId = 0; + private int standingOnBlockTypeId = 0; + + public static ComponentType getComponentType() { + return EntityModule.get().getPositionDataComponentType(); + } + + public PositionDataComponent() { + } + + public PositionDataComponent(int insideBlockTypeId, int standingOnBlockTypeId) { + this.insideBlockTypeId = insideBlockTypeId; + this.standingOnBlockTypeId = standingOnBlockTypeId; + } + + public int getInsideBlockTypeId() { + return this.insideBlockTypeId; + } + + public void setInsideBlockTypeId(int insideBlockTypeId) { + this.insideBlockTypeId = insideBlockTypeId; + } + + public int getStandingOnBlockTypeId() { + return this.standingOnBlockTypeId; + } + + public void setStandingOnBlockTypeId(int standingOnBlockTypeId) { + this.standingOnBlockTypeId = standingOnBlockTypeId; + } + + @Nonnull + @Override + public Component clone() { + return new PositionDataComponent(this.insideBlockTypeId, this.standingOnBlockTypeId); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/PropComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/component/PropComponent.java new file mode 100644 index 0000000..d01eff1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/PropComponent.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PropComponent implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(PropComponent.class, PropComponent::new).build(); + private static final PropComponent INSTANCE = new PropComponent(); + + public PropComponent() { + } + + public static ComponentType getComponentType() { + return EntityModule.get().getPropComponentType(); + } + + public static PropComponent get() { + return INSTANCE; + } + + @Nonnull + @Override + public Component clone() { + return this; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/RespondToHit.java b/src/com/hypixel/hytale/server/core/modules/entity/component/RespondToHit.java new file mode 100644 index 0000000..5b7679e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/RespondToHit.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public class RespondToHit implements Component { + public static final RespondToHit INSTANCE = new RespondToHit(); + public static final BuilderCodec CODEC = BuilderCodec.builder(RespondToHit.class, () -> INSTANCE).build(); + + public static ComponentType getComponentType() { + return EntityModule.get().getRespondToHitComponentType(); + } + + private RespondToHit() { + } + + @Override + public Component clone() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/RotateObjectComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/component/RotateObjectComponent.java new file mode 100644 index 0000000..558dc92 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/RotateObjectComponent.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class RotateObjectComponent implements Component { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(RotateObjectComponent.class, RotateObjectComponent::new) + .append(new KeyedCodec<>("RotationSpeed", Codec.FLOAT), (c, f) -> c.rotationSpeed = f, c -> c.rotationSpeed) + .add() + .build(); + private float rotationSpeed; + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getRotateObjectComponentType(); + } + + public RotateObjectComponent() { + } + + public RotateObjectComponent(float rotationSpeed) { + this.rotationSpeed = rotationSpeed; + } + + @Nonnull + @Override + public Component clone() { + return new RotateObjectComponent(this.rotationSpeed); + } + + public void setRotationSpeed(float rotationSpeed) { + this.rotationSpeed = rotationSpeed; + } + + public float getRotationSpeed() { + return this.rotationSpeed; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/SnapshotBuffer.java b/src/com/hypixel/hytale/server/core/modules/entity/component/SnapshotBuffer.java new file mode 100644 index 0000000..a629d33 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/SnapshotBuffer.java @@ -0,0 +1,144 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.entity.EntitySnapshot; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SnapshotBuffer implements Component { + private EntitySnapshot[] snapshots; + private int currentTickIndex = Integer.MIN_VALUE; + private int oldestTickIndex = Integer.MIN_VALUE; + private int currentIndex = -1; + + public SnapshotBuffer() { + } + + public static ComponentType getComponentType() { + return EntityModule.get().getSnapshotBufferComponentType(); + } + + @Nonnull + public EntitySnapshot getSnapshotClamped(int tickIndex) { + if (this.currentIndex == -1) { + throw new IllegalStateException("Snapshots not initialized"); + } else { + int relIndex = tickIndex - this.currentTickIndex; + int maxRel = this.oldestTickIndex - this.currentTickIndex; + if (relIndex > 0) { + throw new IllegalArgumentException("Tick index is in the future"); + } else { + if (relIndex < maxRel) { + relIndex = maxRel; + } + + return this.getSnapshotRelative(relIndex); + } + } + } + + @Nullable + public EntitySnapshot getSnapshot(int tickIndex) { + if (this.currentIndex == -1) { + return null; + } else { + int relIndex = tickIndex - this.currentTickIndex; + int maxRel = this.oldestTickIndex - this.currentTickIndex; + if (relIndex > 0) { + throw new IllegalArgumentException("Tick index is in the future"); + } else { + return relIndex < maxRel ? null : this.getSnapshotRelative(relIndex); + } + } + } + + private EntitySnapshot getSnapshotRelative(int relIndex) { + int index = this.currentIndex + relIndex; + index = (this.snapshots.length + index) % this.snapshots.length; + return this.snapshots[index]; + } + + public void storeSnapshot(int tickIndex, @Nonnull Vector3d position, @Nonnull Vector3f bodyRotation) { + if (this.currentIndex != -1 && this.currentTickIndex != tickIndex - 1) { + this.currentIndex = -1; + this.currentTickIndex = Integer.MIN_VALUE; + this.oldestTickIndex = Integer.MIN_VALUE; + } + + if (this.currentIndex == -1) { + this.oldestTickIndex = tickIndex; + } + + this.currentTickIndex = tickIndex; + this.currentIndex++; + this.currentIndex = this.currentIndex % this.snapshots.length; + int maxRel = this.currentTickIndex - this.oldestTickIndex; + if (maxRel >= this.snapshots.length) { + this.oldestTickIndex++; + } + + EntitySnapshot snapshot = this.snapshots[this.currentIndex]; + snapshot.init(position, bodyRotation); + } + + public void resize(int newLength) { + if (newLength <= 0) { + throw new IllegalArgumentException("New size is too small: " + newLength); + } else if (this.snapshots == null || newLength != this.snapshots.length) { + if (this.snapshots == null) { + this.snapshots = new EntitySnapshot[newLength]; + + for (int i = 0; i < this.snapshots.length; i++) { + this.snapshots[i] = new EntitySnapshot(); + } + } else { + int oldLength = this.snapshots.length; + this.snapshots = Arrays.copyOf(this.snapshots, newLength); + + for (int i = oldLength; i < newLength; i++) { + this.snapshots[i] = new EntitySnapshot(); + } + } + + this.currentIndex = -1; + this.currentTickIndex = Integer.MIN_VALUE; + this.oldestTickIndex = Integer.MIN_VALUE; + } + } + + public boolean isInitialized() { + return this.currentIndex != -1; + } + + public int getCurrentTickIndex() { + return this.currentTickIndex; + } + + public int getOldestTickIndex() { + return this.oldestTickIndex; + } + + @Nonnull + @Override + public Component clone() { + SnapshotBuffer buffer = new SnapshotBuffer(); + if (this.snapshots != null && this.currentIndex != -1) { + buffer.resize(this.snapshots.length); + + for (int i = this.oldestTickIndex; i <= this.currentTickIndex; i++) { + EntitySnapshot snap = this.getSnapshot(i); + buffer.storeSnapshot(i, snap.getPosition(), snap.getBodyRotation()); + } + + return buffer; + } else { + return buffer; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/TransformComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/component/TransformComponent.java new file mode 100644 index 0000000..3d7883d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/TransformComponent.java @@ -0,0 +1,157 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.ModelTransform; +import com.hypixel.hytale.protocol.Position; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.PositionUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TransformComponent implements Component { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(TransformComponent.class, TransformComponent::new) + .append(new KeyedCodec<>("Position", Vector3d.CODEC), (o, i) -> o.position.assign(i), o -> o.position) + .add() + .append(new KeyedCodec<>("Rotation", Vector3f.ROTATION), (o, i) -> o.rotation.assign(i), o -> o.rotation) + .add() + .build(); + @Nonnull + private final Vector3d position = new Vector3d(); + @Nonnull + private final Vector3f rotation = new Vector3f(); + @Nonnull + private final ModelTransform sentTransform = new ModelTransform(new Position(), new Direction(), new Direction()); + @Nullable + @Deprecated(forRemoval = true) + private WorldChunk chunk; + @Nullable + private Ref chunkRef; + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getTransformComponentType(); + } + + public TransformComponent() { + } + + public TransformComponent(@Nonnull Vector3d position, @Nonnull Vector3f rotation) { + this.position.assign(position); + this.rotation.assign(rotation); + } + + @Nonnull + public Vector3d getPosition() { + return this.position; + } + + public void setPosition(@Nonnull Vector3d position) { + this.position.assign(position); + } + + public void teleportPosition(@Nonnull Vector3d position) { + double x = position.getX(); + if (!Double.isNaN(x)) { + this.position.setX(x); + } + + double y = position.getY(); + if (!Double.isNaN(y)) { + this.position.setY(y); + } + + double z = position.getZ(); + if (!Double.isNaN(z)) { + this.position.setZ(z); + } + } + + @Nonnull + public Vector3f getRotation() { + return this.rotation; + } + + public void setRotation(@Nonnull Vector3f rotation) { + this.rotation.assign(rotation); + } + + @Nonnull + public Transform getTransform() { + return new Transform(this.position, this.rotation); + } + + public void teleportRotation(@Nonnull Vector3f rotation) { + float yaw = rotation.getYaw(); + if (!Float.isNaN(yaw)) { + this.rotation.setYaw(yaw); + } + + float pitch = rotation.getPitch(); + if (!Float.isNaN(pitch)) { + this.rotation.setPitch(pitch); + } + + float roll = rotation.getRoll(); + if (!Float.isNaN(roll)) { + this.rotation.setRoll(roll); + } + } + + @Nonnull + public ModelTransform getSentTransform() { + return this.sentTransform; + } + + @Nullable + @Deprecated(forRemoval = true) + public WorldChunk getChunk() { + return this.chunk; + } + + @Nullable + public Ref getChunkRef() { + return this.chunkRef; + } + + public void markChunkDirty(@Nonnull ComponentAccessor componentAccessor) { + if (this.chunkRef != null && this.chunkRef.isValid()) { + World world = componentAccessor.getExternalData().getWorld(); + Store chunkStore = world.getChunkStore().getStore(); + WorldChunk worldChunkComponent = chunkStore.getComponent(this.chunkRef, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + worldChunkComponent.markNeedsSaving(); + } + } + + public void setChunkLocation(@Nullable Ref chunkRef, @Nullable WorldChunk chunk) { + this.chunkRef = chunkRef; + this.chunk = chunk; + } + + @Nonnull + public TransformComponent clone() { + TransformComponent transformComponent = new TransformComponent(this.position, this.rotation); + ModelTransform transform = transformComponent.sentTransform; + PositionUtil.assign(transform.position, this.sentTransform.position); + PositionUtil.assign(transform.bodyOrientation, this.sentTransform.bodyOrientation); + PositionUtil.assign(transform.lookOrientation, this.sentTransform.lookOrientation); + return transformComponent; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/component/WorldGenId.java b/src/com/hypixel/hytale/server/core/modules/entity/component/WorldGenId.java new file mode 100644 index 0000000..81a361f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/component/WorldGenId.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.modules.entity.component; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WorldGenId implements Component { + public static final int NON_WORLD_GEN_ID = 0; + public static final BuilderCodec CODEC = BuilderCodec.builder(WorldGenId.class, WorldGenId::new) + .append(new KeyedCodec<>("WorldGenId", Codec.INTEGER), (worldGenId, i) -> worldGenId.worldGenId = i, worldGenId -> worldGenId.worldGenId) + .add() + .build(); + private int worldGenId; + + public static ComponentType getComponentType() { + return EntityModule.get().getWorldGenIdComponentType(); + } + + public WorldGenId(int worldGenId) { + this.worldGenId = worldGenId; + } + + private WorldGenId() { + } + + public int getWorldGenId() { + return this.worldGenId; + } + + @Nonnull + @Override + public Component clone() { + return new WorldGenId(this.worldGenId); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/damage/Damage.java b/src/com/hypixel/hytale/server/core/modules/entity/damage/Damage.java new file mode 100644 index 0000000..468acd7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/damage/Damage.java @@ -0,0 +1,280 @@ +package com.hypixel.hytale.server.core.modules.entity.damage; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.system.CancellableEcsEvent; +import com.hypixel.hytale.math.vector.Vector4d; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelParticle; +import com.hypixel.hytale.server.core.asset.type.particle.config.WorldParticle; +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.entity.knockback.KnockbackComponent; +import com.hypixel.hytale.server.core.meta.DynamicMetaStore; +import com.hypixel.hytale.server.core.meta.IMetaStore; +import com.hypixel.hytale.server.core.meta.IMetaStoreImpl; +import com.hypixel.hytale.server.core.meta.MetaKey; +import com.hypixel.hytale.server.core.meta.MetaRegistry; +import com.hypixel.hytale.server.core.modules.entity.component.DisplayNameComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Locale; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Damage extends CancellableEcsEvent implements IMetaStore { + @Nonnull + private static final Message MESSAGE_GENERAL_DAMAGE_CAUSE_UNKNOWN = Message.translation("server.general.damageCauses.unknown"); + @Nonnull + public static final MetaRegistry META_REGISTRY = new MetaRegistry<>(); + @Nonnull + public static final MetaKey HIT_LOCATION = META_REGISTRY.registerMetaObject(); + @Nonnull + public static final MetaKey HIT_ANGLE = META_REGISTRY.registerMetaObject(); + @Nonnull + public static final MetaKey IMPACT_PARTICLES = META_REGISTRY.registerMetaObject(); + @Nonnull + public static final MetaKey IMPACT_SOUND_EFFECT = META_REGISTRY.registerMetaObject(); + @Nonnull + public static final MetaKey PLAYER_IMPACT_SOUND_EFFECT = META_REGISTRY.registerMetaObject(); + @Nonnull + public static final MetaKey CAMERA_EFFECT = META_REGISTRY.registerMetaObject(); + @Nonnull + public static final MetaKey DEATH_ICON = META_REGISTRY.registerMetaObject(); + @Nonnull + public static final MetaKey BLOCKED = META_REGISTRY.registerMetaObject(data -> Boolean.FALSE); + @Nonnull + public static final MetaKey STAMINA_DRAIN_MULTIPLIER = META_REGISTRY.registerMetaObject(); + @Nonnull + public static final MetaKey CAN_BE_PREDICTED = META_REGISTRY.registerMetaObject(data -> Boolean.FALSE); + @Nonnull + public static final MetaKey KNOCKBACK_COMPONENT = META_REGISTRY.registerMetaObject(); + @Nonnull + public static final Damage.Source NULL_SOURCE = new Damage.Source() {}; + @Nonnull + private final IMetaStoreImpl metaStore = new DynamicMetaStore<>(this, META_REGISTRY); + private final float initialAmount; + private int damageCauseIndex; + @Nonnull + private Damage.Source source; + private float amount; + + public Damage(@Nonnull Damage.Source source, @Nonnull DamageCause damageCause, float amount) { + this.source = source; + this.damageCauseIndex = DamageCause.getAssetMap().getIndex(damageCause.getId()); + this.initialAmount = this.amount = amount; + } + + public Damage(@Nonnull Damage.Source source, int damageCauseIndex, float amount) { + this.source = source; + this.damageCauseIndex = damageCauseIndex; + this.initialAmount = this.amount = amount; + } + + public int getDamageCauseIndex() { + return this.damageCauseIndex; + } + + public void setDamageCauseIndex(int damageCauseIndex) { + this.damageCauseIndex = damageCauseIndex; + } + + @Deprecated + @Nullable + public DamageCause getCause() { + return DamageCause.getAssetMap().getAsset(this.damageCauseIndex); + } + + @Nonnull + public Damage.Source getSource() { + return this.source; + } + + public void setSource(@Nonnull Damage.Source source) { + this.source = source; + } + + public float getAmount() { + return this.amount; + } + + public void setAmount(float amount) { + this.amount = amount; + } + + public float getInitialAmount() { + return this.initialAmount; + } + + @Nonnull + public Message getDeathMessage(@Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor) { + return this.source.getDeathMessage(this, targetRef, componentAccessor); + } + + @Nonnull + @Override + public IMetaStoreImpl getMetaStore() { + return this.metaStore; + } + + public record CameraEffect(int cameraEffectIndex) { + public int getEffectIndex() { + return this.cameraEffectIndex; + } + } + + public static class CommandSource implements Damage.Source { + @Nonnull + private static final String COMMAND_NAME_UNKNOWN = "Unknown"; + @Nonnull + private final CommandSender commandSender; + @Nullable + private final String commandName; + + public CommandSource(@Nonnull CommandSender commandSender, @Nonnull AbstractCommand cmd) { + this(commandSender, cmd.getName()); + } + + public CommandSource(@Nonnull CommandSender commandSender, @Nullable String commandName) { + this.commandSender = commandSender; + this.commandName = commandName; + } + + @Nonnull + @Override + public Message getDeathMessage(@Nonnull Damage info, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor) { + return Message.translation("server.general.killedByCommand") + .param("displayName", this.commandSender.getDisplayName()) + .param("commandName", this.commandName != null ? this.commandName : "Unknown"); + } + } + + public static class EntitySource implements Damage.Source { + @Nonnull + protected final Ref sourceRef; + + public EntitySource(@Nonnull Ref sourceRef) { + this.sourceRef = sourceRef; + } + + @Nonnull + public Ref getRef() { + return this.sourceRef; + } + + @Nonnull + @Override + public Message getDeathMessage(@Nonnull Damage info, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor) { + Message damageCauseMessage = Damage.MESSAGE_GENERAL_DAMAGE_CAUSE_UNKNOWN; + DisplayNameComponent displayNameComponent = componentAccessor.getComponent(this.sourceRef, DisplayNameComponent.getComponentType()); + if (displayNameComponent != null) { + Message displayName = displayNameComponent.getDisplayName(); + if (displayName != null) { + damageCauseMessage = displayName; + } + } + + return Message.translation("server.general.killedBy").param("damageSource", damageCauseMessage); + } + } + + public static class EnvironmentSource implements Damage.Source { + @Nonnull + private final String type; + + public EnvironmentSource(@Nonnull String type) { + this.type = type; + } + + @Nonnull + public String getType() { + return this.type; + } + + @Nonnull + @Override + public Message getDeathMessage(@Nonnull Damage info, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor) { + return Message.translation("server.general.killedBy").param("damageSource", this.type); + } + } + + public static class Particles { + @Nullable + protected ModelParticle[] modelParticles; + @Nullable + protected WorldParticle[] worldParticles; + protected double viewDistance; + + public Particles(@Nullable ModelParticle[] modelParticles, @Nullable WorldParticle[] worldParticles, double viewDistance) { + this.modelParticles = modelParticles; + this.worldParticles = worldParticles; + this.viewDistance = viewDistance; + } + + @Nullable + public ModelParticle[] getModelParticles() { + return this.modelParticles; + } + + public void setModelParticles(@Nullable ModelParticle[] modelParticles) { + this.modelParticles = modelParticles; + } + + @Nullable + public WorldParticle[] getWorldParticles() { + return this.worldParticles; + } + + public void setWorldParticles(@Nullable WorldParticle[] worldParticles) { + this.worldParticles = worldParticles; + } + + public double getViewDistance() { + return this.viewDistance; + } + + public void setViewDistance(double viewDistance) { + this.viewDistance = viewDistance; + } + } + + public static class ProjectileSource extends Damage.EntitySource { + @Nonnull + protected final Ref projectile; + + public ProjectileSource(@Nonnull Ref shooter, @Nonnull Ref projectile) { + super(shooter); + this.projectile = projectile; + } + + @Nonnull + public Ref getProjectile() { + return this.projectile; + } + } + + public static class SoundEffect { + private int soundEventIndex; + + public SoundEffect(int soundEventIndex) { + this.soundEventIndex = soundEventIndex; + } + + public void setSoundEventIndex(int soundEventIndex) { + this.soundEventIndex = soundEventIndex; + } + + public int getSoundEventIndex() { + return this.soundEventIndex; + } + } + + public interface Source { + @Nonnull + default Message getDeathMessage(@Nonnull Damage info, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor) { + DamageCause damageCauseAsset = DamageCause.getAssetMap().getAsset(info.damageCauseIndex); + String causeId = damageCauseAsset != null ? damageCauseAsset.getId().toLowerCase(Locale.ROOT) : "unknown"; + Message damageCauseMessage = Message.translation("server.general.damageCauses." + causeId); + return Message.translation("server.general.killedBy").param("damageSource", damageCauseMessage); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/damage/DamageCalculatorSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/damage/DamageCalculatorSystems.java new file mode 100644 index 0000000..0a93e7f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/damage/DamageCalculatorSystems.java @@ -0,0 +1,185 @@ +package com.hypixel.hytale.server.core.modules.entity.damage; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.dependency.SystemGroupDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.server.core.asset.type.gameplay.BrokenPenalties; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.meta.MetaKey; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.DamageEntityInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.DamageCalculator; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DamageCalculatorSystems { + @Nonnull + public static MetaKey DAMAGE_SEQUENCE = Damage.META_REGISTRY.registerMetaObject(); + + public DamageCalculatorSystems() { + } + + @Nonnull + public static Damage[] queueDamageCalculator( + @Nonnull World world, + @Nonnull Object2FloatMap relativeDamage, + @Nonnull Ref ref, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage.Source source, + @Nullable ItemStack itemInHand + ) { + Damage[] results = new Damage[relativeDamage.size()]; + int offset = 0; + + for (DamageCause damageCause : relativeDamage.keySet()) { + float damageAmount = relativeDamage.getFloat(damageCause); + if (damageCause == DamageCause.PHYSICAL && itemInHand != null && itemInHand.isBroken()) { + BrokenPenalties brokenPenalties = world.getGameplayConfig().getItemDurabilityConfig().getBrokenPenalties(); + damageAmount *= (float)(1.0 - brokenPenalties.getWeapon(0.0)); + } + + Damage damage = new Damage(source, damageCause, damageAmount); + damage.getMetaStore().putMetaObject(Damage.CAN_BE_PREDICTED, Boolean.TRUE); + results[offset++] = damage; + } + + return results; + } + + public static class DamageSequence { + @Nonnull + private final DamageCalculatorSystems.Sequence sequence; + @Nonnull + private final DamageCalculator damageCalculator; + @Nullable + private DamageEntityInteraction.EntityStatOnHit[] entityStatOnHit; + + public DamageSequence(@Nonnull DamageCalculatorSystems.Sequence sequence, @Nonnull DamageCalculator damageCalculator) { + this.sequence = sequence; + this.damageCalculator = damageCalculator; + } + + public int getSequentialHits() { + return this.sequence.hits; + } + + public void addSequentialHit() { + this.sequence.hits++; + } + + @Nonnull + public DamageCalculator getDamageCalculator() { + return this.damageCalculator; + } + + @Nullable + public DamageEntityInteraction.EntityStatOnHit[] getEntityStatOnHit() { + return this.entityStatOnHit; + } + + public void setEntityStatOnHit(@Nullable DamageEntityInteraction.EntityStatOnHit[] entityStatOnHit) { + this.entityStatOnHit = entityStatOnHit; + } + } + + public static class Sequence { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + DamageCalculatorSystems.Sequence.class, DamageCalculatorSystems.Sequence::new + ) + .append(new KeyedCodec<>("Hits", Codec.INTEGER), (entityEffect, x) -> entityEffect.hits = x, entityEffect -> entityEffect.hits) + .add() + .build(); + private int hits; + + public Sequence() { + } + + public Sequence(int hits) { + this.hits = hits; + } + + public int getHits() { + return this.hits; + } + + @Nonnull + @Override + public String toString() { + return "Sequence{hits=" + this.hits + "}"; + } + } + + public static class SequenceModifier extends DamageEventSystem { + @Nonnull + private final Set> dependencies = Set.of( + new SystemGroupDependency<>(Order.AFTER, DamageModule.get().getGatherDamageGroup()), + new SystemGroupDependency<>(Order.AFTER, DamageModule.get().getFilterDamageGroup()), + new SystemDependency(Order.BEFORE, DamageSystems.ApplyDamage.class) + ); + + public SequenceModifier() { + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Nonnull + @Override + public Query getQuery() { + return Archetype.empty(); + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + if (!(damage.getAmount() <= 0.0F)) { + DamageCalculatorSystems.DamageSequence damageSequence = damage.getIfPresentMetaObject(DamageCalculatorSystems.DAMAGE_SEQUENCE); + if (damageSequence != null) { + DamageCalculator damageCalculator = damageSequence.getDamageCalculator(); + if (damageSequence.getSequentialHits() > 0) { + float sequentialModifier = Math.max( + 1.0F - damageCalculator.getSequentialModifierStep() * damageSequence.getSequentialHits(), damageCalculator.getSequentialModifierMinimum() + ); + damage.setAmount(damage.getAmount() * sequentialModifier); + } + + damageSequence.addSequentialHit(); + DamageEntityInteraction.EntityStatOnHit[] entityStatsOnHit = damageSequence.getEntityStatOnHit(); + if (entityStatsOnHit != null && damage.getSource() instanceof Damage.EntitySource entitySource) { + Ref attackerRef = entitySource.getRef(); + EntityStatMap entityStatMapComponent = commandBuffer.getComponent(attackerRef, EntityStatMap.getComponentType()); + if (entityStatMapComponent == null) { + return; + } + + for (DamageEntityInteraction.EntityStatOnHit statOnHit : entityStatsOnHit) { + statOnHit.processEntityStatsOnHit(damageSequence.getSequentialHits(), entityStatMapComponent); + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/damage/DamageCause.java b/src/com/hypixel/hytale/server/core/modules/entity/damage/DamageCause.java new file mode 100644 index 0000000..f5c7182 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/damage/DamageCause.java @@ -0,0 +1,143 @@ +package com.hypixel.hytale.server.core.modules.entity.damage; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DamageCause implements JsonAssetWithMap> { + @Nonnull + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + DamageCause.class, + DamageCause::new, + Codec.STRING, + (damageCause, s) -> damageCause.id = s, + damageCause -> damageCause.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .append(new KeyedCodec<>("Inherits", Codec.STRING), (builder, name) -> builder.inherits = name, builder -> builder.inherits) + .add() + .append(new KeyedCodec<>("DurabilityLoss", Codec.BOOLEAN), (builder, name) -> builder.durabilityLoss = name, builder -> builder.durabilityLoss) + .add() + .append(new KeyedCodec<>("StaminaLoss", Codec.BOOLEAN), (builder, name) -> builder.staminaLoss = name, builder -> builder.staminaLoss) + .add() + .append(new KeyedCodec<>("BypassResistances", Codec.BOOLEAN), (builder, name) -> builder.bypassResistances = name, builder -> builder.bypassResistances) + .add() + .append(new KeyedCodec<>("DamageTextColor", Codec.STRING), (builder, name) -> builder.damageTextColor = name, builder -> builder.damageTextColor) + .add() + .append(new KeyedCodec<>("AnimationId", Codec.STRING), (builder, name) -> builder.animationId = name, builder -> builder.animationId) + .add() + .append(new KeyedCodec<>("DeathAnimationId", Codec.STRING), (builder, name) -> builder.deathAnimationId = name, builder -> builder.deathAnimationId) + .add() + .build(); + private static AssetStore> ASSET_STORE; + @Nonnull + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(DamageCause::getAssetStore)); + @Nonnull + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(DamageCause.class, CODEC); + @Nullable + @Deprecated + public static DamageCause PHYSICAL; + @Nullable + @Deprecated + public static DamageCause PROJECTILE; + @Nullable + @Deprecated + public static DamageCause COMMAND; + @Nullable + @Deprecated + public static DamageCause DROWNING; + @Nullable + @Deprecated + public static DamageCause ENVIRONMENT; + @Nullable + @Deprecated + public static DamageCause FALL; + @Nullable + @Deprecated + public static DamageCause OUT_OF_WORLD; + @Nullable + @Deprecated + public static DamageCause SUFFOCATION; + protected AssetExtraInfo.Data data; + protected String id; + protected String inherits; + protected boolean durabilityLoss; + protected boolean staminaLoss; + protected boolean bypassResistances; + protected String damageTextColor; + protected String animationId = "Hurt"; + protected String deathAnimationId = "Death"; + + @Nonnull + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(DamageCause.class); + } + + return ASSET_STORE; + } + + @Nonnull + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public DamageCause() { + } + + public DamageCause(@Nonnull String id) { + this.id = id; + } + + public DamageCause(@Nonnull String id, @Nonnull String inherits, boolean durabilityLoss, boolean staminaLoss, boolean bypassResistances) { + this.id = id; + this.inherits = inherits; + this.durabilityLoss = durabilityLoss; + this.staminaLoss = staminaLoss; + this.bypassResistances = bypassResistances; + } + + public String getId() { + return this.id; + } + + public boolean isDurabilityLoss() { + return this.durabilityLoss; + } + + public boolean isStaminaLoss() { + return this.staminaLoss; + } + + public boolean doesBypassResistances() { + return this.bypassResistances; + } + + public String getInherits() { + return this.inherits; + } + + public String getAnimationId() { + return this.animationId; + } + + public String getDeathAnimationId() { + return this.deathAnimationId; + } + + @Nonnull + public com.hypixel.hytale.protocol.DamageCause toPacket() { + return new com.hypixel.hytale.protocol.DamageCause(this.id, this.damageTextColor); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/damage/DamageEventSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/damage/DamageEventSystem.java new file mode 100644 index 0000000..3016adc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/damage/DamageEventSystem.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.core.modules.entity.damage; + +import com.hypixel.hytale.component.system.EntityEventSystem; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public abstract class DamageEventSystem extends EntityEventSystem { + protected DamageEventSystem() { + super(Damage.class); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/damage/DamageModule.java b/src/com/hypixel/hytale/server/core/modules/entity/damage/DamageModule.java new file mode 100644 index 0000000..14797cf --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/damage/DamageModule.java @@ -0,0 +1,141 @@ +package com.hypixel.hytale.server.core.modules.entity.damage; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemGroupDependency; +import com.hypixel.hytale.component.system.ISystem; +import com.hypixel.hytale.server.core.entity.knockback.KnockbackSystems; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.damage.commands.DesyncDamageCommand; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.modules.entityui.EntityUIModule; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import javax.annotation.Nonnull; + +public class DamageModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(DamageModule.class) + .depends(EntityModule.class) + .depends(EntityStatsModule.class) + .depends(EntityUIModule.class) + .build(); + private static DamageModule instance; + private ComponentType deathComponentType; + private ComponentType deferredCorpseRemovalComponentType; + private SystemGroup gatherDamageGroup; + private SystemGroup filterDamageGroup; + private SystemGroup inspectDamageGroup; + + public static DamageModule get() { + return instance; + } + + public DamageModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + ComponentRegistryProxy entityStoreRegistry = this.getEntityStoreRegistry(); + this.deathComponentType = entityStoreRegistry.registerComponent(DeathComponent.class, "Death", DeathComponent.CODEC); + this.deferredCorpseRemovalComponentType = entityStoreRegistry.registerComponent(DeferredCorpseRemoval.class, () -> { + throw new UnsupportedOperationException("not supported"); + }); + this.gatherDamageGroup = entityStoreRegistry.registerSystemGroup(); + this.filterDamageGroup = entityStoreRegistry.registerSystemGroup(); + this.inspectDamageGroup = entityStoreRegistry.registerSystemGroup(); + entityStoreRegistry.registerSystem(new DamageModule.OrderGatherFilter()); + entityStoreRegistry.registerSystem(new DamageSystems.ApplyDamage()); + entityStoreRegistry.registerSystem(new DamageSystems.CanBreathe()); + entityStoreRegistry.registerSystem(new DamageSystems.OutOfWorldDamage()); + entityStoreRegistry.registerSystem(new DamageSystems.FallDamagePlayers()); + entityStoreRegistry.registerSystem(new DamageSystems.FallDamageNPCs()); + entityStoreRegistry.registerSystem(new DamageSystems.FilterPlayerWorldConfig()); + entityStoreRegistry.registerSystem(new DamageSystems.FilterNPCWorldConfig()); + entityStoreRegistry.registerSystem(new DamageSystems.FilterUnkillable()); + entityStoreRegistry.registerSystem(new DamageSystems.PlayerDamageFilterSystem()); + entityStoreRegistry.registerSystem(new DamageSystems.WieldingDamageReduction()); + entityStoreRegistry.registerSystem(new DamageSystems.WieldingKnockbackReduction()); + entityStoreRegistry.registerSystem(new DamageSystems.ArmorKnockbackReduction()); + entityStoreRegistry.registerSystem(new DamageSystems.ArmorDamageReduction()); + entityStoreRegistry.registerSystem(new DamageSystems.HackKnockbackValues()); + entityStoreRegistry.registerSystem(new DamageSystems.RecordLastCombat()); + entityStoreRegistry.registerSystem(new DamageSystems.ApplyParticles()); + entityStoreRegistry.registerSystem(new DamageSystems.ApplySoundEffects()); + entityStoreRegistry.registerSystem(new DamageSystems.HitAnimation()); + entityStoreRegistry.registerSystem(new DamageSystems.TrackLastDamage()); + entityStoreRegistry.registerSystem(new DamageSystems.DamageArmor()); + entityStoreRegistry.registerSystem(new DamageSystems.DamageStamina()); + entityStoreRegistry.registerSystem(new DamageSystems.DamageAttackerTool()); + entityStoreRegistry.registerSystem(new DamageSystems.PlayerHitIndicators()); + entityStoreRegistry.registerSystem(new DamageSystems.ReticleEvents()); + entityStoreRegistry.registerSystem(new DamageSystems.EntityUIEvents()); + entityStoreRegistry.registerSystem(new KnockbackSystems.ApplyKnockback()); + entityStoreRegistry.registerSystem(new KnockbackSystems.ApplyPlayerKnockback()); + entityStoreRegistry.registerSystem(new DeathSystems.ClearHealth()); + entityStoreRegistry.registerSystem(new DeathSystems.ClearInteractions()); + entityStoreRegistry.registerSystem(new DeathSystems.ClearEntityEffects()); + entityStoreRegistry.registerSystem(new DeathSystems.PlayerKilledPlayer()); + entityStoreRegistry.registerSystem(new DeathSystems.DropPlayerDeathItems()); + entityStoreRegistry.registerSystem(new DeathSystems.PlayerDropItemsConfig()); + entityStoreRegistry.registerSystem(new DeathSystems.RunDeathInteractions()); + entityStoreRegistry.registerSystem(new DeathSystems.KillFeed()); + entityStoreRegistry.registerSystem(new DeathSystems.PlayerDeathScreen()); + entityStoreRegistry.registerSystem(new DeathSystems.PlayerDeathMarker()); + entityStoreRegistry.registerSystem(new DeathSystems.CorpseRemoval()); + entityStoreRegistry.registerSystem(new DeathSystems.DeathAnimation()); + entityStoreRegistry.registerSystem(new DeathSystems.SpawnedDeathAnimation()); + entityStoreRegistry.registerSystem(new RespawnSystems.ResetStatsRespawnSystem()); + entityStoreRegistry.registerSystem(new RespawnSystems.ResetPlayerRespawnSystem()); + entityStoreRegistry.registerSystem(new RespawnSystems.ClearEntityEffectsRespawnSystem()); + entityStoreRegistry.registerSystem(new RespawnSystems.ClearInteractionsRespawnSystem()); + entityStoreRegistry.registerSystem(new RespawnSystems.RespawnControllerRespawnSystem()); + entityStoreRegistry.registerSystem(new RespawnSystems.CheckBrokenItemsRespawnSystem()); + entityStoreRegistry.registerSystem(new DamageCalculatorSystems.SequenceModifier()); + this.getCommandRegistry().registerCommand(new DesyncDamageCommand()); + } + + public ComponentType getDeathComponentType() { + return this.deathComponentType; + } + + public ComponentType getDeferredCorpseRemovalComponentType() { + return this.deferredCorpseRemovalComponentType; + } + + public SystemGroup getGatherDamageGroup() { + return this.gatherDamageGroup; + } + + public SystemGroup getFilterDamageGroup() { + return this.filterDamageGroup; + } + + public SystemGroup getInspectDamageGroup() { + return this.inspectDamageGroup; + } + + @Deprecated + public static class OrderGatherFilter implements ISystem { + private final Set> dependencies = Set.of( + new SystemGroupDependency<>(Order.AFTER, DamageModule.get().getGatherDamageGroup()), + new SystemGroupDependency<>(Order.BEFORE, DamageModule.get().getFilterDamageGroup()) + ); + + public OrderGatherFilter() { + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/damage/DamageSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/damage/DamageSystems.java new file mode 100644 index 0000000..6727dfc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/damage/DamageSystems.java @@ -0,0 +1,1792 @@ +package com.hypixel.hytale.server.core.modules.entity.damage; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.dependency.SystemGroupDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.spatial.SpatialStructure; +import com.hypixel.hytale.component.system.tick.DelayedEntitySystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector4d; +import com.hypixel.hytale.protocol.AnimationSlot; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.protocol.CombatTextUpdate; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.entities.SpawnModelParticles; +import com.hypixel.hytale.protocol.packets.player.DamageInfo; +import com.hypixel.hytale.protocol.packets.player.ReticleEvent; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.asset.type.gameplay.BrokenPenalties; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.gameplay.PlayerConfig; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemArmor; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelParticle; +import com.hypixel.hytale.server.core.asset.type.particle.config.WorldParticle; +import com.hypixel.hytale.server.core.entity.AnimationUtils; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.entity.damage.DamageDataComponent; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementConfig; +import com.hypixel.hytale.server.core.entity.knockback.KnockbackComponent; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.meta.DynamicMetaStore; +import com.hypixel.hytale.server.core.modules.entity.AllLegacyLivingEntityTypesQuery; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.Intangible; +import com.hypixel.hytale.server.core.modules.entity.component.Invulnerable; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerInput; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSystems; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsSystems; +import com.hypixel.hytale.server.core.modules.entitystats.asset.DefaultEntityStatTypes; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.StaticModifier; +import com.hypixel.hytale.server.core.modules.entityui.EntityUIModule; +import com.hypixel.hytale.server.core.modules.entityui.UIComponentList; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.WieldingInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.DamageEffects; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.modules.splitvelocity.SplitVelocity; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.ParticleUtil; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2DoubleMap; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectList; +import it.unimi.dsi.fastutil.shorts.ShortArrayList; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.Map.Entry; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bouncycastle.util.Arrays; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class DamageSystems { + public static final float DEFAULT_DAMAGE_DELAY = 1.0F; + private static final Query NPCS_QUERY = Query.and( + AllLegacyLivingEntityTypesQuery.INSTANCE, + EntityStatMap.getComponentType(), + MovementStatesComponent.getComponentType(), + Query.not(EntityModule.get().getPlayerComponentType()) + ); + + public DamageSystems() { + } + + public static void executeDamage(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor, @Nonnull Damage damage) { + componentAccessor.invoke(ref, damage); + } + + public static void executeDamage( + int index, @Nonnull ArchetypeChunk chunk, @Nonnull CommandBuffer commandBuffer, @Nonnull Damage damage + ) { + commandBuffer.invoke(chunk.getReferenceTo(index), damage); + } + + public static void executeDamage(@Nonnull Ref ref, @Nonnull CommandBuffer commandBuffer, @Nonnull Damage damage) { + commandBuffer.invoke(ref, damage); + } + + public static class ApplyDamage extends DamageEventSystem implements EntityStatsSystems.StatModifyingSystem { + @Nonnull + private static final Query QUERY = EntityStatMap.getComponentType(); + @Nonnull + private static final Set> DEPENDENCIES = Set.of( + new SystemGroupDependency<>(Order.AFTER, DamageModule.get().getGatherDamageGroup()), + new SystemGroupDependency<>(Order.AFTER, DamageModule.get().getFilterDamageGroup()), + new SystemGroupDependency<>(Order.BEFORE, DamageModule.get().getInspectDamageGroup()) + ); + + public ApplyDamage() { + } + + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + EntityStatMap entityStatMapComponent = archetypeChunk.getComponent(index, EntityStatMap.getComponentType()); + + assert entityStatMapComponent != null; + + int healthStat = DefaultEntityStatTypes.getHealth(); + EntityStatValue healthValue = entityStatMapComponent.get(healthStat); + Objects.requireNonNull(healthValue); + boolean isDead = archetypeChunk.getArchetype().contains(DeathComponent.getComponentType()); + if (isDead) { + damage.setCancelled(true); + } else { + damage.setAmount(Math.round(damage.getAmount())); + float newValue = entityStatMapComponent.subtractStatValue(healthStat, damage.getAmount()); + if (newValue <= healthValue.getMin()) { + DeathComponent.tryAddComponent(commandBuffer, archetypeChunk.getReferenceTo(index), damage); + } + } + } + } + + public static class ApplyParticles extends DamageEventSystem { + @Nonnull + private static final ResourceType, EntityStore>> PLAYER_SPATIAL_RESOURCE_TYPE = EntityModule.get() + .getPlayerSpatialResourceType(); + @Nonnull + private static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + @Nonnull + private static final Query QUERY = TRANSFORM_COMPONENT_TYPE; + + public ApplyParticles() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + TransformComponent transformComponent = archetypeChunk.getComponent(index, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + NetworkId networkIdComponent = archetypeChunk.getComponent(index, NetworkId.getComponentType()); + + assert networkIdComponent != null; + + int targetNetworkId = networkIdComponent.getId(); + Damage.Particles particles = damage.getIfPresentMetaObject(Damage.IMPACT_PARTICLES); + if (particles != null) { + if (damage.getSource() instanceof Damage.EntitySource sourceEntity) { + Ref sourceRef = sourceEntity.getRef(); + if (sourceRef.isValid()) { + Vector4d hitLocation = damage.getIfPresentMetaObject(Damage.HIT_LOCATION); + Vector3d targetPosition = hitLocation == null ? transformComponent.getPosition() : new Vector3d(hitLocation.x, hitLocation.y, hitLocation.z); + boolean damageCanBePredicted = damage.getMetaStore().getMetaObject(Damage.CAN_BE_PREDICTED); + double particlesViewDistance = particles.getViewDistance(); + WorldParticle[] worldParticles = particles.getWorldParticles(); + if (!Arrays.isNullOrEmpty((Object[])worldParticles)) { + TransformComponent sourceTransformComponent = commandBuffer.getComponent(sourceRef, TransformComponent.getComponentType()); + + assert sourceTransformComponent != null; + + float angleBetween = TrigMathUtil.atan2( + sourceTransformComponent.getPosition().x - targetPosition.x, sourceTransformComponent.getPosition().z - targetPosition.z + ); + SpatialResource, EntityStore> playerSpatialResource = commandBuffer.getResource( + EntityModule.get().getPlayerSpatialResourceType() + ); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(targetPosition, particlesViewDistance, results); + Ref particleSource = damageCanBePredicted ? sourceRef : null; + + for (WorldParticle particle : worldParticles) { + ParticleUtil.spawnParticleEffect(particle, targetPosition, angleBetween, 0.0F, 0.0F, particleSource, results, commandBuffer); + } + } + + ModelParticle[] modelParticles = particles.getModelParticles(); + if (!Arrays.isNullOrEmpty((Object[])modelParticles)) { + com.hypixel.hytale.protocol.ModelParticle[] modelParticlesProtocol = new com.hypixel.hytale.protocol.ModelParticle[modelParticles.length]; + + for (int j = 0; j < modelParticles.length; j++) { + modelParticlesProtocol[j] = modelParticles[j].toPacket(); + } + + SpawnModelParticles packet = new SpawnModelParticles(targetNetworkId, modelParticlesProtocol); + SpatialResource, EntityStore> spatialResource = store.getResource(PLAYER_SPATIAL_RESOURCE_TYPE); + SpatialStructure> spatialStructure = spatialResource.getSpatialStructure(); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + spatialStructure.ordered(targetPosition, particlesViewDistance, results); + + for (Ref targetRef : results) { + if (damageCanBePredicted && targetRef.equals(sourceRef)) { + return; + } + + PlayerRef playerRefComponent = commandBuffer.getComponent(targetRef, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerRefComponent.getPacketHandler().write(packet); + } + } + } + } + } + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getInspectDamageGroup(); + } + } + + public static class ApplySoundEffects extends DamageEventSystem { + @Nonnull + private static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + @Nonnull + private static final ComponentType PLAYER_COMPONENT_TYPE = Player.getComponentType(); + @Nonnull + private static final Query QUERY = TRANSFORM_COMPONENT_TYPE; + + public ApplySoundEffects() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + } + + public void handleInternal( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + TransformComponent transformComponent = archetypeChunk.getComponent(index, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + Player playerComponent = archetypeChunk.getComponent(index, PLAYER_COMPONENT_TYPE); + Ref ref = archetypeChunk.getReferenceTo(index); + Damage.SoundEffect soundEffect = damage.getIfPresentMetaObject(Damage.IMPACT_SOUND_EFFECT); + Damage.SoundEffect playerSoundEffect = damage.getIfPresentMetaObject(Damage.PLAYER_IMPACT_SOUND_EFFECT); + if (soundEffect != null || playerSoundEffect != null) { + Ref sourceRef; + if (damage.getSource() instanceof Damage.EntitySource source) { + sourceRef = source.getRef().isValid() ? source.getRef() : null; + } else { + sourceRef = null; + } + + Vector4d hitLocation = damage.getIfPresentMetaObject(Damage.HIT_LOCATION); + Vector3d targetPosition = hitLocation == null ? transformComponent.getPosition() : new Vector3d(hitLocation.x, hitLocation.y, hitLocation.z); + if (soundEffect != null && soundEffect.getSoundEventIndex() != 0) { + Predicate> filter = sourceRef != null ? p -> !p.equals(sourceRef) : p -> true; + SoundUtil.playSoundEvent3d(soundEffect.getSoundEventIndex(), targetPosition.x, targetPosition.y, targetPosition.z, filter, commandBuffer); + } + + if (playerComponent != null && playerSoundEffect != null && playerSoundEffect.getSoundEventIndex() != 0) { + SoundUtil.playSoundEvent3dToPlayer( + ref, playerSoundEffect.getSoundEventIndex(), SoundCategory.SFX, targetPosition.x, targetPosition.y, targetPosition.z, commandBuffer + ); + } + } + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getInspectDamageGroup(); + } + } + + @Deprecated + public static class ArmorDamageReduction extends DamageEventSystem { + @Nonnull + private static final Query QUERY = AllLegacyLivingEntityTypesQuery.INSTANCE; + + public ArmorDamageReduction() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getFilterDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + LivingEntity entity = (LivingEntity)EntityUtils.getEntity(index, archetypeChunk); + + assert entity != null; + + World world = commandBuffer.getExternalData().getWorld(); + Ref ref = archetypeChunk.getReferenceTo(index); + Map resistances = getResistanceModifiers( + world, + entity.getInventory().getArmor(), + entity.canApplyItemStackPenalties(ref, commandBuffer), + archetypeChunk.getComponent(index, EffectControllerComponent.getComponentType()) + ); + if (!damage.getCause().doesBypassResistances() && !resistances.isEmpty()) { + DamageSystems.ArmorDamageReduction.ArmorResistanceModifiers damageModEntry = resistances.get(damage.getCause()); + if (damageModEntry == null) { + return; + } + + float amount = Math.max(0.0F, damage.getAmount() - damageModEntry.flatModifier); + amount *= Math.max(0.0F, 1.0F - damageModEntry.multiplierModifier); + + while (damageModEntry.inheritedParentId != null) { + damageModEntry = resistances.get(damageModEntry.inheritedParentId); + if (damageModEntry == null) { + break; + } + + amount = Math.max(0.0F, damage.getAmount() - damageModEntry.flatModifier); + amount *= Math.max(0.0F, 1.0F - damageModEntry.multiplierModifier); + } + + damage.setAmount(amount); + } + } + + @Nonnull + public static Map getResistanceModifiers( + @Nonnull World world, + @Nonnull ItemContainer inventory, + boolean canApplyItemStackPenalties, + @Nullable EffectControllerComponent effectControllerComponent + ) { + Map result = new Object2ObjectOpenHashMap<>(); + + for (short index = 0; index < inventory.getCapacity(); index++) { + ItemStack itemStack = inventory.getItemStack(index); + if (itemStack != null && !itemStack.isEmpty()) { + Item item = itemStack.getItem(); + ItemArmor itemArmor = item.getArmor(); + if (itemArmor != null) { + Map resistances = itemArmor.getDamageResistanceValues(); + double flatResistance = itemArmor.getBaseDamageResistance(); + if (resistances != null) { + for (Entry entry : resistances.entrySet()) { + if (entry.getValue() != null) { + calculateResistanceEntryModifications(entry, world, result, canApplyItemStackPenalties, itemStack.isBroken(), flatResistance); + } + } + } + } + } + } + + addResistanceModifiersFromEntityEffects(result, effectControllerComponent); + return result; + } + + private static void calculateResistanceEntryModifications( + @Nonnull Entry entry, + @Nonnull World world, + @Nonnull Map result, + boolean canApplyItemStackPenalties, + boolean itemStackIsBroken, + double flatResistance + ) { + DamageSystems.ArmorDamageReduction.ArmorResistanceModifiers mods = result.computeIfAbsent( + entry.getKey(), key -> new DamageSystems.ArmorDamageReduction.ArmorResistanceModifiers() + ); + StaticModifier[] valueArray = entry.getValue(); + + for (int x = 0; x < valueArray.length; x++) { + StaticModifier entryValue = valueArray[x]; + if (entryValue.getCalculationType() == StaticModifier.CalculationType.ADDITIVE) { + mods.flatModifier = (int)(mods.flatModifier + entryValue.getAmount()); + } else { + mods.multiplierModifier = mods.multiplierModifier + entryValue.getAmount(); + } + } + + mods.flatModifier = (int)(mods.flatModifier + flatResistance); + DamageCause damageCause = entry.getKey(); + if (damageCause != null && damageCause.getInherits() != null) { + mods.inheritedParentId = DamageCause.getAssetMap().getAsset(damageCause.getInherits()); + } + + if (canApplyItemStackPenalties && itemStackIsBroken) { + BrokenPenalties brokenPenalties = world.getGameplayConfig().getItemDurabilityConfig().getBrokenPenalties(); + double penalty = brokenPenalties.getWeapon(0.0); + mods.flatModifier = (int)(mods.flatModifier * (1.0 - penalty)); + mods.multiplierModifier = (float)(mods.multiplierModifier * (1.0 - penalty)); + } + } + + private static void addResistanceModifiersFromEntityEffects( + Map resistanceModifiers, EffectControllerComponent effectControllerComponent + ) { + if (effectControllerComponent != null) { + for (int entityEffectIndex : effectControllerComponent.getActiveEffects().keySet()) { + EntityEffect entityEffectData = EntityEffect.getAssetMap().getAsset(entityEffectIndex); + if (entityEffectData != null) { + Map damageResistanceValues = entityEffectData.getDamageResistanceValues(); + if (damageResistanceValues != null && !damageResistanceValues.isEmpty()) { + for (Entry entry : damageResistanceValues.entrySet()) { + DamageSystems.ArmorDamageReduction.ArmorResistanceModifiers modifier = resistanceModifiers.computeIfAbsent( + entry.getKey(), damageCause -> new DamageSystems.ArmorDamageReduction.ArmorResistanceModifiers() + ); + + for (StaticModifier staticModifier : entry.getValue()) { + if (staticModifier.getCalculationType() == StaticModifier.CalculationType.ADDITIVE) { + modifier.flatModifier = (int)(modifier.flatModifier + staticModifier.getAmount()); + } else if (staticModifier.getCalculationType() == StaticModifier.CalculationType.MULTIPLICATIVE) { + modifier.multiplierModifier = modifier.multiplierModifier + staticModifier.getAmount(); + } + } + } + } + } + } + } + } + + public static class ArmorResistanceModifiers { + public int flatModifier; + public float multiplierModifier; + @Nullable + public DamageCause inheritedParentId; + + public ArmorResistanceModifiers() { + } + } + } + + @Deprecated + public static class ArmorKnockbackReduction extends DamageEventSystem { + @Nonnull + private static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + @Nonnull + private static final Query QUERY = Query.and( + AllLegacyLivingEntityTypesQuery.INSTANCE, DamageDataComponent.getComponentType(), TRANSFORM_COMPONENT_TYPE + ); + + public ArmorKnockbackReduction() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getFilterDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage event + ) { + } + + public void handleInternal( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + LivingEntity entity = (LivingEntity)EntityUtils.getEntity(index, archetypeChunk); + + assert entity != null; + + Inventory inventory = entity.getInventory(); + if (inventory != null) { + ItemContainer armorContainer = inventory.getArmor(); + if (armorContainer != null) { + KnockbackComponent knockbackComponent = damage.getIfPresentMetaObject(Damage.KNOCKBACK_COMPONENT); + if (knockbackComponent != null) { + float knockbackResistanceModifier = 0.0F; + + for (short i = 0; i < armorContainer.getCapacity(); i++) { + ItemStack itemStack = armorContainer.getItemStack(i); + if (itemStack != null && !itemStack.isEmpty()) { + Item item = itemStack.getItem(); + ItemArmor itemArmor = item.getArmor(); + if (itemArmor != null) { + Map knockbackResistances = itemArmor.getKnockbackResistances(); + if (knockbackResistances != null) { + DamageCause damageCause = damage.getCause(); + knockbackResistanceModifier += knockbackResistances.get(damageCause); + } + } + } + } + + knockbackComponent.addModifier(Math.max(1.0F - knockbackResistanceModifier, 0.0F)); + } + } + } + } + } + + public static class CanBreathe extends DelayedEntitySystem { + private static final float DAMAGE_AMOUNT_DROWNING = 10.0F; + private static final float DAMAGE_AMOUNT_SUFFOCATION = 20.0F; + @Nonnull + private static final ComponentType MODEL_COMPONENT_TYPE = ModelComponent.getComponentType(); + @Nonnull + private static final Query QUERY = Query.and( + AllLegacyLivingEntityTypesQuery.INSTANCE, EntityStatMap.getComponentType(), TransformComponent.getComponentType(), MODEL_COMPONENT_TYPE + ); + + public CanBreathe() { + super(1.0F); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getGatherDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + LivingEntity entity = (LivingEntity)EntityUtils.getEntity(index, archetypeChunk); + + assert entity != null; + + EntityStatMap statMapComponent = archetypeChunk.getComponent(index, EntityStatMap.getComponentType()); + + assert statMapComponent != null; + + EntityStatValue oxygenStatValue = statMapComponent.get(DefaultEntityStatTypes.getOxygen()); + if (oxygenStatValue != null) { + Ref ref = archetypeChunk.getReferenceTo(index); + long packed = LivingEntity.getPackedMaterialAndFluidAtBreathingHeight(ref, commandBuffer); + BlockMaterial material = BlockMaterial.VALUES[MathUtil.unpackLeft(packed)]; + int fluidId = MathUtil.unpackRight(packed); + if (!entity.canBreathe(ref, material, fluidId, commandBuffer) && oxygenStatValue.get() <= oxygenStatValue.getMin()) { + Damage damage; + if (fluidId != 0) { + assert DamageCause.DROWNING != null; + + damage = new Damage(Damage.NULL_SOURCE, DamageCause.DROWNING, 10.0F); + } else { + assert DamageCause.SUFFOCATION != null; + + damage = new Damage(Damage.NULL_SOURCE, DamageCause.SUFFOCATION, 20.0F); + } + + DamageSystems.executeDamage(index, archetypeChunk, commandBuffer, damage); + } + } + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return false; + } + } + + public static class DamageArmor extends DamageEventSystem { + @Nonnull + private static final Query QUERY = AllLegacyLivingEntityTypesQuery.INSTANCE; + + public DamageArmor() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getInspectDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + LivingEntity entity = (LivingEntity)EntityUtils.getEntity(index, archetypeChunk); + + assert entity != null; + + Ref ref = archetypeChunk.getReferenceTo(index); + DamageCause damageCause = damage.getCause(); + if (damageCause.isDurabilityLoss()) { + ItemContainer armor = entity.getInventory().getArmor(); + ShortArrayList armorPartIndexes = new ShortArrayList(); + armor.forEachWithMeta((slotx, itemStack, _armorPartIndexes) -> { + if (!itemStack.isBroken()) { + _armorPartIndexes.add(slotx); + } + }, armorPartIndexes); + if (!armorPartIndexes.isEmpty()) { + int slot = armorPartIndexes.getShort(RandomExtra.randomRange(armorPartIndexes.size())); + entity.decreaseItemStackDurability(ref, armor.getItemStack((short)slot), -3, slot, commandBuffer); + } + } + } + } + + public static class DamageAttackerTool extends DamageEventSystem { + @Nonnull + private static final Query QUERY = AllLegacyLivingEntityTypesQuery.INSTANCE; + + public DamageAttackerTool() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getInspectDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + if (damage.getCause().isDurabilityLoss() && damage.getSource() instanceof Damage.EntitySource) { + Ref attackerRef = ((Damage.EntitySource)damage.getSource()).getRef(); + if (attackerRef.isValid()) { + if (EntityUtils.getEntity(attackerRef, commandBuffer) instanceof LivingEntity attackerLivingEntity) { + Inventory attackerInventory = attackerLivingEntity.getInventory(); + byte activeHotbarSlot = attackerInventory.getActiveHotbarSlot(); + if (activeHotbarSlot != -1) { + attackerLivingEntity.decreaseItemStackDurability( + attackerRef, attackerInventory.getItemInHand(), -1, attackerInventory.getActiveHotbarSlot(), commandBuffer + ); + } + } + } + } + } + } + + public static class DamageStamina extends DamageEventSystem implements EntityStatsSystems.StatModifyingSystem { + @Nonnull + private static final Query QUERY = Query.and(DamageDataComponent.getComponentType(), EntityStatMap.getComponentType()); + + public DamageStamina() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getInspectDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage event + ) { + } + + public void handleInternal( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + EntityStatMap entityStatMapComponent = archetypeChunk.getComponent(index, EntityStatMap.getComponentType()); + + assert entityStatMapComponent != null; + + DamageDataComponent damageDataComponent = archetypeChunk.getComponent(index, DamageDataComponent.getComponentType()); + + assert damageDataComponent != null; + + if (damageDataComponent.getCurrentWielding() != null) { + WieldingInteraction.StaminaCost staminaCost = damageDataComponent.getCurrentWielding().getStaminaCost(); + if (staminaCost != null) { + Boolean isBlocked = damage.getMetaStore().getIfPresentMetaObject(Damage.BLOCKED); + if (isBlocked != null && isBlocked) { + float staminaToConsume = staminaCost.computeStaminaAmountToConsume(damage.getInitialAmount(), entityStatMapComponent); + Float multiplier = damage.getIfPresentMetaObject(Damage.STAMINA_DRAIN_MULTIPLIER); + if (multiplier != null) { + staminaToConsume *= multiplier; + } + + entityStatMapComponent.subtractStatValue(DefaultEntityStatTypes.getStamina(), staminaToConsume); + } + } + } + } + } + + public static class EntityUIEvents extends DamageEventSystem { + @Nonnull + private final ComponentType visibleComponentType = EntityModule.get().getVisibleComponentType(); + @Nonnull + private final ComponentType uiComponentListComponentType = EntityUIModule.get().getUIComponentListType(); + @Nonnull + private final Query query = Query.and(this.visibleComponentType, this.uiComponentListComponentType); + + public EntityUIEvents() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getInspectDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + if (!(damage.getAmount() <= 0.0F)) { + if (damage.getSource() instanceof Damage.EntitySource) { + Ref attackerRef = ((Damage.EntitySource)damage.getSource()).getRef(); + if (attackerRef.isValid()) { + PlayerRef playerRef = commandBuffer.getComponent(attackerRef, PlayerRef.getComponentType()); + if (playerRef != null && playerRef.isValid()) { + EntityTrackerSystems.EntityViewer entityViewer = commandBuffer.getComponent( + attackerRef, EntityTrackerSystems.EntityViewer.getComponentType() + ); + + assert entityViewer != null; + + Float hitAngleDeg = damage.getIfPresentMetaObject(Damage.HIT_ANGLE); + queueUpdateFor(archetypeChunk.getReferenceTo(index), damage.getAmount(), hitAngleDeg, entityViewer); + } + } + } + } + } + + private static void queueUpdateFor( + @Nonnull Ref ref, float damageAmount, @Nullable Float hitAngleDeg, @Nonnull EntityTrackerSystems.EntityViewer viewer + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.CombatText; + CombatTextUpdate combatTextUpdate = new CombatTextUpdate(); + combatTextUpdate.hitAngleDeg = hitAngleDeg == null ? 0.0F : hitAngleDeg; + combatTextUpdate.text = Integer.toString((int)Math.floor(damageAmount)); + update.combatTextUpdate = combatTextUpdate; + viewer.queueUpdate(ref, update); + } + } + + public static class FallDamageNPCs extends EntityTickingSystem { + static final float CURVE_MODIFIER = 0.58F; + static final float CURVE_MULTIPLIER = 2.0F; + public static final double MIN_DAMAGE = 10.0; + + public FallDamageNPCs() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getGatherDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return DamageSystems.NPCS_QUERY; + } + + @Override + public void tick(float dt, int systemIndex, @NonNullDecl Store store) { + World world = store.getExternalData().getWorld(); + if (world.getWorldConfig().isFallDamageEnabled()) { + super.tick(dt, systemIndex, store); + } + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + LivingEntity entity = (LivingEntity)EntityUtils.getEntity(index, archetypeChunk); + + assert entity != null; + + MovementStatesComponent movementStatesComponent = archetypeChunk.getComponent(index, MovementStatesComponent.getComponentType()); + + assert movementStatesComponent != null; + + MovementStates movementStates = movementStatesComponent.getMovementStates(); + if (movementStates.onGround && entity.getCurrentFallDistance() > 0.0) { + Velocity velocityComponent = archetypeChunk.getComponent(index, Velocity.getComponentType()); + + assert velocityComponent != null; + + double yVelocity = Math.abs(velocityComponent.getVelocity().getY()); + World world = commandBuffer.getExternalData().getWorld(); + int movementConfigIndex = world.getGameplayConfig().getPlayerConfig().getMovementConfigIndex(); + MovementConfig movementConfig = MovementConfig.getAssetMap().getAsset(movementConfigIndex); + float minFallSpeedToEngageRoll = movementConfig.getMinFallSpeedToEngageRoll(); + if (yVelocity > minFallSpeedToEngageRoll && !movementStates.inFluid) { + EntityStatMap entityStatMapComponent = archetypeChunk.getComponent(index, EntityStatMap.getComponentType()); + + assert entityStatMapComponent != null; + + double damagePercentage = Math.pow(0.58F * (yVelocity - minFallSpeedToEngageRoll), 2.0) + 10.0; + EntityStatValue healthStatValue = entityStatMapComponent.get(DefaultEntityStatTypes.getHealth()); + + assert healthStatValue != null; + + float maxHealth = healthStatValue.getMax(); + double healthModifier = maxHealth / 100.0; + int damageInt = (int)Math.floor(healthModifier * damagePercentage); + if (movementStates.rolling) { + if (yVelocity <= movementConfig.getMaxFallSpeedRollFullMitigation()) { + damageInt = 0; + } else if (yVelocity <= movementConfig.getMaxFallSpeedToEngageRoll()) { + damageInt = (int)(damageInt * (1.0 - movementConfig.getFallDamagePartialMitigationPercent() / 100.0)); + } + } + + if (damageInt > 0) { + assert DamageCause.FALL != null; + + Damage damage = new Damage(Damage.NULL_SOURCE, DamageCause.FALL, damageInt); + DamageSystems.executeDamage(index, archetypeChunk, commandBuffer, damage); + } + } + + entity.setCurrentFallDistance(0.0); + } + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + } + + public static class FallDamagePlayers extends EntityTickingSystem { + static final float CURVE_MODIFIER = 0.58F; + static final float CURVE_MULTIPLIER = 2.0F; + public static final double MIN_DAMAGE = 10.0; + @Nonnull + private static final Query QUERY = Query.and( + EntityStatMap.getComponentType(), + MovementStatesComponent.getComponentType(), + EntityModule.get().getPlayerComponentType(), + PlayerInput.getComponentType() + ); + @Nonnull + private static final Set> DEPENDENCIES = Set.of(new SystemDependency<>(Order.BEFORE, PlayerSystems.ProcessPlayerInput.class)); + + public FallDamagePlayers() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getGatherDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Override + public void tick(float dt, int systemIndex, @NonNullDecl Store store) { + World world = store.getExternalData().getWorld(); + if (world.getWorldConfig().isFallDamageEnabled()) { + super.tick(dt, systemIndex, store); + } + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + PlayerInput playerInputComponent = archetypeChunk.getComponent(index, PlayerInput.getComponentType()); + + assert playerInputComponent != null; + + Velocity velocityComponent = archetypeChunk.getComponent(index, Velocity.getComponentType()); + + assert velocityComponent != null; + + double yVelocity = Math.abs(velocityComponent.getClientVelocity().getY()); + World world = commandBuffer.getExternalData().getWorld(); + PlayerConfig worldPlayerConfig = world.getGameplayConfig().getPlayerConfig(); + List queue = playerInputComponent.getMovementUpdateQueue(); + + for (int i = 0; i < queue.size(); i++) { + PlayerInput.InputUpdate queueEntry = queue.get(i); + switch (queueEntry) { + case PlayerInput.SetClientVelocity velocityEntry: + yVelocity = Math.abs(velocityEntry.getVelocity().y); + break; + case PlayerInput.SetMovementStates movementStatesEntry: + Player playerComponent = archetypeChunk.getComponent(index, Player.getComponentType()); + + assert playerComponent != null; + + if (movementStatesEntry.movementStates().onGround && playerComponent.getCurrentFallDistance() > 0.0) { + int movementConfigIndex = worldPlayerConfig.getMovementConfigIndex(); + MovementConfig movementConfig = MovementConfig.getAssetMap().getAsset(movementConfigIndex); + float minFallSpeedToEngageRoll = movementConfig.getMinFallSpeedToEngageRoll(); + if (yVelocity > minFallSpeedToEngageRoll && !movementStatesEntry.movementStates().inFluid) { + EntityStatMap entityStatMapComponent = archetypeChunk.getComponent(index, EntityStatMap.getComponentType()); + + assert entityStatMapComponent != null; + + double damagePercentage = Math.pow(0.58F * (yVelocity - minFallSpeedToEngageRoll), 2.0) + 10.0; + EntityStatValue healthStatValue = entityStatMapComponent.get(DefaultEntityStatTypes.getHealth()); + + assert healthStatValue != null; + + float maxHealth = healthStatValue.getMax(); + double healthModifier = maxHealth / 100.0; + int damageInt = (int)Math.floor(healthModifier * damagePercentage); + if (movementStatesEntry.movementStates().rolling) { + if (yVelocity <= movementConfig.getMaxFallSpeedRollFullMitigation()) { + damageInt = 0; + } else if (yVelocity <= movementConfig.getMaxFallSpeedToEngageRoll()) { + damageInt = (int)(damageInt * (1.0 - movementConfig.getFallDamagePartialMitigationPercent() / 100.0)); + } + } + + if (damageInt > 0) { + assert DamageCause.FALL != null; + + Damage damage = new Damage(Damage.NULL_SOURCE, DamageCause.FALL, damageInt); + DamageSystems.executeDamage(index, archetypeChunk, commandBuffer, damage); + } + } + + playerComponent.setCurrentFallDistance(0.0); + } + continue; + default: + } + } + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + } + + public static class FilterNPCWorldConfig extends DamageEventSystem { + public FilterNPCWorldConfig() { + } + + @NullableDecl + @Override + public SystemGroup getGroup() { + return DamageModule.get().getFilterDamageGroup(); + } + + @NullableDecl + @Override + public Query getQuery() { + return DamageSystems.NPCS_QUERY; + } + + public void handle( + int index, + @NonNullDecl ArchetypeChunk archetypeChunk, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer, + @NonNullDecl Damage event + ) { + World world = store.getExternalData().getWorld(); + GameplayConfig gameplayConfig = world.getGameplayConfig(); + if (gameplayConfig.getCombatConfig().isNpcIncomingDamageDisabled()) { + event.setCancelled(true); + Ref ref = archetypeChunk.getReferenceTo(index); + commandBuffer.tryRemoveComponent(ref, KnockbackComponent.getComponentType()); + } + } + } + + public static class FilterPlayerWorldConfig extends DamageEventSystem { + private static final Query QUERY = Query.and(AllLegacyLivingEntityTypesQuery.INSTANCE, Player.getComponentType()); + + public FilterPlayerWorldConfig() { + } + + @NullableDecl + @Override + public SystemGroup getGroup() { + return DamageModule.get().getFilterDamageGroup(); + } + + @NullableDecl + @Override + public Query getQuery() { + return QUERY; + } + + public void handle( + int index, + @NonNullDecl ArchetypeChunk archetypeChunk, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer, + @NonNullDecl Damage event + ) { + World world = store.getExternalData().getWorld(); + GameplayConfig gameplayConfig = world.getGameplayConfig(); + if (gameplayConfig.getCombatConfig().isPlayerIncomingDamageDisabled()) { + event.setCancelled(true); + Ref ref = archetypeChunk.getReferenceTo(index); + commandBuffer.tryRemoveComponent(ref, KnockbackComponent.getComponentType()); + } + } + } + + public static class FilterUnkillable extends DamageEventSystem { + public static boolean CAUSE_DESYNC; + @Nonnull + private static final Query QUERY = AllLegacyLivingEntityTypesQuery.INSTANCE; + + public FilterUnkillable() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getFilterDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + EffectControllerComponent entityEffectControllerComponent = archetypeChunk.getComponent(index, EffectControllerComponent.getComponentType()); + if (entityEffectControllerComponent != null && entityEffectControllerComponent.isInvulnerable()) { + damage.setCancelled(true); + } + + Archetype archetype = archetypeChunk.getArchetype(); + boolean dead = archetype.contains(DeathComponent.getComponentType()); + boolean invulnerable = archetype.contains(Invulnerable.getComponentType()); + boolean intangible = archetype.contains(Intangible.getComponentType()); + if (dead || invulnerable || intangible || CAUSE_DESYNC) { + damage.setCancelled(true); + } + } + } + + @Deprecated + public static class HackKnockbackValues extends EntityTickingSystem { + public static float PLAYER_KNOCKBACK_SCALE = 25.0F; + private static final Query QUERY = Query.and(AllLegacyLivingEntityTypesQuery.INSTANCE, KnockbackComponent.getComponentType()); + + public HackKnockbackValues() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getFilterDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + KnockbackComponent knockbackComponent = archetypeChunk.getComponent(index, KnockbackComponent.getComponentType()); + + assert knockbackComponent != null; + + if (knockbackComponent.getVelocityConfig() == null || SplitVelocity.SHOULD_MODIFY_VELOCITY) { + Vector3d vector = knockbackComponent.getVelocity(); + vector.x = vector.x * PLAYER_KNOCKBACK_SCALE; + vector.z = vector.z * PLAYER_KNOCKBACK_SCALE; + knockbackComponent.setVelocity(vector); + } + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + } + + public static class HitAnimation extends DamageEventSystem { + @Nonnull + private static final Query QUERY = Query.and(Query.not(DeathComponent.getComponentType()), MovementStatesComponent.getComponentType()); + + public HitAnimation() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getInspectDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + ModelComponent modelComponent = archetypeChunk.getComponent(index, ModelComponent.getComponentType()); + Model model = modelComponent != null ? modelComponent.getModel() : null; + MovementStatesComponent movementStatesComponent = archetypeChunk.getComponent(index, MovementStatesComponent.getComponentType()); + + assert movementStatesComponent != null; + + MovementStates movementStates = movementStatesComponent.getMovementStates(); + if (!(damage.getAmount() <= 0.0F)) { + String[] animationIds = Entity.DefaultAnimations.getHurtAnimationIds(movementStates, damage.getCause()); + if (model != null) { + String selectedAnimationId = model.getFirstBoundAnimationId(animationIds); + if (selectedAnimationId != null) { + AnimationUtils.playAnimation(archetypeChunk.getReferenceTo(index), AnimationSlot.Status, selectedAnimationId, true, commandBuffer); + } + } + } + } + } + + public static class OutOfWorldDamage extends DelayedEntitySystem { + @Nonnull + private static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + + public OutOfWorldDamage() { + super(1.0F); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getGatherDamageGroup(); + } + + @Override + public Query getQuery() { + return TRANSFORM_COMPONENT_TYPE; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + TransformComponent transformComponent = archetypeChunk.getComponent(index, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + double posY = transformComponent.getPosition().getY(); + if (!(posY >= 0.0)) { + boolean belowMinimum = posY < -32.0; + Damage damage = new Damage(Damage.NULL_SOURCE, DamageCause.OUT_OF_WORLD, belowMinimum ? 2.1474836E9F : 50.0F); + if (belowMinimum) { + DeathComponent.tryAddComponent(commandBuffer, archetypeChunk.getReferenceTo(index), damage); + } else { + DamageSystems.executeDamage(index, archetypeChunk, commandBuffer, damage); + } + } + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + } + + public static class PlayerDamageFilterSystem extends DamageEventSystem { + @Nonnull + private static final Query QUERY = Player.getComponentType(); + + public PlayerDamageFilterSystem() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getFilterDamageGroup(); + } + + @Override + public Query getQuery() { + return QUERY; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + World world = store.getExternalData().getWorld(); + Player playerComponent = archetypeChunk.getComponent(index, Player.getComponentType()); + + assert playerComponent != null; + + if (playerComponent.hasSpawnProtection()) { + damage.setCancelled(true); + } else { + if (!world.getWorldConfig().isPvpEnabled() && damage.getSource() instanceof Damage.EntitySource entitySource) { + Ref sourceRef = entitySource.getRef(); + if (sourceRef.isValid() && commandBuffer.getComponent(sourceRef, Player.getComponentType()) != null) { + damage.setCancelled(true); + return; + } + } + } + } + } + + public static class PlayerHitIndicators extends DamageEventSystem { + @Nonnull + private static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + @Nonnull + private static final Query QUERY = PlayerRef.getComponentType(); + + public PlayerHitIndicators() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getInspectDamageGroup(); + } + + @Override + public Query getQuery() { + return QUERY; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + PlayerRef playerRefComponent = archetypeChunk.getComponent(index, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + if (damage.getSource() instanceof Damage.EntitySource) { + Ref attackerRef = ((Damage.EntitySource)damage.getSource()).getRef(); + if (attackerRef.isValid()) { + DamageCause damageCause = damage.getCause(); + if (damageCause != null) { + TransformComponent attackerTransform = commandBuffer.getComponent(attackerRef, TRANSFORM_COMPONENT_TYPE); + if (attackerTransform != null) { + Vector3d position = attackerTransform.getPosition(); + playerRefComponent.getPacketHandler() + .writeNoCache( + new DamageInfo( + new com.hypixel.hytale.protocol.Vector3d(position.getX(), position.getY(), position.getZ()), + damage.getAmount(), + damageCause.toPacket() + ) + ); + } + } + } + } + } + } + + public static class RecordLastCombat extends DamageEventSystem { + @Nonnull + private static final ComponentType DAMAGE_DATA_COMPONENT_TYPE = DamageDataComponent.getComponentType(); + @Nonnull + private static final ResourceType TIME_RESOURCE_TYPE = TimeResource.getResourceType(); + @Nonnull + private static final Query QUERY = DAMAGE_DATA_COMPONENT_TYPE; + + public RecordLastCombat() { + } + + @Override + public Query getQuery() { + return QUERY; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + DamageDataComponent damageDataComponent = archetypeChunk.getComponent(index, DAMAGE_DATA_COMPONENT_TYPE); + + assert damageDataComponent != null; + + Instant timestamp = store.getResource(TIME_RESOURCE_TYPE).getNow(); + damageDataComponent.setLastCombatAction(timestamp); + if (damage.getSource() instanceof Damage.EntitySource entitySource) { + Ref sourceRef = entitySource.getRef(); + if (!sourceRef.isValid()) { + return; + } + + DamageDataComponent sourceDamageDataComponent = store.getComponent(sourceRef, DAMAGE_DATA_COMPONENT_TYPE); + if (sourceDamageDataComponent != null) { + sourceDamageDataComponent.setLastCombatAction(timestamp); + } + } + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getInspectDamageGroup(); + } + } + + public static class ReticleEvents extends DamageEventSystem { + private static final int EVENT_ON_HIT_TAG_INDEX = AssetRegistry.getOrCreateTagIndex("OnHit"); + private static final int EVENT_ON_KILL_TAG_INDEX = AssetRegistry.getOrCreateTagIndex("OnKill"); + private static final ReticleEvent ON_HIT = new ReticleEvent(EVENT_ON_HIT_TAG_INDEX); + private static final ReticleEvent ON_KILL = new ReticleEvent(EVENT_ON_KILL_TAG_INDEX); + + public ReticleEvents() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getInspectDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return Archetype.empty(); + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + boolean isDead = archetypeChunk.getArchetype().contains(DeathComponent.getComponentType()); + if (!(damage.getAmount() <= 0.0F)) { + if (damage.getSource() instanceof Damage.EntitySource) { + Ref attackerRef = ((Damage.EntitySource)damage.getSource()).getRef(); + if (attackerRef.isValid()) { + PlayerRef playerRef = commandBuffer.getComponent(attackerRef, PlayerRef.getComponentType()); + if (playerRef != null && playerRef.isValid()) { + playerRef.getPacketHandler().writeNoCache(isDead ? ON_KILL : ON_HIT); + } + } + } + } + } + } + + public static class TrackLastDamage extends DamageEventSystem { + @Nonnull + private static final Query QUERY = AllLegacyLivingEntityTypesQuery.INSTANCE; + + public TrackLastDamage() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getInspectDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + TimeResource timeResource = commandBuffer.getResource(TimeResource.getResourceType()); + DamageDataComponent damageDataComponent = archetypeChunk.getComponent(index, DamageDataComponent.getComponentType()); + + assert damageDataComponent != null; + + damageDataComponent.setLastDamageTime(timeResource.getNow()); + } + } + + @Deprecated + public static class WieldingDamageReduction extends DamageEventSystem { + @Nonnull + private static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + @Nonnull + private static final Query QUERY = Query.and( + AllLegacyLivingEntityTypesQuery.INSTANCE, + DamageDataComponent.getComponentType(), + InteractionModule.get().getInteractionManagerComponent(), + TRANSFORM_COMPONENT_TYPE + ); + + public WieldingDamageReduction() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getFilterDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + DamageDataComponent damageDataComponent = archetypeChunk.getComponent(index, DamageDataComponent.getComponentType()); + + assert damageDataComponent != null; + + InteractionManager interactionManager = archetypeChunk.getComponent(index, InteractionModule.get().getInteractionManagerComponent()); + + assert interactionManager != null; + + WieldingInteraction wielding = damageDataComponent.getCurrentWielding(); + if (wielding != null) { + WieldingInteraction.AngledWielding angledWielding = wielding.getAngledWielding(); + Ref ref = archetypeChunk.getReferenceTo(index); + TransformComponent transformComponent = archetypeChunk.getComponent(index, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + Vector3d targetPosition = transformComponent.getPosition(); + Vector3f targetRotation = transformComponent.getRotation(); + if (damage.getSource() instanceof Damage.EntitySource source) { + Ref attackerRef = source.getRef(); + if (attackerRef.isValid()) { + TransformComponent attackerTransformComponent = commandBuffer.getComponent(attackerRef, TRANSFORM_COMPONENT_TYPE); + + assert attackerTransformComponent != null; + + int damageCauseIndex = damage.getDamageCauseIndex(); + float wieldingModifier = 1.0F; + float angledWieldingModifier = 1.0F; + String blockedInteractions = null; + Int2FloatMap wieldingDamageModifiers = wielding.getDamageModifiers(); + if (!wieldingDamageModifiers.isEmpty()) { + wieldingModifier = wieldingDamageModifiers.getOrDefault(damageCauseIndex, 1.0F); + DamageEffects wieldingBlockedEffects = wielding.getBlockedEffects(); + if (wieldingBlockedEffects != null) { + wieldingBlockedEffects.addToDamage(damage); + } + + String wieldingBlockedInteractions = wielding.getBlockedInteractions(); + if (wieldingBlockedInteractions != null) { + blockedInteractions = wieldingBlockedInteractions; + } + + damage.putMetaObject(Damage.BLOCKED, Boolean.TRUE); + } + + if (angledWielding != null) { + Int2FloatMap angledWieldingDamageModifiers = angledWielding.getDamageModifiers(); + if (angledWieldingDamageModifiers.containsKey(damageCauseIndex)) { + Vector3d attackerPosition = attackerTransformComponent.getPosition(); + float angleBetween = TrigMathUtil.atan2(attackerPosition.x - targetPosition.x, attackerPosition.z - targetPosition.z); + angleBetween = MathUtil.wrapAngle(angleBetween + (float) Math.PI - targetRotation.getYaw()); + if (Math.abs(MathUtil.compareAngle(angleBetween, angledWielding.getAngleRad())) < angledWielding.getAngleDistanceRad()) { + angledWieldingModifier = angledWieldingDamageModifiers.getOrDefault(damageCauseIndex, 1.0F); + DamageEffects wieldingBlockedEffectsx = wielding.getBlockedEffects(); + if (wieldingBlockedEffectsx != null) { + wieldingBlockedEffectsx.addToDamage(damage); + } + + String wieldingBlockedInteractions = wielding.getBlockedInteractions(); + if (wieldingBlockedInteractions != null) { + blockedInteractions = wieldingBlockedInteractions; + } + + damage.putMetaObject(Damage.BLOCKED, Boolean.TRUE); + } + } + } + + damage.setAmount(damage.getAmount() * wieldingModifier * angledWieldingModifier); + if (blockedInteractions != null) { + InteractionContext context = InteractionContext.forInteraction(interactionManager, ref, InteractionType.Wielding, commandBuffer); + DynamicMetaStore contextMetaStore = context.getMetaStore(); + contextMetaStore.putMetaObject(Interaction.TARGET_ENTITY, attackerRef); + contextMetaStore.putMetaObject(Interaction.DAMAGE, damage); + NetworkId attackerNetworkIdComponent = commandBuffer.getComponent(attackerRef, NetworkId.getComponentType()); + + assert attackerNetworkIdComponent != null; + + int networkId = attackerNetworkIdComponent.getId(); + InteractionChain chain = interactionManager.initChain( + InteractionType.Wielding, context, RootInteraction.getRootInteractionOrUnknown(blockedInteractions), networkId, null, false + ); + interactionManager.queueExecuteChain(chain); + } + } + } + } + } + } + + @Deprecated + public static class WieldingKnockbackReduction extends DamageEventSystem { + @Nonnull + private static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + @Nonnull + private static final Query QUERY = Query.and( + AllLegacyLivingEntityTypesQuery.INSTANCE, DamageDataComponent.getComponentType(), TRANSFORM_COMPONENT_TYPE + ); + + public WieldingKnockbackReduction() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getFilterDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + public void handle( + int index, + @NonNullDecl ArchetypeChunk archetypeChunk, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer, + @NonNullDecl Damage event + ) { + } + + public void handleInternal( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + DamageDataComponent damageDataComponent = archetypeChunk.getComponent(index, DamageDataComponent.getComponentType()); + + assert damageDataComponent != null; + + TransformComponent transformComponent = archetypeChunk.getComponent(index, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + WieldingInteraction wielding = damageDataComponent.getCurrentWielding(); + if (wielding != null) { + Int2DoubleMap knockbackModifiers = wielding.getKnockbackModifiers(); + WieldingInteraction.AngledWielding angledWielding = wielding.getAngledWielding(); + KnockbackComponent knockbackComponent = damage.getIfPresentMetaObject(Damage.KNOCKBACK_COMPONENT); + if (knockbackComponent != null) { + if (damage.getSource() instanceof Damage.EntitySource source) { + Ref attackerRef = source.getRef(); + if (attackerRef.isValid()) { + TransformComponent attackerTransformComponent = commandBuffer.getComponent(attackerRef, TRANSFORM_COMPONENT_TYPE); + + assert attackerTransformComponent != null; + + int damageCauseIndex = damage.getDamageCauseIndex(); + double angledWieldingModifier = 1.0; + double wieldingModifier = knockbackModifiers.getOrDefault(damageCauseIndex, 1.0); + if (angledWielding != null) { + Int2DoubleMap angledWieldingKnockbackModifiers = angledWielding.getKnockbackModifiers(); + if (angledWieldingKnockbackModifiers.containsKey(damageCauseIndex)) { + Vector3d targetPos = transformComponent.getPosition(); + Vector3d attackerPos = attackerTransformComponent.getPosition(); + float angleBetween = TrigMathUtil.atan2(attackerPos.x - targetPos.x, attackerPos.z - targetPos.z); + angleBetween = MathUtil.wrapAngle(angleBetween + (float) Math.PI - transformComponent.getRotation().getYaw()); + if (Math.abs(MathUtil.compareAngle(angleBetween, angledWielding.getAngleRad())) < angledWielding.getAngleDistanceRad()) { + angledWieldingModifier = angledWieldingKnockbackModifiers.getOrDefault(damageCauseIndex, 1.0); + } + } + } + + knockbackComponent.addModifier(wieldingModifier); + knockbackComponent.addModifier(angledWieldingModifier); + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/damage/DeathComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/damage/DeathComponent.java new file mode 100644 index 0000000..3af9abe --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/damage/DeathComponent.java @@ -0,0 +1,197 @@ +package com.hypixel.hytale.server.core.modules.entity.damage; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.gameplay.DeathConfig; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DeathComponent implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(DeathComponent.class, DeathComponent::new) + .append(new KeyedCodec<>("DeathCause", Codec.STRING), (o, i) -> o.deathCause = i, o -> o.deathCause) + .add() + .append( + new KeyedCodec<>("DeathMessage", Message.CODEC), + (deathComponent, message) -> deathComponent.deathMessage = message, + deathComponent -> deathComponent.deathMessage + ) + .add() + .append( + new KeyedCodec<>("ShowDeathMenu", BuilderCodec.BOOLEAN), + (deathComponent, showDeathMenu) -> deathComponent.showDeathMenu = showDeathMenu, + deathComponent -> deathComponent.showDeathMenu + ) + .add() + .append( + new KeyedCodec<>("ItemsLostOnDeath", new ArrayCodec<>(ItemStack.CODEC, ItemStack[]::new)), + (deathComponent, itemStacks) -> deathComponent.itemsLostOnDeath = itemStacks, + deathComponent -> deathComponent.itemsLostOnDeath + ) + .add() + .append( + new KeyedCodec<>("ItemsAmountLossPercentage", Codec.DOUBLE), + (deathComponent, aDouble) -> deathComponent.itemsAmountLossPercentage = aDouble, + deathComponent -> deathComponent.itemsAmountLossPercentage + ) + .add() + .append( + new KeyedCodec<>("ItemsDurabilityLossPercentage", Codec.DOUBLE), + (deathComponent, aDouble) -> deathComponent.itemsDurabilityLossPercentage = aDouble, + deathComponent -> deathComponent.itemsDurabilityLossPercentage + ) + .add() + .append( + new KeyedCodec<>("DisplayDataOnDeathScreen", Codec.BOOLEAN), + (deathComponent, aBoolean) -> deathComponent.displayDataOnDeathScreen = aBoolean, + deathComponent -> deathComponent.displayDataOnDeathScreen + ) + .add() + .build(); + private String deathCause; + @Nullable + private Message deathMessage; + private boolean showDeathMenu = true; + private ItemStack[] itemsLostOnDeath; + private double itemsAmountLossPercentage; + private double itemsDurabilityLossPercentage; + private boolean displayDataOnDeathScreen; + @Nullable + private Damage deathInfo; + private DeathConfig.ItemsLossMode itemsLossMode = DeathConfig.ItemsLossMode.ALL; + @Nullable + private InteractionChain interactionChain; + + public static ComponentType getComponentType() { + return DamageModule.get().getDeathComponentType(); + } + + protected DeathComponent(@Nonnull Damage deathInfo) { + this.deathInfo = deathInfo; + this.deathCause = deathInfo.getCause().getId(); + } + + protected DeathComponent() { + } + + @Nullable + public DamageCause getDeathCause() { + return (DamageCause)((IndexedLookupTableAssetMap)AssetRegistry.getAssetStore(DamageCause.class).getAssetMap()).getAsset(this.deathCause); + } + + @Nullable + public Message getDeathMessage() { + return this.deathMessage; + } + + public void setDeathMessage(@Nullable Message deathMessage) { + this.deathMessage = deathMessage; + } + + public boolean isShowDeathMenu() { + return this.showDeathMenu; + } + + public void setShowDeathMenu(boolean showDeathMenu) { + this.showDeathMenu = showDeathMenu; + } + + public ItemStack[] getItemsLostOnDeath() { + return this.itemsLostOnDeath; + } + + public void setItemsLostOnDeath(List itemsLostOnDeath) { + this.itemsLostOnDeath = itemsLostOnDeath.toArray(ItemStack[]::new); + } + + public double getItemsAmountLossPercentage() { + return this.itemsAmountLossPercentage; + } + + public void setItemsAmountLossPercentage(double itemsAmountLossPercentage) { + this.itemsAmountLossPercentage = itemsAmountLossPercentage; + } + + public double getItemsDurabilityLossPercentage() { + return this.itemsDurabilityLossPercentage; + } + + public void setItemsDurabilityLossPercentage(double itemsDurabilityLossPercentage) { + this.itemsDurabilityLossPercentage = itemsDurabilityLossPercentage; + } + + public boolean displayDataOnDeathScreen() { + return this.displayDataOnDeathScreen; + } + + public void setDisplayDataOnDeathScreen(boolean displayDataOnDeathScreen) { + this.displayDataOnDeathScreen = displayDataOnDeathScreen; + } + + @Nullable + public Damage getDeathInfo() { + return this.deathInfo; + } + + public DeathConfig.ItemsLossMode getItemsLossMode() { + return this.itemsLossMode; + } + + public void setItemsLossMode(DeathConfig.ItemsLossMode itemsLossMode) { + this.itemsLossMode = itemsLossMode; + } + + public DeathItemLoss getDeathItemLoss() { + return new DeathItemLoss(this.itemsLossMode, this.itemsLostOnDeath, this.itemsAmountLossPercentage, this.itemsDurabilityLossPercentage); + } + + @Nullable + public InteractionChain getInteractionChain() { + return this.interactionChain; + } + + public void setInteractionChain(@Nullable InteractionChain interactionChain) { + this.interactionChain = interactionChain; + } + + @Nonnull + @Override + public Component clone() { + DeathComponent death = new DeathComponent(); + death.deathCause = this.deathCause; + death.deathMessage = this.deathMessage; + death.showDeathMenu = this.showDeathMenu; + death.itemsLostOnDeath = this.itemsLostOnDeath; + death.itemsAmountLossPercentage = this.itemsAmountLossPercentage; + death.itemsDurabilityLossPercentage = this.itemsDurabilityLossPercentage; + death.displayDataOnDeathScreen = this.displayDataOnDeathScreen; + death.deathInfo = this.deathInfo; + death.itemsLossMode = this.itemsLossMode; + return death; + } + + public static void tryAddComponent(@Nonnull CommandBuffer commandBuffer, @Nonnull Ref ref, @Nonnull Damage damage) { + if (!commandBuffer.getArchetype(ref).contains(getComponentType())) { + commandBuffer.run(store -> tryAddComponent(store, ref, damage)); + } + } + + public static void tryAddComponent(@Nonnull Store store, @Nonnull Ref ref, @Nonnull Damage damage) { + if (!store.getArchetype(ref).contains(getComponentType())) { + store.addComponent(ref, getComponentType(), new DeathComponent(damage)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/damage/DeathItemLoss.java b/src/com/hypixel/hytale/server/core/modules/entity/damage/DeathItemLoss.java new file mode 100644 index 0000000..31e2b5a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/damage/DeathItemLoss.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.server.core.modules.entity.damage; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.server.core.asset.type.gameplay.DeathConfig; +import com.hypixel.hytale.server.core.inventory.ItemStack; + +public class DeathItemLoss { + public static final BuilderCodec CODEC = BuilderCodec.builder(DeathItemLoss.class, DeathItemLoss::new) + .append(new KeyedCodec<>("LossMode", DeathConfig.LOSS_MODE_CODEC), (loss, o) -> loss.lossMode = o, loss -> loss.lossMode) + .add() + .append( + new KeyedCodec<>("ItemsLostOnDeath", new ArrayCodec<>(ItemStack.CODEC, ItemStack[]::new)), + (loss, items) -> loss.itemsLost = items, + loss -> loss.itemsLost + ) + .add() + .append( + new KeyedCodec<>("ItemsAmountLossPercentage", Codec.DOUBLE), (loss, percent) -> loss.amountLossPercentage = percent, loss -> loss.amountLossPercentage + ) + .add() + .append( + new KeyedCodec<>("ItemsDurabilityLossPercentage", Codec.DOUBLE), + (loss, percent) -> loss.durabilityLossPercentage = percent, + loss -> loss.durabilityLossPercentage + ) + .add() + .build(); + private DeathConfig.ItemsLossMode lossMode; + private ItemStack[] itemsLost; + private double amountLossPercentage; + private double durabilityLossPercentage; + private static final DeathItemLoss NO_LOSS_MODE = new DeathItemLoss(DeathConfig.ItemsLossMode.NONE, ItemStack.EMPTY_ARRAY, 0.0, 0.0); + + private DeathItemLoss() { + } + + public DeathItemLoss(DeathConfig.ItemsLossMode lossMode, ItemStack[] itemsLost, double amountLossPercentage, double durabilityLossPercentage) { + this.lossMode = lossMode; + this.itemsLost = itemsLost; + this.amountLossPercentage = amountLossPercentage; + this.durabilityLossPercentage = durabilityLossPercentage; + } + + public static DeathItemLoss noLossMode() { + return NO_LOSS_MODE; + } + + public DeathConfig.ItemsLossMode getLossMode() { + return this.lossMode; + } + + public ItemStack[] getItemsLost() { + return this.itemsLost == null ? ItemStack.EMPTY_ARRAY : this.itemsLost; + } + + public double getAmountLossPercentage() { + return this.amountLossPercentage; + } + + public double getDurabilityLossPercentage() { + return this.durabilityLossPercentage; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/damage/DeathSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/damage/DeathSystems.java new file mode 100644 index 0000000..5d31a73 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/damage/DeathSystems.java @@ -0,0 +1,647 @@ +package com.hypixel.hytale.server.core.modules.entity.damage; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.RootDependency; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.AnimationSlot; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.packets.interface_.KillFeedMessage; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.gameplay.DeathConfig; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.gameplay.WorldMapConfig; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.entity.AnimationUtils; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerWorldData; +import com.hypixel.hytale.server.core.entity.entities.player.pages.PageManager; +import com.hypixel.hytale.server.core.entity.entities.player.pages.RespawnPage; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.entity.nameplate.Nameplate; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction; +import com.hypixel.hytale.server.core.modules.entity.AllLegacyLivingEntityTypesQuery; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.event.KillFeedEvent; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsSystems; +import com.hypixel.hytale.server.core.modules.entitystats.asset.DefaultEntityStatTypes; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.Interactions; +import com.hypixel.hytale.server.core.modules.interaction.interaction.UnarmedInteractions; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DeathSystems { + public DeathSystems() { + } + + private static void playDeathAnimation( + @Nonnull Ref ref, + @Nonnull DeathComponent deathComponent, + @Nullable ModelComponent modelComponent, + @Nonnull MovementStatesComponent movementStatesComponent, + @Nonnull ComponentAccessor componentAccessor + ) { + if (modelComponent != null) { + Model model = modelComponent.getModel(); + String[] animationIds = Entity.DefaultAnimations.getDeathAnimationIds(movementStatesComponent.getMovementStates(), deathComponent.getDeathCause()); + String selectedAnimationId = model.getFirstBoundAnimationId(animationIds); + AnimationUtils.playAnimation(ref, AnimationSlot.Status, selectedAnimationId, true, componentAccessor); + } + } + + public static class ClearEntityEffects extends DeathSystems.OnDeathSystem { + public ClearEntityEffects() { + } + + @Nonnull + @Override + public Query getQuery() { + return EffectControllerComponent.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + EffectControllerComponent effectControllerComponent = commandBuffer.getComponent(ref, EffectControllerComponent.getComponentType()); + + assert effectControllerComponent != null; + + effectControllerComponent.clearEffects(ref, commandBuffer); + } + } + + public static class ClearHealth extends DeathSystems.OnDeathSystem { + @Nonnull + private static final ComponentType ENTITY_STAT_MAP_COMPONENT_TYPE = EntityStatMap.getComponentType(); + + public ClearHealth() { + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.firstSet(); + } + + @Nonnull + @Override + public Query getQuery() { + return ENTITY_STAT_MAP_COMPONENT_TYPE; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + EntityStatMap entityStatMapComponent = store.getComponent(ref, ENTITY_STAT_MAP_COMPONENT_TYPE); + + assert entityStatMapComponent != null; + + entityStatMapComponent.setStatValue(DefaultEntityStatTypes.getHealth(), 0.0F); + } + } + + public static class ClearInteractions extends DeathSystems.OnDeathSystem { + @Nonnull + private static final ComponentType INTERACTION_MANAGER_COMPONENT_TYPE = InteractionModule.get() + .getInteractionManagerComponent(); + + public ClearInteractions() { + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.firstSet(); + } + + @Nonnull + @Override + public Query getQuery() { + return INTERACTION_MANAGER_COMPONENT_TYPE; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + InteractionManager interactionManagerComponent = commandBuffer.getComponent(ref, INTERACTION_MANAGER_COMPONENT_TYPE); + + assert interactionManagerComponent != null; + + interactionManagerComponent.clear(); + } + } + + public static class CorpseRemoval extends EntityTickingSystem { + @Nonnull + private static final ComponentType DEFERRED_CORPSE_REMOVAL_COMPONENT_TYPE = DeferredCorpseRemoval.getComponentType(); + @Nonnull + private static final Query QUERY = Query.and(DeathComponent.getComponentType(), Query.not(Player.getComponentType())); + + public CorpseRemoval() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + DeathComponent deathComponent = archetypeChunk.getComponent(index, DeathComponent.getComponentType()); + + assert deathComponent != null; + + InteractionChain deathInteractionChain = deathComponent.getInteractionChain(); + if (deathInteractionChain == null || deathInteractionChain.getServerState() != InteractionState.NotFinished) { + DeferredCorpseRemoval corpseRemoval = archetypeChunk.getComponent(index, DEFERRED_CORPSE_REMOVAL_COMPONENT_TYPE); + if (corpseRemoval == null || corpseRemoval.tick(dt)) { + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + } + } + } + } + + public static class DeathAnimation extends DeathSystems.OnDeathSystem { + @Nonnull + private final Query query = Query.and(MovementStatesComponent.getComponentType(), AllLegacyLivingEntityTypesQuery.INSTANCE); + @Nonnull + private final Set> dependencies = Set.of( + new SystemDependency<>(Order.BEFORE, EntityStatsSystems.EntityTrackerUpdate.class), + new SystemDependency<>(Order.AFTER, DeathSystems.ClearEntityEffects.class) + ); + + public DeathAnimation() { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + ModelComponent modelComponent = commandBuffer.getComponent(ref, ModelComponent.getComponentType()); + MovementStatesComponent movementStatesComponent = commandBuffer.getComponent(ref, MovementStatesComponent.getComponentType()); + + assert movementStatesComponent != null; + + DeathSystems.playDeathAnimation(ref, component, modelComponent, movementStatesComponent, commandBuffer); + } + } + + public static class DropPlayerDeathItems extends DeathSystems.OnDeathSystem { + @Nonnull + private static final Query QUERY = Archetype.of(Player.getComponentType(), TransformComponent.getComponentType()); + + public DropPlayerDeathItems() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (playerComponent.getGameMode() != GameMode.Creative) { + component.setDisplayDataOnDeathScreen(true); + CombinedItemContainer combinedItemContainer = playerComponent.getInventory().getCombinedEverything(); + if (component.getItemsDurabilityLossPercentage() > 0.0) { + double durabilityLossRatio = component.getItemsDurabilityLossPercentage() / 100.0; + boolean hasArmorBroken = false; + + for (short i = 0; i < combinedItemContainer.getCapacity(); i++) { + ItemStack itemStack = combinedItemContainer.getItemStack(i); + if (!ItemStack.isEmpty(itemStack) && !itemStack.isBroken()) { + double durabilityLoss = itemStack.getMaxDurability() * durabilityLossRatio; + ItemStack updatedItemStack = itemStack.withIncreasedDurability(-durabilityLoss); + ItemStackSlotTransaction transaction = combinedItemContainer.replaceItemStackInSlot(i, itemStack, updatedItemStack); + if (transaction.getSlotAfter().isBroken() && itemStack.getItem().getArmor() != null) { + hasArmorBroken = true; + } + } + } + + if (hasArmorBroken) { + playerComponent.getStatModifiersManager().setRecalculate(true); + } + } + + List itemsToDrop = null; + switch (component.getItemsLossMode()) { + case ALL: + itemsToDrop = playerComponent.getInventory().dropAllItemStacks(); + break; + case CONFIGURED: + double itemsAmountLossPercentage = component.getItemsAmountLossPercentage(); + if (itemsAmountLossPercentage > 0.0) { + double itemAmountLossRatio = itemsAmountLossPercentage / 100.0; + itemsToDrop = new ObjectArrayList<>(); + + for (short ix = 0; ix < combinedItemContainer.getCapacity(); ix++) { + ItemStack itemStack = combinedItemContainer.getItemStack(ix); + if (!ItemStack.isEmpty(itemStack) && itemStack.getItem().dropsOnDeath()) { + int quantityToLose = Math.max(1, MathUtil.floor(itemStack.getQuantity() * itemAmountLossRatio)); + itemsToDrop.add(itemStack.withQuantity(quantityToLose)); + int newQuantity = itemStack.getQuantity() - quantityToLose; + if (newQuantity > 0) { + ItemStack updatedItemStack = itemStack.withQuantity(newQuantity); + combinedItemContainer.replaceItemStackInSlot(ix, itemStack, updatedItemStack); + } else { + combinedItemContainer.removeItemStackFromSlot(ix); + } + } + } + } + case NONE: + } + + if (itemsToDrop != null && !itemsToDrop.isEmpty()) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3f headRotation = headRotationComponent.getRotation(); + Holder[] drops = ItemComponent.generateItemDrops(store, itemsToDrop, position.clone().add(0.0, 1.0, 0.0), headRotation); + commandBuffer.addEntities(drops, AddReason.SPAWN); + component.setItemsLostOnDeath(itemsToDrop); + } + } + } + } + + public static class KillFeed extends DeathSystems.OnDeathSystem { + public KillFeed() { + } + + @Nonnull + @Override + public Query getQuery() { + return Archetype.empty(); + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + Damage deathInfo = component.getDeathInfo(); + if (deathInfo != null) { + World world = commandBuffer.getExternalData().getWorld(); + ObjectArrayList broadcastTargets = new ObjectArrayList<>(world.getPlayerRefs()); + Message killerMessage = null; + if (deathInfo.getSource() instanceof Damage.EntitySource entitySource) { + Ref sourceRef = entitySource.getRef(); + if (sourceRef.isValid()) { + KillFeedEvent.KillerMessage killerMessageEvent = new KillFeedEvent.KillerMessage(deathInfo, ref); + store.invoke(sourceRef, killerMessageEvent); + if (killerMessageEvent.isCancelled()) { + return; + } + + killerMessage = killerMessageEvent.getMessage(); + } + } + + KillFeedEvent.DecedentMessage decedentMessageEvent = new KillFeedEvent.DecedentMessage(deathInfo); + store.invoke(ref, decedentMessageEvent); + if (!decedentMessageEvent.isCancelled()) { + Message decedentMessage = decedentMessageEvent.getMessage(); + if (killerMessage != null || decedentMessage != null) { + KillFeedEvent.Display killFeedEvent = new KillFeedEvent.Display( + deathInfo, deathInfo.getIfPresentMetaObject(Damage.DEATH_ICON), broadcastTargets + ); + store.invoke(ref, killFeedEvent); + if (!killFeedEvent.isCancelled()) { + KillFeedMessage killFeedMessage = new KillFeedMessage( + killerMessage != null ? killerMessage.getFormattedMessage() : null, + decedentMessage != null ? decedentMessage.getFormattedMessage() : null, + killFeedEvent.getIcon() + ); + + for (PlayerRef targetPlayerRef : killFeedEvent.getBroadcastTargets()) { + targetPlayerRef.getPacketHandler().write(killFeedMessage); + } + } + } + } + } + } + } + + public abstract static class OnDeathSystem extends RefChangeSystem { + public OnDeathSystem() { + } + + @Nonnull + @Override + public ComponentType componentType() { + return DeathComponent.getComponentType(); + } + + public void onComponentSet( + @Nonnull Ref ref, + DeathComponent oldComponent, + @Nonnull DeathComponent newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + } + + public static class PlayerDeathMarker extends DeathSystems.OnDeathSystem { + public PlayerDeathMarker() { + } + + @Nonnull + @Override + public Query getQuery() { + return Player.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + World world = commandBuffer.getExternalData().getWorld(); + GameplayConfig gameplayConfig = world.getGameplayConfig(); + WorldMapConfig worldMapConfigGameplayConfig = gameplayConfig.getWorldMapConfig(); + if (worldMapConfigGameplayConfig.isDisplayDeathMarker()) { + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + Transform transform = new Transform(position.getX(), position.getY(), position.getZ(), 0.0F, 0.0F, 0.0F); + WorldTimeResource worldTimeResource = commandBuffer.getResource(WorldTimeResource.getResourceType()); + Instant gameTime = worldTimeResource.getGameTime(); + int daysSinceWorldStart = (int)WorldTimeResource.ZERO_YEAR.until(gameTime, ChronoUnit.DAYS); + String deathMarkerId = "death-marker-" + UUID.randomUUID(); + PlayerWorldData perWorldData = playerComponent.getPlayerConfigData().getPerWorldData(world.getName()); + perWorldData.addLastDeath(deathMarkerId, transform, daysSinceWorldStart); + } + } + } + + public static class PlayerDeathScreen extends DeathSystems.OnDeathSystem { + public PlayerDeathScreen() { + } + + @Nonnull + @Override + public Query getQuery() { + return Player.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (component.isShowDeathMenu()) { + Damage deathInfo = component.getDeathInfo(); + Message deathMessage = deathInfo != null ? deathInfo.getDeathMessage(ref, commandBuffer) : null; + component.setDeathMessage(deathMessage); + PlayerRef playerRefComponent = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + PageManager pageManager = playerComponent.getPageManager(); + pageManager.openCustomPage( + ref, store, new RespawnPage(playerRefComponent, deathMessage, component.displayDataOnDeathScreen(), component.getDeathItemLoss()) + ); + } + } + } + + public static class PlayerDropItemsConfig extends DeathSystems.OnDeathSystem { + @Nonnull + private static final Set> DEPENDENCIES = Set.of(new SystemDependency<>(Order.BEFORE, DeathSystems.DropPlayerDeathItems.class)); + + public PlayerDropItemsConfig() { + } + + @Nonnull + @Override + public Query getQuery() { + return Player.getComponentType(); + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + DeathConfig deathConfig = store.getExternalData().getWorld().getDeathConfig(); + component.setItemsLossMode(deathConfig.getItemsLossMode()); + component.setItemsAmountLossPercentage(deathConfig.getItemsAmountLossPercentage()); + component.setItemsDurabilityLossPercentage(deathConfig.getItemsDurabilityLossPercentage()); + } + } + + public static class PlayerKilledPlayer extends DeathSystems.OnDeathSystem { + @Nonnull + private static final Query QUERY = Archetype.of(Player.getComponentType(), Nameplate.getComponentType()); + + public PlayerKilledPlayer() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + Nameplate nameplateComponent = commandBuffer.getComponent(ref, Nameplate.getComponentType()); + Damage deathInfo = component.getDeathInfo(); + DamageCause deathCause = component.getDeathCause(); + if (deathCause == DamageCause.PHYSICAL || deathCause == DamageCause.PROJECTILE) { + if (deathInfo != null && deathInfo.getSource() instanceof Damage.EntitySource entitySource) { + Ref sourceRef = entitySource.getRef(); + if (sourceRef.isValid()) { + Player attacker = store.getComponent(sourceRef, Player.getComponentType()); + if (attacker != null) { + attacker.sendMessage(Message.translation("server.general.killedEntity").param("entityName", nameplateComponent.getText())); + } + } + } + } + } + } + + public static class RunDeathInteractions extends DeathSystems.OnDeathSystem { + @Nonnull + private static final ComponentType INTERACTIONS_COMPONENT_TYPE = Interactions.getComponentType(); + @Nonnull + private static final ComponentType INTERACTION_MANAGER_COMPONENT_TYPE = InteractionModule.get() + .getInteractionManagerComponent(); + @Nonnull + private static final Query QUERY = Query.and(INTERACTIONS_COMPONENT_TYPE, INTERACTION_MANAGER_COMPONENT_TYPE); + @Nonnull + private static final Set> DEPENDENCIES = Set.of(new SystemDependency<>(Order.AFTER, DeathSystems.ClearEntityEffects.class)); + + public RunDeathInteractions() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + InteractionManager interactionManagerComponent = commandBuffer.getComponent(ref, INTERACTION_MANAGER_COMPONENT_TYPE); + + assert interactionManagerComponent != null; + + Interactions interactionsComponent = commandBuffer.getComponent(ref, INTERACTIONS_COMPONENT_TYPE); + + assert interactionsComponent != null; + + String rootId = interactionsComponent.getInteractionId(InteractionType.Death); + if (rootId == null) { + UnarmedInteractions unarmed = UnarmedInteractions.getAssetMap().getAsset("Empty"); + if (unarmed != null) { + rootId = unarmed.getInteractions().get(InteractionType.Death); + } + } + + RootInteraction rootInteraction = rootId != null ? RootInteraction.getAssetMap().getAsset(rootId) : null; + if (rootInteraction != null) { + InteractionContext context = InteractionContext.forInteraction(interactionManagerComponent, ref, InteractionType.Death, commandBuffer); + InteractionChain chain = interactionManagerComponent.initChain(InteractionType.Death, context, rootInteraction, false); + interactionManagerComponent.queueExecuteChain(chain); + component.setInteractionChain(chain); + } + } + } + + public static class SpawnedDeathAnimation extends RefSystem { + private static final Query QUERY = Query.and( + AllLegacyLivingEntityTypesQuery.INSTANCE, DeathComponent.getComponentType(), MovementStatesComponent.getComponentType() + ); + + public SpawnedDeathAnimation() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + DeathComponent deathComponent = commandBuffer.getComponent(ref, DeathComponent.getComponentType()); + + assert deathComponent != null; + + ModelComponent modelComponent = commandBuffer.getComponent(ref, ModelComponent.getComponentType()); + MovementStatesComponent movementStatesComponent = commandBuffer.getComponent(ref, MovementStatesComponent.getComponentType()); + + assert movementStatesComponent != null; + + DeathSystems.playDeathAnimation(ref, deathComponent, modelComponent, movementStatesComponent, commandBuffer); + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/damage/DeferredCorpseRemoval.java b/src/com/hypixel/hytale/server/core/modules/entity/damage/DeferredCorpseRemoval.java new file mode 100644 index 0000000..791d950 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/damage/DeferredCorpseRemoval.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.modules.entity.damage; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class DeferredCorpseRemoval implements Component { + protected double timeRemaining; + + public static ComponentType getComponentType() { + return DamageModule.get().getDeferredCorpseRemovalComponentType(); + } + + public DeferredCorpseRemoval(double timeUntilCorpseRemoval) { + this.timeRemaining = timeUntilCorpseRemoval; + } + + public boolean tick(float dt) { + return (this.timeRemaining -= dt) <= 0.0; + } + + @Nonnull + @Override + public Component clone() { + return new DeferredCorpseRemoval(this.timeRemaining); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/damage/RespawnSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/damage/RespawnSystems.java new file mode 100644 index 0000000..5827f7f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/damage/RespawnSystems.java @@ -0,0 +1,180 @@ +package com.hypixel.hytale.server.core.modules.entity.damage; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.gameplay.respawn.RespawnController; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class RespawnSystems { + public RespawnSystems() { + } + + public static class CheckBrokenItemsRespawnSystem extends RespawnSystems.OnRespawnSystem { + public CheckBrokenItemsRespawnSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return Player.getComponentType(); + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (playerComponent.getInventory().containsBrokenItem()) { + playerComponent.sendMessage(Message.translation("server.general.repair.itemBrokenOnRespawn").color("#ff5555")); + } + } + } + + public static class ClearEntityEffectsRespawnSystem extends RespawnSystems.OnRespawnSystem { + public ClearEntityEffectsRespawnSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return EffectControllerComponent.getComponentType(); + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + EffectControllerComponent effectControllerComponent = commandBuffer.getComponent(ref, EffectControllerComponent.getComponentType()); + + assert effectControllerComponent != null; + + effectControllerComponent.clearEffects(ref, commandBuffer); + } + } + + public static class ClearInteractionsRespawnSystem extends RespawnSystems.OnRespawnSystem { + public ClearInteractionsRespawnSystem() { + } + + @Override + public Query getQuery() { + return InteractionModule.get().getInteractionManagerComponent(); + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + InteractionManager interactionManagerComponent = store.getComponent(ref, InteractionModule.get().getInteractionManagerComponent()); + interactionManagerComponent.clear(); + } + } + + public abstract static class OnRespawnSystem extends RefChangeSystem { + public OnRespawnSystem() { + } + + @Nonnull + @Override + public ComponentType componentType() { + return DeathComponent.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentSet( + @Nonnull Ref ref, + DeathComponent oldComponent, + @Nonnull DeathComponent newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + } + + public static class ResetPlayerRespawnSystem extends RespawnSystems.OnRespawnSystem { + public ResetPlayerRespawnSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return Player.getComponentType(); + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.setLastSpawnTimeNanos(System.nanoTime()); + } + } + + public static class ResetStatsRespawnSystem extends RespawnSystems.OnRespawnSystem { + @Nonnull + private static final Query QUERY = Archetype.of(Player.getComponentType(), EntityStatMap.getComponentType()); + + public ResetStatsRespawnSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + EntityStatMap entityStatMapComponent = store.getComponent(ref, EntityStatMap.getComponentType()); + + assert entityStatMapComponent != null; + + for (int index = 0; index < entityStatMapComponent.size(); index++) { + EntityStatValue value = entityStatMapComponent.get(index); + if (value != null) { + entityStatMapComponent.resetStatValue(index); + } + } + } + } + + public static class RespawnControllerRespawnSystem extends RespawnSystems.OnRespawnSystem { + public RespawnControllerRespawnSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return Player.getComponentType(); + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + World world = store.getExternalData().getWorld(); + RespawnController respawnController = world.getDeathConfig().getRespawnController(); + respawnController.respawnPlayer(world, ref, commandBuffer); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/damage/commands/DesyncDamageCommand.java b/src/com/hypixel/hytale/server/core/modules/entity/damage/commands/DesyncDamageCommand.java new file mode 100644 index 0000000..54a0b5f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/damage/commands/DesyncDamageCommand.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.server.core.modules.entity.damage.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems; +import javax.annotation.Nonnull; + +public class DesyncDamageCommand extends CommandBase { + public DesyncDamageCommand() { + super("desyncdamage", "server.commands.damage.desyncdamage.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + DamageSystems.FilterUnkillable.CAUSE_DESYNC = !DamageSystems.FilterUnkillable.CAUSE_DESYNC; + context.sendMessage(Message.translation("server.commands.damage.desyncDamageEnabled").param("enabled", DamageSystems.FilterUnkillable.CAUSE_DESYNC)); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/damage/event/KillFeedEvent.java b/src/com/hypixel/hytale/server/core/modules/entity/damage/event/KillFeedEvent.java new file mode 100644 index 0000000..fc6ab7b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/damage/event/KillFeedEvent.java @@ -0,0 +1,107 @@ +package com.hypixel.hytale.server.core.modules.entity.damage.event; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.system.CancellableEcsEvent; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class KillFeedEvent { + public KillFeedEvent() { + } + + public static final class DecedentMessage extends CancellableEcsEvent { + @Nonnull + private final Damage damage; + @Nullable + private Message message = null; + + public DecedentMessage(@Nonnull Damage damage) { + this.damage = damage; + } + + public Damage getDamage() { + return this.damage; + } + + public void setMessage(@Nullable Message message) { + this.message = message; + } + + @Nullable + public Message getMessage() { + return this.message; + } + } + + public static final class Display extends CancellableEcsEvent { + @Nonnull + private final Damage damage; + @Nullable + private String icon; + @Nonnull + private final List broadcastTargets; + + public Display(@Nonnull Damage damage, @Nullable String icon, @Nonnull List broadcastTargets) { + this.damage = damage; + this.icon = icon; + this.broadcastTargets = broadcastTargets; + } + + @Nonnull + public List getBroadcastTargets() { + return this.broadcastTargets; + } + + @Nonnull + public Damage getDamage() { + return this.damage; + } + + @Nullable + public String getIcon() { + return this.icon; + } + + public void setIcon(@Nullable String icon) { + this.icon = icon; + } + } + + public static final class KillerMessage extends CancellableEcsEvent { + @Nonnull + private final Damage damage; + @Nonnull + private final Ref targetRef; + @Nullable + private Message message = null; + + public KillerMessage(@Nonnull Damage damage, @Nonnull Ref targetRef) { + this.damage = damage; + this.targetRef = targetRef; + } + + @Nonnull + public Damage getDamage() { + return this.damage; + } + + @Nonnull + public Ref getTargetRef() { + return this.targetRef; + } + + public void setMessage(@Nullable Message message) { + this.message = message; + } + + @Nullable + public Message getMessage() { + return this.message; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/dynamiclight/DynamicLightSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/dynamiclight/DynamicLightSystems.java new file mode 100644 index 0000000..7653e07 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/dynamiclight/DynamicLightSystems.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.server.core.modules.entity.dynamiclight; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.server.core.modules.entity.component.DynamicLight; +import com.hypixel.hytale.server.core.modules.entity.component.PersistentDynamicLight; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class DynamicLightSystems { + public DynamicLightSystems() { + } + + public static class EntityTrackerRemove extends RefChangeSystem { + private final ComponentType visibleComponentType; + + public EntityTrackerRemove(ComponentType visibleComponentType) { + this.visibleComponentType = visibleComponentType; + } + + @Nonnull + @Override + public Query getQuery() { + return this.visibleComponentType; + } + + @Nonnull + @Override + public ComponentType componentType() { + return DynamicLight.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DynamicLight component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentSet( + @Nonnull Ref ref, + DynamicLight oldComponent, + @Nonnull DynamicLight newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull DynamicLight component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visible = commandBuffer.getComponent(ref, this.visibleComponentType); + if (visible != null) { + for (EntityTrackerSystems.EntityViewer viewer : visible.visibleTo.values()) { + viewer.queueRemove(ref, ComponentUpdateType.DynamicLight); + } + } + } + } + + public static class Setup extends HolderSystem { + private final ComponentType dynamicLightComponentType = DynamicLight.getComponentType(); + private final ComponentType persistentDynamicLightComponentType = PersistentDynamicLight.getComponentType(); + private final Query query = Query.and(this.persistentDynamicLightComponentType, Query.not(this.dynamicLightComponentType)); + + public Setup() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + PersistentDynamicLight persistentLight = holder.getComponent(this.persistentDynamicLightComponentType); + holder.putComponent(this.dynamicLightComponentType, new DynamicLight(persistentLight.getColorLight())); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/hitboxcollision/HitboxCollision.java b/src/com/hypixel/hytale/server/core/modules/entity/hitboxcollision/HitboxCollision.java new file mode 100644 index 0000000..640609f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/hitboxcollision/HitboxCollision.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.core.modules.entity.hitboxcollision; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class HitboxCollision implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(HitboxCollision.class, HitboxCollision::new) + .append( + new KeyedCodec<>("HitboxCollisionConfigIndex", Codec.INTEGER), + (hitboxCollision, integer) -> hitboxCollision.hitboxCollisionConfigIndex = integer, + hitboxCollision -> hitboxCollision.hitboxCollisionConfigIndex + ) + .add() + .build(); + private int hitboxCollisionConfigIndex; + private boolean isNetworkOutdated = true; + + public static ComponentType getComponentType() { + return EntityModule.get().getHitboxCollisionComponentType(); + } + + public HitboxCollision(@Nonnull HitboxCollisionConfig hitboxCollisionConfig) { + this.hitboxCollisionConfigIndex = HitboxCollisionConfig.getAssetMap().getIndexOrDefault(hitboxCollisionConfig.getId(), -1); + } + + protected HitboxCollision() { + } + + public int getHitboxCollisionConfigIndex() { + return this.hitboxCollisionConfigIndex; + } + + public void setHitboxCollisionConfigIndex(int hitboxCollisionConfigIndex) { + this.hitboxCollisionConfigIndex = hitboxCollisionConfigIndex; + } + + public boolean consumeNetworkOutdated() { + boolean temp = this.isNetworkOutdated; + this.isNetworkOutdated = false; + return temp; + } + + @Nonnull + @Override + public Component clone() { + HitboxCollision component = new HitboxCollision(); + component.hitboxCollisionConfigIndex = this.hitboxCollisionConfigIndex; + return component; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/hitboxcollision/HitboxCollisionConfig.java b/src/com/hypixel/hytale/server/core/modules/entity/hitboxcollision/HitboxCollisionConfig.java new file mode 100644 index 0000000..9387fb0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/hitboxcollision/HitboxCollisionConfig.java @@ -0,0 +1,109 @@ +package com.hypixel.hytale.server.core.modules.entity.hitboxcollision; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.CollisionType; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class HitboxCollisionConfig + implements JsonAssetWithMap>, + NetworkSerializable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + HitboxCollisionConfig.class, + HitboxCollisionConfig::new, + Codec.STRING, + (hitboxCollisionConfig, s) -> hitboxCollisionConfig.id = s, + hitboxCollisionConfig -> hitboxCollisionConfig.id, + (hitboxCollisionConfig, data) -> hitboxCollisionConfig.data = data, + hitboxCollisionConfig -> hitboxCollisionConfig.data + ) + .appendInherited( + new KeyedCodec<>("CollisionType", new EnumCodec<>(CollisionType.class)), + (hitboxCollisionConfig, collisionType) -> hitboxCollisionConfig.collisionType = collisionType, + hitboxCollisionConfig -> hitboxCollisionConfig.collisionType, + (hitboxCollisionConfig, parent) -> hitboxCollisionConfig.collisionType = parent.collisionType + ) + .addValidator(Validators.nonNull()) + .documentation("The type of collision, possible values are: Hard, Soft") + .add() + .appendInherited( + new KeyedCodec<>("SoftCollisionOffsetRatio", Codec.FLOAT), + (hitboxCollisionConfig, aFloat) -> hitboxCollisionConfig.softOffsetRatio = aFloat, + hitboxCollisionConfig -> hitboxCollisionConfig.softOffsetRatio, + (hitboxCollisionConfig, parent) -> hitboxCollisionConfig.softOffsetRatio = parent.softOffsetRatio + ) + .documentation("The ratio for how much of the client move offset should be applied when going through a Soft HitboxCollision") + .add() + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(HitboxCollisionConfig::getAssetStore)); + private static AssetStore> ASSET_STORE; + public static final int NO_HITBOX = -1; + protected AssetExtraInfo.Data data; + protected String id; + protected CollisionType collisionType; + protected float softOffsetRatio = 1.0F; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(HitboxCollisionConfig.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public HitboxCollisionConfig(String id) { + this.id = id; + } + + public HitboxCollisionConfig() { + } + + public String getId() { + return this.id; + } + + public CollisionType getCollisionType() { + return this.collisionType; + } + + public float getSoftOffsetRatio() { + return this.softOffsetRatio; + } + + @Nonnull + public com.hypixel.hytale.protocol.HitboxCollisionConfig toPacket() { + com.hypixel.hytale.protocol.HitboxCollisionConfig packet = new com.hypixel.hytale.protocol.HitboxCollisionConfig(); + packet.collisionType = this.collisionType; + packet.softCollisionOffsetRatio = this.softOffsetRatio; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "HitboxCollisionConfig{data=" + + this.data + + ", id='" + + this.id + + "', collisionType=" + + this.collisionType + + ", softOffsetRatio=" + + this.softOffsetRatio + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/hitboxcollision/HitboxCollisionConfigPacketGenerator.java b/src/com/hypixel/hytale/server/core/modules/entity/hitboxcollision/HitboxCollisionConfigPacketGenerator.java new file mode 100644 index 0000000..a68e872 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/hitboxcollision/HitboxCollisionConfigPacketGenerator.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.server.core.modules.entity.hitboxcollision; + +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateHitboxCollisionConfig; +import com.hypixel.hytale.server.core.asset.packet.AssetPacketGenerator; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class HitboxCollisionConfigPacketGenerator + extends AssetPacketGenerator> { + public HitboxCollisionConfigPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket( + @Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets + ) { + Int2ObjectMap hitboxCollisionConfigs = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + hitboxCollisionConfigs.put(assetMap.getIndex(entry.getKey()), entry.getValue().toPacket()); + } + + return new UpdateHitboxCollisionConfig(UpdateType.Init, assetMap.getNextIndex(), hitboxCollisionConfigs); + } + + @Nonnull + public Packet generateUpdatePacket( + @Nonnull IndexedLookupTableAssetMap assetMap, + @Nonnull Map loadedAssets, + @Nonnull AssetUpdateQuery query + ) { + Int2ObjectMap hitboxCollisionConfigs = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + hitboxCollisionConfigs.put(assetMap.getIndex(entry.getKey()), entry.getValue().toPacket()); + } + + return new UpdateHitboxCollisionConfig(UpdateType.AddOrUpdate, assetMap.getNextIndex(), hitboxCollisionConfigs); + } + + @Nonnull + public Packet generateRemovePacket( + @Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed, @Nonnull AssetUpdateQuery query + ) { + Int2ObjectMap hitboxCollisionConfigs = new Int2ObjectOpenHashMap<>(); + + for (String entry : removed) { + hitboxCollisionConfigs.put(assetMap.getIndex(entry), new com.hypixel.hytale.protocol.HitboxCollisionConfig()); + } + + return new UpdateHitboxCollisionConfig(UpdateType.Remove, assetMap.getNextIndex(), hitboxCollisionConfigs); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/hitboxcollision/HitboxCollisionSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/hitboxcollision/HitboxCollisionSystems.java new file mode 100644 index 0000000..b322d15 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/hitboxcollision/HitboxCollisionSystems.java @@ -0,0 +1,177 @@ +package com.hypixel.hytale.server.core.modules.entity.hitboxcollision; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HitboxCollisionSystems { + public HitboxCollisionSystems() { + } + + public static class EntityTrackerRemove extends RefChangeSystem { + private final ComponentType componentType; + private final ComponentType visibleComponentType; + @Nonnull + private final Query query; + + public EntityTrackerRemove( + ComponentType visibleComponentType, ComponentType componentType + ) { + this.visibleComponentType = visibleComponentType; + this.componentType = componentType; + this.query = Query.and(visibleComponentType, componentType); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public ComponentType componentType() { + return this.componentType; + } + + public void onComponentAdded( + @Nonnull Ref ref, + @Nonnull HitboxCollision component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentSet( + @Nonnull Ref ref, + HitboxCollision oldComponent, + @Nonnull HitboxCollision newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, + @Nonnull HitboxCollision component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + for (EntityTrackerSystems.EntityViewer viewer : store.getComponent(ref, this.visibleComponentType).visibleTo.values()) { + viewer.queueRemove(ref, ComponentUpdateType.HitboxCollision); + } + } + } + + public static class EntityTrackerUpdate extends EntityTickingSystem { + private final ComponentType visibleComponentType; + private final ComponentType componentType; + @Nonnull + private final Query query; + + public EntityTrackerUpdate( + ComponentType visibleComponentType, ComponentType componentType + ) { + this.visibleComponentType = visibleComponentType; + this.componentType = componentType; + this.query = Query.and(visibleComponentType, componentType); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visible = archetypeChunk.getComponent(index, this.visibleComponentType); + HitboxCollision hitboxCollision = archetypeChunk.getComponent(index, this.componentType); + if (hitboxCollision.consumeNetworkOutdated()) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), hitboxCollision, visible.visibleTo); + } else if (!visible.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), hitboxCollision, visible.newlyVisibleTo); + } + } + + private static void queueUpdatesFor( + Ref ref, @Nonnull HitboxCollision hitboxCollision, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.HitboxCollision; + update.hitboxCollisionConfigIndex = hitboxCollision.getHitboxCollisionConfigIndex(); + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, update); + } + } + } + + public static class Setup extends HolderSystem { + private final ComponentType componentType; + private final ComponentType playerComponentType; + @Nonnull + private final Query query; + + public Setup(ComponentType componentType, ComponentType playerComponentType) { + this.componentType = componentType; + this.playerComponentType = playerComponentType; + this.query = Query.and(playerComponentType, Query.not(componentType)); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + World world = store.getExternalData().getWorld(); + int hitboxCollisionConfigIndex = world.getGameplayConfig().getPlayerConfig().getHitboxCollisionConfigIndex(); + if (hitboxCollisionConfigIndex != -1) { + holder.addComponent(this.componentType, new HitboxCollision(HitboxCollisionConfig.getAssetMap().getAsset(hitboxCollisionConfigIndex))); + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/item/ItemComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/item/ItemComponent.java new file mode 100644 index 0000000..59734b8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/item/ItemComponent.java @@ -0,0 +1,327 @@ +package com.hypixel.hytale.server.core.modules.entity.item; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.iterator.CircleIterator; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.ColorLight; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemEntityConfig; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; +import com.hypixel.hytale.server.core.modules.entity.BlockMigrationExtraInfo; +import com.hypixel.hytale.server.core.modules.entity.DespawnComponent; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.Intangible; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.modules.physics.component.PhysicsValues; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemComponent implements Component { + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(ItemComponent.class, ItemComponent::new) + .append(new KeyedCodec<>("Item", ItemStack.CODEC), (item, itemStack, extraInfo) -> { + item.itemStack = itemStack; + if (extraInfo instanceof BlockMigrationExtraInfo) { + String newItemId = ((BlockMigrationExtraInfo)extraInfo).getBlockMigration().apply(itemStack.getItemId()); + if (!newItemId.equals(itemStack.getItemId())) { + item.itemStack = new ItemStack(newItemId, itemStack.getQuantity(), itemStack.getMetadata()); + } + } + }, (item, extraInfo) -> item.itemStack) + .add() + .append(new KeyedCodec<>("StackDelay", Codec.FLOAT), (item, v) -> item.mergeDelay = v, item -> item.mergeDelay) + .add() + .append(new KeyedCodec<>("PickupDelay", Codec.FLOAT), (item, v) -> item.pickupDelay = v, item -> item.pickupDelay) + .add() + .append(new KeyedCodec<>("PickupThrottle", Codec.FLOAT), (item, v) -> item.pickupThrottle = v, item -> item.pickupThrottle) + .add() + .append(new KeyedCodec<>("RemovedByPlayerPickup", Codec.BOOLEAN), (item, v) -> item.removedByPlayerPickup = v, item -> item.removedByPlayerPickup) + .add() + .build(); + private static final float DROPPED_ITEM_VERTICAL_BOUNCE_VELOCITY = 3.25F; + private static final float DROPPED_ITEM_HORIZONTAL_BOUNCE_VELOCITY = 3.0F; + public static final float DEFAULT_PICKUP_DELAY = 0.5F; + public static final float PICKUP_DELAY_DROPPED = 1.5F; + public static final float PICKUP_THROTTLE = 0.25F; + public static final float DEFAULT_MERGE_DELAY = 1.5F; + @Nullable + private ItemStack itemStack; + private boolean isNetworkOutdated; + private float mergeDelay = 1.5F; + private float pickupDelay = 0.5F; + private float pickupThrottle; + private boolean removedByPlayerPickup; + private float pickupRange = -1.0F; + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getItemComponentType(); + } + + public ItemComponent() { + } + + public ItemComponent(@Nullable ItemStack itemStack) { + this.itemStack = itemStack; + } + + public ItemComponent(@Nullable ItemStack itemStack, float mergeDelay, float pickupDelay, float pickupThrottle, boolean removedByPlayerPickup) { + this.itemStack = itemStack; + this.mergeDelay = mergeDelay; + this.pickupDelay = pickupDelay; + this.pickupThrottle = pickupThrottle; + this.removedByPlayerPickup = removedByPlayerPickup; + } + + @Nullable + public ItemStack getItemStack() { + return this.itemStack; + } + + public void setItemStack(@Nullable ItemStack itemStack) { + this.itemStack = itemStack; + this.isNetworkOutdated = true; + this.pickupRange = -1.0F; + } + + public void setPickupDelay(float pickupDelay) { + this.pickupDelay = pickupDelay; + } + + public float getPickupRadius(@Nonnull ComponentAccessor componentAccessor) { + if (this.pickupRange < 0.0F) { + World world = componentAccessor.getExternalData().getWorld(); + ItemEntityConfig defaultConfig = world.getGameplayConfig().getItemEntityConfig(); + ItemEntityConfig config = this.itemStack != null ? this.itemStack.getItem().getItemEntityConfig() : null; + this.pickupRange = config != null && config.getPickupRadius() != -1.0F ? config.getPickupRadius() : defaultConfig.getPickupRadius(); + } + + return this.pickupRange; + } + + public float computeLifetimeSeconds(@Nonnull ComponentAccessor componentAccessor) { + ItemEntityConfig itemEntityConfig = this.itemStack != null ? this.itemStack.getItem().getItemEntityConfig() : null; + ItemEntityConfig defaultConfig = componentAccessor.getExternalData().getWorld().getGameplayConfig().getItemEntityConfig(); + Float ttl = itemEntityConfig != null && itemEntityConfig.getTtl() != null ? itemEntityConfig.getTtl() : defaultConfig.getTtl(); + return ttl != null ? ttl : 120.0F; + } + + @Nullable + public ColorLight computeDynamicLight() { + ColorLight dynamicLight = null; + Item item = this.itemStack != null ? this.itemStack.getItem() : null; + if (item != null) { + if (item.hasBlockType()) { + BlockType blockType = BlockType.getAssetMap().getAsset(this.itemStack.getBlockKey()); + if (blockType != null && blockType.getLight() != null) { + dynamicLight = blockType.getLight(); + } + } else if (item.getLight() != null) { + dynamicLight = item.getLight(); + } + } + + return dynamicLight; + } + + public boolean pollPickupDelay(float dt) { + if (this.pickupDelay <= 0.0F) { + return true; + } else { + this.pickupDelay -= dt; + return this.pickupDelay <= 0.0F; + } + } + + public boolean pollPickupThrottle(float dt) { + this.pickupThrottle -= dt; + if (this.pickupThrottle <= 0.0F) { + this.pickupThrottle = 0.25F; + return true; + } else { + return false; + } + } + + public boolean pollMergeDelay(float dt) { + this.mergeDelay -= dt; + if (this.mergeDelay <= 0.0F) { + this.mergeDelay = 1.5F; + return true; + } else { + return false; + } + } + + public boolean canPickUp() { + return this.pickupDelay <= 0.0F; + } + + public boolean isRemovedByPlayerPickup() { + return this.removedByPlayerPickup; + } + + public void setRemovedByPlayerPickup(boolean removedByPlayerPickup) { + this.removedByPlayerPickup = removedByPlayerPickup; + } + + public boolean consumeNetworkOutdated() { + boolean temp = this.isNetworkOutdated; + this.isNetworkOutdated = false; + return temp; + } + + @Nonnull + public ItemComponent clone() { + return new ItemComponent(this.itemStack, this.mergeDelay, this.pickupDelay, this.pickupThrottle, this.removedByPlayerPickup); + } + + @Nonnull + public static Holder[] generateItemDrops( + @Nonnull ComponentAccessor accessor, @Nonnull List itemStacks, @Nonnull Vector3d position, @Nonnull Vector3f rotation + ) { + if (itemStacks.size() == 1) { + Holder itemEntityHolder = generateItemDrop(accessor, itemStacks.getFirst(), position, rotation, 0.0F, 3.25F, 0.0F); + return itemEntityHolder == null ? Holder.emptyArray() : new Holder[]{itemEntityHolder}; + } else { + float randomAngleOffset = ThreadLocalRandom.current().nextFloat() * (float) (Math.PI * 2); + CircleIterator iterator = new CircleIterator(Vector3d.ZERO, 3.0, itemStacks.size(), randomAngleOffset); + return itemStacks.stream().map(item -> { + Vector3d circlePos = iterator.next(); + return generateItemDrop(accessor, item, position, rotation, (float)circlePos.getX(), 3.25F, (float)circlePos.getZ()); + }).filter(Objects::nonNull).toArray(Holder[]::new); + } + } + + @Nullable + public static Holder generateItemDrop( + @Nonnull ComponentAccessor accessor, + @Nullable ItemStack itemStack, + @Nonnull Vector3d position, + @Nonnull Vector3f rotation, + float velocityX, + float velocityY, + float velocityZ + ) { + if (itemStack != null && !itemStack.isEmpty() && itemStack.isValid()) { + Holder holder = EntityStore.REGISTRY.newHolder(); + ItemComponent itemComponent = new ItemComponent(itemStack); + holder.addComponent(getComponentType(), itemComponent); + holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(position, rotation)); + holder.ensureAndGetComponent(Velocity.getComponentType()).set(velocityX, velocityY, velocityZ); + holder.ensureComponent(PhysicsValues.getComponentType()); + holder.ensureComponent(UUIDComponent.getComponentType()); + holder.ensureComponent(Intangible.getComponentType()); + float tempTtl = itemComponent.computeLifetimeSeconds(accessor); + TimeResource timeResource = accessor.getResource(TimeResource.getResourceType()); + holder.addComponent(DespawnComponent.getComponentType(), DespawnComponent.despawnInSeconds(timeResource, tempTtl)); + return holder; + } else { + LOGGER.at(Level.WARNING).log("Attempted to drop invalid item %s at %s", itemStack, position); + return null; + } + } + + @Nonnull + public static Holder generatePickedUpItem( + @Nonnull Ref ref, + @Nonnull ComponentAccessor componentAccessor, + @Nonnull Ref targetRef, + @Nonnull Vector3d targetPosition + ) { + Holder holder = EntityStore.REGISTRY.newHolder(); + TransformComponent itemTransformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert itemTransformComponent != null; + + ItemComponent itemItemComponent = componentAccessor.getComponent(ref, getComponentType()); + + assert itemItemComponent != null; + + HeadRotation itemHeadRotationComponent = componentAccessor.getComponent(ref, HeadRotation.getComponentType()); + + assert itemHeadRotationComponent != null; + + PickupItemComponent pickupItemComponent = new PickupItemComponent(targetRef, targetPosition.clone()); + holder.addComponent(PickupItemComponent.getComponentType(), pickupItemComponent); + holder.addComponent(getComponentType(), itemItemComponent.clone()); + holder.addComponent(TransformComponent.getComponentType(), itemTransformComponent.clone()); + holder.ensureComponent(PreventItemMerging.getComponentType()); + holder.ensureComponent(Intangible.getComponentType()); + holder.addComponent(NetworkId.getComponentType(), new NetworkId(ref.getStore().getExternalData().takeNextNetworkId())); + holder.ensureComponent(EntityStore.REGISTRY.getNonSerializedComponentType()); + return holder; + } + + @Nonnull + public static Holder generatePickedUpItem( + @Nonnull ItemStack itemStack, @Nonnull Vector3d position, @Nonnull ComponentAccessor componentAccessor, @Nonnull Ref targetRef + ) { + Holder holder = EntityStore.REGISTRY.newHolder(); + PickupItemComponent pickupItemComponent = new PickupItemComponent(targetRef, position.clone()); + holder.addComponent(PickupItemComponent.getComponentType(), pickupItemComponent); + holder.addComponent(getComponentType(), new ItemComponent(new ItemStack(itemStack.getItemId()))); + holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(position.clone(), Vector3f.ZERO.clone())); + holder.ensureComponent(PreventItemMerging.getComponentType()); + holder.ensureComponent(Intangible.getComponentType()); + holder.addComponent(NetworkId.getComponentType(), new NetworkId(componentAccessor.getExternalData().takeNextNetworkId())); + holder.ensureComponent(EntityStore.REGISTRY.getNonSerializedComponentType()); + return holder; + } + + @Nullable + public static ItemStack addToItemContainer(@Nonnull Store store, @Nonnull Ref itemRef, @Nonnull ItemContainer itemContainer) { + if (!itemRef.isValid()) { + return null; + } else { + ItemComponent itemComponent = store.getComponent(itemRef, getComponentType()); + if (itemComponent != null && !(itemComponent.pickupDelay > 0.0F)) { + ItemStack itemStack = itemComponent.getItemStack(); + if (itemStack == null) { + return null; + } else { + ItemStackTransaction transaction = itemContainer.addItemStack(itemStack); + ItemStack remainder = transaction.getRemainder(); + if (remainder != null && !remainder.isEmpty()) { + itemComponent.setPickupDelay(0.25F); + itemComponent.setItemStack(remainder); + int quantity = itemStack.getQuantity() - remainder.getQuantity(); + return quantity <= 0 ? null : itemStack.withQuantity(quantity); + } else { + store.removeEntity(itemRef, RemoveReason.REMOVE); + return itemStack; + } + } + } else { + return null; + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/item/ItemMergeSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/item/ItemMergeSystem.java new file mode 100644 index 0000000..295edf7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/item/ItemMergeSystem.java @@ -0,0 +1,144 @@ +package com.hypixel.hytale.server.core.modules.entity.item; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.ColorLight; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.DespawnComponent; +import com.hypixel.hytale.server.core.modules.entity.component.DynamicLight; +import com.hypixel.hytale.server.core.modules.entity.component.Interactable; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import javax.annotation.Nonnull; + +public class ItemMergeSystem extends EntityTickingSystem { + public static final float RADIUS = 2.0F; + @Nonnull + private final ComponentType itemComponentComponentType; + @Nonnull + private final ComponentType interactableComponentType; + @Nonnull + private final ResourceType, EntityStore>> itemSpatialComponent; + @Nonnull + private final Query query; + + public ItemMergeSystem( + @Nonnull ComponentType itemComponentComponentType, + @Nonnull ComponentType interactableComponentType, + @Nonnull ResourceType, EntityStore>> itemSpatialComponent + ) { + this.itemComponentComponentType = itemComponentComponentType; + this.itemSpatialComponent = itemSpatialComponent; + this.interactableComponentType = interactableComponentType; + this.query = Query.and(itemComponentComponentType, Query.not(interactableComponentType), Query.not(PreventItemMerging.getComponentType())); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return false; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + ItemComponent itemComponent = archetypeChunk.getComponent(index, this.itemComponentComponentType); + + assert itemComponent != null; + + ItemStack itemStack = itemComponent.getItemStack(); + if (itemStack != null) { + Item itemAsset = itemStack.getItem(); + int maxStack = itemAsset.getMaxStack(); + if (maxStack > 1 && itemStack.getQuantity() < maxStack) { + if (itemComponent.pollMergeDelay(dt)) { + SpatialResource, EntityStore> spatialResource = store.getResource(this.itemSpatialComponent); + TimeResource timeResource = store.getResource(TimeResource.getResourceType()); + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + spatialResource.getSpatialStructure().ordered(position, 2.0, results); + Ref reference = archetypeChunk.getReferenceTo(index); + + for (Ref otherReference : results) { + if (otherReference.isValid() && !otherReference.equals(reference)) { + ItemComponent otherItemComponent = store.getComponent(otherReference, this.itemComponentComponentType); + + assert otherItemComponent != null; + + ItemStack otherItemStack = otherItemComponent.getItemStack(); + if (otherItemStack != null + && !commandBuffer.getArchetype(otherReference).contains(this.interactableComponentType) + && itemStack.isStackableWith(otherItemStack)) { + int otherQuantity = otherItemStack.getQuantity(); + if (otherQuantity < maxStack) { + int combinedTotal = itemStack.getQuantity() + otherQuantity; + if (combinedTotal <= maxStack) { + commandBuffer.removeEntity(otherReference, RemoveReason.REMOVE); + otherItemComponent.setItemStack(null); + itemStack = itemStack.withQuantity(combinedTotal); + } else { + otherItemComponent.setItemStack(itemStack.withQuantity(combinedTotal - maxStack)); + float newLifetime = otherItemComponent.computeLifetimeSeconds(commandBuffer); + DespawnComponent.trySetDespawn( + commandBuffer, + timeResource, + otherReference, + commandBuffer.getComponent(otherReference, DespawnComponent.getComponentType()), + newLifetime + ); + ColorLight otherItemDynamicLight = otherItemComponent.computeDynamicLight(); + if (otherItemDynamicLight != null) { + DynamicLight otherDynamicLightComponent = commandBuffer.getComponent(otherReference, DynamicLight.getComponentType()); + if (otherDynamicLightComponent != null) { + otherDynamicLightComponent.setColorLight(otherItemDynamicLight); + } else { + commandBuffer.putComponent(otherReference, DynamicLight.getComponentType(), new DynamicLight(otherItemDynamicLight)); + } + } + + itemStack = itemStack.withQuantity(maxStack); + } + + itemComponent.setItemStack(itemStack); + float newLifetime = itemComponent.computeLifetimeSeconds(commandBuffer); + DespawnComponent.trySetDespawn( + commandBuffer, timeResource, reference, archetypeChunk.getComponent(index, DespawnComponent.getComponentType()), newLifetime + ); + if (itemStack.getQuantity() >= maxStack) { + break; + } + } + } + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/item/ItemPhysicsComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/item/ItemPhysicsComponent.java new file mode 100644 index 0000000..5b5b6b8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/item/ItemPhysicsComponent.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.core.modules.entity.item; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.collision.CollisionResult; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +@Deprecated +public class ItemPhysicsComponent implements Component { + public Vector3d scaledVelocity = new Vector3d(); + public CollisionResult collisionResult = new CollisionResult(); + + public static ComponentType getComponentType() { + return EntityModule.get().getItemPhysicsComponentType(); + } + + public ItemPhysicsComponent() { + } + + public ItemPhysicsComponent(Vector3d scaledVelocity, CollisionResult collisionResult) { + this.scaledVelocity = scaledVelocity; + this.collisionResult = collisionResult; + } + + @Nonnull + @Override + public Component clone() { + return new ItemPhysicsComponent(this.scaledVelocity, this.collisionResult); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/item/ItemPhysicsSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/item/ItemPhysicsSystem.java new file mode 100644 index 0000000..9cc66b8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/item/ItemPhysicsSystem.java @@ -0,0 +1,112 @@ +package com.hypixel.hytale.server.core.modules.entity.item; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.collision.BlockCollisionData; +import com.hypixel.hytale.server.core.modules.collision.CollisionModule; +import com.hypixel.hytale.server.core.modules.collision.CollisionResult; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class ItemPhysicsSystem extends EntityTickingSystem { + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private final ComponentType itemPhysicsComponentType; + @Nonnull + private final ComponentType boundingBoxComponentType; + @Nonnull + private final ComponentType velocityComponentType; + @Nonnull + private final ComponentType transformComponentType; + @Nonnull + private final Query query; + + public ItemPhysicsSystem( + @Nonnull ComponentType itemPhysicsComponentType, + @Nonnull ComponentType velocityComponentType, + @Nonnull ComponentType boundingBoxComponentType + ) { + this.itemPhysicsComponentType = itemPhysicsComponentType; + this.velocityComponentType = velocityComponentType; + this.boundingBoxComponentType = boundingBoxComponentType; + this.transformComponentType = TransformComponent.getComponentType(); + this.query = Query.and(itemPhysicsComponentType, boundingBoxComponentType, velocityComponentType); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return false; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + World world = store.getExternalData().getWorld(); + ItemPhysicsComponent itemPhysicsComponent = archetypeChunk.getComponent(index, this.itemPhysicsComponentType); + + assert itemPhysicsComponent != null; + + Velocity velocityComponent = archetypeChunk.getComponent(index, this.velocityComponentType); + + assert velocityComponent != null; + + TransformComponent transformComponent = archetypeChunk.getComponent(index, this.transformComponentType); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + Vector3d scaledVelocity = itemPhysicsComponent.scaledVelocity; + CollisionResult collisionResult = itemPhysicsComponent.collisionResult; + velocityComponent.assignVelocityTo(scaledVelocity).scale(dt); + BoundingBox boundingBoxComponent = archetypeChunk.getComponent(index, this.boundingBoxComponentType); + + assert boundingBoxComponent != null; + + Box boundingBox = boundingBoxComponent.getBoundingBox(); + if (CollisionModule.isBelowMovementThreshold(scaledVelocity)) { + CollisionModule.findBlockCollisionsShortDistance(world, boundingBox, position, scaledVelocity, collisionResult); + } else { + CollisionModule.findBlockCollisionsIterative(world, boundingBox, position, scaledVelocity, true, collisionResult); + } + + BlockCollisionData blockCollisionData = collisionResult.getFirstBlockCollision(); + if (blockCollisionData != null && blockCollisionData.collisionNormal.equals(Vector3d.UP)) { + velocityComponent.setZero(); + position.assign(blockCollisionData.collisionPoint); + } else { + velocityComponent.assignVelocityTo(scaledVelocity).scale(dt); + position.add(scaledVelocity); + } + + collisionResult.reset(); + if (position.getY() < -32.0) { + LOGGER.at(Level.WARNING).log("Item fell out of the world %s", archetypeChunk.getReferenceTo(index)); + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/item/ItemPrePhysicsSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/item/ItemPrePhysicsSystem.java new file mode 100644 index 0000000..671fffe --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/item/ItemPrePhysicsSystem.java @@ -0,0 +1,172 @@ +package com.hypixel.hytale.server.core.modules.entity.item; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.NearestBlockUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.modules.collision.CollisionMath; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.physics.component.PhysicsValues; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemPrePhysicsSystem extends EntityTickingSystem { + public static final NearestBlockUtil.IterationElement[] SEARCH_ELEMENTS = new NearestBlockUtil.IterationElement[]{ + new NearestBlockUtil.IterationElement(-1, 0, 0, x -> 0.0, y -> y, z -> z), + new NearestBlockUtil.IterationElement(1, 0, 0, x -> 1.0, y -> y, z -> z), + new NearestBlockUtil.IterationElement(0, 0, -1, x -> x, y -> y, z -> 0.0), + new NearestBlockUtil.IterationElement(0, 0, 1, x -> x, y -> y, z -> 1.0) + }; + public static final double VERTICAL_CLIMB_SCALE = 7.0; + @Nonnull + private final ComponentType boundingBoxComponentType; + @Nonnull + private final ComponentType velocityComponentType; + @Nonnull + private final ComponentType transformComponentType; + @Nonnull + private final ComponentType physicsValuesComponentType; + @Nonnull + private final Query query; + + public ItemPrePhysicsSystem( + @Nonnull ComponentType itemComponentType, + @Nonnull ComponentType boundingBoxComponentType, + @Nonnull ComponentType velocityComponentType, + @Nonnull ComponentType transformComponentType, + @Nonnull ComponentType physicsValuesComponentType + ) { + this.physicsValuesComponentType = physicsValuesComponentType; + this.boundingBoxComponentType = boundingBoxComponentType; + this.transformComponentType = transformComponentType; + this.velocityComponentType = velocityComponentType; + this.query = Query.and( + itemComponentType, TransformComponent.getComponentType(), boundingBoxComponentType, velocityComponentType, physicsValuesComponentType + ); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Velocity velocityComponent = archetypeChunk.getComponent(index, this.velocityComponentType); + + assert velocityComponent != null; + + TransformComponent transformComponent = archetypeChunk.getComponent(index, this.transformComponentType); + + assert transformComponent != null; + + PhysicsValues physicsValuesComponent = archetypeChunk.getComponent(index, this.physicsValuesComponentType); + + assert physicsValuesComponent != null; + + BoundingBox boundingBoxComponent = archetypeChunk.getComponent(index, this.boundingBoxComponentType); + + assert boundingBoxComponent != null; + + Box boundingBox = boundingBoxComponent.getBoundingBox(); + World world = commandBuffer.getExternalData().getWorld(); + ChunkStore chunkStore = world.getChunkStore(); + Ref chunkRef = transformComponent.getChunkRef(); + WorldChunk worldChunkComponent; + if (chunkRef != null && chunkRef.isValid()) { + worldChunkComponent = chunkStore.getStore().getComponent(chunkRef, WorldChunk.getComponentType()); + } else { + worldChunkComponent = null; + } + + moveOutOfBlock(worldChunkComponent, transformComponent.getPosition(), velocityComponent, boundingBox); + applyGravity(dt, boundingBox, physicsValuesComponent, transformComponent.getPosition(), velocityComponent); + } + + public static void moveOutOfBlock(@Nullable WorldChunk chunk, @Nonnull Vector3d position, @Nonnull Velocity velocityComponent, @Nonnull Box boundingBox) { + if (chunk != null) { + int x = MathUtil.floor(position.x); + int y = MathUtil.floor(position.y); + int z = MathUtil.floor(position.z); + BlockType blockType = chunk.getBlockType(x, y, z); + + assert blockType != null; + + if (blockType.getMaterial() == BlockMaterial.Solid) { + int rotation = chunk.getRotationIndex(x, y, z); + BlockBoundingBoxes.RotatedVariantBoxes blockBoundingBoxes = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex()).get(rotation); + boolean overlap = false; + + for (Box detailBox : blockBoundingBoxes.getDetailBoxes()) { + if (CollisionMath.isOverlapping(CollisionMath.intersectAABBs(x, y, z, detailBox, position.x, position.y, position.z, boundingBox))) { + overlap = true; + break; + } + } + + if (overlap) { + Vector3i nearestBlock = NearestBlockUtil.findNearestBlock(SEARCH_ELEMENTS, position, (block, _worldChunk) -> { + BlockType testBlockType = _worldChunk.getBlockType(block); + return testBlockType.getMaterial() != BlockMaterial.Solid; + }, chunk); + if (nearestBlock != null) { + position.assign(nearestBlock.x + 0.5, nearestBlock.y, nearestBlock.z + 0.5); + } else { + velocityComponent.setY(7.0 * blockBoundingBoxes.getBoundingBox().height()); + } + } + } + } + } + + public static void applyGravity(float dt, @Nullable Box boundingBox, @Nonnull PhysicsValues values, @Nonnull Vector3d position, @Nonnull Velocity velocity) { + double area = 1.0; + if (boundingBox != null) { + area = Math.abs(boundingBox.width() * boundingBox.depth()); + } + + double density = PhysicsMath.getRelativeDensity(position, boundingBox); + double terminalVelocity = PhysicsMath.getTerminalVelocity(values.getMass(), density, area, values.getDragCoefficient()); + double gravityStep = PhysicsMath.getAcceleration(velocity.getY(), terminalVelocity) * dt; + if (!values.isInvertedGravity()) { + terminalVelocity *= -1.0; + gravityStep *= -1.0; + } + + if (velocity.getY() < terminalVelocity && gravityStep > 0.0) { + velocity.setY(Math.min(velocity.getY() + gravityStep, terminalVelocity)); + } else if (velocity.getY() > terminalVelocity && gravityStep < 0.0) { + velocity.setY(Math.max(velocity.getY() + gravityStep, terminalVelocity)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/item/ItemSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/item/ItemSystems.java new file mode 100644 index 0000000..f3dbe61 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/item/ItemSystems.java @@ -0,0 +1,143 @@ +package com.hypixel.hytale.server.core.modules.entity.item; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.protocol.ColorLight; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.DynamicLight; +import com.hypixel.hytale.server.core.modules.entity.component.EntityScaleComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemSystems { + public ItemSystems() { + } + + public static class EnsureRequiredComponents extends HolderSystem { + private static final ComponentType ITEM_COMPONENT_TYPE = ItemComponent.getComponentType(); + + public EnsureRequiredComponents() { + } + + @Nonnull + @Override + public Query getQuery() { + return ITEM_COMPONENT_TYPE; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + if (!holder.getArchetype().contains(NetworkId.getComponentType())) { + holder.addComponent(NetworkId.getComponentType(), new NetworkId(store.getExternalData().takeNextNetworkId())); + } + + holder.ensureComponent(ItemPhysicsComponent.getComponentType()); + holder.putComponent(BoundingBox.getComponentType(), new BoundingBox(Box.horizontallyCentered(0.5, 0.5, 0.5))); + ItemComponent itemComponent = holder.getComponent(ItemComponent.getComponentType()); + + assert itemComponent != null; + + ColorLight itemDynamicLight = itemComponent.computeDynamicLight(); + if (itemDynamicLight != null) { + holder.putComponent(DynamicLight.getComponentType(), new DynamicLight(itemDynamicLight)); + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + } + + public static class TrackerSystem extends EntityTickingSystem { + @Nonnull + private final ComponentType visibleComponentType; + @Nonnull + private final Query query; + + public TrackerSystem(@Nonnull ComponentType visibleComponentType) { + this.visibleComponentType = visibleComponentType; + this.query = Query.and(visibleComponentType, ItemComponent.getComponentType()); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.visibleComponentType); + + assert visibleComponent != null; + + ItemComponent itemComponent = archetypeChunk.getComponent(index, ItemComponent.getComponentType()); + + assert itemComponent != null; + + float entityScale = 0.0F; + EntityScaleComponent entityScaleComponent = archetypeChunk.getComponent(index, EntityScaleComponent.getComponentType()); + if (entityScaleComponent != null) { + entityScale = entityScaleComponent.getScale(); + } + + if (itemComponent.consumeNetworkOutdated()) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), itemComponent, entityScale, visibleComponent.visibleTo); + } else if (!visibleComponent.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), itemComponent, entityScale, visibleComponent.newlyVisibleTo); + } + } + + private static void queueUpdatesFor( + @Nonnull Ref ref, + @Nonnull ItemComponent item, + float entityScale, + @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Item; + ItemStack itemStack = item.getItemStack(); + update.item = itemStack != null ? itemStack.toPacket() : null; + update.entityScale = entityScale; + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, update); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/item/PickupItemComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/item/PickupItemComponent.java new file mode 100644 index 0000000..6285808 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/item/PickupItemComponent.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.server.core.modules.entity.item; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PickupItemComponent implements Component { + public static final float PICKUP_TRAVEL_TIME_DEFAULT = 0.15F; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(PickupItemComponent.class, PickupItemComponent::new).build(); + private Ref targetRef; + private Vector3d startPosition; + private float originalLifeTime; + private float lifeTime = 0.15F; + private boolean finished = false; + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getPickupItemComponentType(); + } + + public PickupItemComponent() { + } + + public PickupItemComponent(@Nonnull Ref targetRef, @Nonnull Vector3d startPosition) { + this(targetRef, startPosition, 0.15F); + } + + public PickupItemComponent(@Nonnull Ref targetRef, @Nonnull Vector3d startPosition, float lifeTime) { + this.targetRef = targetRef; + this.startPosition = startPosition; + this.lifeTime = lifeTime; + this.originalLifeTime = lifeTime; + } + + public PickupItemComponent(@Nonnull PickupItemComponent pickupItemComponent) { + this.targetRef = pickupItemComponent.targetRef; + this.lifeTime = pickupItemComponent.lifeTime; + this.startPosition = pickupItemComponent.startPosition; + this.originalLifeTime = pickupItemComponent.originalLifeTime; + this.finished = pickupItemComponent.finished; + } + + public boolean hasFinished() { + return this.finished; + } + + public void setFinished(boolean finished) { + this.finished = finished; + } + + public void decreaseLifetime(float amount) { + this.lifeTime -= amount; + } + + public float getLifeTime() { + return this.lifeTime; + } + + public float getOriginalLifeTime() { + return this.originalLifeTime; + } + + public void setInitialLifeTime(float lifeTimeS) { + this.originalLifeTime = lifeTimeS; + this.lifeTime = lifeTimeS; + } + + @Nonnull + public Vector3d getStartPosition() { + return this.startPosition; + } + + @Nonnull + public Ref getTargetRef() { + return this.targetRef; + } + + @Nonnull + public PickupItemComponent clone() { + return new PickupItemComponent(this); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/item/PickupItemSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/item/PickupItemSystem.java new file mode 100644 index 0000000..33e7e46 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/item/PickupItemSystem.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.server.core.modules.entity.item; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PickupItemSystem extends EntityTickingSystem { + private static final float EYE_HEIGHT_SCALE = 5.0F; + @Nonnull + private final ComponentType pickupItemComponentType; + @Nonnull + private final ComponentType transformComponentType; + @Nonnull + private final Query query; + + public PickupItemSystem( + @Nonnull ComponentType pickupItemComponentType, + @Nonnull ComponentType transformComponentType + ) { + this.pickupItemComponentType = pickupItemComponentType; + this.transformComponentType = transformComponentType; + this.query = Query.and(pickupItemComponentType, transformComponentType); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + PickupItemComponent pickupItemComponent = archetypeChunk.getComponent(index, this.pickupItemComponentType); + + assert pickupItemComponent != null; + + if (pickupItemComponent.hasFinished()) { + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + } else { + Ref targetRef = pickupItemComponent.getTargetRef(); + if (!targetRef.isValid()) { + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + } else { + TransformComponent transformComponent = archetypeChunk.getComponent(index, this.transformComponentType); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + TransformComponent targetTransformComponent = commandBuffer.getComponent(targetRef, this.transformComponentType); + + assert targetTransformComponent != null; + + Vector3d targetPosition = targetTransformComponent.getPosition().clone(); + ModelComponent targetModelComponent = commandBuffer.getComponent(targetRef, ModelComponent.getComponentType()); + if (targetModelComponent != null) { + float targetModelEyeHeight = targetModelComponent.getModel().getEyeHeight(targetRef, commandBuffer); + targetPosition.add(0.0, targetModelEyeHeight / 5.0F, 0.0); + } + + if (updateMovement(pickupItemComponent, position, targetPosition, dt)) { + pickupItemComponent.setFinished(true); + } + } + } + } + + private static boolean updateMovement(@Nonnull PickupItemComponent pickupItemComponent, @Nonnull Vector3d current, @Nonnull Vector3d target, float dt) { + float remainingTime = pickupItemComponent.getLifeTime(); + float originalLifeTime = pickupItemComponent.getOriginalLifeTime(); + float progress = 1.0F - remainingTime / originalLifeTime; + if (progress >= 1.0F) { + current.assign(target); + return true; + } else { + current.assign(Vector3d.lerp(pickupItemComponent.getStartPosition(), target, progress)); + pickupItemComponent.decreaseLifetime(dt); + return false; + } + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/item/PreventItemMerging.java b/src/com/hypixel/hytale/server/core/modules/entity/item/PreventItemMerging.java new file mode 100644 index 0000000..32a26c3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/item/PreventItemMerging.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.core.modules.entity.item; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PreventItemMerging implements Component { + @Nonnull + public static final PreventItemMerging INSTANCE = new PreventItemMerging(); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(PreventItemMerging.class, () -> INSTANCE).build(); + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getPreventItemMergingType(); + } + + private PreventItemMerging() { + } + + @Nonnull + @Override + public Component clone() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/item/PreventPickup.java b/src/com/hypixel/hytale/server/core/modules/entity/item/PreventPickup.java new file mode 100644 index 0000000..ab60072 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/item/PreventPickup.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.core.modules.entity.item; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PreventPickup implements Component { + @Nonnull + public static final PreventPickup INSTANCE = new PreventPickup(); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(PreventPickup.class, () -> INSTANCE).build(); + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getPreventPickupComponentType(); + } + + private PreventPickup() { + } + + @Nonnull + @Override + public Component clone() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/livingentity/LivingEntityEffectClearChangesSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/livingentity/LivingEntityEffectClearChangesSystem.java new file mode 100644 index 0000000..8708b0f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/livingentity/LivingEntityEffectClearChangesSystem.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.server.core.modules.entity.livingentity; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import javax.annotation.Nonnull; + +public class LivingEntityEffectClearChangesSystem extends EntityTickingSystem { + @Nonnull + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, EntityTrackerSystems.EffectControllerSystem.class) + ); + + public LivingEntityEffectClearChangesSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return EffectControllerComponent.getComponentType(); + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EffectControllerComponent effectControllerComponent = archetypeChunk.getComponent(index, EffectControllerComponent.getComponentType()); + + assert effectControllerComponent != null; + + effectControllerComponent.clearChanges(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/livingentity/LivingEntityEffectSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/livingentity/LivingEntityEffectSystem.java new file mode 100644 index 0000000..9cd202e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/livingentity/LivingEntityEffectSystem.java @@ -0,0 +1,150 @@ +package com.hypixel.hytale.server.core.modules.entity.livingentity; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.DisableProcessingAssert; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.entity.effect.ActiveEntityEffect; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageModule; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.accessor.LocalCachedChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class LivingEntityEffectSystem extends EntityTickingSystem implements DisableProcessingAssert { + public LivingEntityEffectSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return EffectControllerComponent.getComponentType(); + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return false; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EffectControllerComponent effectControllerComponent = archetypeChunk.getComponent(index, EffectControllerComponent.getComponentType()); + + assert effectControllerComponent != null; + + Int2ObjectMap activeEffects = effectControllerComponent.getActiveEffects(); + if (!activeEffects.isEmpty()) { + IndexedLookupTableAssetMap entityEffectAssetMap = EntityEffect.getAssetMap(); + Ref entityRef = archetypeChunk.getReferenceTo(index); + ObjectIterator iterator = activeEffects.values().iterator(); + EntityStatMap entityStatMapComponent = commandBuffer.getComponent(entityRef, EntityStatMap.getComponentType()); + boolean invalidated = false; + boolean invulnerable = false; + + while (iterator.hasNext()) { + ActiveEntityEffect activeEntityEffect = iterator.next(); + int entityEffectIndex = activeEntityEffect.getEntityEffectIndex(); + EntityEffect entityEffect = entityEffectAssetMap.getAsset(entityEffectIndex); + if (entityEffect == null) { + iterator.remove(); + invalidated = true; + } else if (!canApplyEffect(entityRef, entityEffect, commandBuffer)) { + iterator.remove(); + invalidated = true; + } else { + float tickDelta = Math.min(activeEntityEffect.getRemainingDuration(), dt); + activeEntityEffect.tick(commandBuffer, entityRef, entityEffect, entityStatMapComponent, tickDelta); + if (activeEffects.isEmpty()) { + return; + } + + if (!activeEntityEffect.isInfinite() && activeEntityEffect.getRemainingDuration() <= 0.0F) { + iterator.remove(); + effectControllerComponent.tryResetModelChange(entityRef, activeEntityEffect.getEntityEffectIndex(), commandBuffer); + invalidated = true; + } + + if (activeEntityEffect.isInvulnerable()) { + invulnerable = true; + } + } + } + + effectControllerComponent.setInvulnerable(invulnerable); + if (invalidated) { + effectControllerComponent.invalidateCache(); + if (EntityUtils.getEntity(index, archetypeChunk) instanceof LivingEntity livingEntity) { + livingEntity.getStatModifiersManager().setRecalculate(true); + } + } + } + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getGatherDamageGroup(); + } + + public static boolean canApplyEffect( + @Nonnull Ref ownerRef, @Nonnull EntityEffect entityEffect, @Nonnull ComponentAccessor componentAccessor + ) { + TransformComponent transformComponent = componentAccessor.getComponent(ownerRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + BoundingBox boundingBoxComponent = componentAccessor.getComponent(ownerRef, BoundingBox.getComponentType()); + + assert boundingBoxComponent != null; + + Vector3d position = transformComponent.getPosition(); + Box boundingBox = boundingBoxComponent.getBoundingBox(); + World world = componentAccessor.getExternalData().getWorld(); + if ("Burn".equals(entityEffect.getId())) { + Ref chunkRef = transformComponent.getChunkRef(); + if (chunkRef != null && chunkRef.isValid()) { + Store chunkComponentStore = world.getChunkStore().getStore(); + WorldChunk worldChunkComponent = chunkComponentStore.getComponent(chunkRef, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + LocalCachedChunkAccessor chunkAccessor = LocalCachedChunkAccessor.atChunkCoords(world, worldChunkComponent.getX(), worldChunkComponent.getZ(), 1); + return boundingBox.forEachBlock(position, chunkAccessor, (x, y, z, _chunkAccessor) -> { + WorldChunk localChunk = _chunkAccessor.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(x, z)); + return localChunk == null ? true : !localChunk.getBlockType(x, y, z).getId().contains("Fluid_Water"); + }); + } else { + return false; + } + } else { + return true; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/ApplyRandomSkinPersistedComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/player/ApplyRandomSkinPersistedComponent.java new file mode 100644 index 0000000..16be729 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/ApplyRandomSkinPersistedComponent.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ApplyRandomSkinPersistedComponent implements Component { + public static final ApplyRandomSkinPersistedComponent INSTANCE = new ApplyRandomSkinPersistedComponent(); + public static final BuilderCodec CODEC = BuilderCodec.builder(ApplyRandomSkinPersistedComponent.class, () -> INSTANCE) + .build(); + + public static ComponentType getComponentType() { + return EntityModule.get().getApplyRandomSkinPersistedComponent(); + } + + public ApplyRandomSkinPersistedComponent() { + } + + @Nonnull + @Override + public Component clone() { + return this; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/ChunkTracker.java b/src/com/hypixel/hytale/server/core/modules/entity/player/ChunkTracker.java new file mode 100644 index 0000000..31bb12d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/ChunkTracker.java @@ -0,0 +1,642 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.common.fastutil.HLongOpenHashSet; +import com.hypixel.hytale.common.fastutil.HLongSet; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.iterator.CircleSpiralIterator; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.packets.world.UnloadChunk; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkFlag; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.StampedLock; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChunkTracker implements Component { + public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() + .register("ViewRadius", tracker -> tracker.chunkViewRadius, Codec.INTEGER) + .register("SentViewRadius", tracker -> tracker.sentViewRadius, Codec.INTEGER) + .register("HotRadius", tracker -> tracker.hotRadius, Codec.INTEGER) + .register("LoadedChunksCount", ChunkTracker::getLoadedChunksCount, Codec.INTEGER) + .register("LoadingChunksCount", ChunkTracker::getLoadingChunksCount, Codec.INTEGER) + .register("MaxChunksPerSecond", ChunkTracker::getMaxChunksPerSecond, Codec.INTEGER) + .register("MaxChunksPerTick", ChunkTracker::getMaxChunksPerTick, Codec.INTEGER) + .register("ReadyForChunks", ChunkTracker::isReadyForChunks, Codec.BOOLEAN) + .register("LastChunkX", tracker -> tracker.lastChunkX, Codec.INTEGER) + .register("LastChunkZ", tracker -> tracker.lastChunkZ, Codec.INTEGER); + public static final int MAX_CHUNKS_PER_SECOND_LOCAL = 256; + public static final int MAX_CHUNKS_PER_SECOND_LAN = 128; + public static final int MAX_CHUNKS_PER_SECOND = 36; + public static final int MAX_CHUNKS_PER_TICK = 4; + public static final int MIN_LOADED_CHUNKS_RADIUS = 2; + public static final int MAX_HOT_LOADED_CHUNKS_RADIUS = 8; + public static final long MAX_FAILURE_BACKOFF_NANOS = TimeUnit.SECONDS.toNanos(10L); + @Nullable + private TransformComponent transformComponent; + private int chunkViewRadius; + private final CircleSpiralIterator spiralIterator = new CircleSpiralIterator(); + private final StampedLock loadedLock = new StampedLock(); + private final HLongSet loading = new HLongOpenHashSet(); + private final HLongSet loaded = new HLongOpenHashSet(); + private final HLongSet reload = new HLongOpenHashSet(); + private int maxChunksPerSecond; + private float inverseMaxChunksPerSecond; + private int maxChunksPerTick; + private int minLoadedChunksRadius; + private int maxHotLoadedChunksRadius; + private float accumulator; + private int sentViewRadius; + private int hotRadius; + private int lastChunkX; + private int lastChunkZ; + private boolean readyForChunks; + + public static ComponentType getComponentType() { + return EntityModule.get().getChunkTrackerComponentType(); + } + + public ChunkTracker() { + this.minLoadedChunksRadius = 2; + this.maxHotLoadedChunksRadius = 8; + this.maxChunksPerTick = 4; + } + + private ChunkTracker(@Nonnull ChunkTracker other) { + this.copyFrom(other); + } + + public void unloadAll(@Nonnull PlayerRef playerRefComponent) { + long stamp = this.loadedLock.writeLock(); + + try { + this.loading.clear(); + LongIterator iterator = this.loaded.iterator(); + + while (iterator.hasNext()) { + long chunkIndex = iterator.nextLong(); + int chunkX = ChunkUtil.xOfChunkIndex(chunkIndex); + int chunkZ = ChunkUtil.zOfChunkIndex(chunkIndex); + playerRefComponent.getPacketHandler().writeNoCache(new UnloadChunk(chunkX, chunkZ)); + } + + this.loaded.clear(); + this.sentViewRadius = 0; + this.hotRadius = 0; + } finally { + this.loadedLock.unlockWrite(stamp); + } + } + + public void clear() { + long stamp = this.loadedLock.writeLock(); + + try { + this.loading.clear(); + this.loaded.clear(); + this.sentViewRadius = 0; + this.hotRadius = 0; + } finally { + this.loadedLock.unlockWrite(stamp); + } + } + + public void tick(@Nonnull Ref playerRef, float dt, @Nonnull CommandBuffer commandBuffer) { + if (this.readyForChunks) { + World world = commandBuffer.getExternalData().getWorld(); + TransformComponent transformComponent = this.transformComponent = commandBuffer.getComponent(playerRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Player playerComponent = commandBuffer.getComponent(playerRef, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = commandBuffer.getComponent(playerRef, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + int chunkViewRadius = this.chunkViewRadius = playerComponent.getViewRadius(); + Vector3d position = transformComponent.getPosition(); + int chunkX = MathUtil.floor(position.getX()) >> 5; + int chunkZ = MathUtil.floor(position.getZ()) >> 5; + int xDiff = Math.abs(this.lastChunkX - chunkX); + int zDiff = Math.abs(this.lastChunkZ - chunkZ); + int chunkMoveDistance = xDiff <= 0 && zDiff <= 0 ? 0 : (int)Math.ceil(Math.sqrt(xDiff * xDiff + zDiff * zDiff)); + this.sentViewRadius = Math.max(0, this.sentViewRadius - chunkMoveDistance); + this.hotRadius = Math.max(0, this.hotRadius - chunkMoveDistance); + this.lastChunkX = chunkX; + this.lastChunkZ = chunkZ; + if (this.sentViewRadius != chunkViewRadius || this.hotRadius != Math.min(this.maxHotLoadedChunksRadius, chunkViewRadius) || !this.reload.isEmpty()) { + if (this.sentViewRadius > chunkViewRadius) { + this.sentViewRadius = chunkViewRadius; + } + + if (this.hotRadius > chunkViewRadius) { + this.hotRadius = chunkViewRadius; + } + + ChunkStore chunkStore = world.getChunkStore(); + int minLoadedRadius = Math.max(this.minLoadedChunksRadius, chunkViewRadius); + int minLoadedRadiusSq = minLoadedRadius * minLoadedRadius; + long stamp = this.loadedLock.writeLock(); + + try { + this.loaded.removeIf(ChunkTracker::tryUnloadChunk, minLoadedRadiusSq, chunkX, chunkZ, playerRefComponent, this.loading); + this.accumulator += dt; + int toLoad = Math.min((int)(this.maxChunksPerSecond * this.accumulator), this.maxChunksPerTick); + int loadingSize = this.loading.size(); + toLoad -= loadingSize; + if (!this.reload.isEmpty()) { + LongIterator iterator = this.reload.iterator(); + + while (iterator.hasNext()) { + long chunkCoordinates = iterator.nextLong(); + if (!chunkStore.isChunkOnBackoff(chunkCoordinates, MAX_FAILURE_BACKOFF_NANOS) && this.loading.add(chunkCoordinates)) { + this.tryLoadChunkAsync(chunkStore, playerRefComponent, chunkCoordinates, transformComponent, commandBuffer); + iterator.remove(); + toLoad--; + this.accumulator = this.accumulator - this.inverseMaxChunksPerSecond; + } + } + } + + if (this.sentViewRadius < minLoadedRadius) { + boolean areAllLoaded = true; + this.spiralIterator.init(chunkX, chunkZ, this.sentViewRadius, minLoadedRadius); + + while (toLoad > 0 && this.spiralIterator.hasNext()) { + long chunkCoordinates = this.spiralIterator.next(); + if (!this.loaded.contains(chunkCoordinates)) { + areAllLoaded = false; + if (!chunkStore.isChunkOnBackoff(chunkCoordinates, MAX_FAILURE_BACKOFF_NANOS) && this.loading.add(chunkCoordinates)) { + this.tryLoadChunkAsync(chunkStore, playerRefComponent, chunkCoordinates, transformComponent, commandBuffer); + toLoad--; + this.accumulator = this.accumulator - this.inverseMaxChunksPerSecond; + } + } else if (areAllLoaded) { + this.sentViewRadius = this.spiralIterator.getCompletedRadius(); + } + } + + if (areAllLoaded) { + this.sentViewRadius = this.spiralIterator.getCompletedRadius(); + } + } + } finally { + this.loadedLock.unlockWrite(stamp); + } + + int var29 = Math.min(this.maxHotLoadedChunksRadius, this.sentViewRadius); + if (this.hotRadius < var29) { + this.spiralIterator.init(chunkX, chunkZ, this.hotRadius, var29); + + while (this.spiralIterator.hasNext()) { + Ref chunkReference = chunkStore.getChunkReference(this.spiralIterator.next()); + if (chunkReference != null && chunkReference.isValid()) { + WorldChunk worldChunkComponent = chunkStore.getStore().getComponent(chunkReference, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + if (!worldChunkComponent.is(ChunkFlag.TICKING)) { + commandBuffer.run(_store -> worldChunkComponent.setFlag(ChunkFlag.TICKING, true)); + } + } + } + + this.hotRadius = var29; + } + + if (this.sentViewRadius == chunkViewRadius) { + this.accumulator = 0.0F; + } + } + } + } + + public boolean isLoaded(long indexChunk) { + long stamp = this.loadedLock.readLock(); + + boolean var5; + try { + var5 = this.loaded.contains(indexChunk); + } finally { + this.loadedLock.unlockRead(stamp); + } + + return var5; + } + + public void removeForReload(long indexChunk) { + if (this.shouldBeVisible(indexChunk)) { + long stamp = this.loadedLock.writeLock(); + + try { + this.reload.add(indexChunk); + } finally { + this.loadedLock.unlockWrite(stamp); + } + } + } + + public boolean shouldBeVisible(long chunkCoordinates) { + Vector3d position = this.transformComponent.getPosition(); + int chunkX = MathUtil.floor(position.getX()) >> 5; + int chunkZ = MathUtil.floor(position.getZ()) >> 5; + int x = ChunkUtil.xOfChunkIndex(chunkCoordinates); + int z = ChunkUtil.zOfChunkIndex(chunkCoordinates); + int minLoadedRadius = Math.max(this.minLoadedChunksRadius, this.chunkViewRadius); + return shouldBeVisible(minLoadedRadius * minLoadedRadius, chunkX, chunkZ, x, z); + } + + public ChunkTracker.ChunkVisibility getChunkVisibility(long indexChunk) { + Vector3d position = this.transformComponent.getPosition(); + int chunkX = MathUtil.floor(position.getX()) >> 5; + int chunkZ = MathUtil.floor(position.getZ()) >> 5; + int x = ChunkUtil.xOfChunkIndex(indexChunk); + int z = ChunkUtil.zOfChunkIndex(indexChunk); + int xDiff = Math.abs(x - chunkX); + int zDiff = Math.abs(z - chunkZ); + int distanceSq = xDiff * xDiff + zDiff * zDiff; + int minLoadedRadius = Math.max(this.minLoadedChunksRadius, this.chunkViewRadius); + boolean shouldBeVisible = distanceSq <= minLoadedRadius * minLoadedRadius; + if (shouldBeVisible) { + boolean isHot = distanceSq <= this.maxHotLoadedChunksRadius * this.maxHotLoadedChunksRadius; + return isHot ? ChunkTracker.ChunkVisibility.HOT : ChunkTracker.ChunkVisibility.COLD; + } else { + return ChunkTracker.ChunkVisibility.NONE; + } + } + + public int getMaxChunksPerSecond() { + return this.maxChunksPerSecond; + } + + public void setMaxChunksPerSecond(int maxChunksPerSecond) { + this.maxChunksPerSecond = maxChunksPerSecond; + this.inverseMaxChunksPerSecond = 1.0F / maxChunksPerSecond; + } + + public void setDefaultMaxChunksPerSecond(@Nonnull PlayerRef playerRef) { + if (playerRef.getPacketHandler().isLocalConnection()) { + this.maxChunksPerSecond = 256; + } else if (playerRef.getPacketHandler().isLANConnection()) { + this.maxChunksPerSecond = 128; + } else { + this.maxChunksPerSecond = 36; + } + + this.inverseMaxChunksPerSecond = 1.0F / this.maxChunksPerSecond; + } + + public int getMaxChunksPerTick() { + return this.maxChunksPerTick; + } + + public void setMaxChunksPerTick(int maxChunksPerTick) { + this.maxChunksPerTick = maxChunksPerTick; + } + + public int getMinLoadedChunksRadius() { + return this.minLoadedChunksRadius; + } + + public void setMinLoadedChunksRadius(int minLoadedChunksRadius) { + this.minLoadedChunksRadius = minLoadedChunksRadius; + } + + public int getMaxHotLoadedChunksRadius() { + return this.maxHotLoadedChunksRadius; + } + + public void setMaxHotLoadedChunksRadius(int maxHotLoadedChunksRadius) { + this.maxHotLoadedChunksRadius = maxHotLoadedChunksRadius; + } + + public int getLoadedChunksCount() { + long stamp = this.loadedLock.tryOptimisticRead(); + int size = this.loaded.size(); + if (this.loadedLock.validate(stamp)) { + return size; + } else { + stamp = this.loadedLock.readLock(); + + int var4; + try { + var4 = this.loaded.size(); + } finally { + this.loadedLock.unlockRead(stamp); + } + + return var4; + } + } + + public int getLoadingChunksCount() { + long stamp = this.loadedLock.tryOptimisticRead(); + int size = this.loading.size(); + if (this.loadedLock.validate(stamp)) { + return size; + } else { + stamp = this.loadedLock.readLock(); + + int var4; + try { + var4 = this.loading.size(); + } finally { + this.loadedLock.unlockRead(stamp); + } + + return var4; + } + } + + @Nonnull + private String getLoadedChunksGrid() { + int viewRadius = this.chunkViewRadius; + int chunkXMin = this.lastChunkX - viewRadius; + int chunkZMin = this.lastChunkZ - viewRadius; + int chunkXMax = this.lastChunkX + viewRadius; + int chunkZMax = this.lastChunkZ + viewRadius; + StringBuilder sb = new StringBuilder(); + sb.append("(").append(chunkXMin).append(", ").append(chunkZMin).append(") -> (").append(chunkXMax).append(", ").append(chunkZMax).append(")\n"); + + for (int x = chunkXMin; x <= chunkXMax; x++) { + for (int z = chunkZMin; z <= chunkZMax; z++) { + long index = ChunkUtil.indexChunk(x, z); + if (this.loaded.contains(index)) { + ChunkTracker.ChunkVisibility chunkVisibility = this.getChunkVisibility(index); + switch (chunkVisibility) { + case NONE: + sb.append('X'); + break; + case HOT: + sb.append('#'); + break; + case COLD: + sb.append('&'); + } + } else if (this.loading.contains(index)) { + sb.append('%'); + } else { + sb.append(' '); + } + } + + sb.append('\n'); + } + + return sb.toString(); + } + + @Nonnull + public Message getLoadedChunksMessage() { + long stamp = this.loadedLock.readLock(); + + Message var3; + try { + var3 = Message.translation("server.commands.chunkTracker.loaded") + .monospace(true) + .param("grid", this.getLoadedChunksGrid()) + .param("viewRadius", this.chunkViewRadius) + .param("sentViewRadius", this.sentViewRadius) + .param("hotRadius", this.hotRadius) + .param("readyForChunks", this.readyForChunks) + .param("loaded", this.loaded.size()) + .param("loading", this.loading.size()); + } finally { + this.loadedLock.unlockRead(stamp); + } + + return var3; + } + + @Nonnull + public String getLoadedChunksDebug() { + long stamp = this.loadedLock.readLock(); + + String var4; + try { + String sb = "Chunks (#: Loaded, &: Loading, ' ': Not loaded):\n" + + this.getLoadedChunksGrid() + + "\nView Radius: " + + this.chunkViewRadius + + "\nSent View Radius: " + + this.sentViewRadius + + "\nHot Radius: " + + this.hotRadius + + "\nReady For Chunks: " + + this.readyForChunks + + "\nLoaded: " + + this.loaded.size() + + "\nLoading: " + + this.loading.size(); + var4 = sb; + } finally { + this.loadedLock.unlockRead(stamp); + } + + return var4; + } + + public void setReadyForChunks(boolean readyForChunks) { + this.readyForChunks = readyForChunks; + } + + public boolean isReadyForChunks() { + return this.readyForChunks; + } + + public void copyFrom(@Nonnull ChunkTracker chunkTracker) { + long stamp = this.loadedLock.writeLock(); + + try { + long otherStamp = chunkTracker.loadedLock.readLock(); + + try { + this.loading.addAll(chunkTracker.loading); + this.loaded.addAll(chunkTracker.loaded); + this.reload.addAll(chunkTracker.reload); + this.sentViewRadius = 0; + } finally { + chunkTracker.loadedLock.unlockRead(otherStamp); + } + } finally { + this.loadedLock.unlockWrite(stamp); + } + } + + @Nonnull + @Override + public Component clone() { + return new ChunkTracker(this); + } + + private static boolean shouldBeVisible(int chunkViewRadiusSquared, int chunkX, int chunkZ, int x, int z) { + int xDiff = Math.abs(x - chunkX); + int zDiff = Math.abs(z - chunkZ); + int distanceSq = xDiff * xDiff + zDiff * zDiff; + return distanceSq <= chunkViewRadiusSquared; + } + + public static boolean tryUnloadChunk( + long chunkIndex, int chunkViewRadiusSquared, int chunkX, int chunkZ, @Nonnull PlayerRef playerRef, @Nonnull LongSet loading + ) { + int x = ChunkUtil.xOfChunkIndex(chunkIndex); + int z = ChunkUtil.zOfChunkIndex(chunkIndex); + if (shouldBeVisible(chunkViewRadiusSquared, x, z, chunkX, chunkZ)) { + return false; + } else { + ChunkStore chunkComponentStore = playerRef.getReference().getStore().getExternalData().getWorld().getChunkStore(); + Ref reference = chunkComponentStore.getChunkReference(chunkIndex); + if (reference != null) { + ObjectArrayList packets = new ObjectArrayList<>(); + chunkComponentStore.getStore().fetch(Collections.singletonList(reference), ChunkStore.UNLOAD_PACKETS_DATA_QUERY_SYSTEM_TYPE, playerRef, packets); + + for (int i = 0; i < packets.size(); i++) { + playerRef.getPacketHandler().write(packets.get(i)); + } + } + + playerRef.getPacketHandler().writeNoCache(new UnloadChunk(x, z)); + loading.remove(chunkIndex); + return true; + } + } + + public void tryLoadChunkAsync( + @Nonnull ChunkStore chunkStore, + @Nonnull PlayerRef playerRefComponent, + long chunkIndex, + @Nonnull TransformComponent transformComponent, + @Nonnull ComponentAccessor componentAccessor + ) { + World world = componentAccessor.getExternalData().getWorld(); + Vector3d position = transformComponent.getPosition(); + int chunkX = MathUtil.floor(position.getX()) >> 5; + int chunkZ = MathUtil.floor(position.getZ()) >> 5; + int x = ChunkUtil.xOfChunkIndex(chunkIndex); + int z = ChunkUtil.zOfChunkIndex(chunkIndex); + boolean isHot = shouldBeVisible(this.maxHotLoadedChunksRadius, chunkX, chunkZ, x, z); + Ref chunkReference = chunkStore.getChunkReference(chunkIndex); + if (chunkReference != null) { + WorldChunk worldChunkComponent = chunkStore.getStore().getComponent(chunkReference, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + if (worldChunkComponent.is(ChunkFlag.TICKING)) { + this._loadChunkAsync(chunkIndex, playerRefComponent, chunkReference, chunkStore); + return; + } + } + + int flags = -2147483632; + if (isHot) { + flags |= 4; + } + + chunkStore.getChunkReferenceAsync(chunkIndex, flags).thenComposeAsync(reference -> { + if (reference != null && reference.isValid()) { + long stamp = this.loadedLock.readLock(); + + try { + if (!this.loading.contains(chunkIndex)) { + return CompletableFuture.completedFuture(null); + } + } finally { + this.loadedLock.unlockRead(stamp); + } + + return this._loadChunkAsync(chunkIndex, playerRefComponent, (Ref)reference, chunkStore); + } else { + long stamp = this.loadedLock.writeLock(); + + try { + this.loading.remove(chunkIndex); + } finally { + this.loadedLock.unlockWrite(stamp); + } + + return CompletableFuture.completedFuture(null); + } + }, world).exceptionallyAsync(throwable -> { + long stamp = this.loadedLock.writeLock(); + + try { + this.loading.remove(chunkIndex); + } finally { + this.loadedLock.unlockWrite(stamp); + } + + HytaleLogger.getLogger().at(Level.SEVERE).withCause(throwable).log("Failed to load chunk! %s, %s", chunkX, chunkZ); + return null; + }); + } + + @Nonnull + private CompletableFuture _loadChunkAsync( + long chunkIndex, @Nonnull PlayerRef playerRefComponent, @Nonnull Ref chunkRef, @Nonnull ChunkStore chunkStore + ) { + List packets = new ObjectArrayList<>(); + chunkStore.getStore().fetch(Collections.singletonList(chunkRef), ChunkStore.LOAD_PACKETS_DATA_QUERY_SYSTEM_TYPE, playerRefComponent, packets); + ObjectArrayList> futurePackets = new ObjectArrayList<>(); + chunkStore.getStore() + .fetch(Collections.singletonList(chunkRef), ChunkStore.LOAD_FUTURE_PACKETS_DATA_QUERY_SYSTEM_TYPE, playerRefComponent, futurePackets); + return CompletableFuture.allOf(futurePackets.toArray(CompletableFuture[]::new)).thenAcceptAsync(o -> { + for (CompletableFuture futurePacket : futurePackets) { + Packet packet = futurePacket.join(); + if (packet != null) { + packets.add(packet); + } + } + + long writeStamp = this.loadedLock.writeLock(); + + try { + if (this.loading.remove(chunkIndex)) { + for (int i = 0; i < packets.size(); i++) { + playerRefComponent.getPacketHandler().write(packets.get(i)); + } + + this.loaded.add(chunkIndex); + } + } finally { + this.loadedLock.unlockWrite(writeStamp); + } + }); + } + + public static enum ChunkVisibility { + NONE, + HOT, + COLD; + + private ChunkVisibility() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/KnockbackPredictionSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/player/KnockbackPredictionSystems.java new file mode 100644 index 0000000..6e5e888 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/KnockbackPredictionSystems.java @@ -0,0 +1,653 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.MovementSettings; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.protocol.packets.entities.ApplyKnockback; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementManager; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.modules.collision.CollisionModule; +import com.hypixel.hytale.server.core.modules.collision.CollisionResult; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.ParticleUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.PositionUtil; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; + +public class KnockbackPredictionSystems { + public static boolean DEBUG_KNOCKBACK_POSITION = false; + public static final float DEFAULT_BLOCK_DRAG = 0.82F; + public static final float AIR_DENSITY = 0.001225F; + public static final float COLLISION_PADDING = 1.0E-4F; + public static final float MAX_CYCLE_MOVEMENT = 0.25F; + public static final float TIME_STEP = 0.016666668F; + public static final int MAX_JUMP_COMBOS = 3; + + public KnockbackPredictionSystems() { + } + + public static class CaptureKnockbackInput extends EntityTickingSystem { + private static final Query QUERY = Query.and(PlayerInput.getComponentType(), KnockbackSimulation.getComponentType()); + private static final Set> DEPENDENCIES = Set.of(new SystemDependency<>(Order.BEFORE, PlayerSystems.ProcessPlayerInput.class)); + + public CaptureKnockbackInput() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + KnockbackSimulation knockbackSimulationComponent = archetypeChunk.getComponent(index, KnockbackSimulation.getComponentType()); + + assert knockbackSimulationComponent != null; + + PlayerInput playerInputComponent = archetypeChunk.getComponent(index, PlayerInput.getComponentType()); + + assert playerInputComponent != null; + + List queue = playerInputComponent.getMovementUpdateQueue(); + Vector3d client = knockbackSimulationComponent.getClientPosition(); + Vector3d clientLast = knockbackSimulationComponent.getClientLastPosition(); + Vector3d relativeMovement = knockbackSimulationComponent.getRelativeMovement(); + clientLast.assign(client); + boolean hasWishMovement = false; + if (!queue.isEmpty()) { + for (int i = 0; i < queue.size(); i++) { + PlayerInput.InputUpdate update = queue.get(i); + if (update instanceof PlayerInput.AbsoluteMovement abs) { + client.assign(abs.getX(), abs.getY(), abs.getZ()); + } else if (update instanceof PlayerInput.RelativeMovement rel) { + client.add(rel.getX(), rel.getY(), rel.getZ()); + } else if (update instanceof PlayerInput.WishMovement wish) { + hasWishMovement = true; + relativeMovement.assign(wish.getX(), wish.getY(), wish.getZ()); + } else { + if (!(update instanceof PlayerInput.SetMovementStates)) { + continue; + } + + MovementStates movementStates = ((PlayerInput.SetMovementStates)update).movementStates(); + if (movementStates.jumping) { + knockbackSimulationComponent.setWasJumping(true); + } + + knockbackSimulationComponent.setClientMovementStates(movementStates); + } + + queue.remove(i); + i--; + } + + if (!hasWishMovement) { + relativeMovement.assign(client).subtract(clientLast); + if (knockbackSimulationComponent.hadWishMovement()) { + knockbackSimulationComponent.setClientFinished(true); + } + } else { + knockbackSimulationComponent.setHadWishMovement(true); + } + } + } + } + + public static class ClearOnRemove extends RefSystem { + private static final ComponentType KNOCKBACK_SIMULATION_COMPONENT_TYPE = KnockbackSimulation.getComponentType(); + + public ClearOnRemove() { + } + + @Override + public Query getQuery() { + return KNOCKBACK_SIMULATION_COMPONENT_TYPE; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + store.removeComponent(ref, KNOCKBACK_SIMULATION_COMPONENT_TYPE); + } + } + + public static class ClearOnTeleport extends RefChangeSystem { + private static final ComponentType TELEPORT_COMPONENT_TYPE = Teleport.getComponentType(); + private static final ComponentType KNOCKBACK_SIMULATION_COMPONENT_TYPE = KnockbackSimulation.getComponentType(); + + public ClearOnTeleport() { + } + + @Override + public Query getQuery() { + return KNOCKBACK_SIMULATION_COMPONENT_TYPE; + } + + @Nonnull + @Override + public ComponentType componentType() { + return TELEPORT_COMPONENT_TYPE; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull Teleport component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + commandBuffer.removeComponent(ref, KNOCKBACK_SIMULATION_COMPONENT_TYPE); + } + + public void onComponentSet( + @Nonnull Ref ref, + Teleport oldComponent, + @Nonnull Teleport newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull Teleport component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + } + + private static enum CollisionAxis { + X, + Y, + Z; + + private CollisionAxis() { + } + } + + public static class InitKnockback extends RefChangeSystem { + private static final Query QUERY = Query.and( + Player.getComponentType(), TransformComponent.getComponentType(), KnockbackSimulation.getComponentType(), MovementStatesComponent.getComponentType() + ); + + public InitKnockback() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public ComponentType componentType() { + return KnockbackSimulation.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, + @Nonnull KnockbackSimulation component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + component.getClientPosition().assign(transformComponent.getPosition()); + component.getSimPosition().assign(transformComponent.getPosition()); + MovementStatesComponent movementStatesComponent = commandBuffer.getComponent(ref, MovementStatesComponent.getComponentType()); + + assert movementStatesComponent != null; + + component.setClientMovementStates(new MovementStates(movementStatesComponent.getMovementStates())); + } + + public void onComponentSet( + @Nonnull Ref ref, + KnockbackSimulation oldComponent, + @Nonnull KnockbackSimulation newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, + @Nonnull KnockbackSimulation knockbackSimulationComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + Vector3d clientPosition = knockbackSimulationComponent.getClientPosition(); + playerComponent.moveTo(ref, clientPosition.x, clientPosition.y, clientPosition.z, commandBuffer); + MovementStatesComponent movementStatesComponent = commandBuffer.getComponent(ref, MovementStatesComponent.getComponentType()); + + assert movementStatesComponent != null; + + movementStatesComponent.setMovementStates(knockbackSimulationComponent.getClientMovementStates()); + } + } + + @Deprecated + public static class SimulateKnockback extends EntityTickingSystem { + private static final ComponentType BOUNDING_BOX_COMPONENT_TYPE = BoundingBox.getComponentType(); + private static final Query QUERY = Query.and( + Player.getComponentType(), + TransformComponent.getComponentType(), + KnockbackSimulation.getComponentType(), + BOUNDING_BOX_COMPONENT_TYPE, + MovementStatesComponent.getComponentType() + ); + private static final Set> DEPENDENCIES = Set.of(new SystemDependency<>(Order.AFTER, PlayerSystems.ProcessPlayerInput.class)); + + public SimulateKnockback() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + KnockbackSimulation knockbackSimulationComponent = archetypeChunk.getComponent(index, KnockbackSimulation.getComponentType()); + + assert knockbackSimulationComponent != null; + + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Player playerComponent = archetypeChunk.getComponent(index, Player.getComponentType()); + + assert playerComponent != null; + + World world = store.getExternalData().getWorld(); + float time = knockbackSimulationComponent.getRemainingTime(); + time -= dt; + knockbackSimulationComponent.setRemainingTime(time); + if (!(time < 0.0F) && !archetypeChunk.getArchetype().contains(DeathComponent.getComponentType())) { + if (KnockbackPredictionSystems.DEBUG_KNOCKBACK_POSITION) { + Vector3d particlePosition = knockbackSimulationComponent.getClientPosition(); + SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get().getPlayerSpatialResourceType()); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(particlePosition, 75.0, results); + Color color = knockbackSimulationComponent.hadWishMovement() ? new Color((byte)-1, (byte)0, (byte)0) : new Color((byte)0, (byte)0, (byte)-1); + ParticleUtil.spawnParticleEffect("Example_Simple", particlePosition, 0.0F, 0.0F, 0.0F, 1.0F, color, results, commandBuffer); + } + + knockbackSimulationComponent.setTickBuffer(knockbackSimulationComponent.getTickBuffer() + dt); + MovementStates clientStates = knockbackSimulationComponent.getClientMovementStates(); + + while (knockbackSimulationComponent.getTickBuffer() >= 0.016666668F) { + knockbackSimulationComponent.setTickBuffer(knockbackSimulationComponent.getTickBuffer() - 0.016666668F); + Vector3d rel = knockbackSimulationComponent.getRelativeMovement(); + Vector3d requestedVelocity = knockbackSimulationComponent.getRequestedVelocity(); + Vector3d simPos = knockbackSimulationComponent.getSimPosition(); + Vector3d velocity = knockbackSimulationComponent.getSimVelocity(); + MovementStatesComponent movementStatesComponent = archetypeChunk.getComponent(index, MovementStatesComponent.getComponentType()); + + assert movementStatesComponent != null; + + MovementStates movementStates = movementStatesComponent.getMovementStates(); + MovementManager movementManagerComponent = archetypeChunk.getComponent(index, MovementManager.getComponentType()); + + assert movementManagerComponent != null; + + MovementSettings movementManagerSettings = movementManagerComponent.getSettings(); + BoundingBox boundingBoxComponent = archetypeChunk.getComponent(index, BOUNDING_BOX_COMPONENT_TYPE); + + assert boundingBoxComponent != null; + + Box hitBox = boundingBoxComponent.getBoundingBox(); + if (clientStates.flying) { + knockbackSimulationComponent.setRemainingTime(0.0F); + return; + } + + if (clientStates.climbing && !clientStates.onGround) { + knockbackSimulationComponent.setRemainingTime(0.0F); + return; + } + + if (clientStates.swimming) { + knockbackSimulationComponent.setRemainingTime(0.0F); + return; + } + + int invertedGravityModifier = movementManagerSettings.invertedGravity ? 1 : -1; + double terminalVelocity = invertedGravityModifier + * PhysicsMath.getTerminalVelocity( + movementManagerSettings.mass, 0.001225F, Math.abs(hitBox.width() * hitBox.depth()), movementManagerSettings.dragCoefficient + ); + double gravityStep = invertedGravityModifier * PhysicsMath.getAcceleration(velocity.y, terminalVelocity) * 0.016666668F; + if (velocity.y < terminalVelocity && gravityStep > 0.0) { + velocity.y = Math.min(velocity.y + gravityStep, terminalVelocity); + } else if (velocity.y > terminalVelocity && gravityStep < 0.0) { + velocity.y = Math.max(velocity.y + gravityStep, terminalVelocity); + } + + if (movementStates.onGround) { + movementStates.falling = false; + if (knockbackSimulationComponent.wasOnGround() && knockbackSimulationComponent.consumeWasJumping()) { + velocity.y = movementManagerSettings.jumpForce; + movementStates.onGround = false; + knockbackSimulationComponent.setJumpCombo(Math.min(knockbackSimulationComponent.getJumpCombo() + 1, 3)); + } + } else { + Vector3d checkPosition = simPos.clone(); + checkPosition.y += 0.1F; + movementStates.falling = velocity.y < 0.0 && CollisionModule.get().validatePosition(world, hitBox, checkPosition, new CollisionResult()) != 0; + if (movementStates.falling) { + movementStates.jumping = false; + movementStates.swimJumping = false; + } + } + + if (knockbackSimulationComponent.getJumpCombo() != 0 + && (movementStates.onGround && knockbackSimulationComponent.wasOnGround() || velocity.x == 0.0 || velocity.z == 0.0)) { + knockbackSimulationComponent.setJumpCombo(0); + } + + float friction = this.computeMoveForce(knockbackSimulationComponent, movementStates, movementManagerSettings); + if (!movementStates.flying && knockbackSimulationComponent.getRequestedVelocityChangeType() != null) { + switch (knockbackSimulationComponent.getRequestedVelocityChangeType()) { + case Add: + velocity.add( + requestedVelocity.x * 0.18F * movementManagerSettings.velocityResistance, + requestedVelocity.y, + requestedVelocity.z * 0.18F * movementManagerSettings.velocityResistance + ); + break; + case Set: + velocity.assign(requestedVelocity); + } + + PlayerRef playerRefComponent = archetypeChunk.getComponent(index, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerRefComponent.getPacketHandler() + .write( + new ApplyKnockback( + PositionUtil.toPositionPacket(transformComponent.getPosition()), + (float)requestedVelocity.x, + (float)requestedVelocity.y, + (float)requestedVelocity.z, + knockbackSimulationComponent.getRequestedVelocityChangeType() + ) + ); + } + + requestedVelocity.assign(0.0); + knockbackSimulationComponent.setRequestedVelocityChangeType(null); + Vector3d movementOffset = knockbackSimulationComponent.getMovementOffset(); + movementOffset.assign(0.0); + if (knockbackSimulationComponent.hadWishMovement()) { + float converter = this.convertWishMovement(knockbackSimulationComponent, movementStates, movementManagerSettings); + velocity.addScaled(rel, converter); + } else { + movementOffset.addScaled(rel, friction); + rel.assign(0.0); + } + + movementOffset.addScaled(velocity, 0.016666668F); + this.applyMovementOffset(world, hitBox, knockbackSimulationComponent, movementStates, movementOffset); + Ref ref = archetypeChunk.getReferenceTo(index); + if (time < 0.2F) { + Vector3d move = Vector3d.lerp(knockbackSimulationComponent.getClientPosition(), simPos, time / 0.2F); + playerComponent.moveTo(ref, move.x, move.y, move.z, commandBuffer); + } else { + playerComponent.moveTo(ref, simPos.x, simPos.y, simPos.z, commandBuffer); + } + } + } else { + commandBuffer.removeComponent(archetypeChunk.getReferenceTo(index), KnockbackSimulation.getComponentType()); + } + } + + private float convertWishMovement( + @Nonnull KnockbackSimulation simulation, @Nonnull MovementStates movementStates, @Nonnull MovementSettings movementSettings + ) { + MovementStates clientStates = simulation.getClientMovementStates(); + if (clientStates == null) { + clientStates = movementStates; + } + + Vector3d velocity = simulation.getSimVelocity(); + float horizontalSpeed = (float)Math.sqrt(velocity.x * velocity.x + velocity.z * velocity.z); + float drag = 0.0F; + float friction = 1.0F; + if (!movementStates.flying && !movementStates.climbing) { + drag = !movementStates.onGround && !movementStates.swimming + ? convertToNewRange( + horizontalSpeed, movementSettings.airDragMinSpeed, movementSettings.airDragMaxSpeed, movementSettings.airDragMin, movementSettings.airDragMax + ) + : 0.82F; + friction = !movementStates.onGround && !movementStates.swimming + ? convertToNewRange( + horizontalSpeed, + movementSettings.airFrictionMinSpeed, + movementSettings.airFrictionMaxSpeed, + movementSettings.airFrictionMax, + movementSettings.airFrictionMin + ) + : 1.0F - drag; + } + + float clientDrag = 0.0F; + float clientFriction = 1.0F; + if (!clientStates.flying && !clientStates.climbing) { + clientDrag = !clientStates.onGround && !clientStates.swimming + ? convertToNewRange( + horizontalSpeed, movementSettings.airDragMinSpeed, movementSettings.airDragMaxSpeed, movementSettings.airDragMin, movementSettings.airDragMax + ) + : 0.82F; + clientFriction = !clientStates.onGround && !clientStates.swimming + ? convertToNewRange( + horizontalSpeed, + movementSettings.airFrictionMinSpeed, + movementSettings.airFrictionMaxSpeed, + movementSettings.airFrictionMax, + movementSettings.airFrictionMin + ) + : 1.0F - clientDrag; + } + + return friction / clientFriction; + } + + private float computeMoveForce( + @Nonnull KnockbackSimulation simulation, @Nonnull MovementStates movementStates, @Nonnull MovementSettings movementSettings + ) { + float drag = 0.0F; + float friction = 1.0F; + Vector3d velocity = simulation.getSimVelocity(); + float horizontalSpeed = (float)Math.sqrt(velocity.x * velocity.x + velocity.z * velocity.z); + if (!movementStates.flying && !movementStates.climbing) { + drag = !movementStates.onGround && !movementStates.swimming + ? convertToNewRange( + horizontalSpeed, movementSettings.airDragMinSpeed, movementSettings.airDragMaxSpeed, movementSettings.airDragMin, movementSettings.airDragMax + ) + : 0.82F; + friction = !movementStates.onGround && !movementStates.swimming + ? convertToNewRange( + horizontalSpeed, + movementSettings.airFrictionMinSpeed, + movementSettings.airFrictionMaxSpeed, + movementSettings.airFrictionMax, + movementSettings.airFrictionMin + ) + / (1.0F - drag) + : 1.0F; + } + + velocity.x *= drag; + velocity.z *= drag; + return friction; + } + + private static float convertToNewRange(float value, float oldMinRange, float oldMaxRange, float newMinRange, float newMaxRange) { + if (newMinRange != newMaxRange && oldMinRange != oldMaxRange) { + float newValue = (value - oldMinRange) * (newMaxRange - newMinRange) / (oldMaxRange - oldMinRange) + newMinRange; + return MathUtil.clamp(newValue, Math.min(newMinRange, newMaxRange), Math.max(newMinRange, newMaxRange)); + } else { + return newMinRange; + } + } + + public void applyMovementOffset( + @Nonnull World world, + @Nonnull Box hitBox, + @Nonnull KnockbackSimulation simulation, + @Nonnull MovementStates movementStates, + @Nonnull Vector3d movementOffset + ) { + int moveCycles = (int)Math.ceil(movementOffset.length() / 0.25); + Vector3d cycleMovementOffset = moveCycles == 1 ? movementOffset : movementOffset.clone().scale(1.0F / moveCycles); + simulation.setWasOnGround(movementStates.onGround); + + for (int i = 0; i < moveCycles; i++) { + this.doMoveCycle(world, hitBox, simulation, movementStates, cycleMovementOffset); + } + + if (movementStates.onGround) { + simulation.getSimVelocity().y = 0.0; + } + } + + private void doMoveCycle( + @Nonnull World world, @Nonnull Box hitBox, @Nonnull KnockbackSimulation simulation, @Nonnull MovementStates movementStates, @Nonnull Vector3d offset + ) { + Vector3d simPos = simulation.getSimPosition(); + Vector3d velocity = simulation.getSimVelocity(); + Vector3d checkPosition = simulation.getCheckPosition(); + checkPosition.assign(simPos); + CollisionResult collisionResult = simulation.getCollisionResult(); + collisionResult.reset(); + checkPosition.y = checkPosition.y + offset.y; + boolean hasCollidedY = this.checkCollision( + simulation, world, hitBox, checkPosition, offset, KnockbackPredictionSystems.CollisionAxis.Y, collisionResult + ); + if (movementStates.onGround && offset.y < 0.0) { + if (!hasCollidedY) { + movementStates.onGround = false; + } else { + checkPosition.y = checkPosition.y - offset.y; + } + } else if (hasCollidedY) { + if (offset.y <= 0.0) { + movementStates.onGround = true; + checkPosition.y = checkPosition.y - offset.y; + } else { + movementStates.onGround = false; + checkPosition.y = checkPosition.y - offset.y; + } + + velocity.y = 0.0; + } else { + movementStates.onGround = false; + } + + if (offset.x != 0.0) { + checkPosition.x = checkPosition.x + offset.x; + collisionResult.reset(); + boolean hasCollidedX = this.checkCollision( + simulation, world, hitBox, checkPosition, offset, KnockbackPredictionSystems.CollisionAxis.Y, collisionResult + ); + if (hasCollidedX) { + checkPosition.x = checkPosition.x - offset.x; + } + } + + if (offset.z != 0.0) { + checkPosition.z = checkPosition.z + offset.z; + collisionResult.reset(); + boolean hasCollidedZ = this.checkCollision( + simulation, world, hitBox, checkPosition, offset, KnockbackPredictionSystems.CollisionAxis.Y, collisionResult + ); + if (hasCollidedZ) { + checkPosition.z = checkPosition.z - offset.z; + } + } + + simPos.assign(checkPosition); + } + + private boolean checkCollision( + @Nonnull KnockbackSimulation simulation, + @Nonnull World world, + @Nonnull Box hitBox, + @Nonnull Vector3d position, + Vector3d moveOffset, + KnockbackPredictionSystems.CollisionAxis axis, + @Nonnull CollisionResult result + ) { + Vector3d tempPosition = simulation.getTempPosition(); + tempPosition.assign(position); + tempPosition.add(0.0, 1.0E-4F, 0.0); + return CollisionModule.get().validatePosition(world, hitBox, tempPosition, result) != 0; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/KnockbackSimulation.java b/src/com/hypixel/hytale/server/core/modules/entity/player/KnockbackSimulation.java new file mode 100644 index 0000000..956f90a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/KnockbackSimulation.java @@ -0,0 +1,197 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.ChangeVelocityType; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.server.core.modules.collision.CollisionResult; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class KnockbackSimulation implements Component { + public static final float KNOCKBACK_SIMULATION_TIME = 0.5F; + public static final float BLEND_DELAY = 0.2F; + private final Vector3d requestedVelocity = new Vector3d(); + private final Vector3d clientLastPosition = new Vector3d(); + private final Vector3d clientPosition = new Vector3d(); + private final Vector3d relativeMovement = new Vector3d(); + private final Vector3d simPosition = new Vector3d(); + private final Vector3d simVelocity = new Vector3d(); + @Nullable + private ChangeVelocityType requestedVelocityChangeType = null; + private MovementStates clientMovementStates; + private float remainingTime = 0.5F; + private boolean hadWishMovement = false; + private boolean clientFinished = false; + private boolean wasJumping = false; + private int jumpCombo = 0; + private boolean wasOnGround = false; + private float tickBuffer = 0.0F; + private final Vector3d movementOffset = new Vector3d(); + private final CollisionResult collisionResult = new CollisionResult(); + private final Vector3d checkPosition = new Vector3d(); + private final Vector3d tempPosition = new Vector3d(); + + public KnockbackSimulation() { + } + + public static ComponentType getComponentType() { + return EntityModule.get().getKnockbackSimulationComponentType(); + } + + public float getTickBuffer() { + return this.tickBuffer; + } + + public void setTickBuffer(float tickBuffer) { + this.tickBuffer = tickBuffer; + } + + @Nonnull + public Vector3d getRequestedVelocity() { + return this.requestedVelocity; + } + + public void addRequestedVelocity(@Nonnull Vector3d velocity) { + if (this.requestedVelocityChangeType == null || this.requestedVelocityChangeType == ChangeVelocityType.Add) { + this.requestedVelocityChangeType = ChangeVelocityType.Add; + } + + this.requestedVelocity.add(velocity); + } + + public void setRequestedVelocity(@Nonnull Vector3d velocity) { + if (this.requestedVelocityChangeType == null || this.requestedVelocityChangeType == ChangeVelocityType.Add) { + this.requestedVelocityChangeType = ChangeVelocityType.Set; + } + + this.requestedVelocity.assign(velocity); + } + + @Nullable + public ChangeVelocityType getRequestedVelocityChangeType() { + return this.requestedVelocityChangeType; + } + + public void setRequestedVelocityChangeType(ChangeVelocityType requestedVelocityChangeType) { + this.requestedVelocityChangeType = requestedVelocityChangeType; + } + + @Nonnull + public Vector3d getClientLastPosition() { + return this.clientLastPosition; + } + + @Nonnull + public Vector3d getClientPosition() { + return this.clientPosition; + } + + @Nonnull + public Vector3d getRelativeMovement() { + return this.relativeMovement; + } + + @Nonnull + public Vector3d getSimPosition() { + return this.simPosition; + } + + @Nonnull + public Vector3d getSimVelocity() { + return this.simVelocity; + } + + public float getRemainingTime() { + return this.remainingTime; + } + + public void setRemainingTime(float remainingTime) { + this.remainingTime = remainingTime; + } + + public void reset() { + this.remainingTime = 0.5F; + } + + public boolean consumeWasJumping() { + boolean tmp = this.wasJumping; + this.wasJumping = false; + return tmp; + } + + public void setWasJumping(boolean wasJumping) { + this.wasJumping = wasJumping; + } + + public boolean hadWishMovement() { + return this.hadWishMovement; + } + + public void setHadWishMovement(boolean hadWishMovement) { + this.hadWishMovement = hadWishMovement; + } + + public boolean isClientFinished() { + return this.clientFinished; + } + + public void setClientFinished(boolean clientFinished) { + this.clientFinished = clientFinished; + } + + public int getJumpCombo() { + return this.jumpCombo; + } + + public void setJumpCombo(int jumpCombo) { + this.jumpCombo = jumpCombo; + } + + public boolean wasOnGround() { + return this.wasOnGround; + } + + public void setWasOnGround(boolean wasOnGround) { + this.wasOnGround = wasOnGround; + } + + public MovementStates getClientMovementStates() { + return this.clientMovementStates; + } + + public void setClientMovementStates(MovementStates clientMovementStates) { + this.clientMovementStates = clientMovementStates; + } + + @Nonnull + public Vector3d getMovementOffset() { + return this.movementOffset; + } + + @Nonnull + public CollisionResult getCollisionResult() { + return this.collisionResult; + } + + @Nonnull + public Vector3d getCheckPosition() { + return this.checkPosition; + } + + @Nonnull + public Vector3d getTempPosition() { + return this.tempPosition; + } + + @Nonnull + @Override + public Component clone() { + KnockbackSimulation simulation = new KnockbackSimulation(); + simulation.requestedVelocity.assign(this.requestedVelocity); + return simulation; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerCameraAddSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerCameraAddSystem.java new file mode 100644 index 0000000..0607952 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerCameraAddSystem.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.server.core.entity.entities.player.CameraManager; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PlayerCameraAddSystem extends HolderSystem { + private static final ComponentType CAMERA_MANAGER_COMPONENT_TYPE = CameraManager.getComponentType(); + private static final Query QUERY = Query.and(PlayerRef.getComponentType(), Query.not(CAMERA_MANAGER_COMPONENT_TYPE)); + + public PlayerCameraAddSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(CAMERA_MANAGER_COMPONENT_TYPE); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerChunkTrackerSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerChunkTrackerSystems.java new file mode 100644 index 0000000..722693b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerChunkTrackerSystems.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PlayerChunkTrackerSystems { + public PlayerChunkTrackerSystems() { + } + + public static class AddSystem extends HolderSystem { + @Nonnull + private static final ComponentType CHUNK_TRACKER_COMPONENT_TYPE = ChunkTracker.getComponentType(); + + public AddSystem() { + } + + @Override + public Query getQuery() { + return CHUNK_TRACKER_COMPONENT_TYPE; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + ChunkTracker chunkTrackerComponent = holder.getComponent(CHUNK_TRACKER_COMPONENT_TYPE); + + assert chunkTrackerComponent != null; + + chunkTrackerComponent.setReadyForChunks(true); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + } + + public static class UpdateSystem extends EntityTickingSystem { + @Nonnull + private static final ComponentType CHUNK_TRACKER_COMPONENT_TYPE = ChunkTracker.getComponentType(); + + public UpdateSystem() { + } + + @Override + public Query getQuery() { + return CHUNK_TRACKER_COMPONENT_TYPE; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return false; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Ref ref = archetypeChunk.getReferenceTo(index); + ChunkTracker chunkTrackerComponent = archetypeChunk.getComponent(index, CHUNK_TRACKER_COMPONENT_TYPE); + + assert chunkTrackerComponent != null; + + chunkTrackerComponent.tick(ref, dt, commandBuffer); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerConnectionFlushSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerConnectionFlushSystem.java new file mode 100644 index 0000000..0af3d71 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerConnectionFlushSystem.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.OrderPriority; +import com.hypixel.hytale.component.dependency.RootDependency; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.dependency.SystemGroupDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.component.system.tick.RunWhenPausedSystem; +import com.hypixel.hytale.server.core.Constants; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import javax.annotation.Nonnull; + +public class PlayerConnectionFlushSystem extends EntityTickingSystem implements RunWhenPausedSystem { + public static final Set> DEPENDENCIES = Set.of( + new SystemGroupDependency<>(Order.AFTER, EntityStore.SEND_PACKET_GROUP), + new SystemDependency(Order.AFTER, PlayerPingSystem.class, OrderPriority.CLOSEST), + RootDependency.last() + ); + private final ComponentType componentType; + + public PlayerConnectionFlushSystem(ComponentType componentType) { + this.componentType = componentType; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Override + public Query getQuery() { + return this.componentType; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + if (Constants.FORCE_NETWORK_FLUSH) { + store.tick(this, dt, systemIndex); + } + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + archetypeChunk.getComponent(index, this.componentType).getPacketHandler().tryFlush(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerCreativeSettings.java b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerCreativeSettings.java new file mode 100644 index 0000000..d455556 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerCreativeSettings.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import javax.annotation.Nonnull; + +public record PlayerCreativeSettings(boolean allowNPCDetection, boolean respondToHit) { + public PlayerCreativeSettings() { + this(false, false); + } + + @Nonnull + public PlayerCreativeSettings clone() { + return new PlayerCreativeSettings(this.allowNPCDetection, this.respondToHit); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerHudManagerSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerHudManagerSystems.java new file mode 100644 index 0000000..ab12721 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerHudManagerSystems.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.hud.HudManager; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PlayerHudManagerSystems { + public PlayerHudManagerSystems() { + } + + public static class InitializeSystem extends RefSystem { + @Nonnull + private static final ComponentType PLAYER_REF_COMPONENT_TYPE = PlayerRef.getComponentType(); + @Nonnull + private static final ComponentType PLAYER_COMPONENT_TYPE = Player.getComponentType(); + @Nonnull + private static final Query QUERY = Query.and(PLAYER_REF_COMPONENT_TYPE, PLAYER_COMPONENT_TYPE); + + public InitializeSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + Player playerComponent = commandBuffer.getComponent(ref, PLAYER_COMPONENT_TYPE); + + assert playerComponent != null; + + PlayerRef playerRefComponent = commandBuffer.getComponent(ref, PLAYER_REF_COMPONENT_TYPE); + + assert playerRefComponent != null; + + HudManager hudManager = playerComponent.getHudManager(); + PacketHandler packetHandler = playerRefComponent.getPacketHandler(); + hudManager.sendVisibleHudComponents(packetHandler); + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerInput.java b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerInput.java new file mode 100644 index 0000000..eaf9dfd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerInput.java @@ -0,0 +1,255 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class PlayerInput implements Component { + @Nonnull + private final List inputUpdateQueue = new ObjectArrayList<>(); + private int mountId; + + public PlayerInput() { + } + + public static ComponentType getComponentType() { + return EntityModule.get().getPlayerInputComponentType(); + } + + public void queue(PlayerInput.InputUpdate inputUpdate) { + this.inputUpdateQueue.add(inputUpdate); + } + + @Nonnull + public List getMovementUpdateQueue() { + return this.inputUpdateQueue; + } + + public int getMountId() { + return this.mountId; + } + + public void setMountId(int mountId) { + this.mountId = mountId; + } + + @Nonnull + @Override + public Component clone() { + PlayerInput playerInput = new PlayerInput(); + playerInput.inputUpdateQueue.addAll(this.inputUpdateQueue); + return playerInput; + } + + public static class AbsoluteMovement implements PlayerInput.InputUpdate { + private double x; + private double y; + private double z; + + public AbsoluteMovement(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public double getX() { + return this.x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return this.y; + } + + public void setY(double y) { + this.y = y; + } + + public double getZ() { + return this.z; + } + + public void setZ(double z) { + this.z = z; + } + + @Override + public void apply(@Nonnull CommandBuffer commandBuffer, @Nonnull ArchetypeChunk archetypeChunk, int index) { + Ref playerRef = archetypeChunk.getReferenceTo(index); + Player playerComponent = archetypeChunk.getComponent(index, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.moveTo(playerRef, this.x, this.y, this.z, commandBuffer); + } + } + + public interface InputUpdate { + void apply(CommandBuffer var1, ArchetypeChunk var2, int var3); + } + + public static class RelativeMovement implements PlayerInput.InputUpdate { + private double x; + private double y; + private double z; + + public RelativeMovement(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public double getX() { + return this.x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return this.y; + } + + public void setY(double y) { + this.y = y; + } + + public double getZ() { + return this.z; + } + + public void setZ(double z) { + this.z = z; + } + + @Override + public void apply(@Nonnull CommandBuffer commandBuffer, @Nonnull ArchetypeChunk archetypeChunk, int index) { + Ref ref = archetypeChunk.getReferenceTo(index); + Player playerComponent = archetypeChunk.getComponent(index, Player.getComponentType()); + + assert playerComponent != null; + + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + playerComponent.moveTo(ref, position.x + this.x, position.y + this.y, position.z + this.z, commandBuffer); + } + } + + public record SetBody(Direction direction) implements PlayerInput.InputUpdate { + @Override + public void apply(CommandBuffer commandBuffer, @Nonnull ArchetypeChunk archetypeChunk, int index) { + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + if (transformComponent != null) { + transformComponent.getRotation().assign(this.direction.pitch, this.direction.yaw, this.direction.roll); + } + } + } + + public static class SetClientVelocity implements PlayerInput.InputUpdate { + private final Vector3d velocity; + + public SetClientVelocity(com.hypixel.hytale.protocol.Vector3d velocity) { + this.velocity = new Vector3d(velocity.x, velocity.y, velocity.z); + } + + public Vector3d getVelocity() { + return this.velocity; + } + + @Override + public void apply(CommandBuffer commandBuffer, @Nonnull ArchetypeChunk archetypeChunk, int index) { + Velocity velocityComponent = archetypeChunk.getComponent(index, Velocity.getComponentType()); + if (velocityComponent != null) { + velocityComponent.setClient(this.velocity); + } + } + } + + public record SetHead(Direction direction) implements PlayerInput.InputUpdate { + @Override + public void apply(CommandBuffer commandBuffer, @Nonnull ArchetypeChunk archetypeChunk, int index) { + HeadRotation headRotationComponent = archetypeChunk.getComponent(index, HeadRotation.getComponentType()); + if (headRotationComponent != null) { + headRotationComponent.getRotation().assign(this.direction.pitch, this.direction.yaw, this.direction.roll); + } + } + } + + public record SetMovementStates(MovementStates movementStates) implements PlayerInput.InputUpdate { + @Override + public void apply(CommandBuffer commandBuffer, @Nonnull ArchetypeChunk archetypeChunk, int index) { + MovementStatesComponent movementStatesComponent = archetypeChunk.getComponent(index, MovementStatesComponent.getComponentType()); + if (movementStatesComponent != null) { + movementStatesComponent.setMovementStates(this.movementStates); + } + } + } + + public record SetRiderMovementStates(MovementStates movementStates) implements PlayerInput.InputUpdate { + @Override + public void apply(CommandBuffer commandBuffer, ArchetypeChunk archetypeChunk, int index) { + } + } + + public static class WishMovement implements PlayerInput.InputUpdate { + private double x; + private double y; + private double z; + + public WishMovement(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public double getX() { + return this.x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return this.y; + } + + public void setY(double y) { + this.y = y; + } + + public double getZ() { + return this.z; + } + + public void setZ(double z) { + this.z = z; + } + + @Override + public void apply(CommandBuffer commandBuffer, ArchetypeChunk archetypeChunk, int index) { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerItemEntityPickupSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerItemEntityPickupSystem.java new file mode 100644 index 0000000..16320a3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerItemEntityPickupSystem.java @@ -0,0 +1,199 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.OrderPriority; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.spatial.SpatialStructure; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; +import com.hypixel.hytale.server.core.modules.entity.DespawnComponent; +import com.hypixel.hytale.server.core.modules.entity.component.Interactable; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.modules.entity.item.PickupItemComponent; +import com.hypixel.hytale.server.core.modules.entity.item.PreventPickup; +import com.hypixel.hytale.server.core.modules.entity.system.PlayerSpatialSystem; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.Set; +import javax.annotation.Nonnull; + +public class PlayerItemEntityPickupSystem extends EntityTickingSystem { + @Nonnull + private final ComponentType itemComponentType; + @Nonnull + private final ComponentType playerComponentType; + @Nonnull + private final ResourceType, EntityStore>> playerSpatialComponent; + @Nonnull + private final ComponentType interactionManagerType; + @Nonnull + private final Set> dependencies; + @Nonnull + private final Query query; + + public PlayerItemEntityPickupSystem( + @Nonnull ComponentType itemComponentType, + @Nonnull ComponentType playerComponentType, + @Nonnull ResourceType, EntityStore>> playerSpatialComponent + ) { + this.itemComponentType = itemComponentType; + this.playerComponentType = playerComponentType; + this.interactionManagerType = InteractionModule.get().getInteractionManagerComponent(); + this.playerSpatialComponent = playerSpatialComponent; + this.dependencies = Set.of(new SystemDependency<>(Order.AFTER, PlayerSpatialSystem.class, OrderPriority.CLOSEST)); + this.query = Query.and( + itemComponentType, + Query.not(Interactable.getComponentType()), + Query.not(PickupItemComponent.getComponentType()), + Query.not(PreventPickup.getComponentType()) + ); + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return false; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Ref itemRef = archetypeChunk.getReferenceTo(index); + ItemComponent itemComponent = archetypeChunk.getComponent(index, this.itemComponentType); + + assert itemComponent != null; + + if (itemComponent.pollPickupDelay(dt)) { + if (itemComponent.pollPickupThrottle(dt)) { + TimeResource timeResource = commandBuffer.getResource(TimeResource.getResourceType()); + SpatialResource, EntityStore> playerSpatialResource = store.getResource(this.playerSpatialComponent); + SpatialStructure> spatialStructure = playerSpatialResource.getSpatialStructure(); + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d itemEntityPosition = transformComponent.getPosition(); + DespawnComponent despawnComponent = archetypeChunk.getComponent(index, DespawnComponent.getComponentType()); + float pickupRadius = itemComponent.getPickupRadius(commandBuffer); + ItemStack itemStack = itemComponent.getItemStack(); + Item item = itemStack.getItem(); + String interactions = item.getInteractions().get(InteractionType.Pickup); + if (interactions != null) { + Ref targetRef = spatialStructure.closest(itemEntityPosition); + if (targetRef != null) { + TransformComponent targetTransformComponent = store.getComponent(targetRef, TransformComponent.getComponentType()); + + assert targetTransformComponent != null; + + InteractionManager targetInteractionManagerComponent = store.getComponent(targetRef, this.interactionManagerType); + + assert targetInteractionManagerComponent != null; + + Vector3d targetPosition = targetTransformComponent.getPosition(); + double distance = targetPosition.distanceTo(itemEntityPosition); + if (!(distance > pickupRadius)) { + Ref reference = archetypeChunk.getReferenceTo(index); + commandBuffer.run( + _store -> { + InteractionContext context = InteractionContext.forInteraction( + targetInteractionManagerComponent, targetRef, InteractionType.Pickup, commandBuffer + ); + InteractionChain chain = targetInteractionManagerComponent.initChain( + InteractionType.Pickup, context, RootInteraction.getRootInteractionOrUnknown(interactions), false + ); + context.getMetaStore().putMetaObject(Interaction.TARGET_ENTITY, reference); + targetInteractionManagerComponent.executeChain(reference, commandBuffer, chain); + _store.removeEntity(reference, RemoveReason.REMOVE); + } + ); + } + } + } else { + ObjectList> targetPlayerRefs = SpatialResource.getThreadLocalReferenceList(); + spatialStructure.ordered(itemEntityPosition, pickupRadius, targetPlayerRefs); + + for (Ref targetPlayerRef : targetPlayerRefs) { + if (!store.getArchetype(targetPlayerRef).contains(DeathComponent.getComponentType())) { + Player playerComponent = store.getComponent(targetPlayerRef, this.playerComponentType); + + assert playerComponent != null; + + PlayerSettings playerSettings = commandBuffer.getComponent(targetPlayerRef, PlayerSettings.getComponentType()); + if (playerSettings == null) { + playerSettings = PlayerSettings.defaults(); + } + + ItemContainer itemContainer = playerComponent.getInventory().getContainerForItemPickup(item, playerSettings); + ItemStackTransaction transaction = itemContainer.addItemStack(itemStack); + ItemStack remainder = transaction.getRemainder(); + if (ItemStack.isEmpty(remainder)) { + itemComponent.setRemovedByPlayerPickup(true); + commandBuffer.removeEntity(itemRef, RemoveReason.REMOVE); + playerComponent.notifyPickupItem(targetPlayerRef, itemStack, itemEntityPosition, commandBuffer); + Holder pickupItemHolder = ItemComponent.generatePickedUpItem(itemRef, commandBuffer, targetPlayerRef, itemEntityPosition); + commandBuffer.addEntity(pickupItemHolder, AddReason.SPAWN); + break; + } + + if (!remainder.equals(itemStack)) { + int quantity = itemStack.getQuantity() - remainder.getQuantity(); + itemStack = remainder; + itemComponent.setItemStack(remainder); + float newLifetime = itemComponent.computeLifetimeSeconds(commandBuffer); + DespawnComponent.trySetDespawn(commandBuffer, timeResource, itemRef, despawnComponent, newLifetime); + Holder pickupItemHolder = ItemComponent.generatePickedUpItem(itemRef, commandBuffer, targetPlayerRef, itemEntityPosition); + commandBuffer.addEntity(pickupItemHolder, AddReason.SPAWN); + if (quantity > 0) { + playerComponent.notifyPickupItem(targetPlayerRef, remainder.withQuantity(quantity), itemEntityPosition, commandBuffer); + } + } + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerMovementManagerSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerMovementManagerSystems.java new file mode 100644 index 0000000..e9e7330 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerMovementManagerSystems.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementManager; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PlayerMovementManagerSystems { + public PlayerMovementManagerSystems() { + } + + public static class AssignmentSystem extends HolderSystem { + @Nonnull + private static final ComponentType MOVEMENT_MANAGER_COMPONENT_TYPE = MovementManager.getComponentType(); + @Nonnull + private static final Query QUERY = Query.and(PlayerRef.getComponentType(), Query.not(MOVEMENT_MANAGER_COMPONENT_TYPE)); + + public AssignmentSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(MOVEMENT_MANAGER_COMPONENT_TYPE); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + } + + public static class PostAssignmentSystem extends RefSystem { + @Nonnull + private static final ComponentType MOVEMENT_MANAGER_COMPONENT_TYPE = MovementManager.getComponentType(); + @Nonnull + private static final Query QUERY = Query.and(MOVEMENT_MANAGER_COMPONENT_TYPE, PlayerRef.getComponentType()); + + public PostAssignmentSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + MovementManager movementManagerComponent = commandBuffer.getComponent(ref, MOVEMENT_MANAGER_COMPONENT_TYPE); + + assert movementManagerComponent != null; + + movementManagerComponent.resetDefaultsAndUpdate(ref, commandBuffer); + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerPingSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerPingSystem.java new file mode 100644 index 0000000..a8a6098 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerPingSystem.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.RootDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.component.system.tick.RunWhenPausedSystem; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerPingSystem extends EntityTickingSystem implements RunWhenPausedSystem { + @Nonnull + private static final ComponentType PLAYER_REF_COMPONENT_TYPE = PlayerRef.getComponentType(); + + public PlayerPingSystem() { + } + + @Override + public Query getQuery() { + return PLAYER_REF_COMPONENT_TYPE; + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.lastSet(); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityStore.SEND_PACKET_GROUP; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + PlayerRef playerRefComponent = archetypeChunk.getComponent(index, PLAYER_REF_COMPONENT_TYPE); + + assert playerRefComponent != null; + + playerRefComponent.getPacketHandler().tickPing(dt); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerProcessMovementSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerProcessMovementSystem.java new file mode 100644 index 0000000..ee7903d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerProcessMovementSystem.java @@ -0,0 +1,174 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.collision.CollisionModule; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.CollisionResultComponent; +import com.hypixel.hytale.server.core.modules.entity.component.PositionDataComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class PlayerProcessMovementSystem extends EntityTickingSystem { + @Nonnull + private final ComponentType playerComponentType; + @Nonnull + private final ComponentType boundingBoxComponentType; + @Nonnull + private final ComponentType velocityComponentType; + @Nonnull + private final ComponentType collisionResultComponentType; + @Nonnull + private final ComponentType playerRefComponentType; + @Nonnull + private final ComponentType transformComponentType; + @Nonnull + private final ComponentType positionDataComponentType; + @Nonnull + private final Query query; + + public PlayerProcessMovementSystem( + @Nonnull ComponentType playerComponentType, + @Nonnull ComponentType velocityComponentType, + @Nonnull ComponentType collisionResultComponentType + ) { + this.playerComponentType = playerComponentType; + this.velocityComponentType = velocityComponentType; + this.collisionResultComponentType = collisionResultComponentType; + this.boundingBoxComponentType = BoundingBox.getComponentType(); + this.playerRefComponentType = PlayerRef.getComponentType(); + this.transformComponentType = TransformComponent.getComponentType(); + this.positionDataComponentType = PositionDataComponent.getComponentType(); + this.query = Query.and( + playerComponentType, this.boundingBoxComponentType, velocityComponentType, collisionResultComponentType, this.positionDataComponentType + ); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return false; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + World world = store.getExternalData().getWorld(); + Ref ref = archetypeChunk.getReferenceTo(index); + Player playerComponent = archetypeChunk.getComponent(index, this.playerComponentType); + + assert playerComponent != null; + + Velocity velocityComponent = archetypeChunk.getComponent(index, this.velocityComponentType); + + assert velocityComponent != null; + + CollisionResultComponent collisionResultComponent = archetypeChunk.getComponent(index, this.collisionResultComponentType); + + assert collisionResultComponent != null; + + InteractionManager interactionManagerComponent = archetypeChunk.getComponent(index, InteractionModule.get().getInteractionManagerComponent()); + + assert interactionManagerComponent != null; + + PlayerRef playerRefComponent = archetypeChunk.getComponent(index, this.playerRefComponentType); + + assert playerRefComponent != null; + + TransformComponent transformComponent = archetypeChunk.getComponent(index, this.transformComponentType); + + assert transformComponent != null; + + boolean pendingCollisionCheck = collisionResultComponent.isPendingCollisionCheck(); + collisionResultComponent.getCollisionStartPositionCopy() + .assign(pendingCollisionCheck ? collisionResultComponent.getCollisionStartPosition() : transformComponent.getPosition()); + collisionResultComponent.getCollisionPositionOffsetCopy().assign(collisionResultComponent.getCollisionPositionOffset()); + collisionResultComponent.resetLocationChange(); + if (collisionResultComponent.getCollisionPositionOffsetCopy().squaredLength() >= 100.0) { + if (playerComponent.getGameMode() == GameMode.Adventure) { + Entity.LOGGER + .at(Level.WARNING) + .log( + "%s, %s: Jump in location in processMovementBlockCollisions %s", + playerRefComponent.getUsername(), + playerRefComponent.getUuid(), + collisionResultComponent.getCollisionPositionOffsetCopy().length() + ); + } + + playerComponent.resetVelocity(velocityComponent); + } else { + BoundingBox boundingBoxComponent = archetypeChunk.getComponent(index, this.boundingBoxComponentType); + + assert boundingBoxComponent != null; + + Box boundingBox = boundingBoxComponent.getBoundingBox(); + if (pendingCollisionCheck) { + } + + CollisionModule.get() + .findIntersections( + world, boundingBox, collisionResultComponent.getCollisionStartPositionCopy(), collisionResultComponent.getCollisionResult(), true, false + ); + playerComponent.processVelocitySample(dt, collisionResultComponent.getCollisionPositionOffsetCopy(), velocityComponent); + Ref chunkRef = transformComponent.getChunkRef(); + if (chunkRef != null && chunkRef.isValid()) { + Store chunkStore = world.getChunkStore().getStore(); + WorldChunk worldChunkComponent = chunkStore.getComponent(chunkRef, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + PositionDataComponent positionDataComponent = archetypeChunk.getComponent(index, this.positionDataComponentType); + + assert positionDataComponent != null; + + Vector3i blockPosition = transformComponent.getPosition().toVector3i(); + positionDataComponent.setInsideBlockTypeId(worldChunkComponent.getBlock(blockPosition)); + positionDataComponent.setStandingOnBlockTypeId(worldChunkComponent.getBlock(blockPosition.x, blockPosition.y - 1, blockPosition.z)); + } + + commandBuffer.run( + _store -> { + int damageToEntity = collisionResultComponent.getCollisionResult() + .defaultTriggerBlocksProcessing(interactionManagerComponent, playerComponent, ref, playerComponent.executeTriggers, commandBuffer); + if (playerComponent.executeBlockDamage && damageToEntity > 0) { + Damage damage = new Damage(Damage.NULL_SOURCE, DamageCause.ENVIRONMENT, damageToEntity); + DamageSystems.executeDamage(index, archetypeChunk, commandBuffer, damage); + } + } + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerSavingSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerSavingSystems.java new file mode 100644 index 0000000..da30240 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerSavingSystems.java @@ -0,0 +1,176 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.StoreSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.component.system.tick.RunWhenPausedSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class PlayerSavingSystems { + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final float PLAYER_SAVE_INTERVAL_SECONDS = 10.0F; + + public PlayerSavingSystems() { + } + + public static class SaveDataResource implements Resource { + private float delay = 10.0F; + + public SaveDataResource() { + } + + @Nonnull + @Override + public Resource clone() { + PlayerSavingSystems.SaveDataResource data = new PlayerSavingSystems.SaveDataResource(); + data.delay = this.delay; + return data; + } + } + + public static class TickingSystem extends EntityTickingSystem implements RunWhenPausedSystem { + @Nonnull + private final ResourceType dataResourceType = this.registerResource( + PlayerSavingSystems.SaveDataResource.class, PlayerSavingSystems.SaveDataResource::new + ); + @Nonnull + private final ComponentType playerComponentType; + @Nonnull + private final ComponentType transformComponentType; + @Nonnull + private final ComponentType headRotationComponentType; + @Nonnull + private final Query query; + + public TickingSystem(@Nonnull ComponentType playerComponentType) { + this.playerComponentType = playerComponentType; + this.transformComponentType = TransformComponent.getComponentType(); + this.headRotationComponentType = HeadRotation.getComponentType(); + this.query = Archetype.of(playerComponentType, this.transformComponentType, this.headRotationComponentType); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + World world = store.getExternalData().getWorld(); + if (world.getWorldConfig().isSavingPlayers()) { + PlayerSavingSystems.SaveDataResource data = store.getResource(this.dataResourceType); + data.delay -= dt; + if (data.delay <= 0.0F) { + data.delay = 10.0F; + store.tick(this, dt, systemIndex); + } + } + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Player playerComponent = archetypeChunk.getComponent(index, this.playerComponentType); + + assert playerComponent != null; + + TransformComponent transformComponent = archetypeChunk.getComponent(index, this.transformComponentType); + + assert transformComponent != null; + + HeadRotation headRotationComponent = archetypeChunk.getComponent(index, this.headRotationComponentType); + + assert headRotationComponent != null; + + PlayerConfigData data = playerComponent.getPlayerConfigData(); + Vector3d position = transformComponent.getPosition(); + Vector3f rotation = headRotationComponent.getRotation(); + Vector3d lastSavedPosition = data.lastSavedPosition; + Vector3f lastSavedRotation = data.lastSavedRotation; + if (!lastSavedPosition.equals(position) + || !lastSavedRotation.equals(rotation) + || data.consumeHasChanged() + || playerComponent.getInventory().consumeNeedsSaving()) { + lastSavedPosition.assign(position); + lastSavedRotation.assign(rotation); + playerComponent.saveConfig(store.getExternalData().getWorld(), EntityUtils.toHolder(index, archetypeChunk)); + } + } + } + + public static class WorldRemovedSystem extends StoreSystem { + @Nonnull + private final ComponentType playerComponentType; + @Nonnull + private final ComponentType refComponentType; + @Nonnull + private final Query query; + + public WorldRemovedSystem(@Nonnull ComponentType playerComponentType) { + this.playerComponentType = playerComponentType; + this.refComponentType = PlayerRef.getComponentType(); + this.query = Query.and(playerComponentType, this.refComponentType); + } + + @Override + public void onSystemAddedToStore(@Nonnull Store store) { + } + + @Override + public void onSystemRemovedFromStore(@Nonnull Store store) { + if (store.getExternalData().getWorld().getWorldConfig().isSavingPlayers()) { + PlayerSavingSystems.LOGGER.at(Level.INFO).log("Saving Players..."); + } else { + PlayerSavingSystems.LOGGER.at(Level.INFO).log("Disconnecting Players..."); + } + + store.forEachEntityParallel(this.query, (index, archetypeChunk, commandBuffer) -> { + Player playerComponent = archetypeChunk.getComponent(index, this.playerComponentType); + + assert playerComponent != null; + + PlayerRef playerRefComponent = archetypeChunk.getComponent(index, this.refComponentType); + + assert playerRefComponent != null; + + World world = commandBuffer.getExternalData().getWorld(); + if (world.getWorldConfig().isSavingPlayers()) { + playerComponent.saveConfig(world, EntityUtils.toHolder(index, archetypeChunk)); + } + + playerRefComponent.getPacketHandler().disconnect("Stopping world!"); + }); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerSendInventorySystem.java b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerSendInventorySystem.java new file mode 100644 index 0000000..0f0ae87 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerSendInventorySystem.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PlayerSendInventorySystem extends EntityTickingSystem { + @Nonnull + private final ComponentType componentType; + @Nonnull + private final ComponentType refComponentType = PlayerRef.getComponentType(); + @Nonnull + private final Query query; + + public PlayerSendInventorySystem(ComponentType componentType) { + this.componentType = componentType; + this.query = Query.and(componentType, this.refComponentType); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Player playerComponent = archetypeChunk.getComponent(index, this.componentType); + + assert playerComponent != null; + + Inventory inventory = playerComponent.getInventory(); + if (inventory.consumeIsDirty()) { + PlayerRef playerRefComponent = archetypeChunk.getComponent(index, this.refComponentType); + + assert playerRefComponent != null; + + playerRefComponent.getPacketHandler().write(inventory.toPacket()); + } + + playerComponent.getWindowManager().updateWindows(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerSettings.java b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerSettings.java new file mode 100644 index 0000000..3134966 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerSettings.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.protocol.PickupLocation; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public record PlayerSettings( + boolean showEntityMarkers, + @Nonnull PickupLocation armorItemsPreferredPickupLocation, + @Nonnull PickupLocation weaponAndToolItemsPreferredPickupLocation, + @Nonnull PickupLocation usableItemsItemsPreferredPickupLocation, + @Nonnull PickupLocation solidBlockItemsPreferredPickupLocation, + @Nonnull PickupLocation miscItemsPreferredPickupLocation, + PlayerCreativeSettings creativeSettings +) implements Component { + @Nonnull + private static final PlayerSettings INSTANCE = new PlayerSettings( + false, PickupLocation.Hotbar, PickupLocation.Hotbar, PickupLocation.Hotbar, PickupLocation.Hotbar, PickupLocation.Hotbar, new PlayerCreativeSettings() + ); + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getPlayerSettingsComponentType(); + } + + @Nonnull + public static PlayerSettings defaults() { + return INSTANCE; + } + + @Nonnull + @Override + public Component clone() { + return new PlayerSettings( + this.showEntityMarkers, + this.armorItemsPreferredPickupLocation, + this.weaponAndToolItemsPreferredPickupLocation, + this.usableItemsItemsPreferredPickupLocation, + this.solidBlockItemsPreferredPickupLocation, + this.miscItemsPreferredPickupLocation, + this.creativeSettings.clone() + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerSkinComponent.java b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerSkinComponent.java new file mode 100644 index 0000000..09becfb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerSkinComponent.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.protocol.PlayerSkin; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PlayerSkinComponent implements Component { + @Nonnull + private final PlayerSkin playerSkin; + private boolean isNetworkOutdated = true; + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getPlayerSkinComponentType(); + } + + public PlayerSkinComponent(@Nonnull PlayerSkin playerSkin) { + this.playerSkin = playerSkin; + } + + public boolean consumeNetworkOutdated() { + boolean temp = this.isNetworkOutdated; + this.isNetworkOutdated = false; + return temp; + } + + @Nonnull + public PlayerSkin getPlayerSkin() { + return this.playerSkin; + } + + public void setNetworkOutdated() { + this.isNetworkOutdated = true; + } + + @Nonnull + @Override + public Component clone() { + return new PlayerSkinComponent(this.playerSkin); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerSystems.java new file mode 100644 index 0000000..d3d279e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/player/PlayerSystems.java @@ -0,0 +1,637 @@ +package com.hypixel.hytale.server.core.modules.entity.player; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.system.EntityEventSystem; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.QuerySystem; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.component.system.tick.RunWhenPausedSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolsSetSoundSet; +import com.hypixel.hytale.protocol.packets.inventory.SetActiveSlot; +import com.hypixel.hytale.protocol.packets.player.SetBlockPlacementOverride; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.gameplay.SpawnConfig; +import com.hypixel.hytale.server.core.asset.type.particle.config.WorldParticle; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerWorldData; +import com.hypixel.hytale.server.core.entity.entities.player.data.UniqueItemUsagesComponent; +import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementManager; +import com.hypixel.hytale.server.core.entity.entities.player.pages.RespawnPage; +import com.hypixel.hytale.server.core.entity.nameplate.Nameplate; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.DisplayNameComponent; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.event.KillFeedEvent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.modules.entity.tracker.LegacyEntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.ParticleUtil; +import com.hypixel.hytale.server.core.universe.world.PlayerUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class PlayerSystems { + @Nonnull + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + + public PlayerSystems() { + } + + public static class BlockPausedMovementSystem implements RunWhenPausedSystem, QuerySystem { + @Nonnull + private final Query query = Query.and( + Player.getComponentType(), PlayerInput.getComponentType(), TransformComponent.getComponentType(), HeadRotation.getComponentType() + ); + + public BlockPausedMovementSystem() { + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + store.forEachChunk(systemIndex, PlayerSystems.BlockPausedMovementSystem::onTick); + } + + private static void onTick(@Nonnull ArchetypeChunk archetypeChunk, @Nonnull CommandBuffer commandBuffer) { + for (int index = 0; index < archetypeChunk.size(); index++) { + PlayerInput playerInputComponent = archetypeChunk.getComponent(index, PlayerInput.getComponentType()); + + assert playerInputComponent != null; + + boolean shouldTeleport = false; + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = archetypeChunk.getComponent(index, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + List movementUpdateQueue = playerInputComponent.getMovementUpdateQueue(); + + for (PlayerInput.InputUpdate entry : movementUpdateQueue) { + switch (entry) { + case PlayerInput.AbsoluteMovement abs: + shouldTeleport = transformComponent.getPosition().distanceSquaredTo(abs.getX(), abs.getY(), abs.getZ()) > 0.01F; + break; + case PlayerInput.RelativeMovement rel: + Vector3d position = transformComponent.getPosition(); + shouldTeleport = transformComponent.getPosition() + .distanceSquaredTo(position.x + rel.getX(), position.y + rel.getY(), position.z + rel.getZ()) + > 0.01F; + break; + case PlayerInput.SetHead head: + shouldTeleport = headRotationComponent.getRotation() + .distanceSquaredTo(head.direction().pitch, head.direction().yaw, head.direction().roll) + > 0.01F; + break; + default: + } + } + + movementUpdateQueue.clear(); + if (shouldTeleport) { + commandBuffer.addComponent( + archetypeChunk.getReferenceTo(index), + Teleport.getComponentType(), + new Teleport(transformComponent.getPosition(), transformComponent.getRotation()) + .withHeadRotation(headRotationComponent.getRotation()) + .withoutVelocityReset() + ); + } + } + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + } + + public static class EnsureEffectControllerSystem extends HolderSystem { + public EnsureEffectControllerSystem() { + } + + @Override + public Query getQuery() { + return PlayerRef.getComponentType(); + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(EffectControllerComponent.getComponentType()); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + } + + public static class EnsurePlayerInput extends HolderSystem { + public EnsurePlayerInput() { + } + + @Override + public Query getQuery() { + return PlayerRef.getComponentType(); + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(PlayerInput.getComponentType()); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + holder.removeComponent(PlayerInput.getComponentType()); + } + } + + public static class EnsureUniqueItemUsagesSystem extends HolderSystem { + public EnsureUniqueItemUsagesSystem() { + } + + @Override + public Query getQuery() { + return Query.and(PlayerRef.getComponentType(), Query.not(UniqueItemUsagesComponent.getComponentType())); + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(UniqueItemUsagesComponent.getComponentType()); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + } + + public static class KillFeedDecedentEventSystem extends EntityEventSystem { + @Nonnull + private final ComponentType playerRefComponentType = PlayerRef.getComponentType(); + + public KillFeedDecedentEventSystem() { + super(KillFeedEvent.DecedentMessage.class); + } + + public void handle( + int index, + @NonNullDecl ArchetypeChunk archetypeChunk, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer, + @NonNullDecl KillFeedEvent.DecedentMessage event + ) { + DisplayNameComponent displayNameComponent = archetypeChunk.getComponent(index, DisplayNameComponent.getComponentType()); + Message displayName; + if (displayNameComponent != null) { + displayName = displayNameComponent.getDisplayName(); + } else { + PlayerRef playerRefComponent = archetypeChunk.getComponent(index, this.playerRefComponentType); + + assert playerRefComponent != null; + + displayName = Message.raw(playerRefComponent.getUsername()); + } + + event.setMessage(displayName); + } + + @Nonnull + @Override + public Query getQuery() { + return this.playerRefComponentType; + } + } + + public static class KillFeedKillerEventSystem extends EntityEventSystem { + @Nonnull + private final ComponentType playerRefComponentType = PlayerRef.getComponentType(); + + public KillFeedKillerEventSystem() { + super(KillFeedEvent.KillerMessage.class); + } + + public void handle( + int index, + @NonNullDecl ArchetypeChunk archetypeChunk, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer, + @NonNullDecl KillFeedEvent.KillerMessage event + ) { + DisplayNameComponent displayNameComponent = archetypeChunk.getComponent(index, DisplayNameComponent.getComponentType()); + Message displayName; + if (displayNameComponent != null) { + displayName = displayNameComponent.getDisplayName(); + } else { + PlayerRef playerRefComponent = archetypeChunk.getComponent(index, this.playerRefComponentType); + + assert playerRefComponent != null; + + displayName = Message.raw(playerRefComponent.getUsername()); + } + + event.setMessage(displayName); + } + + @Nonnull + @Override + public Query getQuery() { + return this.playerRefComponentType; + } + } + + public static class NameplateRefChangeSystem extends RefChangeSystem { + public NameplateRefChangeSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return Player.getComponentType(); + } + + @Nonnull + @Override + public ComponentType componentType() { + return DisplayNameComponent.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, + @Nonnull DisplayNameComponent component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Nameplate nameplateComponent = commandBuffer.ensureAndGetComponent(ref, Nameplate.getComponentType()); + nameplateComponent.setText(component.getDisplayName() != null ? component.getDisplayName().getAnsiMessage() : ""); + } + + public void onComponentSet( + @Nonnull Ref ref, + DisplayNameComponent oldComponent, + @Nonnull DisplayNameComponent newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Nameplate nameplateComponent = commandBuffer.ensureAndGetComponent(ref, Nameplate.getComponentType()); + nameplateComponent.setText(newComponent.getDisplayName() != null ? newComponent.getDisplayName().getAnsiMessage() : ""); + } + + public void onComponentRemoved( + @Nonnull Ref ref, + @Nonnull DisplayNameComponent component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Nameplate nameplateComponent = commandBuffer.ensureAndGetComponent(ref, Nameplate.getComponentType()); + nameplateComponent.setText(""); + } + } + + public static class NameplateRefSystem extends RefSystem { + public NameplateRefSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return Archetype.of(Player.getComponentType(), DisplayNameComponent.getComponentType()); + } + + @Override + public void onEntityAdded( + @NonNullDecl Ref ref, + @NonNullDecl AddReason reason, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + DisplayNameComponent displayNameComponent = commandBuffer.getComponent(ref, DisplayNameComponent.getComponentType()); + + assert displayNameComponent != null; + + if (commandBuffer.getComponent(ref, Nameplate.getComponentType()) == null) { + String displayName = displayNameComponent.getDisplayName() != null ? displayNameComponent.getDisplayName().getAnsiMessage() : ""; + Nameplate nameplateComponent = new Nameplate(displayName); + commandBuffer.putComponent(ref, Nameplate.getComponentType(), nameplateComponent); + } + } + + @Override + public void onEntityRemove( + @NonNullDecl Ref ref, + @NonNullDecl RemoveReason reason, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + } + } + + public static class PlayerAddedSystem extends RefSystem { + @Nonnull + private static final Message MESSAGE_SERVER_GENERAL_KILLED_BY_UNKNOWN = Message.translation("server.general.killedByUnknown"); + @Nonnull + private final Set> dependencies = Set.of(new SystemDependency<>(Order.AFTER, PlayerSystems.PlayerSpawnedSystem.class)); + @Nonnull + private final Query query; + + public PlayerAddedSystem(@Nonnull ComponentType movementManagerComponentType) { + this.query = Query.and(Player.getComponentType(), PlayerRef.getComponentType(), movementManagerComponentType); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + World world = commandBuffer.getExternalData().getWorld(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + MovementManager movementManagerComponent = commandBuffer.getComponent(ref, MovementManager.getComponentType()); + + assert movementManagerComponent != null; + + if (commandBuffer.getComponent(ref, DisplayNameComponent.getComponentType()) == null) { + Message displayName = Message.raw(playerRefComponent.getUsername()); + commandBuffer.putComponent(ref, DisplayNameComponent.getComponentType(), new DisplayNameComponent(displayName)); + } + + playerComponent.setLastSpawnTimeNanos(System.nanoTime()); + PacketHandler playerConnection = playerRefComponent.getPacketHandler(); + Objects.requireNonNull(world, "world"); + Objects.requireNonNull(playerComponent.getPlayerConfigData(), "data"); + PlayerWorldData perWorldData = playerComponent.getPlayerConfigData().getPerWorldData(world.getName()); + Player.initGameMode(ref, commandBuffer); + playerConnection.writeNoCache(new BuilderToolsSetSoundSet(world.getGameplayConfig().getCreativePlaySoundSetIndex())); + playerComponent.sendInventory(); + Inventory playerInventory = playerComponent.getInventory(); + playerConnection.writeNoCache(new SetActiveSlot(-1, playerInventory.getActiveHotbarSlot())); + playerConnection.writeNoCache(new SetActiveSlot(-5, playerInventory.getActiveUtilitySlot())); + playerConnection.writeNoCache(new SetActiveSlot(-8, playerInventory.getActiveToolsSlot())); + if (playerInventory.containsBrokenItem()) { + playerComponent.sendMessage(Message.translation("server.general.repair.itemBrokenOnRespawn").color("#ff5555")); + } + + playerConnection.writeNoCache(new SetBlockPlacementOverride(playerComponent.isOverrideBlockPlacementRestrictions())); + DeathComponent deathComponent = commandBuffer.getComponent(ref, DeathComponent.getComponentType()); + if (deathComponent != null) { + Message pendingDeathMessage = deathComponent.getDeathMessage(); + if (pendingDeathMessage == null) { + Entity.LOGGER.at(Level.SEVERE).withCause(new Throwable()).log("Player wasn't alive but didn't have a pending death message?"); + pendingDeathMessage = MESSAGE_SERVER_GENERAL_KILLED_BY_UNKNOWN; + } + + RespawnPage respawnPage = new RespawnPage( + playerRefComponent, pendingDeathMessage, deathComponent.displayDataOnDeathScreen(), deathComponent.getDeathItemLoss() + ); + playerComponent.getPageManager().openCustomPage(ref, store, respawnPage); + } + + TransformComponent transform = commandBuffer.getComponent(ref, TransformComponent.getComponentType()); + GameplayConfig gameplayConfig = world.getGameplayConfig(); + SpawnConfig spawnConfig = gameplayConfig.getSpawnConfig(); + if (transform != null) { + Vector3d position = transform.getPosition(); + SpatialResource, EntityStore> playerSpatialResource = commandBuffer.getResource(EntityModule.get().getPlayerSpatialResourceType()); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(position, 75.0, results); + results.add(ref); + if (playerComponent.isFirstSpawn()) { + WorldParticle[] firstSpawnParticles = spawnConfig.getFirstSpawnParticles(); + if (firstSpawnParticles == null) { + firstSpawnParticles = spawnConfig.getSpawnParticles(); + } + + if (firstSpawnParticles != null) { + ParticleUtil.spawnParticleEffects(firstSpawnParticles, position, null, results, commandBuffer); + } + } else { + WorldParticle[] spawnParticles = spawnConfig.getSpawnParticles(); + if (spawnParticles != null) { + ParticleUtil.spawnParticleEffects(spawnParticles, position, null, results, commandBuffer); + } + } + } + + playerConnection.tryFlush(); + perWorldData.setFirstSpawn(false); + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + } + + public static class PlayerRemovedSystem extends HolderSystem { + public PlayerRemovedSystem() { + } + + @Override + public Query getQuery() { + return PlayerRef.getComponentType(); + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + World world = store.getExternalData().getWorld(); + PlayerRef playerRefComponent = holder.getComponent(PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + Player playerComponent = holder.getComponent(Player.getComponentType()); + + assert playerComponent != null; + + TransformComponent transformComponent = holder.getComponent(TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = holder.getComponent(HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + DisplayNameComponent displayNameComponent = holder.getComponent(DisplayNameComponent.getComponentType()); + + assert displayNameComponent != null; + + Message displayName = displayNameComponent.getDisplayName(); + PlayerSystems.LOGGER + .at(Level.INFO) + .log( + "Removing player '%s%s' from world '%s' (%s)", + playerRefComponent.getUsername(), + displayName != null ? " (" + displayName.getAnsiMessage() + ")" : "", + world.getName(), + playerRefComponent.getUuid() + ); + playerComponent.getPlayerConfigData() + .getPerWorldData(world.getName()) + .setLastPosition(new Transform(transformComponent.getPosition().clone(), headRotationComponent.getRotation().clone())); + playerRefComponent.getPacketHandler().setQueuePackets(false); + playerRefComponent.getPacketHandler().tryFlush(); + WorldConfig worldConfig = world.getWorldConfig(); + PlayerUtil.broadcastMessageToPlayers( + playerRefComponent.getUuid(), + Message.translation("server.general.playerLeftWorld") + .param("username", playerRefComponent.getUsername()) + .param("world", worldConfig.getDisplayName() != null ? worldConfig.getDisplayName() : WorldConfig.formatDisplayName(world.getName())), + store + ); + } + } + + public static class PlayerSpawnedSystem extends RefSystem { + public PlayerSpawnedSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return Player.getComponentType(); + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + LegacyEntityTrackerSystems.sendPlayerSelf(ref, store); + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + } + + public static class ProcessPlayerInput extends EntityTickingSystem { + @Nonnull + private final Query query = Query.and(Player.getComponentType(), PlayerInput.getComponentType(), TransformComponent.getComponentType()); + + public ProcessPlayerInput() { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + PlayerInput playerInputComponent = archetypeChunk.getComponent(index, PlayerInput.getComponentType()); + + assert playerInputComponent != null; + + List movementUpdateQueue = playerInputComponent.getMovementUpdateQueue(); + + for (PlayerInput.InputUpdate entry : movementUpdateQueue) { + entry.apply(commandBuffer, archetypeChunk, index); + } + + movementUpdateQueue.clear(); + } + } + + public static class UpdatePlayerRef extends EntityTickingSystem { + @Nonnull + private final Query query = Query.and(PlayerRef.getComponentType(), TransformComponent.getComponentType(), HeadRotation.getComponentType()); + + public UpdatePlayerRef() { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public void tick( + float dt, + int index, + @NonNullDecl ArchetypeChunk archetypeChunk, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + World world = commandBuffer.getExternalData().getWorld(); + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Transform transform = transformComponent.getTransform(); + HeadRotation headRotationComponent = archetypeChunk.getComponent(index, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3f headRotation = headRotationComponent.getRotation(); + PlayerRef playerRefComponent = archetypeChunk.getComponent(index, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerRefComponent.updatePosition(world, transform, headRotation); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/repulsion/Repulsion.java b/src/com/hypixel/hytale/server/core/modules/entity/repulsion/Repulsion.java new file mode 100644 index 0000000..c9fc800 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/repulsion/Repulsion.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.modules.entity.repulsion; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class Repulsion implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(Repulsion.class, Repulsion::new) + .append( + new KeyedCodec<>("RepulsionConfigIndex", Codec.INTEGER), + (hitboxCollision, integer) -> hitboxCollision.repulsionConfigIndex = integer, + hitboxCollision -> hitboxCollision.repulsionConfigIndex + ) + .add() + .build(); + protected AssetExtraInfo.Data data; + private int repulsionConfigIndex; + private boolean isNetworkOutdated = true; + + public static ComponentType getComponentType() { + return EntityModule.get().getRepulsionComponentType(); + } + + public Repulsion(@Nonnull RepulsionConfig repulsionConfig) { + this.repulsionConfigIndex = RepulsionConfig.getAssetMap().getIndexOrDefault(repulsionConfig.getId(), -1); + } + + protected Repulsion() { + } + + public int getRepulsionConfigIndex() { + return this.repulsionConfigIndex; + } + + public void setRepulsionConfigIndex(int repulsionConfigIndex) { + this.repulsionConfigIndex = repulsionConfigIndex; + } + + public boolean consumeNetworkOutdated() { + boolean temp = this.isNetworkOutdated; + this.isNetworkOutdated = false; + return temp; + } + + @Nonnull + @Override + public Component clone() { + Repulsion component = new Repulsion(); + component.repulsionConfigIndex = this.repulsionConfigIndex; + return component; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/repulsion/RepulsionConfig.java b/src/com/hypixel/hytale/server/core/modules/entity/repulsion/RepulsionConfig.java new file mode 100644 index 0000000..f3e61a9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/repulsion/RepulsionConfig.java @@ -0,0 +1,123 @@ +package com.hypixel.hytale.server.core.modules.entity.repulsion; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class RepulsionConfig + implements JsonAssetWithMap>, + NetworkSerializable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + RepulsionConfig.class, + RepulsionConfig::new, + Codec.STRING, + (repulsion, s) -> repulsion.id = s, + repulsion -> repulsion.id, + (repulsion, data) -> repulsion.data = data, + repulsion -> repulsion.data + ) + .appendInherited( + new KeyedCodec<>("Radius", Codec.FLOAT), + (repulsion, radius) -> repulsion.radius = radius, + repulsion -> repulsion.radius, + (repulsion, parent) -> repulsion.radius = parent.radius + ) + .documentation("The radius around the entity") + .add() + .appendInherited( + new KeyedCodec<>("MinForce", Codec.FLOAT), + (repulsion, minForce) -> repulsion.minForce = minForce, + repulsion -> repulsion.minForce, + (repulsion, parent) -> repulsion.minForce = parent.minForce + ) + .documentation("The floor of the applied force while within effective radius") + .add() + .appendInherited( + new KeyedCodec<>("MaxForce", Codec.FLOAT), + (repulsion, maxForce) -> repulsion.maxForce = maxForce, + repulsion -> repulsion.maxForce, + (repulsion, parent) -> repulsion.maxForce = parent.maxForce + ) + .documentation("The max force to be applied at 100% intersection") + .add() + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(RepulsionConfig::getAssetStore)); + private static AssetStore> ASSET_STORE; + public static final int NO_REPULSION = -1; + protected AssetExtraInfo.Data data; + protected String id; + protected float radius; + protected float minForce; + protected float maxForce; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(RepulsionConfig.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public RepulsionConfig() { + } + + public RepulsionConfig(String id) { + this.id = id; + } + + public RepulsionConfig(@Nonnull RepulsionConfig repulsion) { + this(repulsion.radius, repulsion.minForce, repulsion.maxForce); + } + + public RepulsionConfig(float radius, float maxForce) { + this(radius, 0.0F, maxForce); + } + + public RepulsionConfig(float radius, float minForce, float maxForce) { + this.radius = radius; + this.minForce = minForce; + this.maxForce = maxForce; + } + + public String getId() { + return this.id; + } + + @Nonnull + public com.hypixel.hytale.protocol.RepulsionConfig toPacket() { + com.hypixel.hytale.protocol.RepulsionConfig packet = new com.hypixel.hytale.protocol.RepulsionConfig(); + packet.radius = this.radius; + packet.minForce = this.minForce; + packet.maxForce = this.maxForce; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "repulsionConfig{data=" + + this.data + + ", id='" + + this.id + + "', radius=" + + this.radius + + ", minForce=" + + this.minForce + + ", maxForce=" + + this.maxForce + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/repulsion/RepulsionConfigPacketGenerator.java b/src/com/hypixel/hytale/server/core/modules/entity/repulsion/RepulsionConfigPacketGenerator.java new file mode 100644 index 0000000..f1198bb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/repulsion/RepulsionConfigPacketGenerator.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.modules.entity.repulsion; + +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateRepulsionConfig; +import com.hypixel.hytale.server.core.asset.packet.AssetPacketGenerator; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class RepulsionConfigPacketGenerator extends AssetPacketGenerator> { + public RepulsionConfigPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + Int2ObjectMap repulsionConfigs = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + repulsionConfigs.put(assetMap.getIndex(entry.getKey()), entry.getValue().toPacket()); + } + + return new UpdateRepulsionConfig(UpdateType.Init, assetMap.getNextIndex(), repulsionConfigs); + } + + @Nonnull + public Packet generateUpdatePacket( + @Nonnull IndexedLookupTableAssetMap assetMap, + @Nonnull Map loadedAssets, + @Nonnull AssetUpdateQuery query + ) { + Int2ObjectMap repulsionConfigs = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + repulsionConfigs.put(assetMap.getIndex(entry.getKey()), entry.getValue().toPacket()); + } + + return new UpdateRepulsionConfig(UpdateType.AddOrUpdate, assetMap.getNextIndex(), repulsionConfigs); + } + + @Nonnull + public Packet generateRemovePacket( + @Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed, @Nonnull AssetUpdateQuery query + ) { + Int2ObjectMap repulsionConfigs = new Int2ObjectOpenHashMap<>(); + + for (String entry : removed) { + repulsionConfigs.put(assetMap.getIndex(entry), new com.hypixel.hytale.protocol.RepulsionConfig()); + } + + return new UpdateRepulsionConfig(UpdateType.Remove, assetMap.getNextIndex(), repulsionConfigs); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/repulsion/RepulsionSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/repulsion/RepulsionSystems.java new file mode 100644 index 0000000..f1c5d17 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/repulsion/RepulsionSystems.java @@ -0,0 +1,293 @@ +package com.hypixel.hytale.server.core.modules.entity.repulsion; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.OrderPriority; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.ChangeVelocityType; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.system.PlayerSpatialSystem; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.modules.physics.systems.IVelocityModifyingSystem; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RepulsionSystems { + public RepulsionSystems() { + } + + public static class EntityTrackerRemove extends RefChangeSystem { + private final ComponentType componentType; + private final ComponentType visibleComponentType; + @Nonnull + private final Query query; + + public EntityTrackerRemove( + ComponentType visibleComponentType, ComponentType componentType + ) { + this.visibleComponentType = visibleComponentType; + this.componentType = componentType; + this.query = Query.and(visibleComponentType, componentType); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public ComponentType componentType() { + return this.componentType; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull Repulsion component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentSet( + @Nonnull Ref ref, + Repulsion oldComponent, + @Nonnull Repulsion newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull Repulsion component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + for (EntityTrackerSystems.EntityViewer viewer : store.getComponent(ref, this.visibleComponentType).visibleTo.values()) { + viewer.queueRemove(ref, ComponentUpdateType.Repulsion); + } + } + } + + public static class EntityTrackerUpdate extends EntityTickingSystem { + private final ComponentType visibleComponentType; + private final ComponentType componentType; + @Nonnull + private final Query query; + + public EntityTrackerUpdate( + ComponentType visibleComponentType, ComponentType componentType + ) { + this.visibleComponentType = visibleComponentType; + this.componentType = componentType; + this.query = Query.and(visibleComponentType, componentType); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visible = archetypeChunk.getComponent(index, this.visibleComponentType); + Repulsion repulsion = archetypeChunk.getComponent(index, this.componentType); + if (repulsion.consumeNetworkOutdated()) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), repulsion, visible.visibleTo); + } else if (!visible.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), repulsion, visible.newlyVisibleTo); + } + } + + private static void queueUpdatesFor( + Ref ref, @Nonnull Repulsion repulsion, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Repulsion; + update.repulsionConfigIndex = repulsion.getRepulsionConfigIndex(); + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, update); + } + } + } + + public static class PlayerSetup extends HolderSystem { + private final ComponentType componentType; + @Nonnull + private final Query query; + + public PlayerSetup(ComponentType componentType, ComponentType playerComponentType) { + this.componentType = componentType; + this.query = Query.and(playerComponentType, Query.not(componentType)); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + World world = store.getExternalData().getWorld(); + int repulsionConfigIndex = world.getGameplayConfig().getPlayerConfig().getRepulsionConfigIndex(); + if (repulsionConfigIndex == -1) { + if (holder.getComponent(this.componentType) != null) { + holder.removeComponent(this.componentType); + } + } else { + RepulsionConfig repulsion = RepulsionConfig.getAssetMap().getAsset(repulsionConfigIndex); + if (holder.getComponent(this.componentType) != null) { + holder.removeComponent(this.componentType); + } + + holder.addComponent(this.componentType, new Repulsion(repulsion)); + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + } + + public static class RepulsionTicker extends EntityTickingSystem implements IVelocityModifyingSystem { + private final ComponentType repulsionComponentType; + private final ComponentType transformComponentComponentType; + @Nonnull + private final Query query; + @Nonnull + private final Set> dependencies; + private final ResourceType, EntityStore>> spatialComponent; + + public RepulsionTicker( + ComponentType repulsionComponentType, + ComponentType transformComponentComponentType, + ResourceType, EntityStore>> spatialComponent + ) { + this.repulsionComponentType = repulsionComponentType; + this.transformComponentComponentType = transformComponentComponentType; + this.query = Query.and(repulsionComponentType, transformComponentComponentType); + this.spatialComponent = spatialComponent; + this.dependencies = Set.of(new SystemDependency<>(Order.AFTER, PlayerSpatialSystem.class, OrderPriority.CLOSEST)); + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Repulsion repulsionComponent = archetypeChunk.getComponent(index, this.repulsionComponentType); + + assert repulsionComponent != null; + + int repulsionConfigIndex = repulsionComponent.getRepulsionConfigIndex(); + if (repulsionConfigIndex != -1) { + RepulsionConfig repulsion = RepulsionConfig.getAssetMap().getAsset(repulsionConfigIndex); + float radius = repulsion.radius; + TransformComponent transformComponent = archetypeChunk.getComponent(index, this.transformComponentComponentType); + + assert transformComponent != null; + + Vector2d position = new Vector2d(transformComponent.getPosition().x, transformComponent.getPosition().z); + SpatialResource, EntityStore> spatialResource = store.getResource(this.spatialComponent); + List> results = new ObjectArrayList<>(); + spatialResource.getSpatialStructure().ordered(transformComponent.getPosition(), radius, results); + + for (Ref entityRef : results) { + TransformComponent entityTransformComponent = store.getComponent(entityRef, this.transformComponentComponentType); + + assert entityTransformComponent != null; + + Vector2d entityPosition = new Vector2d(entityTransformComponent.getPosition().x, entityTransformComponent.getPosition().z); + if (!entityPosition.equals(position)) { + double distance = position.distanceTo(entityPosition); + if (!(distance < 0.1)) { + double fraction = (radius - distance) / radius; + float maxForce = repulsion.maxForce; + int flip = 1; + if (maxForce < 0.0F) { + flip = -1; + maxForce *= flip; + } + + double force = Math.max((double)repulsion.minForce, maxForce * fraction); + force *= flip; + Vector2d push = entityPosition.subtract(position); + push.normalize(); + push.scale(force); + Velocity entityVelocityComponent = commandBuffer.getComponent(entityRef, Velocity.getComponentType()); + + assert entityVelocityComponent != null; + + Vector3d addedVelocity = new Vector3d((float)push.x, 0.0, (float)push.y); + entityVelocityComponent.addInstruction(addedVelocity, null, ChangeVelocityType.Add); + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/stamina/SprintStaminaRegenDelay.java b/src/com/hypixel/hytale/server/core/modules/entity/stamina/SprintStaminaRegenDelay.java new file mode 100644 index 0000000..5bc30ea --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/stamina/SprintStaminaRegenDelay.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.server.core.modules.entity.stamina; + +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nonnull; + +public class SprintStaminaRegenDelay implements Resource { + private static final AtomicInteger ASSET_VALIDATION_STATE = new AtomicInteger(0); + protected int statIndex = 0; + protected float statValue; + protected int validationState = ASSET_VALIDATION_STATE.get() - 1; + + public static ResourceType getResourceType() { + return StaminaModule.get().getSprintRegenDelayResourceType(); + } + + public SprintStaminaRegenDelay() { + } + + public SprintStaminaRegenDelay(@Nonnull SprintStaminaRegenDelay other) { + this.statIndex = other.statIndex; + this.statValue = other.statValue; + this.validationState = other.validationState; + } + + public int getIndex() { + return this.statIndex; + } + + public float getValue() { + return this.statValue; + } + + public boolean validate() { + return this.validationState == ASSET_VALIDATION_STATE.get(); + } + + public boolean hasDelay() { + return this.statIndex != 0 && this.statValue < 0.0F; + } + + public void markEmpty() { + this.update(0, 0.0F); + } + + public void update(int statIndex, float statValue) { + this.statIndex = statIndex; + this.statValue = statValue; + this.validationState = ASSET_VALIDATION_STATE.get(); + } + + @Nonnull + @Override + public Resource clone() { + return new SprintStaminaRegenDelay(this); + } + + @Nonnull + @Override + public String toString() { + return "SprintStaminaRegenDelay{statIndex=" + this.statIndex + ", statValue=" + this.statValue + ", validationState=" + this.validationState + "}"; + } + + public static void invalidateResources() { + ASSET_VALIDATION_STATE.incrementAndGet(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/stamina/StaminaGameplayConfig.java b/src/com/hypixel/hytale/server/core/modules/entity/stamina/StaminaGameplayConfig.java new file mode 100644 index 0000000..19c2425 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/stamina/StaminaGameplayConfig.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.server.core.modules.entity.stamina; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import javax.annotation.Nonnull; + +public class StaminaGameplayConfig { + public static final String ID = "Stamina"; + public static final BuilderCodec CODEC = BuilderCodec.builder(StaminaGameplayConfig.class, StaminaGameplayConfig::new) + .appendInherited( + new KeyedCodec<>("SprintRegenDelay", StaminaGameplayConfig.SprintRegenDelayConfig.CODEC), + (staminaGameplayConfig, s) -> staminaGameplayConfig.sprintRegenDelay = s, + staminaGameplayConfig -> staminaGameplayConfig.sprintRegenDelay, + (staminaGameplayConfig, parent) -> staminaGameplayConfig.sprintRegenDelay = parent.sprintRegenDelay + ) + .addValidator(Validators.nonNull()) + .documentation("The stamina regeneration delay applied after sprinting") + .add() + .build(); + protected StaminaGameplayConfig.SprintRegenDelayConfig sprintRegenDelay; + + public StaminaGameplayConfig() { + } + + @Nonnull + public StaminaGameplayConfig.SprintRegenDelayConfig getSprintRegenDelay() { + return this.sprintRegenDelay; + } + + @Nonnull + @Override + public String toString() { + return "StaminaGameplayConfig{sprintRegenDelay=" + this.sprintRegenDelay + "}"; + } + + public static class SprintRegenDelayConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder( + StaminaGameplayConfig.SprintRegenDelayConfig.class, StaminaGameplayConfig.SprintRegenDelayConfig::new + ) + .appendInherited( + new KeyedCodec<>("EntityStatId", Codec.STRING), + (entityStatConfig, s) -> entityStatConfig.statId = s, + entityStatConfig -> entityStatConfig.statId, + (entityStatConfig, parent) -> entityStatConfig.statId = parent.statId + ) + .addValidator(Validators.nonNull()) + .addValidator(EntityStatType.VALIDATOR_CACHE.getValidator()) + .documentation("The ID of the stamina regen delay EntityStat") + .add() + .appendInherited( + new KeyedCodec<>("Value", Codec.FLOAT), + (entityStatConfig, s) -> entityStatConfig.statValue = s, + entityStatConfig -> entityStatConfig.statValue, + (entityStatConfig, parent) -> entityStatConfig.statValue = parent.statValue + ) + .addValidator(Validators.max(0.0F)) + .documentation("The amount of stamina regen delay to apply") + .add() + .afterDecode(entityStatConfig -> entityStatConfig.statIndex = EntityStatType.getAssetMap().getIndex(entityStatConfig.statId)) + .build(); + protected String statId; + protected int statIndex; + protected float statValue; + + public SprintRegenDelayConfig() { + } + + public int getIndex() { + return this.statIndex; + } + + public float getValue() { + return this.statValue; + } + + @Nonnull + @Override + public String toString() { + return "SprintRegenDelayConfig{statId='" + this.statId + "', statIndex=" + this.statIndex + ", statValue=" + this.statValue + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/stamina/StaminaModule.java b/src/com/hypixel/hytale/server/core/modules/entity/stamina/StaminaModule.java new file mode 100644 index 0000000..2fdb8cc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/stamina/StaminaModule.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.core.modules.entity.stamina; + +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class StaminaModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(StaminaModule.class) + .depends(EntityModule.class) + .depends(EntityStatsModule.class) + .build(); + private static StaminaModule instance; + private ResourceType sprintRegenDelayResourceType; + + public StaminaModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + this.sprintRegenDelayResourceType = this.getEntityStoreRegistry().registerResource(SprintStaminaRegenDelay.class, SprintStaminaRegenDelay::new); + this.getEntityStoreRegistry().registerSystem(new StaminaSystems.SprintStaminaEffectSystem()); + this.getCodecRegistry(GameplayConfig.PLUGIN_CODEC).register(StaminaGameplayConfig.class, "Stamina", StaminaGameplayConfig.CODEC); + this.getEventRegistry().register(LoadedAssetsEvent.class, GameplayConfig.class, StaminaModule::onGameplayConfigsLoaded); + } + + public ResourceType getSprintRegenDelayResourceType() { + return this.sprintRegenDelayResourceType; + } + + protected static void onGameplayConfigsLoaded(LoadedAssetsEvent> event) { + SprintStaminaRegenDelay.invalidateResources(); + } + + public static StaminaModule get() { + return instance; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/stamina/StaminaSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/stamina/StaminaSystems.java new file mode 100644 index 0000000..955eb95 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/stamina/StaminaSystems.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.server.core.modules.entity.stamina; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesSystems; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import javax.annotation.Nonnull; + +public class StaminaSystems { + public StaminaSystems() { + } + + public static class SprintStaminaEffectSystem extends EntityTickingSystem { + private final ComponentType playerComponentType = Player.getComponentType(); + private final ComponentType entityStatMapComponentType = EntityStatMap.getComponentType(); + private final ComponentType movementStatesComponentType = MovementStatesComponent.getComponentType(); + private final ResourceType sprintRegenDelayResourceType = SprintStaminaRegenDelay.getResourceType(); + private final Query query = Query.and(this.playerComponentType, this.entityStatMapComponentType, this.movementStatesComponentType); + private final Set> dependencies = Set.of( + new SystemDependency<>(Order.BEFORE, MovementStatesSystems.TickingSystem.class), + new SystemDependency<>(Order.BEFORE, EntityStatsModule.PlayerRegenerateStatsSystem.class) + ); + + public SprintStaminaEffectSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + if (this.updateResource(store)) { + super.tick(dt, systemIndex, store); + } + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + MovementStatesComponent movementStates = archetypeChunk.getComponent(index, this.movementStatesComponentType); + if (!movementStates.getMovementStates().sprinting && movementStates.getSentMovementStates().sprinting) { + SprintStaminaRegenDelay regenDelay = store.getResource(this.sprintRegenDelayResourceType); + EntityStatMap statMap = archetypeChunk.getComponent(index, this.entityStatMapComponentType); + EntityStatValue statValue = statMap.get(regenDelay.getIndex()); + if (statValue != null && statValue.get() <= regenDelay.getValue()) { + return; + } + + statMap.setStatValue(regenDelay.getIndex(), regenDelay.getValue()); + } + } + + protected boolean updateResource(@Nonnull Store store) { + SprintStaminaRegenDelay resource = store.getResource(this.sprintRegenDelayResourceType); + if (resource.validate()) { + return resource.hasDelay(); + } else { + GameplayConfig gameplayConfig = store.getExternalData().getWorld().getGameplayConfig(); + StaminaGameplayConfig staminaConfig = gameplayConfig.getPluginConfig().get(StaminaGameplayConfig.class); + if (staminaConfig != null && staminaConfig.getSprintRegenDelay().getIndex() != Integer.MIN_VALUE) { + StaminaGameplayConfig.SprintRegenDelayConfig regenDelay = staminaConfig.getSprintRegenDelay(); + resource.update(regenDelay.getIndex(), regenDelay.getValue()); + return resource.hasDelay(); + } else { + resource.markEmpty(); + return false; + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/AudioSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/system/AudioSystems.java new file mode 100644 index 0000000..8624fdc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/AudioSystems.java @@ -0,0 +1,191 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.BlockSoundEvent; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.server.core.asset.type.blocksound.config.BlockSoundSet; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.modules.entity.component.AudioComponent; +import com.hypixel.hytale.server.core.modules.entity.component.MovementAudioComponent; +import com.hypixel.hytale.server.core.modules.entity.component.PositionDataComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AudioSystems { + public AudioSystems() { + } + + public static class EntityTrackerUpdate extends EntityTickingSystem { + private final ComponentType visibleComponentType = EntityTrackerSystems.Visible.getComponentType(); + private final ComponentType audioComponentType = AudioComponent.getComponentType(); + private final Query query = Query.and(this.visibleComponentType, this.audioComponentType); + + public EntityTrackerUpdate() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.visibleComponentType); + + assert visibleComponent != null; + + AudioComponent audioComponent = archetypeChunk.getComponent(index, this.audioComponentType); + + assert audioComponent != null; + + Ref ref = archetypeChunk.getReferenceTo(index); + if (audioComponent.consumeNetworkOutdated()) { + queueUpdatesFor(ref, audioComponent, visibleComponent.visibleTo); + } else if (!visibleComponent.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(ref, audioComponent, visibleComponent.newlyVisibleTo); + } + } + + private static void queueUpdatesFor( + @Nonnull Ref ref, @Nonnull AudioComponent audioComponent, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Audio; + update.soundEventIds = audioComponent.getSoundEventIds(); + + for (Entry, EntityTrackerSystems.EntityViewer> entry : visibleTo.entrySet()) { + entry.getValue().queueUpdate(ref, update); + } + } + } + + public static class TickMovementAudio extends EntityTickingSystem { + public TickMovementAudio() { + } + + @Nonnull + @Override + public Query getQuery() { + return Query.and( + TransformComponent.getComponentType(), + PositionDataComponent.getComponentType(), + MovementAudioComponent.getComponentType(), + MovementStatesComponent.getComponentType() + ); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + PositionDataComponent positionDataComponent = archetypeChunk.getComponent(index, PositionDataComponent.getComponentType()); + + assert positionDataComponent != null; + + MovementAudioComponent movementAudioComponent = archetypeChunk.getComponent(index, MovementAudioComponent.getComponentType()); + + assert movementAudioComponent != null; + + int insideBlockTypeId = positionDataComponent.getInsideBlockTypeId(); + int lastInsideBlockTypeId = movementAudioComponent.getLastInsideBlockTypeId(); + Ref ref = archetypeChunk.getReferenceTo(index); + Vector3d position = transformComponent.getPosition(); + if (lastInsideBlockTypeId != insideBlockTypeId) { + movementAudioComponent.setLastInsideBlockTypeId(insideBlockTypeId); + playMoveInSound(ref, store, movementAudioComponent, position, insideBlockTypeId); + if (lastInsideBlockTypeId != 0) { + BlockType blockType = BlockType.getAssetMap().getAsset(lastInsideBlockTypeId); + int soundSetId = blockType.getBlockSoundSetIndex(); + if (soundSetId != 0) { + BlockSoundSet soundSet = BlockSoundSet.getAssetMap().getAsset(soundSetId); + int soundEvent = soundSet.getSoundEventIndices().getOrDefault(BlockSoundEvent.MoveOut, 0); + if (soundEvent != 0) { + SoundUtil.playSoundEvent3d( + soundEvent, SoundCategory.SFX, position.x, position.y, position.z, movementAudioComponent.getShouldHearPredicate(ref), commandBuffer + ); + } + } + } + } + + MovementStatesComponent movementStates = archetypeChunk.getComponent(index, MovementStatesComponent.getComponentType()); + + assert movementStates != null; + + if (!movementStates.getMovementStates().idle && movementAudioComponent.canMoveInRepeat() && movementAudioComponent.tickMoveInRepeat(dt)) { + playMoveInSound(ref, commandBuffer, movementAudioComponent, position, insideBlockTypeId); + } + } + + private static void playMoveInSound( + @Nonnull Ref ref, + @Nonnull ComponentAccessor store, + @Nonnull MovementAudioComponent movementAudioComponent, + @Nonnull Vector3d position, + int insideBlockTypeId + ) { + movementAudioComponent.setNextMoveInRepeat(MovementAudioComponent.NO_REPEAT); + if (insideBlockTypeId != 0) { + BlockType blockType = BlockType.getAssetMap().getAsset(insideBlockTypeId); + int soundSetId = blockType.getBlockSoundSetIndex(); + if (soundSetId != 0) { + BlockSoundSet soundSet = BlockSoundSet.getAssetMap().getAsset(soundSetId); + int soundEvent = soundSet.getSoundEventIndices().getOrDefault(BlockSoundEvent.MoveIn, 0); + if (soundEvent != 0) { + SoundUtil.playSoundEvent3d( + soundEvent, SoundCategory.SFX, position.x, position.y, position.z, movementAudioComponent.getShouldHearPredicate(ref), store + ); + movementAudioComponent.setNextMoveInRepeat( + RandomExtra.randomRange(soundSet.getMoveInRepeatRange().getInclusiveMin(), soundSet.getMoveInRepeatRange().getInclusiveMax()) + ); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/EntityInteractableSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/system/EntityInteractableSystems.java new file mode 100644 index 0000000..994acaa --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/EntityInteractableSystems.java @@ -0,0 +1,153 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.Interactable; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityInteractableSystems { + public EntityInteractableSystems() { + } + + public static class EntityTrackerAddAndRemove extends RefChangeSystem { + private final ComponentType interactableComponentType = Interactable.getComponentType(); + private final ComponentType visibleComponentType; + @Nonnull + private final Query query; + + public EntityTrackerAddAndRemove(ComponentType visibleComponentType) { + this.visibleComponentType = visibleComponentType; + this.query = Query.and(visibleComponentType, this.interactableComponentType); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public ComponentType componentType() { + return this.interactableComponentType; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull Interactable component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + commandBuffer.getResource(EntityInteractableSystems.QueueResource.getResourceType()).queue.add(ref); + } + + public void onComponentSet( + @Nonnull Ref ref, + Interactable oldComponent, + @Nonnull Interactable newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull Interactable component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + for (EntityTrackerSystems.EntityViewer viewer : store.getComponent(ref, this.visibleComponentType).visibleTo.values()) { + viewer.queueRemove(ref, ComponentUpdateType.Interactable); + } + } + } + + public static class EntityTrackerUpdate extends EntityTickingSystem { + private final ComponentType componentType; + @Nonnull + private final Query query; + + public EntityTrackerUpdate(ComponentType componentType) { + this.componentType = componentType; + this.query = Query.and(componentType, Interactable.getComponentType()); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + super.tick(dt, systemIndex, store); + store.getResource(EntityInteractableSystems.QueueResource.getResourceType()).queue.clear(); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visible = archetypeChunk.getComponent(index, this.componentType); + Ref ref = archetypeChunk.getReferenceTo(index); + if (commandBuffer.getResource(EntityInteractableSystems.QueueResource.getResourceType()).queue.remove(ref)) { + queueUpdatesFor(ref, visible.visibleTo); + } else if (!visible.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(ref, visible.newlyVisibleTo); + } + } + + private static void queueUpdatesFor(Ref ref, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Interactable; + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, update); + } + } + } + + public static class QueueResource implements Resource { + private final Set> queue = ConcurrentHashMap.newKeySet(); + + public QueueResource() { + } + + public static ResourceType getResourceType() { + return EntityModule.get().getInteractableQueueResourceType(); + } + + @Nonnull + @Override + public Resource clone() { + return new EntityInteractableSystems.QueueResource(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/EntitySpatialSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/system/EntitySpatialSystem.java new file mode 100644 index 0000000..89280e7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/EntitySpatialSystem.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.spatial.SpatialSystem; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.Intangible; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EntitySpatialSystem extends SpatialSystem { + public static final Query QUERY = Query.and( + TransformComponent.getComponentType(), Query.not(Intangible.getComponentType()), Query.not(Player.getComponentType()) + ); + + public EntitySpatialSystem(ResourceType, EntityStore>> spatialResource) { + super(spatialResource); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + super.tick(dt, systemIndex, store); + } + + @Nonnull + @Override + public Vector3d getPosition(@Nonnull ArchetypeChunk archetypeChunk, int index) { + return archetypeChunk.getComponent(index, TransformComponent.getComponentType()).getPosition(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/EntitySystems.java b/src/com/hypixel/hytale/server/core/modules/entity/system/EntitySystems.java new file mode 100644 index 0000000..942a370 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/EntitySystems.java @@ -0,0 +1,354 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.dependency.SystemGroupDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.protocol.ColorLight; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.DynamicLight; +import com.hypixel.hytale.server.core.modules.entity.component.FromPrefab; +import com.hypixel.hytale.server.core.modules.entity.component.FromWorldGen; +import com.hypixel.hytale.server.core.modules.entity.component.NewSpawnComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.EntityChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntitySystems { + public EntitySystems() { + } + + public static class ClearFromPrefabMarker extends EntitySystems.ClearMarker { + public ClearFromPrefabMarker(@Nonnull ComponentType componentType, @Nonnull SystemGroup preGroup) { + super(componentType, preGroup); + } + } + + public static class ClearFromWorldGenMarker extends EntitySystems.ClearMarker { + public ClearFromWorldGenMarker(@Nonnull ComponentType componentType, @Nonnull SystemGroup preGroup) { + super(componentType, preGroup); + } + } + + public abstract static class ClearMarker> extends RefSystem { + @Nonnull + private final ComponentType componentType; + @Nonnull + private final Set> dependencies; + + public ClearMarker(@Nonnull ComponentType componentType, @Nonnull SystemGroup preGroup) { + this.componentType = componentType; + this.dependencies = Set.of(new SystemGroupDependency<>(Order.AFTER, preGroup)); + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + commandBuffer.removeComponent(ref, this.componentType); + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Override + public Query getQuery() { + return this.componentType; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + } + + public static class DynamicLightTracker extends EntityTickingSystem { + @Nonnull + private final ComponentType componentType; + @Nonnull + private final ComponentType dynamicLightType; + @Nonnull + private final Query query; + + public DynamicLightTracker(@Nonnull ComponentType componentType) { + this.componentType = componentType; + this.dynamicLightType = DynamicLight.getComponentType(); + this.query = Query.and(componentType, this.dynamicLightType); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.componentType); + + assert visibleComponent != null; + + DynamicLight dynamicLightComponent = archetypeChunk.getComponent(index, this.dynamicLightType); + + assert dynamicLightComponent != null; + + ColorLight dynamicLight = dynamicLightComponent.getColorLight(); + if (dynamicLightComponent.consumeNetworkOutdated()) { + if (dynamicLight != null) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), dynamicLight, visibleComponent.visibleTo); + } else { + queueRemoveFor(archetypeChunk.getReferenceTo(index), visibleComponent.visibleTo); + } + } else if (!visibleComponent.newlyVisibleTo.isEmpty() && dynamicLight != null) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), dynamicLight, visibleComponent.newlyVisibleTo); + } + } + + private static void queueUpdatesFor( + @Nonnull Ref ref, @Nonnull ColorLight dynamicLight, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.DynamicLight; + update.dynamicLight = dynamicLight; + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, update); + } + } + + private static void queueRemoveFor(@Nonnull Ref ref, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo) { + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueRemove(ref, ComponentUpdateType.DynamicLight); + } + } + } + + public static class NewSpawnEntityTrackerUpdate extends EntityTickingSystem { + @Nonnull + private final ComponentType visibleComponentType = EntityTrackerSystems.Visible.getComponentType(); + @Nonnull + private final ComponentType newSpawnComponentType = NewSpawnComponent.getComponentType(); + @Nonnull + private final Query query = Query.and(this.visibleComponentType, this.newSpawnComponentType); + + public NewSpawnEntityTrackerUpdate() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.visibleComponentType); + + assert visibleComponent != null; + + NewSpawnComponent newSpawnComponent = archetypeChunk.getComponent(index, this.newSpawnComponentType); + + assert newSpawnComponent != null; + + Ref ref = archetypeChunk.getReferenceTo(index); + if (!visibleComponent.newlyVisibleTo.isEmpty()) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.NewSpawn; + + for (Entry, EntityTrackerSystems.EntityViewer> entry : visibleComponent.newlyVisibleTo.entrySet()) { + entry.getValue().queueUpdate(ref, update); + } + } + } + } + + public static class NewSpawnTick extends EntityTickingSystem { + @Nonnull + private final ComponentType newSpawnComponentType = NewSpawnComponent.getComponentType(); + + public NewSpawnTick() { + } + + @Nonnull + @Override + public Query getQuery() { + return this.newSpawnComponentType; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + NewSpawnComponent newSpawnComponent = archetypeChunk.getComponent(index, this.newSpawnComponentType); + + assert newSpawnComponent != null; + + if (newSpawnComponent.newSpawnWindowPassed(dt)) { + commandBuffer.removeComponent(archetypeChunk.getReferenceTo(index), this.newSpawnComponentType); + } + } + } + + public static class OnLoadFromExternal extends HolderSystem { + @Nonnull + private final Query query; + @Nonnull + private final SystemGroup group; + @Nonnull + private final Set> dependencies; + + public OnLoadFromExternal( + @Nonnull ComponentType fromPrefab, + @Nonnull ComponentType fromWorldGen, + @Nonnull SystemGroup group + ) { + this.query = Query.and(Query.or(fromPrefab, fromWorldGen), UUIDComponent.getComponentType()); + this.group = group; + this.dependencies = Set.of( + new SystemDependency<>(Order.BEFORE, EntityStore.UUIDSystem.class), new SystemDependency<>(Order.AFTER, EntityModule.LegacyUUIDSystem.class) + ); + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.putComponent(UUIDComponent.getComponentType(), UUIDComponent.generateVersion3UUID()); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public SystemGroup getGroup() { + return this.group; + } + } + + public static class UnloadEntityFromChunk extends RefSystem { + public UnloadEntityFromChunk() { + } + + @Override + public Query getQuery() { + return TransformComponent.getComponentType(); + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Ref chunkRef = transformComponent.getChunkRef(); + if (chunkRef != null && chunkRef.isValid()) { + World world = commandBuffer.getExternalData().getWorld(); + ChunkStore chunkStore = world.getChunkStore(); + Store chunkComponentStore = chunkStore.getStore(); + EntityChunk entityChunkComponent = chunkComponentStore.getComponent(chunkRef, EntityChunk.getComponentType()); + + assert entityChunkComponent != null; + + switch (reason) { + case REMOVE: + entityChunkComponent.removeEntityReference(ref); + break; + case UNLOAD: + entityChunkComponent.unloadEntityReference(ref); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/HideEntitySystems.java b/src/com/hypixel/hytale/server/core/modules/entity/system/HideEntitySystems.java new file mode 100644 index 0000000..212c4dd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/HideEntitySystems.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.HiddenFromAdventurePlayers; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Collections; +import java.util.Iterator; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HideEntitySystems { + public HideEntitySystems() { + } + + public static class AdventurePlayerSystem extends EntityTickingSystem { + @Nonnull + private final ComponentType entityViewerComponentType = EntityTrackerSystems.EntityViewer.getComponentType(); + @Nonnull + private final ComponentType playerComponentType = Player.getComponentType(); + @Nonnull + private final ComponentType hiddenFromAdventurePlayersComponentType = HiddenFromAdventurePlayers.getComponentType(); + @Nonnull + private final ComponentType playerSettingsComponentType = EntityModule.get().getPlayerSettingsComponentType(); + @Nonnull + private final Query query = Query.and(this.entityViewerComponentType, this.playerComponentType, this.playerSettingsComponentType); + @Nonnull + private final Set> dependencies = Collections.singleton( + new SystemDependency<>(Order.AFTER, EntityTrackerSystems.CollectVisible.class) + ); + + public AdventurePlayerSystem() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.FIND_VISIBLE_ENTITIES_GROUP; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.EntityViewer entityViewerComponent = archetypeChunk.getComponent(index, this.entityViewerComponentType); + + assert entityViewerComponent != null; + + PlayerSettings playerSettingsComponent = archetypeChunk.getComponent(index, this.playerSettingsComponentType); + + assert playerSettingsComponent != null; + + Player playerComponent = archetypeChunk.getComponent(index, this.playerComponentType); + + assert playerComponent != null; + + if (playerComponent.getGameMode() == GameMode.Adventure || !playerSettingsComponent.showEntityMarkers()) { + Iterator> iterator = entityViewerComponent.visible.iterator(); + + while (iterator.hasNext()) { + Ref ref = iterator.next(); + if (commandBuffer.getArchetype(ref).contains(this.hiddenFromAdventurePlayersComponentType)) { + entityViewerComponent.hiddenCount++; + iterator.remove(); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/IntangibleSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/system/IntangibleSystems.java new file mode 100644 index 0000000..6189e18 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/IntangibleSystems.java @@ -0,0 +1,153 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.Intangible; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class IntangibleSystems { + public IntangibleSystems() { + } + + public static class EntityTrackerAddAndRemove extends RefChangeSystem { + private final ComponentType intangibleComponentType = Intangible.getComponentType(); + private final ComponentType visibleComponentType; + @Nonnull + private final Query query; + + public EntityTrackerAddAndRemove(ComponentType visibleComponentType) { + this.visibleComponentType = visibleComponentType; + this.query = Query.and(visibleComponentType, this.intangibleComponentType); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public ComponentType componentType() { + return this.intangibleComponentType; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull Intangible component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + commandBuffer.getResource(IntangibleSystems.QueueResource.getResourceType()).queue.add(ref); + } + + public void onComponentSet( + @Nonnull Ref ref, + Intangible oldComponent, + @Nonnull Intangible newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull Intangible component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + for (EntityTrackerSystems.EntityViewer viewer : store.getComponent(ref, this.visibleComponentType).visibleTo.values()) { + viewer.queueRemove(ref, ComponentUpdateType.Intangible); + } + } + } + + public static class EntityTrackerUpdate extends EntityTickingSystem { + private final ComponentType componentType; + @Nonnull + private final Query query; + + public EntityTrackerUpdate(ComponentType componentType) { + this.componentType = componentType; + this.query = Query.and(componentType, Intangible.getComponentType()); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + super.tick(dt, systemIndex, store); + store.getResource(IntangibleSystems.QueueResource.getResourceType()).queue.clear(); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visible = archetypeChunk.getComponent(index, this.componentType); + Ref ref = archetypeChunk.getReferenceTo(index); + if (commandBuffer.getResource(IntangibleSystems.QueueResource.getResourceType()).queue.remove(ref)) { + queueUpdatesFor(ref, visible.visibleTo); + } else if (!visible.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(ref, visible.newlyVisibleTo); + } + } + + private static void queueUpdatesFor(Ref ref, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Intangible; + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, update); + } + } + } + + public static class QueueResource implements Resource { + private final Set> queue = ConcurrentHashMap.newKeySet(); + + public QueueResource() { + } + + public static ResourceType getResourceType() { + return EntityModule.get().getIntangibleQueueResourceType(); + } + + @Nonnull + @Override + public Resource clone() { + return new IntangibleSystems.QueueResource(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/InvulnerableSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/system/InvulnerableSystems.java new file mode 100644 index 0000000..f11e705 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/InvulnerableSystems.java @@ -0,0 +1,153 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.Invulnerable; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InvulnerableSystems { + public InvulnerableSystems() { + } + + public static class EntityTrackerAddAndRemove extends RefChangeSystem { + private final ComponentType invulnerableComponentType = Invulnerable.getComponentType(); + private final ComponentType visibleComponentType; + @Nonnull + private final Query query; + + public EntityTrackerAddAndRemove(ComponentType visibleComponentType) { + this.visibleComponentType = visibleComponentType; + this.query = Query.and(visibleComponentType, this.invulnerableComponentType); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public ComponentType componentType() { + return this.invulnerableComponentType; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull Invulnerable component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + commandBuffer.getResource(InvulnerableSystems.QueueResource.getResourceType()).queue.add(ref); + } + + public void onComponentSet( + @Nonnull Ref ref, + Invulnerable oldComponent, + @Nonnull Invulnerable newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull Invulnerable component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + for (EntityTrackerSystems.EntityViewer viewer : store.getComponent(ref, this.visibleComponentType).visibleTo.values()) { + viewer.queueRemove(ref, ComponentUpdateType.Invulnerable); + } + } + } + + public static class EntityTrackerUpdate extends EntityTickingSystem { + private final ComponentType componentType; + @Nonnull + private final Query query; + + public EntityTrackerUpdate(ComponentType componentType) { + this.componentType = componentType; + this.query = Query.and(componentType, Invulnerable.getComponentType()); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + super.tick(dt, systemIndex, store); + store.getResource(InvulnerableSystems.QueueResource.getResourceType()).queue.clear(); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visible = archetypeChunk.getComponent(index, this.componentType); + Ref ref = archetypeChunk.getReferenceTo(index); + if (commandBuffer.getResource(InvulnerableSystems.QueueResource.getResourceType()).queue.remove(ref)) { + queueUpdatesFor(ref, visible.visibleTo); + } else if (!visible.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(ref, visible.newlyVisibleTo); + } + } + + private static void queueUpdatesFor(Ref ref, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Invulnerable; + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, update); + } + } + } + + public static class QueueResource implements Resource { + private final Set> queue = ConcurrentHashMap.newKeySet(); + + public QueueResource() { + } + + public static ResourceType getResourceType() { + return EntityModule.get().getInvulnerableQueueResourceType(); + } + + @Nonnull + @Override + public Resource clone() { + return new InvulnerableSystems.QueueResource(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/ItemSpatialSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/system/ItemSpatialSystem.java new file mode 100644 index 0000000..3919b25 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/ItemSpatialSystem.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.AndQuery; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.spatial.SpatialSystem; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.modules.entity.item.PreventItemMerging; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ItemSpatialSystem extends SpatialSystem { + @Nonnull + private static final AndQuery QUERY = Query.and( + ItemComponent.getComponentType(), TransformComponent.getComponentType(), Query.not(PreventItemMerging.getComponentType()) + ); + + public ItemSpatialSystem(ResourceType, EntityStore>> spatialResource) { + super(spatialResource); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + super.tick(dt, systemIndex, store); + } + + @Nonnull + @Override + public Vector3d getPosition(@Nonnull ArchetypeChunk archetypeChunk, int index) { + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + return transformComponent.getPosition(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/ModelSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/system/ModelSystems.java new file mode 100644 index 0000000..cf42c5e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/ModelSystems.java @@ -0,0 +1,504 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.common.util.RandomUtil; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.protocol.PlayerSkin; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementManager; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesSystems; +import com.hypixel.hytale.server.core.modules.entity.component.ActiveAnimationComponent; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.PersistentModel; +import com.hypixel.hytale.server.core.modules.entity.component.PropComponent; +import com.hypixel.hytale.server.core.modules.entity.player.ApplyRandomSkinPersistedComponent; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSkinComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.prefab.PrefabCopyableComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ModelSystems { + public ModelSystems() { + } + + public static class AnimationEntityTrackerUpdate extends EntityTickingSystem { + private final ComponentType visibleComponentType = EntityTrackerSystems.Visible.getComponentType(); + private final ComponentType activeAnimationComponentType = ActiveAnimationComponent.getComponentType(); + private final Query query = Query.and(this.visibleComponentType, this.activeAnimationComponentType); + + public AnimationEntityTrackerUpdate() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.visibleComponentType); + + assert visibleComponent != null; + + ActiveAnimationComponent activeAnimationComponent = archetypeChunk.getComponent(index, this.activeAnimationComponentType); + + assert activeAnimationComponent != null; + + Ref ref = archetypeChunk.getReferenceTo(index); + if (activeAnimationComponent.consumeNetworkOutdated()) { + queueUpdatesFor(ref, activeAnimationComponent, visibleComponent.visibleTo); + } else if (!visibleComponent.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(ref, activeAnimationComponent, visibleComponent.newlyVisibleTo); + } + } + + private static void queueUpdatesFor( + @Nonnull Ref ref, + @Nonnull ActiveAnimationComponent animationComponent, + @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.ActiveAnimations; + update.activeAnimations = animationComponent.getActiveAnimations(); + + for (Entry, EntityTrackerSystems.EntityViewer> entry : visibleTo.entrySet()) { + entry.getValue().queueUpdate(ref, update); + } + } + } + + public static class ApplyRandomSkin extends HolderSystem { + private final ComponentType modelComponentType = ModelComponent.getComponentType(); + private final ComponentType randomSkinComponent = ApplyRandomSkinPersistedComponent.getComponentType(); + private final Query query = Query.and(this.randomSkinComponent, this.modelComponentType); + + public ApplyRandomSkin() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + PlayerSkin playerSkin = CosmeticsModule.get().generateRandomSkin(RandomUtil.getSecureRandom()); + holder.putComponent(PlayerSkinComponent.getComponentType(), new PlayerSkinComponent(playerSkin)); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + } + + public static class AssignNetworkIdToProps extends HolderSystem { + private final ComponentType propComponentType = PropComponent.getComponentType(); + private final ComponentType networkIdComponentType = NetworkId.getComponentType(); + private final Query query = Query.and(this.propComponentType, Query.not(this.networkIdComponentType)); + + public AssignNetworkIdToProps() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.addComponent(this.networkIdComponentType, new NetworkId(store.getExternalData().takeNextNetworkId())); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + } + + public static class EnsurePropsPrefabCopyable extends HolderSystem { + private final ComponentType propComponentType = PropComponent.getComponentType(); + + public EnsurePropsPrefabCopyable() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(PrefabCopyableComponent.getComponentType()); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.propComponentType; + } + } + + public static class ModelChange extends RefChangeSystem { + private final ComponentType modelComponentType = ModelComponent.getComponentType(); + private final ComponentType persistentModelComponentType = PersistentModel.getComponentType(); + + public ModelChange() { + } + + @Override + public Query getQuery() { + return this.persistentModelComponentType; + } + + @Nonnull + @Override + public ComponentType componentType() { + return this.modelComponentType; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull ModelComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentSet( + @Nonnull Ref ref, + ModelComponent oldComponent, + @Nonnull ModelComponent newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + PersistentModel persistentModel = store.getComponent(ref, this.persistentModelComponentType); + persistentModel.setModelReference(newComponent.getModel().toReference()); + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull ModelComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + commandBuffer.removeComponent(ref, this.persistentModelComponentType); + } + } + + public static class ModelSpawned extends HolderSystem { + private final ComponentType modelComponentType = ModelComponent.getComponentType(); + private final ComponentType boundingBoxComponentType = BoundingBox.getComponentType(); + private final Set> dependencies = Set.of(new SystemDependency<>(Order.AFTER, ModelSystems.SetRenderedModel.class)); + + public ModelSpawned() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + Model model = holder.getComponent(this.modelComponentType).getModel(); + Box modelBoundingBox = model.getBoundingBox(); + if (modelBoundingBox != null) { + BoundingBox boundingBox = holder.getComponent(this.boundingBoxComponentType); + if (boundingBox == null) { + boundingBox = new BoundingBox(); + holder.addComponent(this.boundingBoxComponentType, boundingBox); + } + + boundingBox.setBoundingBox(modelBoundingBox); + boundingBox.setDetailBoxes(model.getDetailBoxes()); + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Override + public Query getQuery() { + return this.modelComponentType; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + } + + public static class PlayerConnect extends HolderSystem { + private final ComponentType playerComponentType = Player.getComponentType(); + private final ComponentType modelComponentType = ModelComponent.getComponentType(); + private final Query query = Query.and(this.playerComponentType, Query.not(this.modelComponentType)); + private final Set> dependencies = Set.of(new SystemDependency<>(Order.BEFORE, ModelSystems.ModelSpawned.class)); + + public PlayerConnect() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + Player player = holder.getComponent(this.playerComponentType); + DefaultAssetMap assetMap = ModelAsset.getAssetMap(); + String preset = player.getPlayerConfigData().getPreset(); + ModelAsset modelAsset = preset != null ? assetMap.getAsset(preset) : null; + if (modelAsset != null) { + Model model = Model.createUnitScaleModel(modelAsset); + holder.addComponent(this.modelComponentType, new ModelComponent(model)); + } else { + ModelAsset defaultModelAsset = assetMap.getAsset("Player"); + if (defaultModelAsset != null) { + Model defaultModel = Model.createUnitScaleModel(defaultModelAsset); + holder.addComponent(this.modelComponentType, new ModelComponent(defaultModel)); + } + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + } + + public static class PlayerUpdateMovementManager extends RefChangeSystem { + private final ComponentType modelComponentType = ModelComponent.getComponentType(); + private final ComponentType playerComponentType = Player.getComponentType(); + private final Set> dependencies = Set.of(new SystemDependency<>(Order.AFTER, ModelSystems.UpdateBoundingBox.class)); + + public PlayerUpdateMovementManager() { + } + + @Nonnull + @Override + public Query getQuery() { + return this.playerComponentType; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Nonnull + @Override + public ComponentType componentType() { + return this.modelComponentType; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull ModelComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + this.updateMovementController(ref, commandBuffer); + } + + public void onComponentSet( + @Nonnull Ref ref, + ModelComponent oldComponent, + @Nonnull ModelComponent newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + this.updateMovementController(ref, commandBuffer); + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull ModelComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + this.updateMovementController(ref, commandBuffer); + } + + private void updateMovementController(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + MovementManager movementManagerComponent = componentAccessor.getComponent(ref, MovementManager.getComponentType()); + + assert movementManagerComponent != null; + + movementManagerComponent.resetDefaultsAndUpdate(ref, componentAccessor); + } + } + + public static class SetRenderedModel extends HolderSystem { + private final ComponentType modelComponentType = ModelComponent.getComponentType(); + private final ComponentType persistentModelComponentType = PersistentModel.getComponentType(); + private final Query query = Query.and(this.persistentModelComponentType, Query.not(this.modelComponentType)); + + public SetRenderedModel() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + PersistentModel persistentModel = holder.getComponent(this.persistentModelComponentType); + holder.putComponent(this.modelComponentType, new ModelComponent(persistentModel.getModelReference().toModel())); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + } + + public static class UpdateBoundingBox extends RefChangeSystem { + private final ComponentType modelComponentType = ModelComponent.getComponentType(); + private final ComponentType boundingBoxComponentType = BoundingBox.getComponentType(); + private final ComponentType movementStatesComponentType = MovementStatesComponent.getComponentType(); + + public UpdateBoundingBox() { + } + + @Override + public Query getQuery() { + return this.boundingBoxComponentType; + } + + @Nonnull + @Override + public ComponentType componentType() { + return this.modelComponentType; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull ModelComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + BoundingBox boundingBox = commandBuffer.getComponent(ref, this.boundingBoxComponentType); + MovementStatesComponent movementStates = commandBuffer.getComponent(ref, this.movementStatesComponentType); + updateBoundingBox(component.getModel(), boundingBox, movementStates); + } + + public void onComponentSet( + @Nonnull Ref ref, + ModelComponent oldComponent, + @Nonnull ModelComponent newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + BoundingBox boundingBox = commandBuffer.getComponent(ref, this.boundingBoxComponentType); + MovementStatesComponent movementStates = commandBuffer.getComponent(ref, this.movementStatesComponentType); + updateBoundingBox(newComponent.getModel(), boundingBox, movementStates); + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull ModelComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + commandBuffer.getComponent(ref, this.boundingBoxComponentType).setBoundingBox(new Box()); + } + + protected static void updateBoundingBox(@Nonnull Model model, @Nonnull BoundingBox boundingBox, @Nullable MovementStatesComponent movementStatesComponent) { + updateBoundingBox(model, boundingBox, movementStatesComponent != null ? movementStatesComponent.getMovementStates() : null); + } + + protected static void updateBoundingBox(@Nonnull Model model, @Nonnull BoundingBox boundingBox, @Nullable MovementStates movementStates) { + Box modelBoundingBox = model.getBoundingBox(movementStates); + if (modelBoundingBox == null) { + modelBoundingBox = new Box(); + } + + boundingBox.setBoundingBox(modelBoundingBox); + } + } + + public static class UpdateCrouchingBoundingBox extends EntityTickingSystem { + public static final Set> DEPENDENCIES = Collections.singleton( + new SystemDependency<>(Order.BEFORE, MovementStatesSystems.TickingSystem.class) + ); + private final ComponentType movementStatesComponentType = MovementStatesComponent.getComponentType(); + private final ComponentType boundingBoxComponentType = BoundingBox.getComponentType(); + private final ComponentType modelComponentType = ModelComponent.getComponentType(); + private final Query query = Query.and(this.movementStatesComponentType, this.boundingBoxComponentType, this.modelComponentType); + + public UpdateCrouchingBoundingBox() { + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + MovementStatesComponent movementStates = archetypeChunk.getComponent(index, this.movementStatesComponentType); + MovementStates newMovementStates = movementStates.getMovementStates(); + MovementStates sentMovementStates = movementStates.getSentMovementStates(); + if (newMovementStates.crouching != sentMovementStates.crouching || newMovementStates.forcedCrouching != sentMovementStates.forcedCrouching) { + Model model = archetypeChunk.getComponent(index, this.modelComponentType).getModel(); + BoundingBox boundingBox = archetypeChunk.getComponent(index, this.boundingBoxComponentType); + ModelSystems.UpdateBoundingBox.updateBoundingBox(model, boundingBox, newMovementStates); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/NetworkSendableSpatialSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/system/NetworkSendableSpatialSystem.java new file mode 100644 index 0000000..7fcbce4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/NetworkSendableSpatialSystem.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.spatial.SpatialSystem; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class NetworkSendableSpatialSystem extends SpatialSystem { + private static final Query QUERY = Archetype.of(TransformComponent.getComponentType(), NetworkId.getComponentType()); + + public NetworkSendableSpatialSystem(ResourceType, EntityStore>> spatialResource) { + super(spatialResource); + } + + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + super.tick(dt, systemIndex, store); + } + + @Nonnull + @Override + public Vector3d getPosition(@Nonnull ArchetypeChunk archetypeChunk, int index) { + return archetypeChunk.getComponent(index, TransformComponent.getComponentType()).getPosition(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/PlayerCollisionResultAddSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/system/PlayerCollisionResultAddSystem.java new file mode 100644 index 0000000..453cc52 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/PlayerCollisionResultAddSystem.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.collision.CollisionResult; +import com.hypixel.hytale.server.core.modules.entity.component.CollisionResultComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PlayerCollisionResultAddSystem extends HolderSystem { + @Nonnull + private final Query query; + @Nonnull + private final ComponentType collisionResultComponentType; + @Nonnull + private final ComponentType playerComponentType; + + public PlayerCollisionResultAddSystem( + @Nonnull ComponentType playerComponentType, + @Nonnull ComponentType collisionResultComponentType + ) { + this.collisionResultComponentType = collisionResultComponentType; + this.playerComponentType = playerComponentType; + this.query = Query.and(playerComponentType, Query.not(collisionResultComponentType)); + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + Player playerComponent = holder.getComponent(this.playerComponentType); + + assert playerComponent != null; + + CollisionResultComponent collisionResultComponent = new CollisionResultComponent(); + CollisionResult collisionResult = collisionResultComponent.getCollisionResult(); + collisionResult.setDefaultPlayerSettings(); + collisionResultComponent.resetLocationChange(); + playerComponent.configTriggerBlockProcessing(true, true, collisionResultComponent); + holder.addComponent(this.collisionResultComponentType, collisionResultComponent); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/PlayerSpatialSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/system/PlayerSpatialSystem.java new file mode 100644 index 0000000..431be59 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/PlayerSpatialSystem.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.spatial.SpatialSystem; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PlayerSpatialSystem extends SpatialSystem { + @Nonnull + public static final Query QUERY = Archetype.of(Player.getComponentType(), TransformComponent.getComponentType()); + + public PlayerSpatialSystem(@Nonnull ResourceType, EntityStore>> spatialResource) { + super(spatialResource); + } + + @Override + public Query getQuery() { + return QUERY; + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + super.tick(dt, systemIndex, store); + } + + @Nonnull + @Override + public Vector3d getPosition(@Nonnull ArchetypeChunk archetypeChunk, int index) { + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + return transformComponent.getPosition(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/RespondToHitSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/system/RespondToHitSystems.java new file mode 100644 index 0000000..44fdaa2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/RespondToHitSystems.java @@ -0,0 +1,216 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.RespondToHit; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class RespondToHitSystems { + public RespondToHitSystems() { + } + + public static class EntityTrackerAddAndRemove extends RefChangeSystem { + private final ComponentType respondToHitComponentType = RespondToHit.getComponentType(); + private final ComponentType visibleComponentType; + @Nonnull + private final Query query; + + public EntityTrackerAddAndRemove(ComponentType visibleComponentType) { + this.visibleComponentType = visibleComponentType; + this.query = Query.and(visibleComponentType, this.respondToHitComponentType); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public ComponentType componentType() { + return this.respondToHitComponentType; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull RespondToHit component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + commandBuffer.getResource(RespondToHitSystems.QueueResource.getResourceType()).queue.add(ref); + } + + public void onComponentSet( + @Nonnull Ref ref, + RespondToHit oldComponent, + @Nonnull RespondToHit newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull RespondToHit component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + for (EntityTrackerSystems.EntityViewer viewer : store.getComponent(ref, this.visibleComponentType).visibleTo.values()) { + viewer.queueRemove(ref, ComponentUpdateType.RespondToHit); + } + } + } + + public static class EntityTrackerUpdate extends EntityTickingSystem { + private final ComponentType componentType; + @Nonnull + private final Query query; + + public EntityTrackerUpdate(ComponentType componentType) { + this.componentType = componentType; + this.query = Query.and(componentType, RespondToHit.getComponentType()); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + super.tick(dt, systemIndex, store); + store.getResource(RespondToHitSystems.QueueResource.getResourceType()).queue.clear(); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visible = archetypeChunk.getComponent(index, this.componentType); + Ref ref = archetypeChunk.getReferenceTo(index); + if (commandBuffer.getResource(RespondToHitSystems.QueueResource.getResourceType()).queue.remove(ref)) { + queueUpdatesFor(ref, visible.visibleTo); + } else if (!visible.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(ref, visible.newlyVisibleTo); + } + } + + private static void queueUpdatesFor(Ref ref, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.RespondToHit; + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, update); + } + } + } + + public static class OnPlayerSettingsChange extends RefChangeSystem { + public OnPlayerSettingsChange() { + } + + @NonNullDecl + @Override + public ComponentType componentType() { + return PlayerSettings.getComponentType(); + } + + public void onComponentAdded( + @NonNullDecl Ref ref, + @NonNullDecl PlayerSettings component, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + Player player = commandBuffer.getComponent(ref, Player.getComponentType()); + if (player.getGameMode() == GameMode.Creative) { + if (component.creativeSettings().respondToHit()) { + commandBuffer.ensureComponent(ref, RespondToHit.getComponentType()); + } else { + commandBuffer.tryRemoveComponent(ref, RespondToHit.getComponentType()); + } + } + } + + public void onComponentSet( + @NonNullDecl Ref ref, + @NullableDecl PlayerSettings oldComponent, + @NonNullDecl PlayerSettings newComponent, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + Player player = commandBuffer.getComponent(ref, Player.getComponentType()); + if (player.getGameMode() == GameMode.Creative) { + if (newComponent.creativeSettings().respondToHit()) { + commandBuffer.ensureComponent(ref, RespondToHit.getComponentType()); + } else { + commandBuffer.tryRemoveComponent(ref, RespondToHit.getComponentType()); + } + } + } + + public void onComponentRemoved( + @NonNullDecl Ref ref, + @NonNullDecl PlayerSettings component, + @NonNullDecl Store store, + @NonNullDecl CommandBuffer commandBuffer + ) { + } + + @NullableDecl + @Override + public Query getQuery() { + return Player.getComponentType(); + } + } + + public static class QueueResource implements Resource { + private final Set> queue = ConcurrentHashMap.newKeySet(); + + public QueueResource() { + } + + public static ResourceType getResourceType() { + return EntityModule.get().getRespondToHitQueueResourceType(); + } + + @Nonnull + @Override + public Resource clone() { + return new RespondToHitSystems.QueueResource(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/RotateObjectSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/system/RotateObjectSystem.java new file mode 100644 index 0000000..6d1f2d2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/RotateObjectSystem.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.modules.entity.component.RotateObjectComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class RotateObjectSystem extends EntityTickingSystem { + @Nonnull + private final ComponentType transformComponentType; + @Nonnull + private final ComponentType rotateObjectComponentType; + + public RotateObjectSystem( + @Nonnull ComponentType transformComponentType, + @Nonnull ComponentType rotateObjectComponentType + ) { + this.transformComponentType = transformComponentType; + this.rotateObjectComponentType = rotateObjectComponentType; + } + + @Override + public Query getQuery() { + return this.rotateObjectComponentType; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + TransformComponent transformComponent = archetypeChunk.getComponent(index, this.transformComponentType); + + assert transformComponent != null; + + RotateObjectComponent rotateObjectComponent = archetypeChunk.getComponent(index, this.rotateObjectComponentType); + + assert rotateObjectComponent != null; + + Vector3f rotation = transformComponent.getRotation(); + rotation.y = rotation.y + rotateObjectComponent.getRotationSpeed() * dt; + if (rotation.y >= 360.0F) { + rotation.y %= 360.0F; + } + + transformComponent.setRotation(rotation); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/SnapshotSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/system/SnapshotSystems.java new file mode 100644 index 0000000..51cec60 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/SnapshotSystems.java @@ -0,0 +1,178 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.OrderPriority; +import com.hypixel.hytale.component.dependency.RootDependency; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.SnapshotBuffer; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + +public class SnapshotSystems { + public static long HISTORY_LENGTH_NS = TimeUnit.MILLISECONDS.toNanos(500L); + private static final HytaleLogger LOGGER = HytaleLogger.getLogger(); + + public SnapshotSystems() { + } + + public static class Add extends HolderSystem { + public Add() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + SnapshotBuffer buffer = holder.ensureAndGetComponent(SnapshotBuffer.getComponentType()); + buffer.resize(store.getResource(SnapshotSystems.SnapshotWorldInfo.getResourceType()).historySize); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Override + public Query getQuery() { + return TransformComponent.getComponentType(); + } + } + + public static class Capture extends EntityTickingSystem { + private static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, SnapshotSystems.Resize.class), new RootDependency(OrderPriority.CLOSEST) + ); + @Nonnull + private final Query query = Query.and(TransformComponent.getComponentType(), SnapshotBuffer.getComponentType()); + + public Capture() { + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + SnapshotSystems.SnapshotWorldInfo info = store.getResource(SnapshotSystems.SnapshotWorldInfo.getResourceType()); + info.currentTick++; + super.tick(dt, systemIndex, store); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + SnapshotBuffer buffer = archetypeChunk.getComponent(index, SnapshotBuffer.getComponentType()); + TransformComponent transform = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + SnapshotSystems.SnapshotWorldInfo info = store.getResource(SnapshotSystems.SnapshotWorldInfo.getResourceType()); + buffer.storeSnapshot(info.currentTick, transform.getPosition(), transform.getRotation()); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + } + + public static class Resize extends EntityTickingSystem { + public Resize() { + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.firstSet(); + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + World world = store.getExternalData().getWorld(); + int tickLength = world.getTickStepNanos(); + SnapshotSystems.SnapshotWorldInfo info = store.getResource(SnapshotSystems.SnapshotWorldInfo.getResourceType()); + if (tickLength != info.tickLengthNanos || SnapshotSystems.HISTORY_LENGTH_NS != info.historyLength) { + info.historyLength = SnapshotSystems.HISTORY_LENGTH_NS; + info.tickLengthNanos = tickLength; + int previousHistorySize = info.historySize; + info.historySize = Math.max(1, (int)((info.historyLength + tickLength - 1L) / tickLength)); + super.tick(dt, systemIndex, store); + } + } + + @Override + public Query getQuery() { + return SnapshotBuffer.getComponentType(); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + SnapshotSystems.SnapshotWorldInfo info = store.getResource(SnapshotSystems.SnapshotWorldInfo.getResourceType()); + archetypeChunk.getComponent(index, SnapshotBuffer.getComponentType()).resize(info.historySize); + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + } + + public static class SnapshotWorldInfo implements Resource { + private int tickLengthNanos = -1; + private long historyLength = -1L; + private int historySize = 1; + private int currentTick = -1; + + public static ResourceType getResourceType() { + return EntityModule.get().getSnapshotWorldInfoResourceType(); + } + + public SnapshotWorldInfo() { + } + + public SnapshotWorldInfo(int tickLengthNanos, long historyLength, int historySize, int currentTick) { + this.tickLengthNanos = tickLengthNanos; + this.historyLength = historyLength; + this.historySize = historySize; + this.currentTick = currentTick; + } + + @Nonnull + @Override + public Resource clone() { + return new SnapshotSystems.SnapshotWorldInfo(this.tickLengthNanos, this.historyLength, this.historySize, this.currentTick); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/TransformSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/system/TransformSystems.java new file mode 100644 index 0000000..8b7cb6f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/TransformSystems.java @@ -0,0 +1,141 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.ModelTransform; +import com.hypixel.hytale.protocol.Position; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.PositionUtil; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TransformSystems { + public TransformSystems() { + } + + public static class EntityTrackerUpdate extends EntityTickingSystem { + @Nonnull + private final ComponentType visibleComponentType = EntityTrackerSystems.Visible.getComponentType(); + @Nonnull + private final ComponentType transformComponentType = TransformComponent.getComponentType(); + @Nonnull + private final ComponentType headRotationComponentType = HeadRotation.getComponentType(); + @Nonnull + private final Query query = Query.and(this.visibleComponentType, this.transformComponentType); + + public EntityTrackerUpdate() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.visibleComponentType); + + assert visibleComponent != null; + + TransformComponent transformComponent = archetypeChunk.getComponent(index, this.transformComponentType); + + assert transformComponent != null; + + HeadRotation headRotationComponent = archetypeChunk.getComponent(index, this.headRotationComponentType); + ModelTransform sentTransform = transformComponent.getSentTransform(); + Vector3d position = transformComponent.getPosition(); + Vector3f headRotation = headRotationComponent != null ? headRotationComponent.getRotation() : Vector3f.ZERO; + Vector3f bodyRotation = transformComponent.getRotation(); + Position sentPosition = sentTransform.position; + Direction sentLookOrientation = sentTransform.lookOrientation; + Direction sentBodyOrientation = sentTransform.bodyOrientation; + if (!PositionUtil.equals(position, sentPosition) + || !PositionUtil.equals(headRotation, sentLookOrientation) + || !PositionUtil.equals(bodyRotation, sentBodyOrientation)) { + PositionUtil.assign(sentPosition, position); + PositionUtil.assign(sentLookOrientation, headRotation); + PositionUtil.assign(sentBodyOrientation, bodyRotation); + queueUpdatesFor(archetypeChunk.getReferenceTo(index), sentTransform, visibleComponent.visibleTo, false); + } else if (!visibleComponent.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), sentTransform, visibleComponent.newlyVisibleTo, true); + } + } + + private static void queueUpdatesFor( + @Nonnull Ref ref, + @Nonnull ModelTransform sentTransform, + @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo, + boolean newlyVisible + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Transform; + update.transform = sentTransform; + + for (Entry, EntityTrackerSystems.EntityViewer> entry : visibleTo.entrySet()) { + if (newlyVisible || !ref.equals(entry.getKey())) { + entry.getValue().queueUpdate(ref, update); + } + } + } + } + + public static class OnRemove extends HolderSystem { + @Nonnull + private final ComponentType transformComponentType = TransformComponent.getComponentType(); + + public OnRemove() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + holder.getComponent(this.transformComponentType).setChunkLocation(null, null); + } + + @Override + public Query getQuery() { + return this.transformComponentType; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/UpdateEntitySeedSystem.java b/src/com/hypixel/hytale/server/core/modules/entity/system/UpdateEntitySeedSystem.java new file mode 100644 index 0000000..5b5fe9e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/UpdateEntitySeedSystem.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.DelayedSystem; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class UpdateEntitySeedSystem extends DelayedSystem { + public UpdateEntitySeedSystem() { + super(1.0F); + } + + @Override + public void delayedTick(float dt, int systemIndex, @Nonnull Store store) { + store.getExternalData().getWorld().updateEntitySeed(store); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/system/UpdateLocationSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/system/UpdateLocationSystems.java new file mode 100644 index 0000000..1bc70d1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/system/UpdateLocationSystems.java @@ -0,0 +1,238 @@ +package com.hypixel.hytale.server.core.modules.entity.system; + +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkFlag; +import com.hypixel.hytale.server.core.universe.world.chunk.EntityChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UpdateLocationSystems { + @Nonnull + private static final Message MESSAGE_GENERAL_PLAYER_IN_INVALID_CHUNK = Message.translation("server.general.playerInInvalidChunk"); + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + + public UpdateLocationSystems() { + } + + private static void updateLocation( + @Nonnull Ref ref, @Nonnull TransformComponent transformComponent, @Nullable World world, @Nonnull CommandBuffer commandBuffer + ) { + if (world != null) { + Vector3d position = transformComponent.getPosition(); + if (position.getY() < -32.0 && !commandBuffer.getArchetype(ref).contains(Player.getComponentType())) { + LOGGER.at(Level.WARNING).log("Unable to move entity below the world! -32 < " + position); + commandBuffer.removeEntity(ref, RemoveReason.REMOVE); + } else { + ChunkStore chunkStore = world.getChunkStore(); + Store chunkComponentStore = chunkStore.getStore(); + int chunkX = MathUtil.floor(position.getX()) >> 5; + int chunkZ = MathUtil.floor(position.getZ()) >> 5; + Ref oldChunkRef = transformComponent.getChunkRef(); + boolean hasOldChunk = false; + int oldChunkX = 0; + int oldChunkZ = 0; + if (oldChunkRef != null && oldChunkRef.isValid()) { + WorldChunk oldWorldChunkComponent = chunkComponentStore.getComponent(oldChunkRef, WorldChunk.getComponentType()); + if (oldWorldChunkComponent != null) { + hasOldChunk = true; + oldChunkX = oldWorldChunkComponent.getX(); + oldChunkZ = oldWorldChunkComponent.getZ(); + } + } + + if (!hasOldChunk || oldChunkX != chunkX || oldChunkZ != chunkZ) { + long newChunkIndex = ChunkUtil.indexChunk(chunkX, chunkZ); + Ref newChunkRef = chunkStore.getChunkReference(newChunkIndex); + if (newChunkRef != null && newChunkRef.isValid()) { + WorldChunk newWorldChunkComponent = chunkComponentStore.getComponent(newChunkRef, WorldChunk.getComponentType()); + updateChunk(ref, transformComponent, oldChunkRef, newChunkRef, newWorldChunkComponent, chunkComponentStore, commandBuffer); + } else { + LOGGER.at(Level.WARNING) + .log("Entity has moved into a chunk that isn't currently loaded! " + chunkX + ", " + chunkZ + ", " + transformComponent); + CompletableFutureUtil._catch(chunkStore.getChunkReferenceAsync(newChunkIndex).thenAcceptAsync(asyncChunkRef -> { + if (asyncChunkRef != null && asyncChunkRef.isValid()) { + WorldChunk asyncWorldChunk = chunkComponentStore.getComponent((Ref)asyncChunkRef, WorldChunk.getComponentType()); + updateChunkAsync(ref, (Ref)asyncChunkRef, asyncWorldChunk, chunkComponentStore); + } else { + updateChunkAsync(ref, null, null, chunkComponentStore); + } + }, world)); + } + } + } + } + } + + private static void updateChunkAsync( + @Nonnull Ref ref, @Nullable Ref newChunkRef, @Nullable WorldChunk newWorldChunk, @Nonnull Store chunkComponentStore + ) { + if (ref.isValid()) { + Store entityStore = ref.getStore(); + TransformComponent transformComponent = entityStore.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Ref oldChunkRef = transformComponent.getChunkRef(); + updateChunk(ref, transformComponent, oldChunkRef, newChunkRef, newWorldChunk, chunkComponentStore, entityStore); + } + } + + private static void updateChunk( + @Nonnull Ref ref, + @Nonnull TransformComponent transformComponent, + @Nullable Ref oldChunkRef, + @Nullable Ref newChunkRef, + @Nullable WorldChunk newWorldChunkComponent, + @Nonnull ComponentAccessor chunkComponentStore, + @Nonnull ComponentAccessor entityComponentAccessor + ) { + boolean isPlayer = entityComponentAccessor.getArchetype(ref).contains(Player.getComponentType()); + if (newWorldChunkComponent == null) { + handleInvalidChunk(ref, transformComponent, isPlayer, entityComponentAccessor); + } else if (!newWorldChunkComponent.not(ChunkFlag.INIT)) { + assert newChunkRef != null; + + if (!isPlayer) { + updateEntityInChunk(ref, oldChunkRef, newChunkRef, newWorldChunkComponent, chunkComponentStore, entityComponentAccessor); + } + + transformComponent.setChunkLocation(newChunkRef, newWorldChunkComponent); + } + } + + private static void handleInvalidChunk( + @Nonnull Ref ref, + @Nonnull TransformComponent transformComponent, + boolean isPlayer, + @Nonnull ComponentAccessor entityComponentAccessor + ) { + if (!isPlayer) { + LOGGER.at(Level.SEVERE).log("Entity is in a chunk that can't be loaded! Removing! %s", transformComponent); + entityComponentAccessor.removeEntity(ref, EntityStore.REGISTRY.newHolder(), RemoveReason.REMOVE); + } else { + LOGGER.at(Level.SEVERE).log("Player is in a chunk that can't be loaded! Moving (-%d,0,0)! %s", 32, transformComponent); + Vector3d position = transformComponent.getPosition(); + Vector3f bodyRotation = transformComponent.getRotation(); + Vector3d targetPosition = position.clone().subtract(32.0, 0.0, 0.0); + PlayerRef playerRefComponent = entityComponentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + entityComponentAccessor.addComponent(ref, Teleport.getComponentType(), new Teleport(targetPosition, bodyRotation)); + playerRefComponent.sendMessage(MESSAGE_GENERAL_PLAYER_IN_INVALID_CHUNK); + } + } + + private static void updateEntityInChunk( + @Nonnull Ref ref, + @Nullable Ref oldChunkRef, + @Nonnull Ref newChunkRef, + @Nonnull WorldChunk newWorldChunk, + @Nonnull ComponentAccessor chunkComponentStore, + @Nonnull ComponentAccessor entityComponentAccessor + ) { + if (oldChunkRef != null && oldChunkRef.isValid()) { + EntityChunk oldEntityChunkComponent = chunkComponentStore.getComponent(oldChunkRef, EntityChunk.getComponentType()); + + assert oldEntityChunkComponent != null; + + oldEntityChunkComponent.removeEntityReference(ref); + } + + EntityChunk newEntityChunkComponent = chunkComponentStore.getComponent(newChunkRef, EntityChunk.getComponentType()); + + assert newEntityChunkComponent != null; + + if (newWorldChunk.not(ChunkFlag.TICKING)) { + Holder holder = EntityStore.REGISTRY.newHolder(); + entityComponentAccessor.removeEntity(ref, holder, RemoveReason.UNLOAD); + newEntityChunkComponent.addEntityHolder(holder); + } else { + newEntityChunkComponent.addEntityReference(ref); + } + } + + public static class SpawnSystem extends RefSystem { + public SpawnSystem() { + } + + @Override + public Query getQuery() { + return TransformComponent.getComponentType(); + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Ref chunkRef = transformComponent.getChunkRef(); + if (chunkRef == null || !chunkRef.isValid()) { + UpdateLocationSystems.updateLocation(ref, transformComponent, store.getExternalData().getWorld(), commandBuffer); + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + } + + public static class TickingSystem extends EntityTickingSystem { + public TickingSystem() { + } + + @Override + public Query getQuery() { + return TransformComponent.getComponentType(); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + World world = commandBuffer.getExternalData().getWorld(); + Ref ref = archetypeChunk.getReferenceTo(index); + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + UpdateLocationSystems.updateLocation(ref, transformComponent, world, commandBuffer); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/teleport/PendingTeleport.java b/src/com/hypixel/hytale/server/core/modules/entity/teleport/PendingTeleport.java new file mode 100644 index 0000000..e1ec871 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/teleport/PendingTeleport.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.server.core.modules.entity.teleport; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.Position; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class PendingTeleport implements Component { + public static final double MAX_OFFSET = 0.001; + @Nonnull + private final Vector3d position = new Vector3d(); + @Nonnull + private final List pendingTeleports = new ObjectArrayList<>(); + private int nextTeleportId = 0; + private int lastTeleportId = 0; + + public PendingTeleport() { + } + + public static ComponentType getComponentType() { + return EntityModule.get().getPendingTeleportComponentType(); + } + + @Nonnull + public PendingTeleport.Result validate(int teleportId, @Nonnull Position teleportPosition) { + if (teleportId != this.lastTeleportId) { + return PendingTeleport.Result.INVALID_ID; + } else { + this.position.assign(teleportPosition.x, teleportPosition.y, teleportPosition.z); + Teleport teleport = this.pendingTeleports.removeFirst(); + this.lastTeleportId++; + return teleport.getPosition().distanceSquaredTo(this.position) <= 0.001 ? PendingTeleport.Result.OK : PendingTeleport.Result.INVALID_POSITION; + } + } + + public boolean isEmpty() { + return this.pendingTeleports.isEmpty(); + } + + public int queueTeleport(Teleport teleport) { + this.pendingTeleports.add(teleport); + return this.nextTeleportId++; + } + + @Nonnull + public Vector3d getPosition() { + return this.position; + } + + @Nonnull + @Override + public Component clone() { + return new PendingTeleport(); + } + + public static enum Result { + OK, + INVALID_ID, + INVALID_POSITION; + + private Result() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/teleport/Teleport.java b/src/com/hypixel/hytale/server/core/modules/entity/teleport/Teleport.java new file mode 100644 index 0000000..bf9e5df --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/teleport/Teleport.java @@ -0,0 +1,94 @@ +package com.hypixel.hytale.server.core.modules.entity.teleport; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Teleport implements Component { + @Nullable + private final World world; + @Nonnull + private final Vector3d position = new Vector3d(); + @Nonnull + private final Vector3f rotation = new Vector3f(); + @Nullable + private Vector3f headRotation; + private boolean resetVelocity = true; + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getTeleportComponentType(); + } + + public Teleport(@Nullable World world, @Nonnull Transform transform) { + this(world, transform.getPosition(), transform.getRotation()); + } + + public Teleport(@Nullable World world, @Nonnull Vector3d position, @Nonnull Vector3f rotation) { + this.world = world; + this.position.assign(position); + this.rotation.assign(rotation); + } + + public Teleport(@Nonnull Transform transform) { + this(null, transform.getPosition(), transform.getRotation()); + } + + public Teleport(@Nonnull Vector3d position, @Nonnull Vector3f rotation) { + this.world = null; + this.position.assign(position); + this.rotation.assign(rotation); + } + + @Nonnull + public Teleport withHeadRotation(@Nonnull Vector3f headRotation) { + this.headRotation = headRotation; + return this; + } + + public Teleport withResetRoll() { + this.rotation.setRoll(0.0F); + return this; + } + + public Teleport withoutVelocityReset() { + this.resetVelocity = false; + return this; + } + + @Nullable + public World getWorld() { + return this.world; + } + + @Nonnull + public Vector3d getPosition() { + return this.position; + } + + @Nonnull + public Vector3f getRotation() { + return this.rotation; + } + + @Nullable + public Vector3f getHeadRotation() { + return this.headRotation; + } + + public boolean isResetVelocity() { + return this.resetVelocity; + } + + @Nonnull + public Teleport clone() { + return new Teleport(this.world, this.position, this.rotation); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/teleport/TeleportSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/teleport/TeleportSystems.java new file mode 100644 index 0000000..30332ec --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/teleport/TeleportSystems.java @@ -0,0 +1,276 @@ +package com.hypixel.hytale.server.core.modules.entity.teleport; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.ModelTransform; +import com.hypixel.hytale.protocol.packets.player.ClientTeleport; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.CollisionResultComponent; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.PositionUtil; +import javax.annotation.Nonnull; + +public class TeleportSystems { + public TeleportSystems() { + } + + public static class MoveSystem extends RefChangeSystem { + @Nonnull + private final ComponentType teleportComponentType = Teleport.getComponentType(); + @Nonnull + private final ComponentType transformComponentType = TransformComponent.getComponentType(); + @Nonnull + private final ComponentType headRotationComponentType = HeadRotation.getComponentType(); + @Nonnull + private final Query query = Query.and(this.teleportComponentType, this.transformComponentType, Query.not(PlayerRef.getComponentType())); + + public MoveSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public ComponentType componentType() { + return this.teleportComponentType; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull Teleport teleport, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + TransformComponent transformComponent = commandBuffer.getComponent(ref, this.transformComponentType); + + assert transformComponent != null; + + transformComponent.teleportPosition(teleport.getPosition()); + transformComponent.teleportRotation(teleport.getRotation()); + HeadRotation headRotationComponent = commandBuffer.getComponent(ref, this.headRotationComponentType); + if (headRotationComponent != null) { + headRotationComponent.teleportRotation(teleport.getRotation()); + } + + World targetWorld = teleport.getWorld(); + if (targetWorld != null && !targetWorld.equals(store.getExternalData().getWorld())) { + commandBuffer.run(s -> { + Holder holder = s.removeEntity(ref, RemoveReason.UNLOAD); + targetWorld.execute(() -> targetWorld.getEntityStore().getStore().addEntity(holder, AddReason.LOAD)); + }); + } + + commandBuffer.removeComponent(ref, this.teleportComponentType); + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull Teleport component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentSet( + @Nonnull Ref ref, + Teleport oldComponent, + @Nonnull Teleport newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + } + + public static class PlayerMoveCompleteSystem extends RefChangeSystem { + @Nonnull + private final ComponentType pendingComponentType = PendingTeleport.getComponentType(); + @Nonnull + private final ComponentType playerComponentType = Player.getComponentType(); + @Nonnull + private final ComponentType transformComponentType = TransformComponent.getComponentType(); + @Nonnull + private final ComponentType collisionResultComponentType = CollisionResultComponent.getComponentType(); + @Nonnull + private final Query query = Query.and(this.playerComponentType, this.transformComponentType); + + public PlayerMoveCompleteSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public ComponentType componentType() { + return this.pendingComponentType; + } + + public void onComponentAdded( + @Nonnull Ref ref, + @Nonnull PendingTeleport component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentSet( + @Nonnull Ref ref, + PendingTeleport oldComponent, + @Nonnull PendingTeleport newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, + @Nonnull PendingTeleport component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Player playerComponent = commandBuffer.getComponent(ref, this.playerComponentType); + + assert playerComponent != null; + + TransformComponent transformComponent = commandBuffer.getComponent(ref, this.transformComponentType); + + assert transformComponent != null; + + CollisionResultComponent collisionResultComponent = commandBuffer.getComponent(ref, this.collisionResultComponentType); + if (collisionResultComponent != null) { + collisionResultComponent.getCollisionStartPosition().assign(transformComponent.getPosition()); + } + + playerComponent.moveTo(ref, component.getPosition().x, component.getPosition().y, component.getPosition().z, commandBuffer); + } + } + + public static class PlayerMoveSystem extends RefChangeSystem { + @Nonnull + private final ComponentType teleportComponentType = Teleport.getComponentType(); + @Nonnull + private final ComponentType transformComponentType = TransformComponent.getComponentType(); + @Nonnull + private final ComponentType headRotationComponentType = HeadRotation.getComponentType(); + @Nonnull + private final ComponentType playerRefComponentType = PlayerRef.getComponentType(); + @Nonnull + private final ComponentType playerComponentType = Player.getComponentType(); + @Nonnull + private final ComponentType pendingTeleportComponentType = PendingTeleport.getComponentType(); + @Nonnull + private final Query query = Query.and( + this.teleportComponentType, this.playerRefComponentType, this.transformComponentType, this.playerComponentType + ); + + public PlayerMoveSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public ComponentType componentType() { + return this.teleportComponentType; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull Teleport teleport, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + World targetWorld = teleport.getWorld(); + if (targetWorld != null && !targetWorld.equals(store.getExternalData().getWorld())) { + this.teleportToWorld(ref, teleport, commandBuffer, targetWorld); + } else { + this.teleportToPosition(ref, teleport, commandBuffer); + } + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull Teleport component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentSet( + @Nonnull Ref ref, + Teleport oldComponent, + @Nonnull Teleport newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + private void teleportToWorld( + @Nonnull Ref ref, @Nonnull Teleport teleport, @Nonnull CommandBuffer commandBuffer, @Nonnull World targetWorld + ) { + PlayerRef playerRefComponent = commandBuffer.getComponent(ref, this.playerRefComponentType); + + assert playerRefComponent != null; + + commandBuffer.removeComponent(ref, this.teleportComponentType); + commandBuffer.run(s -> { + playerRefComponent.removeFromStore(); + targetWorld.addPlayer(playerRefComponent, new Transform(teleport.getPosition(), teleport.getRotation())); + }); + } + + private void teleportToPosition(@Nonnull Ref ref, @Nonnull Teleport teleport, @Nonnull CommandBuffer commandBuffer) { + TransformComponent transformComponent = commandBuffer.getComponent(ref, this.transformComponentType); + + assert transformComponent != null; + + PlayerRef playerRefComponent = commandBuffer.getComponent(ref, this.playerRefComponentType); + + assert playerRefComponent != null; + + Player playerComponent = commandBuffer.getComponent(ref, this.playerComponentType); + + assert playerComponent != null; + + PendingTeleport pendingTeleportComponent = commandBuffer.ensureAndGetComponent(ref, this.pendingTeleportComponentType); + Vector3d teleportPosition = teleport.getPosition(); + Vector3f teleportRotation = teleport.getRotation(); + transformComponent.teleportPosition(teleportPosition); + transformComponent.teleportRotation(teleportRotation); + HeadRotation headRotationComponent = commandBuffer.getComponent(ref, this.headRotationComponentType); + if (headRotationComponent != null) { + Vector3f teleportHeadRotation = teleport.getHeadRotation(); + headRotationComponent.teleportRotation(teleportHeadRotation != null ? teleportHeadRotation : teleportRotation); + } + + playerComponent.getWindowManager().validateWindows(); + int id = pendingTeleportComponent.queueTeleport(teleport); + ClientTeleport teleportPacket = new ClientTeleport( + (byte)id, + new ModelTransform( + PositionUtil.toPositionPacket(transformComponent.getPosition()), + PositionUtil.toDirectionPacket(transformComponent.getRotation()), + headRotationComponent != null + ? PositionUtil.toDirectionPacket(headRotationComponent.getRotation()) + : PositionUtil.toDirectionPacket(transformComponent.getRotation()) + ), + teleport.isResetVelocity() + ); + playerRefComponent.getPacketHandler().write(teleportPacket); + commandBuffer.removeComponent(ref, this.teleportComponentType); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/tracker/EntityTrackerSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/tracker/EntityTrackerSystems.java new file mode 100644 index 0000000..a94f73a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/tracker/EntityTrackerSystems.java @@ -0,0 +1,739 @@ +package com.hypixel.hytale.server.core.modules.entity.tracker; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.dependency.SystemGroupDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.spatial.SpatialStructure; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.protocol.packets.entities.EntityUpdates; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.system.NetworkSendableSpatialSystem; +import com.hypixel.hytale.server.core.receiver.IPacketReceiver; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ObjectList; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.StampedLock; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityTrackerSystems { + public static final SystemGroup FIND_VISIBLE_ENTITIES_GROUP = EntityStore.REGISTRY.registerSystemGroup(); + public static final SystemGroup QUEUE_UPDATE_GROUP = EntityStore.REGISTRY.registerSystemGroup(); + + public EntityTrackerSystems() { + } + + public static boolean despawnAll(@Nonnull Ref viewerRef, @Nonnull Store store) { + EntityTrackerSystems.EntityViewer viewer = store.getComponent(viewerRef, EntityTrackerSystems.EntityViewer.getComponentType()); + if (viewer == null) { + return false; + } else { + int networkId = viewer.sent.removeInt(viewerRef); + EntityUpdates packet = new EntityUpdates(); + packet.removed = viewer.sent.values().toIntArray(); + viewer.packetReceiver.writeNoCache(packet); + clear(viewerRef, store); + viewer.sent.put(viewerRef, networkId); + return true; + } + } + + public static boolean clear(@Nonnull Ref viewerRef, @Nonnull Store store) { + EntityTrackerSystems.EntityViewer viewer = store.getComponent(viewerRef, EntityTrackerSystems.EntityViewer.getComponentType()); + if (viewer == null) { + return false; + } else { + for (Ref ref : viewer.sent.keySet()) { + EntityTrackerSystems.Visible visible = store.getComponent(ref, EntityTrackerSystems.Visible.getComponentType()); + if (visible != null) { + visible.visibleTo.remove(viewerRef); + } + } + + viewer.sent.clear(); + return true; + } + } + + public static class AddToVisible extends EntityTickingSystem { + public static final Set> DEPENDENCIES = Collections.singleton( + new SystemDependency<>(Order.AFTER, EntityTrackerSystems.EnsureVisibleComponent.class) + ); + private final ComponentType entityViewerComponentType; + private final ComponentType visibleComponentType; + + public AddToVisible( + ComponentType entityViewerComponentType, + ComponentType visibleComponentType + ) { + this.entityViewerComponentType = entityViewerComponentType; + this.visibleComponentType = visibleComponentType; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Override + public Query getQuery() { + return this.entityViewerComponentType; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Ref viewerRef = archetypeChunk.getReferenceTo(index); + EntityTrackerSystems.EntityViewer viewer = archetypeChunk.getComponent(index, this.entityViewerComponentType); + + for (Ref ref : viewer.visible) { + commandBuffer.getComponent(ref, this.visibleComponentType).addViewerParallel(viewerRef, viewer); + } + } + } + + public static class ClearEntityViewers extends EntityTickingSystem { + public static final Set> DEPENDENCIES = Collections.singleton( + new SystemGroupDependency<>(Order.BEFORE, EntityTrackerSystems.FIND_VISIBLE_ENTITIES_GROUP) + ); + private final ComponentType componentType; + + public ClearEntityViewers(ComponentType componentType) { + this.componentType = componentType; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Override + public Query getQuery() { + return this.componentType; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.EntityViewer viewer = archetypeChunk.getComponent(index, this.componentType); + viewer.visible.clear(); + viewer.lodExcludedCount = 0; + viewer.hiddenCount = 0; + } + } + + public static class ClearPreviouslyVisible extends EntityTickingSystem { + public static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, EntityTrackerSystems.ClearEntityViewers.class), + new SystemGroupDependency(Order.AFTER, EntityTrackerSystems.FIND_VISIBLE_ENTITIES_GROUP) + ); + private final ComponentType componentType; + + public ClearPreviouslyVisible(ComponentType componentType) { + this.componentType = componentType; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Override + public Query getQuery() { + return this.componentType; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visible = archetypeChunk.getComponent(index, this.componentType); + Map, EntityTrackerSystems.EntityViewer> oldVisibleTo = visible.previousVisibleTo; + visible.previousVisibleTo = visible.visibleTo; + visible.visibleTo = oldVisibleTo; + visible.visibleTo.clear(); + visible.newlyVisibleTo.clear(); + } + } + + public static class CollectVisible extends EntityTickingSystem { + private final ComponentType componentType; + private final Query query; + @Nonnull + private final Set> dependencies; + + public CollectVisible(ComponentType componentType) { + this.componentType = componentType; + this.query = Archetype.of(componentType, TransformComponent.getComponentType()); + this.dependencies = Collections.singleton(new SystemDependency<>(Order.AFTER, NetworkSendableSpatialSystem.class)); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.FIND_VISIBLE_ENTITIES_GROUP; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + TransformComponent transform = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + Vector3d position = transform.getPosition(); + EntityTrackerSystems.EntityViewer entityViewer = archetypeChunk.getComponent(index, this.componentType); + SpatialStructure> spatialStructure = store.getResource(EntityModule.get().getNetworkSendableSpatialResourceType()) + .getSpatialStructure(); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + spatialStructure.collect(position, entityViewer.viewRadiusBlocks, results); + entityViewer.visible.addAll(results); + } + } + + public static class EffectControllerSystem extends EntityTickingSystem { + @Nonnull + private final ComponentType visibleComponentType; + @Nonnull + private final ComponentType effectControllerComponentType; + @Nonnull + private final Query query; + + public EffectControllerSystem( + @Nonnull ComponentType visibleComponentType, + @Nonnull ComponentType effectControllerComponentType + ) { + this.visibleComponentType = visibleComponentType; + this.effectControllerComponentType = effectControllerComponentType; + this.query = Query.and(visibleComponentType, effectControllerComponentType); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.visibleComponentType); + + assert visibleComponent != null; + + Ref entityRef = archetypeChunk.getReferenceTo(index); + EffectControllerComponent effectControllerComponent = archetypeChunk.getComponent(index, this.effectControllerComponentType); + + assert effectControllerComponent != null; + + if (!visibleComponent.newlyVisibleTo.isEmpty()) { + queueFullUpdate(entityRef, effectControllerComponent, visibleComponent.newlyVisibleTo); + } + + if (effectControllerComponent.consumeNetworkOutdated()) { + queueUpdatesFor(entityRef, effectControllerComponent, visibleComponent.visibleTo, visibleComponent.newlyVisibleTo); + } + } + + private static void queueFullUpdate( + @Nonnull Ref ref, + @Nonnull EffectControllerComponent effectControllerComponent, + @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.EntityEffects; + update.entityEffectUpdates = effectControllerComponent.createInitUpdates(); + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, update); + } + } + + private static void queueUpdatesFor( + @Nonnull Ref ref, + @Nonnull EffectControllerComponent effectControllerComponent, + @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo, + @Nonnull Map, EntityTrackerSystems.EntityViewer> exclude + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.EntityEffects; + update.entityEffectUpdates = effectControllerComponent.consumeChanges(); + if (!exclude.isEmpty()) { + for (Entry, EntityTrackerSystems.EntityViewer> entry : visibleTo.entrySet()) { + if (!exclude.containsKey(entry.getKey())) { + entry.getValue().queueUpdate(ref, update); + } + } + } else { + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, update); + } + } + } + } + + public static class EnsureVisibleComponent extends EntityTickingSystem { + public static final Set> DEPENDENCIES = Collections.singleton( + new SystemDependency<>(Order.AFTER, EntityTrackerSystems.ClearPreviouslyVisible.class) + ); + private final ComponentType entityViewerComponentType; + private final ComponentType visibleComponentType; + + public EnsureVisibleComponent( + ComponentType entityViewerComponentType, + ComponentType visibleComponentType + ) { + this.entityViewerComponentType = entityViewerComponentType; + this.visibleComponentType = visibleComponentType; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Override + public Query getQuery() { + return this.entityViewerComponentType; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + for (Ref ref : archetypeChunk.getComponent(index, this.entityViewerComponentType).visible) { + if (!commandBuffer.getArchetype(ref).contains(this.visibleComponentType)) { + commandBuffer.ensureComponent(ref, this.visibleComponentType); + } + } + } + } + + public static class EntityUpdate { + @Nonnull + private final StampedLock removeLock = new StampedLock(); + @Nonnull + private final EnumSet removed; + @Nonnull + private final StampedLock updatesLock = new StampedLock(); + @Nonnull + private final List updates; + + public EntityUpdate() { + this.removed = EnumSet.noneOf(ComponentUpdateType.class); + this.updates = new ObjectArrayList<>(); + } + + public EntityUpdate(@Nonnull EntityTrackerSystems.EntityUpdate other) { + this.removed = EnumSet.copyOf(other.removed); + this.updates = new ObjectArrayList<>(other.updates); + } + + @Nonnull + public EntityTrackerSystems.EntityUpdate clone() { + return new EntityTrackerSystems.EntityUpdate(this); + } + + public void queueRemove(@Nonnull ComponentUpdateType type) { + long stamp = this.removeLock.writeLock(); + + try { + this.removed.add(type); + } finally { + this.removeLock.unlockWrite(stamp); + } + } + + public void queueUpdate(@Nonnull ComponentUpdate update) { + long stamp = this.updatesLock.writeLock(); + + try { + this.updates.add(update); + } finally { + this.updatesLock.unlockWrite(stamp); + } + } + + @Nullable + public ComponentUpdateType[] toRemovedArray() { + return this.removed.isEmpty() ? null : this.removed.toArray(ComponentUpdateType[]::new); + } + + @Nullable + public ComponentUpdate[] toUpdatesArray() { + return this.updates.isEmpty() ? null : this.updates.toArray(ComponentUpdate[]::new); + } + } + + public static class EntityViewer implements Component { + public int viewRadiusBlocks; + public IPacketReceiver packetReceiver; + public Set> visible; + public Map, EntityTrackerSystems.EntityUpdate> updates; + public Object2IntMap> sent; + public int lodExcludedCount; + public int hiddenCount; + + public static ComponentType getComponentType() { + return EntityModule.get().getEntityViewerComponentType(); + } + + public EntityViewer(int viewRadiusBlocks, IPacketReceiver packetReceiver) { + this.viewRadiusBlocks = viewRadiusBlocks; + this.packetReceiver = packetReceiver; + this.visible = new ObjectOpenHashSet<>(); + this.updates = new ConcurrentHashMap<>(); + this.sent = new Object2IntOpenHashMap<>(); + this.sent.defaultReturnValue(-1); + } + + public EntityViewer(@Nonnull EntityTrackerSystems.EntityViewer other) { + this.viewRadiusBlocks = other.viewRadiusBlocks; + this.packetReceiver = other.packetReceiver; + this.visible = new HashSet<>(other.visible); + this.updates = new ConcurrentHashMap<>(other.updates.size()); + + for (Entry, EntityTrackerSystems.EntityUpdate> entry : other.updates.entrySet()) { + this.updates.put(entry.getKey(), entry.getValue().clone()); + } + + this.sent = new Object2IntOpenHashMap<>(other.sent); + this.sent.defaultReturnValue(-1); + } + + @Nonnull + @Override + public Component clone() { + return new EntityTrackerSystems.EntityViewer(this); + } + + public void queueRemove(Ref ref, ComponentUpdateType type) { + if (!this.visible.contains(ref)) { + throw new IllegalArgumentException("Entity is not visible!"); + } else { + this.updates.computeIfAbsent(ref, k -> new EntityTrackerSystems.EntityUpdate()).queueRemove(type); + } + } + + public void queueUpdate(Ref ref, ComponentUpdate update) { + if (!this.visible.contains(ref)) { + throw new IllegalArgumentException("Entity is not visible!"); + } else { + this.updates.computeIfAbsent(ref, k -> new EntityTrackerSystems.EntityUpdate()).queueUpdate(update); + } + } + } + + public static class RemoveEmptyVisibleComponent extends EntityTickingSystem { + public static final Set> DEPENDENCIES = Set.of( + new SystemDependency<>(Order.AFTER, EntityTrackerSystems.AddToVisible.class), + new SystemGroupDependency(Order.BEFORE, EntityTrackerSystems.QUEUE_UPDATE_GROUP) + ); + private final ComponentType componentType; + + public RemoveEmptyVisibleComponent(ComponentType componentType) { + this.componentType = componentType; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Override + public Query getQuery() { + return this.componentType; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + if (archetypeChunk.getComponent(index, this.componentType).visibleTo.isEmpty()) { + commandBuffer.removeComponent(archetypeChunk.getReferenceTo(index), this.componentType); + } + } + } + + public static class RemoveVisibleComponent extends HolderSystem { + private final ComponentType componentType; + + public RemoveVisibleComponent(ComponentType componentType) { + this.componentType = componentType; + } + + @Override + public Query getQuery() { + return this.componentType; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + holder.removeComponent(this.componentType); + } + } + + public static class SendPackets extends EntityTickingSystem { + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final ThreadLocal INT_LIST_THREAD_LOCAL = ThreadLocal.withInitial(IntArrayList::new); + public static final Set> DEPENDENCIES = Set.of(new SystemGroupDependency<>(Order.AFTER, EntityTrackerSystems.QUEUE_UPDATE_GROUP)); + private final ComponentType componentType; + + public SendPackets(ComponentType componentType) { + this.componentType = componentType; + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityStore.SEND_PACKET_GROUP; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Override + public Query getQuery() { + return this.componentType; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.EntityViewer viewer = archetypeChunk.getComponent(index, this.componentType); + IntList removedEntities = INT_LIST_THREAD_LOCAL.get(); + removedEntities.clear(); + int before = viewer.updates.size(); + viewer.updates.entrySet().removeIf(v -> !v.getKey().isValid()); + if (before != viewer.updates.size()) { + LOGGER.atWarning().log("Removed %d invalid updates for removed entities.", before - viewer.updates.size()); + } + + ObjectIterator>> iterator = viewer.sent.object2IntEntrySet().iterator(); + + while (iterator.hasNext()) { + it.unimi.dsi.fastutil.objects.Object2IntMap.Entry> entry = iterator.next(); + Ref ref = entry.getKey(); + if (!ref.isValid() || !viewer.visible.contains(ref)) { + removedEntities.add(entry.getIntValue()); + iterator.remove(); + if (viewer.updates.remove(ref) != null) { + LOGGER.atSevere().log("Entity can't be removed and also receive an update! " + ref); + } + } + } + + if (!removedEntities.isEmpty() || !viewer.updates.isEmpty()) { + Iterator> iteratorx = viewer.updates.keySet().iterator(); + + while (iteratorx.hasNext()) { + Ref ref = iteratorx.next(); + if (!ref.isValid() || ref.getStore() != store) { + iteratorx.remove(); + } else if (!viewer.sent.containsKey(ref)) { + int networkId = commandBuffer.getComponent(ref, NetworkId.getComponentType()).getId(); + if (networkId == -1) { + throw new IllegalArgumentException("Invalid entity network id: " + ref); + } + + viewer.sent.put(ref, networkId); + } + } + + EntityUpdates packet = new EntityUpdates(); + packet.removed = !removedEntities.isEmpty() ? removedEntities.toIntArray() : null; + packet.updates = new com.hypixel.hytale.protocol.EntityUpdate[viewer.updates.size()]; + int i = 0; + + for (Entry, EntityTrackerSystems.EntityUpdate> entry : viewer.updates.entrySet()) { + com.hypixel.hytale.protocol.EntityUpdate entityUpdate = packet.updates[i++] = new com.hypixel.hytale.protocol.EntityUpdate(); + entityUpdate.networkId = viewer.sent.getInt(entry.getKey()); + EntityTrackerSystems.EntityUpdate update = entry.getValue(); + entityUpdate.removed = update.toRemovedArray(); + entityUpdate.updates = update.toUpdatesArray(); + } + + viewer.updates.clear(); + viewer.packetReceiver.writeNoCache(packet); + } + } + } + + public static class Visible implements Component { + @Nonnull + private final StampedLock lock = new StampedLock(); + @Nonnull + public Map, EntityTrackerSystems.EntityViewer> previousVisibleTo = new Object2ObjectOpenHashMap<>(); + @Nonnull + public Map, EntityTrackerSystems.EntityViewer> visibleTo = new Object2ObjectOpenHashMap<>(); + @Nonnull + public Map, EntityTrackerSystems.EntityViewer> newlyVisibleTo = new Object2ObjectOpenHashMap<>(); + + public Visible() { + } + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getVisibleComponentType(); + } + + @Nonnull + @Override + public Component clone() { + return new EntityTrackerSystems.Visible(); + } + + public void addViewerParallel(Ref ref, EntityTrackerSystems.EntityViewer entityViewer) { + long stamp = this.lock.writeLock(); + + try { + this.visibleTo.put(ref, entityViewer); + if (!this.previousVisibleTo.containsKey(ref)) { + this.newlyVisibleTo.put(ref, entityViewer); + } + } finally { + this.lock.unlockWrite(stamp); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/tracker/LegacyEntityTrackerSystems.java b/src/com/hypixel/hytale/server/core/modules/entity/tracker/LegacyEntityTrackerSystems.java new file mode 100644 index 0000000..bb714cd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/tracker/LegacyEntityTrackerSystems.java @@ -0,0 +1,546 @@ +package com.hypixel.hytale.server.core.modules.entity.tracker; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.protocol.EntityUpdate; +import com.hypixel.hytale.protocol.Equipment; +import com.hypixel.hytale.protocol.ModelTransform; +import com.hypixel.hytale.protocol.packets.entities.EntityUpdates; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.nameplate.Nameplate; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.modules.entity.AllLegacyLivingEntityTypesQuery; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.EntityScaleComponent; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.Intangible; +import com.hypixel.hytale.server.core.modules.entity.component.Interactable; +import com.hypixel.hytale.server.core.modules.entity.component.Invulnerable; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.RespondToHit; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSkinComponent; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.projectile.component.PredictedProjectile; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.PositionUtil; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class LegacyEntityTrackerSystems { + public LegacyEntityTrackerSystems() { + } + + @Deprecated + public static void sendPlayerSelf(@Nonnull Ref viewerRef, @Nonnull Store store) { + EntityTrackerSystems.EntityViewer viewer = store.getComponent(viewerRef, EntityTrackerSystems.EntityViewer.getComponentType()); + if (viewer == null) { + throw new IllegalArgumentException("Not EntityViewer"); + } else { + LivingEntity entity = (LivingEntity)EntityUtils.getEntity(viewerRef, store); + TransformComponent transformComponent = store.getComponent(viewerRef, TransformComponent.getComponentType()); + HeadRotation headRotationComponent = store.getComponent(viewerRef, HeadRotation.getComponentType()); + ModelComponent modelComponent = store.getComponent(viewerRef, ModelComponent.getComponentType()); + EntityStatMap statMapComponent = store.getComponent(viewerRef, EntityStatMap.getComponentType()); + PredictedProjectile predictionComponent = store.getComponent(viewerRef, PredictedProjectile.getComponentType()); + EffectControllerComponent effectControllerComponent = store.getComponent(viewerRef, EffectControllerComponent.getComponentType()); + Nameplate nameplateComponent = store.getComponent(viewerRef, Nameplate.getComponentType()); + EntityUpdate entityUpdate = new EntityUpdate(); + entityUpdate.networkId = entity.getNetworkId(); + ObjectArrayList list = new ObjectArrayList<>(); + if (store.getArchetype(viewerRef).contains(Interactable.getComponentType())) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Interactable; + list.add(update); + } + + if (store.getArchetype(viewerRef).contains(Intangible.getComponentType())) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Intangible; + list.add(update); + } + + if (store.getArchetype(viewerRef).contains(Invulnerable.getComponentType())) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Invulnerable; + list.add(update); + } + + if (store.getArchetype(viewerRef).contains(RespondToHit.getComponentType())) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.RespondToHit; + list.add(update); + } + + if (nameplateComponent != null) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Nameplate; + update.nameplate = new com.hypixel.hytale.protocol.Nameplate(); + update.nameplate.text = nameplateComponent.getText(); + list.add(update); + } + + if (predictionComponent != null) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Prediction; + update.predictionId = predictionComponent.getUuid(); + list.add(update); + } + + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Model; + update.model = modelComponent != null ? modelComponent.getModel().toPacket() : null; + EntityScaleComponent entityScaleComponent = store.getComponent(viewerRef, EntityScaleComponent.getComponentType()); + if (entityScaleComponent != null) { + update.entityScale = entityScaleComponent.getScale(); + } + + list.add(update); + update = new ComponentUpdate(); + update.type = ComponentUpdateType.PlayerSkin; + PlayerSkinComponent component = store.getComponent(viewerRef, PlayerSkinComponent.getComponentType()); + update.skin = component != null ? component.getPlayerSkin() : null; + list.add(update); + Inventory inventory = entity.getInventory(); + ComponentUpdate updatex = new ComponentUpdate(); + updatex.type = ComponentUpdateType.Equipment; + updatex.equipment = new Equipment(); + ItemContainer armor = inventory.getArmor(); + updatex.equipment.armorIds = new String[armor.getCapacity()]; + Arrays.fill(updatex.equipment.armorIds, ""); + armor.forEachWithMeta((slot, itemStack, armorIds) -> armorIds[slot] = itemStack.getItemId(), updatex.equipment.armorIds); + ItemStack itemInHand = inventory.getItemInHand(); + updatex.equipment.rightHandItemId = itemInHand != null ? itemInHand.getItemId() : "Empty"; + ItemStack utilityItem = inventory.getUtilityItem(); + updatex.equipment.leftHandItemId = utilityItem != null ? utilityItem.getItemId() : "Empty"; + list.add(updatex); + update = new ComponentUpdate(); + update.type = ComponentUpdateType.Transform; + update.transform = new ModelTransform(); + update.transform.position = PositionUtil.toPositionPacket(transformComponent.getPosition()); + update.transform.bodyOrientation = PositionUtil.toDirectionPacket(transformComponent.getRotation()); + update.transform.lookOrientation = PositionUtil.toDirectionPacket(headRotationComponent.getRotation()); + list.add(update); + update = new ComponentUpdate(); + update.type = ComponentUpdateType.EntityEffects; + update.entityEffectUpdates = effectControllerComponent.createInitUpdates(); + list.add(update); + update = new ComponentUpdate(); + update.type = ComponentUpdateType.EntityStats; + update.entityStatUpdates = statMapComponent.createInitUpdate(true); + list.add(update); + entityUpdate.updates = list.toArray(ComponentUpdate[]::new); + viewer.packetReceiver.writeNoCache(new EntityUpdates(null, new EntityUpdate[]{entityUpdate})); + } + } + + @Deprecated + public static boolean clear(@Nonnull Player player, @Nonnull Holder holder) { + World world = player.getWorld(); + if (world != null && world.isInThread()) { + return EntityTrackerSystems.clear(player.getReference(), world.getEntityStore().getStore()); + } else { + EntityTrackerSystems.EntityViewer entityViewerComponent = holder.getComponent(EntityTrackerSystems.EntityViewer.getComponentType()); + if (entityViewerComponent == null) { + return false; + } else { + entityViewerComponent.sent.clear(); + return true; + } + } + } + + public static class LegacyEntityModel extends EntityTickingSystem { + private final ComponentType componentType; + private final ComponentType modelComponentType; + @Nonnull + private final Query query; + + public LegacyEntityModel(ComponentType componentType) { + this.componentType = componentType; + this.modelComponentType = ModelComponent.getComponentType(); + this.query = Query.and(componentType, this.modelComponentType); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.componentType); + + assert visibleComponent != null; + + ModelComponent modelComponent = archetypeChunk.getComponent(index, this.modelComponentType); + + assert modelComponent != null; + + float entityScale = 0.0F; + boolean scaleOutdated = false; + EntityScaleComponent entityScaleComponent = archetypeChunk.getComponent(index, EntityScaleComponent.getComponentType()); + if (entityScaleComponent != null) { + entityScale = entityScaleComponent.getScale(); + scaleOutdated = entityScaleComponent.consumeNetworkOutdated(); + } + + boolean modelOutdated = modelComponent.consumeNetworkOutdated(); + if (modelOutdated || scaleOutdated) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), modelComponent, entityScale, visibleComponent.visibleTo); + } else if (!visibleComponent.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), modelComponent, entityScale, visibleComponent.newlyVisibleTo); + } + } + + private static void queueUpdatesFor( + Ref ref, @Nullable ModelComponent model, float entityScale, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Model; + update.model = model != null ? model.getModel().toPacket() : null; + update.entityScale = entityScale; + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, update); + } + } + } + + public static class LegacyEntitySkin extends EntityTickingSystem { + private final ComponentType playerSkinComponentComponentType; + private final ComponentType visibleComponentType; + @Nonnull + private final Query query; + + public LegacyEntitySkin( + ComponentType visibleComponentType, + ComponentType playerSkinComponentComponentType + ) { + this.visibleComponentType = visibleComponentType; + this.playerSkinComponentComponentType = playerSkinComponentComponentType; + this.query = Query.and(visibleComponentType, playerSkinComponentComponentType); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.visibleComponentType); + + assert visibleComponent != null; + + if (archetypeChunk.getComponent(index, this.playerSkinComponentComponentType).consumeNetworkOutdated()) { + queueUpdatesFor( + archetypeChunk.getReferenceTo(index), archetypeChunk.getComponent(index, this.playerSkinComponentComponentType), visibleComponent.visibleTo + ); + } else if (!visibleComponent.newlyVisibleTo.isEmpty()) { + queueUpdatesFor( + archetypeChunk.getReferenceTo(index), archetypeChunk.getComponent(index, this.playerSkinComponentComponentType), visibleComponent.newlyVisibleTo + ); + } + } + + private static void queueUpdatesFor( + Ref ref, @Nonnull PlayerSkinComponent component, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.PlayerSkin; + update.skin = component.getPlayerSkin(); + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, update); + } + } + } + + public static class LegacyEquipment extends EntityTickingSystem { + private final ComponentType componentType; + @Nonnull + private final Query query; + + public LegacyEquipment(ComponentType componentType) { + this.componentType = componentType; + this.query = Query.and(componentType, AllLegacyLivingEntityTypesQuery.INSTANCE); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.componentType); + + assert visibleComponent != null; + + LivingEntity entity = (LivingEntity)EntityUtils.getEntity(index, archetypeChunk); + + assert entity != null; + + if (entity.consumeEquipmentNetworkOutdated()) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), entity, visibleComponent.visibleTo); + } else if (!visibleComponent.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), entity, visibleComponent.newlyVisibleTo); + } + } + + private static void queueUpdatesFor( + @Nonnull Ref ref, @Nonnull LivingEntity entity, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Equipment; + update.equipment = new Equipment(); + Inventory inventory = entity.getInventory(); + ItemContainer armor = inventory.getArmor(); + update.equipment.armorIds = new String[armor.getCapacity()]; + Arrays.fill(update.equipment.armorIds, ""); + armor.forEachWithMeta((slot, itemStack, armorIds) -> armorIds[slot] = itemStack.getItemId(), update.equipment.armorIds); + ItemStack itemInHand = inventory.getItemInHand(); + update.equipment.rightHandItemId = itemInHand != null ? itemInHand.getItemId() : "Empty"; + ItemStack utilityItem = inventory.getUtilityItem(); + update.equipment.leftHandItemId = utilityItem != null ? utilityItem.getItemId() : "Empty"; + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, update); + } + } + } + + public static class LegacyHideFromEntity extends EntityTickingSystem { + private final ComponentType entityViewerComponentType; + private final ComponentType playerSettingsComponentType; + @Nonnull + private final Query query; + @Nonnull + private final Set> dependencies; + + public LegacyHideFromEntity(ComponentType entityViewerComponentType) { + this.entityViewerComponentType = entityViewerComponentType; + this.playerSettingsComponentType = EntityModule.get().getPlayerSettingsComponentType(); + this.query = Query.and(entityViewerComponentType, AllLegacyLivingEntityTypesQuery.INSTANCE); + this.dependencies = Collections.singleton(new SystemDependency<>(Order.AFTER, EntityTrackerSystems.CollectVisible.class)); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.FIND_VISIBLE_ENTITIES_GROUP; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Ref viewerRef = archetypeChunk.getReferenceTo(index); + PlayerSettings settings = archetypeChunk.getComponent(index, this.playerSettingsComponentType); + if (settings == null) { + settings = PlayerSettings.defaults(); + } + + EntityTrackerSystems.EntityViewer entityViewerComponent = archetypeChunk.getComponent(index, this.entityViewerComponentType); + + assert entityViewerComponent != null; + + Iterator> iterator = entityViewerComponent.visible.iterator(); + + while (iterator.hasNext()) { + Ref ref = iterator.next(); + Entity entity = EntityUtils.getEntity(ref, commandBuffer); + if (entity != null && entity.isHiddenFromLivingEntity(ref, viewerRef, commandBuffer) && canHideEntities(entity, settings)) { + entityViewerComponent.hiddenCount++; + iterator.remove(); + } + } + } + + private static boolean canHideEntities(Entity entity, @Nonnull PlayerSettings settings) { + return entity instanceof Player && !settings.showEntityMarkers(); + } + } + + public static class LegacyLODCull extends EntityTickingSystem { + public static final double ENTITY_LOD_RATIO_DEFAULT = 3.5E-5; + public static double ENTITY_LOD_RATIO = 3.5E-5; + private final ComponentType componentType; + private final ComponentType boundingBoxComponentType; + @Nonnull + private final Query query; + @Nonnull + private final Set> dependencies; + + public LegacyLODCull(ComponentType componentType) { + this.componentType = componentType; + this.boundingBoxComponentType = BoundingBox.getComponentType(); + this.query = Query.and(componentType, TransformComponent.getComponentType()); + this.dependencies = Collections.singleton(new SystemDependency<>(Order.AFTER, EntityTrackerSystems.CollectVisible.class)); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.FIND_VISIBLE_ENTITIES_GROUP; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.EntityViewer entityViewerComponent = archetypeChunk.getComponent(index, this.componentType); + + assert entityViewerComponent != null; + + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + Iterator> iterator = entityViewerComponent.visible.iterator(); + + while (iterator.hasNext()) { + Ref ref = iterator.next(); + BoundingBox boundingBoxComponent = commandBuffer.getComponent(ref, this.boundingBoxComponentType); + if (boundingBoxComponent != null) { + TransformComponent otherTransformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType()); + + assert otherTransformComponent != null; + + double distanceSq = otherTransformComponent.getPosition().distanceSquaredTo(position); + double maximumThickness = boundingBoxComponent.getBoundingBox().getMaximumThickness(); + if (maximumThickness < ENTITY_LOD_RATIO * distanceSq) { + entityViewerComponent.lodExcludedCount++; + iterator.remove(); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entity/tracker/NetworkId.java b/src/com/hypixel/hytale/server/core/modules/entity/tracker/NetworkId.java new file mode 100644 index 0000000..c618106 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entity/tracker/NetworkId.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.core.modules.entity.tracker; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public final class NetworkId implements Component { + private final int id; + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getNetworkIdComponentType(); + } + + public NetworkId(int id) { + this.id = id; + } + + public int getId() { + return this.id; + } + + @Nonnull + @Override + public Component clone() { + return this; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/EntityStatMap.java b/src/com/hypixel/hytale/server/core/modules/entitystats/EntityStatMap.java new file mode 100644 index 0000000..be1a85f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/EntityStatMap.java @@ -0,0 +1,562 @@ +package com.hypixel.hytale.server.core.modules.entitystats; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.ChangeStatBehaviour; +import com.hypixel.hytale.protocol.EntityStatOp; +import com.hypixel.hytale.protocol.EntityStatUpdate; +import com.hypixel.hytale.protocol.ValueType; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.Modifier; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.StaticModifier; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.floats.FloatArrayList; +import it.unimi.dsi.fastutil.floats.FloatList; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityStatMap implements Component { + public static final int VERSION = 5; + public static final BuilderCodec CODEC = BuilderCodec.builder(EntityStatMap.class, EntityStatMap::new) + .legacyVersioned() + .codecVersion(5) + .addField( + new KeyedCodec<>("Stats", new MapCodec<>(EntityStatValue.CODEC, HashMap::new, false)), (statMap, value) -> statMap.unknown = value, statMap -> { + HashMap outMap = new HashMap<>(); + if (statMap.unknown != null) { + outMap.putAll(statMap.unknown); + } + + for (EntityStatValue value : statMap.values) { + if (value != null) { + outMap.putIfAbsent(value.getId(), value); + } + } + + return outMap; + } + ) + .afterDecode(map -> { + map.values = EntityStatValue.EMPTY_ARRAY; + map.update(); + }) + .build(); + private Map unknown; + @Nonnull + private EntityStatValue[] values = EntityStatValue.EMPTY_ARRAY; + float[] tempRegenerationValues = ArrayUtil.EMPTY_FLOAT_ARRAY; + public final Int2ObjectMap> selfUpdates = new Int2ObjectOpenHashMap<>(); + public final Int2ObjectMap selfStatValues = new Int2ObjectOpenHashMap<>(); + public final Int2ObjectMap> otherUpdates = new Int2ObjectOpenHashMap<>(); + protected boolean isSelfNetworkOutdated; + protected boolean isNetworkOutdated; + + public static ComponentType getComponentType() { + return EntityStatsModule.get().getEntityStatMapComponentType(); + } + + public EntityStatMap() { + } + + public int size() { + return this.values.length; + } + + @Nullable + public EntityStatValue get(int index) { + return index >= this.values.length ? null : this.values[index]; + } + + @Deprecated + @Nullable + public EntityStatValue get(String entityStat) { + return this.get(EntityStatType.getAssetMap().getIndex(entityStat)); + } + + public void update() { + IndexedLookupTableAssetMap assetMap = EntityStatType.getAssetMap(); + + for (int index = 0; index < this.values.length; index++) { + EntityStatType asset = assetMap.getAsset(index); + EntityStatValue value = this.values[index]; + if (value != null) { + if (asset.isUnknown()) { + if (this.unknown == null) { + this.unknown = new Object2ObjectOpenHashMap<>(); + } + + this.unknown.put(asset.getId(), value); + this.values[index] = new EntityStatValue(index, asset); + } else if (value.synchronizeAsset(index, asset)) { + this.addInitChange(index, value); + } + } + } + + int assetCount = assetMap.getNextIndex(); + int oldLength = this.values.length; + if (oldLength <= assetCount) { + this.values = Arrays.copyOf(this.values, assetCount); + + for (int indexx = oldLength; indexx < assetCount; indexx++) { + EntityStatType asset = assetMap.getAsset(indexx); + if (asset.isUnknown()) { + EntityStatValue value = this.values[indexx] = new EntityStatValue(indexx, asset); + this.addInitChange(indexx, value); + } else { + EntityStatValue value = this.unknown == null ? null : this.unknown.remove(asset.getId()); + if (value != null) { + value.synchronizeAsset(indexx, asset); + this.values[indexx] = value; + this.addInitChange(indexx, value); + } else { + value = this.values[indexx] = new EntityStatValue(indexx, asset); + this.addInitChange(indexx, value); + } + } + } + } + } + + @Nullable + public Modifier getModifier(int index, String key) { + EntityStatValue entityStatValue = this.get(index); + if (entityStatValue == null) { + HytaleLogger.getLogger().at(Level.WARNING).log("No EntityStatValue found for index: " + index); + return null; + } else { + return entityStatValue.getModifiers() != null ? entityStatValue.getModifiers().get(key) : null; + } + } + + @Nullable + public Modifier putModifier(int index, String key, Modifier modifier) { + return this.putModifier(EntityStatMap.Predictable.NONE, index, key, modifier); + } + + @Nullable + public Modifier putModifier(EntityStatMap.Predictable predictable, int index, String key, Modifier modifier) { + EntityStatValue entityStatValue = this.get(index); + if (entityStatValue == null) { + HytaleLogger.getLogger().at(Level.WARNING).log("No EntityStatValue found for index: " + index); + return null; + } else { + float previousValue = entityStatValue.get(); + Modifier previous = entityStatValue.putModifier(key, modifier); + this.addChange(predictable, index, EntityStatOp.PutModifier, previousValue, key, modifier); + return previous; + } + } + + @Nullable + public Modifier removeModifier(int index, String key) { + return this.removeModifier(EntityStatMap.Predictable.NONE, index, key); + } + + @Nullable + public Modifier removeModifier(EntityStatMap.Predictable predictable, int index, String key) { + EntityStatValue entityStatValue = this.get(index); + if (entityStatValue == null) { + HytaleLogger.getLogger().at(Level.WARNING).log("No EntityStatValue found for index: " + index); + return null; + } else { + float previousValue = entityStatValue.get(); + Modifier previous = entityStatValue.removeModifier(key); + if (previous != null) { + this.addChange(predictable, index, EntityStatOp.RemoveModifier, previousValue, key, null); + } + + return previous; + } + } + + public float setStatValue(int index, float newValue) { + return this.setStatValue(EntityStatMap.Predictable.NONE, index, newValue); + } + + public float setStatValue(EntityStatMap.Predictable predictable, int index, float newValue) { + EntityStatValue entityStatValue = this.get(index); + if (entityStatValue == null) { + HytaleLogger.getLogger().at(Level.WARNING).log("No EntityStatValue found for index: " + index); + return 0.0F; + } else { + float currentValue = entityStatValue.get(); + float ret = entityStatValue.set(newValue); + if (predictable != EntityStatMap.Predictable.NONE || newValue != currentValue) { + this.addChange(predictable, index, EntityStatOp.Set, currentValue, newValue); + } + + return ret; + } + } + + public float addStatValue(int index, float amount) { + return this.addStatValue(EntityStatMap.Predictable.NONE, index, amount); + } + + public float addStatValue(EntityStatMap.Predictable predictable, int index, float amount) { + EntityStatValue entityStatValue = this.get(index); + if (entityStatValue == null) { + HytaleLogger.getLogger().at(Level.WARNING).log("No EntityStatValue found for index: " + index); + return 0.0F; + } else { + float currentValue = entityStatValue.get(); + float ret = entityStatValue.set(currentValue + amount); + if (predictable != EntityStatMap.Predictable.NONE || ret != currentValue) { + this.addChange(predictable, index, EntityStatOp.Add, currentValue, amount); + } + + return ret; + } + } + + public float subtractStatValue(int index, float amount) { + return this.addStatValue(index, -amount); + } + + public float subtractStatValue(EntityStatMap.Predictable predictable, int index, float amount) { + return this.addStatValue(predictable, index, -amount); + } + + public float minimizeStatValue(int index) { + return this.minimizeStatValue(EntityStatMap.Predictable.NONE, index); + } + + public float minimizeStatValue(EntityStatMap.Predictable predictable, int index) { + EntityStatValue entityStatValue = this.get(index); + if (entityStatValue == null) { + HytaleLogger.getLogger().at(Level.WARNING).log("No EntityStatValue found for index: " + index); + return 0.0F; + } else { + float previousValue = entityStatValue.get(); + float ret = entityStatValue.set(entityStatValue.getMin()); + this.addChange(predictable, index, EntityStatOp.Minimize, previousValue, 0.0F); + return ret; + } + } + + public float maximizeStatValue(int index) { + return this.maximizeStatValue(EntityStatMap.Predictable.NONE, index); + } + + public float maximizeStatValue(EntityStatMap.Predictable predictable, int index) { + EntityStatValue entityStatValue = this.get(index); + if (entityStatValue == null) { + HytaleLogger.getLogger().at(Level.WARNING).log("No EntityStatValue found for index: " + index); + return 0.0F; + } else { + float previousValue = entityStatValue.get(); + float ret = entityStatValue.set(entityStatValue.getMax()); + this.addChange(predictable, index, EntityStatOp.Maximize, previousValue, 0.0F); + return ret; + } + } + + public float resetStatValue(int index) { + return this.resetStatValue(EntityStatMap.Predictable.NONE, index); + } + + public float resetStatValue(EntityStatMap.Predictable predictable, int index) { + EntityStatType entityStatType = EntityStatType.getAssetMap().getAsset(index); + if (entityStatType == null) { + HytaleLogger.getLogger().at(Level.WARNING).log("No EntityStatType found for index: " + index); + return 0.0F; + } else { + EntityStatValue entityStatValue = this.get(index); + if (entityStatValue == null) { + HytaleLogger.getLogger().at(Level.WARNING).log("No EntityStatValue found for index: " + index); + return 0.0F; + } else { + float previousValue = entityStatValue.get(); + + float ret = switch (entityStatType.getResetBehavior()) { + case InitialValue -> entityStatValue.set(entityStatType.getInitialValue()); + case MaxValue -> entityStatValue.set(entityStatValue.getMax()); + default -> 0.0F; + }; + this.addChange(predictable, index, EntityStatOp.Reset, previousValue, 0.0F); + return ret; + } + } + } + + @Nonnull + public Int2ObjectMap> getSelfUpdates() { + return this.selfUpdates; + } + + @Nonnull + public Int2ObjectMap getSelfStatValues() { + return this.selfStatValues; + } + + @Nonnull + public Int2ObjectMap consumeSelfUpdates() { + return this.updatesToProtocol(this.selfUpdates); + } + + public void clearUpdates() { + this.selfUpdates.values().forEach(List::clear); + this.selfStatValues.values().forEach(List::clear); + this.otherUpdates.values().forEach(List::clear); + } + + @Nonnull + public Int2ObjectMap consumeOtherUpdates() { + return this.updatesToProtocol(this.otherUpdates); + } + + @Nonnull + private Int2ObjectOpenHashMap updatesToProtocol(@Nonnull Int2ObjectMap> localUpdates) { + Int2ObjectOpenHashMap updates = new Int2ObjectOpenHashMap<>(localUpdates.size()); + ObjectIterator>> iterator = Int2ObjectMaps.fastIterator(localUpdates); + + while (iterator.hasNext()) { + Entry> e = iterator.next(); + if (!e.getValue().isEmpty()) { + updates.put(e.getIntKey(), e.getValue().toArray(EntityStatUpdate[]::new)); + } + } + + return updates; + } + + @Nonnull + public Int2ObjectMap createInitUpdate(boolean all) { + Int2ObjectOpenHashMap updates = new Int2ObjectOpenHashMap<>(this.size()); + + for (int i = 0; i < this.size(); i++) { + EntityStatValue stat = this.get(i); + if (stat != null && (EntityStatType.getAssetMap().getAsset(i).isShared() || all)) { + updates.put(i, new EntityStatUpdate[]{makeInitChange(stat)}); + } + } + + return updates; + } + + public boolean consumeSelfNetworkOutdated() { + boolean temp = this.isSelfNetworkOutdated; + this.isSelfNetworkOutdated = false; + return temp; + } + + public boolean consumeNetworkOutdated() { + boolean temp = this.isNetworkOutdated; + this.isNetworkOutdated = false; + return temp; + } + + private void addInitChange(int index, @Nonnull EntityStatValue value) { + this.addChange(EntityStatMap.Predictable.NONE, index, EntityStatOp.Init, value.get(), value.get(), value.getModifiers()); + } + + private void addChange(EntityStatMap.Predictable predictable, int index, @Nonnull EntityStatOp op, float previousValue, float value) { + this.addChange(predictable, index, op, previousValue, value, null); + } + + private void addChange( + EntityStatMap.Predictable predictable, int index, @Nonnull EntityStatOp op, float previousValue, float value, Map modifierMap + ) { + EntityStatType statType = EntityStatType.getAssetMap().getAsset(index); + if (statType.isShared()) { + boolean isPredictable = predictable == EntityStatMap.Predictable.ALL; + List other = this.otherUpdates.computeIfAbsent(index, v -> new ObjectArrayList<>()); + this.tryMergeUpdate(other, op, value, modifierMap, isPredictable); + this.isNetworkOutdated = true; + } + + boolean isPredictable = predictable != EntityStatMap.Predictable.NONE; + List self = this.selfUpdates.computeIfAbsent(index, v -> new ObjectArrayList<>()); + FloatList values = this.selfStatValues.computeIfAbsent(index, v -> new FloatArrayList()); + if (this.tryMergeUpdate(self, op, value, modifierMap, isPredictable)) { + values.set(values.size() - 1, this.get(index).get()); + } else { + values.add(previousValue); + values.add(this.get(index).get()); + this.isSelfNetworkOutdated = true; + } + } + + private void addChange(EntityStatMap.Predictable predictable, int index, EntityStatOp op, float previousValue, String key, @Nullable Modifier modifier) { + EntityStatType statType = EntityStatType.getAssetMap().getAsset(index); + com.hypixel.hytale.protocol.Modifier modifierPacket = modifier != null ? modifier.toPacket() : null; + if (statType.isShared()) { + boolean isPredictable = predictable == EntityStatMap.Predictable.ALL; + List other = this.otherUpdates.computeIfAbsent(index, v -> new ObjectArrayList<>()); + other.add(new EntityStatUpdate(op, isPredictable, 0.0F, null, key, modifierPacket)); + this.isNetworkOutdated = true; + } + + boolean isPredictable = predictable != EntityStatMap.Predictable.NONE; + List self = this.selfUpdates.computeIfAbsent(index, v -> new ObjectArrayList<>()); + self.add(new EntityStatUpdate(op, isPredictable, 0.0F, null, key, modifierPacket)); + FloatList values = this.selfStatValues.computeIfAbsent(index, v -> new FloatArrayList()); + values.add(previousValue); + values.add(this.get(index).get()); + this.isSelfNetworkOutdated = true; + } + + private boolean tryMergeUpdate( + @Nonnull List updates, @Nonnull EntityStatOp op, float value, @Nullable Map modifierMap, boolean isPredictable + ) { + EntityStatUpdate last = updates.isEmpty() ? null : updates.getLast(); + switch (op) { + case Init: + if (!isPredictable && last != null && !last.predictable && last.op == EntityStatOp.Init) { + last.value = value; + return true; + } + + Map modifiers = null; + if (modifierMap != null) { + modifiers = new Object2ObjectOpenHashMap<>(); + + for (java.util.Map.Entry e : modifierMap.entrySet()) { + modifiers.put(e.getKey(), e.getValue().toPacket()); + } + } + + updates.add(new EntityStatUpdate(op, isPredictable, value, modifiers, null, null)); + return false; + case Remove: + updates.add(new EntityStatUpdate(op, isPredictable, 0.0F, null, null, null)); + default: + return false; + case Add: + if (isPredictable || last == null || last.predictable || last.op != EntityStatOp.Init && last.op != EntityStatOp.Add && last.op != EntityStatOp.Set + ) + { + updates.add(new EntityStatUpdate(op, isPredictable, value, null, null, null)); + return false; + } + + last.value += value; + return true; + case Set: + case Minimize: + case Maximize: + case Reset: + if (!isPredictable && last != null && !last.predictable && last.op != EntityStatOp.Remove) { + if (last.op != EntityStatOp.Init) { + last.op = op; + } + + last.value = value; + return true; + } else { + updates.add(new EntityStatUpdate(op, isPredictable, value, null, null, null)); + return false; + } + } + } + + public void processStatChanges( + EntityStatMap.Predictable predictable, @Nonnull Int2FloatMap entityStats, ValueType valueType, @Nonnull ChangeStatBehaviour changeStatBehaviour + ) { + for (it.unimi.dsi.fastutil.ints.Int2FloatMap.Entry entry : entityStats.int2FloatEntrySet()) { + int statIndex = entry.getIntKey(); + float amount = entry.getFloatValue(); + if (valueType == ValueType.Percent) { + EntityStatValue stat = this.get(statIndex); + if (stat == null) { + continue; + } + + amount = amount * (stat.getMax() - stat.getMin()) / 100.0F; + } + + switch (changeStatBehaviour) { + case Set: + this.setStatValue(predictable, statIndex, amount); + break; + case Add: + this.addStatValue(predictable, statIndex, amount); + } + } + } + + @Nonnull + @Override + public String toString() { + return "EntityStatMap{unknown=" + this.unknown + "values=" + Arrays.toString((Object[])this.values) + "}"; + } + + @Nonnull + public EntityStatMap clone() { + EntityStatMap map = new EntityStatMap(); + map.unknown = this.unknown; + map.update(); + + for (int i = 0; i < this.values.length; i++) { + map.values[i].set(this.values[i].get()); + } + + map.selfUpdates.putAll(this.selfUpdates); + map.selfStatValues.putAll(this.selfStatValues); + map.otherUpdates.putAll(this.otherUpdates); + return map; + } + + @Nonnull + private static EntityStatUpdate makeInitChange(@Nonnull EntityStatValue value) { + Map modifiers = null; + if (value.getModifiers() != null) { + modifiers = new Object2ObjectOpenHashMap<>(); + + for (java.util.Map.Entry e : value.getModifiers().entrySet()) { + modifiers.put(e.getKey(), e.getValue().toPacket()); + } + } + + return new EntityStatUpdate(EntityStatOp.Init, false, value.get(), modifiers, null, null); + } + + public static Int2ObjectMap toPacket(@Nullable Int2ObjectMap modifiers) { + if (modifiers == null) { + return null; + } else { + Int2ObjectOpenHashMap packet = new Int2ObjectOpenHashMap<>(modifiers.size()); + + for (Entry e : modifiers.int2ObjectEntrySet()) { + com.hypixel.hytale.protocol.Modifier[] out = new com.hypixel.hytale.protocol.Modifier[((StaticModifier[])e.getValue()).length]; + + for (int i = 0; i < out.length; i++) { + out[i] = e.getValue()[i].toPacket(); + } + + packet.put(e.getIntKey(), out); + } + + return packet; + } + } + + public static enum Predictable { + NONE, + SELF, + ALL; + + private Predictable() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/EntityStatValue.java b/src/com/hypixel/hytale/server/core/modules/entitystats/EntityStatValue.java new file mode 100644 index 0000000..bd9f7c9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/EntityStatValue.java @@ -0,0 +1,228 @@ +package com.hypixel.hytale.server.core.modules.entitystats; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.Modifier; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.StaticModifier; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityStatValue { + public static final EntityStatValue[] EMPTY_ARRAY = new EntityStatValue[0]; + public static final BuilderCodec CODEC = BuilderCodec.builder(EntityStatValue.class, EntityStatValue::new) + .addField(new KeyedCodec<>("Id", Codec.STRING), (regenerating, value) -> regenerating.id = value, regenerating -> regenerating.id) + .addField(new KeyedCodec<>("Value", Codec.FLOAT), (regenerating, value) -> regenerating.value = value, regenerating -> regenerating.value) + .addField( + new KeyedCodec<>("Modifiers", new MapCodec<>(Modifier.CODEC, HashMap::new, false)), + (regenerating, value) -> regenerating.modifiers = value, + regenerating -> regenerating.modifiers != null && !regenerating.modifiers.isEmpty() ? regenerating.modifiers : null + ) + .build(); + private String id; + private int index = Integer.MIN_VALUE; + private float value; + private float min; + private float max; + private boolean ignoreInvulnerability; + @Nullable + private RegeneratingValue[] regeneratingValues; + @Nullable + private Map modifiers; + + protected EntityStatValue() { + } + + public EntityStatValue(int index, @Nonnull EntityStatType asset) { + this.id = asset.getId(); + this.index = index; + this.value = asset.getInitialValue(); + this.synchronizeAsset(index, asset); + } + + public String getId() { + return this.id; + } + + public int getIndex() { + return this.index; + } + + public float get() { + return this.value; + } + + public float asPercentage() { + return (this.value - this.min) / (this.max - this.min); + } + + public float getMin() { + return this.min; + } + + public float getMax() { + return this.max; + } + + protected float set(float newValue) { + return this.value = MathUtil.clamp(newValue, this.min, this.max); + } + + @Nullable + public RegeneratingValue[] getRegeneratingValues() { + return this.regeneratingValues; + } + + @Nullable + public Modifier getModifier(String key) { + return this.modifiers == null ? null : this.modifiers.get(key); + } + + public boolean getIgnoreInvulnerability() { + return this.ignoreInvulnerability; + } + + @Nullable + public Map getModifiers() { + return this.modifiers; + } + + @Nullable + protected Modifier putModifier(String key, Modifier modifier) { + if (this.modifiers == null) { + this.modifiers = new Object2ObjectOpenHashMap<>(); + } + + Modifier oldModifier = this.modifiers.put(key, modifier); + this.computeModifiers(EntityStatType.getAssetMap().getAsset(this.index)); + return oldModifier; + } + + @Nullable + protected Modifier removeModifier(String key) { + if (this.modifiers == null) { + return null; + } else { + Modifier modifier = this.modifiers.remove(key); + if (modifier != null) { + this.computeModifiers(EntityStatType.getAssetMap().getAsset(this.index)); + } + + return modifier; + } + } + + public boolean synchronizeAsset(int index, @Nonnull EntityStatType asset) { + this.id = asset.getId(); + this.index = index; + this.initializeRegenerating(asset); + boolean minMaxChanged = this.min != asset.getMin() || this.max != asset.getMax(); + this.ignoreInvulnerability = asset.getIgnoreInvulnerability(); + float oldValue = this.value; + this.computeModifiers(asset); + return minMaxChanged || this.value != oldValue; + } + + private void initializeRegenerating(@Nonnull EntityStatType entityStatType) { + EntityStatType.Regenerating[] regeneratingTypes = entityStatType.getRegenerating(); + if (regeneratingTypes != null) { + this.regeneratingValues = new RegeneratingValue[regeneratingTypes.length]; + + for (int i = 0; i < regeneratingTypes.length; i++) { + this.regeneratingValues[i] = new RegeneratingValue(regeneratingTypes[i]); + } + } + } + + protected void computeModifiers(@Nonnull EntityStatType asset) { + this.min = asset.getMin(); + this.max = asset.getMax(); + if (this.modifiers != null) { + for (Modifier.ModifierTarget target : Modifier.ModifierTarget.VALUES) { + boolean hasAdditive = false; + float additive = 0.0F; + boolean hasMultiplicative = false; + float multiplicative = 0.0F; + + for (Modifier modifier : this.modifiers.values()) { + if (modifier instanceof StaticModifier staticModifier && staticModifier.getTarget() == target) { + switch (staticModifier.getCalculationType()) { + case ADDITIVE: + hasAdditive = true; + additive += staticModifier.getAmount(); + break; + case MULTIPLICATIVE: + hasMultiplicative = true; + multiplicative += staticModifier.getAmount(); + } + } + } + + switch (target) { + case MIN: + if (hasAdditive) { + this.min = StaticModifier.CalculationType.ADDITIVE.compute(this.min, additive); + } + + if (hasMultiplicative) { + this.min = StaticModifier.CalculationType.MULTIPLICATIVE.compute(this.min, multiplicative); + } + break; + case MAX: + if (hasAdditive) { + this.max = StaticModifier.CalculationType.ADDITIVE.compute(this.max, additive); + } + + if (hasMultiplicative) { + this.max = StaticModifier.CalculationType.MULTIPLICATIVE.compute(this.max, multiplicative); + } + } + } + + for (Modifier modifierx : this.modifiers.values()) { + if (!(modifierx instanceof StaticModifier)) { + this.applyModifier(modifierx); + } + } + } + + this.value = MathUtil.clamp(this.value, this.min, this.max); + } + + private void applyModifier(@Nonnull Modifier modifier) { + switch (modifier.getTarget()) { + case MIN: + this.min = modifier.apply(this.min); + break; + case MAX: + this.max = modifier.apply(this.max); + } + } + + @Nonnull + @Override + public String toString() { + return "EntityStatValue{id='" + + this.id + + "', index=" + + this.index + + ", value=" + + this.value + + ", min=" + + this.min + + ", max=" + + this.max + + ", regeneratingValues=" + + Arrays.toString((Object[])this.regeneratingValues) + + ", modifiers=" + + this.modifiers + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/EntityStatsModule.java b/src/com/hypixel/hytale/server/core/modules/entitystats/EntityStatsModule.java new file mode 100644 index 0000000..cbfbb83 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/EntityStatsModule.java @@ -0,0 +1,257 @@ +package com.hypixel.hytale.server.core.modules.entitystats; + +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.JsonAsset; +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemType; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.AllLegacyLivingEntityTypesQuery; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entitystats.asset.DefaultEntityStatTypes; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatTypePacketGenerator; +import com.hypixel.hytale.server.core.modules.entitystats.asset.condition.AliveCondition; +import com.hypixel.hytale.server.core.modules.entitystats.asset.condition.ChargingCondition; +import com.hypixel.hytale.server.core.modules.entitystats.asset.condition.Condition; +import com.hypixel.hytale.server.core.modules.entitystats.asset.condition.EnvironmentCondition; +import com.hypixel.hytale.server.core.modules.entitystats.asset.condition.GlidingCondition; +import com.hypixel.hytale.server.core.modules.entitystats.asset.condition.LogicCondition; +import com.hypixel.hytale.server.core.modules.entitystats.asset.condition.NoDamageTakenCondition; +import com.hypixel.hytale.server.core.modules.entitystats.asset.condition.OutOfCombatCondition; +import com.hypixel.hytale.server.core.modules.entitystats.asset.condition.PlayerCondition; +import com.hypixel.hytale.server.core.modules.entitystats.asset.condition.RegenHealthCondition; +import com.hypixel.hytale.server.core.modules.entitystats.asset.condition.SprintingCondition; +import com.hypixel.hytale.server.core.modules.entitystats.asset.condition.StatCondition; +import com.hypixel.hytale.server.core.modules.entitystats.asset.condition.SuffocatingCondition; +import com.hypixel.hytale.server.core.modules.entitystats.asset.condition.WieldingCondition; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.Modifier; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.StaticModifier; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatMap.Entry; +import java.util.Collections; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bouncycastle.util.Arrays; + +public class EntityStatsModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(EntityStatsModule.class) + .depends(EntityModule.class) + .depends(InteractionModule.class) + .build(); + private static EntityStatsModule instance; + private ComponentType entityStatMapComponentType; + private SystemType statModifyingSystemType; + + public static EntityStatsModule get() { + return instance; + } + + public EntityStatsModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + Modifier.CODEC.register("Boost", StaticModifier.class, StaticModifier.ENTITY_CODEC); + Modifier.CODEC.register("Static", StaticModifier.class, StaticModifier.ENTITY_CODEC); + Condition.CODEC.register("LogicCondition", LogicCondition.class, LogicCondition.CODEC); + Condition.CODEC.register("RegenHealth", RegenHealthCondition.class, RegenHealthCondition.CODEC); + Condition.CODEC.register("NoDamageTaken", NoDamageTakenCondition.class, NoDamageTakenCondition.CODEC); + Condition.CODEC.register("Suffocating", SuffocatingCondition.class, SuffocatingCondition.CODEC); + Condition.CODEC.register("Charging", ChargingCondition.class, ChargingCondition.CODEC); + Condition.CODEC.register("Alive", AliveCondition.class, AliveCondition.CODEC); + Condition.CODEC.register("Environment", EnvironmentCondition.class, EnvironmentCondition.CODEC); + Condition.CODEC.register("Player", PlayerCondition.class, PlayerCondition.CODEC); + Condition.CODEC.register("OutOfCombat", OutOfCombatCondition.class, OutOfCombatCondition.CODEC); + Condition.CODEC.register("Wielding", WieldingCondition.class, WieldingCondition.CODEC); + Condition.CODEC.register("Sprinting", SprintingCondition.class, SprintingCondition.CODEC); + Condition.CODEC.register("Gliding", GlidingCondition.class, GlidingCondition.CODEC); + Condition.CODEC.register("Stat", StatCondition.class, StatCondition.CODEC); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + EntityStatType.class, new IndexedLookupTableAssetMap<>(EntityStatType[]::new) + ) + .setPath("Entity/Stats")) + .setCodec(EntityStatType.CODEC)) + .setKeyFunction(EntityStatType::getId)) + .setPacketGenerator(new EntityStatTypePacketGenerator()) + .setReplaceOnRemove(EntityStatType::getUnknownFor)) + .preLoadAssets(Collections.singletonList(EntityStatType.UNKNOWN))) + .loadsAfter(SoundEvent.class, ParticleSystem.class)) + .build() + ); + this.getEventRegistry().register(LoadedAssetsEvent.class, EntityStatType.class, this::onLoadedAssetsEvent); + this.statModifyingSystemType = this.getEntityStoreRegistry().registerSystemType(EntityStatsSystems.StatModifyingSystem.class); + this.entityStatMapComponentType = this.getEntityStoreRegistry().registerComponent(EntityStatMap.class, "EntityStats", EntityStatMap.CODEC); + this.getEntityStoreRegistry().registerSystem(new EntityStatsSystems.Setup(this.entityStatMapComponentType)); + this.getEntityStoreRegistry().registerSystem(new EntityStatsModule.PlayerRegenerateStatsSystem()); + this.getEntityStoreRegistry().registerSystem(new EntityStatsSystems.Recalculate(this.entityStatMapComponentType)); + this.getEntityStoreRegistry().registerSystem(new EntityStatsSystems.EntityTrackerUpdate(this.entityStatMapComponentType)); + this.getEntityStoreRegistry().registerSystem(new EntityStatsSystems.EntityTrackerRemove(this.entityStatMapComponentType)); + this.getEntityStoreRegistry().registerSystem(new EntityStatsSystems.Changes(this.entityStatMapComponentType)); + this.getEntityStoreRegistry().registerSystem(new EntityStatsSystems.ClearChanges(this.entityStatMapComponentType)); + this.getEventRegistry().register(LoadedAssetsEvent.class, Item.class, x$0 -> onLoadedAssetsInvalidate(x$0)); + this.getEventRegistry().register(LoadedAssetsEvent.class, EntityEffect.class, x$0 -> onLoadedAssetsInvalidate(x$0)); + } + + @Override + protected void start() { + DefaultEntityStatTypes.update(); + if (DefaultEntityStatTypes.getHealth() == Integer.MIN_VALUE + || DefaultEntityStatTypes.getOxygen() == Integer.MIN_VALUE + || DefaultEntityStatTypes.getMana() == Integer.MIN_VALUE + || DefaultEntityStatTypes.getStamina() == Integer.MIN_VALUE + || DefaultEntityStatTypes.getSignatureEnergy() == Integer.MIN_VALUE + || DefaultEntityStatTypes.getAmmo() == Integer.MIN_VALUE) { + throw new IllegalStateException("Missing default EntityStatType"); + } + } + + @Nullable + @Deprecated(forRemoval = true) + public static EntityStatMap get(@Nonnull Entity entity) { + Ref ref = entity.getReference(); + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + return store.getComponent(ref, get().getEntityStatMapComponentType()); + } else { + return null; + } + } + + private void onLoadedAssetsEvent(LoadedAssetsEvent> event) { + DefaultEntityStatTypes.update(); + Universe.get() + .getWorlds() + .forEach( + (s, world) -> world.execute( + () -> { + Store store = world.getEntityStore().getStore(); + store.forEachEntityParallel( + EntityStatMap.getComponentType(), + (index, archetypeChunk, commandBuffer) -> archetypeChunk.getComponent(index, EntityStatMap.getComponentType()).update() + ); + } + ) + ); + } + + private static , M extends AssetMap> void onLoadedAssetsInvalidate(LoadedAssetsEvent event) { + Universe.get().getWorlds().forEach((s, world) -> world.execute(() -> { + Store store = world.getEntityStore().getStore(); + store.forEachEntityParallel(AllLegacyLivingEntityTypesQuery.INSTANCE, (index, archetypeChunk, commandBuffer) -> { + LivingEntity livingEntity = (LivingEntity)EntityUtils.getEntity(index, archetypeChunk); + + assert livingEntity != null; + + livingEntity.getStatModifiersManager().setRecalculate(true); + }); + })); + } + + @Nullable + public static Int2FloatMap resolveEntityStats(@Nullable Object2FloatMap raw) { + if (raw != null && !raw.isEmpty()) { + Int2FloatMap out = null; + + for (Entry entry : raw.object2FloatEntrySet()) { + int index = EntityStatType.getAssetMap().getIndex(entry.getKey()); + if (index != Integer.MIN_VALUE) { + if (out == null) { + out = new Int2FloatOpenHashMap(); + } + + out.put(index, entry.getFloatValue()); + } + } + + return out; + } else { + return null; + } + } + + @Nullable + public static Int2ObjectMap resolveEntityStats(@Nullable Map raw) { + if (raw != null && !raw.isEmpty()) { + Int2ObjectMap out = null; + + for (java.util.Map.Entry entry : raw.entrySet()) { + int index = EntityStatType.getAssetMap().getIndex(entry.getKey()); + if (index != Integer.MIN_VALUE) { + if (out == null) { + out = new Int2ObjectOpenHashMap<>(); + } + + out.put(index, entry.getValue()); + } + } + + return out; + } else { + return null; + } + } + + @Nullable + public static int[] resolveEntityStats(@Nullable String[] raw) { + if (Arrays.isNullOrEmpty((Object[])raw)) { + return null; + } else { + int[] out = new int[raw.length]; + int size = 0; + + for (int i = 0; i < raw.length; i++) { + int index = EntityStatType.getAssetMap().getIndex(raw[i]); + if (index != Integer.MIN_VALUE) { + out[size++] = index; + } + } + + if (size != raw.length) { + out = Arrays.copyOf(out, size); + } + + return out; + } + } + + public ComponentType getEntityStatMapComponentType() { + return this.entityStatMapComponentType; + } + + public SystemType getStatModifyingSystemType() { + return this.statModifyingSystemType; + } + + public class PlayerRegenerateStatsSystem extends EntityStatsSystems.Regenerate { + public PlayerRegenerateStatsSystem() { + super(EntityStatsModule.this.entityStatMapComponentType, Player.getComponentType()); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/EntityStatsSystems.java b/src/com/hypixel/hytale/server/core/modules/entitystats/EntityStatsSystems.java new file mode 100644 index 0000000..1430f1b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/EntityStatsSystems.java @@ -0,0 +1,548 @@ +package com.hypixel.hytale.server.core.modules.entitystats; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.dependency.SystemTypeDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.ISystem; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.protocol.EntityStatUpdate; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.modules.entity.AllLegacyLivingEntityTypesQuery; +import com.hypixel.hytale.server.core.modules.entity.component.Invulnerable; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.modules.entitystats.asset.DefaultEntityStatTypes; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.floats.FloatList; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityStatsSystems { + public EntityStatsSystems() { + } + + public static class Changes extends EntityTickingSystem { + private final ComponentType componentType; + @Nonnull + private final Query query; + private final Set> dependencies = Set.of( + new SystemDependency<>(Order.BEFORE, EntityStatsSystems.EntityTrackerUpdate.class), + new SystemTypeDependency(Order.AFTER, EntityStatsModule.get().getStatModifyingSystemType()) + ); + + public Changes(ComponentType componentType) { + this.componentType = componentType; + this.query = Query.and(componentType, InteractionModule.get().getInteractionManagerComponent(), AllLegacyLivingEntityTypesQuery.INSTANCE); + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return false; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Ref ref = archetypeChunk.getReferenceTo(index); + EntityStatMap entityStatMapComponent = archetypeChunk.getComponent(index, this.componentType); + + assert entityStatMapComponent != null; + + InteractionManager interactionManagerComponent = archetypeChunk.getComponent(index, InteractionModule.get().getInteractionManagerComponent()); + + assert interactionManagerComponent != null; + + boolean isDead = archetypeChunk.getArchetype().contains(DeathComponent.getComponentType()); + Int2ObjectMap> statChanges = entityStatMapComponent.getSelfUpdates(); + Int2ObjectMap statValues = entityStatMapComponent.getSelfStatValues(); + + for (int statIndex = 0; statIndex < entityStatMapComponent.size(); statIndex++) { + List updates = statChanges.get(statIndex); + if (updates != null && !updates.isEmpty()) { + FloatList statChangeList = statValues.get(statIndex); + EntityStatValue entityStatValue = entityStatMapComponent.get(statIndex); + if (entityStatValue != null) { + EntityStatType entityStatType = EntityStatType.getAssetMap().getAsset(statIndex); + + for (int i = 0; i < updates.size(); i++) { + EntityStatUpdate update = updates.get(i); + float statPrevious = statChangeList.getFloat(i * 2); + float statValue = statChangeList.getFloat(i * 2 + 1); + if (testMaxValue(statValue, statPrevious, entityStatValue, entityStatType.getMaxValueEffects())) { + runInteractions(ref, interactionManagerComponent, entityStatType.getMaxValueEffects(), commandBuffer); + } + + if (testMinValue(statValue, statPrevious, entityStatValue, entityStatType.getMinValueEffects())) { + runInteractions(ref, interactionManagerComponent, entityStatType.getMinValueEffects(), commandBuffer); + } + + if (!isDead && statIndex == DefaultEntityStatTypes.getHealth() && !(update.value > 0.0F) && statValue <= entityStatValue.getMin()) { + DeathComponent.tryAddComponent( + commandBuffer, archetypeChunk.getReferenceTo(index), new Damage(Damage.NULL_SOURCE, DamageCause.COMMAND, 0.0F) + ); + isDead = true; + } + } + } + } + } + } + + private static boolean testMaxValue( + float value, float previousValue, @Nonnull EntityStatValue stat, @Nullable EntityStatType.EntityStatEffects valueEffects + ) { + if (valueEffects == null) { + return false; + } else { + return valueEffects.triggerAtZero() && stat.getMax() > 0.0F + ? previousValue < 0.0F && value >= 0.0F + : previousValue != stat.getMax() && value == stat.getMax(); + } + } + + private static boolean testMinValue( + float value, float previousValue, @Nonnull EntityStatValue stat, @Nullable EntityStatType.EntityStatEffects valueEffects + ) { + if (valueEffects == null) { + return false; + } else { + return valueEffects.triggerAtZero() && stat.getMin() < 0.0F + ? previousValue > 0.0F && value < 0.0F + : previousValue != stat.getMin() && value == stat.getMin(); + } + } + + private static void runInteractions( + @Nonnull Ref ref, + @Nonnull InteractionManager interactionManager, + @Nullable EntityStatType.EntityStatEffects valueEffects, + @Nonnull ComponentAccessor componentAccessor + ) { + if (valueEffects != null) { + String interactions = valueEffects.getInteractions(); + if (interactions != null) { + InteractionContext context = InteractionContext.forInteraction(interactionManager, ref, InteractionType.EntityStatEffect, componentAccessor); + InteractionChain chain = interactionManager.initChain( + InteractionType.EntityStatEffect, context, RootInteraction.getRootInteractionOrUnknown(interactions), true + ); + interactionManager.queueExecuteChain(chain); + } + } + } + } + + public static class ClearChanges extends EntityTickingSystem { + private final ComponentType componentType; + private final Set> dependencies = Set.of(new SystemDependency<>(Order.AFTER, EntityStatsSystems.EntityTrackerUpdate.class)); + + public ClearChanges(ComponentType componentType) { + this.componentType = componentType; + } + + @Override + public Query getQuery() { + return this.componentType; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityStatMap statMap = archetypeChunk.getComponent(index, this.componentType); + statMap.clearUpdates(); + } + } + + public static class EntityTrackerRemove extends RefChangeSystem { + private final ComponentType componentType; + private final ComponentType visibleComponentType = EntityTrackerSystems.Visible.getComponentType(); + @Nonnull + private final Query query; + + public EntityTrackerRemove(ComponentType componentType) { + this.componentType = componentType; + this.query = Query.and(this.visibleComponentType, componentType); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public ComponentType componentType() { + return this.componentType; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull EntityStatMap component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentSet( + @Nonnull Ref ref, + EntityStatMap oldComponent, + @Nonnull EntityStatMap newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull EntityStatMap component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + for (EntityTrackerSystems.EntityViewer viewer : store.getComponent(ref, this.visibleComponentType).visibleTo.values()) { + viewer.queueRemove(ref, ComponentUpdateType.EntityStats); + } + } + } + + public static class EntityTrackerUpdate extends EntityTickingSystem { + private final ComponentType componentType; + private final ComponentType visibleComponentType = EntityTrackerSystems.Visible.getComponentType(); + @Nonnull + private final Query query; + private final Set> dependencies = Set.of( + new SystemDependency<>(Order.BEFORE, EntityTrackerSystems.EffectControllerSystem.class), + new SystemTypeDependency(Order.AFTER, EntityStatsModule.get().getStatModifyingSystemType()) + ); + + public EntityTrackerUpdate(ComponentType componentType) { + this.componentType = componentType; + this.query = Query.and(this.visibleComponentType, componentType); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Ref ref = archetypeChunk.getReferenceTo(index); + EntityTrackerSystems.Visible visible = archetypeChunk.getComponent(index, this.visibleComponentType); + EntityStatMap statMap = archetypeChunk.getComponent(index, this.componentType); + if (!visible.newlyVisibleTo.isEmpty()) { + queueUpdatesForNewlyVisible(ref, statMap, visible.newlyVisibleTo); + } + + if (statMap.consumeSelfNetworkOutdated()) { + EntityTrackerSystems.EntityViewer selfEntityViewer = visible.visibleTo.get(ref); + if (selfEntityViewer != null && !visible.newlyVisibleTo.containsKey(ref)) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.EntityStats; + update.entityStatUpdates = statMap.consumeSelfUpdates(); + selfEntityViewer.queueUpdate(ref, update); + } + } + + if (statMap.consumeNetworkOutdated()) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.EntityStats; + update.entityStatUpdates = statMap.consumeOtherUpdates(); + + for (Entry, EntityTrackerSystems.EntityViewer> entry : visible.visibleTo.entrySet()) { + Ref viewerRef = entry.getKey(); + if (!visible.newlyVisibleTo.containsKey(viewerRef) && !ref.equals(viewerRef)) { + entry.getValue().queueUpdate(ref, update); + } + } + } + } + + private static void queueUpdatesForNewlyVisible( + @Nonnull Ref ref, @Nonnull EntityStatMap statMap, @Nonnull Map, EntityTrackerSystems.EntityViewer> newlyVisibleTo + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.EntityStats; + update.entityStatUpdates = statMap.createInitUpdate(false); + + for (Entry, EntityTrackerSystems.EntityViewer> entry : newlyVisibleTo.entrySet()) { + if (ref.equals(entry.getKey())) { + queueUpdateForNewlyVisibleSelf(ref, statMap, entry.getValue()); + } else { + entry.getValue().queueUpdate(ref, update); + } + } + } + + private static void queueUpdateForNewlyVisibleSelf( + Ref ref, @Nonnull EntityStatMap statMap, @Nonnull EntityTrackerSystems.EntityViewer viewer + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.EntityStats; + update.entityStatUpdates = statMap.createInitUpdate(true); + viewer.queueUpdate(ref, update); + } + } + + public static class Recalculate extends EntityTickingSystem { + @Nonnull + private final ComponentType entityStatMapComponentType; + + public Recalculate(@Nonnull ComponentType entityStatMapComponentType) { + this.entityStatMapComponentType = entityStatMapComponentType; + } + + @Nonnull + @Override + public Query getQuery() { + return AllLegacyLivingEntityTypesQuery.INSTANCE; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + LivingEntity livingEntity = (LivingEntity)EntityUtils.getEntity(index, archetypeChunk); + + assert livingEntity != null; + + EntityStatMap entityStatMapComponent = archetypeChunk.getComponent(index, this.entityStatMapComponentType); + + assert entityStatMapComponent != null; + + Ref ref = archetypeChunk.getReferenceTo(index); + livingEntity.getStatModifiersManager().recalculateEntityStatModifiers(ref, entityStatMapComponent, commandBuffer); + } + } + + public static class Regenerate extends EntityTickingSystem implements EntityStatsSystems.StatModifyingSystem { + private final ComponentType componentType; + private final ComponentType entityTypeComponent; + private final Query query; + + public Regenerate(ComponentType componentType, ComponentType entityTypeComponent) { + this.componentType = componentType; + this.entityTypeComponent = entityTypeComponent; + this.query = Query.and(componentType, entityTypeComponent); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return false; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Ref ref = archetypeChunk.getReferenceTo(index); + EntityStatMap map = archetypeChunk.getComponent(index, this.componentType); + + assert map != null; + + Instant now = store.getResource(TimeResource.getResourceType()).getNow(); + int size = map.size(); + if (map.tempRegenerationValues.length < size) { + map.tempRegenerationValues = new float[size]; + } + + for (int statIndex = 1; statIndex < size; statIndex++) { + EntityStatValue value = map.get(statIndex); + if (value != null) { + map.tempRegenerationValues[statIndex] = 0.0F; + RegeneratingValue[] regenerating = value.getRegeneratingValues(); + if (regenerating != null) { + for (RegeneratingValue regeneratingValue : regenerating) { + if (regeneratingValue.getRegenerating().getAmount() > 0.0F ? !(value.get() >= value.getMax()) : !(value.get() <= value.getMin())) { + map.tempRegenerationValues[statIndex] = map.tempRegenerationValues[statIndex] + + regeneratingValue.regenerate(commandBuffer, ref, now, dt, value, map.tempRegenerationValues[statIndex]); + } + } + } + } + } + + EntityType entity = archetypeChunk.getComponent(index, this.entityTypeComponent); + + assert entity != null; + + ItemContainer armorContainer = entity.getInventory().getArmor(); + short armorContainerCapacity = armorContainer.getCapacity(); + + for (short i = 0; i < armorContainerCapacity; i++) { + ItemStack itemStack = armorContainer.getItemStack(i); + if (!ItemStack.isEmpty(itemStack)) { + Item item = itemStack.getItem(); + if (item.getArmor() != null && item.getArmor().getRegeneratingValues() != null && !item.getArmor().getRegeneratingValues().isEmpty()) { + for (int statIndexx = 1; statIndexx < size; statIndexx++) { + EntityStatValue value = map.get(statIndexx); + if (value != null) { + List regenValues = item.getArmor().getRegeneratingValues().get(statIndexx); + if (regenValues != null && !regenValues.isEmpty()) { + for (RegeneratingValue regeneratingValuex : regenValues) { + if (regeneratingValuex.getRegenerating().getAmount() > 0.0F ? !(value.get() >= value.getMax()) : !(value.get() <= value.getMin()) + ) + { + map.tempRegenerationValues[statIndexx] = map.tempRegenerationValues[statIndexx] + + regeneratingValuex.regenerate(commandBuffer, ref, now, dt, value, map.tempRegenerationValues[statIndexx]); + } + } + } + } + } + } + } + } + + for (int statIndexxx = 1; statIndexxx < size; statIndexxx++) { + EntityStatValue value = map.get(statIndexxx); + if (value != null) { + float amount = map.tempRegenerationValues[statIndexxx]; + boolean invulnerable = commandBuffer.getArchetype(ref).contains(Invulnerable.getComponentType()); + if (amount < 0.0F && !value.getIgnoreInvulnerability() && invulnerable) { + return; + } + + if (amount != 0.0F) { + map.addStatValue(statIndexxx, amount); + } + } + } + } + } + + public static class Setup extends HolderSystem { + private final ComponentType componentType; + + public Setup(ComponentType componentType) { + this.componentType = componentType; + } + + @Nonnull + @Override + public Query getQuery() { + return AllLegacyLivingEntityTypesQuery.INSTANCE; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + EntityStatMap stats = holder.getComponent(this.componentType); + if (stats == null) { + stats = holder.ensureAndGetComponent(this.componentType); + stats.update(); + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + } + + public interface StatModifyingSystem extends ISystem { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/RegeneratingValue.java b/src/com/hypixel/hytale/server/core/modules/entitystats/RegeneratingValue.java new file mode 100644 index 0000000..2060340 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/RegeneratingValue.java @@ -0,0 +1,71 @@ +package com.hypixel.hytale.server.core.modules.entitystats; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.entitystats.asset.condition.Condition; +import com.hypixel.hytale.server.core.modules.entitystats.asset.modifier.RegeneratingModifier; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class RegeneratingValue { + @Nonnull + private final EntityStatType.Regenerating regenerating; + private float remainingUntilRegen; + + public RegeneratingValue(@Nonnull EntityStatType.Regenerating regenerating) { + this.regenerating = regenerating; + } + + public boolean shouldRegenerate( + @Nonnull ComponentAccessor store, + @Nonnull Ref ref, + @Nonnull Instant currentTime, + float dt, + @Nonnull EntityStatType.Regenerating regenerating + ) { + this.remainingUntilRegen -= dt; + if (this.remainingUntilRegen < 0.0F) { + this.remainingUntilRegen = this.remainingUntilRegen + regenerating.getInterval(); + return Condition.allConditionsMet(store, ref, currentTime, regenerating); + } else { + return false; + } + } + + public float regenerate( + @Nonnull ComponentAccessor store, + @Nonnull Ref ref, + @Nonnull Instant currentTime, + float dt, + @Nonnull EntityStatValue value, + float currentAmount + ) { + if (!this.shouldRegenerate(store, ref, currentTime, dt, this.regenerating)) { + return 0.0F; + } else { + float toAdd = switch (this.regenerating.getRegenType()) { + case ADDITIVE -> this.regenerating.getAmount(); + case PERCENTAGE -> this.regenerating.getAmount() * (value.getMax() - value.getMin()); + }; + if (this.regenerating.getModifiers() != null) { + for (RegeneratingModifier modifier : this.regenerating.getModifiers()) { + toAdd *= modifier.getModifier(store, ref, currentTime); + } + } + + return this.regenerating.clampAmount(toAdd, currentAmount, value); + } + } + + public EntityStatType.Regenerating getRegenerating() { + return this.regenerating; + } + + @Nonnull + @Override + public String toString() { + return "RegeneratingValue{regenerating=" + this.regenerating + ", remainingUntilRegen=" + this.remainingUntilRegen + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/DefaultEntityStatTypes.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/DefaultEntityStatTypes.java new file mode 100644 index 0000000..8dfbf50 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/DefaultEntityStatTypes.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; + +public abstract class DefaultEntityStatTypes { + private static int HEALTH; + private static int OXYGEN; + private static int STAMINA; + private static int MANA; + private static int SIGNATURE_ENERGY; + private static int AMMO; + + public static int getHealth() { + return HEALTH; + } + + public static int getOxygen() { + return OXYGEN; + } + + public static int getStamina() { + return STAMINA; + } + + public static int getMana() { + return MANA; + } + + public static int getSignatureEnergy() { + return SIGNATURE_ENERGY; + } + + public static int getAmmo() { + return AMMO; + } + + private DefaultEntityStatTypes() { + } + + public static void update() { + IndexedLookupTableAssetMap assetMap = EntityStatType.getAssetMap(); + HEALTH = assetMap.getIndex("Health"); + OXYGEN = assetMap.getIndex("Oxygen"); + STAMINA = assetMap.getIndex("Stamina"); + MANA = assetMap.getIndex("Mana"); + SIGNATURE_ENERGY = assetMap.getIndex("SignatureEnergy"); + AMMO = assetMap.getIndex("Ammo"); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/EntityStatType.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/EntityStatType.java new file mode 100644 index 0000000..efa3ae7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/EntityStatType.java @@ -0,0 +1,476 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.EntityStatResetBehavior; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelParticle; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue; +import com.hypixel.hytale.server.core.modules.entitystats.asset.condition.Condition; +import com.hypixel.hytale.server.core.modules.entitystats.asset.modifier.RegeneratingModifier; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import java.lang.ref.SoftReference; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityStatType + implements JsonAssetWithMap>, + NetworkSerializable { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + EntityStatType.class, + EntityStatType::new, + Codec.STRING, + (statType, s) -> statType.id = s, + statType -> statType.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("InitialValue", Codec.FLOAT), + (asset, o) -> asset.initialValue = o, + asset -> asset.initialValue, + (asset, parent) -> asset.initialValue = parent.initialValue + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Min", Codec.FLOAT), (asset, o) -> asset.min = o, asset -> asset.min, (asset, parent) -> asset.min = parent.min) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Max", Codec.FLOAT), (asset, o) -> asset.max = o, asset -> asset.max, (asset, parent) -> asset.max = parent.max) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Shared", Codec.BOOLEAN), (asset, o) -> asset.shared = o, asset -> asset.shared, (asset, parent) -> asset.shared = parent.shared + ) + .add() + .appendInherited( + new KeyedCodec<>("Regenerating", new ArrayCodec<>(EntityStatType.Regenerating.CODEC, EntityStatType.Regenerating[]::new)), + (asset, o) -> asset.regenerating = o, + asset -> asset.regenerating, + (asset, parent) -> asset.regenerating = parent.regenerating + ) + .add() + .appendInherited( + new KeyedCodec<>("MinValueEffects", EntityStatType.EntityStatEffects.CODEC), + (entityStatType, entityStatEffects) -> entityStatType.minValueEffects = entityStatEffects, + entityStatType -> entityStatType.minValueEffects, + (entityStatType, parent) -> entityStatType.minValueEffects = parent.minValueEffects + ) + .add() + .appendInherited( + new KeyedCodec<>("MaxValueEffects", EntityStatType.EntityStatEffects.CODEC), + (entityStatType, entityStatEffects) -> entityStatType.maxValueEffects = entityStatEffects, + entityStatType -> entityStatType.maxValueEffects, + (entityStatType, parent) -> entityStatType.maxValueEffects = parent.maxValueEffects + ) + .add() + .appendInherited( + new KeyedCodec<>("ResetType", new EnumCodec<>(EntityStatResetBehavior.class)), + (entityStatType, value) -> entityStatType.resetBehavior = value, + entityStatType -> entityStatType.resetBehavior, + (entityStatType, parent) -> entityStatType.resetBehavior = parent.resetBehavior + ) + .add() + .appendInherited( + new KeyedCodec<>("IgnoreInvulnerability", Codec.BOOLEAN), + (entityStatType, value) -> entityStatType.ignoreInvulnerability = value, + entityStatType -> entityStatType.ignoreInvulnerability, + (entityStatType, parent) -> entityStatType.ignoreInvulnerability = parent.ignoreInvulnerability + ) + .add() + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(EntityStatType::getAssetStore)); + private static AssetStore> ASSET_STORE; + public static final EntityStatType UNKNOWN = getUnknownFor("Unknown"); + public static final int UNKNOWN_ID = 0; + protected String id; + protected AssetExtraInfo.Data data; + protected boolean unknown; + protected float initialValue; + protected float min; + protected float max; + protected boolean shared = false; + @Nullable + protected EntityStatType.Regenerating[] regenerating; + protected boolean ignoreInvulnerability; + protected EntityStatType.EntityStatEffects minValueEffects; + protected EntityStatType.EntityStatEffects maxValueEffects; + protected EntityStatResetBehavior resetBehavior = EntityStatResetBehavior.InitialValue; + private transient SoftReference cachedPacket; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(EntityStatType.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + protected EntityStatType() { + } + + public EntityStatType( + String id, + int initialValue, + int min, + int max, + boolean shared, + @Nullable EntityStatType.Regenerating[] regenerating, + EntityStatType.EntityStatEffects minValueEffects, + EntityStatType.EntityStatEffects maxValueEffects, + EntityStatResetBehavior entityStatResetBehavior + ) { + this.id = id; + this.initialValue = initialValue; + this.min = min; + this.max = max; + this.shared = shared; + this.regenerating = regenerating; + this.minValueEffects = minValueEffects; + this.maxValueEffects = maxValueEffects; + this.resetBehavior = entityStatResetBehavior; + } + + public String getId() { + return this.id; + } + + public boolean isUnknown() { + return this.unknown; + } + + public float getInitialValue() { + return this.initialValue; + } + + public float getMin() { + return this.min; + } + + public float getMax() { + return this.max; + } + + public boolean getIgnoreInvulnerability() { + return this.ignoreInvulnerability; + } + + public boolean isShared() { + return this.shared; + } + + public EntityStatType.EntityStatEffects getMinValueEffects() { + return this.minValueEffects; + } + + public EntityStatType.EntityStatEffects getMaxValueEffects() { + return this.maxValueEffects; + } + + @Nullable + public EntityStatType.Regenerating[] getRegenerating() { + return this.regenerating; + } + + public EntityStatResetBehavior getResetBehavior() { + return this.resetBehavior; + } + + @Nonnull + public com.hypixel.hytale.protocol.EntityStatType toPacket() { + com.hypixel.hytale.protocol.EntityStatType cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.EntityStatType packet = new com.hypixel.hytale.protocol.EntityStatType(); + packet.id = this.id; + packet.value = this.initialValue; + packet.min = this.min; + packet.max = this.max; + if (this.minValueEffects != null) { + packet.minValueEffects = this.minValueEffects.toPacket(); + } + + if (this.maxValueEffects != null) { + packet.maxValueEffects = this.maxValueEffects.toPacket(); + } + + packet.resetBehavior = this.resetBehavior; + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + @Nonnull + @Override + public String toString() { + return "EntityStatType{id='" + + this.id + + "', data=" + + this.data + + ", unknown=" + + this.unknown + + ", initialValue=" + + this.initialValue + + ", min=" + + this.min + + ", max=" + + this.max + + ", shared=" + + this.shared + + ", regenerating=" + + Arrays.toString((Object[])this.regenerating) + + ", minValueEffects=" + + this.minValueEffects + + ", maxValueEffects=" + + this.maxValueEffects + + ", resetBehavior=" + + this.resetBehavior + + ", ignoreInvulnerability=" + + this.ignoreInvulnerability + + "}"; + } + + @Nonnull + public static EntityStatType getUnknownFor(final String unknownId) { + return new EntityStatType() { + { + this.id = unknownId; + this.unknown = true; + } + }; + } + + public static class EntityStatEffects implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder( + EntityStatType.EntityStatEffects.class, EntityStatType.EntityStatEffects::new + ) + .append( + new KeyedCodec<>("TriggerAtZero", Codec.BOOLEAN), + (entityStatEffects, f) -> entityStatEffects.triggerAtZero = f, + entityStatEffects -> entityStatEffects.triggerAtZero + ) + .documentation("Indicates that the effects should trigger when the stat reaches zero") + .add() + .append( + new KeyedCodec<>("SoundEventId", Codec.STRING), + (entityStatEffects, s) -> entityStatEffects.soundEventId = s, + entityStatEffects -> entityStatEffects.soundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .add() + .append( + new KeyedCodec<>("Particles", ModelParticle.ARRAY_CODEC), + (entityStatEffects, modelParticles) -> entityStatEffects.particles = modelParticles, + entityStatEffects -> entityStatEffects.particles + ) + .add() + .append( + new KeyedCodec<>("Interactions", RootInteraction.CHILD_ASSET_CODEC), + (entityStatEffects, interactions) -> entityStatEffects.interactions = interactions, + entityStatEffects -> entityStatEffects.interactions + ) + .addValidatorLate(() -> RootInteraction.VALIDATOR_CACHE.getValidator().late()) + .add() + .afterDecode(entityStatEffects -> { + String soundEventId = entityStatEffects.soundEventId; + if (soundEventId != null) { + entityStatEffects.soundEventIndex = SoundEvent.getAssetMap().getIndex(soundEventId); + } + }) + .build(); + private boolean triggerAtZero; + @Nullable + private String soundEventId; + private int soundEventIndex; + private ModelParticle[] particles; + private String interactions; + + public EntityStatEffects(@Nullable String soundEventId, ModelParticle[] particles, String interactions) { + this.soundEventId = soundEventId; + if (soundEventId != null) { + this.soundEventIndex = SoundEvent.getAssetMap().getIndex(soundEventId); + } + + this.particles = particles; + this.interactions = interactions; + } + + protected EntityStatEffects() { + } + + @Nullable + public String getSoundEventId() { + return this.soundEventId; + } + + public int getSoundEventIndex() { + return this.soundEventIndex; + } + + public ModelParticle[] getParticles() { + return this.particles; + } + + public String getInteractions() { + return this.interactions; + } + + public boolean triggerAtZero() { + return this.triggerAtZero; + } + + @Nonnull + public com.hypixel.hytale.protocol.EntityStatEffects toPacket() { + com.hypixel.hytale.protocol.EntityStatEffects packet = new com.hypixel.hytale.protocol.EntityStatEffects(); + packet.soundEventIndex = this.soundEventIndex; + packet.triggerAtZero = this.triggerAtZero; + if (this.particles != null && this.particles.length > 0) { + packet.particles = new com.hypixel.hytale.protocol.ModelParticle[this.particles.length]; + + for (int i = 0; i < this.particles.length; i++) { + packet.particles[i] = this.particles[i].toPacket(); + } + } + + return packet; + } + + @Nonnull + @Override + public String toString() { + return "EntityStatEffects{soundEventId='" + + this.soundEventId + + "', particles=" + + Arrays.toString((Object[])this.particles) + + ", interactions=" + + this.interactions + + "}"; + } + } + + public static class Regenerating { + public static final BuilderCodec CODEC = BuilderCodec.builder( + EntityStatType.Regenerating.class, EntityStatType.Regenerating::new + ) + .addField(new KeyedCodec<>("Interval", Codec.FLOAT), (regenerating, value) -> regenerating.interval = value, regenerating -> regenerating.interval) + .addField(new KeyedCodec<>("Amount", Codec.FLOAT), (regenerating, value) -> regenerating.amount = value, regenerating -> regenerating.amount) + .append( + new KeyedCodec<>("ClampAtZero", Codec.BOOLEAN), (regenerating, value) -> regenerating.clampAtZero = value, regenerating -> regenerating.clampAtZero + ) + .documentation("Prevents this regenerating value from taking the stat value below zero.") + .add() + .addField( + new KeyedCodec<>("RegenType", new EnumCodec<>(EntityStatType.Regenerating.RegenType.class)), + (regenerating, value) -> regenerating.regenType = value, + regenerating -> regenerating.regenType + ) + .addField( + new KeyedCodec<>("Conditions", new ArrayCodec<>(Condition.CODEC, Condition[]::new)), + (regenerating, value) -> regenerating.conditions = value, + regenerating -> regenerating.conditions + ) + .addField( + new KeyedCodec<>("Modifiers", new ArrayCodec<>(RegeneratingModifier.CODEC, RegeneratingModifier[]::new)), + (regenerating, value) -> regenerating.modifiers = value, + regenerating -> regenerating.modifiers + ) + .build(); + private float interval; + private float amount; + private boolean clampAtZero; + private EntityStatType.Regenerating.RegenType regenType; + @Nullable + private Condition[] conditions; + private RegeneratingModifier[] modifiers; + + public Regenerating() { + } + + public Regenerating( + long interval, float amount, EntityStatType.Regenerating.RegenType regenType, @Nullable Condition[] conditions, RegeneratingModifier[] modifiers + ) { + this.interval = (float)interval; + this.amount = amount; + this.regenType = regenType; + this.conditions = conditions; + this.modifiers = modifiers; + } + + public float getInterval() { + return this.interval; + } + + public float getAmount() { + return this.amount; + } + + public float clampAmount(float toAdd, float currentAmount, @Nonnull EntityStatValue statValue) { + if (this.clampAtZero && !(toAdd > 0.0F)) { + if (statValue.getMin() >= 0.0F) { + return toAdd; + } else { + float resultValue = statValue.get() + currentAmount + toAdd; + return resultValue >= 0.0F ? toAdd : Math.min(0.0F, toAdd - resultValue); + } + } else { + return toAdd; + } + } + + public EntityStatType.Regenerating.RegenType getRegenType() { + return this.regenType; + } + + @Nullable + public Condition[] getConditions() { + return this.conditions; + } + + public RegeneratingModifier[] getModifiers() { + return this.modifiers; + } + + @Nonnull + @Override + public String toString() { + return "Regenerating{interval=" + + this.interval + + ", amount=" + + this.amount + + ", regenType=" + + this.regenType + + ", conditions=" + + Arrays.toString((Object[])this.conditions) + + ", modifiers=" + + Arrays.toString((Object[])this.modifiers) + + "}"; + } + + public static enum RegenType { + ADDITIVE, + PERCENTAGE; + + private RegenType() { + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/EntityStatTypePacketGenerator.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/EntityStatTypePacketGenerator.java new file mode 100644 index 0000000..e6123f9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/EntityStatTypePacketGenerator.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateEntityStatTypes; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class EntityStatTypePacketGenerator extends SimpleAssetPacketGenerator> { + public EntityStatTypePacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + UpdateEntityStatTypes packet = new UpdateEntityStatTypes(); + packet.type = UpdateType.Init; + packet.types = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.types.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateUpdatePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets) { + UpdateEntityStatTypes packet = new UpdateEntityStatTypes(); + packet.type = UpdateType.AddOrUpdate; + packet.types = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.types.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateRemovePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed) { + UpdateEntityStatTypes packet = new UpdateEntityStatTypes(); + packet.type = UpdateType.Remove; + packet.types = new Int2ObjectOpenHashMap<>(); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.types.put(index, null); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/AliveCondition.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/AliveCondition.java new file mode 100644 index 0000000..1336915 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/AliveCondition.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset.condition; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class AliveCondition extends Condition { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(AliveCondition.class, AliveCondition::new, Condition.BASE_CODEC).build(); + + protected AliveCondition() { + } + + public AliveCondition(boolean inverse) { + super(inverse); + } + + @Override + public boolean eval0(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, @Nonnull Instant currentTime) { + return !componentAccessor.getArchetype(ref).contains(DeathComponent.getComponentType()); + } + + @Nonnull + @Override + public String toString() { + return "AliveCondition{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/ChargingCondition.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/ChargingCondition.java new file mode 100644 index 0000000..7617c13 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/ChargingCondition.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset.condition; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.TimeUtil; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.damage.DamageDataComponent; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.ChargingInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Duration; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class ChargingCondition extends Condition { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(ChargingCondition.class, ChargingCondition::new, Condition.BASE_CODEC) + .append(new KeyedCodec<>("Delay", Codec.DURATION_SECONDS), (condition, value) -> condition.delay = value, condition -> condition.delay) + .documentation("The delay duration within which a recent charge is considered valid.") + .addValidator(Validators.nonNull()) + .add() + .build(); + protected Duration delay = Duration.ZERO; + + protected ChargingCondition() { + } + + @Override + public boolean eval0(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, @Nonnull Instant currentTime) { + InteractionManager interactionManager = componentAccessor.getComponent(ref, InteractionModule.get().getInteractionManagerComponent()); + Boolean result = interactionManager.forEachInteraction( + (chain, interaction, val) -> val ? Boolean.TRUE : interaction instanceof ChargingInteraction, Boolean.FALSE + ); + if (result) { + return true; + } else { + DamageDataComponent damageDataComponent = componentAccessor.getComponent(ref, DamageDataComponent.getComponentType()); + Instant timeInstant = damageDataComponent.getLastChargeTime(); + return timeInstant != null && TimeUtil.compareDifference(timeInstant, currentTime, this.delay) <= 0; + } + } + + @Nonnull + @Override + public String toString() { + return "ChargingCondition{delay=" + this.delay + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/Condition.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/Condition.java new file mode 100644 index 0000000..9b2e79d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/Condition.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset.condition; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import javax.annotation.Nonnull; + +public abstract class Condition { + @Nonnull + public static final CodecMapCodec CODEC = new CodecMapCodec<>(); + @Nonnull + protected static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(Condition.class) + .append(new KeyedCodec<>("Inverse", Codec.BOOLEAN), (regenerating, value) -> regenerating.inverse = value, regenerating -> regenerating.inverse) + .documentation("Determines whether the condition is inverted.") + .add() + .build(); + protected boolean inverse; + + protected Condition() { + this.inverse = false; + } + + public Condition(boolean inverse) { + this.inverse = inverse; + } + + public boolean eval(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, @Nonnull Instant currentTime) { + return this.inverse != this.eval0(componentAccessor, ref, currentTime); + } + + public abstract boolean eval0(@Nonnull ComponentAccessor var1, @Nonnull Ref var2, @Nonnull Instant var3); + + public static boolean allConditionsMet( + @Nonnull ComponentAccessor componentAccessor, + @Nonnull Ref ref, + @Nonnull Instant currentTime, + @Nonnull EntityStatType.Regenerating regenerating + ) { + return regenerating.getConditions() == null ? true : allConditionsMet(componentAccessor, ref, currentTime, regenerating.getConditions()); + } + + public static boolean allConditionsMet( + @Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, @Nonnull Instant currentTime, @Nonnull Condition[] conditions + ) { + boolean allMet = true; + + for (Condition condition : conditions) { + if (!condition.eval(componentAccessor, ref, currentTime)) { + allMet = false; + break; + } + } + + return allMet; + } + + @Nonnull + @Override + public String toString() { + return "Condition{inverse=" + this.inverse + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/EntityStatBoundCondition.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/EntityStatBoundCondition.java new file mode 100644 index 0000000..80a321f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/EntityStatBoundCondition.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset.condition; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import javax.annotation.Nonnull; + +public abstract class EntityStatBoundCondition extends Condition { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.abstractBuilder(EntityStatBoundCondition.class, Condition.BASE_CODEC) + .append(new KeyedCodec<>("Stat", Codec.STRING), (condition, value) -> condition.unknownStat = value, condition -> condition.unknownStat) + .documentation("The stat to evaluate the condition against.") + .addValidator(Validators.nonNull()) + .addValidatorLate(() -> EntityStatType.VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + protected String unknownStat; + protected int stat = Integer.MIN_VALUE; + + protected EntityStatBoundCondition() { + } + + public EntityStatBoundCondition(boolean inverse, int stat) { + super(inverse); + this.stat = stat; + } + + @Override + public boolean eval0(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, @Nonnull Instant currentTime) { + if (this.stat == Integer.MIN_VALUE) { + this.stat = EntityStatType.getAssetMap().getIndex(this.unknownStat); + } + + EntityStatMap entityStatMapComponent = componentAccessor.getComponent(ref, EntityStatsModule.get().getEntityStatMapComponentType()); + if (entityStatMapComponent == null) { + return false; + } else { + EntityStatValue statValue = entityStatMapComponent.get(this.stat); + return statValue == null ? false : this.eval0(ref, currentTime, statValue); + } + } + + public abstract boolean eval0(@Nonnull Ref var1, @Nonnull Instant var2, @Nonnull EntityStatValue var3); + + @Nonnull + @Override + public String toString() { + return "EntityStatBoundCondition{unknownStat='" + this.unknownStat + "', stat=" + this.stat + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/EnvironmentCondition.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/EnvironmentCondition.java new file mode 100644 index 0000000..4adaa63 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/EnvironmentCondition.java @@ -0,0 +1,89 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset.condition; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EnvironmentCondition extends Condition { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + EnvironmentCondition.class, EnvironmentCondition::new, Condition.BASE_CODEC + ) + .append( + new KeyedCodec<>("Environments", new ArrayCodec<>(Codec.STRING, String[]::new)), + (condition, value) -> condition.unknownEnvironments = value, + condition -> condition.unknownEnvironments + ) + .documentation("The environments to evaluate the condition against.") + .add() + .afterDecode(condition -> condition.environments = null) + .build(); + protected String[] unknownEnvironments; + @Nullable + protected int[] environments; + + protected EnvironmentCondition() { + } + + public int[] getEnvironments() { + if (this.environments == null && this.unknownEnvironments != null) { + this.environments = Arrays.stream(this.unknownEnvironments) + .mapToInt(environment -> Environment.getAssetMap().getIndex(environment)) + .sorted() + .toArray(); + } + + return this.environments; + } + + @Override + public boolean eval0(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, @Nonnull Instant currentTime) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + World world = componentAccessor.getExternalData().getWorld(); + ChunkStore chunkStore = world.getChunkStore(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(position.getX(), position.getZ()); + Ref chunkRef = chunkStore.getChunkReference(chunkIndex); + if (chunkRef != null && chunkRef.isValid()) { + Store chunkComponentStore = chunkStore.getStore(); + BlockChunk blockChunkComponent = chunkComponentStore.getComponent(chunkRef, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + int environmentId = blockChunkComponent.getEnvironment(position); + return Arrays.binarySearch(this.getEnvironments(), environmentId) >= 0; + } else { + return false; + } + } + + @Nonnull + @Override + public String toString() { + return "EnvironmentCondition{unknownEnvironments=" + + Arrays.toString((Object[])this.unknownEnvironments) + + ", environments=" + + Arrays.toString(this.environments) + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/GlidingCondition.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/GlidingCondition.java new file mode 100644 index 0000000..658b90b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/GlidingCondition.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset.condition; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class GlidingCondition extends Condition { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(GlidingCondition.class, GlidingCondition::new, Condition.BASE_CODEC).build(); + + protected GlidingCondition() { + } + + public GlidingCondition(boolean inverse) { + super(inverse); + } + + @Override + public boolean eval0(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, @Nonnull Instant currentTime) { + MovementStatesComponent movementStatesComponent = componentAccessor.getComponent(ref, MovementStatesComponent.getComponentType()); + + assert movementStatesComponent != null; + + return movementStatesComponent.getMovementStates().gliding; + } + + @Nonnull + @Override + public String toString() { + return "GlidingCondition{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/LogicCondition.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/LogicCondition.java new file mode 100644 index 0000000..903ffea --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/LogicCondition.java @@ -0,0 +1,95 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset.condition; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class LogicCondition extends Condition { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(LogicCondition.class, LogicCondition::new, Condition.BASE_CODEC) + .append( + new KeyedCodec<>("Operator", new EnumCodec<>(LogicCondition.Operator.class)), + (condition, value) -> condition.operator = value, + condition -> condition.operator + ) + .documentation("The logical operator to combine the conditions.") + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("Conditions", new ArrayCodec<>(Condition.CODEC, Condition[]::new)), + (condition, value) -> condition.conditions = value, + condition -> condition.conditions + ) + .documentation("The array of conditions to be evaluated.") + .addValidator(Validators.nonNull()) + .add() + .build(); + protected LogicCondition.Operator operator; + protected Condition[] conditions; + + protected LogicCondition() { + } + + public LogicCondition(boolean inverse, @Nonnull LogicCondition.Operator operator, @Nonnull Condition[] conditions) { + super(inverse); + this.operator = operator; + this.conditions = conditions; + } + + @Override + public boolean eval0(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, @Nonnull Instant currentTime) { + return this.operator.eval(componentAccessor, ref, currentTime, this.conditions); + } + + @Nonnull + @Override + public String toString() { + return "LogicCondition{operator=" + this.operator + ", conditions=" + Arrays.toString((Object[])this.conditions) + "} " + super.toString(); + } + + public static enum Operator { + AND { + @Override + public boolean eval( + @Nonnull ComponentAccessor store, @Nonnull Ref ref, @Nonnull Instant currentTime, @Nonnull Condition[] conditions + ) { + for (Condition condition : conditions) { + if (!condition.eval(store, ref, currentTime)) { + return false; + } + } + + return true; + } + }, + OR { + @Override + public boolean eval( + @Nonnull ComponentAccessor store, @Nonnull Ref ref, @Nonnull Instant currentTime, @Nonnull Condition[] conditions + ) { + for (Condition condition : conditions) { + if (condition.eval(store, ref, currentTime)) { + return true; + } + } + + return false; + } + }; + + private Operator() { + } + + public abstract boolean eval( + @Nonnull ComponentAccessor var1, @Nonnull Ref var2, @Nonnull Instant var3, @Nonnull Condition[] var4 + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/NoDamageTakenCondition.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/NoDamageTakenCondition.java new file mode 100644 index 0000000..3efd33c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/NoDamageTakenCondition.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset.condition; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.TimeUtil; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.damage.DamageDataComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Duration; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class NoDamageTakenCondition extends Condition { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + NoDamageTakenCondition.class, NoDamageTakenCondition::new, Condition.BASE_CODEC + ) + .append(new KeyedCodec<>("Delay", Codec.DURATION_SECONDS), (condition, value) -> condition.delay = value, condition -> condition.delay) + .documentation("The delay duration for the no damage taken condition.") + .addValidator(Validators.nonNull()) + .add() + .build(); + protected Duration delay; + + protected NoDamageTakenCondition() { + } + + @Override + public boolean eval0(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, @Nonnull Instant currentTime) { + DamageDataComponent damageDataComponent = componentAccessor.getComponent(ref, DamageDataComponent.getComponentType()); + + assert damageDataComponent != null; + + Instant lastDamageTime = damageDataComponent.getLastDamageTime(); + return TimeUtil.compareDifference(lastDamageTime, currentTime, this.delay) >= 0; + } + + @Nonnull + @Override + public String toString() { + return "NoDamageTakenCondition{delay=" + this.delay + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/OutOfCombatCondition.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/OutOfCombatCondition.java new file mode 100644 index 0000000..5f0d329 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/OutOfCombatCondition.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset.condition; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.TimeUtil; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.asset.type.gameplay.CombatConfig; +import com.hypixel.hytale.server.core.entity.damage.DamageDataComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Duration; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class OutOfCombatCondition extends Condition { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + OutOfCombatCondition.class, OutOfCombatCondition::new, Condition.BASE_CODEC + ) + .append(new KeyedCodec<>("DelaySeconds", Codec.DURATION_SECONDS), (condition, value) -> condition.delay = value, condition -> condition.delay) + .documentation("Delay before an entity is considered out of combat. Expressed in seconds.") + .add() + .build(); + protected Duration delay; + + protected OutOfCombatCondition() { + } + + @Override + public boolean eval0(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, @Nonnull Instant currentTime) { + World world = componentAccessor.getExternalData().getWorld(); + CombatConfig combatConfig = world.getGameplayConfig().getCombatConfig(); + Duration delayToUse = this.delay != null ? this.delay : combatConfig.getOutOfCombatDelay(); + DamageDataComponent damageDataComponent = componentAccessor.getComponent(ref, DamageDataComponent.getComponentType()); + + assert damageDataComponent != null; + + Instant lastCombatAction = damageDataComponent.getLastCombatAction(); + return TimeUtil.compareDifference(lastCombatAction, currentTime, delayToUse) >= 0; + } + + @Nonnull + @Override + public String toString() { + return "OutOfCombatCondition{delay=" + this.delay + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/PlayerCondition.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/PlayerCondition.java new file mode 100644 index 0000000..da3f2a7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/PlayerCondition.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset.condition; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerCondition extends Condition { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(PlayerCondition.class, PlayerCondition::new, Condition.BASE_CODEC) + .append( + new KeyedCodec<>("GameMode", new EnumCodec<>(GameMode.class)), + (condition, gameMode) -> condition.gameModeToCheck = gameMode, + condition -> condition.gameModeToCheck + ) + .documentation("The game mode to check for. If null, the condition always passes.") + .add() + .build(); + @Nullable + private GameMode gameModeToCheck; + + protected PlayerCondition() { + } + + @Override + public boolean eval0(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, @Nonnull Instant currentTime) { + if (this.gameModeToCheck == null) { + return true; + } else { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + return playerComponent == null ? false : playerComponent.getGameMode() == this.gameModeToCheck; + } + } + + @Nonnull + @Override + public String toString() { + return "PlayerCondition{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/RegenHealthCondition.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/RegenHealthCondition.java new file mode 100644 index 0000000..e774366 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/RegenHealthCondition.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset.condition; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class RegenHealthCondition extends Condition { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + RegenHealthCondition.class, RegenHealthCondition::new, Condition.BASE_CODEC + ) + .build(); + + protected RegenHealthCondition() { + } + + public RegenHealthCondition(boolean inverse) { + super(inverse); + } + + @Override + public boolean eval0(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, @Nonnull Instant currentTime) { + return true; + } + + @Nonnull + @Override + public String toString() { + return "RegenHealthCondition{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/SprintingCondition.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/SprintingCondition.java new file mode 100644 index 0000000..79227a9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/SprintingCondition.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset.condition; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class SprintingCondition extends Condition { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(SprintingCondition.class, SprintingCondition::new, Condition.BASE_CODEC) + .build(); + + protected SprintingCondition() { + } + + public SprintingCondition(boolean inverse) { + super(inverse); + } + + @Override + public boolean eval0(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, @Nonnull Instant currentTime) { + MovementStatesComponent movementStatesComponent = componentAccessor.getComponent(ref, MovementStatesComponent.getComponentType()); + + assert movementStatesComponent != null; + + return movementStatesComponent.getMovementStates().sprinting; + } + + @Nonnull + @Override + public String toString() { + return "SprintingCondition{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/StatCondition.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/StatCondition.java new file mode 100644 index 0000000..4ae3f15 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/StatCondition.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset.condition; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.function.predicate.BiFloatPredicate; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class StatCondition extends EntityStatBoundCondition { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(StatCondition.class, StatCondition::new, EntityStatBoundCondition.CODEC) + .append(new KeyedCodec<>("Amount", Codec.FLOAT), (condition, value) -> condition.amount = value, condition -> condition.amount) + .documentation("The amount to compare the entity's stat against.") + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Comparison", new EnumCodec<>(StatCondition.StatComparisonType.class)), + (condition, o) -> condition.comparison = o, + condition -> condition.comparison, + (condition, parent) -> condition.comparison = parent.comparison + ) + .documentation("The comparison type used for evaluating the stat condition.") + .add() + .build(); + protected StatCondition.StatComparisonType comparison = StatCondition.StatComparisonType.GTE; + protected float amount; + + protected StatCondition() { + } + + public StatCondition(boolean inverse, int stat, float amount) { + super(inverse, stat); + this.amount = amount; + } + + @Override + public boolean eval0(@Nonnull Ref ref, @Nonnull Instant currentTime, @Nonnull EntityStatValue statValue) { + return this.comparison.satisfies(statValue.get(), this.amount); + } + + @Nonnull + @Override + public String toString() { + return "StatCondition{amount=" + this.amount + "comparison=" + this.comparison.name() + "} " + super.toString(); + } + + public static enum StatComparisonType { + GTE(">=", (v1, v2) -> v1 >= v2), + GT(">", (v1, v2) -> v1 > v2), + LTE("<=", (v1, v2) -> v1 <= v2), + LT("<", (v1, v2) -> v1 < v2), + EQUAL("=", (v1, v2) -> v1 == v2); + + private final String prefix; + private final BiFloatPredicate satisfies; + + private StatComparisonType(String prefix, BiFloatPredicate satisfies) { + this.prefix = prefix; + this.satisfies = satisfies; + } + + public String getPrefix() { + return this.prefix; + } + + public boolean satisfies(float compareTo, float f) { + return this.satisfies.test(compareTo, f); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/SuffocatingCondition.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/SuffocatingCondition.java new file mode 100644 index 0000000..aef9b74 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/SuffocatingCondition.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset.condition; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.modules.collision.WorldUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class SuffocatingCondition extends Condition { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + SuffocatingCondition.class, SuffocatingCondition::new, Condition.BASE_CODEC + ) + .build(); + + protected SuffocatingCondition() { + } + + public SuffocatingCondition(boolean inverse) { + super(inverse); + } + + @Override + public boolean eval0(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, @Nonnull Instant currentTime) { + if (EntityUtils.getEntity(ref, componentAccessor) instanceof LivingEntity livingEntity) { + World world = componentAccessor.getExternalData().getWorld(); + Transform lookVec = TargetUtil.getLook(ref, componentAccessor); + Vector3d position = lookVec.getPosition(); + ChunkStore chunkStore = world.getChunkStore(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(position.x, position.z); + Ref chunkRef = chunkStore.getChunkReference(chunkIndex); + if (chunkRef != null && chunkRef.isValid()) { + long packed = WorldUtil.getPackedMaterialAndFluidAtPosition(chunkRef, chunkStore.getStore(), position.x, position.y, position.z); + BlockMaterial material = BlockMaterial.VALUES[MathUtil.unpackLeft(packed)]; + int fluidId = MathUtil.unpackRight(packed); + return !livingEntity.canBreathe(ref, material, fluidId, componentAccessor); + } else { + return false; + } + } else { + return false; + } + } + + @Nonnull + @Override + public String toString() { + return "SuffocatingCondition{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/WieldingCondition.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/WieldingCondition.java new file mode 100644 index 0000000..35bce17 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/condition/WieldingCondition.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset.condition; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.damage.DamageDataComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class WieldingCondition extends Condition { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(WieldingCondition.class, WieldingCondition::new, Condition.BASE_CODEC) + .build(); + + protected WieldingCondition() { + } + + public WieldingCondition(boolean inverse) { + super(inverse); + } + + @Override + public boolean eval0(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref ref, @Nonnull Instant currentTime) { + DamageDataComponent damageComponent = componentAccessor.getComponent(ref, DamageDataComponent.getComponentType()); + + assert damageComponent != null; + + return damageComponent.getCurrentWielding() != null; + } + + @Nonnull + @Override + public String toString() { + return "WieldingCondition{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/asset/modifier/RegeneratingModifier.java b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/modifier/RegeneratingModifier.java new file mode 100644 index 0000000..a4c2ec0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/asset/modifier/RegeneratingModifier.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.core.modules.entitystats.asset.modifier; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.modules.entitystats.asset.condition.Condition; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class RegeneratingModifier { + public static final BuilderCodec CODEC = BuilderCodec.builder(RegeneratingModifier.class, RegeneratingModifier::new) + .append( + new KeyedCodec<>("Conditions", new ArrayCodec<>(Condition.CODEC, Condition[]::new)), + (condition, value) -> condition.conditions = value, + condition -> condition.conditions + ) + .add() + .append(new KeyedCodec<>("Amount", Codec.FLOAT), (condition, value) -> condition.amount = value, condition -> condition.amount) + .add() + .build(); + protected Condition[] conditions; + protected float amount; + + protected RegeneratingModifier() { + } + + public RegeneratingModifier(Condition[] conditions, float amount) { + this.conditions = conditions; + this.amount = amount; + } + + public float getModifier(ComponentAccessor store, Ref ref, Instant currentTime) { + return Condition.allConditionsMet(store, ref, currentTime, this.conditions) ? this.amount : 1.0F; + } + + @Nonnull + @Override + public String toString() { + return "RegeneratingModifier{conditions=" + Arrays.toString((Object[])this.conditions) + ", amount=" + this.amount + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/modifier/DefaultModifiers.java b/src/com/hypixel/hytale/server/core/modules/entitystats/modifier/DefaultModifiers.java new file mode 100644 index 0000000..1a505fc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/modifier/DefaultModifiers.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.server.core.modules.entitystats.modifier; + +public interface DefaultModifiers { + String EFFECT = "Effect"; + String ARMOR = "Armor"; + String WEAPON_MODIFIER_PREFIX = "*Weapon_"; + String UTILITY_MODIFIER_PREFIX = "*Utility_"; +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/modifier/Modifier.java b/src/com/hypixel/hytale/server/core/modules/entitystats/modifier/Modifier.java new file mode 100644 index 0000000..856660d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/modifier/Modifier.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.server.core.modules.entitystats.modifier; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class Modifier implements NetworkSerializable { + public static final CodecMapCodec CODEC = new CodecMapCodec<>(); + protected static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(Modifier.class) + .append( + new KeyedCodec<>("Target", new EnumCodec<>(Modifier.ModifierTarget.class, EnumCodec.EnumStyle.LEGACY)), + (regenerating, value) -> regenerating.target = value, + regenerating -> regenerating.target + ) + .add() + .build(); + protected Modifier.ModifierTarget target = Modifier.ModifierTarget.MAX; + + public Modifier() { + } + + public Modifier(Modifier.ModifierTarget target) { + this.target = target; + } + + public abstract float apply(float var1); + + public Modifier.ModifierTarget getTarget() { + return this.target; + } + + @Nonnull + public com.hypixel.hytale.protocol.Modifier toPacket() { + if (!(this instanceof StaticModifier)) { + throw new UnsupportedOperationException("Only static modifiers supported on the client currently."); + } else { + com.hypixel.hytale.protocol.Modifier packet = new com.hypixel.hytale.protocol.Modifier(); + + packet.target = switch (this.target) { + case MIN -> com.hypixel.hytale.protocol.ModifierTarget.Min; + case MAX -> com.hypixel.hytale.protocol.ModifierTarget.Max; + }; + return packet; + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + Modifier modifier = (Modifier)o; + return this.target == modifier.target; + } else { + return false; + } + } + + @Override + public int hashCode() { + return this.target != null ? this.target.hashCode() : 0; + } + + @Nonnull + @Override + public String toString() { + return "Modifier{target=" + this.target + "}"; + } + + public static enum ModifierTarget { + MIN, + MAX; + + public static final Modifier.ModifierTarget[] VALUES = values(); + + private ModifierTarget() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entitystats/modifier/StaticModifier.java b/src/com/hypixel/hytale/server/core/modules/entitystats/modifier/StaticModifier.java new file mode 100644 index 0000000..40b1915 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entitystats/modifier/StaticModifier.java @@ -0,0 +1,131 @@ +package com.hypixel.hytale.server.core.modules.entitystats.modifier; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StaticModifier extends Modifier { + public static final BuilderCodec CODEC = BuilderCodec.builder(StaticModifier.class, StaticModifier::new, BASE_CODEC) + .append( + new KeyedCodec<>("CalculationType", new EnumCodec<>(StaticModifier.CalculationType.class)), + (modifier, value) -> modifier.calculationType = value, + modifier -> modifier.calculationType + ) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Amount", Codec.FLOAT), (modifier, value) -> modifier.amount = value, modifier -> modifier.amount) + .add() + .build(); + public static final BuilderCodec ENTITY_CODEC = BuilderCodec.builder(StaticModifier.class, StaticModifier::new) + .append( + new KeyedCodec<>("CalculationType", new EnumCodec<>(StaticModifier.CalculationType.class, EnumCodec.EnumStyle.LEGACY)), + (modifier, value) -> modifier.calculationType = value, + modifier -> modifier.calculationType + ) + .setVersionRange(0, 3) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("CalculationType", new EnumCodec<>(StaticModifier.CalculationType.class)), + (modifier, value) -> modifier.calculationType = value, + modifier -> modifier.calculationType + ) + .setVersionRange(4, 5) + .addValidator(Validators.nonNull()) + .add() + .addField(new KeyedCodec<>("Amount", Codec.FLOAT), (modifier, value) -> modifier.amount = value, modifier -> modifier.amount) + .build(); + protected StaticModifier.CalculationType calculationType; + protected float amount; + + protected StaticModifier() { + } + + public StaticModifier(Modifier.ModifierTarget target, StaticModifier.CalculationType calculationType, float amount) { + super(target); + this.calculationType = calculationType; + this.amount = amount; + } + + public StaticModifier.CalculationType getCalculationType() { + return this.calculationType; + } + + public float getAmount() { + return this.amount; + } + + @Override + public float apply(float statValue) { + return this.calculationType.compute(statValue, this.amount); + } + + @Nonnull + @Override + public com.hypixel.hytale.protocol.Modifier toPacket() { + com.hypixel.hytale.protocol.Modifier packet = super.toPacket(); + + packet.calculationType = switch (this.calculationType) { + case ADDITIVE -> com.hypixel.hytale.protocol.CalculationType.Additive; + case MULTIPLICATIVE -> com.hypixel.hytale.protocol.CalculationType.Multiplicative; + }; + packet.amount = this.amount; + return packet; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o == null || this.getClass() != o.getClass()) { + return false; + } else if (!super.equals(o)) { + return false; + } else { + StaticModifier that = (StaticModifier)o; + return Float.compare(that.amount, this.amount) != 0 ? false : this.calculationType == that.calculationType; + } + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (this.calculationType != null ? this.calculationType.hashCode() : 0); + return 31 * result + (this.amount != 0.0F ? Float.floatToIntBits(this.amount) : 0); + } + + @Nonnull + @Override + public String toString() { + return "StaticModifier{calculationType=" + this.calculationType + ", amount=" + this.amount + "} " + super.toString(); + } + + public static enum CalculationType { + ADDITIVE { + @Override + public float compute(float value, float amount) { + return value + amount; + } + }, + MULTIPLICATIVE { + @Override + public float compute(float value, float amount) { + return value * amount; + } + }; + + private CalculationType() { + } + + public abstract float compute(float var1, float var2); + + @Nonnull + public String createKey(String armor) { + return armor + "_" + this; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entityui/EntityUIModule.java b/src/com/hypixel/hytale/server/core/modules/entityui/EntityUIModule.java new file mode 100644 index 0000000..919e535 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entityui/EntityUIModule.java @@ -0,0 +1,91 @@ +package com.hypixel.hytale.server.core.modules.entityui; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.entityui.asset.CombatTextUIComponent; +import com.hypixel.hytale.server.core.modules.entityui.asset.CombatTextUIComponentAnimationEvent; +import com.hypixel.hytale.server.core.modules.entityui.asset.CombatTextUIComponentOpacityAnimationEvent; +import com.hypixel.hytale.server.core.modules.entityui.asset.CombatTextUIComponentPositionAnimationEvent; +import com.hypixel.hytale.server.core.modules.entityui.asset.CombatTextUIComponentScaleAnimationEvent; +import com.hypixel.hytale.server.core.modules.entityui.asset.EntityStatUIComponent; +import com.hypixel.hytale.server.core.modules.entityui.asset.EntityUIComponent; +import com.hypixel.hytale.server.core.modules.entityui.asset.EntityUIComponentPacketGenerator; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EntityUIModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(EntityUIModule.class).depends(EntityStatsModule.class).build(); + private static EntityUIModule instance; + private ComponentType uiComponentListType; + + public static EntityUIModule get() { + return instance; + } + + public EntityUIModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + public ComponentType getUIComponentListType() { + return this.uiComponentListType; + } + + @Override + protected void setup() { + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + EntityUIComponent.class, new IndexedLookupTableAssetMap<>(EntityUIComponent[]::new) + ) + .setPath("Entity/UI")) + .setCodec(EntityUIComponent.CODEC)) + .setKeyFunction(EntityUIComponent::getId)) + .setPacketGenerator(new EntityUIComponentPacketGenerator()) + .setReplaceOnRemove(EntityUIComponent::getUnknownFor)) + .loadsAfter(EntityStatType.class)) + .build() + ); + this.getCodecRegistry(EntityUIComponent.CODEC).register("EntityStat", EntityStatUIComponent.class, EntityStatUIComponent.CODEC); + this.getCodecRegistry(EntityUIComponent.CODEC).register("CombatText", CombatTextUIComponent.class, CombatTextUIComponent.CODEC); + this.getCodecRegistry(CombatTextUIComponentAnimationEvent.CODEC) + .register("Scale", CombatTextUIComponentScaleAnimationEvent.class, CombatTextUIComponentScaleAnimationEvent.CODEC); + this.getCodecRegistry(CombatTextUIComponentAnimationEvent.CODEC) + .register("Position", CombatTextUIComponentPositionAnimationEvent.class, CombatTextUIComponentPositionAnimationEvent.CODEC); + this.getCodecRegistry(CombatTextUIComponentAnimationEvent.CODEC) + .register("Opacity", CombatTextUIComponentOpacityAnimationEvent.class, CombatTextUIComponentOpacityAnimationEvent.CODEC); + this.uiComponentListType = this.getEntityStoreRegistry().registerComponent(UIComponentList.class, "UIComponentList", UIComponentList.CODEC); + ComponentType visibleComponentType = EntityModule.get().getVisibleComponentType(); + this.getEntityStoreRegistry().registerSystem(new UIComponentSystems.Setup(this.uiComponentListType)); + this.getEntityStoreRegistry().registerSystem(new UIComponentSystems.Update(visibleComponentType, this.uiComponentListType)); + this.getEntityStoreRegistry().registerSystem(new UIComponentSystems.Remove(visibleComponentType, this.uiComponentListType)); + this.getEventRegistry().register(LoadedAssetsEvent.class, EntityUIComponent.class, this::onLoadedAssetsEvent); + } + + private void onLoadedAssetsEvent(LoadedAssetsEvent> event) { + Universe.get() + .getWorlds() + .forEach( + (s, world) -> world.execute( + () -> { + Store store = world.getEntityStore().getStore(); + store.forEachEntityParallel( + UIComponentList.getComponentType(), + (index, archetypeChunk, commandBuffer) -> archetypeChunk.getComponent(index, UIComponentList.getComponentType()).update() + ); + } + ) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entityui/UIComponentList.java b/src/com/hypixel/hytale/server/core/modules/entityui/UIComponentList.java new file mode 100644 index 0000000..0a6e3b1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entityui/UIComponentList.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.core.modules.entityui; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entityui.asset.EntityUIComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class UIComponentList implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(UIComponentList.class, UIComponentList::new) + .append(new KeyedCodec<>("Components", Codec.STRING_ARRAY), (list, v) -> list.components = v, list -> list.components) + .add() + .afterDecode(list -> { + list.componentIds = ArrayUtil.EMPTY_INT_ARRAY; + list.update(); + }) + .build(); + protected String[] components; + protected int[] componentIds; + + public static ComponentType getComponentType() { + return EntityUIModule.get().getUIComponentListType(); + } + + public UIComponentList() { + } + + public UIComponentList(@Nonnull UIComponentList other) { + this.componentIds = other.componentIds; + this.components = other.components; + } + + public void update() { + IndexedLookupTableAssetMap assetMap = EntityUIComponent.getAssetMap(); + int assetCount = assetMap.getNextIndex(); + int oldLength = this.componentIds.length; + if (oldLength <= assetCount) { + this.componentIds = Arrays.copyOf(this.componentIds, assetCount); + int index = oldLength; + + while (index < assetCount) { + this.componentIds[index] = index++; + } + } + } + + public int[] getComponentIds() { + return this.componentIds; + } + + @Nonnull + @Override + public Component clone() { + return new UIComponentList(this); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entityui/UIComponentSystems.java b/src/com/hypixel/hytale/server/core/modules/entityui/UIComponentSystems.java new file mode 100644 index 0000000..5d4b3aa --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entityui/UIComponentSystems.java @@ -0,0 +1,168 @@ +package com.hypixel.hytale.server.core.modules.entityui; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.server.core.modules.entity.AllLegacyLivingEntityTypesQuery; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UIComponentSystems { + public UIComponentSystems() { + } + + public static class Remove extends RefChangeSystem { + private final ComponentType uiComponentListComponentType; + private final ComponentType visibleComponentType; + @Nonnull + private final Query query; + + public Remove(ComponentType visibleComponentType, ComponentType componentType) { + this.visibleComponentType = visibleComponentType; + this.uiComponentListComponentType = componentType; + this.query = Query.and(visibleComponentType, componentType); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public ComponentType componentType() { + return this.uiComponentListComponentType; + } + + public void onComponentAdded( + @Nonnull Ref ref, + @Nonnull UIComponentList component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentSet( + @Nonnull Ref ref, + UIComponentList oldComponent, + @Nonnull UIComponentList newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, + @Nonnull UIComponentList component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + for (EntityTrackerSystems.EntityViewer viewer : store.getComponent(ref, this.visibleComponentType).visibleTo.values()) { + viewer.queueRemove(ref, ComponentUpdateType.UIComponents); + } + } + } + + public static class Setup extends HolderSystem { + private final ComponentType uiComponentListComponentType; + + public Setup(ComponentType uiComponentListType) { + this.uiComponentListComponentType = uiComponentListType; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + UIComponentList components = holder.getComponent(this.uiComponentListComponentType); + if (components == null) { + components = holder.ensureAndGetComponent(this.uiComponentListComponentType); + components.update(); + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return AllLegacyLivingEntityTypesQuery.INSTANCE; + } + } + + public static class Update extends EntityTickingSystem { + private final ComponentType visibleComponentType; + private final ComponentType uiComponentListComponentType; + @Nonnull + private final Query query; + + public Update( + ComponentType visibleComponentType, + ComponentType uiComponentListComponentType + ) { + this.visibleComponentType = visibleComponentType; + this.uiComponentListComponentType = uiComponentListComponentType; + this.query = Query.and(visibleComponentType, uiComponentListComponentType); + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visible = archetypeChunk.getComponent(index, this.visibleComponentType); + UIComponentList uiComponentList = archetypeChunk.getComponent(index, this.uiComponentListComponentType); + if (!visible.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), uiComponentList, visible.newlyVisibleTo); + } + } + + private static void queueUpdatesFor( + Ref ref, @Nonnull UIComponentList uiComponentList, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.UIComponents; + update.entityUIComponents = uiComponentList.getComponentIds(); + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, update); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entityui/asset/CombatTextUIComponent.java b/src/com/hypixel/hytale/server/core/modules/entityui/asset/CombatTextUIComponent.java new file mode 100644 index 0000000..9cb2cc2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entityui/asset/CombatTextUIComponent.java @@ -0,0 +1,138 @@ +package com.hypixel.hytale.server.core.modules.entityui.asset; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.CombatTextEntityUIComponentAnimationEvent; +import com.hypixel.hytale.protocol.EntityUIType; +import com.hypixel.hytale.protocol.RangeVector2f; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class CombatTextUIComponent extends EntityUIComponent { + private static final float DEFAULT_FONT_SIZE = 68.0F; + private static final Color DEFAULT_TEXT_COLOR = new Color((byte)-1, (byte)-1, (byte)-1); + public static final BuilderCodec CODEC = BuilderCodec.builder( + CombatTextUIComponent.class, CombatTextUIComponent::new, EntityUIComponent.ABSTRACT_CODEC + ) + .appendInherited( + new KeyedCodec<>("RandomPositionOffsetRange", ProtocolCodecs.RANGE_VECTOR2F), + (component, v) -> component.randomPositionOffsetRange = v, + component -> component.randomPositionOffsetRange, + (component, parent) -> component.randomPositionOffsetRange = parent.randomPositionOffsetRange + ) + .addValidator(Validators.nonNull()) + .documentation( + "The maximum range for randomly offsetting text instances from their starting position. Values are treated as absolute and apply in both directions of the axis." + ) + .add() + .appendInherited( + new KeyedCodec<>("ViewportMargin", Codec.FLOAT), + (component, f) -> component.viewportMargin = f, + component -> component.viewportMargin, + (component, parent) -> component.viewportMargin = parent.viewportMargin + ) + .addValidator(Validators.range(0.0F, 200.0F)) + .documentation("The minimum distance (in px) from the edges of the viewport that text instances should clamp to.") + .add() + .appendInherited( + new KeyedCodec<>("Duration", Codec.FLOAT), + (component, f) -> component.duration = f, + component -> component.duration, + (component, parent) -> component.duration = parent.duration + ) + .addValidator(Validators.nonNull()) + .addValidator(Validators.range(0.1F, 10.0F)) + .documentation("The duration for which text instances in this component should be visible.") + .add() + .appendInherited( + new KeyedCodec<>("HitAngleModifierStrength", Codec.FLOAT), + (component, f) -> component.hitAngleModifierStrength = f, + component -> component.hitAngleModifierStrength, + (component, parent) -> component.hitAngleModifierStrength = parent.hitAngleModifierStrength + ) + .addValidator(Validators.range(0.0F, 10.0F)) + .documentation("Strength of the modifier to apply to the X axis of a position animation (if set) based on the angle of a melee attack.") + .add() + .appendInherited( + new KeyedCodec<>("FontSize", Codec.FLOAT), + (component, f) -> component.fontSize = f, + component -> component.fontSize, + (component, parent) -> component.fontSize = parent.fontSize + ) + .documentation("The font size to apply to text instances.") + .add() + .appendInherited( + new KeyedCodec<>("TextColor", ProtocolCodecs.COLOR), + (component, c) -> component.textColor = c, + component -> component.textColor, + (component, parent) -> component.textColor = parent.textColor + ) + .documentation("The text color to apply to text instances.") + .add() + .appendInherited( + new KeyedCodec<>("AnimationEvents", new ArrayCodec<>(CombatTextUIComponentAnimationEvent.CODEC, CombatTextUIComponentAnimationEvent[]::new)), + (component, o) -> component.animationEvents = o, + component -> component.animationEvents, + (component, parent) -> component.animationEvents = parent.animationEvents + ) + .addValidator(Validators.nonNull()) + .documentation("Animation events for controlling animation of scale, position, and opacity.") + .add() + .build(); + private RangeVector2f randomPositionOffsetRange; + private float viewportMargin; + private float duration; + private float hitAngleModifierStrength = 1.0F; + private float fontSize = 68.0F; + private Color textColor = DEFAULT_TEXT_COLOR; + private CombatTextUIComponentAnimationEvent[] animationEvents; + + public CombatTextUIComponent() { + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.EntityUIComponent generatePacket() { + com.hypixel.hytale.protocol.EntityUIComponent packet = super.generatePacket(); + packet.type = EntityUIType.CombatText; + packet.combatTextRandomPositionOffsetRange = this.randomPositionOffsetRange; + packet.combatTextViewportMargin = this.viewportMargin; + packet.combatTextDuration = this.duration; + packet.combatTextHitAngleModifierStrength = this.hitAngleModifierStrength; + packet.combatTextFontSize = this.fontSize; + packet.combatTextColor = this.textColor; + packet.combatTextAnimationEvents = new CombatTextEntityUIComponentAnimationEvent[this.animationEvents.length]; + + for (int i = 0; i < this.animationEvents.length; i++) { + packet.combatTextAnimationEvents[i] = this.animationEvents[i].generatePacket(); + } + + return packet; + } + + @Nonnull + @Override + public String toString() { + return "CombatTextUIComponent{randomPositionOffsetRange=" + + this.randomPositionOffsetRange + + ", viewportMargin" + + this.viewportMargin + + ", duration=" + + this.duration + + ", hitAngleModifierStrength=" + + this.hitAngleModifierStrength + + ", fontSize=" + + this.fontSize + + ", textColor=" + + this.textColor + + ", animationEvents=" + + Arrays.toString((Object[])this.animationEvents) + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entityui/asset/CombatTextUIComponentAnimationEvent.java b/src/com/hypixel/hytale/server/core/modules/entityui/asset/CombatTextUIComponentAnimationEvent.java new file mode 100644 index 0000000..0ddb17f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entityui/asset/CombatTextUIComponentAnimationEvent.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.core.modules.entityui.asset; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.CombatTextEntityUIAnimationEventType; +import com.hypixel.hytale.protocol.CombatTextEntityUIComponentAnimationEvent; +import javax.annotation.Nonnull; + +public abstract class CombatTextUIComponentAnimationEvent + implements JsonAssetWithMap> { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + public static final BuilderCodec ABSTRACT_CODEC = AssetBuilderCodec.abstractBuilder( + CombatTextUIComponentAnimationEvent.class + ) + .append(new KeyedCodec<>("StartAt", Codec.FLOAT), (event, f) -> event.startAt = f, event -> event.startAt) + .documentation("The percentage of the display duration at which this event should begin.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .append(new KeyedCodec<>("EndAt", Codec.FLOAT), (event, f) -> event.endAt = f, event -> event.endAt) + .documentation("The percentage of the display duration at which this event should end.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .build(); + protected String id; + protected AssetExtraInfo.Data data; + private CombatTextEntityUIAnimationEventType type; + private float startAt; + private float endAt; + + public CombatTextUIComponentAnimationEvent() { + } + + public String getId() { + return this.id; + } + + @Nonnull + public CombatTextEntityUIComponentAnimationEvent generatePacket() { + CombatTextEntityUIComponentAnimationEvent packet = new CombatTextEntityUIComponentAnimationEvent(); + packet.type = this.type; + packet.startAt = this.startAt; + packet.endAt = this.endAt; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "CombatTextUIComponentAnimationEvent{type=" + this.type + ", startAt=" + this.startAt + ", endAt=" + this.endAt + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entityui/asset/CombatTextUIComponentOpacityAnimationEvent.java b/src/com/hypixel/hytale/server/core/modules/entityui/asset/CombatTextUIComponentOpacityAnimationEvent.java new file mode 100644 index 0000000..ebd5413 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entityui/asset/CombatTextUIComponentOpacityAnimationEvent.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.server.core.modules.entityui.asset; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.CombatTextEntityUIAnimationEventType; +import com.hypixel.hytale.protocol.CombatTextEntityUIComponentAnimationEvent; +import javax.annotation.Nonnull; + +public class CombatTextUIComponentOpacityAnimationEvent extends CombatTextUIComponentAnimationEvent { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CombatTextUIComponentOpacityAnimationEvent.class, CombatTextUIComponentOpacityAnimationEvent::new, CombatTextUIComponentAnimationEvent.ABSTRACT_CODEC + ) + .appendInherited( + new KeyedCodec<>("StartOpacity", Codec.FLOAT), + (event, f) -> event.startOpacity = f, + event -> event.startOpacity, + (parent, event) -> event.startOpacity = parent.startOpacity + ) + .documentation("The opacity that should be applied to text instances before the animation event begins.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .appendInherited( + new KeyedCodec<>("EndOpacity", Codec.FLOAT), + (event, f) -> event.endOpacity = f, + event -> event.endOpacity, + (parent, event) -> event.endOpacity = parent.endOpacity + ) + .documentation("The opacity that should be applied to text instances by the end of the animation.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .build(); + private float startOpacity; + private float endOpacity; + + public CombatTextUIComponentOpacityAnimationEvent() { + } + + @Nonnull + @Override + public CombatTextEntityUIComponentAnimationEvent generatePacket() { + CombatTextEntityUIComponentAnimationEvent packet = super.generatePacket(); + packet.type = CombatTextEntityUIAnimationEventType.Opacity; + packet.startOpacity = this.startOpacity; + packet.endOpacity = this.endOpacity; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "CombatTextUIComponentOpacityAnimationEvent{startOpacity=" + this.startOpacity + ", endOpacity=" + this.endOpacity + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entityui/asset/CombatTextUIComponentPositionAnimationEvent.java b/src/com/hypixel/hytale/server/core/modules/entityui/asset/CombatTextUIComponentPositionAnimationEvent.java new file mode 100644 index 0000000..fb9dc12 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entityui/asset/CombatTextUIComponentPositionAnimationEvent.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.core.modules.entityui.asset; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.CombatTextEntityUIAnimationEventType; +import com.hypixel.hytale.protocol.CombatTextEntityUIComponentAnimationEvent; +import com.hypixel.hytale.protocol.Vector2f; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import javax.annotation.Nonnull; + +public class CombatTextUIComponentPositionAnimationEvent extends CombatTextUIComponentAnimationEvent { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CombatTextUIComponentPositionAnimationEvent.class, + CombatTextUIComponentPositionAnimationEvent::new, + CombatTextUIComponentAnimationEvent.ABSTRACT_CODEC + ) + .appendInherited( + new KeyedCodec<>("PositionOffset", ProtocolCodecs.VECTOR2F), + (event, f) -> event.positionOffset = f, + event -> event.positionOffset, + (parent, event) -> event.positionOffset = parent.positionOffset + ) + .documentation("The offset from the starting position that the text instance should animate to.") + .addValidator(Validators.nonNull()) + .add() + .build(); + private Vector2f positionOffset; + + public CombatTextUIComponentPositionAnimationEvent() { + } + + @Nonnull + @Override + public CombatTextEntityUIComponentAnimationEvent generatePacket() { + CombatTextEntityUIComponentAnimationEvent packet = super.generatePacket(); + packet.type = CombatTextEntityUIAnimationEventType.Position; + packet.positionOffset = this.positionOffset; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "CombatTextUIComponentPositionAnimationEvent{positionOffset=" + this.positionOffset + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entityui/asset/CombatTextUIComponentScaleAnimationEvent.java b/src/com/hypixel/hytale/server/core/modules/entityui/asset/CombatTextUIComponentScaleAnimationEvent.java new file mode 100644 index 0000000..25239ef --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entityui/asset/CombatTextUIComponentScaleAnimationEvent.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.server.core.modules.entityui.asset; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.CombatTextEntityUIAnimationEventType; +import com.hypixel.hytale.protocol.CombatTextEntityUIComponentAnimationEvent; +import javax.annotation.Nonnull; + +public class CombatTextUIComponentScaleAnimationEvent extends CombatTextUIComponentAnimationEvent { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CombatTextUIComponentScaleAnimationEvent.class, CombatTextUIComponentScaleAnimationEvent::new, CombatTextUIComponentAnimationEvent.ABSTRACT_CODEC + ) + .appendInherited( + new KeyedCodec<>("StartScale", Codec.FLOAT), + (event, f) -> event.startScale = f, + event -> event.startScale, + (parent, event) -> event.startScale = parent.startScale + ) + .documentation("The scale that should be applied to text instances before the animation event begins.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .appendInherited( + new KeyedCodec<>("EndScale", Codec.FLOAT), + (event, f) -> event.endScale = f, + event -> event.endScale, + (parent, event) -> event.endScale = parent.endScale + ) + .documentation("The scale that should be applied to text instances by the end of the animation.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .build(); + private float startScale; + private float endScale; + + public CombatTextUIComponentScaleAnimationEvent() { + } + + @Nonnull + @Override + public CombatTextEntityUIComponentAnimationEvent generatePacket() { + CombatTextEntityUIComponentAnimationEvent packet = super.generatePacket(); + packet.type = CombatTextEntityUIAnimationEventType.Scale; + packet.startScale = this.startScale; + packet.endScale = this.endScale; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "CombatTextUIComponentConfig{startScale=" + this.startScale + ", endScale=" + this.endScale + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entityui/asset/EntityStatUIComponent.java b/src/com/hypixel/hytale/server/core/modules/entityui/asset/EntityStatUIComponent.java new file mode 100644 index 0000000..61c4e58 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entityui/asset/EntityStatUIComponent.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.core.modules.entityui.asset; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.EntityUIType; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import javax.annotation.Nonnull; + +public class EntityStatUIComponent extends EntityUIComponent { + public static final BuilderCodec CODEC = BuilderCodec.builder( + EntityStatUIComponent.class, EntityStatUIComponent::new, EntityUIComponent.ABSTRACT_CODEC + ) + .appendInherited( + new KeyedCodec<>("EntityStat", Codec.STRING), + (config, s) -> config.entityStat = s, + config -> config.entityStat, + (config, parent) -> config.entityStat = parent.entityStat + ) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyString()) + .addValidator(EntityStatType.VALIDATOR_CACHE.getValidator()) + .documentation("The entity stat to represent.") + .add() + .afterDecode(config -> config.entityStatIndex = EntityStatType.getAssetMap().getIndex(config.entityStat)) + .build(); + protected String entityStat; + protected int entityStatIndex; + + public EntityStatUIComponent() { + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.EntityUIComponent generatePacket() { + com.hypixel.hytale.protocol.EntityUIComponent packet = super.generatePacket(); + packet.type = EntityUIType.EntityStat; + packet.entityStatIndex = this.entityStatIndex; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "EntityStatUIComponentConfig{entityStat='" + this.entityStat + "'entityStatIndex='" + this.entityStatIndex + "'} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entityui/asset/EntityUIComponent.java b/src/com/hypixel/hytale/server/core/modules/entityui/asset/EntityUIComponent.java new file mode 100644 index 0000000..1ab6c6e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entityui/asset/EntityUIComponent.java @@ -0,0 +1,98 @@ +package com.hypixel.hytale.server.core.modules.entityui.asset; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.Vector2f; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.lang.ref.SoftReference; +import javax.annotation.Nonnull; + +public abstract class EntityUIComponent + implements JsonAssetWithMap>, + NetworkSerializable { + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + public static final BuilderCodec ABSTRACT_CODEC = AssetBuilderCodec.abstractBuilder(EntityUIComponent.class) + .append(new KeyedCodec<>("HitboxOffset", ProtocolCodecs.VECTOR2F), (config, v) -> config.hitboxOffset = v, config -> config.hitboxOffset) + .documentation("Offset from the centre of the entity's hitbox to display this component.") + .add() + .build(); + protected String id; + protected AssetExtraInfo.Data data; + private Vector2f hitboxOffset = new Vector2f(0.0F, 0.0F); + private transient SoftReference cachedPacket; + private static AssetStore> ASSET_STORE; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(EntityUIComponent.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + protected EntityUIComponent() { + } + + @Nonnull + public static EntityUIComponent getUnknownFor(String id) { + return new EntityUIComponent.Unknown(id); + } + + public String getId() { + return this.id; + } + + @Nonnull + public final com.hypixel.hytale.protocol.EntityUIComponent toPacket() { + com.hypixel.hytale.protocol.EntityUIComponent cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.EntityUIComponent packet = this.generatePacket(); + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + @Nonnull + protected com.hypixel.hytale.protocol.EntityUIComponent generatePacket() { + com.hypixel.hytale.protocol.EntityUIComponent packet = new com.hypixel.hytale.protocol.EntityUIComponent(); + packet.hitboxOffset = this.hitboxOffset; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "EntityUIComponentConfig{data=" + this.data + ", id='" + this.id + "', hitboxOffset='" + this.hitboxOffset + "'}"; + } + + private static class Unknown extends EntityUIComponent { + public Unknown(String id) { + this.id = id; + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.EntityUIComponent generatePacket() { + com.hypixel.hytale.protocol.EntityUIComponent packet = super.generatePacket(); + packet.unknown = true; + return packet; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/entityui/asset/EntityUIComponentPacketGenerator.java b/src/com/hypixel/hytale/server/core/modules/entityui/asset/EntityUIComponentPacketGenerator.java new file mode 100644 index 0000000..3714f08 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/entityui/asset/EntityUIComponentPacketGenerator.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.modules.entityui.asset; + +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateEntityUIComponents; +import com.hypixel.hytale.server.core.asset.packet.AssetPacketGenerator; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class EntityUIComponentPacketGenerator extends AssetPacketGenerator> { + public EntityUIComponentPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + Int2ObjectMap configs = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + configs.put(assetMap.getIndex(entry.getKey()), entry.getValue().toPacket()); + } + + return new UpdateEntityUIComponents(UpdateType.Init, assetMap.getNextIndex(), configs); + } + + @Nonnull + public Packet generateUpdatePacket( + @Nonnull IndexedLookupTableAssetMap assetMap, + @Nonnull Map loadedAssets, + @Nonnull AssetUpdateQuery query + ) { + Int2ObjectMap components = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + components.put(assetMap.getIndex(entry.getKey()), entry.getValue().toPacket()); + } + + return new UpdateEntityUIComponents(UpdateType.AddOrUpdate, assetMap.getNextIndex(), components); + } + + @Nonnull + public Packet generateRemovePacket( + @Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed, @Nonnull AssetUpdateQuery query + ) { + Int2ObjectMap configs = new Int2ObjectOpenHashMap<>(); + + for (String entry : removed) { + configs.put(assetMap.getIndex(entry), new com.hypixel.hytale.protocol.EntityUIComponent()); + } + + return new UpdateEntityUIComponents(UpdateType.Remove, assetMap.getNextIndex(), configs); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/i18n/I18nModule.java b/src/com/hypixel/hytale/server/core/modules/i18n/I18nModule.java new file mode 100644 index 0000000..47afe0a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/i18n/I18nModule.java @@ -0,0 +1,459 @@ +package com.hypixel.hytale.server.core.modules.i18n; + +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateTranslations; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.HytaleServerConfig; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.asset.AssetPackRegisterEvent; +import com.hypixel.hytale.server.core.asset.AssetPackUnregisterEvent; +import com.hypixel.hytale.server.core.asset.LoadAssetEvent; +import com.hypixel.hytale.server.core.asset.monitor.AssetMonitor; +import com.hypixel.hytale.server.core.asset.monitor.AssetMonitorHandler; +import com.hypixel.hytale.server.core.asset.monitor.EventKind; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.Bench; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.CraftingBench; +import com.hypixel.hytale.server.core.asset.type.item.config.FieldcraftCategory; +import com.hypixel.hytale.server.core.asset.type.item.config.ResourceType; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.modules.i18n.commands.EnableTmpTagsCommand; +import com.hypixel.hytale.server.core.modules.i18n.commands.InternationalizationCommands; +import com.hypixel.hytale.server.core.modules.i18n.event.MessagesUpdated; +import com.hypixel.hytale.server.core.modules.i18n.parser.LangFileParser; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class I18nModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(I18nModule.class).depends(AssetModule.class).build(); + public static final String DEFAULT_LANGUAGE = "en-US"; + public static final Path FALLBACK_LANG_PATH = Paths.get("fallback.lang"); + public static final String FILE_EXTENSION = ".lang"; + public static final String SERVER_ASSETS = "Server"; + public static final String LANGUAGE_ASSETS = "Languages"; + public static final Path DEFAULT_GENERATED_PATH = Path.of("Server", "Languages", "en-US"); + private static I18nModule instance; + private final Map fallbacks = new ConcurrentHashMap<>(); + private final Map> languages = new ConcurrentHashMap<>(); + private final Map> cachedLanguages = new ConcurrentHashMap<>(); + + public static I18nModule get() { + return instance; + } + + public I18nModule(@Nonnull JavaPluginInit parent) { + super(parent); + instance = this; + } + + @Override + protected void setup() { + this.getEventRegistry().register(LoadAssetEvent.class, event -> { + for (AssetPack pack : AssetModule.get().getAssetPacks()) { + this.loadMessagesFromPack(pack); + } + }); + this.getEventRegistry().register(AssetPackRegisterEvent.class, event -> this.loadMessagesFromPack(event.getAssetPack())); + this.getEventRegistry().register(AssetPackUnregisterEvent.class, event -> {}); + this.getEventRegistry() + .register( + LoadedAssetsEvent.class, + BlockType.class, + event -> { + Map addedMessages = new Object2ObjectOpenHashMap<>(); + event.getLoadedAssets() + .values() + .forEach( + item -> { + Bench bench = item.getBench(); + if (bench != null) { + String id = item.getId(); + if (bench instanceof CraftingBench craftingBench) { + for (CraftingBench.BenchCategory category : craftingBench.getCategories()) { + addedMessages.put("server.items." + id + ".bench.categories." + category.getId() + ".name", category.getName()); + if (category.getItemCategories() != null) { + for (CraftingBench.BenchItemCategory itemCategory : category.getItemCategories()) { + addedMessages.put( + "server.items." + id + ".bench.categories." + category.getId() + ".itemCategories." + itemCategory.getId() + ".name", + itemCategory.getName() + ); + } + } + } + } + + if (bench.getDescriptiveLabel() != null) { + addedMessages.put("server.items." + id + ".bench.descriptiveLabel", bench.getDescriptiveLabel()); + } + } + } + ); + this.addDefaultMessages(addedMessages, event.isInitial()); + } + ); + this.getEventRegistry().register(LoadedAssetsEvent.class, FieldcraftCategory.class, event -> { + Map addedMessages = new Object2ObjectOpenHashMap<>(); + event.getLoadedAssets().values().forEach(category -> { + if (category.getName() != null) { + addedMessages.put("fieldcraftCategories." + category.getId() + ".name", category.getName()); + } + }); + this.addDefaultMessages(addedMessages, event.isInitial()); + }); + this.getEventRegistry().register(LoadedAssetsEvent.class, ResourceType.class, event -> { + Map addedMessages = new Object2ObjectOpenHashMap<>(); + event.getLoadedAssets().values().forEach(resourceType -> { + if (resourceType.getName() != null) { + addedMessages.put("resourceTypes." + resourceType.getId() + ".name", resourceType.getName()); + } + + if (resourceType.getDescription() != null) { + addedMessages.put("resourceTypes." + resourceType.getId() + ".description", resourceType.getDescription()); + } + }); + this.addDefaultMessages(addedMessages, event.isInitial()); + }); + } + + @Override + protected void start() { + this.getCommandRegistry().registerCommand(new InternationalizationCommands()); + this.getCommandRegistry().registerCommand(new EnableTmpTagsCommand()); + } + + private void loadMessagesFromPack(AssetPack pack) { + Path languagesPath = pack.getRoot().resolve("Server").resolve("Languages"); + if (Files.isDirectory(languagesPath)) { + AssetMonitor assetMonitor = AssetModule.get().getAssetMonitor(); + if (assetMonitor != null && !pack.isImmutable()) { + assetMonitor.monitorDirectoryFiles(languagesPath, new I18nModule.I18nAssetMonitorHandler(languagesPath)); + } + + try (DirectoryStream directoryStream = Files.newDirectoryStream(languagesPath, x$0 -> Files.isDirectory(x$0))) { + for (Path path : directoryStream) { + try { + String languageKey = path.getFileName().toString(); + int entriesCount = this.loadMessages(languageKey, path); + this.getLogger().at(Level.INFO).log("Loaded %d entries for '%s' from %s", entriesCount, languageKey, languagesPath); + } catch (IOException var10) { + this.getLogger().at(Level.SEVERE).withCause(var10).log("Failed to load messages from: %s", path); + } + } + } catch (IOException var13) { + this.getLogger().at(Level.SEVERE).withCause(var13).log("Failed to load languages from: %s", languagesPath); + } + } + + Path fallbackPath = languagesPath.resolve("fallback.lang"); + if (Files.exists(fallbackPath)) { + Properties properties = new Properties(); + + try { + properties.load(Files.newInputStream(fallbackPath)); + + for (Entry entry : properties.entrySet()) { + this.fallbacks.put((String)entry.getKey(), (String)entry.getValue()); + } + + if (!properties.isEmpty()) { + this.getLogger().at(Level.INFO).log("Loaded %d entries from %s", properties.size(), fallbackPath); + } + } catch (IOException var11) { + this.getLogger().at(Level.SEVERE).withCause(var11).log("Failed to load fallback languages from: %s", fallbackPath); + } + } + } + + @Nonnull + public UpdateTranslations[] getUpdatePacketsForChanges( + String languageKey, @Nonnull Map> changed, @Nonnull Map> removed + ) { + Map removedMessages = this.getMessages(removed, languageKey); + Map changedMessages = this.getMessages(changed, languageKey); + int size = (removedMessages.isEmpty() ? 0 : 1) + (changedMessages.isEmpty() ? 0 : 1); + UpdateTranslations[] packets = new UpdateTranslations[size]; + int index = 0; + if (!removedMessages.isEmpty()) { + packets[index++] = new UpdateTranslations(UpdateType.Remove, removedMessages); + } + + if (!changedMessages.isEmpty()) { + packets[index] = new UpdateTranslations(UpdateType.AddOrUpdate, changedMessages); + } + + return packets; + } + + private void addDefaultMessages(@Nonnull Map messages, boolean isInitial) { + Map languageMap = this.languages.computeIfAbsent("en-US", k -> new ConcurrentHashMap<>()); + + for (Entry entry : messages.entrySet()) { + if (entry.getKey() != null && entry.getValue() != null) { + languageMap.put(entry.getKey(), entry.getValue()); + } else { + this.getLogger().at(Level.WARNING).log("Attempted to add invalid default translation message: %s=%s", entry.getKey(), entry.getValue()); + } + } + + if (!isInitial) { + UpdateTranslations packet = new UpdateTranslations(UpdateType.AddOrUpdate, messages); + Universe.get().broadcastPacketNoCache(packet); + IEventDispatcher dispatch = HytaleServer.get().getEventBus().dispatchFor(MessagesUpdated.class); + if (dispatch.hasListener()) { + Object2ObjectOpenHashMap> languageMapping = new Object2ObjectOpenHashMap<>(); + languageMapping.put("en-US", messages); + dispatch.dispatch(new MessagesUpdated(languageMapping, new Object2ObjectOpenHashMap<>())); + } + } + } + + private int loadMessages(String languageKey, @Nonnull Path languagePath) throws IOException { + Map messages = this.languages.computeIfAbsent(languageKey, k -> new ConcurrentHashMap<>()); + + int var5; + try (Stream stream = Files.find( + languagePath, + Integer.MAX_VALUE, + (path, attr) -> path.toString().endsWith(".lang") && attr.isRegularFile(), + FileUtil.DEFAULT_WALK_TREE_OPTIONS_ARRAY + )) { + var5 = stream.mapToInt(path -> { + String prefix = this.getPrefix(languagePath, path); + return this.loadMessagesFrom(messages, prefix, path); + }).sum(); + } + + return var5; + } + + private int loadMessagesFrom(@Nonnull Map messages, String prefix, @Nonnull Path path) { + Map properties; + try (BufferedReader inputStream = Files.newBufferedReader(path)) { + properties = LangFileParser.parse(inputStream); + } catch (Exception var12) { + this.getLogger().at(Level.SEVERE).withCause(new SkipSentryException(var12)).log("Error parsing language file: %s", path.toString()); + return 0; + } + + for (Entry entry : properties.entrySet()) { + String key = prefix + "." + entry.getKey(); + String value = entry.getValue(); + String prev = messages.get(key); + if (prev != null) { + if (!prev.equals(value)) { + this.getLogger().at(Level.WARNING).log("'%s' has multiple definitions: `%s` and `%s`", key, prev, value); + } + } else { + messages.put(key, value); + } + } + + return properties.size(); + } + + @Nonnull + private String getPrefix(@Nonnull Path languagePath, @Nonnull Path path) { + String prefix = ""; + Path directory = path.getParent(); + if (!languagePath.equals(directory)) { + Path relativePath = languagePath.relativize(directory); + prefix = prefix + relativePath.toString().replace(File.separatorChar, '.') + "."; + } + + String name = path.getFileName().toString(); + return prefix + name.substring(0, name.length() - ".lang".length()); + } + + @Nonnull + public Map getMessages(String language) { + return this.cachedLanguages.computeIfAbsent(language, key -> Collections.unmodifiableMap(this.getMessages(this.languages, key))); + } + + public Map getMessages(@Nonnull Map> languageMap, @Nullable String language) { + if (language == null) { + return this.getMessages(languageMap, "en-US"); + } else { + Map messages = languageMap.get(language); + if ("en-US".equals(language)) { + return messages != null ? messages : Collections.emptyMap(); + } else { + String fallback = this.fallbacks.getOrDefault(language, "en-US"); + Map fallbackMessages = this.getMessages(languageMap, fallback); + if (fallbackMessages == null) { + return messages != null ? messages : Collections.emptyMap(); + } else if (messages == null) { + return fallbackMessages; + } else { + Object2ObjectOpenHashMap map = new Object2ObjectOpenHashMap<>(fallbackMessages); + map.putAll(messages); + return map; + } + } + } + } + + public void sendTranslations(@Nonnull PacketHandler packetHandler, String language) { + if (!this.isDisabled()) { + packetHandler.writeNoCache(new UpdateTranslations(UpdateType.Init, this.getMessages(language))); + } + } + + @Nullable + public String getMessage(String language, @Nonnull String key) { + HytaleServerConfig config = HytaleServer.get().getConfig(); + if (config != null && config.isDisplayTmpTagsInStrings()) { + return this.getMessages(language).get(key); + } else { + String translatedString = this.getMessages(language).get(key); + return translatedString != null ? translatedString.replace("[TMP] ", "").replace("[TMP]", "") : null; + } + } + + private class I18nAssetMonitorHandler implements AssetMonitorHandler { + private final Path languagesPath; + + public I18nAssetMonitorHandler(Path languagesPath) { + this.languagesPath = languagesPath; + } + + @Override + public Object getKey() { + return I18nModule.this; + } + + public boolean test(Path path, EventKind eventKind) { + return !Files.isDirectory(path) && path.getFileName().toString().endsWith(".lang") && I18nModule.this.isEnabled(); + } + + public void accept(Map map) { + Map> removed = new Object2ObjectOpenHashMap<>(); + Map> changed = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : map.entrySet()) { + Path path = entry.getKey(); + EventKind eventKind = entry.getValue(); + Path normalized = path.toAbsolutePath().normalize(); + Path relativized = this.languagesPath.relativize(normalized); + if (I18nModule.FALLBACK_LANG_PATH.equals(relativized)) { + Properties properties = new Properties(); + + try { + properties.load(Files.newInputStream(path)); + } catch (IOException var18) { + I18nModule.this.getLogger().at(Level.SEVERE).withCause(var18).log("Failed to load fallback languages from: %s", path); + continue; + } + + I18nModule.this.fallbacks.clear(); + properties.forEach((keyx, value) -> I18nModule.this.fallbacks.put((String)keyx, value)); + } else { + String languageKey = relativized.getName(0).toString(); + Path langPath = this.languagesPath.resolve(languageKey).toAbsolutePath().normalize(); + String prefix = I18nModule.this.getPrefix(langPath, normalized); + switch (eventKind) { + case ENTRY_MODIFY: + case ENTRY_DELETE: + String prefixWithDot = prefix + "."; + Map removedMessages = removed.computeIfAbsent(languageKey, k -> new Object2ObjectOpenHashMap<>()); + Map messages = I18nModule.this.languages.computeIfAbsent(languageKey, k -> new ConcurrentHashMap<>()); + Iterator iterator = messages.keySet().iterator(); + + while (iterator.hasNext()) { + String key = iterator.next(); + if (key.startsWith(prefixWithDot)) { + removedMessages.put(key, ""); + iterator.remove(); + } + } + + if (eventKind == EventKind.ENTRY_DELETE) { + break; + } + case ENTRY_CREATE: + Map changedMessages = changed.computeIfAbsent(languageKey, k -> new Object2ObjectOpenHashMap<>()); + I18nModule.this.loadMessagesFrom(changedMessages, prefix, path); + } + } + } + + if (!removed.isEmpty() || !changed.isEmpty()) { + for (Entry> changedLang : changed.entrySet()) { + Map messages = I18nModule.this.languages.computeIfAbsent(changedLang.getKey(), k -> new ConcurrentHashMap<>()); + messages.putAll(changedLang.getValue()); + I18nModule.this.cachedLanguages.remove(changedLang.getKey()); + } + + for (Entry> removedLang : removed.entrySet()) { + I18nModule.this.cachedLanguages.remove(removedLang.getKey()); + Map orig = I18nModule.this.getMessages(removedLang.getKey()); + Map changedMessages = changed.computeIfAbsent(removedLang.getKey(), k -> new Object2ObjectOpenHashMap<>()); + Iterator iterator = removedLang.getValue().keySet().iterator(); + + while (iterator.hasNext()) { + String removedKey = iterator.next(); + if (changedMessages.containsKey(removedKey)) { + iterator.remove(); + } else { + String fallback = orig.get(removedKey); + if (fallback != null) { + iterator.remove(); + changedMessages.put(removedKey, fallback); + } + } + } + } + + List players = Universe.get().getPlayers(); + Map updatePackets = new Object2ObjectOpenHashMap<>(); + + for (PlayerRef playerRef : players) { + PacketHandler handler = playerRef.getPacketHandler(); + String languageKey = playerRef.getLanguage(); + UpdateTranslations[] packets = updatePackets.get(languageKey); + if (packets == null) { + packets = I18nModule.this.getUpdatePacketsForChanges(languageKey, changed, removed); + updatePackets.put(languageKey, packets); + } + + if (packets.length != 0) { + handler.write(packets); + } + } + + IEventDispatcher dispatch = HytaleServer.get().getEventBus().dispatchFor(MessagesUpdated.class); + if (dispatch.hasListener()) { + dispatch.dispatch(new MessagesUpdated(changed, removed)); + } + + I18nModule.this.getLogger().at(Level.INFO).log("Handled language changes for: %s", changed.keySet()); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/i18n/commands/EnableTmpTagsCommand.java b/src/com/hypixel/hytale/server/core/modules/i18n/commands/EnableTmpTagsCommand.java new file mode 100644 index 0000000..20bf8ae --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/i18n/commands/EnableTmpTagsCommand.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.core.modules.i18n.commands; + +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.HytaleServerConfig; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import javax.annotation.Nonnull; + +public class EnableTmpTagsCommand extends CommandBase { + public EnableTmpTagsCommand() { + super("toggletmptags", "server.commands.toggleTmpTags.desc"); + this.addAliases("tmptag", "tmptags", "tmpstring", "tmpstrings", "tmptext"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + HytaleServerConfig config = HytaleServer.get().getConfig(); + if (config != null) { + boolean displayTmpTags = !config.isDisplayTmpTagsInStrings(); + config.setDisplayTmpTagsInStrings(!displayTmpTags); + context.sendMessage(Message.translation("server.commands.toggleTmpTags." + (displayTmpTags ? "enabled" : "disabled"))); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/i18n/commands/GenerateI18nCommand.java b/src/com/hypixel/hytale/server/core/modules/i18n/commands/GenerateI18nCommand.java new file mode 100644 index 0000000..d6c8b92 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/i18n/commands/GenerateI18nCommand.java @@ -0,0 +1,115 @@ +package com.hypixel.hytale.server.core.modules.i18n.commands; + +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.modules.i18n.I18nModule; +import com.hypixel.hytale.server.core.modules.i18n.event.GenerateDefaultLanguageEvent; +import com.hypixel.hytale.server.core.modules.i18n.generator.TranslationMap; +import java.io.BufferedWriter; +import java.io.FileInputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class GenerateI18nCommand extends AbstractAsyncCommand { + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + protected final FlagArg cleanArg = this.withFlagArg("clean", "server.commands.i18n.gen.clean.desc"); + + public GenerateI18nCommand() { + super("gen", "server.commands.i18n.gen.desc"); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + CommandSender commandSender = context.sender(); + AssetPack baseAssetPack = AssetModule.get().getBaseAssetPack(); + if (baseAssetPack.isImmutable()) { + commandSender.sendMessage(Message.translation("server.commands.i18n.gen.immutable")); + return CompletableFuture.completedFuture(null); + } else { + Path baseAssetPackRoot = baseAssetPack.getRoot(); + boolean cleanOldKeys = this.cleanArg.get(context); + ConcurrentHashMap translationFiles = new ConcurrentHashMap<>(); + HytaleServer.get() + .getEventBus() + .dispatchFor(GenerateDefaultLanguageEvent.class) + .dispatch(new GenerateDefaultLanguageEvent(translationFiles)); + return CompletableFuture.runAsync(() -> { + try { + for (Entry entry : translationFiles.entrySet()) { + String filename = entry.getKey(); + TranslationMap generatedMap = entry.getValue(); + Path path = baseAssetPackRoot.resolve(I18nModule.DEFAULT_GENERATED_PATH).resolve(filename + ".lang"); + TranslationMap mergedMap = this.mergei18nWithOnDisk(path, generatedMap, cleanOldKeys); + mergedMap.sortByKeyBeforeFirstDot(); + this.writeTranslationMap(path, mergedMap); + LOGGER.at(Level.INFO).log("Wrote %s translation(s) to %s", mergedMap.size(), path.toAbsolutePath()); + } + + LOGGER.at(Level.INFO).log("Wrote %s generated translation file(s)", translationFiles.size()); + commandSender.sendMessage(Message.translation(cleanOldKeys ? "server.commands.i18n.gen.cleaned" : "server.commands.i18n.gen.done")); + } catch (Throwable var11) { + throw new RuntimeException("Error writing generated translation file(s)", var11); + } + }); + } + } + + @Nonnull + private TranslationMap mergei18nWithOnDisk(@Nonnull Path path, @Nonnull TranslationMap generated, boolean cleanOldKeys) throws Exception { + TranslationMap mergedMap = new TranslationMap(); + if (Files.exists(path)) { + Properties diskAsProperties = new Properties(); + diskAsProperties.load(new FileInputStream(path.toFile())); + TranslationMap diskTranslationMap = new TranslationMap(diskAsProperties); + if (cleanOldKeys) { + Set extraneousDiskKeys = difference(diskTranslationMap.asMap().keySet(), generated.asMap().keySet()); + diskTranslationMap.removeKeys(extraneousDiskKeys); + } + + mergedMap.putAbsentKeys(diskTranslationMap); + } + + mergedMap.putAbsentKeys(generated); + return mergedMap; + } + + private void writeTranslationMap(@Nonnull Path path, @Nonnull TranslationMap translationMap) throws Exception { + Files.createDirectories(path.getParent()); + Map map = translationMap.asMap(); + + try (BufferedWriter writer = Files.newBufferedWriter(path)) { + for (Entry e : map.entrySet()) { + String k = e.getKey(); + String v = e.getValue(); + writer.write(k); + writer.write(" = "); + writer.write(v); + writer.write(System.lineSeparator()); + } + } + } + + @Nonnull + private static Set difference(@Nonnull Set a, @Nonnull Set b) { + Set difference = new HashSet<>(a); + difference.removeAll(b); + return difference; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/i18n/commands/InternationalizationCommands.java b/src/com/hypixel/hytale/server/core/modules/i18n/commands/InternationalizationCommands.java new file mode 100644 index 0000000..583fce3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/i18n/commands/InternationalizationCommands.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.modules.i18n.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class InternationalizationCommands extends AbstractCommandCollection { + public InternationalizationCommands() { + super("lang", "server.commands.i18n.desc"); + this.addAliases("internationalization", "il8n", "translation"); + this.addSubCommand(new GenerateI18nCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/i18n/event/GenerateDefaultLanguageEvent.java b/src/com/hypixel/hytale/server/core/modules/i18n/event/GenerateDefaultLanguageEvent.java new file mode 100644 index 0000000..7aa1e02 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/i18n/event/GenerateDefaultLanguageEvent.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.server.core.modules.i18n.event; + +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.server.core.modules.i18n.generator.TranslationMap; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; + +public class GenerateDefaultLanguageEvent implements IEvent { + private final ConcurrentHashMap translationFiles; + + public GenerateDefaultLanguageEvent(ConcurrentHashMap translationFiles) { + this.translationFiles = translationFiles; + } + + public void putTranslationFile(@Nonnull String filename, @Nonnull TranslationMap translations) { + this.translationFiles.put(filename, translations); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/i18n/event/MessagesUpdated.java b/src/com/hypixel/hytale/server/core/modules/i18n/event/MessagesUpdated.java new file mode 100644 index 0000000..3abdca5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/i18n/event/MessagesUpdated.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.core.modules.i18n.event; + +import com.hypixel.hytale.event.IEvent; +import java.util.Map; +import javax.annotation.Nonnull; + +public class MessagesUpdated implements IEvent { + private final Map> changedMessages; + private final Map> removedMessages; + + public MessagesUpdated(Map> changedMessages, Map> removedMessages) { + this.changedMessages = changedMessages; + this.removedMessages = removedMessages; + } + + public Map> getChangedMessages() { + return this.changedMessages; + } + + public Map> getRemovedMessages() { + return this.removedMessages; + } + + @Nonnull + @Override + public String toString() { + return "MessagesUpdated{changedMessages=" + this.changedMessages + ", removedMessages=" + this.removedMessages + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/i18n/generator/TranslationMap.java b/src/com/hypixel/hytale/server/core/modules/i18n/generator/TranslationMap.java new file mode 100644 index 0000000..d33e35e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/i18n/generator/TranslationMap.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.server.core.modules.i18n.generator; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TranslationMap { + @Nonnull + private LinkedHashMap map = new LinkedHashMap<>(); + + public TranslationMap() { + } + + public TranslationMap(Map initial) { + this.map.putAll(initial); + } + + public TranslationMap(@Nonnull Properties initial) { + for (String key : initial.stringPropertyNames()) { + this.map.put(key, initial.getProperty(key)); + } + } + + @Nullable + public String get(String key) { + return this.map.get(key); + } + + public void put(String key, String value) { + this.map.put(key, value); + } + + public void removeKeys(@Nonnull Collection keys) { + this.map.keySet().removeAll(keys); + } + + public int size() { + return this.map.size(); + } + + public void putAbsentKeys(@Nonnull TranslationMap other) { + for (Entry e : other.map.entrySet()) { + String key = e.getKey(); + String otherValue = e.getValue(); + this.map.putIfAbsent(key, otherValue); + } + } + + public void sortByKeyBeforeFirstDot() { + List keys = new ObjectArrayList<>(this.map.keySet()); + Comparator comparator = Comparator.comparing(fullKey -> { + int firstDotIndex = fullKey.indexOf(46); + return (String)(firstDotIndex == -1 ? fullKey : fullKey.substring(0, firstDotIndex)); + }).thenComparing(fullKey -> { + int firstDotIndex = fullKey.indexOf(46); + return firstDotIndex == -1 ? "" : fullKey.substring(firstDotIndex + 1); + }); + keys.sort(comparator); + LinkedHashMap sorted = new LinkedHashMap<>(); + + for (String key : keys) { + sorted.put(key, this.map.get(key)); + } + + this.map = sorted; + } + + @Nonnull + public Map asMap() { + return Collections.unmodifiableMap(this.map); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/i18n/parser/LangFileParser.java b/src/com/hypixel/hytale/server/core/modules/i18n/parser/LangFileParser.java new file mode 100644 index 0000000..e5d215c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/i18n/parser/LangFileParser.java @@ -0,0 +1,98 @@ +package com.hypixel.hytale.server.core.modules.i18n.parser; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class LangFileParser { + public LangFileParser() { + } + + @Nonnull + private static String literal(@Nonnull String value) { + String literal = value.trim(); + return literal.length() > 1 && literal.charAt(0) == '"' && literal.charAt(literal.length() - 1) == '"' + ? literal.substring(1, literal.length() - 1) + : literal; + } + + @Nonnull + private static String escape(@Nonnull StringBuilder builder) { + return builder.toString().replace("\\n", "\n").replace("\\t", "\t"); + } + + @Nonnull + public static Map parse(@Nonnull BufferedReader reader) throws IOException, LangFileParser.TranslationParseException { + Map translations = new LinkedHashMap<>(); + String currKey = null; + StringBuilder currValue = null; + int lineNumber = 0; + + String line; + while ((line = reader.readLine()) != null) { + lineNumber++; + line = line.trim(); + if (!line.isEmpty() && line.charAt(0) != '#') { + if (currKey == null) { + int eqIdx = line.indexOf(61); + if (eqIdx < 0) { + throw new LangFileParser.TranslationParseException("Missing '=' in key-value line", lineNumber, line); + } + + String key = line.substring(0, eqIdx).trim(); + if (key.isEmpty()) { + throw new LangFileParser.TranslationParseException("Empty key in line", lineNumber, line); + } + + String value = line.substring(eqIdx + 1).trim(); + if (value.isEmpty()) { + throw new LangFileParser.TranslationParseException("Empty value in line", lineNumber, line); + } + + currKey = key; + currValue = new StringBuilder(); + boolean isMultiline = value.charAt(value.length() - 1) == '\\'; + if (isMultiline) { + currValue.append(value, 0, value.length() - 1); + } else { + currValue.append(literal(value)); + String existing = translations.put(key, escape(currValue)); + if (existing != null) { + throw new LangFileParser.TranslationParseException("Duplicate key in line", lineNumber, line); + } + + currKey = null; + currValue = null; + } + } else { + boolean isMultiline = line.charAt(line.length() - 1) == '\\'; + String valueLine = isMultiline ? line.substring(0, line.length() - 1) : line; + currValue.append(valueLine.trim()); + if (!isMultiline) { + String existing = translations.put(currKey, escape(currValue)); + if (existing != null) { + throw new LangFileParser.TranslationParseException("Duplicate key in line", lineNumber, line); + } + + currKey = null; + currValue = null; + } + } + } + } + + if (currKey != null) { + throw new LangFileParser.TranslationParseException("Unexpected end of key-value line", lineNumber, currKey); + } else { + return translations; + } + } + + public static class TranslationParseException extends Exception { + TranslationParseException(String message, int lineNumber, String lineContent) { + super(message + " (at line " + lineNumber + "): " + lineContent); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/BlockHarvestUtils.java b/src/com/hypixel/hytale/server/core/modules/interaction/BlockHarvestUtils.java new file mode 100644 index 0000000..c1b7249 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/BlockHarvestUtils.java @@ -0,0 +1,872 @@ +package com.hypixel.hytale.server.core.modules.interaction; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockSoundEvent; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocksound.config.BlockSoundSet; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockBreakingDropType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockGathering; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.HarvestingDropType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.PhysicsDropType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.SoftBlockDropType; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.core.asset.type.gameplay.BrokenPenalties; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.gameplay.GatheringEffectsConfig; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemTool; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemToolSpec; +import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; +import com.hypixel.hytale.server.core.entity.ItemUtils; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.event.events.ecs.BreakBlockEvent; +import com.hypixel.hytale.server.core.event.events.ecs.DamageBlockEvent; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.modules.blockhealth.BlockHealth; +import com.hypixel.hytale.server.core.modules.blockhealth.BlockHealthChunk; +import com.hypixel.hytale.server.core.modules.blockhealth.BlockHealthModule; +import com.hypixel.hytale.server.core.modules.blockset.BlockSetModule; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.modules.item.ItemModule; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.ParticleUtil; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.connectedblocks.ConnectedBlocksUtil; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import it.unimi.dsi.fastutil.objects.ObjectLists; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockHarvestUtils { + public BlockHarvestUtils() { + } + + @Nullable + public static ItemToolSpec getSpecPowerDamageBlock(@Nullable Item item, @Nullable BlockType blockType, @Nullable ItemTool tool) { + if (blockType == null) { + return null; + } else { + BlockGathering gathering = blockType.getGathering(); + if (gathering == null) { + return null; + } else { + BlockBreakingDropType breaking = gathering.getBreaking(); + if (breaking == null) { + return null; + } else { + String gatherType = breaking.getGatherType(); + if (gatherType == null) { + return null; + } else if (item == null || item.getWeapon() == null && item.getBuilderToolData() == null) { + if (tool == null) { + return ItemToolSpec.getAssetMap().getAsset(gatherType); + } else { + if (tool.getSpecs() != null) { + for (ItemToolSpec spec : tool.getSpecs()) { + if (Objects.equals(spec.getGatherType(), gatherType)) { + return spec; + } + } + } + + return null; + } + } else { + return null; + } + } + } + } + } + + public static double calculateDurabilityUse(@Nonnull Item item, @Nullable BlockType blockType) { + if (blockType == null) { + return 0.0; + } else if (blockType.getGathering().isSoft()) { + return 0.0; + } else if (item.getTool() == null) { + return 0.0; + } else { + ItemTool itemTool = item.getTool(); + ItemTool.DurabilityLossBlockTypes[] durabilityLossBlockTypes = itemTool.getDurabilityLossBlockTypes(); + if (durabilityLossBlockTypes == null) { + return item.getDurabilityLossOnHit(); + } else { + String hitBlockTypeId = blockType.getId(); + int hitBlockTypeIndex = BlockType.getAssetMap().getIndex(hitBlockTypeId); + if (hitBlockTypeIndex == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + hitBlockTypeId); + } else { + BlockSetModule blockSetModule = BlockSetModule.getInstance(); + + for (ItemTool.DurabilityLossBlockTypes durabilityLossBlockType : durabilityLossBlockTypes) { + int[] blockTypeIndexes = durabilityLossBlockType.getBlockTypeIndexes(); + if (blockTypeIndexes != null) { + for (int blockTypeIndex : blockTypeIndexes) { + if (blockTypeIndex == hitBlockTypeIndex) { + return durabilityLossBlockType.getDurabilityLossOnHit(); + } + } + } + + int[] blockSetIndexes = durabilityLossBlockType.getBlockSetIndexes(); + if (blockSetIndexes != null) { + for (int blockSetIndex : blockSetIndexes) { + if (blockSetModule.blockInSet(blockSetIndex, hitBlockTypeId)) { + return durabilityLossBlockType.getDurabilityLossOnHit(); + } + } + } + } + + return item.getDurabilityLossOnHit(); + } + } + } + } + + public static boolean performBlockDamage( + @Nonnull Vector3i targetBlock, + @Nullable ItemStack itemStack, + @Nullable ItemTool tool, + float damageScale, + int setBlockSettings, + @Nonnull Ref chunkReference, + @Nonnull CommandBuffer commandBuffer, + @Nonnull ComponentAccessor chunkStore + ) { + return performBlockDamage(null, null, targetBlock, itemStack, tool, null, false, damageScale, setBlockSettings, chunkReference, commandBuffer, chunkStore); + } + + public static boolean performBlockDamage( + @Nullable LivingEntity entity, + @Nullable Ref ref, + @Nonnull Vector3i targetBlockPos, + @Nullable ItemStack itemStack, + @Nullable ItemTool tool, + @Nullable String toolId, + boolean matchTool, + float damageScale, + int setBlockSettings, + @Nonnull Ref chunkReference, + @Nonnull ComponentAccessor entityStore, + @Nonnull ComponentAccessor chunkStore + ) { + World world = entityStore.getExternalData().getWorld(); + GameplayConfig gameplayConfig = world.getGameplayConfig(); + WorldChunk worldChunkComponent = chunkStore.getComponent(chunkReference, WorldChunk.getComponentType()); + if (worldChunkComponent == null) { + return false; + } else { + BlockChunk blockChunkComponent = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + BlockSection targetSection = blockChunkComponent.getSectionAtBlockY(targetBlockPos.y); + int targetRotationIndex = targetSection.getRotationIndex(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z); + boolean brokeBlock = false; + int environmentId = blockChunkComponent.getEnvironment(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z); + Environment environmentAsset = Environment.getAssetMap().getAsset(environmentId); + SpatialResource, EntityStore> playerSpatialResource = entityStore.getResource(EntityModule.get().getPlayerSpatialResourceType()); + if (environmentAsset != null && !environmentAsset.isBlockModificationAllowed()) { + targetSection.invalidateBlock(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z); + return false; + } else { + BlockType targetBlockType = worldChunkComponent.getBlockType(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z); + if (targetBlockType == null) { + return false; + } else { + BlockGathering blockGathering = targetBlockType.getGathering(); + if (blockGathering == null) { + return false; + } else if (matchTool && !blockGathering.getToolData().containsKey(toolId)) { + return false; + } else { + Vector3d targetBlockCenterPos = new Vector3d(); + targetBlockType.getBlockCenter(targetRotationIndex, targetBlockCenterPos); + targetBlockCenterPos.add(targetBlockPos); + Vector3i originBlock = new Vector3i(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z); + if (!targetBlockType.isUnknown()) { + int filler = targetSection.getFiller(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z); + int fillerX = FillerBlockUtil.unpackX(filler); + int fillerY = FillerBlockUtil.unpackY(filler); + int fillerZ = FillerBlockUtil.unpackZ(filler); + if (fillerX != 0 || fillerY != 0 || fillerZ != 0) { + originBlock = originBlock.clone().subtract(fillerX, fillerY, fillerZ); + String oldBlockTypeKey = targetBlockType.getId(); + targetBlockType = world.getBlockType(originBlock.getX(), originBlock.getY(), originBlock.getZ()); + if (targetBlockType == null) { + return false; + } + + if (!oldBlockTypeKey.equals(targetBlockType.getId())) { + worldChunkComponent.breakBlock(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z); + return true; + } + + blockGathering = targetBlockType.getGathering(); + if (blockGathering == null) { + return false; + } + } + } + + Item heldItem = itemStack != null ? itemStack.getItem() : null; + if (tool == null && heldItem != null) { + tool = heldItem.getTool(); + } + + ItemToolSpec itemToolSpec = getSpecPowerDamageBlock(heldItem, targetBlockType, tool); + float specPower = itemToolSpec != null ? itemToolSpec.getPower() : 0.0F; + boolean canApplyItemStackPenalties = entity != null && entity.canApplyItemStackPenalties(ref, entityStore); + if (specPower != 0.0F && heldItem != null && heldItem.getTool() != null && itemStack.isBroken() && canApplyItemStackPenalties) { + BrokenPenalties brokenPenalties = gameplayConfig.getItemDurabilityConfig().getBrokenPenalties(); + specPower *= 1.0F - (float)brokenPenalties.getTool(0.0); + } + + int dropQuantity = 1; + String itemId; + String dropListId; + float damage; + if (specPower != 0.0F) { + BlockBreakingDropType breaking = blockGathering.getBreaking(); + damage = specPower; + dropQuantity = breaking.getQuantity(); + itemId = breaking.getItemId(); + dropListId = breaking.getDropListId(); + } else { + if (!blockGathering.isSoft()) { + if (heldItem != null && heldItem.getWeapon() == null) { + if (ref != null && entity != null) { + GatheringEffectsConfig unbreakableBlockConfig = gameplayConfig.getGatheringConfig().getUnbreakableBlockConfig(); + if ((setBlockSettings & 4) == 0) { + String particleSystemId = unbreakableBlockConfig.getParticleSystemId(); + if (particleSystemId != null) { + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(targetBlockCenterPos, 75.0, results); + ParticleUtil.spawnParticleEffect(particleSystemId, targetBlockCenterPos, results, entityStore); + } + } + + if ((setBlockSettings & 1024) == 0) { + int soundEventIndex = unbreakableBlockConfig.getSoundEventIndex(); + if (soundEventIndex != 0) { + SoundUtil.playSoundEvent3d(ref, soundEventIndex, targetBlockCenterPos, entityStore); + } + + if (heldItem.getTool() != null) { + int hitSoundEventLayerIndex = heldItem.getTool().getIncorrectMaterialSoundLayerIndex(); + if (hitSoundEventLayerIndex != 0) { + SoundUtil.playSoundEvent3d(ref, hitSoundEventLayerIndex, targetBlockCenterPos, entityStore); + } + } + } + } + + return false; + } + + return false; + } + + SoftBlockDropType soft = blockGathering.getSoft(); + if (!soft.isWeaponBreakable() && heldItem != null && heldItem.getWeapon() != null) { + return false; + } + + damage = 1.0F; + itemId = soft.getItemId(); + dropListId = soft.getDropListId(); + damageScale = 1.0F; + } + + damage *= damageScale; + ChunkColumn chunkColumnComponent = chunkStore.getComponent(chunkReference, ChunkColumn.getComponentType()); + Ref chunkSectionRef = chunkColumnComponent != null + ? chunkColumnComponent.getSection(ChunkUtil.chunkCoordinate(targetBlockPos.y)) + : null; + if (targetBlockType.getGathering().shouldUseDefaultDropWhenPlaced()) { + BlockPhysics decoBlocks = chunkSectionRef != null ? chunkStore.getComponent(chunkSectionRef, BlockPhysics.getComponentType()) : null; + boolean isDeco = decoBlocks != null && decoBlocks.isDeco(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z); + if (isDeco) { + itemId = null; + dropListId = null; + } + } + + TimeResource timeResource = entityStore.getResource(TimeResource.getResourceType()); + BlockHealthChunk blockHealthComponent = chunkStore.getComponent(chunkReference, BlockHealthModule.get().getBlockHealthChunkComponentType()); + + assert blockHealthComponent != null; + + float current = blockHealthComponent.getBlockHealth(originBlock); + DamageBlockEvent event = new DamageBlockEvent(itemStack, originBlock, targetBlockType, current, damage); + if (ref != null) { + entityStore.invoke(ref, event); + } else { + entityStore.invoke(event); + } + + if (event.isCancelled()) { + targetSection.invalidateBlock(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z); + return false; + } else { + damage = event.getDamage(); + targetBlockType = event.getBlockType(); + targetBlockPos = event.getTargetBlock(); + targetSection = blockChunkComponent.getSectionAtBlockY(targetBlockPos.y); + targetRotationIndex = targetSection.getRotationIndex(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z); + targetBlockType.getBlockCenter(targetRotationIndex, targetBlockCenterPos); + targetBlockCenterPos.add(targetBlockPos); + BlockHealth blockDamage = blockHealthComponent.damageBlock(timeResource.getNow(), world, targetBlockPos, damage); + if (blockHealthComponent.isBlockFragile(targetBlockPos) || blockDamage.isDestroyed()) { + BlockGathering.BlockToolData requiredTool = blockGathering.getToolData().get(toolId); + boolean toolsMatch = requiredTool != null; + if (!toolsMatch) { + performBlockBreak( + world, + targetBlockPos, + targetBlockType, + itemStack, + dropQuantity, + itemId, + dropListId, + setBlockSettings, + ref, + chunkReference, + entityStore, + chunkStore + ); + brokeBlock = true; + } else { + String toolStateId = requiredTool.getStateId(); + BlockType newBlockType = toolStateId != null ? targetBlockType.getBlockForState(toolStateId) : null; + boolean shouldChangeState = newBlockType != null && !targetBlockType.getId().equals(newBlockType.getId()); + if (shouldChangeState) { + blockDamage.setHealth(1.0F); + worldChunkComponent.setBlock(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z, newBlockType); + if ((setBlockSettings & 1024) == 0) { + BlockSoundSet soundSet = BlockSoundSet.getAssetMap().getAsset(targetBlockType.getBlockSoundSetIndex()); + if (soundSet != null) { + int soundEventIndexx = soundSet.getSoundEventIndices().getOrDefault(BlockSoundEvent.Break, 0); + if (soundEventIndexx != 0) { + SoundUtil.playSoundEvent3d(soundEventIndexx, SoundCategory.SFX, targetBlockCenterPos, entityStore); + } + } + } + + if ((setBlockSettings & 2048) == 0) { + List itemStacks = getDrops(targetBlockType, 1, requiredTool.getItemId(), requiredTool.getDropListId()); + Vector3d dropPosition = new Vector3d(targetBlockPos.x + 0.5, targetBlockPos.y, targetBlockPos.z + 0.5); + Holder[] itemEntityHolders = ItemComponent.generateItemDrops(entityStore, itemStacks, dropPosition, Vector3f.ZERO); + entityStore.addEntities(itemEntityHolders, AddReason.SPAWN); + } + } else { + performBlockBreak( + world, + targetBlockPos, + targetBlockType, + itemStack, + dropQuantity, + itemId, + dropListId, + setBlockSettings | 2048, + ref, + chunkReference, + entityStore, + chunkStore + ); + brokeBlock = true; + if ((setBlockSettings & 2048) == 0) { + List toolDrops = getDrops(targetBlockType, 1, requiredTool.getItemId(), requiredTool.getDropListId()); + if (!toolDrops.isEmpty()) { + Vector3d dropPosition = new Vector3d(targetBlockPos.x + 0.5, targetBlockPos.y, targetBlockPos.z + 0.5); + Holder[] itemEntityHolders = ItemComponent.generateItemDrops( + entityStore, toolDrops, dropPosition, Vector3f.ZERO + ); + entityStore.addEntities(itemEntityHolders, AddReason.SPAWN); + } + } + } + } + } else if ((setBlockSettings & 1024) == 0) { + BlockSoundSet soundSet = BlockSoundSet.getAssetMap().getAsset(targetBlockType.getBlockSoundSetIndex()); + if (soundSet != null) { + int soundEventIndexx = soundSet.getSoundEventIndices().getOrDefault(BlockSoundEvent.Hit, 0); + if (soundEventIndexx != 0) { + SoundUtil.playSoundEvent3d(soundEventIndexx, SoundCategory.SFX, targetBlockCenterPos, entityStore); + } + } + } + + if (ref != null && entity != null) { + if ((setBlockSettings & 1024) == 0 && !targetBlockCenterPos.equals(Vector3d.MAX)) { + int hitSoundEventLayerIndex = 0; + if (itemToolSpec != null) { + hitSoundEventLayerIndex = itemToolSpec.getHitSoundLayerIndex(); + } + + if (hitSoundEventLayerIndex == 0 && heldItem != null && heldItem.getTool() != null) { + hitSoundEventLayerIndex = heldItem.getTool().getHitSoundLayerIndex(); + } + + if (hitSoundEventLayerIndex != 0) { + SoundUtil.playSoundEvent3d( + ref, + hitSoundEventLayerIndex, + targetBlockCenterPos.getX(), + targetBlockCenterPos.getY(), + targetBlockCenterPos.getZ(), + entityStore + ); + } + } + + if (itemToolSpec != null && itemToolSpec.isIncorrect()) { + GatheringEffectsConfig incorrectToolConfig = gameplayConfig.getGatheringConfig().getIncorrectToolConfig(); + if (incorrectToolConfig != null) { + if ((setBlockSettings & 4) == 0) { + String particleSystemId = incorrectToolConfig.getParticleSystemId(); + if (particleSystemId != null) { + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(targetBlockCenterPos, 75.0, results); + ParticleUtil.spawnParticleEffect(particleSystemId, targetBlockCenterPos, results, entityStore); + } + } + + if ((setBlockSettings & 1024) == 0) { + int soundEventIndexx = incorrectToolConfig.getSoundEventIndex(); + if (soundEventIndexx != 0) { + SoundUtil.playSoundEvent3d(ref, soundEventIndexx, targetBlockCenterPos, entityStore); + } + } + } + } + } + + if (entity != null + && ref != null + && entity.canDecreaseItemStackDurability(ref, entityStore) + && itemStack != null + && !itemStack.isUnbreakable()) { + byte activeHotbarSlot = entity.getInventory().getActiveHotbarSlot(); + if (activeHotbarSlot != -1) { + double durability = calculateDurabilityUse(heldItem, targetBlockType); + ItemContainer hotbar = entity.getInventory().getHotbar(); + entity.updateItemStackDurability(ref, itemStack, hotbar, activeHotbarSlot, -durability, entityStore); + } + } + + return brokeBlock; + } + } + } + } + } + } + + public static void performBlockBreak( + @Nullable Ref ref, + @Nullable ItemStack heldItemStack, + @Nonnull Vector3i targetBlock, + @Nonnull Ref chunkReference, + @Nonnull ComponentAccessor entityStore, + @Nonnull ComponentAccessor chunkStore + ) { + performBlockBreak(ref, heldItemStack, targetBlock, 0, chunkReference, entityStore, chunkStore); + } + + public static void performBlockBreak( + @Nullable Ref ref, + @Nullable ItemStack heldItemStack, + @Nonnull Vector3i targetBlock, + int setBlockSettings, + @Nonnull Ref chunkReference, + @Nonnull ComponentAccessor entityStore, + @Nonnull ComponentAccessor chunkStore + ) { + World world = chunkStore.getExternalData().getWorld(); + int targetBlockX = targetBlock.getX(); + int targetBlockY = targetBlock.getY(); + int targetBlockZ = targetBlock.getZ(); + WorldChunk worldChunkComponent = chunkStore.getComponent(chunkReference, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + int targetBlockTypeIndex = worldChunkComponent.getBlock(targetBlockX, targetBlockY, targetBlockZ); + BlockType targetBlockTypeAsset = BlockType.getAssetMap().getAsset(targetBlockTypeIndex); + if (targetBlockTypeAsset != null) { + Vector3i affectedBlock = targetBlock; + if (!targetBlockTypeAsset.isUnknown()) { + BlockChunk blockChunkComponent = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + BlockSection targetBlockSection = blockChunkComponent.getSectionAtBlockY(targetBlockY); + int filler = targetBlockSection.getFiller(targetBlockX, targetBlockY, targetBlockZ); + int fillerX = FillerBlockUtil.unpackX(filler); + int fillerY = FillerBlockUtil.unpackY(filler); + int fillerZ = FillerBlockUtil.unpackZ(filler); + if (fillerX != 0 || fillerY != 0 || fillerZ != 0) { + affectedBlock = targetBlock.clone().subtract(fillerX, fillerY, fillerZ); + BlockType originBlock = world.getBlockType(affectedBlock); + if (originBlock != null && !targetBlockTypeAsset.getId().equals(originBlock.getId())) { + world.breakBlock(targetBlockX, targetBlockY, targetBlockZ, setBlockSettings); + return; + } + } + } + + performBlockBreak( + world, affectedBlock, targetBlockTypeAsset, heldItemStack, 0, null, null, setBlockSettings, ref, chunkReference, entityStore, chunkStore + ); + } + } + + public static void performBlockBreak( + @Nonnull World world, + @Nonnull Vector3i blockPosition, + @Nonnull BlockType targetBlockTypeKey, + @Nullable ItemStack heldItemStack, + int dropQuantity, + @Nullable String dropItemId, + @Nullable String dropListId, + int setBlockSettings, + @Nullable Ref ref, + @Nonnull Ref chunkReference, + @Nonnull ComponentAccessor entityStore, + @Nonnull ComponentAccessor chunkStore + ) { + Vector3i targetBlockPosition = blockPosition; + Ref targetChunkReference = chunkReference; + ComponentAccessor targetChunkStore = chunkStore; + if (ref != null) { + BreakBlockEvent event = new BreakBlockEvent(heldItemStack, blockPosition, targetBlockTypeKey); + entityStore.invoke(ref, event); + if (event.isCancelled()) { + BlockChunk blockChunkComponent = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + BlockSection blockSection = blockChunkComponent.getSectionAtBlockY(blockPosition.getY()); + blockSection.invalidateBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); + return; + } + + targetBlockPosition = event.getTargetBlock(); + targetChunkStore = world.getChunkStore().getStore(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(targetBlockPosition.x, targetBlockPosition.z); + targetChunkReference = targetChunkStore.getExternalData().getChunkReference(chunkIndex); + if (targetChunkReference == null || !targetChunkReference.isValid()) { + return; + } + } + + if (!targetBlockPosition.equals(blockPosition) || !world.equals(world)) { + BlockChunk blockChunkComponent = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + BlockSection blockSection = blockChunkComponent.getSectionAtBlockY(blockPosition.getY()); + blockSection.invalidateBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); + } + + int x = blockPosition.getX(); + int y = blockPosition.getY(); + int z = blockPosition.getZ(); + BlockChunk blockChunkComponent = chunkStore.getComponent(targetChunkReference, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + BlockSection blockSection = blockChunkComponent.getSectionAtBlockY(y); + int filler = blockSection.getFiller(x, y, z); + int blockTypeIndex = blockSection.get(x, y, z); + BlockType blockTypeAsset = BlockType.getAssetMap().getAsset(blockTypeIndex); + boolean isNaturalBlockBreak = BlockInteractionUtils.isNaturalAction(ref, entityStore); + setBlockSettings |= 256; + if (!isNaturalBlockBreak) { + setBlockSettings |= 2048; + } + + naturallyRemoveBlock( + targetBlockPosition, + blockTypeAsset, + filler, + dropQuantity, + dropItemId, + dropListId, + setBlockSettings, + targetChunkReference, + entityStore, + targetChunkStore + ); + } + + @Deprecated + public static void naturallyRemoveBlockByPhysics( + @Nonnull Vector3i blockPosition, + @Nonnull BlockType blockType, + int filler, + int setBlockSettings, + @Nonnull Ref chunkReference, + @Nonnull ComponentAccessor entityStore, + @Nonnull ComponentAccessor chunkStore + ) { + int quantity = 1; + String itemId = null; + String dropListId = null; + BlockGathering blockGathering = blockType.getGathering(); + if (blockGathering != null) { + PhysicsDropType physics = blockGathering.getPhysics(); + BlockBreakingDropType breaking = blockGathering.getBreaking(); + SoftBlockDropType soft = blockGathering.getSoft(); + HarvestingDropType harvest = blockGathering.getHarvest(); + if (physics != null) { + itemId = physics.getItemId(); + dropListId = physics.getDropListId(); + } else if (breaking != null) { + quantity = breaking.getQuantity(); + itemId = breaking.getItemId(); + dropListId = breaking.getDropListId(); + } else if (soft != null) { + itemId = soft.getItemId(); + dropListId = soft.getDropListId(); + } else if (harvest != null) { + itemId = harvest.getItemId(); + dropListId = harvest.getDropListId(); + } + } + + setBlockSettings |= 32; + naturallyRemoveBlock(blockPosition, blockType, filler, quantity, itemId, dropListId, setBlockSettings, chunkReference, entityStore, chunkStore); + } + + public static void naturallyRemoveBlock( + @Nonnull Vector3i blockPosition, + @Nullable BlockType blockType, + int filler, + int quantity, + String itemId, + String dropListId, + int setBlockSettings, + @Nonnull Ref chunkReference, + @Nonnull ComponentAccessor entityStore, + @Nonnull ComponentAccessor chunkStore + ) { + if (blockType != null) { + WorldChunk worldChunkComponent = chunkStore.getComponent(chunkReference, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + BlockChunk blockChunkComponent = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + Vector3i affectedBlock = blockPosition; + if (!blockType.isUnknown()) { + int fillerX = FillerBlockUtil.unpackX(filler); + int fillerY = FillerBlockUtil.unpackY(filler); + int fillerZ = FillerBlockUtil.unpackZ(filler); + if (fillerX != 0 || fillerY != 0 || fillerZ != 0) { + affectedBlock = blockPosition.clone().subtract(fillerX, fillerY, fillerZ); + String oldBlockTypeKey = blockType.getId(); + blockType = worldChunkComponent.getBlockType(affectedBlock.getX(), affectedBlock.getY(), affectedBlock.getZ()); + if (blockType == null) { + throw new IllegalStateException("Null block type fetched for " + affectedBlock + " during block break"); + } + + if (!oldBlockTypeKey.equals(blockType.getId())) { + worldChunkComponent.breakBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), setBlockSettings); + return; + } + } + } + + if ((setBlockSettings & 1024) == 0) { + BlockSoundSet soundSet = BlockSoundSet.getAssetMap().getAsset(blockType.getBlockSoundSetIndex()); + if (soundSet != null) { + int soundEventIndex = soundSet.getSoundEventIndices().getOrDefault(BlockSoundEvent.Break, 0); + if (soundEventIndex != 0) { + BlockSection section = blockChunkComponent.getSectionAtBlockY(blockPosition.getY()); + int rotationIndex = section.getRotationIndex(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); + Vector3d centerPosition = new Vector3d(); + blockType.getBlockCenter(rotationIndex, centerPosition); + centerPosition.add(blockPosition); + SoundUtil.playSoundEvent3d(soundEventIndex, SoundCategory.SFX, centerPosition, entityStore); + } + } + } + + removeBlock(affectedBlock, blockType, setBlockSettings, chunkReference, chunkStore); + if ((setBlockSettings & 2048) == 0 && quantity > 0) { + Vector3d dropPosition = blockPosition.toVector3d().add(0.5, 0.0, 0.5); + List itemStacks = getDrops(blockType, quantity, itemId, dropListId); + Holder[] itemEntityHolders = ItemComponent.generateItemDrops(entityStore, itemStacks, dropPosition, Vector3f.ZERO); + entityStore.addEntities(itemEntityHolders, AddReason.SPAWN); + } + } + } + + public static boolean shouldPickupByInteraction(@Nullable BlockType blockType) { + return blockType != null && blockType.getGathering() != null && blockType.getGathering().isHarvestable(); + } + + public static void performPickupByInteraction( + @Nonnull Ref ref, + @Nonnull Vector3i targetBlock, + @Nonnull BlockType blockType, + int filler, + @Nonnull Ref chunkReference, + @Nonnull ComponentAccessor entityStore, + @Nonnull ComponentAccessor chunkStore + ) { + WorldChunk worldChunkComponent = chunkStore.getComponent(chunkReference, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + BlockChunk blockChunkComponent = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + Vector3i affectedBlock = targetBlock; + if (!blockType.isUnknown()) { + int fillerX = FillerBlockUtil.unpackX(filler); + int fillerY = FillerBlockUtil.unpackY(filler); + int fillerZ = FillerBlockUtil.unpackZ(filler); + if (fillerX != 0 || fillerY != 0 || fillerZ != 0) { + affectedBlock = targetBlock.clone().subtract(fillerX, fillerY, fillerZ); + String oldBlockTypeKey = blockType.getId(); + blockType = worldChunkComponent.getBlockType(affectedBlock.getX(), affectedBlock.getY(), affectedBlock.getZ()); + if (blockType == null) { + return; + } + + if (!oldBlockTypeKey.equals(blockType.getId())) { + worldChunkComponent.breakBlock(targetBlock.getX(), targetBlock.getY(), targetBlock.getZ()); + return; + } + } + } + + BlockSection section = blockChunkComponent.getSectionAtBlockY(targetBlock.getY()); + Vector3d centerPosition = new Vector3d(); + int rotationIndex = section.getRotationIndex(targetBlock.getX(), targetBlock.getY(), targetBlock.getZ()); + blockType.getBlockCenter(rotationIndex, centerPosition); + centerPosition.add(targetBlock); + int setBlockSettings = 0; + setBlockSettings |= 256; + if (!BlockInteractionUtils.isNaturalAction(ref, entityStore)) { + setBlockSettings |= 2048; + } + + removeBlock(affectedBlock, blockType, setBlockSettings, chunkReference, chunkStore); + HarvestingDropType harvest = blockType.getGathering().getHarvest(); + String itemId = harvest.getItemId(); + String dropListId = harvest.getDropListId(); + + for (ItemStack itemStack : getDrops(blockType, 1, itemId, dropListId)) { + ItemUtils.interactivelyPickupItem(ref, itemStack, centerPosition, entityStore); + } + + if ((setBlockSettings & 1024) == 0) { + BlockSoundSet soundSet = BlockSoundSet.getAssetMap().getAsset(blockType.getBlockSoundSetIndex()); + if (soundSet != null) { + int soundEventIndex = soundSet.getSoundEventIndices().getOrDefault(BlockSoundEvent.Harvest, 0); + if (soundEventIndex != 0) { + SoundUtil.playSoundEvent3d(soundEventIndex, SoundCategory.SFX, centerPosition, entityStore); + } + } + } + } + + protected static void removeBlock( + @Nonnull Vector3i blockPosition, + @Nonnull BlockType blockType, + int setBlockSettings, + @Nonnull Ref chunkReference, + @Nonnull ComponentAccessor chunkStore + ) { + World world = chunkStore.getExternalData().getWorld(); + ComponentType blockHealthComponentType = BlockHealthModule.get().getBlockHealthChunkComponentType(); + BlockHealthChunk blockHealthComponent = chunkStore.getComponent(chunkReference, blockHealthComponentType); + + assert blockHealthComponent != null; + + blockHealthComponent.removeBlock(world, blockPosition); + WorldChunk worldChunkComponent = chunkStore.getComponent(chunkReference, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + BlockChunk blockChunkComponent = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + worldChunkComponent.breakBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), setBlockSettings); + if ((setBlockSettings & 256) != 0) { + BlockSection section = blockChunkComponent.getSectionAtBlockY(blockPosition.y); + int rotationIndex = section.getRotationIndex(blockPosition.x, blockPosition.y, blockPosition.z); + BlockBoundingBoxes hitBoxType = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex()); + if (hitBoxType != null) { + FillerBlockUtil.forEachFillerBlock( + hitBoxType.get(rotationIndex), + (x, y, z) -> world.performBlockUpdate(blockPosition.getX() + x, blockPosition.getY() + y, blockPosition.getZ() + z, false) + ); + } + } + + ConnectedBlocksUtil.setConnectedBlockAndNotifyNeighbors( + BlockType.getAssetMap().getIndex("Empty"), RotationTuple.NONE, Vector3i.ZERO, blockPosition, worldChunkComponent, blockChunkComponent + ); + } + + @Nonnull + public static List getDrops(@Nonnull BlockType blockType, int quantity, @Nullable String itemId, @Nullable String dropListId) { + if (dropListId == null && itemId == null) { + Item item = blockType.getItem(); + return item == null ? ObjectLists.emptyList() : ObjectLists.singleton(new ItemStack(item.getId(), quantity)); + } else { + List randomItemDrops = new ObjectArrayList<>(); + if (dropListId != null) { + ItemModule itemModule = ItemModule.get(); + if (itemModule.isEnabled()) { + for (int i = 0; i < quantity; i++) { + List randomItemsToDrop = itemModule.getRandomItemDrops(dropListId); + randomItemDrops.addAll(randomItemsToDrop); + } + } + } + + if (itemId != null) { + randomItemDrops.add(new ItemStack(itemId, quantity)); + } + + return randomItemDrops; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/BlockInteractionUtils.java b/src/com/hypixel/hytale/server/core/modules/interaction/BlockInteractionUtils.java new file mode 100644 index 0000000..bc89135 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/BlockInteractionUtils.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.core.modules.interaction; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockInteractionUtils { + public BlockInteractionUtils() { + } + + public static boolean isNaturalAction(@Nullable Ref ref, @Nonnull ComponentAccessor componentAccessor) { + if (ref == null) { + return true; + } else { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + return playerComponent != null ? playerComponent.getGameMode() == GameMode.Adventure : true; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/BlockPlaceUtils.java b/src/com/hypixel/hytale/server/core/modules/interaction/BlockPlaceUtils.java new file mode 100644 index 0000000..0084a5d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/BlockPlaceUtils.java @@ -0,0 +1,447 @@ +package com.hypixel.hytale.server.core.modules.interaction; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.protocol.BlockRotation; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockGathering; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockPlacementSettings; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.SoftBlockDropType; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.PrefabListAsset; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.core.asset.type.gameplay.WorldConfig; +import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.event.events.ecs.PlaceBlockEvent; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; +import com.hypixel.hytale.server.core.modules.interaction.components.PlacedByInteractionComponent; +import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabBufferUtil; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.connectedblocks.ConnectedBlocksUtil; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.meta.state.PlacedByBlockState; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import com.hypixel.hytale.server.core.util.PrefabUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Random; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class BlockPlaceUtils { + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private static final Message MESSAGE_MODULES_INTERACTION_FAILED_ADD_BACK_AFTER_FAILED_PLACE = Message.translation( + "server.modules.interaction.failedAddBackAfterFailedPlace" + ); + @Nonnull + private static final Message MESSAGE_MODULES_INTERACTION_FAILED_CHECK_BLOCK = Message.translation("server.modules.interaction.failedCheckBlock"); + @Nonnull + private static final Message MESSAGE_MODULES_INTERACTION_FAILED_CHECK_EMPTY = Message.translation("server.modules.interaction.failedCheckEmpty"); + @Nonnull + private static final Message MESSAGE_MODULES_INTERACTION_FAILED_CHECK_UNKNOWN = Message.translation("server.modules.interaction.failedCheckUnknown"); + @Nonnull + private static final Message MESSAGE_MODULES_INTERACTION_FAILED_CHECK = Message.translation("server.modules.interaction.failedCheck"); + @Nonnull + private static final Message MESSAGE_MODULES_INTERACTION_BUILD_FORBIDDEN = Message.translation("server.modules.interaction.buildForbidden"); + + public BlockPlaceUtils() { + } + + public static void placeBlock( + @Nonnull Ref ref, + @Nonnull ItemStack itemStack, + @Nullable String blockTypeKey, + @Nonnull ItemContainer itemContainer, + @Nonnull Vector3i placementNormal, + @Nonnull Vector3i blockPosition, + @Nonnull BlockRotation blockRotation, + @Nullable Inventory inventory, + byte activeSlot, + boolean removeItemInHand, + @Nonnull Ref chunkReference, + @Nonnull ComponentAccessor chunkStore, + @Nonnull ComponentAccessor entityStore + ) { + if (blockPosition.getY() >= 0 && blockPosition.getY() < 320) { + Ref targetChunkReference = chunkReference; + RotationTuple targetRotation = RotationTuple.of( + Rotation.valueOf(blockRotation.rotationYaw), Rotation.valueOf(blockRotation.rotationPitch), Rotation.valueOf(blockRotation.rotationRoll) + ); + BlockChunk targetBlockChunkComponent = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType()); + + assert targetBlockChunkComponent != null; + + BlockSection targetBlockSection = targetBlockChunkComponent.getSectionAtBlockY(blockPosition.getY()); + PlaceBlockEvent event = new PlaceBlockEvent(itemStack, blockPosition, targetRotation); + entityStore.invoke(ref, event); + if (event.isCancelled()) { + targetBlockSection.invalidateBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); + } else { + Vector3i targetBlockPosition = event.getTargetBlock(); + targetRotation = event.getRotation(); + boolean positionIsDifferent = !ChunkUtil.isSameChunk(targetBlockPosition.x, targetBlockPosition.z, blockPosition.x, blockPosition.z); + if (positionIsDifferent) { + long chunkIndex = ChunkUtil.indexChunkFromBlock(targetBlockPosition.x, targetBlockPosition.z); + targetChunkReference = chunkStore.getExternalData().getChunkReference(chunkIndex); + if (targetChunkReference == null || !targetChunkReference.isValid()) { + return; + } + } + + if (positionIsDifferent) { + targetBlockSection.invalidateBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); + } + + if (!targetChunkReference.equals(chunkReference) || targetBlockPosition.y != blockPosition.y) { + targetBlockChunkComponent = chunkStore.getComponent(targetChunkReference, BlockChunk.getComponentType()); + + assert targetBlockChunkComponent != null; + + targetBlockSection = targetBlockChunkComponent.getSectionAtBlockY(targetBlockPosition.getY()); + } + + PlayerRef playerRefComponent = entityStore.getComponent(ref, PlayerRef.getComponentType()); + Player playerComponent = entityStore.getComponent(ref, Player.getComponentType()); + boolean isAdventureMode = playerComponent == null || playerComponent.getGameMode() == GameMode.Adventure; + if (isAdventureMode && removeItemInHand) { + ItemStackSlotTransaction transaction = itemContainer.removeItemStackFromSlot(activeSlot, itemStack, 1); + if (!transaction.succeeded()) { + if (playerRefComponent != null) { + playerRefComponent.sendMessage(MESSAGE_MODULES_INTERACTION_FAILED_CHECK); + } + + return; + } + + itemStack = transaction.getOutput(); + } + + if (blockTypeKey == null && itemStack != null) { + blockTypeKey = itemStack.getBlockKey(); + } + + if (validateBlockToPlace(blockTypeKey, playerRefComponent)) { + assert blockTypeKey != null; + + BlockType blockTypeAsset = BlockType.getAssetMap().getAsset(blockTypeKey); + if (blockTypeAsset != null) { + String prefabListAssetId = blockTypeAsset.getPrefabListAssetId(); + if (prefabListAssetId != null && !validateAndPlacePrefab(blockPosition, prefabListAssetId, playerRefComponent, entityStore)) { + return; + } + } + + WorldChunk worldChunkComponent = chunkStore.getComponent(targetChunkReference, WorldChunk.getComponentType()); + if (worldChunkComponent != null) { + boolean success = tryPlaceBlock( + ref, + placementNormal, + targetBlockPosition, + blockTypeKey, + targetRotation, + worldChunkComponent, + targetBlockChunkComponent, + chunkReference, + chunkStore, + entityStore + ); + if (success) { + onPlaceBlockSuccess(itemStack, worldChunkComponent, targetBlockPosition); + } else { + onPlaceBlockFailure(itemStack, inventory, activeSlot, playerComponent, targetBlockSection, targetBlockPosition); + } + } + } + } + } + } + + private static void onPlaceBlockFailure( + @Nullable ItemStack itemStack, + @Nullable Inventory inventory, + byte activeSlot, + @Nullable Player playerComponent, + @Nonnull BlockSection blockSection, + @Nonnull Vector3i blockPosition + ) { + boolean isAdventure = playerComponent == null || playerComponent.getGameMode() == GameMode.Adventure; + if (inventory != null && itemStack != null && isAdventure) { + ItemContainer hotbar = inventory.getHotbar(); + ItemStackSlotTransaction transaction = hotbar.addItemStackToSlot(activeSlot, itemStack); + if (!transaction.succeeded()) { + ItemStackTransaction itemStackTransaction = hotbar.addItemStack(itemStack); + if (!itemStackTransaction.succeeded() && playerComponent != null) { + playerComponent.sendMessage(MESSAGE_MODULES_INTERACTION_FAILED_ADD_BACK_AFTER_FAILED_PLACE); + } + + return; + } + } + + blockSection.invalidateBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); + } + + private static void onPlaceBlockSuccess(@Nullable ItemStack itemStack, @Nonnull WorldChunk worldChunkComponent, @Nonnull Vector3i blockPosition) { + if (itemStack != null) { + BsonDocument metadata = itemStack.getMetadata(); + if (metadata != null) { + BsonValue bsonValue = metadata.get("BlockState"); + if (bsonValue != null) { + try { + BsonDocument document = bsonValue.asDocument(); + BlockState blockState = BlockState.load(document, worldChunkComponent, blockPosition.clone()); + if (blockState != null) { + worldChunkComponent.setState(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), blockState); + } else { + LOGGER.at(Level.WARNING).log("Failed to set BlockState from item metadata: %s, %s", itemStack.getItemId(), document); + } + } catch (Exception var7) { + throw SneakyThrow.sneakyThrow(var7); + } + } + } + } + } + + private static boolean validateBlockToPlace(@Nullable String blockTypeKey, @Nullable PlayerRef playerRefComponent) { + if (blockTypeKey == null) { + if (playerRefComponent != null) { + playerRefComponent.sendMessage(MESSAGE_MODULES_INTERACTION_FAILED_CHECK_BLOCK); + } + + return false; + } else if (blockTypeKey.equals("Empty")) { + if (playerRefComponent != null) { + playerRefComponent.sendMessage(MESSAGE_MODULES_INTERACTION_FAILED_CHECK_EMPTY); + } + + return false; + } else if (blockTypeKey.equals("Unknown")) { + if (playerRefComponent != null) { + playerRefComponent.sendMessage(MESSAGE_MODULES_INTERACTION_FAILED_CHECK_UNKNOWN); + } + + return false; + } else { + return true; + } + } + + private static boolean validateAndPlacePrefab( + @Nonnull Vector3i blockPosition, + @Nonnull String prefabListAssetId, + @Nullable PlayerRef playerRefComponent, + @Nonnull ComponentAccessor componentAccessor + ) { + PrefabListAsset prefabListAsset = PrefabListAsset.getAssetMap().getAsset(prefabListAssetId); + if (prefabListAsset == null) { + if (playerRefComponent != null) { + playerRefComponent.sendMessage(Message.translation("server.modules.interaction.placeBlock.prefabListNotFound").param("name", prefabListAssetId)); + } + + return false; + } else { + Path randomPrefab = prefabListAsset.getRandomPrefab(); + if (randomPrefab == null) { + if (playerRefComponent != null) { + playerRefComponent.sendMessage(Message.translation("server.modules.interaction.placeBlock.prefabListEmpty").param("name", prefabListAssetId)); + } + + return false; + } else if (!Files.exists(randomPrefab)) { + if (playerRefComponent != null) { + playerRefComponent.sendMessage(Message.translation("server.commands.editprefab.prefabNotFound").param("name", randomPrefab.toString())); + } + + return false; + } else { + World world = componentAccessor.getExternalData().getWorld(); + PrefabBuffer prefabBuffer = PrefabBufferUtil.loadBuffer(randomPrefab); + world.execute(() -> { + Store store = world.getEntityStore().getStore(); + PrefabBuffer.PrefabBufferAccessor prefabBufferAccessor = prefabBuffer.newAccess(); + PrefabUtil.paste(prefabBufferAccessor, world, blockPosition, Rotation.None, true, new Random(), store); + prefabBufferAccessor.release(); + }); + return true; + } + } + } + + private static boolean tryPlaceBlock( + @Nonnull Ref ref, + @Nonnull Vector3i placementNormal, + @Nonnull Vector3i blockPosition, + @Nonnull String blockTypeKey, + @Nonnull RotationTuple rotation, + @Nonnull WorldChunk worldChunkComponent, + @Nonnull BlockChunk blockChunkComponent, + @Nonnull Ref chunkReference, + @Nonnull ComponentAccessor chunkStore, + @Nonnull ComponentAccessor entityStore + ) { + WorldConfig worldConfig = entityStore.getExternalData().getWorld().getGameplayConfig().getWorldConfig(); + if (!worldConfig.isBlockPlacementAllowed()) { + return false; + } else { + Player playerComponent = entityStore.getComponent(ref, Player.getComponentType()); + PlayerRef playerRefComponent = entityStore.getComponent(ref, PlayerRef.getComponentType()); + boolean isAdventure = playerComponent == null || playerComponent.getGameMode() == GameMode.Adventure; + if (isAdventure) { + int environmentId = blockChunkComponent.getEnvironment(blockPosition); + Environment environment = Environment.getAssetMap().getAsset(environmentId); + if (environment != null && !environment.isBlockModificationAllowed()) { + if (playerRefComponent != null) { + playerRefComponent.sendMessage(MESSAGE_MODULES_INTERACTION_BUILD_FORBIDDEN); + } + + return false; + } + } + + BlockType blockType = BlockType.getAssetMap().getAsset(blockTypeKey); + int rotationIndex = rotation.index(); + if (blockType != null + && worldChunkComponent.testPlaceBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), blockType, rotationIndex)) { + BlockBoundingBoxes hitBoxType = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex()); + if (hitBoxType != null) { + FillerBlockUtil.forEachFillerBlock( + hitBoxType.get(rotationIndex), + (x1, y1, z1) -> breakAndDropReplacedBlock( + blockPosition.clone().add(x1, y1, z1), worldChunkComponent, chunkReference, ref, chunkStore, entityStore + ) + ); + } else { + breakAndDropReplacedBlock(blockPosition, worldChunkComponent, chunkReference, ref, chunkStore, entityStore); + } + + int placeBlockSettings = 10; + if (!worldChunkComponent.placeBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), blockTypeKey, rotation, 10, false)) { + return false; + } else { + if (playerComponent != null && !playerComponent.isOverrideBlockPlacementRestrictions() && blockType.canBePlacedAsDeco()) { + ChunkColumn chunkColumnComponent = chunkStore.getComponent(chunkReference, ChunkColumn.getComponentType()); + + assert chunkColumnComponent != null; + + Ref sectionRef = chunkColumnComponent.getSection(ChunkUtil.chunkCoordinate(blockPosition.y)); + if (sectionRef != null && sectionRef.isValid()) { + BlockPhysics.markDeco(chunkStore, sectionRef, blockPosition.x, blockPosition.y, blockPosition.z); + } + } + + BlockState blockState = worldChunkComponent.getState(blockPosition.x, blockPosition.y, blockPosition.z); + if (blockState instanceof PlacedByBlockState placedByBlockState) { + placedByBlockState.placedBy(ref, blockTypeKey, blockState, entityStore); + } + + int blockIndexInChunk = ChunkUtil.indexBlockInColumn(blockPosition.x, blockPosition.y, blockPosition.z); + BlockComponentChunk blockComponentChunk = worldChunkComponent.getBlockComponentChunk(); + Ref blockRef = blockComponentChunk == null ? null : blockComponentChunk.getEntityReference(blockIndexInChunk); + if (blockRef != null) { + UUIDComponent uuidComponent = entityStore.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + PlacedByInteractionComponent placedByInteractionComponent = new PlacedByInteractionComponent(uuidComponent.getUuid()); + chunkStore.putComponent(blockRef, PlacedByInteractionComponent.getComponentType(), placedByInteractionComponent); + } + + ConnectedBlocksUtil.setConnectedBlockAndNotifyNeighbors( + BlockType.getAssetMap().getIndex(blockTypeKey), rotation, placementNormal, blockPosition, worldChunkComponent, blockChunkComponent + ); + return true; + } + } else { + return false; + } + } + } + + private static void breakAndDropReplacedBlock( + @Nonnull Vector3i blockPosition, + @Nonnull WorldChunk worldChunkComponent, + @Nonnull Ref chunkReference, + @Nonnull Ref ref, + @Nonnull ComponentAccessor chunkStore, + @Nonnull ComponentAccessor entityStore + ) { + BlockType existingBlock = worldChunkComponent.getBlockType(blockPosition); + if (existingBlock != null) { + if (existingBlock.getMaterial() != BlockMaterial.Empty) { + return; + } + + BlockGathering gathering = existingBlock.getGathering(); + int dropQuantity = 1; + String itemId = null; + String dropListId = null; + if (gathering != null) { + SoftBlockDropType softGathering = gathering.getSoft(); + if (softGathering != null) { + itemId = softGathering.getItemId(); + dropListId = softGathering.getDropListId(); + } + } + + int setBlockSettings = 288; + BlockHarvestUtils.performBlockBreak( + chunkStore.getExternalData().getWorld(), + blockPosition, + existingBlock, + null, + dropQuantity, + itemId, + dropListId, + 288, + ref, + chunkReference, + entityStore, + chunkStore + ); + } + } + + public static boolean canPlaceBlock(@Nonnull BlockType blockType, @Nonnull String placedBlockTypeKey) { + if (blockType.getId().equals(placedBlockTypeKey)) { + return true; + } else { + BlockPlacementSettings placementSettings = blockType.getPlacementSettings(); + return placementSettings == null + ? false + : placedBlockTypeKey.equals(placementSettings.getWallPlacementOverrideBlockId()) + || placedBlockTypeKey.equals(placementSettings.getFloorPlacementOverrideBlockId()) + || placedBlockTypeKey.equals(placementSettings.getCeilingPlacementOverrideBlockId()); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/IInteractionSimulationHandler.java b/src/com/hypixel/hytale/server/core/modules/interaction/IInteractionSimulationHandler.java new file mode 100644 index 0000000..32a0cf2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/IInteractionSimulationHandler.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.server.core.modules.interaction; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public interface IInteractionSimulationHandler { + void setState(InteractionType var1, boolean var2); + + boolean isCharging(boolean var1, float var2, InteractionType var3, InteractionContext var4, Ref var5, CooldownHandler var6); + + boolean shouldCancelCharging(boolean var1, float var2, InteractionType var3, InteractionContext var4, Ref var5, CooldownHandler var6); + + float getChargeValue(boolean var1, float var2, InteractionType var3, InteractionContext var4, Ref var5, CooldownHandler var6); +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/InteractionModule.java b/src/com/hypixel/hytale/server/core/modules/interaction/InteractionModule.java new file mode 100644 index 0000000..38b2ee3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/InteractionModule.java @@ -0,0 +1,463 @@ +package com.hypixel.hytale.server.core.modules.interaction; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.assetstore.event.RemovedAssetsEvent; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.set.SetCodec; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.event.EventRegistry; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.MouseButtonType; +import com.hypixel.hytale.protocol.WorldInteraction; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.player.MouseInteraction; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.itemanimation.config.ItemPlayerAnimations; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.trail.config.Trail; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.CameraManager; +import com.hypixel.hytale.server.core.event.events.player.PlayerMouseButtonEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerMouseMotionEvent; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.hitboxcollision.HitboxCollisionConfig; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.interaction.blocktrack.BlockCounter; +import com.hypixel.hytale.server.core.modules.interaction.blocktrack.TrackedPlacement; +import com.hypixel.hytale.server.core.modules.interaction.commands.InteractionCommand; +import com.hypixel.hytale.server.core.modules.interaction.components.PlacedByInteractionComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.InteractionPacketGenerator; +import com.hypixel.hytale.server.core.modules.interaction.interaction.RootInteractionPacketGenerator; +import com.hypixel.hytale.server.core.modules.interaction.interaction.UnarmedInteractions; +import com.hypixel.hytale.server.core.modules.interaction.interaction.UnarmedInteractionsPacketGenerator; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.AddItemInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.ApplyForceInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.BlockConditionInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.BreakBlockInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.ChainingInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.ChangeBlockInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.ChangeStateInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.ChargingInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.CooldownConditionInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.CycleBlockGroupInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.DestroyBlockInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.ExplodeInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.FirstClickInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.IncrementCooldownInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.MovementConditionInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.PickBlockInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.PlaceBlockInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.PlaceFluidInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.ResetCooldownInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.ToggleGliderInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.TriggerCooldownInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.UseBlockInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.UseEntityInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.WieldingInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.BuilderToolInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.CameraInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.CancelChainInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.ChainFlagInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.ChangeActiveSlotInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.ConditionInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.EffectConditionInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.ParallelInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.RepeatInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.ReplaceInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.RunRootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.SelectInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.SerialInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.StatsConditionInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.StatsConditionWithModifierInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.simple.ApplyEffectInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.simple.RemoveEntityInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.simple.SendMessageInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector.AOECircleSelector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector.AOECylinderSelector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector.HorizontalSelector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector.PlayerMatcher; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector.RaycastSelector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector.SelectorType; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector.StabSelector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector.VulnerableMatcher; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.ChangeStatInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.ChangeStatWithModifierInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.CheckUniqueItemUsageInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.ClearEntityEffectInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.DamageEntityInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.DestroyConditionInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.DoorInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.EquipItemInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.IncreaseBackpackCapacityInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.InterruptInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.LaunchPadInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.LaunchProjectileInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.ModifyInventoryInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenContainerInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenCustomUIInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenItemStackContainerInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenPageInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.PlacementCountConditionInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.RefillContainerInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.SpawnPrefabInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.DirectionalKnockback; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.ForceKnockback; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.Knockback; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.PointKnockback; +import com.hypixel.hytale.server.core.modules.interaction.suppliers.ItemRepairPageSupplier; +import com.hypixel.hytale.server.core.modules.interaction.system.InteractionSystems; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.meta.state.LaunchPad; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.EnumSet; +import java.util.List; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class InteractionModule extends JavaPlugin { + @Nonnull + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(InteractionModule.class).depends(EntityModule.class).build(); + @Nonnull + public static final EnumCodec INTERACTION_TYPE_CODEC = new EnumCodec<>(InteractionType.class); + @Nonnull + public static final SetCodec> INTERACTION_TYPE_SET_CODEC = new SetCodec<>( + INTERACTION_TYPE_CODEC, () -> EnumSet.noneOf(InteractionType.class), true + ); + private static InteractionModule instance; + private ComponentType interactionManagerComponent; + private ComponentType chainingDataComponent; + private ComponentType interactionsComponentType; + private ComponentType placedByComponentType; + private ResourceType blockCounterResourceType; + private ComponentType trackedPlacementComponentType; + + @Nonnull + public static InteractionModule get() { + return instance; + } + + public InteractionModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + EventRegistry eventRegistry = this.getEventRegistry(); + ComponentRegistryProxy entityStoreRegistry = this.getEntityStoreRegistry(); + this.getCommandRegistry().registerCommand(new InteractionCommand()); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + UnarmedInteractions.class, new DefaultAssetMap() + ) + .setPath("Item/Unarmed/Interactions")) + .setCodec(UnarmedInteractions.CODEC)) + .setKeyFunction(UnarmedInteractions::getId)) + .loadsAfter(RootInteraction.class)) + .setPacketGenerator(new UnarmedInteractionsPacketGenerator()) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + Interaction.class, new IndexedLookupTableAssetMap<>(Interaction[]::new) + ) + .setPath("Item/Interactions")) + .setCodec(Interaction.CODEC)) + .setKeyFunction(Interaction::getId)) + .setReplaceOnRemove(id -> new SendMessageInteraction(id, "Missing interaction: " + id))) + .setIsUnknown(Interaction::isUnknown)) + .setPacketGenerator(new InteractionPacketGenerator()) + .loadsAfter( + EntityStatType.class, + EntityEffect.class, + Trail.class, + ItemPlayerAnimations.class, + SoundEvent.class, + ParticleSystem.class, + ModelAsset.class, + HitboxCollisionConfig.class + )) + .preLoadAssets(List.of(ChangeActiveSlotInteraction.DEFAULT_INTERACTION))) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + RootInteraction.class, new IndexedLookupTableAssetMap<>(RootInteraction[]::new) + ) + .setPath("Item/RootInteractions")) + .setCodec(RootInteraction.CODEC)) + .setKeyFunction(RootInteraction::getId)) + .setReplaceOnRemove(x$0 -> new RootInteraction(x$0))) + .setPacketGenerator(new RootInteractionPacketGenerator()) + .loadsAfter(Interaction.class)) + .loadsBefore(BlockType.class, Item.class)) + .preLoadAssets(List.of(ChangeActiveSlotInteraction.DEFAULT_ROOT))) + .build() + ); + this.interactionManagerComponent = this.getEntityStoreRegistry().registerComponent(InteractionManager.class, () -> { + throw new UnsupportedOperationException(); + }); + this.interactionsComponentType = this.getEntityStoreRegistry().registerComponent(Interactions.class, "Interactions", Interactions.CODEC); + this.placedByComponentType = this.getChunkStoreRegistry() + .registerComponent(PlacedByInteractionComponent.class, "PlacedByInteraction", PlacedByInteractionComponent.CODEC); + Interaction.CODEC.register("Simple", SimpleInteraction.class, SimpleInteraction.CODEC); + Interaction.CODEC.register("PlaceBlock", PlaceBlockInteraction.class, PlaceBlockInteraction.CODEC); + Interaction.CODEC.register("PlaceFluid", PlaceFluidInteraction.class, PlaceFluidInteraction.CODEC); + Interaction.CODEC.register("BreakBlock", BreakBlockInteraction.class, BreakBlockInteraction.CODEC); + Interaction.CODEC.register("PickBlock", PickBlockInteraction.class, PickBlockInteraction.CODEC); + Interaction.CODEC.register("UseBlock", UseBlockInteraction.class, UseBlockInteraction.CODEC); + Interaction.CODEC.register("BlockCondition", BlockConditionInteraction.class, BlockConditionInteraction.CODEC); + Interaction.CODEC.register("ChangeBlock", ChangeBlockInteraction.class, ChangeBlockInteraction.CODEC); + Interaction.CODEC.register("ChangeState", ChangeStateInteraction.class, ChangeStateInteraction.CODEC); + Interaction.CODEC.register("UseEntity", UseEntityInteraction.class, UseEntityInteraction.CODEC); + Interaction.CODEC.register("BuilderTool", BuilderToolInteraction.class, BuilderToolInteraction.CODEC); + Interaction.CODEC.register("ModifyInventory", ModifyInventoryInteraction.class, ModifyInventoryInteraction.CODEC); + Interaction.CODEC.register("Charging", ChargingInteraction.class, ChargingInteraction.CODEC); + Interaction.CODEC.register("DestroyBlock", DestroyBlockInteraction.class, DestroyBlockInteraction.CODEC); + Interaction.CODEC.register("CycleBlockGroup", CycleBlockGroupInteraction.class, CycleBlockGroupInteraction.CODEC); + Interaction.CODEC.register("Explode", ExplodeInteraction.class, ExplodeInteraction.CODEC); + Interaction.CODEC.register("Chaining", ChainingInteraction.class, ChainingInteraction.CODEC); + Interaction.CODEC.register("ChainFlag", ChainFlagInteraction.class, ChainFlagInteraction.CODEC); + Interaction.CODEC.register("CancelChain", CancelChainInteraction.class, CancelChainInteraction.CODEC); + this.chainingDataComponent = this.getEntityStoreRegistry().registerComponent(ChainingInteraction.Data.class, ChainingInteraction.Data::new); + Interaction.CODEC.register("Condition", ConditionInteraction.class, ConditionInteraction.CODEC); + Interaction.CODEC.register("FirstClick", FirstClickInteraction.class, FirstClickInteraction.CODEC); + Interaction.CODEC.register("Repeat", RepeatInteraction.class, RepeatInteraction.CODEC); + Interaction.CODEC.register("Parallel", ParallelInteraction.class, ParallelInteraction.CODEC); + Interaction.CODEC.register("Serial", SerialInteraction.class, SerialInteraction.CODEC); + Interaction.CODEC.register("ChangeActiveSlot", ChangeActiveSlotInteraction.class, ChangeActiveSlotInteraction.CODEC); + Interaction.CODEC.register("Selector", SelectInteraction.class, SelectInteraction.CODEC); + Interaction.CODEC.register("DamageEntity", DamageEntityInteraction.class, DamageEntityInteraction.CODEC); + Interaction.CODEC.register("LaunchProjectile", LaunchProjectileInteraction.class, LaunchProjectileInteraction.CODEC); + Interaction.CODEC.register("Wielding", WieldingInteraction.class, WieldingInteraction.CODEC); + Interaction.CODEC.register("Replace", ReplaceInteraction.class, ReplaceInteraction.CODEC); + Interaction.CODEC.register("StatsCondition", StatsConditionInteraction.class, StatsConditionInteraction.CODEC); + Interaction.CODEC.register("StatsConditionWithModifier", StatsConditionWithModifierInteraction.class, StatsConditionWithModifierInteraction.CODEC); + Interaction.CODEC.register("SpawnPrefab", SpawnPrefabInteraction.class, SpawnPrefabInteraction.CODEC); + Interaction.CODEC.register("SendMessage", SendMessageInteraction.class, SendMessageInteraction.CODEC); + Interaction.CODEC.register("EquipItem", EquipItemInteraction.class, EquipItemInteraction.CODEC); + Interaction.CODEC.register("RefillContainer", RefillContainerInteraction.class, RefillContainerInteraction.CODEC); + Interaction.CODEC.register("Door", DoorInteraction.class, DoorInteraction.CODEC); + Interaction.CODEC.register("IncreaseBackpackCapacity", IncreaseBackpackCapacityInteraction.class, IncreaseBackpackCapacityInteraction.CODEC); + Interaction.CODEC.register("CheckUniqueItemUsage", CheckUniqueItemUsageInteraction.class, CheckUniqueItemUsageInteraction.CODEC); + Interaction.CODEC.register("LaunchPad", LaunchPadInteraction.class, LaunchPadInteraction.CODEC); + Interaction.CODEC.register("OpenContainer", OpenContainerInteraction.class, OpenContainerInteraction.CODEC); + Interaction.CODEC.register("OpenItemStackContainer", OpenItemStackContainerInteraction.class, OpenItemStackContainerInteraction.CODEC); + Interaction.CODEC.register("DestroyCondition", DestroyConditionInteraction.class, DestroyConditionInteraction.CODEC); + Interaction.CODEC.register("OpenCustomUI", OpenCustomUIInteraction.class, OpenCustomUIInteraction.CODEC); + Interaction.CODEC.register("OpenPage", OpenPageInteraction.class, OpenPageInteraction.CODEC); + Interaction.CODEC.register("ApplyEffect", ApplyEffectInteraction.class, ApplyEffectInteraction.CODEC); + Interaction.CODEC.register("ClearEntityEffect", ClearEntityEffectInteraction.class, ClearEntityEffectInteraction.CODEC); + Interaction.CODEC.register("RemoveEntity", RemoveEntityInteraction.class, RemoveEntityInteraction.CODEC); + Interaction.CODEC.register("EffectCondition", EffectConditionInteraction.class, EffectConditionInteraction.CODEC); + Interaction.CODEC.register("ApplyForce", ApplyForceInteraction.class, ApplyForceInteraction.CODEC); + Interaction.CODEC.register("ChangeStat", ChangeStatInteraction.class, ChangeStatInteraction.CODEC); + Interaction.CODEC.register("ChangeStatWithModifier", ChangeStatWithModifierInteraction.class, ChangeStatWithModifierInteraction.CODEC); + Interaction.CODEC.register("MovementCondition", MovementConditionInteraction.class, MovementConditionInteraction.CODEC); + Interaction.CODEC.register("ResetCooldown", ResetCooldownInteraction.class, ResetCooldownInteraction.CODEC); + Interaction.CODEC.register("TriggerCooldown", TriggerCooldownInteraction.class, TriggerCooldownInteraction.CODEC); + Interaction.CODEC.register("CooldownCondition", CooldownConditionInteraction.class, CooldownConditionInteraction.CODEC); + Interaction.CODEC.register("IncrementCooldown", IncrementCooldownInteraction.class, IncrementCooldownInteraction.CODEC); + Interaction.CODEC.register("AddItem", AddItemInteraction.class, AddItemInteraction.CODEC); + Interaction.CODEC.register("Interrupt", InterruptInteraction.class, InterruptInteraction.CODEC); + Interaction.CODEC.register("RunRootInteraction", RunRootInteraction.class, RunRootInteraction.CODEC); + Interaction.CODEC.register("Camera", CameraInteraction.class, CameraInteraction.CODEC); + Interaction.CODEC.register("ToggleGlider", ToggleGliderInteraction.class, ToggleGliderInteraction.CODEC); + OpenCustomUIInteraction.registerBlockEntityCustomPage( + this, + LaunchPad.LaunchPadSettingsPage.class, + "LaunchPad", + (playerRef, ref) -> ref.getStore().getArchetype(ref).contains(LaunchPad.getComponentType()) + ? new LaunchPad.LaunchPadSettingsPage(playerRef, ref, CustomPageLifetime.CanDismissOrCloseThroughInteraction) + : null + ); + OpenCustomUIInteraction.PAGE_CODEC.register("ItemRepair", ItemRepairPageSupplier.class, ItemRepairPageSupplier.CODEC); + SelectorType.CODEC.register("Horizontal", HorizontalSelector.class, HorizontalSelector.CODEC); + SelectorType.CODEC.register("Stab", StabSelector.class, StabSelector.CODEC); + SelectorType.CODEC.register("AOECircle", AOECircleSelector.class, AOECircleSelector.CODEC); + SelectorType.CODEC.register("AOECylinder", AOECylinderSelector.class, AOECylinderSelector.CODEC); + SelectorType.CODEC.register("Raycast", RaycastSelector.class, RaycastSelector.CODEC); + Knockback.CODEC.register("Directional", DirectionalKnockback.class, DirectionalKnockback.CODEC); + Knockback.CODEC.register("Point", PointKnockback.class, PointKnockback.CODEC); + Knockback.CODEC.register("Force", ForceKnockback.class, ForceKnockback.CODEC); + eventRegistry.register(LoadedAssetsEvent.class, RootInteraction.class, InteractionModule::handledLoadedRootInteractions); + eventRegistry.register(LoadedAssetsEvent.class, Interaction.class, InteractionModule::handledLoadedInteractions); + eventRegistry.register(RemovedAssetsEvent.class, Interaction.class, InteractionModule::handledRemovedInteractions); + entityStoreRegistry.registerSystem(new InteractionSystems.PlayerAddManagerSystem()); + entityStoreRegistry.registerSystem(new InteractionSystems.CleanUpSystem()); + entityStoreRegistry.registerSystem(new InteractionSystems.TickInteractionManagerSystem()); + entityStoreRegistry.registerSystem(new InteractionSystems.TrackerTickSystem()); + entityStoreRegistry.registerSystem(new InteractionSystems.EntityTrackerRemove(EntityTrackerSystems.Visible.getComponentType())); + this.getCodecRegistry(SelectInteraction.EntityMatcher.CODEC).register("Vulnerable", VulnerableMatcher.class, VulnerableMatcher.CODEC); + this.getCodecRegistry(SelectInteraction.EntityMatcher.CODEC).register("Player", PlayerMatcher.class, PlayerMatcher.CODEC); + this.blockCounterResourceType = this.getChunkStoreRegistry().registerResource(BlockCounter.class, "BlockCounter", BlockCounter.CODEC); + this.trackedPlacementComponentType = this.getChunkStoreRegistry().registerComponent(TrackedPlacement.class, "TrackedPlacement", TrackedPlacement.CODEC); + this.getChunkStoreRegistry().registerSystem(new TrackedPlacement.OnAddRemove()); + this.getCodecRegistry(Interaction.CODEC) + .register("PlacementCountCondition", PlacementCountConditionInteraction.class, PlacementCountConditionInteraction.CODEC); + } + + private static void handledLoadedRootInteractions(@Nonnull LoadedAssetsEvent event) { + for (RootInteraction rootInteraction : event.getLoadedAssets().values()) { + rootInteraction.build(); + } + } + + private static void handledLoadedInteractions(@Nonnull LoadedAssetsEvent event) { + for (Entry entry : RootInteraction.getAssetMap().getAssetMap().entrySet()) { + entry.getValue().build(event.getLoadedAssets().keySet()); + } + } + + private static void handledRemovedInteractions(@Nonnull RemovedAssetsEvent event) { + for (Entry entry : RootInteraction.getAssetMap().getAssetMap().entrySet()) { + entry.getValue().build(event.getRemovedAssets()); + } + } + + public void doMouseInteraction( + @Nonnull Ref ref, + @Nonnull ComponentAccessor componentAccessor, + @Nonnull MouseInteraction packet, + @Nonnull Player playerComponent, + @Nonnull PlayerRef playerRefComponent + ) { + if (!this.isDisabled()) { + byte activeHotbarSlot = playerComponent.getInventory().getActiveHotbarSlot(); + if (activeHotbarSlot != packet.activeSlot) { + playerComponent.sendMessage( + Message.translation("server.modules.interaction.failedGetActiveSlot").param("server", (int)activeHotbarSlot).param("packet", packet.activeSlot) + ); + } else { + MouseButtonType mouseButtonType = packet.mouseButton != null ? packet.mouseButton.mouseButtonType : MouseButtonType.Left; + Inventory inventory = playerComponent.getInventory(); + ItemStack itemInHand = inventory.getItemInHand(); + ItemStack itemInOffHand = inventory.getUtilityItem(); + Item primaryItem = itemInHand != null && !itemInHand.isEmpty() ? itemInHand.getItem() : null; + Item secondaryItem = itemInOffHand != null && !itemInOffHand.isEmpty() ? itemInOffHand.getItem() : null; + Item item; + if (mouseButtonType == MouseButtonType.Left) { + item = primaryItem; + } else if (mouseButtonType == MouseButtonType.Right && secondaryItem != null) { + item = secondaryItem; + } else { + item = primaryItem; + } + + WorldInteraction worldInteraction_ = packet.worldInteraction; + BlockPosition blockPositionPacket = worldInteraction_.blockPosition; + if (ref.isValid()) { + EntityStore entityComponentStore = componentAccessor.getExternalData(); + Vector3i targetBlock = blockPositionPacket == null ? null : new Vector3i(blockPositionPacket.x, blockPositionPacket.y, blockPositionPacket.z); + Entity targetEntity; + if (worldInteraction_.entityId < 0) { + targetEntity = null; + } else { + Ref entityReference = entityComponentStore.getRefFromNetworkId(worldInteraction_.entityId); + targetEntity = EntityUtils.getEntity(entityReference, componentAccessor); + } + + CameraManager cameraManagerComponent = componentAccessor.getComponent(ref, CameraManager.getComponentType()); + + assert cameraManagerComponent != null; + + if (packet.mouseButton != null) { + IEventDispatcher dispatcher = HytaleServer.get() + .getEventBus() + .dispatchFor(PlayerMouseButtonEvent.class); + if (dispatcher.hasListener()) { + dispatcher.dispatch( + new PlayerMouseButtonEvent( + ref, + playerComponent, + playerRefComponent, + packet.clientTimestamp, + item, + targetBlock, + targetEntity, + packet.screenPoint, + packet.mouseButton + ) + ); + } + + cameraManagerComponent.handleMouseButtonState(packet.mouseButton.mouseButtonType, packet.mouseButton.state, targetBlock); + } else { + IEventDispatcher dispatcher = HytaleServer.get() + .getEventBus() + .dispatchFor(PlayerMouseMotionEvent.class); + if (dispatcher.hasListener()) { + dispatcher.dispatch( + new PlayerMouseMotionEvent( + ref, playerComponent, packet.clientTimestamp, item, targetBlock, targetEntity, packet.screenPoint, packet.mouseMotion + ) + ); + } + } + + cameraManagerComponent.setLastScreenPoint(new Vector2d(packet.screenPoint.x, packet.screenPoint.y)); + cameraManagerComponent.setLastBlockPosition(targetBlock); + } + } + } + } + + @Nonnull + public ComponentType getChainingDataComponent() { + return this.chainingDataComponent; + } + + @Nonnull + public ComponentType getInteractionsComponentType() { + return this.interactionsComponentType; + } + + @Nonnull + public ComponentType getInteractionManagerComponent() { + return this.interactionManagerComponent; + } + + @Nonnull + public ComponentType getPlacedByComponentType() { + return this.placedByComponentType; + } + + public ResourceType getBlockCounterResourceType() { + return this.blockCounterResourceType; + } + + public ComponentType getTrackedPlacementComponentType() { + return this.trackedPlacementComponentType; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/InteractionSimulationHandler.java b/src/com/hypixel/hytale/server/core/modules/interaction/InteractionSimulationHandler.java new file mode 100644 index 0000000..243b8f1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/InteractionSimulationHandler.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.server.core.modules.interaction; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class InteractionSimulationHandler implements IInteractionSimulationHandler { + private final boolean[] isDown = new boolean[InteractionType.VALUES.length]; + + public InteractionSimulationHandler() { + } + + @Override + public void setState(@Nonnull InteractionType type, boolean state) { + this.isDown[type.ordinal()] = state; + } + + @Override + public boolean isCharging( + boolean firstRun, float time, @Nonnull InteractionType type, InteractionContext context, Ref ref, CooldownHandler cooldownHandler + ) { + return this.isDown[type.ordinal()]; + } + + @Override + public boolean shouldCancelCharging( + boolean firstRun, float time, InteractionType type, InteractionContext context, Ref ref, CooldownHandler cooldownHandler + ) { + return false; + } + + @Override + public float getChargeValue( + boolean firstRun, float time, InteractionType type, InteractionContext context, Ref ref, CooldownHandler cooldownHandler + ) { + return time; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/Interactions.java b/src/com/hypixel/hytale/server/core/modules/interaction/Interactions.java new file mode 100644 index 0000000..5cf452b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/Interactions.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.server.core.modules.interaction; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Collections; +import java.util.EnumMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Interactions implements Component { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(Interactions.class, Interactions::new) + .appendInherited( + new KeyedCodec<>("Interactions", new EnumMapCodec<>(InteractionType.class, Codec.STRING, false)), + (o, v) -> o.interactions = v, + o -> o.interactions, + (o, p) -> o.interactions = p.interactions + ) + .add() + .build(); + @Nonnull + private Map interactions = new EnumMap<>(InteractionType.class); + @Nullable + private String interactionHint; + private boolean isNetworkOutdated = true; + + @Nonnull + public static ComponentType getComponentType() { + return InteractionModule.get().getInteractionsComponentType(); + } + + public Interactions() { + } + + public Interactions(@Nonnull Map interactions) { + this.interactions = new EnumMap<>(interactions); + } + + @Nullable + public String getInteractionId(@Nonnull InteractionType type) { + return this.interactions.get(type); + } + + public void setInteractionId(@Nonnull InteractionType type, @Nonnull String interactionId) { + this.interactions.put(type, interactionId); + this.isNetworkOutdated = true; + } + + @Nonnull + public Map getInteractions() { + return Collections.unmodifiableMap(this.interactions); + } + + @Nullable + public String getInteractionHint() { + return this.interactionHint; + } + + public void setInteractionHint(@Nullable String interactionHint) { + this.interactionHint = interactionHint; + this.isNetworkOutdated = true; + } + + @Nonnull + @Override + public Component clone() { + Interactions clone = new Interactions(this.interactions); + clone.interactionHint = this.interactionHint; + return clone; + } + + public boolean consumeNetworkOutdated() { + boolean tmp = this.isNetworkOutdated; + this.isNetworkOutdated = false; + return tmp; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/blocktrack/BlockCounter.java b/src/com/hypixel/hytale/server/core/modules/interaction/blocktrack/BlockCounter.java new file mode 100644 index 0000000..a9a506b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/blocktrack/BlockCounter.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.core.modules.interaction.blocktrack; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.Object2IntMapCodec; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; + +public class BlockCounter implements Resource { + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockCounter.class, BlockCounter::new) + .append( + new KeyedCodec<>("BlockPlacementCounts", new Object2IntMapCodec<>(Codec.STRING, Object2IntOpenHashMap::new, false)), + (o, v) -> o.blockPlacementCounts = v, + o -> o.blockPlacementCounts + ) + .add() + .build(); + private Object2IntMap blockPlacementCounts = new Object2IntOpenHashMap<>(); + + public static ResourceType getResourceType() { + return InteractionModule.get().getBlockCounterResourceType(); + } + + public BlockCounter() { + this.blockPlacementCounts.defaultReturnValue(0); + } + + public BlockCounter(Object2IntMap blockPlacementCounts) { + this(); + this.blockPlacementCounts = blockPlacementCounts; + } + + public void trackBlock(String blockName) { + this.blockPlacementCounts.mergeInt(blockName, 1, Integer::sum); + } + + public void untrackBlock(String blockName) { + this.blockPlacementCounts.mergeInt(blockName, 0, (left, right) -> left - 1); + } + + public int getBlockPlacementCount(String blockName) { + return this.blockPlacementCounts.getInt(blockName); + } + + @Override + public Resource clone() { + return new BlockCounter(new Object2IntOpenHashMap<>(this.blockPlacementCounts)); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/blocktrack/TrackedPlacement.java b/src/com/hypixel/hytale/server/core/modules/interaction/blocktrack/TrackedPlacement.java new file mode 100644 index 0000000..f9fcc46 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/blocktrack/TrackedPlacement.java @@ -0,0 +1,111 @@ +package com.hypixel.hytale.server.core.modules.interaction.blocktrack; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TrackedPlacement implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(TrackedPlacement.class, TrackedPlacement::new) + .append(new KeyedCodec<>("BlockName", Codec.STRING), (o, v) -> o.blockName = v, o -> o.blockName) + .add() + .build(); + private String blockName; + + public static ComponentType getComponentType() { + return InteractionModule.get().getTrackedPlacementComponentType(); + } + + public TrackedPlacement() { + } + + public TrackedPlacement(String blockName) { + this.blockName = blockName; + } + + @Nullable + @Override + public Component clone() { + return new TrackedPlacement(this.blockName); + } + + public static class OnAddRemove extends RefSystem { + private static final ComponentType COMPONENT_TYPE = TrackedPlacement.getComponentType(); + private static final ResourceType BLOCK_COUNTER_RESOURCE_TYPE = BlockCounter.getResourceType(); + + public OnAddRemove() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + if (reason == AddReason.SPAWN) { + BlockModule.BlockStateInfo blockInfo = commandBuffer.getComponent(ref, BlockModule.BlockStateInfo.getComponentType()); + + assert blockInfo != null; + + Ref chunkRef = blockInfo.getChunkRef(); + if (chunkRef != null && chunkRef.isValid()) { + BlockChunk blockChunk = commandBuffer.getComponent(chunkRef, BlockChunk.getComponentType()); + + assert blockChunk != null; + + int blockId = blockChunk.getBlock( + ChunkUtil.xFromBlockInColumn(blockInfo.getIndex()), + ChunkUtil.yFromBlockInColumn(blockInfo.getIndex()), + ChunkUtil.zFromBlockInColumn(blockInfo.getIndex()) + ); + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + if (blockType != null) { + BlockCounter counter = commandBuffer.getResource(BLOCK_COUNTER_RESOURCE_TYPE); + String blockName = blockType.getId(); + counter.trackBlock(blockName); + TrackedPlacement tracked = commandBuffer.getComponent(ref, COMPONENT_TYPE); + + assert tracked != null; + + tracked.blockName = blockName; + } + } + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + if (reason == RemoveReason.REMOVE) { + TrackedPlacement tracked = commandBuffer.getComponent(ref, COMPONENT_TYPE); + + assert tracked != null; + + BlockCounter counter = commandBuffer.getResource(BLOCK_COUNTER_RESOURCE_TYPE); + counter.untrackBlock(tracked.blockName); + } + } + + @Nullable + @Override + public Query getQuery() { + return COMPONENT_TYPE; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionClearCommand.java b/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionClearCommand.java new file mode 100644 index 0000000..05f99cb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionClearCommand.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.core.modules.interaction.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class InteractionClearCommand extends AbstractPlayerCommand { + public InteractionClearCommand() { + super("clear", "server.commands.interaction.clear.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + InteractionManager interactionManagerComponent = store.getComponent(ref, InteractionModule.get().getInteractionManagerComponent()); + + assert interactionManagerComponent != null; + + interactionManagerComponent.clear(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionCommand.java b/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionCommand.java new file mode 100644 index 0000000..abd4375 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionCommand.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.server.core.modules.interaction.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class InteractionCommand extends AbstractCommandCollection { + public InteractionCommand() { + super("interaction", "server.commands.interaction.desc"); + this.addAliases("interact"); + this.addSubCommand(new InteractionRunCommand()); + this.addSubCommand(new InteractionSnapshotSourceCommand()); + this.addSubCommand(new InteractionClearCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionRunCommand.java b/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionRunCommand.java new file mode 100644 index 0000000..17beee4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionRunCommand.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.core.modules.interaction.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.EnumArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class InteractionRunCommand extends AbstractPlayerCommand { + @Nonnull + private static final EnumArgumentType INTERACTION_TYPE_ARG_TYPE = new EnumArgumentType<>( + "server.commands.parsing.argtype.interactiontype.name", InteractionType.class + ); + @Nonnull + private final RequiredArg interactionTypeArg = this.withRequiredArg( + "interactionType", "server.commands.interaction.run.interactionType.desc", INTERACTION_TYPE_ARG_TYPE + ); + + public InteractionRunCommand() { + super("run", "server.commands.interaction.run.desc"); + this.addSubCommand(new InteractionRunSpecificCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + InteractionType interactionType = this.interactionTypeArg.get(context); + InteractionManager interactionManagerComponent = store.getComponent(ref, InteractionModule.get().getInteractionManagerComponent()); + + assert interactionManagerComponent != null; + + InteractionContext interactionContext = InteractionContext.forInteraction(interactionManagerComponent, ref, interactionType, store); + String root = interactionContext.getRootInteractionId(interactionType); + if (root == null) { + context.sendMessage(Message.translation("server.commands.interaction.run.rootNotFound").param("type", interactionType.name())); + } else { + RootInteraction interactionAsset = RootInteraction.getAssetMap().getAsset(root); + if (interactionAsset == null) { + context.sendMessage( + Message.translation("server.commands.interaction.run.interactionAssetNotFound").param("root", root).param("type", interactionType.name()) + ); + } else { + InteractionChain chain = interactionManagerComponent.initChain(interactionType, interactionContext, interactionAsset, false); + interactionManagerComponent.queueExecuteChain(chain); + context.sendMessage(Message.translation("server.commands.interaction.run.started").param("type", interactionType.name())); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionRunSpecificCommand.java b/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionRunSpecificCommand.java new file mode 100644 index 0000000..d63a9e3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionRunSpecificCommand.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.server.core.modules.interaction.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EnumArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class InteractionRunSpecificCommand extends AbstractPlayerCommand { + @Nonnull + private static final EnumArgumentType INTERACTION_TYPE_ARG_TYPE = new EnumArgumentType<>( + "server.commands.parsing.argtype.interactiontype.name", InteractionType.class + ); + @Nonnull + private final RequiredArg interactionTypeArg = this.withRequiredArg( + "interactionType", "server.commands.interaction.run.interactionType.desc", INTERACTION_TYPE_ARG_TYPE + ); + @Nonnull + private final RequiredArg rootInteractionArg = this.withRequiredArg( + "interaction", "server.commands.interaction.runSpecific.rootinteraction.desc", ArgTypes.ROOT_INTERACTION_ASSET + ); + + public InteractionRunSpecificCommand() { + super("specific", "server.commands.interaction.runSpecific.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + InteractionManager interactionManager = store.getComponent(ref, InteractionModule.get().getInteractionManagerComponent()); + + assert interactionManager != null; + + InteractionType interactionType = this.interactionTypeArg.get(context); + RootInteraction rootInteraction = this.rootInteractionArg.get(context); + InteractionContext interactionContext = InteractionContext.forInteraction(interactionManager, ref, interactionType, store); + InteractionChain chain = interactionManager.initChain(interactionType, interactionContext, rootInteraction, false); + interactionManager.queueExecuteChain(chain); + context.sendMessage( + Message.translation("server.commands.interaction.runSpecific.started").param("type", interactionType.name()).param("root", rootInteraction.getId()) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionSetSnapshotSourceCommand.java b/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionSetSnapshotSourceCommand.java new file mode 100644 index 0000000..f7a38fe --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionSetSnapshotSourceCommand.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.modules.interaction.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.EnumArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.SelectInteraction; +import javax.annotation.Nonnull; + +public class InteractionSetSnapshotSourceCommand extends CommandBase { + @Nonnull + private static final EnumArgumentType SNAPSHOT_SOURCE_ARG_TYPE = new EnumArgumentType<>( + "server.commands.parsing.argtype.snapshotsource.name", SelectInteraction.SnapshotSource.class + ); + @Nonnull + private final RequiredArg snapshotSourceArg = this.withRequiredArg( + "snapshotSource", "server.commands.interaction.snapshotSource.set.snapshotSource.desc", SNAPSHOT_SOURCE_ARG_TYPE + ); + + public InteractionSetSnapshotSourceCommand() { + super("set", "server.commands.interaction.snapshotSource.set.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + SelectInteraction.SnapshotSource source = this.snapshotSourceArg.get(context); + SelectInteraction.SNAPSHOT_SOURCE = source; + context.sendMessage(Message.translation("server.commands.interaction.snapshotSource.set").param("source", source.name())); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionSnapshotSourceCommand.java b/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionSnapshotSourceCommand.java new file mode 100644 index 0000000..1eca6e7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/commands/InteractionSnapshotSourceCommand.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.server.core.modules.interaction.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.SelectInteraction; +import javax.annotation.Nonnull; + +public class InteractionSnapshotSourceCommand extends CommandBase { + public InteractionSnapshotSourceCommand() { + super("snapshotsource", "server.commands.interaction.snapshotSource.desc"); + this.addSubCommand(new InteractionSetSnapshotSourceCommand()); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + context.sendMessage(Message.translation("server.commands.interaction.snapshotSource.get").param("source", SelectInteraction.SNAPSHOT_SOURCE.name())); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/components/PlacedByInteractionComponent.java b/src/com/hypixel/hytale/server/core/modules/interaction/components/PlacedByInteractionComponent.java new file mode 100644 index 0000000..14b1fea --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/components/PlacedByInteractionComponent.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.core.modules.interaction.components; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.UUID; +import javax.annotation.Nullable; + +public class PlacedByInteractionComponent implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder( + PlacedByInteractionComponent.class, PlacedByInteractionComponent::new + ) + .appendInherited( + new KeyedCodec<>("WhoPlacedUuid", Codec.UUID_BINARY), + (o, i) -> o.whoPlacedUuid = i, + o -> o.whoPlacedUuid, + (o, parent) -> o.whoPlacedUuid = parent.whoPlacedUuid + ) + .add() + .build(); + private UUID whoPlacedUuid; + + public static ComponentType getComponentType() { + return InteractionModule.get().getPlacedByComponentType(); + } + + public PlacedByInteractionComponent() { + } + + public PlacedByInteractionComponent(UUID whoPlacedUuid) { + this.whoPlacedUuid = whoPlacedUuid; + } + + public UUID getWhoPlacedUuid() { + return this.whoPlacedUuid; + } + + @Nullable + @Override + public Component clone() { + PlacedByInteractionComponent clone = new PlacedByInteractionComponent(); + clone.whoPlacedUuid = this.whoPlacedUuid; + return clone; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/CooldownHandler.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/CooldownHandler.java new file mode 100644 index 0000000..d4c2580 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/CooldownHandler.java @@ -0,0 +1,161 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction; + +import com.hypixel.hytale.common.thread.ticking.Tickable; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class CooldownHandler implements Tickable { + @Nonnull + private final Map cooldowns = new ConcurrentHashMap<>(); + + public CooldownHandler() { + } + + public boolean isOnCooldown(@Nonnull RootInteraction root, @Nonnull String id, float maxTime, @Nonnull float[] chargeTimes, boolean interruptRecharge) { + if (maxTime <= 0.0F) { + return false; + } else { + CooldownHandler.Cooldown cooldown = this.getCooldown(id, maxTime, chargeTimes, root.resetCooldownOnStart(), interruptRecharge); + return cooldown != null && cooldown.hasCooldown(true); + } + } + + public void resetCooldown(@Nonnull String id, float maxTime, @Nonnull float[] chargeTimes, boolean interruptRecharge) { + CooldownHandler.Cooldown cooldown = this.getCooldown(id, maxTime, chargeTimes, true, interruptRecharge); + if (cooldown != null) { + cooldown.resetCooldown(); + cooldown.resetCharges(); + } + } + + @Nullable + public CooldownHandler.Cooldown getCooldown(@Nonnull String id, float maxTime, @Nonnull float[] chargeTimes, boolean force, boolean interruptRecharge) { + return force ? this.cooldowns.computeIfAbsent(id, k -> new CooldownHandler.Cooldown(maxTime, chargeTimes, interruptRecharge)) : this.cooldowns.get(id); + } + + @Nullable + public CooldownHandler.Cooldown getCooldown(@Nonnull String id) { + return this.cooldowns.get(id); + } + + @Override + public void tick(float dt) { + this.cooldowns.values().removeIf(cooldown -> cooldown.tick(dt)); + } + + public static class Cooldown { + private float cooldownMax; + private float[] charges; + private float remainingCooldown = 0.0F; + private float chargeTimer; + private int chargeCount; + private final boolean interruptRecharge; + + public Cooldown(float cooldownMax, @Nonnull float[] charges, boolean interruptRecharge) { + this.setCooldownMax(cooldownMax); + this.setCharges(charges); + this.resetCharges(); + this.interruptRecharge = interruptRecharge; + } + + public void setCooldownMax(float cooldownMax) { + this.cooldownMax = cooldownMax; + if (this.remainingCooldown > cooldownMax) { + this.remainingCooldown = cooldownMax; + } + } + + public void setCharges(@Nonnull float[] charges) { + this.charges = charges; + if (this.chargeCount > charges.length) { + this.chargeCount = charges.length; + } + } + + public boolean hasCooldown(boolean deduct) { + if (this.remainingCooldown <= 0.0F && this.chargeCount > 0) { + if (deduct) { + this.deductCharge(); + } + + return false; + } else { + return true; + } + } + + public boolean hasMaxCharges() { + return this.chargeCount >= this.charges.length; + } + + public void resetCharges() { + this.chargeCount = this.charges.length; + } + + public void resetCooldown() { + this.remainingCooldown = this.cooldownMax; + } + + public void deductCharge() { + if (this.chargeCount > 0) { + this.chargeCount--; + } + + if (this.interruptRecharge) { + this.chargeTimer = 0.0F; + } + + this.resetCooldown(); + } + + public boolean tick(float dt) { + if (!this.hasMaxCharges()) { + float chargeTimeMax = this.charges[this.chargeCount]; + this.chargeTimer += dt; + if (this.chargeTimer >= chargeTimeMax) { + this.chargeCount++; + this.chargeTimer = 0.0F; + } + } + + this.remainingCooldown -= dt; + return (this.hasMaxCharges() || this.charges.length <= 1) && this.remainingCooldown <= 0.0F; + } + + public float getCooldown() { + return this.cooldownMax; + } + + public float[] getCharges() { + return this.charges; + } + + public boolean interruptRecharge() { + return this.interruptRecharge; + } + + public void replenishCharge(int amount, boolean interrupt) { + this.chargeCount = MathUtil.clamp(this.chargeCount + amount, 0, this.charges.length); + if (interrupt && amount != 0) { + this.chargeTimer = 0.0F; + } + } + + public void increaseTime(float time) { + this.remainingCooldown = MathUtil.clamp(this.remainingCooldown + time, 0.0F, this.cooldownMax); + } + + public void increaseChargeTime(float time) { + if (!this.hasMaxCharges()) { + if (this.charges.length > 1) { + float chargeTimeMax = this.charges[this.chargeCount]; + this.chargeTimer = MathUtil.clamp(this.chargeTimer + time, 0.0F, chargeTimeMax); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/InteractionPacketGenerator.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/InteractionPacketGenerator.java new file mode 100644 index 0000000..e526deb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/InteractionPacketGenerator.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction; + +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.SimpleInteraction; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateInteractions; +import com.hypixel.hytale.server.core.asset.packet.AssetPacketGenerator; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class InteractionPacketGenerator extends AssetPacketGenerator> { + public InteractionPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + Int2ObjectMap interactions = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + interactions.put(assetMap.getIndex(entry.getKey()), entry.getValue().toPacket()); + } + + return new UpdateInteractions(UpdateType.Init, assetMap.getNextIndex(), interactions); + } + + @Nonnull + public Packet generateUpdatePacket( + @Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets, @Nonnull AssetUpdateQuery query + ) { + Int2ObjectMap interactions = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + interactions.put(assetMap.getIndex(entry.getKey()), entry.getValue().toPacket()); + } + + return new UpdateInteractions(UpdateType.AddOrUpdate, assetMap.getNextIndex(), interactions); + } + + @Nonnull + public Packet generateRemovePacket( + @Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed, @Nonnull AssetUpdateQuery query + ) { + Int2ObjectMap interactions = new Int2ObjectOpenHashMap<>(); + + for (String entry : removed) { + interactions.put(assetMap.getIndex(entry), new SimpleInteraction()); + } + + return new UpdateInteractions(UpdateType.Remove, assetMap.getNextIndex(), interactions); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/RootInteractionPacketGenerator.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/RootInteractionPacketGenerator.java new file mode 100644 index 0000000..1dc0493 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/RootInteractionPacketGenerator.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction; + +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateRootInteractions; +import com.hypixel.hytale.server.core.asset.packet.AssetPacketGenerator; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class RootInteractionPacketGenerator extends AssetPacketGenerator> { + public RootInteractionPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + Int2ObjectMap interactions = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + interactions.put(assetMap.getIndex(entry.getKey()), entry.getValue().toPacket()); + } + + return new UpdateRootInteractions(UpdateType.Init, assetMap.getNextIndex(), interactions); + } + + @Nonnull + public Packet generateUpdatePacket( + @Nonnull IndexedLookupTableAssetMap assetMap, + @Nonnull Map loadedAssets, + @Nonnull AssetUpdateQuery query + ) { + Int2ObjectMap interactions = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + interactions.put(assetMap.getIndex(entry.getKey()), entry.getValue().toPacket()); + } + + return new UpdateRootInteractions(UpdateType.AddOrUpdate, assetMap.getNextIndex(), interactions); + } + + @Nonnull + public Packet generateRemovePacket( + @Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed, @Nonnull AssetUpdateQuery query + ) { + Int2ObjectMap interactions = new Int2ObjectOpenHashMap<>(); + + for (String entry : removed) { + interactions.put(assetMap.getIndex(entry), new com.hypixel.hytale.protocol.RootInteraction()); + } + + return new UpdateRootInteractions(UpdateType.Remove, assetMap.getNextIndex(), interactions); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/UnarmedInteractions.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/UnarmedInteractions.java new file mode 100644 index 0000000..cea02d4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/UnarmedInteractions.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.ChangeActiveSlotInteraction; +import java.util.Collections; +import java.util.EnumMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class UnarmedInteractions implements JsonAssetWithMap> { + public static final String DEFAULT_UNARMED_ID = "Empty"; + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + UnarmedInteractions.class, UnarmedInteractions::new, Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ) + .appendInherited( + new KeyedCodec<>("Interactions", new EnumMapCodec<>(InteractionType.class, RootInteraction.CHILD_ASSET_CODEC, false)), + (item, v) -> item.interactions = v, + item -> item.interactions, + (item, parent) -> item.interactions = parent.interactions + ) + .addValidator(RootInteraction.VALIDATOR_CACHE.getMapValueValidator()) + .add() + .afterDecode(o -> { + if (o.interactions == null) { + o.interactions = new EnumMap<>(InteractionType.class); + } + + o.interactions.putIfAbsent(InteractionType.SwapFrom, ChangeActiveSlotInteraction.DEFAULT_ROOT.getId()); + o.interactions = Collections.unmodifiableMap(o.interactions); + }) + .build(); + private static DefaultAssetMap ASSET_MAP; + protected AssetExtraInfo.Data data; + protected String id; + protected Map interactions; + + public UnarmedInteractions() { + } + + public static DefaultAssetMap getAssetMap() { + if (ASSET_MAP == null) { + ASSET_MAP = (DefaultAssetMap)AssetRegistry.getAssetStore(UnarmedInteractions.class).getAssetMap(); + } + + return ASSET_MAP; + } + + public String getId() { + return this.id; + } + + public Map getInteractions() { + return this.interactions; + } + + @Nonnull + @Override + public String toString() { + return "UnarmedInteractions{id=" + this.id + ", interactions=" + this.interactions + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/UnarmedInteractionsPacketGenerator.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/UnarmedInteractionsPacketGenerator.java new file mode 100644 index 0000000..a7142b3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/UnarmedInteractionsPacketGenerator.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateUnarmedInteractions; +import com.hypixel.hytale.server.core.asset.packet.DefaultAssetPacketGenerator; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class UnarmedInteractionsPacketGenerator extends DefaultAssetPacketGenerator { + public UnarmedInteractionsPacketGenerator() { + } + + @Nonnull + @Override + public Packet generateInitPacket(@Nonnull DefaultAssetMap assetMap, Map assets) { + UpdateUnarmedInteractions packet = new UpdateUnarmedInteractions(); + packet.type = UpdateType.Init; + UnarmedInteractions unarmedInteraction = assetMap.getAsset("Empty"); + Object2IntOpenHashMap intMap = new Object2IntOpenHashMap<>(); + + for (Entry e : unarmedInteraction.getInteractions().entrySet()) { + intMap.put(e.getKey(), RootInteraction.getAssetMap().getIndex(e.getValue())); + } + + packet.interactions = intMap; + return packet; + } + + @Nonnull + @Override + public Packet generateUpdatePacket(@Nonnull Map loadedAssets) { + UpdateUnarmedInteractions packet = new UpdateUnarmedInteractions(); + packet.type = UpdateType.AddOrUpdate; + UnarmedInteractions unarmedInteraction = loadedAssets.get("Empty"); + Object2IntOpenHashMap intMap = new Object2IntOpenHashMap<>(); + + for (Entry e : unarmedInteraction.getInteractions().entrySet()) { + intMap.put(e.getKey(), RootInteraction.getAssetMap().getIndex(e.getValue())); + } + + packet.interactions = intMap; + return packet; + } + + @Nonnull + @Override + public Packet generateRemovePacket(Set removed) { + UpdateUnarmedInteractions packet = new UpdateUnarmedInteractions(); + packet.type = UpdateType.Remove; + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/Interaction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/Interaction.java new file mode 100644 index 0000000..486d841 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/Interaction.java @@ -0,0 +1,597 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.codec.schema.metadata.ui.UIEditor; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector4d; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.ForkedChainId; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.InteractionChainData; +import com.hypixel.hytale.protocol.InteractionSettings; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.ItemAnimation; +import com.hypixel.hytale.protocol.packets.interaction.PlayInteractionFor; +import com.hypixel.hytale.server.core.asset.common.BlockyAnimationCache; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.itemanimation.config.ItemPlayerAnimations; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.meta.MetaKey; +import com.hypixel.hytale.server.core.meta.MetaRegistry; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.Collector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.simple.SendMessageInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.Operation; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.OperationsBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.UUIDUtil; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.lang.ref.SoftReference; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class Interaction + implements Operation, + JsonAssetWithMap>, + NetworkSerializable { + @Nonnull + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec<>( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data + ); + @Nonnull + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(Interaction.class, CODEC); + @Nonnull + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + @Nonnull + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(Interaction.class) + .appendInherited( + new KeyedCodec<>("ViewDistance", Codec.DOUBLE), + (damageEffects, s) -> damageEffects.viewDistance = s, + damageEffects -> damageEffects.viewDistance, + (damageEffects, parent) -> damageEffects.viewDistance = parent.viewDistance + ) + .documentation("Configures the distance in which other players will be able to see the effects of this interaction.") + .add() + .appendInherited( + new KeyedCodec<>("Effects", InteractionEffects.CODEC), + (interaction, o) -> interaction.effects = o, + interaction -> interaction.effects, + (interaction, parent) -> interaction.effects = parent.effects + ) + .documentation("Sets effects that will be applied whilst the interaction is running.") + .add() + .appendInherited( + new KeyedCodec<>("HorizontalSpeedMultiplier", Codec.FLOAT), + (activationEffects, s) -> activationEffects.horizontalSpeedMultiplier = s, + activationEffects -> activationEffects.horizontalSpeedMultiplier, + (activationEffects, parent) -> activationEffects.horizontalSpeedMultiplier = parent.horizontalSpeedMultiplier + ) + .documentation("The multiplier to apply to the horizontal speed of the entity whilst this interaction is running.") + .metadata(new UIEditor(new UIEditor.FormattedNumber(0.1, null, null))) + .add() + .appendInherited( + new KeyedCodec<>("RunTime", Codec.FLOAT), + (activationEffects, s) -> activationEffects.runTime = s, + activationEffects -> activationEffects.runTime, + (activationEffects, parent) -> activationEffects.runTime = parent.runTime + ) + .documentation( + "The time in seconds this interaction should run for. \n\nIf *Effects.WaitForAnimationToFinish* is set and the length of the animation is longer than the runtime then the interaction will run for longer than the set time." + ) + .metadata(new UIEditor(new UIEditor.FormattedNumber(0.01, "s", null))) + .add() + .appendInherited( + new KeyedCodec<>("CancelOnItemChange", Codec.BOOLEAN), + (damageEffects, s) -> damageEffects.cancelOnItemChange = s, + damageEffects -> damageEffects.cancelOnItemChange, + (damageEffects, parent) -> damageEffects.cancelOnItemChange = parent.cancelOnItemChange + ) + .documentation("Whether the interaction will be cancelled when the entity's held item changes.") + .add() + .appendInherited(new KeyedCodec<>("Rules", InteractionRules.CODEC), (o, i) -> o.rules = i, o -> o.rules, (o, p) -> o.rules = p.rules) + .documentation("A set of rules that control when this interaction can run.") + .addValidator(Validators.nonNull()) + .add() + .>appendInherited( + new KeyedCodec<>( + "Settings", + new EnumMapCodec<>( + GameMode.class, + BuilderCodec.builder(InteractionSettings.class, InteractionSettings::new) + .appendInherited( + new KeyedCodec<>("AllowSkipOnClick", Codec.BOOLEAN), + (settings, s) -> settings.allowSkipOnClick = s, + settings -> settings.allowSkipOnClick, + (settings, parent) -> settings.allowSkipOnClick = parent.allowSkipOnClick + ) + .documentation("Whether to skip this interaction when another click is sent.") + .add() + .build() + ) + ), + (interaction, o) -> interaction.settings = o, + interaction -> interaction.settings, + (interaction, parent) -> interaction.settings = parent.settings + ) + .documentation("Per a gamemode settings.") + .add() + .appendInherited( + new KeyedCodec<>("Camera", InteractionCameraSettings.CODEC), (o, i) -> o.camera = i, o -> o.camera, (o, p) -> o.camera = p.camera + ) + .documentation("Configures the camera behaviour for this interaction.") + .add() + .build(); + @Nonnull + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(Interaction::getAssetStore)); + private static AssetStore> ASSET_STORE; + public static final MetaRegistry CONTEXT_META_REGISTRY = new MetaRegistry<>(); + public static final MetaRegistry META_REGISTRY = new MetaRegistry<>(); + public static final MetaKey> TARGET_ENTITY = CONTEXT_META_REGISTRY.registerMetaObject(data -> null); + public static final MetaKey HIT_LOCATION = CONTEXT_META_REGISTRY.registerMetaObject(data -> null); + public static final MetaKey HIT_DETAIL = CONTEXT_META_REGISTRY.registerMetaObject(data -> null); + public static final MetaKey TARGET_BLOCK = CONTEXT_META_REGISTRY.registerMetaObject(data -> null); + public static final MetaKey TARGET_BLOCK_RAW = CONTEXT_META_REGISTRY.registerMetaObject(data -> null); + public static final MetaKey TARGET_SLOT = CONTEXT_META_REGISTRY.registerMetaObject(data -> 0); + public static final MetaKey TIME_SHIFT = META_REGISTRY.registerMetaObject(data -> null); + public static final MetaKey DAMAGE = CONTEXT_META_REGISTRY.registerMetaObject(data -> null); + protected String id; + protected AssetExtraInfo.Data data; + protected boolean unknown; + protected double viewDistance = 96.0; + @Nonnull + protected InteractionEffects effects = new InteractionEffects(); + protected float horizontalSpeedMultiplier = 1.0F; + protected float runTime; + protected boolean cancelOnItemChange = true; + @Nonnull + protected Map settings = Collections.emptyMap(); + @Nonnull + protected InteractionRules rules = InteractionRules.DEFAULT_RULES; + @Nullable + protected InteractionCameraSettings camera; + @Nullable + private transient SoftReference cachedPacket; + + @Nonnull + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(Interaction.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public Interaction() { + } + + public Interaction(@Nonnull String id) { + this.id = id; + } + + public String getId() { + return this.id; + } + + public boolean isUnknown() { + return this.unknown; + } + + @Nonnull + public InteractionEffects getEffects() { + return this.effects; + } + + public float getHorizontalSpeedMultiplier() { + return this.horizontalSpeedMultiplier; + } + + public double getViewDistance() { + return this.viewDistance; + } + + public float getRunTime() { + return this.runTime; + } + + public boolean isCancelOnItemChange() { + return this.cancelOnItemChange; + } + + @Nonnull + @Override + public InteractionRules getRules() { + return this.rules; + } + + @Nonnull + public Map getSettings() { + return this.settings; + } + + @Override + public final void tick( + @Nonnull Ref ref, + @Nonnull LivingEntity entity, + boolean firstRun, + float time, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nonnull CooldownHandler cooldownHandler + ) { + int previousCounter = context.getOperationCounter(); + int previousDepth = context.getChain().getCallDepth(); + if (!this.tickInternal(entity, time, type, context)) { + this.tick0(firstRun, time, type, context, cooldownHandler); + } + + InteractionSyncData data = context.getState(); + this.trySkipChain(ref, time, context, data); + switch (data.state) { + case Failed: + case Finished: + case Skip: + Float shift = context.getInstanceStore().getMetaObject(TIME_SHIFT); + if (shift != null) { + context.setTimeShift(shift); + } + + if (context.getOperationCounter() == previousCounter && previousDepth == context.getChain().getCallDepth()) { + context.setOperationCounter(context.getOperationCounter() + 1); + } + default: + return; + case ItemChanged: + throw new InteractionManager.ChainCancelledException(data.state); + } + } + + @Override + public final void simulateTick( + @Nonnull Ref ref, + @Nonnull LivingEntity entity, + boolean firstRun, + float time, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nonnull CooldownHandler cooldownHandler + ) { + int previousCounter = context.getOperationCounter(); + int previousDepth = context.getChain().getSimulatedCallDepth(); + if (!this.tickInternal(entity, time, type, context)) { + this.simulateTick0(firstRun, time, type, context, cooldownHandler); + } + + InteractionSyncData data = context.getState(); + this.trySkipChain(ref, time, context, data); + switch (data.state) { + case Failed: + case Finished: + case Skip: + Float shift = context.getInstanceStore().getMetaObject(TIME_SHIFT); + if (shift != null) { + context.setTimeShift(shift); + } + + if (context.getOperationCounter() == previousCounter && previousDepth == context.getChain().getSimulatedCallDepth()) { + context.setOperationCounter(context.getOperationCounter() + 1); + } + } + } + + private boolean tickInternal(@Nonnull LivingEntity entity, float time, @Nonnull InteractionType type, @Nonnull InteractionContext context) { + Inventory inventory = entity.getInventory(); + if (!UUIDUtil.isEmptyOrNull(context.getChain().getChainData().proxyId) + || !this.cancelOnItemChange + || (type == InteractionType.Equipped || inventory.getActiveSlot(context.getHeldItemSectionId()) == context.getHeldItemSlot()) + && ( + context.getHeldItemSlot() == -1 + || ItemStack.isEquivalentType(context.getHeldItemContainer().getItemStack(context.getHeldItemSlot()), context.getHeldItem()) + )) { + if (!failed(context.getState().state)) { + double animationDuration = 0.0; + if (this.effects.isWaitForAnimationToFinish()) { + ItemStack heldItem = context.getHeldItem(); + Item item = heldItem != null ? heldItem.getItem() : null; + animationDuration = this.getAnimationDuration(item); + } + + InteractionSyncData data = context.getState(); + float maxTime = Math.max(this.runTime, (float)animationDuration); + if (time < maxTime) { + data.state = InteractionState.NotFinished; + } else { + if (maxTime > 0.0F) { + context.getInstanceStore().putMetaObject(TIME_SHIFT, time - maxTime); + } + + data.state = InteractionState.Finished; + } + + data.progress = time; + } + + return false; + } else { + InteractionSyncData data = context.getState(); + data.state = InteractionState.ItemChanged; + data.progress = time; + return true; + } + } + + private void trySkipChain(@Nonnull Ref ref, float time, @Nonnull InteractionContext context, @Nonnull InteractionSyncData data) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + GameMode gameMode = playerComponent != null ? playerComponent.getGameMode() : GameMode.Adventure; + InteractionSettings interactionSettings = this.settings.get(gameMode); + InteractionChain chain = context.getChain(); + + assert chain != null; + + if (!chain.isFirstRun() + && data.state == InteractionState.NotFinished + && (context.allowSkipChainOnClick() || interactionSettings != null && interactionSettings.allowSkipOnClick) + && context.getClientState() != null + && context.getClientState().state == InteractionState.Skip) { + data.state = InteractionState.Skip; + data.progress = time; + } + } + + public void compile(@Nonnull OperationsBuilder builder) { + builder.addOperation(this); + } + + protected abstract void tick0(boolean var1, float var2, @Nonnull InteractionType var3, @Nonnull InteractionContext var4, @Nonnull CooldownHandler var5); + + protected abstract void simulateTick0( + boolean var1, float var2, @Nonnull InteractionType var3, @Nonnull InteractionContext var4, @Nonnull CooldownHandler var5 + ); + + public abstract boolean walk(@Nonnull Collector var1, @Nonnull InteractionContext var2); + + @Override + public void handle(@Nonnull Ref ref, boolean firstRun, float time, @Nonnull InteractionType type, @Nonnull InteractionContext context) { + Ref entity = context.getEntity(); + if (entity.isValid()) { + if (ref.isValid()) { + InteractionSyncData serverData = context.getState(); + InteractionChain chain = context.getChain(); + + assert chain != null; + + if (serverData.state != InteractionState.NotFinished) { + if (firstRun && serverData.state == InteractionState.Finished) { + this.sendPlayInteract(entity, context, chain, false); + } + + this.sendPlayInteract(entity, context, chain, true); + } else { + if (firstRun) { + this.sendPlayInteract(entity, context, chain, false); + } + } + } + } + } + + @Nullable + public InteractionChain mapForkChain(@Nonnull InteractionContext context, @Nonnull InteractionChainData data) { + return null; + } + + private void sendPlayInteract(@Nonnull Ref entity, @Nonnull InteractionContext context, @Nonnull InteractionChain chain, boolean cancel) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + String itemId = null; + InteractionType interactionType = chain.getBaseType(); + int assetId = getInteractionIdOrUnknown(this.id); + int chainId = chain.getChainId(); + ForkedChainId forkedChainId = chain.getForkedChainId(); + int operationIndex = chain.getOperationIndex(); + NetworkId networkIdComponent = commandBuffer.getComponent(entity, NetworkId.getComponentType()); + + assert networkIdComponent != null; + + int entityNetworkId = networkIdComponent.getId(); + PlayInteractionFor packet = new PlayInteractionFor(entityNetworkId, chainId, forkedChainId, operationIndex, assetId, itemId, interactionType, cancel); + TransformComponent transformComponent = commandBuffer.getComponent(entity, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + SpatialResource, EntityStore> playerSpatialResource = commandBuffer.getResource(EntityModule.get().getPlayerSpatialResourceType()); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(position, this.viewDistance, results); + Ref owningEntityRef = context.getOwningEntity(); + ComponentType playerRefComponentType = PlayerRef.getComponentType(); + + for (Ref playerRef : results) { + if (!chain.requiresClient() || !playerRef.equals(owningEntityRef)) { + PlayerRef playerPlayerRefComponent = commandBuffer.getComponent(playerRef, playerRefComponentType); + + assert playerPlayerRefComponent != null; + + playerPlayerRefComponent.getPacketHandler().writeNoCache(packet); + } + } + } + + @Nonnull + public final com.hypixel.hytale.protocol.Interaction toPacket() { + com.hypixel.hytale.protocol.Interaction cached = this.cachedPacket == null ? null : this.cachedPacket.get(); + if (cached != null) { + return cached; + } else { + com.hypixel.hytale.protocol.Interaction packet = this.generatePacket(); + this.configurePacket(packet); + this.cachedPacket = new SoftReference<>(packet); + return packet; + } + } + + @Nonnull + protected abstract com.hypixel.hytale.protocol.Interaction generatePacket(); + + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + packet.waitForDataFrom = this.getWaitForDataFrom(); + packet.effects = this.effects.toPacket(); + packet.horizontalSpeedMultiplier = this.horizontalSpeedMultiplier; + packet.runTime = this.runTime; + packet.cancelOnItemChange = this.cancelOnItemChange; + packet.settings = this.settings; + packet.rules = this.rules.toPacket(); + if (this.data != null) { + packet.tags = this.data.getTags().keySet().toIntArray(); + } + + if (this.camera != null) { + packet.camera = this.camera.toPacket(); + } + } + + protected double getAnimationDuration(@Nullable Item item) { + String playerAnimationsId; + if (this.effects.itemPlayerAnimationsId != null) { + playerAnimationsId = this.effects.itemPlayerAnimationsId; + } else if (item != null) { + playerAnimationsId = item.getPlayerAnimationsId(); + } else { + playerAnimationsId = "Default"; + } + + ItemPlayerAnimations playerAnimations = ItemPlayerAnimations.getAssetMap().getAsset(playerAnimationsId); + if (playerAnimations == null) { + return 0.0; + } else { + ItemAnimation itemAnimation = this.effects.getItemAnimationId() != null + ? playerAnimations.getAnimations().get(this.effects.getItemAnimationId()) + : null; + if (itemAnimation == null) { + return 0.0; + } else { + BlockyAnimationCache.BlockyAnimation animation = BlockyAnimationCache.getNow(itemAnimation.firstPerson); + return animation == null ? 0.0 : animation.getDurationSeconds() * itemAnimation.speed; + } + } + } + + public abstract boolean needsRemoteSync(); + + @Override + public String toString() { + return "Interaction{id='" + + this.id + + "', viewDistance=" + + this.viewDistance + + ", effects=" + + this.effects + + ", horizontalSpeedMultiplier=" + + this.horizontalSpeedMultiplier + + ", runTime=" + + this.runTime + + ", cancelOnItemChange=" + + this.cancelOnItemChange + + ", settings=" + + this.settings + + ", rules=" + + this.rules + + ", camera=" + + this.camera + + "}"; + } + + public static boolean failed(@Nonnull InteractionState state) { + return switch (state) { + case Failed, Skip, ItemChanged -> true; + case Finished, NotFinished -> false; + }; + } + + @Nullable + @Deprecated + public static Interaction getInteractionOrUnknown(@Nonnull String id) { + return getAssetMap().getAsset(getInteractionIdOrUnknown(id)); + } + + @Deprecated + public static int getInteractionIdOrUnknown(@Nullable String id) { + if (id == null) { + return Integer.MIN_VALUE; + } else { + IndexedLookupTableAssetMap assetMap = getAssetMap(); + int interactionId = assetMap.getIndex(id); + if (interactionId == Integer.MIN_VALUE) { + HytaleLogger.getLogger().at(Level.WARNING).log("Missing interaction %s", id); + getAssetStore().loadAssets("Hytale:Hytale", List.of(new SendMessageInteraction(id, "Missing interaction: " + id))); + int index = assetMap.getIndex(id); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + id); + } + + interactionId = index; + } + + return interactionId; + } + } + + protected static boolean needsRemoteSync(@Nullable String id) { + if (id == null) { + return false; + } else { + Interaction interaction = getInteractionOrUnknown(id); + if (interaction == null) { + throw new IllegalArgumentException("Unknown interaction: " + id); + } else { + return interaction.needsRemoteSync(); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionCameraSettings.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionCameraSettings.java new file mode 100644 index 0000000..dea6acb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionCameraSettings.java @@ -0,0 +1,144 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class InteractionCameraSettings implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(InteractionCameraSettings.class, InteractionCameraSettings::new) + .appendInherited( + new KeyedCodec<>( + "FirstPerson", new ArrayCodec<>(InteractionCameraSettings.InteractionCamera.CODEC, InteractionCameraSettings.InteractionCamera[]::new) + ), + (o, i) -> o.firstPerson = i, + o -> o.firstPerson, + (o, p) -> o.firstPerson = p.firstPerson + ) + .addValidator(getInteractionCameraValidator()) + .add() + .appendInherited( + new KeyedCodec<>( + "ThirdPerson", new ArrayCodec<>(InteractionCameraSettings.InteractionCamera.CODEC, InteractionCameraSettings.InteractionCamera[]::new) + ), + (o, i) -> o.thirdPerson = i, + o -> o.thirdPerson, + (o, p) -> o.thirdPerson = p.thirdPerson + ) + .addValidator(getInteractionCameraValidator()) + .add() + .build(); + private InteractionCameraSettings.InteractionCamera[] firstPerson; + private InteractionCameraSettings.InteractionCamera[] thirdPerson; + + public InteractionCameraSettings() { + } + + @Nonnull + private static LegacyValidator getInteractionCameraValidator() { + return (interactionCameras, results) -> { + if (interactionCameras != null) { + float lastTime = -1.0F; + + for (InteractionCameraSettings.InteractionCamera entry : interactionCameras) { + if (entry.time <= lastTime) { + results.fail("Camera entry with time: " + entry.time + " conflicts with another entry"); + } + + lastTime = entry.time; + } + } + }; + } + + @Nonnull + public com.hypixel.hytale.protocol.InteractionCameraSettings toPacket() { + com.hypixel.hytale.protocol.InteractionCameraSettings packet = new com.hypixel.hytale.protocol.InteractionCameraSettings(); + if (this.firstPerson != null) { + packet.firstPerson = new com.hypixel.hytale.protocol.InteractionCamera[this.firstPerson.length]; + + for (int i = 0; i < this.firstPerson.length; i++) { + packet.firstPerson[i] = this.firstPerson[i].toPacket(); + } + } + + if (this.thirdPerson != null) { + packet.thirdPerson = new com.hypixel.hytale.protocol.InteractionCamera[this.thirdPerson.length]; + + for (int i = 0; i < this.thirdPerson.length; i++) { + packet.thirdPerson[i] = this.thirdPerson[i].toPacket(); + } + } + + return packet; + } + + @Nonnull + @Override + public String toString() { + return "InteractionCameraSettings{firstPerson=" + + Arrays.toString((Object[])this.firstPerson) + + ", thirdPerson=" + + Arrays.toString((Object[])this.thirdPerson) + + "}"; + } + + public static class InteractionCamera implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder( + InteractionCameraSettings.InteractionCamera.class, InteractionCameraSettings.InteractionCamera::new + ) + .appendInherited(new KeyedCodec<>("Time", Codec.FLOAT), (o, i) -> o.time = i, o -> o.time, (o, p) -> o.time = p.time) + .addValidator(Validators.greaterThan(0.0F)) + .add() + .appendInherited( + new KeyedCodec<>("Position", ProtocolCodecs.VECTOR3F), (o, i) -> o.position = i, o -> o.position, (o, p) -> o.position = p.position + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Rotation", ProtocolCodecs.DIRECTION), + (o, i) -> { + o.rotation = i; + o.rotation.yaw *= (float) (Math.PI / 180.0); + o.rotation.pitch *= (float) (Math.PI / 180.0); + o.rotation.roll *= (float) (Math.PI / 180.0); + }, + o -> new Direction( + o.rotation.yaw * (180.0F / (float)Math.PI), o.rotation.pitch * (180.0F / (float)Math.PI), o.rotation.roll * (180.0F / (float)Math.PI) + ), + (o, p) -> o.rotation = p.rotation + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + private float time = 0.1F; + private Vector3f position = new Vector3f(0.0F, 0.0F, 0.0F); + private Direction rotation = new Direction(0.0F, 0.0F, 0.0F); + + public InteractionCamera() { + } + + @Nonnull + public com.hypixel.hytale.protocol.InteractionCamera toPacket() { + com.hypixel.hytale.protocol.InteractionCamera packet = new com.hypixel.hytale.protocol.InteractionCamera(); + packet.time = this.time; + packet.position = this.position; + packet.rotation = this.rotation; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "InteractionCamera{time=" + this.time + ", position=" + this.position + ", rotation=" + this.rotation + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionConfiguration.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionConfiguration.java new file mode 100644 index 0000000..c708ee3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionConfiguration.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.PrioritySlot; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatMaps; +import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InteractionConfiguration implements NetworkSerializable { + public static final InteractionConfiguration DEFAULT = new InteractionConfiguration(); + public static final InteractionConfiguration DEFAULT_WEAPON = new InteractionConfiguration(false); + private static final Object2FloatMap DEFAULT_USE_DISTANCE = new Object2FloatOpenHashMap() { + { + this.putIfAbsent(GameMode.Adventure, 5.0F); + this.putIfAbsent(GameMode.Creative, 6.0F); + } + }; + public static final BuilderCodec CODEC = BuilderCodec.builder(InteractionConfiguration.class, InteractionConfiguration::new) + .appendInherited( + new KeyedCodec<>("DisplayOutlines", Codec.BOOLEAN), + (o, i) -> o.displayOutlines = i, + o -> o.displayOutlines, + (o, p) -> o.displayOutlines = p.displayOutlines + ) + .add() + .appendInherited( + new KeyedCodec<>("DebugOutlines", Codec.BOOLEAN), (o, i) -> o.debugOutlines = i, o -> o.debugOutlines, (o, p) -> o.debugOutlines = p.debugOutlines + ) + .add() + .>appendInherited( + new KeyedCodec<>("UseDistance", new EnumMapCodec<>(GameMode.class, Codec.FLOAT, () -> new Object2FloatOpenHashMap<>(DEFAULT_USE_DISTANCE), false)), + (o, i) -> o.useDistance = Object2FloatMaps.unmodifiable((Object2FloatOpenHashMap)i), + o -> o.useDistance, + (o, p) -> o.useDistance = p.useDistance + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("AllEntities", Codec.BOOLEAN), (o, i) -> o.allEntities = i, o -> o.allEntities, (o, p) -> o.allEntities = p.allEntities) + .add() + .>appendInherited( + new KeyedCodec<>("Priorities", new EnumMapCodec<>(InteractionType.class, InteractionPriority.CODEC, Object2ObjectOpenHashMap::new, false)), + (o, v) -> o.priorities = v, + o -> o.priorities, + (o, p) -> o.priorities = p.priorities + ) + .documentation("Configures the priority values for given interaction types on this item when two or more items are equipped.") + .add() + .build(); + protected boolean displayOutlines = true; + protected boolean debugOutlines; + protected Object2FloatMap useDistance = DEFAULT_USE_DISTANCE; + protected boolean allEntities; + @Nullable + protected Map priorities; + + public InteractionConfiguration() { + } + + public InteractionConfiguration(boolean displayOutlines) { + this.displayOutlines = displayOutlines; + } + + public int getPriorityFor(InteractionType interactionType, PrioritySlot slot) { + if (this.priorities == null) { + return 0; + } else { + InteractionPriority priority = this.priorities.get(interactionType); + return priority == null ? 0 : priority.getPriority(slot); + } + } + + @Nonnull + public com.hypixel.hytale.protocol.InteractionConfiguration toPacket() { + com.hypixel.hytale.protocol.InteractionConfiguration packet = new com.hypixel.hytale.protocol.InteractionConfiguration(); + packet.displayOutlines = this.displayOutlines; + packet.debugOutlines = this.debugOutlines; + packet.useDistance = this.useDistance; + packet.allEntities = this.allEntities; + if (this.priorities != null && !this.priorities.isEmpty()) { + Object2ObjectOpenHashMap packetPriorities = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : this.priorities.entrySet()) { + packetPriorities.put(entry.getKey(), entry.getValue().toPacket()); + } + + packet.priorities = packetPriorities; + } + + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionEffects.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionEffects.java new file mode 100644 index 0000000..bb0b568 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionEffects.java @@ -0,0 +1,297 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.protocol.ModelTrail; +import com.hypixel.hytale.server.core.asset.modifiers.MovementEffects; +import com.hypixel.hytale.server.core.asset.type.camera.CameraEffect; +import com.hypixel.hytale.server.core.asset.type.itemanimation.config.ItemPlayerAnimations; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelParticle; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.validator.SoundEventValidators; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class InteractionEffects implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(InteractionEffects.class, InteractionEffects::new) + .appendInherited( + new KeyedCodec<>("Particles", ModelParticle.ARRAY_CODEC), + (activationEffects, s) -> activationEffects.particles = s, + activationEffects -> activationEffects.particles, + (activationEffects, parent) -> activationEffects.particles = parent.particles + ) + .documentation("Particles to play when triggering this interaction.") + .add() + .appendInherited( + new KeyedCodec<>("FirstPersonParticles", ModelParticle.ARRAY_CODEC), + (activationEffects, s) -> activationEffects.firstPersonParticles = s, + activationEffects -> activationEffects.firstPersonParticles, + (activationEffects, parent) -> activationEffects.firstPersonParticles = parent.firstPersonParticles + ) + .documentation("Particles to play when triggering this interaction while in first person.") + .add() + .appendInherited( + new KeyedCodec<>("WorldSoundEventId", Codec.STRING), + (activationEffects, s) -> activationEffects.worldSoundEventId = s, + activationEffects -> activationEffects.worldSoundEventId, + (activationEffects, parent) -> activationEffects.worldSoundEventId = parent.worldSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .documentation("Sound to play when triggering this interaction.") + .add() + .appendInherited( + new KeyedCodec<>("LocalSoundEventId", Codec.STRING), + (activationEffects, s) -> activationEffects.localSoundEventId = s, + activationEffects -> activationEffects.localSoundEventId, + (activationEffects, parent) -> activationEffects.localSoundEventId = parent.localSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .documentation("Sound to play when triggering this interaction but only to the player that triggered it.") + .add() + .appendInherited( + new KeyedCodec<>("Trails", new ArrayCodec<>(ModelAsset.MODEL_TRAIL_CODEC, ModelTrail[]::new)), + (activationEffects, s) -> activationEffects.trails = s, + activationEffects -> activationEffects.trails, + (activationEffects, parent) -> activationEffects.trails = parent.trails + ) + .documentation("The model trails to create when triggering this interaction") + .add() + .appendInherited( + new KeyedCodec<>("WaitForAnimationToFinish", Codec.BOOLEAN), + (activationEffects, s) -> activationEffects.waitForAnimationToFinish = s, + activationEffects -> activationEffects.waitForAnimationToFinish, + (activationEffects, parent) -> activationEffects.waitForAnimationToFinish = parent.waitForAnimationToFinish + ) + .documentation( + "Whether this interaction should hold until the animation is finished before continuing.\nGenerally this overrides the runtime of the interaction." + ) + .add() + .appendInherited( + new KeyedCodec<>("ItemPlayerAnimationsId", ItemPlayerAnimations.CHILD_CODEC), + (o, i) -> o.itemPlayerAnimationsId = i, + o -> o.itemPlayerAnimationsId, + (o, p) -> o.itemPlayerAnimationsId = p.itemPlayerAnimationsId + ) + .addValidator(ItemPlayerAnimations.VALIDATOR_CACHE.getValidator()) + .documentation("The item animations set to use while this interaction is active") + .add() + .appendInherited( + new KeyedCodec<>("ItemAnimationId", Codec.STRING), + (activationEffects, s) -> activationEffects.itemAnimationId = s, + activationEffects -> activationEffects.itemAnimationId, + (activationEffects, parent) -> activationEffects.itemAnimationId = parent.itemAnimationId + ) + .documentation("The item animation to play when triggering this interaction.") + .add() + .appendInherited( + new KeyedCodec<>("ClearAnimationOnFinish", Codec.BOOLEAN), + (activationEffects, s) -> activationEffects.clearAnimationOnFinish = s, + activationEffects -> activationEffects.clearAnimationOnFinish, + (activationEffects, parent) -> activationEffects.clearAnimationOnFinish = parent.clearAnimationOnFinish + ) + .documentation("Whether any animations triggered by this interaction should be cleared when this interaction finishes.") + .add() + .appendInherited( + new KeyedCodec<>("ClearSoundEventOnFinish", Codec.BOOLEAN), + (activationEffects, s) -> activationEffects.clearSoundEventOnFinish = s, + activationEffects -> activationEffects.clearSoundEventOnFinish, + (activationEffects, parent) -> activationEffects.clearSoundEventOnFinish = parent.clearSoundEventOnFinish + ) + .documentation("Whether any sound events triggered by this interaction should be cleared when this interaction finishes.") + .add() + .appendInherited( + new KeyedCodec<>("CameraEffect", CameraEffect.CHILD_ASSET_CODEC), + (activationEffects, s) -> activationEffects.cameraEffectId = s, + activationEffects -> activationEffects.cameraEffectId, + (activationEffects, parent) -> activationEffects.cameraEffectId = parent.cameraEffectId + ) + .addValidator(CameraEffect.VALIDATOR_CACHE.getValidator()) + .documentation("The camera effects to trigger while this interaction is active.") + .add() + .appendInherited( + new KeyedCodec<>("MovementEffects", MovementEffects.CODEC), + (activationEffects, s) -> activationEffects.movementEffects = s, + activationEffects -> activationEffects.movementEffects, + (activationEffects, parent) -> activationEffects.movementEffects = parent.movementEffects + ) + .documentation("The movement effects to apply while this interaction is active.") + .add() + .appendInherited( + new KeyedCodec<>("StartDelay", Codec.FLOAT), + (activationEffects, f) -> activationEffects.startDelay = f, + activationEffects -> activationEffects.startDelay, + (activationEffects, parent) -> activationEffects.startDelay = parent.startDelay + ) + .documentation("An optional delay on applying any interaction effects.") + .add() + .afterDecode(InteractionEffects::processConfig) + .build(); + protected ModelParticle[] particles; + protected ModelParticle[] firstPersonParticles; + protected String worldSoundEventId; + protected transient int worldSoundEventIndex = 0; + protected String localSoundEventId; + protected transient int localSoundEventIndex = 0; + protected String onFinishLocalSoundEventId; + protected transient int onFinishLocalSoundEventIndex = 0; + protected ModelTrail[] trails; + protected boolean waitForAnimationToFinish; + protected String itemPlayerAnimationsId; + protected String itemAnimationId; + protected boolean clearAnimationOnFinish; + protected boolean clearSoundEventOnFinish; + protected String cameraEffectId; + protected int cameraEffectIndex = Integer.MIN_VALUE; + protected MovementEffects movementEffects; + protected float startDelay = 0.0F; + + protected InteractionEffects() { + } + + @Nonnull + public com.hypixel.hytale.protocol.InteractionEffects toPacket() { + com.hypixel.hytale.protocol.InteractionEffects packet = new com.hypixel.hytale.protocol.InteractionEffects(); + if (this.particles != null && this.particles.length > 0) { + packet.particles = new com.hypixel.hytale.protocol.ModelParticle[this.particles.length]; + + for (int i = 0; i < this.particles.length; i++) { + packet.particles[i] = this.particles[i].toPacket(); + } + } + + if (this.firstPersonParticles != null && this.firstPersonParticles.length > 0) { + packet.firstPersonParticles = new com.hypixel.hytale.protocol.ModelParticle[this.firstPersonParticles.length]; + + for (int i = 0; i < this.firstPersonParticles.length; i++) { + packet.firstPersonParticles[i] = this.firstPersonParticles[i].toPacket(); + } + } + + if (this.cameraEffectIndex != Integer.MIN_VALUE) { + CameraEffect cameraShakeEffect = CameraEffect.getAssetMap().getAsset(this.cameraEffectIndex); + if (cameraShakeEffect != null) { + packet.cameraShake = cameraShakeEffect.createCameraShakePacket(); + } + } + + packet.worldSoundEventIndex = this.worldSoundEventIndex; + packet.localSoundEventIndex = this.localSoundEventIndex != 0 ? this.localSoundEventIndex : this.worldSoundEventIndex; + packet.trails = this.trails; + packet.waitForAnimationToFinish = this.waitForAnimationToFinish; + packet.itemPlayerAnimationsId = this.itemPlayerAnimationsId; + packet.itemAnimationId = this.itemAnimationId; + packet.clearAnimationOnFinish = this.clearAnimationOnFinish; + packet.clearSoundEventOnFinish = this.clearSoundEventOnFinish; + packet.startDelay = this.startDelay; + if (this.movementEffects != null) { + packet.movementEffects = this.movementEffects.toPacket(); + } + + return packet; + } + + public ModelParticle[] getParticles() { + return this.particles; + } + + public String getWorldSoundEventId() { + return this.worldSoundEventId; + } + + public int getWorldSoundEventIndex() { + return this.worldSoundEventIndex; + } + + public String getLocalSoundEventId() { + return this.localSoundEventId; + } + + public int getLocalSoundEventIndex() { + return this.localSoundEventIndex; + } + + public ModelTrail[] getTrails() { + return this.trails; + } + + public boolean isWaitForAnimationToFinish() { + return this.waitForAnimationToFinish; + } + + public String getItemPlayerAnimationsId() { + return this.itemPlayerAnimationsId; + } + + public String getItemAnimationId() { + return this.itemAnimationId; + } + + public boolean isClearAnimationOnFinish() { + return this.clearAnimationOnFinish; + } + + public float getStartDelay() { + return this.startDelay; + } + + public MovementEffects getMovementEffects() { + return this.movementEffects; + } + + protected void processConfig() { + if (this.worldSoundEventId != null) { + this.worldSoundEventIndex = SoundEvent.getAssetMap().getIndex(this.worldSoundEventId); + } + + if (this.localSoundEventId != null) { + this.localSoundEventIndex = SoundEvent.getAssetMap().getIndex(this.localSoundEventId); + } + + if (this.cameraEffectId != null) { + this.cameraEffectIndex = CameraEffect.getAssetMap().getIndex(this.cameraEffectId); + } + } + + @Nonnull + @Override + public String toString() { + return "InteractionEffects{particles=" + + Arrays.toString((Object[])this.particles) + + ", firstPersonParticles=" + + Arrays.toString((Object[])this.firstPersonParticles) + + ", worldSoundEventId='" + + this.worldSoundEventId + + "', worldSoundEventIndex=" + + this.worldSoundEventIndex + + ", localSoundEventId='" + + this.localSoundEventId + + "', localSoundEventIndex=" + + this.localSoundEventIndex + + ", trails=" + + Arrays.toString((Object[])this.trails) + + ", waitForAnimationToFinish=" + + this.waitForAnimationToFinish + + ", itemPlayerAnimationsId='" + + this.itemPlayerAnimationsId + + "', itemAnimationId='" + + this.itemAnimationId + + "', clearAnimationOnFinish=" + + this.clearAnimationOnFinish + + ", clearSoundEventOnFinish=" + + this.clearSoundEventOnFinish + + ", cameraShakeEffectId='" + + this.cameraEffectId + + "', cameraShakeEffectIndex=" + + this.cameraEffectIndex + + ", movementEffects=" + + this.movementEffects + + ", startDelay=" + + this.startDelay + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionPriority.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionPriority.java new file mode 100644 index 0000000..240cc4a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionPriority.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.protocol.PrioritySlot; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import java.util.EnumMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public record InteractionPriority(@Nullable Map values) implements NetworkSerializable { + public static final Codec CODEC = new InteractionPriorityCodec(); + + public InteractionPriority(int defaultValue) { + this(defaultValue != 0 ? new EnumMap<>(Map.of(PrioritySlot.Default, defaultValue)) : null); + } + + public int getPriority(PrioritySlot slot) { + if (this.values == null) { + return 0; + } else { + Integer value = this.values.get(slot); + if (value != null) { + return value; + } else { + Integer defaultValue = this.values.get(PrioritySlot.Default); + return defaultValue != null ? defaultValue : 0; + } + } + } + + @Nonnull + public com.hypixel.hytale.protocol.InteractionPriority toPacket() { + com.hypixel.hytale.protocol.InteractionPriority packet = new com.hypixel.hytale.protocol.InteractionPriority(); + if (this.values != null && !this.values.isEmpty()) { + packet.values = new EnumMap<>(this.values); + } + + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionPriorityCodec.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionPriorityCodec.java new file mode 100644 index 0000000..9d7117c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionPriorityCodec.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.codec.exception.CodecException; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.protocol.PrioritySlot; +import java.io.IOException; +import java.util.EnumMap; +import java.util.Map; +import javax.annotation.Nonnull; +import org.bson.BsonInt32; +import org.bson.BsonValue; + +public class InteractionPriorityCodec implements Codec { + private static final EnumMapCodec MAP_CODEC = new EnumMapCodec<>( + PrioritySlot.class, Codec.INTEGER, () -> new EnumMap<>(PrioritySlot.class), false + ); + + public InteractionPriorityCodec() { + } + + @Nonnull + public InteractionPriority decode(@Nonnull BsonValue bsonValue, ExtraInfo extraInfo) { + if (bsonValue.isInt32()) { + return new InteractionPriority(bsonValue.asInt32().getValue()); + } else if (bsonValue.isDocument()) { + return new InteractionPriority(MAP_CODEC.decode(bsonValue, extraInfo)); + } else { + throw new CodecException("Expected integer or object for InteractionPriority, got: " + bsonValue.getBsonType()); + } + } + + @Nonnull + public BsonValue encode(@Nonnull InteractionPriority priority, ExtraInfo extraInfo) { + Map values = priority.values(); + if (values != null && !values.isEmpty()) { + return (BsonValue)(values.size() == 1 && values.containsKey(PrioritySlot.Default) + ? new BsonInt32(values.get(PrioritySlot.Default)) + : MAP_CODEC.encode(values, extraInfo)); + } else { + return new BsonInt32(0); + } + } + + @Nonnull + public InteractionPriority decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException { + reader.consumeWhiteSpace(); + int peek = reader.peek(); + if (peek == 123) { + return new InteractionPriority(MAP_CODEC.decodeJson(reader, extraInfo)); + } else if (peek != 45 && !Character.isDigit(peek)) { + throw new CodecException("Expected integer or object for InteractionPriority, got: " + (char)peek, reader, extraInfo, null); + } else { + return new InteractionPriority(reader.readIntValue()); + } + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + Schema schema = MAP_CODEC.toSchema(context); + schema.setTitle("InteractionPriority"); + schema.setDescription("Either an integer (default for all types) or an object with named priorities (e.g., 'MainHand', 'OffHand', 'Default')."); + return schema; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionRules.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionRules.java new file mode 100644 index 0000000..f2d47bc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionRules.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.Collections; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InteractionRules implements NetworkSerializable { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(InteractionRules.class, InteractionRules::new) + .appendInherited( + new KeyedCodec<>("BlockedBy", InteractionModule.INTERACTION_TYPE_SET_CODEC), + (o, i) -> o.blockedBy = i, + o -> o.blockedBy, + (o, p) -> o.blockedBy = p.blockedBy + ) + .documentation( + "A collection of interaction types that should block this interaction from starting. If not set then a set of default rules will be applied based on the interaction type that theinteraction is fired with.\nThis is only effective when used on the root interaction of a chain." + ) + .add() + .>appendInherited( + new KeyedCodec<>("Blocking", InteractionModule.INTERACTION_TYPE_SET_CODEC), + (o, i) -> o.blocking = i, + o -> o.blocking, + (o, p) -> o.blocking = p.blocking + ) + .documentation( + "A collection of interaction types that this interaction blocks from starting whilst running.\nDefaults to an empty set (blocking nothing)." + ) + .addValidator(Validators.nonNull()) + .add() + .>appendInherited( + new KeyedCodec<>("InterruptedBy", InteractionModule.INTERACTION_TYPE_SET_CODEC), + (o, i) -> o.interruptedBy = i, + o -> o.interruptedBy, + (o, p) -> o.interruptedBy = p.interruptedBy + ) + .documentation( + "A collection of interaction types that should stop this interaction while it's running.\nThis is only effective when used on the root interaction of a chain." + ) + .add() + .>appendInherited( + new KeyedCodec<>("Interrupting", InteractionModule.INTERACTION_TYPE_SET_CODEC), + (o, i) -> o.interrupting = i, + o -> o.interrupting, + (o, p) -> o.interrupting = p.interrupting + ) + .documentation("A collection of interaction types that this interaction should stop when it starts.") + .add() + .appendInherited( + new KeyedCodec<>("BlockedByBypass", Codec.STRING), + (o, i) -> o.blockedByBypass = i, + o -> o.blockedByBypass, + (o, p) -> o.blockedByBypass = p.blockedByBypass + ) + .documentation("A tag that if matched will bypass the `BlockedBy` rules.") + .add() + .appendInherited( + new KeyedCodec<>("BlockingBypass", Codec.STRING), (o, i) -> o.blockingBypass = i, o -> o.blockingBypass, (o, p) -> o.blockingBypass = p.blockingBypass + ) + .documentation("A tag that if matched will bypass the `Blocking` rules.") + .add() + .appendInherited( + new KeyedCodec<>("InterruptedByBypass", Codec.STRING), + (o, i) -> o.interruptedByBypass = i, + o -> o.interruptedByBypass, + (o, p) -> o.interruptedByBypass = p.interruptedByBypass + ) + .documentation("A tag that if matched will bypass the `InterruptedBy` rules.") + .add() + .appendInherited( + new KeyedCodec<>("InterruptingBypass", Codec.STRING), + (o, i) -> o.interruptingBypass = i, + o -> o.interruptingBypass, + (o, p) -> o.interruptingBypass = p.interruptingBypass + ) + .documentation("A tag that if matched will bypass the `Interrupting` rules.") + .add() + .afterDecode(i -> { + if (i.blockedByBypass != null) { + i.blockedByBypassIndex = AssetRegistry.getOrCreateTagIndex(i.blockedByBypass); + } + + if (i.blockingBypass != null) { + i.blockingBypassIndex = AssetRegistry.getOrCreateTagIndex(i.blockingBypass); + } + + if (i.interruptedByBypass != null) { + i.interruptedByBypassIndex = AssetRegistry.getOrCreateTagIndex(i.interruptedByBypass); + } + + if (i.interruptingBypass != null) { + i.interruptingBypassIndex = AssetRegistry.getOrCreateTagIndex(i.interruptingBypass); + } + }) + .build(); + @Nonnull + public static InteractionRules DEFAULT_RULES = new InteractionRules(); + @Nullable + protected Set blockedBy; + @Nonnull + protected Set blocking = Collections.emptySet(); + @Nullable + protected Set interruptedBy; + @Nullable + protected Set interrupting; + @Nullable + protected String blockedByBypass; + protected int blockedByBypassIndex = Integer.MIN_VALUE; + @Nullable + protected String blockingBypass; + protected int blockingBypassIndex = Integer.MIN_VALUE; + @Nullable + protected String interruptedByBypass; + protected int interruptedByBypassIndex = Integer.MIN_VALUE; + @Nullable + protected String interruptingBypass; + protected int interruptingBypassIndex = Integer.MIN_VALUE; + + public InteractionRules() { + } + + public boolean validateInterrupts( + @Nonnull InteractionType type, + @Nonnull Int2ObjectMap selfTags, + @Nonnull InteractionType otherType, + @Nonnull Int2ObjectMap otherTags, + @Nonnull InteractionRules otherRules + ) { + return otherRules.interruptedBy == null + || !otherRules.interruptedBy.contains(type) + || otherRules.interruptedByBypassIndex != Integer.MIN_VALUE && selfTags.containsKey(otherRules.interruptedByBypassIndex) + ? this.interrupting != null + && this.interrupting.contains(otherType) + && (this.interruptingBypassIndex == Integer.MIN_VALUE || !otherTags.containsKey(this.interruptingBypassIndex)) + : true; + } + + public boolean validateBlocked( + @Nonnull InteractionType type, + @Nonnull Int2ObjectMap selfTags, + @Nonnull InteractionType otherType, + @Nonnull Int2ObjectMap otherTags, + @Nonnull InteractionRules otherRules + ) { + Set blockedBy = this.blockedBy != null ? this.blockedBy : InteractionTypeUtils.DEFAULT_INTERACTION_BLOCKED_BY.get(type); + return !blockedBy.contains(otherType) || this.blockedByBypassIndex != Integer.MIN_VALUE && otherTags.containsKey(this.blockedByBypassIndex) + ? otherRules.blocking.contains(type) && (otherRules.blockingBypassIndex == Integer.MIN_VALUE || !selfTags.containsKey(otherRules.blockingBypassIndex)) + : true; + } + + @Nonnull + public com.hypixel.hytale.protocol.InteractionRules toPacket() { + com.hypixel.hytale.protocol.InteractionRules packet = new com.hypixel.hytale.protocol.InteractionRules(); + packet.blockedBy = this.blockedBy == null ? null : this.blockedBy.toArray(InteractionType[]::new); + packet.blocking = this.blocking.isEmpty() ? null : this.blocking.toArray(InteractionType[]::new); + packet.interruptedBy = this.interruptedBy == null ? null : this.interruptedBy.toArray(InteractionType[]::new); + packet.interrupting = this.interrupting == null ? null : this.interrupting.toArray(InteractionType[]::new); + packet.blockedByBypassIndex = this.blockedByBypassIndex; + packet.blockingBypassIndex = this.blockingBypassIndex; + packet.interruptedByBypassIndex = this.interruptedByBypassIndex; + packet.interruptingBypassIndex = this.interruptingBypassIndex; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "InteractionRules{blockedBy=" + + this.blockedBy + + ", blocking=" + + this.blocking + + ", interruptedBy=" + + this.interruptedBy + + ", interrupting=" + + this.interrupting + + ", blockedByBypass='" + + this.blockedByBypass + + "', blockedByBypassIndex=" + + this.blockedByBypassIndex + + ", blockingBypass='" + + this.blockingBypass + + "', blockingBypassIndex=" + + this.blockingBypassIndex + + ", interruptedByBypass='" + + this.interruptedByBypass + + "', interruptedByBypassIndex=" + + this.interruptedByBypassIndex + + ", interruptingBypass='" + + this.interruptingBypass + + "', interruptingBypassIndex=" + + this.interruptedByBypassIndex + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionTypeUtils.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionTypeUtils.java new file mode 100644 index 0000000..3957945 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/InteractionTypeUtils.java @@ -0,0 +1,71 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config; + +import com.hypixel.hytale.protocol.InteractionType; +import java.util.Collections; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; + +public class InteractionTypeUtils { + @Nonnull + public static final Set STANDARD_INPUT = EnumSet.of( + InteractionType.Primary, + InteractionType.Secondary, + InteractionType.Ability1, + InteractionType.Ability2, + InteractionType.Ability3, + InteractionType.Use, + InteractionType.Pick, + InteractionType.SwapFrom, + InteractionType.SwapTo + ); + @Nonnull + public static final Map> DEFAULT_INTERACTION_BLOCKED_BY = Collections.unmodifiableMap( + new EnumMap<>( + Map.ofEntries( + Map.entry(InteractionType.Primary, STANDARD_INPUT), + Map.entry(InteractionType.Secondary, STANDARD_INPUT), + Map.entry(InteractionType.Ability1, STANDARD_INPUT), + Map.entry(InteractionType.Ability2, STANDARD_INPUT), + Map.entry(InteractionType.Ability3, STANDARD_INPUT), + Map.entry(InteractionType.Use, STANDARD_INPUT), + Map.entry(InteractionType.Pick, STANDARD_INPUT), + Map.entry(InteractionType.Pickup, Collections.emptySet()), + Map.entry(InteractionType.CollisionEnter, Collections.emptySet()), + Map.entry(InteractionType.CollisionLeave, Collections.emptySet()), + Map.entry(InteractionType.Collision, Collections.emptySet()), + Map.entry(InteractionType.EntityStatEffect, Collections.emptySet()), + Map.entry(InteractionType.Death, Collections.emptySet()), + Map.entry(InteractionType.Wielding, Collections.emptySet()), + Map.entry(InteractionType.SwapTo, EnumSet.of(InteractionType.SwapFrom, InteractionType.SwapTo)), + Map.entry(InteractionType.SwapFrom, EnumSet.of(InteractionType.SwapFrom, InteractionType.SwapTo)), + Map.entry(InteractionType.ProjectileSpawn, Collections.emptySet()), + Map.entry(InteractionType.ProjectileBounce, Collections.emptySet()), + Map.entry(InteractionType.ProjectileMiss, Collections.emptySet()), + Map.entry(InteractionType.ProjectileHit, Collections.emptySet()), + Map.entry(InteractionType.Held, Set.of(InteractionType.Held)), + Map.entry(InteractionType.HeldOffhand, Set.of(InteractionType.HeldOffhand)), + Map.entry(InteractionType.Equipped, Set.of(InteractionType.Equipped)), + Map.entry(InteractionType.Dodge, Set.of(InteractionType.Dodge)), + Map.entry(InteractionType.GameModeSwap, Set.of(InteractionType.GameModeSwap)) + ) + ) + ); + public static final float DEFAULT_COOLDOWN = 0.35F; + + public InteractionTypeUtils() { + } + + public static float getDefaultCooldown(@Nonnull InteractionType type) { + return switch (type) { + case CollisionEnter, CollisionLeave, ProjectileSpawn, ProjectileHit, ProjectileMiss, ProjectileBounce, GameModeSwap, EntityStatEffect, Pickup -> 0.0F; + default -> 0.35F; + }; + } + + public static boolean isCollisionType(@Nonnull InteractionType type) { + return type == InteractionType.Collision || type == InteractionType.CollisionEnter || type == InteractionType.CollisionLeave; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/RootInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/RootInteraction.java new file mode 100644 index 0000000..ec1ca74 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/RootInteraction.java @@ -0,0 +1,374 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.codec.validation.validator.FloatArrayValidator; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.InteractionCooldown; +import com.hypixel.hytale.protocol.RootInteractionSettings; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.Operation; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.OperationsBuilder; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RootInteraction + implements JsonAssetWithMap>, + NetworkSerializable { + @Nonnull + public static final BuilderCodec COOLDOWN_CODEC = BuilderCodec.builder(InteractionCooldown.class, InteractionCooldown::new) + .appendInherited( + new KeyedCodec<>("Id", Codec.STRING), + (interactionCooldown, s) -> interactionCooldown.cooldownId = s, + interactionCooldown -> interactionCooldown.cooldownId, + (interactionCooldown, parent) -> interactionCooldown.cooldownId = parent.cooldownId + ) + .documentation("The Id for the cooldown.\n\nCooldowns can be used on different interactions but share a cooldown.") + .add() + .appendInherited( + new KeyedCodec<>("Cooldown", Codec.FLOAT), + (interactionCooldown, s) -> interactionCooldown.cooldown = s, + interactionCooldown -> interactionCooldown.cooldown, + (interactionCooldown, parent) -> interactionCooldown.cooldown = parent.cooldown + ) + .addValidator(Validators.greaterThanOrEqual(0.0F)) + .documentation("The time in seconds this cooldown should last for.") + .add() + .appendInherited( + new KeyedCodec<>("Charges", Codec.FLOAT_ARRAY), + (interactionCharges, s) -> interactionCharges.chargeTimes = s, + interactionCharges -> interactionCharges.chargeTimes, + (interactionCharges, parent) -> interactionCharges.chargeTimes = parent.chargeTimes + ) + .addValidator(new FloatArrayValidator(Validators.greaterThanOrEqual(0.0F))) + .documentation("The charge times available for this interaction.") + .add() + .appendInherited( + new KeyedCodec<>("SkipCooldownReset", Codec.BOOLEAN), + (interactionCharges, s) -> interactionCharges.skipCooldownReset = s, + interactionCharges -> interactionCharges.skipCooldownReset, + (interactionCharges, parent) -> interactionCharges.skipCooldownReset = parent.skipCooldownReset + ) + .documentation("Determines whether resetting cooldown should be skipped.") + .add() + .appendInherited( + new KeyedCodec<>("InterruptRecharge", Codec.BOOLEAN), + (interactionCharges, s) -> interactionCharges.interruptRecharge = s, + interactionCharges -> interactionCharges.interruptRecharge, + (interactionCharges, parent) -> interactionCharges.interruptRecharge = parent.interruptRecharge + ) + .documentation("Determines whether recharge is interrupted by use.") + .add() + .appendInherited( + new KeyedCodec<>("ClickBypass", Codec.BOOLEAN), + (interactionCooldown, s) -> interactionCooldown.clickBypass = s, + interactionCooldown -> interactionCooldown.clickBypass, + (interactionCooldown, parent) -> interactionCooldown.clickBypass = parent.clickBypass + ) + .documentation("Whether this cooldown can be bypassed by clicking.") + .add() + .build(); + @Nonnull + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + RootInteraction.class, RootInteraction::new, Codec.STRING, (o, i) -> o.id = i, o -> o.id, (o, i) -> o.data = i, o -> o.data + ) + .documentation( + "A **RootInteraction** serves as an entry point into a set of **Interaction**s.\n\nIn order to start an interaction chain a **RootInteraction** is required.\nA basic **RootInteraction** can simply contain a reference to single interaction within _Interactions_ field which will be the entire chain. More complex cases can configure the other fields.\n\nMost fields configured here apply to all **Interaction**s contained the root and any **Interaction**s they contain as well. Systems that look at tags for interactions may also check the root interaction as well reducing the need to duplicate them on all nested interactions." + ) + .appendInherited( + new KeyedCodec<>("Interactions", Interaction.CHILD_ASSET_CODEC_ARRAY), + (o, i) -> o.interactionIds = i, + o -> o.interactionIds, + (o, p) -> o.interactionIds = p.interactionIds + ) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyArray()) + .addValidatorLate(() -> Interaction.VALIDATOR_CACHE.getArrayValidator().late()) + .documentation( + "The list of interactions that will be run when starting a chain with this root interaction. Interactions in this list will be run in sequence." + ) + .add() + .appendInherited( + new KeyedCodec<>("Cooldown", COOLDOWN_CODEC), (o, i) -> o.cooldown = i, o -> o.cooldown, (o, p) -> o.cooldown = p.cooldown + ) + .documentation( + "Cooldowns are used to prevent an interaction from running repeatedly too quickly.\n\nDuring a cooldown attempting to run an interaction with the same cooldown id will fail." + ) + .add() + .appendInherited(new KeyedCodec<>("Rules", InteractionRules.CODEC), (o, i) -> o.rules = i, o -> o.rules, (o, p) -> o.rules = p.rules) + .documentation("A set of rules that control when this root interaction can run or what interactions this root being active prevents.") + .addValidator(Validators.nonNull()) + .add() + .>appendInherited( + new KeyedCodec<>( + "Settings", + new EnumMapCodec<>( + GameMode.class, + BuilderCodec.builder(RootInteractionSettings.class, RootInteractionSettings::new) + .appendInherited(new KeyedCodec<>("Cooldown", COOLDOWN_CODEC), (o, i) -> o.cooldown = i, o -> o.cooldown, (o, p) -> o.cooldown = p.cooldown) + .documentation( + "Cooldowns are used to prevent an interaction from running repeatedly too quickly.\n\nDuring a cooldown attempting to run an interaction with the same cooldown id will fail." + ) + .add() + .appendInherited( + new KeyedCodec<>("AllowSkipChainOnClick", Codec.BOOLEAN), + (o, i) -> o.allowSkipChainOnClick = i, + o -> o.allowSkipChainOnClick, + (o, p) -> o.allowSkipChainOnClick = p.allowSkipChainOnClick + ) + .documentation("Whether to skip the whole interaction chain when another click is sent.") + .add() + .build() + ) + ), + (o, i) -> o.settings = i, + o -> o.settings, + (o, p) -> o.settings = p.settings + ) + .documentation("Per a gamemode settings.") + .add() + .appendInherited( + new KeyedCodec<>("ClickQueuingTimeout", Codec.FLOAT), + (interaction, s) -> interaction.clickQueuingTimeout = s, + interaction -> interaction.clickQueuingTimeout, + (interaction, parent) -> interaction.clickQueuingTimeout = parent.clickQueuingTimeout + ) + .documentation("Controls the amount of time this root interaction can remain in the click queue before being discarded.") + .add() + .appendInherited( + new KeyedCodec<>("RequireNewClick", Codec.BOOLEAN), + (o, i) -> o.requireNewClick = i, + o -> o.requireNewClick, + (o, p) -> o.requireNewClick = p.requireNewClick + ) + .documentation("Requires the user to click again before running another root interaction of the same type.") + .add() + .build(); + @Nonnull + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(RootInteraction::getAssetStore)); + @Nonnull + public static final ContainedAssetCodec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(RootInteraction.class, CODEC); + @Nonnull + public static final Codec CHILD_ASSET_CODEC_ARRAY = new ArrayCodec<>(CHILD_ASSET_CODEC, String[]::new); + @Nonnull + public static final MapCodec> CHILD_ASSET_CODEC_MAP = new MapCodec<>(CHILD_ASSET_CODEC, HashMap::new); + private static AssetStore> ASSET_STORE; + protected String id; + protected AssetExtraInfo.Data data; + @Nonnull + protected String[] interactionIds = ArrayUtil.EMPTY_STRING_ARRAY; + @Nullable + protected InteractionCooldown cooldown; + @Nonnull + protected Map settings = Collections.emptyMap(); + protected boolean requireNewClick; + protected float clickQueuingTimeout; + @Nonnull + protected InteractionRules rules = InteractionRules.DEFAULT_RULES; + protected Operation[] operations; + protected boolean needsRemoteSync; + + public RootInteraction() { + } + + public RootInteraction(@Nonnull String id, @Nonnull String... interactionIds) { + this.id = id; + this.interactionIds = interactionIds; + this.data = new AssetExtraInfo.Data(RootInteraction.class, id, null); + } + + public RootInteraction(@Nonnull String id, @Nullable InteractionCooldown cooldown, @Nonnull String... interactionIds) { + this.id = id; + this.cooldown = cooldown; + this.interactionIds = interactionIds; + this.data = new AssetExtraInfo.Data(RootInteraction.class, id, null); + } + + @Nonnull + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(RootInteraction.class); + } + + return ASSET_STORE; + } + + @Nonnull + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + public String getId() { + return this.id; + } + + public boolean needsRemoteSync() { + return this.needsRemoteSync; + } + + public boolean resetCooldownOnStart() { + return this.cooldown == null || !this.cooldown.skipCooldownReset; + } + + @Nullable + public Operation getOperation(int index) { + return index >= this.operations.length ? null : this.operations[index]; + } + + public int getOperationMax() { + return this.operations.length; + } + + public String[] getInteractionIds() { + return this.interactionIds; + } + + @Nonnull + public Map getSettings() { + return this.settings; + } + + public float getClickQueuingTimeout() { + return this.clickQueuingTimeout; + } + + @Nonnull + public InteractionRules getRules() { + return this.rules; + } + + @Nullable + public InteractionCooldown getCooldown() { + return this.cooldown; + } + + public AssetExtraInfo.Data getData() { + return this.data; + } + + public void build(@Nonnull Set modifiedInteractions) { + if (this.operations != null) { + this.build(); + } + } + + public void build() { + if (this.interactionIds != null) { + OperationsBuilder builder = new OperationsBuilder(); + boolean needsSyncRemote = false; + + for (String interactionId : this.interactionIds) { + Interaction interaction = Interaction.getAssetMap().getAsset(interactionId); + if (interaction != null) { + interaction.compile(builder); + needsSyncRemote |= interaction.needsRemoteSync(); + } + } + + this.operations = builder.build(); + this.needsRemoteSync = needsSyncRemote; + + for (Operation op : this.operations) { + if (op.getInnerOperation() instanceof Interaction inter && inter.getWaitForDataFrom() == WaitForDataFrom.Client && !inter.needsRemoteSync()) { + throw new IllegalArgumentException(inter + " needs client data but isn't marked as requiring syncing to remote clients"); + } + } + } + } + + @Nonnull + public com.hypixel.hytale.protocol.RootInteraction toPacket() { + com.hypixel.hytale.protocol.RootInteraction packet = new com.hypixel.hytale.protocol.RootInteraction(); + packet.id = this.id; + packet.interactions = new int[this.interactionIds.length]; + + for (int i = 0; i < this.interactionIds.length; i++) { + packet.interactions[i] = Interaction.getInteractionIdOrUnknown(this.interactionIds[i]); + } + + packet.clickQueuingTimeout = this.clickQueuingTimeout; + packet.requireNewClick = this.requireNewClick; + packet.rules = this.rules.toPacket(); + packet.settings = this.settings; + packet.cooldown = this.cooldown; + if (this.data != null) { + packet.tags = this.data.getTags().keySet().toIntArray(); + } + + return packet; + } + + @Nullable + @Deprecated + public static RootInteraction getRootInteractionOrUnknown(@Nonnull String id) { + return getAssetMap().getAsset(getRootInteractionIdOrUnknown(id)); + } + + @Deprecated + public static int getRootInteractionIdOrUnknown(@Nullable String id) { + if (id == null) { + return Integer.MIN_VALUE; + } else { + IndexedLookupTableAssetMap assetMap = getAssetMap(); + int interactionId = assetMap.getIndex(id); + if (interactionId == Integer.MIN_VALUE) { + HytaleLogger.getLogger().at(Level.WARNING).log("Missing root interaction %s", id); + getAssetStore().loadAssets("Hytale:Hytale", List.of(new RootInteraction(id))); + int index = assetMap.getIndex(id); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + id); + } + + interactionId = index; + } + + return interactionId; + } + } + + @Nonnull + @Override + public String toString() { + return "RootInteraction{id='" + + this.id + + "', interactionIds=" + + Arrays.toString((Object[])this.interactionIds) + + ", settings=" + + this.settings + + ", requireNewClick=" + + this.requireNewClick + + ", clickQueuingTimeout=" + + this.clickQueuingTimeout + + ", rules=" + + this.rules + + ", operations=" + + Arrays.toString((Object[])this.operations) + + ", needsRemoteSync=" + + this.needsRemoteSync + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/SimpleInstantInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/SimpleInstantInteraction.java new file mode 100644 index 0000000..3711e32 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/SimpleInstantInteraction.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public abstract class SimpleInstantInteraction extends SimpleInteraction { + public static final BuilderCodec CODEC = BuilderCodec.abstractBuilder(SimpleInstantInteraction.class, SimpleInteraction.CODEC) + .build(); + + public SimpleInstantInteraction(String id) { + super(id); + } + + protected SimpleInstantInteraction() { + } + + @Override + protected final void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + if (firstRun) { + this.firstRun(type, context, cooldownHandler); + super.tick0(firstRun, time, type, context, cooldownHandler); + } + } + + @Override + protected void simulateTick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + if (firstRun) { + this.simulateFirstRun(type, context, cooldownHandler); + if (this.getWaitForDataFrom() == WaitForDataFrom.Server + && context.getServerState() != null + && context.getServerState().state == InteractionState.Failed) { + context.getState().state = InteractionState.Failed; + } + + super.tick0(firstRun, time, type, context, cooldownHandler); + } + } + + protected abstract void firstRun(@Nonnull InteractionType var1, @Nonnull InteractionContext var2, @Nonnull CooldownHandler var3); + + protected void simulateFirstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + this.firstRun(type, context, cooldownHandler); + } + + @Override + public String toString() { + return "SimpleInstantInteraction{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/SimpleInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/SimpleInteraction.java new file mode 100644 index 0000000..33a39cd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/SimpleInteraction.java @@ -0,0 +1,141 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.Collector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.StringTag; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.Label; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.OperationsBuilder; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class SimpleInteraction extends Interaction { + public static final BuilderCodec CODEC = BuilderCodec.builder(SimpleInteraction.class, SimpleInteraction::new, Interaction.ABSTRACT_CODEC) + .documentation( + "A interaction that does nothing other than base interaction features. Can be used for simple delays or triggering animations in between other interactions." + ) + .appendInherited( + new KeyedCodec<>("Next", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.next = s, + interaction -> interaction.next, + (interaction, parent) -> interaction.next = parent.next + ) + .documentation("The interactions to run when this interaction succeeds.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("Failed", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.failed = s, + interaction -> interaction.failed, + (interaction, parent) -> interaction.failed = parent.failed + ) + .documentation("The interactions to run when this interaction fails.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + private static final StringTag TAG_NEXT = StringTag.of("Next"); + private static final StringTag TAG_FAILED = StringTag.of("Failed"); + private static final int FAILED_LABEL_INDEX = 0; + @Nullable + protected String next; + @Nullable + protected String failed; + + protected SimpleInteraction() { + } + + public SimpleInteraction(String id) { + super(id); + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.None; + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + if (context.getState().state == InteractionState.Failed && context.hasLabels()) { + context.jump(context.getLabel(0)); + } + } + + @Override + protected void simulateTick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + if (this.getWaitForDataFrom() == WaitForDataFrom.Server && context.getServerState() != null && context.getServerState().state == InteractionState.Failed) { + context.getState().state = InteractionState.Failed; + } + + this.tick0(firstRun, time, type, context, cooldownHandler); + } + + @Override + public void compile(@Nonnull OperationsBuilder builder) { + if (this.next == null && this.failed == null) { + builder.addOperation(this); + } else { + Label failedLabel = builder.createUnresolvedLabel(); + Label endLabel = builder.createUnresolvedLabel(); + builder.addOperation(this, failedLabel); + if (this.next != null) { + Interaction nextInteraction = Interaction.getInteractionOrUnknown(this.next); + nextInteraction.compile(builder); + } + + if (this.failed != null) { + builder.jump(endLabel); + } + + builder.resolveLabel(failedLabel); + if (this.failed != null) { + Interaction failedInteraction = Interaction.getInteractionOrUnknown(this.failed); + failedInteraction.compile(builder); + } + + builder.resolveLabel(endLabel); + } + } + + @Override + public boolean walk(@Nonnull Collector collector, @Nonnull InteractionContext context) { + return this.next != null && InteractionManager.walkInteraction(collector, context, TAG_NEXT, this.next) + ? true + : this.failed != null && InteractionManager.walkInteraction(collector, context, TAG_FAILED, this.failed); + } + + @NonNullDecl + @Override + protected com.hypixel.hytale.protocol.Interaction generatePacket() { + return new com.hypixel.hytale.protocol.SimpleInteraction(); + } + + @Override + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.SimpleInteraction p = (com.hypixel.hytale.protocol.SimpleInteraction)packet; + p.next = Interaction.getInteractionIdOrUnknown(this.next); + p.failed = Interaction.getInteractionIdOrUnknown(this.failed); + } + + @Override + public boolean needsRemoteSync() { + return needsRemoteSync(this.next) || needsRemoteSync(this.failed); + } + + @Override + public String toString() { + return "SimpleInteraction{next='" + this.next + "'failed='" + this.failed + "'} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/AddItemInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/AddItemInteraction.java new file mode 100644 index 0000000..20d13cb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/AddItemInteraction.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AddItemInteraction extends SimpleBlockInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + AddItemInteraction.class, AddItemInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Adds an item to the users inventory.") + .append(new KeyedCodec<>("ItemId", Codec.STRING), (i, s) -> i.itemId = s, i -> i.itemId) + .documentation("The id of the item to add.") + .addValidator(Validators.nonNull()) + .addValidator(Item.VALIDATOR_CACHE.getValidator()) + .add() + .append(new KeyedCodec<>("Quantity", Codec.INTEGER), (o, v) -> o.quantity = v, o -> o.quantity) + .documentation("The amount of the item to add.") + .add() + .build(); + protected String itemId; + protected int quantity; + + public AddItemInteraction() { + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + if (this.quantity > 0 && this.itemId != null) { + Ref ref = context.getEntity(); + if (EntityUtils.getEntity(ref, commandBuffer) instanceof LivingEntity livingEntity) { + livingEntity.getInventory().getCombinedHotbarFirst().addItemStack(new ItemStack(this.itemId, this.quantity)); + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ApplyForceInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ApplyForceInteraction.java new file mode 100644 index 0000000..8e7f17c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ApplyForceInteraction.java @@ -0,0 +1,439 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.spatial.SpatialStructure; +import com.hypixel.hytale.math.range.FloatRange; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.AppliedForce; +import com.hypixel.hytale.protocol.ApplyForceState; +import com.hypixel.hytale.protocol.ChangeVelocityType; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.protocol.RaycastMode; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.Label; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.OperationsBuilder; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.modules.splitvelocity.VelocityConfig; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class ApplyForceInteraction extends SimpleInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ApplyForceInteraction.class, ApplyForceInteraction::new, SimpleInteraction.CODEC + ) + .documentation("Applies a force to the user, optionally waiting for a condition to met before continuing.") + .appendInherited(new KeyedCodec<>("Direction", Vector3d.CODEC), (o, i) -> o.forces[0].direction = i.normalize(), o -> null, (o, p) -> {}) + .documentation("The direction of the force to apply.") + .add() + .appendInherited(new KeyedCodec<>("AdjustVertical", Codec.BOOLEAN), (o, i) -> o.forces[0].adjustVertical = i, o -> null, (o, p) -> {}) + .documentation("Whether the force should be adjusted based on the vertical look of the user.") + .add() + .appendInherited(new KeyedCodec<>("Force", Codec.DOUBLE), (o, i) -> o.forces[0].force = i, o -> null, (o, p) -> {}) + .documentation("The size of the force to apply.") + .add() + .appendInherited( + new KeyedCodec<>("Forces", new ArrayCodec<>(ApplyForceInteraction.Force.CODEC, ApplyForceInteraction.Force[]::new)), + (o, i) -> o.forces = i, + o -> o.forces, + (o, p) -> o.forces = p.forces + ) + .documentation("A collection of forces to apply to the user.\nReplaces `Direction`, `AdjustVertical` and `Force` if used.") + .add() + .appendInherited(new KeyedCodec<>("Duration", Codec.FLOAT), (o, f) -> o.duration = f, o -> o.duration, (o, p) -> o.duration = p.duration) + .addValidator(Validators.greaterThanOrEqual(0.0F)) + .documentation("The duration for which force should be continuously applied. If 0, force is applied on first run.") + .add() + .appendInherited( + new KeyedCodec<>("VerticalClamp", FloatRange.CODEC), (o, i) -> o.verticalClamp = i, o -> o.verticalClamp, (o, p) -> o.verticalClamp = p.verticalClamp + ) + .documentation("The angles in degrees to clamp the look angle to when adjusting the force") + .add() + .appendInherited( + new KeyedCodec<>("WaitForGround", Codec.BOOLEAN), (o, i) -> o.waitForGround = i, o -> o.waitForGround, (o, p) -> o.waitForGround = p.waitForGround + ) + .documentation("Determines whether or not on ground should be checked") + .add() + .appendInherited( + new KeyedCodec<>("WaitForCollision", Codec.BOOLEAN), + (o, i) -> o.waitForCollision = i, + o -> o.waitForCollision, + (o, p) -> o.waitForCollision = p.waitForCollision + ) + .documentation("Determines whether or not collision should be checked") + .add() + .appendInherited( + new KeyedCodec<>("RaycastDistance", Codec.FLOAT), + (o, i) -> o.raycastDistance = i, + o -> o.raycastDistance, + (o, p) -> o.raycastDistance = p.raycastDistance + ) + .documentation("The distance of the raycast for the collision check") + .add() + .appendInherited( + new KeyedCodec<>("RaycastHeightOffset", Codec.FLOAT), + (o, i) -> o.raycastHeightOffset = i, + o -> o.raycastHeightOffset, + (o, p) -> o.raycastHeightOffset = p.raycastHeightOffset + ) + .documentation("The height offset of the raycast for the collision check (default 0)") + .add() + .appendInherited( + new KeyedCodec<>("RaycastMode", new EnumCodec<>(RaycastMode.class)), + (o, i) -> o.raycastMode = i, + o -> o.raycastMode, + (o, p) -> o.raycastMode = p.raycastMode + ) + .documentation("The type of raycast performed for the collision check") + .add() + .appendInherited( + new KeyedCodec<>("GroundCheckDelay", Codec.FLOAT), + (o, i) -> o.groundCheckDelay = i, + o -> o.groundCheckDelay, + (o, p) -> o.groundCheckDelay = p.groundCheckDelay + ) + .documentation("The delay in seconds before checking if on ground") + .add() + .appendInherited( + new KeyedCodec<>("CollisionCheckDelay", Codec.FLOAT), + (o, i) -> o.collisionCheckDelay = i, + o -> o.collisionCheckDelay, + (o, p) -> o.collisionCheckDelay = p.collisionCheckDelay + ) + .documentation("The delay in seconds before checking entity collision") + .add() + .appendInherited( + new KeyedCodec<>("ChangeVelocityType", ProtocolCodecs.CHANGE_VELOCITY_TYPE_CODEC), + (o, i) -> o.changeVelocityType = i, + o -> o.changeVelocityType, + (o, p) -> o.changeVelocityType = p.changeVelocityType + ) + .documentation("Configures how the velocity gets applied to the user.") + .add() + .appendInherited( + new KeyedCodec<>("VelocityConfig", VelocityConfig.CODEC), + (o, i) -> o.velocityConfig = i, + o -> o.velocityConfig, + (o, p) -> o.velocityConfig = p.velocityConfig + ) + .documentation("Specific configuration options that control how the velocity is affected.") + .add() + .appendInherited( + new KeyedCodec<>("GroundNext", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.groundInteraction = s, + interaction -> interaction.groundInteraction, + (interaction, parent) -> interaction.groundInteraction = parent.groundInteraction + ) + .documentation("The interaction to run if on-ground is apparent.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("CollisionNext", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.collisionInteraction = s, + interaction -> interaction.collisionInteraction, + (interaction, parent) -> interaction.collisionInteraction = parent.collisionInteraction + ) + .documentation("The interaction to run if collision is apparent.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + private static final int LABEL_COUNT = 3; + private static final int NEXT_LABEL_INDEX = 0; + private static final int GROUND_LABEL_INDEX = 1; + private static final int COLLISION_LABEL_INDEX = 2; + private static final float SPATIAL_STRUCTURE_RADIUS = 1.5F; + @Nonnull + private ChangeVelocityType changeVelocityType = ChangeVelocityType.Set; + @Nonnull + private ApplyForceInteraction.Force[] forces = new ApplyForceInteraction.Force[]{new ApplyForceInteraction.Force()}; + private float duration = 0.0F; + private boolean waitForGround = true; + private boolean waitForCollision = false; + private float groundCheckDelay = 0.1F; + private float collisionCheckDelay = 0.0F; + private float raycastDistance = 1.5F; + private float raycastHeightOffset = 0.0F; + @Nonnull + private RaycastMode raycastMode = RaycastMode.FollowMotion; + @Nullable + private VelocityConfig velocityConfig = null; + @Nullable + private FloatRange verticalClamp = null; + @Nullable + private String groundInteraction = null; + @Nullable + private String collisionInteraction = null; + + public ApplyForceInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Client; + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + InteractionSyncData contextState = context.getState(); + if (firstRun) { + contextState.state = InteractionState.NotFinished; + } else { + InteractionSyncData clientState = context.getClientState(); + + assert clientState != null; + + ApplyForceState applyForceState = clientState.applyForceState; + switch (applyForceState) { + case Ground: + contextState.state = InteractionState.Finished; + context.jump(context.getLabel(1)); + break; + case Collision: + contextState.state = InteractionState.Finished; + context.jump(context.getLabel(2)); + break; + case Timer: + contextState.state = InteractionState.Finished; + context.jump(context.getLabel(0)); + break; + default: + contextState.state = InteractionState.NotFinished; + } + + super.tick0(firstRun, time, type, context, cooldownHandler); + } + } + + @Override + protected void simulateTick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + InteractionSyncData contextState = context.getState(); + Ref entityRef = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Store entityStore = commandBuffer.getStore(); + if (!firstRun && (!(this.duration > 0.0F) || !(time < this.duration))) { + MovementStatesComponent movementStatesComponent = entityStore.getComponent(entityRef, MovementStatesComponent.getComponentType()); + + assert movementStatesComponent != null; + + TransformComponent transformComponent = entityStore.getComponent(entityRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + MovementStates entityMovementStates = movementStatesComponent.getMovementStates(); + SpatialResource, EntityStore> networkSendableSpatialComponent = entityStore.getResource( + EntityModule.get().getNetworkSendableSpatialResourceType() + ); + SpatialStructure> spatialStructure = networkSendableSpatialComponent.getSpatialStructure(); + ObjectList> entities = SpatialResource.getThreadLocalReferenceList(); + spatialStructure.collect(transformComponent.getPosition(), 1.5, entities); + boolean checkGround = time >= this.groundCheckDelay; + boolean onGround = checkGround + && this.waitForGround + && (entityMovementStates.onGround || entityMovementStates.inFluid || entityMovementStates.climbing); + boolean checkCollision = time >= this.collisionCheckDelay; + boolean collided = checkCollision && this.waitForCollision && entities.size() > 1; + boolean instantlyComplete = this.runTime <= 0.0F && !this.waitForGround && !this.waitForCollision; + boolean timerFinished = instantlyComplete || this.runTime > 0.0F && time >= this.runTime; + contextState.applyForceState = ApplyForceState.Waiting; + if (onGround) { + contextState.applyForceState = ApplyForceState.Ground; + contextState.state = InteractionState.Finished; + context.jump(context.getLabel(1)); + } else if (collided) { + contextState.applyForceState = ApplyForceState.Collision; + contextState.state = InteractionState.Finished; + context.jump(context.getLabel(2)); + } else if (timerFinished) { + contextState.applyForceState = ApplyForceState.Timer; + contextState.state = InteractionState.Finished; + context.jump(context.getLabel(0)); + } else { + contextState.state = InteractionState.NotFinished; + } + + super.simulateTick0(firstRun, time, type, context, cooldownHandler); + } else { + HeadRotation headRotationComponent = entityStore.getComponent(entityRef, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Velocity velocityComponent = entityStore.getComponent(entityRef, Velocity.getComponentType()); + + assert velocityComponent != null; + + Vector3f entityHeadRotation = headRotationComponent.getRotation(); + ChangeVelocityType velocityType = this.changeVelocityType; + + for (ApplyForceInteraction.Force force : this.forces) { + Vector3d forceDirection = force.direction.clone(); + if (force.adjustVertical) { + float lookX = entityHeadRotation.x; + if (this.verticalClamp != null) { + lookX = MathUtil.clamp( + lookX, this.verticalClamp.getInclusiveMin() * (float) (Math.PI / 180.0), this.verticalClamp.getInclusiveMax() * (float) (Math.PI / 180.0) + ); + } + + forceDirection.rotateX(lookX); + } + + forceDirection.scale(force.force); + forceDirection.rotateY(entityHeadRotation.y); + switch (velocityType) { + case Add: + velocityComponent.addInstruction(forceDirection, null, ChangeVelocityType.Add); + break; + case Set: + velocityComponent.addInstruction(forceDirection, null, ChangeVelocityType.Set); + } + + velocityType = ChangeVelocityType.Add; + } + + contextState.state = InteractionState.NotFinished; + } + } + + @Override + public void compile(@Nonnull OperationsBuilder builder) { + Label[] labels = new Label[3]; + + for (int i = 0; i < labels.length; i++) { + labels[i] = builder.createUnresolvedLabel(); + } + + builder.addOperation(this, labels); + Label endLabel = builder.createUnresolvedLabel(); + resolve(builder, this.next, labels[0], endLabel); + resolve(builder, this.groundInteraction == null ? this.next : this.groundInteraction, labels[1], endLabel); + resolve(builder, this.collisionInteraction == null ? this.next : this.collisionInteraction, labels[2], endLabel); + builder.resolveLabel(endLabel); + } + + private static void resolve(@Nonnull OperationsBuilder builder, @Nullable String id, @Nonnull Label label, @Nonnull Label endLabel) { + builder.resolveLabel(label); + if (id != null) { + Interaction interaction = Interaction.getInteractionOrUnknown(id); + interaction.compile(builder); + } + + builder.jump(endLabel); + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.Interaction generatePacket() { + return new com.hypixel.hytale.protocol.ApplyForceInteraction(); + } + + @Override + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.ApplyForceInteraction p = (com.hypixel.hytale.protocol.ApplyForceInteraction)packet; + p.changeVelocityType = this.changeVelocityType; + p.forces = Arrays.stream(this.forces).map(ApplyForceInteraction.Force::toPacket).toArray(AppliedForce[]::new); + p.duration = this.duration; + p.waitForGround = this.waitForGround; + p.waitForCollision = this.waitForCollision; + p.groundCheckDelay = this.groundCheckDelay; + p.collisionCheckDelay = this.collisionCheckDelay; + p.velocityConfig = this.velocityConfig == null ? null : this.velocityConfig.toPacket(); + if (this.verticalClamp != null) { + p.verticalClamp = new com.hypixel.hytale.protocol.FloatRange( + this.verticalClamp.getInclusiveMin() * (float) (Math.PI / 180.0), this.verticalClamp.getInclusiveMax() * (float) (Math.PI / 180.0) + ); + } + + p.collisionNext = Interaction.getInteractionIdOrUnknown(this.collisionInteraction == null ? this.next : this.collisionInteraction); + p.groundNext = Interaction.getInteractionIdOrUnknown(this.groundInteraction == null ? this.next : this.groundInteraction); + p.raycastDistance = this.raycastDistance; + p.raycastHeightOffset = this.raycastHeightOffset; + p.raycastMode = this.raycastMode; + } + + @Nonnull + @Override + public String toString() { + return "ApplyForceInteraction{changeVelocityType=" + this.changeVelocityType + ", waitForGround=" + this.waitForGround + "} " + super.toString(); + } + + public static class Force implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ApplyForceInteraction.Force.class, ApplyForceInteraction.Force::new + ) + .appendInherited(new KeyedCodec<>("Direction", Vector3d.CODEC), (o, i) -> o.direction = i, o -> o.direction, (o, p) -> o.direction = p.direction) + .documentation("The direction of the force to apply.") + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("AdjustVertical", Codec.BOOLEAN), + (o, i) -> o.adjustVertical = i, + o -> o.adjustVertical, + (o, p) -> o.adjustVertical = p.adjustVertical + ) + .documentation("Whether the force should be adjusted based on the vertical look of the user.") + .add() + .appendInherited(new KeyedCodec<>("Force", Codec.DOUBLE), (o, i) -> o.force = i, o -> o.force, (o, p) -> o.force = p.force) + .documentation("The size of the force to apply.") + .add() + .afterDecode(o -> o.direction.normalize()) + .build(); + @Nonnull + private Vector3d direction = Vector3d.UP; + private boolean adjustVertical = false; + private double force = 1.0; + + public Force() { + } + + @Nonnull + public AppliedForce toPacket() { + return new AppliedForce( + new com.hypixel.hytale.protocol.Vector3f((float)this.direction.x, (float)this.direction.y, (float)this.direction.z), + this.adjustVertical, + (float)this.force + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/BlockConditionInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/BlockConditionInteraction.java new file mode 100644 index 0000000..b0805ea --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/BlockConditionInteraction.java @@ -0,0 +1,306 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockFace; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockConditionInteraction extends SimpleBlockInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + BlockConditionInteraction.class, BlockConditionInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Tests the target block and executes `Next` if it matches all the conditions, otherwise `Failed` is run.") + .appendInherited( + new KeyedCodec<>("Matchers", new ArrayCodec<>(BlockConditionInteraction.BlockMatcher.CODEC, BlockConditionInteraction.BlockMatcher[]::new)), + (o, i) -> o.matchers = i, + o -> o.matchers, + (o, p) -> o.matchers = p.matchers + ) + .documentation("The matchers to test the block against.") + .add() + .build(); + private BlockConditionInteraction.BlockMatcher[] matchers; + + public BlockConditionInteraction() { + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + BlockFace face = context.getClientState().blockFace; + this.doInteraction(context, world, targetBlock, face); + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + context.getState().blockFace = BlockFace.Up; + this.doInteraction(context, world, targetBlock, context.getState().blockFace); + } + + private void doInteraction(@Nonnull InteractionContext context, @Nonnull World world, @Nonnull Vector3i targetBlock, @Nonnull BlockFace face) { + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)); + if (chunk != null) { + BlockType blockType = chunk.getBlockType(targetBlock); + RotationTuple blockRotation = chunk.getRotation(targetBlock.x, targetBlock.y, targetBlock.z); + Item itemType = blockType.getItem(); + if (itemType == null) { + context.getState().state = InteractionState.Failed; + } else { + boolean ok = false; + BlockConditionInteraction.BlockMatcher[] var10 = this.matchers; + int var11 = var10.length; + int var12 = 0; + + while (var12 < var11) { + label69: { + label77: { + BlockConditionInteraction.BlockMatcher matcher = var10[var12]; + if (matcher.face != BlockFace.None) { + BlockFace transformedFace = matcher.face; + if (!matcher.staticFace) { + Rotation yaw = blockRotation.yaw(); + Rotation pitch = blockRotation.pitch(); + com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFace newFace = com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFace.rotate( + com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFace.fromProtocolFace(transformedFace), yaw, pitch + ); + transformedFace = com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFace.toProtocolFace(newFace); + } + + if (!transformedFace.equals(face)) { + break label77; + } + } + + if (matcher.block == null) { + break label69; + } + + label61: + if (matcher.block.id == null || matcher.block.id.equals(itemType.getId())) { + if (matcher.block.state != null) { + String state = blockType.getStateForBlock(blockType); + if (state == null) { + state = "default"; + } + + if (!matcher.block.state.equals(state)) { + break label61; + } + } + + if (matcher.block.tag == null) { + break label69; + } + + AssetExtraInfo.Data data = blockType.getData(); + if (data != null) { + Int2ObjectMap tags = data.getTags(); + if (tags != null && tags.containsKey(matcher.block.tagIndex)) { + break label69; + } + } + } + } + + var12++; + continue; + } + + ok = true; + break; + } + + if (ok) { + context.getState().state = InteractionState.Finished; + } else { + context.getState().state = InteractionState.Failed; + } + } + } + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.BlockConditionInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.BlockConditionInteraction p = (com.hypixel.hytale.protocol.BlockConditionInteraction)packet; + if (this.matchers != null) { + p.matchers = new com.hypixel.hytale.protocol.BlockMatcher[this.matchers.length]; + + for (int i = 0; i < this.matchers.length; i++) { + p.matchers[i] = this.matchers[i].toPacket(); + } + } + } + + @Nonnull + @Override + public String toString() { + return "BlockConditionInteraction{matchers=" + Arrays.toString((Object[])this.matchers) + "} " + super.toString(); + } + + public static class BlockIdMatcher implements NetworkSerializable { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder( + BlockConditionInteraction.BlockIdMatcher.class, BlockConditionInteraction.BlockIdMatcher::new + ) + .appendInherited( + new KeyedCodec<>("Id", Codec.STRING), + (blockIdMatcher, s) -> blockIdMatcher.id = s, + blockIdMatcher -> blockIdMatcher.id, + (blockIdMatcher, parent) -> blockIdMatcher.id = parent.id + ) + .addValidatorLate(() -> BlockType.VALIDATOR_CACHE.getValidator().late()) + .documentation("Match against a specific block id.") + .add() + .appendInherited( + new KeyedCodec<>("State", Codec.STRING), + (blockIdMatcher, s) -> blockIdMatcher.state = s, + blockIdMatcher -> blockIdMatcher.state, + (blockIdMatcher, parent) -> blockIdMatcher.state = parent.state + ) + .documentation("Match against specific block state.") + .add() + .appendInherited( + new KeyedCodec<>("Tag", Codec.STRING), + (blockIdMatcher, s) -> blockIdMatcher.tag = s, + blockIdMatcher -> blockIdMatcher.tag, + (blockIdMatcher, parent) -> blockIdMatcher.tag = parent.tag + ) + .documentation("Match against specific block tag.") + .add() + .afterDecode(blockIdMatcher -> { + if (blockIdMatcher.tag != null) { + blockIdMatcher.tagIndex = AssetRegistry.getOrCreateTagIndex(blockIdMatcher.tag); + } + }) + .build(); + protected String id; + protected String state; + protected String tag; + protected int tagIndex = Integer.MIN_VALUE; + + public BlockIdMatcher() { + } + + @Nonnull + public com.hypixel.hytale.protocol.BlockIdMatcher toPacket() { + com.hypixel.hytale.protocol.BlockIdMatcher packet = new com.hypixel.hytale.protocol.BlockIdMatcher(); + if (this.id != null) { + packet.id = this.id; + } + + if (this.state != null) { + packet.state = this.state; + } + + packet.tagIndex = this.tagIndex; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "BlockIdMatcher{id='" + this.id + "', state='" + this.state + "', tag='" + this.tag + "'}"; + } + } + + public static class BlockMatcher implements NetworkSerializable { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder( + BlockConditionInteraction.BlockMatcher.class, BlockConditionInteraction.BlockMatcher::new + ) + .appendInherited( + new KeyedCodec<>("Block", BlockConditionInteraction.BlockIdMatcher.CODEC), + (blockMatcher, blockIdMatcher) -> blockMatcher.block = blockIdMatcher, + blockMatcher -> blockMatcher.block, + (blockMatcher, parent) -> blockMatcher.block = parent.block + ) + .documentation("Match against block values") + .add() + .appendInherited( + new KeyedCodec<>("Face", com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFace.CODEC), + (blockMatcher, face) -> blockMatcher.face = com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFace.toProtocolFace(face), + blockMatcher -> com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFace.fromProtocolFace(blockMatcher.face), + (blockMatcher, parent) -> blockMatcher.face = parent.face + ) + .documentation("Match against a specific block face.") + .add() + .appendInherited( + new KeyedCodec<>("StaticFace", Codec.BOOLEAN), + (blockMatcher, aBoolean) -> blockMatcher.staticFace = aBoolean, + blockMatcher -> blockMatcher.staticFace, + (blockMatcher, parent) -> blockMatcher.staticFace = parent.staticFace + ) + .documentation("Whether the face matching is unaffected by the block rotation or not.") + .add() + .build(); + protected BlockConditionInteraction.BlockIdMatcher block; + protected BlockFace face = BlockFace.None; + protected boolean staticFace; + + public BlockMatcher() { + } + + @Nonnull + public com.hypixel.hytale.protocol.BlockMatcher toPacket() { + com.hypixel.hytale.protocol.BlockMatcher packet = new com.hypixel.hytale.protocol.BlockMatcher(); + if (this.block != null) { + packet.block = this.block.toPacket(); + } + + if (this.face != null) { + packet.face = this.face; + } + + packet.staticFace = this.staticFace; + return packet; + } + + @Nonnull + @Override + public String toString() { + return "BlockMatcher{block=" + this.block + ", face=" + this.face + ", staticFace=" + this.staticFace + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/BreakBlockInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/BreakBlockInteraction.java new file mode 100644 index 0000000..cd717a8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/BreakBlockInteraction.java @@ -0,0 +1,302 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BreakBlockInteraction extends SimpleBlockInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + BreakBlockInteraction.class, BreakBlockInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Attempts to break the target block.") + .appendInherited( + new KeyedCodec<>("Harvest", Codec.BOOLEAN), + (interaction, v) -> interaction.harvest = v, + interaction -> interaction.harvest, + (o, p) -> o.harvest = p.harvest + ) + .documentation("Whether this should trigger as a harvest gather vs a break gather.") + .add() + .appendInherited( + new KeyedCodec<>("Tool", Codec.STRING), (interaction, v) -> interaction.toolId = v, interaction -> interaction.toolId, (o, p) -> o.toolId = p.toolId + ) + .documentation("Tool to break as.") + .add() + .appendInherited( + new KeyedCodec<>("MatchTool", Codec.BOOLEAN), + (interaction, v) -> interaction.matchTool = v, + interaction -> interaction.matchTool, + (o, p) -> o.matchTool = p.matchTool + ) + .documentation("Whether to require an match to `Tool` to work.") + .add() + .build(); + protected boolean harvest; + @Nullable + protected String toolId; + protected boolean matchTool; + + public BreakBlockInteraction() { + } + + @Override + protected void tick0( + boolean firstRun, float time, @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler + ) { + super.tick0(firstRun, time, type, context, cooldownHandler); + this.computeCurrentBlockSyncData(context); + } + + @Override + protected void interactWithBlock( + @Nonnull World param1, + @Nonnull CommandBuffer param2, + @Nonnull InteractionType param3, + @Nonnull InteractionContext param4, + @Nullable ItemStack param5, + @Nonnull Vector3i param6, + @Nonnull CooldownHandler param7 + ) { + // $VF: Couldn't be decompiled + // Please report this to the Vineflower issue tracker, at https://github.com/Vineflower/vineflower/issues with a copy of the class file (if you have the rights to distribute it!) + // java.lang.IllegalStateException: Invalid switch case set: [[const(0)], [const(1)], [const(null), null]] for selector of type Lcom/hypixel/hytale/protocol/GameMode; + // at org.jetbrains.java.decompiler.modules.decompiler.exps.SwitchHeadExprent.checkExprTypeBounds(SwitchHeadExprent.java:66) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor.checkTypeExpr(VarTypeProcessor.java:140) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor.checkTypeExprent(VarTypeProcessor.java:126) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor.lambda$processVarTypes$2(VarTypeProcessor.java:114) + // at org.jetbrains.java.decompiler.modules.decompiler.flow.DirectGraph.iterateExprents(DirectGraph.java:107) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor.processVarTypes(VarTypeProcessor.java:114) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor.calculateVarTypes(VarTypeProcessor.java:44) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionsProcessor.setVarVersions(VarVersionsProcessor.java:68) + // at org.jetbrains.java.decompiler.modules.decompiler.vars.VarProcessor.setVarVersions(VarProcessor.java:47) + // at org.jetbrains.java.decompiler.main.rels.MethodProcessor.codeToJava(MethodProcessor.java:302) + // + // Bytecode: + // 000: aload 4 + // 002: invokevirtual com/hypixel/hytale/server/core/entity/InteractionContext.getEntity ()Lcom/hypixel/hytale/component/Ref; + // 005: astore 8 + // 007: aload 2 + // 008: aload 8 + // 00a: invokestatic com/hypixel/hytale/server/core/entity/entities/Player.getComponentType ()Lcom/hypixel/hytale/component/ComponentType; + // 00d: invokevirtual com/hypixel/hytale/component/CommandBuffer.getComponent (Lcom/hypixel/hytale/component/Ref;Lcom/hypixel/hytale/component/ComponentType;)Lcom/hypixel/hytale/component/Component; + // 010: checkcast com/hypixel/hytale/server/core/entity/entities/Player + // 013: astore 9 + // 015: aload 9 + // 017: ifnonnull 039 + // 01a: invokestatic com/hypixel/hytale/logger/HytaleLogger.getLogger ()Lcom/hypixel/hytale/logger/HytaleLogger; + // 01d: getstatic java/util/logging/Level.INFO Ljava/util/logging/Level; + // 020: invokevirtual com/hypixel/hytale/logger/HytaleLogger.at (Ljava/util/logging/Level;)Lcom/hypixel/hytale/logger/HytaleLogger$Api; + // 023: bipush 5 + // 024: getstatic java/util/concurrent/TimeUnit.MINUTES Ljava/util/concurrent/TimeUnit; + // 027: invokeinterface com/hypixel/hytale/logger/HytaleLogger$Api.atMostEvery (ILjava/util/concurrent/TimeUnit;)Lcom/google/common/flogger/LoggingApi; 3 + // 02c: checkcast com/hypixel/hytale/logger/HytaleLogger$Api + // 02f: ldc "BreakBlockInteraction requires a Player but was used for: %s" + // 031: aload 8 + // 033: invokeinterface com/hypixel/hytale/logger/HytaleLogger$Api.log (Ljava/lang/String;Ljava/lang/Object;)V 3 + // 038: return + // 039: aload 1 + // 03a: invokevirtual com/hypixel/hytale/server/core/universe/world/World.getChunkStore ()Lcom/hypixel/hytale/server/core/universe/world/storage/ChunkStore; + // 03d: astore 10 + // 03f: aload 10 + // 041: invokevirtual com/hypixel/hytale/server/core/universe/world/storage/ChunkStore.getStore ()Lcom/hypixel/hytale/component/Store; + // 044: astore 11 + // 046: aload 6 + // 048: getfield com/hypixel/hytale/math/vector/Vector3i.x I + // 04b: aload 6 + // 04d: getfield com/hypixel/hytale/math/vector/Vector3i.z I + // 050: invokestatic com/hypixel/hytale/math/util/ChunkUtil.indexChunkFromBlock (II)J + // 053: lstore 12 + // 055: aload 10 + // 057: lload 12 + // 059: invokevirtual com/hypixel/hytale/server/core/universe/world/storage/ChunkStore.getChunkReference (J)Lcom/hypixel/hytale/component/Ref; + // 05c: astore 14 + // 05e: aload 14 + // 060: ifnull 06b + // 063: aload 14 + // 065: invokevirtual com/hypixel/hytale/component/Ref.isValid ()Z + // 068: ifne 06c + // 06b: return + // 06c: aload 11 + // 06e: aload 14 + // 070: invokestatic com/hypixel/hytale/server/core/universe/world/chunk/WorldChunk.getComponentType ()Lcom/hypixel/hytale/component/ComponentType; + // 073: invokevirtual com/hypixel/hytale/component/Store.getComponent (Lcom/hypixel/hytale/component/Ref;Lcom/hypixel/hytale/component/ComponentType;)Lcom/hypixel/hytale/component/Component; + // 076: checkcast com/hypixel/hytale/server/core/universe/world/chunk/WorldChunk + // 079: astore 15 + // 07b: getstatic com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/BreakBlockInteraction.$assertionsDisabled Z + // 07e: ifne 08e + // 081: aload 15 + // 083: ifnonnull 08e + // 086: new java/lang/AssertionError + // 089: dup + // 08a: invokespecial java/lang/AssertionError. ()V + // 08d: athrow + // 08e: aload 11 + // 090: aload 14 + // 092: invokestatic com/hypixel/hytale/server/core/universe/world/chunk/BlockChunk.getComponentType ()Lcom/hypixel/hytale/component/ComponentType; + // 095: invokevirtual com/hypixel/hytale/component/Store.getComponent (Lcom/hypixel/hytale/component/Ref;Lcom/hypixel/hytale/component/ComponentType;)Lcom/hypixel/hytale/component/Component; + // 098: checkcast com/hypixel/hytale/server/core/universe/world/chunk/BlockChunk + // 09b: astore 16 + // 09d: getstatic com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/BreakBlockInteraction.$assertionsDisabled Z + // 0a0: ifne 0b0 + // 0a3: aload 16 + // 0a5: ifnonnull 0b0 + // 0a8: new java/lang/AssertionError + // 0ab: dup + // 0ac: invokespecial java/lang/AssertionError. ()V + // 0af: athrow + // 0b0: aload 16 + // 0b2: aload 6 + // 0b4: invokevirtual com/hypixel/hytale/math/vector/Vector3i.getY ()I + // 0b7: invokevirtual com/hypixel/hytale/server/core/universe/world/chunk/BlockChunk.getSectionAtBlockY (I)Lcom/hypixel/hytale/server/core/universe/world/chunk/section/BlockSection; + // 0ba: astore 17 + // 0bc: aload 1 + // 0bd: invokevirtual com/hypixel/hytale/server/core/universe/world/World.getGameplayConfig ()Lcom/hypixel/hytale/server/core/asset/type/gameplay/GameplayConfig; + // 0c0: astore 18 + // 0c2: aload 18 + // 0c4: invokevirtual com/hypixel/hytale/server/core/asset/type/gameplay/GameplayConfig.getWorldConfig ()Lcom/hypixel/hytale/server/core/asset/type/gameplay/WorldConfig; + // 0c7: astore 19 + // 0c9: aload 0 + // 0ca: getfield com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/BreakBlockInteraction.harvest Z + // 0cd: ifeq 14b + // 0d0: aload 6 + // 0d2: invokevirtual com/hypixel/hytale/math/vector/Vector3i.getX ()I + // 0d5: istore 20 + // 0d7: aload 6 + // 0d9: invokevirtual com/hypixel/hytale/math/vector/Vector3i.getY ()I + // 0dc: istore 21 + // 0de: aload 6 + // 0e0: invokevirtual com/hypixel/hytale/math/vector/Vector3i.getZ ()I + // 0e3: istore 22 + // 0e5: aload 15 + // 0e7: iload 20 + // 0e9: iload 21 + // 0eb: iload 22 + // 0ed: invokevirtual com/hypixel/hytale/server/core/universe/world/chunk/WorldChunk.getBlockType (III)Lcom/hypixel/hytale/server/core/asset/type/blocktype/config/BlockType; + // 0f0: astore 23 + // 0f2: aload 23 + // 0f4: ifnonnull 103 + // 0f7: aload 4 + // 0f9: invokevirtual com/hypixel/hytale/server/core/entity/InteractionContext.getState ()Lcom/hypixel/hytale/protocol/InteractionSyncData; + // 0fc: getstatic com/hypixel/hytale/protocol/InteractionState.Failed Lcom/hypixel/hytale/protocol/InteractionState; + // 0ff: putfield com/hypixel/hytale/protocol/InteractionSyncData.state Lcom/hypixel/hytale/protocol/InteractionState; + // 102: return + // 103: aload 19 + // 105: invokevirtual com/hypixel/hytale/server/core/asset/type/gameplay/WorldConfig.isBlockGatheringAllowed ()Z + // 108: ifne 117 + // 10b: aload 4 + // 10d: invokevirtual com/hypixel/hytale/server/core/entity/InteractionContext.getState ()Lcom/hypixel/hytale/protocol/InteractionSyncData; + // 110: getstatic com/hypixel/hytale/protocol/InteractionState.Failed Lcom/hypixel/hytale/protocol/InteractionState; + // 113: putfield com/hypixel/hytale/protocol/InteractionSyncData.state Lcom/hypixel/hytale/protocol/InteractionState; + // 116: return + // 117: aload 23 + // 119: invokestatic com/hypixel/hytale/server/core/modules/interaction/BlockHarvestUtils.shouldPickupByInteraction (Lcom/hypixel/hytale/server/core/asset/type/blocktype/config/BlockType;)Z + // 11c: ifne 12b + // 11f: aload 4 + // 121: invokevirtual com/hypixel/hytale/server/core/entity/InteractionContext.getState ()Lcom/hypixel/hytale/protocol/InteractionSyncData; + // 124: getstatic com/hypixel/hytale/protocol/InteractionState.Failed Lcom/hypixel/hytale/protocol/InteractionState; + // 127: putfield com/hypixel/hytale/protocol/InteractionSyncData.state Lcom/hypixel/hytale/protocol/InteractionState; + // 12a: return + // 12b: aload 17 + // 12d: iload 20 + // 12f: iload 21 + // 131: iload 22 + // 133: invokevirtual com/hypixel/hytale/server/core/universe/world/chunk/section/BlockSection.getFiller (III)I + // 136: istore 24 + // 138: aload 8 + // 13a: aload 6 + // 13c: aload 23 + // 13e: iload 24 + // 140: aload 14 + // 142: aload 2 + // 143: aload 11 + // 145: invokestatic com/hypixel/hytale/server/core/modules/interaction/BlockHarvestUtils.performPickupByInteraction (Lcom/hypixel/hytale/component/Ref;Lcom/hypixel/hytale/math/vector/Vector3i;Lcom/hypixel/hytale/server/core/asset/type/blocktype/config/BlockType;ILcom/hypixel/hytale/component/Ref;Lcom/hypixel/hytale/component/ComponentAccessor;Lcom/hypixel/hytale/component/ComponentAccessor;)V + // 148: goto 1ca + // 14b: aload 19 + // 14d: invokevirtual com/hypixel/hytale/server/core/asset/type/gameplay/WorldConfig.isBlockBreakingAllowed ()Z + // 150: istore 20 + // 152: iload 20 + // 154: ifne 163 + // 157: aload 4 + // 159: invokevirtual com/hypixel/hytale/server/core/entity/InteractionContext.getState ()Lcom/hypixel/hytale/protocol/InteractionSyncData; + // 15c: getstatic com/hypixel/hytale/protocol/InteractionState.Failed Lcom/hypixel/hytale/protocol/InteractionState; + // 15f: putfield com/hypixel/hytale/protocol/InteractionSyncData.state Lcom/hypixel/hytale/protocol/InteractionState; + // 162: return + // 163: aload 9 + // 165: invokevirtual com/hypixel/hytale/server/core/entity/entities/Player.getGameMode ()Lcom/hypixel/hytale/protocol/GameMode; + // 168: astore 21 + // 16a: bipush 0 + // 16b: istore 22 + // 16d: aload 21 + // 16f: iload 22 + // 171: invokedynamic typeSwitch (Ljava/lang/Object;I)I bsm=java/lang/runtime/SwitchBootstraps.typeSwitch (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; args=[ null.invoke Ljava/lang/Enum$EnumDesc;, null.invoke Ljava/lang/Enum$EnumDesc; ] + // 176: tableswitch 74 -1 1 74 26 57 + // 190: aload 9 + // 192: aload 8 + // 194: aload 6 + // 196: aload 5 + // 198: aconst_null + // 199: aload 0 + // 19a: getfield com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/BreakBlockInteraction.toolId Ljava/lang/String; + // 19d: aload 0 + // 19e: getfield com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/BreakBlockInteraction.matchTool Z + // 1a1: fconst_1 + // 1a2: bipush 0 + // 1a3: aload 14 + // 1a5: aload 2 + // 1a6: aload 11 + // 1a8: invokestatic com/hypixel/hytale/server/core/modules/interaction/BlockHarvestUtils.performBlockDamage (Lcom/hypixel/hytale/server/core/entity/LivingEntity;Lcom/hypixel/hytale/component/Ref;Lcom/hypixel/hytale/math/vector/Vector3i;Lcom/hypixel/hytale/server/core/inventory/ItemStack;Lcom/hypixel/hytale/server/core/asset/type/item/config/ItemTool;Ljava/lang/String;ZFILcom/hypixel/hytale/component/Ref;Lcom/hypixel/hytale/component/ComponentAccessor;Lcom/hypixel/hytale/component/ComponentAccessor;)Z + // 1ab: pop + // 1ac: goto 1ca + // 1af: aload 8 + // 1b1: aload 5 + // 1b3: aload 6 + // 1b5: aload 14 + // 1b7: aload 2 + // 1b8: aload 11 + // 1ba: invokestatic com/hypixel/hytale/server/core/modules/interaction/BlockHarvestUtils.performBlockBreak (Lcom/hypixel/hytale/component/Ref;Lcom/hypixel/hytale/server/core/inventory/ItemStack;Lcom/hypixel/hytale/math/vector/Vector3i;Lcom/hypixel/hytale/component/Ref;Lcom/hypixel/hytale/component/ComponentAccessor;Lcom/hypixel/hytale/component/ComponentAccessor;)V + // 1bd: goto 1ca + // 1c0: new java/lang/UnsupportedOperationException + // 1c3: dup + // 1c4: ldc "GameMode is not supported" + // 1c6: invokespecial java/lang/UnsupportedOperationException. (Ljava/lang/String;)V + // 1c9: athrow + // 1ca: return + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.BreakBlockInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.BreakBlockInteraction p = (com.hypixel.hytale.protocol.BreakBlockInteraction)packet; + p.harvest = this.harvest; + } + + @Nonnull + @Override + public String toString() { + return "BreakBlockInteraction{harvest=" + this.harvest + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ChainingInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ChainingInteraction.java new file mode 100644 index 0000000..049a110 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ChainingInteraction.java @@ -0,0 +1,310 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.Collector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.CollectorTag; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.Label; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.OperationsBuilder; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class ChainingInteraction extends Interaction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ChainingInteraction.class, ChainingInteraction::new, Interaction.ABSTRACT_CODEC + ) + .documentation("Runs one of the entries in `Next` based on how many times this interaction was run before the `ChainingAllowance` timer was reset.") + .appendInherited( + new KeyedCodec<>("ChainingAllowance", Codec.DOUBLE), + (chainingInteraction, d) -> chainingInteraction.chainingAllowance = d.floatValue(), + chainingInteraction -> (double)chainingInteraction.chainingAllowance, + (chainingInteraction, parent) -> chainingInteraction.chainingAllowance = parent.chainingAllowance + ) + .documentation( + "Time in seconds that the user has to run this interaction again in order to hit the next chain entry.\nResets the timer each time the interaction is reached." + ) + .add() + .appendInherited( + new KeyedCodec<>("Next", new ArrayCodec<>(Interaction.CHILD_ASSET_CODEC, String[]::new)), + (interaction, s) -> interaction.next = s, + interaction -> interaction.next, + (interaction, parent) -> interaction.next = parent.next + ) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonNullArrayElements()) + .addValidatorLate(() -> Interaction.VALIDATOR_CACHE.getArrayValidator().late()) + .add() + .appendInherited(new KeyedCodec<>("ChainId", Codec.STRING), (o, i) -> o.chainId = i, o -> o.chainId, (o, p) -> o.chainId = p.chainId) + .add() + .appendInherited( + new KeyedCodec<>("Flags", new MapCodec<>(CHILD_ASSET_CODEC, HashMap::new)), (o, i) -> o.flags = i, o -> o.flags, (o, p) -> o.flags = p.flags + ) + .addValidatorLate(() -> Interaction.VALIDATOR_CACHE.getMapValueValidator().late()) + .add() + .afterDecode(o -> { + if (o.flags != null) { + String[] sortedFlagKeys = o.sortedFlagKeys = o.flags.keySet().toArray(String[]::new); + Arrays.sort((Object[])sortedFlagKeys); + o.flagIndex = new Object2IntOpenHashMap<>(); + + for (int i = 0; i < sortedFlagKeys.length; i++) { + o.flagIndex.put(sortedFlagKeys[i], i); + } + } + }) + .build(); + protected String chainId; + protected float chainingAllowance; + protected String[] next; + @Nullable + protected Map flags; + @Nullable + protected Object2IntMap flagIndex; + private String[] sortedFlagKeys; + + public ChainingInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Client; + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + InteractionSyncData clientState = context.getClientState(); + + assert clientState != null; + + InteractionSyncData state = context.getState(); + if (clientState.flagIndex != -1) { + state.state = InteractionState.Finished; + context.jump(context.getLabel(this.next.length + clientState.flagIndex)); + } else if (clientState.chainingIndex == -1) { + state.state = InteractionState.NotFinished; + } else { + state.state = InteractionState.Finished; + context.jump(context.getLabel(clientState.chainingIndex)); + } + } + + @Override + protected void simulateTick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + if (firstRun) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getEntity(); + ChainingInteraction.Data dataComponent = commandBuffer.getComponent(ref, ChainingInteraction.Data.getComponentType()); + if (dataComponent != null) { + InteractionSyncData state = context.getState(); + String id = this.chainId == null ? this.id : this.chainId; + Object2IntMap map = this.chainId == null ? dataComponent.map : dataComponent.namedMap; + int lastSequenceIndex = map.getInt(id); + if (++lastSequenceIndex >= this.next.length) { + lastSequenceIndex = 0; + } + + if (this.chainingAllowance > 0.0F && dataComponent.getTimeSinceLastAttackInSeconds() > this.chainingAllowance) { + lastSequenceIndex = 0; + } + + map.put(id, lastSequenceIndex); + state.chainingIndex = lastSequenceIndex; + state.state = InteractionState.Finished; + context.jump(context.getLabel(lastSequenceIndex)); + dataComponent.lastAttack = System.nanoTime(); + } + } + } + + @Override + public void compile(@Nonnull OperationsBuilder builder) { + int len = this.next.length + (this.sortedFlagKeys != null ? this.sortedFlagKeys.length : 0); + Label[] labels = new Label[len]; + + for (int i = 0; i < labels.length; i++) { + labels[i] = builder.createUnresolvedLabel(); + } + + builder.addOperation(this, labels); + Label end = builder.createUnresolvedLabel(); + + for (int i = 0; i < this.next.length; i++) { + builder.resolveLabel(labels[i]); + Interaction interaction = Interaction.getInteractionOrUnknown(this.next[i]); + interaction.compile(builder); + builder.jump(end); + } + + if (this.flags != null) { + for (int i = 0; i < this.sortedFlagKeys.length; i++) { + String flag = this.sortedFlagKeys[i]; + builder.resolveLabel(labels[this.next.length + i]); + Interaction interaction = Interaction.getInteractionOrUnknown(this.flags.get(flag)); + interaction.compile(builder); + builder.jump(end); + } + } + + builder.resolveLabel(end); + } + + @Override + public boolean walk(@Nonnull Collector collector, @Nonnull InteractionContext context) { + for (int i = 0; i < this.next.length; i++) { + if (InteractionManager.walkInteraction(collector, context, ChainingInteraction.ChainingTag.of(i), this.next[i])) { + return true; + } + } + + return false; + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.Interaction generatePacket() { + return new com.hypixel.hytale.protocol.ChainingInteraction(); + } + + @Override + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.ChainingInteraction p = (com.hypixel.hytale.protocol.ChainingInteraction)packet; + p.chainingAllowance = this.chainingAllowance; + int[] chainingNext = p.chainingNext = new int[this.next.length]; + + for (int i = 0; i < this.next.length; i++) { + chainingNext[i] = Interaction.getInteractionIdOrUnknown(this.next[i]); + } + + if (this.flags != null) { + p.flags = new Object2IntOpenHashMap<>(); + + for (Entry e : this.flags.entrySet()) { + p.flags.put(e.getKey(), Interaction.getInteractionIdOrUnknown(e.getValue())); + } + } + + p.chainId = this.chainId; + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Nonnull + @Override + public String toString() { + return "ChainingInteraction{chainingAllowance=" + this.chainingAllowance + ", next=" + Arrays.toString((Object[])this.next) + "} " + super.toString(); + } + + private static class ChainingTag implements CollectorTag { + private final int index; + + private ChainingTag(int index) { + this.index = index; + } + + public int getIndex() { + return this.index; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + ChainingInteraction.ChainingTag that = (ChainingInteraction.ChainingTag)o; + return this.index == that.index; + } else { + return false; + } + } + + @Override + public int hashCode() { + return this.index; + } + + @Nonnull + @Override + public String toString() { + return "ChainingTag{index=" + this.index + "}"; + } + + @Nonnull + public static ChainingInteraction.ChainingTag of(int index) { + return new ChainingInteraction.ChainingTag(index); + } + } + + public static class Data implements Component { + private final Object2IntMap map = new Object2IntOpenHashMap<>(); + private final Object2IntMap namedMap = new Object2IntOpenHashMap<>(); + private long lastAttack; + + public Data() { + } + + public static ComponentType getComponentType() { + return InteractionModule.get().getChainingDataComponent(); + } + + public float getTimeSinceLastAttackInSeconds() { + if (this.lastAttack == 0L) { + return 0.0F; + } else { + long diff = System.nanoTime() - this.lastAttack; + return (float)diff / 1.0E9F; + } + } + + @Nonnull + public Object2IntMap getNamedMap() { + return this.namedMap; + } + + @Nonnull + @Override + public Component clone() { + ChainingInteraction.Data c = new ChainingInteraction.Data(); + c.map.putAll(this.map); + c.lastAttack = this.lastAttack; + return c; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ChangeBlockInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ChangeBlockInteraction.java new file mode 100644 index 0000000..3f1675f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ChangeBlockInteraction.java @@ -0,0 +1,200 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.BlockRotation; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.validator.SoundEventValidators; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChangeBlockInteraction extends SimpleBlockInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ChangeBlockInteraction.class, ChangeBlockInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Changes the target block to another block based on the block types provided.") + .appendInherited( + new KeyedCodec<>("Changes", new MapCodec<>(Codec.STRING, HashMap::new)), + (interaction, changeMap) -> interaction.blockTypeKeys = changeMap, + interaction -> interaction.blockTypeKeys, + (o, p) -> o.blockTypeKeys = p.blockTypeKeys + ) + .documentation( + "A map of the target block to the new block.\n\nWhen the interaction runs it will look for the block that was interacted with in this map and if found it will replace it with specified block" + ) + .addValidator(BlockType.VALIDATOR_CACHE.getMapKeyValidator().late()) + .addValidator(BlockType.VALIDATOR_CACHE.getMapValueValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("WorldSoundEventId", Codec.STRING), + (interaction, s) -> interaction.soundEventId = s, + interaction -> interaction.soundEventId, + (interaction, parent) -> interaction.soundEventId = parent.soundEventId + ) + .documentation("Sound event to play at the block location on block change.") + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .add() + .appendInherited( + new KeyedCodec<>("RequireNotBroken", Codec.BOOLEAN), + (interaction, s) -> interaction.requireNotBroken = s, + interaction -> interaction.requireNotBroken, + (interaction, parent) -> interaction.requireNotBroken = parent.requireNotBroken + ) + .documentation("If true, the interaction will fail if the held item is broken (durability = 0).") + .add() + .afterDecode(ChangeBlockInteraction::processConfig) + .build(); + private static final int SET_BLOCK_SETTINGS = 256; + protected Map blockTypeKeys; + protected Int2IntMap changeMapIds; + @Nullable + protected String soundEventId = null; + protected transient int soundEventIndex = 0; + protected boolean requireNotBroken = false; + + public ChangeBlockInteraction() { + } + + protected void processConfig() { + if (this.soundEventId != null) { + this.soundEventIndex = SoundEvent.getAssetMap().getIndex(this.soundEventId); + } + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + if (this.requireNotBroken && itemInHand != null && itemInHand.isBroken()) { + context.getState().state = InteractionState.Failed; + } else { + int x = targetBlock.getX(); + int y = targetBlock.getY(); + int z = targetBlock.getZ(); + WorldChunk chunk = world.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + int current = chunk.getBlock(x, y, z); + int to = this.getChangeMapIds().get(current); + if (to != Integer.MIN_VALUE) { + BlockType toBlockType = BlockType.getAssetMap().getAsset(to); + int rotationBefore = chunk.getRotationIndex(x, y, z); + chunk.setBlock(x, y, z, to, toBlockType, rotationBefore, 0, 256); + context.getState().blockPosition = new BlockPosition(x, y, z); + context.getState().placedBlockId = to; + RotationTuple resultRotation = RotationTuple.get(rotationBefore); + context.getState().blockRotation = new BlockRotation( + resultRotation.yaw().toPacket(), resultRotation.pitch().toPacket(), resultRotation.roll().toPacket() + ); + if (this.soundEventIndex != 0) { + Ref ref = context.getEntity(); + Vector3d pos = new Vector3d(x + 0.5, y + 0.5, z + 0.5); + SoundUtil.playSoundEvent3d(ref, this.soundEventIndex, pos, true, commandBuffer); + } + } else { + context.getState().state = InteractionState.Failed; + } + } + } + + @Nonnull + private Int2IntMap getChangeMapIds() { + if (this.changeMapIds == null) { + Int2IntOpenHashMap ids = new Int2IntOpenHashMap(this.blockTypeKeys.size()); + ids.defaultReturnValue(Integer.MIN_VALUE); + this.blockTypeKeys.forEach((fromKey, toKey) -> { + int fromId = BlockType.getAssetMap().getIndex(fromKey); + int toId = BlockType.getAssetMap().getIndex(toKey); + if (fromId == Integer.MIN_VALUE) { + HytaleLogger.getLogger().at(Level.SEVERE).log("Invalid BlockType: Interaction: %s, BlockType: %s", this.id, fromKey); + } else if (toId == Integer.MIN_VALUE) { + HytaleLogger.getLogger().at(Level.SEVERE).log("Invalid BlockType: Interaction: %s, BlockType: %s", this.id, toKey); + } else { + ids.put(fromId, toId); + } + }); + this.changeMapIds = ids; + } + + return this.changeMapIds; + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + if (this.requireNotBroken && itemInHand != null && itemInHand.isBroken()) { + context.getState().state = InteractionState.Failed; + } else { + int current = world.getBlock(targetBlock); + int to = this.getChangeMapIds().get(current); + if (to == Integer.MIN_VALUE) { + context.getState().state = InteractionState.Failed; + } + } + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.ChangeBlockInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.ChangeBlockInteraction p = (com.hypixel.hytale.protocol.ChangeBlockInteraction)packet; + p.blockChanges = this.getChangeMapIds(); + p.worldSoundEventIndex = this.soundEventIndex; + p.requireNotBroken = this.requireNotBroken; + } + + @Nonnull + @Override + public String toString() { + return "ChangeBlockInteraction{blockTypeKeys=" + + this.blockTypeKeys + + ", changeMapIds=" + + this.changeMapIds + + ", soundEventId='" + + this.soundEventId + + "', soundEventIndex=" + + this.soundEventIndex + + ", requireNotBroken=" + + this.requireNotBroken + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ChangeStateInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ChangeStateInteraction.java new file mode 100644 index 0000000..103123b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ChangeStateInteraction.java @@ -0,0 +1,136 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChangeStateInteraction extends SimpleBlockInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ChangeStateInteraction.class, ChangeStateInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Changes the state of the target block to another state based on the mapping provided.") + .appendInherited( + new KeyedCodec<>("Changes", new MapCodec<>(Codec.STRING, HashMap::new)), + (interaction, changeMap) -> interaction.stateKeys = changeMap, + interaction -> interaction.stateKeys, + (o, p) -> o.stateKeys = p.stateKeys + ) + .documentation("The map of state changes to execute. `\"default\"` can be used for the initial state of a block.") + .add() + .appendInherited( + new KeyedCodec<>("UpdateBlockState", Codec.BOOLEAN), + (o, i) -> o.updateBlockState = i, + o -> o.updateBlockState, + (o, p) -> o.updateBlockState = p.updateBlockState + ) + .add() + .build(); + private static final int SET_SETTINGS = 260; + protected Map stateKeys; + protected boolean updateBlockState = false; + + public ChangeStateInteraction() { + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)); + if (chunk != null) { + BlockType current = chunk.getBlockType(targetBlock); + String currentState = current.getStateForBlock(current); + if (currentState == null) { + currentState = "default"; + } + + String newState = this.stateKeys.get(currentState); + if (newState != null) { + String newBlock = current.getBlockKeyForState(newState); + if (newBlock != null) { + int newBlockId = BlockType.getAssetMap().getIndex(newBlock); + if (newBlockId == Integer.MIN_VALUE) { + context.getState().state = InteractionState.Failed; + return; + } + + BlockType newBlockType = BlockType.getAssetMap().getAsset(newBlockId); + int rotation = chunk.getRotationIndex(targetBlock.x, targetBlock.y, targetBlock.z); + int settings = 260; + if (!this.updateBlockState) { + settings |= 2; + } + + chunk.setBlock(targetBlock.getX(), targetBlock.getY(), targetBlock.getZ(), newBlockId, newBlockType, rotation, 0, settings); + BlockType interactionStateBlock = current.getBlockForState(newState); + if (interactionStateBlock == null) { + return; + } + + int soundEventIndex = interactionStateBlock.getInteractionSoundEventIndex(); + if (soundEventIndex == 0) { + return; + } + + Ref ref = context.getEntity(); + SoundUtil.playSoundEvent3d(ref, soundEventIndex, targetBlock.x + 0.5, targetBlock.y + 0.5, targetBlock.z + 0.5, commandBuffer); + return; + } + } + + context.getState().state = InteractionState.Failed; + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.ChangeStateInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.ChangeStateInteraction p = (com.hypixel.hytale.protocol.ChangeStateInteraction)packet; + p.stateChanges = this.stateKeys; + } + + @Nonnull + @Override + public String toString() { + return "ChangeStateInteraction{stateKeys=" + this.stateKeys + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ChargingInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ChargingInteraction.java new file mode 100644 index 0000000..5eb1556 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ChargingInteraction.java @@ -0,0 +1,476 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.codec.codecs.map.Float2ObjectMapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.ChargingDelay; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.damage.DamageDataComponent; +import com.hypixel.hytale.server.core.meta.MetaKey; +import com.hypixel.hytale.server.core.modules.interaction.IInteractionSimulationHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.Collector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.CollectorTag; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.StringTag; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.Label; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.OperationsBuilder; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.floats.Float2IntOpenHashMap; +import it.unimi.dsi.fastutil.floats.Float2ObjectMap; +import it.unimi.dsi.fastutil.floats.Float2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.floats.FloatIterator; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.Arrays; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class ChargingInteraction extends Interaction { + @Nonnull + public static final BuilderCodec DELAY_CODEC = BuilderCodec.builder(ChargingDelay.class, ChargingDelay::new) + .documentation( + "Configuration for delay when the user is attacked.\nThe delay will be between **MinDelay** when the incoming at **MinHealth** and **MaxDelay** when the incoming damage is at or above **MaxHealth**." + ) + .appendInherited(new KeyedCodec<>("MinDelay", Codec.FLOAT), (o, i) -> o.minDelay = i, o -> o.minDelay, (o, p) -> o.minDelay = p.minDelay) + .documentation("The smallest amount of delay that can be applied.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.greaterThanOrEqual(0.0F)) + .add() + .appendInherited(new KeyedCodec<>("MaxDelay", Codec.FLOAT), (o, i) -> o.maxDelay = i, o -> o.maxDelay, (o, p) -> o.maxDelay = p.maxDelay) + .documentation("The largest amount of delay that can be applied.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.greaterThanOrEqual(0.0F)) + .add() + .appendInherited( + new KeyedCodec<>("MaxTotalDelay", Codec.FLOAT), (o, i) -> o.maxTotalDelay = i, o -> o.maxTotalDelay, (o, p) -> o.maxTotalDelay = p.maxTotalDelay + ) + .documentation("The max amount of delay applied during this interaction before any additional delay is ignored.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.greaterThanOrEqual(0.0F)) + .add() + .appendInherited(new KeyedCodec<>("MinHealth", Codec.FLOAT), (o, i) -> o.minHealth = i, o -> o.minHealth, (o, p) -> o.minHealth = p.minHealth) + .documentation("The amount of health (as a percentage between 1.0 and 0.0) where if the user's health is below the value then the delay wont be applied.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.greaterThanOrEqual(0.0F)) + .add() + .appendInherited(new KeyedCodec<>("MaxHealth", Codec.FLOAT), (o, i) -> o.maxHealth = i, o -> o.maxHealth, (o, p) -> o.maxHealth = p.maxHealth) + .documentation("The amount of health (as a percentage between 1.0 and 0.0) where if the user's health is above the value then the delay will be capped.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.greaterThanOrEqual(0.0F)) + .add() + .build(); + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(ChargingInteraction.class, Interaction.ABSTRACT_CODEC) + .appendInherited( + new KeyedCodec<>("FailOnDamage", Codec.BOOLEAN), + (interaction, s) -> interaction.failOnDamage = s, + interaction -> interaction.failOnDamage, + (interaction, parent) -> interaction.failOnDamage = parent.failOnDamage + ) + .documentation("Whether the interaction will be cancelled and the item removed when the entity takes damage") + .add() + .appendInherited( + new KeyedCodec<>("CancelOnOtherClick", Codec.BOOLEAN), + (interaction, b) -> interaction.cancelOnOtherClick = b, + interaction -> interaction.cancelOnOtherClick, + (interaction, parent) -> interaction.cancelOnOtherClick = parent.cancelOnOtherClick + ) + .add() + .>appendInherited( + new KeyedCodec<>("Forks", new EnumMapCodec<>(InteractionType.class, RootInteraction.CHILD_ASSET_CODEC)), + (o, i) -> o.forks = i, + o -> o.forks, + (o, p) -> o.forks = p.forks + ) + .documentation( + "A collection of interactions to fork into when the input associated with the interaction type is used.\n\nFor example listing a `Primary` interaction type here with interactions will allow the user to press the input tied to the `Primary` interaction type whilst holding the input used to run the current interaction to run the specified interactions. e.g. Having a shield that you can hold `Secondary` to block and whilst blocking press `Primary` to shield bash.\n\nThis does not cancel the current interaction when triggered but the `CancelOnOtherClick` check will still run and may cancel the interaction.\n\nThe existing forks will continue to run even if this interaction ends." + ) + .addValidatorLate(() -> RootInteraction.VALIDATOR_CACHE.getMapValueValidator().late()) + .add() + .afterDecode(interaction -> { + float max = 0.0F; + if (interaction.next != null) { + FloatIterator iterator = interaction.next.keySet().iterator(); + + while (iterator.hasNext()) { + float nextFloat = iterator.nextFloat(); + if (nextFloat > max) { + max = nextFloat; + } + } + + interaction.sortedKeys = interaction.next.keySet().toFloatArray(); + Arrays.sort(interaction.sortedKeys); + } + + interaction.highestChargeValue = max; + }) + .appendInherited( + new KeyedCodec<>("Failed", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.failed = s, + interaction -> interaction.failed, + (interaction, parent) -> interaction.failed = parent.failed + ) + .documentation("The interactions to run when this interaction fails.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(ChargingInteraction.class, ChargingInteraction::new, ABSTRACT_CODEC) + .documentation( + "An interaction that holds until the key is released (or a time limit is reached) and executes different interactions based on how long the key was pressed." + ) + .appendInherited( + new KeyedCodec<>("AllowIndefiniteHold", Codec.BOOLEAN), + (interaction, s) -> interaction.allowIndefiniteHold = s, + interaction -> interaction.allowIndefiniteHold, + (interaction, parent) -> interaction.allowIndefiniteHold = parent.allowIndefiniteHold + ) + .add() + .appendInherited( + new KeyedCodec<>("DisplayProgress", Codec.BOOLEAN), + (interaction, s) -> interaction.displayProgress = s, + interaction -> interaction.displayProgress, + (interaction, parent) -> interaction.displayProgress = parent.displayProgress + ) + .add() + .>appendInherited( + new KeyedCodec<>("Next", new Float2ObjectMapCodec<>(Interaction.CHILD_ASSET_CODEC, Float2ObjectOpenHashMap::new)), + (interaction, s) -> interaction.next = s, + interaction -> interaction.next, + (interaction, parent) -> interaction.next = parent.next + ) + .addValidatorLate(() -> VALIDATOR_CACHE.getMapValueValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("MouseSensitivityAdjustmentTarget", Codec.FLOAT), + (interaction, doubles) -> interaction.mouseSensitivityAdjustmentTarget = doubles, + interaction -> interaction.mouseSensitivityAdjustmentTarget, + (interaction, parent) -> interaction.mouseSensitivityAdjustmentTarget = parent.mouseSensitivityAdjustmentTarget + ) + .documentation("What is the target modifier to apply to mouse sensitivity while this interaction is active.") + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .appendInherited( + new KeyedCodec<>("MouseSensitivityAdjustmentDuration", Codec.FLOAT), + (interaction, doubles) -> interaction.mouseSensitivityAdjustmentDuration = doubles, + interaction -> interaction.mouseSensitivityAdjustmentDuration, + (interaction, parent) -> interaction.mouseSensitivityAdjustmentDuration = parent.mouseSensitivityAdjustmentDuration + ) + .documentation("Override the global linear modifier adjustment with this as the time to go from 1.0 to 0.0.") + .add() + .appendInherited( + new KeyedCodec<>("Delay", DELAY_CODEC), (o, i) -> o.chargingDelay = i, o -> o.chargingDelay, (o, p) -> o.chargingDelay = p.chargingDelay + ) + .documentation("Settings that allow for delaying the charging interaction on damage.") + .add() + .build(); + private static final MetaKey> FORK_COUNTS = Interaction.META_REGISTRY.registerMetaObject(i -> new Object2IntOpenHashMap<>()); + private static final MetaKey FORKED_CHAIN = Interaction.META_REGISTRY.registerMetaObject(i -> null); + private static final float CHARGING_HELD = -1.0F; + private static final float CHARGING_CANCELED = -2.0F; + private static final StringTag TAG_FAILED = StringTag.of("Failed"); + protected boolean allowIndefiniteHold; + protected boolean displayProgress = true; + protected boolean cancelOnOtherClick = true; + protected boolean failOnDamage; + protected float mouseSensitivityAdjustmentTarget = 1.0F; + protected float mouseSensitivityAdjustmentDuration = 1.0F; + @Nullable + protected String failed; + @Nullable + protected Float2ObjectMap next; + protected float[] sortedKeys; + protected Map forks; + @Nullable + protected ChargingDelay chargingDelay; + protected float highestChargeValue; + + public ChargingInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Client; + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + InteractionSyncData clientData = context.getClientState(); + if (context.getClientState().state == InteractionState.Failed && context.hasLabels()) { + context.getState().state = InteractionState.Failed; + context.jump(context.getLabel(this.next != null ? this.next.size() : 0)); + } else { + if (clientData.forkCounts != null && this.forks != null) { + Object2IntMap serverForkCounts = context.getInstanceStore().getMetaObject(FORK_COUNTS); + InteractionChain forked = context.getInstanceStore().getMetaObject(FORKED_CHAIN); + if (forked != null && forked.getServerState() != InteractionState.NotFinished) { + forked = null; + } + + boolean matches = true; + + for (Entry e : clientData.forkCounts.entrySet()) { + int serverCount = serverForkCounts.getInt(e.getKey()); + String forkInteraction = this.forks.get(e.getKey()); + if (forked == null && serverCount < e.getValue() && forkInteraction != null) { + InteractionContext forkContext = context.duplicate(); + forked = context.fork(e.getKey(), forkContext, RootInteraction.getRootInteractionOrUnknown(forkInteraction), true); + context.getInstanceStore().putMetaObject(FORKED_CHAIN, forked); + serverForkCounts.put(e.getKey(), ++serverCount); + } + + matches &= serverCount == e.getValue(); + } + + if (!matches) { + context.getState().state = InteractionState.NotFinished; + return; + } + } + + if (clientData.chargeValue == -1.0F) { + context.getState().state = InteractionState.NotFinished; + } else if (clientData.chargeValue == -2.0F) { + context.getState().state = InteractionState.Finished; + } else { + context.getState().state = InteractionState.Finished; + float chargeValue = clientData.chargeValue; + if (this.next != null) { + this.jumpToChargeValue(context, chargeValue); + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getEntity(); + DamageDataComponent damageDataComponent = commandBuffer.getComponent(ref, DamageDataComponent.getComponentType()); + + assert damageDataComponent != null; + + damageDataComponent.setLastChargeTime(commandBuffer.getResource(TimeResource.getResourceType()).getNow()); + } + } + } + } + + @Override + protected void simulateTick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + Ref ref = context.getEntity(); + IInteractionSimulationHandler simulationHandler = context.getInteractionManager().getInteractionSimulationHandler(); + if (simulationHandler.isCharging(firstRun, time, type, context, ref, cooldownHandler) && (this.allowIndefiniteHold || time < this.highestChargeValue)) { + if (this.failOnDamage && simulationHandler.shouldCancelCharging(firstRun, time, type, context, ref, cooldownHandler)) { + context.getState().state = InteractionState.Failed; + return; + } + + context.getState().state = InteractionState.NotFinished; + } else { + context.getState().state = InteractionState.Finished; + float chargeValue = simulationHandler.getChargeValue(firstRun, time, type, context, ref, cooldownHandler); + context.getState().chargeValue = chargeValue; + if (this.next == null) { + return; + } + + this.jumpToChargeValue(context, chargeValue); + } + } + + private void jumpToChargeValue(@Nonnull InteractionContext context, float chargeValue) { + float closestDiff = 2.1474836E9F; + int closestValue = -1; + int index = 0; + + for (float e : this.sortedKeys) { + if (chargeValue < e) { + index++; + } else { + float diff = chargeValue - e; + if (closestValue == -1 || diff < closestDiff) { + closestDiff = diff; + closestValue = index; + } + + index++; + } + } + + if (closestValue != -1) { + context.jump(context.getLabel(closestValue)); + } + } + + @Override + public void compile(@Nonnull OperationsBuilder builder) { + Label end = builder.createUnresolvedLabel(); + Label[] labels = new Label[(this.next != null ? this.next.size() : 0) + 1]; + + for (int i = 0; i < labels.length; i++) { + labels[i] = builder.createUnresolvedLabel(); + } + + builder.addOperation(this, labels); + builder.jump(end); + if (this.sortedKeys != null) { + for (int i = 0; i < this.sortedKeys.length; i++) { + float key = this.sortedKeys[i]; + builder.resolveLabel(labels[i]); + Interaction interaction = Interaction.getInteractionOrUnknown(this.next.get(key)); + interaction.compile(builder); + builder.jump(end); + } + } + + int failedIndex = this.sortedKeys != null ? this.sortedKeys.length : 0; + builder.resolveLabel(labels[failedIndex]); + if (this.failed != null) { + Interaction interaction = Interaction.getInteractionOrUnknown(this.failed); + interaction.compile(builder); + } + + builder.resolveLabel(end); + } + + @Override + public boolean walk(@Nonnull Collector collector, @Nonnull InteractionContext context) { + if (this.next != null) { + for (it.unimi.dsi.fastutil.floats.Float2ObjectMap.Entry entry : this.next.float2ObjectEntrySet()) { + if (InteractionManager.walkInteraction(collector, context, ChargingInteraction.ChargingTag.of(entry.getFloatKey()), entry.getValue())) { + return true; + } + } + } + + return this.failed != null && InteractionManager.walkInteraction(collector, context, TAG_FAILED, this.failed); + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.Interaction generatePacket() { + return new com.hypixel.hytale.protocol.ChargingInteraction(); + } + + @Override + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.ChargingInteraction p = (com.hypixel.hytale.protocol.ChargingInteraction)packet; + p.allowIndefiniteHold = this.allowIndefiniteHold; + p.mouseSensitivityAdjustmentTarget = this.mouseSensitivityAdjustmentTarget; + p.mouseSensitivityAdjustmentDuration = this.mouseSensitivityAdjustmentDuration; + p.displayProgress = this.displayProgress; + p.cancelOnOtherClick = this.cancelOnOtherClick; + p.failOnDamage = this.failOnDamage; + p.failed = Interaction.getInteractionIdOrUnknown(this.failed); + p.chargingDelay = this.chargingDelay; + if (this.next != null) { + Float2IntOpenHashMap chargedNext = new Float2IntOpenHashMap(); + + for (it.unimi.dsi.fastutil.floats.Float2ObjectMap.Entry e : this.next.float2ObjectEntrySet()) { + chargedNext.put(e.getFloatKey(), Interaction.getInteractionIdOrUnknown(e.getValue())); + } + + p.chargedNext = chargedNext; + } + + if (this.forks != null) { + Object2IntOpenHashMap intForks = new Object2IntOpenHashMap<>(); + + for (Entry e : this.forks.entrySet()) { + intForks.put(e.getKey(), RootInteraction.getRootInteractionIdOrUnknown(e.getValue())); + } + + p.forks = intForks; + } + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Nonnull + @Override + public String toString() { + return "ChargingInteraction{allowIndefiniteHold=" + + this.allowIndefiniteHold + + ", displayProgress=" + + this.displayProgress + + ", mouseSensitivityAdjustmentTarget=" + + this.mouseSensitivityAdjustmentTarget + + ", mouseSensitivityAdjustmentDuration=" + + this.mouseSensitivityAdjustmentDuration + + ", cancelOnOtherClick=" + + this.cancelOnOtherClick + + ", next=" + + this.next + + ", forks=" + + this.forks + + ", highestChargeValue=" + + this.highestChargeValue + + ", failOnDamage=" + + this.failOnDamage + + "} " + + super.toString(); + } + + private static class ChargingTag implements CollectorTag { + private final float seconds; + + private ChargingTag(float seconds) { + this.seconds = seconds; + } + + public double getSeconds() { + return this.seconds; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + ChargingInteraction.ChargingTag that = (ChargingInteraction.ChargingTag)o; + return Float.compare(that.seconds, this.seconds) == 0; + } else { + return false; + } + } + + @Override + public int hashCode() { + return this.seconds != 0.0F ? Float.floatToIntBits(this.seconds) : 0; + } + + @Nonnull + @Override + public String toString() { + return "ChargingTag{seconds=" + this.seconds + "}"; + } + + @Nonnull + public static ChargingInteraction.ChargingTag of(float seconds) { + return new ChargingInteraction.ChargingTag(seconds); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/CooldownConditionInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/CooldownConditionInteraction.java new file mode 100644 index 0000000..b5d9941 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/CooldownConditionInteraction.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class CooldownConditionInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + CooldownConditionInteraction.class, CooldownConditionInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Checks if a given cooldown is complete.") + .appendInherited(new KeyedCodec<>("Id", Codec.STRING), (o, i) -> o.cooldown = i, o -> o.cooldown, (o, p) -> o.cooldown = p.cooldown) + .documentation("The ID of the cooldown to check for in this condition.") + .addValidator(Validators.nonNull()) + .add() + .build(); + private String cooldown; + + public CooldownConditionInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + assert this.cooldown != null; + + InteractionSyncData state = context.getState(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getEntity(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent == null && context.getClientState() != null) { + state.state = context.getClientState().state; + } else { + if (this.checkCooldown(cooldownHandler, this.cooldown)) { + state.state = InteractionState.Failed; + } else { + state.state = InteractionState.Finished; + } + } + } + + protected boolean checkCooldown(@Nonnull CooldownHandler cooldownHandler, @Nonnull String cooldownId) { + CooldownHandler.Cooldown cooldown = cooldownHandler.getCooldown(cooldownId); + return cooldown != null && cooldown.hasCooldown(false); + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.CooldownConditionInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.CooldownConditionInteraction p = (com.hypixel.hytale.protocol.CooldownConditionInteraction)packet; + p.cooldownId = this.cooldown; + } + + @Nonnull + @Override + public String toString() { + return "CooldownConditionInteraction{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/CycleBlockGroupInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/CycleBlockGroupInteraction.java new file mode 100644 index 0000000..64e10cf --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/CycleBlockGroupInteraction.java @@ -0,0 +1,138 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockSoundEvent; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.blocksound.config.BlockSoundSet; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.gameplay.WorldConfig; +import com.hypixel.hytale.server.core.asset.type.item.config.BlockGroup; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class CycleBlockGroupInteraction extends SimpleBlockInteraction { + private static final int SET_SETTINGS = 256; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + CycleBlockGroupInteraction.class, CycleBlockGroupInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Attempts to cycle the target block through its block set.") + .build(); + + public CycleBlockGroupInteraction() { + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack heldItemStack, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + Ref ref = context.getEntity(); + Store store = ref.getStore(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + InteractionSyncData state = context.getState(); + state.state = InteractionState.Failed; + if (playerComponent == null) { + HytaleLogger.getLogger().at(Level.INFO).atMostEvery(5, TimeUnit.MINUTES).log("CycleBlockGroupInteraction requires a Player but was used for: %s", ref); + } else { + ChunkStore chunkStore = world.getChunkStore(); + Store chunkStoreStore = chunkStore.getStore(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z); + Ref chunkReference = chunkStore.getChunkReference(chunkIndex); + if (chunkReference != null && chunkReference.isValid()) { + WorldChunk worldChunkComponent = chunkStoreStore.getComponent(chunkReference, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + BlockChunk blockChunkComponent = chunkStoreStore.getComponent(chunkReference, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + BlockSection blockSection = blockChunkComponent.getSectionAtBlockY(targetBlock.getY()); + GameplayConfig gameplayConfig = world.getGameplayConfig(); + WorldConfig worldConfig = gameplayConfig.getWorldConfig(); + boolean blockBreakingAllowed = worldConfig.isBlockBreakingAllowed(); + if (blockBreakingAllowed) { + int blockIndex = blockSection.get(targetBlock.x, targetBlock.y, targetBlock.z); + BlockType targetBlockType = BlockType.getAssetMap().getAsset(blockIndex); + if (targetBlockType != null) { + Item targetBlockItem = targetBlockType.getItem(); + BlockGroup set = BlockGroup.findItemGroup(targetBlockItem); + if (set != null) { + int currentIndex = set.getIndex(targetBlockItem); + if (currentIndex != -1) { + String nextBlockKey = set.get((currentIndex + 1) % set.size()); + BlockType nextBlockType = BlockType.getAssetMap().getAsset(nextBlockKey); + if (nextBlockType != null) { + ItemStack heldItem = context.getHeldItem(); + if (heldItem != null && playerComponent.canDecreaseItemStackDurability(ref, store) && !heldItem.isUnbreakable()) { + playerComponent.updateItemStackDurability( + ref, + heldItem, + playerComponent.getInventory().getHotbar(), + context.getHeldItemSlot(), + -heldItem.getItem().getDurabilityLossOnHit(), + commandBuffer + ); + } + + int newBlockId = BlockType.getAssetMap().getIndex(nextBlockType.getId()); + int rotation = worldChunkComponent.getRotationIndex(targetBlock.x, targetBlock.y, targetBlock.z); + worldChunkComponent.setBlock(targetBlock.getX(), targetBlock.getY(), targetBlock.getZ(), newBlockId, nextBlockType, rotation, 0, 256); + state.state = InteractionState.NotFinished; + BlockSoundSet soundSet = BlockSoundSet.getAssetMap().getAsset(nextBlockType.getBlockSoundSetIndex()); + if (soundSet != null) { + int soundEventIndex = soundSet.getSoundEventIndices().getOrDefault(BlockSoundEvent.Hit, 0); + if (soundEventIndex != 0) { + SoundUtil.playSoundEvent3d(ref, soundEventIndex, targetBlock.x + 0.5, targetBlock.y + 0.5, targetBlock.z + 0.5, commandBuffer); + } + } + } + } + } + } + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } + + @Nonnull + @Override + public String toString() { + return "CycleBlockGroupInteraction{}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/DestroyBlockInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/DestroyBlockInteraction.java new file mode 100644 index 0000000..bec7c0b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/DestroyBlockInteraction.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.BlockHarvestUtils; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class DestroyBlockInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + DestroyBlockInteraction.class, DestroyBlockInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Destroys the target block.") + .build(); + + public DestroyBlockInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + BlockPosition blockPosition = context.getTargetBlock(); + if (blockPosition != null) { + Ref ref = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + World world = commandBuffer.getExternalData().getWorld(); + ChunkStore chunkStore = world.getChunkStore(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(blockPosition.x, blockPosition.z); + Ref chunkReference = chunkStore.getChunkReference(chunkIndex); + if (chunkReference != null) { + Vector3i position = new Vector3i(blockPosition.x, blockPosition.y, blockPosition.z); + Store chunkStoreStore = chunkStore.getStore(); + BlockHarvestUtils.performBlockBreak(ref, null, position, chunkReference, context.getCommandBuffer(), chunkStoreStore); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ExplodeInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ExplodeInteraction.java new file mode 100644 index 0000000..bfba264 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ExplodeInteraction.java @@ -0,0 +1,117 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector4d; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.ExplosionConfig; +import com.hypixel.hytale.server.core.entity.ExplosionUtils; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.ProjectileComponent; +import com.hypixel.hytale.server.core.meta.DynamicMetaStore; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.InteractionTypeUtils; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.modules.projectile.component.Projectile; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ExplodeInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ExplodeInteraction.class, ExplodeInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Performs an explosion using the provided config.") + .appendInherited( + new KeyedCodec<>("Config", ExplosionConfig.CODEC), + (interaction, s) -> interaction.config = s, + interaction -> interaction.config, + (interaction, parent) -> interaction.config = parent.config + ) + .addValidator(Validators.nonNull()) + .documentation("The explosion config associated with this projectile.") + .add() + .build(); + @Nonnull + public static final Damage.EnvironmentSource DAMAGE_SOURCE_EXPLOSION = new Damage.EnvironmentSource("explosion"); + @Nullable + private ExplosionConfig config; + + public ExplodeInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + assert this.config != null; + + DynamicMetaStore metaStore = context.getMetaStore(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getEntity(); + Ref ownerRef = context.getOwningEntity(); + World world = commandBuffer.getExternalData().getWorld(); + Store chunkStore = world.getChunkStore().getStore(); + BlockPosition blockPosition = metaStore.getIfPresentMetaObject(Interaction.TARGET_BLOCK); + Vector4d hitLocation = metaStore.getIfPresentMetaObject(Interaction.HIT_LOCATION); + Vector3d position; + if (hitLocation != null) { + position = new Vector3d(hitLocation.x, hitLocation.y, hitLocation.z); + } else if (InteractionTypeUtils.isCollisionType(type) && blockPosition != null) { + long chunkIndex = ChunkUtil.indexChunkFromBlock(blockPosition.x, blockPosition.z); + Ref chunkReference = chunkStore.getExternalData().getChunkReference(chunkIndex); + if (chunkReference == null || !chunkReference.isValid()) { + return; + } + + WorldChunk worldChunkComponent = chunkStore.getComponent(chunkReference, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + BlockChunk blockChunkComponent = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + BlockType blockType = worldChunkComponent.getBlockType(blockPosition.x, blockPosition.y, blockPosition.z); + if (blockType == null) { + return; + } + + BlockSection blockSection = blockChunkComponent.getSectionAtBlockY(blockPosition.y); + int rotationIndex = blockSection.getRotationIndex(blockPosition.x, blockPosition.y, blockPosition.z); + position = new Vector3d(); + blockType.getBlockCenter(rotationIndex, position); + position.add(blockPosition.x, blockPosition.y, blockPosition.z); + } else { + TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + position = transformComponent.getPosition(); + } + + Archetype archetype = commandBuffer.getArchetype(ref); + boolean isProjectile = archetype.contains(Projectile.getComponentType()) || archetype.contains(ProjectileComponent.getComponentType()); + Damage.Source damageSource = (Damage.Source)(isProjectile ? new Damage.ProjectileSource(ownerRef, ref) : DAMAGE_SOURCE_EXPLOSION); + ExplosionUtils.performExplosion(damageSource, position, this.config, isProjectile ? ref : null, commandBuffer, chunkStore); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/FirstClickInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/FirstClickInteraction.java new file mode 100644 index 0000000..58627b0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/FirstClickInteraction.java @@ -0,0 +1,159 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.modules.interaction.IInteractionSimulationHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.Collector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.StringTag; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.Label; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.OperationsBuilder; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class FirstClickInteraction extends Interaction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + FirstClickInteraction.class, FirstClickInteraction::new, Interaction.ABSTRACT_CODEC + ) + .documentation("An interaction that runs a different interaction based on if this chain was from a click or due to the key being held down.") + .appendInherited( + new KeyedCodec<>("Click", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.click = s, + interaction -> interaction.click, + (interaction, parent) -> interaction.click = parent.click + ) + .documentation("The interaction to run if this chain was initiated by a click.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("Held", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.held = s, + interaction -> interaction.held, + (interaction, parent) -> interaction.held = parent.held + ) + .documentation("The interaction to run if this chain was initiated by holding down the key.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + @Nonnull + public static final StringTag TAG_CLICK = StringTag.of("Click"); + @Nonnull + public static final StringTag TAG_HELD = StringTag.of("Held"); + private static final int HELD_LABEL_INDEX = 0; + @Nullable + protected String click; + @Nullable + protected String held; + + public FirstClickInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Client; + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + InteractionSyncData clientState = context.getClientState(); + + assert clientState != null; + + if (clientState.state == InteractionState.Failed && context.hasLabels()) { + context.getState().state = InteractionState.Failed; + context.jump(context.getLabel(0)); + } else { + context.getState().state = InteractionState.Finished; + } + } + + @Override + protected void simulateTick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + Ref ref = context.getEntity(); + InteractionManager interactionManager = context.getInteractionManager(); + + assert interactionManager != null; + + IInteractionSimulationHandler simulationHandler = interactionManager.getInteractionSimulationHandler(); + if (!simulationHandler.isCharging(firstRun, time, type, context, ref, cooldownHandler)) { + context.getState().state = InteractionState.Finished; + } else { + context.getState().state = InteractionState.Failed; + } + } + + @Override + public void compile(@Nonnull OperationsBuilder builder) { + if (this.click == null && this.held == null) { + builder.addOperation(this); + } else { + Label failedLabel = builder.createUnresolvedLabel(); + Label endLabel = builder.createUnresolvedLabel(); + builder.addOperation(this, failedLabel); + if (this.click != null) { + Interaction nextInteraction = Interaction.getInteractionOrUnknown(this.click); + nextInteraction.compile(builder); + } + + if (this.held != null) { + builder.jump(endLabel); + } + + builder.resolveLabel(failedLabel); + if (this.held != null) { + Interaction failedInteraction = Interaction.getInteractionOrUnknown(this.held); + failedInteraction.compile(builder); + } + + builder.resolveLabel(endLabel); + } + } + + @Override + public boolean walk(@Nonnull Collector collector, @Nonnull InteractionContext context) { + return this.click != null && InteractionManager.walkInteraction(collector, context, TAG_CLICK, this.click) + ? true + : this.held != null && InteractionManager.walkInteraction(collector, context, TAG_HELD, this.held); + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.Interaction generatePacket() { + return new com.hypixel.hytale.protocol.FirstClickInteraction(); + } + + @Override + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.FirstClickInteraction p = (com.hypixel.hytale.protocol.FirstClickInteraction)packet; + p.click = Interaction.getInteractionIdOrUnknown(this.click); + p.held = Interaction.getInteractionIdOrUnknown(this.held); + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Nonnull + @Override + public String toString() { + return "FirstClickInteraction{click='" + this.click + "', held='" + this.held + "'} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/IncrementCooldownInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/IncrementCooldownInteraction.java new file mode 100644 index 0000000..d6a44e5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/IncrementCooldownInteraction.java @@ -0,0 +1,110 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionCooldown; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import io.netty.util.internal.StringUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class IncrementCooldownInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + IncrementCooldownInteraction.class, IncrementCooldownInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Increase the given cooldown.") + .appendInherited(new KeyedCodec<>("Id", Codec.STRING), (o, i) -> o.cooldown = i, o -> o.cooldown, (o, p) -> o.cooldown = p.cooldown) + .documentation("The ID of the cooldown to increment") + .add() + .appendInherited( + new KeyedCodec<>("Time", Codec.FLOAT), (o, i) -> o.cooldownTime = i, o -> o.cooldownTime, (o, p) -> o.cooldownTime = p.cooldownTime + ) + .documentation("The amount of time to increase the current cooldown time by") + .add() + .appendInherited(new KeyedCodec<>("ChargeTime", Codec.FLOAT), (o, i) -> o.chargeTime = i, o -> o.chargeTime, (o, p) -> o.chargeTime = p.chargeTime) + .documentation("The amount of time to increase the current charge time by") + .add() + .appendInherited(new KeyedCodec<>("Charge", Codec.INTEGER), (o, i) -> o.charge = i, o -> o.charge, (o, p) -> o.charge = p.charge) + .documentation("The amount of empty charges to recharge") + .add() + .appendInherited( + new KeyedCodec<>("InterruptRecharge", Codec.BOOLEAN), + (o, i) -> o.interruptRecharge = i, + o -> o.interruptRecharge, + (o, p) -> o.interruptRecharge = p.interruptRecharge + ) + .documentation("Determines whether the recharge of this cooldown should be interrupted") + .add() + .afterDecode(interaction -> interaction.chargeTime = -interaction.chargeTime) + .build(); + @Nullable + private String cooldown; + private float cooldownTime; + private float chargeTime; + private int charge; + private boolean interruptRecharge; + + public IncrementCooldownInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + String cooldownId = this.cooldown; + if (StringUtil.isNullOrEmpty(cooldownId)) { + InteractionCooldown rootCooldown = context.getChain().getRootInteraction().getCooldown(); + if (rootCooldown != null) { + cooldownId = rootCooldown.cooldownId; + } + } + + this.processCooldown(cooldownHandler, cooldownId); + context.getState().state = InteractionState.Finished; + } + + protected void processCooldown(@Nonnull CooldownHandler cooldownHandler, @Nonnull String cooldownId) { + CooldownHandler.Cooldown cooldown = cooldownHandler.getCooldown(cooldownId); + if (cooldown != null) { + if (this.cooldownTime != 0.0F) { + cooldown.increaseTime(this.cooldownTime); + } + + if (this.charge != 0) { + cooldown.replenishCharge(this.charge, this.interruptRecharge); + } + + if (this.chargeTime != 0.0F) { + cooldown.increaseChargeTime(this.chargeTime); + } + } + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.IncrementCooldownInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.IncrementCooldownInteraction p = (com.hypixel.hytale.protocol.IncrementCooldownInteraction)packet; + p.cooldownId = this.cooldown; + p.cooldownIncrementTime = this.cooldownTime; + p.cooldownIncrementCharge = this.charge; + p.cooldownIncrementChargeTime = this.chargeTime; + p.cooldownIncrementInterrupt = this.interruptRecharge; + } + + @Nonnull + @Override + public String toString() { + return "IncrementCooldownInteraction{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/MovementConditionInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/MovementConditionInteraction.java new file mode 100644 index 0000000..db0992d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/MovementConditionInteraction.java @@ -0,0 +1,216 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.MovementDirection; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.Label; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.OperationsBuilder; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class MovementConditionInteraction extends SimpleInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + MovementConditionInteraction.class, MovementConditionInteraction::new, SimpleInteraction.CODEC + ) + .documentation("An interaction that runs different interactions based on the movement the user is current performing.") + .appendInherited( + new KeyedCodec<>("Forward", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.forward = s, + interaction -> interaction.forward, + (interaction, parent) -> interaction.forward = parent.forward + ) + .documentation("The interaction to run if the player is moving forward.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("Back", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.back = s, + interaction -> interaction.back, + (interaction, parent) -> interaction.back = parent.back + ) + .documentation("The interaction to run if the player is moving backwards.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("Left", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.left = s, + interaction -> interaction.left, + (interaction, parent) -> interaction.left = parent.left + ) + .documentation("The interaction to run if the player is moving left.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("Right", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.right = s, + interaction -> interaction.right, + (interaction, parent) -> interaction.right = parent.right + ) + .documentation("The interaction to run if the player is moving right.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("ForwardLeft", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.forwardLeft = s, + interaction -> interaction.forwardLeft, + (interaction, parent) -> interaction.forwardLeft = parent.forwardLeft + ) + .documentation("The interaction to run if the player is moving forward and left.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("ForwardRight", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.forwardRight = s, + interaction -> interaction.forwardRight, + (interaction, parent) -> interaction.forwardRight = parent.forwardRight + ) + .documentation("The interaction to run if the player is moving forward and right.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("BackLeft", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.backLeft = s, + interaction -> interaction.backLeft, + (interaction, parent) -> interaction.backLeft = parent.backLeft + ) + .documentation("The interaction to run if the player is moving backwards and left.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("BackRight", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.backRight = s, + interaction -> interaction.backRight, + (interaction, parent) -> interaction.backRight = parent.backRight + ) + .documentation("The interaction to run if the player is moving backwards and right.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + private static final int FAILED_LABEL_INDEX = 0; + private static final int FORWARD_LABEL_INDEX = 1; + private static final int BACK_LABEL_INDEX = 2; + private static final int LEFT_LABEL_INDEX = 3; + private static final int RIGHT_LABEL_INDEX = 4; + private static final int FORWARD_LEFT_LABEL_INDEX = 5; + private static final int FORWARD_RIGHT_LABEL_INDEX = 6; + private static final int BACK_LEFT_LABEL_INDEX = 7; + private static final int BACK_RIGHT_LABEL_INDEX = 8; + @Nullable + private String forward; + @Nullable + private String back; + @Nullable + private String left; + @Nullable + private String right; + @Nullable + private String forwardLeft; + @Nullable + private String forwardRight; + @Nullable + private String backLeft; + @Nullable + private String backRight; + + public MovementConditionInteraction() { + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + context.getState().state = InteractionState.Finished; + + context.jump(context.getLabel(switch (context.getClientState().movementDirection) { + case None -> 0; + case Forward -> 1; + case Back -> 2; + case Left -> 3; + case Right -> 4; + case ForwardLeft -> 5; + case ForwardRight -> 6; + case BackLeft -> 7; + case BackRight -> 8; + })); + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Client; + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Override + protected void simulateTick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + context.getState().movementDirection = MovementDirection.None; + context.jump(context.getLabel(0)); + } + + @Override + public void compile(@Nonnull OperationsBuilder builder) { + Label[] labels = new Label[9]; + + for (int i = 0; i < labels.length; i++) { + labels[i] = builder.createUnresolvedLabel(); + } + + builder.addOperation(this, labels); + Label endLabel = builder.createUnresolvedLabel(); + resolve(builder, this.failed, labels[0], endLabel); + resolve(builder, this.forward, labels[1], endLabel); + resolve(builder, this.back, labels[2], endLabel); + resolve(builder, this.left, labels[3], endLabel); + resolve(builder, this.right, labels[4], endLabel); + resolve(builder, this.forwardLeft, labels[5], endLabel); + resolve(builder, this.forwardRight, labels[6], endLabel); + resolve(builder, this.backLeft, labels[7], endLabel); + resolve(builder, this.backRight, labels[8], endLabel); + builder.resolveLabel(endLabel); + } + + private static void resolve(@Nonnull OperationsBuilder builder, @Nullable String id, @Nonnull Label label, @Nonnull Label endLabel) { + builder.resolveLabel(label); + if (id != null) { + Interaction interaction = Interaction.getInteractionOrUnknown(id); + interaction.compile(builder); + } + + builder.jump(endLabel); + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.Interaction generatePacket() { + return new com.hypixel.hytale.protocol.MovementConditionInteraction(); + } + + @Override + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.MovementConditionInteraction p = (com.hypixel.hytale.protocol.MovementConditionInteraction)packet; + p.forward = Interaction.getInteractionIdOrUnknown(this.forward); + p.back = Interaction.getInteractionIdOrUnknown(this.back); + p.left = Interaction.getInteractionIdOrUnknown(this.left); + p.right = Interaction.getInteractionIdOrUnknown(this.right); + p.forwardLeft = Interaction.getInteractionIdOrUnknown(this.forwardLeft); + p.forwardRight = Interaction.getInteractionIdOrUnknown(this.forwardRight); + p.backLeft = Interaction.getInteractionIdOrUnknown(this.backLeft); + p.backRight = Interaction.getInteractionIdOrUnknown(this.backRight); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/PickBlockInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/PickBlockInteraction.java new file mode 100644 index 0000000..c074235 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/PickBlockInteraction.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +public class PickBlockInteraction extends SimpleBlockInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + PickBlockInteraction.class, PickBlockInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Performs a 'block pick', moving a the target block to the user's hand if they have it in their inventory or are in creative.") + .build(); + + public PickBlockInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Client; + } + + @Override + protected void interactWithBlock( + @NonNullDecl World world, + @NonNullDecl CommandBuffer commandBuffer, + @NonNullDecl InteractionType type, + @NonNullDecl InteractionContext context, + @NullableDecl ItemStack itemInHand, + @NonNullDecl Vector3i targetBlock, + @NonNullDecl CooldownHandler cooldownHandler + ) { + } + + @Override + protected void simulateInteractWithBlock( + @NonNullDecl InteractionType type, + @NonNullDecl InteractionContext context, + @NullableDecl ItemStack itemInHand, + @NonNullDecl World world, + @NonNullDecl Vector3i targetBlock + ) { + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.PickBlockInteraction(); + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Nonnull + @Override + public String toString() { + return "PickBlockInteraction{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/PlaceBlockInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/PlaceBlockInteraction.java new file mode 100644 index 0000000..58d2876 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/PlaceBlockInteraction.java @@ -0,0 +1,241 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.BlockRotation; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.Rotation; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFace; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.BlockPlaceUtils; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInteraction; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class PlaceBlockInteraction extends SimpleInteraction { + public static final int MAX_ADVENTURE_PLACEMENT_RANGE_SQUARED = 36; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + PlaceBlockInteraction.class, PlaceBlockInteraction::new, SimpleInteraction.CODEC + ) + .documentation("Places the current or given block.") + .append( + new KeyedCodec<>("BlockTypeToPlace", Codec.STRING), + (placeBlockInteraction, blockTypeKey) -> placeBlockInteraction.blockTypeKey = blockTypeKey, + placeBlockInteraction -> placeBlockInteraction.blockTypeKey + ) + .addValidatorLate(() -> BlockType.VALIDATOR_CACHE.getValidator().late()) + .documentation("Overrides the placed block type of the held item with the provided block type.") + .add() + .append( + new KeyedCodec<>("RemoveItemInHand", Codec.BOOLEAN), + (placeBlockInteraction, aBoolean) -> placeBlockInteraction.removeItemInHand = aBoolean, + placeBlockInteraction -> placeBlockInteraction.removeItemInHand + ) + .documentation("Determines whether to remove the item that is in the instigating entities hand.") + .add() + .appendInherited( + new KeyedCodec<>("AllowDragPlacement", Codec.BOOLEAN), + (placeBlockInteraction, aBoolean) -> placeBlockInteraction.allowDragPlacement = aBoolean, + placeBlockInteraction -> placeBlockInteraction.allowDragPlacement, + (placeBlockInteraction, parent) -> placeBlockInteraction.allowDragPlacement = parent.allowDragPlacement + ) + .documentation("If drag placement should be used when click is held for this interaction.") + .add() + .build(); + @Nullable + protected String blockTypeKey; + protected boolean removeItemInHand = true; + protected boolean allowDragPlacement = true; + + public PlaceBlockInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Client; + } + + @Override + protected final void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + InteractionSyncData clientState = context.getClientState(); + + assert clientState != null; + + if (!firstRun) { + context.getState().state = clientState.state; + } else { + Ref ref = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + BlockPosition blockPosition = clientState.blockPosition; + BlockRotation blockRotation = clientState.blockRotation; + if (blockPosition != null && blockRotation != null) { + World world = commandBuffer.getExternalData().getWorld(); + Store chunkStore = world.getChunkStore().getStore(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(blockPosition.x, blockPosition.z); + Ref chunkReference = chunkStore.getExternalData().getChunkReference(chunkIndex); + if (chunkReference == null || !chunkReference.isValid()) { + return; + } + + ItemStack heldItemStack = context.getHeldItem(); + if (heldItemStack == null) { + return; + } + + ItemContainer heldItemContainer = context.getHeldItemContainer(); + if (heldItemContainer == null) { + return; + } + + TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType()); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (transformComponent != null && playerComponent != null && playerComponent.getGameMode() != GameMode.Creative) { + Vector3d position = transformComponent.getPosition(); + Vector3d blockCenter = new Vector3d(blockPosition.x + 0.5, blockPosition.y + 0.5, blockPosition.z + 0.5); + if (position.distanceSquaredTo(blockCenter) > 36.0) { + return; + } + } + + Inventory inventory = null; + if (EntityUtils.getEntity(ref, commandBuffer) instanceof LivingEntity livingEntity) { + inventory = livingEntity.getInventory(); + } + + Vector3i targetBlockPosition = new Vector3i(blockPosition.x, blockPosition.y, blockPosition.z); + String interactionBlockTypeKey = this.blockTypeKey != null ? this.blockTypeKey : heldItemStack.getBlockKey(); + if (interactionBlockTypeKey == null) { + return; + } + + BlockType interactionBlockType = BlockType.getAssetMap().getAsset(interactionBlockTypeKey); + int clientPlacedBlockId = clientState.placedBlockId; + String clientPlacedBlockTypeKey = clientPlacedBlockId == -1 ? null : BlockType.getAssetMap().getAsset(clientPlacedBlockId).getId(); + if (clientPlacedBlockTypeKey != null + && !clientPlacedBlockTypeKey.equals(this.blockTypeKey) + && (interactionBlockType == null || !BlockPlaceUtils.canPlaceBlock(interactionBlockType, clientPlacedBlockTypeKey))) { + clientPlacedBlockTypeKey = null; + } + + if (blockPosition.y < 0 || blockPosition.y >= 320) { + return; + } + + BlockPlaceUtils.placeBlock( + ref, + heldItemStack, + clientPlacedBlockTypeKey != null ? clientPlacedBlockTypeKey : this.blockTypeKey, + heldItemContainer, + BlockFace.fromProtocolFace(context.getClientState().blockFace).getDirection(), + targetBlockPosition, + blockRotation, + inventory, + context.getHeldItemSlot(), + this.removeItemInHand, + chunkReference, + chunkStore, + commandBuffer + ); + boolean isAdventure = playerComponent == null || playerComponent.getGameMode() == GameMode.Adventure; + if (isAdventure && heldItemStack.getQuantity() == 1 && this.removeItemInHand) { + context.setHeldItem(null); + } + + BlockChunk blockChunk = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType()); + BlockSection section = blockChunk.getSectionAtBlockY(blockPosition.y); + context.getState().blockPosition = blockPosition; + context.getState().placedBlockId = section.get(blockPosition.x, blockPosition.y, blockPosition.z); + RotationTuple resultRotation = section.getRotation(blockPosition.x, blockPosition.y, blockPosition.z); + context.getState().blockRotation = new BlockRotation( + resultRotation.yaw().toPacket(), resultRotation.pitch().toPacket(), resultRotation.roll().toPacket() + ); + } + + super.tick0(firstRun, time, type, context, cooldownHandler); + } + } + + @Override + protected void simulateTick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + super.simulateTick0(firstRun, time, type, context, cooldownHandler); + if (!Interaction.failed(context.getState().state)) { + InteractionSyncData clientState = context.getClientState(); + + assert clientState != null; + + if (!firstRun) { + context.getState().state = context.getClientState().state; + } else { + clientState.blockRotation = new BlockRotation(Rotation.None, Rotation.None, Rotation.None); + } + } + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.Interaction generatePacket() { + return new com.hypixel.hytale.protocol.PlaceBlockInteraction(); + } + + @Override + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.PlaceBlockInteraction p = (com.hypixel.hytale.protocol.PlaceBlockInteraction)packet; + p.blockId = this.blockTypeKey == null ? -1 : BlockType.getAssetMap().getIndex(this.blockTypeKey); + p.removeItemInHand = this.removeItemInHand; + p.allowDragPlacement = this.allowDragPlacement; + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Override + public String toString() { + return "PlaceBlockInteraction{blockTypeKey='" + + this.blockTypeKey + + "', removeItemInHand=" + + this.removeItemInHand + + ", allowDragPlacement=" + + this.allowDragPlacement + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/PlaceFluidInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/PlaceFluidInteraction.java new file mode 100644 index 0000000..4578876 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/PlaceFluidInteraction.java @@ -0,0 +1,130 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFace; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.asset.type.fluid.FluidTicker; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlaceFluidInteraction extends SimpleBlockInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + PlaceFluidInteraction.class, PlaceFluidInteraction::new, SimpleInteraction.CODEC + ) + .documentation("Places the current or given block.") + .append( + new KeyedCodec<>("FluidToPlace", Codec.STRING), + (placeBlockInteraction, blockTypeKey) -> placeBlockInteraction.fluidKey = blockTypeKey, + placeBlockInteraction -> placeBlockInteraction.fluidKey + ) + .addValidatorLate(() -> Fluid.VALIDATOR_CACHE.getValidator().late()) + .add() + .append( + new KeyedCodec<>("RemoveItemInHand", Codec.BOOLEAN), + (placeBlockInteraction, aBoolean) -> placeBlockInteraction.removeItemInHand = aBoolean, + placeBlockInteraction -> placeBlockInteraction.removeItemInHand + ) + .add() + .build(); + @Nullable + protected String fluidKey; + protected boolean removeItemInHand = true; + + public PlaceFluidInteraction() { + } + + @Nullable + public String getFluidKey() { + return this.fluidKey; + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Client; + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + Store store = world.getChunkStore().getStore(); + int fluidIndex = Fluid.getFluidIdOrUnknown(this.fluidKey, "Unknown fluid: %s", this.fluidKey); + Fluid fluid = Fluid.getAssetMap().getAsset(fluidIndex); + Vector3i target = targetBlock; + BlockType targetBlockType = world.getBlockType(targetBlock); + if (FluidTicker.isSolid(targetBlockType)) { + target = targetBlock.clone(); + BlockFace face = BlockFace.fromProtocolFace(context.getClientState().blockFace); + target.add(face.getDirection()); + } + + Ref section = world.getChunkStore() + .getChunkSectionReference(ChunkUtil.chunkCoordinate(target.x), ChunkUtil.chunkCoordinate(target.y), ChunkUtil.chunkCoordinate(target.z)); + if (section != null) { + FluidSection fluidSectionComponent = store.getComponent(section, FluidSection.getComponentType()); + if (fluidSectionComponent != null) { + fluidSectionComponent.setFluid(target.x, target.y, target.z, fluid, (byte)fluid.getMaxFluidLevel()); + Ref chunkColumn = world.getChunkStore().getChunkReference(ChunkUtil.indexChunkFromBlock(target.x, target.z)); + if (chunkColumn != null) { + BlockChunk blockChunkComponent = store.getComponent(chunkColumn, BlockChunk.getComponentType()); + blockChunkComponent.setTicking(target.x, target.y, target.z, true); + Ref ref = context.getEntity(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + PlayerRef playerRefComponent = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); + if ((playerRefComponent == null || playerComponent != null && playerComponent.getGameMode() == GameMode.Adventure) + && itemInHand.getQuantity() == 1 + && this.removeItemInHand) { + context.setHeldItem(null); + } + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Nonnull + @Override + public String toString() { + return "PlaceBlockInteraction{blockTypeKey=" + this.fluidKey + ", removeItemInHand=" + this.removeItemInHand + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ResetCooldownInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ResetCooldownInteraction.java new file mode 100644 index 0000000..9d537d8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ResetCooldownInteraction.java @@ -0,0 +1,138 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionCooldown; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ResetCooldownInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ResetCooldownInteraction.class, ResetCooldownInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Resets the cooldown.") + .appendInherited( + new KeyedCodec<>("Cooldown", RootInteraction.COOLDOWN_CODEC), + (interaction, s) -> interaction.cooldown = s, + interaction -> interaction.cooldown, + (interaction, parent) -> interaction.next = parent.next + ) + .documentation("The cooldown concerning this interaction, defaulting to the root cooldown if none presented") + .add() + .build(); + @Nullable + private InteractionCooldown cooldown; + + public ResetCooldownInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + String cooldownId = null; + float cooldownTime = 0.0F; + float[] charges = null; + boolean interruptRecharge = false; + if (this.cooldown != null) { + cooldownId = this.cooldown.cooldownId; + cooldownTime = this.cooldown.cooldown; + charges = this.cooldown.chargeTimes; + interruptRecharge = this.cooldown.interruptRecharge; + } + + resetCooldown(context, cooldownHandler, cooldownId, cooldownTime, charges, interruptRecharge); + } + + protected static void resetCooldown( + @Nonnull InteractionContext context, + @Nonnull CooldownHandler cooldownHandler, + @Nullable String cooldownId, + float cooldownTime, + @Nullable float[] chargeTimes, + boolean interruptRecharge0 + ) { + float time = 0.35F; + float[] charges = InteractionManager.DEFAULT_CHARGE_TIMES; + boolean interruptRecharge = false; + if (cooldownId == null) { + InteractionChain chain = context.getChain(); + + assert chain != null; + + RootInteraction rootInteraction = context.getChain().getInitialRootInteraction(); + InteractionCooldown rootCooldown = rootInteraction.getCooldown(); + if (rootCooldown != null) { + cooldownId = rootCooldown.cooldownId; + if (rootCooldown.cooldown > 0.0F) { + time = rootCooldown.cooldown; + } + + if (rootCooldown.interruptRecharge) { + interruptRecharge = true; + } + + if (rootCooldown.chargeTimes != null && rootCooldown.chargeTimes.length > 0) { + charges = rootCooldown.chargeTimes; + } + } + + if (cooldownId == null) { + cooldownId = rootInteraction.getId(); + } + } + + CooldownHandler.Cooldown possibleCooldown = cooldownHandler.getCooldown(cooldownId); + if (possibleCooldown != null) { + time = possibleCooldown.getCooldown(); + charges = possibleCooldown.getCharges(); + interruptRecharge = possibleCooldown.interruptRecharge(); + } + + if (cooldownTime > 0.0F) { + time = cooldownTime; + } + + if (chargeTimes != null && chargeTimes.length > 0) { + charges = chargeTimes; + } + + if (interruptRecharge0) { + interruptRecharge = true; + } + + CooldownHandler.Cooldown cooldown = cooldownHandler.getCooldown(cooldownId, time, charges, true, interruptRecharge); + if (cooldown != null) { + cooldown.setCooldownMax(time); + cooldown.setCharges(charges); + cooldown.resetCooldown(); + cooldown.resetCharges(); + } + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.ResetCooldownInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.ResetCooldownInteraction p = (com.hypixel.hytale.protocol.ResetCooldownInteraction)packet; + p.cooldown = this.cooldown; + } + + @Nonnull + @Override + public String toString() { + return "ResetCooldownInteraction{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/SimpleBlockInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/SimpleBlockInteraction.java new file mode 100644 index 0000000..5f8df27 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/SimpleBlockInteraction.java @@ -0,0 +1,229 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockFace; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.BlockRotation; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInteraction; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public abstract class SimpleBlockInteraction extends SimpleInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.abstractBuilder(SimpleBlockInteraction.class, SimpleInteraction.CODEC) + .appendInherited( + new KeyedCodec<>("UseLatestTarget", Codec.BOOLEAN), + (interaction, s) -> interaction.useLatestTarget = s, + interaction -> interaction.useLatestTarget, + (interaction, parent) -> interaction.useLatestTarget = parent.useLatestTarget + ) + .documentation("Determines whether to use the clients latest target block position for this interaction.") + .add() + .build(); + private boolean useLatestTarget = false; + + public SimpleBlockInteraction(@Nonnull String id) { + super(id); + } + + protected SimpleBlockInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Client; + } + + @Override + protected void tick0( + boolean firstRun, float time, @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler + ) { + if (firstRun) { + Ref ref = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + World world = commandBuffer.getExternalData().getWorld(); + if (this.useLatestTarget) { + InteractionSyncData clientState = context.getClientState(); + if (clientState == null || clientState.blockPosition == null) { + context.getState().state = InteractionState.Failed; + super.tick0(firstRun, time, type, context, cooldownHandler); + return; + } + + BlockPosition latestBlockPos = clientState.blockPosition; + TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + double distanceSquared = transformComponent.getPosition().distanceSquaredTo(latestBlockPos.x + 0.5, latestBlockPos.y + 0.5, latestBlockPos.z + 0.5); + BlockPosition baseBlock = world.getBaseBlock(latestBlockPos); + context.getMetaStore().putMetaObject(Interaction.TARGET_BLOCK, baseBlock); + context.getMetaStore().putMetaObject(Interaction.TARGET_BLOCK_RAW, latestBlockPos); + } + + BlockPosition targetBlockPos = context.getTargetBlock(); + if (targetBlockPos == null) { + context.getState().state = InteractionState.Failed; + super.tick0(firstRun, time, type, context, cooldownHandler); + } else if (EntityUtils.getEntity(ref, commandBuffer) instanceof LivingEntity livingEntity) { + Inventory inventory = livingEntity.getInventory(); + ItemStack itemInHand = inventory.getItemInHand(); + Vector3i var21 = new Vector3i(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z); + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(var21.x, var21.z)); + if (chunk == null) { + context.getState().state = InteractionState.Failed; + super.tick0(firstRun, time, type, context, cooldownHandler); + } else { + int blockId = chunk.getBlock(var21); + if (blockId != 1 && blockId != 0) { + this.interactWithBlock(world, commandBuffer, type, context, itemInHand, var21, cooldownHandler); + super.tick0(firstRun, time, type, context, cooldownHandler); + } else { + context.getState().state = InteractionState.Failed; + super.tick0(firstRun, time, type, context, cooldownHandler); + } + } + } + } + } + + protected abstract void interactWithBlock( + @Nonnull World var1, + @Nonnull CommandBuffer var2, + @Nonnull InteractionType var3, + @Nonnull InteractionContext var4, + @Nullable ItemStack var5, + @Nonnull Vector3i var6, + @Nonnull CooldownHandler var7 + ); + + @Override + protected void simulateTick0( + boolean firstRun, float time, @Nonnull InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + if (firstRun) { + Ref ref = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + World world = commandBuffer.getExternalData().getWorld(); + if (EntityUtils.getEntity(ref, commandBuffer) instanceof LivingEntity livingEntity) { + Inventory inventory = livingEntity.getInventory(); + ItemStack itemInHand = inventory.getItemInHand(); + context.getState().blockFace = BlockFace.Up; + BlockPosition contextTargetBlock = context.getTargetBlock(); + Vector3i targetBlock; + if (contextTargetBlock == null) { + targetBlock = TargetUtil.getTargetBlock(ref, 8.0, commandBuffer); + if (targetBlock == null) { + context.getState().state = InteractionState.Failed; + super.tick0(firstRun, time, type, context, cooldownHandler); + return; + } + + context.getState().blockPosition = new BlockPosition(targetBlock.x, targetBlock.y, targetBlock.z); + } else { + context.getState().blockPosition = contextTargetBlock; + targetBlock = new Vector3i(contextTargetBlock.x, contextTargetBlock.y, contextTargetBlock.z); + } + + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)); + if (chunk == null) { + context.getState().state = InteractionState.Failed; + super.tick0(firstRun, time, type, context, cooldownHandler); + } else { + int blockId = chunk.getBlock(targetBlock); + if (blockId != 1 && blockId != 0) { + this.simulateInteractWithBlock(type, context, itemInHand, world, targetBlock); + super.tick0(firstRun, time, type, context, cooldownHandler); + } else { + context.getState().state = InteractionState.Failed; + super.tick0(firstRun, time, type, context, cooldownHandler); + } + } + } + } + } + + protected abstract void simulateInteractWithBlock( + @Nonnull InteractionType var1, @Nonnull InteractionContext var2, @Nullable ItemStack var3, @Nonnull World var4, @Nonnull Vector3i var5 + ); + + protected void computeCurrentBlockSyncData(@Nonnull InteractionContext context) { + BlockPosition targetBlockPos = context.getTargetBlock(); + if (targetBlockPos != null) { + World world = context.getCommandBuffer().getStore().getExternalData().getWorld(); + ChunkStore chunkStore = world.getChunkStore(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(targetBlockPos.x, targetBlockPos.z); + Ref chunkReference = chunkStore.getChunkReference(chunkIndex); + if (chunkReference != null && chunkReference.isValid()) { + BlockChunk blockChunk = chunkStore.getStore().getComponent(chunkReference, BlockChunk.getComponentType()); + if (targetBlockPos.y >= 0 && targetBlockPos.y < 320) { + BlockSection section = blockChunk.getSectionAtBlockY(targetBlockPos.y); + context.getState().blockPosition = new BlockPosition(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z); + context.getState().placedBlockId = section.get(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z); + RotationTuple resultRotation = section.getRotation(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z); + context.getState().blockRotation = new BlockRotation( + resultRotation.yaw().toPacket(), resultRotation.pitch().toPacket(), resultRotation.roll().toPacket() + ); + } + } + } + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.Interaction generatePacket() { + return new com.hypixel.hytale.protocol.SimpleBlockInteraction(); + } + + @Override + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.SimpleBlockInteraction p = (com.hypixel.hytale.protocol.SimpleBlockInteraction)packet; + p.useLatestTarget = this.useLatestTarget; + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Nonnull + @Override + public String toString() { + return "SimpleBlockInteraction{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ToggleGliderInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ToggleGliderInteraction.java new file mode 100644 index 0000000..8c4e696 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/ToggleGliderInteraction.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import javax.annotation.Nonnull; + +public class ToggleGliderInteraction extends SimpleInstantInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ToggleGliderInteraction.class, ToggleGliderInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Toggles Glider movement for the player.") + .build(); + + public ToggleGliderInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.ToggleGliderInteraction(); + } + + @Nonnull + @Override + public String toString() { + return "ToggleGliderInteraction{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/TriggerCooldownInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/TriggerCooldownInteraction.java new file mode 100644 index 0000000..7aa6c8d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/TriggerCooldownInteraction.java @@ -0,0 +1,135 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionCooldown; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TriggerCooldownInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + TriggerCooldownInteraction.class, TriggerCooldownInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Triggers the cooldown as if it was triggered normally.") + .appendInherited( + new KeyedCodec<>("Cooldown", RootInteraction.COOLDOWN_CODEC), + (interaction, s) -> interaction.cooldown = s, + interaction -> interaction.cooldown, + (interaction, parent) -> interaction.next = parent.next + ) + .documentation("The cooldown concerning this interaction, defaulting to the root cooldown if none presented") + .add() + .build(); + @Nullable + private InteractionCooldown cooldown; + + public TriggerCooldownInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + String cooldownId = null; + float cooldownTime = 0.0F; + float[] charges = null; + boolean interruptRecharge = false; + if (this.cooldown != null) { + cooldownId = this.cooldown.cooldownId; + cooldownTime = this.cooldown.cooldown; + charges = this.cooldown.chargeTimes; + interruptRecharge = this.cooldown.interruptRecharge; + } + + resetCooldown(context, cooldownHandler, cooldownId, cooldownTime, charges, interruptRecharge); + } + + protected static void resetCooldown( + @Nonnull InteractionContext context, + @Nonnull CooldownHandler cooldownHandler, + @Nullable String cooldownId, + float cooldownTime, + @Nullable float[] chargeTimes, + boolean interruptRecharge0 + ) { + float time = 0.35F; + float[] charges = InteractionManager.DEFAULT_CHARGE_TIMES; + boolean interruptRecharge = false; + if (cooldownId == null) { + InteractionChain chain = context.getChain(); + + assert chain != null; + + RootInteraction rootInteraction = chain.getInitialRootInteraction(); + InteractionCooldown rootCooldown = rootInteraction.getCooldown(); + if (rootCooldown != null) { + cooldownId = rootCooldown.cooldownId; + if (rootCooldown.cooldown > 0.0F) { + time = rootCooldown.cooldown; + } + + if (rootCooldown.interruptRecharge) { + interruptRecharge = true; + } + + if (rootCooldown.chargeTimes != null && rootCooldown.chargeTimes.length > 0) { + charges = rootCooldown.chargeTimes; + } + } + + if (cooldownId == null) { + cooldownId = rootInteraction.getId(); + } + } + + CooldownHandler.Cooldown possibleCooldown = cooldownHandler.getCooldown(cooldownId); + if (possibleCooldown != null) { + time = possibleCooldown.getCooldown(); + charges = possibleCooldown.getCharges(); + interruptRecharge = possibleCooldown.interruptRecharge(); + } + + if (cooldownTime > 0.0F) { + time = cooldownTime; + } + + if (chargeTimes != null && chargeTimes.length > 0) { + charges = chargeTimes; + } + + if (interruptRecharge0) { + interruptRecharge = true; + } + + CooldownHandler.Cooldown cooldown = cooldownHandler.getCooldown(cooldownId, time, charges, true, interruptRecharge); + cooldown.setCooldownMax(time); + cooldown.setCharges(charges); + cooldown.deductCharge(); + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.TriggerCooldownInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.TriggerCooldownInteraction p = (com.hypixel.hytale.protocol.TriggerCooldownInteraction)packet; + p.cooldown = this.cooldown; + } + + @Nonnull + @Override + public String toString() { + return "TriggerCooldownInteraction{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/UseBlockInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/UseBlockInteraction.java new file mode 100644 index 0000000..bfed64b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/UseBlockInteraction.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.event.events.ecs.UseBlockEvent; +import com.hypixel.hytale.server.core.event.events.entity.LivingEntityUseBlockEvent; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UseBlockInteraction extends SimpleBlockInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + UseBlockInteraction.class, UseBlockInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Attempts to use the target block, executing interactions on it if any.") + .build(); + + public UseBlockInteraction() { + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + doInteraction(type, context, world, targetBlock, true); + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + doInteraction(type, context, world, targetBlock, false); + } + + private static void doInteraction( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull World world, @Nonnull Vector3i targetBlock, boolean fireEvent + ) { + BlockType blockType = world.getBlockType(targetBlock); + String blockTypeInteraction = blockType.getInteractions().get(type); + if (blockTypeInteraction == null) { + context.getState().state = InteractionState.Failed; + } else { + Ref ref = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + if (fireEvent) { + UseBlockEvent.Pre event = new UseBlockEvent.Pre(type, context, targetBlock, blockType); + commandBuffer.invoke(ref, event); + if (event.isCancelled()) { + context.getState().state = InteractionState.Failed; + return; + } + } + + context.getState().state = InteractionState.Finished; + context.execute(RootInteraction.getRootInteractionOrUnknown(blockTypeInteraction)); + if (fireEvent) { + UseBlockEvent.Post event = new UseBlockEvent.Post(type, context, targetBlock, blockType); + commandBuffer.invoke(ref, event); + HytaleServer.get() + .getEventBus() + .dispatchFor(LivingEntityUseBlockEvent.class, world.getName()) + .dispatch(new LivingEntityUseBlockEvent(context.getEntity(), blockType.getId())); + } + } + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.UseBlockInteraction(); + } + + @Nonnull + @Override + public String toString() { + return "UseBlockInteraction{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/UseEntityInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/UseEntityInteraction.java new file mode 100644 index 0000000..518cf18 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/UseEntityInteraction.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.Interactions; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class UseEntityInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + UseEntityInteraction.class, UseEntityInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Attempts to use the target entity, executing interactions on it if any.") + .build(); + + public UseEntityInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Client; + } + + @Override + protected final void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + InteractionSyncData chainData = context.getClientState(); + + assert chainData != null; + + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref targetRef = commandBuffer.getStore().getExternalData().getRefFromNetworkId(chainData.entityId); + if (targetRef != null && targetRef.isValid()) { + Interactions interactionsComponent = commandBuffer.getComponent(targetRef, Interactions.getComponentType()); + if (interactionsComponent != null) { + String interaction = interactionsComponent.getInteractionId(type); + if (interaction == null) { + context.getState().state = InteractionState.Failed; + } else { + context.execute(RootInteraction.getRootInteractionOrUnknown(interaction)); + } + } else { + context.getState().state = InteractionState.Failed; + } + } else { + context.getState().state = InteractionState.Failed; + } + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.UseEntityInteraction(); + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Nonnull + @Override + public String toString() { + return "UseEntityInteraction{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/WieldingInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/WieldingInteraction.java new file mode 100644 index 0000000..f39a2f4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/client/WieldingInteraction.java @@ -0,0 +1,441 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.client; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.map.Object2DoubleMapCodec; +import com.hypixel.hytale.codec.codecs.map.Object2FloatMapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.gameplay.CombatConfig; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.damage.DamageDataComponent; +import com.hypixel.hytale.server.core.entity.effect.ActiveEntityEffect; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.asset.DefaultEntityStatTypes; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.DamageEffects; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.floats.Float2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2DoubleMap; +import it.unimi.dsi.fastutil.ints.Int2DoubleMaps; +import it.unimi.dsi.fastutil.ints.Int2DoubleOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import it.unimi.dsi.fastutil.ints.Int2FloatMaps; +import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2DoubleMap; +import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2DoubleMap.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class WieldingInteraction extends ChargingInteraction { + public static final float WIELDING_INDEX = 0.0F; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + WieldingInteraction.class, WieldingInteraction::new, ChargingInteraction.ABSTRACT_CODEC + ) + .documentation("Interaction that blocks while the key is held and applies various modifiers while active.") + .>appendInherited( + new KeyedCodec<>("KnockbackModifiers", new Object2DoubleMapCodec<>(Codec.STRING, Object2DoubleOpenHashMap::new)), + (damageCalculator, map) -> damageCalculator.knockbackModifiersRaw = map, + damageCalculator -> damageCalculator.knockbackModifiersRaw, + (damageCalculator, parent) -> damageCalculator.knockbackModifiersRaw = parent.knockbackModifiersRaw + ) + .addValidator(DamageCause.VALIDATOR_CACHE.getMapKeyValidator()) + .add() + .>appendInherited( + new KeyedCodec<>("DamageModifiers", new Object2FloatMapCodec<>(Codec.STRING, Object2FloatOpenHashMap::new)), + (damageCalculator, map) -> damageCalculator.damageModifiersRaw = map, + damageCalculator -> damageCalculator.damageModifiersRaw, + (damageCalculator, parent) -> damageCalculator.damageModifiersRaw = parent.damageModifiersRaw + ) + .addValidator(DamageCause.VALIDATOR_CACHE.getMapKeyValidator()) + .add() + .appendInherited( + new KeyedCodec<>("AngledWielding", WieldingInteraction.AngledWielding.CODEC), + (i, o) -> i.angledWielding = o, + i -> i.angledWielding, + (i, parent) -> i.angledWielding = parent.angledWielding + ) + .add() + .appendInherited(new KeyedCodec<>("Next", Interaction.CHILD_ASSET_CODEC), (interaction, s) -> { + interaction.next = new Float2ObjectOpenHashMap<>(); + interaction.next.put(0.0F, s); + }, interaction -> interaction.next != null ? interaction.next.get(0.0F) : null, (interaction, parent) -> interaction.next = parent.next) + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("StaminaCost", WieldingInteraction.StaminaCost.CODEC), + (wieldingInteraction, staminaCost) -> wieldingInteraction.staminaCost = staminaCost, + wieldingInteraction -> wieldingInteraction.staminaCost, + (wieldingInteraction, parent) -> wieldingInteraction.staminaCost = parent.staminaCost + ) + .documentation("Configuration to define how stamina loss is computed.") + .add() + .appendInherited( + new KeyedCodec<>("BlockedEffects", DamageEffects.CODEC), + (wieldingInteraction, interactionEffects) -> wieldingInteraction.blockedEffects = interactionEffects, + wieldingInteraction -> wieldingInteraction.blockedEffects, + (wieldingInteraction, parent) -> wieldingInteraction.blockedEffects = parent.blockedEffects + ) + .add() + .appendInherited( + new KeyedCodec<>("BlockedInteractions", RootInteraction.CHILD_ASSET_CODEC), + (wieldingInteraction, s) -> wieldingInteraction.blockedInteractions = s, + wieldingInteraction -> wieldingInteraction.blockedInteractions, + (wieldingInteraction, parent) -> wieldingInteraction.blockedInteractions = parent.blockedInteractions + ) + .addValidatorLate(() -> RootInteraction.VALIDATOR_CACHE.getValidator().late()) + .add() + .afterDecode(i -> { + i.allowIndefiniteHold = true; + if (i.next != null && i.runTime > 0.0F) { + i.next.put(i.runTime, i.next.get(0.0F)); + } + + if (i.knockbackModifiersRaw != null) { + i.knockbackModifiers = new Int2DoubleOpenHashMap(); + + for (Entry entry : i.knockbackModifiersRaw.object2DoubleEntrySet()) { + int index = DamageCause.getAssetMap().getIndex(entry.getKey()); + i.knockbackModifiers.put(index, entry.getDoubleValue()); + } + } + + if (i.damageModifiersRaw != null) { + i.damageModifiers = new Int2FloatOpenHashMap(); + + for (it.unimi.dsi.fastutil.objects.Object2FloatMap.Entry entry : i.damageModifiersRaw.object2FloatEntrySet()) { + int index = DamageCause.getAssetMap().getIndex(entry.getKey()); + i.damageModifiers.put(index, entry.getFloatValue()); + } + } + }) + .build(); + @Nullable + protected Object2DoubleMap knockbackModifiersRaw; + @Nullable + protected Object2FloatMap damageModifiersRaw; + protected WieldingInteraction.AngledWielding angledWielding; + protected WieldingInteraction.StaminaCost staminaCost; + protected DamageEffects blockedEffects; + protected String blockedInteractions; + @Nonnull + protected transient Int2DoubleMap knockbackModifiers = Int2DoubleMaps.EMPTY_MAP; + @Nonnull + protected transient Int2FloatMap damageModifiers = Int2FloatMaps.EMPTY_MAP; + + public WieldingInteraction() { + } + + @Nonnull + public Int2DoubleMap getKnockbackModifiers() { + return this.knockbackModifiers; + } + + @Nonnull + public Int2FloatMap getDamageModifiers() { + return this.damageModifiers; + } + + public WieldingInteraction.AngledWielding getAngledWielding() { + return this.angledWielding; + } + + public DamageEffects getBlockedEffects() { + return this.blockedEffects; + } + + public WieldingInteraction.StaminaCost getStaminaCost() { + return this.staminaCost; + } + + public String getBlockedInteractions() { + return this.blockedInteractions; + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getEntity(); + World world = commandBuffer.getExternalData().getWorld(); + DamageDataComponent damageDataComponent = commandBuffer.getComponent(ref, DamageDataComponent.getComponentType()); + + assert damageDataComponent != null; + + if (Interaction.failed(context.getState().state)) { + damageDataComponent.setCurrentWielding(null); + } else { + CombatConfig combatConfig = world.getGameplayConfig().getCombatConfig(); + EffectControllerComponent effectControllerComponent = commandBuffer.getComponent(ref, EffectControllerComponent.getComponentType()); + if (effectControllerComponent != null) { + Int2ObjectMap activeEffects = effectControllerComponent.getActiveEffects(); + if (!firstRun && activeEffects.containsKey(combatConfig.getStaminaBrokenEffectIndex())) { + damageDataComponent.setCurrentWielding(null); + context.getState().state = InteractionState.Failed; + if (context.hasLabels()) { + context.jump(context.getLabel(this.next != null ? this.next.size() : 0)); + } + + return; + } + } + + super.tick0(firstRun, time, type, context, cooldownHandler); + if (firstRun && context.getState().state == InteractionState.NotFinished) { + damageDataComponent.setCurrentWielding(this); + } else { + if (context.getState().state == InteractionState.Finished) { + damageDataComponent.setCurrentWielding(null); + } + } + } + } + + @Override + protected void simulateTick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getEntity(); + World world = commandBuffer.getExternalData().getWorld(); + CombatConfig combatConfig = world.getGameplayConfig().getCombatConfig(); + EffectControllerComponent effectControllerComponent = commandBuffer.getComponent(ref, EffectControllerComponent.getComponentType()); + if (effectControllerComponent != null) { + Int2ObjectMap activeEffects = effectControllerComponent.getActiveEffects(); + if (!firstRun && activeEffects.containsKey(combatConfig.getStaminaBrokenEffectIndex())) { + context.getState().state = InteractionState.Failed; + if (context.hasLabels()) { + context.jump(context.getLabel(this.next != null ? this.next.size() : 0)); + } + + return; + } + } + + super.simulateTick0(firstRun, time, type, context, cooldownHandler); + } + + @Override + public void handle(@Nonnull Ref ref, boolean firstRun, float time, @Nonnull InteractionType type, @Nonnull InteractionContext context) { + super.handle(ref, firstRun, time, type, context); + if (context.getState().state != InteractionState.NotFinished) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + DamageDataComponent damageDataComponent = commandBuffer.getComponent(ref, DamageDataComponent.getComponentType()); + + assert damageDataComponent != null; + + damageDataComponent.setCurrentWielding(null); + } + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.Interaction generatePacket() { + return new com.hypixel.hytale.protocol.WieldingInteraction(); + } + + @Override + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.WieldingInteraction p = (com.hypixel.hytale.protocol.WieldingInteraction)packet; + if (this.blockedEffects != null) { + p.blockedEffects = this.blockedEffects.toPacket(); + } + + if (this.angledWielding != null) { + p.angledWielding = this.angledWielding.toPacket(); + } + + p.hasModifiers = this.damageModifiersRaw != null || this.knockbackModifiersRaw != null; + } + + @Nonnull + @Override + public String toString() { + return "WieldingInteraction{knockbackModifiers=" + + this.knockbackModifiersRaw + + ", damageModifiers=" + + this.damageModifiersRaw + + ", angledWielding=" + + this.angledWielding + + ", failed='" + + this.failed + + "', staminaCost=" + + this.staminaCost + + ", blockedEffects=" + + this.blockedEffects + + "} " + + super.toString(); + } + + public static class AngledWielding implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder( + WieldingInteraction.AngledWielding.class, WieldingInteraction.AngledWielding::new + ) + .appendInherited( + new KeyedCodec<>("Angle", Codec.FLOAT), + (o, i) -> o.angleRad = i * (float) (Math.PI / 180.0), + o -> o.angleRad * (180.0F / (float)Math.PI), + (o, p) -> o.angleRad = p.angleRad + ) + .add() + .appendInherited( + new KeyedCodec<>("AngleDistance", Codec.FLOAT), + (o, i) -> o.angleDistanceRad = i * (float) (Math.PI / 180.0), + o -> o.angleDistanceRad * (180.0F / (float)Math.PI), + (o, p) -> o.angleDistanceRad = p.angleDistanceRad + ) + .add() + .>appendInherited( + new KeyedCodec<>("KnockbackModifiers", new Object2DoubleMapCodec<>(Codec.STRING, Object2DoubleOpenHashMap::new)), + (o, m) -> o.knockbackModifiersRaw = m, + o -> o.knockbackModifiersRaw, + (o, p) -> o.knockbackModifiersRaw = p.knockbackModifiersRaw + ) + .addValidator(DamageCause.VALIDATOR_CACHE.getMapKeyValidator()) + .add() + .>appendInherited( + new KeyedCodec<>("DamageModifiers", new Object2FloatMapCodec<>(Codec.STRING, Object2FloatOpenHashMap::new)), + (o, m) -> o.damageModifiersRaw = m, + o -> o.damageModifiersRaw, + (o, p) -> o.damageModifiersRaw = p.damageModifiersRaw + ) + .addValidator(DamageCause.VALIDATOR_CACHE.getMapKeyValidator()) + .add() + .afterDecode(o -> { + if (o.knockbackModifiersRaw != null) { + o.knockbackModifiers = new Int2DoubleOpenHashMap(); + + for (Entry entry : o.knockbackModifiersRaw.object2DoubleEntrySet()) { + int index = DamageCause.getAssetMap().getIndex(entry.getKey()); + o.knockbackModifiers.put(index, entry.getDoubleValue()); + } + } + + if (o.damageModifiersRaw != null) { + o.damageModifiers = new Int2FloatOpenHashMap(); + + for (it.unimi.dsi.fastutil.objects.Object2FloatMap.Entry entry : o.damageModifiersRaw.object2FloatEntrySet()) { + int index = DamageCause.getAssetMap().getIndex(entry.getKey()); + o.damageModifiers.put(index, entry.getFloatValue()); + } + } + }) + .build(); + protected float angleRad; + protected float angleDistanceRad; + @Nullable + protected Object2DoubleMap knockbackModifiersRaw; + @Nullable + protected Object2FloatMap damageModifiersRaw; + @Nonnull + protected transient Int2DoubleMap knockbackModifiers = Int2DoubleMaps.EMPTY_MAP; + @Nonnull + protected transient Int2FloatMap damageModifiers = Int2FloatMaps.EMPTY_MAP; + + public AngledWielding() { + } + + public double getAngleRad() { + return this.angleRad; + } + + public double getAngleDistanceRad() { + return this.angleDistanceRad; + } + + @Nonnull + public Int2DoubleMap getKnockbackModifiers() { + return this.knockbackModifiers; + } + + @Nonnull + public Int2FloatMap getDamageModifiers() { + return this.damageModifiers; + } + + @Nonnull + public com.hypixel.hytale.protocol.AngledWielding toPacket() { + com.hypixel.hytale.protocol.AngledWielding packet = new com.hypixel.hytale.protocol.AngledWielding(); + packet.angleRad = this.angleRad; + packet.angleDistanceRad = this.angleDistanceRad; + packet.hasModifiers = this.damageModifiersRaw != null || this.knockbackModifiersRaw != null; + return packet; + } + } + + public static class StaminaCost { + public static final BuilderCodec CODEC = BuilderCodec.builder( + WieldingInteraction.StaminaCost.class, WieldingInteraction.StaminaCost::new + ) + .append( + new KeyedCodec<>("CostType", new EnumCodec<>(WieldingInteraction.StaminaCost.CostType.class)), + (staminaCost, costType) -> staminaCost.costType = costType, + staminaCost -> staminaCost.costType + ) + .documentation( + "Define how the stamina loss is computed. Use MAX_HEALTH_PERCENTAGE to define how many % of the player's max health 1 stamina point is worth. Use DAMAGE define how much damage 1 stamina point is worth. Default value is MAX_HEALTH_PERCENTAGE." + ) + .add() + .append(new KeyedCodec<>("Value", Codec.FLOAT), (staminaCost, aFloat) -> staminaCost.value = aFloat, staminaCost -> staminaCost.value) + .addValidator(Validators.greaterThanOrEqual(0.0F)) + .documentation( + "The value to define how much a stamina point is worth. When CostType.MAX_HEALTH_PERCENTAGE, a ratio is expected, so for 4% of max health, the value expected here is 0.04. Default value is 0.04f" + ) + .add() + .build(); + private WieldingInteraction.StaminaCost.CostType costType = WieldingInteraction.StaminaCost.CostType.MAX_HEALTH_PERCENTAGE; + private float value = 0.04F; + + public StaminaCost() { + } + + public float computeStaminaAmountToConsume(float damageRaw, @Nonnull EntityStatMap entityStatMap) { + return switch (this.costType) { + case MAX_HEALTH_PERCENTAGE -> damageRaw / (this.value * entityStatMap.get(DefaultEntityStatTypes.getHealth()).getMax()); + case DAMAGE -> damageRaw / this.value; + }; + } + + @Nonnull + @Override + public String toString() { + return "StaminaCost{costType=" + this.costType + ", value=" + this.value + "}"; + } + + static enum CostType { + MAX_HEALTH_PERCENTAGE, + DAMAGE; + + private CostType() { + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/Collector.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/Collector.java new file mode 100644 index 0000000..3fcf43a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/Collector.java @@ -0,0 +1,18 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.data; + +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface Collector { + void start(); + + void into(@Nonnull InteractionContext var1, @Nullable Interaction var2); + + boolean collect(@Nonnull CollectorTag var1, @Nonnull InteractionContext var2, @Nonnull Interaction var3); + + void outof(); + + void finished(); +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/CollectorTag.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/CollectorTag.java new file mode 100644 index 0000000..799569f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/CollectorTag.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.data; + +public interface CollectorTag { + CollectorTag ROOT = new CollectorTag() {}; +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/ListCollector.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/ListCollector.java new file mode 100644 index 0000000..00ba137 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/ListCollector.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.data; + +import com.hypixel.hytale.function.function.TriFunction; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class ListCollector implements Collector { + private final TriFunction function; + private List list; + + public ListCollector(TriFunction function) { + this.function = function; + } + + public List getList() { + return this.list; + } + + @Override + public void start() { + this.list = new ObjectArrayList<>(); + } + + @Override + public void into(@Nonnull InteractionContext context, Interaction interaction) { + } + + @Override + public boolean collect(@Nonnull CollectorTag tag, @Nonnull InteractionContext context, @Nonnull Interaction interaction) { + this.list.add(this.function.apply(tag, context, interaction)); + return false; + } + + @Override + public void outof() { + } + + @Override + public void finished() { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/SingleCollector.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/SingleCollector.java new file mode 100644 index 0000000..f59cab1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/SingleCollector.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.data; + +import com.hypixel.hytale.function.function.TriFunction; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SingleCollector implements Collector { + private final TriFunction function; + @Nullable + private T result; + + public SingleCollector(TriFunction function) { + this.function = function; + } + + @Nullable + public T getResult() { + return this.result; + } + + @Override + public void start() { + this.result = null; + } + + @Override + public void into(@Nonnull InteractionContext context, Interaction interaction) { + } + + @Override + public boolean collect(@Nonnull CollectorTag tag, @Nonnull InteractionContext context, @Nonnull Interaction interaction) { + this.result = this.function.apply(tag, context, interaction); + return this.result != null; + } + + @Override + public void outof() { + } + + @Override + public void finished() { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/StringTag.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/StringTag.java new file mode 100644 index 0000000..72201b6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/StringTag.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.data; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StringTag implements CollectorTag { + private final String tag; + + private StringTag(String tag) { + this.tag = tag; + } + + public String getTag() { + return this.tag; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + StringTag stringTag = (StringTag)o; + return this.tag != null ? this.tag.equals(stringTag.tag) : stringTag.tag == null; + } else { + return false; + } + } + + @Override + public int hashCode() { + return this.tag != null ? this.tag.hashCode() : 0; + } + + @Nonnull + @Override + public String toString() { + return "StringTag{tag='" + this.tag + "'}"; + } + + @Nonnull + public static StringTag of(String tag) { + return new StringTag(tag); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/TreeCollector.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/TreeCollector.java new file mode 100644 index 0000000..ddda985 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/data/TreeCollector.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.data; + +import com.hypixel.hytale.function.function.TriFunction; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class TreeCollector implements Collector { + private final TriFunction function; + private TreeCollector.Node root; + private TreeCollector.Node current; + + public TreeCollector(TriFunction function) { + this.function = function; + } + + public TreeCollector.Node getRoot() { + return this.root; + } + + @Override + public void start() { + this.root = new TreeCollector.Node<>(null); + this.current = this.root; + } + + @Override + public void into(@Nonnull InteractionContext context, Interaction interaction) { + if (this.current.children != null) { + this.current.children = Arrays.copyOf(this.current.children, this.current.children.length + 1); + } else { + this.current.children = new TreeCollector.Node[1]; + } + + this.current = this.current.children[this.current.children.length - 1] = new TreeCollector.Node<>(this.current); + } + + @Override + public boolean collect(@Nonnull CollectorTag tag, @Nonnull InteractionContext context, @Nonnull Interaction interaction) { + this.current.data = this.function.apply(tag, context, interaction); + return false; + } + + @Override + public void outof() { + this.current = this.current.parent; + } + + @Override + public void finished() { + } + + public static class Node { + public static final TreeCollector.Node[] EMPTY_ARRAY = new TreeCollector.Node[0]; + private final TreeCollector.Node parent; + private TreeCollector.Node[] children = EMPTY_ARRAY; + private T data; + + Node(TreeCollector.Node parent) { + this.parent = parent; + } + + public TreeCollector.Node getParent() { + return this.parent; + } + + public TreeCollector.Node[] getChildren() { + return this.children; + } + + public T getData() { + return this.data; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/BuilderToolInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/BuilderToolInteraction.java new file mode 100644 index 0000000..1381d79 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/BuilderToolInteraction.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInteraction; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class BuilderToolInteraction extends SimpleInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + BuilderToolInteraction.class, BuilderToolInteraction::new, SimpleInteraction.CODEC + ) + .documentation("Runs a builder tool") + .build(); + + public BuilderToolInteraction() { + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.BuilderToolInteraction(); + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Client; + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + context.getState().state = context.getClientState().state; + super.tick0(firstRun, time, type, context, cooldownHandler); + } + + @Nonnull + @Override + public String toString() { + return "BuilderToolInteraction{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/CameraInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/CameraInteraction.java new file mode 100644 index 0000000..02896f2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/CameraInteraction.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.protocol.CameraActionType; +import com.hypixel.hytale.protocol.CameraPerspectiveType; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInteraction; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class CameraInteraction extends SimpleInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(CameraInteraction.class, CameraInteraction::new, SimpleInteraction.CODEC) + .documentation("Adjusts the camera of the user.") + .appendInherited( + new KeyedCodec<>("PersistCameraState", Codec.BOOLEAN), + (i, s) -> i.persistCameraState = s, + i -> i.persistCameraState, + (i, parent) -> i.persistCameraState = parent.persistCameraState + ) + .documentation( + "Should the camera state from this interaction persist to the next camera interaction. If the next interaction is null or not a camera interaction then this field does nothing." + ) + .add() + .appendInherited( + new KeyedCodec<>("Action", new EnumCodec<>(CameraActionType.class)), (i, s) -> i.action = s, i -> i.action, (i, parent) -> i.action = parent.action + ) + .documentation("What kind of camera action should we take") + .add() + .appendInherited( + new KeyedCodec<>("Perspective", new EnumCodec<>(CameraPerspectiveType.class)), + (i, s) -> i.perspective = s, + i -> i.perspective, + (i, parent) -> i.perspective = parent.perspective + ) + .documentation("What camera perspective we want this interaction to take place in") + .add() + .appendInherited( + new KeyedCodec<>("CameraInteractionTime", Codec.FLOAT), + (i, s) -> i.cameraInteractionTime = s, + i -> i.cameraInteractionTime, + (i, parent) -> i.cameraInteractionTime = parent.cameraInteractionTime + ) + .documentation("How long this camera action lasts for") + .add() + .build(); + protected CameraActionType action; + protected CameraPerspectiveType perspective; + protected boolean persistCameraState; + protected float cameraInteractionTime; + + public CameraInteraction() { + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.CameraInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.CameraInteraction p = (com.hypixel.hytale.protocol.CameraInteraction)packet; + p.cameraAction = this.action; + p.cameraPerspective = this.perspective; + p.cameraPersist = this.persistCameraState; + p.cameraInteractionTime = this.cameraInteractionTime; + } + + @Nonnull + @Override + public String toString() { + return "CameraInteraction{action=" + + this.action + + ", perspective='" + + this.perspective + + "', persistCameraState='" + + this.persistCameraState + + "', cameraInteractionTime='" + + this.cameraInteractionTime + + "'} " + + super.toString(); + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + super.tick0(firstRun, time, type, context, cooldownHandler); + InteractionSyncData clientState = context.getClientState(); + + assert clientState != null; + + context.getState().state = clientState.state; + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Client; + } + + @Override + public boolean needsRemoteSync() { + return true; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/CancelChainInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/CancelChainInteraction.java new file mode 100644 index 0000000..3703f4d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/CancelChainInteraction.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.ChainingInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class CancelChainInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + CancelChainInteraction.class, CancelChainInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Cancels an active chaining state for the given chain id.") + .appendInherited(new KeyedCodec<>("ChainId", Codec.STRING), (o, i) -> o.chainId = i, o -> o.chainId, (o, p) -> o.chainId = p.chainId) + .documentation("The ID of the chain to cancel.") + .addValidator(Validators.nonNull()) + .add() + .build(); + protected String chainId; + + public CancelChainInteraction() { + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.CancelChainInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.CancelChainInteraction p = (com.hypixel.hytale.protocol.CancelChainInteraction)packet; + p.chainId = this.chainId; + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + } + + @Override + protected void simulateFirstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + Ref ref = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + ChainingInteraction.Data dataComponent = commandBuffer.ensureAndGetComponent(ref, ChainingInteraction.Data.getComponentType()); + dataComponent.getNamedMap().removeInt(this.chainId); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/ChainFlagInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/ChainFlagInteraction.java new file mode 100644 index 0000000..b14947f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/ChainFlagInteraction.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import javax.annotation.Nonnull; + +public class ChainFlagInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ChainFlagInteraction.class, ChainFlagInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Sets a flag on the given chain id that a Chaining interaction can optionally use to adjust what it'll execute.") + .appendInherited(new KeyedCodec<>("ChainId", Codec.STRING), (o, i) -> o.chainId = i, o -> o.chainId, (o, p) -> o.chainId = p.chainId) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Flag", Codec.STRING), (o, i) -> o.flag = i, o -> o.flag, (o, p) -> o.flag = p.flag) + .addValidator(Validators.nonNull()) + .add() + .build(); + protected String chainId; + protected String flag; + + public ChainFlagInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.ChainFlagInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.ChainFlagInteraction p = (com.hypixel.hytale.protocol.ChainFlagInteraction)packet; + p.chainId = this.chainId; + p.flag = this.flag; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/ChangeActiveSlotInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/ChangeActiveSlotInteraction.java new file mode 100644 index 0000000..68301bc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/ChangeActiveSlotInteraction.java @@ -0,0 +1,157 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionCooldown; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.meta.DynamicMetaStore; +import com.hypixel.hytale.server.core.meta.MetaKey; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.Collector; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class ChangeActiveSlotInteraction extends Interaction { + @Nonnull + public static final ChangeActiveSlotInteraction DEFAULT_INTERACTION = new ChangeActiveSlotInteraction("*Change_Active_Slot"); + @Nonnull + public static final RootInteraction DEFAULT_ROOT = new RootInteraction( + "*Default_Swap", + new InteractionCooldown("ChangeActiveSlot", 0.0F, false, InteractionManager.DEFAULT_CHARGE_TIMES, true, false), + DEFAULT_INTERACTION.getId() + ); + @Deprecated + public static final MetaKey PLACE_MOVED_ITEM = CONTEXT_META_REGISTRY.registerMetaObject(i -> null); + private static final int UNSET_INT = Integer.MIN_VALUE; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ChangeActiveSlotInteraction.class, ChangeActiveSlotInteraction::new, Interaction.ABSTRACT_CODEC + ) + .documentation("Changes the active hotbar slot for the user of the interaction.") + .appendInherited( + new KeyedCodec<>("TargetSlot", Codec.INTEGER), + (o, i) -> o.targetSlot = i == null ? Integer.MIN_VALUE : i, + o -> o.targetSlot == Integer.MIN_VALUE ? null : o.targetSlot, + (o, p) -> o.targetSlot = p.targetSlot + ) + .addValidator(Validators.range(0, 8)) + .add() + .afterDecode(i -> i.cancelOnItemChange = false) + .build(); + protected int targetSlot = Integer.MIN_VALUE; + + public ChangeActiveSlotInteraction() { + } + + private ChangeActiveSlotInteraction(@Nonnull String id) { + super(id); + this.cancelOnItemChange = false; + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.None; + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + if (!firstRun) { + context.getState().state = InteractionState.Finished; + } else { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getEntity(); + if (EntityUtils.getEntity(ref, commandBuffer) instanceof LivingEntity livingEntity) { + DynamicMetaStore var15 = context.getMetaStore(); + byte slot; + if (this.targetSlot == Integer.MIN_VALUE) { + slot = var15.getMetaObject(TARGET_SLOT).byteValue(); + } else { + if (livingEntity.getInventory().getActiveHotbarSlot() == this.targetSlot) { + context.getState().state = InteractionState.Finished; + return; + } + + slot = (byte)this.targetSlot; + var15.putMetaObject(TARGET_SLOT, Integer.valueOf(slot)); + } + + livingEntity.getInventory().setActiveHotbarSlot(slot); + Runnable action = var15.removeMetaObject(PLACE_MOVED_ITEM); + if (action != null) { + action.run(); + } + + InteractionManager interactionManager = context.getInteractionManager(); + + assert interactionManager != null; + + InteractionContext forkContext = InteractionContext.forInteraction(interactionManager, ref, InteractionType.SwapTo, commandBuffer); + String forkInteractions = forkContext.getRootInteractionId(InteractionType.SwapTo); + if (forkInteractions != null) { + if (this.targetSlot != Integer.MIN_VALUE) { + forkContext.getMetaStore().putMetaObject(TARGET_SLOT, Integer.valueOf(slot)); + } + + context.fork(InteractionType.SwapTo, forkContext, RootInteraction.getRootInteractionOrUnknown(forkInteractions), action == null); + } + + context.getState().state = InteractionState.Finished; + } + } + } + + @Override + protected void simulateTick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + context.getState().state = context.getServerState().state; + } + + @Override + public boolean walk(@Nonnull Collector collector, @Nonnull InteractionContext context) { + return false; + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.Interaction generatePacket() { + return new com.hypixel.hytale.protocol.ChangeActiveSlotInteraction(); + } + + @Override + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.ChangeActiveSlotInteraction p = (com.hypixel.hytale.protocol.ChangeActiveSlotInteraction)packet; + p.targetSlot = this.targetSlot; + } + + @Nonnull + @Override + public String toString() { + return "ChangeActiveSlotInteraction{targetSlot=" + this.targetSlot + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/ConditionInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/ConditionInteraction.java new file mode 100644 index 0000000..bf25272 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/ConditionInteraction.java @@ -0,0 +1,170 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.MovementStates; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class ConditionInteraction extends SimpleInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ConditionInteraction.class, ConditionInteraction::new, SimpleInteraction.CODEC + ) + .documentation("An interaction that is successful if the given conditions are met.") + .appendInherited( + new KeyedCodec<>("RequiredGameMode", ProtocolCodecs.GAMEMODE), + (interaction, s) -> interaction.requiredGameMode = s, + interaction -> interaction.requiredGameMode, + (interaction, parent) -> interaction.requiredGameMode = parent.requiredGameMode + ) + .add() + .appendInherited( + new KeyedCodec<>("Jumping", Codec.BOOLEAN), + (interaction, s) -> interaction.jumping = s, + interaction -> interaction.jumping, + (interaction, parent) -> interaction.jumping = parent.jumping + ) + .add() + .appendInherited( + new KeyedCodec<>("Swimming", Codec.BOOLEAN), + (interaction, s) -> interaction.swimming = s, + interaction -> interaction.swimming, + (interaction, parent) -> interaction.swimming = parent.swimming + ) + .add() + .appendInherited( + new KeyedCodec<>("Crouching", Codec.BOOLEAN), + (interaction, s) -> interaction.crouching = s, + interaction -> interaction.crouching, + (interaction, parent) -> interaction.crouching = parent.crouching + ) + .add() + .appendInherited( + new KeyedCodec<>("Running", Codec.BOOLEAN), + (interaction, s) -> interaction.running = s, + interaction -> interaction.running, + (interaction, parent) -> interaction.running = parent.running + ) + .add() + .appendInherited( + new KeyedCodec<>("Flying", Codec.BOOLEAN), + (interaction, s) -> interaction.flying = s, + interaction -> interaction.flying, + (interaction, parent) -> interaction.flying = parent.flying + ) + .documentation("Whether the entity can be flying.") + .add() + .build(); + @Nullable + private GameMode requiredGameMode; + @Nullable + private Boolean jumping; + @Nullable + private Boolean swimming; + @Nullable + private Boolean crouching; + @Nullable + private Boolean running; + @Nullable + private Boolean flying; + + public ConditionInteraction() { + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + boolean success = true; + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getEntity(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (this.requiredGameMode != null && playerComponent != null && this.requiredGameMode != playerComponent.getGameMode()) { + success = false; + } + + MovementStatesComponent movementStatesComponent = commandBuffer.getComponent(ref, MovementStatesComponent.getComponentType()); + + assert movementStatesComponent != null; + + MovementStates movementStates = movementStatesComponent.getMovementStates(); + if (this.jumping != null && this.jumping != movementStates.jumping) { + success = false; + } + + if (this.swimming != null && this.swimming != movementStates.swimming) { + success = false; + } + + if (this.crouching != null && this.crouching != movementStates.crouching) { + success = false; + } + + if (this.running != null && this.running != movementStates.running) { + success = false; + } + + if (this.flying != null && this.flying != movementStates.flying) { + success = false; + } + + context.getState().state = success ? InteractionState.Finished : InteractionState.Failed; + super.tick0(firstRun, time, type, context, cooldownHandler); + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.ConditionInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.ConditionInteraction p = (com.hypixel.hytale.protocol.ConditionInteraction)packet; + p.requiredGameMode = this.requiredGameMode; + p.jumping = this.jumping; + p.swimming = this.swimming; + p.crouching = this.crouching; + p.running = this.running; + p.flying = this.flying; + } + + @Nonnull + @Override + public String toString() { + return "ConditionInteraction{requiredGameMode=" + + this.requiredGameMode + + ", jumping=" + + this.jumping + + ", swimming=" + + this.swimming + + ", crouching=" + + this.crouching + + ", running=" + + this.running + + ", flying=" + + this.flying + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/EffectConditionInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/EffectConditionInteraction.java new file mode 100644 index 0000000..5bcd15c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/EffectConditionInteraction.java @@ -0,0 +1,149 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.Match; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.effect.ActiveEntityEffect; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.util.InteractionTarget; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EffectConditionInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + EffectConditionInteraction.class, EffectConditionInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("An interaction that is successful if the given effects exist (or don't) on the target entity.") + .append( + new KeyedCodec<>("EntityEffectIds", Codec.STRING_ARRAY), + (effectConditionInteraction, strings) -> effectConditionInteraction.entityEffectIds = strings, + effectConditionInteraction -> effectConditionInteraction.entityEffectIds + ) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyArray()) + .addValidator(EntityEffect.VALIDATOR_CACHE.getArrayValidator()) + .add() + .append( + new KeyedCodec<>("Match", new EnumCodec<>(Match.class)), + (effectConditionInteraction, aBoolean) -> effectConditionInteraction.match = aBoolean, + effectConditionInteraction -> effectConditionInteraction.match + ) + .documentation( + "Field to specify whether the entity must have the specified effects (All), or must not have the specified effects (None). Default value is: All." + ) + .add() + .appendInherited( + new KeyedCodec<>("Entity", InteractionTarget.CODEC), (o, i) -> o.entityTarget = i, o -> o.entityTarget, (o, p) -> o.entityTarget = p.entityTarget + ) + .documentation("The entity to target for this interaction.") + .addValidator(Validators.nonNull()) + .add() + .afterDecode( + effectConditionInteraction -> effectConditionInteraction.entityEffectIndexes = resolveEntityEffects(effectConditionInteraction.entityEffectIds) + ) + .build(); + protected String[] entityEffectIds; + protected int[] entityEffectIndexes; + @Nonnull + protected Match match = Match.All; + @Nonnull + private InteractionTarget entityTarget = InteractionTarget.USER; + + public EffectConditionInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getEntity(); + Ref targetRef = this.entityTarget.getEntity(context, ref); + if (targetRef != null && targetRef.isValid()) { + EffectControllerComponent effectControllerComponent = commandBuffer.getComponent(targetRef, EffectControllerComponent.getComponentType()); + if (effectControllerComponent != null) { + Int2ObjectMap activeEffects = effectControllerComponent.getActiveEffects(); + + for (int i = 0; i < this.entityEffectIndexes.length; i++) { + switch (this.match) { + case All: + if (!activeEffects.containsKey(this.entityEffectIndexes[i])) { + context.getState().state = InteractionState.Failed; + return; + } + break; + case None: + if (activeEffects.containsKey(this.entityEffectIndexes[i])) { + context.getState().state = InteractionState.Failed; + return; + } + } + } + } + } + } + + private static int[] resolveEntityEffects(@Nullable String[] entityEffectIds) { + if (entityEffectIds == null) { + return ArrayUtil.EMPTY_INT_ARRAY; + } else { + IndexedLookupTableAssetMap entityEffectAssetMap = EntityEffect.getAssetMap(); + int[] entityEffectIndexes = new int[entityEffectIds.length]; + + for (int i = 0; i < entityEffectIds.length; i++) { + entityEffectIndexes[i] = entityEffectAssetMap.getIndex(entityEffectIds[i]); + } + + return entityEffectIndexes; + } + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.EffectConditionInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.EffectConditionInteraction p = (com.hypixel.hytale.protocol.EffectConditionInteraction)packet; + p.entityEffects = this.entityEffectIndexes; + p.match = this.match; + p.entityTarget = this.entityTarget.toProtocol(); + } + + @Nonnull + @Override + public String toString() { + return "EffectConditionInteraction{entityEffectIds=" + + Arrays.toString((Object[])this.entityEffectIds) + + ", entityEffectIndexes=" + + Arrays.toString(this.entityEffectIndexes) + + ", match=" + + this.match + + ", entityTarget=" + + this.entityTarget + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/ParallelInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/ParallelInteraction.java new file mode 100644 index 0000000..d5d737b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/ParallelInteraction.java @@ -0,0 +1,156 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.Collector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.CollectorTag; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class ParallelInteraction extends Interaction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ParallelInteraction.class, ParallelInteraction::new, Interaction.ABSTRACT_CODEC + ) + .documentation("Runs the provided interactions in parallel to this interaction chain.") + .appendInherited( + new KeyedCodec<>("Interactions", new ArrayCodec<>(RootInteraction.CHILD_ASSET_CODEC, String[]::new)), + (i, s) -> i.interactions = s, + i -> i.interactions, + (i, parent) -> i.interactions = parent.interactions + ) + .documentation("The collection of interaction roots to run in parallel via forks.") + .addValidator(Validators.nonNull()) + .addValidatorLate(() -> RootInteraction.VALIDATOR_CACHE.getArrayValidator().late()) + .addValidator(Validators.arraySizeRange(2, Integer.MAX_VALUE)) + .add() + .build(); + protected String[] interactions; + + public ParallelInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.None; + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + IndexedLookupTableAssetMap assetMap = RootInteraction.getAssetMap(); + context.execute(RootInteraction.getRootInteractionOrUnknown(this.interactions[0])); + + for (int i = 1; i < this.interactions.length; i++) { + String interaction = this.interactions[i]; + context.fork(context.duplicate(), RootInteraction.getRootInteractionOrUnknown(interaction), true); + } + + context.getState().state = InteractionState.Finished; + } + + @Override + protected void simulateTick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + IndexedLookupTableAssetMap assetMap = RootInteraction.getAssetMap(); + context.execute(RootInteraction.getRootInteractionOrUnknown(this.interactions[0])); + context.getState().state = InteractionState.Finished; + } + + @Override + public boolean walk(@Nonnull Collector collector, @Nonnull InteractionContext context) { + for (int i = 0; i < this.interactions.length; i++) { + String root = this.interactions[i]; + if (InteractionManager.walkInteractions( + collector, context, ParallelInteraction.ParallelTag.of(i), RootInteraction.getRootInteractionOrUnknown(root).getInteractionIds() + )) { + return true; + } + } + + return false; + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.Interaction generatePacket() { + return new com.hypixel.hytale.protocol.ParallelInteraction(); + } + + @Override + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.ParallelInteraction p = (com.hypixel.hytale.protocol.ParallelInteraction)packet; + int[] chainingNext = p.next = new int[this.interactions.length]; + + for (int i = 0; i < this.interactions.length; i++) { + chainingNext[i] = RootInteraction.getRootInteractionIdOrUnknown(this.interactions[i]); + } + } + + @Nonnull + @Override + public String toString() { + return "ParallelInteraction{interactions=" + Arrays.toString((Object[])this.interactions) + "} " + super.toString(); + } + + private static class ParallelTag implements CollectorTag { + private final int index; + + private ParallelTag(int index) { + this.index = index; + } + + public int getIndex() { + return this.index; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + ParallelInteraction.ParallelTag that = (ParallelInteraction.ParallelTag)o; + return this.index == that.index; + } else { + return false; + } + } + + @Override + public int hashCode() { + return this.index; + } + + @Nonnull + @Override + public String toString() { + return "ParallelTag{index=" + this.index + "}"; + } + + @Nonnull + public static ParallelInteraction.ParallelTag of(int index) { + return new ParallelInteraction.ParallelTag(index); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/RepeatInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/RepeatInteraction.java new file mode 100644 index 0000000..4b91977 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/RepeatInteraction.java @@ -0,0 +1,166 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.meta.DynamicMetaStore; +import com.hypixel.hytale.server.core.meta.MetaKey; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.Collector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.StringTag; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class RepeatInteraction extends SimpleInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder(RepeatInteraction.class, RepeatInteraction::new, SimpleInteraction.CODEC) + .documentation( + "Forks from the current interaction into one or more chains that run the specified interactions.\n\nWhen run this will create a new chain that will run the interactions specified in `ForkInteractions`. This will then wait until that chain completes. If the chain completes successfully it will then check the `Repeat` field to see if it needs to run again, if not then the interactions `Next` are run otherwise this repeats with the next fork. If the chain fails then any repeating is ignored and the interactions `Failed` are run instead." + ) + .appendInherited( + new KeyedCodec<>("ForkInteractions", RootInteraction.CHILD_ASSET_CODEC), + (i, s) -> i.forkInteractions = s, + i -> i.forkInteractions, + (i, parent) -> i.forkInteractions = parent.forkInteractions + ) + .documentation("The interactions to run in the forks created by this interaction.") + .addValidator(Validators.nonNull()) + .addValidatorLate(() -> RootInteraction.VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited(new KeyedCodec<>("Repeat", Codec.INTEGER), (i, s) -> i.repeat = s, i -> i.repeat, (i, parent) -> i.repeat = parent.repeat) + .documentation("The number of times to repeat. -1 is considered as infinite, be careful when using this value.") + .addValidator(Validators.or(Validators.greaterThanOrEqual(1), Validators.equal(-1))) + .add() + .build(); + private static final MetaKey FORKED_CHAIN = Interaction.META_REGISTRY.registerMetaObject(i -> null); + private static final MetaKey REMAINING_REPEATS = Interaction.META_REGISTRY.registerMetaObject(i -> null); + private static final StringTag TAG_FORK = StringTag.of("Fork"); + private static final StringTag TAG_NEXT = StringTag.of("Next"); + private static final StringTag TAG_FAILED = StringTag.of("Failed"); + protected String forkInteractions; + protected int repeat = 1; + + public RepeatInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.None; + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + DynamicMetaStore instanceStore = context.getInstanceStore(); + if (firstRun && this.repeat != -1) { + instanceStore.putMetaObject(REMAINING_REPEATS, this.repeat); + } + + InteractionChain chain = instanceStore.getMetaObject(FORKED_CHAIN); + if (chain != null) { + switch (chain.getServerState()) { + case NotFinished: + context.getState().state = InteractionState.NotFinished; + return; + case Finished: + if (this.repeat != -1 && instanceStore.getMetaObject(REMAINING_REPEATS) <= 0) { + context.getState().state = InteractionState.Finished; + super.tick0(firstRun, time, type, context, cooldownHandler); + return; + } + + context.getState().state = InteractionState.NotFinished; + break; + case Failed: + context.getState().state = InteractionState.Failed; + super.tick0(firstRun, time, type, context, cooldownHandler); + return; + } + } + + chain = context.fork(context.duplicate(), RootInteraction.getRootInteractionOrUnknown(this.forkInteractions), true); + instanceStore.putMetaObject(FORKED_CHAIN, chain); + context.getState().state = InteractionState.NotFinished; + if (this.repeat != -1) { + instanceStore.putMetaObject(REMAINING_REPEATS, instanceStore.getMetaObject(REMAINING_REPEATS) - 1); + } + } + + @Override + protected void simulateTick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + InteractionChain chain = context.getInstanceStore().getMetaObject(FORKED_CHAIN); + DynamicMetaStore instanceStore = context.getInstanceStore(); + if (chain != null) { + switch (chain.getServerState()) { + case NotFinished: + context.getState().state = InteractionState.NotFinished; + break; + case Finished: + if (this.repeat != -1 && instanceStore.getMetaObject(REMAINING_REPEATS) <= 0) { + context.getState().state = InteractionState.Finished; + super.simulateTick0(firstRun, time, type, context, cooldownHandler); + } else { + context.getState().state = InteractionState.NotFinished; + } + break; + case Failed: + context.getState().state = InteractionState.Failed; + super.simulateTick0(firstRun, time, type, context, cooldownHandler); + } + } else { + context.getState().state = InteractionState.NotFinished; + } + } + + @Override + public boolean walk(@Nonnull Collector collector, @Nonnull InteractionContext context) { + if (this.forkInteractions != null + && InteractionManager.walkInteractions( + collector, context, TAG_FORK, RootInteraction.getRootInteractionOrUnknown(this.forkInteractions).getInteractionIds() + )) { + return true; + } else { + return this.next != null && InteractionManager.walkInteraction(collector, context, TAG_NEXT, this.next) + ? true + : this.failed != null && InteractionManager.walkInteraction(collector, context, TAG_FAILED, this.failed); + } + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.Interaction generatePacket() { + return new com.hypixel.hytale.protocol.RepeatInteraction(); + } + + @Override + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.RepeatInteraction p = (com.hypixel.hytale.protocol.RepeatInteraction)packet; + p.forkInteractions = RootInteraction.getRootInteractionIdOrUnknown(this.forkInteractions); + p.repeat = this.repeat; + } + + @Nonnull + @Override + public String toString() { + return "RepeatInteraction{forkInteractions='" + this.forkInteractions + "', repeat=" + this.repeat + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/ReplaceInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/ReplaceInteraction.java new file mode 100644 index 0000000..fde0c9d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/ReplaceInteraction.java @@ -0,0 +1,159 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.Collector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.StringTag; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class ReplaceInteraction extends Interaction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ReplaceInteraction.class, ReplaceInteraction::new, Interaction.ABSTRACT_CODEC + ) + .documentation("Runs the interaction defined by the interaction variables if defined.") + .appendInherited( + new KeyedCodec<>("DefaultValue", RootInteraction.CHILD_ASSET_CODEC), + (i, s) -> i.defaultValue = s, + i -> i.defaultValue, + (i, parent) -> i.defaultValue = parent.defaultValue + ) + .addValidatorLate(() -> RootInteraction.VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited(new KeyedCodec<>("Var", Codec.STRING), (i, s) -> i.variable = s, i -> i.variable, (i, parent) -> i.variable = parent.variable) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("DefaultOk", Codec.BOOLEAN), (i, s) -> i.defaultOk = s, i -> i.defaultOk, (i, parent) -> i.defaultOk = parent.defaultOk) + .add() + .build(); + private static final StringTag TAG_DEFAULT = StringTag.of("Default"); + private static final StringTag TAG_VARS = StringTag.of("Vars"); + @Nullable + protected String defaultValue; + protected String variable; + protected boolean defaultOk; + + public ReplaceInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.None; + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + if (!Interaction.failed(context.getState().state)) { + if (firstRun) { + this.doReplace(context, true); + } + } + } + + @Override + protected void simulateTick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + if (!Interaction.failed(context.getState().state)) { + if (firstRun) { + this.doReplace(context, false); + } + } + } + + private void doReplace(@Nonnull InteractionContext context, boolean log) { + Map vars = context.getInteractionVars(); + String next = vars == null ? null : vars.get(this.variable); + if (next == null && !this.defaultOk && log) { + HytaleLogger.getLogger() + .at(Level.SEVERE) + .atMostEvery(1, TimeUnit.MINUTES) + .log("Missing replacement interactions for interaction: %s for var %s on item %s", this.id, this.variable, context.getHeldItem()); + } + + if (next == null) { + next = this.defaultValue; + } + + if (next == null) { + context.getState().state = InteractionState.Failed; + } else { + RootInteraction nextInteraction = RootInteraction.getRootInteractionOrUnknown(next); + context.getState().state = InteractionState.Finished; + context.execute(nextInteraction); + } + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Override + public boolean walk(@Nonnull Collector collector, @Nonnull InteractionContext context) { + if (this.defaultValue != null + && InteractionManager.walkInteractions( + collector, context, TAG_DEFAULT, RootInteraction.getRootInteractionOrUnknown(this.defaultValue).getInteractionIds() + )) { + return true; + } else { + Map vars = context.getInteractionVars(); + if (vars == null) { + return false; + } else { + String interactionIds = vars.get(this.variable); + return interactionIds == null + ? false + : InteractionManager.walkInteractions( + collector, context, TAG_VARS, RootInteraction.getRootInteractionOrUnknown(interactionIds).getInteractionIds() + ); + } + } + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.Interaction generatePacket() { + return new com.hypixel.hytale.protocol.ReplaceInteraction(); + } + + @Override + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.ReplaceInteraction p = (com.hypixel.hytale.protocol.ReplaceInteraction)packet; + p.defaultValue = RootInteraction.getRootInteractionIdOrUnknown(this.defaultValue); + p.variable = this.variable; + } + + @Nonnull + @Override + public String toString() { + return "ReplaceInteraction{defaultValue='" + + this.defaultValue + + "', variable='" + + this.variable + + "', defaultOk=" + + this.defaultOk + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/RunRootInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/RunRootInteraction.java new file mode 100644 index 0000000..a1cc2cc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/RunRootInteraction.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import javax.annotation.Nonnull; + +public class RunRootInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + RunRootInteraction.class, RunRootInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Runs the given interaction root.") + .appendInherited( + new KeyedCodec<>("RootInteraction", Codec.STRING), + (o, i) -> o.rootInteraction = i, + o -> o.rootInteraction, + (o, p) -> o.rootInteraction = p.rootInteraction + ) + .documentation("A reference to a root interaction to run") + .addValidator(Validators.nonNull()) + .addValidatorLate(() -> RootInteraction.VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + protected String rootInteraction; + + public RunRootInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + context.getState().state = InteractionState.Finished; + context.execute(RootInteraction.getRootInteractionOrUnknown(this.rootInteraction)); + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.RunRootInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.RunRootInteraction p = (com.hypixel.hytale.protocol.RunRootInteraction)packet; + p.rootInteraction = RootInteraction.getRootInteractionIdOrUnknown(this.rootInteraction); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/SelectInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/SelectInteraction.java new file mode 100644 index 0000000..d53fe9c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/SelectInteraction.java @@ -0,0 +1,405 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.EntityMatcherType; +import com.hypixel.hytale.protocol.FailOnType; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.InteractionChainData; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.SelectedHitEntity; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.EntitySnapshot; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.meta.DynamicMetaStore; +import com.hypixel.hytale.server.core.meta.MetaKey; +import com.hypixel.hytale.server.core.modules.entity.component.Invulnerable; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector.ClientSourcedSelector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector.Selector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector.SelectorType; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.PositionUtil; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class SelectInteraction extends SimpleInteraction { + public static boolean SHOW_VISUAL_DEBUG; + public static SelectInteraction.SnapshotSource SNAPSHOT_SOURCE = SelectInteraction.SnapshotSource.CLIENT; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(SelectInteraction.class, SelectInteraction::new, SimpleInteraction.CODEC) + .documentation( + "An interaction that can be used to find entities/blocks within a given area.\n\nThis runs the given `Selector` every tick this interactions runs for, the selector may change the search area over time (based on `RunTime`). e.g. to trace out an arc of a sword swing.\n\nWhen an entity/block is found this interaction will run a set of interactions (as defined by `HitEntity`/`HitBlock`) **per a entity/block**, this will not interrupt the selector and it will continue searching until the select interaction completes.\n\nThis interaction does not wait for any forked interaction chains from `HitEntity`/`HitBlock` to complete before finishing itself." + ) + .appendInherited( + new KeyedCodec<>("Selector", SelectorType.CODEC), (i, o) -> i.selector = o, i -> i.selector, (i, p) -> i.selector = p.selector + ) + .documentation("The selector to use to find entities and blocks in an area.\nThe selector may be spread over the duration `RunTime`.") + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("HitEntity", RootInteraction.CHILD_ASSET_CODEC), (o, i) -> o.hitEntity = i, o -> o.hitEntity, (o, p) -> o.hitEntity = p.hitEntity + ) + .documentation( + "The interactions to fork into when an entity is hit by the selector.\nThe hit entity will be the target of the interaction chain.\n\nAn entity cannot be hit multiple times by a single selector." + ) + .addValidatorLate(() -> RootInteraction.VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("HitEntityRules", new ArrayCodec<>(SelectInteraction.HitEntity.CODEC, SelectInteraction.HitEntity[]::new)), + (o, i) -> o.hitEntityRules = i, + o -> o.hitEntityRules, + (o, p) -> o.hitEntityRules = p.hitEntityRules + ) + .documentation("Tests any hit entity with the given rules, running a fork for the last one matched.\nThis overrides `HitEntity` if any match.") + .add() + .appendInherited( + new KeyedCodec<>("HitBlock", RootInteraction.CHILD_ASSET_CODEC), (o, i) -> o.hitBlock = i, o -> o.hitBlock, (o, p) -> o.hitBlock = p.hitBlock + ) + .documentation( + "The interactions to fork into when a block is hit by the selector.\nThe hit block will be the target of the interaction chain.\n\nA block cannot be hit multiple times by a single selector." + ) + .addValidatorLate(() -> RootInteraction.VALIDATOR_CACHE.getValidator().late()) + .add() + .append(new KeyedCodec<>("FailOn", new EnumCodec<>(FailOnType.class)), (o, v) -> o.failOn = v, o -> o.failOn) + .documentation("Changes what causes the Failed case to run") + .add() + .appendInherited( + new KeyedCodec<>("IgnoreOwner", Codec.BOOLEAN), + (activationEffects, s) -> activationEffects.ignoreOwner = s, + activationEffects -> activationEffects.ignoreOwner, + (activationEffects, parent) -> activationEffects.ignoreOwner = parent.ignoreOwner + ) + .documentation( + "Determines whether the owner of the affiliated entity should be ignored in the selection.\n\nFor example, ignoring the thrower of a projectile." + ) + .add() + .build(); + public static final MetaKey HIT_ENTITIES = META_REGISTRY.registerMetaObject(i -> new IntOpenHashSet()); + public static final MetaKey> HIT_BLOCKS = META_REGISTRY.registerMetaObject(i -> new HashSet<>()); + public static final MetaKey> SELECT_META_STORE = CONTEXT_META_REGISTRY.registerMetaObject(data -> null); + private static final MetaKey ENTITY_SELECTOR = META_REGISTRY.registerMetaObject(data -> null); + protected SelectorType selector; + protected String hitEntity; + protected SelectInteraction.HitEntity[] hitEntityRules; + protected String hitBlock; + protected FailOnType failOn = FailOnType.Neither; + protected boolean ignoreOwner = true; + + public SelectInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Client; + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + Ref ref = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + if (firstRun) { + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + Selector selector = this.selector.newSelector(); + if (playerComponent != null && SNAPSHOT_SOURCE == SelectInteraction.SnapshotSource.CLIENT) { + selector = new ClientSourcedSelector(selector, context); + } + + context.getInstanceStore().putMetaObject(ENTITY_SELECTOR, selector); + if ((playerComponent == null || SNAPSHOT_SOURCE != SelectInteraction.SnapshotSource.CLIENT) && time <= 0.0F && this.getRunTime() > 0.0F) { + return; + } + } + + Player playerComponentx = commandBuffer.getComponent(ref, Player.getComponentType()); + World world = commandBuffer.getExternalData().getWorld(); + Selector selectorx = context.getInstanceStore().getMetaObject(ENTITY_SELECTOR); + selectorx.tick(commandBuffer, context.getEntity(), Math.min(time, this.getRunTime()), this.getRunTime()); + boolean checkEntities = this.hitEntity != null || this.hitEntityRules != null; + if (checkEntities) { + IntSet hitEntities = context.getInstanceStore().getMetaObject(HIT_ENTITIES); + selectorx.selectTargetEntities(commandBuffer, context.getEntity(), (targetRef, hit) -> { + NetworkId networkIdComponent = targetRef.getStore().getComponent(targetRef, NetworkId.getComponentType()); + if (networkIdComponent != null) { + int networkId = networkIdComponent.getId(); + if (hitEntities.add(networkId)) { + String hitEntity = this.hitEntity; + if (hitEntity != null) { + Archetype archetype = commandBuffer.getArchetype(targetRef); + boolean targetDead = archetype.contains(DeathComponent.getComponentType()); + boolean targetInvulnerable = archetype.contains(Invulnerable.getComponentType()); + if (targetInvulnerable) { + Player targetPlayerComponent = commandBuffer.getComponent(targetRef, Player.getComponentType()); + if (targetPlayerComponent != null && targetPlayerComponent.getGameMode() == GameMode.Creative) { + PlayerSettings playerSettingsComponent = commandBuffer.getComponent(targetRef, PlayerSettings.getComponentType()); + if (playerSettingsComponent != null && playerSettingsComponent.creativeSettings().respondToHit()) { + targetInvulnerable = false; + } + } + } + + if (targetDead || targetInvulnerable || targetRef.equals(ref)) { + hitEntity = null; + } + } + + if (this.hitEntityRules != null) { + label57: + for (SelectInteraction.HitEntity rule : this.hitEntityRules) { + for (SelectInteraction.EntityMatcher matcher : rule.matchers) { + if (!matcher.test(ref, targetRef, commandBuffer)) { + continue label57; + } + } + + hitEntity = rule.next; + } + } + + if (hitEntity != null) { + RootInteraction hitEntityInteraction = RootInteraction.getRootInteractionOrUnknown(hitEntity); + InteractionContext subCtx = context.duplicate(); + DynamicMetaStore metaStore = subCtx.getMetaStore(); + metaStore.putMetaObject(TARGET_ENTITY, targetRef); + metaStore.putMetaObject(HIT_LOCATION, hit); + metaStore.putMetaObject(SELECT_META_STORE, context.getInstanceStore()); + metaStore.removeMetaObject(TARGET_BLOCK); + metaStore.removeMetaObject(TARGET_BLOCK_RAW); + if (playerComponent != null && SNAPSHOT_SOURCE == SelectInteraction.SnapshotSource.CLIENT) { + InteractionSyncData currentState = context.getClientState(); + subCtx.setSnapshotProvider((cBuffer, attacker, targetNetworkId) -> { + int attackerNetworkId = cBuffer.getComponent(attacker, NetworkId.getComponentType()).getId(); + if (targetNetworkId == attackerNetworkId) { + return new EntitySnapshot(PositionUtil.toVector3d(currentState.attackerPos), PositionUtil.toRotation(currentState.attackerRot)); + } else { + for (SelectedHitEntity e : currentState.hitEntities) { + if (e.networkId == targetNetworkId) { + return new EntitySnapshot(PositionUtil.toVector3d(e.position), PositionUtil.toRotation(e.bodyRotation)); + } + } + + throw new IllegalArgumentException("No entity " + targetNetworkId + " in client state"); + } + }); + } + + context.fork(new InteractionChainData(), context.getChain().getType(), subCtx, hitEntityInteraction, false); + } + } + } + }, e -> this.ignoreOwner && e.equals(ref) ? false : !e.equals(context.getEntity())); + if (context.hasLabels() + && hitEntities.isEmpty() + && context.getState().state == InteractionState.Finished + && (this.failOn == FailOnType.Entity || this.failOn == FailOnType.Either)) { + context.getState().state = InteractionState.Failed; + } + } + + if (this.hitBlock != null) { + Set hitBlocks = context.getInstanceStore().getMetaObject(HIT_BLOCKS); + RootInteraction hitBlock = RootInteraction.getRootInteractionOrUnknown(this.hitBlock); + selectorx.selectTargetBlocks(commandBuffer, context.getEntity(), (x, y, z) -> { + BlockPosition rawBlock = new BlockPosition(x, y, z); + BlockPosition targetBlock = world.getBaseBlock(rawBlock); + if (hitBlocks.add(targetBlock)) { + InteractionContext subCtx = context.duplicate(); + DynamicMetaStore metaStore = subCtx.getMetaStore(); + metaStore.putMetaObject(TARGET_BLOCK, targetBlock); + metaStore.putMetaObject(TARGET_BLOCK_RAW, rawBlock); + metaStore.putMetaObject(SELECT_META_STORE, context.getInstanceStore()); + metaStore.removeMetaObject(TARGET_ENTITY); + context.fork(new InteractionChainData(), context.getChain().getType(), subCtx, hitBlock, false); + } + }); + if (context.hasLabels() + && hitBlocks.isEmpty() + && context.getState().state == InteractionState.Finished + && (this.failOn == FailOnType.Block || this.failOn == FailOnType.Either)) { + context.getState().state = InteractionState.Failed; + } + } + + if (playerComponentx != null && SNAPSHOT_SOURCE == SelectInteraction.SnapshotSource.CLIENT && context.getState().state != InteractionState.Failed) { + context.getState().state = context.getClientState().state; + } + + super.tick0(firstRun, time, type, context, cooldownHandler); + } + + @Override + protected void simulateTick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @NonNullDecl InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + } + + @Nullable + @Override + public InteractionChain mapForkChain(@Nonnull InteractionContext context, @Nonnull InteractionChainData data) { + if (data.blockPosition != null) { + return null; + } else { + Long2ObjectMap chains = context.getChain().getForkedChains(); + + for (InteractionChain chain : chains.values()) { + if (chain.getBaseForkedChainId().entryIndex == context.getEntry().getIndex()) { + InteractionChainData otherData = chain.getChainData(); + if (otherData.entityId == data.entityId) { + return chain; + } + } + } + + return null; + } + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.Interaction generatePacket() { + return new com.hypixel.hytale.protocol.SelectInteraction(); + } + + @Override + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.SelectInteraction p = (com.hypixel.hytale.protocol.SelectInteraction)packet; + p.hitEntity = RootInteraction.getRootInteractionIdOrUnknown(this.hitEntity); + p.failOn = this.failOn; + p.ignoreOwner = this.ignoreOwner; + p.selector = this.selector.toPacket(); + if (this.hitEntityRules != null) { + com.hypixel.hytale.protocol.HitEntity[] protoHits = new com.hypixel.hytale.protocol.HitEntity[this.hitEntityRules.length]; + + for (int i = 0; i < this.hitEntityRules.length; i++) { + protoHits[i] = this.hitEntityRules[i].toPacket(); + } + + p.hitEntityRules = protoHits; + } + } + + @Nonnull + @Override + public String toString() { + return "SelectInteraction{selector=" + + this.selector + + ", hitEntity='" + + this.hitEntity + + "', hitBlock='" + + this.hitBlock + + "', ignoreOwner='" + + this.ignoreOwner + + "'} " + + super.toString(); + } + + public abstract static class EntityMatcher implements NetworkSerializable { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(SelectInteraction.EntityMatcher.class) + .appendInherited(new KeyedCodec<>("Invert", Codec.BOOLEAN), (o, i) -> o.invert = i, o -> o.invert, (o, p) -> o.invert = p.invert) + .documentation("Inverts the result of the matcher") + .add() + .build(); + protected boolean invert; + + public EntityMatcher() { + } + + public final boolean test(Ref attacker, Ref target, CommandBuffer commandBuffer) { + return this.test0(attacker, target, commandBuffer) ^ this.invert; + } + + public abstract boolean test0(Ref var1, Ref var2, CommandBuffer var3); + + @Nonnull + public com.hypixel.hytale.protocol.EntityMatcher toPacket() { + com.hypixel.hytale.protocol.EntityMatcher packet = new com.hypixel.hytale.protocol.EntityMatcher(); + packet.type = EntityMatcherType.Server; + packet.invert = this.invert; + return packet; + } + } + + public static class HitEntity implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SelectInteraction.HitEntity.class, SelectInteraction.HitEntity::new + ) + .appendInherited(new KeyedCodec<>("Next", RootInteraction.CHILD_ASSET_CODEC), (o, i) -> o.next = i, o -> o.next, (o, p) -> o.next = p.next) + .addValidatorLate(() -> RootInteraction.VALIDATOR_CACHE.getValidator().late()) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("Matchers", new ArrayCodec<>(SelectInteraction.EntityMatcher.CODEC, SelectInteraction.EntityMatcher[]::new)), + (o, i) -> o.matchers = i, + o -> o.matchers, + (o, p) -> o.matchers = p.matchers + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + protected String next; + protected SelectInteraction.EntityMatcher[] matchers; + + public HitEntity() { + } + + @Nonnull + public com.hypixel.hytale.protocol.HitEntity toPacket() { + com.hypixel.hytale.protocol.EntityMatcher[] protoMatchers = new com.hypixel.hytale.protocol.EntityMatcher[this.matchers.length]; + + for (int i = 0; i < this.matchers.length; i++) { + protoMatchers[i] = this.matchers[i].toPacket(); + } + + return new com.hypixel.hytale.protocol.HitEntity(RootInteraction.getRootInteractionIdOrUnknown(this.next), protoMatchers); + } + } + + public static enum SnapshotSource { + SERVER, + @Deprecated + CLIENT; + + private SnapshotSource() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/SerialInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/SerialInteraction.java new file mode 100644 index 0000000..579bbc8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/SerialInteraction.java @@ -0,0 +1,140 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.Collector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.CollectorTag; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.OperationsBuilder; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class SerialInteraction extends Interaction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + SerialInteraction.class, SerialInteraction::new, BuilderCodec.abstractBuilder(Interaction.class).build() + ) + .documentation("Runs the given interactions in order.") + .appendInherited( + new KeyedCodec<>("Interactions", CHILD_ASSET_CODEC_ARRAY), + (o, i) -> o.interactions = i, + o -> o.interactions, + (o, p) -> o.interactions = p.interactions + ) + .documentation("A list of interactions to run. They will be executed in the order specified sequentially.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonNullArrayElements()) + .add() + .build(); + protected String[] interactions; + + public SerialInteraction() { + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @NonNullDecl InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + throw new IllegalStateException("Should not be reached"); + } + + @Override + protected void simulateTick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @NonNullDecl InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + throw new IllegalStateException("Should not be reached"); + } + + @Override + public boolean walk(@Nonnull Collector collector, @Nonnull InteractionContext context) { + for (int i = 0; i < this.interactions.length; i++) { + if (InteractionManager.walkInteraction(collector, context, SerialInteraction.SerialTag.of(i), this.interactions[i])) { + return true; + } + } + + return false; + } + + @Override + public void compile(@Nonnull OperationsBuilder builder) { + for (String interaction : this.interactions) { + Interaction.getInteractionOrUnknown(interaction).compile(builder); + } + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.Interaction generatePacket() { + return new com.hypixel.hytale.protocol.SerialInteraction(); + } + + @Override + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.SerialInteraction p = (com.hypixel.hytale.protocol.SerialInteraction)packet; + int[] serialInteractions = p.serialInteractions = new int[this.interactions.length]; + + for (int i = 0; i < this.interactions.length; i++) { + serialInteractions[i] = Interaction.getInteractionIdOrUnknown(this.interactions[i]); + } + } + + @Override + public boolean needsRemoteSync() { + return false; + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.None; + } + + private static class SerialTag implements CollectorTag { + private final int index; + + private SerialTag(int index) { + this.index = index; + } + + public int getIndex() { + return this.index; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + SerialInteraction.SerialTag that = (SerialInteraction.SerialTag)o; + return this.index == that.index; + } else { + return false; + } + } + + @Override + public int hashCode() { + return this.index; + } + + @Nonnull + @Override + public String toString() { + return "SerialTag{index=" + this.index + "}"; + } + + @Nonnull + public static SerialInteraction.SerialTag of(int index) { + return new SerialInteraction.SerialTag(index); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/StatsConditionBaseInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/StatsConditionBaseInteraction.java new file mode 100644 index 0000000..e6b8b24 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/StatsConditionBaseInteraction.java @@ -0,0 +1,109 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.map.Object2FloatMapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.ValueType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class StatsConditionBaseInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.abstractBuilder( + StatsConditionBaseInteraction.class, SimpleInstantInteraction.CODEC + ) + .appendInherited( + new KeyedCodec<>("Costs", new Object2FloatMapCodec<>(Codec.STRING, Object2FloatOpenHashMap::new)), + (i, s) -> i.rawCosts = s, + i -> i.rawCosts, + (i, parent) -> i.rawCosts = parent.rawCosts + ) + .addValidator(Validators.nonNull()) + .addValidator(EntityStatType.VALIDATOR_CACHE.getMapKeyValidator()) + .add() + .appendInherited( + new KeyedCodec<>("LessThan", Codec.BOOLEAN), + (statsConditionInteraction, aBoolean) -> statsConditionInteraction.lessThan = aBoolean, + statsConditionInteraction -> statsConditionInteraction.lessThan, + (statsConditionInteraction, parent) -> statsConditionInteraction.lessThan = parent.lessThan + ) + .add() + .appendInherited( + new KeyedCodec<>("Lenient", Codec.BOOLEAN), + (statsConditionInteraction, aBoolean) -> statsConditionInteraction.lenient = aBoolean, + statsConditionInteraction -> statsConditionInteraction.lenient, + (statsConditionInteraction, parent) -> statsConditionInteraction.lenient = parent.lenient + ) + .documentation("Specifies that the interaction can run even if the stat cost is not met, providing that the value is greater than zero.") + .add() + .appendInherited( + new KeyedCodec<>("ValueType", new EnumCodec<>(ValueType.class)), + (statsConditionInteraction, valueType) -> statsConditionInteraction.valueType = valueType, + statsConditionInteraction -> statsConditionInteraction.valueType, + (statsConditionInteraction, parent) -> statsConditionInteraction.valueType = parent.valueType + ) + .documentation( + "Enum to specify if the Costs must be considered as absolute values or percent. Default value is Absolute. When using ValueType.Absolute, '100' matches the max value." + ) + .add() + .afterDecode(c -> c.costs = EntityStatsModule.resolveEntityStats(c.rawCosts)) + .build(); + protected Object2FloatMap rawCosts; + @Nullable + protected Int2FloatMap costs; + protected boolean lessThan; + protected boolean lenient; + protected ValueType valueType = ValueType.Absolute; + + public StatsConditionBaseInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + Ref ref = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + if (!this.canAfford(ref, commandBuffer)) { + context.getState().state = InteractionState.Failed; + } + } + + protected abstract boolean canAfford(@Nonnull Ref var1, @Nonnull ComponentAccessor var2); + + protected boolean canOverdraw(float value, float min) { + return this.lenient && value > 0.0F && min < 0.0F; + } + + @Nonnull + @Override + public String toString() { + return "StatsConditionBaseInteraction{rawCosts=" + + this.rawCosts + + ", costs=" + + this.costs + + ", lessThan=" + + this.lessThan + + ", lenient=" + + this.lenient + + ", valueType=" + + this.valueType + + "}" + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/StatsConditionInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/StatsConditionInteraction.java new file mode 100644 index 0000000..5c2e7d8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/StatsConditionInteraction.java @@ -0,0 +1,91 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.ValueType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2FloatMap.Entry; +import javax.annotation.Nonnull; + +public class StatsConditionInteraction extends StatsConditionBaseInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + StatsConditionInteraction.class, StatsConditionInteraction::new, StatsConditionBaseInteraction.CODEC + ) + .documentation("Interaction that is successful if the given stat conditions match.") + .build(); + + public StatsConditionInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getEntity(); + if (!this.canAfford(ref, commandBuffer)) { + context.getState().state = InteractionState.Failed; + } + } + + @Override + protected boolean canAfford(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + EntityStatMap entityStatMapComponent = componentAccessor.getComponent(ref, EntityStatMap.getComponentType()); + if (entityStatMapComponent == null) { + return false; + } else if (this.costs == null) { + return false; + } else { + for (Entry cost : this.costs.int2FloatEntrySet()) { + EntityStatValue stat = entityStatMapComponent.get(cost.getIntKey()); + if (stat == null) { + return false; + } + + float statValue = this.valueType == ValueType.Absolute ? stat.get() : stat.asPercentage() * 100.0F; + if (this.lessThan) { + if (statValue >= cost.getFloatValue()) { + return false; + } + } else if (statValue < cost.getFloatValue() && !this.canOverdraw(statValue, stat.getMin())) { + return false; + } + } + + return true; + } + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.StatsConditionInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.StatsConditionInteraction p = (com.hypixel.hytale.protocol.StatsConditionInteraction)packet; + p.costs = this.costs; + p.lessThan = this.lessThan; + p.lenient = this.lenient; + p.valueType = this.valueType; + } + + @Nonnull + @Override + public String toString() { + return "StatsConditionInteraction{}" + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/StatsConditionWithModifierInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/StatsConditionWithModifierInteraction.java new file mode 100644 index 0000000..77baa63 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/StatsConditionWithModifierInteraction.java @@ -0,0 +1,133 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.ValueType; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemArmor; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.StaticModifier; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2FloatMap.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StatsConditionWithModifierInteraction extends StatsConditionBaseInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + StatsConditionWithModifierInteraction.class, StatsConditionWithModifierInteraction::new, StatsConditionBaseInteraction.CODEC + ) + .documentation("Interaction that is successful if the given stat conditions match.") + .append( + new KeyedCodec<>("InteractionModifierId", new EnumCodec<>(ItemArmor.InteractionModifierId.class)), + (changeStatWithModifierInteraction, s) -> changeStatWithModifierInteraction.interactionModifierId = s, + changeStatWithModifierInteraction -> changeStatWithModifierInteraction.interactionModifierId + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + protected ItemArmor.InteractionModifierId interactionModifierId; + + public StatsConditionWithModifierInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getEntity(); + if (!this.canAfford(ref, commandBuffer)) { + context.getState().state = InteractionState.Failed; + } + } + + @Override + protected boolean canAfford(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + EntityStatMap entityStatMapComponent = componentAccessor.getComponent(ref, EntityStatMap.getComponentType()); + if (entityStatMapComponent == null) { + return false; + } else if (this.costs == null) { + return false; + } else { + for (Entry cost : this.costs.int2FloatEntrySet()) { + EntityStatValue stat = entityStatMapComponent.get(cost.getIntKey()); + if (stat == null) { + return false; + } + + float statValue = this.valueType == ValueType.Absolute ? stat.get() : stat.asPercentage() * 100.0F; + Inventory inventory = null; + if (EntityUtils.getEntity(ref, componentAccessor) instanceof LivingEntity livingEntity) { + inventory = livingEntity.getInventory(); + } + + float modifiedCost = this.calculateDiscount(inventory, cost.getIntKey(), cost.getFloatValue()); + if (this.lessThan) { + if (statValue >= modifiedCost) { + return false; + } + } else if (statValue < modifiedCost && !this.canOverdraw(statValue, stat.getMin())) { + return false; + } + } + + return true; + } + } + + private float calculateDiscount(@Nullable Inventory inventory, int statIndex, float baseCost) { + float flatModifier = 0.0F; + float multiplierModifier = 0.0F; + ItemContainer armorContainer = null; + if (inventory != null) { + armorContainer = inventory.getArmor(); + } + + if (armorContainer != null) { + for (short i = 0; i < armorContainer.getCapacity(); i++) { + ItemStack itemStack = armorContainer.getItemStack(i); + if (itemStack != null && !itemStack.isEmpty()) { + Item item = itemStack.getItem(); + if (item != null && item.getArmor() != null) { + Int2ObjectMap statModifierMap = item.getArmor().getInteractionModifier(this.interactionModifierId.toString()); + if (statModifierMap != null) { + StaticModifier statModifier = statModifierMap.get(statIndex); + if (statModifier.getCalculationType() == StaticModifier.CalculationType.ADDITIVE) { + flatModifier += statModifier.getAmount(); + } else { + multiplierModifier = statModifier.getAmount(); + } + } + } + } + } + } + + float modifiedCost = baseCost + flatModifier; + return modifiedCost * Math.max(0.0F, 1.0F - multiplierModifier); + } + + @Nonnull + @Override + public String toString() { + return "StatsConditionWithModifierInteraction{interactionModifierId=" + this.interactionModifierId + "}" + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/simple/ApplyEffectInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/simple/ApplyEffectInteraction.java new file mode 100644 index 0000000..2219065 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/simple/ApplyEffectInteraction.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.simple; + +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.util.InteractionTarget; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ApplyEffectInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ApplyEffectInteraction.class, ApplyEffectInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Applies the given entity effect to the entity.") + .appendInherited( + new KeyedCodec<>("EffectId", new ContainedAssetCodec<>(EntityEffect.class, EntityEffect.CODEC)), + (interaction, s) -> interaction.effectId = s, + interaction -> interaction.effectId, + (interaction, parent) -> interaction.effectId = parent.effectId + ) + .addValidator(Validators.nonNull()) + .addValidatorLate(() -> EntityEffect.VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("Entity", InteractionTarget.CODEC), (o, i) -> o.entityTarget = i, o -> o.entityTarget, (o, p) -> o.entityTarget = p.entityTarget + ) + .documentation("The entity to target for this interaction.") + .addValidator(Validators.nonNull()) + .add() + .build(); + private String effectId; + @Nonnull + private InteractionTarget entityTarget = InteractionTarget.USER; + + public ApplyEffectInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + if (this.effectId != null) { + EntityEffect entityEffect = EntityEffect.getAssetMap().getAsset(this.effectId); + if (entityEffect != null) { + Ref ref = context.getEntity(); + Ref targetRef = this.entityTarget.getEntity(context, ref); + if (targetRef != null && targetRef.isValid()) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + EffectControllerComponent effectControllerComponent = commandBuffer.getComponent(targetRef, EffectControllerComponent.getComponentType()); + if (effectControllerComponent != null) { + effectControllerComponent.addEffect(targetRef, entityEffect, commandBuffer); + } + } + } + } + } + + @Override + protected void simulateFirstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.ApplyEffectInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.ApplyEffectInteraction p = (com.hypixel.hytale.protocol.ApplyEffectInteraction)packet; + p.effectId = EntityEffect.getAssetMap().getIndex(this.effectId); + p.entityTarget = this.entityTarget.toProtocol(); + } + + @Nonnull + @Override + public String toString() { + return "ApplyEffectInteraction{effectId='" + this.effectId + "', entityTarget=" + this.entityTarget + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/simple/RemoveEntityInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/simple/RemoveEntityInteraction.java new file mode 100644 index 0000000..ab0627d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/simple/RemoveEntityInteraction.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.simple; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.util.InteractionTarget; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class RemoveEntityInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + RemoveEntityInteraction.class, RemoveEntityInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Despawns the given entity.") + .appendInherited( + new KeyedCodec<>("Entity", InteractionTarget.CODEC), (o, i) -> o.entityTarget = i, o -> o.entityTarget, (o, p) -> o.entityTarget = p.entityTarget + ) + .documentation("The entity to target for this interaction.") + .addValidator(Validators.nonNull()) + .add() + .build(); + @Nonnull + private InteractionTarget entityTarget = InteractionTarget.USER; + + public RemoveEntityInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getEntity(); + Ref targetRef = this.entityTarget.getEntity(context, ref); + if (targetRef != null && targetRef.isValid()) { + if (!commandBuffer.getArchetype(targetRef).contains(Player.getComponentType())) { + World world = commandBuffer.getExternalData().getWorld(); + world.execute(() -> { + if (targetRef.isValid()) { + Store store = world.getEntityStore().getStore(); + store.removeEntity(targetRef, RemoveReason.REMOVE); + } + }); + } + } + } + + @Override + protected void simulateFirstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.RemoveEntityInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.RemoveEntityInteraction p = (com.hypixel.hytale.protocol.RemoveEntityInteraction)packet; + p.entityTarget = this.entityTarget.toProtocol(); + } + + @Nonnull + @Override + public String toString() { + return "RemoveEntityInteraction{entityTarget=" + this.entityTarget + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/simple/SendMessageInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/simple/SendMessageInteraction.java new file mode 100644 index 0000000..ded48bc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/none/simple/SendMessageInteraction.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.simple; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.receiver.IMessageReceiver; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class SendMessageInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + SendMessageInteraction.class, SendMessageInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Debug interaction that sends a message on use.") + .appendInherited( + new KeyedCodec<>("Message", Codec.STRING), + (interaction, s) -> interaction.message = s, + interaction -> interaction.message, + (interaction, parent) -> interaction.message = parent.message + ) + .add() + .appendInherited(new KeyedCodec<>("Key", Codec.STRING), (o, v) -> o.key = v, o -> o.key, (o, p) -> o.key = p.key) + .add() + .build(); + private String key; + private String message; + + public SendMessageInteraction(@Nonnull String id, @Nonnull String message) { + super(id); + this.message = message; + this.unknown = true; + } + + public SendMessageInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getOwningEntity(); + Entity entity = EntityUtils.getEntity(ref, commandBuffer); + if (entity instanceof IMessageReceiver messageReceiver) { + if (this.key != null) { + messageReceiver.sendMessage(Message.translation(this.key)); + } else { + messageReceiver.sendMessage(Message.raw(this.message)); + } + } else { + HytaleLogger.getLogger().at(Level.INFO).log("SendMessageInteraction: %s for %s", this.message != null ? this.message : this.key, entity); + } + } + + @Nonnull + @Override + public String toString() { + return "SendMessageInteraction{message=" + this.message + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/AOECircleSelector.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/AOECircleSelector.java new file mode 100644 index 0000000..7628131 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/AOECircleSelector.java @@ -0,0 +1,137 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.function.consumer.TriIntConsumer; +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.math.vector.Vector4d; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.SelectInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.function.BiConsumer; +import java.util.function.Predicate; +import javax.annotation.Nonnull; + +public class AOECircleSelector extends SelectorType { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(AOECircleSelector.class, AOECircleSelector::new, BASE_CODEC) + .documentation("A selector that selects all entities within a given range.") + .appendInherited( + new KeyedCodec<>("Range", Codec.FLOAT), + (aoeCircleEntitySelector, d) -> aoeCircleEntitySelector.range = d, + aoeCircleEntitySelector -> aoeCircleEntitySelector.range, + (aoeCircleSelector, parent) -> aoeCircleSelector.range = parent.range + ) + .addValidator(Validators.max(30.0F)) + .documentation("The range of the area to search for targets in.") + .add() + .append( + new KeyedCodec<>("Offset", Vector3d.CODEC), + (aoeCircleEntitySelector, i) -> aoeCircleEntitySelector.offset = i, + aoeCircleEntitySelector -> aoeCircleEntitySelector.offset + ) + .documentation("The offset of the area to search for targets in.") + .add() + .build(); + @Nonnull + private final AOECircleSelector.RuntimeSelector instance = new AOECircleSelector.RuntimeSelector(); + protected float range; + @Nonnull + protected Vector3d offset = Vector3d.ZERO; + + public AOECircleSelector() { + } + + @Nonnull + @Override + public Selector newSelector() { + return this.instance; + } + + public com.hypixel.hytale.protocol.Selector toPacket() { + return new com.hypixel.hytale.protocol.AOECircleSelector(this.range, this.getOffset()); + } + + @Nonnull + public Vector3f getOffset() { + return new Vector3f((float)this.offset.x, (float)this.offset.y, (float)this.offset.z); + } + + @Nonnull + public Vector3d selectTargetPosition(@Nonnull CommandBuffer commandBuffer, @Nonnull Ref attackerRef) { + TransformComponent transformComponent = commandBuffer.getComponent(attackerRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + if (this.offset.x != 0.0 || this.offset.y != 0.0 || this.offset.z != 0.0) { + HeadRotation headRotationComponent = commandBuffer.getComponent(attackerRef, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + position = this.offset.clone(); + position.rotateY(headRotationComponent.getRotation().getYaw()); + position.add(transformComponent.getPosition()); + } + + return position; + } + + private class RuntimeSelector implements Selector { + private RuntimeSelector() { + } + + @Override + public void tick(@Nonnull CommandBuffer commandBuffer, @Nonnull Ref ref, float time, float runTime) { + if (SelectInteraction.SHOW_VISUAL_DEBUG) { + Vector3d position = AOECircleSelector.this.selectTargetPosition(commandBuffer, ref); + com.hypixel.hytale.math.vector.Vector3f color = new com.hypixel.hytale.math.vector.Vector3f( + (float)HashUtil.random(ref.getIndex(), this.hashCode(), 10L), + (float)HashUtil.random(ref.getIndex(), this.hashCode(), 11L), + (float)HashUtil.random(ref.getIndex(), this.hashCode(), 12L) + ); + DebugUtils.addSphere(commandBuffer.getExternalData().getWorld(), position, color, AOECircleSelector.this.range * 2.0F, 5.0F); + } + } + + @Override + public void selectTargetEntities( + @Nonnull CommandBuffer commandBuffer, + @Nonnull Ref attacker, + @Nonnull BiConsumer, Vector4d> consumer, + Predicate> filter + ) { + Vector3d position = AOECircleSelector.this.selectTargetPosition(commandBuffer, attacker); + Selector.selectNearbyEntities( + commandBuffer, position, (double)AOECircleSelector.this.range, entity -> consumer.accept(entity, Vector4d.newPosition(position)), filter + ); + } + + @Override + public void selectTargetBlocks(@Nonnull CommandBuffer commandBuffer, @Nonnull Ref attacker, @Nonnull TriIntConsumer consumer) { + Vector3d position = AOECircleSelector.this.selectTargetPosition(commandBuffer, attacker); + int radius = (int)AOECircleSelector.this.range; + + for (int x = -radius; x <= radius; x++) { + for (int y = -radius; y <= radius; y++) { + for (int z = -radius; z <= radius; z++) { + double d2 = x * x + y * y + z * z; + if (d2 <= radius * radius) { + Vector3i p = new Vector3i((int)(position.x + x) - 1, (int)(position.y + y), (int)(position.z + z)); + consumer.accept(p.x, p.y, p.z); + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/AOECylinderSelector.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/AOECylinderSelector.java new file mode 100644 index 0000000..ae537ad --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/AOECylinderSelector.java @@ -0,0 +1,134 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.function.consumer.TriIntConsumer; +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector4d; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.component.Invulnerable; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.SelectInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import java.util.function.BiConsumer; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AOECylinderSelector extends AOECircleSelector { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + AOECylinderSelector.class, AOECylinderSelector::new, AOECircleSelector.CODEC + ) + .documentation("A selector that selects all entities within a given range and height.") + .append( + new KeyedCodec<>("Height", Codec.FLOAT), + (aoeCircleEntitySelector, d) -> aoeCircleEntitySelector.height = d, + aoeCircleEntitySelector -> aoeCircleEntitySelector.height + ) + .documentation("The height of the area to search for targets in from the entity position.") + .add() + .build(); + private final AOECylinderSelector.RuntimeSelector instance = new AOECylinderSelector.RuntimeSelector(); + protected float height; + + public AOECylinderSelector() { + } + + @Nonnull + @Override + public Selector newSelector() { + return this.instance; + } + + @Override + public com.hypixel.hytale.protocol.Selector toPacket() { + return new com.hypixel.hytale.protocol.AOECylinderSelector(this.range, this.height, this.getOffset()); + } + + private class RuntimeSelector implements Selector { + private RuntimeSelector() { + } + + @Override + public void tick(@Nonnull CommandBuffer commandBuffer, @Nonnull Ref ref, float time, float runTime) { + if (SelectInteraction.SHOW_VISUAL_DEBUG) { + Vector3d position = AOECylinderSelector.this.selectTargetPosition(commandBuffer, ref); + Vector3f color = new Vector3f( + (float)HashUtil.random(ref.getIndex(), this.hashCode(), 10L), + (float)HashUtil.random(ref.getIndex(), this.hashCode(), 11L), + (float)HashUtil.random(ref.getIndex(), this.hashCode(), 12L) + ); + DebugUtils.addSphere(commandBuffer.getExternalData().getWorld(), position, color, AOECylinderSelector.this.range * 2.0F, 5.0F); + } + } + + @Override + public void selectTargetEntities( + @Nonnull CommandBuffer commandBuffer, + @Nonnull Ref ref, + @Nonnull BiConsumer, Vector4d> consumer, + @Nullable Predicate> filter + ) { + Vector3d position = AOECylinderSelector.this.selectTargetPosition(commandBuffer, ref); + Vector3d min = new Vector3d(position.x - AOECylinderSelector.this.range, position.y, position.z - AOECylinderSelector.this.range); + Vector3d max = new Vector3d( + position.x + AOECylinderSelector.this.range, position.y + AOECylinderSelector.this.height, position.z + AOECylinderSelector.this.range + ); + + for (Ref targetRef : TargetUtil.getAllEntitiesInBox(min, max, commandBuffer)) { + if (targetRef.isValid() && !targetRef.equals(ref) && (filter == null || filter.test(targetRef))) { + Archetype archetype = commandBuffer.getArchetype(targetRef); + boolean isDead = archetype.contains(DeathComponent.getComponentType()); + boolean isInvulnerable = archetype.contains(Invulnerable.getComponentType()); + if (!isDead && !isInvulnerable) { + TransformComponent targetEntityTransformComponent = commandBuffer.getComponent(targetRef, TransformComponent.getComponentType()); + + assert targetEntityTransformComponent != null; + + Vector3d pos = targetEntityTransformComponent.getPosition(); + double dx = pos.x - position.x; + double dy = pos.y - position.y; + double dz = pos.z - position.z; + if (dx * dx + dz * dz <= AOECylinderSelector.this.range * AOECylinderSelector.this.range + && dy <= AOECylinderSelector.this.height + && dy >= 0.0) { + consumer.accept(targetRef, new Vector4d(position.x, position.y, position.z, 1.0)); + } + } + } + } + } + + @Override + public void selectTargetBlocks(@Nonnull CommandBuffer commandBuffer, @Nonnull Ref ref, @Nonnull TriIntConsumer consumer) { + Vector3d position = AOECylinderSelector.this.selectTargetPosition(commandBuffer, ref); + int xStart = MathUtil.floor(-AOECylinderSelector.this.range); + int yStart = 0; + int zStart = MathUtil.floor(-AOECylinderSelector.this.range); + int xEnd = MathUtil.floor(AOECylinderSelector.this.range); + int yEnd = MathUtil.floor(AOECylinderSelector.this.height); + int zEnd = MathUtil.floor(AOECylinderSelector.this.range); + float squaredRange = AOECylinderSelector.this.range * AOECylinderSelector.this.range; + + for (int x = xStart; x < xEnd; x++) { + for (int y = yStart; y < yEnd; y++) { + for (int z = zStart; z < zEnd; z++) { + if (x * x + z * z <= squaredRange) { + consumer.accept(MathUtil.floor(position.x + x), MathUtil.floor(position.y + y), MathUtil.floor(position.z + z)); + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/ClientSourcedSelector.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/ClientSourcedSelector.java new file mode 100644 index 0000000..7697b99 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/ClientSourcedSelector.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector; + +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.function.consumer.TriIntConsumer; +import com.hypixel.hytale.math.vector.Vector4d; +import com.hypixel.hytale.protocol.SelectedHitEntity; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.function.BiConsumer; +import java.util.function.Predicate; +import javax.annotation.Nonnull; + +@Deprecated +public class ClientSourcedSelector implements Selector { + private final Selector parent; + private final InteractionContext context; + + public ClientSourcedSelector(Selector parent, InteractionContext context) { + this.parent = parent; + this.context = context; + } + + @Override + public void tick(@Nonnull CommandBuffer commandBuffer, @Nonnull Ref ref, float time, float runTime) { + this.parent.tick(commandBuffer, ref, time, runTime); + } + + @Override + public void selectTargetEntities( + @Nonnull CommandBuffer commandBuffer, + @Nonnull Ref ref, + @Nonnull BiConsumer, Vector4d> consumer, + Predicate> filter + ) { + SelectedHitEntity[] hits = this.context.getClientState().hitEntities; + if (hits != null) { + EntityStore store = commandBuffer.getStore().getExternalData(); + + for (SelectedHitEntity info : hits) { + Ref targetRef = store.getRefFromNetworkId(info.networkId); + if (targetRef != null) { + consumer.accept(targetRef, new Vector4d(info.hitLocation.x, info.hitLocation.y, info.hitLocation.z, 0.0)); + } + } + } + } + + @Override + public void selectTargetBlocks(@Nonnull CommandBuffer commandBuffer, @Nonnull Ref ref, @Nonnull TriIntConsumer consumer) { + this.parent.selectTargetBlocks(commandBuffer, ref, consumer); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/HorizontalSelector.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/HorizontalSelector.java new file mode 100644 index 0000000..0c37a64 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/HorizontalSelector.java @@ -0,0 +1,304 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.function.consumer.TriIntConsumer; +import com.hypixel.hytale.math.hitdetection.HitDetectionExecutor; +import com.hypixel.hytale.math.hitdetection.LineOfSightProvider; +import com.hypixel.hytale.math.hitdetection.projection.FrustumProjectionProvider; +import com.hypixel.hytale.math.hitdetection.view.DirectionViewProvider; +import com.hypixel.hytale.math.iterator.BlockIterator; +import com.hypixel.hytale.math.matrix.Matrix4d; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector4d; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.protocol.HorizontalSelectorDirection; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.SelectInteraction; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.accessor.LocalCachedChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.function.BiConsumer; +import java.util.function.Predicate; +import javax.annotation.Nonnull; + +public class HorizontalSelector extends SelectorType { + public static final BuilderCodec CODEC = BuilderCodec.builder(HorizontalSelector.class, HorizontalSelector::new, BASE_CODEC) + .documentation("A selector that swings in a horizontal arc over a given period of time.") + .appendInherited( + new KeyedCodec<>("Length", Codec.DOUBLE), + (selector, value) -> selector.yawLength = Math.toRadians(value), + selector -> Math.toDegrees(selector.yawLength), + (selector, parent) -> selector.yawLength = parent.yawLength + ) + .documentation("The angle length in degrees that the arc will cover.") + .add() + .appendInherited( + new KeyedCodec<>("Direction", HorizontalSelector.Direction.CODEC), + (selector, value) -> selector.direction = value, + selector -> selector.direction, + (selector, parent) -> selector.direction = parent.direction + ) + .documentation("The direction the swing should travel in.") + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("StartDistance", Codec.DOUBLE), + (selector, value) -> selector.startDistance = value, + selector -> selector.startDistance, + (selector, parent) -> selector.startDistance = parent.startDistance + ) + .documentation("The distance from the entity that the selector starts its search from.") + .add() + .appendInherited( + new KeyedCodec<>("EndDistance", Codec.DOUBLE), + (selector, value) -> selector.endDistance = value, + selector -> selector.endDistance, + (selector, parent) -> selector.endDistance = parent.endDistance + ) + .documentation("The distance from the entity that the selector ends its search at.") + .add() + .appendInherited( + new KeyedCodec<>("ExtendTop", Codec.DOUBLE), + (selector, value) -> selector.extendTop = value, + selector -> selector.extendTop, + (selector, parent) -> selector.extendTop = parent.extendTop + ) + .documentation("The amount to extend the top of the selector by.") + .add() + .appendInherited( + new KeyedCodec<>("ExtendBottom", Codec.DOUBLE), + (selector, value) -> selector.extendBottom = value, + selector -> selector.extendBottom, + (selector, parent) -> selector.extendBottom = parent.extendBottom + ) + .documentation("The amount to extend the bottom of the selector by.") + .add() + .appendInherited( + new KeyedCodec<>("YawStartOffset", Codec.DOUBLE), + (selector, value) -> selector.yawStartOffset = Math.toRadians(value), + selector -> Math.toDegrees(selector.yawStartOffset), + (selector, parent) -> selector.yawStartOffset = parent.yawStartOffset + ) + .documentation("The yaw rotation offset in degrees for this selector") + .add() + .appendInherited( + new KeyedCodec<>("PitchOffset", Codec.DOUBLE), + (selector, value) -> selector.pitchOffset = Math.toRadians(value), + selector -> Math.toDegrees(selector.pitchOffset), + (selector, parent) -> selector.pitchOffset = parent.pitchOffset + ) + .documentation("The pitch rotation offset in degrees for this selector") + .add() + .appendInherited( + new KeyedCodec<>("RollOffset", Codec.DOUBLE), + (selector, value) -> selector.rollOffset = Math.toRadians(value), + selector -> Math.toDegrees(selector.rollOffset), + (selector, parent) -> selector.rollOffset = parent.rollOffset + ) + .documentation("The roll rotation offset in degrees for this selector") + .add() + .appendInherited( + new KeyedCodec<>("TestLineOfSight", Codec.BOOLEAN), + (selector, value) -> selector.testLineOfSight = value, + selector -> selector.testLineOfSight, + (selector, parent) -> selector.testLineOfSight = parent.testLineOfSight + ) + .documentation("Whether to test for line of sight between the user and the target before counting a hit") + .add() + .build(); + protected double extendTop = 1.0; + protected double extendBottom = 1.0; + protected double yawLength; + protected double yawStartOffset; + protected double pitchOffset; + protected double rollOffset; + protected double startDistance = 0.01; + protected double endDistance; + protected HorizontalSelector.Direction direction; + protected boolean testLineOfSight = true; + + public HorizontalSelector() { + } + + @Nonnull + @Override + public Selector newSelector() { + return new HorizontalSelector.RuntimeSelector(); + } + + public com.hypixel.hytale.protocol.Selector toPacket() { + com.hypixel.hytale.protocol.HorizontalSelector selector = new com.hypixel.hytale.protocol.HorizontalSelector(); + selector.extendTop = (float)this.extendTop; + selector.extendBottom = (float)this.extendBottom; + selector.yawLength = (float)this.yawLength; + selector.yawStartOffset = (float)this.yawStartOffset; + selector.pitchOffset = (float)this.pitchOffset; + selector.rollOffset = (float)this.rollOffset; + selector.startDistance = (float)this.startDistance; + selector.endDistance = (float)this.endDistance; + + selector.direction = switch (this.direction) { + case TO_RIGHT -> HorizontalSelectorDirection.ToRight; + case TO_LEFT -> HorizontalSelectorDirection.ToLeft; + }; + selector.testLineOfSight = this.testLineOfSight; + return selector; + } + + public static enum Direction { + TO_RIGHT(-1.0), + TO_LEFT(1.0); + + public static final EnumCodec CODEC = new EnumCodec<>(HorizontalSelector.Direction.class) + .documentKey(TO_LEFT, "A arc that starts at the right and moves towards the left.") + .documentKey(TO_RIGHT, "A arc that starts at the left and moves towards the right."); + private final double yawModifier; + + private Direction(double yawModifier) { + this.yawModifier = yawModifier; + } + } + + private class RuntimeSelector implements Selector { + @Nonnull + protected HitDetectionExecutor executor = new HitDetectionExecutor(); + @Nonnull + protected Matrix4d modelMatrix = new Matrix4d(); + @Nonnull + protected FrustumProjectionProvider projectionProvider = new FrustumProjectionProvider(); + @Nonnull + protected DirectionViewProvider viewProvider = new DirectionViewProvider(); + protected float lastTime = 0.0F; + protected double runTimeDeltaPercentageSum; + + private RuntimeSelector() { + } + + @Override + public void tick(@Nonnull CommandBuffer commandBuffer, @Nonnull Ref attacker, float time, float runTime) { + float yOffset = commandBuffer.getComponent(attacker, ModelComponent.getComponentType()).getModel().getEyeHeight(attacker, commandBuffer); + Vector3d position = commandBuffer.getComponent(attacker, TransformComponent.getComponentType()).getPosition(); + HeadRotation look = commandBuffer.getComponent(attacker, HeadRotation.getComponentType()); + double posX = position.getX(); + double posY = position.getY() + yOffset; + double posZ = position.getZ(); + float delta = time - this.lastTime; + this.lastTime = time; + float runTimeDeltaPercentage = delta / runTime; + double stretchFactor = HorizontalSelector.this.startDistance / HorizontalSelector.this.endDistance; + double yawDelta = HorizontalSelector.this.yawLength * runTimeDeltaPercentage; + double yawDeltaSum = HorizontalSelector.this.yawLength * this.runTimeDeltaPercentageSum; + double yawArcLength = 2.0 * HorizontalSelector.this.endDistance * yawDelta / (float) Math.PI; + double yawOffset = (yawDeltaSum + yawDelta + HorizontalSelector.this.yawStartOffset) * HorizontalSelector.this.direction.yawModifier; + double extendHorizontal = yawArcLength * stretchFactor; + this.projectionProvider + .setNear(HorizontalSelector.this.startDistance) + .setFar(HorizontalSelector.this.endDistance) + .setLeft(extendHorizontal) + .setRight(extendHorizontal) + .setBottom(HorizontalSelector.this.extendBottom * stretchFactor) + .setRotation(yawOffset, HorizontalSelector.this.pitchOffset, HorizontalSelector.this.rollOffset) + .setTop(HorizontalSelector.this.extendTop * stretchFactor); + this.viewProvider.setPosition(posX, posY, posZ).setDirection(look.getRotation().getYaw(), look.getRotation().getPitch()); + this.executor.setOrigin(posX, posY, posZ).setProjectionProvider(this.projectionProvider).setViewProvider(this.viewProvider); + if (HorizontalSelector.this.testLineOfSight) { + LineOfSightProvider provider = (fromX, fromY, fromZ, toX, toY, toZ) -> { + LocalCachedChunkAccessor localAccessor = LocalCachedChunkAccessor.atWorldCoords( + commandBuffer.getStore().getExternalData().getWorld(), (int)fromX, (int)fromZ, (int)(HorizontalSelector.this.endDistance + 1.0) + ); + return BlockIterator.iterateFromTo( + fromX, + fromY, + fromZ, + toX, + toY, + toZ, + (x, y, z, px, py, pz, qx, qy, qz, accessor) -> accessor.getBlockType(x, y, z).getMaterial() != BlockMaterial.Solid, + localAccessor + ); + }; + this.executor.setLineOfSightProvider(provider); + } else { + this.executor.setLineOfSightProvider(LineOfSightProvider.DEFAULT_TRUE); + } + + if (SelectInteraction.SHOW_VISUAL_DEBUG) { + Matrix4d tmp = new Matrix4d(); + Matrix4d matrix = new Matrix4d(); + matrix.identity() + .translate(posX, posY, posZ) + .rotateAxis(-look.getRotation().getYaw(), 0.0, 1.0, 0.0, tmp) + .rotateAxis(-look.getRotation().getPitch(), 1.0, 0.0, 0.0, tmp); + Vector3f color = new Vector3f( + (float)HashUtil.random(attacker.getIndex(), this.hashCode(), 10L), + (float)HashUtil.random(attacker.getIndex(), this.hashCode(), 11L), + (float)HashUtil.random(attacker.getIndex(), this.hashCode(), 12L) + ); + DebugUtils.addFrustum(commandBuffer.getExternalData().getWorld(), matrix, this.projectionProvider.getMatrix(), color, 5.0F, true); + } + + this.runTimeDeltaPercentageSum += runTimeDeltaPercentage; + } + + @Override + public void selectTargetEntities( + @Nonnull CommandBuffer commandBuffer, + @Nonnull Ref attacker, + @Nonnull BiConsumer, Vector4d> consumer, + Predicate> filter + ) { + Selector.selectNearbyEntities(commandBuffer, attacker, HorizontalSelector.this.endDistance + 3.0, entity -> { + BoundingBox hitboxComponent = commandBuffer.getComponent(entity, BoundingBox.getComponentType()); + if (hitboxComponent != null) { + Box hitbox = hitboxComponent.getBoundingBox(); + TransformComponent transform = commandBuffer.getComponent(entity, TransformComponent.getComponentType()); + this.modelMatrix.identity().translate(transform.getPosition()).translate(hitbox.getMin()).scale(hitbox.width(), hitbox.height(), hitbox.depth()); + if (this.executor.test(HitDetectionExecutor.CUBE_QUADS, this.modelMatrix)) { + consumer.accept(entity, this.executor.getHitLocation()); + } + } + }, filter); + } + + @Override + public void selectTargetBlocks(@Nonnull CommandBuffer commandBuffer, @Nonnull Ref attacker, @Nonnull TriIntConsumer consumer) { + Selector.selectNearbyBlocks(commandBuffer, attacker, HorizontalSelector.this.startDistance + HorizontalSelector.this.endDistance, (x, y, z) -> { + World world = commandBuffer.getStore().getExternalData().getWorld(); + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(x, z)); + if (chunk != null) { + BlockType blockType = chunk.getBlockType(x, y, z); + if (blockType != BlockType.EMPTY) { + int rotation = chunk.getRotationIndex(x, y, z); + Box[] hitboxes = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex()).get(rotation).getDetailBoxes(); + + for (int i = 0; i < hitboxes.length; i++) { + Box hitbox = hitboxes[i]; + this.modelMatrix.identity().translate(x, y, z).translate(hitbox.getMin()).scale(hitbox.width(), hitbox.height(), hitbox.depth()); + if (this.executor.test(HitDetectionExecutor.CUBE_QUADS, this.modelMatrix)) { + consumer.accept(x, y, z); + break; + } + } + } + } + }); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/PlayerMatcher.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/PlayerMatcher.java new file mode 100644 index 0000000..00a42fb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/PlayerMatcher.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.EntityMatcher; +import com.hypixel.hytale.protocol.EntityMatcherType; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.SelectInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PlayerMatcher extends SelectInteraction.EntityMatcher { + public static final BuilderCodec CODEC = BuilderCodec.builder(PlayerMatcher.class, PlayerMatcher::new, BASE_CODEC) + .documentation("Matches only players") + .build(); + + public PlayerMatcher() { + } + + @Override + public boolean test0(Ref attacker, @Nonnull Ref target, @Nonnull CommandBuffer commandBuffer) { + return commandBuffer.getArchetype(target).contains(Player.getComponentType()); + } + + @Nonnull + @Override + public EntityMatcher toPacket() { + EntityMatcher packet = super.toPacket(); + packet.type = EntityMatcherType.Player; + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/RaycastSelector.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/RaycastSelector.java new file mode 100644 index 0000000..8e5cc88 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/RaycastSelector.java @@ -0,0 +1,207 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.function.consumer.TriIntConsumer; +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.math.vector.Vector4d; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.modules.collision.CollisionMath; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.SelectInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.function.BiConsumer; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RaycastSelector extends SelectorType { + public static final BuilderCodec CODEC = BuilderCodec.builder(RaycastSelector.class, RaycastSelector::new, BASE_CODEC) + .appendInherited(new KeyedCodec<>("Offset", Vector3d.CODEC), (o, i) -> o.offset = i, o -> o.offset, (o, p) -> o.offset = p.offset) + .documentation("The offset of the area to search for targets in.") + .add() + .appendInherited(new KeyedCodec<>("Distance", Codec.INTEGER), (o, i) -> o.distance = i, o -> o.distance, (o, p) -> o.distance = p.distance) + .documentation("The max search distance for the raycast") + .add() + .appendInherited( + new KeyedCodec<>("IgnoreFluids", Codec.BOOLEAN), (o, i) -> o.ignoreFluids = i, o -> o.ignoreFluids, (o, p) -> o.ignoreFluids = p.ignoreFluids + ) + .add() + .appendInherited( + new KeyedCodec<>("IgnoreEmptyCollisionMaterial", Codec.BOOLEAN), + (o, i) -> o.ignoreEmptyCollisionMaterial = i, + o -> o.ignoreEmptyCollisionMaterial, + (o, p) -> o.ignoreEmptyCollisionMaterial = p.ignoreEmptyCollisionMaterial + ) + .add() + .appendInherited(new KeyedCodec<>("BlockTag", Codec.STRING), (o, i) -> o.blockTag = i, o -> o.blockTag, (o, p) -> o.blockTag = p.blockTag) + .documentation("The required tag for the block to have to match for the raycast to hit them") + .add() + .afterDecode(o -> { + if (o.blockTag != null) { + o.blockTagIndex = AssetRegistry.getOrCreateTagIndex(o.blockTag); + } + }) + .build(); + protected Vector3d offset = Vector3d.ZERO; + protected int distance = 30; + protected boolean ignoreFluids = false; + protected boolean ignoreEmptyCollisionMaterial = false; + @Nullable + protected String blockTag; + protected int blockTagIndex = Integer.MIN_VALUE; + + public RaycastSelector() { + } + + @Nonnull + @Override + public Selector newSelector() { + return new RaycastSelector.RuntimeSelector(); + } + + @Nonnull + public Vector3f getOffset() { + return new Vector3f((float)this.offset.x, (float)this.offset.y, (float)this.offset.z); + } + + @Nonnull + public Vector3d selectTargetPosition(@Nonnull CommandBuffer commandBuffer, @Nonnull Ref attacker) { + TransformComponent transformComponent = commandBuffer.getComponent(attacker, TransformComponent.getComponentType()); + Vector3d position = transformComponent.getPosition(); + if (this.offset.x != 0.0 || this.offset.y != 0.0 || this.offset.z != 0.0) { + position = this.offset.clone(); + HeadRotation headRotation = commandBuffer.getComponent(attacker, HeadRotation.getComponentType()); + position.rotateY(headRotation.getRotation().getYaw()); + position.add(transformComponent.getPosition()); + } + + return position; + } + + public com.hypixel.hytale.protocol.Selector toPacket() { + return new com.hypixel.hytale.protocol.RaycastSelector( + this.getOffset(), this.distance, this.blockTagIndex, this.ignoreFluids, this.ignoreEmptyCollisionMaterial + ); + } + + private static class Result { + public Ref match; + public double distance = Double.MAX_VALUE; + @Nonnull + public Vector4d hitPosition = new Vector4d(); + + private Result() { + } + } + + private class RuntimeSelector implements Selector { + private final RaycastSelector.Result bestMatch = new RaycastSelector.Result(); + private final Vector2d minMax = new Vector2d(); + @Nullable + private Vector3i blockPosition; + + private RuntimeSelector() { + } + + @Override + public void tick(@Nonnull CommandBuffer commandBuffer, @Nonnull Ref ref, float time, float runTime) { + Vector3d position = RaycastSelector.this.selectTargetPosition(commandBuffer, ref); + HeadRotation headRotation = commandBuffer.getComponent(ref, HeadRotation.getComponentType()); + Vector3d direction = new Vector3d(headRotation.getRotation().getYaw(), headRotation.getRotation().getPitch()); + IntSet blockTags = RaycastSelector.this.blockTag == null ? null : BlockType.getAssetMap().getIndexesForTag(RaycastSelector.this.blockTagIndex); + if (SelectInteraction.SHOW_VISUAL_DEBUG) { + Vector3d dir = direction.clone().scale(RaycastSelector.this.distance); + com.hypixel.hytale.math.vector.Vector3f color = new com.hypixel.hytale.math.vector.Vector3f( + (float)HashUtil.random(ref.getIndex(), this.hashCode(), 10L), + (float)HashUtil.random(ref.getIndex(), this.hashCode(), 11L), + (float)HashUtil.random(ref.getIndex(), this.hashCode(), 12L) + ); + DebugUtils.addArrow(commandBuffer.getExternalData().getWorld(), position, dir, color, 5.0F, true); + } + + this.blockPosition = TargetUtil.getTargetBlock(commandBuffer.getExternalData().getWorld(), (id, fluidId) -> { + if (id == 0) { + return false; + } else { + if (RaycastSelector.this.ignoreFluids || RaycastSelector.this.ignoreEmptyCollisionMaterial) { + BlockType blockType = BlockType.getAssetMap().getAsset(id); + if (RaycastSelector.this.ignoreFluids && blockType.getMaterial() == BlockMaterial.Empty && fluidId != 0) { + return false; + } + + if (RaycastSelector.this.ignoreEmptyCollisionMaterial && blockType.getMaterial() == BlockMaterial.Empty) { + return false; + } + } + + return blockTags == null || blockTags.contains(id); + } + }, position.x, position.y, position.z, direction.x, direction.y, direction.z, RaycastSelector.this.distance); + Vector3d searchPosition = new Vector3d( + position.x + direction.x * RaycastSelector.this.distance * 0.5, + position.y + direction.y * RaycastSelector.this.distance * 0.5, + position.z + direction.z * RaycastSelector.this.distance * 0.5 + ); + Selector.selectNearbyEntities(commandBuffer, searchPosition, RaycastSelector.this.distance * 0.6, entityRef -> { + BoundingBox boundingBox = commandBuffer.getComponent(entityRef, BoundingBox.getComponentType()); + if (boundingBox != null) { + TransformComponent transform = commandBuffer.getComponent(entityRef, TransformComponent.getComponentType()); + Vector3d ePos = transform.getPosition(); + if (CollisionMath.intersectRayAABB(position, direction, ePos.getX(), ePos.getY(), ePos.getZ(), boundingBox.getBoundingBox(), this.minMax)) { + double hitPosX = position.x + direction.x * this.minMax.x; + double hitPosY = position.y + direction.y * this.minMax.x; + double hitPosZ = position.z + direction.z * this.minMax.x; + double matchDistance = position.distanceSquaredTo(hitPosX, hitPosY, hitPosZ); + if (!(matchDistance >= this.bestMatch.distance)) { + this.bestMatch.match = entityRef; + this.bestMatch.distance = matchDistance; + this.bestMatch.hitPosition.assign(hitPosX, hitPosY, hitPosZ, 0.0); + } + } + } + }, e -> !e.equals(ref)); + if (this.bestMatch.match != null && this.blockPosition != null) { + double blockDistance = position.distanceSquaredTo(this.blockPosition.x + 0.5, this.blockPosition.y + 0.5, this.blockPosition.z + 0.5); + if (!(blockDistance < this.bestMatch.distance)) { + this.blockPosition = null; + } + } + } + + @Override + public void selectTargetEntities( + @Nonnull CommandBuffer commandBuffer, + @Nonnull Ref attacker, + @Nonnull BiConsumer, Vector4d> consumer, + Predicate> filter + ) { + if (this.blockPosition == null && this.bestMatch.match != null) { + if (this.bestMatch.match.isValid()) { + consumer.accept(this.bestMatch.match, this.bestMatch.hitPosition); + } + } + } + + @Override + public void selectTargetBlocks(@Nonnull CommandBuffer commandBuffer, @Nonnull Ref attacker, @Nonnull TriIntConsumer consumer) { + if (this.blockPosition != null) { + consumer.accept(this.blockPosition.x, this.blockPosition.y, this.blockPosition.z); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/Selector.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/Selector.java new file mode 100644 index 0000000..32dd9e2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/Selector.java @@ -0,0 +1,120 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector; + +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.function.consumer.TriIntConsumer; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector4d; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface Selector { + static void selectNearbyBlocks( + @Nonnull CommandBuffer commandBuffer, @Nonnull Ref attackerRef, double range, @Nonnull TriIntConsumer consumer + ) { + TransformComponent transformComponent = commandBuffer.getComponent(attackerRef, TransformComponent.getComponentType()); + if (!.$assertionsDisabled && transformComponent == null) { + throw new AssertionError(); + } else { + ModelComponent modelComponent = commandBuffer.getComponent(attackerRef, ModelComponent.getComponentType()); + if (!.$assertionsDisabled && modelComponent == null) { + throw new AssertionError(); + } else { + Vector3d position = transformComponent.getPosition(); + Model model = modelComponent.getModel(); + selectNearbyBlocks(position.x, position.y + model.getEyeHeight(attackerRef, commandBuffer), position.z, range, consumer); + } + } + } + + static void selectNearbyBlocks(@Nonnull Vector3d position, double range, @Nonnull TriIntConsumer consumer) { + selectNearbyBlocks(position.x, position.y, position.z, range, consumer); + } + + static void selectNearbyBlocks(double xPos, double yPos, double zPos, double range, @Nonnull TriIntConsumer consumer) { + int xStart = MathUtil.floor(xPos - range); + int yStart = MathUtil.floor(yPos - range); + int zStart = MathUtil.floor(zPos - range); + int xEnd = MathUtil.floor(xPos + range); + int yEnd = MathUtil.floor(yPos + range); + int zEnd = MathUtil.floor(zPos + range); + + for (int x = xStart; x < xEnd; x++) { + for (int y = yStart; y < yEnd; y++) { + for (int z = zStart; z < zEnd; z++) { + consumer.accept(x, y, z); + } + } + } + } + + static void selectNearbyEntities( + @Nonnull CommandBuffer commandBuffer, + @Nonnull Ref attacker, + double range, + @Nonnull Consumer> consumer, + @Nonnull Predicate> filter + ) { + TransformComponent transformComponent = commandBuffer.getComponent(attacker, TransformComponent.getComponentType()); + if (!.$assertionsDisabled && transformComponent == null) { + throw new AssertionError(); + } else { + ModelComponent modelComponent = commandBuffer.getComponent(attacker, ModelComponent.getComponentType()); + if (!.$assertionsDisabled && modelComponent == null) { + throw new AssertionError(); + } else { + Vector3d attackerPosition = transformComponent.getPosition(); + Model model = modelComponent.getModel(); + Vector3d position = attackerPosition.clone().add(0.0, model.getEyeHeight(attacker, commandBuffer), 0.0); + selectNearbyEntities(commandBuffer, position, range, consumer, filter); + } + } + } + + static void selectNearbyEntities( + @Nonnull ComponentAccessor componentAccessor, + @Nonnull Vector3d position, + double range, + @Nonnull Consumer> consumer, + @Nullable Predicate> filter + ) { + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + SpatialResource, EntityStore> playerSpatialResource = componentAccessor.getResource(EntityModule.get().getPlayerSpatialResourceType()); + playerSpatialResource.getSpatialStructure().collect(position, range, results); + SpatialResource, EntityStore> entitySpatialResource = componentAccessor.getResource(EntityModule.get().getEntitySpatialResourceType()); + entitySpatialResource.getSpatialStructure().collect(position, range, results); + SpatialResource, EntityStore> itemSpatialResource = componentAccessor.getResource(EntityModule.get().getItemSpatialResourceType()); + itemSpatialResource.getSpatialStructure().collect(position, range, results); + + for (Ref ref : results) { + if (ref != null && ref.isValid() && (filter == null || filter.test(ref))) { + consumer.accept(ref); + } + } + } + + void tick(@Nonnull CommandBuffer var1, @Nonnull Ref var2, float var3, float var4); + + void selectTargetEntities( + @Nonnull CommandBuffer var1, @Nonnull Ref var2, BiConsumer, Vector4d> var3, Predicate> var4 + ); + + void selectTargetBlocks(@Nonnull CommandBuffer var1, @Nonnull Ref var2, @Nonnull TriIntConsumer var3); + + static { + if (.$assertionsDisabled) { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/SelectorType.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/SelectorType.java new file mode 100644 index 0000000..76a21de --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/SelectorType.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.server.core.io.NetworkSerializable; + +public abstract class SelectorType implements NetworkSerializable { + public static final CodecMapCodec CODEC = new CodecMapCodec<>(); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(SelectorType.class).build(); + + public SelectorType() { + } + + public abstract Selector newSelector(); +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/StabSelector.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/StabSelector.java new file mode 100644 index 0000000..d595a09 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/StabSelector.java @@ -0,0 +1,274 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.function.consumer.TriIntConsumer; +import com.hypixel.hytale.math.hitdetection.HitDetectionExecutor; +import com.hypixel.hytale.math.hitdetection.LineOfSightProvider; +import com.hypixel.hytale.math.hitdetection.projection.OrthogonalProjectionProvider; +import com.hypixel.hytale.math.hitdetection.view.DirectionViewProvider; +import com.hypixel.hytale.math.iterator.BlockIterator; +import com.hypixel.hytale.math.matrix.Matrix4d; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector4d; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.SelectInteraction; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.accessor.LocalCachedChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.function.BiConsumer; +import java.util.function.Predicate; +import javax.annotation.Nonnull; + +public class StabSelector extends SelectorType { + public static final BuilderCodec CODEC = BuilderCodec.builder(StabSelector.class, StabSelector::new, BASE_CODEC) + .documentation("A selector that stabs in a straight line over a given period of time.") + .appendInherited( + new KeyedCodec<>("StartDistance", Codec.DOUBLE), + (selector, value) -> selector.startDistance = value, + selector -> selector.startDistance, + (selector, parent) -> selector.startDistance = parent.startDistance + ) + .documentation("The distance from the entity that the selector starts its search from.") + .add() + .appendInherited( + new KeyedCodec<>("EndDistance", Codec.DOUBLE), + (selector, value) -> selector.endDistance = value, + selector -> selector.endDistance, + (selector, parent) -> selector.endDistance = parent.endDistance + ) + .documentation("The distance from the entity that the selector ends its search at.") + .add() + .appendInherited( + new KeyedCodec<>("ExtendTop", Codec.DOUBLE), + (selector, value) -> selector.extendTop = value, + selector -> selector.extendTop, + (selector, parent) -> selector.extendTop = parent.extendTop + ) + .documentation("The amount to extend the top of the selector by.") + .add() + .appendInherited( + new KeyedCodec<>("ExtendBottom", Codec.DOUBLE), + (selector, value) -> selector.extendBottom = value, + selector -> selector.extendBottom, + (selector, parent) -> selector.extendBottom = parent.extendBottom + ) + .documentation("The amount to extend the bottom of the selector by.") + .add() + .appendInherited( + new KeyedCodec<>("ExtendLeft", Codec.DOUBLE), + (selector, value) -> selector.extendLeft = value, + selector -> selector.extendLeft, + (selector, parent) -> selector.extendLeft = parent.extendLeft + ) + .documentation("The amount to extend the left side of the selector by.") + .add() + .appendInherited( + new KeyedCodec<>("ExtendRight", Codec.DOUBLE), + (selector, value) -> selector.extendRight = value, + selector -> selector.extendRight, + (selector, parent) -> selector.extendRight = parent.extendRight + ) + .documentation("The amount to extend the right side of the selector by.") + .add() + .appendInherited( + new KeyedCodec<>("YawOffset", Codec.DOUBLE), + (selector, value) -> selector.yawOffset = Math.toRadians(value), + selector -> Math.toDegrees(selector.yawOffset), + (selector, parent) -> selector.yawOffset = parent.yawOffset + ) + .documentation("The yaw rotation offset in degrees for this selector") + .add() + .appendInherited( + new KeyedCodec<>("PitchOffset", Codec.DOUBLE), + (selector, value) -> selector.pitchOffset = Math.toRadians(value), + selector -> Math.toDegrees(selector.pitchOffset), + (selector, parent) -> selector.pitchOffset = parent.pitchOffset + ) + .documentation("The pitch rotation offset in degrees for this selector") + .add() + .appendInherited( + new KeyedCodec<>("RollOffset", Codec.DOUBLE), + (selector, value) -> selector.rollOffset = Math.toRadians(value), + selector -> Math.toDegrees(selector.rollOffset), + (selector, parent) -> selector.rollOffset = parent.rollOffset + ) + .documentation("The roll rotation offset in degrees for this selector") + .add() + .appendInherited( + new KeyedCodec<>("TestLineOfSight", Codec.BOOLEAN), + (selector, value) -> selector.testLineOfSight = value, + selector -> selector.testLineOfSight, + (selector, parent) -> selector.testLineOfSight = parent.testLineOfSight + ) + .documentation("Whether to test for line of sight between the user and the target before counting a hit") + .add() + .build(); + protected double extendTop = 1.0; + protected double extendBottom = 1.0; + protected double extendLeft = 1.0; + protected double extendRight = 1.0; + protected double yawOffset; + protected double pitchOffset; + protected double rollOffset; + protected double startDistance = 0.01; + protected double endDistance; + protected boolean testLineOfSight; + + public StabSelector() { + } + + @Nonnull + @Override + public Selector newSelector() { + return new StabSelector.RuntimeSelector(); + } + + public com.hypixel.hytale.protocol.Selector toPacket() { + com.hypixel.hytale.protocol.StabSelector selector = new com.hypixel.hytale.protocol.StabSelector(); + selector.extendTop = (float)this.extendTop; + selector.extendBottom = (float)this.extendBottom; + selector.extendLeft = (float)this.extendLeft; + selector.extendRight = (float)this.extendRight; + selector.yawOffset = (float)this.yawOffset; + selector.pitchOffset = (float)this.pitchOffset; + selector.rollOffset = (float)this.rollOffset; + selector.startDistance = (float)this.startDistance; + selector.endDistance = (float)this.endDistance; + selector.testLineOfSight = this.testLineOfSight; + return selector; + } + + private class RuntimeSelector implements Selector { + @Nonnull + protected HitDetectionExecutor executor = new HitDetectionExecutor(); + @Nonnull + protected Matrix4d modelMatrix = new Matrix4d(); + @Nonnull + protected OrthogonalProjectionProvider projectionProvider = new OrthogonalProjectionProvider(); + @Nonnull + protected DirectionViewProvider viewProvider = new DirectionViewProvider(); + protected float lastTime = 0.0F; + protected double runTimeDeltaPercentageSum; + + private RuntimeSelector() { + } + + @Override + public void tick(@Nonnull CommandBuffer commandBuffer, @Nonnull Ref attacker, float time, float runTime) { + float yOffset = commandBuffer.getComponent(attacker, ModelComponent.getComponentType()).getModel().getEyeHeight(attacker, commandBuffer); + Vector3d position = commandBuffer.getComponent(attacker, TransformComponent.getComponentType()).getPosition(); + HeadRotation look = commandBuffer.getComponent(attacker, HeadRotation.getComponentType()); + double posX = position.getX(); + double posY = position.getY() + yOffset; + double posZ = position.getZ(); + float delta = time - this.lastTime; + this.lastTime = time; + float runTimeDeltaPercentage = delta / runTime; + double distanceDiff = StabSelector.this.endDistance - StabSelector.this.startDistance; + double deltaStartDistance = this.runTimeDeltaPercentageSum * distanceDiff + StabSelector.this.startDistance; + double deltaEndDistance = (this.runTimeDeltaPercentageSum + runTimeDeltaPercentage) * distanceDiff + StabSelector.this.startDistance; + this.projectionProvider + .setNear(deltaStartDistance) + .setFar(deltaEndDistance) + .setLeft(StabSelector.this.extendLeft) + .setRight(StabSelector.this.extendRight) + .setBottom(StabSelector.this.extendBottom) + .setTop(StabSelector.this.extendTop) + .setRotation(StabSelector.this.yawOffset, StabSelector.this.pitchOffset, StabSelector.this.rollOffset); + this.viewProvider.setPosition(posX, posY, posZ).setDirection(look.getRotation().getYaw(), look.getRotation().getPitch()); + this.executor.setOrigin(posX, posY, posZ).setProjectionProvider(this.projectionProvider).setViewProvider(this.viewProvider); + if (StabSelector.this.testLineOfSight) { + this.executor + .setLineOfSightProvider( + (fromX, fromY, fromZ, toX, toY, toZ) -> { + LocalCachedChunkAccessor localAccessor = LocalCachedChunkAccessor.atWorldCoords( + commandBuffer.getStore().getExternalData().getWorld(), (int)fromX, (int)fromZ, (int)(StabSelector.this.endDistance + 1.0) + ); + return BlockIterator.iterateFromTo(fromX, fromY, fromZ, toX, toY, toZ, (x, y, z, px, py, pz, qx, qy, qz, accessor) -> { + int blockId = accessor.getBlock(x, y, z); + return blockId == 0; + }, localAccessor); + } + ); + } else { + this.executor.setLineOfSightProvider(LineOfSightProvider.DEFAULT_TRUE); + } + + if (SelectInteraction.SHOW_VISUAL_DEBUG) { + Matrix4d tmp = new Matrix4d(); + Matrix4d matrix = new Matrix4d(); + matrix.identity() + .translate(posX, posY, posZ) + .rotateAxis(-look.getRotation().getYaw(), 0.0, 1.0, 0.0, tmp) + .rotateAxis(-look.getRotation().getPitch(), 1.0, 0.0, 0.0, tmp); + Vector3f color = new Vector3f( + (float)HashUtil.random(attacker.getIndex(), this.hashCode(), 10L), + (float)HashUtil.random(attacker.getIndex(), this.hashCode(), 11L), + (float)HashUtil.random(attacker.getIndex(), this.hashCode(), 12L) + ); + DebugUtils.addFrustum(commandBuffer.getExternalData().getWorld(), matrix, this.projectionProvider.getMatrix(), color, 5.0F, true); + } + + this.runTimeDeltaPercentageSum += runTimeDeltaPercentage; + } + + @Override + public void selectTargetEntities( + @Nonnull CommandBuffer commandBuffer, + @Nonnull Ref attacker, + @Nonnull BiConsumer, Vector4d> consumer, + Predicate> filter + ) { + Selector.selectNearbyEntities(commandBuffer, attacker, StabSelector.this.endDistance + 3.0, entity -> { + BoundingBox hitboxComponent = commandBuffer.getComponent(entity, BoundingBox.getComponentType()); + if (hitboxComponent != null) { + Box hitbox = hitboxComponent.getBoundingBox(); + TransformComponent transform = commandBuffer.getComponent(entity, TransformComponent.getComponentType()); + this.modelMatrix.identity().translate(transform.getPosition()).translate(hitbox.getMin()).scale(hitbox.width(), hitbox.height(), hitbox.depth()); + if (this.executor.test(HitDetectionExecutor.CUBE_QUADS, this.modelMatrix)) { + consumer.accept(entity, this.executor.getHitLocation()); + } + } + }, filter); + } + + @Override + public void selectTargetBlocks(@Nonnull CommandBuffer commandBuffer, @Nonnull Ref attacker, @Nonnull TriIntConsumer consumer) { + Selector.selectNearbyBlocks(commandBuffer, attacker, StabSelector.this.startDistance + StabSelector.this.endDistance, (x, y, z) -> { + World world = commandBuffer.getStore().getExternalData().getWorld(); + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(x, z)); + if (chunk != null) { + BlockType blockType = chunk.getBlockType(x, y, z); + if (blockType != BlockType.EMPTY) { + int rotation = chunk.getRotationIndex(x, y, z); + Box[] hitboxes = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex()).get(rotation).getDetailBoxes(); + + for (int i = 0; i < hitboxes.length; i++) { + Box hitbox = hitboxes[i]; + this.modelMatrix.identity().translate(x, y, z).translate(hitbox.getMin()).scale(hitbox.width(), hitbox.height(), hitbox.depth()); + if (this.executor.test(HitDetectionExecutor.CUBE_QUADS, this.modelMatrix)) { + consumer.accept(x, y, z); + break; + } + } + } + } + }); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/VulnerableMatcher.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/VulnerableMatcher.java new file mode 100644 index 0000000..3b34889 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/selector/VulnerableMatcher.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.EntityMatcher; +import com.hypixel.hytale.protocol.EntityMatcherType; +import com.hypixel.hytale.server.core.modules.entity.component.Invulnerable; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.SelectInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class VulnerableMatcher extends SelectInteraction.EntityMatcher { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(VulnerableMatcher.class, VulnerableMatcher::new, BASE_CODEC) + .documentation("Used to match any entity that is attackable") + .build(); + + public VulnerableMatcher() { + } + + @Override + public boolean test0(Ref attacker, @Nonnull Ref target, @Nonnull CommandBuffer commandBuffer) { + boolean invulnerable = commandBuffer.getArchetype(target).contains(Invulnerable.getComponentType()); + return !invulnerable; + } + + @Nonnull + @Override + public EntityMatcher toPacket() { + EntityMatcher packet = super.toPacket(); + packet.type = EntityMatcherType.VulnerableMatcher; + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/ChangeStatBaseInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/ChangeStatBaseInteraction.java new file mode 100644 index 0000000..bc8dcc2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/ChangeStatBaseInteraction.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.map.Object2FloatMapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.ChangeStatBehaviour; +import com.hypixel.hytale.protocol.ValueType; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.util.InteractionTarget; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class ChangeStatBaseInteraction extends SimpleInstantInteraction { + public static final BuilderCodec CODEC = BuilderCodec.abstractBuilder( + ChangeStatBaseInteraction.class, SimpleInstantInteraction.CODEC + ) + .append( + new KeyedCodec<>("StatModifiers", new Object2FloatMapCodec<>(Codec.STRING, Object2FloatOpenHashMap::new), true), + (changeStatInteraction, stringObject2DoubleMap) -> changeStatInteraction.entityStatAssets = stringObject2DoubleMap, + changeStatInteraction -> changeStatInteraction.entityStatAssets + ) + .addValidator(Validators.nonNull()) + .addValidator(Validators.nonEmptyMap()) + .addValidator(EntityStatType.VALIDATOR_CACHE.getMapKeyValidator()) + .documentation("Modifiers to apply to EntityStats.") + .add() + .append( + new KeyedCodec<>("ValueType", new EnumCodec<>(ValueType.class)), + (changeStatInteraction, valueType) -> changeStatInteraction.valueType = valueType, + changeStatInteraction -> changeStatInteraction.valueType + ) + .documentation( + "Enum to specify if the StatModifiers must be considered as absolute values or percent. Default value is Absolute. When using ValueType.Absolute, '100' matches the max value." + ) + .add() + .append( + new KeyedCodec<>("Behaviour", ProtocolCodecs.CHANGE_STAT_BEHAVIOUR_CODEC), + (changeStatInteraction, changeStatBehaviour) -> changeStatInteraction.changeStatBehaviour = changeStatBehaviour, + changeStatInteraction -> changeStatInteraction.changeStatBehaviour + ) + .documentation("Specifies how StatModifiers should be applied to the stats.") + .add() + .appendInherited( + new KeyedCodec<>("Entity", InteractionTarget.CODEC), (o, i) -> o.entityTarget = i, o -> o.entityTarget, (o, p) -> o.entityTarget = p.entityTarget + ) + .documentation("The entity to target for this interaction.") + .addValidator(Validators.nonNull()) + .add() + .afterDecode(changeStatInteraction -> changeStatInteraction.entityStats = EntityStatsModule.resolveEntityStats(changeStatInteraction.entityStatAssets)) + .build(); + protected Object2FloatMap entityStatAssets; + @Nullable + protected Int2FloatMap entityStats; + protected ValueType valueType = ValueType.Absolute; + protected ChangeStatBehaviour changeStatBehaviour = ChangeStatBehaviour.Add; + protected InteractionTarget entityTarget = InteractionTarget.USER; + + public ChangeStatBaseInteraction() { + } + + @Nonnull + @Override + public String toString() { + return "ChangeStatBaseInteraction{unknownEntityStats=" + + this.entityStatAssets + + ", entityStats=" + + this.entityStats + + ", valueType=" + + this.valueType + + ", changeStatBehaviour=" + + this.changeStatBehaviour + + ", entityTarget=" + + this.entityTarget + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/ChangeStatInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/ChangeStatInteraction.java new file mode 100644 index 0000000..8b6f8d8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/ChangeStatInteraction.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ChangeStatInteraction extends ChangeStatBaseInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ChangeStatInteraction.class, ChangeStatInteraction::new, ChangeStatBaseInteraction.CODEC + ) + .documentation("Changes the given stats.") + .build(); + + public ChangeStatInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + Ref ref = context.getEntity(); + EntityStatMap entityStatMapComponent = commandBuffer.getComponent(ref, EntityStatMap.getComponentType()); + if (entityStatMapComponent != null) { + entityStatMapComponent.processStatChanges(EntityStatMap.Predictable.SELF, this.entityStats, this.valueType, this.changeStatBehaviour); + } + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.ChangeStatInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.ChangeStatInteraction p = (com.hypixel.hytale.protocol.ChangeStatInteraction)packet; + p.statModifiers = this.entityStats; + p.valueType = this.valueType; + p.changeStatBehaviour = this.changeStatBehaviour; + p.entityTarget = this.entityTarget.toProtocol(); + } + + @Nonnull + @Override + public String toString() { + return "ChangeStatInteraction{}" + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/ChangeStatWithModifierInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/ChangeStatWithModifierInteraction.java new file mode 100644 index 0000000..ff659ba --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/ChangeStatWithModifierInteraction.java @@ -0,0 +1,102 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemArmor; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.StaticModifier; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import javax.annotation.Nonnull; + +public class ChangeStatWithModifierInteraction extends ChangeStatBaseInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ChangeStatWithModifierInteraction.class, ChangeStatWithModifierInteraction::new, ChangeStatBaseInteraction.CODEC + ) + .documentation("Changes the given stats.") + .append( + new KeyedCodec<>("InteractionModifierId", new EnumCodec<>(ItemArmor.InteractionModifierId.class)), + (changeStatWithModifierInteraction, s) -> changeStatWithModifierInteraction.interactionModifierId = s, + changeStatWithModifierInteraction -> changeStatWithModifierInteraction.interactionModifierId + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + protected ItemArmor.InteractionModifierId interactionModifierId; + + public ChangeStatWithModifierInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + Ref ref = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + EntityStatMap entityStatMapComponent = commandBuffer.getComponent(ref, EntityStatMap.getComponentType()); + + assert entityStatMapComponent != null; + + Int2FloatMap adjustedEntityStats = new Int2FloatOpenHashMap(this.entityStats); + Inventory inventory = null; + if (EntityUtils.getEntity(ref, commandBuffer) instanceof LivingEntity livingEntity) { + inventory = livingEntity.getInventory(); + } + + for (int index : adjustedEntityStats.keySet()) { + if (inventory != null) { + ItemContainer armorContainer = inventory.getArmor(); + if (armorContainer != null) { + float flatModifier = 0.0F; + float multiplierModifier = 0.0F; + + for (short i = 0; i < armorContainer.getCapacity(); i++) { + ItemStack itemStack = armorContainer.getItemStack(i); + if (itemStack != null && !itemStack.isEmpty()) { + Item item = itemStack.getItem(); + if (item != null && item.getArmor() != null) { + Int2ObjectMap statModifierMap = item.getArmor().getInteractionModifier(this.interactionModifierId.toString()); + if (statModifierMap != null) { + StaticModifier statModifier = statModifierMap.get(index); + if (statModifier != null) { + if (statModifier.getCalculationType() == StaticModifier.CalculationType.ADDITIVE) { + flatModifier += statModifier.getAmount(); + } else { + multiplierModifier = statModifier.getAmount(); + } + } + } + } + } + } + + float cost = this.entityStats.get(index); + cost += flatModifier; + cost *= Math.max(0.0F, 1.0F - multiplierModifier); + adjustedEntityStats.replace(index, cost); + } + } + } + + entityStatMapComponent.processStatChanges(EntityStatMap.Predictable.NONE, adjustedEntityStats, this.valueType, this.changeStatBehaviour); + } + + @Nonnull + @Override + public String toString() { + return "ChangeStatWithModifierInteraction{interactionModifierId=" + this.interactionModifierId + "}" + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/CheckUniqueItemUsageInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/CheckUniqueItemUsageInteraction.java new file mode 100644 index 0000000..55afbcc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/CheckUniqueItemUsageInteraction.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.player.data.UniqueItemUsagesComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.NotificationUtil; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class CheckUniqueItemUsageInteraction extends SimpleInstantInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CheckUniqueItemUsageInteraction.class, CheckUniqueItemUsageInteraction::new, SimpleInstantInteraction.CODEC + ) + .build(); + + public CheckUniqueItemUsageInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + protected void firstRun(@NonNullDecl InteractionType type, @NonNullDecl InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getEntity(); + PlayerRef playerRefComponent = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); + if (playerRefComponent == null) { + context.getState().state = InteractionState.Failed; + } else { + UniqueItemUsagesComponent uniqueItemUsagesComponent = commandBuffer.getComponent(ref, UniqueItemUsagesComponent.getComponentType()); + + assert uniqueItemUsagesComponent != null; + + if (uniqueItemUsagesComponent.hasUsedUniqueItem(context.getHeldItem().getItemId())) { + context.getState().state = InteractionState.Failed; + NotificationUtil.sendNotification( + playerRefComponent.getPacketHandler(), Message.translation("server.commands.checkUniqueItemUsage.uniqueItemAlreadyUsed") + ); + } else { + uniqueItemUsagesComponent.recordUniqueItemUsage(context.getHeldItem().getItemId()); + context.getState().state = InteractionState.Finished; + } + } + } + + @Override + public String toString() { + return "CheckUniqueItemUsageInteraction{}" + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/ClearEntityEffectInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/ClearEntityEffectInteraction.java new file mode 100644 index 0000000..abafdee --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/ClearEntityEffectInteraction.java @@ -0,0 +1,82 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.util.InteractionTarget; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ClearEntityEffectInteraction extends SimpleInstantInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ClearEntityEffectInteraction.class, ClearEntityEffectInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Removes the given entity effect from the given entity.") + .append( + new KeyedCodec<>("EntityEffectId", Codec.STRING), + (clearEntityEffectInteraction, string) -> clearEntityEffectInteraction.entityEffectId = string, + clearEntityEffectInteraction -> clearEntityEffectInteraction.entityEffectId + ) + .addValidator(Validators.nonNull()) + .addValidatorLate(() -> EntityEffect.VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("Entity", InteractionTarget.CODEC), (o, i) -> o.entityTarget = i, o -> o.entityTarget, (o, p) -> o.entityTarget = p.entityTarget + ) + .documentation("The entity to target for this interaction.") + .addValidator(Validators.nonNull()) + .add() + .build(); + protected String entityEffectId; + @Nonnull + private InteractionTarget entityTarget = InteractionTarget.USER; + + public ClearEntityEffectInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + Ref ref = context.getEntity(); + Ref targetRef = this.entityTarget.getEntity(context, ref); + if (targetRef != null && targetRef.isValid()) { + EntityEffect entityEffect = EntityEffect.getAssetMap().getAsset(this.entityEffectId); + if (entityEffect != null) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + EffectControllerComponent effectControllerComponent = commandBuffer.getComponent(targetRef, EffectControllerComponent.getComponentType()); + if (effectControllerComponent != null) { + effectControllerComponent.removeEffect(targetRef, EntityEffect.getAssetMap().getIndex(this.entityEffectId), commandBuffer); + } + } + } + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.ClearEntityEffectInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.ClearEntityEffectInteraction p = (com.hypixel.hytale.protocol.ClearEntityEffectInteraction)packet; + p.effectId = EntityEffect.getAssetMap().getIndex(this.entityEffectId); + p.entityTarget = this.entityTarget.toProtocol(); + } + + @Nonnull + @Override + public String toString() { + return "ClearEntityEffectInteraction{entityEffectId='" + this.entityEffectId + "', entityTarget=" + this.entityTarget + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/DamageEntityInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/DamageEntityInteraction.java new file mode 100644 index 0000000..e408a48 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/DamageEntityInteraction.java @@ -0,0 +1,752 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector4d; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.EntitySnapshot; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.knockback.KnockbackComponent; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.meta.DynamicMetaStore; +import com.hypixel.hytale.server.core.meta.MetaKey; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCalculatorSystems; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.entitystats.modifier.StaticModifier; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.Collector; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.SelectInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.DamageCalculator; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.DamageClass; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.DamageEffects; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.Knockback; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.TargetEntityEffect; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.Label; +import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.OperationsBuilder; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.IntStream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class DamageEntityInteraction extends Interaction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + DamageEntityInteraction.class, DamageEntityInteraction::new, Interaction.ABSTRACT_CODEC + ) + .documentation("Damages the target entity.") + .appendInherited( + new KeyedCodec<>("DamageCalculator", DamageCalculator.CODEC), + (i, a) -> i.damageCalculator = a, + i -> i.damageCalculator, + (i, parent) -> i.damageCalculator = parent.damageCalculator + ) + .add() + .appendInherited( + new KeyedCodec<>("DamageEffects", DamageEffects.CODEC), + (i, o) -> i.damageEffects = o, + i -> i.damageEffects, + (i, parent) -> i.damageEffects = parent.damageEffects + ) + .add() + .appendInherited( + new KeyedCodec<>("AngledDamage", new ArrayCodec<>(DamageEntityInteraction.AngledDamage.CODEC, DamageEntityInteraction.AngledDamage[]::new)), + (i, o) -> i.angledDamage = o, + i -> i.angledDamage, + (i, parent) -> i.angledDamage = parent.angledDamage + ) + .add() + .appendInherited( + new KeyedCodec<>("TargetedDamage", new MapCodec<>(DamageEntityInteraction.TargetedDamage.CODEC, HashMap::new)), + (i, o) -> i.targetedDamage = o, + i -> i.targetedDamage, + (i, parent) -> i.targetedDamage = parent.targetedDamage + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("EntityStatsOnHit", new ArrayCodec<>(DamageEntityInteraction.EntityStatOnHit.CODEC, DamageEntityInteraction.EntityStatOnHit[]::new)), + (damageEntityInteraction, entityStatOnHit) -> damageEntityInteraction.entityStatsOnHit = entityStatOnHit, + damageEntityInteraction -> damageEntityInteraction.entityStatsOnHit, + (damageEntityInteraction, parent) -> damageEntityInteraction.entityStatsOnHit = parent.entityStatsOnHit + ) + .documentation("EntityStats to apply based on the hits resulting from this interaction.") + .add() + .appendInherited( + new KeyedCodec<>("Next", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.next = s, + interaction -> interaction.next, + (interaction, parent) -> interaction.next = parent.next + ) + .documentation("The interactions to run when this interaction succeeds.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("Failed", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.failed = s, + interaction -> interaction.failed, + (interaction, parent) -> interaction.failed = parent.failed + ) + .documentation("The interactions to run when this interaction fails.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .appendInherited( + new KeyedCodec<>("Blocked", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.blocked = s, + interaction -> interaction.blocked, + (interaction, parent) -> interaction.blocked = parent.blocked + ) + .documentation("The interactions to run when this interaction fails.") + .addValidatorLate(() -> VALIDATOR_CACHE.getValidator().late()) + .add() + .afterDecode(o -> { + String[] keys = o.sortedTargetDamageKeys = o.targetedDamage.keySet().toArray(String[]::new); + Arrays.sort((Object[])keys); + int i = 0; + + while (i < keys.length) { + String k = keys[i]; + o.targetedDamage.get(k).index = i++; + } + }) + .build(); + private static final int FAILED_LABEL_INDEX = 0; + private static final int SUCCESS_LABEL_INDEX = 1; + private static final int BLOCKED_LABEL_INDEX = 2; + private static final int ANGLED_LABEL_OFFSET = 3; + public static final int ARMOR_RESISTANCE_FLAT_MODIFIER = 0; + public static final int ARMOR_RESISTANCE_MULTIPLIER_MODIFIER = 1; + private static final MetaKey SEQUENTIAL_HITS = META_REGISTRY.registerMetaObject( + i -> new DamageCalculatorSystems.Sequence() + ); + private static final MetaKey NEXT_INDEX = META_REGISTRY.registerMetaObject(); + private static final MetaKey QUEUED_DAMAGE = META_REGISTRY.registerMetaObject(); + protected DamageCalculator damageCalculator; + @Nullable + protected DamageEffects damageEffects; + protected DamageEntityInteraction.AngledDamage[] angledDamage; + protected DamageEntityInteraction.EntityStatOnHit[] entityStatsOnHit; + protected Map targetedDamage = Collections.emptyMap(); + protected String[] sortedTargetDamageKeys; + @Nullable + protected String next; + @Nullable + protected String blocked; + @Nullable + protected String failed; + + public DamageEntityInteraction() { + } + + @Override + protected void tick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + Ref targetRef = context.getTargetEntity(); + if (targetRef != null && targetRef.isValid() && context.getEntity().isValid()) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + if (!this.processDamage(context, context.getInstanceStore().getIfPresentMetaObject(QUEUED_DAMAGE))) { + Ref ref = context.getOwningEntity(); + Vector4d hit = context.getMetaStore().getMetaObject(Interaction.HIT_LOCATION); + Damage.EntitySource source = new Damage.EntitySource(ref); + this.attemptEntityDamage0(source, context, context.getEntity(), targetRef, hit); + if (SelectInteraction.SHOW_VISUAL_DEBUG && hit != null) { + DebugUtils.addSphere(commandBuffer.getExternalData().getWorld(), new Vector3d(hit.x, hit.y, hit.z), new Vector3f(1.0F, 0.0F, 0.0F), 0.2F, 5.0F); + } + } + } else { + context.jump(context.getLabel(0)); + context.getState().nextLabel = 0; + context.getState().state = InteractionState.Failed; + } + } + + @Override + protected void simulateTick0( + boolean firstRun, float time, @NonNullDecl InteractionType type, @Nonnull InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler + ) { + this.tick0(firstRun, time, type, context, cooldownHandler); + } + + private boolean processDamage(@Nonnull InteractionContext context, @Nullable Damage[] queuedDamage) { + if (queuedDamage == null) { + return false; + } else { + boolean failed = true; + boolean blocked = false; + + for (Damage queue : queuedDamage) { + if (!queue.isCancelled()) { + failed = false; + } + + if (queue.getMetaObject(Damage.BLOCKED)) { + blocked = true; + } + } + + if (failed) { + context.jump(context.getLabel(0)); + context.getState().nextLabel = 0; + context.getState().state = InteractionState.Failed; + } else if (blocked) { + context.jump(context.getLabel(2)); + context.getState().nextLabel = 2; + context.getState().state = InteractionState.Finished; + } else { + int index = context.getInstanceStore().getMetaObject(NEXT_INDEX); + context.getState().nextLabel = index; + context.jump(context.getLabel(index)); + context.getState().state = InteractionState.Finished; + } + + return true; + } + } + + @Override + public void compile(@Nonnull OperationsBuilder builder) { + Label[] labels = new Label[3 + (this.angledDamage != null ? this.angledDamage.length : 0) + this.targetedDamage.size()]; + builder.addOperation(this, labels); + Label endLabel = builder.createUnresolvedLabel(); + labels[0] = builder.createLabel(); + if (this.failed != null) { + Interaction.getInteractionOrUnknown(this.failed).compile(builder); + } + + builder.jump(endLabel); + labels[1] = builder.createLabel(); + if (this.next != null) { + Interaction.getInteractionOrUnknown(this.next).compile(builder); + } + + builder.jump(endLabel); + labels[2] = builder.createLabel(); + if (this.blocked != null) { + Interaction.getInteractionOrUnknown(this.blocked).compile(builder); + } + + builder.jump(endLabel); + int offset = 3; + if (this.angledDamage != null) { + for (DamageEntityInteraction.AngledDamage damage : this.angledDamage) { + labels[offset++] = builder.createLabel(); + String next = damage.next; + if (next == null) { + next = this.next; + } + + if (next != null) { + Interaction.getInteractionOrUnknown(next).compile(builder); + } + + builder.jump(endLabel); + } + } + + if (!this.targetedDamage.isEmpty()) { + for (String k : this.sortedTargetDamageKeys) { + DamageEntityInteraction.TargetedDamage entry = this.targetedDamage.get(k); + labels[offset++] = builder.createLabel(); + String nextx = entry.next; + if (nextx == null) { + nextx = this.next; + } + + if (nextx != null) { + Interaction.getInteractionOrUnknown(nextx).compile(builder); + } + + builder.jump(endLabel); + } + } + + builder.resolveLabel(endLabel); + } + + @Override + public boolean walk(@Nonnull Collector collector, @Nonnull InteractionContext context) { + return false; + } + + @Nonnull + @Override + protected com.hypixel.hytale.protocol.Interaction generatePacket() { + return new com.hypixel.hytale.protocol.DamageEntityInteraction(); + } + + @Override + protected void configurePacket(com.hypixel.hytale.protocol.Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.DamageEntityInteraction p = (com.hypixel.hytale.protocol.DamageEntityInteraction)packet; + p.damageEffects = this.damageEffects != null ? this.damageEffects.toPacket() : null; + p.next = Interaction.getInteractionIdOrUnknown(this.next); + p.failed = Interaction.getInteractionIdOrUnknown(this.failed); + p.blocked = Interaction.getInteractionIdOrUnknown(this.blocked); + if (this.angledDamage != null) { + p.angledDamage = new com.hypixel.hytale.protocol.AngledDamage[this.angledDamage.length]; + + for (int i = 0; i < this.angledDamage.length; i++) { + p.angledDamage[i] = this.angledDamage[i].toAngledDamagePacket(); + } + } + + if (this.entityStatsOnHit != null) { + p.entityStatsOnHit = new com.hypixel.hytale.protocol.EntityStatOnHit[this.entityStatsOnHit.length]; + + for (int i = 0; i < this.entityStatsOnHit.length; i++) { + p.entityStatsOnHit[i] = this.entityStatsOnHit[i].toPacket(); + } + } + + p.targetedDamage = new Object2ObjectOpenHashMap<>(); + + for (Entry e : this.targetedDamage.entrySet()) { + p.targetedDamage.put(e.getKey(), e.getValue().toTargetedDamagePacket()); + } + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.None; + } + + private void attemptEntityDamage0( + @Nonnull Damage.Source source, + @Nonnull InteractionContext context, + @Nonnull Ref attackerRef, + @Nonnull Ref targetRef, + @Nullable Vector4d hit + ) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + DamageCalculator damageCalculator = this.damageCalculator; + DamageEffects damageEffects = this.damageEffects; + EntitySnapshot targetSnapshot = context.getSnapshot(targetRef, commandBuffer); + EntitySnapshot attackerSnapshot = context.getSnapshot(attackerRef, commandBuffer); + Vector3d targetPos = targetSnapshot.getPosition(); + Vector3d attackerPos = attackerSnapshot.getPosition(); + float angleBetween = TrigMathUtil.atan2(attackerPos.x - targetPos.x, attackerPos.z - targetPos.z); + int nextLabel = 1; + if (this.angledDamage != null) { + float angleBetweenRotation = MathUtil.wrapAngle(angleBetween + (float) Math.PI - targetSnapshot.getBodyRotation().getYaw()); + + for (int i = 0; i < this.angledDamage.length; i++) { + DamageEntityInteraction.AngledDamage angledDamage = this.angledDamage[i]; + if (Math.abs(MathUtil.compareAngle(angleBetweenRotation, angledDamage.angleRad)) < angledDamage.angleDistanceRad) { + damageCalculator = angledDamage.damageCalculator == null ? damageCalculator : angledDamage.damageCalculator; + damageEffects = angledDamage.damageEffects == null ? damageEffects : angledDamage.damageEffects; + nextLabel = 3 + i; + break; + } + } + } + + String hitDetail = context.getMetaStore().getIfPresentMetaObject(HIT_DETAIL); + if (hitDetail != null) { + DamageEntityInteraction.TargetedDamage entry = this.targetedDamage.get(hitDetail); + if (entry != null) { + damageCalculator = entry.damageCalculator == null ? damageCalculator : entry.damageCalculator; + damageEffects = entry.damageEffects == null ? damageEffects : entry.damageEffects; + nextLabel = entry.index; + } + } + + context.getInstanceStore().putMetaObject(NEXT_INDEX, nextLabel); + if (damageCalculator != null) { + DynamicMetaStore metaStore = context.getMetaStore().getMetaObject(SelectInteraction.SELECT_META_STORE); + DamageCalculatorSystems.Sequence sequentialHits = metaStore == null + ? new DamageCalculatorSystems.Sequence() + : metaStore.getMetaObject(SEQUENTIAL_HITS); + Object2FloatMap damage = damageCalculator.calculateDamage(this.getRunTime()); + HeadRotation attackerHeadRotationComponent = commandBuffer.getComponent(attackerRef, HeadRotation.getComponentType()); + Vector3f attackerDirection; + if (attackerHeadRotationComponent != null) { + attackerDirection = attackerHeadRotationComponent.getRotation(); + } else { + attackerDirection = Vector3f.ZERO; + } + + if (damage != null && !damage.isEmpty()) { + double[] knockbackMultiplier = new double[]{1.0}; + float[] armorDamageModifiers = new float[]{0.0F, 1.0F}; + calculateKnockbackAndArmorModifiers( + damageCalculator.getDamageClass(), damage, targetRef, attackerRef, armorDamageModifiers, knockbackMultiplier, commandBuffer + ); + KnockbackComponent knockbackComponent = null; + if (damageEffects != null && damageEffects.getKnockback() != null) { + knockbackComponent = commandBuffer.getComponent(targetRef, KnockbackComponent.getComponentType()); + if (knockbackComponent == null) { + knockbackComponent = new KnockbackComponent(); + commandBuffer.putComponent(targetRef, KnockbackComponent.getComponentType(), knockbackComponent); + } + + Knockback knockback = damageEffects.getKnockback(); + knockbackComponent.setVelocity(knockback.calculateVector(attackerPos, attackerDirection.getYaw(), targetPos).scale(knockbackMultiplier[0])); + knockbackComponent.setVelocityType(knockback.getVelocityType()); + knockbackComponent.setVelocityConfig(knockback.getVelocityConfig()); + knockbackComponent.setDuration(knockback.getDuration()); + } + + Player attackerPlayerComponent = commandBuffer.getComponent(attackerRef, Player.getComponentType()); + ItemStack itemInHand = attackerPlayerComponent != null && !attackerPlayerComponent.canApplyItemStackPenalties(attackerRef, commandBuffer) + ? null + : context.getHeldItem(); + Damage[] hits = DamageCalculatorSystems.queueDamageCalculator( + commandBuffer.getExternalData().getWorld(), damage, targetRef, context.getCommandBuffer(), source, itemInHand + ); + if (hits.length > 0) { + Damage firstDamage = hits[0]; + DamageCalculatorSystems.DamageSequence seq = new DamageCalculatorSystems.DamageSequence(sequentialHits, damageCalculator); + seq.setEntityStatOnHit(this.entityStatsOnHit); + firstDamage.putMetaObject(DamageCalculatorSystems.DAMAGE_SEQUENCE, seq); + if (damageEffects != null) { + damageEffects.addToDamage(firstDamage); + } + + for (Damage damageEvent : hits) { + if (knockbackComponent != null) { + damageEvent.putMetaObject(Damage.KNOCKBACK_COMPONENT, knockbackComponent); + } + + float damageValue = damageEvent.getAmount(); + damageValue += armorDamageModifiers[0]; + damageEvent.setAmount(damageValue * Math.max(0.0F, armorDamageModifiers[1])); + if (hit != null) { + damageEvent.putMetaObject(Damage.HIT_LOCATION, hit); + float hitAngleRad = TrigMathUtil.atan2(attackerPos.x - hit.x, attackerPos.z - hit.z); + hitAngleRad = MathUtil.wrapAngle(hitAngleRad - attackerDirection.getYaw()); + float hitAngleDeg = hitAngleRad * (180.0F / (float)Math.PI); + damageEvent.putMetaObject(Damage.HIT_ANGLE, hitAngleDeg); + } + + commandBuffer.invoke(targetRef, damageEvent); + } + + this.processDamage(context, hits); + } + + context.getInstanceStore().putMetaObject(QUEUED_DAMAGE, hits); + } + } + } + + private static void calculateKnockbackAndArmorModifiers( + @Nonnull DamageClass damageClass, + @Nonnull Object2FloatMap damage, + @Nonnull Ref targetRef, + @Nonnull Ref attackerRef, + float[] armorDamageModifiers, + double[] knockbackMultiplier, + @Nonnull ComponentAccessor componentAccessor + ) { + EffectControllerComponent effectControllerComponent = componentAccessor.getComponent(targetRef, EffectControllerComponent.getComponentType()); + if (effectControllerComponent != null) { + knockbackMultiplier[0] = IntStream.of(effectControllerComponent.getActiveEffectIndexes()) + .mapToObj(ix -> (EntityEffect)((IndexedLookupTableAssetMap)EntityEffect.getAssetStore().getAssetMap()).getAsset(ix)) + .filter(effect -> effect != null && effect.getApplicationEffects() != null) + .mapToDouble(effect -> effect.getApplicationEffects().getKnockbackMultiplier()) + .reduce(1.0, (a, b) -> a * b); + } + + if (EntityUtils.getEntity(attackerRef, componentAccessor) instanceof LivingEntity livingEntity) { + Inventory inventory = livingEntity.getInventory(); + if (inventory != null) { + ItemContainer armorContainer = inventory.getArmor(); + if (armorContainer != null) { + float knockbackEnhancementModifier = 1.0F; + + for (short i = 0; i < armorContainer.getCapacity(); i++) { + ItemStack itemStack = armorContainer.getItemStack(i); + if (itemStack != null && !itemStack.isEmpty()) { + Item item = itemStack.getItem(); + if (item.getArmor() != null) { + Map armorDamageEnhancementMap = item.getArmor().getDamageEnhancementValues(); + + for (DamageCause damageCause : damage.keySet()) { + if (armorDamageEnhancementMap != null) { + StaticModifier[] armorDamageEnhancementValue = armorDamageEnhancementMap.get(damageCause); + if (armorDamageEnhancementValue != null) { + for (StaticModifier staticModifier : armorDamageEnhancementValue) { + if (staticModifier.getCalculationType() == StaticModifier.CalculationType.ADDITIVE) { + armorDamageModifiers[0] += staticModifier.getAmount(); + } else { + armorDamageModifiers[1] += staticModifier.getAmount(); + } + } + } + } + + Map knockbackEnhancements = item.getArmor().getKnockbackEnhancements(); + if (knockbackEnhancements != null) { + knockbackEnhancementModifier += knockbackEnhancements.get(damageCause); + } + } + + StaticModifier[] damageClassModifier = item.getArmor().getDamageClassEnhancement().get(damageClass); + if (damageClassModifier != null) { + for (StaticModifier modifier : damageClassModifier) { + if (modifier.getCalculationType() == StaticModifier.CalculationType.ADDITIVE) { + armorDamageModifiers[0] += modifier.getAmount(); + } else { + armorDamageModifiers[1] += modifier.getAmount(); + } + } + } + } + } + } + + knockbackMultiplier[0] *= knockbackEnhancementModifier; + } + } + } + } + + public static class AngledDamage extends DamageEntityInteraction.TargetedDamage { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DamageEntityInteraction.AngledDamage.class, DamageEntityInteraction.AngledDamage::new, DamageEntityInteraction.TargetedDamage.CODEC + ) + .appendInherited( + new KeyedCodec<>("Angle", Codec.FLOAT), + (o, i) -> o.angleRad = i * (float) (Math.PI / 180.0), + o -> o.angleRad * (180.0F / (float)Math.PI), + (o, p) -> o.angleRad = p.angleRad + ) + .add() + .appendInherited( + new KeyedCodec<>("AngleDistance", Codec.FLOAT), + (o, i) -> o.angleDistanceRad = i * (float) (Math.PI / 180.0), + o -> o.angleDistanceRad * (180.0F / (float)Math.PI), + (o, p) -> o.angleDistanceRad = p.angleDistanceRad + ) + .add() + .build(); + protected float angleRad; + protected float angleDistanceRad; + + public AngledDamage() { + } + + @Nonnull + public com.hypixel.hytale.protocol.AngledDamage toAngledDamagePacket() { + com.hypixel.hytale.protocol.DamageEffects damageEffectsPacket = this.damageEffects == null ? null : this.damageEffects.toPacket(); + return new com.hypixel.hytale.protocol.AngledDamage( + this.angleRad, this.angleDistanceRad, damageEffectsPacket, Interaction.getInteractionIdOrUnknown(this.next) + ); + } + + @Nonnull + @Override + public String toString() { + return "AngledDamage{angleRad=" + this.angleRad + ", angleDistanceRad=" + this.angleDistanceRad + "} " + super.toString(); + } + } + + public static class EntityStatOnHit implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DamageEntityInteraction.EntityStatOnHit.class, DamageEntityInteraction.EntityStatOnHit::new + ) + .appendInherited( + new KeyedCodec<>("EntityStatId", Codec.STRING), + (entityStatOnHitInteraction, s) -> entityStatOnHitInteraction.entityStatId = s, + entityStatOnHitInteraction -> entityStatOnHitInteraction.entityStatId, + (entityStatOnHitInteraction, parent) -> entityStatOnHitInteraction.entityStatId = parent.entityStatId + ) + .documentation("The id of the EntityStat that will be affected by the interaction.") + .addValidator(Validators.nonNull()) + .addValidator(EntityStatType.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("Amount", Codec.FLOAT), + (entityStatOnHitInteraction, integer) -> entityStatOnHitInteraction.amount = integer, + entityStatOnHitInteraction -> entityStatOnHitInteraction.amount, + (entityStatOnHitInteraction, parent) -> entityStatOnHitInteraction.amount = parent.amount + ) + .documentation("The base amount for a single entity hit.") + .add() + .appendInherited( + new KeyedCodec<>("MultipliersPerEntitiesHit", Codec.FLOAT_ARRAY), + (entityStatOnHitInteraction, doubles) -> entityStatOnHitInteraction.multipliersPerEntitiesHit = doubles, + entityStatOnHitInteraction -> entityStatOnHitInteraction.multipliersPerEntitiesHit, + (entityStatOnHitInteraction, parent) -> entityStatOnHitInteraction.multipliersPerEntitiesHit = parent.multipliersPerEntitiesHit + ) + .documentation("An array of multipliers corresponding to how much the amount should be multiplied by for each entity hit.") + .addValidator(Validators.nonEmptyFloatArray()) + .add() + .appendInherited( + new KeyedCodec<>("MultiplierPerExtraEntityHit", Codec.FLOAT), + (entityStatOnHitInteraction, aDouble) -> entityStatOnHitInteraction.multiplierPerExtraEntityHit = aDouble, + entityStatOnHitInteraction -> entityStatOnHitInteraction.multiplierPerExtraEntityHit, + (entityStatOnHitInteraction, parent) -> entityStatOnHitInteraction.multiplierPerExtraEntityHit = parent.multiplierPerExtraEntityHit + ) + .documentation( + "When the number of entity hit is higher than the number of multipliers defined, the amount will be multiplied by this multiplier for each extra entity hit." + ) + .add() + .afterDecode(entityStatOnHitInteraction -> { + if (entityStatOnHitInteraction.entityStatId != null) { + entityStatOnHitInteraction.entityStatIndex = EntityStatType.getAssetMap().getIndex(entityStatOnHitInteraction.entityStatId); + } + }) + .build(); + public static final float[] DEFAULT_MULTIPLIERS_PER_ENTITIES_HIT = new float[]{1.0F, 0.6F, 0.4F, 0.2F, 0.1F}; + public static final float DEFAULT_MULTIPLIER_PER_EXTRA_ENTITY_HIT = 0.05F; + protected String entityStatId; + protected float amount; + protected float[] multipliersPerEntitiesHit = DEFAULT_MULTIPLIERS_PER_ENTITIES_HIT; + protected float multiplierPerExtraEntityHit = 0.05F; + private int entityStatIndex; + + public EntityStatOnHit() { + } + + public void processEntityStatsOnHit(int hits, @Nonnull EntityStatMap statMap) { + if (hits != 0) { + float multiplier; + if (hits <= this.multipliersPerEntitiesHit.length) { + multiplier = this.multipliersPerEntitiesHit[hits - 1]; + } else { + multiplier = this.multiplierPerExtraEntityHit; + } + + statMap.addStatValue(EntityStatMap.Predictable.SELF, this.entityStatIndex, multiplier * this.amount); + } + } + + @Nonnull + @Override + public String toString() { + return "EntityStatOnHit{entityStatId='" + + this.entityStatId + + "', amount=" + + this.amount + + ", multipliersPerEntitiesHit=" + + Arrays.toString(this.multipliersPerEntitiesHit) + + ", multiplierPerExtraEntityHit=" + + this.multiplierPerExtraEntityHit + + ", entityStatIndex=" + + this.entityStatIndex + + "}"; + } + + @Nonnull + public com.hypixel.hytale.protocol.EntityStatOnHit toPacket() { + return new com.hypixel.hytale.protocol.EntityStatOnHit( + this.entityStatIndex, this.amount, this.multipliersPerEntitiesHit, this.multiplierPerExtraEntityHit + ); + } + } + + public static class TargetedDamage { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DamageEntityInteraction.TargetedDamage.class, DamageEntityInteraction.TargetedDamage::new + ) + .appendInherited( + new KeyedCodec<>("DamageCalculator", DamageCalculator.CODEC), + (i, a) -> i.damageCalculator = a, + i -> i.damageCalculator, + (i, parent) -> i.damageCalculator = parent.damageCalculator + ) + .add() + .appendInherited( + new KeyedCodec<>("TargetEntityEffects", new MapCodec<>(TargetEntityEffect.CODEC, HashMap::new)), + (i, map) -> i.targetEntityEffects = map, + i -> i.targetEntityEffects, + (i, parent) -> i.targetEntityEffects = parent.targetEntityEffects + ) + .add() + .appendInherited( + new KeyedCodec<>("DamageEffects", DamageEffects.CODEC), + (i, o) -> i.damageEffects = o, + i -> i.damageEffects, + (i, parent) -> i.damageEffects = parent.damageEffects + ) + .add() + .appendInherited( + new KeyedCodec<>("Next", Interaction.CHILD_ASSET_CODEC), + (interaction, s) -> interaction.next = s, + interaction -> interaction.next, + (interaction, parent) -> interaction.next = parent.next + ) + .documentation("The interactions to run when this interaction succeeds.") + .addValidatorLate(() -> Interaction.VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + protected int index; + protected DamageCalculator damageCalculator; + protected Map targetEntityEffects; + protected DamageEffects damageEffects; + @Nullable + protected String next; + + public TargetedDamage() { + } + + @Nonnull + public com.hypixel.hytale.protocol.TargetedDamage toTargetedDamagePacket() { + return new com.hypixel.hytale.protocol.TargetedDamage(this.index, this.damageEffects.toPacket(), Interaction.getInteractionIdOrUnknown(this.next)); + } + + @Nonnull + @Override + public String toString() { + return "TargetedDamage{damageCalculator=" + + this.damageCalculator + + ", targetEntityEffects=" + + this.targetEntityEffects + + ", damageEffects=" + + this.damageEffects + + ", next='" + + this.next + + "'}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/DestroyConditionInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/DestroyConditionInteraction.java new file mode 100644 index 0000000..bfaface --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/DestroyConditionInteraction.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.meta.state.BreakValidatedBlockState; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@Deprecated +public class DestroyConditionInteraction extends SimpleBlockInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DestroyConditionInteraction.class, DestroyConditionInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Checks if the target block is destroyable") + .build(); + + public DestroyConditionInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i pos, + @Nonnull CooldownHandler cooldownHandler + ) { + Ref ref = context.getEntity(); + BlockState state = world.getChunk(ChunkUtil.indexChunkFromBlock(pos.x, pos.z)).getState(pos.x, pos.y, pos.z); + if (state instanceof BreakValidatedBlockState && !((BreakValidatedBlockState)state).canDestroy(ref, commandBuffer)) { + context.getState().state = InteractionState.Failed; + } else { + context.getState().state = InteractionState.Finished; + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/DoorInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/DoorInteraction.java new file mode 100644 index 0000000..56792aa --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/DoorInteraction.java @@ -0,0 +1,414 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.accessor.ChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DoorInteraction extends SimpleBlockInteraction { + private static final String OPEN_DOOR_IN = "OpenDoorIn"; + private static final String OPEN_DOOR_OUT = "OpenDoorOut"; + private static final String CLOSE_DOOR_IN = "CloseDoorIn"; + private static final String CLOSE_DOOR_OUT = "CloseDoorOut"; + private static final String DOOR_BLOCKED = "DoorBlocked"; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(DoorInteraction.class, DoorInteraction::new, SimpleBlockInteraction.CODEC) + .documentation("Opens/Closes a door") + .appendInherited( + new KeyedCodec<>("Horizontal", Codec.BOOLEAN), (t, i) -> t.horizontal = i, t -> t.horizontal, (t, parent) -> t.horizontal = parent.horizontal + ) + .documentation("Whether the door is horizontal (e.g. gates) or vertical (e.g. regular doors).") + .add() + .build(); + private boolean horizontal; + + public DoorInteraction() { + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)); + if (chunk != null) { + BlockType blockType = chunk.getBlockType(targetBlock); + int rotation = chunk.getRotationIndex(targetBlock.x, targetBlock.y, targetBlock.z); + RotationTuple rotationTuple = RotationTuple.get(rotation); + String blockState = blockType.getStateForBlock(blockType); + DoorInteraction.DoorState doorState = DoorInteraction.DoorState.fromBlockState(blockState); + Ref ref = context.getEntity(); + TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d entityPosition = transformComponent.getPosition(); + DoorInteraction.DoorState newDoorState; + if (doorState != DoorInteraction.DoorState.CLOSED) { + newDoorState = DoorInteraction.DoorState.CLOSED; + } else if (!this.horizontal && isInFrontOfDoor(targetBlock, rotationTuple.yaw(), entityPosition)) { + newDoorState = DoorInteraction.DoorState.OPENED_OUT; + } else { + newDoorState = DoorInteraction.DoorState.OPENED_IN; + } + + if (newDoorState != DoorInteraction.DoorState.CLOSED) { + DoorInteraction.DoorState checkResult = this.checkDoor(world, targetBlock, blockType, rotation, doorState, newDoorState); + if (checkResult == null) { + context.getState().state = InteractionState.Failed; + return; + } + + newDoorState = checkResult; + } + + DoorInteraction.DoorState stateDoubleDoor = getOppositeDoorState(doorState); + BlockType interactionBlockState = activateDoor(world, blockType, targetBlock, doorState, newDoorState); + boolean doubleDoor = this.checkForDoubleDoor(world, targetBlock, blockType, rotation, newDoorState, stateDoubleDoor); + if (interactionBlockState != null) { + Vector3d pos = new Vector3d(); + int hitboxTypeIndex = BlockType.getAssetMap().getAsset(blockType.getItem().getId()).getHitboxTypeIndex(); + BlockBoundingBoxes blockBoundingBoxes = BlockBoundingBoxes.getAssetMap().getAsset(hitboxTypeIndex); + BlockBoundingBoxes.RotatedVariantBoxes rotatedBoxes = blockBoundingBoxes.get(rotation); + Box hitbox = rotatedBoxes.getBoundingBox(); + if (doubleDoor) { + Vector3d offset = new Vector3d(hitbox.middleX(), 0.0, 0.0); + Rotation rotationToCheck = RotationTuple.get(rotation).yaw(); + pos.add(MathUtil.rotateVectorYAxis(offset, rotationToCheck.getDegrees(), false)); + pos.add(hitbox.middleX(), hitbox.middleY(), hitbox.middleZ()); + } else { + pos.add(hitbox.middleX(), hitbox.middleY(), hitbox.middleZ()); + } + + pos.add(targetBlock); + SoundUtil.playSoundEvent3d(ref, interactionBlockState.getInteractionSoundEventIndex(), pos, commandBuffer); + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } + + private boolean checkForDoubleDoor( + @Nonnull World world, + @Nonnull Vector3i blockPosition, + @Nonnull BlockType blockType, + int rotation, + @Nonnull DoorInteraction.DoorState fromState, + @Nonnull DoorInteraction.DoorState doorStateToCheck + ) { + DoorInteraction.DoorInfo doorToOpen = getDoubleDoor(world, blockPosition, blockType, rotation, doorStateToCheck); + if (doorToOpen == null) { + return false; + } else { + boolean otherDoorIsHorizontal = isHorizontalDoor(doorToOpen.blockType); + DoorInteraction.DoorState stateForDoubleDoor = otherDoorIsHorizontal ? fromState : getOppositeDoorState(fromState); + activateDoor(world, doorToOpen.blockType, doorToOpen.blockPosition, doorToOpen.doorState, stateForDoubleDoor); + return true; + } + } + + private static boolean isHorizontalDoor(@Nonnull BlockType blockType) { + String rootInteractionId = blockType.getInteractions().get(InteractionType.Use); + if (rootInteractionId == null) { + return false; + } else { + RootInteraction rootInteraction = RootInteraction.getAssetMap().getAsset(rootInteractionId); + if (rootInteraction == null) { + return false; + } else { + for (String interactionId : rootInteraction.getInteractionIds()) { + Interaction interaction = Interaction.getAssetMap().getAsset(interactionId); + if (interaction instanceof DoorInteraction doorInteraction) { + return doorInteraction.horizontal; + } + } + + return false; + } + } + } + + @Nullable + private DoorInteraction.DoorState checkDoor( + @Nonnull ChunkAccessor chunkAccessor, + @Nonnull Vector3i blockPosition, + @Nonnull BlockType blockType, + int rotation, + @Nonnull DoorInteraction.DoorState oldDoorState, + @Nonnull DoorInteraction.DoorState newDoorState + ) { + DoorInteraction.DoorInfo doubleDoor = getDoubleDoor(chunkAccessor, blockPosition, blockType, rotation, oldDoorState); + DoorInteraction.DoorState newOppositeDoorState = getOppositeDoorState(newDoorState); + String newOppositeDoorInteractionState = getInteractionState(oldDoorState, newOppositeDoorState); + String newDoorInteractionState = getInteractionState(oldDoorState, newDoorState); + if (canOpenDoor(chunkAccessor, blockPosition, newDoorInteractionState)) { + if (this.horizontal || doubleDoor == null || canOpenDoor(chunkAccessor, doubleDoor.blockPosition, newOppositeDoorInteractionState)) { + return newDoorState; + } else if (canOpenDoor(chunkAccessor, blockPosition, newOppositeDoorInteractionState) + && canOpenDoor(chunkAccessor, doubleDoor.blockPosition, newDoorInteractionState)) { + return newOppositeDoorState; + } else { + chunkAccessor.setBlockInteractionState(blockPosition, blockType, "DoorBlocked"); + return null; + } + } else if (!canOpenDoor(chunkAccessor, blockPosition, newOppositeDoorInteractionState) || this.horizontal) { + chunkAccessor.setBlockInteractionState(blockPosition, blockType, "DoorBlocked"); + return null; + } else if (doubleDoor != null && !canOpenDoor(chunkAccessor, doubleDoor.blockPosition, newDoorInteractionState)) { + chunkAccessor.setBlockInteractionState(blockPosition, blockType, "DoorBlocked"); + return null; + } else { + return newOppositeDoorState; + } + } + + @Nullable + private static BlockType activateDoor( + @Nonnull World world, + @Nonnull BlockType blockType, + @Nonnull Vector3i blockPosition, + @Nonnull DoorInteraction.DoorState fromState, + @Nonnull DoorInteraction.DoorState doorState + ) { + WorldChunk chunk = world.getChunk(ChunkUtil.indexChunkFromBlock(blockPosition.x, blockPosition.z)); + int rotationIndex = chunk.getRotationIndex(blockPosition.x, blockPosition.y, blockPosition.z); + BlockBoundingBoxes oldHitbox = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex()); + String interactionStateToSend = getInteractionState(fromState, doorState); + world.setBlockInteractionState(blockPosition, blockType, interactionStateToSend); + BlockType currentBlockType = world.getBlockType(blockPosition); + if (currentBlockType == null) { + return null; + } else { + BlockType newBlockType = currentBlockType.getBlockForState(interactionStateToSend); + if (oldHitbox != null) { + FillerBlockUtil.forEachFillerBlock( + oldHitbox.get(rotationIndex), (x, y, z) -> world.performBlockUpdate(blockPosition.x + x, blockPosition.y + y, blockPosition.z + z) + ); + } + + if (newBlockType != null) { + BlockBoundingBoxes newHitbox = BlockBoundingBoxes.getAssetMap().getAsset(newBlockType.getHitboxTypeIndex()); + if (newHitbox != null && newHitbox != oldHitbox) { + FillerBlockUtil.forEachFillerBlock( + newHitbox.get(rotationIndex), (x, y, z) -> world.performBlockUpdate(blockPosition.x + x, blockPosition.y + y, blockPosition.z + z) + ); + } + } + + return newBlockType; + } + } + + @Nullable + private static DoorInteraction.DoorInfo getDoubleDoor( + @Nonnull ChunkAccessor chunkAccessor, + @Nonnull Vector3i worldPosition, + @Nonnull BlockType blockType, + int rotation, + @Nonnull DoorInteraction.DoorState doorStateToCheck + ) { + Item blockTypeItem = blockType.getItem(); + if (blockTypeItem == null) { + return null; + } else { + BlockType blockTypeItemAsset = BlockType.getAssetMap().getAsset(blockTypeItem.getId()); + if (blockTypeItemAsset == null) { + return null; + } else { + int hitboxTypeIndex = blockTypeItemAsset.getHitboxTypeIndex(); + BlockBoundingBoxes blockBoundingBoxes = BlockBoundingBoxes.getAssetMap().getAsset(hitboxTypeIndex); + if (blockBoundingBoxes == null) { + return null; + } else { + BlockBoundingBoxes.RotatedVariantBoxes baseBoxes = blockBoundingBoxes.get(Rotation.None, Rotation.None, Rotation.None); + Vector3i offset = new Vector3i((int)baseBoxes.getBoundingBox().getMax().x * 2 - 1, 0, 0); + Rotation rotationToCheck = RotationTuple.get(rotation).yaw(); + Vector3i blockPosition = worldPosition.clone().add(MathUtil.rotateVectorYAxis(offset, rotationToCheck.getDegrees(), false)); + DoorInteraction.DoorInfo matchingDoor = getDoorAtPosition( + chunkAccessor, blockPosition.x, blockPosition.y, blockPosition.z, rotationToCheck.flip() + ); + if (matchingDoor != null && matchingDoor.doorState == doorStateToCheck) { + BlockType matchingBlockType = matchingDoor.blockType; + if (matchingDoor.filler != 0) { + return null; + } else { + int matchingDoorHitboxIndex = BlockType.getAssetMap().getAsset(matchingBlockType.getItem().getId()).getHitboxTypeIndex(); + return matchingDoorHitboxIndex == hitboxTypeIndex ? matchingDoor : null; + } + } else { + return null; + } + } + } + } + } + + @Nullable + public static DoorInteraction.DoorInfo getDoorAtPosition( + @Nonnull ChunkAccessor chunkAccessor, int x, int y, int z, @Nonnull Rotation rotationToCheck + ) { + WorldChunk chunk = chunkAccessor.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(x, z)); + if (chunk == null) { + return null; + } else { + BlockType blockType = chunk.getBlockType(x, y, z); + if (blockType != null && blockType.isDoor()) { + RotationTuple blockRotation = chunk.getRotation(x, y, z); + String blockState = blockType.getStateForBlock(blockType); + DoorInteraction.DoorState doorState = DoorInteraction.DoorState.fromBlockState(blockState); + Rotation doorRotation = blockRotation.yaw(); + int filler = chunk.getFiller(x, y, z); + return doorRotation != rotationToCheck ? null : new DoorInteraction.DoorInfo(blockType, filler, new Vector3i(x, y, z), doorState); + } else { + return null; + } + } + } + + private static boolean canOpenDoor(@Nonnull ChunkAccessor chunkAccessor, @Nonnull Vector3i blockPosition, @Nonnull String state) { + WorldChunk chunk = chunkAccessor.getChunk(ChunkUtil.indexChunkFromBlock(blockPosition.x, blockPosition.z)); + if (chunk == null) { + return false; + } else { + int blockId = chunk.getBlock(blockPosition.x, blockPosition.y, blockPosition.z); + BlockType originalBlockType = BlockType.getAssetMap().getAsset(blockId); + if (originalBlockType == null) { + return false; + } else { + BlockType variantBlockType = originalBlockType.getBlockForState(state); + if (variantBlockType == null) { + return false; + } else { + int rotation = chunk.getRotationIndex(blockPosition.x, blockPosition.y, blockPosition.z); + return chunkAccessor.testPlaceBlock( + blockPosition.x, blockPosition.y, blockPosition.z, variantBlockType, rotation, (blockX, blockY, blockZ, blockType, _rotation, filler) -> { + if (filler != 0) { + blockX -= FillerBlockUtil.unpackX(filler); + blockY -= FillerBlockUtil.unpackY(filler); + blockZ -= FillerBlockUtil.unpackZ(filler); + } + + return blockX == blockPosition.x && blockY == blockPosition.y && blockZ == blockPosition.z; + } + ); + } + } + } + } + + private static boolean isInFrontOfDoor(@Nonnull Vector3i blockPosition, @Nullable Rotation doorRotationYaw, @Nonnull Vector3d playerPosition) { + double doorRotationRad = Math.toRadians(doorRotationYaw != null ? doorRotationYaw.getDegrees() : 0.0); + Vector3d doorRotationVector = new Vector3d(TrigMathUtil.sin(doorRotationRad), 0.0, TrigMathUtil.cos(doorRotationRad)); + Vector3d direction = Vector3d.directionTo(blockPosition, playerPosition); + return direction.dot(doorRotationVector) < 0.0; + } + + @Nonnull + private static String getInteractionState(@Nonnull DoorInteraction.DoorState fromState, @Nonnull DoorInteraction.DoorState doorState) { + String stateToSend; + if (doorState == DoorInteraction.DoorState.CLOSED && fromState == DoorInteraction.DoorState.OPENED_IN) { + stateToSend = "CloseDoorOut"; + } else if (doorState == DoorInteraction.DoorState.CLOSED && fromState == DoorInteraction.DoorState.OPENED_OUT) { + stateToSend = "CloseDoorIn"; + } else if (doorState == DoorInteraction.DoorState.OPENED_IN) { + stateToSend = "OpenDoorOut"; + } else { + stateToSend = "OpenDoorIn"; + } + + return stateToSend; + } + + @Nonnull + private static DoorInteraction.DoorState getOppositeDoorState(@Nonnull DoorInteraction.DoorState doorState) { + return doorState == DoorInteraction.DoorState.OPENED_OUT + ? DoorInteraction.DoorState.OPENED_IN + : (doorState == DoorInteraction.DoorState.OPENED_IN ? DoorInteraction.DoorState.OPENED_OUT : DoorInteraction.DoorState.CLOSED); + } + + public static class DoorInfo { + private final BlockType blockType; + private final int filler; + private final Vector3i blockPosition; + private final DoorInteraction.DoorState doorState; + + public DoorInfo(BlockType blockType, int filler, Vector3i blockPosition, DoorInteraction.DoorState doorState) { + this.blockType = blockType; + this.filler = filler; + this.blockPosition = blockPosition; + this.doorState = doorState; + } + + public BlockType getBlockType() { + return this.blockType; + } + + public Vector3i getBlockPosition() { + return this.blockPosition; + } + + public DoorInteraction.DoorState getDoorState() { + return this.doorState; + } + } + + private static enum DoorState { + CLOSED, + OPENED_IN, + OPENED_OUT; + + private DoorState() { + } + + @Nonnull + public static DoorInteraction.DoorState fromBlockState(@Nullable String state) { + if (state == null) { + return CLOSED; + } else { + return switch (state) { + case "OpenDoorOut" -> OPENED_IN; + case "OpenDoorIn" -> OPENED_OUT; + default -> CLOSED; + }; + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/EquipItemInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/EquipItemInteraction.java new file mode 100644 index 0000000..60bc0f3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/EquipItemInteraction.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemArmor; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.MoveTransaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EquipItemInteraction extends SimpleInstantInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + EquipItemInteraction.class, EquipItemInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Equips the item being held.") + .build(); + + public EquipItemInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + Ref ref = context.getEntity(); + if (EntityUtils.getEntity(ref, commandBuffer) instanceof LivingEntity livingEntity) { + Inventory var15 = livingEntity.getInventory(); + byte activeSlot = context.getHeldItemSlot(); + ItemStack itemInHand = context.getHeldItem(); + if (itemInHand != null) { + Item item = itemInHand.getItem(); + if (item != null) { + ItemArmor armor = item.getArmor(); + if (armor != null) { + short slotId = (short)armor.getArmorSlot().ordinal(); + ItemContainer armorContainer = var15.getArmor(); + if (slotId <= armorContainer.getCapacity()) { + MoveTransaction stackTransaction = context.getHeldItemContainer() + .moveItemStackFromSlot(activeSlot, itemInHand.getQuantity(), armorContainer); + if (!stackTransaction.succeeded()) { + context.getState().state = InteractionState.Failed; + } + } + } + } + } + } + } + + @Nonnull + @Override + public String toString() { + return "EquipItemInteraction{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/IncreaseBackpackCapacityInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/IncreaseBackpackCapacityInteraction.java new file mode 100644 index 0000000..9d79db0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/IncreaseBackpackCapacityInteraction.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class IncreaseBackpackCapacityInteraction extends SimpleInstantInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + IncreaseBackpackCapacityInteraction.class, IncreaseBackpackCapacityInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Increase the player's backpack capacity.") + .appendInherited(new KeyedCodec<>("Capacity", Codec.SHORT), (i, s) -> i.capacity = s, i -> i.capacity, (i, parent) -> i.capacity = parent.capacity) + .documentation("Defines the amount by which the backpack capacity needs to be increased.") + .addValidator(Validators.min((short)1)) + .add() + .build(); + private short capacity = 1; + + public IncreaseBackpackCapacityInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + protected void firstRun(@NonNullDecl InteractionType type, @NonNullDecl InteractionContext context, @NonNullDecl CooldownHandler cooldownHandler) { + Ref ref = context.getEntity(); + Player playerComponent = context.getCommandBuffer().getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + Inventory inventory = playerComponent.getInventory(); + short newBackpackCapacity = (short)(inventory.getBackpack().getCapacity() + this.capacity); + inventory.resizeBackpack(newBackpackCapacity, null); + playerComponent.sendMessage(Message.translation("server.commands.inventory.backpack.size").param("capacity", inventory.getBackpack().getCapacity())); + context.getHeldItemContainer().removeItemStackFromSlot(context.getHeldItemSlot(), context.getHeldItem(), 1); + } + } + + @Override + public String toString() { + return "IncreaseBackpackCapacityInteraction{capacity=" + this.capacity + "}" + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/InterruptInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/InterruptInteraction.java new file mode 100644 index 0000000..d608acc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/InterruptInteraction.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.util.InteractionTarget; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InterruptInteraction extends SimpleInstantInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + InterruptInteraction.class, InterruptInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Interrupts interactions on the target entity.") + .appendInherited( + new KeyedCodec<>("Entity", InteractionTarget.CODEC), (o, i) -> o.entityTarget = i, o -> o.entityTarget, (o, p) -> o.entityTarget = p.entityTarget + ) + .documentation("The entity to target for this interaction.") + .addValidator(Validators.nonNull()) + .add() + .>appendInherited( + new KeyedCodec<>("InterruptTypes", InteractionModule.INTERACTION_TYPE_SET_CODEC), + (o, i) -> o.interruptTypes = i, + o -> o.interruptTypes, + (o, p) -> o.interruptTypes = p.interruptTypes + ) + .documentation("A set of interaction types that this interrupt will cancel") + .add() + .appendInherited( + new KeyedCodec<>("RequiredTag", Codec.STRING), (o, i) -> o.requiredTag = i, o -> o.requiredTag, (o, p) -> o.requiredTag = p.requiredTag + ) + .documentation("The tag that the root interaction of an active interaction chain must have to be interrupted.\nIf not set then no tag is required.") + .add() + .appendInherited( + new KeyedCodec<>("ExcludedTag", Codec.STRING), (o, i) -> o.excludedTag = i, o -> o.excludedTag, (o, p) -> o.excludedTag = p.excludedTag + ) + .documentation("The tag that if the root interaction of an active interaction chain has then it will not be interrupted.") + .add() + .afterDecode(o -> { + if (o.requiredTag != null) { + o.requiredTagIndex = AssetRegistry.getOrCreateTagIndex(o.requiredTag); + } + + if (o.excludedTag != null) { + o.excludedTagIndex = AssetRegistry.getOrCreateTagIndex(o.excludedTag); + } + }) + .build(); + private InteractionTarget entityTarget = InteractionTarget.USER; + @Nullable + private Set interruptTypes; + @Nullable + private String requiredTag; + private int requiredTagIndex = Integer.MIN_VALUE; + @Nullable + private String excludedTag; + private int excludedTagIndex = Integer.MIN_VALUE; + + public InterruptInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + Ref ref = context.getEntity(); + Ref targetRef = this.entityTarget.getEntity(context, ref); + if (targetRef != null) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + InteractionManager interactionManagerComponent = commandBuffer.getComponent(targetRef, InteractionModule.get().getInteractionManagerComponent()); + if (interactionManagerComponent != null) { + for (InteractionChain interactionChain : interactionManagerComponent.getChains().values()) { + if (this.interruptTypes == null || this.interruptTypes.contains(interactionChain.getType())) { + IntSet tags = interactionChain.getInitialRootInteraction().getData().getExpandedTagIndexes(); + if ((this.requiredTag == null || tags.contains(this.requiredTagIndex)) && (this.excludedTag == null || !tags.contains(this.excludedTagIndex)) + ) + { + interactionManagerComponent.cancelChains(interactionChain); + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/LaunchPadInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/LaunchPadInteraction.java new file mode 100644 index 0000000..6e1156b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/LaunchPadInteraction.java @@ -0,0 +1,92 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.ChangeVelocityType; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.universe.world.ParticleUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.meta.state.LaunchPad; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class LaunchPadInteraction extends SimpleBlockInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + LaunchPadInteraction.class, LaunchPadInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Applies the launchpad forces.") + .build(); + + public LaunchPadInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)); + if (chunk != null) { + BlockPosition baseTargetBlock = world.getBaseBlock(new BlockPosition(targetBlock.x, targetBlock.y, targetBlock.z)); + Ref blockEntityRef = chunk.getBlockComponentEntity(baseTargetBlock.x, baseTargetBlock.y, baseTargetBlock.z); + if (blockEntityRef != null) { + LaunchPad launchPadState = blockEntityRef.getStore().getComponent(blockEntityRef, LaunchPad.getComponentType()); + if (launchPadState != null) { + Ref ref = context.getEntity(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (!launchPadState.isPlayersOnly() || playerComponent != null) { + Velocity velocityComponent = commandBuffer.getComponent(ref, Velocity.getComponentType()); + + assert velocityComponent != null; + + velocityComponent.addInstruction( + new Vector3d(launchPadState.getVelocityX(), launchPadState.getVelocityY(), launchPadState.getVelocityZ()), null, ChangeVelocityType.Set + ); + Vector3d particlePos = targetBlock.toVector3d().add(0.5, 0.5, 0.5); + SpatialResource, EntityStore> playerSpatialResource = commandBuffer.getResource( + EntityModule.get().getPlayerSpatialResourceType() + ); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(particlePos, 75.0, results); + ParticleUtil.spawnParticleEffect("Splash", particlePos, results, commandBuffer); + } + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/LaunchProjectileInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/LaunchProjectileInteraction.java new file mode 100644 index 0000000..f059719 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/LaunchProjectileInteraction.java @@ -0,0 +1,127 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.gameplay.BrokenPenalties; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.projectile.config.Projectile; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.ProjectileComponent; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.modules.entity.component.Intangible; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.modules.projectile.config.BallisticData; +import com.hypixel.hytale.server.core.modules.projectile.config.BallisticDataProvider; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@Deprecated(forRemoval = true) +public class LaunchProjectileInteraction extends SimpleInstantInteraction implements BallisticDataProvider { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + LaunchProjectileInteraction.class, LaunchProjectileInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Launches a projectile.") + .appendInherited( + new KeyedCodec<>("ProjectileId", Codec.STRING), (i, o) -> i.projectileId = o, i -> i.projectileId, (i, p) -> i.projectileId = p.projectileId + ) + .addValidator(Validators.nonNull()) + .addValidator(Projectile.VALIDATOR_CACHE.getValidator().late()) + .add() + .build(); + protected String projectileId; + + public LaunchProjectileInteraction() { + } + + public String getProjectileId() { + return this.projectileId; + } + + @Nullable + @Override + public BallisticData getBallisticData() { + return Projectile.getAssetMap().getAsset(this.projectileId); + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + World world = commandBuffer.getExternalData().getWorld(); + Ref attackerRef = context.getEntity(); + if (EntityUtils.getEntity(attackerRef, commandBuffer) instanceof LivingEntity attackerLivingEntity) { + Transform lookVec = TargetUtil.getLook(attackerRef, commandBuffer); + Vector3d lookPosition = lookVec.getPosition(); + Vector3f lookRotation = lookVec.getRotation(); + UUIDComponent attackerUuidComponent = commandBuffer.getComponent(attackerRef, UUIDComponent.getComponentType()); + + assert attackerUuidComponent != null; + + UUID attackerUuid = attackerUuidComponent.getUuid(); + TimeResource timeResource = commandBuffer.getResource(TimeResource.getResourceType()); + Holder holder = ProjectileComponent.assembleDefaultProjectile(timeResource, this.projectileId, lookPosition, lookRotation); + ProjectileComponent projectileComponent = holder.getComponent(ProjectileComponent.getComponentType()); + + assert projectileComponent != null; + + holder.ensureComponent(Intangible.getComponentType()); + if (projectileComponent.getProjectile() == null) { + projectileComponent.initialize(); + if (projectileComponent.getProjectile() == null) { + return; + } + } + + projectileComponent.shoot( + holder, attackerUuid, lookPosition.getX(), lookPosition.getY(), lookPosition.getZ(), lookRotation.getYaw(), lookRotation.getPitch() + ); + commandBuffer.addEntity(holder, AddReason.SPAWN); + ItemStack itemInHand = context.getHeldItem(); + if (itemInHand != null && !itemInHand.isEmpty()) { + Item item = itemInHand.getItem(); + if (attackerLivingEntity.canDecreaseItemStackDurability(attackerRef, commandBuffer) && !itemInHand.isUnbreakable() && item.getWeapon() != null) { + Inventory inventory = attackerLivingEntity.getInventory(); + ItemContainer section = inventory.getSectionById(context.getHeldItemSectionId()); + if (section != null) { + attackerLivingEntity.updateItemStackDurability( + attackerRef, itemInHand, section, context.getHeldItemSlot(), -item.getDurabilityLossOnHit(), commandBuffer + ); + } + } + + if (itemInHand.isBroken()) { + BrokenPenalties brokenPenalties = world.getGameplayConfig().getItemDurabilityConfig().getBrokenPenalties(); + projectileComponent.applyBrokenPenalty((float)brokenPenalties.getWeapon(1.0)); + } + } + } + } + + @Override + protected void simulateFirstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/ModifyInventoryInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/ModifyInventoryInteraction.java new file mode 100644 index 0000000..3b82d61 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/ModifyInventoryInteraction.java @@ -0,0 +1,240 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TempAssetIdUtil; +import javax.annotation.Nonnull; + +public class ModifyInventoryInteraction extends SimpleInstantInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ModifyInventoryInteraction.class, ModifyInventoryInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Modifies an item in the inventory.") + .appendInherited( + new KeyedCodec<>("RequiredGameMode", ProtocolCodecs.GAMEMODE), + (interaction, s) -> interaction.requiredGameMode = s, + interaction -> interaction.requiredGameMode, + (interaction, parent) -> interaction.requiredGameMode = parent.requiredGameMode + ) + .add() + .appendInherited( + new KeyedCodec<>("ItemToRemove", ItemStack.CODEC), + (interaction, s) -> interaction.itemToRemove = s, + interaction -> interaction.itemToRemove, + (interaction, parent) -> interaction.itemToRemove = parent.itemToRemove + ) + .add() + .appendInherited( + new KeyedCodec<>("AdjustHeldItemQuantity", Codec.INTEGER), + (interaction, s) -> interaction.adjustHeldItemQuantity = s, + interaction -> interaction.adjustHeldItemQuantity, + (interaction, parent) -> interaction.adjustHeldItemQuantity = parent.adjustHeldItemQuantity + ) + .add() + .appendInherited( + new KeyedCodec<>("ItemToAdd", ItemStack.CODEC), + (interaction, s) -> interaction.itemToAdd = s, + interaction -> interaction.itemToAdd, + (interaction, parent) -> interaction.itemToAdd = parent.itemToAdd + ) + .add() + .appendInherited( + new KeyedCodec<>("AdjustHeldItemDurability", Codec.DOUBLE), + (interaction, s) -> interaction.adjustHeldItemDurability = s, + interaction -> interaction.adjustHeldItemDurability, + (interaction, parent) -> interaction.adjustHeldItemDurability = parent.adjustHeldItemDurability + ) + .add() + .appendInherited( + new KeyedCodec<>("BrokenItem", Codec.STRING), + (interaction, s) -> interaction.brokenItem = s, + interaction -> interaction.brokenItem, + (interaction, parent) -> interaction.brokenItem = parent.brokenItem + ) + .add() + .appendInherited( + new KeyedCodec<>("NotifyOnBreak", Codec.BOOLEAN), + (interaction, s) -> interaction.notifyOnBreak = s, + interaction -> interaction.notifyOnBreak, + (interaction, parent) -> interaction.notifyOnBreak = parent.notifyOnBreak + ) + .documentation( + "If true, shows the 'item broken' message and plays the break sound when durability reaches 0. Defaults to true for tools (no BrokenItem or same item), false for transformations (different BrokenItem)." + ) + .add() + .appendInherited( + new KeyedCodec<>("NotifyOnBreakMessage", Codec.STRING), + (interaction, s) -> interaction.notifyOnBreakMessage = s, + interaction -> interaction.notifyOnBreakMessage, + (interaction, parent) -> interaction.notifyOnBreakMessage = parent.notifyOnBreakMessage + ) + .documentation( + "Custom translation key for the break notification message. Supports {itemName} parameter. Defaults to 'server.general.repair.itemBroken' if not specified." + ) + .add() + .build(); + private GameMode requiredGameMode; + private ItemStack itemToRemove; + private int adjustHeldItemQuantity; + private ItemStack itemToAdd; + private double adjustHeldItemDurability; + private String brokenItem; + private Boolean notifyOnBreak; + private String notifyOnBreakMessage; + + public ModifyInventoryInteraction() { + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + Ref ref = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + boolean hasRequiredGameMode = this.requiredGameMode == null || playerComponent.getGameMode() == this.requiredGameMode; + if (hasRequiredGameMode) { + CombinedItemContainer combinedHotbarFirst = playerComponent.getInventory().getCombinedHotbarFirst(); + if (this.itemToRemove != null) { + ItemStackTransaction removeItemStack = combinedHotbarFirst.removeItemStack(this.itemToRemove, true, true); + if (!removeItemStack.succeeded()) { + context.getState().state = InteractionState.Failed; + return; + } + } + + ItemStack heldItem = context.getHeldItem(); + if (heldItem != null && this.adjustHeldItemQuantity != 0) { + if (this.adjustHeldItemQuantity < 0) { + ItemStackSlotTransaction slotTransaction = context.getHeldItemContainer() + .removeItemStackFromSlot(context.getHeldItemSlot(), heldItem, -this.adjustHeldItemQuantity); + if (!slotTransaction.succeeded()) { + context.getState().state = InteractionState.Failed; + return; + } + + context.setHeldItem(slotTransaction.getSlotAfter()); + } else { + SimpleItemContainer.addOrDropItemStack(commandBuffer, ref, combinedHotbarFirst, heldItem.withQuantity(this.adjustHeldItemQuantity)); + } + } + + if (this.itemToAdd != null) { + SimpleItemContainer.addOrDropItemStack(commandBuffer, ref, combinedHotbarFirst, this.itemToAdd); + } + + if (this.adjustHeldItemDurability != 0.0) { + ItemStack item = context.getHeldItem(); + if (item != null) { + ItemStack newItem = item.withIncreasedDurability(this.adjustHeldItemDurability); + boolean justBroke = newItem.isBroken() && !item.isBroken(); + if (newItem.isBroken() && this.brokenItem != null) { + if (this.brokenItem.equals("Empty")) { + newItem = null; + } else if (!this.brokenItem.equals(item.getItemId())) { + newItem = new ItemStack(this.brokenItem, 1); + } + } + + boolean isTransformation = this.brokenItem != null && !this.brokenItem.equals(item.getItemId()); + boolean shouldNotify = this.notifyOnBreak != null ? this.notifyOnBreak : !isTransformation; + if (justBroke && shouldNotify) { + Message itemNameMessage = Message.translation(item.getItem().getTranslationKey()); + String messageKey = this.notifyOnBreakMessage != null ? this.notifyOnBreakMessage : "server.general.repair.itemBroken"; + playerComponent.sendMessage(Message.translation(messageKey).param("itemName", itemNameMessage).color("#ff5555")); + PlayerRef playerRefComponent = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); + if (playerRefComponent != null) { + int soundEventIndex = TempAssetIdUtil.getSoundEventIndex("SFX_Item_Break"); + SoundUtil.playSoundEvent2dToPlayer(playerRefComponent, soundEventIndex, SoundCategory.SFX); + } + } + + ItemStackSlotTransaction slotTransaction = context.getHeldItemContainer().setItemStackForSlot(context.getHeldItemSlot(), newItem); + if (!slotTransaction.succeeded()) { + context.getState().state = InteractionState.Failed; + } else { + context.setHeldItem(newItem); + } + } + } + } + } + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.ModifyInventoryInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.ModifyInventoryInteraction p = (com.hypixel.hytale.protocol.ModifyInventoryInteraction)packet; + if (this.itemToRemove != null) { + p.itemToRemove = this.itemToRemove.toPacket(); + } + + p.adjustHeldItemQuantity = this.adjustHeldItemQuantity; + if (this.itemToAdd != null) { + p.itemToAdd = this.itemToAdd.toPacket(); + } + + if (this.brokenItem != null) { + p.brokenItem = this.brokenItem.toString(); + } + + p.adjustHeldItemDurability = this.adjustHeldItemDurability; + } + + @Nonnull + @Override + public String toString() { + return "ModifyInventoryInteraction{requiredGameMode=" + + this.requiredGameMode + + ", itemToRemove=" + + this.itemToRemove + + ", adjustHeldItemQuantity=" + + this.adjustHeldItemQuantity + + ", itemToAdd=" + + this.itemToAdd + + ", adjustHeldItemDurability=" + + this.adjustHeldItemDurability + + ", brokenItem=" + + this.brokenItem + + ", notifyOnBreak=" + + this.notifyOnBreak + + ", notifyOnBreakMessage='" + + this.notifyOnBreakMessage + + "'} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/OpenContainerInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/OpenContainerInteraction.java new file mode 100644 index 0000000..ab31a35 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/OpenContainerInteraction.java @@ -0,0 +1,132 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.windows.ContainerBlockWindow; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.meta.state.ItemContainerState; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class OpenContainerInteraction extends SimpleBlockInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + OpenContainerInteraction.class, OpenContainerInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Opens the container of the block currently being interacted with.") + .build(); + + public OpenContainerInteraction() { + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i pos, + @Nonnull CooldownHandler cooldownHandler + ) { + Ref ref = context.getEntity(); + Store store = ref.getStore(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + BlockState container = world.getState(pos.x, pos.y, pos.z, true); + if (container instanceof ItemContainerState itemContainerState) { + BlockType blockType = world.getBlockType(pos.x, pos.y, pos.z); + if (itemContainerState.isAllowViewing() && itemContainerState.canOpen(ref, commandBuffer)) { + UUIDComponent uuidComponent = commandBuffer.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID uuid = uuidComponent.getUuid(); + WorldChunk chunk = world.getChunk(ChunkUtil.indexChunkFromBlock(pos.x, pos.z)); + ContainerBlockWindow window = new ContainerBlockWindow( + pos.x, pos.y, pos.z, chunk.getRotationIndex(pos.x, pos.y, pos.z), blockType, itemContainerState.getItemContainer() + ); + Map windows = itemContainerState.getWindows(); + if (windows.putIfAbsent(uuid, window) == null) { + if (playerComponent.getPageManager().setPageWithWindows(ref, store, Page.Bench, true, window)) { + window.registerCloseEvent(event -> { + windows.remove(uuid, window); + BlockType currentBlockType = world.getBlockType(pos); + if (windows.isEmpty()) { + world.setBlockInteractionState(pos, currentBlockType, "CloseWindow"); + } + + BlockType interactionStatex = currentBlockType.getBlockForState("CloseWindow"); + if (interactionStatex != null) { + int soundEventIndexx = interactionStatex.getInteractionSoundEventIndex(); + if (soundEventIndexx != 0) { + int rotationIndexx = chunk.getRotationIndex(pos.x, pos.y, pos.z); + Vector3d soundPosx = new Vector3d(); + blockType.getBlockCenter(rotationIndexx, soundPosx); + soundPosx.add(pos); + SoundUtil.playSoundEvent3d(ref, soundEventIndexx, soundPosx, commandBuffer); + } + } + }); + if (windows.size() == 1) { + world.setBlockInteractionState(pos, blockType, "OpenWindow"); + } + + BlockType interactionState = blockType.getBlockForState("OpenWindow"); + if (interactionState == null) { + return; + } + + int soundEventIndex = interactionState.getInteractionSoundEventIndex(); + if (soundEventIndex == 0) { + return; + } + + int rotationIndex = chunk.getRotationIndex(pos.x, pos.y, pos.z); + Vector3d soundPos = new Vector3d(); + blockType.getBlockCenter(rotationIndex, soundPos); + soundPos.add(pos); + SoundUtil.playSoundEvent3d(ref, soundEventIndex, soundPos, commandBuffer); + } else { + windows.remove(uuid, window); + } + } + + itemContainerState.onOpen(ref, world, store); + } + } else { + playerComponent.sendMessage( + Message.translation("server.interactions.invalidBlockState") + .param("interaction", this.getClass().getSimpleName()) + .param("blockState", container != null ? container.getClass().getSimpleName() : "null") + ); + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/OpenCustomUIInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/OpenCustomUIInteraction.java new file mode 100644 index 0000000..48aaf50 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/OpenCustomUIInteraction.java @@ -0,0 +1,220 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.CustomUIPage; +import com.hypixel.hytale.server.core.entity.entities.player.pages.PageManager; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.plugin.PluginBase; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class OpenCustomUIInteraction extends SimpleInstantInteraction { + public static final CodecMapCodec PAGE_CODEC = new CodecMapCodec<>(); + public static final BuilderCodec CODEC = BuilderCodec.builder( + OpenCustomUIInteraction.class, OpenCustomUIInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Opens a custom ui page.") + .appendInherited( + new KeyedCodec<>("Page", PAGE_CODEC), + (o, v) -> o.customPageSupplier = v, + o -> o.customPageSupplier, + (o, p) -> o.customPageSupplier = p.customPageSupplier + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + private OpenCustomUIInteraction.CustomPageSupplier customPageSupplier; + + public OpenCustomUIInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + Ref ref = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + PageManager pageManager = playerComponent.getPageManager(); + if (pageManager.getCustomPage() == null) { + PlayerRef playerRef = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRef != null; + + CustomUIPage page = this.customPageSupplier.tryCreate(ref, commandBuffer, playerRef, context); + if (page != null) { + Store store = commandBuffer.getStore(); + pageManager.openCustomPage(ref, store, page); + } + } + } + } + + public static void registerCustomPageSupplier( + @Nonnull PluginBase plugin, Class tClass, String id, @Nonnull S supplier + ) { + plugin.getCodecRegistry(PAGE_CODEC) + .register( + id, + (Class)supplier.getClass(), + (Codec)BuilderCodec.builder(tClass, () -> supplier).build() + ); + } + + public static void registerSimple(@Nonnull PluginBase plugin, Class tClass, String id, @Nonnull Function supplier) { + registerCustomPageSupplier(plugin, tClass, id, (ref, componentAccessor, playerRef, context) -> supplier.apply(playerRef)); + } + + @Deprecated + public static void registerBlockCustomPage( + @Nonnull PluginBase plugin, + Class tClass, + String id, + @Nonnull Class stateClass, + @Nonnull OpenCustomUIInteraction.BlockCustomPageSupplier blockSupplier + ) { + registerBlockCustomPage(plugin, tClass, id, stateClass, blockSupplier, false); + } + + @Deprecated + public static void registerBlockCustomPage( + @Nonnull PluginBase plugin, + Class tClass, + String id, + @Nonnull Class stateClass, + @Nonnull OpenCustomUIInteraction.BlockCustomPageSupplier blockSupplier, + boolean createState + ) { + OpenCustomUIInteraction.CustomPageSupplier supplier = (ref, componentAccessor, playerRef, context) -> { + BlockPosition targetBlock = context.getTargetBlock(); + if (targetBlock == null) { + return null; + } else { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + BlockState state = world.getState(targetBlock.x, targetBlock.y, targetBlock.z, true); + if (state == null) { + if (createState) { + WorldChunk chunk = world.getChunk(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)); + state = BlockStateModule.get() + .createBlockState( + stateClass, + chunk, + new Vector3i(targetBlock.x, targetBlock.y, targetBlock.z), + chunk.getBlockType(targetBlock.x, targetBlock.y, targetBlock.z) + ); + chunk.setState(targetBlock.x, targetBlock.y, targetBlock.z, state); + } + + if (state == null) { + return null; + } + } + + return stateClass.isInstance(state) ? blockSupplier.tryCreate(playerRef, stateClass.cast(state)) : null; + } + }; + registerCustomPageSupplier(plugin, tClass, id, supplier); + } + + public static void registerBlockEntityCustomPage( + @Nonnull PluginBase plugin, Class tClass, String id, @Nonnull OpenCustomUIInteraction.BlockEntityCustomPageSupplier blockSupplier + ) { + OpenCustomUIInteraction.CustomPageSupplier supplier = (ref, componentAccessor, playerRef, context) -> { + BlockPosition targetBlock = context.getTargetBlock(); + if (targetBlock == null) { + return null; + } else { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)); + if (chunk == null) { + return null; + } else { + BlockPosition targetBaseBlock = world.getBaseBlock(targetBlock); + Ref blockEntityRef = chunk.getBlockComponentEntity(targetBaseBlock.x, targetBaseBlock.y, targetBaseBlock.z); + return blockEntityRef == null ? null : blockSupplier.tryCreate(playerRef, blockEntityRef); + } + } + }; + registerCustomPageSupplier(plugin, tClass, id, supplier); + } + + public static void registerBlockEntityCustomPage( + @Nonnull PluginBase plugin, + Class tClass, + String id, + @Nonnull OpenCustomUIInteraction.BlockEntityCustomPageSupplier blockSupplier, + Supplier> creator + ) { + OpenCustomUIInteraction.CustomPageSupplier supplier = (ref, componentAccessor, playerRef, context) -> { + BlockPosition targetBlock = context.getTargetBlock(); + if (targetBlock == null) { + return null; + } else { + Store store = ref.getStore(); + World world = store.getExternalData().getWorld(); + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)); + if (chunk == null) { + return null; + } else { + BlockPosition targetBaseBlock = world.getBaseBlock(targetBlock); + BlockComponentChunk blockComponentChunk = chunk.getBlockComponentChunk(); + int index = ChunkUtil.indexBlockInColumn(targetBaseBlock.x, targetBaseBlock.y, targetBaseBlock.z); + Ref blockEntityRef = blockComponentChunk.getEntityReference(index); + if (blockEntityRef == null) { + Holder holder = creator.get(); + holder.putComponent(BlockModule.BlockStateInfo.getComponentType(), new BlockModule.BlockStateInfo(index, chunk.getReference())); + blockEntityRef = world.getChunkStore().getStore().addEntity(holder, AddReason.SPAWN); + } + + return blockSupplier.tryCreate(playerRef, blockEntityRef); + } + } + }; + registerCustomPageSupplier(plugin, tClass, id, supplier); + } + + @FunctionalInterface + public interface BlockCustomPageSupplier { + CustomUIPage tryCreate(PlayerRef var1, T var2); + } + + @FunctionalInterface + public interface BlockEntityCustomPageSupplier { + CustomUIPage tryCreate(PlayerRef var1, Ref var2); + } + + @FunctionalInterface + public interface CustomPageSupplier { + @Nullable + CustomUIPage tryCreate(Ref var1, ComponentAccessor var2, PlayerRef var3, InteractionContext var4); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/OpenItemStackContainerInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/OpenItemStackContainerInteraction.java new file mode 100644 index 0000000..15dcfcb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/OpenItemStackContainerInteraction.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemStackContainerConfig; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.PageManager; +import com.hypixel.hytale.server.core.entity.entities.player.windows.ItemStackContainerWindow; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.container.ItemStackItemContainer; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class OpenItemStackContainerInteraction extends SimpleInstantInteraction { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + OpenItemStackContainerInteraction.class, OpenItemStackContainerInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Opens a container contained within the current held item.") + .build(); + + public OpenItemStackContainerInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + Ref ref = context.getEntity(); + Store store = ref.getStore(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + PageManager pageManager = playerComponent.getPageManager(); + if (pageManager.getCustomPage() == null) { + ItemStack heldItem = context.getHeldItem(); + if (!ItemStack.isEmpty(heldItem)) { + byte heldItemSlot = context.getHeldItemSlot(); + ItemContainer itemContainer = playerComponent.getInventory().getSectionById(context.getHeldItemSectionId()); + if (itemContainer != null) { + ItemStack itemStack = itemContainer.getItemStack(heldItemSlot); + ItemStackContainerConfig config = itemStack.getItem().getItemStackContainerConfig(); + ItemStackItemContainer itemStackItemContainer = ItemStackItemContainer.ensureConfiguredContainer(itemContainer, heldItemSlot, config); + if (itemStackItemContainer != null) { + pageManager.setPageWithWindows(ref, store, Page.Bench, true, new ItemStackContainerWindow(itemStackItemContainer)); + } + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/OpenPageInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/OpenPageInteraction.java new file mode 100644 index 0000000..0d86533 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/OpenPageInteraction.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; + +public class OpenPageInteraction extends SimpleInstantInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + OpenPageInteraction.class, OpenPageInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Opens a predefined page.") + .appendInherited(new KeyedCodec<>("Page", new EnumCodec<>(Page.class)), (o, v) -> o.page = v, o -> o.page, (o, p) -> o.page = p.page) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("CanCloseThroughInteraction", Codec.BOOLEAN), + (o, v) -> o.canCloseThroughInteraction = v, + o -> o.canCloseThroughInteraction, + (o, p) -> o.canCloseThroughInteraction = p.canCloseThroughInteraction + ) + .add() + .build(); + private static final Map USAGE_VALIDATOR_MAP = new ConcurrentHashMap<>(); + protected Page page; + protected boolean canCloseThroughInteraction; + + public OpenPageInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + Ref ref = context.getEntity(); + Store store = ref.getStore(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + OpenPageInteraction.PageUsageValidator validator = USAGE_VALIDATOR_MAP.get(this.page); + if (validator == null || validator.canUse(ref, playerComponent, context, context.getCommandBuffer())) { + playerComponent.getPageManager().setPage(ref, store, this.page, this.canCloseThroughInteraction); + } + } + } + + public static void registerUsageValidator(Page page, OpenPageInteraction.PageUsageValidator validator) { + USAGE_VALIDATOR_MAP.put(page, validator); + } + + @FunctionalInterface + public interface PageUsageValidator { + boolean canUse(Ref var1, Player var2, InteractionContext var3, ComponentAccessor var4); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/PlacementCountConditionInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/PlacementCountConditionInteraction.java new file mode 100644 index 0000000..42dcbc2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/PlacementCountConditionInteraction.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.blocktrack.BlockCounter; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import javax.annotation.Nonnull; + +public class PlacementCountConditionInteraction extends SimpleInstantInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + PlacementCountConditionInteraction.class, PlacementCountConditionInteraction::new, SimpleInstantInteraction.CODEC + ) + .appendInherited(new KeyedCodec<>("Block", Codec.STRING), (o, v) -> o.blockType = v, o -> o.blockType, (o, p) -> o.blockType = p.blockType) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Value", Codec.INTEGER), (o, v) -> o.value = v, o -> o.value, (o, p) -> o.value = p.value) + .add() + .appendInherited(new KeyedCodec<>("LessThan", Codec.BOOLEAN), (o, v) -> o.lessThan = v, o -> o.lessThan, (o, p) -> o.lessThan = p.lessThan) + .add() + .build(); + private String blockType; + private int value = 0; + private boolean lessThan = true; + + public PlacementCountConditionInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + BlockCounter counter = context.getCommandBuffer().getExternalData().getWorld().getChunkStore().getStore().getResource(BlockCounter.getResourceType()); + int blockCount = counter.getBlockPlacementCount(this.blockType); + if (this.lessThan) { + if (blockCount < this.value) { + context.getState().state = InteractionState.Finished; + } else { + context.getState().state = InteractionState.Failed; + } + } else if (blockCount > this.value) { + context.getState().state = InteractionState.Finished; + } else { + context.getState().state = InteractionState.Failed; + } + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Server; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/RefillContainerInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/RefillContainerInteraction.java new file mode 100644 index 0000000..7db9891 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/RefillContainerInteraction.java @@ -0,0 +1,285 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer; +import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RefillContainerInteraction extends SimpleBlockInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + RefillContainerInteraction.class, RefillContainerInteraction::new, SimpleBlockInteraction.CODEC + ) + .documentation("Refills a container item that is currently held.") + .appendInherited( + new KeyedCodec<>("States", new MapCodec<>(RefillContainerInteraction.RefillState.CODEC, HashMap::new)), + (interaction, value) -> interaction.refillStateMap = value, + interaction -> interaction.refillStateMap, + (o, p) -> o.refillStateMap = p.refillStateMap + ) + .addValidator(Validators.nonNull()) + .add() + .afterDecode(refillContainerInteraction -> { + refillContainerInteraction.allowedFluidIds = null; + refillContainerInteraction.fluidToState = null; + }) + .build(); + protected Map refillStateMap; + @Nullable + protected int[] allowedFluidIds; + @Nullable + protected Int2ObjectMap fluidToState; + + public RefillContainerInteraction() { + } + + protected int[] getAllowedFluidIds() { + if (this.allowedFluidIds != null) { + return this.allowedFluidIds; + } else { + this.allowedFluidIds = this.refillStateMap + .values() + .stream() + .map(RefillContainerInteraction.RefillState::getAllowedFluids) + .flatMap(Arrays::stream) + .mapToInt(key -> Fluid.getAssetMap().getIndex(key)) + .sorted() + .toArray(); + return this.allowedFluidIds; + } + } + + protected Int2ObjectMap getFluidToState() { + if (this.fluidToState != null) { + return this.fluidToState; + } else { + this.fluidToState = new Int2ObjectOpenHashMap<>(); + this.refillStateMap.forEach((s, refillState) -> { + for (String key : refillState.getAllowedFluids()) { + this.fluidToState.put(Fluid.getAssetMap().getIndex(key), s); + } + }); + return this.fluidToState; + } + } + + @Override + protected void interactWithBlock( + @Nonnull World world, + @Nonnull CommandBuffer commandBuffer, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nullable ItemStack itemInHand, + @Nonnull Vector3i targetBlock, + @Nonnull CooldownHandler cooldownHandler + ) { + Ref ref = context.getEntity(); + if (EntityUtils.getEntity(ref, commandBuffer) instanceof LivingEntity livingEntity) { + BlockPosition var24 = context.getClientState().blockPosition; + InteractionSyncData state = context.getState(); + if (var24 == null) { + state.state = InteractionState.Failed; + } else { + Ref section = world.getChunkStore() + .getChunkSectionReference(ChunkUtil.chunkCoordinate(var24.x), ChunkUtil.chunkCoordinate(var24.y), ChunkUtil.chunkCoordinate(var24.z)); + if (section != null) { + FluidSection fluidSection = section.getStore().getComponent(section, FluidSection.getComponentType()); + if (fluidSection != null) { + int fluidId = fluidSection.getFluidId(var24.x, var24.y, var24.z); + int[] allowedBlockIds = this.getAllowedFluidIds(); + if (allowedBlockIds != null && Arrays.binarySearch(allowedBlockIds, fluidId) < 0) { + state.state = InteractionState.Failed; + } else { + String newState = this.getFluidToState().get(fluidId); + if (newState == null) { + state.state = InteractionState.Failed; + } else { + ItemStack current = context.getHeldItem(); + Item newItemAsset = current.getItem().getItemForState(newState); + if (newItemAsset == null) { + state.state = InteractionState.Failed; + } else { + RefillContainerInteraction.RefillState refillState = this.refillStateMap.get(newState); + if (newItemAsset.getId().equals(current.getItemId())) { + if (refillState != null) { + double newDurability = MathUtil.maxValue(refillState.durability, current.getMaxDurability()); + if (newDurability <= current.getDurability()) { + state.state = InteractionState.Failed; + return; + } + + ItemStack newItem = current.withIncreasedDurability(newDurability); + ItemStackSlotTransaction transaction = context.getHeldItemContainer().setItemStackForSlot(context.getHeldItemSlot(), newItem); + if (!transaction.succeeded()) { + state.state = InteractionState.Failed; + return; + } + + context.setHeldItem(newItem); + } + } else { + ItemStackSlotTransaction removeEmptyTransaction = context.getHeldItemContainer() + .removeItemStackFromSlot(context.getHeldItemSlot(), current, 1); + if (!removeEmptyTransaction.succeeded()) { + state.state = InteractionState.Failed; + return; + } + + ItemStack refilledContainer = new ItemStack(newItemAsset.getId(), 1); + if (refillState != null && refillState.durability > 0.0) { + refilledContainer = refilledContainer.withDurability(refillState.durability); + } + + if (current.getQuantity() == 1) { + ItemStackSlotTransaction addFilledTransaction = context.getHeldItemContainer() + .setItemStackForSlot(context.getHeldItemSlot(), refilledContainer); + if (!addFilledTransaction.succeeded()) { + state.state = InteractionState.Failed; + return; + } + + context.setHeldItem(refilledContainer); + } else { + SimpleItemContainer.addOrDropItemStack( + commandBuffer, ref, livingEntity.getInventory().getCombinedHotbarFirst(), refilledContainer + ); + context.setHeldItem(context.getHeldItemContainer().getItemStack(context.getHeldItemSlot())); + } + } + + if (refillState != null && refillState.getTransformFluid() != null) { + int transformedFluid = Fluid.getFluidIdOrUnknown( + refillState.getTransformFluid(), "Unknown fluid %s", refillState.getTransformFluid() + ); + boolean placed = fluidSection.setFluid( + var24.x, var24.y, var24.z, transformedFluid, (byte)Fluid.getAssetMap().getAsset(transformedFluid).getMaxFluidLevel() + ); + if (!placed) { + state.state = InteractionState.Failed; + } + + world.performBlockUpdate(var24.x, var24.y, var24.z); + } + } + } + } + } + } + } + } + } + + @Override + protected void simulateInteractWithBlock( + @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable ItemStack itemInHand, @Nonnull World world, @Nonnull Vector3i targetBlock + ) { + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.RefillContainerInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.RefillContainerInteraction p = (com.hypixel.hytale.protocol.RefillContainerInteraction)packet; + p.refillFluids = this.getAllowedFluidIds(); + } + + @Nonnull + @Override + public String toString() { + return "RefillContainerInteraction{refillStateMap=" + + this.refillStateMap + + ", allowedBlockIds=" + + Arrays.toString(this.allowedFluidIds) + + ", blockToState=" + + this.fluidToState + + "} " + + super.toString(); + } + + protected static class RefillState { + public static final BuilderCodec CODEC = BuilderCodec.builder( + RefillContainerInteraction.RefillState.class, RefillContainerInteraction.RefillState::new + ) + .append( + new KeyedCodec<>("AllowedFluids", new ArrayCodec<>(Codec.STRING, String[]::new)), + (interaction, value) -> interaction.allowedFluids = value, + interaction -> interaction.allowedFluids + ) + .addValidator(Validators.nonNull()) + .add() + .addField( + new KeyedCodec<>("TransformFluid", Codec.STRING), + (interaction, value) -> interaction.transformFluid = value, + interaction -> interaction.transformFluid + ) + .addField(new KeyedCodec<>("Durability", Codec.DOUBLE), (interaction, value) -> interaction.durability = value, interaction -> interaction.durability) + .build(); + protected String[] allowedFluids; + protected String transformFluid; + protected double durability = -1.0; + + protected RefillState() { + } + + public String[] getAllowedFluids() { + return this.allowedFluids; + } + + public String getTransformFluid() { + return this.transformFluid; + } + + public double getDurability() { + return this.durability; + } + + @Nonnull + @Override + public String toString() { + return "RefillState{allowedFluids=" + + Arrays.toString((Object[])this.allowedFluids) + + ", transformFluid='" + + this.transformFluid + + "', durability=" + + this.durability + + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/SpawnPrefabInteraction.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/SpawnPrefabInteraction.java new file mode 100644 index 0000000..8797679 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/SpawnPrefabInteraction.java @@ -0,0 +1,119 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.FastRandom; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.prefab.PrefabStore; +import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabBufferUtil; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.IPrefabBuffer; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.PrefabUtil; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class SpawnPrefabInteraction extends SimpleInstantInteraction { + public static final BuilderCodec CODEC = BuilderCodec.builder( + SpawnPrefabInteraction.class, SpawnPrefabInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Spawns a prefab at the current location.") + .appendInherited(new KeyedCodec<>("PrefabPath", Codec.STRING), (o, i) -> o.prefabPath = i, o -> o.prefabPath, (o, p) -> o.prefabPath = p.prefabPath) + .add() + .appendInherited(new KeyedCodec<>("Offset", Vector3i.CODEC), (o, i) -> o.offset = i, o -> o.offset, (o, p) -> o.offset = p.offset) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("RotationYaw", Rotation.CODEC), (o, i) -> o.rotationYaw = i, o -> o.rotationYaw, (o, p) -> o.rotationYaw = p.rotationYaw + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("OriginSource", new EnumCodec<>(SpawnPrefabInteraction.OriginSource.class)), + (o, i) -> o.originSource = i, + o -> o.originSource, + (o, p) -> o.originSource = p.originSource + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("Force", Codec.BOOLEAN), (o, i) -> o.force = i, o -> o.force, (o, p) -> o.force = p.force) + .add() + .build(); + private String prefabPath; + private Vector3i offset = Vector3i.ZERO; + private Rotation rotationYaw = Rotation.None; + @Nonnull + private SpawnPrefabInteraction.OriginSource originSource = SpawnPrefabInteraction.OriginSource.ENTITY; + private boolean force; + + public SpawnPrefabInteraction() { + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + Path prefabRoot = PrefabStore.get().getAssetPrefabsPath(); + IPrefabBuffer prefab = PrefabBufferUtil.getCached(prefabRoot.resolve(this.prefabPath)); + if (prefab != null) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + World world = commandBuffer.getExternalData().getWorld(); + Ref ref = context.getEntity(); + TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d entityPosition = transformComponent.getPosition(); + Rotation yaw = this.rotationYaw; + Vector3i target; + switch (this.originSource) { + case ENTITY: + target = entityPosition.toVector3i(); + target.add(this.offset); + break; + case BLOCK: + BlockPosition targetBlock = context.getTargetBlock(); + if (targetBlock == null) { + return; + } + + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z)); + if (chunk == null) { + return; + } + + Rotation blockYaw = chunk.getRotation(targetBlock.x, targetBlock.y, targetBlock.z).yaw(); + target = new Vector3i(); + blockYaw.rotateYaw(this.offset, target); + yaw = yaw.add(blockYaw); + target.add(targetBlock.x, targetBlock.y, targetBlock.z); + break; + default: + throw new IllegalArgumentException("Unhandled origin source"); + } + + PrefabUtil.paste(prefab, world, target, yaw, this.force, new FastRandom(), commandBuffer); + } + } + + private static enum OriginSource { + ENTITY, + BLOCK; + + private OriginSource() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/DamageCalculator.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/DamageCalculator.java new file mode 100644 index 0000000..533c4fc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/DamageCalculator.java @@ -0,0 +1,192 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.map.Object2FloatMapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import it.unimi.dsi.fastutil.ints.Int2FloatMaps; +import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2FloatMap.Entry; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DamageCalculator { + public static final BuilderCodec CODEC = BuilderCodec.builder(DamageCalculator.class, DamageCalculator::new) + .appendInherited( + new KeyedCodec<>("Type", DamageCalculator.Type.CODEC), + (damageCalculator, type) -> damageCalculator.type = type, + damageCalculator -> damageCalculator.type, + (damageCalculator, parent) -> damageCalculator.type = parent.type + ) + .add() + .appendInherited( + new KeyedCodec<>("Class", DamageClass.CODEC), (o, v) -> o.damageClass = v, o -> o.damageClass, (o, p) -> o.damageClass = p.damageClass + ) + .documentation("The class of the damage being created, used by the damage system to apply modifiers based on equipment of the source.") + .addValidator(Validators.nonNull()) + .add() + .>appendInherited( + new KeyedCodec<>("BaseDamage", new Object2FloatMapCodec<>(Codec.STRING, Object2FloatOpenHashMap::new)), + (damageCalculator, map) -> damageCalculator.baseDamageRaw = map, + damageCalculator -> damageCalculator.baseDamageRaw, + (damageCalculator, parent) -> damageCalculator.baseDamageRaw = parent.baseDamageRaw + ) + .addValidator(DamageCause.VALIDATOR_CACHE.getMapKeyValidator()) + .add() + .appendInherited( + new KeyedCodec<>("SequentialModifierStep", Codec.FLOAT), + (damageCalculator, sequentialModifierStep) -> damageCalculator.sequentialModifierStep = sequentialModifierStep, + damageCalculator -> damageCalculator.sequentialModifierStep, + (damageCalculator, parent) -> damageCalculator.sequentialModifierStep = parent.sequentialModifierStep + ) + .add() + .appendInherited( + new KeyedCodec<>("SequentialModifierMinimum", Codec.FLOAT), + (damageCalculator, sequentialModifierMinimum) -> damageCalculator.sequentialModifierMinimum = sequentialModifierMinimum, + damageCalculator -> damageCalculator.sequentialModifierMinimum, + (damageCalculator, parent) -> damageCalculator.sequentialModifierMinimum = parent.sequentialModifierMinimum + ) + .add() + .appendInherited( + new KeyedCodec<>("RandomPercentageModifier", Codec.FLOAT), + (damageCalculator, randomPercentageModifier) -> damageCalculator.randomPercentageModifier = randomPercentageModifier, + damageCalculator -> damageCalculator.randomPercentageModifier, + (damageCalculator, parent) -> damageCalculator.randomPercentageModifier = parent.randomPercentageModifier + ) + .addValidator(Validators.greaterThanOrEqual(0.0F)) + .add() + .afterDecode(asset -> { + if (asset.baseDamageRaw != null) { + asset.baseDamage = new Int2FloatOpenHashMap(); + + for (Entry entry : asset.baseDamageRaw.object2FloatEntrySet()) { + int index = DamageCause.getAssetMap().getIndex(entry.getKey()); + asset.baseDamage.put(index, entry.getFloatValue()); + } + } + }) + .build(); + protected DamageCalculator.Type type = DamageCalculator.Type.ABSOLUTE; + @Nonnull + protected DamageClass damageClass = DamageClass.UNKNOWN; + protected Object2FloatMap baseDamageRaw; + protected float sequentialModifierStep; + protected float sequentialModifierMinimum; + protected float randomPercentageModifier; + @Nonnull + protected transient Int2FloatMap baseDamage = Int2FloatMaps.EMPTY_MAP; + + protected DamageCalculator() { + } + + @Nullable + public Object2FloatMap calculateDamage(double durationSeconds) { + if (this.baseDamageRaw != null && !this.baseDamageRaw.isEmpty()) { + Object2FloatMap outDamage = new Object2FloatOpenHashMap<>(this.baseDamage.size()); + float randomPercentageModifier = MathUtil.randomFloat(-this.randomPercentageModifier, this.randomPercentageModifier); + + for (it.unimi.dsi.fastutil.ints.Int2FloatMap.Entry entry : this.baseDamage.int2FloatEntrySet()) { + DamageCause damageCause = DamageCause.getAssetMap().getAsset(entry.getIntKey()); + float value = entry.getFloatValue(); + float damage = this.scaleDamage(durationSeconds, value); + damage += damage * randomPercentageModifier; + outDamage.put(damageCause, damage); + } + + return outDamage; + } else { + return null; + } + } + + private float scaleDamage(double durationSeconds, float damage) { + return switch (this.type) { + case DPS -> (float)durationSeconds * damage; + case ABSOLUTE -> damage; + }; + } + + public DamageCalculator.Type getType() { + return this.type; + } + + @Nonnull + public DamageClass getDamageClass() { + return this.damageClass; + } + + public float getSequentialModifierStep() { + return this.sequentialModifierStep; + } + + public float getSequentialModifierMinimum() { + return this.sequentialModifierMinimum; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o instanceof DamageCalculator that) { + if (Double.compare(that.sequentialModifierStep, this.sequentialModifierStep) != 0) { + return false; + } else if (Double.compare(that.sequentialModifierMinimum, this.sequentialModifierMinimum) != 0) { + return false; + } else if (Double.compare(that.randomPercentageModifier, this.randomPercentageModifier) != 0) { + return false; + } else if (this.type != that.type) { + return false; + } else { + return !Objects.equals(this.baseDamageRaw, that.baseDamageRaw) ? false : false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.type != null ? this.type.hashCode() : 0; + result = 31 * result + (this.baseDamageRaw != null ? this.baseDamageRaw.hashCode() : 0); + long temp = Double.doubleToLongBits(this.sequentialModifierStep); + result = 31 * result + (int)(temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.sequentialModifierMinimum); + result = 31 * result + (int)(temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.randomPercentageModifier); + return 31 * result + (int)(temp ^ temp >>> 32); + } + + @Nonnull + @Override + public String toString() { + return "DamageCalculator{type=" + + this.type + + ", baseDamage=" + + this.baseDamageRaw + + ", sequentialModifierStep=" + + this.sequentialModifierStep + + ", sequentialModifierMinimum=" + + this.sequentialModifierMinimum + + ", randomPercentageModifier=" + + this.randomPercentageModifier + + "}"; + } + + public static enum Type { + DPS, + ABSOLUTE; + + public static final EnumCodec CODEC = new EnumCodec<>(DamageCalculator.Type.class); + + private Type() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/DamageClass.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/DamageClass.java new file mode 100644 index 0000000..26c2f0e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/DamageClass.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat; + +import com.hypixel.hytale.codec.codecs.EnumCodec; + +public enum DamageClass { + UNKNOWN, + LIGHT, + CHARGED, + SIGNATURE; + + public static final EnumCodec CODEC = new EnumCodec<>(DamageClass.class); + + private DamageClass() { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/DamageEffects.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/DamageEffects.java new file mode 100644 index 0000000..86dd0f4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/DamageEffects.java @@ -0,0 +1,290 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.server.core.asset.type.camera.CameraEffect; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelParticle; +import com.hypixel.hytale.server.core.asset.type.particle.config.WorldParticle; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.validator.SoundEventValidators; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.ParticleUtil; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DamageEffects implements NetworkSerializable { + public static final BuilderCodec CODEC = BuilderCodec.builder(DamageEffects.class, DamageEffects::new) + .appendInherited( + new KeyedCodec<>("ModelParticles", ModelParticle.ARRAY_CODEC), + (damageEffects, s) -> damageEffects.modelParticles = s, + damageEffects -> damageEffects.modelParticles, + (damageEffects, parent) -> damageEffects.modelParticles = parent.modelParticles + ) + .add() + .appendInherited( + new KeyedCodec<>("WorldParticles", WorldParticle.ARRAY_CODEC), + (damageEffects, s) -> damageEffects.worldParticles = s, + damageEffects -> damageEffects.worldParticles, + (damageEffects, parent) -> damageEffects.worldParticles = parent.worldParticles + ) + .add() + .appendInherited( + new KeyedCodec<>("LocalSoundEventId", Codec.STRING), + (damageEffects, s) -> damageEffects.localSoundEventId = s, + damageEffects -> damageEffects.localSoundEventId, + (damageEffects, parent) -> damageEffects.localSoundEventId = parent.localSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("WorldSoundEventId", Codec.STRING), + (damageEffects, s) -> damageEffects.worldSoundEventId = s, + damageEffects -> damageEffects.worldSoundEventId, + (damageEffects, parent) -> damageEffects.worldSoundEventId = parent.worldSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .addValidator(SoundEventValidators.MONO) + .add() + .appendInherited( + new KeyedCodec<>("PlayerSoundEventId", Codec.STRING), + (damageEffects, s) -> damageEffects.playerSoundEventId = s, + damageEffects -> damageEffects.playerSoundEventId, + (damageEffects, parent) -> damageEffects.playerSoundEventId = parent.playerSoundEventId + ) + .addValidator(SoundEvent.VALIDATOR_CACHE.getValidator()) + .documentation("The sound to play to a player receiving the damage.") + .add() + .appendInherited( + new KeyedCodec<>("ViewDistance", Codec.DOUBLE), + (damageEffects, s) -> damageEffects.viewDistance = s, + damageEffects -> damageEffects.viewDistance, + (damageEffects, parent) -> damageEffects.viewDistance = parent.viewDistance + ) + .add() + .appendInherited( + new KeyedCodec<>("Knockback", Knockback.CODEC), + (damageEffects, s) -> damageEffects.knockback = s, + damageEffects -> damageEffects.knockback, + (damageEffects, parent) -> damageEffects.knockback = parent.knockback + ) + .add() + .appendInherited( + new KeyedCodec<>("CameraEffect", CameraEffect.CHILD_ASSET_CODEC), + (damageEffects, s) -> damageEffects.cameraEffectId = s, + damageEffects -> damageEffects.cameraEffectId, + (damageEffects, parent) -> damageEffects.cameraEffectId = parent.cameraEffectId + ) + .addValidator(CameraEffect.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited( + new KeyedCodec<>("StaminaDrainMultiplier", Codec.FLOAT), + (o, i) -> o.staminaDrainMultiplier = i, + o -> o.staminaDrainMultiplier, + (o, p) -> o.staminaDrainMultiplier = p.staminaDrainMultiplier + ) + .documentation("A multiplier to apply to any stamina drain caused by this damage.") + .add() + .afterDecode(DamageEffects::processConfig) + .build(); + protected ModelParticle[] modelParticles; + protected WorldParticle[] worldParticles; + @Nullable + protected String localSoundEventId = null; + protected transient int localSoundEventIndex; + @Nullable + protected String worldSoundEventId = null; + protected transient int worldSoundEventIndex; + @Nullable + protected String playerSoundEventId = null; + protected transient int playerSoundEventIndex; + protected double viewDistance = 75.0; + protected Knockback knockback; + protected String cameraEffectId; + protected int cameraEffectIndex = Integer.MIN_VALUE; + protected float staminaDrainMultiplier = 1.0F; + + public DamageEffects( + ModelParticle[] modelParticles, + WorldParticle[] worldParticles, + String localSoundEventId, + String worldSoundEventId, + double viewDistance, + Knockback knockback + ) { + this.modelParticles = modelParticles; + this.worldParticles = worldParticles; + this.localSoundEventId = localSoundEventId; + this.worldSoundEventId = worldSoundEventId; + this.viewDistance = viewDistance; + this.knockback = knockback; + this.processConfig(); + } + + protected DamageEffects() { + } + + public ModelParticle[] getModelParticles() { + return this.modelParticles; + } + + public WorldParticle[] getWorldParticles() { + return this.worldParticles; + } + + @Nullable + public String getWorldSoundEventId() { + return this.worldSoundEventId; + } + + public int getWorldSoundEventIndex() { + return this.worldSoundEventIndex; + } + + @Nullable + public String getLocalSoundEventId() { + return this.localSoundEventId; + } + + public int getLocalSoundEventIndex() { + return this.localSoundEventIndex; + } + + public double getViewDistance() { + return this.viewDistance; + } + + public Knockback getKnockback() { + return this.knockback; + } + + public String getCameraEffectId() { + return this.cameraEffectId; + } + + protected void processConfig() { + if (this.localSoundEventId != null) { + this.localSoundEventIndex = SoundEvent.getAssetMap().getIndex(this.localSoundEventId); + } + + if (this.worldSoundEventId != null) { + this.worldSoundEventIndex = SoundEvent.getAssetMap().getIndex(this.worldSoundEventId); + } + + if (this.playerSoundEventId != null) { + this.playerSoundEventIndex = SoundEvent.getAssetMap().getIndex(this.playerSoundEventId); + } + + if (this.cameraEffectId != null) { + this.cameraEffectIndex = CameraEffect.getAssetMap().getIndex(this.cameraEffectId); + } + } + + public void addToDamage(@Nonnull Damage damageEvent) { + if (this.worldSoundEventIndex != 0) { + damageEvent.putMetaObject(Damage.IMPACT_SOUND_EFFECT, new Damage.SoundEffect(this.worldSoundEventIndex)); + } + + if (this.playerSoundEventIndex != 0) { + damageEvent.putMetaObject(Damage.PLAYER_IMPACT_SOUND_EFFECT, new Damage.SoundEffect(this.playerSoundEventIndex)); + } + + if (this.worldParticles != null || this.modelParticles != null) { + damageEvent.putMetaObject(Damage.IMPACT_PARTICLES, new Damage.Particles(this.modelParticles, this.worldParticles, this.viewDistance)); + } + + if (this.cameraEffectId != null) { + damageEvent.putMetaObject(Damage.CAMERA_EFFECT, new Damage.CameraEffect(this.cameraEffectIndex)); + } + + if (this.staminaDrainMultiplier != 1.0F) { + damageEvent.putMetaObject(Damage.STAMINA_DRAIN_MULTIPLIER, this.staminaDrainMultiplier); + } + } + + public void spawnAtEntity(@Nonnull CommandBuffer commandBuffer, @Nonnull Ref ref) { + TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType()); + if (transformComponent != null) { + Vector3d position = transformComponent.getPosition(); + if (this.worldParticles != null) { + SpatialResource, EntityStore> playerSpatialResource = commandBuffer.getResource(EntityModule.get().getPlayerSpatialResourceType()); + ObjectList> playerRefs = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(position, this.viewDistance, playerRefs); + ParticleUtil.spawnParticleEffects(this.worldParticles, position, null, playerRefs, commandBuffer); + } + + if (this.worldSoundEventIndex != 0) { + SoundUtil.playSoundEvent3d(ref, this.worldSoundEventIndex, position, commandBuffer); + } + + if (this.playerSoundEventIndex != 0) { + PlayerRef playerRef = commandBuffer.getComponent(ref, PlayerRef.getComponentType()); + if (playerRef != null) { + SoundUtil.playSoundEvent2dToPlayer(playerRef, this.playerSoundEventIndex, SoundCategory.SFX); + } + } + } + } + + @Nonnull + @Override + public String toString() { + return "DamageEffects{modelParticles=" + + Arrays.toString((Object[])this.modelParticles) + + ", worldParticles=" + + Arrays.toString((Object[])this.worldParticles) + + ", localSoundEventId='" + + this.localSoundEventId + + "', localSoundEventIndex=" + + this.localSoundEventIndex + + ", worldSoundEventId='" + + this.worldSoundEventId + + "', worldSoundEventIndex=" + + this.worldSoundEventIndex + + ", viewDistance=" + + this.viewDistance + + ", knockback=" + + this.knockback + + ", cameraShakeId='" + + this.cameraEffectId + + "'}"; + } + + @Nonnull + public com.hypixel.hytale.protocol.DamageEffects toPacket() { + com.hypixel.hytale.protocol.ModelParticle[] modelParticlesProtocol = null; + if (!org.bouncycastle.util.Arrays.isNullOrEmpty((Object[])this.modelParticles)) { + modelParticlesProtocol = new com.hypixel.hytale.protocol.ModelParticle[this.modelParticles.length]; + + for (int i = 0; i < this.modelParticles.length; i++) { + modelParticlesProtocol[i] = this.modelParticles[i].toPacket(); + } + } + + com.hypixel.hytale.protocol.WorldParticle[] worldParticlesProtocol = null; + if (!org.bouncycastle.util.Arrays.isNullOrEmpty((Object[])this.worldParticles)) { + worldParticlesProtocol = new com.hypixel.hytale.protocol.WorldParticle[this.worldParticles.length]; + + for (int i = 0; i < this.worldParticles.length; i++) { + worldParticlesProtocol[i] = this.worldParticles[i].toPacket(); + } + } + + return new com.hypixel.hytale.protocol.DamageEffects( + modelParticlesProtocol, worldParticlesProtocol, this.localSoundEventIndex != 0 ? this.localSoundEventIndex : this.worldSoundEventIndex + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/DirectionalKnockback.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/DirectionalKnockback.java new file mode 100644 index 0000000..332469c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/DirectionalKnockback.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class DirectionalKnockback extends Knockback { + public static final BuilderCodec CODEC = BuilderCodec.builder( + DirectionalKnockback.class, DirectionalKnockback::new, Knockback.BASE_CODEC + ) + .append( + new KeyedCodec<>("RelativeX", Codec.DOUBLE), + (knockbackAttachment, d) -> knockbackAttachment.relativeX = d.floatValue(), + knockbackAttachment -> (double)knockbackAttachment.relativeX + ) + .add() + .append( + new KeyedCodec<>("VelocityY", Codec.DOUBLE), + (knockbackAttachment, d) -> knockbackAttachment.velocityY = d.floatValue(), + knockbackAttachment -> (double)knockbackAttachment.velocityY + ) + .add() + .append( + new KeyedCodec<>("RelativeZ", Codec.DOUBLE), + (knockbackAttachment, d) -> knockbackAttachment.relativeZ = d.floatValue(), + knockbackAttachment -> (double)knockbackAttachment.relativeZ + ) + .add() + .build(); + protected float relativeX; + protected float velocityY; + protected float relativeZ; + + public DirectionalKnockback() { + } + + @Nonnull + @Override + public Vector3d calculateVector(@Nonnull Vector3d source, float yaw, @Nonnull Vector3d target) { + Vector3d vector = source.clone().subtract(target); + if (vector.squaredLength() <= 1.0E-8) { + Vector3d lookVector = new Vector3d(0.0, 0.0, -1.0); + lookVector.rotateY(yaw); + vector.assign(lookVector); + } else { + vector.normalize(); + } + + if (this.relativeX != 0.0F || this.relativeZ != 0.0F) { + Vector3d rotation = new Vector3d(this.relativeX, 0.0, this.relativeZ); + rotation.rotateY(yaw); + vector.add(rotation); + } + + double x = vector.getX() * this.force; + double z = vector.getZ() * this.force; + double y = this.velocityY; + return new Vector3d(x, y, z); + } + + @Nonnull + @Override + public String toString() { + return "DirectionalKnockback{relativeX=" + this.relativeX + ", relativeZ=" + this.relativeZ + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/ForceKnockback.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/ForceKnockback.java new file mode 100644 index 0000000..789bf60 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/ForceKnockback.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class ForceKnockback extends Knockback { + public static final BuilderCodec CODEC = BuilderCodec.builder(ForceKnockback.class, ForceKnockback::new, Knockback.BASE_CODEC) + .appendInherited(new KeyedCodec<>("Direction", Vector3d.CODEC), (o, i) -> o.direction = i, o -> o.direction, (o, p) -> o.direction = p.direction) + .addValidator(Validators.nonNull()) + .add() + .afterDecode(i -> i.direction.normalize()) + .build(); + private Vector3d direction = Vector3d.UP; + + public ForceKnockback() { + } + + @Nonnull + @Override + public Vector3d calculateVector(Vector3d source, float yaw, Vector3d target) { + Vector3d vel = this.direction.clone(); + vel.rotateY(yaw); + vel.scale(this.force); + return vel; + } + + @Nonnull + @Override + public String toString() { + return "ForceKnockback{direction=" + this.direction + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/Knockback.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/Knockback.java new file mode 100644 index 0000000..598e249 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/Knockback.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.ChangeVelocityType; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.modules.splitvelocity.VelocityConfig; +import javax.annotation.Nonnull; + +public abstract class Knockback { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type", true); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(Knockback.class) + .append( + new KeyedCodec<>("Force", Codec.DOUBLE), + (knockbackAttachment, d) -> knockbackAttachment.force = d.floatValue(), + knockbackAttachment -> (double)knockbackAttachment.force + ) + .add() + .append( + new KeyedCodec<>("Duration", Codec.FLOAT), + (knockbackAttachment, f) -> knockbackAttachment.duration = f, + knockbackAttachment -> knockbackAttachment.duration + ) + .addValidator(Validators.greaterThanOrEqual(0.0F)) + .documentation("The duration for which the knockback force should be continuously applied. If 0, force is applied once.") + .add() + .append( + new KeyedCodec<>("VelocityType", ProtocolCodecs.CHANGE_VELOCITY_TYPE_CODEC), + (knockbackAttachment, d) -> knockbackAttachment.velocityType = d, + knockbackAttachment -> knockbackAttachment.velocityType + ) + .add() + .appendInherited( + new KeyedCodec<>("VelocityConfig", VelocityConfig.CODEC), + (o, i) -> o.velocityConfig = i, + o -> o.velocityConfig, + (o, p) -> o.velocityConfig = p.velocityConfig + ) + .add() + .build(); + protected float force; + protected float duration; + protected ChangeVelocityType velocityType = ChangeVelocityType.Add; + private VelocityConfig velocityConfig; + + protected Knockback() { + } + + public float getForce() { + return this.force; + } + + public float getDuration() { + return this.duration; + } + + public ChangeVelocityType getVelocityType() { + return this.velocityType; + } + + public VelocityConfig getVelocityConfig() { + return this.velocityConfig; + } + + public abstract Vector3d calculateVector(Vector3d var1, float var2, Vector3d var3); + + @Nonnull + @Override + public String toString() { + return "Knockback{, force=" + + this.force + + ", duration=" + + this.duration + + ", velocityType=" + + this.velocityType + + ", velocityConfig=" + + this.velocityConfig + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/PointKnockback.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/PointKnockback.java new file mode 100644 index 0000000..4f47226 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/PointKnockback.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class PointKnockback extends Knockback { + public static final BuilderCodec CODEC = BuilderCodec.builder(PointKnockback.class, PointKnockback::new, Knockback.BASE_CODEC) + .append( + new KeyedCodec<>("VelocityY", Codec.DOUBLE), + (knockbackAttachment, d) -> knockbackAttachment.velocityY = d.floatValue(), + knockbackAttachment -> (double)knockbackAttachment.velocityY + ) + .add() + .append( + new KeyedCodec<>("RotateY", Codec.INTEGER), + (knockbackAttachment, i) -> knockbackAttachment.rotateY = i, + knockbackAttachment -> knockbackAttachment.rotateY + ) + .add() + .append( + new KeyedCodec<>("OffsetX", Codec.INTEGER), + (knockbackAttachment, i) -> knockbackAttachment.offsetX = i, + knockbackAttachment -> knockbackAttachment.offsetX + ) + .add() + .append( + new KeyedCodec<>("OffsetZ", Codec.INTEGER), + (knockbackAttachment, i) -> knockbackAttachment.offsetZ = i, + knockbackAttachment -> knockbackAttachment.offsetZ + ) + .add() + .build(); + protected float velocityY; + protected int rotateY; + protected int offsetX; + protected int offsetZ; + + public PointKnockback() { + } + + @Nonnull + @Override + public Vector3d calculateVector(@Nonnull Vector3d source, float yaw, @Nonnull Vector3d target) { + Vector3d from = source; + if (this.offsetX != 0 || this.offsetZ != 0) { + from = new Vector3d(this.offsetX, 0.0, this.offsetZ); + from.rotateY(yaw * (180.0F / (float)Math.PI)); + from.add(source); + } + + Vector3d vector = Vector3d.directionTo(from, target).normalize(); + if (this.rotateY != 0) { + vector.rotateY(this.rotateY); + } + + double x = vector.getX() * this.force; + double z = vector.getZ() * this.force; + double y = this.velocityY; + return new Vector3d(x, y, z); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/TargetEntityEffect.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/TargetEntityEffect.java new file mode 100644 index 0000000..777771c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/config/server/combat/TargetEntityEffect.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.Object2DoubleMapCodec; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.OverlapBehavior; +import it.unimi.dsi.fastutil.objects.Object2DoubleMap; +import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap; +import javax.annotation.Nonnull; + +public class TargetEntityEffect { + public static final BuilderCodec CODEC = BuilderCodec.builder(TargetEntityEffect.class, TargetEntityEffect::new) + .addField(new KeyedCodec<>("Duration", Codec.DOUBLE), (target, value) -> target.duration = value.floatValue(), target -> (double)target.duration) + .addField(new KeyedCodec<>("Chance", Codec.DOUBLE), (target, value) -> target.chance = value, target -> target.chance) + .addField( + new KeyedCodec<>("EntityTypeDurationModifiers", new Object2DoubleMapCodec<>(Codec.STRING, Object2DoubleOpenHashMap::new)), + (target, map) -> target.entityTypeDurationModifiers = map, + target -> target.entityTypeDurationModifiers + ) + .addField(new KeyedCodec<>("OverlapBehavior", OverlapBehavior.CODEC), (target, value) -> target.overlapBehavior = value, target -> target.overlapBehavior) + .build(); + protected float duration; + protected double chance = 1.0; + protected Object2DoubleMap entityTypeDurationModifiers; + protected OverlapBehavior overlapBehavior = OverlapBehavior.IGNORE; + + public TargetEntityEffect(float duration, double chance, Object2DoubleMap entityTypeDurationModifiers, OverlapBehavior overlapBehavior) { + this.duration = duration; + this.chance = chance; + this.entityTypeDurationModifiers = entityTypeDurationModifiers; + this.overlapBehavior = overlapBehavior; + } + + protected TargetEntityEffect() { + } + + public float getDuration() { + return this.duration; + } + + public double getChance() { + return this.chance; + } + + public Object2DoubleMap getEntityTypeDurationModifiers() { + return this.entityTypeDurationModifiers; + } + + public OverlapBehavior getOverlapBehavior() { + return this.overlapBehavior; + } + + @Nonnull + @Override + public String toString() { + return "TargetEntityEffect{duration=" + + this.duration + + ", chance=" + + this.chance + + ", entityTypeDurationModifiers=" + + this.entityTypeDurationModifiers + + ", overlapBehavior=" + + this.overlapBehavior + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/operation/JumpOperation.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/operation/JumpOperation.java new file mode 100644 index 0000000..94a9b76 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/operation/JumpOperation.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.operation; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionState; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class JumpOperation implements Operation { + private final Label target; + + protected JumpOperation(Label target) { + this.target = target; + } + + @Override + public void tick( + @Nonnull Ref ref, + @Nonnull LivingEntity entity, + boolean firstRun, + float time, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nonnull CooldownHandler cooldownHandler + ) { + context.setOperationCounter(this.target.getIndex()); + context.getState().state = InteractionState.Finished; + } + + @Override + public void simulateTick( + @Nonnull Ref ref, + @Nonnull LivingEntity entity, + boolean firstRun, + float time, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nonnull CooldownHandler cooldownHandler + ) { + context.setOperationCounter(this.target.getIndex()); + context.getState().state = InteractionState.Finished; + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.None; + } + + @Nonnull + @Override + public String toString() { + return "JumpOperation{target=" + this.target + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/operation/Label.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/operation/Label.java new file mode 100644 index 0000000..e66e35b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/operation/Label.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.operation; + +import javax.annotation.Nonnull; + +public class Label { + protected int index; + + protected Label(int index) { + this.index = index; + } + + public int getIndex() { + return this.index; + } + + @Nonnull + @Override + public String toString() { + return "Label{index=" + this.index + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/operation/Operation.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/operation/Operation.java new file mode 100644 index 0000000..c816046 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/operation/Operation.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.operation; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.InteractionRules; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.ints.IntSet; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface Operation { + void tick( + @Nonnull Ref var1, + @Nonnull LivingEntity var2, + boolean var3, + float var4, + @Nonnull InteractionType var5, + @Nonnull InteractionContext var6, + @Nonnull CooldownHandler var7 + ); + + void simulateTick( + @Nonnull Ref var1, + @Nonnull LivingEntity var2, + boolean var3, + float var4, + @Nonnull InteractionType var5, + @Nonnull InteractionContext var6, + @Nonnull CooldownHandler var7 + ); + + default void handle(@Nonnull Ref ref, boolean firstRun, float time, @Nonnull InteractionType type, @Nonnull InteractionContext context) { + } + + WaitForDataFrom getWaitForDataFrom(); + + @Nullable + default InteractionRules getRules() { + return null; + } + + default Int2ObjectMap getTags() { + return Int2ObjectMaps.emptyMap(); + } + + default Operation getInnerOperation() { + Operation op = this; + + while (op instanceof Operation.NestedOperation) { + op = ((Operation.NestedOperation)op).inner(); + } + + return op; + } + + public interface NestedOperation { + Operation inner(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/operation/OperationsBuilder.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/operation/OperationsBuilder.java new file mode 100644 index 0000000..21ec721 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/operation/OperationsBuilder.java @@ -0,0 +1,109 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.operation; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.InteractionRules; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nonnull; + +public class OperationsBuilder { + @Nonnull + private final List operationList = new ObjectArrayList<>(); + + public OperationsBuilder() { + } + + @Nonnull + public Label createLabel() { + return new Label(this.operationList.size()); + } + + @Nonnull + public Label createUnresolvedLabel() { + return new Label(Integer.MIN_VALUE); + } + + public void resolveLabel(@Nonnull Label label) { + if (label.index != Integer.MIN_VALUE) { + throw new IllegalArgumentException("Label already resolved"); + } else { + label.index = this.operationList.size(); + } + } + + public void jump(@Nonnull Label target) { + this.operationList.add(new JumpOperation(target)); + } + + public void addOperation(@Nonnull Operation operation) { + this.operationList.add(operation); + } + + public void addOperation(@Nonnull Operation operation, Label... labels) { + this.operationList.add(new OperationsBuilder.LabelOperation(operation, labels)); + } + + @Nonnull + public Operation[] build() { + return this.operationList.toArray(Operation[]::new); + } + + private record LabelOperation(Operation inner, Label[] labels) implements Operation, Operation.NestedOperation { + @Override + public void tick( + @Nonnull Ref ref, + @Nonnull LivingEntity entity, + boolean firstRun, + float time, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nonnull CooldownHandler cooldownHandler + ) { + context.setLabels(this.labels); + this.inner.tick(ref, entity, firstRun, time, type, context, cooldownHandler); + } + + @Override + public void simulateTick( + @Nonnull Ref ref, + @Nonnull LivingEntity entity, + boolean firstRun, + float time, + @Nonnull InteractionType type, + @Nonnull InteractionContext context, + @Nonnull CooldownHandler cooldownHandler + ) { + context.setLabels(this.labels); + this.inner.simulateTick(ref, entity, firstRun, time, type, context, cooldownHandler); + } + + @Override + public void handle(@Nonnull Ref ref, boolean firstRun, float time, @Nonnull InteractionType type, @Nonnull InteractionContext context) { + context.setLabels(this.labels); + this.inner.handle(ref, firstRun, time, type, context); + } + + @Override + public WaitForDataFrom getWaitForDataFrom() { + return this.inner.getWaitForDataFrom(); + } + + @Override + public InteractionRules getRules() { + return this.inner.getRules(); + } + + @Nonnull + @Override + public String toString() { + return "LabelOperation{inner=" + this.inner + ", labels=" + Arrays.toString((Object[])this.labels) + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/interaction/util/InteractionTarget.java b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/util/InteractionTarget.java new file mode 100644 index 0000000..2bb321f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/interaction/util/InteractionTarget.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.core.modules.interaction.interaction.util; + +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public enum InteractionTarget { + USER, + OWNER, + TARGET; + + public static final EnumCodec CODEC = new EnumCodec<>(InteractionTarget.class) + .documentKey(USER, "Causes the interaction to target the entity that triggered/owns the interaction chain.") + .documentKey(TARGET, "Causes the interaction to target the entity that is the target of the interaction chain."); + + private InteractionTarget() { + } + + @Nullable + public Ref getEntity(@Nonnull InteractionContext ctx, @Nonnull Ref ref) { + return switch (this) { + case USER -> ctx.getEntity(); + case OWNER -> ctx.getOwningEntity(); + case TARGET -> ctx.getTargetEntity(); + }; + } + + @Nullable + @Deprecated + public T getEntity(@Nonnull InteractionContext ctx, @Nonnull Ref ref, @Nonnull Class clazz) { + Ref e = this.getEntity(ctx, ref); + Entity legacy = EntityUtils.getEntity(e, ctx.getCommandBuffer()); + return (T)(clazz.isInstance(legacy) ? legacy : null); + } + + @Nonnull + public com.hypixel.hytale.protocol.InteractionTarget toProtocol() { + return switch (this) { + case USER -> com.hypixel.hytale.protocol.InteractionTarget.User; + case OWNER -> com.hypixel.hytale.protocol.InteractionTarget.Owner; + case TARGET -> com.hypixel.hytale.protocol.InteractionTarget.Target; + }; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/suppliers/ItemRepairPageSupplier.java b/src/com/hypixel/hytale/server/core/modules/interaction/suppliers/ItemRepairPageSupplier.java new file mode 100644 index 0000000..b46e37c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/suppliers/ItemRepairPageSupplier.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.core.modules.interaction.suppliers; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.CustomUIPage; +import com.hypixel.hytale.server.core.entity.entities.player.pages.itemrepair.ItemRepairPage; +import com.hypixel.hytale.server.core.inventory.ItemContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenCustomUIInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class ItemRepairPageSupplier implements OpenCustomUIInteraction.CustomPageSupplier { + public static final BuilderCodec CODEC = BuilderCodec.builder(ItemRepairPageSupplier.class, ItemRepairPageSupplier::new) + .appendInherited( + new KeyedCodec<>("RepairPenalty", Codec.DOUBLE), + (data, o) -> data.repairPenalty = o, + data -> data.repairPenalty, + (data, parent) -> data.repairPenalty = parent.repairPenalty + ) + .add() + .build(); + protected double repairPenalty; + + public ItemRepairPageSupplier() { + } + + @Override + public CustomUIPage tryCreate( + @Nonnull Ref ref, + @Nonnull ComponentAccessor componentAccessor, + @Nonnull PlayerRef playerRef, + @Nonnull InteractionContext context + ) { + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + ItemContext itemContext = context.createHeldItemContext(); + return itemContext == null + ? null + : new ItemRepairPage(playerRef, playerComponent.getInventory().getCombinedArmorHotbarUtilityStorage(), this.repairPenalty, itemContext); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/interaction/system/InteractionSystems.java b/src/com/hypixel/hytale/server/core/modules/interaction/system/InteractionSystems.java new file mode 100644 index 0000000..f84021f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/interaction/system/InteractionSystems.java @@ -0,0 +1,273 @@ +package com.hypixel.hytale.server.core.modules.interaction.system; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChain; +import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChains; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageModule; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsSystems; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.InteractionSimulationHandler; +import com.hypixel.hytale.server.core.modules.interaction.Interactions; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InteractionSystems { + public InteractionSystems() { + } + + public static class CleanUpSystem extends RefSystem { + @Nonnull + private final ComponentType interactionComponentType = InteractionModule.get().getInteractionManagerComponent(); + + public CleanUpSystem() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + InteractionManager interactionManager = store.getComponent(ref, this.interactionComponentType); + + assert interactionManager != null; + + interactionManager.clear(); + } + + @Override + public Query getQuery() { + return this.interactionComponentType; + } + } + + public static class EntityTrackerRemove extends RefChangeSystem { + private final ComponentType visibleComponentType; + + public EntityTrackerRemove(ComponentType visibleComponentType) { + this.visibleComponentType = visibleComponentType; + } + + @Nonnull + @Override + public Query getQuery() { + return this.visibleComponentType; + } + + @Nonnull + @Override + public ComponentType componentType() { + return Interactions.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull Interactions component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentSet( + @Nonnull Ref ref, + Interactions oldComponent, + @Nonnull Interactions newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, @Nonnull Interactions component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visible = commandBuffer.getComponent(ref, this.visibleComponentType); + if (visible != null) { + for (EntityTrackerSystems.EntityViewer viewer : visible.visibleTo.values()) { + viewer.queueRemove(ref, ComponentUpdateType.Interactions); + } + } + } + } + + public static class PlayerAddManagerSystem extends HolderSystem { + @Nonnull + private final Query query = Query.and(Player.getComponentType(), Query.not(InteractionModule.get().getInteractionManagerComponent())); + + public PlayerAddManagerSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + Player playerComponent = holder.getComponent(Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = holder.getComponent(PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + holder.addComponent( + InteractionModule.get().getInteractionManagerComponent(), + new InteractionManager(playerComponent, playerRefComponent, new InteractionSimulationHandler()) + ); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + } + + public static class TickInteractionManagerSystem extends EntityTickingSystem implements EntityStatsSystems.StatModifyingSystem { + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private final ComponentType interactionManagerComponent = InteractionModule.get().getInteractionManagerComponent(); + + public TickInteractionManagerSystem() { + } + + @Override + public Query getQuery() { + return this.interactionManagerComponent; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Ref ref = archetypeChunk.getReferenceTo(index); + + try { + InteractionManager interactionManager = archetypeChunk.getComponent(index, this.interactionManagerComponent); + + assert interactionManager != null; + + PlayerRef playerRef = archetypeChunk.getComponent(index, PlayerRef.getComponentType()); + interactionManager.tick(ref, commandBuffer, dt); + ObjectList syncPackets = interactionManager.getSyncPackets(); + if (playerRef != null && !syncPackets.isEmpty()) { + playerRef.getPacketHandler().writeNoCache(new SyncInteractionChains(syncPackets.toArray(SyncInteractionChain[]::new))); + syncPackets.clear(); + } + } catch (Throwable var10) { + LOGGER.at(Level.SEVERE).withCause(var10).log("Exception while ticking entity interactions! Removing!"); + commandBuffer.removeEntity(ref, RemoveReason.REMOVE); + } + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getGatherDamageGroup(); + } + } + + public static class TrackerTickSystem extends EntityTickingSystem { + @Nonnull + private final ComponentType visibleComponentType = EntityTrackerSystems.Visible.getComponentType(); + @Nonnull + private final Query query = Query.and(this.visibleComponentType, Interactions.getComponentType()); + + public TrackerTickSystem() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visibleComponentType = archetypeChunk.getComponent(index, this.visibleComponentType); + + assert visibleComponentType != null; + + Interactions interactionsComponent = archetypeChunk.getComponent(index, Interactions.getComponentType()); + + assert interactionsComponent != null; + + Ref ref = archetypeChunk.getReferenceTo(index); + if (interactionsComponent.consumeNetworkOutdated()) { + queueUpdatesFor(ref, visibleComponentType.visibleTo, interactionsComponent); + } else if (!visibleComponentType.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(ref, visibleComponentType.newlyVisibleTo, interactionsComponent); + } + } + + private static void queueUpdatesFor( + @Nonnull Ref ref, @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo, @Nonnull Interactions component + ) { + ComponentUpdate componentUpdate = new ComponentUpdate(); + componentUpdate.type = ComponentUpdateType.Interactions; + Object2IntOpenHashMap interactions = new Object2IntOpenHashMap<>(); + + for (Entry entry : component.getInteractions().entrySet()) { + interactions.put(entry.getKey(), RootInteraction.getRootInteractionIdOrUnknown(entry.getValue())); + } + + componentUpdate.interactions = interactions; + componentUpdate.interactionHint = component.getInteractionHint(); + + for (EntityTrackerSystems.EntityViewer viewer : visibleTo.values()) { + viewer.queueUpdate(ref, componentUpdate); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/item/CraftingRecipePacketGenerator.java b/src/com/hypixel/hytale/server/core/modules/item/CraftingRecipePacketGenerator.java new file mode 100644 index 0000000..14627a2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/item/CraftingRecipePacketGenerator.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.server.core.modules.item; + +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateRecipes; +import com.hypixel.hytale.server.core.asset.packet.AssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class CraftingRecipePacketGenerator extends AssetPacketGenerator> { + public CraftingRecipePacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(DefaultAssetMap assetMap, @Nonnull Map assets) { + UpdateRecipes packet = new UpdateRecipes(); + packet.type = UpdateType.Init; + packet.recipes = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + packet.recipes.put(entry.getKey(), entry.getValue().toPacket(entry.getKey())); + } + + return packet; + } + + @Nonnull + public Packet generateUpdatePacket( + DefaultAssetMap assetMap, @Nonnull Map loadedAssets, @Nonnull AssetUpdateQuery query + ) { + UpdateRecipes packet = new UpdateRecipes(); + packet.type = UpdateType.AddOrUpdate; + packet.recipes = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + packet.recipes.put(entry.getKey(), entry.getValue().toPacket(entry.getKey())); + } + + return packet; + } + + @Nonnull + public Packet generateRemovePacket(DefaultAssetMap assetMap, @Nonnull Set removed, @Nonnull AssetUpdateQuery query) { + UpdateRecipes packet = new UpdateRecipes(); + packet.type = UpdateType.Remove; + packet.removedRecipes = removed.toArray(String[]::new); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/item/ItemModule.java b/src/com/hypixel/hytale/server/core/modules/item/ItemModule.java new file mode 100644 index 0000000..2b86250 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/item/ItemModule.java @@ -0,0 +1,110 @@ +package com.hypixel.hytale.server.core.modules.item; + +import com.hypixel.hytale.codec.lookup.Priority; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemCategory; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDrop; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.EmptyItemContainer; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer; +import com.hypixel.hytale.server.core.modules.item.commands.SpawnItemCommand; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(ItemModule.class).build(); + private static ItemModule instance; + + public static ItemModule get() { + return instance; + } + + public ItemModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + this.getCommandRegistry().registerCommand(new SpawnItemCommand()); + ItemContainer.CODEC.register(Priority.DEFAULT, "Simple", SimpleItemContainer.class, SimpleItemContainer.CODEC); + ItemContainer.CODEC.register("Empty", EmptyItemContainer.class, EmptyItemContainer.CODEC); + } + + @Nonnull + public List getFlatItemCategoryList() { + List ids = new ObjectArrayList<>(); + ItemCategory[] itemCategories = ItemCategory.getAssetMap().getAssetMap().values().toArray(ItemCategory[]::new); + + for (ItemCategory category : itemCategories) { + ItemCategory[] children = category.getChildren(); + if (children != null) { + this.flattenCategories(category.getId() + ".", children, ids); + } + } + + return ids; + } + + private void flattenCategories(String parent, @Nonnull ItemCategory[] itemCategories, @Nonnull List categoryIds) { + for (ItemCategory category : itemCategories) { + String id = parent + category.getId(); + categoryIds.add(id); + ItemCategory[] children = category.getChildren(); + if (children != null) { + this.flattenCategories(id + ".", children, categoryIds); + } + } + } + + @Nonnull + public List getRandomItemDrops(@Nullable String dropListId) { + if (this.isDisabled()) { + return Collections.emptyList(); + } else if (dropListId == null) { + return Collections.emptyList(); + } else { + ItemDropList itemDropList = ItemDropList.getAssetMap().getAsset(dropListId); + if (itemDropList != null && itemDropList.getContainer() != null) { + List generatedItemDrops = new ObjectArrayList<>(); + ThreadLocalRandom random = ThreadLocalRandom.current(); + List configuredItemDrops = new ObjectArrayList<>(); + itemDropList.getContainer().populateDrops(configuredItemDrops, random::nextDouble, dropListId); + + for (ItemDrop drop : configuredItemDrops) { + if (drop != null && drop.getItemId() != null) { + int amount = drop.getRandomQuantity(random); + if (amount > 0) { + generatedItemDrops.add(new ItemStack(drop.getItemId(), amount, drop.getMetadata())); + } + } else { + this.getLogger() + .atWarning() + .log("ItemModule::getRandomItemDrops - Tried to create ItemDrop for non-existent item in drop list id '%s'", dropListId); + } + } + + return generatedItemDrops; + } else { + return Collections.emptyList(); + } + } + } + + public static boolean exists(String key) { + if ("Empty".equals(key)) { + return true; + } else { + return "Unknown".equals(key) ? true : Item.getAssetMap().getAsset(key) != null; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/item/ItemPacketGenerator.java b/src/com/hypixel/hytale/server/core/modules/item/ItemPacketGenerator.java new file mode 100644 index 0000000..3a5e508 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/item/ItemPacketGenerator.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.server.core.modules.item; + +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateItems; +import com.hypixel.hytale.server.core.asset.packet.AssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class ItemPacketGenerator extends AssetPacketGenerator> { + public ItemPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(DefaultAssetMap assetMap, @Nonnull Map assets) { + UpdateItems packet = new UpdateItems(); + packet.type = UpdateType.Init; + packet.items = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + packet.items.put(entry.getKey(), entry.getValue().toPacket()); + } + + packet.updateModels = true; + packet.updateIcons = true; + return packet; + } + + @Nonnull + public Packet generateUpdatePacket(DefaultAssetMap assetMap, @Nonnull Map loadedAssets, @Nonnull AssetUpdateQuery query) { + UpdateItems packet = new UpdateItems(); + packet.type = UpdateType.AddOrUpdate; + packet.items = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + packet.items.put(entry.getKey(), entry.getValue().toPacket()); + } + + AssetUpdateQuery.RebuildCache rebuildCache = query.getRebuildCache(); + packet.updateModels = rebuildCache.isBlockTextures() || rebuildCache.isModels(); + packet.updateIcons = rebuildCache.isItemIcons(); + return packet; + } + + @Nonnull + public Packet generateRemovePacket(DefaultAssetMap assetMap, @Nonnull Set removed, @Nonnull AssetUpdateQuery query) { + UpdateItems packet = new UpdateItems(); + packet.type = UpdateType.Remove; + packet.removedItems = removed.toArray(String[]::new); + AssetUpdateQuery.RebuildCache rebuildCache = query.getRebuildCache(); + packet.updateModels = rebuildCache.isBlockTextures() || rebuildCache.isModels(); + packet.updateIcons = rebuildCache.isItemIcons(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/item/ItemQualityPacketGenerator.java b/src/com/hypixel/hytale/server/core/modules/item/ItemQualityPacketGenerator.java new file mode 100644 index 0000000..51dfaf5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/item/ItemQualityPacketGenerator.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.modules.item; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateItemQualities; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemQuality; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class ItemQualityPacketGenerator extends SimpleAssetPacketGenerator> { + public ItemQualityPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + UpdateItemQualities packet = new UpdateItemQualities(); + packet.type = UpdateType.Init; + packet.itemQualities = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.itemQualities.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + protected Packet generateUpdatePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets) { + UpdateItemQualities packet = new UpdateItemQualities(); + packet.type = UpdateType.AddOrUpdate; + packet.itemQualities = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.itemQualities.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + protected Packet generateRemovePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed) { + UpdateItemQualities packet = new UpdateItemQualities(); + packet.type = UpdateType.Remove; + packet.itemQualities = new Int2ObjectOpenHashMap<>(); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.itemQualities.put(index, null); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/item/ItemReticleConfigPacketGenerator.java b/src/com/hypixel/hytale/server/core/modules/item/ItemReticleConfigPacketGenerator.java new file mode 100644 index 0000000..bb6e898 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/item/ItemReticleConfigPacketGenerator.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.server.core.modules.item; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateItemReticles; +import com.hypixel.hytale.server.core.asset.packet.SimpleAssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemReticleConfig; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class ItemReticleConfigPacketGenerator + extends SimpleAssetPacketGenerator> { + public ItemReticleConfigPacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map assets) { + UpdateItemReticles packet = new UpdateItemReticles(); + packet.type = UpdateType.Init; + packet.itemReticleConfigs = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.itemReticleConfigs.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateUpdatePacket( + @Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Map loadedAssets + ) { + UpdateItemReticles packet = new UpdateItemReticles(); + packet.type = UpdateType.AddOrUpdate; + packet.itemReticleConfigs = new Int2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + String key = entry.getKey(); + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.itemReticleConfigs.put(index, entry.getValue().toPacket()); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } + + @Nonnull + public Packet generateRemovePacket(@Nonnull IndexedLookupTableAssetMap assetMap, @Nonnull Set removed) { + UpdateItemReticles packet = new UpdateItemReticles(); + packet.type = UpdateType.Remove; + packet.itemReticleConfigs = new Int2ObjectOpenHashMap<>(); + + for (String key : removed) { + int index = assetMap.getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } + + packet.itemReticleConfigs.put(index, null); + } + + packet.maxId = assetMap.getNextIndex(); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/item/RecipePacketGenerator.java b/src/com/hypixel/hytale/server/core/modules/item/RecipePacketGenerator.java new file mode 100644 index 0000000..e0da2bc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/item/RecipePacketGenerator.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.server.core.modules.item; + +import com.hypixel.hytale.assetstore.AssetUpdateQuery; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateRecipes; +import com.hypixel.hytale.server.core.asset.packet.AssetPacketGenerator; +import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class RecipePacketGenerator extends AssetPacketGenerator> { + public RecipePacketGenerator() { + } + + @Nonnull + public Packet generateInitPacket(DefaultAssetMap assetMap, @Nonnull Map assets) { + UpdateRecipes packet = new UpdateRecipes(); + packet.type = UpdateType.Init; + packet.recipes = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : assets.entrySet()) { + packet.recipes.put(entry.getKey(), entry.getValue().toPacket(entry.getKey())); + } + + return packet; + } + + @Nonnull + public Packet generateUpdatePacket( + DefaultAssetMap assetMap, @Nonnull Map loadedAssets, @Nonnull AssetUpdateQuery query + ) { + UpdateRecipes packet = new UpdateRecipes(); + packet.type = UpdateType.AddOrUpdate; + packet.recipes = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + packet.recipes.put(entry.getKey(), entry.getValue().toPacket(entry.getKey())); + } + + return packet; + } + + @Nonnull + public Packet generateRemovePacket(DefaultAssetMap assetMap, @Nonnull Set removed, @Nonnull AssetUpdateQuery query) { + UpdateRecipes packet = new UpdateRecipes(); + packet.type = UpdateType.Remove; + packet.recipes = new Object2ObjectOpenHashMap<>(); + + for (String key : removed) { + packet.recipes.put(key, null); + } + + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/item/commands/SpawnItemCommand.java b/src/com/hypixel/hytale/server/core/modules/item/commands/SpawnItemCommand.java new file mode 100644 index 0000000..f5653e2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/item/commands/SpawnItemCommand.java @@ -0,0 +1,102 @@ +package com.hypixel.hytale.server.core.modules.item.commands; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.ItemUtils; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; + +public class SpawnItemCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD = Message.translation("server.commands.errors.playerNotInWorld"); + @Nonnull + private final RequiredArg itemArg = this.withRequiredArg("item", "server.commands.spawnitem.item.desc", ArgTypes.ITEM_ASSET); + @Nonnull + private final DefaultArg quantityArg = this.withDefaultArg("qty", "server.commands.spawnitem.quantity.desc", ArgTypes.INTEGER, 1, "1"); + @Nonnull + private final OptionalArg countArg = this.withOptionalArg("count", "server.commands.spawnitem.count.desc", ArgTypes.INTEGER).addAliases("n"); + @Nonnull + private final DefaultArg forceArg = this.withDefaultArg("force", "server.commands.spawnitem.force.desc", ArgTypes.FLOAT, 1.0F, "1.0").addAliases("x"); + + public SpawnItemCommand() { + super("spawnitem", "server.commands.spawnitem.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + CommandUtil.requirePermission(context.sender(), HytalePermissions.fromCommand("spawnitem")); + Item item = this.itemArg.get(context); + String itemId = item.getId(); + int quantity = this.quantityArg.get(context); + float force = this.forceArg.get(context); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + ModelComponent modelComponent = store.getComponent(ref, ModelComponent.getComponentType()); + if (transformComponent != null && modelComponent != null) { + Vector3d playerPosition = transformComponent.getPosition(); + Model playerModel = modelComponent.getModel(); + float throwSpeed = 6.0F * force; + if (this.countArg.provided(context)) { + int count = this.countArg.get(context); + Vector3d throwPosition = playerPosition.clone(); + throwPosition.add(0.0, playerModel.getEyeHeight(ref, store), 0.0); + ThreadLocalRandom random = ThreadLocalRandom.current(); + + for (int i = 0; i < count; i++) { + Holder itemEntityHolder = ItemComponent.generateItemDrop( + store, + new ItemStack(itemId, quantity), + throwPosition, + Vector3f.ZERO, + (float)random.nextGaussian() * throwSpeed, + 0.5F, + (float)random.nextGaussian() * throwSpeed + ); + ItemComponent itemEntityComponent = itemEntityHolder.getComponent(ItemComponent.getComponentType()); + if (itemEntityComponent != null) { + itemEntityComponent.setPickupDelay(1.5F); + } + + store.addEntity(itemEntityHolder, AddReason.SPAWN); + } + + int totalQuantity = count * quantity; + context.sendMessage( + Message.translation("server.commands.spawnitem.spawnedMultiple") + .param("count", count) + .param("quantity", quantity) + .param("total", totalQuantity) + .param("item", itemId) + ); + } else { + ItemUtils.throwItem(ref, new ItemStack(itemId, quantity), throwSpeed, store); + context.sendMessage(Message.translation("server.commands.spawnitem.spawned").param("quantity", quantity).param("item", itemId)); + } + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_NOT_IN_WORLD); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/migrations/ChunkColumnMigrationSystem.java b/src/com/hypixel/hytale/server/core/modules/migrations/ChunkColumnMigrationSystem.java new file mode 100644 index 0000000..3a34816 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/migrations/ChunkColumnMigrationSystem.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.server.core.modules.migrations; + +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; + +public abstract class ChunkColumnMigrationSystem extends HolderSystem { + public ChunkColumnMigrationSystem() { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/migrations/ChunkSectionMigrationSystem.java b/src/com/hypixel/hytale/server/core/modules/migrations/ChunkSectionMigrationSystem.java new file mode 100644 index 0000000..12e7560 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/migrations/ChunkSectionMigrationSystem.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.server.core.modules.migrations; + +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; + +public abstract class ChunkSectionMigrationSystem extends HolderSystem { + public ChunkSectionMigrationSystem() { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/migrations/EntityMigration.java b/src/com/hypixel/hytale/server/core/modules/migrations/EntityMigration.java new file mode 100644 index 0000000..1b4435b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/migrations/EntityMigration.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.server.core.modules.migrations; + +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import java.util.function.IntFunction; + +public abstract class EntityMigration implements Migration { + private Class tClass; + private IntFunction extraInfoSupplier; + + public EntityMigration(Class tClass, IntFunction extraInfoSupplier) { + this.tClass = tClass; + this.extraInfoSupplier = extraInfoSupplier; + } + + @Override + public final void run(WorldChunk chunk) { + throw new UnsupportedOperationException("Not implemented!"); + } + + protected abstract boolean migrate(T var1); +} diff --git a/src/com/hypixel/hytale/server/core/modules/migrations/Migration.java b/src/com/hypixel/hytale/server/core/modules/migrations/Migration.java new file mode 100644 index 0000000..f60a4ea --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/migrations/Migration.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.server.core.modules.migrations; + +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; + +public interface Migration { + void run(WorldChunk var1); +} diff --git a/src/com/hypixel/hytale/server/core/modules/migrations/MigrationModule.java b/src/com/hypixel/hytale/server/core/modules/migrations/MigrationModule.java new file mode 100644 index 0000000..401a59d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/migrations/MigrationModule.java @@ -0,0 +1,159 @@ +package com.hypixel.hytale.server.core.modules.migrations; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.SystemType; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.event.events.BootEvent; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.IChunkLoader; +import com.hypixel.hytale.server.core.universe.world.storage.IChunkSaver; +import com.hypixel.hytale.server.core.universe.world.storage.component.ChunkSavingSystems; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import joptsimple.OptionSet; + +public class MigrationModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(MigrationModule.class).build(); + protected static MigrationModule instance; + @Nonnull + private final Map> migrationCtors = new Object2ObjectOpenHashMap<>(); + private SystemType chunkColumnMigrationSystem; + private SystemType chunkSectionMigrationSystem; + + public MigrationModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + public static MigrationModule get() { + return instance; + } + + @Override + protected void setup() { + this.getEventRegistry().register(BootEvent.class, event -> { + if (Options.getOptionSet().has(Options.MIGRATIONS)) { + this.runMigrations(); + HytaleServer.get().shutdownServer(); + } + }); + this.chunkColumnMigrationSystem = this.getChunkStoreRegistry().registerSystemType(ChunkColumnMigrationSystem.class); + this.chunkSectionMigrationSystem = this.getChunkStoreRegistry().registerSystemType(ChunkSectionMigrationSystem.class); + } + + public SystemType getChunkColumnMigrationSystem() { + return this.chunkColumnMigrationSystem; + } + + public SystemType getChunkSectionMigrationSystem() { + return this.chunkSectionMigrationSystem; + } + + public void register(String id, Function migration) { + this.migrationCtors.put(id, migration); + } + + public void runMigrations() { + OptionSet optionSet = Options.getOptionSet(); + List worldsToMigrate = optionSet.has(Options.MIGRATE_WORLDS) ? optionSet.valuesOf(Options.MIGRATE_WORLDS) : null; + Map migrationMap = Options.getOptionSet().valueOf(Options.MIGRATIONS); + List migrations = new ObjectArrayList<>(); + migrationMap.forEach((s, path) -> { + Function migrationCtor = this.migrationCtors.get(s); + if (migrationCtor != null) { + migrations.add(migrationCtor.apply(path)); + } + }); + if (!migrations.isEmpty()) { + AtomicInteger worldsCount = new AtomicInteger(); + + for (World world : Universe.get().getWorlds().values()) { + String worldName = world.getName(); + if (worldsToMigrate == null || worldsToMigrate.contains(worldName)) { + worldsCount.incrementAndGet(); + this.getLogger().at(Level.INFO).log("Starting to migrate world '%s'...", worldName); + ChunkStore chunkComponentStore = world.getChunkStore(); + IChunkSaver saver = chunkComponentStore.getSaver(); + IChunkLoader loader = chunkComponentStore.getLoader(); + world.execute( + () -> { + ChunkSavingSystems.Data data = chunkComponentStore.getStore().getResource(ChunkStore.SAVE_RESOURCE); + data.isSaving = false; + data.waitForSavingChunks() + .whenComplete( + (aVoid, throwable) -> { + try { + LongSet chunks = loader.getIndexes(); + this.getLogger().at(Level.INFO).log("Found %d chunks in world '%s'. Starting iteration...", chunks.size(), worldName); + List> futures = new ObjectArrayList<>(chunks.size()); + LongIterator iterator = chunks.iterator(); + + while (iterator.hasNext()) { + long index = iterator.nextLong(); + int chunkX = ChunkUtil.xOfChunkIndex(index); + int chunkZ = ChunkUtil.zOfChunkIndex(index); + futures.add( + loader.loadHolder(chunkX, chunkZ) + .thenCompose( + holder -> { + if (holder == null) { + return CompletableFuture.completedFuture(null); + } else { + WorldChunk chunk = holder.getComponent(WorldChunk.getComponentType()); + migrations.forEach(migration -> migration.run(chunk)); + return chunk.getNeedsSaving() + ? saver.saveHolder(chunkX, chunkZ, (Holder)holder) + : CompletableFuture.completedFuture(null); + } + } + ) + ); + } + + try { + CompletableFutureUtil.joinWithProgress( + futures, + (value, completed, max) -> this.getLogger() + .at(Level.INFO) + .log("Scanning + Migrating world '%s': %.2d% (%d of %d)", worldName, value, completed, max), + 250, + 750 + ); + } catch (InterruptedException var20) { + this.getLogger().at(Level.SEVERE).withCause(var20).log("Interrupted while loading chunks:"); + Thread.currentThread().interrupt(); + } + } catch (Throwable var21) { + this.getLogger().at(Level.SEVERE).withCause(var21).log("Failed to migrate chunks!"); + } finally { + data.isSaving = true; + this.getLogger().at(Level.INFO).log("%d world(s) left to migrate.", worldsCount.decrementAndGet()); + } + } + ); + } + ); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/RestingSupport.java b/src/com/hypixel/hytale/server/core/modules/physics/RestingSupport.java new file mode 100644 index 0000000..5355cf6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/RestingSupport.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.server.core.modules.physics; + +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RestingSupport { + protected int supportMinX; + protected int supportMaxX; + protected int supportMinZ; + protected int supportMaxZ; + protected int supportMinY; + protected int supportMaxY; + @Nullable + protected int[] supportBlocks; + + public RestingSupport() { + } + + public boolean hasChanged(@Nonnull World world) { + if (this.supportBlocks == null) { + return false; + } else { + int index = 0; + + for (int z = this.supportMinZ; z <= this.supportMaxZ; z++) { + for (int x = this.supportMinX; x <= this.supportMaxX; x++) { + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(x, z)); + if (chunk != null) { + for (int y = this.supportMinY; y <= this.supportMaxY; y++) { + if (this.supportBlocks[index++] != chunk.getBlock(x, y, z)) { + return true; + } + } + } + } + } + + return false; + } + } + + public void rest(@Nonnull World world, @Nonnull Box boundingBox, @Nonnull Vector3d position) { + if (this.supportBlocks == null) { + int maxSize = (int)(Math.ceil(boundingBox.width() + 1.0) * Math.ceil(boundingBox.depth() + 1.0) * Math.ceil(boundingBox.height() + 1.0)); + this.supportBlocks = new int[maxSize]; + } + + this.supportMinX = MathUtil.floor(position.x + boundingBox.min.x); + this.supportMaxX = MathUtil.floor(position.x + boundingBox.max.x); + this.supportMinZ = MathUtil.floor(position.z + boundingBox.min.z); + this.supportMaxZ = MathUtil.floor(position.z + boundingBox.max.z); + this.supportMinY = MathUtil.floor(position.y + boundingBox.min.y); + this.supportMaxY = MathUtil.floor(position.y + boundingBox.max.y); + int index = 0; + + for (int z = this.supportMinZ; z <= this.supportMaxZ; z++) { + for (int x = this.supportMinX; x <= this.supportMaxX; x++) { + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(x, z)); + if (chunk != null) { + for (int y = this.supportMinY; y <= this.supportMaxY; y++) { + this.supportBlocks[index++] = chunk.getBlock(x, y, z); + } + } else { + for (int y = this.supportMinY; y <= this.supportMaxY; y++) { + this.supportBlocks[index++] = 1; + } + } + } + } + } + + public void clear() { + this.supportBlocks = null; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/SimplePhysicsProvider.java b/src/com/hypixel/hytale/server/core/modules/physics/SimplePhysicsProvider.java new file mode 100644 index 0000000..5bbe447 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/SimplePhysicsProvider.java @@ -0,0 +1,624 @@ +package com.hypixel.hytale.server.core.modules.physics; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.function.consumer.QuadConsumer; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.NearestBlockUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.server.core.asset.type.projectile.config.Projectile; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.modules.collision.BlockCollisionProvider; +import com.hypixel.hytale.server.core.modules.collision.BlockContactData; +import com.hypixel.hytale.server.core.modules.collision.BlockData; +import com.hypixel.hytale.server.core.modules.collision.BlockTracker; +import com.hypixel.hytale.server.core.modules.collision.EntityCollisionProvider; +import com.hypixel.hytale.server.core.modules.collision.EntityContactData; +import com.hypixel.hytale.server.core.modules.collision.IBlockCollisionConsumer; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.modules.physics.util.ForceProvider; +import com.hypixel.hytale.server.core.modules.physics.util.ForceProviderEntity; +import com.hypixel.hytale.server.core.modules.physics.util.ForceProviderStandardState; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsBodyState; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsBodyStateUpdater; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsBodyStateUpdaterSymplecticEuler; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@Deprecated +public class SimplePhysicsProvider implements IBlockCollisionConsumer { + protected static final double HIT_WATER_IMPULSE_LOSS = 0.2; + protected static final double ROTATION_FORCE = 3.0; + protected static final float SPEED_ROTATION_FACTOR = 2.0F; + protected static final double SWIMMING_DAMPING_FACTOR = 1.0; + protected static final double DEFAULT_MOVE_OUT_OF_SOLID_SPEED = 5.0; + protected static final int WATER_DETECTION_EXTREMA_COUNT = 2; + protected static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + protected final BlockCollisionProvider blockCollisionProvider; + @Nonnull + protected final EntityCollisionProvider entityCollisionProvider; + @Nonnull + protected final BlockTracker triggerTracker; + @Nonnull + protected final RestingSupport restingSupport; + @Nullable + protected World world; + @Nonnull + protected final Vector3d velocity; + @Nonnull + protected final Vector3d position; + @Nonnull + protected final Vector3d movement; + protected boolean bounced; + protected boolean onGround; + protected boolean provideCharacterCollisions; + protected double gravity; + protected double bounciness; + protected boolean sticksVertically; + protected boolean computeYaw = true; + protected boolean computePitch = true; + protected SimplePhysicsProvider.ROTATION_MODE rotationMode = SimplePhysicsProvider.ROTATION_MODE.VelocityDamped; + protected UUID creatorUuid; + protected static final double minBounceEpsilon = 0.4; + protected static final double minBounceEpsilonSquared = 0.16000000000000003; + protected final Vector3d tempVector = new Vector3d(); + protected BiConsumer> bounceConsumer; + protected QuadConsumer, Vector3d, Ref, ComponentAccessor> impactConsumer; + protected double moveOutOfSolidSpeed; + protected boolean movedInsideSolid; + protected final Vector3d moveOutOfSolidVelocity = new Vector3d(); + protected final Vector3d contactPosition = new Vector3d(); + protected final Vector3d contactNormal = new Vector3d(); + protected double collisionStart; + protected final PhysicsBodyStateUpdater stateUpdater = new PhysicsBodyStateUpdaterSymplecticEuler(); + protected final PhysicsBodyState stateBefore = new PhysicsBodyState(); + protected final PhysicsBodyState stateAfter = new PhysicsBodyState(); + protected double displacedMass; + protected double subSurfaceVolume; + protected double enterFluid; + protected double leaveFluid; + protected boolean inFluid; + protected int velocityExtremaCount = Integer.MAX_VALUE; + @Nonnull + protected SimplePhysicsProvider.STATE state = SimplePhysicsProvider.STATE.Active; + protected ForceProviderEntity forceProviderEntity; + protected ForceProvider[] forceProviders; + protected final ForceProviderStandardState forceProviderStandardState = new ForceProviderStandardState(); + protected double terminalVelocity1; + protected double density1; + protected double terminalVelocity2; + protected double density2; + protected double dragMultiplier; + protected double dragOffset; + protected final BlockTracker fluidTracker = new BlockTracker(); + protected double hitWaterImpulseLoss = 0.2; + protected double rotationForce = 3.0; + protected float speedRotationFactor = 2.0F; + protected double swimmingDampingFactor = 1.0; + @Deprecated(forRemoval = true) + protected BoundingBox boundingBox; + + public SimplePhysicsProvider() { + this.blockCollisionProvider = new BlockCollisionProvider(); + this.blockCollisionProvider.setRequestedCollisionMaterials(6); + this.blockCollisionProvider.setReportOverlaps(true); + this.entityCollisionProvider = new EntityCollisionProvider(); + this.triggerTracker = new BlockTracker(); + this.restingSupport = new RestingSupport(); + this.velocity = new Vector3d(); + this.position = new Vector3d(); + this.movement = new Vector3d(); + } + + public SimplePhysicsProvider( + @Nonnull BiConsumer> bounceConsumer, + @Nonnull QuadConsumer, Vector3d, Ref, ComponentAccessor> impactConsumer + ) { + this(); + this.bounceConsumer = bounceConsumer; + this.impactConsumer = impactConsumer; + } + + public void setImpacted(boolean impacted) { + this.state = impacted ? SimplePhysicsProvider.STATE.Inactive : SimplePhysicsProvider.STATE.Active; + } + + public boolean isImpacted() { + return this.state == SimplePhysicsProvider.STATE.Inactive; + } + + public void setResting(boolean resting) { + if (this.state != SimplePhysicsProvider.STATE.Inactive) { + this.state = resting ? SimplePhysicsProvider.STATE.Resting : SimplePhysicsProvider.STATE.Active; + } + } + + public boolean isResting() { + return this.state == SimplePhysicsProvider.STATE.Resting; + } + + @Nonnull + @Override + public IBlockCollisionConsumer.Result onCollision( + int blockX, + int blockY, + int blockZ, + @Nonnull Vector3d direction, + @Nonnull BlockContactData contactData, + @Nonnull BlockData blockData, + @Nonnull Box collider + ) { + BlockMaterial blockMaterial = blockData.getBlockType().getMaterial(); + if (this.moveOutOfSolidSpeed > 0.0 && contactData.isOverlapping() && blockMaterial == BlockMaterial.Solid) { + Vector3i nearestBlock = NearestBlockUtil.findNearestBlock(this.position, (block, w) -> { + WorldChunk worldChunk = w.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(block.getX(), block.getZ())); + return worldChunk != null && worldChunk.getBlockType(block).getMaterial() != BlockMaterial.Solid; + }, this.world); + if (nearestBlock != null) { + this.tempVector.assign(nearestBlock.x, nearestBlock.y, nearestBlock.z); + this.tempVector.add(0.5, 0.5, 0.5); + this.tempVector.subtract(this.position); + this.tempVector.setLength(this.moveOutOfSolidSpeed); + } else { + this.tempVector.assign(0.0, this.moveOutOfSolidSpeed, 0.0); + } + + this.moveOutOfSolidVelocity.add(this.tempVector); + this.movedInsideSolid = true; + return IBlockCollisionConsumer.Result.CONTINUE; + } else if (blockData.getFluidId() != 0 && !this.fluidTracker.isTracked(blockX, blockY, blockZ)) { + double collisionStart = contactData.getCollisionStart(); + double collisionEnd = contactData.getCollisionEnd(); + if (collisionStart < this.enterFluid) { + this.enterFluid = collisionStart; + } + + if (collisionEnd > this.leaveFluid) { + this.leaveFluid = collisionEnd; + } + + if (collisionEnd <= collisionStart) { + return IBlockCollisionConsumer.Result.CONTINUE; + } else { + double density = 1000.0; + double volume = PhysicsMath.volumeOfIntersection(this.boundingBox.getBoundingBox(), this.contactPosition, collider, blockX, blockY, blockZ); + this.subSurfaceVolume += volume; + this.displacedMass += volume * density; + this.fluidTracker.trackNew(blockX, blockY, blockZ); + return IBlockCollisionConsumer.Result.CONTINUE; + } + } else if (contactData.isOverlapping()) { + return IBlockCollisionConsumer.Result.CONTINUE; + } else { + double surfaceAlignment = direction.dot(contactData.getCollisionNormal()); + if (blockMaterial == BlockMaterial.Solid && surfaceAlignment == 0.0) { + } + + if (surfaceAlignment >= 0.0) { + return IBlockCollisionConsumer.Result.CONTINUE; + } else { + this.contactPosition.assign(contactData.getCollisionPoint()); + this.contactNormal.assign(contactData.getCollisionNormal()); + this.collisionStart = contactData.getCollisionStart(); + this.bounced = true; + return IBlockCollisionConsumer.Result.STOP; + } + } + } + + @Nonnull + @Override + public IBlockCollisionConsumer.Result probeCollisionDamage( + int blockX, int blockY, int blockZ, Vector3d direction, BlockContactData collisionData, BlockData blockData + ) { + return IBlockCollisionConsumer.Result.CONTINUE; + } + + @Override + public void onCollisionDamage(int blockX, int blockY, int blockZ, Vector3d direction, BlockContactData collisionData, BlockData blockData) { + } + + @Nonnull + @Override + public IBlockCollisionConsumer.Result onCollisionSliceFinished() { + return IBlockCollisionConsumer.Result.CONTINUE; + } + + @Override + public void onCollisionFinished() { + } + + @Nullable + public Entity tick( + double dt, + @Nonnull Velocity entityVelocity, + @Nonnull World entityWorld, + @Nonnull TransformComponent entityTransform, + Ref selfRef, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.state == SimplePhysicsProvider.STATE.Inactive) { + entityVelocity.setZero(); + return null; + } else { + if (this.state == SimplePhysicsProvider.STATE.Resting) { + if (this.forceProviderStandardState.externalForce.squaredLength() == 0.0 && !this.restingSupport.hasChanged(entityWorld)) { + return null; + } + + this.state = SimplePhysicsProvider.STATE.Active; + } + + this.world = entityWorld; + this.position.assign(entityTransform.getPosition()); + entityVelocity.assignVelocityTo(this.velocity); + double mass = this.forceProviderEntity.getMass(this.boundingBox.getBoundingBox().getVolume()); + this.forceProviderStandardState.convertToForces(dt, mass); + this.forceProviderStandardState.updateVelocity(this.velocity); + if (!(this.velocity.squaredLength() * dt * dt >= 1.0000000000000002E-10) && !(this.forceProviderStandardState.externalForce.squaredLength() >= 0.0)) { + this.velocity.assign(Vector3d.ZERO); + } else { + this.state = SimplePhysicsProvider.STATE.Active; + } + + if (this.state == SimplePhysicsProvider.STATE.Resting && this.restingSupport.hasChanged(entityWorld)) { + this.state = SimplePhysicsProvider.STATE.Active; + } + + this.stateBefore.position.assign(this.position); + this.stateBefore.velocity.assign(this.velocity); + this.forceProviderEntity.setForceProviderStandardState(this.forceProviderStandardState); + this.stateUpdater.update(this.stateBefore, this.stateAfter, mass, dt, this.onGround, this.forceProviders); + this.velocity.assign(this.stateAfter.velocity); + this.movement.assign(this.velocity).scale(dt); + this.forceProviderStandardState.clear(); + if (this.velocity.squaredLength() * dt * dt >= 1.0000000000000002E-10) { + this.state = SimplePhysicsProvider.STATE.Active; + } else { + this.velocity.assign(Vector3d.ZERO); + } + + double maxRelativeDistance = 1.0; + if (this.provideCharacterCollisions) { + Ref creatorReference = null; + if (this.creatorUuid != null) { + Entity creator = entityWorld.getEntity(this.creatorUuid); + if (creator != null) { + creatorReference = creator.getReference(); + } + } + + maxRelativeDistance = this.entityCollisionProvider + .computeNearest(this.boundingBox.getBoundingBox(), this.position, this.movement, selfRef, creatorReference, componentAccessor); + if (maxRelativeDistance < 0.0 || maxRelativeDistance > 1.0) { + maxRelativeDistance = 1.0; + } + } + + this.bounced = false; + this.onGround = false; + this.moveOutOfSolidVelocity.assign(Vector3d.ZERO); + this.movedInsideSolid = false; + this.displacedMass = 0.0; + this.subSurfaceVolume = 0.0; + this.enterFluid = Double.MAX_VALUE; + this.leaveFluid = -Double.MAX_VALUE; + this.collisionStart = maxRelativeDistance; + this.contactPosition.assign(this.position).addScaled(this.movement, this.collisionStart); + this.contactNormal.assign(Vector3d.ZERO); + this.blockCollisionProvider + .cast(entityWorld, this.boundingBox.getBoundingBox(), this.position, this.movement, this, this.triggerTracker, maxRelativeDistance); + this.fluidTracker.reset(); + double density = this.displacedMass > 0.0 ? this.displacedMass / this.subSurfaceVolume : 1.2; + if (this.movedInsideSolid) { + this.position.addScaled(this.moveOutOfSolidVelocity, dt); + this.velocity.assign(this.moveOutOfSolidVelocity); + this.forceProviderStandardState.dragCoefficient = this.getDragCoefficient(density); + this.forceProviderStandardState.displacedMass = this.displacedMass; + this.forceProviderStandardState.gravity = this.gravity; + this.finishTick(entityTransform, entityVelocity); + return null; + } else { + double velocityClip = this.bounced ? this.collisionStart : 1.0; + boolean enteringWater = false; + if (!this.inFluid && this.enterFluid < this.collisionStart) { + this.inFluid = true; + velocityClip = this.enterFluid; + this.velocityExtremaCount = 2; + enteringWater = true; + } else if (this.inFluid && this.leaveFluid < this.collisionStart) { + this.inFluid = false; + velocityClip = this.leaveFluid; + this.velocityExtremaCount = 2; + } + + if (velocityClip > 0.0 && velocityClip < 1.0) { + this.stateUpdater.update(this.stateBefore, this.stateAfter, mass, dt * velocityClip, this.onGround, this.forceProviders); + this.velocity.assign(this.stateAfter.velocity); + } + + if (this.inFluid && this.subSurfaceVolume < this.boundingBox.getBoundingBox().getVolume() && this.velocityExtremaCount > 0) { + double speedBefore = this.stateBefore.velocity.y; + double speedAfter = this.stateAfter.velocity.y; + if (speedBefore * speedAfter <= 0.0) { + this.velocityExtremaCount--; + } + } + + if (this.isSwimming()) { + this.forceProviderStandardState.externalForce.y = this.forceProviderStandardState.externalForce.y + - this.stateAfter.velocity.y * (this.swimmingDampingFactor / mass); + } + + if (enteringWater) { + this.forceProviderStandardState.externalImpulse.addScaled(this.stateAfter.velocity, -this.hitWaterImpulseLoss * mass); + } + + this.forceProviderStandardState.displacedMass = this.displacedMass; + this.forceProviderStandardState.dragCoefficient = this.getDragCoefficient(density); + this.forceProviderStandardState.gravity = this.gravity; + if (!this.bounced) { + if (this.entityCollisionProvider.getCount() > 0) { + EntityContactData contact = this.entityCollisionProvider.getContact(0); + Ref contactRef = contact.getEntityReference(); + Entity target = EntityUtils.getEntity(contactRef, componentAccessor); + this.position.assign(contact.getCollisionPoint()); + this.state = SimplePhysicsProvider.STATE.Inactive; + if (this.impactConsumer != null) { + this.impactConsumer.accept(selfRef, this.position, contactRef, componentAccessor); + } + + this.rotateBody(dt, entityTransform.getRotation()); + this.finishTick(entityTransform, entityVelocity); + return target; + } else { + this.position.add(this.movement); + this.rotateBody(dt, entityTransform.getRotation()); + this.finishTick(entityTransform, entityVelocity); + return null; + } + } else { + this.position.assign(this.contactPosition); + computeReflectedVector(this.velocity, this.contactNormal, this.velocity); + this.velocity.scale(this.bounciness); + if (this.velocity.squaredLength() * dt * dt < 0.16000000000000003) { + boolean hitGround = this.contactNormal.equals(Vector3d.UP); + if (this.sticksVertically || hitGround) { + this.state = SimplePhysicsProvider.STATE.Resting; + this.restingSupport.rest(entityWorld, this.boundingBox.getBoundingBox(), this.position); + this.onGround = hitGround; + if (this.impactConsumer != null) { + this.impactConsumer.accept(selfRef, this.position, null, componentAccessor); + } + } + + this.velocity.assign(Vector3d.ZERO); + } else if (this.bounceConsumer != null) { + this.bounceConsumer.accept(this.position, componentAccessor); + } + + this.rotateBody(dt, entityTransform.getRotation()); + this.finishTick(entityTransform, entityVelocity); + return null; + } + } + } + } + + protected void finishTick(@Nonnull TransformComponent position, @Nonnull Velocity velocity) { + position.setPosition(this.position); + velocity.set(this.velocity); + this.world = null; + this.entityCollisionProvider.clear(); + } + + protected void rotateBody(double dt, @Nonnull Vector3f bodyRotation) { + if (this.isComputeYaw() || this.isComputePitch()) { + double vx = this.stateAfter.velocity.x; + double vz = this.stateAfter.velocity.z; + if (!(vx * vx + vz * vz <= 1.0000000000000002E-10)) { + switch (this.rotationMode) { + case None: + default: + break; + case Velocity: + if (this.isComputeYaw()) { + bodyRotation.setYaw(PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(vx, vz))); + } + + if (this.isComputePitch()) { + bodyRotation.setPitch(PhysicsMath.pitchFromDirection(vx, this.stateAfter.velocity.y, vz)); + } + break; + case VelocityDamped: + if (this.isComputeYaw()) { + bodyRotation.setYaw(PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(vx, vz))); + } + + if (this.isComputePitch()) { + float pitch = bodyRotation.getPitch(); + float targetPitch = PhysicsMath.pitchFromDirection(vx, this.velocity.y, vz); + float delta = PhysicsMath.normalizeTurnAngle(targetPitch - pitch); + float maxDelta = (float)(this.velocity.squaredLength() * dt * this.speedRotationFactor); + if (delta > maxDelta) { + targetPitch = pitch + maxDelta; + delta = maxDelta; + } else if (delta < -maxDelta) { + targetPitch = pitch - maxDelta; + delta = maxDelta; + } + + bodyRotation.setPitch(targetPitch); + this.forceProviderStandardState.externalForce.addScaled(this.stateAfter.velocity, delta * -this.rotationForce); + } + } + } + } + } + + public boolean isOnGround() { + return this.onGround; + } + + public boolean isSwimming() { + return this.velocityExtremaCount <= 0; + } + + public static void computeReflectedVector(@Nonnull Vector3d vec, @Nonnull Vector3d normal, @Nonnull Vector3d result) { + result.assign(vec); + double squaredLength = normal.squaredLength(); + if (squaredLength != 0.0) { + double proj = vec.dot(normal) / squaredLength; + result.addScaled(normal, -2.0 * proj); + } + } + + public boolean isProvidingCharacterCollisions() { + return this.provideCharacterCollisions; + } + + public void setProvideCharacterCollisions(boolean provideCharacterCollisions) { + this.provideCharacterCollisions = provideCharacterCollisions; + } + + public void setGravity(double gravity, @Nonnull BoundingBox boundingBox) { + this.gravity = gravity; + this.recomputeDragFactors(boundingBox); + } + + public void setBounciness(double bounciness) { + this.bounciness = bounciness; + } + + public void setTerminalVelocities(double terminalVelocityAir, double terminalVelocityWater, @Nonnull BoundingBox boundingBox) { + this.setTerminalVelocities(terminalVelocityAir, 1.2, terminalVelocityWater, 998.0, boundingBox); + } + + public void setTerminalVelocities(double terminalVelocity1, double density1, double terminalVelocity2, double density2, @Nonnull BoundingBox boundingBox) { + this.terminalVelocity1 = terminalVelocity1; + this.density1 = density1; + this.terminalVelocity2 = terminalVelocity2; + this.density2 = density2; + this.recomputeDragFactors(boundingBox); + } + + @Nonnull + @Deprecated + public SimplePhysicsProvider setImpactSlowdown(double impactSlowdown) { + return this; + } + + public void setSticksVertically(boolean sticksVertically) { + this.sticksVertically = sticksVertically; + } + + public boolean isComputeYaw() { + return this.computeYaw; + } + + public void setComputeYaw(boolean computeYaw) { + this.computeYaw = computeYaw; + } + + public boolean isComputePitch() { + return this.computePitch; + } + + public void setComputePitch(boolean computePitch) { + this.computePitch = computePitch; + } + + public void setCreatorId(UUID creatorUuid) { + this.creatorUuid = creatorUuid; + } + + public void initialize(@Nullable Projectile projectile, @Nonnull BoundingBox boundingBox) { + if (projectile != null) { + this.boundingBox = boundingBox; + this.forceProviderEntity = new ForceProviderEntity(boundingBox); + this.forceProviders = new ForceProvider[]{this.forceProviderEntity}; + this.setGravity(projectile.getGravity(), boundingBox); + double terminalVelocity = projectile.getTerminalVelocity(); + this.setTerminalVelocities(terminalVelocity, terminalVelocity * projectile.getWaterTerminalVelocityMultiplier(), boundingBox); + this.hitWaterImpulseLoss = projectile.getWaterHitImpulseLoss(); + this.rotationForce = projectile.getDampingRotation(); + this.speedRotationFactor = (float)projectile.getRotationSpeedVelocityRatio(); + this.swimmingDampingFactor = projectile.getSwimmingDampingFactor(); + this.rotationMode = projectile.getRotationMode(); + this.setBounciness(projectile.getBounciness() * (1.0 - projectile.getImpactSlowdown())); + this.setImpactSlowdown(projectile.getImpactSlowdown()); + this.setSticksVertically(projectile.isSticksVertically()); + this.setComputeYaw(projectile.isComputeYaw()); + this.setComputePitch(projectile.isComputePitch()); + this.forceProviderEntity.setDensity(projectile.getDensity()); + } + } + + @Nonnull + public Vector3d getVelocity() { + return this.velocity; + } + + public void addVelocity(float x, float y, float z) { + this.forceProviderStandardState.externalVelocity.add(x, y, z); + } + + public void setVelocity(@Nonnull Vector3d velocity) { + this.forceProviderStandardState.nextTickVelocity.assign(velocity); + } + + public void setMoveOutOfSolid(boolean moveOutOfSolid) { + this.setMoveOutOfSolid(moveOutOfSolid ? 5.0 : 0.0); + } + + public void setMoveOutOfSolid(double speed) { + this.moveOutOfSolidSpeed = Math.max(0.0, speed); + } + + protected double getDragCoefficient(double density) { + return this.dragMultiplier * density + this.dragOffset; + } + + protected void recomputeDragFactors(@Nonnull BoundingBox boundingBoxComponent) { + Box boundingBox = boundingBoxComponent.getBoundingBox(); + double area = boundingBox.width() * boundingBox.depth(); + double mass = this.forceProviderEntity.getMass(boundingBox.getVolume()); + double drag1 = PhysicsMath.computeDragCoefficient(this.terminalVelocity1, area, mass, this.gravity); + double drag2 = PhysicsMath.computeDragCoefficient(this.terminalVelocity2, area, mass, this.gravity); + this.dragMultiplier = (drag2 - drag1) / (this.density2 - this.density1); + this.dragOffset = drag1 - this.dragMultiplier * this.density1; + } + + public static enum ROTATION_MODE { + None, + Velocity, + VelocityDamped; + + private ROTATION_MODE() { + } + } + + public static enum STATE { + Active, + Resting, + Inactive; + + private STATE() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/component/PhysicsValues.java b/src/com/hypixel/hytale/server/core/modules/physics/component/PhysicsValues.java new file mode 100644 index 0000000..c1c3499 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/component/PhysicsValues.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.server.core.modules.physics.component; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PhysicsValues implements Component { + @Nonnull + public static final Double ZERO = 0.0; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(PhysicsValues.class, PhysicsValues::new) + .append(new KeyedCodec<>("Mass", Codec.DOUBLE), (instance, value) -> instance.mass = value, instance -> instance.mass) + .addValidator(Validators.greaterThan(ZERO)) + .add() + .append( + new KeyedCodec<>("DragCoefficient", Codec.DOUBLE), (instance, value) -> instance.dragCoefficient = value, instance -> instance.dragCoefficient + ) + .addValidator(Validators.greaterThanOrEqual(ZERO)) + .add() + .append(new KeyedCodec<>("InvertedGravity", Codec.BOOLEAN), (instance, value) -> instance.invertedGravity = value, instance -> instance.invertedGravity) + .add() + .build(); + private static final double DEFAULT_MASS = 1.0; + private static final double DEFAULT_DRAG_COEFFICIENT = 0.5; + private static final boolean DEFAULT_INVERTED_GRAVITY = false; + protected double mass; + protected double dragCoefficient; + protected boolean invertedGravity; + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getPhysicsValuesComponentType(); + } + + public PhysicsValues() { + this(1.0, 0.5, false); + } + + public PhysicsValues(@Nonnull PhysicsValues other) { + this(other.mass, other.dragCoefficient, other.isInvertedGravity()); + } + + public PhysicsValues(double mass, double dragCoefficient, boolean invertedGravity) { + this.mass = mass; + this.dragCoefficient = dragCoefficient; + this.invertedGravity = invertedGravity; + } + + public void replaceValues(@Nonnull PhysicsValues other) { + this.mass = other.mass; + this.dragCoefficient = other.dragCoefficient; + this.invertedGravity = other.invertedGravity; + } + + public void resetToDefault() { + this.mass = 1.0; + this.dragCoefficient = 0.5; + this.invertedGravity = false; + } + + public void scale(float scale) { + this.mass *= scale; + this.dragCoefficient *= scale; + } + + public double getMass() { + return this.mass; + } + + public double getDragCoefficient() { + return this.dragCoefficient; + } + + public boolean isInvertedGravity() { + return this.invertedGravity; + } + + @Nonnull + public static PhysicsValues getDefault() { + return new PhysicsValues(); + } + + @Nonnull + @Override + public String toString() { + return "PhysicsValues{mass=" + this.mass + ", dragCoefficient=" + this.dragCoefficient + ", invertedGravity=" + this.invertedGravity + "}"; + } + + @Nonnull + @Override + public Component clone() { + return new PhysicsValues(this); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/component/Velocity.java b/src/com/hypixel/hytale/server/core/modules/physics/component/Velocity.java new file mode 100644 index 0000000..f997491 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/component/Velocity.java @@ -0,0 +1,161 @@ +package com.hypixel.hytale.server.core.modules.physics.component; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.ChangeVelocityType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.splitvelocity.VelocityConfig; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Velocity implements Component { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(Velocity.class, Velocity::new) + .append(new KeyedCodec<>("Velocity", Vector3d.CODEC), (entity, o) -> entity.velocity.assign(o), entity -> entity.velocity) + .add() + .build(); + @Nonnull + protected final List instructions = new ObjectArrayList<>(); + @Nonnull + protected final Vector3d velocity = new Vector3d(); + @Nonnull + protected final Vector3d clientVelocity = new Vector3d(); + + @Nonnull + public static ComponentType getComponentType() { + return EntityModule.get().getVelocityComponentType(); + } + + public Velocity() { + } + + public Velocity(@Nonnull Velocity other) { + this(other.velocity.clone()); + } + + public Velocity(@Nonnull Vector3d initialVelocity) { + this.velocity.assign(initialVelocity); + } + + public void setZero() { + this.set(0.0, 0.0, 0.0); + } + + public void addForce(@Nonnull Vector3d force) { + this.velocity.add(force); + } + + public void addForce(double x, double y, double z) { + this.velocity.add(x, y, z); + } + + public void set(@Nonnull Vector3d newVelocity) { + this.set(newVelocity.getX(), newVelocity.getY(), newVelocity.getZ()); + } + + public void set(double x, double y, double z) { + this.velocity.assign(x, y, z); + } + + public void setClient(@Nonnull Vector3d newVelocity) { + this.setClient(newVelocity.getX(), newVelocity.getY(), newVelocity.getZ()); + } + + public void setClient(double x, double y, double z) { + this.clientVelocity.assign(x, y, z); + } + + public void setX(double x) { + this.velocity.setX(x); + } + + public void setY(double y) { + this.velocity.setY(y); + } + + public void setZ(double z) { + this.velocity.setZ(z); + } + + public double getX() { + return this.velocity.getX(); + } + + public double getY() { + return this.velocity.getY(); + } + + public double getZ() { + return this.velocity.getZ(); + } + + public double getSpeed() { + return this.velocity.length(); + } + + public void addInstruction(@Nonnull Vector3d velocity, @Nullable VelocityConfig config, @Nonnull ChangeVelocityType type) { + this.instructions.add(new Velocity.Instruction(velocity, config, type)); + } + + @Nonnull + public List getInstructions() { + return this.instructions; + } + + @Nonnull + public Vector3d getVelocity() { + return this.velocity; + } + + @Nonnull + public Vector3d getClientVelocity() { + return this.clientVelocity; + } + + @Nonnull + public Vector3d assignVelocityTo(@Nonnull Vector3d vector) { + return vector.assign(this.velocity); + } + + @Nonnull + @Override + public Component clone() { + return new Velocity(this); + } + + public static final class Instruction { + @Nonnull + private final Vector3d velocity; + @Nullable + private final VelocityConfig config; + @Nonnull + private final ChangeVelocityType type; + + public Instruction(@Nonnull Vector3d velocity, @Nullable VelocityConfig config, @Nonnull ChangeVelocityType type) { + this.velocity = velocity; + this.config = config; + this.type = type; + } + + @Nonnull + public Vector3d getVelocity() { + return this.velocity; + } + + @Nullable + public VelocityConfig getConfig() { + return this.config; + } + + @Nonnull + public ChangeVelocityType getType() { + return this.type; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/systems/GenericVelocityInstructionSystem.java b/src/com/hypixel/hytale/server/core/modules/physics/systems/GenericVelocityInstructionSystem.java new file mode 100644 index 0000000..3cae599 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/systems/GenericVelocityInstructionSystem.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.server.core.modules.physics.systems; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemTypeDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import javax.annotation.Nonnull; + +public class GenericVelocityInstructionSystem extends EntityTickingSystem { + @Nonnull + private final Set> dependencies = Set.of( + new SystemTypeDependency<>(Order.AFTER, EntityModule.get().getVelocityModifyingSystemType()) + ); + @Nonnull + private final Query query = Query.and(Velocity.getComponentType()); + + public GenericVelocityInstructionSystem() { + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Velocity velocityComponent = archetypeChunk.getComponent(index, Velocity.getComponentType()); + + assert velocityComponent != null; + + for (Velocity.Instruction instruction : velocityComponent.getInstructions()) { + switch (instruction.getType()) { + case Set: + velocityComponent.set(instruction.getVelocity()); + break; + case Add: + velocityComponent.addForce(instruction.getVelocity()); + } + } + + velocityComponent.getInstructions().clear(); + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/systems/IVelocityModifyingSystem.java b/src/com/hypixel/hytale/server/core/modules/physics/systems/IVelocityModifyingSystem.java new file mode 100644 index 0000000..8716e3f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/systems/IVelocityModifyingSystem.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.server.core.modules.physics.systems; + +import com.hypixel.hytale.component.system.ISystem; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public interface IVelocityModifyingSystem extends ISystem { +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/systems/PhysicsValuesAddSystem.java b/src/com/hypixel/hytale/server/core/modules/physics/systems/PhysicsValuesAddSystem.java new file mode 100644 index 0000000..b85ae78 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/systems/PhysicsValuesAddSystem.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.modules.physics.systems; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.server.core.modules.entity.AllLegacyEntityTypesQuery; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.system.ModelSystems; +import com.hypixel.hytale.server.core.modules.physics.component.PhysicsValues; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import javax.annotation.Nonnull; + +public class PhysicsValuesAddSystem extends HolderSystem { + private final ComponentType physicsValuesComponentType; + @Nonnull + private final Query query; + private final Set> dependencies = Set.of( + new SystemDependency<>(Order.AFTER, ModelSystems.SetRenderedModel.class), new SystemDependency<>(Order.AFTER, ModelSystems.PlayerConnect.class) + ); + + public PhysicsValuesAddSystem(ComponentType physicsValuesComponentType) { + this.physicsValuesComponentType = physicsValuesComponentType; + this.query = Query.and(AllLegacyEntityTypesQuery.INSTANCE, Query.not(physicsValuesComponentType)); + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + PhysicsValues physicsValues = holder.ensureAndGetComponent(this.physicsValuesComponentType); + ModelComponent modelComponent = holder.getComponent(ModelComponent.getComponentType()); + if (modelComponent != null) { + physicsValues.replaceValues(modelComponent.getModel().getPhysicsValues()); + } + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/systems/VelocitySystems.java b/src/com/hypixel/hytale/server/core/modules/physics/systems/VelocitySystems.java new file mode 100644 index 0000000..93d1c35 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/systems/VelocitySystems.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.core.modules.physics.systems; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.server.core.modules.entity.AllLegacyEntityTypesQuery; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class VelocitySystems { + public VelocitySystems() { + } + + public static class AddSystem extends HolderSystem { + private final ComponentType velocityComponentType; + @Nonnull + private final Query query; + + public AddSystem(ComponentType velocityComponentType) { + this.velocityComponentType = velocityComponentType; + this.query = Query.and(AllLegacyEntityTypesQuery.INSTANCE, Query.not(velocityComponentType)); + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(this.velocityComponentType); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/util/ForceAccumulator.java b/src/com/hypixel/hytale/server/core/modules/physics/util/ForceAccumulator.java new file mode 100644 index 0000000..3c81e52 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/util/ForceAccumulator.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.core.modules.physics.util; + +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class ForceAccumulator { + public double speed; + public final Vector3d force = new Vector3d(); + public final Vector3d resistanceForceLimit = new Vector3d(); + + public ForceAccumulator() { + } + + public void initialize(@Nonnull PhysicsBodyState state, double mass, double timeStep) { + this.force.assign(Vector3d.ZERO); + this.speed = state.velocity.length(); + this.resistanceForceLimit.assign(state.velocity).scale(-mass / timeStep); + } + + protected void computeResultingForce( + @Nonnull PhysicsBodyState state, boolean onGround, @Nonnull ForceProvider[] forceProviders, double mass, double timeStep + ) { + this.initialize(state, mass, timeStep); + + for (ForceProvider provider : forceProviders) { + provider.update(state, this, onGround); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/util/ForceProvider.java b/src/com/hypixel/hytale/server/core/modules/physics/util/ForceProvider.java new file mode 100644 index 0000000..aa6d26b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/util/ForceProvider.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.server.core.modules.physics.util; + +public interface ForceProvider { + void update(PhysicsBodyState var1, ForceAccumulator var2, boolean var3); +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/util/ForceProviderEntity.java b/src/com/hypixel/hytale/server/core/modules/physics/util/ForceProviderEntity.java new file mode 100644 index 0000000..2b6ead9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/util/ForceProviderEntity.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.core.modules.physics.util; + +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import javax.annotation.Nonnull; + +@Deprecated +public class ForceProviderEntity extends ForceProviderStandard { + protected BoundingBox boundingBox; + protected ForceProviderStandardState forceProviderStandardState; + protected double density = 700.0; + + public ForceProviderEntity(BoundingBox boundingBox) { + this.boundingBox = boundingBox; + } + + public void setDensity(double density) { + this.density = density; + } + + public void setForceProviderStandardState(ForceProviderStandardState forceProviderStandardState) { + this.forceProviderStandardState = forceProviderStandardState; + } + + @Override + public ForceProviderStandardState getForceProviderStandardState() { + return this.forceProviderStandardState; + } + + @Override + public double getMass(double volume) { + return volume * this.getDensity(); + } + + @Override + public double getVolume() { + return this.boundingBox.getBoundingBox().getVolume(); + } + + @Override + public double getProjectedArea(@Nonnull PhysicsBodyState bodyState, double speed) { + double area = PhysicsMath.computeProjectedArea(bodyState.velocity, this.boundingBox.getBoundingBox()); + return area == 0.0 ? 0.0 : area / speed; + } + + @Override + public double getDensity() { + return this.density; + } + + @Override + public double getFrictionCoefficient() { + return 0.0; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/util/ForceProviderStandard.java b/src/com/hypixel/hytale/server/core/modules/physics/util/ForceProviderStandard.java new file mode 100644 index 0000000..ba6e605 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/util/ForceProviderStandard.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.server.core.modules.physics.util; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public abstract class ForceProviderStandard implements ForceProvider { + @Nonnull + public static HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + protected Vector3d dragForce = new Vector3d(); + + public ForceProviderStandard() { + } + + public abstract double getMass(double var1); + + public abstract double getVolume(); + + public abstract double getDensity(); + + public abstract double getProjectedArea(PhysicsBodyState var1, double var2); + + public abstract double getFrictionCoefficient(); + + public abstract ForceProviderStandardState getForceProviderStandardState(); + + @Override + public void update(@Nonnull PhysicsBodyState bodyState, @Nonnull ForceAccumulator accumulator, boolean onGround) { + ForceProviderStandardState standardState = this.getForceProviderStandardState(); + Vector3d extForce = standardState.externalForce; + double extForceY = extForce.y; + accumulator.force.add(extForce); + double speed = accumulator.speed; + double dragForceDivSpeed = standardState.dragCoefficient * this.getProjectedArea(bodyState, speed) * speed; + this.dragForce.assign(bodyState.velocity).scale(-dragForceDivSpeed); + this.clipForce(this.dragForce, accumulator.resistanceForceLimit); + accumulator.force.add(this.dragForce); + double gravityForce = -standardState.gravity * this.getMass(this.getVolume()); + if (onGround) { + double frictionForce = (gravityForce + extForceY) * this.getFrictionCoefficient(); + if (speed > 0.0 && frictionForce > 0.0) { + frictionForce /= speed; + accumulator.force.x = accumulator.force.x - bodyState.velocity.x * frictionForce; + accumulator.force.z = accumulator.force.z - bodyState.velocity.z * frictionForce; + } + } else { + accumulator.force.y += gravityForce; + } + + if (standardState.displacedMass != 0.0) { + accumulator.force.y = accumulator.force.y + standardState.displacedMass * standardState.gravity; + } + } + + public void clipForce(@Nonnull Vector3d value, @Nonnull Vector3d threshold) { + if (threshold.x < 0.0) { + if (value.x < threshold.x) { + value.x = threshold.x; + } + } else if (value.x > threshold.x) { + value.x = threshold.x; + } + + if (threshold.y < 0.0) { + if (value.y < threshold.y) { + value.y = threshold.y; + } + } else if (value.y > threshold.y) { + value.y = threshold.y; + } + + if (threshold.z < 0.0) { + if (value.z < threshold.z) { + value.z = threshold.z; + } + } else if (value.z > threshold.z) { + value.z = threshold.z; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/util/ForceProviderStandardState.java b/src/com/hypixel/hytale/server/core/modules/physics/util/ForceProviderStandardState.java new file mode 100644 index 0000000..b739a32 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/util/ForceProviderStandardState.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.modules.physics.util; + +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class ForceProviderStandardState { + public double displacedMass; + public double dragCoefficient; + public double gravity; + public final Vector3d nextTickVelocity = new Vector3d(); + public final Vector3d externalVelocity = new Vector3d(); + public final Vector3d externalAcceleration = new Vector3d(); + public final Vector3d externalForce = new Vector3d(); + public final Vector3d externalImpulse = new Vector3d(); + + public ForceProviderStandardState() { + this.nextTickVelocity.assign(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE); + } + + public void convertToForces(double dt, double mass) { + this.externalForce.addScaled(this.externalAcceleration, 1.0 / mass); + this.externalForce.addScaled(this.externalImpulse, 1.0 / dt); + this.externalAcceleration.assign(Vector3d.ZERO); + this.externalImpulse.assign(Vector3d.ZERO); + } + + public void updateVelocity(@Nonnull Vector3d velocity) { + if (this.nextTickVelocity.x < Double.MAX_VALUE) { + velocity.assign(this.nextTickVelocity); + this.nextTickVelocity.assign(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE); + } + + velocity.add(this.externalVelocity); + this.externalVelocity.assign(Vector3d.ZERO); + } + + public void clear() { + this.externalForce.assign(Vector3d.ZERO); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsBodyState.java b/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsBodyState.java new file mode 100644 index 0000000..593c1c8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsBodyState.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.modules.physics.util; + +import com.hypixel.hytale.math.vector.Vector3d; + +public class PhysicsBodyState { + public final Vector3d position = new Vector3d(); + public final Vector3d velocity = new Vector3d(); + + public PhysicsBodyState() { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsBodyStateUpdater.java b/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsBodyStateUpdater.java new file mode 100644 index 0000000..fda5487 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsBodyStateUpdater.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.server.core.modules.physics.util; + +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class PhysicsBodyStateUpdater { + protected static double MIN_VELOCITY = 1.0E-6; + @Nonnull + protected Vector3d acceleration = new Vector3d(); + protected final ForceAccumulator accumulator = new ForceAccumulator(); + + public PhysicsBodyStateUpdater() { + } + + public void update( + @Nonnull PhysicsBodyState before, @Nonnull PhysicsBodyState after, double mass, double dt, boolean onGround, @Nonnull ForceProvider[] forceProvider + ) { + this.computeAcceleration(before, onGround, forceProvider, mass, dt); + updatePositionBeforeVelocity(before, after, dt); + this.updateAndClampVelocity(before, after, dt); + } + + protected static void updatePositionBeforeVelocity(@Nonnull PhysicsBodyState before, @Nonnull PhysicsBodyState after, double dt) { + after.position.assign(before.position).addScaled(before.velocity, dt); + } + + protected static void updatePositionAfterVelocity(@Nonnull PhysicsBodyState before, @Nonnull PhysicsBodyState after, double dt) { + after.position.assign(before.position).addScaled(after.velocity, dt); + } + + protected void updateAndClampVelocity(@Nonnull PhysicsBodyState before, @Nonnull PhysicsBodyState after, double dt) { + this.updateVelocity(before, after, dt); + after.velocity.clipToZero(MIN_VELOCITY); + } + + protected void updateVelocity(@Nonnull PhysicsBodyState before, @Nonnull PhysicsBodyState after, double dt) { + after.velocity.assign(before.velocity).addScaled(this.acceleration, dt); + } + + protected void computeAcceleration(double mass) { + this.acceleration.assign(this.accumulator.force).scale(1.0 / mass); + } + + protected void computeAcceleration(@Nonnull PhysicsBodyState state, boolean onGround, @Nonnull ForceProvider[] forceProviders, double mass, double timeStep) { + this.accumulator.computeResultingForce(state, onGround, forceProviders, mass, timeStep); + this.computeAcceleration(mass); + } + + protected void assignAcceleration(@Nonnull PhysicsBodyState state) { + state.velocity.assign(this.acceleration); + } + + protected void addAcceleration(@Nonnull PhysicsBodyState state, double scale) { + state.velocity.addScaled(this.acceleration, scale); + } + + protected void addAcceleration(@Nonnull PhysicsBodyState state) { + state.velocity.add(this.acceleration); + } + + protected void convertAccelerationToVelocity(@Nonnull PhysicsBodyState before, @Nonnull PhysicsBodyState after, double scale) { + after.velocity.scale(scale).add(before.velocity).clipToZero(MIN_VELOCITY); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsBodyStateUpdaterMidpoint.java b/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsBodyStateUpdaterMidpoint.java new file mode 100644 index 0000000..2a40113 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsBodyStateUpdaterMidpoint.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.server.core.modules.physics.util; + +import javax.annotation.Nonnull; + +public class PhysicsBodyStateUpdaterMidpoint extends PhysicsBodyStateUpdater { + public PhysicsBodyStateUpdaterMidpoint() { + } + + @Override + public void update( + @Nonnull PhysicsBodyState before, @Nonnull PhysicsBodyState after, double mass, double dt, boolean onGround, @Nonnull ForceProvider[] forceProvider + ) { + double halfTime = 0.5 * dt; + this.computeAcceleration(before, onGround, forceProvider, mass, halfTime); + this.updateVelocity(before, after, halfTime); + updatePositionBeforeVelocity(before, after, halfTime); + this.computeAcceleration(after, onGround, forceProvider, mass, dt); + this.updateAndClampVelocity(before, after, dt); + updatePositionAfterVelocity(before, after, dt); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsBodyStateUpdaterRK4.java b/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsBodyStateUpdaterRK4.java new file mode 100644 index 0000000..f99833e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsBodyStateUpdaterRK4.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.core.modules.physics.util; + +import javax.annotation.Nonnull; + +public class PhysicsBodyStateUpdaterRK4 extends PhysicsBodyStateUpdater { + private final PhysicsBodyState state = new PhysicsBodyState(); + + public PhysicsBodyStateUpdaterRK4() { + } + + @Override + public void update( + @Nonnull PhysicsBodyState before, @Nonnull PhysicsBodyState after, double mass, double dt, boolean onGround, @Nonnull ForceProvider[] forceProvider + ) { + double halfTime = dt * 0.5; + this.computeAcceleration(before, onGround, forceProvider, mass, halfTime); + this.assignAcceleration(after); + this.updateVelocity(before, this.state, halfTime); + updatePositionBeforeVelocity(before, this.state, halfTime); + this.computeAcceleration(this.state, onGround, forceProvider, mass, halfTime); + this.addAcceleration(after, 2.0); + this.updateVelocity(before, this.state, halfTime); + updatePositionAfterVelocity(before, this.state, halfTime); + this.computeAcceleration(this.state, onGround, forceProvider, mass, halfTime); + this.addAcceleration(after, 2.0); + this.updateVelocity(before, this.state, dt); + updatePositionAfterVelocity(before, this.state, dt); + this.computeAcceleration(this.state, onGround, forceProvider, mass, dt); + this.addAcceleration(after); + this.convertAccelerationToVelocity(before, after, dt / 6.0); + updatePositionAfterVelocity(before, after, dt); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsBodyStateUpdaterSymplecticEuler.java b/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsBodyStateUpdaterSymplecticEuler.java new file mode 100644 index 0000000..c615965 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsBodyStateUpdaterSymplecticEuler.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.server.core.modules.physics.util; + +import javax.annotation.Nonnull; + +public class PhysicsBodyStateUpdaterSymplecticEuler extends PhysicsBodyStateUpdater { + public PhysicsBodyStateUpdaterSymplecticEuler() { + } + + @Override + public void update( + @Nonnull PhysicsBodyState before, @Nonnull PhysicsBodyState after, double mass, double dt, boolean onGround, @Nonnull ForceProvider[] forceProvider + ) { + this.computeAcceleration(before, onGround, forceProvider, mass, dt); + this.updateAndClampVelocity(before, after, dt); + updatePositionAfterVelocity(before, after, dt); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsConstants.java b/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsConstants.java new file mode 100644 index 0000000..8857710 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsConstants.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.server.core.modules.physics.util; + +public class PhysicsConstants { + public static final double GRAVITY_ACCELERATION = 32.0; + + public PhysicsConstants() { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsFlags.java b/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsFlags.java new file mode 100644 index 0000000..e460c93 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsFlags.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.modules.physics.util; + +public class PhysicsFlags { + public static final int NO_COLLISIONS = 0; + public static final int ENTITY_COLLISIONS = 1; + public static final int BLOCK_COLLISIONS = 2; + public static final int ALL_COLLISIONS = 3; + + public PhysicsFlags() { + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsMath.java b/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsMath.java new file mode 100644 index 0000000..e15bce3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/physics/util/PhysicsMath.java @@ -0,0 +1,130 @@ +package com.hypixel.hytale.server.core.modules.physics.util; + +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.TrigMathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public class PhysicsMath { + public static final double DENSITY_AIR = 1.2; + public static final double DENSITY_WATER = 998.0; + public static final double AIR_DENSITY = 0.001225; + public static final float HEADING_DIRECTION = 1.0F; + + public PhysicsMath() { + } + + public static double getAcceleration(double speed, double terminalSpeed) { + double ratio = Math.abs(speed / terminalSpeed); + return 32.0 * (1.0 - ratio * ratio * ratio); + } + + public static double getTerminalVelocity(double mass, double density, double areaMillimetersSquared, double dragCoefficient) { + double massGrams = mass * 1000.0; + double areaMetersSquared = areaMillimetersSquared * 1000000.0; + return Math.sqrt(64.0 * massGrams / (density * areaMetersSquared * dragCoefficient)); + } + + public static double getRelativeDensity(Vector3d position, Box boundingBox) { + return 0.001225; + } + + public static double computeDragCoefficient(double terminalSpeed, double area, double mass, double gravity) { + return mass * gravity / (area * terminalSpeed * terminalSpeed); + } + + public static double computeTerminalSpeed(double dragCoefficient, double area, double mass, double gravity) { + return Math.sqrt(mass * gravity / (area * dragCoefficient)); + } + + public static double computeProjectedArea(double x, double y, double z, @Nonnull Box box) { + double area = 0.0; + if (x != 0.0) { + area += Math.abs(x) * box.depth() * box.height(); + } + + if (y != 0.0) { + area += Math.abs(y) * box.depth() * box.width(); + } + + if (z != 0.0) { + area += Math.abs(z) * box.width() * box.height(); + } + + return area; + } + + public static double computeProjectedArea(@Nonnull Vector3d direction, @Nonnull Box box) { + return computeProjectedArea(direction.x, direction.y, direction.z, box); + } + + public static double volumeOfIntersection(@Nonnull Box a, @Nonnull Vector3d posA, @Nonnull Box b, @Nonnull Vector3d posB) { + return volumeOfIntersection(a, posA, b, posB.x, posB.y, posB.z); + } + + public static double volumeOfIntersection(@Nonnull Box a, @Nonnull Vector3d posA, @Nonnull Box b, double posBX, double posBY, double posBZ) { + posBX -= posA.x; + posBY -= posA.y; + posBZ -= posA.z; + return lengthOfIntersection(a.min.x, a.max.x, posBX + b.min.x, posBX + b.max.x) + * lengthOfIntersection(a.min.y, a.max.y, posBY + b.min.y, posBY + b.max.y) + * lengthOfIntersection(a.min.z, a.max.z, posBZ + b.min.z, posBZ + b.max.z); + } + + public static double lengthOfIntersection(double aMin, double aMax, double bMin, double bMax) { + double left = Math.max(aMin, bMin); + double right = Math.min(aMax, bMax); + return Math.max(0.0, right - left); + } + + public static float headingFromDirection(double x, double z) { + return 1.0F * TrigMathUtil.atan2(-x, -z); + } + + public static float normalizeAngle(float rad) { + rad %= (float) (Math.PI * 2); + if (rad < 0.0F) { + rad += (float) (Math.PI * 2); + } + + return rad; + } + + public static float normalizeTurnAngle(float rad) { + rad = normalizeAngle(rad); + if (rad >= (float) Math.PI) { + rad -= (float) (Math.PI * 2); + } + + return rad; + } + + public static float pitchFromDirection(double x, double y, double z) { + return TrigMathUtil.atan2(y, Math.sqrt(x * x + z * z)); + } + + @Nonnull + public static Vector3d vectorFromAngles(float heading, float pitch, @Nonnull Vector3d outDirection) { + float sx = pitchX(pitch); + outDirection.y = pitchY(pitch); + outDirection.x = headingX(heading) * sx; + outDirection.z = headingZ(heading) * sx; + return outDirection; + } + + public static float pitchX(float pitch) { + return TrigMathUtil.cos(pitch); + } + + public static float pitchY(float pitch) { + return TrigMathUtil.sin(pitch); + } + + public static float headingX(float heading) { + return -TrigMathUtil.sin(1.0F * heading); + } + + public static float headingZ(float heading) { + return -TrigMathUtil.cos(1.0F * heading); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/prefabspawner/PrefabSpawnerModule.java b/src/com/hypixel/hytale/server/core/modules/prefabspawner/PrefabSpawnerModule.java new file mode 100644 index 0000000..e84f5b9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/prefabspawner/PrefabSpawnerModule.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.core.modules.prefabspawner; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.server.core.modules.prefabspawner.commands.PrefabSpawnerCommand; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule; +import javax.annotation.Nonnull; + +public class PrefabSpawnerModule extends JavaPlugin { + @Nonnull + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(PrefabSpawnerModule.class).depends(BlockStateModule.class).build(); + + public PrefabSpawnerModule(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + this.getBlockStateRegistry().registerBlockState(PrefabSpawnerState.class, "prefabspawner", PrefabSpawnerState.CODEC); + this.getCommandRegistry().registerCommand(new PrefabSpawnerCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/prefabspawner/PrefabSpawnerState.java b/src/com/hypixel/hytale/server/core/modules/prefabspawner/PrefabSpawnerState.java new file mode 100644 index 0000000..03175b1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/prefabspawner/PrefabSpawnerState.java @@ -0,0 +1,190 @@ +package com.hypixel.hytale.server.core.modules.prefabspawner; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.prefab.PrefabWeights; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabSpawnerState extends BlockState { + public static final String PREFAB_SPAWNER_TYPE = "prefabspawner"; + public static final KeyedCodec FIT_HEIGHTMAP_CODEC = new KeyedCodec<>("FitHeightmap", Codec.BOOLEAN); + public static final KeyedCodec INHERIT_SEED_CODEC = new KeyedCodec<>("InheritSeed", Codec.BOOLEAN); + public static final KeyedCodec INHERIT_HEIGHT_CONDITION_CODEC = new KeyedCodec<>("InheritHeightCondition", Codec.BOOLEAN); + public static final KeyedCodec PREFAB_WEIGHTS_CODEC = new KeyedCodec<>("PrefabWeights", PrefabWeights.CODEC); + @Nonnull + public static final Codec CODEC = BuilderCodec.builder(PrefabSpawnerState.class, PrefabSpawnerState::new, BlockState.BASE_CODEC) + .append(new KeyedCodec<>("PrefabPath", Codec.STRING), (state, s) -> state.prefabPath = s, state -> state.prefabPath) + .documentation("The prefab path where the prefab is located. This uses the dot-notation. 'folder.folder.folder.filename'") + .add() + .append(FIT_HEIGHTMAP_CODEC, (state, s) -> state.fitHeightmap = s, state -> state.fitHeightmap) + .documentation("Determines if the child prefab should follow the heightmap during generation in worldgen.") + .add() + .append(INHERIT_SEED_CODEC, (state, s) -> state.inheritSeed = s, state -> state.inheritSeed) + .documentation("Determines if the child prefab should inherit the worldgen-id from the parent. This allows child prefabs to have independent markers.") + .add() + .append(INHERIT_HEIGHT_CONDITION_CODEC, (state, s) -> state.inheritHeightCondition = s, state -> state.inheritHeightCondition) + .documentation( + "Determines if the child prefab should inherit the HeightCondition from the parent. Setting to false allows child prefabs to bypass the height condition check." + ) + .add() + .append(PREFAB_WEIGHTS_CODEC, PrefabSpawnerState::setPrefabWeights, PrefabSpawnerState::getPrefabWeightsNullable) + .documentation( + "Determines the probability of each individual prefab file being selected to generate when the PrefabPath points to a folder containing multiple prefabs." + ) + .add() + .build(); + private String prefabPath; + private boolean fitHeightmap = false; + private boolean inheritSeed = true; + private boolean inheritHeightCondition = true; + private PrefabWeights prefabWeights = PrefabWeights.NONE; + + public PrefabSpawnerState() { + } + + public String getPrefabPath() { + return this.prefabPath; + } + + public void setPrefabPath(String prefabPath) { + this.prefabPath = prefabPath; + } + + public boolean isFitHeightmap() { + return this.fitHeightmap; + } + + public void setFitHeightmap(boolean fitHeightmap) { + this.fitHeightmap = fitHeightmap; + } + + public boolean isInheritSeed() { + return this.inheritSeed; + } + + public void setInheritSeed(boolean inheritSeed) { + this.inheritSeed = inheritSeed; + } + + public boolean isInheritHeightCondition() { + return this.inheritHeightCondition; + } + + public void setInheritHeightCondition(boolean inheritHeightCondition) { + this.inheritHeightCondition = inheritHeightCondition; + } + + public PrefabWeights getPrefabWeights() { + return this.prefabWeights; + } + + public void setPrefabWeights(PrefabWeights prefabWeights) { + this.prefabWeights = prefabWeights; + } + + @Nullable + private PrefabWeights getPrefabWeightsNullable() { + return this.prefabWeights.size() == 0 ? null : this.prefabWeights; + } + + public static class PrefabSpawnerSettingsPage extends InteractiveCustomUIPage { + private final PrefabSpawnerState state; + + public PrefabSpawnerSettingsPage(@Nonnull PlayerRef playerRef, PrefabSpawnerState state, @Nonnull CustomPageLifetime lifetime) { + super(playerRef, lifetime, PrefabSpawnerState.PrefabSpawnerSettingsPageEventData.CODEC); + this.state = state; + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/PrefabSpawnerSettingsPage.ui"); + commandBuilder.set("#PrefabPath.Value", Objects.requireNonNullElse(this.state.prefabPath, "")); + commandBuilder.set("#FitHeightmap #CheckBox.Value", this.state.fitHeightmap); + commandBuilder.set("#InheritSeed #CheckBox.Value", this.state.inheritSeed); + commandBuilder.set("#InheritHeightCondition #CheckBox.Value", this.state.inheritHeightCondition); + commandBuilder.set("#DefaultWeight.Value", this.state.getPrefabWeights().getDefaultWeight()); + commandBuilder.set("#PrefabWeights.Value", this.state.getPrefabWeights().getMappingString()); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#SaveButton", + new EventData() + .append("@PrefabPath", "#PrefabPath.Value") + .append("@FitHeightmap", "#FitHeightmap #CheckBox.Value") + .append("@InheritSeed", "#InheritSeed #CheckBox.Value") + .append("@InheritHeightCondition", "#InheritHeightCondition #CheckBox.Value") + .append("@DefaultWeight", "#DefaultWeight.Value") + .append("@PrefabWeights", "#PrefabWeights.Value") + ); + } + + public void handleDataEvent( + @Nonnull Ref ref, @Nonnull Store store, @Nonnull PrefabSpawnerState.PrefabSpawnerSettingsPageEventData data + ) { + this.state.prefabPath = data.prefabPath; + this.state.fitHeightmap = data.fitHeightmap; + this.state.inheritSeed = data.inheritSeed; + this.state.inheritHeightCondition = data.inheritHeightCondition; + this.state.prefabWeights = PrefabWeights.parse(data.prefabWeights); + this.state.prefabWeights.setDefaultWeight(data.defaultWeight); + this.state.markNeedsSave(); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + playerComponent.getPageManager().setPage(ref, store, Page.None); + } + } + + public static class PrefabSpawnerSettingsPageEventData { + public static final String KEY_PREFAB_PATH = "@PrefabPath"; + public static final String KEY_FIT_HEIGHTMAP = "@FitHeightmap"; + public static final String KEY_INHERIT_SEED = "@InheritSeed"; + public static final String KEY_INHERIT_HEIGHT_CONDITION = "@InheritHeightCondition"; + public static final String KEY_DEFAULT_WEIGHT = "@DefaultWeight"; + public static final String KEY_PREFAB_WEIGHTS = "@PrefabWeights"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + PrefabSpawnerState.PrefabSpawnerSettingsPageEventData.class, PrefabSpawnerState.PrefabSpawnerSettingsPageEventData::new + ) + .append(new KeyedCodec<>("@PrefabPath", Codec.STRING), (entry, s) -> entry.prefabPath = s, entry -> entry.prefabPath) + .add() + .append(new KeyedCodec<>("@FitHeightmap", Codec.BOOLEAN), (entry, s) -> entry.fitHeightmap = s, entry -> entry.fitHeightmap) + .add() + .append(new KeyedCodec<>("@InheritSeed", Codec.BOOLEAN), (entry, s) -> entry.inheritSeed = s, entry -> entry.inheritSeed) + .add() + .append( + new KeyedCodec<>("@InheritHeightCondition", Codec.BOOLEAN), (entry, s) -> entry.inheritHeightCondition = s, entry -> entry.inheritHeightCondition + ) + .add() + .append(new KeyedCodec<>("@DefaultWeight", Codec.DOUBLE), (entry, s) -> entry.defaultWeight = s, entry -> entry.defaultWeight) + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .append(new KeyedCodec<>("@PrefabWeights", Codec.STRING), (entry, s) -> entry.prefabWeights = s, entry -> entry.prefabWeights) + .add() + .build(); + private String prefabPath; + private boolean fitHeightmap; + private boolean inheritSeed; + private boolean inheritHeightCondition; + private double defaultWeight; + private String prefabWeights; + + public PrefabSpawnerSettingsPageEventData() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/prefabspawner/commands/PrefabSpawnerCommand.java b/src/com/hypixel/hytale/server/core/modules/prefabspawner/commands/PrefabSpawnerCommand.java new file mode 100644 index 0000000..adaa6b4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/prefabspawner/commands/PrefabSpawnerCommand.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.server.core.modules.prefabspawner.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class PrefabSpawnerCommand extends AbstractCommandCollection { + public PrefabSpawnerCommand() { + super("prefabspawner", "server.commands.prefabspawner.desc"); + this.addAliases("pspawner"); + this.addSubCommand(new PrefabSpawnerGetCommand()); + this.addSubCommand(new PrefabSpawnerSetCommand()); + this.addSubCommand(new PrefabSpawnerWeightCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/prefabspawner/commands/PrefabSpawnerGetCommand.java b/src/com/hypixel/hytale/server/core/modules/prefabspawner/commands/PrefabSpawnerGetCommand.java new file mode 100644 index 0000000..2b09f78 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/prefabspawner/commands/PrefabSpawnerGetCommand.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.modules.prefabspawner.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.modules.prefabspawner.PrefabSpawnerState; +import com.hypixel.hytale.server.core.prefab.PrefabWeights; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class PrefabSpawnerGetCommand extends TargetPrefabSpawnerCommand { + public PrefabSpawnerGetCommand() { + super("get", "server.commands.prefabspawner.get.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull WorldChunk chunk, @Nonnull PrefabSpawnerState prefabSpawner) { + String prefab = Objects.requireNonNullElse(prefabSpawner.getPrefabPath(), ""); + context.sendMessage(Message.translation("server.commands.prefabspawner.get.path").param("prefab", prefab)); + context.sendMessage(Message.translation("server.commands.prefabspawner.get.fitsHeightmap").param("fitHeightmap", prefabSpawner.isFitHeightmap())); + context.sendMessage(Message.translation("server.commands.prefabspawner.get.inheritsSeed").param("inheritSeed", prefabSpawner.isInheritSeed())); + context.sendMessage( + Message.translation("server.commands.prefabspawner.get.inheritsHeightCheck").param("inheritHeightCheck", prefabSpawner.isInheritHeightCondition()) + ); + PrefabWeights weights = prefabSpawner.getPrefabWeights(); + if (weights.size() != 0) { + context.sendMessage(Message.translation("server.commands.prefabspawner.get.defaultWeight").param("weight", weights.getDefaultWeight())); + context.sendMessage(Message.translation("server.commands.prefabspawner.get.weights").param("weights", weights.getMappingString())); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/prefabspawner/commands/PrefabSpawnerSetCommand.java b/src/com/hypixel/hytale/server/core/modules/prefabspawner/commands/PrefabSpawnerSetCommand.java new file mode 100644 index 0000000..994f486 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/prefabspawner/commands/PrefabSpawnerSetCommand.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.server.core.modules.prefabspawner.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.modules.prefabspawner.PrefabSpawnerState; +import com.hypixel.hytale.server.core.prefab.PrefabWeights; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import javax.annotation.Nonnull; + +public class PrefabSpawnerSetCommand extends TargetPrefabSpawnerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_PREFAB_SPAWNER_SET = Message.translation("server.commands.prefabspawner.set"); + @Nonnull + protected final RequiredArg prefabPathArg = this.withRequiredArg("prefab", "server.commands.prefabspawner.set.prefab.desc", ArgTypes.STRING); + @Nonnull + protected final OptionalArg fitHeightmapArg = this.withOptionalArg( + "fitHeightmap", "server.commands.prefabspawner.set.fitHeightmap.desc", ArgTypes.BOOLEAN + ); + @Nonnull + protected final OptionalArg inheritSeedArg = this.withOptionalArg( + "inheritSeed", "server.commands.prefabspawner.set.inheritSeed.desc", ArgTypes.BOOLEAN + ); + @Nonnull + protected final OptionalArg inheritHeightCheckArg = this.withOptionalArg( + "inheritHeightCheck", "server.commands.prefabspawner.set.inheritHeightCheck.desc", ArgTypes.BOOLEAN + ); + @Nonnull + protected final OptionalArg defaultWeightArg = this.withOptionalArg( + "defaultWeight", "server.commands.prefabspawner.set.defaultWeight.desc", ArgTypes.DOUBLE + ); + + public PrefabSpawnerSetCommand() { + super("set", "server.commands.prefabspawner.set.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull WorldChunk chunk, @Nonnull PrefabSpawnerState prefabSpawner) { + String prefabPath = this.prefabPathArg.get(context); + prefabSpawner.setPrefabPath(prefabPath); + if (this.fitHeightmapArg.provided(context)) { + boolean fitHeightmap = getOrDefault(this.fitHeightmapArg, context, true); + prefabSpawner.setFitHeightmap(fitHeightmap); + } + + if (this.inheritSeedArg.provided(context)) { + boolean inheritSeed = getOrDefault(this.inheritSeedArg, context, true); + prefabSpawner.setInheritSeed(inheritSeed); + } + + if (this.inheritHeightCheckArg.provided(context)) { + boolean inheritHeightCheck = getOrDefault(this.inheritHeightCheckArg, context, true); + prefabSpawner.setInheritHeightCondition(inheritHeightCheck); + } + + if (this.defaultWeightArg.provided(context)) { + double weight = this.defaultWeightArg.get(context); + PrefabWeights prefabWeights = prefabSpawner.getPrefabWeights(); + if (prefabWeights == PrefabWeights.NONE) { + prefabWeights = new PrefabWeights(); + } + + prefabWeights.setDefaultWeight(weight); + prefabSpawner.setPrefabWeights(prefabWeights); + } + + chunk.markNeedsSaving(); + context.sendMessage(MESSAGE_COMMANDS_PREFAB_SPAWNER_SET); + } + + protected static boolean getOrDefault(@Nonnull OptionalArg arg, @Nonnull CommandContext context, boolean defaultValue) { + if (!arg.provided(context)) { + return defaultValue; + } else { + Boolean value = arg.get(context); + return value != null ? value : defaultValue; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/prefabspawner/commands/PrefabSpawnerWeightCommand.java b/src/com/hypixel/hytale/server/core/modules/prefabspawner/commands/PrefabSpawnerWeightCommand.java new file mode 100644 index 0000000..8d71fb7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/prefabspawner/commands/PrefabSpawnerWeightCommand.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.server.core.modules.prefabspawner.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.modules.prefabspawner.PrefabSpawnerState; +import com.hypixel.hytale.server.core.prefab.PrefabWeights; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import javax.annotation.Nonnull; + +public class PrefabSpawnerWeightCommand extends TargetPrefabSpawnerCommand { + @Nonnull + private final RequiredArg prefabArg = this.withRequiredArg("prefab", "server.commands.prefabspawner.weight.prefab.desc", ArgTypes.STRING); + @Nonnull + private final RequiredArg weightArg = this.withRequiredArg("weight", "server.commands.prefabspawner.weight.weight.desc", ArgTypes.FLOAT); + + public PrefabSpawnerWeightCommand() { + super("weight", "server.commands.prefabspawner.weight.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull WorldChunk chunk, @Nonnull PrefabSpawnerState prefabSpawner) { + String prefab = this.prefabArg.get(context); + Float weight = this.weightArg.get(context); + PrefabWeights prefabWeights = prefabSpawner.getPrefabWeights(); + if (prefabWeights == PrefabWeights.NONE) { + prefabWeights = new PrefabWeights(); + } + + if (weight < 0.0F) { + prefabWeights.removeWeight(prefab); + context.sendMessage(Message.translation("server.commands.prefabspawner.weight.remove").param("prefab", prefab)); + } else { + prefabWeights.setWeight(prefab, weight.floatValue()); + context.sendMessage(Message.translation("server.commands.prefabspawner.weight.set").param("prefab", prefab).param("weight", weight)); + } + + prefabSpawner.setPrefabWeights(prefabWeights); + chunk.markNeedsSaving(); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/prefabspawner/commands/TargetPrefabSpawnerCommand.java b/src/com/hypixel/hytale/server/core/modules/prefabspawner/commands/TargetPrefabSpawnerCommand.java new file mode 100644 index 0000000..7c28983 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/prefabspawner/commands/TargetPrefabSpawnerCommand.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.server.core.modules.prefabspawner.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeIntPosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.modules.prefabspawner.PrefabSpawnerState; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import javax.annotation.Nonnull; + +public abstract class TargetPrefabSpawnerCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_GENERAL_BLOCK_TARGET_NOT_IN_RANGE = Message.translation("server.general.blockTargetNotInRange"); + @Nonnull + private static final Message MESSAGE_COMMANDS_ERRORS_PROVIDE_POSITION = Message.translation("server.commands.errors.providePosition"); + @Nonnull + protected final OptionalArg positionArg = this.withOptionalArg( + "position", "server.commands.prefabspawner.position.desc", ArgTypes.RELATIVE_BLOCK_POSITION + ); + + public TargetPrefabSpawnerCommand(@Nonnull String name, @Nonnull String description) { + super(name, description); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Vector3i target; + if (this.positionArg.provided(context)) { + RelativeIntPosition relativePosition = this.positionArg.get(context); + target = relativePosition.getBlockPosition(context, store); + } else { + if (!context.isPlayer()) { + throw new GeneralCommandException(MESSAGE_COMMANDS_ERRORS_PROVIDE_POSITION); + } + + Ref playerRef = context.senderAsPlayerRef(); + Vector3i targetBlock = TargetUtil.getTargetBlock(playerRef, 10.0, store); + if (targetBlock == null) { + throw new GeneralCommandException(MESSAGE_GENERAL_BLOCK_TARGET_NOT_IN_RANGE); + } + + target = targetBlock; + } + + WorldChunk chunk = world.getChunk(ChunkUtil.indexChunkFromBlock(target.x, target.z)); + BlockState state = chunk.getState(target.x, target.y, target.z); + if (!(state instanceof PrefabSpawnerState)) { + context.sendMessage(Message.translation("server.commands.prefabspawner.spawnerNotFoundAtTarget").param("pos", target.toString())); + } else { + this.execute(context, chunk, (PrefabSpawnerState)state); + } + } + + protected abstract void execute(@Nonnull CommandContext var1, @Nonnull WorldChunk var2, @Nonnull PrefabSpawnerState var3); +} diff --git a/src/com/hypixel/hytale/server/core/modules/projectile/ProjectileModule.java b/src/com/hypixel/hytale/server/core/modules/projectile/ProjectileModule.java new file mode 100644 index 0000000..f6aab7c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/projectile/ProjectileModule.java @@ -0,0 +1,205 @@ +package com.hypixel.hytale.server.core.modules.projectile; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.modules.collision.CollisionModule; +import com.hypixel.hytale.server.core.modules.entity.DespawnComponent; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.AudioComponent; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.PersistentModel; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.Interactions; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.modules.projectile.component.PredictedProjectile; +import com.hypixel.hytale.server.core.modules.projectile.component.Projectile; +import com.hypixel.hytale.server.core.modules.projectile.config.PhysicsConfig; +import com.hypixel.hytale.server.core.modules.projectile.config.ProjectileConfig; +import com.hypixel.hytale.server.core.modules.projectile.config.StandardPhysicsConfig; +import com.hypixel.hytale.server.core.modules.projectile.config.StandardPhysicsProvider; +import com.hypixel.hytale.server.core.modules.projectile.interaction.ProjectileInteraction; +import com.hypixel.hytale.server.core.modules.projectile.system.PredictedProjectileSystems; +import com.hypixel.hytale.server.core.modules.projectile.system.StandardPhysicsTickSystem; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Duration; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ProjectileModule extends JavaPlugin { + @Nonnull + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(ProjectileModule.class) + .description( + "This module implements the new projectile system. Disabling this module will prevent anything using the new projectile system from functioning." + ) + .depends(CollisionModule.class) + .depends(EntityModule.class) + .build(); + public static final Message MESSAGE_GENERAL_UNKNOWN = Message.translation("server.general.unknown"); + private static ProjectileModule instance; + private ComponentType projectileComponentType; + private ComponentType standardPhysicsProviderComponentType; + private ComponentType predictedProjectileComponentType; + + public static ProjectileModule get() { + return instance; + } + + public ProjectileModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + ComponentRegistryProxy entityStoreRegistry = this.getEntityStoreRegistry(); + this.getCodecRegistry(Interaction.CODEC).register("Projectile", ProjectileInteraction.class, ProjectileInteraction.CODEC); + this.projectileComponentType = entityStoreRegistry.registerComponent(Projectile.class, "IsProjectile", Projectile.CODEC); + this.predictedProjectileComponentType = entityStoreRegistry.registerComponent(PredictedProjectile.class, () -> { + throw new UnsupportedOperationException(); + }); + this.standardPhysicsProviderComponentType = entityStoreRegistry.registerComponent(StandardPhysicsProvider.class, () -> { + throw new UnsupportedOperationException(); + }); + entityStoreRegistry.registerSystem(new StandardPhysicsTickSystem()); + entityStoreRegistry.registerSystem(new PredictedProjectileSystems.EntityTrackerUpdate()); + this.getCodecRegistry(PhysicsConfig.CODEC).register("Standard", StandardPhysicsConfig.class, StandardPhysicsConfig.CODEC); + } + + @Nonnull + public Ref spawnProjectile( + Ref creatorRef, + @Nonnull CommandBuffer commandBuffer, + @Nonnull ProjectileConfig config, + @Nonnull Vector3d position, + @Nonnull Vector3d direction + ) { + return this.spawnProjectile(null, creatorRef, commandBuffer, config, position, direction); + } + + @Nonnull + public Ref spawnProjectile( + @Nullable UUID predictionId, + Ref creatorRef, + @Nonnull CommandBuffer commandBuffer, + @Nonnull ProjectileConfig config, + @Nonnull Vector3d position, + @Nonnull Vector3d direction + ) { + Holder holder = EntityStore.REGISTRY.newHolder(); + Vector3f rotation = new Vector3f(); + Direction rotationOffset = config.getSpawnRotationOffset(); + rotation.setYaw(PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(direction.x, direction.z))); + rotation.setPitch(PhysicsMath.pitchFromDirection(direction.x, direction.y, direction.z)); + rotation.add(rotationOffset.pitch, rotationOffset.yaw, rotationOffset.roll); + PhysicsMath.vectorFromAngles(rotation.getYaw(), rotation.getPitch(), direction); + Vector3d offset = config.getCalculatedOffset(rotation.getPitch(), rotation.getYaw()); + position.add(offset); + holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(position, rotation)); + holder.addComponent(HeadRotation.getComponentType(), new HeadRotation(rotation)); + if (predictionId != null) { + holder.addComponent(UUIDComponent.getComponentType(), new UUIDComponent(predictionId)); + } + + holder.addComponent(Interactions.getComponentType(), new Interactions(config.getInteractions())); + Model model = config.getModel(); + holder.addComponent(ModelComponent.getComponentType(), new ModelComponent(model)); + holder.addComponent(PersistentModel.getComponentType(), new PersistentModel(model.toReference())); + holder.addComponent(BoundingBox.getComponentType(), new BoundingBox(model.getBoundingBox())); + holder.addComponent(NetworkId.getComponentType(), new NetworkId(commandBuffer.getExternalData().takeNextNetworkId())); + holder.ensureComponent(Projectile.getComponentType()); + if (predictionId != null) { + holder.addComponent(PredictedProjectile.getComponentType(), new PredictedProjectile(predictionId)); + } + + holder.addComponent(Velocity.getComponentType(), new Velocity()); + config.getPhysicsConfig().apply(holder, creatorRef, direction.clone().scale(config.getLaunchForce()), commandBuffer, predictionId != null); + holder.ensureComponent(EntityStore.REGISTRY.getNonSerializedComponentType()); + holder.addComponent( + DespawnComponent.getComponentType(), + new DespawnComponent(commandBuffer.getResource(TimeResource.getResourceType()).getNow().plus(Duration.ofSeconds(300L))) + ); + int launchWorldSoundEventIndex = config.getLaunchWorldSoundEventIndex(); + if (launchWorldSoundEventIndex != 0) { + SoundUtil.playSoundEvent3d( + launchWorldSoundEventIndex, SoundCategory.SFX, position.x, position.y, position.z, targetRef -> !targetRef.equals(creatorRef), commandBuffer + ); + } + + int projectileSoundEventIndex = config.getProjectileSoundEventIndex(); + if (projectileSoundEventIndex != 0) { + AudioComponent audioComponent = new AudioComponent(); + audioComponent.addSound(projectileSoundEventIndex); + holder.addComponent(AudioComponent.getComponentType(), audioComponent); + } + + Ref projectileRef = commandBuffer.addEntity(holder, AddReason.SPAWN); + if (predictionId == null && creatorRef != null) { + commandBuffer.run(store -> onProjectileSpawnInteraction(projectileRef, creatorRef, store)); + } + + return projectileRef; + } + + private static void onProjectileSpawnInteraction(@Nonnull Ref ref, @Nonnull Ref creatorRef, @Nonnull Store store) { + InteractionManager interactionManagerComponent = store.getComponent(creatorRef, InteractionModule.get().getInteractionManagerComponent()); + if (interactionManagerComponent != null) { + if (EntityUtils.getEntity(creatorRef, store) instanceof LivingEntity livingEntity) { + InteractionContext context = InteractionContext.forProxyEntity(interactionManagerComponent, livingEntity, ref); + String rootInteractionId = context.getRootInteractionId(InteractionType.ProjectileSpawn); + if (rootInteractionId != null) { + RootInteraction rootInteraction = RootInteraction.getRootInteractionOrUnknown(rootInteractionId); + if (rootInteraction != null) { + InteractionChain chain = interactionManagerComponent.initChain(InteractionType.ProjectileSpawn, context, rootInteraction, true); + interactionManagerComponent.queueExecuteChain(chain); + } + } + } + } + } + + @Nonnull + public ComponentType getProjectileComponentType() { + return this.projectileComponentType; + } + + @Nonnull + public ComponentType getStandardPhysicsProviderComponentType() { + return this.standardPhysicsProviderComponentType; + } + + @Nonnull + public ComponentType getPredictedProjectileComponentType() { + return this.predictedProjectileComponentType; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/projectile/component/PredictedProjectile.java b/src/com/hypixel/hytale/server/core/modules/projectile/component/PredictedProjectile.java new file mode 100644 index 0000000..d30fcdc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/projectile/component/PredictedProjectile.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.core.modules.projectile.component; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.projectile.ProjectileModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class PredictedProjectile implements Component { + @Nonnull + private final UUID uuid; + + @Nonnull + public static ComponentType getComponentType() { + return ProjectileModule.get().getPredictedProjectileComponentType(); + } + + public PredictedProjectile(@Nonnull UUID uuid) { + this.uuid = uuid; + } + + @Nonnull + public UUID getUuid() { + return this.uuid; + } + + @Nonnull + @Override + public Component clone() { + return this; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/projectile/component/Projectile.java b/src/com/hypixel/hytale/server/core/modules/projectile/component/Projectile.java new file mode 100644 index 0000000..cf2abd6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/projectile/component/Projectile.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.modules.projectile.component; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.projectile.ProjectileModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class Projectile implements Component { + @Nonnull + public static Projectile INSTANCE = new Projectile(); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(Projectile.class, () -> INSTANCE).build(); + + public static ComponentType getComponentType() { + return ProjectileModule.get().getProjectileComponentType(); + } + + private Projectile() { + } + + @Nonnull + @Override + public Component clone() { + return this; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/projectile/config/BallisticData.java b/src/com/hypixel/hytale/server/core/modules/projectile/config/BallisticData.java new file mode 100644 index 0000000..6c330eb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/projectile/config/BallisticData.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.server.core.modules.projectile.config; + +public interface BallisticData { + double getMuzzleVelocity(); + + double getGravity(); + + double getVerticalCenterShot(); + + double getDepthShot(); + + boolean isPitchAdjustShot(); +} diff --git a/src/com/hypixel/hytale/server/core/modules/projectile/config/BallisticDataProvider.java b/src/com/hypixel/hytale/server/core/modules/projectile/config/BallisticDataProvider.java new file mode 100644 index 0000000..0e33e52 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/projectile/config/BallisticDataProvider.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.server.core.modules.projectile.config; + +import javax.annotation.Nullable; + +public interface BallisticDataProvider { + @Nullable + BallisticData getBallisticData(); +} diff --git a/src/com/hypixel/hytale/server/core/modules/projectile/config/BounceConsumer.java b/src/com/hypixel/hytale/server/core/modules/projectile/config/BounceConsumer.java new file mode 100644 index 0000000..597400d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/projectile/config/BounceConsumer.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.core.modules.projectile.config; + +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +@FunctionalInterface +public interface BounceConsumer { + void onBounce(@Nonnull Ref var1, @Nonnull Vector3d var2, @Nonnull CommandBuffer var3); +} diff --git a/src/com/hypixel/hytale/server/core/modules/projectile/config/ImpactConsumer.java b/src/com/hypixel/hytale/server/core/modules/projectile/config/ImpactConsumer.java new file mode 100644 index 0000000..c4a3ae3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/projectile/config/ImpactConsumer.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.server.core.modules.projectile.config; + +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@FunctionalInterface +public interface ImpactConsumer { + void onImpact( + @Nonnull Ref var1, @Nonnull Vector3d var2, @Nullable Ref var3, @Nullable String var4, @Nonnull CommandBuffer var5 + ); +} diff --git a/src/com/hypixel/hytale/server/core/modules/projectile/config/PhysicsConfig.java b/src/com/hypixel/hytale/server/core/modules/projectile/config/PhysicsConfig.java new file mode 100644 index 0000000..035e6b2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/projectile/config/PhysicsConfig.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.core.modules.projectile.config; + +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface PhysicsConfig extends NetworkSerializable { + @Nonnull + CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + + void apply( + @Nonnull Holder var1, @Nullable Ref var2, @Nonnull Vector3d var3, @Nonnull ComponentAccessor var4, boolean var5 + ); + + default double getGravity() { + return 0.0; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/projectile/config/ProjectileConfig.java b/src/com/hypixel/hytale/server/core/modules/projectile/config/ProjectileConfig.java new file mode 100644 index 0000000..f62b702 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/projectile/config/ProjectileConfig.java @@ -0,0 +1,265 @@ +package com.hypixel.hytale.server.core.modules.projectile.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.Vector3f; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.asset.type.soundevent.validator.SoundEventValidators; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import java.util.Collections; +import java.util.EnumMap; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ProjectileConfig + implements JsonAssetWithMap>, + NetworkSerializable, + BallisticData { + @Nonnull + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ProjectileConfig.class, + ProjectileConfig::new, + Codec.STRING, + (config, s) -> config.id = s, + config -> config.id, + (asset, data) -> asset.data = data, + asset -> asset.data + ) + .appendInherited( + new KeyedCodec<>("Physics", PhysicsConfig.CODEC), (o, i) -> o.physicsConfig = i, o -> o.physicsConfig, (o, p) -> o.physicsConfig = p.physicsConfig + ) + .add() + .appendInherited(new KeyedCodec<>("Model", Codec.STRING), (o, i) -> o.model = i, o -> o.model, (o, p) -> o.model = p.model) + .addValidator(Validators.nonNull()) + .addValidator(ModelAsset.VALIDATOR_CACHE.getValidator()) + .add() + .appendInherited(new KeyedCodec<>("LaunchForce", Codec.DOUBLE), (o, i) -> o.launchForce = i, o -> o.launchForce, (o, p) -> o.launchForce = p.launchForce) + .add() + .appendInherited( + new KeyedCodec<>("SpawnOffset", ProtocolCodecs.VECTOR3F), (o, i) -> o.spawnOffset = i, o -> o.spawnOffset, (o, p) -> o.spawnOffset = p.spawnOffset + ) + .addValidator(Validators.nonNull()) + .add() + .appendInherited(new KeyedCodec<>("SpawnRotationOffset", ProtocolCodecs.DIRECTION), (o, i) -> { + o.spawnRotationOffset = i; + o.spawnRotationOffset.yaw *= (float) (Math.PI / 180.0); + o.spawnRotationOffset.pitch *= (float) (Math.PI / 180.0); + o.spawnRotationOffset.roll *= (float) (Math.PI / 180.0); + }, o -> o.spawnRotationOffset, (o, p) -> o.spawnRotationOffset = p.spawnRotationOffset) + .addValidator(Validators.nonNull()) + .add() + .>appendInherited( + new KeyedCodec<>("Interactions", new EnumMapCodec<>(InteractionType.class, RootInteraction.CHILD_ASSET_CODEC)), + (o, i) -> o.interactions = i, + o -> o.interactions, + (o, p) -> o.interactions = p.interactions + ) + .addValidatorLate(() -> RootInteraction.VALIDATOR_CACHE.getMapValueValidator().late()) + .addValidator(Validators.nonNull()) + .add() + .appendInherited( + new KeyedCodec<>("LaunchLocalSoundEventId", Codec.STRING), + (o, i) -> o.launchLocalSoundEventId = i, + o -> o.launchLocalSoundEventId, + (o, p) -> o.launchLocalSoundEventId = p.launchLocalSoundEventId + ) + .addValidator(SoundEventValidators.ONESHOT) + .documentation("The sound event played to the throwing player when the projectile is spawned/launched") + .add() + .appendInherited( + new KeyedCodec<>("LaunchWorldSoundEventId", Codec.STRING), + (o, i) -> o.launchWorldSoundEventId = i, + o -> o.launchWorldSoundEventId, + (o, p) -> o.launchWorldSoundEventId = p.launchWorldSoundEventId + ) + .addValidator(SoundEventValidators.MONO) + .addValidator(SoundEventValidators.ONESHOT) + .documentation("The positioned sound event played to surrounding players when the projectile is spawned/launched") + .add() + .appendInherited( + new KeyedCodec<>("ProjectileSoundEventId", Codec.STRING), + (o, i) -> o.projectileSoundEventId = i, + o -> o.projectileSoundEventId, + (o, p) -> o.projectileSoundEventId = p.projectileSoundEventId + ) + .addValidator(SoundEventValidators.LOOPING) + .addValidator(SoundEventValidators.MONO) + .documentation("The looping sound event to attach to the projectile.") + .add() + .afterDecode(ProjectileConfig::processConfig) + .build(); + @Nullable + private static AssetStore> ASSET_STORE; + @Nonnull + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(ProjectileConfig::getAssetStore)); + @Nullable + protected AssetExtraInfo.Data data; + @Nullable + protected String id; + @Nonnull + protected PhysicsConfig physicsConfig = StandardPhysicsConfig.DEFAULT; + protected String model; + protected Model generatedModel; + protected double launchForce = 1.0; + @Nonnull + protected Vector3f spawnOffset = new Vector3f(0.0F, 0.0F, 0.0F); + @Nonnull + protected Direction spawnRotationOffset = new Direction(0.0F, 0.0F, 0.0F); + @Nonnull + protected Map interactions = Collections.emptyMap(); + protected String launchLocalSoundEventId; + protected String launchWorldSoundEventId; + protected String projectileSoundEventId; + protected int launchLocalSoundEventIndex = 0; + protected int launchWorldSoundEventIndex = 0; + protected int projectileSoundEventIndex = 0; + + public ProjectileConfig() { + } + + @Nonnull + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(ProjectileConfig.class); + } + + return ASSET_STORE; + } + + @Nonnull + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + @Nullable + public String getId() { + return this.id; + } + + protected void processConfig() { + if (this.launchWorldSoundEventId != null) { + this.launchWorldSoundEventIndex = this.launchLocalSoundEventIndex = SoundEvent.getAssetMap().getIndex(this.launchWorldSoundEventId); + } + + if (this.launchLocalSoundEventId != null) { + this.launchLocalSoundEventIndex = SoundEvent.getAssetMap().getIndex(this.launchLocalSoundEventId); + } + + if (this.projectileSoundEventId != null) { + this.projectileSoundEventIndex = SoundEvent.getAssetMap().getIndex(this.projectileSoundEventId); + } + } + + @Nonnull + public PhysicsConfig getPhysicsConfig() { + return this.physicsConfig; + } + + @Nonnull + public Model getModel() { + if (this.generatedModel != null) { + return this.generatedModel; + } else { + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(this.model); + this.generatedModel = Model.createUnitScaleModel(modelAsset); + return this.generatedModel; + } + } + + public double getLaunchForce() { + return this.launchForce; + } + + @Override + public double getMuzzleVelocity() { + return this.launchForce; + } + + @Override + public double getGravity() { + return this.physicsConfig.getGravity(); + } + + @Override + public double getVerticalCenterShot() { + return this.spawnOffset.y; + } + + @Override + public double getDepthShot() { + return this.spawnOffset.z; + } + + @Override + public boolean isPitchAdjustShot() { + return true; + } + + public Map getInteractions() { + return this.interactions; + } + + public int getLaunchWorldSoundEventIndex() { + return this.launchWorldSoundEventIndex; + } + + public int getProjectileSoundEventIndex() { + return this.projectileSoundEventIndex; + } + + @Nonnull + public Vector3f getSpawnOffset() { + return this.spawnOffset; + } + + @Nonnull + public Direction getSpawnRotationOffset() { + return this.spawnRotationOffset; + } + + @Nonnull + public Vector3d getCalculatedOffset(float pitch, float yaw) { + Vector3d offset = new Vector3d(this.spawnOffset.x, this.spawnOffset.y, this.spawnOffset.z); + offset.rotateX(pitch); + offset.rotateY(yaw); + return offset; + } + + @Nonnull + public com.hypixel.hytale.protocol.ProjectileConfig toPacket() { + com.hypixel.hytale.protocol.ProjectileConfig config = new com.hypixel.hytale.protocol.ProjectileConfig(); + config.physicsConfig = this.physicsConfig.toPacket(); + config.model = this.getModel().toPacket(); + config.launchForce = this.launchForce; + config.spawnOffset = this.spawnOffset; + config.rotationOffset = this.spawnRotationOffset; + config.launchLocalSoundEventIndex = this.launchLocalSoundEventIndex; + config.projectileSoundEventIndex = this.projectileSoundEventIndex; + config.interactions = new EnumMap<>(InteractionType.class); + + for (Entry e : this.interactions.entrySet()) { + config.interactions.put(e.getKey(), RootInteraction.getRootInteractionIdOrUnknown(e.getValue())); + } + + return config; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/projectile/config/ProjectileConfigPacketGenerator.java b/src/com/hypixel/hytale/server/core/modules/projectile/config/ProjectileConfigPacketGenerator.java new file mode 100644 index 0000000..0cd3760 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/projectile/config/ProjectileConfigPacketGenerator.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.server.core.modules.projectile.config; + +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.UpdateType; +import com.hypixel.hytale.protocol.packets.assets.UpdateProjectileConfigs; +import com.hypixel.hytale.server.core.asset.packet.DefaultAssetPacketGenerator; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ProjectileConfigPacketGenerator extends DefaultAssetPacketGenerator { + public ProjectileConfigPacketGenerator() { + } + + @Nonnull + @Override + public Packet generateInitPacket(@Nonnull DefaultAssetMap assetMap, Map assets) { + UpdateProjectileConfigs packet = new UpdateProjectileConfigs(); + packet.type = UpdateType.Init; + Map map = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : assetMap.getAssetMap().entrySet()) { + if (map.put(entry.getKey(), entry.getValue().toPacket()) != null) { + throw new IllegalStateException("Duplicate key"); + } + } + + packet.configs = map; + return packet; + } + + @Nonnull + @Override + public Packet generateUpdatePacket(@Nonnull Map loadedAssets) { + UpdateProjectileConfigs packet = new UpdateProjectileConfigs(); + packet.type = UpdateType.AddOrUpdate; + Map map = new Object2ObjectOpenHashMap<>(); + + for (Entry entry : loadedAssets.entrySet()) { + if (map.put(entry.getKey(), entry.getValue().toPacket()) != null) { + throw new IllegalStateException("Duplicate key"); + } + } + + packet.configs = map; + return packet; + } + + @Nullable + @Override + public Packet generateRemovePacket(@Nonnull Set removed) { + UpdateProjectileConfigs packet = new UpdateProjectileConfigs(); + packet.type = UpdateType.Remove; + packet.removedConfigs = removed.toArray(String[]::new); + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/projectile/config/StandardPhysicsConfig.java b/src/com/hypixel/hytale/server/core/modules/projectile/config/StandardPhysicsConfig.java new file mode 100644 index 0000000..2167fcd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/projectile/config/StandardPhysicsConfig.java @@ -0,0 +1,248 @@ +package com.hypixel.hytale.server.core.modules.projectile.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.PhysicsType; +import com.hypixel.hytale.protocol.RotationMode; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StandardPhysicsConfig implements PhysicsConfig { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(StandardPhysicsConfig.class, StandardPhysicsConfig::new) + .appendInherited(new KeyedCodec<>("Density", Codec.DOUBLE), (o, i) -> o.density = i, o -> o.density, (o, p) -> o.density = p.density) + .add() + .appendInherited(new KeyedCodec<>("Gravity", Codec.DOUBLE), (o, i) -> o.gravity = i, o -> o.gravity, (o, p) -> o.gravity = p.gravity) + .add() + .appendInherited( + new KeyedCodec<>("Bounciness", Codec.DOUBLE), (o, i) -> o.bounciness = i, o -> o.bounciness, (o, p) -> o.bounciness = p.bounciness + ) + .addValidator(Validators.range(0.0, 1.0)) + .add() + .appendInherited(new KeyedCodec<>("BounceLimit", Codec.DOUBLE), (o, i) -> o.bounceLimit = i, o -> o.bounceLimit, (o, p) -> o.bounceLimit = p.bounceLimit) + .add() + .appendInherited(new KeyedCodec<>("BounceCount", Codec.INTEGER), (o, i) -> o.bounceCount = i, o -> o.bounceCount, (o, p) -> o.bounceCount = p.bounceCount) + .add() + .appendInherited( + new KeyedCodec<>("SticksVertically", Codec.BOOLEAN), + (o, i) -> o.sticksVertically = i, + o -> o.sticksVertically, + (o, p) -> o.sticksVertically = p.sticksVertically + ) + .add() + .appendInherited(new KeyedCodec<>("ComputeYaw", Codec.BOOLEAN), (o, i) -> o.computeYaw = i, o -> o.computeYaw, (o, p) -> o.computeYaw = p.computeYaw) + .add() + .appendInherited( + new KeyedCodec<>("ComputePitch", Codec.BOOLEAN), (o, i) -> o.computePitch = i, o -> o.computePitch, (o, p) -> o.computePitch = p.computePitch + ) + .add() + .appendInherited( + new KeyedCodec<>("RotationMode", new EnumCodec<>(RotationMode.class)), + (o, i) -> o.rotationMode = i, + o -> o.rotationMode, + (o, p) -> o.rotationMode = p.rotationMode + ) + .add() + .appendInherited( + new KeyedCodec<>("MoveOutOfSolidSpeed", Codec.DOUBLE), + (o, i) -> o.moveOutOfSolidSpeed = i, + o -> o.moveOutOfSolidSpeed, + (o, p) -> o.moveOutOfSolidSpeed = p.moveOutOfSolidSpeed + ) + .add() + .appendInherited( + new KeyedCodec<>("TerminalVelocityAir", Codec.DOUBLE), + (o, i) -> o.terminalVelocityAir = i, + o -> o.terminalVelocityAir, + (o, p) -> o.terminalVelocityAir = p.terminalVelocityAir + ) + .addValidator(Validators.greaterThan(0.0)) + .add() + .appendInherited(new KeyedCodec<>("DensityAir", Codec.DOUBLE), (o, i) -> o.densityAir = i, o -> o.densityAir, (o, p) -> o.densityAir = p.densityAir) + .add() + .appendInherited( + new KeyedCodec<>("TerminalVelocityWater", Codec.DOUBLE), + (o, i) -> o.terminalVelocityWater = i, + o -> o.terminalVelocityWater, + (o, p) -> o.terminalVelocityWater = p.terminalVelocityWater + ) + .addValidator(Validators.greaterThan(0.0)) + .add() + .appendInherited( + new KeyedCodec<>("DensityWater", Codec.DOUBLE), (o, i) -> o.densityWater = i, o -> o.densityWater, (o, p) -> o.densityWater = p.densityWater + ) + .add() + .appendInherited( + new KeyedCodec<>("HitWaterImpulseLoss", Codec.DOUBLE), + (o, i) -> o.hitWaterImpulseLoss = i, + o -> o.hitWaterImpulseLoss, + (o, p) -> o.hitWaterImpulseLoss = p.hitWaterImpulseLoss + ) + .add() + .appendInherited( + new KeyedCodec<>("RotationForce", Codec.DOUBLE), (o, i) -> o.rotationForce = i, o -> o.rotationForce, (o, p) -> o.rotationForce = p.rotationForce + ) + .add() + .appendInherited( + new KeyedCodec<>("SpeedRotationFactor", Codec.FLOAT), + (o, i) -> o.speedRotationFactor = i, + o -> o.speedRotationFactor, + (o, p) -> o.speedRotationFactor = p.speedRotationFactor + ) + .add() + .appendInherited( + new KeyedCodec<>("SwimmingDampingFactor", Codec.DOUBLE), + (o, i) -> o.swimmingDampingFactor = i, + o -> o.swimmingDampingFactor, + (o, p) -> o.swimmingDampingFactor = p.swimmingDampingFactor + ) + .add() + .appendInherited( + new KeyedCodec<>("AllowRolling", Codec.BOOLEAN), (o, i) -> o.allowRolling = i, o -> o.allowRolling, (o, p) -> o.allowRolling = p.allowRolling + ) + .add() + .appendInherited( + new KeyedCodec<>("RollingFrictionFactor", Codec.DOUBLE), + (o, i) -> o.rollingFrictionFactor = i, + o -> o.rollingFrictionFactor, + (o, p) -> o.rollingFrictionFactor = p.rollingFrictionFactor + ) + .add() + .appendInherited( + new KeyedCodec<>("RollingSpeed", Codec.FLOAT), (o, i) -> o.rollingSpeed = i, o -> o.rollingSpeed, (o, p) -> o.rollingSpeed = p.rollingSpeed + ) + .add() + .build(); + public static final StandardPhysicsConfig DEFAULT = new StandardPhysicsConfig(); + protected static final double HIT_WATER_IMPULSE_LOSS = 0.2; + protected static final double ROTATION_FORCE = 3.0; + protected static final float SPEED_ROTATION_FACTOR = 2.0F; + protected static final double SWIMMING_DAMPING_FACTOR = 1.0; + protected double density = 700.0; + protected double gravity; + protected double bounciness; + protected int bounceCount = -1; + protected double bounceLimit = 0.4; + protected boolean sticksVertically; + protected boolean computeYaw = true; + protected boolean computePitch = true; + @Nonnull + protected RotationMode rotationMode = RotationMode.VelocityDamped; + protected boolean allowRolling = false; + protected double rollingFrictionFactor = 0.99; + protected float rollingSpeed = 0.1F; + protected double moveOutOfSolidSpeed; + protected double terminalVelocityAir = 1.0; + protected double densityAir = 1.2; + protected double terminalVelocityWater = 1.0; + protected double densityWater = 998.0; + protected double hitWaterImpulseLoss = 0.2; + protected double rotationForce = 3.0; + protected float speedRotationFactor = 2.0F; + protected double swimmingDampingFactor = 1.0; + + public StandardPhysicsConfig() { + } + + @Override + public double getGravity() { + return this.gravity; + } + + @Override + public void apply( + @Nonnull Holder holder, + @Nullable Ref creatorRef, + @Nonnull Vector3d velocity, + @Nonnull ComponentAccessor componentAccessor, + boolean predicted + ) { + UUID creatorUUID; + if (creatorRef != null) { + UUIDComponent uuidComponent = componentAccessor.getComponent(creatorRef, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + creatorUUID = uuidComponent.getUuid(); + } else { + creatorUUID = null; + } + + BoundingBox boundingBoxComponent = holder.getComponent(BoundingBox.getComponentType()); + + assert boundingBoxComponent != null; + + holder.addComponent(StandardPhysicsProvider.getComponentType(), new StandardPhysicsProvider(boundingBoxComponent, creatorUUID, this, velocity, predicted)); + } + + @Nonnull + public com.hypixel.hytale.protocol.PhysicsConfig toPacket() { + com.hypixel.hytale.protocol.PhysicsConfig packet = new com.hypixel.hytale.protocol.PhysicsConfig(); + packet.type = PhysicsType.Standard; + packet.density = this.density; + packet.gravity = this.gravity; + packet.bounciness = this.bounciness; + packet.bounceCount = this.bounceCount; + packet.bounceLimit = this.bounceLimit; + packet.sticksVertically = this.sticksVertically; + packet.computeYaw = this.computeYaw; + packet.computePitch = this.computePitch; + packet.rotationMode = this.rotationMode; + packet.moveOutOfSolidSpeed = this.moveOutOfSolidSpeed; + packet.terminalVelocityAir = this.terminalVelocityAir; + packet.densityAir = this.densityAir; + packet.terminalVelocityWater = this.terminalVelocityWater; + packet.densityWater = this.densityWater; + packet.hitWaterImpulseLoss = this.hitWaterImpulseLoss; + packet.rotationForce = this.rotationForce; + packet.speedRotationFactor = this.speedRotationFactor; + packet.swimmingDampingFactor = this.swimmingDampingFactor; + packet.allowRolling = this.allowRolling; + packet.rollingFrictionFactor = this.rollingFrictionFactor; + packet.rollingSpeed = this.rollingSpeed; + return packet; + } + + public double getBounciness() { + return this.bounciness; + } + + public int getBounceCount() { + return this.bounceCount; + } + + public double getBounceLimit() { + return this.bounceLimit; + } + + public boolean isSticksVertically() { + return this.sticksVertically; + } + + public boolean isAllowRolling() { + return this.allowRolling; + } + + public double getRollingFrictionFactor() { + return this.rollingFrictionFactor; + } + + public double getSwimmingDampingFactor() { + return this.swimmingDampingFactor; + } + + public double getHitWaterImpulseLoss() { + return this.hitWaterImpulseLoss; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/projectile/config/StandardPhysicsProvider.java b/src/com/hypixel/hytale/server/core/modules/projectile/config/StandardPhysicsProvider.java new file mode 100644 index 0000000..2168b9c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/projectile/config/StandardPhysicsProvider.java @@ -0,0 +1,618 @@ +package com.hypixel.hytale.server.core.modules.projectile.config; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.NearestBlockUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.math.vector.Vector4d; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.meta.DynamicMetaStore; +import com.hypixel.hytale.server.core.modules.collision.BlockCollisionProvider; +import com.hypixel.hytale.server.core.modules.collision.BlockContactData; +import com.hypixel.hytale.server.core.modules.collision.BlockData; +import com.hypixel.hytale.server.core.modules.collision.BlockTracker; +import com.hypixel.hytale.server.core.modules.collision.EntityRefCollisionProvider; +import com.hypixel.hytale.server.core.modules.collision.IBlockCollisionConsumer; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.physics.RestingSupport; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.modules.physics.util.ForceProvider; +import com.hypixel.hytale.server.core.modules.physics.util.ForceProviderEntity; +import com.hypixel.hytale.server.core.modules.physics.util.ForceProviderStandardState; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsBodyState; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsBodyStateUpdater; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsBodyStateUpdaterSymplecticEuler; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.modules.projectile.ProjectileModule; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StandardPhysicsProvider implements IBlockCollisionConsumer, Component { + public static final int WATER_DETECTION_EXTREMA_COUNT = 2; + public static final double MIN_BOUNCE_EPSILON = 0.4; + public static final double MIN_BOUNCE_EPSILON_SQUARED = 0.16000000000000003; + @Nonnull + protected static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + protected final BlockCollisionProvider blockCollisionProvider; + @Nonnull + protected final EntityRefCollisionProvider entityCollisionProvider; + @Nonnull + protected final BlockTracker triggerTracker; + @Nonnull + protected final RestingSupport restingSupport; + @Nullable + protected World world; + @Nonnull + protected final Vector3d velocity; + @Nonnull + protected final Vector3d position; + @Nonnull + protected final Vector3d movement; + protected final Vector3d nextMovement = new Vector3d(); + protected boolean bounced; + protected int bounces = 0; + protected boolean onGround; + protected boolean provideCharacterCollisions = true; + @Nullable + protected final UUID creatorUuid; + @Nonnull + protected final StandardPhysicsConfig physicsConfig; + protected final Vector3d tempVector = new Vector3d(); + @Nullable + protected BounceConsumer bounceConsumer; + @Nullable + protected ImpactConsumer impactConsumer; + protected boolean movedInsideSolid; + protected final Vector3d moveOutOfSolidVelocity = new Vector3d(); + protected final Vector3d contactPosition = new Vector3d(); + protected final Vector3d contactNormal = new Vector3d(); + protected double collisionStart; + protected final PhysicsBodyStateUpdater stateUpdater = new PhysicsBodyStateUpdaterSymplecticEuler(); + protected final PhysicsBodyState stateBefore = new PhysicsBodyState(); + protected final PhysicsBodyState stateAfter = new PhysicsBodyState(); + protected double displacedMass; + protected double subSurfaceVolume; + protected double enterFluid; + protected double leaveFluid; + protected boolean inFluid; + protected int velocityExtremaCount = Integer.MAX_VALUE; + @Nonnull + protected StandardPhysicsProvider.STATE state = StandardPhysicsProvider.STATE.ACTIVE; + protected ForceProviderEntity forceProviderEntity; + protected ForceProvider[] forceProviders; + protected final ForceProviderStandardState forceProviderStandardState = new ForceProviderStandardState(); + protected double dragMultiplier; + protected double dragOffset; + protected final BlockTracker fluidTracker = new BlockTracker(); + protected boolean isSliding; + @Deprecated(forRemoval = true) + protected BoundingBox boundingBox; + + @Nonnull + public static ComponentType getComponentType() { + return ProjectileModule.get().getStandardPhysicsProviderComponentType(); + } + + public StandardPhysicsProvider( + @Nonnull BoundingBox boundingBox, + @Nullable UUID creatorUuid, + @Nonnull StandardPhysicsConfig physicsConfig, + @Nonnull Vector3d initialForce, + boolean predicted + ) { + this.creatorUuid = creatorUuid; + this.physicsConfig = physicsConfig; + this.blockCollisionProvider = new BlockCollisionProvider(); + this.blockCollisionProvider.setRequestedCollisionMaterials(6); + this.blockCollisionProvider.setReportOverlaps(true); + this.entityCollisionProvider = new EntityRefCollisionProvider(); + this.triggerTracker = new BlockTracker(); + this.restingSupport = new RestingSupport(); + this.velocity = new Vector3d(); + this.position = new Vector3d(); + this.movement = new Vector3d(); + this.boundingBox = boundingBox; + this.forceProviderEntity = new ForceProviderEntity(boundingBox); + this.forceProviderEntity.setDensity(physicsConfig.density); + this.forceProviders = new ForceProvider[]{this.forceProviderEntity}; + this.forceProviderStandardState.nextTickVelocity.assign(initialForce); + this.recomputeDragFactors(boundingBox); + if (!predicted) { + this.impactConsumer = (ref, position, targetRef, collisionDetailName, commandBuffer) -> { + if (creatorUuid != null) { + Ref creatorRef = commandBuffer.getExternalData().getRefFromUUID(creatorUuid); + if (creatorRef != null && creatorRef.isValid() && EntityUtils.getEntity(creatorRef, commandBuffer) instanceof LivingEntity livingEntity) { + InteractionManager interactionManagerComponent = commandBuffer.getComponent( + creatorRef, InteractionModule.get().getInteractionManagerComponent() + ); + if (interactionManagerComponent == null) { + commandBuffer.removeEntity(ref, RemoveReason.REMOVE); + } else { + InteractionContext context = InteractionContext.forProxyEntity(interactionManagerComponent, livingEntity, ref); + DynamicMetaStore metaStore = context.getMetaStore(); + metaStore.putMetaObject(Interaction.TARGET_ENTITY, targetRef); + metaStore.putMetaObject(Interaction.HIT_LOCATION, new Vector4d(position.x, position.y, position.z, 1.0)); + metaStore.putMetaObject(Interaction.HIT_DETAIL, collisionDetailName); + InteractionType interactionType = targetRef != null ? InteractionType.ProjectileHit : InteractionType.ProjectileMiss; + String rootInteractionId = context.getRootInteractionId(interactionType); + if (rootInteractionId != null) { + RootInteraction rootInteraction = RootInteraction.getRootInteractionOrUnknown(rootInteractionId); + if (rootInteraction != null) { + InteractionChain chain = interactionManagerComponent.initChain(interactionType, context, rootInteraction, true); + interactionManagerComponent.queueExecuteChain(chain); + } + } + } + } else { + commandBuffer.removeEntity(ref, RemoveReason.REMOVE); + } + } + }; + this.bounceConsumer = (ref, position, commandBuffer) -> { + if (creatorUuid != null) { + Ref creatorRef = commandBuffer.getExternalData().getRefFromUUID(creatorUuid); + if (creatorRef != null && creatorRef.isValid() && EntityUtils.getEntity(creatorRef, commandBuffer) instanceof LivingEntity livingEntity) { + InteractionManager interactionManagerComponent = commandBuffer.getComponent( + creatorRef, InteractionModule.get().getInteractionManagerComponent() + ); + if (interactionManagerComponent == null) { + commandBuffer.removeEntity(ref, RemoveReason.REMOVE); + } else { + InteractionContext context = InteractionContext.forProxyEntity(interactionManagerComponent, livingEntity, ref); + context.getMetaStore().putMetaObject(Interaction.HIT_LOCATION, new Vector4d(position.x, position.y, position.z, 1.0)); + InteractionType interactionType = InteractionType.ProjectileBounce; + String rootInteractionId = context.getRootInteractionId(interactionType); + if (rootInteractionId != null) { + RootInteraction rootInteraction = RootInteraction.getRootInteractionOrUnknown(rootInteractionId); + if (rootInteraction != null) { + InteractionChain chain = interactionManagerComponent.initChain(interactionType, context, rootInteraction, true); + interactionManagerComponent.queueExecuteChain(chain); + } + } + } + } else { + commandBuffer.removeEntity(ref, RemoveReason.REMOVE); + } + } + }; + } + } + + @Nonnull + @Override + public IBlockCollisionConsumer.Result onCollision( + int blockX, + int blockY, + int blockZ, + @Nonnull Vector3d direction, + @Nonnull BlockContactData contactData, + @Nonnull BlockData blockData, + @Nonnull Box collider + ) { + BlockMaterial blockMaterial = blockData.getBlockType().getMaterial(); + if (this.physicsConfig.moveOutOfSolidSpeed > 0.0 && contactData.isOverlapping() && blockMaterial == BlockMaterial.Solid) { + Vector3i nearestBlock = NearestBlockUtil.findNearestBlock(this.position, (block, w) -> { + WorldChunk worldChunk = w.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(block.getX(), block.getZ())); + return worldChunk != null && worldChunk.getBlockType(block).getMaterial() != BlockMaterial.Solid; + }, this.world); + if (nearestBlock != null) { + this.tempVector.assign(nearestBlock.x, nearestBlock.y, nearestBlock.z); + this.tempVector.add(0.5, 0.5, 0.5); + this.tempVector.subtract(this.position); + this.tempVector.setLength(this.physicsConfig.moveOutOfSolidSpeed); + } else { + this.tempVector.assign(0.0, this.physicsConfig.moveOutOfSolidSpeed, 0.0); + } + + this.moveOutOfSolidVelocity.add(this.tempVector); + this.movedInsideSolid = true; + return IBlockCollisionConsumer.Result.CONTINUE; + } else if (blockData.getFluidId() != 0 && !this.fluidTracker.isTracked(blockX, blockY, blockZ)) { + double collisionStart = contactData.getCollisionStart(); + double collisionEnd = contactData.getCollisionEnd(); + if (collisionStart < this.enterFluid) { + this.enterFluid = collisionStart; + } + + if (collisionEnd > this.leaveFluid) { + this.leaveFluid = collisionEnd; + } + + if (collisionEnd <= collisionStart) { + return IBlockCollisionConsumer.Result.CONTINUE; + } else { + double density = 1000.0; + double volume = PhysicsMath.volumeOfIntersection(this.boundingBox.getBoundingBox(), this.contactPosition, collider, blockX, blockY, blockZ); + this.subSurfaceVolume += volume; + this.displacedMass += volume * density; + this.fluidTracker.trackNew(blockX, blockY, blockZ); + return IBlockCollisionConsumer.Result.CONTINUE; + } + } else if (contactData.isOverlapping()) { + return IBlockCollisionConsumer.Result.CONTINUE; + } else { + double surfaceAlignment = direction.dot(contactData.getCollisionNormal()); + if (blockMaterial == BlockMaterial.Solid && surfaceAlignment == 0.0) { + } + + if (surfaceAlignment >= 0.0) { + return IBlockCollisionConsumer.Result.CONTINUE; + } else { + this.contactPosition.assign(contactData.getCollisionPoint()); + this.contactNormal.assign(contactData.getCollisionNormal()); + if (this.physicsConfig.allowRolling) { + Vector3d remaining = this.stateBefore.position.clone().add(this.movement).subtract(this.contactPosition); + if (!remaining.equals(Vector3d.ZERO)) { + double t = remaining.dot(this.contactNormal); + this.nextMovement.assign(remaining); + this.nextMovement.addScaled(this.contactNormal, -t); + this.isSliding = true; + } + } + + this.collisionStart = contactData.getCollisionStart(); + this.bounced = true; + return IBlockCollisionConsumer.Result.STOP; + } + } + } + + @Nonnull + @Override + public IBlockCollisionConsumer.Result probeCollisionDamage( + int blockX, int blockY, int blockZ, Vector3d direction, BlockContactData collisionData, BlockData blockData + ) { + return IBlockCollisionConsumer.Result.CONTINUE; + } + + @Override + public void onCollisionDamage(int blockX, int blockY, int blockZ, Vector3d direction, BlockContactData collisionData, BlockData blockData) { + } + + @Nonnull + @Override + public IBlockCollisionConsumer.Result onCollisionSliceFinished() { + return IBlockCollisionConsumer.Result.CONTINUE; + } + + @Override + public void onCollisionFinished() { + } + + public void finishTick(@Nonnull TransformComponent position, @Nonnull Velocity velocity) { + position.setPosition(this.position); + velocity.set(this.velocity); + this.world = null; + this.entityCollisionProvider.clear(); + } + + public void rotateBody(double dt, @Nonnull Vector3f bodyRotation) { + if (this.physicsConfig.computeYaw || this.physicsConfig.computePitch) { + double vx = this.stateAfter.velocity.x; + double vz = this.stateAfter.velocity.z; + if (!(vx * vx + vz * vz <= 1.0000000000000002E-10)) { + switch (this.physicsConfig.rotationMode) { + case None: + default: + break; + case Velocity: + if (this.physicsConfig.computeYaw) { + bodyRotation.setYaw(PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(vx, vz))); + } + + if (this.physicsConfig.computePitch) { + bodyRotation.setPitch(PhysicsMath.pitchFromDirection(vx, this.stateAfter.velocity.y, vz)); + } + break; + case VelocityDamped: + if (this.physicsConfig.computeYaw) { + bodyRotation.setYaw(PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(vx, vz))); + } + + if (this.physicsConfig.computePitch) { + float pitch = bodyRotation.getPitch(); + float targetPitch = PhysicsMath.pitchFromDirection(vx, this.velocity.y, vz); + float delta = PhysicsMath.normalizeTurnAngle(targetPitch - pitch); + float maxDelta = (float)(this.velocity.squaredLength() * dt * this.physicsConfig.speedRotationFactor); + if (delta > maxDelta) { + targetPitch = pitch + maxDelta; + delta = maxDelta; + } else if (delta < -maxDelta) { + targetPitch = pitch - maxDelta; + delta = maxDelta; + } + + bodyRotation.setPitch(targetPitch); + this.forceProviderStandardState.externalForce.addScaled(this.stateAfter.velocity, delta * -this.physicsConfig.rotationForce); + } + break; + case VelocityRoll: + bodyRotation.setYaw(PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(vx, vz))); + bodyRotation.setPitch(bodyRotation.getPitch() - (float)this.stateBefore.velocity.length() * this.physicsConfig.rollingSpeed); + } + } + } + } + + public boolean isOnGround() { + return this.onGround; + } + + public boolean isSwimming() { + return this.velocityExtremaCount <= 0; + } + + public double getDragCoefficient(double density) { + return this.dragMultiplier * density + this.dragOffset; + } + + protected void recomputeDragFactors(@Nonnull BoundingBox boundingBoxComponent) { + Box boundingBox = boundingBoxComponent.getBoundingBox(); + double area = boundingBox.width() * boundingBox.depth(); + double mass = this.forceProviderEntity.getMass(boundingBox.getVolume()); + double drag1 = PhysicsMath.computeDragCoefficient(this.physicsConfig.terminalVelocityAir, area, mass, this.physicsConfig.gravity); + double drag2 = PhysicsMath.computeDragCoefficient(this.physicsConfig.terminalVelocityWater, area, mass, this.physicsConfig.gravity); + this.dragMultiplier = (drag2 - drag1) / (this.physicsConfig.densityWater - this.physicsConfig.densityAir); + this.dragOffset = drag1 - this.dragMultiplier * this.physicsConfig.densityAir; + } + + @Nonnull + public StandardPhysicsProvider.STATE getState() { + return this.state; + } + + public void setState(@Nonnull StandardPhysicsProvider.STATE state) { + this.state = state; + } + + @Nonnull + public StandardPhysicsConfig getPhysicsConfig() { + return this.physicsConfig; + } + + @Nonnull + public ForceProviderStandardState getForceProviderStandardState() { + return this.forceProviderStandardState; + } + + @Nonnull + public RestingSupport getRestingSupport() { + return this.restingSupport; + } + + public void setWorld(@Nullable World world) { + this.world = world; + } + + @Nonnull + public Vector3d getPosition() { + return this.position; + } + + @Nonnull + public Vector3d getVelocity() { + return this.velocity; + } + + @Nonnull + public Vector3d getMovement() { + return this.movement; + } + + @Nonnull + public Vector3d getNextMovement() { + return this.nextMovement; + } + + @Nonnull + public ForceProviderEntity getForceProviderEntity() { + return this.forceProviderEntity; + } + + @Nonnull + public ForceProvider[] getForceProviders() { + return this.forceProviders; + } + + @Nonnull + public PhysicsBodyStateUpdater getStateUpdater() { + return this.stateUpdater; + } + + @Nonnull + public PhysicsBodyState getStateBefore() { + return this.stateBefore; + } + + @Nonnull + public PhysicsBodyState getStateAfter() { + return this.stateAfter; + } + + public boolean isProvidesCharacterCollisions() { + return this.provideCharacterCollisions; + } + + @Nullable + public UUID getCreatorUuid() { + return this.creatorUuid; + } + + @Nonnull + public EntityRefCollisionProvider getEntityCollisionProvider() { + return this.entityCollisionProvider; + } + + public boolean isBounced() { + return this.bounced; + } + + public void setBounced(boolean bounced) { + this.bounced = bounced; + } + + public int getBounces() { + return this.bounces; + } + + public void incrementBounces() { + this.bounces++; + } + + @Nonnull + public Vector3d getMoveOutOfSolidVelocity() { + return this.moveOutOfSolidVelocity; + } + + public boolean isMovedInsideSolid() { + return this.movedInsideSolid; + } + + public void setMovedInsideSolid(boolean movedInsideSolid) { + this.movedInsideSolid = movedInsideSolid; + } + + public double getDisplacedMass() { + return this.displacedMass; + } + + public void setDisplacedMass(double displacedMass) { + this.displacedMass = displacedMass; + } + + public double getSubSurfaceVolume() { + return this.subSurfaceVolume; + } + + public void setSubSurfaceVolume(double subSurfaceVolume) { + this.subSurfaceVolume = subSurfaceVolume; + } + + public double getEnterFluid() { + return this.enterFluid; + } + + public void setEnterFluid(double enterFluid) { + this.enterFluid = enterFluid; + } + + public double getLeaveFluid() { + return this.leaveFluid; + } + + public void setLeaveFluid(double leaveFluid) { + this.leaveFluid = leaveFluid; + } + + public double getCollisionStart() { + return this.collisionStart; + } + + public void setCollisionStart(double collisionStart) { + this.collisionStart = collisionStart; + } + + @Nonnull + public Vector3d getContactPosition() { + return this.contactPosition; + } + + @Nonnull + public Vector3d getContactNormal() { + return this.contactNormal; + } + + public boolean isSliding() { + return this.isSliding; + } + + public void setSliding(boolean sliding) { + this.isSliding = sliding; + } + + @Nonnull + public BlockCollisionProvider getBlockCollisionProvider() { + return this.blockCollisionProvider; + } + + @Nonnull + public BlockTracker getTriggerTracker() { + return this.triggerTracker; + } + + @Nonnull + public BlockTracker getFluidTracker() { + return this.fluidTracker; + } + + public boolean isInFluid() { + return this.inFluid; + } + + public void setInFluid(boolean inFluid) { + this.inFluid = inFluid; + } + + public int getVelocityExtremaCount() { + return this.velocityExtremaCount; + } + + public void setVelocityExtremaCount(int velocityExtremaCount) { + this.velocityExtremaCount = velocityExtremaCount; + } + + public void decrementVelocityExtremaCount() { + this.velocityExtremaCount--; + } + + public void setOnGround(boolean onGround) { + this.onGround = onGround; + } + + @Nullable + public ImpactConsumer getImpactConsumer() { + return this.impactConsumer; + } + + @Nullable + public BounceConsumer getBounceConsumer() { + return this.bounceConsumer; + } + + @Nonnull + @Override + public Component clone() { + return this; + } + + public static enum STATE { + ACTIVE, + RESTING, + INACTIVE; + + private STATE() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/projectile/interaction/ProjectileInteraction.java b/src/com/hypixel/hytale/server/core/modules/projectile/interaction/ProjectileInteraction.java new file mode 100644 index 0000000..7bc2c2a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/projectile/interaction/ProjectileInteraction.java @@ -0,0 +1,129 @@ +package com.hypixel.hytale.server.core.modules.projectile.interaction; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.Interaction; +import com.hypixel.hytale.protocol.InteractionSyncData; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.protocol.WaitForDataFrom; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; +import com.hypixel.hytale.server.core.modules.projectile.ProjectileModule; +import com.hypixel.hytale.server.core.modules.projectile.config.BallisticData; +import com.hypixel.hytale.server.core.modules.projectile.config.BallisticDataProvider; +import com.hypixel.hytale.server.core.modules.projectile.config.ProjectileConfig; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.PositionUtil; +import com.hypixel.hytale.server.core.util.TargetUtil; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ProjectileInteraction extends SimpleInstantInteraction implements BallisticDataProvider { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + ProjectileInteraction.class, ProjectileInteraction::new, SimpleInstantInteraction.CODEC + ) + .documentation("Fires a projectile.") + .appendInherited(new KeyedCodec<>("Config", Codec.STRING), (o, i) -> o.config = i, o -> o.config, (o, p) -> o.config = p.config) + .addValidator(ProjectileConfig.VALIDATOR_CACHE.getValidator().late()) + .documentation("The ID of the projectile config asset to use for the projectile.") + .add() + .build(); + protected String config; + + public ProjectileInteraction() { + } + + @Nullable + public ProjectileConfig getConfig() { + return ProjectileConfig.getAssetMap().getAsset(this.config); + } + + @Nullable + @Override + public BallisticData getBallisticData() { + return this.getConfig(); + } + + @Nonnull + @Override + public WaitForDataFrom getWaitForDataFrom() { + return WaitForDataFrom.Client; + } + + @Override + public boolean needsRemoteSync() { + return true; + } + + @Override + protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + ProjectileConfig config = this.getConfig(); + if (config != null) { + InteractionSyncData clientState = context.getClientState(); + Ref ref = context.getEntity(); + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + boolean hasClientState = clientState != null && clientState.attackerPos != null && clientState.attackerRot != null; + Vector3d position; + Vector3d direction; + UUID generatedUUID; + if (hasClientState) { + position = PositionUtil.toVector3d(clientState.attackerPos); + Vector3f lookVec = PositionUtil.toRotation(clientState.attackerRot); + direction = new Vector3d(lookVec.getYaw(), lookVec.getPitch()); + generatedUUID = clientState.generatedUUID; + } else { + Transform lookVec = TargetUtil.getLook(ref, commandBuffer); + position = lookVec.getPosition(); + direction = lookVec.getDirection(); + generatedUUID = null; + } + + ProjectileModule.get().spawnProjectile(generatedUUID, ref, commandBuffer, config, position, direction); + } + } + + @Override + protected void simulateFirstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { + CommandBuffer commandBuffer = context.getCommandBuffer(); + + assert commandBuffer != null; + + Ref ref = context.getEntity(); + Transform lookVec = TargetUtil.getLook(ref, commandBuffer); + InteractionSyncData state = context.getState(); + state.attackerPos = PositionUtil.toPositionPacket(lookVec.getPosition()); + Vector3f rotation = lookVec.getRotation(); + state.attackerRot = new Direction(rotation.getYaw(), rotation.getPitch(), rotation.getRoll()); + } + + @Nonnull + @Override + protected Interaction generatePacket() { + return new com.hypixel.hytale.protocol.ProjectileInteraction(); + } + + @Override + protected void configurePacket(Interaction packet) { + super.configurePacket(packet); + com.hypixel.hytale.protocol.ProjectileInteraction p = (com.hypixel.hytale.protocol.ProjectileInteraction)packet; + ProjectileConfig config = this.getConfig(); + if (config == null) { + throw new IllegalStateException("ProjectileInteraction '" + this.getId() + "' has no valid ProjectileConfig: " + this.config); + } else { + p.configId = this.config; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/projectile/system/PredictedProjectileSystems.java b/src/com/hypixel/hytale/server/core/modules/projectile/system/PredictedProjectileSystems.java new file mode 100644 index 0000000..43b6b52 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/projectile/system/PredictedProjectileSystems.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.server.core.modules.projectile.system; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.protocol.ComponentUpdate; +import com.hypixel.hytale.protocol.ComponentUpdateType; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.modules.projectile.component.PredictedProjectile; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PredictedProjectileSystems { + public PredictedProjectileSystems() { + } + + public static class EntityTrackerUpdate extends EntityTickingSystem { + @Nonnull + private final ComponentType visibleComponentType = EntityTrackerSystems.Visible.getComponentType(); + @Nonnull + private final ComponentType predictedProjectileComponentType = PredictedProjectile.getComponentType(); + @Nonnull + private final Query query = Query.and(this.visibleComponentType, this.predictedProjectileComponentType); + + public EntityTrackerUpdate() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityTrackerSystems.QUEUE_UPDATE_GROUP; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + EntityTrackerSystems.Visible visibleComponent = archetypeChunk.getComponent(index, this.visibleComponentType); + + assert visibleComponent != null; + + PredictedProjectile predictedProjectileComponent = archetypeChunk.getComponent(index, this.predictedProjectileComponentType); + + assert predictedProjectileComponent != null; + + if (!visibleComponent.newlyVisibleTo.isEmpty()) { + queueUpdatesFor(archetypeChunk.getReferenceTo(index), predictedProjectileComponent, visibleComponent.newlyVisibleTo); + } + } + + private static void queueUpdatesFor( + @Nonnull Ref ref, + @Nonnull PredictedProjectile predictedProjectile, + @Nonnull Map, EntityTrackerSystems.EntityViewer> visibleTo + ) { + ComponentUpdate update = new ComponentUpdate(); + update.type = ComponentUpdateType.Prediction; + update.predictionId = predictedProjectile.getUuid(); + + for (Entry, EntityTrackerSystems.EntityViewer> entry : visibleTo.entrySet()) { + entry.getValue().queueUpdate(ref, update); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/projectile/system/StandardPhysicsTickSystem.java b/src/com/hypixel/hytale/server/core/modules/projectile/system/StandardPhysicsTickSystem.java new file mode 100644 index 0000000..97ac54a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/projectile/system/StandardPhysicsTickSystem.java @@ -0,0 +1,302 @@ +package com.hypixel.hytale.server.core.modules.projectile.system; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.OrderPriority; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.collision.BlockCollisionProvider; +import com.hypixel.hytale.server.core.modules.collision.BlockTracker; +import com.hypixel.hytale.server.core.modules.collision.EntityContactData; +import com.hypixel.hytale.server.core.modules.collision.EntityRefCollisionProvider; +import com.hypixel.hytale.server.core.modules.collision.TangiableEntitySpatialSystem; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.system.TransformSystems; +import com.hypixel.hytale.server.core.modules.physics.RestingSupport; +import com.hypixel.hytale.server.core.modules.physics.SimplePhysicsProvider; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.modules.physics.util.ForceProvider; +import com.hypixel.hytale.server.core.modules.physics.util.ForceProviderEntity; +import com.hypixel.hytale.server.core.modules.physics.util.ForceProviderStandardState; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsBodyState; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsBodyStateUpdater; +import com.hypixel.hytale.server.core.modules.projectile.config.StandardPhysicsConfig; +import com.hypixel.hytale.server.core.modules.projectile.config.StandardPhysicsProvider; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import javax.annotation.Nonnull; + +public class StandardPhysicsTickSystem extends EntityTickingSystem { + @Nonnull + private final Query query = Query.and( + StandardPhysicsProvider.getComponentType(), + TransformComponent.getComponentType(), + HeadRotation.getComponentType(), + Velocity.getComponentType(), + BoundingBox.getComponentType() + ); + @Nonnull + private final Set> dependencies = Set.of( + new SystemDependency<>(Order.AFTER, TangiableEntitySpatialSystem.class, OrderPriority.CLOSEST), + new SystemDependency<>(Order.BEFORE, TransformSystems.EntityTrackerUpdate.class) + ); + + public StandardPhysicsTickSystem() { + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + TimeResource timeResource = store.getResource(TimeResource.getResourceType()); + float timeDilationModifier = timeResource.getTimeDilationModifier(); + World world = store.getExternalData().getWorld(); + dt = 1.0F / world.getTps(); + dt *= timeDilationModifier; + StandardPhysicsProvider physicsComponent = archetypeChunk.getComponent(index, StandardPhysicsProvider.getComponentType()); + + assert physicsComponent != null; + + Velocity velocityComponent = archetypeChunk.getComponent(index, Velocity.getComponentType()); + + assert velocityComponent != null; + + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + BoundingBox boundingBoxComponent = archetypeChunk.getComponent(index, BoundingBox.getComponentType()); + + assert boundingBoxComponent != null; + + StandardPhysicsConfig physicsConfig = physicsComponent.getPhysicsConfig(); + Ref selfRef = archetypeChunk.getReferenceTo(index); + if (physicsComponent.getState() == StandardPhysicsProvider.STATE.INACTIVE) { + velocityComponent.setZero(); + } else { + ForceProviderStandardState forceState = physicsComponent.getForceProviderStandardState(); + RestingSupport restingSupport = physicsComponent.getRestingSupport(); + if (physicsComponent.getState() == StandardPhysicsProvider.STATE.RESTING) { + if (forceState.externalForce.squaredLength() == 0.0 && !restingSupport.hasChanged(world)) { + return; + } + + physicsComponent.setState(StandardPhysicsProvider.STATE.ACTIVE); + } + + Vector3d position = physicsComponent.getPosition(); + Vector3d velocity = physicsComponent.getVelocity(); + Vector3d movement = physicsComponent.getMovement(); + Box boundingBox = boundingBoxComponent.getBoundingBox(); + PhysicsBodyState stateBefore = physicsComponent.getStateBefore(); + PhysicsBodyState stateAfter = physicsComponent.getStateAfter(); + ForceProviderEntity forceProviderEntity = physicsComponent.getForceProviderEntity(); + PhysicsBodyStateUpdater stateUpdater = physicsComponent.getStateUpdater(); + ForceProvider[] forceProviders = physicsComponent.getForceProviders(); + Vector3d moveOutOfSolidVelocity = physicsComponent.getMoveOutOfSolidVelocity(); + double gravity = physicsConfig.getGravity(); + int bounceCount = physicsConfig.getBounceCount(); + boolean allowRolling = physicsConfig.isAllowRolling(); + physicsComponent.setWorld(world); + position.assign(transformComponent.getPosition()); + velocityComponent.assignVelocityTo(velocity); + double mass = forceProviderEntity.getMass(boundingBox.getVolume()); + forceState.convertToForces(dt, mass); + forceState.updateVelocity(velocity); + if (!(velocity.squaredLength() * dt * dt >= 1.0000000000000002E-10) && !(forceState.externalForce.squaredLength() >= 0.0)) { + velocity.assign(Vector3d.ZERO); + } else { + physicsComponent.setState(StandardPhysicsProvider.STATE.ACTIVE); + } + + if (physicsComponent.getState() == StandardPhysicsProvider.STATE.RESTING && restingSupport.hasChanged(world)) { + physicsComponent.setState(StandardPhysicsProvider.STATE.ACTIVE); + } + + stateBefore.position.assign(position); + stateBefore.velocity.assign(velocity); + forceProviderEntity.setForceProviderStandardState(forceState); + stateUpdater.update(stateBefore, stateAfter, mass, dt, physicsComponent.isOnGround(), forceProviders); + velocity.assign(stateAfter.velocity); + movement.assign(velocity).scale(dt); + forceState.clear(); + if (velocity.squaredLength() * dt * dt >= 1.0000000000000002E-10) { + physicsComponent.setState(StandardPhysicsProvider.STATE.ACTIVE); + } else { + velocity.assign(Vector3d.ZERO); + } + + EntityRefCollisionProvider entityCollisionProvider = physicsComponent.getEntityCollisionProvider(); + BlockCollisionProvider blockCollisionProvider = physicsComponent.getBlockCollisionProvider(); + BlockTracker triggerTracker = physicsComponent.getTriggerTracker(); + Vector3d contactPosition = physicsComponent.getContactPosition(); + Vector3d contactNormal = physicsComponent.getContactNormal(); + Vector3d nextMovement = physicsComponent.getNextMovement(); + double maxRelativeDistance = 1.0; + if (physicsComponent.isProvidesCharacterCollisions()) { + Ref creatorReference = null; + if (physicsComponent.getCreatorUuid() != null) { + creatorReference = store.getExternalData().getRefFromUUID(physicsComponent.getCreatorUuid()); + } + + maxRelativeDistance = entityCollisionProvider.computeNearest(commandBuffer, boundingBox, position, movement, selfRef, creatorReference); + if (maxRelativeDistance < 0.0 || maxRelativeDistance > 1.0) { + maxRelativeDistance = 1.0; + } + } + + physicsComponent.setBounced(false); + physicsComponent.setOnGround(false); + moveOutOfSolidVelocity.assign(Vector3d.ZERO); + physicsComponent.setMovedInsideSolid(false); + physicsComponent.setDisplacedMass(0.0); + physicsComponent.setSubSurfaceVolume(0.0); + physicsComponent.setEnterFluid(Double.MAX_VALUE); + physicsComponent.setLeaveFluid(-Double.MAX_VALUE); + physicsComponent.setCollisionStart(maxRelativeDistance); + contactPosition.assign(position).addScaled(movement, physicsComponent.getCollisionStart()); + contactNormal.assign(Vector3d.ZERO); + physicsComponent.setSliding(true); + Vector3d tmpPosition = position.clone(); + nextMovement.assign(Vector3d.ZERO); + + while (physicsComponent.isSliding() && !movement.equals(Vector3d.ZERO)) { + contactPosition.assign(tmpPosition).addScaled(movement, physicsComponent.getCollisionStart()); + physicsComponent.setSliding(false); + blockCollisionProvider.cast(world, boundingBox, tmpPosition, movement, physicsComponent, triggerTracker, maxRelativeDistance); + movement.assign(nextMovement); + tmpPosition.assign(contactPosition); + } + + movement.assign(tmpPosition).add(nextMovement).subtract(position); + physicsComponent.getFluidTracker().reset(); + double density = physicsComponent.getDisplacedMass() > 0.0 ? physicsComponent.getDisplacedMass() / physicsComponent.getSubSurfaceVolume() : 1.2; + if (physicsComponent.isMovedInsideSolid()) { + position.addScaled(moveOutOfSolidVelocity, dt); + velocity.assign(moveOutOfSolidVelocity); + forceState.dragCoefficient = physicsComponent.getDragCoefficient(density); + forceState.displacedMass = physicsComponent.getDisplacedMass(); + forceState.gravity = gravity; + physicsComponent.finishTick(transformComponent, velocityComponent); + } else { + double velocityClip = physicsComponent.isBounced() ? physicsComponent.getCollisionStart() : 1.0; + boolean enteringWater = false; + if (!physicsComponent.isInFluid() && physicsComponent.getEnterFluid() < physicsComponent.getCollisionStart()) { + physicsComponent.setInFluid(true); + velocityClip = physicsComponent.getEnterFluid(); + physicsComponent.setVelocityExtremaCount(2); + enteringWater = true; + } else if (physicsComponent.isInFluid() && physicsComponent.getLeaveFluid() < physicsComponent.getCollisionStart()) { + physicsComponent.setInFluid(false); + velocityClip = physicsComponent.getLeaveFluid(); + physicsComponent.setVelocityExtremaCount(2); + } + + if (velocityClip > 0.0 && velocityClip < 1.0) { + stateUpdater.update(stateBefore, stateAfter, mass, dt * velocityClip, physicsComponent.isOnGround(), forceProviders); + velocity.assign(stateAfter.velocity); + } + + if (physicsComponent.isInFluid() + && physicsComponent.getSubSurfaceVolume() < boundingBox.getVolume() + && physicsComponent.getVelocityExtremaCount() > 0) { + double speedBefore = stateBefore.velocity.y; + double speedAfter = stateAfter.velocity.y; + if (speedBefore * speedAfter <= 0.0) { + physicsComponent.decrementVelocityExtremaCount(); + } + } + + if (physicsComponent.isSwimming()) { + forceState.externalForce.y = forceState.externalForce.y - stateAfter.velocity.y * (physicsConfig.getSwimmingDampingFactor() / mass); + } + + if (enteringWater) { + forceState.externalImpulse.addScaled(stateAfter.velocity, -physicsConfig.getHitWaterImpulseLoss() * mass); + } + + forceState.displacedMass = physicsComponent.getDisplacedMass(); + forceState.dragCoefficient = physicsComponent.getDragCoefficient(density); + forceState.gravity = gravity; + if (entityCollisionProvider.getCount() > 0) { + EntityContactData contact = entityCollisionProvider.getContact(0); + Ref contactRef = contact.getEntityReference(); + position.assign(contact.getCollisionPoint()); + physicsComponent.setState(StandardPhysicsProvider.STATE.INACTIVE); + if (physicsComponent.getImpactConsumer() != null) { + physicsComponent.getImpactConsumer().onImpact(selfRef, position, contactRef, contact.getCollisionDetailName(), commandBuffer); + } + + physicsComponent.rotateBody(dt, transformComponent.getRotation()); + physicsComponent.finishTick(transformComponent, velocityComponent); + } else if (!physicsComponent.isBounced()) { + position.add(movement); + physicsComponent.rotateBody(dt, transformComponent.getRotation()); + physicsComponent.finishTick(transformComponent, velocityComponent); + } else { + position.assign(contactPosition); + physicsComponent.incrementBounces(); + SimplePhysicsProvider.computeReflectedVector(velocity, contactNormal, velocity); + if (bounceCount == -1 || physicsComponent.getBounces() <= bounceCount) { + velocity.scale(physicsConfig.getBounciness()); + } + + if ((bounceCount == -1 || physicsComponent.getBounces() <= bounceCount) + && !(velocity.squaredLength() * dt * dt < physicsConfig.getBounceLimit() * physicsConfig.getBounceLimit())) { + if (physicsComponent.getBounceConsumer() != null) { + physicsComponent.getBounceConsumer().onBounce(selfRef, position, commandBuffer); + } + } else { + boolean hitGround = contactNormal.equals(Vector3d.UP); + if (!allowRolling && (physicsConfig.isSticksVertically() || hitGround)) { + physicsComponent.setState(StandardPhysicsProvider.STATE.RESTING); + restingSupport.rest(world, boundingBox, position); + physicsComponent.setOnGround(hitGround); + if (physicsComponent.getImpactConsumer() != null) { + physicsComponent.getImpactConsumer().onImpact(selfRef, position, null, null, commandBuffer); + } + } + + if (allowRolling) { + velocity.y = 0.0; + velocity.scale(physicsConfig.getRollingFrictionFactor()); + physicsComponent.setOnGround(hitGround); + } else { + velocity.assign(Vector3d.ZERO); + } + } + + physicsComponent.rotateBody(dt, transformComponent.getRotation()); + physicsComponent.finishTick(transformComponent, velocityComponent); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/serverplayerlist/ServerPlayerListModule.java b/src/com/hypixel/hytale/server/core/modules/serverplayerlist/ServerPlayerListModule.java new file mode 100644 index 0000000..a46ea23 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/serverplayerlist/ServerPlayerListModule.java @@ -0,0 +1,131 @@ +package com.hypixel.hytale.server.core.modules.serverplayerlist; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.event.EventRegistry; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.metrics.metric.HistoricMetric; +import com.hypixel.hytale.protocol.packets.connection.PongType; +import com.hypixel.hytale.protocol.packets.interface_.AddToServerPlayerList; +import com.hypixel.hytale.protocol.packets.interface_.RemoveFromServerPlayerList; +import com.hypixel.hytale.protocol.packets.interface_.ServerPlayerListPlayer; +import com.hypixel.hytale.protocol.packets.interface_.ServerPlayerListUpdate; +import com.hypixel.hytale.protocol.packets.interface_.UpdateServerPlayerList; +import com.hypixel.hytale.protocol.packets.interface_.UpdateServerPlayerListPing; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.event.events.player.AddPlayerToWorldEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerDisconnectEvent; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + +public class ServerPlayerListModule extends JavaPlugin { + @Nonnull + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(ServerPlayerListModule.class).depends(Universe.class).build(); + private static final int PING_UPDATE_INTERVAL_SECONDS = 10; + private static ServerPlayerListModule instance; + + @Nonnull + public static ServerPlayerListModule get() { + return instance; + } + + public ServerPlayerListModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + EventRegistry eventRegistry = this.getEventRegistry(); + eventRegistry.register(PlayerConnectEvent.class, this::onPlayerConnect); + eventRegistry.register(PlayerDisconnectEvent.class, this::onPlayerDisconnect); + eventRegistry.registerGlobal(AddPlayerToWorldEvent.class, this::onPlayerAddedToWorld); + HytaleServer.SCHEDULED_EXECUTOR.scheduleWithFixedDelay(this::broadcastPingUpdates, 10L, 10L, TimeUnit.SECONDS); + } + + private void onPlayerConnect(@Nonnull PlayerConnectEvent event) { + PlayerRef joiningPlayerRef = event.getPlayerRef(); + UUID joiningPlayerUuid = joiningPlayerRef.getUuid(); + List allPlayers = Universe.get().getPlayers(); + ServerPlayerListPlayer[] serverListPlayers = new ServerPlayerListPlayer[allPlayers.size()]; + int index = 0; + + for (PlayerRef playerRef : allPlayers) { + serverListPlayers[index++] = createServerPlayerListPlayer(playerRef); + } + + AddToServerPlayerList fullListPacket = new AddToServerPlayerList(serverListPlayers); + joiningPlayerRef.getPacketHandler().write(fullListPacket); + AddToServerPlayerList newPlayerPacket = new AddToServerPlayerList(new ServerPlayerListPlayer[]{createServerPlayerListPlayer(joiningPlayerRef)}); + + for (PlayerRef playerRef : allPlayers) { + if (!playerRef.getUuid().equals(joiningPlayerUuid)) { + playerRef.getPacketHandler().write(newPlayerPacket); + } + } + } + + private void onPlayerDisconnect(@Nonnull PlayerDisconnectEvent event) { + PlayerRef leavingPlayerRef = event.getPlayerRef(); + UUID leavingPlayerUuid = leavingPlayerRef.getUuid(); + RemoveFromServerPlayerList removePacket = new RemoveFromServerPlayerList(new UUID[]{leavingPlayerUuid}); + + for (PlayerRef playerRef : Universe.get().getPlayers()) { + if (!playerRef.getUuid().equals(leavingPlayerUuid)) { + playerRef.getPacketHandler().write(removePacket); + } + } + } + + private void onPlayerAddedToWorld(@Nonnull AddPlayerToWorldEvent event) { + Holder holder = event.getHolder(); + PlayerRef playerRefComponent = holder.getComponent(PlayerRef.getComponentType()); + if (playerRefComponent != null) { + UUID playerUuid = playerRefComponent.getUuid(); + UUID worldUuid = event.getWorld().getWorldConfig().getUuid(); + UpdateServerPlayerList updatePacket = new UpdateServerPlayerList(new ServerPlayerListUpdate[]{new ServerPlayerListUpdate(playerUuid, worldUuid)}); + + for (PlayerRef otherPlayerRef : Universe.get().getPlayers()) { + otherPlayerRef.getPacketHandler().write(updatePacket); + } + } + } + + private void broadcastPingUpdates() { + List allPlayers = Universe.get().getPlayers(); + if (!allPlayers.isEmpty()) { + Object2IntOpenHashMap pingMap = new Object2IntOpenHashMap<>(allPlayers.size()); + + for (PlayerRef playerRef : allPlayers) { + pingMap.put(playerRef.getUuid(), getPingValue(playerRef.getPacketHandler())); + } + + UpdateServerPlayerListPing packet = new UpdateServerPlayerListPing(pingMap); + + for (PlayerRef playerRef : allPlayers) { + playerRef.getPacketHandler().writeNoCache(packet); + } + } + } + + private static int getPingValue(@Nonnull PacketHandler handler) { + HistoricMetric historicMetric = handler.getPingInfo(PongType.Direct).getPingMetricSet(); + double average = historicMetric.getAverage(0); + return (int)PacketHandler.PingInfo.TIME_UNIT.toMillis(MathUtil.fastCeil(average)); + } + + @Nonnull + private static ServerPlayerListPlayer createServerPlayerListPlayer(@Nonnull PlayerRef playerRef) { + return new ServerPlayerListPlayer(playerRef.getUuid(), playerRef.getUsername(), playerRef.getWorldUuid(), getPingValue(playerRef.getPacketHandler())); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/singleplayer/SingleplayerModule.java b/src/com/hypixel/hytale/server/core/modules/singleplayer/SingleplayerModule.java new file mode 100644 index 0000000..a7a021b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/singleplayer/SingleplayerModule.java @@ -0,0 +1,162 @@ +package com.hypixel.hytale.server.core.modules.singleplayer; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.protocol.packets.serveraccess.Access; +import com.hypixel.hytale.protocol.packets.serveraccess.RequestServerAccess; +import com.hypixel.hytale.server.core.Constants; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.ShutdownReason; +import com.hypixel.hytale.server.core.auth.PlayerAuthentication; +import com.hypixel.hytale.server.core.io.ServerManager; +import com.hypixel.hytale.server.core.modules.accesscontrol.AccessControlModule; +import com.hypixel.hytale.server.core.modules.accesscontrol.provider.ClientDelegatingProvider; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.singleplayer.commands.PlayCommand; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.util.ProcessUtil; +import io.netty.channel.Channel; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class SingleplayerModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(SingleplayerModule.class) + .depends(AccessControlModule.class) + .optDepends(InteractionModule.class) + .build(); + private static SingleplayerModule instance; + private Access access; + private Access requestedAccess; + private List publicAddresses = new CopyOnWriteArrayList<>(); + + public static SingleplayerModule get() { + return instance; + } + + public SingleplayerModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + if (Constants.SINGLEPLAYER) { + AccessControlModule.get().registerAccessProvider(new ClientDelegatingProvider()); + } + + this.getCommandRegistry().registerCommand(new PlayCommand(this)); + } + + @Override + protected void start() { + Integer pid = Options.getOptionSet().valueOf(Options.CLIENT_PID); + if (pid != null) { + this.getLogger().at(Level.INFO).log("Client PID: %d", pid); + HytaleServer.SCHEDULED_EXECUTOR.scheduleWithFixedDelay(() -> { + try { + checkClientPid(); + } catch (Exception var2) { + this.getLogger().at(Level.SEVERE).withCause(var2).log("Failed to check client PID!"); + } + }, 60L, 60L, TimeUnit.SECONDS); + } + } + + public Access getAccess() { + return this.access; + } + + public Access getRequestedAccess() { + return this.requestedAccess; + } + + public void requestServerAccess(Access access) { + if (!Constants.SINGLEPLAYER) { + throw new IllegalArgumentException("Server access can only be modified in singleplayer!"); + } else { + ServerManager serverManager = ServerManager.get(); + short externalPort = 0; + if (access != Access.Private) { + if (!serverManager.bind(new InetSocketAddress(0))) { + this.requestServerAccess(Access.Private); + return; + } + + try { + InetSocketAddress boundAddress = serverManager.getNonLoopbackAddress(); + if (boundAddress != null) { + externalPort = (short)boundAddress.getPort(); + } + } catch (Exception var8) { + this.getLogger().at(Level.WARNING).withCause(var8).log("Failed to get bound port"); + } + } else { + for (Channel channel : serverManager.getListeners()) { + if (channel.localAddress() instanceof InetSocketAddress inetSocketAddress && inetSocketAddress.getAddress().isAnyLocalAddress()) { + serverManager.unbind(channel); + } + } + } + + IEventDispatcher dispatchFor = HytaleServer.get() + .getEventBus() + .dispatchFor(SingleplayerRequestAccessEvent.class); + if (dispatchFor.hasListener()) { + dispatchFor.dispatch(new SingleplayerRequestAccessEvent(access)); + } + + Universe.get().getPlayer(getUuid()).getPacketHandler().writeNoCache(new RequestServerAccess(access, externalPort)); + this.requestedAccess = access; + } + } + + public void setPublicAddresses(List publicAddresses) { + this.publicAddresses = publicAddresses; + } + + public void updateAccess(@Nonnull Access access) { + if (this.requestedAccess != access) { + Universe.get() + .sendMessage( + Message.translation("server.modules.sp.requestAccessDifferent") + .param("requestedAccess", this.requestedAccess.toString()) + .param("access", access.toString()) + ); + } + + Universe.get().sendMessage(Message.translation("server.modules.sp.serverAccessUpdated").param("access", access.toString())); + this.access = access; + } + + public static void checkClientPid() { + if (!ProcessUtil.isProcessRunning(Options.getOptionSet().valueOf(Options.CLIENT_PID))) { + HytaleServer.get().shutdownServer(ShutdownReason.CLIENT_GONE); + } + } + + public static UUID getUuid() { + return Options.getOptionSet().valueOf(Options.OWNER_UUID); + } + + public static String getUsername() { + return Options.getOptionSet().valueOf(Options.OWNER_NAME); + } + + public static boolean isOwner(@Nonnull PlayerRef player) { + return isOwner(player.getPacketHandler().getAuth(), player.getUuid()); + } + + public static boolean isOwner(PlayerAuthentication playerAuth, @Nonnull UUID playerUuid) { + return playerUuid.equals(getUuid()); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/singleplayer/SingleplayerRequestAccessEvent.java b/src/com/hypixel/hytale/server/core/modules/singleplayer/SingleplayerRequestAccessEvent.java new file mode 100644 index 0000000..c11095a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/singleplayer/SingleplayerRequestAccessEvent.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.core.modules.singleplayer; + +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.protocol.packets.serveraccess.Access; +import javax.annotation.Nonnull; + +public class SingleplayerRequestAccessEvent implements IEvent { + private final Access access; + + public SingleplayerRequestAccessEvent(Access access) { + this.access = access; + } + + public Access getAccess() { + return this.access; + } + + @Nonnull + @Override + public String toString() { + return "SingleplayerRequestAccessEvent{access=" + this.access + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/singleplayer/commands/PlayCommand.java b/src/com/hypixel/hytale/server/core/modules/singleplayer/commands/PlayCommand.java new file mode 100644 index 0000000..fb1acfc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/singleplayer/commands/PlayCommand.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.server.core.modules.singleplayer.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import javax.annotation.Nonnull; + +public class PlayCommand extends AbstractCommandCollection { + public PlayCommand(@Nonnull SingleplayerModule singleplayerModule) { + super("play", "server.commands.play.desc"); + this.addSubCommand(new PlayLanCommand(singleplayerModule)); + this.addSubCommand(new PlayFriendCommand(singleplayerModule)); + this.addSubCommand(new PlayOnlineCommand(singleplayerModule)); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/singleplayer/commands/PlayCommandBase.java b/src/com/hypixel/hytale/server/core/modules/singleplayer/commands/PlayCommandBase.java new file mode 100644 index 0000000..03b9136 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/singleplayer/commands/PlayCommandBase.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.server.core.modules.singleplayer.commands; + +import com.hypixel.hytale.protocol.packets.serveraccess.Access; +import com.hypixel.hytale.server.core.Constants; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import javax.annotation.Nonnull; + +public abstract class PlayCommandBase extends CommandBase { + @Nonnull + private final SingleplayerModule singleplayerModule; + @Nonnull + private final Access commandAccess; + @Nonnull + private final OptionalArg enabledArg = this.withOptionalArg("enabled", "server.commands.play.enabled.desc", ArgTypes.BOOLEAN); + + protected PlayCommandBase(@Nonnull String name, @Nonnull String description, @Nonnull SingleplayerModule singleplayerModule, @Nonnull Access commandAccess) { + super(name, description); + this.singleplayerModule = singleplayerModule; + this.commandAccess = commandAccess; + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + if (!Constants.SINGLEPLAYER) { + context.sendMessage(Message.translation("server.commands.play.singleplayerOnly").param("commandAccess", this.commandAccess.toString())); + } else { + Access access = SingleplayerModule.get().getAccess(); + if (!this.enabledArg.provided(context)) { + if (access == this.commandAccess) { + this.singleplayerModule.requestServerAccess(Access.Private); + context.sendMessage(Message.translation("server.commands.play.accessDisabled").param("commandAccess", this.commandAccess.toString())); + } else { + this.singleplayerModule.requestServerAccess(this.commandAccess); + context.sendMessage(Message.translation("server.commands.play.accessEnabled").param("commandAccess", this.commandAccess.toString())); + } + } else { + boolean enabled = this.enabledArg.get(context); + if (!enabled && access == this.commandAccess) { + this.singleplayerModule.requestServerAccess(Access.Private); + context.sendMessage(Message.translation("server.commands.play.accessDisabled").param("commandAccess", this.commandAccess.toString())); + } else if (enabled && access != this.commandAccess) { + this.singleplayerModule.requestServerAccess(this.commandAccess); + context.sendMessage(Message.translation("server.commands.play.accessEnabled").param("commandAccess", this.commandAccess.toString())); + } else { + context.sendMessage( + Message.translation("server.commands.play.accessAlreadyToggled") + .param("commandAccess", this.commandAccess.toString()) + .param("enabled", MessageFormat.enabled(enabled)) + ); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/singleplayer/commands/PlayFriendCommand.java b/src/com/hypixel/hytale/server/core/modules/singleplayer/commands/PlayFriendCommand.java new file mode 100644 index 0000000..77f2340 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/singleplayer/commands/PlayFriendCommand.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.modules.singleplayer.commands; + +import com.hypixel.hytale.protocol.packets.serveraccess.Access; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import javax.annotation.Nonnull; + +public class PlayFriendCommand extends PlayCommandBase { + public PlayFriendCommand(@Nonnull SingleplayerModule singleplayerModule) { + super("friend", "server.commands.play.friend.desc", singleplayerModule, Access.Friend); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/singleplayer/commands/PlayLanCommand.java b/src/com/hypixel/hytale/server/core/modules/singleplayer/commands/PlayLanCommand.java new file mode 100644 index 0000000..8058f96 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/singleplayer/commands/PlayLanCommand.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.modules.singleplayer.commands; + +import com.hypixel.hytale.protocol.packets.serveraccess.Access; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import javax.annotation.Nonnull; + +public class PlayLanCommand extends PlayCommandBase { + public PlayLanCommand(@Nonnull SingleplayerModule singleplayerModule) { + super("lan", "server.commands.play.lan.desc", singleplayerModule, Access.LAN); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/singleplayer/commands/PlayOnlineCommand.java b/src/com/hypixel/hytale/server/core/modules/singleplayer/commands/PlayOnlineCommand.java new file mode 100644 index 0000000..daefcd7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/singleplayer/commands/PlayOnlineCommand.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.modules.singleplayer.commands; + +import com.hypixel.hytale.protocol.packets.serveraccess.Access; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import javax.annotation.Nonnull; + +public class PlayOnlineCommand extends PlayCommandBase { + public PlayOnlineCommand(@Nonnull SingleplayerModule singleplayerModule) { + super("online", "server.commands.play.online.desc", singleplayerModule, Access.Open); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/splitvelocity/SplitVelocity.java b/src/com/hypixel/hytale/server/core/modules/splitvelocity/SplitVelocity.java new file mode 100644 index 0000000..17ccf58 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/splitvelocity/SplitVelocity.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.server.core.modules.splitvelocity; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.protocol.packets.setup.ClientFeature; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import javax.annotation.Nonnull; + +public class SplitVelocity extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(SplitVelocity.class).build(); + public static boolean SHOULD_MODIFY_VELOCITY = true; + + public SplitVelocity(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + this.getClientFeatureRegistry().register(ClientFeature.SplitVelocity); + SHOULD_MODIFY_VELOCITY = false; + } + + @Override + protected void shutdown() { + SHOULD_MODIFY_VELOCITY = true; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/splitvelocity/VelocityConfig.java b/src/com/hypixel/hytale/server/core/modules/splitvelocity/VelocityConfig.java new file mode 100644 index 0000000..53f5f7d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/splitvelocity/VelocityConfig.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.server.core.modules.splitvelocity; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.protocol.VelocityThresholdStyle; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import javax.annotation.Nonnull; + +public class VelocityConfig implements NetworkSerializable { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder(VelocityConfig.class, VelocityConfig::new) + .appendInherited( + new KeyedCodec<>("GroundResistance", Codec.FLOAT), + (o, i) -> o.groundResistance = i, + o -> o.groundResistance, + (o, p) -> o.groundResistance = p.groundResistance + ) + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .appendInherited( + new KeyedCodec<>("GroundResistanceMax", Codec.FLOAT), + (o, i) -> o.groundResistanceMax = i, + o -> o.groundResistanceMax, + (o, p) -> o.groundResistanceMax = p.groundResistanceMax + ) + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .appendInherited( + new KeyedCodec<>("AirResistance", Codec.FLOAT), (o, i) -> o.airResistance = i, o -> o.airResistance, (o, p) -> o.airResistance = p.airResistance + ) + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .appendInherited( + new KeyedCodec<>("AirResistanceMax", Codec.FLOAT), + (o, i) -> o.airResistanceMax = i, + o -> o.airResistanceMax, + (o, p) -> o.airResistance = p.airResistanceMax + ) + .addValidator(Validators.range(0.0F, 1.0F)) + .add() + .appendInherited(new KeyedCodec<>("Threshold", Codec.FLOAT), (o, i) -> o.threshold = i, o -> o.threshold, (o, p) -> o.threshold = p.threshold) + .documentation("The threshold of the velocity's length before resistance starts to transition to the Max values (if set)") + .add() + .appendInherited( + new KeyedCodec<>("Style", new EnumCodec<>(VelocityThresholdStyle.class)), (o, i) -> o.style = i, o -> o.style, (o, p) -> o.style = p.style + ) + .documentation("Whether the transition from min to max resistance values should be linear or not") + .add() + .build(); + private float groundResistance = 0.82F; + private float groundResistanceMax = 0.0F; + private float airResistance = 0.96F; + private float airResistanceMax = 0.0F; + private float threshold = 1.0F; + private VelocityThresholdStyle style = VelocityThresholdStyle.Linear; + + public VelocityConfig() { + } + + public float getGroundResistance() { + return this.groundResistance; + } + + public float getAirResistance() { + return this.airResistance; + } + + public float getGroundResistanceMax() { + return this.groundResistanceMax; + } + + public float getAirResistanceMax() { + return this.airResistanceMax; + } + + public float getThreshold() { + return this.threshold; + } + + public VelocityThresholdStyle getStyle() { + return this.style; + } + + @Nonnull + public com.hypixel.hytale.protocol.VelocityConfig toPacket() { + return new com.hypixel.hytale.protocol.VelocityConfig( + this.groundResistance, this.groundResistanceMax, this.airResistance, this.airResistanceMax, this.threshold, this.style + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/time/TimeModule.java b/src/com/hypixel/hytale/server/core/modules/time/TimeModule.java new file mode 100644 index 0000000..3d3aeb5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/time/TimeModule.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.core.modules.time; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.modules.time.commands.TimeCommand; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class TimeModule extends JavaPlugin { + @Nonnull + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(TimeModule.class).build(); + private static TimeModule instance; + private ResourceType worldTimeResourceType; + private ResourceType timeResourceType; + + public static TimeModule get() { + return instance; + } + + public TimeModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + ComponentRegistryProxy entityStoreRegistry = this.getEntityStoreRegistry(); + this.getCommandRegistry().registerCommand(new TimeCommand()); + this.worldTimeResourceType = entityStoreRegistry.registerResource(WorldTimeResource.class, WorldTimeResource::new); + entityStoreRegistry.registerSystem(new WorldTimeSystems.Init(this.worldTimeResourceType)); + entityStoreRegistry.registerSystem(new WorldTimeSystems.Ticking(this.worldTimeResourceType)); + this.timeResourceType = entityStoreRegistry.registerResource(TimeResource.class, "Time", TimeResource.CODEC); + entityStoreRegistry.registerSystem(new TimeSystem(this.timeResourceType)); + entityStoreRegistry.registerSystem(new TimePacketSystem(this.worldTimeResourceType)); + } + + @Nonnull + public ResourceType getWorldTimeResourceType() { + return this.worldTimeResourceType; + } + + @Nonnull + public ResourceType getTimeResourceType() { + return this.timeResourceType; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/time/TimePacketSystem.java b/src/com/hypixel/hytale/server/core/modules/time/TimePacketSystem.java new file mode 100644 index 0000000..6347414 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/time/TimePacketSystem.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.modules.time; + +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.DelayedSystem; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class TimePacketSystem extends DelayedSystem { + private static final float BROADCAST_INTERVAL = 1.0F; + @Nonnull + private final ResourceType worldTimeResourceType; + + public TimePacketSystem(@Nonnull ResourceType worldTimeResourceType) { + super(1.0F); + this.worldTimeResourceType = worldTimeResourceType; + } + + @Override + public void delayedTick(float dt, int systemIndex, @Nonnull Store store) { + World world = store.getExternalData().getWorld(); + if (!world.getWorldConfig().isGameTimePaused()) { + WorldTimeResource worldTimeResource = store.getResource(this.worldTimeResourceType); + worldTimeResource.broadcastTimePacket(store); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/time/TimeResource.java b/src/com/hypixel/hytale/server/core/modules/time/TimeResource.java new file mode 100644 index 0000000..283269d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/time/TimeResource.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.server.core.modules.time; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.TemporalUnit; +import javax.annotation.Nonnull; + +public class TimeResource implements Resource { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(TimeResource.class, TimeResource::new) + .append(new KeyedCodec<>("Now", Codec.INSTANT), (o, now) -> o.now = now, o -> o.now) + .documentation("Now. The current instant of time.") + .add() + .build(); + @Nonnull + private Instant now; + private float timeDilationModifier = 1.0F; + + @Nonnull + public static ResourceType getResourceType() { + return TimeModule.get().getTimeResourceType(); + } + + public TimeResource() { + this(Instant.EPOCH); + } + + public TimeResource(@Nonnull Instant now) { + this.now = now; + } + + public float getTimeDilationModifier() { + return this.timeDilationModifier; + } + + public void setTimeDilationModifier(float timeDilationModifier) { + this.timeDilationModifier = timeDilationModifier; + } + + @Nonnull + public Instant getNow() { + return this.now; + } + + public void setNow(@Nonnull Instant now) { + this.now = now; + } + + public void add(@Nonnull Duration duration) { + this.now = this.now.plus(duration); + } + + public void add(long time, @Nonnull TemporalUnit unit) { + this.now = this.now.plus(time, unit); + } + + @Nonnull + @Override + public Resource clone() { + return new TimeResource(this.now); + } + + @Nonnull + @Override + public String toString() { + return "TimeResource{now=" + this.now + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/time/TimeSystem.java b/src/com/hypixel/hytale/server/core/modules/time/TimeSystem.java new file mode 100644 index 0000000..7336636 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/time/TimeSystem.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.core.modules.time; + +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.temporal.ChronoUnit; +import javax.annotation.Nonnull; + +public class TimeSystem extends TickingSystem { + @Nonnull + private final ResourceType timeResourceType; + + public TimeSystem(@Nonnull ResourceType timeResourceType) { + this.timeResourceType = timeResourceType; + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + long nanos = (long)(1.0E9F * dt); + store.getResource(this.timeResourceType).add(nanos, ChronoUnit.NANOS); + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/time/WorldTimeResource.java b/src/com/hypixel/hytale/server/core/modules/time/WorldTimeResource.java new file mode 100644 index 0000000..b495ef6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/time/WorldTimeResource.java @@ -0,0 +1,332 @@ +package com.hypixel.hytale.server.core.modules.time; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.InstantData; +import com.hypixel.hytale.protocol.packets.world.UpdateTime; +import com.hypixel.hytale.protocol.packets.world.UpdateTimeSettings; +import com.hypixel.hytale.server.core.asset.type.gameplay.WorldConfig; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.PlayerUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.events.ecs.MoonPhaseChangeEvent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import javax.annotation.Nonnull; + +public class WorldTimeResource implements Resource { + public static final long NANOS_PER_DAY = ChronoUnit.DAYS.getDuration().toNanos(); + public static final int SECONDS_PER_DAY = (int)ChronoUnit.DAYS.getDuration().getSeconds(); + public static final int HOURS_PER_DAY = (int)ChronoUnit.DAYS.getDuration().toHours(); + public static final int DAYS_PER_YEAR = (int)ChronoUnit.YEARS.getDuration().toDays(); + public static final Instant ZERO_YEAR = Instant.parse("0001-01-01T00:00:00.00Z"); + public static final Instant MAX_TIME = Instant.ofEpochSecond(31553789759L, 99999999L); + public static final ZoneOffset ZONE_OFFSET = ZoneOffset.UTC; + public static final float SUN_HEIGHT = 2.0F; + public static final boolean USE_SHADOW_MAPPING_SAFE_ANGLE = true; + public static final float DAYTIME_PORTION_PERCENTAGE = 0.6F; + public static final int DAYTIME_SECONDS = (int)(SECONDS_PER_DAY * 0.6F); + public static final int NIGHTTIME_SECONDS = (int)(SECONDS_PER_DAY * 0.39999998F); + public static final int SUNRISE_SECONDS = NIGHTTIME_SECONDS / 2; + public static final float SHADOW_MAPPING_SAFE_ANGLE_LERP = 0.35F; + @Nonnull + private final UpdateTime currentTimePacket = new UpdateTime(); + private Instant gameTime; + private LocalDateTime _gameTimeLocalDateTime; + private int currentHour; + private double sunlightFactor; + private double scaledTime; + private int moonPhase; + @Nonnull + private final UpdateTimeSettings currentSettings = new UpdateTimeSettings(); + @Nonnull + private final UpdateTimeSettings tempSettings = new UpdateTimeSettings(); + + public WorldTimeResource() { + } + + @Nonnull + public static ResourceType getResourceType() { + return TimeModule.get().getWorldTimeResourceType(); + } + + public static double getSecondsPerTick(World world) { + int daytimeDurationSeconds = world.getDaytimeDurationSeconds(); + int nighttimeDurationSeconds = world.getNighttimeDurationSeconds(); + int totalDurationSeconds = daytimeDurationSeconds + nighttimeDurationSeconds; + return (double)SECONDS_PER_DAY / totalDurationSeconds; + } + + public void tick(float dt, @Nonnull Store store) { + World world = store.getExternalData().getWorld(); + if (!updateTimeSettingsPacket(this.tempSettings, world).equals(this.currentSettings)) { + boolean wasTimePausedChanged = this.currentSettings.timePaused != this.tempSettings.timePaused; + updateTimeSettingsPacket(this.currentSettings, world); + PlayerUtil.broadcastPacketToPlayers(store, this.currentSettings); + if (wasTimePausedChanged) { + this.broadcastTimePacket(store); + } + } + + if (!world.getWorldConfig().isGameTimePaused()) { + int secondsOfDay = this._gameTimeLocalDateTime.get(ChronoField.SECOND_OF_DAY); + int daytimeDurationSeconds = world.getDaytimeDurationSeconds(); + int nighttimeDurationSeconds = world.getNighttimeDurationSeconds(); + int totalDurationSeconds = daytimeDurationSeconds + nighttimeDurationSeconds; + double daytimeRate = (double)DAYTIME_SECONDS / daytimeDurationSeconds; + double nighttimeRate = (double)NIGHTTIME_SECONDS / nighttimeDurationSeconds; + double x0; + if (secondsOfDay >= SUNRISE_SECONDS && secondsOfDay < SUNRISE_SECONDS + DAYTIME_SECONDS) { + x0 = (secondsOfDay - SUNRISE_SECONDS) / daytimeRate; + } else { + x0 = daytimeDurationSeconds + MathUtil.floorMod(secondsOfDay - SUNRISE_SECONDS - DAYTIME_SECONDS, SECONDS_PER_DAY) / nighttimeRate; + } + + double x1 = x0 + dt; + long whole = (long)Math.floor(x1 / totalDurationSeconds) - (long)Math.floor(x0 / totalDurationSeconds); + double m0 = MathUtil.floorMod(x0, totalDurationSeconds); + double m1 = MathUtil.floorMod(x1, totalDurationSeconds); + double f0 = m0 <= daytimeDurationSeconds ? daytimeRate * m0 : DAYTIME_SECONDS + nighttimeRate * (m0 - daytimeDurationSeconds); + double f1 = m1 <= daytimeDurationSeconds ? daytimeRate * m1 : DAYTIME_SECONDS + nighttimeRate * (m1 - daytimeDurationSeconds); + double advance = whole * SECONDS_PER_DAY + (f1 - f0); + Instant temp = this.gameTime.plusNanos((long)(advance * 1.0E9)); + if (temp.isBefore(ZERO_YEAR)) { + temp = MAX_TIME.minusSeconds(ZERO_YEAR.getEpochSecond() - this.gameTime.getEpochSecond()).minusNanos(ZERO_YEAR.getNano() - this.gameTime.getNano()); + } + + if (temp.isAfter(MAX_TIME)) { + temp = ZERO_YEAR.plusSeconds(MAX_TIME.getEpochSecond() - this.gameTime.getEpochSecond()).plusNanos(MAX_TIME.getNano() - this.gameTime.getNano()); + } + + this.setGameTime0(temp); + this.updateMoonPhase(world, store); + } + } + + public int getMoonPhase() { + return this.moonPhase; + } + + public void setMoonPhase(int moonPhase, @Nonnull ComponentAccessor componentAccessor) { + if (moonPhase != this.moonPhase) { + MoonPhaseChangeEvent event = new MoonPhaseChangeEvent(moonPhase); + componentAccessor.invoke(event); + } + + this.moonPhase = moonPhase; + } + + public void updateMoonPhase(@Nonnull World world, @Nonnull ComponentAccessor componentAccessor) { + WorldConfig worldGameplayConfig = world.getGameplayConfig().getWorldConfig(); + int totalMoonPhases = worldGameplayConfig.getTotalMoonPhases(); + double dayProgress = (double)this.currentHour / HOURS_PER_DAY; + int currentDay = this._gameTimeLocalDateTime.getDayOfYear(); + int weekDay = (currentDay - 1) % totalMoonPhases; + if (dayProgress < 0.5) { + if (weekDay == 0) { + this.setMoonPhase(totalMoonPhases - 1, componentAccessor); + } else { + this.setMoonPhase(weekDay - 1, componentAccessor); + } + } else { + this.setMoonPhase(weekDay, componentAccessor); + } + } + + public boolean isMoonPhaseWithinRange(@Nonnull World world, int minMoonPhase, int maxMoonPhase) { + WorldConfig worldGameplayConfig = world.getGameplayConfig().getWorldConfig(); + int totalMoonPhases = worldGameplayConfig.getTotalMoonPhases(); + return minMoonPhase <= maxMoonPhase + ? MathUtil.within(this.moonPhase, minMoonPhase, maxMoonPhase) + : MathUtil.within(this.moonPhase, minMoonPhase, totalMoonPhases) || MathUtil.within(this.moonPhase, 0.0, maxMoonPhase); + } + + public void setGameTime0(@Nonnull Instant gameTime) { + this.gameTime = gameTime; + this._gameTimeLocalDateTime = LocalDateTime.ofInstant(gameTime, ZONE_OFFSET); + this.updateTimePacket(this.currentTimePacket); + this.currentHour = this._gameTimeLocalDateTime.getHour(); + int dayProgress = this._gameTimeLocalDateTime.get(ChronoField.SECOND_OF_DAY); + float dayDuration = 0.6F * SECONDS_PER_DAY; + float nightDuration = SECONDS_PER_DAY - dayDuration; + float halfNight = nightDuration * 0.5F; + this.updateSunlightFactor(dayProgress, halfNight); + this.updateScaledTime(dayProgress, dayDuration, halfNight); + } + + private void updateSunlightFactor(int dayProgress, float halfNight) { + float dawnRelativeProgress = (dayProgress - halfNight) / SECONDS_PER_DAY; + this.sunlightFactor = MathUtil.clamp(TrigMathUtil.sin((float) (Math.PI * 2) * dawnRelativeProgress) + 0.2, 0.0, 1.0); + } + + private void updateScaledTime(float dayProgress, float dayDuration, float halfNight) { + if (dayProgress <= halfNight) { + this.scaledTime = MathUtil.lerp(0.0F, 0.25F, dayProgress / halfNight); + } else { + dayProgress -= halfNight; + if (dayProgress <= dayDuration) { + this.scaledTime = MathUtil.lerp(0.25F, 0.75F, dayProgress / dayDuration); + } else { + dayProgress -= dayDuration; + this.scaledTime = MathUtil.lerp(0.75F, 1.0F, dayProgress / halfNight); + } + } + } + + public Instant getGameTime() { + return this.gameTime; + } + + public LocalDateTime getGameDateTime() { + return this._gameTimeLocalDateTime; + } + + public double getSunlightFactor() { + return this.sunlightFactor; + } + + public void setGameTime(@Nonnull Instant gameTime, @Nonnull World world, @Nonnull Store store) { + this.setGameTime0(gameTime); + this.updateMoonPhase(world, store); + this.broadcastTimePacket(store); + } + + public void setDayTime(double dayTime, @Nonnull World world, @Nonnull Store store) { + if (!(dayTime < 0.0) && !(dayTime > 1.0)) { + Instant oldGameTime = this.gameTime; + Instant dayStart = oldGameTime.truncatedTo(ChronoUnit.DAYS); + Instant newGameTime = dayStart.plusNanos((long)(dayTime * NANOS_PER_DAY)); + if (newGameTime.isBefore(oldGameTime)) { + this.setGameTime(newGameTime.plus(1L, ChronoUnit.DAYS), world, store); + } else { + this.setGameTime(newGameTime, world, store); + } + } else { + throw new IllegalArgumentException("Day time must be between 0 and 1"); + } + } + + public void broadcastTimePacket(@Nonnull Store store) { + PlayerUtil.broadcastPacketToPlayers(store, this.currentTimePacket); + } + + public void sendTimePackets(@Nonnull PlayerRef playerRef) { + playerRef.getPacketHandler().write(this.currentSettings); + playerRef.getPacketHandler().write(this.currentTimePacket); + } + + public boolean isDayTimeWithinRange(double minTime, double maxTime) { + double dayProgress = (double)this._gameTimeLocalDateTime.getHour() / HOURS_PER_DAY; + return !(minTime > maxTime) + ? MathUtil.within(dayProgress, minTime, maxTime) + : MathUtil.within(dayProgress, minTime, 1.0) || MathUtil.within(dayProgress, 0.0, maxTime); + } + + public void updateTimePacket(@Nonnull UpdateTime currentTimePacket) { + if (currentTimePacket.gameTime == null) { + currentTimePacket.gameTime = new InstantData(); + } + + currentTimePacket.gameTime.seconds = this.gameTime.getEpochSecond(); + currentTimePacket.gameTime.nanos = this.gameTime.getNano(); + } + + @Nonnull + public static UpdateTimeSettings updateTimeSettingsPacket(@Nonnull UpdateTimeSettings settings, @Nonnull World world) { + WorldConfig worldGameplayConfig = world.getGameplayConfig().getWorldConfig(); + settings.daytimeDurationSeconds = world.getDaytimeDurationSeconds(); + settings.nighttimeDurationSeconds = world.getNighttimeDurationSeconds(); + settings.totalMoonPhases = (byte)worldGameplayConfig.getTotalMoonPhases(); + settings.timePaused = world.getWorldConfig().isGameTimePaused(); + return settings; + } + + public boolean isScaledDayTimeWithinRange(double minTime, double maxTime) { + return !(minTime > maxTime) + ? MathUtil.within(this.scaledTime, minTime, maxTime) + : MathUtil.within(this.scaledTime, minTime, 1.0) || MathUtil.within(this.scaledTime, 0.0, maxTime); + } + + public boolean isYearWithinRange(double minTime, double maxTime) { + return false; + } + + public int getCurrentHour() { + return this.currentHour; + } + + public float getDayProgress() { + return (float)this._gameTimeLocalDateTime.get(ChronoField.SECOND_OF_DAY) / SECONDS_PER_DAY; + } + + @Nonnull + public Vector3f getSunDirection() { + float dayTime = this.getDayProgress() * HOURS_PER_DAY; + float daylightDuration = 0.6F * HOURS_PER_DAY; + float nightDuration = HOURS_PER_DAY - daylightDuration; + float halfNightDuration = nightDuration * 0.5F; + float sunAngle; + if (dayTime < halfNightDuration) { + float inverseAllNightDay = 1.0F / (nightDuration * 2.0F); + sunAngle = MathUtil.wrapAngle((dayTime * inverseAllNightDay - halfNightDuration * inverseAllNightDay) * (float) (Math.PI * 2)); + } else if (dayTime > HOURS_PER_DAY - halfNightDuration) { + float inverseAllNightDay = 1.0F / (nightDuration * 2.0F); + sunAngle = MathUtil.wrapAngle((dayTime * inverseAllNightDay - (HOURS_PER_DAY + halfNightDuration) * inverseAllNightDay) * (float) (Math.PI * 2)); + } else { + float halfDaylightDuration = daylightDuration * 0.5F; + float inverseAllDaylightDay = 1.0F / (daylightDuration * 2.0F); + sunAngle = MathUtil.wrapAngle( + (dayTime * inverseAllDaylightDay - (HOURS_PER_DAY * 0.5F - halfDaylightDuration) * inverseAllDaylightDay) * (float) (Math.PI * 2) + ); + } + + Vector3f sunPosition = new Vector3f(TrigMathUtil.cos(sunAngle), TrigMathUtil.sin(sunAngle) * 2.0F, TrigMathUtil.sin(sunAngle)); + sunPosition.normalize(); + float tweakedSunHeight = sunPosition.y + 0.2F; + if (tweakedSunHeight > 0.0F) { + sunPosition.scale(-1.0F); + } + + sunPosition.x = MathUtil.lerp(sunPosition.x, Vector3f.DOWN.x, 0.35F); + sunPosition.y = MathUtil.lerp(sunPosition.y, Vector3f.DOWN.y, 0.35F); + sunPosition.z = MathUtil.lerp(sunPosition.z, Vector3f.DOWN.z, 0.35F); + return sunPosition; + } + + @Nonnull + public static InstantData instantToInstantData(@Nonnull Instant instant) { + return new InstantData(instant.getEpochSecond(), instant.getNano()); + } + + @Nonnull + public static Instant instantDataToInstant(@Nonnull InstantData instantData) { + return Instant.ofEpochSecond(instantData.seconds, instantData.nanos); + } + + @Nonnull + @Override + public Resource clone() { + WorldTimeResource worldTimeComponent = new WorldTimeResource(); + worldTimeComponent.gameTime = this.gameTime; + worldTimeComponent._gameTimeLocalDateTime = this._gameTimeLocalDateTime; + worldTimeComponent.currentHour = this.currentHour; + worldTimeComponent.sunlightFactor = this.sunlightFactor; + worldTimeComponent.scaledTime = this.scaledTime; + return worldTimeComponent; + } + + @Nonnull + @Override + public String toString() { + return "WorldTimeResource{, gameTime=" + this.gameTime + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/time/WorldTimeSystems.java b/src/com/hypixel/hytale/server/core/modules/time/WorldTimeSystems.java new file mode 100644 index 0000000..6134254 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/time/WorldTimeSystems.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.core.modules.time; + +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.StoreSystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WorldTimeSystems { + public WorldTimeSystems() { + } + + public static class Init extends StoreSystem { + @Nonnull + private final ResourceType worldTimeResourceType; + + public Init(@Nonnull ResourceType worldTimeResourceType) { + this.worldTimeResourceType = worldTimeResourceType; + } + + @Override + public void onSystemAddedToStore(@Nonnull Store store) { + World world = store.getExternalData().getWorld(); + WorldTimeResource worldTimeResource = store.getResource(this.worldTimeResourceType); + worldTimeResource.setGameTime0(world.getWorldConfig().getGameTime()); + world.execute(() -> worldTimeResource.updateMoonPhase(world, store)); + } + + @Override + public void onSystemRemovedFromStore(@Nonnull Store store) { + World world = store.getExternalData().getWorld(); + WorldTimeResource worldTimeResource = store.getResource(this.worldTimeResourceType); + WorldConfig worldConfig = world.getWorldConfig(); + worldConfig.setGameTime(worldTimeResource.getGameTime()); + worldConfig.markChanged(); + } + } + + public static class Ticking extends TickingSystem { + @Nonnull + private final ResourceType worldTimeResourceType; + + public Ticking(@Nonnull ResourceType worldTimeResourceType) { + this.worldTimeResourceType = worldTimeResourceType; + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + WorldTimeResource worldTimeResource = store.getResource(this.worldTimeResourceType); + worldTimeResource.tick(dt, store); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/modules/time/commands/TimeCommand.java b/src/com/hypixel/hytale/server/core/modules/time/commands/TimeCommand.java new file mode 100644 index 0000000..4b76b08 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/modules/time/commands/TimeCommand.java @@ -0,0 +1,161 @@ +package com.hypixel.hytale.server.core.modules.time.commands; + +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.commands.worldconfig.WorldConfigPauseTimeCommand; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.floats.Float2FloatFunction; +import java.time.LocalDateTime; +import java.time.temporal.ChronoField; +import javax.annotation.Nonnull; + +public class TimeCommand extends AbstractWorldCommand { + public TimeCommand() { + super("time", "server.commands.time.get.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addAliases("daytime"); + this.addUsageVariant(new TimeCommand.SetTimeHourCommand()); + + for (TimeCommand.TimeOfDay value : TimeCommand.TimeOfDay.values()) { + this.addSubCommand(new TimeCommand.SetTimePeriodCommand(value)); + } + + this.addSubCommand(new TimeCommand.TimeSetSubCommand()); + this.addSubCommand(new TimeCommand.TimePauseCommand()); + this.addSubCommand(new TimeCommand.TimeDilationCommand()); + } + + @Override + public void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + LocalDateTime gameDateTime = worldTimeResource.getGameDateTime(); + Message pausedMessage = Message.translation(world.getWorldConfig().isGameTimePaused() ? "server.commands.time.paused" : "server.commands.time.unpaused"); + Message message = Message.translation("server.commands.time.info").param("worldName", world.getName()).param("timePaused", pausedMessage); + context.sendMessage( + message.param("time", worldTimeResource.getGameTime().toString()) + .param("dayOfWeek", FormatUtil.addNumberSuffix(gameDateTime.get(ChronoField.DAY_OF_WEEK))) + .param("weekOfMonth", FormatUtil.addNumberSuffix(gameDateTime.get(ChronoField.ALIGNED_WEEK_OF_MONTH))) + .param("weekOfYear", FormatUtil.addNumberSuffix(gameDateTime.get(ChronoField.ALIGNED_WEEK_OF_YEAR))) + .param("dayOfYear", FormatUtil.addNumberSuffix(gameDateTime.getDayOfYear())) + .param("moonPhase", FormatUtil.addNumberSuffix(worldTimeResource.getMoonPhase() + 1)) + ); + } + + private static class SetTimeHourCommand extends AbstractWorldCommand { + @Nonnull + private final RequiredArg timeArg = this.withRequiredArg("time", "server.commands.time.set.timeArg.desc", ArgTypes.FLOAT) + .addValidator(Validators.range(0.0F, (float)WorldTimeResource.HOURS_PER_DAY)); + + public SetTimeHourCommand() { + super("server.commands.time.set.desc"); + this.setPermissionGroup(null); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + float time = this.timeArg.get(context); + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + worldTimeResource.setDayTime(time / WorldTimeResource.HOURS_PER_DAY, world, store); + context.sendMessage( + Message.translation("server.commands.time.set").param("worldName", world.getName()).param("time", worldTimeResource.getGameTime().toString()) + ); + } + } + + private static class SetTimePeriodCommand extends AbstractWorldCommand { + @Nonnull + private final TimeCommand.TimeOfDay timeOfDay; + + public SetTimePeriodCommand(@Nonnull TimeCommand.TimeOfDay timeOfDay) { + super(timeOfDay.name(), "server.commands.time.period." + timeOfDay.name().toLowerCase() + ".desc"); + this.setPermissionGroup(null); + this.timeOfDay = timeOfDay; + this.addAliases(timeOfDay.aliases); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + float daylightHours = WorldTimeResource.HOURS_PER_DAY * 0.6F; + float periodTime = Math.max(0.0F, this.timeOfDay.periodFunc.apply(daylightHours)); + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + worldTimeResource.setDayTime(periodTime / WorldTimeResource.HOURS_PER_DAY, world, store); + context.sendMessage( + Message.translation("server.commands.time.set") + .param("worldName", world.getName()) + .param("time", String.format("%s (%s)", worldTimeResource.getGameTime().toString(), this.timeOfDay.name())) + ); + } + } + + private static class TimeDilationCommand extends AbstractWorldCommand { + private static final float TIME_DILATION_MIN = 0.01F; + private static final float TIME_DILATION_MAX = 4.0F; + @Nonnull + private final RequiredArg timeDilationArg = this.withRequiredArg("timeDilation", "server.commands.time.dilation.timeDilation.desc", ArgTypes.FLOAT) + .addValidator(Validators.greaterThan(0.01F)) + .addValidator(Validators.max(4.0F)); + + public TimeDilationCommand() { + super("dilation", "server.commands.time.dilation.desc"); + this.setPermissionGroup(null); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + float timeDilation = this.timeDilationArg.get(context); + World.setTimeDilation(timeDilation, store); + context.sendMessage(Message.translation("server.commands.time.dilation.set.success").param("timeDilation", timeDilation)); + } + } + + public static enum TimeOfDay { + Dawn(hoursOfDaylight -> (WorldTimeResource.HOURS_PER_DAY - hoursOfDaylight) / 2.0F, "day", "morning"), + Midday(hoursOfDaylight -> WorldTimeResource.HOURS_PER_DAY / 2.0F, "noon"), + Dusk(hoursOfDaylight -> (WorldTimeResource.HOURS_PER_DAY - hoursOfDaylight) / 2.0F + hoursOfDaylight, "night"), + Midnight(hoursOfDaylight -> 0.0F); + + @Nonnull + private final Float2FloatFunction periodFunc; + private final String[] aliases; + + private TimeOfDay(@Nonnull final Float2FloatFunction periodFunc, String... aliases) { + this.periodFunc = periodFunc; + this.aliases = aliases; + } + } + + private static class TimePauseCommand extends AbstractWorldCommand { + public TimePauseCommand() { + super("pause", "server.commands.pausetime.desc"); + this.setPermissionGroup(null); + this.addAliases("stop"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + WorldConfigPauseTimeCommand.pauseTime(context.sender(), world, store); + } + } + + private static class TimeSetSubCommand extends AbstractCommandCollection { + public TimeSetSubCommand() { + super("set", "server.commands.time.set.desc"); + this.setPermissionGroup(null); + this.addUsageVariant(new TimeCommand.SetTimeHourCommand()); + + for (TimeCommand.TimeOfDay value : TimeCommand.TimeOfDay.values()) { + this.addSubCommand(new TimeCommand.SetTimePeriodCommand(value)); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/permissions/HytalePermissions.java b/src/com/hypixel/hytale/server/core/permissions/HytalePermissions.java new file mode 100644 index 0000000..520d14a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/permissions/HytalePermissions.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.core.permissions; + +import javax.annotation.Nonnull; + +public class HytalePermissions { + public static final String NAMESPACE = "hytale"; + public static final String COMMAND_BASE = "hytale.command"; + public static final String ASSET_EDITOR = "hytale.editor.asset"; + public static final String ASSET_EDITOR_PACKS_CREATE = "hytale.editor.packs.create"; + public static final String ASSET_EDITOR_PACKS_EDIT = "hytale.editor.packs.edit"; + public static final String ASSET_EDITOR_PACKS_DELETE = "hytale.editor.packs.delete"; + public static final String BUILDER_TOOLS_EDITOR = "hytale.editor.builderTools"; + public static final String EDITOR_BRUSH_USE = "hytale.editor.brush.use"; + public static final String EDITOR_BRUSH_CONFIG = "hytale.editor.brush.config"; + public static final String EDITOR_PREFAB_USE = "hytale.editor.prefab.use"; + public static final String EDITOR_PREFAB_MANAGE = "hytale.editor.prefab.manage"; + public static final String EDITOR_SELECTION_USE = "hytale.editor.selection.use"; + public static final String EDITOR_SELECTION_CLIPBOARD = "hytale.editor.selection.clipboard"; + public static final String EDITOR_SELECTION_MODIFY = "hytale.editor.selection.modify"; + public static final String EDITOR_HISTORY = "hytale.editor.history"; + public static final String FLY_CAM = "hytale.camera.flycam"; + + public HytalePermissions() { + } + + @Nonnull + public static String fromCommand(@Nonnull String name) { + return "hytale.command." + name; + } + + @Nonnull + public static String fromCommand(@Nonnull String name, @Nonnull String subCommand) { + return "hytale.command." + name + "." + subCommand; + } +} diff --git a/src/com/hypixel/hytale/server/core/permissions/PermissionHolder.java b/src/com/hypixel/hytale/server/core/permissions/PermissionHolder.java new file mode 100644 index 0000000..a90dbcd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/permissions/PermissionHolder.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.server.core.permissions; + +import javax.annotation.Nonnull; + +public interface PermissionHolder { + boolean hasPermission(@Nonnull String var1); + + boolean hasPermission(@Nonnull String var1, boolean var2); +} diff --git a/src/com/hypixel/hytale/server/core/permissions/PermissionsModule.java b/src/com/hypixel/hytale/server/core/permissions/PermissionsModule.java new file mode 100644 index 0000000..c791dc5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/permissions/PermissionsModule.java @@ -0,0 +1,223 @@ +package com.hypixel.hytale.server.core.permissions; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.command.system.CommandManager; +import com.hypixel.hytale.server.core.command.system.CommandRegistry; +import com.hypixel.hytale.server.core.event.events.permissions.GroupPermissionChangeEvent; +import com.hypixel.hytale.server.core.event.events.permissions.PlayerGroupEvent; +import com.hypixel.hytale.server.core.event.events.permissions.PlayerPermissionChangeEvent; +import com.hypixel.hytale.server.core.permissions.commands.PermCommand; +import com.hypixel.hytale.server.core.permissions.commands.op.OpCommand; +import com.hypixel.hytale.server.core.permissions.provider.HytalePermissionsProvider; +import com.hypixel.hytale.server.core.permissions.provider.PermissionProvider; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PermissionsModule extends JavaPlugin { + @Nonnull + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(PermissionsModule.class).build(); + private static PermissionsModule instance; + @Nonnull + private final HytalePermissionsProvider standardProvider = new HytalePermissionsProvider(); + @Nonnull + private Map> virtualGroups = Object2ObjectMaps.emptyMap(); + @Nonnull + private final List providers = new CopyOnWriteArrayList() { + { + this.add(PermissionsModule.this.standardProvider); + } + }; + + public static PermissionsModule get() { + return instance; + } + + public PermissionsModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + CommandRegistry commandRegistry = this.getCommandRegistry(); + commandRegistry.registerCommand(new OpCommand()); + commandRegistry.registerCommand(new PermCommand()); + } + + @Override + protected void start() { + Map> virtualGroups = CommandManager.get().createVirtualPermissionGroups(); + virtualGroups.computeIfAbsent(GameMode.Creative.toString(), k -> new HashSet<>()).add("hytale.editor.builderTools"); + this.setVirtualGroups(virtualGroups); + this.standardProvider.syncLoad(); + } + + public void addProvider(@Nonnull PermissionProvider permissionProvider) { + this.providers.add(permissionProvider); + } + + public void removeProvider(@Nonnull PermissionProvider provider) { + this.providers.remove(provider); + } + + @Nonnull + public List getProviders() { + return Collections.unmodifiableList(this.providers); + } + + public PermissionProvider getFirstPermissionProvider() { + return this.providers.getFirst(); + } + + public boolean areProvidersTampered() { + return this.providers.size() != 1 || this.providers.getFirst() != this.standardProvider; + } + + public void addUserPermission(@Nonnull UUID uuid, @Nonnull Set permissions) { + this.getFirstPermissionProvider().addUserPermissions(uuid, permissions); + HytaleServer.get() + .getEventBus() + .dispatchFor(PlayerPermissionChangeEvent.PermissionsAdded.class) + .dispatch(new PlayerPermissionChangeEvent.PermissionsAdded(uuid, permissions)); + } + + public void removeUserPermission(@Nonnull UUID uuid, @Nonnull Set permissions) { + this.getFirstPermissionProvider().removeUserPermissions(uuid, permissions); + HytaleServer.get() + .getEventBus() + .dispatchFor(PlayerPermissionChangeEvent.PermissionsRemoved.class) + .dispatch(new PlayerPermissionChangeEvent.PermissionsRemoved(uuid, permissions)); + } + + public void addGroupPermission(@Nonnull String group, @Nonnull Set permissions) { + this.getFirstPermissionProvider().addGroupPermissions(group, permissions); + HytaleServer.get() + .getEventBus() + .dispatchFor(GroupPermissionChangeEvent.Added.class) + .dispatch(new GroupPermissionChangeEvent.Added(group, permissions)); + } + + public void removeGroupPermission(@Nonnull String group, @Nonnull Set permissions) { + this.getFirstPermissionProvider().removeGroupPermissions(group, permissions); + HytaleServer.get() + .getEventBus() + .dispatchFor(GroupPermissionChangeEvent.Removed.class) + .dispatch(new GroupPermissionChangeEvent.Removed(group, permissions)); + } + + public void addUserToGroup(@Nonnull UUID uuid, @Nonnull String group) { + this.getFirstPermissionProvider().addUserToGroup(uuid, group); + HytaleServer.get() + .getEventBus() + .dispatchFor(PlayerGroupEvent.Added.class) + .dispatch(new PlayerGroupEvent.Added(uuid, group)); + } + + public void removeUserFromGroup(@Nonnull UUID uuid, @Nonnull String group) { + this.getFirstPermissionProvider().removeUserFromGroup(uuid, group); + HytaleServer.get() + .getEventBus() + .dispatchFor(PlayerGroupEvent.Removed.class) + .dispatch(new PlayerGroupEvent.Removed(uuid, group)); + } + + public void setVirtualGroups(@Nonnull Map> virtualGroups) { + this.virtualGroups = new Object2ObjectOpenHashMap<>(virtualGroups); + } + + @Nonnull + public Set getGroupsForUser(@Nonnull UUID uuid) { + Set groups = null; + + for (PermissionProvider permissionProvider : this.providers) { + Set providerGroups = permissionProvider.getGroupsForUser(uuid); + if (!providerGroups.isEmpty()) { + if (groups == null) { + groups = new HashSet<>(); + } + + groups.addAll(providerGroups); + } + } + + return groups != null ? Collections.unmodifiableSet(groups) : Collections.emptySet(); + } + + public boolean hasPermission(@Nonnull UUID uuid, @Nonnull String id) { + return this.hasPermission(uuid, id, false); + } + + public boolean hasPermission(@Nonnull UUID uuid, @Nonnull String id, boolean def) { + for (PermissionProvider permissionProvider : this.providers) { + Set userNodes = permissionProvider.getUserPermissions(uuid); + Boolean userHasPerm = hasPermission(userNodes, id); + if (userHasPerm != null) { + return userHasPerm; + } + + for (String group : permissionProvider.getGroupsForUser(uuid)) { + Set groupNodes = permissionProvider.getGroupPermissions(group); + Boolean groupHasPerm = hasPermission(groupNodes, id); + if (groupHasPerm != null) { + return groupHasPerm; + } + + Set virtualNodes = this.virtualGroups.get(group); + Boolean virtualHasPerm = hasPermission(virtualNodes, id); + if (virtualHasPerm != null) { + return virtualHasPerm; + } + } + } + + return def; + } + + @Nullable + public static Boolean hasPermission(@Nullable Set nodes, @Nonnull String id) { + if (nodes == null) { + return null; + } else if (nodes.contains("*")) { + return Boolean.TRUE; + } else if (nodes.contains("-*")) { + return Boolean.FALSE; + } else if (nodes.contains(id)) { + return Boolean.TRUE; + } else if (nodes.contains("-" + id)) { + return Boolean.FALSE; + } else { + String[] split = id.split("\\."); + StringBuilder completeTrace = new StringBuilder(); + + for (int i = 0; i < split.length; i++) { + if (i > 0) { + completeTrace.append('.'); + } + + completeTrace.append(split[i]); + if (nodes.contains(completeTrace + ".*")) { + return Boolean.TRUE; + } + + if (nodes.contains("-" + completeTrace.toString() + ".*")) { + return Boolean.FALSE; + } + } + + return null; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/permissions/commands/PermCommand.java b/src/com/hypixel/hytale/server/core/permissions/commands/PermCommand.java new file mode 100644 index 0000000..5a8c43f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/permissions/commands/PermCommand.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.core.permissions.commands; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class PermCommand extends AbstractCommandCollection { + public PermCommand() { + super("perm", "server.commands.perm.desc"); + this.addSubCommand(new PermGroupCommand()); + this.addSubCommand(new PermUserCommand()); + this.addSubCommand(new PermTestCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/permissions/commands/PermGroupCommand.java b/src/com/hypixel/hytale/server/core/permissions/commands/PermGroupCommand.java new file mode 100644 index 0000000..f3dc2ba --- /dev/null +++ b/src/com/hypixel/hytale/server/core/permissions/commands/PermGroupCommand.java @@ -0,0 +1,87 @@ +package com.hypixel.hytale.server.core.permissions.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.permissions.PermissionsModule; +import com.hypixel.hytale.server.core.permissions.provider.PermissionProvider; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class PermGroupCommand extends AbstractCommandCollection { + public PermGroupCommand() { + super("group", "server.commands.perm.group.desc"); + this.addSubCommand(new PermGroupCommand.PermGroupListCommand()); + this.addSubCommand(new PermGroupCommand.PermGroupAddCommand()); + this.addSubCommand(new PermGroupCommand.PermGroupRemoveCommand()); + } + + private static class PermGroupAddCommand extends CommandBase { + @Nonnull + private final RequiredArg groupArg = this.withRequiredArg("group", "server.commands.perm.group.add.group.desc", ArgTypes.STRING); + @Nonnull + private final RequiredArg> permissionsArg = this.withListRequiredArg( + "permissions", "server.commands.perm.group.add.permissions.desc", ArgTypes.STRING + ); + + public PermGroupAddCommand() { + super("add", "server.commands.perm.group.add.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + String group = this.groupArg.get(context); + HashSet permissions = new HashSet<>(this.permissionsArg.get(context)); + PermissionsModule.get().addGroupPermission(group, permissions); + context.sendMessage(Message.translation("server.commands.perm.addPermToGroup").param("group", group)); + } + } + + private static class PermGroupListCommand extends CommandBase { + @Nonnull + private final RequiredArg groupArg = this.withRequiredArg("group", "server.commands.perm.group.list.group.desc", ArgTypes.STRING); + + public PermGroupListCommand() { + super("list", "server.commands.perm.group.list.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + String group = this.groupArg.get(context); + + for (PermissionProvider permissionProvider : PermissionsModule.get().getProviders()) { + Message header = Message.raw(permissionProvider.getName()); + Set groupPermissions = permissionProvider.getGroupPermissions(group).stream().map(Message::raw).collect(Collectors.toSet()); + context.sendMessage(MessageFormat.list(header, groupPermissions)); + } + } + } + + private static class PermGroupRemoveCommand extends CommandBase { + @Nonnull + private final RequiredArg groupArg = this.withRequiredArg("group", "server.commands.perm.group.remove.group.desc", ArgTypes.STRING); + @Nonnull + private final RequiredArg> permissionsArg = this.withListRequiredArg( + "permissions", "server.commands.perm.group.remove.permissions.desc", ArgTypes.STRING + ); + + public PermGroupRemoveCommand() { + super("remove", "server.commands.perm.group.remove.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + String group = this.groupArg.get(context); + HashSet permissions = new HashSet<>(this.permissionsArg.get(context)); + PermissionsModule.get().removeGroupPermission(group, permissions); + context.sendMessage(Message.translation("server.commands.perm.permRemovedFromGroup").param("group", group)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/permissions/commands/PermTestCommand.java b/src/com/hypixel/hytale/server/core/permissions/commands/PermTestCommand.java new file mode 100644 index 0000000..f6b8fa3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/permissions/commands/PermTestCommand.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.core.permissions.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import java.util.List; +import javax.annotation.Nonnull; + +public class PermTestCommand extends CommandBase { + @Nonnull + private final RequiredArg> nodesArg = this.withListRequiredArg("nodes", "server.commands.perm.test.nodes.desc", ArgTypes.STRING); + + public PermTestCommand() { + super("test", "server.commands.testperm.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + CommandSender sender = context.sender(); + + for (String node : this.nodesArg.get(context)) { + context.sendMessage( + Message.translation("server.commands.testperm.hasPermission").param("permission", node).param("hasPermission", sender.hasPermission(node)) + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/permissions/commands/PermUserCommand.java b/src/com/hypixel/hytale/server/core/permissions/commands/PermUserCommand.java new file mode 100644 index 0000000..6d8025a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/permissions/commands/PermUserCommand.java @@ -0,0 +1,156 @@ +package com.hypixel.hytale.server.core.permissions.commands; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.permissions.PermissionsModule; +import com.hypixel.hytale.server.core.permissions.provider.PermissionProvider; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class PermUserCommand extends AbstractCommandCollection { + public PermUserCommand() { + super("user", "server.commands.perm.user.desc"); + this.addSubCommand(new PermUserCommand.PermUserListCommand()); + this.addSubCommand(new PermUserCommand.PermUserAddCommand()); + this.addSubCommand(new PermUserCommand.PermUserRemoveCommand()); + this.addSubCommand(new PermUserCommand.PermUserGroupCommand()); + } + + private static class PermUserAddCommand extends CommandBase { + @Nonnull + private final RequiredArg uuidArg = this.withRequiredArg("uuid", "server.commands.perm.user.add.uuid.desc", ArgTypes.UUID); + @Nonnull + private final RequiredArg> permissionsArg = this.withListRequiredArg( + "permissions", "server.commands.perm.user.add.permissions.desc", ArgTypes.STRING + ); + + public PermUserAddCommand() { + super("add", "server.commands.perm.user.add.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + UUID uuid = this.uuidArg.get(context); + HashSet permissions = new HashSet<>(this.permissionsArg.get(context)); + PermissionsModule.get().addUserPermission(uuid, permissions); + context.sendMessage(Message.translation("server.commands.perm.permAddedToUser").param("uuid", uuid.toString())); + } + } + + private static class PermUserGroupCommand extends AbstractCommandCollection { + public PermUserGroupCommand() { + super("group", "server.commands.perm.user.group.desc"); + this.addSubCommand(new PermUserCommand.PermUserGroupCommand.PermUserGroupListCommand()); + this.addSubCommand(new PermUserCommand.PermUserGroupCommand.PermUserGroupAddCommand()); + this.addSubCommand(new PermUserCommand.PermUserGroupCommand.PermUserGroupRemoveCommand()); + } + + private static class PermUserGroupAddCommand extends CommandBase { + @Nonnull + private final RequiredArg uuidArg = this.withRequiredArg("uuid", "server.commands.perm.user.group.add.uuid.desc", ArgTypes.UUID); + @Nonnull + private final RequiredArg groupArg = this.withRequiredArg("group", "server.commands.perm.user.group.add.group.desc", ArgTypes.STRING); + + public PermUserGroupAddCommand() { + super("add", "server.commands.perm.user.group.add.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + UUID uuid = this.uuidArg.get(context); + String group = this.groupArg.get(context); + PermissionsModule.get().addUserToGroup(uuid, group); + context.sendMessage(Message.translation("server.commands.perm.userAddedToGroup").param("uuid", uuid.toString()).param("group", group)); + } + } + + private static class PermUserGroupListCommand extends CommandBase { + @Nonnull + private final RequiredArg uuidArg = this.withRequiredArg("uuid", "server.commands.perm.user.group.list.uuid.desc", ArgTypes.UUID); + + public PermUserGroupListCommand() { + super("list", "server.commands.perm.user.group.list.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + UUID uuid = this.uuidArg.get(context); + + for (PermissionProvider permissionProvider : PermissionsModule.get().getProviders()) { + Message header = Message.raw(permissionProvider.getName()); + Set groups = permissionProvider.getGroupsForUser(uuid).stream().map(Message::raw).collect(Collectors.toSet()); + context.sendMessage(MessageFormat.list(header, groups)); + } + } + } + + private static class PermUserGroupRemoveCommand extends CommandBase { + @Nonnull + private final RequiredArg uuidArg = this.withRequiredArg("uuid", "server.commands.perm.user.group.remove.uuid.desc", ArgTypes.UUID); + @Nonnull + private final RequiredArg groupArg = this.withRequiredArg("group", "server.commands.perm.user.group.remove.group.desc", ArgTypes.STRING); + + public PermUserGroupRemoveCommand() { + super("remove", "server.commands.perm.user.group.remove.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + UUID uuid = this.uuidArg.get(context); + String group = this.groupArg.get(context); + PermissionsModule.get().removeUserFromGroup(uuid, group); + context.sendMessage(Message.translation("server.commands.perm.userRemovedFromGroup").param("uuid", uuid.toString()).param("group", group)); + } + } + } + + private static class PermUserListCommand extends CommandBase { + @Nonnull + private final RequiredArg uuidArg = this.withRequiredArg("uuid", "server.commands.perm.user.list.uuid.desc", ArgTypes.UUID); + + public PermUserListCommand() { + super("list", "server.commands.perm.user.list.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + UUID uuid = this.uuidArg.get(context); + + for (PermissionProvider permissionProvider : PermissionsModule.get().getProviders()) { + Message header = Message.raw(permissionProvider.getName()); + Set userPermissions = permissionProvider.getUserPermissions(uuid).stream().map(Message::raw).collect(Collectors.toSet()); + context.sendMessage(MessageFormat.list(header, userPermissions)); + } + } + } + + private static class PermUserRemoveCommand extends CommandBase { + @Nonnull + private final RequiredArg uuidArg = this.withRequiredArg("uuid", "server.commands.perm.user.remove.uuid.desc", ArgTypes.UUID); + @Nonnull + private final RequiredArg> permissionsArg = this.withListRequiredArg( + "permissions", "server.commands.perm.user.remove.permissions.desc", ArgTypes.STRING + ); + + public PermUserRemoveCommand() { + super("remove", "server.commands.perm.user.remove.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + UUID uuid = this.uuidArg.get(context); + HashSet permissions = new HashSet<>(this.permissionsArg.get(context)); + PermissionsModule.get().removeUserPermission(uuid, permissions); + context.sendMessage(Message.translation("server.commands.perm.permRemovedFromUser").param("uuid", uuid.toString())); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/permissions/commands/op/OpAddCommand.java b/src/com/hypixel/hytale/server/core/permissions/commands/op/OpAddCommand.java new file mode 100644 index 0000000..9816b4d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/permissions/commands/op/OpAddCommand.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.server.core.permissions.commands.op; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.permissions.PermissionsModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class OpAddCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_OP_ADDED = Message.translation("server.commands.op.added"); + @Nonnull + private static final Message MESSAGE_COMMANDS_OP_ADDED_TARGET = Message.translation("server.commands.op.added.target"); + @Nonnull + private static final Message MESSAGE_COMMANDS_OP_ALREADY = Message.translation("server.commands.op.already"); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.op.add.player.desc", ArgTypes.PLAYER_UUID); + + public OpAddCommand() { + super("add", "server.commands.op.add.desc"); + this.requirePermission(HytalePermissions.fromCommand("op.add")); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + UUID uuid = this.playerArg.get(context); + PermissionsModule permissionsModule = PermissionsModule.get(); + String opGroup = "OP"; + String rawInput = context.getInput(this.playerArg)[0]; + Message displayMessage = Message.raw(rawInput).bold(true); + Set groups = permissionsModule.getGroupsForUser(uuid); + if (groups.contains("OP")) { + context.sendMessage(MESSAGE_COMMANDS_OP_ALREADY.param("username", displayMessage)); + } else { + permissionsModule.addUserToGroup(uuid, "OP"); + context.sendMessage(MESSAGE_COMMANDS_OP_ADDED.param("username", displayMessage)); + PlayerRef oppedPlayerRef = Universe.get().getPlayer(uuid); + if (oppedPlayerRef != null) { + oppedPlayerRef.sendMessage(MESSAGE_COMMANDS_OP_ADDED_TARGET); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/permissions/commands/op/OpCommand.java b/src/com/hypixel/hytale/server/core/permissions/commands/op/OpCommand.java new file mode 100644 index 0000000..82d6487 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/permissions/commands/op/OpCommand.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.server.core.permissions.commands.op; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class OpCommand extends AbstractCommandCollection { + public OpCommand() { + super("op", "server.commands.op.desc"); + this.addSubCommand(new OpSelfCommand()); + this.addSubCommand(new OpAddCommand()); + this.addSubCommand(new OpRemoveCommand()); + } + + @Override + protected boolean canGeneratePermission() { + return false; + } +} diff --git a/src/com/hypixel/hytale/server/core/permissions/commands/op/OpRemoveCommand.java b/src/com/hypixel/hytale/server/core/permissions/commands/op/OpRemoveCommand.java new file mode 100644 index 0000000..8de1304 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/permissions/commands/op/OpRemoveCommand.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.server.core.permissions.commands.op; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.permissions.HytalePermissions; +import com.hypixel.hytale.server.core.permissions.PermissionsModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class OpRemoveCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_OP_REMOVED = Message.translation("server.commands.op.removed"); + @Nonnull + private static final Message MESSAGE_COMMANDS_OP_REMOVED_TARGET = Message.translation("server.commands.op.removed.target"); + @Nonnull + private static final Message MESSAGE_COMMANDS_OP_NOT_OP = Message.translation("server.commands.op.notOp"); + @Nonnull + private final RequiredArg playerArg = this.withRequiredArg("player", "server.commands.op.remove.player.desc", ArgTypes.PLAYER_UUID); + + public OpRemoveCommand() { + super("remove", "server.commands.op.remove.desc"); + this.requirePermission(HytalePermissions.fromCommand("op.remove")); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + UUID uuid = this.playerArg.get(context); + PermissionsModule permissionsModule = PermissionsModule.get(); + String opGroup = "OP"; + String rawInput = context.getInput(this.playerArg)[0]; + Message displayMessage = Message.raw(rawInput).bold(true); + Set groups = permissionsModule.getGroupsForUser(uuid); + if (groups.contains("OP")) { + permissionsModule.removeUserFromGroup(uuid, "OP"); + context.sendMessage(MESSAGE_COMMANDS_OP_REMOVED.param("username", displayMessage)); + PlayerRef oppedPlayerRef = Universe.get().getPlayer(uuid); + if (oppedPlayerRef != null) { + oppedPlayerRef.sendMessage(MESSAGE_COMMANDS_OP_REMOVED_TARGET); + } + } else { + context.sendMessage(MESSAGE_COMMANDS_OP_NOT_OP.param("username", displayMessage)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/permissions/commands/op/OpSelfCommand.java b/src/com/hypixel/hytale/server/core/permissions/commands/op/OpSelfCommand.java new file mode 100644 index 0000000..27fbeba --- /dev/null +++ b/src/com/hypixel/hytale/server/core/permissions/commands/op/OpSelfCommand.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.server.core.permissions.commands.op; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Constants; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import com.hypixel.hytale.server.core.permissions.PermissionsModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import java.util.UUID; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class OpSelfCommand extends AbstractPlayerCommand { + private static final Message MESSAGE_COMMANDS_OP_ADDED = Message.translation("server.commands.op.self.added"); + private static final Message MESSAGE_COMMANDS_OP_REMOVED = Message.translation("server.commands.op.self.removed"); + private static final Message MESSAGE_COMMANDS_NON_VANILLA_PERMISSIONS = Message.translation("server.commands.op.self.nonVanillaPermissions"); + private static final Message MESSAGE_COMMANDS_SINGLEPLAYER_OWNER_REQ = Message.translation("server.commands.op.self.singleplayerOwnerReq"); + private static final Message MESSAGE_COMMANDS_MULTIPLAYER_TIP = Message.translation("server.commands.op.self.multiplayerTip"); + + public OpSelfCommand() { + super("self", "server.commands.op.self.desc"); + } + + @Override + protected boolean canGeneratePermission() { + return false; + } + + @Override + protected void execute( + @NonNullDecl CommandContext context, + @NonNullDecl Store store, + @NonNullDecl Ref ref, + @NonNullDecl PlayerRef playerRef, + @NonNullDecl World world + ) { + if (PermissionsModule.get().areProvidersTampered()) { + playerRef.sendMessage(MESSAGE_COMMANDS_NON_VANILLA_PERMISSIONS); + } else if (Constants.SINGLEPLAYER && !SingleplayerModule.isOwner(playerRef)) { + playerRef.sendMessage(MESSAGE_COMMANDS_SINGLEPLAYER_OWNER_REQ); + } else if (!Constants.SINGLEPLAYER && !Constants.ALLOWS_SELF_OP_COMMAND) { + playerRef.sendMessage( + MESSAGE_COMMANDS_MULTIPLAYER_TIP.param("uuidCommand", "uuid").param("permissionFile", "permissions.json").param("launchArg", "--allow-op") + ); + } else { + UUID uuid = playerRef.getUuid(); + PermissionsModule perms = PermissionsModule.get(); + String opGroup = "OP"; + Set groups = perms.getGroupsForUser(uuid); + if (groups.contains("OP")) { + perms.removeUserFromGroup(uuid, "OP"); + context.sendMessage(MESSAGE_COMMANDS_OP_REMOVED); + } else { + perms.addUserToGroup(uuid, "OP"); + context.sendMessage(MESSAGE_COMMANDS_OP_ADDED); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/permissions/provider/HytalePermissionsProvider.java b/src/com/hypixel/hytale/server/core/permissions/provider/HytalePermissionsProvider.java new file mode 100644 index 0000000..bc8a548 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/permissions/provider/HytalePermissionsProvider.java @@ -0,0 +1,316 @@ +package com.hypixel.hytale.server.core.permissions.provider; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.hypixel.hytale.server.core.util.io.BlockingDiskFile; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public final class HytalePermissionsProvider extends BlockingDiskFile implements PermissionProvider { + @Nonnull + public static final String DEFAULT_GROUP = "Default"; + @Nonnull + public static final Set DEFAULT_GROUP_LIST = Set.of("Default"); + @Nonnull + public static final String OP_GROUP = "OP"; + @Nonnull + public static final Map> DEFAULT_GROUPS = Map.ofEntries(Map.entry("OP", Set.of("*")), Map.entry("Default", Set.of())); + @Nonnull + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + @Nonnull + public static final String PERMISSIONS_FILE_PATH = "permissions.json"; + @Nonnull + private final Map> userPermissions = new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map> groupPermissions = new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Map> userGroups = new Object2ObjectOpenHashMap<>(); + + public HytalePermissionsProvider() { + super(Paths.get("permissions.json")); + } + + @Nonnull + @Override + public String getName() { + return "HytalePermissionsProvider"; + } + + @Override + public void addUserPermissions(@Nonnull UUID uuid, @Nonnull Set permissions) { + this.fileLock.writeLock().lock(); + + try { + Set set = this.userPermissions.computeIfAbsent(uuid, k -> new HashSet<>()); + if (set.addAll(permissions)) { + this.syncSave(); + } + } finally { + this.fileLock.writeLock().unlock(); + } + } + + @Override + public void removeUserPermissions(@Nonnull UUID uuid, @Nonnull Set permissions) { + this.fileLock.writeLock().lock(); + + try { + Set set = this.userPermissions.get(uuid); + if (set != null) { + boolean hasChanges = set.removeAll(permissions); + if (set.isEmpty()) { + this.userPermissions.remove(uuid); + } + + if (hasChanges) { + this.syncSave(); + } + } + } finally { + this.fileLock.writeLock().unlock(); + } + } + + @Nonnull + @Override + public Set getUserPermissions(@Nonnull UUID uuid) { + this.fileLock.readLock().lock(); + + Set var3; + try { + Set set = this.userPermissions.get(uuid); + if (set != null) { + return Collections.unmodifiableSet(set); + } + + var3 = Collections.emptySet(); + } finally { + this.fileLock.readLock().unlock(); + } + + return var3; + } + + @Override + public void addGroupPermissions(@Nonnull String group, @Nonnull Set permissions) { + this.fileLock.writeLock().lock(); + + try { + Set set = this.groupPermissions.computeIfAbsent(group, k -> new HashSet<>()); + if (set.addAll(permissions)) { + this.syncSave(); + } + } finally { + this.fileLock.writeLock().unlock(); + } + } + + @Override + public void removeGroupPermissions(@Nonnull String group, @Nonnull Set permissions) { + this.fileLock.writeLock().lock(); + + try { + Set set = this.groupPermissions.get(group); + if (set != null) { + boolean hasChanges = set.removeAll(permissions); + if (set.isEmpty()) { + this.groupPermissions.remove(group); + } + + if (hasChanges) { + this.syncSave(); + } + } + } finally { + this.fileLock.writeLock().unlock(); + } + } + + @Nonnull + @Override + public Set getGroupPermissions(@Nonnull String group) { + this.fileLock.readLock().lock(); + + Set var3; + try { + Set set = this.groupPermissions.get(group); + if (set != null) { + return Collections.unmodifiableSet(set); + } + + var3 = Collections.emptySet(); + } finally { + this.fileLock.readLock().unlock(); + } + + return var3; + } + + @Override + public void addUserToGroup(@Nonnull UUID uuid, @Nonnull String group) { + this.fileLock.writeLock().lock(); + + try { + Set list = this.userGroups.computeIfAbsent(uuid, k -> new HashSet<>()); + if (list.add(group)) { + this.syncSave(); + } + } finally { + this.fileLock.writeLock().unlock(); + } + } + + @Override + public void removeUserFromGroup(@Nonnull UUID uuid, @Nonnull String group) { + this.fileLock.writeLock().lock(); + + try { + Set list = this.userGroups.get(uuid); + if (list != null) { + boolean hasChanges = list.remove(group); + if (list.isEmpty()) { + this.userGroups.remove(uuid); + } + + if (hasChanges) { + this.syncSave(); + } + } + } finally { + this.fileLock.writeLock().unlock(); + } + } + + @Nonnull + @Override + public Set getGroupsForUser(@Nonnull UUID uuid) { + this.fileLock.readLock().lock(); + + Set var3; + try { + Set list = this.userGroups.get(uuid); + if (list != null) { + return Collections.unmodifiableSet(list); + } + + var3 = DEFAULT_GROUP_LIST; + } finally { + this.fileLock.readLock().unlock(); + } + + return var3; + } + + @Override + protected void read(@Nonnull BufferedReader fileReader) throws IOException { + try (JsonReader jsonReader = new JsonReader(fileReader)) { + JsonObject root = JsonParser.parseReader(jsonReader).getAsJsonObject(); + this.userPermissions.clear(); + this.groupPermissions.clear(); + this.userGroups.clear(); + if (root.has("users")) { + JsonObject users = root.getAsJsonObject("users"); + + for (Entry entry : users.entrySet()) { + UUID uuid = UUID.fromString(entry.getKey()); + JsonObject user = entry.getValue().getAsJsonObject(); + if (user.has("permissions")) { + Set set = new HashSet<>(); + this.userPermissions.put(uuid, set); + user.getAsJsonArray("permissions").forEach(e -> set.add(e.getAsString())); + } + + if (user.has("groups")) { + Set list = new HashSet<>(); + this.userGroups.put(uuid, list); + user.getAsJsonArray("groups").forEach(e -> list.add(e.getAsString())); + } + } + } + + if (root.has("groups")) { + JsonObject groups = root.getAsJsonObject("groups"); + + for (Entry entry : groups.entrySet()) { + Set set = new HashSet<>(); + this.groupPermissions.put(entry.getKey(), set); + entry.getValue().getAsJsonArray().forEach(e -> set.add(e.getAsString())); + } + } + + for (Entry> entry : DEFAULT_GROUPS.entrySet()) { + this.groupPermissions.put(entry.getKey(), new HashSet<>(entry.getValue())); + } + } + } + + @Override + protected void write(@Nonnull BufferedWriter fileWriter) throws IOException { + JsonObject root = new JsonObject(); + JsonObject usersObj = new JsonObject(); + + for (Entry> entry : this.userPermissions.entrySet()) { + JsonArray asArray = new JsonArray(); + entry.getValue().forEach(asArray::add); + String memberName = entry.getKey().toString(); + if (!usersObj.has(memberName)) { + usersObj.add(memberName, new JsonObject()); + } + + usersObj.getAsJsonObject(memberName).add("permissions", asArray); + } + + for (Entry> entry : this.userGroups.entrySet()) { + JsonArray asArray = new JsonArray(); + entry.getValue().forEach(asArray::add); + String memberName = entry.getKey().toString(); + if (!usersObj.has(memberName)) { + usersObj.add(memberName, new JsonObject()); + } + + usersObj.getAsJsonObject(memberName).add("groups", asArray); + } + + if (!usersObj.isEmpty()) { + root.add("users", usersObj); + } + + JsonObject groupsObj = new JsonObject(); + + for (Entry> entry : this.groupPermissions.entrySet()) { + JsonArray asArray = new JsonArray(); + entry.getValue().forEach(asArray::add); + groupsObj.add(entry.getKey(), asArray); + } + + if (!groupsObj.isEmpty()) { + root.add("groups", groupsObj); + } + + fileWriter.write(GSON.toJson((JsonElement)root)); + } + + @Override + protected void create(@Nonnull BufferedWriter fileWriter) throws IOException { + try (JsonWriter jsonWriter = new JsonWriter(fileWriter)) { + jsonWriter.beginObject(); + jsonWriter.endObject(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/permissions/provider/PermissionProvider.java b/src/com/hypixel/hytale/server/core/permissions/provider/PermissionProvider.java new file mode 100644 index 0000000..5834bf1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/permissions/provider/PermissionProvider.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.permissions.provider; + +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; + +public interface PermissionProvider { + @Nonnull + String getName(); + + void addUserPermissions(@Nonnull UUID var1, @Nonnull Set var2); + + void removeUserPermissions(@Nonnull UUID var1, @Nonnull Set var2); + + Set getUserPermissions(@Nonnull UUID var1); + + void addGroupPermissions(@Nonnull String var1, @Nonnull Set var2); + + void removeGroupPermissions(@Nonnull String var1, @Nonnull Set var2); + + Set getGroupPermissions(@Nonnull String var1); + + void addUserToGroup(@Nonnull UUID var1, @Nonnull String var2); + + void removeUserFromGroup(@Nonnull UUID var1, @Nonnull String var2); + + Set getGroupsForUser(@Nonnull UUID var1); +} diff --git a/src/com/hypixel/hytale/server/core/plugin/JavaPlugin.java b/src/com/hypixel/hytale/server/core/plugin/JavaPlugin.java new file mode 100644 index 0000000..46422ee --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/JavaPlugin.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.core.plugin; + +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.common.plugin.PluginIdentifier; +import com.hypixel.hytale.server.core.asset.AssetModule; +import java.nio.file.Path; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public abstract class JavaPlugin extends PluginBase { + @Nonnull + private final Path file; + @Nonnull + private final PluginClassLoader classLoader; + + public JavaPlugin(@Nonnull JavaPluginInit init) { + super(init); + this.file = init.getFile(); + this.classLoader = init.getClassLoader(); + this.classLoader.setPlugin(this); + } + + @Nonnull + public Path getFile() { + return this.file; + } + + @Override + protected void start0() { + super.start0(); + if (this.getManifest().includesAssetPack()) { + AssetModule assetModule = AssetModule.get(); + String id = new PluginIdentifier(this.getManifest()).toString(); + AssetPack existing = assetModule.getAssetPack(id); + if (existing != null) { + this.getLogger().at(Level.WARNING).log("Asset pack %s already exists, skipping embedded pack", id); + return; + } + + assetModule.registerPack(id, this.file, this.getManifest()); + } + } + + @Nonnull + public PluginClassLoader getClassLoader() { + return this.classLoader; + } + + @Nonnull + @Override + public final PluginType getType() { + return PluginType.PLUGIN; + } +} diff --git a/src/com/hypixel/hytale/server/core/plugin/JavaPluginInit.java b/src/com/hypixel/hytale/server/core/plugin/JavaPluginInit.java new file mode 100644 index 0000000..23a79f8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/JavaPluginInit.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.core.plugin; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class JavaPluginInit extends PluginInit { + @Nonnull + private final Path file; + @Nonnull + private final PluginClassLoader classLoader; + + public JavaPluginInit(@Nonnull PluginManifest pluginManifest, @Nonnull Path dataDirectory, @Nonnull Path file, @Nonnull PluginClassLoader classLoader) { + super(pluginManifest, dataDirectory); + this.file = file; + this.classLoader = classLoader; + } + + @Nonnull + public Path getFile() { + return this.file; + } + + @Nonnull + public PluginClassLoader getClassLoader() { + return this.classLoader; + } + + @Override + public boolean isInServerClassPath() { + return this.classLoader.isInServerClassPath(); + } +} diff --git a/src/com/hypixel/hytale/server/core/plugin/MissingPluginDependencyException.java b/src/com/hypixel/hytale/server/core/plugin/MissingPluginDependencyException.java new file mode 100644 index 0000000..f93a052 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/MissingPluginDependencyException.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.server.core.plugin; + +import javax.annotation.Nonnull; + +public class MissingPluginDependencyException extends RuntimeException { + public MissingPluginDependencyException(@Nonnull String message) { + super(message); + } +} diff --git a/src/com/hypixel/hytale/server/core/plugin/PluginBase.java b/src/com/hypixel/hytale/server/core/plugin/PluginBase.java new file mode 100644 index 0000000..c61b369 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/PluginBase.java @@ -0,0 +1,315 @@ +package com.hypixel.hytale.server.core.plugin; + +import com.hypixel.hytale.assetstore.JsonAsset; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.lookup.MapKeyMapCodec; +import com.hypixel.hytale.codec.lookup.StringCodecMapCodec; +import com.hypixel.hytale.common.plugin.PluginIdentifier; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.event.EventRegistry; +import com.hypixel.hytale.function.consumer.BooleanConsumer; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.metrics.MetricProvider; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.command.system.CommandOwner; +import com.hypixel.hytale.server.core.command.system.CommandRegistry; +import com.hypixel.hytale.server.core.modules.entity.EntityRegistry; +import com.hypixel.hytale.server.core.plugin.registry.AssetRegistry; +import com.hypixel.hytale.server.core.plugin.registry.CodecMapRegistry; +import com.hypixel.hytale.server.core.plugin.registry.IRegistry; +import com.hypixel.hytale.server.core.plugin.registry.MapKeyMapRegistry; +import com.hypixel.hytale.server.core.registry.ClientFeatureRegistry; +import com.hypixel.hytale.server.core.task.TaskRegistry; +import com.hypixel.hytale.server.core.universe.world.meta.BlockStateRegistry; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.Config; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Function; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class PluginBase implements CommandOwner { + @Nonnull + public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry<>(MetricProvider.maybe(Function.identity())) + .register("Identifier", plugin -> plugin.identifier.toString(), Codec.STRING) + .register("Type", PluginBase::getType, new EnumCodec<>(PluginType.class)) + .register("Manifest", plugin -> plugin.manifest, PluginManifest.CODEC) + .register("State", plugin -> plugin.state, new EnumCodec<>(PluginState.class)) + .register("Builtin", plugin -> plugin instanceof JavaPlugin jp && jp.getClassLoader().isInServerClassPath(), Codec.BOOLEAN); + @Nonnull + private final HytaleLogger logger; + @Nonnull + private final PluginIdentifier identifier; + @Nonnull + private final PluginManifest manifest; + @Nonnull + private final Path dataDirectory; + @Nonnull + private final List> configs = new CopyOnWriteArrayList<>(); + @Nonnull + private PluginState state = PluginState.NONE; + private final String notEnabledString = "The plugin " + this.getIdentifier() + " is not enabled!"; + @Nonnull + private final CopyOnWriteArrayList shutdownTasks = new CopyOnWriteArrayList<>(); + private final ClientFeatureRegistry clientFeatureRegistry = new ClientFeatureRegistry( + this.shutdownTasks, () -> this.state != PluginState.NONE && this.state != PluginState.DISABLED, this.notEnabledString, this + ); + private final CommandRegistry commandRegistry = new CommandRegistry( + this.shutdownTasks, () -> this.state != PluginState.NONE && this.state != PluginState.DISABLED, this.notEnabledString, this + ); + private final EventRegistry eventRegistry = new EventRegistry( + this.shutdownTasks, () -> this.state != PluginState.NONE && this.state != PluginState.DISABLED, this.notEnabledString, HytaleServer.get().getEventBus() + ); + private final BlockStateRegistry blockStateRegistry = new BlockStateRegistry( + this.shutdownTasks, () -> this.state != PluginState.NONE && this.state != PluginState.DISABLED, this.notEnabledString + ); + private final EntityRegistry entityRegistry = new EntityRegistry( + this.shutdownTasks, () -> this.state != PluginState.NONE && this.state != PluginState.DISABLED, this.notEnabledString + ); + private final TaskRegistry taskRegistry = new TaskRegistry( + this.shutdownTasks, () -> this.state != PluginState.NONE && this.state != PluginState.DISABLED, this.notEnabledString + ); + private final ComponentRegistryProxy entityStoreRegistry = new ComponentRegistryProxy<>(this.shutdownTasks, EntityStore.REGISTRY); + private final ComponentRegistryProxy chunkStoreRegistry = new ComponentRegistryProxy<>(this.shutdownTasks, ChunkStore.REGISTRY); + private final AssetRegistry assetRegistry = new AssetRegistry(this.shutdownTasks); + private final Map, IRegistry> codecMapRegistries = new ConcurrentHashMap<>(); + @Nonnull + private final String basePermission; + + public PluginBase(@Nonnull PluginInit init) { + PluginManifest pluginManifest = init.getPluginManifest(); + String pluginName = pluginManifest.getName(); + boolean isPlugin = this.getType() == PluginType.PLUGIN; + this.logger = HytaleLogger.get(pluginName + (isPlugin ? "|P" : "|A")); + this.dataDirectory = init.getDataDirectory(); + this.identifier = new PluginIdentifier(pluginManifest); + this.manifest = pluginManifest; + if (!init.isInServerClassPath()) { + this.logger.setPropagatesSentryToParent(false); + } + + this.basePermission = (pluginManifest.getGroup() + "." + pluginName).toLowerCase(); + } + + @Nonnull + protected final Config withConfig(@Nonnull BuilderCodec configCodec) { + return this.withConfig("config", configCodec); + } + + @Nonnull + protected final Config withConfig(@Nonnull String name, @Nonnull BuilderCodec configCodec) { + if (this.state != PluginState.NONE) { + throw new IllegalStateException("Must be called before setup"); + } else { + Config config = new Config<>(this.dataDirectory, name, configCodec); + this.configs.add(config); + return config; + } + } + + @Nullable + public CompletableFuture preLoad() { + if (this.configs.isEmpty()) { + return null; + } else { + CompletableFuture[] futures = new CompletableFuture[this.configs.size()]; + + for (int i = 0; i < this.configs.size(); i++) { + futures[i] = this.configs.get(i).load(); + } + + return CompletableFuture.allOf(futures); + } + } + + @Nonnull + @Override + public String getName() { + return this.identifier.toString(); + } + + @Nonnull + public HytaleLogger getLogger() { + return this.logger; + } + + @Nonnull + public PluginIdentifier getIdentifier() { + return this.identifier; + } + + @Nonnull + public PluginManifest getManifest() { + return this.manifest; + } + + @Nonnull + public Path getDataDirectory() { + return this.dataDirectory; + } + + @Nonnull + public PluginState getState() { + return this.state; + } + + @Nonnull + public ClientFeatureRegistry getClientFeatureRegistry() { + return this.clientFeatureRegistry; + } + + @Nonnull + public CommandRegistry getCommandRegistry() { + return this.commandRegistry; + } + + @Nonnull + public EventRegistry getEventRegistry() { + return this.eventRegistry; + } + + @Nonnull + public BlockStateRegistry getBlockStateRegistry() { + return this.blockStateRegistry; + } + + @Nonnull + public EntityRegistry getEntityRegistry() { + return this.entityRegistry; + } + + @Nonnull + public TaskRegistry getTaskRegistry() { + return this.taskRegistry; + } + + @Nonnull + public ComponentRegistryProxy getEntityStoreRegistry() { + return this.entityStoreRegistry; + } + + @Nonnull + public ComponentRegistryProxy getChunkStoreRegistry() { + return this.chunkStoreRegistry; + } + + @Nonnull + public AssetRegistry getAssetRegistry() { + return this.assetRegistry; + } + + @Nonnull + public > CodecMapRegistry getCodecRegistry(@Nonnull StringCodecMapCodec mapCodec) { + IRegistry registry = this.codecMapRegistries.computeIfAbsent(mapCodec, v -> new CodecMapRegistry<>(this.shutdownTasks, mapCodec)); + return (CodecMapRegistry)registry; + } + + @Nonnull + public > CodecMapRegistry.Assets getCodecRegistry(@Nonnull AssetCodecMapCodec mapCodec) { + IRegistry registry = this.codecMapRegistries.computeIfAbsent(mapCodec, v -> new CodecMapRegistry.Assets<>(this.shutdownTasks, mapCodec)); + return (CodecMapRegistry.Assets)registry; + } + + @Nonnull + public MapKeyMapRegistry getCodecRegistry(@Nonnull MapKeyMapCodec mapCodec) { + IRegistry registry = this.codecMapRegistries.computeIfAbsent(mapCodec, v -> new MapKeyMapRegistry<>(this.shutdownTasks, mapCodec)); + return (MapKeyMapRegistry)registry; + } + + @Nonnull + public final String getBasePermission() { + return this.basePermission; + } + + public boolean isDisabled() { + return this.state == PluginState.NONE || this.state == PluginState.DISABLED || this.state == PluginState.SHUTDOWN; + } + + public boolean isEnabled() { + return !this.isDisabled(); + } + + protected void setup0() { + if (this.state != PluginState.NONE && this.state != PluginState.DISABLED) { + throw new IllegalArgumentException(String.valueOf(this.state)); + } else { + this.state = PluginState.SETUP; + + try { + this.setup(); + } catch (Throwable var2) { + this.logger.at(Level.SEVERE).withCause(var2).log("Failed to setup plugin %s", this.identifier); + this.state = PluginState.DISABLED; + } + } + } + + protected void setup() { + } + + protected void start0() { + if (this.state != PluginState.SETUP) { + throw new IllegalArgumentException(String.valueOf(this.state)); + } else { + this.state = PluginState.START; + + try { + this.start(); + this.state = PluginState.ENABLED; + } catch (Throwable var2) { + this.logger.at(Level.SEVERE).withCause(var2).log("Failed to start %s", this.identifier); + this.state = PluginState.DISABLED; + } + } + } + + protected void start() { + } + + protected void shutdown0(boolean shutdown) { + this.state = PluginState.SHUTDOWN; + + try { + this.shutdown(); + this.state = PluginState.DISABLED; + } catch (Throwable var3) { + this.logger.at(Level.SEVERE).withCause(var3).log("Failed to shutdown %s", this.identifier); + } + + this.cleanup(shutdown); + } + + protected void shutdown() { + } + + void cleanup(boolean shutdown) { + this.commandRegistry.shutdown(); + this.eventRegistry.shutdown(); + this.clientFeatureRegistry.shutdown(); + this.blockStateRegistry.shutdown(); + this.taskRegistry.shutdown(); + this.entityStoreRegistry.shutdown(); + this.chunkStoreRegistry.shutdown(); + this.codecMapRegistries.forEach((k, v) -> v.shutdown()); + this.assetRegistry.shutdown(); + + for (int i = this.shutdownTasks.size() - 1; i >= 0; i--) { + this.shutdownTasks.get(i).accept(shutdown); + } + } + + @Nonnull + public abstract PluginType getType(); +} diff --git a/src/com/hypixel/hytale/server/core/plugin/PluginClassLoader.java b/src/com/hypixel/hytale/server/core/plugin/PluginClassLoader.java new file mode 100644 index 0000000..5bbc3a2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/PluginClassLoader.java @@ -0,0 +1,178 @@ +package com.hypixel.hytale.server.core.plugin; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Collections; +import java.util.Enumeration; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PluginClassLoader extends URLClassLoader { + public static final String THIRD_PARTY_LOADER_NAME = "ThirdPartyPlugin"; + @Nonnull + private final PluginManager pluginManager; + private final boolean inServerClassPath; + @Nullable + private JavaPlugin plugin; + + public PluginClassLoader(@Nonnull PluginManager pluginManager, boolean inServerClassPath, @Nonnull URL... urls) { + super(inServerClassPath ? "BuiltinPlugin" : "ThirdPartyPlugin", urls, null); + this.inServerClassPath = inServerClassPath; + this.pluginManager = pluginManager; + } + + public boolean isInServerClassPath() { + return this.inServerClassPath; + } + + void setPlugin(@Nonnull JavaPlugin plugin) { + this.plugin = plugin; + } + + @Nonnull + @Override + protected Class loadClass(@Nonnull String name, boolean resolve) throws ClassNotFoundException { + return this.loadClass0(name, true); + } + + @Nonnull + private Class loadClass0(@Nonnull String name, boolean useBridge) throws ClassNotFoundException { + try { + Class loadClass = PluginManager.class.getClassLoader().loadClass(name); + if (loadClass != null) { + return loadClass; + } + } catch (ClassNotFoundException var7) { + } + + try { + Class loadClass = super.loadClass(name, false); + if (loadClass != null) { + return loadClass; + } + } catch (ClassNotFoundException var6) { + } + + if (useBridge) { + if (this.plugin != null) { + try { + Class loadClass = this.pluginManager.getBridgeClassLoader().loadClass0(name, this, this.plugin.getManifest()); + if (loadClass != null) { + return loadClass; + } + } catch (ClassNotFoundException var5) { + } + } else { + try { + Class loadClass = this.pluginManager.getBridgeClassLoader().loadClass0(name, this); + if (loadClass != null) { + return loadClass; + } + } catch (ClassNotFoundException var4) { + } + } + } + + throw new ClassNotFoundException(name); + } + + @Nonnull + public Class loadLocalClass(@Nonnull String name) throws ClassNotFoundException { + synchronized (this.getClassLoadingLock(name)) { + Class loadedClass = this.findLoadedClass(name); + if (loadedClass == null) { + try { + ClassLoader parent = this.getParent(); + if (parent != null) { + loadedClass = parent.loadClass(name); + } + } catch (ClassNotFoundException var6) { + } + + if (loadedClass == null) { + loadedClass = this.loadClass0(name, false); + } + } + + return loadedClass; + } + } + + @Nullable + @Override + public URL getResource(@Nonnull String name) { + URL resource = PluginManager.class.getClassLoader().getResource(name); + if (resource != null) { + return resource; + } else { + resource = super.getResource(name); + if (resource != null) { + return resource; + } else { + PluginManager.PluginBridgeClassLoader bridge = this.pluginManager.getBridgeClassLoader(); + if (this.plugin != null) { + resource = bridge.getResource0(name, this, this.plugin.getManifest()); + } else { + resource = bridge.getResource0(name, this); + } + + return resource; + } + } + } + + @Nonnull + @Override + public Enumeration getResources(@Nonnull String name) throws IOException { + ObjectArrayList results = new ObjectArrayList<>(); + Enumeration serverResources = PluginManager.class.getClassLoader().getResources(name); + + while (serverResources.hasMoreElements()) { + results.add(serverResources.nextElement()); + } + + Enumeration pluginResources = super.getResources(name); + + while (pluginResources.hasMoreElements()) { + results.add(pluginResources.nextElement()); + } + + PluginManager.PluginBridgeClassLoader bridge = this.pluginManager.getBridgeClassLoader(); + Enumeration bridgeResources; + if (this.plugin != null) { + bridgeResources = bridge.getResources0(name, this, this.plugin.getManifest()); + } else { + bridgeResources = bridge.getResources0(name, this); + } + + while (bridgeResources.hasMoreElements()) { + results.add(bridgeResources.nextElement()); + } + + return Collections.enumeration(results); + } + + public static boolean isFromThirdPartyPlugin(@Nullable Throwable throwable) { + while (throwable != null) { + for (StackTraceElement element : throwable.getStackTrace()) { + if ("ThirdPartyPlugin".equals(element.getClassLoaderName())) { + return true; + } + } + + if (throwable.getCause() == throwable) { + break; + } + + throwable = throwable.getCause(); + } + + return false; + } + + static { + registerAsParallelCapable(); + } +} diff --git a/src/com/hypixel/hytale/server/core/plugin/PluginInit.java b/src/com/hypixel/hytale/server/core/plugin/PluginInit.java new file mode 100644 index 0000000..e1ee0f2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/PluginInit.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.plugin; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +public class PluginInit { + @Nonnull + private final PluginManifest pluginManifest; + @Nonnull + private final Path dataDirectory; + + public PluginInit(@Nonnull PluginManifest pluginManifest, @Nonnull Path dataDirectory) { + this.pluginManifest = pluginManifest; + this.dataDirectory = dataDirectory; + } + + @Nonnull + public PluginManifest getPluginManifest() { + return this.pluginManifest; + } + + @Nonnull + public Path getDataDirectory() { + return this.dataDirectory; + } + + public boolean isInServerClassPath() { + return true; + } +} diff --git a/src/com/hypixel/hytale/server/core/plugin/PluginListPageManager.java b/src/com/hypixel/hytale/server/core/plugin/PluginListPageManager.java new file mode 100644 index 0000000..180b45f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/PluginListPageManager.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.server.core.plugin; + +import com.hypixel.hytale.common.plugin.PluginIdentifier; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.plugin.pages.PluginListPage; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import javax.annotation.Nonnull; + +public class PluginListPageManager { + public static PluginListPageManager instance; + @Nonnull + private final List activePages = new CopyOnWriteArrayList<>(); + + public PluginListPageManager() { + instance = this; + } + + @Nonnull + public static PluginListPageManager get() { + return instance; + } + + public void registerPluginListPage(@Nonnull PluginListPage page) { + if (!this.activePages.contains(page)) { + this.activePages.add(page); + } + } + + public void deregisterPluginListPage(@Nonnull PluginListPage page) { + if (this.activePages.contains(page)) { + this.activePages.remove(page); + } + } + + public void notifyPluginChange(@Nonnull Map plugins, @Nonnull PluginIdentifier pluginIdentifier) { + PluginBase plugin = plugins.get(pluginIdentifier); + this.activePages.forEach(page -> page.handlePluginChangeEvent(pluginIdentifier, plugin != null && plugin.isEnabled())); + } + + public static class SessionSettings implements Component { + public boolean descriptiveOnly; + + public SessionSettings() { + this.descriptiveOnly = true; + } + + public SessionSettings(boolean descriptiveOnly) { + this.descriptiveOnly = descriptiveOnly; + } + + @Nonnull + public static ComponentType getComponentType() { + return PluginManager.get().getSessionSettingsComponentType(); + } + + @Nonnull + @Override + public Component clone() { + return new PluginListPageManager.SessionSettings(this.descriptiveOnly); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/plugin/PluginManager.java b/src/com/hypixel/hytale/server/core/plugin/PluginManager.java new file mode 100644 index 0000000..2a78a06 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/PluginManager.java @@ -0,0 +1,1085 @@ +package com.hypixel.hytale.server.core.plugin; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.common.plugin.PluginIdentifier; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.common.semver.Semver; +import com.hypixel.hytale.common.semver.SemverRange; +import com.hypixel.hytale.common.util.java.ManifestUtil; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.HytaleServerConfig; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.ShutdownReason; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.command.system.CommandManager; +import com.hypixel.hytale.server.core.plugin.commands.PluginCommand; +import com.hypixel.hytale.server.core.plugin.event.PluginSetupEvent; +import com.hypixel.hytale.server.core.plugin.pending.PendingLoadJavaPlugin; +import com.hypixel.hytale.server.core.plugin.pending.PendingLoadPlugin; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.JarURLConnection; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PluginManager { + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + public static final Path MODS_PATH = Path.of("mods"); + @Nonnull + public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() + .register( + "Plugins", pluginManager -> pluginManager.getPlugins().toArray(PluginBase[]::new), new ArrayCodec<>(PluginBase.METRICS_REGISTRY, PluginBase[]::new) + ); + private static PluginManager instance; + @Nonnull + private final PluginClassLoader corePluginClassLoader = new PluginClassLoader(this, true); + @Nonnull + private final List corePlugins = new ObjectArrayList<>(); + private final PluginManager.PluginBridgeClassLoader bridgeClassLoader = new PluginManager.PluginBridgeClassLoader(this, PluginManager.class.getClassLoader()); + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final Map plugins = new Object2ObjectLinkedOpenHashMap<>(); + private final Map classLoaders = new ConcurrentHashMap<>(); + private final boolean loadExternalPlugins = true; + @Nonnull + private PluginState state = PluginState.NONE; + @Nullable + private List loadOrder; + @Nullable + private Map loading; + @Nonnull + private final Map availablePlugins = new Object2ObjectLinkedOpenHashMap<>(); + public PluginListPageManager pluginListPageManager; + private ComponentType sessionSettingsComponentType; + + public static PluginManager get() { + return instance; + } + + public PluginManager() { + instance = this; + this.pluginListPageManager = new PluginListPageManager(); + } + + public void registerCorePlugin(@Nonnull PluginManifest builder) { + this.corePlugins.add(new PendingLoadJavaPlugin(null, builder, this.corePluginClassLoader)); + } + + private boolean canLoadOnBoot(@Nonnull PluginManifest manifest) { + PluginIdentifier identifier = new PluginIdentifier(manifest); + HytaleServerConfig.ModConfig modConfig = HytaleServer.get().getConfig().getModConfig().get(identifier); + boolean enabled; + if (modConfig != null && modConfig.getEnabled() != null) { + enabled = modConfig.getEnabled(); + } else { + enabled = !manifest.isDisabledByDefault(); + } + + if (enabled) { + return true; + } else { + LOGGER.at(Level.WARNING).log("Skipping mod %s (Disabled by server config)", identifier); + return false; + } + } + + public void setup() { + if (this.state != PluginState.NONE) { + throw new IllegalStateException("Expected PluginState.NONE but found " + this.state); + } else { + this.state = PluginState.SETUP; + CommandManager.get().registerSystemCommand(new PluginCommand()); + this.sessionSettingsComponentType = EntityStore.REGISTRY + .registerComponent(PluginListPageManager.SessionSettings.class, PluginListPageManager.SessionSettings::new); + HashMap pending = new HashMap<>(); + this.availablePlugins.clear(); + LOGGER.at(Level.INFO).log("Loading pending core plugins!"); + + for (int i = 0; i < this.corePlugins.size(); i++) { + PendingLoadPlugin plugin = this.corePlugins.get(i); + LOGGER.at(Level.INFO).log("- %s", plugin.getIdentifier()); + if (this.canLoadOnBoot(plugin.getManifest())) { + loadPendingPlugin(pending, plugin); + } else { + this.availablePlugins.put(plugin.getIdentifier(), plugin.getManifest()); + } + } + + Path self; + try { + self = Paths.get(PluginManager.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + } catch (URISyntaxException var18) { + throw new RuntimeException(var18); + } + + this.loadPluginsFromDirectory(pending, self.getParent().resolve("builtin"), false, this.availablePlugins); + this.loadPluginsInClasspath(pending, this.availablePlugins); + this.loadPluginsFromDirectory(pending, MODS_PATH, !Options.getOptionSet().has(Options.BARE), this.availablePlugins); + + for (Path modsPath : Options.getOptionSet().valuesOf(Options.MODS_DIRECTORIES)) { + this.loadPluginsFromDirectory(pending, modsPath, false, this.availablePlugins); + } + + this.lock.readLock().lock(); + + try { + this.plugins.keySet().forEach(key -> { + pending.remove(key); + LOGGER.at(Level.WARNING).log("Skipping loading of %s because it is already loaded!", key); + }); + Iterator iterator = pending.values().iterator(); + + while (iterator.hasNext()) { + PendingLoadPlugin pendingLoadPlugin = iterator.next(); + + try { + this.validatePluginDeps(pendingLoadPlugin, pending); + } catch (MissingPluginDependencyException var17) { + LOGGER.at(Level.SEVERE).log(var17.getMessage()); + iterator.remove(); + } + } + } finally { + this.lock.readLock().unlock(); + } + + this.loadOrder = PendingLoadPlugin.calculateLoadOrder(pending); + this.loading = new Object2ObjectOpenHashMap<>(); + pending.forEach((identifier, pendingLoad) -> this.availablePlugins.put(identifier, pendingLoad.getManifest())); + ObjectArrayList var24 = new ObjectArrayList(); + this.lock.writeLock().lock(); + + try { + LOGGER.at(Level.FINE).log("Loading plugins!"); + + for (PendingLoadPlugin pendingLoadPlugin : this.loadOrder) { + LOGGER.at(Level.FINE).log("- %s", pendingLoadPlugin.getIdentifier()); + PluginBase plugin = pendingLoadPlugin.load(); + if (plugin != null) { + this.plugins.put(plugin.getIdentifier(), plugin); + this.loading.put(plugin.getIdentifier(), plugin); + CompletableFuture future = plugin.preLoad(); + if (future != null) { + var24.add(future); + } + } + } + } finally { + this.lock.writeLock().unlock(); + } + + CompletableFuture.allOf(var24.toArray(CompletableFuture[]::new)).join(); + + for (PendingLoadPlugin pendingPlugin : this.loadOrder) { + PluginBase plugin = this.loading.get(pendingPlugin.getIdentifier()); + if (plugin != null && !this.setup(plugin)) { + this.loading.remove(pendingPlugin.getIdentifier()); + } + } + } + } + + public void start() { + if (this.state != PluginState.SETUP) { + throw new IllegalStateException("Expected PluginState.SETUP but found " + this.state); + } else { + this.state = PluginState.START; + + for (PendingLoadPlugin pendingPlugin : this.loadOrder) { + PluginBase plugin = this.loading.get(pendingPlugin.getIdentifier()); + if (plugin != null && !this.start(plugin)) { + this.loading.remove(pendingPlugin.getIdentifier()); + } + } + + this.loadOrder = null; + this.loading = null; + StringBuilder sb = new StringBuilder(); + + for (Entry entry : HytaleServer.get().getConfig().getModConfig().entrySet()) { + PluginIdentifier identifier = entry.getKey(); + HytaleServerConfig.ModConfig modConfig = entry.getValue(); + SemverRange requiredVersion = modConfig.getRequiredVersion(); + if (requiredVersion != null && !this.hasPlugin(identifier, requiredVersion)) { + sb.append(String.format("%s, Version: %s\n", identifier, modConfig)); + return; + } + } + + if (!sb.isEmpty()) { + String msg = "Failed to start server! Missing Mods:\n" + sb; + LOGGER.at(Level.SEVERE).log(msg); + HytaleServer.get().shutdownServer(ShutdownReason.MISSING_REQUIRED_PLUGIN.withMessage(msg)); + } + } + } + + public void shutdown() { + this.state = PluginState.SHUTDOWN; + LOGGER.at(Level.INFO).log("Saving plugins config..."); + this.lock.writeLock().lock(); + + try { + List list = new ObjectArrayList<>(this.plugins.values()); + + for (int i = list.size() - 1; i >= 0; i--) { + PluginBase plugin = list.get(i); + if (plugin.getState() == PluginState.ENABLED) { + LOGGER.at(Level.FINE).log("Shutting down %s %s", plugin.getType().getDisplayName(), plugin.getIdentifier()); + plugin.shutdown0(true); + HytaleServer.get().doneStop(plugin); + LOGGER.at(Level.INFO).log("Shut down plugin %s", plugin.getIdentifier()); + } + } + + this.plugins.clear(); + } finally { + this.lock.writeLock().unlock(); + } + } + + @Nonnull + public PluginState getState() { + return this.state; + } + + @Nonnull + public PluginManager.PluginBridgeClassLoader getBridgeClassLoader() { + return this.bridgeClassLoader; + } + + private void validatePluginDeps(@Nonnull PendingLoadPlugin pendingLoadPlugin, @Nullable Map pending) { + Semver serverVersion = ManifestUtil.getVersion(); + SemverRange serverVersionRange = pendingLoadPlugin.getManifest().getServerVersion(); + if (serverVersionRange != null && serverVersion != null && !serverVersionRange.satisfies(serverVersion)) { + throw new MissingPluginDependencyException( + String.format("Failed to load '%s' because version of server does not satisfy '%s'! ", pendingLoadPlugin.getIdentifier(), serverVersion) + ); + } else { + for (Entry entry : pendingLoadPlugin.getManifest().getDependencies().entrySet()) { + PluginIdentifier identifier = entry.getKey(); + PluginManifest dependency = null; + if (pending != null) { + PendingLoadPlugin pendingDependency = pending.get(identifier); + if (pendingDependency != null) { + dependency = pendingDependency.getManifest(); + } + } + + if (dependency == null) { + PluginBase loadedBase = this.plugins.get(identifier); + if (loadedBase != null) { + dependency = loadedBase.getManifest(); + } + } + + if (dependency == null) { + throw new MissingPluginDependencyException( + String.format("Failed to load '%s' because the dependency '%s' could not be found!", pendingLoadPlugin.getIdentifier(), identifier) + ); + } + + SemverRange expectedVersion = entry.getValue(); + if (!dependency.getVersion().satisfies(expectedVersion)) { + throw new MissingPluginDependencyException( + String.format( + "Failed to load '%s' because version of dependency '%s'(%s) does not satisfy '%s'!", + pendingLoadPlugin.getIdentifier(), + identifier, + dependency.getVersion(), + expectedVersion + ) + ); + } + } + } + } + + private void loadPluginsFromDirectory( + @Nonnull Map pending, + @Nonnull Path path, + boolean create, + @Nonnull Map bootRejectMap + ) { + if (!Files.isDirectory(path)) { + if (create) { + try { + Files.createDirectories(path); + } catch (IOException var9) { + LOGGER.at(Level.SEVERE).withCause(var9).log("Failed to create directory: %s", path); + } + } + } else { + LOGGER.at(Level.INFO).log("Loading pending plugins from directory: " + path); + + try (DirectoryStream stream = Files.newDirectoryStream(path)) { + for (Path file : stream) { + if (Files.isRegularFile(file) && file.getFileName().toString().toLowerCase().endsWith(".jar")) { + PendingLoadJavaPlugin plugin = this.loadPendingJavaPlugin(file); + if (plugin != null) { + assert plugin.getPath() != null; + + LOGGER.at(Level.INFO).log("- %s from path %s", plugin.getIdentifier(), path.relativize(plugin.getPath())); + if (this.canLoadOnBoot(plugin.getManifest())) { + loadPendingPlugin(pending, plugin); + } else { + bootRejectMap.put(plugin.getIdentifier(), plugin.getManifest()); + } + } + } + } + } catch (IOException var12) { + LOGGER.at(Level.SEVERE).withCause(var12).log("Failed to find pending plugins from: %s", path); + } + } + } + + @Nullable + private PendingLoadJavaPlugin loadPendingJavaPlugin(@Nonnull Path file) { + try { + URL url = file.toUri().toURL(); + PluginClassLoader pluginClassLoader = this.classLoaders.computeIfAbsent(file, path -> new PluginClassLoader(this, false, url)); + URL resource = pluginClassLoader.findResource("manifest.json"); + if (resource == null) { + LOGGER.at(Level.SEVERE).log("Failed to load pending plugin from '%s'. Failed to load manifest file!", file.toString()); + return null; + } + + PendingLoadJavaPlugin var11; + try ( + InputStream stream = resource.openStream(); + InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8); + ) { + char[] buffer = RawJsonReader.READ_BUFFER.get(); + RawJsonReader rawJsonReader = new RawJsonReader(reader, buffer); + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + PluginManifest manifest = PluginManifest.CODEC.decodeJson(rawJsonReader, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER); + var11 = new PendingLoadJavaPlugin(file, manifest, pluginClassLoader); + } + + return var11; + } catch (MalformedURLException var16) { + LOGGER.at(Level.SEVERE).withCause(var16).log("Failed to load pending plugin from '%s'. Failed to create URLClassLoader!", file.toString()); + } catch (IOException var17) { + LOGGER.at(Level.SEVERE).withCause(var17).log("Failed to load pending plugin %s. Failed to load manifest file!", file.toString()); + } + + return null; + } + + private void loadPluginsInClasspath( + @Nonnull Map pending, @Nonnull Map rejectedBootList + ) { + LOGGER.at(Level.INFO).log("Loading pending classpath plugins!"); + + try { + URI uri = PluginManager.class.getProtectionDomain().getCodeSource().getLocation().toURI(); + ClassLoader classLoader = PluginManager.class.getClassLoader(); + + try { + for (URL manifestUrl : new HashSet<>(Collections.list(classLoader.getResources("manifest.json")))) { + URLConnection connection = manifestUrl.openConnection(); + + try ( + InputStream stream = connection.getInputStream(); + InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8); + ) { + char[] buffer = RawJsonReader.READ_BUFFER.get(); + RawJsonReader rawJsonReader = new RawJsonReader(reader, buffer); + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + PluginManifest manifest = PluginManifest.CODEC.decodeJson(rawJsonReader, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER); + PendingLoadJavaPlugin plugin; + if (connection instanceof JarURLConnection jarURLConnection) { + URL classpathUrl = jarURLConnection.getJarFileURL(); + Path path = Path.of(classpathUrl.toURI()); + PluginClassLoader pluginClassLoader = this.classLoaders.computeIfAbsent(path, f -> new PluginClassLoader(this, true, classpathUrl)); + plugin = new PendingLoadJavaPlugin(path, manifest, pluginClassLoader); + } else { + URI pluginUri = manifestUrl.toURI().resolve("."); + Path path = Paths.get(pluginUri); + URL classpathUrl = pluginUri.toURL(); + PluginClassLoader pluginClassLoader = this.classLoaders.computeIfAbsent(path, f -> new PluginClassLoader(this, true, classpathUrl)); + plugin = new PendingLoadJavaPlugin(path, manifest, pluginClassLoader); + } + + LOGGER.at(Level.INFO).log("- %s", plugin.getIdentifier()); + if (this.canLoadOnBoot(plugin.getManifest())) { + loadPendingPlugin(pending, plugin); + } else { + rejectedBootList.put(plugin.getIdentifier(), plugin.getManifest()); + } + } + } + + URL manifestsUrl = classLoader.getResource("manifests.json"); + if (manifestsUrl != null) { + try ( + InputStream stream = manifestsUrl.openStream(); + InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8); + ) { + char[] bufferx = RawJsonReader.READ_BUFFER.get(); + RawJsonReader rawJsonReaderx = new RawJsonReader(reader, bufferx); + ExtraInfo extraInfox = ExtraInfo.THREAD_LOCAL.get(); + PluginManifest[] manifests = PluginManifest.ARRAY_CODEC.decodeJson(rawJsonReaderx, extraInfox); + extraInfox.getValidationResults().logOrThrowValidatorExceptions(LOGGER); + URL url = uri.toURL(); + Path path = Paths.get(uri); + PluginClassLoader pluginClassLoader = this.classLoaders.computeIfAbsent(path, f -> new PluginClassLoader(this, true, url)); + + for (PluginManifest manifestx : manifests) { + PendingLoadJavaPlugin pluginx = new PendingLoadJavaPlugin(path, manifestx, pluginClassLoader); + LOGGER.at(Level.INFO).log("- %s", pluginx.getIdentifier()); + if (this.canLoadOnBoot(pluginx.getManifest())) { + loadPendingPlugin(pending, pluginx); + } else { + rejectedBootList.put(pluginx.getIdentifier(), pluginx.getManifest()); + } + } + } + } + } catch (IOException var29) { + LOGGER.at(Level.SEVERE).withCause(var29).log("Failed to load pending classpath plugin from '%s'. Failed to load manifest file!", uri.toString()); + } + } catch (URISyntaxException var30) { + LOGGER.at(Level.SEVERE).withCause(var30).log("Failed to get jar path!"); + } + } + + @Nonnull + public List getPlugins() { + this.lock.readLock().lock(); + + ObjectArrayList var1; + try { + var1 = new ObjectArrayList<>(this.plugins.values()); + } finally { + this.lock.readLock().unlock(); + } + + return var1; + } + + @Nullable + public PluginBase getPlugin(PluginIdentifier identifier) { + this.lock.readLock().lock(); + + PluginBase var2; + try { + var2 = this.plugins.get(identifier); + } finally { + this.lock.readLock().unlock(); + } + + return var2; + } + + public boolean hasPlugin(PluginIdentifier identifier, @Nonnull SemverRange range) { + PluginBase plugin = this.getPlugin(identifier); + return plugin != null && plugin.getManifest().getVersion().satisfies(range); + } + + public boolean reload(@Nonnull PluginIdentifier identifier) { + boolean result = this.unload(identifier) && this.load(identifier); + this.pluginListPageManager.notifyPluginChange(this.plugins, identifier); + return result; + } + + public boolean unload(@Nonnull PluginIdentifier identifier) { + this.lock.writeLock().lock(); + AssetRegistry.ASSET_LOCK.writeLock().lock(); + + boolean var7; + try { + PluginBase plugin = this.plugins.get(identifier); + if (plugin.getState() != PluginState.ENABLED) { + this.pluginListPageManager.notifyPluginChange(this.plugins, identifier); + return false; + } + + plugin.shutdown0(false); + HytaleServer.get().doneStop(plugin); + this.plugins.remove(identifier); + if (plugin instanceof JavaPlugin javaPlugin) { + this.unloadJavaPlugin(javaPlugin); + } + + this.pluginListPageManager.notifyPluginChange(this.plugins, identifier); + var7 = true; + } finally { + AssetRegistry.ASSET_LOCK.writeLock().unlock(); + this.lock.writeLock().unlock(); + } + + return var7; + } + + protected void unloadJavaPlugin(JavaPlugin plugin) { + Path path = plugin.getFile(); + PluginClassLoader classLoader = this.classLoaders.remove(path); + if (classLoader != null) { + try { + classLoader.close(); + } catch (IOException var5) { + LOGGER.at(Level.SEVERE).log("Failed to close Class Loader for JavaPlugin %s", plugin.getIdentifier()); + } + } + } + + public boolean load(@Nonnull PluginIdentifier identifier) { + this.lock.readLock().lock(); + + try { + PluginBase plugin = this.plugins.get(identifier); + if (plugin != null) { + this.pluginListPageManager.notifyPluginChange(this.plugins, identifier); + return false; + } + } finally { + this.lock.readLock().unlock(); + } + + boolean var7 = this.findAndLoadPlugin(identifier); + this.pluginListPageManager.notifyPluginChange(this.plugins, identifier); + return var7; + } + + private boolean findAndLoadPlugin(PluginIdentifier identifier) { + for (PendingLoadPlugin plugin : this.corePlugins) { + if (plugin.getIdentifier().equals(identifier)) { + return this.load(plugin); + } + } + + try { + URI uri = PluginManager.class.getProtectionDomain().getCodeSource().getLocation().toURI(); + ClassLoader classLoader = PluginManager.class.getClassLoader(); + + for (URL manifestUrl : new HashSet<>(Collections.list(classLoader.getResources("manifest.json")))) { + boolean manifest; + try ( + InputStream stream = manifestUrl.openStream(); + InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8); + ) { + char[] buffer = RawJsonReader.READ_BUFFER.get(); + RawJsonReader rawJsonReader = new RawJsonReader(reader, buffer); + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + PluginManifest manifestx = PluginManifest.CODEC.decodeJson(rawJsonReader, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER); + if (!new PluginIdentifier(manifestx).equals(identifier)) { + continue; + } + + PluginClassLoader pluginClassLoader = new PluginClassLoader(this, true, uri.toURL()); + PendingLoadJavaPlugin pluginx = new PendingLoadJavaPlugin(Paths.get(uri), manifestx, pluginClassLoader); + manifest = this.load(pluginx); + } + + return manifest; + } + + URL manifestsUrl = classLoader.getResource("manifests.json"); + if (manifestsUrl != null) { + try ( + InputStream stream = manifestsUrl.openStream(); + InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8); + ) { + char[] buffer = RawJsonReader.READ_BUFFER.get(); + RawJsonReader rawJsonReader = new RawJsonReader(reader, buffer); + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + PluginManifest[] manifests = PluginManifest.ARRAY_CODEC.decodeJson(rawJsonReader, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER); + + for (PluginManifest manifest : manifests) { + if (new PluginIdentifier(manifest).equals(identifier)) { + PluginClassLoader pluginClassLoader = new PluginClassLoader(this, true, uri.toURL()); + PendingLoadJavaPlugin pluginx = new PendingLoadJavaPlugin(Paths.get(uri), manifest, pluginClassLoader); + return this.load(pluginx); + } + } + } + } + + Path path = Paths.get(uri).getParent().resolve("builtin"); + if (Files.exists(path)) { + try (DirectoryStream stream = Files.newDirectoryStream(path)) { + for (Path file : stream) { + if (Files.isRegularFile(file) && file.getFileName().toString().toLowerCase().endsWith(".jar")) { + PluginManifest manifestx = loadManifest(file); + if (manifestx != null && new PluginIdentifier(manifestx).equals(identifier)) { + PendingLoadJavaPlugin pendingLoadJavaPlugin = this.loadPendingJavaPlugin(file); + if (pendingLoadJavaPlugin != null) { + return this.load(pendingLoadJavaPlugin); + } + break; + } + } + } + } catch (IOException var29) { + LOGGER.at(Level.SEVERE).withCause(var29).log("Failed to find plugins!"); + } + } + } catch (URISyntaxException | IOException var30) { + LOGGER.at(Level.SEVERE).withCause(var30).log("Failed to load pending classpath plugin. Failed to load manifest file!"); + } + + Boolean result = this.findPluginInDirectory(identifier, MODS_PATH); + if (result != null) { + return result; + } else { + for (Path modsPath : Options.getOptionSet().valuesOf(Options.MODS_DIRECTORIES)) { + result = this.findPluginInDirectory(identifier, modsPath); + if (result != null) { + return result; + } + } + + return false; + } + } + + @Nullable + private Boolean findPluginInDirectory(@Nonnull PluginIdentifier identifier, @Nonnull Path modsPath) { + if (!Files.isDirectory(modsPath)) { + return null; + } else { + try (DirectoryStream stream = Files.newDirectoryStream(modsPath)) { + for (Path file : stream) { + if (Files.isRegularFile(file) && file.getFileName().toString().toLowerCase().endsWith(".jar")) { + PluginManifest manifest = loadManifest(file); + if (manifest != null && new PluginIdentifier(manifest).equals(identifier)) { + PendingLoadJavaPlugin pendingLoadJavaPlugin = this.loadPendingJavaPlugin(file); + if (pendingLoadJavaPlugin != null) { + return this.load(pendingLoadJavaPlugin); + } + + return false; + } + } + } + } catch (IOException var11) { + LOGGER.at(Level.SEVERE).withCause(var11).log("Failed to find plugins in %s!", modsPath); + } + + return null; + } + } + + @Nullable + private static PluginManifest loadManifest(@Nonnull Path file) { + try { + PluginManifest var8; + try ( + URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{file.toUri().toURL()}, PluginManager.class.getClassLoader()); + InputStream stream = urlClassLoader.findResource("manifest.json").openStream(); + InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8); + ) { + char[] buffer = RawJsonReader.READ_BUFFER.get(); + RawJsonReader rawJsonReader = new RawJsonReader(reader, buffer); + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + PluginManifest manifest = PluginManifest.CODEC.decodeJson(rawJsonReader, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER); + var8 = manifest; + } + + return var8; + } catch (IOException var15) { + LOGGER.at(Level.SEVERE).withCause(var15).log("Failed to load manifest %s.", file); + return null; + } + } + + private boolean load(@Nullable PendingLoadPlugin pendingLoadPlugin) { + if (pendingLoadPlugin == null) { + return false; + } else { + this.validatePluginDeps(pendingLoadPlugin, null); + PluginBase plugin = pendingLoadPlugin.load(); + if (plugin != null) { + this.lock.writeLock().lock(); + + try { + this.plugins.put(plugin.getIdentifier(), plugin); + } finally { + this.lock.writeLock().unlock(); + } + + CompletableFuture preload = plugin.preLoad(); + if (preload == null) { + boolean result = this.setup(plugin) && this.start(plugin); + this.pluginListPageManager.notifyPluginChange(this.plugins, plugin.getIdentifier()); + return result; + } + + preload.thenAccept(v -> { + this.setup(plugin); + this.start(plugin); + this.pluginListPageManager.notifyPluginChange(this.plugins, plugin.getIdentifier()); + }); + } + + this.pluginListPageManager.notifyPluginChange(this.plugins, pendingLoadPlugin.getIdentifier()); + return false; + } + } + + private boolean setup(@Nonnull PluginBase plugin) { + if (plugin.getState() == PluginState.NONE && this.dependenciesMatchState(plugin, PluginState.SETUP, PluginState.SETUP)) { + LOGGER.at(Level.FINE).log("Setting up plugin %s", plugin.getIdentifier()); + boolean prev = AssetStore.DISABLE_DYNAMIC_DEPENDENCIES; + AssetStore.DISABLE_DYNAMIC_DEPENDENCIES = false; + plugin.setup0(); + AssetStore.DISABLE_DYNAMIC_DEPENDENCIES = prev; + AssetModule.get().initPendingStores(); + HytaleServer.get().doneSetup(plugin); + if (plugin.getState() != PluginState.DISABLED) { + IEventDispatcher dispatch = HytaleServer.get() + .getEventBus() + .dispatchFor(PluginSetupEvent.class, (Class)plugin.getClass()); + if (dispatch.hasListener()) { + dispatch.dispatch(new PluginSetupEvent(plugin)); + } + + return true; + } + + plugin.shutdown0(false); + this.plugins.remove(plugin.getIdentifier()); + } else { + plugin.shutdown0(false); + this.plugins.remove(plugin.getIdentifier()); + } + + return false; + } + + private boolean start(@Nonnull PluginBase plugin) { + if (plugin.getState() == PluginState.SETUP && this.dependenciesMatchState(plugin, PluginState.ENABLED, PluginState.START)) { + LOGGER.at(Level.FINE).log("Starting plugin %s", plugin.getIdentifier()); + plugin.start0(); + HytaleServer.get().doneStart(plugin); + if (plugin.getState() != PluginState.DISABLED) { + LOGGER.at(Level.INFO).log("Enabled plugin %s", plugin.getIdentifier()); + return true; + } + + plugin.shutdown0(false); + this.plugins.remove(plugin.getIdentifier()); + } else { + plugin.shutdown0(false); + this.plugins.remove(plugin.getIdentifier()); + } + + return false; + } + + private boolean dependenciesMatchState(PluginBase plugin, PluginState requiredState, PluginState stage) { + for (PluginIdentifier dependencyOnManifest : plugin.getManifest().getDependencies().keySet()) { + PluginBase dependency = this.plugins.get(dependencyOnManifest); + if (dependency == null || dependency.getState() != requiredState) { + LOGGER.at(Level.SEVERE).log(plugin.getName() + " is lacking dependency " + dependencyOnManifest.getName() + " at stage " + stage); + LOGGER.at(Level.SEVERE).log(plugin.getName() + " DISABLED!"); + return false; + } + } + + return true; + } + + private static void loadPendingPlugin(@Nonnull Map pending, @Nonnull PendingLoadPlugin plugin) { + if (pending.putIfAbsent(plugin.getIdentifier(), plugin) != null) { + throw new IllegalArgumentException("Tried to load duplicate plugin"); + } else { + for (PendingLoadPlugin subPlugin : plugin.createSubPendingLoadPlugins()) { + loadPendingPlugin(pending, subPlugin); + } + } + } + + @Nonnull + public Map getAvailablePlugins() { + return this.availablePlugins; + } + + public ComponentType getSessionSettingsComponentType() { + return this.sessionSettingsComponentType; + } + + public static class PluginBridgeClassLoader extends ClassLoader { + private final PluginManager pluginManager; + + public PluginBridgeClassLoader(PluginManager pluginManager, ClassLoader parent) { + super(parent); + this.pluginManager = pluginManager; + } + + @Nonnull + @Override + protected Class loadClass(@Nonnull String name, boolean resolve) throws ClassNotFoundException { + return this.loadClass0(name, null); + } + + @Nonnull + public Class loadClass0(@Nonnull String name, PluginClassLoader pluginClassLoader) throws ClassNotFoundException { + this.pluginManager.lock.readLock().lock(); + + Class var7; + try { + Iterator var3 = this.pluginManager.plugins.entrySet().iterator(); + + Class loadClass; + do { + if (!var3.hasNext()) { + throw new ClassNotFoundException(); + } + + Entry entry = (Entry)var3.next(); + PluginBase pluginBase = entry.getValue(); + loadClass = tryGetClass(name, pluginClassLoader, pluginBase); + } while (loadClass == null); + + var7 = loadClass; + } finally { + this.pluginManager.lock.readLock().unlock(); + } + + return var7; + } + + @Nonnull + public Class loadClass0(@Nonnull String name, PluginClassLoader pluginClassLoader, @Nonnull PluginManifest manifest) throws ClassNotFoundException { + this.pluginManager.lock.readLock().lock(); + + try { + for (PluginIdentifier pluginIdentifier : manifest.getDependencies().keySet()) { + PluginBase pluginBase = this.pluginManager.plugins.get(pluginIdentifier); + Class loadClass = tryGetClass(name, pluginClassLoader, pluginBase); + if (loadClass != null) { + return loadClass; + } + } + + for (PluginIdentifier pluginIdentifierx : manifest.getOptionalDependencies().keySet()) { + if (!manifest.getDependencies().containsKey(pluginIdentifierx)) { + PluginBase pluginBase = this.pluginManager.plugins.get(pluginIdentifierx); + if (pluginBase != null) { + Class loadClass = tryGetClass(name, pluginClassLoader, pluginBase); + if (loadClass != null) { + return loadClass; + } + } + } + } + + for (Entry entry : this.pluginManager.plugins.entrySet()) { + if (!manifest.getDependencies().containsKey(entry.getKey()) && !manifest.getOptionalDependencies().containsKey(entry.getKey())) { + PluginBase pluginBase = entry.getValue(); + Class loadClass = tryGetClass(name, pluginClassLoader, pluginBase); + if (loadClass != null) { + return loadClass; + } + } + } + + throw new ClassNotFoundException(); + } finally { + this.pluginManager.lock.readLock().unlock(); + } + } + + public static Class tryGetClass(@Nonnull String name, PluginClassLoader pluginClassLoader, PluginBase pluginBase) { + if (!(pluginBase instanceof JavaPlugin)) { + return null; + } else { + try { + PluginClassLoader classLoader = ((JavaPlugin)pluginBase).getClassLoader(); + if (classLoader != pluginClassLoader) { + Class loadClass = classLoader.loadLocalClass(name); + if (loadClass != null) { + return loadClass; + } + } + } catch (ClassNotFoundException var5) { + } + + return null; + } + } + + @Nullable + public URL getResource0(@Nonnull String name, @Nullable PluginClassLoader pluginClassLoader) { + this.pluginManager.lock.readLock().lock(); + + URL var6; + try { + Iterator var3 = this.pluginManager.plugins.entrySet().iterator(); + + URL resource; + do { + if (!var3.hasNext()) { + return null; + } + + Entry entry = (Entry)var3.next(); + resource = tryGetResource(name, pluginClassLoader, entry.getValue()); + } while (resource == null); + + var6 = resource; + } finally { + this.pluginManager.lock.readLock().unlock(); + } + + return var6; + } + + @Nullable + public URL getResource0(@Nonnull String name, @Nullable PluginClassLoader pluginClassLoader, @Nonnull PluginManifest manifest) { + this.pluginManager.lock.readLock().lock(); + + try { + for (PluginIdentifier pluginIdentifier : manifest.getDependencies().keySet()) { + URL resource = tryGetResource(name, pluginClassLoader, this.pluginManager.plugins.get(pluginIdentifier)); + if (resource != null) { + return resource; + } + } + + for (PluginIdentifier pluginIdentifierx : manifest.getOptionalDependencies().keySet()) { + if (!manifest.getDependencies().containsKey(pluginIdentifierx)) { + PluginBase pluginBase = this.pluginManager.plugins.get(pluginIdentifierx); + if (pluginBase != null) { + URL resource = tryGetResource(name, pluginClassLoader, pluginBase); + if (resource != null) { + return resource; + } + } + } + } + + for (Entry entry : this.pluginManager.plugins.entrySet()) { + if (!manifest.getDependencies().containsKey(entry.getKey()) && !manifest.getOptionalDependencies().containsKey(entry.getKey())) { + URL resource = tryGetResource(name, pluginClassLoader, entry.getValue()); + if (resource != null) { + return resource; + } + } + } + + return null; + } finally { + this.pluginManager.lock.readLock().unlock(); + } + } + + @Nonnull + public Enumeration getResources0(@Nonnull String name, @Nullable PluginClassLoader pluginClassLoader) { + ObjectArrayList results = new ObjectArrayList<>(); + this.pluginManager.lock.readLock().lock(); + + try { + for (Entry entry : this.pluginManager.plugins.entrySet()) { + URL resource = tryGetResource(name, pluginClassLoader, entry.getValue()); + if (resource != null) { + results.add(resource); + } + } + } finally { + this.pluginManager.lock.readLock().unlock(); + } + + return Collections.enumeration(results); + } + + @Nonnull + public Enumeration getResources0(@Nonnull String name, @Nullable PluginClassLoader pluginClassLoader, @Nonnull PluginManifest manifest) { + ObjectArrayList results = new ObjectArrayList<>(); + this.pluginManager.lock.readLock().lock(); + + try { + for (PluginIdentifier pluginIdentifier : manifest.getDependencies().keySet()) { + URL resource = tryGetResource(name, pluginClassLoader, this.pluginManager.plugins.get(pluginIdentifier)); + if (resource != null) { + results.add(resource); + } + } + + for (PluginIdentifier pluginIdentifierx : manifest.getOptionalDependencies().keySet()) { + if (!manifest.getDependencies().containsKey(pluginIdentifierx)) { + PluginBase pluginBase = this.pluginManager.plugins.get(pluginIdentifierx); + if (pluginBase != null) { + URL resource = tryGetResource(name, pluginClassLoader, pluginBase); + if (resource != null) { + results.add(resource); + } + } + } + } + + for (Entry entry : this.pluginManager.plugins.entrySet()) { + if (!manifest.getDependencies().containsKey(entry.getKey()) && !manifest.getOptionalDependencies().containsKey(entry.getKey())) { + URL resource = tryGetResource(name, pluginClassLoader, entry.getValue()); + if (resource != null) { + results.add(resource); + } + } + } + } finally { + this.pluginManager.lock.readLock().unlock(); + } + + return Collections.enumeration(results); + } + + @Nullable + private static URL tryGetResource(@Nonnull String name, @Nullable PluginClassLoader pluginClassLoader, @Nullable PluginBase pluginBase) { + if (pluginBase instanceof JavaPlugin javaPlugin) { + PluginClassLoader classLoader = javaPlugin.getClassLoader(); + return classLoader != pluginClassLoader ? classLoader.findResource(name) : null; + } else { + return null; + } + } + + static { + registerAsParallelCapable(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/plugin/PluginState.java b/src/com/hypixel/hytale/server/core/plugin/PluginState.java new file mode 100644 index 0000000..201ea8d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/PluginState.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.server.core.plugin; + +public enum PluginState { + NONE, + SETUP, + START, + ENABLED, + SHUTDOWN, + DISABLED; + + private PluginState() { + } +} diff --git a/src/com/hypixel/hytale/server/core/plugin/PluginType.java b/src/com/hypixel/hytale/server/core/plugin/PluginType.java new file mode 100644 index 0000000..481b71f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/PluginType.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.server.core.plugin; + +import javax.annotation.Nonnull; + +public enum PluginType { + PLUGIN("Plugin"); + + @Nonnull + private final String displayName; + + private PluginType(@Nonnull final String displayName) { + this.displayName = displayName; + } + + @Nonnull + public String getDisplayName() { + return this.displayName; + } +} diff --git a/src/com/hypixel/hytale/server/core/plugin/commands/PluginCommand.java b/src/com/hypixel/hytale/server/core/plugin/commands/PluginCommand.java new file mode 100644 index 0000000..0f35e2c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/commands/PluginCommand.java @@ -0,0 +1,265 @@ +package com.hypixel.hytale.server.core.plugin.commands; + +import com.hypixel.hytale.common.plugin.PluginIdentifier; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.HytaleServerConfig; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.SingleArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.plugin.PluginBase; +import com.hypixel.hytale.server.core.plugin.PluginManager; +import com.hypixel.hytale.server.core.plugin.PluginState; +import com.hypixel.hytale.server.core.plugin.pages.PluginListPage; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class PluginCommand extends AbstractCommandCollection { + @Nonnull + private static final SingleArgumentType PLUGIN_IDENTIFIER_ARG_TYPE = new SingleArgumentType( + "server.commands.parsing.argtype.pluginidentifier.name", "server.commands.parsing.argtype.pluginidentifier.usage" + ) { + @Nonnull + public PluginIdentifier parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + try { + return PluginIdentifier.fromString(input); + } catch (Exception var4) { + parseResult.fail( + Message.translation("server.commands.parsing.argtype.pluginidentifier.fail").param("input", input).param("error", var4.getMessage()) + ); + return null; + } + } + }; + + public PluginCommand() { + super("plugin", "server.commands.plugin.desc"); + this.addAliases("plugins", "pl"); + this.addSubCommand(new PluginCommand.PluginListCommand()); + this.addSubCommand(new PluginCommand.PluginLoadCommand()); + this.addSubCommand(new PluginCommand.PluginUnloadCommand()); + this.addSubCommand(new PluginCommand.PluginReloadCommand()); + this.addSubCommand(new PluginCommand.PluginManageCommand()); + } + + private static class PluginListCommand extends CommandBase { + public PluginListCommand() { + super("list", "server.commands.plugin.list.desc"); + this.addAliases("ls"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PluginManager module = PluginManager.get(); + Set plugins = module.getPlugins() + .stream() + .map(PluginBase::getIdentifier) + .map(PluginIdentifier::toString) + .map(Message::raw) + .collect(Collectors.toSet()); + context.sendMessage(MessageFormat.list(Message.translation("server.commands.plugin.plugins"), plugins)); + } + } + + private static class PluginLoadCommand extends CommandBase { + @Nonnull + private final RequiredArg pluginNameArg = this.withRequiredArg( + "pluginName", "server.commands.plugin.load.pluginName.desc", PluginCommand.PLUGIN_IDENTIFIER_ARG_TYPE + ); + @Nonnull + private final FlagArg bootFlag = this.withFlagArg("boot", "server.commands.plugin.load.boot.desc"); + + public PluginLoadCommand() { + super("load", "server.commands.plugin.load.desc"); + this.addAliases("l"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PluginManager module = PluginManager.get(); + PluginIdentifier identifier = this.pluginNameArg.get(context); + PluginBase plugin = module.getPlugin(identifier); + if (identifier != null) { + boolean onlyBootList = this.bootFlag.get(context); + HytaleServerConfig serverConfig = HytaleServer.get().getConfig(); + HytaleServerConfig.ModConfig.setBoot(serverConfig, identifier, true); + if (serverConfig.consumeHasChanged()) { + HytaleServerConfig.save(serverConfig).join(); + } + + context.sendMessage(Message.translation("server.commands.plugin.bootListEnabled").param("id", identifier.toString())); + if (onlyBootList) { + return; + } + } + + if (plugin != null && plugin.getState() != PluginState.DISABLED) { + assert identifier != null; + + switch (plugin.getState()) { + case NONE: + context.sendMessage(Message.translation("server.commands.plugin.failedToLoadInvalidState").param("id", identifier.toString())); + break; + case SETUP: + context.sendMessage(Message.translation("server.commands.plugin.failedToLoadSetup").param("id", identifier.toString())); + break; + case START: + context.sendMessage(Message.translation("server.commands.plugin.failedToLoadStarted").param("id", identifier.toString())); + break; + case ENABLED: + context.sendMessage(Message.translation("server.commands.plugin.failedToLoadAlreadyEnabled").param("id", identifier.toString())); + break; + default: + context.sendMessage(Message.translation("server.commands.plugin.failedPluginState").param("state", plugin.getState().toString())); + } + } else { + context.sendMessage(Message.translation("server.commands.plugin.pluginLoading").param("id", identifier.toString())); + if (module.load(identifier)) { + context.sendMessage(Message.translation("server.commands.plugin.pluginLoaded").param("id", identifier.toString())); + } else { + context.sendMessage(Message.translation("server.commands.plugin.failedToLoadPlugin").param("id", identifier.toString())); + } + } + } + } + + private static class PluginManageCommand extends AbstractPlayerCommand { + public PluginManageCommand() { + super("manage", "server.commands.plugin.manage.desc"); + this.addAliases("m"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getPageManager().openCustomPage(ref, store, new PluginListPage(playerRef)); + } + } + + private static class PluginReloadCommand extends CommandBase { + @Nonnull + private final RequiredArg pluginNameArg = this.withRequiredArg( + "pluginName", "server.commands.plugin.reload.pluginName.desc", PluginCommand.PLUGIN_IDENTIFIER_ARG_TYPE + ); + + public PluginReloadCommand() { + super("reload", "server.commands.plugin.reload.desc"); + this.addAliases("r"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PluginManager module = PluginManager.get(); + PluginIdentifier identifier = this.pluginNameArg.get(context); + PluginBase plugin = module.getPlugin(identifier); + if (plugin != null) { + switch (plugin.getState()) { + case NONE: + context.sendMessage(Message.translation("server.commands.plugin.failedToReloadState").param("id", identifier.toString())); + break; + case SETUP: + context.sendMessage(Message.translation("server.commands.plugin.failedToReloadSetup").param("id", identifier.toString())); + break; + case START: + context.sendMessage(Message.translation("server.commands.plugin.failedToReloadStarted").param("id", identifier.toString())); + break; + case ENABLED: + if (module.reload(identifier)) { + context.sendMessage(Message.translation("server.commands.plugin.pluginReloaded").param("id", identifier.toString())); + } else { + context.sendMessage(Message.translation("server.commands.plugin.failedToReload").param("id", identifier.toString())); + } + break; + case DISABLED: + context.sendMessage(Message.translation("server.commands.plugin.failedToReloadDisabled").param("id", identifier.toString())); + break; + default: + context.sendMessage(Message.translation("server.commands.plugin.failedPluginState").param("state", plugin.getState().toString())); + } + } else { + context.sendMessage(Message.translation("server.commands.plugin.notLoaded").param("id", identifier.toString())); + } + } + } + + private static class PluginUnloadCommand extends CommandBase { + @Nonnull + private final RequiredArg pluginNameArg = this.withRequiredArg( + "pluginName", "server.commands.plugin.unload.pluginName.desc", PluginCommand.PLUGIN_IDENTIFIER_ARG_TYPE + ); + @Nonnull + private final FlagArg bootFlag = this.withFlagArg("boot", "server.commands.plugin.unload.boot.desc"); + + public PluginUnloadCommand() { + super("unload", "server.commands.plugin.unload.desc"); + this.addAliases("u"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + PluginManager module = PluginManager.get(); + PluginIdentifier identifier = this.pluginNameArg.get(context); + PluginBase plugin = module.getPlugin(identifier); + if (identifier != null) { + boolean onlyBootList = this.bootFlag.get(context); + HytaleServerConfig serverConfig = HytaleServer.get().getConfig(); + HytaleServerConfig.ModConfig.setBoot(serverConfig, identifier, false); + if (serverConfig.consumeHasChanged()) { + HytaleServerConfig.save(serverConfig).join(); + } + + context.sendMessage(Message.translation("server.commands.plugin.bootListDisabled").param("id", identifier.toString())); + if (onlyBootList) { + return; + } + } + + if (plugin != null) { + switch (plugin.getState()) { + case NONE: + context.sendMessage(Message.translation("server.commands.plugin.failedToUnloadState").param("id", identifier.toString())); + break; + case SETUP: + context.sendMessage(Message.translation("server.commands.plugin.failedToUnloadSetup").param("id", identifier.toString())); + break; + case START: + context.sendMessage(Message.translation("server.commands.plugin.failedToUnloadStarted").param("id", identifier.toString())); + break; + case ENABLED: + context.sendMessage(Message.translation("server.commands.plugin.pluginUnloading").param("id", identifier.toString())); + if (module.unload(identifier)) { + context.sendMessage(Message.translation("server.commands.plugin.pluginUnloaded").param("id", identifier.toString())); + } else { + context.sendMessage(Message.translation("server.commands.plugin.failedToUnload").param("id", identifier.toString())); + } + break; + case DISABLED: + context.sendMessage(Message.translation("server.commands.plugin.failedToUnloadDisabled").param("id", identifier.toString())); + break; + default: + context.sendMessage(Message.translation("server.commands.plugin.failedPluginState").param("state", plugin.getState().toString())); + } + } else { + context.sendMessage(Message.translation("server.commands.plugin.notLoaded").param("id", identifier.toString())); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/plugin/event/PluginEvent.java b/src/com/hypixel/hytale/server/core/plugin/event/PluginEvent.java new file mode 100644 index 0000000..ae2d0f6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/event/PluginEvent.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.core.plugin.event; + +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.server.core.plugin.PluginBase; +import javax.annotation.Nonnull; + +public abstract class PluginEvent implements IEvent> { + @Nonnull + private final PluginBase plugin; + + public PluginEvent(@Nonnull PluginBase plugin) { + this.plugin = plugin; + } + + @Nonnull + public PluginBase getPlugin() { + return this.plugin; + } + + @Nonnull + @Override + public String toString() { + return "PluginEvent{}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/plugin/event/PluginSetupEvent.java b/src/com/hypixel/hytale/server/core/plugin/event/PluginSetupEvent.java new file mode 100644 index 0000000..185c779 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/event/PluginSetupEvent.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.core.plugin.event; + +import com.hypixel.hytale.server.core.plugin.PluginBase; +import javax.annotation.Nonnull; + +public class PluginSetupEvent extends PluginEvent { + public PluginSetupEvent(@Nonnull PluginBase plugin) { + super(plugin); + } +} diff --git a/src/com/hypixel/hytale/server/core/plugin/pages/PluginListPage.java b/src/com/hypixel/hytale/server/core/plugin/pages/PluginListPage.java new file mode 100644 index 0000000..cd542c2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/pages/PluginListPage.java @@ -0,0 +1,261 @@ +package com.hypixel.hytale.server.core.plugin.pages; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.plugin.PluginIdentifier; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.server.core.command.system.CommandManager; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.plugin.PluginBase; +import com.hypixel.hytale.server.core.plugin.PluginListPageManager; +import com.hypixel.hytale.server.core.plugin.PluginManager; +import com.hypixel.hytale.server.core.ui.Value; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PluginListPage extends InteractiveCustomUIPage { + private static final Value BUTTON_LABEL_STYLE = Value.ref("Pages/PluginListButton.ui", "LabelStyle"); + private static final Value BUTTON_LABEL_STYLE_SELECTED = Value.ref("Pages/PluginListButton.ui", "SelectedLabelStyle"); + @Nullable + private PluginListPage.PluginDetails selectedPlugin; + @Nonnull + private final ObjectList availablePlugins = new ObjectArrayList<>(); + @Nonnull + private final ObjectList visiblePlugins = new ObjectArrayList<>(); + @Nullable + private PluginListPageManager.SessionSettings playerSessionSettings; + + public PluginListPage(@Nonnull PlayerRef playerRef) { + super(playerRef, CustomPageLifetime.CanDismiss, PluginListPage.PluginListPageEventData.CODEC); + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + PluginListPageManager pageManager = PluginListPageManager.get(); + pageManager.registerPluginListPage(this); + this.playerSessionSettings = store.ensureAndGetComponent(ref, PluginListPageManager.SessionSettings.getComponentType()); + commandBuilder.append("Pages/PluginListPage.ui"); + this.buildPluginList(commandBuilder, eventBuilder); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, "#DescriptiveOnlyOption #CheckBox", new EventData().append("Option", "DescriptiveOnly") + ); + if (!this.visiblePlugins.isEmpty()) { + this.selectPlugin(this.visiblePlugins.getFirst().identifier.toString(), commandBuilder); + } + + commandBuilder.set("#DescriptiveOnlyOption #CheckBox.Value", this.playerSessionSettings.descriptiveOnly); + } + + public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull PluginListPage.PluginListPageEventData data) { + assert this.playerSessionSettings != null; + + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + if (data.plugin != null) { + String var6 = data.type; + switch (var6) { + case "Select": + this.selectPlugin(data.plugin, commandBuilder); + break; + case "Toggle": + this.checkBoxChanged(data.plugin, commandBuilder); + } + + this.sendUpdate(commandBuilder, null, false); + } else if (data.option != null) { + String var8 = data.option; + byte var9 = -1; + switch (var8.hashCode()) { + case 180783736: + if (var8.equals("DescriptiveOnly")) { + var9 = 0; + } + default: + switch (var9) { + case 0: + this.playerSessionSettings.descriptiveOnly = !this.playerSessionSettings.descriptiveOnly; + this.buildPluginList(commandBuilder, eventBuilder); + if (!this.visiblePlugins.isEmpty()) { + this.selectPlugin(this.visiblePlugins.getFirst().identifier.toString(), commandBuilder); + } + default: + this.sendUpdate(commandBuilder, eventBuilder, false); + } + } + } + } + + @Override + public void onDismiss(@Nonnull Ref ref, @Nonnull Store store) { + PluginListPageManager.get().deregisterPluginListPage(this); + } + + private void buildPluginList(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) { + assert this.playerSessionSettings != null; + + commandBuilder.clear("#PluginList"); + this.visiblePlugins.clear(); + this.availablePlugins.clear(); + PluginManager module = PluginManager.get(); + Map loadedPlugins = module.getAvailablePlugins(); + loadedPlugins.forEach((identifierx, manifest) -> this.availablePlugins.add(new PluginListPage.PluginDetails(manifest, identifierx))); + int i = 0; + + for (int bound = this.availablePlugins.size(); i < bound; i++) { + PluginListPage.PluginDetails plugin = this.availablePlugins.get(i); + String desc = plugin.manifest.getDescription(); + if (!this.playerSessionSettings.descriptiveOnly || desc != null && !desc.isEmpty()) { + this.visiblePlugins.add(plugin); + } + } + + i = 0; + + for (int boundx = this.visiblePlugins.size(); i < boundx; i++) { + PluginIdentifier identifier = this.visiblePlugins.get(i).identifier; + String id = identifier.toString(); + boolean enabled = false; + PluginBase loadedPlugin = module.getPlugin(identifier); + if (loadedPlugin != null) { + enabled = loadedPlugin.isEnabled(); + } + + String selector = "#PluginList[" + i + "]"; + commandBuilder.append("#PluginList", "Pages/PluginListButton.ui"); + commandBuilder.set(selector + " #Button.Text", id); + commandBuilder.set(selector + " #CheckBox.Value", enabled); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, selector + " #Button", new EventData().append("Plugin", id).append("Type", "Select"), false + ); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, selector + " #CheckBox", new EventData().append("Plugin", id).append("Type", "Toggle"), false + ); + } + } + + private void selectPlugin(@Nonnull String playerSelectedPlugin, @Nonnull UICommandBuilder commandBuilder) { + PluginListPage.PluginDetails nextSelectedPlugin = null; + + for (PluginListPage.PluginDetails plugin : this.visiblePlugins) { + if (playerSelectedPlugin.equals(plugin.identifier.toString())) { + nextSelectedPlugin = plugin; + break; + } + } + + if (nextSelectedPlugin != null) { + if (this.selectedPlugin != null && this.visiblePlugins.contains(this.selectedPlugin)) { + commandBuilder.set("#PluginList[" + this.visiblePlugins.indexOf(this.selectedPlugin) + "] #Button.Style", BUTTON_LABEL_STYLE); + } + + commandBuilder.set("#PluginList[" + this.visiblePlugins.indexOf(nextSelectedPlugin) + "] #Button.Style", BUTTON_LABEL_STYLE_SELECTED); + commandBuilder.set("#PluginName.Text", nextSelectedPlugin.manifest.getName()); + commandBuilder.set("#PluginIdentifier.Text", nextSelectedPlugin.identifier.toString()); + if (nextSelectedPlugin.manifest.getVersion() != null) { + commandBuilder.set("#PluginVersion.Text", nextSelectedPlugin.manifest.getVersion().toString()); + } else { + commandBuilder.set("#PluginVersion.Text", ""); + } + + if (nextSelectedPlugin.manifest.getDescription() != null) { + commandBuilder.set("#PluginDescription.Text", nextSelectedPlugin.manifest.getDescription()); + } else { + commandBuilder.set("#PluginDescription.Text", ""); + } + + this.selectedPlugin = nextSelectedPlugin; + } + } + + private void checkBoxChanged(@Nonnull String pluginName, @Nonnull UICommandBuilder commandBuilder) { + PluginListPage.PluginDetails changedPlugin = null; + + for (PluginListPage.PluginDetails plugin : this.visiblePlugins) { + if (pluginName.equals(plugin.identifier.toString())) { + changedPlugin = plugin; + break; + } + } + + if (changedPlugin != null) { + PluginManager module = PluginManager.get(); + PluginBase activePlugin = module.getPlugin(changedPlugin.identifier); + CommandManager commandManager = CommandManager.get(); + if (activePlugin != null && activePlugin.isEnabled()) { + commandManager.handleCommand(this.playerRef, "plugin unload " + changedPlugin.identifier); + } else { + commandManager.handleCommand(this.playerRef, "plugin load " + changedPlugin.identifier); + } + } + } + + public void handlePluginChangeEvent(@Nonnull PluginIdentifier plugin, boolean activeState) { + UICommandBuilder commandBuilder = new UICommandBuilder(); + UIEventBuilder eventBuilder = new UIEventBuilder(); + PluginListPage.PluginDetails key = null; + int i = 0; + + for (int bound = this.visiblePlugins.size(); i < bound; i++) { + PluginListPage.PluginDetails details = this.visiblePlugins.get(i); + if (details.identifier.equals(plugin)) { + key = details; + break; + } + } + + if (key != null) { + String selector = "#PluginList[" + this.visiblePlugins.indexOf(key) + "]"; + commandBuilder.set(selector + " #CheckBox.Value", activeState); + this.sendUpdate(commandBuilder, eventBuilder, false); + } + } + + private static class PluginDetails { + @Nonnull + private final PluginManifest manifest; + @Nonnull + private final PluginIdentifier identifier; + + public PluginDetails(@Nonnull PluginManifest manifest, @Nonnull PluginIdentifier identifier) { + this.identifier = identifier; + this.manifest = manifest; + } + } + + public static class PluginListPageEventData { + static final String KEY_PLUGIN = "Plugin"; + static final String TYPE_KEY = "Type"; + static final String KEY_OPTION = "Option"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + PluginListPage.PluginListPageEventData.class, PluginListPage.PluginListPageEventData::new + ) + .append(new KeyedCodec<>("Plugin", Codec.STRING), (entry, s) -> entry.plugin = s, entry -> entry.plugin) + .add() + .append(new KeyedCodec<>("Option", Codec.STRING), (entry, s) -> entry.option = s, entry -> entry.option) + .add() + .append(new KeyedCodec<>("Type", Codec.STRING), (entry, s) -> entry.type = s, entry -> entry.type) + .add() + .build(); + private String plugin; + private String option; + private String type; + + public PluginListPageEventData() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/plugin/pending/PendingLoadJavaPlugin.java b/src/com/hypixel/hytale/server/core/plugin/pending/PendingLoadJavaPlugin.java new file mode 100644 index 0000000..6bb39ec --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/pending/PendingLoadJavaPlugin.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.server.core.plugin.pending; + +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.plugin.PluginClassLoader; +import com.hypixel.hytale.server.core.plugin.PluginManager; +import java.lang.reflect.Constructor; +import java.nio.file.Path; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PendingLoadJavaPlugin extends PendingLoadPlugin { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private final PluginClassLoader urlClassLoader; + + public PendingLoadJavaPlugin(@Nullable Path path, @Nonnull PluginManifest manifest, @Nonnull PluginClassLoader urlClassLoader) { + super(path, manifest); + this.urlClassLoader = urlClassLoader; + } + + @Nonnull + @Override + public PendingLoadPlugin createSubPendingLoadPlugin(@Nonnull PluginManifest manifest) { + return new PendingLoadJavaPlugin(this.getPath(), manifest, this.urlClassLoader); + } + + @Override + public boolean isInServerClassPath() { + return this.urlClassLoader.isInServerClassPath(); + } + + @Nullable + public JavaPlugin load() { + try { + PluginManifest manifest = this.getManifest(); + Class mainClass = this.urlClassLoader.loadLocalClass(manifest.getMain()); + if (JavaPlugin.class.isAssignableFrom(mainClass)) { + Constructor constructor = mainClass.getConstructor(JavaPluginInit.class); + Path dataDirectory = PluginManager.MODS_PATH.resolve(manifest.getGroup() + "_" + manifest.getName()); + JavaPluginInit init = new JavaPluginInit(manifest, dataDirectory, this.getPath(), this.urlClassLoader); + return (JavaPlugin)constructor.newInstance(init); + } + + throw new ClassCastException(manifest.getMain() + " does not extend JavaPlugin"); + } catch (ClassNotFoundException var6) { + LOGGER.at(Level.SEVERE).withCause(var6).log("Failed to load plugin %s. Failed to find main class!", this.getPath()); + } catch (NoSuchMethodException var7) { + LOGGER.at(Level.SEVERE).withCause(var7).log("Failed to load plugin %s. Requires default constructor!", this.getPath()); + } catch (Throwable var8) { + LOGGER.at(Level.SEVERE).withCause(var8).log("Failed to load plugin %s", this.getPath()); + } + + return null; + } + + @Nonnull + @Override + public String toString() { + return "PendingLoadJavaPlugin{" + super.toString() + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/plugin/pending/PendingLoadPlugin.java b/src/com/hypixel/hytale/server/core/plugin/pending/PendingLoadPlugin.java new file mode 100644 index 0000000..b043a09 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/pending/PendingLoadPlugin.java @@ -0,0 +1,232 @@ +package com.hypixel.hytale.server.core.plugin.pending; + +import com.hypixel.hytale.common.plugin.PluginIdentifier; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.server.core.plugin.PluginBase; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class PendingLoadPlugin { + @Nonnull + private final PluginIdentifier identifier; + @Nonnull + private final PluginManifest manifest; + @Nullable + private final Path path; + + PendingLoadPlugin(@Nullable Path path, @Nonnull PluginManifest manifest) { + this.path = path; + this.identifier = new PluginIdentifier(manifest); + this.manifest = manifest; + } + + @Nonnull + public PluginIdentifier getIdentifier() { + return this.identifier; + } + + @Nonnull + public PluginManifest getManifest() { + return this.manifest; + } + + @Nullable + public Path getPath() { + return this.path; + } + + public abstract PendingLoadPlugin createSubPendingLoadPlugin(PluginManifest var1); + + @Nullable + public abstract PluginBase load(); + + @Nonnull + public List createSubPendingLoadPlugins() { + List subPlugins = this.manifest.getSubPlugins(); + if (subPlugins.isEmpty()) { + return Collections.emptyList(); + } else { + ObjectArrayList plugins = new ObjectArrayList<>(subPlugins.size()); + + for (PluginManifest subManifest : subPlugins) { + subManifest.inherit(this.manifest); + plugins.add(this.createSubPendingLoadPlugin(subManifest)); + } + + return plugins; + } + } + + public boolean dependsOn(PluginIdentifier identifier) { + return this.manifest.getDependencies().containsKey(identifier) || this.manifest.getOptionalDependencies().containsKey(identifier); + } + + public abstract boolean isInServerClassPath(); + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + PendingLoadPlugin that = (PendingLoadPlugin)o; + if (!this.identifier.equals(that.identifier)) { + return false; + } else { + return !this.manifest.equals(that.manifest) ? false : Objects.equals(this.path, that.path); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.identifier.hashCode(); + result = 31 * result + this.manifest.hashCode(); + return 31 * result + (this.path != null ? this.path.hashCode() : 0); + } + + @Nonnull + @Override + public String toString() { + return "PendingLoadPlugin{identifier=" + this.identifier + ", manifest=" + this.manifest + ", path=" + this.path + "}"; + } + + @Nonnull + public static List calculateLoadOrder(@Nonnull Map pending) { + HashMap nodes = new HashMap<>(pending.size()); + + for (Entry entry : pending.entrySet()) { + nodes.put(entry.getKey(), new PendingLoadPlugin.EntryNode(entry.getValue())); + } + + HashSet classpathPlugins = new HashSet<>(); + + for (Entry entry : pending.entrySet()) { + if (entry.getValue().isInServerClassPath()) { + classpathPlugins.add(entry.getKey()); + } + } + + HashMap> missingDependencies = new HashMap<>(); + + for (PendingLoadPlugin.EntryNode node : nodes.values()) { + PluginManifest manifest = node.plugin.manifest; + + for (PluginIdentifier depId : manifest.getDependencies().keySet()) { + if (nodes.containsKey(depId)) { + node.edge.add(depId); + } else { + missingDependencies.computeIfAbsent(node.plugin.identifier, k -> new HashSet<>()).add(depId); + } + } + + for (PluginIdentifier identifier : manifest.getOptionalDependencies().keySet()) { + PendingLoadPlugin.EntryNode dep = nodes.get(identifier); + if (dep != null) { + node.edge.add(identifier); + } + } + + if (!node.plugin.isInServerClassPath()) { + node.edge.addAll(classpathPlugins); + } + } + + HashMap> missingLoadBefore = new HashMap<>(); + + for (Entry entryx : pending.entrySet()) { + PluginManifest manifest = entryx.getValue().manifest; + + for (PluginIdentifier targetId : manifest.getLoadBefore().keySet()) { + PendingLoadPlugin.EntryNode targetNode = nodes.get(targetId); + if (targetNode != null) { + targetNode.edge.add(entryx.getKey()); + } else { + missingLoadBefore.computeIfAbsent(entryx.getKey(), k -> new HashSet<>()).add(targetId); + } + } + } + + if (missingDependencies.isEmpty() && missingLoadBefore.isEmpty()) { + ObjectArrayList loadOrder = new ObjectArrayList<>(nodes.size()); + + while (!nodes.isEmpty()) { + boolean didWork = false; + Iterator> iterator = nodes.entrySet().iterator(); + + while (iterator.hasNext()) { + Entry entryx = iterator.next(); + PendingLoadPlugin.EntryNode node = entryx.getValue(); + if (node.edge.isEmpty()) { + didWork = true; + iterator.remove(); + loadOrder.add(node.plugin); + PluginIdentifier identifierx = entryx.getKey(); + + for (PendingLoadPlugin.EntryNode otherNode : nodes.values()) { + otherNode.edge.remove(identifierx); + } + } + } + + if (!didWork) { + StringBuilder sb = new StringBuilder("Found cyclic dependency between plugins:\n"); + + for (Entry entryx : nodes.entrySet()) { + sb.append(" ").append(entryx.getKey()).append(" waiting on: ").append(entryx.getValue().edge).append("\n"); + } + + throw new IllegalArgumentException(sb.toString()); + } + } + + return loadOrder; + } else { + StringBuilder sb = new StringBuilder(); + if (!missingDependencies.isEmpty()) { + sb.append("Missing required dependencies:\n"); + + for (Entry> entryx : missingDependencies.entrySet()) { + sb.append(" ").append(entryx.getKey()).append(" requires: ").append(entryx.getValue()).append("\n"); + } + } + + if (!missingLoadBefore.isEmpty()) { + sb.append("Missing loadBefore targets:\n"); + + for (Entry> entryx : missingLoadBefore.entrySet()) { + sb.append(" ").append(entryx.getKey()).append(" loadBefore: ").append(entryx.getValue()).append("\n"); + } + } + + throw new IllegalArgumentException(sb.toString()); + } + } + + private static final class EntryNode { + private final Set edge = new HashSet<>(); + private final PendingLoadPlugin plugin; + + private EntryNode(PendingLoadPlugin plugin) { + this.plugin = plugin; + } + + @Nonnull + @Override + public String toString() { + return "EntryNode{plugin=" + this.plugin + ", dependencies=" + this.edge + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/plugin/registry/AssetRegistry.java b/src/com/hypixel/hytale/server/core/plugin/registry/AssetRegistry.java new file mode 100644 index 0000000..b6a1b68 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/registry/AssetRegistry.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.core.plugin.registry; + +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.function.consumer.BooleanConsumer; +import java.util.List; +import javax.annotation.Nonnull; + +public class AssetRegistry { + protected final List unregister; + + public AssetRegistry(List unregister) { + this.unregister = unregister; + } + + @Nonnull + public , M extends AssetMap, S extends AssetStore> AssetRegistry register(@Nonnull S assetStore) { + com.hypixel.hytale.assetstore.AssetRegistry.register(assetStore); + this.unregister.add(shutdown -> com.hypixel.hytale.assetstore.AssetRegistry.unregister(assetStore)); + return this; + } + + public void shutdown() { + } +} diff --git a/src/com/hypixel/hytale/server/core/plugin/registry/CodecMapRegistry.java b/src/com/hypixel/hytale/server/core/plugin/registry/CodecMapRegistry.java new file mode 100644 index 0000000..2e2d58f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/registry/CodecMapRegistry.java @@ -0,0 +1,91 @@ +package com.hypixel.hytale.server.core.plugin.registry; + +import com.hypixel.hytale.assetstore.JsonAsset; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.Priority; +import com.hypixel.hytale.codec.lookup.StringCodecMapCodec; +import com.hypixel.hytale.function.consumer.BooleanConsumer; +import java.util.List; +import javax.annotation.Nonnull; + +public class CodecMapRegistry> implements IRegistry { + protected final StringCodecMapCodec mapCodec; + protected final List unregister; + + public CodecMapRegistry(List unregister, StringCodecMapCodec mapCodec) { + this.unregister = unregister; + this.mapCodec = mapCodec; + } + + @Nonnull + public CodecMapRegistry register(String id, Class aClass, C codec) { + this.mapCodec.register(id, aClass, codec); + this.unregister.add(shutdown -> { + com.hypixel.hytale.assetstore.AssetRegistry.ASSET_LOCK.writeLock().lock(); + + try { + this.mapCodec.remove(aClass); + } finally { + com.hypixel.hytale.assetstore.AssetRegistry.ASSET_LOCK.writeLock().unlock(); + } + }); + return this; + } + + @Nonnull + public CodecMapRegistry register(@Nonnull Priority priority, @Nonnull String id, Class aClass, C codec) { + this.mapCodec.register(priority, id, aClass, codec); + this.unregister.add(shutdown -> { + com.hypixel.hytale.assetstore.AssetRegistry.ASSET_LOCK.writeLock().lock(); + + try { + this.mapCodec.remove(aClass); + } finally { + com.hypixel.hytale.assetstore.AssetRegistry.ASSET_LOCK.writeLock().unlock(); + } + }); + return this; + } + + @Override + public void shutdown() { + } + + public static class Assets, C extends Codec> extends CodecMapRegistry { + public Assets(List unregister, StringCodecMapCodec mapCodec) { + super(unregister, mapCodec); + } + + @Nonnull + public CodecMapRegistry.Assets register(@Nonnull String id, Class aClass, BuilderCodec codec) { + ((AssetCodecMapCodec)this.mapCodec).register(id, aClass, codec); + this.unregister.add(shutdown -> { + com.hypixel.hytale.assetstore.AssetRegistry.ASSET_LOCK.writeLock().lock(); + + try { + this.mapCodec.remove(aClass); + } finally { + com.hypixel.hytale.assetstore.AssetRegistry.ASSET_LOCK.writeLock().unlock(); + } + }); + return this; + } + + @Nonnull + public CodecMapRegistry.Assets register(@Nonnull Priority priority, @Nonnull String id, Class aClass, BuilderCodec codec) { + ((AssetCodecMapCodec)this.mapCodec).register(priority, id, aClass, codec); + this.unregister.add(shutdown -> { + com.hypixel.hytale.assetstore.AssetRegistry.ASSET_LOCK.writeLock().lock(); + + try { + this.mapCodec.remove(aClass); + } finally { + com.hypixel.hytale.assetstore.AssetRegistry.ASSET_LOCK.writeLock().unlock(); + } + }); + return this; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/plugin/registry/IRegistry.java b/src/com/hypixel/hytale/server/core/plugin/registry/IRegistry.java new file mode 100644 index 0000000..161086a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/registry/IRegistry.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.server.core.plugin.registry; + +public interface IRegistry { + void shutdown(); +} diff --git a/src/com/hypixel/hytale/server/core/plugin/registry/MapKeyMapRegistry.java b/src/com/hypixel/hytale/server/core/plugin/registry/MapKeyMapRegistry.java new file mode 100644 index 0000000..26e1dd6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/plugin/registry/MapKeyMapRegistry.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.server.core.plugin.registry; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.lookup.MapKeyMapCodec; +import com.hypixel.hytale.function.consumer.BooleanConsumer; +import java.util.List; +import javax.annotation.Nonnull; + +public class MapKeyMapRegistry implements IRegistry { + protected final MapKeyMapCodec mapCodec; + protected final List unregister; + + public MapKeyMapRegistry(List unregister, MapKeyMapCodec mapCodec) { + this.unregister = unregister; + this.mapCodec = mapCodec; + } + + @Nonnull + public MapKeyMapRegistry register(@Nonnull Class tClass, @Nonnull String id, @Nonnull Codec codec) { + this.mapCodec.register(tClass, id, codec); + this.unregister.add(shutdown -> { + if (!shutdown) { + com.hypixel.hytale.assetstore.AssetRegistry.ASSET_LOCK.writeLock().lock(); + + try { + this.mapCodec.unregister(tClass); + } finally { + com.hypixel.hytale.assetstore.AssetRegistry.ASSET_LOCK.writeLock().unlock(); + } + } + }); + return this; + } + + @Override + public void shutdown() { + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/PrefabCopyableComponent.java b/src/com/hypixel/hytale/server/core/prefab/PrefabCopyableComponent.java new file mode 100644 index 0000000..8ba431c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/PrefabCopyableComponent.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.core.prefab; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public class PrefabCopyableComponent implements Component { + public static final PrefabCopyableComponent INSTANCE = new PrefabCopyableComponent(); + + public PrefabCopyableComponent() { + } + + public static ComponentType getComponentType() { + return EntityModule.get().getPrefabCopyableComponentType(); + } + + public static PrefabCopyableComponent get() { + return INSTANCE; + } + + @Override + public Component clone() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/PrefabEntry.java b/src/com/hypixel/hytale/server/core/prefab/PrefabEntry.java new file mode 100644 index 0000000..ef3e90e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/PrefabEntry.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.server.core.prefab; + +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.server.core.asset.AssetModule; +import java.nio.file.Path; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public record PrefabEntry(@Nonnull Path path, @Nonnull Path relativePath, @Nullable AssetPack pack, @Nonnull String displayName) { + public PrefabEntry(@Nonnull Path path, @Nonnull Path relativePath, @Nullable AssetPack pack) { + this(path, relativePath, pack, buildDisplayName(relativePath, pack)); + } + + public boolean isFromBasePack() { + return this.pack != null && this.pack.equals(AssetModule.get().getBaseAssetPack()); + } + + public boolean isFromAssetPack() { + return this.pack != null; + } + + @Nonnull + public String getPackName() { + return this.pack != null ? this.pack.getName() : "Server"; + } + + @Nonnull + public String getFileName() { + return this.path.getFileName().toString(); + } + + @Nonnull + public String getDisplayNameWithPack() { + return this.pack != null && !this.isFromBasePack() ? "[" + this.pack.getName() + "] " + this.getFileName() : this.getFileName(); + } + + @Nonnull + private static String buildDisplayName(@Nonnull Path relativePath, @Nullable AssetPack pack) { + String fileName = relativePath.getFileName().toString(); + return pack != null && !pack.equals(AssetModule.get().getBaseAssetPack()) ? "[" + pack.getName() + "] " + fileName : fileName; + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/PrefabLoadException.java b/src/com/hypixel/hytale/server/core/prefab/PrefabLoadException.java new file mode 100644 index 0000000..0172496 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/PrefabLoadException.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.server.core.prefab; + +import javax.annotation.Nonnull; + +public class PrefabLoadException extends RuntimeException { + private PrefabLoadException.Type type; + + public PrefabLoadException(@Nonnull PrefabLoadException.Type type) { + super(type.name()); + this.type = type; + } + + public PrefabLoadException(PrefabLoadException.Type type, String message) { + super(message); + this.type = type; + } + + public PrefabLoadException(PrefabLoadException.Type type, String message, Throwable cause) { + super(message, cause); + this.type = type; + } + + public PrefabLoadException(PrefabLoadException.Type type, Throwable cause) { + super(cause); + this.type = type; + } + + public PrefabLoadException.Type getType() { + return this.type; + } + + public static enum Type { + ERROR, + NOT_FOUND; + + private Type() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/PrefabRotation.java b/src/com/hypixel/hytale/server/core/prefab/PrefabRotation.java new file mode 100644 index 0000000..db6558c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/PrefabRotation.java @@ -0,0 +1,269 @@ +package com.hypixel.hytale.server.core.prefab; + +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.math.vector.Vector3l; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockRotationUtil; +import javax.annotation.Nonnull; + +public enum PrefabRotation { + ROTATION_0(Rotation.None, new PrefabRotation.RotationExecutor_0()), + ROTATION_90(Rotation.Ninety, new PrefabRotation.RotationExecutor_90()), + ROTATION_180(Rotation.OneEighty, new PrefabRotation.RotationExecutor_180()), + ROTATION_270(Rotation.TwoSeventy, new PrefabRotation.RotationExecutor_270()); + + public static final PrefabRotation[] VALUES = values(); + public static final String PREFIX = "ROTATION_"; + private final Rotation rotation; + private final PrefabRotation.RotationExecutor executor; + + @Nonnull + public static PrefabRotation fromRotation(@Nonnull Rotation rotation) { + return switch (rotation) { + case None -> ROTATION_0; + case Ninety -> ROTATION_90; + case OneEighty -> ROTATION_180; + case TwoSeventy -> ROTATION_270; + }; + } + + @Nonnull + public static PrefabRotation valueOfExtended(@Nonnull String s) { + return s.startsWith("ROTATION_") ? valueOf(s) : valueOf("ROTATION_" + s); + } + + private PrefabRotation(Rotation rotation, PrefabRotation.RotationExecutor executor) { + this.rotation = rotation; + this.executor = executor; + } + + public PrefabRotation add(@Nonnull PrefabRotation other) { + int val = this.rotation.getDegrees() + other.rotation.getDegrees(); + return VALUES[val % 360 / 90]; + } + + public void rotate(@Nonnull Vector3d v) { + double x = v.x; + double z = v.z; + v.x = this.executor.rotateDoubleX(x, z); + v.z = this.executor.rotateDoubleZ(x, z); + } + + public void rotate(@Nonnull Vector3i v) { + int x = v.x; + int z = v.z; + v.x = this.executor.rotateIntX(x, z); + v.z = this.executor.rotateIntZ(x, z); + } + + public void rotate(@Nonnull Vector3l v) { + long x = v.x; + long z = v.z; + v.x = this.executor.rotateLongX(x, z); + v.z = this.executor.rotateLongZ(x, z); + } + + public int getX(int x, int z) { + return this.executor.rotateIntX(x, z); + } + + public int getZ(int x, int z) { + return this.executor.rotateIntZ(x, z); + } + + public float getYaw() { + return this.executor.getYaw(); + } + + public int getRotation(int rotation) { + if (this.rotation == Rotation.None) { + return rotation; + } else { + RotationTuple inRotation = RotationTuple.get(rotation); + return RotationTuple.of(inRotation.yaw().add(this.rotation), inRotation.pitch(), inRotation.roll()).index(); + } + } + + public int getFiller(int filler) { + return this.rotation == Rotation.None ? filler : BlockRotationUtil.getRotatedFiller(filler, Axis.Y, this.rotation); + } + + private interface RotationExecutor { + float getYaw(); + + int rotateIntX(int var1, int var2); + + long rotateLongX(long var1, long var3); + + double rotateDoubleX(double var1, double var3); + + int rotateIntZ(int var1, int var2); + + long rotateLongZ(long var1, long var3); + + double rotateDoubleZ(double var1, double var3); + } + + private static class RotationExecutor_0 implements PrefabRotation.RotationExecutor { + private RotationExecutor_0() { + } + + @Override + public float getYaw() { + return 0.0F; + } + + @Override + public int rotateIntX(int x, int z) { + return x; + } + + @Override + public long rotateLongX(long x, long z) { + return x; + } + + @Override + public double rotateDoubleX(double x, double z) { + return x; + } + + @Override + public int rotateIntZ(int x, int z) { + return z; + } + + @Override + public long rotateLongZ(long x, long z) { + return z; + } + + @Override + public double rotateDoubleZ(double x, double z) { + return z; + } + } + + private static class RotationExecutor_180 implements PrefabRotation.RotationExecutor { + private RotationExecutor_180() { + } + + @Override + public float getYaw() { + return (float) -Math.PI; + } + + @Override + public int rotateIntX(int x, int z) { + return -x; + } + + @Override + public long rotateLongX(long x, long z) { + return -x; + } + + @Override + public double rotateDoubleX(double x, double z) { + return -x; + } + + @Override + public int rotateIntZ(int x, int z) { + return -z; + } + + @Override + public long rotateLongZ(long x, long z) { + return -z; + } + + @Override + public double rotateDoubleZ(double x, double z) { + return -z; + } + } + + private static class RotationExecutor_270 implements PrefabRotation.RotationExecutor { + private RotationExecutor_270() { + } + + @Override + public float getYaw() { + return (float) (-Math.PI * 3.0 / 2.0); + } + + @Override + public int rotateIntX(int x, int z) { + return -z; + } + + @Override + public long rotateLongX(long x, long z) { + return -z; + } + + @Override + public double rotateDoubleX(double x, double z) { + return -z; + } + + @Override + public int rotateIntZ(int x, int z) { + return x; + } + + @Override + public long rotateLongZ(long x, long z) { + return x; + } + + @Override + public double rotateDoubleZ(double x, double z) { + return x; + } + } + + private static class RotationExecutor_90 implements PrefabRotation.RotationExecutor { + private RotationExecutor_90() { + } + + @Override + public float getYaw() { + return (float) (-Math.PI / 2); + } + + @Override + public int rotateIntX(int x, int z) { + return z; + } + + @Override + public long rotateLongX(long x, long z) { + return z; + } + + @Override + public double rotateDoubleX(double x, double z) { + return z; + } + + @Override + public int rotateIntZ(int x, int z) { + return -x; + } + + @Override + public long rotateLongZ(long x, long z) { + return -x; + } + + @Override + public double rotateDoubleZ(double x, double z) { + return -x; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/PrefabSaveException.java b/src/com/hypixel/hytale/server/core/prefab/PrefabSaveException.java new file mode 100644 index 0000000..671fddb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/PrefabSaveException.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.server.core.prefab; + +public class PrefabSaveException extends RuntimeException { + private PrefabSaveException.Type type; + + public PrefabSaveException(PrefabSaveException.Type type) { + this.type = type; + } + + public PrefabSaveException(PrefabSaveException.Type type, String message) { + super(message); + this.type = type; + } + + public PrefabSaveException(PrefabSaveException.Type type, String message, Throwable cause) { + super(message, cause); + this.type = type; + } + + public PrefabSaveException(PrefabSaveException.Type type, Throwable cause) { + super(cause); + this.type = type; + } + + public PrefabSaveException.Type getType() { + return this.type; + } + + public static enum Type { + ERROR, + ALREADY_EXISTS; + + private Type() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/PrefabStore.java b/src/com/hypixel/hytale/server/core/prefab/PrefabStore.java new file mode 100644 index 0000000..37f86bd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/PrefabStore.java @@ -0,0 +1,253 @@ +package com.hypixel.hytale.server.core.prefab; + +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.prefab.config.SelectionPrefabSerializer; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.util.AssetUtil; +import com.hypixel.hytale.server.core.util.BsonUtil; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabStore { + public static final Predicate PREFAB_FILTER = path -> path.toString().endsWith(".prefab.json"); + public static final Path PREFABS_PATH = Path.of("prefabs"); + private static final String DEFAULT_WORLDGEN_NAME = "Default"; + private static final PrefabStore INSTANCE = new PrefabStore(); + private final Map PREFAB_CACHE = new ConcurrentHashMap<>(); + + private PrefabStore() { + } + + @Nonnull + public BlockSelection getServerPrefab(@Nonnull String key) { + return this.getPrefab(this.getServerPrefabsPath().resolve(key)); + } + + @Nonnull + public BlockSelection getPrefab(@Nonnull Path path) { + return this.PREFAB_CACHE.computeIfAbsent(path.toAbsolutePath().normalize(), p -> { + if (Files.exists(p)) { + return SelectionPrefabSerializer.deserialize(BsonUtil.readDocument(p).join()); + } else { + throw new PrefabLoadException(PrefabLoadException.Type.NOT_FOUND); + } + }); + } + + public Path getServerPrefabsPath() { + return PREFABS_PATH; + } + + @Nonnull + public Map getServerPrefabDir(@Nonnull String key) { + return this.getPrefabDir(this.getServerPrefabsPath().resolve(key)); + } + + @Nonnull + public Map getPrefabDir(@Nonnull Path dir) { + try { + Map var3; + try (Stream stream = Files.list(dir)) { + var3 = stream.filter(PREFAB_FILTER).sorted().collect(Collectors.toMap(Function.identity(), this::getPrefab, (u, v) -> { + throw new IllegalStateException(String.format("Duplicate key %s", u)); + }, LinkedHashMap::new)); + } + + return var3; + } catch (IOException var7) { + throw new RuntimeException("Failed to list directory " + dir, var7); + } + } + + public void saveServerPrefab(@Nonnull String key, @Nonnull BlockSelection prefab) { + this.saveWorldGenPrefab(key, prefab, false); + } + + public void saveWorldGenPrefab(@Nonnull String key, @Nonnull BlockSelection prefab, boolean overwrite) { + this.savePrefab(this.getWorldGenPrefabsPath().resolve(key), prefab, overwrite); + } + + public void savePrefab(@Nonnull Path path, @Nonnull BlockSelection prefab, boolean overwrite) { + File file = path.toFile(); + if (file.exists() && !overwrite) { + throw new PrefabSaveException(PrefabSaveException.Type.ALREADY_EXISTS); + } else { + file.getParentFile().mkdirs(); + + try { + BsonUtil.writeDocument(path, SelectionPrefabSerializer.serialize(prefab)).join(); + } catch (Throwable var6) { + throw new PrefabSaveException(PrefabSaveException.Type.ERROR, var6); + } + + this.PREFAB_CACHE.remove(path); + } + } + + @Nonnull + public Path getWorldGenPrefabsPath() { + return this.getWorldGenPrefabsPath("Default"); + } + + public Path getAssetRootPath() { + return AssetUtil.getHytaleAssetsPath(); + } + + @Nonnull + public Path getWorldGenPrefabsPath(@Nullable String name) { + name = name == null ? "Default" : name; + return Universe.getWorldGenPath().resolve(name).resolve("Prefabs"); + } + + public void saveServerPrefab(@Nonnull String key, @Nonnull BlockSelection prefab, boolean overwrite) { + this.savePrefab(this.getServerPrefabsPath().resolve(key), prefab, overwrite); + } + + @Nonnull + public Path getAssetPrefabsPath() { + return AssetUtil.getHytaleAssetsPath().resolve("Server").resolve("Prefabs"); + } + + @Nonnull + public Path getAssetPrefabsPathForPack(@Nonnull AssetPack pack) { + return pack.getRoot().resolve("Server").resolve("Prefabs"); + } + + @Nonnull + public List getAllAssetPrefabPaths() { + List result = new ObjectArrayList<>(); + + for (AssetPack pack : AssetModule.get().getAssetPacks()) { + Path prefabsPath = this.getAssetPrefabsPathForPack(pack); + if (Files.isDirectory(prefabsPath)) { + result.add(new PrefabStore.AssetPackPrefabPath(pack, prefabsPath)); + } + } + + return result; + } + + @Nullable + public BlockSelection getAssetPrefabFromAnyPack(@Nonnull String key) { + for (AssetPack pack : AssetModule.get().getAssetPacks()) { + Path prefabsPath = this.getAssetPrefabsPathForPack(pack); + Path prefabPath = prefabsPath.resolve(key); + if (Files.exists(prefabPath)) { + return this.getPrefab(prefabPath); + } + } + + return null; + } + + @Nullable + public Path findAssetPrefabPath(@Nonnull String key) { + for (AssetPack pack : AssetModule.get().getAssetPacks()) { + Path prefabsPath = this.getAssetPrefabsPathForPack(pack); + Path prefabPath = prefabsPath.resolve(key); + if (Files.exists(prefabPath)) { + return prefabPath; + } + } + + return null; + } + + @Nullable + public AssetPack findAssetPackForPrefabPath(@Nonnull Path prefabPath) { + Path normalizedPath = prefabPath.toAbsolutePath().normalize(); + + for (AssetPack pack : AssetModule.get().getAssetPacks()) { + Path prefabsPath = this.getAssetPrefabsPathForPack(pack).toAbsolutePath().normalize(); + if (normalizedPath.startsWith(prefabsPath)) { + return pack; + } + } + + return null; + } + + @Nonnull + public BlockSelection getAssetPrefab(@Nonnull String key) { + return this.getPrefab(this.getAssetPrefabsPath().resolve(key)); + } + + @Nonnull + public Map getAssetPrefabDir(@Nonnull String key) { + return this.getPrefabDir(this.getAssetPrefabsPath().resolve(key)); + } + + public void saveAssetPrefab(@Nonnull String key, @Nonnull BlockSelection prefab) { + this.saveWorldGenPrefab(key, prefab, false); + } + + public void saveAssetPrefab(@Nonnull String key, @Nonnull BlockSelection prefab, boolean overwrite) { + this.savePrefab(this.getAssetPrefabsPath().resolve(key), prefab, overwrite); + } + + @Nonnull + public BlockSelection getWorldGenPrefab(@Nonnull String key) { + return this.getWorldGenPrefab(this.getWorldGenPrefabsPath(), key); + } + + @Nonnull + public BlockSelection getWorldGenPrefab(@Nonnull Path prefabsPath, @Nonnull String key) { + return this.getPrefab(prefabsPath.resolve(key)); + } + + @Nonnull + public Map getWorldGenPrefabDir(@Nonnull String key) { + return this.getPrefabDir(this.getWorldGenPrefabsPath().resolve(key)); + } + + public void saveWorldGenPrefab(@Nonnull String key, @Nonnull BlockSelection prefab) { + this.saveWorldGenPrefab(key, prefab, false); + } + + public static PrefabStore get() { + return INSTANCE; + } + + public record AssetPackPrefabPath(@Nullable AssetPack pack, @Nonnull Path prefabsPath) { + public boolean isBasePack() { + return this.pack != null && this.pack.equals(AssetModule.get().getBaseAssetPack()); + } + + public boolean isFromAssetPack() { + return this.pack != null; + } + + @Nonnull + public String getPackName() { + return this.pack != null ? this.pack.getName() : "Server"; + } + + @Nonnull + public String getDisplayName() { + if (this.pack == null) { + return "Server"; + } else if (this.isBasePack()) { + return "Assets"; + } else { + PluginManifest manifest = this.pack.getManifest(); + return manifest != null ? manifest.getName() : this.pack.getName(); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/PrefabWeights.java b/src/com/hypixel/hytale/server/core/prefab/PrefabWeights.java new file mode 100644 index 0000000..6921ca5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/PrefabWeights.java @@ -0,0 +1,214 @@ +package com.hypixel.hytale.server.core.prefab; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.Object2DoubleMapCodec; +import com.hypixel.hytale.codec.validation.LegacyValidator; +import com.hypixel.hytale.codec.validation.ValidationResults; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.ArrayUtil; +import it.unimi.dsi.fastutil.objects.Object2DoubleMap; +import it.unimi.dsi.fastutil.objects.Object2DoubleMaps; +import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2DoubleMap.Entry; +import java.util.Random; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabWeights { + public static final Supplier> MAP_SUPPLIER = Object2DoubleOpenHashMap::new; + public static final Codec> MAP_CODEC = new Object2DoubleMapCodec<>(Codec.STRING, MAP_SUPPLIER, false); + public static final Codec CODEC = BuilderCodec.builder(PrefabWeights.class, PrefabWeights::new) + .append(new KeyedCodec<>("Default", Codec.DOUBLE), (weights, def) -> weights.defaultWeight = def, weights -> weights.defaultWeight) + .documentation("The default weight to use for entries that are not specifically mapped to a weight value.") + .addValidator(Validators.greaterThanOrEqual(0.0)) + .add() + .>append(new KeyedCodec<>("Weights", MAP_CODEC), (weights, map) -> weights.weightsLookup = map, weights -> weights.weightsLookup) + .documentation("The mapping of prefab names to weight values.") + .addValidator(new PrefabWeights.WeightMapValidator()) + .add() + .build(); + public static final PrefabWeights NONE = new PrefabWeights(Object2DoubleMaps.emptyMap()) { + { + this.sum = 0.0; + this.weights = ArrayUtil.EMPTY_DOUBLE_ARRAY; + this.initialized = true; + } + }; + public static final double DEFAULT_WEIGHT = 1.0; + public static final char DELIMITER_CHAR = ','; + public static final char ASSIGNMENT_CHAR = '='; + private double defaultWeight; + private Object2DoubleMap weightsLookup; + protected double sum; + protected double[] weights; + protected volatile boolean initialized; + + public PrefabWeights() { + this(MAP_SUPPLIER.get()); + } + + private PrefabWeights(Object2DoubleMap weights) { + this.weightsLookup = weights; + this.defaultWeight = 1.0; + } + + public int size() { + return this.weightsLookup.size(); + } + + @Nullable + public T get(@Nonnull T[] elements, @Nonnull Function nameFunc, @Nonnull Random random) { + return this.get(elements, nameFunc, random.nextDouble()); + } + + @Nullable + public T get(@Nonnull T[] elements, @Nonnull Function nameFunc, double value) { + if (value < 0.0) { + return null; + } else { + this.initialize(elements, nameFunc); + if (this.weights.length != elements.length) { + return null; + } else { + double weightedValue = Math.min(value, 0.99999) * this.sum; + + for (int i = 0; i < this.weights.length; i++) { + if (weightedValue <= this.weights[i]) { + return elements[i]; + } + } + + return null; + } + } + } + + public double getWeight(String prefab) { + return this.weightsLookup.getOrDefault(prefab, this.defaultWeight); + } + + public void setWeight(String prefab, double weight) { + if (this != NONE) { + checkWeight(prefab, weight); + this.weightsLookup.put(prefab, weight); + } + } + + public void removeWeight(String prefab) { + if (this != NONE) { + this.weightsLookup.removeDouble(prefab); + } + } + + public double getDefaultWeight() { + return this.defaultWeight; + } + + public void setDefaultWeight(double defaultWeight) { + if (this != NONE) { + this.defaultWeight = Math.max(0.0, defaultWeight); + } + } + + @Nonnull + public String getMappingString() { + if (this.weightsLookup.isEmpty()) { + return ""; + } else { + StringBuilder sb = new StringBuilder(); + + for (Entry entry : Object2DoubleMaps.fastIterable(this.weightsLookup)) { + if (!sb.isEmpty()) { + sb.append(',').append(' '); + } + + sb.append(entry.getKey()).append('=').append(entry.getDoubleValue()); + } + + return sb.toString(); + } + } + + @Nonnull + @Override + public String toString() { + return "PrefabWeights{default=" + this.defaultWeight + ", weights=" + this.getMappingString() + "}"; + } + + private void initialize(@Nonnull T[] elements, @Nonnull Function nameFunc) { + if (!this.initialized) { + synchronized (this) { + if (!this.initialized) { + double sum = 0.0; + double[] weights = new double[elements.length]; + + for (int i = 0; i < elements.length; i++) { + String name = nameFunc.apply(elements[i]); + sum += this.getWeight(name); + weights[i] = sum; + } + + this.sum = sum; + this.weights = weights; + this.initialized = true; + } + } + } + } + + @Nonnull + public static PrefabWeights parse(@Nonnull String mappingString) { + Object2DoubleMap map = null; + int startPoint = 0; + + while (startPoint < mappingString.length()) { + int endPoint = mappingString.indexOf(44, startPoint); + if (endPoint == -1) { + endPoint = mappingString.length(); + } + + int equalsPoint = mappingString.indexOf(61, startPoint); + if (equalsPoint <= startPoint) { + break; + } + + String name = mappingString.substring(startPoint, equalsPoint).trim(); + String value = mappingString.substring(equalsPoint + 1, endPoint).trim(); + double weight = Double.parseDouble(value); + if (map == null) { + map = MAP_SUPPLIER.get(); + } + + map.put(name, weight); + startPoint = endPoint + 1; + } + + return map == null ? NONE : new PrefabWeights(map); + } + + public Set> entrySet() { + return this.weightsLookup.object2DoubleEntrySet(); + } + + private static void checkWeight(String prefab, double weight) { + if (weight < 0.0) { + throw new IllegalArgumentException(String.format("Negative weight %.5f assigned to prefab %s", weight, prefab)); + } + } + + private static class WeightMapValidator implements LegacyValidator> { + private WeightMapValidator() { + } + + public void accept(@Nonnull Object2DoubleMap stringObject2DoubleMap, ValidationResults results) { + for (Entry entry : Object2DoubleMaps.fastIterable(stringObject2DoubleMap)) { + PrefabWeights.checkWeight(entry.getKey(), entry.getDoubleValue()); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/config/SelectionPrefabSerializer.java b/src/com/hypixel/hytale/server/core/prefab/config/SelectionPrefabSerializer.java new file mode 100644 index 0000000..3169714 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/config/SelectionPrefabSerializer.java @@ -0,0 +1,409 @@ +package com.hypixel.hytale.server.core.prefab.config; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.codec.DirectDecodeCodec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.lookup.ACodecMapCodec; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.data.unknown.TempUnknownComponent; +import com.hypixel.hytale.component.data.unknown.UnknownComponents; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockMigration; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.prefab.selection.buffer.BsonPrefabBufferDeserializer; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import java.util.Comparator; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonArray; +import org.bson.BsonDocument; +import org.bson.BsonInt32; +import org.bson.BsonString; +import org.bson.BsonValue; + +public class SelectionPrefabSerializer { + public static final int VERSION = 8; + private static final Comparator COMPARE_BLOCK_POSITION = Comparator.comparingInt(doc -> doc.getInt32("x").getValue()) + .thenComparingInt(doc -> doc.getInt32("z").getValue()) + .thenComparingInt(doc -> doc.getInt32("y").getValue()); + private static final BsonInt32 DEFAULT_SUPPORT_VALUE = new BsonInt32(0); + private static final BsonInt32 DEFAULT_FILLER_VALUE = new BsonInt32(0); + private static final BsonInt32 DEFAULT_ROTATION_VALUE = new BsonInt32(0); + + private SelectionPrefabSerializer() { + } + + @Nonnull + public static BlockSelection deserialize(@Nonnull BsonDocument doc) { + BsonValue versionValue = doc.get("version"); + int version = versionValue != null ? versionValue.asInt32().getValue() : -1; + if (version <= 0) { + throw new IllegalArgumentException("Prefab version is too old: " + version); + } else if (version > 8) { + throw new IllegalArgumentException("Prefab version is too new: " + version + " by expected 8"); + } else { + int worldVersion = version < 4 ? readWorldVersion(doc) : 0; + BsonValue entityVersionValue = doc.get("entityVersion"); + int entityVersion = entityVersionValue != null ? entityVersionValue.asInt32().getValue() : 0; + int anchorX = doc.getInt32("anchorX").getValue(); + int anchorY = doc.getInt32("anchorY").getValue(); + int anchorZ = doc.getInt32("anchorZ").getValue(); + BlockSelection selection = new BlockSelection(); + selection.setAnchor(anchorX, anchorY, anchorZ); + int blockIdVersion = doc.getInt32("blockIdVersion", BsonPrefabBufferDeserializer.LEGACY_BLOCK_ID_VERSION).getValue(); + Function blockMigration = null; + Map blockMigrationMap = BlockMigration.getAssetMap().getAssetMap(); + int v = blockIdVersion; + + for (BlockMigration migration = blockMigrationMap.get(blockIdVersion); migration != null; migration = blockMigrationMap.get(++v)) { + if (blockMigration == null) { + blockMigration = migration::getMigration; + } else { + blockMigration = blockMigration.andThen(migration::getMigration); + } + } + + BsonValue blocksValue = doc.get("blocks"); + if (blocksValue != null) { + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + BsonArray bsonArray = blocksValue.asArray(); + + for (int i = 0; i < bsonArray.size(); i++) { + BsonDocument innerObj = bsonArray.get(i).asDocument(); + int x = innerObj.getInt32("x").getValue(); + int y = innerObj.getInt32("y").getValue(); + int z = innerObj.getInt32("z").getValue(); + String blockTypeStr = innerObj.getString("name").getValue(); + boolean legacyStripName = false; + if (version <= 4) { + Fluid.ConversionResult result = Fluid.convertBlockToFluid(blockTypeStr); + if (result != null) { + legacyStripName = true; + selection.addFluidAtLocalPos(x, y, z, result.fluidId, result.fluidLevel); + if (result.blockTypeStr == null) { + continue; + } + } + } + + int support = 0; + if (version >= 6) { + support = innerObj.getInt32("support", DEFAULT_SUPPORT_VALUE).getValue(); + } else if (blockTypeStr.contains("|Deco")) { + legacyStripName = true; + support = 15; + } else if (blockTypeStr.contains("|Support=")) { + legacyStripName = true; + int start = blockTypeStr.indexOf("|Support=") + "|Support=".length(); + int end = blockTypeStr.indexOf(124, start); + if (end == -1) { + end = blockTypeStr.length(); + } + + support = Integer.parseInt(blockTypeStr, start, end, 10); + } else { + support = 0; + } + + int filler = 0; + if (version >= 7) { + filler = innerObj.getInt32("filler", DEFAULT_FILLER_VALUE).getValue(); + } else if (blockTypeStr.contains("|Filler=")) { + legacyStripName = true; + int start = blockTypeStr.indexOf("|Filler=") + "|Filler=".length(); + int firstComma = blockTypeStr.indexOf(44, start); + if (firstComma == -1) { + throw new IllegalArgumentException("Invalid filler metadata! Missing comma"); + } + + int secondComma = blockTypeStr.indexOf(44, firstComma + 1); + if (secondComma == -1) { + throw new IllegalArgumentException("Invalid filler metadata! Missing second comma"); + } + + int end = blockTypeStr.indexOf(124, start); + if (end == -1) { + end = blockTypeStr.length(); + } + + int fillerX = Integer.parseInt(blockTypeStr, start, firstComma, 10); + int fillerY = Integer.parseInt(blockTypeStr, firstComma + 1, secondComma, 10); + int fillerZ = Integer.parseInt(blockTypeStr, secondComma + 1, end, 10); + filler = FillerBlockUtil.pack(fillerX, fillerY, fillerZ); + } else { + filler = 0; + } + + int rotation = 0; + if (version >= 8) { + rotation = innerObj.getInt32("rotation", DEFAULT_ROTATION_VALUE).getValue(); + } else { + Rotation yaw = Rotation.None; + Rotation pitch = Rotation.None; + Rotation roll = Rotation.None; + if (blockTypeStr.contains("|Yaw=")) { + legacyStripName = true; + int startx = blockTypeStr.indexOf("|Yaw=") + "|Yaw=".length(); + int end = blockTypeStr.indexOf(124, startx); + if (end == -1) { + end = blockTypeStr.length(); + } + + yaw = Rotation.ofDegrees(Integer.parseInt(blockTypeStr, startx, end, 10)); + } + + if (blockTypeStr.contains("|Pitch=")) { + legacyStripName = true; + int startx = blockTypeStr.indexOf("|Pitch=") + "|Pitch=".length(); + int end = blockTypeStr.indexOf(124, startx); + if (end == -1) { + end = blockTypeStr.length(); + } + + pitch = Rotation.ofDegrees(Integer.parseInt(blockTypeStr, startx, end, 10)); + } + + if (blockTypeStr.contains("|Roll=")) { + legacyStripName = true; + int startx = blockTypeStr.indexOf("|Roll=") + "|Roll=".length(); + int end = blockTypeStr.indexOf(124, startx); + if (end == -1) { + end = blockTypeStr.length(); + } + + pitch = Rotation.ofDegrees(Integer.parseInt(blockTypeStr, startx, end, 10)); + } + + rotation = RotationTuple.index(yaw, pitch, roll); + } + + if (legacyStripName) { + int endOfName = blockTypeStr.indexOf(124); + if (endOfName != -1) { + blockTypeStr = blockTypeStr.substring(0, endOfName); + } + } + + String blockTypeKey = blockTypeStr; + if (blockMigration != null) { + blockTypeKey = blockMigration.apply(blockTypeStr); + } + + int blockId = BlockType.getBlockIdOrUnknown(assetMap, blockTypeKey, "Failed to find block '%s' in unknown legacy prefab!", blockTypeStr); + Holder wrapper = null; + if (version <= 2) { + BsonValue stateValue = innerObj.get("state"); + if (stateValue != null) { + wrapper = legacyStateDecode(stateValue.asDocument()); + } + } else { + BsonValue stateValue = innerObj.get("components"); + if (stateValue != null) { + if (version < 4) { + wrapper = ChunkStore.REGISTRY.deserialize(stateValue.asDocument(), worldVersion); + } else { + wrapper = ChunkStore.REGISTRY.deserialize(stateValue.asDocument()); + } + } + } + + selection.addBlockAtLocalPos(x, y, z, blockId, rotation, filler, support, wrapper); + } + } + + BsonValue fluidsValue = doc.get("fluids"); + if (fluidsValue != null) { + IndexedLookupTableAssetMap assetMap = Fluid.getAssetMap(); + BsonArray bsonArray = fluidsValue.asArray(); + + for (int i = 0; i < bsonArray.size(); i++) { + BsonDocument innerObjx = bsonArray.get(i).asDocument(); + int xx = innerObjx.getInt32("x").getValue(); + int yx = innerObjx.getInt32("y").getValue(); + int zx = innerObjx.getInt32("z").getValue(); + String fluidName = innerObjx.getString("name").getValue(); + int fluidId = Fluid.getFluidIdOrUnknown(assetMap, fluidName, "Failed to find fluid '%s' in unknown legacy prefab!", fluidName); + byte fluidLevel = (byte)innerObjx.getInt32("level").getValue(); + selection.addFluidAtLocalPos(xx, yx, zx, fluidId, fluidLevel); + } + } + + BsonValue entitiesValues = doc.get("entities"); + if (entitiesValues != null) { + BsonArray entities = entitiesValues.asArray(); + + for (int i = 0; i < entities.size(); i++) { + BsonDocument bsonDocument = entities.get(i).asDocument(); + if (version <= 1) { + try { + selection.addEntityHolderRaw(legacyEntityDecode(bsonDocument, entityVersion)); + } catch (Throwable var34) { + HytaleLogger.getLogger().at(Level.WARNING).withCause(var34).log("Exception when loading entity state %s", bsonDocument); + } + } else { + selection.addEntityHolderRaw(EntityStore.REGISTRY.deserialize(bsonDocument)); + } + } + } + + return selection; + } + } + + @Nonnull + public static BsonDocument serialize(@Nonnull BlockSelection prefab) { + Objects.requireNonNull(prefab, "null prefab"); + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + IndexedLookupTableAssetMap fluidMap = Fluid.getAssetMap(); + BsonDocument out = new BsonDocument(); + out.put("version", new BsonInt32(8)); + out.put("blockIdVersion", new BsonInt32(BlockMigration.getAssetMap().getAssetCount())); + out.put("anchorX", new BsonInt32(prefab.getAnchorX())); + out.put("anchorY", new BsonInt32(prefab.getAnchorY())); + out.put("anchorZ", new BsonInt32(prefab.getAnchorZ())); + BsonArray contentOut = new BsonArray(); + prefab.forEachBlock((x, y, z, block) -> { + BsonDocument innerObj = new BsonDocument(); + innerObj.put("x", new BsonInt32(x)); + innerObj.put("y", new BsonInt32(y)); + innerObj.put("z", new BsonInt32(z)); + innerObj.put("name", new BsonString(assetMap.getAsset(block.blockId()).getId().toString())); + if (block.holder() != null) { + innerObj.put("components", ChunkStore.REGISTRY.serialize(block.holder())); + } + + if (block.supportValue() != 0) { + innerObj.put("support", new BsonInt32(block.supportValue())); + } + + if (block.filler() != 0) { + innerObj.put("filler", new BsonInt32(block.filler())); + } + + if (block.rotation() != 0) { + innerObj.put("rotation", new BsonInt32(block.rotation())); + } + + contentOut.add((BsonValue)innerObj); + }); + contentOut.sort((a, b) -> { + BsonDocument aDoc = a.asDocument(); + BsonDocument bDoc = b.asDocument(); + return COMPARE_BLOCK_POSITION.compare(aDoc, bDoc); + }); + out.put("blocks", contentOut); + BsonArray fluidContentOut = new BsonArray(); + prefab.forEachFluid((x, y, z, fluid, level) -> { + BsonDocument innerObj = new BsonDocument(); + innerObj.put("x", new BsonInt32(x)); + innerObj.put("y", new BsonInt32(y)); + innerObj.put("z", new BsonInt32(z)); + innerObj.put("name", new BsonString(fluidMap.getAsset(fluid).getId())); + innerObj.put("level", new BsonInt32(level)); + fluidContentOut.add((BsonValue)innerObj); + }); + fluidContentOut.sort((a, b) -> { + BsonDocument aDoc = a.asDocument(); + BsonDocument bDoc = b.asDocument(); + return COMPARE_BLOCK_POSITION.compare(aDoc, bDoc); + }); + if (!fluidContentOut.isEmpty()) { + out.put("fluids", fluidContentOut); + } + + BsonArray entities = new BsonArray(); + prefab.forEachEntity(holder -> entities.add((BsonValue)EntityStore.REGISTRY.serialize(holder))); + if (!entities.isEmpty()) { + out.put("entities", entities); + } + + return out; + } + + public static int readWorldVersion(@Nonnull BsonDocument document) { + int worldVersion; + if (document.containsKey("worldVersion")) { + worldVersion = document.getInt32("worldVersion").getValue(); + } else if (document.containsKey("worldver")) { + worldVersion = document.getInt32("worldver").getValue(); + } else { + worldVersion = 5; + } + + if (worldVersion == 18553) { + throw new IllegalArgumentException("WorldChunk version old format! Update!"); + } else if (worldVersion > 23) { + throw new IllegalArgumentException("WorldChunk version is newer than we understand! Version: " + worldVersion + ", Latest Version: 23"); + } else { + return worldVersion; + } + } + + @Nullable + public static Holder legacyEntityDecode(@Nonnull BsonDocument document, int version) { + String entityTypeStr = document.getString("EntityType").getValue(); + Class entityType = EntityModule.get().getClass(entityTypeStr); + if (entityType == null) { + UnknownComponents unknownComponents = new UnknownComponents(); + unknownComponents.addComponent(entityTypeStr, new TempUnknownComponent(document)); + return EntityStore.REGISTRY.newHolder(Archetype.of(EntityStore.REGISTRY.getUnknownComponentType()), new Component[]{unknownComponents}); + } else { + Function constructor = EntityModule.get().getConstructor(entityType); + if (constructor == null) { + return null; + } else { + DirectDecodeCodec codec = EntityModule.get().getCodec(entityType); + Objects.requireNonNull(codec, "Unable to create entity because there is no associated codec"); + Entity entity = constructor.apply(null); + codec.decode(document, entity, new ExtraInfo(version)); + return entity.toHolder(); + } + } + } + + @Nonnull + public static Holder legacyStateDecode(@Nonnull BsonDocument document) { + ExtraInfo extraInto = ExtraInfo.THREAD_LOCAL.get(); + String type = BlockState.TYPE_STRUCTURE.getNow(document, extraInto); + Class blockStateClass = BlockState.CODEC.getClassFor(type); + if (blockStateClass != null) { + try { + BlockState t = BlockState.CODEC.decode(document, extraInto); + Holder holder = ChunkStore.REGISTRY.newHolder(); + ComponentType componentType = BlockStateModule.get().getComponentType(blockStateClass); + if (componentType == null) { + throw new IllegalArgumentException("Unable to find component type for: " + blockStateClass); + } + + holder.addComponent(componentType, t); + return holder; + } catch (ACodecMapCodec.UnknownIdException var7) { + } + } + + Holder holder = ChunkStore.REGISTRY.newHolder(); + UnknownComponents unknownComponents = new UnknownComponents<>(); + unknownComponents.addComponent(type, new TempUnknownComponent<>(document)); + holder.addComponent(ChunkStore.REGISTRY.getUnknownComponentType(), unknownComponents); + return holder; + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/event/PrefabPasteEvent.java b/src/com/hypixel/hytale/server/core/prefab/event/PrefabPasteEvent.java new file mode 100644 index 0000000..ba1f37b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/event/PrefabPasteEvent.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.server.core.prefab.event; + +import com.hypixel.hytale.component.system.CancellableEcsEvent; + +public class PrefabPasteEvent extends CancellableEcsEvent { + private final int prefabId; + private final boolean pasteStart; + + public PrefabPasteEvent(int prefabId, boolean pasteStart) { + this.prefabId = prefabId; + this.pasteStart = pasteStart; + } + + public int getPrefabId() { + return this.prefabId; + } + + public boolean isPasteStart() { + return this.pasteStart; + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/event/PrefabPlaceEntityEvent.java b/src/com/hypixel/hytale/server/core/prefab/event/PrefabPlaceEntityEvent.java new file mode 100644 index 0000000..2030865 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/event/PrefabPlaceEntityEvent.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.core.prefab.event; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.system.EcsEvent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class PrefabPlaceEntityEvent extends EcsEvent { + private final int prefabId; + @Nonnull + private final Holder holder; + + public PrefabPlaceEntityEvent(int prefabId, @Nonnull Holder holder) { + this.prefabId = prefabId; + this.holder = holder; + } + + public int getPrefabId() { + return this.prefabId; + } + + @Nonnull + public Holder getHolder() { + return this.holder; + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/SelectionManager.java b/src/com/hypixel/hytale/server/core/prefab/selection/SelectionManager.java new file mode 100644 index 0000000..d5290c1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/SelectionManager.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.server.core.prefab.selection; + +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nullable; + +public final class SelectionManager { + private static final AtomicReference SELECTION_PROVIDER = new AtomicReference<>(); + + private SelectionManager() { + } + + public static void setSelectionProvider(SelectionProvider provider) { + SELECTION_PROVIDER.set(provider); + } + + @Nullable + public static SelectionProvider getSelectionProvider() { + return SELECTION_PROVIDER.get(); + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/SelectionProvider.java b/src/com/hypixel/hytale/server/core/prefab/selection/SelectionProvider.java new file mode 100644 index 0000000..41a27d2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/SelectionProvider.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.server.core.prefab.selection; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.sneakythrow.consumer.ThrowableConsumer; +import javax.annotation.Nonnull; + +public interface SelectionProvider { + void computeSelectionCopy( + @Nonnull Ref var1, @Nonnull Player var2, @Nonnull ThrowableConsumer var3, @Nonnull ComponentAccessor var4 + ); +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/buffer/BinaryPrefabBufferCodec.java b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/BinaryPrefabBufferCodec.java new file mode 100644 index 0000000..0261649 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/BinaryPrefabBufferCodec.java @@ -0,0 +1,372 @@ +package com.hypixel.hytale.server.core.prefab.selection.buffer; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.math.block.BlockUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockMigration; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.prefab.config.SelectionPrefabSerializer; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBufferBlockEntry; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.BsonUtil; +import com.hypixel.hytale.server.core.util.io.ByteBufUtil; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.nio.file.Path; +import java.util.Map; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public class BinaryPrefabBufferCodec implements PrefabBufferCodec { + public static final BinaryPrefabBufferCodec INSTANCE = new BinaryPrefabBufferCodec(); + public static final int VERSION = 21; + private static final int MASK_CHANCE = 1; + private static final int MASK_COMPONENTS = 2; + private static final int MASK_FLUID = 4; + private static final int MASK_SUPPORT_VALUE = 8; + private static final int MASK_FILLER = 16; + private static final int MASK_ROTATION = 32; + + public BinaryPrefabBufferCodec() { + } + + @Nonnull + public PrefabBuffer deserialize(Path path, @Nonnull ByteBuf buffer) { + int version = buffer.readUnsignedShort(); + if (version == 18553) { + throw new UpdateBinaryPrefabException("Old prefab format!"); + } else if (21 < version) { + throw new IllegalStateException("Prefab version is newer than supported. Given: " + version); + } else { + int worldVersion = version < 17 ? buffer.readUnsignedShort() : 0; + if (version == 11) { + buffer.readUnsignedShort(); + } + + int entityVersion = version >= 14 && version < 17 ? buffer.readUnsignedShort() : 0; + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + int blockIdVersion = 8; + if (version >= 13) { + blockIdVersion = buffer.readShort(); + } + + Vector3i anchor = Vector3i.ZERO; + if (version >= 16) { + long packedAnchor = buffer.readLong(); + anchor = new Vector3i(BlockUtil.unpackX(packedAnchor), BlockUtil.unpackY(packedAnchor), BlockUtil.unpackZ(packedAnchor)); + } + + Function blockMigration = null; + Map blockMigrationMap = BlockMigration.getAssetMap().getAssetMap(); + int v = blockIdVersion; + + for (BlockMigration migration = blockMigrationMap.get(blockIdVersion); migration != null; migration = blockMigrationMap.get(++v)) { + if (blockMigration == null) { + blockMigration = migration::getMigration; + } else { + blockMigration = blockMigration.andThen(migration::getMigration); + } + } + + int blockNameCount = buffer.readInt(); + Int2ObjectOpenHashMap blockIdMapping = new Int2ObjectOpenHashMap<>(blockNameCount); + + for (int i = 0; i < blockNameCount; i++) { + try { + int readId = buffer.readInt(); + BinaryPrefabBufferCodec.BlockIdEntry block = this.deserializeBlock(buffer, assetMap, blockMigration); + blockIdMapping.put(readId, block); + } catch (Exception var44) { + throw new IllegalStateException("Failed to deserialize block name #" + i, var44); + } + } + + IndexedLookupTableAssetMap fluidMap = Fluid.getAssetMap(); + int fluidNameCount = version >= 18 ? buffer.readInt() : 0; + Int2ObjectOpenHashMap fluidIdMapping = new Int2ObjectOpenHashMap<>(fluidNameCount); + + for (int i = 0; i < fluidNameCount; i++) { + try { + int readId = buffer.readInt(); + BinaryPrefabBufferCodec.FluidIdEntry fluid = this.deserializeFluid(buffer, fluidMap); + fluidIdMapping.put(readId, fluid); + } catch (Exception var43) { + throw new IllegalStateException("Failed to deserialize block name #" + i, var43); + } + } + + PrefabBuffer.Builder builder = PrefabBuffer.newBuilder(); + builder.setAnchor(anchor); + int columnCount = buffer.readInt(); + + for (int i = 0; i < columnCount; i++) { + int columnIndex = buffer.readInt(); + int blocks = buffer.readInt(); + PrefabBufferBlockEntry[] blockEntries = new PrefabBufferBlockEntry[blocks]; + + for (int j = 0; j < blocks; j++) { + int y = buffer.readShort(); + int readId = buffer.readInt(); + BinaryPrefabBufferCodec.BlockIdEntry block = blockIdMapping.get(readId); + int mask = buffer.readUnsignedByte(); + boolean hasChance = (mask & 1) == 1; + boolean hasState = (mask & 2) == 2; + boolean hasFluid = (mask & 4) == 4; + boolean hasSupportValue = (mask & 8) == 8; + boolean hasFiller = (mask & 16) == 16; + boolean hasRotation = (mask & 32) == 32; + float chance = hasChance ? buffer.readFloat() : 1.0F; + Holder holder = null; + if (hasState) { + BsonDocument doc = BsonUtil.readFromBinaryStream(buffer); + if (version < 15) { + holder = SelectionPrefabSerializer.legacyStateDecode(doc); + } else if (version < 17) { + holder = ChunkStore.REGISTRY.deserialize(doc, worldVersion); + } else { + holder = ChunkStore.REGISTRY.deserialize(doc); + } + } + + byte supportValue = 0; + if (hasSupportValue) { + supportValue = (byte)(buffer.readByte() & 15); + } + + int filler = 0; + if (hasFiller) { + filler = buffer.readUnsignedShort(); + } + + int rotation = 0; + if (hasRotation) { + rotation = buffer.readUnsignedByte(); + } + + int fluidId = 0; + byte fluidLevel = 0; + if (hasFluid) { + int id = buffer.readInt(); + fluidId = fluidIdMapping.get(id).id; + fluidLevel = buffer.readByte(); + } + + blockEntries[j] = new PrefabBufferBlockEntry(y, block.id, block.key, chance, holder, fluidId, fluidLevel, supportValue, rotation, filler); + } + + int entityCount = buffer.readUnsignedShort(); + Holder[] entityHolders = null; + if (entityCount > 0) { + entityHolders = new Holder[entityCount]; + + for (int j = 0; j < entityCount; j++) { + try { + if (version >= 12 && version < 14) { + entityVersion = buffer.readUnsignedShort(); + } + + BsonDocument entityDocument = BsonUtil.readFromBinaryStream(buffer); + Holder entityHolder; + if (version < 14) { + entityHolder = SelectionPrefabSerializer.legacyEntityDecode(entityDocument, entityVersion); + } else if (version < 17) { + entityHolder = EntityStore.REGISTRY.deserialize(entityDocument, entityVersion); + } else { + entityHolder = EntityStore.REGISTRY.deserialize(entityDocument); + } + + entityHolders[j] = entityHolder; + } catch (Exception var45) { + throw new IllegalStateException("Failed to deserialize entity wrapper #" + i, var45); + } + } + } + + int x = MathUtil.unpackLeft(columnIndex); + int z = MathUtil.unpackRight(columnIndex); + builder.addColumn(x, z, blockEntries, entityHolders); + } + + return builder.build(); + } + } + + @Nonnull + private BinaryPrefabBufferCodec.BlockIdEntry deserializeBlock( + @Nonnull ByteBuf buffer, @Nonnull BlockTypeAssetMap assetMap, @Nullable Function blockMigration + ) { + String blockTypeString = ByteBufUtil.readUTF(buffer); + String blockTypeKey = blockTypeString; + if (blockMigration != null) { + blockTypeKey = blockMigration.apply(blockTypeString); + } + + int blockId = BlockType.getBlockIdOrUnknown(assetMap, blockTypeKey, "Failed to find block '%s'", blockTypeString); + return new BinaryPrefabBufferCodec.BlockIdEntry(blockId, blockTypeKey); + } + + @Nonnull + private BinaryPrefabBufferCodec.FluidIdEntry deserializeFluid(@Nonnull ByteBuf buffer, @Nonnull IndexedLookupTableAssetMap assetMap) { + String fluidName = ByteBufUtil.readUTF(buffer); + int fluidId = Fluid.getFluidIdOrUnknown(assetMap, fluidName, "Failed to find fluid '%s'", fluidName); + return new BinaryPrefabBufferCodec.FluidIdEntry(fluidId, fluidName); + } + + @Nonnull + public ByteBuf serialize(@Nonnull PrefabBuffer prefabBuffer) { + PrefabBuffer.PrefabBufferAccessor access = prefabBuffer.newAccess(); + Int2ObjectOpenHashMap blockNameMapping = new Int2ObjectOpenHashMap<>(); + Int2ObjectOpenHashMap fluidNameMapping = new Int2ObjectOpenHashMap<>(); + int[] counts = new int[3]; + access.forEachRaw((x, z, blocks, o) -> { + counts[0]++; + counts[1] += blocks; + return true; + }, (x, y, z, mask, blockId, chance, holder, support, rotation, filler, o) -> { + if (!blockNameMapping.containsKey(blockId)) { + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + BlockType blockType = assetMap.getAsset(blockId); + if (blockType == null) { + blockType = BlockType.UNKNOWN; + } + + blockNameMapping.put(blockId, blockType.getId().toString()); + } + }, (x, y, z, fluidId, level, o) -> { + if (!fluidNameMapping.containsKey(fluidId)) { + IndexedLookupTableAssetMap assetMap = Fluid.getAssetMap(); + Fluid fluidType = assetMap.getAsset(fluidId); + if (fluidType == null) { + fluidType = Fluid.UNKNOWN; + } + + fluidNameMapping.put(fluidId, fluidType.getId()); + } + }, (x, z, entityHolders, o) -> { + if (entityHolders != null) { + counts[2] += entityHolders.length; + } + }, null); + ByteBuf buffer = Unpooled.buffer(4 + blockNameMapping.size() * 261 + counts[0] * 8 + counts[1] * 13 + counts[2] * 2048); + buffer.writeShort(21); + buffer.writeShort(BlockMigration.getAssetMap().getAssetCount()); + buffer.writeLong(BlockUtil.pack(prefabBuffer.getAnchorX(), prefabBuffer.getAnchorY(), prefabBuffer.getAnchorZ())); + buffer.writeInt(blockNameMapping.size()); + blockNameMapping.int2ObjectEntrySet().fastForEach(entry -> { + buffer.writeInt(entry.getIntKey()); + ByteBufUtil.writeUTF(buffer, entry.getValue()); + }); + buffer.writeInt(fluidNameMapping.size()); + fluidNameMapping.int2ObjectEntrySet().fastForEach(entry -> { + buffer.writeInt(entry.getIntKey()); + ByteBufUtil.writeUTF(buffer, entry.getValue()); + }); + buffer.writeInt(access.getColumnCount()); + access.forEachRaw((x, z, blocks, o) -> { + buffer.writeInt(MathUtil.packInt(x, z)); + buffer.writeInt(blocks); + return true; + }, (x, y, z, entryMask, blockId, chance, holder, supportValue, rotation, filler, o) -> { + buffer.writeShort((short)y); + buffer.writeInt(blockId); + boolean hasChance = chance < 1.0F; + boolean hasComponents = holder != null; + int mask = 0; + if (hasChance) { + mask |= 1; + } + + if (hasComponents) { + mask |= 2; + } + + if ((entryMask & 192) != 0) { + mask |= 4; + } + + if (supportValue != 0) { + mask |= 8; + } + + if (filler != 0) { + mask |= 16; + } + + if (rotation != 0) { + mask |= 32; + } + + buffer.writeByte(mask); + if (hasChance) { + buffer.writeFloat(chance); + } + + if (hasComponents) { + try { + BsonUtil.writeToBinaryStream(buffer, ChunkStore.REGISTRY.serialize(holder)); + } catch (Throwable var16) { + throw new IllegalStateException(String.format("Exception while writing %d, %d, %d state!", x, y, z), var16); + } + } + + if (supportValue != 0) { + buffer.writeByte(supportValue); + } + + if (filler != 0) { + buffer.writeShort(filler); + } + + if (rotation != 0) { + buffer.writeByte(rotation); + } + }, (x, y, z, fluidId, level, o) -> { + buffer.writeInt(fluidId); + buffer.writeByte(level); + }, (x, z, entityHolders, o) -> { + int entities = entityHolders != null ? entityHolders.length : 0; + buffer.writeShort(entities); + + for (int i = 0; i < entities; i++) { + Holder entityHolder = entityHolders[i]; + + try { + BsonDocument document = EntityStore.REGISTRY.serialize(entityHolder); + BsonUtil.writeToBinaryStream(buffer, document); + } catch (Exception var9) { + throw new IllegalStateException(String.format("Failed to write EntityWrapper at %d, %d #%d", x, z, i), var9); + } + } + }, null); + return buffer; + } + + private static class BlockIdEntry { + public int id; + public String key; + + public BlockIdEntry(int id, String key) { + this.id = id; + this.key = key; + } + } + + private static class FluidIdEntry { + public int id; + public String key; + + public FluidIdEntry(int id, String key) { + this.id = id; + this.key = key; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/buffer/BsonPrefabBufferDeserializer.java b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/BsonPrefabBufferDeserializer.java new file mode 100644 index 0000000..2f001c8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/BsonPrefabBufferDeserializer.java @@ -0,0 +1,303 @@ +package com.hypixel.hytale.server.core.prefab.selection.buffer; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockMigration; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.prefab.config.SelectionPrefabSerializer; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBufferBlockEntry; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonArray; +import org.bson.BsonDocument; +import org.bson.BsonInt32; +import org.bson.BsonValue; + +public class BsonPrefabBufferDeserializer implements PrefabBufferDeserializer { + public static final BsonPrefabBufferDeserializer INSTANCE = new BsonPrefabBufferDeserializer(); + public static final BsonInt32 LEGACY_BLOCK_ID_VERSION = new BsonInt32(8); + private static final BsonInt32 DEFAULT_SUPPORT_VALUE = new BsonInt32(0); + private static final BsonInt32 DEFAULT_FILLER_VALUE = new BsonInt32(0); + private static final BsonInt32 DEFAULT_ROTATION_VALUE = new BsonInt32(0); + + public BsonPrefabBufferDeserializer() { + } + + @Nonnull + public PrefabBuffer deserialize(Path path, @Nonnull BsonDocument document) { + BsonValue versionValue = document.get("version"); + int version = versionValue != null ? versionValue.asInt32().getValue() : -1; + if (version > 8) { + throw new IllegalArgumentException("Prefab version is too new: " + version + " by expected 8"); + } else { + int worldVersion = version < 4 ? SelectionPrefabSerializer.readWorldVersion(document) : 0; + BsonValue entityVersionValue = document.get("entityVersion"); + int entityVersion = entityVersionValue != null ? entityVersionValue.asInt32().getValue() : 0; + if (version < 1) { + throw new IllegalArgumentException("Prefab version " + version + " is no longer supported. Please re-save the prefab."); + } else { + Vector3i anchor = new Vector3i(); + anchor.x = document.getInt32("anchorX").getValue(); + anchor.y = document.getInt32("anchorY").getValue(); + anchor.z = document.getInt32("anchorZ").getValue(); + int blockIdVersion = document.getInt32("blockIdVersion", LEGACY_BLOCK_ID_VERSION).getValue(); + Function blockMigration = null; + Map blockMigrationMap = BlockMigration.getAssetMap().getAssetMap(); + int v = blockIdVersion; + + for (BlockMigration migration = blockMigrationMap.get(blockIdVersion); migration != null; migration = blockMigrationMap.get(++v)) { + if (blockMigration == null) { + blockMigration = migration::getMigration; + } else { + blockMigration = blockMigration.andThen(migration::getMigration); + } + } + + Int2ObjectOpenHashMap> columnMap = new Int2ObjectOpenHashMap<>(); + PrefabBuffer.Builder builder = PrefabBuffer.newBuilder(); + builder.setAnchor(anchor); + BsonValue blocksValue = document.get("blocks"); + if (blocksValue != null) { + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + + for (BsonValue blockValue : blocksValue.asArray()) { + BsonDocument blockDocument = blockValue.asDocument(); + int realX = blockDocument.getInt32("x").getValue(); + int realY = blockDocument.getInt32("y").getValue(); + int realZ = blockDocument.getInt32("z").getValue(); + int x = realX - anchor.x; + int y = realY - anchor.y; + int z = realZ - anchor.z; + if (-32768 > x || x > 32767) { + throw new IllegalArgumentException("Violation X: Short.MIN_VALUE < " + x + " < Short.MAX_VALUE"); + } + + if (-32768 > y || y > 32767) { + throw new IllegalArgumentException("Violation Y: Short.MIN_VALUE < " + y + " < Short.MAX_VALUE"); + } + + if (-32768 > z || z > 32767) { + throw new IllegalArgumentException("Violation Z: Short.MIN_VALUE < " + z + " < Short.MAX_VALUE"); + } + + PrefabBufferBlockEntry blockEntry = builder.newBlockEntry(y); + + try { + deserializeBlockType(blockEntry, blockDocument, assetMap, blockMigration); + } catch (Throwable var33) { + throw new IllegalStateException("Failed to load block type for " + path + " at " + realX + ", " + realY + ", " + realZ, var33); + } + + deserializeState(blockEntry, blockDocument, version, worldVersion); + blockEntry.supportValue = (byte)blockDocument.getInt32("support", DEFAULT_SUPPORT_VALUE).getValue(); + blockEntry.filler = blockDocument.getInt32("filler", DEFAULT_FILLER_VALUE).getValue(); + blockEntry.rotation = blockDocument.getInt32("rotation", DEFAULT_ROTATION_VALUE).getValue(); + int columnIndex = MathUtil.packInt(x, z); + Int2ObjectMap column = columnMap.get(columnIndex); + if (column == null) { + columnMap.put(columnIndex, column = new Int2ObjectOpenHashMap<>()); + } + + PrefabBufferBlockEntry existing = column.putIfAbsent(y, blockEntry); + if (existing != null) { + throw new IllegalStateException( + "Block is already present in column. Given: " + + realX + + ", " + + realY + + ", " + + realZ + + ", " + + blockEntry.blockTypeKey + + " - Existing: " + + existing.y + + ", " + + existing.blockTypeKey + ); + } + } + } + + BsonValue fluidsValue = document.get("fluids"); + if (fluidsValue != null) { + IndexedLookupTableAssetMap assetMap = Fluid.getAssetMap(); + + for (BsonValue fluidValue : fluidsValue.asArray()) { + BsonDocument fluidDocument = fluidValue.asDocument(); + int realXx = fluidDocument.getInt32("x").getValue(); + int realYx = fluidDocument.getInt32("y").getValue(); + int realZx = fluidDocument.getInt32("z").getValue(); + int xx = realXx - anchor.x; + int yx = realYx - anchor.y; + int zx = realZx - anchor.z; + if (-32768 > xx || xx > 32767) { + throw new IllegalArgumentException("Violation X: Short.MIN_VALUE < " + xx + " < Short.MAX_VALUE"); + } + + if (-32768 > yx || yx > 32767) { + throw new IllegalArgumentException("Violation Y: Short.MIN_VALUE < " + yx + " < Short.MAX_VALUE"); + } + + if (-32768 > zx || zx > 32767) { + throw new IllegalArgumentException("Violation Z: Short.MIN_VALUE < " + zx + " < Short.MAX_VALUE"); + } + + int columnIndexx = MathUtil.packInt(xx, zx); + Int2ObjectMap columnx = columnMap.get(columnIndexx); + if (columnx == null) { + columnMap.put(columnIndexx, columnx = new Int2ObjectOpenHashMap<>()); + } + + PrefabBufferBlockEntry entry = columnx.computeIfAbsent(yx, builder::newBlockEntry); + String fluidName = fluidDocument.getString("name").getValue(); + entry.fluidId = Fluid.getFluidIdOrUnknown(fluidName, "Unknown fluid '%s'", fluidName); + entry.fluidLevel = (byte)fluidDocument.getInt32("level").getValue(); + } + } + + Int2ObjectOpenHashMap>> entityMap = deserializeEntityHolders(document, anchor, version, entityVersion); + columnMap.int2ObjectEntrySet().fastForEach(entryx -> { + int columnIndexx = entryx.getIntKey(); + int xxx = MathUtil.unpackLeft(columnIndexx); + int zxx = MathUtil.unpackRight(columnIndexx); + Int2ObjectMap columnBlockMap = (Int2ObjectMap)entryx.getValue(); + PrefabBufferBlockEntry[] entries = columnBlockMap.values().toArray(PrefabBufferBlockEntry[]::new); + Arrays.sort(entries, Comparator.comparingInt(o -> o.y)); + List> entityColumn = entityMap.remove(columnIndexx); + Holder[] entityArray = entityColumn != null && !entityColumn.isEmpty() ? entityColumn.toArray(Holder[]::new) : null; + builder.addColumn(xxx, zxx, entries, entityArray); + }); + entityMap.int2ObjectEntrySet().fastForEach(entryx -> { + int columnIndexx = entryx.getIntKey(); + int xxx = MathUtil.unpackLeft(columnIndexx); + int zxx = MathUtil.unpackRight(columnIndexx); + List> entityColumn = (List>)entryx.getValue(); + Holder[] entityArray = !entityColumn.isEmpty() ? entityColumn.toArray(Holder[]::new) : null; + if (entityArray != null) { + builder.addColumn(xxx, zxx, PrefabBufferBlockEntry.EMPTY_ARRAY, entityArray); + } + }); + return builder.build(); + } + } + } + + private static void deserializeBlockType( + @Nonnull PrefabBufferBlockEntry blockEntry, + @Nonnull BsonDocument blockDocument, + @Nonnull BlockTypeAssetMap assetMap, + @Nullable Function blockMigration + ) { + String blockType = blockDocument.getString("name").getValue(); + int idx = blockType.indexOf(37); + String blockTypeStr; + if (idx != -1) { + blockTypeStr = blockType.substring(idx + 1); + } else { + blockTypeStr = blockType; + } + + blockEntry.blockTypeKey = blockTypeStr; + if (blockMigration != null) { + blockEntry.blockTypeKey = blockMigration.apply(blockEntry.blockTypeKey); + } + + blockEntry.blockId = BlockType.getBlockIdOrUnknown(assetMap, blockEntry.blockTypeKey, "Failed to find block. Given %s", blockTypeStr); + if (idx != -1) { + String chanceString = blockType.substring(0, idx); + float chancePercent = Float.parseFloat(chanceString); + if (chancePercent < 0.0F) { + throw new IllegalArgumentException("Chance is smaller than 0%. Given: " + chancePercent); + } + + if (chancePercent > 100.0F) { + throw new IllegalArgumentException("Chance is larger than 100%. Given: " + chancePercent); + } + + blockEntry.chance = chancePercent / 100.0F; + } + } + + private static void deserializeState(@Nonnull PrefabBufferBlockEntry blockEntry, @Nonnull BsonDocument blockDocument, int version, int worldVersion) { + if (version <= 2) { + BsonValue stateValue = blockDocument.get("state"); + if (stateValue != null) { + blockEntry.state = SelectionPrefabSerializer.legacyStateDecode(stateValue.asDocument()); + } + } else { + BsonValue stateValue = blockDocument.get("components"); + if (stateValue != null) { + if (version < 4) { + blockEntry.state = ChunkStore.REGISTRY.deserialize(stateValue.asDocument(), worldVersion); + } else { + blockEntry.state = ChunkStore.REGISTRY.deserialize(stateValue.asDocument()); + } + } + } + } + + @Nonnull + private static Int2ObjectOpenHashMap>> deserializeEntityHolders( + @Nonnull BsonDocument document, @Nonnull Vector3i anchor, int version, int entityVersion + ) { + BsonValue entitiesValue = document.get("entities"); + Int2ObjectOpenHashMap>> entityMap = new Int2ObjectOpenHashMap<>(); + if (entitiesValue == null) { + return entityMap; + } else { + BsonArray entitiesArray = entitiesValue.asArray(); + int i = 0; + + for (int size = entitiesArray.size(); i < size; i++) { + BsonDocument entityDocument = entitiesArray.get(i).asDocument(); + + try { + Holder entityHolder; + if (version <= 1) { + entityHolder = SelectionPrefabSerializer.legacyEntityDecode(entityDocument, entityVersion); + } else { + entityHolder = EntityStore.REGISTRY.deserialize(entityDocument); + } + + TransformComponent transformComponent = entityHolder.getComponent(TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + position.add(-anchor.x, -anchor.y, -anchor.z); + int x = MathUtil.floor(position.getX()) & 65535; + int z = MathUtil.floor(position.getZ()) & 65535; + int columnIndex = MathUtil.packInt(x, z); + List> entityColumn = entityMap.get(columnIndex); + if (entityColumn == null) { + entityMap.put(columnIndex, entityColumn = new ObjectArrayList<>()); + } + + entityColumn.add(entityHolder); + } catch (Exception var17) { + throw new IllegalStateException("Failed to load entity wrapper #" + i + ": " + entityDocument, var17); + } + } + + return entityMap; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabBufferCall.java b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabBufferCall.java new file mode 100644 index 0000000..e849f7e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabBufferCall.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.server.core.prefab.selection.buffer; + +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import java.util.Random; + +public class PrefabBufferCall { + public Random random; + public PrefabRotation rotation; + + public PrefabBufferCall() { + } + + public PrefabBufferCall(Random random, PrefabRotation rotation) { + this.random = random; + this.rotation = rotation; + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabBufferCodec.java b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabBufferCodec.java new file mode 100644 index 0000000..0f7c29c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabBufferCodec.java @@ -0,0 +1,4 @@ +package com.hypixel.hytale.server.core.prefab.selection.buffer; + +public interface PrefabBufferCodec extends PrefabBufferSerializer, PrefabBufferDeserializer { +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabBufferDeserializer.java b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabBufferDeserializer.java new file mode 100644 index 0000000..4ceca07 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabBufferDeserializer.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.server.core.prefab.selection.buffer; + +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer; +import java.nio.file.Path; + +public interface PrefabBufferDeserializer { + PrefabBuffer deserialize(Path var1, T var2); +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabBufferSerializer.java b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabBufferSerializer.java new file mode 100644 index 0000000..26d067a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabBufferSerializer.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.server.core.prefab.selection.buffer; + +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer; + +public interface PrefabBufferSerializer { + T serialize(PrefabBuffer var1); +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabBufferUtil.java b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabBufferUtil.java new file mode 100644 index 0000000..2415ec1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabBufferUtil.java @@ -0,0 +1,247 @@ +package com.hypixel.hytale.server.core.prefab.selection.buffer; + +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.IPrefabBuffer; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer; +import com.hypixel.hytale.server.core.util.BsonUtil; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.StampedLock; +import java.util.logging.Level; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabBufferUtil { + public static final Path CACHE_PATH = Options.getOrDefault(Options.PREFAB_CACHE_DIRECTORY, Options.getOptionSet(), Path.of(".cache/prefabs")); + public static final String LPF_FILE_SUFFIX = ".lpf"; + public static final String JSON_FILE_SUFFIX = ".prefab.json"; + public static final String JSON_LPF_FILE_SUFFIX = ".prefab.json.lpf"; + public static final String FILE_SUFFIX_REGEX = "((!\\.prefab\\.json)\\.lpf|\\.prefab\\.json)$"; + public static final Pattern FILE_SUFFIX_PATTERN = Pattern.compile("((!\\.prefab\\.json)\\.lpf|\\.prefab\\.json)$"); + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final Map> CACHE = new ConcurrentHashMap<>(); + + public PrefabBufferUtil() { + } + + @Nonnull + public static IPrefabBuffer getCached(@Nonnull Path path) { + WeakReference reference = CACHE.get(path); + PrefabBufferUtil.CachedEntry cachedPrefab = reference != null ? reference.get() : null; + if (cachedPrefab != null) { + long stamp = cachedPrefab.lock.readLock(); + + try { + if (cachedPrefab.buffer != null) { + return cachedPrefab.buffer.newAccess(); + } + } finally { + cachedPrefab.lock.unlockRead(stamp); + } + } + + cachedPrefab = getOrCreateCacheEntry(path); + long stamp = cachedPrefab.lock.writeLock(); + + PrefabBuffer.PrefabBufferAccessor var5; + try { + if (cachedPrefab.buffer == null) { + cachedPrefab.buffer = loadBuffer(path); + return cachedPrefab.buffer.newAccess(); + } + + var5 = cachedPrefab.buffer.newAccess(); + } finally { + cachedPrefab.lock.unlockWrite(stamp); + } + + return var5; + } + + @Nonnull + public static PrefabBuffer loadBuffer(@Nonnull Path path) { + String fileNameStr = path.getFileName().toString(); + String fileName = fileNameStr.replace(".prefab.json.lpf", "").replace(".prefab.json", ""); + Path lpfPath = path.resolveSibling(fileName + ".lpf"); + if (Files.exists(lpfPath)) { + return loadFromLPF(path, lpfPath); + } else { + Path cachedLpfPath; + AssetPack pack; + if (AssetModule.get().isAssetPathImmutable(path)) { + Path lpfConvertedPath = path.resolveSibling(fileName + ".prefab.json.lpf"); + if (Files.exists(lpfConvertedPath)) { + return loadFromLPF(path, lpfConvertedPath); + } + + pack = AssetModule.get().findAssetPackForPath(path); + if (pack != null) { + String safePackName = FileUtil.INVALID_FILENAME_CHARACTERS.matcher(pack.getName()).replaceAll("_"); + cachedLpfPath = CACHE_PATH.resolve(safePackName).resolve(pack.getRoot().relativize(lpfConvertedPath).toString()); + } else if (lpfConvertedPath.getRoot() != null) { + cachedLpfPath = CACHE_PATH.resolve(lpfConvertedPath.subpath(1, lpfConvertedPath.getNameCount()).toString()); + } else { + cachedLpfPath = CACHE_PATH.resolve(lpfConvertedPath.toString()); + } + } else { + cachedLpfPath = path.resolveSibling(fileName + ".prefab.json.lpf"); + pack = null; + } + + Path jsonPath = path.resolveSibling(fileName + ".prefab.json"); + if (!Files.exists(jsonPath)) { + try { + Files.deleteIfExists(cachedLpfPath); + } catch (IOException var8) { + } + + throw new Error("Error loading Prefab from " + jsonPath.toAbsolutePath() + " (.lpf and .prefab.json) File NOT found!"); + } else { + try { + return loadFromJson(pack, path, cachedLpfPath, jsonPath); + } catch (IOException var9) { + throw SneakyThrow.sneakyThrow(var9); + } + } + } + } + + @Nonnull + public static CompletableFuture writeToFileAsync(@Nonnull PrefabBuffer prefab, @Nonnull Path path) { + return CompletableFuture.runAsync(SneakyThrow.sneakyRunnable(() -> { + try (SeekableByteChannel channel = Files.newByteChannel(path, FileUtil.DEFAULT_WRITE_OPTIONS)) { + channel.write(BinaryPrefabBufferCodec.INSTANCE.serialize(prefab).nioBuffer()); + } + })); + } + + public static PrefabBuffer readFromFile(@Nonnull Path path) { + return readFromFileAsync(path).join(); + } + + @Nonnull + public static CompletableFuture readFromFileAsync(@Nonnull Path path) { + return CompletableFuture.supplyAsync(SneakyThrow.sneakySupplier(() -> { + PrefabBuffer var4; + try (SeekableByteChannel channel = Files.newByteChannel(path)) { + int size = (int)channel.size(); + ByteBuf buf = Unpooled.buffer(size); + buf.writerIndex(size); + if (channel.read(buf.internalNioBuffer(0, size)) != size) { + throw new IOException("Didn't read full file!"); + } + + var4 = BinaryPrefabBufferCodec.INSTANCE.deserialize(path, buf); + } + + return var4; + })); + } + + @Nonnull + public static PrefabBuffer loadFromLPF(@Nonnull Path path, @Nonnull Path realPath) { + try { + return readFromFile(realPath); + } catch (Exception var3) { + throw new Error("Error while loading prefab " + path.toAbsolutePath() + " from " + realPath.toAbsolutePath(), var3); + } + } + + @Nonnull + public static PrefabBuffer loadFromJson(@Nullable AssetPack pack, Path path, @Nonnull Path cachedLpfPath, @Nonnull Path jsonPath) throws IOException { + BasicFileAttributes cachedAttr = null; + + try { + cachedAttr = Files.readAttributes(cachedLpfPath, BasicFileAttributes.class); + } catch (IOException var10) { + } + + FileTime targetModifiedTime; + if (pack != null && pack.isImmutable()) { + targetModifiedTime = Files.readAttributes(pack.getPackLocation(), BasicFileAttributes.class).lastModifiedTime(); + } else { + targetModifiedTime = Files.readAttributes(jsonPath, BasicFileAttributes.class).lastModifiedTime(); + } + + if (cachedAttr != null && targetModifiedTime.compareTo(cachedAttr.lastModifiedTime()) <= 0) { + try { + return readFromFile(cachedLpfPath); + } catch (CompletionException var11) { + if (!Options.getOptionSet().has(Options.VALIDATE_PREFABS)) { + if (var11.getCause() instanceof UpdateBinaryPrefabException) { + LOGGER.at(Level.FINE).log("Ignoring LPF %s due to: %s", path, var11.getMessage()); + } else { + LOGGER.at(Level.WARNING).withCause(new SkipSentryException(var11)).log("Failed to load %s", cachedLpfPath); + } + } + } + } + + try { + PrefabBuffer buffer = BsonPrefabBufferDeserializer.INSTANCE.deserialize(jsonPath, BsonUtil.readDocument(jsonPath, false).join()); + if (!Options.getOptionSet().has(Options.DISABLE_CPB_BUILD)) { + try { + Files.createDirectories(cachedLpfPath.getParent()); + writeToFileAsync(buffer, cachedLpfPath).thenRun(() -> { + try { + Files.setLastModifiedTime(cachedLpfPath, targetModifiedTime); + } catch (IOException var3x) { + } + }).exceptionally(throwable -> { + HytaleLogger.getLogger().at(Level.FINE).withCause(new SkipSentryException(throwable)).log("Failed to save prefab cache %s", cachedLpfPath); + return null; + }); + } catch (IOException var8) { + LOGGER.at(Level.FINE).log("Cannot create cache directory for %s: %s", cachedLpfPath, var8.getMessage()); + } + } + + return buffer; + } catch (Exception var9) { + throw new Error("Error while loading Prefab from " + jsonPath.toAbsolutePath(), var9); + } + } + + @Nonnull + private static PrefabBufferUtil.CachedEntry getOrCreateCacheEntry(Path path) { + PrefabBufferUtil.CachedEntry[] temp = new PrefabBufferUtil.CachedEntry[1]; + CACHE.compute(path, (p, ref) -> { + if (ref != null) { + PrefabBufferUtil.CachedEntry cached = ref.get(); + temp[0] = cached; + if (cached != null) { + return ref; + } + } + + return new WeakReference<>(temp[0] = new PrefabBufferUtil.CachedEntry()); + }); + return temp[0]; + } + + private static class CachedEntry { + private final StampedLock lock = new StampedLock(); + private PrefabBuffer buffer; + + private CachedEntry() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabLoader.java b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabLoader.java new file mode 100644 index 0000000..8f53b25 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabLoader.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.server.core.prefab.selection.buffer; + +import com.hypixel.hytale.server.core.util.io.FileUtil; +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.NotDirectoryException; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import javax.annotation.Nonnull; + +public class PrefabLoader { + private static final char JSON_FILEPATH_SEPARATOR = '.'; + private final Path rootFolder; + + public PrefabLoader(Path rootFolder) { + this.rootFolder = rootFolder; + } + + public Path getRootFolder() { + return this.rootFolder; + } + + public void resolvePrefabs(@Nonnull String prefabName, @Nonnull Consumer pathConsumer) throws IOException { + resolvePrefabs(this.rootFolder, prefabName, pathConsumer); + } + + public static void resolvePrefabs(@Nonnull Path rootFolder, @Nonnull String prefabName, @Nonnull Consumer pathConsumer) throws IOException { + if (prefabName.endsWith(".*")) { + resolvePrefabFolder(rootFolder, prefabName, pathConsumer); + } else { + Path prefabPath = rootFolder.resolve(prefabName.replace('.', File.separatorChar) + ".prefab.json"); + if (!Files.exists(prefabPath)) { + throw new NoSuchFileException(prefabPath.toString()); + } + + pathConsumer.accept(prefabPath); + } + } + + public static void resolvePrefabFolder(@Nonnull Path rootFolder, @Nonnull String prefabName, @Nonnull final Consumer pathConsumer) throws IOException { + String prefabDirectory = prefabName.substring(0, prefabName.length() - 2); + Path directoryPath = rootFolder.resolve(prefabDirectory.replace('.', File.separatorChar)); + if (!Files.isDirectory(directoryPath)) { + throw new NotDirectoryException(directoryPath.toString()); + } else { + Files.walkFileTree(directoryPath, FileUtil.DEFAULT_WALK_TREE_OPTIONS_SET, Integer.MAX_VALUE, new SimpleFileVisitor() { + @Nonnull + public FileVisitResult visitFile(@Nonnull Path file, BasicFileAttributes attrs) { + String fileName = file.getFileName().toString(); + Matcher matcher = PrefabBufferUtil.FILE_SUFFIX_PATTERN.matcher(fileName); + if (matcher.find()) { + String fileNameNoExtension = matcher.replaceAll(""); + pathConsumer.accept(file.resolveSibling(fileNameNoExtension)); + } + + return FileVisitResult.CONTINUE; + } + }); + } + } + + @Nonnull + public static String resolveRelativeJsonPath(@Nonnull String prefabName, @Nonnull Path prefabPath, @Nonnull Path rootPrefabDir) { + if (!prefabName.endsWith(".*")) { + return prefabName; + } else { + String filepath = rootPrefabDir.relativize(prefabPath).toString(); + int start = prefabName.equals(".*") ? 0 : prefabName.length() - 1; + int length = getFilepathLengthNoExtension(filepath); + if (length < start) { + throw new IllegalArgumentException(String.format("Prefab key '%s' is longer than its filepath '%s'", prefabName, filepath)); + } else { + char[] chars = new char[length - start]; + filepath.getChars(start, length, chars, 0); + + for (int i = 0; i < chars.length; i++) { + if (chars[i] == File.separatorChar) { + chars[i] = '.'; + } + } + + return new String(chars); + } + } + } + + private static int getFilepathLengthNoExtension(@Nonnull String filepath) { + int extensionSize = 0; + if (filepath.endsWith(".prefab.json")) { + extensionSize = ".prefab.json".length(); + } else if (filepath.endsWith(".prefab.json.lpf")) { + extensionSize = ".prefab.json.lpf".length(); + } + + return filepath.length() - extensionSize; + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabSupplier.java b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabSupplier.java new file mode 100644 index 0000000..be478c9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/PrefabSupplier.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.server.core.prefab.selection.buffer; + +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.IPrefabBuffer; +import java.util.function.Supplier; + +public interface PrefabSupplier extends Supplier { +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/buffer/UpdateBinaryPrefabException.java b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/UpdateBinaryPrefabException.java new file mode 100644 index 0000000..6ba0681 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/UpdateBinaryPrefabException.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.server.core.prefab.selection.buffer; + +public class UpdateBinaryPrefabException extends RuntimeException { + public UpdateBinaryPrefabException(String message) { + super(message); + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/buffer/impl/IPrefabBuffer.java b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/impl/IPrefabBuffer.java new file mode 100644 index 0000000..2cc130b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/impl/IPrefabBuffer.java @@ -0,0 +1,198 @@ +package com.hypixel.hytale.server.core.prefab.selection.buffer.impl; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import com.hypixel.hytale.server.core.prefab.PrefabWeights; +import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabBufferCall; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface IPrefabBuffer { + IPrefabBuffer.ColumnPredicate ALL_COLUMNS = (x, z, blocks, o) -> true; + + int getAnchorX(); + + int getAnchorY(); + + int getAnchorZ(); + + int getMinX(@Nonnull PrefabRotation var1); + + int getMinY(); + + int getMinZ(@Nonnull PrefabRotation var1); + + int getMaxX(@Nonnull PrefabRotation var1); + + int getMaxY(); + + int getMaxZ(@Nonnull PrefabRotation var1); + + default int getMinX() { + return this.getMinX(PrefabRotation.ROTATION_0); + } + + default int getMinZ() { + return this.getMinZ(PrefabRotation.ROTATION_0); + } + + default int getMaxX() { + return this.getMaxX(PrefabRotation.ROTATION_0); + } + + default int getMaxZ() { + return this.getMaxZ(PrefabRotation.ROTATION_0); + } + + int getMinYAt(@Nonnull PrefabRotation var1, int var2, int var3); + + int getMaxYAt(@Nonnull PrefabRotation var1, int var2, int var3); + + int getColumnCount(); + + @Nonnull + PrefabBuffer.ChildPrefab[] getChildPrefabs(); + + default int getMaximumExtend() { + int max = 0; + + for (PrefabRotation rotation : PrefabRotation.VALUES) { + int x = this.getMaxX(rotation) - this.getMinX(rotation); + if (x > max) { + max = x; + } + + int z = this.getMaxZ(rotation) - this.getMinZ(rotation); + if (z > max) { + max = z; + } + } + + return max; + } + + void forEach( + @Nonnull IPrefabBuffer.ColumnPredicate var1, + @Nonnull IPrefabBuffer.BlockConsumer var2, + @Nullable IPrefabBuffer.EntityConsumer var3, + @Nullable IPrefabBuffer.ChildConsumer var4, + @Nonnull T var5 + ); + + void forEachRaw( + @Nonnull IPrefabBuffer.ColumnPredicate var1, + @Nonnull IPrefabBuffer.RawBlockConsumer var2, + @Nonnull IPrefabBuffer.FluidConsumer var3, + @Nullable IPrefabBuffer.EntityConsumer var4, + @Nullable T var5 + ); + + boolean forEachRaw( + @Nonnull IPrefabBuffer.ColumnPredicate var1, + @Nonnull IPrefabBuffer.RawBlockPredicate var2, + @Nonnull IPrefabBuffer.FluidPredicate var3, + @Nullable IPrefabBuffer.EntityPredicate var4, + @Nullable T var5 + ); + + void release(); + + default boolean compare(@Nonnull IPrefabBuffer.BlockComparingPredicate blockComparingPredicate, @Nonnull T t) { + return this.forEachRaw( + iterateAllColumns(), + (x, y, z, blockId, chance, holder, support, rotation, filler, o) -> blockComparingPredicate.test(x, y, z, blockId, rotation, holder, (T)o), + (x, y, z, fluidId, level, o) -> true, + (x, z, entityWrappers, o) -> true, + t + ); + } + + default boolean compare( + @Nonnull IPrefabBuffer.BlockComparingPrefabPredicate blockComparingIterator, @Nonnull T t, @Nonnull IPrefabBuffer secondPrefab + ) { + throw new UnsupportedOperationException("Not implemented! Please implement some inefficient default here!"); + } + + int getBlockId(int var1, int var2, int var3); + + int getFiller(int var1, int var2, int var3); + + int getRotationIndex(int var1, int var2, int var3); + + @Nonnull + static IPrefabBuffer.ColumnPredicate iterateAllColumns() { + return (IPrefabBuffer.ColumnPredicate)ALL_COLUMNS; + } + + @FunctionalInterface + public interface BlockComparingPredicate { + boolean test(int var1, int var2, int var3, int var4, int var5, Holder var6, T var7); + } + + @FunctionalInterface + public interface BlockComparingPrefabPredicate { + boolean test( + int var1, + int var2, + int var3, + int var4, + Holder var5, + float var6, + int var7, + int var8, + int var9, + Holder var10, + float var11, + int var12, + int var13, + T var14 + ); + } + + @FunctionalInterface + public interface BlockConsumer { + void accept(int var1, int var2, int var3, int var4, @Nullable Holder var5, int var6, int var7, int var8, T var9, int var10, int var11); + } + + @FunctionalInterface + public interface ChildConsumer { + void accept(int var1, int var2, int var3, String var4, boolean var5, boolean var6, boolean var7, PrefabWeights var8, PrefabRotation var9, T var10); + } + + @FunctionalInterface + public interface ColumnPredicate { + boolean test(int var1, int var2, int var3, T var4); + } + + @FunctionalInterface + public interface EntityConsumer { + void accept(int var1, int var2, @Nullable Holder[] var3, T var4); + } + + @FunctionalInterface + public interface EntityPredicate { + boolean test(int var1, int var2, @Nonnull Holder[] var3, T var4); + } + + @FunctionalInterface + public interface FluidConsumer { + void accept(int var1, int var2, int var3, int var4, byte var5, T var6); + } + + @FunctionalInterface + public interface FluidPredicate { + boolean test(int var1, int var2, int var3, int var4, byte var5, T var6); + } + + @FunctionalInterface + public interface RawBlockConsumer { + void accept(int var1, int var2, int var3, int var4, int var5, float var6, Holder var7, int var8, int var9, int var10, T var11); + } + + @FunctionalInterface + public interface RawBlockPredicate { + boolean test(int var1, int var2, int var3, int var4, float var5, Holder var6, int var7, int var8, int var9, T var10); + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/buffer/impl/PrefabBuffer.java b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/impl/PrefabBuffer.java new file mode 100644 index 0000000..2c3118e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/impl/PrefabBuffer.java @@ -0,0 +1,1185 @@ +package com.hypixel.hytale.server.core.prefab.selection.buffer.impl; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.modules.prefabspawner.PrefabSpawnerState; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import com.hypixel.hytale.server.core.prefab.PrefabWeights; +import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabBufferCall; +import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.io.ByteBufUtil; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntIterator; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabBuffer { + public static final float DEFAULT_CHANCE = 1.0F; + @Nonnull + private final Vector3i anchor; + @Nonnull + private final Vector3i min; + @Nonnull + private final Vector3i max; + @Nonnull + private final Int2ObjectMap columns; + @Nonnull + private final PrefabBuffer.ChildPrefab[] childPrefabs; + @Nullable + private ByteBuf buf; + + private PrefabBuffer( + @Nonnull ByteBuf buf, + @Nonnull Vector3i anchor, + @Nonnull Vector3i min, + @Nonnull Vector3i max, + @Nonnull Int2ObjectMap columns, + @Nonnull PrefabBuffer.ChildPrefab[] childPrefabs + ) { + this.buf = buf; + this.anchor = anchor; + this.min = min; + this.max = max; + this.columns = columns; + this.childPrefabs = childPrefabs; + } + + @Nonnull + public static PrefabBuffer.Builder newBuilder() { + return new PrefabBuffer.Builder(); + } + + public int getAnchorX() { + return this.anchor.getX(); + } + + public int getAnchorY() { + return this.anchor.getY(); + } + + public int getAnchorZ() { + return this.anchor.getZ(); + } + + @Nonnull + public PrefabBuffer.PrefabBufferAccessor newAccess() { + this.checkReleased(); + return new PrefabBuffer.PrefabBufferAccessor(this); + } + + public void release() { + this.checkReleased(); + this.buf.release(); + this.buf = null; + } + + private void checkReleased() { + if (this.buf == null) { + throw new IllegalStateException("PrefabBuffer has already been released!"); + } + } + + public interface BlockMaskConstants { + int ID_IS_BYTE = 1; + int ID_IS_SHORT = 2; + int ID_IS_INT = 3; + int ID_MASK = 3; + int HAS_CHANCE = 4; + int OFFSET_IS_BYTE = 8; + int OFFSET_IS_SHORT = 16; + int OFFSET_IS_INT = 24; + int OFFSET_MASK = 24; + int HAS_COMPONENTS = 32; + int FLUID_IS_BYTE = 64; + int FLUID_IS_SHORT = 128; + int FLUID_IS_INT = 192; + int FLUID_MASK = 192; + int SUPPORT_MASK = 3840; + int SUPPORT_OFFSET = 8; + int HAS_FILLER = 4096; + int HAS_ROTATION = 8192; + + static int getBlockMask( + int blockBytes, int fluidBytes, boolean chance, int offsetBytes, @Nullable Holder holder, byte supportValue, int rotation, int filler + ) { + int mask = 0; + + mask = switch (blockBytes) { + case 0 -> { + } + case 1 -> 1; + case 2 -> 2; + default -> throw new IllegalArgumentException("Unsupported amount of bytes for blocks (0, 1, 2, 4). Given: " + blockBytes); + case 4 -> 3; + }; + if (chance) { + mask |= 4; + } + mask = switch (offsetBytes) { + case 0 -> { + } + case 1 -> 8; + case 2 -> 16; + default -> throw new IllegalArgumentException("Unsupported amount of bytes for offset (0, 1, 2, 4). Given: " + offsetBytes); + case 4 -> 24; + }; + if (holder != null) { + mask |= 32; + } + mask = switch (fluidBytes) { + case 0 -> { + } + case 1 -> 64; + case 2 -> 128; + default -> throw new IllegalArgumentException("Unsupported amount of bytes for fluids (0, 1, 2, 4). Given: " + fluidBytes); + case 4 -> 192; + } | supportValue << 8 & 3840; + if (filler != 0) { + mask |= 4096; + } + + if (rotation != 0) { + mask |= 8192; + } + + return mask; + } + + static int getSkipBytes(int mask) { + int bytes = 0; + bytes += getBlockBytes(mask); + bytes += getOffsetBytes(mask); + if (hasChance(mask)) { + bytes += 4; + } + + bytes += getFluidBytes(mask); + if (hasFiller(mask)) { + bytes += 2; + } + + if (hasRotation(mask)) { + bytes++; + } + + return bytes; + } + + static boolean hasChance(int mask) { + return (mask & 4) == 4; + } + + static boolean hasFiller(int mask) { + return (mask & 4096) == 4096; + } + + static boolean hasRotation(int mask) { + return (mask & 8192) == 8192; + } + + static int getBlockBytes(int mask) { + return switch (mask & 3) { + case 1 -> 1; + case 2 -> 2; + case 3 -> 4; + default -> 0; + }; + } + + static int getOffsetBytes(int mask) { + return switch (mask & 24) { + case 8 -> 1; + case 16 -> 2; + case 24 -> 4; + default -> 0; + }; + } + + static int getFluidBytes(int mask) { + return switch (mask & 192) { + case 64 -> 2; + case 128 -> 3; + case 192 -> 5; + default -> 0; + }; + } + + static int getSupportValue(int mask) { + return (mask & 3840) >> 8; + } + + static boolean hasComponents(int mask) { + return (mask & 32) == 32; + } + } + + public static class Builder { + private final ByteBuf buf = Unpooled.buffer(); + @Nonnull + private final Vector3i min = new Vector3i(Vector3i.MAX); + @Nonnull + private final Vector3i max = new Vector3i(Vector3i.MIN); + @Nonnull + private final Int2ObjectMap columns = new Int2ObjectOpenHashMap<>(); + @Nonnull + private final List childPrefabs = new ObjectArrayList<>(0); + private Vector3i anchor = Vector3i.ZERO; + + private Builder() { + } + + public void setAnchor(@Nonnull Vector3i anchor) { + this.anchor = anchor; + } + + public void addColumn(int x, int z, @Nonnull PrefabBufferBlockEntry[] entries, @Nullable Holder[] entityHolders) { + if (x < -32768) { + throw new IllegalArgumentException("x is smaller than -32768. Given: " + x); + } else if (x > 32767) { + throw new IllegalArgumentException("x is larger than 32767. Given: " + x); + } else if (z < -32768) { + throw new IllegalArgumentException("z is smaller than -32768. Given: " + z); + } else if (z > 32767) { + throw new IllegalArgumentException("z is larger than 32767. Given: " + z); + } else { + int columnIndex = MathUtil.packInt((short)x, (short)z); + if (this.columns.containsKey(columnIndex)) { + throw new IllegalStateException("Column is already set! Given: " + x + ", " + z); + } else { + int blockCount = entries.length; + Int2ObjectOpenHashMap> holderMap = new Int2ObjectOpenHashMap<>(); + if (blockCount != 0 || entityHolders != null && entityHolders.length != 0) { + int readerIndex = this.buf.writerIndex(); + this.buf.writeInt(blockCount); + if (blockCount > 0) { + int offset = entries[0].y; + if (offset < this.min.y) { + this.min.y = offset; + } + + this.buf.writeInt(offset - 1); + offset = Integer.MIN_VALUE; + + for (int i = 0; i < blockCount; i++) { + PrefabBufferBlockEntry entry = entries[i]; + int y = entry.y; + int blockId = entry.blockId; + float chance = entry.chance; + Holder holder = entry.state; + int fluidId = entry.fluidId; + byte fluidLevel = entry.fluidLevel; + if (y <= offset) { + throw new IllegalArgumentException("Y Values are not sequential. " + offset + " -> " + y); + } + + int offsetx = i == 0 ? 0 : y - offset; + if (offsetx > 65535) { + throw new IllegalArgumentException("Offset is larger than 65535. Given: " + offsetx); + } + + boolean hasChance = chance < 1.0F; + int blockBytes = MathUtil.byteCount(blockId); + int offsetBytes = offsetx == 1 ? 0 : MathUtil.byteCount(offsetx); + int fluidBytes = MathUtil.byteCount(fluidId); + int mask = PrefabBuffer.BlockMaskConstants.getBlockMask( + blockBytes, fluidBytes, hasChance, offsetBytes, holder, entry.supportValue, entry.rotation, entry.filler + ); + this.buf.writeShort(mask); + ByteBufUtil.writeNumber(this.buf, blockBytes, blockId); + ByteBufUtil.writeNumber(this.buf, offsetBytes, offsetx); + if (hasChance) { + this.buf.writeFloat(chance); + } + + if (entry.rotation != 0) { + this.buf.writeByte(entry.rotation); + } + + if (entry.filler != 0) { + this.buf.writeShort(entry.filler); + } + + if (fluidId != 0) { + ByteBufUtil.writeNumber(this.buf, fluidBytes, fluidId); + this.buf.writeByte(fluidLevel); + } + + if (holder != null) { + holderMap.put(y, holder); + this.handleBlockComponents(entry.rotation, x, y, z, holder); + } + + offset = y; + } + + if (offset > this.max.y) { + this.max.y = offset; + } + } + + if (x < this.min.x) { + this.min.x = x; + } + + if (x > this.max.x) { + this.max.x = x; + } + + if (z < this.min.z) { + this.min.z = z; + } + + if (z > this.max.z) { + this.max.z = z; + } + + if (holderMap.isEmpty()) { + holderMap = null; + } + + PrefabBufferColumn column = new PrefabBufferColumn(readerIndex, entityHolders, holderMap); + this.columns.put(columnIndex, column); + } + } + } + } + + private void handleBlockComponents(int blockRotation, int x, int y, int z, @Nonnull Holder holder) { + ComponentType componentType = BlockStateModule.get().getComponentType(PrefabSpawnerState.class); + PrefabSpawnerState spawnerState = holder.getComponent(componentType); + if (spawnerState != null) { + String path = spawnerState.getPrefabPath(); + if (path == null) { + HytaleLogger.getLogger().at(Level.WARNING).log("Prefab spawner at %d, %d, %d is missing prefab path!", x, y, z); + } else { + PrefabWeights weights = spawnerState.getPrefabWeights(); + PrefabRotation rotation = PrefabRotation.fromRotation(RotationTuple.get(blockRotation).yaw()); + this.addChildPrefab( + x, y, z, path, spawnerState.isFitHeightmap(), spawnerState.isInheritSeed(), spawnerState.isInheritHeightCondition(), weights, rotation + ); + } + } + } + + public void addChildPrefab( + int x, + int y, + int z, + @Nonnull String path, + boolean fitHeightmap, + boolean inheritSeed, + boolean inheritHeightCondition, + @Nullable PrefabWeights weights, + @Nonnull PrefabRotation rotation + ) { + this.childPrefabs.add(new PrefabBuffer.ChildPrefab(x, y, z, path, fitHeightmap, inheritSeed, inheritHeightCondition, weights, rotation)); + } + + @Nonnull + public PrefabBufferBlockEntry newBlockEntry(int y) { + return new PrefabBufferBlockEntry(y); + } + + @Nonnull + public PrefabBuffer build() { + ByteBuf buffer = Unpooled.copiedBuffer(this.buf); + this.buf.release(); + PrefabBuffer.ChildPrefab[] childPrefabArray = this.childPrefabs.toArray(PrefabBuffer.ChildPrefab[]::new); + if (this.columns.isEmpty()) { + this.min.assign(0); + this.max.assign(0); + } + + return new PrefabBuffer(buffer, this.anchor, this.min, this.max, this.columns, childPrefabArray); + } + } + + public static class ChildPrefab { + private final int x; + private final int y; + private final int z; + @Nonnull + private final String path; + private final boolean fitHeightmap; + private final boolean inheritSeed; + private final boolean inheritHeightCondition; + @Nonnull + private final PrefabWeights weights; + @Nonnull + private final PrefabRotation rotation; + + private ChildPrefab( + int x, + int y, + int z, + @Nonnull String path, + boolean fitHeightmap, + boolean inheritSeed, + boolean inheritHeightCondition, + @Nonnull PrefabWeights weights, + @Nonnull PrefabRotation rotation + ) { + this.x = x; + this.y = y; + this.z = z; + this.path = path; + this.fitHeightmap = fitHeightmap; + this.inheritSeed = inheritSeed; + this.inheritHeightCondition = inheritHeightCondition; + this.weights = weights; + this.rotation = rotation; + } + + public int getX() { + return this.x; + } + + public int getY() { + return this.y; + } + + public int getZ() { + return this.z; + } + + @Nonnull + public String getPath() { + return this.path; + } + + public boolean isFitHeightmap() { + return this.fitHeightmap; + } + + public boolean isInheritSeed() { + return this.inheritSeed; + } + + public boolean isInheritHeightCondition() { + return this.inheritHeightCondition; + } + + @Nonnull + public PrefabWeights getWeights() { + return this.weights; + } + + @Nonnull + public PrefabRotation getRotation() { + return this.rotation; + } + } + + public static class PrefabBufferAccessor implements IPrefabBuffer { + @Nonnull + private final PrefabBuffer prefabBuffer; + @Nullable + private ByteBuf buffer; + + private PrefabBufferAccessor(@Nonnull PrefabBuffer prefabBuffer) { + this.buffer = prefabBuffer.buf.retainedDuplicate(); + this.prefabBuffer = prefabBuffer; + } + + @Override + public int getAnchorX() { + return this.prefabBuffer.getAnchorX(); + } + + @Override + public int getAnchorY() { + return this.prefabBuffer.getAnchorY(); + } + + @Override + public int getAnchorZ() { + return this.prefabBuffer.getAnchorZ(); + } + + @Override + public int getMinX(@Nonnull PrefabRotation rotation) { + this.prefabBuffer.checkReleased(); + return Math.min( + rotation.getX(this.prefabBuffer.min.getX(), this.prefabBuffer.min.getZ()), + rotation.getX(this.prefabBuffer.max.getX(), this.prefabBuffer.max.getZ()) + ); + } + + @Override + public int getMinY() { + this.prefabBuffer.checkReleased(); + return this.prefabBuffer.min.getY(); + } + + @Override + public int getMinZ(@Nonnull PrefabRotation rotation) { + this.prefabBuffer.checkReleased(); + return Math.min( + rotation.getZ(this.prefabBuffer.min.getX(), this.prefabBuffer.min.getZ()), + rotation.getZ(this.prefabBuffer.max.getX(), this.prefabBuffer.max.getZ()) + ); + } + + @Override + public int getMaxX(@Nonnull PrefabRotation rotation) { + this.prefabBuffer.checkReleased(); + return Math.max( + rotation.getX(this.prefabBuffer.min.getX(), this.prefabBuffer.min.getZ()), + rotation.getX(this.prefabBuffer.max.getX(), this.prefabBuffer.max.getZ()) + ); + } + + @Override + public int getMaxY() { + this.prefabBuffer.checkReleased(); + return this.prefabBuffer.max.getY(); + } + + @Override + public int getMaxZ(@Nonnull PrefabRotation rotation) { + this.prefabBuffer.checkReleased(); + return Math.max( + rotation.getZ(this.prefabBuffer.min.getX(), this.prefabBuffer.min.getZ()), + rotation.getZ(this.prefabBuffer.max.getX(), this.prefabBuffer.max.getZ()) + ); + } + + @Override + public int getColumnCount() { + return this.prefabBuffer.columns.size(); + } + + @Nonnull + @Override + public PrefabBuffer.ChildPrefab[] getChildPrefabs() { + return this.prefabBuffer.childPrefabs; + } + + @Override + public int getMinYAt(@Nonnull PrefabRotation rotation, int x, int z) { + this.prefabBuffer.checkReleased(); + int rotatedX = rotation.getX(x, z); + int rotatedZ = rotation.getZ(x, z); + int columnIndex = MathUtil.packInt(rotatedX, rotatedZ); + PrefabBufferColumn columnData = this.prefabBuffer.columns.get(columnIndex); + if (columnData != null) { + this.buffer.readerIndex(columnData.getReaderIndex()); + int blockCount = this.buffer.readInt(); + if (blockCount > 0) { + return this.buffer.readInt() + 1; + } + } + + return -1; + } + + @Override + public int getMaxYAt(@Nonnull PrefabRotation rotation, int x, int z) { + this.prefabBuffer.checkReleased(); + int rotatedX = rotation.getX(x, z); + int rotatedZ = rotation.getZ(x, z); + int columnIndex = MathUtil.packInt(rotatedX, rotatedZ); + PrefabBufferColumn column = this.prefabBuffer.columns.get(columnIndex); + if (column == null) { + return -1; + } else { + this.buffer.readerIndex(column.getReaderIndex()); + int blockCount = this.buffer.readInt(); + if (blockCount > 0) { + int y = this.buffer.readInt(); + + for (int i = 0; i < blockCount; i++) { + int mask = this.buffer.readUnsignedShort(); + if (PrefabBuffer.BlockMaskConstants.getOffsetBytes(mask) > 0) { + this.buffer.skipBytes(PrefabBuffer.BlockMaskConstants.getBlockBytes(mask)); + y += ByteBufUtil.readNumber(this.buffer, PrefabBuffer.BlockMaskConstants.getOffsetBytes(mask)); + if (PrefabBuffer.BlockMaskConstants.hasChance(mask)) { + this.buffer.skipBytes(4); + } + + if (PrefabBuffer.BlockMaskConstants.hasRotation(mask)) { + this.buffer.skipBytes(1); + } + + if (PrefabBuffer.BlockMaskConstants.hasFiller(mask)) { + this.buffer.skipBytes(2); + } + + this.buffer.skipBytes(PrefabBuffer.BlockMaskConstants.getFluidBytes(mask)); + } else { + this.buffer.skipBytes(PrefabBuffer.BlockMaskConstants.getSkipBytes(mask)); + y++; + } + } + + return y; + } else { + return -1; + } + } + } + + @Override + public void forEach( + @Nonnull IPrefabBuffer.ColumnPredicate columnPredicate, + @Nonnull IPrefabBuffer.BlockConsumer blockConsumer, + @Nullable IPrefabBuffer.EntityConsumer entityConsumer, + @Nullable IPrefabBuffer.ChildConsumer childConsumer, + @Nonnull T t + ) { + this.prefabBuffer.checkReleased(); + this.prefabBuffer.columns.int2ObjectEntrySet().forEach(entry -> { + int columnIndex = entry.getIntKey(); + int cx = MathUtil.unpackLeft(columnIndex); + int cz = MathUtil.unpackRight(columnIndex); + int xx = t.rotation.getX(cx, cz); + int zx = t.rotation.getZ(cx, cz); + PrefabBufferColumn column = entry.getValue(); + this.buffer.readerIndex(column.getReaderIndex()); + int blockCount = this.buffer.readInt(); + if (columnPredicate.test(xx, zx, blockCount, t)) { + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + if (blockCount > 0) { + int y = this.buffer.readInt(); + + for (int i = 0; i < blockCount; i++) { + int mask = this.buffer.readUnsignedShort(); + int blockBytes = PrefabBuffer.BlockMaskConstants.getBlockBytes(mask); + int blockId = ByteBufUtil.readNumber(this.buffer, blockBytes); + int offsetBytes = PrefabBuffer.BlockMaskConstants.getOffsetBytes(mask); + y += offsetBytes == 0 ? 1 : ByteBufUtil.readNumber(this.buffer, offsetBytes); + if (PrefabBuffer.BlockMaskConstants.hasChance(mask)) { + float chance = this.buffer.readFloat(); + if (chance < t.random.nextFloat()) { + this.buffer.skipBytes(2); + this.buffer.skipBytes(PrefabBuffer.BlockMaskConstants.getFluidBytes(mask)); + continue; + } + } + + Holder holder = PrefabBuffer.BlockMaskConstants.hasComponents(mask) ? column.getBlockComponents().get(y) : null; + int supportValue = PrefabBuffer.BlockMaskConstants.getSupportValue(mask); + int rotation = 0; + if (PrefabBuffer.BlockMaskConstants.hasRotation(mask)) { + rotation = this.buffer.readUnsignedByte(); + } + + rotation = t.rotation.getRotation(rotation); + int filler = 0; + if (PrefabBuffer.BlockMaskConstants.hasFiller(mask)) { + filler = t.rotation.getFiller(this.buffer.readUnsignedShort()); + } + + int fluidBytes = PrefabBuffer.BlockMaskConstants.getFluidBytes(mask); + int fluidId = 0; + int fluidLevel = 0; + if (fluidBytes != 0) { + fluidId = ByteBufUtil.readNumber(this.buffer, fluidBytes - 1); + fluidLevel = this.buffer.readByte(); + } + + blockConsumer.accept(xx, y, zx, blockId, holder, supportValue, rotation, filler, t, fluidId, fluidLevel); + } + } + + Holder[] entityHolders = column.getEntityHolders(); + if (entityHolders != null && entityConsumer != null) { + entityConsumer.accept(xx, zx, entityHolders, t); + } + } + }); + if (this.prefabBuffer.childPrefabs != null && childConsumer != null) { + for (PrefabBuffer.ChildPrefab childPrefab : this.prefabBuffer.childPrefabs) { + int x = t.rotation.getX(childPrefab.x, childPrefab.z); + int z = t.rotation.getZ(childPrefab.x, childPrefab.z); + childConsumer.accept( + x, + childPrefab.y, + z, + childPrefab.path, + childPrefab.fitHeightmap, + childPrefab.inheritSeed, + childPrefab.inheritHeightCondition, + childPrefab.weights, + childPrefab.rotation, + t + ); + } + } + } + + @Override + public void forEachRaw( + @Nonnull IPrefabBuffer.ColumnPredicate columnPredicate, + @Nonnull IPrefabBuffer.RawBlockConsumer blockConsumer, + @Nonnull IPrefabBuffer.FluidConsumer fluidConsumer, + @Nullable IPrefabBuffer.EntityConsumer entityConsumer, + @Nullable T t + ) { + this.prefabBuffer.checkReleased(); + this.prefabBuffer.columns.int2ObjectEntrySet().forEach(entry -> { + int columnIndex = entry.getIntKey(); + int x = MathUtil.unpackLeft(columnIndex); + int z = MathUtil.unpackRight(columnIndex); + PrefabBufferColumn column = entry.getValue(); + this.buffer.readerIndex(column.getReaderIndex()); + int blockCount = this.buffer.readInt(); + if (columnPredicate.test(x, z, blockCount, t)) { + if (blockCount > 0) { + int y = this.buffer.readInt(); + + for (int i = 0; i < blockCount; i++) { + int mask = this.buffer.readUnsignedShort(); + int blockBytes = PrefabBuffer.BlockMaskConstants.getBlockBytes(mask); + int blockId = ByteBufUtil.readNumber(this.buffer, blockBytes); + int offsetBytes = PrefabBuffer.BlockMaskConstants.getOffsetBytes(mask); + y += offsetBytes == 0 ? 1 : ByteBufUtil.readNumber(this.buffer, offsetBytes); + float chance = PrefabBuffer.BlockMaskConstants.hasChance(mask) ? this.buffer.readFloat() : 1.0F; + Holder holder = PrefabBuffer.BlockMaskConstants.hasComponents(mask) ? column.getBlockComponents().get(y) : null; + int supportValue = PrefabBuffer.BlockMaskConstants.getSupportValue(mask); + int rotation = 0; + if (PrefabBuffer.BlockMaskConstants.hasRotation(mask)) { + rotation = this.buffer.readUnsignedByte(); + } + + int filler = 0; + if (PrefabBuffer.BlockMaskConstants.hasFiller(mask)) { + filler = this.buffer.readUnsignedShort(); + } + + int position = this.buffer.readerIndex(); + blockConsumer.accept(x, y, z, mask, blockId, chance, holder, supportValue, rotation, filler, t); + this.buffer.readerIndex(position); + int fluidBytes = PrefabBuffer.BlockMaskConstants.getFluidBytes(mask); + if (fluidBytes != 0) { + int fluidId = ByteBufUtil.readNumber(this.buffer, fluidBytes - 1); + byte fluidLevel = this.buffer.readByte(); + position = this.buffer.readerIndex(); + fluidConsumer.accept(x, y, z, fluidId, fluidLevel, t); + this.buffer.readerIndex(position); + } + } + } + + Holder[] entityHolders = column.getEntityHolders(); + if (entityConsumer != null) { + entityConsumer.accept(x, z, entityHolders, t); + } + } + }); + } + + @Override + public boolean forEachRaw( + @Nonnull IPrefabBuffer.ColumnPredicate columnPredicate, + @Nonnull IPrefabBuffer.RawBlockPredicate blockPredicate, + @Nonnull IPrefabBuffer.FluidPredicate fluidPredicate, + @Nullable IPrefabBuffer.EntityPredicate entityPredicate, + @Nullable T t + ) { + this.prefabBuffer.checkReleased(); + + for (Entry entry : this.prefabBuffer.columns.int2ObjectEntrySet()) { + int columnIndex = entry.getIntKey(); + int x = MathUtil.unpackLeft(columnIndex); + int z = MathUtil.unpackRight(columnIndex); + PrefabBufferColumn column = entry.getValue(); + this.buffer.readerIndex(column.getReaderIndex()); + int blockCount = this.buffer.readInt(); + if (!columnPredicate.test(x, z, blockCount, t)) { + return false; + } + + if (blockCount > 0) { + int y = this.buffer.readInt(); + + for (int i = 0; i < blockCount; i++) { + int mask = this.buffer.readUnsignedShort(); + int blockBytes = PrefabBuffer.BlockMaskConstants.getBlockBytes(mask); + int blockId = ByteBufUtil.readNumber(this.buffer, blockBytes); + int offsetBytes = PrefabBuffer.BlockMaskConstants.getOffsetBytes(mask); + y += offsetBytes == 0 ? 1 : ByteBufUtil.readNumber(this.buffer, offsetBytes); + float chance = PrefabBuffer.BlockMaskConstants.hasChance(mask) ? this.buffer.readFloat() : 1.0F; + Holder holder = PrefabBuffer.BlockMaskConstants.hasComponents(mask) ? column.getBlockComponents().get(y) : null; + short rotation = PrefabBuffer.BlockMaskConstants.hasRotation(mask) ? this.buffer.readUnsignedByte() : 0; + int filler = PrefabBuffer.BlockMaskConstants.hasFiller(mask) ? this.buffer.readUnsignedShort() : 0; + int supportValue = PrefabBuffer.BlockMaskConstants.getSupportValue(mask); + int position = this.buffer.readerIndex(); + if (!blockPredicate.test(x, y, z, blockId, chance, holder, supportValue, rotation, filler, t)) { + return false; + } + + this.buffer.readerIndex(position); + int fluidBytes = PrefabBuffer.BlockMaskConstants.getFluidBytes(mask); + if (fluidBytes != 0) { + int fluidId = ByteBufUtil.readNumber(this.buffer, fluidBytes - 1); + byte fluidLevel = this.buffer.readByte(); + position = this.buffer.readerIndex(); + if (!fluidPredicate.test(x, y, z, fluidId, fluidLevel, t)) { + return false; + } + + this.buffer.readerIndex(position); + } + } + } + + Holder[] entityHolders = column.getEntityHolders(); + if (entityPredicate != null && !entityPredicate.test(x, z, entityHolders, t)) { + return false; + } + } + + return true; + } + + @Override + public void release() { + this.buffer.release(); + this.buffer = null; + } + + @Override + public boolean compare( + @Nonnull IPrefabBuffer.BlockComparingPrefabPredicate blockComparingIterator, @Nonnull T t, @Nonnull IPrefabBuffer otherPrefab + ) { + if (!(otherPrefab instanceof PrefabBuffer.PrefabBufferAccessor secondPrefab)) { + return IPrefabBuffer.super.compare(blockComparingIterator, t, otherPrefab); + } else { + Int2ObjectMap secondPrefabColumns = secondPrefab.prefabBuffer.columns; + IntOpenHashSet columnIndexes = new IntOpenHashSet(this.prefabBuffer.columns.size() + secondPrefabColumns.size()); + columnIndexes.addAll(this.prefabBuffer.columns.keySet()); + columnIndexes.addAll(secondPrefabColumns.keySet()); + this.prefabBuffer.checkReleased(); + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + IntIterator columnIterator = columnIndexes.iterator(); + + while (columnIterator.hasNext()) { + int columnIndex = columnIterator.nextInt(); + int cx = MathUtil.unpackLeft(columnIndex); + int cz = MathUtil.unpackRight(columnIndex); + int x = t.rotation.getX(cx, cz); + int z = t.rotation.getZ(cx, cz); + PrefabBufferColumn firstColumn = this.prefabBuffer.columns.get(columnIndex); + PrefabBufferColumn secondColumn = (PrefabBufferColumn)secondPrefabColumns.get(columnIndex); + if (firstColumn != null) { + this.buffer.readerIndex(firstColumn.getReaderIndex()); + } + + if (secondColumn != null) { + secondPrefab.buffer.readerIndex(secondColumn.getReaderIndex()); + } + + int firstColumnBlockCount = firstColumn != null ? this.buffer.readInt() : 0; + int secondColumnBlockCount = secondColumn != null ? secondPrefab.buffer.readInt() : 0; + if (firstColumnBlockCount != 0 || secondColumnBlockCount != 0) { + int firstColumnY = firstColumnBlockCount > 0 ? this.buffer.readInt() : Integer.MAX_VALUE; + int secondColumnY = secondColumnBlockCount > 0 ? secondPrefab.buffer.readInt() : Integer.MAX_VALUE; + int firstColumnBlockId = Integer.MIN_VALUE; + float firstColumnChance = 1.0F; + int firstColumnRotation = 0; + int firstColumnFiller = 0; + Holder firstColumnComponents = null; + int secondColumnBlockId = Integer.MIN_VALUE; + float secondColumnChance = 1.0F; + int secondColumnRotation = 0; + int secondColumnFiller = 0; + Holder secondColumnComponents = null; + int firstColumnBlocksRead = 0; + int secondColumnBlocksRead = 0; + + while (firstColumnBlocksRead < firstColumnBlockCount || secondColumnBlocksRead < secondColumnBlockCount) { + int oldFirstColumnY = firstColumnY; + int oldSecondColumnY = secondColumnY; + int oldFirstColumnReaderIndex = firstColumnBlocksRead < firstColumnBlockCount ? this.buffer.readerIndex() : -1; + int oldSecondColumnReaderIndex = secondColumnBlocksRead < secondColumnBlockCount ? secondPrefab.buffer.readerIndex() : -1; + if (firstColumnBlocksRead < firstColumnBlockCount) { + int mask = this.buffer.readUnsignedShort(); + int blockBytes = PrefabBuffer.BlockMaskConstants.getBlockBytes(mask); + firstColumnBlockId = ByteBufUtil.readNumber(this.buffer, blockBytes); + int offsetBytes = PrefabBuffer.BlockMaskConstants.getOffsetBytes(mask); + firstColumnY += offsetBytes == 0 ? 1 : ByteBufUtil.readNumber(this.buffer, offsetBytes); + firstColumnChance = PrefabBuffer.BlockMaskConstants.hasChance(mask) ? this.buffer.readFloat() : 1.0F; + firstColumnRotation = t.rotation.getRotation(PrefabBuffer.BlockMaskConstants.hasRotation(mask) ? this.buffer.readUnsignedByte() : 0); + firstColumnFiller = PrefabBuffer.BlockMaskConstants.hasFiller(mask) ? t.rotation.getFiller(this.buffer.readUnsignedShort()) : 0; + firstColumnComponents = PrefabBuffer.BlockMaskConstants.hasComponents(mask) ? firstColumn.getBlockComponents().get(firstColumnY) : null; + this.buffer.skipBytes(PrefabBuffer.BlockMaskConstants.getFluidBytes(mask)); + } + + if (secondColumnBlocksRead < secondColumnBlockCount) { + int mask = secondPrefab.buffer.readUnsignedShort(); + int blockBytes = PrefabBuffer.BlockMaskConstants.getBlockBytes(mask); + secondColumnBlockId = ByteBufUtil.readNumber(secondPrefab.buffer, blockBytes); + int offsetBytes = PrefabBuffer.BlockMaskConstants.getOffsetBytes(mask); + secondColumnY += offsetBytes == 0 ? 1 : ByteBufUtil.readNumber(secondPrefab.buffer, offsetBytes); + secondColumnChance = PrefabBuffer.BlockMaskConstants.hasChance(mask) ? secondPrefab.buffer.readFloat() : 1.0F; + secondColumnRotation = t.rotation + .getRotation(PrefabBuffer.BlockMaskConstants.hasRotation(mask) ? secondPrefab.buffer.readUnsignedByte() : 0); + secondColumnFiller = PrefabBuffer.BlockMaskConstants.hasFiller(mask) + ? t.rotation.getFiller(secondPrefab.buffer.readUnsignedShort()) + : 0; + secondColumnComponents = PrefabBuffer.BlockMaskConstants.hasComponents(mask) + ? secondColumn.getBlockComponents().get(secondColumnY) + : null; + secondPrefab.buffer.skipBytes(PrefabBuffer.BlockMaskConstants.getFluidBytes(mask)); + } + + if (firstColumnY == secondColumnY) { + firstColumnBlocksRead++; + secondColumnBlocksRead++; + boolean test = blockComparingIterator.test( + x, + firstColumnY, + z, + firstColumnBlockId, + firstColumnComponents, + firstColumnChance, + firstColumnRotation, + firstColumnFiller, + secondColumnBlockId, + secondColumnComponents, + secondColumnChance, + secondColumnRotation, + secondColumnFiller, + t + ); + if (!test) { + return false; + } + } else if ((firstColumnY >= secondColumnY || firstColumnBlocksRead >= firstColumnBlockCount) + && secondColumnBlocksRead < secondColumnBlockCount) { + secondColumnBlocksRead++; + firstColumnY = oldFirstColumnY; + if (oldFirstColumnReaderIndex != -1) { + this.buffer.readerIndex(oldFirstColumnReaderIndex); + } + + boolean test = blockComparingIterator.test( + x, + secondColumnY, + z, + Integer.MIN_VALUE, + null, + 1.0F, + 0, + 0, + secondColumnBlockId, + secondColumnComponents, + secondColumnChance, + secondColumnRotation, + secondColumnFiller, + t + ); + if (!test) { + return false; + } + } else { + firstColumnBlocksRead++; + secondColumnY = oldSecondColumnY; + if (oldSecondColumnReaderIndex != -1) { + secondPrefab.buffer.readerIndex(oldSecondColumnReaderIndex); + } + + boolean test = blockComparingIterator.test( + x, + firstColumnY, + z, + firstColumnBlockId, + firstColumnComponents, + firstColumnChance, + firstColumnRotation, + firstColumnFiller, + Integer.MIN_VALUE, + null, + 1.0F, + 0, + 0, + t + ); + if (!test) { + return false; + } + } + } + } + } + + return true; + } + } + + @Override + public int getBlockId(int x, int y, int z) { + this.prefabBuffer.checkReleased(); + PrefabBufferColumn column = this.prefabBuffer.columns.get(MathUtil.packInt(x, z)); + if (column == null) { + return 0; + } else { + this.buffer.readerIndex(column.getReaderIndex()); + int blockCount = this.buffer.readInt(); + if (blockCount <= 0) { + return 0; + } else { + int blockY = this.buffer.readInt(); + + for (int i = 0; i < blockCount; i++) { + int mask = this.buffer.readUnsignedShort(); + int blockBytes = PrefabBuffer.BlockMaskConstants.getBlockBytes(mask); + int blockId = ByteBufUtil.readNumber(this.buffer, blockBytes); + int offsetBytes = PrefabBuffer.BlockMaskConstants.getOffsetBytes(mask); + blockY += offsetBytes == 0 ? 1 : ByteBufUtil.readNumber(this.buffer, offsetBytes); + if (blockY > y) { + return 0; + } + + if (PrefabBuffer.BlockMaskConstants.hasChance(mask)) { + this.buffer.readFloat(); + } + + if (PrefabBuffer.BlockMaskConstants.hasRotation(mask)) { + this.buffer.readUnsignedByte(); + } + + if (PrefabBuffer.BlockMaskConstants.hasFiller(mask)) { + this.buffer.readUnsignedShort(); + } + + int fluidBytes = PrefabBuffer.BlockMaskConstants.getFluidBytes(mask); + this.buffer.skipBytes(fluidBytes); + if (blockY == y) { + if (PrefabBuffer.BlockMaskConstants.hasChance(mask)) { + throw new UnsupportedOperationException("Unable to access block with chance!"); + } + + return blockId; + } + } + + return 0; + } + } + } + + @Override + public int getFiller(int x, int y, int z) { + this.prefabBuffer.checkReleased(); + PrefabBufferColumn column = this.prefabBuffer.columns.get(MathUtil.packInt(x, z)); + if (column == null) { + return 0; + } else { + this.buffer.readerIndex(column.getReaderIndex()); + int blockCount = this.buffer.readInt(); + if (blockCount <= 0) { + return 0; + } else { + int blockY = this.buffer.readInt(); + + for (int i = 0; i < blockCount; i++) { + int mask = this.buffer.readUnsignedShort(); + int blockBytes = PrefabBuffer.BlockMaskConstants.getBlockBytes(mask); + int blockId = ByteBufUtil.readNumber(this.buffer, blockBytes); + int offsetBytes = PrefabBuffer.BlockMaskConstants.getOffsetBytes(mask); + blockY += offsetBytes == 0 ? 1 : ByteBufUtil.readNumber(this.buffer, offsetBytes); + if (blockY > y) { + return 0; + } + + if (PrefabBuffer.BlockMaskConstants.hasChance(mask)) { + this.buffer.readFloat(); + } + + if (PrefabBuffer.BlockMaskConstants.hasRotation(mask)) { + this.buffer.readUnsignedByte(); + } + + int filler = 0; + if (PrefabBuffer.BlockMaskConstants.hasFiller(mask)) { + filler = this.buffer.readUnsignedShort(); + } + + int fluidBytes = PrefabBuffer.BlockMaskConstants.getFluidBytes(mask); + this.buffer.skipBytes(fluidBytes); + if (blockY == y) { + if (PrefabBuffer.BlockMaskConstants.hasChance(mask)) { + throw new UnsupportedOperationException("Unable to access block with chance!"); + } + + return filler; + } + } + + return 0; + } + } + } + + @Override + public int getRotationIndex(int x, int y, int z) { + this.prefabBuffer.checkReleased(); + PrefabBufferColumn column = this.prefabBuffer.columns.get(MathUtil.packInt(x, z)); + if (column == null) { + return 0; + } else { + this.buffer.readerIndex(column.getReaderIndex()); + int blockCount = this.buffer.readInt(); + if (blockCount <= 0) { + return 0; + } else { + int blockY = this.buffer.readInt(); + + for (int i = 0; i < blockCount; i++) { + int mask = this.buffer.readUnsignedShort(); + int blockBytes = PrefabBuffer.BlockMaskConstants.getBlockBytes(mask); + int blockId = ByteBufUtil.readNumber(this.buffer, blockBytes); + int offsetBytes = PrefabBuffer.BlockMaskConstants.getOffsetBytes(mask); + blockY += offsetBytes == 0 ? 1 : ByteBufUtil.readNumber(this.buffer, offsetBytes); + if (blockY > y) { + return 0; + } + + if (PrefabBuffer.BlockMaskConstants.hasChance(mask)) { + this.buffer.readFloat(); + } + + int rotation = 0; + if (PrefabBuffer.BlockMaskConstants.hasRotation(mask)) { + rotation = this.buffer.readUnsignedByte(); + } + + if (PrefabBuffer.BlockMaskConstants.hasFiller(mask)) { + this.buffer.readUnsignedShort(); + } + + int fluidBytes = PrefabBuffer.BlockMaskConstants.getFluidBytes(mask); + this.buffer.skipBytes(fluidBytes); + if (blockY == y) { + if (PrefabBuffer.BlockMaskConstants.hasChance(mask)) { + throw new UnsupportedOperationException("Unable to access block with chance!"); + } + + return rotation; + } + } + + return 0; + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/buffer/impl/PrefabBufferBlockEntry.java b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/impl/PrefabBufferBlockEntry.java new file mode 100644 index 0000000..a99d5bf --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/impl/PrefabBufferBlockEntry.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.core.prefab.selection.buffer.impl; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nullable; + +public class PrefabBufferBlockEntry { + public static final PrefabBufferBlockEntry[] EMPTY_ARRAY = new PrefabBufferBlockEntry[0]; + public final int y; + public String blockTypeKey; + public int blockId; + public float chance; + @Nullable + public Holder state; + public int fluidId; + public byte fluidLevel; + public byte supportValue; + public int filler; + public int rotation; + + public PrefabBufferBlockEntry(int y) { + this(y, 0, "Empty"); + } + + public PrefabBufferBlockEntry(int y, int blockId, String blockTypeKey) { + this(y, blockId, blockTypeKey, 1.0F); + } + + public PrefabBufferBlockEntry(int y, int blockId, String blockTypeKey, float chance) { + this(y, blockId, blockTypeKey, chance, null, 0, (byte)0, (byte)0, 0, 0); + } + + public PrefabBufferBlockEntry( + int y, + int blockId, + String blockTypeKey, + float chance, + Holder state, + int fluidId, + byte fluidLevel, + byte supportValue, + int rotation, + int filler + ) { + this.y = y; + this.blockId = blockId; + this.blockTypeKey = blockTypeKey; + this.chance = chance; + this.state = state; + this.fluidId = fluidId; + this.fluidLevel = fluidLevel; + this.supportValue = supportValue; + this.rotation = rotation; + this.filler = filler; + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/buffer/impl/PrefabBufferColumn.java b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/impl/PrefabBufferColumn.java new file mode 100644 index 0000000..2383ed5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/buffer/impl/PrefabBufferColumn.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.server.core.prefab.selection.buffer.impl; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import javax.annotation.Nullable; + +public class PrefabBufferColumn { + private final int readerIndex; + private final Holder[] entityHolders; + private final Int2ObjectMap> blockComponents; + + public PrefabBufferColumn(int readerIndex, Holder[] entityHolders, Int2ObjectMap> blockComponents) { + this.readerIndex = readerIndex; + this.entityHolders = entityHolders; + this.blockComponents = blockComponents; + } + + public int getReaderIndex() { + return this.readerIndex; + } + + @Nullable + public Holder[] getEntityHolders() { + return this.entityHolders; + } + + public Int2ObjectMap> getBlockComponents() { + return this.blockComponents; + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/mask/BlockFilter.java b/src/com/hypixel/hytale/server/core/prefab/selection/mask/BlockFilter.java new file mode 100644 index 0000000..906be7d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/mask/BlockFilter.java @@ -0,0 +1,363 @@ +package com.hypixel.hytale.server.core.prefab.selection.mask; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.function.FunctionCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BlockTypeListAsset; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.PlaceFluidInteraction; +import com.hypixel.hytale.server.core.universe.world.accessor.ChunkAccessor; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.IntSets; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockFilter { + public static final BlockFilter[] EMPTY_ARRAY = new BlockFilter[0]; + public static final Codec CODEC = new FunctionCodec<>(Codec.STRING, BlockFilter::parse, BlockFilter::toString); + public static final String BLOCK_SEPARATOR = "|"; + public static final Pattern BLOCK_SEPARATOR_PATTERN = Pattern.compile(Pattern.quote("|")); + @Nonnull + private final BlockFilter.FilterType blockFilterType; + @Nonnull + private final String[] blocks; + private final boolean inverted; + @Nonnull + private final transient String toString0; + private IntSet resolvedBlocks; + private IntSet resolvedFluids; + + public BlockFilter(@Nonnull BlockFilter.FilterType blockFilterType, @Nonnull String[] blocks, boolean inverted) { + Objects.requireNonNull(blockFilterType); + Objects.requireNonNull(blocks); + this.blockFilterType = blockFilterType; + this.blocks = blocks; + this.inverted = inverted; + this.toString0 = this.toString0(); + } + + public void resolve() { + if (this.resolvedBlocks == null) { + BlockFilter.BlocksAndFluids result = parseBlocksAndFluids(this.blocks); + this.resolvedBlocks = result.blocks; + this.resolvedFluids = result.fluids; + } + } + + @Nonnull + public BlockFilter.FilterType getBlockFilterType() { + return this.blockFilterType; + } + + @Nonnull + public String[] getBlocks() { + return this.blocks; + } + + public boolean isInverted() { + return this.inverted; + } + + public boolean isExcluded(@Nonnull ChunkAccessor accessor, int x, int y, int z, Vector3i min, Vector3i max, int blockId) { + return this.isExcluded(accessor, x, y, z, min, max, blockId, -1); + } + + public boolean isExcluded(@Nonnull ChunkAccessor accessor, int x, int y, int z, Vector3i min, Vector3i max, int blockId, int fluidId) { + boolean exclude = !this.isIncluded(accessor, x, y, z, min, max, blockId, fluidId); + return this.inverted != exclude; + } + + private boolean isIncluded(@Nonnull ChunkAccessor accessor, int x, int y, int z, @Nullable Vector3i min, @Nullable Vector3i max, int blockId) { + return this.isIncluded(accessor, x, y, z, min, max, blockId, -1); + } + + private boolean isIncluded(@Nonnull ChunkAccessor accessor, int x, int y, int z, @Nullable Vector3i min, @Nullable Vector3i max, int blockId, int fluidId) { + switch (this.blockFilterType) { + case TargetBlock: + this.resolve(); + boolean matchesBlock = this.resolvedBlocks.contains(blockId); + boolean matchesFluid = fluidId >= 0 && this.resolvedFluids != null && this.resolvedFluids.contains(fluidId); + return matchesBlock || matchesFluid; + case AboveBlock: + return this.matchesAt(accessor, x, y - 1, z); + case BelowBlock: + return this.matchesAt(accessor, x, y + 1, z); + case AdjacentBlock: + return this.matchesAt(accessor, x - 1, y, z) + || this.matchesAt(accessor, x + 1, y, z) + || this.matchesAt(accessor, x, y, z - 1) + || this.matchesAt(accessor, x, y, z + 1); + case NeighborBlock: + for (int xo = -1; xo < 2; xo++) { + for (int yo = -1; yo < 2; yo++) { + for (int zo = -1; zo < 2; zo++) { + if ((xo != 0 || yo != 0 || zo != 0) && this.matchesAt(accessor, x + xo, y + yo, z + zo)) { + return true; + } + } + } + } + + return false; + case NorthBlock: + return this.matchesAt(accessor, x, y, z - 1); + case EastBlock: + return this.matchesAt(accessor, x, y, z + 1); + case SouthBlock: + return this.matchesAt(accessor, x, y, z + 1); + case WestBlock: + return this.matchesAt(accessor, x, y, z - 1); + case DiagonalXy: + return this.matchesAt(accessor, x - 1, y + 1, z) + || this.matchesAt(accessor, x - 1, y - 1, z) + || this.matchesAt(accessor, x + 1, y + 1, z) + || this.matchesAt(accessor, x + 1, y - 1, z); + case DiagonalXz: + return this.matchesAt(accessor, x - 1, y, z + 1) + || this.matchesAt(accessor, x - 1, y, z - 1) + || this.matchesAt(accessor, x + 1, y, z + 1) + || this.matchesAt(accessor, x + 1, y, z - 1); + case DiagonalZy: + return this.matchesAt(accessor, x, y - 1, z + 1) + || this.matchesAt(accessor, x, y - 1, z - 1) + || this.matchesAt(accessor, x, y + 1, z + 1) + || this.matchesAt(accessor, x, y + 1, z - 1); + case Selection: + if (min != null && max != null) { + return x >= min.x && y >= min.y && z >= min.z && x <= max.x && y <= max.y && z <= max.z; + } + + return false; + default: + throw new IllegalArgumentException("Unknown filter type: " + this.blockFilterType); + } + } + + private boolean matchesAt(@Nonnull ChunkAccessor accessor, int x, int y, int z) { + this.resolve(); + return this.resolvedBlocks.contains(accessor.getBlock(x, y, z)) + ? true + : this.resolvedFluids != null && this.resolvedFluids.contains(accessor.getFluidId(x, y, z)); + } + + @Nonnull + @Override + public String toString() { + return this.toString0; + } + + @Nonnull + public String toString0() { + return (this.inverted ? "!" : "") + this.blockFilterType.getPrefix() + String.join("|", this.blocks); + } + + @Nonnull + public String informativeToString() { + StringBuilder builder = new StringBuilder(); + String prefix = (this.inverted ? "!" : "") + this.blockFilterType.getPrefix(); + if (this.blocks.length > 1) { + builder.append("("); + } + + for (int i = 0; i < this.blocks.length; i++) { + builder.append(prefix).append(this.blocks[i]); + if (i != this.blocks.length - 1) { + builder.append(" OR "); + } + } + + if (this.blocks.length > 1) { + builder.append(")"); + } + + return builder.toString(); + } + + @Nonnull + public static BlockFilter parse(@Nonnull String str) { + BlockFilter.ParsedFilterParts parts = parseComponents(str); + String[] blocks = parts.type.hasBlocks() ? BLOCK_SEPARATOR_PATTERN.split(parts.blocks) : ArrayUtil.EMPTY_STRING_ARRAY; + return new BlockFilter(parts.type, blocks, parts.inverted); + } + + @Nonnull + public static BlockFilter.ParsedFilterParts parseComponents(@Nonnull String str) { + boolean invert = str.startsWith("!"); + int index = invert ? 1 : 0; + BlockFilter.FilterType filterType = BlockFilter.FilterType.parse(str, index); + index += filterType.getPrefix().length(); + String blocks = str.substring(index); + return new BlockFilter.ParsedFilterParts(filterType, invert, blocks); + } + + @Nonnull + public static IntSet parseBlocks(@Nonnull String[] blocksArgs) { + return parseBlocksAndFluids(blocksArgs).blocks; + } + + @Nonnull + private static BlockFilter.BlocksAndFluids parseBlocksAndFluids(@Nonnull String[] blocksArgs) { + IntSet blocks = new IntOpenHashSet(); + IntSet fluids = new IntOpenHashSet(); + + for (String blockArg : blocksArgs) { + Item item = Item.getAssetMap().getAsset(blockArg); + if (item != null) { + int fluidId = getFluidIdFromItem(item); + if (fluidId >= 0) { + fluids.add(fluidId); + continue; + } + } + + int blockId = BlockPattern.parseBlock(blockArg); + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + if (blockType != null && blockType.getBlockListAssetId() != null) { + BlockTypeListAsset blockTypeListAsset = BlockTypeListAsset.getAssetMap().getAsset(blockType.getBlockListAssetId()); + if (blockTypeListAsset != null && blockTypeListAsset.getBlockPattern() != null) { + Integer[] var11 = blockTypeListAsset.getBlockPattern().getResolvedKeys(); + int var12 = var11.length; + + for (int var13 = 0; var13 < var12; var13++) { + int resolvedKey = var11[var13]; + blocks.add(resolvedKey); + } + continue; + } + } + + blocks.add(blockId); + } + + return new BlockFilter.BlocksAndFluids(IntSets.unmodifiable(blocks), fluids.isEmpty() ? null : IntSets.unmodifiable(fluids)); + } + + private static int getFluidIdFromItem(@Nonnull Item item) { + Map interactions = item.getInteractions(); + String secondaryRootId = interactions.get(InteractionType.Secondary); + if (secondaryRootId == null) { + return -1; + } else { + RootInteraction rootInteraction = RootInteraction.getAssetMap().getAsset(secondaryRootId); + if (rootInteraction == null) { + return -1; + } else { + for (String interactionId : rootInteraction.getInteractionIds()) { + Interaction interaction = Interaction.getAssetMap().getAsset(interactionId); + if (interaction instanceof PlaceFluidInteraction placeFluidInteraction) { + String fluidKey = placeFluidInteraction.getFluidKey(); + if (fluidKey != null) { + int fluidId = Fluid.getAssetMap().getIndex(fluidKey); + if (fluidId >= 0) { + return fluidId; + } + } + } + } + + return -1; + } + } + } + + private static class BlocksAndFluids { + final IntSet blocks; + final IntSet fluids; + + BlocksAndFluids(IntSet blocks, IntSet fluids) { + this.blocks = blocks; + this.fluids = fluids; + } + } + + public static enum FilterType { + TargetBlock(""), + AboveBlock(">"), + BelowBlock("<"), + AdjacentBlock("~"), + NeighborBlock("^"), + NorthBlock("+n"), + EastBlock("+e"), + SouthBlock("+s"), + WestBlock("+w"), + DiagonalXy("%xy"), + DiagonalXz("%xz"), + DiagonalZy("%zy"), + Selection("#", false); + + public static final String INVERT_PREFIX = "!"; + public static final String TARGET_BLOCK_PREFIX = ""; + public static final String ABOVE_BLOCK_PREFIX = ">"; + public static final String BELOW_BLOCK_PREFIX = "<"; + public static final String ADJACENT_BLOCK_PREFIX = "~"; + public static final String NEIGHBOR_BLOCK_PREFIX = "^"; + public static final String SELECTION_PREFIX = "#"; + public static final String CARDINAL_NORTH_PREFIX = "+n"; + public static final String CARDINAL_EAST_PREFIX = "+e"; + public static final String CARDINAL_SOUTH_PREFIX = "+s"; + public static final String CARDINAL_WEST_PREFIX = "+w"; + public static final String DIAGONAL_XY_PREFIX = "%xy"; + public static final String DIAGONAL_XZ_PREFIX = "%xz"; + public static final String DIAGONAL_ZY_PREFIX = "%zy"; + @Nonnull + private static final BlockFilter.FilterType[] VALUES_TO_PARSE; + private final String prefix; + private final boolean hasBlocks; + + private FilterType(String prefix) { + this.prefix = prefix; + this.hasBlocks = true; + } + + private FilterType(String prefix, boolean hasBlocks) { + this.prefix = prefix; + this.hasBlocks = hasBlocks; + } + + public boolean hasBlocks() { + return this.hasBlocks; + } + + public String getPrefix() { + return this.prefix; + } + + @Nonnull + public static BlockFilter.FilterType parse(@Nonnull String str, int index) { + for (BlockFilter.FilterType filterType : VALUES_TO_PARSE) { + if (str.startsWith(filterType.prefix, index)) { + return filterType; + } + } + + return TargetBlock; + } + + static { + BlockFilter.FilterType[] values = values(); + BlockFilter.FilterType[] valuesToParse = new BlockFilter.FilterType[values.length - 1]; + int i = 0; + + for (BlockFilter.FilterType value : values) { + if (value != TargetBlock) { + valuesToParse[i++] = value; + } + } + + VALUES_TO_PARSE = valuesToParse; + } + } + + public record ParsedFilterParts(BlockFilter.FilterType type, boolean inverted, String blocks) { + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/mask/BlockMask.java b/src/com/hypixel/hytale/server/core/prefab/selection/mask/BlockMask.java new file mode 100644 index 0000000..a8636e1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/mask/BlockMask.java @@ -0,0 +1,237 @@ +package com.hypixel.hytale.server.core.prefab.selection.mask; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.function.FunctionCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.universe.world.accessor.ChunkAccessor; +import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockMask { + public static final BlockMask EMPTY = new BlockMask(BlockFilter.EMPTY_ARRAY); + public static final Codec CODEC = new FunctionCodec<>(Codec.STRING, BlockMask::parse, BlockMask::toString); + public static final String MASK_SEPARATOR = ","; + public static final String ALT_MASK_SEPARATOR = ";"; + public static final String EMPTY_MASK_CHARACTER = "-"; + private final BlockFilter[] filters; + private boolean inverted; + + public BlockMask(BlockFilter[] filters) { + this.filters = filters; + } + + @Nonnull + public BlockMask withOptions(@Nonnull BlockFilter.FilterType filterType, boolean inverted) { + if (this == EMPTY) { + return this; + } else { + BlockFilter[] filters = this.filters; + + for (BlockFilter filter : filters) { + if (filter.getBlockFilterType() != filterType || filter.isInverted() != inverted) { + filters = new BlockFilter[filters.length]; + break; + } + } + + if (filters == this.filters) { + return this; + } else { + for (int i = 0; i < filters.length; i++) { + BlockFilter filterx = this.filters[i]; + if (filterx.getBlockFilterType() != filterType || filterx.isInverted() != inverted) { + filterx = new BlockFilter(filterType, filterx.getBlocks(), inverted); + } + + filters[i] = filterx; + } + + return new BlockMask(filters); + } + } + } + + public BlockFilter[] getFilters() { + return this.filters; + } + + public void setInverted(boolean inverted) { + this.inverted = inverted; + } + + public boolean isInverted() { + return this.inverted; + } + + public boolean isExcluded(@Nonnull ChunkAccessor accessor, int x, int y, int z, Vector3i min, Vector3i max, int blockId) { + return this.isExcluded(accessor, x, y, z, min, max, blockId, -1); + } + + public boolean isExcluded(@Nonnull ChunkAccessor accessor, int x, int y, int z, Vector3i min, Vector3i max, int blockId, int fluidId) { + boolean excluded = false; + + for (BlockFilter filter : this.filters) { + if (filter.isExcluded(accessor, x, y, z, min, max, blockId, fluidId)) { + excluded = true; + break; + } + } + + return this.inverted != excluded; + } + + @Nonnull + @Override + public String toString() { + if (this.filters.length == 0) { + return "-"; + } else { + String base = joinElements(",", this.filters); + return this.inverted ? "!" + base : base; + } + } + + @Nonnull + public String informativeToString() { + if (this.filters.length == 0) { + return "-"; + } else { + StringBuilder builder = new StringBuilder(); + if (this.inverted) { + builder.append("NOT("); + } + + if (this.filters.length > 1) { + builder.append("("); + } + + for (int i = 0; i < this.filters.length; i++) { + builder.append(this.filters[i].informativeToString()); + if (i != this.filters.length - 1) { + builder.append(" AND "); + } + } + + if (this.filters.length > 1) { + builder.append(")"); + } + + if (this.inverted) { + builder.append(")"); + } + + return builder.toString(); + } + } + + @Nonnull + protected static String joinElements(String separator, @Nonnull Object[] elements) { + StringBuilder sb = new StringBuilder(); + + for (Object o : elements) { + if (!sb.isEmpty()) { + sb.append(separator); + } + + sb.append(o); + } + + return sb.toString(); + } + + public static BlockMask parse(@Nonnull String masks) { + if (!masks.isEmpty() && !masks.equals("-")) { + masks = masks.replace(";", ","); + return parse(masks.split(",")); + } else { + return EMPTY; + } + } + + public static BlockMask parse(@Nonnull String[] masks) { + if (masks.length == 0) { + return EMPTY; + } else if (masks.length == 1) { + return new BlockMask(new BlockFilter[]{BlockFilter.parse(masks[0])}); + } else { + BlockFilter[] parsedFilters = new BlockFilter[masks.length]; + + for (int i = 0; i < masks.length; i++) { + parsedFilters[i] = BlockFilter.parse(masks[i]); + } + + return groupFilters(parsedFilters); + } + } + + public static BlockMask combine(@Nullable BlockMask... masks) { + if (masks != null && masks.length != 0) { + int totalFilters = 0; + + for (BlockMask mask : masks) { + if (mask != null && mask != EMPTY) { + totalFilters += mask.getFilters().length; + } + } + + if (totalFilters == 0) { + return EMPTY; + } else { + BlockFilter[] allFilters = new BlockFilter[totalFilters]; + int idx = 0; + + for (BlockMask maskx : masks) { + if (maskx != null && maskx != EMPTY) { + for (BlockFilter filter : maskx.getFilters()) { + allFilters[idx++] = filter; + } + } + } + + return groupFilters(allFilters); + } + } else { + return EMPTY; + } + } + + private static BlockMask groupFilters(@Nonnull BlockFilter[] inputFilters) { + if (inputFilters.length == 0) { + return EMPTY; + } else if (inputFilters.length == 1) { + return new BlockMask(inputFilters); + } else { + Int2ObjectLinkedOpenHashMap> groups = new Int2ObjectLinkedOpenHashMap<>(); + + for (BlockFilter filter : inputFilters) { + int key = filter.getBlockFilterType().ordinal() << 1 | (filter.isInverted() ? 1 : 0); + List list = groups.computeIfAbsent(key, k -> new ArrayList<>()); + + for (String block : filter.getBlocks()) { + list.add(block); + } + } + + if (groups.size() == inputFilters.length) { + return new BlockMask(inputFilters); + } else { + BlockFilter[] filters = new BlockFilter[groups.size()]; + int i = 0; + + for (Entry> entry : groups.int2ObjectEntrySet()) { + int key = entry.getIntKey(); + BlockFilter.FilterType filterType = BlockFilter.FilterType.values()[key >> 1]; + boolean inverted = (key & 1) != 0; + String[] blocks = entry.getValue().toArray(new String[0]); + filters[i++] = new BlockFilter(filterType, blocks, inverted); + } + + return new BlockMask(filters); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/mask/BlockPattern.java b/src/com/hypixel/hytale/server/core/prefab/selection/mask/BlockPattern.java new file mode 100644 index 0000000..ae9c167 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/mask/BlockPattern.java @@ -0,0 +1,287 @@ +package com.hypixel.hytale.server.core.prefab.selection.mask; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.function.FunctionCodec; +import com.hypixel.hytale.common.map.IWeightedMap; +import com.hypixel.hytale.common.map.WeightedMap; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BlockTypeListAsset; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Random; +import java.util.logging.Level; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockPattern { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final Codec CODEC = new FunctionCodec<>(Codec.STRING, BlockPattern::parse, BlockPattern::toString); + public static final BlockPattern EMPTY = new BlockPattern(parseBlockPattern("Empty")); + public static final BlockPattern[] EMPTY_ARRAY = new BlockPattern[0]; + private static final Pattern FILLER_TEMP_REMOVER_PATTERN = Pattern.compile("(Filler=-?\\d+),(-?\\d+),(-?\\d+)"); + private static final String BLOCK_SEPARATOR = ","; + private static final String ALT_BLOCK_SEPARATOR = ";"; + private static final String CHANCE_SUFFIX = "%"; + private static final double DEFAULT_CHANCE = 100.0; + private final IWeightedMap weightedMap; + private final transient String toString0; + private IWeightedMap resolvedWeightedMap; + private IWeightedMap resolvedWeightedMapBtk; + + public BlockPattern(IWeightedMap weightedMap) { + this.weightedMap = weightedMap; + this.toString0 = this.toString0(); + } + + public Integer[] getResolvedKeys() { + this.resolve(); + return this.resolvedWeightedMap.internalKeys(); + } + + public void resolve() { + if (this.resolvedWeightedMap == null) { + WeightedMap.Builder mapBuilder = WeightedMap.builder(ArrayUtil.EMPTY_INTEGER_ARRAY); + WeightedMap.Builder mapBuilderKey = WeightedMap.builder(new BlockPattern.BlockEntry[0]); + this.weightedMap.forEachEntry((blockName, weight) -> { + int blockId = parseBlock(blockName); + BlockPattern.BlockEntry key = tryParseBlockTypeKey(blockName); + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + if (blockType != null && blockType.getBlockListAssetId() != null) { + BlockTypeListAsset blockTypeListAsset = BlockTypeListAsset.getAssetMap().getAsset(blockType.getBlockListAssetId()); + if (blockTypeListAsset != null && blockTypeListAsset.getBlockPattern() != null) { + for (String resolvedKey : blockTypeListAsset.getBlockTypeKeys()) { + int resolvedId = BlockType.getAssetMap().getIndex(resolvedKey); + if (resolvedId == Integer.MIN_VALUE) { + LOGGER.at(Level.WARNING).log("BlockTypeList '%s' contains invalid block '%s' - skipping", blockType.getBlockListAssetId(), resolvedKey); + } else { + mapBuilder.put(resolvedId, weight / blockTypeListAsset.getBlockTypeKeys().size()); + } + } + + return; + } + } + + mapBuilder.put(blockId, weight); + if (key != null) { + mapBuilderKey.put(key, weight); + } + }); + this.resolvedWeightedMap = mapBuilder.build(); + this.resolvedWeightedMapBtk = mapBuilderKey.build(); + } + } + + public boolean isEmpty() { + return this.weightedMap.size() == 0; + } + + public int nextBlock(Random random) { + this.resolve(); + return this.resolvedWeightedMap.get(random); + } + + @Nullable + public BlockPattern.BlockEntry nextBlockTypeKey(Random random) { + this.resolve(); + return this.resolvedWeightedMapBtk.get(random); + } + + @Deprecated + public int firstBlock() { + this.resolve(); + return this.resolvedWeightedMap.size() > 0 ? this.resolvedWeightedMap.internalKeys()[0] : 0; + } + + @Override + public String toString() { + return this.toString0; + } + + private String toString0() { + if (this.weightedMap.size() == 1) { + return this.weightedMap.internalKeys()[0]; + } else { + List blocks = new ObjectArrayList<>(); + this.weightedMap.forEachEntry((k, v) -> blocks.add(v + "%" + k)); + return String.join(",", blocks); + } + } + + public static BlockPattern parse(@Nonnull String str) { + if (!str.isEmpty() && !str.equals("Empty")) { + if (str.toLowerCase().contains("filler=")) { + str = FILLER_TEMP_REMOVER_PATTERN.matcher(str).replaceAll("$1;$2;$3"); + } + + str = str.replace(";", ","); + return new BlockPattern(parseBlockPattern(str.split(","))); + } else { + return EMPTY; + } + } + + @Nonnull + private static IWeightedMap parseBlockPattern(@Nonnull String... blocksArgs) { + WeightedMap.Builder builder = WeightedMap.builder(ArrayUtil.EMPTY_STRING_ARRAY); + + for (String blockArg : blocksArgs) { + if (!blockArg.isEmpty()) { + double chance = 100.0; + String[] blockArr = blockArg.split("%"); + if (blockArr.length > 1) { + try { + chance = Double.parseDouble(blockArr[0]); + } catch (NumberFormatException var10) { + throw new IllegalArgumentException("Invalid Chance Value: " + blockArr[0], var10); + } + + blockArg = blockArr[1]; + } + + builder.put(blockArg, chance); + } + } + + return builder.build(); + } + + public static int parseBlock(@Nonnull String blockText) { + int blockId; + try { + blockId = Integer.parseInt(blockText); + if (BlockType.getAssetMap().getAsset(blockId) == null) { + throw new IllegalArgumentException("Block with id '" + blockText + "' doesn't exist!"); + } + } catch (NumberFormatException var4) { + blockText = blockText.replace(";", ","); + int oldData = blockText.indexOf(124); + if (oldData != -1) { + blockText = blockText.substring(0, oldData); + } + + blockId = BlockType.getAssetMap().getIndex(blockText); + } + + return blockId; + } + + @Nullable + public static BlockPattern.BlockEntry tryParseBlockTypeKey(String blockText) { + try { + blockText = blockText.replace(";", ","); + return BlockPattern.BlockEntry.decode(blockText); + } catch (Exception var2) { + return null; + } + } + + public record BlockEntry(String blockTypeKey, int rotation, int filler) { + @Deprecated(forRemoval = true) + public static Codec CODEC = new FunctionCodec<>(Codec.STRING, BlockPattern.BlockEntry::decode, BlockPattern.BlockEntry::encode); + + @Deprecated(forRemoval = true) + private String encode() { + if (this.filler == 0 && this.rotation == 0) { + return this.blockTypeKey; + } else { + StringBuilder out = new StringBuilder(this.blockTypeKey); + RotationTuple rot = RotationTuple.get(this.rotation); + if (rot.yaw() != Rotation.None) { + out.append("|Yaw=").append(rot.yaw().getDegrees()); + } + + if (rot.pitch() != Rotation.None) { + out.append("|Pitch=").append(rot.pitch().getDegrees()); + } + + if (rot.roll() != Rotation.None) { + out.append("|Roll=").append(rot.roll().getDegrees()); + } + + if (this.filler != 0) { + int fillerX = FillerBlockUtil.unpackX(this.filler); + int fillerY = FillerBlockUtil.unpackY(this.filler); + int fillerZ = FillerBlockUtil.unpackZ(this.filler); + out.append("|Filler=").append(fillerX).append(",").append(fillerY).append(",").append(fillerZ); + } + + return out.toString(); + } + } + + @Deprecated(forRemoval = true) + public static BlockPattern.BlockEntry decode(String key) { + int filler = 0; + if (key.contains("|Filler=")) { + int start = key.indexOf("|Filler=") + "|Filler=".length(); + int firstComma = key.indexOf(44, start); + if (firstComma == -1) { + throw new IllegalArgumentException("Invalid filler metadata! Missing comma"); + } + + int secondComma = key.indexOf(44, firstComma + 1); + if (secondComma == -1) { + throw new IllegalArgumentException("Invalid filler metadata! Missing second comma"); + } + + int end = key.indexOf(124, start); + if (end == -1) { + end = key.length(); + } + + int fillerX = Integer.parseInt(key, start, firstComma, 10); + int fillerY = Integer.parseInt(key, firstComma + 1, secondComma, 10); + int fillerZ = Integer.parseInt(key, secondComma + 1, end, 10); + filler = FillerBlockUtil.pack(fillerX, fillerY, fillerZ); + } + + Rotation rotationYaw = Rotation.None; + Rotation rotationPitch = Rotation.None; + Rotation rotationRoll = Rotation.None; + if (key.contains("|Yaw=")) { + int startx = key.indexOf("|Yaw=") + "|Yaw=".length(); + int end = key.indexOf(124, startx); + if (end == -1) { + end = key.length(); + } + + rotationYaw = Rotation.ofDegrees(Integer.parseInt(key, startx, end, 10)); + } + + if (key.contains("|Pitch=")) { + int startx = key.indexOf("|Pitch=") + "|Pitch=".length(); + int end = key.indexOf(124, startx); + if (end == -1) { + end = key.length(); + } + + rotationPitch = Rotation.ofDegrees(Integer.parseInt(key, startx, end, 10)); + } + + if (key.contains("|Roll=")) { + int startx = key.indexOf("|Roll=") + "|Roll=".length(); + int end = key.indexOf(124, startx); + if (end == -1) { + end = key.length(); + } + + rotationRoll = Rotation.ofDegrees(Integer.parseInt(key, startx, end, 10)); + } + + int end = key.indexOf(124); + if (end == -1) { + end = key.length(); + } + + String name = key.substring(0, end); + return new BlockPattern.BlockEntry(name, RotationTuple.of(rotationYaw, rotationPitch, rotationRoll).index(), filler); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/mask/MultiBlockMask.java b/src/com/hypixel/hytale/server/core/prefab/selection/mask/MultiBlockMask.java new file mode 100644 index 0000000..9f93556 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/mask/MultiBlockMask.java @@ -0,0 +1,72 @@ +package com.hypixel.hytale.server.core.prefab.selection.mask; + +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.universe.world.accessor.ChunkAccessor; +import javax.annotation.Nonnull; + +public class MultiBlockMask extends BlockMask { + private static final String BLOCK_MASK_SEPARATOR = ";"; + private final BlockMask[] masks; + + public MultiBlockMask(BlockMask[] masks) { + super(BlockFilter.EMPTY_ARRAY); + this.masks = masks; + } + + @Override + public boolean isExcluded(@Nonnull ChunkAccessor accessor, int x, int y, int z, Vector3i min, Vector3i max, int blockId) { + return this.isExcluded(accessor, x, y, z, min, max, blockId, -1); + } + + @Override + public boolean isExcluded(@Nonnull ChunkAccessor accessor, int x, int y, int z, Vector3i min, Vector3i max, int blockId, int fluidId) { + boolean excluded = false; + + for (BlockMask mask : this.masks) { + if (mask.isExcluded(accessor, x, y, z, min, max, blockId, fluidId)) { + excluded = true; + break; + } + } + + return this.isInverted() != excluded; + } + + @Nonnull + @Override + public String toString() { + if (this.masks.length == 0) { + return "-"; + } else { + String base = joinElements(";", this.masks); + return this.isInverted() ? "!" + base : base; + } + } + + @Nonnull + @Override + public String informativeToString() { + if (this.masks.length == 0) { + return "-"; + } else { + StringBuilder builder = new StringBuilder(); + if (this.isInverted()) { + builder.append("NOT("); + } + + for (int i = 0; i < this.masks.length; i++) { + BlockMask mask = this.masks[i]; + builder.append(mask.informativeToString()); + if (i != this.masks.length - 1) { + builder.append(" AND "); + } + } + + if (this.isInverted()) { + builder.append(")"); + } + + return builder.toString(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/standard/BlockSelection.java b/src/com/hypixel/hytale/server/core/prefab/selection/standard/BlockSelection.java new file mode 100644 index 0000000..0e77069 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/standard/BlockSelection.java @@ -0,0 +1,1828 @@ +package com.hypixel.hytale.server.core.prefab.selection.standard; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemType; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.block.BlockUtil; +import com.hypixel.hytale.math.matrix.Matrix4d; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.metrics.MetricProvider; +import com.hypixel.hytale.metrics.MetricResults; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.protocol.Opacity; +import com.hypixel.hytale.protocol.packets.interface_.BlockChange; +import com.hypixel.hytale.protocol.packets.interface_.EditorBlocksChange; +import com.hypixel.hytale.protocol.packets.interface_.EditorSelection; +import com.hypixel.hytale.protocol.packets.interface_.FluidChange; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.StateData; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.VariantRotation; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.asset.type.fluid.FluidTicker; +import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.FromPrefab; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.prefab.event.PrefabPlaceEntityEvent; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockMask; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockRotationUtil; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.util.BitSet; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Consumer; +import java.util.function.IntUnaryOperator; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public class BlockSelection implements NetworkSerializable, MetricProvider { + public static final Consumer> DEFAULT_ENTITY_CONSUMER = ref -> {}; + public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() + .register("BlocksLock", selection -> selection.blocksLock.toString(), Codec.STRING) + .register("EntitiesLock", selection -> selection.entitiesLock.toString(), Codec.STRING) + .register("Position", selection -> new Vector3i(selection.x, selection.y, selection.z), Vector3i.CODEC) + .register("Anchor", selection -> new Vector3i(selection.anchorX, selection.anchorY, selection.anchorZ), Vector3i.CODEC) + .register("Min", BlockSelection::getSelectionMin, Vector3i.CODEC) + .register("Max", BlockSelection::getSelectionMax, Vector3i.CODEC) + .register("BlockCount", BlockSelection::getBlockCount, Codec.INTEGER) + .register("EntityCount", BlockSelection::getEntityCount, Codec.INTEGER); + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private int x; + private int y; + private int z; + private int anchorX; + private int anchorY; + private int anchorZ; + private int prefabId = -1; + @Nonnull + private Vector3i min = Vector3i.ZERO; + @Nonnull + private Vector3i max = Vector3i.ZERO; + @Nonnull + private final Long2ObjectMap blocks; + @Nonnull + private final Long2ObjectMap fluids; + @Nonnull + private final List> entities; + private final ReentrantReadWriteLock blocksLock = new ReentrantReadWriteLock(); + private final ReentrantReadWriteLock entitiesLock = new ReentrantReadWriteLock(); + + public BlockSelection() { + this.blocks = new Long2ObjectOpenHashMap<>(); + this.fluids = new Long2ObjectOpenHashMap<>(); + this.entities = new ObjectArrayList<>(); + } + + public BlockSelection(int initialBlockCapacity, int initialEntityCapacity) { + this.blocks = new Long2ObjectOpenHashMap<>(initialBlockCapacity); + this.fluids = new Long2ObjectOpenHashMap<>(initialBlockCapacity); + this.entities = new ObjectArrayList<>(initialEntityCapacity); + } + + public BlockSelection(@Nonnull BlockSelection other) { + if (other == this) { + throw new IllegalArgumentException("Cannot duplicate a BlockSelection with this method! Use clone()!"); + } else { + this.blocks = new Long2ObjectOpenHashMap<>(other.getBlockCount()); + this.fluids = new Long2ObjectOpenHashMap<>(other.getFluidCount()); + this.entities = new ObjectArrayList<>(other.getEntityCount()); + this.copyPropertiesFrom(other); + this.add(other); + } + } + + public int getX() { + return this.x; + } + + public int getY() { + return this.y; + } + + public int getZ() { + return this.z; + } + + public int getAnchorX() { + return this.anchorX; + } + + public int getAnchorY() { + return this.anchorY; + } + + public int getAnchorZ() { + return this.anchorZ; + } + + @Nonnull + public Vector3i getSelectionMin() { + return this.min.clone(); + } + + @Nonnull + public Vector3i getSelectionMax() { + return this.max.clone(); + } + + public boolean hasSelectionBounds() { + return !this.min.equals(Vector3i.ZERO) || !this.max.equals(Vector3i.ZERO); + } + + public int getBlockCount() { + this.blocksLock.readLock().lock(); + + int var1; + try { + var1 = this.blocks.size(); + } finally { + this.blocksLock.readLock().unlock(); + } + + return var1; + } + + public int getFluidCount() { + this.blocksLock.readLock().lock(); + + int var1; + try { + var1 = this.fluids.size(); + } finally { + this.blocksLock.readLock().unlock(); + } + + return var1; + } + + public int getSelectionVolume() { + int xLength = this.max.x - this.min.x; + int yLength = this.max.y - this.min.y; + int zLength = this.max.z - this.min.z; + return xLength * yLength & zLength; + } + + public int getEntityCount() { + this.entitiesLock.readLock().lock(); + + int var1; + try { + var1 = this.entities.size(); + } finally { + this.entitiesLock.readLock().unlock(); + } + + return var1; + } + + public void setPosition(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public void setAnchorAtWorldPos(int anchorX, int anchorY, int anchorZ) { + this.setAnchor(anchorX - this.x, anchorY - this.y, anchorZ - this.z); + } + + public void setAnchor(int anchorX, int anchorY, int anchorZ) { + this.anchorX = anchorX; + this.anchorY = anchorY; + this.anchorZ = anchorZ; + } + + public void setSelectionArea(@Nonnull Vector3i min, @Nonnull Vector3i max) { + this.min = Vector3i.min(min, max); + this.max = Vector3i.max(min, max); + } + + public void setPrefabId(int id) { + this.prefabId = id; + } + + public void copyPropertiesFrom(@Nonnull BlockSelection other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.anchorX = other.anchorX; + this.anchorY = other.anchorY; + this.anchorZ = other.anchorZ; + this.min = other.min.clone(); + this.max = other.max.clone(); + } + + public boolean canPlace(@Nonnull World world, @Nonnull Vector3i position, @Nullable IntList mask) { + return this.compare((x1, y1, z1, block) -> { + int blockX = x1 + position.getX() - this.anchorX; + int blockY = y1 + position.getY() - this.anchorY; + int blockZ = z1 + position.getZ() - this.anchorZ; + int blockId = world.getBlock(blockX, blockY, blockZ); + return blockId == 0 || mask == null || mask.contains(blockId); + }); + } + + public boolean matches(@Nonnull World world, @Nonnull Vector3i position) { + return this.compare((x1, y1, z1, block) -> { + int blockX = x1 + position.getX() - this.anchorX; + int blockY = y1 + position.getY() - this.anchorY; + int blockZ = z1 + position.getZ() - this.anchorZ; + int blockId = world.getBlock(blockX, blockY, blockZ); + return block.blockId == blockId; + }); + } + + public boolean compare(@Nonnull BlockSelection.BlockComparingIterator iterator) { + for (Entry entry : this.blocks.long2ObjectEntrySet()) { + long packed = entry.getLongKey(); + BlockSelection.BlockHolder value = entry.getValue(); + int x1 = BlockUtil.unpackX(packed); + int y1 = BlockUtil.unpackY(packed); + int z1 = BlockUtil.unpackZ(packed); + if (!iterator.test(x1, y1, z1, value)) { + return false; + } + } + + return true; + } + + public boolean hasBlockAtWorldPos(int x, int y, int z) { + return this.hasBlockAtLocalPos(x - this.x, y - this.y, z - this.z); + } + + public boolean hasBlockAtLocalPos(int x, int y, int z) { + this.blocksLock.readLock().lock(); + + boolean var4; + try { + var4 = this.blocks.containsKey(BlockUtil.pack(x, y, z)); + } finally { + this.blocksLock.readLock().unlock(); + } + + return var4; + } + + public int getBlockAtWorldPos(int x, int y, int z) { + return this.getBlockAtLocalPos(x - this.x, y - this.y, z - this.z); + } + + private int getBlockAtLocalPos(int x, int y, int z) { + this.blocksLock.readLock().lock(); + + int var5; + try { + BlockSelection.BlockHolder blockHolder = this.blocks.get(BlockUtil.pack(x, y, z)); + if (blockHolder != null) { + return blockHolder.blockId(); + } + + var5 = Integer.MIN_VALUE; + } finally { + this.blocksLock.readLock().unlock(); + } + + return var5; + } + + public BlockSelection.BlockHolder getBlockHolderAtWorldPos(int x, int y, int z) { + return this.getBlockHolderAtLocalPos(x - this.x, y - this.y, z - this.z); + } + + private BlockSelection.BlockHolder getBlockHolderAtLocalPos(int x, int y, int z) { + this.blocksLock.readLock().lock(); + + BlockSelection.BlockHolder var4; + try { + var4 = this.blocks.get(BlockUtil.pack(x, y, z)); + } finally { + this.blocksLock.readLock().unlock(); + } + + return var4; + } + + public int getFluidAtWorldPos(int x, int y, int z) { + return this.getFluidAtLocalPos(x - this.x, y - this.y, z - this.z); + } + + private int getFluidAtLocalPos(int x, int y, int z) { + this.blocksLock.readLock().lock(); + + int var5; + try { + BlockSelection.FluidHolder fluidStore = this.fluids.get(BlockUtil.pack(x, y, z)); + if (fluidStore != null) { + return fluidStore.fluidId(); + } + + var5 = Integer.MIN_VALUE; + } finally { + this.blocksLock.readLock().unlock(); + } + + return var5; + } + + public byte getFluidLevelAtWorldPos(int x, int y, int z) { + return this.getFluidLevelAtLocalPos(x - this.x, y - this.y, z - this.z); + } + + private byte getFluidLevelAtLocalPos(int x, int y, int z) { + this.blocksLock.readLock().lock(); + + byte var5; + try { + BlockSelection.FluidHolder fluidStore = this.fluids.get(BlockUtil.pack(x, y, z)); + if (fluidStore != null) { + return fluidStore.fluidLevel(); + } + + var5 = 0; + } finally { + this.blocksLock.readLock().unlock(); + } + + return var5; + } + + public int getSupportValueAtWorldPos(int x, int y, int z) { + return this.getSupportValueAtLocalPos(x - this.x, y - this.y, z - this.z); + } + + private int getSupportValueAtLocalPos(int x, int y, int z) { + this.blocksLock.readLock().lock(); + + byte var5; + try { + BlockSelection.BlockHolder blockHolder = this.blocks.get(BlockUtil.pack(x, y, z)); + if (blockHolder != null) { + return blockHolder.supportValue(); + } + + var5 = 0; + } finally { + this.blocksLock.readLock().unlock(); + } + + return var5; + } + + @Nullable + public Holder getStateAtWorldPos(int x, int y, int z) { + return this.getStateAtLocalPos(x - this.x, y - this.y, z - this.z); + } + + @Nullable + private Holder getStateAtLocalPos(int x, int y, int z) { + this.blocksLock.readLock().lock(); + + Holder holder; + try { + BlockSelection.BlockHolder blockHolder = this.blocks.get(BlockUtil.pack(x, y, z)); + if (blockHolder != null) { + holder = blockHolder.holder(); + return holder != null ? holder.clone() : null; + } + + holder = null; + } finally { + this.blocksLock.readLock().unlock(); + } + + return holder; + } + + public void forEachBlock(@Nonnull BlockSelection.BlockIterator iterator) { + this.blocksLock.readLock().lock(); + + try { + Long2ObjectMaps.fastForEach(this.blocks, e -> { + long packed = e.getLongKey(); + BlockSelection.BlockHolder block = e.getValue(); + int x1 = BlockUtil.unpackX(packed); + int y1 = BlockUtil.unpackY(packed); + int z1 = BlockUtil.unpackZ(packed); + iterator.accept(x1, y1, z1, block); + }); + } finally { + this.blocksLock.readLock().unlock(); + } + } + + public void forEachFluid(@Nonnull BlockSelection.FluidIterator iterator) { + this.blocksLock.readLock().lock(); + + try { + Long2ObjectMaps.fastForEach(this.fluids, e -> { + long packed = e.getLongKey(); + BlockSelection.FluidHolder block = e.getValue(); + int x1 = BlockUtil.unpackX(packed); + int y1 = BlockUtil.unpackY(packed); + int z1 = BlockUtil.unpackZ(packed); + iterator.accept(x1, y1, z1, block.fluidId(), block.fluidLevel()); + }); + } finally { + this.blocksLock.readLock().unlock(); + } + } + + public void forEachEntity(Consumer> consumer) { + this.entitiesLock.readLock().lock(); + + try { + this.entities.forEach(consumer); + } finally { + this.entitiesLock.readLock().unlock(); + } + } + + public void copyFromAtWorld(int x, int y, int z, @Nonnull WorldChunk other, @Nullable BlockPhysics blockPhysics) { + this.addBlockAtWorldPos( + x, + y, + z, + other.getBlock(x, y, z), + other.getRotationIndex(x, y, z), + other.getFiller(x, y, z), + blockPhysics != null ? blockPhysics.get(x, y, z) : 0, + other.getBlockComponentHolder(x, y, z) + ); + this.addFluidAtWorldPos(x, y, z, other.getFluidId(x, y, z), other.getFluidLevel(x, y, z)); + } + + public void addEmptyAtWorldPos(int x, int y, int z) { + this.addBlockAtWorldPos(x, y, z, 0, 0, 0, 0); + this.addFluidAtWorldPos(x, y, z, 0, (byte)0); + } + + public void addBlockAtWorldPos(int x, int y, int z, int block, int rotation, int filler, int supportValue) { + this.addBlockAtWorldPos(x, y, z, block, rotation, filler, supportValue, null); + } + + public void addBlockAtWorldPos(int x, int y, int z, int block, int rotation, int filler, int supportValue, Holder state) { + this.addBlockAtLocalPos(x - this.x, y - this.y, z - this.z, block, rotation, filler, supportValue, state); + } + + public void addBlockAtLocalPos(int x, int y, int z, int block, int rotation, int filler, int supportValue) { + this.addBlockAtLocalPos(x, y, z, block, rotation, filler, supportValue, null); + } + + public void addBlockAtLocalPos(int x, int y, int z, int block, int rotation, int filler, int supportValue, Holder state) { + this.blocksLock.writeLock().lock(); + + try { + this.addBlock0(x, y, z, block, rotation, filler, supportValue, state); + } finally { + this.blocksLock.writeLock().unlock(); + } + } + + private void addBlock0(int x, int y, int z, int block, int rotation, int filler, int supportValue, Holder state) { + this.blocks.put(BlockUtil.pack(x, y, z), new BlockSelection.BlockHolder(block, rotation, filler, supportValue, state)); + } + + private void addBlock0(int x, int y, int z, @Nonnull BlockSelection.BlockHolder block) { + this.blocks.put(BlockUtil.pack(x, y, z), block.cloneBlockHolder()); + } + + public void addFluidAtWorldPos(int x, int y, int z, int fluidId, byte fluidLevel) { + this.addFluidAtLocalPos(x - this.x, y - this.y, z - this.z, fluidId, fluidLevel); + } + + public void addFluidAtLocalPos(int x, int y, int z, int fluidId, byte fluidLevel) { + this.blocksLock.writeLock().lock(); + + try { + this.addFluid0(x, y, z, fluidId, fluidLevel); + } finally { + this.blocksLock.writeLock().unlock(); + } + } + + private void addFluid0(int x, int y, int z, int fluidId, byte fluidLevel) { + this.fluids.put(BlockUtil.pack(x, y, z), new BlockSelection.FluidHolder(fluidId, fluidLevel)); + } + + private void addEntity0(Holder holder) { + this.entities.add(holder); + } + + public void reserializeBlockStates(ChunkStore store, boolean destructive) { + this.blocksLock.writeLock().lock(); + + try { + this.blocks + .replaceAll( + (k, b) -> { + Holder holder = b.holder(); + if (holder == null && b.filler == 0) { + BlockType blockType = BlockType.getAssetMap().getAsset(b.blockId); + if (blockType == null) { + return (BlockSelection.BlockHolder)b; + } + + if (blockType.getBlockEntity() != null) { + holder = blockType.getBlockEntity().clone(); + } + + StateData state = blockType.getState(); + if (state != null && state.getId() != null) { + Vector3i position = new Vector3i(BlockUtil.unpackX(k), BlockUtil.unpackY(k), BlockUtil.unpackZ(k)); + Codec codec = BlockState.CODEC.getCodecFor(state.getId()); + if (codec == null) { + return (BlockSelection.BlockHolder)b; + } + + BlockState blockState = codec.decode(new BsonDocument()); + if (blockState == null) { + return (BlockSelection.BlockHolder)b; + } + + blockState.setPosition(null, position); + holder = blockState.toHolder(); + } + } + + if (holder == null) { + return (BlockSelection.BlockHolder)b; + } else { + try { + ComponentRegistry registry = ChunkStore.REGISTRY; + ComponentRegistry.Data data = registry.getData(); + SystemType systemType = BlockModule.get().getMigrationSystemType(); + BitSet systemIndexes = data.getSystemIndexesForType(systemType); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + BlockModule.MigrationSystem system = data.getSystem(systemIndex, systemType); + if (system.test(registry, holder.getArchetype())) { + system.onEntityAdd(holder, AddReason.LOAD, store.getStore()); + } + } + + systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + BlockModule.MigrationSystem system = data.getSystem(systemIndex, systemType); + if (system.test(registry, holder.getArchetype())) { + system.onEntityRemoved(holder, RemoveReason.UNLOAD, store.getStore()); + } + } + + if (destructive) { + holder.tryRemoveComponent(registry.getUnknownComponentType()); + } + + return !holder.hasSerializableComponents(data) + ? new BlockSelection.BlockHolder(b.blockId(), b.rotation(), b.filler(), b.supportValue(), null) + : new BlockSelection.BlockHolder(b.blockId(), b.rotation(), b.filler(), b.supportValue(), holder.clone()); + } catch (Throwable var11) { + throw new RuntimeException("Failed to read block state: " + b, var11); + } + } + } + ); + } finally { + this.blocksLock.writeLock().unlock(); + } + } + + public void addEntityFromWorld(@Nonnull Holder entityHolder) { + TransformComponent transformComponent = entityHolder.getComponent(TransformComponent.getComponentType()); + + assert transformComponent != null; + + transformComponent.getPosition().subtract(this.x, this.y, this.z); + this.addEntityHolderRaw(entityHolder); + } + + public void addEntityHolderRaw(Holder entityHolder) { + this.entitiesLock.writeLock().lock(); + + try { + this.entities.add(entityHolder); + } finally { + this.entitiesLock.writeLock().unlock(); + } + } + + public void placeNoReturn(@Nonnull World world, Vector3i position, ComponentAccessor componentAccessor) { + this.placeNoReturn(null, null, FeedbackConsumer.DEFAULT, world, position, null, componentAccessor); + } + + public void placeNoReturn(String feedbackKey, CommandSender feedback, @Nonnull World outerWorld, ComponentAccessor componentAccessor) { + this.placeNoReturn(feedbackKey, feedback, FeedbackConsumer.DEFAULT, outerWorld, Vector3i.ZERO, null, componentAccessor); + } + + public void placeNoReturn( + String feedbackKey, + CommandSender feedback, + @Nonnull FeedbackConsumer feedbackConsumer, + @Nonnull World outerWorld, + ComponentAccessor componentAccessor + ) { + this.placeNoReturn(feedbackKey, feedback, feedbackConsumer, outerWorld, Vector3i.ZERO, null, componentAccessor); + } + + public void placeNoReturn( + @Nullable String feedbackKey, + @Nullable CommandSender feedback, + @Nonnull FeedbackConsumer feedbackConsumer, + @Nonnull World outerWorld, + @Nullable Vector3i position, + @Nullable BlockMask blockMask, + ComponentAccessor componentAccessor + ) { + IntUnaryOperator xConvert; + if (position != null && position.getX() != 0) { + xConvert = localX -> localX + this.x + position.getX() - this.anchorX; + } else { + xConvert = localX -> localX + this.x - this.anchorX; + } + + IntUnaryOperator yConvert; + if (position != null && position.getY() != 0) { + yConvert = localY -> localY + this.y + position.getY() - this.anchorY; + } else { + yConvert = localY -> localY + this.y - this.anchorY; + } + + IntUnaryOperator zConvert; + if (position != null && position.getZ() != 0) { + zConvert = localZ -> localZ + this.z + position.getZ() - this.anchorZ; + } else { + zConvert = localZ -> localZ + this.z - this.anchorZ; + } + + LongSet dirtyChunks = new LongOpenHashSet(); + this.blocksLock.readLock().lock(); + + try { + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + int totalBlocks = this.blocks.size(); + AtomicInteger counter = new AtomicInteger(); + outerWorld.getBlockBulkRelative( + this.blocks, + xConvert, + yConvert, + zConvert, + (world, blockHolder, chunkIndex, chunk, blockX, blockY, blockZ, localX, localY, localZ) -> { + int newBlockId = blockHolder.blockId(); + Holder holder = blockHolder.holder(); + this.placeBlockNoReturn( + feedbackKey, + feedback, + feedbackConsumer, + outerWorld, + blockMask, + dirtyChunks, + assetMap, + totalBlocks, + counter.incrementAndGet(), + chunkIndex, + chunk, + blockX, + blockY, + blockZ, + newBlockId, + blockHolder.rotation(), + blockHolder.filler(), + holder != null ? holder.clone() : null, + componentAccessor + ); + } + ); + outerWorld.getBlockBulkRelative( + this.fluids, + xConvert, + yConvert, + zConvert, + (world, fluidStore, chunkIndex, chunk, blockX, blockY, blockZ, localX, localY, localZ) -> this.placeFluidNoReturn( + feedbackKey, + feedback, + feedbackConsumer, + outerWorld, + blockMask, + dirtyChunks, + assetMap, + totalBlocks, + counter.incrementAndGet(), + chunkIndex, + chunk, + blockX, + blockY, + blockZ, + fluidStore.fluidId, + fluidStore.fluidLevel, + componentAccessor + ) + ); + } finally { + this.blocksLock.readLock().unlock(); + } + + dirtyChunks.forEach(value -> outerWorld.getChunkLighting().invalidateLightInChunk(outerWorld.getChunkIfInMemory(value))); + this.placeEntities(outerWorld, position); + dirtyChunks.forEach(value -> outerWorld.getNotificationHandler().updateChunk(value)); + } + + private void placeBlockNoReturn( + String feedbackKey, + CommandSender feedback, + @Nonnull FeedbackConsumer feedbackConsumer, + @Nonnull World outerWorld, + @Nullable BlockMask blockMask, + @Nonnull LongSet dirtyChunks, + @Nonnull BlockTypeAssetMap assetMap, + int totalBlocks, + int counter, + long chunkIndex, + @Nonnull WorldChunk chunk, + int blockX, + int blockY, + int blockZ, + int newBlockId, + int newRotation, + int newFiller, + Holder holder, + ComponentAccessor componentAccessor + ) { + if (blockY >= 0 && blockY < 320) { + int oldBlockId = chunk.getBlock(blockX, blockY, blockZ); + if (blockMask == null || !blockMask.isExcluded(outerWorld, blockX, blockY, blockZ, this.min, this.max, oldBlockId)) { + BlockChunk blockChunk = chunk.getBlockChunk(); + if (blockChunk.setBlock(blockX, blockY, blockZ, newBlockId, newRotation, newFiller)) { + BlockType newBlockType = assetMap.getAsset(newBlockId); + if (newBlockType != null && FluidTicker.isFullySolid(newBlockType)) { + this.clearFluidAtPosition(outerWorld, chunk, blockX, blockY, blockZ); + } + + short height = blockChunk.getHeight(blockX, blockZ); + if (height <= blockY) { + if (height == blockY && newBlockId == 0) { + blockChunk.updateHeight(blockX, blockZ, (short)blockY); + } else if (height < blockY && newBlockId != 0 && newBlockType != null && newBlockType.getOpacity() != Opacity.Transparent) { + blockChunk.setHeight(blockX, blockZ, (short)blockY); + } + } + } + + chunk.setState(blockX, blockY, blockZ, holder); + dirtyChunks.add(chunkIndex); + feedbackConsumer.accept(feedbackKey, totalBlocks, counter, feedback, componentAccessor); + } + } + } + + private void placeFluidNoReturn( + String feedbackKey, + CommandSender feedback, + @Nonnull FeedbackConsumer feedbackConsumer, + @Nonnull World outerWorld, + BlockMask blockMask, + @Nonnull LongSet dirtyChunks, + BlockTypeAssetMap assetMap, + int totalBlocks, + int counter, + long chunkIndex, + @Nonnull WorldChunk chunk, + int blockX, + int blockY, + int blockZ, + int newFluidId, + byte newFluidLevel, + ComponentAccessor componentAccessor + ) { + if (blockY >= 0 && blockY < 320) { + int sectionY = ChunkUtil.chunkCoordinate(blockY); + Store store = outerWorld.getChunkStore().getStore(); + ChunkColumn column = store.getComponent(chunk.getReference(), ChunkColumn.getComponentType()); + Ref section = column.getSection(sectionY); + FluidSection fluidSection = store.ensureAndGetComponent(section, FluidSection.getComponentType()); + fluidSection.setFluid(blockX, blockY, blockZ, newFluidId, newFluidLevel); + dirtyChunks.add(chunkIndex); + feedbackConsumer.accept(feedbackKey, totalBlocks, counter, feedback, componentAccessor); + } + } + + private void clearFluidAtPosition(@Nonnull World world, @Nonnull WorldChunk chunk, int blockX, int blockY, int blockZ) { + Ref ref = chunk.getReference(); + if (ref != null && ref.isValid()) { + Store store = world.getChunkStore().getStore(); + ChunkColumn column = store.getComponent(ref, ChunkColumn.getComponentType()); + if (column != null) { + Ref section = column.getSection(ChunkUtil.chunkCoordinate(blockY)); + if (section != null) { + FluidSection fluidSection = store.getComponent(section, FluidSection.getComponentType()); + if (fluidSection != null) { + fluidSection.setFluid(blockX, blockY, blockZ, 0, (byte)0); + } + } + } + } + } + + @Nonnull + public BlockSelection place(CommandSender feedback, @Nonnull World outerWorld) { + return this.place(feedback, outerWorld, Vector3i.ZERO, null); + } + + @Nonnull + public BlockSelection place(CommandSender feedback, @Nonnull World outerWorld, BlockMask blockMask) { + return this.place(feedback, outerWorld, Vector3i.ZERO, blockMask); + } + + @Nonnull + public BlockSelection place(CommandSender feedback, @Nonnull World outerWorld, Vector3i position, BlockMask blockMask) { + return this.place(feedback, outerWorld, position, blockMask, DEFAULT_ENTITY_CONSUMER); + } + + @Nonnull + public BlockSelection place( + CommandSender feedback, + @Nonnull World outerWorld, + @Nullable Vector3i position, + @Nullable BlockMask blockMask, + @Nonnull Consumer> entityConsumer + ) { + BlockSelection before = new BlockSelection(this.getBlockCount(), 0); + before.setAnchor(this.anchorX, this.anchorY, this.anchorZ); + before.setPosition(this.x, this.y, this.z); + IntUnaryOperator xConvert; + if (position != null && position.getX() != 0) { + xConvert = localX -> localX + this.x + position.getX() - this.anchorX; + } else { + xConvert = localX -> localX + this.x - this.anchorX; + } + + IntUnaryOperator yConvert; + if (position != null && position.getY() != 0) { + yConvert = localY -> localY + this.y + position.getY() - this.anchorY; + } else { + yConvert = localY -> localY + this.y - this.anchorY; + } + + IntUnaryOperator zConvert; + if (position != null && position.getZ() != 0) { + zConvert = localZ -> localZ + this.z + position.getZ() - this.anchorZ; + } else { + zConvert = localZ -> localZ + this.z - this.anchorZ; + } + + LongSet dirtyChunks = new LongOpenHashSet(); + this.blocksLock.readLock().lock(); + + try { + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + outerWorld.getBlockBulkRelative( + this.blocks, + xConvert, + yConvert, + zConvert, + (world, blockHolder, chunkIndex, chunk, blockX, blockY, blockZ, localX, localY, localZ) -> { + Holder holder = blockHolder.holder(); + this.placeBlock( + feedback, + outerWorld, + blockMask, + before, + dirtyChunks, + assetMap, + chunkIndex, + chunk, + blockX, + blockY, + blockZ, + localX, + localY, + localZ, + blockHolder.blockId(), + blockHolder.rotation(), + blockHolder.filler(), + holder != null ? holder.clone() : null, + blockHolder.supportValue() + ); + } + ); + IndexedLookupTableAssetMap fluidMap = Fluid.getAssetMap(); + outerWorld.getBlockBulkRelative( + this.fluids, + xConvert, + yConvert, + zConvert, + (world, fluidStore, chunkIndex, chunk, blockX, blockY, blockZ, localX, localY, localZ) -> this.placeFluid( + feedback, + outerWorld, + before, + dirtyChunks, + fluidMap, + chunkIndex, + chunk, + blockX, + blockY, + blockZ, + localX, + localY, + localZ, + fluidStore.fluidId, + fluidStore.fluidLevel + ) + ); + } finally { + this.blocksLock.readLock().unlock(); + } + + dirtyChunks.forEach(value -> outerWorld.getChunkLighting().invalidateLightInChunk(outerWorld.getChunkIfInMemory(value))); + this.placeEntities(outerWorld, position, entityConsumer); + dirtyChunks.forEach(value -> outerWorld.getNotificationHandler().updateChunk(value)); + return before; + } + + private void placeBlock( + CommandSender feedback, + @Nonnull World outerWorld, + @Nullable BlockMask blockMask, + @Nonnull BlockSelection before, + @Nonnull LongSet dirtyChunks, + @Nonnull BlockTypeAssetMap assetMap, + long chunkIndex, + @Nonnull WorldChunk chunk, + int blockX, + int blockY, + int blockZ, + int localX, + int localY, + int localZ, + int newBlockId, + int newRotation, + int newFiller, + Holder holder, + int newSupportValue + ) { + if (blockY >= 0 && blockY < 320) { + Store chunkStore = chunk.getWorld().getChunkStore().getStore(); + ChunkColumn chunkColumn = chunkStore.getComponent(chunk.getReference(), ChunkColumn.getComponentType()); + Ref section = chunkColumn.getSection(ChunkUtil.chunkCoordinate(blockY)); + BlockSection blockSection = chunkStore.getComponent(section, BlockSection.getComponentType()); + int oldBlockId = chunk.getBlock(blockX, blockY, blockZ); + if (blockMask == null || !blockMask.isExcluded(outerWorld, blockX, blockY, blockZ, this.min, this.max, oldBlockId)) { + BlockPhysics blockPhysics = section != null ? chunkStore.getComponent(section, BlockPhysics.getComponentType()) : null; + int supportValue = blockPhysics != null ? blockPhysics.get(blockX, blockY, blockZ) : 0; + int filler = blockSection.getFiller(blockX, blockY, blockZ); + int rotation = blockSection.getRotationIndex(blockX, blockY, blockZ); + before.addBlockAtLocalPos(localX, localY, localZ, oldBlockId, rotation, filler, supportValue, chunk.getBlockComponentHolder(blockX, blockY, blockZ)); + BlockChunk blockChunk = chunk.getBlockChunk(); + if (blockChunk.setBlock(blockX, blockY, blockZ, newBlockId, newRotation, newFiller)) { + BlockType newBlockType = assetMap.getAsset(newBlockId); + if (newBlockType != null && FluidTicker.isFullySolid(newBlockType)) { + this.clearFluidAtPosition(outerWorld, chunk, blockX, blockY, blockZ); + } + + short height = blockChunk.getHeight(blockX, blockZ); + if (height <= blockY) { + if (height == blockY && newBlockId == 0) { + blockChunk.updateHeight(blockX, blockZ, (short)blockY); + } else if (height < blockY && newBlockId != 0 && newBlockType.getOpacity() != Opacity.Transparent) { + blockChunk.setHeight(blockX, blockZ, (short)blockY); + } + } + + if (newSupportValue != supportValue) { + if (newSupportValue != 0) { + if (blockPhysics == null) { + blockPhysics = chunkStore.ensureAndGetComponent(section, BlockPhysics.getComponentType()); + } + + blockPhysics.set(blockX, blockY, blockZ, newSupportValue); + } else if (blockPhysics != null) { + blockPhysics.set(blockX, blockY, blockZ, 0); + } + } + } + + chunk.setState(blockX, blockY, blockZ, holder); + dirtyChunks.add(chunkIndex); + } + } + } + + private void placeFluid( + CommandSender feedback, + @Nonnull World outerWorld, + @Nonnull BlockSelection before, + @Nonnull LongSet dirtyChunks, + IndexedLookupTableAssetMap assetMap, + long chunkIndex, + @Nonnull WorldChunk chunk, + int blockX, + int blockY, + int blockZ, + int localX, + int localY, + int localZ, + int newFluidId, + byte newFluidLevel + ) { + if (blockY >= 0 && blockY < 320) { + int sectionY = ChunkUtil.chunkCoordinate(blockY); + Store store = outerWorld.getChunkStore().getStore(); + ChunkColumn column = store.getComponent(chunk.getReference(), ChunkColumn.getComponentType()); + Ref section = column.getSection(sectionY); + FluidSection fluidSection = store.ensureAndGetComponent(section, FluidSection.getComponentType()); + int oldFluidId = fluidSection.getFluidId(blockX, blockY, blockZ); + byte oldFluidLevel = fluidSection.getFluidLevel(blockX, blockY, blockZ); + before.addFluidAtLocalPos(localX, localY, localZ, oldFluidId, oldFluidLevel); + fluidSection.setFluid(blockX, blockY, blockZ, newFluidId, newFluidLevel); + dirtyChunks.add(chunkIndex); + } + } + + private void placeEntities(@Nonnull World world, @Nonnull Vector3i pos) { + this.placeEntities(world, pos, DEFAULT_ENTITY_CONSUMER); + } + + private void placeEntities(@Nonnull World world, @Nonnull Vector3i pos, @Nonnull Consumer> entityConsumer) { + this.entitiesLock.readLock().lock(); + + try { + for (Holder entityHolder : this.entities) { + Ref entity = this.placeEntity(world, entityHolder.clone(), pos, this.prefabId); + if (entity == null) { + LOGGER.at(Level.WARNING).log("Failed to spawn entity in world %s! Data: %s", world.getName(), entityHolder); + } else { + entityConsumer.accept(entity); + } + } + } finally { + this.entitiesLock.readLock().unlock(); + } + } + + @Nonnull + private Ref placeEntity(@Nonnull World world, @Nonnull Holder entityHolder, @Nonnull Vector3i pos, int prefabId) { + TransformComponent transformComponent = entityHolder.getComponent(TransformComponent.getComponentType()); + + assert transformComponent != null; + + transformComponent.getPosition().add(this.x + pos.getX() - this.anchorX, this.y + pos.getY() - this.anchorY, this.z + pos.getZ() - this.anchorZ); + Store store = world.getEntityStore().getStore(); + PrefabPlaceEntityEvent prefabPlaceEntityEvent = new PrefabPlaceEntityEvent(prefabId, entityHolder); + store.invoke(prefabPlaceEntityEvent); + entityHolder.addComponent(FromPrefab.getComponentType(), FromPrefab.INSTANCE); + Ref entityRef = new Ref<>(store); + world.execute(() -> store.addEntity(entityHolder, entityRef, AddReason.LOAD)); + return entityRef; + } + + @Nonnull + public BlockSelection rotate(@Nonnull Axis axis, int angle) { + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + BlockSelection selection = new BlockSelection(this.getBlockCount(), this.getEntityCount()); + selection.copyPropertiesFrom(this); + Vector3i mutable = new Vector3i(0, 0, 0); + Rotation rotation = Rotation.ofDegrees(angle); + this.forEachBlock( + (x1, y1, z1, block) -> { + mutable.assign(x1 - this.anchorX, y1 - this.anchorY, z1 - this.anchorZ); + axis.rotate(mutable, angle); + int blockId = block.blockId; + Holder holder = block.holder; + RotationTuple blockRotation = RotationTuple.get(block.rotation); + + RotationTuple rotatedRotation = switch (axis) { + case X -> RotationTuple.of(blockRotation.yaw(), blockRotation.pitch().add(rotation), blockRotation.roll()); + case Y -> RotationTuple.of(blockRotation.yaw().add(rotation), blockRotation.pitch(), blockRotation.roll()); + case Z -> RotationTuple.of(blockRotation.yaw(), blockRotation.pitch(), blockRotation.roll().add(rotation)); + }; + if (rotatedRotation == null) { + rotatedRotation = blockRotation; + } + + int rotatedFiller = BlockRotationUtil.getRotatedFiller(block.filler, axis, rotation); + selection.addBlock0( + mutable.getX() + this.anchorX, + mutable.getY() + this.anchorY, + mutable.getZ() + this.anchorZ, + blockId, + rotatedRotation.index(), + rotatedFiller, + block.supportValue(), + holder != null ? holder.clone() : null + ); + } + ); + this.forEachEntity(entityHolder -> { + Holder copy = entityHolder.clone(); + TransformComponent transformComponent = copy.getComponent(TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + HeadRotation headRotationComponent = copy.getComponent(HeadRotation.getComponentType()); + position.subtract(this.anchorX, this.anchorY, this.anchorZ).subtract(0.5, 0.0, 0.5); + axis.rotate(position, angle); + position.add(this.anchorX, this.anchorY, this.anchorZ).add(0.5, 0.0, 0.5); + transformComponent.getRotation().addRotationOnAxis(axis, angle); + if (headRotationComponent != null) { + headRotationComponent.getRotation().addRotationOnAxis(axis, angle); + } + + selection.addEntity0(copy); + }); + return selection; + } + + @Nonnull + public BlockSelection rotate(@Nonnull Axis axis, int angle, @Nonnull Vector3f originOfRotation) { + BlockSelection selection = new BlockSelection(this.getBlockCount(), this.getEntityCount()); + selection.copyPropertiesFrom(this); + Vector3d mutable = new Vector3d(0.0, 0.0, 0.0); + Rotation rotation = Rotation.ofDegrees(angle); + Vector3f finalOriginOfRotation = originOfRotation.clone().subtract(this.x, this.y, this.z); + this.forEachBlock( + (x1, y1, z1, block) -> { + mutable.assign(x1 - finalOriginOfRotation.x, y1 - finalOriginOfRotation.y, z1 - finalOriginOfRotation.z); + axis.rotate(mutable, angle); + int blockId = block.blockId; + Holder holder = block.holder; + int supportValue = block.supportValue(); + RotationTuple blockRotation = RotationTuple.get(block.rotation); + + RotationTuple rotatedRotation = switch (axis) { + case X -> RotationTuple.of(blockRotation.yaw(), blockRotation.pitch().add(rotation), blockRotation.roll()); + case Y -> RotationTuple.of(blockRotation.yaw().add(rotation), blockRotation.pitch(), blockRotation.roll()); + case Z -> RotationTuple.of(blockRotation.yaw(), blockRotation.pitch(), blockRotation.roll().add(rotation)); + }; + if (rotatedRotation == null) { + rotatedRotation = blockRotation; + } + + int rotatedFiller = BlockRotationUtil.getRotatedFiller(block.filler, axis, rotation); + selection.addBlock0( + (int)(mutable.getX() + finalOriginOfRotation.x), + (int)(mutable.getY() + finalOriginOfRotation.z), + (int)(mutable.getZ() + finalOriginOfRotation.z), + blockId, + rotatedRotation.index(), + rotatedFiller, + supportValue, + holder != null ? holder.clone() : null + ); + } + ); + this.forEachEntity(entityHolder -> { + Holder copy = entityHolder.clone(); + TransformComponent transformComponent = copy.getComponent(TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + HeadRotation headRotationComponent = copy.getComponent(HeadRotation.getComponentType()); + position.subtract(this.anchorX, this.anchorY, this.anchorZ).subtract(0.5, 0.0, 0.5); + axis.rotate(position, angle); + position.add(this.anchorX, this.anchorY, this.anchorZ).add(0.5, 0.0, 0.5); + transformComponent.getRotation().addRotationOnAxis(axis, angle); + if (headRotationComponent != null) { + headRotationComponent.getRotation().addRotationOnAxis(axis, angle); + } + + selection.addEntity0(copy); + }); + return selection; + } + + @Nonnull + public BlockSelection rotateArbitrary(float yawDegrees, float pitchDegrees, float rollDegrees) { + double pitchRad = Math.toRadians(pitchDegrees); + double yawRad = Math.toRadians(yawDegrees); + double rollRad = Math.toRadians(rollDegrees); + Matrix4d rotation = new Matrix4d(); + rotation.setRotateEuler(pitchRad, yawRad, rollRad); + Matrix4d inverse = new Matrix4d(rotation); + inverse.invert(); + Vector3d tempVec = new Vector3d(); + int destMinX = Integer.MAX_VALUE; + int destMinY = Integer.MAX_VALUE; + int destMinZ = Integer.MAX_VALUE; + int destMaxX = Integer.MIN_VALUE; + int destMaxY = Integer.MIN_VALUE; + int destMaxZ = Integer.MIN_VALUE; + int srcMinX = Integer.MAX_VALUE; + int srcMinY = Integer.MAX_VALUE; + int srcMinZ = Integer.MAX_VALUE; + int srcMaxX = Integer.MIN_VALUE; + int srcMaxY = Integer.MIN_VALUE; + int srcMaxZ = Integer.MIN_VALUE; + this.blocksLock.readLock().lock(); + + try { + for (Entry entry : this.blocks.long2ObjectEntrySet()) { + long packed = entry.getLongKey(); + int bx = BlockUtil.unpackX(packed) - this.anchorX; + int by = BlockUtil.unpackY(packed) - this.anchorY; + int bz = BlockUtil.unpackZ(packed) - this.anchorZ; + srcMinX = Math.min(srcMinX, bx); + srcMinY = Math.min(srcMinY, by); + srcMinZ = Math.min(srcMinZ, bz); + srcMaxX = Math.max(srcMaxX, bx); + srcMaxY = Math.max(srcMaxY, by); + srcMaxZ = Math.max(srcMaxZ, bz); + } + } finally { + this.blocksLock.readLock().unlock(); + } + + if (srcMinX == Integer.MAX_VALUE) { + BlockSelection selection = new BlockSelection(0, this.getEntityCount()); + selection.copyPropertiesFrom(this); + return selection; + } else { + int[][] corners = new int[][]{ + {srcMinX, srcMinY, srcMinZ}, + {srcMaxX, srcMinY, srcMinZ}, + {srcMinX, srcMaxY, srcMinZ}, + {srcMaxX, srcMaxY, srcMinZ}, + {srcMinX, srcMinY, srcMaxZ}, + {srcMaxX, srcMinY, srcMaxZ}, + {srcMinX, srcMaxY, srcMaxZ}, + {srcMaxX, srcMaxY, srcMaxZ} + }; + + for (int[] corner : corners) { + tempVec.assign(corner[0], corner[1], corner[2]); + rotation.multiplyDirection(tempVec); + int rx = MathUtil.floor(tempVec.x); + int ry = MathUtil.floor(tempVec.y); + int rz = MathUtil.floor(tempVec.z); + destMinX = Math.min(destMinX, rx); + destMinY = Math.min(destMinY, ry); + destMinZ = Math.min(destMinZ, rz); + destMaxX = Math.max(destMaxX, rx + 1); + destMaxY = Math.max(destMaxY, ry + 1); + destMaxZ = Math.max(destMaxZ, rz + 1); + } + + BlockSelection selection = new BlockSelection(this.getBlockCount(), this.getEntityCount()); + selection.copyPropertiesFrom(this); + Rotation snappedYaw = Rotation.ofDegrees(Math.round(yawDegrees / 90.0F) * 90); + Rotation snappedPitch = Rotation.ofDegrees(Math.round(pitchDegrees / 90.0F) * 90); + Rotation snappedRoll = Rotation.ofDegrees(Math.round(rollDegrees / 90.0F) * 90); + this.blocksLock.readLock().lock(); + + try { + for (int dx = destMinX; dx <= destMaxX; dx++) { + for (int dy = destMinY; dy <= destMaxY; dy++) { + for (int dz = destMinZ; dz <= destMaxZ; dz++) { + tempVec.assign(dx, dy, dz); + inverse.multiplyDirection(tempVec); + int sx = (int)Math.round(tempVec.x); + int sy = (int)Math.round(tempVec.y); + int sz = (int)Math.round(tempVec.z); + long packedSource = BlockUtil.pack(sx + this.anchorX, sy + this.anchorY, sz + this.anchorZ); + BlockSelection.BlockHolder block = this.blocks.get(packedSource); + if (block != null) { + RotationTuple blockRotation = RotationTuple.get(block.rotation()); + RotationTuple rotatedRotation = RotationTuple.of( + blockRotation.yaw().add(snappedYaw), blockRotation.pitch().add(snappedPitch), blockRotation.roll().add(snappedRoll) + ); + if (rotatedRotation == null) { + rotatedRotation = blockRotation; + } + + int rotatedFiller = block.filler(); + if (rotatedFiller != 0) { + int fillerX = FillerBlockUtil.unpackX(rotatedFiller); + int fillerY = FillerBlockUtil.unpackY(rotatedFiller); + int fillerZ = FillerBlockUtil.unpackZ(rotatedFiller); + tempVec.assign(fillerX, fillerY, fillerZ); + rotation.multiplyDirection(tempVec); + rotatedFiller = FillerBlockUtil.pack((int)Math.round(tempVec.x), (int)Math.round(tempVec.y), (int)Math.round(tempVec.z)); + } + + Holder holder = block.holder(); + selection.addBlock0( + dx + this.anchorX, + dy + this.anchorY, + dz + this.anchorZ, + block.blockId(), + rotatedRotation.index(), + rotatedFiller, + block.supportValue(), + holder != null ? holder.clone() : null + ); + } + } + } + } + + for (int dx = destMinX; dx <= destMaxX; dx++) { + for (int dy = destMinY; dy <= destMaxY; dy++) { + for (int dzx = destMinZ; dzx <= destMaxZ; dzx++) { + tempVec.assign(dx, dy, dzx); + inverse.multiplyDirection(tempVec); + int sx = (int)Math.round(tempVec.x); + int sy = (int)Math.round(tempVec.y); + int sz = (int)Math.round(tempVec.z); + long packedSource = BlockUtil.pack(sx + this.anchorX, sy + this.anchorY, sz + this.anchorZ); + BlockSelection.FluidHolder fluid = this.fluids.get(packedSource); + if (fluid != null) { + selection.addFluid0(dx + this.anchorX, dy + this.anchorY, dzx + this.anchorZ, fluid.fluidId(), fluid.fluidLevel()); + } + } + } + } + } finally { + this.blocksLock.readLock().unlock(); + } + + float var64 = (float)yawRad; + float var68 = (float)pitchRad; + float var71 = (float)rollRad; + this.forEachEntity(entityHolder -> { + Holder copy = entityHolder.clone(); + TransformComponent transformComponent = copy.getComponent(TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + HeadRotation headRotationComp = copy.getComponent(HeadRotation.getComponentType()); + position.subtract(this.anchorX, this.anchorY, this.anchorZ).subtract(0.5, 0.0, 0.5); + rotation.multiplyDirection(position); + position.add(this.anchorX, this.anchorY, this.anchorZ).add(0.5, 0.0, 0.5); + Vector3f bodyRotation = transformComponent.getRotation(); + bodyRotation.addPitch(var68); + bodyRotation.addYaw(var64); + bodyRotation.addRoll(var71); + if (headRotationComp != null) { + Vector3f headRot = headRotationComp.getRotation(); + headRot.addPitch(var68); + headRot.addYaw(var64); + headRot.addRoll(var71); + } + + selection.addEntity0(copy); + }); + return selection; + } + } + + @Nonnull + public BlockSelection flip(@Nonnull Axis axis) { + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + BlockSelection selection = new BlockSelection(this.getBlockCount(), this.getEntityCount()); + selection.copyPropertiesFrom(this); + Vector3i mutable = new Vector3i(0, 0, 0); + this.forEachBlock( + (x1, y1, z1, block) -> { + mutable.assign(x1 - this.anchorX, y1 - this.anchorY, z1 - this.anchorZ); + axis.flip(mutable); + int blockId = block.blockId; + Holder holder = block.holder; + int supportValue = block.supportValue(); + int filler = block.filler; + BlockType blockType = assetMap.getAsset(blockId); + VariantRotation variantRotation = blockType.getVariantRotation(); + if (variantRotation == VariantRotation.None) { + selection.addBlock0(mutable.getX() + this.anchorX, mutable.getY() + this.anchorY, mutable.getZ() + this.anchorZ, block); + } else { + RotationTuple blockRotation = RotationTuple.get(block.rotation); + RotationTuple rotatedRotation = BlockRotationUtil.getFlipped(blockRotation, blockType.getFlipType(), axis, variantRotation); + if (rotatedRotation != null) { + rotatedRotation = blockRotation; + } + + int rotatedFiller = BlockRotationUtil.getFlippedFiller(filler, axis); + selection.addBlock0( + mutable.getX() + this.anchorX, + mutable.getY() + this.anchorY, + mutable.getZ() + this.anchorZ, + blockId, + rotatedRotation.index(), + rotatedFiller, + supportValue, + holder != null ? holder.clone() : null + ); + } + } + ); + this.forEachEntity(entityHolder -> { + Holder copy = entityHolder.clone(); + HeadRotation headRotationComponent = copy.getComponent(HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3f headRotation = headRotationComponent.getRotation(); + TransformComponent transformComponent = copy.getComponent(TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + Vector3f bodyRotation = transformComponent.getRotation(); + position.subtract(this.anchorX, this.anchorY, this.anchorZ).subtract(0.5, 0.0, 0.5); + axis.flip(position); + position.add(this.anchorX, this.anchorY, this.anchorZ).add(0.5, 0.0, 0.5); + axis.flipRotation(bodyRotation); + axis.flipRotation(headRotation); + selection.addEntity0(copy); + }); + return selection; + } + + @Nonnull + public BlockSelection relativize() { + return this.relativize(this.anchorX, this.anchorY, this.anchorZ); + } + + @Nonnull + public BlockSelection relativize(int originX, int originY, int originZ) { + if (originX == 0 && originY == 0 && originZ == 0) { + return this.cloneSelection(); + } else { + BlockSelection selection = new BlockSelection(this.getBlockCount(), this.getEntityCount()); + selection.setAnchor(this.anchorX - originX, this.anchorY - originY, this.anchorZ - originZ); + selection.setPosition(this.x - originX, this.y - originY, this.z - originZ); + selection.setSelectionArea(this.min.clone().subtract(originX, originY, originZ), this.max.clone().subtract(originX, originY, originZ)); + this.forEachBlock((x, y, z, block) -> selection.addBlock0(x - originX, y - originY, z - originZ, block)); + this.forEachEntity(holder -> { + Holder copy = holder.clone(); + TransformComponent transformComponent = copy.getComponent(TransformComponent.getComponentType()); + + assert transformComponent != null; + + transformComponent.getPosition().subtract(originX, originY, originZ); + selection.addEntity0(copy); + }); + return selection; + } + } + + @Nonnull + public BlockSelection cloneSelection() { + BlockSelection selection = new BlockSelection(this.getBlockCount(), this.getEntityCount()); + selection.copyPropertiesFrom(this); + this.blocksLock.readLock().lock(); + + try { + Long2ObjectMaps.fastForEach(this.blocks, entry -> selection.blocks.put(entry.getLongKey(), entry.getValue().cloneBlockHolder())); + selection.fluids.putAll(this.fluids); + } finally { + this.blocksLock.readLock().unlock(); + } + + this.entitiesLock.readLock().lock(); + + try { + this.entities.forEach(holder -> selection.entities.add(holder.clone())); + } finally { + this.entitiesLock.readLock().unlock(); + } + + return selection; + } + + public void add(@Nonnull BlockSelection other) { + this.entitiesLock.writeLock().lock(); + + try { + other.forEachEntity(holder -> { + Holder copy = holder.clone(); + TransformComponent transformComponent = copy.getComponent(TransformComponent.getComponentType()); + + assert transformComponent != null; + + transformComponent.getPosition().add(other.x, other.y, other.z).subtract(this.x, this.y, this.z); + this.addEntity0(copy); + }); + } finally { + this.entitiesLock.writeLock().unlock(); + } + + this.blocksLock.writeLock().lock(); + + try { + other.forEachBlock((x1, y1, z1, block) -> this.addBlock0(x1 + other.x - this.x, y1 + other.y - this.y, z1 + other.z - this.z, block)); + other.forEachFluid( + (x1, y1, z1, fluidId, fluidLevel) -> this.addFluid0(x1 + other.x - this.x, y1 + other.y - this.y, z1 + other.z - this.z, fluidId, fluidLevel) + ); + } finally { + this.blocksLock.writeLock().unlock(); + } + } + + @Nonnull + @Override + public MetricResults toMetricResults() { + return METRICS_REGISTRY.toMetricResults(this); + } + + @Nonnull + public EditorBlocksChange toPacket() { + EditorBlocksChange packet = new EditorBlocksChange(); + this.blocksLock.readLock().lock(); + + try { + int blockCount = this.getBlockCount(); + List blockList = new ObjectArrayList<>(blockCount); + this.forEachBlock((x1, y1, z1, block) -> { + if (block.filler == 0) { + blockList.add(new BlockChange(x1 - this.anchorX, y1 - this.anchorY, z1 - this.anchorZ, block.blockId, (byte)block.rotation)); + } + }); + List fluidList = new ObjectArrayList<>(); + this.forEachFluid((x1, y1, z1, fluidId, fluidLevel) -> { + if (fluidId != 0) { + fluidList.add(new FluidChange(x1 - this.anchorX, y1 - this.anchorY, z1 - this.anchorZ, fluidId, fluidLevel)); + } + }); + packet.blocksChange = blockList.toArray(BlockChange[]::new); + packet.fluidsChange = fluidList.toArray(FluidChange[]::new); + packet.advancedPreview = true; + packet.blocksCount = blockCount; + } finally { + this.blocksLock.readLock().unlock(); + } + + return packet; + } + + @Nonnull + public EditorBlocksChange toSelectionPacket() { + EditorBlocksChange packet = new EditorBlocksChange(); + EditorSelection selection = new EditorSelection(); + if (this.min != null) { + selection.minX = this.min.getX(); + selection.minY = this.min.getY(); + selection.minZ = this.min.getZ(); + } + + if (this.max != null) { + selection.maxX = this.max.getX(); + selection.maxY = this.max.getY(); + selection.maxZ = this.max.getZ(); + } + + packet.selection = selection; + return packet; + } + + @Nonnull + public EditorBlocksChange toPacketWithSelection() { + EditorBlocksChange packet = this.toPacket(); + if (this.min != null && this.max != null) { + EditorSelection selection = new EditorSelection(); + selection.minX = this.min.getX(); + selection.minY = this.min.getY(); + selection.minZ = this.min.getZ(); + selection.maxX = this.max.getX(); + selection.maxY = this.max.getY(); + selection.maxZ = this.max.getZ(); + packet.selection = selection; + } + + return packet; + } + + public void tryFixFiller(boolean allowDestructive) { + this.blocksLock.readLock().lock(); + + LongOpenHashSet blockPositions; + try { + blockPositions = new LongOpenHashSet(this.blocks.keySet()); + } finally { + this.blocksLock.readLock().unlock(); + } + + BlockTypeAssetMap blockTypeAssetMap = BlockType.getAssetMap(); + IndexedLookupTableAssetMap hitboxAssetMap = BlockBoundingBoxes.getAssetMap(); + LongIterator it = blockPositions.iterator(); + + while (it.hasNext()) { + long packed = it.nextLong(); + int x = BlockUtil.unpackX(packed); + int y = BlockUtil.unpackY(packed); + int z = BlockUtil.unpackZ(packed); + BlockSelection.BlockHolder blockHolder = this.getBlockHolderAtLocalPos(x, y, z); + if (blockHolder != null) { + int blockId = blockHolder.blockId; + if (blockId != 0) { + BlockType blockType = (BlockType)blockTypeAssetMap.getAsset(blockId); + if (blockType != null) { + String id = blockType.getId(); + if (blockHolder.filler != 0) { + int fillerX = FillerBlockUtil.unpackX(blockHolder.filler); + int fillerY = FillerBlockUtil.unpackY(blockHolder.filler); + int fillerZ = FillerBlockUtil.unpackZ(blockHolder.filler); + BlockSelection.BlockHolder baseBlockHolder = this.getBlockHolderAtLocalPos(x - fillerX, y - fillerY, z - fillerZ); + BlockType baseBlock = (BlockType)blockTypeAssetMap.getAsset(baseBlockHolder.blockId); + if (baseBlock == null) { + this.addBlockAtLocalPos(x, y, z, 0, 0, 0, 0); + } else { + String baseId = baseBlock.getId(); + BlockBoundingBoxes hitbox = (BlockBoundingBoxes)hitboxAssetMap.getAsset(baseBlock.getHitboxTypeIndex()); + if (hitbox != null + && ( + !id.equals(baseId) + || baseBlockHolder.rotation != blockHolder.rotation + || !hitbox.get(blockHolder.rotation).getBoundingBox().containsBlock(fillerX, fillerY, fillerZ) + )) { + this.addBlockAtLocalPos(x, y, z, 0, 0, 0, 0); + } + } + } else { + BlockBoundingBoxes hitbox = (BlockBoundingBoxes)hitboxAssetMap.getAsset(blockType.getHitboxTypeIndex()); + if (hitbox != null && hitbox.protrudesUnitBox()) { + FillerBlockUtil.forEachFillerBlock( + hitbox.get(blockHolder.rotation), + (x1, y1, z1) -> { + if (x1 != 0 || y1 != 0 || z1 != 0) { + int worldX = x + x1; + int worldY = y + y1; + int worldZ = z + z1; + BlockSelection.BlockHolder fillerBlockHolder = this.getBlockHolderAtLocalPos(worldX, worldY, worldZ); + BlockType fillerBlock = (BlockType)blockTypeAssetMap.getAsset(fillerBlockHolder.blockId); + int filler = FillerBlockUtil.pack(x1, y1, z1); + if (fillerBlock == null || !fillerBlock.getId().equals(id) || filler != fillerBlockHolder.filler) { + if (!allowDestructive && fillerBlockHolder.blockId != 0) { + throw new IllegalArgumentException( + "Cannot replace " + + fillerBlock.getId() + + " with " + + blockType.getId() + + " in order to repair filler\n at " + + worldX + + ", " + + worldY + + ", " + + worldZ + + "\n base " + + x + + ", " + + y + + ", " + + z + ); + } + + this.addBlockAtLocalPos(worldX, worldY, worldZ, blockId, blockHolder.rotation, filler, 0); + } + } + } + ); + } + } + } + } + } + } + } + + public void reserializeEntities(@Nonnull Store store, boolean destructive) throws IOException { + this.entitiesLock.writeLock().lock(); + + try { + if (this.entities.isEmpty()) { + return; + } + + ComponentRegistry registry = EntityStore.REGISTRY; + ComponentRegistry.Data data = registry.getData(); + SystemType systemType = EntityModule.get().getMigrationSystemType(); + BitSet systemIndexes = data.getSystemIndexesForType(systemType); + int systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + EntityModule.MigrationSystem system = data.getSystem(systemIndex, systemType); + + for (int i = 0; i < this.entities.size(); i++) { + Holder holder = this.entities.get(i); + if (system.test(registry, holder.getArchetype())) { + system.onEntityAdd(holder, AddReason.LOAD, store); + } + } + } + + systemIndex = -1; + + while ((systemIndex = systemIndexes.nextSetBit(systemIndex + 1)) >= 0) { + EntityModule.MigrationSystem system = data.getSystem(systemIndex, systemType); + + for (int ix = 0; ix < this.entities.size(); ix++) { + Holder holder = this.entities.get(ix); + if (system.test(registry, holder.getArchetype())) { + system.onEntityRemoved(holder, RemoveReason.UNLOAD, store); + } + } + } + + if (destructive) { + for (int ixx = 0; ixx < this.entities.size(); ixx++) { + Holder holder = this.entities.get(ixx); + holder.tryRemoveComponent(registry.getUnknownComponentType()); + } + } + } finally { + this.entitiesLock.writeLock().unlock(); + } + } + + @Nonnull + @Override + public String toString() { + return "BlockSelection{blocksLock=" + + this.blocksLock + + ", x=" + + this.x + + ", y=" + + this.y + + ", z=" + + this.z + + ", originX=" + + this.anchorX + + ", originY=" + + this.anchorY + + ", originZ=" + + this.anchorZ + + ", min=" + + this.min + + ", max=" + + this.max + + "}"; + } + + @FunctionalInterface + public interface BlockComparingIterator { + boolean test(int var1, int var2, int var3, BlockSelection.BlockHolder var4); + } + + public record BlockHolder(int blockId, int rotation, int filler, int supportValue, Holder holder) { + @Nonnull + public BlockSelection.BlockHolder cloneBlockHolder() { + return this.holder == null ? this : new BlockSelection.BlockHolder(this.blockId, this.rotation, this.filler, this.supportValue, this.holder.clone()); + } + } + + @FunctionalInterface + public interface BlockIterator { + void accept(int var1, int var2, int var3, BlockSelection.BlockHolder var4); + } + + public static enum FallbackMode { + PASS_THOUGH, + COPY; + + private FallbackMode() { + } + } + + public record FluidHolder(int fluidId, byte fluidLevel) { + } + + @FunctionalInterface + public interface FluidIterator { + void accept(int var1, int var2, int var3, int var4, byte var5); + } +} diff --git a/src/com/hypixel/hytale/server/core/prefab/selection/standard/FeedbackConsumer.java b/src/com/hypixel/hytale/server/core/prefab/selection/standard/FeedbackConsumer.java new file mode 100644 index 0000000..6d27324 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/prefab/selection/standard/FeedbackConsumer.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.server.core.prefab.selection.standard; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +@FunctionalInterface +public interface FeedbackConsumer { + FeedbackConsumer DEFAULT = (key, total, count, target, componentAccessor) -> {}; + + void accept(@Nonnull String var1, int var2, int var3, @Nonnull CommandSender var4, @Nonnull ComponentAccessor var5); +} diff --git a/src/com/hypixel/hytale/server/core/receiver/IEventTitleReceiver.java b/src/com/hypixel/hytale/server/core/receiver/IEventTitleReceiver.java new file mode 100644 index 0000000..35b6b00 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/receiver/IEventTitleReceiver.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.core.receiver; + +import com.hypixel.hytale.server.core.Message; + +public interface IEventTitleReceiver { + float DEFAULT_DURATION = 4.0F; + float DEFAULT_FADE_DURATION = 1.5F; + + default void showEventTitle(Message primaryTitle, Message secondaryTitle, boolean isMajor, String icon) { + this.showEventTitle(primaryTitle, secondaryTitle, isMajor, icon, 4.0F); + } + + default void showEventTitle(Message primaryTitle, Message secondaryTitle, boolean isMajor, String icon, float duration) { + this.showEventTitle(primaryTitle, secondaryTitle, isMajor, icon, duration, 1.5F, 1.5F); + } + + void showEventTitle(Message var1, Message var2, boolean var3, String var4, float var5, float var6, float var7); + + default void hideEventTitle() { + this.hideEventTitle(1.5F); + } + + void hideEventTitle(float var1); +} diff --git a/src/com/hypixel/hytale/server/core/receiver/IMessageReceiver.java b/src/com/hypixel/hytale/server/core/receiver/IMessageReceiver.java new file mode 100644 index 0000000..a0c694c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/receiver/IMessageReceiver.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.server.core.receiver; + +import com.hypixel.hytale.server.core.Message; +import javax.annotation.Nonnull; + +public interface IMessageReceiver { + void sendMessage(@Nonnull Message var1); +} diff --git a/src/com/hypixel/hytale/server/core/receiver/IPacketReceiver.java b/src/com/hypixel/hytale/server/core/receiver/IPacketReceiver.java new file mode 100644 index 0000000..38171f1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/receiver/IPacketReceiver.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.core.receiver; + +import com.hypixel.hytale.protocol.Packet; +import javax.annotation.Nonnull; + +public interface IPacketReceiver { + void write(@Nonnull Packet var1); + + void writeNoCache(@Nonnull Packet var1); +} diff --git a/src/com/hypixel/hytale/server/core/registry/ClientFeatureRegistration.java b/src/com/hypixel/hytale/server/core/registry/ClientFeatureRegistration.java new file mode 100644 index 0000000..25ae7c3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/registry/ClientFeatureRegistration.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.core.registry; + +import com.hypixel.hytale.protocol.packets.setup.ClientFeature; +import com.hypixel.hytale.registry.Registration; +import com.hypixel.hytale.server.core.client.ClientFeatureHandler; +import java.util.function.BooleanSupplier; +import javax.annotation.Nonnull; + +public class ClientFeatureRegistration extends Registration { + private final ClientFeature feature; + + public ClientFeatureRegistration(@Nonnull ClientFeatureRegistration registration, BooleanSupplier isEnabled, Runnable unregister) { + this(registration.feature, isEnabled, unregister); + } + + public ClientFeatureRegistration(ClientFeature feature) { + super(() -> true, () -> ClientFeatureHandler.unregister(feature)); + this.feature = feature; + } + + public ClientFeatureRegistration(ClientFeature feature, BooleanSupplier isEnabled, Runnable unregister) { + super(isEnabled, unregister); + this.feature = feature; + } + + public ClientFeature getFeature() { + return this.feature; + } +} diff --git a/src/com/hypixel/hytale/server/core/registry/ClientFeatureRegistry.java b/src/com/hypixel/hytale/server/core/registry/ClientFeatureRegistry.java new file mode 100644 index 0000000..154f4c7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/registry/ClientFeatureRegistry.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.registry; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.function.consumer.BooleanConsumer; +import com.hypixel.hytale.protocol.packets.setup.ClientFeature; +import com.hypixel.hytale.protocol.packets.setup.ServerTags; +import com.hypixel.hytale.registry.Registry; +import com.hypixel.hytale.server.core.client.ClientFeatureHandler; +import com.hypixel.hytale.server.core.plugin.PluginBase; +import com.hypixel.hytale.server.core.universe.Universe; +import java.util.List; +import java.util.function.BooleanSupplier; +import javax.annotation.Nonnull; + +public class ClientFeatureRegistry extends Registry { + public ClientFeatureRegistry(@Nonnull List registrations, BooleanSupplier precondition, String preconditionMessage, PluginBase plugin) { + super(registrations, precondition, preconditionMessage, ClientFeatureRegistration::new); + } + + public ClientFeatureRegistration register(ClientFeature feature) { + ClientFeatureHandler.register(feature); + return super.register(new ClientFeatureRegistration(feature)); + } + + public void registerClientTag(@Nonnull String tag) { + if (AssetRegistry.registerClientTag(tag)) { + ServerTags packet = new ServerTags(AssetRegistry.getClientTags()); + Universe.get().broadcastPacketNoCache(packet); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/task/TaskRegistration.java b/src/com/hypixel/hytale/server/core/task/TaskRegistration.java new file mode 100644 index 0000000..38e3cd3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/task/TaskRegistration.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.core.task; + +import com.hypixel.hytale.registry.Registration; +import java.util.concurrent.Future; +import java.util.function.BooleanSupplier; +import javax.annotation.Nonnull; + +public class TaskRegistration extends Registration { + private final Future task; + + public TaskRegistration(@Nonnull Future task) { + super(() -> true, () -> task.cancel(false)); + this.task = task; + } + + public TaskRegistration(@Nonnull TaskRegistration registration, BooleanSupplier isEnabled, Runnable unregister) { + super(isEnabled, unregister); + this.task = registration.task; + } + + public Future getTask() { + return this.task; + } + + @Nonnull + @Override + public String toString() { + return "TaskRegistration{task=" + this.task + ", " + super.toString() + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/task/TaskRegistry.java b/src/com/hypixel/hytale/server/core/task/TaskRegistry.java new file mode 100644 index 0000000..72a643a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/task/TaskRegistry.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.core.task; + +import com.hypixel.hytale.function.consumer.BooleanConsumer; +import com.hypixel.hytale.registry.Registry; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledFuture; +import java.util.function.BooleanSupplier; +import javax.annotation.Nonnull; + +public class TaskRegistry extends Registry { + public TaskRegistry(@Nonnull List registrations, BooleanSupplier precondition, String preconditionMessage) { + super(registrations, precondition, preconditionMessage, TaskRegistration::new); + } + + public TaskRegistration registerTask(@Nonnull CompletableFuture task) { + return this.register(new TaskRegistration(task)); + } + + public TaskRegistration registerTask(@Nonnull ScheduledFuture task) { + return this.register(new TaskRegistration(task)); + } +} diff --git a/src/com/hypixel/hytale/server/core/ui/Anchor.java b/src/com/hypixel/hytale/server/core/ui/Anchor.java new file mode 100644 index 0000000..6a3d62a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/ui/Anchor.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.server.core.ui; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; + +public class Anchor { + public static final BuilderCodec CODEC = BuilderCodec.builder(Anchor.class, Anchor::new) + .addField(new KeyedCodec<>("Left", ValueCodec.INTEGER), (p, t) -> p.left = t, p -> p.left) + .addField(new KeyedCodec<>("Right", ValueCodec.INTEGER), (p, t) -> p.right = t, p -> p.right) + .addField(new KeyedCodec<>("Top", ValueCodec.INTEGER), (p, t) -> p.top = t, p -> p.top) + .addField(new KeyedCodec<>("Bottom", ValueCodec.INTEGER), (p, t) -> p.bottom = t, p -> p.bottom) + .addField(new KeyedCodec<>("Height", ValueCodec.INTEGER), (p, t) -> p.height = t, p -> p.height) + .addField(new KeyedCodec<>("Full", ValueCodec.INTEGER), (p, t) -> p.full = t, p -> p.full) + .addField(new KeyedCodec<>("Horizontal", ValueCodec.INTEGER), (p, t) -> p.horizontal = t, p -> p.horizontal) + .addField(new KeyedCodec<>("Vertical", ValueCodec.INTEGER), (p, t) -> p.vertical = t, p -> p.vertical) + .addField(new KeyedCodec<>("Width", ValueCodec.INTEGER), (p, t) -> p.width = t, p -> p.width) + .addField(new KeyedCodec<>("MinWidth", ValueCodec.INTEGER), (p, t) -> p.minWidth = t, p -> p.minWidth) + .addField(new KeyedCodec<>("MaxWidth", ValueCodec.INTEGER), (p, t) -> p.maxWidth = t, p -> p.maxWidth) + .build(); + private Value left; + private Value right; + private Value top; + private Value bottom; + private Value height; + private Value full; + private Value horizontal; + private Value vertical; + private Value width; + private Value minWidth; + private Value maxWidth; + + public Anchor() { + } + + public void setLeft(Value left) { + this.left = left; + } + + public void setRight(Value right) { + this.right = right; + } + + public void setTop(Value top) { + this.top = top; + } + + public void setBottom(Value bottom) { + this.bottom = bottom; + } + + public void setHeight(Value height) { + this.height = height; + } + + public void setFull(Value full) { + this.full = full; + } + + public void setHorizontal(Value horizontal) { + this.horizontal = horizontal; + } + + public void setVertical(Value vertical) { + this.vertical = vertical; + } + + public void setWidth(Value width) { + this.width = width; + } + + public void setMinWidth(Value minWidth) { + this.minWidth = minWidth; + } + + public void setMaxWidth(Value maxWidth) { + this.maxWidth = maxWidth; + } +} diff --git a/src/com/hypixel/hytale/server/core/ui/Area.java b/src/com/hypixel/hytale/server/core/ui/Area.java new file mode 100644 index 0000000..f2fa278 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/ui/Area.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.core.ui; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class Area { + public static final BuilderCodec CODEC = BuilderCodec.builder(Area.class, Area::new) + .addField(new KeyedCodec<>("X", Codec.INTEGER), (p, t) -> p.x = t, p -> p.x) + .addField(new KeyedCodec<>("Y", Codec.INTEGER), (p, t) -> p.y = t, p -> p.y) + .addField(new KeyedCodec<>("Width", Codec.INTEGER), (p, t) -> p.width = t, p -> p.width) + .addField(new KeyedCodec<>("Height", Codec.INTEGER), (p, t) -> p.height = t, p -> p.height) + .build(); + private int x; + private int y; + private int width; + private int height; + + public Area() { + } + + @Nonnull + public Area setX(int x) { + this.x = x; + return this; + } + + @Nonnull + public Area setY(int y) { + this.y = y; + return this; + } + + @Nonnull + public Area setWidth(int width) { + this.width = width; + return this; + } + + @Nonnull + public Area setHeight(int height) { + this.height = height; + return this; + } +} diff --git a/src/com/hypixel/hytale/server/core/ui/DropdownEntryInfo.java b/src/com/hypixel/hytale/server/core/ui/DropdownEntryInfo.java new file mode 100644 index 0000000..e3791a5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/ui/DropdownEntryInfo.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.server.core.ui; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; + +public class DropdownEntryInfo { + public static final BuilderCodec CODEC = BuilderCodec.builder(DropdownEntryInfo.class, DropdownEntryInfo::new) + .addField(new KeyedCodec<>("Label", LocalizableString.CODEC), (p, t) -> p.label = t, p -> p.label) + .addField(new KeyedCodec<>("Value", Codec.STRING), (p, t) -> p.value = t, p -> p.value) + .build(); + private LocalizableString label; + private String value; + + public DropdownEntryInfo(LocalizableString label, String value) { + this.label = label; + this.value = value; + } + + private DropdownEntryInfo() { + } +} diff --git a/src/com/hypixel/hytale/server/core/ui/ItemGridSlot.java b/src/com/hypixel/hytale/server/core/ui/ItemGridSlot.java new file mode 100644 index 0000000..59678e7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/ui/ItemGridSlot.java @@ -0,0 +1,105 @@ +package com.hypixel.hytale.server.core.ui; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import javax.annotation.Nonnull; + +public class ItemGridSlot { + public static final BuilderCodec CODEC = BuilderCodec.builder(ItemGridSlot.class, ItemGridSlot::new) + .addField(new KeyedCodec<>("ItemStack", ItemStack.CODEC), (p, t) -> p.itemStack = t, p -> p.itemStack) + .addField(new KeyedCodec<>("Background", ValueCodec.PATCH_STYLE), (p, t) -> p.background = t, p -> p.background) + .addField(new KeyedCodec<>("Overlay", ValueCodec.PATCH_STYLE), (p, t) -> p.overlay = t, p -> p.overlay) + .addField(new KeyedCodec<>("Icon", ValueCodec.PATCH_STYLE), (p, t) -> p.icon = t, p -> p.icon) + .addField(new KeyedCodec<>("IsItemIncompatible", Codec.BOOLEAN), (p, t) -> p.isItemIncompatible = t, p -> p.isItemIncompatible) + .addField(new KeyedCodec<>("Name", Codec.STRING), (p, t) -> p.name = t, p -> p.name) + .addField(new KeyedCodec<>("Description", Codec.STRING), (p, t) -> p.description = t, p -> p.description) + .addField(new KeyedCodec<>("SkipItemQualityBackground", Codec.BOOLEAN), (p, t) -> p.skipItemQualityBackground = t, p -> p.skipItemQualityBackground) + .addField(new KeyedCodec<>("IsActivatable", Codec.BOOLEAN), (p, t) -> p.isActivatable = t, p -> p.isActivatable) + .addField(new KeyedCodec<>("IsItemUncraftable", Codec.BOOLEAN), (p, t) -> p.isItemUncraftable = t, p -> p.isItemUncraftable) + .build(); + private ItemStack itemStack; + private Value background; + private Value overlay; + private Value icon; + private boolean isItemIncompatible; + private String name; + private String description; + private boolean skipItemQualityBackground; + private boolean isActivatable; + private boolean isItemUncraftable; + + public ItemGridSlot() { + } + + public ItemGridSlot(ItemStack itemStack) { + this.itemStack = itemStack; + } + + @Nonnull + public ItemGridSlot setItemStack(ItemStack itemStack) { + this.itemStack = itemStack; + return this; + } + + @Nonnull + public ItemGridSlot setBackground(Value background) { + this.background = background; + return this; + } + + @Nonnull + public ItemGridSlot setOverlay(Value overlay) { + this.overlay = overlay; + return this; + } + + @Nonnull + public ItemGridSlot setIcon(Value icon) { + this.icon = icon; + return this; + } + + @Nonnull + public ItemGridSlot setItemIncompatible(boolean itemIncompatible) { + this.isItemIncompatible = itemIncompatible; + return this; + } + + @Nonnull + public ItemGridSlot setName(String name) { + this.name = name; + return this; + } + + @Nonnull + public ItemGridSlot setDescription(String description) { + this.description = description; + return this; + } + + public boolean isItemUncraftable() { + return this.isItemUncraftable; + } + + public void setItemUncraftable(boolean itemUncraftable) { + this.isItemUncraftable = itemUncraftable; + } + + public boolean isActivatable() { + return this.isActivatable; + } + + public void setActivatable(boolean activatable) { + this.isActivatable = activatable; + } + + public boolean isSkipItemQualityBackground() { + return this.skipItemQualityBackground; + } + + public void setSkipItemQualityBackground(boolean skipItemQualityBackground) { + this.skipItemQualityBackground = skipItemQualityBackground; + } +} diff --git a/src/com/hypixel/hytale/server/core/ui/LocalizableString.java b/src/com/hypixel/hytale/server/core/ui/LocalizableString.java new file mode 100644 index 0000000..601ef46 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/ui/LocalizableString.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.core.ui; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import java.util.Map; +import javax.annotation.Nonnull; +import org.bson.BsonString; +import org.bson.BsonValue; + +public class LocalizableString { + public static final LocalizableString.LocalizableStringCodec CODEC = new LocalizableString.LocalizableStringCodec(); + public static final BuilderCodec MESSAGE_OBJECT_CODEC = BuilderCodec.builder(LocalizableString.class, LocalizableString::new) + .addField(new KeyedCodec<>("MessageId", Codec.STRING), (p, t) -> p.messageId = t, p -> p.messageId) + .addField(new KeyedCodec<>("MessageParams", MapCodec.STRING_HASH_MAP_CODEC), (p, t) -> p.messageParams = t, p -> p.messageParams) + .build(); + private String stringValue; + private String messageId; + private Map messageParams; + + public LocalizableString() { + } + + @Nonnull + public static LocalizableString fromString(String str) { + LocalizableString instance = new LocalizableString(); + instance.stringValue = str; + return instance; + } + + @Nonnull + public static LocalizableString fromMessageId(String messageId) { + return fromMessageId(messageId, null); + } + + @Nonnull + public static LocalizableString fromMessageId(String messageId, Map params) { + LocalizableString instance = new LocalizableString(); + instance.messageId = messageId; + instance.messageParams = params; + return instance; + } + + public static class LocalizableStringCodec implements Codec { + public LocalizableStringCodec() { + } + + public LocalizableString decode(BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { + return bsonValue instanceof BsonString + ? LocalizableString.fromString(bsonValue.asString().getValue()) + : LocalizableString.MESSAGE_OBJECT_CODEC.decode(bsonValue, extraInfo); + } + + @Nonnull + public BsonValue encode(@Nonnull LocalizableString t, @Nonnull ExtraInfo extraInfo) { + return (BsonValue)(t.stringValue != null ? new BsonString(t.stringValue) : LocalizableString.MESSAGE_OBJECT_CODEC.encode(t, extraInfo)); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return Schema.anyOf(new StringSchema(), LocalizableString.MESSAGE_OBJECT_CODEC.toSchema(context)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/ui/PatchStyle.java b/src/com/hypixel/hytale/server/core/ui/PatchStyle.java new file mode 100644 index 0000000..0021188 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/ui/PatchStyle.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.core.ui; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class PatchStyle { + public static final BuilderCodec CODEC = BuilderCodec.builder(PatchStyle.class, PatchStyle::new) + .addField(new KeyedCodec<>("TexturePath", ValueCodec.STRING), (p, t) -> p.texturePath = t, p -> p.texturePath) + .addField(new KeyedCodec<>("Border", ValueCodec.INTEGER), (p, t) -> p.border = t, p -> p.border) + .addField(new KeyedCodec<>("HorizonzalBorder", ValueCodec.INTEGER), (p, t) -> p.horizontalBorder = t, p -> p.horizontalBorder) + .addField(new KeyedCodec<>("VerticalBorder", ValueCodec.INTEGER), (p, t) -> p.verticalBorder = t, p -> p.verticalBorder) + .addField(new KeyedCodec<>("Color", ValueCodec.STRING), (p, t) -> p.color = t, p -> p.color) + .addField(new KeyedCodec<>("Area", new ValueCodec<>(Area.CODEC)), (p, t) -> p.area = t, p -> p.area) + .build(); + private Value texturePath; + private Value border; + private Value horizontalBorder; + private Value verticalBorder; + private Value color; + private Value area; + + public PatchStyle() { + } + + public PatchStyle(Value texturePath) { + this.texturePath = texturePath; + } + + public PatchStyle(Value texturePath, Value border) { + this.texturePath = texturePath; + this.border = border; + } + + @Nonnull + public PatchStyle setTexturePath(Value texturePath) { + this.texturePath = texturePath; + return this; + } + + @Nonnull + public PatchStyle setBorder(Value border) { + this.border = border; + return this; + } + + @Nonnull + public PatchStyle setHorizontalBorder(Value horizontalBorder) { + this.horizontalBorder = horizontalBorder; + return this; + } + + @Nonnull + public PatchStyle setVerticalBorder(Value verticalBorder) { + this.verticalBorder = verticalBorder; + return this; + } + + @Nonnull + public PatchStyle setColor(Value color) { + this.color = color; + return this; + } + + @Nonnull + public PatchStyle setArea(Value area) { + this.area = area; + return this; + } +} diff --git a/src/com/hypixel/hytale/server/core/ui/Value.java b/src/com/hypixel/hytale/server/core/ui/Value.java new file mode 100644 index 0000000..dbc4186 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/ui/Value.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.ui; + +import javax.annotation.Nonnull; + +public class Value { + private T value; + private String documentPath; + private String valueName; + + private Value(String documentPath, String valueName) { + this.documentPath = documentPath; + this.valueName = valueName; + } + + private Value(T value) { + this.value = value; + } + + public T getValue() { + return this.value; + } + + public String getDocumentPath() { + return this.documentPath; + } + + public String getValueName() { + return this.valueName; + } + + @Nonnull + public static Value ref(String document, String value) { + return new Value<>(document, value); + } + + @Nonnull + public static Value of(T value) { + return new Value<>(value); + } +} diff --git a/src/com/hypixel/hytale/server/core/ui/ValueCodec.java b/src/com/hypixel/hytale/server/core/ui/ValueCodec.java new file mode 100644 index 0000000..d3477ed --- /dev/null +++ b/src/com/hypixel/hytale/server/core/ui/ValueCodec.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.server.core.ui; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.Schema; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.BsonValue; + +public class ValueCodec implements Codec> { + public static final ValueCodec REFERENCE_ONLY = new ValueCodec<>(null); + public static final ValueCodec STRING = new ValueCodec<>(Codec.STRING); + public static final ValueCodec LOCALIZABLE_STRING = new ValueCodec<>(LocalizableString.CODEC); + public static final ValueCodec INTEGER = new ValueCodec<>(Codec.INTEGER); + public static final ValueCodec PATCH_STYLE = new ValueCodec<>(PatchStyle.CODEC); + protected Codec codec; + + ValueCodec(Codec codec) { + this.codec = codec; + } + + public Value decode(BsonValue bsonValue, ExtraInfo extraInfo) { + throw new UnsupportedOperationException(); + } + + public BsonValue encode(@Nonnull Value r, ExtraInfo extraInfo) { + return (BsonValue)(r.getValue() != null + ? this.codec.encode(r.getValue(), extraInfo) + : new BsonDocument().append("$Document", new BsonString(r.getDocumentPath())).append("@Value", new BsonString(r.getValueName()))); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return this.codec.toSchema(context); + } +} diff --git a/src/com/hypixel/hytale/server/core/ui/browser/FileBrowserConfig.java b/src/com/hypixel/hytale/server/core/ui/browser/FileBrowserConfig.java new file mode 100644 index 0000000..f92b5e6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/ui/browser/FileBrowserConfig.java @@ -0,0 +1,133 @@ +package com.hypixel.hytale.server.core.ui.browser; + +import com.hypixel.hytale.server.core.ui.LocalizableString; +import java.nio.file.Path; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public record FileBrowserConfig( + @Nonnull String listElementId, + @Nullable String rootSelectorId, + @Nullable String searchInputId, + @Nullable String currentPathId, + @Nonnull List roots, + @Nonnull Set allowedExtensions, + boolean enableRootSelector, + boolean enableSearch, + boolean enableDirectoryNav, + boolean enableMultiSelect, + int maxResults, + @Nullable FileListProvider customProvider +) { + public static FileBrowserConfig.Builder builder() { + return new FileBrowserConfig.Builder(); + } + + public static class Builder { + private String listElementId = "#FileList"; + private String rootSelectorId = "#RootSelector"; + private String searchInputId = "#SearchInput"; + private String currentPathId = null; + private List roots = List.of(); + private Set allowedExtensions = Set.of(); + private boolean enableRootSelector = true; + private boolean enableSearch = true; + private boolean enableDirectoryNav = true; + private boolean enableMultiSelect = false; + private int maxResults = 50; + private FileListProvider customProvider = null; + + public Builder() { + } + + public FileBrowserConfig.Builder listElementId(@Nonnull String listElementId) { + this.listElementId = listElementId; + return this; + } + + public FileBrowserConfig.Builder rootSelectorId(@Nullable String rootSelectorId) { + this.rootSelectorId = rootSelectorId; + return this; + } + + public FileBrowserConfig.Builder searchInputId(@Nullable String searchInputId) { + this.searchInputId = searchInputId; + return this; + } + + public FileBrowserConfig.Builder currentPathId(@Nullable String currentPathId) { + this.currentPathId = currentPathId; + return this; + } + + public FileBrowserConfig.Builder roots(@Nonnull List roots) { + this.roots = roots; + return this; + } + + public FileBrowserConfig.Builder allowedExtensions(@Nonnull String... extensions) { + this.allowedExtensions = Set.of(extensions); + return this; + } + + public FileBrowserConfig.Builder allowedExtensions(@Nonnull Set extensions) { + this.allowedExtensions = extensions; + return this; + } + + public FileBrowserConfig.Builder enableRootSelector(boolean enable) { + this.enableRootSelector = enable; + return this; + } + + public FileBrowserConfig.Builder enableSearch(boolean enable) { + this.enableSearch = enable; + return this; + } + + public FileBrowserConfig.Builder enableDirectoryNav(boolean enable) { + this.enableDirectoryNav = enable; + return this; + } + + public FileBrowserConfig.Builder enableMultiSelect(boolean enable) { + this.enableMultiSelect = enable; + return this; + } + + public FileBrowserConfig.Builder maxResults(int maxResults) { + this.maxResults = maxResults; + return this; + } + + public FileBrowserConfig.Builder customProvider(@Nullable FileListProvider provider) { + this.customProvider = provider; + return this; + } + + public FileBrowserConfig build() { + return new FileBrowserConfig( + this.listElementId, + this.rootSelectorId, + this.searchInputId, + this.currentPathId, + this.roots, + this.allowedExtensions, + this.enableRootSelector, + this.enableSearch, + this.enableDirectoryNav, + this.enableMultiSelect, + this.maxResults, + this.customProvider + ); + } + } + + public record RootEntry(@Nonnull LocalizableString displayName, @Nonnull Path path) { + public RootEntry(@Nonnull String displayName, @Nonnull Path path) { + this(LocalizableString.fromString(displayName), path); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/ui/browser/FileBrowserEventData.java b/src/com/hypixel/hytale/server/core/ui/browser/FileBrowserEventData.java new file mode 100644 index 0000000..49cd7bf --- /dev/null +++ b/src/com/hypixel/hytale/server/core/ui/browser/FileBrowserEventData.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.core.ui.browser; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nullable; + +public class FileBrowserEventData { + public static final String KEY_FILE = "File"; + public static final String KEY_ROOT = "@Root"; + public static final String KEY_SEARCH_QUERY = "@SearchQuery"; + public static final String KEY_SEARCH_RESULT = "SearchResult"; + public static final String KEY_BROWSE = "Browse"; + public static final BuilderCodec CODEC = BuilderCodec.builder(FileBrowserEventData.class, FileBrowserEventData::new) + .addField(new KeyedCodec<>("File", Codec.STRING), (entry, s) -> entry.file = s, entry -> entry.file) + .addField(new KeyedCodec<>("@Root", Codec.STRING), (entry, s) -> entry.root = s, entry -> entry.root) + .addField(new KeyedCodec<>("@SearchQuery", Codec.STRING), (entry, s) -> entry.searchQuery = s, entry -> entry.searchQuery) + .addField(new KeyedCodec<>("SearchResult", Codec.STRING), (entry, s) -> entry.searchResult = s, entry -> entry.searchResult) + .addField( + new KeyedCodec<>("Browse", Codec.STRING), + (entry, s) -> entry.browse = "true".equalsIgnoreCase(s), + entry -> entry.browse != null && entry.browse ? "true" : null + ) + .build(); + @Nullable + private String file; + @Nullable + private String root; + @Nullable + private String searchQuery; + @Nullable + private String searchResult; + @Nullable + private Boolean browse; + + public FileBrowserEventData() { + } + + @Nullable + public String getFile() { + return this.file; + } + + @Nullable + public String getRoot() { + return this.root; + } + + @Nullable + public String getSearchQuery() { + return this.searchQuery; + } + + @Nullable + public String getSearchResult() { + return this.searchResult; + } + + public boolean isBrowseRequested() { + return this.browse != null && this.browse; + } + + public static FileBrowserEventData file(String file) { + FileBrowserEventData data = new FileBrowserEventData(); + data.file = file; + return data; + } +} diff --git a/src/com/hypixel/hytale/server/core/ui/browser/FileListProvider.java b/src/com/hypixel/hytale/server/core/ui/browser/FileListProvider.java new file mode 100644 index 0000000..619cf92 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/ui/browser/FileListProvider.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.server.core.ui.browser; + +import java.nio.file.Path; +import java.util.List; +import javax.annotation.Nonnull; + +@FunctionalInterface +public interface FileListProvider { + @Nonnull + List getFiles(@Nonnull Path var1, @Nonnull String var2); + + public record FileEntry(@Nonnull String name, @Nonnull String displayName, boolean isDirectory, int matchScore) { + public FileEntry(@Nonnull String name, boolean isDirectory) { + this(name, name, isDirectory, 0); + } + + public FileEntry(@Nonnull String name, @Nonnull String displayName, boolean isDirectory) { + this(name, displayName, isDirectory, 0); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/ui/browser/ServerFileBrowser.java b/src/com/hypixel/hytale/server/core/ui/browser/ServerFileBrowser.java new file mode 100644 index 0000000..2ce017f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/ui/browser/ServerFileBrowser.java @@ -0,0 +1,379 @@ +package com.hypixel.hytale.server.core.ui.browser; + +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.common.util.StringCompareUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.server.core.ui.DropdownEntryInfo; +import com.hypixel.hytale.server.core.ui.Value; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ServerFileBrowser { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final Value BUTTON_HIGHLIGHTED = Value.ref("Pages/BasicTextButton.ui", "SelectedLabelStyle"); + @Nonnull + private final FileBrowserConfig config; + @Nonnull + private Path root; + @Nonnull + private Path currentDir; + @Nonnull + private String searchQuery; + @Nonnull + private final Set selectedItems; + + public ServerFileBrowser(@Nonnull FileBrowserConfig config) { + this.config = config; + this.selectedItems = new LinkedHashSet<>(); + this.searchQuery = ""; + if (!config.roots().isEmpty()) { + this.root = config.roots().get(0).path(); + } else { + this.root = Paths.get(""); + } + + this.currentDir = this.root.getFileSystem().getPath(""); + } + + public ServerFileBrowser(@Nonnull FileBrowserConfig config, @Nullable Path initialRoot, @Nullable Path initialDir) { + this(config); + if (initialRoot != null && Files.isDirectory(initialRoot)) { + this.root = initialRoot; + this.currentDir = this.root.getFileSystem().getPath(""); + } + + if (initialDir != null) { + this.currentDir = initialDir; + } + } + + public void buildRootSelector(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) { + if (this.config.enableRootSelector() && this.config.rootSelectorId() != null) { + ObjectArrayList entries = new ObjectArrayList<>(); + + for (FileBrowserConfig.RootEntry rootEntry : this.config.roots()) { + entries.add(new DropdownEntryInfo(rootEntry.displayName(), rootEntry.path().toString())); + } + + commandBuilder.set(this.config.rootSelectorId() + ".Entries", entries); + commandBuilder.set(this.config.rootSelectorId() + ".Value", this.root.toString()); + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, + this.config.rootSelectorId(), + new EventData().append("@Root", this.config.rootSelectorId() + ".Value"), + false + ); + } else { + if (this.config.rootSelectorId() != null) { + commandBuilder.set(this.config.rootSelectorId() + ".Visible", false); + } + } + } + + public void buildSearchInput(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) { + if (this.config.enableSearch() && this.config.searchInputId() != null) { + if (!this.searchQuery.isEmpty()) { + commandBuilder.set(this.config.searchInputId() + ".Value", this.searchQuery); + } + + eventBuilder.addEventBinding( + CustomUIEventBindingType.ValueChanged, this.config.searchInputId(), EventData.of("@SearchQuery", this.config.searchInputId() + ".Value"), false + ); + } + } + + public void buildCurrentPath(@Nonnull UICommandBuilder commandBuilder) { + if (this.config.currentPathId() != null) { + String rootDisplay = this.root.toString().replace("\\", "/"); + String relativeDisplay = this.currentDir.toString().isEmpty() ? "" : "/" + this.currentDir.toString().replace("\\", "/"); + String displayPath = rootDisplay + relativeDisplay; + commandBuilder.set(this.config.currentPathId() + ".Text", displayPath); + } + } + + public void buildFileList(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) { + commandBuilder.clear(this.config.listElementId()); + List entries; + if (this.config.customProvider() != null) { + entries = this.config.customProvider().getFiles(this.currentDir, this.searchQuery); + } else if (!this.searchQuery.isEmpty() && this.config.enableSearch()) { + entries = this.buildSearchResults(); + } else { + entries = this.buildDirectoryListing(); + } + + int buttonIndex = 0; + if (this.config.enableDirectoryNav() && !this.currentDir.toString().isEmpty() && this.searchQuery.isEmpty()) { + commandBuilder.append(this.config.listElementId(), "Pages/BasicTextButton.ui"); + commandBuilder.set(this.config.listElementId() + "[0].Text", "../"); + eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, this.config.listElementId() + "[0]", EventData.of("File", "..")); + buttonIndex++; + } + + for (FileListProvider.FileEntry entry : entries) { + String displayText = entry.isDirectory() ? entry.displayName() + "/" : entry.displayName(); + commandBuilder.append(this.config.listElementId(), "Pages/BasicTextButton.ui"); + commandBuilder.set(this.config.listElementId() + "[" + buttonIndex + "].Text", displayText); + if (!entry.isDirectory()) { + commandBuilder.set(this.config.listElementId() + "[" + buttonIndex + "].Style", BUTTON_HIGHLIGHTED); + } + + String eventKey = !this.searchQuery.isEmpty() && !entry.isDirectory() ? "SearchResult" : "File"; + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, this.config.listElementId() + "[" + buttonIndex + "]", EventData.of(eventKey, entry.name()) + ); + buttonIndex++; + } + } + + public void buildUI(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) { + this.buildRootSelector(commandBuilder, eventBuilder); + this.buildSearchInput(commandBuilder, eventBuilder); + this.buildCurrentPath(commandBuilder); + this.buildFileList(commandBuilder, eventBuilder); + } + + public boolean handleEvent(@Nonnull FileBrowserEventData data) { + if (data.getSearchQuery() != null) { + this.searchQuery = data.getSearchQuery().trim().toLowerCase(); + return true; + } else if (data.getRoot() != null) { + Path newRoot = this.findConfigRoot(data.getRoot()); + if (newRoot == null) { + newRoot = Path.of(data.getRoot()); + } + + this.setRoot(newRoot); + this.currentDir = this.root.getFileSystem().getPath(""); + this.searchQuery = ""; + return true; + } else if (data.getFile() != null) { + String fileName = data.getFile(); + if ("..".equals(fileName)) { + this.navigateUp(); + return true; + } else { + if (this.config.enableDirectoryNav()) { + Path targetPath = this.root.resolve(this.currentDir.toString()).resolve(fileName); + if (Files.isDirectory(targetPath)) { + this.currentDir = PathUtil.relativize(this.root, targetPath); + return true; + } + } + + return false; + } + } else { + return data.getSearchResult() != null ? false : false; + } + } + + private List buildDirectoryListing() { + List entries = new ObjectArrayList<>(); + Path path = this.root.resolve(this.currentDir.toString()); + if (!Files.isDirectory(path)) { + return entries; + } else { + try (DirectoryStream stream = Files.newDirectoryStream(path)) { + for (Path file : stream) { + String fileName = file.getFileName().toString(); + if (!fileName.startsWith(".")) { + boolean isDirectory = Files.isDirectory(file); + if (isDirectory || this.matchesExtension(fileName)) { + entries.add(new FileListProvider.FileEntry(fileName, isDirectory)); + } + } + } + } catch (IOException var10) { + LOGGER.atSevere().withCause(var10).log("Error listing directory: %s", path); + } + + entries.sort((a, b) -> { + if (a.isDirectory() == b.isDirectory()) { + return a.name().compareToIgnoreCase(b.name()); + } else { + return a.isDirectory() ? -1 : 1; + } + }); + return entries; + } + } + + private List buildSearchResults() { + final List allFiles = new ObjectArrayList<>(); + if (!Files.isDirectory(this.root)) { + return List.of(); + } else { + try { + Files.walkFileTree(this.root, new SimpleFileVisitor() { + @Nonnull + public FileVisitResult visitFile(@Nonnull Path file, @Nonnull BasicFileAttributes attrs) { + String fileName = file.getFileName().toString(); + if (ServerFileBrowser.this.matchesExtension(fileName)) { + allFiles.add(file); + } + + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException var8) { + LOGGER.atSevere().withCause(var8).log("Error walking directory: %s", this.root); + } + + Object2IntMap matchScores = new Object2IntOpenHashMap<>(allFiles.size()); + + for (Path file : allFiles) { + String fileName = file.getFileName().toString(); + String baseName = this.removeExtensions(fileName); + int score = StringCompareUtil.getFuzzyDistance(baseName, this.searchQuery, Locale.ENGLISH); + if (score > 0) { + matchScores.put(file, score); + } + } + + return matchScores.keySet().stream().sorted(Comparator.comparingInt(matchScores::getInt).reversed()).limit(this.config.maxResults()).map(filex -> { + Path relativePath = PathUtil.relativize(this.root, filex); + String fileNamex = filex.getFileName().toString(); + String displayName = this.removeExtensions(fileNamex); + return new FileListProvider.FileEntry(relativePath.toString(), displayName, false, matchScores.getInt(filex)); + }).collect(Collectors.toList()); + } + } + + private boolean matchesExtension(@Nonnull String fileName) { + if (this.config.allowedExtensions().isEmpty()) { + return true; + } else { + for (String ext : this.config.allowedExtensions()) { + if (fileName.endsWith(ext)) { + return true; + } + } + + return false; + } + } + + private String removeExtensions(@Nonnull String fileName) { + for (String ext : this.config.allowedExtensions()) { + if (fileName.endsWith(ext)) { + return fileName.substring(0, fileName.length() - ext.length()); + } + } + + return fileName; + } + + @Nonnull + public Path getRoot() { + return this.root; + } + + public void setRoot(@Nonnull Path root) { + if (Files.isDirectory(root)) { + this.root = root; + } + } + + @Nonnull + public Path getCurrentDir() { + return this.currentDir; + } + + public void setCurrentDir(@Nonnull Path currentDir) { + this.currentDir = currentDir; + } + + @Nonnull + public String getSearchQuery() { + return this.searchQuery; + } + + public void setSearchQuery(@Nonnull String searchQuery) { + this.searchQuery = searchQuery; + } + + public void navigateUp() { + if (!this.currentDir.toString().isEmpty()) { + Path parent = this.currentDir.getParent(); + this.currentDir = parent != null ? parent : Paths.get(""); + } + } + + public void navigateTo(@Nonnull Path relativePath) { + Path targetPath = this.root.resolve(this.currentDir.toString()).resolve(relativePath.toString()); + if (targetPath.normalize().startsWith(this.root.normalize())) { + if (Files.isDirectory(targetPath)) { + this.currentDir = PathUtil.relativize(this.root, targetPath); + } + } + } + + @Nonnull + public Set getSelectedItems() { + return Collections.unmodifiableSet(this.selectedItems); + } + + public void addSelection(@Nonnull String item) { + if (this.config.enableMultiSelect()) { + this.selectedItems.add(item); + } else { + this.selectedItems.clear(); + this.selectedItems.add(item); + } + } + + public void clearSelection() { + this.selectedItems.clear(); + } + + @Nonnull + public FileBrowserConfig getConfig() { + return this.config; + } + + @Nullable + public Path resolveSecure(@Nonnull String relativePath) { + Path resolved = this.root.resolve(relativePath); + return !resolved.normalize().startsWith(this.root.normalize()) ? null : resolved; + } + + @Nullable + public Path resolveFromCurrent(@Nonnull String fileName) { + Path resolved = this.root.resolve(this.currentDir.toString()).resolve(fileName); + return !resolved.normalize().startsWith(this.root.normalize()) ? null : resolved; + } + + @Nullable + private Path findConfigRoot(@Nonnull String pathStr) { + for (FileBrowserConfig.RootEntry rootEntry : this.config.roots()) { + if (rootEntry.path().toString().equals(pathStr)) { + return rootEntry.path(); + } + } + + return null; + } +} diff --git a/src/com/hypixel/hytale/server/core/ui/builder/EventData.java b/src/com/hypixel/hytale/server/core/ui/builder/EventData.java new file mode 100644 index 0000000..2dabe6d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/ui/builder/EventData.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.core.ui.builder; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public record EventData(Map events) { + public EventData() { + this(new Object2ObjectOpenHashMap<>()); + } + + @Nonnull + public EventData append(String key, String value) { + return this.put(key, value); + } + + @Nonnull + public > EventData append(String key, @Nonnull T enumValue) { + return this.put(key, enumValue.name()); + } + + @Nonnull + public EventData put(String key, String value) { + this.events.put(key, value); + return this; + } + + @Nonnull + public static EventData of(@Nonnull String key, @Nonnull String value) { + HashMap map = new HashMap<>(); + map.put(key, value); + return new EventData(map); + } +} diff --git a/src/com/hypixel/hytale/server/core/ui/builder/UICommandBuilder.java b/src/com/hypixel/hytale/server/core/ui/builder/UICommandBuilder.java new file mode 100644 index 0000000..426de15 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/ui/builder/UICommandBuilder.java @@ -0,0 +1,194 @@ +package com.hypixel.hytale.server.core.ui.builder; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.EmptyExtraInfo; +import com.hypixel.hytale.protocol.packets.interface_.CustomUICommand; +import com.hypixel.hytale.protocol.packets.interface_.CustomUICommandType; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.ui.Anchor; +import com.hypixel.hytale.server.core.ui.Area; +import com.hypixel.hytale.server.core.ui.DropdownEntryInfo; +import com.hypixel.hytale.server.core.ui.ItemGridSlot; +import com.hypixel.hytale.server.core.ui.LocalizableString; +import com.hypixel.hytale.server.core.ui.PatchStyle; +import com.hypixel.hytale.server.core.ui.Value; +import com.hypixel.hytale.server.core.ui.ValueCodec; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.bson.BsonArray; +import org.bson.BsonBoolean; +import org.bson.BsonDocument; +import org.bson.BsonDouble; +import org.bson.BsonInt32; +import org.bson.BsonNull; +import org.bson.BsonString; +import org.bson.BsonValue; + +public class UICommandBuilder { + private static final Map CODEC_MAP = new Object2ObjectOpenHashMap<>(); + public static final CustomUICommand[] EMPTY_COMMAND_ARRAY = new CustomUICommand[0]; + @Nonnull + private final List commands = new ObjectArrayList<>(); + + public UICommandBuilder() { + } + + @Nonnull + public UICommandBuilder clear(String selector) { + this.commands.add(new CustomUICommand(CustomUICommandType.Clear, selector, null, null)); + return this; + } + + @Nonnull + public UICommandBuilder remove(String selector) { + this.commands.add(new CustomUICommand(CustomUICommandType.Remove, selector, null, null)); + return this; + } + + @Nonnull + public UICommandBuilder append(String documentPath) { + this.commands.add(new CustomUICommand(CustomUICommandType.Append, null, null, documentPath)); + return this; + } + + @Nonnull + public UICommandBuilder append(String selector, String documentPath) { + this.commands.add(new CustomUICommand(CustomUICommandType.Append, selector, null, documentPath)); + return this; + } + + @Nonnull + public UICommandBuilder appendInline(String selector, String document) { + this.commands.add(new CustomUICommand(CustomUICommandType.AppendInline, selector, null, document)); + return this; + } + + @Nonnull + public UICommandBuilder insertBefore(String selector, String documentPath) { + this.commands.add(new CustomUICommand(CustomUICommandType.InsertBefore, selector, null, documentPath)); + return this; + } + + @Nonnull + public UICommandBuilder insertBeforeInline(String selector, String document) { + this.commands.add(new CustomUICommand(CustomUICommandType.InsertBeforeInline, selector, null, document)); + return this; + } + + @Nonnull + @Deprecated + private UICommandBuilder setBsonValue(String selector, BsonValue bsonValue) { + BsonDocument valueWrapper = new BsonDocument(); + valueWrapper.put("0", bsonValue); + this.commands.add(new CustomUICommand(CustomUICommandType.Set, selector, valueWrapper.toJson(), null)); + return this; + } + + @Nonnull + public UICommandBuilder set(String selector, @Nonnull Value ref) { + if (ref.getValue() != null) { + throw new IllegalArgumentException("Method only accepts references without a direct value"); + } else { + return this.setBsonValue(selector, ValueCodec.REFERENCE_ONLY.encode(ref)); + } + } + + @Nonnull + public UICommandBuilder setNull(String selector) { + return this.setBsonValue(selector, BsonNull.VALUE); + } + + @Nonnull + public UICommandBuilder set(String selector, @Nonnull String str) { + return this.setBsonValue(selector, new BsonString(str)); + } + + @Nonnull + public UICommandBuilder set(String selector, @Nonnull Message message) { + return this.setBsonValue(selector, Message.CODEC.encode(message, EmptyExtraInfo.EMPTY)); + } + + @Nonnull + public UICommandBuilder set(String selector, boolean b) { + return this.setBsonValue(selector, new BsonBoolean(b)); + } + + @Nonnull + public UICommandBuilder set(String selector, float n) { + return this.setBsonValue(selector, new BsonDouble(n)); + } + + @Nonnull + public UICommandBuilder set(String selector, int n) { + return this.setBsonValue(selector, new BsonInt32(n)); + } + + @Nonnull + public UICommandBuilder set(String selector, double n) { + return this.setBsonValue(selector, new BsonDouble(n)); + } + + @Nonnull + public UICommandBuilder setObject(String selector, @Nonnull Object data) { + Codec codec = CODEC_MAP.get(data.getClass()); + if (codec == null) { + throw new IllegalArgumentException(data.getClass().getName() + " is not a compatible class"); + } else { + return this.setBsonValue(selector, codec.encode(data)); + } + } + + @Nonnull + public UICommandBuilder set(String selector, @Nonnull T[] data) { + Codec codec = CODEC_MAP.get(data.getClass().getComponentType()); + if (codec == null) { + throw new IllegalArgumentException(data.getClass().getName() + " is not a compatible class"); + } else { + BsonArray arr = new BsonArray(); + + for (T d : data) { + arr.add(codec.encode(d)); + } + + return this.setBsonValue(selector, arr); + } + } + + @Nonnull + public UICommandBuilder set(String selector, @Nonnull List data) { + Codec codec = null; + BsonArray arr = new BsonArray(); + + for (T d : data) { + if (codec == null) { + codec = CODEC_MAP.get(d.getClass()); + if (codec == null) { + throw new IllegalArgumentException(data.getClass().getName() + " is not a compatible class"); + } + } + + arr.add(codec.encode(d)); + } + + return this.setBsonValue(selector, arr); + } + + @Nonnull + public CustomUICommand[] getCommands() { + return this.commands.toArray(CustomUICommand[]::new); + } + + static { + CODEC_MAP.put(Area.class, Area.CODEC); + CODEC_MAP.put(ItemGridSlot.class, ItemGridSlot.CODEC); + CODEC_MAP.put(ItemStack.class, ItemStack.CODEC); + CODEC_MAP.put(LocalizableString.class, LocalizableString.CODEC); + CODEC_MAP.put(PatchStyle.class, PatchStyle.CODEC); + CODEC_MAP.put(DropdownEntryInfo.class, DropdownEntryInfo.CODEC); + CODEC_MAP.put(Anchor.class, Anchor.CODEC); + } +} diff --git a/src/com/hypixel/hytale/server/core/ui/builder/UIEventBuilder.java b/src/com/hypixel/hytale/server/core/ui/builder/UIEventBuilder.java new file mode 100644 index 0000000..3869b34 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/ui/builder/UIEventBuilder.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.core.ui.builder; + +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBinding; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class UIEventBuilder { + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final CustomUIEventBinding[] EMPTY_EVENT_BINDING_ARRAY = new CustomUIEventBinding[0]; + @Nonnull + private final List events = new ObjectArrayList<>(); + + public UIEventBuilder() { + } + + @Nonnull + public UIEventBuilder addEventBinding(CustomUIEventBindingType type, String selector) { + return this.addEventBinding(type, selector, null); + } + + @Nonnull + public UIEventBuilder addEventBinding(CustomUIEventBindingType type, String selector, boolean locksInterface) { + return this.addEventBinding(type, selector, null, locksInterface); + } + + @Nonnull + public UIEventBuilder addEventBinding(CustomUIEventBindingType type, String selector, EventData data) { + return this.addEventBinding(type, selector, data, true); + } + + @Nonnull + public UIEventBuilder addEventBinding(CustomUIEventBindingType type, String selector, @Nullable EventData data, boolean locksInterface) { + String dataString = null; + if (data != null) { + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + dataString = MapCodec.STRING_HASH_MAP_CODEC.encode(data.events(), extraInfo).asDocument().toJson(); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER); + } + + this.events.add(new CustomUIEventBinding(type, selector, dataString, locksInterface)); + return this; + } + + @Nonnull + public CustomUIEventBinding[] getEvents() { + return this.events.toArray(CustomUIEventBinding[]::new); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/PlayerRef.java b/src/com/hypixel/hytale/server/core/universe/PlayerRef.java new file mode 100644 index 0000000..9f35990 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/PlayerRef.java @@ -0,0 +1,295 @@ +package com.hypixel.hytale.server.core.universe; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.metrics.MetricProvider; +import com.hypixel.hytale.metrics.MetricResults; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.protocol.HostAddress; +import com.hypixel.hytale.protocol.packets.auth.ClientReferral; +import com.hypixel.hytale.protocol.packets.connection.PongType; +import com.hypixel.hytale.protocol.packets.interface_.ChatType; +import com.hypixel.hytale.protocol.packets.interface_.ServerMessage; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.entity.entities.player.CameraManager; +import com.hypixel.hytale.server.core.entity.entities.player.HiddenPlayersManager; +import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementManager; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.io.PacketStatsRecorderImpl; +import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; +import com.hypixel.hytale.server.core.receiver.IMessageReceiver; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerRef implements Component, MetricProvider, IMessageReceiver { + @Nonnull + public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() + .register("Username", PlayerRef::getUsername, Codec.STRING) + .register("Language", PlayerRef::getLanguage, Codec.STRING) + .register("QueuedPacketsCount", ref -> ref.getPacketHandler().getQueuedPacketsCount(), Codec.INTEGER) + .register("PingInfo", ref -> { + PacketHandler handler = ref.getPacketHandler(); + PongType[] pongTypes = PongType.values(); + PacketHandler.PingInfo[] pingInfos = new PacketHandler.PingInfo[pongTypes.length]; + + for (int i = 0; i < pongTypes.length; i++) { + pingInfos[i] = handler.getPingInfo(pongTypes[i]); + } + + return pingInfos; + }, new ArrayCodec<>(PacketHandler.PingInfo.METRICS_REGISTRY, PacketHandler.PingInfo[]::new)) + .register( + "PacketStatsRecorder", + ref -> ref.getPacketHandler().getPacketStatsRecorder() instanceof PacketStatsRecorderImpl impl ? impl : null, + PacketStatsRecorderImpl.METRICS_REGISTRY + ) + .register("ChunkTracker", PlayerRef::getChunkTracker, ChunkTracker.METRICS_REGISTRY); + @Nonnull + public static final MetricsRegistry COMPONENT_METRICS_REGISTRY = new MetricsRegistry().register("MovementStates", ref -> { + Ref entityRef = ref.getReference(); + if (entityRef == null) { + return null; + } else { + MovementStatesComponent component = entityRef.getStore().getComponent(entityRef, MovementStatesComponent.getComponentType()); + return component != null ? component.getMovementStates().toString() : null; + } + }, Codec.STRING).register("MovementManager", ref -> { + Ref entityRef = ref.getReference(); + if (entityRef == null) { + return null; + } else { + MovementManager component = entityRef.getStore().getComponent(entityRef, MovementManager.getComponentType()); + return component != null ? component.toString() : null; + } + }, Codec.STRING).register("CameraManager", ref -> { + Ref entityRef = ref.getReference(); + if (entityRef == null) { + return null; + } else { + CameraManager component = entityRef.getStore().getComponent(entityRef, CameraManager.getComponentType()); + return component != null ? component.toString() : null; + } + }, Codec.STRING); + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private final UUID uuid; + @Nonnull + private final String username; + @Nonnull + private final PacketHandler packetHandler; + @Nonnull + private final ChunkTracker chunkTracker; + @Nonnull + private final HiddenPlayersManager hiddenPlayersManager = new HiddenPlayersManager(); + @Nonnull + private String language; + @Nullable + private Ref entity; + @Nullable + private Holder holder; + @Nullable + private UUID worldUuid; + private Transform transform = new Transform(0.0, 0.0, 0.0, 0.0F, 0.0F, 0.0F); + private Vector3f headRotation = new Vector3f(0.0F, 0.0F, 0.0F); + + @Nonnull + public static ComponentType getComponentType() { + return Universe.get().getPlayerRefComponentType(); + } + + public PlayerRef( + @Nonnull Holder holder, + @Nonnull UUID uuid, + @Nonnull String username, + @Nonnull String language, + @Nonnull PacketHandler packetHandler, + @Nonnull ChunkTracker chunkTracker + ) { + this.holder = holder; + this.uuid = uuid; + this.username = username; + this.language = language; + this.packetHandler = packetHandler; + this.chunkTracker = chunkTracker; + } + + @Nullable + public Ref addToStore(@Nonnull Store store) { + store.assertThread(); + if (this.holder == null) { + throw new IllegalStateException("Already in world"); + } else { + return store.addEntity(this.holder, AddReason.LOAD); + } + } + + public void addedToStore(Ref ref) { + this.holder = null; + this.entity = ref; + } + + @Nonnull + public Holder removeFromStore() { + if (this.entity == null) { + throw new IllegalStateException("Not in world"); + } else { + this.entity.getStore().assertThread(); + Ref entity = this.entity; + this.entity = null; + return this.holder = entity.getStore().removeEntity(entity, RemoveReason.UNLOAD); + } + } + + public boolean isValid() { + return this.entity != null || this.holder != null; + } + + @Nullable + public Ref getReference() { + return this.entity != null && this.entity.isValid() ? this.entity : null; + } + + @Nullable + public Holder getHolder() { + return this.holder; + } + + @Nullable + @Deprecated + public > T getComponent(@Nonnull ComponentType componentType) { + if (this.holder != null) { + return this.holder.getComponent(componentType); + } else { + Store store = this.entity.getStore(); + if (store.isInThread()) { + return store.getComponent(this.entity, componentType); + } else { + LOGGER.at(Level.SEVERE) + .withCause(new SkipSentryException()) + .log("PlayerRef.getComponent(%s) called async with player in world", componentType.getTypeClass().getSimpleName()); + return CompletableFuture.supplyAsync(() -> this.getComponent(componentType), store.getExternalData().getWorld()).join(); + } + } + } + + @Nonnull + public UUID getUuid() { + return this.uuid; + } + + @Nonnull + public String getUsername() { + return this.username; + } + + @Nonnull + public PacketHandler getPacketHandler() { + return this.packetHandler; + } + + @Nonnull + public ChunkTracker getChunkTracker() { + return this.chunkTracker; + } + + @Nonnull + public HiddenPlayersManager getHiddenPlayersManager() { + return this.hiddenPlayersManager; + } + + @Nonnull + public String getLanguage() { + return this.language; + } + + public void setLanguage(@Nonnull String language) { + this.language = language; + } + + @Nonnull + public Transform getTransform() { + return this.transform; + } + + @Nullable + public UUID getWorldUuid() { + return this.worldUuid; + } + + @Nonnull + public Vector3f getHeadRotation() { + return this.headRotation; + } + + public void updatePosition(@Nonnull World world, @Nonnull Transform transform, @Nonnull Vector3f headRotation) { + this.worldUuid = world.getWorldConfig().getUuid(); + this.transform.assign(transform); + this.headRotation.assign(headRotation); + } + + @Deprecated + public void replaceHolder(@Nonnull Holder holder) { + if (holder == null) { + throw new IllegalStateException("Player is still in the world"); + } else { + this.holder = holder; + } + } + + @Nonnull + @Override + public Component clone() { + return this; + } + + @Nonnull + @Override + public MetricResults toMetricResults() { + return METRICS_REGISTRY.toMetricResults(this); + } + + public void referToServer(@Nonnull String host, int port) { + this.referToServer(host, port, null); + } + + public void referToServer(@Nonnull String host, int port, @Nullable byte[] data) { + int MAX_REFERRAL_DATA_SIZE = 4096; + Objects.requireNonNull(host, "Host cannot be null"); + if (port > 0 && port <= 65535) { + if (data != null && data.length > 4096) { + throw new IllegalArgumentException("Referral data exceeds maximum size of 4096 bytes (got " + data.length + ")"); + } else { + HytaleLogger.getLogger() + .at(Level.INFO) + .log("Referring player %s (%s) to %s:%d with %d bytes of data", this.username, this.uuid, host, port, data != null ? data.length : 0); + this.packetHandler.writeNoCache(new ClientReferral(new HostAddress(host, (short)port), data)); + } + } else { + throw new IllegalArgumentException("Port must be between 1 and 65535"); + } + } + + @Override + public void sendMessage(@Nonnull Message message) { + this.packetHandler.writeNoCache(new ServerMessage(ChatType.Chat, message.getFormattedMessage())); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/Universe.java b/src/com/hypixel/hytale/server/core/universe/Universe.java new file mode 100644 index 0000000..ee0447c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/Universe.java @@ -0,0 +1,915 @@ +package com.hypixel.hytale.server.core.universe; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.lookup.Priority; +import com.hypixel.hytale.common.plugin.PluginIdentifier; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.common.semver.SemverRange; +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.EventRegistry; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.metrics.MetricProvider; +import com.hypixel.hytale.metrics.MetricResults; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.PlayerSkin; +import com.hypixel.hytale.protocol.packets.setup.ServerTags; +import com.hypixel.hytale.server.core.Constants; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.HytaleServerConfig; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.NameMatching; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.auth.PlayerAuthentication; +import com.hypixel.hytale.server.core.command.system.CommandRegistry; +import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData; +import com.hypixel.hytale.server.core.event.events.PrepareUniverseEvent; +import com.hypixel.hytale.server.core.event.events.ShutdownEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerDisconnectEvent; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.io.ProtocolVersion; +import com.hypixel.hytale.server.core.io.handlers.game.GamePacketHandler; +import com.hypixel.hytale.server.core.io.netty.NettyUtil; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.MovementAudioComponent; +import com.hypixel.hytale.server.core.modules.entity.component.PositionDataComponent; +import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerConnectionFlushSystem; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerPingSystem; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSkinComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.plugin.PluginManager; +import com.hypixel.hytale.server.core.receiver.IMessageReceiver; +import com.hypixel.hytale.server.core.universe.playerdata.PlayerStorage; +import com.hypixel.hytale.server.core.universe.system.PlayerRefAddedSystem; +import com.hypixel.hytale.server.core.universe.system.WorldConfigSaveSystem; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.WorldConfigProvider; +import com.hypixel.hytale.server.core.universe.world.commands.SetTickingCommand; +import com.hypixel.hytale.server.core.universe.world.commands.block.BlockCommand; +import com.hypixel.hytale.server.core.universe.world.commands.block.BlockSelectCommand; +import com.hypixel.hytale.server.core.universe.world.commands.world.WorldCommand; +import com.hypixel.hytale.server.core.universe.world.events.AddWorldEvent; +import com.hypixel.hytale.server.core.universe.world.events.AllWorldsLoadedEvent; +import com.hypixel.hytale.server.core.universe.world.events.RemoveWorldEvent; +import com.hypixel.hytale.server.core.universe.world.spawn.FitToHeightMapSpawnProvider; +import com.hypixel.hytale.server.core.universe.world.spawn.GlobalSpawnProvider; +import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider; +import com.hypixel.hytale.server.core.universe.world.spawn.IndividualSpawnProvider; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.component.ChunkSavingSystems; +import com.hypixel.hytale.server.core.universe.world.storage.provider.DefaultChunkStorageProvider; +import com.hypixel.hytale.server.core.universe.world.storage.provider.EmptyChunkStorageProvider; +import com.hypixel.hytale.server.core.universe.world.storage.provider.IChunkStorageProvider; +import com.hypixel.hytale.server.core.universe.world.storage.provider.IndexedStorageChunkStorageProvider; +import com.hypixel.hytale.server.core.universe.world.storage.provider.MigrationChunkStorageProvider; +import com.hypixel.hytale.server.core.universe.world.storage.resources.DefaultResourceStorageProvider; +import com.hypixel.hytale.server.core.universe.world.storage.resources.DiskResourceStorageProvider; +import com.hypixel.hytale.server.core.universe.world.storage.resources.EmptyResourceStorageProvider; +import com.hypixel.hytale.server.core.universe.world.storage.resources.IResourceStorageProvider; +import com.hypixel.hytale.server.core.universe.world.system.WorldPregenerateSystem; +import com.hypixel.hytale.server.core.universe.world.worldgen.provider.DummyWorldGenProvider; +import com.hypixel.hytale.server.core.universe.world.worldgen.provider.FlatWorldGenProvider; +import com.hypixel.hytale.server.core.universe.world.worldgen.provider.IWorldGenProvider; +import com.hypixel.hytale.server.core.universe.world.worldgen.provider.VoidWorldGenProvider; +import com.hypixel.hytale.server.core.universe.world.worldmap.provider.DisabledWorldMapProvider; +import com.hypixel.hytale.server.core.universe.world.worldmap.provider.IWorldMapProvider; +import com.hypixel.hytale.server.core.universe.world.worldmap.provider.chunk.WorldGenWorldMapProvider; +import com.hypixel.hytale.server.core.util.AssetUtil; +import com.hypixel.hytale.server.core.util.BsonUtil; +import com.hypixel.hytale.server.core.util.backup.BackupTask; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import io.netty.channel.Channel; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Collections; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.function.BiPredicate; +import java.util.logging.Level; +import javax.annotation.CheckReturnValue; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import joptsimple.OptionSet; +import org.bson.BsonDocument; +import org.bson.BsonValue; + +public class Universe extends JavaPlugin implements IMessageReceiver, MetricProvider { + @Nonnull + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(Universe.class).build(); + @Nonnull + private static Map LEGACY_BLOCK_ID_MAP = Collections.emptyMap(); + @Nonnull + public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() + .register("Worlds", universe -> universe.getWorlds().values().toArray(World[]::new), new ArrayCodec<>(World.METRICS_REGISTRY, World[]::new)) + .register("PlayerCount", Universe::getPlayerCount, Codec.INTEGER); + private static Universe instance; + private ComponentType playerRefComponentType; + @Nonnull + private final Path path = Constants.UNIVERSE_PATH; + @Nonnull + private final Map players = new ConcurrentHashMap<>(); + @Nonnull + private final Map worlds = new ConcurrentHashMap<>(); + @Nonnull + private final Map worldsByUuid = new ConcurrentHashMap<>(); + @Nonnull + private final Map unmodifiableWorlds = Collections.unmodifiableMap(this.worlds); + private PlayerStorage playerStorage; + private WorldConfigProvider worldConfigProvider; + private ResourceType indexedStorageCacheResourceType; + private CompletableFuture universeReady; + + public static Universe get() { + return instance; + } + + public Universe(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + if (!Files.isDirectory(this.path) && !Options.getOptionSet().has(Options.BARE)) { + try { + Files.createDirectories(this.path); + } catch (IOException var3) { + throw new RuntimeException("Failed to create universe directory", var3); + } + } + + if (Options.getOptionSet().has(Options.BACKUP)) { + int frequencyMinutes = Math.max(Options.getOptionSet().valueOf(Options.BACKUP_FREQUENCY_MINUTES), 1); + this.getLogger().at(Level.INFO).log("Scheduled backup to run every %d minute(s)", frequencyMinutes); + HytaleServer.SCHEDULED_EXECUTOR.scheduleWithFixedDelay(() -> { + try { + this.getLogger().at(Level.INFO).log("Backing up universe..."); + this.runBackup().thenAccept(aVoid -> this.getLogger().at(Level.INFO).log("Completed scheduled backup.")); + } catch (Exception var2x) { + this.getLogger().at(Level.SEVERE).withCause(var2x).log("Error backing up universe"); + } + }, frequencyMinutes, frequencyMinutes, TimeUnit.MINUTES); + } + } + + @Nonnull + public CompletableFuture runBackup() { + return CompletableFuture.allOf(this.worlds.values().stream().map(world -> CompletableFuture.supplyAsync(() -> { + Store componentStore = world.getChunkStore().getStore(); + ChunkSavingSystems.Data data = componentStore.getResource(ChunkStore.SAVE_RESOURCE); + data.isSaving = false; + return data; + }, world).thenCompose(ChunkSavingSystems.Data::waitForSavingChunks)).toArray(CompletableFuture[]::new)) + .thenCompose(aVoid -> BackupTask.start(this.path, Options.getOptionSet().valueOf(Options.BACKUP_DIRECTORY))) + .thenCompose(success -> CompletableFuture.allOf(this.worlds.values().stream().map(world -> CompletableFuture.runAsync(() -> { + Store componentStore = world.getChunkStore().getStore(); + ChunkSavingSystems.Data data = componentStore.getResource(ChunkStore.SAVE_RESOURCE); + data.isSaving = true; + }, world)).toArray(CompletableFuture[]::new)).thenApply(aVoid -> success)); + } + + @Override + protected void setup() { + EventRegistry eventRegistry = this.getEventRegistry(); + ComponentRegistryProxy chunkStoreRegistry = this.getChunkStoreRegistry(); + ComponentRegistryProxy entityStoreRegistry = this.getEntityStoreRegistry(); + CommandRegistry commandRegistry = this.getCommandRegistry(); + eventRegistry.register((short)-48, ShutdownEvent.class, event -> this.disconnectAllPLayers()); + eventRegistry.register((short)-32, ShutdownEvent.class, event -> this.shutdownAllWorlds()); + ISpawnProvider.CODEC.register("Global", GlobalSpawnProvider.class, GlobalSpawnProvider.CODEC); + ISpawnProvider.CODEC.register("Individual", IndividualSpawnProvider.class, IndividualSpawnProvider.CODEC); + ISpawnProvider.CODEC.register("FitToHeightMap", FitToHeightMapSpawnProvider.class, FitToHeightMapSpawnProvider.CODEC); + IWorldGenProvider.CODEC.register("Flat", FlatWorldGenProvider.class, FlatWorldGenProvider.CODEC); + IWorldGenProvider.CODEC.register("Dummy", DummyWorldGenProvider.class, DummyWorldGenProvider.CODEC); + IWorldGenProvider.CODEC.register(Priority.DEFAULT, "Void", VoidWorldGenProvider.class, VoidWorldGenProvider.CODEC); + IWorldMapProvider.CODEC.register("Disabled", DisabledWorldMapProvider.class, DisabledWorldMapProvider.CODEC); + IWorldMapProvider.CODEC.register(Priority.DEFAULT, "WorldGen", WorldGenWorldMapProvider.class, WorldGenWorldMapProvider.CODEC); + IChunkStorageProvider.CODEC.register(Priority.DEFAULT, "Hytale", DefaultChunkStorageProvider.class, DefaultChunkStorageProvider.CODEC); + IChunkStorageProvider.CODEC.register("Migration", MigrationChunkStorageProvider.class, MigrationChunkStorageProvider.CODEC); + IChunkStorageProvider.CODEC.register("IndexedStorage", IndexedStorageChunkStorageProvider.class, IndexedStorageChunkStorageProvider.CODEC); + IChunkStorageProvider.CODEC.register("Empty", EmptyChunkStorageProvider.class, EmptyChunkStorageProvider.CODEC); + IResourceStorageProvider.CODEC.register(Priority.DEFAULT, "Hytale", DefaultResourceStorageProvider.class, DefaultResourceStorageProvider.CODEC); + IResourceStorageProvider.CODEC.register("Disk", DiskResourceStorageProvider.class, DiskResourceStorageProvider.CODEC); + IResourceStorageProvider.CODEC.register("Empty", EmptyResourceStorageProvider.class, EmptyResourceStorageProvider.CODEC); + this.indexedStorageCacheResourceType = chunkStoreRegistry.registerResource( + IndexedStorageChunkStorageProvider.IndexedStorageCache.class, IndexedStorageChunkStorageProvider.IndexedStorageCache::new + ); + chunkStoreRegistry.registerSystem(new IndexedStorageChunkStorageProvider.IndexedStorageCacheSetupSystem()); + chunkStoreRegistry.registerSystem(new WorldPregenerateSystem()); + entityStoreRegistry.registerSystem(new WorldConfigSaveSystem()); + this.playerRefComponentType = entityStoreRegistry.registerComponent(PlayerRef.class, () -> { + throw new UnsupportedOperationException(); + }); + entityStoreRegistry.registerSystem(new PlayerPingSystem()); + entityStoreRegistry.registerSystem(new PlayerConnectionFlushSystem(this.playerRefComponentType)); + entityStoreRegistry.registerSystem(new PlayerRefAddedSystem(this.playerRefComponentType)); + commandRegistry.registerCommand(new SetTickingCommand()); + commandRegistry.registerCommand(new BlockCommand()); + commandRegistry.registerCommand(new BlockSelectCommand()); + commandRegistry.registerCommand(new WorldCommand()); + } + + @Override + protected void start() { + HytaleServerConfig config = HytaleServer.get().getConfig(); + if (config == null) { + throw new IllegalStateException("Server config is not loaded!"); + } else { + this.playerStorage = config.getPlayerStorageProvider().getPlayerStorage(); + WorldConfigProvider.Default defaultConfigProvider = new WorldConfigProvider.Default(); + PrepareUniverseEvent event = HytaleServer.get() + .getEventBus() + .dispatchFor(PrepareUniverseEvent.class) + .dispatch(new PrepareUniverseEvent(defaultConfigProvider)); + WorldConfigProvider worldConfigProvider = event.getWorldConfigProvider(); + if (worldConfigProvider == null) { + worldConfigProvider = defaultConfigProvider; + } + + this.worldConfigProvider = worldConfigProvider; + + try { + Path blockIdMapPath = this.path.resolve("blockIdMap.json"); + Path path = this.path.resolve("blockIdMap.legacy.json"); + if (Files.isRegularFile(blockIdMapPath)) { + Files.move(blockIdMapPath, path, StandardCopyOption.REPLACE_EXISTING); + } + + Files.deleteIfExists(this.path.resolve("blockIdMap.json.bak")); + if (Files.isRegularFile(path)) { + Map map = new Int2ObjectOpenHashMap<>(); + + for (BsonValue bsonValue : BsonUtil.readDocument(path).thenApply(document -> document.getArray("Blocks")).join()) { + BsonDocument bsonDocument = bsonValue.asDocument(); + map.put(bsonDocument.getNumber("Id").intValue(), bsonDocument.getString("BlockType").getValue()); + } + + LEGACY_BLOCK_ID_MAP = Collections.unmodifiableMap(map); + } + } catch (IOException var14) { + this.getLogger().at(Level.SEVERE).withCause(var14).log("Failed to delete blockIdMap.json"); + } + + if (Options.getOptionSet().has(Options.BARE)) { + this.universeReady = CompletableFuture.completedFuture(null); + HytaleServer.get().getEventBus().dispatch(AllWorldsLoadedEvent.class); + } else { + ObjectArrayList> loadingWorlds = new ObjectArrayList<>(); + + try { + Path worldsPath = this.path.resolve("worlds"); + Files.createDirectories(worldsPath); + + try (DirectoryStream stream = Files.newDirectoryStream(worldsPath)) { + for (Path file : stream) { + if (HytaleServer.get().isShuttingDown()) { + return; + } + + if (!file.equals(worldsPath) && Files.isDirectory(file)) { + String name = file.getFileName().toString(); + if (this.getWorld(name) == null) { + loadingWorlds.add(this.loadWorldFromStart(file, name).exceptionally(throwable -> { + this.getLogger().at(Level.SEVERE).withCause(throwable).log("Failed to load world: %s", name); + return null; + })); + } else { + this.getLogger().at(Level.SEVERE).log("Skipping loading world '%s' because it already exists!", name); + } + } + } + } + + this.universeReady = CompletableFutureUtil._catch( + CompletableFuture.allOf(loadingWorlds.toArray(CompletableFuture[]::new)) + .thenCompose( + v -> { + String worldName = config.getDefaults().getWorld(); + return worldName != null && !this.worlds.containsKey(worldName.toLowerCase()) + ? CompletableFutureUtil._catch(this.addWorld(worldName)) + : CompletableFuture.completedFuture(null); + } + ) + .thenRun(() -> HytaleServer.get().getEventBus().dispatch(AllWorldsLoadedEvent.class)) + ); + } catch (IOException var13) { + throw new RuntimeException("Failed to load Worlds", var13); + } + } + } + } + + @Override + protected void shutdown() { + this.disconnectAllPLayers(); + this.shutdownAllWorlds(); + } + + public void disconnectAllPLayers() { + this.players.values().forEach(player -> player.getPacketHandler().disconnect("Stopping server!")); + } + + public void shutdownAllWorlds() { + Iterator iterator = this.worlds.values().iterator(); + + while (iterator.hasNext()) { + World world = iterator.next(); + world.stop(); + iterator.remove(); + } + } + + @Nonnull + @Override + public MetricResults toMetricResults() { + return METRICS_REGISTRY.toMetricResults(this); + } + + public CompletableFuture getUniverseReady() { + return this.universeReady; + } + + public ResourceType getIndexedStorageCacheResourceType() { + return this.indexedStorageCacheResourceType; + } + + public boolean isWorldLoadable(@Nonnull String name) { + Path savePath = this.path.resolve("worlds").resolve(name); + return Files.isDirectory(savePath) && (Files.exists(savePath.resolve("config.bson")) || Files.exists(savePath.resolve("config.json"))); + } + + @Nonnull + @CheckReturnValue + public CompletableFuture addWorld(@Nonnull String name) { + return this.addWorld(name, null, null); + } + + @Nonnull + @Deprecated + @CheckReturnValue + public CompletableFuture addWorld(@Nonnull String name, @Nullable String generatorType, @Nullable String chunkStorageType) { + if (this.worlds.containsKey(name)) { + throw new IllegalArgumentException("World " + name + " already exists!"); + } else if (this.isWorldLoadable(name)) { + throw new IllegalArgumentException("World " + name + " already exists on disk!"); + } else { + Path savePath = this.path.resolve("worlds").resolve(name); + return this.worldConfigProvider.load(savePath, name).thenCompose(worldConfig -> { + if (generatorType != null && !"default".equals(generatorType)) { + BuilderCodec providerCodec = IWorldGenProvider.CODEC.getCodecFor(generatorType); + if (providerCodec == null) { + throw new IllegalArgumentException("Unknown generatorType '" + generatorType + "'"); + } + + IWorldGenProvider provider = providerCodec.getDefaultValue(); + worldConfig.setWorldGenProvider(provider); + worldConfig.markChanged(); + } + + if (chunkStorageType != null && !"default".equals(chunkStorageType)) { + BuilderCodec providerCodec = IChunkStorageProvider.CODEC.getCodecFor(chunkStorageType); + if (providerCodec == null) { + throw new IllegalArgumentException("Unknown chunkStorageType '" + chunkStorageType + "'"); + } + + IChunkStorageProvider provider = providerCodec.getDefaultValue(); + worldConfig.setChunkStorageProvider(provider); + worldConfig.markChanged(); + } + + return this.makeWorld(name, savePath, worldConfig); + }); + } + } + + @Nonnull + @CheckReturnValue + public CompletableFuture makeWorld(@Nonnull String name, @Nonnull Path savePath, @Nonnull WorldConfig worldConfig) { + return this.makeWorld(name, savePath, worldConfig, true); + } + + @Nonnull + @CheckReturnValue + public CompletableFuture makeWorld(@Nonnull String name, @Nonnull Path savePath, @Nonnull WorldConfig worldConfig, boolean start) { + Map map = worldConfig.getRequiredPlugins(); + if (map != null) { + PluginManager pluginManager = PluginManager.get(); + + for (Entry entry : map.entrySet()) { + if (!pluginManager.hasPlugin(entry.getKey(), entry.getValue())) { + this.getLogger().at(Level.SEVERE).log("Failed to load world! Missing plugin: %s, Version: %s", entry.getKey(), entry.getValue()); + throw new IllegalStateException("Missing plugin"); + } + } + } + + if (this.worlds.containsKey(name)) { + throw new IllegalArgumentException("World " + name + " already exists!"); + } else { + return CompletableFuture.supplyAsync( + SneakyThrow.sneakySupplier( + () -> { + World world = new World(name, savePath, worldConfig); + AddWorldEvent event = HytaleServer.get().getEventBus().dispatchFor(AddWorldEvent.class, name).dispatch(new AddWorldEvent(world)); + if (!event.isCancelled() && !HytaleServer.get().isShuttingDown()) { + World oldWorldByName = this.worlds.putIfAbsent(name.toLowerCase(), world); + if (oldWorldByName != null) { + throw new ConcurrentModificationException( + "World with name " + name + " already exists but didn't before! Looks like you have a race condition." + ); + } else { + World oldWorldByUuid = this.worldsByUuid.putIfAbsent(worldConfig.getUuid(), world); + if (oldWorldByUuid != null) { + throw new ConcurrentModificationException( + "World with UUID " + worldConfig.getUuid() + " already exists but didn't before! Looks like you have a race condition." + ); + } else { + return world; + } + } + } else { + throw new WorldLoadCancelledException(); + } + } + ) + ) + .thenCompose(World::init) + .thenCompose( + world -> !Options.getOptionSet().has(Options.MIGRATIONS) && start + ? world.start().thenApply(v -> world) + : CompletableFuture.completedFuture(world) + ) + .whenComplete((world, throwable) -> { + if (throwable != null) { + String nameLower = name.toLowerCase(); + if (this.worlds.containsKey(nameLower)) { + try { + this.removeWorldExceptionally(name); + } catch (Exception var6x) { + this.getLogger().at(Level.WARNING).withCause(var6x).log("Failed to clean up world '%s' after init failure", name); + } + } + } + }); + } + } + + private CompletableFuture loadWorldFromStart(@Nonnull Path savePath, @Nonnull String name) { + return this.worldConfigProvider + .load(savePath, name) + .thenCompose(worldConfig -> worldConfig.isDeleteOnUniverseStart() ? CompletableFuture.runAsync(() -> { + try { + FileUtil.deleteDirectory(savePath); + this.getLogger().at(Level.INFO).log("Deleted world " + name + " from DeleteOnUniverseStart flag on universe start at " + savePath); + } catch (Throwable var4) { + throw new RuntimeException("Error deleting world directory on universe start", var4); + } + }) : this.makeWorld(name, savePath, worldConfig).thenApply(x -> null)); + } + + @Nonnull + @CheckReturnValue + public CompletableFuture loadWorld(@Nonnull String name) { + if (this.worlds.containsKey(name)) { + throw new IllegalArgumentException("World " + name + " already loaded!"); + } else { + Path savePath = this.path.resolve("worlds").resolve(name); + if (!Files.isDirectory(savePath)) { + throw new IllegalArgumentException("World " + name + " does not exist!"); + } else { + return this.worldConfigProvider.load(savePath, name).thenCompose(worldConfig -> this.makeWorld(name, savePath, worldConfig)); + } + } + } + + @Nullable + public World getWorld(@Nullable String worldName) { + return worldName == null ? null : this.worlds.get(worldName.toLowerCase()); + } + + @Nullable + public World getWorld(@Nonnull UUID uuid) { + return this.worldsByUuid.get(uuid); + } + + @Nullable + public World getDefaultWorld() { + HytaleServerConfig config = HytaleServer.get().getConfig(); + if (config == null) { + return null; + } else { + String worldName = config.getDefaults().getWorld(); + return worldName != null ? this.getWorld(worldName) : null; + } + } + + public boolean removeWorld(@Nonnull String name) { + Objects.requireNonNull(name, "Name can't be null!"); + String nameLower = name.toLowerCase(); + World world = this.worlds.get(nameLower); + if (world == null) { + throw new NullPointerException("World " + name + " doesn't exist!"); + } else { + RemoveWorldEvent event = HytaleServer.get() + .getEventBus() + .dispatchFor(RemoveWorldEvent.class, name) + .dispatch(new RemoveWorldEvent(world, RemoveWorldEvent.RemovalReason.GENERAL)); + if (event.isCancelled()) { + return false; + } else { + this.worlds.remove(nameLower); + this.worldsByUuid.remove(world.getWorldConfig().getUuid()); + if (world.isAlive()) { + world.stopIndividualWorld(); + } + + world.validateDeleteOnRemove(); + return true; + } + } + } + + public void removeWorldExceptionally(@Nonnull String name) { + Objects.requireNonNull(name, "Name can't be null!"); + this.getLogger().at(Level.INFO).log("Removing world exceptionally: %s", name); + String nameLower = name.toLowerCase(); + World world = this.worlds.get(nameLower); + if (world == null) { + throw new NullPointerException("World " + name + " doesn't exist!"); + } else { + HytaleServer.get() + .getEventBus() + .dispatchFor(RemoveWorldEvent.class, name) + .dispatch(new RemoveWorldEvent(world, RemoveWorldEvent.RemovalReason.EXCEPTIONAL)); + this.worlds.remove(nameLower); + this.worldsByUuid.remove(world.getWorldConfig().getUuid()); + if (world.isAlive()) { + world.stopIndividualWorld(); + } + + world.validateDeleteOnRemove(); + } + } + + @Nonnull + public Path getPath() { + return this.path; + } + + @Nonnull + public Map getWorlds() { + return this.unmodifiableWorlds; + } + + @Nonnull + public List getPlayers() { + return new ObjectArrayList<>(this.players.values()); + } + + @Nullable + public PlayerRef getPlayer(@Nonnull UUID uuid) { + return this.players.get(uuid); + } + + @Nullable + public PlayerRef getPlayer(@Nonnull String value, @Nonnull NameMatching matching) { + return matching.find(this.players.values(), value, v -> v.getComponent(PlayerRef.getComponentType()).getUsername()); + } + + @Nullable + public PlayerRef getPlayer(@Nonnull String value, @Nonnull Comparator comparator, @Nonnull BiPredicate equality) { + return NameMatching.find(this.players.values(), value, v -> v.getComponent(PlayerRef.getComponentType()).getUsername(), comparator, equality); + } + + @Nullable + public PlayerRef getPlayerByUsername(@Nonnull String value, @Nonnull NameMatching matching) { + return matching.find(this.players.values(), value, PlayerRef::getUsername); + } + + @Nullable + public PlayerRef getPlayerByUsername(@Nonnull String value, @Nonnull Comparator comparator, @Nonnull BiPredicate equality) { + return NameMatching.find(this.players.values(), value, PlayerRef::getUsername, comparator, equality); + } + + public int getPlayerCount() { + return this.players.size(); + } + + @Nonnull + public CompletableFuture addPlayer( + @Nonnull Channel channel, + @Nonnull String language, + @Nonnull ProtocolVersion protocolVersion, + @Nonnull UUID uuid, + @Nonnull String username, + @Nonnull PlayerAuthentication auth, + int clientViewRadiusChunks, + @Nullable PlayerSkin skin + ) { + GamePacketHandler playerConnection = new GamePacketHandler(channel, protocolVersion, auth); + playerConnection.setQueuePackets(false); + this.getLogger().at(Level.INFO).log("Adding player '%s (%s)", username, uuid); + return this.playerStorage + .load(uuid) + .exceptionally(throwable -> { + throw new RuntimeException("Exception when adding player to universe:", throwable); + }) + .thenCompose( + holder -> { + ChunkTracker chunkTrackerComponent = new ChunkTracker(); + PlayerRef playerRefComponent = new PlayerRef((Holder)holder, uuid, username, language, playerConnection, chunkTrackerComponent); + chunkTrackerComponent.setDefaultMaxChunksPerSecond(playerRefComponent); + holder.putComponent(PlayerRef.getComponentType(), playerRefComponent); + holder.putComponent(ChunkTracker.getComponentType(), chunkTrackerComponent); + holder.putComponent(UUIDComponent.getComponentType(), new UUIDComponent(uuid)); + holder.ensureComponent(PositionDataComponent.getComponentType()); + holder.ensureComponent(MovementAudioComponent.getComponentType()); + Player playerComponent = holder.ensureAndGetComponent(Player.getComponentType()); + playerComponent.init(uuid, playerRefComponent); + PlayerConfigData playerConfig = playerComponent.getPlayerConfigData(); + playerConfig.cleanup(this); + PacketHandler.logConnectionTimings(channel, "Load Player Config", Level.FINEST); + if (skin != null) { + holder.putComponent(PlayerSkinComponent.getComponentType(), new PlayerSkinComponent(skin)); + holder.putComponent(ModelComponent.getComponentType(), new ModelComponent(CosmeticsModule.get().createModel(skin))); + } + + playerConnection.setPlayerRef(playerRefComponent, playerComponent); + NettyUtil.setChannelHandler(channel, playerConnection); + playerComponent.setClientViewRadius(clientViewRadiusChunks); + EntityTrackerSystems.EntityViewer entityViewerComponent = holder.getComponent(EntityTrackerSystems.EntityViewer.getComponentType()); + if (entityViewerComponent != null) { + entityViewerComponent.viewRadiusBlocks = playerComponent.getViewRadius() * 32; + } else { + entityViewerComponent = new EntityTrackerSystems.EntityViewer(playerComponent.getViewRadius() * 32, playerConnection); + holder.addComponent(EntityTrackerSystems.EntityViewer.getComponentType(), entityViewerComponent); + } + + PlayerRef existingPlayer = this.players.putIfAbsent(uuid, playerRefComponent); + if (existingPlayer != null) { + this.getLogger().at(Level.WARNING).log("Player '%s' (%s) already joining from another connection, rejecting duplicate", username, uuid); + playerConnection.disconnect("A connection with this account is already in progress"); + return CompletableFuture.completedFuture(null); + } else { + String lastWorldName = playerConfig.getWorld(); + World lastWorld = this.getWorld(lastWorldName); + PlayerConnectEvent event = HytaleServer.get() + .getEventBus() + .dispatchFor(PlayerConnectEvent.class) + .dispatch(new PlayerConnectEvent((Holder)holder, playerRefComponent, lastWorld != null ? lastWorld : this.getDefaultWorld())); + World world = event.getWorld() != null ? event.getWorld() : this.getDefaultWorld(); + if (world == null) { + this.players.remove(uuid, playerRefComponent); + playerConnection.disconnect("No world available to join"); + this.getLogger().at(Level.SEVERE).log("Player '%s' (%s) could not join - no default world configured", username, uuid); + return CompletableFuture.completedFuture(null); + } else { + if (lastWorldName != null && lastWorld == null) { + playerComponent.sendMessage( + Message.translation("server.universe.failedToFindWorld").param("lastWorldName", lastWorldName).param("name", world.getName()) + ); + } + + PacketHandler.logConnectionTimings(channel, "Processed Referral", Level.FINEST); + playerRefComponent.getPacketHandler().write(new ServerTags(AssetRegistry.getClientTags())); + return world.addPlayer(playerRefComponent, null, false, false).thenApply(p -> { + PacketHandler.logConnectionTimings(channel, "Add to World", Level.FINEST); + if (!channel.isActive()) { + if (p != null) { + playerComponent.remove(); + } + + this.players.remove(uuid, playerRefComponent); + this.getLogger().at(Level.WARNING).log("Player '%s' (%s) disconnected during world join, cleaned up from universe", username, uuid); + return null; + } else if (playerComponent.wasRemoved()) { + this.players.remove(uuid, playerRefComponent); + return null; + } else { + return (PlayerRef)p; + } + }).exceptionally(throwable -> { + this.players.remove(uuid, playerRefComponent); + playerComponent.remove(); + throw new RuntimeException("Exception when adding player to universe:", throwable); + }); + } + } + } + ); + } + + public void removePlayer(@Nonnull PlayerRef playerRef) { + this.getLogger().at(Level.INFO).log("Removing player '" + playerRef.getUsername() + "' (" + playerRef.getUuid() + ")"); + IEventDispatcher eventDispatcher = HytaleServer.get() + .getEventBus() + .dispatchFor(PlayerDisconnectEvent.class); + if (eventDispatcher.hasListener()) { + eventDispatcher.dispatch(new PlayerDisconnectEvent(playerRef)); + } + + Ref ref = playerRef.getReference(); + if (ref == null) { + this.finalizePlayerRemoval(playerRef); + } else { + World world = ref.getStore().getExternalData().getWorld(); + if (world.isInThread()) { + Player playerComponent = ref.getStore().getComponent(ref, Player.getComponentType()); + if (playerComponent != null) { + playerComponent.remove(); + } + + this.finalizePlayerRemoval(playerRef); + } else { + CompletableFuture.runAsync(() -> { + Player playerComponentx = ref.getStore().getComponent(ref, Player.getComponentType()); + if (playerComponentx != null) { + playerComponentx.remove(); + } + }, world) + .orTimeout(5L, TimeUnit.SECONDS) + .whenComplete( + (result, error) -> { + if (error != null) { + this.getLogger() + .at(Level.WARNING) + .withCause(error) + .log("Timeout or error waiting for player '%s' removal from world store", playerRef.getUsername()); + } + + this.finalizePlayerRemoval(playerRef); + } + ); + } + } + } + + private void finalizePlayerRemoval(@Nonnull PlayerRef playerRef) { + this.players.remove(playerRef.getUuid()); + if (Constants.SINGLEPLAYER) { + if (this.players.isEmpty()) { + this.getLogger().at(Level.INFO).log("No players left on singleplayer server shutting down!"); + HytaleServer.get().shutdownServer(); + } else if (SingleplayerModule.isOwner(playerRef)) { + this.getLogger().at(Level.INFO).log("Owner left the singleplayer server shutting down!"); + this.getPlayers().forEach(p -> p.getPacketHandler().disconnect(playerRef.getUsername() + " left! Shutting down singleplayer world!")); + HytaleServer.get().shutdownServer(); + } + } + } + + @Nonnull + public CompletableFuture resetPlayer(@Nonnull PlayerRef oldPlayer) { + return this.playerStorage.load(oldPlayer.getUuid()).exceptionally(throwable -> { + throw new RuntimeException("Exception when adding player to universe:", throwable); + }).thenCompose(holder -> this.resetPlayer(oldPlayer, (Holder)holder)); + } + + @Nonnull + public CompletableFuture resetPlayer(@Nonnull PlayerRef oldPlayer, @Nonnull Holder holder) { + return this.resetPlayer(oldPlayer, holder, null, null); + } + + @Nonnull + public CompletableFuture resetPlayer( + @Nonnull PlayerRef playerRef, @Nonnull Holder holder, @Nullable World world, @Nullable Transform transform + ) { + UUID uuid = playerRef.getUuid(); + Player oldPlayer = playerRef.getComponent(Player.getComponentType()); + World targetWorld; + if (world == null) { + targetWorld = oldPlayer.getWorld(); + } else { + targetWorld = world; + } + + this.getLogger() + .at(Level.INFO) + .log( + "Resetting player '%s', moving to world '%s' at location %s (%s)", + playerRef.getUsername(), + world != null ? world.getName() : null, + transform, + playerRef.getUuid() + ); + GamePacketHandler playerConnection = (GamePacketHandler)playerRef.getPacketHandler(); + Player newPlayer = holder.ensureAndGetComponent(Player.getComponentType()); + newPlayer.init(uuid, playerRef); + CompletableFuture leaveWorld = new CompletableFuture<>(); + if (oldPlayer.getWorld() != null) { + oldPlayer.getWorld().execute(() -> { + playerRef.removeFromStore(); + leaveWorld.complete(null); + }); + } else { + leaveWorld.complete(null); + } + + return leaveWorld.thenAccept(v -> { + oldPlayer.resetManagers(holder); + newPlayer.copyFrom(oldPlayer); + EntityTrackerSystems.EntityViewer viewer = holder.getComponent(EntityTrackerSystems.EntityViewer.getComponentType()); + if (viewer != null) { + viewer.viewRadiusBlocks = newPlayer.getViewRadius() * 32; + } else { + viewer = new EntityTrackerSystems.EntityViewer(newPlayer.getViewRadius() * 32, playerConnection); + holder.addComponent(EntityTrackerSystems.EntityViewer.getComponentType(), viewer); + } + + playerConnection.setPlayerRef(playerRef, newPlayer); + playerRef.replaceHolder(holder); + holder.putComponent(PlayerRef.getComponentType(), playerRef); + }).thenCompose(v -> targetWorld.addPlayer(playerRef, transform)); + } + + @Override + public void sendMessage(@Nonnull Message message) { + for (PlayerRef ref : this.players.values()) { + ref.sendMessage(message); + } + } + + public void broadcastPacket(@Nonnull Packet packet) { + for (PlayerRef player : this.players.values()) { + player.getPacketHandler().write(packet); + } + } + + public void broadcastPacketNoCache(@Nonnull Packet packet) { + for (PlayerRef player : this.players.values()) { + player.getPacketHandler().writeNoCache(packet); + } + } + + public void broadcastPacket(@Nonnull Packet... packets) { + for (PlayerRef player : this.players.values()) { + player.getPacketHandler().write(packets); + } + } + + public PlayerStorage getPlayerStorage() { + return this.playerStorage; + } + + public void setPlayerStorage(@Nonnull PlayerStorage playerStorage) { + this.playerStorage = playerStorage; + } + + public WorldConfigProvider getWorldConfigProvider() { + return this.worldConfigProvider; + } + + @Nonnull + public ComponentType getPlayerRefComponentType() { + return this.playerRefComponentType; + } + + @Nonnull + @Deprecated + public static Map getLegacyBlockIdMap() { + return LEGACY_BLOCK_ID_MAP; + } + + public static Path getWorldGenPath() { + OptionSet optionSet = Options.getOptionSet(); + Path worldGenPath; + if (optionSet.has(Options.WORLD_GEN_DIRECTORY)) { + worldGenPath = optionSet.valueOf(Options.WORLD_GEN_DIRECTORY); + } else { + worldGenPath = AssetUtil.getHytaleAssetsPath().resolve("Server").resolve("World"); + } + + return worldGenPath; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/WorldLoadCancelledException.java b/src/com/hypixel/hytale/server/core/universe/WorldLoadCancelledException.java new file mode 100644 index 0000000..a65cb7d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/WorldLoadCancelledException.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.server.core.universe; + +public class WorldLoadCancelledException extends RuntimeException { + public WorldLoadCancelledException() { + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/datastore/DataStore.java b/src/com/hypixel/hytale/server/core/universe/datastore/DataStore.java new file mode 100644 index 0000000..e77525c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/datastore/DataStore.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.core.universe.datastore; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface DataStore { + BuilderCodec getCodec(); + + @Nullable + T load(String var1) throws IOException; + + void save(String var1, T var2); + + void remove(String var1) throws IOException; + + List list() throws IOException; + + @Nonnull + default Map loadAll() throws IOException { + Map map = new Object2ObjectOpenHashMap<>(); + + for (String id : this.list()) { + T value = this.load(id); + if (value != null) { + map.put(id, value); + } + } + + return map; + } + + default void saveAll(@Nonnull Map objectsToSave) { + for (Entry entry : objectsToSave.entrySet()) { + this.save(entry.getKey(), entry.getValue()); + } + } + + default void removeAll() throws IOException { + for (String id : this.list()) { + this.remove(id); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/datastore/DataStoreProvider.java b/src/com/hypixel/hytale/server/core/universe/datastore/DataStoreProvider.java new file mode 100644 index 0000000..4b46ab4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/datastore/DataStoreProvider.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.core.universe.datastore; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.BuilderCodecMapCodec; + +public interface DataStoreProvider { + BuilderCodecMapCodec CODEC = new BuilderCodecMapCodec<>("Type"); + + DataStore create(BuilderCodec var1); +} diff --git a/src/com/hypixel/hytale/server/core/universe/datastore/DiskDataStore.java b/src/com/hypixel/hytale/server/core/universe/datastore/DiskDataStore.java new file mode 100644 index 0000000..e44819e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/datastore/DiskDataStore.java @@ -0,0 +1,142 @@ +package com.hypixel.hytale.server.core.universe.datastore; + +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.util.BsonUtil; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public class DiskDataStore implements DataStore { + private static final String EXTENSION = ".json"; + private static final int EXTENSION_LEN = ".json".length(); + private static final String EXTENSION_BACKUP = ".json.bak"; + private static final String GLOB = "*.json"; + private static final String GLOB_WITH_BACKUP = "*.{json,json.bak}"; + @Nonnull + private final HytaleLogger logger; + @Nonnull + private final Path path; + private final BuilderCodec codec; + + public DiskDataStore(@Nonnull String path, BuilderCodec codec) { + this.logger = HytaleLogger.get("DataStore|" + path); + this.path = Universe.get().getPath().resolve(path); + this.codec = codec; + if (Files.isDirectory(this.path)) { + try (DirectoryStream paths = Files.newDirectoryStream(this.path, "*.bson")) { + for (Path oldPath : paths) { + Path newPath = getPathFromId(this.path, getIdFromPath(oldPath)); + + try { + Files.move(oldPath, newPath); + } catch (IOException var9) { + } + } + } catch (IOException var11) { + this.logger.at(Level.SEVERE).withCause(var11).log("Failed to migrate files form .bson to .json!"); + } + } + } + + @Nonnull + public Path getPath() { + return this.path; + } + + @Override + public BuilderCodec getCodec() { + return this.codec; + } + + @Nullable + @Override + public T load(String id) throws IOException { + Path filePath = getPathFromId(this.path, id); + return Files.exists(filePath) ? this.load0(filePath) : null; + } + + @Override + public void save(String id, T value) { + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + BsonDocument bsonValue = this.codec.encode(value, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(this.logger); + BsonUtil.writeDocument(getPathFromId(this.path, id), bsonValue.asDocument()).join(); + } + + @Override + public void remove(String id) throws IOException { + Files.deleteIfExists(getPathFromId(this.path, id)); + Files.deleteIfExists(getBackupPathFromId(this.path, id)); + } + + @Nonnull + @Override + public List list() throws IOException { + List list = new ObjectArrayList<>(); + + try (DirectoryStream paths = Files.newDirectoryStream(this.path, "*.json")) { + for (Path path : paths) { + list.add(getIdFromPath(path)); + } + } + + return list; + } + + @Nonnull + @Override + public Map loadAll() throws IOException { + Map map = new Object2ObjectOpenHashMap<>(); + + try (DirectoryStream paths = Files.newDirectoryStream(this.path, "*.json")) { + for (Path path : paths) { + map.put(getIdFromPath(path), this.load0(path)); + } + } + + return map; + } + + @Override + public void removeAll() throws IOException { + try (DirectoryStream paths = Files.newDirectoryStream(this.path, "*.{json,json.bak}")) { + for (Path path : paths) { + Files.delete(path); + } + } + } + + @Nullable + protected T load0(@Nonnull Path path) throws IOException { + return RawJsonReader.readSync(path, this.codec, this.logger); + } + + @Nonnull + protected static Path getPathFromId(@Nonnull Path path, String id) { + return path.resolve(id + ".json"); + } + + @Nonnull + protected static Path getBackupPathFromId(@Nonnull Path path, String id) { + return path.resolve(id + ".json.bak"); + } + + @Nonnull + protected static String getIdFromPath(@Nonnull Path path) { + String fileName = path.getFileName().toString(); + return fileName.substring(0, fileName.length() - EXTENSION_LEN); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/datastore/DiskDataStoreProvider.java b/src/com/hypixel/hytale/server/core/universe/datastore/DiskDataStoreProvider.java new file mode 100644 index 0000000..4c9229a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/datastore/DiskDataStoreProvider.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.universe.datastore; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import javax.annotation.Nonnull; + +public class DiskDataStoreProvider implements DataStoreProvider { + public static final String ID = "Disk"; + public static final BuilderCodec CODEC = BuilderCodec.builder(DiskDataStoreProvider.class, DiskDataStoreProvider::new) + .append( + new KeyedCodec<>("Path", Codec.STRING), + (diskDataStoreProvider, s) -> diskDataStoreProvider.path = s, + diskDataStoreProvider -> diskDataStoreProvider.path + ) + .addValidator(Validators.nonNull()) + .add() + .build(); + private String path; + + public DiskDataStoreProvider(String path) { + this.path = path; + } + + protected DiskDataStoreProvider() { + } + + @Nonnull + @Override + public DataStore create(BuilderCodec builderCodec) { + return new DiskDataStore<>(this.path, builderCodec); + } + + @Nonnull + @Override + public String toString() { + return "DiskDataStoreProvider{path='" + this.path + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/playerdata/DefaultPlayerStorageProvider.java b/src/com/hypixel/hytale/server/core/universe/playerdata/DefaultPlayerStorageProvider.java new file mode 100644 index 0000000..036e164 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/playerdata/DefaultPlayerStorageProvider.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.core.universe.playerdata; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import javax.annotation.Nonnull; + +public class DefaultPlayerStorageProvider implements PlayerStorageProvider { + public static final DefaultPlayerStorageProvider INSTANCE = new DefaultPlayerStorageProvider(); + public static final String ID = "Hytale"; + public static final BuilderCodec CODEC = BuilderCodec.builder(DefaultPlayerStorageProvider.class, () -> INSTANCE).build(); + public static final DiskPlayerStorageProvider DEFAULT = new DiskPlayerStorageProvider(); + + public DefaultPlayerStorageProvider() { + } + + @Nonnull + @Override + public PlayerStorage getPlayerStorage() { + return DEFAULT.getPlayerStorage(); + } + + @Nonnull + @Override + public String toString() { + return "DefaultPlayerStorageProvider{}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/playerdata/DiskPlayerStorageProvider.java b/src/com/hypixel/hytale/server/core/universe/playerdata/DiskPlayerStorageProvider.java new file mode 100644 index 0000000..35adfe8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/playerdata/DiskPlayerStorageProvider.java @@ -0,0 +1,125 @@ +package com.hypixel.hytale.server.core.universe.playerdata; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.server.core.Constants; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.BsonUtil; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; + +public class DiskPlayerStorageProvider implements PlayerStorageProvider { + public static final String ID = "Disk"; + public static final BuilderCodec CODEC = BuilderCodec.builder(DiskPlayerStorageProvider.class, DiskPlayerStorageProvider::new) + .append(new KeyedCodec<>("Path", Codec.STRING), (o, s) -> o.path = PathUtil.get(s), o -> o.path.toString()) + .add() + .build(); + @Nonnull + private Path path = Constants.UNIVERSE_PATH.resolve("players"); + + public DiskPlayerStorageProvider() { + } + + @Nonnull + public Path getPath() { + return this.path; + } + + @Nonnull + @Override + public PlayerStorage getPlayerStorage() { + return new DiskPlayerStorageProvider.DiskPlayerStorage(this.path); + } + + @Nonnull + @Override + public String toString() { + return "DiskPlayerStorageProvider{path=" + this.path + "}"; + } + + public static class DiskPlayerStorage implements PlayerStorage { + public static final String FILE_EXTENSION = ".json"; + @Nonnull + private final Path path; + + public DiskPlayerStorage(@Nonnull Path path) { + this.path = path; + if (!Options.getOptionSet().has(Options.BARE)) { + try { + Files.createDirectories(path); + } catch (IOException var3) { + throw new RuntimeException("Failed to create players directory", var3); + } + } + } + + @Nonnull + @Override + public CompletableFuture> load(@Nonnull UUID uuid) { + Path file = this.path.resolve(uuid + ".json"); + return BsonUtil.readDocument(file).thenApply(bsonDocument -> { + if (bsonDocument == null) { + bsonDocument = new BsonDocument(); + } + + return EntityStore.REGISTRY.deserialize(bsonDocument); + }); + } + + @Nonnull + @Override + public CompletableFuture save(@Nonnull UUID uuid, @Nonnull Holder holder) { + Path file = this.path.resolve(uuid + ".json"); + BsonDocument document = EntityStore.REGISTRY.serialize(holder); + return BsonUtil.writeDocument(file, document); + } + + @Nonnull + @Override + public CompletableFuture remove(@Nonnull UUID uuid) { + Path file = this.path.resolve(uuid + ".json"); + + try { + Files.deleteIfExists(file); + return CompletableFuture.completedFuture(null); + } catch (IOException var4) { + return CompletableFuture.failedFuture(var4); + } + } + + @Nonnull + @Override + public Set getPlayers() throws IOException { + Set var2; + try (Stream stream = Files.list(this.path)) { + var2 = stream.map(p -> { + String fileName = p.getFileName().toString(); + if (!fileName.endsWith(".json")) { + return null; + } else { + try { + return UUID.fromString(fileName.substring(0, fileName.length() - ".json".length())); + } catch (IllegalArgumentException var3) { + return null; + } + } + }).filter(Objects::nonNull).collect(Collectors.toSet()); + } + + return var2; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/playerdata/PlayerStorage.java b/src/com/hypixel/hytale/server/core/universe/playerdata/PlayerStorage.java new file mode 100644 index 0000000..177e759 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/playerdata/PlayerStorage.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.core.universe.playerdata; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.io.IOException; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public interface PlayerStorage { + @Nonnull + CompletableFuture> load(@Nonnull UUID var1); + + @Nonnull + CompletableFuture save(@Nonnull UUID var1, @Nonnull Holder var2); + + @Nonnull + CompletableFuture remove(@Nonnull UUID var1); + + @Nonnull + Set getPlayers() throws IOException; +} diff --git a/src/com/hypixel/hytale/server/core/universe/playerdata/PlayerStorageProvider.java b/src/com/hypixel/hytale/server/core/universe/playerdata/PlayerStorageProvider.java new file mode 100644 index 0000000..1ea13e1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/playerdata/PlayerStorageProvider.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.server.core.universe.playerdata; + +import com.hypixel.hytale.codec.lookup.BuilderCodecMapCodec; + +public interface PlayerStorageProvider { + BuilderCodecMapCodec CODEC = new BuilderCodecMapCodec<>("Type", true); + + PlayerStorage getPlayerStorage(); +} diff --git a/src/com/hypixel/hytale/server/core/universe/system/PlayerRefAddedSystem.java b/src/com/hypixel/hytale/server/core/universe/system/PlayerRefAddedSystem.java new file mode 100644 index 0000000..08be358 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/system/PlayerRefAddedSystem.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.core.universe.system; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.RootDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import javax.annotation.Nonnull; + +public class PlayerRefAddedSystem extends RefSystem { + @Nonnull + private final ComponentType playerRefComponentType; + + public PlayerRefAddedSystem(@Nonnull ComponentType playerRefComponentType) { + this.playerRefComponentType = playerRefComponentType; + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.firstSet(); + } + + @Override + public Query getQuery() { + return this.playerRefComponentType; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + PlayerRef playerRefComponent = store.getComponent(ref, this.playerRefComponentType); + + assert playerRefComponent != null; + + playerRefComponent.addedToStore(ref); + store.getExternalData().getWorld().trackPlayerRef(playerRefComponent); + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + PlayerRef playerRefComponent = store.getComponent(ref, this.playerRefComponentType); + + assert playerRefComponent != null; + + store.getExternalData().getWorld().untrackPlayerRef(playerRefComponent); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/system/PlayerVelocityInstructionSystem.java b/src/com/hypixel/hytale/server/core/universe/system/PlayerVelocityInstructionSystem.java new file mode 100644 index 0000000..98bf025 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/system/PlayerVelocityInstructionSystem.java @@ -0,0 +1,117 @@ +package com.hypixel.hytale.server.core.universe.system; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.dependency.SystemTypeDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.ChangeVelocityType; +import com.hypixel.hytale.protocol.packets.entities.ChangeVelocity; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.modules.physics.systems.GenericVelocityInstructionSystem; +import com.hypixel.hytale.server.core.modules.splitvelocity.VelocityConfig; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Set; +import javax.annotation.Nonnull; + +public class PlayerVelocityInstructionSystem extends EntityTickingSystem { + @Nonnull + private final Set> dependencies = Set.of( + new SystemDependency<>(Order.BEFORE, GenericVelocityInstructionSystem.class), + new SystemTypeDependency(Order.AFTER, EntityModule.get().getVelocityModifyingSystemType()) + ); + @Nonnull + private final Query query = Query.and(PlayerRef.getComponentType(), Velocity.getComponentType()); + + public PlayerVelocityInstructionSystem() { + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + PlayerRef playerRefComponent = archetypeChunk.getComponent(index, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + Velocity velocityComponent = archetypeChunk.getComponent(index, Velocity.getComponentType()); + + assert velocityComponent != null; + + for (Velocity.Instruction instruction : velocityComponent.getInstructions()) { + switch (instruction.getType()) { + case Set: + Vector3d velocityx = instruction.getVelocity(); + VelocityConfig velocityConfigx = instruction.getConfig(); + playerRefComponent.getPacketHandler() + .writeNoCache( + new ChangeVelocity( + (float)velocityx.x, + (float)velocityx.y, + (float)velocityx.z, + ChangeVelocityType.Set, + velocityConfigx != null ? velocityConfigx.toPacket() : null + ) + ); + if (DebugUtils.DISPLAY_FORCES) { + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + World world = commandBuffer.getExternalData().getWorld(); + DebugUtils.addForce(world, transformComponent.getPosition(), velocityx, velocityConfigx); + } + break; + case Add: + Vector3d velocity = instruction.getVelocity(); + VelocityConfig velocityConfig = instruction.getConfig(); + playerRefComponent.getPacketHandler() + .writeNoCache( + new ChangeVelocity( + (float)velocity.x, + (float)velocity.y, + (float)velocity.z, + ChangeVelocityType.Add, + velocityConfig != null ? velocityConfig.toPacket() : null + ) + ); + if (DebugUtils.DISPLAY_FORCES) { + TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType()); + + assert transformComponent != null; + + World world = commandBuffer.getExternalData().getWorld(); + DebugUtils.addForce(world, transformComponent.getPosition(), new Vector3d(velocity.x, velocity.y, velocity.z), velocityConfig); + } + } + } + + velocityComponent.getInstructions().clear(); + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/system/WorldConfigSaveSystem.java b/src/com/hypixel/hytale/server/core/universe/system/WorldConfigSaveSystem.java new file mode 100644 index 0000000..4b57bdc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/system/WorldConfigSaveSystem.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.core.universe.system; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.DelayedSystem; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class WorldConfigSaveSystem extends DelayedSystem { + public WorldConfigSaveSystem() { + super(10.0F); + } + + @Override + public void delayedTick(float dt, int systemIndex, @Nonnull Store store) { + World world = store.getExternalData().getWorld(); + saveWorldConfigAndResources(world).join(); + } + + @Nonnull + public static CompletableFuture saveWorldConfigAndResources(@Nonnull World world) { + WorldConfig worldConfig = world.getWorldConfig(); + return worldConfig.isSavingConfig() && worldConfig.consumeHasChanged() + ? CompletableFuture.allOf( + world.getChunkStore().getStore().saveAllResources(), + world.getEntityStore().getStore().saveAllResources(), + Universe.get().getWorldConfigProvider().save(world.getSavePath(), world.getWorldConfig(), world) + ) + : CompletableFuture.allOf(world.getChunkStore().getStore().saveAllResources(), world.getEntityStore().getStore().saveAllResources()); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/ClientEffectWorldSettings.java b/src/com/hypixel/hytale/server/core/universe/world/ClientEffectWorldSettings.java new file mode 100644 index 0000000..9ddea08 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/ClientEffectWorldSettings.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.server.core.universe.world; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.packets.world.UpdatePostFxSettings; +import com.hypixel.hytale.protocol.packets.world.UpdateSunSettings; + +public class ClientEffectWorldSettings { + public static BuilderCodec CODEC = BuilderCodec.builder(ClientEffectWorldSettings.class, ClientEffectWorldSettings::new) + .append(new KeyedCodec<>("SunHeightPercent", Codec.FLOAT), (settings, o) -> settings.sunHeightPercent = o, settings -> settings.sunHeightPercent) + .add() + .append( + new KeyedCodec<>("SunAngleDegrees", Codec.FLOAT), + (settings, o) -> settings.sunAngleRadians = (float)Math.toRadians(o.floatValue()), + settings -> (float)Math.toDegrees(settings.sunAngleRadians) + ) + .add() + .append(new KeyedCodec<>("BloomIntensity", Codec.FLOAT), (settings, o) -> settings.bloomIntensity = o, settings -> settings.bloomIntensity) + .add() + .append(new KeyedCodec<>("BloomPower", Codec.FLOAT), (settings, o) -> settings.bloomPower = o, settings -> settings.bloomPower) + .add() + .append(new KeyedCodec<>("SunIntensity", Codec.FLOAT), (settings, o) -> settings.sunIntensity = o, settings -> settings.sunIntensity) + .add() + .append(new KeyedCodec<>("SunshaftIntensity", Codec.FLOAT), (settings, o) -> settings.sunshaftIntensity = o, settings -> settings.sunshaftIntensity) + .add() + .append(new KeyedCodec<>("SunshaftScaleFactor", Codec.FLOAT), (settings, o) -> settings.sunshaftScaleFactor = o, settings -> settings.sunshaftScaleFactor) + .add() + .build(); + private float sunHeightPercent = 100.0F; + private float sunAngleRadians = 0.0F; + private float bloomIntensity = 0.3F; + private float bloomPower = 8.0F; + private float sunIntensity = 0.25F; + private float sunshaftIntensity = 0.3F; + private float sunshaftScaleFactor = 4.0F; + + public ClientEffectWorldSettings() { + } + + public float getSunHeightPercent() { + return this.sunHeightPercent; + } + + public void setSunHeightPercent(float sunHeightPercent) { + this.sunHeightPercent = sunHeightPercent; + } + + public float getSunAngleRadians() { + return this.sunAngleRadians; + } + + public void setSunAngleRadians(float sunAngleRadians) { + this.sunAngleRadians = sunAngleRadians; + } + + public float getBloomIntensity() { + return this.bloomIntensity; + } + + public void setBloomIntensity(float bloomIntensity) { + this.bloomIntensity = bloomIntensity; + } + + public float getBloomPower() { + return this.bloomPower; + } + + public void setBloomPower(float bloomPower) { + this.bloomPower = bloomPower; + } + + public float getSunIntensity() { + return this.sunIntensity; + } + + public void setSunIntensity(float sunIntensity) { + this.sunIntensity = sunIntensity; + } + + public float getSunshaftIntensity() { + return this.sunshaftIntensity; + } + + public void setSunshaftIntensity(float sunshaftIntensity) { + this.sunshaftIntensity = sunshaftIntensity; + } + + public float getSunshaftScaleFactor() { + return this.sunshaftScaleFactor; + } + + public void setSunshaftScaleFactor(float sunshaftScaleFactor) { + this.sunshaftScaleFactor = sunshaftScaleFactor; + } + + public UpdateSunSettings createSunSettingsPacket() { + return new UpdateSunSettings(this.sunHeightPercent, this.sunAngleRadians); + } + + public UpdatePostFxSettings createPostFxSettingsPacket() { + return new UpdatePostFxSettings(this.bloomIntensity, this.bloomPower, this.sunshaftScaleFactor, this.sunIntensity, this.sunshaftIntensity); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/IWorldChunks.java b/src/com/hypixel/hytale/server/core/universe/world/IWorldChunks.java new file mode 100644 index 0000000..6175ae9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/IWorldChunks.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.core.universe.world; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.server.core.universe.world.accessor.IChunkAccessorSync; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +@Deprecated +public interface IWorldChunks extends IChunkAccessorSync, IWorldChunksAsync { + @Deprecated + void consumeTaskQueue(); + + boolean isInThread(); + + default WorldChunk getChunk(long index) { + WorldChunk worldChunk = this.loadChunkIfInMemory(index); + if (worldChunk != null) { + return worldChunk; + } else { + CompletableFuture future = this.getChunkAsync(index); + return this.waitForFutureWithoutLock(future); + } + } + + default WorldChunk getNonTickingChunk(long index) { + WorldChunk worldChunk = this.getChunkIfInMemory(index); + if (worldChunk != null) { + return worldChunk; + } else { + CompletableFuture future = this.getNonTickingChunkAsync(index); + return this.waitForFutureWithoutLock(future); + } + } + + default T waitForFutureWithoutLock(@Nonnull CompletableFuture future) { + if (!this.isInThread()) { + return future.join(); + } else { + AssetRegistry.ASSET_LOCK.readLock().unlock(); + + for (; !future.isDone(); Thread.yield()) { + AssetRegistry.ASSET_LOCK.readLock().lock(); + + try { + this.consumeTaskQueue(); + } finally { + AssetRegistry.ASSET_LOCK.readLock().unlock(); + } + } + + AssetRegistry.ASSET_LOCK.readLock().lock(); + return future.join(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/IWorldChunksAsync.java b/src/com/hypixel/hytale/server/core/universe/world/IWorldChunksAsync.java new file mode 100644 index 0000000..1a7d846 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/IWorldChunksAsync.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.server.core.universe.world; + +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import java.util.concurrent.CompletableFuture; + +@Deprecated +public interface IWorldChunksAsync { + CompletableFuture getChunkAsync(long var1); + + CompletableFuture getNonTickingChunkAsync(long var1); + + default CompletableFuture getChunkAsync(int x, int z) { + return this.getChunkAsync(ChunkUtil.indexChunk(x, z)); + } + + default CompletableFuture getNonTickingChunkAsync(int x, int z) { + return this.getNonTickingChunkAsync(ChunkUtil.indexChunk(x, z)); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/ParticleUtil.java b/src/com/hypixel/hytale/server/core/universe/world/ParticleUtil.java new file mode 100644 index 0000000..341a574 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/ParticleUtil.java @@ -0,0 +1,264 @@ +package com.hypixel.hytale.server.core.universe.world; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.Position; +import com.hypixel.hytale.protocol.packets.world.SpawnParticleSystem; +import com.hypixel.hytale.server.core.asset.type.particle.config.WorldParticle; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ParticleUtil { + public static final double DEFAULT_PARTICLE_DISTANCE = 75.0; + + public ParticleUtil() { + } + + public static void spawnParticleEffect(@Nonnull String name, @Nonnull Vector3d position, @Nonnull ComponentAccessor componentAccessor) { + SpatialResource, EntityStore> playerSpatialResource = componentAccessor.getResource(EntityModule.get().getPlayerSpatialResourceType()); + ObjectList> playerRefs = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(position, 75.0, playerRefs); + spawnParticleEffect(name, position.getX(), position.getY(), position.getZ(), null, playerRefs, componentAccessor); + } + + public static void spawnParticleEffect( + @Nonnull String name, @Nonnull Vector3d position, @Nonnull List> playerRefs, @Nonnull ComponentAccessor componentAccessor + ) { + spawnParticleEffect(name, position.getX(), position.getY(), position.getZ(), null, playerRefs, componentAccessor); + } + + public static void spawnParticleEffect( + @Nonnull String name, + @Nonnull Vector3d position, + @Nullable Ref sourceRef, + @Nonnull List> playerRefs, + @Nonnull ComponentAccessor componentAccessor + ) { + spawnParticleEffect(name, position.getX(), position.getY(), position.getZ(), sourceRef, playerRefs, componentAccessor); + } + + public static void spawnParticleEffect( + @Nonnull String name, + @Nonnull Vector3d position, + @Nonnull Vector3f rotation, + @Nonnull List> playerRefs, + @Nonnull ComponentAccessor componentAccessor + ) { + spawnParticleEffect( + name, + position.getX(), + position.getY(), + position.getZ(), + rotation.getYaw(), + rotation.getPitch(), + rotation.getRoll(), + null, + playerRefs, + componentAccessor + ); + } + + public static void spawnParticleEffect( + @Nonnull String name, + @Nonnull Vector3d position, + @Nonnull Vector3f rotation, + @Nullable Ref sourceRef, + @Nonnull List> playerRefs, + @Nonnull ComponentAccessor componentAccessor + ) { + spawnParticleEffect( + name, + position.getX(), + position.getY(), + position.getZ(), + rotation.getYaw(), + rotation.getPitch(), + rotation.getRoll(), + sourceRef, + playerRefs, + componentAccessor + ); + } + + public static void spawnParticleEffect( + String name, + @Nonnull Vector3d position, + float yaw, + float pitch, + float roll, + @Nullable Ref sourceRef, + @Nonnull List> playerRefs, + @Nonnull ComponentAccessor componentAccessor + ) { + spawnParticleEffect(name, position.getX(), position.getY(), position.getZ(), yaw, pitch, roll, sourceRef, playerRefs, componentAccessor); + } + + public static void spawnParticleEffect( + @Nonnull String name, + @Nonnull Vector3d position, + float yaw, + float pitch, + float roll, + float scale, + @Nonnull Color color, + @Nonnull List> playerRefs, + @Nonnull ComponentAccessor componentAccessor + ) { + spawnParticleEffect(name, position.getX(), position.getY(), position.getZ(), yaw, pitch, roll, scale, color, null, playerRefs, componentAccessor); + } + + public static void spawnParticleEffect( + @Nonnull WorldParticle particles, + @Nonnull Vector3d position, + @Nonnull List> playerRefs, + @Nonnull ComponentAccessor componentAccessor + ) { + spawnParticleEffect(particles, position, null, playerRefs, componentAccessor); + } + + public static void spawnParticleEffect( + @Nonnull WorldParticle particles, + @Nonnull Vector3d position, + @Nullable Ref sourceRef, + @Nonnull List> playerRefs, + @Nonnull ComponentAccessor componentAccessor + ) { + spawnParticleEffect(particles, position, 0.0F, 0.0F, 0.0F, sourceRef, playerRefs, componentAccessor); + } + + public static void spawnParticleEffects( + @Nonnull WorldParticle[] particles, + @Nonnull Vector3d position, + @Nullable Ref sourceRef, + @Nonnull List> playerRefs, + @Nonnull ComponentAccessor componentAccessor + ) { + for (WorldParticle particle : particles) { + spawnParticleEffect(particle, position, sourceRef, playerRefs, componentAccessor); + } + } + + public static void spawnParticleEffect( + @Nonnull WorldParticle particles, + @Nonnull Vector3d position, + float yaw, + float pitch, + float roll, + @Nullable Ref sourceRef, + @Nonnull List> playerRefs, + @Nonnull ComponentAccessor componentAccessor + ) { + com.hypixel.hytale.protocol.Vector3f positionOffset = particles.getPositionOffset(); + if (positionOffset != null) { + Vector3d offset = new Vector3d(positionOffset.x, positionOffset.y, positionOffset.z); + offset.rotateY(yaw); + offset.rotateX(pitch); + offset.rotateZ(roll); + position.x = position.x + offset.x; + position.y = position.y + offset.y; + position.z = position.z + offset.z; + } + + Direction rotationOffset = particles.getRotationOffset(); + if (rotationOffset != null) { + yaw += (float)Math.toRadians(rotationOffset.yaw); + pitch += (float)Math.toRadians(rotationOffset.pitch); + roll += (float)Math.toRadians(rotationOffset.roll); + } + + String systemId = particles.getSystemId(); + if (systemId != null) { + spawnParticleEffect( + systemId, + position.getX(), + position.getY(), + position.getZ(), + yaw, + pitch, + roll, + particles.getScale(), + particles.getColor(), + sourceRef, + playerRefs, + componentAccessor + ); + } + } + + public static void spawnParticleEffect( + @Nonnull String name, double x, double y, double z, @Nonnull List> playerRefs, @Nonnull ComponentAccessor componentAccessor + ) { + spawnParticleEffect(name, x, y, z, null, playerRefs, componentAccessor); + } + + public static void spawnParticleEffect( + @Nonnull String name, + double x, + double y, + double z, + @Nullable Ref sourceRef, + @Nonnull List> playerRefs, + @Nonnull ComponentAccessor componentAccessor + ) { + spawnParticleEffect(name, x, y, z, 0.0F, 0.0F, 0.0F, 1.0F, null, sourceRef, playerRefs, componentAccessor); + } + + public static void spawnParticleEffect( + @Nonnull String name, + double x, + double y, + double z, + float rotationYaw, + float rotationPitch, + float rotationRoll, + @Nullable Ref sourceRef, + @Nonnull List> playerRefs, + @Nonnull ComponentAccessor componentAccessor + ) { + spawnParticleEffect(name, x, y, z, rotationYaw, rotationPitch, rotationRoll, 1.0F, null, sourceRef, playerRefs, componentAccessor); + } + + public static void spawnParticleEffect( + @Nonnull String name, + double x, + double y, + double z, + float rotationYaw, + float rotationPitch, + float rotationRoll, + float scale, + @Nullable Color color, + @Nullable Ref sourceRef, + @Nonnull List> playerRefs, + @Nonnull ComponentAccessor componentAccessor + ) { + Direction rotation = null; + if (rotationYaw != 0.0F || rotationPitch != 0.0F || rotationRoll != 0.0F) { + rotation = new Direction(rotationYaw, rotationPitch, rotationRoll); + } + + SpawnParticleSystem packet = new SpawnParticleSystem(name, new Position(x, y, z), rotation, scale, color); + ComponentType playerRefComponentType = PlayerRef.getComponentType(); + + for (Ref playerRef : playerRefs) { + if (playerRef.isValid() && (sourceRef == null || !playerRef.equals(sourceRef))) { + PlayerRef playerRefComponent = componentAccessor.getComponent(playerRef, playerRefComponentType); + + assert playerRefComponent != null; + + playerRefComponent.getPacketHandler().writeNoCache(packet); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/PlaceBlockSettings.java b/src/com/hypixel/hytale/server/core/universe/world/PlaceBlockSettings.java new file mode 100644 index 0000000..43e0005 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/PlaceBlockSettings.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.core.universe.world; + +public class PlaceBlockSettings { + public static final int NONE = 0; + public static final int PERFORM_BLOCK_UPDATE = 2; + public static final int UPDATE_CONNECTIONS = 8; + + public PlaceBlockSettings() { + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/PlayerUtil.java b/src/com/hypixel/hytale/server/core/universe/world/PlayerUtil.java new file mode 100644 index 0000000..48a3195 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/PlayerUtil.java @@ -0,0 +1,119 @@ +package com.hypixel.hytale.server.core.universe.world; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.function.consumer.TriConsumer; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule; +import com.hypixel.hytale.server.core.entity.entities.player.HiddenPlayersManager; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSkinComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.EntityTrackerSystems; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PlayerUtil { + public PlayerUtil() { + } + + public static void forEachPlayerThatCanSeeEntity( + @Nonnull Ref ref, + @Nonnull TriConsumer, PlayerRef, ComponentAccessor> consumer, + @Nonnull ComponentAccessor componentAccessor + ) { + Store store = componentAccessor.getExternalData().getStore(); + ComponentType playerRefComponentType = PlayerRef.getComponentType(); + store.forEachChunk(playerRefComponentType, (archetypeChunk, commandBuffer) -> { + for (int index = 0; index < archetypeChunk.size(); index++) { + EntityTrackerSystems.EntityViewer entityViewerComponent = archetypeChunk.getComponent(index, EntityTrackerSystems.EntityViewer.getComponentType()); + if (entityViewerComponent != null && entityViewerComponent.visible.contains(ref)) { + Ref targetPlayerRef = archetypeChunk.getReferenceTo(index); + PlayerRef targetPlayerRefComponent = archetypeChunk.getComponent(index, playerRefComponentType); + + assert targetPlayerRefComponent != null; + + consumer.accept(targetPlayerRef, targetPlayerRefComponent, commandBuffer); + } + } + }); + } + + public static void forEachPlayerThatCanSeeEntity( + @Nonnull Ref ref, + @Nonnull TriConsumer, PlayerRef, ComponentAccessor> consumer, + @Nullable Ref ignoredPlayerRef, + @Nonnull ComponentAccessor componentAccessor + ) { + Store store = componentAccessor.getExternalData().getStore(); + ComponentType playerRefComponentType = PlayerRef.getComponentType(); + store.forEachChunk(playerRefComponentType, (archetypeChunk, commandBuffer) -> { + for (int index = 0; index < archetypeChunk.size(); index++) { + EntityTrackerSystems.EntityViewer entityViewerComponent = archetypeChunk.getComponent(index, EntityTrackerSystems.EntityViewer.getComponentType()); + if (entityViewerComponent != null && entityViewerComponent.visible.contains(ref)) { + Ref targetRef = archetypeChunk.getReferenceTo(index); + if (!targetRef.equals(ignoredPlayerRef)) { + PlayerRef targetPlayerRefComponent = archetypeChunk.getComponent(index, playerRefComponentType); + + assert targetPlayerRefComponent != null; + + consumer.accept(targetRef, targetPlayerRefComponent, commandBuffer); + } + } + } + }); + } + + public static void broadcastMessageToPlayers(@Nullable UUID sourcePlayerUuid, @Nonnull Message message, @Nonnull Store store) { + World world = store.getExternalData().getWorld(); + + for (PlayerRef targetPlayerRef : world.getPlayerRefs()) { + HiddenPlayersManager targetHiddenPlayersManager = targetPlayerRef.getHiddenPlayersManager(); + if (sourcePlayerUuid == null || !targetHiddenPlayersManager.isPlayerHidden(sourcePlayerUuid)) { + targetPlayerRef.sendMessage(message); + } + } + } + + public static void broadcastPacketToPlayers(@Nonnull ComponentAccessor componentAccessor, @Nonnull Packet packet) { + World world = componentAccessor.getExternalData().getWorld(); + + for (PlayerRef targetPlayerRef : world.getPlayerRefs()) { + targetPlayerRef.getPacketHandler().write(packet); + } + } + + public static void broadcastPacketToPlayersNoCache(@Nonnull ComponentAccessor componentAccessor, @Nonnull Packet packet) { + World world = componentAccessor.getExternalData().getWorld(); + + for (PlayerRef targetPlayerRef : world.getPlayerRefs()) { + targetPlayerRef.getPacketHandler().writeNoCache(packet); + } + } + + public static void broadcastPacketToPlayers(@Nonnull ComponentAccessor componentAccessor, @Nonnull Packet... packets) { + World world = componentAccessor.getExternalData().getWorld(); + + for (PlayerRef targetPlayerRef : world.getPlayerRefs()) { + targetPlayerRef.getPacketHandler().write(packets); + } + } + + @Deprecated(forRemoval = true) + public static void resetPlayerModel(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + PlayerSkinComponent playerSkinComponent = componentAccessor.getComponent(ref, PlayerSkinComponent.getComponentType()); + if (playerSkinComponent != null) { + playerSkinComponent.setNetworkOutdated(); + Model newModel = CosmeticsModule.get().createModel(playerSkinComponent.getPlayerSkin()); + if (newModel != null) { + componentAccessor.putComponent(ref, ModelComponent.getComponentType(), new ModelComponent(newModel)); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/SetBlockSettings.java b/src/com/hypixel/hytale/server/core/universe/world/SetBlockSettings.java new file mode 100644 index 0000000..776cf10 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/SetBlockSettings.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.server.core.universe.world; + +public class SetBlockSettings { + public static final int NONE = 0; + public static final int NO_NOTIFY = 1; + public static final int NO_UPDATE_STATE = 2; + public static final int NO_SEND_PARTICLES = 4; + public static final int NO_SET_FILLER = 8; + public static final int NO_BREAK_FILLER = 16; + public static final int PHYSICS = 32; + public static final int FORCE_CHANGED = 64; + public static final int NO_UPDATE_NEIGHBOR_CONNECTIONS = 128; + public static final int PERFORM_BLOCK_UPDATE = 256; + public static final int NO_UPDATE_HEIGHTMAP = 512; + public static final int NO_SEND_AUDIO = 1024; + public static final int NO_DROP_ITEMS = 2048; + + public SetBlockSettings() { + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/SoundUtil.java b/src/com/hypixel/hytale/server/core/universe/world/SoundUtil.java new file mode 100644 index 0000000..e3d684c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/SoundUtil.java @@ -0,0 +1,286 @@ +package com.hypixel.hytale.server.core.universe.world; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.Position; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.world.PlaySoundEvent2D; +import com.hypixel.hytale.protocol.packets.world.PlaySoundEvent3D; +import com.hypixel.hytale.protocol.packets.world.PlaySoundEventEntity; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SoundUtil { + public SoundUtil() { + } + + public static void playSoundEventEntity(int soundEventIndex, int networkId, @Nonnull ComponentAccessor componentAccessor) { + playSoundEventEntity(soundEventIndex, networkId, 1.0F, 1.0F, componentAccessor); + } + + public static void playSoundEventEntity( + int soundEventIndex, int networkId, float volumeModifier, float pitchModifier, @Nonnull ComponentAccessor componentAccessor + ) { + if (soundEventIndex != 0) { + PlayerUtil.broadcastPacketToPlayers(componentAccessor, new PlaySoundEventEntity(soundEventIndex, networkId, volumeModifier, pitchModifier)); + } + } + + public static void playSoundEvent2dToPlayer(@Nonnull PlayerRef playerRefComponent, int soundEventIndex, @Nonnull SoundCategory soundCategory) { + playSoundEvent2dToPlayer(playerRefComponent, soundEventIndex, soundCategory, 1.0F, 1.0F); + } + + public static void playSoundEvent2dToPlayer( + @Nonnull PlayerRef playerRefComponent, int soundEventIndex, @Nonnull SoundCategory soundCategory, float volumeModifier, float pitchModifier + ) { + if (soundEventIndex != 0) { + playerRefComponent.getPacketHandler().write(new PlaySoundEvent2D(soundEventIndex, soundCategory, volumeModifier, pitchModifier)); + } + } + + public static void playSoundEvent2d(int soundEventIndex, @Nonnull SoundCategory soundCategory, @Nonnull ComponentAccessor componentAccessor) { + playSoundEvent2d(soundEventIndex, soundCategory, 1.0F, 1.0F, componentAccessor); + } + + public static void playSoundEvent2d( + int soundEventIndex, + @Nonnull SoundCategory soundCategory, + float volumeModifier, + float pitchModifier, + @Nonnull ComponentAccessor componentAccessor + ) { + if (soundEventIndex != 0) { + PlayerUtil.broadcastPacketToPlayers(componentAccessor, new PlaySoundEvent2D(soundEventIndex, soundCategory, volumeModifier, pitchModifier)); + } + } + + public static void playSoundEvent2d( + @Nonnull Ref ref, int soundEventIndex, @Nonnull SoundCategory soundCategory, @Nonnull ComponentAccessor componentAccessor + ) { + playSoundEvent2d(ref, soundEventIndex, soundCategory, 1.0F, 1.0F, componentAccessor); + } + + public static void playSoundEvent2d( + @Nonnull Ref ref, + int soundEventIndex, + @Nonnull SoundCategory soundCategory, + float volumeModifier, + float pitchModifier, + @Nonnull ComponentAccessor componentAccessor + ) { + if (soundEventIndex != 0) { + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + if (playerRefComponent != null) { + playerRefComponent.getPacketHandler().write(new PlaySoundEvent2D(soundEventIndex, soundCategory, volumeModifier, pitchModifier)); + } + } + } + + public static void playSoundEvent3d( + int soundEventIndex, @Nonnull SoundCategory soundCategory, double x, double y, double z, @Nonnull ComponentAccessor componentAccessor + ) { + playSoundEvent3d(soundEventIndex, soundCategory, x, y, z, 1.0F, 1.0F, componentAccessor); + } + + public static void playSoundEvent3d( + int soundEventIndex, + @Nonnull SoundCategory soundCategory, + double x, + double y, + double z, + float volumeModifier, + float pitchModifier, + @Nonnull ComponentAccessor componentAccessor + ) { + if (soundEventIndex != 0) { + SoundEvent soundEvent = SoundEvent.getAssetMap().getAsset(soundEventIndex); + if (soundEvent != null) { + PlaySoundEvent3D packet = new PlaySoundEvent3D(soundEventIndex, soundCategory, new Position(x, y, z), volumeModifier, pitchModifier); + Vector3d position = new Vector3d(x, y, z); + SpatialResource, EntityStore> playerSpatialResource = componentAccessor.getResource( + EntityModule.get().getPlayerSpatialResourceType() + ); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(position, soundEvent.getMaxDistance(), results); + + for (Ref playerRef : results) { + if (playerRef != null && playerRef.isValid()) { + PlayerRef playerRefComponent = componentAccessor.getComponent(playerRef, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerRefComponent.getPacketHandler().write(packet); + } + } + } + } + } + + public static void playSoundEvent3d( + int soundEventIndex, @Nonnull SoundCategory soundCategory, @Nonnull Vector3d position, @Nonnull ComponentAccessor componentAccessor + ) { + playSoundEvent3d(soundEventIndex, soundCategory, position.getX(), position.getY(), position.getZ(), componentAccessor); + } + + public static void playSoundEvent3d( + int soundEventIndex, + @Nonnull SoundCategory soundCategory, + double x, + double y, + double z, + @Nonnull Predicate> shouldHear, + @Nonnull ComponentAccessor componentAccessor + ) { + playSoundEvent3d(soundEventIndex, soundCategory, x, y, z, 1.0F, 1.0F, shouldHear, componentAccessor); + } + + public static void playSoundEvent3d( + int soundEventIndex, + @Nonnull SoundCategory soundCategory, + double x, + double y, + double z, + float volumeModifier, + float pitchModifier, + @Nonnull Predicate> shouldHear, + @Nonnull ComponentAccessor componentAccessor + ) { + if (soundEventIndex != 0) { + SoundEvent soundEvent = SoundEvent.getAssetMap().getAsset(soundEventIndex); + if (soundEvent != null) { + PlaySoundEvent3D packet = new PlaySoundEvent3D(soundEventIndex, soundCategory, new Position(x, y, z), volumeModifier, pitchModifier); + SpatialResource, EntityStore> playerSpatialResource = componentAccessor.getResource( + EntityModule.get().getPlayerSpatialResourceType() + ); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(new Vector3d(x, y, z), soundEvent.getMaxDistance(), results); + + for (Ref playerRef : results) { + if (playerRef != null && playerRef.isValid() && shouldHear.test(playerRef)) { + PlayerRef playerRefComponent = componentAccessor.getComponent(playerRef, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerRefComponent.getPacketHandler().write(packet); + } + } + } + } + } + + public static void playSoundEvent3d( + @Nullable Ref sourceRef, int soundEventIndex, @Nonnull Vector3d pos, @Nonnull ComponentAccessor componentAccessor + ) { + playSoundEvent3d(sourceRef, soundEventIndex, pos.getX(), pos.getY(), pos.getZ(), componentAccessor); + } + + public static void playSoundEvent3d( + @Nullable Ref sourceRef, int soundEventIndex, double x, double y, double z, @Nonnull ComponentAccessor componentAccessor + ) { + playSoundEvent3d(sourceRef, soundEventIndex, x, y, z, false, componentAccessor); + } + + public static void playSoundEvent3d( + @Nullable Ref sourceRef, + int soundEventIndex, + @Nonnull Vector3d position, + boolean ignoreSource, + @Nonnull ComponentAccessor componentAccessor + ) { + playSoundEvent3d(sourceRef, soundEventIndex, position.getX(), position.getY(), position.getZ(), ignoreSource, componentAccessor); + } + + public static void playSoundEvent3d( + @Nullable Ref sourceRef, + int soundEventIndex, + double x, + double y, + double z, + boolean ignoreSource, + @Nonnull ComponentAccessor componentAccessor + ) { + Entity sourceEntity = sourceRef != null ? EntityUtils.getEntity(sourceRef, componentAccessor) : null; + playSoundEvent3d(soundEventIndex, x, y, z, playerRef -> { + if (sourceEntity == null) { + return true; + } else { + return ignoreSource && sourceRef.equals(playerRef) ? false : !sourceEntity.isHiddenFromLivingEntity(sourceRef, playerRef, componentAccessor); + } + }, componentAccessor); + } + + public static void playSoundEvent3d( + int soundEventIndex, + double x, + double y, + double z, + @Nonnull Predicate> shouldHear, + @Nonnull ComponentAccessor componentAccessor + ) { + playSoundEvent3d(soundEventIndex, SoundCategory.SFX, x, y, z, shouldHear, componentAccessor); + } + + public static void playSoundEvent3dToPlayer( + @Nullable Ref playerRef, + int soundEventIndex, + @Nonnull SoundCategory soundCategory, + double x, + double y, + double z, + @Nonnull ComponentAccessor componentAccessor + ) { + playSoundEvent3dToPlayer(playerRef, soundEventIndex, soundCategory, x, y, z, 1.0F, 1.0F, componentAccessor); + } + + public static void playSoundEvent3dToPlayer( + @Nullable Ref playerRef, + int soundEventIndex, + @Nonnull SoundCategory soundCategory, + double x, + double y, + double z, + float volumeModifier, + float pitchModifier, + @Nonnull ComponentAccessor componentAccessor + ) { + if (playerRef != null && soundEventIndex != 0) { + SoundEvent soundEventAsset = SoundEvent.getAssetMap().getAsset(soundEventIndex); + if (soundEventAsset != null) { + float maxDistance = soundEventAsset.getMaxDistance(); + TransformComponent transformComponent = componentAccessor.getComponent(playerRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + if (transformComponent.getPosition().distanceSquaredTo(x, y, z) <= maxDistance * maxDistance) { + PlayerRef playerRefComponent = componentAccessor.getComponent(playerRef, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerRefComponent.getPacketHandler() + .write(new PlaySoundEvent3D(soundEventIndex, soundCategory, new Position(x, y, z), volumeModifier, pitchModifier)); + } + } + } + } + + public static void playSoundEvent3dToPlayer( + @Nullable Ref playerRef, + int soundEventIndex, + @Nonnull SoundCategory soundCategory, + @Nonnull Vector3d position, + @Nonnull ComponentAccessor componentAccessor + ) { + playSoundEvent3dToPlayer(playerRef, soundEventIndex, soundCategory, position.x, position.y, position.z, componentAccessor); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/SpawnUtil.java b/src/com/hypixel/hytale/server/core/universe/world/SpawnUtil.java new file mode 100644 index 0000000..8fd4c25 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/SpawnUtil.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.core.universe.world; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class SpawnUtil { + public SpawnUtil() { + } + + @Nullable + public static TransformComponent applyFirstSpawnTransform( + @Nonnull Holder holder, @Nonnull World world, @Nonnull WorldConfig worldConfig, @Nonnull UUID playerUuid + ) { + ISpawnProvider spawnProvider = worldConfig.getSpawnProvider(); + if (spawnProvider == null) { + return null; + } else { + Transform spawnPoint = spawnProvider.getSpawnPoint(world, playerUuid); + Vector3f bodyRotation = new Vector3f(0.0F, spawnPoint.getRotation().getYaw(), 0.0F); + TransformComponent transformComponent = new TransformComponent(spawnPoint.getPosition(), bodyRotation); + holder.addComponent(TransformComponent.getComponentType(), transformComponent); + HeadRotation headRotationComponent = holder.ensureAndGetComponent(HeadRotation.getComponentType()); + headRotationComponent.teleportRotation(spawnPoint.getRotation()); + return transformComponent; + } + } + + public static void applyTransform(@Nonnull Holder holder, @Nonnull Transform transform) { + TransformComponent transformComponent = holder.getComponent(TransformComponent.getComponentType()); + + assert transformComponent != null; + + transformComponent.setPosition(transform.getPosition()); + transformComponent.getRotation().setYaw(transform.getRotation().getYaw()); + HeadRotation headRotationComponent = holder.ensureAndGetComponent(HeadRotation.getComponentType()); + headRotationComponent.teleportRotation(transform.getRotation()); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/ValidationOption.java b/src/com/hypixel/hytale/server/core/universe/world/ValidationOption.java new file mode 100644 index 0000000..a5810f6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/ValidationOption.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.core.universe.world; + +public enum ValidationOption { + PHYSICS, + BLOCKS, + BLOCK_STATES, + ENTITIES, + BLOCK_FILLER; + + private ValidationOption() { + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/World.java b/src/com/hypixel/hytale/server/core/universe/world/World.java new file mode 100644 index 0000000..69fd762 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/World.java @@ -0,0 +1,1222 @@ +package com.hypixel.hytale.server.core.universe.world; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.IResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.data.unknown.UnknownComponents; +import com.hypixel.hytale.event.EventRegistry; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.math.block.BlockUtil; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.metrics.ExecutorMetricsRegistry; +import com.hypixel.hytale.metrics.metric.HistoricMetric; +import com.hypixel.hytale.protocol.packets.entities.SetEntitySeed; +import com.hypixel.hytale.protocol.packets.player.JoinWorld; +import com.hypixel.hytale.protocol.packets.player.SetClientId; +import com.hypixel.hytale.protocol.packets.setup.ClientFeature; +import com.hypixel.hytale.protocol.packets.setup.SetTimeDilation; +import com.hypixel.hytale.protocol.packets.setup.SetUpdateRate; +import com.hypixel.hytale.protocol.packets.setup.UpdateFeatures; +import com.hypixel.hytale.protocol.packets.setup.ViewRadius; +import com.hypixel.hytale.protocol.packets.world.ServerSetPaused; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.ShutdownReason; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.gameplay.CombatConfig; +import com.hypixel.hytale.server.core.asset.type.gameplay.DeathConfig; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; +import com.hypixel.hytale.server.core.console.ConsoleModule; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData; +import com.hypixel.hytale.server.core.event.events.player.AddPlayerToWorldEvent; +import com.hypixel.hytale.server.core.event.events.player.DrainPlayerFromWorldEvent; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; +import com.hypixel.hytale.server.core.modules.entity.tracker.LegacyEntityTrackerSystems; +import com.hypixel.hytale.server.core.modules.time.TimeResource; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.IPrefabBuffer; +import com.hypixel.hytale.server.core.receiver.IMessageReceiver; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.accessor.ChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkFlag; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection; +import com.hypixel.hytale.server.core.universe.world.events.StartWorldEvent; +import com.hypixel.hytale.server.core.universe.world.lighting.ChunkLightingManager; +import com.hypixel.hytale.server.core.universe.world.path.WorldPathConfig; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.resources.DiskResourceStorageProvider; +import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen; +import com.hypixel.hytale.server.core.universe.world.worldgen.WorldGenLoadException; +import com.hypixel.hytale.server.core.universe.world.worldmap.IWorldMap; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapLoadException; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import com.hypixel.hytale.server.core.util.MessageUtil; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import com.hypixel.hytale.server.core.util.thread.TickingThread; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.IntUnaryOperator; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class World extends TickingThread implements Executor, ExecutorMetricsRegistry.ExecutorMetric, ChunkAccessor, IWorldChunks, IMessageReceiver { + public static final float SAVE_INTERVAL = 10.0F; + public static final String DEFAULT = "default"; + @Nonnull + public static final ExecutorMetricsRegistry METRICS_REGISTRY = new ExecutorMetricsRegistry() + .register("Name", world -> world.name, Codec.STRING) + .register("Alive", world -> world.alive.get(), Codec.BOOLEAN) + .register("TickLength", TickingThread::getBufferedTickLengthMetricSet, HistoricMetric.METRICS_CODEC) + .register("EntityStore", World::getEntityStore, EntityStore.METRICS_REGISTRY) + .register("ChunkStore", World::getChunkStore, ChunkStore.METRICS_REGISTRY); + @Nonnull + private final HytaleLogger logger; + @Nonnull + private final String name; + @Nonnull + private final Path savePath; + @Nonnull + private final WorldConfig worldConfig; + @Nonnull + private final ChunkStore chunkStore = new ChunkStore(this); + @Nonnull + private final EntityStore entityStore = new EntityStore(this); + @Nonnull + private final ChunkLightingManager chunkLighting; + @Nonnull + private final WorldMapManager worldMapManager; + private WorldPathConfig worldPathConfig; + private final AtomicBoolean acceptingTasks = new AtomicBoolean(true); + @Nonnull + private final Deque taskQueue = new LinkedBlockingDeque<>(); + @Nonnull + private final AtomicBoolean alive = new AtomicBoolean(true); + @Nonnull + private final EventRegistry eventRegistry = new EventRegistry(new CopyOnWriteArrayList<>(), () -> true, null, HytaleServer.get().getEventBus()); + @Nonnull + private final WorldNotificationHandler notificationHandler = new WorldNotificationHandler(this); + private boolean isTicking; + private boolean isPaused; + private long tick; + @Nonnull + private final Random random = new Random(); + @Nonnull + private final AtomicInteger entitySeed = new AtomicInteger(); + @Nonnull + private final Map players = new ConcurrentHashMap<>(); + @Nonnull + private final Collection playerRefs = Collections.unmodifiableCollection(this.players.values()); + @Nonnull + private final Map features = Collections.synchronizedMap(new EnumMap<>(ClientFeature.class)); + private volatile boolean gcHasRun; + + public World(@Nonnull String name, @Nonnull Path savePath, @Nonnull WorldConfig worldConfig) throws IOException { + super("WorldThread - " + name); + this.name = name; + this.logger = HytaleLogger.get("World|" + name); + this.savePath = savePath; + this.worldConfig = worldConfig; + this.logger + .at(Level.INFO) + .log( + "Loading world '%s' with generator type: '%s' and chunk storage: '%s'...", + name, + worldConfig.getWorldGenProvider(), + worldConfig.getChunkStorageProvider() + ); + this.worldMapManager = new WorldMapManager(this); + this.chunkLighting = new ChunkLightingManager(this); + this.isTicking = worldConfig.isTicking(); + + for (ClientFeature feature : ClientFeature.VALUES) { + this.features.put(feature, true); + } + + CombatConfig combatConfig = this.getGameplayConfig().getCombatConfig(); + this.features.put(ClientFeature.DisplayHealthBars, combatConfig.isDisplayHealthBars()); + this.features.put(ClientFeature.DisplayCombatText, combatConfig.isDisplayCombatText()); + this.logger.at(Level.INFO).log("Added world '%s' - Seed: %s, GameTime: %s", name, Long.toString(worldConfig.getSeed()), worldConfig.getGameTime()); + } + + @Nonnull + public CompletableFuture init() { + CompletableFuture savingFuture; + if (this.worldConfig.isSavingConfig()) { + savingFuture = Universe.get().getWorldConfigProvider().save(this.savePath, this.worldConfig, this); + } else { + savingFuture = CompletableFuture.completedFuture(null); + } + + CompletableFuture loadWorldGen = CompletableFuture.supplyAsync(() -> { + try { + IWorldGen worldGen = this.worldConfig.getWorldGenProvider().getGenerator(); + this.chunkStore.setGenerator(worldGen); + this.worldConfig.setDefaultSpawnProvider(worldGen); + IWorldMap worldMap = this.worldConfig.getWorldMapProvider().getGenerator(this); + this.worldMapManager.setGenerator(worldMap); + return this; + } catch (WorldGenLoadException var3x) { + if (this.name.equals(HytaleServer.get().getConfig().getDefaults().getWorld())) { + HytaleServer.get().shutdownServer(ShutdownReason.WORLD_GEN.withMessage(var3x.getTraceMessage("\n"))); + } + + throw new SkipSentryException("Failed to load WorldGen!", var3x); + } catch (WorldMapLoadException var4) { + if (this.name.equals(HytaleServer.get().getConfig().getDefaults().getWorld())) { + HytaleServer.get().shutdownServer(ShutdownReason.WORLD_GEN.withMessage(var4.getTraceMessage("\n"))); + } + + throw new SkipSentryException("Failed to load WorldGen!", var4); + } + }); + CompletableFuture loadPaths = WorldPathConfig.load(this).thenAccept(config -> this.worldPathConfig = config); + return this.worldConfig.getSpawnProvider() != null + ? CompletableFuture.allOf(savingFuture, loadPaths).thenApply(v -> this) + : CompletableFuture.allOf(savingFuture, loadPaths).thenCompose(v -> loadWorldGen); + } + + @Override + protected void onStart() { + DiskResourceStorageProvider.migrateFiles(this); + IResourceStorage resourceStorage = this.worldConfig.getResourceStorageProvider().getResourceStorage(this); + this.chunkStore.start(resourceStorage); + this.entityStore.start(resourceStorage); + this.chunkLighting.start(); + this.worldMapManager.updateTickingState(this.worldMapManager.isStarted()); + Path rffPath = this.savePath.resolve("rff"); + if (Files.exists(rffPath)) { + throw new RuntimeException(rffPath + " directory exists but this version of the server doesn't support migrating RFF worlds!"); + } else { + IEventDispatcher dispatcher = HytaleServer.get().getEventBus().dispatchFor(StartWorldEvent.class, this.name); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new StartWorldEvent(this)); + } + } + } + + public void stopIndividualWorld() { + this.logger.at(Level.INFO).log("Removing individual world: %s", this.name); + World defaultWorld = Universe.get().getDefaultWorld(); + if (defaultWorld != null) { + this.drainPlayersTo(defaultWorld).join(); + } else { + for (PlayerRef playerRef : this.players.values()) { + playerRef.getPacketHandler().disconnect("The world you were in was shutdown and there was no default world to move you to!"); + } + } + + if (this.alive.getAndSet(false)) { + try { + super.stop(); + } catch (Throwable var4) { + this.logger.at(Level.SEVERE).withCause(var4).log("Exception while shutting down world:"); + } + } + } + + public void validateDeleteOnRemove() { + if (this.worldConfig.isDeleteOnRemove()) { + try { + FileUtil.deleteDirectory(this.getSavePath()); + } catch (Throwable var2) { + this.logger.at(Level.SEVERE).withCause(var2).log("Exception while deleting world on remove:"); + } + } + } + + @Override + protected boolean isIdle() { + return this.players.isEmpty(); + } + + @Override + protected void tick(float dt) { + if (this.alive.get()) { + TimeResource worldTimeResource = this.entityStore.getStore().getResource(TimeResource.getResourceType()); + dt *= worldTimeResource.getTimeDilationModifier(); + AssetRegistry.ASSET_LOCK.readLock().lock(); + + try { + this.consumeTaskQueue(); + if (!this.isPaused) { + this.entityStore.getStore().tick(dt); + } else { + this.entityStore.getStore().pausedTick(dt); + } + + if (this.isTicking && !this.isPaused) { + this.chunkStore.getStore().tick(dt); + } else { + this.chunkStore.getStore().pausedTick(dt); + } + + this.consumeTaskQueue(); + } finally { + AssetRegistry.ASSET_LOCK.readLock().unlock(); + } + + this.tick++; + } + } + + @Override + protected void onShutdown() { + this.logger.at(Level.INFO).log("Stopping world %s...", this.name); + this.logger.at(Level.INFO).log("Stopping background threads..."); + long start = System.nanoTime(); + + while (this.chunkLighting.interrupt() || this.worldMapManager.interrupt()) { + this.consumeTaskQueue(); + if (System.nanoTime() - start > 5000000000L) { + break; + } + } + + this.chunkLighting.stop(); + this.worldMapManager.stop(); + this.logger.at(Level.INFO).log("Removing players..."); + + for (PlayerRef playerRef : this.playerRefs) { + if (playerRef.getReference() != null) { + playerRef.removeFromStore(); + } + } + + this.consumeTaskQueue(); + this.logger.at(Level.INFO).log("Waiting for loading chunks..."); + this.chunkStore.waitForLoadingChunks(); + + try { + this.logger.at(Level.INFO).log("Shutting down stores..."); + HytaleServer.get().reportSingleplayerStatus("Saving world '" + this.name + "'"); + this.chunkStore.shutdown(); + this.consumeTaskQueue(); + this.entityStore.shutdown(); + this.consumeTaskQueue(); + } finally { + this.logger.at(Level.INFO).log("Saving Config..."); + if (this.worldConfig.isSavingConfig()) { + Universe.get().getWorldConfigProvider().save(this.savePath, this.worldConfig, this).join(); + } + } + + this.acceptingTasks.set(false); + if (this.alive.getAndSet(false)) { + Universe.get().removeWorldExceptionally(this.name); + } + + HytaleServer.get().reportSingleplayerStatus("Closing world '" + this.name + "'"); + } + + @Override + public void setTps(int tps) { + super.setTps(tps); + SetUpdateRate setUpdateRatePacket = new SetUpdateRate(tps); + this.entityStore + .getStore() + .forEachEntityParallel( + PlayerRef.getComponentType(), + (index, archetypeChunk, commandBuffer) -> archetypeChunk.getComponent(index, PlayerRef.getComponentType()) + .getPacketHandler() + .writeNoCache(setUpdateRatePacket) + ); + } + + public static void setTimeDilation(float timeDilationModifier, @Nonnull ComponentAccessor componentAccessor) { + World world = componentAccessor.getExternalData().getWorld(); + if (!(timeDilationModifier <= 0.01) && !(timeDilationModifier > 4.0F)) { + TimeResource worldTimeResource = componentAccessor.getResource(TimeResource.getResourceType()); + worldTimeResource.setTimeDilationModifier(timeDilationModifier); + SetTimeDilation setTimeDilationPacket = new SetTimeDilation(timeDilationModifier); + + for (PlayerRef playerRef : world.playerRefs) { + playerRef.getPacketHandler().writeNoCache(setTimeDilationPacket); + } + } else { + throw new IllegalArgumentException("TimeDilation is out of bounds (<=0.01 or >4)"); + } + } + + @Nonnull + public String getName() { + return this.name; + } + + public boolean isAlive() { + return this.alive.get(); + } + + @Nonnull + public WorldConfig getWorldConfig() { + return this.worldConfig; + } + + @Nonnull + public DeathConfig getDeathConfig() { + DeathConfig override = this.worldConfig.getDeathConfigOverride(); + return override != null ? override : this.getGameplayConfig().getDeathConfig(); + } + + public int getDaytimeDurationSeconds() { + Integer override = this.worldConfig.getDaytimeDurationSecondsOverride(); + return override != null ? override : this.getGameplayConfig().getWorldConfig().getDaytimeDurationSeconds(); + } + + public int getNighttimeDurationSeconds() { + Integer override = this.worldConfig.getNighttimeDurationSecondsOverride(); + return override != null ? override : this.getGameplayConfig().getWorldConfig().getNighttimeDurationSeconds(); + } + + public boolean isTicking() { + return this.isTicking; + } + + public void setTicking(boolean ticking) { + this.isTicking = ticking; + this.worldConfig.setTicking(ticking); + this.worldConfig.markChanged(); + } + + public boolean isPaused() { + return this.isPaused; + } + + public void setPaused(boolean paused) { + if (this.isPaused != paused) { + this.isPaused = paused; + ServerSetPaused setPaused = new ServerSetPaused(paused); + PlayerUtil.broadcastPacketToPlayersNoCache(this.entityStore.getStore(), setPaused); + } + } + + public long getTick() { + return this.tick; + } + + @Nonnull + public HytaleLogger getLogger() { + return this.logger; + } + + public boolean isCompassUpdating() { + return this.worldConfig.isCompassUpdating(); + } + + public void setCompassUpdating(boolean compassUpdating) { + boolean before = this.worldMapManager.shouldTick(); + this.worldConfig.setCompassUpdating(compassUpdating); + this.worldConfig.markChanged(); + this.worldMapManager.updateTickingState(before); + } + + public void getBlockBulkRelative( + @Nonnull Long2ObjectMap blocks, + @Nonnull IntUnaryOperator xConvert, + @Nonnull IntUnaryOperator yConvert, + @Nonnull IntUnaryOperator zConvert, + @Nonnull World.GenericBlockBulkUpdater consumer + ) { + Long2ObjectMap chunks = new Long2ObjectOpenHashMap<>(); + blocks.forEach((a, b) -> { + int localX = BlockUtil.unpackX(a); + int localY = BlockUtil.unpackY(a); + int localZ = BlockUtil.unpackZ(a); + int x = xConvert.applyAsInt(localX); + int y = yConvert.applyAsInt(localY); + int z = zConvert.applyAsInt(localZ); + long chunkIndex = ChunkUtil.indexChunkFromBlock(x, z); + WorldChunk chunk = chunks.get(chunkIndex); + if (chunk == null) { + chunk = this.getNonTickingChunk(chunkIndex); + chunks.put(chunkIndex, chunk); + } + + consumer.apply(this, (T)b, chunkIndex, chunk, x, y, z, localX, localY, localZ); + }); + } + + @Nullable + public WorldChunk loadChunkIfInMemory(long index) { + if (!this.isInThread()) { + return CompletableFuture.supplyAsync(() -> this.loadChunkIfInMemory(index), this).join(); + } else { + Ref reference = this.chunkStore.getChunkReference(index); + if (reference == null) { + return null; + } else { + WorldChunk worldChunkComponent = this.chunkStore.getStore().getComponent(reference, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + worldChunkComponent.setFlag(ChunkFlag.TICKING, true); + return worldChunkComponent; + } + } + } + + @Nullable + public WorldChunk getChunkIfInMemory(long index) { + Ref reference = this.chunkStore.getChunkReference(index); + if (reference == null) { + return null; + } else { + return !this.isInThread() + ? CompletableFuture.supplyAsync(() -> this.getChunkIfInMemory(index), this).join() + : this.chunkStore.getStore().getComponent(reference, WorldChunk.getComponentType()); + } + } + + @Nullable + public WorldChunk getChunkIfLoaded(long index) { + if (!this.isInThread()) { + return CompletableFuture.supplyAsync(() -> this.getChunkIfLoaded(index), this).join(); + } else { + Ref reference = this.chunkStore.getChunkReference(index); + if (reference == null) { + return null; + } else { + WorldChunk worldChunkComponent = this.chunkStore.getStore().getComponent(reference, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + return worldChunkComponent.is(ChunkFlag.TICKING) ? worldChunkComponent : null; + } + } + } + + @Nullable + public WorldChunk getChunkIfNonTicking(long index) { + if (!this.isInThread()) { + return CompletableFuture.supplyAsync(() -> this.getChunkIfNonTicking(index), this).join(); + } else { + Ref reference = this.chunkStore.getChunkReference(index); + if (reference == null) { + return null; + } else { + WorldChunk worldChunkComponent = this.chunkStore.getStore().getComponent(reference, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + return worldChunkComponent.is(ChunkFlag.TICKING) ? null : worldChunkComponent; + } + } + } + + @Nonnull + @Override + public CompletableFuture getChunkAsync(long index) { + return this.chunkStore + .getChunkReferenceAsync(index, 4) + .thenApplyAsync( + reference -> reference == null ? null : this.chunkStore.getStore().getComponent((Ref)reference, WorldChunk.getComponentType()), this + ); + } + + @Nonnull + @Override + public CompletableFuture getNonTickingChunkAsync(long index) { + return this.chunkStore + .getChunkReferenceAsync(index) + .thenApplyAsync( + reference -> reference == null ? null : this.chunkStore.getStore().getComponent((Ref)reference, WorldChunk.getComponentType()), this + ); + } + + @Deprecated(forRemoval = true) + public List getPlayers() { + if (!this.isInThread()) { + return !this.isStarted() ? Collections.emptyList() : CompletableFuture.supplyAsync(this::getPlayers, this).join(); + } else { + ObjectArrayList players = new ObjectArrayList<>(32); + this.entityStore.getStore().forEachChunk(Player.getComponentType(), (archetypeChunk, commandBuffer) -> { + players.ensureCapacity(players.size() + archetypeChunk.size()); + + for (int index = 0; index < archetypeChunk.size(); index++) { + players.add(archetypeChunk.getComponent(index, Player.getComponentType())); + } + }); + return players; + } + } + + @Nullable + @Deprecated + public Entity getEntity(@Nonnull UUID uuid) { + if (!this.isInThread()) { + return CompletableFuture.supplyAsync(() -> this.getEntity(uuid), this).join(); + } else { + Ref reference = this.entityStore.getRefFromUUID(uuid); + return EntityUtils.getEntity(reference, this.entityStore.getStore()); + } + } + + @Nullable + public Ref getEntityRef(@Nonnull UUID uuid) { + return !this.isInThread() + ? CompletableFuture.>supplyAsync(() -> this.getEntityRef(uuid), this).join() + : this.entityStore.getRefFromUUID(uuid); + } + + public int getPlayerCount() { + return this.players.size(); + } + + @Nonnull + public Collection getPlayerRefs() { + return this.playerRefs; + } + + public void trackPlayerRef(@Nonnull PlayerRef playerRef) { + this.players.put(playerRef.getUuid(), playerRef); + } + + public void untrackPlayerRef(@Nonnull PlayerRef playerRef) { + this.players.remove(playerRef.getUuid(), playerRef); + } + + @Deprecated + @Nullable + public T spawnEntity(T entity, @Nonnull Vector3d position, Vector3f rotation) { + return this.addEntity(entity, position, rotation, AddReason.SPAWN); + } + + @Deprecated + @Nullable + public T addEntity(T entity, @Nonnull Vector3d position, @Nullable Vector3f rotation, @Nonnull AddReason reason) { + if (!EntityModule.get().isKnown(entity)) { + throw new IllegalArgumentException("Unknown entity"); + } else if (entity instanceof Player) { + throw new IllegalArgumentException("Entity can't be a Player!"); + } else if (entity.getNetworkId() == -1) { + throw new IllegalArgumentException("Entity id can't be Entity.UNASSIGNED_ID (-1)!"); + } else if (!this.equals(entity.getWorld())) { + throw new IllegalStateException("Expected entity to already have its world set to " + this.getName() + " but it has " + entity.getWorld()); + } else if (entity.getReference() != null && entity.getReference().isValid()) { + throw new IllegalArgumentException("Entity already has a valid EntityReference: " + entity.getReference()); + } else if (position.getY() < -32.0) { + throw new IllegalArgumentException("Unable to spawn entity below the world! -32 < " + position); + } else if (!this.isInThread()) { + this.logger.at(Level.WARNING).withCause(new SkipSentryException()).log("Warning addEntity was called off thread!"); + this.execute(() -> this.addEntity(entity, position, rotation, reason)); + return entity; + } else { + entity.unloadFromWorld(); + Holder holder = entity.toHolder(); + HeadRotation headRotation = holder.ensureAndGetComponent(HeadRotation.getComponentType()); + if (rotation != null) { + headRotation.teleportRotation(rotation); + } + + holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(position, rotation)); + holder.ensureComponent(UUIDComponent.getComponentType()); + this.entityStore.getStore().addEntity(holder, reason); + return entity; + } + } + + @Override + public void sendMessage(@Nonnull Message message) { + if (!this.isInThread()) { + this.execute(() -> this.sendMessage(message)); + } else { + this.entityStore.getStore().forEachEntityParallel(PlayerRef.getComponentType(), (index, archetypeChunk, commandBuffer) -> { + PlayerRef playerRefComponent = archetypeChunk.getComponent(index, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerRefComponent.sendMessage(message); + }); + this.logger.at(Level.INFO).log("[Broadcast] [Message] %s", MessageUtil.toAnsiString(message).toAnsi(ConsoleModule.get().getTerminal())); + } + } + + @Override + public void execute(@Nonnull Runnable command) { + if (!this.acceptingTasks.get()) { + throw new SkipSentryException(new IllegalThreadStateException("World thread is not accepting tasks: " + this.name + ", " + this.getThread())); + } else { + this.taskQueue.offer(command); + } + } + + @Override + public void consumeTaskQueue() { + this.debugAssertInTickingThread(); + int tickStepNanos = this.getTickStepNanos(); + + Runnable runnable; + while ((runnable = this.taskQueue.poll()) != null) { + try { + long before = System.nanoTime(); + runnable.run(); + long after = System.nanoTime(); + long diff = after - before; + if (diff > tickStepNanos) { + this.logger.at(Level.WARNING).log("Task took %s ns: %s", FormatUtil.nanosToString(diff), runnable); + } + } catch (Exception var9) { + this.logger.at(Level.SEVERE).withCause(var9).log("Failed to run task!"); + } + } + } + + @Nonnull + public ChunkStore getChunkStore() { + return this.chunkStore; + } + + @Nonnull + public EntityStore getEntityStore() { + return this.entityStore; + } + + @Nonnull + public ChunkLightingManager getChunkLighting() { + return this.chunkLighting; + } + + @Nonnull + public WorldMapManager getWorldMapManager() { + return this.worldMapManager; + } + + public WorldPathConfig getWorldPathConfig() { + return this.worldPathConfig; + } + + @Nonnull + public WorldNotificationHandler getNotificationHandler() { + return this.notificationHandler; + } + + @Nonnull + public EventRegistry getEventRegistry() { + return this.eventRegistry; + } + + @Nullable + public CompletableFuture addPlayer(@Nonnull PlayerRef playerRef) { + return this.addPlayer(playerRef, null); + } + + @Nullable + public CompletableFuture addPlayer(@Nonnull PlayerRef playerRef, @Nullable Transform transform) { + return this.addPlayer(playerRef, transform, null, null); + } + + @Nullable + public CompletableFuture addPlayer( + @Nonnull PlayerRef playerRef, + @Deprecated(forRemoval = true) @Nullable Transform transform, + @Nullable Boolean clearWorldOverride, + @Nullable Boolean fadeInOutOverride + ) { + if (!this.alive.get()) { + return CompletableFuture.failedFuture(new IllegalStateException("This world has already been shutdown!")); + } else if (playerRef.getReference() != null) { + throw new IllegalStateException("Player is already in a world"); + } else { + PacketHandler packetHandler = playerRef.getPacketHandler(); + if (!packetHandler.stillActive()) { + return null; + } else { + Holder holder = playerRef.getHolder(); + + assert holder != null; + + TransformComponent transformComponent = holder.getComponent(TransformComponent.getComponentType()); + if (transformComponent == null && transform == null) { + transformComponent = SpawnUtil.applyFirstSpawnTransform(holder, this, this.worldConfig, playerRef.getUuid()); + if (transformComponent == null) { + return CompletableFuture.failedFuture(new IllegalStateException("Spawn provider cannot be null for positioning new entities!")); + } + } + + assert transformComponent != null; + + Player playerComponent = holder.getComponent(Player.getComponentType()); + + assert playerComponent != null; + + boolean firstSpawn = !playerComponent.getPlayerConfigData().getPerWorldData().containsKey(this.name); + playerComponent.setFirstSpawn(firstSpawn); + if (transform != null) { + SpawnUtil.applyTransform(holder, transform); + } + + AddPlayerToWorldEvent event = HytaleServer.get() + .getEventBus() + .dispatchFor(AddPlayerToWorldEvent.class, this.name) + .dispatch(new AddPlayerToWorldEvent(holder, this)); + ChunkTracker chunkTrackerComponent = holder.getComponent(ChunkTracker.getComponentType()); + boolean clearWorld = clearWorldOverride != null ? clearWorldOverride : true; + boolean fadeInOut = fadeInOutOverride != null ? fadeInOutOverride : true; + if (chunkTrackerComponent != null && (clearWorld || fadeInOut)) { + chunkTrackerComponent.setReadyForChunks(false); + } + + Vector3d spawnPosition = transformComponent.getPosition(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(spawnPosition.getX(), spawnPosition.getZ()); + CompletableFuture loadTargetChunkFuture = this.chunkStore + .getChunkReferenceAsync(chunkIndex) + .thenAccept(v -> playerComponent.startClientReadyTimeout()); + CompletableFuture clientReadyFuture = new CompletableFuture<>(); + packetHandler.setClientReadyForChunksFuture(clientReadyFuture); + CompletableFuture setupPlayerFuture = CompletableFuture.runAsync( + () -> this.onSetupPlayerJoining(holder, playerComponent, playerRef, packetHandler, transform, clearWorld, fadeInOut) + ); + CompletableFuture playerReadyFuture = clientReadyFuture.orTimeout(30L, TimeUnit.SECONDS); + return CompletableFuture.allOf(setupPlayerFuture, playerReadyFuture, loadTargetChunkFuture) + .thenApplyAsync(aVoid -> this.onFinishPlayerJoining(playerComponent, playerRef, packetHandler, event.shouldBroadcastJoinMessage()), this) + .exceptionally(throwable -> { + this.logger.at(Level.WARNING).withCause(throwable).log("Exception when adding player to world!"); + playerRef.getPacketHandler().disconnect("Exception when adding player to world!"); + throw new RuntimeException("Exception when adding player '" + playerRef.getUsername() + "' to world '" + this.name + "'", throwable); + }); + } + } + } + + @Nonnull + private PlayerRef onFinishPlayerJoining( + @Nonnull Player playerComponent, @Nonnull PlayerRef playerRefComponent, @Nonnull PacketHandler packetHandler, boolean broadcastJoin + ) { + TimeResource timeResource = this.entityStore.getStore().getResource(TimeResource.getResourceType()); + float timeDilationModifier = timeResource.getTimeDilationModifier(); + int maxViewRadius = HytaleServer.get().getConfig().getMaxViewRadius(); + packetHandler.write( + new ViewRadius(maxViewRadius * 32), + new SetEntitySeed(this.entitySeed.get()), + new SetClientId(playerComponent.getNetworkId()), + new SetTimeDilation(timeDilationModifier) + ); + packetHandler.write(new UpdateFeatures(this.features)); + packetHandler.write(this.worldConfig.getClientEffects().createSunSettingsPacket()); + packetHandler.write(this.worldConfig.getClientEffects().createPostFxSettingsPacket()); + UUID playerUuid = playerRefComponent.getUuid(); + Store store = this.entityStore.getStore(); + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + World world = store.getExternalData().getWorld(); + packetHandler.writeNoCache(new SetUpdateRate(this.getTps())); + if (this.isPaused) { + this.setPaused(false); + } + + Ref ref = playerRefComponent.addToStore(store); + if (ref != null && ref.isValid()) { + worldTimeResource.sendTimePackets(playerRefComponent); + WorldMapTracker worldMapTracker = playerComponent.getWorldMapTracker(); + worldMapTracker.clear(); + worldMapTracker.sendSettings(world); + if (broadcastJoin) { + Message message = Message.translation("server.general.playerJoinedWorld") + .param("username", playerRefComponent.getUsername()) + .param("world", this.worldConfig.getDisplayName() != null ? this.worldConfig.getDisplayName() : WorldConfig.formatDisplayName(this.name)); + PlayerUtil.broadcastMessageToPlayers(playerUuid, message, store); + } + + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + String position = transformComponent.getPosition().toString(); + this.logger.at(Level.INFO).log("Player '%s' joined world '%s' at location %s (%s)", playerRefComponent.getUsername(), this.name, position, playerUuid); + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + return playerRefComponent; + } else { + throw new IllegalStateException("Failed to add player ref of joining player to the world store"); + } + } + + private void onSetupPlayerJoining( + @Nonnull Holder holder, + @Nonnull Player playerComponent, + @Nonnull PlayerRef playerRefComponent, + @Nonnull PacketHandler packetHandler, + @Nullable Transform transform, + boolean clearWorld, + boolean fadeInOut + ) { + UUID playerUuid = playerRefComponent.getUuid(); + this.logger + .at(Level.INFO) + .log("Adding player '%s' to world '%s' at location %s (%s)", playerRefComponent.getUsername(), this.name, transform, playerUuid); + int entityId = this.entityStore.takeNextNetworkId(); + playerComponent.setNetworkId(entityId); + PlayerConfigData configData = playerComponent.getPlayerConfigData(); + configData.setWorld(this.name); + if (clearWorld) { + LegacyEntityTrackerSystems.clear(playerComponent, holder); + ChunkTracker chunkTrackerComponent = holder.getComponent(ChunkTracker.getComponentType()); + if (chunkTrackerComponent != null) { + chunkTrackerComponent.clear(); + } + } + + playerComponent.getPageManager().clearCustomPageAcknowledgements(); + JoinWorld packet = new JoinWorld(clearWorld, fadeInOut, this.worldConfig.getUuid()); + packetHandler.write(packet); + packetHandler.tryFlush(); + HytaleLogger.getLogger().at(Level.INFO).log("%s: Sent %s", packetHandler.getIdentifier(), packet); + packetHandler.setQueuePackets(true); + } + + @Nonnull + public CompletableFuture drainPlayersTo(@Nonnull World fallbackTargetWorld) { + return CompletableFuture.completedFuture((Void)null) + .thenComposeAsync( + aVoid -> { + ObjectArrayList> futures = new ObjectArrayList<>(); + + for (PlayerRef playerRef : this.playerRefs) { + Holder holder = playerRef.removeFromStore(); + DrainPlayerFromWorldEvent event = HytaleServer.get() + .getEventBus() + .dispatchFor(DrainPlayerFromWorldEvent.class, this.name) + .dispatch(new DrainPlayerFromWorldEvent(holder, fallbackTargetWorld, null)); + futures.add(event.getWorld().addPlayer(playerRef, event.getTransform())); + } + + return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)); + }, + this + ); + } + + @Nonnull + public GameplayConfig getGameplayConfig() { + String gameplayConfigId = this.worldConfig.getGameplayConfig(); + GameplayConfig gameplayConfig = GameplayConfig.getAssetMap().getAsset(gameplayConfigId); + if (gameplayConfig == null) { + gameplayConfig = GameplayConfig.DEFAULT; + } + + return gameplayConfig; + } + + @Nonnull + public Map getFeatures() { + return Collections.unmodifiableMap(this.features); + } + + public boolean isFeatureEnabled(@Nonnull ClientFeature feature) { + return this.features.getOrDefault(feature, false); + } + + public void registerFeature(@Nonnull ClientFeature feature, boolean enabled) { + this.features.put(feature, enabled); + this.broadcastFeatures(); + } + + public void broadcastFeatures() { + UpdateFeatures packet = new UpdateFeatures(this.features); + + for (PlayerRef playerRef : this.playerRefs) { + playerRef.getPacketHandler().write(packet); + } + } + + @Nonnull + public Path getSavePath() { + return this.savePath; + } + + public void updateEntitySeed(@Nonnull Store store) { + int newEntitySeed = this.random.nextInt(); + this.entitySeed.set(newEntitySeed); + PlayerUtil.broadcastPacketToPlayers(store, new SetEntitySeed(newEntitySeed)); + } + + public void markGCHasRun() { + this.gcHasRun = true; + } + + public boolean consumeGCHasRun() { + boolean gcHasRun = this.gcHasRun; + this.gcHasRun = false; + return gcHasRun; + } + + @Override + public int hashCode() { + return this.name != null ? this.name.hashCode() : 0; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + World world = (World)o; + return this.name.equals(world.name); + } else { + return false; + } + } + + @Nonnull + @Override + public String toString() { + return "World{name='" + + this.name + + "', alive=" + + this.alive.get() + + ", loadedChunksCount=" + + this.chunkStore.getLoadedChunksCount() + + ", totalLoadedChunksCount=" + + this.chunkStore.getTotalLoadedChunksCount() + + ", totalGeneratedChunksCount=" + + this.chunkStore.getTotalGeneratedChunksCount() + + ", entityCount=" + + this.entityStore.getStore().getEntityCount() + + "}"; + } + + public void validate(@Nonnull StringBuilder errors, @Nonnull IPrefabBuffer.RawBlockConsumer blockValidator, @Nonnull EnumSet options) throws IOException { + this.setThread(Thread.currentThread()); + this.onStart(); + Store store = this.chunkStore.getStore(); + StringBuilder tempBuilder = new StringBuilder(); + + for (long index : this.chunkStore.getLoader().getIndexes()) { + int chunkX = ChunkUtil.xOfChunkIndex(index); + int chunkZ = ChunkUtil.zOfChunkIndex(index); + + try { + CompletableFuture> future = this.chunkStore.getChunkReferenceAsync(index, 2); + + while (!future.isDone()) { + this.consumeTaskQueue(); + } + + Ref reference = future.join(); + if (reference != null && reference.isValid()) { + WorldChunk chunk = store.getComponent(reference, WorldChunk.getComponentType()); + ChunkColumn chunkColumn = store.getComponent(reference, ChunkColumn.getComponentType()); + if (chunkColumn != null) { + for (Ref section : chunkColumn.getSections()) { + final ChunkSection sectionInfo = store.getComponent(section, ChunkSection.getComponentType()); + BlockSection blockSection = store.getComponent(section, BlockSection.getComponentType()); + if (blockSection != null) { + BlockPhysics blockPhys = store.getComponent(section, BlockPhysics.getComponentType()); + + for (int y = 0; y < 32; y++) { + int worldY = (sectionInfo.getY() << 5) + y; + + for (int z = 0; z < 32; z++) { + for (int x = 0; x < 32; x++) { + int blockId = blockSection.get(x, y, z); + int filler = blockSection.getFiller(x, y, z); + int rotation = blockSection.getRotationIndex(x, y, z); + Holder holder = chunk.getBlockComponentHolder(x, worldY, z); + int worldX = ChunkUtil.minBlock(chunk.getX()) + x; + int worldZ = ChunkUtil.minBlock(chunk.getZ()) + z; + blockValidator.accept( + worldX, y, worldZ, blockId, 0, 1.0F, holder, blockPhys != null ? blockPhys.get(x, y, z) : 0, rotation, filler, null + ); + if (options.contains(ValidationOption.BLOCK_FILLER)) { + var fetcher = new FillerBlockUtil.FillerFetcher() { + public int getBlock(BlockSection blockSection, ChunkStore chunkStore, int xx, int yx, int zx) { + if (xx >= 0 && yx >= 0 && zx >= 0 && xx < 32 && yx < 32 && zx < 32) { + return blockSection.get(xx, yx, zx); + } else { + int nx = sectionInfo.getX() + ChunkUtil.chunkCoordinate(xx); + int ny = sectionInfo.getY() + ChunkUtil.chunkCoordinate(yx); + int nz = sectionInfo.getZ() + ChunkUtil.chunkCoordinate(zx); + CompletableFuture> refFuture = chunkStore.getChunkSectionReferenceAsync(nx, ny, nz); + + while (!refFuture.isDone()) { + World.this.consumeTaskQueue(); + } + + Ref ref = refFuture.join(); + BlockSection blocks = chunkStore.getStore().getComponent(ref, BlockSection.getComponentType()); + return blocks == null ? Integer.MIN_VALUE : blocks.get(xx, yx, zx); + } + } + + public int getFiller(BlockSection blockSection, ChunkStore chunkStore, int xx, int yx, int zx) { + if (xx >= 0 && yx >= 0 && zx >= 0 && xx < 32 && yx < 32 && zx < 32) { + return blockSection.getFiller(xx, yx, zx); + } else { + int nx = sectionInfo.getX() + ChunkUtil.chunkCoordinate(xx); + int ny = sectionInfo.getY() + ChunkUtil.chunkCoordinate(yx); + int nz = sectionInfo.getZ() + ChunkUtil.chunkCoordinate(zx); + CompletableFuture> refFuture = chunkStore.getChunkSectionReferenceAsync(nx, ny, nz); + + while (!refFuture.isDone()) { + World.this.consumeTaskQueue(); + } + + Ref ref = refFuture.join(); + BlockSection blocks = chunkStore.getStore().getComponent(ref, BlockSection.getComponentType()); + return blocks == null ? Integer.MIN_VALUE : blocks.getFiller(xx, yx, zx); + } + } + + public int getRotationIndex(BlockSection blockSection, ChunkStore chunkStore, int xx, int yx, int zx) { + if (xx >= 0 && yx >= 0 && zx >= 0 && xx < 32 && yx < 32 && zx < 32) { + return blockSection.getFiller(xx, yx, zx); + } else { + int nx = sectionInfo.getX() + ChunkUtil.chunkCoordinate(xx); + int ny = sectionInfo.getY() + ChunkUtil.chunkCoordinate(yx); + int nz = sectionInfo.getZ() + ChunkUtil.chunkCoordinate(zx); + CompletableFuture> refFuture = chunkStore.getChunkSectionReferenceAsync(nx, ny, nz); + + while (!refFuture.isDone()) { + World.this.consumeTaskQueue(); + } + + Ref ref = refFuture.join(); + BlockSection blocks = chunkStore.getStore().getComponent(ref, BlockSection.getComponentType()); + return blocks == null ? Integer.MIN_VALUE : blocks.getRotationIndex(xx, yx, zx); + } + } + }; + FillerBlockUtil.ValidationResult fillerResult = FillerBlockUtil.validateBlock( + x, y, z, blockId, rotation, filler, blockSection, this.chunkStore, fetcher + ); + switch (fillerResult) { + case OK: + default: + break; + case INVALID_BLOCK: { + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + tempBuilder.append("\tBlock ") + .append(blockType != null ? blockType.getId() : "") + .append(" at ") + .append(x) + .append(", ") + .append(y) + .append(", ") + .append(z) + .append(" is not valid filler") + .append('\n'); + break; + } + case INVALID_FILLER: { + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + tempBuilder.append("\tBlock ") + .append(blockType != null ? blockType.getId() : "") + .append(" at ") + .append(x) + .append(", ") + .append(y) + .append(", ") + .append(z) + .append(" has invalid/missing filler blocks") + .append('\n'); + } + } + } + } + } + } + + if (!tempBuilder.isEmpty()) { + errors.append("\tChunk ") + .append(sectionInfo.getX()) + .append(", ") + .append(sectionInfo.getY()) + .append(", ") + .append(sectionInfo.getZ()) + .append(" validation errors:") + .append((CharSequence)tempBuilder); + tempBuilder.setLength(0); + } + } + } + + if (options.contains(ValidationOption.ENTITIES)) { + ComponentType> unknownComponentType = EntityStore.REGISTRY.getUnknownComponentType(); + + for (Holder entityHolder : chunk.getEntityChunk().getEntityHolders()) { + UnknownComponents unknownComponents = entityHolder.getComponent(unknownComponentType); + if (unknownComponents != null && !unknownComponents.getUnknownComponents().isEmpty()) { + errors.append("\tUnknown Entity Components: ").append(unknownComponents.getUnknownComponents()).append("\n"); + } + } + } + + store.tick(1.0F); + } + } + } catch (CompletionException var35) { + this.getLogger().at(Level.SEVERE).withCause(var35).log("Failed to validate chunk: %d, %d", chunkX, chunkZ); + errors.append('\t') + .append("Exception validating chunk: ") + .append(chunkX) + .append(", ") + .append(chunkZ) + .append('\n') + .append("\t\t") + .append(var35.getCause().getMessage()) + .append('\n'); + } + } + + if (this.alive.getAndSet(false)) { + this.onShutdown(); + } + + this.setThread(null); + } + + @FunctionalInterface + public interface GenericBlockBulkUpdater { + void apply(World var1, T var2, long var3, WorldChunk var5, int var6, int var7, int var8, int var9, int var10, int var11); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/WorldConfig.java b/src/com/hypixel/hytale/server/core/universe/world/WorldConfig.java new file mode 100644 index 0000000..5e649e2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/WorldConfig.java @@ -0,0 +1,645 @@ +package com.hypixel.hytale.server.core.universe.world; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.ObjectMapCodec; +import com.hypixel.hytale.codec.lookup.MapKeyMapCodec; +import com.hypixel.hytale.codec.schema.metadata.NoDefaultValue; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.plugin.PluginIdentifier; +import com.hypixel.hytale.common.semver.SemverRange; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.shape.Box2D; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.asset.type.gameplay.DeathConfig; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.codec.ShapeCodecs; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.spawn.GlobalSpawnProvider; +import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider; +import com.hypixel.hytale.server.core.universe.world.storage.provider.IChunkStorageProvider; +import com.hypixel.hytale.server.core.universe.world.storage.resources.IResourceStorageProvider; +import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen; +import com.hypixel.hytale.server.core.universe.world.worldgen.provider.IWorldGenProvider; +import com.hypixel.hytale.server.core.universe.world.worldmap.provider.IWorldMapProvider; +import com.hypixel.hytale.server.core.util.BsonUtil; +import java.nio.file.Path; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +public class WorldConfig { + public static final int VERSION = 4; + public static final int INITIAL_GAME_DAY_START_HOUR = 5; + public static final int INITIAL_GAME_DAY_START_MINS = 30; + public static final MapKeyMapCodec PLUGIN_CODEC = new MapKeyMapCodec<>(false); + public static final BuilderCodec CODEC = BuilderCodec.builder(WorldConfig.class, () -> new WorldConfig(null)) + .versioned() + .codecVersion(4) + .documentation( + "Configuration for a single world. Settings in here only affect the world this configuration belongs to.\n\nInstances share this configuration but ignore certain parameters (e.g. *UUID*). In this case the configuration willbe cloned before loading the instance." + ) + .append(new KeyedCodec<>("UUID", Codec.UUID_BINARY), (o, s) -> o.uuid = s, o -> o.uuid) + .documentation( + "The unique identifier for this world.\n\nInstances will ignore this and replace it with a freshly generated UUID when spawning the instance." + ) + .add() + .append(new KeyedCodec<>("DisplayName", Codec.STRING), (o, s) -> o.displayName = s, o -> o.displayName) + .documentation("The player facing name of this world.") + .add() + .append(new KeyedCodec<>("Seed", Codec.LONG), (o, i) -> o.seed = i, o -> o.seed) + .documentation("The seed of the world, used for world generation.\n\nIf a seed is not set then one will be randomly generated.") + .metadata(NoDefaultValue.INSTANCE) + .add() + .append(new KeyedCodec<>("SpawnPoint", Transform.CODEC), (o, s) -> o.spawnProvider = new GlobalSpawnProvider(s), o -> null) + .documentation("**Deprecated**: Please use **SpawnProvider** instead.") + .setVersionRange(0, 1) + .add() + .append(new KeyedCodec<>("SpawnProvider", ISpawnProvider.CODEC), (o, s) -> o.spawnProvider = s, o -> o.spawnProvider) + .documentation( + "Sets the spawn provider for the world. \n\nThis controls where new players will enter the world at. This can be provided by world generation in some cases." + ) + .add() + .append(new KeyedCodec<>("WorldGen", IWorldGenProvider.CODEC), (o, i) -> o.worldGenProvider = i, o -> o.worldGenProvider) + .documentation("Sets the world generator that will be used by the world.") + .add() + .append(new KeyedCodec<>("WorldMap", IWorldMapProvider.CODEC), (o, i) -> o.worldMapProvider = i, o -> o.worldMapProvider) + .add() + .append( + new KeyedCodec<>("ChunkStorage", IChunkStorageProvider.CODEC), (o, i) -> o.chunkStorageProvider = i, o -> o.chunkStorageProvider + ) + .documentation("Sets the storage system that will be used by the world to store chunks.") + .add() + .append(new KeyedCodec<>("ChunkConfig", WorldConfig.ChunkConfig.CODEC), (o, i) -> o.chunkConfig = i, o -> o.chunkConfig) + .documentation("Configuration for chunk related settings") + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("IsTicking", Codec.BOOLEAN), (o, i) -> o.isTicking = i, o -> o.isTicking) + .documentation("Sets whether chunks in this world are ticking or not.") + .add() + .append(new KeyedCodec<>("IsBlockTicking", Codec.BOOLEAN), (o, i) -> o.isBlockTicking = i, o -> o.isBlockTicking) + .documentation("Sets whether blocks in this world are ticking or not.") + .add() + .append(new KeyedCodec<>("IsPvpEnabled", Codec.BOOLEAN), (o, i) -> o.isPvpEnabled = i, o -> o.isPvpEnabled) + .documentation("Sets whether PvP is allowed in this world or not.") + .add() + .append(new KeyedCodec<>("IsFallDamageEnabled", Codec.BOOLEAN), (o, i) -> o.isFallDamageEnabled = i, o -> o.isFallDamageEnabled) + .documentation("Sets whether fall damage is allowed in this world or not.") + .add() + .append(new KeyedCodec<>("IsGameTimePaused", Codec.BOOLEAN), (o, i) -> o.isGameTimePaused = i, o -> o.isGameTimePaused) + .documentation("Sets whether the game time is paused.\n\nThis effects things like day/night cycles and things that rely on those.") + .add() + .append(new KeyedCodec<>("GameTime", Codec.INSTANT), (o, i) -> o.gameTime = i, o -> o.gameTime) + .documentation("The current time of day in the world.") + .add() + .append(new KeyedCodec<>("ForcedWeather", Codec.STRING), (o, i) -> o.forcedWeather = i, o -> o.forcedWeather) + .documentation("Sets the type of weather that is being forced to be active in this world.") + .addValidator(Weather.VALIDATOR_CACHE.getValidator()) + .add() + .append( + new KeyedCodec<>("ClientEffects", ClientEffectWorldSettings.CODEC), (o, i) -> o.clientEffects = i, o -> o.clientEffects + ) + .documentation("Settings for the client's weather and post-processing effects in this world.") + .add() + .append( + new KeyedCodec<>("PregenerateRegion", ShapeCodecs.BOX), + (o, i) -> o.chunkConfig.setPregenerateRegion(new Box2D(new Vector2d(i.min.x, i.min.z), new Vector2d(i.max.x, i.max.z))), + o -> null + ) + .setVersionRange(1, 3) + .addValidator(Validators.deprecated()) + .add() + .append( + new KeyedCodec<>( + "RequiredPlugins", new ObjectMapCodec<>(SemverRange.CODEC, HashMap::new, PluginIdentifier::toString, PluginIdentifier::fromString, false) + ), + (o, i) -> o.requiredPlugins = i, + o -> o.requiredPlugins + ) + .documentation("Sets the plugins that are required to be enabled for this world to function.") + .add() + .append(new KeyedCodec<>("GameMode", ProtocolCodecs.GAMEMODE), (o, i) -> o.gameMode = i, o -> o.gameMode) + .documentation("Sets the default gamemode for this world.") + .add() + .append(new KeyedCodec<>("IsSpawningNPC", Codec.BOOLEAN), (o, i) -> o.isSpawningNPC = i, o -> o.isSpawningNPC) + .documentation("Whether NPCs can spawn in this world or not.") + .add() + .append(new KeyedCodec<>("IsSpawnMarkersEnabled", Codec.BOOLEAN), (o, i) -> o.isSpawnMarkersEnabled = i, o -> o.isSpawnMarkersEnabled) + .documentation("Whether spawn markers are enabled for this world.") + .add() + .append(new KeyedCodec<>("IsAllNPCFrozen", Codec.BOOLEAN), (o, i) -> o.isAllNPCFrozen = i, o -> o.isAllNPCFrozen) + .documentation("Whether all NPCs are frozen for this world") + .add() + .append(new KeyedCodec<>("GameplayConfig", Codec.STRING), (o, i) -> o.gameplayConfig = i, o -> o.gameplayConfig) + .addValidator(GameplayConfig.VALIDATOR_CACHE.getValidator()) + .documentation("The gameplay configuration being used by this world") + .add() + .append(new KeyedCodec<>("Death", DeathConfig.CODEC), (o, i) -> o.deathConfigOverride = i, o -> o.deathConfigOverride) + .documentation("Inline death configuration overrides for this world. If set, these values take precedence over the referenced GameplayConfig.") + .add() + .append( + new KeyedCodec<>("DaytimeDurationSeconds", Codec.INTEGER), (o, i) -> o.daytimeDurationSecondsOverride = i, o -> o.daytimeDurationSecondsOverride + ) + .documentation("Override for the duration of daytime in seconds. If set, takes precedence over the referenced GameplayConfig.") + .add() + .append( + new KeyedCodec<>("NighttimeDurationSeconds", Codec.INTEGER), (o, i) -> o.nighttimeDurationSecondsOverride = i, o -> o.nighttimeDurationSecondsOverride + ) + .documentation("Override for the duration of nighttime in seconds. If set, takes precedence over the referenced GameplayConfig.") + .add() + .append(new KeyedCodec<>("IsCompassUpdating", Codec.BOOLEAN), (o, i) -> o.isCompassUpdating = i, o -> o.isCompassUpdating) + .documentation("Whether the compass is updating in this world") + .add() + .append(new KeyedCodec<>("IsSavingPlayers", Codec.BOOLEAN), (o, i) -> o.isSavingPlayers = i, o -> o.isSavingPlayers) + .documentation("Whether the configuration for player's is being saved for players in this world.") + .add() + .append(new KeyedCodec<>("IsSavingChunks", Codec.BOOLEAN), (o, i) -> o.canSaveChunks = i, o -> o.canSaveChunks) + .documentation("Whether the chunk data is allowed to be saved to the disk for this world.") + .add() + .append(new KeyedCodec<>("SaveNewChunks", Codec.BOOLEAN), (o, i) -> o.saveNewChunks = i, o -> o.saveNewChunks) + .documentation( + "Whether newly generated chunks should be marked for saving or not.\nEnabling this can prevent random chunks from being out of place if/when worldgen changes but will increase the size of the world on disk." + ) + .add() + .append(new KeyedCodec<>("IsUnloadingChunks", Codec.BOOLEAN), (o, i) -> o.canUnloadChunks = i, o -> o.canUnloadChunks) + .documentation("Whether the chunks should be unloaded like normally, or should be prevented from unloading at all.") + .add() + .append( + new KeyedCodec<>("IsObjectiveMarkersEnabled", Codec.BOOLEAN), (o, i) -> o.isObjectiveMarkersEnabled = i, o -> o.isObjectiveMarkersEnabled + ) + .documentation("Whether objective markers are enabled for this world.") + .add() + .append(new KeyedCodec<>("DeleteOnUniverseStart", Codec.BOOLEAN), (o, i) -> o.deleteOnUniverseStart = i, o -> o.deleteOnUniverseStart) + .documentation( + "Whether this world should be deleted when loaded from Universe start. By default this is when going through the world folders in the universe directory." + ) + .add() + .append(new KeyedCodec<>("DeleteOnRemove", Codec.BOOLEAN), (o, i) -> o.deleteOnRemove = i, o -> o.deleteOnRemove) + .documentation("Whether this world should be deleted once its been removed from the server") + .add() + .append( + new KeyedCodec<>("Instance", Codec.BSON_DOCUMENT), + (o, i, e) -> o.pluginConfig.put(PLUGIN_CODEC.getKeyForId("Instance"), PLUGIN_CODEC.decodeById("Instance", i, e)), + (o, e) -> null + ) + .setVersionRange(0, 2) + .documentation("Instance specific configuration.") + .addValidator(Validators.deprecated()) + .add() + .append( + new KeyedCodec<>("ResourceStorage", IResourceStorageProvider.CODEC), (o, i) -> o.resourceStorageProvider = i, o -> o.resourceStorageProvider + ) + .documentation("Sets the storage system that will be used to store resources.") + .add() + .>appendInherited(new KeyedCodec<>("Plugin", PLUGIN_CODEC), (o, i) -> { + if (o.pluginConfig.isEmpty()) { + o.pluginConfig = i; + } else { + MapKeyMapCodec.TypeMap temp = o.pluginConfig; + o.pluginConfig.putAll(temp); + o.pluginConfig.putAll(i); + } + }, o -> o.pluginConfig, (o, p) -> o.pluginConfig = p.pluginConfig) + .addValidator(Validators.nonNull()) + .add() + .build(); + @Nonnull + private transient AtomicBoolean hasChanged = new AtomicBoolean(); + private UUID uuid = UUID.randomUUID(); + private String displayName; + private long seed = System.currentTimeMillis(); + @Nullable + private ISpawnProvider spawnProvider = null; + private IWorldGenProvider worldGenProvider = IWorldGenProvider.CODEC.getDefault(); + private IWorldMapProvider worldMapProvider = IWorldMapProvider.CODEC.getDefault(); + private IChunkStorageProvider chunkStorageProvider = IChunkStorageProvider.CODEC.getDefault(); + @Nonnull + private WorldConfig.ChunkConfig chunkConfig = new WorldConfig.ChunkConfig(); + private boolean isTicking = true; + private boolean isBlockTicking = true; + private boolean isPvpEnabled = false; + private boolean isFallDamageEnabled = true; + private boolean isGameTimePaused = false; + private Instant gameTime = WorldTimeResource.ZERO_YEAR.plus(5L, ChronoUnit.HOURS).plus(30L, ChronoUnit.MINUTES); + private String forcedWeather; + private ClientEffectWorldSettings clientEffects = new ClientEffectWorldSettings(); + private Map requiredPlugins = Collections.emptyMap(); + private GameMode gameMode; + private boolean isSpawningNPC = true; + private boolean isSpawnMarkersEnabled = true; + private boolean isAllNPCFrozen = false; + private String gameplayConfig = "Default"; + @Nullable + private DeathConfig deathConfigOverride = null; + @Nullable + private Integer daytimeDurationSecondsOverride = null; + @Nullable + private Integer nighttimeDurationSecondsOverride = null; + private boolean isCompassUpdating = true; + private boolean isSavingPlayers = true; + private boolean canSaveChunks = true; + private boolean saveNewChunks = true; + private boolean canUnloadChunks = true; + private boolean isObjectiveMarkersEnabled = true; + private boolean deleteOnUniverseStart = false; + private boolean deleteOnRemove = false; + private IResourceStorageProvider resourceStorageProvider = IResourceStorageProvider.CODEC.getDefault(); + protected MapKeyMapCodec.TypeMap pluginConfig = new MapKeyMapCodec.TypeMap<>(PLUGIN_CODEC); + @Nullable + private transient ISpawnProvider defaultSpawnProvider; + private transient boolean isSavingConfig = true; + + public WorldConfig() { + this.markChanged(); + } + + private WorldConfig(Void dummy) { + } + + @Nonnull + public UUID getUuid() { + return this.uuid; + } + + public void setUuid(UUID uuid) { + this.uuid = uuid; + } + + public boolean isDeleteOnUniverseStart() { + return this.deleteOnUniverseStart; + } + + public void setDeleteOnUniverseStart(boolean deleteOnUniverseStart) { + this.deleteOnUniverseStart = deleteOnUniverseStart; + } + + public boolean isDeleteOnRemove() { + return this.deleteOnRemove; + } + + public void setDeleteOnRemove(boolean deleteOnRemove) { + this.deleteOnRemove = deleteOnRemove; + } + + public boolean isSavingConfig() { + return this.isSavingConfig; + } + + public void setSavingConfig(boolean savingConfig) { + this.isSavingConfig = savingConfig; + } + + public String getDisplayName() { + return this.displayName; + } + + public void setDisplayName(String name) { + this.displayName = name; + } + + @Nonnull + public static String formatDisplayName(@Nonnull String name) { + return name.replaceAll("([a-z])([A-Z])", "$1 $2").replaceAll("([A-Za-z])([0-9])", "$1 $2").replaceAll("_", " "); + } + + public long getSeed() { + return this.seed; + } + + public void setSeed(long seed) { + this.seed = seed; + } + + @Nullable + public ISpawnProvider getSpawnProvider() { + return this.spawnProvider != null ? this.spawnProvider : this.defaultSpawnProvider; + } + + public void setSpawnProvider(ISpawnProvider spawnProvider) { + this.spawnProvider = spawnProvider; + } + + public void setDefaultSpawnProvider(@Nonnull IWorldGen generator) { + this.defaultSpawnProvider = generator.getDefaultSpawnProvider((int)this.seed); + } + + public IWorldGenProvider getWorldGenProvider() { + return this.worldGenProvider; + } + + public void setWorldGenProvider(IWorldGenProvider worldGenProvider) { + this.worldGenProvider = worldGenProvider; + } + + public IWorldMapProvider getWorldMapProvider() { + return this.worldMapProvider; + } + + public void setWorldMapProvider(IWorldMapProvider worldMapProvider) { + this.worldMapProvider = worldMapProvider; + } + + public IChunkStorageProvider getChunkStorageProvider() { + return this.chunkStorageProvider; + } + + public void setChunkStorageProvider(IChunkStorageProvider chunkStorageProvider) { + this.chunkStorageProvider = chunkStorageProvider; + } + + @Nonnull + public WorldConfig.ChunkConfig getChunkConfig() { + return this.chunkConfig; + } + + public void setChunkConfig(@Nonnull WorldConfig.ChunkConfig chunkConfig) { + this.chunkConfig = chunkConfig; + } + + public boolean isTicking() { + return this.isTicking; + } + + public void setTicking(boolean ticking) { + this.isTicking = ticking; + } + + public boolean isBlockTicking() { + return this.isBlockTicking; + } + + public void setBlockTicking(boolean ticking) { + this.isBlockTicking = ticking; + } + + public boolean isPvpEnabled() { + return this.isPvpEnabled; + } + + public boolean isFallDamageEnabled() { + return this.isFallDamageEnabled; + } + + public void setPvpEnabled(boolean pvpEnabled) { + this.isPvpEnabled = pvpEnabled; + } + + public boolean isGameTimePaused() { + return this.isGameTimePaused; + } + + public void setGameTimePaused(boolean gameTimePaused) { + this.isGameTimePaused = gameTimePaused; + } + + public Instant getGameTime() { + return this.gameTime; + } + + public void setGameTime(Instant gameTime) { + this.gameTime = gameTime; + } + + public String getForcedWeather() { + return this.forcedWeather; + } + + public void setForcedWeather(String forcedWeather) { + this.forcedWeather = forcedWeather; + } + + public void setClientEffects(ClientEffectWorldSettings clientEffects) { + this.clientEffects = clientEffects; + } + + public ClientEffectWorldSettings getClientEffects() { + return this.clientEffects; + } + + @Nonnull + public Map getRequiredPlugins() { + return Collections.unmodifiableMap(this.requiredPlugins); + } + + public void setRequiredPlugins(Map requiredPlugins) { + this.requiredPlugins = requiredPlugins; + } + + public GameMode getGameMode() { + return this.gameMode != null ? this.gameMode : HytaleServer.get().getConfig().getDefaults().getGameMode(); + } + + public void setGameMode(GameMode gameMode) { + this.gameMode = gameMode; + } + + public boolean isSpawningNPC() { + return this.isSpawningNPC; + } + + public void setSpawningNPC(boolean spawningNPC) { + this.isSpawningNPC = spawningNPC; + } + + public boolean isSpawnMarkersEnabled() { + return this.isSpawnMarkersEnabled; + } + + public void setIsSpawnMarkersEnabled(boolean spawnMarkersEnabled) { + this.isSpawnMarkersEnabled = spawnMarkersEnabled; + } + + public boolean isAllNPCFrozen() { + return this.isAllNPCFrozen; + } + + public void setIsAllNPCFrozen(boolean allNPCFrozen) { + this.isAllNPCFrozen = allNPCFrozen; + } + + public String getGameplayConfig() { + return this.gameplayConfig; + } + + public void setGameplayConfig(String gameplayConfig) { + this.gameplayConfig = gameplayConfig; + } + + @Nullable + public DeathConfig getDeathConfigOverride() { + return this.deathConfigOverride; + } + + @Nullable + public Integer getDaytimeDurationSecondsOverride() { + return this.daytimeDurationSecondsOverride; + } + + @Nullable + public Integer getNighttimeDurationSecondsOverride() { + return this.nighttimeDurationSecondsOverride; + } + + public boolean isCompassUpdating() { + return this.isCompassUpdating; + } + + public void setCompassUpdating(boolean compassUpdating) { + this.isCompassUpdating = compassUpdating; + } + + public boolean isSavingPlayers() { + return this.isSavingPlayers; + } + + public void setSavingPlayers(boolean savingPlayers) { + this.isSavingPlayers = savingPlayers; + } + + public boolean canUnloadChunks() { + return this.canUnloadChunks; + } + + public void setCanUnloadChunks(boolean unloadingChunks) { + this.canUnloadChunks = unloadingChunks; + } + + public boolean canSaveChunks() { + return this.canSaveChunks; + } + + public void setCanSaveChunks(boolean savingChunks) { + this.canSaveChunks = savingChunks; + } + + public boolean shouldSaveNewChunks() { + return this.saveNewChunks; + } + + public void setSaveNewChunks(boolean saveNewChunks) { + this.saveNewChunks = saveNewChunks; + } + + public boolean isObjectiveMarkersEnabled() { + return this.isObjectiveMarkersEnabled; + } + + public void setObjectiveMarkersEnabled(boolean objectiveMarkersEnabled) { + this.isObjectiveMarkersEnabled = objectiveMarkersEnabled; + } + + public IResourceStorageProvider getResourceStorageProvider() { + return this.resourceStorageProvider; + } + + public void setResourceStorageProvider(@Nonnull IResourceStorageProvider resourceStorageProvider) { + this.resourceStorageProvider = resourceStorageProvider; + } + + public MapKeyMapCodec.TypeMap getPluginConfig() { + return this.pluginConfig; + } + + public void markChanged() { + this.hasChanged.set(true); + } + + public boolean consumeHasChanged() { + return this.hasChanged.getAndSet(false); + } + + @Nonnull + public static CompletableFuture load(@Nonnull Path path) { + return CompletableFuture.supplyAsync(() -> { + WorldConfig config = RawJsonReader.readSyncWithBak(path, CODEC, HytaleLogger.getLogger()); + return config != null ? config : new WorldConfig(); + }); + } + + @Nonnull + public static CompletableFuture save(@Nonnull Path path, WorldConfig worldConfig) { + BsonDocument document = CODEC.encode(worldConfig, ExtraInfo.THREAD_LOCAL.get()); + return BsonUtil.writeDocument(path, document); + } + + public static class ChunkConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder(WorldConfig.ChunkConfig.class, WorldConfig.ChunkConfig::new) + .appendInherited( + new KeyedCodec<>("PregenerateRegion", Box2D.CODEC), + (o, i) -> o.pregenerateRegion = i, + o -> o.pregenerateRegion, + (o, p) -> o.pregenerateRegion = p.pregenerateRegion + ) + .documentation("Sets the region that will be pregenerated for the world.\n\nIf set, the specified region will be pregenerated when the world starts.") + .add() + .appendInherited( + new KeyedCodec<>("KeepLoadedRegion", Box2D.CODEC), + (o, i) -> o.keepLoadedRegion = i, + o -> o.keepLoadedRegion, + (o, p) -> o.keepLoadedRegion = p.keepLoadedRegion + ) + .documentation("Sets a region of chunks that will never be unloaded.") + .add() + .afterDecode(o -> { + if (o.pregenerateRegion != null) { + o.pregenerateRegion.normalize(); + } + + if (o.keepLoadedRegion != null) { + o.keepLoadedRegion.normalize(); + } + }) + .build(); + private static final Box2D DEFAULT_PREGENERATE_REGION = new Box2D(new Vector2d(-512.0, -512.0), new Vector2d(512.0, 512.0)); + @Nullable + private Box2D pregenerateRegion; + @Nullable + private Box2D keepLoadedRegion; + + public ChunkConfig() { + } + + @Nullable + public Box2D getPregenerateRegion() { + return this.pregenerateRegion; + } + + public void setPregenerateRegion(@Nullable Box2D pregenerateRegion) { + if (pregenerateRegion != null) { + pregenerateRegion.normalize(); + } + + this.pregenerateRegion = pregenerateRegion; + } + + @Nullable + public Box2D getKeepLoadedRegion() { + return this.keepLoadedRegion; + } + + public void setKeepLoadedRegion(@Nullable Box2D keepLoadedRegion) { + if (keepLoadedRegion != null) { + keepLoadedRegion.normalize(); + } + + this.keepLoadedRegion = keepLoadedRegion; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/WorldConfigProvider.java b/src/com/hypixel/hytale/server/core/universe/world/WorldConfigProvider.java new file mode 100644 index 0000000..75bbf38 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/WorldConfigProvider.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.core.universe.world; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public interface WorldConfigProvider { + @Nonnull + default CompletableFuture load(@Nonnull Path savePath, String name) { + Path oldPath = savePath.resolve("config.bson"); + Path path = savePath.resolve("config.json"); + if (Files.exists(oldPath) && !Files.exists(path)) { + try { + Files.move(oldPath, path); + } catch (IOException var6) { + } + } + + return WorldConfig.load(path); + } + + @Nonnull + default CompletableFuture save(@Nonnull Path savePath, WorldConfig config, World world) { + return WorldConfig.save(savePath.resolve("config.json"), config); + } + + public static class Default implements WorldConfigProvider { + public Default() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/WorldMapTracker.java b/src/com/hypixel/hytale/server/core/universe/world/WorldMapTracker.java new file mode 100644 index 0000000..4ca7417 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/WorldMapTracker.java @@ -0,0 +1,794 @@ +package com.hypixel.hytale.server.core.universe.world; + +import com.hypixel.hytale.common.fastutil.HLongOpenHashSet; +import com.hypixel.hytale.common.fastutil.HLongSet; +import com.hypixel.hytale.common.thread.ticking.Tickable; +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.function.function.TriFunction; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.iterator.CircleSpiralIterator; +import com.hypixel.hytale.math.shape.Box2D; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.Position; +import com.hypixel.hytale.protocol.SoundCategory; +import com.hypixel.hytale.protocol.packets.worldmap.ClearWorldMap; +import com.hypixel.hytale.protocol.packets.worldmap.MapChunk; +import com.hypixel.hytale.protocol.packets.worldmap.MapImage; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.protocol.packets.worldmap.UpdateWorldMap; +import com.hypixel.hytale.protocol.packets.worldmap.UpdateWorldMapSettings; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.event.events.ecs.DiscoverZoneEvent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapSettings; +import com.hypixel.hytale.server.core.util.EventTitleUtil; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Predicate; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WorldMapTracker implements Tickable { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final float UPDATE_SPEED = 1.0F; + public static final float MIN_PLAYER_MARKER_UPDATE_SPEED = 10.0F; + public static final int RADIUS_MAX = 512; + public static final int EMPTY_UPDATE_WORLD_MAP_SIZE = 13; + private static final int EMPTY_MAP_CHUNK_SIZE = 10; + private static final int FULL_MAP_CHUNK_SIZE = 23; + public static final int MAX_IMAGE_GENERATION = 20; + public static final int MAX_FRAME = 2621440; + private final Player player; + private final CircleSpiralIterator spiralIterator = new CircleSpiralIterator(); + private final ReentrantReadWriteLock loadedLock = new ReentrantReadWriteLock(); + private final HLongSet loaded = new HLongOpenHashSet(); + private final HLongSet pendingReloadChunks = new HLongOpenHashSet(); + private final Long2ObjectOpenHashMap> pendingReloadFutures = new Long2ObjectOpenHashMap<>(); + private final Map sentMarkers = new ConcurrentHashMap<>(); + private float updateTimer; + private float playerMarkersUpdateTimer; + private Integer viewRadiusOverride; + private boolean started; + private int sentViewRadius; + private int lastChunkX; + private int lastChunkZ; + @Nullable + private String currentBiomeName; + @Nullable + private WorldMapTracker.ZoneDiscoveryInfo currentZone; + private boolean allowTeleportToCoordinates = true; + private boolean allowTeleportToMarkers = true; + private boolean clientHasWorldMapVisible; + private Predicate playerMapFilter; + @Nonnull + private final Set tempToRemove = new HashSet<>(); + @Nonnull + private final Set tempToAdd = new HashSet<>(); + @Nonnull + private final Set tempTestedMarkers = new HashSet<>(); + @Nullable + private TransformComponent transformComponent; + + public WorldMapTracker(@Nonnull Player player) { + this.player = player; + } + + @Override + public void tick(float dt) { + if (!this.started) { + this.started = true; + LOGGER.at(Level.INFO).log("Started Generating Map!"); + } + + World world = this.player.getWorld(); + if (world != null) { + if (this.transformComponent == null) { + this.transformComponent = this.player.getTransformComponent(); + if (this.transformComponent == null) { + return; + } + } + + WorldMapManager worldMapManager = world.getWorldMapManager(); + WorldMapSettings worldMapSettings = worldMapManager.getWorldMapSettings(); + int viewRadius; + if (this.viewRadiusOverride != null) { + viewRadius = this.viewRadiusOverride; + } else { + viewRadius = worldMapSettings.getViewRadius(this.player.getViewRadius()); + } + + Vector3d position = this.transformComponent.getPosition(); + int playerX = MathUtil.floor(position.getX()); + int playerZ = MathUtil.floor(position.getZ()); + int playerChunkX = playerX >> 5; + int playerChunkZ = playerZ >> 5; + if (world.isCompassUpdating()) { + this.playerMarkersUpdateTimer -= dt; + this.updatePointsOfInterest(world, viewRadius, playerChunkX, playerChunkZ); + } + + if (worldMapManager.isWorldMapEnabled()) { + this.updateWorldMap(world, dt, worldMapSettings, viewRadius, playerChunkX, playerChunkZ); + } + } + } + + public void updateCurrentZoneAndBiome( + @Nonnull Ref ref, + @Nullable WorldMapTracker.ZoneDiscoveryInfo zoneDiscoveryInfo, + @Nullable String biomeName, + @Nonnull ComponentAccessor componentAccessor + ) { + this.currentBiomeName = biomeName; + this.currentZone = zoneDiscoveryInfo; + Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + if (!playerComponent.isWaitingForClientReady()) { + World world = componentAccessor.getExternalData().getWorld(); + if (zoneDiscoveryInfo != null && this.discoverZone(world, zoneDiscoveryInfo.regionName())) { + this.onZoneDiscovered(ref, zoneDiscoveryInfo, componentAccessor); + } + } + } + + private void onZoneDiscovered( + @Nonnull Ref ref, @Nonnull WorldMapTracker.ZoneDiscoveryInfo zoneDiscoveryInfo, @Nonnull ComponentAccessor componentAccessor + ) { + WorldMapTracker.ZoneDiscoveryInfo discoverZoneEventInfo = zoneDiscoveryInfo.clone(); + DiscoverZoneEvent.Display discoverZoneEvent = new DiscoverZoneEvent.Display(discoverZoneEventInfo); + componentAccessor.invoke(ref, discoverZoneEvent); + if (!discoverZoneEvent.isCancelled() && discoverZoneEventInfo.display()) { + PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + EventTitleUtil.showEventTitleToPlayer( + playerRefComponent, + Message.translation(String.format("server.map.region.%s", discoverZoneEventInfo.regionName())), + Message.translation(String.format("server.map.zone.%s", discoverZoneEventInfo.zoneName())), + discoverZoneEventInfo.major(), + discoverZoneEventInfo.icon(), + discoverZoneEventInfo.duration(), + discoverZoneEventInfo.fadeInDuration(), + discoverZoneEventInfo.fadeOutDuration() + ); + String discoverySoundEventId = discoverZoneEventInfo.discoverySoundEventId(); + if (discoverySoundEventId != null) { + int assetIndex = SoundEvent.getAssetMap().getIndex(discoverySoundEventId); + if (assetIndex != Integer.MIN_VALUE) { + SoundUtil.playSoundEvent2d(ref, assetIndex, SoundCategory.UI, componentAccessor); + } + } + } + } + + private void updateWorldMap( + @Nonnull World world, float dt, @Nonnull WorldMapSettings worldMapSettings, int chunkViewRadius, int playerChunkX, int playerChunkZ + ) { + this.processPendingReloadChunks(world); + Box2D worldMapArea = worldMapSettings.getWorldMapArea(); + if (worldMapArea == null) { + int xDiff = Math.abs(this.lastChunkX - playerChunkX); + int zDiff = Math.abs(this.lastChunkZ - playerChunkZ); + int chunkMoveDistance = xDiff <= 0 && zDiff <= 0 ? 0 : (int)Math.ceil(Math.sqrt(xDiff * xDiff + zDiff * zDiff)); + this.sentViewRadius = Math.max(0, this.sentViewRadius - chunkMoveDistance); + this.lastChunkX = playerChunkX; + this.lastChunkZ = playerChunkZ; + this.updateTimer -= dt; + if (this.updateTimer > 0.0F) { + return; + } + + if (this.sentViewRadius != chunkViewRadius) { + if (this.sentViewRadius > chunkViewRadius) { + this.sentViewRadius = chunkViewRadius; + } + + this.unloadImages(chunkViewRadius, playerChunkX, playerChunkZ); + if (this.sentViewRadius < chunkViewRadius) { + this.loadImages(world, chunkViewRadius, playerChunkX, playerChunkZ, 20); + } + } else { + this.updateTimer = 1.0F; + } + } else { + this.updateTimer -= dt; + if (this.updateTimer > 0.0F) { + return; + } + + this.loadWorldMap(world, worldMapArea, 20); + } + } + + private void updatePointsOfInterest(@Nonnull World world, int chunkViewRadius, int playerChunkX, int playerChunkZ) { + if (this.transformComponent != null) { + WorldMapManager worldMapManager = world.getWorldMapManager(); + Map markerProviders = worldMapManager.getMarkerProviders(); + this.tempToAdd.clear(); + this.tempTestedMarkers.clear(); + + for (WorldMapManager.MarkerProvider provider : markerProviders.values()) { + provider.update(world, world.getGameplayConfig(), this, chunkViewRadius, playerChunkX, playerChunkZ); + } + + this.tempToRemove.clear(); + this.tempToRemove.addAll(this.sentMarkers.keySet()); + if (!this.tempTestedMarkers.isEmpty()) { + this.tempToRemove.removeAll(this.tempTestedMarkers); + } + + for (String removedMarkerId : this.tempToRemove) { + this.sentMarkers.remove(removedMarkerId); + } + + if (!this.tempToAdd.isEmpty() || !this.tempToRemove.isEmpty()) { + MapMarker[] addedMarkers = !this.tempToAdd.isEmpty() ? this.tempToAdd.toArray(MapMarker[]::new) : null; + String[] removedMarkers = !this.tempToRemove.isEmpty() ? this.tempToRemove.toArray(String[]::new) : null; + this.player.getPlayerConnection().writeNoCache(new UpdateWorldMap(null, addedMarkers, removedMarkers)); + } + } + } + + public void trySendMarker(int chunkViewRadius, int playerChunkX, int playerChunkZ, @Nonnull MapMarker marker) { + this.trySendMarker( + chunkViewRadius, + playerChunkX, + playerChunkZ, + marker.transform.position.x, + marker.transform.position.z, + marker.transform.orientation.yaw, + marker.id, + marker.name, + marker, + (id, name, m) -> m + ); + } + + public void trySendMarker( + int chunkViewRadius, + int playerChunkX, + int playerChunkZ, + @Nonnull Vector3d markerPos, + float markerYaw, + @Nonnull String markerId, + @Nonnull String markerDisplayName, + @Nonnull T param, + @Nonnull TriFunction markerSupplier + ) { + this.trySendMarker(chunkViewRadius, playerChunkX, playerChunkZ, markerPos.x, markerPos.z, markerYaw, markerId, markerDisplayName, param, markerSupplier); + } + + private void trySendMarker( + int chunkViewRadius, + int playerChunkX, + int playerChunkZ, + double markerX, + double markerZ, + float markerYaw, + @Nonnull String markerId, + @Nonnull String markerName, + @Nonnull T param, + @Nonnull TriFunction markerSupplier + ) { + int markerXBlock = MathUtil.floor(markerX); + int markerZBlock = MathUtil.floor(markerZ); + boolean shouldBeVisible = chunkViewRadius == -1 || shouldBeVisible(chunkViewRadius, markerXBlock >> 5, markerZBlock >> 5, playerChunkX, playerChunkZ); + if (shouldBeVisible) { + this.tempTestedMarkers.add(markerId); + boolean needsUpdate = false; + MapMarker oldMarker = this.sentMarkers.get(markerId); + if (oldMarker != null) { + if (!markerName.equals(oldMarker.name)) { + needsUpdate = true; + } + + if (!needsUpdate) { + double distance = Math.abs(oldMarker.transform.orientation.yaw - markerYaw); + needsUpdate = distance > 0.05 || this.playerMarkersUpdateTimer < 0.0F && distance > 0.001; + } + + if (!needsUpdate) { + Position oldPosition = oldMarker.transform.position; + double distance = Vector2d.distance(oldPosition.x, oldPosition.z, markerX, markerZ); + needsUpdate = distance > 5.0 || this.playerMarkersUpdateTimer < 0.0F && distance > 0.1; + } + } else { + needsUpdate = true; + } + + if (needsUpdate) { + MapMarker marker = markerSupplier.apply(markerId, markerName, param); + this.sentMarkers.put(markerId, marker); + this.tempToAdd.add(marker); + } + } + } + + private void unloadImages(int chunkViewRadius, int playerChunkX, int playerChunkZ) { + List currentUnloadList = null; + List> allUnloadLists = null; + this.loadedLock.writeLock().lock(); + + try { + int packetSize = 2621427; + LongIterator iterator = this.loaded.iterator(); + + while (iterator.hasNext()) { + long chunkCoordinates = iterator.nextLong(); + int mapChunkX = ChunkUtil.xOfChunkIndex(chunkCoordinates); + int mapChunkZ = ChunkUtil.zOfChunkIndex(chunkCoordinates); + if (!shouldBeVisible(chunkViewRadius, playerChunkX, playerChunkZ, mapChunkX, mapChunkZ)) { + if (currentUnloadList == null) { + currentUnloadList = new ObjectArrayList<>(packetSize / 10); + } + + currentUnloadList.add(new MapChunk(mapChunkX, mapChunkZ, null)); + packetSize -= 10; + iterator.remove(); + if (packetSize < 10) { + packetSize = 2621427; + if (allUnloadLists == null) { + allUnloadLists = new ObjectArrayList<>(this.loaded.size() / (packetSize / 10)); + } + + allUnloadLists.add(currentUnloadList); + currentUnloadList = new ObjectArrayList<>(packetSize / 10); + } + } + } + + if (allUnloadLists != null) { + for (List unloadList : allUnloadLists) { + this.writeUpdatePacket(unloadList); + } + } + + this.writeUpdatePacket(currentUnloadList); + } finally { + this.loadedLock.writeLock().unlock(); + } + } + + private void processPendingReloadChunks(@Nonnull World world) { + List chunksToSend = null; + this.loadedLock.writeLock().lock(); + + try { + if (!this.pendingReloadChunks.isEmpty()) { + int imageSize = MathUtil.fastFloor(32.0F * world.getWorldMapManager().getWorldMapSettings().getImageScale()); + int fullMapChunkSize = 23 + 4 * imageSize * imageSize; + int packetSize = 2621427; + LongIterator iterator = this.pendingReloadChunks.iterator(); + + while (iterator.hasNext()) { + long chunkCoordinates = iterator.nextLong(); + CompletableFuture future = this.pendingReloadFutures.get(chunkCoordinates); + if (future == null) { + future = world.getWorldMapManager().getImageAsync(chunkCoordinates); + this.pendingReloadFutures.put(chunkCoordinates, future); + } + + if (future.isDone()) { + iterator.remove(); + this.pendingReloadFutures.remove(chunkCoordinates); + if (chunksToSend == null) { + chunksToSend = new ObjectArrayList<>(packetSize / fullMapChunkSize); + } + + int mapChunkX = ChunkUtil.xOfChunkIndex(chunkCoordinates); + int mapChunkZ = ChunkUtil.zOfChunkIndex(chunkCoordinates); + chunksToSend.add(new MapChunk(mapChunkX, mapChunkZ, future.getNow(null))); + this.loaded.add(chunkCoordinates); + packetSize -= fullMapChunkSize; + if (packetSize < fullMapChunkSize) { + this.writeUpdatePacket(chunksToSend); + chunksToSend = new ObjectArrayList<>(2621440 - 13 / fullMapChunkSize); + packetSize = 2621427; + } + } + } + + this.writeUpdatePacket(chunksToSend); + return; + } + } finally { + this.loadedLock.writeLock().unlock(); + } + } + + private int loadImages(@Nonnull World world, int chunkViewRadius, int playerChunkX, int playerChunkZ, int maxGeneration) { + List currentLoadList = null; + List> allLoadLists = null; + this.loadedLock.writeLock().lock(); + + try { + int packetSize = 2621427; + int imageSize = MathUtil.fastFloor(32.0F * world.getWorldMapManager().getWorldMapSettings().getImageScale()); + int fullMapChunkSize = 23 + 4 * imageSize * imageSize; + boolean areAllLoaded = true; + this.spiralIterator.init(playerChunkX, playerChunkZ, this.sentViewRadius, chunkViewRadius); + + while (maxGeneration > 0 && this.spiralIterator.hasNext()) { + long chunkCoordinates = this.spiralIterator.next(); + if (!this.loaded.contains(chunkCoordinates)) { + areAllLoaded = false; + CompletableFuture future = world.getWorldMapManager().getImageAsync(chunkCoordinates); + if (!future.isDone()) { + maxGeneration--; + } else if (this.loaded.add(chunkCoordinates)) { + if (currentLoadList == null) { + currentLoadList = new ObjectArrayList<>(packetSize / fullMapChunkSize); + } + + int mapChunkX = ChunkUtil.xOfChunkIndex(chunkCoordinates); + int mapChunkZ = ChunkUtil.zOfChunkIndex(chunkCoordinates); + currentLoadList.add(new MapChunk(mapChunkX, mapChunkZ, future.getNow(null))); + packetSize -= fullMapChunkSize; + if (packetSize < fullMapChunkSize) { + packetSize = 2621427; + if (allLoadLists == null) { + allLoadLists = new ObjectArrayList<>(); + } + + allLoadLists.add(currentLoadList); + currentLoadList = new ObjectArrayList<>(packetSize / fullMapChunkSize); + } + } + } else if (areAllLoaded) { + this.sentViewRadius = this.spiralIterator.getCompletedRadius(); + } + } + + if (areAllLoaded) { + this.sentViewRadius = this.spiralIterator.getCompletedRadius(); + } + + if (allLoadLists != null) { + for (List unloadList : allLoadLists) { + this.writeUpdatePacket(unloadList); + } + } + + this.writeUpdatePacket(currentLoadList); + } finally { + this.loadedLock.writeLock().unlock(); + } + + return maxGeneration; + } + + private int loadWorldMap(@Nonnull World world, @Nonnull Box2D worldMapArea, int maxGeneration) { + List currentLoadList = null; + List> allLoadLists = null; + this.loadedLock.writeLock().lock(); + + try { + int packetSize = 2621427; + int imageSize = MathUtil.fastFloor(32.0F * world.getWorldMapManager().getWorldMapSettings().getImageScale()); + int fullMapChunkSize = 23 + 4 * imageSize * imageSize; + + for (int mapChunkX = MathUtil.floor(worldMapArea.min.x); mapChunkX < MathUtil.ceil(worldMapArea.max.x) && maxGeneration > 0; mapChunkX++) { + for (int mapChunkZ = MathUtil.floor(worldMapArea.min.y); mapChunkZ < MathUtil.ceil(worldMapArea.max.y) && maxGeneration > 0; mapChunkZ++) { + long chunkCoordinates = ChunkUtil.indexChunk(mapChunkX, mapChunkZ); + if (!this.loaded.contains(chunkCoordinates)) { + CompletableFuture future = CompletableFutureUtil._catch(world.getWorldMapManager().getImageAsync(chunkCoordinates)); + if (!future.isDone()) { + maxGeneration--; + } else { + if (currentLoadList == null) { + currentLoadList = new ObjectArrayList<>(packetSize / fullMapChunkSize); + } + + currentLoadList.add(new MapChunk(mapChunkX, mapChunkZ, future.getNow(null))); + this.loaded.add(chunkCoordinates); + packetSize -= fullMapChunkSize; + if (packetSize < fullMapChunkSize) { + packetSize = 2621427; + if (allLoadLists == null) { + allLoadLists = new ObjectArrayList<>(Math.max(packetSize / fullMapChunkSize, 1)); + } + + allLoadLists.add(currentLoadList); + currentLoadList = new ObjectArrayList<>(packetSize / fullMapChunkSize); + } + } + } + } + } + } finally { + this.loadedLock.writeLock().unlock(); + } + + if (allLoadLists != null) { + for (List unloadList : allLoadLists) { + this.writeUpdatePacket(unloadList); + } + } + + this.writeUpdatePacket(currentLoadList); + return maxGeneration; + } + + private void writeUpdatePacket(@Nullable List list) { + if (list != null) { + UpdateWorldMap packet = new UpdateWorldMap(list.toArray(MapChunk[]::new), null, null); + LOGGER.at(Level.FINE).log("Sending world map update to %s - %d chunks", this.player.getUuid(), list.size()); + this.player.getPlayerConnection().write(packet); + } + } + + @Nonnull + public Map getSentMarkers() { + return this.sentMarkers; + } + + @Nonnull + public Player getPlayer() { + return this.player; + } + + public void clear() { + this.loadedLock.writeLock().lock(); + + try { + this.loaded.clear(); + this.sentViewRadius = 0; + this.sentMarkers.clear(); + } finally { + this.loadedLock.writeLock().unlock(); + } + + this.player.getPlayerConnection().write(new ClearWorldMap()); + } + + public void clearChunks(@Nonnull LongSet chunkIndices) { + this.loadedLock.writeLock().lock(); + + try { + chunkIndices.forEach(index -> { + this.loaded.remove(index); + this.pendingReloadChunks.add(index); + this.pendingReloadFutures.remove(index); + }); + } finally { + this.loadedLock.writeLock().unlock(); + } + + this.updateTimer = 0.0F; + } + + public void sendSettings(@Nonnull World world) { + UpdateWorldMapSettings worldMapSettingsPacket = new UpdateWorldMapSettings(world.getWorldMapManager().getWorldMapSettings().getSettingsPacket()); + world.execute(() -> { + Store store = world.getEntityStore().getStore(); + Ref ref = this.player.getReference(); + if (ref != null) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + worldMapSettingsPacket.allowTeleportToCoordinates = this.allowTeleportToCoordinates && playerComponent.getGameMode() != GameMode.Adventure; + worldMapSettingsPacket.allowTeleportToMarkers = this.allowTeleportToMarkers && playerComponent.getGameMode() != GameMode.Adventure; + playerRefComponent.getPacketHandler().write(worldMapSettingsPacket); + } + }); + } + + private boolean hasDiscoveredZone(@Nonnull String zoneName) { + return this.player.getPlayerConfigData().getDiscoveredZones().contains(zoneName); + } + + public boolean discoverZone(@Nonnull World world, @Nonnull String zoneName) { + Set discoveredZones = this.player.getPlayerConfigData().getDiscoveredZones(); + if (!discoveredZones.contains(zoneName)) { + Set var4 = new HashSet<>(discoveredZones); + var4.add(zoneName); + this.player.getPlayerConfigData().setDiscoveredZones(var4); + this.sendSettings(world); + return true; + } else { + return false; + } + } + + public boolean undiscoverZone(@Nonnull World world, @Nonnull String zoneName) { + Set discoveredZones = this.player.getPlayerConfigData().getDiscoveredZones(); + if (discoveredZones.contains(zoneName)) { + Set var4 = new HashSet<>(discoveredZones); + var4.remove(zoneName); + this.player.getPlayerConfigData().setDiscoveredZones(var4); + this.sendSettings(world); + return true; + } else { + return false; + } + } + + public boolean discoverZones(@Nonnull World world, @Nonnull Set zoneNames) { + Set discoveredZones = this.player.getPlayerConfigData().getDiscoveredZones(); + if (!discoveredZones.containsAll(zoneNames)) { + Set var4 = new HashSet<>(discoveredZones); + var4.addAll(zoneNames); + this.player.getPlayerConfigData().setDiscoveredZones(var4); + this.sendSettings(world); + return true; + } else { + return false; + } + } + + public boolean undiscoverZones(@Nonnull World world, @Nonnull Set zoneNames) { + Set discoveredZones = this.player.getPlayerConfigData().getDiscoveredZones(); + if (discoveredZones.containsAll(zoneNames)) { + Set var4 = new HashSet<>(discoveredZones); + var4.removeAll(zoneNames); + this.player.getPlayerConfigData().setDiscoveredZones(var4); + this.sendSettings(world); + return true; + } else { + return false; + } + } + + public boolean isAllowTeleportToCoordinates() { + return this.allowTeleportToCoordinates; + } + + public void setAllowTeleportToCoordinates(@Nonnull World world, boolean allowTeleportToCoordinates) { + this.allowTeleportToCoordinates = allowTeleportToCoordinates; + this.sendSettings(world); + } + + public boolean isAllowTeleportToMarkers() { + return this.allowTeleportToMarkers; + } + + public void setAllowTeleportToMarkers(@Nonnull World world, boolean allowTeleportToMarkers) { + this.allowTeleportToMarkers = allowTeleportToMarkers; + this.sendSettings(world); + } + + public Predicate getPlayerMapFilter() { + return this.playerMapFilter; + } + + public void setPlayerMapFilter(Predicate playerMapFilter) { + this.playerMapFilter = playerMapFilter; + } + + public void setClientHasWorldMapVisible(boolean visible) { + this.clientHasWorldMapVisible = visible; + } + + public boolean shouldUpdatePlayerMarkers() { + return this.clientHasWorldMapVisible || this.playerMarkersUpdateTimer < 0.0F; + } + + public void resetPlayerMarkersUpdateTimer() { + this.playerMarkersUpdateTimer = 10.0F; + } + + @Nullable + public Integer getViewRadiusOverride() { + return this.viewRadiusOverride; + } + + @Nullable + public String getCurrentBiomeName() { + return this.currentBiomeName; + } + + @Nullable + public WorldMapTracker.ZoneDiscoveryInfo getCurrentZone() { + return this.currentZone; + } + + public void setViewRadiusOverride(@Nullable Integer viewRadiusOverride) { + this.viewRadiusOverride = viewRadiusOverride; + this.clear(); + } + + public int getEffectiveViewRadius(@Nonnull World world) { + return this.viewRadiusOverride != null + ? this.viewRadiusOverride + : world.getWorldMapManager().getWorldMapSettings().getViewRadius(this.player.getViewRadius()); + } + + public boolean shouldBeVisible(int chunkViewRadius, long chunkCoordinates) { + if (this.player != null && this.transformComponent != null) { + Vector3d position = this.transformComponent.getPosition(); + int chunkX = MathUtil.floor(position.getX()) >> 5; + int chunkZ = MathUtil.floor(position.getZ()) >> 5; + int x = ChunkUtil.xOfChunkIndex(chunkCoordinates); + int z = ChunkUtil.zOfChunkIndex(chunkCoordinates); + return shouldBeVisible(chunkViewRadius, chunkX, chunkZ, x, z); + } else { + return false; + } + } + + public void copyFrom(@Nonnull WorldMapTracker worldMapTracker) { + this.loadedLock.writeLock().lock(); + + try { + worldMapTracker.loadedLock.readLock().lock(); + + try { + this.loaded.addAll(worldMapTracker.loaded); + + for (Entry entry : worldMapTracker.sentMarkers.entrySet()) { + this.sentMarkers.put(entry.getKey(), new MapMarker(entry.getValue())); + } + } finally { + worldMapTracker.loadedLock.readLock().unlock(); + } + } finally { + this.loadedLock.writeLock().unlock(); + } + } + + private static boolean shouldBeVisible(int chunkViewRadius, int chunkX, int chunkZ, int x, int z) { + int xDiff = Math.abs(x - chunkX); + int zDiff = Math.abs(z - chunkZ); + int distanceSq = xDiff * xDiff + zDiff * zDiff; + return distanceSq <= chunkViewRadius * chunkViewRadius; + } + + public record ZoneDiscoveryInfo( + @Nonnull String zoneName, + @Nonnull String regionName, + boolean display, + @Nullable String discoverySoundEventId, + @Nullable String icon, + boolean major, + float duration, + float fadeInDuration, + float fadeOutDuration + ) { + @Nonnull + public WorldMapTracker.ZoneDiscoveryInfo clone() { + return new WorldMapTracker.ZoneDiscoveryInfo( + this.zoneName, + this.regionName, + this.display, + this.discoverySoundEventId, + this.icon, + this.major, + this.duration, + this.fadeInDuration, + this.fadeOutDuration + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/WorldNotificationHandler.java b/src/com/hypixel/hytale/server/core/universe/world/WorldNotificationHandler.java new file mode 100644 index 0000000..7977da1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/WorldNotificationHandler.java @@ -0,0 +1,160 @@ +package com.hypixel.hytale.server.core.universe.world; + +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.protocol.BlockParticleEvent; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.Position; +import com.hypixel.hytale.protocol.packets.world.SpawnBlockParticleSystem; +import com.hypixel.hytale.protocol.packets.world.UpdateBlockDamage; +import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.meta.state.SendableBlockState; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WorldNotificationHandler { + @Nonnull + private final World world; + + public WorldNotificationHandler(@Nonnull World world) { + this.world = world; + } + + public void updateState(int x, int y, int z, BlockState state, BlockState oldState) { + this.updateState(x, y, z, state, oldState, null); + } + + public void updateState(int x, int y, int z, BlockState state, BlockState oldState, @Nullable Predicate skip) { + if (y >= 0 && y < 320) { + Consumer> removeOldState; + Predicate canPlayerSeeOld; + if (oldState instanceof SendableBlockState sendableBlockState && state != oldState) { + removeOldState = sendableBlockState::unloadFrom; + canPlayerSeeOld = sendableBlockState::canPlayerSee; + } else { + removeOldState = null; + canPlayerSeeOld = null; + } + + Predicate canPlayerSee; + Consumer> updateBlockState; + if (state instanceof SendableBlockState sendableBlockState) { + updateBlockState = sendableBlockState::sendTo; + canPlayerSee = sendableBlockState::canPlayerSee; + } else { + updateBlockState = null; + canPlayerSee = null; + } + + if (removeOldState != null || updateBlockState != null) { + long indexChunk = ChunkUtil.indexChunkFromBlock(x, z); + List packets = new ObjectArrayList<>(); + + for (PlayerRef playerRef : this.world.getPlayerRefs()) { + ChunkTracker chunkTracker = playerRef.getChunkTracker(); + if (chunkTracker.isLoaded(indexChunk) && (skip == null || !skip.test(playerRef))) { + if (removeOldState != null && canPlayerSeeOld.test(playerRef)) { + removeOldState.accept(packets); + } + + if (updateBlockState != null && canPlayerSee.test(playerRef)) { + updateBlockState.accept(packets); + } + + for (Packet packet : packets) { + playerRef.getPacketHandler().write(packet); + } + + packets.clear(); + } + } + } + } else { + throw new IllegalArgumentException("Y value is outside the world! " + x + ", " + y + ", " + z); + } + } + + public void updateChunk(long indexChunk) { + for (PlayerRef playerRef : this.world.getPlayerRefs()) { + playerRef.getChunkTracker().removeForReload(indexChunk); + } + } + + public void sendBlockParticle(double x, double y, double z, int id, @Nonnull BlockParticleEvent particleType) { + this.sendPacketIfChunkLoaded(this.getBlockParticlePacket(x, y, z, id, particleType), MathUtil.floor(x), MathUtil.floor(z)); + } + + public void sendBlockParticle(@Nonnull PlayerRef playerRef, double x, double y, double z, int id, @Nonnull BlockParticleEvent particleType) { + this.sendPacketIfChunkLoaded(playerRef, this.getBlockParticlePacket(x, y, z, id, particleType), MathUtil.floor(x), MathUtil.floor(z)); + } + + public void updateBlockDamage(int x, int y, int z, float health, float healthDelta) { + this.sendPacketIfChunkLoaded(this.getBlockDamagePacket(x, y, z, health, healthDelta), x, z); + } + + public void updateBlockDamage(int x, int y, int z, float health, float healthDelta, @Nullable Predicate filter) { + this.sendPacketIfChunkLoaded(this.getBlockDamagePacket(x, y, z, health, healthDelta), x, z, filter); + } + + public void sendPacketIfChunkLoaded(@Nonnull Packet packet, int x, int z) { + long indexChunk = ChunkUtil.indexChunkFromBlock(x, z); + this.sendPacketIfChunkLoaded(packet, indexChunk); + } + + public void sendPacketIfChunkLoaded(@Nonnull Packet packet, long indexChunk) { + for (PlayerRef playerRef : this.world.getPlayerRefs()) { + if (playerRef.getChunkTracker().isLoaded(indexChunk)) { + playerRef.getPacketHandler().write(packet); + } + } + } + + public void sendPacketIfChunkLoaded(@Nonnull Packet packet, int x, int z, @Nullable Predicate filter) { + long indexChunk = ChunkUtil.indexChunkFromBlock(x, z); + this.sendPacketIfChunkLoaded(packet, indexChunk, filter); + } + + public void sendPacketIfChunkLoaded(@Nonnull Packet packet, long indexChunk, @Nullable Predicate filter) { + for (PlayerRef playerRef : this.world.getPlayerRefs()) { + if ((filter == null || filter.test(playerRef)) && playerRef.getChunkTracker().isLoaded(indexChunk)) { + playerRef.getPacketHandler().write(packet); + } + } + } + + private void sendPacketIfChunkLoaded(@Nonnull PlayerRef player, @Nonnull Packet packet, int x, int z) { + long indexChunk = ChunkUtil.indexChunkFromBlock(x, z); + this.sendPacketIfChunkLoaded(player, packet, indexChunk); + } + + private void sendPacketIfChunkLoaded(@Nonnull PlayerRef playerRef, @Nonnull Packet packet, long indexChunk) { + if (playerRef.getChunkTracker().isLoaded(indexChunk)) { + playerRef.getPacketHandler().write(packet); + } + } + + @Nonnull + public SpawnBlockParticleSystem getBlockParticlePacket(double x, double y, double z, int id, @Nonnull BlockParticleEvent particleType) { + if (!(y < 0.0) && !(y >= 320.0)) { + return new SpawnBlockParticleSystem(id, particleType, new Position(x, y, z)); + } else { + throw new IllegalArgumentException("Y value is outside the world! " + x + ", " + y + ", " + z); + } + } + + @Nonnull + public UpdateBlockDamage getBlockDamagePacket(int x, int y, int z, float health, float healthDelta) { + if (y >= 0 && y < 320) { + return new UpdateBlockDamage(new BlockPosition(x, y, z), health, healthDelta); + } else { + throw new IllegalArgumentException("Y value is outside the world! " + x + ", " + y + ", " + z); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/WorldProvider.java b/src/com/hypixel/hytale/server/core/universe/world/WorldProvider.java new file mode 100644 index 0000000..2d5cdde --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/WorldProvider.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.server.core.universe.world; + +import javax.annotation.Nonnull; + +public interface WorldProvider { + @Nonnull + World getWorld(); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/accessor/BlockAccessor.java b/src/com/hypixel/hytale/server/core/universe/world/accessor/BlockAccessor.java new file mode 100644 index 0000000..23cfe35 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/accessor/BlockAccessor.java @@ -0,0 +1,242 @@ +package com.hypixel.hytale.server.core.universe.world.accessor; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.function.predicate.TriIntPredicate; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface BlockAccessor { + int getX(); + + int getZ(); + + ChunkAccessor getChunkAccessor(); + + int getBlock(int var1, int var2, int var3); + + default int getBlock(@Nonnull Vector3i pos) { + return this.getBlock(pos.getX(), pos.getY(), pos.getZ()); + } + + boolean setBlock(int var1, int var2, int var3, int var4, BlockType var5, int var6, int var7, int var8); + + default boolean setBlock(int x, int y, int z, int id, BlockType blockType) { + return this.setBlock(x, y, z, id, blockType, 0, 0, 0); + } + + default boolean setBlock(int x, int y, int z, String blockTypeKey) { + return this.setBlock(x, y, z, blockTypeKey, 0); + } + + default boolean setBlock(int x, int y, int z, String blockTypeKey, int settings) { + int index = BlockType.getAssetMap().getIndex(blockTypeKey); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + blockTypeKey); + } else { + return this.setBlock(x, y, z, index, settings); + } + } + + default boolean setBlock(int x, int y, int z, int id) { + return this.setBlock(x, y, z, id, 0); + } + + default boolean setBlock(int x, int y, int z, int id, int settings) { + return this.setBlock(x, y, z, id, BlockType.getAssetMap().getAsset(id), 0, 0, settings); + } + + default boolean setBlock(int x, int y, int z, @Nonnull BlockType blockType) { + return this.setBlock(x, y, z, blockType, 0); + } + + default boolean setBlock(int x, int y, int z, @Nonnull BlockType blockType, int settings) { + String key = blockType.getId(); + int index = BlockType.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } else { + return this.setBlock(x, y, z, index, blockType, 0, 0, settings); + } + } + + default boolean breakBlock(int x, int y, int z, int filler, int settings) { + if ((settings & 16) == 0) { + x -= FillerBlockUtil.unpackX(filler); + y -= FillerBlockUtil.unpackY(filler); + z -= FillerBlockUtil.unpackZ(filler); + } + + return this.setBlock(x, y, z, 0, BlockType.EMPTY, 0, 0, settings); + } + + default boolean breakBlock(int x, int y, int z) { + return this.breakBlock(x, y, z, 0); + } + + default boolean breakBlock(int x, int y, int z, int settings) { + return this.breakBlock(x, y, z, 0, settings); + } + + default boolean testBlocks(int x, int y, int z, @Nonnull BlockType blockTypeToTest, int rotation, @Nonnull TriIntPredicate predicate) { + int worldX = (this.getX() << 5) + (x & 31); + int worldZ = (this.getZ() << 5) + (z & 31); + return FillerBlockUtil.testFillerBlocks(BlockBoundingBoxes.getAssetMap().getAsset(blockTypeToTest.getHitboxTypeIndex()).get(rotation), (x1, y1, z1) -> { + int blockX = worldX + x1; + int blockY = y + y1; + int blockZ = worldZ + z1; + return predicate.test(blockX, blockY, blockZ); + }); + } + + default boolean testBlockTypes( + int x, int y, int z, @Nonnull BlockType blockTypeToTest, int rotation, @Nonnull IChunkAccessorSync.TestBlockFunction predicate + ) { + int worldX = (this.getX() << 5) + (x & 31); + int worldZ = (this.getZ() << 5) + (z & 31); + return this.testBlocks(x, y, z, blockTypeToTest, rotation, (blockX, blockY, blockZ) -> { + boolean sameChunk = ChunkUtil.isSameChunk(worldX, worldZ, blockX, blockZ); + int block; + int otherRotation; + int filler; + if (sameChunk) { + block = this.getBlock(blockX, blockY, blockZ); + otherRotation = this.getRotationIndex(blockX, blockY, blockZ); + filler = this.getFiller(blockX, blockY, blockZ); + } else { + BlockAccessor chunk = this.getChunkAccessor().getNonTickingChunk(ChunkUtil.indexChunkFromBlock(blockX, blockZ)); + block = chunk.getBlock(blockX, blockY, blockZ); + otherRotation = chunk.getRotationIndex(blockX, blockY, blockZ); + filler = chunk.getFiller(blockX, blockY, blockZ); + } + + return predicate.test(blockX, blockY, blockZ, BlockType.getAssetMap().getAsset(block), otherRotation, filler); + }); + } + + default boolean placeBlock( + int x, int y, int z, String originalBlockTypeKey, @Nonnull Rotation yaw, @Nonnull Rotation pitch, @Nonnull Rotation roll, int settings + ) { + return this.placeBlock(x, y, z, originalBlockTypeKey, RotationTuple.of(yaw, pitch, roll), settings, true); + } + + default boolean placeBlock(int x, int y, int z, String originalBlockTypeKey, @Nonnull RotationTuple rotationTuple, int settings, boolean validatePlacement) { + BlockTypeAssetMap assetMap = BlockType.getAssetMap(); + BlockType placedBlockType = assetMap.getAsset(originalBlockTypeKey); + int rotationIndex = rotationTuple.index(); + if (validatePlacement && !this.testPlaceBlock(x, y, z, placedBlockType, rotationIndex)) { + return false; + } else { + int setBlockSettings = 0; + if ((settings & 2) != 0) { + setBlockSettings |= 256; + } + + this.setBlock(x, y, z, assetMap.getIndex(originalBlockTypeKey), placedBlockType, rotationIndex, 0, setBlockSettings); + return true; + } + } + + default boolean placeBlock(int x, int y, int z, String blockTypeKey, @Nonnull Rotation yaw, @Nonnull Rotation pitch, @Nonnull Rotation roll) { + return this.placeBlock(x, y, z, blockTypeKey, yaw, pitch, roll, 0); + } + + default boolean testPlaceBlock(int x, int y, int z, @Nonnull BlockType blockTypeToTest, int rotationIndex) { + return this.testPlaceBlock(x, y, z, blockTypeToTest, rotationIndex, (x1, y1, z1, blockType, rotation, filler) -> false); + } + + default boolean testPlaceBlock( + int x, int y, int z, @Nonnull BlockType blockTypeToTest, int rotationIndex, @Nonnull IChunkAccessorSync.TestBlockFunction filter + ) { + return this.testBlockTypes(x, y, z, blockTypeToTest, rotationIndex, (blockX, blockY, blockZ, blockType, rotation, filler) -> { + if (blockType == BlockType.EMPTY) { + return true; + } else if (blockType.getMaterial() == BlockMaterial.Empty) { + return true; + } else { + return filler != 0 && blockType.isUnknown() ? true : filter.test(blockX, blockY, blockZ, blockType, rotation, filler); + } + }); + } + + @Nullable + default BlockType getBlockType(int x, int y, int z) { + return BlockType.getAssetMap().getAsset(this.getBlock(x, y, z)); + } + + @Nullable + default BlockType getBlockType(@Nonnull Vector3i block) { + return this.getBlockType(block.getX(), block.getY(), block.getZ()); + } + + boolean setTicking(int var1, int var2, int var3, boolean var4); + + boolean isTicking(int var1, int var2, int var3); + + @Nullable + @Deprecated + BlockState getState(int var1, int var2, int var3); + + @Nullable + @Deprecated + Holder getBlockComponentHolder(int var1, int var2, int var3); + + @Deprecated + void setState(int var1, int var2, int var3, BlockState var4, boolean var5); + + @Deprecated + default void setState(int x, int y, int z, BlockState state) { + this.setState(x, y, z, state, true); + } + + default void setBlockInteractionState(@Nonnull Vector3i blockPosition, @Nonnull BlockType blockType, @Nonnull String state) { + this.setBlockInteractionState(blockPosition.x, blockPosition.y, blockPosition.z, blockType, state, false); + } + + default void setBlockInteractionState(int x, int y, int z, @Nonnull BlockType blockType, @Nonnull String state, boolean force) { + String currentState = getCurrentInteractionState(blockType); + if (force || currentState == null || !currentState.equals(state)) { + BlockType newState = blockType.getBlockForState(state); + if (newState != null) { + int settings = 198; + int currentRotation = this.getRotationIndex(x, y, z); + this.setBlock(x, y, z, BlockType.getAssetMap().getIndex(newState.getId()), newState, currentRotation, 0, 198); + } + } + } + + @Nullable + static String getCurrentInteractionState(@Nonnull BlockType blockType) { + return blockType.getState() != null ? blockType.getStateForBlock(blockType) : null; + } + + @Deprecated(forRemoval = true) + int getFluidId(int var1, int var2, int var3); + + @Deprecated(forRemoval = true) + byte getFluidLevel(int var1, int var2, int var3); + + @Deprecated(forRemoval = true) + int getSupportValue(int var1, int var2, int var3); + + @Deprecated(forRemoval = true) + int getFiller(int var1, int var2, int var3); + + @Deprecated(forRemoval = true) + int getRotationIndex(int var1, int var2, int var3); + + @Deprecated(forRemoval = true) + default RotationTuple getRotation(int x, int y, int z) { + return RotationTuple.get(this.getRotationIndex(x, y, z)); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/accessor/ChunkAccessor.java b/src/com/hypixel/hytale/server/core/universe/world/accessor/ChunkAccessor.java new file mode 100644 index 0000000..3a0da48 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/accessor/ChunkAccessor.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.universe.world.accessor; + +import com.hypixel.hytale.math.util.ChunkUtil; + +@Deprecated +public interface ChunkAccessor extends IChunkAccessorSync { + default int getFluidId(int x, int y, int z) { + WorldChunk chunk = this.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + return chunk != null ? chunk.getFluidId(x, y, z) : 0; + } + + default boolean performBlockUpdate(int x, int y, int z) { + return this.performBlockUpdate(x, y, z, true); + } + + default boolean performBlockUpdate(int x, int y, int z, boolean allowPartialLoad) { + boolean success = true; + + for (int ix = -1; ix < 2; ix++) { + int wx = x + ix; + + for (int iz = -1; iz < 2; iz++) { + int wz = z + iz; + WorldChunk worldChunk = allowPartialLoad + ? this.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(wx, wz)) + : this.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(wx, wz)); + if (worldChunk == null) { + success = false; + } else { + for (int iy = -1; iy < 2; iy++) { + int wy = y + iy; + worldChunk.setTicking(wx, wy, wz, true); + } + } + } + } + + return success; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/accessor/EmptyBlockAccessor.java b/src/com/hypixel/hytale/server/core/universe/world/accessor/EmptyBlockAccessor.java new file mode 100644 index 0000000..20240fa --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/accessor/EmptyBlockAccessor.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.server.core.universe.world.accessor; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.function.predicate.TriIntPredicate; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nullable; + +public class EmptyBlockAccessor implements BlockAccessor { + public static final EmptyBlockAccessor INSTANCE = new EmptyBlockAccessor(); + + public EmptyBlockAccessor() { + } + + @Override + public int getX() { + throw new UnsupportedOperationException("Empty block accessor doesn't have a position!"); + } + + @Override + public int getZ() { + throw new UnsupportedOperationException("Empty block accessor doesn't have a position!"); + } + + @Override + public ChunkAccessor getChunkAccessor() { + throw new UnsupportedOperationException("Empty block accessor doesn't have a chunk accessor!"); + } + + @Override + public int getBlock(int x, int y, int z) { + return 0; + } + + @Override + public boolean setBlock(int x, int y, int z, int id, BlockType blockType, int rotation, int filler, int settings) { + return false; + } + + @Override + public boolean breakBlock(int x, int y, int z, int filler, int settings) { + return false; + } + + @Override + public boolean testBlocks(int x, int y, int z, BlockType blockTypeToTest, int rotation, TriIntPredicate predicate) { + return false; + } + + @Override + public boolean testBlockTypes(int x, int y, int z, BlockType blockTypeToTest, int rotation, IChunkAccessorSync.TestBlockFunction predicate) { + return false; + } + + @Override + public boolean testPlaceBlock(int x, int y, int z, BlockType blockTypeToTest, int rotation) { + return false; + } + + @Override + public boolean testPlaceBlock(int x, int y, int z, BlockType blockTypeToTest, int rotation, IChunkAccessorSync.TestBlockFunction filter) { + return false; + } + + @Override + public boolean setTicking(int x, int y, int z, boolean ticking) { + return false; + } + + @Override + public boolean isTicking(int x, int y, int z) { + return false; + } + + @Nullable + @Override + public BlockState getState(int x, int y, int z) { + return null; + } + + @Nullable + @Override + public Holder getBlockComponentHolder(int x, int y, int z) { + return null; + } + + @Override + public void setState(int x, int y, int z, BlockState state, boolean notify) { + } + + @Override + public int getFluidId(int x, int y, int z) { + return 0; + } + + @Override + public byte getFluidLevel(int x, int y, int z) { + return 0; + } + + @Override + public int getSupportValue(int x, int y, int z) { + return 0; + } + + @Override + public int getFiller(int x, int y, int z) { + return 0; + } + + @Override + public int getRotationIndex(int x, int y, int z) { + return 0; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/accessor/IChunkAccessorSync.java b/src/com/hypixel/hytale/server/core/universe/world/accessor/IChunkAccessorSync.java new file mode 100644 index 0000000..7526707 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/accessor/IChunkAccessorSync.java @@ -0,0 +1,123 @@ +package com.hypixel.hytale.server.core.universe.world.accessor; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockPosition; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@Deprecated +public interface IChunkAccessorSync { + @Nullable + WorldChunk getChunkIfInMemory(long var1); + + @Nullable + WorldChunk loadChunkIfInMemory(long var1); + + @Nullable + WorldChunk getChunkIfLoaded(long var1); + + @Nullable + WorldChunk getChunkIfNonTicking(long var1); + + @Nullable + WorldChunk getChunk(long var1); + + @Nullable + WorldChunk getNonTickingChunk(long var1); + + default int getBlock(@Nonnull Vector3i pos) { + return this.getChunk(ChunkUtil.indexChunkFromBlock(pos.getX(), pos.getZ())).getBlock(pos.getX(), pos.getY(), pos.getZ()); + } + + default int getBlock(int x, int y, int z) { + return this.getChunk(ChunkUtil.indexChunkFromBlock(x, z)).getBlock(x, y, z); + } + + @Nullable + default BlockType getBlockType(@Nonnull Vector3i pos) { + return this.getBlockType(pos.getX(), pos.getY(), pos.getZ()); + } + + @Nullable + default BlockType getBlockType(int x, int y, int z) { + WorldChunk chunk = this.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + int blockId = chunk.getBlock(x, y, z); + return BlockType.getAssetMap().getAsset(blockId); + } + + default void setBlock(int x, int y, int z, String blockTypeKey) { + this.setBlock(x, y, z, blockTypeKey, 0); + } + + default void setBlock(int x, int y, int z, String blockTypeKey, int settings) { + this.getChunk(ChunkUtil.indexChunkFromBlock(x, z)).setBlock(x, y, z, blockTypeKey, settings); + } + + default boolean breakBlock(int x, int y, int z, int settings) { + return this.getChunk(ChunkUtil.indexChunkFromBlock(x, z)).breakBlock(x, y, z, settings); + } + + default boolean testBlockTypes( + int x, int y, int z, @Nonnull BlockType blockTypeToTest, int rotation, @Nonnull IChunkAccessorSync.TestBlockFunction predicate + ) { + return this.getChunk(ChunkUtil.indexChunkFromBlock(x, z)).testBlockTypes(x, y, z, blockTypeToTest, rotation, predicate); + } + + default boolean testPlaceBlock(int x, int y, int z, @Nonnull BlockType blockTypeToTest, int rotation) { + return this.getChunk(ChunkUtil.indexChunkFromBlock(x, z)).testPlaceBlock(x, y, z, blockTypeToTest, rotation); + } + + default boolean testPlaceBlock( + int x, int y, int z, @Nonnull BlockType blockTypeToTest, int rotation, @Nonnull IChunkAccessorSync.TestBlockFunction predicate + ) { + return this.getChunk(ChunkUtil.indexChunkFromBlock(x, z)).testPlaceBlock(x, y, z, blockTypeToTest, rotation, predicate); + } + + @Nullable + @Deprecated + default BlockState getState(int x, int y, int z, boolean followFiller) { + WorldChunk chunk = this.getChunk(ChunkUtil.indexChunkFromBlock(x, z)); + if (followFiller) { + int filler = chunk.getFiller(x, y, z); + if (filler != 0) { + x -= FillerBlockUtil.unpackX(filler); + y -= FillerBlockUtil.unpackY(filler); + z -= FillerBlockUtil.unpackZ(filler); + } + } + + return y >= 0 && y < 320 ? chunk.getState(x, y, z) : null; + } + + @Nullable + default Holder getBlockComponentHolder(int x, int y, int z) { + return y >= 0 && y < 320 ? this.getChunk(ChunkUtil.indexChunkFromBlock(x, z)).getBlockComponentHolder(x, y, z) : null; + } + + default void setBlockInteractionState(@Nonnull Vector3i blockPosition, @Nonnull BlockType blockType, @Nonnull String state) { + this.getChunk(ChunkUtil.indexChunkFromBlock(blockPosition.x, blockPosition.z)).setBlockInteractionState(blockPosition, blockType, state); + } + + @Nonnull + @Deprecated(forRemoval = true) + default BlockPosition getBaseBlock(@Nonnull BlockPosition position) { + WorldChunk chunk = this.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(position.x, position.z)); + int filler = chunk.getFiller(position.x, position.y, position.z); + return filler != 0 + ? new BlockPosition( + position.x - FillerBlockUtil.unpackX(filler), position.y - FillerBlockUtil.unpackY(filler), position.z - FillerBlockUtil.unpackZ(filler) + ) + : position; + } + + @FunctionalInterface + public interface TestBlockFunction { + boolean test(int var1, int var2, int var3, BlockType var4, int var5, int var6); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/accessor/LocalCachedChunkAccessor.java b/src/com/hypixel/hytale/server/core/universe/world/accessor/LocalCachedChunkAccessor.java new file mode 100644 index 0000000..de3b799 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/accessor/LocalCachedChunkAccessor.java @@ -0,0 +1,218 @@ +package com.hypixel.hytale.server.core.universe.world.accessor; + +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkFlag; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class LocalCachedChunkAccessor implements OverridableChunkAccessor { + private final ChunkAccessor delegate; + private final int minX; + private final int minZ; + private final int length; + @Nonnull + private final WorldChunk[] chunks; + + @Nonnull + public static LocalCachedChunkAccessor atWorldCoords(ChunkAccessor delegate, int centerX, int centerZ, int blockRadius) { + int chunkRadius = ChunkUtil.chunkCoordinate(blockRadius) + 1; + return atChunkCoords(delegate, ChunkUtil.chunkCoordinate(centerX), ChunkUtil.chunkCoordinate(centerZ), chunkRadius); + } + + @Nonnull + public static LocalCachedChunkAccessor atChunkCoords(ChunkAccessor delegate, int centerX, int centerZ, int chunkRadius) { + return new LocalCachedChunkAccessor(delegate, centerX, centerZ, chunkRadius); + } + + @Nonnull + public static LocalCachedChunkAccessor atChunk(ChunkAccessor delegate, @Nonnull WorldChunk chunk, int chunkRadius) { + LocalCachedChunkAccessor accessor = new LocalCachedChunkAccessor(delegate, chunk.getX(), chunk.getZ(), chunkRadius); + accessor.overwrite(chunk); + return accessor; + } + + protected LocalCachedChunkAccessor(ChunkAccessor delegate, int centerX, int centerZ, int radius) { + this.delegate = delegate; + this.minX = centerX - radius; + this.minZ = centerZ - radius; + this.length = radius * 2 + 1; + this.chunks = new WorldChunk[this.length * this.length]; + } + + public ChunkAccessor getDelegate() { + return this.delegate; + } + + public int getMinX() { + return this.minX; + } + + public int getMinZ() { + return this.minZ; + } + + public int getLength() { + return this.length; + } + + public int getCenterX() { + return this.minX + this.length / 2; + } + + public int getCenterZ() { + return this.minZ + this.length / 2; + } + + public void cacheChunksInRadius() { + for (int xOffset = 0; xOffset < this.length; xOffset++) { + for (int zOffset = 0; zOffset < this.length; zOffset++) { + int arrayIndex = xOffset * this.length + zOffset; + WorldChunk chunk = this.chunks[arrayIndex]; + if (chunk == null) { + this.chunks[arrayIndex] = this.delegate.getChunkIfInMemory(ChunkUtil.indexChunk(this.minX + xOffset, this.minZ + zOffset)); + } + } + } + } + + public void overwrite(@Nonnull WorldChunk wc) { + int x = wc.getX(); + int z = wc.getZ(); + x -= this.minX; + z -= this.minZ; + if (x >= 0 && x < this.length && z >= 0 && z < this.length) { + int arrayIndex = x * this.length + z; + this.chunks[arrayIndex] = wc; + } + } + + public WorldChunk getChunkIfInMemory(long index) { + int x = ChunkUtil.xOfChunkIndex(index); + int z = ChunkUtil.zOfChunkIndex(index); + x -= this.minX; + z -= this.minZ; + if (x >= 0 && x < this.length && z >= 0 && z < this.length) { + int arrayIndex = x * this.length + z; + WorldChunk chunk = this.chunks[arrayIndex]; + return chunk != null ? chunk : (this.chunks[arrayIndex] = this.delegate.getChunkIfInMemory(index)); + } else { + return this.delegate.getChunkIfInMemory(index); + } + } + + @Nullable + public WorldChunk getChunkIfInMemory(int x, int z) { + int xOffset = x - this.minX; + int zOffset = z - this.minZ; + if (xOffset >= 0 && xOffset < this.length && zOffset >= 0 && zOffset < this.length) { + int arrayIndex = xOffset * this.length + zOffset; + WorldChunk chunk = this.chunks[arrayIndex]; + return chunk != null ? chunk : (this.chunks[arrayIndex] = this.delegate.getChunkIfInMemory(ChunkUtil.indexChunk(x, z))); + } else { + return this.delegate.getChunkIfInMemory(ChunkUtil.indexChunk(x, z)); + } + } + + public WorldChunk loadChunkIfInMemory(long index) { + int x = ChunkUtil.xOfChunkIndex(index); + int z = ChunkUtil.zOfChunkIndex(index); + x -= this.minX; + z -= this.minZ; + if (x >= 0 && x < this.length && z >= 0 && z < this.length) { + int arrayIndex = x * this.length + z; + WorldChunk chunk = this.chunks[arrayIndex]; + if (chunk != null) { + chunk.setFlag(ChunkFlag.TICKING, true); + return chunk; + } else { + return this.chunks[arrayIndex] = this.delegate.loadChunkIfInMemory(index); + } + } else { + return this.delegate.loadChunkIfInMemory(index); + } + } + + @Nullable + public WorldChunk getChunkIfLoaded(long index) { + int x = ChunkUtil.xOfChunkIndex(index); + int z = ChunkUtil.zOfChunkIndex(index); + x -= this.minX; + z -= this.minZ; + if (x >= 0 && x < this.length && z >= 0 && z < this.length) { + int arrayIndex = x * this.length + z; + WorldChunk chunk = this.chunks[arrayIndex]; + if (chunk == null) { + chunk = this.chunks[arrayIndex] = this.delegate.getChunkIfInMemory(index); + } + + return chunk != null && chunk.is(ChunkFlag.TICKING) ? chunk : null; + } else { + return this.delegate.getChunkIfLoaded(index); + } + } + + @Nullable + public WorldChunk getChunkIfLoaded(int x, int z) { + int xOffset = x - this.minX; + int zOffset = z - this.minZ; + if (xOffset >= 0 && xOffset < this.length && zOffset >= 0 && zOffset < this.length) { + int arrayIndex = xOffset * this.length + zOffset; + WorldChunk chunk = this.chunks[arrayIndex]; + if (chunk == null) { + chunk = this.chunks[arrayIndex] = this.delegate.getChunkIfInMemory(ChunkUtil.indexChunk(x, z)); + } + + return chunk != null && chunk.is(ChunkFlag.TICKING) ? chunk : null; + } else { + return this.delegate.getChunkIfLoaded(ChunkUtil.indexChunk(x, z)); + } + } + + @Nullable + public WorldChunk getChunkIfNonTicking(long index) { + int x = ChunkUtil.xOfChunkIndex(index); + int z = ChunkUtil.zOfChunkIndex(index); + x -= this.minX; + z -= this.minZ; + if (x >= 0 && x < this.length && z >= 0 && z < this.length) { + int arrayIndex = x * this.length + z; + WorldChunk chunk = this.chunks[arrayIndex]; + if (chunk == null) { + chunk = this.chunks[arrayIndex] = this.delegate.getChunkIfInMemory(index); + } + + return chunk != null && chunk.is(ChunkFlag.TICKING) ? null : chunk; + } else { + return this.delegate.getChunkIfNonTicking(index); + } + } + + public WorldChunk getChunk(long index) { + int x = ChunkUtil.xOfChunkIndex(index); + int z = ChunkUtil.zOfChunkIndex(index); + x -= this.minX; + z -= this.minZ; + if (x >= 0 && x < this.length && z >= 0 && z < this.length) { + int arrayIndex = x * this.length + z; + WorldChunk chunk = this.chunks[arrayIndex]; + return chunk != null ? chunk : (this.chunks[arrayIndex] = this.delegate.getChunk(index)); + } else { + return this.delegate.getChunk(index); + } + } + + public WorldChunk getNonTickingChunk(long index) { + int x = ChunkUtil.xOfChunkIndex(index); + int z = ChunkUtil.zOfChunkIndex(index); + x -= this.minX; + z -= this.minZ; + if (x >= 0 && x < this.length && z >= 0 && z < this.length) { + int arrayIndex = x * this.length + z; + WorldChunk chunk = this.chunks[arrayIndex]; + return chunk != null ? chunk : (this.chunks[arrayIndex] = this.delegate.getNonTickingChunk(index)); + } else { + return this.delegate.getNonTickingChunk(index); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/accessor/OverridableChunkAccessor.java b/src/com/hypixel/hytale/server/core/universe/world/accessor/OverridableChunkAccessor.java new file mode 100644 index 0000000..fd76d70 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/accessor/OverridableChunkAccessor.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.server.core.universe.world.accessor; + +public interface OverridableChunkAccessor extends ChunkAccessor { + void overwrite(X var1); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/AbstractCachedAccessor.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/AbstractCachedAccessor.java new file mode 100644 index 0000000..76f928f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/AbstractCachedAccessor.java @@ -0,0 +1,140 @@ +package com.hypixel.hytale.server.core.universe.world.chunk; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class AbstractCachedAccessor { + protected ComponentAccessor commandBuffer; + private int minX; + private int minY; + private int minZ; + private int length; + private Ref[] chunks; + private Ref[] sections; + private Component[][] sectionComponents; + + protected AbstractCachedAccessor(int numComponents) { + this.sectionComponents = new Component[numComponents][]; + } + + protected void init(ComponentAccessor commandBuffer, int cx, int cy, int cz, int radius) { + this.commandBuffer = commandBuffer; + radius = ChunkUtil.chunkCoordinate(radius) + 1; + this.minX = cx - radius; + this.minY = cy - radius; + this.minZ = cz - radius; + this.length = radius * 2 + 1; + int size2d = this.length * this.length; + int size3d = this.length * this.length * this.length; + if (this.chunks != null && this.chunks.length >= size2d) { + Arrays.fill(this.chunks, null); + } else { + this.chunks = new Ref[size2d]; + } + + for (int i = 0; i < this.sectionComponents.length; i++) { + Component[] sectionComps = this.sectionComponents[i]; + if (sectionComps != null && sectionComps.length >= size3d) { + Arrays.fill(sectionComps, null); + } else { + this.sectionComponents[i] = new Component[size3d]; + } + } + + if (this.sections != null && this.sections.length >= size3d) { + Arrays.fill(this.sections, null); + } else { + this.sections = new Ref[size3d]; + } + } + + protected void insertSection(Ref section, int cx, int cy, int cz) { + int x = cx - this.minX; + int y = cy - this.minY; + int z = cz - this.minZ; + int index3d = x + z * this.length + y * this.length * this.length; + if (index3d >= 0 && index3d < this.sections.length) { + this.sections[index3d] = section; + } + } + + protected void insertSectionComponent(int index, Component component, int cx, int cy, int cz) { + int x = cx - this.minX; + int y = cy - this.minY; + int z = cz - this.minZ; + int index3d = x + z * this.length + y * this.length * this.length; + if (index3d >= 0 && index3d < this.sectionComponents[index].length) { + this.sectionComponents[index][index3d] = component; + } + } + + @Nullable + public Ref getChunk(int cx, int cz) { + int x = cx - this.minX; + int z = cz - this.minZ; + int index = x + z * this.length; + if (index >= 0 && index < this.chunks.length) { + Ref chunk = this.chunks[index]; + if (chunk == null) { + this.chunks[index] = chunk = this.commandBuffer.getExternalData().getChunkReference(ChunkUtil.indexChunk(cx, cz)); + } + + return chunk; + } else { + return this.commandBuffer.getExternalData().getChunkReference(ChunkUtil.indexChunk(cx, cz)); + } + } + + @Nullable + public Ref getSection(int cx, int cy, int cz) { + if (cy >= 0 && cy < 10) { + int x = cx - this.minX; + int y = cy - this.minY; + int z = cz - this.minZ; + int index = x + z * this.length + y * this.length * this.length; + if (index >= 0 && index < this.sections.length) { + Ref section = this.sections[index]; + if (section == null) { + this.sections[index] = section = this.commandBuffer.getExternalData().getChunkSectionReference(this.commandBuffer, cx, cy, cz); + } + + return section; + } else { + return this.commandBuffer.getExternalData().getChunkSectionReference(this.commandBuffer, cx, cy, cz); + } + } else { + return null; + } + } + + @Nullable + protected > T getComponentSection(int cx, int cy, int cz, int typeIndex, @Nonnull ComponentType componentType) { + int x = cx - this.minX; + int y = cy - this.minY; + int z = cz - this.minZ; + int index = x + z * this.length + y * this.length * this.length; + if (index >= 0 && index < this.sections.length) { + T comp = (T)this.sectionComponents[typeIndex][index]; + if (comp == null) { + Ref section = this.getSection(cx, cy, cz); + if (section == null) { + return null; + } + + this.sectionComponents[typeIndex][index] = comp = this.commandBuffer.getComponent(section, componentType); + } + + return comp; + } else { + Ref section = this.getSection(cx, cy, cz); + return section == null ? null : this.commandBuffer.getComponent(section, componentType); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/BlockChunk.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/BlockChunk.java new file mode 100644 index 0000000..b3bd242 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/BlockChunk.java @@ -0,0 +1,615 @@ +package com.hypixel.hytale.server.core.universe.world.chunk; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.function.predicate.ObjectPositionBlockFunction; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.CachedPacket; +import com.hypixel.hytale.protocol.Opacity; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.protocol.packets.world.SetChunkEnvironments; +import com.hypixel.hytale.protocol.packets.world.SetChunkHeightmap; +import com.hypixel.hytale.protocol.packets.world.SetChunkTintmap; +import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.modules.LegacyModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.chunk.environment.EnvironmentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.environment.EnvironmentColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.palette.IntBytePalette; +import com.hypixel.hytale.server.core.universe.world.chunk.palette.ShortBytePalette; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.util.io.ByteBufUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.Int2ShortMap.Entry; +import java.lang.ref.SoftReference; +import java.time.Instant; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockChunk implements Component { + public static final int VERSION = 3; + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockChunk.class, BlockChunk::new) + .versioned() + .codecVersion(3) + .append(new KeyedCodec<>("Data", Codec.BYTE_ARRAY), BlockChunk::deserialize, BlockChunk::serialize) + .add() + .build(); + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static boolean SEND_LOCAL_LIGHTING_DATA = true; + public static boolean SEND_GLOBAL_LIGHTING_DATA = false; + private long index; + private int x; + private int z; + private final ShortBytePalette height; + private final IntBytePalette tint; + @Deprecated(forRemoval = true) + private BlockSection[] chunkSections; + @Nullable + @Deprecated(forRemoval = true) + private BlockSection[] migratedChunkSections; + private EnvironmentChunk environments; + private boolean needsPhysics = true; + private boolean needsSaving = false; + @Nullable + private transient SoftReference>> cachedHeightmapPacket; + @Nullable + private transient SoftReference>> cachedTintmapPacket; + @Nullable + private transient SoftReference>> cachedEnvironmentsPacket; + + public static ComponentType getComponentType() { + return LegacyModule.get().getBlockChunkComponentType(); + } + + private BlockChunk() { + this(new ShortBytePalette(), new IntBytePalette(), new EnvironmentChunk(), new BlockSection[10]); + } + + public void load(int x, int z) { + this.x = x; + this.z = z; + this.index = ChunkUtil.indexChunk(x, z); + } + + public BlockChunk(int x, int z) { + this(x, z, new ShortBytePalette(), new IntBytePalette(), new EnvironmentChunk()); + } + + public BlockChunk(int x, int z, ShortBytePalette height, IntBytePalette tint, EnvironmentChunk environments) { + this(height, tint, environments, new BlockSection[10]); + this.x = x; + this.z = z; + this.index = ChunkUtil.indexChunk(x, z); + } + + public BlockChunk(ShortBytePalette height, IntBytePalette tint, EnvironmentChunk environments, BlockSection[] chunkSections) { + this.height = height; + this.tint = tint; + this.environments = environments; + this.chunkSections = chunkSections; + } + + @Override + public Component clone() { + throw new UnsupportedOperationException("Not implemented!"); + } + + @Nonnull + @Override + public Component cloneSerializable() { + return this; + } + + public long getIndex() { + return this.index; + } + + public int getX() { + return this.x; + } + + public int getZ() { + return this.z; + } + + public EnvironmentChunk getEnvironmentChunk() { + return this.environments; + } + + public void setEnvironmentChunk(EnvironmentChunk environmentChunk) { + this.environments = environmentChunk; + } + + public short getHeight(int x, int z) { + return this.height.get(x, z); + } + + public short getHeight(int index) { + return this.height.get(index); + } + + public void setHeight(int x, int z, short height) { + this.height.set(x, z, height); + this.cachedHeightmapPacket = null; + this.markNeedsSaving(); + } + + public void updateHeightmap() { + for (int cx = 0; cx < 32; cx++) { + for (int cz = 0; cz < 32; cz++) { + this.updateHeight(cx, cz); + } + } + } + + public short updateHeight(int x, int z) { + return this.updateHeight(x, z, (short)320); + } + + public short updateHeight(int x, int z, short startY) { + short y = startY; + + while (--y > 0) { + BlockSection section = this.getSectionAtBlockY(y); + if (section.isSolidAir()) { + y = (short)(ChunkUtil.indexSection(y) * 32); + if (y == 0) { + break; + } + } else { + int blockId = section.get(x, y, z); + BlockType type = BlockType.getAssetMap().getAsset(blockId); + if (blockId != 0 && type != null && type.getOpacity() != Opacity.Transparent) { + break; + } + } + } + + this.setHeight(x, z, y); + return y; + } + + @Deprecated(forRemoval = true) + public void loadFromHolder(@Nonnull Holder holder) { + ChunkColumn column = holder.getComponent(ChunkColumn.getComponentType()); + if (column != null) { + Holder[] sections = column.getSectionHolders(); + + for (int i = 0; i < sections.length; i++) { + Holder section = sections[i]; + this.chunkSections[i] = this.migratedChunkSections != null + ? this.migratedChunkSections[i] + : section.ensureAndGetComponent(BlockSection.getComponentType()); + } + } + } + + @Deprecated(forRemoval = false) + public BlockSection getSectionAtIndex(int index) { + if (index >= 0 && index < this.chunkSections.length) { + return this.chunkSections[index]; + } else { + throw new IllegalArgumentException("Section index must >=0 and <" + this.chunkSections.length + " but was given " + index); + } + } + + @Deprecated(forRemoval = false) + public BlockSection getSectionAtBlockY(int y) { + int index = ChunkUtil.indexSection(y); + if (index >= 0 && index < this.chunkSections.length) { + return this.chunkSections[index]; + } else { + throw new IllegalArgumentException("Section y must >=0 and <320 but was given " + y); + } + } + + @Deprecated(forRemoval = false) + public BlockSection[] getChunkSections() { + return this.chunkSections; + } + + public int getSectionCount() { + return this.chunkSections.length; + } + + public int getTint(int x, int z) { + return this.tint.get(x, z); + } + + public void setTint(int x, int z, int tint) { + this.tint.set(x, z, tint); + this.cachedTintmapPacket = null; + this.markNeedsSaving(); + } + + public int getEnvironment(@Nonnull Vector3d position) { + return this.getEnvironment(MathUtil.floor(position.x), MathUtil.floor(position.y), MathUtil.floor(position.z)); + } + + public int getEnvironment(@Nonnull Vector3i position) { + return this.getEnvironment(position.x, position.y, position.z); + } + + public int getEnvironment(int x, int y, int z) { + return y >= 0 && y < 320 ? this.environments.get(x, y, z) : 0; + } + + public EnvironmentColumn getEnvironmentColumn(int x, int z) { + return this.environments.get(x, z); + } + + public void setEnvironment(int x, int y, int z, int environment) { + if (y >= 0 && y < 320) { + this.environments.set(x, y, z, environment); + this.cachedEnvironmentsPacket = null; + this.markNeedsSaving(); + } + } + + public byte getRedBlockLight(int x, int y, int z) { + return y >= 0 && y < 320 ? this.getSectionAtBlockY(y).getGlobalLight().getRedBlockLight(x, y, z) : 0; + } + + public byte getGreenBlockLight(int x, int y, int z) { + return y >= 0 && y < 320 ? this.getSectionAtBlockY(y).getGlobalLight().getGreenBlockLight(x, y, z) : 0; + } + + public byte getBlueBlockLight(int x, int y, int z) { + return y >= 0 && y < 320 ? this.getSectionAtBlockY(y).getGlobalLight().getBlueBlockLight(x, y, z) : 0; + } + + public short getBlockLight(int x, int y, int z) { + return y >= 0 && y < 320 ? this.getSectionAtBlockY(y).getGlobalLight().getBlockLight(x, y, z) : 0; + } + + public byte getSkyLight(int x, int y, int z) { + return y >= 0 && y < 320 ? this.getSectionAtBlockY(y).getGlobalLight().getSkyLight(x, y, z) : 0; + } + + public byte getBlockLightIntensity(int x, int y, int z) { + return y >= 0 && y < 320 ? this.getSectionAtBlockY(y).getGlobalLight().getBlockLightIntensity(x, y, z) : 0; + } + + public int getBlock(int x, int y, int z) { + return y >= 0 && y < 320 ? this.getSectionAtBlockY(y).get(x, y, z) : 0; + } + + public boolean setBlock(int x, int y, int z, int blockId, int rotation, int filler) { + if (y >= 0 && y < 320) { + int sectionIndex = ChunkUtil.indexSection(y); + BlockSection section = this.chunkSections[sectionIndex]; + boolean changed = section.set(x, y, z, blockId, rotation, filler); + if (changed) { + this.invalidateChunkSection(sectionIndex); + this.markNeedsSaving(); + } + + return changed; + } else { + throw new IllegalArgumentException(String.format("Failed to set block at %d, %d, %d to %d because it is outside the world bounds", x, y, z, blockId)); + } + } + + public boolean contains(int blockId) { + return this.count(blockId) != 0; + } + + public int count(int blockId) { + int count = 0; + + for (BlockSection section : this.chunkSections) { + count += section.count(blockId); + } + + return count; + } + + @Nonnull + public Int2IntMap blockCounts() { + Int2IntMap map = new Int2IntOpenHashMap(); + + for (BlockSection section : this.chunkSections) { + for (Entry entry : section.valueCounts().int2ShortEntrySet()) { + int blockId = entry.getIntKey(); + short count = entry.getShortValue(); + map.mergeInt(blockId, count, Integer::sum); + } + } + + return map; + } + + @Nonnull + public IntSet blocks() { + IntSet set = new IntOpenHashSet(); + + for (BlockSection section : this.chunkSections) { + set.addAll(section.values()); + } + + return set; + } + + public int blockCount() { + return this.blocks().size(); + } + + public void preTick(Instant gameTime) { + for (int sectionIndex = 0; sectionIndex < this.chunkSections.length; sectionIndex++) { + this.chunkSections[sectionIndex].preTick(gameTime); + } + } + + public int forEachTicking(T t, V v, ObjectPositionBlockFunction acceptor) { + int ticked = 0; + + for (int sectionIndex = 0; sectionIndex < this.chunkSections.length; sectionIndex++) { + BlockSection section = this.chunkSections[sectionIndex]; + ticked += section.forEachTicking(t, v, sectionIndex, acceptor); + } + + if (ticked > 0) { + this.markNeedsSaving(); + } + + return ticked; + } + + public void mergeTickingBlocks() { + for (BlockSection section : this.chunkSections) { + section.mergeTickingBlocks(); + } + } + + public boolean setTicking(int x, int y, int z, boolean ticking) { + if (y >= 0 && y < 320) { + boolean changed = this.getSectionAtBlockY(y).setTicking(x, y, z, ticking); + if (changed) { + this.markNeedsSaving(); + } + + return changed; + } else { + return false; + } + } + + public boolean isTicking(int x, int y, int z) { + return y >= 0 && y < 320 ? this.getSectionAtBlockY(y).isTicking(x, y, z) : false; + } + + public int getTickingBlocksCount() { + int ticking = 0; + + for (BlockSection chunkSection : this.chunkSections) { + ticking += chunkSection.getTickingBlocksCount(); + } + + return ticking; + } + + public boolean setNeighbourBlocksTicking(int x, int y, int z) { + boolean success = true; + + for (int ix = -1; ix < 2; ix++) { + int wx = x + ix; + + for (int iz = -1; iz < 2; iz++) { + int wz = z + iz; + if (!ChunkUtil.isInsideChunkRelative(wx, wz)) { + success = false; + } else { + for (int iy = -1; iy < 2; iy++) { + int wy = y + iy; + this.setTicking(wx, wy, wz, true); + } + } + } + } + + return success; + } + + public void markNeedsSaving() { + this.needsSaving = true; + } + + public boolean getNeedsSaving() { + return this.needsSaving; + } + + public boolean consumeNeedsSaving() { + boolean out = this.needsSaving; + this.needsSaving = false; + return out; + } + + public void markNeedsPhysics() { + this.needsPhysics = true; + } + + public boolean consumeNeedsPhysics() { + boolean old = this.needsPhysics; + this.needsPhysics = false; + return old; + } + + public void invalidateChunkSection(int sectionIndex) { + this.chunkSections[sectionIndex].invalidate(); + } + + @Nullable + @Deprecated(forRemoval = true) + public BlockSection[] takeMigratedSections() { + BlockSection[] temp = this.migratedChunkSections; + this.migratedChunkSections = null; + return temp; + } + + @Nullable + @Deprecated(forRemoval = true) + public BlockSection[] getMigratedSections() { + return this.migratedChunkSections; + } + + private byte[] serialize(ExtraInfo extraInfo) { + ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(); + + try { + buf.writeBoolean(this.needsPhysics); + this.height.serialize(buf); + this.tint.serialize(buf); + return ByteBufUtil.getBytesRelease(buf); + } catch (Throwable var4) { + buf.release(); + throw SneakyThrow.sneakyThrow(var4); + } + } + + private void deserialize(@Nonnull byte[] bytes, @Nonnull ExtraInfo extraInfo) { + ByteBuf buf = Unpooled.wrappedBuffer(bytes); + this.needsPhysics = buf.readBoolean(); + this.height.deserialize(buf); + this.tint.deserialize(buf); + if (extraInfo.getVersion() <= 2) { + int sections = buf.readInt(); + this.migratedChunkSections = new BlockSection[sections]; + + for (int y = 0; y < sections; y++) { + BlockSection section = new BlockSection(); + section.deserialize(BlockType.KEY_DESERIALIZER, buf, extraInfo.getVersion()); + this.migratedChunkSections[y] = section; + } + } + } + + @Nonnull + private CompletableFuture> getCachedHeightmapPacket() { + SoftReference>> ref = this.cachedHeightmapPacket; + CompletableFuture> future = ref != null ? ref.get() : null; + if (future != null) { + return future; + } else { + future = CompletableFuture.supplyAsync(() -> { + SetChunkHeightmap packet = new SetChunkHeightmap(this.x, this.z, this.height.serialize()); + return CachedPacket.cache(packet); + }); + this.cachedHeightmapPacket = new SoftReference<>(future); + return future; + } + } + + @Nonnull + private CompletableFuture> getCachedTintsPacket() { + SoftReference>> ref = this.cachedTintmapPacket; + CompletableFuture> future = ref != null ? ref.get() : null; + if (future != null) { + return future; + } else { + future = CompletableFuture.supplyAsync(() -> { + SetChunkTintmap packet = new SetChunkTintmap(this.x, this.z, this.tint.serialize()); + return CachedPacket.cache(packet); + }); + this.cachedTintmapPacket = new SoftReference<>(future); + return future; + } + } + + @Nonnull + private CompletableFuture> getCachedEnvironmentsPacket() { + SoftReference>> ref = this.cachedEnvironmentsPacket; + CompletableFuture> future = ref != null ? ref.get() : null; + if (future != null) { + return future; + } else { + future = CompletableFuture.supplyAsync(() -> { + SetChunkEnvironments packet = new SetChunkEnvironments(this.x, this.z, this.environments.serializeProtocol()); + return CachedPacket.cache(packet); + }); + this.cachedEnvironmentsPacket = new SoftReference<>(future); + return future; + } + } + + public static class LoadBlockChunkPacketSystem extends ChunkStore.LoadFuturePacketDataQuerySystem { + private final ComponentType componentType; + + public LoadBlockChunkPacketSystem(ComponentType blockChunkComponentType) { + this.componentType = blockChunkComponentType; + } + + @Override + public Query getQuery() { + return this.componentType; + } + + public void fetch( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + Store store, + CommandBuffer commandBuffer, + PlayerRef player, + @Nonnull List> results + ) { + BlockChunk component = archetypeChunk.getComponent(index, this.componentType); + results.add(component.getCachedHeightmapPacket().exceptionally(throwable -> { + if (throwable != null) { + BlockChunk.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception when compressing chunk heightmap:"); + } + + return null; + }).thenApply(Function.identity())); + results.add(component.getCachedTintsPacket().exceptionally(throwable -> { + if (throwable != null) { + BlockChunk.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception when compressing chunk tints:"); + } + + return null; + }).thenApply(Function.identity())); + results.add(component.getCachedEnvironmentsPacket().exceptionally(throwable -> { + if (throwable != null) { + BlockChunk.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception when compressing chunk environments:"); + } + + return null; + }).thenApply(Function.identity())); + + for (int y = 0; y < component.chunkSections.length; y++) { + BlockSection section = component.chunkSections[y]; + results.add(section.getCachedChunkPacket(component.getX(), y, component.getZ()).exceptionally(throwable -> { + if (throwable != null) { + BlockChunk.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception while compressing set chunk (%d, %d):", component.x, component.z); + } + + return null; + }).thenApply(Function.identity())); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/BlockComponentChunk.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/BlockComponentChunk.java new file mode 100644 index 0000000..24a6992 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/BlockComponentChunk.java @@ -0,0 +1,417 @@ +package com.hypixel.hytale.server.core.universe.world.chunk; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.Int2ObjectMapCodec; +import com.hypixel.hytale.codec.store.StoredCodec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.NonTicking; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.server.core.modules.LegacyModule; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; +import it.unimi.dsi.fastutil.objects.ObjectCollection; +import java.util.List; +import java.util.Objects; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockComponentChunk implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockComponentChunk.class, BlockComponentChunk::new) + .addField( + new KeyedCodec<>("BlockComponents", new Int2ObjectMapCodec<>(new StoredCodec<>(ChunkStore.HOLDER_CODEC_KEY), Int2ObjectOpenHashMap::new)), + (entityChunk, map) -> { + entityChunk.entityHolders.clear(); + entityChunk.entityHolders.putAll(map); + }, + entityChunk -> { + if (entityChunk.entityReferences.isEmpty()) { + return entityChunk.entityHolders; + } else { + Int2ObjectMap> map = new Int2ObjectOpenHashMap<>(entityChunk.entityHolders.size() + entityChunk.entityReferences.size()); + map.putAll(entityChunk.entityHolders); + + for (Entry> entry : entityChunk.entityReferences.int2ObjectEntrySet()) { + Ref reference = entry.getValue(); + Store store = reference.getStore(); + if (store.getArchetype(reference).hasSerializableComponents(store.getRegistry().getData())) { + map.put(entry.getIntKey(), store.copySerializableEntity(reference)); + } + } + + return map; + } + } + ) + .build(); + @Nonnull + private final Int2ObjectMap> entityHolders; + @Nonnull + private final Int2ObjectMap> entityReferences; + @Nonnull + private final Int2ObjectMap> entityHoldersUnmodifiable; + @Nonnull + private final Int2ObjectMap> entityReferencesUnmodifiable; + private boolean needsSaving; + + public static ComponentType getComponentType() { + return LegacyModule.get().getBlockComponentChunkComponentType(); + } + + public BlockComponentChunk() { + this.entityHolders = new Int2ObjectOpenHashMap<>(); + this.entityReferences = new Int2ObjectOpenHashMap<>(); + this.entityHoldersUnmodifiable = Int2ObjectMaps.unmodifiable(this.entityHolders); + this.entityReferencesUnmodifiable = Int2ObjectMaps.unmodifiable(this.entityReferences); + } + + public BlockComponentChunk(@Nonnull Int2ObjectMap> entityHolders, @Nonnull Int2ObjectMap> entityReferences) { + this.entityHolders = entityHolders; + this.entityReferences = entityReferences; + this.entityHoldersUnmodifiable = Int2ObjectMaps.unmodifiable(entityHolders); + this.entityReferencesUnmodifiable = Int2ObjectMaps.unmodifiable(entityReferences); + } + + @Nonnull + @Override + public Component clone() { + Int2ObjectOpenHashMap> entityHoldersClone = new Int2ObjectOpenHashMap<>(this.entityHolders.size() + this.entityReferences.size()); + + for (Entry> entry : this.entityHolders.int2ObjectEntrySet()) { + entityHoldersClone.put(entry.getIntKey(), entry.getValue().clone()); + } + + for (Entry> entry : this.entityReferences.int2ObjectEntrySet()) { + Ref reference = entry.getValue(); + entityHoldersClone.put(entry.getIntKey(), reference.getStore().copyEntity(reference)); + } + + return new BlockComponentChunk(entityHoldersClone, new Int2ObjectOpenHashMap<>()); + } + + @Nonnull + @Override + public Component cloneSerializable() { + Int2ObjectOpenHashMap> entityHoldersClone = new Int2ObjectOpenHashMap<>(this.entityHolders.size() + this.entityReferences.size()); + + for (Entry> entry : this.entityHolders.int2ObjectEntrySet()) { + entityHoldersClone.put(entry.getIntKey(), entry.getValue().clone()); + } + + for (Entry> entry : this.entityReferences.int2ObjectEntrySet()) { + Ref reference = entry.getValue(); + entityHoldersClone.put(entry.getIntKey(), reference.getStore().copySerializableEntity(reference)); + } + + return new BlockComponentChunk(entityHoldersClone, new Int2ObjectOpenHashMap<>()); + } + + @Nonnull + public Int2ObjectMap> getEntityHolders() { + return this.entityHoldersUnmodifiable; + } + + @Nullable + public Holder getEntityHolder(int index) { + return this.entityHolders.get(index); + } + + public void addEntityHolder(int index, @Nonnull Holder holder) { + if (this.entityReferences.containsKey(index)) { + throw new IllegalArgumentException("Duplicate block components at: " + index); + } else if (this.entityHolders.putIfAbsent(index, Objects.requireNonNull(holder)) != null) { + throw new IllegalArgumentException("Duplicate block components (entity holder) at: " + index); + } else { + this.markNeedsSaving(); + } + } + + public void storeEntityHolder(int index, @Nonnull Holder holder) { + if (this.entityHolders.putIfAbsent(index, Objects.requireNonNull(holder)) != null) { + throw new IllegalArgumentException("Duplicate block components (entity holder) at: " + index); + } + } + + @Nullable + public Holder removeEntityHolder(int index) { + Holder reference = this.entityHolders.remove(index); + if (reference != null) { + this.markNeedsSaving(); + } + + return reference; + } + + @Nonnull + public Int2ObjectMap> getEntityReferences() { + return this.entityReferencesUnmodifiable; + } + + @Nullable + public Ref getEntityReference(int index) { + return this.entityReferences.get(index); + } + + public void addEntityReference(int index, @Nonnull Ref reference) { + reference.validate(); + if (this.entityHolders.containsKey(index)) { + throw new IllegalArgumentException("Duplicate block components at: " + index); + } else if (this.entityReferences.putIfAbsent(index, Objects.requireNonNull(reference)) != null) { + throw new IllegalArgumentException("Duplicate block components (entity reference) at: " + index); + } else { + this.markNeedsSaving(); + } + } + + public void loadEntityReference(int index, @Nonnull Ref reference) { + reference.validate(); + if (this.entityHolders.containsKey(index)) { + throw new IllegalArgumentException("Duplicate block components at: " + index); + } else if (this.entityReferences.putIfAbsent(index, Objects.requireNonNull(reference)) != null) { + throw new IllegalArgumentException("Duplicate block components (entity reference) at: " + index); + } + } + + public void removeEntityReference(int index, Ref reference) { + if (this.entityReferences.remove(index, reference)) { + this.markNeedsSaving(); + } + } + + public void unloadEntityReference(int index, Ref reference) { + this.entityReferences.remove(index, reference); + } + + @Nullable + public Int2ObjectMap> takeEntityHolders() { + if (this.entityHolders.isEmpty()) { + return null; + } else { + Int2ObjectOpenHashMap> holders = new Int2ObjectOpenHashMap<>(this.entityHolders); + this.entityHolders.clear(); + return holders; + } + } + + @Nullable + public Int2ObjectMap> takeEntityReferences() { + if (this.entityReferences.isEmpty()) { + return null; + } else { + Int2ObjectOpenHashMap> holders = new Int2ObjectOpenHashMap<>(this.entityReferences); + this.entityReferences.clear(); + return holders; + } + } + + @Nullable + public > T getComponent(int index, @Nonnull ComponentType componentType) { + Ref reference = this.entityReferences.get(index); + if (reference != null) { + return reference.getStore().getComponent(reference, componentType); + } else { + Holder holder = this.entityHolders.get(index); + return holder != null ? holder.getComponent(componentType) : null; + } + } + + public boolean hasComponents(int index) { + return this.entityReferences.containsKey(index) || this.entityHolders.containsKey(index); + } + + public boolean getNeedsSaving() { + return this.needsSaving; + } + + public void markNeedsSaving() { + this.needsSaving = true; + } + + public boolean consumeNeedsSaving() { + boolean out = this.needsSaving; + this.needsSaving = false; + return out; + } + + public static class BlockComponentChunkLoadingSystem extends RefChangeSystem> { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final Archetype archetype = Archetype.of(WorldChunk.getComponentType(), BlockComponentChunk.getComponentType()); + + public BlockComponentChunkLoadingSystem() { + } + + @Override + public Query getQuery() { + return this.archetype; + } + + @Nonnull + @Override + public ComponentType> componentType() { + return ChunkStore.REGISTRY.getNonTickingComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, + @Nonnull NonTicking component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + BlockComponentChunk blockComponentChunk = store.getComponent(ref, BlockComponentChunk.getComponentType()); + Int2ObjectMap> entityReferences = blockComponentChunk.takeEntityReferences(); + if (entityReferences != null) { + int size = entityReferences.size(); + int[] indexes = new int[size]; + Ref[] references = new Ref[size]; + int j = 0; + + for (Entry> entry : entityReferences.int2ObjectEntrySet()) { + indexes[j] = entry.getIntKey(); + references[j] = entry.getValue(); + j++; + } + + ComponentRegistry.Data data = ChunkStore.REGISTRY.getData(); + + for (int i = 0; i < size; i++) { + if (store.getArchetype(references[i]).hasSerializableComponents(data)) { + Holder holder = ChunkStore.REGISTRY.newHolder(); + commandBuffer.removeEntity(references[i], holder, RemoveReason.UNLOAD); + blockComponentChunk.storeEntityHolder(indexes[i], holder); + } else { + commandBuffer.removeEntity(references[i], RemoveReason.UNLOAD); + } + } + } + } + + public void onComponentSet( + @Nonnull Ref ref, + NonTicking oldComponent, + @Nonnull NonTicking newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, + @Nonnull NonTicking component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + WorldChunk chunk = store.getComponent(ref, WorldChunk.getComponentType()); + BlockComponentChunk blockComponentChunk = store.getComponent(ref, BlockComponentChunk.getComponentType()); + Int2ObjectMap> entityHolders = blockComponentChunk.takeEntityHolders(); + if (entityHolders != null) { + int holderCount = entityHolders.size(); + int[] indexes = new int[holderCount]; + Holder[] holders = new Holder[holderCount]; + int j = 0; + + for (Entry> entry : entityHolders.int2ObjectEntrySet()) { + indexes[j] = entry.getIntKey(); + holders[j] = entry.getValue(); + j++; + } + + for (int i = holderCount - 1; i >= 0; i--) { + Holder holder = holders[i]; + if (holder.getArchetype().isEmpty()) { + LOGGER.at(Level.SEVERE).log("Empty archetype entity holder: %s (#%d)", holder, i); + holders[i] = holders[--holderCount]; + holders[holderCount] = holder; + } else { + int index = indexes[i]; + int x = ChunkUtil.xFromBlockInColumn(index); + int y = ChunkUtil.yFromBlockInColumn(index); + int z = ChunkUtil.zFromBlockInColumn(index); + holder.putComponent(BlockModule.BlockStateInfo.getComponentType(), new BlockModule.BlockStateInfo(index, ref)); + BlockState state = BlockState.getBlockState(holder); + if (state != null) { + state.setPosition(chunk, new Vector3i(x, y, z)); + } + } + } + + commandBuffer.addEntities(holders, AddReason.LOAD); + } + } + } + + public static class LoadBlockComponentPacketSystem extends ChunkStore.LoadPacketDataQuerySystem { + private final ComponentType componentType; + + public LoadBlockComponentPacketSystem(ComponentType blockComponentChunkComponentType) { + this.componentType = blockComponentChunkComponentType; + } + + @Override + public Query getQuery() { + return this.componentType; + } + + public void fetch( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + CommandBuffer commandBuffer, + PlayerRef player, + @Nonnull List results + ) { + BlockComponentChunk component = archetypeChunk.getComponent(index, this.componentType); + ObjectCollection> references = component.entityReferences.values(); + Store componentStore = store.getExternalData().getWorld().getChunkStore().getStore(); + componentStore.fetch(references, ChunkStore.LOAD_PACKETS_DATA_QUERY_SYSTEM_TYPE, player, results); + } + } + + public static class UnloadBlockComponentPacketSystem extends ChunkStore.UnloadPacketDataQuerySystem { + private final ComponentType componentType; + + public UnloadBlockComponentPacketSystem(ComponentType blockComponentChunkComponentType) { + this.componentType = blockComponentChunkComponentType; + } + + @Override + public Query getQuery() { + return this.componentType; + } + + public void fetch( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + CommandBuffer commandBuffer, + PlayerRef player, + @Nonnull List results + ) { + BlockComponentChunk component = archetypeChunk.getComponent(index, this.componentType); + ObjectCollection> references = component.entityReferences.values(); + Store componentStore = store.getExternalData().getWorld().getChunkStore().getStore(); + componentStore.fetch(references, ChunkStore.UNLOAD_PACKETS_DATA_QUERY_SYSTEM_TYPE, player, results); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/BlockRotationUtil.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/BlockRotationUtil.java new file mode 100644 index 0000000..c6790d4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/BlockRotationUtil.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.server.core.universe.world.chunk; + +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFlipType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.VariantRotation; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockRotationUtil { + public BlockRotationUtil() { + } + + @Nullable + public static RotationTuple getFlipped( + @Nonnull RotationTuple blockRotation, @Nullable BlockFlipType flipType, @Nonnull Axis axis, @Nonnull VariantRotation variantRotation + ) { + Rotation rotationYaw = blockRotation.yaw(); + Rotation rotationPitch = blockRotation.pitch(); + Rotation rotationRoll = blockRotation.roll(); + if (flipType != null) { + rotationYaw = flipType.flipYaw(rotationYaw, axis); + } + + boolean preventPitchRotation = axis != Axis.Y; + return get(rotationYaw, rotationPitch, rotationRoll, axis, Rotation.OneEighty, variantRotation, preventPitchRotation); + } + + @Nullable + public static RotationTuple getRotated(@Nonnull RotationTuple blockRotation, @Nonnull Axis axis, Rotation rotation, @Nonnull VariantRotation variantRotation) { + return get(blockRotation.yaw(), blockRotation.pitch(), blockRotation.roll(), axis, rotation, variantRotation, false); + } + + @Nullable + private static RotationTuple get( + @Nonnull Rotation rotationYaw, + @Nonnull Rotation rotationPitch, + @Nonnull Rotation rotationRoll, + @Nonnull Axis axis, + Rotation rotation, + @Nonnull VariantRotation variantRotation, + boolean preventPitchRotation + ) { + RotationTuple rotationPair = null; + switch (axis) { + case X: + RotationTuple rotateX = variantRotation.rotateX(RotationTuple.of(rotationYaw, rotationPitch), rotation); + rotationPair = variantRotation.verify(rotateX); + break; + case Y: + rotationPair = variantRotation.verify(RotationTuple.of(rotationYaw.add(rotation), rotationPitch)); + break; + case Z: + RotationTuple rotateZ = variantRotation.rotateZ(RotationTuple.of(rotationYaw, rotationPitch), rotation); + rotationPair = variantRotation.verify(rotateZ); + } + + if (rotationPair == null) { + return null; + } else { + if (preventPitchRotation) { + rotationPair = RotationTuple.of(rotationPair.yaw(), rotationPitch); + } + + return rotationPair; + } + } + + public static int getFlippedFiller(int filler, @Nonnull Axis axis) { + return getRotatedFiller(filler, axis, Rotation.OneEighty); + } + + public static int getRotatedFiller(int filler, @Nonnull Axis axis, Rotation rotation) { + return switch (axis) { + case X -> rotation.rotateX(filler); + case Y -> rotation.rotateY(filler); + case Z -> rotation.rotateZ(filler); + }; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/ChunkColumn.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/ChunkColumn.java new file mode 100644 index 0000000..deb4180 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/ChunkColumn.java @@ -0,0 +1,149 @@ +package com.hypixel.hytale.server.core.universe.world.chunk; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.store.StoredCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.LegacyModule; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@Deprecated +public class ChunkColumn implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(ChunkColumn.class, ChunkColumn::new) + .append( + new KeyedCodec<>("Sections", new ArrayCodec<>(new StoredCodec<>(ChunkStore.HOLDER_CODEC_KEY), Holder[]::new)), + (chunk, holders) -> chunk.sectionHolders = holders, + chunk -> { + int length = chunk.sections.length; + if (chunk.sectionHolders != null) { + length = Math.max(chunk.sectionHolders.length, chunk.sections.length); + } + + Holder[] array = new Holder[length]; + if (chunk.sectionHolders != null) { + System.arraycopy(chunk.sectionHolders, 0, array, 0, chunk.sectionHolders.length); + } + + for (int i = 0; i < chunk.sections.length; i++) { + Ref section = chunk.sections[i]; + if (section == null) { + break; + } + + Store store = section.getStore(); + array[i] = store.copySerializableEntity(section); + } + + return array; + } + ) + .add() + .build(); + private final Ref[] sections = new Ref[10]; + @Nullable + private Holder[] sectionHolders; + + public static ComponentType getComponentType() { + return LegacyModule.get().getChunkColumnComponentType(); + } + + public ChunkColumn() { + } + + public ChunkColumn(Holder[] sectionHolders) { + this.sectionHolders = sectionHolders; + } + + @Nullable + public Ref getSection(int section) { + return section >= 0 && section < this.sections.length ? this.sections[section] : null; + } + + @Nonnull + public Ref[] getSections() { + return this.sections; + } + + @Nullable + public Holder[] getSectionHolders() { + return this.sectionHolders; + } + + @Nullable + public Holder[] takeSectionHolders() { + Holder[] temp = this.sectionHolders; + this.sectionHolders = null; + return temp; + } + + public void putSectionHolders(Holder[] holders) { + this.sectionHolders = holders; + } + + @Nonnull + @Override + public Component clone() { + ChunkColumn newChunk = new ChunkColumn(); + int length = this.sections.length; + if (this.sectionHolders != null) { + length = Math.max(this.sectionHolders.length, this.sections.length); + } + + Holder[] holders = new Holder[length]; + if (this.sectionHolders != null) { + for (int i = 0; i < this.sectionHolders.length; i++) { + Holder sectionHolder = this.sectionHolders[i]; + if (sectionHolder != null) { + holders[i] = sectionHolder.clone(); + } + } + } + + for (int ix = 0; ix < this.sections.length; ix++) { + Ref section = this.sections[ix]; + if (section != null) { + holders[ix] = section.getStore().copyEntity(section); + } + } + + newChunk.sectionHolders = holders; + return newChunk; + } + + @Nonnull + @Override + public Component cloneSerializable() { + ChunkColumn newChunk = new ChunkColumn(); + int length = this.sections.length; + if (this.sectionHolders != null) { + length = Math.max(this.sectionHolders.length, this.sections.length); + } + + Holder[] holders = new Holder[length]; + if (this.sectionHolders != null) { + for (int i = 0; i < this.sectionHolders.length; i++) { + Holder sectionHolder = this.sectionHolders[i]; + if (sectionHolder != null) { + holders[i] = sectionHolder.clone(); + } + } + } + + for (int ix = 0; ix < this.sections.length; ix++) { + Ref section = this.sections[ix]; + if (section != null) { + holders[ix] = section.getStore().copySerializableEntity(section); + } + } + + newChunk.sectionHolders = holders; + return newChunk; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/ChunkFlag.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/ChunkFlag.java new file mode 100644 index 0000000..91886c2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/ChunkFlag.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.server.core.universe.world.chunk; + +import com.hypixel.hytale.common.collection.Flag; + +public enum ChunkFlag implements Flag { + START_INIT, + INIT, + NEWLY_GENERATED, + ON_DISK, + TICKING; + + public static final ChunkFlag[] VALUES = values(); + private final int mask = 1 << this.ordinal(); + + private ChunkFlag() { + } + + @Override + public int mask() { + return this.mask; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/EntityChunk.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/EntityChunk.java new file mode 100644 index 0000000..1dbbd1b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/EntityChunk.java @@ -0,0 +1,349 @@ +package com.hypixel.hytale.server.core.universe.world.chunk; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.store.StoredCodec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.NonTicking; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.nameplate.Nameplate; +import com.hypixel.hytale.server.core.modules.LegacyModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityChunk implements Component { + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(EntityChunk.class, EntityChunk::new) + .addField(new KeyedCodec<>("Entities", new ArrayCodec<>(new StoredCodec<>(EntityStore.HOLDER_CODEC_KEY), Holder[]::new)), (entityChunk, array) -> { + entityChunk.entityHolders.clear(); + Collections.addAll(entityChunk.entityHolders, array); + }, entityChunk -> { + if (entityChunk.entityReferences.isEmpty()) { + return entityChunk.entityHolders.toArray(new Holder[entityChunk.entityHolders.size()]); + } else { + Holder[] array = new Holder[entityChunk.entityHolders.size() + entityChunk.entityReferences.size()]; + array = entityChunk.entityHolders.toArray(array); + int index = entityChunk.entityHolders.size(); + + for (Ref reference : entityChunk.entityReferences) { + Store store = reference.getStore(); + if (store.getArchetype(reference).hasSerializableComponents(store.getRegistry().getData())) { + array[index++] = store.copyEntity(reference); + } + } + + return index == array.length ? array : Arrays.copyOfRange(array, 0, index); + } + }) + .build(); + @Nonnull + private final List> entityHolders; + @Nonnull + private final Set> entityReferences; + @Nonnull + private final List> entityHoldersUnmodifiable; + @Nonnull + private final Set> entityReferencesUnmodifiable; + private boolean needsSaving; + + @Nonnull + public static ComponentType getComponentType() { + return LegacyModule.get().getEntityChunkComponentType(); + } + + public EntityChunk() { + this.entityHolders = new ObjectArrayList<>(); + this.entityReferences = new HashSet<>(); + this.entityHoldersUnmodifiable = Collections.unmodifiableList(this.entityHolders); + this.entityReferencesUnmodifiable = Collections.unmodifiableSet(this.entityReferences); + } + + public EntityChunk(@Nonnull List> entityHolders, @Nonnull Set> entityReferences) { + this.entityHolders = entityHolders; + this.entityReferences = entityReferences; + this.entityHoldersUnmodifiable = Collections.unmodifiableList(entityHolders); + this.entityReferencesUnmodifiable = Collections.unmodifiableSet(entityReferences); + } + + @Nonnull + @Override + public Component clone() { + ObjectArrayList> entityHoldersClone = new ObjectArrayList<>(this.entityHolders.size() + this.entityReferences.size()); + + for (Holder entityHolder : this.entityHolders) { + entityHoldersClone.add(entityHolder.clone()); + } + + for (Ref reference : this.entityReferences) { + entityHoldersClone.add(reference.getStore().copyEntity(reference)); + } + + return new EntityChunk(entityHoldersClone, new HashSet<>()); + } + + @Nonnull + @Override + public Component cloneSerializable() { + ObjectArrayList> entityHoldersClone = new ObjectArrayList<>(this.entityHolders.size() + this.entityReferences.size()); + + for (Holder entityHolder : this.entityHolders) { + entityHoldersClone.add(entityHolder.clone()); + } + + for (Ref reference : this.entityReferences) { + entityHoldersClone.add(reference.getStore().copySerializableEntity(reference)); + } + + return new EntityChunk(entityHoldersClone, new HashSet<>()); + } + + @Nonnull + public List> getEntityHolders() { + return this.entityHoldersUnmodifiable; + } + + public void addEntityHolder(@Nonnull Holder holder) { + this.entityHolders.add(Objects.requireNonNull(holder)); + this.markNeedsSaving(); + } + + public void storeEntityHolder(@Nonnull Holder holder) { + this.entityHolders.add(Objects.requireNonNull(holder)); + } + + @Nonnull + public Set> getEntityReferences() { + return this.entityReferencesUnmodifiable; + } + + public void addEntityReference(@Nonnull Ref reference) { + this.entityReferences.add(Objects.requireNonNull(reference)); + this.markNeedsSaving(); + } + + public void loadEntityReference(@Nonnull Ref reference) { + this.entityReferences.add(Objects.requireNonNull(reference)); + } + + public void removeEntityReference(@Nonnull Ref reference) { + this.entityReferences.remove(Objects.requireNonNull(reference)); + this.markNeedsSaving(); + } + + public void unloadEntityReference(@Nonnull Ref reference) { + this.entityReferences.remove(Objects.requireNonNull(reference)); + } + + @Nullable + public Holder[] takeEntityHolders() { + if (this.entityHolders.isEmpty()) { + return null; + } else { + Holder[] holders = this.entityHolders.toArray(Holder[]::new); + this.entityHolders.clear(); + return holders; + } + } + + @Nullable + public Ref[] takeEntityReferences() { + if (this.entityReferences.isEmpty()) { + return null; + } else { + Ref[] holders = this.entityReferences.toArray(Ref[]::new); + this.entityReferences.clear(); + return holders; + } + } + + public boolean getNeedsSaving() { + return this.needsSaving; + } + + public void markNeedsSaving() { + this.needsSaving = true; + } + + public boolean consumeNeedsSaving() { + boolean out = this.needsSaving; + this.needsSaving = false; + return out; + } + + @Nonnull + @Deprecated(forRemoval = true) + public Iterable getEntities() { + if (this.entityReferences.isEmpty()) { + return Collections.emptyList(); + } else { + Ref[] references = this.entityReferences.toArray(Ref[]::new); + return () -> new Iterator() { + int index = references.length; + + @Override + public boolean hasNext() { + if (this.index <= 0) { + return false; + } else { + for (Ref reference = references[this.index - 1]; + !reference.isValid() || EntityUtils.getEntity(reference, reference.getStore()) == null; + reference = references[this.index - 1] + ) { + this.index--; + if (this.index <= 0) { + return false; + } + } + + return true; + } + } + + public Entity next() { + Ref reference = references[--this.index]; + return EntityUtils.getEntity(reference, reference.getStore()); + } + }; + } + } + + public static class EntityChunkLoadingSystem extends RefChangeSystem> { + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private final Archetype archetype = Archetype.of(WorldChunk.getComponentType(), EntityChunk.getComponentType()); + + public EntityChunkLoadingSystem() { + } + + @Override + public Query getQuery() { + return this.archetype; + } + + @Nonnull + @Override + public ComponentType> componentType() { + return ChunkStore.REGISTRY.getNonTickingComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, + @Nonnull NonTicking component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + World world = store.getExternalData().getWorld(); + EntityChunk entityChunkComponent = store.getComponent(ref, EntityChunk.getComponentType()); + + assert entityChunkComponent != null; + + Ref[] references = entityChunkComponent.takeEntityReferences(); + if (references != null) { + Store entityStore = world.getEntityStore().getStore(); + Holder[] holders = entityStore.removeEntities(references, RemoveReason.UNLOAD); + ComponentRegistry.Data data = EntityStore.REGISTRY.getData(); + + for (int i = 0; i < holders.length; i++) { + Holder holder = holders[i]; + if (holder.hasSerializableComponents(data)) { + entityChunkComponent.storeEntityHolder(holder); + } + } + } + } + + public void onComponentSet( + @Nonnull Ref ref, + NonTicking oldComponent, + @Nonnull NonTicking newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, + @Nonnull NonTicking component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + World world = store.getExternalData().getWorld(); + WorldChunk worldChunkComponent = store.getComponent(ref, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + EntityChunk entityChunkComponent = store.getComponent(ref, EntityChunk.getComponentType()); + + assert entityChunkComponent != null; + + Store entityStore = world.getEntityStore().getStore(); + Holder[] holders = entityChunkComponent.takeEntityHolders(); + if (holders != null) { + int holderCount = holders.length; + + for (int i = holderCount - 1; i >= 0; i--) { + Holder holder = holders[i]; + Archetype archetype = holder.getArchetype(); + + assert archetype != null; + + if (archetype.isEmpty()) { + LOGGER.at(Level.SEVERE).log("Empty archetype entity holder: %s (#%d)", holder, i); + holders[i] = holders[--holderCount]; + holders[holderCount] = holder; + } else if (archetype.count() == 1 && archetype.contains(Nameplate.getComponentType())) { + LOGGER.at(Level.SEVERE).log("Nameplate only entity holder: %s (#%d)", holder, i); + holders[i] = holders[--holderCount]; + holders[holderCount] = holder; + worldChunkComponent.markNeedsSaving(); + } else { + TransformComponent transformComponent = holder.getComponent(TransformComponent.getComponentType()); + + assert transformComponent != null; + + transformComponent.setChunkLocation(ref, worldChunkComponent); + } + } + + Ref[] refs = entityStore.addEntities(holders, 0, holderCount, AddReason.LOAD); + + for (int i = 0; i < refs.length; i++) { + Ref entityRef = refs[i]; + if (!entityRef.isValid()) { + break; + } + + entityChunkComponent.loadEntityReference(entityRef); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/WorldChunk.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/WorldChunk.java new file mode 100644 index 0000000..3680b89 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/WorldChunk.java @@ -0,0 +1,737 @@ +package com.hypixel.hytale.server.core.universe.world.chunk; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.collection.Flags; +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.BlockParticleEvent; +import com.hypixel.hytale.protocol.Opacity; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickManager; +import com.hypixel.hytale.server.core.asset.type.blocktick.config.TickProcedure; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; +import com.hypixel.hytale.server.core.modules.LegacyModule; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldNotificationHandler; +import com.hypixel.hytale.server.core.universe.world.accessor.BlockAccessor; +import com.hypixel.hytale.server.core.universe.world.accessor.ChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.environment.EnvironmentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule; +import com.hypixel.hytale.server.core.universe.world.meta.state.ItemContainerState; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.StampedLock; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WorldChunk implements BlockAccessor, Component { + public static final int KEEP_ALIVE_DEFAULT = 15; + public static final BuilderCodec CODEC = BuilderCodec.builder(WorldChunk.class, WorldChunk::new).build(); + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private World world; + private final StampedLock flagsLock = new StampedLock(); + private final Flags flags; + private Ref reference; + @Nullable + private BlockChunk blockChunk; + @Nullable + private BlockComponentChunk blockComponentChunk; + @Nullable + private EntityChunk entityChunk; + private int keepAlive = 15; + private int activeTimer = 15; + private boolean needsSaving; + private boolean isSaving; + private boolean keepLoaded; + private boolean lightingUpdatesEnabled = true; + @Deprecated + public final AtomicLong chunkLightTiming = new AtomicLong(); + + public static ComponentType getComponentType() { + return LegacyModule.get().getWorldChunkComponentType(); + } + + private WorldChunk() { + this.flags = new Flags<>(); + } + + private WorldChunk(World world, Flags flags) { + this.world = world; + this.flags = flags; + } + + public WorldChunk(World world, Flags state, BlockChunk blockChunk, BlockComponentChunk blockComponentChunk, EntityChunk entityChunk) { + this(world, state); + this.blockChunk = blockChunk; + this.blockComponentChunk = blockComponentChunk; + this.entityChunk = entityChunk; + } + + @Nonnull + public Holder toHolder() { + if (this.reference != null && this.reference.isValid() && this.world != null) { + Holder holder = ChunkStore.REGISTRY.newHolder(); + Store componentStore = this.world.getChunkStore().getStore(); + Archetype archetype = componentStore.getArchetype(this.reference); + + for (int i = archetype.getMinIndex(); i < archetype.length(); i++) { + ComponentType componentType = archetype.get(i); + if (componentType != null) { + holder.addComponent(componentType, componentStore.getComponent(this.reference, componentType)); + } + } + + return holder; + } else { + Holder holder = ChunkStore.REGISTRY.newHolder(); + holder.addComponent(getComponentType(), this); + holder.addComponent(BlockChunk.getComponentType(), this.blockChunk); + holder.addComponent(EnvironmentChunk.getComponentType(), this.blockChunk.getEnvironmentChunk()); + holder.addComponent(EntityChunk.getComponentType(), this.entityChunk); + holder.addComponent(BlockComponentChunk.getComponentType(), this.blockComponentChunk); + return holder; + } + } + + @Deprecated + public void setReference(Ref reference) { + if (this.reference != null && this.reference.isValid()) { + throw new IllegalArgumentException("Chunk already has a valid EntityReference: " + this.reference + " new reference " + reference); + } else { + this.reference = reference; + } + } + + public Ref getReference() { + return this.reference; + } + + @Nonnull + @Override + public Component clone() { + return new WorldChunk(); + } + + public boolean is(@Nonnull ChunkFlag flag) { + long stamp = this.flagsLock.tryOptimisticRead(); + boolean value = this.flags.is(flag); + if (this.flagsLock.validate(stamp)) { + return value; + } else { + stamp = this.flagsLock.readLock(); + + boolean var5; + try { + var5 = this.flags.is(flag); + } finally { + this.flagsLock.unlockRead(stamp); + } + + return var5; + } + } + + public boolean not(@Nonnull ChunkFlag flag) { + long stamp = this.flagsLock.tryOptimisticRead(); + boolean value = this.flags.not(flag); + if (this.flagsLock.validate(stamp)) { + return value; + } else { + stamp = this.flagsLock.readLock(); + + boolean var5; + try { + var5 = this.flags.not(flag); + } finally { + this.flagsLock.unlockRead(stamp); + } + + return var5; + } + } + + public void setFlag(@Nonnull ChunkFlag flag, boolean value) { + long lock = this.flagsLock.writeLock(); + + boolean isInit; + try { + if (!this.flags.set(flag, value)) { + return; + } + + isInit = this.flags.is(ChunkFlag.INIT); + } finally { + this.flagsLock.unlockWrite(lock); + } + + if (isInit) { + this.updateFlag(flag, value); + } + + LOGGER.at(Level.FINER).log("[%d, %d] updated chunk flag (init: %s): %s, %s ", this.getX(), this.getZ(), isInit, flag, value); + } + + public boolean toggleFlag(@Nonnull ChunkFlag flag) { + long lock = this.flagsLock.writeLock(); + + boolean value; + boolean isInit; + try { + value = this.flags.toggle(flag); + isInit = this.flags.is(ChunkFlag.INIT); + } finally { + this.flagsLock.unlockWrite(lock); + } + + if (isInit) { + this.updateFlag(flag, value); + } + + LOGGER.at(Level.FINER).log("[%d, %d] updated chunk flag (init: %s): %s, %s ", this.getX(), this.getZ(), isInit, flag, value); + return value; + } + + @Deprecated + public void loadFromHolder(World world, int x, int z, @Nonnull Holder holder) { + this.world = world; + this.blockChunk = holder.getComponent(BlockChunk.getComponentType()); + this.blockChunk.setEnvironmentChunk(holder.getComponent(EnvironmentChunk.getComponentType())); + this.blockComponentChunk = holder.getComponent(BlockComponentChunk.getComponentType()); + this.entityChunk = holder.getComponent(EntityChunk.getComponentType()); + this.blockChunk.load(x, z); + } + + @Deprecated + public void setBlockComponentChunk(BlockComponentChunk blockComponentChunk) { + this.blockComponentChunk = blockComponentChunk; + } + + public void initFlags() { + this.world.debugAssertInTickingThread(); + if (!this.is(ChunkFlag.START_INIT)) { + throw new IllegalArgumentException("START_INIT hasn't been set!"); + } else if (this.is(ChunkFlag.INIT)) { + throw new IllegalArgumentException("INIT is already set!"); + } else { + for (int i = 0; i < ChunkFlag.VALUES.length; i++) { + ChunkFlag flag = ChunkFlag.VALUES[i]; + this.updateFlag(flag, this.is(flag)); + } + + this.setFlag(ChunkFlag.INIT, true); + } + } + + private void updateFlag(ChunkFlag flag, boolean value) { + if (flag == ChunkFlag.TICKING) { + this.world.debugAssertInTickingThread(); + this.resetKeepAlive(); + if (value) { + this.startsTicking(); + } else { + this.stopsTicking(); + } + } + } + + private void startsTicking() { + this.world.debugAssertInTickingThread(); + LOGGER.at(Level.FINER).log("Chunk started ticking %s", this); + Store componentStore = this.world.getChunkStore().getStore(); + componentStore.tryRemoveComponent(this.reference, ChunkStore.REGISTRY.getNonTickingComponentType()); + } + + private void stopsTicking() { + this.world.debugAssertInTickingThread(); + LOGGER.at(Level.FINER).log("Chunk stopped ticking %s", this); + Store componentStore = this.world.getChunkStore().getStore(); + componentStore.ensureComponent(this.reference, ChunkStore.REGISTRY.getNonTickingComponentType()); + } + + @Nullable + public BlockChunk getBlockChunk() { + return this.blockChunk; + } + + @Nullable + public BlockComponentChunk getBlockComponentChunk() { + return this.blockComponentChunk; + } + + @Nullable + public EntityChunk getEntityChunk() { + return this.entityChunk; + } + + public boolean shouldKeepLoaded() { + return this.keepLoaded; + } + + public void setKeepLoaded(boolean keepLoaded) { + this.keepLoaded = keepLoaded; + } + + public int pollKeepAlive(int pollCount) { + return this.keepAlive = Math.max(this.keepAlive - pollCount, 0); + } + + public void resetKeepAlive() { + this.keepAlive = 15; + } + + public int pollActiveTimer(int pollCount) { + return this.activeTimer = Math.max(this.activeTimer - pollCount, 0); + } + + public void resetActiveTimer() { + this.activeTimer = 15; + } + + @Override + public ChunkAccessor getChunkAccessor() { + return this.world; + } + + @Override + public int getBlock(int x, int y, int z) { + return y >= 0 && y < 320 ? this.blockChunk.getBlock(x, y, z) : 0; + } + + @Override + public boolean setBlock(int x, int y, int z, int id, @Nonnull BlockType blockType, int rotation, int filler, int settings) { + if (y >= 0 && y < 320) { + short oldHeight = this.blockChunk.getHeight(x, z); + BlockSection blockSection = this.blockChunk.getSectionAtBlockY(y); + int oldRotation = blockSection.getRotationIndex(x, y, z); + int oldBlock = blockSection.get(x, y, z); + boolean changed = (oldBlock != id || rotation != oldRotation) && blockSection.set(x, y, z, id, rotation, filler); + if (changed || (settings & 64) != 0) { + int worldX = (this.getX() << 5) + (x & 31); + int worldZ = (this.getZ() << 5) + (z & 31); + if ((settings & 64) != 0) { + blockSection.invalidateBlock(x, y, z); + } + + short newHeight = oldHeight; + if ((settings & 512) == 0 && oldHeight <= y) { + if (oldHeight == y && id == 0) { + newHeight = this.blockChunk.updateHeight(x, z, (short)y); + } else if (oldHeight < y && id != 0 && blockType.getOpacity() != Opacity.Transparent) { + newHeight = (short)y; + this.blockChunk.setHeight(x, z, newHeight); + } + } + + WorldNotificationHandler notificationHandler = this.getWorld().getNotificationHandler(); + if ((settings & 4) == 0) { + if (oldBlock == 0 && id != 0) { + notificationHandler.sendBlockParticle(worldX + 0.5, y + 0.5, worldZ + 0.5, id, BlockParticleEvent.Build); + } else if (oldBlock != 0 && id == 0) { + BlockParticleEvent particleType = (settings & 32) != 0 ? BlockParticleEvent.Physics : BlockParticleEvent.Break; + notificationHandler.sendBlockParticle(worldX + 0.5, y + 0.5, worldZ + 0.5, oldBlock, particleType); + } else { + notificationHandler.sendBlockParticle(worldX + 0.5, y + 0.5, worldZ + 0.5, oldBlock, BlockParticleEvent.Break); + notificationHandler.sendBlockParticle(worldX + 0.5, y + 0.5, worldZ + 0.5, id, BlockParticleEvent.Build); + } + } + + BlockTypeAssetMap blockTypeAssetMap = BlockType.getAssetMap(); + IndexedLookupTableAssetMap hitboxAssetMap = BlockBoundingBoxes.getAssetMap(); + String blockTypeKey = blockType.getId(); + if ((settings & 2) == 0) { + Holder blockEntity = blockType.getBlockEntity(); + if (blockEntity != null && filler == 0) { + Holder newComponents = blockEntity.clone(); + this.setState(x, y, z, newComponents); + } else { + BlockState blockState = null; + String blockStateType = blockType.getState() != null ? blockType.getState().getId() : null; + if (id != 0 && blockStateType != null && filler == 0) { + blockState = BlockStateModule.get().createBlockState(blockStateType, this, new Vector3i(x, y, z), blockType); + if (blockState == null) { + LOGGER.at(Level.WARNING).log("Failed to create BlockState: %s for BlockType: %s", blockStateType, blockTypeKey); + } + } + + BlockState oldState = this.getState(x, y, z); + if (blockState instanceof ItemContainerState newState) { + FillerBlockUtil.forEachFillerBlock(hitboxAssetMap.getAsset(blockType.getHitboxTypeIndex()).get(rotation), (x1, y1, z1) -> { + int blockX = worldX + x1; + int blockY = y + y1; + int blockZ = worldZ + z1; + boolean isZero = x1 == 0 && y1 == 0 && z1 == 0; + if ((isZero ? oldState : this.getState(blockX, blockY, blockZ)) instanceof ItemContainerState oldContainer) { + oldContainer.getItemContainer().moveAllItemStacksTo(newState.getItemContainer()); + } + }); + } + + this.setState(x, y, z, blockState, (settings & 1) == 0); + } + } + + if (this.lightingUpdatesEnabled) { + this.world.getChunkLighting().invalidateLightAtBlock(this, x, y, z, blockType, oldHeight, newHeight); + } + + TickProcedure tickProcedure = BlockTickManager.getBlockTickProvider().getTickProcedure(id); + this.blockChunk.setTicking(x, y, z, tickProcedure != null); + if ((settings & 16) == 0) { + int settingsWithoutFiller = settings | 8 | 16; + BlockType oldBlockType = blockTypeAssetMap.getAsset(oldBlock); + String oldBlockKey = oldBlockType.getId(); + FillerBlockUtil.forEachFillerBlock(hitboxAssetMap.getAsset(oldBlockType.getHitboxTypeIndex()).get(oldRotation), (x1, y1, z1) -> { + if (x1 != 0 || y1 != 0 || z1 != 0) { + int blockX = worldX + x1; + int blockY = y + y1; + int blockZ = worldZ + z1; + if (ChunkUtil.isSameChunk(worldX, worldZ, blockX, blockZ)) { + String blockTypeKey1 = this.getBlockType(blockX, blockY, blockZ).getId(); + if (blockTypeKey1.equals(oldBlockKey)) { + this.breakBlock(blockX, blockY, blockZ, settingsWithoutFiller); + } + } else { + String blockTypeKey1 = this.getWorld().getBlockType(blockX, blockY, blockZ).getId(); + if (blockTypeKey1.equals(oldBlockKey)) { + this.getWorld().breakBlock(blockX, blockY, blockZ, settingsWithoutFiller); + } + } + } + }); + } + + if ((settings & 8) == 0 && filler == 0) { + int settingsWithoutSetFiller = settings | 8; + FillerBlockUtil.forEachFillerBlock( + hitboxAssetMap.getAsset(blockType.getHitboxTypeIndex()).get(rotation), + (x1, y1, z1) -> { + if (x1 != 0 || y1 != 0 || z1 != 0) { + int blockX = worldX + x1; + int blockY = y + y1; + int blockZ = worldZ + z1; + boolean sameChunk = ChunkUtil.isSameChunk(worldX, worldZ, blockX, blockZ); + if (sameChunk) { + this.setBlock(blockX, blockY, blockZ, id, blockType, rotation, FillerBlockUtil.pack(x1, y1, z1), settingsWithoutSetFiller); + } else { + this.getWorld() + .getNonTickingChunk(ChunkUtil.indexChunkFromBlock(blockX, blockZ)) + .setBlock(blockX, blockY, blockZ, id, blockType, rotation, FillerBlockUtil.pack(x1, y1, z1), settingsWithoutSetFiller); + } + } + } + ); + } + + if ((settings & 256) != 0) { + FillerBlockUtil.forEachFillerBlock( + hitboxAssetMap.getAsset(blockType.getHitboxTypeIndex()).get(rotation), + (x1, y1, z1) -> this.getChunkAccessor().performBlockUpdate(worldX + x1, y + y1, worldZ + z1) + ); + } + + if (this.reference != null && this.reference.isValid()) { + if (this.world.isInThread()) { + this.setBlockPhysics(x, y, z, blockType); + } else { + CompletableFutureUtil._catch(CompletableFuture.runAsync(() -> this.setBlockPhysics(x, y, z, blockType), this.world)); + } + } + } + + return changed; + } else { + return false; + } + } + + private void setBlockPhysics(int x, int y, int z, @Nonnull BlockType blockType) { + Store store = this.reference.getStore(); + ChunkColumn column = store.getComponent(this.reference, ChunkColumn.getComponentType()); + Ref section = column.getSection(ChunkUtil.chunkCoordinate(y)); + if (section != null) { + if (!blockType.hasSupport()) { + BlockPhysics.clear(store, section, x, y, z); + } else { + BlockPhysics.reset(store, section, x, y, z); + } + } + } + + @Deprecated(forRemoval = true) + @Override + public int getFiller(int x, int y, int z) { + return y >= 0 && y < 320 ? this.blockChunk.getSectionAtBlockY(y).getFiller(x, y, z) : 0; + } + + @Deprecated(forRemoval = true) + @Override + public int getRotationIndex(int x, int y, int z) { + return y >= 0 && y < 320 ? this.blockChunk.getSectionAtBlockY(y).getRotationIndex(x, y, z) : 0; + } + + @Override + public boolean setTicking(int x, int y, int z, boolean ticking) { + return this.blockChunk.setTicking(x, y, z, ticking); + } + + @Override + public boolean isTicking(int x, int y, int z) { + return this.blockChunk.isTicking(x, y, z); + } + + public short getHeight(int x, int z) { + return this.blockChunk.getHeight(x, z); + } + + public short getHeight(int index) { + return this.blockChunk.getHeight(index); + } + + public int getTint(int x, int z) { + return this.blockChunk.getTint(x, z); + } + + @Nullable + @Override + public BlockState getState(int x, int y, int z) { + if (y < 0 || y >= 320) { + return null; + } else if (!this.world.isInThread()) { + return CompletableFuture.supplyAsync(() -> this.getState(x, y, z), this.world).join(); + } else { + int index = ChunkUtil.indexBlockInColumn(x, y, z); + Ref reference = this.blockComponentChunk.getEntityReference(index); + if (reference != null) { + return BlockState.getBlockState(reference, reference.getStore()); + } else { + Holder holder = this.blockComponentChunk.getEntityHolder(index); + return holder != null ? BlockState.getBlockState(holder) : null; + } + } + } + + @Nullable + public Ref getBlockComponentEntity(int x, int y, int z) { + if (y < 0 || y >= 320) { + return null; + } else if (!this.world.isInThread()) { + return CompletableFuture.>supplyAsync(() -> this.getBlockComponentEntity(x, y, z), this.world).join(); + } else { + int index = ChunkUtil.indexBlockInColumn(x, y, z); + return this.blockComponentChunk.getEntityReference(index); + } + } + + @Nullable + @Override + public Holder getBlockComponentHolder(int x, int y, int z) { + if (y < 0 || y >= 320) { + return null; + } else if (!this.world.isInThread()) { + return CompletableFuture.>supplyAsync(() -> this.getBlockComponentHolder(x, y, z), this.world).join(); + } else { + int index = ChunkUtil.indexBlockInColumn(x, y, z); + Ref reference = this.blockComponentChunk.getEntityReference(index); + return reference == null ? null : reference.getStore().copyEntity(reference); + } + } + + @Override + public void setState(int x, int y, int z, @Nullable BlockState state, boolean notify) { + if (y >= 0 && y < 320) { + Holder holder = state != null ? state.toHolder() : null; + this.setState(x, y, z, holder); + } + } + + @Deprecated(forRemoval = true) + @Override + public int getFluidId(int x, int y, int z) { + Ref columnRef = this.getReference(); + Store store = columnRef.getStore(); + ChunkColumn column = store.getComponent(columnRef, ChunkColumn.getComponentType()); + Ref section = column.getSection(ChunkUtil.chunkCoordinate(y)); + if (section == null) { + return Integer.MIN_VALUE; + } else { + FluidSection fluidSection = store.getComponent(section, FluidSection.getComponentType()); + return fluidSection.getFluidId(x, y, z); + } + } + + @Deprecated(forRemoval = true) + @Override + public byte getFluidLevel(int x, int y, int z) { + Ref columnRef = this.getReference(); + Store store = columnRef.getStore(); + ChunkColumn column = store.getComponent(columnRef, ChunkColumn.getComponentType()); + Ref section = column.getSection(ChunkUtil.chunkCoordinate(y)); + if (section == null) { + return 0; + } else { + FluidSection fluidSection = store.getComponent(section, FluidSection.getComponentType()); + return fluidSection.getFluidLevel(x, y, z); + } + } + + @Deprecated(forRemoval = true) + @Override + public int getSupportValue(int x, int y, int z) { + Ref columnRef = this.getReference(); + Store store = columnRef.getStore(); + ChunkColumn column = store.getComponent(columnRef, ChunkColumn.getComponentType()); + Ref section = column.getSection(ChunkUtil.chunkCoordinate(y)); + if (section == null) { + return 0; + } else { + BlockPhysics blockPhysics = store.getComponent(section, BlockPhysics.getComponentType()); + return blockPhysics != null ? blockPhysics.get(x, y, z) : 0; + } + } + + @Deprecated + public void setState(int x, int y, int z, @Nullable Holder holder) { + if (y >= 0 && y < 320) { + if (!this.world.isInThread()) { + CompletableFutureUtil._catch(CompletableFuture.runAsync(() -> this.setState(x, y, z, holder), this.world)); + } else { + boolean notify = true; + int index = ChunkUtil.indexBlockInColumn(x, y, z); + if (holder == null) { + Ref reference = this.blockComponentChunk.getEntityReference(index); + if (reference != null) { + Holder oldHolder = reference.getStore().removeEntity(reference, RemoveReason.REMOVE); + BlockState oldState = BlockState.getBlockState(oldHolder); + if (notify) { + this.world.getNotificationHandler().updateState(x, y, z, null, oldState); + } + } else { + this.blockComponentChunk.removeEntityHolder(index); + } + } else { + BlockState state = BlockState.getBlockState(holder); + if (state != null) { + state.setPosition(this, new Vector3i(x, y, z)); + } + + Store blockComponentStore = this.world.getChunkStore().getStore(); + if (!this.is(ChunkFlag.TICKING)) { + Holder oldHolder = this.blockComponentChunk.getEntityHolder(index); + BlockState oldState = null; + if (oldHolder != null) { + oldState = BlockState.getBlockState(oldHolder); + } + + this.blockComponentChunk.removeEntityHolder(index); + holder.putComponent(BlockModule.BlockStateInfo.getComponentType(), new BlockModule.BlockStateInfo(index, this.reference)); + this.blockComponentChunk.addEntityHolder(index, holder); + if (notify) { + this.world.getNotificationHandler().updateState(x, y, z, state, oldState); + } + } else { + Ref oldReference = this.blockComponentChunk.getEntityReference(index); + BlockState oldStatex = null; + if (oldReference != null) { + Holder oldEntityHolder = blockComponentStore.removeEntity(oldReference, RemoveReason.REMOVE); + oldStatex = BlockState.getBlockState(oldEntityHolder); + } else { + this.blockComponentChunk.removeEntityHolder(index); + } + + holder.putComponent(BlockModule.BlockStateInfo.getComponentType(), new BlockModule.BlockStateInfo(index, this.reference)); + blockComponentStore.addEntity(holder, AddReason.SPAWN); + if (notify) { + this.world.getNotificationHandler().updateState(x, y, z, state, oldStatex); + } + } + } + } + } + } + + public void markNeedsSaving() { + this.needsSaving = true; + } + + public boolean getNeedsSaving() { + return this.needsSaving || this.blockChunk.getNeedsSaving() || this.blockComponentChunk.getNeedsSaving() || this.entityChunk.getNeedsSaving(); + } + + public boolean consumeNeedsSaving() { + boolean out = this.needsSaving; + if (this.blockChunk.consumeNeedsSaving()) { + out = true; + } + + if (this.blockComponentChunk.consumeNeedsSaving()) { + out = true; + } + + if (this.entityChunk.consumeNeedsSaving()) { + out = true; + } + + this.needsSaving = false; + return out; + } + + public boolean isSaving() { + return this.isSaving; + } + + public void setSaving(boolean saving) { + this.isSaving = saving; + } + + public long getIndex() { + return this.blockChunk.getIndex(); + } + + @Override + public int getX() { + return this.blockChunk.getX(); + } + + @Override + public int getZ() { + return this.blockChunk.getZ(); + } + + public void setLightingUpdatesEnabled(boolean enableLightUpdates) { + this.lightingUpdatesEnabled = enableLightUpdates; + } + + public boolean isLightingUpdatesEnabled() { + return this.lightingUpdatesEnabled; + } + + public World getWorld() { + return this.world; + } + + @Nonnull + @Override + public String toString() { + return "WorldChunk{x=" + this.blockChunk.getX() + ", z=" + this.blockChunk.getZ() + ", flags=" + this.flags + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/environment/EnvironmentChunk.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/environment/EnvironmentChunk.java new file mode 100644 index 0000000..655c8d3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/environment/EnvironmentChunk.java @@ -0,0 +1,196 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.environment; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.core.modules.LegacyModule; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.util.io.ByteBufUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2LongMap; +import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2LongMap.Entry; +import javax.annotation.Nonnull; + +public class EnvironmentChunk implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(EnvironmentChunk.class, EnvironmentChunk::new) + .addField(new KeyedCodec<>("Data", Codec.BYTE_ARRAY), EnvironmentChunk::deserialize, EnvironmentChunk::serialize) + .build(); + private final EnvironmentColumn[] columns = new EnvironmentColumn[1024]; + private final Int2LongMap counts = new Int2LongOpenHashMap(); + + public static ComponentType getComponentType() { + return LegacyModule.get().getEnvironmentChunkComponentType(); + } + + public EnvironmentChunk() { + this(0); + } + + public EnvironmentChunk(int defaultId) { + for (int i = 0; i < this.columns.length; i++) { + this.columns[i] = new EnvironmentColumn(defaultId); + } + } + + @Nonnull + @Override + public Component clone() { + EnvironmentChunk chunk = new EnvironmentChunk(); + + for (int i = 0; i < this.columns.length; i++) { + chunk.columns[i].copyFrom(this.columns[i]); + } + + chunk.counts.putAll(this.counts); + return chunk; + } + + public int get(int x, int y, int z) { + return this.columns[idx(x, z)].get(y); + } + + public EnvironmentColumn get(int x, int z) { + return this.columns[idx(x, z)]; + } + + public void setColumn(int x, int z, int environmentId) { + EnvironmentColumn column = this.columns[idx(x, z)]; + column.set(environmentId); + int minY = Integer.MIN_VALUE; + + int maxY; + do { + int id = column.get(minY); + maxY = column.getMax(minY); + int count = maxY - minY + 1; + this.decrementBlockCount(id, count); + } while (maxY < Integer.MAX_VALUE); + + this.createIfNotExist(environmentId); + this.incrementBlockCount(environmentId, Integer.MAX_VALUE); + column.set(environmentId); + } + + public boolean set(int x, int y, int z, int environmentId) { + EnvironmentColumn column = this.columns[idx(x, z)]; + int oldInternalId = column.get(y); + if (environmentId != oldInternalId) { + this.decrementBlockCount(oldInternalId, 1L); + this.createIfNotExist(environmentId); + this.incrementBlockCount(environmentId); + column.set(y, environmentId); + return true; + } else { + return false; + } + } + + public boolean contains(int environmentId) { + return this.counts.containsKey(environmentId); + } + + private void createIfNotExist(int environmentId) { + if (!this.counts.containsKey(environmentId)) { + this.counts.put(environmentId, 0L); + } + } + + private void incrementBlockCount(int internalId) { + this.counts.mergeLong(internalId, 1L, Long::sum); + } + + private void incrementBlockCount(int internalId, int count) { + long oldCount = this.counts.get(internalId); + this.counts.put(internalId, oldCount + count); + } + + private boolean decrementBlockCount(int environmentId, long count) { + long oldCount = this.counts.get(environmentId); + if (oldCount <= count) { + this.counts.remove(environmentId); + return true; + } else { + this.counts.put(environmentId, oldCount - count); + return false; + } + } + + private byte[] serialize() { + ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(); + + try { + buf.writeInt(this.counts.size()); + + for (Entry entry : this.counts.int2LongEntrySet()) { + int environmentId = entry.getIntKey(); + Environment environment = Environment.getAssetMap().getAsset(environmentId); + String key = environment != null ? environment.getId() : Environment.UNKNOWN.getId(); + buf.writeInt(environmentId); + ByteBufUtil.writeUTF(buf, key); + } + + for (int i = 0; i < this.columns.length; i++) { + this.columns[i].serialize(buf, (environmentIdx, buf0) -> buf0.writeInt(environmentIdx)); + } + + return ByteBufUtil.getBytesRelease(buf); + } catch (Throwable var7) { + buf.release(); + throw SneakyThrow.sneakyThrow(var7); + } + } + + private void deserialize(@Nonnull byte[] bytes) { + ByteBuf buf = Unpooled.wrappedBuffer(bytes); + this.counts.clear(); + int mappingCount = buf.readInt(); + Int2IntMap idMapping = new Int2IntOpenHashMap(mappingCount); + + for (int i = 0; i < mappingCount; i++) { + int serialId = buf.readInt(); + String key = ByteBufUtil.readUTF(buf); + int environmentId = Environment.getIndexOrUnknown(key, "Failed to find environment '%s' when deserializing environment chunk", key); + idMapping.put(serialId, environmentId); + this.counts.put(environmentId, 0L); + } + + for (int i = 0; i < this.columns.length; i++) { + EnvironmentColumn column = this.columns[i]; + column.deserialize(buf, buf0 -> idMapping.get(buf0.readInt())); + + for (int x = 0; x < column.size(); x++) { + this.counts.mergeLong(column.getValue(x), 1L, Long::sum); + } + } + } + + public byte[] serializeProtocol() { + ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(); + + for (int i = 0; i < this.columns.length; i++) { + this.columns[i].serializeProtocol(buf); + } + + return ByteBufUtil.getBytesRelease(buf); + } + + public void trim() { + for (int i = 0; i < this.columns.length; i++) { + this.columns[i].trim(); + } + } + + private static int idx(int x, int z) { + return ChunkUtil.indexColumn(x, z); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/environment/EnvironmentColumn.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/environment/EnvironmentColumn.java new file mode 100644 index 0000000..e159401 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/environment/EnvironmentColumn.java @@ -0,0 +1,255 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.environment; + +import com.hypixel.hytale.function.consumer.IntObjectConsumer; +import io.netty.buffer.ByteBuf; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import java.util.function.ToIntFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EnvironmentColumn { + public static final int MIN = Integer.MIN_VALUE; + public static final int MAX = Integer.MAX_VALUE; + @Nonnull + private final IntArrayList maxYs; + @Nonnull + private final IntArrayList values; + + public EnvironmentColumn(@Nonnull int[] maxYs, @Nonnull int[] values) { + this(new IntArrayList(maxYs), new IntArrayList(values)); + } + + public EnvironmentColumn(@Nonnull IntArrayList maxYs, @Nonnull IntArrayList values) { + if (maxYs.size() + 1 != values.size()) { + throw new IllegalStateException("maxY + 1 != values"); + } else { + this.maxYs = maxYs; + this.values = values; + } + } + + public EnvironmentColumn(int initialId) { + this(new IntArrayList(0), new IntArrayList(new int[]{initialId})); + } + + int maxys_size() { + return this.maxYs.size(); + } + + public int size() { + return this.values.size(); + } + + public int getValue(int index) { + return this.values.getInt(index); + } + + public int getValueMin(int index) { + return index <= 0 ? Integer.MIN_VALUE : this.maxYs.getInt(index - 1) + 1; + } + + public int getValueMax(int index) { + return index >= this.maxYs.size() ? Integer.MAX_VALUE : this.maxYs.getInt(index); + } + + public int indexOf(int y) { + int n = this.maxYs.size(); + if (n == 0) { + return 0; + } else { + int l = 0; + int r = n - 1; + int i = n; + + while (l <= r) { + int mid = (l + r) / 2; + if (this.maxYs.getInt(mid) < y) { + l = mid + 1; + } else { + i = mid; + r = mid - 1; + } + } + + return i; + } + } + + public void set(int value) { + this.maxYs.clear(); + this.values.clear(); + this.values.add(value); + } + + public int get(int y) { + return this.values.getInt(this.indexOf(y)); + } + + public void set(int y, int value) { + int idx = this.indexOf(y); + int currentValue = this.values.getInt(idx); + if (currentValue != value) { + int keys = this.maxYs.size(); + int max = Integer.MAX_VALUE; + if (idx < keys) { + max = this.maxYs.getInt(idx); + } + + int min = Integer.MIN_VALUE; + if (idx > 0) { + min = this.maxYs.getInt(idx - 1) + 1; + } + + if (min == max) { + if (idx < keys && this.values.getInt(idx + 1) == value) { + this.maxYs.removeInt(idx); + this.values.removeInt(idx); + } else { + this.values.set(idx, value); + } + + if (idx != 0 && this.values.getInt(idx - 1) == value) { + this.maxYs.removeInt(idx - 1); + this.values.removeInt(idx - 1); + } + } else if (min == y) { + if (idx != 0 && this.values.getInt(idx - 1) == value) { + this.maxYs.set(idx - 1, y); + } else { + this.maxYs.add(idx, y); + this.values.add(idx, value); + } + } else if (max == y) { + if (idx == keys) { + this.maxYs.add(idx, y - 1); + this.values.add(idx + 1, value); + } else { + this.maxYs.set(idx, y - 1); + if (this.values.getInt(idx + 1) != value) { + this.maxYs.add(idx + 1, y); + this.values.add(idx + 1, value); + } + } + } else { + this.maxYs.add(idx, y); + this.values.add(idx, value); + this.maxYs.add(idx, y - 1); + this.values.add(idx, currentValue); + } + } + } + + public int getMin(int y) { + int idx = this.indexOf(y); + int min = Integer.MIN_VALUE; + if (idx > 0) { + min = this.maxYs.getInt(idx - 1) + 1; + } + + return min; + } + + public int getMax(int y) { + int idx = this.indexOf(y); + int keys = this.maxYs.size(); + int max = Integer.MAX_VALUE; + if (idx < keys) { + max = this.maxYs.getInt(idx); + } + + return max; + } + + public void set(int fromY, int toY, int value) { + for (int y = fromY; y <= toY; y++) { + this.set(y, value); + } + } + + public void serialize(@Nonnull ByteBuf buf, @Nonnull IntObjectConsumer valueSerializer) { + int n = this.maxYs.size(); + buf.writeInt(n); + + for (int i = 0; i < n; i++) { + buf.writeInt(this.maxYs.getInt(i)); + } + + for (int i = 0; i <= n; i++) { + valueSerializer.accept(this.values.getInt(i), buf); + } + } + + public void serializeProtocol(@Nonnull ByteBuf buf) { + int n = this.maxYs.size(); + buf.writeShortLE(n + 1); + int min = Integer.MIN_VALUE; + + for (int i = 0; i < n; i++) { + buf.writeShortLE(min); + buf.writeShortLE(this.values.getInt(i)); + int max = this.maxYs.getInt(i); + min = max + 1; + } + + buf.writeShortLE(min); + buf.writeShortLE(this.values.getInt(n)); + } + + public void deserialize(@Nonnull ByteBuf buf, @Nonnull ToIntFunction valueDeserializer) { + this.maxYs.clear(); + this.values.clear(); + int n = buf.readInt(); + this.maxYs.ensureCapacity(n); + this.values.ensureCapacity(n + 1); + + for (int i = 0; i < n; i++) { + this.maxYs.add(buf.readInt()); + } + + for (int i = 0; i <= n; i++) { + this.values.add(valueDeserializer.applyAsInt(buf)); + } + } + + public void copyFrom(@Nonnull EnvironmentColumn other) { + this.maxYs.clear(); + this.values.clear(); + this.maxYs.ensureCapacity(other.maxYs.size()); + this.values.ensureCapacity(other.values.size()); + this.maxYs.addAll(other.maxYs); + this.values.addAll(other.values); + } + + public void trim() { + this.maxYs.trim(); + this.values.trim(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + EnvironmentColumn that = (EnvironmentColumn)o; + if (this.maxYs != null ? this.maxYs.equals(that.maxYs) : that.maxYs == null) { + return this.values != null ? this.values.equals(that.values) : that.values == null; + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = this.maxYs != null ? this.maxYs.hashCode() : 0; + return 31 * result + (this.values != null ? this.values.hashCode() : 0); + } + + @Nonnull + @Override + public String toString() { + return "EnvironmentColumn{maxYs=" + this.maxYs + ", values=" + this.values + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/environment/EnvironmentRange.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/environment/EnvironmentRange.java new file mode 100644 index 0000000..abfcf5c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/environment/EnvironmentRange.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.environment; + +import javax.annotation.Nonnull; + +public class EnvironmentRange { + private int min; + private int max; + private int id; + + public EnvironmentRange(int id) { + this(0, 2147483646, id); + } + + public EnvironmentRange(int min, int max, int id) { + this.min = min; + this.max = max; + this.id = id; + } + + public int getMin() { + return this.min; + } + + void setMin(int min) { + this.min = min; + } + + public int getMax() { + return this.max; + } + + void setMax(int max) { + this.max = max; + } + + public int getId() { + return this.id; + } + + void setId(int id) { + this.id = id; + } + + public int height() { + return this.max - this.min + 1; + } + + @Nonnull + public EnvironmentRange copy() { + return new EnvironmentRange(this.min, this.max, this.id); + } + + @Nonnull + @Override + public String toString() { + return "EnvironmentRange{min=" + this.min + ", max=" + this.max + ", id='" + this.id + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/palette/BitFieldArr.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/palette/BitFieldArr.java new file mode 100644 index 0000000..7c498c6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/palette/BitFieldArr.java @@ -0,0 +1,121 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.palette; + +import javax.annotation.Nonnull; + +public class BitFieldArr { + public static final int BITS_PER_INDEX = 8; + public static final int LAST_BIT_INDEX = 7; + public static final int INDEX_MASK = 255; + private final int bits; + private final int length; + @Nonnull + private final byte[] array; + + public BitFieldArr(int bits, int length) { + if (bits <= 0) { + throw new IllegalArgumentException("The number of bits must be greater than zero."); + } else if (length <= 0) { + throw new IllegalArgumentException("The length must be greater than zero."); + } else { + this.bits = bits; + this.array = new byte[length * bits / 8]; + this.length = length; + } + } + + public int getLength() { + return this.length; + } + + public int get(int index) { + int bitIndex = index * this.bits; + int endBitIndex = (index + 1) * this.bits - 1; + int endArrIndex = endBitIndex / 8; + int value = 0; + + for (int i = 0; i < this.bits; bitIndex++) { + int arrIndex = bitIndex / 8; + int startBit = bitIndex % 8; + if (arrIndex <= endArrIndex && startBit != 7) { + int endBit; + if (arrIndex == endArrIndex) { + endBit = endBitIndex % 8; + if (startBit == endBit) { + value |= (this.array[arrIndex] >> startBit & 1) << i; + } else if (startBit == 0 && endBit == 7) { + value |= (this.array[arrIndex] & 255) << i; + } else { + int mask = -1 >>> 32 - (endBit + 1 - startBit); + value |= (this.array[arrIndex] >>> startBit & mask) << i; + } + } else { + endBit = 7; + if (startBit == 0) { + value |= (this.array[arrIndex] & 255) << i; + } else { + int mask = -1 >>> 32 - (endBit + 1 - startBit); + value |= (this.array[arrIndex] >>> startBit & mask) << i; + } + } + + int inc = endBit - startBit; + i += inc; + bitIndex += inc; + } else { + value |= (this.array[arrIndex] >> startBit & 1) << i; + } + + i++; + } + + return value; + } + + public void set(int index, int value) { + int bitIndex = index * this.bits; + + for (int i = 0; i < this.bits; bitIndex++) { + this.setBit(bitIndex, value >> i & 1); + i++; + } + } + + private void setBit(int bitIndex, int bit) { + if (bit == 0) { + this.array[bitIndex / 8] = (byte)(this.array[bitIndex / 8] & ~(1 << bitIndex % 8)); + } else { + this.array[bitIndex / 8] = (byte)(this.array[bitIndex / 8] | 1 << bitIndex % 8); + } + } + + public byte[] get() { + byte[] bytes = new byte[this.array.length]; + System.arraycopy(this.array, 0, bytes, 0, this.array.length); + return bytes; + } + + public void set(@Nonnull byte[] bytes) { + System.arraycopy(bytes, 0, this.array, 0, Math.min(bytes.length, this.array.length)); + } + + @Nonnull + public String toBitString() { + StringBuilder sb = new StringBuilder(); + + for (byte b : this.array) { + sb.append(String.format("%8s", Integer.toBinaryString(b & 255)).replace(' ', '0')); + } + + return sb.toString(); + } + + public void copyFrom(@Nonnull BitFieldArr other) { + if (this.bits == other.bits) { + throw new IllegalArgumentException("bits must be the same"); + } else if (this.length == other.length) { + throw new IllegalArgumentException("length must be the same"); + } else { + System.arraycopy(other.array, 0, this.array, 0, this.array.length); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/palette/IntBytePalette.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/palette/IntBytePalette.java new file mode 100644 index 0000000..2a7ae59 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/palette/IntBytePalette.java @@ -0,0 +1,179 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.palette; + +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.util.io.ByteBufUtil; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import javax.annotation.Nonnull; + +public class IntBytePalette { + public static final int LENGTH = 1024; + private short count = 1; + private final Lock keysLock = new ReentrantLock(); + private int[] keys = new int[]{0}; + private final BitFieldArr array = new BitFieldArr(10, 1024); + + public IntBytePalette() { + } + + public IntBytePalette(int aDefault) { + this.keys = new int[]{aDefault}; + } + + public boolean set(int x, int z, int key) { + short id = this.contains(key); + int index = ChunkUtil.indexColumn(x, z); + if (id >= 1024) { + this.optimize(index); + id = this.contains(key); + } + + if (id >= 0) { + this.array.set(index, id); + } else { + this.keysLock.lock(); + + try { + short oldId = this.contains(key); + if (oldId >= 1024) { + this.optimize(index); + oldId = this.contains(key); + } + + if (oldId >= 0) { + this.array.set(index, oldId); + } else { + short newId = this.count++; + if (newId >= 32767) { + throw new IllegalArgumentException("Can't have more than 32767"); + } + + if (newId >= 1024) { + this.optimize(index); + newId = this.count++; + } + + if (newId >= this.keys.length) { + int[] keys = new int[newId + 1]; + System.arraycopy(this.keys, 0, keys, 0, this.keys.length); + this.keys = keys; + } + + this.keys[newId] = key; + this.array.set(index, newId); + } + } finally { + this.keysLock.unlock(); + } + } + + return true; + } + + public int get(int x, int z) { + return this.keys[this.array.get(ChunkUtil.indexColumn(x, z))]; + } + + public short contains(int key) { + this.keysLock.lock(); + + try { + for (short i = 0; i < this.keys.length; i++) { + int k = this.keys[i]; + if (k == key) { + return i; + } + } + + return -1; + } finally { + this.keysLock.unlock(); + } + } + + public void optimize() { + this.optimize(-1); + } + + private void optimize(int index) { + IntBytePalette intBytePalette = new IntBytePalette(this.keys[this.array.get(0)]); + + for (int i = 0; i < this.array.getLength(); i++) { + if (i != index) { + intBytePalette.set(ChunkUtil.xFromColumn(i), ChunkUtil.zFromColumn(i), this.keys[this.array.get(i)]); + } + } + + this.keysLock.lock(); + + try { + this.count = intBytePalette.count; + this.keys = intBytePalette.keys; + this.array.set(intBytePalette.array.get()); + } finally { + this.keysLock.unlock(); + } + } + + public void serialize(@Nonnull ByteBuf dos) { + this.keysLock.lock(); + + try { + dos.writeShortLE(this.count); + + for (int i = 0; i < this.count; i++) { + dos.writeIntLE(this.keys[i]); + } + + byte[] bytes = this.array.get(); + dos.writeIntLE(bytes.length); + dos.writeBytes(bytes); + } finally { + this.keysLock.unlock(); + } + } + + public void deserialize(@Nonnull ByteBuf dis) { + this.keysLock.lock(); + + try { + this.count = dis.readShortLE(); + this.keys = new int[this.count]; + + for (int i = 0; i < this.count; i++) { + this.keys[i] = dis.readIntLE(); + } + + int length = dis.readIntLE(); + byte[] bytes = new byte[length]; + dis.readBytes(bytes); + this.array.set(bytes); + if (this.count == 0) { + this.count = 1; + this.keys = new int[]{0}; + } + } finally { + this.keysLock.unlock(); + } + } + + public byte[] serialize() { + ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(); + this.serialize(buf); + return ByteBufUtil.getBytesRelease(buf); + } + + public void copyFrom(@Nonnull IntBytePalette other) { + this.keysLock.lock(); + + try { + this.count = other.count; + System.arraycopy(other.keys, 0, this.keys, 0, this.keys.length); + this.array.copyFrom(other.array); + } finally { + this.keysLock.unlock(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/palette/ShortBytePalette.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/palette/ShortBytePalette.java new file mode 100644 index 0000000..8c40924 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/palette/ShortBytePalette.java @@ -0,0 +1,182 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.palette; + +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.util.io.ByteBufUtil; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import javax.annotation.Nonnull; + +public class ShortBytePalette { + public static final int LENGTH = 1024; + private short count = 1; + private final Lock keysLock = new ReentrantLock(); + private short[] keys = new short[]{0}; + private final BitFieldArr array = new BitFieldArr(10, 1024); + + public ShortBytePalette() { + } + + public ShortBytePalette(short aDefault) { + this.keys = new short[]{aDefault}; + } + + public boolean set(int x, int z, short key) { + short id = this.contains(key); + int index = ChunkUtil.indexColumn(x, z); + if (id >= 1024) { + this.optimize(index); + id = this.contains(key); + } + + if (id >= 0) { + this.array.set(index, id); + } else { + this.keysLock.lock(); + + try { + short oldId = this.contains(key); + if (oldId >= 1024) { + this.optimize(index); + oldId = this.contains(key); + } + + if (oldId >= 0) { + this.array.set(index, oldId); + } else { + short newId = this.count++; + if (newId >= 32767) { + throw new IllegalArgumentException("Can't have more than 32767"); + } + + if (newId >= 1024) { + this.optimize(index); + newId = this.count++; + } + + if (newId >= this.keys.length) { + short[] keys = new short[newId + 1]; + System.arraycopy(this.keys, 0, keys, 0, this.keys.length); + this.keys = keys; + } + + this.keys[newId] = key; + this.array.set(index, newId); + } + } finally { + this.keysLock.unlock(); + } + } + + return true; + } + + public short get(int x, int z) { + return this.keys[this.array.get(ChunkUtil.indexColumn(x, z))]; + } + + public short get(int index) { + return this.keys[this.array.get(index)]; + } + + public short contains(short key) { + this.keysLock.lock(); + + try { + for (short i = 0; i < this.keys.length; i++) { + if (this.keys[i] == key) { + return i; + } + } + + return -1; + } finally { + this.keysLock.unlock(); + } + } + + public void optimize() { + this.optimize(-1); + } + + private void optimize(int index) { + ShortBytePalette shortBytePalette = new ShortBytePalette(this.keys[this.array.get(0)]); + + for (int i = 0; i < this.array.getLength(); i++) { + if (i != index) { + shortBytePalette.set(ChunkUtil.xFromColumn(i), ChunkUtil.zFromColumn(i), this.keys[this.array.get(i)]); + } + } + + this.keysLock.lock(); + + try { + this.count = shortBytePalette.count; + this.keys = shortBytePalette.keys; + this.array.set(shortBytePalette.array.get()); + } finally { + this.keysLock.unlock(); + } + } + + public void serialize(@Nonnull ByteBuf dos) { + this.keysLock.lock(); + + try { + dos.writeShortLE(this.count); + + for (int i = 0; i < this.count; i++) { + dos.writeShortLE(this.keys[i]); + } + + byte[] bytes = this.array.get(); + dos.writeIntLE(bytes.length); + dos.writeBytes(bytes); + } finally { + this.keysLock.unlock(); + } + } + + public void deserialize(@Nonnull ByteBuf buf) { + this.keysLock.lock(); + + try { + this.count = buf.readShortLE(); + this.keys = new short[this.count]; + + for (int i = 0; i < this.count; i++) { + this.keys[i] = buf.readShortLE(); + } + + int length = buf.readIntLE(); + byte[] bytes = new byte[length]; + buf.readBytes(bytes); + this.array.set(bytes); + if (this.count == 0) { + this.count = 1; + this.keys = new short[]{0}; + } + } finally { + this.keysLock.unlock(); + } + } + + public byte[] serialize() { + ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(); + this.serialize(buf); + return ByteBufUtil.getBytesRelease(buf); + } + + public void copyFrom(@Nonnull ShortBytePalette other) { + this.keysLock.lock(); + + try { + this.count = other.count; + System.arraycopy(other.keys, 0, this.keys, 0, this.keys.length); + this.array.copyFrom(other.array); + } finally { + this.keysLock.unlock(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/section/BlockSection.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/BlockSection.java new file mode 100644 index 0000000..2fc3b2b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/BlockSection.java @@ -0,0 +1,1078 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.section; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.BitSetUtil; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.function.predicate.ObjectPositionBlockFunction; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.protocol.CachedPacket; +import com.hypixel.hytale.protocol.packets.world.PaletteType; +import com.hypixel.hytale.protocol.packets.world.SetChunk; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockMigration; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; +import com.hypixel.hytale.server.core.modules.LegacyModule; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.palette.EmptySectionPalette; +import com.hypixel.hytale.server.core.universe.world.chunk.section.palette.ISectionPalette; +import com.hypixel.hytale.server.core.universe.world.chunk.section.palette.PaletteTypeEnum; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import com.hypixel.hytale.server.core.util.io.ByteBufUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ShortMap; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectHeapPriorityQueue; +import java.lang.ref.SoftReference; +import java.time.Instant; +import java.util.BitSet; +import java.util.Comparator; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.locks.StampedLock; +import java.util.function.Function; +import java.util.function.IntConsumer; +import java.util.function.ToIntFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockSection implements Component { + public static final int VERSION = 6; + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockSection.class, BlockSection::new) + .versioned() + .codecVersion(6) + .append(new KeyedCodec<>("Data", Codec.BYTE_ARRAY), BlockSection::deserialize, BlockSection::serialize) + .add() + .build(); + private final StampedLock chunkSectionLock = new StampedLock(); + public boolean loaded = false; + @Nonnull + private IntOpenHashSet changedPositions = new IntOpenHashSet(0); + @Nonnull + private IntOpenHashSet swapChangedPositions = new IntOpenHashSet(0); + private ISectionPalette chunkSection; + private ISectionPalette fillerSection; + private ISectionPalette rotationSection; + private ChunkLightData localLight; + private short localChangeCounter; + private ChunkLightData globalLight; + private short globalChangeCounter; + private BitSet tickingBlocks; + private final BitSet tickingBlocksCopy; + @Nonnull + private final BitSet tickingWaitAdjacentBlocks; + private int tickingBlocksCount; + private int tickingBlocksCountCopy; + private int tickingWaitAdjacentBlockCount; + private final ObjectHeapPriorityQueue tickRequests; + private double maximumHitboxExtent; + @Nullable + private transient SoftReference>> cachedChunkPacket; + @Nullable + @Deprecated(forRemoval = true) + private FluidSection migratedFluidSection; + @Nullable + @Deprecated(forRemoval = true) + private BlockPhysics migratedBlockPhysics; + private static final Comparator TICK_REQUEST_COMPARATOR = Comparator.comparing(t -> t.requestedGameTime); + + public static ComponentType getComponentType() { + return LegacyModule.get().getBlockSectionComponentType(); + } + + public BlockSection() { + this(EmptySectionPalette.INSTANCE, EmptySectionPalette.INSTANCE, EmptySectionPalette.INSTANCE); + } + + public BlockSection(ISectionPalette chunkSection, ISectionPalette fillerSection, ISectionPalette rotationSection) { + this.tickRequests = new ObjectHeapPriorityQueue<>(TICK_REQUEST_COMPARATOR); + this.maximumHitboxExtent = -1.0; + this.chunkSection = chunkSection; + this.fillerSection = fillerSection; + this.rotationSection = rotationSection; + this.tickingBlocks = new BitSet(); + this.tickingBlocksCopy = new BitSet(); + this.tickingWaitAdjacentBlocks = new BitSet(); + this.tickingBlocksCount = 0; + this.tickingBlocksCountCopy = 0; + this.localLight = ChunkLightData.EMPTY; + this.localChangeCounter = 0; + this.globalLight = ChunkLightData.EMPTY; + this.globalChangeCounter = 0; + } + + public ISectionPalette getChunkSection() { + return this.chunkSection; + } + + public void setChunkSection(ISectionPalette chunkSection) { + this.chunkSection = chunkSection; + } + + public void setLocalLight(@Nonnull ChunkLightDataBuilder localLight) { + Objects.requireNonNull(localLight); + this.localLight = localLight.build(); + } + + public void setGlobalLight(@Nonnull ChunkLightDataBuilder globalLight) { + Objects.requireNonNull(globalLight); + this.globalLight = globalLight.build(); + } + + public ChunkLightData getLocalLight() { + return this.localLight; + } + + public ChunkLightData getGlobalLight() { + return this.globalLight; + } + + public boolean hasLocalLight() { + return this.localLight.getChangeId() == this.localChangeCounter; + } + + public boolean hasGlobalLight() { + return this.globalLight.getChangeId() == this.globalChangeCounter; + } + + public void invalidateLocalLight() { + this.localChangeCounter++; + this.invalidateGlobalLight(); + } + + public void invalidateGlobalLight() { + this.globalChangeCounter++; + } + + public short getLocalChangeCounter() { + return this.localChangeCounter; + } + + public short getGlobalChangeCounter() { + return this.globalChangeCounter; + } + + public void invalidate() { + this.cachedChunkPacket = null; + } + + public int get(int index) { + long lock = this.chunkSectionLock.tryOptimisticRead(); + int i = this.chunkSection.get(index); + if (!this.chunkSectionLock.validate(lock)) { + lock = this.chunkSectionLock.readLock(); + + int var5; + try { + var5 = this.chunkSection.get(index); + } finally { + this.chunkSectionLock.unlockRead(lock); + } + + return var5; + } else { + return i; + } + } + + public int getFiller(int index) { + long lock = this.chunkSectionLock.tryOptimisticRead(); + int i = this.fillerSection.get(index); + if (!this.chunkSectionLock.validate(lock)) { + lock = this.chunkSectionLock.readLock(); + + int var5; + try { + var5 = this.fillerSection.get(index); + } finally { + this.chunkSectionLock.unlockRead(lock); + } + + return var5; + } else { + return i; + } + } + + public int getFiller(int x, int y, int z) { + return this.getFiller(ChunkUtil.indexBlock(x, y, z)); + } + + public int getRotationIndex(int index) { + long lock = this.chunkSectionLock.tryOptimisticRead(); + int i = this.rotationSection.get(index); + if (!this.chunkSectionLock.validate(lock)) { + lock = this.chunkSectionLock.readLock(); + + int var5; + try { + var5 = this.rotationSection.get(index); + } finally { + this.chunkSectionLock.unlockRead(lock); + } + + return var5; + } else { + return i; + } + } + + public int getRotationIndex(int x, int y, int z) { + return this.getRotationIndex(ChunkUtil.indexBlock(x, y, z)); + } + + public RotationTuple getRotation(int index) { + return RotationTuple.get(this.getRotationIndex(index)); + } + + public RotationTuple getRotation(int x, int y, int z) { + return this.getRotation(ChunkUtil.indexBlock(x, y, z)); + } + + public boolean set(int blockIdx, int blockId, int rotation, int filler) { + long lock = this.chunkSectionLock.writeLock(); + + boolean changed; + try { + ISectionPalette.SetResult result = this.chunkSection.set(blockIdx, blockId); + if (result == ISectionPalette.SetResult.REQUIRES_PROMOTE) { + this.chunkSection = this.chunkSection.promote(); + ISectionPalette.SetResult repeatResult = this.chunkSection.set(blockIdx, blockId); + if (repeatResult != ISectionPalette.SetResult.ADDED_OR_REMOVED) { + throw new IllegalStateException("Promoted chunk section failed to correctly add the new block!"); + } + } else { + if (result == ISectionPalette.SetResult.ADDED_OR_REMOVED) { + this.maximumHitboxExtent = -1.0; + } + + if (this.chunkSection.shouldDemote()) { + this.chunkSection = this.chunkSection.demote(); + } + } + + changed = result != ISectionPalette.SetResult.UNCHANGED; + result = this.fillerSection.set(blockIdx, filler); + if (result == ISectionPalette.SetResult.REQUIRES_PROMOTE) { + this.fillerSection = this.fillerSection.promote(); + ISectionPalette.SetResult repeatResult = this.fillerSection.set(blockIdx, filler); + if (repeatResult != ISectionPalette.SetResult.ADDED_OR_REMOVED) { + throw new IllegalStateException("Promoted chunk section failed to correctly add the new block!"); + } + } else if (this.fillerSection.shouldDemote()) { + this.fillerSection = this.fillerSection.demote(); + } + + changed |= result != ISectionPalette.SetResult.UNCHANGED; + result = this.rotationSection.set(blockIdx, rotation); + if (result == ISectionPalette.SetResult.REQUIRES_PROMOTE) { + this.rotationSection = this.rotationSection.promote(); + ISectionPalette.SetResult repeatResult = this.rotationSection.set(blockIdx, rotation); + if (repeatResult != ISectionPalette.SetResult.ADDED_OR_REMOVED) { + throw new IllegalStateException("Promoted chunk section failed to correctly add the new block!"); + } + } else if (this.rotationSection.shouldDemote()) { + this.rotationSection = this.rotationSection.demote(); + } + + changed |= result != ISectionPalette.SetResult.UNCHANGED; + if (changed && this.loaded) { + this.changedPositions.add(blockIdx); + } + } finally { + this.chunkSectionLock.unlockWrite(lock); + } + + if (changed) { + this.invalidateLocalLight(); + } + + return changed; + } + + @Nonnull + public IntOpenHashSet getAndClearChangedPositions() { + long stamp = this.chunkSectionLock.writeLock(); + + IntOpenHashSet var4; + try { + this.swapChangedPositions.clear(); + IntOpenHashSet tmp = this.changedPositions; + this.changedPositions = this.swapChangedPositions; + this.swapChangedPositions = tmp; + var4 = tmp; + } finally { + this.chunkSectionLock.unlockWrite(stamp); + } + + return var4; + } + + public boolean contains(int id) { + long lock = this.chunkSectionLock.tryOptimisticRead(); + boolean contains = this.chunkSection.contains(id); + if (!this.chunkSectionLock.validate(lock)) { + lock = this.chunkSectionLock.readLock(); + + boolean var5; + try { + var5 = this.chunkSection.contains(id); + } finally { + this.chunkSectionLock.unlockRead(lock); + } + + return var5; + } else { + return contains; + } + } + + public boolean containsAny(IntList ids) { + long lock = this.chunkSectionLock.tryOptimisticRead(); + boolean contains = this.chunkSection.containsAny(ids); + if (!this.chunkSectionLock.validate(lock)) { + lock = this.chunkSectionLock.readLock(); + + boolean var5; + try { + var5 = this.chunkSection.containsAny(ids); + } finally { + this.chunkSectionLock.unlockRead(lock); + } + + return var5; + } else { + return contains; + } + } + + public int count() { + long lock = this.chunkSectionLock.tryOptimisticRead(); + int count = this.chunkSection.count(); + if (!this.chunkSectionLock.validate(lock)) { + lock = this.chunkSectionLock.readLock(); + + int var4; + try { + var4 = this.chunkSection.count(); + } finally { + this.chunkSectionLock.unlockRead(lock); + } + + return var4; + } else { + return count; + } + } + + public int count(int id) { + long lock = this.chunkSectionLock.tryOptimisticRead(); + int count = this.chunkSection.count(id); + if (!this.chunkSectionLock.validate(lock)) { + lock = this.chunkSectionLock.readLock(); + + int var5; + try { + var5 = this.chunkSection.count(id); + } finally { + this.chunkSectionLock.unlockRead(lock); + } + + return var5; + } else { + return count; + } + } + + public IntSet values() { + long lock = this.chunkSectionLock.tryOptimisticRead(); + IntSet values = this.chunkSection.values(); + if (!this.chunkSectionLock.validate(lock)) { + lock = this.chunkSectionLock.readLock(); + + IntSet var4; + try { + var4 = this.chunkSection.values(); + } finally { + this.chunkSectionLock.unlockRead(lock); + } + + return var4; + } else { + return values; + } + } + + public void forEachValue(IntConsumer consumer) { + long lock = this.chunkSectionLock.readLock(); + + try { + this.chunkSection.forEachValue(consumer); + } finally { + this.chunkSectionLock.unlockRead(lock); + } + } + + public Int2ShortMap valueCounts() { + long lock = this.chunkSectionLock.tryOptimisticRead(); + Int2ShortMap valueCounts = this.chunkSection.valueCounts(); + if (!this.chunkSectionLock.validate(lock)) { + lock = this.chunkSectionLock.readLock(); + + Int2ShortMap var4; + try { + var4 = this.chunkSection.valueCounts(); + } finally { + this.chunkSectionLock.unlockRead(lock); + } + + return var4; + } else { + return valueCounts; + } + } + + public boolean isSolidAir() { + long lock = this.chunkSectionLock.tryOptimisticRead(); + boolean isSolid = this.chunkSection.isSolid(0); + if (!this.chunkSectionLock.validate(lock)) { + lock = this.chunkSectionLock.readLock(); + + boolean var4; + try { + var4 = this.chunkSection.isSolid(0); + } finally { + this.chunkSectionLock.unlockRead(lock); + } + + return var4; + } else { + return isSolid; + } + } + + public void find(IntList ids, IntSet internalIdHolder, IntConsumer indexConsumer) { + long lock = this.chunkSectionLock.readLock(); + + try { + this.chunkSection.find(ids, internalIdHolder, indexConsumer); + } finally { + this.chunkSectionLock.unlockRead(lock); + } + } + + public boolean setTicking(int blockIdx, boolean ticking) { + long readStamp = this.chunkSectionLock.readLock(); + + try { + if (this.tickingBlocks.get(blockIdx) == ticking) { + return false; + } + } finally { + this.chunkSectionLock.unlockRead(readStamp); + } + + long writeStamp = this.chunkSectionLock.writeLock(); + + boolean var7; + try { + if (this.tickingBlocks.get(blockIdx) == ticking) { + return false; + } + + if (ticking) { + this.tickingBlocksCount++; + } else { + this.tickingBlocksCount--; + } + + this.tickingBlocks.set(blockIdx, ticking); + var7 = true; + } finally { + this.chunkSectionLock.unlockWrite(writeStamp); + } + + return var7; + } + + public int getTickingBlocksCount() { + return this.tickingBlocksCount > 0 ? this.tickingBlocksCount : 0; + } + + public int getTickingBlocksCountCopy() { + return this.tickingBlocksCountCopy; + } + + public boolean hasTicking() { + return this.tickingBlocksCount > 0; + } + + public boolean isTicking(int blockIdx) { + if (this.tickingBlocksCount > 0) { + long readStamp = this.chunkSectionLock.readLock(); + + boolean var4; + try { + var4 = this.tickingBlocks.get(blockIdx); + } finally { + this.chunkSectionLock.unlockRead(readStamp); + } + + return var4; + } else { + return false; + } + } + + public void scheduleTick(int index, @Nullable Instant gameTime) { + if (gameTime != null) { + this.tickRequests.enqueue(new BlockSection.TickRequest(index, gameTime)); + } + } + + public void preTick(Instant gameTime) { + BlockSection.TickRequest request; + while (!this.tickRequests.isEmpty() && (request = this.tickRequests.first()).requestedGameTime.isBefore(gameTime)) { + this.tickRequests.dequeue(); + this.setTicking(request.index, true); + } + + long writeStamp = this.chunkSectionLock.writeLock(); + + try { + if (this.tickingBlocksCount != 0) { + BitSetUtil.copyValues(this.tickingBlocks, this.tickingBlocksCopy); + this.tickingBlocksCountCopy = this.tickingBlocksCount; + this.tickingBlocks.clear(); + this.tickingBlocksCount = 0; + return; + } + + this.tickingBlocksCountCopy = 0; + } finally { + this.chunkSectionLock.unlockWrite(writeStamp); + } + } + + public int forEachTicking(T t, V v, int sectionIndex, @Nonnull ObjectPositionBlockFunction acceptor) { + if (this.tickingBlocksCountCopy == 0) { + return 0; + } else { + int sectionStartYBlock = sectionIndex << 5; + int ticked = 0; + + for (int index = this.tickingBlocksCopy.nextSetBit(0); index >= 0; index = this.tickingBlocksCopy.nextSetBit(index + 1)) { + int x = ChunkUtil.xFromIndex(index); + int y = ChunkUtil.yFromIndex(index); + int z = ChunkUtil.zFromIndex(index); + BlockTickStrategy strategy = acceptor.accept(t, v, x, y | sectionStartYBlock, z, this.get(index)); + long writeStamp = this.chunkSectionLock.writeLock(); + + try { + switch (strategy) { + case WAIT_FOR_ADJACENT_CHUNK_LOAD: + if (!this.tickingWaitAdjacentBlocks.get(index)) { + this.tickingWaitAdjacentBlockCount++; + this.tickingWaitAdjacentBlocks.set(index, true); + } + break; + case CONTINUE: + if (!this.tickingBlocks.get(index)) { + this.tickingBlocksCount++; + this.tickingBlocks.set(index, true); + } + } + } finally { + this.chunkSectionLock.unlockWrite(writeStamp); + } + + ticked++; + } + + return ticked; + } + } + + public void mergeTickingBlocks() { + long writeStamp = this.chunkSectionLock.writeLock(); + + try { + this.tickingBlocks.or(this.tickingWaitAdjacentBlocks); + this.tickingBlocksCount = this.tickingBlocks.cardinality(); + this.tickingWaitAdjacentBlocks.clear(); + this.tickingWaitAdjacentBlockCount = 0; + } finally { + this.chunkSectionLock.unlockWrite(writeStamp); + } + } + + public double getMaximumHitboxExtent() { + double extent = this.maximumHitboxExtent; + if (extent != -1.0) { + return extent; + } else { + double maximumExtent = BlockBoundingBoxes.UNIT_BOX_MAXIMUM_EXTENT; + long lock = this.chunkSectionLock.readLock(); + + try { + IndexedLookupTableAssetMap hitBoxAssetMap = BlockBoundingBoxes.getAssetMap(); + BlockTypeAssetMap blockTypeMap = BlockType.getAssetMap(); + + for (int idx = 0; idx < 32768; idx++) { + int blockId = this.chunkSection.get(idx); + if (blockId != 0) { + int rotation = this.rotationSection.get(idx); + BlockType blockType = blockTypeMap.getAsset(blockId); + BlockBoundingBoxes asset = hitBoxAssetMap.getAsset(blockType.getHitboxTypeIndex()); + if (asset != BlockBoundingBoxes.UNIT_BOX) { + double boxMaximumExtent = asset.get(rotation).getBoundingBox().getMaximumExtent(); + if (boxMaximumExtent > maximumExtent) { + maximumExtent = boxMaximumExtent; + } + } + } + } + } finally { + this.chunkSectionLock.unlockRead(lock); + } + + return this.maximumHitboxExtent = maximumExtent; + } + } + + @Deprecated + public void invalidateBlock(int x, int y, int z) { + long stamp = this.chunkSectionLock.writeLock(); + + try { + this.changedPositions.add(ChunkUtil.indexBlock(x, y, z)); + } finally { + this.chunkSectionLock.unlockWrite(stamp); + } + } + + @Nullable + @Deprecated(forRemoval = true) + public FluidSection takeMigratedFluid() { + FluidSection temp = this.migratedFluidSection; + this.migratedFluidSection = null; + return temp; + } + + @Nullable + @Deprecated(forRemoval = true) + public BlockPhysics takeMigratedDecoBlocks() { + BlockPhysics temp = this.migratedBlockPhysics; + this.migratedBlockPhysics = null; + return temp; + } + + public void serializeForPacket(@Nonnull ByteBuf buf) { + long lock = this.chunkSectionLock.readLock(); + + try { + PaletteType paletteType = this.chunkSection.getPaletteType(); + byte paletteTypeId = (byte)paletteType.ordinal(); + buf.writeByte(paletteTypeId); + this.chunkSection.serializeForPacket(buf); + PaletteType fillerType = this.fillerSection.getPaletteType(); + byte fillerTypeId = (byte)fillerType.ordinal(); + buf.writeByte(fillerTypeId); + this.fillerSection.serializeForPacket(buf); + PaletteType rotationType = this.rotationSection.getPaletteType(); + byte rotationTypeId = (byte)rotationType.ordinal(); + buf.writeByte(rotationTypeId); + this.rotationSection.serializeForPacket(buf); + } finally { + this.chunkSectionLock.unlockRead(lock); + } + } + + public void serialize(ISectionPalette.KeySerializer keySerializer, @Nonnull ByteBuf buf) { + long lock = this.chunkSectionLock.readLock(); + + try { + buf.writeInt(BlockMigration.getAssetMap().getAssetCount()); + PaletteType paletteType = this.chunkSection.getPaletteType(); + buf.writeByte(paletteType.ordinal()); + this.chunkSection.serialize(keySerializer, buf); + if (paletteType != PaletteType.Empty) { + BitSet combinedTickingBlock = (BitSet)this.tickingBlocks.clone(); + combinedTickingBlock.or(this.tickingWaitAdjacentBlocks); + buf.writeShort(combinedTickingBlock.cardinality()); + long[] data = combinedTickingBlock.toLongArray(); + buf.writeShort(data.length); + + for (long l : data) { + buf.writeLong(l); + } + } + + buf.writeByte(this.fillerSection.getPaletteType().ordinal()); + this.fillerSection.serialize(ByteBuf::writeShort, buf); + buf.writeByte(this.rotationSection.getPaletteType().ordinal()); + this.rotationSection.serialize(ByteBuf::writeByte, buf); + this.localLight.serialize(buf); + this.globalLight.serialize(buf); + buf.writeShort(this.localChangeCounter); + buf.writeShort(this.globalChangeCounter); + } finally { + this.chunkSectionLock.unlockRead(lock); + } + } + + public byte[] serialize(ExtraInfo extraInfo) { + ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(); + + try { + this.serialize(BlockType.KEY_SERIALIZER, buf); + return ByteBufUtil.getBytesRelease(buf); + } catch (Throwable var4) { + buf.release(); + throw SneakyThrow.sneakyThrow(var4); + } + } + + public void deserialize(ToIntFunction keyDeserializer, @Nonnull ByteBuf buf, int version) { + int blockMigrationVersion = 0; + if (version >= 6) { + blockMigrationVersion = buf.readInt(); + } + + Function blockMigration = null; + Map blockMigrationMap = BlockMigration.getAssetMap().getAssetMap(); + + for (BlockMigration migration = blockMigrationMap.get(blockMigrationVersion); + migration != null; + migration = blockMigrationMap.get(++blockMigrationVersion) + ) { + if (blockMigration == null) { + blockMigration = migration::getMigration; + } else { + blockMigration = blockMigration.andThen(migration::getMigration); + } + } + + PaletteTypeEnum typeEnum = PaletteTypeEnum.get(buf.readByte()); + PaletteType paletteType = typeEnum.getPaletteType(); + this.chunkSection = typeEnum.getConstructor().get(); + if (version <= 4) { + ISectionPalette tempSection = typeEnum.getConstructor().get(); + boolean[] foundMigratable = new boolean[]{false}; + boolean[] needsPhysics = new boolean[]{false}; + int[] nextTempIndex = new int[]{-1}; + Int2ObjectOpenHashMap types = new Int2ObjectOpenHashMap<>(); + Object2IntOpenHashMap typesRev = new Object2IntOpenHashMap<>(); + typesRev.defaultReturnValue(Integer.MIN_VALUE); + Function finalBlockMigration = blockMigration; + tempSection.deserialize( + bytebuf -> { + String keyx = ByteBufUtil.readUTF(bytebuf); + if (finalBlockMigration != null) { + keyx = finalBlockMigration.apply(keyx); + } + + int indexx = typesRev.getInt(keyx); + if (indexx != Integer.MIN_VALUE) { + return indexx; + } else { + boolean migratable = keyx.startsWith("Fluid_") + || keyx.contains("|Fluid=") + || keyx.contains("|Deco") + || keyx.contains("|Support") + || keyx.contains("|Filler") + || keyx.contains("|Yaw=") + || keyx.contains("|Pitch=") + || keyx.contains("|Roll="); + foundMigratable[0] |= migratable; + Object var10x; + if (migratable) { + var10x = nextTempIndex[0]--; + } else { + var10x = BlockType.getBlockIdOrUnknown(keyx, "Unknown BlockType %s", keyx); + needsPhysics[0] |= BlockType.getAssetMap().getAsset((int)var10x).hasSupport(); + } + + types.put((int)var10x, keyx); + typesRev.put(keyx, (int)var10x); + return (int)var10x; + } + }, + buf, + version + ); + if (needsPhysics[0]) { + this.migratedBlockPhysics = new BlockPhysics(); + } + + if (foundMigratable[0]) { + for (int index = 0; index < 32768; index++) { + int id = tempSection.get(index); + if (id >= 0) { + this.chunkSection.set(index, id); + } else { + Rotation rotationYaw = Rotation.None; + Rotation rotationPitch = Rotation.None; + Rotation rotationRoll = Rotation.None; + String key = types.get(id); + if (key.startsWith("Fluid_") || key.contains("|Fluid=")) { + if (this.migratedFluidSection == null) { + this.migratedFluidSection = new FluidSection(); + } + + Fluid.ConversionResult result = Fluid.convertBlockToFluid(key); + if (result == null) { + throw new RuntimeException("Invalid Fluid Key " + key); + } + + if (result.blockTypeStr == null) { + this.migratedFluidSection.setFluid(index, result.fluidId, result.fluidLevel); + continue; + } + + key = result.blockTypeStr; + this.migratedFluidSection.setFluid(index, result.fluidId, result.fluidLevel); + } + + if (key.contains("|Deco")) { + if (this.migratedBlockPhysics == null) { + this.migratedBlockPhysics = new BlockPhysics(); + } + + this.migratedBlockPhysics.set(index, 15); + } + + if (key.contains("|Support=")) { + if (this.migratedBlockPhysics == null) { + this.migratedBlockPhysics = new BlockPhysics(); + } + + int start = key.indexOf("|Support=") + "|Support=".length(); + int end = key.indexOf(124, start); + if (end == -1) { + end = key.length(); + } + + this.migratedBlockPhysics.set(index, Integer.parseInt(key, start, end, 10)); + } + + if (key.contains("|Filler=")) { + int start = key.indexOf("|Filler=") + "|Filler=".length(); + int firstComma = key.indexOf(44, start); + if (firstComma == -1) { + throw new IllegalArgumentException("Invalid filler metadata! Missing comma"); + } + + int secondComma = key.indexOf(44, firstComma + 1); + if (secondComma == -1) { + throw new IllegalArgumentException("Invalid filler metadata! Missing second comma"); + } + + int end = key.indexOf(124, start); + if (end == -1) { + end = key.length(); + } + + int fillerX = Integer.parseInt(key, start, firstComma, 10); + int fillerY = Integer.parseInt(key, firstComma + 1, secondComma, 10); + int fillerZ = Integer.parseInt(key, secondComma + 1, end, 10); + int filler = FillerBlockUtil.pack(fillerX, fillerY, fillerZ); + ISectionPalette.SetResult resultx = this.fillerSection.set(index, filler); + if (resultx == ISectionPalette.SetResult.REQUIRES_PROMOTE) { + this.fillerSection = this.fillerSection.promote(); + this.fillerSection.set(index, filler); + } + } + + if (key.contains("|Yaw=")) { + int startx = key.indexOf("|Yaw=") + "|Yaw=".length(); + int endx = key.indexOf(124, startx); + if (endx == -1) { + endx = key.length(); + } + + rotationYaw = Rotation.ofDegrees(Integer.parseInt(key, startx, endx, 10)); + } + + if (key.contains("|Pitch=")) { + int startx = key.indexOf("|Pitch=") + "|Pitch=".length(); + int endx = key.indexOf(124, startx); + if (endx == -1) { + endx = key.length(); + } + + rotationPitch = Rotation.ofDegrees(Integer.parseInt(key, startx, endx, 10)); + } + + if (key.contains("|Roll=")) { + int startx = key.indexOf("|Roll=") + "|Roll=".length(); + int endx = key.indexOf(124, startx); + if (endx == -1) { + endx = key.length(); + } + + rotationRoll = Rotation.ofDegrees(Integer.parseInt(key, startx, endx, 10)); + } + + if (rotationYaw != Rotation.None || rotationPitch != Rotation.None || rotationRoll != Rotation.None) { + int rotation = RotationTuple.index(rotationYaw, rotationPitch, rotationRoll); + ISectionPalette.SetResult resultx = this.rotationSection.set(index, rotation); + if (resultx == ISectionPalette.SetResult.REQUIRES_PROMOTE) { + this.rotationSection = this.rotationSection.promote(); + this.rotationSection.set(index, rotation); + } + } + + int endOfName = key.indexOf(124); + if (endOfName != -1) { + key = key.substring(0, endOfName); + } + + this.chunkSection.set(index, BlockType.getBlockIdOrUnknown(key, "Unknown BlockType: %s", key)); + } + } + + if (this.chunkSection.shouldDemote()) { + this.chunkSection.demote(); + } + } else { + this.chunkSection = tempSection; + } + } else if (blockMigration != null) { + this.chunkSection.deserialize(bytebuf -> { + String keyx = ByteBufUtil.readUTF(bytebuf); + keyx = blockMigration.apply(keyx); + return BlockType.getBlockIdOrUnknown(keyx, "Unknown BlockType %s", keyx); + }, buf, version); + } else { + this.chunkSection.deserialize(keyDeserializer, buf, version); + } + + if (paletteType != PaletteType.Empty) { + this.tickingBlocksCount = buf.readUnsignedShort(); + int len = buf.readUnsignedShort(); + long[] tickingBlocksData = new long[len]; + + for (int i = 0; i < tickingBlocksData.length; i++) { + tickingBlocksData[i] = buf.readLong(); + } + + this.tickingBlocks = BitSet.valueOf(tickingBlocksData); + this.tickingBlocksCount = this.tickingBlocks.cardinality(); + } + + if (version >= 4) { + PaletteTypeEnum fillerTypeEnum = PaletteTypeEnum.get(buf.readByte()); + this.fillerSection = fillerTypeEnum.getConstructor().get(); + this.fillerSection.deserialize(ByteBuf::readUnsignedShort, buf, version); + } + + if (version >= 5) { + PaletteTypeEnum rotationTypeEnum = PaletteTypeEnum.get(buf.readByte()); + this.rotationSection = rotationTypeEnum.getConstructor().get(); + this.rotationSection.deserialize(ByteBuf::readUnsignedByte, buf, version); + } + + this.localLight = ChunkLightData.deserialize(buf, version); + this.globalLight = ChunkLightData.deserialize(buf, version); + this.localChangeCounter = buf.readShort(); + this.globalChangeCounter = buf.readShort(); + } + + public void deserialize(@Nonnull byte[] bytes, @Nonnull ExtraInfo extraInfo) { + ByteBuf buf = Unpooled.wrappedBuffer(bytes); + this.deserialize(BlockType.KEY_DESERIALIZER, buf, extraInfo.getVersion()); + } + + @Override + public Component clone() { + throw new UnsupportedOperationException("Not implemented!"); + } + + @Nonnull + @Override + public Component cloneSerializable() { + return this; + } + + @Nonnull + public CompletableFuture> getCachedChunkPacket(int x, int y, int z) { + SoftReference>> ref = this.cachedChunkPacket; + CompletableFuture> future = ref != null ? ref.get() : null; + if (future != null) { + return future; + } else { + future = CompletableFuture.supplyAsync(() -> { + byte[] localLightArr = null; + byte[] globalLightArr = null; + byte[] data = null; + if (BlockChunk.SEND_LOCAL_LIGHTING_DATA && this.hasLocalLight()) { + ChunkLightData localLight = this.getLocalLight(); + ByteBuf buffer = Unpooled.buffer(); + localLight.serializeForPacket(buffer); + if (this.getLocalChangeCounter() == localLight.getChangeId()) { + localLightArr = ByteBufUtil.getBytesRelease(buffer); + } + } + + if (BlockChunk.SEND_GLOBAL_LIGHTING_DATA && this.hasGlobalLight()) { + ByteBuf buffer = Unpooled.buffer(); + ChunkLightData globalLight = this.getGlobalLight(); + globalLight.serializeForPacket(buffer); + if (this.getGlobalChangeCounter() == globalLight.getChangeId()) { + globalLightArr = ByteBufUtil.getBytesRelease(buffer); + } + } + + if (!this.isSolidAir()) { + ByteBuf buf = Unpooled.buffer(65536); + this.serializeForPacket(buf); + data = ByteBufUtil.getBytesRelease(buf); + } + + SetChunk setChunk = new SetChunk(x, y, z, localLightArr, globalLightArr, data); + return CachedPacket.cache(setChunk); + }); + this.cachedChunkPacket = new SoftReference<>(future); + return future; + } + } + + public int get(int x, int y, int z) { + return this.get(ChunkUtil.indexBlock(x, y, z)); + } + + public boolean set(int x, int y, int z, int blockId, int rotation, int filler) { + return this.set(ChunkUtil.indexBlock(x, y, z), blockId, rotation, filler); + } + + public boolean setTicking(int x, int y, int z, boolean ticking) { + return this.setTicking(ChunkUtil.indexBlock(x, y, z), ticking); + } + + public boolean isTicking(int x, int y, int z) { + return this.isTicking(ChunkUtil.indexBlock(x, y, z)); + } + + private record TickRequest(int index, @Nonnull Instant requestedGameTime) { + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/section/ChunkLightData.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/ChunkLightData.java new file mode 100644 index 0000000..a49edad --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/ChunkLightData.java @@ -0,0 +1,246 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.section; + +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import javax.annotation.Nonnull; + +public class ChunkLightData { + public static final ChunkLightData EMPTY = new ChunkLightData(null, (short)0); + public static final int TREE_SIZE = 8; + public static final int TREE_MASK = 7; + public static final int DEPTH_MAGIC = 12; + public static final int SIZE_MAGIC = 17; + public static final int INITIAL_CAPACITY = 128; + public static final byte MAX_VALUE = 15; + public static final int CHANNEL_COUNT = 4; + public static final int BITS_PER_CHANNEL = 4; + public static final int CHANNEL_MASK = 15; + public static final int RED_CHANNEL = 0; + public static final int GREEN_CHANNEL = 1; + public static final int BLUE_CHANNEL = 2; + public static final int SKY_CHANNEL = 3; + public static final int RED_CHANNEL_BIT = 0; + public static final int GREEN_CHANNEL_BIT = 4; + public static final int BLUE_CHANNEL_BIT = 8; + public static final int SKY_CHANNEL_BIT = 12; + public static final int RGB_MASK = -61441; + protected final short changeId; + ByteBuf light; + + public ChunkLightData(ByteBuf light, short changeId) { + this.light = light; + this.changeId = changeId; + } + + public short getChangeId() { + return this.changeId; + } + + public byte getRedBlockLight(int x, int y, int z) { + return this.getRedBlockLight(ChunkUtil.indexBlock(x, y, z)); + } + + public byte getRedBlockLight(int index) { + return this.light == null ? 0 : this.getLight(index, 0); + } + + public byte getGreenBlockLight(int x, int y, int z) { + return this.getGreenBlockLight(ChunkUtil.indexBlock(x, y, z)); + } + + public byte getGreenBlockLight(int index) { + return this.light == null ? 0 : this.getLight(index, 1); + } + + public byte getBlueBlockLight(int x, int y, int z) { + return this.getBlueBlockLight(ChunkUtil.indexBlock(x, y, z)); + } + + public byte getBlueBlockLight(int index) { + return this.light == null ? 0 : this.getLight(index, 2); + } + + public byte getBlockLightIntensity(int x, int y, int z) { + return this.getBlockLightIntensity(ChunkUtil.indexBlock(x, y, z)); + } + + public byte getBlockLightIntensity(int index) { + if (this.light == null) { + return 0; + } else { + byte r = this.getLight(index, 0); + byte g = this.getLight(index, 1); + byte b = this.getLight(index, 2); + return (byte)(MathUtil.maxValue(b, g, r) & 15); + } + } + + public short getBlockLight(int x, int y, int z) { + return this.getBlockLight(ChunkUtil.indexBlock(x, y, z)); + } + + public short getBlockLight(int index) { + return this.light == null ? 0 : (short)(this.getLightRaw(index) & -61441); + } + + public byte getSkyLight(int x, int y, int z) { + return this.getSkyLight(ChunkUtil.indexBlock(x, y, z)); + } + + public byte getSkyLight(int index) { + return this.light == null ? 0 : this.getLight(index, 3); + } + + public byte getLight(int index, int channel) { + if (channel < 0 || channel >= 4) { + throw new IllegalArgumentException(); + } else if (this.light == null) { + return 0; + } else { + short value = this.getLightRaw(index); + return (byte)(value >> channel * 4 & 15); + } + } + + public short getLightRaw(int x, int y, int z) { + return this.getLightRaw(ChunkUtil.indexBlock(x, y, z)); + } + + public short getLightRaw(int index) { + if (this.light == null) { + return 0; + } else if (index >= 0 && index < 32768) { + return getTraverse(this.light, index, 0, 0); + } else { + throw new IllegalArgumentException("Index " + index + " is outside of the bounds!"); + } + } + + protected static short getTraverse(@Nonnull ByteBuf local, int index, int pointer, int depth) { + int loc = -1; + int result = -1; + + try { + int position = pointer * 17; + byte mask = local.getByte(position); + int innerIndex = index >> 12 - depth & 7; + loc = innerIndex * 2 + position + 1; + result = local.getUnsignedShort(loc); + return (mask >> innerIndex & 1) == 1 ? getTraverse(local, index, result, depth + 3) : (short)result; + } catch (Throwable var9) { + throw new RuntimeException("Failed with " + index + ", " + pointer + ", " + depth + ". Result: " + result + " from " + loc, var9); + } + } + + public void serialize(@Nonnull ByteBuf buf) { + buf.writeShort(this.changeId); + boolean hasLight = this.light != null; + buf.writeBoolean(hasLight); + if (hasLight) { + buf.ensureWritable(this.light.readableBytes()); + int before = buf.writerIndex(); + buf.writeInt(0); + this.serializeOctree(buf, 0); + int after = buf.writerIndex(); + buf.writerIndex(before); + buf.writeInt(after - before - 4); + buf.writerIndex(after); + } + } + + private void serializeOctree(@Nonnull ByteBuf buf, int position) { + int mask = this.light.getByte(position * 17); + buf.writeByte(mask); + + for (int i = 0; i < 8; i++) { + int val = this.light.getUnsignedShort(position * 17 + i * 2 + 1); + if ((mask >> i & 1) == 1) { + this.serializeOctree(buf, val); + } else { + buf.writeShort(val); + } + } + } + + public void serializeForPacket(@Nonnull ByteBuf buf) { + boolean hasLight = this.light != null; + buf.writeBoolean(hasLight); + if (hasLight) { + buf.ensureWritable(this.light.readableBytes()); + this.serializeOctreeForPacket(buf, 0); + } + } + + private void serializeOctreeForPacket(@Nonnull ByteBuf buf, int position) { + int mask = this.light.getByte(position * 17); + buf.writeByte(mask); + + for (int i = 0; i < 8; i++) { + int val = this.light.getUnsignedShort(position * 17 + i * 2 + 1); + if ((mask >> i & 1) == 1) { + this.serializeOctreeForPacket(buf, val); + } else { + buf.writeShortLE(val); + } + } + } + + @Nonnull + public static ChunkLightData deserialize(@Nonnull ByteBuf buf, int version) { + short changeId = buf.readShort(); + boolean hasLight = buf.readBoolean(); + ChunkLightData chunkLightData; + if (hasLight) { + int length = buf.readInt(); + ByteBuf from = buf.readSlice(length); + int estSize = length * 23 / 20; + ByteBuf buffer = Unpooled.buffer(estSize); + buffer.writerIndex(17); + deserializeOctree(from, buffer, 0, 0); + chunkLightData = new ChunkLightData(buffer.copy(), changeId); + } else { + chunkLightData = new ChunkLightData(null, changeId); + } + + return chunkLightData; + } + + private static int deserializeOctree(@Nonnull ByteBuf from, @Nonnull ByteBuf to, int position, int segmentIndex) { + int mask = from.readByte(); + to.setByte(position * 17, mask); + + for (int i = 0; i < 8; i++) { + int val; + if ((mask >> i & 1) == 1) { + to.writerIndex((++segmentIndex + 1) * 17); + val = segmentIndex; + segmentIndex = deserializeOctree(from, to, segmentIndex, segmentIndex); + } else { + val = from.readShort(); + } + + to.setShort(position * 17 + i * 2 + 1, val); + } + + return segmentIndex; + } + + @Nonnull + public String octreeToString() { + return this.light == null ? "NULL" : ChunkLightDataBuilder.octreeToString(this.light); + } + + public static short combineLightValues(byte red, byte green, byte blue, byte sky) { + return (short)(sky << 12 | blue << 8 | green << 4 | red << 0); + } + + public static short combineLightValues(byte red, byte green, byte blue) { + return (short)(blue << 8 | green << 4 | red << 0); + } + + public static byte getLightValue(short value, int channel) { + return (byte)(value >> channel * 4 & 15); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/section/ChunkLightDataBuilder.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/ChunkLightDataBuilder.java new file mode 100644 index 0000000..760191d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/ChunkLightDataBuilder.java @@ -0,0 +1,324 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.section; + +import com.hypixel.hytale.math.util.ChunkUtil; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import java.util.BitSet; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChunkLightDataBuilder extends ChunkLightData { + static boolean DEBUG = false; + protected BitSet currentSegments; + + public ChunkLightDataBuilder(short changeId) { + super(null, changeId); + } + + public ChunkLightDataBuilder(@Nonnull ChunkLightData lightData, short changeId) { + super(lightData.light != null ? lightData.light.copy() : null, changeId); + if (lightData instanceof ChunkLightDataBuilder) { + throw new IllegalArgumentException("ChunkLightDataBuilder light data isn't compacted so we can't read this cleanly atm"); + } else { + if (this.light != null) { + this.currentSegments = new BitSet(); + this.currentSegments.set(0); + findSegments(this.light, 0, this.currentSegments); + } + } + } + + protected static void findSegments(@Nonnull ByteBuf light, int position, @Nonnull BitSet currentSegments) { + int mask = light.getByte(position * 17); + + for (int i = 0; i < 8; i++) { + int val = light.getUnsignedShort(position * 17 + i * 2 + 1); + if ((mask >> i & 1) == 1) { + currentSegments.set(val); + findSegments(light, val, currentSegments); + } + } + } + + public void setBlockLight(int x, int y, int z, byte red, byte green, byte blue) { + this.setBlockLight(ChunkUtil.indexBlock(x, y, z), red, green, blue); + } + + public void setBlockLight(int index, byte red, byte green, byte blue) { + byte sky = this.getLight(index, 3); + this.setLightRaw(index, combineLightValues(red, green, blue, sky)); + } + + public void setSkyLight(int x, int y, int z, byte light) { + this.setSkyLight(ChunkUtil.indexBlock(x, y, z), light); + } + + public void setSkyLight(int index, byte light) { + this.setLight(index, 3, light); + } + + public void setLight(int index, int channel, byte value) { + if (channel >= 0 && channel < 4) { + int current = this.getLightRaw(index); + current &= ~(15 << channel * 4); + current |= (value & 15) << channel * 4; + this.setLightRaw(index, (short)current); + } else { + throw new IllegalArgumentException(); + } + } + + public void setLightRaw(int index, short value) { + if (index >= 0 && index < 32768) { + if (this.light == null) { + this.light = Unpooled.buffer(2176); + } + + if (this.currentSegments == null) { + this.currentSegments = new BitSet(); + this.currentSegments.set(0); + } + + setTraverse(this.light, this.currentSegments, index, 0, 0, value); + } else { + throw new IllegalArgumentException("Index " + index + " is outside of the bounds!"); + } + } + + @Nonnull + public ChunkLightData build() { + if (this.light == null) { + return new ChunkLightData(null, this.changeId); + } else { + ByteBuf buffer = Unpooled.buffer(this.currentSegments.cardinality() * 17); + buffer.writerIndex(17); + this.serializeOctree(buffer, 0, 0); + return new ChunkLightData(buffer, this.changeId); + } + } + + private int serializeOctree(@Nonnull ByteBuf to, int position, int segmentIndex) { + int toPosition = segmentIndex; + int mask = this.light.getByte(position * 17); + to.setByte(segmentIndex * 17, mask); + + for (int i = 0; i < 8; i++) { + int val = this.light.getUnsignedShort(position * 17 + i * 2 + 1); + if ((mask >> i & 1) == 1) { + to.ensureWritable(17); + to.writerIndex((++segmentIndex + 1) * 17); + int from = val; + val = segmentIndex; + segmentIndex = this.serializeOctree(to, from, segmentIndex); + } + + to.setShort(toPosition * 17 + i * 2 + 1, val); + } + + return segmentIndex; + } + + @Nullable + private static ChunkLightDataBuilder.Res setTraverse(@Nonnull ByteBuf local, @Nonnull BitSet currentSegments, int index, int pointer, int depth, short value) { + int headerLocation = pointer * 17; + byte i = local.getByte(headerLocation); + int innerIndex = index >> 12 - depth & 7; + int position = innerIndex * 2 + headerLocation + 1; + short currentValue = local.getShort(position); + + try { + if ((i >> innerIndex & 1) == 1) { + int currentValueMasked = currentValue & '\uffff'; + if (depth == 12) { + throw new RuntimeException( + "Discovered branch at deepest point in octree! Mask " + + i + + " innerIndex " + + innerIndex + + " depth " + + depth + + " setValue " + + value + + " currentValue " + + currentValue + + " at index " + + index + + " pointer " + + pointer + ); + } + + if (setTraverse(local, currentSegments, index, currentValueMasked, depth + 3, value) != null) { + currentSegments.clear(currentValueMasked); + local.setShort(position, value); + int mask = ~(1 << innerIndex); + i = (byte)(i & mask); + local.setByte(headerLocation, i); + if (i == 0) { + for (int j = 0; j < 8; j++) { + short s = local.getShort(j * 2 + headerLocation + 1); + if (s != value) { + return null; + } + } + + return ChunkLightDataBuilder.Res.INSTANCE; + } + } + } else if (value != currentValue) { + if (depth > 12) { + throw new IllegalStateException( + "Somehow have invalid octree state: " + + octreeToString(local) + + " when setTraverse(" + + index + + ", " + + pointer + + ", " + + depth + + ", " + + value + + ");" + ); + } + + if (depth == 12) { + byte[] bytes = null; + if (DEBUG) { + bytes = new byte[17]; + local.getBytes(headerLocation, bytes, 0, bytes.length); + } + + local.setShort(position, value); + + for (int jx = 0; jx < 8; jx++) { + short s = local.getShort(jx * 2 + headerLocation + 1); + if (s != value) { + return null; + } + } + + return DEBUG ? new ChunkLightDataBuilder.Res(ByteBufUtil.hexDump(bytes)) : ChunkLightDataBuilder.Res.INSTANCE; + } + + i = (byte)(i | 1 << innerIndex); + local.setByte(headerLocation, i); + int newSegmentIndex = growSegment(local, currentSegments, currentValue); + local.setShort(position, newSegmentIndex); + ChunkLightDataBuilder.Res out = setTraverse(local, currentSegments, index, newSegmentIndex, depth + 3, value); + if (out != null) { + throw new RuntimeException( + "Created new segment that instantly collapsed with (" + + index + + ", " + + pointer + + ", " + + depth + + ", " + + value + + "): with currentValue mismatch " + + currentValue + + " res " + + out + ); + } + + return null; + } + + return null; + } catch (Throwable var15) { + throw new RuntimeException( + "Failed to setTraverse(" + + index + + ", " + + pointer + + ", " + + depth + + ", " + + value + + ") with i " + + i + + ", innerIndex " + + innerIndex + + ", position " + + position + + ", currentValue " + + currentValue, + var15 + ); + } + } + + protected static int growSegment(@Nonnull ByteBuf local, @Nonnull BitSet currentSegments, short val) { + int newSegmentIndex = currentSegments.nextClearBit(0); + currentSegments.set(newSegmentIndex); + int currentCapacity = local.capacity(); + if (currentCapacity <= (newSegmentIndex + 1) * 17) { + int newCap = currentCapacity + 1088; + local.capacity(newCap); + } + + local.setByte(newSegmentIndex * 17, 0); + + for (int j = 0; j < 8; j++) { + local.setShort(newSegmentIndex * 17 + j * 2 + 1, val); + } + + return newSegmentIndex; + } + + @Nonnull + public String toStringOctree() { + return this.light == null ? "NULL" : octreeToString(this.light); + } + + @Nonnull + public static String octreeToString(@Nonnull ByteBuf buffer) { + StringBuffer out = new StringBuffer(); + + try { + octreeToString(buffer, 0, out, 0); + } catch (Throwable var3) { + throw new RuntimeException("Failed at " + out, var3); + } + + return out.toString(); + } + + public static void octreeToString(@Nonnull ByteBuf buffer, int pointer, @Nonnull StringBuffer out, int recursion) { + int i = buffer.getByte(pointer * 17); + + for (int j = 0; j < 8; j++) { + int loc = pointer * 17 + j * 2 + 1; + int s = buffer.getUnsignedShort(loc); + out.append("\t".repeat(Math.max(0, recursion))); + if ((i & 1 << j) != 0) { + out.append("SUBTREE AT ").append(j).append('\n'); + octreeToString(buffer, s, out, recursion + 1); + } else { + out.append("INDEX ").append(j).append(" VALUE: ").append(s); + } + + if (j != 7) { + out.append('\n'); + } + } + } + + private static class Res { + public static final ChunkLightDataBuilder.Res INSTANCE = new ChunkLightDataBuilder.Res(null); + private final String segment; + + private Res(String segment) { + this.segment = segment; + } + + @Nonnull + @Override + public String toString() { + return "Res{segment='" + this.segment + "'}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/section/ChunkSection.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/ChunkSection.java new file mode 100644 index 0000000..00ac339 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/ChunkSection.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.section; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.modules.LegacyModule; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; + +public class ChunkSection implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(ChunkSection.class, ChunkSection::new).build(); + private Ref chunkColumnReference; + private int x; + private int y; + private int z; + + public static ComponentType getComponentType() { + return LegacyModule.get().getChunkSectionComponentType(); + } + + private ChunkSection() { + } + + public ChunkSection(Ref chunkColumnReference, int x, int y, int z) { + this.chunkColumnReference = chunkColumnReference; + this.x = x; + this.y = y; + this.z = z; + } + + public void load(Ref chunkReference, int x, int y, int z) { + this.chunkColumnReference = chunkReference; + this.x = x; + this.y = y; + this.z = z; + } + + public Ref getChunkColumnReference() { + return this.chunkColumnReference; + } + + public int getX() { + return this.x; + } + + public int getY() { + return this.y; + } + + public int getZ() { + return this.z; + } + + @Nonnull + @Override + public Component clone() { + return this; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/section/ChunkSectionReference.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/ChunkSectionReference.java new file mode 100644 index 0000000..cdd6028 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/ChunkSectionReference.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.section; + +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; + +public class ChunkSectionReference { + private BlockChunk chunk; + private BlockSection section; + private int sectionIndex; + + public ChunkSectionReference(BlockChunk chunk, BlockSection section, int sectionIndex) { + this.section = section; + this.chunk = chunk; + this.sectionIndex = sectionIndex; + } + + public BlockChunk getChunk() { + return this.chunk; + } + + public BlockSection getSection() { + return this.section; + } + + public int getSectionIndex() { + return this.sectionIndex; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/section/FluidSection.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/FluidSection.java new file mode 100644 index 0000000..dd4b115 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/FluidSection.java @@ -0,0 +1,378 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.section; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.protocol.CachedPacket; +import com.hypixel.hytale.protocol.packets.world.SetFluids; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.modules.LegacyModule; +import com.hypixel.hytale.server.core.universe.world.chunk.section.palette.EmptySectionPalette; +import com.hypixel.hytale.server.core.universe.world.chunk.section.palette.ISectionPalette; +import com.hypixel.hytale.server.core.universe.world.chunk.section.palette.PaletteTypeEnum; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.util.io.ByteBufUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import java.lang.ref.SoftReference; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.locks.StampedLock; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FluidSection implements Component { + public static final int LEVEL_DATA_SIZE = 16384; + public static final int VERSION = 0; + public static final BuilderCodec CODEC = BuilderCodec.builder(FluidSection.class, FluidSection::new) + .versioned() + .codecVersion(0) + .append(new KeyedCodec<>("Data", Codec.BYTE_ARRAY), FluidSection::deserialize, FluidSection::serialize) + .add() + .build(); + private final StampedLock lock = new StampedLock(); + private int x; + private int y; + private int z; + private boolean loaded = false; + private ISectionPalette typePalette = EmptySectionPalette.INSTANCE; + @Nullable + private byte[] levelData = null; + private int nonZeroLevels = 0; + @Nonnull + private IntOpenHashSet changedPositions = new IntOpenHashSet(0); + @Nonnull + private IntOpenHashSet swapChangedPositions = new IntOpenHashSet(0); + @Nullable + private transient SoftReference>> cachedPacket = null; + + public FluidSection() { + } + + public static ComponentType getComponentType() { + return LegacyModule.get().getFluidSectionComponentType(); + } + + public void preload(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public void load(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + this.loaded = true; + } + + private boolean setFluidRaw(int x, int y, int z, int fluidId) { + return this.setFluidRaw(ChunkUtil.indexBlock(x, y, z), fluidId); + } + + private boolean setFluidRaw(int index, int fluidId) { + ISectionPalette.SetResult result = this.typePalette.set(index, fluidId); + if (result == ISectionPalette.SetResult.REQUIRES_PROMOTE) { + this.typePalette = this.typePalette.promote(); + result = this.typePalette.set(index, fluidId); + if (result != ISectionPalette.SetResult.ADDED_OR_REMOVED) { + throw new IllegalStateException("Promoted fluid section failed to correctly add the new fluid"); + } + } else if (this.typePalette.shouldDemote()) { + this.typePalette = this.typePalette.demote(); + } + + return result != ISectionPalette.SetResult.UNCHANGED; + } + + public boolean setFluid(int x, int y, int z, @Nonnull Fluid fluid, byte level) { + return this.setFluid(ChunkUtil.indexBlock(x, y, z), Fluid.getAssetMap().getIndex(fluid.getId()), level); + } + + public boolean setFluid(int x, int y, int z, int fluidId, byte level) { + return this.setFluid(ChunkUtil.indexBlock(x, y, z), fluidId, level); + } + + public boolean setFluid(int index, @Nonnull Fluid fluid, byte level) { + return this.setFluid(index, Fluid.getAssetMap().getIndex(fluid.getId()), level); + } + + public boolean setFluid(int index, int fluidId, byte level) { + level = (byte)(level & 15); + if (level == 0) { + fluidId = 0; + } + + if (fluidId == 0) { + level = 0; + } + + long stamp = this.lock.writeLock(); + + boolean var7; + try { + boolean changed = this.setFluidRaw(index, fluidId); + changed |= this.setFluidLevel(index, level); + if (changed && this.loaded) { + this.cachedPacket = null; + this.changedPositions.add(index); + } + + var7 = changed; + } finally { + this.lock.unlockWrite(stamp); + } + + return var7; + } + + private boolean setFluidRaw(int x, int y, int z, @Nonnull Fluid fluid) { + return this.setFluidRaw(ChunkUtil.indexBlock(x, y, z), fluid); + } + + private boolean setFluidRaw(int index, @Nonnull Fluid fluid) { + IndexedLookupTableAssetMap assetMap = Fluid.getAssetMap(); + return this.setFluidRaw(index, assetMap.getIndex(fluid.getId())); + } + + public int getFluidId(int x, int y, int z) { + return this.getFluidId(ChunkUtil.indexBlock(x, y, z)); + } + + public int getFluidId(int index) { + long stamp = this.lock.tryOptimisticRead(); + int currentId = this.typePalette.get(index); + if (!this.lock.validate(stamp)) { + stamp = this.lock.readLock(); + + try { + currentId = this.typePalette.get(index); + } finally { + this.lock.unlockRead(stamp); + } + } + + return currentId; + } + + @Nullable + public Fluid getFluid(int x, int y, int z) { + return this.getFluid(ChunkUtil.indexBlock(x, y, z)); + } + + @Nullable + public Fluid getFluid(int index) { + IndexedLookupTableAssetMap assetMap = Fluid.getAssetMap(); + return assetMap.getAsset(this.getFluidId(index)); + } + + private boolean setFluidLevel(int x, int y, int z, byte level) { + return this.setFluidLevel(ChunkUtil.indexBlock(x, y, z), level); + } + + private boolean setFluidLevel(int index, byte level) { + level = (byte)(level & 15); + if (this.levelData == null) { + if (level == 0) { + return false; + } + + this.levelData = new byte[16384]; + } + + int byteIndex = index >> 1; + byte byteValue = this.levelData[byteIndex]; + int value = byteValue >> (index & 1) * 4 & 15; + if (value == level) { + return false; + } else { + if (value == 0) { + this.nonZeroLevels++; + } else if (level == 0) { + this.nonZeroLevels--; + if (this.nonZeroLevels <= 0) { + this.levelData = null; + return true; + } + } + + if ((index & 1) == 0) { + this.levelData[byteIndex] = (byte)(byteValue & 240 | level); + } else { + this.levelData[byteIndex] = (byte)(byteValue & 15 | level << 4); + } + + return true; + } + } + + public byte getFluidLevel(int x, int y, int z) { + return this.getFluidLevel(ChunkUtil.indexBlock(x, y, z)); + } + + public byte getFluidLevel(int index) { + long stamp = this.lock.tryOptimisticRead(); + byte[] localData = this.levelData; + byte result = 0; + if (localData != null) { + int byteIndex = index >> 1; + byte byteValue = localData[byteIndex]; + result = (byte)(byteValue >> (index & 1) * 4 & 15); + } + + if (!this.lock.validate(stamp)) { + stamp = this.lock.readLock(); + + int byteIndex; + try { + if (this.levelData != null) { + byteIndex = index >> 1; + byte byteValue = this.levelData[byteIndex]; + return (byte)(byteValue >> (index & 1) * 4 & 15); + } + + byteIndex = 0; + } finally { + this.lock.unlockRead(stamp); + } + + return (byte)byteIndex; + } else { + return result; + } + } + + public int getX() { + return this.x; + } + + public int getY() { + return this.y; + } + + public int getZ() { + return this.z; + } + + @Nonnull + public IntOpenHashSet getAndClearChangedPositions() { + long stamp = this.lock.writeLock(); + + IntOpenHashSet var4; + try { + this.swapChangedPositions.clear(); + IntOpenHashSet tmp = this.changedPositions; + this.changedPositions = this.swapChangedPositions; + this.swapChangedPositions = tmp; + var4 = tmp; + } finally { + this.lock.unlockWrite(stamp); + } + + return var4; + } + + @Nonnull + @Override + public Component clone() { + return this; + } + + private void serializeForPacket(@Nonnull ByteBuf buf) { + long stamp = this.lock.readLock(); + + try { + buf.writeByte(this.typePalette.getPaletteType().ordinal()); + this.typePalette.serializeForPacket(buf); + if (this.levelData != null) { + buf.writeBoolean(true); + buf.writeBytes(this.levelData); + } else { + buf.writeBoolean(false); + } + } finally { + this.lock.unlockRead(stamp); + } + } + + private byte[] serialize(ExtraInfo extraInfo) { + ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(); + long stamp = this.lock.readLock(); + + byte[] e; + try { + buf.writeByte(this.typePalette.getPaletteType().ordinal()); + this.typePalette.serialize(Fluid.KEY_SERIALIZER, buf); + if (this.levelData != null) { + buf.writeBoolean(true); + buf.writeBytes(this.levelData); + } else { + buf.writeBoolean(false); + } + + e = ByteBufUtil.getBytesRelease(buf); + } catch (Throwable var9) { + buf.release(); + throw SneakyThrow.sneakyThrow(var9); + } finally { + this.lock.unlockRead(stamp); + } + + return e; + } + + private void deserialize(@Nonnull byte[] bytes, ExtraInfo extraInfo) { + ByteBuf buf = Unpooled.wrappedBuffer(bytes); + PaletteTypeEnum type = PaletteTypeEnum.get(buf.readByte()); + this.typePalette = type.getConstructor().get(); + this.typePalette.deserialize(Fluid.KEY_DESERIALIZER, buf, 0); + if (buf.readBoolean()) { + this.levelData = new byte[16384]; + buf.readBytes(this.levelData); + this.nonZeroLevels = 0; + + for (int i = 0; i < 16384; i++) { + byte v = this.levelData[i]; + if ((v & 15) != 0) { + this.nonZeroLevels++; + } + + if ((v & 240) != 0) { + this.nonZeroLevels++; + } + } + } else { + this.levelData = null; + } + } + + @Nonnull + public CompletableFuture> getCachedPacket() { + SoftReference>> ref = this.cachedPacket; + CompletableFuture> future = ref != null ? ref.get() : null; + if (future != null) { + return future; + } else { + future = CompletableFuture.supplyAsync(() -> { + ByteBuf buf = Unpooled.buffer(65536); + this.serializeForPacket(buf); + byte[] data = ByteBufUtil.getBytesRelease(buf); + SetFluids packet = new SetFluids(this.x, this.y, this.z, data); + return CachedPacket.cache(packet); + }); + this.cachedPacket = new SoftReference<>(future); + return future; + } + } + + public boolean isEmpty() { + return this.typePalette.isSolid(0) && this.nonZeroLevels == 0; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/section/blockpositions/BlockPositionData.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/blockpositions/BlockPositionData.java new file mode 100644 index 0000000..e5f16ad --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/blockpositions/BlockPositionData.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.section.blockpositions; + +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSectionReference; + +public class BlockPositionData implements IBlockPositionData { + private static final double HALF_BLOCK = 0.5; + private int blockIndex; + private ChunkSectionReference section; + private int blockType; + + public BlockPositionData(int blockIndex, ChunkSectionReference section, int blockType) { + this.blockIndex = blockIndex; + this.section = section; + this.blockType = blockType; + } + + @Override + public BlockSection getChunkSection() { + return this.section.getSection(); + } + + @Override + public int getBlockType() { + return this.blockType; + } + + @Override + public int getX() { + return ChunkUtil.xFromIndex(this.blockIndex) + (this.section.getChunk().getX() << 5); + } + + @Override + public int getY() { + return ChunkUtil.yFromIndex(this.blockIndex) + (this.section.getSectionIndex() << 5); + } + + @Override + public int getZ() { + return ChunkUtil.zFromIndex(this.blockIndex) + (this.section.getChunk().getZ() << 5); + } + + @Override + public double getXCentre() { + return this.getX() + 0.5; + } + + @Override + public double getYCentre() { + return this.getY() + 0.5; + } + + @Override + public double getZCentre() { + return this.getZ() + 0.5; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/section/blockpositions/BlockPositionProvider.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/blockpositions/BlockPositionProvider.java new file mode 100644 index 0000000..460578b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/blockpositions/BlockPositionProvider.java @@ -0,0 +1,95 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.section.blockpositions; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.function.consumer.IntObjectConsumer; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.LegacyModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.BitSet; +import java.util.List; +import java.util.function.BiPredicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockPositionProvider implements Component { + private final BitSet searchedBlockSets; + @Nullable + private final Int2ObjectMap> blockData; + private final short lightChangeCounter; + + public static ComponentType getComponentType() { + return LegacyModule.get().getBlockPositionProviderComponentType(); + } + + public BlockPositionProvider(@Nonnull BitSet blockSets, @Nullable Int2ObjectOpenHashMap> data, short lightChangeCounter) { + this.searchedBlockSets = (BitSet)blockSets.clone(); + this.lightChangeCounter = lightChangeCounter; + if (data != null) { + this.blockData = Int2ObjectMaps.unmodifiable(data); + } else { + this.blockData = null; + } + } + + public boolean isStale(int currentBlockSet, @Nonnull BlockSection section) { + return this.lightChangeCounter != section.getLocalChangeCounter() || !this.searchedBlockSets.get(currentBlockSet); + } + + public void findBlocks( + @Nonnull List resultList, + int blockSet, + double range, + double yRange, + @Nonnull Ref ref, + @Nullable BiPredicate filter, + T obj, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.blockData != null) { + List data = this.blockData.getOrDefault(blockSet, null); + if (data != null) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d pos = transformComponent.getPosition(); + double range2 = range * range; + + for (int i = 0; i < data.size(); i++) { + IBlockPositionData entry = data.get(i); + double entryY = entry.getYCentre(); + if (Math.abs(pos.y - entryY) <= yRange + && pos.distanceSquaredTo(entry.getXCentre(), entryY, entry.getZCentre()) <= range2 + && (filter == null || !filter.test(entry, obj))) { + resultList.add(entry); + } + } + } + } + } + + public BitSet getSearchedBlockSets() { + return (BitSet)this.searchedBlockSets.clone(); + } + + public void forEachBlockSet(@Nonnull IntObjectConsumer> listConsumer) { + if (this.blockData != null) { + this.blockData.forEach(listConsumer::accept); + } + } + + @Nonnull + @Override + public Component clone() { + return this; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/section/blockpositions/IBlockPositionData.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/blockpositions/IBlockPositionData.java new file mode 100644 index 0000000..17bd7d6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/blockpositions/IBlockPositionData.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.section.blockpositions; + +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; + +public interface IBlockPositionData { + BlockSection getChunkSection(); + + int getBlockType(); + + int getX(); + + int getY(); + + int getZ(); + + double getXCentre(); + + double getYCentre(); + + double getZCentre(); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/AbstractByteSectionPalette.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/AbstractByteSectionPalette.java new file mode 100644 index 0000000..ec25ef6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/AbstractByteSectionPalette.java @@ -0,0 +1,282 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.section.palette; + +import com.hypixel.hytale.math.util.NumberUtil; +import io.netty.buffer.ByteBuf; +import it.unimi.dsi.fastutil.bytes.Byte2ByteMap; +import it.unimi.dsi.fastutil.bytes.Byte2ByteOpenHashMap; +import it.unimi.dsi.fastutil.bytes.Byte2IntMap; +import it.unimi.dsi.fastutil.bytes.Byte2IntOpenHashMap; +import it.unimi.dsi.fastutil.bytes.Byte2ShortMap; +import it.unimi.dsi.fastutil.bytes.Byte2ShortOpenHashMap; +import it.unimi.dsi.fastutil.bytes.Byte2ShortMap.Entry; +import it.unimi.dsi.fastutil.ints.Int2ByteMap; +import it.unimi.dsi.fastutil.ints.Int2ByteOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ShortMap; +import it.unimi.dsi.fastutil.ints.Int2ShortOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.BitSet; +import java.util.function.IntConsumer; +import java.util.function.ToIntFunction; +import javax.annotation.Nonnull; + +public abstract class AbstractByteSectionPalette implements ISectionPalette { + protected final Int2ByteMap externalToInternal; + protected final Byte2IntMap internalToExternal; + protected final BitSet internalIdSet; + protected final Byte2ShortMap internalIdCount; + protected final byte[] blocks; + + protected AbstractByteSectionPalette(byte[] blocks) { + this(new Int2ByteOpenHashMap(), new Byte2IntOpenHashMap(), new BitSet(), new Byte2ShortOpenHashMap(), blocks); + this.externalToInternal.put(0, (byte)0); + this.internalToExternal.put((byte)0, 0); + this.internalIdSet.set(0); + this.internalIdCount.put((byte)0, (short)-32768); + } + + public AbstractByteSectionPalette(byte[] blocks, @Nonnull int[] data, int[] unique, int count) { + this(new Int2ByteOpenHashMap(count), new Byte2IntOpenHashMap(count), new BitSet(count), new Byte2ShortOpenHashMap(count), blocks); + + for (int internalId = 0; internalId < count; internalId++) { + int blockId = unique[internalId]; + this.internalToExternal.put((byte)internalId, blockId); + this.externalToInternal.put(blockId, (byte)internalId); + this.internalIdSet.set(this.unsignedInternalId((byte)internalId)); + this.internalIdCount.put((byte)internalId, (short)0); + } + + for (int index = 0; index < data.length; index++) { + int id = data[index]; + byte internalId = this.externalToInternal.get(id); + this.incrementBlockCount(internalId); + this.set0(index, internalId); + } + } + + protected AbstractByteSectionPalette( + Int2ByteMap externalToInternal, Byte2IntMap internalToExternal, BitSet internalIdSet, Byte2ShortMap internalIdCount, byte[] blocks + ) { + this.externalToInternal = externalToInternal; + this.internalToExternal = internalToExternal; + this.internalIdSet = internalIdSet; + this.internalIdCount = internalIdCount; + this.blocks = blocks; + } + + @Override + public int get(int index) { + byte internalId = this.get0(index); + return this.internalToExternal.get(internalId); + } + + @Nonnull + @Override + public ISectionPalette.SetResult set(int index, int id) { + byte oldInternalId = this.get0(index); + if (this.externalToInternal.containsKey(id)) { + byte newInternalId = this.externalToInternal.get(id); + if (newInternalId == oldInternalId) { + return ISectionPalette.SetResult.UNCHANGED; + } else { + boolean removed = this.decrementBlockCount(oldInternalId); + this.incrementBlockCount(newInternalId); + this.set0(index, newInternalId); + return removed ? ISectionPalette.SetResult.ADDED_OR_REMOVED : ISectionPalette.SetResult.CHANGED; + } + } else { + int nextInternalId = this.nextInternalId(oldInternalId); + if (!this.isValidInternalId(nextInternalId)) { + return ISectionPalette.SetResult.REQUIRES_PROMOTE; + } else { + this.decrementBlockCount(oldInternalId); + byte newInternalId = (byte)nextInternalId; + this.createBlockId(newInternalId, id); + this.set0(index, newInternalId); + return ISectionPalette.SetResult.ADDED_OR_REMOVED; + } + } + } + + protected abstract byte get0(int var1); + + protected abstract void set0(int var1, byte var2); + + @Override + public boolean contains(int id) { + return this.externalToInternal.containsKey(id); + } + + @Override + public boolean containsAny(@Nonnull IntList ids) { + for (int i = 0; i < ids.size(); i++) { + if (this.externalToInternal.containsKey(ids.getInt(i))) { + return true; + } + } + + return false; + } + + @Override + public int count() { + return this.internalIdCount.size(); + } + + @Override + public int count(int id) { + if (this.externalToInternal.containsKey(id)) { + byte internalId = this.externalToInternal.get(id); + return this.internalIdCount.get(internalId); + } else { + return 0; + } + } + + @Nonnull + @Override + public IntSet values() { + return new IntOpenHashSet(this.externalToInternal.keySet()); + } + + @Override + public void forEachValue(IntConsumer consumer) { + this.externalToInternal.keySet().forEach(consumer); + } + + @Nonnull + @Override + public Int2ShortMap valueCounts() { + Int2ShortMap map = new Int2ShortOpenHashMap(); + + for (Entry entry : this.internalIdCount.byte2ShortEntrySet()) { + byte internalId = entry.getByteKey(); + short count = entry.getShortValue(); + int externalId = this.internalToExternal.get(internalId); + map.put(externalId, count); + } + + return map; + } + + private void createBlockId(byte internalId, int blockId) { + this.internalToExternal.put(internalId, blockId); + this.externalToInternal.put(blockId, internalId); + this.internalIdSet.set(this.unsignedInternalId(internalId)); + this.internalIdCount.put(internalId, (short)1); + } + + private boolean decrementBlockCount(byte internalId) { + short oldCount = this.internalIdCount.get(internalId); + if (oldCount == 1) { + this.internalIdCount.remove(internalId); + int externalId = this.internalToExternal.remove(internalId); + this.externalToInternal.remove(externalId); + this.internalIdSet.clear(this.unsignedInternalId(internalId)); + return true; + } else { + this.internalIdCount.mergeShort(internalId, (short)1, NumberUtil::subtract); + return false; + } + } + + private void incrementBlockCount(byte internalId) { + this.internalIdCount.mergeShort(internalId, (short)1, NumberUtil::sum); + } + + private int nextInternalId(byte oldInternalId) { + return this.internalIdCount.get(oldInternalId) == 1 ? this.unsignedInternalId(oldInternalId) : this.internalIdSet.nextClearBit(0); + } + + protected abstract boolean isValidInternalId(int var1); + + protected abstract int unsignedInternalId(byte var1); + + @Override + public void serializeForPacket(@Nonnull ByteBuf buf) { + buf.writeShortLE(this.internalToExternal.size()); + + for (it.unimi.dsi.fastutil.bytes.Byte2IntMap.Entry entry : this.internalToExternal.byte2IntEntrySet()) { + byte internalId = entry.getByteKey(); + int externalId = entry.getIntValue(); + buf.writeByte(internalId); + buf.writeIntLE(externalId); + buf.writeShortLE(this.internalIdCount.get(internalId)); + } + + buf.writeBytes(this.blocks); + } + + @Override + public void serialize(@Nonnull ISectionPalette.KeySerializer keySerializer, @Nonnull ByteBuf buf) { + buf.writeShort(this.internalToExternal.size()); + + for (it.unimi.dsi.fastutil.bytes.Byte2IntMap.Entry entry : this.internalToExternal.byte2IntEntrySet()) { + byte internalId = entry.getByteKey(); + int externalId = entry.getIntValue(); + buf.writeByte(internalId); + keySerializer.serialize(buf, externalId); + buf.writeShort(this.internalIdCount.get(internalId)); + } + + buf.writeBytes(this.blocks); + } + + @Override + public void deserialize(@Nonnull ToIntFunction deserializer, @Nonnull ByteBuf buf, int version) { + this.externalToInternal.clear(); + this.internalToExternal.clear(); + this.internalIdSet.clear(); + this.internalIdCount.clear(); + Byte2ByteMap internalIdRemapping = null; + int blockCount = buf.readShort(); + + for (int i = 0; i < blockCount; i++) { + byte internalId = buf.readByte(); + int externalId = deserializer.applyAsInt(buf); + short count = buf.readShort(); + if (this.externalToInternal.containsKey(externalId)) { + byte existingInternalId = this.externalToInternal.get(externalId); + if (internalIdRemapping == null) { + internalIdRemapping = new Byte2ByteOpenHashMap(); + } + + internalIdRemapping.put(internalId, existingInternalId); + this.internalIdCount.mergeShort(existingInternalId, count, NumberUtil::sum); + } else { + this.externalToInternal.put(externalId, internalId); + this.internalToExternal.put(internalId, externalId); + this.internalIdSet.set(this.unsignedInternalId(internalId)); + this.internalIdCount.put(internalId, count); + } + } + + buf.readBytes(this.blocks); + if (internalIdRemapping != null) { + for (int ix = 0; ix < 32768; ix++) { + byte oldInternalId = this.get0(ix); + if (internalIdRemapping.containsKey(oldInternalId)) { + this.set0(ix, internalIdRemapping.get(oldInternalId)); + } + } + } + } + + @Override + public void find(@Nonnull IntList ids, @Nonnull IntSet internalIdHolder, @Nonnull IntConsumer indexConsumer) { + for (int i = 0; i < ids.size(); i++) { + byte internal = this.externalToInternal.getOrDefault(ids.getInt(i), (byte)-128); + if (internal != -128) { + internalIdHolder.add(internal); + } + } + + for (int ix = 0; ix < 32768; ix++) { + byte type = this.get0(ix); + if (internalIdHolder.contains(type)) { + indexConsumer.accept(ix); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/AbstractShortSectionPalette.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/AbstractShortSectionPalette.java new file mode 100644 index 0000000..429ac66 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/AbstractShortSectionPalette.java @@ -0,0 +1,283 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.section.palette; + +import com.hypixel.hytale.math.util.NumberUtil; +import io.netty.buffer.ByteBuf; +import it.unimi.dsi.fastutil.ints.Int2ShortMap; +import it.unimi.dsi.fastutil.ints.Int2ShortOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.shorts.Short2IntMap; +import it.unimi.dsi.fastutil.shorts.Short2IntOpenHashMap; +import it.unimi.dsi.fastutil.shorts.Short2ShortMap; +import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap; +import it.unimi.dsi.fastutil.shorts.Short2ShortMap.Entry; +import java.util.BitSet; +import java.util.function.IntConsumer; +import java.util.function.ToIntFunction; +import javax.annotation.Nonnull; + +public abstract class AbstractShortSectionPalette implements ISectionPalette { + protected final Int2ShortMap externalToInternal; + protected final Short2IntMap internalToExternal; + protected final BitSet internalIdSet; + protected final Short2ShortMap internalIdCount; + protected final short[] blocks; + + public AbstractShortSectionPalette(short[] blocks) { + this(new Int2ShortOpenHashMap(), new Short2IntOpenHashMap(), new BitSet(), new Short2ShortOpenHashMap(), blocks); + this.externalToInternal.put(0, (short)0); + this.internalToExternal.put((short)0, 0); + this.internalIdSet.set(0); + this.internalIdCount.put((short)0, (short)-32768); + } + + public AbstractShortSectionPalette(short[] blocks, @Nonnull int[] data, int[] unique, int count) { + this(new Int2ShortOpenHashMap(count), new Short2IntOpenHashMap(count), new BitSet(count), new Short2ShortOpenHashMap(count), blocks); + + for (int internalId = 0; internalId < count; internalId++) { + int blockId = unique[internalId]; + this.internalToExternal.put((short)internalId, blockId); + this.externalToInternal.put(blockId, (short)internalId); + this.internalIdSet.set(internalId); + this.internalIdCount.put((short)internalId, (short)0); + } + + for (int index = 0; index < data.length; index++) { + int id = data[index]; + short internalId = this.externalToInternal.get(id); + this.incrementBlockCount(internalId); + this.set0(index, internalId); + } + } + + protected AbstractShortSectionPalette( + Int2ShortMap externalToInternal, Short2IntMap internalToExternal, BitSet internalIdSet, Short2ShortMap internalIdCount, short[] blocks + ) { + this.externalToInternal = externalToInternal; + this.internalToExternal = internalToExternal; + this.internalIdSet = internalIdSet; + this.internalIdCount = internalIdCount; + this.blocks = blocks; + } + + @Override + public int get(int index) { + short internalId = this.get0(index); + return this.internalToExternal.get(internalId); + } + + @Nonnull + @Override + public ISectionPalette.SetResult set(int index, int id) { + short oldInternalId = this.get0(index); + if (this.externalToInternal.containsKey(id)) { + short newInternalId = this.externalToInternal.get(id); + if (newInternalId == oldInternalId) { + return ISectionPalette.SetResult.UNCHANGED; + } else { + boolean removed = this.decrementBlockCount(oldInternalId); + this.incrementBlockCount(newInternalId); + this.set0(index, newInternalId); + return removed ? ISectionPalette.SetResult.ADDED_OR_REMOVED : ISectionPalette.SetResult.CHANGED; + } + } else { + int nextInternalId = this.nextInternalId(oldInternalId); + if (!this.isValidInternalId(nextInternalId)) { + return ISectionPalette.SetResult.REQUIRES_PROMOTE; + } else { + this.decrementBlockCount(oldInternalId); + short newInternalId = (short)nextInternalId; + this.createBlockId(newInternalId, id); + this.set0(index, newInternalId); + return ISectionPalette.SetResult.ADDED_OR_REMOVED; + } + } + } + + protected abstract short get0(int var1); + + protected abstract void set0(int var1, short var2); + + @Override + public boolean contains(int id) { + return this.externalToInternal.containsKey(id); + } + + @Override + public boolean containsAny(@Nonnull IntList ids) { + for (int i = 0; i < ids.size(); i++) { + if (this.externalToInternal.containsKey(ids.getInt(i))) { + return true; + } + } + + return false; + } + + @Override + public int count() { + return this.internalIdCount.size(); + } + + @Override + public int count(int id) { + if (this.externalToInternal.containsKey(id)) { + short internalId = this.externalToInternal.get(id); + return this.internalIdCount.get(internalId); + } else { + return 0; + } + } + + @Nonnull + @Override + public IntSet values() { + return new IntOpenHashSet(this.externalToInternal.keySet()); + } + + @Override + public void forEachValue(IntConsumer consumer) { + this.externalToInternal.keySet().forEach(consumer); + } + + @Nonnull + @Override + public Int2ShortMap valueCounts() { + Int2ShortMap map = new Int2ShortOpenHashMap(); + + for (Entry entry : this.internalIdCount.short2ShortEntrySet()) { + short internalId = entry.getShortKey(); + short count = entry.getShortValue(); + int externalId = this.internalToExternal.get(internalId); + map.put(externalId, count); + } + + return map; + } + + private void createBlockId(short internalId, int blockId) { + this.internalToExternal.put(internalId, blockId); + this.externalToInternal.put(blockId, internalId); + this.internalIdSet.set(internalId); + this.internalIdCount.put(internalId, (short)1); + } + + private boolean decrementBlockCount(short internalId) { + short oldCount = this.internalIdCount.get(internalId); + if (oldCount == 1) { + this.internalIdCount.remove(internalId); + int externalId = this.internalToExternal.remove(internalId); + this.externalToInternal.remove(externalId); + this.internalIdSet.clear(internalId); + return true; + } else { + this.internalIdCount.mergeShort(internalId, (short)1, NumberUtil::subtract); + return false; + } + } + + private void incrementBlockCount(short internalId) { + this.internalIdCount.mergeShort(internalId, (short)1, NumberUtil::sum); + } + + private int nextInternalId(short oldInternalId) { + return this.internalIdCount.get(oldInternalId) == 1 ? oldInternalId : this.internalIdSet.nextClearBit(0); + } + + protected abstract boolean isValidInternalId(int var1); + + @Override + public void serializeForPacket(@Nonnull ByteBuf buf) { + buf.writeShortLE(this.internalToExternal.size()); + + for (it.unimi.dsi.fastutil.shorts.Short2IntMap.Entry entry : this.internalToExternal.short2IntEntrySet()) { + short internalId = entry.getShortKey(); + int externalId = entry.getIntValue(); + buf.writeShortLE(internalId & '\uffff'); + buf.writeIntLE(externalId); + buf.writeShortLE(this.internalIdCount.get(internalId)); + } + + for (int i = 0; i < this.blocks.length; i++) { + buf.writeShortLE(this.blocks[i]); + } + } + + @Override + public void serialize(@Nonnull ISectionPalette.KeySerializer keySerializer, @Nonnull ByteBuf buf) { + buf.writeShort(this.internalToExternal.size()); + + for (it.unimi.dsi.fastutil.shorts.Short2IntMap.Entry entry : this.internalToExternal.short2IntEntrySet()) { + short internalId = entry.getShortKey(); + int externalId = entry.getIntValue(); + buf.writeShort(internalId & '\uffff'); + keySerializer.serialize(buf, externalId); + buf.writeShort(this.internalIdCount.get(internalId)); + } + + for (int i = 0; i < this.blocks.length; i++) { + buf.writeShort(this.blocks[i]); + } + } + + @Override + public void deserialize(@Nonnull ToIntFunction deserializer, @Nonnull ByteBuf buf, int version) { + this.externalToInternal.clear(); + this.internalToExternal.clear(); + this.internalIdSet.clear(); + this.internalIdCount.clear(); + Short2ShortMap internalIdRemapping = null; + int blockCount = buf.readShort(); + + for (int i = 0; i < blockCount; i++) { + short internalId = buf.readShort(); + int externalId = deserializer.applyAsInt(buf); + short count = buf.readShort(); + if (this.externalToInternal.containsKey(externalId)) { + short existingInternalId = this.externalToInternal.get(externalId); + if (internalIdRemapping == null) { + internalIdRemapping = new Short2ShortOpenHashMap(); + } + + internalIdRemapping.put(internalId, existingInternalId); + this.internalIdCount.mergeShort(existingInternalId, count, NumberUtil::sum); + } else { + this.externalToInternal.put(externalId, internalId); + this.internalToExternal.put(internalId, externalId); + this.internalIdSet.set(internalId); + this.internalIdCount.put(internalId, count); + } + } + + for (int ix = 0; ix < this.blocks.length; ix++) { + this.blocks[ix] = buf.readShort(); + } + + if (internalIdRemapping != null) { + for (int ix = 0; ix < 32768; ix++) { + short oldInternalId = this.get0(ix); + if (internalIdRemapping.containsKey(oldInternalId)) { + this.set0(ix, internalIdRemapping.get(oldInternalId)); + } + } + } + } + + @Override + public void find(@Nonnull IntList ids, @Nonnull IntSet internalIdHolder, @Nonnull IntConsumer indexConsumer) { + for (int i = 0; i < ids.size(); i++) { + short internal = this.externalToInternal.getOrDefault(ids.getInt(i), (short)-32768); + if (internal != -32768) { + internalIdHolder.add(internal); + } + } + + for (int ix = 0; ix < 32768; ix++) { + short type = this.get0(ix); + if (internalIdHolder.contains(type)) { + indexConsumer.accept(ix); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/ByteSectionPalette.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/ByteSectionPalette.java new file mode 100644 index 0000000..8678555 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/ByteSectionPalette.java @@ -0,0 +1,126 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.section.palette; + +import com.hypixel.hytale.common.util.BitSetUtil; +import com.hypixel.hytale.protocol.packets.world.PaletteType; +import it.unimi.dsi.fastutil.bytes.Byte2IntMap; +import it.unimi.dsi.fastutil.bytes.Byte2ShortMap; +import it.unimi.dsi.fastutil.ints.Int2ByteMap; +import it.unimi.dsi.fastutil.shorts.Short2ByteMap; +import it.unimi.dsi.fastutil.shorts.Short2ByteOpenHashMap; +import it.unimi.dsi.fastutil.shorts.Short2IntMap.Entry; +import java.util.BitSet; +import javax.annotation.Nonnull; + +public class ByteSectionPalette extends AbstractByteSectionPalette { + private static final int KEY_MASK = 255; + public static final int MAX_SIZE = 256; + public static final int DEMOTE_SIZE = 14; + + public ByteSectionPalette() { + super(new byte[32768]); + } + + public ByteSectionPalette(Int2ByteMap externalToInternal, Byte2IntMap internalToExternal, BitSet internalIdSet, Byte2ShortMap internalIdCount, byte[] blocks) { + super(externalToInternal, internalToExternal, internalIdSet, internalIdCount, blocks); + } + + public ByteSectionPalette(@Nonnull int[] data, int[] unique, int count) { + super(new byte[32768], data, unique, count); + } + + @Nonnull + @Override + public PaletteType getPaletteType() { + return PaletteType.Byte; + } + + @Override + protected byte get0(int idx) { + return this.blocks[idx]; + } + + @Override + protected void set0(int idx, byte b) { + this.blocks[idx] = b; + } + + @Override + public boolean shouldDemote() { + return this.count() <= 14; + } + + @Nonnull + public HalfByteSectionPalette demote() { + return HalfByteSectionPalette.fromBytePalette(this); + } + + @Nonnull + public ShortSectionPalette promote() { + return ShortSectionPalette.fromBytePalette(this); + } + + @Override + protected boolean isValidInternalId(int internalId) { + return (internalId & 0xFF) == internalId; + } + + @Override + protected int unsignedInternalId(byte internalId) { + return internalId & 0xFF; + } + + private static int sUnsignedInternalId(byte internalId) { + return internalId & 0xFF; + } + + @Nonnull + public static ByteSectionPalette fromHalfBytePalette(@Nonnull HalfByteSectionPalette section) { + ByteSectionPalette byteSection = new ByteSectionPalette(); + byteSection.externalToInternal.clear(); + byteSection.externalToInternal.putAll(section.externalToInternal); + byteSection.internalToExternal.clear(); + byteSection.internalToExternal.putAll(section.internalToExternal); + BitSetUtil.copyValues(section.internalIdSet, byteSection.internalIdSet); + byteSection.internalIdCount.clear(); + byteSection.internalIdCount.putAll(section.internalIdCount); + + for (int i = 0; i < byteSection.blocks.length; i++) { + byteSection.blocks[i] = section.get0(i); + } + + return byteSection; + } + + @Nonnull + public static ByteSectionPalette fromShortPalette(@Nonnull ShortSectionPalette section) { + if (section.count() > 256) { + throw new IllegalStateException("Cannot demote short palette to byte palette. Too many blocks! Count: " + section.count()); + } else { + ByteSectionPalette byteSection = new ByteSectionPalette(); + Short2ByteMap internalIdRemapping = new Short2ByteOpenHashMap(); + byteSection.internalToExternal.clear(); + byteSection.externalToInternal.clear(); + byteSection.internalIdSet.clear(); + byteSection.internalIdCount.clear(); + + for (Entry entry : section.internalToExternal.short2IntEntrySet()) { + short oldInternalId = entry.getShortKey(); + int externalId = entry.getIntValue(); + byte newInternalId = (byte)byteSection.internalIdSet.nextClearBit(0); + byteSection.internalIdSet.set(sUnsignedInternalId(newInternalId)); + internalIdRemapping.put(oldInternalId, newInternalId); + byteSection.internalToExternal.put(newInternalId, externalId); + byteSection.externalToInternal.put(externalId, newInternalId); + byteSection.internalIdCount.put(newInternalId, section.internalIdCount.get(oldInternalId)); + } + + for (int i = 0; i < 32768; i++) { + short internalId = section.blocks[i]; + byte byteInternalId = internalIdRemapping.get(internalId); + byteSection.blocks[i] = byteInternalId; + } + + return byteSection; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/EmptySectionPalette.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/EmptySectionPalette.java new file mode 100644 index 0000000..6880c96 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/EmptySectionPalette.java @@ -0,0 +1,120 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.section.palette; + +import com.hypixel.hytale.protocol.packets.world.PaletteType; +import io.netty.buffer.ByteBuf; +import it.unimi.dsi.fastutil.ints.Int2ShortMap; +import it.unimi.dsi.fastutil.ints.Int2ShortOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.function.IntConsumer; +import java.util.function.ToIntFunction; +import javax.annotation.Nonnull; + +public class EmptySectionPalette implements ISectionPalette { + public static final int EMPTY_ID = 0; + public static final EmptySectionPalette INSTANCE = new EmptySectionPalette(); + + private EmptySectionPalette() { + } + + @Nonnull + @Override + public PaletteType getPaletteType() { + return PaletteType.Empty; + } + + @Nonnull + @Override + public ISectionPalette.SetResult set(int index, int id) { + return id == 0 ? ISectionPalette.SetResult.UNCHANGED : ISectionPalette.SetResult.REQUIRES_PROMOTE; + } + + @Override + public int get(int index) { + return 0; + } + + @Override + public boolean shouldDemote() { + return false; + } + + @Override + public ISectionPalette demote() { + throw new UnsupportedOperationException("Cannot demote empty chunk section!"); + } + + @Nonnull + @Override + public ISectionPalette promote() { + return new HalfByteSectionPalette(); + } + + @Override + public boolean contains(int id) { + return id == 0; + } + + @Override + public boolean containsAny(@Nonnull IntList ids) { + return ids.contains(0); + } + + @Override + public boolean isSolid(int id) { + return id == 0; + } + + @Override + public int count() { + return 1; + } + + @Override + public int count(int id) { + return id == 0 ? 32768 : 0; + } + + @Nonnull + @Override + public IntSet values() { + IntSet set = new IntOpenHashSet(); + set.add(0); + return set; + } + + @Override + public void forEachValue(@Nonnull IntConsumer consumer) { + consumer.accept(0); + } + + @Nonnull + @Override + public Int2ShortMap valueCounts() { + Int2ShortMap map = new Int2ShortOpenHashMap(); + map.put(0, (short)-32768); + return map; + } + + @Override + public void find(@Nonnull IntList ids, IntSet internalIdHolder, @Nonnull IntConsumer indexConsumer) { + if (ids.contains(0)) { + for (int i = 0; i < 32768; i++) { + indexConsumer.accept(i); + } + } + } + + @Override + public void serializeForPacket(ByteBuf buf) { + } + + @Override + public void serialize(ISectionPalette.KeySerializer keySerializer, ByteBuf buf) { + } + + @Override + public void deserialize(ToIntFunction deserializer, ByteBuf buf, int version) { + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/HalfByteSectionPalette.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/HalfByteSectionPalette.java new file mode 100644 index 0000000..c40b72a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/HalfByteSectionPalette.java @@ -0,0 +1,117 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.section.palette; + +import com.hypixel.hytale.common.util.BitUtil; +import com.hypixel.hytale.protocol.packets.world.PaletteType; +import it.unimi.dsi.fastutil.bytes.Byte2ByteMap; +import it.unimi.dsi.fastutil.bytes.Byte2ByteOpenHashMap; +import it.unimi.dsi.fastutil.bytes.Byte2IntMap; +import it.unimi.dsi.fastutil.bytes.Byte2ShortMap; +import it.unimi.dsi.fastutil.bytes.Byte2IntMap.Entry; +import it.unimi.dsi.fastutil.ints.Int2ByteMap; +import java.util.BitSet; +import javax.annotation.Nonnull; + +public class HalfByteSectionPalette extends AbstractByteSectionPalette { + private static final int KEY_MASK = 15; + public static final int MAX_SIZE = 16; + + public HalfByteSectionPalette() { + super(new byte[16384]); + } + + public HalfByteSectionPalette( + Int2ByteMap externalToInternal, Byte2IntMap internalToExternal, BitSet internalIdSet, Byte2ShortMap internalIdCount, byte[] blocks + ) { + super(externalToInternal, internalToExternal, internalIdSet, internalIdCount, blocks); + } + + public HalfByteSectionPalette(@Nonnull int[] data, int[] unique, int count) { + super(new byte[16384], data, unique, count); + } + + @Nonnull + @Override + public PaletteType getPaletteType() { + return PaletteType.HalfByte; + } + + @Override + protected void set0(int idx, byte b) { + BitUtil.setNibble(this.blocks, idx, b); + } + + @Override + protected byte get0(int idx) { + return BitUtil.getNibble(this.blocks, idx); + } + + @Override + public boolean shouldDemote() { + return this.isSolid(0); + } + + @Nonnull + @Override + public ISectionPalette demote() { + return EmptySectionPalette.INSTANCE; + } + + @Nonnull + public ByteSectionPalette promote() { + return ByteSectionPalette.fromHalfBytePalette(this); + } + + @Override + protected boolean isValidInternalId(int internalId) { + return (internalId & 15) == internalId; + } + + @Override + protected int unsignedInternalId(byte internalId) { + return internalId & 15; + } + + private static int sUnsignedInternalId(byte internalId) { + return internalId & 15; + } + + @Nonnull + public static HalfByteSectionPalette fromBytePalette(@Nonnull ByteSectionPalette section) { + if (section.count() > 16) { + throw new IllegalStateException("Cannot demote byte palette to half byte palette. Too many blocks! Count: " + section.count()); + } else { + HalfByteSectionPalette halfByteSection = new HalfByteSectionPalette(); + Byte2ByteMap internalIdRemapping = new Byte2ByteOpenHashMap(); + halfByteSection.internalToExternal.clear(); + halfByteSection.externalToInternal.clear(); + halfByteSection.internalIdSet.clear(); + + for (Entry entry : section.internalToExternal.byte2IntEntrySet()) { + byte oldInternalId = entry.getByteKey(); + int externalId = entry.getIntValue(); + byte newInternalId = (byte)halfByteSection.internalIdSet.nextClearBit(0); + halfByteSection.internalIdSet.set(sUnsignedInternalId(newInternalId)); + internalIdRemapping.put(oldInternalId, newInternalId); + halfByteSection.internalToExternal.put(newInternalId, externalId); + halfByteSection.externalToInternal.put(externalId, newInternalId); + } + + halfByteSection.internalIdCount.clear(); + + for (it.unimi.dsi.fastutil.bytes.Byte2ShortMap.Entry entry : section.internalIdCount.byte2ShortEntrySet()) { + byte internalId = entry.getByteKey(); + short count = entry.getShortValue(); + byte newInternalId = internalIdRemapping.get(internalId); + halfByteSection.internalIdCount.put(newInternalId, count); + } + + for (int i = 0; i < section.blocks.length; i++) { + byte internalId = section.blocks[i]; + byte byteInternalId = internalIdRemapping.get(internalId); + halfByteSection.set0(i, byteInternalId); + } + + return halfByteSection; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/ISectionPalette.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/ISectionPalette.java new file mode 100644 index 0000000..fbac56e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/ISectionPalette.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.section.palette; + +import com.hypixel.hytale.protocol.packets.world.PaletteType; +import io.netty.buffer.ByteBuf; +import it.unimi.dsi.fastutil.ints.Int2ShortMap; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.function.IntConsumer; +import java.util.function.ToIntFunction; +import javax.annotation.Nonnull; + +public interface ISectionPalette { + PaletteType getPaletteType(); + + ISectionPalette.SetResult set(int var1, int var2); + + int get(int var1); + + boolean contains(int var1); + + boolean containsAny(IntList var1); + + default boolean isSolid(int id) { + return this.count() == 1 && this.contains(id); + } + + int count(); + + int count(int var1); + + IntSet values(); + + void forEachValue(IntConsumer var1); + + Int2ShortMap valueCounts(); + + void find(IntList var1, IntSet var2, IntConsumer var3); + + boolean shouldDemote(); + + ISectionPalette demote(); + + ISectionPalette promote(); + + void serializeForPacket(ByteBuf var1); + + void serialize(ISectionPalette.KeySerializer var1, ByteBuf var2); + + void deserialize(ToIntFunction var1, ByteBuf var2, int var3); + + @Nonnull + static ISectionPalette from(@Nonnull int[] data, int[] unique, int count) { + if (count == 1 && unique[0] == 0) { + return EmptySectionPalette.INSTANCE; + } else if (count < 16) { + return new HalfByteSectionPalette(data, unique, count); + } else if (count < 256) { + return new ByteSectionPalette(data, unique, count); + } else if (count < 65536) { + return new ShortSectionPalette(data, unique, count); + } else { + throw new UnsupportedOperationException("Too many block types for palette."); + } + } + + @FunctionalInterface + public interface KeySerializer { + void serialize(ByteBuf var1, int var2); + } + + public static enum SetResult { + ADDED_OR_REMOVED, + CHANGED, + UNCHANGED, + REQUIRES_PROMOTE; + + private SetResult() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/PaletteTypeEnum.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/PaletteTypeEnum.java new file mode 100644 index 0000000..4d087fe --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/PaletteTypeEnum.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.section.palette; + +import com.hypixel.hytale.protocol.packets.world.PaletteType; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public enum PaletteTypeEnum { + EMPTY(PaletteType.Empty, () -> EmptySectionPalette.INSTANCE), + HALF_BYTE(PaletteType.HalfByte, HalfByteSectionPalette::new), + BYTE(PaletteType.Byte, ByteSectionPalette::new), + SHORT(PaletteType.Short, ShortSectionPalette::new); + + private static final PaletteTypeEnum[] values = values(); + @Nonnull + private final PaletteType paletteType; + private final Supplier constructor; + private final byte paletteId; + + public static PaletteTypeEnum get(byte paletteId) { + return values[paletteId]; + } + + private PaletteTypeEnum(@Nonnull PaletteType paletteType, Supplier constructor) { + this.paletteType = paletteType; + this.constructor = constructor; + this.paletteId = (byte)paletteType.ordinal(); + } + + @Nonnull + public PaletteType getPaletteType() { + return this.paletteType; + } + + public Supplier getConstructor() { + return this.constructor; + } + + public byte getPaletteId() { + return this.paletteId; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/ShortSectionPalette.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/ShortSectionPalette.java new file mode 100644 index 0000000..bc93855 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/section/palette/ShortSectionPalette.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.section.palette; + +import com.hypixel.hytale.protocol.packets.world.PaletteType; +import it.unimi.dsi.fastutil.bytes.Byte2IntMap.Entry; +import it.unimi.dsi.fastutil.ints.Int2ShortMap; +import it.unimi.dsi.fastutil.ints.Int2ShortOpenHashMap; +import it.unimi.dsi.fastutil.shorts.Short2IntMap; +import it.unimi.dsi.fastutil.shorts.Short2IntOpenHashMap; +import it.unimi.dsi.fastutil.shorts.Short2ShortMap; +import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap; +import java.util.BitSet; +import javax.annotation.Nonnull; + +public class ShortSectionPalette extends AbstractShortSectionPalette { + private static final int KEY_MASK = 65535; + public static final int MAX_SIZE = 65536; + public static final int DEMOTE_SIZE = 251; + + public ShortSectionPalette() { + super(new short[32768]); + } + + public ShortSectionPalette( + Int2ShortMap externalToInternal, Short2IntMap internalToExternal, BitSet internalIdSet, Short2ShortMap internalIdCount, short[] blocks + ) { + super(externalToInternal, internalToExternal, internalIdSet, internalIdCount, blocks); + } + + public ShortSectionPalette(@Nonnull int[] data, int[] unique, int count) { + super(new short[32768], data, unique, count); + } + + @Nonnull + @Override + public PaletteType getPaletteType() { + return PaletteType.Short; + } + + @Override + protected short get0(int idx) { + return this.blocks[idx]; + } + + @Override + protected void set0(int idx, short s) { + this.blocks[idx] = s; + } + + @Override + public boolean shouldDemote() { + return this.count() <= 251; + } + + @Nonnull + public ByteSectionPalette demote() { + return ByteSectionPalette.fromShortPalette(this); + } + + @Override + public ISectionPalette promote() { + throw new UnsupportedOperationException("Short palette cannot be promoted."); + } + + @Override + protected boolean isValidInternalId(int internalId) { + return (internalId & 65535) == internalId; + } + + @Nonnull + public static ShortSectionPalette fromBytePalette(@Nonnull ByteSectionPalette section) { + Int2ShortMap shortExternalToInternal = new Int2ShortOpenHashMap(); + Short2IntMap shortInternalToExternal = new Short2IntOpenHashMap(); + BitSet shortInternalIdSet = new BitSet(section.internalToExternal.size()); + Short2ShortMap shortInternalIdCount = new Short2ShortOpenHashMap(); + + for (Entry entry : section.internalToExternal.byte2IntEntrySet()) { + short internal = (short)(entry.getByteKey() & 255); + int external = entry.getIntValue(); + shortInternalToExternal.put(internal, external); + shortExternalToInternal.put(external, internal); + shortInternalIdSet.set(internal); + shortInternalIdCount.put(internal, section.internalIdCount.get(entry.getByteKey())); + } + + short[] shortBlocks = new short[32768]; + + for (int i = 0; i < 32768; i++) { + shortBlocks[i] = (short)(section.blocks[i] & 255); + } + + return new ShortSectionPalette(shortExternalToInternal, shortInternalToExternal, shortInternalIdSet, shortInternalIdCount, shortBlocks); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/state/TickableBlockState.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/state/TickableBlockState.java new file mode 100644 index 0000000..cd007ba --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/state/TickableBlockState.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.state; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nullable; + +public interface TickableBlockState { + void tick(float var1, int var2, ArchetypeChunk var3, Store var4, CommandBuffer var5); + + Vector3i getPosition(); + + Vector3i getBlockPosition(); + + @Nullable + WorldChunk getChunk(); + + void invalidate(); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/chunk/systems/ChunkSystems.java b/src/com/hypixel/hytale/server/core/universe/world/chunk/systems/ChunkSystems.java new file mode 100644 index 0000000..ed63063 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/chunk/systems/ChunkSystems.java @@ -0,0 +1,414 @@ +package com.hypixel.hytale.server.core.universe.world.chunk.systems; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.NonTicking; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.RootDependency; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.component.system.tick.RunWhenPausedSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.protocol.CachedPacket; +import com.hypixel.hytale.protocol.packets.world.ServerSetBlock; +import com.hypixel.hytale.protocol.packets.world.ServerSetBlocks; +import com.hypixel.hytale.protocol.packets.world.SetBlockCmd; +import com.hypixel.hytale.protocol.packets.world.SetChunk; +import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; +import com.hypixel.hytale.server.core.modules.migrations.ChunkColumnMigrationSystem; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.IntIterator; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChunkSystems { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final int MAX_CHANGES_PER_PACKET = 1024; + + public ChunkSystems() { + } + + public static class EnsureBlockSection extends HolderSystem { + private static final Query QUERY = Query.and(ChunkSection.getComponentType(), Query.not(BlockSection.getComponentType())); + + public EnsureBlockSection() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(BlockSection.getComponentType()); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.firstSet(); + } + } + + public static class LoadBlockSection extends HolderSystem { + public LoadBlockSection() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + BlockSection section = holder.getComponent(BlockSection.getComponentType()); + + assert section != null; + + section.loaded = true; + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Override + public Query getQuery() { + return BlockSection.getComponentType(); + } + } + + public static class OnChunkLoad extends RefSystem { + private static final Query QUERY = Query.and(ChunkColumn.getComponentType(), WorldChunk.getComponentType()); + private static final Set> DEPENDENCIES = Set.of(new SystemDependency<>(Order.AFTER, ChunkSystems.OnNewChunk.class)); + + public OnChunkLoad() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + ChunkColumn chunk = commandBuffer.getComponent(ref, ChunkColumn.getComponentType()); + + assert chunk != null; + + WorldChunk worldChunk = commandBuffer.getComponent(ref, WorldChunk.getComponentType()); + + assert worldChunk != null; + + Ref[] sections = chunk.getSections(); + Holder[] sectionHolders = chunk.takeSectionHolders(); + boolean isNonTicking = commandBuffer.getArchetype(ref).contains(ChunkStore.REGISTRY.getNonTickingComponentType()); + if (sectionHolders != null && sectionHolders.length > 0 && sectionHolders[0] != null) { + for (int i = 0; i < sectionHolders.length; i++) { + if (isNonTicking) { + sectionHolders[i].ensureComponent(ChunkStore.REGISTRY.getNonTickingComponentType()); + } else { + sectionHolders[i].tryRemoveComponent(ChunkStore.REGISTRY.getNonTickingComponentType()); + } + + ChunkSection section = sectionHolders[i].getComponent(ChunkSection.getComponentType()); + if (section == null) { + sectionHolders[i].addComponent(ChunkSection.getComponentType(), new ChunkSection(ref, worldChunk.getX(), i, worldChunk.getZ())); + } else { + section.load(ref, worldChunk.getX(), i, worldChunk.getZ()); + } + } + + commandBuffer.addEntities(sectionHolders, 0, sections, 0, sections.length, AddReason.LOAD); + } + + for (int i = 0; i < sections.length; i++) { + if (sections[i] == null) { + Holder newSection = ChunkStore.REGISTRY.newHolder(); + if (isNonTicking) { + newSection.ensureComponent(ChunkStore.REGISTRY.getNonTickingComponentType()); + } else { + newSection.tryRemoveComponent(ChunkStore.REGISTRY.getNonTickingComponentType()); + } + + newSection.addComponent(ChunkSection.getComponentType(), new ChunkSection(ref, worldChunk.getX(), i, worldChunk.getZ())); + sections[i] = commandBuffer.addEntity(newSection, AddReason.SPAWN); + } + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + ChunkColumn chunk = commandBuffer.getComponent(ref, ChunkColumn.getComponentType()); + + assert chunk != null; + + Ref[] sections = chunk.getSections(); + Holder[] holders = new Holder[sections.length]; + + for (int i = 0; i < sections.length; i++) { + Ref section = sections[i]; + holders[i] = ChunkStore.REGISTRY.newHolder(); + commandBuffer.removeEntity(section, holders[i], reason); + } + + chunk.putSectionHolders(holders); + Arrays.fill(sections, null); + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + } + + public static class OnNewChunk extends ChunkColumnMigrationSystem { + private static final Query QUERY = Query.and(WorldChunk.getComponentType(), Query.not(ChunkColumn.getComponentType())); + + public OnNewChunk() { + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + Holder[] sectionHolders = new Holder[10]; + + for (int i = 0; i < sectionHolders.length; i++) { + sectionHolders[i] = ChunkStore.REGISTRY.newHolder(); + } + + holder.addComponent(ChunkColumn.getComponentType(), new ChunkColumn(sectionHolders)); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.firstSet(); + } + } + + public static class OnNonTicking extends RefChangeSystem> { + private final Archetype archetype = Archetype.of(WorldChunk.getComponentType(), ChunkColumn.getComponentType()); + + public OnNonTicking() { + } + + @Nonnull + @Override + public ComponentType> componentType() { + return ChunkStore.REGISTRY.getNonTickingComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, + @Nonnull NonTicking component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + ChunkColumn column = commandBuffer.getComponent(ref, ChunkColumn.getComponentType()); + + assert column != null; + + Ref[] sections = column.getSections(); + + for (int i = 0; i < sections.length; i++) { + Ref section = sections[i]; + commandBuffer.ensureComponent(section, ChunkStore.REGISTRY.getNonTickingComponentType()); + } + } + + public void onComponentSet( + @Nonnull Ref ref, + @Nullable NonTicking oldComponent, + @Nonnull NonTicking newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + } + + public void onComponentRemoved( + @Nonnull Ref ref, + @Nonnull NonTicking component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + ChunkColumn column = commandBuffer.getComponent(ref, ChunkColumn.getComponentType()); + + assert column != null; + + Ref[] sections = column.getSections(); + + for (int i = 0; i < sections.length; i++) { + Ref section = sections[i]; + commandBuffer.tryRemoveComponent(section, ChunkStore.REGISTRY.getNonTickingComponentType()); + } + } + + @Override + public Query getQuery() { + return this.archetype; + } + } + + public static class ReplicateChanges extends EntityTickingSystem implements RunWhenPausedSystem { + private static final Query QUERY = Query.and(ChunkSection.getComponentType(), BlockSection.getComponentType()); + + public ReplicateChanges() { + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + BlockSection blockSection = archetypeChunk.getComponent(index, BlockSection.getComponentType()); + + assert blockSection != null; + + IntOpenHashSet changes = blockSection.getAndClearChangedPositions(); + if (!changes.isEmpty()) { + ChunkSection section = archetypeChunk.getComponent(index, ChunkSection.getComponentType()); + + assert section != null; + + Collection players = store.getExternalData().getWorld().getPlayerRefs(); + if (players.isEmpty()) { + changes.clear(); + } else { + long chunkIndex = ChunkUtil.indexChunk(section.getX(), section.getZ()); + if (changes.size() >= 1024) { + ObjectArrayList playersCopy = new ObjectArrayList<>(players); + CompletableFuture> set = blockSection.getCachedChunkPacket(section.getX(), section.getY(), section.getZ()); + set.thenAccept(s -> { + for (PlayerRef playerx : playersCopy) { + Ref refx = playerx.getReference(); + if (refx != null) { + ChunkTracker trackerx = playerx.getChunkTracker(); + if (trackerx != null && trackerx.isLoaded(chunkIndex)) { + playerx.getPacketHandler().writeNoCache(s); + } + } + } + }).exceptionally(throwable -> { + if (throwable != null) { + ChunkSystems.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception when compressing chunk fluids:"); + } + + return null; + }); + changes.clear(); + } else { + if (changes.size() == 1) { + int change = changes.iterator().nextInt(); + int x = ChunkUtil.minBlock(section.getX()) + ChunkUtil.xFromIndex(change); + int y = ChunkUtil.minBlock(section.getY()) + ChunkUtil.yFromIndex(change); + int z = ChunkUtil.minBlock(section.getZ()) + ChunkUtil.zFromIndex(change); + int blockId = blockSection.get(change); + int filler = blockSection.getFiller(change); + int rotation = blockSection.getRotationIndex(change); + ServerSetBlock packet = new ServerSetBlock(x, y, z, blockId, (short)filler, (byte)rotation); + + for (PlayerRef player : players) { + Ref ref = player.getReference(); + if (ref != null) { + ChunkTracker tracker = player.getChunkTracker(); + if (tracker != null && tracker.isLoaded(chunkIndex)) { + player.getPacketHandler().writeNoCache(packet); + } + } + } + } else { + SetBlockCmd[] cmds = new SetBlockCmd[changes.size()]; + IntIterator iter = changes.intIterator(); + int i = 0; + + while (iter.hasNext()) { + int change = iter.nextInt(); + int blockId = blockSection.get(change); + int filler = blockSection.getFiller(change); + int rotation = blockSection.getRotationIndex(change); + cmds[i++] = new SetBlockCmd((short)change, blockId, (short)filler, (byte)rotation); + } + + ServerSetBlocks packet = new ServerSetBlocks(section.getX(), section.getY(), section.getZ(), cmds); + + for (PlayerRef playerx : players) { + Ref ref = playerx.getReference(); + if (ref != null) { + ChunkTracker tracker = playerx.getChunkTracker(); + if (tracker != null && tracker.isLoaded(chunkIndex)) { + playerx.getPacketHandler().writeNoCache(packet); + } + } + } + } + + changes.clear(); + } + } + } + } + + @Nonnull + @Override + public Query getQuery() { + return QUERY; + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.lastSet(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/SetTickingCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/SetTickingCommand.java new file mode 100644 index 0000000..84d995e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/SetTickingCommand.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.server.core.universe.world.commands; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class SetTickingCommand extends AbstractWorldCommand { + @Nonnull + private final RequiredArg tickingArg = this.withRequiredArg("ticking", "server.commands.setticking.ticking.desc", ArgTypes.BOOLEAN); + + public SetTickingCommand() { + super("setticking", "server.commands.setticking.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + boolean isTicking = this.tickingArg.get(context); + world.setTicking(isTicking); + context.sendMessage(Message.translation("server.universe.settick.info").param("status", isTicking).param("worldName", world.getName())); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/WorldSettingsCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/WorldSettingsCommand.java new file mode 100644 index 0000000..d56c4aa --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/WorldSettingsCommand.java @@ -0,0 +1,426 @@ +package com.hypixel.hytale.server.core.universe.world.commands; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.shape.Box2D; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgumentType; +import com.hypixel.hytale.server.core.command.system.arguments.types.EnumArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.provider.IChunkStorageProvider; +import com.hypixel.hytale.server.core.universe.world.worldgen.provider.IWorldGenProvider; +import com.hypixel.hytale.server.core.universe.world.worldmap.provider.IWorldMapProvider; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Function; +import javax.annotation.Nonnull; + +public class WorldSettingsCommand extends AbstractCommandCollection { + @Nonnull + private final WorldConfig defaultWorldConfig = new WorldConfig(); + + public WorldSettingsCommand() { + super("settings", "server.commands.world.settings.desc"); + this.addAliases("ws"); + this.generateSubCommand( + "worldgentype", + "server.commands.world.settings.worldgentype.desc", + "type", + ArgTypes.STRING, + "WorldGen Type", + worldConfig -> IWorldGenProvider.CODEC.getIdFor((Class)worldConfig.getWorldGenProvider().getClass()), + (worldConfig, path) -> worldConfig.setWorldGenProvider(IWorldGenProvider.CODEC.getCodecFor(path).getDefaultValue()) + ); + this.generateSubCommand( + "worldmaptype", + "server.commands.world.settings.worldmaptype.desc", + "type", + ArgTypes.STRING, + "WorldMap Type", + worldConfig -> IWorldMapProvider.CODEC.getIdFor((Class)worldConfig.getWorldMapProvider().getClass()), + (worldConfig, path) -> worldConfig.setWorldMapProvider(IWorldMapProvider.CODEC.getCodecFor(path).getDefaultValue()) + ); + this.generateSubCommand( + "chunkstoragetype", + "server.commands.world.settings.chunkstoragetype.desc", + "type", + ArgTypes.STRING, + "ChunkStorage Type", + worldConfig -> IChunkStorageProvider.CODEC.getIdFor((Class)worldConfig.getChunkStorageProvider().getClass()), + (worldConfig, path) -> worldConfig.setChunkStorageProvider(IChunkStorageProvider.CODEC.getCodecFor(path).getDefaultValue()) + ); + this.generateSubCommand( + "ticking", "server.commands.world.settings.ticking.desc", "ticking", ArgTypes.BOOLEAN, "Ticking", WorldConfig::isTicking, WorldConfig::setTicking + ); + this.generateSubCommand( + "blockticking", + "server.commands.world.settings.blockticking.desc", + "blockticking", + ArgTypes.BOOLEAN, + "Block Ticking", + WorldConfig::isBlockTicking, + WorldConfig::setBlockTicking + ); + this.generateSubCommand( + "pvp", "server.commands.world.settings.pvp.desc", "pvp", ArgTypes.BOOLEAN, "PvP", WorldConfig::isPvpEnabled, WorldConfig::setPvpEnabled + ); + this.generateSubCommand( + "timepaused", + "server.commands.world.settings.timepaused.desc", + "timepaused", + ArgTypes.BOOLEAN, + "Time Paused", + WorldConfig::isGameTimePaused, + WorldConfig::setGameTimePaused + ); + this.generateSubCommand( + "spawningnpc", + "server.commands.world.settings.spawningnpc.desc", + "spawning", + ArgTypes.BOOLEAN, + "Spawning NPC's", + WorldConfig::isSpawningNPC, + WorldConfig::setSpawningNPC + ); + this.generateSubCommand( + "spawnmarkers", + "server.commands.world.settings.spawnmarkers.desc", + "enabled", + ArgTypes.BOOLEAN, + "Spawn markers enabled", + WorldConfig::isSpawnMarkersEnabled, + WorldConfig::setIsSpawnMarkersEnabled + ); + this.generateSubCommand( + "freezeallnpcs", + "server.commands.world.settings.freezeallnpcs.desc", + "enabled", + ArgTypes.BOOLEAN, + "NPCs will be frozen", + WorldConfig::isAllNPCFrozen, + WorldConfig::setIsAllNPCFrozen + ); + this.generateSubCommand( + "compassupdating", + "server.commands.world.settings.compassupdating.desc", + "updating", + ArgTypes.BOOLEAN, + "Compass Updating", + World::isCompassUpdating, + WorldConfig::isCompassUpdating, + World::setCompassUpdating + ); + this.generateSubCommand( + "playersaving", + "server.commands.world.settings.playersaving.desc", + "enabled", + ArgTypes.BOOLEAN, + "Player Saving Enabled", + WorldConfig::isSavingPlayers, + WorldConfig::setSavingPlayers + ); + this.generateSubCommand( + "chunksaving", + "server.commands.world.settings.chunksaving.desc", + "enabled", + ArgTypes.BOOLEAN, + "Chunk Saving Enabled", + WorldConfig::canSaveChunks, + WorldConfig::setCanSaveChunks + ); + this.generateSubCommand( + "chunkunloading", + "server.commands.world.settings.chunkunload.desc", + "enabled", + ArgTypes.BOOLEAN, + "Chunk Unloading Enabled", + WorldConfig::canUnloadChunks, + WorldConfig::setCanUnloadChunks + ); + this.generateSubCommand( + "gamemode", + "server.commands.world.settings.gamemode.desc", + "gamemode", + new EnumArgumentType<>("server.commands.parsing.argtype.gamemode.name", GameMode.class), + "Default GameMode", + WorldConfig::getGameMode, + WorldConfig::setGameMode + ); + this.generateSubCommand( + "gameplayconfig", + "server.commands.world.settings.gameplayconfig.desc", + "id", + ArgTypes.STRING, + "GameplayConfigId", + WorldConfig::getGameplayConfig, + WorldConfig::setGameplayConfig + ); + this.addSubCommand( + new WorldSettingsCommand.WorldSettingsBox2DCommand( + "pregenerate", + "server.commands.world.settings.pregenerate.desc", + "Pre-generate region", + w -> w.getWorldConfig().getChunkConfig().getPregenerateRegion(), + wc -> wc.getChunkConfig().getPregenerateRegion(), + (w, v) -> { + WorldConfig worldConfig = w.getWorldConfig(); + worldConfig.getChunkConfig().setPregenerateRegion(v); + worldConfig.markChanged(); + } + ) + ); + this.addSubCommand( + new WorldSettingsCommand.WorldSettingsBox2DCommand( + "keeploaded", + "server.commands.world.settings.keeploaded.desc", + "Keep loaded region", + w -> w.getWorldConfig().getChunkConfig().getKeepLoadedRegion(), + wc -> wc.getChunkConfig().getKeepLoadedRegion(), + (w, v) -> { + WorldConfig worldConfig = w.getWorldConfig(); + worldConfig.getChunkConfig().setKeepLoadedRegion(v); + worldConfig.markChanged(); + } + ) + ); + } + + private void generateSubCommand( + @Nonnull String command, + @Nonnull String description, + @Nonnull String arg, + @Nonnull ArgumentType argumentType, + @Nonnull String display, + @Nonnull Function getter, + @Nonnull BiConsumer setter + ) { + this.generateSubCommand( + command, + description, + arg, + argumentType, + display, + world -> getter.apply(world.getWorldConfig()), + getter, + (world, v) -> setter.accept(world.getWorldConfig(), v) + ); + } + + private void generateSubCommand( + @Nonnull String command, + @Nonnull String description, + @Nonnull String arg, + ArgumentType argumentType, + @Nonnull String display, + @Nonnull Function getter, + Function defaultGetter, + @Nonnull BiConsumer setter + ) { + this.addSubCommand( + new WorldSettingsCommand.WorldSettingsSubCommand<>( + command, description, arg, argumentType, display, getter, defaultGetter, setter, this.defaultWorldConfig + ) + ); + } + + private static class WorldSettingsBox2DCommand extends AbstractWorldCommand { + @Nonnull + private final String display; + @Nonnull + private final Function getter; + @Nonnull + private final Function defaultGetter; + @Nonnull + private final BiConsumer setter; + @Nonnull + private final WorldConfig defaultWorldConfig; + + public WorldSettingsBox2DCommand( + String name, + String description, + @Nonnull String display, + @Nonnull Function getter, + @Nonnull Function defaultGetter, + @Nonnull BiConsumer setter + ) { + super(name, description); + this.display = display; + this.getter = getter; + this.defaultGetter = defaultGetter; + this.setter = setter; + this.defaultWorldConfig = new WorldConfig(); + this.addSubCommand(new WorldSettingsCommand.WorldSettingsBox2DCommand.SetSubCommand()); + this.addSubCommand(new WorldSettingsCommand.WorldSettingsBox2DCommand.ResetSubCommand()); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Box2D currentValue = this.getter.apply(world); + context.sendMessage( + Message.translation("server.commands.world.settings.currentValue") + .param("display", this.display) + .param("worldName", world.getName()) + .param("currentValue", Objects.toString(currentValue)) + ); + } + + private class ResetSubCommand extends AbstractWorldCommand { + public ResetSubCommand() { + super("reset", "server.commands.world.settings.reset.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Box2D currentValue = WorldSettingsBox2DCommand.this.getter.apply(world); + Box2D newValue = WorldSettingsBox2DCommand.this.defaultGetter.apply(WorldSettingsBox2DCommand.this.defaultWorldConfig); + WorldSettingsBox2DCommand.this.setter.accept(world, newValue); + world.getWorldConfig().markChanged(); + context.sendMessage( + Message.translation("server.commands.world.settings.displaySet") + .param("display", WorldSettingsBox2DCommand.this.display) + .param("worldName", world.getName()) + .param("isDefault", " default value ") + .param("newValue", Objects.toString(newValue)) + .param("oldValue", Objects.toString(currentValue)) + ); + } + } + + private class SetSubCommand extends AbstractWorldCommand { + @Nonnull + private final RequiredArg minXArg = this.withRequiredArg("minX", "server.commands.world.settings.box2d.minX.desc", ArgTypes.DOUBLE); + @Nonnull + private final RequiredArg minZArg = this.withRequiredArg("minZ", "server.commands.world.settings.box2d.minZ.desc", ArgTypes.DOUBLE); + @Nonnull + private final RequiredArg maxXArg = this.withRequiredArg("maxX", "server.commands.world.settings.box2d.maxX.desc", ArgTypes.DOUBLE); + @Nonnull + private final RequiredArg maxZArg = this.withRequiredArg("maxZ", "server.commands.world.settings.box2d.maxZ.desc", ArgTypes.DOUBLE); + + public SetSubCommand() { + super("set", "server.commands.world.settings.set.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Box2D currentValue = WorldSettingsBox2DCommand.this.getter.apply(world); + Box2D newValue = new Box2D( + new Vector2d(context.get(this.minXArg), context.get(this.minZArg)), new Vector2d(context.get(this.maxXArg), context.get(this.maxZArg)) + ); + WorldSettingsBox2DCommand.this.setter.accept(world, newValue); + world.getWorldConfig().markChanged(); + context.sendMessage( + Message.translation("server.commands.world.settings.displaySet") + .param("display", WorldSettingsBox2DCommand.this.display) + .param("worldName", world.getName()) + .param("isDefault", "") + .param("newValue", Objects.toString(newValue)) + .param("oldValue", Objects.toString(currentValue)) + ); + } + } + } + + private static class WorldSettingsSubCommand extends AbstractWorldCommand { + @Nonnull + private final ArgumentType argumentType; + @Nonnull + private final String display; + @Nonnull + private final Function getter; + private final Function defaultGetter; + @Nonnull + private final BiConsumer setter; + @Nonnull + private final WorldConfig defaultWorldConfig; + + public WorldSettingsSubCommand( + @Nonnull String name, + @Nonnull String description, + @Nonnull String arg, + @Nonnull ArgumentType argumentType, + @Nonnull String display, + @Nonnull Function getter, + @Nonnull Function defaultGetter, + @Nonnull BiConsumer setter, + @Nonnull WorldConfig defaultWorldConfig + ) { + super(name, description); + this.argumentType = argumentType; + this.display = display; + this.getter = getter; + this.defaultGetter = defaultGetter; + this.setter = setter; + this.defaultWorldConfig = defaultWorldConfig; + this.addSubCommand(new WorldSettingsCommand.WorldSettingsSubCommand.SetSubCommand()); + this.addSubCommand(new WorldSettingsCommand.WorldSettingsSubCommand.ResetSubCommand()); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + T currentValue = this.getter.apply(world); + context.sendMessage( + Message.translation("server.commands.world.settings.currentValue") + .param("display", this.display) + .param("worldName", world.getName()) + .param("currentValue", currentValue.toString()) + ); + } + + private class ResetSubCommand extends AbstractWorldCommand { + public ResetSubCommand() { + super("reset", "server.commands.world.settings.reset.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + T currentValue = WorldSettingsSubCommand.this.getter.apply(world); + T newValue = WorldSettingsSubCommand.this.defaultGetter.apply(WorldSettingsSubCommand.this.defaultWorldConfig); + WorldSettingsSubCommand.this.setter.accept(world, newValue); + world.getWorldConfig().markChanged(); + context.sendMessage( + Message.translation("server.commands.world.settings.displaySet") + .param("display", WorldSettingsSubCommand.this.display) + .param("worldName", world.getName()) + .param("isDefault", " default value ") + .param("newValue", newValue.toString()) + .param("oldValue", currentValue.toString()) + ); + } + } + + private class SetSubCommand extends AbstractWorldCommand { + @Nonnull + private final RequiredArg valueArg = this.withRequiredArg( + "value", "server.commands.world.settings.value.desc", WorldSettingsSubCommand.this.argumentType + ); + + public SetSubCommand() { + super("set", "server.commands.world.settings.set.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + T currentValue = WorldSettingsSubCommand.this.getter.apply(world); + T newValue = context.get(this.valueArg); + WorldSettingsSubCommand.this.setter.accept(world, newValue); + world.getWorldConfig().markChanged(); + context.sendMessage( + Message.translation("server.commands.world.settings.displaySet") + .param("display", WorldSettingsSubCommand.this.display) + .param("worldName", world.getName()) + .param("isDefault", "") + .param("newValue", newValue.toString()) + .param("oldValue", currentValue.toString()) + ); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockCommand.java new file mode 100644 index 0000000..0910657 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockCommand.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.core.universe.world.commands.block; + +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.universe.world.commands.block.bulk.BlockBulkCommand; + +public class BlockCommand extends AbstractCommandCollection { + public BlockCommand() { + super("block", "server.commands.block.desc"); + this.setPermissionGroup(GameMode.Creative); + this.addAliases("blocks"); + this.addSubCommand(new BlockSetCommand()); + this.addSubCommand(new BlockSetTickingCommand()); + this.addSubCommand(new BlockGetCommand()); + this.addSubCommand(new BlockGetStateCommand()); + this.addSubCommand(new BlockRowCommand()); + this.addSubCommand(new BlockBulkCommand()); + this.addSubCommand(new BlockInspectPhysicsCommand()); + this.addSubCommand(new BlockInspectFillerCommand()); + this.addSubCommand(new BlockInspectRotationCommand()); + this.addSubCommand(new BlockSetStateCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockGetCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockGetCommand.java new file mode 100644 index 0000000..e971ec2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockGetCommand.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.universe.world.commands.block; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import javax.annotation.Nonnull; + +public class BlockGetCommand extends SimpleBlockCommand { + public BlockGetCommand() { + super("get", "server.commands.block.get.desc"); + } + + @Override + protected void executeWithBlock(@Nonnull CommandContext context, @Nonnull WorldChunk chunk, int x, int y, int z) { + CommandSender sender = context.sender(); + int blockId = chunk.getBlock(x, y, z); + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + int support = chunk.getSupportValue(x, y, z); + sender.sendMessage( + Message.translation("server.commands.block.get.info") + .param("x", x) + .param("y", y) + .param("z", z) + .param("id", blockType.getId().toString()) + .param("blockId", blockId) + .param("support", support) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockGetStateCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockGetStateCommand.java new file mode 100644 index 0000000..6b11925 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockGetStateCommand.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.server.core.universe.world.commands.block; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; + +public class BlockGetStateCommand extends SimpleBlockCommand { + public BlockGetStateCommand() { + super("getstate", "server.commands.block.getstate.desc"); + } + + @Override + protected void executeWithBlock(@Nonnull CommandContext context, @Nonnull WorldChunk chunk, int x, int y, int z) { + CommandSender sender = context.sender(); + Ref ref = chunk.getBlockComponentEntity(x, y, z); + if (ref != null) { + StringBuilder stateString = new StringBuilder(); + Archetype archetype = ref.getStore().getArchetype(ref); + + for (int i = archetype.getMinIndex(); i < archetype.length(); i++) { + ComponentType> c = (ComponentType>)archetype.get(i); + if (c != null) { + stateString.append(c.getTypeClass().getSimpleName()).append(" = ").append(ref.getStore().getComponent(ref, c)).append('\n'); + } + } + + sender.sendMessage( + Message.translation("server.commands.block.getstate.info").param("x", x).param("y", y).param("z", z).param("state", stateString.toString()) + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockInspectFillerCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockInspectFillerCommand.java new file mode 100644 index 0000000..05ce2be --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockInspectFillerCommand.java @@ -0,0 +1,126 @@ +package com.hypixel.hytale.server.core.universe.world.commands.block; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import javax.annotation.Nonnull; + +public class BlockInspectFillerCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_BLOCK_INSPECT_FILLER_DONE = Message.translation("server.commands.block.inspectfiller.done"); + @Nonnull + private static final Message MESSAGE_COMMANDS_BLOCK_INSPECT_FILLER_NO_BLOCKS = Message.translation("server.commands.block.inspectfiller.noblocks"); + + public BlockInspectFillerCommand() { + super("inspectfiller", "server.commands.block.inspectfiller.desc"); + this.setPermissionGroup(null); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + int x = MathUtil.floor(position.getX()); + int z = MathUtil.floor(position.getZ()); + int y = MathUtil.floor(position.getY()); + int chunkX = ChunkUtil.chunkCoordinate(x); + int chunkY = ChunkUtil.chunkCoordinate(y); + int chunkZ = ChunkUtil.chunkCoordinate(z); + CompletableFutureUtil._catch(world.getChunkStore().getChunkSectionReferenceAsync(chunkX, chunkY, chunkZ).thenAcceptAsync(chunk -> { + Store chunkStore = chunk.getStore(); + BlockSection blockSection = chunkStore.getComponent((Ref)chunk, BlockSection.getComponentType()); + if (blockSection == null) { + playerRef.sendMessage(MESSAGE_COMMANDS_BLOCK_INSPECT_FILLER_NO_BLOCKS); + } else { + BlockTypeAssetMap blockTypeMap = BlockType.getAssetMap(); + IndexedLookupTableAssetMap hitboxMap = BlockBoundingBoxes.getAssetMap(); + Vector3d offset = new Vector3d(ChunkUtil.minBlock(chunkX), ChunkUtil.minBlock(chunkY), ChunkUtil.minBlock(chunkZ)); + + for (int idx = 0; idx < 32768; idx++) { + int blockId = blockSection.get(idx); + BlockType blockType = blockTypeMap.getAsset(blockId); + if (blockType != null) { + BlockBoundingBoxes hitbox = hitboxMap.getAsset(blockType.getHitboxTypeIndex()); + if (hitbox != null && hitbox.protrudesUnitBox()) { + int filler = blockSection.getFiller(idx); + int bx = ChunkUtil.xFromIndex(idx); + int by = ChunkUtil.yFromIndex(idx); + int bz = ChunkUtil.zFromIndex(idx); + Vector3d pos = new Vector3d(bx, by, bz); + pos.add(0.5, 0.5, 0.5); + pos.add(offset); + int rotation = blockSection.getRotationIndex(idx); + BlockBoundingBoxes.RotatedVariantBoxes rotatedHitbox = hitbox.get(rotation); + int fillerX = FillerBlockUtil.unpackX(filler); + int fillerY = FillerBlockUtil.unpackY(filler); + int fillerZ = FillerBlockUtil.unpackZ(filler); + Box boundingBox = rotatedHitbox.getBoundingBox(); + int minX = (int)boundingBox.min.x; + int minY = (int)boundingBox.min.y; + int minZ = (int)boundingBox.min.z; + if (minX - boundingBox.min.x > 0.0) { + minX--; + } + + if (minY - boundingBox.min.y > 0.0) { + minY--; + } + + if (minZ - boundingBox.min.z > 0.0) { + minZ--; + } + + int maxX = (int)boundingBox.max.x; + int maxY = (int)boundingBox.max.y; + int maxZ = (int)boundingBox.max.z; + if (boundingBox.max.x - maxX > 0.0) { + maxX++; + } + + if (boundingBox.max.y - maxY > 0.0) { + maxY++; + } + + if (boundingBox.max.z - maxZ > 0.0) { + maxZ++; + } + + Vector3f colour = new Vector3f(); + colour.x = (float)(fillerX - minX) / (maxX - minX); + colour.y = (float)(fillerY - minY) / (maxY - minY); + colour.z = (float)(fillerZ - minZ) / (maxZ - minZ); + DebugUtils.addCube(chunkStore.getExternalData().getWorld(), pos, colour, 1.05, 30.0F); + } + } + } + + playerRef.sendMessage(MESSAGE_COMMANDS_BLOCK_INSPECT_FILLER_DONE); + } + }, world)); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockInspectPhysicsCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockInspectPhysicsCommand.java new file mode 100644 index 0000000..5fecdff --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockInspectPhysicsCommand.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.server.core.universe.world.commands.block; + +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class BlockInspectPhysicsCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_BLOCK_INSPECT_PHYS_NO_BLOCKS = Message.translation("server.commands.block.inspectphys.noblocks"); + @Nonnull + private static final Message MESSAGE_COMMANDS_BLOCK_INSPECT_PHYS_DONE = Message.translation("server.commands.block.inspectphys.done"); + private final FlagArg ALL = this.withFlagArg("all", ""); + + public BlockInspectPhysicsCommand() { + super("inspectphys", "server.commands.block.inspectphys.desc"); + this.setPermissionGroup(null); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + boolean all = this.ALL.get(context); + int x = MathUtil.floor(position.getX()); + int z = MathUtil.floor(position.getZ()); + int y = MathUtil.floor(position.getY()); + int chunkX = ChunkUtil.chunkCoordinate(x); + int chunkY = ChunkUtil.chunkCoordinate(y); + int chunkZ = ChunkUtil.chunkCoordinate(z); + CompletableFutureUtil._catch(world.getChunkStore().getChunkSectionReferenceAsync(chunkX, chunkY, chunkZ).thenAcceptAsync(chunk -> { + Store chunkStore = chunk.getStore(); + BlockPhysics blockPhysics = chunkStore.getComponent((Ref)chunk, BlockPhysics.getComponentType()); + if (blockPhysics == null) { + playerRef.sendMessage(MESSAGE_COMMANDS_BLOCK_INSPECT_PHYS_NO_BLOCKS); + } else { + Vector3d offset = new Vector3d(ChunkUtil.minBlock(chunkX), ChunkUtil.minBlock(chunkY), ChunkUtil.minBlock(chunkZ)); + + for (int idx = 0; idx < 32768; idx++) { + int supportValue = blockPhysics.get(idx); + if (supportValue != 0 || all) { + int bx = ChunkUtil.xFromIndex(idx); + int by = ChunkUtil.yFromIndex(idx); + int bz = ChunkUtil.zFromIndex(idx); + Vector3d pos = new Vector3d(bx, by, bz); + pos.add(0.5, 0.5, 0.5); + pos.add(offset); + Vector3f colour; + if (supportValue == 15) { + colour = new Vector3f(0.0F, 1.0F, 0.0F); + } else { + BlockType block = world.getBlockType(pos.toVector3i()); + if (!block.hasSupport()) { + if (supportValue == 0) { + continue; + } + + colour = new Vector3f(1.0F, 1.0F, 0.0F); + } else if (block.getMaxSupportDistance() != 0) { + float len = (float)supportValue / block.getMaxSupportDistance(); + colour = new Vector3f(len, 0.0F, 1.0F - len); + } else { + colour = new Vector3f(0.0F, 1.0F, 1.0F); + } + } + + DebugUtils.addCube(chunkStore.getExternalData().getWorld(), pos, colour, 1.05, 30.0F); + } + } + + playerRef.sendMessage(MESSAGE_COMMANDS_BLOCK_INSPECT_PHYS_DONE); + } + }, world)); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockInspectRotationCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockInspectRotationCommand.java new file mode 100644 index 0000000..8c02a39 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockInspectRotationCommand.java @@ -0,0 +1,86 @@ +package com.hypixel.hytale.server.core.universe.world.commands.block; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class BlockInspectRotationCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_BLOCK_INSPECT_ROTATION_NO_BLOCKS = Message.translation("server.commands.block.inspectrotation.noblocks"); + @Nonnull + private static final Message MESSAGE_COMMANDS_BLOCK_INSPECT_ROTATION_DONE = Message.translation("server.commands.block.inspectrotation.done"); + + public BlockInspectRotationCommand() { + super("inspectrotation", "server.commands.block.inspectrotation.desc"); + this.setPermissionGroup(null); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + int x = MathUtil.floor(position.getX()); + int z = MathUtil.floor(position.getZ()); + int y = MathUtil.floor(position.getY()); + int chunkX = ChunkUtil.chunkCoordinate(x); + int chunkY = ChunkUtil.chunkCoordinate(y); + int chunkZ = ChunkUtil.chunkCoordinate(z); + CompletableFutureUtil._catch(world.getChunkStore().getChunkSectionReferenceAsync(chunkX, chunkY, chunkZ).thenAcceptAsync(chunk -> { + Store chunkStore = chunk.getStore(); + BlockSection blockSection = chunkStore.getComponent((Ref)chunk, BlockSection.getComponentType()); + if (blockSection == null) { + playerRef.sendMessage(MESSAGE_COMMANDS_BLOCK_INSPECT_ROTATION_NO_BLOCKS); + } else { + BlockTypeAssetMap blockTypeMap = BlockType.getAssetMap(); + Vector3d offset = new Vector3d(ChunkUtil.minBlock(chunkX), ChunkUtil.minBlock(chunkY), ChunkUtil.minBlock(chunkZ)); + + for (int idx = 0; idx < 32768; idx++) { + int blockId = blockSection.get(idx); + BlockType blockType = blockTypeMap.getAsset(blockId); + if (blockType != null) { + int bx = ChunkUtil.xFromIndex(idx); + int by = ChunkUtil.yFromIndex(idx); + int bz = ChunkUtil.zFromIndex(idx); + Vector3d pos = new Vector3d(bx, by, bz); + pos.add(0.5, 0.5, 0.5); + pos.add(offset); + RotationTuple rotation = blockSection.getRotation(idx); + if (rotation.index() != 0) { + Vector3f colour = new Vector3f(); + colour.x = rotation.yaw().ordinal() / (Rotation.VALUES.length - 1.0F); + colour.y = rotation.pitch().ordinal() / (Rotation.VALUES.length - 1.0F); + colour.z = rotation.roll().ordinal() / (Rotation.VALUES.length - 1.0F); + DebugUtils.addCube(chunkStore.getExternalData().getWorld(), pos, colour, 1.05, 30.0F); + } + } + } + + playerRef.sendMessage(MESSAGE_COMMANDS_BLOCK_INSPECT_ROTATION_DONE); + } + }, world)); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockRowCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockRowCommand.java new file mode 100644 index 0000000..555eadb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockRowCommand.java @@ -0,0 +1,131 @@ +package com.hypixel.hytale.server.core.universe.world.commands.block; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.WildcardMatch; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Comparator; +import java.util.List; +import javax.annotation.Nonnull; + +public class BlockRowCommand extends AbstractPlayerCommand { + private final RequiredArg queryArg = this.withRequiredArg("wildcard block query", "server.commands.block.row.arg.desc", ArgTypes.STRING); + private static final int MAX_MATCHES = 64; + + public BlockRowCommand() { + super("row", "server.commands.block.row.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3d playerPos = transformComponent.getPosition(); + Vector3i axisDirection = getDominantCardinal(headRotationComponent.getDirection()); + String query = context.get(this.queryArg); + List blockTypes = this.findBlockTypes(query); + if (blockTypes.isEmpty()) { + playerRef.sendMessage(Message.translation("server.commands.block.row.nonefound").param("query", query)); + List fuzzyMatches = StringUtil.sortByFuzzyDistance(query, BlockType.getAssetMap().getAssetMap().keySet(), CommandUtil.RECOMMEND_COUNT); + if (!fuzzyMatches.isEmpty()) { + playerRef.sendMessage(Message.translation("server.commands.block.row.fuzzymatches").param("choices", fuzzyMatches.toString())); + } + } else if (blockTypes.size() >= 64000) { + playerRef.sendMessage(Message.translation("server.commands.block.row.toomanymatches").param("max", 64)); + } else { + this.spawnBlocksRow(world, playerPos, axisDirection, blockTypes); + } + } + + private void spawnBlocksRow(@Nonnull World world, @Nonnull Vector3d origin, @Nonnull Vector3i direction, @Nonnull List blockTypes) { + IndexedLookupTableAssetMap boundingBoxes = BlockBoundingBoxes.getAssetMap(); + Axis axis = getAxis(direction); + int step = 25; + + for (int x = 0; x < blockTypes.size(); x += step) { + double distance = 1.0; + + for (int i = 0; i < step; i++) { + BlockType blockType = blockTypes.get(i + x); + Box boundingBox = boundingBoxes.getAsset(blockType.getHitboxTypeIndex()).get(0).getBoundingBox(); + double dimension = Math.ceil(boundingBox.dimension(axis)); + distance += Math.floor(dimension) + 1.0; + Vector3i blockPos = origin.clone() + .add(direction.clone().scale(distance)) + .add(Rotation.Ninety.rotateY(direction, new Vector3i()).scale(x / step * 2)) + .toVector3i(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(blockPos.x, blockPos.z); + world.getChunkAsync(chunkIndex).thenAccept(chunk -> { + int settings = 196; + chunk.setBlock(blockPos.x, blockPos.y, blockPos.z, blockType, 196); + }); + } + } + } + + @Nonnull + private static Vector3i getDominantCardinal(@Nonnull Vector3d direction) { + double ax = Math.abs(direction.x); + double ay = Math.abs(direction.y); + double az = Math.abs(direction.z); + if (ax > ay && ax > az) { + return new Vector3i((int)Math.signum(direction.x), 0, 0); + } else { + return ay > az ? new Vector3i(0, (int)Math.signum(direction.y), 0) : new Vector3i(0, 0, (int)Math.signum(direction.z)); + } + } + + @Nonnull + private static Axis getAxis(@Nonnull Vector3i direction) { + if (direction.x != 0) { + return Axis.X; + } else { + return direction.z != 0 ? Axis.Z : Axis.Y; + } + } + + @Nonnull + private List findBlockTypes(String wildcardQuery) { + List results = new ObjectArrayList<>(); + BlockTypeAssetMap blockTypeAssets = BlockType.getAssetMap(); + + for (String blockName : blockTypeAssets.getAssetMap().keySet()) { + if (WildcardMatch.test(blockName, wildcardQuery)) { + BlockType blockType = blockTypeAssets.getAsset(blockName); + results.add(blockType); + } + } + + return results.stream().sorted(Comparator.comparing(BlockType::getId)).toList(); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockSelectCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockSelectCommand.java new file mode 100644 index 0000000..2f4ea44 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockSelectCommand.java @@ -0,0 +1,188 @@ +package com.hypixel.hytale.server.core.universe.world.commands.block; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFlipType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.VariantRotation; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EnumArgumentType; +import com.hypixel.hytale.server.core.command.system.arguments.types.SingleArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.prefab.selection.SelectionManager; +import com.hypixel.hytale.server.core.prefab.selection.SelectionProvider; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +public class BlockSelectCommand extends AbstractPlayerCommand { + @Nonnull + private static final SingleArgumentType BLOCK_FLIP_TYPE = new EnumArgumentType<>( + "server.commands.parsing.argtype.blockfliptype.name", BlockFlipType.class + ); + @Nonnull + private static final SingleArgumentType VARIANT_ROTATION = new EnumArgumentType<>( + "server.commands.parsing.argtype.variantrotation.name", VariantRotation.class + ); + @Nonnull + private static final Message MESSAGE_COMMANDS_BLOCK_SELECT_DONE = Message.translation("server.commands.block.select.done"); + @Nonnull + private static final Message MESSAGE_COMMANDS_BLOCK_SELECT_NO_SELECTION_PROVIDER = Message.translation("server.commands.block.select.noSelectionProvider"); + @Nonnull + private final OptionalArg regexArg = this.withOptionalArg("regex", "server.commands.block.select.regex.desc", ArgTypes.STRING); + @Nonnull + private final FlagArg allFlag = this.withFlagArg("all", "server.commands.block.select.all.desc"); + @Nonnull + private final OptionalArg sortArg = this.withOptionalArg("sort", "server.commands.block.select.sort.desc", ArgTypes.STRING); + @Nonnull + private final OptionalArg flipTypeArg = this.withOptionalArg("fliptype", "server.commands.block.select.fliptype.desc", BLOCK_FLIP_TYPE); + @Nonnull + private final OptionalArg variantRotationArg = this.withOptionalArg( + "variantrotation", "server.commands.block.select.variantrotation.desc", VARIANT_ROTATION + ); + @Nonnull + private final DefaultArg paddingArg = this.withDefaultArg("padding", "server.commands.block.select.padding.desc", ArgTypes.INTEGER, 1, "1"); + @Nonnull + private final OptionalArg groundArg = this.withOptionalArg("ground", "server.commands.block.select.ground.desc", ArgTypes.STRING); + + public BlockSelectCommand() { + super("blockselect", "server.commands.block.select.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + SelectionProvider selectionProvider = SelectionManager.getSelectionProvider(); + if (selectionProvider == null) { + context.sendMessage(MESSAGE_COMMANDS_BLOCK_SELECT_NO_SELECTION_PROVIDER); + } else { + Pattern pattern = this.regexArg.provided(context) ? Pattern.compile(this.regexArg.get(context)) : null; + Stream> stream = BlockType.getAssetMap() + .getAssetMap() + .entrySet() + .parallelStream() + .filter(e -> !e.getValue().isUnknown()) + .filter(e -> pattern == null || pattern.matcher(e.getKey()).matches()); + if (!this.allFlag.get(context)) { + stream = stream.filter(e -> !e.getValue().isState()); + } + + if (this.flipTypeArg.provided(context)) { + BlockFlipType flipType = this.flipTypeArg.get(context); + stream = stream.filter(e -> e.getValue().getFlipType() == flipType); + } + + if (this.variantRotationArg.provided(context)) { + VariantRotation variantRotation = this.variantRotationArg.get(context); + stream = stream.filter(e -> e.getValue().getVariantRotation() == variantRotation); + } + + if (this.sortArg.provided(context)) { + String sort = this.sortArg.get(context); + if (sort.isEmpty()) { + stream = stream.sorted(Entry.comparingByKey()); + } else { + for (String sortType : sort.split(",")) { + stream = switch (sortType) { + case "key" -> stream.sorted(Entry.comparingByKey()); + case "name" -> stream.sorted(Entry.comparingByKey()); + case "reverse" -> stream.sorted(Collections.reverseOrder()); + default -> throw new GeneralCommandException( + Message.translation("server.commands.block.select.invalidSortType").param("sortType", sortType) + ); + }; + } + } + } + + List> blocks = stream.map( + e -> Map.entry(e.getKey(), BlockBoundingBoxes.getAssetMap().getAsset(e.getValue().getHitboxTypeIndex())) + ) + .toList(); + context.sendMessage(Message.translation("server.commands.block.select.select").param("count", blocks.size())); + Box largestBox = new Box(); + + for (Entry block : blocks) { + largestBox.union(block.getValue().get(0).getBoundingBox()); + } + + int paddingSize = this.paddingArg.get(context); + int sqrt = MathUtil.ceil(Math.sqrt(blocks.size())) + 1; + int strideX = MathUtil.ceil(largestBox.width()) + paddingSize; + int strideZ = MathUtil.ceil(largestBox.depth()) + paddingSize; + int halfStrideX = strideX / 2; + int halfStrideZ = strideZ / 2; + double height = largestBox.height(); + String groundBlock; + if (this.groundArg.provided(context)) { + groundBlock = this.groundArg.get(context); + } else { + String rockStone = "Rock_Stone"; + if (BlockType.getAssetMap().getAsset("Rock_Stone") != null) { + groundBlock = "Rock_Stone"; + } else { + groundBlock = "Unknown"; + } + } + + selectionProvider.computeSelectionCopy(ref, playerComponent, selection -> { + BlockTypeAssetMap blockTypeAssetMap = BlockType.getAssetMap(); + int groundId = blockTypeAssetMap.getIndex(groundBlock); + + for (int x = -paddingSize; x < sqrt * strideX; x++) { + for (int z = -paddingSize; z < sqrt * strideZ; z++) { + selection.addBlockAtWorldPos(x, 0, z, groundId, 0, 0, 0); + + for (int y = 1; y < height; y++) { + selection.addBlockAtWorldPos(x, y, z, 0, 0, 0, 0); + } + } + } + + for (int i = 0; i < blocks.size(); i++) { + Entry entry = blocks.get(i); + BlockBoundingBoxes.RotatedVariantBoxes rotatedBoxes = entry.getValue().get(0); + Box boundingBox = rotatedBoxes.getBoundingBox(); + int x = i % sqrt * strideX + halfStrideX + MathUtil.floor(boundingBox.middleX()); + int z = i / sqrt * strideZ + halfStrideZ + MathUtil.floor(boundingBox.middleZ()); + int blockId = blockTypeAssetMap.getIndex(entry.getKey()); + selection.addBlockAtWorldPos(x, 1, z, blockId, 0, 0, 0); + if (entry.getValue().protrudesUnitBox()) { + FillerBlockUtil.forEachFillerBlock(rotatedBoxes, (x1, y1, z1) -> { + if (x1 != 0 || y1 != 0 || z1 != 0) { + int filler = FillerBlockUtil.pack(x1, y1, z1); + selection.addBlockAtWorldPos(x + x1, 1 + y1, z + z1, blockId, 0, filler, 0); + } + }); + } + } + + context.sendMessage(MESSAGE_COMMANDS_BLOCK_SELECT_DONE); + }, store); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockSetCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockSetCommand.java new file mode 100644 index 0000000..04b3fbb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockSetCommand.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.core.universe.world.commands.block; + +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import javax.annotation.Nonnull; + +public class BlockSetCommand extends SimpleBlockCommand { + private final RequiredArg blockArg = this.withRequiredArg("block", "server.commands.block.set.arg.block", ArgTypes.BLOCK_TYPE_ASSET); + + public BlockSetCommand() { + super("set", "server.commands.block.set.desc"); + this.setPermissionGroup(GameMode.Creative); + } + + @Override + protected void executeWithBlock(@Nonnull CommandContext context, @Nonnull WorldChunk chunk, int x, int y, int z) { + CommandSender sender = context.sender(); + BlockType blockType = context.get(this.blockArg); + chunk.setBlock(x, y, z, blockType); + sender.sendMessage( + Message.translation("server.commands.block.set.success").param("x", x).param("y", y).param("z", z).param("id", blockType.getId().toString()) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockSetStateCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockSetStateCommand.java new file mode 100644 index 0000000..09ee31e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockSetStateCommand.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.core.universe.world.commands.block; + +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import javax.annotation.Nonnull; + +public class BlockSetStateCommand extends SimpleBlockCommand { + private final RequiredArg stateArg = this.withRequiredArg("state", "", ArgTypes.STRING); + + public BlockSetStateCommand() { + super("setstate", ""); + } + + @Override + protected void executeWithBlock(@Nonnull CommandContext context, @Nonnull WorldChunk chunk, int x, int y, int z) { + int blockId = chunk.getBlock(x, y, z); + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + String state = context.get(this.stateArg); + chunk.setBlockInteractionState(x, y, z, blockType, state, true); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockSetTickingCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockSetTickingCommand.java new file mode 100644 index 0000000..078afe0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/block/BlockSetTickingCommand.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.server.core.universe.world.commands.block; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import javax.annotation.Nonnull; + +public class BlockSetTickingCommand extends SimpleBlockCommand { + public BlockSetTickingCommand() { + super("setticking", "server.commands.block.setticking.desc"); + this.setPermissionGroup(null); + } + + @Override + protected void executeWithBlock(@Nonnull CommandContext context, @Nonnull WorldChunk chunk, int x, int y, int z) { + CommandSender sender = context.sender(); + chunk.setTicking(x, y, z, true); + sender.sendMessage(Message.translation("server.commands.block.setticking.success").param("x", x).param("y", y).param("z", z)); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/block/SimpleBlockCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/block/SimpleBlockCommand.java new file mode 100644 index 0000000..f475321 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/block/SimpleBlockCommand.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.core.universe.world.commands.block; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeIntPosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public abstract class SimpleBlockCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_ERROR_EXCEPTION = Message.translation("server.commands.error.exception"); + @Nonnull + private final RequiredArg coordsArg = this.withRequiredArg( + "x y z", "server.commands.block.set.arg.coords", ArgTypes.RELATIVE_BLOCK_POSITION + ); + + public SimpleBlockCommand(@Nonnull String name, @Nonnull String description) { + super(name, description); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + CommandSender sender = context.sender(); + RelativeIntPosition coords = context.get(this.coordsArg); + Vector3i blockPos = coords.getBlockPosition(context, store); + int x = blockPos.x; + int y = blockPos.y; + int z = blockPos.z; + long chunkIndex = ChunkUtil.indexChunkFromBlock(x, z); + world.getChunkAsync(chunkIndex).thenAcceptAsync(chunk -> this.executeWithBlock(context, chunk, x, y, z), world).exceptionally(t -> { + HytaleLogger.getLogger().at(Level.SEVERE).withCause(t).log("Error getting chunk for command"); + sender.sendMessage(MESSAGE_COMMANDS_ERROR_EXCEPTION); + return null; + }); + } + + protected abstract void executeWithBlock(@Nonnull CommandContext var1, @Nonnull WorldChunk var2, int var3, int var4, int var5); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/block/bulk/BlockBulkCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/block/bulk/BlockBulkCommand.java new file mode 100644 index 0000000..49db26e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/block/bulk/BlockBulkCommand.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.server.core.universe.world.commands.block.bulk; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class BlockBulkCommand extends AbstractCommandCollection { + public BlockBulkCommand() { + super("bulk", "server.commands.block.bulk.desc"); + this.setPermissionGroup(null); + this.addSubCommand(new BlockBulkFindCommand()); + this.addSubCommand(new BlockBulkFindHereCommand()); + this.addSubCommand(new BlockBulkReplaceCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/block/bulk/BlockBulkFindCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/block/bulk/BlockBulkFindCommand.java new file mode 100644 index 0000000..f140698 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/block/bulk/BlockBulkFindCommand.java @@ -0,0 +1,131 @@ +package com.hypixel.hytale.server.core.universe.world.commands.block.bulk; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.iterator.SpiralIterator; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.IntCoord; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntLists; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + +public class BlockBulkFindCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_BLOCK_FIND_TIME_OUT = Message.translation("server.commands.block.find.timeout"); + @Nonnull + private static final Message MESSAGE_COMMANDS_BLOCK_FIND_DONE = Message.translation("server.commands.block.find.done"); + @Nonnull + private final RequiredArg chunkXArg = this.withRequiredArg("chunkX", "", ArgTypes.RELATIVE_INT_COORD); + @Nonnull + private final RequiredArg chunkZArg = this.withRequiredArg("chunkZ", "", ArgTypes.RELATIVE_INT_COORD); + @Nonnull + private final RequiredArg blockTypeArg = this.withRequiredArg("block", "", ArgTypes.BLOCK_TYPE_KEY); + @Nonnull + private final RequiredArg countArg = this.withRequiredArg("count", "", ArgTypes.INTEGER); + @Nonnull + private final RequiredArg timeoutArg = this.withRequiredArg("timeout", "", ArgTypes.INTEGER); + + public BlockBulkFindCommand() { + super("find", "server.commands.find.desc", true); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + CommandSender sender = context.sender(); + IntCoord relChunkX = this.chunkXArg.get(context); + IntCoord relChunkZ = this.chunkZArg.get(context); + int baseChunkX = 0; + int baseChunkZ = 0; + if (context.isPlayer()) { + Ref playerRef = context.senderAsPlayerRef(); + if (playerRef != null) { + TransformComponent transformComponent = store.getComponent(playerRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d playerPos = transformComponent.getPosition(); + baseChunkX = MathUtil.floor(playerPos.getX()) >> 5; + baseChunkZ = MathUtil.floor(playerPos.getZ()) >> 5; + } + } + + int originChunkX = relChunkX.resolveXZ(baseChunkX); + int originChunkZ = relChunkZ.resolveXZ(baseChunkZ); + String blockTypeKey = this.blockTypeArg.get(context); + int blockId = BlockType.getAssetMap().getIndex(blockTypeKey); + IntList idAsList = IntLists.singleton(blockId); + int count = this.countArg.get(context); + int timeout = this.timeoutArg.get(context); + CompletableFuture.runAsync( + () -> { + long start = System.nanoTime(); + int tested = 0; + int[] found = new int[]{0}; + IntOpenHashSet temp = new IntOpenHashSet(); + ChunkStore chunkComponentStore = world.getChunkStore(); + SpiralIterator iterator = new SpiralIterator(originChunkX, originChunkZ, SpiralIterator.MAX_RADIUS); + + label34: + while (iterator.hasNext()) { + long key = iterator.next(); + BlockChunk blockChunk = chunkComponentStore.getChunkReferenceAsync(key) + .thenApplyAsync(chunkRef -> chunkComponentStore.getStore().getComponent((Ref)chunkRef, BlockChunk.getComponentType()), world) + .join(); + + for (int sectionIndex = 0; sectionIndex < 10; sectionIndex++) { + BlockSection section = blockChunk.getSectionAtIndex(sectionIndex); + if (section.contains(blockId)) { + int chunkX = ChunkUtil.xOfChunkIndex(key); + int chunkY = sectionIndex; + int chunkZ = ChunkUtil.zOfChunkIndex(key); + section.find(idAsList, temp, blockIndex -> { + if (found[0] < count) { + found[0]++; + int x = chunkX << 5 | ChunkUtil.xFromIndex(blockIndex); + int y = chunkY << 5 | ChunkUtil.yFromIndex(blockIndex); + int z = chunkZ << 5 | ChunkUtil.zFromIndex(blockIndex); + sender.sendMessage(Message.translation("server.commands.block.find.blockFound").param("x", x).param("y", y).param("z", z)); + } + }); + if (found[0] >= count) { + break label34; + } + + temp.clear(); + } + } + + if (++tested % 100 == 0) { + sender.sendMessage(Message.translation("server.commands.block.find.chunksTested").param("nb", tested)); + } + + long diff = System.nanoTime() - start; + if (diff > TimeUnit.SECONDS.toNanos(timeout)) { + sender.sendMessage(MESSAGE_COMMANDS_BLOCK_FIND_TIME_OUT); + return; + } + } + + sender.sendMessage(MESSAGE_COMMANDS_BLOCK_FIND_DONE); + } + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/block/bulk/BlockBulkFindHereCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/block/bulk/BlockBulkFindHereCommand.java new file mode 100644 index 0000000..70cec30 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/block/bulk/BlockBulkFindHereCommand.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.server.core.universe.world.commands.block.bulk; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.iterator.SpiralIterator; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nonnull; + +public class BlockBulkFindHereCommand extends AbstractPlayerCommand { + @Nonnull + private final FlagArg printNameArg = this.withFlagArg("print", ""); + @Nonnull + private final RequiredArg blockTypeArg = this.withRequiredArg("block", "", ArgTypes.BLOCK_TYPE_KEY); + @Nonnull + private final DefaultArg radiusArg = this.withDefaultArg("radius", "", ArgTypes.INTEGER, 3, ""); + + public BlockBulkFindHereCommand() { + super("find-here", "server.commands.block.find-here.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + String blockTypeKey = this.blockTypeArg.get(context); + int blockId = BlockType.getAssetMap().getIndex(blockTypeKey); + IntList blockIdList = BlockBulkReplaceCommand.getBlockIdList(blockId); + int radius = this.radiusArg.get(context); + boolean printBlockName = this.printNameArg.get(context); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d playerPos = transformComponent.getPosition(); + int originChunkX = MathUtil.floor(playerPos.getX()) >> 5; + int originChunkZ = MathUtil.floor(playerPos.getZ()) >> 5; + CompletableFuture.runAsync( + () -> { + long start = System.nanoTime(); + IntOpenHashSet temp = new IntOpenHashSet(); + ChunkStore chunkComponentStore = world.getChunkStore(); + AtomicInteger found = new AtomicInteger(); + SpiralIterator iterator = new SpiralIterator(originChunkX, originChunkZ, radius); + + while (iterator.hasNext()) { + long key = iterator.next(); + BlockChunk blockChunk = chunkComponentStore.getChunkReferenceAsync(key) + .thenApplyAsync(chunkRef -> chunkComponentStore.getStore().getComponent((Ref)chunkRef, BlockChunk.getComponentType()), world) + .join(); + + for (int sectionIndex = 0; sectionIndex < 10; sectionIndex++) { + BlockSection section = blockChunk.getSectionAtIndex(sectionIndex); + if (section.containsAny(blockIdList)) { + section.find(blockIdList, temp, blockIndex -> found.getAndIncrement()); + temp.clear(); + } + } + } + + long diff = System.nanoTime() - start; + BlockType findBlock = BlockType.getAssetMap().getAsset(blockId); + String blockName = printBlockName ? " " + findBlock.getId() : ""; + playerRef.sendMessage(Message.translation("Found " + found.get() + blockName + " blocks in " + TimeUnit.NANOSECONDS.toSeconds(diff) + " seconds!")); + } + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/block/bulk/BlockBulkReplaceCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/block/bulk/BlockBulkReplaceCommand.java new file mode 100644 index 0000000..e906784 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/block/bulk/BlockBulkReplaceCommand.java @@ -0,0 +1,132 @@ +package com.hypixel.hytale.server.core.universe.world.commands.block.bulk; + +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.iterator.SpiralIterator; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.VariantRotation; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nonnull; + +public class BlockBulkReplaceCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg findArg = this.withRequiredArg("find", "", ArgTypes.BLOCK_TYPE_KEY); + @Nonnull + private final RequiredArg replaceArg = this.withRequiredArg("replaceWith", "", ArgTypes.BLOCK_TYPE_KEY); + @Nonnull + private final RequiredArg radiusArg = this.withRequiredArg("radius", "", ArgTypes.INTEGER); + + public BlockBulkReplaceCommand() { + super("replace", ""); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + String findBlockTypeKey = this.findArg.get(context); + String replaceBlockTypeKey = this.replaceArg.get(context); + int findBlockId = BlockType.getAssetMap().getIndex(findBlockTypeKey); + int replaceBlockId = BlockType.getAssetMap().getIndex(replaceBlockTypeKey); + IntList findBlockIdList = getBlockIdList(findBlockId); + IntList replaceBlockIdList = getBlockIdList(replaceBlockId); + int radius = this.radiusArg.get(context); + Vector3d playerPos = transformComponent.getPosition(); + int originChunkX = MathUtil.floor(playerPos.getX()) >> 5; + int originChunkZ = MathUtil.floor(playerPos.getZ()) >> 5; + CompletableFuture.runAsync( + () -> { + long start = System.nanoTime(); + IntOpenHashSet temp = new IntOpenHashSet(); + ChunkStore chunkComponentStore = world.getChunkStore(); + AtomicInteger replaced = new AtomicInteger(); + SpiralIterator iterator = new SpiralIterator(originChunkX, originChunkZ, radius); + + while (iterator.hasNext()) { + long key = iterator.next(); + BlockChunk blockChunk = chunkComponentStore.getChunkReferenceAsync(key) + .thenApplyAsync(chunkRef -> chunkComponentStore.getStore().getComponent((Ref)chunkRef, BlockChunk.getComponentType()), world) + .join(); + + for (int sectionIndex = 0; sectionIndex < 10; sectionIndex++) { + BlockSection section = blockChunk.getSectionAtIndex(sectionIndex); + if (section.containsAny(findBlockIdList)) { + int chunkX = ChunkUtil.xOfChunkIndex(key); + int chunkY = sectionIndex; + int chunkZ = ChunkUtil.zOfChunkIndex(key); + section.find(findBlockIdList, temp, blockIndex -> { + int x = chunkX << 5 | ChunkUtil.xFromIndex(blockIndex); + int y = chunkY << 5 | ChunkUtil.yFromIndex(blockIndex); + int z = chunkZ << 5 | ChunkUtil.zFromIndex(blockIndex); + CompletableFutureUtil._catch(world.getChunkAsync(ChunkUtil.indexChunkFromBlock(x, z))).thenAccept(chunk -> { + int foundBlock = chunk.getBlock(x, y, z); + int replaceIndex = findBlockIdList.indexOf(Integer.valueOf(foundBlock)); + chunk.setBlock(x, y, z, replaceBlockIdList.getInt(replaceIndex)); + replaced.getAndIncrement(); + }); + }); + } + } + } + + long diff = System.nanoTime() - start; + playerRef.sendMessage( + Message.translation("Found and replaced " + replaced.get() + " blocks in " + TimeUnit.NANOSECONDS.toSeconds(diff) + " seconds!") + ); + } + ); + } + + @Nonnull + protected static IntList getBlockIdList(int blockId) { + IntList blockIdList = new IntArrayList(); + BlockType findBlock = BlockType.getAssetMap().getAsset(blockId); + if (findBlock.getVariantRotation().equals(VariantRotation.NESW)) { + blockIdList = createNESWRotationLists(findBlock, blockIdList); + } else { + blockIdList.add(blockId); + } + + return blockIdList; + } + + @Nonnull + private static IntList createNESWRotationLists(@Nonnull BlockType block, @Nonnull IntList blockIdList) { + RotationTuple[] rotations = block.getVariantRotation().getRotations(); + String blockName = block.getId(); + blockIdList.add(BlockType.getAssetMap().getIndex(blockName)); + + for (RotationTuple rp : rotations) { + String newBlockRotation = blockName + "|Yaw=" + rp.yaw().getDegrees(); + blockIdList.add(BlockType.getAssetMap().getIndex(newBlockRotation)); + } + + return blockIdList; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldAddCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldAddCommand.java new file mode 100644 index 0000000..03a3166 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldAddCommand.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.server.core.universe.world.commands.world; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.worldgen.provider.IWorldGenProvider; +import java.util.Objects; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class WorldAddCommand extends CommandBase { + @Nonnull + private final RequiredArg nameArg = this.withRequiredArg("name", "server.commands.addworld.arg.name.desc", ArgTypes.STRING); + @Nonnull + private final DefaultArg genArg = this.withDefaultArg("gen", "server.commands.addworld.arg.gen.desc", ArgTypes.STRING, "default", ""); + @Nonnull + private final DefaultArg storageArg = this.withDefaultArg("storage", "server.commands.addworld.arg.gen.desc", ArgTypes.STRING, "default", ""); + + public WorldAddCommand() { + super("add", "server.commands.addworld.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + CommandSender sender = context.sender(); + String name = context.get(this.nameArg); + if (Universe.get().getWorld(name) != null) { + sender.sendMessage(Message.translation("server.universe.addWorld.alreadyExists").param("worldName", name)); + } else if (Universe.get().isWorldLoadable(name)) { + sender.sendMessage(Message.translation("server.universe.addWorld.alreadyExistsDisk").param("worldName", name)); + } else { + String generatorType = context.get(this.genArg); + String chunkStorageType = context.get(this.storageArg); + if (generatorType != null && !"default".equals(generatorType)) { + BuilderCodec providerCodec = IWorldGenProvider.CODEC.getCodecFor(generatorType); + if (providerCodec == null) { + throw new IllegalArgumentException("Unknown generatorType '" + generatorType + "'"); + } + } + + CompletableFutureUtil._catch( + Universe.get() + .addWorld(name, generatorType, chunkStorageType) + .thenRun( + () -> sender.sendMessage( + Message.translation("server.universe.addWorld.worldCreated") + .param("worldName", name) + .param("generator", Objects.requireNonNullElse(generatorType, "default")) + .param("storage", Objects.requireNonNullElse(chunkStorageType, "default")) + ) + ) + .exceptionally( + throwable -> { + LOGGER.at(Level.SEVERE).withCause(throwable).log("Failed to add world '%s'", name); + sender.sendMessage( + Message.translation("server.universe.addWorld.failed") + .param("worldName", name) + .param("error", throwable.getCause() != null ? throwable.getCause().getMessage() : throwable.getMessage()) + ); + return null; + } + ) + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldCommand.java new file mode 100644 index 0000000..636a4ea --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldCommand.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.server.core.universe.world.commands.world; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.universe.world.commands.WorldSettingsCommand; +import com.hypixel.hytale.server.core.universe.world.commands.world.perf.WorldPerfCommand; +import com.hypixel.hytale.server.core.universe.world.commands.world.tps.WorldTpsCommand; +import com.hypixel.hytale.server.core.universe.world.commands.worldconfig.WorldConfigCommand; +import com.hypixel.hytale.server.core.universe.world.commands.worldconfig.WorldPauseCommand; + +public class WorldCommand extends AbstractCommandCollection { + public WorldCommand() { + super("world", "server.commands.world.desc"); + this.addAliases("worlds"); + this.addSubCommand(new WorldListCommand()); + this.addSubCommand(new WorldRemoveCommand()); + this.addSubCommand(new WorldPruneCommand()); + this.addSubCommand(new WorldLoadCommand()); + this.addSubCommand(new WorldAddCommand()); + this.addSubCommand(new WorldSetDefaultCommand()); + this.addSubCommand(new WorldSaveCommand()); + this.addSubCommand(new WorldPauseCommand()); + this.addSubCommand(new WorldConfigCommand()); + this.addSubCommand(new WorldSettingsCommand()); + this.addSubCommand(new WorldPerfCommand()); + this.addSubCommand(new WorldTpsCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldListCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldListCommand.java new file mode 100644 index 0000000..0db0d9b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldListCommand.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.core.universe.world.commands.world; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; + +public class WorldListCommand extends CommandBase { + public WorldListCommand() { + super("list", "server.commands.worlds.desc"); + this.addAliases("ls"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + Set worlds = Universe.get().getWorlds().keySet().stream().map(Message::raw).collect(Collectors.toSet()); + Message message = MessageFormat.list(Message.translation("server.commands.worlds.header"), worlds); + context.sender().sendMessage(message); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldLoadCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldLoadCommand.java new file mode 100644 index 0000000..ab3e270 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldLoadCommand.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.core.universe.world.commands.world; + +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.universe.Universe; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class WorldLoadCommand extends CommandBase { + @Nonnull + private final RequiredArg nameArg = this.withRequiredArg("name", "server.commands.loadworld.arg.name.desc", ArgTypes.STRING); + + public WorldLoadCommand() { + super("load", "server.commands.loadworld.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + CommandSender sender = context.sender(); + String name = context.get(this.nameArg); + if (Universe.get().getWorld(name) != null) { + sender.sendMessage(Message.translation("server.universe.loadWorld.alreadyExists").param("worldName", name)); + } else if (!Universe.get().isWorldLoadable(name)) { + sender.sendMessage(Message.translation("server.universe.loadWorld.notExist").param("worldName", name)); + } else { + CompletableFutureUtil._catch( + Universe.get() + .loadWorld(name) + .thenRun(() -> sender.sendMessage(Message.translation("server.universe.loadWorld.worldCreated").param("worldName", name))) + .exceptionally( + throwable -> { + LOGGER.at(Level.SEVERE).withCause(throwable).log("Failed to load world '%s'", name); + sender.sendMessage( + Message.translation("server.universe.loadWorld.failed") + .param("worldName", name) + .param("error", throwable.getCause() != null ? throwable.getCause().getMessage() : throwable.getMessage()) + ); + return null; + } + ) + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldPruneCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldPruneCommand.java new file mode 100644 index 0000000..c3f148b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldPruneCommand.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.core.universe.world.commands.world; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class WorldPruneCommand extends AbstractAsyncCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_PRUNE_NONE_TO_PRUNE = Message.translation("server.commands.world.prune.noneToPrune"); + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_PRUNE_PRUNE_ERROR = Message.translation("server.commands.world.prune.pruneError"); + + public WorldPruneCommand() { + super("prune", "server.commands.world.prune.desc", true); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + CommandSender sender = context.sender(); + World defaultWorld = Universe.get().getDefaultWorld(); + Map worlds = Universe.get().getWorlds(); + Set toRemove = new HashSet<>(); + worlds.forEach((worldKey, world) -> { + if (world != defaultWorld && world.getPlayerCount() == 0) { + toRemove.add(worldKey); + world.getWorldConfig().setDeleteOnRemove(true); + } + }); + if (toRemove.isEmpty()) { + sender.sendMessage(MESSAGE_COMMANDS_WORLD_PRUNE_NONE_TO_PRUNE); + return CompletableFuture.completedFuture(null); + } else { + return CompletableFuture.runAsync(() -> { + toRemove.forEach(worldKey -> { + try { + boolean removed = Universe.get().removeWorld(worldKey); + String msgKey = removed ? "server.commands.world.prune.prunedWorld" : "server.commands.world.prune.pruneFailed"; + sender.sendMessage(Message.translation(msgKey).param("world", worldKey)); + } catch (Throwable var4x) { + sender.sendMessage(MESSAGE_COMMANDS_WORLD_PRUNE_PRUNE_ERROR); + HytaleLogger.getLogger().at(Level.SEVERE).withCause(var4x).log("Error pruning world " + worldKey); + } + }); + sender.sendMessage(Message.translation("server.commands.world.prune.done").param("count", toRemove.size())); + }); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldRemoveCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldRemoveCommand.java new file mode 100644 index 0000000..5e6de61 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldRemoveCommand.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.universe.world.commands.world; + +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.universe.Universe; +import javax.annotation.Nonnull; + +public class WorldRemoveCommand extends CommandBase { + public static final Message MESSAGE_UNIVERSE_REMOVE_WORLD_NOT_FOUND = Message.translation("server.universe.removeworld.notFound"); + public static final Message MESSAGE_UNIVERSE_REMOVE_WORLD_ONLY_ONE_WORLD_LOADED = Message.translation("server.universe.removeworld.onlyOneWorldLoaded"); + public static final Message MESSAGE_UNIVERSE_REMOVE_WORLD_CHANGE_DEFAULT_WORLD = Message.translation("server.universe.removeworld.changeDefaultWorld"); + @Nonnull + private final RequiredArg nameArg = this.withRequiredArg("name", "server.commands.removeworld.arg.name.desc", ArgTypes.STRING); + + public WorldRemoveCommand() { + super("remove", "server.commands.removeworld.desc"); + this.addAliases("rm"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + CommandSender sender = context.sender(); + String name = context.get(this.nameArg); + if (Universe.get().getWorld(name) == null) { + sender.sendMessage(MESSAGE_UNIVERSE_REMOVE_WORLD_NOT_FOUND); + } else if (Universe.get().getWorlds().size() == 1) { + sender.sendMessage(MESSAGE_UNIVERSE_REMOVE_WORLD_ONLY_ONE_WORLD_LOADED); + } else if (name.equalsIgnoreCase(HytaleServer.get().getConfig().getDefaults().getWorld())) { + sender.sendMessage(MESSAGE_UNIVERSE_REMOVE_WORLD_CHANGE_DEFAULT_WORLD); + } else { + Universe.get().removeWorld(name); + sender.sendMessage(Message.translation("server.universe.removeworld.success").param("worldName", name)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldSaveCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldSaveCommand.java new file mode 100644 index 0000000..cf6428e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldSaveCommand.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.server.core.universe.world.commands.world; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.system.WorldConfigSaveSystem; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.component.ChunkSavingSystems; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class WorldSaveCommand extends AbstractAsyncCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_SAVE_NO_WORLD_SPECIFIED = Message.translation("server.commands.world.save.noWorldSpecified"); + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_SAVE_SAVING_ALL = Message.translation("server.commands.world.save.savingAll"); + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_SAVE_SAVING_ALL_DONE = Message.translation("server.commands.world.save.savingAllDone"); + @Nonnull + private final OptionalArg worldArg = this.withOptionalArg("world", "server.commands.worldthread.arg.desc", ArgTypes.WORLD); + @Nonnull + private final FlagArg saveAllFlag = this.withFlagArg("all", "server.commands.world.save.all.desc"); + + public WorldSaveCommand() { + super("save", "server.commands.world.save.desc", true); + } + + @Nonnull + @Override + protected CompletableFuture executeAsync(@Nonnull CommandContext context) { + if (this.saveAllFlag.get(context)) { + return this.saveAllWorlds(context); + } else if (!this.worldArg.provided(context)) { + context.sendMessage(MESSAGE_COMMANDS_WORLD_SAVE_NO_WORLD_SPECIFIED); + return CompletableFuture.completedFuture(null); + } else { + World world = this.worldArg.getProcessed(context); + context.sendMessage(Message.translation("server.commands.world.save.saving").param("world", world.getName())); + return CompletableFuture.runAsync(() -> saveWorld(world), world) + .thenRun(() -> context.sendMessage(Message.translation("server.commands.world.save.savingDone").param("world", world.getName()))); + } + } + + @Nonnull + private CompletableFuture saveAllWorlds(@Nonnull CommandContext context) { + context.sendMessage(MESSAGE_COMMANDS_WORLD_SAVE_SAVING_ALL); + CompletableFuture[] completableFutures = Universe.get() + .getWorlds() + .values() + .stream() + .map(world -> CompletableFuture.runAsync(() -> saveWorld(world), world)) + .toArray(CompletableFuture[]::new); + return CompletableFuture.allOf(completableFutures).thenRun(() -> context.sendMessage(MESSAGE_COMMANDS_WORLD_SAVE_SAVING_ALL_DONE)); + } + + @Nonnull + private static CompletableFuture saveWorld(@Nonnull World world) { + return CompletableFuture.allOf( + WorldConfigSaveSystem.saveWorldConfigAndResources(world), ChunkSavingSystems.saveChunksInWorld(world.getChunkStore().getStore()) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldSetDefaultCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldSetDefaultCommand.java new file mode 100644 index 0000000..61932c9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/world/WorldSetDefaultCommand.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.server.core.universe.world.commands.world; + +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.universe.Universe; +import javax.annotation.Nonnull; + +public class WorldSetDefaultCommand extends CommandBase { + @Nonnull + private final RequiredArg nameArg = this.withRequiredArg("name", "server.commands.world.setdefault.arg.name.desc", ArgTypes.STRING); + + public WorldSetDefaultCommand() { + super("setdefault", "server.commands.world.setdefault.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + CommandSender sender = context.sender(); + String worldName = context.get(this.nameArg); + if (Universe.get().getWorld(worldName) == null) { + sender.sendMessage(Message.translation("server.world.notFound").param("worldName", worldName)); + } else { + HytaleServer.get().getConfig().getDefaults().setWorld(worldName); + sender.sendMessage(Message.translation("server.universe.defaultWorldSet").param("worldName", worldName)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/world/perf/WorldPerfCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/world/perf/WorldPerfCommand.java new file mode 100644 index 0000000..93fd8d6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/world/perf/WorldPerfCommand.java @@ -0,0 +1,117 @@ +package com.hypixel.hytale.server.core.universe.world.commands.world.perf; + +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.metrics.metric.HistoricMetric; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + +public class WorldPerfCommand extends AbstractWorldCommand { + public static final double PRECISION = 1000.0; + @Nonnull + private final FlagArg allFlag = this.withFlagArg("all", "server.commands.world.perf.all.desc"); + @Nonnull + private final FlagArg deltaFlag = this.withFlagArg("delta", "server.commands.world.perf.delta.desc"); + + public WorldPerfCommand() { + super("perf", "server.commands.world.perf.desc"); + this.addSubCommand(new WorldPerfGraphCommand()); + this.addSubCommand(new WorldPerfResetCommand()); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + HistoricMetric historicMetric = world.getBufferedTickLengthMetricSet(); + long[] periods = historicMetric.getPeriodsNanos(); + int tickStepNanos = world.getTickStepNanos(); + Message msg = Message.empty(); + boolean showDelta = this.deltaFlag.provided(context); + boolean showAll = this.allFlag.provided(context); + if (context.sender() instanceof Player) { + for (int i = 0; i < periods.length; i++) { + String length = FormatUtil.timeUnitToString(periods[i], TimeUnit.NANOSECONDS, true); + double average = historicMetric.getAverage(i); + long min = historicMetric.calculateMin(i); + long max = historicMetric.calculateMax(i); + if (showDelta) { + String value = FormatUtil.simpleTimeUnitFormat(min, average, max, TimeUnit.NANOSECONDS, TimeUnit.MILLISECONDS, 3); + String padding = " ".repeat(Math.max(0, 24 - value.length())); + msg.insert( + Message.translation("server.commands.world.perf.period").param("length", length).param("padding", padding).param("value", value).insert("\n") + ); + } else { + msg.insert( + Message.translation("server.commands.world.perf.tpsTime") + .param("time", length) + .param("tps", FormatUtil.simpleFormat(min, average, max, d1 -> tpsFromDelta(d1, (long)tickStepNanos), 2)) + .insert("\n") + ); + } + } + } else { + String tickLimitFormatted = FormatUtil.simpleTimeUnitFormat(tickStepNanos, TimeUnit.NANOSECONDS, 3); + msg.insert(Message.translation("server.commands.world.perf.tickLimit").param("tickLimit", tickLimitFormatted).insert("\n")); + + for (int ix = 0; ix < periods.length; ix++) { + String length = FormatUtil.timeUnitToString(periods[ix], TimeUnit.NANOSECONDS, true); + double average = historicMetric.getAverage(ix); + long min = historicMetric.calculateMin(ix); + long max = historicMetric.calculateMax(ix); + if (showDelta) { + String value = FormatUtil.simpleTimeUnitFormat(min, average, max, TimeUnit.NANOSECONDS, TimeUnit.MILLISECONDS, 3); + String padding = " ".repeat(Math.max(0, 24 - value.length())); + msg.insert( + Message.translation("server.commands.world.perf.period").param("length", length).param("padding", padding).param("value", value).insert("\n") + ); + } else { + msg.insert( + Message.translation("server.commands.world.perf.tpsMinMaxMetric") + .param("time", length) + .param("min", tpsFromDelta(max, (long)tickStepNanos)) + .param("avg", tpsFromDelta(average, (long)tickStepNanos)) + .param("max", tpsFromDelta(min, (long)tickStepNanos)) + .insert("\n") + ); + } + + if (showAll) { + msg.insert( + Message.translation("server.commands.world.perf.deltaMinMaxMetric") + .param("time", length) + .param("min", min) + .param("avg", (long)average) + .param("max", max) + .insert("\n") + ); + } + } + } + + context.sendMessage(msg); + } + + public static double tpsFromDelta(long delta, long min) { + long adjustedDelta = delta; + if (delta < min) { + adjustedDelta = min; + } + + return Math.round(1.0 / adjustedDelta * 1.0E9 * 1000.0) / 1000.0; + } + + public static double tpsFromDelta(double delta, long min) { + double adjustedDelta = delta; + if (delta < min) { + adjustedDelta = min; + } + + return Math.round(1.0 / adjustedDelta * 1.0E9 * 1000.0) / 1000.0; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/world/perf/WorldPerfGraphCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/world/perf/WorldPerfGraphCommand.java new file mode 100644 index 0000000..dff338b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/world/perf/WorldPerfGraphCommand.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.server.core.universe.world.commands.world.perf; + +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.metrics.metric.HistoricMetric; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; + +public class WorldPerfGraphCommand extends AbstractWorldCommand { + @Nonnull + private final DefaultArg widthArg = this.withDefaultArg( + "width", "server.commands.world.perf.graph.width.desc", ArgTypes.INTEGER, 100, "server.commands.world.perf.graph.width.default" + ); + @Nonnull + private final DefaultArg heightArg = this.withDefaultArg( + "height", "server.commands.world.perf.graph.height.desc", ArgTypes.INTEGER, 10, "server.commands.world.perf.graph.height.default" + ); + + public WorldPerfGraphCommand() { + super("graph", "server.commands.world.perf.graph.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Integer width = this.widthArg.get(context); + Integer height = this.heightArg.get(context); + long startNano = System.nanoTime(); + Message msg = Message.empty(); + HistoricMetric historicMetric = world.getBufferedTickLengthMetricSet(); + long[] periods = historicMetric.getPeriodsNanos(); + + for (int i = 0; i < periods.length; i++) { + long period = periods[i]; + long[] historyTimestamps = historicMetric.getTimestamps(i); + long[] historyValues = historicMetric.getValues(i); + String historyLengthFormatted = FormatUtil.timeUnitToString(period, TimeUnit.NANOSECONDS, true); + msg.insert(Message.translation("server.commands.world.perf.graph").param("time", historyLengthFormatted)); + StringBuilder sb = new StringBuilder(); + StringUtil.generateGraph( + sb, + width, + height, + startNano - period, + startNano, + 0.0, + world.getTps(), + value -> String.valueOf(MathUtil.round(value, 2)), + historyTimestamps.length, + ii -> historyTimestamps[ii], + ii -> WorldPerfCommand.tpsFromDelta(historyValues[ii], (long)world.getTickStepNanos()) + ); + msg.insert(sb.toString()).insert("\n"); + } + + context.sendMessage(msg); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/world/perf/WorldPerfResetCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/world/perf/WorldPerfResetCommand.java new file mode 100644 index 0000000..5b3c8ca --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/world/perf/WorldPerfResetCommand.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.core.universe.world.commands.world.perf; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class WorldPerfResetCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_PERF_RESET_ALL = Message.translation("server.commands.world.perf.reset.all"); + private final FlagArg allFlag = this.withFlagArg("all", "server.commands.world.perf.reset.all.desc"); + + public WorldPerfResetCommand() { + super("reset", "server.commands.world.perf.reset.desc"); + } + + @Override + protected void execute(@NonNullDecl CommandContext context, @NonNullDecl World world, @NonNullDecl Store store) { + if (this.allFlag.provided(context)) { + Universe.get().getWorlds().forEach((name, w) -> w.clearMetrics()); + context.sendMessage(MESSAGE_COMMANDS_WORLD_PERF_RESET_ALL); + } else { + world.clearMetrics(); + context.sendMessage(Message.translation("server.commands.world.perf.reset").param("worldName", world.getName())); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/world/tps/WorldTpsCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/world/tps/WorldTpsCommand.java new file mode 100644 index 0000000..9d49d13 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/world/tps/WorldTpsCommand.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.core.universe.world.commands.world.tps; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WorldTpsCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_TPS_SET_SUCCESS = Message.translation("server.commands.world.tps.set.success"); + @Nonnull + private static final Message MESSAGE_COMMANDS_WORLD_TPS_SET_INVALID = Message.translation("server.commands.world.tps.set.invalid"); + @Nonnull + private final RequiredArg tickRateArg = this.withRequiredArg("rate", "server.commands.world.tps.rate.desc", ArgTypes.TICK_RATE); + + public WorldTpsCommand() { + super("tps", "server.commands.world.tps.desc"); + this.addAliases("tickrate"); + this.addSubCommand(new WorldTpsResetCommand()); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + int newTickRate = this.tickRateArg.get(context); + if (newTickRate > 0 && newTickRate <= 2048) { + world.setTps(newTickRate); + double newMs = 1000.0 / newTickRate; + context.sendMessage( + MESSAGE_COMMANDS_WORLD_TPS_SET_SUCCESS.param("worldName", world.getName()).param("tps", newTickRate).param("ms", String.format("%.2f", newMs)) + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_WORLD_TPS_SET_INVALID.param("value", newTickRate)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/world/tps/WorldTpsResetCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/world/tps/WorldTpsResetCommand.java new file mode 100644 index 0000000..9196c78 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/world/tps/WorldTpsResetCommand.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.universe.world.commands.world.tps; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WorldTpsResetCommand extends AbstractWorldCommand { + public WorldTpsResetCommand() { + super("reset", "server.commands.world.tps.reset.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + int defaultTps = 30; + world.setTps(30); + double defaultMs = 33.333333333333336; + context.sendMessage( + Message.translation("server.commands.world.tps.reset.success") + .param("worldName", world.getName()) + .param("tps", 30) + .param("ms", String.format("%.2f", 33.333333333333336)) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigCommand.java new file mode 100644 index 0000000..9409725 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigCommand.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.server.core.universe.world.commands.worldconfig; + +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; + +public class WorldConfigCommand extends AbstractCommandCollection { + public WorldConfigCommand() { + super("config", "server.commands.world.config.desc"); + this.addSubCommand(new WorldConfigPauseTimeCommand()); + this.addSubCommand(new WorldConfigSeedCommand()); + this.addSubCommand(new WorldConfigSetPvpCommand()); + this.addSubCommand(new WorldConfigSetSpawnCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigPauseTimeCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigPauseTimeCommand.java new file mode 100644 index 0000000..cdb88dd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigPauseTimeCommand.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.server.core.universe.world.commands.worldconfig; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WorldConfigPauseTimeCommand extends AbstractWorldCommand { + public WorldConfigPauseTimeCommand() { + super("pausetime", "server.commands.pausetime.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + pauseTime(context.sender(), world, store); + } + + public static void pauseTime(@Nonnull CommandSender commandSender, @Nonnull World world, @Nonnull Store store) { + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + boolean timePause = !world.getWorldConfig().isGameTimePaused(); + WorldConfig worldConfig = world.getWorldConfig(); + worldConfig.setGameTimePaused(timePause); + worldConfig.markChanged(); + Message timePausedMessage = Message.translation(timePause ? "server.general.paused" : "server.general.resumed"); + commandSender.sendMessage( + Message.translation("server.commands.pausetime.timeInfo") + .param("msg", timePausedMessage) + .param("worldName", world.getName()) + .param("time", worldTimeResource.getGameTime().toString()) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigSeedCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigSeedCommand.java new file mode 100644 index 0000000..3ccc01f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigSeedCommand.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.server.core.universe.world.commands.worldconfig; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WorldConfigSeedCommand extends AbstractWorldCommand { + public WorldConfigSeedCommand() { + super("seed", "server.commands.seed.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + context.sendMessage(Message.translation("server.universe.seed.info").param("seed", world.getWorldConfig().getSeed())); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigSetPvpCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigSetPvpCommand.java new file mode 100644 index 0000000..418029d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigSetPvpCommand.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.core.universe.world.commands.worldconfig; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.message.MessageFormat; +import javax.annotation.Nonnull; + +public class WorldConfigSetPvpCommand extends AbstractWorldCommand { + @Nonnull + private final RequiredArg stateArg = this.withRequiredArg("enabled", "server.commands.world.config.setpvp.stateArg.desc", ArgTypes.BOOLEAN); + + public WorldConfigSetPvpCommand() { + super("pvp", "server.commands.setpvp.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + boolean isPvpEnabled = this.stateArg.provided(context) ? this.stateArg.get(context) : !world.getWorldConfig().isPvpEnabled(); + WorldConfig worldConfig = world.getWorldConfig(); + worldConfig.setPvpEnabled(isPvpEnabled); + worldConfig.markChanged(); + context.sendMessage( + Message.translation("server.universe.setpvp.info").param("status", MessageFormat.enabled(isPvpEnabled)).param("worldName", world.getName()) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigSetSpawnCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigSetSpawnCommand.java new file mode 100644 index 0000000..acc5e05 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigSetSpawnCommand.java @@ -0,0 +1,105 @@ +package com.hypixel.hytale.server.core.universe.world.commands.worldconfig; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeDoublePosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import com.hypixel.hytale.server.core.universe.world.spawn.GlobalSpawnProvider; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.text.DecimalFormat; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class WorldConfigSetSpawnCommand extends AbstractWorldCommand { + @Nonnull + private static final DecimalFormat DECIMAL = new DecimalFormat("#.###"); + @Nonnull + private static final Message MESSAGE_COMMANDS_ERROR_PROVIDE_POSITION = Message.translation("server.commands.errors.providePosition"); + @Nonnull + private final OptionalArg positionArg = this.withOptionalArg( + "position", "server.commands.world.config.setspawn.position.desc", ArgTypes.RELATIVE_POSITION + ); + @Nonnull + private final DefaultArg rotationArg = this.withDefaultArg( + "rotation", + "server.commands.world.config.setspawn.rotation.desc", + ArgTypes.ROTATION, + Vector3f.FORWARD, + "server.commands.world.config.setspawn.rotation.default.desc" + ); + + public WorldConfigSetSpawnCommand() { + super("setspawn", "server.commands.world.config.setspawn.desc"); + this.addSubCommand(new WorldConfigSetSpawnDefaultCommand()); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Vector3d position; + if (this.positionArg.provided(context)) { + RelativeDoublePosition relativePosition = this.positionArg.get(context); + position = relativePosition.getRelativePosition(context, world, store); + } else { + if (!context.isPlayer()) { + throw new GeneralCommandException(MESSAGE_COMMANDS_ERROR_PROVIDE_POSITION); + } + + Ref playerRef = context.senderAsPlayerRef(); + if (playerRef == null || !playerRef.isValid()) { + throw new GeneralCommandException(MESSAGE_COMMANDS_ERROR_PROVIDE_POSITION); + } + + TransformComponent transformComponent = store.getComponent(playerRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + position = transformComponent.getPosition().clone(); + } + + Vector3f rotation; + if (this.rotationArg.provided(context)) { + rotation = this.rotationArg.get(context); + } else if (context.isPlayer()) { + Ref playerRefx = context.senderAsPlayerRef(); + if (playerRefx != null && playerRefx.isValid()) { + HeadRotation headRotationComponent = store.getComponent(playerRefx, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + rotation = headRotationComponent.getRotation(); + } else { + rotation = this.rotationArg.get(context); + } + } else { + rotation = this.rotationArg.get(context); + } + + Transform transform = new Transform(position, rotation); + WorldConfig worldConfig = world.getWorldConfig(); + worldConfig.setSpawnProvider(new GlobalSpawnProvider(transform)); + worldConfig.markChanged(); + world.getLogger().at(Level.INFO).log("Set spawn provider to: %s", worldConfig.getSpawnProvider()); + context.sendMessage( + Message.translation("server.universe.setspawn.info") + .param("posX", DECIMAL.format(position.getX())) + .param("posY", DECIMAL.format(position.getY())) + .param("posZ", DECIMAL.format(position.getZ())) + .param("rotX", DECIMAL.format(rotation.getX())) + .param("rotY", DECIMAL.format(rotation.getY())) + .param("rotZ", DECIMAL.format(rotation.getZ())) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigSetSpawnDefaultCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigSetSpawnDefaultCommand.java new file mode 100644 index 0000000..e75a9fb --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldConfigSetSpawnDefaultCommand.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.core.universe.world.commands.worldconfig; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class WorldConfigSetSpawnDefaultCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_UNIVERSE_SET_SPAWN_DEFAULT = Message.translation("server.universe.setspawn.default"); + + public WorldConfigSetSpawnDefaultCommand() { + super("default", "server.commands.world.config.setspawn.default.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + world.getWorldConfig().setSpawnProvider(null); + world.getLogger().at(Level.INFO).log("Set spawn provider to: %s", world.getWorldConfig().getSpawnProvider()); + context.sendMessage(MESSAGE_UNIVERSE_SET_SPAWN_DEFAULT); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldPauseCommand.java b/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldPauseCommand.java new file mode 100644 index 0000000..316b856 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/commands/worldconfig/WorldPauseCommand.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.server.core.universe.world.commands.worldconfig; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Constants; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class WorldPauseCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_PAUSE_TOO_MANY_PLAYERS = Message.translation("server.commands.pause.tooManyPlayers"); + + public WorldPauseCommand() { + super("pause", "server.commands.pause.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + if (world.getPlayerCount() == 1 && Constants.SINGLEPLAYER) { + world.setPaused(!world.isPaused()); + context.sendMessage( + Message.translation("server.commands.pause.updated") + .param("state", Message.translation(world.isPaused() ? "server.commands.pause.paused" : "server.commands.pause.unpaused")) + ); + } else { + context.sendMessage(MESSAGE_COMMANDS_PAUSE_TOO_MANY_PLAYERS); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlockFaceTags.java b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlockFaceTags.java new file mode 100644 index 0000000..5d6cec0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlockFaceTags.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.server.core.universe.world.connectedblocks; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; + +public class ConnectedBlockFaceTags { + public static final BuilderCodec CODEC = BuilderCodec.builder(ConnectedBlockFaceTags.class, ConnectedBlockFaceTags::new) + .append(new KeyedCodec<>("North", new ArrayCodec<>(Codec.STRING, String[]::new), false), (o, tags) -> { + HashSet strings = new HashSet<>(tags.length); + strings.addAll(Arrays.asList(tags)); + o.blockFaceTags.put(Vector3i.NORTH, strings); + }, o -> o.blockFaceTags.containsKey(Vector3i.NORTH) ? o.blockFaceTags.get(Vector3i.NORTH).toArray(String[]::new) : new String[0]) + .add() + .append(new KeyedCodec<>("East", new ArrayCodec<>(Codec.STRING, String[]::new), false), (o, tags) -> { + HashSet strings = new HashSet<>(tags.length); + strings.addAll(Arrays.asList(tags)); + o.blockFaceTags.put(Vector3i.EAST, strings); + }, o -> o.blockFaceTags.containsKey(Vector3i.EAST) ? o.blockFaceTags.get(Vector3i.EAST).toArray(String[]::new) : new String[0]) + .add() + .append(new KeyedCodec<>("South", new ArrayCodec<>(Codec.STRING, String[]::new), false), (o, tags) -> { + HashSet strings = new HashSet<>(tags.length); + strings.addAll(Arrays.asList(tags)); + o.blockFaceTags.put(Vector3i.SOUTH, strings); + }, o -> o.blockFaceTags.containsKey(Vector3i.SOUTH) ? o.blockFaceTags.get(Vector3i.SOUTH).toArray(String[]::new) : new String[0]) + .add() + .append(new KeyedCodec<>("West", new ArrayCodec<>(Codec.STRING, String[]::new), false), (o, tags) -> { + HashSet strings = new HashSet<>(tags.length); + strings.addAll(Arrays.asList(tags)); + o.blockFaceTags.put(Vector3i.WEST, strings); + }, o -> o.blockFaceTags.containsKey(Vector3i.WEST) ? o.blockFaceTags.get(Vector3i.WEST).toArray(String[]::new) : new String[0]) + .add() + .append(new KeyedCodec<>("Up", new ArrayCodec<>(Codec.STRING, String[]::new), false), (o, tags) -> { + HashSet strings = new HashSet<>(tags.length); + strings.addAll(Arrays.asList(tags)); + o.blockFaceTags.put(Vector3i.UP, strings); + }, o -> o.blockFaceTags.containsKey(Vector3i.UP) ? o.blockFaceTags.get(Vector3i.UP).toArray(String[]::new) : new String[0]) + .add() + .append(new KeyedCodec<>("Down", new ArrayCodec<>(Codec.STRING, String[]::new), false), (o, tags) -> { + HashSet strings = new HashSet<>(tags.length); + strings.addAll(Arrays.asList(tags)); + o.blockFaceTags.put(Vector3i.DOWN, strings); + }, o -> o.blockFaceTags.containsKey(Vector3i.DOWN) ? o.blockFaceTags.get(Vector3i.DOWN).toArray(String[]::new) : new String[0]) + .add() + .build(); + public static final ConnectedBlockFaceTags EMPTY = new ConnectedBlockFaceTags(); + @Nonnull + private final Map> blockFaceTags = new Object2ObjectOpenHashMap<>(); + + public ConnectedBlockFaceTags() { + } + + public boolean contains(Vector3i direction, String blockFaceTag) { + return this.blockFaceTags.containsKey(direction) && this.blockFaceTags.get(direction).contains(blockFaceTag); + } + + @Nonnull + public Map> getBlockFaceTags() { + return this.blockFaceTags; + } + + public Set getBlockFaceTags(Vector3i direction) { + return (Set)(this.blockFaceTags.containsKey(direction) ? this.blockFaceTags.get(direction) : Collections.emptySet()); + } + + @Nonnull + public Set getDirections() { + return this.blockFaceTags.keySet(); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlockPatternRule.java b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlockPatternRule.java new file mode 100644 index 0000000..44edc96 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlockPatternRule.java @@ -0,0 +1,144 @@ +package com.hypixel.hytale.server.core.universe.world.connectedblocks; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.set.SetCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BlockTypeListAsset; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ConnectedBlockPatternRule { + public static final BuilderCodec CODEC = BuilderCodec.builder(ConnectedBlockPatternRule.class, ConnectedBlockPatternRule::new) + .append(new KeyedCodec<>("Position", Vector3i.CODEC, false), (o, relativePosition) -> o.relativePosition = relativePosition, o -> o.relativePosition) + .add() + .append( + new KeyedCodec<>("IncludeOrExclude", new EnumCodec<>(ConnectedBlockPatternRule.IncludeOrExclude.class), true), + (o, allowOrExclude) -> o.includeOrExclude = allowOrExclude, + o -> o.includeOrExclude + ) + .add() + .append( + new KeyedCodec<>( + "PlacementNormals", + new ArrayCodec<>(new EnumCodec<>(ConnectedBlockPatternRule.AdjacentSide.class), ConnectedBlockPatternRule.AdjacentSide[]::new), + false + ), + (o, placementNormals) -> o.placementNormals = placementNormals, + o -> o.placementNormals + ) + .add() + .documentation("Queries the face the block was placed against") + .append(new KeyedCodec<>("FaceTags", ConnectedBlockFaceTags.CODEC, false), (o, faceTags) -> o.faceTags = faceTags, o -> o.faceTags) + .add() + .append( + new KeyedCodec<>("Shapes", new SetCodec<>(BlockPattern.BlockEntry.CODEC, HashSet::new, true)), + (o, blockTypesAllowed) -> o.shapeBlockTypeKeys = blockTypesAllowed, + o -> o.shapeBlockTypeKeys + ) + .add() + .append(new KeyedCodec<>("BlockTypes", new ArrayCodec<>(Codec.STRING, String[]::new)), (o, blockTypesAllowed) -> { + if (blockTypesAllowed != null) { + Collections.addAll(o.blockTypes, blockTypesAllowed); + } + }, o -> o.blockTypes != null ? o.blockTypes.toArray(String[]::new) : null) + .add() + .append(new KeyedCodec<>("BlockTypeLists", Codec.STRING_ARRAY), (o, blockTypeListAssetsAllowed) -> { + if (blockTypeListAssetsAllowed != null) { + o.blockTypeListAssets = new BlockTypeListAsset[blockTypeListAssetsAllowed.length]; + + for (int i = 0; i < blockTypeListAssetsAllowed.length; i++) { + o.blockTypeListAssets[i] = BlockTypeListAsset.getAssetMap().getAsset(blockTypeListAssetsAllowed[i]); + if (o.blockTypeListAssets[i] == null) { + System.out.println("BlockTypeListAsset with name: " + blockTypeListAssetsAllowed[i] + " does not exist"); + } + } + } + }, o -> { + if (o.blockTypeListAssets == null) { + return null; + } else { + String[] assetIds = new String[o.blockTypeListAssets.length]; + + for (int i = 0; i < o.blockTypeListAssets.length; i++) { + assetIds[i] = o.blockTypeListAssets[i].getId(); + } + + return assetIds; + } + }) + .add() + .build(); + private ConnectedBlockPatternRule.IncludeOrExclude includeOrExclude; + private Vector3i relativePosition = Vector3i.ZERO; + private final HashSet blockTypes = new HashSet<>(); + @Nullable + private BlockTypeListAsset[] blockTypeListAssets; + private Set shapeBlockTypeKeys = Collections.emptySet(); + private ConnectedBlockFaceTags faceTags = ConnectedBlockFaceTags.EMPTY; + private ConnectedBlockPatternRule.AdjacentSide[] placementNormals; + + public ConnectedBlockPatternRule() { + } + + public Vector3i getRelativePosition() { + return this.relativePosition; + } + + @Nonnull + public HashSet getBlockTypes() { + return this.blockTypes; + } + + @Nonnull + public Set getShapeBlockTypeKeys() { + return this.shapeBlockTypeKeys; + } + + public ConnectedBlockFaceTags getFaceTags() { + return this.faceTags; + } + + @Nullable + public BlockTypeListAsset[] getBlockTypeListAssets() { + return this.blockTypeListAssets; + } + + public ConnectedBlockPatternRule.AdjacentSide[] getPlacementNormals() { + return this.placementNormals; + } + + public boolean isInclude() { + return this.includeOrExclude == ConnectedBlockPatternRule.IncludeOrExclude.INCLUDE; + } + + public static enum AdjacentSide { + Up(Vector3i.UP), + Down(Vector3i.DOWN), + North(Vector3i.NORTH), + East(Vector3i.EAST), + South(Vector3i.SOUTH), + West(Vector3i.WEST); + + public final Vector3i relativePosition; + + private AdjacentSide(Vector3i side) { + this.relativePosition = side; + } + } + + public static enum IncludeOrExclude { + INCLUDE, + EXCLUDE; + + private IncludeOrExclude() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlockRuleSet.java b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlockRuleSet.java new file mode 100644 index 0000000..6b99b66 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlockRuleSet.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.core.universe.world.connectedblocks; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.Optional; +import javax.annotation.Nullable; + +public abstract class ConnectedBlockRuleSet { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + + public ConnectedBlockRuleSet() { + } + + public abstract boolean onlyUpdateOnPlacement(); + + public abstract Optional getConnectedBlockType( + World var1, Vector3i var2, BlockType var3, int var4, Vector3i var5, boolean var6 + ); + + public void updateCachedBlockTypes(BlockType blockType, BlockTypeAssetMap assetMap) { + } + + @Nullable + public com.hypixel.hytale.protocol.ConnectedBlockRuleSet toPacket(BlockTypeAssetMap assetMap) { + return null; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlockShape.java b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlockShape.java new file mode 100644 index 0000000..508a2ca --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlockShape.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.universe.world.connectedblocks; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; + +public class ConnectedBlockShape { + public static final BuilderCodec CODEC = BuilderCodec.builder(ConnectedBlockShape.class, ConnectedBlockShape::new) + .append( + new KeyedCodec<>("PatternsToMatchAnyOf", new ArrayCodec<>(CustomTemplateConnectedBlockPattern.CODEC, CustomTemplateConnectedBlockPattern[]::new), true), + (o, patternsToMatchAnyOf) -> o.patternsToMatchAnyOf = patternsToMatchAnyOf, + o -> o.patternsToMatchAnyOf + ) + .add() + .append(new KeyedCodec<>("FaceTags", ConnectedBlockFaceTags.CODEC, false), (o, faceTags) -> o.faceTags = faceTags, o -> o.faceTags) + .add() + .build(); + private CustomTemplateConnectedBlockPattern[] patternsToMatchAnyOf; + private ConnectedBlockFaceTags faceTags; + + public ConnectedBlockShape() { + } + + public CustomTemplateConnectedBlockPattern[] getPatternsToMatchAnyOf() { + return this.patternsToMatchAnyOf; + } + + public ConnectedBlockFaceTags getFaceTags() { + return this.faceTags; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlocksModule.java b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlocksModule.java new file mode 100644 index 0000000..001ac61 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlocksModule.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.server.core.universe.world.connectedblocks; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.world.connectedblocks.builtin.RoofConnectedBlockRuleSet; +import com.hypixel.hytale.server.core.universe.world.connectedblocks.builtin.StairConnectedBlockRuleSet; +import javax.annotation.Nonnull; + +public class ConnectedBlocksModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(ConnectedBlocksModule.class) + .depends(EntityModule.class) + .depends(InteractionModule.class) + .build(); + private static ConnectedBlocksModule instance; + + public static ConnectedBlocksModule get() { + return instance; + } + + public ConnectedBlocksModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + CustomConnectedBlockTemplateAsset.class, new DefaultAssetMap() + ) + .setPath("Item/CustomConnectedBlockTemplates")) + .setKeyFunction(CustomConnectedBlockTemplateAsset::getId)) + .setCodec(CustomConnectedBlockTemplateAsset.CODEC)) + .build() + ); + this.getEventRegistry().register(LoadedAssetsEvent.class, BlockType.class, ConnectedBlocksModule::onBlockTypesChanged); + CustomTemplateConnectedBlockPattern.CODEC.register("Custom", CustomConnectedBlockPattern.class, CustomConnectedBlockPattern.CODEC); + ConnectedBlockRuleSet.CODEC.register("CustomTemplate", CustomTemplateConnectedBlockRuleSet.class, CustomTemplateConnectedBlockRuleSet.CODEC); + ConnectedBlockRuleSet.CODEC.register("Stair", StairConnectedBlockRuleSet.class, StairConnectedBlockRuleSet.CODEC); + ConnectedBlockRuleSet.CODEC.register("Roof", RoofConnectedBlockRuleSet.class, RoofConnectedBlockRuleSet.CODEC); + } + + private static void onBlockTypesChanged(@Nonnull LoadedAssetsEvent> event) { + for (BlockType blockType : event.getLoadedAssets().values()) { + ConnectedBlockRuleSet ruleSet = blockType.getConnectedBlockRuleSet(); + if (ruleSet != null) { + ruleSet.updateCachedBlockTypes(blockType, (BlockTypeAssetMap)event.getAssetMap()); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlocksUtil.java b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlocksUtil.java new file mode 100644 index 0000000..394665e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/ConnectedBlocksUtil.java @@ -0,0 +1,272 @@ +package com.hypixel.hytale.server.core.universe.world.connectedblocks; + +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectIntPair; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.ArrayDeque; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class ConnectedBlocksUtil { + private static final int MAX_UPDATE_DEPTH = 3; + + public ConnectedBlocksUtil() { + } + + public static void setConnectedBlockAndNotifyNeighbors( + int blockTypeId, + @Nonnull RotationTuple blockTypeRotation, + @Nonnull Vector3i placementNormal, + @Nonnull Vector3i blockPosition, + @Nonnull WorldChunk worldChunkComponent, + @Nonnull BlockChunk blockChunkComponent + ) { + Vector3i coordinate = new Vector3i(blockPosition); + BlockType blockType = BlockType.getAssetMap().getAsset(blockTypeId); + if (blockType != null) { + BlockSection sectionAtY = blockChunkComponent.getSectionAtBlockY(blockPosition.y); + int filler = sectionAtY.getFiller(blockPosition.x, blockPosition.y, blockPosition.z); + int settings = 132; + if (blockType.getConnectedBlockRuleSet() != null && filler == 0) { + int rotationIndex = blockTypeRotation.index(); + Optional foundPattern = getDesiredConnectedBlockType( + worldChunkComponent.getWorld(), coordinate, blockType, rotationIndex, placementNormal, true + ); + if (foundPattern.isPresent() && (!foundPattern.get().blockTypeKey().equals(blockType.getId()) || foundPattern.get().rotationIndex != rotationIndex) + ) + { + ConnectedBlocksUtil.ConnectedBlockResult result = foundPattern.get(); + int id = BlockType.getAssetMap().getIndex(result.blockTypeKey()); + int rotation = result.rotationIndex(); + worldChunkComponent.setBlock(coordinate.x, coordinate.y, coordinate.z, id, BlockType.getAssetMap().getAsset(id), rotation, 0, settings); + } + } + + updateNeighborsWithDepth(worldChunkComponent, coordinate, placementNormal, settings); + } + } + + private static void updateNeighborsWithDepth( + @Nonnull WorldChunk worldChunkComponent, @Nonnull Vector3i startCoordinate, @Nonnull Vector3i placementNormal, int settings + ) { + record QueueEntry(Vector3i coordinate, int depth) { + } + + Queue queue = new ArrayDeque<>(); + Set visited = new ObjectOpenHashSet<>(); + queue.add(new QueueEntry(new Vector3i(startCoordinate), 0)); + + label67: + while (!queue.isEmpty()) { + QueueEntry entry = queue.poll(); + Vector3i coordinate = entry.coordinate; + int depth = entry.depth; + Map desiredChanges = new Object2ObjectOpenHashMap<>(); + notifyNeighborsAndCollectChanges(worldChunkComponent.getWorld(), coordinate, desiredChanges, placementNormal); + Iterator var10 = desiredChanges.entrySet().iterator(); + + while (true) { + Vector3i location; + ConnectedBlocksUtil.ConnectedBlockResult connectedBlockResult; + WorldChunk newWorldChunk; + while (true) { + if (!var10.hasNext()) { + continue label67; + } + + Entry result = (Entry)var10.next(); + location = result.getKey(); + connectedBlockResult = result.getValue(); + if (visited.add(location.clone()) && (location.x != coordinate.x || location.y != coordinate.y || location.z != coordinate.z)) { + newWorldChunk = worldChunkComponent; + long chunkIndex = ChunkUtil.indexChunkFromBlock(location.x, location.z); + if (chunkIndex == worldChunkComponent.getIndex()) { + break; + } + + newWorldChunk = worldChunkComponent.getWorld().getChunkIfLoaded(chunkIndex); + if (newWorldChunk != null) { + break; + } + } + } + + int blockId = BlockType.getAssetMap().getIndex(connectedBlockResult.blockTypeKey()); + BlockType block = BlockType.getAssetMap().getAsset(blockId); + newWorldChunk.setBlock(location.x, location.y, location.z, blockId, block, connectedBlockResult.rotationIndex(), 0, settings); + + for (Entry> additionalEntry : connectedBlockResult.getAdditionalConnectedBlocks().entrySet()) { + Vector3i offset = additionalEntry.getKey(); + ObjectIntPair blockData = additionalEntry.getValue(); + Vector3i additionalLocation = new Vector3i(location).add(offset); + WorldChunk additionalChunk = newWorldChunk; + long additionalChunkIndex = ChunkUtil.indexChunkFromBlock(additionalLocation.x, additionalLocation.z); + if (additionalChunkIndex != newWorldChunk.getIndex()) { + additionalChunk = worldChunkComponent.getWorld().getChunkIfLoaded(additionalChunkIndex); + if (additionalChunk == null) { + continue; + } + } + + int additionalBlockId = BlockType.getAssetMap().getIndex(blockData.first()); + BlockType additionalBlock = BlockType.getAssetMap().getAsset(additionalBlockId); + if (additionalBlock != null) { + additionalChunk.setBlock( + additionalLocation.x, additionalLocation.y, additionalLocation.z, additionalBlockId, additionalBlock, blockData.rightInt(), 0, settings + ); + } + } + + if (depth + 1 < 3) { + queue.add(new QueueEntry(location.clone(), depth + 1)); + } + } + } + } + + public static void notifyNeighborsAndCollectChanges( + @Nonnull World world, @Nonnull Vector3i origin, @Nonnull Map desiredChanges, Vector3i placementNormal + ) { + Vector3i coordinate = origin.clone(); + long chunkIndex = ChunkUtil.indexChunkFromBlock(origin.x, origin.z); + WorldChunk chunk = world.getChunkIfLoaded(chunkIndex); + + for (int x1 = -1; x1 <= 1; x1++) { + for (int z1 = -1; z1 <= 1; z1++) { + for (int y1 = -1; y1 <= 1; y1++) { + if (x1 != 0 || y1 != 0 || z1 != 0) { + coordinate.assign(origin).add(x1, y1, z1); + if (coordinate.y >= 0 && coordinate.y < 320 && !desiredChanges.containsKey(coordinate)) { + long neighborChunkIndex = ChunkUtil.indexChunkFromBlock(coordinate.x, coordinate.z); + if (neighborChunkIndex != chunkIndex) { + chunkIndex = neighborChunkIndex; + chunk = world.getChunkIfLoaded(neighborChunkIndex); + } + + if (chunk != null) { + BlockChunk blockChunk = chunk.getBlockChunk(); + if (blockChunk != null) { + BlockSection blockSection = blockChunk.getSectionAtBlockY(coordinate.y); + if (blockSection != null) { + int neighborBlockId = blockSection.get(coordinate.x, coordinate.y, coordinate.z); + BlockType neighborBlockType = BlockType.getAssetMap().getAsset(neighborBlockId); + if (neighborBlockType != null) { + ConnectedBlockRuleSet ruleSet = neighborBlockType.getConnectedBlockRuleSet(); + if (ruleSet != null && !ruleSet.onlyUpdateOnPlacement()) { + int filler = blockSection.getFiller(coordinate.x, coordinate.y, coordinate.z); + int existingRotation = blockSection.getRotationIndex(coordinate.x, coordinate.y, coordinate.z); + if (filler != 0) { + int originX = coordinate.x - FillerBlockUtil.unpackX(filler); + int originY = coordinate.y - FillerBlockUtil.unpackY(filler); + int originZ = coordinate.z - FillerBlockUtil.unpackZ(filler); + coordinate.assign(originX, originY, originZ); + } + + Optional output = getDesiredConnectedBlockType( + world, coordinate, neighborBlockType, existingRotation, placementNormal, false + ); + if (output.isPresent() + && (!neighborBlockType.getId().equals(output.get().blockTypeKey()) || output.get().rotationIndex != existingRotation)) { + desiredChanges.put(coordinate.clone(), output.get()); + } + } + } + } + } + } + } + } + } + } + } + } + + @Nonnull + public static Optional getDesiredConnectedBlockType( + @Nonnull World world, + @Nonnull Vector3i coordinate, + @Nonnull BlockType currentBlockType, + int currentRotation, + @Nonnull Vector3i placementNormal, + boolean isPlacement + ) { + ConnectedBlockRuleSet ruleSet = currentBlockType.getConnectedBlockRuleSet(); + return ruleSet == null + ? Optional.empty() + : ruleSet.getConnectedBlockType(world, coordinate, currentBlockType, currentRotation, placementNormal, isPlacement); + } + + public static final class ConnectedBlockResult { + private final String blockTypeKey; + private final int rotationIndex; + private final Map> additionalConnectedBlocks = new Object2ObjectOpenHashMap<>(); + + public ConnectedBlockResult(@Nonnull String blockTypeKey, int rotationIndex) { + this.blockTypeKey = blockTypeKey; + this.rotationIndex = rotationIndex; + } + + @Nonnull + public String blockTypeKey() { + return this.blockTypeKey; + } + + public int rotationIndex() { + return this.rotationIndex; + } + + @Nonnull + public Map> getAdditionalConnectedBlocks() { + return this.additionalConnectedBlocks; + } + + public void addAdditionalBlock(@Nonnull Vector3i offset, @Nonnull String blockTypeKey, int rotationIndex) { + this.additionalConnectedBlocks.put(offset, ObjectIntPair.of(blockTypeKey, rotationIndex)); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (obj != null && obj.getClass() == this.getClass()) { + ConnectedBlocksUtil.ConnectedBlockResult that = (ConnectedBlocksUtil.ConnectedBlockResult)obj; + return Objects.equals(this.blockTypeKey, that.blockTypeKey) + && this.rotationIndex == that.rotationIndex + && Objects.equals(this.additionalConnectedBlocks, that.additionalConnectedBlocks); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.blockTypeKey, this.rotationIndex, this.additionalConnectedBlocks); + } + + @Override + public String toString() { + return "ConnectedBlockResult[blockTypeKey=" + + this.blockTypeKey + + ", rotationIndex=" + + this.rotationIndex + + ", additionalConnectedBlocks=" + + this.additionalConnectedBlocks + + "]"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/CustomConnectedBlockPattern.java b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/CustomConnectedBlockPattern.java new file mode 100644 index 0000000..bfbae2f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/CustomConnectedBlockPattern.java @@ -0,0 +1,309 @@ +package com.hypixel.hytale.server.core.universe.world.connectedblocks; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.codecs.simple.BooleanCodec; +import com.hypixel.hytale.math.Axis; +import com.hypixel.hytale.math.block.BlockUtil; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFlipType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.asset.type.buildertool.config.BlockTypeListAsset; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import it.unimi.dsi.fastutil.Pair; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class CustomConnectedBlockPattern extends CustomTemplateConnectedBlockPattern { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CustomConnectedBlockPattern.class, CustomConnectedBlockPattern::new + ) + .append( + new KeyedCodec<>("TransformRulesToOrientation", Codec.BOOLEAN, false), + (o, transformRulesToPlacedOrientation) -> o.transformRulesToOrientation = transformRulesToPlacedOrientation, + o -> o.transformRulesToOrientation + ) + .documentation("If the rules should be transformed to the current orientation of the block.") + .add() + .append( + new KeyedCodec<>("YawToApplyAddReplacedBlockType", Rotation.CODEC, false), + (o, yawToApplyAddReplacedBlockType) -> o.yawToApplyAddReplacedBlockType = yawToApplyAddReplacedBlockType, + o -> o.yawToApplyAddReplacedBlockType + ) + .documentation( + "Apply an additional Yaw to the resulting BlockType represented by this shape. This allows your replacement to be offset from your original placement" + ) + .add() + .append( + new KeyedCodec<>("RequireFaceTagsMatchingRoll", Codec.BOOLEAN, false), + (o, requireFaceTagsMatchingRoll) -> o.requireFaceTagsMatchingRoll = requireFaceTagsMatchingRoll, + o -> o.requireFaceTagsMatchingRoll + ) + .documentation("Adds Roll comparison to face tag matching in patterns below") + .add() + .append( + new KeyedCodec<>("AllowedPatternTransformations", PatternRotationDefinition.CODEC, false), + (o, patternRotations) -> o.patternRotationDefinition = patternRotations, + o -> o.patternRotationDefinition + ) + .documentation( + "Will create additional generated patterns that are variants of this pattern, but rotated/mirrored/flipped to achieve different results. A common example of this is the Fence, which should its resulting shape based on the rotation of its pattern (fence corner rotates depending on which two sides have the corner fence shape)" + ) + .add() + .append( + new KeyedCodec<>("RulesToMatch", new ArrayCodec<>(ConnectedBlockPatternRule.CODEC, ConnectedBlockPatternRule[]::new), true), + (o, matchingPatterns) -> o.rulesToMatch = matchingPatterns, + o -> o.rulesToMatch + ) + .documentation("All rules must match in order for the pattern to match") + .add() + .append( + new KeyedCodec<>("OnlyOnPlacement", new BooleanCodec(), false), (o, onlyOnPlacement) -> o.onlyOnPlacement = onlyOnPlacement, o -> o.onlyOnPlacement + ) + .documentation("If true, this pattern will only be checked when the block is first placed.") + .add() + .append(new KeyedCodec<>("OnlyOnUpdate", new BooleanCodec(), false), (o, onlyOnUpdate) -> o.onlyOnUpdate = onlyOnUpdate, o -> o.onlyOnUpdate) + .documentation("If true, this pattern will only be checked when the block is updated by neighboring block changes.") + .add() + .build(); + @Nonnull + private static final Random random = new Random(); + private boolean transformRulesToOrientation = true; + private PatternRotationDefinition patternRotationDefinition = PatternRotationDefinition.DEFAULT; + private ConnectedBlockPatternRule[] rulesToMatch; + private Rotation yawToApplyAddReplacedBlockType; + private boolean requireFaceTagsMatchingRoll; + private boolean onlyOnUpdate; + private boolean onlyOnPlacement; + + public CustomConnectedBlockPattern() { + } + + private static boolean checkPatternRuleAgainstBlockType( + @Nonnull CustomTemplateConnectedBlockRuleSet placedRuleset, + @Nonnull CustomConnectedBlockTemplateAsset template, + @Nonnull String block, + @Nonnull ConnectedBlockPatternRule rule, + @Nonnull String blockToTest, + RotationTuple rotationToCheckUnrotated, + int fillerToCheckUnrotated + ) { + if (!rule.getFaceTags().getDirections().isEmpty()) { + BlockType checkingBlockType = BlockType.getAssetMap().getAsset(blockToTest); + if (!(checkingBlockType.getConnectedBlockRuleSet() instanceof CustomTemplateConnectedBlockRuleSet checkingConnectedBlockRuleSet)) { + return !rule.isInclude(); + } + + int blockTypeListAsset = BlockType.getAssetMap().getIndex(blockToTest); + Set shapeNames = checkingConnectedBlockRuleSet.getShapesForBlockType(blockTypeListAsset); + CustomConnectedBlockTemplateAsset checkingTemplateAsset = checkingConnectedBlockRuleSet.getShapeTemplateAsset(); + if (checkingTemplateAsset == null) { + return !rule.isInclude(); + } + + for (String shapeName : shapeNames) { + if (template.connectsToOtherMaterials + || placedRuleset.getShapeNameToBlockPatternMap().equals(checkingConnectedBlockRuleSet.getShapeNameToBlockPatternMap())) { + ConnectedBlockShape blockToCheckConnectedBlockShape = checkingTemplateAsset.connectedBlockShapes.get(shapeName); + Map> ruleFaceTags = rule.getFaceTags().getBlockFaceTags(); + + for (Entry> ruleFaceTag : ruleFaceTags.entrySet()) { + Vector3i adjustedDirectionOfPattern = Rotation.rotate( + ruleFaceTag.getKey().clone(), Rotation.None.subtract(rotationToCheckUnrotated.yaw()), Rotation.None + ); + + for (String faceTag : ruleFaceTag.getValue()) { + boolean containsFaceTag = blockToCheckConnectedBlockShape.getFaceTags() != null + && blockToCheckConnectedBlockShape.getFaceTags().contains(adjustedDirectionOfPattern, faceTag); + if (containsFaceTag) { + return rule.isInclude(); + } + } + } + } + } + } + + if (!rule.getShapeBlockTypeKeys().isEmpty()) { + BlockType checkingBlockTypex = BlockType.getAssetMap().getAsset(blockToTest); + if (!(checkingBlockTypex.getConnectedBlockRuleSet() instanceof CustomTemplateConnectedBlockRuleSet checkingConnectedBlockRuleSet)) { + return !rule.isInclude(); + } + + int var29 = BlockType.getAssetMap().getIndex(blockToTest); + + for (String shapeNamex : checkingConnectedBlockRuleSet.getShapesForBlockType(var29)) { + if (( + template.connectsToOtherMaterials + || placedRuleset.getShapeNameToBlockPatternMap().equals(checkingConnectedBlockRuleSet.getShapeNameToBlockPatternMap()) + ) + && rule.getShapeBlockTypeKeys().contains(new BlockPattern.BlockEntry(shapeNamex, rotationToCheckUnrotated.index(), fillerToCheckUnrotated))) { + return rule.isInclude(); + } + } + } + + if (!rule.getBlockTypes().isEmpty() && rule.getBlockTypes().contains(blockToTest)) { + return rule.isInclude(); + } else { + if (rule.getBlockTypeListAssets() != null) { + for (BlockTypeListAsset blockTypeListAsset : rule.getBlockTypeListAssets()) { + if (blockTypeListAsset.getBlockTypeKeys().contains(blockToTest)) { + return rule.isInclude(); + } + } + } + + return !rule.isInclude(); + } + } + + @Nonnull + @Override + public Optional getConnectedBlockTypeKey( + String shapeName, + @Nonnull World world, + @Nonnull Vector3i coordinate, + @Nonnull CustomTemplateConnectedBlockRuleSet connectedBlockRuleset, + @Nonnull BlockType blockType, + int rotation, + @Nonnull Vector3i placementNormal, + boolean isPlacement + ) { + if ((!isPlacement || !this.onlyOnUpdate) && (isPlacement || !this.onlyOnPlacement)) { + CustomConnectedBlockTemplateAsset shapeTemplate = connectedBlockRuleset.getShapeTemplateAsset(); + if (shapeTemplate == null) { + return Optional.empty(); + } else { + Vector3i coordinateToTest = new Vector3i(); + Rotation3D totalRotation = new Rotation3D(Rotation.None, Rotation.None, Rotation.None); + Rotation3D tempRotation = new Rotation3D(Rotation.None, Rotation.None, Rotation.None); + List> rotations = this.patternRotationDefinition.getRotations(); + + label99: + for (int i = 0; i < rotations.size(); i++) { + Pair patternTransform = rotations.get(i); + totalRotation.assign(patternTransform.first(), Rotation.None, Rotation.None); + if (this.transformRulesToOrientation) { + RotationTuple rotationTuple = RotationTuple.get(rotation); + tempRotation.assign(rotationTuple.yaw(), rotationTuple.pitch(), rotationTuple.roll()); + totalRotation.add(tempRotation); + } + + label96: + for (ConnectedBlockPatternRule ruleToMatch : this.rulesToMatch) { + coordinateToTest.assign(ruleToMatch.getRelativePosition()); + switch ((PatternRotationDefinition.MirrorAxis)patternTransform.second()) { + case X: + coordinateToTest.setX(-coordinateToTest.getX()); + break; + case Z: + coordinateToTest.setZ(-coordinateToTest.getZ()); + } + + if (ruleToMatch.getPlacementNormals() != null) { + for (ConnectedBlockPatternRule.AdjacentSide normal : ruleToMatch.getPlacementNormals()) { + if (normal.relativePosition.equals(placementNormal)) { + continue label96; + } + } + + return Optional.empty(); + } else { + coordinateToTest = Rotation.rotate(coordinateToTest, totalRotation.rotationYaw, totalRotation.rotationPitch, totalRotation.rotationRoll); + coordinateToTest.add(coordinate); + WorldChunk chunkIfLoaded = world.getChunkIfLoaded(ChunkUtil.indexChunkFromBlock(coordinateToTest.x, coordinateToTest.z)); + if (chunkIfLoaded == null) { + return Optional.empty(); + } + + String blockToCheckUnrotated = chunkIfLoaded.getBlockType(coordinateToTest).getId(); + RotationTuple rotationToCheckUnrotated = chunkIfLoaded.getRotation(coordinateToTest.x, coordinateToTest.y, coordinateToTest.z); + tempRotation.assign(rotationToCheckUnrotated); + tempRotation.subtract(totalRotation); + int fillerToCheckUnrotated = chunkIfLoaded.getFiller(coordinateToTest.x, coordinateToTest.y, coordinateToTest.z); + fillerToCheckUnrotated = tempRotation.rotationPitch.subtract(rotationToCheckUnrotated.pitch()).rotateX(fillerToCheckUnrotated); + fillerToCheckUnrotated = tempRotation.rotationYaw.subtract(rotationToCheckUnrotated.yaw()).rotateY(fillerToCheckUnrotated); + fillerToCheckUnrotated = tempRotation.rotationRoll.subtract(rotationToCheckUnrotated.roll()).rotateY(fillerToCheckUnrotated); + rotationToCheckUnrotated = RotationTuple.of(tempRotation.rotationYaw, tempRotation.rotationPitch, tempRotation.rotationRoll); + BlockType blockTypeToCheckUnrotated = BlockType.getAssetMap().getAsset(blockToCheckUnrotated); + if (patternTransform.second() != PatternRotationDefinition.MirrorAxis.NONE) { + Rotation newYawMirrored = blockTypeToCheckUnrotated.getFlipType() + .flipYaw(rotationToCheckUnrotated.yaw(), patternTransform.second() == PatternRotationDefinition.MirrorAxis.X ? Axis.X : Axis.Z); + fillerToCheckUnrotated = newYawMirrored.subtract(rotationToCheckUnrotated.yaw()).rotateY(fillerToCheckUnrotated); + rotationToCheckUnrotated = RotationTuple.of(newYawMirrored, rotationToCheckUnrotated.pitch(), rotationToCheckUnrotated.roll()); + } + + boolean patternMatches = checkPatternRuleAgainstBlockType( + connectedBlockRuleset, + shapeTemplate, + blockType.getId(), + ruleToMatch, + blockToCheckUnrotated, + rotationToCheckUnrotated, + fillerToCheckUnrotated + ); + if (!patternMatches) { + continue label99; + } + } + } + + BlockPattern resultBlockPattern = connectedBlockRuleset.getShapeNameToBlockPatternMap().get(shapeName); + if (resultBlockPattern == null) { + return Optional.empty(); + } + + random.setSeed(BlockUtil.pack(coordinate)); + BlockPattern.BlockEntry resultBlockTypeKey = resultBlockPattern.nextBlockTypeKey(random); + if (resultBlockTypeKey == null) { + return Optional.empty(); + } + + BlockType baseBlockTypeForFlip = BlockType.getAssetMap().getAsset(resultBlockTypeKey.blockTypeKey()); + if (baseBlockTypeForFlip == null) { + return Optional.empty(); + } + + BlockFlipType flipType = baseBlockTypeForFlip.getFlipType(); + RotationTuple resultRotation = RotationTuple.get(resultBlockTypeKey.rotation()); + resultRotation = RotationTuple.of(resultRotation.yaw().add(this.yawToApplyAddReplacedBlockType), resultRotation.pitch(), resultRotation.roll()); + if (patternTransform.second() != PatternRotationDefinition.MirrorAxis.NONE) { + Rotation newYawMirrored = flipType.flipYaw( + resultRotation.yaw(), patternTransform.second() == PatternRotationDefinition.MirrorAxis.X ? Axis.X : Axis.Z + ); + resultRotation = RotationTuple.of(newYawMirrored, resultRotation.pitch(), resultRotation.roll()); + } + + resultRotation = RotationTuple.of( + resultRotation.yaw().add(totalRotation.rotationYaw), + resultRotation.pitch().add(totalRotation.rotationPitch), + resultRotation.roll().add(totalRotation.rotationRoll) + ); + if (resultRotation.pitch().equals(Rotation.OneEighty) && flipType.equals(BlockFlipType.ORTHOGONAL)) { + resultRotation = RotationTuple.of(resultRotation.yaw().subtract(Rotation.Ninety), resultRotation.pitch(), resultRotation.roll()); + } + + return Optional.of(new ConnectedBlocksUtil.ConnectedBlockResult(resultBlockTypeKey.blockTypeKey(), resultRotation.index())); + } + + return Optional.empty(); + } + } else { + return Optional.empty(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/CustomConnectedBlockTemplateAsset.java b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/CustomConnectedBlockTemplateAsset.java new file mode 100644 index 0000000..eeb3508 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/CustomConnectedBlockTemplateAsset.java @@ -0,0 +1,134 @@ +package com.hypixel.hytale.server.core.universe.world.connectedblocks; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Map.Entry; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; + +public class CustomConnectedBlockTemplateAsset implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + CustomConnectedBlockTemplateAsset.class, + CustomConnectedBlockTemplateAsset::new, + Codec.STRING, + (builder, id) -> builder.id = id, + builder -> builder.id, + (builder, data) -> builder.data = data, + builder -> builder.data + ) + .append( + new KeyedCodec<>("DontUpdateAfterInitialPlacement", Codec.BOOLEAN, false), + (o, dontUpdateAfterInitialPlacement) -> o.dontUpdateAfterInitialPlacement = dontUpdateAfterInitialPlacement, + o -> o.dontUpdateAfterInitialPlacement + ) + .documentation( + "Default to false. When true, will not update the connected block after initial placement. Neighboring block updates won't affect this block when true." + ) + .add() + .append( + new KeyedCodec<>("ConnectsToOtherMaterials", Codec.BOOLEAN, false), + (o, connectsToOtherMaterials) -> o.connectsToOtherMaterials = connectsToOtherMaterials, + o -> o.connectsToOtherMaterials + ) + .documentation( + "Defaults to true. If true, the material will connect to other materials of different block type sets, if false, the material will only connect to its own block types within the material" + ) + .add() + .append(new KeyedCodec<>("DefaultShape", Codec.STRING, false), (o, defaultShapeName) -> o.defaultShapeName = defaultShapeName, o -> o.defaultShapeName) + .add() + .append( + new KeyedCodec<>("Shapes", new MapCodec<>(ConnectedBlockShape.CODEC, HashMap::new), true), + (o, connectedBlockShapes) -> o.connectedBlockShapes = connectedBlockShapes, + o -> o.connectedBlockShapes + ) + .add() + .build(); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(CustomConnectedBlockTemplateAsset::getAssetStore)); + private static AssetStore> ASSET_STORE; + private String id; + private AssetExtraInfo.Data data; + protected boolean connectsToOtherMaterials = true; + private boolean dontUpdateAfterInitialPlacement; + private String defaultShapeName; + protected Map connectedBlockShapes; + + public CustomConnectedBlockTemplateAsset() { + } + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(CustomConnectedBlockTemplateAsset.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + @Nonnull + public Optional getConnectedBlockType( + World world, + Vector3i coordinate, + CustomTemplateConnectedBlockRuleSet ruleSet, + BlockType blockType, + int rotation, + Vector3i placementNormal, + boolean useDefaultShapeIfNoMatch, + boolean isPlacement + ) { + for (Entry entry : this.connectedBlockShapes.entrySet()) { + ConnectedBlockShape connectedBlockShape = entry.getValue(); + if (connectedBlockShape != null) { + CustomTemplateConnectedBlockPattern[] patterns = connectedBlockShape.getPatternsToMatchAnyOf(); + if (patterns != null) { + for (CustomTemplateConnectedBlockPattern connectedBlockPattern : patterns) { + Optional blockRotationIfMatchedOptional = connectedBlockPattern.getConnectedBlockTypeKey( + entry.getKey(), world, coordinate, ruleSet, blockType, rotation, placementNormal, isPlacement + ); + if (!blockRotationIfMatchedOptional.isEmpty()) { + return blockRotationIfMatchedOptional; + } + } + } + } + } + + if (useDefaultShapeIfNoMatch) { + BlockPattern defaultShapeBlockPattern = ruleSet.getShapeNameToBlockPatternMap().get(this.defaultShapeName); + if (defaultShapeBlockPattern == null) { + return Optional.empty(); + } else { + BlockPattern.BlockEntry defaultBlock = defaultShapeBlockPattern.nextBlockTypeKey(ThreadLocalRandom.current()); + return Optional.of(new ConnectedBlocksUtil.ConnectedBlockResult(defaultBlock.blockTypeKey(), rotation)); + } + } else { + return Optional.empty(); + } + } + + public boolean isDontUpdateAfterInitialPlacement() { + return this.dontUpdateAfterInitialPlacement; + } + + public String getId() { + return this.id; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/CustomTemplateConnectedBlockPattern.java b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/CustomTemplateConnectedBlockPattern.java new file mode 100644 index 0000000..16567f8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/CustomTemplateConnectedBlockPattern.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.core.universe.world.connectedblocks; + +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.Optional; +import javax.annotation.Nonnull; + +public abstract class CustomTemplateConnectedBlockPattern { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + + public CustomTemplateConnectedBlockPattern() { + } + + public abstract Optional getConnectedBlockTypeKey( + String var1, + @Nonnull World var2, + @Nonnull Vector3i var3, + @Nonnull CustomTemplateConnectedBlockRuleSet var4, + @Nonnull BlockType var5, + int var6, + @Nonnull Vector3i var7, + boolean var8 + ); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/CustomTemplateConnectedBlockRuleSet.java b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/CustomTemplateConnectedBlockRuleSet.java new file mode 100644 index 0000000..d8bdf39 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/CustomTemplateConnectedBlockRuleSet.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.server.core.universe.world.connectedblocks; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern; +import com.hypixel.hytale.server.core.universe.world.World; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.Map.Entry; +import javax.annotation.Nullable; + +public class CustomTemplateConnectedBlockRuleSet extends ConnectedBlockRuleSet { + public static final BuilderCodec CODEC = BuilderCodec.builder( + CustomTemplateConnectedBlockRuleSet.class, CustomTemplateConnectedBlockRuleSet::new + ) + .append( + new KeyedCodec<>("TemplateShapeAssetId", Codec.STRING), + (ruleSet, shapeAssetId) -> ruleSet.shapeAssetId = shapeAssetId, + ruleSet -> ruleSet.shapeAssetId + ) + .addValidator(CustomConnectedBlockTemplateAsset.VALIDATOR_CACHE.getValidator()) + .documentation("The name of a ConnectedBlockTemplateAsset asset") + .add() + .append( + new KeyedCodec<>("TemplateShapeBlockPatterns", new MapCodec<>(BlockPattern.CODEC, HashMap::new), true), + (material, shapeNameToBlockPatternMap) -> material.shapeNameToBlockPatternMap = shapeNameToBlockPatternMap, + material -> material.shapeNameToBlockPatternMap + ) + .documentation("You must specify all shapes as a BlockPattern. The shapes are as outlined in the keys of the ShapeTemplateAsset's map.") + .add() + .build(); + private String shapeAssetId; + private Map shapeNameToBlockPatternMap = new Object2ObjectOpenHashMap<>(); + private final Int2ObjectMap> shapesPerBlockType = new Int2ObjectOpenHashMap<>(); + + public CustomTemplateConnectedBlockRuleSet() { + } + + public Map getShapeNameToBlockPatternMap() { + return this.shapeNameToBlockPatternMap; + } + + @Override + public void updateCachedBlockTypes(BlockType blockType, BlockTypeAssetMap assetMap) { + super.updateCachedBlockTypes(blockType, assetMap); + + for (Entry entry : this.shapeNameToBlockPatternMap.entrySet()) { + String name = entry.getKey(); + BlockPattern blockPattern = entry.getValue(); + Integer[] var7 = blockPattern.getResolvedKeys(); + int var8 = var7.length; + + for (int var9 = 0; var9 < var8; var9++) { + int resolvedKey = var7[var9]; + Set shapes = this.shapesPerBlockType.computeIfAbsent(resolvedKey, k -> new ObjectOpenHashSet<>()); + shapes.add(name); + } + } + } + + @Nullable + public Set getShapesForBlockType(int blockTypeKey) { + return this.shapesPerBlockType.getOrDefault(blockTypeKey, Set.of()); + } + + @Nullable + public CustomConnectedBlockTemplateAsset getShapeTemplateAsset() { + return CustomConnectedBlockTemplateAsset.getAssetMap().getAsset(this.shapeAssetId); + } + + @Override + public boolean onlyUpdateOnPlacement() { + CustomConnectedBlockTemplateAsset templateAsset = this.getShapeTemplateAsset(); + return templateAsset != null && templateAsset.isDontUpdateAfterInitialPlacement(); + } + + @Override + public Optional getConnectedBlockType( + World world, Vector3i testedCoordinate, BlockType blockType, int rotation, Vector3i placementNormal, boolean isPlacement + ) { + CustomConnectedBlockTemplateAsset shapeTemplateAsset = this.getShapeTemplateAsset(); + return shapeTemplateAsset == null + ? Optional.empty() + : shapeTemplateAsset.getConnectedBlockType(world, testedCoordinate, this, blockType, rotation, placementNormal, true, isPlacement); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/PatternRotationDefinition.java b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/PatternRotationDefinition.java new file mode 100644 index 0000000..5cb3bc0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/PatternRotationDefinition.java @@ -0,0 +1,94 @@ +package com.hypixel.hytale.server.core.universe.world.connectedblocks; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class PatternRotationDefinition { + public static final BuilderCodec CODEC = BuilderCodec.builder(PatternRotationDefinition.class, PatternRotationDefinition::new) + .append( + new KeyedCodec<>("IsCardinallyRotatable", Codec.BOOLEAN, false), + (o, isCardinallyRotatable) -> o.isCardinallyRotatable = isCardinallyRotatable, + o -> o.isCardinallyRotatable + ) + .add() + .append(new KeyedCodec<>("MirrorZ", Codec.BOOLEAN, false), (o, isMirrorZ) -> o.isMirrorZ = isMirrorZ, o -> o.isMirrorZ) + .add() + .append(new KeyedCodec<>("MirrorX", Codec.BOOLEAN, false), (o, isMirrorX) -> o.isMirrorX = isMirrorX, o -> o.isMirrorX) + .add() + .build(); + @Nonnull + public static PatternRotationDefinition DEFAULT = new PatternRotationDefinition(); + private boolean isCardinallyRotatable; + private boolean isMirrorZ; + private boolean isMirrorX; + public static final List> ROTATIONS = new ArrayList<>(); + + public PatternRotationDefinition() { + } + + @Nonnull + public List> getRotations() { + return new AbstractList>() { + private final int[] enabledIndexes = this.computeEnabled(); + + private int[] computeEnabled() { + IntList idx = new IntArrayList(); + idx.add(0); + if (PatternRotationDefinition.this.isCardinallyRotatable) { + idx.addAll(IntList.of(1, 2, 3)); + } + + if (PatternRotationDefinition.this.isMirrorX) { + idx.add(4); + if (PatternRotationDefinition.this.isCardinallyRotatable) { + idx.addAll(IntList.of(5, 6, 7)); + } + } + + if (PatternRotationDefinition.this.isMirrorZ) { + idx.add(8); + if (PatternRotationDefinition.this.isCardinallyRotatable) { + idx.addAll(IntList.of(9, 10, 11)); + } + } + + return idx.toIntArray(); + } + + public Pair get(int i) { + return PatternRotationDefinition.ROTATIONS.get(this.enabledIndexes[i]); + } + + @Override + public int size() { + return this.enabledIndexes.length; + } + }; + } + + static { + for (PatternRotationDefinition.MirrorAxis mirrorAxis : PatternRotationDefinition.MirrorAxis.values()) { + for (Rotation value : Rotation.VALUES) { + ROTATIONS.add(Pair.of(value, mirrorAxis)); + } + } + } + + public static enum MirrorAxis { + NONE, + X, + Z; + + private MirrorAxis() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/Rotation3D.java b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/Rotation3D.java new file mode 100644 index 0000000..07d239f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/Rotation3D.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.server.core.universe.world.connectedblocks; + +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import javax.annotation.Nonnull; + +@Deprecated(forRemoval = true) +public class Rotation3D { + public Rotation rotationYaw; + public Rotation rotationPitch; + public Rotation rotationRoll; + + public Rotation3D(Rotation rotationYaw, Rotation rotationPitch, Rotation rotationRoll) { + this.rotationYaw = rotationYaw; + this.rotationPitch = rotationPitch; + this.rotationRoll = rotationRoll; + } + + public void assign(Rotation yaw, Rotation pitch, Rotation roll) { + this.rotationYaw = yaw; + this.rotationPitch = pitch; + this.rotationRoll = roll; + } + + public void assign(@Nonnull RotationTuple rotation) { + this.assign(rotation.yaw(), rotation.pitch(), rotation.roll()); + } + + public void add(@Nonnull Rotation3D toAdd) { + this.rotationYaw = this.rotationYaw.add(toAdd.rotationYaw); + this.rotationPitch = this.rotationPitch.add(toAdd.rotationPitch); + this.rotationRoll = this.rotationRoll.add(toAdd.rotationRoll); + } + + public void subtract(@Nonnull Rotation3D toSubtract) { + this.rotationYaw = this.rotationYaw.subtract(toSubtract.rotationYaw); + this.rotationPitch = this.rotationPitch.subtract(toSubtract.rotationPitch); + this.rotationRoll = this.rotationRoll.subtract(toSubtract.rotationRoll); + } + + public void negate() { + this.assign(Rotation.None.subtract(this.rotationYaw), Rotation.None.subtract(this.rotationPitch), Rotation.None.subtract(this.rotationRoll)); + } + + @Nonnull + public Rotation3D rotateSelfBy(@Nonnull Rotation rotationYawToRotate, @Nonnull Rotation rotationPitchToRotate, @Nonnull Rotation rotationRollToRotate) { + Vector3f vector3f = new Vector3f(this.rotationPitch.getDegrees(), this.rotationYaw.getDegrees(), this.rotationRoll.getDegrees()); + vector3f = Rotation.rotate(vector3f, rotationYawToRotate, rotationPitchToRotate, rotationRollToRotate); + this.assign(Rotation.closestOfDegrees(vector3f.y), Rotation.closestOfDegrees(vector3f.x), Rotation.closestOfDegrees(vector3f.z)); + return this; + } + + public void rotateSelfBy(@Nonnull Rotation3D rotation) { + this.rotateSelfBy(rotation.rotationYaw, rotation.rotationPitch, rotation.rotationRoll); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/ConnectedBlockOutput.java b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/ConnectedBlockOutput.java new file mode 100644 index 0000000..3ccee85 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/ConnectedBlockOutput.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.server.core.universe.world.connectedblocks.builtin; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; + +public class ConnectedBlockOutput { + public static final BuilderCodec CODEC = BuilderCodec.builder(ConnectedBlockOutput.class, ConnectedBlockOutput::new) + .append(new KeyedCodec<>("State", Codec.STRING), (output, state) -> output.state = state, output -> output.state) + .documentation("An optional state definition to apply to the base block type") + .add() + .append(new KeyedCodec<>("Block", Codec.STRING), (output, blockTypeKey) -> output.blockTypeKey = blockTypeKey, output -> output.blockTypeKey) + .documentation("An optional block ID to use instead of the base block type") + .add() + .build(); + protected String state; + protected String blockTypeKey; + + protected ConnectedBlockOutput() { + } + + public int resolve(BlockType baseBlockType, BlockTypeAssetMap assetMap) { + String blockTypeKey = this.blockTypeKey; + if (blockTypeKey == null) { + blockTypeKey = baseBlockType.getId(); + } + + BlockType blockType = assetMap.getAsset(blockTypeKey); + if (blockType == null) { + return -1; + } else { + if (this.state != null) { + String baseKey = blockType.getDefaultStateKey(); + BlockType baseBlock = baseKey == null ? blockType : BlockType.getAssetMap().getAsset(baseKey); + if ("default".equals(this.state)) { + blockTypeKey = baseBlock.getId(); + } else { + blockTypeKey = baseBlock.getBlockKeyForState(this.state); + } + + if (blockTypeKey == null) { + return -1; + } + } + + int index = assetMap.getIndex(blockTypeKey); + if (index == Integer.MIN_VALUE) { + return -1; + } else { + this.blockTypeKey = blockTypeKey; + return index; + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/RoofConnectedBlockRuleSet.java b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/RoofConnectedBlockRuleSet.java new file mode 100644 index 0000000..b1004dc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/RoofConnectedBlockRuleSet.java @@ -0,0 +1,374 @@ +package com.hypixel.hytale.server.core.universe.world.connectedblocks.builtin; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.simple.IntegerCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.ConnectedBlockRuleSetType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFace; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFaceSupport; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.connectedblocks.ConnectedBlockRuleSet; +import com.hypixel.hytale.server.core.universe.world.connectedblocks.ConnectedBlocksUtil; +import com.hypixel.hytale.server.core.util.FillerBlockUtil; +import it.unimi.dsi.fastutil.objects.ObjectIntPair; +import java.util.Map; +import java.util.Optional; +import javax.annotation.Nullable; + +public class RoofConnectedBlockRuleSet extends ConnectedBlockRuleSet implements StairLikeConnectedBlockRuleSet { + public static final BuilderCodec CODEC = BuilderCodec.builder(RoofConnectedBlockRuleSet.class, RoofConnectedBlockRuleSet::new) + .append(new KeyedCodec<>("Regular", StairConnectedBlockRuleSet.CODEC), (ruleSet, output) -> ruleSet.regular = output, ruleSet -> ruleSet.regular) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Hollow", StairConnectedBlockRuleSet.CODEC), (ruleSet, output) -> ruleSet.hollow = output, ruleSet -> ruleSet.hollow) + .add() + .append(new KeyedCodec<>("Topper", ConnectedBlockOutput.CODEC), (ruleSet, output) -> ruleSet.topper = output, ruleSet -> ruleSet.topper) + .add() + .append(new KeyedCodec<>("Width", new IntegerCodec()), (ruleSet, output) -> ruleSet.width = output, ruleSet -> ruleSet.width) + .add() + .append(new KeyedCodec<>("MaterialName", Codec.STRING), (ruleSet, materialName) -> ruleSet.materialName = materialName, ruleSet -> ruleSet.materialName) + .add() + .build(); + private StairConnectedBlockRuleSet regular; + private StairConnectedBlockRuleSet hollow; + private ConnectedBlockOutput topper; + private String materialName; + private int width = 1; + + public RoofConnectedBlockRuleSet() { + } + + private static StairConnectedBlockRuleSet.StairType getConnectedBlockStairType( + World world, Vector3i coordinate, StairLikeConnectedBlockRuleSet currentRuleSet, int blockId, int rotation, int width + ) { + RotationTuple currentRotation = RotationTuple.get(rotation); + Rotation currentYaw = currentRotation.yaw(); + Rotation currentPitch = currentRotation.pitch(); + boolean upsideDown = currentPitch != Rotation.None; + if (upsideDown) { + currentYaw = currentYaw.flip(); + } + + Vector3i mutablePos = new Vector3i(); + StairConnectedBlockRuleSet.StairType resultingStair = StairConnectedBlockRuleSet.StairType.STRAIGHT; + StairConnectedBlockRuleSet.StairConnection frontConnection = StairConnectedBlockRuleSet.getInvertedCornerConnection( + world, currentRuleSet, coordinate, mutablePos, currentYaw, upsideDown + ); + if (frontConnection != null) { + boolean valid = isWidthFulfilled(world, coordinate, mutablePos, frontConnection, currentYaw, blockId, width); + if (valid) { + resultingStair = frontConnection.getStairType(true); + } + } + + StairConnectedBlockRuleSet.StairConnection backConnection = StairConnectedBlockRuleSet.getCornerConnection( + world, currentRuleSet, coordinate, mutablePos, rotation, currentYaw, upsideDown, width + ); + if (backConnection != null) { + boolean valid = isWidthFulfilled(world, coordinate, mutablePos, backConnection, currentYaw, blockId, width); + if (valid) { + resultingStair = backConnection.getStairType(false); + } + } + + if (resultingStair == StairConnectedBlockRuleSet.StairType.STRAIGHT) { + Vector3i aboveCoordinate = new Vector3i(coordinate).add(0, 1, 0); + StairConnectedBlockRuleSet.StairConnection resultingConnection = getValleyConnection( + world, aboveCoordinate, currentRuleSet, currentRotation, mutablePos, false, width + ); + if (resultingConnection != null) { + resultingStair = resultingConnection.getStairType(true); + } + } + + if (resultingStair == StairConnectedBlockRuleSet.StairType.STRAIGHT) { + Vector3i belowCoordinate = new Vector3i(coordinate).add(0, -1, 0); + StairConnectedBlockRuleSet.StairConnection resultingConnection = getValleyConnection( + world, belowCoordinate, currentRuleSet, currentRotation, mutablePos, true, width + ); + if (resultingConnection != null) { + resultingStair = resultingConnection.getStairType(false); + } + } + + if (upsideDown) { + resultingStair = switch (resultingStair) { + case CORNER_LEFT -> StairConnectedBlockRuleSet.StairType.CORNER_RIGHT; + case CORNER_RIGHT -> StairConnectedBlockRuleSet.StairType.CORNER_LEFT; + case INVERTED_CORNER_LEFT -> StairConnectedBlockRuleSet.StairType.INVERTED_CORNER_RIGHT; + case INVERTED_CORNER_RIGHT -> StairConnectedBlockRuleSet.StairType.INVERTED_CORNER_LEFT; + default -> resultingStair; + }; + } + + return resultingStair; + } + + private static boolean isWidthFulfilled( + World world, + Vector3i coordinate, + Vector3i mutablePos, + StairConnectedBlockRuleSet.StairConnection backConnection, + Rotation currentYaw, + int blockId, + int width + ) { + boolean valid = true; + + for (int i = 0; i < width - 1; i++) { + mutablePos.assign(backConnection == StairConnectedBlockRuleSet.StairConnection.CORNER_LEFT ? Vector3i.WEST : Vector3i.EAST).scale(i + 1); + currentYaw.rotateY(mutablePos, mutablePos); + int requiredFiller = FillerBlockUtil.pack(mutablePos.x, mutablePos.y, mutablePos.z); + mutablePos.add(coordinate.x, coordinate.y, coordinate.z); + WorldChunk chunk = world.getChunkIfLoaded(ChunkUtil.indexChunkFromBlock(mutablePos.x, mutablePos.z)); + if (chunk != null) { + int otherFiller = chunk.getFiller(mutablePos.x, mutablePos.y, mutablePos.z); + int otherBlockId = chunk.getBlock(mutablePos); + if ((otherFiller != 0 || otherBlockId != blockId) && (otherFiller != requiredFiller || otherBlockId != blockId)) { + valid = false; + break; + } + } + } + + return valid; + } + + private static StairConnectedBlockRuleSet.StairConnection getValleyConnection( + World world, Vector3i coordinate, StairLikeConnectedBlockRuleSet currentRuleSet, RotationTuple rotation, Vector3i mutablePos, boolean reverse, int width + ) { + Rotation yaw = rotation.yaw(); + mutablePos.assign(reverse ? Vector3i.SOUTH : Vector3i.NORTH).scale(width); + yaw.rotateY(mutablePos, mutablePos); + mutablePos.add(coordinate.x, coordinate.y, coordinate.z); + ObjectIntPair backStair = StairConnectedBlockRuleSet.getStairData( + world, mutablePos, currentRuleSet.getMaterialName() + ); + if (backStair == null) { + return null; + } else { + boolean backConnection = reverse + ? isTopperConnectionCompatible(rotation, backStair, Rotation.None) + : isValleyConnectionCompatible(rotation, backStair, Rotation.None, false); + if (!backConnection) { + return null; + } else { + mutablePos.assign(reverse ? Vector3i.EAST : Vector3i.WEST).scale(width); + yaw.rotateY(mutablePos, mutablePos); + mutablePos.add(coordinate.x, coordinate.y, coordinate.z); + ObjectIntPair leftStair = StairConnectedBlockRuleSet.getStairData( + world, mutablePos, currentRuleSet.getMaterialName() + ); + mutablePos.assign(reverse ? Vector3i.WEST : Vector3i.EAST).scale(width); + yaw.rotateY(mutablePos, mutablePos); + mutablePos.add(coordinate.x, coordinate.y, coordinate.z); + ObjectIntPair rightStair = StairConnectedBlockRuleSet.getStairData( + world, mutablePos, currentRuleSet.getMaterialName() + ); + boolean leftConnection = reverse + ? isTopperConnectionCompatible(rotation, leftStair, Rotation.Ninety) + : isValleyConnectionCompatible(rotation, leftStair, Rotation.Ninety, false); + boolean rightConnection = reverse + ? isTopperConnectionCompatible(rotation, rightStair, Rotation.TwoSeventy) + : isValleyConnectionCompatible(rotation, rightStair, Rotation.TwoSeventy, false); + if (leftConnection == rightConnection) { + return null; + } else { + return leftConnection ? StairConnectedBlockRuleSet.StairConnection.CORNER_LEFT : StairConnectedBlockRuleSet.StairConnection.CORNER_RIGHT; + } + } + } + } + + private static boolean isTopperConnectionCompatible( + RotationTuple rotation, ObjectIntPair otherStair, Rotation yawOffset + ) { + return isValleyConnectionCompatible(rotation, otherStair, yawOffset, true); + } + + private static boolean canBeTopper( + World world, Vector3i coordinate, StairLikeConnectedBlockRuleSet currentRuleSet, RotationTuple rotation, Vector3i mutablePos + ) { + Rotation yaw = rotation.yaw(); + Vector3i[] directions = new Vector3i[]{Vector3i.NORTH, Vector3i.SOUTH, Vector3i.EAST, Vector3i.WEST}; + Rotation[] yawOffsets = new Rotation[]{Rotation.OneEighty, Rotation.None, Rotation.Ninety, Rotation.TwoSeventy}; + + for (int i = 0; i < directions.length; i++) { + mutablePos.assign(directions[i]); + yaw.rotateY(mutablePos, mutablePos); + mutablePos.add(coordinate.x, coordinate.y, coordinate.z); + ObjectIntPair stair = StairConnectedBlockRuleSet.getStairData( + world, mutablePos, currentRuleSet.getMaterialName() + ); + if (stair == null || !isTopperConnectionCompatible(rotation, stair, yawOffsets[i])) { + return false; + } + } + + return true; + } + + private static boolean isValleyConnectionCompatible( + RotationTuple rotation, ObjectIntPair otherStair, Rotation yawOffset, boolean inverted + ) { + Rotation targetYaw = rotation.yaw().add(yawOffset); + if (otherStair == null) { + return false; + } else { + RotationTuple stairRotation = RotationTuple.get(otherStair.rightInt()); + StairConnectedBlockRuleSet.StairType otherStairType = otherStair.first(); + if (stairRotation.pitch() != rotation.pitch()) { + return false; + } else if (inverted && otherStairType.isCorner()) { + return false; + } else { + return !inverted && otherStairType.isInvertedCorner() + ? false + : stairRotation.yaw() == targetYaw + || otherStairType == StairConnectedBlockRuleSet.StairConnection.CORNER_RIGHT.getStairType(inverted) + && stairRotation.yaw() == targetYaw.add(Rotation.Ninety) + || otherStairType == StairConnectedBlockRuleSet.StairConnection.CORNER_LEFT.getStairType(inverted) + && stairRotation.yaw() == targetYaw.add(Rotation.TwoSeventy); + } + } + } + + @Override + public boolean onlyUpdateOnPlacement() { + return false; + } + + @Override + public Optional getConnectedBlockType( + World world, Vector3i coordinate, BlockType blockType, int rotation, Vector3i placementNormal, boolean isPlacement + ) { + WorldChunk chunk = world.getChunkIfLoaded(ChunkUtil.indexChunkFromBlock(coordinate.x, coordinate.z)); + if (chunk == null) { + return Optional.empty(); + } else { + int belowBlockId = chunk.getBlock(coordinate.x, coordinate.y - 1, coordinate.z); + BlockType belowBlockType = BlockType.getAssetMap().getAsset(belowBlockId); + int belowBlockRotation = chunk.getRotationIndex(coordinate.x, coordinate.y - 1, coordinate.z); + boolean hollow = true; + if (belowBlockType != null) { + Map supporting = belowBlockType.getSupporting(belowBlockRotation); + if (supporting != null) { + BlockFaceSupport[] support = supporting.get(BlockFace.UP); + hollow = support == null; + } + } + + int blockId = BlockType.getAssetMap().getIndex(blockType.getId()); + StairConnectedBlockRuleSet.StairType stairType = getConnectedBlockStairType(world, coordinate, this, blockId, rotation, this.width); + if (this.topper != null && stairType == StairConnectedBlockRuleSet.StairType.STRAIGHT) { + Vector3i belowCoordinate = new Vector3i(coordinate).add(0, -1, 0); + RotationTuple currentRotation = RotationTuple.get(rotation); + currentRotation = RotationTuple.of(Rotation.None, currentRotation.pitch(), currentRotation.roll()); + Vector3i mutablePos = new Vector3i(); + boolean topper = canBeTopper(world, belowCoordinate, this, currentRotation, mutablePos); + if (topper) { + BlockType topperBlockType = BlockType.getAssetMap().getAsset(this.topper.blockTypeKey); + if (topperBlockType != null) { + return Optional.of(new ConnectedBlocksUtil.ConnectedBlockResult(topperBlockType.getId(), rotation)); + } + } + } + + if (this.hollow != null && hollow) { + BlockType hollowBlockType = this.hollow.getStairBlockType(stairType); + if (hollowBlockType != null) { + return Optional.of(new ConnectedBlocksUtil.ConnectedBlockResult(hollowBlockType.getId(), rotation)); + } + } + + BlockType regularBlockType = this.regular.getStairBlockType(stairType); + if (regularBlockType != null) { + ConnectedBlocksUtil.ConnectedBlockResult result = new ConnectedBlocksUtil.ConnectedBlockResult(regularBlockType.getId(), rotation); + if (this.regular != null && this.width > 0) { + StairConnectedBlockRuleSet.StairType existingStairType = this.regular.getStairType(BlockType.getAssetMap().getIndex(blockType.getId())); + if (existingStairType != null && existingStairType != StairConnectedBlockRuleSet.StairType.STRAIGHT) { + int previousWidth = existingStairType.isLeft() ? -(this.width - 1) : (existingStairType.isRight() ? this.width - 1 : 0); + int newWidth = stairType.isLeft() ? -(this.width - 1) : (stairType.isRight() ? this.width - 1 : 0); + if (newWidth != previousWidth) { + Vector3i mutablePos = new Vector3i(); + Rotation currentYaw = RotationTuple.get(rotation).yaw(); + mutablePos.assign(Vector3i.EAST).scale(previousWidth); + currentYaw.rotateY(mutablePos, mutablePos); + result.addAdditionalBlock(mutablePos, regularBlockType.getId(), rotation); + } + } + } + + return Optional.of(result); + } else { + return Optional.empty(); + } + } + } + + @Override + public void updateCachedBlockTypes(BlockType baseBlockType, BlockTypeAssetMap assetMap) { + if (this.regular != null) { + this.regular.updateCachedBlockTypes(baseBlockType, assetMap); + } + + if (this.hollow != null) { + this.hollow.updateCachedBlockTypes(baseBlockType, assetMap); + } + + if (this.topper != null) { + this.topper.resolve(baseBlockType, assetMap); + } + } + + @Override + public StairConnectedBlockRuleSet.StairType getStairType(int blockId) { + StairConnectedBlockRuleSet.StairType regularStairType = this.regular.getStairType(blockId); + if (regularStairType != null) { + return regularStairType; + } else { + return this.hollow != null ? this.hollow.getStairType(blockId) : null; + } + } + + @Nullable + @Override + public String getMaterialName() { + return this.materialName; + } + + @Nullable + @Override + public com.hypixel.hytale.protocol.ConnectedBlockRuleSet toPacket(BlockTypeAssetMap assetMap) { + com.hypixel.hytale.protocol.ConnectedBlockRuleSet packet = new com.hypixel.hytale.protocol.ConnectedBlockRuleSet(); + packet.type = ConnectedBlockRuleSetType.Roof; + com.hypixel.hytale.protocol.RoofConnectedBlockRuleSet roofPacket = new com.hypixel.hytale.protocol.RoofConnectedBlockRuleSet(); + if (this.regular != null) { + roofPacket.regular = this.regular.toProtocol(assetMap); + } + + if (this.hollow != null) { + roofPacket.hollow = this.hollow.toProtocol(assetMap); + } + + if (this.topper != null) { + roofPacket.topperBlockId = assetMap.getIndex(this.topper.blockTypeKey); + } else { + roofPacket.topperBlockId = -1; + } + + roofPacket.width = this.width; + roofPacket.materialName = this.materialName; + packet.roof = roofPacket; + return packet; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/StairConnectedBlockRuleSet.java b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/StairConnectedBlockRuleSet.java new file mode 100644 index 0000000..50bc578 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/StairConnectedBlockRuleSet.java @@ -0,0 +1,368 @@ +package com.hypixel.hytale.server.core.universe.world.connectedblocks.builtin; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.ConnectedBlockRuleSetType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.connectedblocks.ConnectedBlockRuleSet; +import com.hypixel.hytale.server.core.universe.world.connectedblocks.ConnectedBlocksUtil; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectIntImmutablePair; +import it.unimi.dsi.fastutil.objects.ObjectIntPair; +import java.util.Optional; +import javax.annotation.Nullable; + +public class StairConnectedBlockRuleSet extends ConnectedBlockRuleSet implements StairLikeConnectedBlockRuleSet { + public static final String DEFAULT_MATERIAL_NAME = "Stair"; + public static final BuilderCodec CODEC = BuilderCodec.builder(StairConnectedBlockRuleSet.class, StairConnectedBlockRuleSet::new) + .append(new KeyedCodec<>("Straight", ConnectedBlockOutput.CODEC), (ruleSet, output) -> ruleSet.straight = output, ruleSet -> ruleSet.straight) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("Corner_Left", ConnectedBlockOutput.CODEC), (ruleSet, output) -> ruleSet.cornerLeft = output, ruleSet -> ruleSet.cornerLeft + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("Corner_Right", ConnectedBlockOutput.CODEC), (ruleSet, output) -> ruleSet.cornerRight = output, ruleSet -> ruleSet.cornerRight + ) + .addValidator(Validators.nonNull()) + .add() + .append( + new KeyedCodec<>("Inverted_Corner_Left", ConnectedBlockOutput.CODEC), + (ruleSet, output) -> ruleSet.invertedCornerLeft = output, + ruleSet -> ruleSet.invertedCornerLeft + ) + .add() + .append( + new KeyedCodec<>("Inverted_Corner_Right", ConnectedBlockOutput.CODEC), + (ruleSet, output) -> ruleSet.invertedCornerRight = output, + ruleSet -> ruleSet.invertedCornerRight + ) + .add() + .append(new KeyedCodec<>("MaterialName", Codec.STRING), (ruleSet, materialName) -> ruleSet.materialName = materialName, ruleSet -> ruleSet.materialName) + .add() + .build(); + private ConnectedBlockOutput straight; + private ConnectedBlockOutput cornerLeft; + private ConnectedBlockOutput cornerRight; + private ConnectedBlockOutput invertedCornerLeft; + private ConnectedBlockOutput invertedCornerRight; + private String materialName = "Stair"; + private Int2ObjectMap blockIdToStairType; + protected Object2IntMap stairTypeToBlockId; + + public StairConnectedBlockRuleSet() { + } + + @Override + public boolean onlyUpdateOnPlacement() { + return false; + } + + @Override + public void updateCachedBlockTypes(BlockType baseBlockType, BlockTypeAssetMap assetMap) { + int baseIndex = assetMap.getIndex(baseBlockType.getId()); + Int2ObjectMap blockIdToStairType = new Int2ObjectOpenHashMap<>(); + Object2IntMap stairTypeToBlockId = new Object2IntOpenHashMap<>(); + stairTypeToBlockId.defaultReturnValue(baseIndex); + ConnectedBlockOutput[] outputs = new ConnectedBlockOutput[]{ + this.straight, this.cornerLeft, this.cornerRight, this.invertedCornerLeft, this.invertedCornerRight + }; + StairConnectedBlockRuleSet.StairType[] stairTypes = StairConnectedBlockRuleSet.StairType.VALUES; + + for (int i = 0; i < outputs.length; i++) { + ConnectedBlockOutput output = outputs[i]; + if (output != null) { + int index = output.resolve(baseBlockType, assetMap); + if (index != -1) { + blockIdToStairType.put(index, stairTypes[i]); + stairTypeToBlockId.put(stairTypes[i], index); + } + } + } + + this.blockIdToStairType = blockIdToStairType; + this.stairTypeToBlockId = stairTypeToBlockId; + } + + @Nullable + protected static ObjectIntPair getStairData(World world, Vector3i coordinate, @Nullable String requiredMaterialName) { + WorldChunk chunk = world.getChunkIfLoaded(ChunkUtil.indexChunkFromBlock(coordinate.x, coordinate.z)); + if (chunk == null) { + return null; + } else { + int filler = chunk.getFiller(coordinate.x, coordinate.y, coordinate.z); + if (filler != 0) { + return null; + } else { + int blockId = chunk.getBlock(coordinate); + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + if (blockType == null) { + return null; + } else if (blockType.getConnectedBlockRuleSet() instanceof StairLikeConnectedBlockRuleSet stairRuleSet) { + String otherMaterialName = stairRuleSet.getMaterialName(); + if (requiredMaterialName != null && otherMaterialName != null && !requiredMaterialName.equals(otherMaterialName)) { + return null; + } else { + StairConnectedBlockRuleSet.StairType stairType = stairRuleSet.getStairType(blockId); + if (stairType == null) { + return null; + } else { + int rotation = chunk.getRotationIndex(coordinate.x, coordinate.y, coordinate.z); + return new ObjectIntImmutablePair<>(stairType, rotation); + } + } + } else { + return null; + } + } + } + } + + @Override + public StairConnectedBlockRuleSet.StairType getStairType(int blockId) { + return this.blockIdToStairType.get(blockId); + } + + @Nullable + @Override + public String getMaterialName() { + return this.materialName; + } + + public BlockType getStairBlockType(StairConnectedBlockRuleSet.StairType stairType) { + if (this.stairTypeToBlockId == null) { + return null; + } else { + int resultingBlockTypeIndex = this.stairTypeToBlockId.getInt(stairType); + return BlockType.getAssetMap().getAsset(resultingBlockTypeIndex); + } + } + + @Override + public Optional getConnectedBlockType( + World world, Vector3i coordinate, BlockType currentBlockType, int rotation, Vector3i placementNormal, boolean isPlacement + ) { + RotationTuple currentRotation = RotationTuple.get(rotation); + Rotation currentYaw = currentRotation.yaw(); + Rotation currentPitch = currentRotation.pitch(); + boolean upsideDown = currentPitch != Rotation.None; + if (upsideDown) { + currentYaw = currentYaw.flip(); + } + + Vector3i mutablePos = new Vector3i(); + StairConnectedBlockRuleSet.StairType resultingStair = StairConnectedBlockRuleSet.StairType.STRAIGHT; + StairConnectedBlockRuleSet.StairConnection frontConnection = getInvertedCornerConnection(world, this, coordinate, mutablePos, currentYaw, upsideDown); + if (frontConnection != null) { + resultingStair = frontConnection.getStairType(true); + } + + StairConnectedBlockRuleSet.StairConnection backConnection = getCornerConnection(world, this, coordinate, mutablePos, rotation, currentYaw, upsideDown, 1); + if (backConnection != null) { + resultingStair = backConnection.getStairType(false); + } + + if (upsideDown) { + resultingStair = switch (resultingStair) { + case CORNER_LEFT -> StairConnectedBlockRuleSet.StairType.CORNER_RIGHT; + case CORNER_RIGHT -> StairConnectedBlockRuleSet.StairType.CORNER_LEFT; + case INVERTED_CORNER_LEFT -> StairConnectedBlockRuleSet.StairType.INVERTED_CORNER_RIGHT; + case INVERTED_CORNER_RIGHT -> StairConnectedBlockRuleSet.StairType.INVERTED_CORNER_LEFT; + default -> resultingStair; + }; + } + + int resultingBlockTypeIndex = this.stairTypeToBlockId.getInt(resultingStair); + BlockType resultingBlockType = BlockType.getAssetMap().getAsset(resultingBlockTypeIndex); + if (resultingBlockType == null) { + return Optional.empty(); + } else { + String resultingBlockTypeKey = resultingBlockType.getId(); + return Optional.of(new ConnectedBlocksUtil.ConnectedBlockResult(resultingBlockTypeKey, rotation)); + } + } + + protected static StairConnectedBlockRuleSet.StairConnection getCornerConnection( + World world, + StairLikeConnectedBlockRuleSet currentRuleSet, + Vector3i coordinate, + Vector3i mutablePos, + int rotation, + Rotation currentYaw, + boolean upsideDown, + int width + ) { + StairConnectedBlockRuleSet.StairConnection backConnection = null; + mutablePos.assign(Vector3i.NORTH).scale(width); + currentYaw.rotateY(mutablePos, mutablePos); + mutablePos.add(coordinate.x, coordinate.y, coordinate.z); + ObjectIntPair backStair = getStairData(world, mutablePos, currentRuleSet.getMaterialName()); + if (backStair == null && width > 1) { + mutablePos.assign(Vector3i.NORTH).scale(width + 1); + currentYaw.rotateY(mutablePos, mutablePos); + mutablePos.add(coordinate.x, coordinate.y, coordinate.z); + backStair = getStairData(world, mutablePos, currentRuleSet.getMaterialName()); + if (backStair != null && backStair.first() == StairConnectedBlockRuleSet.StairType.STRAIGHT) { + backStair = null; + } + } + + if (backStair != null) { + StairConnectedBlockRuleSet.StairType otherStairType = backStair.left(); + RotationTuple otherStairRotation = RotationTuple.get(backStair.rightInt()); + Rotation otherYaw = otherStairRotation.yaw(); + boolean otherUpsideDown = otherStairRotation.pitch() != Rotation.None; + if (otherUpsideDown) { + otherYaw = otherYaw.flip(); + } + + if (canConnectTo(currentYaw, otherYaw, upsideDown, otherUpsideDown)) { + mutablePos.assign(Vector3i.SOUTH); + otherYaw.rotateY(mutablePos, mutablePos); + mutablePos.add(coordinate.x, coordinate.y, coordinate.z); + ObjectIntPair sidewaysStair = getStairData(world, mutablePos, currentRuleSet.getMaterialName()); + if (sidewaysStair == null || sidewaysStair.rightInt() != rotation) { + backConnection = getConnection(currentYaw, otherYaw, otherStairType, false); + } + } + } + + return backConnection; + } + + protected static StairConnectedBlockRuleSet.StairConnection getInvertedCornerConnection( + World world, StairLikeConnectedBlockRuleSet currentRuleSet, Vector3i coordinate, Vector3i mutablePos, Rotation currentYaw, boolean upsideDown + ) { + StairConnectedBlockRuleSet.StairConnection frontConnection = null; + mutablePos.assign(Vector3i.SOUTH); + currentYaw.rotateY(mutablePos, mutablePos); + mutablePos.add(coordinate.x, coordinate.y, coordinate.z); + ObjectIntPair frontStair = getStairData(world, mutablePos, currentRuleSet.getMaterialName()); + if (frontStair != null) { + StairConnectedBlockRuleSet.StairType otherStairType = frontStair.left(); + RotationTuple otherStairRotation = RotationTuple.get(frontStair.rightInt()); + Rotation otherYaw = otherStairRotation.yaw(); + boolean otherUpsideDown = otherStairRotation.pitch() != Rotation.None; + if (otherUpsideDown) { + otherYaw = otherYaw.flip(); + } + + if (canConnectTo(currentYaw, otherYaw, upsideDown, otherUpsideDown)) { + frontConnection = getConnection(currentYaw, otherYaw, otherStairType, true); + } + } + + return frontConnection; + } + + private static boolean canConnectTo(Rotation currentYaw, Rotation otherYaw, boolean upsideDown, boolean otherUpsideDown) { + return otherUpsideDown == upsideDown && otherYaw != currentYaw && otherYaw.add(Rotation.OneEighty) != currentYaw; + } + + private static StairConnectedBlockRuleSet.StairConnection getConnection( + Rotation currentYaw, Rotation otherYaw, StairConnectedBlockRuleSet.StairType otherStairType, boolean inverted + ) { + if (otherYaw == currentYaw.add(Rotation.Ninety) + && otherStairType != StairConnectedBlockRuleSet.StairType.invertedCorner(inverted) + && otherStairType != StairConnectedBlockRuleSet.StairType.corner(!inverted)) { + return StairConnectedBlockRuleSet.StairConnection.CORNER_LEFT; + } else { + return otherYaw == currentYaw.subtract(Rotation.Ninety) + && otherStairType != StairConnectedBlockRuleSet.StairType.invertedCorner(!inverted) + && otherStairType != StairConnectedBlockRuleSet.StairType.corner(inverted) + ? StairConnectedBlockRuleSet.StairConnection.CORNER_RIGHT + : null; + } + } + + @Nullable + @Override + public com.hypixel.hytale.protocol.ConnectedBlockRuleSet toPacket(BlockTypeAssetMap assetMap) { + com.hypixel.hytale.protocol.ConnectedBlockRuleSet packet = new com.hypixel.hytale.protocol.ConnectedBlockRuleSet(); + packet.type = ConnectedBlockRuleSetType.Stair; + packet.stair = this.toProtocol(assetMap); + return packet; + } + + public com.hypixel.hytale.protocol.StairConnectedBlockRuleSet toProtocol(BlockTypeAssetMap assetMap) { + com.hypixel.hytale.protocol.StairConnectedBlockRuleSet stairPacket = new com.hypixel.hytale.protocol.StairConnectedBlockRuleSet(); + stairPacket.straightBlockId = this.getBlockIdForStairType(StairConnectedBlockRuleSet.StairType.STRAIGHT, assetMap); + stairPacket.cornerLeftBlockId = this.getBlockIdForStairType(StairConnectedBlockRuleSet.StairType.CORNER_LEFT, assetMap); + stairPacket.cornerRightBlockId = this.getBlockIdForStairType(StairConnectedBlockRuleSet.StairType.CORNER_RIGHT, assetMap); + stairPacket.invertedCornerLeftBlockId = this.getBlockIdForStairType(StairConnectedBlockRuleSet.StairType.INVERTED_CORNER_LEFT, assetMap); + stairPacket.invertedCornerRightBlockId = this.getBlockIdForStairType(StairConnectedBlockRuleSet.StairType.INVERTED_CORNER_RIGHT, assetMap); + stairPacket.materialName = this.materialName; + return stairPacket; + } + + private int getBlockIdForStairType(StairConnectedBlockRuleSet.StairType stairType, BlockTypeAssetMap assetMap) { + BlockType blockType = this.getStairBlockType(stairType); + return blockType == null ? -1 : assetMap.getIndex(blockType.getId()); + } + + protected static enum StairConnection { + CORNER_LEFT, + CORNER_RIGHT; + + private StairConnection() { + } + + public StairConnectedBlockRuleSet.StairType getStairType(boolean inverted) { + return switch (this) { + case CORNER_LEFT -> inverted ? StairConnectedBlockRuleSet.StairType.INVERTED_CORNER_LEFT : StairConnectedBlockRuleSet.StairType.CORNER_LEFT; + case CORNER_RIGHT -> inverted ? StairConnectedBlockRuleSet.StairType.INVERTED_CORNER_RIGHT : StairConnectedBlockRuleSet.StairType.CORNER_RIGHT; + }; + } + } + + public static enum StairType { + STRAIGHT, + CORNER_LEFT, + CORNER_RIGHT, + INVERTED_CORNER_LEFT, + INVERTED_CORNER_RIGHT; + + private static final StairConnectedBlockRuleSet.StairType[] VALUES = values(); + + private StairType() { + } + + public static StairConnectedBlockRuleSet.StairType corner(boolean right) { + return right ? CORNER_RIGHT : CORNER_LEFT; + } + + public static StairConnectedBlockRuleSet.StairType invertedCorner(boolean right) { + return right ? INVERTED_CORNER_RIGHT : INVERTED_CORNER_LEFT; + } + + public boolean isCorner() { + return this == CORNER_LEFT || this == CORNER_RIGHT; + } + + public boolean isInvertedCorner() { + return this == INVERTED_CORNER_LEFT || this == INVERTED_CORNER_RIGHT; + } + + public boolean isLeft() { + return this == CORNER_LEFT || this == INVERTED_CORNER_LEFT; + } + + public boolean isRight() { + return this == CORNER_RIGHT || this == INVERTED_CORNER_RIGHT; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/StairLikeConnectedBlockRuleSet.java b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/StairLikeConnectedBlockRuleSet.java new file mode 100644 index 0000000..aa88ec9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/StairLikeConnectedBlockRuleSet.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.core.universe.world.connectedblocks.builtin; + +import javax.annotation.Nullable; + +public interface StairLikeConnectedBlockRuleSet { + StairConnectedBlockRuleSet.StairType getStairType(int var1); + + @Nullable + String getMaterialName(); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/events/AddWorldEvent.java b/src/com/hypixel/hytale/server/core/universe/world/events/AddWorldEvent.java new file mode 100644 index 0000000..0f66b14 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/events/AddWorldEvent.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.core.universe.world.events; + +import com.hypixel.hytale.event.ICancellable; +import com.hypixel.hytale.server.core.universe.world.World; +import javax.annotation.Nonnull; + +public class AddWorldEvent extends WorldEvent implements ICancellable { + private boolean cancelled = false; + + public AddWorldEvent(@Nonnull World world) { + super(world); + } + + @Nonnull + @Override + public String toString() { + return "AddWorldEvent{cancelled=" + this.cancelled + "} " + super.toString(); + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/events/AllWorldsLoadedEvent.java b/src/com/hypixel/hytale/server/core/universe/world/events/AllWorldsLoadedEvent.java new file mode 100644 index 0000000..72e972d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/events/AllWorldsLoadedEvent.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.server.core.universe.world.events; + +import com.hypixel.hytale.event.IEvent; +import javax.annotation.Nonnull; + +public class AllWorldsLoadedEvent implements IEvent { + public AllWorldsLoadedEvent() { + } + + @Nonnull + @Override + public String toString() { + return "AllWorldsLoadedEvent{}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/events/ChunkEvent.java b/src/com/hypixel/hytale/server/core/universe/world/events/ChunkEvent.java new file mode 100644 index 0000000..b9db670 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/events/ChunkEvent.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.core.universe.world.events; + +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import javax.annotation.Nonnull; + +public abstract class ChunkEvent implements IEvent { + @Nonnull + private final WorldChunk chunk; + + public ChunkEvent(@Nonnull WorldChunk chunk) { + this.chunk = chunk; + } + + public WorldChunk getChunk() { + return this.chunk; + } + + @Nonnull + @Override + public String toString() { + return "ChunkEvent{chunk=" + this.chunk + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/events/ChunkPreLoadProcessEvent.java b/src/com/hypixel/hytale/server/core/universe/world/events/ChunkPreLoadProcessEvent.java new file mode 100644 index 0000000..8418040 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/events/ChunkPreLoadProcessEvent.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.server.core.universe.world.events; + +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.event.IProcessedEvent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class ChunkPreLoadProcessEvent extends ChunkEvent implements IProcessedEvent { + private final boolean newlyGenerated; + private long lastDispatchNanos; + private boolean didLog; + @Nonnull + private final Holder holder; + + public ChunkPreLoadProcessEvent(@Nonnull Holder holder, @Nonnull WorldChunk chunk, boolean newlyGenerated, long lastDispatchNanos) { + super(chunk); + this.newlyGenerated = newlyGenerated; + this.lastDispatchNanos = lastDispatchNanos; + this.holder = holder; + } + + public boolean isNewlyGenerated() { + return this.newlyGenerated; + } + + public Holder getHolder() { + return this.holder; + } + + @Override + public void processEvent(@Nonnull String hookName) { + long end = System.nanoTime(); + long diff = end - this.lastDispatchNanos; + this.lastDispatchNanos = end; + if (diff > this.getChunk().getWorld().getTickStepNanos()) { + World world = this.getChunk().getWorld(); + if (world.consumeGCHasRun()) { + world.getLogger() + .at(Level.SEVERE) + .log( + String.format( + "Took too long to run pre-load process hook for chunk: %s > TICK_STEP, Has GC Run: true, %%s, Hook: %%s", FormatUtil.nanosToString(diff) + ), + this.getChunk(), + hookName + ); + } else { + world.getLogger() + .at(Level.SEVERE) + .log( + String.format("Took too long to run pre-load process hook for chunk: %s > TICK_STEP, %%s, Hook: %%s", FormatUtil.nanosToString(diff)), + this.getChunk(), + hookName + ); + } + + this.didLog = true; + } + } + + public boolean didLog() { + return this.didLog; + } + + @Nonnull + @Override + public String toString() { + return "ChunkPreLoadProcessEvent{newlyGenerated=" + + this.newlyGenerated + + ", lastDispatchNanos=" + + this.lastDispatchNanos + + ", didLog=" + + this.didLog + + "} " + + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/events/RemoveWorldEvent.java b/src/com/hypixel/hytale/server/core/universe/world/events/RemoveWorldEvent.java new file mode 100644 index 0000000..555fce2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/events/RemoveWorldEvent.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.core.universe.world.events; + +import com.hypixel.hytale.event.ICancellable; +import com.hypixel.hytale.server.core.universe.world.World; +import javax.annotation.Nonnull; + +public class RemoveWorldEvent extends WorldEvent implements ICancellable { + private boolean cancelled; + @Nonnull + private final RemoveWorldEvent.RemovalReason removalReason; + + public RemoveWorldEvent(@Nonnull World world, @Nonnull RemoveWorldEvent.RemovalReason removalReason) { + super(world); + this.removalReason = removalReason; + } + + @Nonnull + public RemoveWorldEvent.RemovalReason getRemovalReason() { + return this.removalReason; + } + + @Override + public boolean isCancelled() { + return this.removalReason == RemoveWorldEvent.RemovalReason.EXCEPTIONAL ? false : this.cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Nonnull + @Override + public String toString() { + return "RemoveWorldEvent{cancelled=" + this.cancelled + "} " + super.toString(); + } + + public static enum RemovalReason { + GENERAL, + EXCEPTIONAL; + + private RemovalReason() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/events/StartWorldEvent.java b/src/com/hypixel/hytale/server/core/universe/world/events/StartWorldEvent.java new file mode 100644 index 0000000..1658b71 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/events/StartWorldEvent.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.server.core.universe.world.events; + +import com.hypixel.hytale.server.core.universe.world.World; +import javax.annotation.Nonnull; + +public class StartWorldEvent extends WorldEvent { + public StartWorldEvent(@Nonnull World world) { + super(world); + } + + @Nonnull + @Override + public String toString() { + return "StartWorldEvent{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/events/WorldEvent.java b/src/com/hypixel/hytale/server/core/universe/world/events/WorldEvent.java new file mode 100644 index 0000000..c811787 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/events/WorldEvent.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.core.universe.world.events; + +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.server.core.universe.world.World; +import javax.annotation.Nonnull; + +public abstract class WorldEvent implements IEvent { + @Nonnull + private final World world; + + public WorldEvent(@Nonnull World world) { + this.world = world; + } + + @Nonnull + public World getWorld() { + return this.world; + } + + @Nonnull + @Override + public String toString() { + return "WorldEvent{world=" + this.world + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/events/ecs/ChunkSaveEvent.java b/src/com/hypixel/hytale/server/core/universe/world/events/ecs/ChunkSaveEvent.java new file mode 100644 index 0000000..4fe2d9b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/events/ecs/ChunkSaveEvent.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.server.core.universe.world.events.ecs; + +import com.hypixel.hytale.component.system.CancellableEcsEvent; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import javax.annotation.Nonnull; + +public class ChunkSaveEvent extends CancellableEcsEvent { + @Nonnull + private final WorldChunk chunk; + + public ChunkSaveEvent(@Nonnull WorldChunk chunk) { + this.chunk = chunk; + } + + @Nonnull + public WorldChunk getChunk() { + return this.chunk; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/events/ecs/ChunkUnloadEvent.java b/src/com/hypixel/hytale/server/core/universe/world/events/ecs/ChunkUnloadEvent.java new file mode 100644 index 0000000..f8b457e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/events/ecs/ChunkUnloadEvent.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.universe.world.events.ecs; + +import com.hypixel.hytale.component.system.CancellableEcsEvent; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import javax.annotation.Nonnull; + +public class ChunkUnloadEvent extends CancellableEcsEvent { + @Nonnull + private final WorldChunk chunk; + private boolean resetKeepAlive = true; + + public ChunkUnloadEvent(@Nonnull WorldChunk chunk) { + this.chunk = chunk; + } + + @Nonnull + public WorldChunk getChunk() { + return this.chunk; + } + + public void setResetKeepAlive(boolean willResetKeepAlive) { + this.resetKeepAlive = willResetKeepAlive; + } + + public boolean willResetKeepAlive() { + return this.resetKeepAlive; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/events/ecs/MoonPhaseChangeEvent.java b/src/com/hypixel/hytale/server/core/universe/world/events/ecs/MoonPhaseChangeEvent.java new file mode 100644 index 0000000..c08a653 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/events/ecs/MoonPhaseChangeEvent.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.server.core.universe.world.events.ecs; + +import com.hypixel.hytale.component.system.EcsEvent; + +public class MoonPhaseChangeEvent extends EcsEvent { + private final int newMoonPhase; + + public MoonPhaseChangeEvent(int newMoonPhase) { + this.newMoonPhase = newMoonPhase; + } + + public int getNewMoonPhase() { + return this.newMoonPhase; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/lighting/CalculationResult.java b/src/com/hypixel/hytale/server/core/universe/world/lighting/CalculationResult.java new file mode 100644 index 0000000..686ce5f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/lighting/CalculationResult.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.universe.world.lighting; + +public enum CalculationResult { + NOT_LOADED, + DONE, + INVALIDATED, + WAITING_FOR_NEIGHBOUR; + + private CalculationResult() { + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/lighting/ChunkLightingManager.java b/src/com/hypixel/hytale/server/core/universe/world/lighting/ChunkLightingManager.java new file mode 100644 index 0000000..704f67a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/lighting/ChunkLightingManager.java @@ -0,0 +1,224 @@ +package com.hypixel.hytale.server.core.universe.world.lighting; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Semaphore; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class ChunkLightingManager implements Runnable { + @Nonnull + private final HytaleLogger logger; + @Nonnull + private final Thread thread; + @Nonnull + private final World world; + private final Semaphore semaphore = new Semaphore(1); + private final Set set = ConcurrentHashMap.newKeySet(); + private final ObjectArrayFIFOQueue queue = new ObjectArrayFIFOQueue<>(); + private LightCalculation lightCalculation; + + public ChunkLightingManager(@Nonnull World world) { + this.logger = HytaleLogger.get("World|" + world.getName() + "|L"); + this.thread = new Thread(this, "ChunkLighting - " + world.getName()); + this.thread.setDaemon(true); + this.world = world; + this.lightCalculation = new FloodLightCalculation(this); + } + + @Nonnull + protected HytaleLogger getLogger() { + return this.logger; + } + + @Nonnull + public World getWorld() { + return this.world; + } + + public void setLightCalculation(LightCalculation lightCalculation) { + this.lightCalculation = lightCalculation; + } + + public LightCalculation getLightCalculation() { + return this.lightCalculation; + } + + public void start() { + this.thread.start(); + } + + @Override + public void run() { + try { + int lastSize = 0; + int count = 0; + + while (!this.thread.isInterrupted()) { + this.semaphore.drainPermits(); + Vector3i pos; + synchronized (this.queue) { + pos = this.queue.isEmpty() ? null : this.queue.dequeue(); + } + + if (pos != null) { + this.process(pos); + } + + Thread.yield(); + int currentSize; + synchronized (this.queue) { + currentSize = this.queue.size(); + } + + if (currentSize != lastSize) { + count = 0; + lastSize = currentSize; + } else if (count <= currentSize) { + count++; + } else { + this.semaphore.acquire(); + } + } + } catch (InterruptedException var9) { + Thread.currentThread().interrupt(); + } + } + + private void process(Vector3i chunkPosition) { + try { + switch (this.lightCalculation.calculateLight(chunkPosition)) { + case NOT_LOADED: + case WAITING_FOR_NEIGHBOUR: + case DONE: + this.set.remove(chunkPosition); + break; + case INVALIDATED: + synchronized (this.queue) { + this.queue.enqueue(chunkPosition); + } + } + } catch (Exception var5) { + this.logger.at(Level.WARNING).withCause(var5).log("Failed to calculate lighting for: %s", chunkPosition); + this.set.remove(chunkPosition); + } + } + + public boolean interrupt() { + if (this.thread.isAlive()) { + this.thread.interrupt(); + return true; + } else { + return false; + } + } + + public void stop() { + try { + int i = 0; + + while (this.thread.isAlive()) { + this.thread.interrupt(); + this.thread.join(this.world.getTickStepNanos() / 1000000); + i += this.world.getTickStepNanos() / 1000000; + if (i > 5000) { + StringBuilder sb = new StringBuilder(); + + for (StackTraceElement traceElement : this.thread.getStackTrace()) { + sb.append("\tat ").append(traceElement).append('\n'); + } + + HytaleLogger.getLogger().at(Level.SEVERE).log("Forcing ChunkLighting Thread %s to stop:\n%s", this.thread, sb.toString()); + this.thread.stop(); + break; + } + } + } catch (InterruptedException var7) { + Thread.currentThread().interrupt(); + } + } + + public void init(WorldChunk worldChunk) { + this.lightCalculation.init(worldChunk); + } + + public void addToQueue(Vector3i chunkPosition) { + if (this.set.add(chunkPosition)) { + synchronized (this.queue) { + this.queue.enqueue(chunkPosition); + } + + this.semaphore.release(1); + } + } + + public boolean isQueued(int chunkX, int chunkZ) { + Vector3i chunkPos = new Vector3i(chunkX, 0, chunkZ); + + for (int chunkY = 0; chunkY < 10; chunkY++) { + chunkPos.setY(chunkY); + if (this.isQueued(chunkPos)) { + return true; + } + } + + return false; + } + + public boolean isQueued(Vector3i chunkPosition) { + return this.set.contains(chunkPosition); + } + + public int getQueueSize() { + synchronized (this.queue) { + return this.queue.size(); + } + } + + public boolean invalidateLightAtBlock(WorldChunk worldChunk, int blockX, int blockY, int blockZ, BlockType blockType, int oldHeight, int newHeight) { + return this.lightCalculation.invalidateLightAtBlock(worldChunk, blockX, blockY, blockZ, blockType, oldHeight, newHeight); + } + + public boolean invalidateLightInChunk(WorldChunk worldChunk) { + return this.lightCalculation.invalidateLightInChunkSections(worldChunk, 0, 10); + } + + public boolean invalidateLightInChunkSection(WorldChunk worldChunk, int sectionIndex) { + return this.lightCalculation.invalidateLightInChunkSections(worldChunk, sectionIndex, sectionIndex + 1); + } + + public boolean invalidateLightInChunkSections(WorldChunk worldChunk, int sectionIndexFrom, int sectionIndexTo) { + return this.lightCalculation.invalidateLightInChunkSections(worldChunk, sectionIndexFrom, sectionIndexTo); + } + + public void invalidateLoadedChunks() { + this.world.getChunkStore().getStore().forEachEntityParallel(WorldChunk.getComponentType(), (index, archetypeChunk, storeCommandBuffer) -> { + WorldChunk chunk = archetypeChunk.getComponent(index, WorldChunk.getComponentType()); + + for (int y = 0; y < 10; y++) { + BlockSection section = chunk.getBlockChunk().getSectionAtIndex(y); + section.invalidateLocalLight(); + if (BlockChunk.SEND_LOCAL_LIGHTING_DATA || BlockChunk.SEND_GLOBAL_LIGHTING_DATA) { + chunk.getBlockChunk().invalidateChunkSection(y); + } + } + }); + this.world.getChunkStore().getChunkIndexes().forEach(index -> { + int x = ChunkUtil.xOfChunkIndex(index); + int z = ChunkUtil.zOfChunkIndex(index); + + for (int y = 0; y < 10; y++) { + this.addToQueue(new Vector3i(x, y, z)); + } + }); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/lighting/FloodLightCalculation.java b/src/com/hypixel/hytale/server/core/universe/world/lighting/FloodLightCalculation.java new file mode 100644 index 0000000..c7f41fd --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/lighting/FloodLightCalculation.java @@ -0,0 +1,876 @@ +package com.hypixel.hytale.server.core.universe.world.lighting; + +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.metrics.metric.AverageCollector; +import com.hypixel.hytale.protocol.ColorLight; +import com.hypixel.hytale.protocol.Opacity; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.accessor.LocalCachedChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkLightData; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkLightDataBuilder; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import it.unimi.dsi.fastutil.ints.Int2IntFunction; +import it.unimi.dsi.fastutil.ints.IntIterator; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.Arrays; +import java.util.BitSet; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.IntBinaryOperator; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FloodLightCalculation implements LightCalculation { + protected final ChunkLightingManager chunkLightingManager; + protected final AverageCollector emptyAvg = new AverageCollector(); + protected final AverageCollector blocksAvg = new AverageCollector(); + protected final AverageCollector borderAvg = new AverageCollector(); + protected final AverageCollector avgChunk = new AverageCollector(); + protected final BlockSection[][] fromSections = new BlockSection[][]{ + new BlockSection[Vector3i.BLOCK_SIDES.length], new BlockSection[Vector3i.BLOCK_EDGES.length], new BlockSection[Vector3i.BLOCK_CORNERS.length] + }; + + public FloodLightCalculation(ChunkLightingManager chunkLightingManager) { + this.chunkLightingManager = chunkLightingManager; + } + + @Override + public void init(@Nonnull WorldChunk chunk) { + this.chunkLightingManager.getWorld().debugAssertInTickingThread(); + int x = chunk.getX(); + int z = chunk.getZ(); + this.initChunk(chunk, x, z); + this.initNeighbours(x, z); + } + + private void initChunk(int x, int z) { + WorldChunk chunk = this.chunkLightingManager.getWorld().getChunkIfInMemory(ChunkUtil.indexChunk(x, z)); + if (chunk != null) { + this.initChunk(chunk, x, z); + } + } + + private void initChunk(@Nonnull WorldChunk chunk, int x, int z) { + for (int y = 0; y < 10; y++) { + this.initSection(chunk, x, y, z); + } + } + + private void initNeighbours(int x, int z) { + this.initChunk(x - 1, z - 1); + this.initChunk(x - 1, z + 1); + this.initChunk(x + 1, z - 1); + this.initChunk(x + 1, z + 1); + this.initChunk(x - 1, z); + this.initChunk(x + 1, z); + this.initChunk(x, z - 1); + this.initChunk(x, z + 1); + } + + private void initSection(@Nonnull WorldChunk chunk, int x, int y, int z) { + BlockSection section = chunk.getBlockChunk().getSectionAtIndex(y); + if (!section.hasLocalLight()) { + this.chunkLightingManager.getLogger().at(Level.FINEST).log("Init chunk %d, %d, %d because doesn't have local light", x, y, z); + } else { + if (section.hasGlobalLight()) { + return; + } + + this.chunkLightingManager.getLogger().at(Level.FINEST).log("Init chunk %d, %d, %d because doesn't have global light", x, y, z); + } + + this.chunkLightingManager.addToQueue(new Vector3i(x, y, z)); + } + + private void initNeighbours(@Nonnull LocalCachedChunkAccessor accessor, int chunkX, int chunkY, int chunkZ) { + this.initNeighbourSections(accessor, chunkX - 1, chunkY, chunkZ - 1); + this.initNeighbourSections(accessor, chunkX - 1, chunkY, chunkZ + 1); + this.initNeighbourSections(accessor, chunkX + 1, chunkY, chunkZ - 1); + this.initNeighbourSections(accessor, chunkX + 1, chunkY, chunkZ + 1); + this.initNeighbourSections(accessor, chunkX - 1, chunkY, chunkZ); + this.initNeighbourSections(accessor, chunkX + 1, chunkY, chunkZ); + this.initNeighbourSections(accessor, chunkX, chunkY, chunkZ - 1); + this.initNeighbourSections(accessor, chunkX, chunkY, chunkZ + 1); + } + + private void initNeighbourSections(@Nonnull LocalCachedChunkAccessor accessor, int x, int y, int z) { + WorldChunk chunk = accessor.getChunkIfInMemory(x, z); + if (chunk != null) { + if (y < 9) { + this.initSection(chunk, x, y + 1, z); + } + + if (y > 0) { + this.initSection(chunk, x, y - 1, z); + } + } + } + + @Nonnull + @Override + public CalculationResult calculateLight(@Nonnull Vector3i chunkPosition) { + int chunkX = chunkPosition.x; + int chunkY = chunkPosition.y; + int chunkZ = chunkPosition.z; + WorldChunk worldChunk = this.chunkLightingManager.getWorld().getChunkIfInMemory(ChunkUtil.indexChunk(chunkX, chunkZ)); + if (worldChunk == null) { + return CalculationResult.NOT_LOADED; + } else { + AtomicLong chunkLightTiming = worldChunk.chunkLightTiming; + boolean fineLoggable = this.chunkLightingManager.getLogger().at(Level.FINE).isEnabled(); + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atChunkCoords(this.chunkLightingManager.getWorld(), chunkX, chunkZ, 1); + accessor.overwrite(worldChunk); + CompletableFuture.runAsync(accessor::cacheChunksInRadius, this.chunkLightingManager.getWorld()).join(); + BlockSection toSection = worldChunk.getBlockChunk().getSectionAtIndex(chunkY); + FluidSection fluidSection = CompletableFuture.supplyAsync(() -> { + Ref sectionx = this.chunkLightingManager.getWorld().getChunkStore().getChunkSectionReference(chunkX, chunkY, chunkZ); + return sectionx == null ? null : sectionx.getStore().getComponent(sectionx, FluidSection.getComponentType()); + }, this.chunkLightingManager.getWorld()).join(); + if (fluidSection == null) { + return CalculationResult.NOT_LOADED; + } else if (toSection.hasLocalLight() && toSection.hasGlobalLight()) { + this.initNeighbours(accessor, chunkX, chunkY, chunkZ); + return CalculationResult.DONE; + } else { + if (!toSection.hasLocalLight()) { + CalculationResult localLightResult = this.updateLocalLight( + accessor, worldChunk, chunkX, chunkY, chunkZ, toSection, fluidSection, chunkLightTiming, fineLoggable + ); + switch (localLightResult) { + case NOT_LOADED: + case INVALIDATED: + case WAITING_FOR_NEIGHBOUR: + return localLightResult; + case DONE: + default: + this.initNeighbours(accessor, chunkX, chunkY, chunkZ); + } + } + + if (!toSection.hasGlobalLight()) { + CalculationResult globalLightResult = this.updateGlobalLight( + accessor, worldChunk, chunkX, chunkY, chunkZ, toSection, chunkLightTiming, fineLoggable + ); + switch (globalLightResult) { + case NOT_LOADED: + case INVALIDATED: + case WAITING_FOR_NEIGHBOUR: + return globalLightResult; + case DONE: + } + } + + if (fineLoggable) { + long chunkDiff = chunkLightTiming.get(); + boolean done = chunkDiff != 0L; + + for (int i = 0; i < 10; i++) { + BlockSection section = worldChunk.getBlockChunk().getSectionAtIndex(i); + done = done && section.hasLocalLight() && section.hasGlobalLight(); + } + + if (done) { + this.avgChunk.add(chunkDiff); + this.chunkLightingManager + .getLogger() + .at(Level.FINE) + .log( + "Flood Chunk: Took %s at %d, %d - Avg: %s", + FormatUtil.nanosToString(chunkDiff), + chunkX, + chunkZ, + FormatUtil.nanosToString((long)this.avgChunk.get()) + ); + } + } + + if (BlockChunk.SEND_LOCAL_LIGHTING_DATA || BlockChunk.SEND_GLOBAL_LIGHTING_DATA) { + worldChunk.getBlockChunk().invalidateChunkSection(chunkY); + } + + return CalculationResult.DONE; + } + } + } + + @Nonnull + public CalculationResult updateLocalLight( + LocalCachedChunkAccessor accessor, + @Nonnull WorldChunk worldChunk, + int chunkX, + int chunkY, + int chunkZ, + @Nonnull BlockSection toSection, + @Nonnull FluidSection fluidSection, + @Nonnull AtomicLong chunkLightTiming, + boolean fineLoggable + ) { + long start = System.nanoTime(); + boolean solidAir = toSection.isSolidAir() && fluidSection.isEmpty(); + ChunkLightDataBuilder localLight; + if (solidAir) { + localLight = this.floodEmptyChunkSection(worldChunk, toSection.getLocalChangeCounter(), chunkY); + } else { + localLight = this.floodChunkSection(worldChunk, toSection, fluidSection, chunkY); + } + + toSection.setLocalLight(localLight); + worldChunk.markNeedsSaving(); + if (fineLoggable) { + long end = System.nanoTime(); + long diff = end - start; + if (solidAir) { + this.emptyAvg.add(diff); + } else { + this.blocksAvg.add(diff); + } + + chunkLightTiming.addAndGet(diff); + this.chunkLightingManager + .getLogger() + .at(Level.FINER) + .log( + "Flood Chunk Section (local): Took %s at %d, %d, %d - %s Avg: %s", + FormatUtil.nanosToString(diff), + chunkX, + chunkY, + chunkZ, + solidAir ? "air" : "blocks", + FormatUtil.nanosToString((long)(solidAir ? this.emptyAvg.get() : this.blocksAvg.get())) + ); + } + + if (!toSection.hasLocalLight()) { + this.chunkLightingManager + .getLogger() + .at(Level.FINEST) + .log( + "Chunk Section still needs relight! (local) %d, %d, %d - %d != %d (counter != id)", + chunkX, + chunkY, + chunkZ, + toSection.getLocalChangeCounter(), + toSection.getLocalLight().getChangeId() + ); + return CalculationResult.INVALIDATED; + } else { + return CalculationResult.DONE; + } + } + + @Nonnull + public CalculationResult updateGlobalLight( + @Nonnull LocalCachedChunkAccessor accessor, + @Nonnull WorldChunk worldChunk, + int chunkX, + int chunkY, + int chunkZ, + @Nonnull BlockSection toSection, + @Nonnull AtomicLong chunkLightTiming, + boolean fineLoggable + ) { + long start = System.nanoTime(); + if (this.testNeighboursForLocalLight(accessor, worldChunk, chunkX, chunkY, chunkZ)) { + return CalculationResult.WAITING_FOR_NEIGHBOUR; + } else { + ChunkLightDataBuilder globalLight = new ChunkLightDataBuilder(toSection.getLocalLight(), toSection.getGlobalChangeCounter()); + BitSet bitSetQueue = new BitSet(32768); + this.propagateSides(toSection, globalLight, bitSetQueue); + this.propagateEdges(toSection, globalLight, bitSetQueue); + this.propagateCorners(toSection, globalLight, bitSetQueue); + this.propagateLight(bitSetQueue, toSection, globalLight); + toSection.setGlobalLight(globalLight); + worldChunk.markNeedsSaving(); + if (fineLoggable) { + long end = System.nanoTime(); + long diff = end - start; + chunkLightTiming.addAndGet(diff); + this.borderAvg.add(diff); + this.chunkLightingManager + .getLogger() + .at(Level.FINER) + .log( + "Flood Chunk Section (global): Took " + + FormatUtil.nanosToString(diff) + + " at " + + chunkX + + ", " + + chunkY + + ", " + + chunkZ + + " - Avg: " + + FormatUtil.nanosToString((long)this.borderAvg.get()) + ); + } + + if (!toSection.hasGlobalLight()) { + this.chunkLightingManager + .getLogger() + .at(Level.FINEST) + .log( + "Chunk Section still needs relight! (global) %d, %d, %d - %d != %d (counter != id)", + chunkX, + chunkY, + chunkZ, + toSection.getGlobalChangeCounter(), + toSection.getGlobalLight().getChangeId() + ); + return CalculationResult.INVALIDATED; + } else { + return CalculationResult.DONE; + } + } + } + + @Override + public boolean invalidateLightAtBlock( + @Nonnull WorldChunk worldChunk, int blockX, int blockY, int blockZ, @Nonnull BlockType blockType, int oldHeight, int newHeight + ) { + int chunkX = worldChunk.getX(); + int chunkY = blockY >> 5; + int chunkZ = worldChunk.getZ(); + int oldHeightChunk = oldHeight >> 5; + int newHeightChunk = newHeight >> 5; + int from = Math.max(MathUtil.minValue(oldHeightChunk, newHeightChunk, chunkY), 0); + int to = MathUtil.maxValue(oldHeightChunk, newHeightChunk, chunkY) + 1; + boolean handled = this.invalidateLightInChunkSections(worldChunk, from, to); + this.chunkLightingManager + .getLogger() + .at(Level.FINER) + .log("updateLightAtBlock(%d, %d, %d, %s): %d, %d, %d", blockX, blockY, blockZ, blockType.getId(), chunkX, chunkY, chunkZ); + return handled; + } + + @Override + public boolean invalidateLightInChunkSections(@Nonnull WorldChunk worldChunk, int sectionIndexFrom, int sectionIndexTo) { + int chunkX = worldChunk.getX(); + int chunkZ = worldChunk.getZ(); + World world = this.chunkLightingManager.getWorld(); + LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atChunkCoords(world, chunkX, chunkZ, 1); + accessor.overwrite(worldChunk); + if (!world.isInThread()) { + CompletableFuture.runAsync(accessor::cacheChunksInRadius, world).join(); + } else { + accessor.cacheChunksInRadius(); + } + + for (int x = chunkX - 1; x <= chunkX + 1; x++) { + for (int z = chunkZ - 1; z <= chunkZ + 1; z++) { + WorldChunk worldChunkTemp = accessor.getChunkIfInMemory(x, z); + if (worldChunkTemp != null) { + for (int y = sectionIndexTo - 1; y >= sectionIndexFrom; y--) { + BlockSection section = worldChunkTemp.getBlockChunk().getSectionAtIndex(y); + if (worldChunkTemp == worldChunk) { + section.invalidateLocalLight(); + } else { + section.invalidateGlobalLight(); + } + + if (BlockChunk.SEND_LOCAL_LIGHTING_DATA || BlockChunk.SEND_GLOBAL_LIGHTING_DATA) { + worldChunkTemp.getBlockChunk().invalidateChunkSection(y); + } + } + } + } + } + + for (int x = chunkX - 1; x <= chunkX + 1; x++) { + for (int zx = chunkZ - 1; zx <= chunkZ + 1; zx++) { + WorldChunk worldChunkTemp = accessor.getChunkIfInMemory(x, zx); + if (worldChunkTemp != null) { + for (int y = sectionIndexTo - 1; y >= sectionIndexFrom; y--) { + this.chunkLightingManager.addToQueue(new Vector3i(x, y, zx)); + } + } + } + } + + return false; + } + + @Nonnull + private ChunkLightDataBuilder floodEmptyChunkSection(@Nonnull WorldChunk worldChunk, short changeCounter, int chunkY) { + int sectionY = chunkY * 32; + ChunkLightDataBuilder light = new ChunkLightDataBuilder(changeCounter); + BitSet bitSetQueue = new BitSet(1024); + + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + int column = ChunkUtil.indexColumn(x, z); + short height = worldChunk.getHeight(column); + if (sectionY > height) { + for (int y = 0; y < 32; y++) { + light.setLight(ChunkUtil.indexBlockFromColumn(column, y), 3, (byte)15); + } + + bitSetQueue.set(column); + } + } + } + + if (bitSetQueue.cardinality() < 1024) { + IntSet changedColumns = new IntOpenHashSet(1024); + int counter = 0; + + while (true) { + int column = bitSetQueue.nextSetBit(counter); + if (column == -1) { + if (bitSetQueue.isEmpty()) { + IntIterator iterator = changedColumns.iterator(); + + while (iterator.hasNext()) { + int columnx = iterator.nextInt(); + byte skyLight = light.getLight(columnx, 3); + + for (int y = 1; y < 32; y++) { + light.setLight(ChunkUtil.indexBlockFromColumn(columnx, y), 3, skyLight); + } + } + break; + } + + counter = 0; + } else { + bitSetQueue.clear(column); + counter = column; + int x = ChunkUtil.xFromColumn(column); + int zx = ChunkUtil.zFromColumn(column); + byte skyLight = light.getLight(column, 3); + byte propagatedValue = (byte)(skyLight - 1); + if (propagatedValue >= 1) { + for (Vector2i side : Vector2i.DIRECTIONS) { + int nx = x + side.x; + int nz = zx + side.y; + if (nx >= 0 && nx < 32 && nz >= 0 && nz < 32) { + int neighbourColumn = ChunkUtil.indexColumn(nx, nz); + byte neighbourSkyLight = light.getLight(neighbourColumn, 3); + if (neighbourSkyLight < propagatedValue) { + light.setLight(neighbourColumn, 3, propagatedValue); + changedColumns.add(neighbourColumn); + if (propagatedValue > 1) { + bitSetQueue.set(neighbourColumn); + } + } + } + } + } + } + } + } + + return light; + } + + @Nonnull + private ChunkLightDataBuilder floodChunkSection( + @Nonnull WorldChunk worldChunk, @Nonnull BlockSection toSection, @Nonnull FluidSection fluidSection, int chunkY + ) { + int sectionY = chunkY * 32; + ChunkLightDataBuilder toLight = new ChunkLightDataBuilder(toSection.getLocalChangeCounter()); + BitSet bitSetQueue = new BitSet(32768); + + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + int column = ChunkUtil.indexColumn(x, z); + short height = worldChunk.getHeight(column); + + for (int y = 0; y < 32; y++) { + int blockIndex = ChunkUtil.indexBlockFromColumn(column, y); + byte skyValue = this.getSkyValue(worldChunk, chunkY, x, y, z, sectionY, height); + short lightValue = (short)(skyValue << 12); + int blockId = toSection.get(blockIndex); + BlockType blockType = BlockType.getAssetMap().getAsset(blockId); + ColorLight blockTypeLight = blockType.getLight(); + int fluidId = fluidSection.getFluidId(blockIndex); + Fluid fluid = Fluid.getAssetMap().getAsset(fluidId); + ColorLight fluidLight = fluid.getLight(); + if (blockTypeLight != null && fluidLight != null) { + lightValue = ChunkLightData.combineLightValues( + (byte)Math.max(blockTypeLight.red, fluidLight.red), + (byte)Math.max(blockTypeLight.green, fluidLight.green), + (byte)Math.max(blockTypeLight.blue, fluidLight.blue), + skyValue + ); + } else if (fluidLight != null) { + lightValue = ChunkLightData.combineLightValues(fluidLight.red, fluidLight.green, fluidLight.blue, skyValue); + } else if (blockTypeLight != null) { + lightValue = ChunkLightData.combineLightValues(blockTypeLight.red, blockTypeLight.green, blockTypeLight.blue, skyValue); + } + + if (lightValue != 0) { + toLight.setLightRaw(blockIndex, lightValue); + bitSetQueue.set(blockIndex); + } + } + } + } + + this.propagateLight(bitSetQueue, toSection, toLight); + return toLight; + } + + protected byte getSkyValue(WorldChunk worldChunk, int chunkY, int blockX, int blockY, int blockZ, int sectionY, int height) { + int originY = sectionY + blockY; + boolean hasSky = originY >= height; + return (byte)(hasSky ? 15 : 0); + } + + private void propagateLight(@Nonnull BitSet bitSetQueue, @Nonnull BlockSection section, @Nonnull ChunkLightDataBuilder light) { + int counter = 0; + + while (true) { + int blockIndex = bitSetQueue.nextSetBit(counter); + if (blockIndex == -1) { + if (bitSetQueue.isEmpty()) { + return; + } + + counter = 0; + } else { + bitSetQueue.clear(blockIndex); + counter = blockIndex; + BlockType fromBlockType = BlockType.getAssetMap().getAsset(section.get(blockIndex)); + Opacity fromOpacity = fromBlockType.getOpacity(); + if (fromOpacity != Opacity.Solid) { + short lightValue = light.getLightRaw(blockIndex); + byte redLight = ChunkLightData.getLightValue(lightValue, 0); + byte greenLight = ChunkLightData.getLightValue(lightValue, 1); + byte blueLight = ChunkLightData.getLightValue(lightValue, 2); + byte skyLight = ChunkLightData.getLightValue(lightValue, 3); + if (redLight >= 2 || greenLight >= 2 || blueLight >= 2 || skyLight >= 2) { + byte propagatedRedValue = (byte)(redLight - 1); + byte propagatedGreenValue = (byte)(greenLight - 1); + byte propagatedBlueValue = (byte)(blueLight - 1); + byte propagatedSkyValue = (byte)(skyLight - 1); + if (fromOpacity == Opacity.Semitransparent || fromOpacity == Opacity.Cutout) { + propagatedRedValue--; + propagatedGreenValue--; + propagatedBlueValue--; + propagatedSkyValue--; + } + + if (propagatedRedValue >= 1 || propagatedGreenValue >= 1 || propagatedBlueValue >= 1 || propagatedSkyValue >= 1) { + int x = ChunkUtil.xFromIndex(blockIndex); + int y = ChunkUtil.yFromIndex(blockIndex); + int z = ChunkUtil.zFromIndex(blockIndex); + + for (Vector3i side : Vector3i.BLOCK_SIDES) { + int nx = x + side.x; + if (nx >= 0 && nx < 32) { + int ny = y + side.y; + if (ny >= 0 && ny < 32) { + int nz = z + side.z; + if (nz >= 0 && nz < 32) { + int neighbourBlock = ChunkUtil.indexBlock(nx, ny, nz); + this.propagateLight( + bitSetQueue, + propagatedRedValue, + propagatedGreenValue, + propagatedBlueValue, + propagatedSkyValue, + section, + light, + neighbourBlock + ); + } + } + } + } + } + } + } + } + } + } + + public boolean testNeighboursForLocalLight(@Nonnull LocalCachedChunkAccessor accessor, @Nonnull WorldChunk worldChunk, int chunkX, int chunkY, int chunkZ) { + Vector3i[][] blockParts = Vector3i.BLOCK_PARTS; + + for (int partType = 0; partType < this.fromSections.length; partType++) { + BlockSection[] partSections = this.fromSections[partType]; + Arrays.fill(partSections, null); + Vector3i[] directions = blockParts[partType]; + + for (int i = 0; i < directions.length; i++) { + Vector3i side = directions[i]; + int nx = chunkX + side.x; + int ny = chunkY + side.y; + int nz = chunkZ + side.z; + if (ny >= 0 && ny < 10) { + if (nx == chunkX && nz == chunkZ) { + BlockSection fromSection = worldChunk.getBlockChunk().getSectionAtIndex(ny); + if (!fromSection.hasLocalLight()) { + return true; + } + + partSections[i] = fromSection; + } else { + WorldChunk neighbourChunk = accessor.getChunkIfInMemory(nx, nz); + if (neighbourChunk == null) { + return true; + } + + BlockSection fromSection = neighbourChunk.getBlockChunk().getSectionAtIndex(ny); + if (!fromSection.hasLocalLight()) { + return true; + } + + partSections[i] = fromSection; + } + } + } + } + + return false; + } + + public void propagateSides(@Nonnull BlockSection toSection, @Nonnull ChunkLightDataBuilder globalLight, @Nonnull BitSet bitSetQueue) { + BlockSection[] fromSectionsSides = this.fromSections[0]; + int i = 0; + this.propagateSide( + bitSetQueue, fromSectionsSides[i++], toSection, globalLight, (a, b) -> ChunkUtil.indexBlock(a, 0, b), (a, b) -> ChunkUtil.indexBlock(a, 31, b) + ); + this.propagateSide( + bitSetQueue, fromSectionsSides[i++], toSection, globalLight, (a, b) -> ChunkUtil.indexBlock(a, 31, b), (a, b) -> ChunkUtil.indexBlock(a, 0, b) + ); + this.propagateSide( + bitSetQueue, fromSectionsSides[i++], toSection, globalLight, (a, b) -> ChunkUtil.indexBlock(a, b, 31), (a, b) -> ChunkUtil.indexBlock(a, b, 0) + ); + this.propagateSide( + bitSetQueue, fromSectionsSides[i++], toSection, globalLight, (a, b) -> ChunkUtil.indexBlock(a, b, 0), (a, b) -> ChunkUtil.indexBlock(a, b, 31) + ); + this.propagateSide( + bitSetQueue, fromSectionsSides[i++], toSection, globalLight, (a, b) -> ChunkUtil.indexBlock(31, a, b), (a, b) -> ChunkUtil.indexBlock(0, a, b) + ); + this.propagateSide( + bitSetQueue, fromSectionsSides[i++], toSection, globalLight, (a, b) -> ChunkUtil.indexBlock(0, a, b), (a, b) -> ChunkUtil.indexBlock(31, a, b) + ); + } + + private void propagateSide( + @Nonnull BitSet bitSetQueue, + @Nullable BlockSection fromSection, + @Nonnull BlockSection toSection, + @Nonnull ChunkLightDataBuilder toLight, + @Nonnull IntBinaryOperator fromIndex, + @Nonnull IntBinaryOperator toIndex + ) { + if (fromSection != null) { + ChunkLightData fromLight = fromSection.getLocalLight(); + + for (int a = 0; a < 32; a++) { + for (int b = 0; b < 32; b++) { + int fromBlockIndex = fromIndex.applyAsInt(a, b); + int toBlockIndex = toIndex.applyAsInt(a, b); + BlockType fromBlockType = BlockType.getAssetMap().getAsset(fromSection.get(fromBlockIndex)); + Opacity fromOpacity = fromBlockType.getOpacity(); + if (fromOpacity != Opacity.Solid) { + short lightValue = fromLight.getLightRaw(fromBlockIndex); + byte redLight = ChunkLightData.getLightValue(lightValue, 0); + byte greenLight = ChunkLightData.getLightValue(lightValue, 1); + byte blueLight = ChunkLightData.getLightValue(lightValue, 2); + byte skyLight = ChunkLightData.getLightValue(lightValue, 3); + if (redLight >= 2 || greenLight >= 2 || blueLight >= 2 || skyLight >= 2) { + byte propagatedRedValue = (byte)(redLight - 1); + byte propagatedGreenValue = (byte)(greenLight - 1); + byte propagatedBlueValue = (byte)(blueLight - 1); + byte propagatedSkyValue = (byte)(skyLight - 1); + if (fromOpacity == Opacity.Semitransparent || fromOpacity == Opacity.Cutout) { + propagatedRedValue--; + propagatedGreenValue--; + propagatedBlueValue--; + propagatedSkyValue--; + } + + if (propagatedRedValue >= 1 || propagatedGreenValue >= 1 || propagatedBlueValue >= 1 || propagatedSkyValue >= 1) { + this.propagateLight( + bitSetQueue, propagatedRedValue, propagatedGreenValue, propagatedBlueValue, propagatedSkyValue, toSection, toLight, toBlockIndex + ); + } + } + } + } + } + } + } + + public void propagateEdges(@Nonnull BlockSection toSection, @Nonnull ChunkLightDataBuilder globalLight, @Nonnull BitSet bitSetQueue) { + BlockSection[] fromSectionsEdges = this.fromSections[1]; + int i = 0; + this.propagateEdge(bitSetQueue, fromSectionsEdges[i++], toSection, globalLight, a -> ChunkUtil.indexBlock(a, 0, 31), a -> ChunkUtil.indexBlock(a, 31, 0)); + this.propagateEdge(bitSetQueue, fromSectionsEdges[i++], toSection, globalLight, a -> ChunkUtil.indexBlock(a, 31, 31), a -> ChunkUtil.indexBlock(a, 0, 0)); + this.propagateEdge(bitSetQueue, fromSectionsEdges[i++], toSection, globalLight, a -> ChunkUtil.indexBlock(a, 0, 0), a -> ChunkUtil.indexBlock(a, 31, 31)); + this.propagateEdge(bitSetQueue, fromSectionsEdges[i++], toSection, globalLight, a -> ChunkUtil.indexBlock(a, 31, 0), a -> ChunkUtil.indexBlock(a, 0, 31)); + this.propagateEdge(bitSetQueue, fromSectionsEdges[i++], toSection, globalLight, a -> ChunkUtil.indexBlock(31, 0, a), a -> ChunkUtil.indexBlock(0, 31, a)); + this.propagateEdge(bitSetQueue, fromSectionsEdges[i++], toSection, globalLight, a -> ChunkUtil.indexBlock(31, 31, a), a -> ChunkUtil.indexBlock(0, 0, a)); + this.propagateEdge(bitSetQueue, fromSectionsEdges[i++], toSection, globalLight, a -> ChunkUtil.indexBlock(0, 0, a), a -> ChunkUtil.indexBlock(31, 31, a)); + this.propagateEdge(bitSetQueue, fromSectionsEdges[i++], toSection, globalLight, a -> ChunkUtil.indexBlock(0, 31, a), a -> ChunkUtil.indexBlock(31, 0, a)); + this.propagateEdge(bitSetQueue, fromSectionsEdges[i++], toSection, globalLight, a -> ChunkUtil.indexBlock(31, a, 31), a -> ChunkUtil.indexBlock(0, a, 0)); + this.propagateEdge(bitSetQueue, fromSectionsEdges[i++], toSection, globalLight, a -> ChunkUtil.indexBlock(0, a, 31), a -> ChunkUtil.indexBlock(31, a, 0)); + this.propagateEdge(bitSetQueue, fromSectionsEdges[i++], toSection, globalLight, a -> ChunkUtil.indexBlock(31, a, 0), a -> ChunkUtil.indexBlock(0, a, 31)); + this.propagateEdge(bitSetQueue, fromSectionsEdges[i++], toSection, globalLight, a -> ChunkUtil.indexBlock(0, a, 0), a -> ChunkUtil.indexBlock(31, a, 31)); + } + + private void propagateEdge( + @Nonnull BitSet bitSetQueue, + @Nullable BlockSection fromSection, + @Nonnull BlockSection toSection, + @Nonnull ChunkLightDataBuilder toLight, + @Nonnull Int2IntFunction fromIndex, + @Nonnull Int2IntFunction toIndex + ) { + if (fromSection != null) { + ChunkLightData fromLight = fromSection.getLocalLight(); + + for (int a = 0; a < 32; a++) { + int fromBlockIndex = fromIndex.applyAsInt(a); + int toBlockIndex = toIndex.applyAsInt(a); + BlockType fromBlockType = BlockType.getAssetMap().getAsset(fromSection.get(fromBlockIndex)); + Opacity fromOpacity = fromBlockType.getOpacity(); + if (fromOpacity != Opacity.Solid) { + short lightValue = fromLight.getLightRaw(fromBlockIndex); + byte redLight = ChunkLightData.getLightValue(lightValue, 0); + byte greenLight = ChunkLightData.getLightValue(lightValue, 1); + byte blueLight = ChunkLightData.getLightValue(lightValue, 2); + byte skyLight = ChunkLightData.getLightValue(lightValue, 3); + if (redLight >= 3 || greenLight >= 3 || blueLight >= 3 || skyLight >= 3) { + byte propagatedRedValue = (byte)(redLight - 2); + byte propagatedGreenValue = (byte)(greenLight - 2); + byte propagatedBlueValue = (byte)(blueLight - 2); + byte propagatedSkyValue = (byte)(skyLight - 2); + if (fromOpacity == Opacity.Semitransparent || fromOpacity == Opacity.Cutout) { + propagatedRedValue--; + propagatedGreenValue--; + propagatedBlueValue--; + propagatedSkyValue--; + } + + if (propagatedRedValue >= 1 || propagatedGreenValue >= 1 || propagatedBlueValue >= 1 || propagatedSkyValue >= 1) { + this.propagateLight( + bitSetQueue, propagatedRedValue, propagatedGreenValue, propagatedBlueValue, propagatedSkyValue, toSection, toLight, toBlockIndex + ); + } + } + } + } + } + } + + public void propagateCorners(@Nonnull BlockSection toSection, @Nonnull ChunkLightDataBuilder globalLight, @Nonnull BitSet bitSetQueue) { + BlockSection[] fromSectionsCorners = this.fromSections[2]; + int i = 0; + this.propagateCorner(bitSetQueue, fromSectionsCorners[i++], toSection, globalLight, ChunkUtil.indexBlock(31, 0, 31), ChunkUtil.indexBlock(0, 31, 0)); + this.propagateCorner(bitSetQueue, fromSectionsCorners[i++], toSection, globalLight, ChunkUtil.indexBlock(0, 0, 31), ChunkUtil.indexBlock(31, 31, 0)); + this.propagateCorner(bitSetQueue, fromSectionsCorners[i++], toSection, globalLight, ChunkUtil.indexBlock(31, 31, 31), ChunkUtil.indexBlock(0, 0, 0)); + this.propagateCorner(bitSetQueue, fromSectionsCorners[i++], toSection, globalLight, ChunkUtil.indexBlock(0, 31, 31), ChunkUtil.indexBlock(31, 0, 0)); + this.propagateCorner(bitSetQueue, fromSectionsCorners[i++], toSection, globalLight, ChunkUtil.indexBlock(31, 0, 0), ChunkUtil.indexBlock(0, 31, 31)); + this.propagateCorner(bitSetQueue, fromSectionsCorners[i++], toSection, globalLight, ChunkUtil.indexBlock(0, 0, 0), ChunkUtil.indexBlock(31, 31, 31)); + this.propagateCorner(bitSetQueue, fromSectionsCorners[i++], toSection, globalLight, ChunkUtil.indexBlock(31, 31, 0), ChunkUtil.indexBlock(0, 0, 31)); + this.propagateCorner(bitSetQueue, fromSectionsCorners[i++], toSection, globalLight, ChunkUtil.indexBlock(0, 31, 0), ChunkUtil.indexBlock(31, 0, 31)); + } + + private void propagateCorner( + @Nonnull BitSet bitSetQueue, + @Nullable BlockSection fromSection, + @Nonnull BlockSection toSection, + @Nonnull ChunkLightDataBuilder toLight, + int fromBlockIndex, + int toBlockIndex + ) { + if (fromSection != null) { + ChunkLightData fromLight = fromSection.getLocalLight(); + BlockType fromBlockType = BlockType.getAssetMap().getAsset(fromSection.get(fromBlockIndex)); + Opacity fromOpacity = fromBlockType.getOpacity(); + if (fromOpacity != Opacity.Solid) { + short lightValue = fromLight.getLightRaw(fromBlockIndex); + byte redLight = ChunkLightData.getLightValue(lightValue, 0); + byte greenLight = ChunkLightData.getLightValue(lightValue, 1); + byte blueLight = ChunkLightData.getLightValue(lightValue, 2); + byte skyLight = ChunkLightData.getLightValue(lightValue, 3); + if (redLight >= 4 || greenLight >= 4 || blueLight >= 4 || skyLight >= 4) { + byte propagatedRedValue = (byte)(redLight - 3); + byte propagatedGreenValue = (byte)(greenLight - 3); + byte propagatedBlueValue = (byte)(blueLight - 3); + byte propagatedSkyValue = (byte)(skyLight - 3); + if (fromOpacity == Opacity.Semitransparent || fromOpacity == Opacity.Cutout) { + propagatedRedValue--; + propagatedGreenValue--; + propagatedBlueValue--; + propagatedSkyValue--; + } + + if (propagatedRedValue >= 1 || propagatedGreenValue >= 1 || propagatedBlueValue >= 1 || propagatedSkyValue >= 1) { + this.propagateLight( + bitSetQueue, propagatedRedValue, propagatedGreenValue, propagatedBlueValue, propagatedSkyValue, toSection, toLight, toBlockIndex + ); + } + } + } + } + } + + private void propagateLight( + @Nonnull BitSet bitSetQueue, + byte propagatedRedValue, + byte propagatedGreenValue, + byte propagatedBlueValue, + byte propagatedSkyValue, + @Nonnull BlockSection toSection, + @Nonnull ChunkLightDataBuilder toLight, + int toBlockIndex + ) { + BlockType toBlockType = BlockType.getAssetMap().getAsset(toSection.get(toBlockIndex)); + Opacity toOpacity = toBlockType.getOpacity(); + if (toOpacity == Opacity.Cutout) { + propagatedRedValue--; + propagatedGreenValue--; + propagatedBlueValue--; + propagatedSkyValue--; + } + + if (propagatedRedValue >= 1 || propagatedGreenValue >= 1 || propagatedBlueValue >= 1 || propagatedSkyValue >= 1) { + short oldLightValue = toLight.getLightRaw(toBlockIndex); + byte neighbourRedLight = ChunkLightData.getLightValue(oldLightValue, 0); + byte neighbourGreenLight = ChunkLightData.getLightValue(oldLightValue, 1); + byte neighbourBlueLight = ChunkLightData.getLightValue(oldLightValue, 2); + byte neighbourSkyLight = ChunkLightData.getLightValue(oldLightValue, 3); + if (neighbourRedLight < propagatedRedValue) { + neighbourRedLight = propagatedRedValue; + } + + if (neighbourGreenLight < propagatedGreenValue) { + neighbourGreenLight = propagatedGreenValue; + } + + if (neighbourBlueLight < propagatedBlueValue) { + neighbourBlueLight = propagatedBlueValue; + } + + if (neighbourSkyLight < propagatedSkyValue) { + neighbourSkyLight = propagatedSkyValue; + } + + short newLightValue = (short)((neighbourRedLight & 15) << 0); + newLightValue = (short)(newLightValue | (neighbourGreenLight & 15) << 4); + newLightValue = (short)(newLightValue | (neighbourBlueLight & 15) << 8); + newLightValue = (short)(newLightValue | (neighbourSkyLight & 15) << 12); + toLight.setLightRaw(toBlockIndex, newLightValue); + if (newLightValue != oldLightValue && (propagatedRedValue > 1 || propagatedGreenValue > 1 || propagatedBlueValue > 1 || propagatedSkyValue > 1)) { + bitSetQueue.set(toBlockIndex); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/lighting/FullBrightLightCalculation.java b/src/com/hypixel/hytale/server/core/universe/world/lighting/FullBrightLightCalculation.java new file mode 100644 index 0000000..d419328 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/lighting/FullBrightLightCalculation.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.server.core.universe.world.lighting; + +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkLightDataBuilder; +import javax.annotation.Nonnull; + +public class FullBrightLightCalculation implements LightCalculation { + private final ChunkLightingManager chunkLightingManager; + private LightCalculation delegate; + + public FullBrightLightCalculation(ChunkLightingManager chunkLightingManager, LightCalculation delegate) { + this.chunkLightingManager = chunkLightingManager; + this.delegate = delegate; + } + + @Override + public void init(@Nonnull WorldChunk worldChunk) { + this.delegate.init(worldChunk); + } + + @Nonnull + @Override + public CalculationResult calculateLight(@Nonnull Vector3i chunkPosition) { + CalculationResult result = this.delegate.calculateLight(chunkPosition); + if (result == CalculationResult.DONE) { + WorldChunk worldChunk = this.chunkLightingManager.getWorld().getChunkIfInMemory(ChunkUtil.indexChunk(chunkPosition.x, chunkPosition.z)); + if (worldChunk == null) { + return CalculationResult.NOT_LOADED; + } + + this.setFullBright(worldChunk, chunkPosition.y); + } + + return result; + } + + @Override + public boolean invalidateLightAtBlock( + @Nonnull WorldChunk worldChunk, int blockX, int blockY, int blockZ, @Nonnull BlockType blockType, int oldHeight, int newHeight + ) { + boolean handled = this.delegate.invalidateLightAtBlock(worldChunk, blockX, blockY, blockZ, blockType, oldHeight, newHeight); + if (handled) { + this.setFullBright(worldChunk, blockY >> 5); + } + + return handled; + } + + @Override + public boolean invalidateLightInChunkSections(@Nonnull WorldChunk worldChunk, int sectionIndexFrom, int sectionIndexTo) { + boolean handled = this.delegate.invalidateLightInChunkSections(worldChunk, sectionIndexFrom, sectionIndexTo); + if (handled) { + for (int y = sectionIndexTo - 1; y >= sectionIndexFrom; y--) { + this.setFullBright(worldChunk, y); + } + } + + return handled; + } + + public void setFullBright(@Nonnull WorldChunk worldChunk, int chunkY) { + BlockSection section = worldChunk.getBlockChunk().getSectionAtIndex(chunkY); + ChunkLightDataBuilder light = new ChunkLightDataBuilder(section.getGlobalChangeCounter()); + + for (int i = 0; i < 32768; i++) { + light.setSkyLight(i, (byte)15); + } + + section.setGlobalLight(light); + if (BlockChunk.SEND_LOCAL_LIGHTING_DATA || BlockChunk.SEND_GLOBAL_LIGHTING_DATA) { + worldChunk.getBlockChunk().invalidateChunkSection(chunkY); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/lighting/LightCalculation.java b/src/com/hypixel/hytale/server/core/universe/world/lighting/LightCalculation.java new file mode 100644 index 0000000..bce852c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/lighting/LightCalculation.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.server.core.universe.world.lighting; + +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import javax.annotation.Nonnull; + +public interface LightCalculation { + void init(@Nonnull WorldChunk var1); + + @Nonnull + CalculationResult calculateLight(@Nonnull Vector3i var1); + + boolean invalidateLightAtBlock(@Nonnull WorldChunk var1, int var2, int var3, int var4, @Nonnull BlockType var5, int var6, int var7); + + boolean invalidateLightInChunkSections(@Nonnull WorldChunk var1, int var2, int var3); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/map/WorldMap.java b/src/com/hypixel/hytale/server/core/universe/world/map/WorldMap.java new file mode 100644 index 0000000..b09522f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/map/WorldMap.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.server.core.universe.world.map; + +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.packets.worldmap.MapChunk; +import com.hypixel.hytale.protocol.packets.worldmap.MapImage; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.protocol.packets.worldmap.UpdateWorldMap; +import com.hypixel.hytale.server.core.io.NetworkSerializable; +import com.hypixel.hytale.server.core.util.PositionUtil; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class WorldMap implements NetworkSerializable { + private final Map pointsOfInterest = new Object2ObjectOpenHashMap<>(); + @Nonnull + private final Long2ObjectMap chunks; + private UpdateWorldMap packet; + + public WorldMap(int chunks) { + this.chunks = new Long2ObjectOpenHashMap<>(chunks); + } + + @Nonnull + public Map getPointsOfInterest() { + return this.pointsOfInterest; + } + + @Nonnull + public Long2ObjectMap getChunks() { + return this.chunks; + } + + public void addPointOfInterest(String id, String name, String markerType, @Nonnull Vector3i pos) { + this.addPointOfInterest(id, name, markerType, new Transform(pos)); + } + + public void addPointOfInterest(String id, String name, String markerType, @Nonnull Vector3d pos) { + this.addPointOfInterest(id, name, markerType, new Transform(pos)); + } + + public void addPointOfInterest(String id, String name, String markerType, @Nonnull Transform transform) { + MapMarker old = this.pointsOfInterest.putIfAbsent(id, new MapMarker(id, name, markerType, PositionUtil.toTransformPacket(transform), null)); + if (old != null) { + throw new IllegalArgumentException("Id " + id + " already exists!"); + } + } + + @Nonnull + public UpdateWorldMap toPacket() { + if (this.packet != null) { + return this.packet; + } else { + MapChunk[] mapChunks = new MapChunk[this.chunks.size()]; + int i = 0; + + for (Entry entry : this.chunks.long2ObjectEntrySet()) { + long index = entry.getLongKey(); + int chunkX = ChunkUtil.xOfChunkIndex(index); + int chunkZ = ChunkUtil.zOfChunkIndex(index); + mapChunks[i++] = new MapChunk(chunkX, chunkZ, entry.getValue()); + } + + return this.packet = new UpdateWorldMap(mapChunks, this.pointsOfInterest.values().toArray(MapMarker[]::new), null); + } + } + + @Nonnull + @Override + public String toString() { + return "WorldMap{pointsOfInterest=" + this.pointsOfInterest + ", chunks=" + this.chunks + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/meta/BlockState.java b/src/com/hypixel/hytale/server/core/universe/world/meta/BlockState.java new file mode 100644 index 0000000..d59959f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/meta/BlockState.java @@ -0,0 +1,292 @@ +package com.hypixel.hytale.server.core.universe.world.meta; + +import com.google.common.flogger.StackSize; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.ACodecMapCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.StateData; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.meta.state.exceptions.NoSuchBlockStateException; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +@Deprecated(forRemoval = true) +public abstract class BlockState implements Component { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(BlockState.class) + .addField( + new KeyedCodec<>("Position", Vector3i.CODEC), + (entity, o) -> entity.position = o, + entity -> Vector3i.ZERO.equals(entity.position) ? null : entity.position + ) + .build(); + public static final KeyedCodec TYPE_STRUCTURE = new KeyedCodec<>("Type", Codec.STRING); + public static final String OPEN_WINDOW = "OpenWindow"; + public static final String CLOSE_WINDOW = "CloseWindow"; + final AtomicBoolean initialized = new AtomicBoolean(false); + @Nullable + private WorldChunk chunk; + private Vector3i position; + protected Ref reference; + + public BlockState() { + } + + public void setReference(Ref reference) { + if (this.reference != null && this.reference.isValid()) { + throw new IllegalArgumentException("Entity already has a valid EntityReference: " + this.reference + " new reference " + reference); + } else { + this.reference = reference; + } + } + + public Ref getReference() { + return this.reference; + } + + public void unloadFromWorld() { + if (this.reference != null && this.reference.isValid()) { + throw new IllegalArgumentException("Tried to unlock used block state"); + } else { + this.chunk = null; + } + } + + public boolean initialize(BlockType blockType) { + return true; + } + + public void onUnload() { + } + + public void validateInitialized() { + if (!this.initialized.get()) { + throw new IllegalArgumentException(String.valueOf(this)); + } + } + + public int getIndex() { + return ChunkUtil.indexBlockInColumn(this.position.x, this.position.y, this.position.z); + } + + public void setPosition(WorldChunk chunk, @Nullable Vector3i position) { + this.chunk = chunk; + if (position != null) { + position.assign(position.getX() & 31, position.getY(), position.getZ() & 31); + if (position.equals(Vector3i.ZERO)) { + LOGGER.at(Level.WARNING).withStackTrace(StackSize.FULL).log("BlockState position set to (0,0,0): %s", this); + } + + this.position = position; + } + } + + public void setPosition(@Nonnull Vector3i position) { + position.assign(position.getX() & 31, position.getY(), position.getZ() & 31); + if (position.equals(Vector3i.ZERO)) { + LOGGER.at(Level.WARNING).withStackTrace(StackSize.FULL).log("BlockState position set to (0,0,0): %s", this); + } + + this.position = position; + } + + @Nonnull + public Vector3i getPosition() { + return this.position.clone(); + } + + public Vector3i __internal_getPosition() { + return this.position; + } + + public int getBlockX() { + return this.chunk.getX() << 5 | this.position.getX(); + } + + public int getBlockY() { + return this.position.y; + } + + public int getBlockZ() { + return this.chunk.getZ() << 5 | this.position.getZ(); + } + + @Nonnull + public Vector3i getBlockPosition() { + return new Vector3i(this.getBlockX(), this.getBlockY(), this.getBlockZ()); + } + + @Nonnull + public Vector3d getCenteredBlockPosition() { + BlockType blockType = this.getBlockType(); + Vector3d blockCenter = new Vector3d(0.0, 0.0, 0.0); + blockType.getBlockCenter(this.getRotationIndex(), blockCenter); + return blockCenter.add(this.getBlockX(), this.getBlockY(), this.getBlockZ()); + } + + @Nullable + public WorldChunk getChunk() { + return this.chunk; + } + + @Nullable + public BlockType getBlockType() { + return this.getChunk().getBlockType(this.position); + } + + public int getRotationIndex() { + return this.getChunk().getRotationIndex(this.position.x, this.position.y, this.position.z); + } + + public void invalidate() { + } + + public void markNeedsSave() { + this.getChunk().markNeedsSaving(); + } + + public BsonDocument saveToDocument() { + return CODEC.encode(this).asDocument(); + } + + @Nullable + @Override + public Component clone() { + BsonDocument document = CODEC.encode(this, ExtraInfo.THREAD_LOCAL.get()).asDocument(); + return CODEC.decode(document, ExtraInfo.THREAD_LOCAL.get()); + } + + @Nonnull + public Holder toHolder() { + if (this.reference != null && this.reference.isValid() && this.chunk != null) { + Holder holder = ChunkStore.REGISTRY.newHolder(); + Store componentStore = this.chunk.getWorld().getChunkStore().getStore(); + Archetype archetype = componentStore.getArchetype(this.reference); + + for (int i = archetype.getMinIndex(); i < archetype.length(); i++) { + ComponentType componentType = archetype.get(i); + if (componentType != null) { + holder.addComponent(componentType, componentStore.getComponent(this.reference, componentType)); + } + } + + return holder; + } else { + Holder holder = ChunkStore.REGISTRY.newHolder(); + ComponentType componentType = BlockStateModule.get().getComponentType((Class)this.getClass()); + if (componentType == null) { + throw new IllegalArgumentException("Unable to find component type for: " + this); + } else { + holder.addComponent(componentType, this); + return holder; + } + } + } + + @Nullable + public static BlockState load(BsonDocument doc, @Nonnull WorldChunk chunk, @Nonnull Vector3i pos) throws NoSuchBlockStateException { + return load(doc, chunk, pos, chunk.getBlockType(pos.getX(), pos.getY(), pos.getZ())); + } + + @Nullable + public static BlockState load(BsonDocument doc, @Nullable WorldChunk chunk, Vector3i pos, BlockType blockType) throws NoSuchBlockStateException { + BlockState blockState; + try { + blockState = CODEC.decode(doc); + } catch (ACodecMapCodec.UnknownIdException var6) { + throw new NoSuchBlockStateException(var6); + } + + blockState.setPosition(chunk, pos); + if (chunk != null) { + if (!blockState.initialize(blockType)) { + return null; + } + + blockState.initialized.set(true); + } + + return blockState; + } + + @Nullable + @Deprecated + public static BlockState ensureState(@Nonnull WorldChunk worldChunk, int x, int y, int z) { + BlockType blockType = worldChunk.getBlockType(x, y, z); + if (blockType != null && !blockType.isUnknown()) { + StateData state = blockType.getState(); + if (state != null && state.getId() != null) { + Vector3i position = new Vector3i(x, y, z); + BlockState blockState = BlockStateModule.get().createBlockState(state.getId(), worldChunk, position, blockType); + if (blockState != null) { + worldChunk.setState(x, y, z, blockState); + } + + return blockState; + } else { + return null; + } + } else { + return null; + } + } + + @Deprecated + public static BlockState getBlockState(@Nullable Ref reference, @Nonnull ComponentAccessor componentAccessor) { + if (reference == null) { + return null; + } else { + ComponentType componentType = findComponentType(componentAccessor.getArchetype(reference), BlockState.class); + return componentType == null ? null : componentAccessor.getComponent(reference, componentType); + } + } + + @Nullable + @Deprecated + public static BlockState getBlockState(int index, @Nonnull ArchetypeChunk archetypeChunk) { + ComponentType componentType = findComponentType(archetypeChunk.getArchetype(), BlockState.class); + return componentType == null ? null : archetypeChunk.getComponent(index, componentType); + } + + @Nullable + @Deprecated + public static BlockState getBlockState(@Nonnull Holder holder) { + ComponentType componentType = findComponentType(holder.getArchetype(), BlockState.class); + return componentType == null ? null : holder.getComponent(componentType); + } + + @Nullable + private static , T extends C> ComponentType findComponentType( + @Nonnull Archetype archetype, @Nonnull Class entityClass + ) { + for (int i = archetype.getMinIndex(); i < archetype.length(); i++) { + ComponentType> componentType = (ComponentType>)archetype.get(i); + if (componentType != null && entityClass.isAssignableFrom(componentType.getTypeClass())) { + return (ComponentType)componentType; + } + } + + return null; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/meta/BlockStateModule.java b/src/com/hypixel/hytale/server/core/universe/world/meta/BlockStateModule.java new file mode 100644 index 0000000..54a52a3 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/meta/BlockStateModule.java @@ -0,0 +1,521 @@ +package com.hypixel.hytale.server.core.universe.world.meta; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.DisableProcessingAssert; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.RootDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.spatial.KDTree; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.MetricSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.metrics.MetricResults; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.StateData; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.modules.block.system.ItemContainerStateSpatialSystem; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.plugin.PluginState; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkFlag; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.state.TickableBlockState; +import com.hypixel.hytale.server.core.universe.world.meta.state.DestroyableBlockState; +import com.hypixel.hytale.server.core.universe.world.meta.state.ItemContainerState; +import com.hypixel.hytale.server.core.universe.world.meta.state.SendableBlockState; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonDocument; + +@Deprecated(forRemoval = true) +public class BlockStateModule extends JavaPlugin { + public static final PluginManifest MANIFEST = PluginManifest.corePlugin(BlockStateModule.class).depends(BlockModule.class).build(); + private static BlockStateModule instance; + @Deprecated + private final Map, ComponentType> classToComponentType = new ConcurrentHashMap<>(); + private ResourceType, ChunkStore>> itemContainerSpatialResourceType; + + public static BlockStateModule get() { + return instance; + } + + public ResourceType, ChunkStore>> getItemContainerSpatialResourceType() { + return this.itemContainerSpatialResourceType; + } + + public BlockStateModule(@Nonnull JavaPluginInit init) { + super(init); + instance = this; + } + + @Override + protected void setup() { + this.registerBlockState( + ItemContainerState.class, + "container", + ItemContainerState.CODEC, + ItemContainerState.ItemContainerStateData.class, + ItemContainerState.ItemContainerStateData.CODEC + ); + this.itemContainerSpatialResourceType = this.getChunkStoreRegistry().registerSpatialResource(() -> new KDTree<>(Ref::isValid)); + this.getChunkStoreRegistry().registerSystem(new ItemContainerStateSpatialSystem(this.itemContainerSpatialResourceType)); + this.getChunkStoreRegistry().registerSystem(new BlockStateModule.ItemContainerStateRefSystem()); + } + + @Nullable + public BlockStateRegistration registerBlockState(@Nonnull Class clazz, @Nonnull String key, Codec codec) { + return this.registerBlockState(clazz, key, codec, null, null); + } + + @Nullable + public BlockStateRegistration registerBlockState( + @Nonnull Class clazz, @Nonnull String key, @Nullable Codec codec, Class dataClass, @Nullable Codec dataCodec + ) { + if (this.isDisabled()) { + return null; + } else { + BlockState.CODEC.register(key, clazz, codec); + if (dataCodec != null) { + StateData.CODEC.register(key, dataClass, dataCodec); + } + + ComponentType componentType; + if (codec != null) { + componentType = this.getChunkStoreRegistry().registerComponent(clazz, key, (BuilderCodec)codec); + } else { + componentType = this.getChunkStoreRegistry().registerComponent(clazz, () -> { + throw new UnsupportedOperationException("Not implemented!"); + }); + } + + this.classToComponentType.put(clazz, componentType); + this.getChunkStoreRegistry().registerSystem(new BlockStateModule.LegacyLateInitBlockStateSystem<>(componentType), true); + this.getChunkStoreRegistry().registerSystem(new BlockStateModule.LegacyBlockStateHolderSystem<>(componentType), true); + this.getChunkStoreRegistry().registerSystem(new BlockStateModule.LegacyBlockStateRefSystem<>(componentType), true); + if (TickableBlockState.class.isAssignableFrom(clazz)) { + this.getChunkStoreRegistry().registerSystem(new BlockStateModule.LegacyTickingBlockStateSystem<>(componentType), true); + } + + if (SendableBlockState.class.isAssignableFrom(clazz)) { + this.getChunkStoreRegistry().registerSystem(new BlockStateModule.LegacyLoadPacketBlockStateSystem<>(componentType), true); + this.getChunkStoreRegistry().registerSystem(new BlockStateModule.LegacyUnloadPacketBlockStateSystem<>(componentType), true); + } + + return new BlockStateRegistration(clazz, () -> this.getState() == PluginState.ENABLED, () -> this.unregisterBlockState(clazz, dataClass)); + } + } + + public void unregisterBlockState(Class clazz, @Nullable Class dataClass) { + if (!HytaleServer.get().isShuttingDown()) { + BlockState.CODEC.remove(clazz); + ChunkStore.REGISTRY.unregisterComponent(this.classToComponentType.remove(clazz)); + if (dataClass != null) { + StateData.CODEC.remove(dataClass); + } + } + } + + @Nullable + public T createBlockState(Class clazz, WorldChunk chunk, Vector3i pos, BlockType blockType) { + String id = BlockState.CODEC.getIdFor(clazz); + return (T)this.createBlockState(id, chunk, pos, blockType); + } + + @Nullable + public BlockState createBlockState(String key, WorldChunk chunk, Vector3i pos, BlockType blockType) { + Codec codec = BlockState.CODEC.getCodecFor(key); + if (codec == null) { + this.getLogger().at(Level.WARNING).log("Failed to create BlockState for '%s' null codec", key); + return null; + } else { + BlockState blockState = codec.decode(new BsonDocument()); + if (blockState == null) { + this.getLogger().at(Level.WARNING).log("Failed to create BlockState for '%s' null value from supplier", key); + return null; + } else { + blockState.setPosition(chunk, pos); + if (!blockState.initialize(blockType)) { + return null; + } else { + blockState.initialized.set(true); + return blockState; + } + } + } + } + + @Nullable + public ComponentType getComponentType(@Nullable Class entityClass) { + if (this.isDisabled()) { + return null; + } else { + return (ComponentType)(entityClass == null ? null : this.classToComponentType.get(entityClass)); + } + } + + public static class ItemContainerStateRefSystem extends RefSystem { + private static final Query query = BlockStateModule.get().getComponentType(ItemContainerState.class); + + public ItemContainerStateRefSystem() { + } + + @Override + public Query getQuery() { + return query; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + commandBuffer.getExternalData() + .getWorld() + .getChunkStore() + .getStore() + .getResource(BlockModule.BlockStateInfoNeedRebuild.getResourceType()) + .markAsNeedRebuild(); + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + commandBuffer.getExternalData() + .getWorld() + .getChunkStore() + .getStore() + .getResource(BlockModule.BlockStateInfoNeedRebuild.getResourceType()) + .markAsNeedRebuild(); + } + + @Nonnull + @Override + public String toString() { + return "ItemContainerStateRefSystem{}"; + } + } + + public static class LegacyBlockStateHolderSystem extends HolderSystem implements DisableProcessingAssert { + private final ComponentType componentType; + + public LegacyBlockStateHolderSystem(ComponentType componentType) { + this.componentType = componentType; + } + + @Override + public Query getQuery() { + return this.componentType; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + T blockState = holder.getComponent(this.componentType); + switch (reason) { + case REMOVE: + if (blockState instanceof DestroyableBlockState) { + ((DestroyableBlockState)blockState).onDestroy(); + } + + blockState.unloadFromWorld(); + break; + case UNLOAD: + blockState.onUnload(); + blockState.unloadFromWorld(); + } + } + + @Nonnull + @Override + public String toString() { + return "LegacyBlockStateSystem{componentType=" + this.componentType + "}"; + } + } + + public static class LegacyBlockStateRefSystem extends RefSystem implements DisableProcessingAssert { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final ComponentType componentType; + + public LegacyBlockStateRefSystem(ComponentType componentType) { + this.componentType = componentType; + } + + @Override + public Query getQuery() { + return this.componentType; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + T blockState = store.getComponent(ref, this.componentType); + int index = blockState.getIndex(); + WorldChunk chunk = blockState.getChunk(); + if (chunk == null) { + Vector3i position = blockState.getBlockPosition(); + int chunkX = MathUtil.floor(position.getX()) >> 5; + int chunkZ = MathUtil.floor(position.getZ()) >> 5; + World world = store.getExternalData().getWorld(); + WorldChunk worldChunk = world.getChunkIfInMemory(ChunkUtil.indexChunk(chunkX, chunkZ)); + if (worldChunk != null && !worldChunk.not(ChunkFlag.INIT)) { + if (worldChunk.not(ChunkFlag.TICKING)) { + commandBuffer.run(_store -> { + Holder holder = _store.removeEntity(ref, RemoveReason.UNLOAD); + worldChunk.getBlockComponentChunk().addEntityHolder(index, holder); + }); + } + + int x = ChunkUtil.xFromBlockInColumn(index); + int y = ChunkUtil.yFromBlockInColumn(index); + int z = ChunkUtil.zFromBlockInColumn(index); + blockState.setPosition(worldChunk, new Vector3i(x, y, z)); + } + } + + blockState.setReference(ref); + if (!blockState.initialized.get()) { + if (!blockState.initialize(blockState.getChunk().getBlockType(blockState.getPosition()))) { + LOGGER.at(Level.WARNING).log("Block State failed initialize: %s, %s, %s", blockState, blockState.getPosition(), chunk); + commandBuffer.removeEntity(ref, RemoveReason.REMOVE); + } else { + blockState.initialized.set(true); + } + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Nonnull + @Override + public String toString() { + return "LegacyBlockStateSystem{componentType=" + this.componentType + "}"; + } + } + + public static class LegacyLateInitBlockStateSystem extends EntityTickingSystem implements DisableProcessingAssert { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final ComponentType componentType; + @Nonnull + private final Query query; + + public LegacyLateInitBlockStateSystem(ComponentType componentType) { + this.componentType = componentType; + this.query = Query.and(componentType, BlockModule.BlockStateInfo.getComponentType()); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.firstSet(); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + T blockStateComponent = archetypeChunk.getComponent(index, this.componentType); + + assert blockStateComponent != null; + + BlockModule.BlockStateInfo blockStateInfoComponent = archetypeChunk.getComponent(index, BlockModule.BlockStateInfo.getComponentType()); + + assert blockStateInfoComponent != null; + + try { + if (!blockStateComponent.initialized.get()) { + blockStateComponent.initialized.set(true); + if (blockStateComponent.getReference() == null || !blockStateComponent.getReference().isValid()) { + blockStateComponent.setReference(archetypeChunk.getReferenceTo(index)); + } + + World world = store.getExternalData().getWorld(); + Store chunkStore = world.getChunkStore().getStore(); + WorldChunk worldChunkComponent = chunkStore.getComponent(blockStateInfoComponent.getChunkRef(), WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + int x = ChunkUtil.xFromBlockInColumn(blockStateInfoComponent.getIndex()); + int y = ChunkUtil.yFromBlockInColumn(blockStateInfoComponent.getIndex()); + int z = ChunkUtil.zFromBlockInColumn(blockStateInfoComponent.getIndex()); + blockStateComponent.setPosition(worldChunkComponent, new Vector3i(x, y, z)); + int blockIndex = worldChunkComponent.getBlock(x, y, z); + BlockType blockType = BlockType.getAssetMap().getAsset(blockIndex); + if (!blockStateComponent.initialize(blockType)) { + LOGGER.at(Level.SEVERE).log("Removing invalid block state %s", blockStateComponent); + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + } + } + } catch (Exception var16) { + LOGGER.at(Level.SEVERE).withCause(var16).log("Exception while re-init BlockState! Removing!! %s", blockStateComponent); + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + } + } + + @Nonnull + @Override + public String toString() { + return "LegacyLateInitBlockStateSystem{componentType=" + this.componentType + "}"; + } + } + + public static class LegacyLoadPacketBlockStateSystem extends ChunkStore.LoadPacketDataQuerySystem { + private final ComponentType componentType; + + public LegacyLoadPacketBlockStateSystem(ComponentType componentType) { + this.componentType = componentType; + } + + @Override + public Query getQuery() { + return this.componentType; + } + + public void fetch( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + Store store, + CommandBuffer commandBuffer, + PlayerRef player, + List results + ) { + SendableBlockState state = (SendableBlockState)BlockState.getBlockState(index, archetypeChunk); + if (state.canPlayerSee(player)) { + state.sendTo(results); + } + } + + @Nonnull + @Override + public String toString() { + return "LegacyLoadPacketBlockStateSystem{componentType=" + this.componentType + "}"; + } + } + + public static class LegacyTickingBlockStateSystem + extends EntityTickingSystem + implements DisableProcessingAssert, + MetricSystem { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private static final MetricsRegistry> METRICS_REGISTRY = new MetricsRegistry>() + .register("ComponentType", o -> o.componentType.getTypeClass().toString(), Codec.STRING); + private final ComponentType componentType; + + public LegacyTickingBlockStateSystem(ComponentType componentType) { + this.componentType = componentType; + } + + @Override + public Query getQuery() { + return this.componentType; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + T blockState = archetypeChunk.getComponent(index, this.componentType); + + try { + ((TickableBlockState)blockState).tick(dt, index, archetypeChunk, store, commandBuffer); + } catch (Throwable var8) { + LOGGER.at(Level.SEVERE).withCause(var8).log("Exception while ticking BlockState! Removing!! %s", blockState); + commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE); + } + } + + @Nonnull + @Override + public MetricResults toMetricResults(Store store) { + return METRICS_REGISTRY.toMetricResults(this); + } + + @Nonnull + @Override + public String toString() { + return "LegacyTickingBlockStateSystem{componentType=" + this.componentType + "}"; + } + } + + public static class LegacyUnloadPacketBlockStateSystem extends ChunkStore.UnloadPacketDataQuerySystem { + private final ComponentType componentType; + + public LegacyUnloadPacketBlockStateSystem(ComponentType componentType) { + this.componentType = componentType; + } + + @Override + public Query getQuery() { + return this.componentType; + } + + public void fetch( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + Store store, + CommandBuffer commandBuffer, + PlayerRef player, + List results + ) { + SendableBlockState state = (SendableBlockState)BlockState.getBlockState(index, archetypeChunk); + if (state.canPlayerSee(player)) { + state.unloadFrom(results); + } + } + + @Nonnull + @Override + public String toString() { + return "LegacyUnloadPacketBlockStateSystem{componentType=" + this.componentType + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/meta/BlockStateRegistration.java b/src/com/hypixel/hytale/server/core/universe/world/meta/BlockStateRegistration.java new file mode 100644 index 0000000..054ee8e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/meta/BlockStateRegistration.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.core.universe.world.meta; + +import com.hypixel.hytale.registry.Registration; +import java.util.function.BooleanSupplier; +import javax.annotation.Nonnull; + +public class BlockStateRegistration extends Registration { + private final Class blockStateClass; + + public BlockStateRegistration(Class blockStateClass, BooleanSupplier isEnabled, Runnable unregister) { + super(isEnabled, unregister); + this.blockStateClass = blockStateClass; + } + + public BlockStateRegistration(@Nonnull BlockStateRegistration registration, BooleanSupplier isEnabled, Runnable unregister) { + super(isEnabled, unregister); + this.blockStateClass = registration.blockStateClass; + } + + public Class getBlockStateClass() { + return this.blockStateClass; + } + + @Nonnull + @Override + public String toString() { + return "BlockStateRegistration{blockStateClass=" + this.blockStateClass + ", " + super.toString() + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/meta/BlockStateRegistry.java b/src/com/hypixel/hytale/server/core/universe/world/meta/BlockStateRegistry.java new file mode 100644 index 0000000..92f06f2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/meta/BlockStateRegistry.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.core.universe.world.meta; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.function.consumer.BooleanConsumer; +import com.hypixel.hytale.registry.Registry; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.StateData; +import java.util.List; +import java.util.function.BooleanSupplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockStateRegistry extends Registry { + public BlockStateRegistry(@Nonnull List registrations, BooleanSupplier precondition, String preconditionMessage) { + super(registrations, precondition, preconditionMessage, BlockStateRegistration::new); + } + + @Nullable + public BlockStateRegistration registerBlockState(@Nonnull Class clazz, @Nonnull String key, Codec codec) { + this.checkPrecondition(); + return this.register(BlockStateModule.get().registerBlockState(clazz, key, codec)); + } + + @Nullable + public BlockStateRegistration registerBlockState( + @Nonnull Class clazz, @Nonnull String key, Codec codec, Class dataClass, Codec dataCodec + ) { + this.checkPrecondition(); + return this.register(BlockStateModule.get().registerBlockState(clazz, key, codec, dataClass, dataCodec)); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/meta/state/BlockMapMarker.java b/src/com/hypixel/hytale/server/core/universe/world/meta/state/BlockMapMarker.java new file mode 100644 index 0000000..8dcc4ec --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/meta/state/BlockMapMarker.java @@ -0,0 +1,160 @@ +package com.hypixel.hytale.server.core.universe.world.meta.state; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.Position; +import com.hypixel.hytale.protocol.Transform; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockMapMarker implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockMapMarker.class, BlockMapMarker::new) + .append(new KeyedCodec<>("Name", Codec.STRING), (o, v) -> o.name = v, o -> o.name) + .addValidator(Validators.nonNull()) + .add() + .append(new KeyedCodec<>("Icon", Codec.STRING), (o, v) -> o.icon = v, o -> o.icon) + .addValidator(Validators.nonNull()) + .add() + .build(); + private String name; + private String icon; + + public BlockMapMarker() { + } + + public BlockMapMarker(String name, String icon) { + this.name = name; + this.icon = icon; + } + + public static ComponentType getComponentType() { + return BlockModule.get().getBlockMapMarkerComponentType(); + } + + public String getName() { + return this.name; + } + + public String getIcon() { + return this.icon; + } + + @Nullable + @Override + public Component clone() { + return new BlockMapMarker(this.name, this.icon); + } + + public static class MarkerProvider implements WorldMapManager.MarkerProvider { + public static final BlockMapMarker.MarkerProvider INSTANCE = new BlockMapMarker.MarkerProvider(); + + public MarkerProvider() { + } + + @Override + public void update(World world, GameplayConfig gameplayConfig, WorldMapTracker tracker, int chunkViewRadius, int playerChunkX, int playerChunkZ) { + BlockMapMarkersResource resource = world.getChunkStore().getStore().getResource(BlockMapMarkersResource.getResourceType()); + Long2ObjectMap markers = resource.getMarkers(); + + for (BlockMapMarkersResource.BlockMapMarkerData markerData : markers.values()) { + Vector3i position = markerData.getPosition(); + Transform transform = new Transform(); + transform.position = new Position(position.getX() + 0.5F, position.getY(), position.getZ() + 0.5F); + transform.orientation = new Direction(0.0F, 0.0F, 0.0F); + MapMarker marker = new MapMarker(markerData.getMarkerId(), markerData.getName(), markerData.getIcon(), transform, null); + tracker.trySendMarker(chunkViewRadius, playerChunkX, playerChunkZ, marker); + } + } + } + + public static class OnAddRemove extends RefSystem { + private static final ComponentType COMPONENT_TYPE = BlockMapMarker.getComponentType(); + private static final ResourceType BLOCK_MAP_MARKERS_RESOURCE_TYPE = BlockMapMarkersResource.getResourceType(); + + public OnAddRemove() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + BlockModule.BlockStateInfo blockInfo = commandBuffer.getComponent(ref, BlockModule.BlockStateInfo.getComponentType()); + + assert blockInfo != null; + + Ref chunkRef = blockInfo.getChunkRef(); + if (chunkRef.isValid()) { + BlockChunk blockChunk = commandBuffer.getComponent(chunkRef, BlockChunk.getComponentType()); + + assert blockChunk != null; + + BlockMapMarker blockMapMarker = commandBuffer.getComponent(ref, COMPONENT_TYPE); + + assert blockMapMarker != null; + + WorldChunk wc = commandBuffer.getComponent(chunkRef, WorldChunk.getComponentType()); + Vector3i blockPosition = new Vector3i( + ChunkUtil.worldCoordFromLocalCoord(wc.getX(), ChunkUtil.xFromBlockInColumn(blockInfo.getIndex())), + ChunkUtil.yFromBlockInColumn(blockInfo.getIndex()), + ChunkUtil.worldCoordFromLocalCoord(wc.getZ(), ChunkUtil.zFromBlockInColumn(blockInfo.getIndex())) + ); + BlockMapMarkersResource resource = commandBuffer.getResource(BLOCK_MAP_MARKERS_RESOURCE_TYPE); + resource.addMarker(blockPosition, blockMapMarker.getName(), blockMapMarker.getIcon()); + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + if (reason == RemoveReason.REMOVE) { + BlockModule.BlockStateInfo blockInfo = commandBuffer.getComponent(ref, BlockModule.BlockStateInfo.getComponentType()); + + assert blockInfo != null; + + Ref chunkRef = blockInfo.getChunkRef(); + if (chunkRef.isValid()) { + WorldChunk wc = commandBuffer.getComponent(chunkRef, WorldChunk.getComponentType()); + Vector3i blockPosition = new Vector3i( + ChunkUtil.worldCoordFromLocalCoord(wc.getX(), ChunkUtil.xFromBlockInColumn(blockInfo.getIndex())), + ChunkUtil.yFromBlockInColumn(blockInfo.getIndex()), + ChunkUtil.worldCoordFromLocalCoord(wc.getZ(), ChunkUtil.zFromBlockInColumn(blockInfo.getIndex())) + ); + BlockMapMarkersResource resource = commandBuffer.getResource(BLOCK_MAP_MARKERS_RESOURCE_TYPE); + resource.removeMarker(blockPosition); + } + } + } + + @Nullable + @Override + public Query getQuery() { + return COMPONENT_TYPE; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/meta/state/BlockMapMarkersResource.java b/src/com/hypixel/hytale/server/core/universe/world/meta/state/BlockMapMarkersResource.java new file mode 100644 index 0000000..db52087 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/meta/state/BlockMapMarkersResource.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.server.core.universe.world.meta.state; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.math.block.BlockUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import java.util.HashMap; +import java.util.UUID; +import java.util.Map.Entry; +import javax.annotation.Nonnull; + +public class BlockMapMarkersResource implements Resource { + public static final BuilderCodec CODEC = BuilderCodec.builder(BlockMapMarkersResource.class, BlockMapMarkersResource::new) + .append(new KeyedCodec<>("Markers", new MapCodec<>(BlockMapMarkersResource.BlockMapMarkerData.CODEC, HashMap::new), true), (o, markersMap) -> { + if (markersMap != null) { + for (Entry entry : markersMap.entrySet()) { + o.markers.put(Long.valueOf(entry.getKey()).longValue(), entry.getValue()); + } + } + }, o -> { + HashMap returnMap = new HashMap<>(o.markers.size()); + + for (Entry entry : o.markers.entrySet()) { + returnMap.put(String.valueOf(entry.getKey()), entry.getValue()); + } + + return returnMap; + }) + .add() + .build(); + private Long2ObjectMap markers = new Long2ObjectOpenHashMap<>(); + + public BlockMapMarkersResource() { + } + + public BlockMapMarkersResource(Long2ObjectMap markers) { + this.markers = markers; + } + + public static ResourceType getResourceType() { + return BlockModule.get().getBlockMapMarkersResourceType(); + } + + @Nonnull + public Long2ObjectMap getMarkers() { + return this.markers; + } + + public void addMarker(@Nonnull Vector3i position, @Nonnull String name, @Nonnull String icon) { + long key = BlockUtil.pack(position); + this.markers.put(key, new BlockMapMarkersResource.BlockMapMarkerData(position, name, icon, UUID.randomUUID().toString())); + } + + public void removeMarker(@Nonnull Vector3i position) { + long key = BlockUtil.pack(position); + this.markers.remove(key); + } + + @Override + public Resource clone() { + return new BlockMapMarkersResource(new Long2ObjectOpenHashMap<>(this.markers)); + } + + public static class BlockMapMarkerData { + public static final BuilderCodec CODEC = BuilderCodec.builder( + BlockMapMarkersResource.BlockMapMarkerData.class, BlockMapMarkersResource.BlockMapMarkerData::new + ) + .append(new KeyedCodec<>("Position", Vector3i.CODEC), (o, v) -> o.position = v, o -> o.position) + .add() + .append(new KeyedCodec<>("Name", Codec.STRING), (o, v) -> o.name = v, o -> o.name) + .add() + .append(new KeyedCodec<>("Icon", Codec.STRING), (o, v) -> o.icon = v, o -> o.icon) + .add() + .append(new KeyedCodec<>("MarkerId", Codec.STRING), (o, v) -> o.markerId = v, o -> o.markerId) + .add() + .build(); + private Vector3i position; + private String name; + private String icon; + private String markerId; + + public BlockMapMarkerData() { + } + + public BlockMapMarkerData(Vector3i position, String name, String icon, String markerId) { + this.position = position; + this.name = name; + this.icon = icon; + this.markerId = markerId; + } + + public Vector3i getPosition() { + return this.position; + } + + public String getName() { + return this.name; + } + + public String getIcon() { + return this.icon; + } + + public String getMarkerId() { + return this.markerId; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/meta/state/BreakValidatedBlockState.java b/src/com/hypixel/hytale/server/core/universe/world/meta/state/BreakValidatedBlockState.java new file mode 100644 index 0000000..2bec2dc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/meta/state/BreakValidatedBlockState.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.core.universe.world.meta.state; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public interface BreakValidatedBlockState { + boolean canDestroy(@Nonnull Ref var1, @Nonnull ComponentAccessor var2); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/meta/state/DestroyableBlockState.java b/src/com/hypixel/hytale/server/core/universe/world/meta/state/DestroyableBlockState.java new file mode 100644 index 0000000..b1389f1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/meta/state/DestroyableBlockState.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.server.core.universe.world.meta.state; + +@Deprecated +public interface DestroyableBlockState { + void onDestroy(); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/meta/state/ItemContainerBlockState.java b/src/com/hypixel/hytale/server/core/universe/world/meta/state/ItemContainerBlockState.java new file mode 100644 index 0000000..b90b9f1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/meta/state/ItemContainerBlockState.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.server.core.universe.world.meta.state; + +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; + +public interface ItemContainerBlockState { + ItemContainer getItemContainer(); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/meta/state/ItemContainerState.java b/src/com/hypixel/hytale/server/core/universe/world/meta/state/ItemContainerState.java new file mode 100644 index 0000000..7995c8d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/meta/state/ItemContainerState.java @@ -0,0 +1,197 @@ +package com.hypixel.hytale.server.core.universe.world.meta.state; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.EventPriority; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.StateData; +import com.hypixel.hytale.server.core.entity.entities.player.windows.ContainerBlockWindow; +import com.hypixel.hytale.server.core.entity.entities.player.windows.WindowManager; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemContainerState extends BlockState implements ItemContainerBlockState, DestroyableBlockState, MarkerBlockState { + public static final Codec CODEC = BuilderCodec.builder(ItemContainerState.class, ItemContainerState::new, BlockState.BASE_CODEC) + .addField(new KeyedCodec<>("Custom", Codec.BOOLEAN), (state, o) -> state.custom = o, state -> state.custom) + .addField(new KeyedCodec<>("AllowViewing", Codec.BOOLEAN), (state, o) -> state.allowViewing = o, state -> state.allowViewing) + .addField(new KeyedCodec<>("Droplist", Codec.STRING), (state, o) -> state.droplist = o, state -> state.droplist) + .addField(new KeyedCodec<>("Marker", WorldMapManager.MarkerReference.CODEC), (state, o) -> state.marker = o, state -> state.marker) + .addField(new KeyedCodec<>("ItemContainer", SimpleItemContainer.CODEC), (state, o) -> state.itemContainer = o, state -> state.itemContainer) + .build(); + private final Map windows = new ConcurrentHashMap<>(); + protected boolean custom; + protected boolean allowViewing = true; + @Nullable + protected String droplist; + protected SimpleItemContainer itemContainer; + protected WorldMapManager.MarkerReference marker; + + public ItemContainerState() { + } + + @Override + public boolean initialize(@Nonnull BlockType blockType) { + if (!super.initialize(blockType)) { + return false; + } else if (this.custom) { + return true; + } else { + short capacity = 20; + if (blockType.getState() instanceof ItemContainerState.ItemContainerStateData itemContainerStateData) { + capacity = itemContainerStateData.getCapacity(); + } + + List remainder = new ObjectArrayList<>(); + this.itemContainer = ItemContainer.ensureContainerCapacity(this.itemContainer, capacity, SimpleItemContainer::new, remainder); + this.itemContainer.registerChangeEvent(EventPriority.LAST, this::onItemChange); + if (!remainder.isEmpty()) { + WorldChunk chunk = this.getChunk(); + World world = chunk.getWorld(); + Store store = world.getEntityStore().getStore(); + HytaleLogger.getLogger() + .at(Level.WARNING) + .withCause(new Throwable()) + .log( + "Dropping %d excess items from item container: %s at world: %s, chunk: %s, block: %s", + remainder.size(), + blockType.getId(), + chunk.getWorld().getName(), + chunk, + this.getPosition() + ); + Vector3i blockPosition = this.getBlockPosition(); + Holder[] itemEntityHolders = ItemComponent.generateItemDrops(store, remainder, blockPosition.toVector3d(), Vector3f.ZERO); + store.addEntities(itemEntityHolders, AddReason.SPAWN); + } + + return true; + } + } + + public boolean canOpen(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + return true; + } + + public void onOpen(@Nonnull Ref ref, @Nonnull World world, @Nonnull Store store) { + } + + @Override + public void onDestroy() { + WindowManager.closeAndRemoveAll(this.windows); + WorldChunk chunk = this.getChunk(); + World world = chunk.getWorld(); + Store store = world.getEntityStore().getStore(); + List allItemStacks = this.itemContainer.dropAllItemStacks(); + Vector3d dropPosition = this.getBlockPosition().toVector3d().add(0.5, 0.0, 0.5); + Holder[] itemEntityHolders = ItemComponent.generateItemDrops(store, allItemStacks, dropPosition, Vector3f.ZERO); + if (itemEntityHolders.length > 0) { + world.execute(() -> store.addEntities(itemEntityHolders, AddReason.SPAWN)); + } + + if (this.marker != null) { + this.marker.remove(); + } + } + + public void setCustom(boolean custom) { + this.custom = custom; + this.markNeedsSave(); + } + + public void setAllowViewing(boolean allowViewing) { + this.allowViewing = allowViewing; + this.markNeedsSave(); + } + + public boolean isAllowViewing() { + return this.allowViewing; + } + + public void setItemContainer(SimpleItemContainer itemContainer) { + this.itemContainer = itemContainer; + this.markNeedsSave(); + } + + @Nullable + public String getDroplist() { + return this.droplist; + } + + public void setDroplist(@Nullable String droplist) { + this.droplist = droplist; + this.markNeedsSave(); + } + + @Override + public void setMarker(WorldMapManager.MarkerReference marker) { + this.marker = marker; + this.markNeedsSave(); + } + + @Nonnull + public Map getWindows() { + return this.windows; + } + + @Override + public ItemContainer getItemContainer() { + return this.itemContainer; + } + + public void onItemChange(ItemContainer.ItemContainerChangeEvent event) { + this.markNeedsSave(); + } + + public static class ItemContainerStateData extends StateData { + public static final BuilderCodec CODEC = BuilderCodec.builder( + ItemContainerState.ItemContainerStateData.class, ItemContainerState.ItemContainerStateData::new, StateData.DEFAULT_CODEC + ) + .appendInherited( + new KeyedCodec<>("Capacity", Codec.INTEGER), + (t, i) -> t.capacity = i.shortValue(), + t -> Integer.valueOf(t.capacity), + (o, p) -> o.capacity = p.capacity + ) + .add() + .build(); + private short capacity = 20; + + protected ItemContainerStateData() { + } + + public short getCapacity() { + return this.capacity; + } + + @Nonnull + @Override + public String toString() { + return "ItemContainerStateData{capacity=" + this.capacity + "} " + super.toString(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/meta/state/LaunchPad.java b/src/com/hypixel/hytale/server/core/universe/world/meta/state/LaunchPad.java new file mode 100644 index 0000000..23d3cdc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/meta/state/LaunchPad.java @@ -0,0 +1,183 @@ +package com.hypixel.hytale.server.core.universe.world.meta.state; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime; +import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; +import com.hypixel.hytale.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.ui.builder.EventData; +import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder; +import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class LaunchPad implements Component { + private static final int MAX_VELOCITY = 50000; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(LaunchPad.class, LaunchPad::new) + .append(new KeyedCodec<>("VelocityX", Codec.DOUBLE), (state, d) -> state.velocityX = d.floatValue(), state -> (double)state.velocityX) + .documentation("The X velocity of the launch pad.") + .add() + .append(new KeyedCodec<>("VelocityY", Codec.DOUBLE), (state, d) -> state.velocityY = d.floatValue(), state -> (double)state.velocityY) + .documentation("The Y velocity of the launch pad.") + .add() + .append(new KeyedCodec<>("VelocityZ", Codec.DOUBLE), (state, d) -> state.velocityZ = d.floatValue(), state -> (double)state.velocityZ) + .documentation("The Z velocity of the launch pad.") + .add() + .append(new KeyedCodec<>("PlayersOnly", Codec.BOOLEAN), (state, b) -> state.playersOnly = b, state -> state.playersOnly) + .documentation("Determines whether only players can use this launch pad.") + .add() + .build(); + private float velocityX; + private float velocityY; + private float velocityZ; + private boolean playersOnly; + + @Nonnull + public static ComponentType getComponentType() { + return BlockModule.get().getLaunchPadComponentType(); + } + + public LaunchPad() { + } + + public LaunchPad(float velocityX, float velocityY, float velocityZ, boolean playersOnly) { + this.velocityX = velocityX; + this.velocityY = velocityY; + this.velocityZ = velocityZ; + this.playersOnly = playersOnly; + } + + public float getVelocityX() { + return this.velocityX; + } + + public float getVelocityY() { + return this.velocityY; + } + + public float getVelocityZ() { + return this.velocityZ; + } + + public boolean isPlayersOnly() { + return this.playersOnly; + } + + private static float clampVelocity(float velocity) { + return Math.max(Math.min(velocity, 50000.0F), -50000.0F); + } + + @Nonnull + @Override + public String toString() { + return "LaunchPadState{velocityX=" + + this.velocityX + + ", velocityY=" + + this.velocityY + + ", velocityZ=" + + this.velocityZ + + ", playersOnly=" + + this.playersOnly + + "}"; + } + + @Nullable + @Override + public Component clone() { + return new LaunchPad(this.velocityX, this.velocityY, this.velocityZ, this.playersOnly); + } + + public static class LaunchPadSettingsPage extends InteractiveCustomUIPage { + private final Ref ref; + + public LaunchPadSettingsPage(@Nonnull PlayerRef playerRef, Ref ref, @Nonnull CustomPageLifetime lifetime) { + super(playerRef, lifetime, LaunchPad.LaunchPadSettingsPage.LaunchPadSettingsPageEventData.CODEC); + this.ref = ref; + } + + @Override + public void build( + @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store + ) { + commandBuilder.append("Pages/LaunchPadSettingsPage.ui"); + ChunkStore chunkStore = store.getExternalData().getWorld().getChunkStore(); + LaunchPad launchPadComponent = chunkStore.getStore().getComponent(this.ref, LaunchPad.getComponentType()); + commandBuilder.set("#VelocityX.Value", launchPadComponent.velocityX); + commandBuilder.set("#VelocityY.Value", launchPadComponent.velocityY); + commandBuilder.set("#VelocityZ.Value", launchPadComponent.velocityZ); + commandBuilder.set("#PlayersOnlyContainer #CheckBox.Value", launchPadComponent.playersOnly); + eventBuilder.addEventBinding( + CustomUIEventBindingType.Activating, + "#SaveButton", + new EventData() + .append("@X", "#VelocityX.Value") + .append("@Y", "#VelocityY.Value") + .append("@Z", "#VelocityZ.Value") + .append("@PlayersOnly", "#PlayersOnlyContainer #CheckBox.Value") + ); + } + + public void handleDataEvent( + @Nonnull Ref ref, @Nonnull Store store, @Nonnull LaunchPad.LaunchPadSettingsPage.LaunchPadSettingsPageEventData data + ) { + ChunkStore chunkStore = store.getExternalData().getWorld().getChunkStore(); + LaunchPad launchPadComponent = chunkStore.getStore().getComponent(this.ref, LaunchPad.getComponentType()); + + assert launchPadComponent != null; + + BlockModule.BlockStateInfo blockStateInfoComponent = chunkStore.getStore().getComponent(this.ref, BlockModule.BlockStateInfo.getComponentType()); + + assert blockStateInfoComponent != null; + + launchPadComponent.velocityX = LaunchPad.clampVelocity((float)data.x); + launchPadComponent.velocityY = LaunchPad.clampVelocity((float)data.y); + launchPadComponent.velocityZ = LaunchPad.clampVelocity((float)data.z); + launchPadComponent.playersOnly = data.playersOnly; + WorldChunk worldChunkComponent = chunkStore.getStore().getComponent(blockStateInfoComponent.getChunkRef(), WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + worldChunkComponent.markNeedsSaving(); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + playerComponent.getPageManager().setPage(ref, store, Page.None); + } + + public static class LaunchPadSettingsPageEventData { + public static final String KEY_X = "@X"; + public static final String KEY_Y = "@Y"; + public static final String KEY_Z = "@Z"; + public static final String KEY_PLAYERS_ONLY = "@PlayersOnly"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + LaunchPad.LaunchPadSettingsPage.LaunchPadSettingsPageEventData.class, LaunchPad.LaunchPadSettingsPage.LaunchPadSettingsPageEventData::new + ) + .addField(new KeyedCodec<>("@X", Codec.DOUBLE), (entry, s) -> entry.x = s, entry -> entry.x) + .addField(new KeyedCodec<>("@Y", Codec.DOUBLE), (entry, s) -> entry.y = s, entry -> entry.y) + .addField(new KeyedCodec<>("@Z", Codec.DOUBLE), (entry, s) -> entry.z = s, entry -> entry.z) + .addField(new KeyedCodec<>("@PlayersOnly", Codec.BOOLEAN), (entry, s) -> entry.playersOnly = s, entry -> entry.playersOnly) + .build(); + private double x; + private double y; + private double z; + private boolean playersOnly; + + public LaunchPadSettingsPageEventData() { + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/meta/state/MarkerBlockState.java b/src/com/hypixel/hytale/server/core/universe/world/meta/state/MarkerBlockState.java new file mode 100644 index 0000000..b376bc2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/meta/state/MarkerBlockState.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.server.core.universe.world.meta.state; + +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; + +public interface MarkerBlockState { + void setMarker(WorldMapManager.MarkerReference var1); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/meta/state/PlacedByBlockState.java b/src/com/hypixel/hytale/server/core/universe/world/meta/state/PlacedByBlockState.java new file mode 100644 index 0000000..daeaea7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/meta/state/PlacedByBlockState.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.universe.world.meta.state; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public interface PlacedByBlockState { + void placedBy(@Nonnull Ref var1, @Nonnull String var2, @Nonnull BlockState var3, @Nonnull ComponentAccessor var4); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/meta/state/RespawnBlock.java b/src/com/hypixel/hytale/server/core/universe/world/meta/state/RespawnBlock.java new file mode 100644 index 0000000..782f75b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/meta/state/RespawnBlock.java @@ -0,0 +1,131 @@ +package com.hypixel.hytale.server.core.universe.world.meta.state; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerRespawnPointData; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerWorldData; +import com.hypixel.hytale.server.core.modules.block.BlockModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RespawnBlock implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(RespawnBlock.class, RespawnBlock::new) + .append( + new KeyedCodec<>("OwnerUUID", Codec.UUID_BINARY), + (respawnBlockState, uuid) -> respawnBlockState.ownerUUID = uuid, + respawnBlockState -> respawnBlockState.ownerUUID + ) + .add() + .build(); + private UUID ownerUUID; + + public static ComponentType getComponentType() { + return BlockModule.get().getRespawnBlockComponentType(); + } + + public RespawnBlock() { + } + + public RespawnBlock(UUID ownerUUID) { + this.ownerUUID = ownerUUID; + } + + public UUID getOwnerUUID() { + return this.ownerUUID; + } + + public void setOwnerUUID(UUID ownerUUID) { + this.ownerUUID = ownerUUID; + } + + @Nullable + @Override + public Component clone() { + return new RespawnBlock(this.ownerUUID); + } + + public static class OnRemove extends RefSystem { + public static final ComponentType COMPONENT_TYPE = RespawnBlock.getComponentType(); + public static final ComponentType BLOCK_INFO_TYPE = BlockModule.BlockStateInfo.getComponentType(); + public static final Query QUERY = Query.and(COMPONENT_TYPE, BLOCK_INFO_TYPE); + + public OnRemove() { + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + if (reason != RemoveReason.UNLOAD) { + RespawnBlock respawnState = commandBuffer.getComponent(ref, COMPONENT_TYPE); + + assert respawnState != null; + + if (respawnState.ownerUUID != null) { + BlockModule.BlockStateInfo blockInfo = commandBuffer.getComponent(ref, BLOCK_INFO_TYPE); + + assert blockInfo != null; + + PlayerRef playerRef = Universe.get().getPlayer(respawnState.ownerUUID); + if (playerRef == null) { + HytaleLogger.getLogger().at(Level.WARNING).log("Need to load PlayerConfig to remove RespawnPoint!"); + } else { + Player player = playerRef.getComponent(Player.getComponentType()); + Ref chunkRef = blockInfo.getChunkRef(); + if (chunkRef != null && chunkRef.isValid()) { + PlayerWorldData playerWorldData = player.getPlayerConfigData().getPerWorldData(store.getExternalData().getWorld().getName()); + PlayerRespawnPointData[] respawnPoints = playerWorldData.getRespawnPoints(); + WorldChunk wc = commandBuffer.getComponent(chunkRef, WorldChunk.getComponentType()); + Vector3i blockPosition = new Vector3i( + ChunkUtil.worldCoordFromLocalCoord(wc.getX(), ChunkUtil.xFromBlockInColumn(blockInfo.getIndex())), + ChunkUtil.yFromBlockInColumn(blockInfo.getIndex()), + ChunkUtil.worldCoordFromLocalCoord(wc.getZ(), ChunkUtil.zFromBlockInColumn(blockInfo.getIndex())) + ); + + for (int i = 0; i < respawnPoints.length; i++) { + PlayerRespawnPointData respawnPoint = respawnPoints[i]; + if (respawnPoint.getBlockPosition().equals(blockPosition)) { + playerWorldData.setRespawnPoints(ArrayUtil.remove(respawnPoints, i)); + return; + } + } + } + } + } + } + } + + @Nullable + @Override + public Query getQuery() { + return QUERY; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/meta/state/SendableBlockState.java b/src/com/hypixel/hytale/server/core/universe/world/meta/state/SendableBlockState.java new file mode 100644 index 0000000..97a8636 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/meta/state/SendableBlockState.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.server.core.universe.world.meta.state; + +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import java.util.List; + +@Deprecated +public interface SendableBlockState { + void sendTo(List var1); + + void unloadFrom(List var1); + + default boolean canPlayerSee(PlayerRef player) { + return true; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/meta/state/exceptions/NoSuchBlockStateException.java b/src/com/hypixel/hytale/server/core/universe/world/meta/state/exceptions/NoSuchBlockStateException.java new file mode 100644 index 0000000..9a6422e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/meta/state/exceptions/NoSuchBlockStateException.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.universe.world.meta.state.exceptions; + +public class NoSuchBlockStateException extends Exception { + public NoSuchBlockStateException(String message) { + super(message); + } + + public NoSuchBlockStateException(Throwable cause) { + super(cause); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/npc/INonPlayerCharacter.java b/src/com/hypixel/hytale/server/core/universe/world/npc/INonPlayerCharacter.java new file mode 100644 index 0000000..bc15a60 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/npc/INonPlayerCharacter.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.server.core.universe.world.npc; + +public interface INonPlayerCharacter { + String getNPCTypeId(); + + int getNPCTypeIndex(); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/path/IPath.java b/src/com/hypixel/hytale/server/core/universe/world/path/IPath.java new file mode 100644 index 0000000..f813914 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/path/IPath.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.server.core.universe.world.path; + +import java.util.List; +import java.util.UUID; +import javax.annotation.Nullable; + +public interface IPath { + @Nullable + UUID getId(); + + @Nullable + String getName(); + + List getPathWaypoints(); + + int length(); + + Waypoint get(int var1); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/path/IPathWaypoint.java b/src/com/hypixel/hytale/server/core/universe/world/path/IPathWaypoint.java new file mode 100644 index 0000000..475eac2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/path/IPathWaypoint.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.server.core.universe.world.path; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public interface IPathWaypoint { + int getOrder(); + + Vector3d getWaypointPosition(@Nonnull ComponentAccessor var1); + + Vector3f getWaypointRotation(@Nonnull ComponentAccessor var1); + + double getPauseTime(); + + float getObservationAngle(); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/path/SimplePathWaypoint.java b/src/com/hypixel/hytale/server/core/universe/world/path/SimplePathWaypoint.java new file mode 100644 index 0000000..568ff95 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/path/SimplePathWaypoint.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.core.universe.world.path; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class SimplePathWaypoint implements IPathWaypoint { + private int order; + private Transform transform; + + public SimplePathWaypoint(int order, Transform transform) { + this.order = order; + this.transform = transform; + } + + @Override + public int getOrder() { + return this.order; + } + + @Nonnull + @Override + public Vector3d getWaypointPosition(@NonNullDecl ComponentAccessor componentAccessor) { + return this.transform.getPosition(); + } + + @Nonnull + @Override + public Vector3f getWaypointRotation(@Nonnull ComponentAccessor componentAccessor) { + return this.transform.getRotation(); + } + + @Override + public double getPauseTime() { + return 0.0; + } + + @Override + public float getObservationAngle() { + return 0.0F; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/path/WorldPath.java b/src/com/hypixel/hytale/server/core/universe/world/path/WorldPath.java new file mode 100644 index 0000000..afda5f9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/path/WorldPath.java @@ -0,0 +1,82 @@ +package com.hypixel.hytale.server.core.universe.world.path; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.math.vector.Transform; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class WorldPath implements IPath { + public static final BuilderCodec CODEC = BuilderCodec.builder(WorldPath.class, WorldPath::new) + .addField(new KeyedCodec<>("Id", Codec.UUID_BINARY), (worldPath, uuid) -> worldPath.id = uuid, worldPath -> worldPath.id) + .addField(new KeyedCodec<>("Name", Codec.STRING), (worldPath, s) -> worldPath.name = s, worldPath -> worldPath.name) + .addField( + new KeyedCodec<>("Waypoints", new ArrayCodec<>(Transform.CODEC, Transform[]::new)), + (worldPath, wayPoints) -> worldPath.waypoints = List.of(wayPoints), + worldPath -> worldPath.waypoints.toArray(Transform[]::new) + ) + .build(); + protected UUID id; + protected String name; + protected List waypoints = Collections.emptyList(); + protected List simpleWaypoints; + + protected WorldPath() { + } + + public WorldPath(String name, List waypoints) { + this.id = UUID.randomUUID(); + this.name = name; + this.waypoints = waypoints; + } + + @Override + public UUID getId() { + return this.id; + } + + @Override + public String getName() { + return this.name; + } + + @Nonnull + @Override + public List getPathWaypoints() { + if (this.simpleWaypoints == null || this.simpleWaypoints.size() != this.waypoints.size()) { + this.simpleWaypoints = new ObjectArrayList<>(); + + for (short i = 0; i < this.waypoints.size(); i++) { + this.simpleWaypoints.add(new SimplePathWaypoint(i, this.waypoints.get(i))); + } + } + + this.simpleWaypoints = Collections.unmodifiableList(this.simpleWaypoints); + return this.simpleWaypoints; + } + + @Override + public int length() { + return this.waypoints.size(); + } + + public SimplePathWaypoint get(int index) { + List path = this.getPathWaypoints(); + return path.get(index); + } + + public List getWaypoints() { + return this.waypoints; + } + + @Nonnull + @Override + public String toString() { + return "WorldPath{name='" + this.name + "', waypoints=" + this.waypoints + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/path/WorldPathChangedEvent.java b/src/com/hypixel/hytale/server/core/universe/world/path/WorldPathChangedEvent.java new file mode 100644 index 0000000..aade266 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/path/WorldPathChangedEvent.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.core.universe.world.path; + +import com.hypixel.hytale.event.IEvent; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class WorldPathChangedEvent implements IEvent { + private WorldPath worldPath; + + public WorldPathChangedEvent(WorldPath worldPath) { + Objects.requireNonNull(worldPath, "World path must not be null in an event"); + this.worldPath = worldPath; + } + + public WorldPath getWorldPath() { + return this.worldPath; + } + + @Nonnull + @Override + public String toString() { + return "WorldPathChangedEvent{worldPath=" + this.worldPath + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/path/WorldPathConfig.java b/src/com/hypixel/hytale/server/core/universe/world/path/WorldPathConfig.java new file mode 100644 index 0000000..df06e22 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/path/WorldPathConfig.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.server.core.universe.world.path; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.map.MapCodec; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.util.BsonUtil; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonValue; + +public class WorldPathConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder(WorldPathConfig.class, WorldPathConfig::new) + .addField( + new KeyedCodec<>("Paths", new MapCodec<>(WorldPath.CODEC, ConcurrentHashMap::new, false)), + (config, paths) -> config.paths = paths, + config -> config.paths + ) + .build(); + protected Map paths = new ConcurrentHashMap<>(); + + public WorldPathConfig() { + } + + public WorldPath getPath(String name) { + return this.paths.get(name); + } + + @Nonnull + public Map getPaths() { + return Collections.unmodifiableMap(this.paths); + } + + @Nullable + public WorldPath putPath(@Nonnull WorldPath worldPath) { + Objects.requireNonNull(worldPath); + IEventDispatcher dispatcher = HytaleServer.get().getEventBus().dispatchFor(WorldPathChangedEvent.class); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new WorldPathChangedEvent(worldPath)); + } + + return this.paths.put(worldPath.getName(), worldPath); + } + + public WorldPath removePath(String path) { + Objects.requireNonNull(path); + return this.paths.remove(path); + } + + @Nonnull + public CompletableFuture save(World world) { + BsonValue bsonValue = CODEC.encode(this); + return BsonUtil.writeDocument(world.getSavePath().resolve("paths.json"), bsonValue.asDocument()); + } + + @Nonnull + public static CompletableFuture load(World world) { + Path oldPath = world.getSavePath().resolve("paths.bson"); + Path path = world.getSavePath().resolve("paths.json"); + if (Files.exists(oldPath) && !Files.exists(path)) { + try { + Files.move(oldPath, path); + } catch (IOException var4) { + } + } + + return CompletableFuture.supplyAsync(() -> { + WorldPathConfig config = RawJsonReader.readSyncWithBak(path, CODEC, HytaleLogger.getLogger()); + return config != null ? config : new WorldPathConfig(); + }); + } + + @Nonnull + @Override + public String toString() { + return "WorldPathConfig{paths=" + this.paths + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/spawn/FitToHeightMapSpawnProvider.java b/src/com/hypixel/hytale/server/core/universe/world/spawn/FitToHeightMapSpawnProvider.java new file mode 100644 index 0000000..b6ae56c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/spawn/FitToHeightMapSpawnProvider.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.core.universe.world.spawn; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class FitToHeightMapSpawnProvider implements ISpawnProvider { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder(FitToHeightMapSpawnProvider.class, FitToHeightMapSpawnProvider::new) + .documentation( + "A spawn provider that takes a spawn point from another provider and attempts to fit it to the heightmap of the world whenever the spawn point would place the player out of bounds." + ) + .append(new KeyedCodec<>("SpawnProvider", ISpawnProvider.CODEC), (o, i) -> o.spawnProvider = i, o -> o.spawnProvider) + .documentation("The target spawn provider to take the initial spawn point from.") + .add() + .build(); + private ISpawnProvider spawnProvider; + + protected FitToHeightMapSpawnProvider() { + } + + public FitToHeightMapSpawnProvider(ISpawnProvider spawnProvider) { + this.spawnProvider = spawnProvider; + } + + @Nonnull + @Override + public Transform getSpawnPoint(@Nonnull World world, @Nonnull UUID uuid) { + Transform spawnPoint = this.spawnProvider.getSpawnPoint(world, uuid); + Vector3d position = spawnPoint.getPosition(); + if (position.getY() < 0.0) { + WorldChunk worldChunk = world.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(position.getX(), position.getZ())); + if (worldChunk != null) { + int x = MathUtil.floor(position.getX()); + int z = MathUtil.floor(position.getZ()); + position.setY(worldChunk.getHeight(x, z) + 1); + } + } + + return spawnPoint; + } + + @Override + public Transform[] getSpawnPoints() { + return this.spawnProvider.getSpawnPoints(); + } + + @Override + public boolean isWithinSpawnDistance(@Nonnull Vector3d position, double distance) { + return this.spawnProvider.isWithinSpawnDistance(position, distance); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/spawn/GlobalSpawnProvider.java b/src/com/hypixel/hytale/server/core/universe/world/spawn/GlobalSpawnProvider.java new file mode 100644 index 0000000..43ee05a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/spawn/GlobalSpawnProvider.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.server.core.universe.world.spawn; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class GlobalSpawnProvider implements ISpawnProvider { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder(GlobalSpawnProvider.class, GlobalSpawnProvider::new) + .documentation("A spawn provider that provides a single static spawn point for all players.") + .append(new KeyedCodec<>("SpawnPoint", Transform.CODEC_DEGREES), (o, i) -> o.spawnPoint = i, o -> o.spawnPoint) + .documentation("The spawn point for all players to spawn at") + .add() + .build(); + private Transform spawnPoint; + + public GlobalSpawnProvider() { + } + + public GlobalSpawnProvider(Transform spawnPoint) { + this.spawnPoint = spawnPoint; + } + + @Override + public Transform getSpawnPoint(@Nonnull World world, @Nonnull UUID uuid) { + return this.spawnPoint; + } + + @Nonnull + @Override + public Transform[] getSpawnPoints() { + return new Transform[]{this.spawnPoint}; + } + + @Override + public boolean isWithinSpawnDistance(@Nonnull Vector3d position, double distance) { + return position.distanceSquaredTo(this.spawnPoint.getPosition()) < distance * distance; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/spawn/ISpawnProvider.java b/src/com/hypixel/hytale/server/core/universe/world/spawn/ISpawnProvider.java new file mode 100644 index 0000000..747eea6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/spawn/ISpawnProvider.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.core.universe.world.spawn; + +import com.hypixel.hytale.codec.lookup.BuilderCodecMapCodec; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; + +public interface ISpawnProvider { + @Nonnull + BuilderCodecMapCodec CODEC; + + default Transform getSpawnPoint(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + UUIDComponent uuidComponent = componentAccessor.getComponent(ref, UUIDComponent.getComponentType()); + if (!.$assertionsDisabled && uuidComponent == null) { + throw new AssertionError(); + } else { + World world = componentAccessor.getExternalData().getWorld(); + return this.getSpawnPoint(world, uuidComponent.getUuid()); + } + } + + @Deprecated(forRemoval = true) + default Transform getSpawnPoint(@Nonnull Entity entity) { + return this.getSpawnPoint(entity.getWorld(), entity.getUuid()); + } + + Transform getSpawnPoint(@Nonnull World var1, @Nonnull UUID var2); + + @Deprecated + Transform[] getSpawnPoints(); + + boolean isWithinSpawnDistance(@Nonnull Vector3d var1, double var2); + + static { + if (.$assertionsDisabled) { + } + + CODEC = new BuilderCodecMapCodec<>(true); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/spawn/IndividualSpawnProvider.java b/src/com/hypixel/hytale/server/core/universe/world/spawn/IndividualSpawnProvider.java new file mode 100644 index 0000000..f5b13d5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/spawn/IndividualSpawnProvider.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.server.core.universe.world.spawn; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.math.util.HashUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.World; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class IndividualSpawnProvider implements ISpawnProvider { + @Nonnull + public static BuilderCodec CODEC = BuilderCodec.builder(IndividualSpawnProvider.class, IndividualSpawnProvider::new) + .documentation( + "A spawn provider that selects a spawn point from a list based on the player being spawned in. This gives random but consistent spawn points for players." + ) + .append( + new KeyedCodec<>("SpawnPoints", new ArrayCodec<>(Transform.CODEC, Transform[]::new)), (o, i) -> o.spawnPoints = i, o -> o.spawnPoints + ) + .documentation("The list of spawn points to select from.") + .add() + .build(); + private Transform[] spawnPoints; + + public IndividualSpawnProvider() { + } + + public IndividualSpawnProvider(Transform spawnPoint) { + this.spawnPoints = new Transform[1]; + this.spawnPoints[0] = spawnPoint; + } + + public IndividualSpawnProvider(Transform[] spawnPoints) { + this.spawnPoints = spawnPoints; + } + + @Override + public Transform getSpawnPoint(@Nonnull World world, @Nonnull UUID uuid) { + return this.spawnPoints[Math.abs((int)HashUtil.hashUuid(uuid)) % this.spawnPoints.length]; + } + + @Override + public Transform[] getSpawnPoints() { + return this.spawnPoints; + } + + @Nullable + public Transform getFirstSpawnPoint() { + return this.spawnPoints.length == 0 ? null : this.spawnPoints[0]; + } + + @Override + public boolean isWithinSpawnDistance(@Nonnull Vector3d position, double distance) { + double distanceSquared = distance * distance; + + for (Transform point : this.spawnPoints) { + if (position.distanceSquaredTo(point.getPosition()) < distanceSquared) { + return true; + } + } + + return false; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/BufferChunkLoader.java b/src/com/hypixel/hytale/server/core/universe/world/storage/BufferChunkLoader.java new file mode 100644 index 0000000..50cbf31 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/BufferChunkLoader.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.core.universe.world.storage; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.util.BsonUtil; +import java.nio.ByteBuffer; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; + +public abstract class BufferChunkLoader implements IChunkLoader { + @Nonnull + private final Store store; + + public BufferChunkLoader(@Nonnull Store store) { + Objects.requireNonNull(store); + this.store = store; + } + + @Nonnull + public Store getStore() { + return this.store; + } + + public abstract CompletableFuture loadBuffer(int var1, int var2); + + @Nonnull + @Override + public CompletableFuture> loadHolder(int x, int z) { + return this.loadBuffer(x, z).thenApplyAsync(buffer -> { + if (buffer == null) { + return null; + } else { + BsonDocument bsonDocument = BsonUtil.readFromBuffer(buffer); + Holder holder = ChunkStore.REGISTRY.deserialize(bsonDocument); + WorldChunk worldChunkComponent = holder.getComponent(WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + worldChunkComponent.loadFromHolder(this.store.getExternalData().getWorld(), x, z, holder); + return holder; + } + }); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/BufferChunkSaver.java b/src/com/hypixel/hytale/server/core/universe/world/storage/BufferChunkSaver.java new file mode 100644 index 0000000..5ff64b5 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/BufferChunkSaver.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.core.universe.world.storage; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.util.BsonUtil; +import java.nio.ByteBuffer; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; + +public abstract class BufferChunkSaver implements IChunkSaver { + @Nonnull + private final Store store; + + protected BufferChunkSaver(@Nonnull Store store) { + Objects.requireNonNull(store); + this.store = store; + } + + @Nonnull + public Store getStore() { + return this.store; + } + + @Nonnull + public abstract CompletableFuture saveBuffer(int var1, int var2, @Nonnull ByteBuffer var3); + + @Nonnull + public abstract CompletableFuture removeBuffer(int var1, int var2); + + @Nonnull + @Override + public CompletableFuture saveHolder(int x, int z, @Nonnull Holder holder) { + BsonDocument document = ChunkStore.REGISTRY.serialize(holder); + ByteBuffer buffer = ByteBuffer.wrap(BsonUtil.writeToBytes(document)); + return this.saveBuffer(x, z, buffer); + } + + @Nonnull + @Override + public CompletableFuture removeHolder(int x, int z) { + return this.removeBuffer(x, z); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/ChunkStore.java b/src/com/hypixel/hytale/server/core/universe/world/storage/ChunkStore.java new file mode 100644 index 0000000..614ba6d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/ChunkStore.java @@ -0,0 +1,786 @@ +package com.hypixel.hytale.server.core.universe.world.storage; + +import com.hypixel.fastutil.longs.Long2ObjectConcurrentHashMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.store.CodecKey; +import com.hypixel.hytale.codec.store.CodecStore; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.IResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.SystemType; +import com.hypixel.hytale.component.system.StoreSystem; +import com.hypixel.hytale.component.system.data.EntityDataSystem; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.metrics.MetricProvider; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.protocol.Packet; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldProvider; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkFlag; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.events.ChunkPreLoadProcessEvent; +import com.hypixel.hytale.server.core.universe.world.storage.component.ChunkSavingSystems; +import com.hypixel.hytale.server.core.universe.world.storage.component.ChunkUnloadingSystem; +import com.hypixel.hytale.server.core.universe.world.storage.provider.IChunkStorageProvider; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.longs.LongSets; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.StampedLock; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChunkStore implements WorldProvider { + @Nonnull + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() + .register("Store", ChunkStore::getStore, Store.METRICS_REGISTRY) + .register("ChunkLoader", MetricProvider.maybe(ChunkStore::getLoader)) + .register("ChunkSaver", MetricProvider.maybe(ChunkStore::getSaver)) + .register("WorldGen", MetricProvider.maybe(ChunkStore::getGenerator)) + .register("TotalGeneratedChunkCount", chunkComponentStore -> (long)chunkComponentStore.totalGeneratedChunksCount.get(), Codec.LONG) + .register("TotalLoadedChunkCount", chunkComponentStore -> (long)chunkComponentStore.totalLoadedChunksCount.get(), Codec.LONG); + public static final long MAX_FAILURE_BACKOFF_NANOS = TimeUnit.SECONDS.toNanos(10L); + public static final long FAILURE_BACKOFF_NANOS = TimeUnit.MILLISECONDS.toNanos(1L); + public static final ComponentRegistry REGISTRY = new ComponentRegistry<>(); + public static final CodecKey> HOLDER_CODEC_KEY = new CodecKey<>("ChunkHolder"); + @Nonnull + public static final SystemType LOAD_PACKETS_DATA_QUERY_SYSTEM_TYPE = REGISTRY.registerSystemType( + ChunkStore.LoadPacketDataQuerySystem.class + ); + @Nonnull + public static final SystemType LOAD_FUTURE_PACKETS_DATA_QUERY_SYSTEM_TYPE = REGISTRY.registerSystemType( + ChunkStore.LoadFuturePacketDataQuerySystem.class + ); + @Nonnull + public static final SystemType UNLOAD_PACKETS_DATA_QUERY_SYSTEM_TYPE = REGISTRY.registerSystemType( + ChunkStore.UnloadPacketDataQuerySystem.class + ); + @Nonnull + public static final ResourceType UNLOAD_RESOURCE = REGISTRY.registerResource( + ChunkUnloadingSystem.Data.class, ChunkUnloadingSystem.Data::new + ); + @Nonnull + public static final ResourceType SAVE_RESOURCE = REGISTRY.registerResource( + ChunkSavingSystems.Data.class, ChunkSavingSystems.Data::new + ); + public static final SystemGroup INIT_GROUP = REGISTRY.registerSystemGroup(); + @Nonnull + private final World world; + @Nonnull + private final Long2ObjectConcurrentHashMap chunks = new Long2ObjectConcurrentHashMap<>(true, ChunkUtil.NOT_FOUND); + private Store store; + @Nullable + private IChunkLoader loader; + @Nullable + private IChunkSaver saver; + @Nullable + private IWorldGen generator; + @Nonnull + private CompletableFuture generatorLoaded = new CompletableFuture<>(); + private final AtomicInteger totalGeneratedChunksCount = new AtomicInteger(); + private final AtomicInteger totalLoadedChunksCount = new AtomicInteger(); + + public ChunkStore(@Nonnull World world) { + this.world = world; + } + + @Nonnull + @Override + public World getWorld() { + return this.world; + } + + @Nonnull + public Store getStore() { + return this.store; + } + + @Nullable + public IChunkLoader getLoader() { + return this.loader; + } + + @Nullable + public IChunkSaver getSaver() { + return this.saver; + } + + @Nullable + public IWorldGen getGenerator() { + return this.generator; + } + + public void setGenerator(@Nullable IWorldGen generator) { + if (this.generator != null) { + this.generator.shutdown(); + } + + this.totalGeneratedChunksCount.set(0); + this.generator = generator; + if (generator != null) { + this.generatorLoaded.complete(null); + this.generatorLoaded = new CompletableFuture<>(); + } + } + + @Nonnull + public LongSet getChunkIndexes() { + return LongSets.unmodifiable(this.chunks.keySet()); + } + + public int getLoadedChunksCount() { + return this.chunks.size(); + } + + public int getTotalGeneratedChunksCount() { + return this.totalGeneratedChunksCount.get(); + } + + public int getTotalLoadedChunksCount() { + return this.totalLoadedChunksCount.get(); + } + + public void start(@Nonnull IResourceStorage resourceStorage) { + this.store = REGISTRY.addStore(this, resourceStorage, store -> this.store = store); + } + + public void waitForLoadingChunks() { + long start = System.nanoTime(); + + boolean hasLoadingChunks; + do { + this.world.consumeTaskQueue(); + Thread.yield(); + hasLoadingChunks = false; + + for (Entry entry : this.chunks.long2ObjectEntrySet()) { + ChunkStore.ChunkLoadState chunkState = entry.getValue(); + long stamp = chunkState.lock.readLock(); + + try { + CompletableFuture> future = chunkState.future; + if (future != null && !future.isDone()) { + hasLoadingChunks = true; + break; + } + } finally { + chunkState.lock.unlockRead(stamp); + } + } + } while (hasLoadingChunks && System.nanoTime() - start <= 5000000000L); + + this.world.consumeTaskQueue(); + } + + public void shutdown() { + this.store.shutdown(); + this.chunks.clear(); + } + + @Nonnull + private Ref add(@Nonnull Holder holder) { + this.world.debugAssertInTickingThread(); + WorldChunk worldChunkComponent = holder.getComponent(WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + ChunkStore.ChunkLoadState chunkState = this.chunks.get(worldChunkComponent.getIndex()); + if (chunkState == null) { + throw new IllegalStateException("Expected the ChunkLoadState to exist!"); + } else { + Ref oldReference = null; + long stamp = chunkState.lock.writeLock(); + + try { + if (chunkState.future == null) { + throw new IllegalStateException("Expected the ChunkLoadState to have a future!"); + } + + if (chunkState.reference != null) { + oldReference = chunkState.reference; + chunkState.reference = null; + } + } finally { + chunkState.lock.unlockWrite(stamp); + } + + if (oldReference != null) { + WorldChunk oldWorldChunkComponent = this.store.getComponent(oldReference, WorldChunk.getComponentType()); + + assert oldWorldChunkComponent != null; + + oldWorldChunkComponent.setFlag(ChunkFlag.TICKING, false); + this.store.removeEntity(oldReference, RemoveReason.REMOVE); + this.world.getNotificationHandler().updateChunk(worldChunkComponent.getIndex()); + } + + oldReference = this.store.addEntity(holder, AddReason.SPAWN); + if (oldReference == null) { + throw new UnsupportedOperationException("Unable to add the chunk to the world!"); + } else { + worldChunkComponent.setReference(oldReference); + stamp = chunkState.lock.writeLock(); + + Ref var17; + try { + chunkState.reference = oldReference; + chunkState.flags = 0; + chunkState.future = null; + chunkState.throwable = null; + chunkState.failedWhen = 0L; + chunkState.failedCounter = 0; + var17 = oldReference; + } finally { + chunkState.lock.unlockWrite(stamp); + } + + return var17; + } + } + } + + public void remove(@Nonnull Ref reference, @Nonnull RemoveReason reason) { + this.world.debugAssertInTickingThread(); + WorldChunk worldChunkComponent = this.store.getComponent(reference, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + long index = worldChunkComponent.getIndex(); + ChunkStore.ChunkLoadState chunkState = this.chunks.get(index); + long stamp = chunkState.lock.readLock(); + + try { + worldChunkComponent.setFlag(ChunkFlag.TICKING, false); + this.store.removeEntity(reference, reason); + if (chunkState.future != null) { + chunkState.reference = null; + } else { + this.chunks.remove(index, chunkState); + } + } finally { + chunkState.lock.unlockRead(stamp); + } + } + + @Nullable + public Ref getChunkReference(long index) { + ChunkStore.ChunkLoadState chunkState = this.chunks.get(index); + if (chunkState == null) { + return null; + } else { + long stamp = chunkState.lock.tryOptimisticRead(); + Ref reference = chunkState.reference; + if (chunkState.lock.validate(stamp)) { + return reference; + } else { + stamp = chunkState.lock.readLock(); + + Ref var7; + try { + var7 = chunkState.reference; + } finally { + chunkState.lock.unlockRead(stamp); + } + + return var7; + } + } + } + + @Nullable + public Ref getChunkSectionReference(int x, int y, int z) { + Ref ref = this.getChunkReference(ChunkUtil.indexChunk(x, z)); + if (ref == null) { + return null; + } else { + ChunkColumn chunkColumnComponent = this.store.getComponent(ref, ChunkColumn.getComponentType()); + return chunkColumnComponent == null ? null : chunkColumnComponent.getSection(y); + } + } + + @Nullable + public Ref getChunkSectionReference(@Nonnull ComponentAccessor commandBuffer, int x, int y, int z) { + Ref ref = this.getChunkReference(ChunkUtil.indexChunk(x, z)); + if (ref == null) { + return null; + } else { + ChunkColumn chunkColumnComponent = commandBuffer.getComponent(ref, ChunkColumn.getComponentType()); + return chunkColumnComponent == null ? null : chunkColumnComponent.getSection(y); + } + } + + @Nonnull + public CompletableFuture> getChunkSectionReferenceAsync(int x, int y, int z) { + return y >= 0 && y < 10 ? this.getChunkReferenceAsync(ChunkUtil.indexChunk(x, z)).thenApplyAsync(ref -> { + if (ref != null && ref.isValid()) { + Store store = ref.getStore(); + ChunkColumn chunkColumnComponent = store.getComponent((Ref)ref, ChunkColumn.getComponentType()); + return chunkColumnComponent == null ? null : chunkColumnComponent.getSection(y); + } else { + return null; + } + }, this.store.getExternalData().getWorld()) : CompletableFuture.failedFuture(new IndexOutOfBoundsException("Invalid y: " + y)); + } + + @Nullable + public > T getChunkComponent(long index, @Nonnull ComponentType componentType) { + Ref reference = this.getChunkReference(index); + return reference != null && reference.isValid() ? this.store.getComponent(reference, componentType) : null; + } + + @Nonnull + public CompletableFuture> getChunkReferenceAsync(long index) { + return this.getChunkReferenceAsync(index, 0); + } + + @Nonnull + public CompletableFuture> getChunkReferenceAsync(long index, int flags) { + if (this.store.isShutdown()) { + return CompletableFuture.completedFuture(null); + } else { + ChunkStore.ChunkLoadState chunkState; + if ((flags & 3) == 3) { + chunkState = this.chunks.get(index); + if (chunkState == null) { + return CompletableFuture.completedFuture(null); + } + + long stamp = chunkState.lock.readLock(); + + try { + if ((flags & 4) == 0 || (chunkState.flags & 4) != 0) { + if (chunkState.reference != null) { + return CompletableFuture.completedFuture(chunkState.reference); + } + + if (chunkState.future != null) { + return chunkState.future; + } + + return CompletableFuture.completedFuture(null); + } + } finally { + chunkState.lock.unlockRead(stamp); + } + } else { + chunkState = this.chunks.computeIfAbsent(index, l -> new ChunkStore.ChunkLoadState()); + } + + long stamp = chunkState.lock.writeLock(); + if (chunkState.future == null && chunkState.reference != null && (flags & 8) == 0) { + Ref reference = chunkState.reference; + if ((flags & 4) == 0) { + chunkState.lock.unlockWrite(stamp); + return CompletableFuture.completedFuture(reference); + } else if (this.world.isInThread() && (flags & -2147483648) == 0) { + chunkState.lock.unlockWrite(stamp); + WorldChunk worldChunkComponent = this.store.getComponent(reference, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + worldChunkComponent.setFlag(ChunkFlag.TICKING, true); + return CompletableFuture.completedFuture(reference); + } else { + chunkState.lock.unlockWrite(stamp); + return CompletableFuture.supplyAsync(() -> { + WorldChunk worldChunkComponent = this.store.getComponent(reference, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + worldChunkComponent.setFlag(ChunkFlag.TICKING, true); + return reference; + }, this.world); + } + } else { + try { + if (chunkState.throwable != null) { + long nanosSince = System.nanoTime() - chunkState.failedWhen; + int count = chunkState.failedCounter; + if (nanosSince < Math.min(MAX_FAILURE_BACKOFF_NANOS, count * count * FAILURE_BACKOFF_NANOS)) { + return CompletableFuture.failedFuture(new RuntimeException("Chunk failure backoff", chunkState.throwable)); + } + + chunkState.throwable = null; + chunkState.failedWhen = 0L; + } + + boolean isNew = chunkState.future == null; + if (isNew) { + chunkState.flags = flags; + } + + int x = ChunkUtil.xOfChunkIndex(index); + int z = ChunkUtil.zOfChunkIndex(index); + if ((isNew || (chunkState.flags & 1) != 0) && (flags & 1) == 0) { + if (chunkState.future == null) { + chunkState.future = this.loader.loadHolder(x, z).thenApplyAsync(holder -> { + if (holder != null && !this.store.isShutdown()) { + this.totalLoadedChunksCount.getAndIncrement(); + return this.preLoadChunkAsync(index, (Holder)holder, false); + } else { + return null; + } + }).thenApplyAsync(this::postLoadChunk, this.world).exceptionally(throwable -> { + LOGGER.at(Level.SEVERE).withCause(throwable).log("Failed to load chunk! %s, %s", x, z); + chunkState.fail(throwable); + throw SneakyThrow.sneakyThrow(throwable); + }); + } else { + chunkState.flags &= -2; + chunkState.future = chunkState.future + .thenCompose( + reference -> reference != null + ? CompletableFuture.completedFuture((Ref)reference) + : this.loader.loadHolder(x, z).thenApplyAsync(holder -> { + if (holder != null && !this.store.isShutdown()) { + this.totalLoadedChunksCount.getAndIncrement(); + return this.preLoadChunkAsync(index, (Holder)holder, false); + } else { + return null; + } + }).thenApplyAsync(this::postLoadChunk, this.world).exceptionally(throwable -> { + LOGGER.at(Level.SEVERE).withCause(throwable).log("Failed to load chunk! %s, %s", x, z); + chunkState.fail(throwable); + throw SneakyThrow.sneakyThrow(throwable); + }) + ); + } + } + + if ((isNew || (chunkState.flags & 2) != 0) && (flags & 2) == 0) { + int seed = (int)this.world.getWorldConfig().getSeed(); + if (chunkState.future == null) { + CompletableFuture future; + if (this.generator == null) { + future = this.generatorLoaded + .thenCompose(aVoid -> this.generator.generate(seed, index, x, z, (flags & 16) != 0 ? this::isChunkStillNeeded : null)); + } else { + future = this.generator.generate(seed, index, x, z, (flags & 16) != 0 ? this::isChunkStillNeeded : null); + } + + chunkState.future = future.>thenApplyAsync(generatedChunk -> { + if (generatedChunk != null && !this.store.isShutdown()) { + this.totalGeneratedChunksCount.getAndIncrement(); + return this.preLoadChunkAsync(index, generatedChunk.toHolder(this.world), true); + } else { + return null; + } + }).thenApplyAsync(this::postLoadChunk, this.world).exceptionally(throwable -> { + LOGGER.at(Level.SEVERE).withCause(throwable).log("Failed to generate chunk! %s, %s", x, z); + chunkState.fail(throwable); + throw SneakyThrow.sneakyThrow(throwable); + }); + } else { + chunkState.flags &= -3; + chunkState.future = chunkState.future.thenCompose(reference -> { + if (reference != null) { + return CompletableFuture.completedFuture((Ref)reference); + } else { + CompletableFuture futurex; + if (this.generator == null) { + futurex = this.generatorLoaded.thenCompose(aVoid -> this.generator.generate(seed, index, x, z, null)); + } else { + futurex = this.generator.generate(seed, index, x, z, null); + } + + return futurex.>thenApplyAsync(generatedChunk -> { + if (generatedChunk != null && !this.store.isShutdown()) { + this.totalGeneratedChunksCount.getAndIncrement(); + return this.preLoadChunkAsync(index, generatedChunk.toHolder(this.world), true); + } else { + return null; + } + }).thenApplyAsync(this::postLoadChunk, this.world).exceptionally(throwable -> { + LOGGER.at(Level.SEVERE).withCause(throwable).log("Failed to generate chunk! %s, %s", x, z); + chunkState.fail(throwable); + throw SneakyThrow.sneakyThrow(throwable); + }); + } + }); + } + } + + if ((isNew || (chunkState.flags & 4) == 0) && (flags & 4) != 0) { + chunkState.flags |= 4; + if (chunkState.future != null) { + chunkState.future = chunkState.future.>thenApplyAsync(reference -> { + if (reference != null) { + WorldChunk worldChunkComponent = this.store.getComponent((Ref)reference, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + worldChunkComponent.setFlag(ChunkFlag.TICKING, true); + } + + return reference; + }, this.world).exceptionally(throwable -> { + LOGGER.at(Level.SEVERE).withCause(throwable).log("Failed to set chunk ticking! %s, %s", x, z); + chunkState.fail(throwable); + throw SneakyThrow.sneakyThrow(throwable); + }); + } + } + + return chunkState.future != null ? chunkState.future : CompletableFuture.completedFuture(null); + } finally { + chunkState.lock.unlockWrite(stamp); + } + } + } + } + + private boolean isChunkStillNeeded(long index) { + for (PlayerRef playerRef : this.world.getPlayerRefs()) { + if (playerRef.getChunkTracker().shouldBeVisible(index)) { + return true; + } + } + + return false; + } + + public boolean isChunkOnBackoff(long index, long maxFailureBackoffNanos) { + ChunkStore.ChunkLoadState chunkState = this.chunks.get(index); + if (chunkState == null) { + return false; + } else { + long stamp = chunkState.lock.readLock(); + + boolean nanosSince; + try { + if (chunkState.throwable != null) { + long nanosSincex = System.nanoTime() - chunkState.failedWhen; + int count = chunkState.failedCounter; + return nanosSincex < Math.min(maxFailureBackoffNanos, count * count * FAILURE_BACKOFF_NANOS); + } + + nanosSince = false; + } finally { + chunkState.lock.unlockRead(stamp); + } + + return nanosSince; + } + } + + @Nonnull + private Holder preLoadChunkAsync(long index, @Nonnull Holder holder, boolean newlyGenerated) { + WorldChunk worldChunkComponent = holder.getComponent(WorldChunk.getComponentType()); + if (worldChunkComponent == null) { + throw new IllegalStateException( + String.format("Holder missing WorldChunk component! (%d, %d)", ChunkUtil.xOfChunkIndex(index), ChunkUtil.zOfChunkIndex(index)) + ); + } else if (worldChunkComponent.getIndex() != index) { + throw new IllegalStateException( + String.format( + "Incorrect chunk index! Got (%d, %d) expected (%d, %d)", + worldChunkComponent.getX(), + worldChunkComponent.getZ(), + ChunkUtil.xOfChunkIndex(index), + ChunkUtil.zOfChunkIndex(index) + ) + ); + } else { + BlockChunk blockChunk = holder.getComponent(BlockChunk.getComponentType()); + if (blockChunk == null) { + throw new IllegalStateException( + String.format("Holder missing BlockChunk component! (%d, %d)", ChunkUtil.xOfChunkIndex(index), ChunkUtil.zOfChunkIndex(index)) + ); + } else { + blockChunk.loadFromHolder(holder); + worldChunkComponent.setFlag(ChunkFlag.NEWLY_GENERATED, newlyGenerated); + worldChunkComponent.setLightingUpdatesEnabled(false); + if (newlyGenerated && this.world.getWorldConfig().shouldSaveNewChunks()) { + worldChunkComponent.markNeedsSaving(); + } + + try { + long start = System.nanoTime(); + IEventDispatcher dispatcher = HytaleServer.get() + .getEventBus() + .dispatchFor(ChunkPreLoadProcessEvent.class, this.world.getName()); + if (dispatcher.hasListener()) { + ChunkPreLoadProcessEvent event = dispatcher.dispatch(new ChunkPreLoadProcessEvent(holder, worldChunkComponent, newlyGenerated, start)); + if (!event.didLog()) { + long end = System.nanoTime(); + long diff = end - start; + if (diff > this.world.getTickStepNanos()) { + LOGGER.at(Level.SEVERE) + .log( + "Took too long to pre-load process chunk: %s > TICK_STEP, Has GC Run: %s, %s", + FormatUtil.nanosToString(diff), + this.world.consumeGCHasRun(), + worldChunkComponent + ); + } + } + } + } finally { + worldChunkComponent.setLightingUpdatesEnabled(true); + } + + return holder; + } + } + } + + @Nullable + private Ref postLoadChunk(@Nullable Holder holder) { + this.world.debugAssertInTickingThread(); + if (holder != null && !this.store.isShutdown()) { + long start = System.nanoTime(); + WorldChunk worldChunkComponent = holder.getComponent(WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + worldChunkComponent.setFlag(ChunkFlag.START_INIT, true); + if (worldChunkComponent.is(ChunkFlag.TICKING)) { + holder.tryRemoveComponent(REGISTRY.getNonTickingComponentType()); + } else { + holder.ensureComponent(REGISTRY.getNonTickingComponentType()); + } + + Ref reference = this.add(holder); + worldChunkComponent.initFlags(); + this.world.getChunkLighting().init(worldChunkComponent); + long end = System.nanoTime(); + long diff = end - start; + if (diff > this.world.getTickStepNanos()) { + LOGGER.at(Level.SEVERE) + .log( + "Took too long to post-load process chunk: %s > TICK_STEP, Has GC Run: %s, %s", + FormatUtil.nanosToString(diff), + this.world.consumeGCHasRun(), + worldChunkComponent + ); + } + + return reference; + } else { + return null; + } + } + + static { + CodecStore.STATIC.putCodecSupplier(HOLDER_CODEC_KEY, REGISTRY::getEntityCodec); + REGISTRY.registerSystem(new ChunkStore.ChunkLoaderSaverSetupSystem()); + REGISTRY.registerSystem(new ChunkUnloadingSystem()); + REGISTRY.registerSystem(new ChunkSavingSystems.WorldRemoved()); + REGISTRY.registerSystem(new ChunkSavingSystems.Ticking()); + } + + private static class ChunkLoadState { + private final StampedLock lock = new StampedLock(); + private int flags = 0; + @Nullable + private CompletableFuture> future; + @Nullable + private Ref reference; + @Nullable + private Throwable throwable; + private long failedWhen; + private int failedCounter; + + private ChunkLoadState() { + } + + private void fail(Throwable throwable) { + long stamp = this.lock.writeLock(); + + try { + this.flags = 0; + this.future = null; + this.throwable = throwable; + this.failedWhen = System.nanoTime(); + this.failedCounter++; + } finally { + this.lock.unlockWrite(stamp); + } + } + } + + public static class ChunkLoaderSaverSetupSystem extends StoreSystem { + public ChunkLoaderSaverSetupSystem() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return ChunkStore.INIT_GROUP; + } + + @Override + public void onSystemAddedToStore(@Nonnull Store store) { + ChunkStore data = store.getExternalData(); + World world = data.getWorld(); + IChunkStorageProvider chunkStorageProvider = world.getWorldConfig().getChunkStorageProvider(); + + try { + data.loader = chunkStorageProvider.getLoader(store); + data.saver = chunkStorageProvider.getSaver(store); + } catch (IOException var6) { + throw SneakyThrow.sneakyThrow(var6); + } + } + + @Override + public void onSystemRemovedFromStore(@Nonnull Store store) { + ChunkStore data = store.getExternalData(); + + try { + if (data.loader != null) { + IChunkLoader oldLoader = data.loader; + data.loader = null; + oldLoader.close(); + } + + if (data.saver != null) { + IChunkSaver oldSaver = data.saver; + data.saver = null; + oldSaver.close(); + } + } catch (IOException var4) { + ChunkStore.LOGGER.at(Level.SEVERE).withCause(var4).log("Failed to close storage!"); + } + } + } + + public abstract static class LoadFuturePacketDataQuerySystem extends EntityDataSystem> { + public LoadFuturePacketDataQuerySystem() { + } + } + + public abstract static class LoadPacketDataQuerySystem extends EntityDataSystem { + public LoadPacketDataQuerySystem() { + } + } + + public abstract static class UnloadPacketDataQuerySystem extends EntityDataSystem { + public UnloadPacketDataQuerySystem() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/EntityStore.java b/src/com/hypixel/hytale/server/core/universe/world/storage/EntityStore.java new file mode 100644 index 0000000..809897f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/EntityStore.java @@ -0,0 +1,172 @@ +package com.hypixel.hytale.server.core.universe.world.storage; + +import com.hypixel.hytale.codec.store.CodecKey; +import com.hypixel.hytale.codec.store.CodecStore; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.IResourceStorage; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldProvider; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityStore implements WorldProvider { + @Nonnull + public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() + .register("Store", EntityStore::getStore, Store.METRICS_REGISTRY); + @Nonnull + public static final ComponentRegistry REGISTRY = new ComponentRegistry<>(); + @Nonnull + public static final CodecKey> HOLDER_CODEC_KEY = new CodecKey<>("EntityHolder"); + @Nonnull + public static final SystemGroup SEND_PACKET_GROUP = REGISTRY.registerSystemGroup(); + @Nonnull + private final AtomicInteger networkIdCounter = new AtomicInteger(1); + @Nonnull + private final World world; + private Store store; + @Nonnull + private final Map> entitiesByUuid = new ConcurrentHashMap<>(); + @Nonnull + private final Int2ObjectMap> networkIdToRef = new Int2ObjectOpenHashMap<>(); + + public EntityStore(@Nonnull World world) { + this.world = world; + } + + public void start(@Nonnull IResourceStorage resourceStorage) { + this.store = REGISTRY.addStore(this, resourceStorage, store -> this.store = store); + } + + public void shutdown() { + this.store.shutdown(); + this.entitiesByUuid.clear(); + } + + public Store getStore() { + return this.store; + } + + @Nullable + public Ref getRefFromUUID(@Nonnull UUID uuid) { + return this.entitiesByUuid.get(uuid); + } + + @Nullable + public Ref getRefFromNetworkId(int networkId) { + return this.networkIdToRef.get(networkId); + } + + public int takeNextNetworkId() { + return this.networkIdCounter.getAndIncrement(); + } + + @Nonnull + @Override + public World getWorld() { + return this.world; + } + + static { + CodecStore.STATIC.putCodecSupplier(HOLDER_CODEC_KEY, REGISTRY::getEntityCodec); + } + + public static class NetworkIdSystem extends RefSystem { + public NetworkIdSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return NetworkId.getComponentType(); + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + EntityStore entityStore = store.getExternalData(); + NetworkId networkIdComponent = commandBuffer.getComponent(ref, NetworkId.getComponentType()); + + assert networkIdComponent != null; + + int networkId = networkIdComponent.getId(); + if (entityStore.networkIdToRef.putIfAbsent(networkId, ref) != null) { + networkId = entityStore.takeNextNetworkId(); + commandBuffer.putComponent(ref, NetworkId.getComponentType(), new NetworkId(networkId)); + entityStore.networkIdToRef.put(networkId, ref); + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + EntityStore entityStore = store.getExternalData(); + NetworkId networkIdComponent = commandBuffer.getComponent(ref, NetworkId.getComponentType()); + + assert networkIdComponent != null; + + entityStore.networkIdToRef.remove(networkIdComponent.getId(), ref); + } + } + + public static class UUIDSystem extends RefSystem { + @Nonnull + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + + public UUIDSystem() { + } + + @Nonnull + @Override + public Query getQuery() { + return UUIDComponent.getComponentType(); + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + UUIDComponent uuidComponent = commandBuffer.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + Ref currentRef = store.getExternalData().entitiesByUuid.putIfAbsent(uuidComponent.getUuid(), ref); + if (currentRef != null) { + LOGGER.at(Level.WARNING).log("Removing duplicate entity with UUID: %s", uuidComponent.getUuid()); + commandBuffer.removeEntity(ref, RemoveReason.REMOVE); + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + UUIDComponent uuidComponent = commandBuffer.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + store.getExternalData().entitiesByUuid.remove(uuidComponent.getUuid(), ref); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/GetChunkFlags.java b/src/com/hypixel/hytale/server/core/universe/world/storage/GetChunkFlags.java new file mode 100644 index 0000000..b3e5b7e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/GetChunkFlags.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.server.core.universe.world.storage; + +public class GetChunkFlags { + public static final int NONE = 0; + public static final int NO_LOAD = 1; + public static final int NO_GENERATE = 2; + public static final int SET_TICKING = 4; + public static final int BYPASS_LOADED = 8; + public static final int POLL_STILL_NEEDED = 16; + public static final int NO_SET_TICKING_SYNC = Integer.MIN_VALUE; + + public GetChunkFlags() { + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/IChunkLoader.java b/src/com/hypixel/hytale/server/core/universe/world/storage/IChunkLoader.java new file mode 100644 index 0000000..33177c6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/IChunkLoader.java @@ -0,0 +1,16 @@ +package com.hypixel.hytale.server.core.universe.world.storage; + +import com.hypixel.hytale.component.Holder; +import it.unimi.dsi.fastutil.longs.LongSet; +import java.io.Closeable; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public interface IChunkLoader extends Closeable { + @Nonnull + CompletableFuture> loadHolder(int var1, int var2); + + @Nonnull + LongSet getIndexes() throws IOException; +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/IChunkSaver.java b/src/com/hypixel/hytale/server/core/universe/world/storage/IChunkSaver.java new file mode 100644 index 0000000..a6046ce --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/IChunkSaver.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.server.core.universe.world.storage; + +import com.hypixel.hytale.component.Holder; +import it.unimi.dsi.fastutil.longs.LongSet; +import java.io.Closeable; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public interface IChunkSaver extends Closeable { + @Nonnull + CompletableFuture saveHolder(int var1, int var2, @Nonnull Holder var3); + + @Nonnull + CompletableFuture removeHolder(int var1, int var2); + + @Nonnull + LongSet getIndexes() throws IOException; + + void flush() throws IOException; +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/component/ChunkSavingSystems.java b/src/com/hypixel/hytale/server/core/universe/world/storage/component/ChunkSavingSystems.java new file mode 100644 index 0000000..c63b85d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/component/ChunkSavingSystems.java @@ -0,0 +1,304 @@ +package com.hypixel.hytale.server.core.universe.world.storage.component; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.RootDependency; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.StoreSystem; +import com.hypixel.hytale.component.system.tick.RunWhenPausedSystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkFlag; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.events.ecs.ChunkSaveEvent; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.IChunkSaver; +import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Deque; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ChunkSavingSystems { + @Nonnull + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private static final ComponentType WORLD_CHUNK_COMPONENT_TYPE = WorldChunk.getComponentType(); + @Nonnull + public static final Query QUERY = Query.and(WORLD_CHUNK_COMPONENT_TYPE, Query.not(ChunkStore.REGISTRY.getNonSerializedComponentType())); + + public ChunkSavingSystems() { + } + + @Nonnull + public static CompletableFuture saveChunksInWorld(@Nonnull Store store) { + HytaleLogger logger = store.getExternalData().getWorld().getLogger(); + ChunkSavingSystems.Data data = store.getResource(ChunkStore.SAVE_RESOURCE); + logger.at(Level.INFO).log("Queuing Chunks..."); + store.forEachChunk(QUERY, (archetypeChunk, b) -> { + for (int index = 0; index < archetypeChunk.size(); index++) { + tryQueue(index, archetypeChunk, b.getStore()); + } + }); + logger.at(Level.INFO).log("Saving All Chunks..."); + + Ref reference; + while ((reference = data.poll()) != null) { + saveChunk(reference, data, true, store); + } + + logger.at(Level.INFO).log("Waiting for Saving Chunks..."); + return data.waitForSavingChunks(); + } + + public static void tryQueue(int index, @Nonnull ArchetypeChunk archetypeChunk, @Nonnull Store store) { + WorldChunk worldChunkComponent = archetypeChunk.getComponent(index, WORLD_CHUNK_COMPONENT_TYPE); + + assert worldChunkComponent != null; + + if (worldChunkComponent.getNeedsSaving() && !worldChunkComponent.isSaving()) { + Ref chunkRef = archetypeChunk.getReferenceTo(index); + ChunkSaveEvent event = new ChunkSaveEvent(worldChunkComponent); + store.invoke(chunkRef, event); + if (!event.isCancelled()) { + store.getResource(ChunkStore.SAVE_RESOURCE).push(chunkRef); + } + } + } + + public static void tryQueueSync(@Nonnull ArchetypeChunk archetypeChunk, @Nonnull CommandBuffer commandBuffer) { + Store store = commandBuffer.getStore(); + ChunkSavingSystems.Data data = store.getResource(ChunkStore.SAVE_RESOURCE); + + for (int index = 0; index < archetypeChunk.size(); index++) { + WorldChunk worldChunkComponent = archetypeChunk.getComponent(index, WORLD_CHUNK_COMPONENT_TYPE); + + assert worldChunkComponent != null; + + if (worldChunkComponent.getNeedsSaving() && !worldChunkComponent.isSaving()) { + Ref chunkRef = archetypeChunk.getReferenceTo(index); + ChunkSaveEvent event = new ChunkSaveEvent(worldChunkComponent); + store.invoke(chunkRef, event); + if (!event.isCancelled()) { + data.push(chunkRef); + } + } + } + } + + public static void saveChunk(@Nonnull Ref reference, @Nonnull ChunkSavingSystems.Data data, boolean report, @Nonnull Store store) { + if (!reference.isValid()) { + LOGGER.at(Level.FINEST).log("Chunk reference in queue is for a chunk that has been removed!"); + } else { + data.toSaveTotal.getAndIncrement(); + WorldChunk worldChunkComponent = store.getComponent(reference, WORLD_CHUNK_COMPONENT_TYPE); + + assert worldChunkComponent != null; + + Holder holder = worldChunkComponent.toHolder(); + ChunkStore chunkStore = store.getExternalData(); + World world = chunkStore.getWorld(); + IChunkSaver saver = chunkStore.getSaver(); + CompletableFuture future = saver.saveHolder(worldChunkComponent.getX(), worldChunkComponent.getZ(), holder).whenComplete((aVoid, throwable) -> { + if (throwable != null) { + LOGGER.at(Level.SEVERE).withCause(throwable).log("Failed to save chunk (%d, %d):", worldChunkComponent.getX(), worldChunkComponent.getZ()); + } else { + worldChunkComponent.setFlag(ChunkFlag.ON_DISK, true); + LOGGER.at(Level.FINEST).log("Finished saving chunk (%d, %d)", worldChunkComponent.getX(), worldChunkComponent.getZ()); + } + }); + data.chunkSavingFutures.add(future); + if (report) { + future.thenRunAsync( + () -> HytaleServer.get().reportSaveProgress(world, data.savedCount.incrementAndGet(), data.toSaveTotal.get() + data.queue.size()) + ); + } + + worldChunkComponent.consumeNeedsSaving(); + } + } + + public static class Data implements Resource { + public static final float QUEUE_UPDATE_INTERVAL = 0.5F; + @Nonnull + private final Set> set = ConcurrentHashMap.newKeySet(); + @Nonnull + private final Deque> queue = new ConcurrentLinkedDeque<>(); + @Nonnull + private final List> chunkSavingFutures = new ObjectArrayList<>(); + private float time; + public boolean isSaving = true; + @Nonnull + private final AtomicInteger savedCount = new AtomicInteger(); + @Nonnull + private final AtomicInteger toSaveTotal = new AtomicInteger(); + + public Data() { + this.time = 0.5F; + } + + public Data(float time) { + this.time = time; + } + + @Nonnull + @Override + public Resource clone() { + return new ChunkSavingSystems.Data(this.time); + } + + public void clearSaveQueue() { + this.queue.clear(); + this.set.clear(); + } + + public void push(@Nonnull Ref reference) { + if (this.set.add(reference)) { + this.queue.push(reference); + } + } + + @Nullable + public Ref poll() { + Ref reference = this.queue.poll(); + if (reference == null) { + return null; + } else { + this.set.remove(reference); + return reference; + } + } + + public boolean checkTimer(float dt) { + this.time -= dt; + if (this.time <= 0.0F) { + this.time += 0.5F; + return true; + } else { + return false; + } + } + + @Nonnull + public CompletableFuture waitForSavingChunks() { + return CompletableFuture.allOf(this.chunkSavingFutures.toArray(CompletableFuture[]::new)); + } + } + + public static class Ticking extends TickingSystem implements RunWhenPausedSystem { + public Ticking() { + } + + @Nonnull + @Override + public Set> getDependencies() { + return RootDependency.lastSet(); + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + ChunkSavingSystems.Data data = store.getResource(ChunkStore.SAVE_RESOURCE); + if (data.isSaving && store.getExternalData().getWorld().getWorldConfig().canSaveChunks()) { + data.chunkSavingFutures.removeIf(CompletableFuture::isDone); + if (data.checkTimer(dt)) { + store.forEachChunk(ChunkSavingSystems.QUERY, ChunkSavingSystems::tryQueueSync); + } + + World world = store.getExternalData().getWorld(); + IChunkSaver saver = store.getExternalData().getSaver(); + int parallelSaves = ForkJoinPool.commonPool().getParallelism(); + + for (int i = 0; i < parallelSaves; i++) { + Ref reference = data.poll(); + if (reference == null) { + break; + } + + if (!reference.isValid()) { + ChunkSavingSystems.LOGGER.at(Level.FINEST).log("Chunk reference in queue is for a chunk that has been removed!"); + return; + } + + WorldChunk chunk = store.getComponent(reference, ChunkSavingSystems.WORLD_CHUNK_COMPONENT_TYPE); + chunk.setSaving(true); + Holder holder = store.copySerializableEntity(reference); + data.toSaveTotal.getAndIncrement(); + data.chunkSavingFutures + .add( + CompletableFuture.>supplyAsync(() -> saver.saveHolder(chunk.getX(), chunk.getZ(), holder)) + .thenCompose(Function.identity()) + .whenCompleteAsync((aVoid, throwable) -> { + if (throwable != null) { + ChunkSavingSystems.LOGGER.at(Level.SEVERE).withCause(throwable).log("Failed to save chunk (%d, %d):", chunk.getX(), chunk.getZ()); + } else { + chunk.setFlag(ChunkFlag.ON_DISK, true); + ChunkSavingSystems.LOGGER.at(Level.FINEST).log("Finished saving chunk (%d, %d)", chunk.getX(), chunk.getZ()); + } + + chunk.consumeNeedsSaving(); + chunk.setSaving(false); + }, world) + ); + } + } + } + } + + public static class WorldRemoved extends StoreSystem { + @Nonnull + private final Set> dependencies = Set.of(new SystemDependency<>(Order.AFTER, ChunkStore.ChunkLoaderSaverSetupSystem.class)); + + public WorldRemoved() { + } + + @Nonnull + @Override + public Set> getDependencies() { + return this.dependencies; + } + + @Override + public void onSystemAddedToStore(@Nonnull Store store) { + } + + @Override + public void onSystemRemovedFromStore(@Nonnull Store store) { + World world = store.getExternalData().getWorld(); + IWorldGen generator = world.getChunkStore().getGenerator(); + if (generator != null) { + world.getLogger().at(Level.INFO).log("Shutting down chunk generator..."); + generator.shutdown(); + } + + if (!world.getWorldConfig().canSaveChunks()) { + world.getLogger().at(Level.INFO).log("This world has opted to disable chunk saving so it will not be saved on shutdown"); + } else { + world.getLogger().at(Level.INFO).log("Saving Chunks..."); + ChunkSavingSystems.Data data = store.getResource(ChunkStore.SAVE_RESOURCE); + data.savedCount.set(0); + data.toSaveTotal.set(0); + ChunkSavingSystems.saveChunksInWorld(store).join(); + world.getLogger().at(Level.INFO).log("Done Saving Chunks!"); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/component/ChunkUnloadingSystem.java b/src/com/hypixel/hytale/server/core/universe/world/storage/component/ChunkUnloadingSystem.java new file mode 100644 index 0000000..e1a8356 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/component/ChunkUnloadingSystem.java @@ -0,0 +1,166 @@ +package com.hypixel.hytale.server.core.universe.world.storage.component; + +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.tick.RunWhenPausedSystem; +import com.hypixel.hytale.component.system.tick.TickingSystem; +import com.hypixel.hytale.math.shape.Box2D; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkFlag; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.events.ecs.ChunkUnloadEvent; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class ChunkUnloadingSystem extends TickingSystem implements RunWhenPausedSystem { + public static final double DESPERATE_UNLOAD_RAM_USAGE_THRESHOLD = 0.85; + public static final int DESPERATE_UNLOAD_MAX_POLL_COUNT = 3; + public static final int TICKS_BEFORE_CHUNK_UNLOADING_REMINDER = 5000; + public int ticksUntilUnloadingReminder = 5000; + + public ChunkUnloadingSystem() { + } + + @Override + public void tick(float dt, int systemIndex, @Nonnull Store store) { + ChunkUnloadingSystem.Data dataResource = store.getResource(ChunkStore.UNLOAD_RESOURCE); + World world = store.getExternalData().getWorld(); + if (!world.getWorldConfig().canUnloadChunks()) { + this.ticksUntilUnloadingReminder--; + if (this.ticksUntilUnloadingReminder <= 0) { + world.getLogger().at(Level.INFO).log("This world has disabled chunk unloading"); + this.ticksUntilUnloadingReminder = 5000; + } + } else { + int pollCount = 1; + double percentOfRAMUsed = 1.0 - (double)Runtime.getRuntime().freeMemory() / Runtime.getRuntime().maxMemory(); + if (percentOfRAMUsed > 0.85) { + double desperatePercent = (percentOfRAMUsed - 0.85) / 0.15000000000000002; + pollCount = Math.max(MathUtil.ceil(desperatePercent * 3.0), 1); + } + + dataResource.pollCount = pollCount; + if (dataResource.tick(dt)) { + dataResource.chunkTrackers.clear(); + world.getEntityStore().getStore().forEachChunk(ChunkTracker.getComponentType(), ChunkUnloadingSystem::collectTrackers); + store.forEachEntityParallel(WorldChunk.getComponentType(), ChunkUnloadingSystem::tryUnload); + } + } + } + + public static void tryUnload(int index, @Nonnull ArchetypeChunk archetypeChunk, @Nonnull CommandBuffer commandBuffer) { + Store store = commandBuffer.getStore(); + World world = store.getExternalData().getWorld(); + WorldChunk worldChunkComponent = archetypeChunk.getComponent(index, WorldChunk.getComponentType()); + + assert worldChunkComponent != null; + + ChunkUnloadingSystem.Data dataResource = commandBuffer.getResource(ChunkStore.UNLOAD_RESOURCE); + ChunkTracker.ChunkVisibility chunkVisibility = getChunkVisibility(dataResource.chunkTrackers, worldChunkComponent.getIndex()); + if (chunkVisibility == ChunkTracker.ChunkVisibility.HOT) { + worldChunkComponent.resetKeepAlive(); + worldChunkComponent.resetActiveTimer(); + } else { + Box2D keepLoaded = world.getWorldConfig().getChunkConfig().getKeepLoadedRegion(); + boolean shouldKeepLoaded = worldChunkComponent.shouldKeepLoaded() + || keepLoaded != null && isChunkInBox(keepLoaded, worldChunkComponent.getX(), worldChunkComponent.getZ()); + int pollCount = dataResource.pollCount; + if (chunkVisibility == ChunkTracker.ChunkVisibility.COLD || worldChunkComponent.getNeedsSaving() || shouldKeepLoaded) { + worldChunkComponent.resetKeepAlive(); + if (worldChunkComponent.is(ChunkFlag.TICKING) && worldChunkComponent.pollActiveTimer(pollCount) <= 0) { + commandBuffer.run(s -> worldChunkComponent.setFlag(ChunkFlag.TICKING, false)); + } + } else if (worldChunkComponent.pollKeepAlive(pollCount) <= 0) { + Ref chunkRef = archetypeChunk.getReferenceTo(index); + ChunkUnloadEvent event = new ChunkUnloadEvent(worldChunkComponent); + commandBuffer.invoke(chunkRef, event); + if (event.isCancelled()) { + if (event.willResetKeepAlive()) { + worldChunkComponent.resetKeepAlive(); + } + } else { + commandBuffer.run(s -> s.getExternalData().remove(chunkRef, RemoveReason.UNLOAD)); + } + } + } + } + + public static ChunkTracker.ChunkVisibility getChunkVisibility(@Nonnull List playerChunkTrackers, long chunkIndex) { + boolean isVisible = false; + + for (ChunkTracker chunkTracker : playerChunkTrackers) { + switch (chunkTracker.getChunkVisibility(chunkIndex)) { + case NONE: + default: + break; + case HOT: + return ChunkTracker.ChunkVisibility.HOT; + case COLD: + isVisible = true; + } + } + + return isVisible ? ChunkTracker.ChunkVisibility.COLD : ChunkTracker.ChunkVisibility.NONE; + } + + private static boolean isChunkInBox(@Nonnull Box2D box, int x, int z) { + int minX = ChunkUtil.minBlock(x); + int minZ = ChunkUtil.minBlock(z); + int maxX = ChunkUtil.maxBlock(x); + int maxZ = ChunkUtil.maxBlock(z); + return maxX >= box.min.x && minX <= box.max.x && maxZ >= box.min.y && minZ <= box.max.y; + } + + private static void collectTrackers(@Nonnull ArchetypeChunk archetypeChunk, @Nonnull CommandBuffer commandBuffer) { + Store chunkStore = commandBuffer.getExternalData().getWorld().getChunkStore().getStore(); + ChunkUnloadingSystem.Data dataResource = chunkStore.getResource(ChunkStore.UNLOAD_RESOURCE); + + for (int index = 0; index < archetypeChunk.size(); index++) { + ChunkTracker chunkTracker = archetypeChunk.getComponent(index, ChunkTracker.getComponentType()); + dataResource.chunkTrackers.add(chunkTracker); + } + } + + public static class Data implements Resource { + public static final float UNLOAD_INTERVAL = 0.5F; + private float time; + private int pollCount = 1; + @Nonnull + private final List chunkTrackers = new ObjectArrayList<>(); + + public Data() { + this.time = 0.5F; + } + + public Data(float time) { + this.time = time; + } + + @Nonnull + @Override + public Resource clone() { + return new ChunkUnloadingSystem.Data(this.time); + } + + public boolean tick(float dt) { + this.time -= dt; + if (this.time <= 0.0F) { + this.time += 0.5F; + return true; + } else { + return false; + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/provider/DefaultChunkStorageProvider.java b/src/com/hypixel/hytale/server/core/universe/world/storage/provider/DefaultChunkStorageProvider.java new file mode 100644 index 0000000..a16ce3a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/provider/DefaultChunkStorageProvider.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.server.core.universe.world.storage.provider; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.IChunkLoader; +import com.hypixel.hytale.server.core.universe.world.storage.IChunkSaver; +import java.io.IOException; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class DefaultChunkStorageProvider implements IChunkStorageProvider { + @Nonnull + public static final DefaultChunkStorageProvider INSTANCE = new DefaultChunkStorageProvider(); + public static final String ID = "Hytale"; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(DefaultChunkStorageProvider.class, () -> INSTANCE) + .documentation("Selects the default recommended storage as decided by the server.") + .build(); + @Nonnull + public static final IChunkStorageProvider DEFAULT = new IndexedStorageChunkStorageProvider(); + + public DefaultChunkStorageProvider() { + } + + @NonNullDecl + @Override + public IChunkLoader getLoader(@NonNullDecl Store store) throws IOException { + return DEFAULT.getLoader(store); + } + + @Nonnull + @Override + public IChunkSaver getSaver(@NonNullDecl Store store) throws IOException { + return DEFAULT.getSaver(store); + } + + @Nonnull + @Override + public String toString() { + return "DefaultChunkStorageProvider{DEFAULT=" + DEFAULT + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/provider/EmptyChunkStorageProvider.java b/src/com/hypixel/hytale/server/core/universe/world/storage/provider/EmptyChunkStorageProvider.java new file mode 100644 index 0000000..23956f7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/provider/EmptyChunkStorageProvider.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.server.core.universe.world.storage.provider; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.IChunkLoader; +import com.hypixel.hytale.server.core.universe.world.storage.IChunkSaver; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.longs.LongSets; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class EmptyChunkStorageProvider implements IChunkStorageProvider { + public static final String ID = "Empty"; + @Nonnull + public static final EmptyChunkStorageProvider INSTANCE = new EmptyChunkStorageProvider(); + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder(EmptyChunkStorageProvider.class, () -> INSTANCE) + .documentation("A chunk storage provider that discards any chunks to save and will always fail to find chunks.") + .build(); + @Nonnull + public static final EmptyChunkStorageProvider.EmptyChunkLoader EMPTY_CHUNK_LOADER = new EmptyChunkStorageProvider.EmptyChunkLoader(); + @Nonnull + public static final EmptyChunkStorageProvider.EmptyChunkSaver EMPTY_CHUNK_SAVER = new EmptyChunkStorageProvider.EmptyChunkSaver(); + + public EmptyChunkStorageProvider() { + } + + @NonNullDecl + @Override + public IChunkLoader getLoader(@NonNullDecl Store store) { + return EMPTY_CHUNK_LOADER; + } + + @Nonnull + @Override + public IChunkSaver getSaver(@NonNullDecl Store store) { + return EMPTY_CHUNK_SAVER; + } + + @Nonnull + @Override + public String toString() { + return "EmptyChunkStorageProvider{}"; + } + + private static class EmptyChunkLoader implements IChunkLoader { + private EmptyChunkLoader() { + } + + @Override + public void close() { + } + + @Nonnull + @Override + public CompletableFuture> loadHolder(int x, int z) { + return CompletableFuture.completedFuture(null); + } + + @Nonnull + @Override + public LongSet getIndexes() { + return LongSets.EMPTY_SET; + } + } + + private static class EmptyChunkSaver implements IChunkSaver { + private EmptyChunkSaver() { + } + + @Override + public void close() { + } + + @Nonnull + @Override + public CompletableFuture saveHolder(int x, int z, @Nonnull Holder holder) { + return CompletableFuture.completedFuture(null); + } + + @Nonnull + @Override + public CompletableFuture removeHolder(int x, int z) { + return CompletableFuture.completedFuture(null); + } + + @Nonnull + @Override + public LongSet getIndexes() { + return LongSets.EMPTY_SET; + } + + @Override + public void flush() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/provider/IChunkStorageProvider.java b/src/com/hypixel/hytale/server/core/universe/world/storage/provider/IChunkStorageProvider.java new file mode 100644 index 0000000..e69af57 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/provider/IChunkStorageProvider.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.server.core.universe.world.storage.provider; + +import com.hypixel.hytale.codec.lookup.BuilderCodecMapCodec; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.IChunkLoader; +import com.hypixel.hytale.server.core.universe.world.storage.IChunkSaver; +import java.io.IOException; +import javax.annotation.Nonnull; + +public interface IChunkStorageProvider { + @Nonnull + BuilderCodecMapCodec CODEC = new BuilderCodecMapCodec<>("Type", true); + + @Nonnull + IChunkLoader getLoader(@Nonnull Store var1) throws IOException; + + @Nonnull + IChunkSaver getSaver(@Nonnull Store var1) throws IOException; +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/provider/IndexedStorageChunkStorageProvider.java b/src/com/hypixel/hytale/server/core/universe/world/storage/provider/IndexedStorageChunkStorageProvider.java new file mode 100644 index 0000000..0d0e0a9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/provider/IndexedStorageChunkStorageProvider.java @@ -0,0 +1,407 @@ +package com.hypixel.hytale.server.core.universe.world.storage.provider; + +import com.hypixel.fastutil.longs.Long2ObjectConcurrentHashMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.system.StoreSystem; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.metrics.MetricProvider; +import com.hypixel.hytale.metrics.MetricResults; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.BufferChunkLoader; +import com.hypixel.hytale.server.core.universe.world.storage.BufferChunkSaver; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.IChunkLoader; +import com.hypixel.hytale.server.core.universe.world.storage.IChunkSaver; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import com.hypixel.hytale.storage.IndexedStorageFile; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntListIterator; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.longs.LongSets; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; +import java.io.Closeable; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Iterator; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class IndexedStorageChunkStorageProvider implements IChunkStorageProvider { + public static final String ID = "IndexedStorage"; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + IndexedStorageChunkStorageProvider.class, IndexedStorageChunkStorageProvider::new + ) + .documentation("Uses the indexed storage file format to store chunks.") + .build(); + + public IndexedStorageChunkStorageProvider() { + } + + @Nonnull + @Override + public IChunkLoader getLoader(@Nonnull Store store) { + return new IndexedStorageChunkStorageProvider.IndexedStorageChunkLoader(store); + } + + @Nonnull + @Override + public IChunkSaver getSaver(@Nonnull Store store) { + return new IndexedStorageChunkStorageProvider.IndexedStorageChunkSaver(store); + } + + @Nonnull + @Override + public String toString() { + return "IndexedStorageChunkStorageProvider{}"; + } + + @Nonnull + private static String toFileName(int regionX, int regionZ) { + return regionX + "." + regionZ + ".region.bin"; + } + + private static long fromFileName(@Nonnull String fileName) { + String[] split = fileName.split("\\."); + if (split.length != 4) { + throw new IllegalArgumentException("Unexpected file name format!"); + } else if (!"region".equals(split[2])) { + throw new IllegalArgumentException("Unexpected file name format!"); + } else if (!"bin".equals(split[3])) { + throw new IllegalArgumentException("Unexpected file extension!"); + } else { + int regionX = Integer.parseInt(split[0]); + int regionZ = Integer.parseInt(split[1]); + return ChunkUtil.indexChunk(regionX, regionZ); + } + } + + public static class IndexedStorageCache implements Closeable, MetricProvider, Resource { + @Nonnull + public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() + .register( + "Files", + cache -> cache.cache + .long2ObjectEntrySet() + .stream() + .map(IndexedStorageChunkStorageProvider.IndexedStorageCache.CacheEntryMetricData::new) + .toArray(IndexedStorageChunkStorageProvider.IndexedStorageCache.CacheEntryMetricData[]::new), + new ArrayCodec<>( + IndexedStorageChunkStorageProvider.IndexedStorageCache.CacheEntryMetricData.CODEC, + IndexedStorageChunkStorageProvider.IndexedStorageCache.CacheEntryMetricData[]::new + ) + ); + private final Long2ObjectConcurrentHashMap cache = new Long2ObjectConcurrentHashMap<>(true, ChunkUtil.NOT_FOUND); + private Path path; + + public IndexedStorageCache() { + } + + public static ResourceType getResourceType() { + return Universe.get().getIndexedStorageCacheResourceType(); + } + + @Nonnull + public Long2ObjectConcurrentHashMap getCache() { + return this.cache; + } + + @Override + public void close() throws IOException { + IOException exception = null; + Iterator iterator = this.cache.values().iterator(); + + while (iterator.hasNext()) { + try { + iterator.next().close(); + iterator.remove(); + } catch (Exception var4) { + if (exception == null) { + exception = new IOException("Failed to close one or more loaders!"); + } + + exception.addSuppressed(var4); + } + } + + if (exception != null) { + throw exception; + } + } + + @Nullable + public IndexedStorageFile getOrTryOpen(int regionX, int regionZ) { + return this.cache.computeIfAbsent(ChunkUtil.indexChunk(regionX, regionZ), k -> { + Path regionFile = this.path.resolve(IndexedStorageChunkStorageProvider.toFileName(regionX, regionZ)); + if (!Files.exists(regionFile)) { + return null; + } else { + try { + return IndexedStorageFile.open(regionFile, StandardOpenOption.READ, StandardOpenOption.WRITE); + } catch (FileNotFoundException var7) { + return null; + } catch (IOException var8) { + throw SneakyThrow.sneakyThrow(var8); + } + } + }); + } + + @Nonnull + public IndexedStorageFile getOrCreate(int regionX, int regionZ) { + return this.cache.computeIfAbsent(ChunkUtil.indexChunk(regionX, regionZ), k -> { + try { + if (!Files.exists(this.path)) { + try { + Files.createDirectory(this.path); + } catch (FileAlreadyExistsException var6) { + } + } + + Path regionFile = this.path.resolve(IndexedStorageChunkStorageProvider.toFileName(regionX, regionZ)); + return IndexedStorageFile.open(regionFile, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); + } catch (IOException var7) { + throw SneakyThrow.sneakyThrow(var7); + } + }); + } + + @Nonnull + public LongSet getIndexes() throws IOException { + if (!Files.exists(this.path)) { + return LongSets.EMPTY_SET; + } else { + LongOpenHashSet chunkIndexes = new LongOpenHashSet(); + + try (Stream stream = Files.list(this.path)) { + stream.forEach(path -> { + if (!Files.isDirectory(path)) { + long regionIndex; + try { + regionIndex = IndexedStorageChunkStorageProvider.fromFileName(path.getFileName().toString()); + } catch (IllegalArgumentException var15) { + return; + } + + int regionX = ChunkUtil.xOfChunkIndex(regionIndex); + int regionZ = ChunkUtil.zOfChunkIndex(regionIndex); + IndexedStorageFile regionFile = this.getOrTryOpen(regionX, regionZ); + if (regionFile != null) { + IntList blobIndexes = regionFile.keys(); + IntListIterator iterator = blobIndexes.iterator(); + + while (iterator.hasNext()) { + int blobIndex = iterator.nextInt(); + int localX = ChunkUtil.xFromColumn(blobIndex); + int localZ = ChunkUtil.zFromColumn(blobIndex); + int chunkX = regionX << 5 | localX; + int chunkZ = regionZ << 5 | localZ; + chunkIndexes.add(ChunkUtil.indexChunk(chunkX, chunkZ)); + } + } + } + }); + } + + return chunkIndexes; + } + } + + public void flush() throws IOException { + IOException exception = null; + + for (IndexedStorageFile indexedStorageFile : this.cache.values()) { + try { + indexedStorageFile.force(false); + } catch (Exception var5) { + if (exception == null) { + exception = new IOException("Failed to close one or more loaders!"); + } + + exception.addSuppressed(var5); + } + } + + if (exception != null) { + throw exception; + } + } + + @Nonnull + @Override + public MetricResults toMetricResults() { + return METRICS_REGISTRY.toMetricResults(this); + } + + @Nonnull + @Override + public Resource clone() { + return new IndexedStorageChunkStorageProvider.IndexedStorageCache(); + } + + private static class CacheEntryMetricData { + @Nonnull + private static final Codec CODEC = BuilderCodec.builder( + IndexedStorageChunkStorageProvider.IndexedStorageCache.CacheEntryMetricData.class, + IndexedStorageChunkStorageProvider.IndexedStorageCache.CacheEntryMetricData::new + ) + .append(new KeyedCodec<>("Key", Codec.LONG), (entry, o) -> entry.key = o, entry -> entry.key) + .add() + .append(new KeyedCodec<>("File", IndexedStorageFile.METRICS_REGISTRY), (entry, o) -> entry.value = o, entry -> entry.value) + .add() + .build(); + private long key; + private IndexedStorageFile value; + + public CacheEntryMetricData() { + } + + public CacheEntryMetricData(@Nonnull Entry entry) { + this.key = entry.getLongKey(); + this.value = entry.getValue(); + } + } + } + + public static class IndexedStorageCacheSetupSystem extends StoreSystem { + public IndexedStorageCacheSetupSystem() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return ChunkStore.INIT_GROUP; + } + + @Override + public void onSystemAddedToStore(@Nonnull Store store) { + World world = store.getExternalData().getWorld(); + store.getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()).path = world.getSavePath().resolve("chunks"); + } + + @Override + public void onSystemRemovedFromStore(@Nonnull Store store) { + } + } + + public static class IndexedStorageChunkLoader extends BufferChunkLoader implements MetricProvider { + public IndexedStorageChunkLoader(@Nonnull Store store) { + super(store); + } + + @Override + public void close() throws IOException { + this.getStore().getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()).close(); + } + + @Nonnull + @Override + public CompletableFuture loadBuffer(int x, int z) { + int regionX = x >> 5; + int regionZ = z >> 5; + int localX = x & 31; + int localZ = z & 31; + int index = ChunkUtil.indexColumn(localX, localZ); + IndexedStorageChunkStorageProvider.IndexedStorageCache indexedStorageCache = this.getStore() + .getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()); + return CompletableFuture.supplyAsync(SneakyThrow.sneakySupplier(() -> { + IndexedStorageFile chunks = indexedStorageCache.getOrTryOpen(regionX, regionZ); + return chunks == null ? null : chunks.readBlob(index); + })); + } + + @Nonnull + @Override + public LongSet getIndexes() throws IOException { + return this.getStore().getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()).getIndexes(); + } + + @Nullable + @Override + public MetricResults toMetricResults() { + return this.getStore().getExternalData().getSaver() instanceof IndexedStorageChunkStorageProvider.IndexedStorageChunkSaver + ? null + : this.getStore().getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()).toMetricResults(); + } + } + + public static class IndexedStorageChunkSaver extends BufferChunkSaver implements MetricProvider { + protected IndexedStorageChunkSaver(@Nonnull Store store) { + super(store); + } + + @Override + public void close() throws IOException { + IndexedStorageChunkStorageProvider.IndexedStorageCache indexedStorageCache = this.getStore() + .getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()); + indexedStorageCache.close(); + } + + @Nonnull + @Override + public CompletableFuture saveBuffer(int x, int z, @Nonnull ByteBuffer buffer) { + int regionX = x >> 5; + int regionZ = z >> 5; + int localX = x & 31; + int localZ = z & 31; + int index = ChunkUtil.indexColumn(localX, localZ); + IndexedStorageChunkStorageProvider.IndexedStorageCache indexedStorageCache = this.getStore() + .getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()); + return CompletableFuture.runAsync(SneakyThrow.sneakyRunnable(() -> { + IndexedStorageFile chunks = indexedStorageCache.getOrCreate(regionX, regionZ); + chunks.writeBlob(index, buffer); + })); + } + + @Nonnull + @Override + public CompletableFuture removeBuffer(int x, int z) { + int regionX = x >> 5; + int regionZ = z >> 5; + int localX = x & 31; + int localZ = z & 31; + int index = ChunkUtil.indexColumn(localX, localZ); + IndexedStorageChunkStorageProvider.IndexedStorageCache indexedStorageCache = this.getStore() + .getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()); + return CompletableFuture.runAsync(SneakyThrow.sneakyRunnable(() -> { + IndexedStorageFile chunks = indexedStorageCache.getOrTryOpen(regionX, regionZ); + if (chunks != null) { + chunks.removeBlob(index); + } + })); + } + + @Nonnull + @Override + public LongSet getIndexes() throws IOException { + return this.getStore().getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()).getIndexes(); + } + + @Override + public void flush() throws IOException { + this.getStore().getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()).flush(); + } + + @Override + public MetricResults toMetricResults() { + return this.getStore().getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()).toMetricResults(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/provider/MigrationChunkStorageProvider.java b/src/com/hypixel/hytale/server/core/universe/world/storage/provider/MigrationChunkStorageProvider.java new file mode 100644 index 0000000..919cea8 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/provider/MigrationChunkStorageProvider.java @@ -0,0 +1,142 @@ +package com.hypixel.hytale.server.core.universe.world.storage.provider; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.IChunkLoader; +import com.hypixel.hytale.server.core.universe.world.storage.IChunkSaver; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import javax.annotation.Nonnull; +import org.checkerframework.checker.nullness.compatqual.NonNullDecl; + +public class MigrationChunkStorageProvider implements IChunkStorageProvider { + public static final String ID = "Migration"; + @Nonnull + public static final BuilderCodec CODEC = BuilderCodec.builder( + MigrationChunkStorageProvider.class, MigrationChunkStorageProvider::new + ) + .documentation( + "A provider that combines multiple storage providers in a chain to assist with migrating worlds between storage formats.\n\nCan also be used to set storage to load chunks but block saving them if combined with the **Empty** storage provider" + ) + .append( + new KeyedCodec<>("Loaders", new ArrayCodec<>(IChunkStorageProvider.CODEC, IChunkStorageProvider[]::new)), + (migration, o) -> migration.from = o, + migration -> migration.from + ) + .documentation( + "A list of storage providers to use as chunk loaders.\n\nEach loader will be tried in order to load a chunk, returning the chunk if found otherwise trying the next loaded until found or none are left." + ) + .add() + .append(new KeyedCodec<>("Saver", IChunkStorageProvider.CODEC), (migration, o) -> migration.to = o, migration -> migration.to) + .documentation("The storage provider to use to save chunks.") + .add() + .build(); + private IChunkStorageProvider[] from; + private IChunkStorageProvider to; + + public MigrationChunkStorageProvider() { + } + + public MigrationChunkStorageProvider(@Nonnull IChunkStorageProvider[] from, @Nonnull IChunkStorageProvider to) { + this.from = from; + this.to = to; + } + + @Nonnull + @Override + public IChunkLoader getLoader(@NonNullDecl Store store) throws IOException { + IChunkLoader[] loaders = new IChunkLoader[this.from.length]; + + for (int i = 0; i < this.from.length; i++) { + loaders[i] = this.from[i].getLoader(store); + } + + return new MigrationChunkStorageProvider.MigrationChunkLoader(loaders); + } + + @Nonnull + @Override + public IChunkSaver getSaver(@NonNullDecl Store store) throws IOException { + return this.to.getSaver(store); + } + + @Nonnull + @Override + public String toString() { + return "MigrationChunkStorageProvider{from=" + Arrays.toString((Object[])this.from) + ", to=" + this.to + "}"; + } + + public static class MigrationChunkLoader implements IChunkLoader { + @Nonnull + private final IChunkLoader[] loaders; + + public MigrationChunkLoader(@Nonnull IChunkLoader... loaders) { + this.loaders = loaders; + } + + @Override + public void close() throws IOException { + IOException exception = null; + + for (IChunkLoader loader : this.loaders) { + try { + loader.close(); + } catch (Exception var7) { + if (exception == null) { + exception = new IOException("Failed to close one or more loaders!"); + } + + exception.addSuppressed(var7); + } + } + + if (exception != null) { + throw exception; + } + } + + @Nonnull + @Override + public CompletableFuture> loadHolder(int x, int z) { + CompletableFuture> future = this.loaders[0].loadHolder(x, z); + + for (int i = 1; i < this.loaders.length; i++) { + IChunkLoader loader = this.loaders[i]; + CompletableFuture> previous = future; + future = previous.>>handle((worldChunk, throwable) -> { + if (throwable != null) { + return loader.loadHolder(x, z).exceptionally(throwable1 -> { + throwable1.addSuppressed(throwable); + throw SneakyThrow.sneakyThrow(throwable1); + }); + } else { + return worldChunk == null ? loader.loadHolder(x, z) : previous; + } + }).thenCompose(Function.identity()); + } + + return future; + } + + @Nonnull + @Override + public LongSet getIndexes() throws IOException { + LongOpenHashSet indexes = new LongOpenHashSet(); + + for (IChunkLoader loader : this.loaders) { + indexes.addAll(loader.getIndexes()); + } + + return indexes; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/resources/DefaultResourceStorageProvider.java b/src/com/hypixel/hytale/server/core/universe/world/storage/resources/DefaultResourceStorageProvider.java new file mode 100644 index 0000000..78935c1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/resources/DefaultResourceStorageProvider.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.core.universe.world.storage.resources; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.IResourceStorage; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldProvider; +import javax.annotation.Nonnull; + +public class DefaultResourceStorageProvider implements IResourceStorageProvider { + public static final DefaultResourceStorageProvider INSTANCE = new DefaultResourceStorageProvider(); + public static final String ID = "Hytale"; + public static final BuilderCodec CODEC = BuilderCodec.builder(DefaultResourceStorageProvider.class, () -> INSTANCE).build(); + public static final DiskResourceStorageProvider DEFAULT = new DiskResourceStorageProvider(); + + public DefaultResourceStorageProvider() { + } + + @Nonnull + @Override + public IResourceStorage getResourceStorage(@Nonnull World world) { + return DEFAULT.getResourceStorage(world); + } + + @Nonnull + @Override + public String toString() { + return "DefaultResourceStorageProvider{}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/resources/DiskResourceStorageProvider.java b/src/com/hypixel/hytale/server/core/universe/world/storage/resources/DiskResourceStorageProvider.java new file mode 100644 index 0000000..0184ba0 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/resources/DiskResourceStorageProvider.java @@ -0,0 +1,175 @@ +package com.hypixel.hytale.server.core.universe.world.storage.resources; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.IResourceStorage; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldProvider; +import com.hypixel.hytale.server.core.util.BsonUtil; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import org.bson.BsonDocument; + +public class DiskResourceStorageProvider implements IResourceStorageProvider { + public static final String ID = "Disk"; + public static final BuilderCodec CODEC = BuilderCodec.builder( + DiskResourceStorageProvider.class, DiskResourceStorageProvider::new + ) + .append(new KeyedCodec<>("Path", Codec.STRING), (o, s) -> o.path = s, o -> o.path) + .add() + .build(); + @Nonnull + private String path = "resources"; + + public DiskResourceStorageProvider() { + } + + @Nonnull + public String getPath() { + return this.path; + } + + @Nonnull + @Override + public IResourceStorage getResourceStorage(@Nonnull World world) { + return new DiskResourceStorageProvider.DiskResourceStorage(world.getSavePath().resolve(this.path)); + } + + @Nonnull + @Override + public String toString() { + return "DiskResourceStorageProvider{path=" + this.path + "}"; + } + + @Deprecated(forRemoval = true) + public static void migrateFiles(@Nonnull World world) { + Path resourcesPath = world.getSavePath().resolve("resources"); + Path chunkStorePath = resourcesPath.resolve("chunkstore"); + if (Files.exists(chunkStorePath)) { + try { + FileUtil.moveDirectoryContents(chunkStorePath, resourcesPath, StandardCopyOption.REPLACE_EXISTING); + FileUtil.deleteDirectory(chunkStorePath); + } catch (IOException var6) { + throw new RuntimeException("Failed to migrate old chunkstore resources!", var6); + } + } + + Path entityStorePath = resourcesPath.resolve("entitystore"); + if (Files.exists(entityStorePath)) { + try { + FileUtil.moveDirectoryContents(entityStorePath, resourcesPath, StandardCopyOption.REPLACE_EXISTING); + FileUtil.deleteDirectory(entityStorePath); + } catch (IOException var5) { + throw new RuntimeException("Failed to migrate old entitystore resources!", var5); + } + } + } + + public static class DiskResourceStorage implements IResourceStorage { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final String FILE_EXTENSION = ".json"; + @Nonnull + private final Path path; + + public DiskResourceStorage(@Nonnull Path path) { + this.path = path; + if (!Options.getOptionSet().has(Options.BARE)) { + try { + Files.createDirectories(path); + } catch (IOException var3) { + throw new RuntimeException("Failed to create Resources directory", var3); + } + } + } + + @Nonnull + @Override + public , ECS_TYPE> CompletableFuture load( + @Nonnull Store store, @Nonnull ComponentRegistry.Data data, @Nonnull ResourceType resourceType + ) { + BuilderCodec codec = data.getResourceCodec(resourceType); + return codec == null + ? CompletableFuture.completedFuture(data.createResource(resourceType)) + : CompletableFuture.supplyAsync(SneakyThrow.sneakySupplier(() -> { + String id = data.getResourceId(resourceType); + Path file = this.path.resolve(id + ".json"); + + BasicFileAttributes attributes; + try { + attributes = Files.readAttributes(file, BasicFileAttributes.class); + } catch (IOException var9) { + LOGGER.at(Level.FINE).log("File '%s' was not found, using the default file", file); + return data.createResource(resourceType); + } + + if (attributes.size() == 0L) { + LOGGER.at(Level.WARNING).log("Error loading file %s, file was found to be entirely empty, using the default file", file); + return data.createResource(resourceType); + } else { + try { + T resource = RawJsonReader.readSync(file, codec, LOGGER); + return resource != null ? resource : data.createResource(resourceType); + } catch (IOException var8) { + LOGGER.at(Level.WARNING).withCause(var8).log("Failed to load resource from %s, using default", file); + return data.createResource(resourceType); + } + } + })); + } + + @Nonnull + @Override + public , ECS_TYPE> CompletableFuture save( + @Nonnull Store store, @Nonnull ComponentRegistry.Data data, @Nonnull ResourceType resourceType, T resource + ) { + BuilderCodec codec = data.getResourceCodec(resourceType); + if (codec == null) { + return CompletableFuture.completedFuture(null); + } else { + String id = data.getResourceId(resourceType); + Path file = this.path.resolve(id + ".json"); + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + BsonDocument document = codec.encode(resource, extraInfo).asDocument(); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER); + return BsonUtil.writeDocument(file, document); + } + } + + @Nonnull + @Override + public , ECS_TYPE> CompletableFuture remove( + @Nonnull Store store, @Nonnull ComponentRegistry.Data data, @Nonnull ResourceType resourceType + ) { + String id = data.getResourceId(resourceType); + if (id == null) { + return CompletableFuture.completedFuture(null); + } else { + Path file = this.path.resolve(id + ".json"); + + try { + Files.deleteIfExists(file); + return CompletableFuture.completedFuture(null); + } catch (IOException var7) { + return CompletableFuture.failedFuture(var7); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/resources/EmptyResourceStorageProvider.java b/src/com/hypixel/hytale/server/core/universe/world/storage/resources/EmptyResourceStorageProvider.java new file mode 100644 index 0000000..99b9254 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/resources/EmptyResourceStorageProvider.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.core.universe.world.storage.resources; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.EmptyResourceStorage; +import com.hypixel.hytale.component.IResourceStorage; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldProvider; +import javax.annotation.Nonnull; + +public class EmptyResourceStorageProvider implements IResourceStorageProvider { + public static final EmptyResourceStorageProvider INSTANCE = new EmptyResourceStorageProvider(); + public static final String ID = "Empty"; + public static final BuilderCodec CODEC = BuilderCodec.builder(EmptyResourceStorageProvider.class, () -> INSTANCE).build(); + + public EmptyResourceStorageProvider() { + } + + @Nonnull + @Override + public IResourceStorage getResourceStorage(@Nonnull World world) { + return EmptyResourceStorage.get(); + } + + @Nonnull + @Override + public String toString() { + return "EmptyResourceStorageProvider{}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/storage/resources/IResourceStorageProvider.java b/src/com/hypixel/hytale/server/core/universe/world/storage/resources/IResourceStorageProvider.java new file mode 100644 index 0000000..cd80f65 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/storage/resources/IResourceStorageProvider.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.server.core.universe.world.storage.resources; + +import com.hypixel.hytale.codec.lookup.BuilderCodecMapCodec; +import com.hypixel.hytale.component.IResourceStorage; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldProvider; +import javax.annotation.Nonnull; + +public interface IResourceStorageProvider { + @Nonnull + BuilderCodecMapCodec CODEC = new BuilderCodecMapCodec<>("Type", true); + + IResourceStorage getResourceStorage(@Nonnull World var1); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/system/WorldPregenerateSystem.java b/src/com/hypixel/hytale/server/core/universe/world/system/WorldPregenerateSystem.java new file mode 100644 index 0000000..584d944 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/system/WorldPregenerateSystem.java @@ -0,0 +1,72 @@ +package com.hypixel.hytale.server.core.universe.world.system; + +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemGroupDependency; +import com.hypixel.hytale.component.system.StoreSystem; +import com.hypixel.hytale.math.shape.Box2D; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class WorldPregenerateSystem extends StoreSystem { + private static final Set> DEPENDENCIES = Set.of(new SystemGroupDependency<>(Order.AFTER, ChunkStore.INIT_GROUP)); + + public WorldPregenerateSystem() { + } + + @Nonnull + @Override + public Set> getDependencies() { + return DEPENDENCIES; + } + + @Override + public void onSystemAddedToStore(@Nonnull Store store) { + World world = store.getExternalData().getWorld(); + Box2D region = world.getWorldConfig().getChunkConfig().getPregenerateRegion(); + if (region != null) { + world.getLogger().at(Level.INFO).log("Ensuring region is generated: %s", region); + long start = System.nanoTime(); + int lowX = MathUtil.floor(region.min.x); + int lowZ = MathUtil.floor(region.min.y); + int highX = MathUtil.floor(region.max.x); + int highZ = MathUtil.floor(region.max.y); + List>> futures = new ObjectArrayList<>(); + + for (int x = lowX; x <= highX; x += 32) { + for (int z = lowZ; z <= highZ; z += 32) { + futures.add(world.getChunkStore().getChunkReferenceAsync(ChunkUtil.indexChunkFromBlock(x, z))); + } + } + + int allFutures = futures.size(); + AtomicInteger done = new AtomicInteger(); + futures.forEach(f -> f.whenComplete((worldChunk, throwable) -> { + if (throwable != null) { + world.getLogger().at(Level.SEVERE).withCause(throwable).log("Failed to load/generate chunk:"); + } + + if (done.incrementAndGet() == allFutures) { + long end = System.nanoTime(); + world.getLogger().at(Level.INFO).log("Finished loading %d chunks. Finished in %s", allFutures, FormatUtil.nanosToString(end - start)); + } + })); + } + } + + @Override + public void onSystemRemovedFromStore(@Nonnull Store store) { + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldgen/GeneratedBlockChunk.java b/src/com/hypixel/hytale/server/core/universe/world/worldgen/GeneratedBlockChunk.java new file mode 100644 index 0000000..3f61093 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldgen/GeneratedBlockChunk.java @@ -0,0 +1,184 @@ +package com.hypixel.hytale.server.core.universe.world.worldgen; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.protocol.Opacity; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.environment.EnvironmentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.palette.IntBytePalette; +import com.hypixel.hytale.server.core.universe.world.chunk.palette.ShortBytePalette; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class GeneratedBlockChunk { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + protected long index; + protected int x; + protected int z; + protected final IntBytePalette tint; + protected final EnvironmentChunk environments; + protected final GeneratedChunkSection[] chunkSections; + + public GeneratedBlockChunk() { + this(0L, 0, 0); + } + + public GeneratedBlockChunk(long index, int x, int z) { + this(index, x, z, new IntBytePalette(), new EnvironmentChunk(), new GeneratedChunkSection[10]); + } + + public GeneratedBlockChunk(long index, int x, int z, IntBytePalette tint, EnvironmentChunk environments, GeneratedChunkSection[] chunkSections) { + this.index = index; + this.x = x; + this.z = z; + this.tint = tint; + this.environments = environments; + this.chunkSections = chunkSections; + } + + public long getIndex() { + return this.index; + } + + public int getX() { + return this.x; + } + + public int getZ() { + return this.z; + } + + public void setCoordinates(long index, int x, int z) { + this.index = index; + this.x = x; + this.z = z; + } + + public int getHeight(int x, int z) { + int y = 320; + + while (--y > 0) { + GeneratedChunkSection section = this.getSection(y); + if (section == null) { + y = ChunkUtil.indexSection(y) * 32; + if (y == 0) { + break; + } + } else { + int blockId = section.getBlock(x, y, z); + BlockType type = BlockType.getAssetMap().getAsset(blockId); + if (blockId != 0 && type != null && type.getOpacity() != Opacity.Transparent) { + break; + } + } + } + + return y; + } + + @Nonnull + public ShortBytePalette generateHeight() { + ShortBytePalette height = new ShortBytePalette(); + + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + height.set(x, z, (short)this.getHeight(x, z)); + } + } + + return height; + } + + @Nullable + public GeneratedChunkSection getSection(int y) { + int index = ChunkUtil.indexSection(y); + return index >= 0 && index < this.chunkSections.length ? this.chunkSections[index] : null; + } + + public int getTint(int x, int z) { + return this.tint.get(x, z); + } + + public void setTint(int x, int z, int tint) { + this.tint.set(x, z, tint); + } + + public void setEnvironment(int x, int y, int z, int environment) { + this.environments.set(x, y, z, environment); + } + + public void setEnvironmentColumn(int x, int z, int environment) { + this.environments.setColumn(x, z, environment); + } + + public int getEnvironment(int x, int y, int z) { + return this.environments.get(x, y, z); + } + + public int getRotationIndex(int x, int y, int z) { + if (y >= 0 && y < 320) { + GeneratedChunkSection section = this.getSection(y); + return section == null ? 0 : section.getRotationIndex(x, y, z); + } else { + return 0; + } + } + + public int getBlock(int x, int y, int z) { + if (y >= 0 && y < 320) { + GeneratedChunkSection section = this.getSection(y); + return section == null ? 0 : section.getBlock(x, y, z); + } else { + return 0; + } + } + + public void setBlock(int x, int y, int z, int blockId, int rotation, int filler) { + if (y >= 0 && y < 320) { + GeneratedChunkSection section = this.getSection(y); + int sectionIndex = ChunkUtil.indexSection(y); + if (section == null) { + if (blockId == 0) { + return; + } + + section = this.initialize(sectionIndex); + } + + section.setBlock(x, y, z, blockId, rotation, filler); + } else { + LOGGER.at(Level.INFO).withCause(new Exception()).log("Failed to set block %d, %d, %d to %d because it is outside the world bounds", x, y, z, blockId); + } + } + + @Nonnull + private GeneratedChunkSection initialize(int section) { + return this.chunkSections[section] = new GeneratedChunkSection(); + } + + public void removeSection(int y) { + int index = ChunkUtil.indexSection(y); + if (index >= 0 && index < this.chunkSections.length) { + this.chunkSections[index] = null; + } + } + + @Nonnull + public BlockChunk toBlockChunk(Holder[] sectionHolders) { + for (int y = 0; y < this.chunkSections.length; y++) { + GeneratedChunkSection chunkSection = this.chunkSections[y]; + if (chunkSection != null) { + sectionHolders[y].putComponent(BlockSection.getComponentType(), chunkSection.toChunkSection()); + } + } + + ShortBytePalette height = this.generateHeight(); + this.environments.trim(); + return new BlockChunk(this.x, this.z, height, this.tint, this.environments); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldgen/GeneratedBlockStateChunk.java b/src/com/hypixel/hytale/server/core/universe/world/worldgen/GeneratedBlockStateChunk.java new file mode 100644 index 0000000..24e82ce --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldgen/GeneratedBlockStateChunk.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.server.core.universe.world.worldgen; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.meta.BlockState; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class GeneratedBlockStateChunk { + private final Int2ObjectMap> mapping = new Int2ObjectOpenHashMap<>(); + + public GeneratedBlockStateChunk() { + } + + public Holder getState(int x, int y, int z) { + return this.mapping.get(ChunkUtil.indexBlockInColumn(x, y, z)); + } + + public void setState(int x, int y, int z, @Nullable Holder state) { + int index = ChunkUtil.indexBlockInColumn(x, y, z); + if (state == null) { + this.mapping.remove(index); + } else { + BlockState blockState = BlockState.getBlockState(state); + if (blockState != null) { + blockState.setPosition(new Vector3i(x, y, z)); + } + + this.mapping.put(index, state); + } + } + + @Nonnull + public BlockComponentChunk toBlockComponentChunk() { + return new BlockComponentChunk(this.mapping, new Int2ObjectOpenHashMap<>()); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldgen/GeneratedChunk.java b/src/com/hypixel/hytale/server/core/universe/world/worldgen/GeneratedChunk.java new file mode 100644 index 0000000..66df1b1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldgen/GeneratedChunk.java @@ -0,0 +1,79 @@ +package com.hypixel.hytale.server.core.universe.world.worldgen; + +import com.hypixel.hytale.common.collection.Flags; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkFlag; +import com.hypixel.hytale.server.core.universe.world.chunk.EntityChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import javax.annotation.Nonnull; + +public class GeneratedChunk { + private final GeneratedBlockChunk generatedBlockChunk; + private final GeneratedBlockStateChunk generatedBlockStateChunk; + private final GeneratedEntityChunk generatedEntityChunk; + private final Holder[] sections; + + public GeneratedChunk() { + this(new GeneratedBlockChunk(), new GeneratedBlockStateChunk(), new GeneratedEntityChunk(), makeSections()); + } + + public GeneratedChunk( + GeneratedBlockChunk generatedBlockChunk, + GeneratedBlockStateChunk generatedBlockStateChunk, + GeneratedEntityChunk generatedEntityChunk, + Holder[] sections + ) { + this.generatedBlockChunk = generatedBlockChunk; + this.generatedBlockStateChunk = generatedBlockStateChunk; + this.generatedEntityChunk = generatedEntityChunk; + this.sections = sections; + } + + public GeneratedBlockChunk getBlockChunk() { + return this.generatedBlockChunk; + } + + public GeneratedBlockStateChunk getBlockStateChunk() { + return this.generatedBlockStateChunk; + } + + public GeneratedEntityChunk getEntityChunk() { + return this.generatedEntityChunk; + } + + public Holder[] getSections() { + return this.sections; + } + + @Nonnull + public Holder toWorldChunk(World world) { + BlockChunk blockChunk = this.generatedBlockChunk.toBlockChunk(this.sections); + BlockComponentChunk blockComponentChunk = this.generatedBlockStateChunk.toBlockComponentChunk(); + EntityChunk entityChunk = this.generatedEntityChunk.toEntityChunk(); + WorldChunk worldChunk = new WorldChunk(world, new Flags<>(ChunkFlag.NEWLY_GENERATED), blockChunk, blockComponentChunk, entityChunk); + Holder holder = worldChunk.toHolder(); + holder.putComponent(ChunkColumn.getComponentType(), new ChunkColumn(this.sections)); + return holder; + } + + @Nonnull + public Holder toHolder(World world) { + return this.toWorldChunk(world); + } + + @Nonnull + public static Holder[] makeSections() { + Holder[] holders = new Holder[10]; + + for (int i = 0; i < holders.length; i++) { + holders[i] = ChunkStore.REGISTRY.newHolder(); + } + + return holders; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldgen/GeneratedChunkSection.java b/src/com/hypixel/hytale/server/core/universe/world/worldgen/GeneratedChunkSection.java new file mode 100644 index 0000000..4662c10 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldgen/GeneratedChunkSection.java @@ -0,0 +1,112 @@ +package com.hypixel.hytale.server.core.universe.world.worldgen; + +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.palette.EmptySectionPalette; +import com.hypixel.hytale.server.core.universe.world.chunk.section.palette.ISectionPalette; +import io.netty.buffer.ByteBuf; +import it.unimi.dsi.fastutil.ints.IntArrays; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class GeneratedChunkSection { + @Nonnull + private final int[] data = new int[32768]; + @Nonnull + private final int[] temp = new int[32768]; + private ISectionPalette fillers = EmptySectionPalette.INSTANCE; + private ISectionPalette rotations = EmptySectionPalette.INSTANCE; + + public GeneratedChunkSection() { + } + + public int getRotationIndex(int x, int y, int z) { + return this.getRotationIndex(ChunkUtil.indexBlock(x, y, z)); + } + + private int getRotationIndex(int index) { + return this.rotations.get(index); + } + + public int getBlock(int x, int y, int z) { + return this.getBlock(ChunkUtil.indexBlock(x, y, z)); + } + + public int getFiller(int x, int y, int z) { + return this.fillers.get(ChunkUtil.indexBlock(x, y, z)); + } + + private int getBlock(int index) { + return this.data[index]; + } + + public void setBlock(int x, int y, int z, int block, int rotation, int filler) { + this.setBlock(ChunkUtil.indexBlock(x, y, z), block, rotation, filler); + } + + public void setBlock(int index, int block, int rotation, int filler) { + this.data[index] = block; + ISectionPalette.SetResult result = this.fillers.set(index, filler); + if (result == ISectionPalette.SetResult.REQUIRES_PROMOTE) { + this.fillers = this.fillers.promote(); + this.fillers.set(index, filler); + } else if (result == ISectionPalette.SetResult.ADDED_OR_REMOVED && this.fillers.shouldDemote()) { + this.fillers = this.fillers.demote(); + } + + result = this.rotations.set(index, rotation); + if (result == ISectionPalette.SetResult.REQUIRES_PROMOTE) { + this.rotations = this.rotations.promote(); + this.rotations.set(index, rotation); + } else if (result == ISectionPalette.SetResult.ADDED_OR_REMOVED && this.rotations.shouldDemote()) { + this.rotations = this.rotations.demote(); + } + } + + public int[] getData() { + return this.data; + } + + public void reset() { + Arrays.fill(this.data, 0); + } + + public boolean isSolidAir() { + for (int i = 0; i < this.data.length; i++) { + if (this.data[i] != 0) { + return false; + } + } + + return true; + } + + @Nonnull + public BlockSection toChunkSection() { + System.arraycopy(this.data, 0, this.temp, 0, 32768); + IntArrays.unstableSort(this.temp); + int count = 1; + + for (int i = 1; i < 32768; i++) { + if (this.temp[i] != this.temp[i - 1]) { + this.temp[count++] = this.temp[i]; + } + } + + return new BlockSection(ISectionPalette.from(this.data, this.temp, count), this.fillers, this.rotations); + } + + public void serialize(@Nonnull ByteBuf buf) { + for (int i = 0; i < 32768; i++) { + buf.writeInt(this.data[i]); + } + } + + public void deserialize(@Nonnull ByteBuf buf, int version) { + int[] blocks = new int[32768]; + + for (int i = 0; i < blocks.length; i++) { + blocks[i] = buf.readInt(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldgen/GeneratedEntityChunk.java b/src/com/hypixel/hytale/server/core/universe/world/worldgen/GeneratedEntityChunk.java new file mode 100644 index 0000000..4bf853b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldgen/GeneratedEntityChunk.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.server.core.universe.world.worldgen; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.modules.entity.component.FromWorldGen; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import com.hypixel.hytale.server.core.universe.world.chunk.EntityChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class GeneratedEntityChunk { + private final List entities; + + public GeneratedEntityChunk() { + this(new ObjectArrayList<>()); + } + + protected GeneratedEntityChunk(List entities) { + this.entities = entities; + } + + public List getEntities() { + return this.entities; + } + + public void forEachEntity(Consumer consumer) { + this.entities.forEach(consumer); + } + + public void addEntities(Vector3i offset, PrefabRotation rotation, @Nullable Holder[] entityHolders, int objectId) { + if (entityHolders != null && entityHolders.length > 0) { + this.entities.add(new GeneratedEntityChunk.EntityWrapperEntry(offset, rotation, entityHolders, objectId)); + } + } + + @Nonnull + public EntityChunk toEntityChunk() { + EntityChunk entityChunk = new EntityChunk(); + + for (GeneratedEntityChunk.EntityWrapperEntry entry : this.entities) { + FromWorldGen fromWorldGen = new FromWorldGen(entry.worldgenId()); + + for (Holder entityHolder : entry.entityHolders()) { + TransformComponent transformComponent = entityHolder.getComponent(TransformComponent.getComponentType()); + + assert transformComponent != null; + + entry.rotation().rotate(transformComponent.getPosition().subtract(0.5, 0.0, 0.5)); + transformComponent.getPosition().add(0.5, 0.0, 0.5); + HeadRotation headRotationComponent = entityHolder.getComponent(HeadRotation.getComponentType()); + if (headRotationComponent != null) { + headRotationComponent.getRotation().addYaw(-entry.rotation().getYaw()); + } + + transformComponent.getRotation().addYaw(-entry.rotation().getYaw()); + transformComponent.getPosition().add(entry.offset()); + entityHolder.putComponent(FromWorldGen.getComponentType(), fromWorldGen); + entityChunk.storeEntityHolder(entityHolder); + } + } + + return entityChunk; + } + + public record EntityWrapperEntry(Vector3i offset, PrefabRotation rotation, Holder[] entityHolders, int worldgenId) { + @Nonnull + @Override + public String toString() { + return "EntityWrapperEntry{offset=" + + this.offset + + ", rotation=" + + this.rotation + + ", entityHolders=" + + Arrays.toString((Object[])this.entityHolders) + + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldgen/IBenchmarkableWorldGen.java b/src/com/hypixel/hytale/server/core/universe/world/worldgen/IBenchmarkableWorldGen.java new file mode 100644 index 0000000..1ad5b94 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldgen/IBenchmarkableWorldGen.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.server.core.universe.world.worldgen; + +public interface IBenchmarkableWorldGen extends IWorldGen { + IWorldGenBenchmark getBenchmark(); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldgen/IWorldGen.java b/src/com/hypixel/hytale/server/core/universe/world/worldgen/IWorldGen.java new file mode 100644 index 0000000..b608d6a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldgen/IWorldGen.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.universe.world.worldgen; + +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.server.core.universe.world.spawn.FitToHeightMapSpawnProvider; +import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider; +import com.hypixel.hytale.server.core.universe.world.spawn.IndividualSpawnProvider; +import java.util.concurrent.CompletableFuture; +import java.util.function.LongPredicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface IWorldGen { + @Nullable + WorldGenTimingsCollector getTimings(); + + CompletableFuture generate(int var1, long var2, int var4, int var5, LongPredicate var6); + + @Deprecated + Transform[] getSpawnPoints(int var1); + + @Nonnull + default ISpawnProvider getDefaultSpawnProvider(int seed) { + return new FitToHeightMapSpawnProvider(new IndividualSpawnProvider(this.getSpawnPoints(seed))); + } + + default void shutdown() { + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldgen/IWorldGenBenchmark.java b/src/com/hypixel/hytale/server/core/universe/world/worldgen/IWorldGenBenchmark.java new file mode 100644 index 0000000..826b4f7 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldgen/IWorldGenBenchmark.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.universe.world.worldgen; + +import java.util.concurrent.CompletableFuture; + +public interface IWorldGenBenchmark { + void start(); + + void stop(); + + CompletableFuture buildReport(); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldgen/ValidatableWorldGen.java b/src/com/hypixel/hytale/server/core/universe/world/worldgen/ValidatableWorldGen.java new file mode 100644 index 0000000..3cd5f31 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldgen/ValidatableWorldGen.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.server.core.universe.world.worldgen; + +public interface ValidatableWorldGen { + boolean validate(); +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldgen/WorldGenLoadException.java b/src/com/hypixel/hytale/server/core/universe/world/worldgen/WorldGenLoadException.java new file mode 100644 index 0000000..21540c9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldgen/WorldGenLoadException.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.core.universe.world.worldgen; + +import com.hypixel.hytale.common.util.ExceptionUtil; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class WorldGenLoadException extends Exception { + public WorldGenLoadException(@Nonnull String message) { + super(Objects.requireNonNull(message)); + } + + public WorldGenLoadException(@Nonnull String message, Throwable cause) { + super(Objects.requireNonNull(message), cause); + } + + @Nonnull + public String getTraceMessage() { + return this.getTraceMessage(", "); + } + + @Nonnull + public String getTraceMessage(@Nonnull String joiner) { + return ExceptionUtil.combineMessages(this, joiner); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldgen/WorldGenTimingsCollector.java b/src/com/hypixel/hytale/server/core/universe/world/worldgen/WorldGenTimingsCollector.java new file mode 100644 index 0000000..5b6ef9e --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldgen/WorldGenTimingsCollector.java @@ -0,0 +1,132 @@ +package com.hypixel.hytale.server.core.universe.world.worldgen; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.metrics.MetricsRegistry; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicLongArray; +import javax.annotation.Nonnull; + +public class WorldGenTimingsCollector { + public static final MetricsRegistry METRICS_REGISTRY = new MetricsRegistry() + .register("Chunks", worldGenTimingsCollector -> worldGenTimingsCollector.chunkCounter.get(), Codec.LONG) + .register("ChunkTime", worldGenTimingsCollector -> worldGenTimingsCollector.getChunkTime(), Codec.DOUBLE) + .register("ZoneBiomeResultTime", worldGenTimingsCollector -> worldGenTimingsCollector.zoneBiomeResult(), Codec.DOUBLE) + .register("PrepareTime", worldGenTimingsCollector -> worldGenTimingsCollector.prepare(), Codec.DOUBLE) + .register("BlocksTime", worldGenTimingsCollector -> worldGenTimingsCollector.blocksGeneration(), Codec.DOUBLE) + .register("CaveTime", worldGenTimingsCollector -> worldGenTimingsCollector.caveGeneration(), Codec.DOUBLE) + .register("PrefabTime", worldGenTimingsCollector -> worldGenTimingsCollector.prefabGeneration(), Codec.DOUBLE) + .register("QueueLength", WorldGenTimingsCollector::getQueueLength, Codec.INTEGER) + .register("GeneratingCount", WorldGenTimingsCollector::getGeneratingCount, Codec.INTEGER); + private static final double NANOS_TO_SECONDS = 1.0E-9; + private static final int WARMUP = 100; + private static final double WARMUP_VALUE = Double.NEGATIVE_INFINITY; + private static final int CHUNKS = 0; + private static final int ZONE_BIOME_RESULT = 1; + private static final int PREPARE = 2; + private static final int BLOCKS = 3; + private static final int CAVES = 4; + private static final int PREFABS = 5; + private final AtomicLong chunkCounter = new AtomicLong(); + private final AtomicLongArray times = new AtomicLongArray(6); + private final AtomicLongArray counts = new AtomicLongArray(6); + private final ThreadPoolExecutor threadPoolExecutor; + + public WorldGenTimingsCollector(ThreadPoolExecutor threadPoolExecutor) { + this.threadPoolExecutor = threadPoolExecutor; + } + + public double reportChunk(long nanos) { + return this.chunkCounter.incrementAndGet() > 100L ? this.addAndGet(0, nanos) : Double.NEGATIVE_INFINITY; + } + + public double reportZoneBiomeResult(long nanos) { + return this.chunkCounter.get() > 100L ? this.addAndGet(1, nanos) : Double.NEGATIVE_INFINITY; + } + + public double reportPrepare(long nanos) { + return this.chunkCounter.get() > 100L ? this.addAndGet(2, nanos) : Double.NEGATIVE_INFINITY; + } + + public double reportBlocksGeneration(long nanos) { + return this.chunkCounter.get() > 100L ? this.addAndGet(3, nanos) : Double.NEGATIVE_INFINITY; + } + + public double reportCaveGeneration(long nanos) { + return this.chunkCounter.get() > 100L ? this.addAndGet(4, nanos) : Double.NEGATIVE_INFINITY; + } + + public double reportPrefabGeneration(long nanos) { + return this.chunkCounter.get() > 100L ? this.addAndGet(5, nanos) : Double.NEGATIVE_INFINITY; + } + + public double getWarmupValue() { + return Double.NEGATIVE_INFINITY; + } + + public double zoneBiomeResult() { + return this.get(1); + } + + public double prepare() { + return this.get(2); + } + + public double blocksGeneration() { + return this.get(3); + } + + public double caveGeneration() { + return this.get(4); + } + + public double prefabGeneration() { + return this.get(5); + } + + public long getChunkCounter() { + return this.chunkCounter.get(); + } + + public double getChunkTime() { + return this.get(0); + } + + public int getQueueLength() { + return this.threadPoolExecutor.getQueue().size(); + } + + public int getGeneratingCount() { + return this.threadPoolExecutor.getActiveCount(); + } + + @Nonnull + @Override + public String toString() { + return String.format( + "cnt: %s, zbr: %s, pp: %s, b: %s, c: %s, pf: %s", + this.getChunkCounter(), + this.zoneBiomeResult(), + this.prepare(), + this.blocksGeneration(), + this.caveGeneration(), + this.prefabGeneration() + ); + } + + protected double get(int index) { + long sum = this.times.get(index); + long count = this.counts.get(index); + return getAvgSeconds(sum, count); + } + + protected double addAndGet(int index, long nanos) { + long sum = this.times.addAndGet(index, nanos); + long count = this.counts.incrementAndGet(index); + return getAvgSeconds(sum, count); + } + + protected static double getAvgSeconds(long nanos, long count) { + return count == 0L ? 0.0 : nanos * 1.0E-9 / count; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldgen/provider/DummyWorldGenProvider.java b/src/com/hypixel/hytale/server/core/universe/world/worldgen/provider/DummyWorldGenProvider.java new file mode 100644 index 0000000..a4c0e85 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldgen/provider/DummyWorldGenProvider.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.server.core.universe.world.worldgen.provider; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedBlockChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedBlockStateChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedEntityChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen; +import com.hypixel.hytale.server.core.universe.world.worldgen.WorldGenTimingsCollector; +import java.util.concurrent.CompletableFuture; +import java.util.function.LongPredicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DummyWorldGenProvider implements IWorldGenProvider { + public static final String ID = "Dummy"; + public static final BuilderCodec CODEC = BuilderCodec.builder(DummyWorldGenProvider.class, DummyWorldGenProvider::new) + .documentation("A dummy world generation provider that places a single layer of unknown blocks in each chunk.") + .build(); + + public DummyWorldGenProvider() { + } + + @Nonnull + @Override + public IWorldGen getGenerator() { + return new DummyWorldGenProvider.DummyWorldGen(); + } + + @Nonnull + @Override + public String toString() { + return "DummyWorldGenProvider{}"; + } + + private static class DummyWorldGen implements IWorldGen { + public DummyWorldGen() { + } + + @Nullable + @Override + public WorldGenTimingsCollector getTimings() { + return null; + } + + @Nonnull + @Override + public Transform[] getSpawnPoints(int seed) { + return new Transform[]{new Transform(0.0, 1.0, 0.0)}; + } + + @Nonnull + @Override + public CompletableFuture generate(int seed, long index, int cx, int cz, LongPredicate stillNeeded) { + GeneratedBlockChunk chunk = new GeneratedBlockChunk(index, cx, cz); + + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + chunk.setBlock(x, 0, z, 1, 0, 0); + } + } + + return CompletableFuture.completedFuture( + new GeneratedChunk(chunk, new GeneratedBlockStateChunk(), new GeneratedEntityChunk(), GeneratedChunk.makeSections()) + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldgen/provider/FlatWorldGenProvider.java b/src/com/hypixel/hytale/server/core/universe/world/worldgen/provider/FlatWorldGenProvider.java new file mode 100644 index 0000000..9d6e12b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldgen/provider/FlatWorldGenProvider.java @@ -0,0 +1,196 @@ +package com.hypixel.hytale.server.core.universe.world.worldgen.provider; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.codec.validation.validator.RangeRefValidator; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.core.asset.util.ColorParseUtil; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedBlockChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedBlockStateChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedEntityChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen; +import com.hypixel.hytale.server.core.universe.world.worldgen.WorldGenLoadException; +import com.hypixel.hytale.server.core.universe.world.worldgen.WorldGenTimingsCollector; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.function.LongPredicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FlatWorldGenProvider implements IWorldGenProvider { + public static final String ID = "Flat"; + public static final BuilderCodec CODEC = BuilderCodec.builder(FlatWorldGenProvider.class, FlatWorldGenProvider::new) + .documentation("A world generation provider that generates a flat world with defined layers.") + .append(new KeyedCodec<>("Tint", ProtocolCodecs.COLOR), (config, o) -> config.tint = o, config -> config.tint) + .documentation("The tint to set for all chunks that are generated.") + .add() + .append( + new KeyedCodec<>("Layers", new ArrayCodec<>(FlatWorldGenProvider.Layer.CODEC, FlatWorldGenProvider.Layer[]::new)), + (config, o) -> config.layers = o, + config -> config.layers + ) + .documentation("The list of layers to add to the world.") + .addValidator(Validators.nonNull()) + .add() + .build(); + public static final Color DEFAULT_TINT = new Color((byte)91, (byte)-98, (byte)40); + private Color tint = DEFAULT_TINT; + private FlatWorldGenProvider.Layer[] layers; + + public FlatWorldGenProvider() { + this.layers = new FlatWorldGenProvider.Layer[]{new FlatWorldGenProvider.Layer(0, 1, Environment.UNKNOWN.getId(), "Soil_Grass")}; + } + + public FlatWorldGenProvider(Color tint, FlatWorldGenProvider.Layer[] layers) { + this.tint = tint; + this.layers = layers; + } + + @Nonnull + @Override + public IWorldGen getGenerator() throws WorldGenLoadException { + int tintId = ColorParseUtil.colorToARGBInt(this.tint); + + for (FlatWorldGenProvider.Layer layer : this.layers) { + if (layer.from >= layer.to) { + throw new WorldGenLoadException("Failed to load 'Flat' WorldGen config, 'To' must be greater than 'From': " + layer); + } + + layer.from = Math.max(layer.from, 0); + layer.to = Math.min(layer.to, 320); + if (layer.environment != null) { + int index = Environment.getAssetMap().getIndex(layer.environment); + if (index == Integer.MIN_VALUE) { + throw new WorldGenLoadException("Unknown key! " + layer.environment); + } + + layer.environmentId = index; + } else { + layer.environmentId = 0; + } + + if (layer.blockType != null) { + int index = BlockType.getAssetMap().getIndex(layer.blockType); + if (index == Integer.MIN_VALUE) { + throw new WorldGenLoadException("Unknown key! " + layer.blockType); + } + + layer.blockId = index; + } else { + layer.blockId = 0; + } + } + + return new FlatWorldGenProvider.FlatWorldGen(this.layers, tintId); + } + + @Nonnull + @Override + public String toString() { + return "FlatWorldGenProvider{tint=" + this.tint + ", layers=" + Arrays.toString((Object[])this.layers) + "}"; + } + + private static class FlatWorldGen implements IWorldGen { + private final FlatWorldGenProvider.Layer[] layers; + private final int tintId; + + public FlatWorldGen(FlatWorldGenProvider.Layer[] layers, int tintId) { + this.layers = layers; + this.tintId = tintId; + } + + @Nullable + @Override + public WorldGenTimingsCollector getTimings() { + return null; + } + + @Nonnull + @Override + public Transform[] getSpawnPoints(int seed) { + return new Transform[]{new Transform(0.0, 81.0, 0.0)}; + } + + @Nonnull + @Override + public CompletableFuture generate(int seed, long index, int cx, int cz, LongPredicate stillNeeded) { + GeneratedBlockChunk generatedBlockChunk = new GeneratedBlockChunk(index, cx, cz); + + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + generatedBlockChunk.setTint(x, z, this.tintId); + } + } + + for (FlatWorldGenProvider.Layer layer : this.layers) { + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + for (int y = layer.from; y < layer.to; y++) { + generatedBlockChunk.setBlock(x, y, z, layer.blockId, 0, 0); + generatedBlockChunk.setEnvironment(x, y, z, layer.environmentId); + } + + generatedBlockChunk.setTint(x, z, this.tintId); + } + } + } + + return CompletableFuture.completedFuture( + new GeneratedChunk(generatedBlockChunk, new GeneratedBlockStateChunk(), new GeneratedEntityChunk(), GeneratedChunk.makeSections()) + ); + } + } + + public static class Layer { + public static final BuilderCodec CODEC = BuilderCodec.builder( + FlatWorldGenProvider.Layer.class, FlatWorldGenProvider.Layer::new + ) + .documentation("A layer of blocks for a given range.") + .append(new KeyedCodec<>("From", Codec.INTEGER), (layer, i) -> layer.from = i, layer -> layer.from) + .documentation("The Y coordinate (inclusive) to start placing blocks at.") + .add() + .append(new KeyedCodec<>("To", Codec.INTEGER), (layer, i) -> layer.to = i, layer -> layer.to) + .documentation("The Y coordinate (exclusive) to stop placing blocks at.") + .addValidator(new RangeRefValidator<>("1/From", null, false)) + .add() + .append(new KeyedCodec<>("BlockType", Codec.STRING), (layer, s) -> layer.blockType = s, layer -> layer.blockType) + .documentation("The type of block that will be used for all blocks placed at this layer.") + .addValidator(BlockType.VALIDATOR_CACHE.getValidator()) + .add() + .append(new KeyedCodec<>("Environment", Codec.STRING), (layer, s) -> layer.environment = s, layer -> layer.environment) + .documentation("The environment to set for every block placed.") + .addValidator(Environment.VALIDATOR_CACHE.getValidator()) + .add() + .build(); + public int from = Integer.MIN_VALUE; + public int to = Integer.MAX_VALUE; + public String environment; + public String blockType; + public int environmentId; + public int blockId; + + public Layer() { + } + + public Layer(int from, int to, String environment, String blockType) { + this.from = from; + this.to = to; + this.environment = environment; + this.blockType = blockType; + } + + @Nonnull + @Override + public String toString() { + return "Layer{from=" + this.from + ", to=" + this.to + ", environment='" + this.environment + "', blockType=" + this.blockType + "}"; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldgen/provider/IWorldGenProvider.java b/src/com/hypixel/hytale/server/core/universe/world/worldgen/provider/IWorldGenProvider.java new file mode 100644 index 0000000..dcdfb53 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldgen/provider/IWorldGenProvider.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.core.universe.world.worldgen.provider; + +import com.hypixel.hytale.codec.lookup.BuilderCodecMapCodec; +import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen; +import com.hypixel.hytale.server.core.universe.world.worldgen.WorldGenLoadException; + +public interface IWorldGenProvider { + BuilderCodecMapCodec CODEC = new BuilderCodecMapCodec<>("Type", true); + + IWorldGen getGenerator() throws WorldGenLoadException; +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldgen/provider/VoidWorldGenProvider.java b/src/com/hypixel/hytale/server/core/universe/world/worldgen/provider/VoidWorldGenProvider.java new file mode 100644 index 0000000..2ae295d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldgen/provider/VoidWorldGenProvider.java @@ -0,0 +1,123 @@ +package com.hypixel.hytale.server.core.universe.world.worldgen.provider; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.core.asset.util.ColorParseUtil; +import com.hypixel.hytale.server.core.codec.ProtocolCodecs; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedBlockChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedBlockStateChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedEntityChunk; +import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen; +import com.hypixel.hytale.server.core.universe.world.worldgen.WorldGenLoadException; +import com.hypixel.hytale.server.core.universe.world.worldgen.WorldGenTimingsCollector; +import java.util.concurrent.CompletableFuture; +import java.util.function.LongPredicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class VoidWorldGenProvider implements IWorldGenProvider { + public static final String ID = "Void"; + public static final BuilderCodec CODEC = BuilderCodec.builder(VoidWorldGenProvider.class, VoidWorldGenProvider::new) + .documentation("A world generation provider that does not generate any blocks.") + .append(new KeyedCodec<>("Tint", ProtocolCodecs.COLOR), (config, o) -> config.tint = o, config -> config.tint) + .documentation("The tint to set for all chunks that are generated.") + .add() + .append(new KeyedCodec<>("Environment", Codec.STRING), (config, s) -> config.environment = s, config -> config.environment) + .documentation("The environment to set for every column in generated chunks.") + .addValidator(Environment.VALIDATOR_CACHE.getValidator()) + .add() + .build(); + private Color tint; + private String environment; + + public VoidWorldGenProvider() { + } + + public VoidWorldGenProvider(Color tint, String environment) { + this.tint = tint; + this.environment = environment; + } + + @Nonnull + @Override + public IWorldGen getGenerator() throws WorldGenLoadException { + int tintId = this.tint == null ? 0 : ColorParseUtil.colorToARGBInt(this.tint); + String key = this.environment != null ? this.environment : "Default"; + int index = Environment.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new WorldGenLoadException("Unknown key! " + key); + } else { + return new VoidWorldGenProvider.VoidWorldGen(tintId, index); + } + } + + @Nonnull + @Override + public String toString() { + return "VoidWorldGenProvider{environment='" + this.environment + "'}"; + } + + public static class VoidWorldGen implements IWorldGen { + private final int tintId; + private final int environmentId; + + public VoidWorldGen() { + this.tintId = 0; + this.environmentId = 0; + } + + public VoidWorldGen(@Nullable Color tint, @Nullable String environment) throws WorldGenLoadException { + int tintId = tint == null ? 0 : ColorParseUtil.colorToARGBInt(tint); + this.tintId = tintId; + String key = environment != null ? environment : "Default"; + int index = Environment.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new WorldGenLoadException("Unknown key! " + key); + } else { + this.environmentId = index; + } + } + + public VoidWorldGen(int tintId, int environmentId) { + this.tintId = tintId; + this.environmentId = environmentId; + } + + @Nullable + @Override + public WorldGenTimingsCollector getTimings() { + return null; + } + + @Nonnull + @Override + public Transform[] getSpawnPoints(int seed) { + return new Transform[]{new Transform(0.0, 1.0, 0.0)}; + } + + @Nonnull + @Override + public CompletableFuture generate(int seed, long index, int cx, int cz, LongPredicate stillNeeded) { + GeneratedBlockChunk generatedBlockChunk = new GeneratedBlockChunk(index, cx, cz); + + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + if (this.environmentId != 0) { + generatedBlockChunk.setEnvironmentColumn(x, z, this.environmentId); + } + + generatedBlockChunk.setTint(x, z, this.tintId); + } + } + + return CompletableFuture.completedFuture( + new GeneratedChunk(generatedBlockChunk, new GeneratedBlockStateChunk(), new GeneratedEntityChunk(), GeneratedChunk.makeSections()) + ); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldlocationcondition/WorldLocationCondition.java b/src/com/hypixel/hytale/server/core/universe/world/worldlocationcondition/WorldLocationCondition.java new file mode 100644 index 0000000..d2d5215 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldlocationcondition/WorldLocationCondition.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.universe.world.worldlocationcondition; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.server.core.universe.world.World; +import javax.annotation.Nonnull; + +public abstract class WorldLocationCondition { + public static final CodecMapCodec CODEC = new CodecMapCodec<>("Type"); + public static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(WorldLocationCondition.class).build(); + + public WorldLocationCondition() { + } + + public abstract boolean test(World var1, int var2, int var3, int var4); + + @Override + public abstract boolean equals(Object var1); + + @Override + public abstract int hashCode(); + + @Nonnull + @Override + public String toString() { + return "WorldLocationCondition{}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldmap/IWorldMap.java b/src/com/hypixel/hytale/server/core/universe/world/worldmap/IWorldMap.java new file mode 100644 index 0000000..c069619 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldmap/IWorldMap.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.server.core.universe.world.worldmap; + +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.map.WorldMap; +import it.unimi.dsi.fastutil.longs.LongSet; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public interface IWorldMap { + WorldMapSettings getWorldMapSettings(); + + CompletableFuture generate(World var1, int var2, int var3, LongSet var4); + + CompletableFuture> generatePointsOfInterest(World var1); + + default void shutdown() { + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldmap/WorldMapLoadException.java b/src/com/hypixel/hytale/server/core/universe/world/worldmap/WorldMapLoadException.java new file mode 100644 index 0000000..eb33bc4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldmap/WorldMapLoadException.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.core.universe.world.worldmap; + +import com.hypixel.hytale.common.util.ExceptionUtil; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class WorldMapLoadException extends Exception { + public WorldMapLoadException(@Nonnull String message) { + super(Objects.requireNonNull(message)); + } + + public WorldMapLoadException(@Nonnull String message, Throwable cause) { + super(Objects.requireNonNull(message), cause); + } + + @Nonnull + public String getTraceMessage() { + return this.getTraceMessage(", "); + } + + @Nonnull + public String getTraceMessage(@Nonnull String joiner) { + return ExceptionUtil.combineMessages(this, joiner); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldmap/WorldMapManager.java b/src/com/hypixel/hytale/server/core/universe/world/worldmap/WorldMapManager.java new file mode 100644 index 0000000..85f8067 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldmap/WorldMapManager.java @@ -0,0 +1,440 @@ +package com.hypixel.hytale.server.core.universe.world.worldmap; + +import com.hypixel.fastutil.longs.Long2ObjectConcurrentHashMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.CodecMapCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.protocol.packets.worldmap.MapImage; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerWorldData; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.worldmap.markers.DeathMarkerProvider; +import com.hypixel.hytale.server.core.universe.world.worldmap.markers.POIMarkerProvider; +import com.hypixel.hytale.server.core.universe.world.worldmap.markers.PlayerIconMarkerProvider; +import com.hypixel.hytale.server.core.universe.world.worldmap.markers.PlayerMarkersProvider; +import com.hypixel.hytale.server.core.universe.world.worldmap.markers.RespawnMarkerProvider; +import com.hypixel.hytale.server.core.universe.world.worldmap.markers.SpawnMarkerProvider; +import com.hypixel.hytale.server.core.util.thread.TickingThread; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WorldMapManager extends TickingThread { + private static final int IMAGE_KEEP_ALIVE = 60; + private static final float DEFAULT_UNLOAD_DELAY = 1.0F; + @Nonnull + private final HytaleLogger logger; + @Nonnull + private final World world; + private final Long2ObjectConcurrentHashMap images = new Long2ObjectConcurrentHashMap<>( + true, ChunkUtil.indexChunk(Integer.MIN_VALUE, Integer.MIN_VALUE) + ); + private final Long2ObjectConcurrentHashMap> generating = new Long2ObjectConcurrentHashMap<>( + true, ChunkUtil.indexChunk(Integer.MIN_VALUE, Integer.MIN_VALUE) + ); + private final Map markerProviders = new ConcurrentHashMap<>(); + private final Map pointsOfInterest = new ConcurrentHashMap<>(); + @Nonnull + private WorldMapSettings worldMapSettings = WorldMapSettings.DISABLED; + @Nullable + private IWorldMap generator; + @Nonnull + private CompletableFuture generatorLoaded = new CompletableFuture<>(); + private float unloadDelay = 1.0F; + + public WorldMapManager(@Nonnull World world) { + super("WorldMap - " + world.getName(), 10, true); + this.logger = HytaleLogger.get("World|" + world.getName() + "|M"); + this.world = world; + this.addMarkerProvider("spawn", SpawnMarkerProvider.INSTANCE); + this.addMarkerProvider("playerIcons", PlayerIconMarkerProvider.INSTANCE); + this.addMarkerProvider("death", DeathMarkerProvider.INSTANCE); + this.addMarkerProvider("respawn", RespawnMarkerProvider.INSTANCE); + this.addMarkerProvider("playerMarkers", PlayerMarkersProvider.INSTANCE); + this.addMarkerProvider("poi", POIMarkerProvider.INSTANCE); + } + + @Nullable + public IWorldMap getGenerator() { + return this.generator; + } + + public void setGenerator(@Nullable IWorldMap generator) { + boolean before = this.shouldTick(); + if (this.generator != null) { + this.generator.shutdown(); + } + + this.generator = generator; + if (generator != null) { + this.logger.at(Level.INFO).log("Initializing world map generator: %s", generator.toString()); + this.generatorLoaded.complete(null); + this.generatorLoaded = new CompletableFuture<>(); + this.worldMapSettings = generator.getWorldMapSettings(); + this.images.clear(); + this.generating.clear(); + + for (Player worldPlayer : this.world.getPlayers()) { + worldPlayer.getWorldMapTracker().clear(); + } + + this.updateTickingState(before); + this.sendSettings(); + this.logger.at(Level.INFO).log("Generating Points of Interest..."); + CompletableFutureUtil._catch(generator.generatePointsOfInterest(this.world).thenAcceptAsync(pointsOfInterest -> { + this.pointsOfInterest.putAll((Map)pointsOfInterest); + this.logger.at(Level.INFO).log("Finished Generating Points of Interest!"); + })); + } else { + this.logger.at(Level.INFO).log("World map disabled!"); + this.worldMapSettings = WorldMapSettings.DISABLED; + this.sendSettings(); + } + } + + @Override + protected boolean isIdle() { + return this.world.getPlayerCount() == 0; + } + + @Override + protected void tick(float dt) { + for (Player player : this.world.getPlayers()) { + player.getWorldMapTracker().tick(dt); + } + + this.unloadDelay -= dt; + if (this.unloadDelay <= 0.0F) { + this.unloadDelay = 1.0F; + this.unloadImages(); + } + } + + @Override + protected void onShutdown() { + } + + public void unloadImages() { + int imagesCount = this.images.size(); + if (imagesCount != 0) { + List players = this.world.getPlayers(); + LongSet toRemove = new LongOpenHashSet(); + this.images.forEach((index, chunk) -> { + if (this.isWorldMapEnabled() && isWorldMapImageVisibleToAnyPlayer(players, index, this.worldMapSettings)) { + chunk.keepAlive.set(60); + } else { + if (chunk.keepAlive.decrementAndGet() <= 0) { + toRemove.add(index); + } + } + }); + if (!toRemove.isEmpty()) { + toRemove.forEach(value -> { + this.logger.at(Level.FINE).log("Unloading world map image: %s", value); + this.images.remove(value); + }); + } + + int toRemoveSize = toRemove.size(); + if (toRemoveSize > 0) { + this.logger + .at(Level.FINE) + .log("Cleaned %s world map images from memory, with %s images remaining in memory.", toRemoveSize, imagesCount - toRemoveSize); + } + } + } + + public boolean isWorldMapEnabled() { + return this.worldMapSettings.getSettingsPacket().enabled; + } + + public static boolean isWorldMapImageVisibleToAnyPlayer(@Nonnull List players, long imageIndex, @Nonnull WorldMapSettings settings) { + for (Player player : players) { + int viewRadius = settings.getViewRadius(player.getViewRadius()); + if (player.getWorldMapTracker().shouldBeVisible(viewRadius, imageIndex)) { + return true; + } + } + + return false; + } + + @Nonnull + public World getWorld() { + return this.world; + } + + @Nonnull + public WorldMapSettings getWorldMapSettings() { + return this.worldMapSettings; + } + + public Map getMarkerProviders() { + return this.markerProviders; + } + + public void addMarkerProvider(@Nonnull String key, @Nonnull WorldMapManager.MarkerProvider provider) { + this.markerProviders.put(key, provider); + } + + public Map getPointsOfInterest() { + return this.pointsOfInterest; + } + + @Nullable + public MapImage getImageIfInMemory(int x, int z) { + return this.getImageIfInMemory(ChunkUtil.indexChunk(x, z)); + } + + @Nullable + public MapImage getImageIfInMemory(long index) { + WorldMapManager.ImageEntry pair = this.images.get(index); + return pair != null ? pair.image : null; + } + + @Nonnull + public CompletableFuture getImageAsync(int x, int z) { + return this.getImageAsync(ChunkUtil.indexChunk(x, z)); + } + + @Nonnull + public CompletableFuture getImageAsync(long index) { + WorldMapManager.ImageEntry pair = this.images.get(index); + MapImage image = pair != null ? pair.image : null; + if (image != null) { + return CompletableFuture.completedFuture(image); + } else { + CompletableFuture gen = this.generating.get(index); + if (gen != null) { + return gen; + } else { + int imageSize = MathUtil.fastFloor(32.0F * this.worldMapSettings.getImageScale()); + LongSet chunksToGenerate = new LongOpenHashSet(); + chunksToGenerate.add(index); + CompletableFuture future = CompletableFutureUtil._catch( + this.generator.generate(this.world, imageSize, imageSize, chunksToGenerate).thenApplyAsync(worldMap -> { + MapImage newImage = worldMap.getChunks().get(index); + if (this.generating.remove(index) != null) { + this.images.put(index, new WorldMapManager.ImageEntry(newImage)); + } + + return newImage; + }) + ); + this.generating.put(index, future); + return future; + } + } + } + + public void generate() { + } + + public void sendSettings() { + for (Player player : this.world.getPlayers()) { + player.getWorldMapTracker().sendSettings(this.world); + } + } + + public boolean shouldTick() { + return this.world.getWorldConfig().isCompassUpdating() || this.isWorldMapEnabled(); + } + + public void updateTickingState(boolean before) { + boolean after = this.shouldTick(); + if (before != after) { + if (after) { + this.start(); + } else { + this.stop(); + } + } + } + + public void clearImages() { + this.images.clear(); + this.generating.clear(); + } + + public void clearImagesInChunks(@Nonnull LongSet chunkIndices) { + chunkIndices.forEach(index -> { + this.images.remove(index); + this.generating.remove(index); + }); + } + + @Nonnull + public static WorldMapManager.PlayerMarkerReference createPlayerMarker( + @Nonnull Ref playerRef, @Nonnull MapMarker marker, @Nonnull ComponentAccessor componentAccessor + ) { + World world = componentAccessor.getExternalData().getWorld(); + Player playerComponent = componentAccessor.getComponent(playerRef, Player.getComponentType()); + + assert playerComponent != null; + + UUIDComponent uuidComponent = componentAccessor.getComponent(playerRef, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + PlayerWorldData perWorldData = playerComponent.getPlayerConfigData().getPerWorldData(world.getName()); + MapMarker[] worldMapMarkers = perWorldData.getWorldMapMarkers(); + perWorldData.setWorldMapMarkers(ArrayUtil.append(worldMapMarkers, marker)); + return new WorldMapManager.PlayerMarkerReference(uuidComponent.getUuid(), world.getName(), marker.id); + } + + static { + WorldMapManager.MarkerReference.CODEC.register("Player", WorldMapManager.PlayerMarkerReference.class, WorldMapManager.PlayerMarkerReference.CODEC); + } + + public static class ImageEntry { + private final AtomicInteger keepAlive = new AtomicInteger(); + private final MapImage image; + + public ImageEntry(MapImage image) { + this.image = image; + } + } + + public interface MarkerProvider { + void update(World var1, GameplayConfig var2, WorldMapTracker var3, int var4, int var5, int var6); + } + + public interface MarkerReference { + CodecMapCodec CODEC = new CodecMapCodec<>(); + + String getMarkerId(); + + void remove(); + } + + public static class PlayerMarkerReference implements WorldMapManager.MarkerReference { + public static final BuilderCodec CODEC = BuilderCodec.builder( + WorldMapManager.PlayerMarkerReference.class, WorldMapManager.PlayerMarkerReference::new + ) + .addField( + new KeyedCodec<>("Player", Codec.UUID_BINARY), + (playerMarkerReference, uuid) -> playerMarkerReference.player = uuid, + playerMarkerReference -> playerMarkerReference.player + ) + .addField( + new KeyedCodec<>("World", Codec.STRING), + (playerMarkerReference, s) -> playerMarkerReference.world = s, + playerMarkerReference -> playerMarkerReference.world + ) + .addField( + new KeyedCodec<>("MarkerId", Codec.STRING), + (playerMarkerReference, s) -> playerMarkerReference.markerId = s, + playerMarkerReference -> playerMarkerReference.markerId + ) + .build(); + private UUID player; + private String world; + private String markerId; + + private PlayerMarkerReference() { + } + + public PlayerMarkerReference(@Nonnull UUID player, @Nonnull String world, @Nonnull String markerId) { + this.player = player; + this.world = world; + this.markerId = markerId; + } + + public UUID getPlayer() { + return this.player; + } + + @Override + public String getMarkerId() { + return this.markerId; + } + + @Override + public void remove() { + PlayerRef playerRef = Universe.get().getPlayer(this.player); + if (playerRef != null) { + Player playerComponent = playerRef.getComponent(Player.getComponentType()); + this.removeMarkerFromOnlinePlayer(playerComponent); + } else { + this.removeMarkerFromOfflinePlayer(); + } + } + + private void removeMarkerFromOnlinePlayer(@Nonnull Player player) { + PlayerConfigData data = player.getPlayerConfigData(); + String world = this.world; + if (world == null) { + world = player.getWorld().getName(); + } + + removeMarkerFromData(data, world, this.markerId); + } + + private void removeMarkerFromOfflinePlayer() { + Universe.get().getPlayerStorage().load(this.player).thenApply(holder -> { + Player player = holder.getComponent(Player.getComponentType()); + PlayerConfigData data = player.getPlayerConfigData(); + String world = this.world; + if (world == null) { + world = data.getWorld(); + } + + removeMarkerFromData(data, world, this.markerId); + return holder; + }).thenCompose(holder -> Universe.get().getPlayerStorage().save(this.player, (Holder)holder)); + } + + @Nullable + private static MapMarker removeMarkerFromData(@Nonnull PlayerConfigData data, @Nonnull String worldName, @Nonnull String markerId) { + PlayerWorldData perWorldData = data.getPerWorldData(worldName); + MapMarker[] worldMapMarkers = perWorldData.getWorldMapMarkers(); + if (worldMapMarkers == null) { + return null; + } else { + int index = -1; + + for (int i = 0; i < worldMapMarkers.length; i++) { + if (worldMapMarkers[i].id.equals(markerId)) { + index = i; + break; + } + } + + if (index == -1) { + return null; + } else { + MapMarker[] newWorldMapMarkers = new MapMarker[worldMapMarkers.length - 1]; + System.arraycopy(worldMapMarkers, 0, newWorldMapMarkers, 0, index); + System.arraycopy(worldMapMarkers, index + 1, newWorldMapMarkers, index, newWorldMapMarkers.length - index); + perWorldData.setWorldMapMarkers(newWorldMapMarkers); + return worldMapMarkers[index]; + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldmap/WorldMapSettings.java b/src/com/hypixel/hytale/server/core/universe/world/worldmap/WorldMapSettings.java new file mode 100644 index 0000000..444f046 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldmap/WorldMapSettings.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.core.universe.world.worldmap; + +import com.hypixel.hytale.math.shape.Box2D; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.protocol.packets.worldmap.UpdateWorldMapSettings; +import javax.annotation.Nonnull; + +public class WorldMapSettings { + public static final WorldMapSettings DISABLED = new WorldMapSettings(); + private Box2D worldMapArea; + private float imageScale = 0.5F; + private float viewRadiusMultiplier = 1.0F; + private int viewRadiusMin = 1; + private int viewRadiusMax = 512; + @Nonnull + private UpdateWorldMapSettings settingsPacket; + + public WorldMapSettings() { + this.settingsPacket = new UpdateWorldMapSettings(); + this.settingsPacket.enabled = false; + } + + public WorldMapSettings( + Box2D worldMapArea, float imageScale, float viewRadiusMultiplier, int viewRadiusMin, int viewRadiusMax, @Nonnull UpdateWorldMapSettings settingsPacket + ) { + this.worldMapArea = worldMapArea; + this.imageScale = imageScale; + this.viewRadiusMultiplier = viewRadiusMultiplier; + this.viewRadiusMin = viewRadiusMin; + this.viewRadiusMax = viewRadiusMax; + this.settingsPacket = settingsPacket; + } + + public Box2D getWorldMapArea() { + return this.worldMapArea; + } + + public float getImageScale() { + return this.imageScale; + } + + @Nonnull + public UpdateWorldMapSettings getSettingsPacket() { + return this.settingsPacket; + } + + public int getViewRadius(int viewRadius) { + return MathUtil.clamp(Math.round(viewRadius * this.viewRadiusMultiplier), this.viewRadiusMin, this.viewRadiusMax); + } + + @Nonnull + @Override + public String toString() { + return "WorldMapSettings{worldMapArea=" + + this.worldMapArea + + ", imageScale=" + + this.imageScale + + ", viewRadiusMultiplier=" + + this.viewRadiusMultiplier + + ", viewRadiusMin=" + + this.viewRadiusMin + + ", viewRadiusMax=" + + this.viewRadiusMax + + ", settingsPacket=" + + this.settingsPacket + + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/DeathMarkerProvider.java b/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/DeathMarkerProvider.java new file mode 100644 index 0000000..cf38a27 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/DeathMarkerProvider.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.core.universe.world.worldmap.markers; + +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.gameplay.WorldMapConfig; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerDeathPositionData; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerWorldData; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; +import com.hypixel.hytale.server.core.util.PositionUtil; +import javax.annotation.Nonnull; + +public class DeathMarkerProvider implements WorldMapManager.MarkerProvider { + public static final DeathMarkerProvider INSTANCE = new DeathMarkerProvider(); + + private DeathMarkerProvider() { + } + + @Override + public void update( + @Nonnull World world, @Nonnull GameplayConfig gameplayConfig, @Nonnull WorldMapTracker tracker, int chunkViewRadius, int playerChunkX, int playerChunkZ + ) { + WorldMapConfig worldMapConfig = gameplayConfig.getWorldMapConfig(); + if (worldMapConfig.isDisplayDeathMarker()) { + Player player = tracker.getPlayer(); + PlayerWorldData perWorldData = player.getPlayerConfigData().getPerWorldData(world.getName()); + + for (PlayerDeathPositionData deathPosition : perWorldData.getDeathPositions()) { + addDeathMarker(tracker, playerChunkX, playerChunkZ, deathPosition); + } + } + } + + private static void addDeathMarker(@Nonnull WorldMapTracker tracker, int playerChunkX, int playerChunkZ, @Nonnull PlayerDeathPositionData deathPosition) { + String markerId = deathPosition.getMarkerId(); + Transform transform = deathPosition.getTransform(); + int deathDay = deathPosition.getDay(); + tracker.trySendMarker( + -1, + playerChunkX, + playerChunkZ, + transform.getPosition(), + transform.getRotation().getYaw(), + markerId, + "Death (Day " + deathDay + ")", + transform, + (id, name, t) -> new MapMarker(id, name, "Death.png", PositionUtil.toTransformPacket(t), null) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/POIMarkerProvider.java b/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/POIMarkerProvider.java new file mode 100644 index 0000000..f6281ad --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/POIMarkerProvider.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.universe.world.worldmap.markers; + +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; +import java.util.Map; +import javax.annotation.Nonnull; + +public class POIMarkerProvider implements WorldMapManager.MarkerProvider { + public static final POIMarkerProvider INSTANCE = new POIMarkerProvider(); + + private POIMarkerProvider() { + } + + @Override + public void update( + @Nonnull World world, @Nonnull GameplayConfig gameplayConfig, @Nonnull WorldMapTracker tracker, int chunkViewRadius, int playerChunkX, int playerChunkZ + ) { + Map globalMarkers = world.getWorldMapManager().getPointsOfInterest(); + if (!globalMarkers.isEmpty()) { + for (MapMarker marker : globalMarkers.values()) { + tracker.trySendMarker(chunkViewRadius, playerChunkX, playerChunkZ, marker); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/PlayerIconMarkerProvider.java b/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/PlayerIconMarkerProvider.java new file mode 100644 index 0000000..fa22d50 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/PlayerIconMarkerProvider.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.server.core.universe.world.worldmap.markers; + +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.gameplay.WorldMapConfig; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; +import com.hypixel.hytale.server.core.util.PositionUtil; +import java.util.function.Predicate; +import javax.annotation.Nonnull; + +public class PlayerIconMarkerProvider implements WorldMapManager.MarkerProvider { + public static final PlayerIconMarkerProvider INSTANCE = new PlayerIconMarkerProvider(); + + private PlayerIconMarkerProvider() { + } + + @Override + public void update( + @Nonnull World world, @Nonnull GameplayConfig gameplayConfig, @Nonnull WorldMapTracker tracker, int chunkViewRadius, int playerChunkX, int playerChunkZ + ) { + WorldMapConfig worldMapConfig = gameplayConfig.getWorldMapConfig(); + if (worldMapConfig.isDisplayPlayers()) { + if (tracker.shouldUpdatePlayerMarkers()) { + Player player = tracker.getPlayer(); + int chunkViewRadiusSq = chunkViewRadius * chunkViewRadius; + Predicate playerMapFilter = tracker.getPlayerMapFilter(); + + for (PlayerRef otherPlayer : world.getPlayerRefs()) { + if (!otherPlayer.getUuid().equals(player.getUuid())) { + Transform otherPlayerTransform = otherPlayer.getTransform(); + Vector3d otherPos = otherPlayerTransform.getPosition(); + int otherChunkX = (int)otherPos.x >> 5; + int otherChunkZ = (int)otherPos.z >> 5; + int chunkDiffX = otherChunkX - playerChunkX; + int chunkDiffZ = otherChunkZ - playerChunkZ; + int chunkDistSq = chunkDiffX * chunkDiffX + chunkDiffZ * chunkDiffZ; + if (chunkDistSq <= chunkViewRadiusSq && (playerMapFilter == null || !playerMapFilter.test(otherPlayer))) { + tracker.trySendMarker( + chunkViewRadius, + playerChunkX, + playerChunkZ, + otherPos, + otherPlayer.getHeadRotation().getYaw(), + "Player-" + otherPlayer.getUuid(), + "Player: " + otherPlayer.getUsername(), + otherPlayer, + (id, name, op) -> new MapMarker(id, name, "Player.png", PositionUtil.toTransformPacket(op.getTransform()), null) + ); + } + } + } + + tracker.resetPlayerMarkersUpdateTimer(); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/PlayerMarkersProvider.java b/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/PlayerMarkersProvider.java new file mode 100644 index 0000000..31c5157 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/PlayerMarkersProvider.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.universe.world.worldmap.markers; + +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerWorldData; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; +import javax.annotation.Nonnull; + +public class PlayerMarkersProvider implements WorldMapManager.MarkerProvider { + public static final PlayerMarkersProvider INSTANCE = new PlayerMarkersProvider(); + + private PlayerMarkersProvider() { + } + + @Override + public void update( + @Nonnull World world, @Nonnull GameplayConfig gameplayConfig, @Nonnull WorldMapTracker tracker, int chunkViewRadius, int playerChunkX, int playerChunkZ + ) { + Player player = tracker.getPlayer(); + PlayerWorldData perWorldData = player.getPlayerConfigData().getPerWorldData(world.getName()); + MapMarker[] worldMapMarkers = perWorldData.getWorldMapMarkers(); + if (worldMapMarkers != null) { + for (MapMarker marker : worldMapMarkers) { + tracker.trySendMarker(chunkViewRadius, playerChunkX, playerChunkZ, marker); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/RespawnMarkerProvider.java b/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/RespawnMarkerProvider.java new file mode 100644 index 0000000..892ef0c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/RespawnMarkerProvider.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.core.universe.world.worldmap.markers; + +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.gameplay.WorldMapConfig; +import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerRespawnPointData; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; +import com.hypixel.hytale.server.core.util.PositionUtil; +import javax.annotation.Nonnull; + +public class RespawnMarkerProvider implements WorldMapManager.MarkerProvider { + public static final RespawnMarkerProvider INSTANCE = new RespawnMarkerProvider(); + + private RespawnMarkerProvider() { + } + + @Override + public void update( + @Nonnull World world, @Nonnull GameplayConfig gameplayConfig, @Nonnull WorldMapTracker tracker, int chunkViewRadius, int playerChunkX, int playerChunkZ + ) { + WorldMapConfig worldMapConfig = gameplayConfig.getWorldMapConfig(); + if (worldMapConfig.isDisplayHome()) { + PlayerRespawnPointData[] respawnPoints = tracker.getPlayer().getPlayerConfigData().getPerWorldData(world.getName()).getRespawnPoints(); + if (respawnPoints != null) { + for (int i = 0; i < respawnPoints.length; i++) { + addRespawnMarker(tracker, playerChunkX, playerChunkZ, respawnPoints[i], i); + } + } + } + } + + private static void addRespawnMarker( + @Nonnull WorldMapTracker tracker, int playerChunkX, int playerChunkZ, @Nonnull PlayerRespawnPointData respawnPoint, int index + ) { + String respawnPointName = respawnPoint.getName(); + Vector3i respawnPointPosition = respawnPoint.getBlockPosition(); + tracker.trySendMarker( + -1, + playerChunkX, + playerChunkZ, + respawnPointPosition.toVector3d(), + 0.0F, + respawnPointName + index, + respawnPointName, + respawnPointPosition, + (id, name, rp) -> new MapMarker(id, name, "Home.png", PositionUtil.toTransformPacket(new Transform(rp)), null) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/SpawnMarkerProvider.java b/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/SpawnMarkerProvider.java new file mode 100644 index 0000000..63b6a9d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldmap/markers/SpawnMarkerProvider.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.core.universe.world.worldmap.markers; + +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.asset.type.gameplay.WorldMapConfig; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldMapTracker; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager; +import com.hypixel.hytale.server.core.util.PositionUtil; +import javax.annotation.Nonnull; + +public class SpawnMarkerProvider implements WorldMapManager.MarkerProvider { + public static final SpawnMarkerProvider INSTANCE = new SpawnMarkerProvider(); + + private SpawnMarkerProvider() { + } + + @Override + public void update( + @Nonnull World world, @Nonnull GameplayConfig gameplayConfig, @Nonnull WorldMapTracker tracker, int chunkViewRadius, int playerChunkX, int playerChunkZ + ) { + WorldMapConfig worldMapConfig = gameplayConfig.getWorldMapConfig(); + if (worldMapConfig.isDisplaySpawn()) { + Player player = tracker.getPlayer(); + Transform spawnPoint = world.getWorldConfig().getSpawnProvider().getSpawnPoint(player); + if (spawnPoint != null) { + Vector3d spawnPosition = spawnPoint.getPosition(); + tracker.trySendMarker( + chunkViewRadius, + playerChunkX, + playerChunkZ, + spawnPosition, + spawnPoint.getRotation().getYaw(), + "Spawn", + "Spawn", + spawnPosition, + (id, name, pos) -> new MapMarker(id, name, "Spawn.png", PositionUtil.toTransformPacket(new Transform(pos)), null) + ); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldmap/provider/DisabledWorldMapProvider.java b/src/com/hypixel/hytale/server/core/universe/world/worldmap/provider/DisabledWorldMapProvider.java new file mode 100644 index 0000000..3af04e9 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldmap/provider/DisabledWorldMapProvider.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.server.core.universe.world.worldmap.provider; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.map.WorldMap; +import com.hypixel.hytale.server.core.universe.world.worldmap.IWorldMap; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapLoadException; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapSettings; +import it.unimi.dsi.fastutil.longs.LongSet; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class DisabledWorldMapProvider implements IWorldMapProvider { + public static final String ID = "Disabled"; + public static final BuilderCodec CODEC = BuilderCodec.builder(DisabledWorldMapProvider.class, DisabledWorldMapProvider::new) + .build(); + + public DisabledWorldMapProvider() { + } + + @Nonnull + @Override + public IWorldMap getGenerator(World world) throws WorldMapLoadException { + return DisabledWorldMapProvider.DisabledWorldMap.INSTANCE; + } + + @Nonnull + @Override + public String toString() { + return "DisabledWorldMapProvider{}"; + } + + static class DisabledWorldMap implements IWorldMap { + public static final IWorldMap INSTANCE = new DisabledWorldMapProvider.DisabledWorldMap(); + + DisabledWorldMap() { + } + + @Nonnull + @Override + public WorldMapSettings getWorldMapSettings() { + return WorldMapSettings.DISABLED; + } + + @Nonnull + @Override + public CompletableFuture generate(World world, int imageWidth, int imageHeight, LongSet chunksToGenerate) { + return CompletableFuture.completedFuture(new WorldMap(0)); + } + + @Nonnull + @Override + public CompletableFuture> generatePointsOfInterest(World world) { + return CompletableFuture.completedFuture(Collections.emptyMap()); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldmap/provider/IWorldMapProvider.java b/src/com/hypixel/hytale/server/core/universe/world/worldmap/provider/IWorldMapProvider.java new file mode 100644 index 0000000..98b7ba6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldmap/provider/IWorldMapProvider.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.core.universe.world.worldmap.provider; + +import com.hypixel.hytale.codec.lookup.BuilderCodecMapCodec; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.worldmap.IWorldMap; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapLoadException; + +public interface IWorldMapProvider { + BuilderCodecMapCodec CODEC = new BuilderCodecMapCodec<>("Type", true); + + IWorldMap getGenerator(World var1) throws WorldMapLoadException; +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldmap/provider/chunk/ChunkWorldMap.java b/src/com/hypixel/hytale/server/core/universe/world/worldmap/provider/chunk/ChunkWorldMap.java new file mode 100644 index 0000000..a1769b4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldmap/provider/chunk/ChunkWorldMap.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.core.universe.world.worldmap.provider.chunk; + +import com.hypixel.hytale.protocol.packets.worldmap.MapMarker; +import com.hypixel.hytale.protocol.packets.worldmap.UpdateWorldMapSettings; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.map.WorldMap; +import com.hypixel.hytale.server.core.universe.world.worldmap.IWorldMap; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapSettings; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongSet; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class ChunkWorldMap implements IWorldMap { + public static final ChunkWorldMap INSTANCE = new ChunkWorldMap(); + + public ChunkWorldMap() { + } + + @Nonnull + @Override + public WorldMapSettings getWorldMapSettings() { + UpdateWorldMapSettings settingsPacket = new UpdateWorldMapSettings(); + settingsPacket.defaultScale = 128.0F; + settingsPacket.minScale = 32.0F; + settingsPacket.maxScale = 175.0F; + return new WorldMapSettings(null, 3.0F, 2.0F, 3, 32, settingsPacket); + } + + @Nonnull + @Override + public CompletableFuture generate(World world, int imageWidth, int imageHeight, @Nonnull LongSet chunksToGenerate) { + CompletableFuture[] futures = new CompletableFuture[chunksToGenerate.size()]; + int futureIndex = 0; + LongIterator iterator = chunksToGenerate.iterator(); + + while (iterator.hasNext()) { + futures[futureIndex++] = ImageBuilder.build(iterator.nextLong(), imageWidth, imageHeight, world); + } + + return CompletableFuture.allOf(futures).thenApply(unused -> { + WorldMap worldMap = new WorldMap(futures.length); + + for (CompletableFuture future : futures) { + ImageBuilder builder = future.getNow(null); + if (builder != null) { + worldMap.getChunks().put(builder.getIndex(), builder.getImage()); + } + } + + return worldMap; + }); + } + + @Nonnull + @Override + public CompletableFuture> generatePointsOfInterest(World world) { + return CompletableFuture.completedFuture(Collections.emptyMap()); + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldmap/provider/chunk/ImageBuilder.java b/src/com/hypixel/hytale/server/core/universe/world/worldmap/provider/chunk/ImageBuilder.java new file mode 100644 index 0000000..ad328c1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldmap/provider/chunk/ImageBuilder.java @@ -0,0 +1,452 @@ +package com.hypixel.hytale.server.core.universe.world.worldmap.provider.chunk; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.protocol.packets.worldmap.MapImage; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +class ImageBuilder { + private final long index; + private final World world; + @Nonnull + private final MapImage image; + private final int sampleWidth; + private final int sampleHeight; + private final int blockStepX; + private final int blockStepZ; + @Nonnull + private final short[] heightSamples; + @Nonnull + private final int[] tintSamples; + @Nonnull + private final int[] blockSamples; + @Nonnull + private final short[] neighborHeightSamples; + @Nonnull + private final short[] fluidDepthSamples; + @Nonnull + private final int[] environmentSamples; + @Nonnull + private final int[] fluidSamples; + private final ImageBuilder.Color outColor = new ImageBuilder.Color(); + @Nullable + private WorldChunk worldChunk; + private FluidSection[] fluidSections; + + public ImageBuilder(long index, int imageWidth, int imageHeight, World world) { + this.index = index; + this.world = world; + this.image = new MapImage(imageWidth, imageHeight, new int[imageWidth * imageHeight]); + this.sampleWidth = Math.min(32, this.image.width); + this.sampleHeight = Math.min(32, this.image.height); + this.blockStepX = Math.max(1, 32 / this.image.width); + this.blockStepZ = Math.max(1, 32 / this.image.height); + this.heightSamples = new short[this.sampleWidth * this.sampleHeight]; + this.tintSamples = new int[this.sampleWidth * this.sampleHeight]; + this.blockSamples = new int[this.sampleWidth * this.sampleHeight]; + this.neighborHeightSamples = new short[(this.sampleWidth + 2) * (this.sampleHeight + 2)]; + this.fluidDepthSamples = new short[this.sampleWidth * this.sampleHeight]; + this.environmentSamples = new int[this.sampleWidth * this.sampleHeight]; + this.fluidSamples = new int[this.sampleWidth * this.sampleHeight]; + } + + public long getIndex() { + return this.index; + } + + @Nonnull + public MapImage getImage() { + return this.image; + } + + @Nonnull + private CompletableFuture fetchChunk() { + return this.world.getChunkStore().getChunkReferenceAsync(this.index).thenApplyAsync(ref -> { + if (ref != null && ref.isValid()) { + this.worldChunk = ref.getStore().getComponent((Ref)ref, WorldChunk.getComponentType()); + ChunkColumn chunkColumn = ref.getStore().getComponent((Ref)ref, ChunkColumn.getComponentType()); + this.fluidSections = new FluidSection[10]; + + for (int y = 0; y < 10; y++) { + Ref sectionRef = chunkColumn.getSection(y); + this.fluidSections[y] = this.world.getChunkStore().getStore().getComponent(sectionRef, FluidSection.getComponentType()); + } + + return this; + } else { + return null; + } + }, this.world); + } + + @Nonnull + private CompletableFuture sampleNeighborsSync() { + CompletableFuture north = this.world + .getChunkStore() + .getChunkReferenceAsync(ChunkUtil.indexChunk(this.worldChunk.getX(), this.worldChunk.getZ() - 1)) + .thenAcceptAsync(ref -> { + if (ref != null && ref.isValid()) { + WorldChunk worldChunk = ref.getStore().getComponent((Ref)ref, WorldChunk.getComponentType()); + int z = (this.sampleHeight - 1) * this.blockStepZ; + + for (int ix = 0; ix < this.sampleWidth; ix++) { + int x = ix * this.blockStepX; + this.neighborHeightSamples[1 + ix] = worldChunk.getHeight(x, z); + } + } + }, this.world); + CompletableFuture south = this.world + .getChunkStore() + .getChunkReferenceAsync(ChunkUtil.indexChunk(this.worldChunk.getX(), this.worldChunk.getZ() + 1)) + .thenAcceptAsync(ref -> { + if (ref != null && ref.isValid()) { + WorldChunk worldChunk = ref.getStore().getComponent((Ref)ref, WorldChunk.getComponentType()); + int z = 0; + int neighbourStartIndex = (this.sampleHeight + 1) * (this.sampleWidth + 2) + 1; + + for (int ix = 0; ix < this.sampleWidth; ix++) { + int x = ix * this.blockStepX; + this.neighborHeightSamples[neighbourStartIndex + ix] = worldChunk.getHeight(x, z); + } + } + }, this.world); + CompletableFuture west = this.world + .getChunkStore() + .getChunkReferenceAsync(ChunkUtil.indexChunk(this.worldChunk.getX() - 1, this.worldChunk.getZ())) + .thenAcceptAsync(ref -> { + if (ref != null && ref.isValid()) { + WorldChunk worldChunk = ref.getStore().getComponent((Ref)ref, WorldChunk.getComponentType()); + int x = (this.sampleWidth - 1) * this.blockStepX; + + for (int iz = 0; iz < this.sampleHeight; iz++) { + int z = iz * this.blockStepZ; + this.neighborHeightSamples[(iz + 1) * (this.sampleWidth + 2)] = worldChunk.getHeight(x, z); + } + } + }, this.world); + CompletableFuture east = this.world + .getChunkStore() + .getChunkReferenceAsync(ChunkUtil.indexChunk(this.worldChunk.getX() + 1, this.worldChunk.getZ())) + .thenAcceptAsync(ref -> { + if (ref != null && ref.isValid()) { + WorldChunk worldChunk = ref.getStore().getComponent((Ref)ref, WorldChunk.getComponentType()); + int x = 0; + + for (int iz = 0; iz < this.sampleHeight; iz++) { + int z = iz * this.blockStepZ; + this.neighborHeightSamples[(iz + 1) * (this.sampleWidth + 2) + this.sampleWidth + 1] = worldChunk.getHeight(x, z); + } + } + }, this.world); + CompletableFuture northeast = this.world + .getChunkStore() + .getChunkReferenceAsync(ChunkUtil.indexChunk(this.worldChunk.getX() + 1, this.worldChunk.getZ() - 1)) + .thenAcceptAsync(ref -> { + if (ref != null && ref.isValid()) { + WorldChunk worldChunk = ref.getStore().getComponent((Ref)ref, WorldChunk.getComponentType()); + int x = 0; + int z = (this.sampleHeight - 1) * this.blockStepZ; + this.neighborHeightSamples[0] = worldChunk.getHeight(x, z); + } + }, this.world); + CompletableFuture northwest = this.world + .getChunkStore() + .getChunkReferenceAsync(ChunkUtil.indexChunk(this.worldChunk.getX() - 1, this.worldChunk.getZ() - 1)) + .thenAcceptAsync(ref -> { + if (ref != null && ref.isValid()) { + WorldChunk worldChunk = ref.getStore().getComponent((Ref)ref, WorldChunk.getComponentType()); + int x = (this.sampleWidth - 1) * this.blockStepX; + int z = (this.sampleHeight - 1) * this.blockStepZ; + this.neighborHeightSamples[this.sampleWidth + 1] = worldChunk.getHeight(x, z); + } + }, this.world); + CompletableFuture southeast = this.world + .getChunkStore() + .getChunkReferenceAsync(ChunkUtil.indexChunk(this.worldChunk.getX() + 1, this.worldChunk.getZ() + 1)) + .thenAcceptAsync(ref -> { + if (ref != null && ref.isValid()) { + WorldChunk worldChunk = ref.getStore().getComponent((Ref)ref, WorldChunk.getComponentType()); + int x = 0; + int z = 0; + this.neighborHeightSamples[(this.sampleHeight + 1) * (this.sampleWidth + 2) + this.sampleWidth + 1] = worldChunk.getHeight(x, z); + } + }, this.world); + CompletableFuture southwest = this.world + .getChunkStore() + .getChunkReferenceAsync(ChunkUtil.indexChunk(this.worldChunk.getX() - 1, this.worldChunk.getZ() + 1)) + .thenAcceptAsync(ref -> { + if (ref != null && ref.isValid()) { + WorldChunk worldChunk = ref.getStore().getComponent((Ref)ref, WorldChunk.getComponentType()); + int x = (this.sampleWidth - 1) * this.blockStepX; + int z = 0; + this.neighborHeightSamples[(this.sampleHeight + 1) * (this.sampleWidth + 2)] = worldChunk.getHeight(x, z); + } + }, this.world); + return CompletableFuture.allOf(north, south, west, east, northeast, northwest, southeast, southwest).thenApply(v -> this); + } + + @Nonnull + private ImageBuilder generateImageAsync() { + for (int ix = 0; ix < this.sampleWidth; ix++) { + for (int iz = 0; iz < this.sampleHeight; iz++) { + int sampleIndex = iz * this.sampleWidth + ix; + int x = ix * this.blockStepX; + int z = iz * this.blockStepZ; + short height = this.worldChunk.getHeight(x, z); + int tint = this.worldChunk.getTint(x, z); + this.heightSamples[sampleIndex] = height; + this.tintSamples[sampleIndex] = tint; + int blockId = this.worldChunk.getBlock(x, height, z); + this.blockSamples[sampleIndex] = blockId; + int fluidId = 0; + int fluidTop = 320; + Fluid fluid = null; + int chunkYGround = ChunkUtil.chunkCoordinate((int)height); + int chunkY = 9; + + label107: + while (chunkY >= 0 && chunkY >= chunkYGround) { + FluidSection fluidSection = this.fluidSections[chunkY]; + if (fluidSection != null && !fluidSection.isEmpty()) { + int minBlockY = Math.max(ChunkUtil.minBlock(chunkY), height); + int maxBlockY = ChunkUtil.maxBlock(chunkY); + + for (int blockY = maxBlockY; blockY >= minBlockY; blockY--) { + fluidId = fluidSection.getFluidId(x, blockY, z); + if (fluidId != 0) { + fluid = Fluid.getAssetMap().getAsset(fluidId); + fluidTop = blockY; + break label107; + } + } + + chunkY--; + } else { + chunkY--; + } + } + + int fluidBottom; + label128: + for (fluidBottom = height; chunkY >= 0 && chunkY >= chunkYGround; chunkY--) { + FluidSection fluidSection = this.fluidSections[chunkY]; + if (fluidSection == null || fluidSection.isEmpty()) { + fluidBottom = Math.min(ChunkUtil.maxBlock(chunkY) + 1, fluidTop); + break; + } + + int minBlockY = Math.max(ChunkUtil.minBlock(chunkY), height); + int maxBlockY = Math.min(ChunkUtil.maxBlock(chunkY), fluidTop - 1); + + for (int blockYx = maxBlockY; blockYx >= minBlockY; blockYx--) { + int nextFluidId = fluidSection.getFluidId(x, blockYx, z); + if (nextFluidId != fluidId) { + Fluid nextFluid = Fluid.getAssetMap().getAsset(nextFluidId); + if (!Objects.equals(fluid.getParticleColor(), nextFluid.getParticleColor())) { + fluidBottom = blockYx + 1; + break label128; + } + } + } + } + + short fluidDepth = fluidId != 0 ? (short)(fluidTop - fluidBottom + 1) : 0; + int environmentId = this.worldChunk.getBlockChunk().getEnvironment(x, fluidTop, z); + this.fluidDepthSamples[sampleIndex] = fluidDepth; + this.environmentSamples[sampleIndex] = environmentId; + this.fluidSamples[sampleIndex] = fluidId; + } + } + + float imageToSampleRatioWidth = (float)this.sampleWidth / this.image.width; + float imageToSampleRatioHeight = (float)this.sampleHeight / this.image.height; + int blockPixelWidth = Math.max(1, this.image.width / this.sampleWidth); + int blockPixelHeight = Math.max(1, this.image.height / this.sampleHeight); + + for (int iz = 0; iz < this.sampleHeight; iz++) { + System.arraycopy(this.heightSamples, iz * this.sampleWidth, this.neighborHeightSamples, (iz + 1) * (this.sampleWidth + 2) + 1, this.sampleWidth); + } + + for (int ix = 0; ix < this.image.width; ix++) { + for (int iz = 0; iz < this.image.height; iz++) { + int sampleX = Math.min((int)(ix * imageToSampleRatioWidth), this.sampleWidth - 1); + int sampleZ = Math.min((int)(iz * imageToSampleRatioHeight), this.sampleHeight - 1); + int sampleIndex = sampleZ * this.sampleWidth + sampleX; + int blockPixelX = ix % blockPixelWidth; + int blockPixelZ = iz % blockPixelHeight; + short height = this.heightSamples[sampleIndex]; + int tint = this.tintSamples[sampleIndex]; + int blockId = this.blockSamples[sampleIndex]; + if (height >= 0 && blockId != 0) { + getBlockColor(blockId, tint, this.outColor); + short north = this.neighborHeightSamples[sampleZ * (this.sampleWidth + 2) + sampleX + 1]; + short south = this.neighborHeightSamples[(sampleZ + 2) * (this.sampleWidth + 2) + sampleX + 1]; + short west = this.neighborHeightSamples[(sampleZ + 1) * (this.sampleWidth + 2) + sampleX]; + short east = this.neighborHeightSamples[(sampleZ + 1) * (this.sampleWidth + 2) + sampleX + 2]; + short northWest = this.neighborHeightSamples[sampleZ * (this.sampleWidth + 2) + sampleX]; + short northEast = this.neighborHeightSamples[sampleZ * (this.sampleWidth + 2) + sampleX + 2]; + short southWest = this.neighborHeightSamples[(sampleZ + 2) * (this.sampleWidth + 2) + sampleX]; + short southEast = this.neighborHeightSamples[(sampleZ + 2) * (this.sampleWidth + 2) + sampleX + 2]; + float shade = shadeFromHeights( + blockPixelX, blockPixelZ, blockPixelWidth, blockPixelHeight, height, north, south, west, east, northWest, northEast, southWest, southEast + ); + this.outColor.multiply(shade); + if (height < 320) { + int fluidId = this.fluidSamples[sampleIndex]; + if (fluidId != 0) { + short fluidDepth = this.fluidDepthSamples[sampleIndex]; + int environmentId = this.environmentSamples[sampleIndex]; + getFluidColor(fluidId, environmentId, fluidDepth, this.outColor); + } + } + + this.packImageData(ix, iz); + } else { + this.outColor.r = this.outColor.g = this.outColor.b = this.outColor.a = 0; + this.packImageData(ix, iz); + } + } + } + + return this; + } + + private void packImageData(int ix, int iz) { + this.image.data[iz * this.image.width + ix] = this.outColor.pack(); + } + + private static float shadeFromHeights( + int blockPixelX, + int blockPixelZ, + int blockPixelWidth, + int blockPixelHeight, + short height, + short north, + short south, + short west, + short east, + short northWest, + short northEast, + short southWest, + short southEast + ) { + float u = (blockPixelX + 0.5F) / blockPixelWidth; + float v = (blockPixelZ + 0.5F) / blockPixelHeight; + float ud = (u + v) / 2.0F; + float vd = (1.0F - u + v) / 2.0F; + float dhdx1 = (height - west) * (1.0F - u) + (east - height) * u; + float dhdz1 = (height - north) * (1.0F - v) + (south - height) * v; + float dhdx2 = (height - northWest) * (1.0F - ud) + (southEast - height) * ud; + float dhdz2 = (height - northEast) * (1.0F - vd) + (southWest - height) * vd; + float dhdx = dhdx1 * 2.0F + dhdx2; + float dhdz = dhdz1 * 2.0F + dhdz2; + float dy = 3.0F; + float invS = 1.0F / (float)Math.sqrt(dhdx * dhdx + dy * dy + dhdz * dhdz); + float nx = dhdx * invS; + float ny = dy * invS; + float nz = dhdz * invS; + float lx = -0.2F; + float ly = 0.8F; + float lz = 0.5F; + float invL = 1.0F / (float)Math.sqrt(lx * lx + ly * ly + lz * lz); + lx *= invL; + ly *= invL; + lz *= invL; + float lambert = Math.max(0.0F, nx * lx + ny * ly + nz * lz); + float ambient = 0.4F; + float diffuse = 0.6F; + return ambient + diffuse * lambert; + } + + private static void getBlockColor(int blockId, int biomeTintColor, @Nonnull ImageBuilder.Color outColor) { + BlockType block = BlockType.getAssetMap().getAsset(blockId); + int biomeTintR = biomeTintColor >> 16 & 0xFF; + int biomeTintG = biomeTintColor >> 8 & 0xFF; + int biomeTintB = biomeTintColor >> 0 & 0xFF; + com.hypixel.hytale.protocol.Color[] tintUp = block.getTintUp(); + boolean hasTint = tintUp != null && tintUp.length > 0; + int selfTintR = hasTint ? tintUp[0].red & 255 : 255; + int selfTintG = hasTint ? tintUp[0].green & 255 : 255; + int selfTintB = hasTint ? tintUp[0].blue & 255 : 255; + float biomeTintMultiplier = block.getBiomeTintUp() / 100.0F; + int tintColorR = (int)(selfTintR + (biomeTintR - selfTintR) * biomeTintMultiplier); + int tintColorG = (int)(selfTintG + (biomeTintG - selfTintG) * biomeTintMultiplier); + int tintColorB = (int)(selfTintB + (biomeTintB - selfTintB) * biomeTintMultiplier); + com.hypixel.hytale.protocol.Color particleColor = block.getParticleColor(); + if (particleColor != null && biomeTintMultiplier < 1.0F) { + tintColorR = tintColorR * (particleColor.red & 255) / 255; + tintColorG = tintColorG * (particleColor.green & 255) / 255; + tintColorB = tintColorB * (particleColor.blue & 255) / 255; + } + + outColor.r = tintColorR & 0xFF; + outColor.g = tintColorG & 0xFF; + outColor.b = tintColorB & 0xFF; + outColor.a = 255; + } + + private static void getFluidColor(int fluidId, int environmentId, int fluidDepth, @Nonnull ImageBuilder.Color outColor) { + int tintColorR = 255; + int tintColorG = 255; + int tintColorB = 255; + Environment environment = Environment.getAssetMap().getAsset(environmentId); + com.hypixel.hytale.protocol.Color waterTint = environment.getWaterTint(); + if (waterTint != null) { + tintColorR = tintColorR * (waterTint.red & 255) / 255; + tintColorG = tintColorG * (waterTint.green & 255) / 255; + tintColorB = tintColorB * (waterTint.blue & 255) / 255; + } + + Fluid fluid = Fluid.getAssetMap().getAsset(fluidId); + com.hypixel.hytale.protocol.Color partcileColor = fluid.getParticleColor(); + if (partcileColor != null) { + tintColorR = tintColorR * (partcileColor.red & 255) / 255; + tintColorG = tintColorG * (partcileColor.green & 255) / 255; + tintColorB = tintColorB * (partcileColor.blue & 255) / 255; + } + + float depthMultiplier = Math.min(1.0F, 1.0F / fluidDepth); + outColor.r = (int)(tintColorR + ((outColor.r & 0xFF) - tintColorR) * depthMultiplier) & 0xFF; + outColor.g = (int)(tintColorG + ((outColor.g & 0xFF) - tintColorG) * depthMultiplier) & 0xFF; + outColor.b = (int)(tintColorB + ((outColor.b & 0xFF) - tintColorB) * depthMultiplier) & 0xFF; + } + + @Nonnull + public static CompletableFuture build(long index, int imageWidth, int imageHeight, World world) { + return CompletableFuture.completedFuture(new ImageBuilder(index, imageWidth, imageHeight, world)) + .thenCompose(ImageBuilder::fetchChunk) + .thenCompose(builder -> builder != null ? builder.sampleNeighborsSync() : CompletableFuture.completedFuture(null)) + .thenApplyAsync(builder -> builder != null ? builder.generateImageAsync() : null); + } + + private static class Color { + public int r; + public int g; + public int b; + public int a; + + private Color() { + } + + public int pack() { + return (this.r & 0xFF) << 24 | (this.g & 0xFF) << 16 | (this.b & 0xFF) << 8 | this.a & 0xFF; + } + + public void multiply(float value) { + this.r = Math.min(255, Math.max(0, (int)(this.r * value))); + this.g = Math.min(255, Math.max(0, (int)(this.g * value))); + this.b = Math.min(255, Math.max(0, (int)(this.b * value))); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/universe/world/worldmap/provider/chunk/WorldGenWorldMapProvider.java b/src/com/hypixel/hytale/server/core/universe/world/worldmap/provider/chunk/WorldGenWorldMapProvider.java new file mode 100644 index 0000000..9152723 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/universe/world/worldmap/provider/chunk/WorldGenWorldMapProvider.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.core.universe.world.worldmap.provider.chunk; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen; +import com.hypixel.hytale.server.core.universe.world.worldmap.IWorldMap; +import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapLoadException; +import com.hypixel.hytale.server.core.universe.world.worldmap.provider.IWorldMapProvider; +import javax.annotation.Nonnull; + +public class WorldGenWorldMapProvider implements IWorldMapProvider { + public static final String ID = "WorldGen"; + public static final BuilderCodec CODEC = BuilderCodec.builder(WorldGenWorldMapProvider.class, WorldGenWorldMapProvider::new) + .build(); + + public WorldGenWorldMapProvider() { + } + + @Override + public IWorldMap getGenerator(@Nonnull World world) throws WorldMapLoadException { + IWorldGen generator = world.getChunkStore().getGenerator(); + return (IWorldMap)(generator instanceof IWorldMapProvider ? ((IWorldMapProvider)generator).getGenerator(world) : ChunkWorldMap.INSTANCE); + } + + @Nonnull + @Override + public String toString() { + return "DisabledWorldMapProvider{}"; + } +} diff --git a/src/com/hypixel/hytale/server/core/util/AssetUtil.java b/src/com/hypixel/hytale/server/core/util/AssetUtil.java new file mode 100644 index 0000000..059696d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/AssetUtil.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.server.core.util; + +import com.hypixel.hytale.server.core.asset.AssetModule; +import java.nio.file.Path; + +public class AssetUtil { + public AssetUtil() { + } + + @Deprecated(forRemoval = true) + public static Path getHytaleAssetsPath() { + return AssetModule.get().getBaseAssetPack().getRoot(); + } +} diff --git a/src/com/hypixel/hytale/server/core/util/AuthUtil.java b/src/com/hypixel/hytale/server/core/util/AuthUtil.java new file mode 100644 index 0000000..47c9ccc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/AuthUtil.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.core.util; + +import com.hypixel.hytale.server.core.NameMatching; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import java.nio.charset.StandardCharsets; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public class AuthUtil { + public AuthUtil() { + } + + @Nonnull + @Deprecated + public static CompletableFuture lookupUuid(String username) { + PlayerRef player = Universe.get().getPlayerByUsername(username, NameMatching.EXACT); + return player != null + ? CompletableFuture.completedFuture(player.getUuid()) + : CompletableFuture.completedFuture(UUID.nameUUIDFromBytes(("NO_AUTH|" + username).getBytes(StandardCharsets.UTF_8))); + } +} diff --git a/src/com/hypixel/hytale/server/core/util/BsonUtil.java b/src/com/hypixel/hytale/server/core/util/BsonUtil.java new file mode 100644 index 0000000..08104d1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/BsonUtil.java @@ -0,0 +1,247 @@ +package com.hypixel.hytale.server.core.util; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.common.util.ExceptionUtil; +import com.hypixel.hytale.common.util.PathUtil; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.util.io.ByteBufUtil; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import io.netty.buffer.ByteBuf; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bson.BsonBinaryReader; +import org.bson.BsonBinaryWriter; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.BsonValue; +import org.bson.codecs.BsonDocumentCodec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.io.BasicOutputBuffer; +import org.bson.json.JsonMode; +import org.bson.json.JsonWriter; +import org.bson.json.JsonWriterSettings; + +public class BsonUtil { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static final JsonWriterSettings SETTINGS = JsonWriterSettings.builder() + .outputMode(JsonMode.STRICT) + .indent(true) + .newLineCharacters("\n") + .int64Converter((value, writer) -> writer.writeNumber(Long.toString(value))) + .build(); + private static final BsonDocumentCodec codec = new BsonDocumentCodec(); + private static final DecoderContext decoderContext = DecoderContext.builder().build(); + private static final EncoderContext encoderContext = EncoderContext.builder().build(); + public static final BsonDocumentCodec BSON_DOCUMENT_CODEC = new BsonDocumentCodec(); + + public BsonUtil() { + } + + public static byte[] writeToBytes(@Nullable BsonDocument document) { + if (document == null) { + return ArrayUtil.EMPTY_BYTE_ARRAY; + } else { + byte[] var2; + try (BasicOutputBuffer buffer = new BasicOutputBuffer()) { + codec.encode(new BsonBinaryWriter(buffer), document, encoderContext); + var2 = buffer.toByteArray(); + } + + return var2; + } + } + + public static BsonDocument readFromBytes(@Nullable byte[] buf) { + return buf != null && buf.length != 0 ? codec.decode(new BsonBinaryReader(ByteBuffer.wrap(buf)), decoderContext) : null; + } + + public static BsonDocument readFromBuffer(@Nullable ByteBuffer buf) { + return buf != null && buf.hasRemaining() ? codec.decode(new BsonBinaryReader(buf), decoderContext) : null; + } + + public static BsonDocument readFromBinaryStream(@Nonnull ByteBuf buf) { + return readFromBytes(ByteBufUtil.readByteArray(buf)); + } + + public static void writeToBinaryStream(@Nonnull ByteBuf buf, BsonDocument doc) { + ByteBufUtil.writeByteArray(buf, writeToBytes(doc)); + } + + @Nonnull + public static CompletableFuture writeDocumentBytes(@Nonnull Path file, BsonDocument document) { + try { + if (Files.isRegularFile(file)) { + Path resolve = file.resolveSibling(file.getFileName() + ".bak"); + Files.move(file, resolve, StandardCopyOption.REPLACE_EXISTING); + } + + byte[] bytes; + try (BasicOutputBuffer bob = new BasicOutputBuffer()) { + codec.encode(new BsonBinaryWriter(bob), document, encoderContext); + bytes = bob.toByteArray(); + } + + return CompletableFuture.runAsync(SneakyThrow.sneakyRunnable(() -> Files.write(file, bytes))); + } catch (IOException var8) { + return CompletableFuture.failedFuture(var8); + } + } + + @Nonnull + public static CompletableFuture writeDocument(@Nonnull Path file, BsonDocument document) { + return writeDocument(file, document, true); + } + + @Nonnull + public static CompletableFuture writeDocument(@Nonnull Path file, BsonDocument document, boolean backup) { + try { + Path parent = PathUtil.getParent(file); + if (!Files.exists(parent)) { + Files.createDirectories(parent); + } + + if (backup && Files.isRegularFile(file)) { + Path resolve = file.resolveSibling(file.getFileName() + ".bak"); + Files.move(file, resolve, StandardCopyOption.REPLACE_EXISTING); + } + + String json = toJson(document); + return CompletableFuture.runAsync(SneakyThrow.sneakyRunnable(() -> Files.writeString(file, json))); + } catch (IOException var5) { + return CompletableFuture.failedFuture(var5); + } + } + + @Nonnull + public static CompletableFuture readDocument(@Nonnull Path file) { + return readDocument(file, true); + } + + @Nonnull + public static CompletableFuture readDocument(@Nonnull Path file, boolean backup) { + BasicFileAttributes attributes; + try { + attributes = Files.readAttributes(file, BasicFileAttributes.class); + } catch (IOException var4) { + if (backup) { + return readDocumentBak(file); + } + + return CompletableFuture.completedFuture(null); + } + + if (attributes.size() == 0L) { + LOGGER.at(Level.WARNING).log("Error loading file %s, file was found to be entirely empty", file); + return backup ? readDocumentBak(file) : CompletableFuture.completedFuture(null); + } else { + CompletableFuture future = CompletableFuture.supplyAsync(SneakyThrow.sneakySupplier(() -> Files.readString(file))) + .thenApply(BsonDocument::parse); + return backup ? future.exceptionallyCompose(t -> readDocumentBak(file)) : future; + } + } + + @Nullable + public static BsonDocument readDocumentNow(@Nonnull Path file) { + BasicFileAttributes attributes; + try { + attributes = Files.readAttributes(file, BasicFileAttributes.class); + } catch (IOException var5) { + HytaleLogger.getLogger().atWarning().log(ExceptionUtil.toStringWithStack(var5)); + return null; + } + + if (attributes.size() == 0L) { + return null; + } else { + String contentsString; + try { + contentsString = Files.readString(file); + } catch (IOException var4) { + return null; + } + + return BsonDocument.parse(contentsString); + } + } + + @Nonnull + public static CompletableFuture readDocumentBak(@Nonnull Path fileOrig) { + Path file = fileOrig.resolveSibling(fileOrig.getFileName() + ".bak"); + + BasicFileAttributes attributes; + try { + attributes = Files.readAttributes(file, BasicFileAttributes.class); + } catch (IOException var4) { + return CompletableFuture.completedFuture(null); + } + + if (attributes.size() == 0L) { + LOGGER.at(Level.WARNING).log("Error loading backup file %s, file was found to be entirely empty", file); + return CompletableFuture.completedFuture(null); + } else { + LOGGER.at(Level.WARNING).log("Loading %s backup file for %s!", file, fileOrig); + return CompletableFuture.supplyAsync(SneakyThrow.sneakySupplier(() -> Files.readString(file))).thenApply(BsonDocument::parse); + } + } + + public static BsonValue translateJsonToBson(@Nonnull JsonElement element) { + return (BsonValue)(element.isJsonObject() ? BsonDocument.parse(element.toString()) : new BsonString(element.getAsString())); + } + + public static JsonElement translateBsonToJson(BsonDocument value) { + try { + JsonElement var2; + try (StringWriter writer = new StringWriter()) { + codec.encode(new JsonWriter(writer, SETTINGS), value, encoderContext); + var2 = JsonParser.parseString(writer.toString()); + } + + return var2; + } catch (IOException var6) { + throw new RuntimeException(var6); + } + } + + public static String toJson(BsonDocument document) { + StringWriter writer = new StringWriter(); + BSON_DOCUMENT_CODEC.encode(new JsonWriter(writer, SETTINGS), document, encoderContext); + return writer.toString(); + } + + public static void writeSync(@Nonnull Path path, @Nonnull Codec codec, T value, @Nonnull HytaleLogger logger) throws IOException { + Path parent = PathUtil.getParent(path); + if (!Files.exists(parent)) { + Files.createDirectories(parent); + } + + if (Files.isRegularFile(path)) { + Path resolve = path.resolveSibling(path.getFileName() + ".bak"); + Files.move(path, resolve, StandardCopyOption.REPLACE_EXISTING); + } + + ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get(); + BsonValue bsonValue = codec.encode(value, extraInfo); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(logger); + BsonDocument document = bsonValue.asDocument(); + + try (BufferedWriter writer = Files.newBufferedWriter(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) { + BSON_DOCUMENT_CODEC.encode(new JsonWriter(writer, SETTINGS), document, encoderContext); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/util/Config.java b/src/com/hypixel/hytale/server/core/util/Config.java new file mode 100644 index 0000000..4546b5d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/Config.java @@ -0,0 +1,72 @@ +package com.hypixel.hytale.server.core.util; + +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.util.RawJsonReader; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Config { + @Nonnull + private final Path path; + private final String name; + private final BuilderCodec codec; + @Nullable + private T config; + @Nullable + private CompletableFuture loadingConfig; + + public Config(@Nonnull Path path, String name, BuilderCodec codec) { + this.path = path.resolve(name + ".json"); + this.name = name; + this.codec = codec; + } + + @Nonnull + @Deprecated(forRemoval = true) + public static Config preloadedConfig(@Nonnull Path path, String name, BuilderCodec codec, T config) { + Config c = new Config<>(path, name, codec); + c.config = config; + return c; + } + + @Nonnull + public CompletableFuture load() { + if (this.loadingConfig != null) { + return this.loadingConfig; + } else if (!Files.exists(this.path)) { + this.config = this.codec.getDefaultValue(); + return CompletableFuture.completedFuture(this.config); + } else { + return this.loadingConfig = CompletableFuture.supplyAsync(SneakyThrow.sneakySupplier(() -> { + this.config = RawJsonReader.readSync(this.path, this.codec, HytaleLogger.getLogger()); + this.loadingConfig = null; + return this.config; + })); + } + } + + public T get() { + if (this.config == null && this.loadingConfig == null) { + throw new IllegalStateException("Config is not loaded"); + } else { + return this.loadingConfig != null ? this.loadingConfig.join() : this.config; + } + } + + @Nonnull + public CompletableFuture save() { + if (this.config == null && this.loadingConfig == null) { + throw new IllegalStateException("Config is not loaded"); + } else { + return this.loadingConfig != null + ? CompletableFuture.completedFuture(null) + : BsonUtil.writeDocument(this.path, this.codec.encode(this.config, new ExtraInfo())); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/util/ConsoleColorUtil.java b/src/com/hypixel/hytale/server/core/util/ConsoleColorUtil.java new file mode 100644 index 0000000..b939ba1 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/ConsoleColorUtil.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.server.core.util; + +public class ConsoleColorUtil { + public static final String BLACK = "\u001b[0;30m"; + public static final String RED = "\u001b[0;31m"; + public static final String GREEN = "\u001b[0;32m"; + public static final String YELLOW = "\u001b[0;33m"; + public static final String BLUE = "\u001b[0;34m"; + public static final String PURPLE = "\u001b[0;35m"; + public static final String CYAN = "\u001b[0;36m"; + public static final String WHITE = "\u001b[0;37m"; + + public ConsoleColorUtil() { + } +} diff --git a/src/com/hypixel/hytale/server/core/util/DumpUtil.java b/src/com/hypixel/hytale/server/core/util/DumpUtil.java new file mode 100644 index 0000000..d41e616 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/DumpUtil.java @@ -0,0 +1,1052 @@ +package com.hypixel.hytale.server.core.util; + +import com.hypixel.fastutil.longs.Long2ObjectConcurrentHashMap; +import com.hypixel.hytale.common.plugin.PluginManifest; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.metric.ArchetypeChunkData; +import com.hypixel.hytale.component.system.ISystem; +import com.hypixel.hytale.logger.backend.HytaleFileHandler; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.metrics.InitStackThread; +import com.hypixel.hytale.metrics.MetricsRegistry; +import com.hypixel.hytale.metrics.metric.HistoricMetric; +import com.hypixel.hytale.plugin.early.ClassTransformer; +import com.hypixel.hytale.plugin.early.EarlyPluginLoader; +import com.hypixel.hytale.protocol.io.PacketStatsRecorder; +import com.hypixel.hytale.protocol.packets.connection.PongType; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.HytaleServerConfig; +import com.hypixel.hytale.server.core.ShutdownReason; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.CameraManager; +import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementManager; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.PluginBase; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.universe.world.storage.provider.IndexedStorageChunkStorageProvider; +import com.hypixel.hytale.server.core.universe.world.worldgen.WorldGenTimingsCollector; +import com.hypixel.hytale.storage.IndexedStorageFile; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; +import java.io.BufferedWriter; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.lang.management.ClassLoadingMXBean; +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryUsage; +import java.lang.management.OperatingSystemMXBean; +import java.lang.management.RuntimeMXBean; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.bouncycastle.util.io.TeeOutputStream; +import org.bson.BsonArray; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.BsonValue; + +public class DumpUtil { + public DumpUtil() { + } + + @Nonnull + public static Path dumpToJson() throws IOException { + Map playerComponents = collectPlayerComponentMetrics(); + BsonDocument bson = HytaleServer.METRICS_REGISTRY.dumpToBson(HytaleServer.get()).asDocument(); + BsonDocument universeBson = Universe.METRICS_REGISTRY.dumpToBson(Universe.get()).asDocument(); + BsonArray playersArray = new BsonArray(); + + for (PlayerRef ref : Universe.get().getPlayers()) { + BsonDocument playerBson = PlayerRef.METRICS_REGISTRY.dumpToBson(ref).asDocument(); + BsonDocument componentData = playerComponents.get(ref.getUuid()); + if (componentData != null) { + playerBson.putAll(componentData); + } + + playersArray.add((BsonValue)playerBson); + } + + universeBson.put("Players", playersArray); + bson.put("Universe", universeBson); + BsonArray earlyPluginsArray = new BsonArray(); + + for (ClassTransformer transformer : EarlyPluginLoader.getTransformers()) { + earlyPluginsArray.add((BsonValue)(new BsonString(transformer.getClass().getName()))); + } + + bson.put("EarlyPlugins", earlyPluginsArray); + Path path = MetricsRegistry.createDumpPath(".dump.json"); + Files.writeString(path, BsonUtil.toJson(bson)); + return path; + } + + @Nonnull + private static Map collectPlayerComponentMetrics() { + ConcurrentHashMap result = new ConcurrentHashMap<>(); + Collection worlds = Universe.get().getWorlds().values(); + CompletableFuture[] futures = worlds.stream().map(world -> CompletableFuture.runAsync(() -> { + for (PlayerRef playerRef : world.getPlayerRefs()) { + BsonValue bson = PlayerRef.COMPONENT_METRICS_REGISTRY.dumpToBson(playerRef); + result.put(playerRef.getUuid(), bson.asDocument()); + } + }, world).orTimeout(30L, TimeUnit.SECONDS)).toArray(CompletableFuture[]::new); + CompletableFuture.allOf(futures).join(); + return result; + } + + @Nonnull + public static Map collectPlayerTextData() { + ConcurrentHashMap result = new ConcurrentHashMap<>(); + Collection worlds = Universe.get().getWorlds().values(); + CompletableFuture[] futures = worlds.stream() + .map( + world -> CompletableFuture.runAsync( + () -> { + for (PlayerRef playerRef : world.getPlayerRefs()) { + Ref entityRef = playerRef.getReference(); + if (entityRef != null) { + Store store = entityRef.getStore(); + MovementStatesComponent ms = store.getComponent(entityRef, MovementStatesComponent.getComponentType()); + MovementManager mm = store.getComponent(entityRef, MovementManager.getComponentType()); + CameraManager cm = store.getComponent(entityRef, CameraManager.getComponentType()); + result.put( + playerRef.getUuid(), + new DumpUtil.PlayerTextData( + playerRef.getUuid(), + ms != null ? ms.getMovementStates().toString() : null, + mm != null ? mm.toString() : null, + cm != null ? cm.toString() : null + ) + ); + } + } + }, + world + ) + .orTimeout(30L, TimeUnit.SECONDS) + ) + .toArray(CompletableFuture[]::new); + CompletableFuture.allOf(futures).join(); + return result; + } + + @Nonnull + public static String hexDump(@Nonnull ByteBuf buf) { + int readerIndex = buf.readerIndex(); + byte[] data = new byte[buf.readableBytes()]; + buf.readBytes(data); + buf.readerIndex(readerIndex); + return hexDump(data); + } + + @Nonnull + public static String hexDump(@Nonnull byte[] data) { + return data.length == 0 ? "[EMPTY ARRAY]" : ByteBufUtil.hexDump(data); + } + + @Nonnull + public static Path dump(boolean crash, boolean printToConsole) { + Path filePath = createDumpPath(crash, "dump.txt"); + FileOutputStream fileOutputStream = null; + + OutputStream outputStream; + try { + fileOutputStream = new FileOutputStream(filePath.toFile()); + if (printToConsole) { + outputStream = new TeeOutputStream(fileOutputStream, System.err); + } else { + outputStream = fileOutputStream; + } + } catch (IOException var13) { + var13.printStackTrace(); + System.err.println(); + System.err.println("FAILED TO GET OUTPUT STREAM FOR " + filePath); + System.err.println("FAILED TO GET OUTPUT STREAM FOR " + filePath); + System.err.println(); + outputStream = System.err; + } + + try { + write(new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)), true)); + } finally { + if (fileOutputStream != null) { + try { + fileOutputStream.close(); + } catch (IOException var12) { + } + } + } + + return filePath; + } + + @Nonnull + public static Path createDumpPath(boolean crash, String ext) { + Path path = Paths.get("dumps"); + + try { + if (!Files.exists(path)) { + Files.createDirectories(path); + } + } catch (IOException var6) { + var6.printStackTrace(); + } + + String name = (crash ? "crash-" : "") + HytaleFileHandler.LOG_FILE_DATE_FORMAT.format(LocalDateTime.now()); + Path filePath = path.resolve(name + "." + ext); + int i = 0; + + while (Files.exists(filePath)) { + filePath = path.resolve(name + "_" + i++ + "." + ext); + } + + return filePath; + } + + private static void write(@Nonnull PrintWriter writer) { + int width = 200; + int height = 20; + long startNanos = System.nanoTime(); + section( + "Summary", + () -> { + Universe universe = Universe.get(); + writer.println("World Count: " + universe.getWorlds().size()); + + for (World world : universe.getWorlds().values()) { + writer.println("- " + world.getName()); + HistoricMetric metrics = world.getBufferedTickLengthMetricSet(); + long[] periodsNanos = metrics.getPeriodsNanos(); + int periodIndex = periodsNanos.length - 1; + long lastTime = periodsNanos[periodIndex]; + double average = metrics.getAverage(periodIndex); + long max = metrics.calculateMax(periodIndex); + long min = metrics.calculateMin(periodIndex); + String length = FormatUtil.timeUnitToString(lastTime, TimeUnit.NANOSECONDS, true); + String value = FormatUtil.simpleTimeUnitFormat(min, average, max, TimeUnit.NANOSECONDS, TimeUnit.MILLISECONDS, 3); + String limit = FormatUtil.simpleTimeUnitFormat(world.getTickStepNanos(), TimeUnit.NANOSECONDS, 3); + writer.printf("\tTick (%s): %s (Limit: %s)\n", length, value, limit); + writer.printf("\tPlayer count: %d\n", world.getPlayerCount()); + } + + writer.println("Player count: " + universe.getPlayerCount()); + + for (PlayerRef ref : universe.getPlayers()) { + writer.printf("- %s (%s)\n", ref.getUsername(), ref.getUuid()); + PacketHandler.PingInfo pingInfo = ref.getPacketHandler().getPingInfo(PongType.Raw); + HistoricMetric pingMetricSet = pingInfo.getPingMetricSet(); + long min = pingMetricSet.calculateMin(1); + long average = (long)pingMetricSet.getAverage(1); + long max = pingMetricSet.calculateMax(1); + writer.println( + "\tPing(raw) Min: " + + FormatUtil.timeUnitToString(min, PacketHandler.PingInfo.TIME_UNIT) + + ", Avg: " + + FormatUtil.timeUnitToString(average, PacketHandler.PingInfo.TIME_UNIT) + + ", Max: " + + FormatUtil.timeUnitToString(max, PacketHandler.PingInfo.TIME_UNIT) + ); + } + }, + writer + ); + section("Server Lifecycle", () -> { + HytaleServer server = HytaleServer.get(); + writer.println("Boot Timestamp: " + server.getBoot()); + writer.println("Boot Start (nanos): " + server.getBootStart()); + writer.println("Booting: " + server.isBooting()); + writer.println("Booted: " + server.isBooted()); + writer.println("Shutting Down: " + server.isShuttingDown()); + ShutdownReason shutdownReason = server.getShutdownReason(); + if (shutdownReason != null) { + writer.println("Shutdown Reason: " + shutdownReason); + } + }, writer); + section("Early Plugins", () -> { + List transformers = EarlyPluginLoader.getTransformers(); + writer.println("Class Transformer Count: " + transformers.size()); + + for (ClassTransformer transformer : transformers) { + writer.println("- " + transformer.getClass().getName() + " (priority=" + transformer.priority() + ")"); + } + }, writer); + section("Plugins", () -> { + List plugins = HytaleServer.get().getPluginManager().getPlugins(); + writer.println("Plugin Count: " + plugins.size()); + + for (PluginBase plugin : plugins) { + boolean isBuiltin = plugin instanceof JavaPlugin javaPlugin && javaPlugin.getClassLoader().isInServerClassPath(); + writer.println("- " + plugin.getIdentifier() + (isBuiltin ? " [Builtin]" : " [External]")); + writer.println("\tType: " + plugin.getType().getDisplayName()); + writer.println("\tState: " + plugin.getState()); + writer.println("\tManifest:"); + BsonDocument manifestBson = PluginManifest.CODEC.encode(plugin.getManifest()).asDocument(); + printIndented(writer, BsonUtil.toJson(manifestBson), "\t\t"); + } + }, writer); + section("Server Config", () -> { + HytaleServerConfig config = HytaleServer.get().getConfig(); + BsonDocument bson = HytaleServerConfig.CODEC.encode(config).asDocument(); + printIndented(writer, BsonUtil.toJson(bson), "\t"); + }, writer); + Map playerTextData = collectPlayerTextData(); + section( + "Server Info", + () -> { + writer.println("HytaleServer:"); + writer.println("\t- " + HytaleServer.get()); + writer.println("\tBooted: " + HytaleServer.get().isBooting()); + writer.println("\tShutting Down: " + HytaleServer.get().isShuttingDown()); + writer.println(); + writer.println("Worlds: "); + Map worlds = Universe.get().getWorlds(); + worlds.forEach( + (worldName, world) -> { + writer.println("- " + worldName + ":"); + writer.println("\t" + world); + HistoricMetric bufferedDeltaMetricSet = world.getBufferedTickLengthMetricSet(); + long[] periods = bufferedDeltaMetricSet.getPeriodsNanos(); + + for (int i = 0; i < periods.length; i++) { + long period = periods[i]; + String historyLengthFormatted = FormatUtil.timeUnitToString(period, TimeUnit.NANOSECONDS, true); + double average = bufferedDeltaMetricSet.getAverage(i); + long minxx = bufferedDeltaMetricSet.calculateMin(i); + long maxxx = bufferedDeltaMetricSet.calculateMax(i); + writer.println( + "\tTick (" + + historyLengthFormatted + + "): Min: " + + FormatUtil.simpleTimeUnitFormat(minxx, TimeUnit.NANOSECONDS, 3) + + ", Avg: " + + FormatUtil.simpleTimeUnitFormat(Math.round(average), TimeUnit.NANOSECONDS, 3) + + "ns, Max: " + + FormatUtil.simpleTimeUnitFormat(maxxx, TimeUnit.NANOSECONDS, 3) + ); + long[] historyTimestamps = bufferedDeltaMetricSet.getTimestamps(i); + long[] historyValues = bufferedDeltaMetricSet.getValues(i); + StringBuilder sb = new StringBuilder(); + sb.append("\tTick Graph ").append(historyLengthFormatted).append(":\n"); + StringUtil.generateGraph( + sb, + width, + height, + startNanos - period, + startNanos, + 0.0, + Math.max(maxxx, (long)world.getTickStepNanos()), + value -> FormatUtil.simpleTimeUnitFormat(MathUtil.fastCeil(value), TimeUnit.NANOSECONDS, 3), + historyTimestamps.length, + ii -> historyTimestamps[ii], + ii -> historyValues[ii] + ); + writer.println(sb); + } + + writer.println("\tPlayers: "); + + for (Player player : world.getPlayers()) { + writer.println("\t- " + player); + DumpUtil.PlayerTextData playerData = playerTextData.get(player.getUuid()); + writer.println("\t\tMovement States: " + (playerData != null ? playerData.movementStates() : "N/A")); + writer.println("\t\tMovement Manager: " + (playerData != null ? playerData.movementManager() : "N/A")); + writer.println("\t\tPage Manager: " + player.getPageManager()); + writer.println("\t\tHud Manager: " + player.getHudManager()); + writer.println("\t\tCamera Manager: " + (playerData != null ? playerData.cameraManager() : "N/A")); + writer.println("\t\tChunk Tracker:"); + + for (String line : player.getPlayerRef().getChunkTracker().getLoadedChunksDebug().split("\n")) { + writer.println("\t\t\t" + line); + } + + writer.println("\t\tQueued Packets Count: " + player.getPlayerConnection().getQueuedPacketsCount()); + writer.println("\t\tPing:"); + + for (PongType pongType : PongType.values()) { + PacketHandler.PingInfo pingInfox = player.getPlayerConnection().getPingInfo(pongType); + writer.println("\t\t- " + pongType.name() + ":"); + HistoricMetric pingMetricSetx = pingInfox.getPingMetricSet(); + long average = (long)pingMetricSetx.getAverage(1); + long minx = pingMetricSetx.calculateMin(1); + long maxxxx = pingMetricSetx.calculateMax(1); + writer.println("\t\t\tPing: Min: " + minx + ", Avg: " + average + ", Max: " + maxxxx); + writer.println( + "\t\t\t Min: " + + FormatUtil.timeUnitToString(minx, PacketHandler.PingInfo.TIME_UNIT) + + ", Avg: " + + FormatUtil.timeUnitToString(average, PacketHandler.PingInfo.TIME_UNIT) + + ", Max: " + + FormatUtil.timeUnitToString(maxxxx, PacketHandler.PingInfo.TIME_UNIT) + ); + long[] pingPeriods = pingMetricSetx.getPeriodsNanos(); + + for (int i = 0; i < pingPeriods.length; i++) { + minx = pingPeriods[i]; + maxxxx = pingMetricSetx.calculateMin(1); + long maxx = pingMetricSetx.calculateMax(1); + long[] historyTimestamps = pingMetricSetx.getTimestamps(i); + long[] historyValues = pingMetricSetx.getValues(i); + String historyLengthFormatted = FormatUtil.timeUnitToString(minx, TimeUnit.NANOSECONDS, true); + StringBuilder sb = new StringBuilder(); + sb.append("\t\t\tPing Graph ").append(historyLengthFormatted).append(":\n"); + StringUtil.generateGraph( + sb, + width, + height, + startNanos - minx, + startNanos, + maxxxx, + maxx, + value -> FormatUtil.timeUnitToString(MathUtil.fastCeil(value), PacketHandler.PingInfo.TIME_UNIT), + historyTimestamps.length, + ii -> historyTimestamps[ii], + ii -> historyValues[ii] + ); + writer.println(sb); + } + + writer.println( + "\t\t\tPacket Queue: Min: " + + pingInfox.getPacketQueueMetric().getMin() + + ", Avg: " + + (long)pingInfox.getPacketQueueMetric().getAverage() + + ", Max: " + + pingInfox.getPacketQueueMetric().getMax() + ); + } + + writer.println(); + PacketStatsRecorder recorder = player.getPlayerConnection().getPacketStatsRecorder(); + if (recorder != null) { + int recentSeconds = 30; + long totalSentCount = 0L; + long totalSentUncompressed = 0L; + long totalSentWire = 0L; + int recentSentCount = 0; + long recentSentUncompressed = 0L; + long recentSentWire = 0L; + writer.println("\t\tPackets Sent:"); + + for (int i = 0; i < 512; i++) { + PacketStatsRecorder.PacketStatsEntry entry = recorder.getEntry(i); + if (entry.getSentCount() > 0) { + totalSentCount += entry.getSentCount(); + totalSentUncompressed += entry.getSentUncompressedTotal(); + totalSentWire += entry.getSentCompressedTotal() > 0L ? entry.getSentCompressedTotal() : entry.getSentUncompressedTotal(); + writer.println("\t\t\t" + entry.getName() + " (" + i + "):"); + printPacketStats( + writer, + "\t\t\t\t", + "Total", + entry.getSentCount(), + entry.getSentUncompressedTotal(), + entry.getSentCompressedTotal(), + entry.getSentUncompressedMin(), + entry.getSentUncompressedMax(), + entry.getSentCompressedMin(), + entry.getSentCompressedMax(), + entry.getSentUncompressedAvg(), + entry.getSentCompressedAvg(), + 0 + ); + PacketStatsRecorder.RecentStats recent = entry.getSentRecently(); + if (recent.count() > 0) { + recentSentCount += recent.count(); + recentSentUncompressed += recent.uncompressedTotal(); + recentSentWire += recent.compressedTotal() > 0L ? recent.compressedTotal() : recent.uncompressedTotal(); + printPacketStats( + writer, + "\t\t\t\t", + "Recent", + recent.count(), + recent.uncompressedTotal(), + recent.compressedTotal(), + recent.uncompressedMin(), + recent.uncompressedMax(), + recent.compressedMin(), + recent.compressedMax(), + (double)recent.uncompressedTotal() / recent.count(), + recent.compressedTotal() > 0L ? (double)recent.compressedTotal() / recent.count() : 0.0, + recentSeconds + ); + } + } + } + + writer.println("\t\t\t--- Summary ---"); + writer.println( + "\t\t\t\tTotal: " + + totalSentCount + + " packets, " + + FormatUtil.bytesToString(totalSentUncompressed) + + " serialized, " + + FormatUtil.bytesToString(totalSentWire) + + " wire" + ); + if (recentSentCount > 0) { + writer.println( + String.format( + "\t\t\t\tRecent: %d packets (%.1f/sec), %s serialized, %s wire", + recentSentCount, + (double)recentSentCount / recentSeconds, + FormatUtil.bytesToString(recentSentUncompressed), + FormatUtil.bytesToString(recentSentWire) + ) + ); + } + + writer.println(); + long totalRecvCount = 0L; + long totalRecvUncompressed = 0L; + long totalRecvWire = 0L; + int recentRecvCount = 0; + long recentRecvUncompressed = 0L; + long recentRecvWire = 0L; + writer.println("\t\tPackets Received:"); + + for (int ix = 0; ix < 512; ix++) { + PacketStatsRecorder.PacketStatsEntry entry = recorder.getEntry(ix); + if (entry.getReceivedCount() > 0) { + totalRecvCount += entry.getReceivedCount(); + totalRecvUncompressed += entry.getReceivedUncompressedTotal(); + totalRecvWire += entry.getReceivedCompressedTotal() > 0L + ? entry.getReceivedCompressedTotal() + : entry.getReceivedUncompressedTotal(); + writer.println("\t\t\t" + entry.getName() + " (" + ix + "):"); + printPacketStats( + writer, + "\t\t\t\t", + "Total", + entry.getReceivedCount(), + entry.getReceivedUncompressedTotal(), + entry.getReceivedCompressedTotal(), + entry.getReceivedUncompressedMin(), + entry.getReceivedUncompressedMax(), + entry.getReceivedCompressedMin(), + entry.getReceivedCompressedMax(), + entry.getReceivedUncompressedAvg(), + entry.getReceivedCompressedAvg(), + 0 + ); + PacketStatsRecorder.RecentStats recent = entry.getReceivedRecently(); + if (recent.count() > 0) { + recentRecvCount += recent.count(); + recentRecvUncompressed += recent.uncompressedTotal(); + recentRecvWire += recent.compressedTotal() > 0L ? recent.compressedTotal() : recent.uncompressedTotal(); + printPacketStats( + writer, + "\t\t\t\t", + "Recent", + recent.count(), + recent.uncompressedTotal(), + recent.compressedTotal(), + recent.uncompressedMin(), + recent.uncompressedMax(), + recent.compressedMin(), + recent.compressedMax(), + (double)recent.uncompressedTotal() / recent.count(), + recent.compressedTotal() > 0L ? (double)recent.compressedTotal() / recent.count() : 0.0, + recentSeconds + ); + } + } + } + + writer.println("\t\t\t--- Summary ---"); + writer.println( + "\t\t\t\tTotal: " + + totalRecvCount + + " packets, " + + FormatUtil.bytesToString(totalRecvUncompressed) + + " serialized, " + + FormatUtil.bytesToString(totalRecvWire) + + " wire" + ); + if (recentRecvCount > 0) { + writer.println( + String.format( + "\t\t\t\tRecent: %d packets (%.1f/sec), %s serialized, %s wire", + recentRecvCount, + (double)recentRecvCount / recentSeconds, + FormatUtil.bytesToString(recentRecvUncompressed), + FormatUtil.bytesToString(recentRecvWire) + ) + ); + } + + writer.println(); + } + } + + writer.println("\tComponent Stores:"); + + try { + CompletableFuture.runAsync(() -> { + printComponentStore(writer, width, height, "Chunks", startNanos, world.getChunkStore().getStore()); + printComponentStore(writer, width, height, "Entities", startNanos, world.getEntityStore().getStore()); + }, world).orTimeout(30L, TimeUnit.SECONDS).join(); + } catch (CompletionException var41) { + if (!(var41.getCause() instanceof TimeoutException)) { + var41.printStackTrace(); + writer.println("\t\tFAILED TO DUMP COMPONENT STORES! EXCEPTION!"); + } else { + writer.println("\t\tFAILED TO DUMP COMPONENT STORES! TIMEOUT!"); + } + } + + writer.println(); + writer.println(); + WorldGenTimingsCollector timings = world.getChunkStore().getGenerator().getTimings(); + writer.println("\tWorld Gen Timings: "); + if (timings != null) { + writer.println("\t\tChunk Count: " + timings.getChunkCounter()); + writer.println("\t\tChunk Time: " + timings.getChunkTime()); + writer.println("\t\tZone Biome Result Time: " + timings.zoneBiomeResult()); + writer.println("\t\tPrepare Time: " + timings.prepare()); + writer.println("\t\tBlock Generation Time: " + timings.blocksGeneration()); + writer.println("\t\tCave Generation Time: " + timings.caveGeneration()); + writer.println("\t\tPrefab Generation: " + timings.prefabGeneration()); + writer.println("\t\tQueue Length: " + timings.getQueueLength()); + writer.println("\t\tGenerating Count: " + timings.getGeneratingCount()); + } else { + writer.println("\t\tNo Timings Data Collected!"); + } + + IndexedStorageChunkStorageProvider.IndexedStorageCache storageCache = world.getChunkStore() + .getStore() + .getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType()); + if (storageCache != null) { + Long2ObjectConcurrentHashMap cache = storageCache.getCache(); + writer.println(); + writer.println("\tIndexed Storage Cache:"); + + for (Entry entry : cache.long2ObjectEntrySet()) { + long key = entry.getLongKey(); + writer.println("\t\t" + ChunkUtil.xOfChunkIndex(key) + ", " + ChunkUtil.zOfChunkIndex(key)); + IndexedStorageFile storageFile = entry.getValue(); + + try { + writer.println("\t\t- Size: " + FormatUtil.bytesToString(storageFile.size())); + } catch (IOException var40) { + writer.println("\t\t- Size: ERROR: " + var40.getMessage()); + } + + writer.println("\t\t- Blob Count: " + storageFile.keys().size()); + int segmentSize = storageFile.segmentSize(); + int segmentCount = storageFile.segmentCount(); + writer.println("\t\t- Segment Size: " + segmentSize); + writer.println("\t\t- Segment Count: " + segmentCount); + writer.println("\t\t- Segment Used %: " + (double)(segmentCount * 100) / segmentSize + "%"); + writer.println("\t\t- " + storageFile); + } + } + } + ); + List playersNotInWorld = Universe.get().getPlayers().stream().filter(refx -> refx.getReference() == null).toList(); + if (!playersNotInWorld.isEmpty()) { + writer.println(); + writer.println("Players not in world (" + playersNotInWorld.size() + "):"); + + for (PlayerRef ref : playersNotInWorld) { + writer.println("- " + ref.getUsername() + " (" + ref.getUuid() + ")"); + writer.println("\tQueued Packets: " + ref.getPacketHandler().getQueuedPacketsCount()); + PacketHandler.PingInfo pingInfo = ref.getPacketHandler().getPingInfo(PongType.Raw); + HistoricMetric pingMetricSet = pingInfo.getPingMetricSet(); + long min = pingMetricSet.calculateMin(1); + long avg = (long)pingMetricSet.getAverage(1); + long max = pingMetricSet.calculateMax(1); + writer.println( + "\tPing(raw): Min: " + + FormatUtil.timeUnitToString(min, PacketHandler.PingInfo.TIME_UNIT) + + ", Avg: " + + FormatUtil.timeUnitToString(avg, PacketHandler.PingInfo.TIME_UNIT) + + ", Max: " + + FormatUtil.timeUnitToString(max, PacketHandler.PingInfo.TIME_UNIT) + ); + } + } + }, + writer + ); + section( + "System info", + () -> { + RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); + OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean(); + long currentTimeMillis = System.currentTimeMillis(); + writer.println("Start Time: " + new Date(runtimeMXBean.getStartTime()) + " (" + runtimeMXBean.getStartTime() + "ms)"); + writer.println("Current Time: " + new Date(currentTimeMillis) + " (" + currentTimeMillis + "ms)"); + writer.println( + "Process Uptime: " + FormatUtil.timeUnitToString(runtimeMXBean.getUptime(), TimeUnit.MILLISECONDS) + " (" + runtimeMXBean.getUptime() + "ms)" + ); + writer.println( + "Available processors (cores): " + Runtime.getRuntime().availableProcessors() + " - " + operatingSystemMXBean.getAvailableProcessors() + ); + writer.println("System Load Average: " + operatingSystemMXBean.getSystemLoadAverage()); + writer.println(); + if (operatingSystemMXBean instanceof com.sun.management.OperatingSystemMXBean sunOSBean) { + writer.println( + "Total Physical Memory: " + + FormatUtil.bytesToString(sunOSBean.getTotalPhysicalMemorySize()) + + " (" + + sunOSBean.getTotalPhysicalMemorySize() + + " Bytes)" + ); + writer.println( + "Free Physical Memory: " + + FormatUtil.bytesToString(sunOSBean.getFreePhysicalMemorySize()) + + " (" + + sunOSBean.getFreePhysicalMemorySize() + + " Bytes)" + ); + writer.println( + "Total Swap Memory: " + FormatUtil.bytesToString(sunOSBean.getTotalSwapSpaceSize()) + " (" + sunOSBean.getTotalSwapSpaceSize() + " Bytes)" + ); + writer.println( + "Free Swap Memory: " + FormatUtil.bytesToString(sunOSBean.getFreeSwapSpaceSize()) + " (" + sunOSBean.getFreeSwapSpaceSize() + " Bytes)" + ); + writer.println("System CPU Load: " + sunOSBean.getSystemCpuLoad()); + writer.println("Process CPU Load: " + sunOSBean.getProcessCpuLoad()); + writer.println(); + } + + writer.println("Processor Identifier: " + System.getenv("PROCESSOR_IDENTIFIER")); + writer.println("Processor Architecture: " + System.getenv("PROCESSOR_ARCHITECTURE")); + writer.println("Processor Architecture W64/32: " + System.getenv("PROCESSOR_ARCHITEW6432")); + writer.println("Number of Processors: " + System.getenv("NUMBER_OF_PROCESSORS")); + writer.println(); + writer.println("Runtime Name: " + runtimeMXBean.getName()); + writer.println(); + writer.println("OS Name: " + operatingSystemMXBean.getName()); + writer.println("OS Arch: " + operatingSystemMXBean.getArch()); + writer.println("OS Version: " + operatingSystemMXBean.getVersion()); + writer.println(); + writer.println("Spec Name: " + runtimeMXBean.getSpecName()); + writer.println("Spec Vendor: " + runtimeMXBean.getSpecVendor()); + writer.println("Spec Version: " + runtimeMXBean.getSpecVersion()); + writer.println(); + writer.println("VM Name: " + runtimeMXBean.getVmName()); + writer.println("VM Vendor: " + runtimeMXBean.getVmVendor()); + writer.println("VM Version: " + runtimeMXBean.getVmVersion()); + writer.println(); + writer.println("Management Spec Version: " + runtimeMXBean.getManagementSpecVersion()); + writer.println(); + writer.println("Library Path: " + runtimeMXBean.getLibraryPath()); + + try { + writer.println("Boot ClassPath: " + runtimeMXBean.getBootClassPath()); + } catch (UnsupportedOperationException var6x) { + } + + writer.println("ClassPath: " + runtimeMXBean.getClassPath()); + writer.println(); + writer.println("Input Arguments: " + runtimeMXBean.getInputArguments()); + writer.println("System Properties: " + runtimeMXBean.getSystemProperties()); + }, + writer + ); + section("Current process info", () -> { + MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + writeMemoryUsage(writer, "Heap Memory Usage: ", memoryMXBean.getHeapMemoryUsage()); + writeMemoryUsage(writer, "Non-Heap Memory Usage: ", memoryMXBean.getNonHeapMemoryUsage()); + writer.println("Objects Pending Finalization Count: " + memoryMXBean.getObjectPendingFinalizationCount()); + }, writer); + section("Garbage collector", () -> { + for (GarbageCollectorMXBean garbageCollectorMXBean : ManagementFactory.getGarbageCollectorMXBeans()) { + writer.println("Name: " + garbageCollectorMXBean.getName()); + writer.println("\tMemory Pool Names: " + Arrays.toString((Object[])garbageCollectorMXBean.getMemoryPoolNames())); + writer.println("\tCollection Count: " + garbageCollectorMXBean.getCollectionCount()); + writer.println("\tCollection Time: " + garbageCollectorMXBean.getCollectionTime()); + writer.println(); + } + }, writer); + section("Memory pools", () -> { + for (MemoryPoolMXBean memoryPoolMXBean : ManagementFactory.getMemoryPoolMXBeans()) { + writer.println("Name: " + memoryPoolMXBean.getName()); + writer.println("\tType: " + memoryPoolMXBean.getType()); + writer.println("\tPeak Usage: " + memoryPoolMXBean.getPeakUsage()); + writer.println("\tUsage: " + memoryPoolMXBean.getUsage()); + writer.println("\tUsage Threshold Supported: " + memoryPoolMXBean.isUsageThresholdSupported()); + if (memoryPoolMXBean.isUsageThresholdSupported()) { + writer.println("\tUsage Threshold: " + memoryPoolMXBean.getUsageThreshold()); + writer.println("\tUsage Threshold Count: " + memoryPoolMXBean.getUsageThresholdCount()); + writer.println("\tUsage Threshold Exceeded: " + memoryPoolMXBean.isUsageThresholdExceeded()); + } + + writer.println("\tCollection Usage: " + memoryPoolMXBean.getCollectionUsage()); + writer.println("\tCollection Usage Threshold Supported: " + memoryPoolMXBean.isCollectionUsageThresholdSupported()); + if (memoryPoolMXBean.isCollectionUsageThresholdSupported()) { + writer.println("\tCollection Usage Threshold: " + memoryPoolMXBean.getCollectionUsageThreshold()); + writer.println("\tCollection Usage Threshold Count: " + memoryPoolMXBean.getCollectionUsageThresholdCount()); + writer.println("\tCollection Usage Threshold Exceeded: " + memoryPoolMXBean.isCollectionUsageThresholdExceeded()); + } + + writer.println(); + } + }, writer); + ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(true, true); + section("Threads (Count: " + threadInfos.length + ")", () -> { + Map allStackTraces = Thread.getAllStackTraces(); + Long2ObjectMap threadIdMap = new Long2ObjectOpenHashMap<>(); + + for (Thread thread : allStackTraces.keySet()) { + threadIdMap.put(thread.getId(), thread); + } + + for (ThreadInfo threadInfo : threadInfos) { + Thread thread = threadIdMap.get(threadInfo.getThreadId()); + if (thread == null) { + writer.println("Failed to find thread!!!"); + } else { + writer.println("Name: " + thread.getName()); + writer.println("State: " + threadInfo.getThreadState()); + writer.println("Thread Class: " + thread.getClass()); + writer.println("Thread Group: " + thread.getThreadGroup()); + writer.println("Priority: " + thread.getPriority()); + writer.println("CPU Time: " + threadMXBean.getThreadCpuTime(threadInfo.getThreadId())); + writer.println("Waited Time: " + threadInfo.getWaitedTime()); + writer.println("Waited Count: " + threadInfo.getWaitedCount()); + writer.println("Blocked Time: " + threadInfo.getBlockedTime()); + writer.println("Blocked Count: " + threadInfo.getBlockedCount()); + writer.println("Lock Name: " + threadInfo.getLockName()); + writer.println("Lock Owner Id: " + threadInfo.getLockOwnerId()); + writer.println("Lock Owner Name: " + threadInfo.getLockOwnerName()); + writer.println("Daemon: " + thread.isDaemon()); + writer.println("Interrupted: " + thread.isInterrupted()); + writer.println("Uncaught Exception Handler: " + thread.getUncaughtExceptionHandler().getClass()); + if (thread instanceof InitStackThread) { + writer.println("Init Stack: "); + StackTraceElement[] trace = ((InitStackThread)thread).getInitStack(); + + for (StackTraceElement traceElement : trace) { + writer.println("\tat " + traceElement); + } + } + + writer.println("Current Stack: "); + StackTraceElement[] trace = allStackTraces.get(thread); + + for (StackTraceElement traceElement : trace) { + writer.println("\tat " + traceElement); + } + } + + writer.println(threadInfo); + } + }, writer); + section("Security Manager", () -> { + SecurityManager securityManager = System.getSecurityManager(); + if (securityManager != null) { + writer.println("Class: " + securityManager.getClass().getName()); + } else { + writer.println("No Security Manager found!"); + } + }, writer); + section("Classes", () -> { + ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean(); + writer.println("Loaded Class Count: " + classLoadingMXBean.getLoadedClassCount()); + writer.println("Unloaded Class Count: " + classLoadingMXBean.getUnloadedClassCount()); + writer.println("Total Loaded Class Count: " + classLoadingMXBean.getTotalLoadedClassCount()); + }, writer); + section("System Classloader", () -> { + ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); + writeClassLoader(writer, systemClassLoader); + }, writer); + section("DumpUtil Classloader", () -> { + ClassLoader systemClassLoader = DumpUtil.class.getClassLoader(); + writeClassLoader(writer, systemClassLoader); + }, writer); + } + + private static void printPacketStats( + @Nonnull PrintWriter writer, + @Nonnull String indent, + @Nonnull String label, + int count, + long uncompressedTotal, + long compressedTotal, + long uncompressedMin, + long uncompressedMax, + long compressedMin, + long compressedMax, + double uncompressedAvg, + double compressedAvg, + int recentSeconds + ) { + StringBuilder sb = new StringBuilder(); + sb.append(label).append(": ").append(count).append(" packet").append(count != 1 ? "s" : ""); + if (recentSeconds > 0) { + sb.append(String.format(" (%.1f/sec)", (double)count / recentSeconds)); + } + + sb.append("\n").append(indent).append(" Size: ").append(FormatUtil.bytesToString(uncompressedTotal)); + if (compressedTotal > 0L) { + sb.append(" -> ").append(FormatUtil.bytesToString(compressedTotal)).append(" wire"); + double ratio = 100.0 * compressedTotal / uncompressedTotal; + sb.append(String.format(" (%.1f%%)", ratio)); + } + + sb.append("\n").append(indent).append(" Avg: ").append(FormatUtil.bytesToString((long)uncompressedAvg)); + if (compressedAvg > 0.0) { + sb.append(" -> ").append(FormatUtil.bytesToString((long)compressedAvg)).append(" wire"); + } + + sb.append("\n") + .append(indent) + .append(" Range: ") + .append(FormatUtil.bytesToString(uncompressedMin)) + .append(" - ") + .append(FormatUtil.bytesToString(uncompressedMax)); + if (compressedMax > 0L) { + sb.append(" (wire: ").append(FormatUtil.bytesToString(compressedMin)).append(" - ").append(FormatUtil.bytesToString(compressedMax)).append(")"); + } + + writer.println(indent + sb); + } + + private static void printComponentStore(@Nonnull PrintWriter writer, int width, int height, String name, long startNanos, @Nonnull Store componentStore) { + writer.println("\t- " + name + ":"); + writer.println("\t Archetype Chunk Count: " + componentStore.getArchetypeChunkCount()); + writer.println("\t Entity Count: " + componentStore.getEntityCount()); + ComponentRegistry.Data data = componentStore.getRegistry().getData(); + HistoricMetric[] systemMetrics = componentStore.getSystemMetrics(); + + for (int systemIndex = 0; systemIndex < data.getSystemSize(); systemIndex++) { + ISystem system = data.getSystem(systemIndex); + HistoricMetric systemMetric = systemMetrics[systemIndex]; + writer.println("\t\t " + system.getClass().getName()); + writer.println("\t\t " + system); + writer.println("\t\t Archetype Chunk Count: " + componentStore.getArchetypeChunkCountFor(systemIndex)); + writer.println("\t\t Entity Count: " + componentStore.getEntityCountFor(systemIndex)); + if (systemMetric != null) { + long[] periods = systemMetric.getPeriodsNanos(); + + for (int i = 0; i < periods.length; i++) { + long period = periods[i]; + String historyLengthFormatted = FormatUtil.timeUnitToString(period, TimeUnit.NANOSECONDS, true); + double average = systemMetric.getAverage(i); + long min = systemMetric.calculateMin(i); + long max = systemMetric.calculateMax(i); + writer.println( + "\t\t\t(" + + historyLengthFormatted + + "): Min: " + + FormatUtil.timeUnitToString(min, TimeUnit.NANOSECONDS) + + ", Avg: " + + FormatUtil.timeUnitToString((long)average, TimeUnit.NANOSECONDS) + + ", Max: " + + FormatUtil.timeUnitToString(max, TimeUnit.NANOSECONDS) + ); + long[] historyTimestamps = systemMetric.getTimestamps(i); + long[] historyValues = systemMetric.getValues(i); + StringBuilder sb = new StringBuilder(); + StringUtil.generateGraph( + sb, + width, + height, + startNanos - period, + startNanos, + min, + max, + value -> FormatUtil.timeUnitToString(MathUtil.fastCeil(value), TimeUnit.NANOSECONDS), + historyTimestamps.length, + ii -> historyTimestamps[ii], + ii -> historyValues[ii] + ); + writer.println(sb); + } + } + } + + writer.println("\t\t Archetype Chunks:"); + + for (ArchetypeChunkData chunkData : componentStore.collectArchetypeChunkData()) { + writer.println("\t\t\t- Entities: " + chunkData.getEntityCount() + ", Components: " + Arrays.toString((Object[])chunkData.getComponentTypes())); + } + } + + private static void section(String name, @Nonnull Runnable runnable, @Nonnull PrintWriter writer) { + writer.println("**** " + name + " ****"); + + try { + runnable.run(); + } catch (Throwable var4) { + new RuntimeException("Failed to get data for section: " + name, var4).printStackTrace(writer); + } + + writer.println(); + writer.println(); + } + + private static void printIndented(@Nonnull PrintWriter writer, @Nonnull String text, @Nonnull String indent) { + for (String line : text.split("\n")) { + writer.println(indent + line); + } + } + + private static void writeMemoryUsage(@Nonnull PrintWriter writer, String title, @Nonnull MemoryUsage memoryUsage) { + writer.println(title); + writer.println("\tInit: " + FormatUtil.bytesToString(memoryUsage.getInit()) + " (" + memoryUsage.getInit() + " Bytes)"); + writer.println("\tUsed: " + FormatUtil.bytesToString(memoryUsage.getUsed()) + " (" + memoryUsage.getUsed() + " Bytes)"); + writer.println("\tCommitted: " + FormatUtil.bytesToString(memoryUsage.getCommitted()) + " (" + memoryUsage.getCommitted() + " Bytes)"); + long max = memoryUsage.getMax(); + if (max > 0L) { + writer.println("\tMax: " + FormatUtil.bytesToString(max) + " (" + max + " Bytes)"); + long free = max - memoryUsage.getCommitted(); + writer.println("\tFree: " + FormatUtil.bytesToString(free) + " (" + free + " Bytes)"); + } + } + + private static void writeClassLoader(@Nonnull PrintWriter writer, @Nullable ClassLoader systemClassLoader) { + if (systemClassLoader != null) { + writer.println("Class: " + systemClassLoader.getClass().getName()); + + while (systemClassLoader.getParent() != null) { + systemClassLoader = systemClassLoader.getParent(); + writer.println(" - Parent: " + systemClassLoader.getClass().getName()); + } + } else { + writer.println("No class loader found!"); + } + } + + public record PlayerTextData(@Nonnull UUID uuid, @Nullable String movementStates, @Nullable String movementManager, @Nullable String cameraManager) { + } +} diff --git a/src/com/hypixel/hytale/server/core/util/EventTitleUtil.java b/src/com/hypixel/hytale/server/core/util/EventTitleUtil.java new file mode 100644 index 0000000..1267678 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/EventTitleUtil.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.server.core.util; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.packets.interface_.HideEventTitle; +import com.hypixel.hytale.protocol.packets.interface_.ShowEventTitle; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EventTitleUtil { + public static final String DEFAULT_ZONE = "Void"; + public static final float DEFAULT_DURATION = 4.0F; + public static final float DEFAULT_FADE_DURATION = 1.5F; + + public EventTitleUtil() { + } + + public static void showEventTitleToUniverse( + @Nonnull Message primaryTitle, @Nonnull Message secondaryTitle, boolean isMajor, String icon, float duration, float fadeInDuration, float fadeOutDuration + ) { + for (World world : Universe.get().getWorlds().values()) { + world.execute(() -> { + Store store = world.getEntityStore().getStore(); + showEventTitleToWorld(primaryTitle, secondaryTitle, isMajor, icon, duration, fadeInDuration, fadeOutDuration, store); + }); + } + } + + public static void showEventTitleToWorld( + @Nonnull Message primaryTitle, + @Nonnull Message secondaryTitle, + boolean isMajor, + String icon, + float duration, + float fadeInDuration, + float fadeOutDuration, + @Nonnull Store store + ) { + World world = store.getExternalData().getWorld(); + + for (PlayerRef playerRef : world.getPlayerRefs()) { + showEventTitleToPlayer(playerRef, primaryTitle, secondaryTitle, isMajor, icon, duration, fadeInDuration, fadeOutDuration); + } + } + + public static void hideEventTitleFromWorld(float fadeOutDuration, @Nonnull Store store) { + World world = store.getExternalData().getWorld(); + + for (PlayerRef playerRef : world.getPlayerRefs()) { + hideEventTitleFromPlayer(playerRef, fadeOutDuration); + } + } + + public static void showEventTitleToPlayer( + @Nonnull PlayerRef playerRefComponent, + @Nonnull Message primaryTitle, + @Nonnull Message secondaryTitle, + boolean isMajor, + @Nullable String icon, + float duration, + float fadeInDuration, + float fadeOutDuration + ) { + playerRefComponent.getPacketHandler() + .writeNoCache( + new ShowEventTitle( + fadeInDuration, fadeOutDuration, duration, icon, isMajor, primaryTitle.getFormattedMessage(), secondaryTitle.getFormattedMessage() + ) + ); + } + + public static void showEventTitleToPlayer( + @Nonnull PlayerRef playerRefComponent, @Nonnull Message primaryTitle, @Nonnull Message secondaryTitle, boolean isMajor + ) { + showEventTitleToPlayer(playerRefComponent, primaryTitle, secondaryTitle, isMajor, null, 4.0F, 1.5F, 1.5F); + } + + public static void hideEventTitleFromPlayer(@Nonnull PlayerRef playerRefComponent, float fadeOutDuration) { + playerRefComponent.getPacketHandler().writeNoCache(new HideEventTitle(fadeOutDuration)); + } +} diff --git a/src/com/hypixel/hytale/server/core/util/FillerBlockUtil.java b/src/com/hypixel/hytale/server/core/util/FillerBlockUtil.java new file mode 100644 index 0000000..5215b6c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/FillerBlockUtil.java @@ -0,0 +1,252 @@ +package com.hypixel.hytale.server.core.util; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.function.consumer.TriIntConsumer; +import com.hypixel.hytale.function.predicate.TriIntPredicate; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import javax.annotation.Nonnull; + +public class FillerBlockUtil { + public static final float THRESHOLD = 0.0F; + public static final int NO_FILLER = 0; + private static final int BITS_PER_AXIS = 5; + private static final int MASK = 31; + private static final int INVERT = -32; + + public FillerBlockUtil() { + } + + public static void forEachFillerBlock(@Nonnull BlockBoundingBoxes.RotatedVariantBoxes blockBoundingBoxes, @Nonnull TriIntConsumer consumer) { + forEachFillerBlock(0.0F, blockBoundingBoxes, consumer); + } + + public static void forEachFillerBlock(float threshold, @Nonnull BlockBoundingBoxes.RotatedVariantBoxes blockBoundingBoxes, @Nonnull TriIntConsumer consumer) { + if (!(threshold < 0.0F) && !(threshold >= 1.0F)) { + Box boundingBox = blockBoundingBoxes.getBoundingBox(); + int minX = (int)boundingBox.min.x; + int minY = (int)boundingBox.min.y; + int minZ = (int)boundingBox.min.z; + if (minX - boundingBox.min.x > threshold) { + minX--; + } + + if (minY - boundingBox.min.y > threshold) { + minY--; + } + + if (minZ - boundingBox.min.z > threshold) { + minZ--; + } + + int maxX = (int)boundingBox.max.x; + int maxY = (int)boundingBox.max.y; + int maxZ = (int)boundingBox.max.z; + if (boundingBox.max.x - maxX > threshold) { + maxX++; + } + + if (boundingBox.max.y - maxY > threshold) { + maxY++; + } + + if (boundingBox.max.z - maxZ > threshold) { + maxZ++; + } + + int blockWidth = Math.max(maxX - minX, 1); + int blockHeight = Math.max(maxY - minY, 1); + int blockDepth = Math.max(maxZ - minZ, 1); + + for (int x = 0; x < blockWidth; x++) { + for (int y = 0; y < blockHeight; y++) { + for (int z = 0; z < blockDepth; z++) { + consumer.accept(minX + x, minY + y, minZ + z); + } + } + } + } else { + throw new IllegalArgumentException("Threshold must be between 0 and 1"); + } + } + + public static boolean testFillerBlocks(@Nonnull BlockBoundingBoxes.RotatedVariantBoxes blockBoundingBoxes, @Nonnull TriIntPredicate predicate) { + return testFillerBlocks(0.0F, blockBoundingBoxes, predicate); + } + + public static boolean testFillerBlocks( + float threshold, @Nonnull BlockBoundingBoxes.RotatedVariantBoxes blockBoundingBoxes, @Nonnull TriIntPredicate predicate + ) { + if (!(threshold < 0.0F) && !(threshold >= 1.0F)) { + Box boundingBox = blockBoundingBoxes.getBoundingBox(); + int minX = (int)boundingBox.min.x; + int minY = (int)boundingBox.min.y; + int minZ = (int)boundingBox.min.z; + if (minX - boundingBox.min.x > threshold) { + minX--; + } + + if (minY - boundingBox.min.y > threshold) { + minY--; + } + + if (minZ - boundingBox.min.z > threshold) { + minZ--; + } + + int maxX = (int)boundingBox.max.x; + int maxY = (int)boundingBox.max.y; + int maxZ = (int)boundingBox.max.z; + if (boundingBox.max.x - maxX > threshold) { + maxX++; + } + + if (boundingBox.max.y - maxY > threshold) { + maxY++; + } + + if (boundingBox.max.z - maxZ > threshold) { + maxZ++; + } + + int blockWidth = Math.max(maxX - minX, 1); + int blockHeight = Math.max(maxY - minY, 1); + int blockDepth = Math.max(maxZ - minZ, 1); + + for (int x = 0; x < blockWidth; x++) { + for (int y = 0; y < blockHeight; y++) { + for (int z = 0; z < blockDepth; z++) { + if (!predicate.test(minX + x, minY + y, minZ + z)) { + return false; + } + } + } + } + + return true; + } else { + throw new IllegalArgumentException("Threshold must be between 0 and 1"); + } + } + + public static FillerBlockUtil.ValidationResult validateBlock( + int x, int y, int z, int blockId, int rotation, int filler, A a, B b, @Nonnull FillerBlockUtil.FillerFetcher fetcher + ) { + if (blockId == 0) { + return FillerBlockUtil.ValidationResult.OK; + } else { + BlockTypeAssetMap blockTypeAssetMap = BlockType.getAssetMap(); + BlockType blockType = blockTypeAssetMap.getAsset(blockId); + if (blockType == null) { + return FillerBlockUtil.ValidationResult.OK; + } else { + String id = blockType.getId(); + IndexedLookupTableAssetMap hitboxAssetMap = BlockBoundingBoxes.getAssetMap(); + if (filler != 0) { + int fillerX = unpackX(filler); + int fillerY = unpackY(filler); + int fillerZ = unpackZ(filler); + int baseBlockId = fetcher.getBlock(a, b, x - fillerX, y - fillerY, z - fillerZ); + BlockType baseBlock = blockTypeAssetMap.getAsset(baseBlockId); + if (baseBlock == null) { + return FillerBlockUtil.ValidationResult.INVALID_BLOCK; + } else { + String baseId = baseBlock.getId(); + BlockBoundingBoxes hitbox = hitboxAssetMap.getAsset(baseBlock.getHitboxTypeIndex()); + if (hitbox == null) { + return FillerBlockUtil.ValidationResult.OK; + } else { + int baseFiller = fetcher.getFiller(a, b, x - fillerX, y - fillerY, z - fillerZ); + int baseRotation = fetcher.getRotationIndex(a, b, x - fillerX, y - fillerY, z - fillerZ); + return baseFiller == 0 + && baseRotation == rotation + && id.equals(baseId) + && hitbox.get(baseRotation).getBoundingBox().containsBlock(fillerX, fillerY, fillerZ) + ? FillerBlockUtil.ValidationResult.OK + : FillerBlockUtil.ValidationResult.INVALID_BLOCK; + } + } + } else { + BlockBoundingBoxes hitbox = hitboxAssetMap.getAsset(blockType.getHitboxTypeIndex()); + if (hitbox != null && hitbox.protrudesUnitBox()) { + boolean result = testFillerBlocks(hitbox.get(rotation), (x1, y1, z1) -> { + if (x1 == 0 && y1 == 0 && z1 == 0) { + return true; + } else { + int worldX = x + x1; + int worldY = y + y1; + int worldZ = z + z1; + int fillerBlockId = fetcher.getBlock(a, b, worldX, worldY, worldZ); + BlockType fillerBlock = blockTypeAssetMap.getAsset(fillerBlockId); + int expectedFiller = pack(x1, y1, z1); + if (fetcher.getFiller(a, b, worldX, worldY, worldZ) != expectedFiller) { + return false; + } else if (fetcher.getRotationIndex(a, b, worldX, worldY, worldZ) != rotation) { + return false; + } else if (fillerBlock == null) { + return false; + } else { + String blockTypeKey = fillerBlock.getId(); + return blockTypeKey.equals(id); + } + } + }); + return result ? FillerBlockUtil.ValidationResult.OK : FillerBlockUtil.ValidationResult.INVALID_FILLER; + } else { + return FillerBlockUtil.ValidationResult.OK; + } + } + } + } + } + + public static int pack(int x, int y, int z) { + return x & 31 | (z & 31) << 5 | (y & 31) << 10; + } + + public static int unpackX(int val) { + int result = val & 31; + if ((result & 16) != 0) { + result |= -32; + } + + return result; + } + + public static int unpackY(int val) { + int result = val >> 10 & 31; + if ((result & 16) != 0) { + result |= -32; + } + + return result; + } + + public static int unpackZ(int val) { + int result = val >> 5 & 31; + if ((result & 16) != 0) { + result |= -32; + } + + return result; + } + + public interface FillerFetcher { + int getBlock(A var1, B var2, int var3, int var4, int var5); + + int getFiller(A var1, B var2, int var3, int var4, int var5); + + int getRotationIndex(A var1, B var2, int var3, int var4, int var5); + } + + public static enum ValidationResult { + OK, + INVALID_BLOCK, + INVALID_FILLER; + + private ValidationResult() { + } + } +} diff --git a/src/com/hypixel/hytale/server/core/util/HashUtil.java b/src/com/hypixel/hytale/server/core/util/HashUtil.java new file mode 100644 index 0000000..fa5575d --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/HashUtil.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.server.core.util; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import javax.annotation.Nonnull; +import org.bouncycastle.util.encoders.Hex; + +public class HashUtil { + public HashUtil() { + } + + @Nonnull + public static String sha256(byte[] bytes) { + try { + MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); + messageDigest.update(bytes); + return Hex.toHexString(messageDigest.digest()); + } catch (NoSuchAlgorithmException var2) { + throw new RuntimeException(var2); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/util/MessageUtil.java b/src/com/hypixel/hytale/server/core/util/MessageUtil.java new file mode 100644 index 0000000..6403802 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/MessageUtil.java @@ -0,0 +1,295 @@ +package com.hypixel.hytale.server.core.util; + +import com.hypixel.hytale.protocol.BoolParamValue; +import com.hypixel.hytale.protocol.Color; +import com.hypixel.hytale.protocol.DoubleParamValue; +import com.hypixel.hytale.protocol.FormattedMessage; +import com.hypixel.hytale.protocol.IntParamValue; +import com.hypixel.hytale.protocol.LongParamValue; +import com.hypixel.hytale.protocol.ParamValue; +import com.hypixel.hytale.protocol.StringParamValue; +import com.hypixel.hytale.protocol.packets.asseteditor.FailureReply; +import com.hypixel.hytale.protocol.packets.asseteditor.SuccessReply; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.util.ColorParseUtil; +import com.hypixel.hytale.server.core.modules.i18n.I18nModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.jline.utils.AttributedString; +import org.jline.utils.AttributedStringBuilder; +import org.jline.utils.AttributedStyle; +import org.jline.utils.Colors; + +public class MessageUtil { + public MessageUtil() { + } + + public static AttributedString toAnsiString(@Nonnull Message message) { + AttributedStyle style = AttributedStyle.DEFAULT; + String color = message.getColor(); + if (color != null) { + style = hexToStyle(color); + } + + AttributedStringBuilder sb = new AttributedStringBuilder(); + sb.style(style).append(message.getAnsiMessage()); + + for (Message child : message.getChildren()) { + sb.append(toAnsiString(child)); + } + + return sb.toAttributedString(); + } + + public static AttributedStyle hexToStyle(@Nonnull String str) { + Color color = ColorParseUtil.parseColor(str); + if (color == null) { + return AttributedStyle.DEFAULT; + } else { + int colorId = Colors.roundRgbColor(color.red & 255, color.green & 255, color.blue & 255, 256); + return AttributedStyle.DEFAULT.foreground(colorId); + } + } + + @Deprecated + public static void sendSuccessReply(@Nonnull PlayerRef playerRef, int token) { + sendSuccessReply(playerRef, token, null); + } + + @Deprecated + public static void sendSuccessReply(@Nonnull PlayerRef playerRef, int token, @Nullable Message message) { + FormattedMessage msg = message != null ? message.getFormattedMessage() : null; + playerRef.getPacketHandler().writeNoCache(new SuccessReply(token, msg)); + } + + @Deprecated + public static void sendFailureReply(@Nonnull PlayerRef playerRef, int token, @Nonnull Message message) { + FormattedMessage msg = message != null ? message.getFormattedMessage() : null; + playerRef.getPacketHandler().writeNoCache(new FailureReply(token, msg)); + } + + @Nonnull + public static String formatText(String text, @Nullable Map params, @Nullable Map messageParams) { + if (text == null) { + throw new IllegalArgumentException("text cannot be null"); + } else if (params == null && messageParams == null) { + return text; + } else { + int len = text.length(); + StringBuilder sb = new StringBuilder(text.length()); + int lastWritePos = 0; + + for (int i = 0; i < len; i++) { + char ch = text.charAt(i); + if (ch == '{') { + if (i + 1 < len && text.charAt(i + 1) == '{') { + if (i > lastWritePos) { + sb.append(text, lastWritePos, i); + } + + sb.append('{'); + lastWritePos = ++i + 1; + } else { + int end = findMatchingBrace(text, i); + if (end >= 0) { + if (i > lastWritePos) { + sb.append(text, lastWritePos, i); + } + + int contentStart = i + 1; + int c1 = text.indexOf(44, contentStart, end); + int c2 = c1 >= 0 ? text.indexOf(44, c1 + 1, end) : -1; + int nameEndExclusive = c1 >= 0 && c1 < end ? c1 : end; + int ns = trimStart(text, contentStart, nameEndExclusive - 1); + int nl = trimEnd(text, ns, nameEndExclusive - 1); + String key = nl > 0 ? text.substring(ns, ns + nl) : ""; + String format = null; + if (c1 >= 0 && c1 < end) { + int formatStart = c1 + 1; + int formatEndExclusive = c2 >= 0 ? c2 : end; + int fs = trimStart(text, formatStart, formatEndExclusive - 1); + int fl = trimEnd(text, fs, formatEndExclusive - 1); + if (fl > 0) { + format = text.substring(fs, fs + fl); + } + } + + String options = null; + if (c2 >= 0 && c2 < end) { + int optionsStart = c2 + 1; + int os = trimStart(text, optionsStart, end - 1); + int ol = trimEnd(text, os, end - 1); + if (ol > 0) { + options = text.substring(os, os + ol); + } + } + + ParamValue replacement = params != null ? params.get(key) : null; + FormattedMessage replacementMessage = messageParams != null ? messageParams.get(key) : null; + if (replacementMessage != null) { + if (replacementMessage.rawText != null) { + sb.append(replacementMessage.rawText); + } else if (replacementMessage.messageId != null) { + String message = I18nModule.get().getMessage("en-US", replacementMessage.messageId); + if (message != null) { + sb.append(formatText(message, replacementMessage.params, replacementMessage.messageParams)); + } else { + sb.append(replacementMessage.messageId); + } + } + } else if (replacement == null) { + sb.append(text, i, end); + } else { + String formattedReplacement; + formattedReplacement = ""; + label185: + switch (format) { + case "upper": + if (replacement instanceof StringParamValue s) { + formattedReplacement = s.value.toUpperCase(); + } + break; + case "lower": + if (replacement instanceof StringParamValue s) { + formattedReplacement = s.value.toLowerCase(); + } + break; + case "number": + switch (options) { + case "integer": + formattedReplacement = switch (replacement) { + case StringParamValue s -> s.value; + case BoolParamValue b -> b.value ? "1" : "0"; + case DoubleParamValue d -> Integer.toString((int)d.value); + case IntParamValue iv -> Integer.toString(iv.value); + case LongParamValue l -> Long.toString(l.value); + default -> ""; + }; + break label185; + case "decimal": + case null: + default: + formattedReplacement = switch (replacement) { + case StringParamValue s -> s.value; + case BoolParamValue b -> b.value ? "1" : "0"; + case DoubleParamValue d -> Double.toString((int)d.value); + case IntParamValue iv -> Integer.toString(iv.value); + case LongParamValue l -> Long.toString(l.value); + default -> ""; + }; + break label185; + } + case "plural": + if (options != null) { + String oneText = null; + String otherText = null; + int oneIdx = options.indexOf("one {"); + int otherIdx = options.indexOf("other {"); + if (oneIdx >= 0) { + int oneStart = oneIdx + "one {".length(); + int oneEnd = findMatchingBrace(options, oneStart - 1); + if (oneEnd > oneStart) { + oneText = options.substring(oneStart, oneEnd); + } + } + + if (otherIdx >= 0) { + int otherStart = otherIdx + "other {".length(); + int otherEnd = findMatchingBrace(options, otherStart - 1); + if (otherEnd > otherStart) { + otherText = options.substring(otherStart, otherEnd); + } + } + + int value = Integer.parseInt(replacement.toString()); + String selected; + if (value == 1 && oneText != null) { + selected = oneText; + } else if (otherText != null) { + selected = otherText; + } else if (oneText != null) { + selected = oneText; + } else { + selected = ""; + } + + formattedReplacement = formatText(selected, params, messageParams); + } + case null: + } + + if (format == null) { + formattedReplacement = switch (replacement) { + case StringParamValue s -> s.value; + case BoolParamValue b -> Boolean.toString(b.value); + case DoubleParamValue d -> Double.toString(d.value); + case IntParamValue iv -> Integer.toString(iv.value); + case LongParamValue l -> Long.toString(l.value); + default -> ""; + }; + } + + sb.append(formattedReplacement); + } + + i = end; + lastWritePos = end + 1; + } + } + } else if (ch == '}' && i + 1 < len && text.charAt(i + 1) == '}') { + if (i > lastWritePos) { + sb.append(text, lastWritePos, i); + } + + sb.append('}'); + lastWritePos = ++i + 1; + } + } + + if (lastWritePos < len) { + sb.append(text, lastWritePos, len); + } + + return sb.toString(); + } + } + + private static int findMatchingBrace(@Nonnull String text, int start) { + int depth = 0; + int len = text.length(); + + for (int i = start; i < len; i++) { + if (text.charAt(i) == '{') { + depth++; + } else if (text.charAt(i) == '}') { + if (--depth == 0) { + return i; + } + } + } + + return -1; + } + + private static int trimStart(@Nonnull String text, int start, int end) { + int i = start; + + while (i <= end && Character.isWhitespace(text.charAt(i))) { + i++; + } + + return i; + } + + private static int trimEnd(@Nonnull String text, int start, int end) { + int i = start; + + while (end >= i && Character.isWhitespace(text.charAt(i))) { + end--; + } + + return end >= i ? end - i + 1 : 0; + } +} diff --git a/src/com/hypixel/hytale/server/core/util/NotificationUtil.java b/src/com/hypixel/hytale/server/core/util/NotificationUtil.java new file mode 100644 index 0000000..16c8739 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/NotificationUtil.java @@ -0,0 +1,169 @@ +package com.hypixel.hytale.server.core.util; + +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.protocol.ItemWithAllMetadata; +import com.hypixel.hytale.protocol.packets.interface_.Notification; +import com.hypixel.hytale.protocol.packets.interface_.NotificationStyle; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.io.PacketHandler; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NotificationUtil { + public NotificationUtil() { + } + + public static void sendNotificationToUniverse( + @Nonnull Message message, @Nullable Message secondaryMessage, @Nullable String icon, @Nullable ItemWithAllMetadata item, @Nonnull NotificationStyle style + ) { + for (World world : Universe.get().getWorlds().values()) { + world.execute(() -> { + Store store = world.getEntityStore().getStore(); + sendNotificationToWorld(message, secondaryMessage, icon, item, style, store); + }); + } + } + + public static void sendNotificationToUniverse(@Nonnull String message) { + sendNotificationToUniverse(Message.raw(message), null, null, null, NotificationStyle.Default); + } + + public static void sendNotificationToUniverse(@Nonnull String message, @Nonnull String secondaryMessage) { + sendNotificationToUniverse(Message.raw(message), Message.raw(secondaryMessage), null, null, NotificationStyle.Default); + } + + public static void sendNotificationToUniverse(@Nonnull String message, @Nonnull NotificationStyle style) { + sendNotificationToUniverse(Message.raw(message), null, null, null, style); + } + + public static void sendNotificationToUniverse(@Nonnull Message message) { + sendNotificationToUniverse(message, null, null, null, NotificationStyle.Default); + } + + public static void sendNotificationToUniverse(@Nonnull Message message, @Nonnull NotificationStyle style) { + sendNotificationToUniverse(message, null, null, null, style); + } + + public static void sendNotificationToUniverse(@Nonnull Message message, @Nullable String icon, @Nonnull NotificationStyle style) { + sendNotificationToUniverse(message, null, icon, null, style); + } + + public static void sendNotificationToUniverse(@Nonnull Message message, @Nullable ItemWithAllMetadata item, @Nonnull NotificationStyle style) { + sendNotificationToUniverse(message, null, null, item, style); + } + + public static void sendNotificationToUniverse(@Nonnull Message message, @Nullable Message secondaryMessage, @Nullable String icon) { + sendNotificationToUniverse(message, secondaryMessage, icon, null, NotificationStyle.Default); + } + + public static void sendNotificationToUniverse(@Nonnull Message message, @Nullable Message secondaryMessage, @Nullable ItemWithAllMetadata item) { + sendNotificationToUniverse(message, secondaryMessage, null, item, NotificationStyle.Default); + } + + public static void sendNotificationToUniverse(@Nonnull Message message, @Nullable Message secondaryMessage, @Nonnull NotificationStyle style) { + sendNotificationToUniverse(message, secondaryMessage, null, null, style); + } + + public static void sendNotificationToUniverse( + @Nonnull Message message, @Nullable Message secondaryMessage, @Nullable String icon, @Nonnull NotificationStyle style + ) { + sendNotificationToUniverse(message, secondaryMessage, icon, null, style); + } + + public static void sendNotificationToUniverse( + @Nonnull Message message, @Nullable Message secondaryMessage, @Nullable ItemWithAllMetadata item, @Nonnull NotificationStyle style + ) { + sendNotificationToUniverse(message, secondaryMessage, null, item, style); + } + + public static void sendNotificationToWorld( + @Nonnull Message message, + @Nullable Message secondaryMessage, + @Nullable String icon, + @Nullable ItemWithAllMetadata item, + @Nonnull NotificationStyle style, + @Nonnull Store store + ) { + World world = store.getExternalData().getWorld(); + + for (PlayerRef playerRefComponent : world.getPlayerRefs()) { + sendNotification(playerRefComponent.getPacketHandler(), message, secondaryMessage, icon, item, style); + } + } + + public static void sendNotification( + @Nonnull PacketHandler handler, + @Nonnull Message message, + @Nullable Message secondaryMessage, + @Nullable String icon, + @Nullable ItemWithAllMetadata item, + @Nonnull NotificationStyle style + ) { + Objects.requireNonNull(message, "Notification message can't be null!"); + Objects.requireNonNull(style, "Notification style can't be null!"); + handler.writeNoCache( + new Notification(message.getFormattedMessage(), secondaryMessage != null ? secondaryMessage.getFormattedMessage() : null, icon, item, style) + ); + } + + public static void sendNotification(@Nonnull PacketHandler handler, @Nonnull String message) { + sendNotification(handler, Message.raw(message), null, null, null, NotificationStyle.Default); + } + + public static void sendNotification(@Nonnull PacketHandler handler, @Nonnull Message message, @Nonnull String icon) { + sendNotification(handler, message, null, icon, null, NotificationStyle.Default); + } + + public static void sendNotification( + @Nonnull PacketHandler handler, @Nonnull Message message, @Nonnull String icon, @Nonnull NotificationStyle notificationStyle + ) { + sendNotification(handler, message, null, icon, null, notificationStyle); + } + + public static void sendNotification(@Nonnull PacketHandler handler, @Nonnull String message, @Nonnull NotificationStyle style) { + sendNotification(handler, Message.raw(message), null, null, null, style); + } + + public static void sendNotification(@Nonnull PacketHandler handler, @Nonnull Message message) { + sendNotification(handler, message, null, null, null, NotificationStyle.Default); + } + + public static void sendNotification(@Nonnull PacketHandler handler, Message message, NotificationStyle style) { + sendNotification(handler, message, null, null, null, style); + } + + public static void sendNotification(@Nonnull PacketHandler handler, @Nonnull String message, @Nonnull String secondaryMessage) { + sendNotification(handler, Message.raw(message), Message.raw(secondaryMessage), null, null, NotificationStyle.Default); + } + + public static void sendNotification(@Nonnull PacketHandler handler, Message message, Message secondaryMessage, String icon) { + sendNotification(handler, message, secondaryMessage, icon, null, NotificationStyle.Default); + } + + public static void sendNotification(@Nonnull PacketHandler handler, Message message, Message secondaryMessage) { + sendNotification(handler, message, secondaryMessage, null, null, NotificationStyle.Default); + } + + public static void sendNotification(@Nonnull PacketHandler handler, Message message, Message secondaryMessage, ItemWithAllMetadata item) { + sendNotification(handler, message, secondaryMessage, null, item, NotificationStyle.Default); + } + + public static void sendNotification(@Nonnull PacketHandler handler, Message message, Message secondaryMessage, @Nonnull NotificationStyle style) { + sendNotification(handler, message, secondaryMessage, null, null, style); + } + + public static void sendNotification(@Nonnull PacketHandler handler, Message message, Message secondaryMessage, String icon, @Nonnull NotificationStyle style) { + sendNotification(handler, message, secondaryMessage, icon, null, style); + } + + public static void sendNotification( + @Nonnull PacketHandler handler, Message message, Message secondaryMessage, ItemWithAllMetadata item, @Nonnull NotificationStyle style + ) { + sendNotification(handler, message, secondaryMessage, null, item, style); + } +} diff --git a/src/com/hypixel/hytale/server/core/util/PositionUtil.java b/src/com/hypixel/hytale/server/core/util/PositionUtil.java new file mode 100644 index 0000000..8136fa4 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/PositionUtil.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.core.util; + +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.Position; +import com.hypixel.hytale.protocol.Transform; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PositionUtil { + public PositionUtil() { + } + + @Nonnull + public static Transform toTransformPacket(@Nonnull com.hypixel.hytale.math.vector.Transform transform) { + Vector3d position = transform.getPosition(); + Vector3f rotation = transform.getRotation(); + return new Transform(toPositionPacket(position), toDirectionPacket(rotation)); + } + + @Nonnull + public static Position toPositionPacket(@Nonnull Vector3d position) { + return new Position(position.x, position.y, position.z); + } + + @Nonnull + public static Direction toDirectionPacket(@Nonnull Vector3f rotation) { + return new Direction(rotation.getYaw(), rotation.getPitch(), rotation.getRoll()); + } + + public static com.hypixel.hytale.math.vector.Transform toTransform(@Nullable Transform transform) { + return transform == null ? null : new com.hypixel.hytale.math.vector.Transform(toVector3d(transform.position), toRotation(transform.orientation)); + } + + @Nonnull + public static Vector3d toVector3d(@Nonnull Position position_) { + return new Vector3d(position_.x, position_.y, position_.z); + } + + @Nonnull + public static Vector3f toRotation(@Nonnull Direction orientation) { + return new Vector3f(orientation.pitch, orientation.yaw, orientation.roll); + } + + public static boolean equals(@Nonnull Vector3d vector, @Nonnull Position position) { + return vector.x == position.x && vector.y == position.y && vector.z == position.z; + } + + public static void assign(@Nonnull Position position, @Nonnull Vector3d vector) { + position.x = vector.x; + position.y = vector.y; + position.z = vector.z; + } + + public static boolean equals(@Nonnull Vector3f vector, @Nonnull Direction direction) { + return vector.x == direction.pitch && vector.y == direction.yaw && vector.z == direction.roll; + } + + public static void assign(@Nonnull Direction direction, @Nonnull Vector3f vector) { + direction.pitch = vector.x; + direction.yaw = vector.y; + direction.roll = vector.z; + } + + public static void assign(@Nonnull Position position, @Nonnull Position other) { + position.x = other.x; + position.y = other.y; + position.z = other.z; + } + + public static void assign(@Nonnull Direction direction, @Nonnull Direction other) { + direction.pitch = other.pitch; + direction.yaw = other.yaw; + direction.roll = other.roll; + } +} diff --git a/src/com/hypixel/hytale/server/core/util/PrefabUtil.java b/src/com/hypixel/hytale/server/core/util/PrefabUtil.java new file mode 100644 index 0000000..f5d520a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/PrefabUtil.java @@ -0,0 +1,312 @@ +package com.hypixel.hytale.server.core.util; + +import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; +import com.hypixel.hytale.common.util.CompletableFutureUtil; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; +import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics; +import com.hypixel.hytale.server.core.modules.entity.component.FromPrefab; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.prefab.PrefabRotation; +import com.hypixel.hytale.server.core.prefab.event.PrefabPasteEvent; +import com.hypixel.hytale.server.core.prefab.event.PrefabPlaceEntityEvent; +import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabBufferCall; +import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.IPrefabBuffer; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.accessor.LocalCachedChunkAccessor; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PrefabUtil { + protected static final String EDITOR_BLOCK = "Editor_Block"; + protected static final String EDITOR_BLOCK_PREFAB_AIR = "Editor_Empty"; + protected static final String EDITOR_BLOCK_PREFAB_ANCHOR = "Editor_Anchor"; + private static final AtomicInteger PREFAB_ID_SOURCE = new AtomicInteger(0); + + public PrefabUtil() { + } + + public static boolean prefabMatchesAtPosition( + @Nonnull IPrefabBuffer prefabBuffer, World world, @Nonnull Vector3i position, @Nonnull Rotation yaw, Random random + ) { + double xLength = prefabBuffer.getMaxX() - prefabBuffer.getMinX(); + double zLength = prefabBuffer.getMaxZ() - prefabBuffer.getMinZ(); + int prefabRadius = (int)MathUtil.fastFloor(0.5 * Math.sqrt(xLength * xLength + zLength * zLength)); + LocalCachedChunkAccessor chunkAccessor = LocalCachedChunkAccessor.atWorldCoords(world, position.getX(), position.getZ(), prefabRadius); + return prefabBuffer.compare((x, y, z, blockId, rotation, holder, prefabBufferCall) -> { + int bx = position.x + x; + int by = position.y + y; + int bz = position.z + z; + WorldChunk chunk = chunkAccessor.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(bx, bz)); + int blockIdAtPos = chunk.getBlock(bx, by, bz); + return blockIdAtPos == blockId; + }, new PrefabBufferCall(random, PrefabRotation.fromRotation(yaw))); + } + + public static boolean canPlacePrefab( + @Nonnull IPrefabBuffer prefabBuffer, + World world, + @Nonnull Vector3i position, + @Nonnull Rotation yaw, + @Nullable IntSet mask, + Random random, + boolean ignoreOrigin + ) { + double xLength = prefabBuffer.getMaxX() - prefabBuffer.getMinX(); + double zLength = prefabBuffer.getMaxZ() - prefabBuffer.getMinZ(); + int prefabRadius = (int)MathUtil.fastFloor(0.5 * Math.sqrt(xLength * xLength + zLength * zLength)); + LocalCachedChunkAccessor chunkAccessor = LocalCachedChunkAccessor.atWorldCoords(world, position.getX(), position.getZ(), prefabRadius); + return prefabBuffer.compare( + (x, y, z, blockId, rotation, holder, prefabBufferCall) -> { + if (ignoreOrigin && x == 0 && y == 0 && z == 0) { + return true; + } else { + int bx = position.x + x; + int by = position.y + y; + int bz = position.z + z; + WorldChunk chunk = chunkAccessor.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(bx, bz)); + return chunk.testPlaceBlock( + bx, + by, + bz, + BlockType.getAssetMap().getAsset(blockId), + rotation, + (x1, y1, z1, blockType, _rotation, filler) -> mask != null && mask.contains(BlockType.getAssetMap().getIndex(blockType.getId())) + ); + } + }, + new PrefabBufferCall(random, PrefabRotation.fromRotation(yaw)) + ); + } + + public static void paste( + @Nonnull IPrefabBuffer buffer, + @Nonnull World world, + @Nonnull Vector3i position, + @Nonnull Rotation yaw, + boolean force, + Random random, + @Nonnull ComponentAccessor componentAccessor + ) { + paste(buffer, world, position, yaw, force, random, 0, componentAccessor); + } + + public static void paste( + @Nonnull IPrefabBuffer buffer, + @Nonnull World world, + @Nonnull Vector3i position, + @Nonnull Rotation yaw, + boolean force, + Random random, + int setBlockSettings, + @Nonnull ComponentAccessor componentAccessor + ) { + paste(buffer, world, position, yaw, force, random, setBlockSettings, false, false, false, componentAccessor); + } + + public static int getNextPrefabId() { + return PREFAB_ID_SOURCE.getAndIncrement(); + } + + public static void paste( + @Nonnull IPrefabBuffer buffer, + @Nonnull World world, + @Nonnull Vector3i position, + @Nonnull Rotation yaw, + boolean force, + Random random, + int setBlockSettings, + boolean technicalPaste, + boolean pasteAnchorAsBlock, + boolean loadEntities, + @Nonnull ComponentAccessor componentAccessor + ) { + double xLength = buffer.getMaxX() - buffer.getMinX(); + double zLength = buffer.getMaxZ() - buffer.getMinZ(); + int prefabRadius = (int)MathUtil.fastFloor(0.5 * Math.sqrt(xLength * xLength + zLength * zLength)); + LocalCachedChunkAccessor chunkAccessor = LocalCachedChunkAccessor.atWorldCoords(world, position.getX(), position.getZ(), prefabRadius); + BlockTypeAssetMap blockTypeMap = BlockType.getAssetMap(); + int editorBlock = blockTypeMap.getIndex("Editor_Block"); + if (editorBlock == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! Editor_Block"); + } else { + PrefabRotation rotation = PrefabRotation.fromRotation(yaw); + int prefabId = getNextPrefabId(); + PrefabPasteEvent startEvent = new PrefabPasteEvent(prefabId, true); + componentAccessor.invoke(startEvent); + if (!startEvent.isCancelled()) { + buffer.forEach(IPrefabBuffer.iterateAllColumns(), (x, y, z, blockId, holder, supportValue, blockRotation, filler, call, fluidId, fluidLevel) -> { + int bx = position.x + x; + int by = position.y + y; + int bz = position.z + z; + WorldChunk chunk = chunkAccessor.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(bx, bz)); + Store fluidStore = world.getChunkStore().getStore(); + ChunkColumn fluidColumn = fluidStore.getComponent(chunk.getReference(), ChunkColumn.getComponentType()); + Ref section = fluidColumn.getSection(ChunkUtil.chunkCoordinate(by)); + FluidSection fluidSection = fluidStore.ensureAndGetComponent(section, FluidSection.getComponentType()); + fluidSection.setFluid(bx, by, bz, fluidId, (byte)fluidLevel); + BlockType block; + if (technicalPaste) { + if (blockId == 0 && fluidId == 0) { + block = blockTypeMap.getAsset("Editor_Empty"); + } else { + block = blockTypeMap.getAsset(blockId); + } + } else { + block = blockTypeMap.getAsset(blockId); + } + + String blockKey = block.getId(); + if (filler == 0) { + if (pasteAnchorAsBlock && technicalPaste && x == buffer.getAnchorX() && y == buffer.getAnchorY() && z == buffer.getAnchorZ()) { + int index = blockTypeMap.getIndex("Editor_Anchor"); + BlockType type = blockTypeMap.getAsset(index); + chunk.setBlock(bx, by, bz, index, type, blockRotation, filler, setBlockSettings); + } else if (!force) { + RotationTuple rot = RotationTuple.get(blockRotation); + chunk.placeBlock(bx, by, bz, blockKey, rot.yaw(), rot.pitch(), rot.roll(), setBlockSettings); + } else { + int index = blockTypeMap.getIndex(blockKey); + BlockType type = blockTypeMap.getAsset(index); + chunk.setBlock(bx, by, bz, index, type, blockRotation, filler, setBlockSettings); + } + + if (supportValue != 0) { + if (!world.isInThread()) { + CompletableFutureUtil._catch(CompletableFuture.runAsync(() -> { + Ref refx = chunk.getReference(); + Store storex = refx.getStore(); + ChunkColumn columnx = storex.getComponent(refx, ChunkColumn.getComponentType()); + BlockPhysics.setSupportValue(storex, columnx.getSection(ChunkUtil.chunkCoordinate(by)), bx, by, bz, supportValue); + }, world)); + } else { + Ref ref = chunk.getReference(); + Store store = ref.getStore(); + ChunkColumn column = store.getComponent(ref, ChunkColumn.getComponentType()); + BlockPhysics.setSupportValue(store, column.getSection(ChunkUtil.chunkCoordinate(by)), bx, by, bz, supportValue); + } + } + + if (holder != null) { + chunk.setState(bx, by, bz, holder.clone()); + } + } + }, (x, z, entityWrappers, t) -> { + if (loadEntities) { + if (entityWrappers != null && entityWrappers.length != 0) { + for (int i = 0; i < entityWrappers.length; i++) { + Holder entityToAdd = entityWrappers[i].clone(); + TransformComponent transformComp = entityToAdd.getComponent(TransformComponent.getComponentType()); + if (transformComp != null) { + Vector3d entityPosition = transformComp.getPosition().clone(); + rotation.rotate(entityPosition); + Vector3d entityWorldPosition = entityPosition.add(position); + transformComp = entityToAdd.getComponent(TransformComponent.getComponentType()); + if (transformComp != null) { + entityPosition = transformComp.getPosition(); + entityPosition.x = entityWorldPosition.x; + entityPosition.y = entityWorldPosition.y; + entityPosition.z = entityWorldPosition.z; + PrefabPlaceEntityEvent prefabPlaceEntityEvent = new PrefabPlaceEntityEvent(prefabId, entityToAdd); + componentAccessor.invoke(prefabPlaceEntityEvent); + entityToAdd.addComponent(FromPrefab.getComponentType(), FromPrefab.INSTANCE); + componentAccessor.addEntity(entityToAdd, AddReason.LOAD); + } + } + } + } + } + }, (x, y, z, path, fitHeightmap, inheritSeed, inheritHeightCondition, weights, rot, t) -> {}, new PrefabBufferCall(random, rotation)); + PrefabPasteEvent endEvent = new PrefabPasteEvent(prefabId, false); + componentAccessor.invoke(endEvent); + } + } + } + + public static void remove( + @Nonnull IPrefabBuffer prefabBuffer, @Nonnull World world, @Nonnull Vector3i position, boolean force, @Nonnull Random random, int setBlockSettings + ) { + remove(prefabBuffer, world, position, force, random, setBlockSettings, 1.0); + } + + public static void remove( + @Nonnull IPrefabBuffer prefabBuffer, + @Nonnull World world, + @Nonnull Vector3i position, + boolean force, + @Nonnull Random random, + int setBlockSettings, + double brokenParticlesRate + ) { + } + + public static void remove( + @Nonnull IPrefabBuffer prefabBuffer, + @Nonnull World world, + @Nonnull Vector3i position, + Rotation prefabRotation, + boolean force, + @Nonnull Random random, + int setBlockSettings, + double brokenParticlesRate + ) { + double xLength = prefabBuffer.getMaxX() - prefabBuffer.getMinX(); + double zLength = prefabBuffer.getMaxZ() - prefabBuffer.getMinZ(); + int prefabRadius = (int)MathUtil.fastFloor(0.5 * Math.sqrt(xLength * xLength + zLength * zLength)); + LocalCachedChunkAccessor chunkAccessor = LocalCachedChunkAccessor.atWorldCoords(world, position.getX(), position.getZ(), prefabRadius); + BlockTypeAssetMap blockTypeMap = BlockType.getAssetMap(); + prefabBuffer.forEach( + IPrefabBuffer.iterateAllColumns(), + (x, y, z, blockId, state, support, rotation, filler, call, fluidId, fluidLevel) -> { + int bx = position.x + x; + int by = position.y + y; + int bz = position.z + z; + WorldChunk chunk = chunkAccessor.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(bx, bz)); + Store store = world.getChunkStore().getStore(); + if (fluidId != 0) { + ChunkColumn column = store.getComponent(chunk.getReference(), ChunkColumn.getComponentType()); + Ref section = column.getSection(ChunkUtil.chunkCoordinate(by)); + FluidSection fluidSection = store.ensureAndGetComponent(section, FluidSection.getComponentType()); + fluidSection.setFluid(bx, by, bz, 0, (byte)0); + } + + if (blockId != 0) { + if (filler == 0) { + int updatedSetBlockSettings = setBlockSettings; + if ((setBlockSettings & 4) != 4 && random.nextDouble() > brokenParticlesRate) { + updatedSetBlockSettings = setBlockSettings | 4; + } + + if (!force) { + chunk.breakBlock(bx, by, bz, updatedSetBlockSettings); + } else { + chunk.setBlock(bx, by, bz, "Empty", updatedSetBlockSettings); + } + } + } + }, + (x, z, entityWrappers, t) -> {}, + (x, y, z, path, fitHeightmap, inheritSeed, inheritHeightCondition, weights, rotation, t) -> {}, + new PrefabBufferCall(random, PrefabRotation.fromRotation(prefabRotation)) + ); + } +} diff --git a/src/com/hypixel/hytale/server/core/util/ProcessUtil.java b/src/com/hypixel/hytale/server/core/util/ProcessUtil.java new file mode 100644 index 0000000..c5f1752 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/ProcessUtil.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.core.util; + +public class ProcessUtil { + public ProcessUtil() { + } + + public static boolean isProcessRunning(int pid) { + return ProcessHandle.of(pid).isPresent(); + } +} diff --git a/src/com/hypixel/hytale/server/core/util/TargetUtil.java b/src/com/hypixel/hytale/server/core/util/TargetUtil.java new file mode 100644 index 0000000..5dbf46b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/TargetUtil.java @@ -0,0 +1,401 @@ +package com.hypixel.hytale.server.core.util; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.function.predicate.BiIntPredicate; +import com.hypixel.hytale.math.block.BlockUtil; +import com.hypixel.hytale.math.iterator.BlockIterator; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector2d; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.modules.collision.CollisionMath; +import com.hypixel.hytale.server.core.modules.collision.WorldUtil; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.LinkedList; +import java.util.List; +import java.util.function.IntPredicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class TargetUtil { + private static final float ENTITY_TARGET_RADIUS = 8.0F; + + public TargetUtil() { + } + + @Nullable + public static Vector3i getTargetBlock( + @Nonnull World world, + @Nonnull BiIntPredicate blockIdPredicate, + double originX, + double originY, + double originZ, + double directionX, + double directionY, + double directionZ, + double maxDistance + ) { + TargetUtil.TargetBuffer buffer = new TargetUtil.TargetBuffer(world); + buffer.updateChunk((int)originX, (int)originZ); + boolean success = BlockIterator.iterate( + originX, originY, originZ, directionX, directionY, directionZ, maxDistance, (x, y, z, px, py, pz, qx, qy, qz, iBuffer) -> { + if (y >= 0 && y < 320) { + iBuffer.updateChunk(x, z); + if (iBuffer.currentBlockChunk != null && iBuffer.currentChunkColumn != null) { + iBuffer.x = x; + iBuffer.y = y; + iBuffer.z = z; + BlockSection blockSection = iBuffer.currentBlockChunk.getSectionAtBlockY(y); + int blockId = blockSection.get(x, y, z); + int fluidId = WorldUtil.getFluidIdAtPosition(iBuffer.chunkStoreAccessor, iBuffer.currentChunkColumn, x, y, z); + return !blockIdPredicate.test(blockId, fluidId); + } else { + return false; + } + } else { + return false; + } + }, buffer + ); + return success ? null : new Vector3i(buffer.x, buffer.y, buffer.z); + } + + @Nullable + public static Vector3d getTargetLocation( + @Nonnull World world, + @Nonnull IntPredicate blockIdPredicate, + double originX, + double originY, + double originZ, + double directionX, + double directionY, + double directionZ, + double maxDistance + ) { + TargetUtil.TargetBufferLocation buffer = new TargetUtil.TargetBufferLocation(world); + buffer.updateChunk((int)originX, (int)originZ); + boolean success = BlockIterator.iterate( + originX, originY, originZ, directionX, directionY, directionZ, maxDistance, (x, y, z, px, py, pz, qx, qy, qz, iBuffer) -> { + if (y >= 0 && y < 320) { + iBuffer.updateChunk(x, z); + if (iBuffer.currentBlockChunk == null) { + return false; + } else { + iBuffer.x = x + px; + iBuffer.y = y + py; + iBuffer.z = z + pz; + BlockSection blockSection = iBuffer.currentBlockChunk.getSectionAtBlockY(y); + int blockId = blockSection.get(x, y, z); + return !blockIdPredicate.test(blockId); + } + } else { + return false; + } + }, buffer + ); + return success ? null : new Vector3d(buffer.x, buffer.y, buffer.z); + } + + @Nullable + public static Vector3i getTargetBlockAvoidLocations( + @Nonnull World world, + @Nonnull IntPredicate blockIdPredicate, + double originX, + double originY, + double originZ, + double directionX, + double directionY, + double directionZ, + double maxDistance, + @Nonnull LinkedList blocksToIgnore + ) { + TargetUtil.TargetBuffer buffer = new TargetUtil.TargetBuffer(world); + buffer.updateChunk((int)originX, (int)originZ); + boolean success = BlockIterator.iterate( + originX, originY, originZ, directionX, directionY, directionZ, maxDistance, (x, y, z, px, py, pz, qx, qy, qz, iBuffer) -> { + if (y >= 0 && y < 320) { + iBuffer.updateChunk(x, z); + if (iBuffer.currentBlockChunk == null) { + return false; + } else { + iBuffer.x = x; + iBuffer.y = y; + iBuffer.z = z; + BlockSection blockSection = iBuffer.currentBlockChunk.getSectionAtBlockY(y); + int blockId = blockSection.get(x, y, z); + if (blockId != 0) { + long packedBlockLocation = BlockUtil.pack(x, y, z); + + for (LongOpenHashSet locations : blocksToIgnore) { + if (locations.contains(packedBlockLocation)) { + return true; + } + } + } + + return !blockIdPredicate.test(blockId); + } + } else { + return false; + } + }, buffer + ); + return success ? null : new Vector3i(buffer.x, buffer.y, buffer.z); + } + + @Nullable + public static Vector3i getTargetBlock(@Nonnull Ref ref, double maxDistance, @Nonnull ComponentAccessor componentAccessor) { + return getTargetBlock(ref, blockId -> blockId != 0, maxDistance, componentAccessor); + } + + @Nullable + public static Vector3i getTargetBlock( + @Nonnull Ref ref, @Nonnull IntPredicate blockIdPredicate, double maxDistance, @Nonnull ComponentAccessor componentAccessor + ) { + World world = componentAccessor.getExternalData().getWorld(); + Transform transform = getLook(ref, componentAccessor); + Vector3d pos = transform.getPosition(); + Vector3d dir = transform.getDirection(); + return getTargetBlock(world, (id, _fluidId) -> blockIdPredicate.test(id), pos.x, pos.y, pos.z, dir.x, dir.y, dir.z, maxDistance); + } + + @Nullable + public static Vector3d getTargetLocation(@Nonnull Ref ref, double maxDistance, @Nonnull ComponentAccessor componentAccessor) { + return getTargetLocation(ref, blockId -> blockId != 0, maxDistance, componentAccessor); + } + + @Nullable + public static Vector3d getTargetLocation( + @Nonnull Ref ref, @Nonnull IntPredicate blockIdPredicate, double maxDistance, @Nonnull ComponentAccessor componentAccessor + ) { + Transform transform = getLook(ref, componentAccessor); + return getTargetLocation(transform, blockIdPredicate, maxDistance, componentAccessor); + } + + @Nullable + public static Vector3d getTargetLocation( + @Nonnull Transform transform, @Nonnull IntPredicate blockIdPredicate, double maxDistance, @Nonnull ComponentAccessor componentAccessor + ) { + World world = componentAccessor.getExternalData().getWorld(); + Vector3d pos = transform.getPosition(); + Vector3d dir = transform.getDirection(); + return getTargetLocation(world, blockIdPredicate, pos.x, pos.y, pos.z, dir.x, dir.y, dir.z, maxDistance); + } + + @Nonnull + public static List> getAllEntitiesInSphere( + @Nonnull Vector3d position, double radius, @Nonnull ComponentAccessor componentAccessor + ) { + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + SpatialResource, EntityStore> entitySpatialResource = componentAccessor.getResource(EntityModule.get().getEntitySpatialResourceType()); + entitySpatialResource.getSpatialStructure().collect(position, (float)radius, results); + SpatialResource, EntityStore> playerSpatialResource = componentAccessor.getResource(EntityModule.get().getPlayerSpatialResourceType()); + playerSpatialResource.getSpatialStructure().collect(position, (float)radius, results); + return results; + } + + @Nonnull + public static List> getAllEntitiesInCylinder( + @Nonnull Vector3d position, double radius, double height, @Nonnull ComponentAccessor componentAccessor + ) { + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + SpatialResource, EntityStore> entitySpatialResource = componentAccessor.getResource(EntityModule.get().getEntitySpatialResourceType()); + entitySpatialResource.getSpatialStructure().collectCylinder(position, radius, height, results); + SpatialResource, EntityStore> playerSpatialResource = componentAccessor.getResource(EntityModule.get().getPlayerSpatialResourceType()); + playerSpatialResource.getSpatialStructure().collectCylinder(position, radius, height, results); + return results; + } + + @Nonnull + public static List> getAllEntitiesInBox( + @Nonnull Vector3d min, @Nonnull Vector3d max, @Nonnull ComponentAccessor componentAccessor + ) { + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + SpatialResource, EntityStore> entitySpatialResource = componentAccessor.getResource(EntityModule.get().getEntitySpatialResourceType()); + entitySpatialResource.getSpatialStructure().collectBox(min, max, results); + SpatialResource, EntityStore> playerSpatialResource = componentAccessor.getResource(EntityModule.get().getPlayerSpatialResourceType()); + playerSpatialResource.getSpatialStructure().collectBox(min, max, results); + return results; + } + + @Nullable + public static Ref getTargetEntity(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + return getTargetEntity(ref, 8.0F, componentAccessor); + } + + @Nullable + public static Ref getTargetEntity(@Nonnull Ref ref, float radius, @Nonnull ComponentAccessor componentAccessor) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d transformPosition = transformComponent.getPosition(); + Transform lookVec = getLook(ref, componentAccessor); + Vector3d position = lookVec.getPosition(); + Vector3d direction = lookVec.getDirection(); + List> targetEntities = getAllEntitiesInSphere(position, radius, componentAccessor); + targetEntities.removeIf( + targetRefx -> targetRefx != null && targetRefx.isValid() && !targetRefx.equals(ref) + ? !isHitByRay(targetRefx, position, direction, componentAccessor) + : true + ); + if (targetEntities.isEmpty()) { + return null; + } else { + Ref closest = null; + double minDist2 = Double.MAX_VALUE; + + for (Ref targetRef : targetEntities) { + if (targetRef != null && targetRef.isValid()) { + TransformComponent targetTransformComponent = componentAccessor.getComponent(targetRef, TransformComponent.getComponentType()); + + assert targetTransformComponent != null; + + double distance = transformPosition.distanceSquaredTo(targetTransformComponent.getPosition()); + if (distance < minDist2) { + minDist2 = distance; + closest = targetRef; + } + } + } + + return closest; + } + } + + @Nonnull + public static Transform getLook(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + float eyeHeight = 0.0F; + ModelComponent modelComponent = componentAccessor.getComponent(ref, ModelComponent.getComponentType()); + if (modelComponent != null) { + eyeHeight = modelComponent.getModel().getEyeHeight(ref, componentAccessor); + } + + HeadRotation headRotationComponent = componentAccessor.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3d position = transformComponent.getPosition(); + Vector3f headRotation = headRotationComponent.getRotation(); + return new Transform( + position.getX(), position.getY() + eyeHeight, position.getZ(), headRotation.getPitch(), headRotation.getYaw(), headRotation.getRoll() + ); + } + + private static boolean isHitByRay( + @Nonnull Ref ref, @Nonnull Vector3d rayStart, @Nonnull Vector3d rayDir, @Nonnull ComponentAccessor componentAccessor + ) { + BoundingBox boundingBoxComponent = componentAccessor.getComponent(ref, BoundingBox.getComponentType()); + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + if (boundingBoxComponent == null) { + return false; + } else { + Box boundingBox = boundingBoxComponent.getBoundingBox(); + Vector3d position = transformComponent.getPosition(); + Vector2d minMax = new Vector2d(); + return CollisionMath.intersectRayAABB(rayStart, rayDir, position.getX(), position.getY(), position.getZ(), boundingBox, minMax); + } + } + + private static class TargetBuffer { + @Nonnull + private final World world; + @Nonnull + private final ComponentAccessor chunkStoreAccessor; + private int x; + private int y; + private int z; + private int currentChunkX; + private int currentChunkZ; + @Nullable + private Ref currentChunkRef; + @Nullable + private ChunkColumn currentChunkColumn; + @Nullable + private BlockChunk currentBlockChunk; + + public TargetBuffer(@Nonnull World world) { + this.world = world; + this.chunkStoreAccessor = world.getChunkStore().getStore(); + } + + public void updateChunk(int blockX, int blockZ) { + int chunkX = ChunkUtil.chunkCoordinate(blockX); + int chunkZ = ChunkUtil.chunkCoordinate(blockZ); + if (this.currentChunkRef == null || chunkX != this.currentChunkX || chunkZ != this.currentChunkZ) { + this.currentChunkX = chunkX; + this.currentChunkZ = chunkZ; + long chunkIndex = ChunkUtil.indexChunk(chunkX, chunkZ); + this.currentChunkRef = this.world.getChunkStore().getChunkReference(chunkIndex); + if (this.currentChunkRef != null && this.currentChunkRef.isValid()) { + this.currentChunkColumn = this.chunkStoreAccessor.getComponent(this.currentChunkRef, ChunkColumn.getComponentType()); + this.currentBlockChunk = this.chunkStoreAccessor.getComponent(this.currentChunkRef, BlockChunk.getComponentType()); + } else { + this.currentChunkColumn = null; + this.currentBlockChunk = null; + } + } + } + } + + private static class TargetBufferLocation { + @Nonnull + public final World world; + @Nonnull + public final ComponentAccessor chunkStoreAccessor; + private double x; + private double y; + private double z; + private int currentChunkX; + private int currentChunkZ; + @Nullable + public Ref currentChunkRef; + @Nullable + public BlockChunk currentBlockChunk; + + public TargetBufferLocation(@Nonnull World world) { + this.world = world; + this.chunkStoreAccessor = world.getChunkStore().getStore(); + } + + public void updateChunk(int blockX, int blockZ) { + int chunkX = ChunkUtil.chunkCoordinate(blockX); + int chunkZ = ChunkUtil.chunkCoordinate(blockZ); + if (this.currentChunkRef == null || chunkX != this.currentChunkX || chunkZ != this.currentChunkZ) { + this.currentChunkX = chunkX; + this.currentChunkZ = chunkZ; + long chunkIndex = ChunkUtil.indexChunk(chunkX, chunkZ); + this.currentChunkRef = this.world.getChunkStore().getChunkReference(chunkIndex); + if (this.currentChunkRef != null && this.currentChunkRef.isValid()) { + this.currentBlockChunk = this.chunkStoreAccessor.getComponent(this.currentChunkRef, BlockChunk.getComponentType()); + } else { + this.currentBlockChunk = null; + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/util/TempAssetIdUtil.java b/src/com/hypixel/hytale/server/core/util/TempAssetIdUtil.java new file mode 100644 index 0000000..b3d8db6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/TempAssetIdUtil.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.core.util; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import java.util.logging.Level; + +@Deprecated(forRemoval = true) +public class TempAssetIdUtil { + public static final String SOIL_GRASS = "Soil_Grass"; + public static final String SOUND_EVENT_ITEM_REPAIR = "SFX_Item_Repair"; + public static final String SOUND_EVENT_PLAYER_PICKUP_ITEM = "SFX_Player_Pickup_Item"; + public static final String SOUND_EVENT_ITEM_BREAK = "SFX_Item_Break"; + public static final String SOUND_EVENT_PLAYER_CRAFT_ITEM_INVENTORY = "SFX_Player_Craft_Item_Inventory"; + public static final String SOUND_EVENT_PLAYER_DROP_ITEM = "SFX_Player_Drop_Item"; + public static final String PARTICLE_EXAMPLE_SIMPLE = "Example_Simple"; + public static final String PARTICLE_SPLASH = "Splash"; + public static final String DEFAULT_PLAYER_MODEL_NAME = "Player"; + + public TempAssetIdUtil() { + } + + public static int getSoundEventIndex(String soundEventId) { + int soundEventIndex = SoundEvent.getAssetMap().getIndex(soundEventId); + if (soundEventIndex == Integer.MIN_VALUE) { + HytaleLogger.getLogger().at(Level.WARNING).log("Attempted to play an invalid sound event %s", soundEventId); + soundEventIndex = 0; + } + + return soundEventIndex; + } +} diff --git a/src/com/hypixel/hytale/server/core/util/UUIDUtil.java b/src/com/hypixel/hytale/server/core/util/UUIDUtil.java new file mode 100644 index 0000000..d73e6f2 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/UUIDUtil.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.core.util; + +import java.util.UUID; +import javax.annotation.Nonnull; + +public class UUIDUtil { + public static final UUID EMPTY_UUID = new UUID(0L, 0L); + + public UUIDUtil() { + } + + @Nonnull + public static UUID generateVersion3UUID() { + UUID out = UUID.randomUUID(); + if (out.version() != 3) { + long msb = out.getMostSignificantBits(); + msb &= -16385L; + msb |= 12288L; + out = new UUID(msb, out.getLeastSignificantBits()); + } + + return out; + } + + public static boolean isEmptyOrNull(UUID uuid) { + return uuid == null || uuid.equals(EMPTY_UUID); + } +} diff --git a/src/com/hypixel/hytale/server/core/util/ValidateUtil.java b/src/com/hypixel/hytale/server/core/util/ValidateUtil.java new file mode 100644 index 0000000..736bd2a --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/ValidateUtil.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.core.util; + +import com.hypixel.hytale.protocol.Direction; +import com.hypixel.hytale.protocol.Position; +import javax.annotation.Nonnull; + +public class ValidateUtil { + public ValidateUtil() { + } + + public static boolean isSafeDouble(double x) { + return !Double.isNaN(x) && Double.isFinite(x); + } + + public static boolean isSafeFloat(float x) { + return !Float.isNaN(x) && Float.isFinite(x); + } + + public static boolean isSafePosition(@Nonnull Position position) { + return isSafeDouble(position.x) && isSafeDouble(position.y) && isSafeDouble(position.z); + } + + public static boolean isSafeDirection(@Nonnull Direction direction) { + return isSafeFloat(direction.yaw) && isSafeFloat(direction.pitch) && isSafeFloat(direction.roll); + } +} diff --git a/src/com/hypixel/hytale/server/core/util/WildcardMatch.java b/src/com/hypixel/hytale/server/core/util/WildcardMatch.java new file mode 100644 index 0000000..b4739f6 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/WildcardMatch.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.core.util; + +public final class WildcardMatch { + private WildcardMatch() { + } + + public static boolean test(String text, String pattern) { + return test(text, pattern, false); + } + + public static boolean test(String text, String pattern, boolean ignoreCase) { + if (ignoreCase) { + text = text.toLowerCase(); + pattern = pattern.toLowerCase(); + } + + if (text.equals(pattern)) { + return true; + } else { + int t = 0; + int p = 0; + int starIdx = -1; + int match = 0; + + while (t < text.length()) { + if (p >= pattern.length() || pattern.charAt(p) != '?' && pattern.charAt(p) != text.charAt(t)) { + if (p < pattern.length() && pattern.charAt(p) == '*') { + starIdx = p++; + match = t; + } else { + if (starIdx == -1) { + return false; + } + + p = starIdx + 1; + t = ++match; + } + } else { + t++; + p++; + } + } + + while (p < pattern.length() && pattern.charAt(p) == '*') { + p++; + } + + return p == pattern.length(); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/util/backup/BackupTask.java b/src/com/hypixel/hytale/server/core/util/backup/BackupTask.java new file mode 100644 index 0000000..9e37b1b --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/backup/BackupTask.java @@ -0,0 +1,121 @@ +package com.hypixel.hytale.server.core.util.backup; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.Options; +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileTime; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BackupTask { + private static final DateTimeFormatter BACKUP_FILE_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"); + private static final Duration BACKUP_ARCHIVE_FREQUENCY = Duration.of(12L, ChronoUnit.HOURS); + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private final CompletableFuture completion = new CompletableFuture<>(); + + public static CompletableFuture start(@Nonnull Path universeDir, @Nonnull Path backupDir) { + BackupTask task = new BackupTask(universeDir, backupDir); + return task.completion; + } + + private BackupTask(@Nonnull final Path universeDir, @Nonnull final Path backupDir) { + (new Thread("Backup Runner") { + { + this.setDaemon(false); + } + + @Override + public void run() { + BackupUtil.broadcastBackupStatus(true); + + try { + Path archiveDir = backupDir.resolve("archive"); + Files.createDirectories(backupDir); + Files.createDirectories(archiveDir); + BackupTask.cleanOrArchiveOldBackups(backupDir, archiveDir); + BackupTask.cleanOldBackups(archiveDir); + String backupName = BackupTask.BACKUP_FILE_DATE_FORMATTER.format(LocalDateTime.now()) + ".zip"; + Path tempZip = backupDir.resolve(backupName + ".tmp"); + BackupUtil.walkFileTreeAndZip(universeDir, tempZip); + Path backupZip = backupDir.resolve(backupName); + Files.move(tempZip, backupZip, StandardCopyOption.REPLACE_EXISTING); + BackupTask.LOGGER.at(Level.INFO).log("Successfully created backup %s", backupZip); + BackupTask.this.completion.complete(null); + } catch (Throwable var8) { + BackupTask.LOGGER.at(Level.SEVERE).withCause(var8).log("Backup failed with exception"); + BackupUtil.broadcastBackupError(var8); + BackupTask.this.completion.completeExceptionally(var8); + } finally { + BackupUtil.broadcastBackupStatus(false); + } + } + }).start(); + } + + private static void cleanOrArchiveOldBackups(@Nonnull Path sourceDir, @Nonnull Path archiveDir) throws IOException { + int maxCount = Options.getOptionSet().valueOf(Options.BACKUP_MAX_COUNT); + if (maxCount >= 1) { + List oldBackups = BackupUtil.findOldBackups(sourceDir, maxCount); + if (oldBackups != null && !oldBackups.isEmpty()) { + Path oldestBackup = oldBackups.getFirst(); + FileTime oldestBackupTime = Files.getLastModifiedTime(oldestBackup); + FileTime lastArchive = getMostRecentArchive(archiveDir); + boolean doArchive = lastArchive == null + || Duration.between(oldestBackupTime.toInstant(), lastArchive.toInstant()).compareTo(BACKUP_ARCHIVE_FREQUENCY) > 0; + if (doArchive) { + oldBackups = oldBackups.subList(1, oldBackups.size()); + Files.move(oldestBackup, archiveDir.resolve(oldestBackup.getFileName()), StandardCopyOption.REPLACE_EXISTING); + LOGGER.at(Level.INFO).log("Archived old backup: %s", oldestBackup); + } + + for (Path path : oldBackups) { + LOGGER.at(Level.INFO).log("Clearing old backup: %s", path); + Files.deleteIfExists(path); + } + } + } + } + + private static void cleanOldBackups(@Nonnull Path dir) throws IOException { + int maxCount = Options.getOptionSet().valueOf(Options.BACKUP_MAX_COUNT); + if (maxCount >= 1) { + List oldBackups = BackupUtil.findOldBackups(dir, maxCount); + if (oldBackups != null && !oldBackups.isEmpty()) { + for (Path path : oldBackups) { + LOGGER.at(Level.INFO).log("Clearing old backup: %s", path); + Files.deleteIfExists(path); + } + } + } + } + + @Nullable + private static FileTime getMostRecentArchive(@Nonnull Path dir) throws IOException { + FileTime mostRecent = null; + + try (DirectoryStream stream = Files.newDirectoryStream(dir)) { + for (Path path : stream) { + if (Files.isRegularFile(path)) { + FileTime modifiedTime = Files.getLastModifiedTime(path); + if (mostRecent == null || modifiedTime.compareTo(mostRecent) > 0) { + mostRecent = modifiedTime; + } + } + } + } + + return mostRecent; + } +} diff --git a/src/com/hypixel/hytale/server/core/util/backup/BackupUtil.java b/src/com/hypixel/hytale/server/core/util/backup/BackupUtil.java new file mode 100644 index 0000000..751c5fc --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/backup/BackupUtil.java @@ -0,0 +1,99 @@ +package com.hypixel.hytale.server.core.util.backup; + +import com.hypixel.hytale.common.util.java.ManifestUtil; +import com.hypixel.hytale.protocol.packets.interface_.WorldSavingStatus; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.permissions.PermissionsModule; +import com.hypixel.hytale.server.core.universe.Universe; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Stream; +import java.util.zip.CRC32; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +class BackupUtil { + BackupUtil() { + } + + static void walkFileTreeAndZip(@Nonnull Path sourceDir, @Nonnull Path zipPath) throws IOException { + try (ZipOutputStream zipOutputStream = new ZipOutputStream(Files.newOutputStream(zipPath))) { + zipOutputStream.setMethod(0); + zipOutputStream.setLevel(0); + zipOutputStream.setComment( + "Automated backup by HytaleServer - Version: " + + ManifestUtil.getImplementationVersion() + + ", Revision: " + + ManifestUtil.getImplementationRevisionId() + ); + + try (Stream stream = Files.walk(sourceDir)) { + for (Path path : stream.filter(x$0 -> Files.isRegularFile(x$0)).toList()) { + long size = Files.size(path); + CRC32 crc = new CRC32(); + + try (InputStream inputStream = Files.newInputStream(path)) { + byte[] buffer = new byte[16384]; + + int len; + while ((len = inputStream.read(buffer)) != -1) { + crc.update(buffer, 0, len); + } + } + + ZipEntry zipEntry = new ZipEntry(sourceDir.relativize(path).toString()); + zipEntry.setSize(size); + zipEntry.setCompressedSize(size); + zipEntry.setCrc(crc.getValue()); + zipOutputStream.putNextEntry(zipEntry); + Files.copy(path, zipOutputStream); + zipOutputStream.closeEntry(); + } + } + } + } + + static void broadcastBackupStatus(boolean isWorldSaving) { + Universe.get().broadcastPacket(new WorldSavingStatus(isWorldSaving)); + } + + static void broadcastBackupError(Throwable cause) { + Message message = Message.translation("server.universe.backup.error").param("message", cause.getLocalizedMessage()); + Universe.get().getPlayers().forEach(player -> { + boolean hasPermission = PermissionsModule.get().hasPermission(player.getUuid(), "hytale.status.backup.error"); + if (hasPermission) { + player.sendMessage(message); + } + }); + } + + @Nullable + static List findOldBackups(@Nonnull Path backupDirectory, int maxBackupCount) throws IOException { + if (!backupDirectory.toFile().isDirectory()) { + return null; + } else { + try (Stream files = Files.list(backupDirectory)) { + List zipFiles = files.filter(p -> p.getFileName().toString().endsWith(".zip")).sorted(Comparator.comparing(p -> { + try { + return Files.readAttributes(p, BasicFileAttributes.class).creationTime(); + } catch (IOException var2x) { + return FileTime.fromMillis(0L); + } + })).toList(); + if (zipFiles.size() > maxBackupCount) { + return zipFiles.subList(0, zipFiles.size() - maxBackupCount); + } + } + + return null; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/util/concurrent/ThreadUtil.java b/src/com/hypixel/hytale/server/core/util/concurrent/ThreadUtil.java new file mode 100644 index 0000000..f6288ec --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/concurrent/ThreadUtil.java @@ -0,0 +1,94 @@ +package com.hypixel.hytale.server.core.util.concurrent; + +import java.security.Permission; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Semaphore; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; +import java.util.function.Predicate; +import javax.annotation.Nonnull; + +public class ThreadUtil { + public ThreadUtil() { + } + + public static void forceTimeHighResolution() { + Thread t = new Thread(() -> { + try { + while (!Thread.interrupted()) { + Thread.sleep(Long.MAX_VALUE); + } + } catch (InterruptedException var1) { + Thread.currentThread().interrupt(); + } + }, "ForceTimeHighResolution"); + t.setDaemon(true); + t.start(); + } + + public static void createKeepAliveThread(@Nonnull Semaphore alive) { + Thread t = new Thread(() -> { + try { + alive.acquire(); + } catch (InterruptedException var2) { + Thread.currentThread().interrupt(); + } + }, "KeepAlive"); + t.setDaemon(false); + t.start(); + } + + @Nonnull + public static ExecutorService newCachedThreadPool(int maximumPoolSize, @Nonnull ThreadFactory threadFactory) { + return new ThreadPoolExecutor(0, maximumPoolSize, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), threadFactory); + } + + @Nonnull + public static ThreadFactory daemon(@Nonnull String name) { + return r -> { + Thread t = new Thread(r, name); + t.setDaemon(true); + return t; + }; + } + + @Nonnull + public static ThreadFactory daemonCounted(@Nonnull String name) { + AtomicLong count = new AtomicLong(); + return r -> { + Thread t = new Thread(r, String.format(name, count.incrementAndGet())); + t.setDaemon(true); + return t; + }; + } + + static class ThreadWatcher extends SecurityManager { + private final Predicate predicate; + private final Consumer action; + + public ThreadWatcher(Predicate predicate, Consumer action) { + this.predicate = predicate; + this.action = action; + } + + @Override + public void checkPermission(Permission perm) { + } + + @Override + public void checkPermission(Permission perm, Object context) { + } + + @Override + public void checkAccess(ThreadGroup g) { + Thread creatingThread = Thread.currentThread(); + if (this.predicate.test(creatingThread)) { + this.action.accept(creatingThread); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/util/io/BlockingDiskFile.java b/src/com/hypixel/hytale/server/core/util/io/BlockingDiskFile.java new file mode 100644 index 0000000..a58c66c --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/io/BlockingDiskFile.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.server.core.util.io; + +import com.hypixel.hytale.server.core.Options; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import javax.annotation.Nonnull; + +public abstract class BlockingDiskFile { + protected final ReadWriteLock fileLock = new ReentrantReadWriteLock(); + protected final Path path; + + public BlockingDiskFile(Path path) { + this.path = path; + } + + protected abstract void read(BufferedReader var1) throws IOException; + + protected abstract void write(BufferedWriter var1) throws IOException; + + protected abstract void create(BufferedWriter var1) throws IOException; + + public void syncLoad() { + this.fileLock.writeLock().lock(); + + try { + File file = this.toLocalFile(); + + try { + if (!file.exists()) { + if (Options.getOptionSet().has(Options.BARE)) { + byte[] bytes; + try ( + ByteArrayOutputStream out = new ByteArrayOutputStream(); + BufferedWriter buf = new BufferedWriter(new OutputStreamWriter(out)); + ) { + this.create(buf); + bytes = out.toByteArray(); + } + + try (BufferedReader var34 = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes)))) { + this.read(var34); + return; + } + } + + try (BufferedWriter fileWriter = Files.newBufferedWriter(file.toPath())) { + this.create(fileWriter); + } + } + + try (BufferedReader fileReader = Files.newBufferedReader(file.toPath())) { + this.read(fileReader); + } + } catch (Exception var30) { + throw new RuntimeException("Failed to syncLoad() " + file.getAbsolutePath(), var30); + } + } finally { + this.fileLock.writeLock().unlock(); + } + } + + public void syncSave() { + File file = null; + this.fileLock.readLock().lock(); + + try { + file = this.toLocalFile(); + + try (BufferedWriter fileWriter = Files.newBufferedWriter(file.toPath())) { + this.write(fileWriter); + } + } catch (Exception var12) { + throw new RuntimeException("Failed to syncSave() " + (file != null ? file.getAbsolutePath() : null), var12); + } finally { + this.fileLock.readLock().unlock(); + } + } + + @Nonnull + protected File toLocalFile() { + return this.path.toFile(); + } +} diff --git a/src/com/hypixel/hytale/server/core/util/io/ByteBufUtil.java b/src/com/hypixel/hytale/server/core/util/io/ByteBufUtil.java new file mode 100644 index 0000000..eb59e03 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/io/ByteBufUtil.java @@ -0,0 +1,137 @@ +package com.hypixel.hytale.server.core.util.io; + +import com.hypixel.hytale.common.util.BitSetUtil; +import com.hypixel.hytale.unsafe.UnsafeUtil; +import io.netty.buffer.ByteBuf; +import java.nio.charset.StandardCharsets; +import java.util.BitSet; +import javax.annotation.Nonnull; + +public class ByteBufUtil { + private static int MAX_UNSIGNED_SHORT_VALUE = 65535; + + public ByteBufUtil() { + } + + public static void writeUTF(@Nonnull ByteBuf buf, @Nonnull String string) { + if (io.netty.buffer.ByteBufUtil.utf8MaxBytes(string) >= MAX_UNSIGNED_SHORT_VALUE) { + throw new IllegalArgumentException("String is too large"); + } else { + int before = buf.writerIndex(); + buf.writeShort(-1); + int length = buf.writeCharSequence(string, StandardCharsets.UTF_8); + if (length >= 0 && length < MAX_UNSIGNED_SHORT_VALUE) { + int after = buf.writerIndex(); + buf.writerIndex(before); + buf.writeShort(length); + buf.writerIndex(after); + } else { + throw new IllegalArgumentException("Serialized string is too large"); + } + } + } + + @Nonnull + public static String readUTF(@Nonnull ByteBuf buf) { + int length = buf.readUnsignedShort(); + return buf.readCharSequence(length, StandardCharsets.UTF_8).toString(); + } + + public static void writeByteArray(@Nonnull ByteBuf buf, @Nonnull byte[] arr) { + writeByteArray(buf, arr, 0, arr.length); + } + + public static void writeByteArray(@Nonnull ByteBuf buf, byte[] arr, int src, int length) { + if (length > 0 && length < MAX_UNSIGNED_SHORT_VALUE) { + buf.writeShort(length); + buf.writeBytes(arr, src, length); + } else { + throw new IllegalArgumentException("length is too large"); + } + } + + public static byte[] readByteArray(@Nonnull ByteBuf buf) { + int length = buf.readUnsignedShort(); + byte[] bytes = new byte[length]; + buf.readBytes(bytes); + return bytes; + } + + public static byte[] getBytesRelease(@Nonnull ByteBuf buf) { + byte[] var1; + try { + var1 = io.netty.buffer.ByteBufUtil.getBytes(buf, 0, buf.writerIndex(), false); + } finally { + buf.release(); + } + + return var1; + } + + public static void writeNumber(@Nonnull ByteBuf buf, int bytes, int value) { + switch (bytes) { + case 1: + buf.writeByte(value); + break; + case 2: + buf.writeShort(value); + case 3: + default: + break; + case 4: + buf.writeInt(value); + } + } + + public static int readNumber(@Nonnull ByteBuf buf, int bytes) { + return switch (bytes) { + case 1 -> buf.readByte() & 255; + case 2 -> buf.readShort() & '\uffff'; + default -> 0; + case 4 -> buf.readInt(); + }; + } + + public static void writeBitSet(@Nonnull ByteBuf buf, @Nonnull BitSet bitset) { + int wordsInUse; + long[] words; + if (UnsafeUtil.UNSAFE == null) { + words = bitset.toLongArray(); + wordsInUse = words.length; + } else { + wordsInUse = UnsafeUtil.UNSAFE.getInt(bitset, BitSetUtil.WORDS_IN_USE_OFFSET); + words = (long[])UnsafeUtil.UNSAFE.getObject(bitset, BitSetUtil.WORDS_OFFSET); + } + + buf.writeInt(wordsInUse); + + for (int i = 0; i < wordsInUse; i++) { + buf.writeLong(words[i]); + } + } + + public static void readBitSet(@Nonnull ByteBuf buf, @Nonnull BitSet bitset) { + int wordsInUse = buf.readInt(); + if (UnsafeUtil.UNSAFE == null) { + long[] words = new long[wordsInUse]; + + for (int i = 0; i < wordsInUse; i++) { + words[i] = buf.readLong(); + } + + bitset.clear(); + bitset.or(BitSet.valueOf(words)); + } else { + UnsafeUtil.UNSAFE.putInt(bitset, BitSetUtil.WORDS_IN_USE_OFFSET, wordsInUse); + long[] words = (long[])UnsafeUtil.UNSAFE.getObject(bitset, BitSetUtil.WORDS_OFFSET); + if (wordsInUse > words.length) { + words = new long[wordsInUse]; + UnsafeUtil.UNSAFE.putObject(bitset, BitSetUtil.WORDS_OFFSET, words); + } + + for (int i = 0; i < wordsInUse; i++) { + words[i] = buf.readLong(); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/core/util/io/FileUtil.java b/src/com/hypixel/hytale/server/core/util/io/FileUtil.java new file mode 100644 index 0000000..3716b00 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/io/FileUtil.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.server.core.util.io; + +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.CopyOption; +import java.nio.file.FileVisitOption; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Comparator; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipInputStream; +import javax.annotation.Nonnull; + +public class FileUtil { + public static final Set DEFAULT_WRITE_OPTIONS = Set.of(StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); + public static final Set DEFAULT_WALK_TREE_OPTIONS_SET = Set.of(); + public static final FileVisitOption[] DEFAULT_WALK_TREE_OPTIONS_ARRAY = new FileVisitOption[0]; + public static final Pattern INVALID_FILENAME_CHARACTERS = Pattern.compile("[<>:\"|?*/\\\\]"); + + public FileUtil() { + } + + public static void unzipFile(@Nonnull Path path, @Nonnull byte[] buffer, @Nonnull ZipInputStream zipStream, @Nonnull ZipEntry zipEntry, @Nonnull String name) throws IOException { + Path filePath = path.resolve(name); + if (!filePath.toAbsolutePath().startsWith(path)) { + throw new ZipException("Entry is outside of the target dir: " + zipEntry.getName()); + } else { + if (zipEntry.isDirectory()) { + Files.createDirectory(filePath); + } else { + int len; + try (OutputStream stream = Files.newOutputStream(filePath)) { + while ((len = zipStream.read(buffer)) > 0) { + stream.write(buffer, 0, len); + } + } + } + + zipStream.closeEntry(); + } + } + + public static void copyDirectory(@Nonnull Path origin, @Nonnull Path destination) throws IOException { + try (Stream paths = Files.walk(origin)) { + paths.forEach(originSubPath -> { + try { + Path relative = origin.relativize(originSubPath); + Path destinationSubPath = destination.resolve(relative); + Files.copy(originSubPath, destinationSubPath); + } catch (Throwable var5) { + throw new RuntimeException("Error copying path", var5); + } + }); + } + } + + public static void moveDirectoryContents(@Nonnull Path origin, @Nonnull Path destination, CopyOption... options) throws IOException { + try (Stream paths = Files.walk(origin)) { + paths.forEach(originSubPath -> { + if (!originSubPath.equals(origin)) { + try { + Path relative = origin.relativize(originSubPath); + Path destinationSubPath = destination.resolve(relative); + Files.move(originSubPath, destinationSubPath, options); + } catch (Throwable var6) { + throw new RuntimeException("Error moving path", var6); + } + } + }); + } + } + + public static void deleteDirectory(@Nonnull Path path) throws IOException { + try (Stream stream = Files.walk(path)) { + stream.sorted(Comparator.reverseOrder()).forEach(SneakyThrow.sneakyConsumer(Files::delete)); + } + } +} diff --git a/src/com/hypixel/hytale/server/core/util/message/MessageFormat.java b/src/com/hypixel/hytale/server/core/util/message/MessageFormat.java new file mode 100644 index 0000000..c1c1a75 --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/message/MessageFormat.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.core.util.message; + +import com.hypixel.hytale.server.core.Message; +import java.awt.Color; +import java.util.Collection; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class MessageFormat { + private static final Message ENABLED = Message.translation("server.general.enabled").color(Color.GREEN); + private static final Message DISABLED = Message.translation("server.general.disabled").color(Color.RED); + private static final int LIST_MAX_INLINE_VALUES = 4; + + public MessageFormat() { + } + + @Nonnull + public static Message enabled(boolean b) { + return b ? ENABLED : DISABLED; + } + + @Nonnull + public static Message list(@Nullable Message header, @Nonnull Collection values) { + Message msg = Message.empty(); + if (header != null) { + msg.insert(Message.translation("server.formatting.list.header").param("header", header).param("count", values.size())); + if (values.size() <= 4) { + msg.insert(Message.translation("server.formatting.list.inlineHeaderSuffix")); + } + } + + if (values.isEmpty()) { + msg.insert(Message.translation("server.formatting.list.empty")); + return msg; + } else { + if (values.size() <= 4) { + Message separator = Message.translation("server.formatting.list.itemSeparator"); + Message[] array = values.toArray(Message[]::new); + + for (int i = 0; i < array.length; i++) { + msg.insert(array[i]); + if (i < array.length - 1) { + msg.insert(separator); + } + } + } else { + Message delim = Message.raw("\n"); + + for (Message value : values) { + msg.insert(delim); + msg.insert(Message.translation("server.formatting.list.row").param("value", value)); + } + } + + return msg; + } + } +} diff --git a/src/com/hypixel/hytale/server/core/util/thread/TickingThread.java b/src/com/hypixel/hytale/server/core/util/thread/TickingThread.java new file mode 100644 index 0000000..ef3de5f --- /dev/null +++ b/src/com/hypixel/hytale/server/core/util/thread/TickingThread.java @@ -0,0 +1,207 @@ +package com.hypixel.hytale.server.core.util.thread; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.metrics.metric.HistoricMetric; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class TickingThread implements Runnable { + public static final int NANOS_IN_ONE_MILLI = 1000000; + public static final int NANOS_IN_ONE_SECOND = 1000000000; + public static final int TPS = 30; + public static long SLEEP_OFFSET = 3000000L; + private final String threadName; + private final boolean daemon; + private final AtomicBoolean needsShutdown = new AtomicBoolean(true); + private int tps; + private int tickStepNanos; + private HistoricMetric bufferedTickLengthMetricSet; + @Nullable + private Thread thread; + @Nonnull + private CompletableFuture startedFuture = new CompletableFuture<>(); + + public TickingThread(String threadName) { + this(threadName, 30, false); + } + + public TickingThread(String threadName, int tps, boolean daemon) { + this.threadName = threadName; + this.daemon = daemon; + this.tps = tps; + this.tickStepNanos = 1000000000 / tps; + this.bufferedTickLengthMetricSet = HistoricMetric.builder(this.tickStepNanos, TimeUnit.NANOSECONDS) + .addPeriod(10L, TimeUnit.SECONDS) + .addPeriod(1L, TimeUnit.MINUTES) + .addPeriod(5L, TimeUnit.MINUTES) + .build(); + } + + @Override + public void run() { + try { + this.onStart(); + this.startedFuture.complete(null); + long beforeTick = System.nanoTime() - this.tickStepNanos; + + while (this.thread != null && !this.thread.isInterrupted()) { + long delta; + if (!this.isIdle()) { + while ((delta = System.nanoTime() - beforeTick) < this.tickStepNanos) { + Thread.onSpinWait(); + } + } else { + delta = System.nanoTime() - beforeTick; + } + + beforeTick = System.nanoTime(); + this.tick((float)delta / 1.0E9F); + long tickLength = System.nanoTime() - beforeTick; + this.bufferedTickLengthMetricSet.add(System.nanoTime(), tickLength); + long sleepLength = this.tickStepNanos - tickLength; + if (!this.isIdle()) { + sleepLength -= SLEEP_OFFSET; + } + + if (sleepLength > 0L) { + Thread.sleep(sleepLength / 1000000L); + } + } + } catch (InterruptedException var9) { + Thread.currentThread().interrupt(); + } catch (Throwable var10) { + HytaleLogger.getLogger().at(Level.SEVERE).withCause(var10).log("Exception in thread %s:", this.thread); + } + + if (this.needsShutdown.getAndSet(false)) { + this.onShutdown(); + } + } + + protected boolean isIdle() { + return false; + } + + protected abstract void tick(float var1); + + protected void onStart() { + } + + protected abstract void onShutdown(); + + @Nonnull + public CompletableFuture start() { + if (this.thread == null) { + this.thread = new Thread(this, this.threadName); + this.thread.setDaemon(this.daemon); + } else if (this.thread.isAlive()) { + throw new IllegalStateException("Thread '" + this.thread.getName() + "' is already started!"); + } + + this.thread.start(); + return this.startedFuture; + } + + public boolean interrupt() { + if (this.thread != null && this.thread.isAlive()) { + this.thread.interrupt(); + return true; + } else { + return false; + } + } + + public void stop() { + Thread thread = this.thread; + if (thread != null) { + try { + int i = 0; + + while (thread.isAlive()) { + thread.interrupt(); + thread.join(this.tickStepNanos / 1000000); + i += this.tickStepNanos / 1000000; + if (i > 30000) { + StringBuilder sb = new StringBuilder(); + + for (StackTraceElement traceElement : thread.getStackTrace()) { + sb.append("\tat ").append(traceElement).append('\n'); + } + + HytaleLogger.getLogger().at(Level.SEVERE).log("Forcing TickingThread %s to stop:\n%s", thread, sb.toString()); + thread.stop(); + Thread var9 = null; + if (this.needsShutdown.getAndSet(false)) { + this.onShutdown(); + } + + return; + } + } + + Thread var10 = null; + } catch (InterruptedException var8) { + Thread.currentThread().interrupt(); + } + } + } + + public void setTps(int tps) { + this.debugAssertInTickingThread(); + if (tps > 0 && tps <= 2048) { + this.tps = tps; + this.tickStepNanos = 1000000000 / tps; + this.bufferedTickLengthMetricSet = HistoricMetric.builder(this.tickStepNanos, TimeUnit.NANOSECONDS) + .addPeriod(10L, TimeUnit.SECONDS) + .addPeriod(1L, TimeUnit.MINUTES) + .addPeriod(5L, TimeUnit.MINUTES) + .build(); + } else { + throw new IllegalArgumentException("UpdatesPerSecond is out of bounds (<=0 or >2048): " + tps); + } + } + + public int getTps() { + return this.tps; + } + + public int getTickStepNanos() { + return this.tickStepNanos; + } + + public HistoricMetric getBufferedTickLengthMetricSet() { + return this.bufferedTickLengthMetricSet; + } + + public void clearMetrics() { + this.bufferedTickLengthMetricSet.clear(); + } + + public void debugAssertInTickingThread() { + if (!Thread.currentThread().equals(this.thread) && this.thread != null) { + throw new AssertionError("Assert not in ticking thread!"); + } + } + + public boolean isInThread() { + return Thread.currentThread().equals(this.thread); + } + + public boolean isStarted() { + return this.thread != null && this.thread.isAlive() && this.needsShutdown.get(); + } + + @Deprecated + protected void setThread(Thread thread) { + this.thread = thread; + } + + @Nullable + protected Thread getThread() { + return this.thread; + } +} diff --git a/src/com/hypixel/hytale/server/flock/Flock.java b/src/com/hypixel/hytale/server/flock/Flock.java new file mode 100644 index 0000000..68f1054 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/Flock.java @@ -0,0 +1,134 @@ +package com.hypixel.hytale.server.flock; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.config.FlockAsset; +import com.hypixel.hytale.server.npc.util.DamageData; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class Flock implements Component { + private boolean trace; + private PersistentFlockData flockData; + private DamageData nextDamageData = new DamageData(); + private DamageData currentDamageData = new DamageData(); + private DamageData nextLeaderDamageData = new DamageData(); + private DamageData currentLeaderDamageData = new DamageData(); + private Flock.FlockRemovedStatus removedStatus = Flock.FlockRemovedStatus.NOT_REMOVED; + + public static ComponentType getComponentType() { + return FlockPlugin.get().getFlockComponentType(); + } + + public Flock() { + } + + public Flock(@Nullable FlockAsset flockDefinition, @Nonnull String[] allowedRoles) { + this.flockData = new PersistentFlockData(flockDefinition, allowedRoles); + } + + public DamageData getDamageData() { + return this.currentDamageData; + } + + public DamageData getNextDamageData() { + return this.nextDamageData; + } + + public DamageData getLeaderDamageData() { + return this.currentLeaderDamageData; + } + + public DamageData getNextLeaderDamageData() { + return this.nextLeaderDamageData; + } + + public boolean isTrace() { + return this.trace; + } + + public void setTrace(boolean trace) { + this.trace = trace; + } + + public PersistentFlockData getFlockData() { + return this.flockData; + } + + public void setFlockData(PersistentFlockData flockData) { + this.flockData = flockData; + } + + public Flock.FlockRemovedStatus getRemovedStatus() { + return this.removedStatus; + } + + public void setRemovedStatus(Flock.FlockRemovedStatus status) { + this.removedStatus = status; + } + + public void onTargetKilled(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref targetEntityReference) { + TransformComponent targetTransformComponent = componentAccessor.getComponent(targetEntityReference, TransformComponent.getComponentType()); + if (targetTransformComponent != null) { + this.nextDamageData.onKill(targetEntityReference, targetTransformComponent.getPosition().clone()); + } + } + + public void swapDamageDataBuffers() { + DamageData nextData = this.nextDamageData; + this.nextDamageData = this.currentDamageData; + this.currentDamageData = nextData; + DamageData nextLeaderData = this.nextLeaderDamageData; + this.nextLeaderDamageData = this.currentLeaderDamageData; + this.currentLeaderDamageData = nextLeaderData; + this.nextDamageData.reset(); + this.nextLeaderDamageData.reset(); + } + + @Nonnull + @Override + public Component clone() { + Flock flock = new Flock(); + flock.trace = this.trace; + flock.flockData = (PersistentFlockData)this.flockData.clone(); + flock.nextDamageData = this.nextDamageData.clone(); + flock.currentDamageData = this.currentDamageData.clone(); + flock.nextLeaderDamageData = this.nextLeaderDamageData.clone(); + flock.currentLeaderDamageData = this.currentLeaderDamageData.clone(); + flock.removedStatus = this.removedStatus; + return flock; + } + + @Nonnull + @Override + public String toString() { + return "Flock{trace=" + + this.trace + + ", flockData=" + + this.flockData + + ", nextDamageData=" + + this.nextDamageData + + ", currentDamageData=" + + this.currentDamageData + + ", nextLeaderDamageData=" + + this.nextLeaderDamageData + + ", currentLeaderDamageData=" + + this.currentLeaderDamageData + + ", removedStatus=" + + this.removedStatus + + "}"; + } + + public static enum FlockRemovedStatus { + NOT_REMOVED, + DISSOLVED, + UNLOADED; + + private FlockRemovedStatus() { + } + } +} diff --git a/src/com/hypixel/hytale/server/flock/FlockDeathSystems.java b/src/com/hypixel/hytale/server/flock/FlockDeathSystems.java new file mode 100644 index 0000000..b24e35e --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/FlockDeathSystems.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.server.flock; + +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.AllLegacyLivingEntityTypesQuery; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathSystems; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import javax.annotation.Nonnull; + +public class FlockDeathSystems { + public FlockDeathSystems() { + } + + public static class EntityDeath extends DeathSystems.OnDeathSystem { + @Nonnull + private final Query query = Query.and(AllLegacyLivingEntityTypesQuery.INSTANCE, Query.not(Player.getComponentType())); + + public EntityDeath() { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + NPCEntity npcComponent = commandBuffer.getComponent(ref, NPCEntity.getComponentType()); + if (npcComponent != null) { + Role role = npcComponent.getRole(); + if (role != null && !role.isCorpseStaysInFlock()) { + commandBuffer.tryRemoveComponent(ref, FlockMembership.getComponentType()); + } + } + + Damage damageInfo = component.getDeathInfo(); + Ref attackerRef = null; + if (damageInfo != null && damageInfo.getSource() instanceof Damage.EntitySource entitySource) { + attackerRef = entitySource.getRef(); + } + + if (attackerRef != null) { + Flock attackerFlock = FlockPlugin.getFlock(commandBuffer, attackerRef); + if (attackerFlock != null) { + attackerFlock.onTargetKilled(commandBuffer, ref); + } + } + } + } + + public static class PlayerDeath extends DeathSystems.OnDeathSystem { + public PlayerDeath() { + } + + @Nonnull + @Override + public Query getQuery() { + return Player.getComponentType(); + } + + public void onComponentAdded( + @Nonnull Ref ref, @Nonnull DeathComponent component, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + commandBuffer.tryRemoveComponent(ref, FlockMembership.getComponentType()); + } + } +} diff --git a/src/com/hypixel/hytale/server/flock/FlockMembership.java b/src/com/hypixel/hytale/server/flock/FlockMembership.java new file mode 100644 index 0000000..00e1698 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/FlockMembership.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.server.flock; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.EnumCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FlockMembership implements Component { + public static final int VERSION = 5; + public static final BuilderCodec CODEC = BuilderCodec.builder(FlockMembership.class, FlockMembership::new) + .legacyVersioned() + .codecVersion(5) + .append(new KeyedCodec<>("FlockId", Codec.UUID_BINARY), (membership, uuid) -> membership.flockId = uuid, membership -> membership.flockId) + .setVersionRange(5, 5) + .add() + .append( + new KeyedCodec<>("Type", new EnumCodec<>(FlockMembership.Type.class, EnumCodec.EnumStyle.LEGACY)), + (membership, type) -> membership.membershipType = type, + membership -> membership.membershipType + ) + .add() + .build(); + private UUID flockId; + private FlockMembership.Type membershipType; + @Nullable + private Ref flockRef; + + public FlockMembership() { + } + + public static ComponentType getComponentType() { + return FlockPlugin.get().getFlockMembershipComponentType(); + } + + public UUID getFlockId() { + return this.flockId; + } + + public void setFlockId(UUID flockId) { + this.flockId = flockId; + } + + @Nullable + public Ref getFlockRef() { + return this.flockRef; + } + + public void setFlockRef(Ref flockRef) { + this.flockRef = flockRef; + } + + public void setMembershipType(FlockMembership.Type membershipType) { + this.membershipType = membershipType; + } + + public FlockMembership.Type getMembershipType() { + return this.membershipType; + } + + public void unload() { + this.flockRef = null; + } + + @Nonnull + @Override + public Component clone() { + FlockMembership membership = new FlockMembership(); + membership.setFlockId(this.flockId); + membership.setFlockRef(this.flockRef); + membership.setMembershipType(this.membershipType); + return membership; + } + + public static enum Type { + JOINING(false), + MEMBER(false), + LEADER(true), + INTERIM_LEADER(true); + + private final boolean actsAsLeader; + + private Type(boolean actsAsLeader) { + this.actsAsLeader = actsAsLeader; + } + + public boolean isActingAsLeader() { + return this.actsAsLeader; + } + } +} diff --git a/src/com/hypixel/hytale/server/flock/FlockMembershipSystems.java b/src/com/hypixel/hytale/server/flock/FlockMembershipSystems.java new file mode 100644 index 0000000..a307393 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/FlockMembershipSystems.java @@ -0,0 +1,770 @@ +package com.hypixel.hytale.server.flock; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.SystemGroup; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.RefChangeSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.group.EntityGroup; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.FromWorldGen; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageEventSystem; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageModule; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.RoleDebugFlags; +import java.util.EnumSet; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FlockMembershipSystems { + public FlockMembershipSystems() { + } + + public static boolean canJoinFlock(@Nonnull Ref reference, @Nonnull Ref flockReference, @Nonnull Store store) { + Flock flockComponent = store.getComponent(flockReference, Flock.getComponentType()); + + assert flockComponent != null; + + PersistentFlockData flockData = flockComponent.getFlockData(); + if (flockData == null) { + return false; + } else { + EntityGroup entityGroupComponent = store.getComponent(flockReference, EntityGroup.getComponentType()); + + assert entityGroupComponent != null; + + if (entityGroupComponent.size() >= flockData.getMaxGrowSize()) { + return false; + } else { + NPCEntity npcComponent = store.getComponent(reference, NPCEntity.getComponentType()); + if (npcComponent == null) { + return false; + } else { + String roleName = npcComponent.getRoleName(); + return roleName != null && flockData.isFlockAllowedRole(roleName); + } + } + } + } + + public static void join(@Nonnull Ref ref, @Nonnull Ref flockRef, @Nonnull Store store) { + FlockMembership membership = new FlockMembership(); + UUIDComponent uuidComponent = store.getComponent(flockRef, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + membership.setFlockId(uuidComponent.getUuid()); + membership.setFlockRef(flockRef); + membership.setMembershipType(FlockMembership.Type.JOINING); + store.putComponent(ref, FlockMembership.getComponentType(), membership); + } + + private static boolean canBecomeLeader(@Nonnull Ref ref) { + Store store = ref.getStore(); + if (store.getComponent(ref, Player.getComponentType()) != null) { + return true; + } else { + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + return npcComponent != null && npcComponent.getRole() != null ? npcComponent.getRole().isCanLeadFlock() : false; + } + } + + private static void markChunkNeedsSaving(@Nonnull Ref ref, @Nonnull Store store) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + if (transformComponent != null) { + transformComponent.markChunkDirty(store); + } + } + + public static class EntityRef extends RefSystem { + @Nonnull + private final ComponentType flockMembershipComponentType; + + public EntityRef(@Nonnull ComponentType flockMembershipComponentType) { + this.flockMembershipComponentType = flockMembershipComponentType; + } + + @Override + public Query getQuery() { + return this.flockMembershipComponentType; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + commandBuffer.run(_store -> this.joinOrCreateFlock(ref, _store)); + } + + private void joinOrCreateFlock(@Nonnull Ref ref, @Nonnull Store store) { + FlockMembership flockMembershipComponent = store.getComponent(ref, this.flockMembershipComponentType); + + assert flockMembershipComponent != null; + + UUID flockId = flockMembershipComponent.getFlockId(); + Ref flockReference = store.getExternalData().getRefFromUUID(flockId); + EntityGroup entityGroup; + Flock flock; + if (flockReference != null) { + entityGroup = store.getComponent(flockReference, EntityGroup.getComponentType()); + + assert entityGroup != null; + + flock = store.getComponent(flockReference, Flock.getComponentType()); + + assert flock != null; + } else { + entityGroup = new EntityGroup(); + flock = new Flock(); + Holder holder = EntityStore.REGISTRY.newHolder(); + holder.addComponent(UUIDComponent.getComponentType(), new UUIDComponent(flockId)); + holder.addComponent(EntityGroup.getComponentType(), entityGroup); + holder.addComponent(Flock.getComponentType(), flock); + flockReference = store.addEntity(holder, AddReason.LOAD); + } + + flockMembershipComponent.setFlockRef(flockReference); + if (entityGroup.isMember(ref)) { + throw new IllegalStateException(String.format("Entity %s attempting to reload into group with ID %s despite already being a member", ref, flockId)); + } else { + entityGroup.add(ref); + if (flockMembershipComponent.getMembershipType() == FlockMembership.Type.LEADER) { + PersistentFlockData persistentFlockData = store.getComponent(ref, PersistentFlockData.getComponentType()); + if (persistentFlockData != null) { + flock.setFlockData(persistentFlockData); + } else { + PersistentFlockData flockData = flock.getFlockData(); + if (flockData != null) { + store.putComponent(ref, PersistentFlockData.getComponentType(), flockData); + } + } + + Ref oldLeaderRef = entityGroup.getLeaderRef(); + entityGroup.setLeaderRef(ref); + if (oldLeaderRef != null && !oldLeaderRef.equals(ref)) { + FlockMembership oldLeaderComponent = store.getComponent(oldLeaderRef, this.flockMembershipComponentType); + if (oldLeaderComponent != null) { + oldLeaderComponent.setMembershipType(FlockMembership.Type.MEMBER); + } + + store.tryRemoveComponent(oldLeaderRef, PersistentFlockData.getComponentType()); + FlockMembershipSystems.markChunkNeedsSaving(oldLeaderRef, store); + } + + markNeedsSave(ref, store, flock); + if (flock.isTrace()) { + FlockPlugin.get() + .getLogger() + .at(Level.INFO) + .log("Flock %s: Set new leader, old=%s, new=%s, size=%s", flockId, oldLeaderRef, ref, entityGroup.size()); + } + } else if (entityGroup.getLeaderRef() == null) { + setInterimLeader(store, flockMembershipComponent, entityGroup, ref, flock, flockId); + } + + if (flock.isTrace()) { + FlockPlugin.get().getLogger().at(Level.INFO).log("Flock %s: reference=%s, size=%s", flockId, ref, entityGroup.size()); + } + } + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + FlockMembership membership = store.getComponent(ref, this.flockMembershipComponentType); + + assert membership != null; + + Ref flockRef = membership.getFlockRef(); + if (flockRef != null && flockRef.isValid()) { + Flock flockComponent = commandBuffer.getComponent(flockRef, Flock.getComponentType()); + + assert flockComponent != null; + + EntityGroup entityGroupComponent = commandBuffer.getComponent(flockRef, EntityGroup.getComponentType()); + + assert entityGroupComponent != null; + + UUIDComponent uuidComponent = commandBuffer.getComponent(flockRef, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID flockId = uuidComponent.getUuid(); + if (reason == RemoveReason.REMOVE || store.getArchetype(ref).contains(Player.getComponentType())) { + entityGroupComponent.remove(ref); + if (flockComponent.isTrace()) { + FlockPlugin.get() + .getLogger() + .at(Level.INFO) + .log( + "Flock %s: Left flock, reference=%s, leader=%s, size=%s", + flockId, + ref, + entityGroupComponent.getLeaderRef(), + entityGroupComponent.size() + ); + } + + if (!entityGroupComponent.isDissolved() && entityGroupComponent.size() < 2) { + commandBuffer.removeEntity(flockRef, RemoveReason.REMOVE); + return; + } + + if (entityGroupComponent.isDissolved()) { + return; + } + + PersistentFlockData flockData = flockComponent.getFlockData(); + if (flockData != null) { + flockData.decreaseSize(); + } + + Ref leader = entityGroupComponent.getLeaderRef(); + if (leader != null && !leader.equals(ref)) { + FlockMembershipSystems.markChunkNeedsSaving(leader, store); + return; + } + + Ref newLeader = entityGroupComponent.testMembers(FlockMembershipSystems::canBecomeLeader, true); + if (newLeader == null) { + if (flockComponent.isTrace()) { + FlockPlugin.get() + .getLogger() + .at(Level.INFO) + .log( + "Flock %s: Leave failed to get new leader, reference=%s, leader=%s, size=%s", + flockId, + ref, + entityGroupComponent.getLeaderRef(), + entityGroupComponent.size() + ); + } + + commandBuffer.removeEntity(flockRef, RemoveReason.REMOVE); + return; + } + + entityGroupComponent.setLeaderRef(newLeader); + FlockMembership flockMembershipComponent = store.getComponent(newLeader, this.flockMembershipComponentType); + + assert flockMembershipComponent != null; + + flockMembershipComponent.setMembershipType(FlockMembership.Type.LEADER); + if (flockData != null) { + commandBuffer.putComponent(newLeader, PersistentFlockData.getComponentType(), flockData); + } + + markNeedsSave(newLeader, store, flockComponent); + if (flockComponent.isTrace()) { + FlockPlugin.get() + .getLogger() + .at(Level.INFO) + .log("Flock %s: Set new leader, old=%s, new=%s, size=%s", flockId, ref, newLeader, entityGroupComponent.size()); + } + } else if (reason == RemoveReason.UNLOAD) { + entityGroupComponent.remove(ref); + if (!entityGroupComponent.isDissolved() && membership.getMembershipType().isActingAsLeader()) { + Ref interimLeader = entityGroupComponent.testMembers(member -> true, true); + if (interimLeader != null) { + FlockMembership interimLeaderMembership = store.getComponent(interimLeader, this.flockMembershipComponentType); + if (interimLeaderMembership == null) { + throw new IllegalStateException("Member is missing FlockMembership component!"); + } + + setInterimLeader(store, interimLeaderMembership, entityGroupComponent, interimLeader, flockComponent, flockId); + } + } + + membership.unload(); + if (entityGroupComponent.size() <= 0) { + commandBuffer.tryRemoveEntity(flockRef, RemoveReason.UNLOAD); + } + + if (flockComponent.isTrace()) { + FlockPlugin.get() + .getLogger() + .at(Level.INFO) + .log( + "Flock %s: Unloaded from flock, reference=%s, leader=%s, size=%s", + flockId, + ref, + entityGroupComponent.getLeaderRef(), + entityGroupComponent.size() + ); + } + } + } + } + + private static void markNeedsSave(@Nonnull Ref ref, @Nonnull Store store, @Nonnull Flock flockComponent) { + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + if (npcComponent != null) { + Role role = npcComponent.getRole(); + if (role != null) { + EnumSet flags = role.getDebugSupport().getDebugFlags(); + flockComponent.setTrace(flags.contains(RoleDebugFlags.Flock)); + } + } + + FlockMembershipSystems.markChunkNeedsSaving(ref, store); + } + + private static void setInterimLeader( + @Nonnull Store store, + @Nonnull FlockMembership interimLeaderMembership, + @Nonnull EntityGroup entityGroup, + Ref interimLeader, + @Nonnull Flock flockComponent, + @Nonnull UUID flockId + ) { + interimLeaderMembership.setMembershipType(FlockMembership.Type.INTERIM_LEADER); + entityGroup.setLeaderRef(interimLeader); + markNeedsSave(interimLeader, store, flockComponent); + if (flockComponent.isTrace()) { + FlockPlugin.get() + .getLogger() + .at(Level.INFO) + .log("Flock %s: Set new interim leader, old=%s, new=%s, size=%s", flockId, entityGroup.getLeaderRef(), interimLeader, entityGroup.size()); + } + } + } + + public static class NPCAddedFromWorldGen extends HolderSystem { + @Nullable + private final ComponentType npcComponentType = NPCEntity.getComponentType(); + @Nonnull + private final ComponentType fromWorldGenComponentType = FromWorldGen.getComponentType(); + @Nonnull + private final ComponentType flockMembershipComponentType = FlockMembership.getComponentType(); + @Nonnull + private final Query query = Query.and(this.npcComponentType, this.fromWorldGenComponentType, this.flockMembershipComponentType); + + public NPCAddedFromWorldGen() { + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + @Nullable + @Override + public SystemGroup getGroup() { + return EntityModule.get().getPreClearMarkersGroup(); + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.removeComponent(this.flockMembershipComponentType); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + } + + public static class OnDamageDealt extends DamageEventSystem { + public OnDamageDealt() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getInspectDamageGroup(); + } + + @Nullable + @Override + public Query getQuery() { + return Archetype.empty(); + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage damage + ) { + if (damage.getSource() instanceof Damage.EntitySource entitySource) { + Ref damageSourceRef = entitySource.getRef(); + if (damageSourceRef.isValid()) { + FlockMembership flockMembershipComponent = commandBuffer.getComponent(damageSourceRef, FlockMembership.getComponentType()); + if (flockMembershipComponent != null) { + Ref flockReference = flockMembershipComponent.getFlockRef(); + if (flockReference != null && flockReference.isValid()) { + Flock flockComponent = commandBuffer.getComponent(flockReference, Flock.getComponentType()); + + assert flockComponent != null; + + Ref entityRef = archetypeChunk.getReferenceTo(index); + flockComponent.getNextDamageData().onInflictedDamage(entityRef, damage.getAmount()); + if (flockMembershipComponent.getMembershipType().isActingAsLeader()) { + flockComponent.getNextLeaderDamageData().onInflictedDamage(entityRef, damage.getAmount()); + } + } + } + } + } + } + } + + public static class OnDamageReceived extends DamageEventSystem { + @Nonnull + private final Query query = FlockMembership.getComponentType(); + + public OnDamageReceived() { + } + + @Nullable + @Override + public SystemGroup getGroup() { + return DamageModule.get().getInspectDamageGroup(); + } + + @Nonnull + @Override + public Query getQuery() { + return this.query; + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull Damage danage + ) { + FlockMembership flockMembershipComponent = archetypeChunk.getComponent(index, FlockMembership.getComponentType()); + if (flockMembershipComponent != null) { + Ref flockRef = flockMembershipComponent.getFlockRef(); + if (flockRef != null && flockRef.isValid()) { + Flock flockComponent = commandBuffer.getComponent(flockRef, Flock.getComponentType()); + + assert flockComponent != null; + + flockComponent.getNextDamageData().onSufferedDamage(commandBuffer, danage); + if (flockMembershipComponent.getMembershipType().isActingAsLeader()) { + flockComponent.getNextLeaderDamageData().onSufferedDamage(commandBuffer, danage); + } + } + } + } + } + + public static class RefChange extends RefChangeSystem { + @Nonnull + private final ComponentType flockMembershipComponentType; + + public RefChange(@Nonnull ComponentType flockMembershipComponentType) { + this.flockMembershipComponentType = flockMembershipComponentType; + } + + @Override + public Query getQuery() { + return this.flockMembershipComponentType; + } + + @Nonnull + @Override + public ComponentType componentType() { + return this.flockMembershipComponentType; + } + + public void onComponentAdded( + @Nonnull Ref ref, + @Nonnull FlockMembership component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + this.doJoin(ref, component, store, commandBuffer); + } + + public void onComponentSet( + @Nonnull Ref ref, + FlockMembership oldComponent, + @Nonnull FlockMembership newComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + assert oldComponent != null; + + if (oldComponent.getMembershipType() == FlockMembership.Type.JOINING) { + this.doJoin(ref, newComponent, store, commandBuffer); + } else { + doLeave(ref, oldComponent, store, commandBuffer); + commandBuffer.run(_store -> this.doJoin(ref, newComponent, store, commandBuffer)); + } + } + + public void onComponentRemoved( + @Nonnull Ref ref, + @Nonnull FlockMembership component, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + if (component.getMembershipType() != FlockMembership.Type.JOINING) { + doLeave(ref, component, store, commandBuffer); + } + } + + private void doJoin( + @Nonnull Ref ref, + @Nonnull FlockMembership membershipComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Ref flockRef = membershipComponent.getFlockRef(); + if (flockRef != null) { + if (!flockRef.isValid()) { + FlockPlugin.get().getLogger().atWarning().log("Entity %s attempting to join invalid flock with ref %s", ref, flockRef); + commandBuffer.removeComponent(ref, this.flockMembershipComponentType); + } else { + Flock flockComponent = commandBuffer.getComponent(flockRef, Flock.getComponentType()); + + assert flockComponent != null; + + EntityGroup entityGroupComponent = commandBuffer.getComponent(flockRef, EntityGroup.getComponentType()); + + assert entityGroupComponent != null; + + UUIDComponent uuidComponent = commandBuffer.getComponent(flockRef, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID flockId = uuidComponent.getUuid(); + if (!entityGroupComponent.isMember(ref)) { + if (membershipComponent.getMembershipType() != FlockMembership.Type.JOINING) { + throw new IllegalStateException( + String.format( + "Entity %s attempting to join group with ID %s but has wrong membership status %s", + ref, + flockId, + membershipComponent.getMembershipType() + ) + ); + } else { + boolean isDead = store.getArchetype(ref).contains(DeathComponent.getComponentType()); + if (ref.isValid() && !isDead) { + Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType()); + if (playerComponent != null && playerComponent.getGameMode() != GameMode.Adventure) { + if (flockComponent.isTrace()) { + FlockPlugin.get().getLogger().at(Level.INFO).log("Flock %s: Failed to join, ref=%s. Player in creative mode.", flockId, ref); + } + + commandBuffer.removeComponent(ref, this.flockMembershipComponentType); + } else { + PersistentFlockData flockData = flockComponent.getFlockData(); + if (flockData == null) { + if (flockComponent.isTrace()) { + FlockPlugin.get() + .getLogger() + .at(Level.INFO) + .log( + "Flock %s: Rejected join entity due to leader not being loaded, ref=%s, size=%s", + flockId, + ref, + entityGroupComponent.size() + ); + } + + commandBuffer.removeComponent(ref, this.flockMembershipComponentType); + } else { + Ref leader = entityGroupComponent.getLeaderRef(); + boolean mustBecomeLeader = leader == null; + boolean wasFirstJoiner = mustBecomeLeader; + if (playerComponent != null) { + if (leader != null && store.getComponent(leader, Player.getComponentType()) != null) { + if (flockComponent.isTrace()) { + FlockPlugin.get() + .getLogger() + .at(Level.INFO) + .log("Flock %s: Failed join 2 players, ref=%s, size=%s", flockId, ref, entityGroupComponent.size()); + } + + commandBuffer.removeComponent(ref, this.flockMembershipComponentType); + return; + } + + mustBecomeLeader = true; + } + + entityGroupComponent.add(ref); + if (mustBecomeLeader) { + setNewLeader(flockId, entityGroupComponent, flockComponent, ref, store, commandBuffer); + if (wasFirstJoiner && flockComponent.isTrace()) { + FlockPlugin.get() + .getLogger() + .at(Level.INFO) + .log("Flock %s: Joined no leader, ref=%s, size=%s", flockId, ref, entityGroupComponent.size()); + } + } else { + membershipComponent.setMembershipType(FlockMembership.Type.MEMBER); + } + + flockData.increaseSize(); + FlockMembershipSystems.markChunkNeedsSaving(entityGroupComponent.getLeaderRef(), store); + if (flockComponent.isTrace()) { + FlockPlugin.get() + .getLogger() + .at(Level.INFO) + .log("Flock %s: Joined join ref=%s, size=%s", flockId, ref, entityGroupComponent.size()); + } + + FlockMembershipSystems.markChunkNeedsSaving(ref, store); + } + } + } else { + if (flockComponent.isTrace()) { + FlockPlugin.get() + .getLogger() + .at(Level.INFO) + .log("Flock %s: Failed to join entity ref=%s, size=%s", flockId, ref, entityGroupComponent.size()); + } + + commandBuffer.removeComponent(ref, this.flockMembershipComponentType); + } + } + } + } + } + } + + private static void doLeave( + @Nonnull Ref ref, + @Nonnull FlockMembership membershipComponent, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Ref flockReference = membershipComponent.getFlockRef(); + if (flockReference != null && flockReference.isValid()) { + Flock flockComponent = commandBuffer.getComponent(flockReference, Flock.getComponentType()); + + assert flockComponent != null; + + EntityGroup entityGroupComponent = commandBuffer.getComponent(flockReference, EntityGroup.getComponentType()); + + assert entityGroupComponent != null; + + UUIDComponent uuidComponent = commandBuffer.getComponent(flockReference, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID flockId = uuidComponent.getUuid(); + entityGroupComponent.remove(ref); + if (flockComponent.isTrace()) { + FlockPlugin.get() + .getLogger() + .at(Level.INFO) + .log("Flock %s: Left flock, reference=%s, leader=%s, size=%s", flockId, ref, entityGroupComponent.getLeaderRef(), entityGroupComponent.size()); + } + + if (!entityGroupComponent.isDissolved() && entityGroupComponent.size() < 2) { + commandBuffer.removeEntity(flockReference, RemoveReason.REMOVE); + } else if (!entityGroupComponent.isDissolved()) { + PersistentFlockData flockData = flockComponent.getFlockData(); + if (flockData != null) { + flockData.decreaseSize(); + } + + Ref leader = entityGroupComponent.getLeaderRef(); + if (leader != null && !ref.equals(leader)) { + FlockMembershipSystems.markChunkNeedsSaving(leader, store); + } else { + Ref newLeader = entityGroupComponent.testMembers(FlockMembershipSystems::canBecomeLeader, true); + if (newLeader == null) { + if (flockComponent.isTrace()) { + FlockPlugin.get() + .getLogger() + .at(Level.INFO) + .log( + "Flock %s: Leave failed to get new leader, reference=%s, leader=%s, size=%s", + flockId, + ref, + entityGroupComponent.getLeaderRef(), + entityGroupComponent.size() + ); + } + + commandBuffer.removeEntity(flockReference, RemoveReason.REMOVE); + return; + } + + setNewLeader(flockId, entityGroupComponent, flockComponent, newLeader, store, commandBuffer); + } + } + } + } + + private static void setNewLeader( + @Nonnull UUID flockId, + @Nonnull EntityGroup entityGroup, + @Nonnull Flock flock, + @Nonnull Ref ref, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Ref oldLeader = entityGroup.getLeaderRef(); + if (oldLeader == null || !oldLeader.equals(ref)) { + entityGroup.setLeaderRef(ref); + if (oldLeader != null) { + FlockMembership oldLeaderComponent = store.getComponent(oldLeader, FlockMembership.getComponentType()); + if (oldLeaderComponent != null) { + oldLeaderComponent.setMembershipType(FlockMembership.Type.MEMBER); + } + + commandBuffer.tryRemoveComponent(oldLeader, PersistentFlockData.getComponentType()); + FlockMembershipSystems.markChunkNeedsSaving(oldLeader, store); + } + + FlockMembership flockMembershipComponent = store.getComponent(ref, FlockMembership.getComponentType()); + + assert flockMembershipComponent != null; + + flockMembershipComponent.setMembershipType(FlockMembership.Type.LEADER); + NPCEntity newLeaderNpcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + if (newLeaderNpcComponent != null) { + Role role = newLeaderNpcComponent.getRole(); + if (role != null) { + EnumSet flags = role.getDebugSupport().getDebugFlags(); + flock.setTrace(flags.contains(RoleDebugFlags.Flock)); + } + } + + PersistentFlockData flockData = flock.getFlockData(); + if (flockData != null) { + commandBuffer.putComponent(ref, PersistentFlockData.getComponentType(), flockData); + } + + FlockMembershipSystems.markChunkNeedsSaving(ref, store); + if (flock.isTrace()) { + FlockPlugin.get() + .getLogger() + .at(Level.INFO) + .log("Flock %s: Set new leader, old=%s, new=%s, size=%s", flockId, oldLeader, ref, entityGroup.size()); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/flock/FlockPlugin.java b/src/com/hypixel/hytale/server/flock/FlockPlugin.java new file mode 100644 index 0000000..0687a14 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/FlockPlugin.java @@ -0,0 +1,314 @@ +package com.hypixel.hytale.server.flock; + +import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.system.WorldEventSystem; +import com.hypixel.hytale.function.consumer.TriConsumer; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.group.EntityGroup; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.prefab.event.PrefabPasteEvent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.config.FlockAsset; +import com.hypixel.hytale.server.flock.config.RangeSizeFlockAsset; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockBeacon; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockJoin; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockLeave; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockSetTarget; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockState; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderBodyMotionFlock; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderEntityFilterFlock; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderSensorFlockCombatDamage; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderSensorFlockLeader; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderSensorInflictedDamage; +import com.hypixel.hytale.server.flock.decisionmaker.conditions.FlockSizeCondition; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.decisionmaker.core.conditions.base.Condition; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.systems.PositionCacheSystems; +import it.unimi.dsi.fastutil.Pair; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FlockPlugin extends JavaPlugin { + private static FlockPlugin instance; + private final Int2ObjectConcurrentHashMap> prefabFlockRemappings = new Int2ObjectConcurrentHashMap<>(); + private ComponentType flockComponentType; + private ComponentType flockMembershipComponentType; + private ComponentType persistentFlockDataComponentType; + + public static FlockPlugin get() { + return instance; + } + + public FlockPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + public void setup() { + instance = this; + ComponentRegistryProxy entityStoreRegistry = this.getEntityStoreRegistry(); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + FlockAsset.class, new IndexedLookupTableAssetMap<>(FlockAsset[]::new) + ) + .setPath("NPC/Flocks")) + .setCodec(FlockAsset.CODEC)) + .setKeyFunction(FlockAsset::getId)) + .setReplaceOnRemove(RangeSizeFlockAsset::getUnknownFor)) + .build() + ); + this.getEntityStoreRegistry().registerSystem(new FlockPlugin.PrefabPasteEventSystem(this)); + NPCPlugin.get() + .registerCoreComponentType("Flock", BuilderBodyMotionFlock::new) + .registerCoreComponentType("JoinFlock", BuilderActionFlockJoin::new) + .registerCoreComponentType("LeaveFlock", BuilderActionFlockLeave::new) + .registerCoreComponentType("FlockState", BuilderActionFlockState::new) + .registerCoreComponentType("FlockTarget", BuilderActionFlockSetTarget::new) + .registerCoreComponentType("FlockBeacon", BuilderActionFlockBeacon::new) + .registerCoreComponentType("Flock", BuilderEntityFilterFlock::new) + .registerCoreComponentType("FlockCombatDamage", BuilderSensorFlockCombatDamage::new) + .registerCoreComponentType("InflictedDamage", BuilderSensorInflictedDamage::new) + .registerCoreComponentType("FlockLeader", BuilderSensorFlockLeader::new); + Condition.CODEC.register("FlockSize", FlockSizeCondition.class, FlockSizeCondition.CODEC); + this.flockComponentType = entityStoreRegistry.registerComponent(Flock.class, () -> { + throw new UnsupportedOperationException("Not implemented"); + }); + entityStoreRegistry.registerSystem(new FlockSystems.EntityRemoved(this.flockComponentType)); + entityStoreRegistry.registerSystem(new FlockSystems.Ticking(this.flockComponentType)); + entityStoreRegistry.registerSystem(new FlockSystems.PlayerChangeGameModeEventSystem()); + this.flockMembershipComponentType = entityStoreRegistry.registerComponent(FlockMembership.class, "FlockMembership", FlockMembership.CODEC); + this.persistentFlockDataComponentType = entityStoreRegistry.registerComponent(PersistentFlockData.class, "FlockData", PersistentFlockData.CODEC); + entityStoreRegistry.registerSystem(new FlockMembershipSystems.EntityRef(this.flockMembershipComponentType)); + entityStoreRegistry.registerSystem(new FlockMembershipSystems.RefChange(this.flockMembershipComponentType)); + entityStoreRegistry.registerSystem(new PositionCacheSystems.OnFlockJoinSystem(NPCEntity.getComponentType(), this.flockMembershipComponentType)); + entityStoreRegistry.registerSystem(new FlockDeathSystems.EntityDeath()); + entityStoreRegistry.registerSystem(new FlockDeathSystems.PlayerDeath()); + entityStoreRegistry.registerSystem(new FlockMembershipSystems.OnDamageReceived()); + entityStoreRegistry.registerSystem(new FlockMembershipSystems.OnDamageDealt()); + entityStoreRegistry.registerSystem(new FlockMembershipSystems.NPCAddedFromWorldGen()); + } + + @Override + public void start() { + } + + @Override + public void shutdown() { + } + + public ComponentType getFlockComponentType() { + return this.flockComponentType; + } + + public ComponentType getFlockMembershipComponentType() { + return this.flockMembershipComponentType; + } + + public ComponentType getPersistentFlockDataComponentType() { + return this.persistentFlockDataComponentType; + } + + @Nonnull + public UUID getPrefabRemappedFlockReference(int prefabId, UUID oldId) { + return this.prefabFlockRemappings.get(prefabId).computeIfAbsent(oldId, s -> UUID.randomUUID()); + } + + @Nullable + public static Ref trySpawnFlock( + @Nonnull Ref npcRef, + @Nonnull NPCEntity npc, + @Nonnull Store store, + int roleIndex, + @Nonnull Vector3d position, + Vector3f rotation, + @Nullable FlockAsset flockDefinition, + TriConsumer, Store> postSpawn + ) { + int flockSize = flockDefinition != null ? flockDefinition.pickFlockSize() : 1; + return trySpawnFlock(npcRef, npc, roleIndex, position, rotation, flockSize, flockDefinition, null, postSpawn, store); + } + + @Nullable + public static Ref trySpawnFlock( + @Nonnull Ref npcRef, + @Nonnull NPCEntity npc, + @Nonnull Store store, + int roleIndex, + @Nonnull Vector3d position, + Vector3f rotation, + int flockSize, + TriConsumer, Store> postSpawn + ) { + return trySpawnFlock(npcRef, npc, roleIndex, position, rotation, flockSize, null, null, postSpawn, store); + } + + @Nullable + public static Ref trySpawnFlock( + @Nonnull Ref npcRef, + @Nonnull NPCEntity npc, + int roleIndex, + @Nonnull Vector3d position, + Vector3f rotation, + int flockSize, + FlockAsset flockDefinition, + TriConsumer, Store> preAddToWorld, + TriConsumer, Store> postSpawn, + @Nonnull Store store + ) { + if (flockSize > 1 && npcRef.isValid()) { + Role role = npc.getRole(); + + assert role != null; + + FlockMembership membershipComponent = store.getComponent(npcRef, FlockMembership.getComponentType()); + Ref flockReference = membershipComponent != null + ? membershipComponent.getFlockRef() + : createFlock(store, flockDefinition, role.getFlockAllowedRoles()); + if (membershipComponent == null) { + FlockMembershipSystems.join(npcRef, flockReference, store); + } + + BoundingBox boundingBoxComponent = store.getComponent(npcRef, BoundingBox.getComponentType()); + + assert boundingBoxComponent != null; + + TransformComponent transformComponent = store.getComponent(npcRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Box boundingBox = boundingBoxComponent.getBoundingBox(); + Vector3f bodyRotation = transformComponent.getRotation(); + double x = position.getX(); + int y = MathUtil.floor(position.getY() + boundingBox.min.y + 1.0E-6); + double z = position.getZ(); + double yaw = bodyRotation.getYaw(); + boolean randomSpawn = role.isFlockSpawnTypesRandom(); + int[] roles = role.getFlockSpawnTypes(); + int rolesSize = roles == null ? 0 : roles.length; + int index = 0; + int memberRoleIndex = roleIndex; + + for (int i = 1; i < flockSize; i++) { + if (rolesSize > 0) { + if (randomSpawn) { + memberRoleIndex = roles[RandomExtra.randomRange(rolesSize)]; + } else { + memberRoleIndex = roles[index]; + index = (index + 1) % rolesSize; + } + } + + Pair, NPCEntity> memberPair = NPCPlugin.get() + .spawnEntity(store, memberRoleIndex, position, rotation, null, preAddToWorld, postSpawn); + Ref memberRef = memberPair.first(); + if (memberRef != null && memberRef.isValid()) { + BoundingBox memberBoundingBoxComponent = store.getComponent(memberRef, BoundingBox.getComponentType()); + + assert memberBoundingBoxComponent != null; + + TransformComponent memberTransformComponent = store.getComponent(memberRef, TransformComponent.getComponentType()); + + assert memberTransformComponent != null; + + HeadRotation memberHeadRotationComponent = store.getComponent(memberRef, HeadRotation.getComponentType()); + + assert memberHeadRotationComponent != null; + + double offsetY = y - memberBoundingBoxComponent.getBoundingBox().min.y; + memberTransformComponent.getRotation().setYaw((float)(yaw + RandomExtra.randomRange((float) (Math.PI / 4), (float) (Math.PI / 4)))); + memberHeadRotationComponent.getRotation().setPitch(0.0F); + memberTransformComponent.getPosition().assign(x + RandomExtra.randomRange(-0.5, 0.5), offsetY, z + RandomExtra.randomRange(-0.5, 0.5)); + FlockMembershipSystems.join(memberRef, flockReference, store); + } + } + + return flockReference; + } else { + return null; + } + } + + @Nullable + @Deprecated + public static Flock getFlock(@Nonnull ComponentAccessor componentAccessor, @Nonnull Ref reference) { + FlockMembership flockMembershipComponent = componentAccessor.getComponent(reference, FlockMembership.getComponentType()); + if (flockMembershipComponent == null) { + return null; + } else { + Ref membershipRef = flockMembershipComponent.getFlockRef(); + return membershipRef != null && membershipRef.isValid() ? componentAccessor.getComponent(membershipRef, Flock.getComponentType()) : null; + } + } + + @Nonnull + public static Ref createFlock(@Nonnull Store store, @Nonnull Role role) { + return createFlock(store, null, role.getFlockAllowedRoles()); + } + + @Nonnull + public static Ref createFlock(@Nonnull Store store, @Nullable FlockAsset flockDefinition, @Nonnull String[] allowedRoles) { + Holder holder = EntityStore.REGISTRY.newHolder(); + holder.addComponent(UUIDComponent.getComponentType(), UUIDComponent.randomUUID()); + holder.addComponent(EntityGroup.getComponentType(), new EntityGroup()); + holder.addComponent(Flock.getComponentType(), new Flock(flockDefinition, allowedRoles)); + Ref ref = store.addEntity(holder, AddReason.SPAWN); + if (ref == null) { + throw new UnsupportedOperationException("Unable to handle non-spawned flock!"); + } else { + return ref; + } + } + + @Nullable + public static Ref getFlockReference(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + FlockMembership flockMembershipComponent = componentAccessor.getComponent(ref, FlockMembership.getComponentType()); + return flockMembershipComponent == null ? null : flockMembershipComponent.getFlockRef(); + } + + public static boolean isFlockMember(@Nonnull Ref ref, @Nonnull Store store) { + FlockMembership flockMembershipComponent = store.getComponent(ref, FlockMembership.getComponentType()); + return flockMembershipComponent != null; + } + + private static final class PrefabPasteEventSystem extends WorldEventSystem { + private final FlockPlugin plugin; + + PrefabPasteEventSystem(@Nonnull FlockPlugin plugin) { + super(PrefabPasteEvent.class); + this.plugin = plugin; + } + + public void handle(@Nonnull Store store, @Nonnull CommandBuffer commandBuffer, @Nonnull PrefabPasteEvent event) { + if (event.isPasteStart()) { + this.plugin.prefabFlockRemappings.put(event.getPrefabId(), new HashMap<>()); + } else { + this.plugin.prefabFlockRemappings.remove(event.getPrefabId()); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/flock/FlockSystems.java b/src/com/hypixel/hytale/server/flock/FlockSystems.java new file mode 100644 index 0000000..0bb0512 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/FlockSystems.java @@ -0,0 +1,142 @@ +package com.hypixel.hytale.server.flock; + +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.EntityEventSystem; +import com.hypixel.hytale.component.system.RefSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.group.EntityGroup; +import com.hypixel.hytale.server.core.event.events.ecs.ChangeGameModeEvent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FlockSystems { + public FlockSystems() { + } + + public static class EntityRemoved extends RefSystem { + private final ComponentType flockIdComponentType = UUIDComponent.getComponentType(); + private final ComponentType entityGroupComponentType = EntityGroup.getComponentType(); + private final ComponentType flockComponentType; + private final Archetype archetype; + + public EntityRemoved(ComponentType flockComponentType) { + this.flockComponentType = flockComponentType; + this.archetype = Archetype.of(this.flockIdComponentType, this.entityGroupComponentType, flockComponentType); + } + + @Override + public Query getQuery() { + return this.archetype; + } + + @Override + public void onEntityAdded( + @Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + } + + @Override + public void onEntityRemove( + @Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer + ) { + UUID flockId = store.getComponent(ref, this.flockIdComponentType).getUuid(); + EntityGroup entityGroup = store.getComponent(ref, this.entityGroupComponentType); + Flock flock = store.getComponent(ref, this.flockComponentType); + switch (reason) { + case REMOVE: + entityGroup.setDissolved(true); + + for (Ref memberRef : entityGroup.getMemberList()) { + commandBuffer.removeComponent(memberRef, FlockMembership.getComponentType()); + TransformComponent transformComponent = commandBuffer.getComponent(memberRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + transformComponent.markChunkDirty(commandBuffer); + } + + flock.setRemovedStatus(Flock.FlockRemovedStatus.DISSOLVED); + entityGroup.clear(); + if (flock.isTrace()) { + FlockPlugin.get().getLogger().at(Level.INFO).log("Flock %s: Dissolving", flockId); + } + break; + case UNLOAD: + flock.setRemovedStatus(Flock.FlockRemovedStatus.UNLOADED); + entityGroup.clear(); + if (flock.isTrace()) { + FlockPlugin.get().getLogger().at(Level.INFO).log("Flock %s: Flock unloaded, size=%s", flockId, entityGroup.size()); + } + } + } + } + + public static class PlayerChangeGameModeEventSystem extends EntityEventSystem { + public PlayerChangeGameModeEventSystem() { + super(ChangeGameModeEvent.class); + } + + public void handle( + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer, + @Nonnull ChangeGameModeEvent event + ) { + if (event.getGameMode() != GameMode.Adventure) { + Ref ref = archetypeChunk.getReferenceTo(index); + commandBuffer.tryRemoveComponent(ref, FlockMembership.getComponentType()); + } + } + + @Nullable + @Override + public Query getQuery() { + return Archetype.empty(); + } + } + + public static class Ticking extends EntityTickingSystem { + private final ComponentType flockComponentType; + + public Ticking(ComponentType flockComponentType) { + this.flockComponentType = flockComponentType; + } + + @Override + public Query getQuery() { + return this.flockComponentType; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount); + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + Flock flock = archetypeChunk.getComponent(index, this.flockComponentType); + flock.swapDamageDataBuffers(); + } + } +} diff --git a/src/com/hypixel/hytale/server/flock/PersistentFlockData.java b/src/com/hypixel/hytale/server/flock/PersistentFlockData.java new file mode 100644 index 0000000..0c3a836 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/PersistentFlockData.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.server.flock; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.config.FlockAsset; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.Collections; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PersistentFlockData implements Component { + public static final BuilderCodec CODEC = BuilderCodec.builder(PersistentFlockData.class, PersistentFlockData::new) + .append(new KeyedCodec<>("MaxGrowSize", Codec.INTEGER), (flock, i) -> flock.maxGrowSize = i, flock -> flock.maxGrowSize) + .add() + .append(new KeyedCodec<>("AllowedRoles", Codec.STRING_ARRAY), (flock, o) -> flock.flockAllowedRoles = o, flock -> flock.flockAllowedRoles) + .add() + .append(new KeyedCodec<>("Size", Codec.INTEGER), (flock, i) -> flock.size = i, flock -> flock.size) + .add() + .build(); + private int maxGrowSize = Integer.MAX_VALUE; + private String[] flockAllowedRoles = ArrayUtil.EMPTY_STRING_ARRAY; + private int size; + + public static ComponentType getComponentType() { + return FlockPlugin.get().getPersistentFlockDataComponentType(); + } + + public PersistentFlockData(@Nullable FlockAsset flockDefinition, @Nonnull String[] allowedRoles) { + this.flockAllowedRoles = allowedRoles; + Arrays.sort((Object[])this.flockAllowedRoles); + if (flockDefinition != null) { + this.maxGrowSize = flockDefinition.getMaxGrowSize(); + String[] blockedRoles = flockDefinition.getBlockedRoles(); + if (blockedRoles.length > 0 && this.flockAllowedRoles.length > 0) { + ObjectArrayList combinedList = new ObjectArrayList<>(this.flockAllowedRoles.length); + Collections.addAll(combinedList, this.flockAllowedRoles); + + for (String blockedRole : blockedRoles) { + combinedList.remove(blockedRole); + } + + this.flockAllowedRoles = combinedList.toArray(String[]::new); + } + } + } + + private PersistentFlockData() { + } + + public int getMaxGrowSize() { + return this.maxGrowSize; + } + + public boolean isFlockAllowedRole(String role) { + return Arrays.binarySearch(this.flockAllowedRoles, role) >= 0; + } + + public void increaseSize() { + this.size++; + } + + public void decreaseSize() { + this.size--; + } + + @Nonnull + @Override + public Component clone() { + PersistentFlockData data = new PersistentFlockData(); + data.maxGrowSize = this.maxGrowSize; + data.flockAllowedRoles = this.flockAllowedRoles; + data.size = this.size; + return data; + } +} diff --git a/src/com/hypixel/hytale/server/flock/StoredFlock.java b/src/com/hypixel/hytale/server/flock/StoredFlock.java new file mode 100644 index 0000000..e8a015d --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/StoredFlock.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.server.flock; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.codecs.array.ArrayCodec; +import com.hypixel.hytale.codec.store.StoredCodec; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistry; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.spawning.SpawningPlugin; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StoredFlock { + public static final BuilderCodec CODEC = BuilderCodec.builder(StoredFlock.class, StoredFlock::new) + .append( + new KeyedCodec<>("Members", new ArrayCodec<>(new StoredCodec<>(EntityStore.HOLDER_CODEC_KEY), Holder[]::new)), + (flock, array) -> flock.members = array, + flock -> flock.members + ) + .add() + .build(); + @Nullable + private Holder[] members; + + public StoredFlock() { + } + + public void storeNPCs(@Nonnull List> refs, @Nonnull Store store) { + ComponentRegistry.Data data = EntityStore.REGISTRY.getData(); + ObjectArrayList> members = new ObjectArrayList<>(); + + for (int i = 0; i < refs.size(); i++) { + Ref ref = refs.get(i); + if (ref.isValid()) { + Holder holder = store.removeEntity(ref, RemoveReason.UNLOAD); + if (holder.hasSerializableComponents(data)) { + members.add(holder); + } + } + } + + this.members = members.toArray(Holder[]::new); + } + + public boolean hasStoredNPCs() { + return this.members != null; + } + + public void restoreNPCs(@Nonnull List> output, @Nonnull Store store) { + for (Holder member : this.members) { + Ref ref = store.addEntity(member, AddReason.LOAD); + if (ref == null) { + SpawningPlugin.get().getLogger().at(Level.WARNING).log("Failed to restore stored spawn marker member! " + member); + } else { + output.add(ref); + } + } + + this.clear(); + } + + public void clear() { + this.members = null; + } + + @Nonnull + @Override + public String toString() { + return "StoredFlock{, members=" + Arrays.toString((Object[])this.members) + "}"; + } + + @Nonnull + public StoredFlock clone() { + StoredFlock storedFlock = new StoredFlock(); + if (this.members != null) { + Holder[] newMembers = new Holder[this.members.length]; + + for (int i = 0; i < newMembers.length; i++) { + newMembers[i] = this.members[i].clone(); + } + + storedFlock.members = newMembers; + } + + return storedFlock; + } +} diff --git a/src/com/hypixel/hytale/server/flock/commands/NPCFlockCommand.java b/src/com/hypixel/hytale/server/flock/commands/NPCFlockCommand.java new file mode 100644 index 0000000..42c7aa6 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/commands/NPCFlockCommand.java @@ -0,0 +1,222 @@ +package com.hypixel.hytale.server.flock.commands; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.FlockMembership; +import com.hypixel.hytale.server.flock.FlockMembershipSystems; +import com.hypixel.hytale.server.flock.FlockPlugin; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.util.NPCPhysicsMath; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import javax.annotation.Nonnull; + +public class NPCFlockCommand extends AbstractCommandCollection { + private static final double ENTITY_IN_VIEW_DISTANCE = 8.0; + private static final float ENTITY_IN_VIEW_ANGLE = 30.0F; + private static final int ENTITY_IN_VIEW_HEIGHT = 2; + + public NPCFlockCommand() { + super("flock", "server.commands.npc.flock.desc"); + this.addSubCommand(new NPCFlockCommand.LeaveCommand()); + this.addSubCommand(new NPCFlockCommand.GrabCommand()); + this.addSubCommand(new NPCFlockCommand.JoinCommand()); + this.addSubCommand(new NPCFlockCommand.PlayerLeaveCommand()); + } + + public static int forNpcEntitiesInViewCone( + @Nonnull Ref playerReference, @Nonnull Store store, @Nonnull BiPredicate, NPCEntity> predicate + ) { + ComponentType transformComponentType = TransformComponent.getComponentType(); + TransformComponent transformComponent = store.getComponent(playerReference, transformComponentType); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + HeadRotation headRotationComponent = store.getComponent(playerReference, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3f headRotation = headRotationComponent.getRotation(); + float lookYaw = headRotation.getYaw(); + double x = position.getX(); + double y = position.getY(); + double z = position.getZ(); + SpatialResource, EntityStore> spatialResource = store.getResource(NPCPlugin.get().getNpcSpatialResource()); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + spatialResource.getSpatialStructure().collect(position, 8.0, results); + ComponentType npcComponentType = NPCEntity.getComponentType(); + + assert npcComponentType != null; + + int count = 0; + + for (Ref targetRef : results) { + NPCEntity targetNpcComponent = store.getComponent(targetRef, npcComponentType); + + assert targetNpcComponent != null; + + TransformComponent entityTransformComponent = store.getComponent(targetRef, transformComponentType); + + assert entityTransformComponent != null; + + Vector3d entityPosition = entityTransformComponent.getPosition(); + if (Math.abs(entityPosition.getY() - y) < 2.0 + && NPCPhysicsMath.inViewSector(x, z, lookYaw, (float) (Math.PI / 6), entityPosition.getX(), entityPosition.getZ()) + && predicate.test(targetRef, targetNpcComponent)) { + count++; + } + } + + return count; + } + + public static boolean anyEntityInViewCone( + @Nonnull Ref playerReference, @Nonnull Store store, @Nonnull Predicate> predicate + ) { + ComponentType transformComponentType = TransformComponent.getComponentType(); + TransformComponent transformComponent = store.getComponent(playerReference, transformComponentType); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + HeadRotation headRotationComponent = store.getComponent(playerReference, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3f headRotation = headRotationComponent.getRotation(); + float lookYaw = headRotation.getYaw(); + double x = position.getX(); + double y = position.getY(); + double z = position.getZ(); + SpatialResource, EntityStore> spatialResource = store.getResource(NPCPlugin.get().getNpcSpatialResource()); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + spatialResource.getSpatialStructure().ordered(position, 8.0, results); + + for (Ref entityRef : results) { + TransformComponent entityTransformComponent = store.getComponent(entityRef, transformComponentType); + + assert entityTransformComponent != null; + + Vector3d entityPosition = entityTransformComponent.getPosition(); + if (Math.abs(entityPosition.getY() - y) < 2.0 + && NPCPhysicsMath.inViewSector(x, z, lookYaw, (float) (Math.PI / 6), entityPosition.getX(), entityPosition.getZ()) + && predicate.test(entityRef)) { + return true; + } + } + + return false; + } + + public static class GrabCommand extends AbstractPlayerCommand { + public GrabCommand() { + super("grab", "server.commands.npc.flock.grab.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + int count = NPCFlockCommand.forNpcEntitiesInViewCone(ref, store, (targetRef, targetNpcComponent) -> { + FlockMembership membership = store.getComponent(targetRef, FlockMembership.getComponentType()); + if (membership == null) { + Ref flockReference = FlockPlugin.getFlockReference(ref, store); + if (flockReference == null) { + flockReference = FlockPlugin.createFlock(store, targetNpcComponent.getRole()); + FlockMembershipSystems.join(ref, flockReference, store); + } + + FlockMembershipSystems.join(targetRef, flockReference, store); + return true; + } else { + return false; + } + }); + context.sendMessage(Message.translation("server.commands.npc.flock.addedToFlock").param("count", count)); + } + } + + public static class JoinCommand extends AbstractPlayerCommand { + public JoinCommand() { + super("join", "server.commands.npc.flock.join.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + boolean success = NPCFlockCommand.anyEntityInViewCone(ref, store, npcReference -> { + Ref flockReference = FlockPlugin.getFlockReference(npcReference, store); + if (flockReference != null) { + FlockMembershipSystems.join(ref, flockReference, store); + return true; + } else { + return false; + } + }); + if (!success) { + context.sendMessage(Message.translation("server.commands.npc.flock.resultJoinFlock").param("status", "Failed")); + } else { + world.execute(() -> { + String status = FlockPlugin.isFlockMember(ref, store) ? "Succeeded" : "Failed"; + context.sendMessage(Message.translation("server.commands.npc.flock.resultJoinFlock").param("status", status)); + }); + } + } + } + + public static class LeaveCommand extends AbstractPlayerCommand { + public LeaveCommand() { + super("leave", "server.commands.npc.flock.leave.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + int count = NPCFlockCommand.forNpcEntitiesInViewCone(ref, store, (targetRef, targetNpcComponent) -> { + store.tryRemoveComponent(targetRef, FlockMembership.getComponentType()); + return true; + }); + context.sendMessage(Message.translation("server.commands.npc.flock.removedFromFlock").param("count", count)); + } + } + + public static class PlayerLeaveCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_NPC_FLOCK_LEFT_FLOCK = Message.translation("server.commands.npc.flock.leftFlock"); + @Nonnull + private static final Message MESSAGE_COMMANDS_NPC_FLOCK_FAILED_LEAVE_FLOCK = Message.translation("server.commands.npc.flock.failedLeaveFlock"); + + public PlayerLeaveCommand() { + super("playerleave", "server.commands.npc.flock.playerleave.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + if (store.removeComponentIfExists(ref, FlockMembership.getComponentType())) { + context.sendMessage(MESSAGE_COMMANDS_NPC_FLOCK_LEFT_FLOCK); + } else { + context.sendMessage(MESSAGE_COMMANDS_NPC_FLOCK_FAILED_LEAVE_FLOCK); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/flock/config/FlockAsset.java b/src/com/hypixel/hytale/server/flock/config/FlockAsset.java new file mode 100644 index 0000000..c725bdb --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/config/FlockAsset.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.server.flock.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.Priority; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.ArrayUtil; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public abstract class FlockAsset implements JsonAssetWithMap> { + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(FlockAsset.class) + .documentation("A flock definition.") + .appendInherited( + new KeyedCodec<>("MaxGrowSize", Codec.INTEGER), + (flock, i) -> flock.maxGrowSize = i, + flock -> flock.maxGrowSize, + (flock, parent) -> flock.maxGrowSize = parent.maxGrowSize + ) + .documentation( + "The maximum size a flock can possibly grow to after spawning. It is technically possible to spawn a flock without specifying a definition (e.g. via a command), in which case the maximum grow size is irrelevant." + ) + .addValidator(Validators.greaterThanOrEqual(0)) + .add() + .appendInherited( + new KeyedCodec<>("BlockedRoles", Codec.STRING_ARRAY), + (flock, o) -> flock.blockedRoles = o, + flock -> flock.blockedRoles, + (flock, parent) -> flock.blockedRoles = parent.blockedRoles + ) + .documentation( + "An array of roles that will not be allowed to join this flock once it has been spawned. This is used to exclude roles from the list of allowed roles in the NPC configuration of the initial leader." + ) + .addValidator(Validators.uniqueInArray()) + .add() + .build(); + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data, true + ) + .register(Priority.DEFAULT, "Default", RangeSizeFlockAsset.class, RangeSizeFlockAsset.CODEC); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(FlockAsset.class, CODEC); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(FlockAsset::getAssetStore)); + private static AssetStore> ASSET_STORE; + private AssetExtraInfo.Data data; + protected String id; + protected int maxGrowSize = 8; + protected String[] blockedRoles = ArrayUtil.EMPTY_STRING_ARRAY; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(FlockAsset.class); + } + + return ASSET_STORE; + } + + public static IndexedLookupTableAssetMap getAssetMap() { + return (IndexedLookupTableAssetMap)getAssetStore().getAssetMap(); + } + + protected FlockAsset() { + } + + protected FlockAsset(String id) { + this.id = id; + } + + public String getId() { + return this.id; + } + + public abstract int getMinFlockSize(); + + public abstract int pickFlockSize(); + + public int getMaxGrowSize() { + return this.maxGrowSize; + } + + public String[] getBlockedRoles() { + return this.blockedRoles; + } + + @Nonnull + @Override + public String toString() { + return "FlockAsset{id='" + this.id + "', maxGrowSize=" + this.maxGrowSize + ", blockedRoles=" + Arrays.toString((Object[])this.blockedRoles) + "}"; + } + + static { + CODEC.register("Weighted", WeightedSizeFlockAsset.class, WeightedSizeFlockAsset.CODEC); + } +} diff --git a/src/com/hypixel/hytale/server/flock/config/RangeSizeFlockAsset.java b/src/com/hypixel/hytale/server/flock/config/RangeSizeFlockAsset.java new file mode 100644 index 0000000..1490924 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/config/RangeSizeFlockAsset.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.server.flock.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.codec.validation.validator.IntArrayValidator; +import com.hypixel.hytale.math.random.RandomExtra; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class RangeSizeFlockAsset extends FlockAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder(RangeSizeFlockAsset.class, RangeSizeFlockAsset::new, ABSTRACT_CODEC) + .documentation("A flock definition in which the initial random size is picked from a range.") + .appendInherited( + new KeyedCodec<>("Size", Codec.INT_ARRAY), (flock, o) -> flock.size = o, flock -> flock.size, (flock, parent) -> flock.size = parent.size + ) + .documentation( + "An array with two values specifying the random range from which to pick the size of the flock when it spawns. e.g. [ 2, 4 ] will randomly pick a size between two and four (inclusive)." + ) + .addValidator(Validators.nonNull()) + .addValidator(Validators.intArraySize(2)) + .addValidator(new IntArrayValidator(Validators.greaterThan(0))) + .add() + .build(); + private static final int[] DEFAULT_SIZE = new int[]{1, 1}; + protected int[] size = DEFAULT_SIZE; + + protected RangeSizeFlockAsset(String id) { + super(id); + } + + protected RangeSizeFlockAsset() { + } + + public int[] getSize() { + return this.size; + } + + @Override + public int getMinFlockSize() { + return this.size[0]; + } + + @Override + public int pickFlockSize() { + return RandomExtra.randomRange(Math.max(1, this.size[0]), this.size[1]); + } + + @Nonnull + public static RangeSizeFlockAsset getUnknownFor(String id) { + return new RangeSizeFlockAsset(id); + } + + @Nonnull + @Override + public String toString() { + return "RangeSizeFlockAsset{size=" + Arrays.toString(this.size) + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/flock/config/WeightedSizeFlockAsset.java b/src/com/hypixel/hytale/server/flock/config/WeightedSizeFlockAsset.java new file mode 100644 index 0000000..460d923 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/config/WeightedSizeFlockAsset.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.server.flock.config; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.codec.validation.validator.DoubleArrayValidator; +import com.hypixel.hytale.math.random.RandomExtra; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class WeightedSizeFlockAsset extends FlockAsset { + public static final BuilderCodec CODEC = BuilderCodec.builder( + WeightedSizeFlockAsset.class, WeightedSizeFlockAsset::new, FlockAsset.ABSTRACT_CODEC + ) + .documentation("A flock definition where the initial random size is picked from a weighted map of sizes.") + .appendInherited( + new KeyedCodec<>("MinSize", Codec.INTEGER), (flock, i) -> flock.minSize = i, flock -> flock.minSize, (flock, parent) -> flock.minSize = parent.minSize + ) + .documentation("The absolute minimum size to spawn the flock with.") + .addValidator(Validators.nonNull()) + .addValidator(Validators.greaterThanOrEqual(0)) + .add() + .appendInherited( + new KeyedCodec<>("SizeWeights", Codec.DOUBLE_ARRAY), + (spawn, o) -> spawn.sizeWeights = o, + spawn -> spawn.sizeWeights, + (spawn, parent) -> spawn.sizeWeights = parent.sizeWeights + ) + .documentation( + "An array of weights which is used in conjunction with the **MinSize** to determine the weighted size of the flock. The first value in the array corresponds to the weight of the minimum size and each successive value to a flock larger by one. e.g. If **MinSize** is 2 and **SizeWeights** is [ 25, 75 ], there will be a 25% chance that the flock will spawn with a size of 2 and a 75% chance that the flock will spawn with a size of 3. As these are weights, they do not need to add up to 100 and their percentage is relative to the total sum." + ) + .addValidator(Validators.nonNull()) + .addValidator(new DoubleArrayValidator(Validators.greaterThan(0.0))) + .add() + .build(); + protected int minSize; + protected double[] sizeWeights; + + protected WeightedSizeFlockAsset() { + } + + public int getMinSize() { + return this.minSize; + } + + public double[] getSizeWeights() { + return this.sizeWeights; + } + + @Override + public int getMinFlockSize() { + return this.minSize; + } + + @Override + public int pickFlockSize() { + int index = RandomExtra.pickWeightedIndex(this.sizeWeights); + return Math.max(this.minSize, 1) + index; + } + + @Nonnull + @Override + public String toString() { + return "WeightedSizeFlockAsset{minSize=" + this.minSize + ", sizeWeights=" + Arrays.toString(this.sizeWeights) + "} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/ActionFlockBeacon.java b/src/com/hypixel/hytale/server/flock/corecomponents/ActionFlockBeacon.java new file mode 100644 index 0000000..98f5c52 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/ActionFlockBeacon.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.server.flock.corecomponents; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.group.EntityGroup; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.FlockMembership; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockBeacon; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.components.messaging.BeaconSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionFlockBeacon extends ActionBase { + protected static final ComponentType BEACON_SUPPORT_COMPONENT_TYPE = BeaconSupport.getComponentType(); + protected static final ComponentType FLOCK_MEMBERSHIP_COMPONENT_TYPE = FlockMembership.getComponentType(); + protected static final ComponentType ENTITY_GROUP_COMPONENT_TYPE = EntityGroup.getComponentType(); + protected final String message; + protected final double expirationTime; + protected final boolean sendToSelf; + protected final boolean sendToLeaderOnly; + protected final int sendTargetSlot; + + public ActionFlockBeacon(@Nonnull BuilderActionFlockBeacon builderActionFlockBeacon, @Nonnull BuilderSupport builderSupport) { + super(builderActionFlockBeacon); + this.message = builderActionFlockBeacon.getMessage(builderSupport); + this.sendTargetSlot = builderActionFlockBeacon.getSendTargetSlot(builderSupport); + this.expirationTime = builderActionFlockBeacon.getExpirationTime(); + this.sendToSelf = builderActionFlockBeacon.isSendToSelf(); + this.sendToLeaderOnly = builderActionFlockBeacon.isSendToLeaderOnly(); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return !super.canExecute(ref, role, sensorInfo, dt, store) + ? false + : store.getArchetype(ref).contains(FLOCK_MEMBERSHIP_COMPONENT_TYPE) + && (this.sendTargetSlot == Integer.MIN_VALUE || role.getMarkedEntitySupport().hasMarkedEntityInSlot(this.sendTargetSlot)); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + FlockMembership flockMembership = store.getComponent(ref, FLOCK_MEMBERSHIP_COMPONENT_TYPE); + Ref flockReference = flockMembership != null ? flockMembership.getFlockRef() : null; + if (flockReference != null && flockReference.isValid()) { + EntityGroup entityGroup = store.getComponent(flockReference, ENTITY_GROUP_COMPONENT_TYPE); + if (entityGroup == null) { + return true; + } else { + Ref targetRef = this.sendTargetSlot >= 0 ? role.getMarkedEntitySupport().getMarkedEntityRef(this.sendTargetSlot) : ref; + if (this.sendToLeaderOnly) { + Ref leaderReference = entityGroup.getLeaderRef(); + if ((this.sendToSelf || targetRef == null || !targetRef.equals(leaderReference)) && leaderReference.isValid()) { + this.sendNPCMessage(leaderReference, targetRef, store); + } + } else { + entityGroup.forEachMember( + (flockMember, entity, _target) -> this.sendNPCMessage(flockMember, _target, store), ref, targetRef, this.sendToSelf ? null : ref + ); + } + + return true; + } + } else { + return true; + } + } + + protected void sendNPCMessage(@Nonnull Ref ref, @Nullable Ref targetRef, @Nonnull ComponentAccessor componentAccessor) { + BeaconSupport beaconSupport = componentAccessor.getComponent(ref, BEACON_SUPPORT_COMPONENT_TYPE); + if (beaconSupport != null) { + beaconSupport.postMessage(this.message, targetRef, this.expirationTime); + } + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/ActionFlockJoin.java b/src/com/hypixel/hytale/server/flock/corecomponents/ActionFlockJoin.java new file mode 100644 index 0000000..9b25580 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/ActionFlockJoin.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.server.flock.corecomponents; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.FlockMembership; +import com.hypixel.hytale.server.flock.FlockMembershipSystems; +import com.hypixel.hytale.server.flock.FlockPlugin; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockJoin; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionFlockJoin extends ActionBase { + protected final boolean forceJoin; + + public ActionFlockJoin(@Nonnull BuilderActionFlockJoin builderActionFlockJoin) { + super(builderActionFlockJoin); + this.forceJoin = builderActionFlockJoin.isForceJoin(); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && sensorInfo != null && sensorInfo.hasPosition(); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + Ref target = sensorInfo != null && sensorInfo.hasPosition() ? sensorInfo.getPositionProvider().getTarget() : null; + if (target == null) { + return false; + } else { + FlockMembership targetMembership = target.getStore().getComponent(target, FlockMembership.getComponentType()); + Ref targetFlockReference = targetMembership != null ? targetMembership.getFlockRef() : null; + Ref flockReference = FlockPlugin.getFlockReference(ref, store); + if (flockReference != null && targetFlockReference != null) { + return true; + } else { + if (flockReference != null) { + FlockMembershipSystems.join(target, flockReference, store); + } else if (targetFlockReference != null) { + FlockMembershipSystems.join(ref, targetFlockReference, store); + } else { + flockReference = FlockPlugin.createFlock(store, role); + if (role.isCanLeadFlock()) { + FlockMembershipSystems.join(ref, flockReference, store); + FlockMembershipSystems.join(target, flockReference, store); + } else { + FlockMembershipSystems.join(target, flockReference, store); + FlockMembershipSystems.join(ref, flockReference, store); + } + } + + return true; + } + } + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/ActionFlockLeave.java b/src/com/hypixel/hytale/server/flock/corecomponents/ActionFlockLeave.java new file mode 100644 index 0000000..3f3918d --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/ActionFlockLeave.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.flock.corecomponents; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.FlockMembership; +import com.hypixel.hytale.server.flock.FlockPlugin; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockLeave; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionFlockLeave extends ActionBase { + public ActionFlockLeave(@Nonnull BuilderActionFlockLeave builderActionFlockLeave) { + super(builderActionFlockLeave); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && FlockPlugin.isFlockMember(ref, store); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + store.tryRemoveComponent(ref, FlockMembership.getComponentType()); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/ActionFlockSetTarget.java b/src/com/hypixel/hytale/server/flock/corecomponents/ActionFlockSetTarget.java new file mode 100644 index 0000000..eb1eecb --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/ActionFlockSetTarget.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.flock.corecomponents; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.FlockPlugin; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockSetTarget; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionFlockSetTarget extends ActionBase { + protected final boolean clear; + protected final String targetSlot; + + public ActionFlockSetTarget(@Nonnull BuilderActionFlockSetTarget builderActionFlockSetTarget, @Nonnull BuilderSupport support) { + super(builderActionFlockSetTarget); + this.clear = builderActionFlockSetTarget.isClear(); + this.targetSlot = builderActionFlockSetTarget.getTargetSlot(support); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, double dt, @Nonnull Store store) { + if (!super.canExecute(ref, role, sensorInfo, dt, store) || !FlockPlugin.isFlockMember(ref, store)) { + return false; + } else if (this.clear) { + return true; + } else { + Ref target = sensorInfo != null && sensorInfo.hasPosition() ? sensorInfo.getPositionProvider().getTarget() : null; + return target != null; + } + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, @Nonnull InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + if (this.clear) { + role.getMarkedEntitySupport().flockSetTarget(this.targetSlot, null, store); + return true; + } else { + Ref targetRef = sensorInfo.getPositionProvider().getTarget(); + role.getMarkedEntitySupport().flockSetTarget(this.targetSlot, targetRef, store); + return true; + } + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/ActionFlockState.java b/src/com/hypixel/hytale/server/flock/corecomponents/ActionFlockState.java new file mode 100644 index 0000000..169caff --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/ActionFlockState.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.server.flock.corecomponents; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionFlockState extends ActionBase { + protected final String state; + @Nullable + protected final String subState; + + public ActionFlockState(@Nonnull BuilderActionFlockState builderActionFlockState, @Nonnull BuilderSupport support) { + super(builderActionFlockState); + String[] split = builderActionFlockState.getState(support).split("\\."); + this.state = split[0]; + this.subState = split.length > 1 && split[1] != null && !split[1].isEmpty() ? split[1] : null; + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + role.getStateSupport().flockSetState(ref, this.state, this.subState, store); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/BodyMotionFlock.java b/src/com/hypixel/hytale/server/flock/corecomponents/BodyMotionFlock.java new file mode 100644 index 0000000..93d59fd --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/BodyMotionFlock.java @@ -0,0 +1,109 @@ +package com.hypixel.hytale.server.flock.corecomponents; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.entity.group.EntityGroup; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.FlockMembership; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderBodyMotionFlock; +import com.hypixel.hytale.server.npc.corecomponents.BodyMotionBase; +import com.hypixel.hytale.server.npc.movement.GroupSteeringAccumulator; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BodyMotionFlock extends BodyMotionBase { + private static final ComponentType FLOCK_MEMBERSHIP_COMPONENT_TYPE = FlockMembership.getComponentType(); + private static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + private static final ComponentType ENTITY_GROUP_COMPONENT_TYPE = EntityGroup.getComponentType(); + protected final GroupSteeringAccumulator groupSteeringAccumulator = new GroupSteeringAccumulator(); + + public BodyMotionFlock(@Nonnull BuilderBodyMotionFlock builderBodyMotionFlock) { + super(builderBodyMotionFlock); + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role role, + @Nullable InfoProvider sensorInfo, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + FlockMembership flockMembership = componentAccessor.getComponent(ref, FLOCK_MEMBERSHIP_COMPONENT_TYPE); + Ref flockReference = flockMembership != null ? flockMembership.getFlockRef() : null; + if (flockReference != null && flockReference.isValid()) { + EntityGroup entityGroup = componentAccessor.getComponent(flockReference, ENTITY_GROUP_COMPONENT_TYPE); + Vector3d componentSelector = role.getActiveMotionController().getComponentSelector(); + this.groupSteeringAccumulator.setComponentSelector(componentSelector); + this.groupSteeringAccumulator.setMaxRange(role.getFlockInfluenceRange()); + this.groupSteeringAccumulator.begin(ref, componentAccessor); + entityGroup.forEachMemberExcludingSelf( + (iGroupEntity, _entity, _groupSteeringAccumulator, _store) -> _groupSteeringAccumulator.processEntity(iGroupEntity, _store), + ref, + this.groupSteeringAccumulator, + componentAccessor + ); + this.groupSteeringAccumulator.end(); + double weightCohesion = 1.0; + double weightSeparation = 1.0; + Ref leaderRef = entityGroup.getLeaderRef(); + if (!leaderRef.isValid()) { + return false; + } else { + Vector3d sumOfPositions = this.groupSteeringAccumulator.getSumOfPositions(); + Vector3d sumOfVelocities = this.groupSteeringAccumulator.getSumOfVelocities(); + Vector3d sumOfDistances = this.groupSteeringAccumulator.getSumOfDistances(); + TransformComponent leaderTransformComponent = componentAccessor.getComponent(leaderRef, TRANSFORM_COMPONENT_TYPE); + + assert leaderTransformComponent != null; + + Vector3d position = leaderTransformComponent.getPosition(); + Vector3d toLeader = new Vector3d(position.getX(), position.getY(), position.getZ()); + if (sumOfVelocities.squaredLength() > 1.0E-4) { + desiredSteering.setYaw(PhysicsMath.headingFromDirection(sumOfVelocities.getX(), sumOfVelocities.getZ())); + } else { + TransformComponent parentEntityTransformComponent = componentAccessor.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + + assert parentEntityTransformComponent != null; + + desiredSteering.setYaw(parentEntityTransformComponent.getRotation().getYaw()); + } + + sumOfPositions.subtract(position.getX(), position.getY(), position.getZ()).scale(componentSelector); + if (sumOfPositions.squaredLength() > 1.0E-4) { + sumOfPositions.normalize().scale(weightCohesion); + } else { + sumOfPositions.assign(0.0); + } + + if (sumOfDistances.squaredLength() > 1.0E-4) { + sumOfDistances.normalize().scale(-weightSeparation); + } else { + sumOfDistances.assign(0.0); + } + + toLeader.subtract(position.getX(), position.getY(), position.getZ()).scale(componentSelector); + toLeader.normalize().scale(0.5); + sumOfPositions.add(sumOfDistances).add(toLeader); + if (sumOfPositions.squaredLength() > 1.0E-4) { + sumOfPositions.normalize(); + } else { + sumOfPositions.assign(0.0); + } + + desiredSteering.setTranslation(sumOfPositions); + return true; + } + } else { + return false; + } + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/EntityFilterFlock.java b/src/com/hypixel/hytale/server/flock/corecomponents/EntityFilterFlock.java new file mode 100644 index 0000000..749266a --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/EntityFilterFlock.java @@ -0,0 +1,92 @@ +package com.hypixel.hytale.server.flock.corecomponents; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.group.EntityGroup; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.FlockMembership; +import com.hypixel.hytale.server.flock.FlockMembershipSystems; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderEntityFilterFlock; +import com.hypixel.hytale.server.npc.corecomponents.EntityFilterBase; +import com.hypixel.hytale.server.npc.movement.FlockMembershipType; +import com.hypixel.hytale.server.npc.movement.FlockPlayerMembership; +import com.hypixel.hytale.server.npc.role.Role; +import javax.annotation.Nonnull; + +public class EntityFilterFlock extends EntityFilterBase { + public static final int COST = 100; + protected static final ComponentType FLOCK_MEMBERSHIP_COMPONENT_TYPE = FlockMembership.getComponentType(); + protected static final ComponentType PLAYER_COMPONENT_TYPE = Player.getComponentType(); + protected static final ComponentType ENTITY_GROUP_COMPONENT_TYPE = EntityGroup.getComponentType(); + protected final FlockMembershipType flockMembership; + protected final FlockPlayerMembership flockPlayerMembership; + protected final int[] size; + protected final boolean checkCanJoin; + + public EntityFilterFlock(@Nonnull BuilderEntityFilterFlock builder) { + this.flockMembership = builder.getFlockMembership(); + this.flockPlayerMembership = builder.getFlockPlayerMembership(); + this.size = builder.getSize(); + this.checkCanJoin = builder.isCheckCanJoin(); + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + FlockMembership membership = store.getComponent(targetRef, FLOCK_MEMBERSHIP_COMPONENT_TYPE); + switch (this.flockMembership) { + case Leader: + if (membership == null || !membership.getMembershipType().isActingAsLeader()) { + return false; + } + break; + case Follower: + if (membership == null || membership.getMembershipType().isActingAsLeader()) { + return false; + } + break; + case Member: + if (membership == null) { + return false; + } + break; + case NotMember: + if (membership != null) { + return false; + } + case Any: + break; + default: + throw new MatchException(null, null); + } + + EntityGroup group = null; + if (membership != null) { + Ref flockReference = membership.getFlockRef(); + if (flockReference != null && flockReference.isValid()) { + group = store.getComponent(flockReference, ENTITY_GROUP_COMPONENT_TYPE); + } + } + + if (this.size != null && group != null && (group.size() < this.size[0] || group.size() > this.size[1])) { + return false; + } else if (!this.checkCanJoin || membership != null && FlockMembershipSystems.canJoinFlock(targetRef, membership.getFlockRef(), store)) { + Ref leaderRef = group != null ? group.getLeaderRef() : null; + boolean leaderIsPlayer = leaderRef != null && leaderRef.isValid() && store.getArchetype(leaderRef).contains(PLAYER_COMPONENT_TYPE); + + return switch (this.flockPlayerMembership) { + case Member -> leaderIsPlayer; + case NotMember -> !leaderIsPlayer; + case Any -> true; + }; + } else { + return false; + } + } + + @Override + public int cost() { + return 100; + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/SensorFlockCombatDamage.java b/src/com/hypixel/hytale/server/flock/corecomponents/SensorFlockCombatDamage.java new file mode 100644 index 0000000..dc76f8a --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/SensorFlockCombatDamage.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.flock.corecomponents; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.Flock; +import com.hypixel.hytale.server.flock.FlockPlugin; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderSensorFlockCombatDamage; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.EntityPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.DamageData; +import javax.annotation.Nonnull; + +public class SensorFlockCombatDamage extends SensorBase { + protected final boolean leaderOnly; + protected final EntityPositionProvider positionProvider = new EntityPositionProvider(); + + public SensorFlockCombatDamage(@Nonnull BuilderSensorFlockCombatDamage builder) { + super(builder); + this.leaderOnly = builder.isLeaderOnly(); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + this.positionProvider.clear(); + return false; + } else { + Flock flock = FlockPlugin.getFlock(store, ref); + if (flock == null) { + return false; + } else { + DamageData damageData = this.leaderOnly ? flock.getLeaderDamageData() : flock.getDamageData(); + Ref entity = damageData.getMostDamagingAttacker(); + return this.positionProvider.setTarget(entity, store) != null; + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/SensorFlockLeader.java b/src/com/hypixel/hytale/server/flock/corecomponents/SensorFlockLeader.java new file mode 100644 index 0000000..deadd04 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/SensorFlockLeader.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.flock.corecomponents; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.group.EntityGroup; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.FlockMembership; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderSensorFlockLeader; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.EntityPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorFlockLeader extends SensorBase { + protected final EntityPositionProvider positionProvider = new EntityPositionProvider(); + + public SensorFlockLeader(@Nonnull BuilderSensorFlockLeader builder) { + super(builder); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + this.positionProvider.clear(); + return false; + } else { + FlockMembership membership = store.getComponent(ref, FlockMembership.getComponentType()); + if (membership == null) { + this.positionProvider.clear(); + return false; + } else { + EntityGroup group = null; + Ref flockReference = membership.getFlockRef(); + if (flockReference != null && flockReference.isValid()) { + group = store.getComponent(flockReference, EntityGroup.getComponentType()); + } + + if (group == null) { + this.positionProvider.clear(); + return false; + } else { + return this.positionProvider.setTarget(group.getLeaderRef(), store) != null; + } + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/SensorInflictedDamage.java b/src/com/hypixel/hytale/server/flock/corecomponents/SensorInflictedDamage.java new file mode 100644 index 0000000..4362e51 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/SensorInflictedDamage.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.server.flock.corecomponents; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.group.EntityGroup; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.Flock; +import com.hypixel.hytale.server.flock.FlockPlugin; +import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderSensorInflictedDamage; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.EntityPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.DamageData; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class SensorInflictedDamage extends SensorBase { + protected final SensorInflictedDamage.Target target; + protected final boolean friendlyFire; + protected final EntityPositionProvider positionProvider = new EntityPositionProvider(); + + public SensorInflictedDamage(@Nonnull BuilderSensorInflictedDamage builder) { + super(builder); + this.target = builder.getTarget(); + this.friendlyFire = builder.isFriendlyFire(); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + this.positionProvider.clear(); + return false; + } else { + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + DamageData damageData; + if (this.target == SensorInflictedDamage.Target.Self) { + damageData = npcComponent.getDamageData(); + } else { + Flock npcFlock = FlockPlugin.getFlock(store, ref); + if (npcFlock == null) { + this.positionProvider.clear(); + return false; + } + + damageData = this.target == SensorInflictedDamage.Target.FlockLeader ? npcFlock.getLeaderDamageData() : npcFlock.getDamageData(); + } + + Ref victimReference = damageData.getMostDamagedVictim(); + if (victimReference == null) { + this.positionProvider.clear(); + return false; + } else if (!this.friendlyFire && inSameFlock(ref, victimReference, store)) { + this.positionProvider.clear(); + return false; + } else { + return this.positionProvider.setTarget(victimReference, store) != null; + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } + + protected static boolean inSameFlock(@Nonnull Ref selfReference, @Nonnull Ref target, @Nonnull Store store) { + Ref flockReference = FlockPlugin.getFlockReference(selfReference, store); + if (flockReference == null) { + return false; + } else { + EntityGroup entityGroupComponent = store.getComponent(flockReference, EntityGroup.getComponentType()); + + assert entityGroupComponent != null; + + return entityGroupComponent.isMember(target); + } + } + + public static enum Target implements Supplier { + Flock("Check flock"), + FlockLeader("Check flock leader only"), + Self("Check self"); + + private final String description; + + private Target(String description) { + this.description = description; + } + + public String get() { + return this.description; + } + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderActionFlockBeacon.java b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderActionFlockBeacon.java new file mode 100644 index 0000000..21db366 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderActionFlockBeacon.java @@ -0,0 +1,109 @@ +package com.hypixel.hytale.server.flock.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.flock.corecomponents.ActionFlockBeacon; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleOrValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNullOrNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import java.util.Set; +import javax.annotation.Nonnull; + +public class BuilderActionFlockBeacon extends BuilderActionBase { + protected final StringHolder message = new StringHolder(); + protected String sendTargetSlot; + protected double expirationTime; + protected boolean sendToSelf; + protected boolean sendToLeaderOnly; + + public BuilderActionFlockBeacon() { + } + + @Nonnull + public ActionFlockBeacon build(@Nonnull BuilderSupport builderSupport) { + return new ActionFlockBeacon(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Send beacon message to flock"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Let the NPC send out a message to the flock members"; + } + + @Override + public void registerTags(@Nonnull Set tags) { + super.registerTags(tags); + tags.add("message"); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + public BuilderActionFlockBeacon readConfig(@Nonnull JsonElement data) { + this.requireString(data, "Message", this.message, StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "Message to send to targets", null); + this.getString( + data, + "SendTargetSlot", + b -> this.sendTargetSlot = b, + null, + StringNullOrNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "The marked target slot to send. If omitted, sends own position", + null + ); + this.getDouble( + data, + "ExpirationTime", + d -> this.expirationTime = d, + 1.0, + DoubleOrValidator.greaterEqual0OrMinus1(), + BuilderDescriptorState.Stable, + "The number of seconds that the message should last. -1 represents infinite time.", + "The number of seconds that the message should last and be acknowledged by the receiving NPC. -1 represents infinite time." + ); + this.getBoolean(data, "SendToSelf", b -> this.sendToSelf = b, true, BuilderDescriptorState.Stable, "Send the message to self", null); + this.getBoolean( + data, + "SendToLeaderOnly", + b -> this.sendToLeaderOnly = b, + false, + BuilderDescriptorState.Stable, + "Only send the message to the leader of the flock", + null + ); + return this; + } + + public String getMessage(@Nonnull BuilderSupport builderSupport) { + return this.message.get(builderSupport.getExecutionContext()); + } + + public int getSendTargetSlot(@Nonnull BuilderSupport support) { + return this.sendTargetSlot == null ? Integer.MIN_VALUE : support.getTargetSlot(this.sendTargetSlot); + } + + public double getExpirationTime() { + return this.expirationTime; + } + + public boolean isSendToSelf() { + return this.sendToSelf; + } + + public boolean isSendToLeaderOnly() { + return this.sendToLeaderOnly; + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderActionFlockJoin.java b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderActionFlockJoin.java new file mode 100644 index 0000000..2493d26 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderActionFlockJoin.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.flock.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.flock.corecomponents.ActionFlockJoin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import javax.annotation.Nonnull; + +public class BuilderActionFlockJoin extends BuilderActionBase { + protected boolean forceJoin; + + public BuilderActionFlockJoin() { + } + + @Nonnull + public ActionFlockJoin build(BuilderSupport builderSupport) { + return new ActionFlockJoin(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Join/build a flock with other entity"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Tries to build/join flock with target. Fails if both NPC and target are in a flock. If either NPC or target are in a flock, the one not in flock tries to join existing flock.If NPC and target are both not in a flock, a new flock with NPC is created and target is tried to be joined.Joining the flock can be rejected if the joining entity does have the correct type or the flock is full. This can be overridden by setting the ForceJoin flag to true."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionFlockJoin readConfig(@Nonnull JsonElement data) { + this.getBoolean( + data, + "ForceJoin", + b -> this.forceJoin = b, + false, + BuilderDescriptorState.Stable, + "Enforce joining flock if true", + "Disables checking flock join conditions test and forces joining flock." + ); + this.requireFeature(Feature.LiveEntity); + return this; + } + + public boolean isForceJoin() { + return this.forceJoin; + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderActionFlockLeave.java b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderActionFlockLeave.java new file mode 100644 index 0000000..2359531 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderActionFlockLeave.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.server.flock.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.flock.corecomponents.ActionFlockLeave; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import javax.annotation.Nonnull; + +public class BuilderActionFlockLeave extends BuilderActionBase { + public BuilderActionFlockLeave() { + } + + @Nonnull + public ActionFlockLeave build(BuilderSupport builderSupport) { + return new ActionFlockLeave(this); + } + + @Nonnull + public BuilderActionFlockLeave readConfig(JsonElement data) { + return this; + } + + @Nonnull + @Override + public String getShortDescription() { + return "Leave flock."; + } + + @Nonnull + @Override + public String getLongDescription() { + return "NPC leaves flock currently in. Does nothing when not in flock."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderActionFlockSetTarget.java b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderActionFlockSetTarget.java new file mode 100644 index 0000000..2b94b22 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderActionFlockSetTarget.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.flock.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.flock.corecomponents.ActionFlockSetTarget; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import javax.annotation.Nonnull; + +public class BuilderActionFlockSetTarget extends BuilderActionBase { + protected boolean clear; + protected final StringHolder targetSlot = new StringHolder(); + + public BuilderActionFlockSetTarget() { + } + + @Nonnull + public ActionFlockSetTarget build(@Nonnull BuilderSupport builderSupport) { + return new ActionFlockSetTarget(this, builderSupport); + } + + @Nonnull + public BuilderActionFlockSetTarget readConfig(@Nonnull JsonElement data) { + this.getBoolean( + data, + "Clear", + v -> this.clear = v, + false, + BuilderDescriptorState.Stable, + "Clear locked target if true else set.", + "If true, clear locked target. If false, set to current target." + ); + this.getString( + data, "TargetSlot", this.targetSlot, "LockedTarget", StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The target slot to use", null + ); + this.requireFeature(Feature.LiveEntity); + return this; + } + + @Nonnull + @Override + public String getShortDescription() { + return "Set or clear locked target for flock."; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Sets or clears the locked target for the flock the NPC is member of. If Clear flag is true, the locked target is cleared otherwise it is set to the the target.The flock leader is explicitly excluded from this operation."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + public boolean isClear() { + return this.clear; + } + + public String getTargetSlot(@Nonnull BuilderSupport support) { + return this.targetSlot.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderActionFlockState.java b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderActionFlockState.java new file mode 100644 index 0000000..1969b19 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderActionFlockState.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.server.flock.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.flock.corecomponents.ActionFlockState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StateStringValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import javax.annotation.Nonnull; + +public class BuilderActionFlockState extends BuilderActionBase { + protected final StringHolder state = new StringHolder(); + + public BuilderActionFlockState() { + } + + @Nonnull + public ActionFlockState build(@Nonnull BuilderSupport builderSupport) { + return new ActionFlockState(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Set state name for flock."; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Sets the state name for the flock the NPC is member of.The flock leader is explicitly excluded from this operation."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionFlockState readConfig(@Nonnull JsonElement data) { + this.requireString(data, "State", this.state, StateStringValidator.requireMainState(), BuilderDescriptorState.Stable, "State name to set", null); + this.requireInstructionType(InstructionType.StateChangeAllowedInstructions); + return this; + } + + public String getState(@Nonnull BuilderSupport support) { + return this.state.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderBodyMotionFlock.java b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderBodyMotionFlock.java new file mode 100644 index 0000000..45f80f5 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderBodyMotionFlock.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.flock.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.flock.corecomponents.BodyMotionFlock; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderBodyMotionBase; +import com.hypixel.hytale.server.npc.instructions.BodyMotion; +import javax.annotation.Nonnull; + +public class BuilderBodyMotionFlock extends BuilderBodyMotionBase { + public BuilderBodyMotionFlock() { + } + + @Nonnull + public BodyMotionFlock build(BuilderSupport builderSupport) { + return new BodyMotionFlock(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Flocking - WIP"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Flocking - WIP"; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + @Override + public Builder readConfig(JsonElement data) { + return this; + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderEntityFilterFlock.java b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderEntityFilterFlock.java new file mode 100644 index 0000000..b631e53 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderEntityFilterFlock.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.server.flock.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.flock.corecomponents.EntityFilterFlock; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderEntityFilterBase; +import com.hypixel.hytale.server.npc.movement.FlockMembershipType; +import com.hypixel.hytale.server.npc.movement.FlockPlayerMembership; +import javax.annotation.Nonnull; + +public class BuilderEntityFilterFlock extends BuilderEntityFilterBase { + protected FlockMembershipType flockMembership; + protected FlockPlayerMembership flockPlayerMembership; + protected int[] size; + protected boolean checkCanJoin; + + public BuilderEntityFilterFlock() { + } + + @Nonnull + public IEntityFilter build(BuilderSupport builderSupport) { + return new EntityFilterFlock(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test for flock membership and related properties"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getEnum( + data, + "FlockStatus", + v -> this.flockMembership = v, + FlockMembershipType.class, + FlockMembershipType.Any, + BuilderDescriptorState.Stable, + "Test for NPC status in relation to flock", + null + ); + this.getEnum( + data, + "FlockPlayerStatus", + v -> this.flockPlayerMembership = v, + FlockPlayerMembership.class, + FlockPlayerMembership.Any, + BuilderDescriptorState.Stable, + "Test for Player status for flock NPC is member", + null + ); + this.getIntRange(data, "Size", v -> this.size = v, null, null, BuilderDescriptorState.Stable, "Check for a certain range of NPCs in the flock", null); + this.getBoolean( + data, + "CheckCanJoin", + v -> this.checkCanJoin = v, + false, + BuilderDescriptorState.Stable, + "If true, will filter entities in a flock the executor can join", + null + ); + return this; + } + + public int[] getSize() { + return this.size; + } + + public FlockMembershipType getFlockMembership() { + return this.flockMembership; + } + + public FlockPlayerMembership getFlockPlayerMembership() { + return this.flockPlayerMembership; + } + + public boolean isCheckCanJoin() { + return this.checkCanJoin; + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderSensorFlockCombatDamage.java b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderSensorFlockCombatDamage.java new file mode 100644 index 0000000..6cacda2 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderSensorFlockCombatDamage.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.flock.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.flock.corecomponents.SensorFlockCombatDamage; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorFlockCombatDamage extends BuilderSensorBase { + protected boolean leaderOnly; + + public BuilderSensorFlockCombatDamage() { + } + + @Nonnull + public SensorFlockCombatDamage build(BuilderSupport builderSupport) { + return new SensorFlockCombatDamage(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test if flock with NPC received combat damage"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Return true if flock with NPC received combat damage. Target position is entity which did most damage."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getBoolean(data, "LeaderOnly", v -> this.leaderOnly = v, true, BuilderDescriptorState.Stable, "Only test for damage to flock leader", null); + this.provideFeature(Feature.LiveEntity); + return this; + } + + public boolean isLeaderOnly() { + return this.leaderOnly; + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderSensorFlockLeader.java b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderSensorFlockLeader.java new file mode 100644 index 0000000..1aa0285 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderSensorFlockLeader.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.flock.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.flock.corecomponents.SensorFlockLeader; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorFlockLeader extends BuilderSensorBase { + public BuilderSensorFlockLeader() { + } + + @Nonnull + public SensorFlockLeader build(BuilderSupport builderSupport) { + return new SensorFlockLeader(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test for the presence and provide position of the flock leader"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(JsonElement data) { + this.provideFeature(Feature.LiveEntity); + return this; + } +} diff --git a/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderSensorInflictedDamage.java b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderSensorInflictedDamage.java new file mode 100644 index 0000000..463634e --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/corecomponents/builders/BuilderSensorInflictedDamage.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.flock.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.flock.corecomponents.SensorInflictedDamage; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorInflictedDamage extends BuilderSensorBase { + protected SensorInflictedDamage.Target target; + protected boolean friendlyFire; + + public BuilderSensorInflictedDamage() { + } + + @Nonnull + public SensorInflictedDamage build(BuilderSupport builderSupport) { + return new SensorInflictedDamage(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test if an individual or the flock it belongs to inflicted combat damage"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Return true if an individual or the flock it belongs to inflicted combat damage. Target position is entity which received most damage."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getEnum( + data, + "Target", + v -> this.target = v, + SensorInflictedDamage.Target.class, + SensorInflictedDamage.Target.Self, + BuilderDescriptorState.Stable, + "Who to check has inflicted damage", + null + ); + this.getBoolean(data, "FriendlyFire", v -> this.friendlyFire = v, false, BuilderDescriptorState.Stable, "Consider friendly fire too", null); + this.provideFeature(Feature.LiveEntity); + return this; + } + + public boolean isFriendlyFire() { + return this.friendlyFire; + } + + public SensorInflictedDamage.Target getTarget() { + return this.target; + } +} diff --git a/src/com/hypixel/hytale/server/flock/decisionmaker/conditions/FlockSizeCondition.java b/src/com/hypixel/hytale/server/flock/decisionmaker/conditions/FlockSizeCondition.java new file mode 100644 index 0000000..e760d41 --- /dev/null +++ b/src/com/hypixel/hytale/server/flock/decisionmaker/conditions/FlockSizeCondition.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.flock.decisionmaker.conditions; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.group.EntityGroup; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.FlockMembership; +import com.hypixel.hytale.server.npc.decisionmaker.core.EvaluationContext; +import com.hypixel.hytale.server.npc.decisionmaker.core.conditions.base.ScaledCurveCondition; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import javax.annotation.Nonnull; + +public class FlockSizeCondition extends ScaledCurveCondition { + public static final BuilderCodec CODEC = BuilderCodec.builder( + FlockSizeCondition.class, FlockSizeCondition::new, ScaledCurveCondition.ABSTRACT_CODEC + ) + .build(); + + protected FlockSizeCondition() { + } + + @Override + protected double getInput( + int selfIndex, + @Nonnull ArchetypeChunk archetypeChunk, + Ref target, + @Nonnull CommandBuffer commandBuffer, + EvaluationContext context + ) { + NPCEntity self = archetypeChunk.getComponent(selfIndex, NPCEntity.getComponentType()); + FlockMembership membership = archetypeChunk.getComponent(selfIndex, FlockMembership.getComponentType()); + if (membership == null) { + return 1.0; + } else { + Ref flockReference = membership.getFlockRef(); + return flockReference != null && flockReference.isValid() ? commandBuffer.getComponent(flockReference, EntityGroup.getComponentType()).size() : 1.0; + } + } + + @Nonnull + @Override + public String toString() { + return "FlockSizeCondition{} " + super.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/migrations/RenameSpawnMarkerMigration.java b/src/com/hypixel/hytale/server/migrations/RenameSpawnMarkerMigration.java new file mode 100644 index 0000000..f4550ba --- /dev/null +++ b/src/com/hypixel/hytale/server/migrations/RenameSpawnMarkerMigration.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.server.migrations; + +import com.hypixel.hytale.assetstore.AssetValidationResults; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.modules.migrations.EntityMigration; +import com.hypixel.hytale.server.spawning.assets.spawnmarker.config.SpawnMarker; +import com.hypixel.hytale.server.spawning.spawnmarkers.SpawnMarkerEntity; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class RenameSpawnMarkerMigration extends EntityMigration { + @Nonnull + public static HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + @Nonnull + private Map idMigrations = new HashMap<>(); + + public RenameSpawnMarkerMigration(@Nonnull Path filePath) { + super(SpawnMarkerEntity.class, version -> { + ExtraInfo extraInfo = new ExtraInfo(version, AssetValidationResults::new); + ((AssetValidationResults)extraInfo.getValidationResults()).disableMissingAssetFor(SpawnMarker.class); + return extraInfo; + }); + + List lines; + try { + lines = Files.readAllLines(filePath); + } catch (IOException var8) { + throw SneakyThrow.sneakyThrow(var8); + } + + for (String line : lines) { + String[] split = line.split(":"); + if (split.length == 2) { + String spawnMarkerMigrationId = split[1]; + SpawnMarker spawnMarker = SpawnMarker.getAssetMap().getAsset(spawnMarkerMigrationId); + if (spawnMarker == null) { + LOGGER.at(Level.WARNING).log("SpawnMarker '%s' does not exist!", spawnMarkerMigrationId); + } else { + this.idMigrations.put(split[0], spawnMarker); + } + } + } + } + + protected boolean migrate(@Nonnull SpawnMarkerEntity entity) { + String spawnMarkerId = entity.getSpawnMarkerId(); + SpawnMarker spawnMarker = this.idMigrations.get(spawnMarkerId); + if (spawnMarker == null) { + return false; + } else { + entity.setSpawnMarker(spawnMarker); + return true; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/AllNPCsLoadedEvent.java b/src/com/hypixel/hytale/server/npc/AllNPCsLoadedEvent.java new file mode 100644 index 0000000..b8559a5 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/AllNPCsLoadedEvent.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.server.npc; + +import com.hypixel.hytale.event.IEvent; +import com.hypixel.hytale.server.npc.asset.builder.BuilderInfo; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class AllNPCsLoadedEvent implements IEvent { + @Nonnull + private final Int2ObjectMap allNPCs; + @Nonnull + private final Int2ObjectMap loadedNPCs; + + public AllNPCsLoadedEvent(@Nonnull Int2ObjectMap allNPCs, @Nonnull Int2ObjectMap loadedNPCs) { + Objects.requireNonNull(allNPCs, "Map of all NPCs must not be empty in AllNPCsLoadedEvent"); + Objects.requireNonNull(loadedNPCs, "Map of loaded NPCs must not be empty in AllNPCsLoadedEvent"); + this.allNPCs = Int2ObjectMaps.unmodifiable(allNPCs); + this.loadedNPCs = Int2ObjectMaps.unmodifiable(loadedNPCs); + } + + @Nonnull + public Int2ObjectMap getAllNPCs() { + return this.allNPCs; + } + + @Nonnull + public Int2ObjectMap getLoadedNPCs() { + return this.loadedNPCs; + } + + @Nonnull + @Override + public String toString() { + return "AllNPCsLoadedEvent{allNPCs=" + this.allNPCs + ", loadedNPCs=" + this.loadedNPCs + "}"; + } +} diff --git a/src/com/hypixel/hytale/server/npc/NPCPlugin.java b/src/com/hypixel/hytale/server/npc/NPCPlugin.java new file mode 100644 index 0000000..a118c5d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/NPCPlugin.java @@ -0,0 +1,1651 @@ +package com.hypixel.hytale.server.npc; + +import com.hypixel.hytale.assetstore.AssetMap; +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; +import com.hypixel.hytale.assetstore.event.RemovedAssetsEvent; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.builtin.path.path.TransientPathDefinition; +import com.hypixel.hytale.builtin.path.waypoint.RelativeWaypointDefinition; +import com.hypixel.hytale.builtin.tagset.TagSetPlugin; +import com.hypixel.hytale.builtin.tagset.config.NPCGroup; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.common.benchmark.TimeDistributionRecorder; +import com.hypixel.hytale.common.util.FormatUtil; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.dependency.Dependency; +import com.hypixel.hytale.component.dependency.Order; +import com.hypixel.hytale.component.dependency.SystemDependency; +import com.hypixel.hytale.component.spatial.KDTree; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.event.EventRegistry; +import com.hypixel.hytale.function.consumer.TriConsumer; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.Options; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.asset.AssetPackRegisterEvent; +import com.hypixel.hytale.server.core.asset.AssetPackUnregisterEvent; +import com.hypixel.hytale.server.core.asset.GenerateSchemaEvent; +import com.hypixel.hytale.server.core.asset.HytaleAssetStore; +import com.hypixel.hytale.server.core.asset.LoadAssetEvent; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.asset.type.responsecurve.config.ResponseCurve; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.DisplayNameComponent; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.PersistentModel; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsSystems; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.migrations.MigrationModule; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.npc.INonPlayerCharacter; +import com.hypixel.hytale.server.core.universe.world.path.WorldPathChangedEvent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.Config; +import com.hypixel.hytale.server.flock.FlockPlugin; +import com.hypixel.hytale.server.flock.config.FlockAsset; +import com.hypixel.hytale.server.migrations.RenameSpawnMarkerMigration; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptor; +import com.hypixel.hytale.server.npc.asset.builder.BuilderFactory; +import com.hypixel.hytale.server.npc.asset.builder.BuilderInfo; +import com.hypixel.hytale.server.npc.asset.builder.BuilderManager; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.blackboard.Blackboard; +import com.hypixel.hytale.server.npc.blackboard.view.attitude.AttitudeMap; +import com.hypixel.hytale.server.npc.blackboard.view.attitude.ItemAttitudeMap; +import com.hypixel.hytale.server.npc.blackboard.view.combat.CombatViewSystems; +import com.hypixel.hytale.server.npc.commands.NPCCommand; +import com.hypixel.hytale.server.npc.commands.NPCRunTestsCommand; +import com.hypixel.hytale.server.npc.components.FailedSpawnComponent; +import com.hypixel.hytale.server.npc.components.SortBufferProviderResource; +import com.hypixel.hytale.server.npc.components.StepComponent; +import com.hypixel.hytale.server.npc.components.Timers; +import com.hypixel.hytale.server.npc.components.messaging.BeaconSupport; +import com.hypixel.hytale.server.npc.components.messaging.NPCBlockEventSupport; +import com.hypixel.hytale.server.npc.components.messaging.NPCEntityEventSupport; +import com.hypixel.hytale.server.npc.components.messaging.PlayerBlockEventSupport; +import com.hypixel.hytale.server.npc.components.messaging.PlayerEntityEventSupport; +import com.hypixel.hytale.server.npc.config.AttitudeGroup; +import com.hypixel.hytale.server.npc.config.ItemAttitudeGroup; +import com.hypixel.hytale.server.npc.config.balancing.BalanceAsset; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.ISensorEntityCollector; +import com.hypixel.hytale.server.npc.corecomponents.ISensorEntityPrioritiser; +import com.hypixel.hytale.server.npc.corecomponents.WeightedAction; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionAppearance; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionDisplayName; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionModelAttachment; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionPlayAnimation; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionPlaySound; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionSpawnParticles; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderSensorAnimation; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderWeightedAction; +import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderActionApplyEntityEffect; +import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderActionAttack; +import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderBodyMotionAimCharge; +import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderHeadMotionAim; +import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderSensorDamage; +import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderSensorIsBackingAway; +import com.hypixel.hytale.server.npc.corecomponents.debug.builders.BuilderActionLog; +import com.hypixel.hytale.server.npc.corecomponents.debug.builders.BuilderActionTest; +import com.hypixel.hytale.server.npc.corecomponents.debug.builders.BuilderBodyMotionTestProbe; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionBeacon; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionIgnoreForAvoidance; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionNotify; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionOverrideAttitude; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionReleaseTarget; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionSetMarkedTarget; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionSetStat; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderHeadMotionWatch; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorBeacon; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorCount; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorEntity; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorKill; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorPlayer; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorSelf; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorTarget; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterAltitude; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterAnd; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterAttitude; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterCombat; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterHeightDifference; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterInsideBlock; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterInventory; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterItemInHand; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterLineOfSight; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterMovementState; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterNPCGroup; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterNot; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterOr; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterSpotsMe; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterStandingOnBlock; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterStat; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterViewSector; +import com.hypixel.hytale.server.npc.corecomponents.entity.prioritisers.builders.BuilderSensorEntityPrioritiserAttitude; +import com.hypixel.hytale.server.npc.corecomponents.interaction.builders.BuilderActionLockOnInteractionTarget; +import com.hypixel.hytale.server.npc.corecomponents.interaction.builders.BuilderActionSetInteractable; +import com.hypixel.hytale.server.npc.corecomponents.interaction.builders.BuilderSensorCanInteract; +import com.hypixel.hytale.server.npc.corecomponents.interaction.builders.BuilderSensorHasInteracted; +import com.hypixel.hytale.server.npc.corecomponents.interaction.builders.BuilderSensorInteractionContext; +import com.hypixel.hytale.server.npc.corecomponents.items.builders.BuilderActionDropItem; +import com.hypixel.hytale.server.npc.corecomponents.items.builders.BuilderActionInventory; +import com.hypixel.hytale.server.npc.corecomponents.items.builders.BuilderActionPickUpItem; +import com.hypixel.hytale.server.npc.corecomponents.items.builders.BuilderSensorDroppedItem; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionDelayDespawn; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionDespawn; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionDie; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionRemove; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionRole; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionSpawn; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderSensorAge; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderActionCrouch; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderActionOverrideAltitude; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderActionRecomputePath; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionFind; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionLand; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionLeave; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionMaintainDistance; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionMatchLook; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionMoveAway; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionTakeOff; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionTeleport; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionWander; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionWanderInCircle; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionWanderInRect; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderSensorInAir; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderSensorMotionController; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderSensorNav; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderSensorOnGround; +import com.hypixel.hytale.server.npc.corecomponents.statemachine.builders.BuilderActionParentState; +import com.hypixel.hytale.server.npc.corecomponents.statemachine.builders.BuilderActionState; +import com.hypixel.hytale.server.npc.corecomponents.statemachine.builders.BuilderActionToggleStateEvaluator; +import com.hypixel.hytale.server.npc.corecomponents.statemachine.builders.BuilderSensorIsBusy; +import com.hypixel.hytale.server.npc.corecomponents.statemachine.builders.BuilderSensorState; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionSetAlarm; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerContinue; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerModify; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerPause; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerRestart; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerStart; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerStop; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderBodyMotionTimer; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderHeadMotionTimer; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderSensorAlarm; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderSensorTimer; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderActionNothing; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderActionRandom; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderActionResetInstructions; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderActionSequence; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderActionSetFlag; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderActionTimeout; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderBodyMotionNothing; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderBodyMotionSequence; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderHeadMotionNothing; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderHeadMotionSequence; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorAdjustPosition; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorAnd; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorAny; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorEval; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorFlag; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorNot; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorOr; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorRandom; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorSwitch; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorValueProviderWrapper; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderValueToParameterMapping; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionMakePath; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionPlaceBlock; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionResetBlockSensors; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionResetPath; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionResetSearchRays; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionSetBlockToPlace; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionSetLeashPosition; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionStorePosition; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionTriggerSpawners; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderBodyMotionPath; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderHeadMotionObserve; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorBlock; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorBlockChange; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorBlockType; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorCanPlace; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorEntityEvent; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorInWater; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorLeash; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorLight; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorPath; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorReadPosition; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorSearchRay; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorTime; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorWeather; +import com.hypixel.hytale.server.npc.decisionmaker.core.conditions.base.Condition; +import com.hypixel.hytale.server.npc.decisionmaker.stateevaluator.StateEvaluator; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.instructions.Action; +import com.hypixel.hytale.server.npc.instructions.ActionList; +import com.hypixel.hytale.server.npc.instructions.BodyMotion; +import com.hypixel.hytale.server.npc.instructions.HeadMotion; +import com.hypixel.hytale.server.npc.instructions.Instruction; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.instructions.builders.BuilderActionList; +import com.hypixel.hytale.server.npc.instructions.builders.BuilderInstruction; +import com.hypixel.hytale.server.npc.instructions.builders.BuilderInstructionRandomized; +import com.hypixel.hytale.server.npc.instructions.builders.BuilderInstructionReference; +import com.hypixel.hytale.server.npc.interactions.ContextualUseNPCInteraction; +import com.hypixel.hytale.server.npc.interactions.SpawnNPCInteraction; +import com.hypixel.hytale.server.npc.interactions.UseNPCInteraction; +import com.hypixel.hytale.server.npc.movement.controllers.BuilderMotionControllerMapUtil; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.movement.controllers.builders.BuilderMotionControllerDive; +import com.hypixel.hytale.server.npc.movement.controllers.builders.BuilderMotionControllerFly; +import com.hypixel.hytale.server.npc.movement.controllers.builders.BuilderMotionControllerMap; +import com.hypixel.hytale.server.npc.movement.controllers.builders.BuilderMotionControllerWalk; +import com.hypixel.hytale.server.npc.navigation.AStarNodePoolProviderSimple; +import com.hypixel.hytale.server.npc.path.builders.BuilderRelativeWaypointDefinition; +import com.hypixel.hytale.server.npc.path.builders.BuilderTransientPathDefinition; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.builders.BuilderRole; +import com.hypixel.hytale.server.npc.role.builders.BuilderRoleAbstract; +import com.hypixel.hytale.server.npc.role.builders.BuilderRoleVariant; +import com.hypixel.hytale.server.npc.statetransition.StateTransitionController; +import com.hypixel.hytale.server.npc.systems.AvoidanceSystem; +import com.hypixel.hytale.server.npc.systems.BalancingInitialisationSystem; +import com.hypixel.hytale.server.npc.systems.BlackboardSystems; +import com.hypixel.hytale.server.npc.systems.ComputeVelocitySystem; +import com.hypixel.hytale.server.npc.systems.FailedSpawnSystem; +import com.hypixel.hytale.server.npc.systems.MessageSupportSystem; +import com.hypixel.hytale.server.npc.systems.MovementStatesSystem; +import com.hypixel.hytale.server.npc.systems.NPCDamageSystems; +import com.hypixel.hytale.server.npc.systems.NPCDeathSystems; +import com.hypixel.hytale.server.npc.systems.NPCInteractionSystems; +import com.hypixel.hytale.server.npc.systems.NPCPreTickSystem; +import com.hypixel.hytale.server.npc.systems.NPCSpatialSystem; +import com.hypixel.hytale.server.npc.systems.NPCSystems; +import com.hypixel.hytale.server.npc.systems.NPCVelocityInstructionSystem; +import com.hypixel.hytale.server.npc.systems.NewSpawnStartTickingSystem; +import com.hypixel.hytale.server.npc.systems.PositionCacheSystems; +import com.hypixel.hytale.server.npc.systems.RoleBuilderSystem; +import com.hypixel.hytale.server.npc.systems.RoleChangeSystem; +import com.hypixel.hytale.server.npc.systems.RoleSystems; +import com.hypixel.hytale.server.npc.systems.StateEvaluatorSystem; +import com.hypixel.hytale.server.npc.systems.SteeringSystem; +import com.hypixel.hytale.server.npc.systems.StepCleanupSystem; +import com.hypixel.hytale.server.npc.systems.TimerSystem; +import com.hypixel.hytale.server.npc.util.SensorSupportBenchmark; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.valuestore.ValueStore; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NPCPlugin extends JavaPlugin { + @Nonnull + public static String FACTORY_CLASS_ROLE = "Role"; + @Nonnull + public static String FACTORY_CLASS_BODY_MOTION = "BodyMotion"; + @Nonnull + public static String FACTORY_CLASS_HEAD_MOTION = "HeadMotion"; + @Nonnull + public static String FACTORY_CLASS_ACTION = "Action"; + @Nonnull + public static String FACTORY_CLASS_SENSOR = "Sensor"; + @Nonnull + public static String FACTORY_CLASS_INSTRUCTION = "Instruction"; + @Nonnull + public static String FACTORY_CLASS_TRANSIENT_PATH = "Path"; + @Nonnull + public static String FACTORY_CLASS_ACTION_LIST = "ActionList"; + @Nonnull + public static String ROLE_ASSETS_PATH = "Server/NPC/Roles"; + private static NPCPlugin instance; + protected List builderDescriptors; + protected final BuilderManager builderManager = new BuilderManager(); + protected boolean validateBuilder; + protected int maxBlackboardBlockCountPerType = 20; + protected boolean logFailingTestErrors; + protected String[] presetCoverageTestNPCs; + @Nonnull + protected AtomicInteger pathChangeRevision = new AtomicInteger(0); + @Nonnull + protected Lock benchmarkLock = new ReentrantLock(); + @Nullable + protected Int2ObjectMap roleTickDistribution; + @Nullable + protected Int2ObjectMap roleSensorSupportDistribution; + @Nullable + protected TimeDistributionRecorder roleTickDistributionAll; + @Nullable + protected SensorSupportBenchmark roleSensorSupportDistributionAll; + protected boolean autoReload; + private AttitudeMap attitudeMap; + private ItemAttitudeMap itemAttitudeMap; + private static final Vector3f NULL_ROTATION = new Vector3f(0.0F, 0.0F, 0.0F); + public static final short PRIORITY_LOAD_NPC = -8; + public static final short PRIORITY_SPAWN_VALIDATION = -7; + private final Config config = this.withConfig("NPCModule", NPCPlugin.NPCConfig.CODEC); + private ResourceType blackboardResourceType; + private ResourceType combatDataPoolResourceType; + private ResourceType roleChangeQueueResourceType; + private ResourceType newSpawnStartTickingQueueResourceType; + private ResourceType sortBufferProviderResourceResourceType; + private ResourceType aStarNodePoolProviderSimpleResourceType; + private ResourceType, EntityStore>> npcSpatialResource; + private ComponentType combatDataComponentType; + private ComponentType npcTestDataComponentType; + private ComponentType beaconSupportComponentType; + private ComponentType npcBlockEventSupportComponentType; + private ComponentType playerBlockEventSupportComponentType; + private ComponentType npcEntityEventSupportComponentType; + private ComponentType playerEntityEventSupportComponentType; + private ComponentType stepComponentType; + private ComponentType failedSpawnComponentType; + private ComponentType timersComponentType; + private ComponentType stateEvaluatorComponentType; + private ComponentType valueStoreComponentType; + + public static NPCPlugin get() { + return instance; + } + + public NPCPlugin(@Nonnull JavaPluginInit init) { + super(init); + } + + @Override + protected void setup() { + instance = this; + ComponentRegistryProxy entityStoreRegistry = this.getEntityStoreRegistry(); + EventRegistry eventRegistry = this.getEventRegistry(); + this.getCommandRegistry().registerCommand(new NPCCommand()); + eventRegistry.register(LoadedAssetsEvent.class, ModelAsset.class, this::onModelsChanged); + eventRegistry.register(LoadedAssetsEvent.class, NPCGroup.class, this::onNPCGroupsLoaded); + eventRegistry.register(RemovedAssetsEvent.class, NPCGroup.class, this::onNPCGroupsRemoved); + eventRegistry.register(LoadedAssetsEvent.class, AttitudeGroup.class, this::onAttitudeGroupsLoaded); + eventRegistry.register(RemovedAssetsEvent.class, AttitudeGroup.class, this::onAttitudeGroupsRemoved); + eventRegistry.register(LoadedAssetsEvent.class, ItemAttitudeGroup.class, this::onItemAttitudeGroupsLoaded); + eventRegistry.register(RemovedAssetsEvent.class, ItemAttitudeGroup.class, this::onItemAttitudeGroupsRemoved); + eventRegistry.register(LoadedAssetsEvent.class, BalanceAsset.class, NPCPlugin::onBalanceAssetsChanged); + eventRegistry.register(RemovedAssetsEvent.class, BalanceAsset.class, NPCPlugin::onBalanceAssetsRemoved); + eventRegistry.register(WorldPathChangedEvent.class, this::onPathChange); + eventRegistry.register(AllNPCsLoadedEvent.class, this::onNPCsLoaded); + eventRegistry.register( + (short)-8, + LoadAssetEvent.class, + event -> { + HytaleLogger.getLogger().at(Level.INFO).log("Loading NPC assets phase..."); + long start = System.nanoTime(); + this.builderManager.setAutoReload(this.autoReload); + boolean validateAssets = Options.getOptionSet().has(Options.VALIDATE_ASSETS); + List assetPacks = AssetModule.get().getAssetPacks(); + + for (int i = 0; i < assetPacks.size(); i++) { + boolean includeTests = i == 0; + boolean loadSucceeded = this.builderManager.loadBuilders(assetPacks.get(i), includeTests); + if (!loadSucceeded) { + event.failed(validateAssets, "failed to validate npc's"); + } + } + + HytaleLogger.getLogger() + .at(Level.INFO) + .log( + "Loading NPC assets phase completed! Boot time %s, Took %s", + FormatUtil.nanosToString(System.nanoTime() - event.getBootStart()), + FormatUtil.nanosToString(System.nanoTime() - start) + ); + } + ); + eventRegistry.register(AssetPackRegisterEvent.class, event -> this.builderManager.loadBuilders(event.getAssetPack(), false)); + eventRegistry.register(AssetPackUnregisterEvent.class, event -> this.builderManager.unloadBuilders(event.getAssetPack())); + eventRegistry.register(GenerateSchemaEvent.class, this::onSchemaGenerate); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + AttitudeGroup.class, new IndexedLookupTableAssetMap<>(AttitudeGroup[]::new) + ) + .setPath("NPC/Attitude/Roles")) + .setCodec(AttitudeGroup.CODEC)) + .setKeyFunction(AttitudeGroup::getId)) + .setReplaceOnRemove(AttitudeGroup::new)) + .loadsAfter(NPCGroup.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + ItemAttitudeGroup.class, new IndexedLookupTableAssetMap<>(ItemAttitudeGroup[]::new) + ) + .setPath("NPC/Attitude/Items")) + .setCodec(ItemAttitudeGroup.CODEC)) + .setKeyFunction(ItemAttitudeGroup::getId)) + .setReplaceOnRemove(ItemAttitudeGroup::new)) + .loadsAfter(Item.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + BalanceAsset.class, new DefaultAssetMap() + ) + .setPath("NPC/Balancing")) + .setCodec(BalanceAsset.CODEC)) + .setKeyFunction(BalanceAsset::getId)) + .loadsAfter(Condition.class)) + .build() + ); + AssetRegistry.register( + ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder( + Condition.class, new IndexedLookupTableAssetMap<>(Condition[]::new) + ) + .setPath("NPC/DecisionMaking/Conditions")) + .setCodec(Condition.CODEC)) + .setKeyFunction(Condition::getId)) + .setReplaceOnRemove(Condition::getAlwaysTrueFor)) + .loadsAfter(ResponseCurve.class, NPCGroup.class, EntityStatType.class)) + .build() + ); + this.getEntityRegistry().registerEntity("NPC", NPCEntity.class, NPCEntity::new, NPCEntity.CODEC); + Interaction.CODEC.register("ContextualUseNPC", ContextualUseNPCInteraction.class, ContextualUseNPCInteraction.CODEC); + Interaction.CODEC.register("UseNPC", UseNPCInteraction.class, UseNPCInteraction.CODEC); + Interaction.CODEC.register("SpawnNPC", SpawnNPCInteraction.class, SpawnNPCInteraction.CODEC); + Interaction.getAssetStore().loadAssets("Hytale:Hytale", List.of(new UseNPCInteraction("*UseNPC"))); + RootInteraction.getAssetStore().loadAssets("Hytale:Hytale", List.of(UseNPCInteraction.DEFAULT_ROOT)); + MigrationModule.get().register("spawnMarkers", RenameSpawnMarkerMigration::new); + this.setupNPCLoading(); + this.blackboardResourceType = entityStoreRegistry.registerResource(Blackboard.class, Blackboard::new); + this.combatDataPoolResourceType = entityStoreRegistry.registerResource(CombatViewSystems.CombatDataPool.class, CombatViewSystems.CombatDataPool::new); + this.roleChangeQueueResourceType = entityStoreRegistry.registerResource(RoleChangeSystem.RoleChangeQueue.class, RoleChangeSystem.RoleChangeQueue::new); + this.newSpawnStartTickingQueueResourceType = entityStoreRegistry.registerResource( + NewSpawnStartTickingSystem.QueueResource.class, NewSpawnStartTickingSystem.QueueResource::new + ); + this.sortBufferProviderResourceResourceType = entityStoreRegistry.registerResource(SortBufferProviderResource.class, SortBufferProviderResource::new); + this.aStarNodePoolProviderSimpleResourceType = entityStoreRegistry.registerResource(AStarNodePoolProviderSimple.class, AStarNodePoolProviderSimple::new); + this.npcSpatialResource = entityStoreRegistry.registerSpatialResource(() -> new KDTree<>(Ref::isValid)); + this.combatDataComponentType = entityStoreRegistry.registerComponent(CombatViewSystems.CombatData.class, CombatViewSystems.CombatData::new); + this.npcTestDataComponentType = entityStoreRegistry.registerComponent(NPCRunTestsCommand.NPCTestData.class, NPCRunTestsCommand.NPCTestData::new); + this.beaconSupportComponentType = entityStoreRegistry.registerComponent(BeaconSupport.class, BeaconSupport::new); + this.npcBlockEventSupportComponentType = entityStoreRegistry.registerComponent(NPCBlockEventSupport.class, NPCBlockEventSupport::new); + this.playerBlockEventSupportComponentType = entityStoreRegistry.registerComponent(PlayerBlockEventSupport.class, PlayerBlockEventSupport::new); + this.npcEntityEventSupportComponentType = entityStoreRegistry.registerComponent(NPCEntityEventSupport.class, NPCEntityEventSupport::new); + this.playerEntityEventSupportComponentType = entityStoreRegistry.registerComponent(PlayerEntityEventSupport.class, PlayerEntityEventSupport::new); + this.stepComponentType = entityStoreRegistry.registerComponent(StepComponent.class, () -> { + throw new UnsupportedOperationException("Not implemented"); + }); + this.failedSpawnComponentType = entityStoreRegistry.registerComponent(FailedSpawnComponent.class, FailedSpawnComponent::new); + this.timersComponentType = entityStoreRegistry.registerComponent(Timers.class, () -> { + throw new UnsupportedOperationException("Not implemented"); + }); + this.stateEvaluatorComponentType = entityStoreRegistry.registerComponent(StateEvaluator.class, () -> { + throw new UnsupportedOperationException("Not implemented"); + }); + this.valueStoreComponentType = entityStoreRegistry.registerComponent(ValueStore.class, () -> { + throw new UnsupportedOperationException("Not implemented"); + }); + ComponentType npcComponentType = NPCEntity.getComponentType(); + entityStoreRegistry.registerSystem(new BlackboardSystems.InitSystem(this.blackboardResourceType)); + entityStoreRegistry.registerSystem(new BlackboardSystems.TickingSystem(this.blackboardResourceType)); + entityStoreRegistry.registerSystem(new BlackboardSystems.DamageBlockEventSystem()); + entityStoreRegistry.registerSystem(new BlackboardSystems.BreakBlockEventSystem()); + entityStoreRegistry.registerSystem(new CombatViewSystems.Ensure(this.combatDataComponentType)); + entityStoreRegistry.registerSystem(new CombatViewSystems.EntityRemoved(this.combatDataComponentType, this.combatDataPoolResourceType)); + entityStoreRegistry.registerSystem(new CombatViewSystems.Ticking(this.combatDataComponentType, this.combatDataPoolResourceType)); + entityStoreRegistry.registerSystem(new NPCSystems.ModelChangeSystem()); + entityStoreRegistry.registerSystem(new RoleBuilderSystem()); + entityStoreRegistry.registerSystem(new BalancingInitialisationSystem()); + entityStoreRegistry.registerSystem(new RoleSystems.RoleActivateSystem(npcComponentType)); + entityStoreRegistry.registerSystem(new PositionCacheSystems.RoleActivateSystem(npcComponentType, this.stateEvaluatorComponentType)); + entityStoreRegistry.registerSystem(new NPCInteractionSystems.AddSimulationManagerSystem(npcComponentType)); + entityStoreRegistry.registerSystem(new NPCInteractionSystems.TickHeldInteractionsSystem(npcComponentType)); + entityStoreRegistry.registerSystem(new FailedSpawnSystem()); + entityStoreRegistry.registerSystem(new NPCSystems.AddedSystem(npcComponentType)); + entityStoreRegistry.registerSystem(new NPCSystems.AddedFromExternalSystem(npcComponentType)); + entityStoreRegistry.registerSystem(new NPCSystems.AddedFromWorldGenSystem()); + entityStoreRegistry.registerSystem(new NPCSystems.AddSpawnEntityEffectSystem(npcComponentType)); + entityStoreRegistry.registerSystem(new RoleSystems.BehaviourTickSystem(npcComponentType, this.stepComponentType)); + entityStoreRegistry.registerSystem(new RoleSystems.PreBehaviourSupportTickSystem(npcComponentType)); + entityStoreRegistry.registerSystem(new StateEvaluatorSystem(this.stateEvaluatorComponentType, npcComponentType)); + entityStoreRegistry.registerSystem(new PositionCacheSystems.UpdateSystem(npcComponentType, this.npcSpatialResource)); + entityStoreRegistry.registerSystem(new NPCPreTickSystem(npcComponentType)); + Set> postBehaviourDependency = Set.of(new SystemDependency<>(Order.AFTER, RoleSystems.PostBehaviourSupportTickSystem.class)); + entityStoreRegistry.registerSystem(new AvoidanceSystem(npcComponentType)); + entityStoreRegistry.registerSystem(new SteeringSystem(npcComponentType)); + entityStoreRegistry.registerSystem(new RoleSystems.PostBehaviourSupportTickSystem(npcComponentType)); + entityStoreRegistry.registerSystem(new RoleSystems.RoleDebugSystem(npcComponentType, postBehaviourDependency)); + entityStoreRegistry.registerSystem(new TimerSystem(this.timersComponentType, postBehaviourDependency)); + entityStoreRegistry.registerSystem(new ComputeVelocitySystem(npcComponentType, EntityModule.get().getVelocityComponentType(), postBehaviourDependency)); + entityStoreRegistry.registerSystem( + new MovementStatesSystem(npcComponentType, EntityModule.get().getVelocityComponentType(), EntityModule.get().getMovementStatesComponentType()) + ); + entityStoreRegistry.registerSystem(new MessageSupportSystem.BeaconSystem(this.beaconSupportComponentType, postBehaviourDependency)); + entityStoreRegistry.registerSystem(new MessageSupportSystem.NPCBlockEventSystem(this.npcBlockEventSupportComponentType, postBehaviourDependency)); + entityStoreRegistry.registerSystem(new MessageSupportSystem.PlayerBlockEventSystem(this.playerBlockEventSupportComponentType, postBehaviourDependency)); + entityStoreRegistry.registerSystem(new MessageSupportSystem.NPCEntityEventSystem(this.npcEntityEventSupportComponentType, postBehaviourDependency)); + entityStoreRegistry.registerSystem(new MessageSupportSystem.PlayerEntityEventSystem(this.playerEntityEventSupportComponentType, postBehaviourDependency)); + entityStoreRegistry.registerSystem(new StepCleanupSystem(this.stepComponentType)); + entityStoreRegistry.registerSystem(new NewSpawnStartTickingSystem(this.newSpawnStartTickingQueueResourceType)); + entityStoreRegistry.registerSystem( + new RoleChangeSystem( + this.roleChangeQueueResourceType, + this.beaconSupportComponentType, + this.playerBlockEventSupportComponentType, + this.npcBlockEventSupportComponentType, + this.playerEntityEventSupportComponentType, + this.npcEntityEventSupportComponentType, + this.timersComponentType, + this.stateEvaluatorComponentType, + this.valueStoreComponentType + ) + ); + entityStoreRegistry.registerSystem(new NPCSpatialSystem(this.npcSpatialResource)); + entityStoreRegistry.registerSystem(new NPCDeathSystems.NPCKillsEntitySystem()); + entityStoreRegistry.registerSystem(new NPCDeathSystems.EntityViewSystem()); + entityStoreRegistry.registerSystem(new NPCDamageSystems.FilterDamageSystem()); + entityStoreRegistry.registerSystem(new NPCDamageSystems.DamageReceivedSystem()); + entityStoreRegistry.registerSystem(new NPCDamageSystems.DamageDealtSystem()); + entityStoreRegistry.registerSystem(new NPCDamageSystems.DamageReceivedEventViewSystem()); + entityStoreRegistry.registerSystem(new NPCDamageSystems.DropDeathItems()); + entityStoreRegistry.registerSystem(new NPCSystems.OnTeleportSystem()); + entityStoreRegistry.registerSystem(new NPCSystems.OnDeathSystem()); + entityStoreRegistry.registerSystem(new NPCSystems.LegacyWorldGenId()); + entityStoreRegistry.registerSystem(new NPCSystems.KillFeedKillerEventSystem()); + entityStoreRegistry.registerSystem(new NPCSystems.KillFeedDecedentEventSystem()); + entityStoreRegistry.registerSystem(new NPCSystems.PrefabPlaceEntityEventSystem()); + entityStoreRegistry.registerSystem(new NPCVelocityInstructionSystem()); + this.getEntityStoreRegistry().registerSystem(new NPCPlugin.NPCEntityRegenerateStatsSystem()); + } + + public void onSchemaGenerate(@Nonnull GenerateSchemaEvent event) { + Schema schema = this.builderManager.generateSchema(event.getContext()); + event.addSchema("NPCRole.json", schema); + event.addSchemaLink("NPCRole", List.of("NPC/Roles/*.json", "NPC/Roles/**/*.json"), null); + Schema.HytaleMetadata hytale = schema.getHytale(); + hytale.setPath("NPC/Roles"); + hytale.setExtension(".json"); + schema.setId("NPCRole.json"); + schema.setTitle("NPCRole"); + } + + @Override + protected void start() { + NPCPlugin.NPCConfig config = this.config.get(); + if (config.isGenerateDescriptors()) { + this.generateDescriptors(); + if (config.isGenerateDescriptorsFile()) { + this.saveDescriptors(); + } + } + } + + public ResourceType getBlackboardResourceType() { + return this.blackboardResourceType; + } + + public ResourceType getCombatDataPoolResourceType() { + return this.combatDataPoolResourceType; + } + + public ResourceType getRoleChangeQueueResourceType() { + return this.roleChangeQueueResourceType; + } + + public ResourceType getNewSpawnStartTickingQueueResourceType() { + return this.newSpawnStartTickingQueueResourceType; + } + + public ResourceType getSortBufferProviderResourceResourceType() { + return this.sortBufferProviderResourceResourceType; + } + + public ResourceType getAStarNodePoolProviderSimpleResourceType() { + return this.aStarNodePoolProviderSimpleResourceType; + } + + public ResourceType, EntityStore>> getNpcSpatialResource() { + return this.npcSpatialResource; + } + + public ComponentType getCombatDataComponentType() { + return this.combatDataComponentType; + } + + public ComponentType getNpcTestDataComponentType() { + return this.npcTestDataComponentType; + } + + public ComponentType getBeaconSupportComponentType() { + return this.beaconSupportComponentType; + } + + public ComponentType getNpcBlockEventSupportComponentType() { + return this.npcBlockEventSupportComponentType; + } + + public ComponentType getPlayerBlockEventSupportComponentType() { + return this.playerBlockEventSupportComponentType; + } + + public ComponentType getNpcEntityEventSupportComponentType() { + return this.npcEntityEventSupportComponentType; + } + + public ComponentType getPlayerEntityEventSupportComponentType() { + return this.playerEntityEventSupportComponentType; + } + + public ComponentType getStepComponentType() { + return this.stepComponentType; + } + + public ComponentType getFailedSpawnComponentType() { + return this.failedSpawnComponentType; + } + + public ComponentType getTimersComponentType() { + return this.timersComponentType; + } + + public ComponentType getStateEvaluatorComponentType() { + return this.stateEvaluatorComponentType; + } + + public ComponentType getValueStoreComponentType() { + return this.valueStoreComponentType; + } + + public void setupNPCLoading() { + this.builderManager.addCategory(FACTORY_CLASS_ROLE, Role.class); + this.builderManager.addCategory(FACTORY_CLASS_BODY_MOTION, BodyMotion.class); + this.builderManager.addCategory(FACTORY_CLASS_HEAD_MOTION, HeadMotion.class); + this.builderManager.addCategory(FACTORY_CLASS_ACTION, Action.class); + this.builderManager.addCategory(FACTORY_CLASS_SENSOR, Sensor.class); + this.builderManager.addCategory(FACTORY_CLASS_INSTRUCTION, Instruction.class); + this.builderManager.addCategory(FACTORY_CLASS_TRANSIENT_PATH, TransientPathDefinition.class); + this.builderManager.addCategory(FACTORY_CLASS_ACTION_LIST, ActionList.class); + this.registerCoreFactories(); + this.registerCoreComponentType("Nothing", BuilderBodyMotionNothing::new) + .registerCoreComponentType("Wander", BuilderBodyMotionWander::new) + .registerCoreComponentType("WanderInCircle", BuilderBodyMotionWanderInCircle::new) + .registerCoreComponentType("WanderInRect", BuilderBodyMotionWanderInRect::new) + .registerCoreComponentType("Timer", BuilderBodyMotionTimer::new) + .registerCoreComponentType("Sequence", BuilderBodyMotionSequence::new) + .registerCoreComponentType("Flee", BuilderBodyMotionMoveAway::new) + .registerCoreComponentType("Seek", BuilderBodyMotionFind::new) + .registerCoreComponentType("Leave", BuilderBodyMotionLeave::new) + .registerCoreComponentType("Path", BuilderBodyMotionPath::new) + .registerCoreComponentType("TakeOff", BuilderBodyMotionTakeOff::new) + .registerCoreComponentType("TestProbe", BuilderBodyMotionTestProbe::new) + .registerCoreComponentType("Teleport", BuilderBodyMotionTeleport::new) + .registerCoreComponentType("Land", BuilderBodyMotionLand::new) + .registerCoreComponentType("MatchLook", BuilderBodyMotionMatchLook::new) + .registerCoreComponentType("MaintainDistance", BuilderBodyMotionMaintainDistance::new) + .registerCoreComponentType("AimCharge", BuilderBodyMotionAimCharge::new); + this.registerCoreComponentType("Aim", BuilderHeadMotionAim::new) + .registerCoreComponentType("Watch", BuilderHeadMotionWatch::new) + .registerCoreComponentType("Observe", BuilderHeadMotionObserve::new) + .registerCoreComponentType("Sequence", BuilderHeadMotionSequence::new) + .registerCoreComponentType("Timer", BuilderHeadMotionTimer::new) + .registerCoreComponentType("Nothing", BuilderHeadMotionNothing::new); + this.registerCoreComponentType("Appearance", BuilderActionAppearance::new) + .registerCoreComponentType("Timeout", BuilderActionTimeout::new) + .registerCoreComponentType("Spawn", BuilderActionSpawn::new) + .registerCoreComponentType("Nothing", BuilderActionNothing::new) + .registerCoreComponentType("Attack", BuilderActionAttack::new) + .registerCoreComponentType("State", BuilderActionState::new) + .registerCoreComponentType("ReleaseTarget", BuilderActionReleaseTarget::new) + .registerCoreComponentType("SetMarkedTarget", BuilderActionSetMarkedTarget::new) + .registerCoreComponentType("Inventory", BuilderActionInventory::new) + .registerCoreComponentType("DisplayName", BuilderActionDisplayName::new) + .registerCoreComponentType("Sequence", BuilderActionSequence::new) + .registerCoreComponentType("Random", BuilderActionRandom::new) + .registerCoreComponentType("Beacon", BuilderActionBeacon::new) + .registerCoreComponentType("SetLeashPosition", BuilderActionSetLeashPosition::new) + .registerCoreComponentType("PlaySound", BuilderActionPlaySound::new) + .registerCoreComponentType("Despawn", BuilderActionDespawn::new) + .registerCoreComponentType("PlayAnimation", BuilderActionPlayAnimation::new) + .registerCoreComponentType("DelayDespawn", BuilderActionDelayDespawn::new) + .registerCoreComponentType("SpawnParticles", BuilderActionSpawnParticles::new) + .registerCoreComponentType("Crouch", BuilderActionCrouch::new) + .registerCoreComponentType("TimerStart", BuilderActionTimerStart::new) + .registerCoreComponentType("TimerContinue", BuilderActionTimerContinue::new) + .registerCoreComponentType("TimerPause", BuilderActionTimerPause::new) + .registerCoreComponentType("TimerModify", BuilderActionTimerModify::new) + .registerCoreComponentType("TimerStop", BuilderActionTimerStop::new) + .registerCoreComponentType("TimerRestart", BuilderActionTimerRestart::new) + .registerCoreComponentType("Test", BuilderActionTest::new) + .registerCoreComponentType("Log", BuilderActionLog::new) + .registerCoreComponentType("Role", BuilderActionRole::new) + .registerCoreComponentType("SetFlag", BuilderActionSetFlag::new) + .registerCoreComponentType("DropItem", BuilderActionDropItem::new) + .registerCoreComponentType("PickUpItem", BuilderActionPickUpItem::new) + .registerCoreComponentType("ResetInstructions", BuilderActionResetInstructions::new) + .registerCoreComponentType("ParentState", BuilderActionParentState::new) + .registerCoreComponentType("Notify", BuilderActionNotify::new) + .registerCoreComponentType("TriggerSpawners", BuilderActionTriggerSpawners::new) + .registerCoreComponentType("ResetBlockSensors", BuilderActionResetBlockSensors::new) + .registerCoreComponentType("MakePath", BuilderActionMakePath::new) + .registerCoreComponentType("OverrideAttitude", BuilderActionOverrideAttitude::new) + .registerCoreComponentType("SetInteractable", BuilderActionSetInteractable::new) + .registerCoreComponentType("LockOnInteractionTarget", BuilderActionLockOnInteractionTarget::new) + .registerCoreComponentType("StorePosition", BuilderActionStorePosition::new) + .registerCoreComponentType("SetBlockToPlace", BuilderActionSetBlockToPlace::new) + .registerCoreComponentType("PlaceBlock", BuilderActionPlaceBlock::new) + .registerCoreComponentType("RecomputePath", BuilderActionRecomputePath::new) + .registerCoreComponentType("IgnoreForAvoidance", BuilderActionIgnoreForAvoidance::new) + .registerCoreComponentType("ModelAttachment", BuilderActionModelAttachment::new) + .registerCoreComponentType("SetAlarm", BuilderActionSetAlarm::new) + .registerCoreComponentType("ToggleStateEvaluator", BuilderActionToggleStateEvaluator::new) + .registerCoreComponentType("OverrideAltitude", BuilderActionOverrideAltitude::new) + .registerCoreComponentType("ResetSearchRays", BuilderActionResetSearchRays::new) + .registerCoreComponentType("Die", BuilderActionDie::new) + .registerCoreComponentType("Remove", BuilderActionRemove::new) + .registerCoreComponentType("ApplyEntityEffect", BuilderActionApplyEntityEffect::new) + .registerCoreComponentType("ResetPath", BuilderActionResetPath::new) + .registerCoreComponentType("SetStat", BuilderActionSetStat::new); + this.registerCoreComponentType("Any", BuilderSensorAny::new) + .registerCoreComponentType("And", BuilderSensorAnd::new) + .registerCoreComponentType("Or", BuilderSensorOr::new) + .registerCoreComponentType("Not", BuilderSensorNot::new) + .registerCoreComponentType("Player", BuilderSensorPlayer::new) + .registerCoreComponentType("Mob", BuilderSensorEntity::new) + .registerCoreComponentType("State", BuilderSensorState::new) + .registerCoreComponentType("InAir", BuilderSensorInAir::new) + .registerCoreComponentType("OnGround", BuilderSensorOnGround::new) + .registerCoreComponentType("Eval", BuilderSensorEval::new) + .registerCoreComponentType("Damage", BuilderSensorDamage::new) + .registerCoreComponentType("IsBackingAway", BuilderSensorIsBackingAway::new) + .registerCoreComponentType("Kill", BuilderSensorKill::new) + .registerCoreComponentType("Beacon", BuilderSensorBeacon::new) + .registerCoreComponentType("MotionController", BuilderSensorMotionController::new) + .registerCoreComponentType("Leash", BuilderSensorLeash::new) + .registerCoreComponentType("Time", BuilderSensorTime::new) + .registerCoreComponentType("Count", BuilderSensorCount::new) + .registerCoreComponentType("Target", BuilderSensorTarget::new) + .registerCoreComponentType("Timer", BuilderSensorTimer::new) + .registerCoreComponentType("Switch", BuilderSensorSwitch::new) + .registerCoreComponentType("Light", BuilderSensorLight::new) + .registerCoreComponentType("Age", BuilderSensorAge::new) + .registerCoreComponentType("Flag", BuilderSensorFlag::new) + .registerCoreComponentType("DroppedItem", BuilderSensorDroppedItem::new) + .registerCoreComponentType("Path", BuilderSensorPath::new) + .registerCoreComponentType("Weather", BuilderSensorWeather::new) + .registerCoreComponentType("Block", BuilderSensorBlock::new) + .registerCoreComponentType("BlockChange", BuilderSensorBlockChange::new) + .registerCoreComponentType("EntityEvent", BuilderSensorEntityEvent::new) + .registerCoreComponentType("Random", BuilderSensorRandom::new) + .registerCoreComponentType("CanInteract", BuilderSensorCanInteract::new) + .registerCoreComponentType("HasInteracted", BuilderSensorHasInteracted::new) + .registerCoreComponentType("ReadPosition", BuilderSensorReadPosition::new) + .registerCoreComponentType("Animation", BuilderSensorAnimation::new) + .registerCoreComponentType("CanPlaceBlock", BuilderSensorCanPlace::new) + .registerCoreComponentType("Nav", BuilderSensorNav::new) + .registerCoreComponentType("InWater", BuilderSensorInWater::new) + .registerCoreComponentType("IsBusy", BuilderSensorIsBusy::new) + .registerCoreComponentType("InteractionContext", BuilderSensorInteractionContext::new) + .registerCoreComponentType("Alarm", BuilderSensorAlarm::new) + .registerCoreComponentType("AdjustPosition", BuilderSensorAdjustPosition::new) + .registerCoreComponentType("SearchRay", BuilderSensorSearchRay::new) + .registerCoreComponentType("BlockType", BuilderSensorBlockType::new) + .registerCoreComponentType("Self", BuilderSensorSelf::new) + .registerCoreComponentType("ValueProviderWrapper", BuilderSensorValueProviderWrapper::new); + this.registerCoreComponentType("Attitude", BuilderEntityFilterAttitude::new) + .registerCoreComponentType("LineOfSight", BuilderEntityFilterLineOfSight::new) + .registerCoreComponentType("HeightDifference", BuilderEntityFilterHeightDifference::new) + .registerCoreComponentType("ViewSector", BuilderEntityFilterViewSector::new) + .registerCoreComponentType("Combat", BuilderEntityFilterCombat::new) + .registerCoreComponentType("ItemInHand", BuilderEntityFilterItemInHand::new) + .registerCoreComponentType("NPCGroup", BuilderEntityFilterNPCGroup::new) + .registerCoreComponentType("MovementState", BuilderEntityFilterMovementState::new) + .registerCoreComponentType("SpotsMe", BuilderEntityFilterSpotsMe::new) + .registerCoreComponentType("StandingOnBlock", BuilderEntityFilterStandingOnBlock::new) + .registerCoreComponentType("Stat", BuilderEntityFilterStat::new) + .registerCoreComponentType("Inventory", BuilderEntityFilterInventory::new) + .registerCoreComponentType("Not", BuilderEntityFilterNot::new) + .registerCoreComponentType("And", BuilderEntityFilterAnd::new) + .registerCoreComponentType("Or", BuilderEntityFilterOr::new) + .registerCoreComponentType("Altitude", BuilderEntityFilterAltitude::new) + .registerCoreComponentType("InsideBlock", BuilderEntityFilterInsideBlock::new); + this.registerCoreComponentType("Attitude", BuilderSensorEntityPrioritiserAttitude::new); + NPCPlugin.NPCConfig config = this.config.get(); + this.autoReload = config.isAutoReload(); + this.validateBuilder = config.isValidateBuilder(); + this.maxBlackboardBlockCountPerType = config.getMaxBlackboardBlockType(); + this.logFailingTestErrors = config.isLogFailingTestErrors(); + this.presetCoverageTestNPCs = config.getPresetCoverageTestNPCs(); + } + + public String[] getPresetCoverageTestNPCs() { + return this.presetCoverageTestNPCs; + } + + @Nullable + public Pair, INonPlayerCharacter> spawnNPC( + @Nonnull Store store, @Nonnull String npcType, @Nullable String groupType, @Nonnull Vector3d position, @Nonnull Vector3f rotation + ) { + int roleIndex = this.getIndex(npcType); + if (roleIndex < 0) { + return null; + } else { + Pair, NPCEntity> npcPair = this.spawnEntity(store, roleIndex, position, rotation, null, null); + FlockAsset flockDefinition = groupType != null ? FlockAsset.getAssetMap().getAsset(groupType) : null; + if (npcPair != null) { + Ref npcRef = npcPair.first(); + NPCEntity npcComponent = npcPair.second(); + FlockPlugin.trySpawnFlock(npcRef, npcComponent, store, roleIndex, position, rotation, flockDefinition, null); + return Pair.of(npcPair.first(), npcPair.second()); + } else { + return null; + } + } + } + + public static void reloadNPCsWithRole(int roleIndex) { + Universe.get() + .getWorlds() + .forEach( + (s, world) -> world.execute(() -> world.getEntityStore().getStore().forEachChunk(NPCEntity.getComponentType(), (archetypeChunk, commandBuffer) -> { + for (int index = 0; index < archetypeChunk.size(); index++) { + NPCEntity npc = archetypeChunk.getComponent(index, NPCEntity.getComponentType()); + if (npc.getRoleIndex() == roleIndex && !npc.getRole().isRoleChangeRequested()) { + RoleChangeSystem.requestRoleChange(archetypeChunk.getReferenceTo(index), npc.getRole(), roleIndex, true, world.getEntityStore().getStore()); + } + } + })) + ); + } + + protected void onNPCGroupsLoaded(LoadedAssetsEvent> event) { + this.putNPCGroups(); + } + + protected void onNPCGroupsRemoved(RemovedAssetsEvent> event) { + this.putNPCGroups(); + } + + protected void onAttitudeGroupsLoaded(@Nonnull LoadedAssetsEvent> event) { + if (this.attitudeMap == null) { + this.putAttitudeGroups(); + } else { + Map loadedAssets = event.getLoadedAssets(); + IndexedLookupTableAssetMap assets = AttitudeGroup.getAssetMap(); + int attitudeGroupCount = this.attitudeMap.getAttitudeGroupCount(); + + for (String id : loadedAssets.keySet()) { + int index = assets.getIndex(id); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + id); + } + + if (index >= attitudeGroupCount) { + this.putAttitudeGroups(); + return; + } + } + + loadedAssets.forEach((idx, group) -> { + int indexx = assets.getIndex(idx); + if (indexx == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + idx); + } else { + this.attitudeMap.updateAttitudeGroup(indexx, group); + } + }); + } + } + + protected void onAttitudeGroupsRemoved(RemovedAssetsEvent> event) { + this.putAttitudeGroups(); + } + + protected void onItemAttitudeGroupsLoaded(@Nonnull LoadedAssetsEvent> event) { + if (this.itemAttitudeMap == null) { + this.putItemAttitudeGroups(); + } else { + Map loadedAssets = event.getLoadedAssets(); + IndexedLookupTableAssetMap assets = ItemAttitudeGroup.getAssetMap(); + int attitudeGroupCount = this.itemAttitudeMap.getAttitudeGroupCount(); + + for (String id : loadedAssets.keySet()) { + int index = assets.getIndex(id); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + id); + } + + if (index >= attitudeGroupCount) { + this.putItemAttitudeGroups(); + return; + } + } + + loadedAssets.forEach((idx, group) -> { + int indexx = assets.getIndex(idx); + if (indexx == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + idx); + } else { + this.itemAttitudeMap.updateAttitudeGroup(indexx, group); + } + }); + } + } + + protected void onItemAttitudeGroupsRemoved(RemovedAssetsEvent> event) { + this.putItemAttitudeGroups(); + } + + private void putItemAttitudeGroups() { + ItemAttitudeMap.Builder builder = new ItemAttitudeMap.Builder(); + builder.addAttitudeGroups(ItemAttitudeGroup.getAssetMap().getAssetMap()); + this.itemAttitudeMap = builder.build(); + } + + protected void onPathChange(WorldPathChangedEvent event) { + this.pathChangeRevision.getAndIncrement(); + } + + public int getPathChangeRevision() { + return this.pathChangeRevision.get(); + } + + protected void onNPCsLoaded(AllNPCsLoadedEvent event) { + this.putNPCGroups(); + } + + private void putNPCGroups() { + IndexedLookupTableAssetMap indexedAssetMap = NPCGroup.getAssetMap(); + Object2IntOpenHashMap tagSetIndexMap = new Object2IntOpenHashMap<>(); + indexedAssetMap.getAssetMap().forEach((name, group) -> { + int index = indexedAssetMap.getIndex(name); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + name); + } else { + tagSetIndexMap.put(name, index); + } + }); + TagSetPlugin.get(NPCGroup.class).putAssetSets(indexedAssetMap.getAssetMap(), tagSetIndexMap, this.builderManager.getNameToIndexMap()); + this.putAttitudeGroups(); + } + + private void putAttitudeGroups() { + AttitudeMap.Builder builder = new AttitudeMap.Builder(); + builder.addAttitudeGroups(AttitudeGroup.getAssetMap().getAssetMap()); + this.attitudeMap = builder.build(); + } + + @Nullable + public String getName(int builderIndex) { + return this.builderManager.lookupName(builderIndex); + } + + public int getIndex(String builderName) { + return this.builderManager.getIndex(builderName); + } + + @Nullable + public Builder tryGetCachedValidRole(int roleIndex) { + return this.builderManager.tryGetCachedValidRole(roleIndex); + } + + @Nullable + public BuilderInfo getBuilderInfo(Builder builder) { + return this.builderManager.getBuilderInfo(builder); + } + + public List getRoleTemplateNames(boolean spawnableOnly) { + return this.builderManager + .collectMatchingBuilders( + new ObjectArrayList<>(), + entry -> entry.getBuilder().category() == Role.class && (!spawnableOnly || entry.getBuilder().isSpawnable()), + (builderInfo, objects) -> objects.add(builderInfo.getKeyName()) + ); + } + + public boolean hasRoleName(String roleName) { + return this.getRoleBuilderInfo(this.getIndex(roleName)) != null; + } + + public void validateSpawnableRole(String roleName) { + BuilderInfo builder = this.getRoleBuilderInfo(this.getIndex(roleName)); + if (builder == null) { + throw new SkipSentryException(new IllegalArgumentException(roleName + " does not exist as a role!")); + } else if (!builder.getBuilder().isSpawnable()) { + throw new SkipSentryException(new IllegalArgumentException(roleName + " is an abstract role type and cannot be spawned directly!")); + } + } + + @Nullable + public BuilderInfo getRoleBuilderInfo(int roleIndex) { + BuilderInfo builderInfo = this.builderManager.tryGetBuilderInfo(roleIndex); + return builderInfo != null && builderInfo.getBuilder().category() == Role.class ? builderInfo : null; + } + + public void setBuilderInvalid(int builderIndex) { + BuilderInfo builderInfo = this.builderManager.tryGetBuilderInfo(builderIndex); + if (builderInfo != null) { + builderInfo.setNeedsReload(); + } + } + + public AttitudeMap getAttitudeMap() { + return this.attitudeMap; + } + + public ItemAttitudeMap getItemAttitudeMap() { + return this.itemAttitudeMap; + } + + public boolean testAndValidateRole(@Nullable BuilderInfo builderInfo) { + return builderInfo != null + && builderInfo.getBuilder() != null + && builderInfo.getBuilder().category() == Role.class + && this.builderManager.validateBuilder(builderInfo); + } + + public void forceValidation(int builderIndex) { + this.builderManager.forceValidation(builderIndex); + } + + @Nullable + public Pair, NPCEntity> spawnEntity( + @Nonnull Store store, + int roleIndex, + @Nonnull Vector3d position, + Vector3f rotation, + Model spawnModel, + TriConsumer, Store> postSpawn + ) { + return this.spawnEntity(store, roleIndex, position, rotation, spawnModel, null, postSpawn); + } + + @Nullable + public Pair, NPCEntity> spawnEntity( + @Nonnull Store store, + int roleIndex, + @Nonnull Vector3d position, + @Nullable Vector3f rotation, + @Nullable Model spawnModel, + @Nullable TriConsumer, Store> preAddToWorld, + @Nullable TriConsumer, Store> postSpawn + ) { + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + NPCEntity npcComponent = new NPCEntity(); + npcComponent.setSpawnInstant(worldTimeResource.getGameTime()); + if (rotation == null) { + rotation = NULL_ROTATION; + } + + npcComponent.saveLeashInformation(position, rotation); + String roleName = this.getName(roleIndex); + if (roleName == null) { + get().getLogger().at(Level.WARNING).log("Unable to spawn entity with invalid role index: %s!", roleIndex); + return null; + } else { + npcComponent.setRoleName(roleName); + npcComponent.setRoleIndex(roleIndex); + Holder holder = EntityStore.REGISTRY.newHolder(); + holder.addComponent(NPCEntity.getComponentType(), npcComponent); + holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(position, rotation)); + holder.addComponent(HeadRotation.getComponentType(), new HeadRotation(rotation)); + DisplayNameComponent displayNameComponent = new DisplayNameComponent(Message.raw(roleName)); + holder.addComponent(DisplayNameComponent.getComponentType(), displayNameComponent); + holder.ensureComponent(UUIDComponent.getComponentType()); + if (spawnModel != null) { + npcComponent.setInitialModelScale(spawnModel.getScale()); + holder.addComponent(ModelComponent.getComponentType(), new ModelComponent(spawnModel)); + holder.addComponent(PersistentModel.getComponentType(), new PersistentModel(spawnModel.toReference())); + } + + if (preAddToWorld != null) { + preAddToWorld.accept(npcComponent, holder, store); + } + + Ref ref = store.addEntity(holder, AddReason.SPAWN); + if (ref == null) { + get().getLogger().at(Level.WARNING).log("Unable to handle non-spawned entity: %s!", this.getName(roleIndex)); + return null; + } else { + if (postSpawn != null) { + postSpawn.accept(npcComponent, ref, store); + } + + return Pair.of(ref, npcComponent); + } + } + } + + @Nonnull + public BuilderInfo prepareRoleBuilderInfo(int roleIndex) { + try { + BuilderInfo builderInfo = this.builderManager.getCachedBuilderInfo(roleIndex, Role.class); + if (this.validateBuilder) { + if (!builderInfo.isValidated()) { + this.builderManager.validateBuilder(builderInfo); + } + + if (!builderInfo.isValid()) { + throw new SkipSentryException(new IllegalStateException("Builder " + builderInfo.getKeyName() + " failed validation!")); + } + } + + return builderInfo; + } catch (RuntimeException var4) { + throw new SkipSentryException( + new RuntimeException(String.format("Cannot use role template '%s' (%s): %s", this.getName(roleIndex), roleIndex, var4.getMessage()), var4) + ); + } + } + + @Nonnull + public static Role buildRole(@Nonnull Builder roleBuilder, @Nonnull BuilderInfo builderInfo, @Nonnull BuilderSupport builderSupport, int roleIndex) { + Role role; + try { + StdScope scope = roleBuilder.getBuilderParameters().createScope(); + builderSupport.setScope(scope); + builderSupport.setGlobalScope(scope); + role = roleBuilder.build(builderSupport); + role.postRoleBuilt(builderSupport); + } catch (Throwable var6) { + builderInfo.setNeedsReload(); + throw new SkipSentryException(var6); + } + + role.setRoleIndex(roleIndex, builderInfo.getKeyName()); + return role; + } + + protected void onModelsChanged(@Nonnull LoadedAssetsEvent> event) { + Map loadedModelAssets = event.getLoadedAssets(); + Universe.get() + .getWorlds() + .values() + .forEach( + world -> world.execute( + () -> { + Store store = world.getEntityStore().getStore(); + store.forEachEntityParallel( + NPCEntity.getComponentType(), + (index, archetypeChunk, commandBuffer) -> { + ModelComponent entityModelComponent = archetypeChunk.getComponent(index, ModelComponent.getComponentType()); + if (entityModelComponent != null) { + Model oldModel = entityModelComponent.getModel(); + ModelAsset newModelAsset = loadedModelAssets.get(oldModel.getModelAssetId()); + if (newModelAsset != null) { + Ref entityReference = archetypeChunk.getReferenceTo(index); + commandBuffer.putComponent( + entityReference, + ModelComponent.getComponentType(), + new ModelComponent(Model.createScaledModel(newModelAsset, oldModel.getScale(), oldModel.getRandomAttachmentIds())) + ); + } + } + } + ); + } + ) + ); + } + + protected void generateDescriptors() { + this.getLogger().at(Level.INFO).log("===== Generating descriptors for NPC!"); + this.builderDescriptors = this.builderManager.generateDescriptors(); + } + + protected void saveDescriptors() { + this.getLogger().at(Level.INFO).log("===== Saving descriptors for NPC!"); + Path path = Path.of("npc_descriptors.json"); + BuilderManager.saveDescriptors(this.builderDescriptors, path); + this.getLogger().at(Level.INFO).log("Saved NPC descriptors to: %s", path); + } + + public BuilderManager getBuilderManager() { + return this.builderManager; + } + + public int getMaxBlackboardBlockCountPerType() { + return this.maxBlackboardBlockCountPerType; + } + + public boolean isLogFailingTestErrors() { + return this.logFailingTestErrors; + } + + public boolean startRoleBenchmark(double seconds, @Nonnull Consumer> onFinished) { + this.benchmarkLock.lock(); + + label37: { + boolean var4; + try { + if (!this.isBenchmarking()) { + this.roleTickDistribution = new Int2ObjectOpenHashMap<>(); + this.roleTickDistributionAll = new TimeDistributionRecorder(0.01, 1.0E-5); + this.roleTickDistribution.put(-1, this.roleTickDistributionAll); + break label37; + } + + var4 = false; + } finally { + this.benchmarkLock.unlock(); + } + + return var4; + } + + new CompletableFuture().completeOnTimeout(null, Math.round(seconds * 1000.0), TimeUnit.MILLISECONDS).thenRunAsync(() -> { + Int2ObjectMap distribution = this.roleTickDistribution; + this.benchmarkLock.lock(); + + try { + this.roleTickDistribution = null; + this.roleTickDistributionAll = null; + } finally { + this.benchmarkLock.unlock(); + } + + onFinished.accept(distribution); + }); + return true; + } + + public void collectRoleTick(int roleIndex, long nanos) { + if (this.benchmarkLock.tryLock()) { + try { + if (this.roleTickDistribution != null) { + this.roleTickDistribution.computeIfAbsent(roleIndex, i -> new TimeDistributionRecorder(0.01, 1.0E-5)).recordNanos(nanos); + this.roleTickDistributionAll.recordNanos(nanos); + } + } finally { + this.benchmarkLock.unlock(); + } + } + } + + public boolean isBenchmarkingRole() { + return this.roleTickDistribution != null; + } + + public boolean startSensorSupportBenchmark(double seconds, @Nonnull Consumer> onFinished) { + this.benchmarkLock.lock(); + + label37: { + boolean var4; + try { + if (!this.isBenchmarking()) { + this.roleSensorSupportDistribution = new Int2ObjectOpenHashMap<>(); + this.roleSensorSupportDistributionAll = new SensorSupportBenchmark(); + this.roleSensorSupportDistribution.put(-1, this.roleSensorSupportDistributionAll); + break label37; + } + + var4 = false; + } finally { + this.benchmarkLock.unlock(); + } + + return var4; + } + + new CompletableFuture().completeOnTimeout(null, Math.round(seconds * 1000.0), TimeUnit.MILLISECONDS).thenRunAsync(() -> { + Int2ObjectMap distribution = this.roleSensorSupportDistribution; + this.benchmarkLock.lock(); + + try { + this.roleSensorSupportDistribution = null; + this.roleSensorSupportDistributionAll = null; + } finally { + this.benchmarkLock.unlock(); + } + + onFinished.accept(distribution); + }); + return true; + } + + public boolean isBenchmarkingSensorSupport() { + return this.roleSensorSupportDistributionAll != null; + } + + protected boolean isBenchmarking() { + return this.isBenchmarkingRole() || this.isBenchmarkingSensorSupport(); + } + + public void collectSensorSupportPlayerList( + int roleIndex, long getNanos, double maxPlayerDistanceSorted, double maxPlayerDistance, double maxPlayerDistanceAvoidance, int numPlayers + ) { + if (this.benchmarkLock.tryLock()) { + try { + if (this.roleSensorSupportDistribution != null) { + this.roleSensorSupportDistribution + .computeIfAbsent(roleIndex, i -> new SensorSupportBenchmark()) + .collectPlayerList(getNanos, maxPlayerDistanceSorted, maxPlayerDistance, maxPlayerDistanceAvoidance, numPlayers); + this.roleSensorSupportDistributionAll + .collectPlayerList(getNanos, maxPlayerDistanceSorted, maxPlayerDistance, maxPlayerDistanceAvoidance, numPlayers); + } + } finally { + this.benchmarkLock.unlock(); + } + } + } + + public void collectSensorSupportEntityList( + int roleIndex, long getNanos, double maxEntityDistanceSorted, double maxEntityDistance, double maxEntityDistanceAvoidance, int numEntities + ) { + if (this.benchmarkLock.tryLock()) { + try { + if (this.roleSensorSupportDistribution != null) { + this.roleSensorSupportDistribution + .computeIfAbsent(roleIndex, i -> new SensorSupportBenchmark()) + .collectEntityList(getNanos, maxEntityDistanceSorted, maxEntityDistance, maxEntityDistanceAvoidance, numEntities); + this.roleSensorSupportDistributionAll + .collectEntityList(getNanos, maxEntityDistanceSorted, maxEntityDistance, maxEntityDistanceAvoidance, numEntities); + } + } finally { + this.benchmarkLock.unlock(); + } + } + } + + public void collectSensorSupportLosTest(int roleIndex, boolean cacheHit, long time) { + if (this.isBenchmarkingSensorSupport() && this.benchmarkLock.tryLock()) { + try { + if (this.roleSensorSupportDistribution != null) { + this.roleSensorSupportDistribution.computeIfAbsent(roleIndex, i -> new SensorSupportBenchmark()).collectLosTest(cacheHit, time); + this.roleSensorSupportDistributionAll.collectLosTest(cacheHit, time); + } + } finally { + this.benchmarkLock.unlock(); + } + } + } + + public void collectSensorSupportInverseLosTest(int roleIndex, boolean cacheHit) { + if (this.isBenchmarkingSensorSupport() && this.benchmarkLock.tryLock()) { + try { + if (this.roleSensorSupportDistribution != null) { + this.roleSensorSupportDistribution.computeIfAbsent(roleIndex, i -> new SensorSupportBenchmark()).collectInverseLosTest(cacheHit); + this.roleSensorSupportDistributionAll.collectInverseLosTest(cacheHit); + } + } finally { + this.benchmarkLock.unlock(); + } + } + } + + public void collectSensorSupportFriendlyBlockingTest(int roleIndex, boolean cacheHit) { + if (this.isBenchmarkingSensorSupport() && this.benchmarkLock.tryLock()) { + try { + if (this.roleSensorSupportDistribution != null) { + this.roleSensorSupportDistribution.computeIfAbsent(roleIndex, i -> new SensorSupportBenchmark()).collectFriendlyBlockingTest(cacheHit); + this.roleSensorSupportDistributionAll.collectFriendlyBlockingTest(cacheHit); + } + } finally { + this.benchmarkLock.unlock(); + } + } + } + + public void collectSensorSupportTickDone(int roleIndex) { + if (this.isBenchmarkingSensorSupport() && this.benchmarkLock.tryLock()) { + try { + if (this.roleSensorSupportDistribution != null) { + this.roleSensorSupportDistribution.computeIfAbsent(roleIndex, i -> new SensorSupportBenchmark()).tickDone(); + this.roleSensorSupportDistributionAll.tickDone(); + } + } finally { + this.benchmarkLock.unlock(); + } + } + } + + @Nonnull + public NPCPlugin registerCoreComponentType(String name, @Nonnull Supplier> builder) { + BuilderFactory factory = this.builderManager.getFactory(builder.get().category()); + factory.add(name, builder); + return this; + } + + public void setRoleBuilderNeedsReload(Builder builder) { + BuilderInfo builderInfo = this.getBuilderInfo(builder); + Objects.requireNonNull(builderInfo, "Have builder but can't get builderInfo for it"); + builderInfo.setNeedsReload(); + } + + protected void registerCoreFactories() { + BuilderFactory roleFactory = new BuilderFactory<>(Role.class, "Type"); + roleFactory.add("Generic", BuilderRole::new); + roleFactory.add("Abstract", BuilderRoleAbstract::new); + roleFactory.add("Variant", BuilderRoleVariant::new); + this.builderManager.registerFactory(roleFactory); + BuilderFactory motionControllerFactory = new BuilderFactory<>(MotionController.class, "Type"); + motionControllerFactory.add("Walk", BuilderMotionControllerWalk::new); + motionControllerFactory.add("Fly", BuilderMotionControllerFly::new); + motionControllerFactory.add("Dive", BuilderMotionControllerDive::new); + this.builderManager.registerFactory(motionControllerFactory); + BuilderFactory> motionControllerMapFactory = new BuilderFactory<>( + BuilderMotionControllerMapUtil.CLASS_REFERENCE, "Type", BuilderMotionControllerMap::new + ); + this.builderManager.registerFactory(motionControllerMapFactory); + BuilderFactory actionListFactory = new BuilderFactory<>(ActionList.class, "Type", BuilderActionList::new); + this.builderManager.registerFactory(actionListFactory); + BuilderFactory instructionFactory = new BuilderFactory<>(Instruction.class, "Type", BuilderInstruction::new); + instructionFactory.add("Random", BuilderInstructionRandomized::new); + instructionFactory.add("Reference", BuilderInstructionReference::new); + this.builderManager.registerFactory(instructionFactory); + BuilderFactory transientPathFactory = new BuilderFactory<>( + TransientPathDefinition.class, "Type", BuilderTransientPathDefinition::new + ); + this.builderManager.registerFactory(transientPathFactory); + BuilderFactory relativeWaypointFactory = new BuilderFactory<>( + RelativeWaypointDefinition.class, "Type", BuilderRelativeWaypointDefinition::new + ); + this.builderManager.registerFactory(relativeWaypointFactory); + BuilderFactory weightedActionFactory = new BuilderFactory<>(WeightedAction.class, "Type", BuilderWeightedAction::new); + this.builderManager.registerFactory(weightedActionFactory); + BuilderFactory valueToParameterMappingFactory = new BuilderFactory<>( + BuilderValueToParameterMapping.ValueToParameterMapping.class, "Type", BuilderValueToParameterMapping::new + ); + this.builderManager.registerFactory(valueToParameterMappingFactory); + StateTransitionController.registerFactories(this.builderManager); + this.builderManager.registerFactory(new BuilderFactory<>(BodyMotion.class, "Type")); + this.builderManager.registerFactory(new BuilderFactory<>(HeadMotion.class, "Type")); + this.builderManager.registerFactory(new BuilderFactory<>(Action.class, "Type")); + this.builderManager.registerFactory(new BuilderFactory<>(Sensor.class, "Type")); + this.builderManager.registerFactory(new BuilderFactory<>(IEntityFilter.class, "Type")); + this.builderManager.registerFactory(new BuilderFactory<>(ISensorEntityPrioritiser.class, "Type")); + this.builderManager.registerFactory(new BuilderFactory<>(ISensorEntityCollector.class, "Type")); + } + + protected static void onBalanceAssetsChanged(@Nonnull LoadedAssetsEvent> event) { + Map loadedAssets = event.getLoadedAssets(); + Universe.get() + .getWorlds() + .forEach( + (name, world) -> world.execute( + () -> world.getEntityStore().getStore().forEachChunk(NPCEntity.getComponentType(), (archetypeChunk, commandBuffer) -> { + for (int index = 0; index < archetypeChunk.size(); index++) { + NPCEntity npcComponent = archetypeChunk.getComponent(index, NPCEntity.getComponentType()); + + assert npcComponent != null; + + if (loadedAssets.containsKey(npcComponent.getRole().getBalanceAsset())) { + Ref ref = archetypeChunk.getReferenceTo(index); + RoleChangeSystem.requestRoleChange(ref, npcComponent.getRole(), npcComponent.getRoleIndex(), false, world.getEntityStore().getStore()); + } + } + }) + ) + ); + } + + protected static void onBalanceAssetsRemoved(@Nonnull RemovedAssetsEvent> event) { + Set removedAssets = event.getRemovedAssets(); + Universe.get() + .getWorlds() + .forEach( + (name, world) -> world.execute( + () -> world.getEntityStore().getStore().forEachChunk(NPCEntity.getComponentType(), (archetypeChunk, commandBuffer) -> { + for (int index = 0; index < archetypeChunk.size(); index++) { + NPCEntity npcComponent = archetypeChunk.getComponent(index, NPCEntity.getComponentType()); + + assert npcComponent != null; + + if (removedAssets.contains(npcComponent.getRole().getBalanceAsset())) { + Ref ref = archetypeChunk.getReferenceTo(index); + commandBuffer.removeEntity(ref, RemoveReason.REMOVE); + } + } + }) + ) + ); + } + + public static class NPCConfig { + public static final BuilderCodec CODEC = BuilderCodec.builder(NPCPlugin.NPCConfig.class, NPCPlugin.NPCConfig::new) + .append(new KeyedCodec<>("Descriptors", Codec.BOOLEAN), (o, i) -> o.generateDescriptors = i, o -> o.generateDescriptors) + .add() + .append(new KeyedCodec<>("DescriptorsFile", Codec.BOOLEAN), (o, i) -> o.generateDescriptorsFile = i, o -> o.generateDescriptorsFile) + .add() + .append(new KeyedCodec<>("AutoReload", Codec.BOOLEAN), (o, i) -> o.autoReload = i, o -> o.autoReload) + .add() + .append(new KeyedCodec<>("ValidateBuilders", Codec.BOOLEAN), (o, i) -> o.validateBuilder = i, o -> o.validateBuilder) + .add() + .append(new KeyedCodec<>("MaxBlackboardBlockType", Codec.INTEGER), (o, i) -> o.maxBlackboardBlockType = i, o -> o.maxBlackboardBlockType) + .add() + .append(new KeyedCodec<>("LogFailingTestErrors", Codec.BOOLEAN), (o, i) -> o.logFailingTestErrors = i, o -> o.logFailingTestErrors) + .add() + .append(new KeyedCodec<>("PresetCoverageTestNPCs", Codec.STRING_ARRAY), (o, i) -> o.presetCoverageTestNPCs = i, o -> o.presetCoverageTestNPCs) + .add() + .build(); + private boolean generateDescriptors; + private boolean generateDescriptorsFile; + private boolean autoReload = true; + private boolean validateBuilder = true; + private int maxBlackboardBlockType = 20; + private boolean logFailingTestErrors; + private String[] presetCoverageTestNPCs = new String[]{ + "Test_Bird", + "Test_Block_Sensor", + "Test_Attack_Bow", + "Test_Combat_Sensor_Sheep", + "Test_Bow_Charge", + "Test_OffHand_Swap", + "Test_Patrol_Path", + "Test_Flock_Mixed#4", + "Test_Group_Sheep", + "Test_Attack_Flying_Ranged", + "Test_Interaction_Complete_Task", + "Test_Hotbar_Weapon_Swap", + "Test_Inventory_Contents", + "Test_Dive_Flee", + "Test_Jumping", + "Test_Walk_Leave", + "Test_Walk_Seek", + "Test_Watch", + "Test_Chained_Path", + "Test_Spawn_Action", + "Test_State_Evaluator_Toggle", + "Test_State_Evaluator_Sleep", + "Test_Alarm", + "Test_Timer_Repeating", + "Test_Action_Model_Attachment", + "Test_Animation_State_Change", + "Test_Block_Change", + "Test_Crouch", + "Test_Drop_Item", + "Test_Entity_Damage_Event", + "Test_Hover_Parrot", + "Test_In_Water", + "Test_Light_Sensor", + "Test_Model_Change", + "Test_Particle_Emotions", + "Test_Place_Blocks", + "Test_Position_Adjustment_Wrapper", + "Test_Probe", + "Test_Sensor_Age", + "Test_Sensor_DroppedItem", + "Test_Shoot_At_Block", + "Test_Species_Attitude", + "Test_Standing_On_Block_Sensor", + "Test_Teleport", + "Test_Throw_NPC", + "Test_Trigger_Spawners", + "Test_Weather_Sensor", + "Test_Bird_Land" + }; + + public NPCConfig() { + } + + public boolean isGenerateDescriptors() { + return this.generateDescriptors; + } + + public boolean isGenerateDescriptorsFile() { + return this.generateDescriptorsFile; + } + + public boolean isAutoReload() { + return this.autoReload; + } + + public boolean isValidateBuilder() { + return this.validateBuilder; + } + + public int getMaxBlackboardBlockType() { + return this.maxBlackboardBlockType; + } + + public boolean isLogFailingTestErrors() { + return this.logFailingTestErrors; + } + + public String[] getPresetCoverageTestNPCs() { + return this.presetCoverageTestNPCs; + } + } + + public static class NPCEntityRegenerateStatsSystem extends EntityStatsSystems.Regenerate { + public NPCEntityRegenerateStatsSystem() { + super(EntityStatMap.getComponentType(), NPCEntity.getComponentType()); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/animations/NPCAnimationSlot.java b/src/com/hypixel/hytale/server/npc/animations/NPCAnimationSlot.java new file mode 100644 index 0000000..2b3e465 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/animations/NPCAnimationSlot.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.npc.animations; + +import com.hypixel.hytale.protocol.AnimationSlot; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +@Deprecated +public enum NPCAnimationSlot implements Supplier { + Status(AnimationSlot.Status), + Action(AnimationSlot.Action), + Face(AnimationSlot.Face); + + public static final NPCAnimationSlot[] VALUES = values(); + private final AnimationSlot mappedSlot; + + private NPCAnimationSlot(AnimationSlot mappedSlot) { + this.mappedSlot = mappedSlot; + } + + @Nonnull + public String get() { + return this.name(); + } + + public AnimationSlot getMappedSlot() { + return this.mappedSlot; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/Builder.java b/src/com/hypixel/hytale/server/npc/asset/builder/Builder.java new file mode 100644 index 0000000..27b4603 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/Builder.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.codec.schema.NamedSchema; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.SchemaConvertable; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface Builder extends BuilderContext, SchemaConvertable, NamedSchema { + @Nullable + T build(BuilderSupport var1); + + boolean validate(String var1, NPCLoadTimeValidationHelper var2, ExecutionContext var3, Scope var4, List var5); + + void readConfig(BuilderContext var1, JsonElement var2, BuilderManager var3, BuilderParameters var4, BuilderValidationHelper var5); + + void ignoreAttribute(String var1); + + Class category(); + + void setTypeName(String var1); + + String getTypeName(); + + void setLabel(String var1); + + @Nonnull + @Override + Schema toSchema(@Nonnull SchemaContext var1); + + BuilderDescriptor getDescriptor(String var1, String var2, BuilderManager var3); + + default boolean isDeprecated() { + return this.getBuilderDescriptorState() == BuilderDescriptorState.Deprecated; + } + + @Nullable + BuilderDescriptorState getBuilderDescriptorState(); + + IntSet getDependencies(); + + default boolean hasDynamicDependencies() { + return false; + } + + default void addDynamicDependency(int builderIndex) { + throw new IllegalStateException("Builder: Adding dynamic dependencies is not supported"); + } + + @Nullable + default IntSet getDynamicDependencies() { + return null; + } + + default void clearDynamicDependencies() { + } + + BuilderParameters getBuilderParameters(); + + FeatureEvaluatorHelper getEvaluatorHelper(); + + StateMappingHelper getStateMappingHelper(); + + InstructionContextHelper getInstructionContextHelper(); + + boolean canRequireFeature(); + + void validateReferencedProvidedFeatures(BuilderManager var1, ExecutionContext var2); + + boolean excludeFromRegularBuilding(); + + boolean isEnabled(ExecutionContext var1); + + default boolean isSpawnable() { + return false; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderAttributeDescriptor.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderAttributeDescriptor.java new file mode 100644 index 0000000..a02818f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderAttributeDescriptor.java @@ -0,0 +1,193 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.hypixel.hytale.server.npc.asset.builder.validators.Validator; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderAttributeDescriptor { + private final String name; + private final String type; + private BuilderAttributeDescriptor.RequirementType required; + private boolean computable; + private final BuilderDescriptorState state; + private final String shortDescription; + private final String longDescription; + @Nullable + private String defaultValue; + @Nullable + private String domain; + private int minSize; + private int maxSize; + @Nullable + private Validator validator; + @Nullable + private Map flagDescriptions; + + public BuilderAttributeDescriptor(String name, String type, BuilderDescriptorState state, String shortDescription, String longDescription) { + this.name = name; + this.type = type; + this.state = state; + this.shortDescription = shortDescription; + this.longDescription = longDescription; + this.required = BuilderAttributeDescriptor.RequirementType.OPTIONAL; + this.computable = false; + this.defaultValue = null; + this.domain = null; + this.validator = null; + this.flagDescriptions = null; + this.minSize = -1; + this.maxSize = -1; + } + + @Nonnull + public BuilderAttributeDescriptor required() { + this.required = BuilderAttributeDescriptor.RequirementType.REQUIRED; + this.defaultValue = null; + return this; + } + + @Nonnull + public BuilderAttributeDescriptor requiredIfNotOverridden() { + this.required = BuilderAttributeDescriptor.RequirementType.REQUIRED_IF_NOT_OVERRIDDEN; + this.defaultValue = null; + return this; + } + + @Nonnull + public BuilderAttributeDescriptor optional(String defaultValue) { + this.required = BuilderAttributeDescriptor.RequirementType.OPTIONAL; + this.defaultValue = defaultValue; + return this; + } + + @Nonnull + public BuilderAttributeDescriptor optional(double[] defaultValue) { + this.required = BuilderAttributeDescriptor.RequirementType.OPTIONAL; + this.defaultValue = Arrays.toString(defaultValue); + return this; + } + + @Nonnull + public BuilderAttributeDescriptor optional(int[] defaultValue) { + this.required = BuilderAttributeDescriptor.RequirementType.OPTIONAL; + this.defaultValue = Arrays.toString(defaultValue); + return this; + } + + @Nonnull + public BuilderAttributeDescriptor optional(String[] defaultValue) { + this.required = BuilderAttributeDescriptor.RequirementType.OPTIONAL; + this.defaultValue = Arrays.toString((Object[])defaultValue); + return this; + } + + @Nonnull + public BuilderAttributeDescriptor optional(boolean[] defaultValue) { + this.required = BuilderAttributeDescriptor.RequirementType.OPTIONAL; + this.defaultValue = Arrays.toString(defaultValue); + return this; + } + + @Nonnull + public BuilderAttributeDescriptor computable() { + this.computable = true; + return this; + } + + @Nonnull + public > BuilderAttributeDescriptor setBasicEnum(@Nonnull Class clazz) { + E[] enumConstants = (E[])clazz.getEnumConstants(); + this.domain = BuilderBase.getDomain(enumConstants); + HashMap result = new HashMap<>(); + + for (E E : enumConstants) { + result.put(E.toString(), E.toString()); + } + + this.flagDescriptions = result; + return this; + } + + @Nonnull + public & Supplier> BuilderAttributeDescriptor setEnum(@Nonnull Class clazz) { + E[] enumConstants = (E[])clazz.getEnumConstants(); + this.domain = BuilderBase.getDomain(enumConstants); + HashMap result = new HashMap<>(); + + for (E E : enumConstants) { + result.put(E.toString(), (String)E.get()); + } + + this.flagDescriptions = result; + return this; + } + + @Nonnull + public BuilderAttributeDescriptor domain(String domain) { + this.domain = domain; + return this; + } + + @Nonnull + public BuilderAttributeDescriptor validator(Validator validator) { + this.validator = validator; + return this; + } + + @Nonnull + public BuilderAttributeDescriptor length(int size) { + return this.length(size, size); + } + + @Nonnull + public BuilderAttributeDescriptor length(int minSize, int maxSize) { + this.minSize = minSize; + this.maxSize = maxSize; + return this; + } + + @Nonnull + @Override + public String toString() { + return "BuilderAttributeDescriptor{name='" + + this.name + + "', type='" + + this.type + + "', required=" + + this.required + + ", computable=" + + this.computable + + ", state=" + + this.state + + ", shortDescription='" + + this.shortDescription + + "', longDescription='" + + this.longDescription + + "', defaultValue='" + + this.defaultValue + + "', domain='" + + this.domain + + "', minSize=" + + this.minSize + + ", maxSize=" + + this.maxSize + + ", validator=" + + this.validator + + ", flagDescriptions=" + + this.flagDescriptions + + "}"; + } + + private static enum RequirementType { + REQUIRED, + OPTIONAL, + REQUIRED_IF_NOT_OVERRIDDEN; + + private RequirementType() { + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderBase.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderBase.java new file mode 100644 index 0000000..73d75eb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderBase.java @@ -0,0 +1,5073 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.BooleanSchema; +import com.hypixel.hytale.codec.schema.config.IntegerSchema; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.function.consumer.BooleanConsumer; +import com.hypixel.hytale.function.consumer.FloatConsumer; +import com.hypixel.hytale.function.consumer.TriConsumer; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpressionDynamic; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumSetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.FloatHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.IntHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.TemporalArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.ValueHolder; +import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.ParameterProviderEvaluator; +import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.ParameterType; +import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.ProviderEvaluator; +import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.UnconditionalFeatureProviderEvaluator; +import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.UnconditionalParameterProviderEvaluator; +import com.hypixel.hytale.server.npc.asset.builder.util.StringListHelpers; +import com.hypixel.hytale.server.npc.asset.builder.validators.AnyBooleanValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.AnyPresentValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.ArrayValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.ArraysOneSetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.AtMostOneBooleanValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.AttributeRelationValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.BooleanImplicationValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.ComponentOnlyValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleArrayValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.EnumArrayValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.ExistsIfParameterSetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.InstructionContextValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.IntArrayValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.IntValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.NoDuplicatesValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.OneOrNonePresentValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.OnePresentValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.RelationalOperator; +import com.hypixel.hytale.server.npc.asset.builder.validators.RequiredFeatureValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.RequiresFeatureIfEnumValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.RequiresFeatureIfValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.RequiresOneOfFeaturesValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StateStringValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringArrayNoEmptyStringsValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringArrayValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringsAtMostOneValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringsNotEmptyValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringsOneSetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.TemporalArrayValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.ValidateAssetIfEnumIsValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.ValidateIfEnumIsValidator; +import com.hypixel.hytale.server.npc.decisionmaker.core.Evaluator; +import com.hypixel.hytale.server.npc.role.RoleDebugFlags; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import com.hypixel.hytale.server.npc.valuestore.ValueStore; +import com.hypixel.hytale.server.npc.valuestore.ValueStoreValidator; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.Map.Entry; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.DoubleConsumer; +import java.util.function.Function; +import java.util.function.IntConsumer; +import java.util.function.Supplier; +import java.util.function.ToIntFunction; +import java.util.logging.Level; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class BuilderBase implements Builder { + private static final Pattern PATTERN = Pattern.compile("\\s*,\\s*"); + protected String fileName; + @Nullable + protected Set queriedKeys = new HashSet<>(); + protected boolean useDefaultsOnly; + protected String label; + protected String typeName; + protected FeatureEvaluatorHelper evaluatorHelper; + protected InternalReferenceResolver internalReferenceResolver; + protected StateMappingHelper stateHelper; + protected InstructionContextHelper instructionContextHelper; + protected ExtraInfo extraInfo; + protected List> evaluators; + protected BuilderValidationHelper validationHelper; + @Nullable + protected BuilderDescriptor builderDescriptor; + protected BuilderParameters builderParameters; + protected BuilderManager builderManager; + protected BuilderContext owner; + @Nullable + protected List readErrors; + private List dynamicHolders; + private List valueStoreUsages; + @Nullable + protected ObjectSchema builderSchema; + protected Schema builderSchemaRaw; + @Nullable + protected SchemaContext builderSchemaContext; + + public BuilderBase() { + } + + @Override + public void setTypeName(String name) { + this.typeName = name; + } + + @Override + public String getTypeName() { + return this.typeName; + } + + @Override + public String getLabel() { + return this.label; + } + + @Override + public void setLabel(String label) { + this.label = label; + } + + @Override + public FeatureEvaluatorHelper getEvaluatorHelper() { + return this.evaluatorHelper; + } + + @Override + public StateMappingHelper getStateMappingHelper() { + return this.stateHelper; + } + + @Override + public InstructionContextHelper getInstructionContextHelper() { + return this.instructionContextHelper; + } + + @Override + public void validateReferencedProvidedFeatures(BuilderManager manager, ExecutionContext context) { + if (this.evaluatorHelper != null) { + this.evaluatorHelper.validateProviderReferences(manager, context); + } + } + + @Override + public boolean canRequireFeature() { + return false; + } + + @Override + public boolean excludeFromRegularBuilding() { + return false; + } + + @Override + public final void readConfig( + BuilderContext owner, + @Nonnull JsonElement data, + BuilderManager builderManager, + BuilderParameters builderParameters, + BuilderValidationHelper builderValidationHelper + ) { + this.preReadConfig(owner, builderManager, builderParameters, builderValidationHelper); + this.readCommonConfig(data); + this.readConfig(data); + this.postReadConfig(data); + } + + private void preReadConfig( + BuilderContext owner, BuilderManager builderManager, BuilderParameters builderParameters, @Nullable BuilderValidationHelper builderValidationHelper + ) { + this.owner = owner; + this.useDefaultsOnly = false; + this.builderParameters = builderParameters; + this.builderManager = builderManager; + this.queriedKeys.add("Comment"); + this.queriedKeys.add("$Title"); + this.queriedKeys.add("$Comment"); + this.queriedKeys.add("$Author"); + this.queriedKeys.add("$TODO"); + this.queriedKeys.add("$Position"); + this.queriedKeys.add("$FloatingFunctionNodes"); + this.queriedKeys.add("$Groups"); + this.queriedKeys.add("$WorkspaceID"); + this.queriedKeys.add("$NodeEditorMetadata"); + this.queriedKeys.add("$NodeId"); + if (builderValidationHelper != null) { + this.validationHelper = builderValidationHelper; + this.fileName = builderValidationHelper.getName(); + this.evaluatorHelper = builderValidationHelper.getFeatureEvaluatorHelper(); + this.internalReferenceResolver = builderValidationHelper.getInternalReferenceResolver(); + this.stateHelper = builderValidationHelper.getStateMappingHelper(); + this.instructionContextHelper = builderValidationHelper.getInstructionContextHelper(); + this.extraInfo = builderValidationHelper.getExtraInfo(); + this.evaluators = builderValidationHelper.getEvaluators(); + this.readErrors = builderValidationHelper.getReadErrors(); + } + } + + private void addQueryKey(String name) { + if (!this.queriedKeys.add(name)) { + throw new IllegalArgumentException(String.valueOf(name)); + } + } + + @Override + public BuilderContext getOwner() { + return this.owner; + } + + @Override + public void ignoreAttribute(String name) { + this.queriedKeys.add(name); + } + + private void postReadConfig(@Nonnull JsonElement data) { + if (this.builderDescriptor == null && data.isJsonObject()) { + this.queriedKeys.add("Type"); + + for (Entry entry : data.getAsJsonObject().entrySet()) { + String key = entry.getKey(); + if (!this.queriedKeys.contains(key)) { + String string = data.toString(); + NPCPlugin.get() + .getLogger() + .at(Level.WARNING) + .log( + "Unknown JSON attribute '%s' found in %s: %s (JSON: %s)", + key, + this.getBreadCrumbs(), + this.builderParameters.getFileName(), + string.length() > 60 ? string.substring(60) + "..." : string + ); + } + } + } + + this.queriedKeys = null; + this.readErrors = null; + } + + public Builder readCommonConfig(JsonElement data) { + return this; + } + + public Builder readConfig(JsonElement data) { + return this; + } + + public BuilderManager getBuilderManager() { + return this.builderManager; + } + + @Override + public BuilderParameters getBuilderParameters() { + return this.builderParameters; + } + + protected JsonObject expectJsonObject(@Nonnull JsonElement data, String name) { + if (data.isJsonObject()) { + return data.getAsJsonObject(); + } else { + this.checkForUnexpectedComputeObject(data, name); + throw new IllegalStateException( + "Expected object when looking for parameter \"" + name + "\" but found '" + data + "' in context " + this.getBreadCrumbs() + ); + } + } + + protected JsonArray expectJsonArray(@Nonnull JsonElement data, String name) { + if (data.isJsonArray()) { + return data.getAsJsonArray(); + } else { + this.checkForUnexpectedComputeObject(data, name); + throw new IllegalStateException( + "Expected array when looking for parameter \"" + name + "\" but found '" + data + "' in context " + this.getBreadCrumbs() + ); + } + } + + @Nullable + protected String expectString(@Nonnull JsonElement data, String name) { + if (data.isJsonPrimitive() && data.getAsJsonPrimitive().isString()) { + return data.getAsJsonPrimitive().getAsString(); + } else if (data.isJsonNull()) { + return null; + } else { + this.checkForUnexpectedComputeObject(data, name); + throw new IllegalStateException( + "Expected string when looking for parameter \"" + name + "\" but found '" + data + "' in context " + this.getBreadCrumbs() + ); + } + } + + protected double expectDouble(@Nonnull JsonElement data, String name) { + if (data.isJsonPrimitive() && data.getAsJsonPrimitive().isNumber()) { + try { + return data.getAsJsonPrimitive().getAsDouble(); + } catch (NumberFormatException var4) { + throw new IllegalStateException( + "Invalid number when looking for parameter \"" + name + "\", found '" + data + "' in context " + this.getBreadCrumbs() + ); + } + } else { + this.checkForUnexpectedComputeObject(data, name); + throw new IllegalStateException( + "Expected number when looking for parameter \"" + name + "\" but found '" + data + "' in context " + this.getBreadCrumbs() + ); + } + } + + protected int expectInteger(@Nonnull JsonElement data, String name) { + if (data.isJsonPrimitive() && data.getAsJsonPrimitive().isNumber()) { + try { + return data.getAsJsonPrimitive().getAsInt(); + } catch (NumberFormatException var4) { + throw new IllegalStateException( + "Invalid integer number when looking for parameter \"" + name + "\", found '" + data + "' in context " + this.getBreadCrumbs() + ); + } + } else { + this.checkForUnexpectedComputeObject(data, name); + throw new IllegalStateException( + "Expected integer number when looking for parameter \"" + name + "\" but found '" + data + "' in context " + this.getBreadCrumbs() + ); + } + } + + protected boolean expectBoolean(@Nonnull JsonElement data, String name) { + if (data.isJsonPrimitive() && data.getAsJsonPrimitive().isBoolean()) { + return data.getAsJsonPrimitive().getAsBoolean(); + } else { + this.checkForUnexpectedComputeObject(data, name); + throw new IllegalStateException( + "Expected boolean value when looking for parameter \"" + name + "\" but found '" + data + "' in context " + this.getBreadCrumbs() + ); + } + } + + protected int[] expectIntArray(@Nonnull JsonElement data, String name, int minSize, int maxSize) { + JsonArray jsonArray = this.expectJsonArray(data, name, minSize, maxSize); + int count = jsonArray.size(); + int[] array = new int[count]; + + for (int i = 0; i < count; i++) { + array[i] = this.expectInteger(jsonArray.get(i), name); + } + + return array; + } + + protected int[] expectIntArray(@Nonnull JsonElement data, String name, int size) { + return this.expectIntArray(data, name, size, size); + } + + protected double[] expectDoubleArray(@Nonnull JsonElement data, String name, int minSize, int maxSize) { + JsonArray jsonArray = this.expectJsonArray(data, name, minSize, maxSize); + int count = jsonArray.size(); + double[] array = new double[count]; + + for (int i = 0; i < count; i++) { + array[i] = this.expectDouble(jsonArray.get(i), name); + } + + return array; + } + + protected double[] expectDoubleArray(@Nonnull JsonElement data, String name, int size) { + return this.expectDoubleArray(data, name, size, size); + } + + @Nonnull + protected JsonArray expectJsonArray(@Nonnull JsonElement data, String name, int minSize, int maxSize) { + JsonArray jsonArray = this.expectJsonArray(data, name); + int count = jsonArray.size(); + if (count >= minSize && count <= maxSize) { + return jsonArray; + } else if (maxSize == minSize) { + throw new IllegalStateException( + "Expected array with " + + maxSize + + " elements when looking for parameter \"" + + name + + "\" but found " + + count + + " elements in context " + + this.getBreadCrumbs() + ); + } else { + throw new IllegalStateException( + "Expected array with " + + minSize + + " to " + + maxSize + + " elements when looking for parameter \"" + + name + + "\" but found " + + count + + " elements in context " + + this.getBreadCrumbs() + ); + } + } + + protected void checkForUnexpectedComputeObject(@Nonnull JsonElement data, String name) { + if (data.isJsonObject() && data.getAsJsonObject().has("Compute")) { + throw new IllegalStateException( + "Parameter \"" + + name + + "\" of " + + this.category().getSimpleName() + + " " + + this.getTypeName() + + " is not computable (yet) in context " + + this.getBreadCrumbs() + ); + } + } + + @Nonnull + protected JsonElement getRequiredJsonElement(@Nonnull JsonElement data, String name, boolean addKey) { + if (addKey) { + this.addQueryKey(name); + } + + JsonElement element = this.expectJsonObject(data, name).get(name); + if (element == null) { + throw new IllegalStateException("Parameter \"" + name + "\" is missing in context " + this.getBreadCrumbs()); + } else { + return element; + } + } + + @Nonnull + protected JsonElement getRequiredJsonElement(@Nonnull JsonElement data, String name) { + return this.getRequiredJsonElement(data, name, true); + } + + @Nullable + protected JsonElement getRequiredJsonElementIfNotOverridden(@Nonnull JsonElement data, String name, @Nonnull ParameterType type, boolean addKey) { + if (addKey) { + this.addQueryKey(name); + } + + JsonElement element = this.expectJsonObject(data, name).get(name); + if (element != null) { + return element; + } else if (this.evaluatorHelper.belongsToFeatureRequiringComponent()) { + this.evaluatorHelper.addComponentRequirementValidator((helper, executionContext) -> this.validateOverriddenParameter(name, type, helper)); + return null; + } else if (this.hasOverriddenParameter(name, type, this.evaluatorHelper)) { + return null; + } else if (this.evaluatorHelper.requiresProviderReferenceEvaluation()) { + this.evaluatorHelper.addProviderReferenceValidator((manager, context) -> { + this.resolveFeatureProviderReverences(manager); + this.validateOverriddenParameter(name, type, this.evaluatorHelper); + }); + return null; + } else { + throw new IllegalStateException( + String.format( + "Parameter %s is missing and either not provided by a sensor, or provided with the wrong parameter type (expected %s) in context %s", + name, + type.get(), + this.getBreadCrumbs() + ) + ); + } + } + + @Nullable + protected JsonElement getRequiredJsonElementIfNotOverridden(@Nonnull JsonElement data, String name, @Nonnull ParameterType type) { + return this.getRequiredJsonElementIfNotOverridden(data, name, type, true); + } + + @Nullable + protected JsonElement getOptionalJsonElement(@Nonnull JsonElement data, String name, boolean addKey) { + JsonElement result = null; + if (!this.useDefaultsOnly) { + if (addKey) { + this.addQueryKey(name); + } + + result = this.expectJsonObject(data, name).get(name); + } + + return result; + } + + @Nullable + protected JsonElement getOptionalJsonElement(@Nonnull JsonElement data, String name) { + return this.getOptionalJsonElement(data, name, true); + } + + public void requireString( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + StringValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new StringSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor.addAttribute(name, String.class.getSimpleName(), state, shortDescription, longDescription).required().validator(validator); + } else { + try { + this.validateAndSet(this.expectString(this.getRequiredJsonElement(data, name), name), validator, setter, name); + } catch (Exception var9) { + this.addError(var9); + } + } + } + + public boolean getString( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + String defaultValue, + StringValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new StringSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, String.class.getSimpleName(), state, shortDescription, longDescription) + .optional(defaultValue) + .validator(validator); + return false; + } else { + try { + JsonElement element = this.getOptionalJsonElement(data, name); + boolean haveValue = element != null; + if (haveValue) { + defaultValue = this.expectString(element, name); + } + + this.validateAndSet(defaultValue, validator, setter, name); + return haveValue; + } catch (Exception var11) { + this.addError(var11); + return false; + } + } + } + + public void requireString( + @Nonnull JsonElement data, + String name, + @Nonnull StringHolder stringHolder, + StringValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new StringSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + stringHolder.setName(name); + this.builderDescriptor + .addAttribute(name, String.class.getSimpleName(), state, shortDescription, longDescription) + .required() + .computable() + .validator(validator); + } else { + Objects.requireNonNull(stringHolder, "stringHolder is null"); + + try { + stringHolder.readJSON(this.getRequiredJsonElement(data, name), validator, name, this.builderParameters); + this.trackDynamicHolder(stringHolder); + } catch (Exception var9) { + this.addError(var9); + } + } + } + + public boolean requireStringIfNotOverridden( + @Nonnull JsonElement data, + String name, + @Nonnull StringHolder stringHolder, + StringValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new StringSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + stringHolder.setName(name); + this.builderDescriptor + .addAttribute(name, String.class.getSimpleName(), state, shortDescription, longDescription) + .requiredIfNotOverridden() + .computable() + .validator(validator); + return false; + } else { + Objects.requireNonNull(stringHolder, "stringHolder is null"); + + try { + JsonElement element = this.getRequiredJsonElementIfNotOverridden(data, name, ParameterType.STRING); + boolean valueProvided = element != null; + stringHolder.readJSON(element, null, !valueProvided ? null : validator, name, this.builderParameters); + this.trackDynamicHolder(stringHolder); + return valueProvided; + } catch (Exception var10) { + this.addError(var10); + return false; + } + } + } + + public boolean getString( + @Nonnull JsonElement data, + String name, + @Nonnull StringHolder stringHolder, + String defaultValue, + StringValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new StringSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + stringHolder.setName(name); + this.builderDescriptor + .addAttribute(name, String.class.getSimpleName(), state, shortDescription, longDescription) + .optional(defaultValue) + .computable() + .validator(validator); + return false; + } else { + Objects.requireNonNull(stringHolder, "stringHolder is null"); + + try { + JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name); + stringHolder.readJSON(optionalJsonElement, defaultValue, validator, name, this.builderParameters); + this.trackDynamicHolder(stringHolder); + return optionalJsonElement != null; + } catch (Exception var10) { + this.addError(var10); + return false; + } + } + } + + private void validateAndSet(String str, @Nullable StringValidator validator, @Nonnull Consumer setter, String name) { + if (validator != null && !validator.test(str)) { + throw new IllegalStateException(validator.errorMessage(str, name) + " in " + this.getBreadCrumbs()); + } else { + setter.accept(str); + } + } + + @Nonnull + protected String[] nonNull(@Nullable String[] array) { + return array == null ? ArrayUtil.EMPTY_STRING_ARRAY : array; + } + + @Nonnull + public String[] expectStringArray(@Nonnull JsonElement data, @Nullable Function mapper, String name, boolean warning) { + if (mapper == null) { + mapper = Function.identity(); + } + + if (data.isJsonPrimitive() && data.getAsJsonPrimitive().isString()) { + if (warning) { + NPCPlugin.get() + .getLogger() + .at(Level.WARNING) + .log( + "Use of strings for lists is deprecated for JSON attribute '%s' (use []) in %s: %s", + name, + this.getBreadCrumbs(), + this.builderParameters.getFileName() + ); + } + + return StringListHelpers.splitToStringList(data.getAsJsonPrimitive().getAsString(), mapper).toArray(String[]::new); + } else if (!data.isJsonArray()) { + throw new IllegalStateException( + "Expected string or array when looking for parameter \"" + name + "\" but found '" + data + "' in context " + this.getBreadCrumbs() + ); + } else { + JsonArray array = data.getAsJsonArray(); + String[] result = new String[array.size()]; + + for (int i = 0; i < array.size(); i++) { + String s = mapper.apply(this.expectString(array.get(i), name).trim()); + if (s != null && !s.isEmpty()) { + result[i] = s; + } + } + + return result; + } + } + + @Nonnull + public String[] expectStringArray(@Nonnull JsonElement data, Function mapper, String name) { + return this.expectStringArray(data, mapper, name, true); + } + + public boolean getStringArray( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + Function mapper, + String[] defaultValue, + StringArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + ArraySchema a = new ArraySchema(); + a.setItem(new StringSchema()); + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "StringList", state, shortDescription, longDescription) + .optional(this.defaultArrayToString(defaultValue)) + .validator(validator); + return false; + } else { + try { + JsonElement element = this.getOptionalJsonElement(data, name); + boolean haveValue = element != null; + String[] array; + if (haveValue) { + array = this.expectStringArray(element, mapper, name); + } else { + array = defaultValue; + } + + this.validateAndSet(array, validator, setter, name); + return haveValue; + } catch (Exception var13) { + this.addError(var13); + return false; + } + } + } + + public void requireStringArray( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + Function mapper, + StringArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + ArraySchema a = new ArraySchema(); + a.setItem(new StringSchema()); + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor.addAttribute(name, "StringList", state, shortDescription, longDescription).required().validator(validator); + } else { + try { + JsonElement element = this.getRequiredJsonElement(data, name); + this.validateAndSet(this.expectStringArray(element, mapper, name), validator, setter, name); + } catch (Exception var11) { + this.addError(var11); + } + } + } + + public void requireStringArray( + @Nonnull JsonElement data, + String name, + @Nonnull StringArrayHolder holder, + int minLength, + int maxLength, + StringArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + ArraySchema a = new ArraySchema(); + a.setItem(new StringSchema()); + if (minLength != 0) { + a.setMinItems(minLength); + } + + if (maxLength != Integer.MAX_VALUE) { + a.setMaxItems(maxLength); + } + + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "Array", state, shortDescription, longDescription) + .domain("String") + .length(minLength, maxLength) + .validator(validator) + .computable() + .required(); + } else { + Objects.requireNonNull(holder, "string array holder is null"); + + try { + holder.readJSON(this.getRequiredJsonElement(data, name), minLength, maxLength, validator, name, this.builderParameters); + this.trackDynamicHolder(holder); + } catch (Exception var12) { + this.addError(var12); + } + } + } + + public void requireTemporalArray( + @Nonnull JsonElement data, + String name, + @Nonnull TemporalArrayHolder holder, + int minLength, + int maxLength, + TemporalArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + ArraySchema a = new ArraySchema(); + a.setItem(new StringSchema()); + if (minLength != 0) { + a.setMinItems(minLength); + } + + if (maxLength != Integer.MAX_VALUE) { + a.setMaxItems(maxLength); + } + + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "Array", state, shortDescription, longDescription) + .domain("TemporalAmount") + .length(minLength, maxLength) + .validator(validator) + .computable() + .required(); + } else { + Objects.requireNonNull(holder, "temporal array holder is null"); + + try { + holder.readJSON(this.getRequiredJsonElement(data, name), minLength, maxLength, validator, name, this.builderParameters); + this.trackDynamicHolder(holder); + } catch (Exception var12) { + this.addError(var12); + } + } + } + + public void requireTemporalRange( + @Nonnull JsonElement data, + String name, + @Nonnull TemporalArrayHolder holder, + TemporalArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + this.requireTemporalArray(data, name, holder, 2, 2, validator, state, shortDescription, longDescription); + } + + public boolean getStringArray( + @Nonnull JsonElement data, + String name, + @Nonnull StringArrayHolder holder, + String[] defaultValue, + int minLength, + int maxLength, + StringArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + ArraySchema a = new ArraySchema(); + a.setItem(new StringSchema()); + if (minLength != 0) { + a.setMinItems(minLength); + } + + if (maxLength != Integer.MAX_VALUE) { + a.setMaxItems(maxLength); + } + + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "Array", state, shortDescription, longDescription) + .domain("String") + .length(minLength, maxLength) + .validator(validator) + .computable() + .optional(defaultValue); + return false; + } else { + Objects.requireNonNull(holder, "string array holder is null"); + + try { + JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name); + holder.readJSON(optionalJsonElement, minLength, maxLength, defaultValue, validator, name, this.builderParameters); + this.trackDynamicHolder(holder); + return optionalJsonElement != null; + } catch (Exception var13) { + this.addError(var13); + return false; + } + } + } + + private void validateAndSet(String[] value, @Nullable StringArrayValidator validator, @Nonnull Consumer setter, String name) { + if (validator != null && !validator.test(value)) { + throw new IllegalStateException(validator.errorMessage(name, value) + " in " + this.getBreadCrumbs()); + } else { + setter.accept(value); + } + } + + private String defaultArrayToString(@Nullable String[] defaultValue) { + return defaultValue == null ? null : Arrays.toString((Object[])defaultValue); + } + + private boolean requireOrGetDictionary( + @Nonnull JsonElement data, + String name, + String domain, + @Nonnull BiConsumer setter, + boolean required, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addAttribute(name, "Dictionary", state, shortDescription, longDescription).required().domain(domain); + return false; + } else { + try { + JsonElement element = required ? this.getRequiredJsonElement(data, name) : this.getOptionalJsonElement(data, name); + boolean haveValue = element != null; + if (haveValue) { + JsonObject object = this.expectJsonObject(element, name); + object.entrySet() + .forEach(stringJsonElementEntry -> setter.accept((T)((String)stringJsonElementEntry.getKey()), stringJsonElementEntry.getValue())); + } + + return haveValue; + } catch (Exception var12) { + this.addError(var12); + return false; + } + } + } + + public void requireDictionary( + @Nonnull JsonElement data, + String name, + String domain, + @Nonnull BiConsumer setter, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + this.requireOrGetDictionary(data, name, domain, setter, true, state, shortDescription, longDescription); + } + + public boolean getDictionary( + @Nonnull JsonElement data, + String name, + String domain, + @Nonnull BiConsumer setter, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + return this.requireOrGetDictionary(data, name, domain, setter, false, state, shortDescription, longDescription); + } + + public void requireDouble( + @Nonnull JsonElement data, + String name, + @Nonnull DoubleConsumer setter, + DoubleValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor.addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription).required().validator(validator); + } else { + try { + this.validateAndSet(this.expectDouble(this.getRequiredJsonElement(data, name), name), validator, setter, name); + } catch (Exception var9) { + this.addError(var9); + } + } + } + + public boolean getDouble( + @Nonnull JsonElement data, + String name, + @Nonnull DoubleConsumer setter, + double defaultValue, + DoubleValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription) + .optional(Double.toString(defaultValue)) + .validator(validator); + return false; + } else { + try { + JsonElement element = this.getOptionalJsonElement(data, name); + boolean haveValue = element != null; + if (haveValue) { + defaultValue = this.expectDouble(element, name); + } + + this.validateAndSet(defaultValue, validator, setter, name); + return haveValue; + } catch (Exception var12) { + this.addError(var12); + return false; + } + } + } + + public void requireDouble( + @Nonnull JsonElement data, + String name, + @Nonnull DoubleHolder doubleHolder, + DoubleValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + doubleHolder.setName(name); + this.builderDescriptor + .addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription) + .required() + .computable() + .validator(validator); + } else { + Objects.requireNonNull(doubleHolder, "doubleHolder is null"); + + try { + doubleHolder.readJSON(this.getRequiredJsonElement(data, name), validator, name, this.builderParameters); + this.trackDynamicHolder(doubleHolder); + } catch (Exception var9) { + this.addError(var9); + } + } + } + + public boolean requireDoubleIfNotOverridden( + @Nonnull JsonElement data, + String name, + @Nonnull DoubleHolder doubleHolder, + DoubleValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + doubleHolder.setName(name); + this.builderDescriptor + .addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription) + .requiredIfNotOverridden() + .computable() + .validator(validator); + return false; + } else { + Objects.requireNonNull(doubleHolder, "doubleHolder is null"); + + try { + JsonElement element = this.getRequiredJsonElementIfNotOverridden(data, name, ParameterType.DOUBLE); + boolean valueProvided = element != null; + doubleHolder.readJSON(element, -Double.MAX_VALUE, !valueProvided ? null : validator, name, this.builderParameters); + this.trackDynamicHolder(doubleHolder); + return valueProvided; + } catch (Exception var10) { + this.addError(var10); + return false; + } + } + } + + public boolean getDouble( + @Nonnull JsonElement data, + String name, + @Nonnull DoubleHolder doubleHolder, + double defaultValue, + DoubleValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + doubleHolder.setName(name); + this.builderDescriptor + .addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription) + .optional(Double.toString(defaultValue)) + .computable() + .validator(validator); + return false; + } else { + Objects.requireNonNull(doubleHolder, "doubleHolder is null"); + + try { + JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name); + doubleHolder.readJSON(optionalJsonElement, defaultValue, validator, name, this.builderParameters); + this.trackDynamicHolder(doubleHolder); + return optionalJsonElement != null; + } catch (Exception var11) { + this.addError(var11); + return false; + } + } + } + + private void validateAndSet(double v, @Nullable DoubleValidator validator, @Nonnull DoubleConsumer setter, String name) { + if (validator != null && !validator.test(v)) { + throw new IllegalStateException(validator.errorMessage(v, name) + " in " + this.getBreadCrumbs()); + } else { + setter.accept(v); + } + } + + public void requireIntArray( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + int minLength, + int maxLength, + IntArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + ArraySchema a = new ArraySchema(new IntegerSchema()); + if (minLength != 0) { + a.setMinItems(minLength); + } + + if (maxLength != Integer.MAX_VALUE) { + a.setMaxItems(maxLength); + } + + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "Array", state, shortDescription, longDescription) + .domain("integer") + .length(minLength, maxLength) + .validator(validator) + .required(); + } else { + try { + this.validateAndSet(this.expectIntArray(this.getRequiredJsonElement(data, name), name, minLength, maxLength), validator, setter, name); + } catch (Exception var12) { + this.addError(var12); + } + } + } + + public boolean getIntArray( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + int[] defaultValue, + int minLength, + int maxLength, + IntArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + ArraySchema a = new ArraySchema(new IntegerSchema()); + if (minLength != 0) { + a.setMinItems(minLength); + } + + if (maxLength != Integer.MAX_VALUE) { + a.setMaxItems(maxLength); + } + + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "Array", state, shortDescription, longDescription) + .domain("integer") + .length(minLength, maxLength) + .validator(validator) + .optional(defaultValue); + return false; + } else { + try { + JsonElement element = this.getOptionalJsonElement(data, name); + boolean haveValue = element != null; + if (haveValue) { + defaultValue = this.expectIntArray(element, name, minLength, maxLength); + } + + this.validateAndSet(defaultValue, validator, setter, name); + return haveValue; + } catch (Exception var13) { + this.addError(var13); + return false; + } + } + } + + public void requireIntArray( + @Nonnull JsonElement data, + String name, + @Nonnull NumberArrayHolder holder, + int minLength, + int maxLength, + IntArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + ArraySchema a = new ArraySchema(new IntegerSchema()); + if (minLength != 0) { + a.setMinItems(minLength); + } + + if (maxLength != Integer.MAX_VALUE) { + a.setMaxItems(maxLength); + } + + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "Array", state, shortDescription, longDescription) + .domain("integer") + .length(minLength, maxLength) + .validator(validator) + .computable() + .required(); + } else { + Objects.requireNonNull(holder, "int array holder is null"); + + try { + holder.readJSON(this.getRequiredJsonElement(data, name), minLength, maxLength, validator, name, this.builderParameters); + this.trackDynamicHolder(holder); + } catch (Exception var12) { + this.addError(var12); + } + } + } + + public boolean getIntArray( + @Nonnull JsonElement data, + String name, + @Nonnull NumberArrayHolder holder, + int[] defaultValue, + int minLength, + int maxLength, + IntArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + ArraySchema a = new ArraySchema(new IntegerSchema()); + if (minLength != 0) { + a.setMinItems(minLength); + } + + if (maxLength != Integer.MAX_VALUE) { + a.setMaxItems(maxLength); + } + + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "Array", state, shortDescription, longDescription) + .domain("integer") + .length(minLength, maxLength) + .validator(validator) + .computable() + .optional(defaultValue); + return false; + } else { + Objects.requireNonNull(holder, "int array holder is null"); + + try { + JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name); + holder.readJSON(optionalJsonElement, minLength, maxLength, defaultValue, validator, name, this.builderParameters); + this.trackDynamicHolder(holder); + return optionalJsonElement != null; + } catch (Exception var13) { + this.addError(var13); + return false; + } + } + } + + public void requireIntRange( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + IntArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + this.requireIntArray(data, name, setter, 2, 2, validator, state, shortDescription, longDescription); + } + + public boolean getIntRange( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + int[] defaultValue, + IntArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + return this.getIntArray(data, name, setter, defaultValue, 2, 2, validator, state, shortDescription, longDescription); + } + + public void requireIntRange( + @Nonnull JsonElement data, + String name, + @Nonnull NumberArrayHolder holder, + IntArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + this.requireIntArray(data, name, holder, 2, 2, validator, state, shortDescription, longDescription); + } + + public boolean getIntRange( + @Nonnull JsonElement data, + String name, + @Nonnull NumberArrayHolder holder, + int[] defaultValue, + IntArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + return this.getIntArray(data, name, holder, defaultValue, 2, 2, validator, state, shortDescription, longDescription); + } + + private void validateAndSet(int[] v, @Nullable IntArrayValidator validator, @Nonnull Consumer setter, String name) { + if (validator != null && !validator.test(v)) { + throw new IllegalStateException(validator.errorMessage(v, name) + " in " + this.getBreadCrumbs()); + } else { + setter.accept(v); + } + } + + public void requireDoubleArray( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + int minLength, + int maxLength, + DoubleArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + ArraySchema a = new ArraySchema(new NumberSchema()); + if (minLength != 0) { + a.setMinItems(minLength); + } + + if (maxLength != Integer.MAX_VALUE) { + a.setMaxItems(maxLength); + } + + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "Array", state, shortDescription, longDescription) + .domain("Double") + .length(minLength, maxLength) + .validator(validator) + .required(); + } else { + try { + this.validateAndSet(this.expectDoubleArray(this.getRequiredJsonElement(data, name), name, minLength, maxLength), validator, setter, name); + } catch (Exception var12) { + this.addError(var12); + } + } + } + + public boolean getDoubleArray( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + double[] defaultValue, + int minLength, + int maxLength, + DoubleArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + ArraySchema a = new ArraySchema(new NumberSchema()); + if (minLength != 0) { + a.setMinItems(minLength); + } + + if (maxLength != Integer.MAX_VALUE) { + a.setMaxItems(maxLength); + } + + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "Array", state, shortDescription, longDescription) + .domain("Double") + .length(minLength, maxLength) + .validator(validator) + .optional(defaultValue); + return false; + } else { + try { + JsonElement element = this.getOptionalJsonElement(data, name); + boolean haveValue = element != null; + if (haveValue) { + defaultValue = this.expectDoubleArray(element, name, minLength, maxLength); + } + + this.validateAndSet(defaultValue, validator, setter, name); + return haveValue; + } catch (Exception var13) { + this.addError(var13); + return false; + } + } + } + + public void requireDoubleArray( + @Nonnull JsonElement data, + String name, + @Nonnull NumberArrayHolder holder, + int minLength, + int maxLength, + DoubleArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + ArraySchema a = new ArraySchema(new NumberSchema()); + if (minLength != 0) { + a.setMinItems(minLength); + } + + if (maxLength != Integer.MAX_VALUE) { + a.setMaxItems(maxLength); + } + + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "Array", state, shortDescription, longDescription) + .domain("Double") + .length(minLength, maxLength) + .validator(validator) + .computable() + .required(); + } else { + Objects.requireNonNull(holder, "double array holder is null"); + + try { + holder.readJSON(this.getRequiredJsonElement(data, name), minLength, maxLength, validator, name, this.builderParameters); + this.trackDynamicHolder(holder); + } catch (Exception var12) { + this.addError(var12); + } + } + } + + public boolean getDoubleArray( + @Nonnull JsonElement data, + String name, + @Nonnull NumberArrayHolder holder, + double[] defaultValue, + int minLength, + int maxLength, + DoubleArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + ArraySchema a = new ArraySchema(new NumberSchema()); + if (minLength != 0) { + a.setMinItems(minLength); + } + + if (maxLength != Integer.MAX_VALUE) { + a.setMaxItems(maxLength); + } + + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "Array", state, shortDescription, longDescription) + .domain("Double") + .length(minLength, maxLength) + .validator(validator) + .computable() + .optional(defaultValue); + return false; + } else { + Objects.requireNonNull(holder, "double array holder is null"); + + try { + JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name); + holder.readJSON(optionalJsonElement, minLength, maxLength, defaultValue, validator, name, this.builderParameters); + this.trackDynamicHolder(holder); + return optionalJsonElement != null; + } catch (Exception var13) { + this.addError(var13); + return false; + } + } + } + + public void requireDoubleRange( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + DoubleArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + this.requireDoubleArray(data, name, setter, 2, 2, validator, state, shortDescription, longDescription); + } + + public boolean getDoubleRange( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + double[] defaultValue, + DoubleArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + return this.getDoubleArray(data, name, setter, defaultValue, 2, 2, validator, state, shortDescription, longDescription); + } + + public void requireVector3d( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + DoubleArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + this.requireDoubleArray(data, name, setter, 3, 3, validator, state, shortDescription, longDescription); + } + + public boolean getVector3d( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + double[] defaultValue, + DoubleArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + return this.getDoubleArray(data, name, setter, defaultValue, 3, 3, validator, state, shortDescription, longDescription); + } + + public void requireDoubleRange( + @Nonnull JsonElement data, + String name, + @Nonnull NumberArrayHolder holder, + DoubleArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + this.requireDoubleArray(data, name, holder, 2, 2, validator, state, shortDescription, longDescription); + } + + public boolean getDoubleRange( + @Nonnull JsonElement data, + String name, + @Nonnull NumberArrayHolder holder, + double[] defaultValue, + DoubleArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + return this.getDoubleArray(data, name, holder, defaultValue, 2, 2, validator, state, shortDescription, longDescription); + } + + public void requireVector3d( + @Nonnull JsonElement data, + String name, + @Nonnull NumberArrayHolder holder, + DoubleArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + this.requireDoubleArray(data, name, holder, 3, 3, validator, state, shortDescription, longDescription); + } + + public boolean getVector3d( + @Nonnull JsonElement data, + String name, + @Nonnull NumberArrayHolder holder, + double[] defaultValue, + DoubleArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + return this.getDoubleArray(data, name, holder, defaultValue, 3, 3, validator, state, shortDescription, longDescription); + } + + private void validateAndSet(double[] v, @Nullable DoubleArrayValidator validator, @Nonnull Consumer setter, String name) { + if (validator != null && !validator.test(v)) { + throw new IllegalStateException(validator.errorMessage(v, name) + " in " + this.getBreadCrumbs()); + } else { + setter.accept(v); + } + } + + @Nonnull + public static Vector3d createVector3d(@Nonnull double[] coordinates) { + return new Vector3d(coordinates[0], coordinates[1], coordinates[2]); + } + + public static Vector3d createVector3d(@Nullable double[] coordinates, @Nonnull Supplier defaultSupplier) { + return coordinates != null ? createVector3d(coordinates) : defaultSupplier.get(); + } + + public void requireFloat( + @Nonnull JsonElement data, + String name, + @Nonnull FloatConsumer setter, + DoubleValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor.addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription).required().validator(validator); + } else { + try { + this.validateAndSet((float)this.expectDouble(this.getRequiredJsonElement(data, name), name), validator, setter, name); + } catch (Exception var9) { + this.addError(var9); + } + } + } + + public boolean getFloat( + @Nonnull JsonElement data, + String name, + @Nonnull FloatConsumer setter, + float defaultValue, + DoubleValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription) + .optional(Double.toString(defaultValue)) + .validator(validator); + return false; + } else { + try { + JsonElement element = this.getOptionalJsonElement(data, name); + boolean haveValue = element != null; + if (haveValue) { + defaultValue = (float)this.expectDouble(element, name); + } + + this.validateAndSet(defaultValue, validator, setter, name); + return haveValue; + } catch (Exception var11) { + this.addError(var11); + return false; + } + } + } + + public void requireFloat( + @Nonnull JsonElement data, + String name, + @Nonnull FloatHolder floatHolder, + DoubleValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + floatHolder.setName(name); + this.builderDescriptor + .addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription) + .required() + .computable() + .validator(validator); + } else { + Objects.requireNonNull(floatHolder, "floatHolder is null"); + + try { + floatHolder.readJSON(this.getRequiredJsonElement(data, name), validator, name, this.builderParameters); + this.trackDynamicHolder(floatHolder); + } catch (Exception var9) { + this.addError(var9); + } + } + } + + public boolean getFloat( + @Nonnull JsonElement data, + String name, + @Nonnull FloatHolder floatHolder, + double defaultValue, + DoubleValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + floatHolder.setName(name); + this.builderDescriptor + .addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription) + .optional(Double.toString(defaultValue)) + .computable() + .validator(validator); + return false; + } else { + Objects.requireNonNull(floatHolder, "floatHolder is null"); + + try { + JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name); + floatHolder.readJSON(optionalJsonElement, defaultValue, validator, name, this.builderParameters); + this.trackDynamicHolder(floatHolder); + return optionalJsonElement != null; + } catch (Exception var11) { + this.addError(var11); + return false; + } + } + } + + private void validateAndSet(float v, @Nullable DoubleValidator validator, @Nonnull FloatConsumer setter, String name) { + if (validator != null && !validator.test(v)) { + throw new IllegalStateException(validator.errorMessage(v, name) + " in " + this.getBreadCrumbs()); + } else { + setter.accept(v); + } + } + + public void requireInt( + @Nonnull JsonElement data, + String name, + @Nonnull IntConsumer setter, + IntValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new IntegerSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor.addAttribute(name, Integer.class.getSimpleName(), state, shortDescription, longDescription).required().validator(validator); + } else { + try { + this.validateAndSet(this.expectInteger(this.getRequiredJsonElement(data, name), name), validator, setter, name); + } catch (Exception var9) { + this.addError(var9); + } + } + } + + public boolean getInt( + @Nonnull JsonElement data, + String name, + @Nonnull IntConsumer setter, + int defaultValue, + IntValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new IntegerSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, Integer.class.getSimpleName(), state, shortDescription, longDescription) + .optional(Integer.toString(defaultValue)) + .validator(validator); + return false; + } else { + try { + JsonElement element = this.getOptionalJsonElement(data, name); + boolean haveValue = element != null; + if (haveValue) { + defaultValue = this.expectInteger(element, name); + } + + this.validateAndSet(defaultValue, validator, setter, name); + return haveValue; + } catch (Exception var11) { + this.addError(var11); + return false; + } + } + } + + public void requireInt( + @Nonnull JsonElement data, + String name, + @Nonnull IntHolder intHolder, + IntValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new IntegerSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + intHolder.setName(name); + this.builderDescriptor + .addAttribute(name, Integer.class.getSimpleName(), state, shortDescription, longDescription) + .required() + .computable() + .validator(validator); + } else { + Objects.requireNonNull(intHolder, "intHolder is null"); + + try { + intHolder.readJSON(this.getRequiredJsonElement(data, name), validator, name, this.builderParameters); + this.trackDynamicHolder(intHolder); + } catch (Exception var9) { + this.addError(var9); + } + } + } + + public boolean requireIntIfNotOverridden( + @Nonnull JsonElement data, + String name, + @Nonnull IntHolder intHolder, + IntValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new IntegerSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + intHolder.setName(name); + this.builderDescriptor + .addAttribute(name, Integer.class.getSimpleName(), state, shortDescription, longDescription) + .requiredIfNotOverridden() + .computable() + .validator(validator); + return false; + } else { + Objects.requireNonNull(intHolder, "intHolder is null"); + + try { + JsonElement element = this.getRequiredJsonElementIfNotOverridden(data, name, ParameterType.INTEGER); + boolean valueProvided = element != null; + intHolder.readJSON(element, Integer.MIN_VALUE, !valueProvided ? null : validator, name, this.builderParameters); + this.trackDynamicHolder(intHolder); + return valueProvided; + } catch (Exception var10) { + this.addError(var10); + return false; + } + } + } + + public boolean getInt( + @Nonnull JsonElement data, + String name, + @Nonnull IntHolder intHolder, + int defaultValue, + IntValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new IntegerSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + intHolder.setName(name); + this.builderDescriptor + .addAttribute(name, Integer.class.getSimpleName(), state, shortDescription, longDescription) + .optional(Double.toString(defaultValue)) + .computable() + .validator(validator); + return false; + } else { + try { + Objects.requireNonNull(intHolder, "intHolder is null"); + JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name); + intHolder.readJSON(optionalJsonElement, defaultValue, validator, name, this.builderParameters); + this.trackDynamicHolder(intHolder); + return optionalJsonElement != null; + } catch (Exception var10) { + this.addError(var10); + return false; + } + } + } + + private void validateAndSet(int v, @Nullable IntValidator validator, @Nonnull IntConsumer setter, String name) { + if (validator != null && !validator.test(v)) { + throw new IllegalStateException(validator.errorMessage(v, name) + " in " + this.getBreadCrumbs()); + } else { + setter.accept(v); + } + } + + public void requireBoolean( + @Nonnull JsonElement data, + String name, + @Nonnull BooleanHolder booleanHolder, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new BooleanSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + booleanHolder.setName(name); + this.builderDescriptor.addAttribute(name, Boolean.class.getSimpleName(), state, shortDescription, longDescription).required().computable(); + } else { + Objects.requireNonNull(booleanHolder, "booleanHolder is null"); + + try { + booleanHolder.readJSON(this.getRequiredJsonElement(data, name), name, this.builderParameters); + this.trackDynamicHolder(booleanHolder); + } catch (Exception var8) { + this.addError(var8); + } + } + } + + public boolean getBoolean( + @Nonnull JsonElement data, + String name, + @Nonnull BooleanHolder booleanHolder, + boolean defaultValue, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new BooleanSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + booleanHolder.setName(name); + this.builderDescriptor + .addAttribute(name, Boolean.class.getSimpleName(), state, shortDescription, longDescription) + .optional(Boolean.toString(defaultValue)) + .computable(); + return false; + } else { + Objects.requireNonNull(booleanHolder, "booleanHolder is null"); + + try { + JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name); + booleanHolder.readJSON(optionalJsonElement, defaultValue, name, this.builderParameters); + this.trackDynamicHolder(booleanHolder); + return optionalJsonElement != null; + } catch (Exception var9) { + this.addError(var9); + return false; + } + } + } + + public void requireBoolean( + @Nonnull JsonElement data, + String name, + @Nonnull BooleanConsumer setter, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new BooleanSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor.addAttribute(name, Boolean.class.getSimpleName(), state, shortDescription, longDescription).required(); + } else { + try { + setter.accept(this.expectBoolean(this.getRequiredJsonElement(data, name), name)); + } catch (Exception var8) { + this.addError(var8); + } + } + } + + public boolean getBoolean( + @Nonnull JsonElement data, + String name, + @Nonnull BooleanConsumer setter, + boolean defaultValue, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = BuilderExpressionDynamic.computableSchema(new BooleanSchema()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, Boolean.class.getSimpleName(), state, shortDescription, longDescription) + .optional(Boolean.toString(defaultValue)); + return false; + } else { + try { + JsonElement element = this.getOptionalJsonElement(data, name); + boolean haveValue = element != null; + if (haveValue) { + defaultValue = this.expectBoolean(element, name); + } + + setter.accept(defaultValue); + return haveValue; + } catch (Exception var10) { + this.addError(var10); + return false; + } + } + } + + public void getParameterBlock(@Nonnull JsonElement data, BuilderDescriptorState state, String shortDescription, String longDescription) { + if (this.isCreatingSchema()) { + this.builderSchema.getProperties().put("Parameters", new ObjectSchema()); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor.addAttribute("Parameters", "Parameters", state, shortDescription, longDescription).optional(""); + } else if (!data.isJsonObject()) { + throw new IllegalStateException(String.format("Looking for parameter block in a JsonElement that isn't an object at %s", this.getBreadCrumbs())); + } else { + BuilderParameters builderParameters = new BuilderParameters(this.builderParameters); + builderParameters.readJSON(data.getAsJsonObject(), this.stateHelper); + builderParameters.validateNoDuplicateParameters(this.builderParameters); + builderParameters.addParametersToScope(); + this.builderParameters = builderParameters; + this.addQueryKey("Parameters"); + } + } + + public void cleanupParameters() { + if (!this.isCreatingDescriptor()) { + this.builderParameters.disposeCompileContext(); + } + } + + @Nonnull + protected > E resolveValue(String txt, E[] enumConstants, String paramName) { + try { + return stringToEnum(txt, enumConstants, paramName); + } catch (IllegalArgumentException var5) { + throw new IllegalArgumentException(var5.getMessage() + " in " + this.getBreadCrumbs(), var5); + } + } + + @Nonnull + public static > E stringToEnum(@Nullable String value, E[] enumConstants, String ident) { + if (value != null && !value.isBlank()) { + String trimmed = value.trim(); + + for (E E : enumConstants) { + if (E.name().equalsIgnoreCase(trimmed)) { + return E; + } + } + } + + throw new IllegalArgumentException(String.format("Enum value '%s' is '%s', must be one of %s", ident, value, getDomain(enumConstants))); + } + + @Nonnull + public static > String getDomain(E[] enumConstants) { + return Arrays.toString((Object[])enumConstants); + } + + @Nonnull + private static String formatEnumCamelCase(@Nonnull String name) { + boolean isLower = Character.isLowerCase(name.charAt(0)); + if (name.chars().anyMatch(v -> Character.isLowerCase(v) != isLower)) { + return name; + } else { + StringBuilder nameParts = new StringBuilder(); + + for (String part : name.split("_")) { + nameParts.append(Character.toUpperCase(part.charAt(0))).append(part.substring(1).toLowerCase()).append('_'); + } + + nameParts.deleteCharAt(nameParts.length() - 1); + return nameParts.toString(); + } + } + + @Nonnull + private static > String[] getEnumValues(@Nonnull Class enumClass) { + return Arrays.stream(enumClass.getEnumConstants()).map(Enum::name).map(BuilderBase::formatEnumCamelCase).toArray(String[]::new); + } + + public & Supplier> void requireEnum( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + @Nonnull Class clazz, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + StringSchema s = new StringSchema(); + s.setEnum(getEnumValues(clazz)); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(s)); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor.addAttribute(name, "Flag", state, shortDescription, longDescription).required().setEnum(clazz); + } else { + try { + setter.accept(this.resolveValue(this.expectString(this.getRequiredJsonElement(data, name), name), clazz.getEnumConstants(), name)); + } catch (Exception var9) { + this.addError(var9); + } + } + } + + public & Supplier> boolean getEnum( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + @Nonnull Class clazz, + @Nullable E defaultValue, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + StringSchema s = new StringSchema(); + s.setEnum(getEnumValues(clazz)); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(s)); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "Flag", state, shortDescription, longDescription) + .optional(defaultValue != null ? defaultValue.toString() : "") + .setEnum(clazz); + return false; + } else { + try { + JsonElement element = this.getOptionalJsonElement(data, name); + boolean haveValue = element != null; + if (haveValue) { + defaultValue = this.resolveValue(this.expectString(element, name), clazz.getEnumConstants(), name); + } + + setter.accept(defaultValue); + return haveValue; + } catch (Exception var11) { + this.addError(var11); + return false; + } + } + } + + public & Supplier> void requireEnum( + @Nonnull JsonElement data, + String name, + @Nonnull EnumHolder enumHolder, + @Nonnull Class clazz, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + StringSchema s = new StringSchema(); + s.setEnum(getEnumValues(clazz)); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(s)); + } else if (this.isCreatingDescriptor()) { + enumHolder.setName(name); + this.builderDescriptor.addAttribute(name, "Flag", state, shortDescription, longDescription).required().computable().setEnum(clazz); + } else { + Objects.requireNonNull(enumHolder, "enumHolder is null"); + + try { + enumHolder.readJSON(this.getRequiredJsonElement(data, name), clazz, name, this.builderParameters); + this.trackDynamicHolder(enumHolder); + } catch (Exception var9) { + this.addError(var9); + } + } + } + + public & Supplier> boolean getEnum( + @Nonnull JsonElement data, + String name, + @Nonnull EnumHolder enumHolder, + @Nonnull Class clazz, + @Nonnull E defaultValue, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + StringSchema s = new StringSchema(); + s.setEnum(getEnumValues(clazz)); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(s)); + return false; + } else if (this.isCreatingDescriptor()) { + enumHolder.setName(name); + this.builderDescriptor + .addAttribute(name, "Flag", state, shortDescription, longDescription) + .optional(defaultValue.toString()) + .computable() + .setEnum(clazz); + return false; + } else { + Objects.requireNonNull(enumHolder, "enumHolder is null"); + + try { + JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name); + enumHolder.readJSON(optionalJsonElement, clazz, defaultValue, name, this.builderParameters); + this.trackDynamicHolder(enumHolder); + return optionalJsonElement != null; + } catch (Exception var10) { + this.addError(var10); + return false; + } + } + } + + @Nonnull + public static > String[] enumSetToStrings(@Nonnull EnumSet enumSet) { + int count = 0; + Iterator it = enumSet.iterator(); + + while (it.hasNext()) { + count++; + it.next(); + } + + if (count == 0) { + return ArrayUtil.EMPTY_STRING_ARRAY; + } else { + String[] result = new String[count]; + it = enumSet.iterator(); + + for (int i = 0; i < count; i++) { + result[i] = it.next().toString(); + } + + return result; + } + } + + @Nonnull + public static > EnumSet stringsToEnumSet(@Nullable String[] array, @Nonnull Class clazz, E[] enumConstants, String ident) { + EnumSet value = EnumSet.noneOf(clazz); + if (array == null) { + return value; + } else { + for (String s : array) { + value.add(stringToEnum(s, enumConstants, ident)); + } + + return value; + } + } + + @Nonnull + public static > E[] stringsToEnumArray(@Nullable String[] array, @Nonnull Class clazz, E[] enumConstants, String ident) { + if (array != null && array.length != 0) { + E[] value = (E[])Array.newInstance(clazz, array.length); + + for (int i = 0; i < array.length; i++) { + value[i] = stringToEnum(array[i], enumConstants, ident); + } + + return value; + } else { + return (E[])((Enum[])Array.newInstance(clazz, 0)); + } + } + + protected > void toSet(String name, @Nonnull Class clazz, @Nonnull EnumSet t, @Nonnull String elementAsString) { + E[] enumConstants = (E[])clazz.getEnumConstants(); + + for (String s : elementAsString.split(",")) { + t.add(this.resolveValue(s.trim(), enumConstants, name)); + } + } + + @Nonnull + protected EnumSet toDebugFlagSet(String name, @Nonnull String elementAsString) { + try { + return RoleDebugFlags.getFlags(PATTERN.split(elementAsString.trim())); + } catch (IllegalArgumentException var4) { + throw new IllegalArgumentException(var4.getMessage() + " in parameter " + name + " at " + this.getBreadCrumbs(), var4); + } + } + + protected > void toSet(String name, @Nonnull Class clazz, @Nonnull EnumSet t, @Nonnull JsonArray jsonArray) { + E[] enumConstants = (E[])clazz.getEnumConstants(); + + for (JsonElement jsonElement : jsonArray) { + t.add(this.resolveValue(this.expectString(jsonElement, name), enumConstants, name)); + } + } + + protected > void toSet(String name, @Nonnull Class clazz, @Nonnull EnumSet t, @Nonnull JsonElement jsonElement) { + if (jsonElement.isJsonArray()) { + this.toSet(name, clazz, t, this.expectJsonArray(jsonElement, name)); + NPCPlugin.get() + .getLogger() + .at(Level.WARNING) + .log( + "Use of strings for enum sets is deprecated for JSON attribute '%s' (use []) in %s: %s", + name, + this.getBreadCrumbs(), + this.builderParameters.getFileName() + ); + } else { + this.toSet(name, clazz, t, this.expectString(jsonElement, name)); + } + } + + public & Supplier> void requireEnumArray( + @Nonnull JsonElement data, + String name, + @Nonnull EnumArrayHolder enumArrayHolderHolder, + @Nonnull Class clazz, + EnumArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + StringSchema s = new StringSchema(); + s.setEnum(getEnumValues(clazz)); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + ArraySchema a = new ArraySchema(); + a.setItem(s); + this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(a)); + } else if (this.isCreatingDescriptor()) { + enumArrayHolderHolder.setName(name); + this.builderDescriptor + .addAttribute(name, "FlagArray", state, shortDescription, longDescription) + .required() + .computable() + .validator(validator) + .setEnum(clazz); + } else { + Objects.requireNonNull(enumArrayHolderHolder, "enumArrayHolder is null"); + + try { + enumArrayHolderHolder.readJSON(this.getRequiredJsonElement(data, name), clazz, validator, name, this.builderParameters); + this.trackDynamicHolder(enumArrayHolderHolder); + } catch (Exception var11) { + this.addError(var11); + } + } + } + + public & Supplier> void requireEnumSet( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer> setter, + @Nonnull Class clazz, + @Nonnull Supplier> factory, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + StringSchema s = new StringSchema(); + s.setEnum(getEnumValues(clazz)); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + ArraySchema a = new ArraySchema(); + a.setItem(s); + a.setUniqueItems(true); + this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(a)); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor.addAttribute(name, "FlagSet", state, shortDescription, longDescription).required().setEnum(clazz); + } else { + try { + EnumSet t = (EnumSet)factory.get(); + this.toSet(name, clazz, t, this.getRequiredJsonElement(data, name)); + setter.accept(t); + } catch (Exception var11) { + this.addError(var11); + } + } + } + + public & Supplier> boolean getEnumSet( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer> setter, + @Nonnull Class clazz, + @Nonnull Supplier> factory, + @Nonnull EnumSet defaultValue, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + StringSchema s = new StringSchema(); + s.setEnum(getEnumValues(clazz)); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + ArraySchema a = new ArraySchema(); + a.setItem(s); + a.setUniqueItems(true); + this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(a)); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor.addAttribute(name, "FlagSet", state, shortDescription, longDescription).optional(defaultValue.toString()).setEnum(clazz); + return false; + } else { + try { + JsonElement element = this.getOptionalJsonElement(data, name); + if (element != null) { + EnumSet t = (EnumSet)factory.get(); + this.toSet(name, clazz, t, element); + setter.accept(t); + return true; + } + + setter.accept(defaultValue); + } catch (Exception var12) { + this.addError(var12); + } + + return false; + } + } + + public & Supplier> void requireEnumSet( + @Nonnull JsonElement data, + String name, + @Nonnull EnumSetHolder enumSetHolder, + @Nonnull Class clazz, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + StringSchema s = new StringSchema(); + s.setEnum(getEnumValues(clazz)); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + ArraySchema a = new ArraySchema(); + a.setItem(s); + a.setUniqueItems(true); + this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(a)); + } else if (this.isCreatingSchema()) { + StringSchema s = new StringSchema(); + s.setEnum(getEnumValues(clazz)); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + ArraySchema a = new ArraySchema(); + a.setItem(s); + a.setUniqueItems(true); + this.builderSchema.getProperties().put(name, a); + } else if (this.isCreatingDescriptor()) { + enumSetHolder.setName(name); + this.builderDescriptor.addAttribute(name, "FlagSet", state, shortDescription, longDescription).required().computable().setEnum(clazz); + } else { + Objects.requireNonNull(enumSetHolder, "enumSetHolder is null"); + + try { + enumSetHolder.readJSON(this.getRequiredJsonElement(data, name), clazz, name, this.builderParameters); + this.trackDynamicHolder(enumSetHolder); + } catch (Exception var10) { + this.addError(var10); + } + } + } + + public & Supplier> boolean getEnumSet( + @Nonnull JsonElement data, + String name, + @Nonnull EnumSetHolder enumSetHolder, + @Nonnull Class clazz, + @Nonnull EnumSet defaultValue, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + StringSchema s = new StringSchema(); + s.setEnum(getEnumValues(clazz)); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + ArraySchema a = new ArraySchema(); + a.setItem(s); + a.setUniqueItems(true); + this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(a)); + return false; + } else if (this.isCreatingDescriptor()) { + enumSetHolder.setName(name); + this.builderDescriptor + .addAttribute(name, "FlagSet", state, shortDescription, longDescription) + .optional(defaultValue.toString()) + .computable() + .setEnum(clazz); + return false; + } else { + Objects.requireNonNull(enumSetHolder, "enumSetHolder is null"); + + try { + JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name); + enumSetHolder.readJSON(optionalJsonElement, defaultValue, clazz, name, this.builderParameters); + this.trackDynamicHolder(enumSetHolder); + return optionalJsonElement != null; + } catch (Exception var11) { + this.addError(var11); + return false; + } + } + } + + @Nonnull + private Schema getObjectSchema(@Nonnull Class classType) { + BuilderFactory factory = this.builderManager.getFactory(classType); + Schema subSchema = this.builderSchemaContext.refDefinition(factory); + ObjectSchema ref = new ObjectSchema(); + ref.setProperties(new LinkedHashMap<>()); + Map props = ref.getProperties(); + props.put("Reference", BuilderExpressionDynamic.computableSchema(new StringSchema())); + props.put("Local", BuilderExpressionDynamic.computableSchema(new BooleanSchema())); + props.put("$Label", BuilderExpressionDynamic.computableSchema(new StringSchema())); + props.put("Nullable", BuilderExpressionDynamic.computableSchema(new BooleanSchema())); + props.put("Interfaces", BuilderExpressionDynamic.computableSchema(new ArraySchema(new StringSchema()))); + props.put("Modify", BuilderModifier.toSchema(this.builderSchemaContext)); + Schema comment = new Schema(); + comment.setDoNotSuggest(true); + props.put("Comment", comment); + props.put("$Title", comment); + props.put("$Comment", comment); + props.put("$TODO", comment); + props.put("$Author", comment); + props.put("$Position", comment); + props.put("$FloatingFunctionNodes", comment); + props.put("$Groups", comment); + props.put("$WorkspaceID", comment); + props.put("$NodeEditorMetadata", comment); + props.put("$NodeId", comment); + ref.setTitle("Object reference"); + ref.setRequired("Reference"); + ref.setAdditionalProperties(false); + Schema cond = new Schema(); + ObjectSchema check = new ObjectSchema(); + check.setProperties(Map.of("Reference", BuilderExpressionDynamic.computableSchema(new StringSchema()))); + check.setRequired("Reference"); + cond.setIf(check); + cond.setThen(ref); + cond.setElse(subSchema); + return BuilderExpressionDynamic.computableSchema(cond); + } + + public boolean getObject( + @Nonnull JsonElement data, + String name, + @Nonnull BuilderObjectReferenceHelper builderObjectReferenceHelper, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription, + @Nonnull BuilderValidationHelper builderValidationHelper + ) { + if (this.isCreatingSchema()) { + Schema s = this.getObjectSchema(builderObjectReferenceHelper.getClassType()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "ObjectRef", state, shortDescription, longDescription) + .domain(this.builderManager.getCategoryName(builderObjectReferenceHelper.getClassType())) + .optional((String)null); + return false; + } else { + this.addQueryKey(name); + + try { + JsonElement element = this.expectJsonObject(data, name).get(name); + if (element != null) { + builderObjectReferenceHelper.setLabel(name); + this.extraInfo.pushKey(name); + builderObjectReferenceHelper.readConfig(element, this.builderManager, this.builderParameters, builderValidationHelper); + this.extraInfo.popKey(); + return true; + } + } catch (Exception var9) { + this.addError(var9); + } + + return false; + } + } + + public void requireObject( + @Nonnull JsonElement data, + String name, + @Nonnull BuilderObjectReferenceHelper builderObjectReferenceHelper, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription, + @Nonnull BuilderValidationHelper builderValidationHelper + ) { + if (this.isCreatingSchema()) { + Schema s = this.getObjectSchema(builderObjectReferenceHelper.getClassType()); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "ObjectRef", state, shortDescription, longDescription) + .domain(this.builderManager.getCategoryName(builderObjectReferenceHelper.getClassType())) + .required(); + } else { + try { + builderObjectReferenceHelper.setLabel(name); + this.extraInfo.pushKey(name); + builderObjectReferenceHelper.readConfig( + this.getRequiredJsonElement(data, name), this.builderManager, this.builderParameters, builderValidationHelper + ); + this.extraInfo.popKey(); + } catch (Exception var9) { + this.addError(var9); + } + } + } + + public boolean getCodecObject( + @Nonnull JsonElement data, + String name, + @Nonnull BuilderCodecObjectHelper helper, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = this.builderSchemaContext.refDefinition(helper.codec); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "CodecObject", state, shortDescription, longDescription) + .domain(helper.getClassType().getSimpleName()) + .optional((String)null); + return false; + } else { + this.addQueryKey(name); + + try { + JsonElement element = this.expectJsonObject(data, name).get(name); + if (element != null) { + this.extraInfo.pushKey(name); + + try { + helper.readConfig(element, this.extraInfo); + } catch (Exception var9) { + this.addError(var9); + } + + this.extraInfo.popKey(); + return true; + } + } catch (Exception var10) { + this.addError(var10); + } + + return false; + } + } + + public void requireCodecObject( + @Nonnull JsonElement data, + String name, + @Nonnull BuilderCodecObjectHelper helper, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + Schema s = this.builderSchemaContext.refDefinition(helper.codec); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "CodecObject", state, shortDescription, longDescription) + .domain(helper.getClassType().getSimpleName()) + .required(); + } else { + this.extraInfo.pushKey(name); + + try { + helper.readConfig(this.getRequiredJsonElement(data, name), this.extraInfo); + } catch (Exception var8) { + this.addError(var8); + } + + this.extraInfo.popKey(); + } + } + + public void requireEmbeddableArray( + @Nonnull JsonElement data, + String embedTag, + @Nonnull BuilderObjectArrayHelper builderObjectArrayHelper, + @Nonnull ArrayValidator arrayValidator, + BuilderDescriptorState state, + String shortDescription, + String longDescription, + @Nonnull BuilderValidationHelper builderValidationHelper + ) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(embedTag, "EmbeddableArray", state, shortDescription, longDescription) + .domain(this.builderManager.getCategoryName(builderObjectArrayHelper.getClassType())) + .validator(arrayValidator) + .required(); + } else if (this.useDefaultsOnly) { + throw new IllegalArgumentException("An embeddable array can be only used once!"); + } else { + try { + if (data.isJsonArray()) { + builderObjectArrayHelper.readConfig(data, this.builderManager, this.builderParameters, builderValidationHelper); + if (!arrayValidator.test(builderObjectArrayHelper)) { + throw new IllegalStateException(arrayValidator.errorMessage(builderObjectArrayHelper) + " at " + this.getBreadCrumbs()); + } + + this.useDefaultsOnly = true; + } else { + this.requireArray0(data, embedTag, builderObjectArrayHelper, arrayValidator, builderValidationHelper); + } + } catch (Exception var10) { + this.addError(var10); + } + } + } + + public boolean getArray( + @Nonnull JsonElement data, + String name, + @Nonnull BuilderObjectArrayHelper builderObjectArrayHelper, + ArrayValidator arrayValidator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription, + @Nonnull BuilderValidationHelper builderValidationHelper + ) { + if (this.isCreatingSchema()) { + ArraySchema a = new ArraySchema(); + Schema s = this.getObjectSchema(builderObjectArrayHelper.getClassType()); + a.setDescription(longDescription == null ? shortDescription : longDescription); + a.setItem(s); + this.builderSchema.getProperties().put(name, a); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "Array", state, shortDescription, longDescription) + .optional((String)null) + .validator(arrayValidator) + .domain(this.builderManager.getCategoryName(builderObjectArrayHelper.getClassType())); + return false; + } else { + try { + JsonElement element = this.getOptionalJsonElement(data, name); + if (element != null) { + this.requireArray0(element, name, builderObjectArrayHelper, arrayValidator, builderValidationHelper); + return true; + } + } catch (Exception var11) { + this.addError(var11); + } + + return false; + } + } + + public void requireArray( + @Nonnull JsonElement data, + String name, + @Nonnull BuilderObjectArrayHelper builderObjectArrayHelper, + ArrayValidator arrayValidator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription, + @Nonnull BuilderValidationHelper builderValidationHelper + ) { + if (this.isCreatingSchema()) { + ArraySchema a = new ArraySchema(); + Schema s = this.getObjectSchema(builderObjectArrayHelper.getClassType()); + a.setDescription(longDescription == null ? shortDescription : longDescription); + a.setItem(s); + this.builderSchema.getProperties().put(name, a); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "Array", state, shortDescription, longDescription) + .required() + .validator(arrayValidator) + .domain(this.builderManager.getCategoryName(builderObjectArrayHelper.getClassType())); + } else { + try { + this.requireArray0(this.getRequiredJsonElement(data, name), name, builderObjectArrayHelper, arrayValidator, builderValidationHelper); + } catch (Exception var11) { + this.addError(var11); + } + } + } + + private void requireArray0( + @Nonnull JsonElement data, + String name, + @Nonnull BuilderObjectArrayHelper builderObjectArrayHelper, + @Nullable ArrayValidator validator, + @Nonnull BuilderValidationHelper builderValidationHelper + ) { + builderObjectArrayHelper.setLabel(name); + this.extraInfo.pushKey(name); + builderObjectArrayHelper.readConfig(data, this.builderManager, this.builderParameters, builderValidationHelper); + this.extraInfo.popKey(); + if (validator != null && !validator.test(builderObjectArrayHelper)) { + throw new IllegalStateException(validator.errorMessage(builderObjectArrayHelper) + " at " + this.getBreadCrumbs()); + } + } + + public void requireArray( + @Nonnull JsonElement data, + @Nonnull BuilderObjectArrayHelper builderObjectArrayHelper, + ArrayValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription, + @Nonnull BuilderValidationHelper builderValidationHelper + ) { + if (this.isCreatingSchema()) { + this.builderSchemaRaw = new ArraySchema(); + Schema s = this.getObjectSchema(builderObjectArrayHelper.getClassType()); + this.builderSchemaRaw.setDescription(longDescription == null ? shortDescription : longDescription); + ((ArraySchema)this.builderSchemaRaw).setItem(s); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(null, "Array", state, shortDescription, longDescription) + .domain(this.builderManager.getCategoryName(builderObjectArrayHelper.getClassType())) + .validator(validator) + .required(); + } else { + try { + builderObjectArrayHelper.readConfig(data, this.builderManager, this.builderParameters, builderValidationHelper); + } catch (Exception var9) { + this.addError(var9); + } + } + } + + public void requireAsset( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + @Nonnull AssetValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + StringSchema assetS = new StringSchema(); + validator.updateSchema(assetS); + Schema s = BuilderExpressionDynamic.computableSchema(assetS); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor.addAttribute(name, "Asset", state, shortDescription, longDescription).required().domain(validator.getDomain()); + } else { + try { + this.validateAndSet(this.expectString(this.getRequiredJsonElement(data, name), name), validator, setter, name); + } catch (Exception var10) { + this.addError(var10); + } + } + } + + public boolean getAsset( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + String defaultValue, + @Nonnull AssetValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + StringSchema assetS = new StringSchema(); + validator.updateSchema(assetS); + Schema s = BuilderExpressionDynamic.computableSchema(assetS); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor.addAttribute(name, "Asset", state, shortDescription, longDescription).optional(defaultValue).domain(validator.getDomain()); + return false; + } else { + try { + JsonElement element = this.getOptionalJsonElement(data, name); + boolean haveValue = element != null; + if (haveValue) { + defaultValue = this.expectString(element, name); + } + + this.validateAndSet(defaultValue, validator, setter, name); + return haveValue; + } catch (Exception var11) { + this.addError(var11); + return false; + } + } + } + + public void requireAsset( + @Nonnull JsonElement data, + String name, + @Nonnull AssetHolder assetHolder, + @Nonnull AssetValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + StringSchema assetS = new StringSchema(); + validator.updateSchema(assetS); + Schema s = BuilderExpressionDynamic.computableSchema(assetS); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + assetHolder.setName(name); + this.builderDescriptor.addAttribute(name, "Asset", state, shortDescription, longDescription).required().computable().domain(validator.getDomain()); + } else { + Objects.requireNonNull(assetHolder, "assetHolder is null"); + + try { + assetHolder.readJSON(this.getRequiredJsonElement(data, name), validator, name, this.builderParameters); + assetHolder.staticValidate(); + this.trackDynamicHolder(assetHolder); + } catch (Exception var10) { + this.addError(var10); + } + } + } + + public boolean getAsset( + @Nonnull JsonElement data, + String name, + @Nonnull AssetHolder assetHolder, + String defaultValue, + @Nonnull AssetValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + StringSchema assetS = new StringSchema(); + validator.updateSchema(assetS); + Schema s = BuilderExpressionDynamic.computableSchema(assetS); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + assetHolder.setName(name); + this.builderDescriptor + .addAttribute(name, "Asset", state, shortDescription, longDescription) + .optional(defaultValue) + .computable() + .domain(validator.getDomain()); + return false; + } else { + Objects.requireNonNull(assetHolder, "assetHolder is null"); + + try { + JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name); + assetHolder.readJSON(optionalJsonElement, defaultValue, validator, name, this.builderParameters); + assetHolder.staticValidate(); + this.trackDynamicHolder(assetHolder); + return optionalJsonElement != null; + } catch (Exception var11) { + this.addError(var11); + return false; + } + } + } + + private void validateAndSet(String str, @Nullable AssetValidator validator, @Nonnull Consumer setter, String name) { + if (validator != null) { + this.validateSingleAsset(str, validator, name); + } + + setter.accept(str); + } + + public static boolean validateAssetList(@Nullable String[] assetList, @Nonnull AssetValidator validator, String attributeName, boolean testExistance) { + if (assetList == null) { + if (!validator.canListBeEmpty()) { + throw new IllegalStateException("Null is not an allowed list of " + validator.getDomain() + " value for attribute \"" + attributeName + "\""); + } else { + return true; + } + } else if (assetList.length == 0) { + if (!validator.canListBeEmpty()) { + throw new IllegalStateException("List of " + validator.getDomain() + " value for attribute \"" + attributeName + "\" must not be empty"); + } else { + return true; + } + } else { + for (String asset : assetList) { + validateAsset(asset, validator, attributeName, testExistance); + } + + return true; + } + } + + public static boolean validateAsset(@Nullable String assetName, @Nonnull AssetValidator validator, String attributeName, boolean testExistance) { + if (assetName == null) { + if (!validator.isNullable()) { + throw new SkipSentryException( + new IllegalStateException("Null is not an allowed " + validator.getDomain() + " value for attribute \"" + attributeName + "\"") + ); + } else { + return true; + } + } else if (assetName.isEmpty()) { + if (!validator.canBeEmpty()) { + throw new SkipSentryException( + new IllegalStateException("Empty string is not an allowed " + validator.getDomain() + " value for attribute \"" + attributeName + "\"") + ); + } else { + return true; + } + } else if (!testExistance) { + return true; + } else if (validator.isMatcher() && StringUtil.isGlobPattern(assetName)) { + return true; + } else if (!validator.test(assetName)) { + throw new SkipSentryException(new IllegalStateException(validator.errorMessage(assetName, attributeName))); + } else { + return true; + } + } + + private void validateSingleAsset(String assetName, @Nonnull AssetValidator validator, String attributeName) { + try { + if (!validateAsset(assetName, validator, attributeName, true)) { + NPCPlugin.get() + .getLogger() + .at(Level.WARNING) + .log("%s in %s: %s", validator.errorMessage(assetName, attributeName), this.getBreadCrumbs(), this.builderParameters.getFileName()); + } + } catch (IllegalStateException var5) { + throw new IllegalStateException(var5.getMessage() + " in " + this.getBreadCrumbs()); + } + } + + public boolean getAssetArray( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + Function mapper, + String[] defaultValue, + @Nonnull AssetValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + StringSchema assetS = new StringSchema(); + validator.updateSchema(assetS); + ArraySchema a = new ArraySchema(assetS); + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addAttribute(name, "AssetArray", state, shortDescription, longDescription) + .optional(this.defaultArrayToString(defaultValue)) + .domain(validator.getDomain()); + return false; + } else { + try { + JsonElement element = this.getOptionalJsonElement(data, name); + boolean haveValue = element != null; + String[] list; + if (haveValue) { + list = this.expectStringArray(element, mapper, name); + } else { + list = defaultValue; + } + + this.validateAndSet(list, validator, setter, name); + return haveValue; + } catch (Exception var13) { + this.addError(var13); + return false; + } + } + } + + public boolean getAssetArray( + @Nonnull JsonElement data, + String name, + @Nonnull AssetArrayHolder assetHolder, + String[] defaultValue, + int minLength, + int maxLength, + @Nonnull AssetValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + StringSchema assetS = new StringSchema(); + validator.updateSchema(assetS); + ArraySchema a = new ArraySchema(assetS); + if (minLength != 0) { + a.setMinItems(minLength); + } + + if (maxLength != Integer.MAX_VALUE) { + a.setMaxItems(maxLength); + } + + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + assetHolder.setName(name); + this.builderDescriptor + .addAttribute(name, "AssetArray", state, shortDescription, longDescription) + .optional(this.defaultArrayToString(defaultValue)) + .computable() + .domain(validator.getDomain()); + return false; + } else { + Objects.requireNonNull(assetHolder, "assetHolder is null"); + + try { + JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name); + assetHolder.readJSON(optionalJsonElement, minLength, maxLength, defaultValue, validator, name, this.builderParameters); + assetHolder.staticValidate(); + this.trackDynamicHolder(assetHolder); + return optionalJsonElement != null; + } catch (Exception var14) { + this.addError(var14); + return false; + } + } + } + + public void requireAssetArray( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + Function mapper, + @Nonnull AssetValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + StringSchema assetS = new StringSchema(); + validator.updateSchema(assetS); + ArraySchema a = new ArraySchema(assetS); + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor.addAttribute(name, "AssetArray", state, shortDescription, longDescription).required().domain(validator.getDomain()); + } else { + try { + JsonElement element = this.getRequiredJsonElement(data, name); + this.validateAndSet(this.expectStringArray(element, mapper, name), validator, setter, name); + } catch (Exception var12) { + this.addError(var12); + } + } + } + + public void requireAssetArray( + @Nonnull JsonElement data, + String name, + @Nonnull AssetArrayHolder assetHolder, + int minLength, + int maxLength, + @Nonnull AssetValidator validator, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + if (this.isCreatingSchema()) { + StringSchema assetS = new StringSchema(); + validator.updateSchema(assetS); + ArraySchema a = new ArraySchema(assetS); + if (minLength != 0) { + a.setMinItems(minLength); + } + + if (maxLength != Integer.MAX_VALUE) { + a.setMaxItems(maxLength); + } + + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + } else if (this.isCreatingDescriptor()) { + assetHolder.setName(name); + this.builderDescriptor + .addAttribute(name, "AssetArray", state, shortDescription, longDescription) + .required() + .computable() + .domain(validator.getDomain()); + } else { + Objects.requireNonNull(assetHolder, "assetHolder is null"); + + try { + assetHolder.readJSON(this.getRequiredJsonElement(data, name), minLength, maxLength, validator, name, this.builderParameters); + assetHolder.staticValidate(); + this.trackDynamicHolder(assetHolder); + } catch (Exception var13) { + this.addError(var13); + } + } + } + + private void validateAndSet(@Nullable String[] assetList, @Nullable AssetValidator validator, @Nonnull Consumer setter, String attributeName) { + if (validator != null) { + if (assetList == null) { + if (!validator.canListBeEmpty()) { + throw new IllegalStateException( + "Null is not an allowed list of " + validator.getDomain() + " value for attribute \"" + attributeName + "\" in " + this.getBreadCrumbs() + ); + } + } else if (assetList.length == 0) { + if (!validator.canListBeEmpty()) { + throw new IllegalStateException( + "List of " + validator.getDomain() + " value for attribute \"" + attributeName + "\" must not be empty in " + this.getBreadCrumbs() + ); + } + } else { + for (String asset : assetList) { + this.validateSingleAsset(asset, validator, attributeName); + } + } + } + + setter.accept(assetList); + } + + protected BuilderDescriptor createDescriptor( + @Nonnull Builder builder, + String builderName, + String categoryName, + BuilderManager builderManager, + BuilderDescriptorState state, + String shortDescription, + String longDescription, + Set tags + ) { + this.builderDescriptor = new BuilderDescriptor(builderName, categoryName, shortDescription, longDescription, tags, state); + + BuilderDescriptor var9; + try { + builder.readConfig(null, null, builderManager, this.builderParameters, null); + var9 = this.builderDescriptor; + } finally { + this.builderDescriptor = null; + } + + return var9; + } + + protected boolean isCreatingDescriptor() { + return this.builderDescriptor != null; + } + + protected boolean isCreatingSchema() { + return this.builderSchema != null; + } + + @Nonnull + @Override + public String getSchemaName() { + return this.typeName == null ? "NPC:Class:" + this.getClass().getSimpleName() : "NPC:" + this.typeName; + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + Schema var3; + try { + this.builderSchemaContext = context; + this.builderSchemaRaw = this.builderSchema = new ObjectSchema(); + this.builderSchema.setProperties(new LinkedHashMap<>()); + this.builderSchema.setAdditionalProperties(false); + this.builderDescriptor = new BuilderDescriptor(null, null, null, null, null, null); + + try { + this.readConfig(null, null, BuilderManager.SCHEMA_BUILDER_MANAGER, this.builderParameters, null); + } finally { + this.builderDescriptor = null; + } + + Schema comment = new Schema(); + comment.setDoNotSuggest(true); + this.builderSchema.getProperties().put("Comment", comment); + this.builderSchema.getProperties().put("$Title", comment); + this.builderSchema.getProperties().put("$Comment", comment); + this.builderSchema.getProperties().put("$TODO", comment); + this.builderSchema.getProperties().put("$Author", comment); + this.builderSchema.getProperties().put("$Position", comment); + this.builderSchema.getProperties().put("$FloatingFunctionNodes", comment); + this.builderSchema.getProperties().put("$Groups", comment); + this.builderSchema.getProperties().put("$WorkspaceID", comment); + this.builderSchema.getProperties().put("$NodeEditorMetadata", comment); + this.builderSchema.getProperties().put("$NodeId", comment); + this.builderSchemaRaw.setTitle(this.typeName); + this.builderSchemaRaw.setDescription(this.getLongDescription()); + var3 = this.builderSchemaRaw; + } finally { + this.builderSchema = null; + this.builderSchemaContext = null; + } + + return var3; + } + + @Override + public final BuilderDescriptor getDescriptor(String builderName, String categoryName, BuilderManager builderManager) { + HashSet tags = new HashSet<>(); + this.registerTags(tags); + return this.createDescriptor( + this, builderName, categoryName, builderManager, this.getBuilderDescriptorState(), this.getShortDescription(), this.getLongDescription(), tags + ); + } + + @Nullable + public abstract String getShortDescription(); + + @Nullable + public abstract String getLongDescription(); + + public void registerTags(@Nonnull Set tags) { + String pkg = this.getClass().getPackageName().replaceFirst("\\.builders$", ""); + tags.add(pkg.substring(pkg.lastIndexOf(46) + 1)); + } + + @Nullable + @Override + public abstract BuilderDescriptorState getBuilderDescriptorState(); + + protected void validateNotAllStringsEmpty(String attribute1, String string1, String attribute2, String string2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(StringsNotEmptyValidator.withAttributes(attribute1, attribute2)); + } else { + if (!StringsNotEmptyValidator.test(string1, string2)) { + this.addError(StringsNotEmptyValidator.errorMessage(string1, attribute1, string2, attribute2, this.getBreadCrumbs())); + } + } + } + + protected void validateAtMostOneString(String attribute1, String string1, String attribute2, String string2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(StringsAtMostOneValidator.withAttributes(attribute1, attribute2)); + } else { + if (StringsAtMostOneValidator.test(string1, string2)) { + this.addError(StringsAtMostOneValidator.errorMessage(string1, attribute1, string2, attribute2, this.getBreadCrumbs())); + } + } + } + + protected void validateOneSetString(String attribute1, String string1, String attribute2, String string2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(StringsOneSetValidator.withAttributes(attribute1, attribute2)); + } else { + if (!StringsOneSetValidator.test(string1, string2)) { + this.addError(StringsOneSetValidator.formatErrorMessage(string1, attribute1, string2, attribute2, this.getBreadCrumbs())); + } + } + } + + protected void validateOneSetAsset(@Nonnull AssetHolder value1, String attribute2, String string2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(StringsOneSetValidator.withAttributes(value1.getName(), string2)); + } else if (value1.isStatic()) { + this.validateOneSetString(value1.getName(), value1.get(null), attribute2, string2); + } else { + value1.addRelationValidator((executionContext, v1) -> { + if (!StringsOneSetValidator.test(v1, string2)) { + throw new IllegalStateException(StringsOneSetValidator.formatErrorMessage(v1, value1.getName(), string2, attribute2, this.getBreadCrumbs())); + } + }); + } + } + + protected void validateOneSetAsset(@Nonnull AssetHolder value1, @Nonnull AssetHolder value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(StringsOneSetValidator.withAttributes(value1.getName(), value2.getName())); + } else if (value1.isStatic()) { + this.validateOneSetAsset(value2, value1.getName(), value1.get(null)); + } else if (value2.isStatic()) { + this.validateOneSetAsset(value1, value2.getName(), value2.get(null)); + } else { + value1.addRelationValidator((executionContext, v1) -> { + String s2 = value2.rawGet(executionContext); + if (!StringsOneSetValidator.test(v1, s2)) { + throw new IllegalStateException(StringsOneSetValidator.formatErrorMessage(v1, value1.getName(), s2, value2.getName(), this.getBreadCrumbs())); + } + }); + value2.addRelationValidator((executionContext, v2) -> { + String s1 = value1.rawGet(executionContext); + if (!StringsOneSetValidator.test(s1, v2)) { + throw new IllegalStateException(StringsOneSetValidator.formatErrorMessage(s1, value1.getName(), v2, value2.getName(), this.getBreadCrumbs())); + } + }); + } + } + + protected void validateOneSetAssetArray(@Nonnull AssetArrayHolder value1, String attribute2, String[] value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(ArraysOneSetValidator.withAttributes(value1.getName(), attribute2)); + } else if (value1.isStatic()) { + if (!ArraysOneSetValidator.validate(value1.get(null), value2)) { + this.addError(ArraysOneSetValidator.formatErrorMessage(value1.getName(), attribute2, this.getBreadCrumbs())); + } + } else { + value1.addRelationValidator((executionContext, v1) -> { + if (!ArraysOneSetValidator.validate(v1, value2)) { + throw new IllegalStateException(ArraysOneSetValidator.formatErrorMessage(value1.getName(), attribute2, this.getBreadCrumbs())); + } + }); + } + } + + protected void validateOneSetAssetArray(@Nonnull AssetArrayHolder value1, @Nonnull AssetArrayHolder value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(ArraysOneSetValidator.withAttributes(value1.getName(), value2.getName())); + } else if (value1.isStatic()) { + this.validateOneSetAssetArray(value2, value1.getName(), value1.get(null)); + } else if (value2.isStatic()) { + this.validateOneSetAssetArray(value1, value2.getName(), value2.get(null)); + } else { + value1.addRelationValidator((executionContext, v1) -> { + String[] s2 = value2.rawGet(executionContext); + if (!ArraysOneSetValidator.validate(v1, s2)) { + throw new IllegalStateException(ArraysOneSetValidator.formatErrorMessage(value1.getName(), value2.getName(), this.getBreadCrumbs())); + } + }); + value2.addRelationValidator((executionContext, v2) -> { + String[] s1 = value1.rawGet(executionContext); + if (!ArraysOneSetValidator.validate(s1, v2)) { + throw new IllegalStateException(ArraysOneSetValidator.formatErrorMessage(value1.getName(), value2.getName(), this.getBreadCrumbs())); + } + }); + } + } + + protected void validateNoDuplicates(Iterable list, String variableName) { + NoDuplicatesValidator validator = NoDuplicatesValidator.withAttributes(list, variableName); + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(validator); + } else { + if (!validator.test()) { + this.addError(validator.errorMessage()); + } + } + } + + protected void validateDoubleRelation(String attribute1, double value1, @Nonnull RelationalOperator relation, String attribute2, double value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(attribute1, relation, attribute2)); + } else { + if (!DoubleValidator.compare(value1, relation, value2)) { + this.addError( + String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", attribute1, value1, relation.asText(), attribute2, value2, this.getBreadCrumbs()) + ); + } + } + } + + protected void validateDoubleRelation(@Nonnull DoubleHolder value1, @Nonnull RelationalOperator relation, String attribute2, double value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(value1.getName(), relation, attribute2)); + } else { + if (value1.isStatic()) { + this.validateDoubleRelation(value1.getName(), value1.get(null), relation, attribute2, value2); + } else { + value1.addRelationValidator( + (executionContext, v1) -> { + if (!DoubleValidator.compare(v1, relation, value2)) { + throw new IllegalStateException( + String.format( + "'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v1, relation.asText(), attribute2, value2, this.getBreadCrumbs() + ) + ); + } + } + ); + } + } + } + + protected void validateDoubleRelation(String attribute1, double value1, @Nonnull RelationalOperator relation, @Nonnull DoubleHolder value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(attribute1, relation, value2.getName())); + } else { + if (value2.isStatic()) { + this.validateDoubleRelation(attribute1, value1, relation, value2.getName(), value2.get(null)); + } else { + value2.addRelationValidator( + (executionContext, v2) -> { + if (!DoubleValidator.compare(value1, relation, v2)) { + throw new IllegalStateException( + String.format( + "'%s'(=%s) should be %s '%s'(=%s) in %s", attribute1, value1, relation.asText(), value2.getName(), v2, this.getBreadCrumbs() + ) + ); + } + } + ); + } + } + } + + protected void validateDoubleRelation(@Nonnull DoubleHolder value1, @Nonnull RelationalOperator relation, @Nonnull DoubleHolder value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(value1.getName(), relation, value2.getName())); + } else { + if (value1.isStatic()) { + this.validateDoubleRelation(value1.getName(), value1.get(null), relation, value2); + } else if (value2.isStatic()) { + this.validateDoubleRelation(value1, relation, value2.getName(), value2.get(null)); + } else { + value1.addRelationValidator( + (executionContext1, v1) -> { + double v2 = value2.rawGet(executionContext1); + if (!DoubleValidator.compare(v1, relation, v2)) { + throw new IllegalStateException( + String.format( + "'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v1, relation.asText(), value2.getName(), v2, this.getBreadCrumbs() + ) + ); + } + } + ); + value2.addRelationValidator( + (executionContext, v2) -> { + double v1 = value1.rawGet(executionContext); + if (!DoubleValidator.compare(v1, relation, v2)) { + throw new IllegalStateException( + String.format( + "'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v1, relation.asText(), value2.getName(), v2, this.getBreadCrumbs() + ) + ); + } + } + ); + } + } + } + + protected void validateFloatRelation(String attribute1, float value1, @Nonnull RelationalOperator relation, String attribute2, float value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(attribute1, relation, attribute2)); + } else { + if (!DoubleValidator.compare(value1, relation, value2)) { + this.addError( + String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", attribute1, value1, relation.asText(), attribute2, value2, this.getBreadCrumbs()) + ); + } + } + } + + protected void validateFloatRelation(@Nonnull FloatHolder value1, @Nonnull RelationalOperator relation, String attribute2, float value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(value1.getName(), relation, attribute2)); + } else { + if (value1.isStatic()) { + this.validateFloatRelation(value1.getName(), value1.get(null), relation, attribute2, value2); + } else { + value1.addRelationValidator( + (executionContext, v1) -> { + if (!DoubleValidator.compare(v1, relation, value2)) { + throw new IllegalStateException( + String.format( + "'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v1, relation.asText(), attribute2, value2, this.getBreadCrumbs() + ) + ); + } + } + ); + } + } + } + + protected void validateFloatRelation(String attribute1, float value1, @Nonnull RelationalOperator relation, @Nonnull FloatHolder value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(attribute1, relation, value2.getName())); + } else { + if (value2.isStatic()) { + this.validateFloatRelation(attribute1, value1, relation, value2.getName(), value2.get(null)); + } else { + value2.addRelationValidator( + (executionContext, v2) -> { + if (!DoubleValidator.compare(value1, relation, v2)) { + throw new IllegalStateException( + String.format( + "'%s'(=%s) should be %s '%s'(=%s) in %s", attribute1, value1, relation.asText(), value2.getName(), v2, this.getBreadCrumbs() + ) + ); + } + } + ); + } + } + } + + protected void validateFloatRelation(@Nonnull FloatHolder value1, @Nonnull RelationalOperator relation, @Nonnull FloatHolder value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(value1.getName(), relation, value2.getName())); + } else { + if (value1.isStatic()) { + this.validateFloatRelation(value1.getName(), value1.get(null), relation, value2); + } else if (value2.isStatic()) { + this.validateFloatRelation(value1, relation, value2.getName(), value2.get(null)); + } else { + value1.addRelationValidator( + (executionContext1, v1) -> { + double v2 = value2.rawGet(executionContext1); + if (!DoubleValidator.compare(v1, relation, v2)) { + throw new IllegalStateException( + String.format( + "'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v1, relation.asText(), value2.getName(), v2, this.getBreadCrumbs() + ) + ); + } + } + ); + value2.addRelationValidator( + (executionContext, v2) -> { + double v1 = value1.rawGet(executionContext); + if (!DoubleValidator.compare(v1, relation, v2)) { + throw new IllegalStateException( + String.format( + "'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v1, relation.asText(), value2.getName(), v2, this.getBreadCrumbs() + ) + ); + } + } + ); + } + } + } + + protected void validateIntRelation(String attribute1, int value1, @Nonnull RelationalOperator relation, String attribute2, int value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(attribute1, relation, attribute2)); + } else { + if (!IntValidator.compare(value1, relation, value2)) { + this.addError( + String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", attribute1, value1, relation.asText(), attribute2, value2, this.getBreadCrumbs()) + ); + } + } + } + + protected void validateIntRelation(@Nonnull IntHolder value1, @Nonnull RelationalOperator relation, String attribute2, int value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(value1.getName(), relation, attribute2)); + } else { + if (value1.isStatic()) { + this.validateIntRelation(value1.getName(), value1.get(null), relation, attribute2, value2); + } else { + value1.addRelationValidator( + (executionContext, v1) -> { + if (!IntValidator.compare(v1, relation, value2)) { + throw new IllegalStateException( + String.format( + "'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v1, relation.asText(), attribute2, value2, this.getBreadCrumbs() + ) + ); + } + } + ); + } + } + } + + protected void validateIntRelation(String attribute1, int value1, @Nonnull RelationalOperator relation, @Nonnull IntHolder value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(attribute1, relation, value2.getName())); + } else { + if (value2.isStatic()) { + this.validateIntRelation(attribute1, value1, relation, value2.getName(), value2.get(null)); + } else { + value2.addRelationValidator( + (executionContext, v2) -> { + if (!IntValidator.compare(value1, relation, v2)) { + throw new IllegalStateException( + String.format( + "'%s'(=%s) should be %s '%s'(=%s) in %s", attribute1, value1, relation.asText(), value2.getName(), v2, this.getBreadCrumbs() + ) + ); + } + } + ); + } + } + } + + protected void validateIntRelation(@Nonnull IntHolder value1, @Nonnull RelationalOperator relation, @Nonnull IntHolder value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(value1.getName(), relation, value2.getName())); + } else { + if (value1.isStatic()) { + this.validateIntRelation(value1.getName(), value1.get(null), relation, value2); + } else if (value2.isStatic()) { + this.validateIntRelation(value1, relation, value2.getName(), value2.get(null)); + } else { + value1.addRelationValidator( + (executionContext1, v1) -> { + int v2 = value2.rawGet(executionContext1); + if (!IntValidator.compare(v1, relation, v2)) { + throw new IllegalStateException( + String.format( + "'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v1, relation.asText(), value2.getName(), v2, this.getBreadCrumbs() + ) + ); + } + } + ); + value2.addRelationValidator( + (executionContext, v2) -> { + int v1 = value1.rawGet(executionContext); + if (!IntValidator.compare(v1, relation, v2)) { + throw new IllegalStateException( + String.format( + "'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v1, relation.asText(), value2.getName(), v2, this.getBreadCrumbs() + ) + ); + } + } + ); + } + } + } + + protected void validateIntRelationIfBooleanIs( + boolean targetValue, boolean actualValue, @Nonnull IntHolder value1, @Nonnull RelationalOperator relation, @Nonnull IntHolder value2 + ) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(value1.getName(), relation, value2.getName())); + } else if (actualValue == targetValue) { + this.validateIntRelation(value1, relation, value2); + } + } + + protected void validateAnyPresent( + String attribute1, @Nonnull BuilderObjectHelper objectHelper1, String attribute2, @Nonnull BuilderObjectHelper objectHelper2 + ) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AnyPresentValidator.withAttributes(attribute1, attribute2)); + } else if (!objectHelper1.isPresent() && !objectHelper2.isPresent()) { + this.addError(AnyPresentValidator.errorMessage(new String[]{attribute1, attribute2}) + " in " + this.getBreadCrumbs()); + } + } + + protected void validateAnyPresent( + String attribute1, + @Nonnull BuilderObjectHelper objectHelper1, + String attribute2, + @Nonnull BuilderObjectHelper objectHelper2, + String attribute3, + @Nonnull BuilderObjectHelper objectHelper3 + ) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AnyPresentValidator.withAttributes(new String[]{attribute1, attribute2, attribute3})); + } else if (!objectHelper1.isPresent() && !objectHelper2.isPresent() && !objectHelper3.isPresent()) { + this.addError(AnyPresentValidator.errorMessage(new String[]{attribute1, attribute2, attribute3}) + " in " + this.getBreadCrumbs()); + } + } + + protected void validateAnyPresent(@Nonnull String[] attributes, @Nonnull BuilderObjectHelper[] objectHelpers) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AnyPresentValidator.withAttributes(attributes)); + } else if (!AnyPresentValidator.test(objectHelpers)) { + this.addError(AnyPresentValidator.errorMessage(attributes) + " in " + this.getBreadCrumbs()); + } + } + + protected void validateOnePresent( + String attribute1, @Nonnull BuilderObjectHelper objectHelper1, String attribute2, @Nonnull BuilderObjectHelper objectHelper2 + ) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(OnePresentValidator.withAttributes(attribute1, attribute2)); + } else if (!OnePresentValidator.test(objectHelper1, objectHelper2)) { + this.addError( + OnePresentValidator.errorMessage(new String[]{attribute1, attribute2}, new BuilderObjectHelper[]{objectHelper1, objectHelper2}) + + " in " + + this.getBreadCrumbs() + ); + } + } + + protected void validateOnePresent( + String attribute1, + @Nonnull BuilderObjectHelper objectHelper1, + String attribute2, + @Nonnull BuilderObjectHelper objectHelper2, + String attribute3, + @Nonnull BuilderObjectHelper objectHelper3 + ) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(OnePresentValidator.withAttributes(attribute1, attribute2, attribute3)); + } else if (!OnePresentValidator.test(objectHelper1, objectHelper2, objectHelper3)) { + this.addError( + OnePresentValidator.errorMessage( + new String[]{attribute1, attribute2, attribute3}, new BuilderObjectHelper[]{objectHelper1, objectHelper2, objectHelper3} + ) + + " in " + + this.getBreadCrumbs() + ); + } + } + + protected void validateOnePresent(@Nonnull String[] attributes, @Nonnull BuilderObjectHelper[] objectHelpers) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(OnePresentValidator.withAttributes(attributes)); + } else if (!OnePresentValidator.test(objectHelpers)) { + this.addError(OnePresentValidator.errorMessage(attributes, objectHelpers) + " in " + this.getBreadCrumbs()); + } + } + + protected void validateOnePresent(@Nonnull String[] attributes, @Nonnull boolean[] readStatus) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(OnePresentValidator.withAttributes(attributes)); + } else if (!OnePresentValidator.test(readStatus)) { + this.addError(OnePresentValidator.errorMessage(attributes, readStatus) + " in " + this.getBreadCrumbs()); + } + } + + protected void validateOneOrNonePresent( + String attribute1, @Nonnull BuilderObjectHelper objectHelper1, String attribute2, @Nonnull BuilderObjectHelper objectHelper2 + ) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(OneOrNonePresentValidator.withAttributes(attribute1, attribute2)); + } else if (!OneOrNonePresentValidator.test(objectHelper1, objectHelper2)) { + this.addError( + OneOrNonePresentValidator.errorMessage(new String[]{attribute1, attribute2}, new BuilderObjectHelper[]{objectHelper1, objectHelper2}) + + " in " + + this.getBreadCrumbs() + ); + } + } + + protected void validateOneOrNonePresent( + String attribute1, + @Nonnull BuilderObjectHelper objectHelper1, + String attribute2, + @Nonnull BuilderObjectHelper objectHelper2, + String attribute3, + @Nonnull BuilderObjectHelper objectHelper3 + ) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(OneOrNonePresentValidator.withAttributes(attribute1, attribute2, attribute3)); + } else if (!OneOrNonePresentValidator.test(objectHelper1, objectHelper2, objectHelper3)) { + this.addError( + OneOrNonePresentValidator.errorMessage( + new String[]{attribute1, attribute2, attribute3}, new BuilderObjectHelper[]{objectHelper1, objectHelper2, objectHelper3} + ) + + " in " + + this.getBreadCrumbs() + ); + } + } + + protected void validateOneOrNonePresent(@Nonnull String[] attributes, @Nonnull BuilderObjectHelper[] objectHelpers) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(OneOrNonePresentValidator.withAttributes(attributes)); + } else if (!OneOrNonePresentValidator.test(objectHelpers)) { + this.addError(OneOrNonePresentValidator.errorMessage(attributes, objectHelpers) + " in " + this.getBreadCrumbs()); + } + } + + protected void validateOneOrNonePresent(@Nonnull String[] attributes, @Nonnull boolean[] readStatus) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(OneOrNonePresentValidator.withAttributes(attributes)); + } else if (!OneOrNonePresentValidator.test(readStatus)) { + this.addError(OneOrNonePresentValidator.errorMessage(attributes, readStatus) + " in " + this.getBreadCrumbs()); + } + } + + protected void validateExistsIfParameterSet(String parameter, boolean value, String attribute, @Nonnull BuilderObjectHelper objectHelper) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(ExistsIfParameterSetValidator.withAttributes(parameter, attribute)); + } else if (value) { + if (!objectHelper.isPresent()) { + this.addError(ExistsIfParameterSetValidator.errorMessage(parameter, attribute) + " in " + this.getBreadCrumbs()); + } + } + } + + protected & Supplier> void validateStringIfEnumIs( + @Nonnull StringHolder parameter, @Nonnull StringValidator validator, @Nonnull EnumHolder enumParameter, E targetValue + ) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(ValidateIfEnumIsValidator.withAttributes(parameter.getName(), validator, enumParameter.getName(), targetValue)); + } else if (enumParameter.isStatic()) { + this.validateStringIfEnumIs(parameter, validator, enumParameter.getName(), enumParameter.get(null), targetValue); + } else { + parameter.addRelationValidator( + (context, s) -> { + E enumValue = enumParameter.rawGet(context); + if (enumValue == targetValue) { + if (!validator.test(s)) { + throw new IllegalStateException( + validator.errorMessage(s, parameter.getName()) + + " if " + + enumParameter.getName() + + " is " + + targetValue + + " in " + + this.getBreadCrumbs() + ); + } + } + } + ); + enumParameter.addEnumRelationValidator( + (context, e) -> { + if (e == targetValue) { + String stringValue = parameter.rawGet(context); + if (!validator.test(stringValue)) { + throw new IllegalStateException( + validator.errorMessage(stringValue, parameter.getName()) + + " if " + + enumParameter.getName() + + " is " + + targetValue + + " in " + + this.getBreadCrumbs() + ); + } + } + } + ); + } + } + + protected & Supplier> void validateStringIfEnumIs( + @Nonnull StringHolder parameter, @Nonnull StringValidator validator, String enumName, E targetValue, E enumValue + ) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(ValidateIfEnumIsValidator.withAttributes(parameter.getName(), validator, enumName, targetValue)); + } else if (targetValue == enumValue) { + if (parameter.isStatic()) { + String value = parameter.get(null); + if (!validator.test(value)) { + this.addError(validator.errorMessage(value, parameter.getName()) + " if " + enumName + " is " + targetValue + " in " + this.getBreadCrumbs()); + } + } else { + parameter.addRelationValidator( + (context, s) -> { + if (!validator.test(s)) { + throw new IllegalStateException( + validator.errorMessage(s, parameter.getName()) + " if " + enumName + " is " + targetValue + " in " + this.getBreadCrumbs() + ); + } + } + ); + } + } + } + + protected & Supplier> void validateAssetIfEnumIs( + @Nonnull AssetHolder parameter, @Nonnull AssetValidator validator, @Nonnull EnumHolder enumParameter, E targetValue + ) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor + .addValidator(ValidateAssetIfEnumIsValidator.withAttributes(parameter.getName(), validator, enumParameter.getName(), targetValue)); + } else if (enumParameter.isStatic()) { + this.validateAssetIfEnumIs(parameter, validator, enumParameter.getName(), enumParameter.get(null), targetValue); + } else { + parameter.addRelationValidator( + (context, s) -> { + E enumValue = enumParameter.rawGet(context); + if (enumValue == targetValue) { + if (!validator.test(s)) { + throw new IllegalStateException( + validator.errorMessage(s, parameter.getName()) + + " if " + + enumParameter.getName() + + " is " + + targetValue + + " in " + + this.getBreadCrumbs() + ); + } + } + } + ); + enumParameter.addEnumRelationValidator( + (context, e) -> { + if (e == targetValue) { + String stringValue = parameter.rawGet(context); + if (!validator.test(stringValue)) { + throw new IllegalStateException( + validator.errorMessage(stringValue, parameter.getName()) + + " if " + + enumParameter.getName() + + " is " + + targetValue + + " in " + + this.getBreadCrumbs() + ); + } + } + } + ); + } + } + + protected & Supplier> void validateAssetIfEnumIs( + @Nonnull AssetHolder parameter, @Nonnull AssetValidator validator, String enumName, E targetValue, E enumValue + ) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(ValidateAssetIfEnumIsValidator.withAttributes(parameter.getName(), validator, enumName, targetValue)); + } else if (targetValue == enumValue) { + if (parameter.isStatic()) { + String value = parameter.get(null); + if (!validator.test(value)) { + throw new IllegalStateException( + validator.errorMessage(value, parameter.getName()) + " if " + enumName + " is " + targetValue + " in " + this.getBreadCrumbs() + ); + } + } else { + parameter.addRelationValidator( + (context, s) -> { + if (!validator.test(s)) { + throw new IllegalStateException( + validator.errorMessage(s, parameter.getName()) + " if " + enumName + " is " + targetValue + " in " + this.getBreadCrumbs() + ); + } + } + ); + } + } + } + + protected void validateAny(String attribute1, boolean value1, String attribute2, boolean value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AnyBooleanValidator.withAttributes(attribute1, attribute2)); + } else if (!value1 && !value2) { + this.addError(AnyBooleanValidator.errorMessage(new String[]{attribute1, attribute2}) + " in " + this.getBreadCrumbs()); + } + } + + protected void validateAny(@Nonnull BooleanHolder value1, @Nonnull BooleanHolder value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AnyBooleanValidator.withAttributes(value1.getName(), value2.getName())); + } else if (value1.isStatic()) { + boolean v1 = value1.get(null); + if (!v1) { + this.validateAny(value2, value1.getName(), v1); + } + } else if (value2.isStatic()) { + boolean v2 = value2.get(null); + if (!v2) { + this.validateAny(value1, value2.getName(), v2); + } + } else { + value1.addRelationValidator( + (executionContext, v1) -> { + boolean v2x = value2.rawGet(executionContext); + if (!v1 && !v2x) { + throw new IllegalStateException( + AnyBooleanValidator.errorMessage(new String[]{value1.getName(), value2.getName()}) + " in " + this.getBreadCrumbs() + ); + } + } + ); + value2.addRelationValidator( + (executionContext, v2x) -> { + boolean v1 = value1.rawGet(executionContext); + if (!v1 && !v2x) { + throw new IllegalStateException( + AnyBooleanValidator.errorMessage(new String[]{value1.getName(), value2.getName()}) + " in " + this.getBreadCrumbs() + ); + } + } + ); + } + } + + protected void validateAny(@Nonnull BooleanHolder value1, String attribute2, boolean value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AnyBooleanValidator.withAttributes(value1.getName(), attribute2)); + } else if (!value2) { + if (value1.isStatic()) { + if (value1.get(null)) { + return; + } + + this.addError(AnyBooleanValidator.errorMessage(new String[]{value1.getName(), attribute2}) + " in " + this.getBreadCrumbs()); + } + + value1.addRelationValidator((executionContext, v1) -> { + if (!v1) { + throw new IllegalStateException(AnyBooleanValidator.errorMessage(new String[]{value1.getName(), attribute2}) + " in " + this.getBreadCrumbs()); + } + }); + } + } + + protected void validateAny(String attribute1, boolean value1, String attribute2, boolean value2, String attribute3, boolean value3) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AnyBooleanValidator.withAttributes(new String[]{attribute1, attribute2, attribute3})); + } else if (!value1 && !value2 && !value3) { + this.addError(AnyBooleanValidator.errorMessage(new String[]{attribute1, attribute2, attribute3}) + " in " + this.getBreadCrumbs()); + } + } + + protected void validateAny(@Nonnull String[] attributes, @Nonnull boolean[] values) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AnyBooleanValidator.withAttributes(attributes)); + } else if (!AnyBooleanValidator.test(values)) { + this.addError(AnyBooleanValidator.errorMessage(attributes) + " in " + this.getBreadCrumbs()); + } + } + + protected void validateAtMostOne(@Nonnull BooleanHolder value1, @Nonnull BooleanHolder value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AtMostOneBooleanValidator.withAttributes(value1.getName(), value2.getName())); + } else if (value1.isStatic()) { + boolean v1 = value1.get(null); + if (v1) { + this.validateAtMostOne(value2, value1.getName(), v1); + } + } else if (value2.isStatic()) { + boolean v2 = value2.get(null); + if (v2) { + this.validateAtMostOne(value1, value2.getName(), v2); + } + } else { + value1.addRelationValidator( + (executionContext, v1) -> { + boolean v2x = value2.rawGet(executionContext); + if (v1 && v2x) { + throw new IllegalStateException( + AtMostOneBooleanValidator.errorMessage(new String[]{value1.getName(), value2.getName()}) + " in " + this.getBreadCrumbs() + ); + } + } + ); + value2.addRelationValidator( + (executionContext, v2x) -> { + boolean v1 = value1.rawGet(executionContext); + if (v1 && v2x) { + throw new IllegalStateException( + AtMostOneBooleanValidator.errorMessage(new String[]{value1.getName(), value2.getName()}) + " in " + this.getBreadCrumbs() + ); + } + } + ); + } + } + + protected void validateAtMostOne(@Nonnull BooleanHolder value1, String attribute2, boolean value2) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(AtMostOneBooleanValidator.withAttributes(value1.getName(), attribute2)); + } else if (value2) { + if (value1.isStatic() && value1.get(null)) { + this.addError(AtMostOneBooleanValidator.errorMessage(new String[]{value1.getName(), attribute2}) + " in " + this.getBreadCrumbs()); + } + + value1.addRelationValidator( + (executionContext, v1) -> { + if (v1) { + throw new IllegalStateException( + AtMostOneBooleanValidator.errorMessage(new String[]{value1.getName(), attribute2}) + " in " + this.getBreadCrumbs() + ); + } + } + ); + } + } + + protected void validateBooleanImplicationAnyAntecedent( + String[] attributes1, @Nonnull boolean[] values1, boolean antecedentState, String[] attributes2, @Nonnull boolean[] values2, boolean consequentState + ) { + this.validateBooleanImplication(attributes1, values1, antecedentState, attributes2, values2, consequentState, true); + } + + protected void validateBooleanImplicationAllAntecedents( + String[] attributes1, @Nonnull boolean[] values1, boolean antecedentState, String[] attributes2, @Nonnull boolean[] values2, boolean consequentState + ) { + this.validateBooleanImplication(attributes1, values1, antecedentState, attributes2, values2, consequentState, false); + } + + @Nonnull + protected ToIntFunction requireStringValueStoreParameter(String parameter, ValueStoreValidator.UseType useType) { + if (this.valueStoreUsages == null) { + this.valueStoreUsages = new ObjectArrayList<>(); + } + + this.valueStoreUsages.add(new ValueStoreValidator.ValueUsage(parameter, ValueStore.Type.String, useType, this)); + return support -> support.getValueStoreStringSlot(parameter); + } + + @Nonnull + protected ToIntFunction requireIntValueStoreParameter(String parameter, ValueStoreValidator.UseType useType) { + if (this.valueStoreUsages == null) { + this.valueStoreUsages = new ObjectArrayList<>(); + } + + this.valueStoreUsages.add(new ValueStoreValidator.ValueUsage(parameter, ValueStore.Type.Int, useType, this)); + return support -> support.getValueStoreIntSlot(parameter); + } + + @Nonnull + protected ToIntFunction requireDoubleValueStoreParameter(String parameter, ValueStoreValidator.UseType useType) { + if (this.valueStoreUsages == null) { + this.valueStoreUsages = new ObjectArrayList<>(); + } + + this.valueStoreUsages.add(new ValueStoreValidator.ValueUsage(parameter, ValueStore.Type.Double, useType, this)); + return support -> support.getValueStoreDoubleSlot(parameter); + } + + private void validateBooleanImplication( + String[] attributes1, + @Nonnull boolean[] values1, + boolean antecedentState, + String[] attributes2, + @Nonnull boolean[] values2, + boolean consequentState, + boolean anyAntecedent + ) { + BooleanImplicationValidator validator = BooleanImplicationValidator.withAttributes( + attributes1, antecedentState, attributes2, consequentState, anyAntecedent + ); + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(validator); + } else if (!validator.test(values1, values2)) { + this.addError(validator.errorMessage() + " in " + this.getBreadCrumbs()); + } + } + + protected void provideFeature(@Nonnull Feature feature) { + this.provideFeatureOrParameters(new UnconditionalFeatureProviderEvaluator(feature)); + } + + protected void overrideParameters(@Nonnull String[] parameters, @Nonnull ParameterType... types) { + if (this.isCreatingDescriptor() || !this.evaluatorHelper.isDisallowParameterProviders()) { + this.provideFeatureOrParameters(new UnconditionalParameterProviderEvaluator(parameters, types)); + } + } + + protected void preventParameterOverride() { + if (!this.isCreatingDescriptor()) { + this.evaluatorHelper.disallowParameterProviders(); + } + } + + private void provideFeatureOrParameters(ProviderEvaluator evaluator) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addProviderEvaluator(evaluator); + } else { + this.evaluatorHelper.add(evaluator); + } + } + + protected void provideFeature(@Nonnull EnumSet feature) { + feature.forEach(this::provideFeature); + } + + protected void requireFeature(@Nonnull EnumSet feature) { + this.requireFeature(RequiresOneOfFeaturesValidator.withFeatures(feature)); + } + + protected & Supplier> void requireFeatureIf(String enumName, E targetValue, E enumValue, @Nonnull EnumSet feature) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(RequiresFeatureIfEnumValidator.withAttributes(enumName, targetValue, feature)); + } else if (targetValue == enumValue) { + if (this.evaluatorHelper.belongsToFeatureRequiringComponent()) { + this.evaluatorHelper + .addComponentRequirementValidator( + (helper, executionContext) -> this.validateRequiresFeatureIf(enumName, targetValue, enumValue, feature, helper) + ); + } else if (!RequiresFeatureIfEnumValidator.staticValidate(this.evaluatorHelper, feature, targetValue, enumValue)) { + if (this.evaluatorHelper.requiresProviderReferenceEvaluation()) { + this.evaluatorHelper.addProviderReferenceValidator((manager, context) -> { + this.resolveFeatureProviderReverences(manager); + this.validateRequiresFeatureIf(enumName, targetValue, enumValue, feature, this.evaluatorHelper); + }); + } else { + String[] description = getDescriptionArray(feature); + this.addError( + String.format("If %s is %s, one of %s must be provided at %s", enumName, targetValue, String.join(", ", description), this.getBreadCrumbs()) + ); + } + } + } + } + + protected void requireFeatureIf(String attribute, boolean requiredValue, boolean value, @Nonnull EnumSet feature) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(RequiresFeatureIfValidator.withAttributes(attribute, requiredValue, feature)); + } else if (this.evaluatorHelper.belongsToFeatureRequiringComponent()) { + this.evaluatorHelper + .addComponentRequirementValidator((helper, executionContext) -> this.validateRequiresFeatureIf(attribute, requiredValue, value, feature, helper)); + } else if (!RequiresFeatureIfValidator.staticValidate(this.evaluatorHelper, feature, requiredValue, value)) { + if (this.evaluatorHelper.requiresProviderReferenceEvaluation()) { + this.evaluatorHelper.addProviderReferenceValidator((manager, context) -> { + this.resolveFeatureProviderReverences(manager); + this.validateRequiresFeatureIf(attribute, requiredValue, value, feature, this.evaluatorHelper); + }); + } else { + String[] description = getDescriptionArray(feature); + this.addError( + String.format( + "If %s is %s, one of %s must be provided at %s", + attribute, + requiredValue ? "set" : "not set", + String.join(", ", description), + this.getBreadCrumbs() + ) + ); + } + } + } + + protected void requireFeatureIf(@Nonnull BooleanHolder parameter, boolean requiredValue, @Nonnull EnumSet feature) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(RequiresFeatureIfValidator.withAttributes(parameter.getName(), requiredValue, feature)); + } else if (this.evaluatorHelper.belongsToFeatureRequiringComponent()) { + this.evaluatorHelper + .addComponentRequirementValidator( + (helper, executionContext) -> this.validateRequiresFeatureIf( + parameter.getName(), requiredValue, parameter.get(executionContext), feature, helper + ) + ); + } else if (!parameter.isStatic() || !RequiresFeatureIfValidator.staticValidate(this.evaluatorHelper, feature, requiredValue, parameter.get(null))) { + if (this.evaluatorHelper.requiresProviderReferenceEvaluation()) { + this.evaluatorHelper.addProviderReferenceValidator((manager, context) -> { + this.resolveFeatureProviderReverences(manager); + this.validateRequiresFeatureIf(parameter.getName(), requiredValue, parameter.rawGet(context), feature, this.evaluatorHelper); + }); + } else { + parameter.addRelationValidator( + (context, value) -> this.validateRequiresFeatureIf(parameter.getName(), requiredValue, value, feature, this.evaluatorHelper) + ); + } + } + } + + private boolean hasOverriddenParameter(String parameter, ParameterType type, @Nonnull FeatureEvaluatorHelper helper) { + for (ProviderEvaluator provider : helper.getProviders()) { + if (provider instanceof ParameterProviderEvaluator && ((ParameterProviderEvaluator)provider).hasParameter(parameter, type)) { + return true; + } + } + + return false; + } + + private void validateOverriddenParameter(String parameter, @Nonnull ParameterType type, @Nonnull FeatureEvaluatorHelper helper) { + if (!this.hasOverriddenParameter(parameter, type, helper)) { + throw new IllegalStateException( + String.format( + "Parameter %s is missing and either not provided by a sensor, or provided with the wrong parameter type (expected %s) in context %s", + parameter, + type.get(), + this.getBreadCrumbs() + ) + ); + } + } + + private & Supplier> void validateRequiresFeatureIf( + String attribute, E requiredValue, E value, @Nonnull EnumSet feature, @Nonnull FeatureEvaluatorHelper helper + ) { + if (!RequiresFeatureIfEnumValidator.staticValidate(helper, feature, requiredValue, value)) { + String[] description = getDescriptionArray(feature); + throw new IllegalStateException( + String.format("If %s is %s, one of %s must be provided at %s", attribute, requiredValue, String.join(", ", description), this.getBreadCrumbs()) + ); + } + } + + private void validateRequiresFeatureIf( + String attribute, boolean requiredValue, boolean value, @Nonnull EnumSet feature, @Nonnull FeatureEvaluatorHelper helper + ) { + if (!RequiresFeatureIfValidator.staticValidate(helper, feature, requiredValue, value)) { + String[] description = getDescriptionArray(feature); + throw new IllegalStateException( + String.format( + "If %s is %s, one of %s must be provided at %s", + attribute, + requiredValue ? "set" : "not set", + String.join(", ", description), + this.getBreadCrumbs() + ) + ); + } + } + + private void requireFeature(@Nonnull RequiredFeatureValidator validator) { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(validator); + } else if (this.evaluatorHelper.belongsToFeatureRequiringComponent()) { + this.evaluatorHelper.addComponentRequirementValidator((helper, executionContext) -> { + if (!validator.validate(helper)) { + throw new IllegalStateException(validator.getErrorMessage(this.getBreadCrumbs())); + } + }); + } else if (!validator.validate(this.evaluatorHelper)) { + if (this.evaluatorHelper.requiresProviderReferenceEvaluation()) { + this.evaluatorHelper.addProviderReferenceValidator((manager, context) -> { + this.resolveFeatureProviderReverences(manager); + if (!validator.validate(this.evaluatorHelper)) { + throw new IllegalStateException(validator.getErrorMessage(this.getBreadCrumbs())); + } + }); + } else { + this.addError(validator.getErrorMessage(this.getBreadCrumbs())); + } + } + } + + @Nonnull + public static String[] getDescriptionArray(@Nonnull EnumSet feature) { + String[] description = new String[feature.size()]; + Feature[] featureArray = feature.toArray(Feature[]::new); + + for (int i = 0; i < featureArray.length; i++) { + description[i] = featureArray[i].get(); + } + + return description; + } + + private void resolveFeatureProviderReverences(BuilderManager manager) { + for (ProviderEvaluator providerEvaluator : this.evaluatorHelper.getProviders()) { + providerEvaluator.resolveReferences(manager); + } + } + + protected void registerStateSensor(String name, String subState, @Nonnull BiConsumer setter) { + if (!this.isCreatingDescriptor()) { + this.stateHelper.getAndPutSensorIndex(name, subState, setter); + this.getParent().setCurrentStateName(name); + } + } + + protected void registerStateSetter(String name, String subState, @Nonnull BiConsumer setter) { + if (!this.isCreatingDescriptor()) { + this.stateHelper.getAndPutSetterIndex(name, subState, setter); + } + } + + protected void registerStateRequirer(String name, String subState, @Nonnull BiConsumer setter) { + if (!this.isCreatingDescriptor()) { + this.stateHelper.getAndPutStateRequirerIndex(name, subState, setter); + } + } + + protected void validateIsComponent() { + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(ComponentOnlyValidator.get()); + } else { + if (!this.isComponent()) { + this.addError(String.format("Element not valid outside of component at: %s", this.getBreadCrumbs())); + } + } + } + + protected void requireStateString( + @Nonnull JsonElement data, + String name, + boolean componentAllowed, + @Nonnull TriConsumer setter, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + StateStringValidator validator = StateStringValidator.get(); + this.requireString(data, name, v -> {}, validator, state, shortDescription, longDescription); + if (!this.isCreatingDescriptor()) { + String mainState = validator.hasMainState() ? validator.getMainState() : null; + String subState = validator.hasSubState() ? validator.getSubState() : null; + if (this.stateHelper.isComponent()) { + if (!componentAllowed) { + this.addError(String.format("Components not supported for state setter %s at %s", subState, this.getBreadCrumbs())); + } + + if (!this.stateHelper.hasDefaultLocalState()) { + this.addError("Component with local states must define a 'DefaultState' at the top of the file"); + } + + if (mainState != null) { + this.addError(String.format("Components must not contain references to main states (%s) at %s", mainState, this.getBreadCrumbs())); + } + + setter.accept(subState, null, false); + } else { + if (mainState == null) { + mainState = this.stateHelper.getCurrentParentState(); + } + + boolean isDefaultSubState = false; + if (subState == null) { + subState = this.stateHelper.getDefaultSubState(); + isDefaultSubState = true; + } + + if (mainState == null) { + this.addError(String.format("Substate %s does not have a specified main state at %s", subState, this.getBreadCrumbs())); + } + + setter.accept(mainState, subState, isDefaultSubState); + } + } + } + + protected boolean getExistentStateSet( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer> setter, + @Nonnull StateMappingHelper stateHelper, + BuilderDescriptorState state, + String shortDescription, + @Nullable String longDescription + ) { + StateStringValidator validator = StateStringValidator.requireMainState(); + if (this.isCreatingSchema()) { + ArraySchema a = new ArraySchema(); + a.setItem(new StringSchema()); + Schema s = BuilderExpressionDynamic.computableSchema(a); + s.setTitle(name); + s.setDescription(longDescription == null ? shortDescription : longDescription); + this.builderSchema.getProperties().put(name, s); + return false; + } else if (this.isCreatingDescriptor()) { + this.builderDescriptor.addAttribute(name, "StringList", state, shortDescription, longDescription).required().validator(validator); + return false; + } else { + try { + JsonElement element = this.getOptionalJsonElement(data, name); + if (element == null) { + setter.accept(null); + return false; + } else { + String[][] strings = new String[1][1]; + this.validateAndSet(this.expectStringArray(element, null, name), StringArrayNoEmptyStringsValidator.get(), s -> strings[0] = s, name); + String[] stringStates = strings[0]; + Int2ObjectOpenHashMap stateSets = new Int2ObjectOpenHashMap<>(); + + for (String stringState : stringStates) { + if (!validator.test(stringState)) { + throw new IllegalStateException(validator.errorMessage(stringState)); + } + + String subState = validator.hasSubState() ? validator.getSubState() : stateHelper.getDefaultSubState(); + stateHelper.getAndPutStateRequirerIndex( + validator.getMainState(), subState, (m, s) -> stateSets.computeIfAbsent(m, k -> new IntOpenHashSet()).add(s.intValue()) + ); + } + + stateSets.trim(); + setter.accept(stateSets); + return true; + } + } catch (Exception var18) { + this.addError(var18); + return false; + } + } + } + + protected boolean getDefaultSubState( + @Nonnull JsonElement data, + String name, + @Nonnull Consumer setter, + StringValidator validator, + BuilderDescriptorState state, + String shortDescription, + String longDescription + ) { + String[] defaultSubState = new String[1]; + boolean read = this.getString(data, name, v -> defaultSubState[0] = v, "Default", validator, state, shortDescription, longDescription); + if (!this.isCreatingDescriptor()) { + this.stateHelper.setDefaultSubState(defaultSubState[0]); + } + + setter.accept(defaultSubState[0]); + return read; + } + + protected void increaseDepth() { + if (!this.isCreatingDescriptor()) { + this.stateHelper.increaseDepth(); + } + } + + protected void decreaseDepth() { + if (!this.isCreatingDescriptor()) { + this.stateHelper.decreaseDepth(); + } + } + + protected void setNotComponent() { + if (!this.isCreatingDescriptor()) { + this.stateHelper.setNotComponent(); + } + } + + protected boolean isComponent() { + return this.isCreatingDescriptor() ? false : this.stateHelper.isComponent(); + } + + protected void requireInstructionType(@Nonnull EnumSet instructionType) { + this.requireContext(instructionType, null); + } + + protected void requireContext(@Nonnull EnumSet instructionType, EnumSet componentContexts) { + InstructionContextValidator validator = InstructionContextValidator.inInstructions(instructionType, componentContexts); + if (this.isCreatingDescriptor()) { + this.builderDescriptor.addValidator(validator); + } else if (this.instructionContextHelper.isComponent()) { + this.instructionContextHelper + .addComponentContextEvaluator( + (type, extraContext) -> { + boolean correctInstructionx = InstructionContextHelper.isInCorrectInstruction(instructionType, type); + boolean correctExtraContextx = InstructionContextHelper.extraContextMatches(componentContexts, extraContext); + if (!correctInstructionx || !correctExtraContextx) { + throw new IllegalStateException( + InstructionContextValidator.getErrorMessage( + this.getTypeName(), type, correctInstructionx, extraContext, correctExtraContextx, this.getBreadCrumbs() + ) + ); + } + } + ); + } else { + boolean correctInstruction = this.instructionContextHelper.isInCorrectInstruction(instructionType); + boolean correctExtraContext = this.instructionContextHelper.extraContextMatches(componentContexts); + if (!correctInstruction || !correctExtraContext) { + this.addError( + InstructionContextValidator.getErrorMessage( + this.getTypeName(), + this.instructionContextHelper.getInstructionContext(), + correctInstruction, + this.instructionContextHelper.getComponentContext(), + correctExtraContext, + this.getBreadCrumbs() + ) + ); + } + } + } + + @Override + public IntSet getDependencies() { + return this.builderParameters.getDependencies(); + } + + @Override + public boolean validate( + String configName, @Nonnull NPCLoadTimeValidationHelper validationHelper, ExecutionContext context, Scope globalScope, @Nonnull List errors + ) { + boolean result = true; + if (this.dynamicHolders != null) { + for (ValueHolder assetHolder : this.dynamicHolders) { + result &= this.validateDynamicHolder(configName, assetHolder, context, errors); + } + } + + ValueStoreValidator valueStoreValidator = validationHelper.getValueStoreValidator(); + if (this.valueStoreUsages != null) { + for (ValueStoreValidator.ValueUsage usage : this.valueStoreUsages) { + valueStoreValidator.registerValueUsage(usage); + } + } + + return result & this.runLoadTimeValidationHelper(configName, validationHelper, context, errors); + } + + protected void runLoadTimeValidationHelper0( + String configName, NPCLoadTimeValidationHelper loadTimeValidationHelper, ExecutionContext context, List errors + ) { + } + + private boolean runLoadTimeValidationHelper( + String configName, NPCLoadTimeValidationHelper loadTimeValidationHelper, ExecutionContext context, @Nonnull List errors + ) { + try { + this.runLoadTimeValidationHelper0(configName, loadTimeValidationHelper, context, errors); + return true; + } catch (Exception var6) { + errors.add(String.format("%s: %s", configName, var6.getMessage())); + return false; + } + } + + private boolean validateDynamicHolder(String configName, @Nonnull ValueHolder holder, ExecutionContext context, @Nonnull List errors) { + try { + holder.validate(context); + return true; + } catch (Exception var6) { + errors.add(String.format("%s: %s", configName, var6.getMessage())); + return false; + } + } + + private void trackDynamicHolder(@Nonnull ValueHolder holder) { + if (!holder.isStatic()) { + if (this.dynamicHolders == null) { + this.dynamicHolders = new ArrayList<>(); + } + + this.dynamicHolders.add(holder); + } + } + + public static String readString(@Nonnull JsonObject object, String key) { + return expectStringElement(expectKey(object, key), key); + } + + public static String readString(@Nonnull JsonObject jsonObject, String key, String defaultValue) { + JsonElement value = jsonObject.get(key); + return value == null ? defaultValue : expectStringElement(value, key); + } + + public static boolean readBoolean(@Nonnull JsonObject jsonObject, String key, boolean defaultValue) { + JsonElement value = jsonObject.get(key); + return value == null ? defaultValue : expectBooleanElement(value, key); + } + + @Nonnull + public static JsonElement expectKey(@Nonnull JsonObject jsonObject, String key) { + JsonElement value = jsonObject.get(key); + if (value == null) { + throw new IllegalStateException("'" + key + "' missing in JSON object"); + } else { + return value; + } + } + + public static String expectStringElement(@Nonnull JsonElement element, String key) { + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + return element.getAsJsonPrimitive().getAsString(); + } else { + throw new IllegalStateException("'" + key + "' must be a string"); + } + } + + public static boolean expectBooleanElement(@Nonnull JsonElement element, String key) { + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isBoolean()) { + return element.getAsJsonPrimitive().getAsBoolean(); + } else { + throw new IllegalStateException("'" + key + "' must be a boolean value"); + } + } + + public static JsonObject expectObject(@Nonnull JsonElement element) { + if (!element.isJsonObject()) { + throw new IllegalStateException("Expected a JSON object"); + } else { + return element.getAsJsonObject(); + } + } + + public static JsonObject expectObject(@Nonnull JsonElement element, String key) { + if (!element.isJsonObject()) { + throw new IllegalStateException("'" + key + "' must be an object: " + element); + } else { + return element.getAsJsonObject(); + } + } + + public static String[] readStringArray(@Nonnull JsonObject object, String key, @Nonnull StringValidator validator, String[] defaultValue) { + JsonElement value = object.get(key); + return value == null ? defaultValue : readStringArray(value, key, validator); + } + + @Nonnull + public static String[] readStringArray(@Nonnull JsonElement element, String key, @Nonnull StringValidator validator) { + if (!element.isJsonArray()) { + throw new IllegalStateException(key + " must be an array: " + element); + } else { + JsonArray array = element.getAsJsonArray(); + String[] ret = new String[array.size()]; + + for (int i = 0; i < array.size(); i++) { + String string = expectStringElement(array.get(i), String.format("%s element at position %s", key, i)); + if (!validator.test(string)) { + throw new IllegalStateException(validator.errorMessage(string)); + } + + ret[i] = string; + } + + return ret; + } + } + + protected void addError(String error) { + this.readErrors.add(this.fileName + ": " + error); + } + + protected void addError(@Nonnull Exception e) { + this.addError(e.getMessage()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderBaseWithType.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderBaseWithType.java new file mode 100644 index 0000000..ff4a08a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderBaseWithType.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.spawning.ISpawnable; +import javax.annotation.Nonnull; + +public abstract class BuilderBaseWithType extends BuilderBase implements ISpawnable { + private String type; + + public BuilderBaseWithType() { + } + + @Override + public Builder readCommonConfig(JsonElement data) { + return super.readCommonConfig(data); + } + + protected void readTypeKey(@Nonnull JsonElement data, String typeKey) { + this.requireString(data, typeKey, s -> this.type = s, StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "Type field", null); + } + + protected void readTypeKey(@Nonnull JsonElement data) { + this.readTypeKey(data, "Type"); + } + + public String getType() { + return this.type; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderCodecObjectHelper.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderCodecObjectHelper.java new file mode 100644 index 0000000..5d9efb8 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderCodecObjectHelper.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.validation.Validator; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.util.BsonUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderCodecObjectHelper { + protected final Codec codec; + protected final Class classType; + protected final Validator validator; + @Nullable + protected T value; + + public BuilderCodecObjectHelper(Class classType, Codec codec, Validator validator) { + this.classType = classType; + this.codec = codec; + this.validator = validator; + } + + @Nullable + public T build() { + return this.value; + } + + public void readConfig(@Nonnull JsonElement data, @Nonnull ExtraInfo extraInfo) { + this.value = this.codec.decode(BsonUtil.translateJsonToBson(data), extraInfo); + if (this.validator != null) { + this.validator.accept(this.value, extraInfo.getValidationResults()); + } + + extraInfo.getValidationResults()._processValidationResults(); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(HytaleLogger.getLogger()); + } + + public boolean hasValue() { + return this.value != null; + } + + public Class getClassType() { + return this.classType; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderCombatConfig.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderCombatConfig.java new file mode 100644 index 0000000..c83df9b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderCombatConfig.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.validation.Validator; +import com.hypixel.hytale.server.npc.config.balancing.BalanceAsset; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderCombatConfig extends BuilderCodecObjectHelper { + private boolean inline; + + public BuilderCombatConfig(Codec codec, Validator validator) { + super(String.class, codec, validator); + } + + public String build() { + throw new UnsupportedOperationException(); + } + + @Override + public void readConfig(@Nonnull JsonElement data, @Nonnull ExtraInfo extraInfo) { + this.inline = data.isJsonObject(); + super.readConfig(data, extraInfo); + } + + @Nullable + public String build(@Nonnull ExecutionContext context) { + String override = context.getCombatConfig(); + return override != null ? override : this.value; + } + + public boolean validate( + String configName, NPCLoadTimeValidationHelper loadTimeValidationHelper, @Nonnull ExecutionContext context, @Nonnull List errors + ) { + String override = context.getCombatConfig(); + boolean success = true; + if (override != null && BalanceAsset.getAssetMap().getAsset(override) == null) { + errors.add(String.format("%s: CombatConfig refers to a non-existent balancing file: %s", configName, override)); + success = false; + } + + return success; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderComponent.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderComponent.java new file mode 100644 index 0000000..9520db3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderComponent.java @@ -0,0 +1,98 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.BooleanSchema; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.server.npc.instructions.Action; +import com.hypixel.hytale.server.npc.instructions.Motion; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderComponent extends BuilderBase { + private final Class classType; + @Nonnull + private final BuilderObjectReferenceHelper content; + + public BuilderComponent(Class classType) { + this.classType = classType; + this.content = new BuilderObjectReferenceHelper<>(classType, null); + } + + @Nullable + @Override + public String getShortDescription() { + return null; + } + + @Nullable + @Override + public String getLongDescription() { + return null; + } + + @Override + public T build(@Nonnull BuilderSupport builderSupport) { + return this.content.build(builderSupport); + } + + @Override + public Class category() { + return this.classType; + } + + @Nullable + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return null; + } + + @Override + public boolean isEnabled(ExecutionContext context) { + return true; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireObject(data, "Content", this.content, null, null, null, this.validationHelper); + return this; + } + + @Override + public boolean validate( + String configName, + @Nonnull NPCLoadTimeValidationHelper validationHelper, + @Nonnull ExecutionContext context, + Scope globalScope, + @Nonnull List errors + ) { + return super.validate(configName, validationHelper, context, globalScope, errors) + & this.content.validate(configName, validationHelper, this.builderManager, context, globalScope, errors); + } + + @Override + public boolean canRequireFeature() { + return this.classType.isAssignableFrom(Action.class) || this.classType.isAssignableFrom(Motion.class); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ObjectSchema s = (ObjectSchema)super.toSchema(context); + Map props = s.getProperties(); + props.put("Class", new StringSchema()); + props.put("Interface", new StringSchema()); + props.put("Default", new StringSchema()); + props.put("DefaultState", new StringSchema()); + props.put("ResetOnStateChange", new BooleanSchema()); + return s; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderContext.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderContext.java new file mode 100644 index 0000000..0d78c9e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderContext.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface BuilderContext { + BuilderContext getOwner(); + + String getLabel(); + + default void setCurrentStateName(String name) { + } + + @Nullable + default Builder getParent() { + BuilderContext owner = this.getOwner(); + return owner instanceof Builder ? (Builder)owner : (owner != null ? owner.getParent() : null); + } + + default void getBreadCrumbs(@Nonnull StringBuilder stringBuilder) { + BuilderContext owner = this.getOwner(); + if (owner != null) { + owner.getBreadCrumbs(stringBuilder); + } + + String label = this.getLabel(); + if (label != null && !label.isEmpty()) { + if (!stringBuilder.isEmpty()) { + stringBuilder.append('|'); + } + + stringBuilder.append(label); + } + } + + @Nonnull + default String getBreadCrumbs() { + StringBuilder stringBuilder = new StringBuilder(80); + this.getBreadCrumbs(stringBuilder); + return stringBuilder.toString(); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderDescriptor.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderDescriptor.java new file mode 100644 index 0000000..416a35a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderDescriptor.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.ProviderEvaluator; +import com.hypixel.hytale.server.npc.asset.builder.validators.Validator; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Set; + +public class BuilderDescriptor { + private final String name; + private final String category; + private final BuilderDescriptorState state; + private final String shortDescription; + private final String longDescription; + private final List attributes = new ObjectArrayList<>(); + private final List validators = new ObjectArrayList<>(); + private final List providerEvaluators = new ObjectArrayList<>(); + private final Set tags; + + public BuilderDescriptor(String name, String category, String shortDescription, String longDescription, Set tags, BuilderDescriptorState state) { + this.name = name; + this.category = category; + this.shortDescription = shortDescription; + this.longDescription = longDescription; + this.state = state; + this.tags = tags; + } + + public BuilderAttributeDescriptor addAttribute(BuilderAttributeDescriptor attributeDescriptor) { + this.attributes.add(attributeDescriptor); + return attributeDescriptor; + } + + public BuilderAttributeDescriptor addAttribute(String name, String type, BuilderDescriptorState state, String shortDescription, String longDescription) { + return this.addAttribute(new BuilderAttributeDescriptor(name, type, state, shortDescription, longDescription)); + } + + public void addValidator(Validator validator) { + this.validators.add(validator); + } + + public void addProviderEvaluator(ProviderEvaluator providerEvaluator) { + this.providerEvaluators.add(providerEvaluator); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderDescriptorState.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderDescriptorState.java new file mode 100644 index 0000000..14c16e8 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderDescriptorState.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +public enum BuilderDescriptorState { + Unknown, + WorkInProgress, + Experimental, + Stable, + Deprecated; + + private BuilderDescriptorState() { + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderFactory.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderFactory.java new file mode 100644 index 0000000..29968ad --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderFactory.java @@ -0,0 +1,190 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.hypixel.hytale.codec.schema.NamedSchema; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.SchemaConvertable; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderFactory implements SchemaConvertable, NamedSchema { + public static final String DEFAULT_TYPE = "Type"; + public static final String COMPONENT_TYPE = "Component"; + private final String typeTag; + private final Supplier> defaultBuilder; + private final Class category; + private final Map>> buildersSuppliers = new HashMap<>(); + + public BuilderFactory(Class category, String typeTag) { + this(category, typeTag, null); + } + + public BuilderFactory(Class category, String typeTag, Supplier> defaultBuilder) { + this.category = category; + this.typeTag = typeTag; + this.defaultBuilder = defaultBuilder; + this.add("Component", () -> new BuilderComponent<>(category)); + } + + @Nonnull + public BuilderFactory add(String name, Supplier> builder) { + if (this.buildersSuppliers.containsKey(name)) { + throw new IllegalArgumentException(String.format("Builder with name %s already exists", name)); + } else if (this.typeTag.isEmpty()) { + throw new IllegalArgumentException("Can't add named builder to array builder factory"); + } else { + this.buildersSuppliers.put(name, builder); + return this; + } + } + + public Class getCategory() { + return this.category; + } + + public Builder createBuilder(@Nonnull JsonElement config) { + if (!config.isJsonObject()) { + if (this.defaultBuilder == null) { + throw new IllegalArgumentException(String.format("Array builder must have default builder defined: %s", config)); + } else { + return this.defaultBuilder.get(); + } + } else { + return this.createBuilder(config.getAsJsonObject(), this.typeTag); + } + } + + public String getKeyName(@Nonnull JsonElement config) { + if (!config.isJsonObject()) { + return "-"; + } else { + JsonElement element = config.getAsJsonObject().get(this.typeTag); + return element != null ? element.getAsString() : "???"; + } + } + + @Nonnull + public Builder createBuilder(String name) { + if (!this.buildersSuppliers.containsKey(name)) { + throw new IllegalArgumentException(String.format("Builder %s does not exist", name)); + } else { + Builder builder = this.buildersSuppliers.get(name).get(); + if (builder.category() != this.getCategory()) { + throw new IllegalArgumentException( + String.format("Builder %s has category %s which does not match %s", name, builder.category().getName(), this.getCategory().getName()) + ); + } else { + builder.setTypeName(name); + return builder; + } + } + } + + @Nullable + public Builder tryCreateDefaultBuilder() { + return this.defaultBuilder != null ? this.defaultBuilder.get() : null; + } + + @Nonnull + public List getBuilderNames() { + return new ObjectArrayList<>(this.buildersSuppliers.keySet()); + } + + private Builder createBuilder(@Nonnull JsonObject config, @Nonnull String tag) { + if (config == null) { + throw new IllegalArgumentException("JSON config cannot be null when creating builder"); + } else if (tag != null && !tag.trim().isEmpty()) { + JsonElement element = config.get(tag); + if (element == null && this.defaultBuilder != null) { + return this.defaultBuilder.get(); + } else if (element == null) { + throw new IllegalArgumentException(String.format("Builder tag of type %s must be supplied if no default is defined in %s", tag, config)); + } else { + return this.createBuilder(element.getAsString()); + } + } else { + throw new IllegalArgumentException(String.format("Tag cannot be null or empty when creating builder with content %s", config)); + } + } + + @Nonnull + @Override + public String getSchemaName() { + return "NPCType:" + this.getCategory().getSimpleName(); + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + return this.toSchema(context, false); + } + + @Nonnull + public Schema toSchema(@Nonnull SchemaContext context, boolean isRoot) { + int index = 0; + Schema[] schemas = new Schema[this.getBuilderNames().size()]; + ObjectSchema check = new ObjectSchema(); + check.setRequired(this.typeTag); + StringSchema keys = new StringSchema(); + keys.setEnum(this.getBuilderNames().toArray(String[]::new)); + check.setProperties(Map.of(this.typeTag, keys)); + Schema root = new Schema(); + if (this.defaultBuilder == null && this.getBuilderNames().isEmpty()) { + root.setAnyOf(schemas); + } else { + root.setIf(check); + root.setThen(Schema.anyOf(schemas)); + } + + for (String builderName : this.getBuilderNames()) { + Builder builder = this.createBuilder(builderName); + Schema schemaRef = context.refDefinition(builder); + ObjectSchema schema = (ObjectSchema)context.getRawDefinition(builder); + LinkedHashMap newProps = new LinkedHashMap<>(); + Schema type = StringSchema.constant(builderName); + if (builder instanceof BuilderBase) { + type.setDescription(((BuilderBase)builder).getLongDescription()); + } + + newProps.put(this.typeTag, type); + if (isRoot) { + newProps.put("TestType", new StringSchema()); + newProps.put("FailReason", new StringSchema()); + newProps.put("Parameters", BuilderParameters.toSchema(context)); + } + + newProps.putAll(schema.getProperties()); + schema.setProperties(newProps); + Schema cond = new Schema(); + ObjectSchema checkType = new ObjectSchema(); + checkType.setProperties(Map.of(this.typeTag, StringSchema.constant(builderName))); + checkType.setRequired(this.typeTag); + cond.setIf(checkType); + cond.setThen(schemaRef); + cond.setElse(false); + schemas[index++] = cond; + } + + if (this.defaultBuilder != null) { + Builder builderx = this.defaultBuilder.get(); + Schema schemaRefx = context.refDefinition(builderx); + root.setElse(schemaRefx); + } else { + root.setElse(false); + } + + root.setHytaleSchemaTypeField(new Schema.SchemaTypeField(this.typeTag, null, this.getBuilderNames().toArray(String[]::new))); + root.setTitle(this.getCategory().getSimpleName()); + return root; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderInfo.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderInfo.java new file mode 100644 index 0000000..3661323 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderInfo.java @@ -0,0 +1,91 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import java.nio.file.Path; + +public class BuilderInfo { + private final String keyName; + private final int index; + private final Builder builder; + private final Path path; + private BuilderInfo.State status; + + public BuilderInfo(int index, String keyName, Builder builder, Path path) { + this.index = index; + this.keyName = keyName; + this.builder = builder; + this.path = path; + this.status = BuilderInfo.State.NEEDS_VALIDATION; + } + + public int getIndex() { + return this.index; + } + + public String getKeyName() { + return this.keyName; + } + + public Builder getBuilder() { + return this.builder; + } + + public Path getPath() { + return this.path; + } + + public boolean isValidated() { + return this.status == BuilderInfo.State.VALID || this.status == BuilderInfo.State.INVALID; + } + + public boolean isValid() { + return this.status == BuilderInfo.State.VALID; + } + + public boolean setValidated(boolean success) { + this.status = success ? BuilderInfo.State.VALID : BuilderInfo.State.INVALID; + return success; + } + + public void setForceValidation() { + this.status = BuilderInfo.State.NEEDS_VALIDATION; + } + + public void setNeedsValidation() { + if (this.status != BuilderInfo.State.REMOVED) { + this.status = BuilderInfo.State.NEEDS_VALIDATION; + } + } + + public void setNeedsReload() { + if (this.status != BuilderInfo.State.REMOVED) { + this.status = BuilderInfo.State.NEEDS_RELOAD; + } + } + + public boolean canBeValidated() { + return this.status != BuilderInfo.State.NEEDS_RELOAD && this.status != BuilderInfo.State.REMOVED; + } + + public boolean needsValidation() { + return this.status == BuilderInfo.State.NEEDS_VALIDATION; + } + + public void setRemoved() { + this.status = BuilderInfo.State.REMOVED; + } + + public boolean isRemoved() { + return this.status == BuilderInfo.State.REMOVED; + } + + protected static enum State { + NEEDS_RELOAD, + NEEDS_VALIDATION, + VALID, + INVALID, + REMOVED; + + private State() { + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderManager.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderManager.java new file mode 100644 index 0000000..1c6bc99 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderManager.java @@ -0,0 +1,1239 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; +import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap; +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetPack; +import com.hypixel.hytale.assetstore.map.CaseInsensitiveHashStrategy; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.event.IEventDispatcher; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.protocol.packets.interface_.NotificationStyle; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.AssetModule; +import com.hypixel.hytale.server.core.asset.monitor.AssetMonitor; +import com.hypixel.hytale.server.core.asset.monitor.AssetMonitorHandler; +import com.hypixel.hytale.server.core.asset.monitor.EventKind; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.util.NotificationUtil; +import com.hypixel.hytale.server.core.util.io.FileUtil; +import com.hypixel.hytale.server.npc.AllNPCsLoadedEvent; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.ProviderEvaluatorTypeRegistry; +import com.hypixel.hytale.server.npc.asset.builder.validators.ValidatorTypeRegistry; +import com.hypixel.hytale.server.npc.decisionmaker.core.Evaluator; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.StdLib; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import com.hypixel.hytale.server.spawning.LoadedNPCEvent; +import com.hypixel.hytale.sneakythrow.SneakyThrow; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntIterator; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntMaps; +import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderManager { + public static final String CONTENT_KEY = "Content"; + private static final String CLASS_KEY = "Class"; + private static final String TEST_TYPE_KEY = "TestType"; + private static final String FAIL_REASON_KEY = "FailReason"; + private static final String PLAYER_GROUP_TAG = "$player"; + private static final String SELF_GROUP_TAG = "$self"; + private static int playerGroupID; + private static int selfGroupID; + private final Int2ObjectConcurrentHashMap builderCache = new Int2ObjectConcurrentHashMap<>(); + private final String elementTypeName = "NPC"; + private final String defaultFileType = NPCPlugin.FACTORY_CLASS_ROLE; + private boolean autoReload; + private final Map, BuilderFactory> factoryMap = new HashMap<>(); + private final Map> categoryNames = new HashMap<>(); + @Nonnull + private final Object2IntMap nameToIndexMap; + private final AtomicInteger nextIndex = new AtomicInteger(); + private final ReentrantReadWriteLock indexLock = new ReentrantReadWriteLock(); + private boolean setup; + @Nullable + public static BuilderManager SCHEMA_BUILDER_MANAGER; + + public BuilderManager() { + this.nameToIndexMap = new Object2IntOpenCustomHashMap<>(CaseInsensitiveHashStrategy.getInstance()); + this.nameToIndexMap.defaultReturnValue(Integer.MIN_VALUE); + playerGroupID = this.getOrCreateIndex("$player"); + selfGroupID = this.getOrCreateIndex("$self"); + } + + public void registerFactory(@Nonnull BuilderFactory factory) { + if (factory == null) { + throw new IllegalArgumentException(); + } else { + Class clazz = factory.getCategory(); + if (clazz == null) { + throw new IllegalArgumentException(); + } else if (this.factoryMap.containsKey(clazz)) { + throw new IllegalArgumentException(factory.getClass().getSimpleName()); + } else { + this.factoryMap.put(clazz, factory); + } + } + } + + public void addCategory(String name, Class clazz) { + this.categoryNames.put(name, clazz); + } + + public String getCategoryName(@Nonnull Class factoryClass) { + for (Entry> stringClassEntry : this.categoryNames.entrySet()) { + if (stringClassEntry.getValue() == factoryClass) { + return stringClassEntry.getKey(); + } + } + + return factoryClass.getSimpleName(); + } + + public int getIndex(@Nullable String name) { + if (name != null && !name.isEmpty()) { + this.indexLock.readLock().lock(); + + int var2; + try { + var2 = this.nameToIndexMap.getInt(name); + } finally { + this.indexLock.readLock().unlock(); + } + + return var2; + } else { + return Integer.MIN_VALUE; + } + } + + public void setAutoReload(boolean autoReload) { + this.autoReload = autoReload; + } + + @Nullable + public String lookupName(int index) { + if (index < 0) { + return null; + } else { + BuilderInfo info = this.builderCache.get(index); + if (info != null) { + return info.getKeyName(); + } else { + this.indexLock.readLock().lock(); + + String var5; + try { + ObjectIterator> iterator = Object2IntMaps.fastIterator(this.nameToIndexMap); + + it.unimi.dsi.fastutil.objects.Object2IntMap.Entry entry; + do { + if (!iterator.hasNext()) { + return null; + } + + entry = iterator.next(); + } while (entry.getIntValue() != index); + + var5 = entry.getKey(); + } finally { + this.indexLock.readLock().unlock(); + } + + return var5; + } + } + } + + public int getOrCreateIndex(String name) { + this.indexLock.writeLock().lock(); + + int var3; + try { + int index = this.nameToIndexMap.getInt(name); + if (index < 0) { + index = this.nextIndex.getAndIncrement(); + this.nameToIndexMap.put(name, index); + return index; + } + + var3 = index; + } finally { + this.indexLock.writeLock().unlock(); + } + + return var3; + } + + @Nullable + public BuilderInfo tryGetBuilderInfo(int builderIndex) { + return builderIndex < 0 ? null : this.builderCache.get(builderIndex); + } + + public void unloadBuilders(AssetPack pack) { + Path path = pack.getRoot().resolve(NPCPlugin.ROLE_ASSETS_PATH); + AssetMonitor assetMonitor = AssetModule.get().getAssetMonitor(); + if (assetMonitor != null) { + assetMonitor.removeMonitorDirectoryFiles(path, pack); + } + + if (Files.isDirectory(path)) { + try { + Files.walkFileTree(path, FileUtil.DEFAULT_WALK_TREE_OPTIONS_SET, Integer.MAX_VALUE, new SimpleFileVisitor() { + @Nonnull + public FileVisitResult visitFile(@Nonnull Path file, @Nonnull BasicFileAttributes attrs) { + if (BuilderManager.isJsonFile(file) && !BuilderManager.isIgnoredFile(file)) { + String builderName = BuilderManager.builderNameFromPath(file); + BuilderManager.this.removeBuilder(builderName); + NPCPlugin.get().getLogger().at(Level.INFO).log("Deleted %s builder %s", "NPC", builderName); + } + + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException var5) { + throw SneakyThrow.sneakyThrow(var5); + } + } + } + + public boolean loadBuilders(@Nonnull AssetPack pack, final boolean includeTests) { + Path path = pack.getRoot().resolve(NPCPlugin.ROLE_ASSETS_PATH); + boolean valid = true; + NPCPlugin.get().getLogger().at(Level.INFO).log("Starting to load NPC builders!"); + final Object2IntOpenHashMap typeCounter = new Object2IntOpenHashMap<>(); + + try { + AssetMonitor assetMonitor = AssetModule.get().getAssetMonitor(); + if (assetMonitor != null && !pack.isImmutable() && Files.isDirectory(path)) { + assetMonitor.removeMonitorDirectoryFiles(path, pack); + assetMonitor.monitorDirectoryFiles(path, new BuilderManager.BuilderAssetMonitorHandler(pack, includeTests)); + } + + final ObjectArrayList errors = new ObjectArrayList<>(); + if (Files.isDirectory(path)) { + Files.walkFileTree(path, FileUtil.DEFAULT_WALK_TREE_OPTIONS_SET, Integer.MAX_VALUE, new SimpleFileVisitor() { + @Nonnull + public FileVisitResult visitFile(@Nonnull Path file, @Nonnull BasicFileAttributes attrs) { + if (BuilderManager.isJsonFile(file) && !BuilderManager.isIgnoredFile(file)) { + BuilderManager.this.loadFile(file, errors, typeCounter, includeTests, false); + } + + return FileVisitResult.CONTINUE; + } + }); + } + + Int2ObjectOpenHashMap loadedBuilders = new Int2ObjectOpenHashMap<>(); + + for (BuilderInfo builderInfo : this.builderCache.values()) { + try { + if (this.validateBuilder(builderInfo)) { + loadedBuilders.put(builderInfo.getIndex(), builderInfo); + } else { + valid = false; + } + } catch (IllegalStateException | IllegalArgumentException var12) { + valid = false; + errors.add(String.format("%s: %s", builderInfo.getKeyName(), var12.getMessage())); + } + } + + this.setup = true; + this.validateAllLoadedBuilders(loadedBuilders, false, errors); + if (!errors.isEmpty()) { + valid = false; + + for (String error : errors) { + NPCPlugin.get().getLogger().at(Level.SEVERE).log("FAIL: " + error); + } + } + + errors.clear(); + this.onAllBuildersLoaded(loadedBuilders); + } catch (IOException var13) { + throw new SkipSentryException(new RuntimeException(var13)); + } + + StringBuilder output = new StringBuilder(); + output.append("Loaded ").append(this.builderCache.size()).append(" ").append("NPC").append(" configurations"); + + for (it.unimi.dsi.fastutil.objects.Object2IntMap.Entry entry : typeCounter.object2IntEntrySet()) { + output.append(", ").append(entry.getKey()).append(": ").append(entry.getIntValue()); + } + + NPCPlugin.get().getLogger().at(Level.INFO).log(output.toString()); + return valid; + } + + private void finishLoadingBuilders(@Nonnull Int2ObjectOpenHashMap loadedBuilders, @Nonnull List errors) { + this.onAllBuildersLoaded(loadedBuilders); + this.validateAllLoadedBuilders(loadedBuilders, true, errors); + if (!errors.isEmpty()) { + for (String error : errors) { + NPCPlugin.get().getLogger().at(Level.SEVERE).log(error); + } + } + + errors.clear(); + } + + public void assetEditorLoadFile(@Nonnull Path fileName) { + HashSet failedBuilderTexts = new HashSet<>(); + ObjectArrayList errors = new ObjectArrayList<>(); + Int2ObjectOpenHashMap loadedBuilders = new Int2ObjectOpenHashMap<>(); + HashSet loadedBuilderNames = new HashSet<>(); + + try { + int builderIndex = this.loadFile(fileName, errors, null, true, true); + if (builderIndex < 0) { + return; + } + + String name = builderNameFromPath(fileName); + NPCPlugin.get().getLogger().at(Level.INFO).log("Reloaded NPC builder " + name); + loadedBuilderNames.add(name); + + for (BuilderInfo builderInfo : this.builderCache.values()) { + if (this.isDependant(builderInfo.getBuilder(), builderInfo.getIndex(), builderIndex)) { + builderInfo.setNeedsValidation(); + } + } + + if (this.autoReload) { + this.reloadDependants(builderIndex); + } + + BuilderInfo builder = this.builderCache.get(builderIndex); + onBuilderReloaded(builder); + loadedBuilders.put(builderIndex, builder); + } catch (Throwable var10) { + NPCPlugin.get().getLogger().at(Level.SEVERE).log("Failed to reload %s config %s: %s", "NPC", fileName, var10.getMessage()); + failedBuilderTexts.add(builderNameFromPath(fileName) + ": " + var10.getMessage()); + } + + sendReloadNotification(Message.translation("server.general.assetstore.reloadAssets").param("class", "NPC"), loadedBuilderNames); + sendReloadNotification(Message.translation("server.general.assetstore.loadFailed").param("class", "NPC"), failedBuilderTexts); + this.finishLoadingBuilders(loadedBuilders, errors); + } + + public void assetEditorRemoveFile(@Nonnull Path filePath) { + String builderName = builderNameFromPath(filePath); + this.removeBuilder(builderName); + NPCPlugin.get().getLogger().at(Level.INFO).log("Deleted %s builder %s", "NPC", builderName); + sendReloadNotification(Message.translation("server.general.assetstore.removedAssets").param("class", "NPC"), Set.of(builderName)); + ObjectArrayList errors = new ObjectArrayList<>(); + this.finishLoadingBuilders(new Int2ObjectOpenHashMap<>(), errors); + } + + public int loadFile(@Nonnull Path fileName, boolean reloading, @Nonnull List errors) { + return this.loadFile(fileName, errors, null, false, reloading); + } + + public int loadFile( + @Nonnull Path fileName, @Nonnull List errors, @Nullable Object2IntMap typeCounter, boolean includeTests, boolean reloading + ) { + int errorCount = errors.size(); + + JsonObject data; + try ( + BufferedReader fileReader = Files.newBufferedReader(fileName); + JsonReader reader = new JsonReader(fileReader); + ) { + data = JsonParser.parseReader(reader).getAsJsonObject(); + } catch (Exception var38) { + errors.add(fileName + ": Failed to load NPC builder: " + var38.getMessage()); + return Integer.MIN_VALUE; + } + + String categoryName = this.defaultFileType; + JsonElement content = data; + BuilderManager.TestType testType = null; + JsonElement testTypeElement = data.get("TestType"); + if (testTypeElement != null) { + try { + testType = Enum.valueOf(BuilderManager.TestType.class, testTypeElement.getAsString().toUpperCase()); + } catch (Exception var35) { + errors.add(fileName + ": " + var35.getMessage()); + } + + if (!includeTests) { + return Integer.MIN_VALUE; + } + } + + String keyName = builderNameFromPath(fileName); + String componentInterface = null; + StateMappingHelper stateHelper = new StateMappingHelper(); + JsonElement classData = data.get("Class"); + if (classData != null) { + categoryName = classData.getAsString(); + stateHelper.readComponentDefaultLocalState(data); + JsonElement interfaceData = data.get("Interface"); + if (interfaceData != null) { + componentInterface = interfaceData.getAsString(); + } + } + + Class category = this.categoryNames.get(categoryName); + if (category == null) { + errors.add(fileName + ": Failed to load NPC builder, unknown class " + categoryName); + return Integer.MIN_VALUE; + } else { + if (typeCounter != null) { + JsonElement type = data.get("Type"); + String typeString = testType == null ? (type != null ? type.getAsString() : categoryName) : "Test"; + typeCounter.mergeInt(typeString, 1, Integer::sum); + } + + BuilderFactory factory = this.getFactory(category); + + Builder builder; + try { + builder = factory.createBuilder(content); + } catch (Exception var34) { + errors.add(fileName + ": " + var34.getMessage()); + return Integer.MIN_VALUE; + } + + String fileNameString = fileName.toString(); + this.checkIfDeprecated(builder, factory, data, fileNameString, categoryName); + builder.setLabel(categoryName + "|" + factory.getKeyName(data)); + builder.ignoreAttribute("TestType"); + if (testType == BuilderManager.TestType.FAILING) { + builder.ignoreAttribute("FailReason"); + } + + builder.ignoreAttribute("Parameters"); + BuilderParameters builderParameters = new BuilderParameters(StdLib.getInstance(), fileNameString, componentInterface); + + try { + builderParameters.readJSON(data, stateHelper); + } catch (Exception var33) { + errors.add(fileNameString + ": Failed to load NPC builder, 'Parameters' section invalid: " + var33.getMessage()); + return Integer.MIN_VALUE; + } + + if (classData != null) { + builder.ignoreAttribute("Class"); + builder.ignoreAttribute("Interface"); + builder.ignoreAttribute("DefaultState"); + builder.ignoreAttribute("ResetOnStateChange"); + } + + builderParameters.addParametersToScope(); + InternalReferenceResolver internalReferenceResolver = new InternalReferenceResolver(); + AssetExtraInfo.Data extraInfoData = new AssetExtraInfo.Data(null, keyName, null); + AssetExtraInfo extraInfo = new AssetExtraInfo<>(extraInfoData); + ObjectArrayList> evaluators = new ObjectArrayList<>(); + BuilderValidationHelper validationHelper = new BuilderValidationHelper( + fileNameString, + new FeatureEvaluatorHelper(builder.canRequireFeature()), + internalReferenceResolver, + stateHelper, + new InstructionContextHelper(InstructionType.Component), + extraInfo, + evaluators, + errors + ); + + try { + builder.readConfig(null, content, this, builderParameters, validationHelper); + } catch (Exception var32) { + errors.add(fileNameString + ": Failed to load NPC: " + var32.getMessage()); + return Integer.MIN_VALUE; + } + + internalReferenceResolver.validateInternalReferences(fileNameString, errors); + extraInfoData.loadContainedAssets(reloading); + + for (Evaluator evaluator : evaluators) { + evaluator.initialise(); + } + + internalReferenceResolver.optimise(); + builderParameters.disposeCompileContext(); + stateHelper.validate(fileNameString, errors); + stateHelper.optimise(); + BuilderInfo entry = this.tryGetBuilderInfo(this.getIndex(keyName)); + if (entry != null && entry.getPath() != null) { + try { + if (!Files.isSameFile(fileName, entry.getPath())) { + NPCPlugin.get() + .getLogger() + .at(Level.WARNING) + .log("Replacing asset '%s' of file '%s' with other file '%s'", keyName, entry.getPath(), fileName); + } + } catch (IOException var31) { + } + } + + if (testType == BuilderManager.TestType.FAILING) { + JsonElement failReasonElement = data.get("FailReason"); + if (failReasonElement == null) { + errors.add(fileName + ": Missing fail reason!"); + return Integer.MIN_VALUE; + } else if (errors.size() == errorCount) { + errors.add(fileName + ": Should have failed validation: " + failReasonElement.getAsString()); + return Integer.MIN_VALUE; + } else if (errors.size() - errorCount > 1) { + errors.add(fileName + ": Should have failed validation: " + failReasonElement.getAsString() + ", but additional errors were also detected."); + return Integer.MIN_VALUE; + } else { + String error = errors.removeLast(); + if (!error.contains(failReasonElement.getAsString())) { + errors.add(fileName + ": Should have failed validation: " + failReasonElement.getAsString() + ", but was instead: " + error); + return Integer.MIN_VALUE; + } else { + if (NPCPlugin.get().isLogFailingTestErrors()) { + NPCPlugin.get().getLogger().at(Level.WARNING).log("Expected test failure: " + error); + } + + return Integer.MIN_VALUE; + } + } + } else { + return errors.size() > errorCount ? Integer.MIN_VALUE : this.cacheBuilder(keyName, builder, fileName); + } + } + } + + public boolean validateBuilder(@Nonnull BuilderInfo builderInfo) { + if (builderInfo.isValidated()) { + return builderInfo.isValid(); + } else if (!builderInfo.canBeValidated()) { + return false; + } else { + Builder builder = builderInfo.getBuilder(); + return builder.getDependencies().isEmpty() && !builder.hasDynamicDependencies() + ? builderInfo.setValidated(true) + : this.validateBuilder(builderInfo, new IntOpenHashSet(), new IntArrayList()); + } + } + + @Nonnull + public BuilderFactory getFactory(@Nonnull Class clazz) { + if (clazz == null) { + throw new IllegalArgumentException("No factory class supplied!"); + } else { + BuilderFactory factory = (BuilderFactory)this.factoryMap.get(clazz); + if (factory == null) { + throw new NullPointerException(String.format("Factory for type '%s' is not registered!", clazz.getSimpleName())); + } else if (factory.getCategory() != clazz) { + throw new IllegalArgumentException( + String.format("Factory class mismatch! Expected %s, was %s", clazz.getSimpleName(), factory.getCategory().getSimpleName()) + ); + } else { + return factory; + } + } + } + + @Nonnull + public BuilderInfo getCachedBuilderInfo(int index, @Nonnull Class classType) { + if (index < 0) { + throw new SkipSentryException(new IllegalArgumentException("Builder asset can't have negative index " + index)); + } else { + BuilderInfo builderInfo = this.tryGetCachedBuilderInfo(index, classType); + if (builderInfo == null) { + throw new SkipSentryException(new IllegalArgumentException(String.format("Asset '%s' (%s) is not available", this.lookupName(index), index))); + } else { + return builderInfo; + } + } + } + + @Nullable + public Builder tryGetCachedValidBuilder(int index, @Nonnull Class classType) { + BuilderInfo builderInfo = this.tryGetCachedBuilderInfo(index, classType); + return (Builder)(builderInfo != null && builderInfo.isValid() ? builderInfo.getBuilder() : null); + } + + public Builder getCachedBuilder(int index, @Nonnull Class classType) { + BuilderInfo builderInfo = this.getCachedBuilderInfo(index, classType); + return (Builder)builderInfo.getBuilder(); + } + + public boolean isEmpty() { + return this.builderCache.isEmpty(); + } + + @Nonnull + public Int2ObjectMap getAllBuilders() { + Int2ObjectOpenHashMap builders = new Int2ObjectOpenHashMap<>(); + + for (BuilderInfo builder : this.builderCache.values()) { + builders.put(builder.getIndex(), builder); + } + + return builders; + } + + public > T collectMatchingBuilders( + T collection, @Nonnull Predicate filter, @Nonnull BiConsumer consumer + ) { + for (BuilderInfo builderInfo : this.builderCache.values()) { + if (filter.test(builderInfo)) { + consumer.accept(builderInfo, collection); + } + } + + return collection; + } + + @Nonnull + public Object2IntMap getNameToIndexMap() { + Object2IntOpenHashMap map = new Object2IntOpenHashMap<>(); + if (!this.setup) { + return Object2IntMaps.unmodifiable(map); + } else { + this.indexLock.readLock().lock(); + + try { + ObjectIterator> iterator = Object2IntMaps.fastIterator(this.nameToIndexMap); + + while (iterator.hasNext()) { + it.unimi.dsi.fastutil.objects.Object2IntMap.Entry next = iterator.next(); + map.put(next.getKey(), next.getIntValue()); + } + } finally { + this.indexLock.readLock().unlock(); + } + + return Object2IntMaps.unmodifiable(map); + } + } + + @Nullable + public BuilderInfo findMatchingBuilder(@Nonnull BiPredicate filter, T t) { + for (BuilderInfo builderInfo : this.builderCache.values()) { + if (filter.test(builderInfo, t)) { + return builderInfo; + } + } + + return null; + } + + @Nullable + public BuilderInfo getBuilderInfo(Builder builder) { + return this.findMatchingBuilder((builderInfo, b) -> builderInfo.getBuilder() == b, builder); + } + + public List getTemplateNames() { + return this.collectMatchingBuilders(new ObjectArrayList<>(), builderInfo -> true, (builderInfo, strings) -> strings.add(builderInfo.getKeyName())); + } + + public void forceValidation(int builderIndex) { + BuilderInfo builderInfo = this.tryGetBuilderInfo(builderIndex); + if (builderInfo != null) { + IntSet dependencies = this.computeAllDependencies(builderInfo.getBuilder(), builderInfo.getIndex()); + builderInfo.setForceValidation(); + IntIterator i = dependencies.iterator(); + + while (i.hasNext()) { + builderInfo = this.tryGetBuilderInfo(i.nextInt()); + if (builderInfo != null) { + builderInfo.setForceValidation(); + } + } + } + } + + public void checkIfDeprecated( + @Nonnull Builder builder, @Nonnull BuilderFactory builderFactory, @Nonnull JsonElement element, String fileName, String context + ) { + if (builder.isDeprecated()) { + NPCPlugin.get() + .getLogger() + .at(Level.WARNING) + .log( + "Builder %s of type %s is deprecated and should be replaced in %s: %s", + builderFactory.getKeyName(element), + this.getCategoryName(builderFactory.getCategory()), + context, + fileName + ); + } + } + + @Nonnull + public Schema generateSchema(@Nonnull SchemaContext context) { + Schema var18; + try { + SCHEMA_BUILDER_MANAGER = this; + BuilderFactory roleFactory = this.factoryMap.get(Role.class); + Schema schema = roleFactory.toSchema(context, true); + ObjectSchema check = new ObjectSchema(); + check.setRequired("Class", "Type"); + StringSchema keys = new StringSchema(); + keys.setEnum(this.categoryNames.keySet().toArray(String[]::new)); + check.setProperties(Map.of("Class", keys)); + check.setProperties(Map.of("Type", StringSchema.constant("Component"))); + Schema dynamicComponent = new Schema(); + dynamicComponent.setIf(check); + Schema[] subSchemas = new Schema[this.categoryNames.size()]; + int index = 0; + + for (Entry> cats : this.categoryNames.entrySet()) { + BuilderFactory factory = this.getFactory(cats.getValue()); + Schema s = factory.toSchema(context, true); + Schema cond = new Schema(); + ObjectSchema classCheck = new ObjectSchema(); + classCheck.setProperties(Map.of("Class", StringSchema.constant(cats.getKey()))); + cond.setIf(classCheck); + cond.setThen(s); + cond.setElse(false); + subSchemas[index++] = cond; + } + + dynamicComponent.setThen(Schema.anyOf(subSchemas)); + dynamicComponent.setElse(false); + schema.getThen().setAnyOf(ArrayUtil.append(schema.getThen().getAnyOf(), dynamicComponent)); + var18 = schema; + } finally { + SCHEMA_BUILDER_MANAGER = null; + } + + return var18; + } + + @Nonnull + public List generateDescriptors() { + ObjectArrayList builderDescriptors = new ObjectArrayList<>(); + + for (BuilderFactory builderFactory : this.factoryMap.values()) { + String categoryName = this.getCategoryName(builderFactory.getCategory()); + Builder defaultBuilder = builderFactory.tryCreateDefaultBuilder(); + if (defaultBuilder != null) { + try { + builderDescriptors.add(defaultBuilder.getDescriptor(categoryName, categoryName, this)); + } catch (NullPointerException | IllegalStateException var11) { + NPCPlugin.get().getLogger().at(Level.SEVERE).log("Failed to build descriptor for %s %s: %s", categoryName, categoryName, var11.getMessage()); + } + } + + for (String builderName : builderFactory.getBuilderNames()) { + Builder builder = builderFactory.createBuilder(builderName); + Objects.requireNonNull(builder, "Unable to create builder for descriptor generation"); + String name = builderName != null && !builderName.isEmpty() ? builderName : categoryName; + if (!name.equals("Component")) { + try { + builderDescriptors.add(builder.getDescriptor(name, categoryName, this)); + } catch (NullPointerException | IllegalStateException var12) { + NPCPlugin.get().getLogger().at(Level.SEVERE).log("Failed to build descriptor for %s %s: %s", categoryName, name, var12.getMessage()); + } + } + } + } + + return builderDescriptors; + } + + public static void saveDescriptors(List builderDescriptors, @Nonnull Path fileName) { + try (BufferedWriter fileWriter = Files.newBufferedWriter(fileName)) { + GsonBuilder gsonBuilder = new GsonBuilder().setPrettyPrinting().serializeNulls().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE); + ValidatorTypeRegistry.registerTypes(gsonBuilder); + ProviderEvaluatorTypeRegistry.registerTypes(gsonBuilder); + Gson gson = gsonBuilder.create(); + gson.toJson(builderDescriptors, fileWriter); + } catch (IOException var7) { + NPCPlugin.get().getLogger().at(Level.SEVERE).log("Failed to write builder descriptors to %s", fileName); + } + } + + @Nullable + public Builder tryGetCachedValidRole(int builderIndex) { + return this.tryGetCachedValidBuilder(builderIndex, Role.class); + } + + public void validateAllLoadedBuilders(@Nonnull Int2ObjectMap loadedBuilders, boolean validateDependents, @Nonnull List errors) { + NPCPlugin.get().getLogger().at(Level.INFO).log("Validating loaded NPC configurations..."); + validateAllSpawnableNPCs(loadedBuilders, errors); + if (validateDependents) { + Int2ObjectOpenHashMap dependents = new Int2ObjectOpenHashMap<>(); + loadedBuilders.forEach( + (index, builderInfo) -> { + for (BuilderInfo info : this.builderCache.values()) { + int builderIndex = info.getIndex(); + Builder builder = info.getBuilder(); + + boolean isDependent; + try { + isDependent = this.isDependant(builder, builderIndex, index); + } catch (IllegalStateException | IllegalArgumentException | SkipSentryException var10) { + NPCPlugin.get() + .getLogger() + .at(Level.WARNING) + .log("Could not check if builder %s was dependent: %s", this.lookupName(info.getIndex()), var10.getMessage()); + continue; + } + + if (builder.isSpawnable() && isDependent) { + dependents.put(builderIndex, info); + } + } + } + ); + validateAllSpawnableNPCs(dependents, errors); + } + + NPCPlugin.get().getLogger().at(Level.INFO).log("Validation complete."); + } + + public void onAllBuildersLoaded(@Nonnull Int2ObjectMap loadedBuilders) { + if (!loadedBuilders.isEmpty()) { + IEventDispatcher dispatcher = HytaleServer.get().getEventBus().dispatchFor(AllNPCsLoadedEvent.class); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new AllNPCsLoadedEvent(this.getAllBuilders(), loadedBuilders)); + } + + this.getAllBuilders().forEach((index, builderInfo) -> { + if (builderInfo.needsValidation()) { + NPCPlugin.get().testAndValidateRole(builderInfo); + } + }); + } + } + + public static void onBuilderReloaded(@Nonnull BuilderInfo builderInfo) { + builderInfo.getBuilder().clearDynamicDependencies(); + NPCPlugin.reloadNPCsWithRole(builderInfo.getIndex()); + } + + public static int getPlayerGroupID() { + return playerGroupID; + } + + public static int getSelfGroupID() { + return selfGroupID; + } + + protected static void onBuilderAdded(@Nonnull BuilderInfo builderInfo) { + if (builderInfo.getBuilder().isSpawnable()) { + IEventDispatcher dispatcher = HytaleServer.get().getEventBus().dispatchFor(LoadedNPCEvent.class); + if (dispatcher.hasListener()) { + dispatcher.dispatch(new LoadedNPCEvent(builderInfo)); + } + } + } + + protected boolean isDependant(@Nonnull Builder builder, int builderIndex, int dependencyIndex) { + return builderIndex == dependencyIndex ? true : this.computeAllDependencies(builder, builderIndex).contains(dependencyIndex); + } + + protected int cacheBuilder(String name, Builder builder, Path path) { + this.indexLock.writeLock().lock(); + + int index; + try { + index = this.nameToIndexMap.getInt(name); + if (index >= 0) { + this.removeBuilder(index); + } else { + index = this.nextIndex.getAndIncrement(); + this.nameToIndexMap.put(name, index); + } + } finally { + this.indexLock.writeLock().unlock(); + } + + BuilderInfo builderInfo = new BuilderInfo(index, name, builder, path); + this.builderCache.put(index, builderInfo); + onBuilderAdded(builderInfo); + return index; + } + + private void removeBuilder(int index) { + BuilderInfo builder = this.builderCache.remove(index); + if (builder != null) { + builder.setRemoved(); + } + } + + private void removeBuilder(String name) { + int index = this.getIndex(name); + if (index >= 0) { + this.removeBuilder(index); + } + } + + @Nullable + private Builder tryGetCachedBuilder(int index) { + BuilderInfo entry = this.tryGetBuilderInfo(index); + return entry == null ? null : entry.getBuilder(); + } + + @Nullable + private BuilderInfo tryGetCachedBuilderInfo(int index, @Nonnull Class classType) { + BuilderInfo entry = this.tryGetBuilderInfo(index); + if (entry == null) { + return null; + } else { + Builder cachedBuilder = entry.getBuilder(); + if (cachedBuilder.category() != classType) { + throw new IllegalArgumentException( + String.format( + "Asset '%s'(%s) is different type. Is '%s' but should be '%s'", + this.lookupName(index), + index, + cachedBuilder.category().getName(), + classType.getName() + ) + ); + } else { + return entry; + } + } + } + + private static void validateAllSpawnableNPCs(@Nonnull Int2ObjectMap builders, @Nonnull List errors) { + builders.forEach( + (index, builderInfo) -> { + Builder builder = builderInfo.getBuilder(); + if (builder.isSpawnable() && builder instanceof SpawnableWithModelBuilder spawnableBuilder) { + ExecutionContext context = new ExecutionContext(builder.getBuilderParameters().createScope()); + String fileName = builderInfo.getPath().toString(); + + String modelName; + try { + modelName = spawnableBuilder.getSpawnModelName(context, spawnableBuilder.createModifierScope(context)); + } catch (IllegalStateException | SkipSentryException var12) { + errors.add(String.format("%s: %s", fileName, var12.getMessage())); + builderInfo.setValidated(false); + return; + } + + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(modelName); + if (modelAsset == null) { + errors.add(String.format("%s: Model %s does not exist.", fileName, modelName)); + builderInfo.setValidated(false); + } else { + Model model = Model.createScaledModel(modelAsset, modelAsset.getMaxScale()); + Builder builderInstance = builderInfo.getBuilder(); + NPCLoadTimeValidationHelper validationHelper = new NPCLoadTimeValidationHelper(fileName, model, !builderInstance.isSpawnable()); + if (!builderInstance.validate(fileName, validationHelper, context, context.getScope(), errors) + || !validationHelper.validateMotionControllers(errors) + || !validationHelper.getValueStoreValidator().validate(errors)) { + builderInfo.setValidated(false); + } + } + } + } + ); + } + + private static void sendReloadNotification(Message message, @Nonnull Set builders) { + if (!builders.isEmpty()) { + NotificationUtil.sendNotificationToUniverse(message, Message.raw(builders.toString()), NotificationStyle.Warning); + } + } + + private static boolean isIgnoredFile(@Nonnull Path path) { + return !path.getFileName().toString().isEmpty() && path.getFileName().toString().charAt(0) == '!'; + } + + private static boolean isJsonFile(@Nonnull Path path) { + return Files.isRegularFile(path) && path.toString().endsWith(".json"); + } + + private static boolean isJsonFileName(@Nonnull Path path, EventKind eventKind) { + return path.toString().endsWith(".json"); + } + + @Nonnull + private static String builderNameFromPath(@Nonnull Path path) { + String fileName = path.getFileName().toString(); + if (fileName.startsWith("NPCRole-")) { + fileName = fileName.split("-")[1]; + } + + int endIndex = fileName.lastIndexOf(46); + return endIndex >= 0 ? fileName.substring(0, endIndex) : fileName; + } + + @Nonnull + private String buildPathString(@Nonnull IntArrayList path, int index) { + if (path.isEmpty()) { + return ""; + } else { + StringBuilder result = new StringBuilder(); + result.append(" (Path: "); + IntIterator i = path.iterator(); + + while (i.hasNext()) { + result.append(this.lookupName(i.nextInt())).append(" -> "); + } + + result.append(this.lookupName(index)).append(')'); + return result.toString(); + } + } + + private boolean validateBuilder(@Nonnull BuilderInfo builderInfo, @Nonnull IntSet validatedDependencies, @Nonnull IntArrayList path) { + int index = builderInfo.getIndex(); + if (path.contains(index)) { + NPCPlugin.get() + .getLogger() + .at(Level.SEVERE) + .log( + "Builder '%s' validation failed: Cyclic reference detected for builder '%s'%s", + this.lookupName(path.getInt(0)), + this.lookupName(index), + this.buildPathString(path, index) + ); + return builderInfo.setValidated(false); + } else { + path.add(index); + + IntSet dependencies; + try { + dependencies = this.computeAllDependencies(builderInfo.getBuilder(), builderInfo.getIndex()); + } catch (IllegalStateException | IllegalArgumentException | SkipSentryException var10) { + NPCPlugin.get().getLogger().at(Level.SEVERE).log("Builder '%s' validation failed: %s", this.lookupName(path.getInt(0)), var10.getMessage()); + return builderInfo.setValidated(false); + } + + boolean valid = true; + IntIterator i = dependencies.iterator(); + + while (i.hasNext()) { + int dependency = i.nextInt(); + if (path.contains(dependency)) { + NPCPlugin.get() + .getLogger() + .at(Level.SEVERE) + .log( + "Builder '%s' validation failed: Cyclic reference detected for builder '%s'%s", + this.lookupName(path.getInt(0)), + this.lookupName(dependency), + this.buildPathString(path, index) + ); + return builderInfo.setValidated(false); + } + + if (validatedDependencies.add(dependency)) { + BuilderInfo childBuilder = this.builderCache.get(dependency); + if (childBuilder == null) { + NPCPlugin.get() + .getLogger() + .at(Level.SEVERE) + .log( + "Builder '%s' validation failed: Reference to unknown builder '%s'%s", + this.lookupName(path.getInt(0)), + this.lookupName(dependency), + this.buildPathString(path, dependency) + ); + valid = false; + } else if (!childBuilder.isValidated()) { + valid = this.validateBuilder(childBuilder, validatedDependencies, path); + } else if (!childBuilder.isValid()) { + NPCPlugin.get() + .getLogger() + .at(Level.SEVERE) + .log( + "Builder '%s' validation failed: Reference to invalid builder '%s'%s", + this.lookupName(path.getInt(0)), + childBuilder.getKeyName(), + this.buildPathString(path, dependency) + ); + valid = false; + } + } + } + + path.removeInt(path.size() - 1); + return builderInfo.setValidated(valid); + } + } + + @Nonnull + private IntSet computeAllDependencies(@Nonnull Builder builder, int builderIndex) { + return this.computeAllDependencies(builder, builderIndex, new IntOpenHashSet(), new IntArrayList()); + } + + @Nonnull + private IntSet computeAllDependencies(@Nonnull Builder builder, int builderIndex, @Nonnull IntSet dependencies, @Nonnull IntArrayList path) { + if (path.contains(builderIndex)) { + throw new SkipSentryException(new IllegalArgumentException("Cyclic reference detected for builder: " + this.lookupName(builderIndex))); + } else { + path.add(builderIndex); + this.iterateDependencies(builder.getDependencies().iterator(), dependencies, path); + if (builder.hasDynamicDependencies()) { + this.iterateDependencies(builder.getDynamicDependencies().iterator(), dependencies, path); + } + + path.removeInt(path.size() - 1); + return dependencies; + } + } + + private void iterateDependencies(@Nonnull IntIterator iterator, @Nonnull IntSet dependencies, @Nonnull IntArrayList path) { + while (iterator.hasNext()) { + int dependency = iterator.nextInt(); + if (!dependencies.contains(dependency)) { + dependencies.add(dependency); + Builder child = this.tryGetCachedBuilder(dependency); + if (child == null) { + throw new SkipSentryException(new IllegalStateException("Reference to unknown builder: " + this.lookupName(dependency))); + } + + this.computeAllDependencies(child, dependency, dependencies, path); + } + } + } + + private void reloadDependants(int dependency) { + for (BuilderInfo builderInfo : this.builderCache.values()) { + int index = builderInfo.getIndex(); + String keyName = builderInfo.getKeyName(); + Builder builder = builderInfo.getBuilder(); + + try { + if (builder.isSpawnable() && this.isDependant(builder, index, dependency)) { + NPCPlugin.get() + .getLogger() + .at(Level.INFO) + .log("Reloading entities of type '%s' because dependency '%s' changed", keyName, this.lookupName(dependency)); + onBuilderReloaded(builderInfo); + } + } catch (Throwable var8) { + NPCPlugin.get().getLogger().at(Level.INFO).log("Failed to reload entities of type '%s': %s", keyName, var8.getMessage()); + } + } + } + + private class BuilderAssetMonitorHandler implements AssetMonitorHandler { + private final AssetPack pack; + private final boolean includeTests; + + public BuilderAssetMonitorHandler(AssetPack pack, boolean includeTests) { + this.pack = pack; + this.includeTests = includeTests; + } + + @Override + public Object getKey() { + return this.pack; + } + + public boolean test(Path path, EventKind eventKind) { + return BuilderManager.isJsonFileName(path, eventKind); + } + + public void accept(Map map) { + Int2ObjectOpenHashMap loadedBuilders = new Int2ObjectOpenHashMap<>(); + HashSet loadedBuilderNames = new HashSet<>(); + HashSet failedBuilderTexts = new HashSet<>(); + HashSet deletedBuilderNames = new HashSet<>(); + ObjectArrayList errors = new ObjectArrayList<>(); + + for (Entry entry : map.entrySet()) { + Path path = entry.getKey(); + EventKind eventKind = entry.getValue(); + if (eventKind != EventKind.ENTRY_CREATE && eventKind != EventKind.ENTRY_MODIFY) { + if (eventKind == EventKind.ENTRY_DELETE) { + String builderName = BuilderManager.builderNameFromPath(path); + BuilderManager.this.removeBuilder(builderName); + NPCPlugin.get().getLogger().at(Level.INFO).log("Deleted %s builder %s", "NPC", builderName); + deletedBuilderNames.add(builderName); + } + } else if (Files.isRegularFile(path) && !BuilderManager.isIgnoredFile(path)) { + try { + int builderIndex = BuilderManager.this.loadFile(path, errors, null, this.includeTests, true); + if (builderIndex >= 0) { + String name = BuilderManager.builderNameFromPath(path); + NPCPlugin.get().getLogger().at(Level.INFO).log("Reloaded NPC builder " + name); + loadedBuilderNames.add(name); + + for (BuilderInfo builderInfo : BuilderManager.this.builderCache.values()) { + try { + if (BuilderManager.this.isDependant(builderInfo.getBuilder(), builderInfo.getIndex(), builderIndex)) { + builderInfo.setNeedsValidation(); + } + } catch (IllegalArgumentException | IllegalStateException var16) { + NPCPlugin.get() + .getLogger() + .at(Level.WARNING) + .log( + "Could not check if builder %s was dependent: %s", BuilderManager.this.lookupName(builderInfo.getIndex()), var16.getMessage() + ); + } + } + + if (BuilderManager.this.autoReload) { + BuilderManager.this.reloadDependants(builderIndex); + } + + BuilderInfo builder = BuilderManager.this.builderCache.get(builderIndex); + BuilderManager.onBuilderReloaded(builder); + loadedBuilders.put(builderIndex, builder); + } + } catch (Throwable var17) { + NPCPlugin.get().getLogger().at(Level.SEVERE).log("Failed to reload %s config %s: %s", "NPC", path, var17.getMessage()); + failedBuilderTexts.add(BuilderManager.builderNameFromPath(path) + ": " + var17.getMessage()); + } + } + } + + BuilderManager.sendReloadNotification(Message.translation("server.general.assetstore.reloadAssets").param("class", "NPC"), loadedBuilderNames); + BuilderManager.sendReloadNotification(Message.translation("server.general.assetstore.loadFailed").param("class", "NPC"), failedBuilderTexts); + BuilderManager.sendReloadNotification(Message.translation("server.general.assetstore.removedAssets").param("class", "NPC"), deletedBuilderNames); + BuilderManager.this.finishLoadingBuilders(loadedBuilders, errors); + } + } + + private static enum TestType { + NORMAL, + FAILING; + + private TestType() { + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderModifier.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderModifier.java new file mode 100644 index 0000000..b7bbdf6 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderModifier.java @@ -0,0 +1,305 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.schema.NamedSchema; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.SchemaConvertable; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.util.BsonUtil; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpression; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StateStringValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.config.balancing.BalanceAsset; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap.Entry; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderModifier { + public static final String KEY_MODIFY = "Modify"; + public static final String KEY_EXPORT_STATES = "_ExportStates"; + public static final String KEY_INTERFACE_PARAMETERS = "_InterfaceParameters"; + public static final String KEY_COMBAT_CONFIG = "_CombatConfig"; + public static final String KEY_INTERACTION_VARS = "_InteractionVars"; + private final Object2ObjectMap builderExpressionMap; + private final StatePair[] exportedStateIndexes; + private final StateMappingHelper stateHelper; + private final String combatConfig; + private final Map interactionVars; + + protected BuilderModifier( + Object2ObjectMap builderExpressionMap, + StatePair[] exportedStateIndexes, + StateMappingHelper stateHelper, + String combatConfig, + Map interactionVars + ) { + this.builderExpressionMap = builderExpressionMap; + this.exportedStateIndexes = exportedStateIndexes; + this.stateHelper = stateHelper; + this.combatConfig = combatConfig; + this.interactionVars = interactionVars; + } + + public String getCombatConfig() { + return this.combatConfig; + } + + public Map getInteractionVars() { + return this.interactionVars; + } + + public boolean isEmpty() { + return this.builderExpressionMap.isEmpty(); + } + + public int exportedStateCount() { + return this.exportedStateIndexes.length; + } + + public void applyComponentStateMap(@Nonnull BuilderSupport support) { + support.setModifiedStateMap(this.stateHelper, this.exportedStateIndexes); + } + + public void popComponentStateMap(@Nonnull BuilderSupport support) { + support.popModifiedStateMap(); + } + + @Nonnull + public Scope createScope(@Nonnull BuilderSupport builderSupport, @Nonnull BuilderParameters builderParameters, Scope globalScope) { + ExecutionContext executionContext = builderSupport.getExecutionContext(); + return this.createScope(executionContext, builderParameters, globalScope); + } + + @Nonnull + public Scope createScope(ExecutionContext executionContext, @Nonnull BuilderParameters builderParameters, @Nullable Scope globalScope) { + StdScope scope = builderParameters.createScope(); + if (globalScope != null) { + StdScope mergedScope = new StdScope(globalScope); + mergedScope.merge(scope); + scope = mergedScope; + } + + StdScope finalScope = scope; + ObjectIterator> iterator = Object2ObjectMaps.fastIterator(this.builderExpressionMap); + + while (iterator.hasNext()) { + Entry pair = iterator.next(); + String name = pair.getKey(); + BuilderModifier.ExpressionHolder holder = pair.getValue(); + ValueType valueType = builderParameters.getParameterType(name); + BuilderExpression expression = holder.getExpression(builderParameters.getInterfaceCode()); + if (expression != null) { + if (valueType == ValueType.VOID) { + throw new SkipSentryException(new IllegalStateException("Parameter " + name + " does not exist or is private")); + } + + if (!ValueType.isAssignableType(expression.getType(), valueType)) { + throw new SkipSentryException( + new IllegalStateException("Parameter " + name + " has type " + expression.getType() + " but should be " + valueType) + ); + } + + expression.updateScope(finalScope, name, executionContext); + } + } + + return scope; + } + + @Nonnull + public static BuilderModifier fromJSON( + @Nonnull JsonObject jsonObject, @Nonnull BuilderParameters builderParameters, @Nonnull StateMappingHelper helper, @Nonnull ExtraInfo extraInfo + ) { + JsonObject modify = null; + JsonElement modifyObject = jsonObject.get("Modify"); + if (modifyObject != null) { + modify = BuilderBase.expectObject(modifyObject, "Modify"); + } + + if (modify != null && !modify.entrySet().isEmpty()) { + Object2ObjectMap map = new Object2ObjectOpenHashMap<>(); + List exportedStateIndexes = new ObjectArrayList<>(); + + for (java.util.Map.Entry stringElementPair : modify.entrySet()) { + String key = stringElementPair.getKey(); + if (map.containsKey(key)) { + throw new SkipSentryException(new IllegalStateException("Duplicate entry '" + key + "' in 'Modify' block")); + } + + if (!key.equals("_InterfaceParameters") && !key.equals("_CombatConfig") && !key.equals("_InteractionVars")) { + if (key.equals("_ExportStates")) { + if (!stringElementPair.getValue().isJsonArray()) { + throw new SkipSentryException(new IllegalStateException(String.format("%s in modifier block must be a Json Array", "_ExportStates"))); + } + + StateStringValidator validator = StateStringValidator.requireMainState(); + JsonArray array = stringElementPair.getValue().getAsJsonArray(); + + for (int i = 0; i < array.size(); i++) { + String state = array.get(i).getAsString(); + if (!validator.test(state)) { + throw new SkipSentryException(new IllegalStateException(validator.errorMessage(state))); + } + + String substate = validator.hasSubState() ? validator.getSubState() : helper.getDefaultSubState(); + helper.getAndPutSetterIndex( + validator.getMainState(), substate, (m, s) -> exportedStateIndexes.add(new StatePair(validator.getMainState(), m, s)) + ); + } + } else { + BuilderExpression expression = BuilderExpression.fromJSON(stringElementPair.getValue(), builderParameters, false); + map.put(key, new BuilderModifier.ExpressionHolder(expression)); + } + } + } + + JsonElement interfaceValue = modify.get("_InterfaceParameters"); + if (interfaceValue != null) { + JsonObject interfaceParameters = BuilderBase.expectObject(interfaceValue, "_InterfaceParameters"); + + for (java.util.Map.Entry interfaceEntry : interfaceParameters.entrySet()) { + String interfaceKey = interfaceEntry.getKey(); + JsonObject parameters = BuilderBase.expectObject(interfaceEntry.getValue()); + + for (java.util.Map.Entry parameterEntry : parameters.entrySet()) { + BuilderModifier.ExpressionHolder holder = map.computeIfAbsent(parameterEntry.getKey(), keyx -> new BuilderModifier.ExpressionHolder()); + if (holder.hasInterfaceMappedExpression(interfaceKey)) { + throw new SkipSentryException( + new IllegalStateException("Duplicate entry '" + parameterEntry.getKey() + "' in 'Modify' block for interface '" + interfaceKey) + ); + } + + holder.addInterfaceMappedExpression(interfaceKey, BuilderExpression.fromJSON(parameterEntry.getValue(), builderParameters, false)); + } + } + } + + String combatConfig = null; + JsonElement combatConfigValue = modify.get("_CombatConfig"); + if (combatConfigValue != null) { + combatConfig = combatConfigValue.getAsString(); + } + + Map interactionVars = null; + JsonElement interactionVarsValue = modify.get("_InteractionVars"); + if (interactionVarsValue != null) { + interactionVars = RootInteraction.CHILD_ASSET_CODEC_MAP.decode(BsonUtil.translateJsonToBson(interactionVarsValue), extraInfo); + extraInfo.getValidationResults()._processValidationResults(); + extraInfo.getValidationResults().logOrThrowValidatorExceptions(HytaleLogger.getLogger()); + } + + return new BuilderModifier(map, exportedStateIndexes.toArray(StatePair[]::new), helper, combatConfig, interactionVars); + } else { + return EmptyBuilderModifier.INSTANCE; + } + } + + public static void readModifierObject( + @Nonnull JsonObject jsonObject, + @Nonnull BuilderParameters builderParameters, + @Nonnull StringHolder holder, + @Nonnull Consumer referenceConsumer, + @Nonnull Consumer builderModifierConsumer, + @Nonnull StateMappingHelper helper, + @Nonnull ExtraInfo extraInfo + ) { + holder.readJSON(BuilderBase.expectKey(jsonObject, "Reference"), StringNotEmptyValidator.get(), "Reference", builderParameters); + BuilderModifier modifier = fromJSON(jsonObject, builderParameters, helper, extraInfo); + referenceConsumer.accept(holder); + builderModifierConsumer.accept(modifier); + } + + @Nonnull + public static Schema toSchema(@Nonnull SchemaContext context) { + return context.refDefinition(BuilderModifier.SchemaGenerator.INSTANCE); + } + + private static class ExpressionHolder { + private final BuilderExpression expression; + private Object2ObjectMap interfaceMappedExpressions; + + public ExpressionHolder() { + this(null); + } + + public ExpressionHolder(BuilderExpression expression) { + this.expression = expression; + } + + public boolean hasInterfaceMappedExpression(String interfaceKey) { + return this.interfaceMappedExpressions != null && this.interfaceMappedExpressions.containsKey(interfaceKey); + } + + public void addInterfaceMappedExpression(String interfaceKey, BuilderExpression expression) { + if (this.interfaceMappedExpressions == null) { + this.interfaceMappedExpressions = new Object2ObjectOpenHashMap<>(); + } + + this.interfaceMappedExpressions.put(interfaceKey, expression); + } + + public BuilderExpression getExpression(@Nullable String interfaceKey) { + return interfaceKey != null && this.interfaceMappedExpressions != null && this.interfaceMappedExpressions.containsKey(interfaceKey) + ? this.interfaceMappedExpressions.get(interfaceKey) + : this.expression; + } + } + + private static class SchemaGenerator implements SchemaConvertable, NamedSchema { + @Nonnull + public static BuilderModifier.SchemaGenerator INSTANCE = new BuilderModifier.SchemaGenerator(); + + private SchemaGenerator() { + } + + @Nonnull + @Override + public String getSchemaName() { + return "NPC:Type:BuilderModifier"; + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + ObjectSchema s = new ObjectSchema(); + s.setTitle("BuilderModifier"); + LinkedHashMap props = new LinkedHashMap<>(); + s.setProperties(props); + props.put("_ExportStates", new ArraySchema(new StringSchema())); + props.put("_InterfaceParameters", new ObjectSchema()); + StringSchema combatConfig = new StringSchema(); + combatConfig.setHytaleAssetRef(BalanceAsset.class.getSimpleName()); + props.put("_CombatConfig", combatConfig); + ObjectSchema interactionVars = new ObjectSchema(); + interactionVars.setTitle("Map"); + Schema childSchema = context.refDefinition(RootInteraction.CHILD_ASSET_CODEC); + interactionVars.setAdditionalProperties(childSchema); + props.put("_InteractionVars", interactionVars); + s.setAdditionalProperties(BuilderExpression.toSchema(context)); + return s; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectArrayHelper.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectArrayHelper.java new file mode 100644 index 0000000..80c0e76 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectArrayHelper.java @@ -0,0 +1,111 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class BuilderObjectArrayHelper extends BuilderObjectHelper { + @Nullable + protected BuilderObjectReferenceHelper[] builders; + protected String label; + + public BuilderObjectArrayHelper(Class classType, BuilderContext owner) { + super(classType, owner); + } + + @Override + public void readConfig( + @Nonnull JsonElement data, + @Nonnull BuilderManager builderManager, + @Nonnull BuilderParameters builderParameters, + @Nonnull BuilderValidationHelper builderValidationHelper + ) { + super.readConfig(data, builderManager, builderParameters, builderValidationHelper); + if (data.isJsonNull()) { + this.builders = null; + } else { + if (!data.isJsonArray()) { + String string = data.toString(); + throw new IllegalArgumentException( + String.format( + "Expected a JSON array of '%s' at %s (JSON: %s)", + this.classType.getSimpleName(), + this.getBreadCrumbs(), + string.length() > 60 ? string.substring(60) + "..." : string + ) + ); + } + + JsonArray array = data.getAsJsonArray(); + BuilderFactory factory = builderManager.getFactory(this.classType); + this.builders = new BuilderObjectReferenceHelper[array.size()]; + int index = 0; + + for (JsonElement element : array) { + BuilderObjectReferenceHelper builderObjectReferenceHelper = this.createReferenceHelper(); + builderObjectReferenceHelper.readConfig(element, factory, builderManager, builderParameters, builderValidationHelper); + if (!builderObjectReferenceHelper.isPresent()) { + throw new IllegalStateException("Missing builder reference at " + this.getBreadCrumbs() + ": " + builderParameters.getFileName()); + } + + this.builders[index++] = builderObjectReferenceHelper; + } + } + } + + @Override + public boolean validate( + String configName, + NPCLoadTimeValidationHelper loadTimeValidationHelper, + @Nonnull BuilderManager manager, + @Nonnull ExecutionContext context, + Scope globalScope, + @Nonnull List errors + ) { + if (this.hasNoElements()) { + return true; + } else { + boolean result = true; + + for (BuilderObjectReferenceHelper builder : this.builders) { + if (!builder.excludeFromRegularBuild()) { + result &= builder.validate(configName, loadTimeValidationHelper, manager, context, globalScope, errors); + } + } + + return result; + } + } + + @Override + public boolean isPresent() { + return this.builders != null; + } + + public boolean isEmpty() { + return this.isPresent() && this.builders.length == 0; + } + + public boolean hasNoElements() { + return this.builders == null || this.builders.length == 0; + } + + @Override + public String getLabel() { + return this.label; + } + + public void setLabel(String label) { + this.label = label; + } + + @Nonnull + protected BuilderObjectReferenceHelper createReferenceHelper() { + return new BuilderObjectReferenceHelper(this.classType, this); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectHelper.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectHelper.java new file mode 100644 index 0000000..ee7969c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectHelper.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nullable; + +public abstract class BuilderObjectHelper implements BuilderContext { + protected final Class classType; + protected BuilderParameters builderParameters; + protected final BuilderContext owner; + + protected BuilderObjectHelper(Class classType, BuilderContext owner) { + this.owner = owner; + this.classType = classType; + } + + @Nullable + public abstract T build(BuilderSupport var1); + + public abstract boolean validate(String var1, NPCLoadTimeValidationHelper var2, BuilderManager var3, ExecutionContext var4, Scope var5, List var6); + + @Override + public BuilderContext getOwner() { + return this.owner; + } + + public final Class getClassType() { + return this.classType; + } + + public void readConfig(JsonElement data, BuilderManager builderManager, BuilderParameters builderParameters, BuilderValidationHelper builderValidationHelper) { + this.builderParameters = builderParameters; + } + + public abstract boolean isPresent(); +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectListHelper.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectListHelper.java new file mode 100644 index 0000000..3add7d2 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectListHelper.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderObjectListHelper extends BuilderObjectArrayHelper, T> { + public BuilderObjectListHelper(Class classType, BuilderContext owner) { + super(classType, owner); + } + + @Nullable + public List build(@Nonnull BuilderSupport builderSupport) { + if (this.hasNoElements()) { + return null; + } else { + List objects = new ObjectArrayList<>(); + + for (BuilderObjectReferenceHelper builder : this.builders) { + if (!builder.excludeFromRegularBuild()) { + T obj = builder.build(builderSupport); + if (obj != null) { + objects.add(obj); + } + } + } + + return objects; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectMapHelper.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectMapHelper.java new file mode 100644 index 0000000..da4f124 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectMapHelper.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderObjectMapHelper extends BuilderObjectArrayHelper, V> { + private Function id; + + public BuilderObjectMapHelper(Class classType, Function id, BuilderContext owner) { + super(classType, owner); + this.id = id; + } + + @Nullable + public Map build(@Nonnull BuilderSupport builderSupport) { + if (this.hasNoElements()) { + return null; + } else { + Map objects = new Object2ObjectLinkedOpenHashMap<>(); + + for (BuilderObjectReferenceHelper builderObjectReferenceHelper : this.builders) { + if (!builderObjectReferenceHelper.excludeFromRegularBuild()) { + V value = builderObjectReferenceHelper.build(builderSupport); + K key = this.id.apply(value); + if (objects.containsKey(key)) { + throw new IllegalArgumentException("Duplicate key \"" + key + "\" at " + this.getBreadCrumbs() + ": " + this.builderParameters.getFileName()); + } + + objects.put(key, value); + } + } + + return objects; + } + } + + @Override + public void readConfig( + @Nonnull JsonElement data, + @Nonnull BuilderManager builderManager, + @Nonnull BuilderParameters builderParameters, + @Nonnull BuilderValidationHelper builderValidationHelper + ) { + super.readConfig(data, builderManager, builderParameters, builderValidationHelper); + } + + @Nullable + public T testEach( + @Nonnull BiFunction, U, T> test, + @Nonnull BuilderManager builderManager, + ExecutionContext executionContext, + U meta, + T successResult, + T emptyResult, + Builder parentSpawnable + ) { + if (this.hasNoElements()) { + return emptyResult; + } else { + for (BuilderObjectReferenceHelper builderObjectReferenceHelper : this.builders) { + T result = test.apply(builderObjectReferenceHelper.getBuilder(builderManager, executionContext, parentSpawnable), meta); + if (!Objects.equals(result, successResult)) { + return result; + } + } + + return successResult; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectReferenceHelper.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectReferenceHelper.java new file mode 100644 index 0000000..cda6fd5 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectReferenceHelper.java @@ -0,0 +1,406 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.hypixel.hytale.logger.sentry.SkipSentryException; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.ReferenceProviderEvaluator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringArrayNotEmptyValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderObjectReferenceHelper extends BuilderObjectHelper { + public static final String KEY_REFERENCE = "Reference"; + public static final String KEY_LOCAL = "Local"; + public static final String KEY_INTERFACE_LIST = "Interfaces"; + public static final String KEY_NULLABLE = "Nullable"; + public static final String NULL_COMPONENT = "$Null"; + public static final String KEY_LABEL = "$Label"; + @Nullable + protected Builder builder; + protected final StringHolder fileReference = new StringHolder(); + protected String[] componentInterfaces; + protected int referenceIndex; + protected boolean isReference; + protected boolean isNullable; + @Nullable + protected BuilderModifier modifier; + protected FeatureEvaluatorHelper evaluatorHelper; + protected InternalReferenceResolver internalReferenceResolver; + protected boolean isInternalReference; + protected String label; + + public BuilderObjectReferenceHelper(Class classType, BuilderContext owner) { + super(classType, owner); + this.builder = null; + this.referenceIndex = Integer.MIN_VALUE; + this.modifier = null; + } + + public boolean excludeFromRegularBuild() { + return this.builder == null ? false : this.builder.excludeFromRegularBuilding(); + } + + @Nullable + @Override + public T build(@Nonnull BuilderSupport builderSupport) { + if (!this.isPresent()) { + return null; + } else { + Builder builder = this.getBuilder(builderSupport.getBuilderManager(), builderSupport, this.isNullable); + if (builder == null) { + return null; + } else { + StateMappingHelper mappingHelper = builder.getStateMappingHelper(); + boolean hasLocalComponentStates = this.builder == null && mappingHelper != null && mappingHelper.hasComponentStates(); + if (hasLocalComponentStates) { + mappingHelper.initialiseComponentState(builderSupport); + } + + if (this.modifier == null) { + this.validateRequiredFeatures(builder, builderSupport.getBuilderManager(), builderSupport.getExecutionContext()); + T instance = builder.isEnabled(builderSupport.getExecutionContext()) ? builder.build(builderSupport) : null; + if (hasLocalComponentStates) { + mappingHelper.popComponentState(builderSupport); + } + + return instance; + } else { + Scope globalScope = null; + if (this.isInternalReference) { + globalScope = builderSupport.getGlobalScope(); + Objects.requireNonNull(globalScope, "Global scope should not be null when applying to an internal component"); + } + + if (this.modifier.exportedStateCount() != builder.getStateMappingHelper().importedStateCount()) { + throw new SkipSentryException( + new IllegalStateException( + String.format( + "Number of exported states does not match imported states in component %s", + this.fileReference.get(builderSupport.getExecutionContext()) + ) + ) + ); + } else { + ExecutionContext context = builderSupport.getExecutionContext(); + Scope newScope = this.modifier.createScope(builderSupport, builder.getBuilderParameters(), globalScope); + Scope oldScope = context.setScope(newScope); + if (this.modifier.exportedStateCount() > 0) { + this.modifier.applyComponentStateMap(builderSupport); + } + + this.validateRequiredFeatures(builder, builderSupport.getBuilderManager(), context); + this.validateInstructionContext(builder, builderSupport); + T instance = builder.isEnabled(builderSupport.getExecutionContext()) ? builder.build(builderSupport) : null; + if (this.modifier.exportedStateCount() > 0) { + this.modifier.popComponentStateMap(builderSupport); + } + + if (hasLocalComponentStates) { + mappingHelper.popComponentState(builderSupport); + } + + builderSupport.getExecutionContext().setScope(oldScope); + return instance; + } + } + } + } + } + + @Override + public boolean validate( + String configName, + NPCLoadTimeValidationHelper loadTimeValidationHelper, + @Nonnull BuilderManager manager, + @Nonnull ExecutionContext context, + Scope globalScope, + @Nonnull List errors + ) { + if (!this.isPresent()) { + return true; + } else { + Builder builder; + try { + builder = this.getBuilder(manager, context, null); + } catch (Exception var16) { + errors.add(String.format("%s: %s", configName, var16.getMessage())); + return false; + } + + if (builder == null) { + if (this.isNullable) { + return true; + } else { + errors.add( + String.format( + "%s: %s is not a nullable component reference but a null component was passed", configName, this.fileReference.getExpressionString() + ) + ); + return false; + } + } else if (this.modifier == null) { + if (!builder.isEnabled(context)) { + return true; + } else { + boolean result = true; + + try { + this.validateRequiredFeatures(builder, manager, context); + } catch (Exception var13) { + errors.add(String.format("%s: %s", configName, var13.getMessage())); + result = false; + } + + return result & builder.validate(configName, loadTimeValidationHelper, context, globalScope, errors); + } + } else { + boolean result = true; + if (this.modifier.exportedStateCount() != builder.getStateMappingHelper().importedStateCount()) { + errors.add( + String.format("%s: Number of exported states does not match imported states in component %s", configName, this.fileReference.get(context)) + ); + result = false; + } + + Scope additionalScope = this.isInternalReference ? globalScope : null; + + Scope newScope; + try { + newScope = this.modifier.createScope(context, builder.getBuilderParameters(), additionalScope); + } catch (Exception var15) { + errors.add(String.format("%s: %s", configName, var15.getMessage())); + return false; + } + + Scope oldScope = context.setScope(newScope); + if (builder.isEnabled(context)) { + try { + this.validateRequiredFeatures(builder, manager, context); + } catch (Exception var14) { + errors.add(String.format("%s: %s", configName, var14.getMessage())); + result = false; + } + + result &= builder.validate(configName, loadTimeValidationHelper, context, globalScope, errors); + } + + context.setScope(oldScope); + return result; + } + } + } + + @Nullable + public Builder getBuilder(@Nonnull BuilderManager builderManager, @Nonnull BuilderSupport support, boolean nullable) { + Builder builder = this.getBuilder(builderManager, support.getExecutionContext(), support.getParentSpawnable()); + if (!nullable && builder == null) { + throw new NullPointerException(String.format("ReferenceHelper failed to get builder: %s", this.getClassType().getSimpleName())); + } else { + return builder; + } + } + + @Nullable + public Builder getBuilder(@Nonnull BuilderManager builderManager, ExecutionContext context, @Nullable Builder parentSpawnable) { + if (this.builder != null) { + return this.builder; + } else if (this.isInternalReference) { + return this.internalReferenceResolver.getBuilder(this.referenceIndex, this.classType); + } else if (this.referenceIndex >= 0) { + Builder builder = builderManager.tryGetCachedValidBuilder(this.referenceIndex, this.classType); + if (builder == null) { + throw new SkipSentryException( + new IllegalStateException(String.format("Builder %s exists but is not valid!", builderManager.lookupName(this.referenceIndex))) + ); + } else { + return builder; + } + } else { + String reference = this.fileReference.get(context); + if (reference.equals("$Null")) { + return null; + } else { + int idx = builderManager.getIndex(reference); + if (idx >= 0) { + if (parentSpawnable != null) { + parentSpawnable.addDynamicDependency(idx); + } + + Builder builder = builderManager.getCachedBuilder(idx, this.classType); + String builderInterfaceCode = builder.getBuilderParameters().getInterfaceCode(); + this.validateComponentInterfaceMatch(builderInterfaceCode); + return builder; + } else if (!reference.isEmpty()) { + throw new SkipSentryException(new IllegalStateException("Failed to find builder for: " + reference)); + } else { + return null; + } + } + } + } + + @Override + public void readConfig( + @Nonnull JsonElement data, + @Nonnull BuilderManager builderManager, + @Nonnull BuilderParameters builderParameters, + @Nonnull BuilderValidationHelper builderValidationHelper + ) { + this.readConfig(data, builderManager.getFactory(this.classType), builderManager, builderParameters, builderValidationHelper); + } + + public void readConfig( + @Nonnull JsonElement data, + @Nonnull BuilderFactory factory, + @Nonnull BuilderManager builderManager, + @Nonnull BuilderParameters builderParameters, + @Nonnull BuilderValidationHelper builderValidationHelper + ) { + super.readConfig(data, builderManager, builderParameters, builderValidationHelper); + if (data.isJsonNull()) { + this.builder = null; + } else if (data.isJsonPrimitive() && data.getAsJsonPrimitive().isString()) { + builderValidationHelper.getReadErrors() + .add( + builderValidationHelper.getName() + + ": String reference '" + + data.getAsString() + + "' to a component is deprecated. Use the 'Reference' parameter instead." + ); + } else { + JsonObject jsonObject = data.isJsonObject() ? data.getAsJsonObject() : null; + JsonElement referenceValue = jsonObject != null ? jsonObject.get("Reference") : null; + if (referenceValue != null) { + try { + if (BuilderBase.readBoolean(jsonObject, "Local", false)) { + BuilderModifier.readModifierObject( + data.getAsJsonObject(), + builderParameters, + this.fileReference, + holder -> this.setInternalReference(holder, builderValidationHelper.getInternalReferenceResolver()), + modifier -> this.modifier = modifier, + builderValidationHelper.getStateMappingHelper(), + builderValidationHelper.getExtraInfo() + ); + } else { + JsonObject dataObj = data.getAsJsonObject(); + BuilderModifier.readModifierObject( + dataObj, + builderParameters, + this.fileReference, + holder -> this.setFileReference(holder, dataObj, builderManager), + modifier -> this.modifier = modifier, + builderValidationHelper.getStateMappingHelper(), + builderValidationHelper.getExtraInfo() + ); + } + + FeatureEvaluatorHelper evaluatorHelper = builderValidationHelper.getFeatureEvaluatorHelper(); + if (evaluatorHelper != null) { + if (evaluatorHelper.canAddProvider()) { + evaluatorHelper.add(new ReferenceProviderEvaluator(this.referenceIndex, this.classType)); + evaluatorHelper.setContainsReference(); + return; + } + + this.evaluatorHelper = evaluatorHelper; + } + } catch (IllegalStateException | IllegalArgumentException var9) { + builderValidationHelper.getReadErrors().add(builderValidationHelper.getName() + ": " + var9.getMessage() + " at " + this.getBreadCrumbs()); + } + } else { + this.builder = factory.createBuilder(data); + if (this.builder.isDeprecated()) { + builderManager.checkIfDeprecated(this.builder, factory, data, builderParameters.getFileName(), this.getBreadCrumbs()); + } + + if (data.isJsonObject() && data.getAsJsonObject().has("$Label") && data.getAsJsonObject().get("$Label").isJsonPrimitive()) { + this.builder.setLabel(data.getAsJsonObject().get("$Label").getAsString()); + } else { + this.builder.setLabel(factory.getKeyName(data)); + } + + this.builder.readConfig(this, data, builderManager, builderParameters, builderValidationHelper); + } + } + } + + protected void setInternalReference(@Nonnull StringHolder holder, InternalReferenceResolver referenceResolver) { + this.isInternalReference = true; + this.isReference = true; + if (holder.isStatic()) { + this.internalReferenceResolver = referenceResolver; + this.referenceIndex = this.internalReferenceResolver.getOrCreateIndex(holder.get(null)); + } + } + + protected void setFileReference(@Nonnull StringHolder holder, @Nonnull JsonObject jsonObject, @Nonnull BuilderManager builderManager) { + this.isInternalReference = false; + this.isReference = true; + this.componentInterfaces = BuilderBase.readStringArray(jsonObject, "Interfaces", StringNotEmptyValidator.get(), null); + this.isNullable = BuilderBase.readBoolean(jsonObject, "Nullable", false); + if (holder.isStatic()) { + this.referenceIndex = builderManager.getOrCreateIndex(holder.get(null)); + this.builderParameters.addDependency(this.referenceIndex); + } else if (!StringArrayNotEmptyValidator.get().test(this.componentInterfaces)) { + throw new SkipSentryException( + new IllegalStateException("Computable references must define a list of 'Interfaces' to control which components can be attached.") + ); + } + } + + private void validateRequiredFeatures(@Nonnull Builder builder, BuilderManager manager, ExecutionContext context) { + builder.validateReferencedProvidedFeatures(manager, context); + if (this.evaluatorHelper != null) { + this.evaluatorHelper.validateProviderReferences(manager, context); + builder.getEvaluatorHelper().validateComponentRequirements(this.evaluatorHelper, context); + } + } + + private void validateInstructionContext(@Nonnull Builder builder, @Nonnull BuilderSupport support) { + InstructionContextHelper instructionContextHelper = builder.getInstructionContextHelper(); + if (instructionContextHelper != null && !this.isInternalReference) { + instructionContextHelper.validateComponentContext(support.getCurrentInstructionContext(), support.getCurrentComponentContext()); + } + } + + private void validateComponentInterfaceMatch(String builderInterfaceCode) { + for (String componentInterface : this.componentInterfaces) { + if (componentInterface.equals(builderInterfaceCode)) { + return; + } + } + + throw new SkipSentryException( + new IllegalStateException( + String.format("Component code %s does not match any of slot codes: %s.", builderInterfaceCode, Arrays.toString((Object[])this.componentInterfaces)) + ) + ); + } + + @Override + public boolean isPresent() { + return this.isFinal() || this.isReference; + } + + public boolean isFinal() { + return this.builder != null; + } + + @Override + public String getLabel() { + return this.label; + } + + public void setLabel(String label) { + this.label = label; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectStaticHelper.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectStaticHelper.java new file mode 100644 index 0000000..b8b28bb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectStaticHelper.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderObjectStaticHelper extends BuilderObjectReferenceHelper { + public BuilderObjectStaticHelper(Class classType, BuilderContext owner) { + super(classType, owner); + } + + @Override + public void readConfig( + @Nonnull JsonElement data, + @Nonnull BuilderManager builderManager, + @Nonnull BuilderParameters builderParameters, + @Nonnull BuilderValidationHelper builderValidationHelper + ) { + super.readConfig(data, builderManager, builderParameters, builderValidationHelper); + if (!this.isFinal()) { + throw new IllegalStateException("Static object must be static, at: " + this.getBreadCrumbs()); + } + } + + @Override + protected void setInternalReference(StringHolder holder, InternalReferenceResolver referenceResolver) { + throw new IllegalStateException("Static object cannot contain a reference, at: " + this.getBreadCrumbs()); + } + + @Override + protected void setFileReference(StringHolder holder, JsonObject jsonObject, BuilderManager builderManager) { + throw new IllegalStateException("Static object cannot contain a reference, at: " + this.getBreadCrumbs()); + } + + @Nullable + public T staticBuild(@Nonnull BuilderManager manager) { + return this.getBuilder(manager, null, null).build(null); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectStaticListHelper.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectStaticListHelper.java new file mode 100644 index 0000000..cf35b01 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderObjectStaticListHelper.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderObjectStaticListHelper extends BuilderObjectListHelper { + public BuilderObjectStaticListHelper(Class classType, BuilderContext owner) { + super(classType, owner); + } + + @Nonnull + @Override + protected BuilderObjectReferenceHelper createReferenceHelper() { + return new BuilderObjectStaticHelper<>(this.classType, this); + } + + @Nullable + public List staticBuild(@Nonnull BuilderManager manager) { + if (this.hasNoElements()) { + return null; + } else { + List objects = new ObjectArrayList<>(); + + for (BuilderObjectReferenceHelper builder : this.builders) { + T obj = ((BuilderObjectStaticHelper)builder).staticBuild(manager); + if (obj != null) { + objects.add(obj); + } + } + + return objects; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderParameters.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderParameters.java new file mode 100644 index 0000000..fcfae02 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderParameters.java @@ -0,0 +1,250 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.BooleanSchema; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpression; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpressionStaticBooleanArray; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpressionStaticEmptyArray; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpressionStaticNumberArray; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpressionStaticStringArray; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import com.hypixel.hytale.server.npc.util.expression.compile.CompileContext; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderParameters { + public static final String KEY_PARAMETERS = "Parameters"; + public static final String KEY_IMPORT_STATES = "_ImportStates"; + public static final String KEY_INTERFACE = "Interface"; + @Nonnull + protected final Map parameters; + protected StdScope scope; + @Nullable + protected CompileContext compileContext; + protected final String fileName; + protected final IntSet dependencies; + protected final String interfaceCode; + + protected BuilderParameters(StdScope scope, String fileName, String interfaceCode) { + this(scope, fileName, interfaceCode, new IntOpenHashSet()); + } + + protected BuilderParameters(StdScope scope, String fileName, String interfaceCode, IntSet dependencies) { + this.scope = new StdScope(scope); + this.compileContext = new CompileContext(this.scope); + this.parameters = new HashMap<>(); + this.fileName = fileName; + this.dependencies = dependencies; + this.interfaceCode = interfaceCode; + } + + protected BuilderParameters(@Nonnull BuilderParameters other) { + this(other.scope, other.fileName, other.interfaceCode, other.dependencies); + } + + public boolean isEmpty() { + return this.parameters.isEmpty(); + } + + public void addParametersToScope() { + this.parameters.forEach((name, parameter) -> parameter.expression.addToScope(name, this.scope)); + } + + public ValueType getParameterType(String name) { + BuilderParameters.Parameter p = this.parameters.get(name); + return p != null && !p.isPrivate() ? p.getExpression().getType() : ValueType.VOID; + } + + public void readJSON(@Nonnull JsonObject jsonObject, @Nonnull StateMappingHelper stateHelper) { + JsonElement parameterValue = jsonObject.get("Parameters"); + if (parameterValue != null) { + JsonObject modify = BuilderBase.expectObject(parameterValue, "Parameters"); + modify.entrySet().forEach(stringElementPair -> { + String key = stringElementPair.getKey(); + if (this.parameters.containsKey(key)) { + throw new IllegalStateException("Duplicate entry '" + key + "' in 'Parameters' block"); + } else if (key.equals("_ImportStates")) { + if (!stringElementPair.getValue().isJsonArray()) { + throw new IllegalStateException(String.format("%s in parameter block must be a Json Array", "_ImportStates")); + } else { + stateHelper.setComponentImportStateMappings(stringElementPair.getValue().getAsJsonArray()); + } + } else { + this.parameters.put(key, BuilderParameters.Parameter.fromJSON(stringElementPair.getValue(), this, key)); + } + }); + } + } + + public void createCompileContext() { + this.compileContext = new CompileContext(); + } + + public void disposeCompileContext() { + this.compileContext = null; + } + + @Nullable + public CompileContext getCompileContext() { + return this.compileContext; + } + + public ValueType compile(@Nonnull String expression) { + return this.getCompileContext().compile(expression, this.getScope(), false); + } + + public List getInstructions() { + return this.getCompileContext().getInstructions(); + } + + @Nullable + public ExecutionContext.Operand getConstantOperand() { + return this.getCompileContext().getAsOperand(); + } + + public StdScope getScope() { + return this.scope; + } + + @Nonnull + public StdScope createScope() { + return StdScope.copyOf(this.scope); + } + + public void validateNoDuplicateParameters(@Nonnull BuilderParameters other) { + other.parameters.keySet().forEach(key -> { + if (this.parameters.containsKey(key)) { + throw new IllegalStateException("Parameter '" + key + "' in 'Parameters' block hides parameter from parent scope"); + } + }); + } + + public String getFileName() { + return this.fileName; + } + + public IntSet getDependencies() { + return this.dependencies; + } + + public String getInterfaceCode() { + return this.interfaceCode; + } + + public void addDependency(int d) { + this.dependencies.add(d); + } + + @Nonnull + public static ObjectSchema toSchema(@Nonnull SchemaContext context) { + ObjectSchema schema = new ObjectSchema(); + schema.setTitle("Parameters"); + schema.setProperties(Map.of("_ImportStates", new ArraySchema(new StringSchema()))); + schema.setAdditionalProperties(BuilderParameters.Parameter.toSchema(context)); + return schema; + } + + public static class Parameter { + public static final String KEY_VALUE = "Value"; + public static final String KEY_TYPE_HINT = "TypeHint"; + public static final String KEY_VALIDATE = "Validate"; + public static final String KEY_CONFINE = "Confine"; + public static final String KEY_DESCRIPTION = "Description"; + public static final String KEY_PRIVATE = "Private"; + private final BuilderExpression expression; + private final String description; + private final String code; + private List instructionList; + private final boolean isValidation; + private final boolean isPrivate; + + public Parameter(BuilderExpression expression, String description, String code, boolean isValidation, boolean isPrivate) { + this.expression = expression; + this.description = description; + this.code = code; + this.isValidation = isValidation; + this.isPrivate = isPrivate; + } + + public BuilderExpression getExpression() { + return this.expression; + } + + public String getDescription() { + return this.description; + } + + public boolean isValidation() { + return this.isValidation; + } + + public boolean isPrivate() { + return this.isPrivate; + } + + @Nonnull + public static ObjectSchema toSchema(@Nonnull SchemaContext context) { + ObjectSchema props = new ObjectSchema(); + props.setTitle("Parameter"); + LinkedHashMap map = new LinkedHashMap<>(); + map.put("Value", BuilderExpression.toSchema(context)); + map.put("TypeHint", new StringSchema()); + map.put("Validate", new StringSchema()); + map.put("Confine", new StringSchema()); + map.put("Description", new StringSchema()); + map.put("Private", new BooleanSchema()); + props.setProperties(map); + return props; + } + + @Nonnull + private static BuilderParameters.Parameter fromJSON(@Nonnull JsonElement element, @Nonnull BuilderParameters builderParameters, String parameterName) { + JsonObject jsonObject = BuilderBase.expectObject(element); + BuilderExpression expression = BuilderExpression.fromJSON(BuilderBase.expectKey(jsonObject, "Value"), builderParameters, true); + if (expression instanceof BuilderExpressionStaticEmptyArray) { + if (!jsonObject.has("TypeHint")) { + throw new IllegalStateException("TypeHint missing for parameter " + parameterName); + } + + String type = BuilderBase.readString(jsonObject, "TypeHint"); + if ("STRING".equalsIgnoreCase(type)) { + expression = BuilderExpressionStaticStringArray.INSTANCE_EMPTY; + } else if ("NUMBER".equalsIgnoreCase(type)) { + expression = BuilderExpressionStaticNumberArray.INSTANCE_EMPTY; + } else { + if (!"BOOLEAN".equalsIgnoreCase(type)) { + throw new IllegalStateException("TypeHint must be one of STRING, NUMBER, BOOLEAN for parameter " + parameterName); + } + + expression = BuilderExpressionStaticBooleanArray.INSTANCE_EMPTY; + } + } + + String validate = BuilderBase.readString(jsonObject, "Validate", null); + String confine = BuilderBase.readString(jsonObject, "Confine", null); + boolean hasValidate = validate != null; + if (hasValidate && confine != null) { + throw new IllegalStateException("Only either 'Confine' or 'Validate' allowed for parameter " + parameterName); + } else { + String code = hasValidate ? validate : confine; + return new BuilderParameters.Parameter( + expression, BuilderBase.readString(jsonObject, "Description", null), code, hasValidate, BuilderBase.readBoolean(jsonObject, "Private", false) + ); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderSupport.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderSupport.java new file mode 100644 index 0000000..99ee7f0 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderSupport.java @@ -0,0 +1,590 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.hypixel.hytale.common.thread.ticking.Tickable; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.blackboard.view.event.block.BlockEventType; +import com.hypixel.hytale.server.npc.blackboard.view.event.entity.EntityEventType; +import com.hypixel.hytale.server.npc.decisionmaker.stateevaluator.StateEvaluator; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.instructions.Instruction; +import com.hypixel.hytale.server.npc.role.support.EntitySupport; +import com.hypixel.hytale.server.npc.role.support.RoleStats; +import com.hypixel.hytale.server.npc.storage.AlarmStore; +import com.hypixel.hytale.server.npc.util.Alarm; +import com.hypixel.hytale.server.npc.util.Timer; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.valuestore.ValueStore; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntLists; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.IntSets; +import it.unimi.dsi.fastutil.ints.IntStack; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.ArrayDeque; +import java.util.BitSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderSupport { + private final BuilderManager builderManager; + @Nonnull + private final NPCEntity npcEntity; + private final Holder holder; + private final ExecutionContext executionContext; + private boolean requireLeashPosition; + private final SlotMapper flagSlotMapper = new SlotMapper(); + private final SlotMapper beaconSlotMapper = new SlotMapper(); + private final SlotMapper targetSlotMapper = new SlotMapper(true); + private final SlotMapper positionSlotMapper = new SlotMapper(); + private final ReferenceSlotMapper timerSlotMapper = new ReferenceSlotMapper<>(Timer::new); + private final SlotMapper searchRaySlotMapper = new SlotMapper(); + private final SlotMapper parameterSlotMapper = new SlotMapper(); + @Nonnull + private final Object2IntMap instructionSlotMappings; + private final Int2ObjectMap instructionNameMappings = new Int2ObjectOpenHashMap<>(); + private final List instructions = new ObjectArrayList<>(); + private EventSlotMapper playerBlockEventSlotMapper; + private EventSlotMapper npcBlockEventSlotMapper; + private EventSlotMapper playerEntityEventSlotMapper; + private EventSlotMapper npcEntityEventSlotMapper; + private Scope globalScope; + private int currentComponentIndex; + private IntStack componentIndexStack; + private int componentIndexSource; + private int currentAttackIndex; + private Int2IntMap componentLocalStateMachines; + private BitSet localStateMachineAutoResetStates; + private final StateMappingHelper stateHelper; + private List> modifiedStateMap; + private IntSet blackboardBlockSets; + private IntSet blockSensorResetBlockSets; + private boolean requiresAttitudeOverrideMemory; + private boolean trackInteractions; + private InstructionType currentInstructionContext = InstructionType.Component; + private ComponentContext currentComponentContext; + @Nonnull + private final StdScope sensorScope; + @Nonnull + private final Builder roleBuilder; + private final RoleStats roleStats; + private StateEvaluator stateEvaluator; + private ValueStore.Builder valueStoreBuilder; + private final ArrayDeque stateStack = new ArrayDeque<>(); + + public BuilderSupport( + BuilderManager builderManager, + @Nonnull NPCEntity npcEntity, + Holder holder, + ExecutionContext executionContext, + @Nonnull Builder roleBuilder, + RoleStats roleStats + ) { + this.builderManager = builderManager; + this.npcEntity = npcEntity; + this.holder = holder; + this.executionContext = executionContext; + this.roleBuilder = roleBuilder; + this.stateHelper = roleBuilder.getStateMappingHelper(); + this.roleStats = roleStats; + this.sensorScope = EntitySupport.createScope(npcEntity); + this.instructionSlotMappings = new Object2IntOpenHashMap<>(); + this.instructionSlotMappings.defaultReturnValue(Integer.MIN_VALUE); + } + + public BuilderManager getBuilderManager() { + return this.builderManager; + } + + @Nonnull + public NPCEntity getEntity() { + return this.npcEntity; + } + + public Holder getHolder() { + return this.holder; + } + + public ExecutionContext getExecutionContext() { + return this.executionContext; + } + + @Nonnull + public Builder getParentSpawnable() { + return this.roleBuilder; + } + + public void setScope(Scope scope) { + this.getExecutionContext().setScope(scope); + } + + public void setGlobalScope(Scope scope) { + this.globalScope = scope; + } + + public Scope getGlobalScope() { + return this.globalScope; + } + + public void setRequireLeashPosition() { + this.requireLeashPosition = true; + } + + public int getFlagSlot(String name) { + return this.flagSlotMapper.getSlot(name); + } + + public Timer getTimerByName(String name) { + return this.timerSlotMapper.getReference(name); + } + + public int getBeaconMessageSlot(String name) { + return this.beaconSlotMapper.getSlot(name); + } + + public int getTargetSlot(String name) { + return this.targetSlotMapper.getSlot(name); + } + + public Alarm getAlarm(String name) { + NPCEntity npc = this.holder.getComponent(NPCEntity.getComponentType()); + AlarmStore alarmStore = npc.getAlarmStore(); + return alarmStore.get(this.npcEntity, name); + } + + @Nullable + public Object2IntMap getTargetSlotMappings() { + return this.targetSlotMapper.getSlotMappings(); + } + + @Nullable + public Int2ObjectMap getTargetSlotToNameMap() { + return this.targetSlotMapper.getNameMap(); + } + + public int getPositionSlot(String name) { + return this.positionSlotMapper.getSlot(name); + } + + public int getParameterSlot(String name) { + return this.parameterSlotMapper.getSlot(name); + } + + public int getSearchRaySlot(String name) { + return this.searchRaySlotMapper.getSlot(name); + } + + @Nullable + public Vector3d[] allocatePositionSlots() { + return allocatePositionSlots(this.positionSlotMapper); + } + + public boolean requiresLeashPosition() { + return this.requireLeashPosition; + } + + public StateEvaluator getStateEvaluator() { + return this.stateEvaluator; + } + + public void setStateEvaluator(StateEvaluator stateEvaluator) { + this.stateEvaluator = stateEvaluator; + } + + public boolean[] allocateFlags() { + int slotCount = this.flagSlotMapper.slotCount(); + return slotCount == 0 ? null : new boolean[slotCount]; + } + + @Nullable + public Tickable[] allocateTimers() { + List referenceList = this.timerSlotMapper.getReferenceList(); + return referenceList.isEmpty() ? null : referenceList.toArray(Tickable[]::new); + } + + @Nullable + public Vector3d[] allocateSearchRayPositionSlots() { + return allocatePositionSlots(this.searchRaySlotMapper); + } + + @Nonnull + public StdScope getSensorScope() { + return this.sensorScope; + } + + public void setToNewComponent() { + if (this.componentIndexStack == null) { + this.componentIndexStack = new IntArrayList(); + } + + this.componentIndexStack.push(this.currentComponentIndex); + this.currentComponentIndex = this.componentIndexSource++; + } + + public void addComponentLocalStateMachine(int defaultState) { + if (this.componentLocalStateMachines == null) { + this.componentLocalStateMachines = new Int2IntOpenHashMap(); + this.componentLocalStateMachines.defaultReturnValue(Integer.MIN_VALUE); + } + + this.componentLocalStateMachines.put(this.getComponentIndex(), defaultState); + } + + public int getComponentIndex() { + return this.currentComponentIndex; + } + + public void popComponent() { + this.currentComponentIndex = this.componentIndexStack.popInt(); + } + + public boolean hasComponentLocalStateMachines() { + return this.componentLocalStateMachines != null; + } + + public Int2IntMap getComponentLocalStateMachines() { + return this.componentLocalStateMachines; + } + + public void setLocalStateMachineAutoReset() { + if (this.localStateMachineAutoResetStates == null) { + this.localStateMachineAutoResetStates = new BitSet(); + } + + this.localStateMachineAutoResetStates.set(this.getComponentIndex()); + } + + public BitSet getLocalStateMachineAutoResetStates() { + return this.localStateMachineAutoResetStates; + } + + public StateMappingHelper getStateHelper() { + return this.stateHelper; + } + + @Nullable + public Object2IntMap getBeaconSlotMappings() { + return this.beaconSlotMapper.getSlotMappings(); + } + + public boolean hasBlockEventSupport() { + return this.playerBlockEventSlotMapper != null || this.npcBlockEventSlotMapper != null; + } + + public EventSlotMapper getPlayerBlockEventSlotMapper() { + return this.playerBlockEventSlotMapper; + } + + public EventSlotMapper getNPCBlockEventSlotMapper() { + return this.npcBlockEventSlotMapper; + } + + public boolean hasEntityEventSupport() { + return this.playerEntityEventSlotMapper != null || this.npcEntityEventSlotMapper != null; + } + + public EventSlotMapper getPlayerEntityEventSlotMapper() { + return this.playerEntityEventSlotMapper; + } + + public EventSlotMapper getNPCEntityEventSlotMapper() { + return this.npcEntityEventSlotMapper; + } + + public int getInstructionSlot(@Nullable String name) { + int slot = this.instructionSlotMappings.getInt(name); + if (slot == Integer.MIN_VALUE) { + slot = this.instructions.size(); + if (name != null && !name.isEmpty()) { + this.instructionSlotMappings.put(name, slot); + this.instructionNameMappings.put(slot, name); + } + + this.instructions.add(null); + } + + return slot; + } + + public void putInstruction(int slot, Instruction instruction) { + Objects.requireNonNull(instruction, "Instruction cannot be null when putting instruction"); + if (slot < 0 || slot >= this.instructions.size()) { + throw new IllegalArgumentException("Slot for putting instruction must be >= 0 and < the size of the list"); + } else if (this.instructions.get(slot) != null) { + throw new IllegalStateException(String.format("Duplicate instruction with name: %s", this.instructionNameMappings.get(slot))); + } else { + this.instructions.set(slot, instruction); + } + } + + @Nonnull + public Instruction[] getInstructionSlotMappings() { + Instruction[] slots = this.instructions.toArray(Instruction[]::new); + + for (int i = 0; i < slots.length; i++) { + Instruction instruction = slots[i]; + if (instruction == null) { + throw new IllegalStateException("Instruction: " + this.instructionNameMappings.get(i) + " doesn't exist"); + } + } + + return slots; + } + + public void setModifiedStateMap(@Nonnull StateMappingHelper helper, @Nonnull StatePair[] map) { + if (this.modifiedStateMap == null) { + this.modifiedStateMap = new ObjectArrayList<>(); + } + + this.modifiedStateMap.add(Map.entry(helper, map)); + } + + @Nonnull + public StatePair getMappedStatePair(int index) { + StatePair result = null; + + for (int i = this.modifiedStateMap.size() - 1; i >= 0; i--) { + Entry entry = this.modifiedStateMap.get(i); + result = entry.getValue()[index]; + index = entry.getKey().getComponentImportStateIndex(result.getFullStateName()); + if (index < 0) { + break; + } + } + + Objects.requireNonNull(result, "Result should not be null after iterating mapped state pairs"); + return result; + } + + public void popModifiedStateMap() { + this.modifiedStateMap.removeLast(); + } + + public void requireBlockTypeBlackboard(int blockSet) { + if (this.blackboardBlockSets == null) { + this.blackboardBlockSets = new IntOpenHashSet(); + } + + this.blackboardBlockSets.add(blockSet); + } + + public void registerBlockSensorResetAction(int blockSet) { + if (this.blockSensorResetBlockSets == null) { + this.blockSensorResetBlockSets = new IntOpenHashSet(); + } + + this.blockSensorResetBlockSets.add(blockSet); + } + + public boolean requiresBlockTypeBlackboard() { + return this.blackboardBlockSets != null; + } + + @Nonnull + public IntList getBlockTypeBlackboardBlockSets() { + if (this.blockSensorResetBlockSets != null) { + this.blockSensorResetBlockSets + .forEach( + blockSet -> { + if (!this.blackboardBlockSets.contains(blockSet)) { + throw new IllegalStateException( + String.format("No block sensors match BlockSet %s in ResetBlockSensors action", BlockSet.getAssetMap().getAsset(blockSet).getId()) + ); + } + } + ); + } + + IntArrayList blockSets = new IntArrayList(); + blockSets.addAll(this.blackboardBlockSets); + blockSets.trim(); + return IntLists.unmodifiable(blockSets); + } + + public int getBlockEventSlot(BlockEventType type, int blockSet, double maxRange, boolean player) { + if (player) { + if (this.playerBlockEventSlotMapper == null) { + this.playerBlockEventSlotMapper = new EventSlotMapper<>(BlockEventType.class, BlockEventType.VALUES); + } + + return this.playerBlockEventSlotMapper.getEventSlot(type, blockSet, maxRange); + } else { + if (this.npcBlockEventSlotMapper == null) { + this.npcBlockEventSlotMapper = new EventSlotMapper<>(BlockEventType.class, BlockEventType.VALUES); + } + + return this.npcBlockEventSlotMapper.getEventSlot(type, blockSet, maxRange); + } + } + + @Nullable + public IntSet getBlockChangeSets(BlockEventType type) { + IntSet playerEventSets = this.playerBlockEventSlotMapper != null ? this.playerBlockEventSlotMapper.getEventSets().get(type) : null; + IntSet npcEventSets = this.npcBlockEventSlotMapper != null ? this.npcBlockEventSlotMapper.getEventSets().get(type) : null; + if (playerEventSets == null && npcEventSets == null) { + return null; + } else { + IntOpenHashSet set = new IntOpenHashSet(); + if (playerEventSets != null) { + set.addAll(playerEventSets); + } + + if (npcEventSets != null) { + set.addAll(npcEventSets); + } + + set.trim(); + return IntSets.unmodifiable(set); + } + } + + public int getEntityEventSlot(EntityEventType type, int npcGroup, double maxRange, boolean player) { + if (player) { + if (this.playerEntityEventSlotMapper == null) { + this.playerEntityEventSlotMapper = new EventSlotMapper<>(EntityEventType.class, EntityEventType.VALUES); + } + + return this.playerEntityEventSlotMapper.getEventSlot(type, npcGroup, maxRange); + } else { + if (this.npcEntityEventSlotMapper == null) { + this.npcEntityEventSlotMapper = new EventSlotMapper<>(EntityEventType.class, EntityEventType.VALUES); + } + + return this.npcEntityEventSlotMapper.getEventSlot(type, npcGroup, maxRange); + } + } + + @Nullable + public IntSet getEventNPCGroups(EntityEventType type) { + IntSet playerEventSets = this.playerEntityEventSlotMapper != null ? this.playerEntityEventSlotMapper.getEventSets().get(type) : null; + IntSet npcEventSets = this.npcEntityEventSlotMapper != null ? this.npcEntityEventSlotMapper.getEventSets().get(type) : null; + if (playerEventSets == null && npcEventSets == null) { + return null; + } else { + IntOpenHashSet set = new IntOpenHashSet(); + if (playerEventSets != null) { + set.addAll(playerEventSets); + } + + if (npcEventSets != null) { + set.addAll(npcEventSets); + } + + set.trim(); + return IntSets.unmodifiable(set); + } + } + + public void requireAttitudeOverrideMemory() { + this.requiresAttitudeOverrideMemory = true; + } + + public void trackInteractions() { + this.trackInteractions = true; + } + + public boolean isTrackInteractions() { + return this.trackInteractions; + } + + public boolean requiresAttitudeOverrideMemory() { + return this.requiresAttitudeOverrideMemory; + } + + public void setCurrentInstructionContext(InstructionType context) { + this.currentInstructionContext = context; + } + + public InstructionType getCurrentInstructionContext() { + return this.currentInstructionContext; + } + + public ComponentContext getCurrentComponentContext() { + return this.currentComponentContext; + } + + public void setCurrentComponentContext(ComponentContext currentComponentContext) { + this.currentComponentContext = currentComponentContext; + } + + public RoleStats getRoleStats() { + return this.roleStats; + } + + public int getNextAttackIndex() { + return this.currentAttackIndex++; + } + + public int getValueStoreStringSlot(String name) { + if (this.valueStoreBuilder == null) { + this.valueStoreBuilder = new ValueStore.Builder(); + } + + return this.valueStoreBuilder.getStringSlot(name); + } + + public int getValueStoreIntSlot(String name) { + if (this.valueStoreBuilder == null) { + this.valueStoreBuilder = new ValueStore.Builder(); + } + + return this.valueStoreBuilder.getIntSlot(name); + } + + public int getValueStoreDoubleSlot(String name) { + if (this.valueStoreBuilder == null) { + this.valueStoreBuilder = new ValueStore.Builder(); + } + + return this.valueStoreBuilder.getDoubleSlot(name); + } + + public ValueStore.Builder getValueStoreBuilder() { + return this.valueStoreBuilder; + } + + @Nullable + public String getCurrentStateName() { + return this.stateStack.peek(); + } + + public void pushCurrentStateName(@Nonnull String currentStateName) { + this.stateStack.push(currentStateName); + } + + public void popCurrentStateName() { + this.stateStack.pop(); + } + + @Nullable + private static Vector3d[] allocatePositionSlots(@Nonnull SlotMapper mapper) { + int slotCount = mapper.slotCount(); + if (slotCount == 0) { + return null; + } else { + Vector3d[] slots = new Vector3d[slotCount]; + + for (int i = 0; i < slots.length; i++) { + slots[i] = new Vector3d(); + } + + return slots; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderTemplateInteractionVars.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderTemplateInteractionVars.java new file mode 100644 index 0000000..da48531 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderTemplateInteractionVars.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderTemplateInteractionVars extends BuilderCodecObjectHelper> { + public BuilderTemplateInteractionVars() { + super(RootInteraction.class, RootInteraction.CHILD_ASSET_CODEC_MAP, null); + } + + public Map build() { + throw new UnsupportedOperationException(); + } + + @Override + public void readConfig(@Nonnull JsonElement data, @Nonnull ExtraInfo extraInfo) { + super.readConfig(data, extraInfo); + } + + @Nullable + public Map build(@Nonnull ExecutionContext context) { + Map override = context.getInteractionVars(); + return override != null ? override : this.value; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/BuilderValidationHelper.java b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderValidationHelper.java new file mode 100644 index 0000000..77c473a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/BuilderValidationHelper.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.server.npc.decisionmaker.core.Evaluator; +import java.util.List; + +public class BuilderValidationHelper { + private final String name; + private final FeatureEvaluatorHelper featureEvaluatorHelper; + private final InternalReferenceResolver internalReferenceResolver; + private final StateMappingHelper stateMappingHelper; + private final InstructionContextHelper instructionContextHelper; + private final ExtraInfo extraInfo; + private final List> evaluators; + private final List readErrors; + + public BuilderValidationHelper( + String name, + FeatureEvaluatorHelper featureEvaluator, + InternalReferenceResolver internalReferenceResolver, + StateMappingHelper stateMappingHelper, + InstructionContextHelper instructionContextHelper, + ExtraInfo extraInfo, + List> evaluators, + List readErrors + ) { + this.name = name; + this.featureEvaluatorHelper = featureEvaluator; + this.internalReferenceResolver = internalReferenceResolver; + this.stateMappingHelper = stateMappingHelper; + this.instructionContextHelper = instructionContextHelper; + this.extraInfo = extraInfo; + this.evaluators = evaluators; + this.readErrors = readErrors; + } + + public String getName() { + return this.name; + } + + public FeatureEvaluatorHelper getFeatureEvaluatorHelper() { + return this.featureEvaluatorHelper; + } + + public InternalReferenceResolver getInternalReferenceResolver() { + return this.internalReferenceResolver; + } + + public StateMappingHelper getStateMappingHelper() { + return this.stateMappingHelper; + } + + public InstructionContextHelper getInstructionContextHelper() { + return this.instructionContextHelper; + } + + public ExtraInfo getExtraInfo() { + return this.extraInfo; + } + + public List getReadErrors() { + return this.readErrors; + } + + public List> getEvaluators() { + return this.evaluators; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/ComponentContext.java b/src/com/hypixel/hytale/server/npc/asset/builder/ComponentContext.java new file mode 100644 index 0000000..0902f31 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/ComponentContext.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import java.util.EnumSet; +import java.util.function.Supplier; + +public enum ComponentContext implements Supplier { + SensorSelf("self sensor"), + SensorTarget("target sensor"), + SensorEntity("entity sensor"); + + private final String description; + public static final EnumSet NotSelfEntitySensor = EnumSet.of(SensorTarget, SensorEntity); + + private ComponentContext(String description) { + this.description = description; + } + + public String get() { + return this.description; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/EmptyBuilderModifier.java b/src/com/hypixel/hytale/server/npc/asset/builder/EmptyBuilderModifier.java new file mode 100644 index 0000000..a977738 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/EmptyBuilderModifier.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; + +public class EmptyBuilderModifier extends BuilderModifier { + public static final EmptyBuilderModifier INSTANCE = new EmptyBuilderModifier(); + + private EmptyBuilderModifier() { + super(Object2ObjectMaps.EMPTY_MAP, null, null, null, null); + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public int exportedStateCount() { + return 0; + } + + @Override + public void applyComponentStateMap(BuilderSupport support) { + throw new UnsupportedOperationException("applyComponentStateMap is not valid for EmptyBuilderModifier"); + } + + @Override + public void popComponentStateMap(BuilderSupport support) { + throw new UnsupportedOperationException("popComponentStateMap is not valid for EmptyBuilderModifier"); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/EventSlotMapper.java b/src/com/hypixel/hytale/server/npc/asset/builder/EventSlotMapper.java new file mode 100644 index 0000000..89ac661 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/EventSlotMapper.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import it.unimi.dsi.fastutil.ints.Int2DoubleMap; +import it.unimi.dsi.fastutil.ints.Int2DoubleOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.EnumMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class EventSlotMapper> { + @Nonnull + private final Map eventSets; + @Nonnull + private final Map eventSlotMappings; + private final Int2DoubleMap eventSlotRanges = new Int2DoubleOpenHashMap(); + private int nextEventSlot; + + public EventSlotMapper(Class classType, EventType[] types) { + this.eventSets = new EnumMap<>(classType); + this.eventSlotMappings = new EnumMap<>(classType); + } + + @Nonnull + public Map getEventSets() { + return this.eventSets; + } + + @Nonnull + public Map getEventSlotMappings() { + return this.eventSlotMappings; + } + + @Nonnull + public Int2DoubleMap getEventSlotRanges() { + return this.eventSlotRanges; + } + + public int getEventSlotCount() { + return this.nextEventSlot; + } + + public int getEventSlot(EventType type, int set, double maxRange) { + this.eventSets.computeIfAbsent(type, k -> new IntOpenHashSet()).add(set); + Int2IntMap typeSlots = this.eventSlotMappings.computeIfAbsent(type, k -> { + Int2IntOpenHashMap m = new Int2IntOpenHashMap(); + m.defaultReturnValue(Integer.MIN_VALUE); + return m; + }); + int slot = typeSlots.get(set); + if (slot == Integer.MIN_VALUE) { + slot = this.nextEventSlot++; + typeSlots.put(set, slot); + } + + double currentRange = this.eventSlotRanges.getOrDefault(slot, Double.MIN_VALUE); + if (currentRange == Double.MIN_VALUE || currentRange < maxRange) { + this.eventSlotRanges.put(slot, maxRange); + } + + return slot; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/Feature.java b/src/com/hypixel/hytale/server/npc/asset/builder/Feature.java new file mode 100644 index 0000000..16c5c11 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/Feature.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import java.util.EnumSet; +import java.util.function.Supplier; + +public enum Feature implements Supplier { + Player("player target"), + NPC("NPC target"), + Drop("dropped item target"), + Position("vector position"), + Path("path"); + + private final String description; + public static final EnumSet AnyPosition = EnumSet.of(Player, NPC, Drop, Position); + public static final EnumSet AnyEntity = EnumSet.of(Player, NPC, Drop); + public static final EnumSet LiveEntity = EnumSet.of(Player, NPC); + + private Feature(String description) { + this.description = description; + } + + public String get() { + return this.description; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/FeatureEvaluatorHelper.java b/src/com/hypixel/hytale/server/npc/asset/builder/FeatureEvaluatorHelper.java new file mode 100644 index 0000000..33f14c9 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/FeatureEvaluatorHelper.java @@ -0,0 +1,99 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.ProviderEvaluator; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; + +public class FeatureEvaluatorHelper { + @Nonnull + private List evaluators = new ObjectArrayList<>(); + private List> providerReferenceValidators; + private List> componentRequirementValidators; + private boolean locked; + private boolean containsProviderReference; + private boolean isFeatureRequiringComponent; + private boolean disallowParameterProviders; + + public FeatureEvaluatorHelper() { + } + + public FeatureEvaluatorHelper(boolean couldRequireFeature) { + this.isFeatureRequiringComponent = couldRequireFeature; + } + + public boolean isDisallowParameterProviders() { + return this.disallowParameterProviders; + } + + public void add(ProviderEvaluator evaluator) { + this.evaluators.add(evaluator); + } + + public boolean canAddProvider() { + return !this.locked; + } + + @Nonnull + public FeatureEvaluatorHelper lock() { + this.locked = true; + this.evaluators = Collections.unmodifiableList(this.evaluators); + return this; + } + + public void setContainsReference() { + this.containsProviderReference = true; + } + + public void addProviderReferenceValidator(BiConsumer referenceValidator) { + if (this.providerReferenceValidators == null) { + this.providerReferenceValidators = new ObjectArrayList<>(); + } + + this.providerReferenceValidators.add(referenceValidator); + } + + public void addComponentRequirementValidator(BiConsumer validator) { + if (this.componentRequirementValidators == null) { + this.componentRequirementValidators = new ObjectArrayList<>(); + } + + this.componentRequirementValidators.add(validator); + } + + @Nonnull + public List getProviders() { + return this.evaluators; + } + + public boolean requiresProviderReferenceEvaluation() { + return this.containsProviderReference; + } + + public boolean belongsToFeatureRequiringComponent() { + return this.isFeatureRequiringComponent; + } + + public void validateProviderReferences(BuilderManager manager, ExecutionContext context) { + if (this.providerReferenceValidators != null) { + for (BiConsumer validator : this.providerReferenceValidators) { + validator.accept(manager, context); + } + } + } + + public void validateComponentRequirements(FeatureEvaluatorHelper providers, ExecutionContext context) { + if (this.componentRequirementValidators != null) { + for (BiConsumer validator : this.componentRequirementValidators) { + validator.accept(providers, context); + } + } + } + + public void disallowParameterProviders() { + this.disallowParameterProviders = true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/InstructionContextHelper.java b/src/com/hypixel/hytale/server/npc/asset/builder/InstructionContextHelper.java new file mode 100644 index 0000000..540bf80 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/InstructionContextHelper.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InstructionContextHelper { + private final InstructionType context; + private ComponentContext componentContext; + private List> componentContextEvaluators; + + public InstructionContextHelper(InstructionType context) { + this.context = context; + } + + public boolean isComponent() { + return this.context == InstructionType.Component; + } + + public void setComponentContext(ComponentContext context) { + this.componentContext = context; + } + + public boolean isInCorrectInstruction(@Nonnull EnumSet validTypes) { + return validTypes.contains(this.context); + } + + public static boolean isInCorrectInstruction(@Nonnull EnumSet validTypes, InstructionType instructionContext) { + return validTypes.contains(instructionContext); + } + + public boolean extraContextMatches(@Nullable EnumSet contexts) { + return contexts == null || contexts.contains(this.componentContext); + } + + public static boolean extraContextMatches(@Nullable EnumSet validContexts, ComponentContext context) { + return validContexts == null || validContexts.contains(context); + } + + public void addComponentContextEvaluator(BiConsumer evaluator) { + if (this.componentContextEvaluators == null) { + this.componentContextEvaluators = new ObjectArrayList<>(); + } + + this.componentContextEvaluators.add(evaluator); + } + + public void validateComponentContext(InstructionType instructionContext, ComponentContext componentContext) { + if (!this.isComponent()) { + throw new IllegalStateException("Calling validateComponentContext on a InstructionContextHelper that is not part of a component!"); + } else if (this.componentContextEvaluators != null) { + for (BiConsumer evaluator : this.componentContextEvaluators) { + evaluator.accept(instructionContext, componentContext); + } + } + } + + public InstructionType getInstructionContext() { + return this.context; + } + + public ComponentContext getComponentContext() { + return this.componentContext; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/InstructionType.java b/src/com/hypixel/hytale/server/npc/asset/builder/InstructionType.java new file mode 100644 index 0000000..ce2bb5f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/InstructionType.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import java.util.EnumSet; +import java.util.function.Supplier; + +public enum InstructionType implements Supplier { + Default("the default behaviour instruction"), + Interaction("the interaction instruction"), + Death("the death instruction"), + Component("a component"), + StateTransitions("state transition actions"); + + private final String description; + public static final EnumSet Any = EnumSet.allOf(InstructionType.class); + public static final EnumSet MotionAllowedInstructions = EnumSet.of(Default); + public static final EnumSet StateChangeAllowedInstructions = EnumSet.of(Default, Interaction, Death, Component); + + private InstructionType(String description) { + this.description = description; + } + + public String get() { + return this.description; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/InternalReferenceResolver.java b/src/com/hypixel/hytale/server/npc/asset/builder/InternalReferenceResolver.java new file mode 100644 index 0000000..b160f4a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/InternalReferenceResolver.java @@ -0,0 +1,121 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.hypixel.hytale.server.npc.instructions.Instruction; +import com.hypixel.hytale.server.npc.instructions.builders.BuilderInstructionReference; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntIterator; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class InternalReferenceResolver { + private final List builders = new ObjectArrayList<>(); + @Nullable + private Object2IntMap indexMap; + @Nullable + private Int2ObjectMap nameMap = new Int2ObjectOpenHashMap<>(); + @Nullable + private IntSet recordedDependencies; + + public InternalReferenceResolver() { + this.indexMap = new Object2IntOpenHashMap<>(); + this.indexMap.defaultReturnValue(Integer.MIN_VALUE); + } + + public int getOrCreateIndex(String name) { + int index = this.indexMap.getInt(name); + if (index == Integer.MIN_VALUE) { + index = this.builders.size(); + this.indexMap.put(name, index); + this.nameMap.put(index, name); + this.builders.add(null); + } + + if (this.recordedDependencies != null) { + this.recordedDependencies.add(index); + } + + return index; + } + + public void setRecordDependencies() { + this.recordedDependencies = new IntOpenHashSet(); + } + + @Nullable + public IntSet getRecordedDependenices() { + return this.recordedDependencies; + } + + public void stopRecordingDependencies() { + this.recordedDependencies = null; + } + + public void addBuilder(int index, BuilderInstructionReference builder) { + Objects.requireNonNull(builder, "Builder cannot be null when adding as a reference"); + if (index < 0 || index >= this.builders.size()) { + throw new IllegalArgumentException("Slot for putting builder must be >= 0 and < the size of the list"); + } else if (this.builders.get(index) != null) { + throw new IllegalStateException(String.format("Duplicate internal reference builder with name: %s", this.nameMap.get(index))); + } else { + this.builders.set(index, builder); + } + } + + public void validateInternalReferences(String configName, @Nonnull List errors) { + for (int i = 0; i < this.builders.size(); i++) { + BuilderInstructionReference builder = this.builders.get(i); + if (builder == null) { + errors.add(configName + ": Internal reference builder: " + this.nameMap.get(i) + " doesn't exist"); + } else { + try { + this.validateNoCycles(builder, i, new IntArrayList()); + } catch (IllegalArgumentException var6) { + errors.add(configName + ": " + var6.getMessage()); + } + } + } + } + + private void validateNoCycles(@Nonnull BuilderInstructionReference builder, int index, @Nonnull IntArrayList path) { + if (path.contains(index)) { + throw new IllegalArgumentException("Cyclic reference detected for internal component reference: " + this.nameMap.get(index)); + } else { + path.add(index); + IntIterator i = builder.getInternalDependencies().iterator(); + + while (i.hasNext()) { + int dependency = i.nextInt(); + BuilderInstructionReference nextBuilder = this.builders.get(dependency); + if (nextBuilder == null) { + throw new IllegalStateException("Reference to internal reference builder: " + this.nameMap.get(dependency) + " which doesn't exist"); + } + + this.validateNoCycles(nextBuilder, dependency, path); + } + + path.removeInt(path.size() - 1); + } + } + + public Builder getBuilder(int index, Class classType) { + if (classType != Instruction.class) { + throw new IllegalArgumentException("Internal references are currently only supported for instruction list"); + } else { + return this.builders.get(index); + } + } + + public void optimise() { + this.indexMap = null; + this.nameMap = null; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/ReferenceSlotMapper.java b/src/com/hypixel/hytale/server/npc/asset/builder/ReferenceSlotMapper.java new file mode 100644 index 0000000..53eed74 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/ReferenceSlotMapper.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.function.Supplier; + +public class ReferenceSlotMapper extends SlotMapper { + private final List list = new ObjectArrayList<>(); + private final Supplier slotSupplier; + + public ReferenceSlotMapper(Supplier slotSupplier) { + this.slotSupplier = slotSupplier; + } + + public ReferenceSlotMapper(Supplier slotSupplier, boolean trackNames) { + super(trackNames); + this.slotSupplier = slotSupplier; + } + + public T getReference(String name) { + int slot = this.getSlot(name); + if (slot < this.list.size()) { + return this.list.get(slot); + } else { + T object = this.slotSupplier.get(); + this.list.add(object); + return object; + } + } + + public List getReferenceList() { + return this.list; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/SlotMapper.java b/src/com/hypixel/hytale/server/npc/asset/builder/SlotMapper.java new file mode 100644 index 0000000..c119292 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/SlotMapper.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import javax.annotation.Nullable; + +public class SlotMapper { + public static final int NO_SLOT = Integer.MIN_VALUE; + private final Object2IntMap mappings = new Object2IntOpenHashMap<>(); + @Nullable + private final Int2ObjectMap nameMap; + private int nextSlot; + + public SlotMapper() { + this(false); + } + + public SlotMapper(boolean trackNames) { + this.nameMap = trackNames ? new Int2ObjectOpenHashMap<>() : null; + this.mappings.defaultReturnValue(Integer.MIN_VALUE); + } + + public int getSlot(String name) { + int slot = this.mappings.getInt(name); + if (slot == Integer.MIN_VALUE) { + slot = this.nextSlot++; + this.mappings.put(name, slot); + if (this.nameMap != null) { + this.nameMap.put(slot, name); + } + } + + return slot; + } + + public int slotCount() { + return this.mappings.size(); + } + + @Nullable + public Object2IntMap getSlotMappings() { + return this.mappings.isEmpty() ? null : this.mappings; + } + + @Nullable + public Int2ObjectMap getNameMap() { + return this.nameMap; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/SpawnableWithModelBuilder.java b/src/com/hypixel/hytale/server/npc/asset/builder/SpawnableWithModelBuilder.java new file mode 100644 index 0000000..0e1f140 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/SpawnableWithModelBuilder.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.hypixel.hytale.server.spawning.ISpawnableWithModel; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; + +public abstract class SpawnableWithModelBuilder extends BuilderBase implements ISpawnableWithModel { + private IntSet dynamicDependencies; + + public SpawnableWithModelBuilder() { + } + + @Override + public boolean hasDynamicDependencies() { + return this.dynamicDependencies != null; + } + + @Override + public void addDynamicDependency(int builderIndex) { + if (this.dynamicDependencies == null) { + this.dynamicDependencies = new IntOpenHashSet(); + } + + this.dynamicDependencies.add(builderIndex); + } + + @Override + public IntSet getDynamicDependencies() { + return this.dynamicDependencies; + } + + @Override + public void clearDynamicDependencies() { + if (this.dynamicDependencies != null) { + this.dynamicDependencies.clear(); + } + } + + @Override + public boolean isSpawnable() { + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/StateMappingHelper.java b/src/com/hypixel/hytale/server/npc/asset/builder/StateMappingHelper.java new file mode 100644 index 0000000..c4afd9b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/StateMappingHelper.java @@ -0,0 +1,457 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.hypixel.hytale.server.npc.asset.builder.validators.StateStringValidator; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import java.util.ArrayDeque; +import java.util.BitSet; +import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StateMappingHelper { + public static final String DEFAULT_STATE = "start"; + public static final String DEFAULT_SUB_STATE = "Default"; + public static final String DEFAULT_STATE_PARAMETER = "DefaultState"; + public static final String STATE_CHANGE_RESET_PARAMETER = "ResetOnStateChange"; + @Nullable + private StateMappingHelper.StateMap mainStateMap = new StateMappingHelper.StateMap(); + private int[] allMainStates; + @Nullable + private Int2ObjectOpenHashMap subStateMap = new Int2ObjectOpenHashMap<>(); + private int depth; + @Nullable + private ArrayDeque currentParentState = new ArrayDeque<>(); + private boolean component = true; + private boolean hasStateEvaluator; + private boolean requiresStateEvaluator; + private String defaultSubState; + private String defaultComponentLocalState; + private int defaultComponentLocalStateIndex; + private boolean componentLocalStateAutoReset; + private Object2IntOpenHashMap componentImportStateMappings; + private StateMappingHelper.SingletonStateMap singletonDefaultStateMap; + + public StateMappingHelper() { + } + + public int[] getAllMainStates() { + return this.allMainStates; + } + + public int getHighestSubStateIndex(int mainStateIndex) { + return this.subStateMap.get(mainStateIndex).size() - 1; + } + + public void getAndPutSensorIndex(String state, String subState, @Nonnull BiConsumer setter) { + this.currentParentState.push(new StateMappingHelper.StateDepth(this.depth, state)); + this.getAndPutIndex(state, subState, setter, this.mainStateMap::getAndPutSensorIndex, (i, s) -> { + StateMappingHelper.IStateMap helper = this.initialiseDefaultSubStates(i); + return helper.getAndPutSensorIndex(s); + }); + } + + public void getAndPutSetterIndex(String state, String subState, @Nonnull BiConsumer setter) { + this.getAndPutIndex(state, subState, setter, this.mainStateMap::getAndPutSetterIndex, (i, s) -> { + StateMappingHelper.IStateMap helper = this.initialiseDefaultSubStates(i); + return helper.getAndPutSetterIndex(s); + }); + } + + public void getAndPutStateRequirerIndex(String state, String subState, @Nonnull BiConsumer setter) { + this.getAndPutIndex(state, subState, setter, this.mainStateMap::getAndPutRequirerIndex, (i, s) -> { + StateMappingHelper.IStateMap helper = this.initialiseDefaultSubStates(i); + return helper.getAndPutRequirerIndex(s); + }); + } + + private void getAndPutIndex( + String state, + @Nullable String subState, + @Nonnull BiConsumer setter, + @Nonnull Function mainStateFunction, + @Nonnull BiFunction subStateFunction + ) { + Integer index = mainStateFunction.apply(state); + if (subState == null) { + setter.accept(index, -1); + } else { + Integer subStateIndex = subStateFunction.apply(index, subState); + setter.accept(index, subStateIndex); + } + } + + @Nonnull + private StateMappingHelper.IStateMap initialiseDefaultSubStates(int index) { + return this.subStateMap.computeIfAbsent(index, v -> { + StateMappingHelper.StateMap map = new StateMappingHelper.StateMap(); + map.getAndPutSensorIndex(this.defaultSubState); + map.getAndPutSetterIndex(this.defaultSubState); + return map; + }); + } + + public void validate(String configName, @Nonnull List errors) { + this.mainStateMap.validate(configName, null, errors); + this.subStateMap.forEach((i, v) -> v.validate(configName, this.mainStateMap.getStateName(i), errors)); + if (!this.hasStateEvaluator && this.requiresStateEvaluator) { + errors.add(String.format("%s: Expects a state evaluator but does not have one defined", configName)); + } + } + + public int getStateIndex(String state) { + return this.mainStateMap.getStateIndex(state); + } + + public int getSubStateIndex(int index, String subState) { + return this.subStateMap.get(index).getStateIndex(subState); + } + + public String getStateName(int index) { + return this.mainStateMap.getStateName(index); + } + + public String getSubStateName(int index, int subState) { + return this.subStateMap.get(index).getStateName(subState); + } + + @Nullable + public String getCurrentParentState() { + return this.currentParentState.isEmpty() ? null : this.currentParentState.peek().state; + } + + public void increaseDepth() { + this.depth++; + } + + public void decreaseDepth() { + this.depth--; + if (!this.currentParentState.isEmpty() && this.depth < this.currentParentState.peek().depth) { + this.currentParentState.pop(); + } + } + + public void setDefaultSubState(String subState) { + this.defaultSubState = subState; + } + + public String getDefaultSubState() { + return this.defaultSubState; + } + + public void setNotComponent() { + this.mainStateMap.getAndPutSensorIndex("start"); + this.mainStateMap.getAndPutSetterIndex("start"); + this.component = false; + } + + public boolean isComponent() { + return this.component; + } + + public boolean hasComponentStates() { + return this.component && this.mainStateMap != null; + } + + public void initialiseComponentState(@Nonnull BuilderSupport support) { + support.setToNewComponent(); + support.addComponentLocalStateMachine(this.defaultComponentLocalStateIndex); + if (this.componentLocalStateAutoReset) { + support.setLocalStateMachineAutoReset(); + } + } + + public void popComponentState(@Nonnull BuilderSupport support) { + support.popComponent(); + } + + public void readComponentDefaultLocalState(@Nonnull JsonObject data) { + String state = BuilderBase.readString(data, "DefaultState", null); + if (state != null) { + StateStringValidator validator = StateStringValidator.get(); + if (!validator.test(state)) { + throw new IllegalStateException(validator.errorMessage(state)); + } + + if (validator.hasMainState()) { + throw new IllegalStateException(String.format("Default component local state must be defined with a '.' prefix: %s", validator.getMainState())); + } + + this.defaultComponentLocalState = validator.getSubState(); + this.defaultComponentLocalStateIndex = this.mainStateMap.getAndPutSetterIndex(this.defaultComponentLocalState); + } + + JsonElement resetValue = data.get("ResetOnStateChange"); + if (resetValue != null) { + this.componentLocalStateAutoReset = BuilderBase.expectBooleanElement(resetValue, "ResetOnStateChange"); + } + } + + public boolean hasDefaultLocalState() { + return this.defaultComponentLocalState != null; + } + + public String getDefaultLocalState() { + return this.defaultComponentLocalState; + } + + public void setComponentImportStateMappings(@Nonnull JsonArray states) { + this.componentImportStateMappings = new Object2IntOpenHashMap<>(); + this.componentImportStateMappings.defaultReturnValue(Integer.MIN_VALUE); + StateStringValidator validator = StateStringValidator.mainStateOnly(); + + for (int i = 0; i < states.size(); i++) { + String string = states.get(i).getAsString(); + if (!validator.test(string)) { + throw new IllegalStateException(validator.errorMessage(string)); + } + + this.getAndPutSensorIndex(validator.getMainState(), null, (m, s) -> {}); + this.componentImportStateMappings.put(validator.getMainState(), i); + } + + this.componentImportStateMappings.trim(); + } + + public int getComponentImportStateIndex(String state) { + return this.componentImportStateMappings == null ? Integer.MIN_VALUE : this.componentImportStateMappings.getInt(state); + } + + public int importedStateCount() { + return this.componentImportStateMappings == null ? 0 : this.componentImportStateMappings.size(); + } + + public void setRequiresStateEvaluator() { + this.requiresStateEvaluator = true; + } + + public void setHasStateEvaluator() { + this.hasStateEvaluator = true; + } + + public void optimise() { + this.currentParentState = null; + if (this.mainStateMap.isEmpty()) { + this.mainStateMap = null; + this.subStateMap = null; + } else { + ObjectIterator> iterator = Int2ObjectMaps.fastIterator(this.subStateMap); + + while (iterator.hasNext()) { + Entry next = iterator.next(); + StateMappingHelper.IStateMap map = next.getValue(); + if (map.size() == 1) { + if (this.singletonDefaultStateMap == null) { + this.singletonDefaultStateMap = new StateMappingHelper.SingletonStateMap(this.defaultSubState); + } + + next.setValue(this.singletonDefaultStateMap); + } else { + map.optimise(); + } + } + + this.subStateMap.trim(); + this.mainStateMap.optimise(); + this.allMainStates = this.mainStateMap.stateNameMap.keySet().toIntArray(); + } + } + + private interface IStateMap { + int getAndPutSensorIndex(String var1); + + int getAndPutSetterIndex(String var1); + + int getAndPutRequirerIndex(String var1); + + int getStateIndex(String var1); + + String getStateName(int var1); + + void validate(String var1, String var2, List var3); + + boolean isEmpty(); + + int size(); + + void optimise(); + } + + private static class SingletonStateMap implements StateMappingHelper.IStateMap { + private final String stateName; + + private SingletonStateMap(String name) { + this.stateName = name; + } + + @Override + public int getAndPutSensorIndex(String state) { + return 0; + } + + @Override + public int getAndPutSetterIndex(String targetState) { + return 0; + } + + @Override + public int getAndPutRequirerIndex(String targetState) { + return 0; + } + + @Override + public int getStateIndex(@Nonnull String state) { + return !state.equals(this.stateName) ? Integer.MIN_VALUE : 0; + } + + @Override + public String getStateName(int index) { + return this.stateName; + } + + @Override + public void validate(String configName, String parent, List errors) { + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public int size() { + return 1; + } + + @Override + public void optimise() { + } + } + + private static class StateDepth { + private final int depth; + private final String state; + + private StateDepth(int depth, String state) { + this.depth = depth; + this.state = state; + } + } + + private static class StateMap implements StateMappingHelper.IStateMap { + private final Int2ObjectOpenHashMap stateNameMap = new Int2ObjectOpenHashMap<>(); + @Nonnull + private final Object2IntOpenHashMap stateIndexMap; + private int stateIndexSource; + @Nullable + private BitSet stateSensors = new BitSet(); + @Nullable + private BitSet stateSetters = new BitSet(); + @Nullable + private BitSet stateRequirers = new BitSet(); + + private StateMap() { + this.stateIndexMap = new Object2IntOpenHashMap<>(); + this.stateIndexMap.defaultReturnValue(Integer.MIN_VALUE); + } + + private int getOrCreateIndex(String name) { + int index = this.stateIndexMap.getInt(name); + if (index == Integer.MIN_VALUE) { + index = this.stateIndexSource++; + this.stateIndexMap.put(name, index); + this.stateNameMap.put(index, name); + } + + return index; + } + + @Override + public int getAndPutSensorIndex(String state) { + int index = this.getOrCreateIndex(state); + this.stateSensors.set(index); + return index; + } + + @Override + public int getAndPutSetterIndex(String targetState) { + int index = this.getOrCreateIndex(targetState); + this.stateSetters.set(index); + return index; + } + + @Override + public int getAndPutRequirerIndex(String targetState) { + int index = this.getOrCreateIndex(targetState); + this.stateRequirers.set(index); + return index; + } + + @Override + public int getStateIndex(String state) { + Objects.requireNonNull(state, "State must not be null when fetching index"); + return this.stateIndexMap.getInt(state); + } + + @Override + public String getStateName(int index) { + return this.stateNameMap.get(index); + } + + @Override + public void validate(String configName, @Nullable String parent, @Nonnull List errors) { + this.stateSetters.xor(this.stateSensors); + if (this.stateSetters.cardinality() > 0) { + errors.add( + String.format( + "%s: State sensor or State setter action/motion exists without accompanying state/setter: %s%s", + configName, + parent != null ? parent + "." : "", + this.stateNameMap.get(this.stateSetters.nextSetBit(0)) + ) + ); + } + + this.stateRequirers.andNot(this.stateSensors); + if (this.stateRequirers.cardinality() > 0) { + errors.add( + String.format( + "%s: State required by a parameter does not exist: %s%s", + configName, + parent != null ? parent + "." : "", + this.stateNameMap.get(this.stateRequirers.nextSetBit(0)) + ) + ); + } + } + + @Override + public boolean isEmpty() { + return this.stateNameMap.isEmpty(); + } + + @Override + public int size() { + return this.stateNameMap.size(); + } + + @Override + public void optimise() { + this.stateSensors = null; + this.stateSetters = null; + this.stateRequirers = null; + this.stateNameMap.trim(); + this.stateIndexMap.trim(); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/StatePair.java b/src/com/hypixel/hytale/server/npc/asset/builder/StatePair.java new file mode 100644 index 0000000..226dc24 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/StatePair.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.npc.asset.builder; + +public class StatePair { + private final String fullStateName; + private final int state; + private final int subState; + + public StatePair(String fullStateName, int state, int subState) { + this.fullStateName = fullStateName; + this.state = state; + this.subState = subState; + } + + public String getFullStateName() { + return this.fullStateName; + } + + public int getState() { + return this.state; + } + + public int getSubState() { + return this.subState; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpression.java b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpression.java new file mode 100644 index 0000000..a894c54 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpression.java @@ -0,0 +1,194 @@ +package com.hypixel.hytale.server.npc.asset.builder.expression; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.hypixel.hytale.codec.schema.NamedSchema; +import com.hypixel.hytale.codec.schema.SchemaContext; +import com.hypixel.hytale.codec.schema.SchemaConvertable; +import com.hypixel.hytale.codec.schema.config.ArraySchema; +import com.hypixel.hytale.codec.schema.config.BooleanSchema; +import com.hypixel.hytale.codec.schema.config.NumberSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class BuilderExpression { + public static final String STATIC = ""; + + public BuilderExpression() { + } + + public abstract ValueType getType(); + + public abstract boolean isStatic(); + + public double getNumber(ExecutionContext executionContext) { + throw new IllegalStateException("BuilderExpression: Reading number is not supported"); + } + + public String getString(ExecutionContext executionContext) { + throw new IllegalStateException("BuilderExpression: Reading string is not supported"); + } + + public boolean getBoolean(ExecutionContext executionContext) { + throw new IllegalStateException("BuilderExpression: Reading boolean is not supported"); + } + + public double[] getNumberArray(ExecutionContext executionContext) { + throw new IllegalStateException("BuilderExpression: Reading number array is not supported"); + } + + public int[] getIntegerArray(ExecutionContext executionContext) { + throw new IllegalStateException("BuilderExpression: Reading integer is not supported"); + } + + @Nullable + public String[] getStringArray(ExecutionContext executionContext) { + throw new IllegalStateException("BuilderExpression: Reading string array is not supported"); + } + + public boolean[] getBooleanArray(ExecutionContext executionContext) { + throw new IllegalStateException("BuilderExpression: Reading boolean array is not supported"); + } + + public void addToScope(String name, StdScope scope) { + throw new IllegalStateException("This type of builder expression cannot be added to a scope"); + } + + public void updateScope(StdScope scope, String name, ExecutionContext executionContext) { + throw new IllegalStateException("This type of builder expression cannot update a scope"); + } + + public String getExpression() { + return ""; + } + + @Nonnull + public static BuilderExpression fromOperand(@Nonnull ExecutionContext.Operand operand) { + return (BuilderExpression)(switch (operand.type) { + case NUMBER -> new BuilderExpressionStaticNumber(operand.number); + case STRING -> new BuilderExpressionStaticString(operand.string); + case BOOLEAN -> new BuilderExpressionStaticBoolean(operand.bool); + case EMPTY_ARRAY -> BuilderExpressionStaticEmptyArray.INSTANCE; + case NUMBER_ARRAY -> new BuilderExpressionStaticNumberArray(operand.numberArray); + case STRING_ARRAY -> new BuilderExpressionStaticStringArray(operand.stringArray); + case BOOLEAN_ARRAY -> new BuilderExpressionStaticBooleanArray(operand.boolArray); + default -> throw new IllegalStateException("Operand cannot be converted to builder expression"); + }); + } + + @Nonnull + public static BuilderExpression fromJSON(@Nonnull JsonElement jsonElement, @Nonnull BuilderParameters builderParameters, boolean constantsOnly) { + BuilderExpression builderExpression = fromJSON(jsonElement, builderParameters); + if (constantsOnly && !builderExpression.isStatic()) { + throw new IllegalArgumentException("Only constant string, number or boolean or arrays allowed, found: " + jsonElement); + } else { + return builderExpression; + } + } + + @Nonnull + public static BuilderExpression fromJSON(@Nonnull JsonElement jsonElement, @Nonnull BuilderParameters builderParameters, ValueType expectedType) { + BuilderExpression builderExpression = fromJSON(jsonElement, builderParameters); + if (!ValueType.isAssignableType(builderExpression.getType(), expectedType)) { + throw new IllegalStateException( + "Expression type mismatch. Got " + builderExpression.getType() + " but expected " + expectedType + " from: " + jsonElement + ); + } else { + return builderExpression; + } + } + + @Nonnull + public static BuilderExpression fromJSON(@Nonnull JsonElement jsonElement, @Nonnull BuilderParameters builderParameters) { + if (jsonElement.isJsonObject()) { + return BuilderExpressionDynamic.fromJSON(jsonElement, builderParameters); + } else { + if (jsonElement.isJsonPrimitive()) { + BuilderExpression jsonPrimitive = readJSONPrimitive(jsonElement); + if (jsonPrimitive != null) { + return jsonPrimitive; + } + } else if (jsonElement.isJsonArray()) { + BuilderExpression result = readStaticArray(jsonElement); + if (result != null) { + return result; + } + } + + throw new IllegalArgumentException("Illegal JSON value for expression: " + jsonElement); + } + } + + @Nullable + private static BuilderExpression readJSONPrimitive(@Nonnull JsonElement jsonElement) { + JsonPrimitive jsonPrimitive = jsonElement.getAsJsonPrimitive(); + if (jsonPrimitive.isString()) { + return new BuilderExpressionStaticString(jsonPrimitive.getAsString()); + } else if (jsonPrimitive.isBoolean()) { + return new BuilderExpressionStaticBoolean(jsonPrimitive.getAsBoolean()); + } else { + return jsonPrimitive.isNumber() ? new BuilderExpressionStaticNumber(jsonPrimitive.getAsDouble()) : null; + } + } + + @Nullable + private static BuilderExpression readStaticArray(@Nonnull JsonElement jsonElement) { + JsonArray jsonArray = jsonElement.getAsJsonArray(); + if (jsonArray.isEmpty()) { + return BuilderExpressionStaticEmptyArray.INSTANCE; + } else { + JsonElement firstElement = jsonArray.get(0); + BuilderExpression result = null; + if (firstElement.isJsonPrimitive()) { + JsonPrimitive jsonPrimitive = firstElement.getAsJsonPrimitive(); + if (jsonPrimitive.isString()) { + result = BuilderExpressionStaticStringArray.fromJSON(jsonArray); + } else if (jsonPrimitive.isBoolean()) { + result = BuilderExpressionStaticBooleanArray.fromJSON(jsonArray); + } else if (jsonPrimitive.isNumber()) { + result = BuilderExpressionStaticNumberArray.fromJSON(jsonArray); + } + } + + return result; + } + } + + public void compile(BuilderParameters builderParameters) { + } + + @Nonnull + public static Schema toSchema(@Nonnull SchemaContext context) { + return context.refDefinition(BuilderExpression.SchemaGenerator.INSTANCE); + } + + private static class SchemaGenerator implements SchemaConvertable, NamedSchema { + @Nonnull + public static BuilderExpression.SchemaGenerator INSTANCE = new BuilderExpression.SchemaGenerator(); + + private SchemaGenerator() { + } + + @Nonnull + @Override + public String getSchemaName() { + return "NPC:Type:BuilderExpression"; + } + + @Nonnull + @Override + public Schema toSchema(@Nonnull SchemaContext context) { + Schema s = new Schema(); + s.setTitle("Expression"); + s.setAnyOf(new ArraySchema(), new NumberSchema(), new StringSchema(), new BooleanSchema(), BuilderExpressionDynamic.toSchema()); + return s; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamic.java b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamic.java new file mode 100644 index 0000000..1ed5514 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamic.java @@ -0,0 +1,87 @@ +package com.hypixel.hytale.server.npc.asset.builder.expression; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.hypixel.hytale.codec.schema.config.ObjectSchema; +import com.hypixel.hytale.codec.schema.config.Schema; +import com.hypixel.hytale.codec.schema.config.StringSchema; +import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import java.util.Map; +import java.util.Objects; +import javax.annotation.Nonnull; + +public abstract class BuilderExpressionDynamic extends BuilderExpression { + public static final String KEY_COMPUTE = "Compute"; + private final String expression; + private final ExecutionContext.Instruction[] instructionSequence; + + public BuilderExpressionDynamic(String expression, ExecutionContext.Instruction[] instructionSequence) { + this.expression = expression; + this.instructionSequence = instructionSequence; + } + + @Override + public boolean isStatic() { + return false; + } + + @Override + public String getExpression() { + return this.expression; + } + + protected void execute(@Nonnull ExecutionContext executionContext) { + Objects.requireNonNull(executionContext, "ExecutionContext not initialised"); + if (executionContext.execute(this.instructionSequence) != this.getType()) { + throw new IllegalStateException( + "Expression returned wrong type " + executionContext.getType() + " but expected " + this.getType() + ": " + this.expression + ); + } + } + + @Nonnull + public static BuilderExpression fromJSON(@Nonnull JsonElement jsonElement, @Nonnull BuilderParameters builderParameters) { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + JsonElement computeValue = jsonObject.get("Compute"); + if (computeValue == null) { + throw new IllegalArgumentException("JSON expression missing 'Compute' member: " + jsonElement); + } else { + String expression = BuilderBase.expectStringElement(computeValue, "Compute"); + ValueType type = builderParameters.compile(expression); + ExecutionContext.Operand operand = builderParameters.getConstantOperand(); + if (operand != null) { + return BuilderExpression.fromOperand(operand); + } else { + ExecutionContext.Instruction[] instructionSequence = builderParameters.getInstructions().toArray(ExecutionContext.Instruction[]::new); + + return (BuilderExpression)(switch (type) { + case NUMBER -> new BuilderExpressionDynamicNumber(expression, instructionSequence); + case STRING -> new BuilderExpressionDynamicString(expression, instructionSequence); + case BOOLEAN -> new BuilderExpressionDynamicBoolean(expression, instructionSequence); + case NUMBER_ARRAY -> new BuilderExpressionDynamicNumberArray(expression, instructionSequence); + case STRING_ARRAY -> new BuilderExpressionDynamicStringArray(expression, instructionSequence); + case BOOLEAN_ARRAY -> new BuilderExpressionDynamicBooleanArray(expression, instructionSequence); + default -> throw new IllegalStateException("Unable to create dynamic expression from type " + type); + }); + } + } + } + + @Nonnull + public static Schema toSchema() { + ObjectSchema s = new ObjectSchema(); + s.setTitle("ExpressionDynamic"); + s.setProperties(Map.of("Compute", new StringSchema())); + s.setRequired("Compute"); + s.setAdditionalProperties(false); + return s; + } + + @Nonnull + public static Schema computableSchema(Schema toWrap) { + return Schema.anyOf(toWrap, toSchema()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicBoolean.java b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicBoolean.java new file mode 100644 index 0000000..f9454b3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicBoolean.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.npc.asset.builder.expression; + +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; + +public class BuilderExpressionDynamicBoolean extends BuilderExpressionDynamic { + public BuilderExpressionDynamicBoolean(String expression, ExecutionContext.Instruction[] instructionSequence) { + super(expression, instructionSequence); + } + + @Nonnull + @Override + public ValueType getType() { + return ValueType.BOOLEAN; + } + + @Override + public boolean getBoolean(@Nonnull ExecutionContext executionContext) { + this.execute(executionContext); + return executionContext.popBoolean(); + } + + @Override + public void updateScope(@Nonnull StdScope scope, String name, @Nonnull ExecutionContext executionContext) { + scope.changeValue(name, this.getBoolean(executionContext)); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicBooleanArray.java b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicBooleanArray.java new file mode 100644 index 0000000..ea2899b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicBooleanArray.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.npc.asset.builder.expression; + +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; + +public class BuilderExpressionDynamicBooleanArray extends BuilderExpressionDynamic { + public BuilderExpressionDynamicBooleanArray(String expression, ExecutionContext.Instruction[] instructionSequence) { + super(expression, instructionSequence); + } + + @Nonnull + @Override + public ValueType getType() { + return ValueType.BOOLEAN_ARRAY; + } + + @Override + public boolean[] getBooleanArray(@Nonnull ExecutionContext executionContext) { + this.execute(executionContext); + return executionContext.popBooleanArray(); + } + + @Override + public void updateScope(@Nonnull StdScope scope, String name, @Nonnull ExecutionContext executionContext) { + scope.changeValue(name, this.getBooleanArray(executionContext)); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicNumber.java b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicNumber.java new file mode 100644 index 0000000..fdc2a4f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicNumber.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.npc.asset.builder.expression; + +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; + +public class BuilderExpressionDynamicNumber extends BuilderExpressionDynamic { + public BuilderExpressionDynamicNumber(String expression, ExecutionContext.Instruction[] instructionSequence) { + super(expression, instructionSequence); + } + + @Nonnull + @Override + public ValueType getType() { + return ValueType.NUMBER; + } + + @Override + public double getNumber(@Nonnull ExecutionContext executionContext) { + this.execute(executionContext); + return executionContext.popNumber(); + } + + @Override + public void updateScope(@Nonnull StdScope scope, String name, @Nonnull ExecutionContext executionContext) { + scope.changeValue(name, this.getNumber(executionContext)); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicNumberArray.java b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicNumberArray.java new file mode 100644 index 0000000..b92cbec --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicNumberArray.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.npc.asset.builder.expression; + +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; + +public class BuilderExpressionDynamicNumberArray extends BuilderExpressionDynamic { + public BuilderExpressionDynamicNumberArray(String expression, ExecutionContext.Instruction[] instructionSequence) { + super(expression, instructionSequence); + } + + @Nonnull + @Override + public ValueType getType() { + return ValueType.NUMBER_ARRAY; + } + + @Override + public double[] getNumberArray(@Nonnull ExecutionContext executionContext) { + this.execute(executionContext); + return executionContext.popNumberArray(); + } + + @Override + public int[] getIntegerArray(@Nonnull ExecutionContext executionContext) { + return BuilderExpressionStaticNumberArray.convertDoubleToIntArray(this.getNumberArray(executionContext)); + } + + @Override + public void updateScope(@Nonnull StdScope scope, String name, @Nonnull ExecutionContext executionContext) { + scope.changeValue(name, this.getNumberArray(executionContext)); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicString.java b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicString.java new file mode 100644 index 0000000..7a240fb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicString.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.npc.asset.builder.expression; + +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; + +public class BuilderExpressionDynamicString extends BuilderExpressionDynamic { + public BuilderExpressionDynamicString(String expression, ExecutionContext.Instruction[] instructionSequence) { + super(expression, instructionSequence); + } + + @Nonnull + @Override + public ValueType getType() { + return ValueType.STRING; + } + + @Override + public String getString(@Nonnull ExecutionContext executionContext) { + this.execute(executionContext); + return executionContext.popString(); + } + + @Override + public void updateScope(@Nonnull StdScope scope, String name, @Nonnull ExecutionContext executionContext) { + scope.changeValue(name, this.getString(executionContext)); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicStringArray.java b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicStringArray.java new file mode 100644 index 0000000..6ec15e1 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionDynamicStringArray.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.npc.asset.builder.expression; + +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderExpressionDynamicStringArray extends BuilderExpressionDynamic { + public BuilderExpressionDynamicStringArray(String expression, ExecutionContext.Instruction[] instructionSequence) { + super(expression, instructionSequence); + } + + @Nonnull + @Override + public ValueType getType() { + return ValueType.STRING_ARRAY; + } + + @Nullable + @Override + public String[] getStringArray(@Nonnull ExecutionContext executionContext) { + this.execute(executionContext); + return executionContext.popStringArray(); + } + + @Override + public void updateScope(@Nonnull StdScope scope, String name, @Nonnull ExecutionContext executionContext) { + scope.changeValue(name, this.getStringArray(executionContext)); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticBoolean.java b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticBoolean.java new file mode 100644 index 0000000..5ee442e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticBoolean.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.npc.asset.builder.expression; + +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; + +public class BuilderExpressionStaticBoolean extends BuilderExpression { + private final boolean bool; + + public BuilderExpressionStaticBoolean(boolean bool) { + this.bool = bool; + } + + @Nonnull + @Override + public ValueType getType() { + return ValueType.BOOLEAN; + } + + @Override + public boolean isStatic() { + return true; + } + + @Override + public boolean getBoolean(ExecutionContext executionContext) { + return this.bool; + } + + @Override + public void addToScope(String name, @Nonnull StdScope scope) { + scope.addVar(name, this.bool); + } + + @Override + public void updateScope(@Nonnull StdScope scope, String name, ExecutionContext executionContext) { + scope.changeValue(name, this.bool); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticBooleanArray.java b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticBooleanArray.java new file mode 100644 index 0000000..4b7e541 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticBooleanArray.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.npc.asset.builder.expression; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderExpressionStaticBooleanArray extends BuilderExpression { + public static final BuilderExpressionStaticBooleanArray INSTANCE_EMPTY = new BuilderExpressionStaticBooleanArray(ArrayUtil.EMPTY_BOOLEAN_ARRAY); + private final boolean[] booleanArray; + + public BuilderExpressionStaticBooleanArray(boolean[] array) { + this.booleanArray = array; + } + + @Nonnull + @Override + public ValueType getType() { + return ValueType.BOOLEAN_ARRAY; + } + + @Override + public boolean isStatic() { + return true; + } + + @Override + public boolean[] getBooleanArray(ExecutionContext executionContext) { + return this.booleanArray; + } + + @Override + public void addToScope(String name, @Nonnull StdScope scope) { + scope.addVar(name, this.booleanArray); + } + + @Override + public void updateScope(@Nonnull StdScope scope, String name, ExecutionContext executionContext) { + scope.changeValue(name, this.booleanArray); + } + + @Nullable + public static BuilderExpressionStaticBooleanArray fromJSON(@Nonnull JsonArray jsonArray) { + int size = jsonArray.size(); + boolean[] array = new boolean[size]; + + for (int i = 0; i < size; i++) { + JsonElement element = jsonArray.get(i); + if (!element.isJsonPrimitive()) { + return null; + } + + JsonPrimitive primitive = element.getAsJsonPrimitive(); + if (!primitive.isBoolean()) { + return null; + } + + array[i] = primitive.getAsBoolean(); + } + + return new BuilderExpressionStaticBooleanArray(array); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticEmptyArray.java b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticEmptyArray.java new file mode 100644 index 0000000..2d8838c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticEmptyArray.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.npc.asset.builder.expression; + +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; + +public class BuilderExpressionStaticEmptyArray extends BuilderExpression { + public static final BuilderExpressionStaticEmptyArray INSTANCE = new BuilderExpressionStaticEmptyArray(); + + public BuilderExpressionStaticEmptyArray() { + } + + @Nonnull + @Override + public ValueType getType() { + return ValueType.EMPTY_ARRAY; + } + + @Override + public boolean isStatic() { + return true; + } + + @Override + public double[] getNumberArray(ExecutionContext executionContext) { + return ArrayUtil.EMPTY_DOUBLE_ARRAY; + } + + @Override + public int[] getIntegerArray(ExecutionContext executionContext) { + return ArrayUtil.EMPTY_INT_ARRAY; + } + + @Nonnull + @Override + public String[] getStringArray(ExecutionContext executionContext) { + return ArrayUtil.EMPTY_STRING_ARRAY; + } + + @Override + public boolean[] getBooleanArray(ExecutionContext executionContext) { + return ArrayUtil.EMPTY_BOOLEAN_ARRAY; + } + + @Override + public void addToScope(String name, @Nonnull StdScope scope) { + scope.addConstEmptyArray(name); + } + + @Override + public void updateScope(@Nonnull StdScope scope, String name, ExecutionContext executionContext) { + scope.changeValueToEmptyArray(name); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticNumber.java b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticNumber.java new file mode 100644 index 0000000..06bd9ac --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticNumber.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.npc.asset.builder.expression; + +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; + +public class BuilderExpressionStaticNumber extends BuilderExpression { + private final double number; + + public BuilderExpressionStaticNumber(double number) { + this.number = number; + } + + @Nonnull + @Override + public ValueType getType() { + return ValueType.NUMBER; + } + + @Override + public boolean isStatic() { + return true; + } + + @Override + public double getNumber(ExecutionContext executionContext) { + return this.number; + } + + @Override + public void addToScope(String name, @Nonnull StdScope scope) { + scope.addVar(name, this.number); + } + + @Override + public void updateScope(@Nonnull StdScope scope, String name, ExecutionContext executionContext) { + scope.changeValue(name, this.number); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticNumberArray.java b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticNumberArray.java new file mode 100644 index 0000000..4d6e9b4 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticNumberArray.java @@ -0,0 +1,113 @@ +package com.hypixel.hytale.server.npc.asset.builder.expression; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderExpressionStaticNumberArray extends BuilderExpression { + public static final BuilderExpressionStaticNumberArray INSTANCE_EMPTY = new BuilderExpressionStaticNumberArray(ArrayUtil.EMPTY_DOUBLE_ARRAY); + private final double[] numberArray; + @Nullable + private int[] cachedIntArray; + + public BuilderExpressionStaticNumberArray(double[] array) { + this.numberArray = array; + this.cachedIntArray = null; + } + + @Nonnull + @Override + public ValueType getType() { + return ValueType.NUMBER_ARRAY; + } + + @Override + public boolean isStatic() { + return true; + } + + @Override + public double[] getNumberArray(ExecutionContext executionContext) { + return this.numberArray; + } + + @Override + public int[] getIntegerArray(ExecutionContext executionContext) { + this.createCacheIfAbsent(); + return this.cachedIntArray; + } + + @Override + public void addToScope(String name, @Nonnull StdScope scope) { + scope.addVar(name, this.numberArray); + } + + @Override + public void updateScope(@Nonnull StdScope scope, String name, ExecutionContext executionContext) { + scope.changeValue(name, this.numberArray); + } + + private void createCacheIfAbsent() { + if (this.cachedIntArray == null) { + this.cachedIntArray = convertDoubleToIntArray(this.numberArray); + } + } + + @Nullable + public static BuilderExpressionStaticNumberArray fromJSON(@Nonnull JsonArray jsonArray) { + int size = jsonArray.size(); + double[] array = new double[size]; + + for (int i = 0; i < size; i++) { + JsonElement element = jsonArray.get(i); + if (!element.isJsonPrimitive()) { + return null; + } + + JsonPrimitive primitive = element.getAsJsonPrimitive(); + if (!primitive.isNumber()) { + return null; + } + + array[i] = primitive.getAsDouble(); + } + + return new BuilderExpressionStaticNumberArray(array); + } + + public static int[] convertDoubleToIntArray(@Nullable double[] source) { + if (source == null) { + return null; + } else { + int length = source.length; + int[] result = new int[length]; + + for (int i = 0; i < length; i++) { + result[i] = (int)Math.round(source[i]); + } + + return result; + } + } + + public static double[] convertIntToDoubleArray(@Nullable int[] source) { + if (source == null) { + return null; + } else { + int length = source.length; + double[] result = new double[length]; + + for (int i = 0; i < length; i++) { + result[i] = source[i]; + } + + return result; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticString.java b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticString.java new file mode 100644 index 0000000..065b61b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticString.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.npc.asset.builder.expression; + +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; + +public class BuilderExpressionStaticString extends BuilderExpression { + private final String string; + + public BuilderExpressionStaticString(String string) { + this.string = string; + } + + @Nonnull + @Override + public ValueType getType() { + return ValueType.STRING; + } + + @Override + public boolean isStatic() { + return true; + } + + @Override + public String getString(ExecutionContext executionContext) { + return this.string; + } + + @Override + public void addToScope(String name, @Nonnull StdScope scope) { + scope.addVar(name, this.string); + } + + @Override + public void updateScope(@Nonnull StdScope scope, String name, ExecutionContext executionContext) { + scope.changeValue(name, this.string); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticStringArray.java b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticStringArray.java new file mode 100644 index 0000000..e42e5c1 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/expression/BuilderExpressionStaticStringArray.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.npc.asset.builder.expression; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderExpressionStaticStringArray extends BuilderExpression { + public static final BuilderExpressionStaticStringArray INSTANCE_EMPTY = new BuilderExpressionStaticStringArray(ArrayUtil.EMPTY_STRING_ARRAY); + private final String[] stringArray; + + public BuilderExpressionStaticStringArray(String[] array) { + this.stringArray = array; + } + + @Nonnull + @Override + public ValueType getType() { + return ValueType.STRING_ARRAY; + } + + @Override + public boolean isStatic() { + return true; + } + + @Override + public String[] getStringArray(ExecutionContext executionContext) { + return this.stringArray; + } + + @Override + public void addToScope(String name, @Nonnull StdScope scope) { + scope.addVar(name, this.stringArray); + } + + @Override + public void updateScope(@Nonnull StdScope scope, String name, ExecutionContext executionContext) { + scope.changeValue(name, this.stringArray); + } + + @Nullable + public static BuilderExpressionStaticStringArray fromJSON(@Nonnull JsonArray jsonArray) { + int size = jsonArray.size(); + String[] array = new String[size]; + + for (int i = 0; i < size; i++) { + JsonElement element = jsonArray.get(i); + if (!element.isJsonPrimitive()) { + return null; + } + + JsonPrimitive primitive = element.getAsJsonPrimitive(); + if (!primitive.isString()) { + return null; + } + + array[i] = primitive.getAsString(); + } + + return new BuilderExpressionStaticStringArray(array); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/ArrayHolder.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/ArrayHolder.java new file mode 100644 index 0000000..929d230 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/ArrayHolder.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpressionStaticBooleanArray; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpressionStaticNumberArray; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpressionStaticStringArray; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; + +public abstract class ArrayHolder extends ValueHolder { + protected int minLength; + protected int maxLength = Integer.MAX_VALUE; + + public ArrayHolder(ValueType valueType) { + super(valueType); + } + + protected void readJSON(@Nonnull JsonElement requiredJsonElement, int minLength, int maxLength, String name, @Nonnull BuilderParameters builderParameters) { + this.setLength(minLength, maxLength); + this.readJSON(requiredJsonElement, name, builderParameters); + } + + protected void readJSON( + JsonElement optionalJsonElement, int minLength, int maxLength, double[] defaultValue, String name, @Nonnull BuilderParameters builderParameters + ) { + this.setLength(minLength, maxLength); + this.readJSON(optionalJsonElement, () -> new BuilderExpressionStaticNumberArray(defaultValue), name, builderParameters); + } + + protected void readJSON( + JsonElement optionalJsonElement, int minLength, int maxLength, String[] defaultValue, String name, @Nonnull BuilderParameters builderParameters + ) { + this.setLength(minLength, maxLength); + this.readJSON(optionalJsonElement, () -> new BuilderExpressionStaticStringArray(defaultValue), name, builderParameters); + } + + protected void readJSON( + JsonElement optionalJsonElement, int minLength, int maxLength, boolean[] defaultValue, String name, @Nonnull BuilderParameters builderParameters + ) { + this.setLength(minLength, maxLength); + this.readJSON(optionalJsonElement, () -> new BuilderExpressionStaticBooleanArray(defaultValue), name, builderParameters); + } + + protected void validateLength(int length) { + if (length < this.minLength || length > this.maxLength) { + StringBuilder errorString = new StringBuilder(100); + errorString.append(this.name).append(": Invalid array size in array holder (Should be "); + if (this.minLength == this.maxLength) { + errorString.append(this.minLength); + } else if (this.maxLength < Integer.MAX_VALUE) { + errorString.append("between ").append(this.minLength).append(" and ").append(this.maxLength); + } else { + errorString.append("a minimum length of ").append(this.minLength); + } + + errorString.append(" but is ").append(length).append(')'); + throw new IllegalStateException(errorString.toString()); + } + } + + protected void setLength(int minLength, int maxLength) { + if (minLength > maxLength) { + throw new IllegalArgumentException("Illegal length for array in array holder specified"); + } else if (minLength < 0) { + throw new IllegalArgumentException("Illegal minimum length for array in array holder specified"); + } else { + this.minLength = minLength; + this.maxLength = maxLength; + } + } + + protected void setLength(int length) { + this.setLength(length, length); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/AssetArrayHolder.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/AssetArrayHolder.java new file mode 100644 index 0000000..0176fd8 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/AssetArrayHolder.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AssetArrayHolder extends StringArrayHolder { + protected AssetValidator assetValidator; + + public AssetArrayHolder() { + } + + public void readJSON( + @Nonnull JsonElement requiredJsonElement, + int minLength, + int maxLength, + AssetValidator validator, + String name, + @Nonnull BuilderParameters builderParameters + ) { + this.readJSON(requiredJsonElement, minLength, maxLength, name, builderParameters); + this.assetValidator = validator; + } + + public void readJSON( + JsonElement optionalJsonElement, + int minLength, + int maxLength, + String[] defaultValue, + AssetValidator validator, + String name, + @Nonnull BuilderParameters builderParameters + ) { + this.readJSON(optionalJsonElement, minLength, maxLength, defaultValue, name, builderParameters); + this.assetValidator = validator; + } + + @Nullable + @Override + public String[] get(ExecutionContext executionContext) { + String[] value = this.rawGet(executionContext); + this.validateRelations(executionContext, value); + return value; + } + + @Nullable + @Override + public String[] rawGet(ExecutionContext executionContext) { + String[] value = this.expression.getStringArray(executionContext); + if (this.assetValidator != null) { + BuilderBase.validateAssetList(value, this.assetValidator, this.name, true); + } + + return value; + } + + public void staticValidate() { + if (this.assetValidator != null && this.expression.isStatic()) { + BuilderBase.validateAssetList(this.expression.getStringArray(null), this.assetValidator, this.name, true); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/AssetHolder.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/AssetHolder.java new file mode 100644 index 0000000..9d680b7 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/AssetHolder.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpressionStaticString; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import javax.annotation.Nonnull; + +public class AssetHolder extends StringHolderBase { + protected AssetValidator assetValidator; + + public AssetHolder() { + } + + @Override + public void validate(ExecutionContext context) { + this.get(context); + } + + public void readJSON(@Nonnull JsonElement requiredJsonElement, AssetValidator validator, String name, @Nonnull BuilderParameters builderParameters) { + this.readJSON(requiredJsonElement, name, builderParameters); + this.assetValidator = validator; + } + + public void readJSON( + JsonElement optionalJsonElement, String defaultValue, AssetValidator validator, String name, @Nonnull BuilderParameters builderParameters + ) { + this.readJSON(optionalJsonElement, () -> new BuilderExpressionStaticString(defaultValue), name, builderParameters); + this.assetValidator = validator; + } + + public String get(ExecutionContext executionContext) { + String value = this.rawGet(executionContext); + this.validateRelations(executionContext, value); + return value; + } + + public String rawGet(ExecutionContext executionContext) { + String value = this.expression.getString(executionContext); + if (this.assetValidator != null) { + BuilderBase.validateAsset(value, this.assetValidator, this.name, true); + } + + return value; + } + + public void staticValidate() { + if (this.assetValidator != null && this.isStatic()) { + BuilderBase.validateAsset(this.expression.getString(null), this.assetValidator, this.name, true); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/BooleanArrayHolder.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/BooleanArrayHolder.java new file mode 100644 index 0000000..273df23 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/BooleanArrayHolder.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.asset.builder.validators.BooleanArrayValidator; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BooleanArrayHolder extends ArrayHolder { + protected BooleanArrayValidator booleanArrayValidator; + + public BooleanArrayHolder() { + super(ValueType.BOOLEAN_ARRAY); + } + + @Override + public void validate(ExecutionContext context) { + this.get(context); + } + + public void readJSON( + @Nonnull JsonElement requiredJsonElement, + int minLength, + int maxLength, + BooleanArrayValidator validator, + String name, + @Nonnull BuilderParameters builderParameters + ) { + this.readJSON(requiredJsonElement, minLength, maxLength, name, builderParameters); + this.booleanArrayValidator = validator; + if (this.isStatic()) { + this.validate(this.expression.getBooleanArray(null)); + } + } + + public void readJSON( + JsonElement optionalJsonElement, + int minLength, + int maxLength, + boolean[] defaultValue, + BooleanArrayValidator validator, + String name, + @Nonnull BuilderParameters builderParameters + ) { + this.readJSON(optionalJsonElement, minLength, maxLength, defaultValue, name, builderParameters); + this.booleanArrayValidator = validator; + if (this.isStatic()) { + this.validate(this.expression.getBooleanArray(null)); + } + } + + public boolean[] get(ExecutionContext executionContext) { + return this.rawGet(executionContext); + } + + public boolean[] rawGet(ExecutionContext executionContext) { + boolean[] value = this.expression.getBooleanArray(executionContext); + if (!this.isStatic()) { + this.validate(value); + } + + return value; + } + + public void validate(@Nullable boolean[] value) { + if (value != null) { + this.validateLength(value.length); + } + + if (this.booleanArrayValidator != null && !this.booleanArrayValidator.test(value)) { + throw new IllegalStateException(this.booleanArrayValidator.errorMessage(this.name, value)); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/BooleanHolder.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/BooleanHolder.java new file mode 100644 index 0000000..9e44c31 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/BooleanHolder.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpressionStaticBoolean; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; + +public class BooleanHolder extends ValueHolder { + protected List> relationValidators; + + public BooleanHolder() { + super(ValueType.BOOLEAN); + } + + @Override + public void readJSON(@Nonnull JsonElement requiredJsonElement, String name, @Nonnull BuilderParameters builderParameters) { + super.readJSON(requiredJsonElement, name, builderParameters); + } + + @Override + public void validate(ExecutionContext context) { + this.get(context); + } + + public void readJSON(JsonElement optionalJsonElement, boolean defaultValue, String name, @Nonnull BuilderParameters builderParameters) { + super.readJSON(optionalJsonElement, () -> new BuilderExpressionStaticBoolean(defaultValue), name, builderParameters); + } + + public boolean get(ExecutionContext executionContext) { + boolean value = this.rawGet(executionContext); + this.validateRelations(executionContext, value); + return value; + } + + public boolean rawGet(ExecutionContext executionContext) { + return this.expression.getBoolean(executionContext); + } + + public void addRelationValidator(BiConsumer validator) { + if (this.relationValidators == null) { + this.relationValidators = new ObjectArrayList<>(); + } + + this.relationValidators.add(validator); + } + + protected void validateRelations(ExecutionContext executionContext, boolean value) { + if (this.relationValidators != null) { + for (BiConsumer executionContextConsumer : this.relationValidators) { + executionContextConsumer.accept(executionContext, value); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/DeferEvaluateAssetHolder.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/DeferEvaluateAssetHolder.java new file mode 100644 index 0000000..34c2bb8 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/DeferEvaluateAssetHolder.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +public class DeferEvaluateAssetHolder extends AssetHolder { + public DeferEvaluateAssetHolder() { + } + + @Override + public boolean isStatic() { + return false; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/DoubleHolder.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/DoubleHolder.java new file mode 100644 index 0000000..9c75fe9 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/DoubleHolder.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; + +public class DoubleHolder extends DoubleHolderBase { + public DoubleHolder() { + } + + @Override + public void validate(ExecutionContext context) { + this.get(context); + } + + public double get(ExecutionContext executionContext) { + double value = this.rawGet(executionContext); + this.validateRelations(executionContext, value); + return value; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/DoubleHolderBase.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/DoubleHolderBase.java new file mode 100644 index 0000000..e1a6feb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/DoubleHolderBase.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpressionStaticNumber; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleValidator; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.function.ObjDoubleConsumer; +import javax.annotation.Nonnull; + +public abstract class DoubleHolderBase extends ValueHolder { + protected List> relationValidators; + protected DoubleValidator doubleValidator; + + protected DoubleHolderBase() { + super(ValueType.NUMBER); + } + + public void readJSON(@Nonnull JsonElement requiredJsonElement, DoubleValidator validator, String name, @Nonnull BuilderParameters builderParameters) { + this.readJSON(requiredJsonElement, name, builderParameters); + this.doubleValidator = validator; + if (this.isStatic()) { + this.validate(this.expression.getNumber(null)); + } + } + + public void readJSON( + JsonElement optionalJsonElement, double defaultValue, DoubleValidator validator, String name, @Nonnull BuilderParameters builderParameters + ) { + this.readJSON(optionalJsonElement, () -> new BuilderExpressionStaticNumber(defaultValue), name, builderParameters); + this.doubleValidator = validator; + if (this.isStatic()) { + this.validate(this.expression.getNumber(null)); + } + } + + public void addRelationValidator(ObjDoubleConsumer validator) { + if (this.relationValidators == null) { + this.relationValidators = new ObjectArrayList<>(); + } + + this.relationValidators.add(validator); + } + + protected void validateRelations(ExecutionContext executionContext, double value) { + if (this.relationValidators != null) { + for (ObjDoubleConsumer executionContextConsumer : this.relationValidators) { + executionContextConsumer.accept(executionContext, value); + } + } + } + + public double rawGet(ExecutionContext executionContext) { + double value = this.expression.getNumber(executionContext); + if (!this.isStatic()) { + this.validate(value); + } + + return value; + } + + public void validate(double value) { + if (this.doubleValidator != null && !this.doubleValidator.test(value)) { + throw new IllegalStateException(this.doubleValidator.errorMessage(value, this.name)); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/EnumArrayHolder.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/EnumArrayHolder.java new file mode 100644 index 0000000..27b0323 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/EnumArrayHolder.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.asset.builder.validators.EnumArrayValidator; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; + +public class EnumArrayHolder> extends ArrayHolder { + private Class clazz; + private E[] enumConstants; + private EnumArrayValidator validator; + private E[] value; + + public EnumArrayHolder() { + super(ValueType.STRING_ARRAY); + } + + @Override + public void validate(ExecutionContext context) { + this.get(context); + } + + public void readJSON( + @Nonnull JsonElement requiredJsonElement, Class clazz, EnumArrayValidator validator, String name, @Nonnull BuilderParameters builderParameters + ) { + this.clazz = clazz; + this.enumConstants = clazz.getEnumConstants(); + this.validator = validator; + this.readJSON(requiredJsonElement, 0, Integer.MAX_VALUE, name, builderParameters); + if (this.isStatic()) { + this.resolve(this.expression.getStringArray(null)); + } + } + + public E[] get(ExecutionContext executionContext) { + return this.rawGet(executionContext); + } + + public E[] rawGet(ExecutionContext executionContext) { + if (!this.isStatic()) { + this.resolve(this.expression.getStringArray(executionContext)); + } + + return this.value; + } + + public void resolve(String[] value) { + this.value = BuilderBase.stringsToEnumArray(value, this.clazz, this.enumConstants, this.getName()); + if (this.validator != null && !this.validator.test(this.value, this.clazz)) { + throw new IllegalStateException(this.validator.errorMessage(this.name, this.value)); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/EnumHolder.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/EnumHolder.java new file mode 100644 index 0000000..8e8f8ef --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/EnumHolder.java @@ -0,0 +1,71 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpressionStaticString; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; + +public class EnumHolder> extends StringHolderBase { + protected List> enumRelationValidators; + private E[] enumConstants; + private E value; + + public EnumHolder() { + } + + @Override + public void validate(ExecutionContext context) { + this.get(context); + } + + public void readJSON(@Nonnull JsonElement requiredJsonElement, Class clazz, String name, @Nonnull BuilderParameters builderParameters) { + this.enumConstants = clazz.getEnumConstants(); + this.readJSON(requiredJsonElement, name, builderParameters); + if (this.isStatic()) { + this.value = BuilderBase.stringToEnum(this.expression.getString(null), this.enumConstants, this.getName()); + } + } + + public void readJSON(JsonElement optionalJsonElement, Class clazz, @Nonnull E defaultValue, String name, @Nonnull BuilderParameters builderParameters) { + this.enumConstants = clazz.getEnumConstants(); + this.readJSON(optionalJsonElement, () -> new BuilderExpressionStaticString(defaultValue.toString()), name, builderParameters); + if (this.isStatic()) { + this.value = BuilderBase.stringToEnum(this.expression.getString(null), this.enumConstants, this.getName()); + } + } + + public E get(ExecutionContext executionContext) { + E value = this.rawGet(executionContext); + this.validateEnumRelations(executionContext, value); + return value; + } + + public void addEnumRelationValidator(BiConsumer validator) { + if (this.enumRelationValidators == null) { + this.enumRelationValidators = new ObjectArrayList<>(); + } + + this.enumRelationValidators.add(validator); + } + + public E rawGet(ExecutionContext executionContext) { + if (!this.isStatic()) { + this.value = BuilderBase.stringToEnum(this.expression.getString(executionContext), this.enumConstants, this.getName()); + } + + return this.value; + } + + private void validateEnumRelations(ExecutionContext context, E value) { + if (this.enumRelationValidators != null) { + for (BiConsumer executionContextConsumer : this.enumRelationValidators) { + executionContextConsumer.accept(context, value); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/EnumSetHolder.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/EnumSetHolder.java new file mode 100644 index 0000000..900e85b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/EnumSetHolder.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class EnumSetHolder> extends ArrayHolder { + private Class clazz; + private E[] enumConstants; + private EnumSet value; + + public EnumSetHolder() { + super(ValueType.STRING_ARRAY); + } + + @Override + public void validate(ExecutionContext context) { + this.get(context); + } + + public void readJSON(@Nonnull JsonElement requiredJsonElement, Class clazz, String name, @Nonnull BuilderParameters builderParameters) { + this.clazz = clazz; + this.enumConstants = clazz.getEnumConstants(); + this.readJSON(requiredJsonElement, 0, Integer.MAX_VALUE, name, builderParameters); + if (this.isStatic()) { + this.value = BuilderBase.stringsToEnumSet(this.expression.getStringArray(null), clazz, this.enumConstants, this.getName()); + } + } + + public void readJSON( + JsonElement optionalJsonElement, @Nonnull EnumSet defaultValue, Class clazz, String name, @Nonnull BuilderParameters builderParameters + ) { + this.clazz = clazz; + this.enumConstants = clazz.getEnumConstants(); + this.readJSON(optionalJsonElement, 0, Integer.MAX_VALUE, BuilderBase.enumSetToStrings(defaultValue), name, builderParameters); + if (this.isStatic()) { + this.value = BuilderBase.stringsToEnumSet(this.expression.getStringArray(null), clazz, this.enumConstants, this.getName()); + } + } + + public EnumSet get(ExecutionContext executionContext) { + return this.rawGet(executionContext); + } + + public EnumSet rawGet(ExecutionContext executionContext) { + if (!this.isStatic()) { + this.value = BuilderBase.stringsToEnumSet(this.expression.getStringArray(executionContext), this.clazz, this.enumConstants, this.getName()); + } + + return this.value; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/FloatHolder.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/FloatHolder.java new file mode 100644 index 0000000..5a637ac --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/FloatHolder.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleValidator; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import javax.annotation.Nonnull; + +public class FloatHolder extends DoubleHolderBase { + public FloatHolder() { + } + + @Override + public void validate(ExecutionContext context) { + this.get(context); + } + + public void readJSON( + JsonElement optionalJsonElement, float defaultValue, DoubleValidator validator, String name, @Nonnull BuilderParameters builderParameters + ) { + this.readJSON(optionalJsonElement, defaultValue, validator, name, builderParameters); + } + + public float get(ExecutionContext executionContext) { + double value = this.rawGet(executionContext); + this.validateRelations(executionContext, value); + return (float)value; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/IntHolder.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/IntHolder.java new file mode 100644 index 0000000..0b97f7e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/IntHolder.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpressionStaticNumber; +import com.hypixel.hytale.server.npc.asset.builder.validators.IntValidator; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.function.ObjIntConsumer; +import javax.annotation.Nonnull; + +public class IntHolder extends ValueHolder { + protected List> relationValidators; + protected IntValidator intValidator; + + public IntHolder() { + super(ValueType.NUMBER); + } + + @Override + public void validate(ExecutionContext context) { + this.get(context); + } + + public void readJSON(@Nonnull JsonElement requiredJsonElement, IntValidator validator, String name, @Nonnull BuilderParameters builderParameters) { + this.readJSON(requiredJsonElement, name, builderParameters); + this.intValidator = validator; + if (this.isStatic()) { + this.validate(MathUtil.floor(this.expression.getNumber(null))); + } + } + + public void readJSON(JsonElement optionalJsonElement, int defaultValue, IntValidator validator, String name, @Nonnull BuilderParameters builderParameters) { + this.readJSON(optionalJsonElement, () -> new BuilderExpressionStaticNumber(defaultValue), name, builderParameters); + this.intValidator = validator; + if (this.isStatic()) { + this.validate(MathUtil.floor(this.expression.getNumber(null))); + } + } + + public int get(ExecutionContext executionContext) { + int value = this.rawGet(executionContext); + this.validateRelations(executionContext, value); + return value; + } + + public int rawGet(ExecutionContext executionContext) { + int value = MathUtil.floor(this.expression.getNumber(executionContext)); + if (!this.isStatic()) { + this.validate(value); + } + + return value; + } + + public void validate(int value) { + if (this.intValidator != null && !this.intValidator.test(value)) { + throw new IllegalStateException(this.intValidator.errorMessage(value, this.name)); + } + } + + public void addRelationValidator(ObjIntConsumer validator) { + if (this.relationValidators == null) { + this.relationValidators = new ObjectArrayList<>(); + } + + this.relationValidators.add(validator); + } + + protected void validateRelations(ExecutionContext executionContext, int value) { + if (this.relationValidators != null) { + for (ObjIntConsumer executionContextConsumer : this.relationValidators) { + executionContextConsumer.accept(executionContext, value); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/NumberArrayHolder.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/NumberArrayHolder.java new file mode 100644 index 0000000..f621daa --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/NumberArrayHolder.java @@ -0,0 +1,135 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpressionStaticNumberArray; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleArrayValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.IntArrayValidator; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NumberArrayHolder extends ArrayHolder { + protected IntArrayValidator intArrayValidator; + protected DoubleArrayValidator doubleArrayValidator; + + public NumberArrayHolder() { + super(ValueType.NUMBER_ARRAY); + } + + @Override + public void validate(ExecutionContext context) { + this.get(context); + } + + public void readJSON( + @Nonnull JsonElement requiredJsonElement, + int minLength, + int maxLength, + IntArrayValidator validator, + String name, + @Nonnull BuilderParameters builderParameters + ) { + this.readJSON(requiredJsonElement, minLength, maxLength, name, builderParameters); + this.intArrayValidator = validator; + if (this.isStatic()) { + this.validate(this.expression.getIntegerArray(null)); + } + } + + public void readJSON( + @Nonnull JsonElement requiredJsonElement, + int minLength, + int maxLength, + DoubleArrayValidator validator, + String name, + @Nonnull BuilderParameters builderParameters + ) { + this.readJSON(requiredJsonElement, minLength, maxLength, name, builderParameters); + this.doubleArrayValidator = validator; + if (this.isStatic()) { + this.validate(this.expression.getNumberArray(null)); + } + } + + public void readJSON( + JsonElement optionalJsonElement, + int minLength, + int maxLength, + int[] defaultValue, + IntArrayValidator validator, + String name, + @Nonnull BuilderParameters builderParameters + ) { + this.readJSON( + optionalJsonElement, minLength, maxLength, BuilderExpressionStaticNumberArray.convertIntToDoubleArray(defaultValue), name, builderParameters + ); + this.intArrayValidator = validator; + if (this.isStatic()) { + this.validate(this.expression.getIntegerArray(null)); + } + } + + public void readJSON( + JsonElement optionalJsonElement, + int minLength, + int maxLength, + double[] defaultValue, + DoubleArrayValidator validator, + String name, + @Nonnull BuilderParameters builderParameters + ) { + this.readJSON(optionalJsonElement, minLength, maxLength, defaultValue, name, builderParameters); + this.doubleArrayValidator = validator; + if (this.isStatic()) { + this.validate(this.expression.getNumberArray(null)); + } + } + + public double[] get(ExecutionContext executionContext) { + return this.rawGet(executionContext); + } + + public double[] rawGet(ExecutionContext executionContext) { + double[] value = this.expression.getNumberArray(executionContext); + if (!this.isStatic()) { + this.validate(value); + } + + return value; + } + + public int[] getIntArray(ExecutionContext executionContext) { + return this.rawGetIntArray(executionContext); + } + + public int[] rawGetIntArray(ExecutionContext executionContext) { + int[] value = this.expression.getIntegerArray(executionContext); + if (!this.isStatic()) { + this.validate(value); + } + + return value; + } + + public void validate(@Nullable int[] value) { + if (value != null) { + this.validateLength(value.length); + } + + if (this.intArrayValidator != null && !this.intArrayValidator.test(value)) { + throw new IllegalStateException(this.intArrayValidator.errorMessage(value, this.name)); + } + } + + public void validate(@Nullable double[] value) { + if (value != null) { + this.validateLength(value.length); + } + + if (this.doubleArrayValidator != null && !this.doubleArrayValidator.test(value)) { + throw new IllegalStateException(this.doubleArrayValidator.errorMessage(value, this.name)); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/StringArrayHolder.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/StringArrayHolder.java new file mode 100644 index 0000000..616b581 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/StringArrayHolder.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringArrayValidator; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StringArrayHolder extends ArrayHolder { + protected StringArrayValidator stringArrayValidator; + protected List> relationValidators; + + public StringArrayHolder() { + super(ValueType.STRING_ARRAY); + } + + @Override + public void validate(ExecutionContext context) { + this.get(context); + } + + public void readJSON( + @Nonnull JsonElement requiredJsonElement, + int minLength, + int maxLength, + StringArrayValidator validator, + String name, + @Nonnull BuilderParameters builderParameters + ) { + this.readJSON(requiredJsonElement, minLength, maxLength, name, builderParameters); + this.stringArrayValidator = validator; + if (this.isStatic()) { + this.validate(this.expression.getStringArray(null)); + } + } + + public void readJSON( + JsonElement optionalJsonElement, + int minLength, + int maxLength, + String[] defaultValue, + StringArrayValidator validator, + String name, + @Nonnull BuilderParameters builderParameters + ) { + this.readJSON(optionalJsonElement, minLength, maxLength, defaultValue, name, builderParameters); + this.stringArrayValidator = validator; + if (this.isStatic()) { + this.validate(this.expression.getStringArray(null)); + } + } + + @Nullable + public String[] get(ExecutionContext executionContext) { + String[] value = this.rawGet(executionContext); + this.validateRelations(executionContext, value); + return value; + } + + @Nullable + public String[] rawGet(ExecutionContext executionContext) { + String[] value = this.expression.getStringArray(executionContext); + if (!this.isStatic()) { + this.validate(value); + } + + return value; + } + + public void validate(@Nullable String[] value) { + if (value != null) { + this.validateLength(value.length); + } + + if (this.stringArrayValidator != null && !this.stringArrayValidator.test(value)) { + throw new IllegalStateException(this.stringArrayValidator.errorMessage(this.name, value)); + } + } + + public void addRelationValidator(BiConsumer validator) { + if (this.relationValidators == null) { + this.relationValidators = new ObjectArrayList<>(); + } + + this.relationValidators.add(validator); + } + + protected void validateRelations(ExecutionContext executionContext, String[] value) { + if (this.relationValidators != null) { + for (BiConsumer executionContextConsumer : this.relationValidators) { + executionContextConsumer.accept(executionContext, value); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/StringHolder.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/StringHolder.java new file mode 100644 index 0000000..db9d801 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/StringHolder.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpressionStaticString; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringValidator; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import javax.annotation.Nonnull; + +public class StringHolder extends StringHolderBase { + protected StringValidator stringValidator; + + public StringHolder() { + } + + @Override + public void validate(ExecutionContext context) { + this.get(context); + } + + public void readJSON(@Nonnull JsonElement requiredJsonElement, StringValidator validator, String name, @Nonnull BuilderParameters builderParameters) { + this.readJSON(requiredJsonElement, name, builderParameters); + this.stringValidator = validator; + if (this.isStatic()) { + this.validate(this.expression.getString(null)); + } + } + + public void readJSON( + JsonElement optionalJsonElement, String defaultValue, StringValidator validator, String name, @Nonnull BuilderParameters builderParameters + ) { + this.readJSON(optionalJsonElement, () -> new BuilderExpressionStaticString(defaultValue), name, builderParameters); + this.stringValidator = validator; + if (this.isStatic()) { + this.validate(this.expression.getString(null)); + } + } + + public String get(ExecutionContext executionContext) { + String value = this.rawGet(executionContext); + this.validateRelations(executionContext, value); + return value; + } + + public String rawGet(ExecutionContext executionContext) { + String value = this.expression.getString(executionContext); + if (!this.isStatic()) { + this.validate(value); + } + + return value; + } + + public void validate(String value) { + if (this.stringValidator != null && !this.stringValidator.test(value)) { + throw new IllegalStateException(this.stringValidator.errorMessage(value, this.name)); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/StringHolderBase.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/StringHolderBase.java new file mode 100644 index 0000000..6851ba6 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/StringHolderBase.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.function.BiConsumer; + +public abstract class StringHolderBase extends ValueHolder { + protected List> relationValidators; + + protected StringHolderBase() { + super(ValueType.STRING); + } + + public void addRelationValidator(BiConsumer validator) { + if (this.relationValidators == null) { + this.relationValidators = new ObjectArrayList<>(); + } + + this.relationValidators.add(validator); + } + + protected void validateRelations(ExecutionContext executionContext, String value) { + if (this.relationValidators != null) { + for (BiConsumer executionContextConsumer : this.relationValidators) { + executionContextConsumer.accept(executionContext, value); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/TemporalArrayHolder.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/TemporalArrayHolder.java new file mode 100644 index 0000000..5110db2 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/TemporalArrayHolder.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.asset.builder.validators.TemporalArrayValidator; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import java.time.Duration; +import java.time.Period; +import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalAmount; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TemporalArrayHolder extends StringArrayHolder { + protected TemporalArrayValidator validator; + private TemporalAmount[] cachedTemporalArray; + + public TemporalArrayHolder() { + } + + public static TemporalAmount[] convertStringToTemporalArray(@Nullable String[] source) { + if (source == null) { + return null; + } else { + int length = source.length; + TemporalAmount[] result = new TemporalAmount[length]; + + for (int i = 0; i < length; i++) { + String text = source[i]; + String period = !source[i].isEmpty() && source[i].charAt(0) == 'P' ? text : "P" + text; + + try { + result[i] = Period.parse(period); + } catch (DateTimeParseException var9) { + try { + result[i] = Duration.parse(period); + } catch (DateTimeParseException var8) { + throw new IllegalStateException(String.format("Cannot parse text %s to Duration or Period", source[i])); + } + } + } + + return result; + } + } + + public void readJSON( + @Nonnull JsonElement requiredJsonElement, + int minLength, + int maxLength, + TemporalArrayValidator validator, + String name, + @Nonnull BuilderParameters builderParameters + ) { + this.readJSON(requiredJsonElement, minLength, maxLength, name, builderParameters); + this.validator = validator; + if (this.isStatic()) { + String[] array = this.expression.getStringArray(null); + this.cachedTemporalArray = convertStringToTemporalArray(array); + this.validate(this.cachedTemporalArray); + } + } + + @Nullable + public TemporalAmount[] getTemporalArray(ExecutionContext executionContext) { + return this.rawGetTemporalArray(executionContext); + } + + @Nullable + public TemporalAmount[] rawGetTemporalArray(ExecutionContext executionContext) { + if (this.isStatic()) { + return this.cachedTemporalArray; + } else { + String[] array = this.expression.getStringArray(executionContext); + TemporalAmount[] value = convertStringToTemporalArray(array); + this.validate(value); + return value; + } + } + + public void validate(@Nullable TemporalAmount[] value) { + if (value != null) { + this.validateLength(value.length); + } + + if (this.validator != null && !this.validator.test(value)) { + throw new IllegalStateException(this.validator.errorMessage(this.name, value)); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/holder/ValueHolder.java b/src/com/hypixel/hytale/server/npc/asset/builder/holder/ValueHolder.java new file mode 100644 index 0000000..4212d61 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/holder/ValueHolder.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.npc.asset.builder.holder; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.npc.asset.builder.BuilderParameters; +import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpression; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class ValueHolder { + protected static final boolean LOG_VALUES = false; + protected static final HytaleLogger LOGGER = HytaleLogger.get("BuilderManager"); + protected ValueType valueType; + protected String name; + protected BuilderExpression expression; + + protected ValueHolder(ValueType valueType) { + this.valueType = valueType; + } + + public abstract void validate(ExecutionContext var1); + + protected void readJSON(@Nonnull JsonElement requiredJsonElement, String name, @Nonnull BuilderParameters builderParameters) { + this.name = name; + this.expression = BuilderExpression.fromJSON(requiredJsonElement, builderParameters, this.valueType); + } + + protected void readJSON( + @Nullable JsonElement optionalJsonElement, @Nonnull Supplier defaultValue, String name, @Nonnull BuilderParameters builderParameters + ) { + this.name = name; + this.expression = optionalJsonElement != null ? BuilderExpression.fromJSON(optionalJsonElement, builderParameters, this.valueType) : defaultValue.get(); + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isStatic() { + return this.expression.isStatic(); + } + + public String getExpressionString() { + return this.expression.getExpression(); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/FeatureProviderEvaluator.java b/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/FeatureProviderEvaluator.java new file mode 100644 index 0000000..87df053 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/FeatureProviderEvaluator.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.server.npc.asset.builder.providerevaluators; + +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import java.util.EnumSet; + +public interface FeatureProviderEvaluator extends ProviderEvaluator { + boolean provides(EnumSet var1); +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/ParameterProviderEvaluator.java b/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/ParameterProviderEvaluator.java new file mode 100644 index 0000000..53944e7 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/ParameterProviderEvaluator.java @@ -0,0 +1,5 @@ +package com.hypixel.hytale.server.npc.asset.builder.providerevaluators; + +public interface ParameterProviderEvaluator extends ProviderEvaluator { + boolean hasParameter(String var1, ParameterType var2); +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/ParameterType.java b/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/ParameterType.java new file mode 100644 index 0000000..c1eac0a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/ParameterType.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.server.npc.asset.builder.providerevaluators; + +import java.util.function.Supplier; + +public enum ParameterType implements Supplier { + DOUBLE("double"), + STRING("string"), + INTEGER("int"); + + private final String description; + + private ParameterType(String description) { + this.description = description; + } + + public String get() { + return this.description; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/ProviderEvaluator.java b/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/ProviderEvaluator.java new file mode 100644 index 0000000..7090db8 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/ProviderEvaluator.java @@ -0,0 +1,7 @@ +package com.hypixel.hytale.server.npc.asset.builder.providerevaluators; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderManager; + +public interface ProviderEvaluator { + void resolveReferences(BuilderManager var1); +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/ProviderEvaluatorTypeRegistry.java b/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/ProviderEvaluatorTypeRegistry.java new file mode 100644 index 0000000..de13a4a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/ProviderEvaluatorTypeRegistry.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.server.npc.asset.builder.providerevaluators; + +import com.google.gson.GsonBuilder; +import com.hypixel.hytale.server.npc.asset.builder.validators.SubTypeTypeAdapterFactory; +import javax.annotation.Nonnull; + +public class ProviderEvaluatorTypeRegistry { + public ProviderEvaluatorTypeRegistry() { + } + + @Nonnull + public static GsonBuilder registerTypes(@Nonnull GsonBuilder gsonBuilder) { + SubTypeTypeAdapterFactory factory = SubTypeTypeAdapterFactory.of(ProviderEvaluator.class, "Type"); + factory.registerSubType(UnconditionalFeatureProviderEvaluator.class, "ProvidesFeatureUnconditionally"); + factory.registerSubType(UnconditionalParameterProviderEvaluator.class, "ProvidesParameterUnconditionally"); + gsonBuilder.registerTypeAdapterFactory(factory); + return gsonBuilder; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/ReferenceProviderEvaluator.java b/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/ReferenceProviderEvaluator.java new file mode 100644 index 0000000..c9c8ec7 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/ReferenceProviderEvaluator.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.server.npc.asset.builder.providerevaluators; + +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderManager; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.FeatureEvaluatorHelper; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class ReferenceProviderEvaluator implements FeatureProviderEvaluator, ParameterProviderEvaluator { + private final int referenceIndex; + private final Class classType; + private FeatureEvaluatorHelper resolvedProviderSet; + + public ReferenceProviderEvaluator(int referenceIndex, Class classType) { + this.referenceIndex = referenceIndex; + this.classType = classType; + } + + @Override + public boolean provides(EnumSet feature) { + if (this.resolvedProviderSet == null) { + return false; + } else { + for (ProviderEvaluator evaluator : this.resolvedProviderSet.getProviders()) { + if (evaluator instanceof FeatureProviderEvaluator && ((FeatureProviderEvaluator)evaluator).provides(feature)) { + return true; + } + } + + return false; + } + } + + @Override + public boolean hasParameter(String parameter, ParameterType type) { + if (this.resolvedProviderSet == null) { + return false; + } else { + for (ProviderEvaluator evaluator : this.resolvedProviderSet.getProviders()) { + if (evaluator instanceof ParameterProviderEvaluator && ((ParameterProviderEvaluator)evaluator).hasParameter(parameter, type)) { + return true; + } + } + + return false; + } + } + + @Override + public void resolveReferences(@Nonnull BuilderManager manager) { + Builder referencedBuilder = manager.getCachedBuilder(this.referenceIndex, this.classType); + this.resolvedProviderSet = referencedBuilder.getEvaluatorHelper(); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/UnconditionalFeatureProviderEvaluator.java b/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/UnconditionalFeatureProviderEvaluator.java new file mode 100644 index 0000000..51f238c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/UnconditionalFeatureProviderEvaluator.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.npc.asset.builder.providerevaluators; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderManager; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class UnconditionalFeatureProviderEvaluator implements FeatureProviderEvaluator { + @Nonnull + private final Feature feature; + private final String description; + + public UnconditionalFeatureProviderEvaluator(@Nonnull Feature feature) { + this.feature = feature; + this.description = feature.get(); + } + + @Override + public boolean provides(@Nonnull EnumSet feature) { + return feature.contains(this.feature); + } + + @Override + public void resolveReferences(BuilderManager manager) { + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/UnconditionalParameterProviderEvaluator.java b/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/UnconditionalParameterProviderEvaluator.java new file mode 100644 index 0000000..a83ecf6 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/providerevaluators/UnconditionalParameterProviderEvaluator.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.npc.asset.builder.providerevaluators; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderManager; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class UnconditionalParameterProviderEvaluator implements ParameterProviderEvaluator { + private final Map parameters = new HashMap<>(); + + public UnconditionalParameterProviderEvaluator(@Nonnull String[] parameters, @Nonnull ParameterType[] types) { + if (parameters.length != types.length) { + throw new IllegalArgumentException("Different number of parameters to types"); + } else { + for (int i = 0; i < parameters.length; i++) { + this.parameters.put(parameters[i], types[i]); + } + } + } + + @Override + public boolean hasParameter(String parameter, ParameterType type) { + return this.parameters.get(parameter) == type; + } + + @Override + public void resolveReferences(BuilderManager builderManager) { + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/util/StringListHelpers.java b/src/com/hypixel/hytale/server/npc/asset/builder/util/StringListHelpers.java new file mode 100644 index 0000000..3ba4e6c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/util/StringListHelpers.java @@ -0,0 +1,96 @@ +package com.hypixel.hytale.server.npc.asset.builder.util; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StringListHelpers { + @Nonnull + private static Pattern listSplitter = Pattern.compile("[,; \t]"); + @Nonnull + private static Pattern listListSplitter = Pattern.compile("\\|"); + + private StringListHelpers() { + } + + @Nonnull + public static String stringListToString(@Nullable Collection list) { + return list == null ? "" : list.stream().map(String::trim).collect(Collectors.joining(", ")); + } + + @Nonnull + public static List splitToStringList(String string, @Nullable Function mapper) { + if (mapper == null) { + mapper = Function.identity(); + } + + return listSplitter.splitAsStream(string).filter(s -> !s.isEmpty()).map(mapper).collect(Collectors.toList()); + } + + public static void splitToStringList(String string, @Nullable Function mapper, @Nonnull Collection result) { + if (mapper == null) { + mapper = Function.identity(); + } + + listSplitter.splitAsStream(string).filter(s -> !s.isEmpty()).map(mapper).forEachOrdered(result::add); + } + + @Nonnull + public static String stringListListToString(@Nonnull Collection> list) { + return list.stream().map(StringListHelpers::stringListToString).collect(Collectors.joining("| ")); + } + + @Nonnull + public static List> splitToStringListList(@Nullable String string, Function mapper) { + return string != null && !string.isEmpty() + ? listListSplitter.splitAsStream(string) + .filter(s -> !s.isEmpty()) + .map(s -> splitToStringList(s, mapper)) + .filter(l -> l != null && !l.isEmpty()) + .collect(Collectors.toList()) + : Collections.emptyList(); + } + + public static void splitToStringListList( + String string, Function mapper, @Nonnull Collection> result, @Nonnull Supplier> supplier + ) { + listListSplitter.splitAsStream(string).filter(s -> !s.isEmpty()).map(s -> { + Collection r = supplier.get(); + splitToStringList(s, mapper, r); + return r; + }).filter(l -> l != null && !l.isEmpty()).forEachOrdered(result::add); + } + + @Nonnull + public static Set stringListToStringSet(@Nonnull List list) { + return new HashSet<>(list); + } + + @Nonnull + public static Set splitToStringSet(@Nullable String input) { + if (input != null && !input.isEmpty()) { + List list = splitToStringList(input, null); + return new HashSet<>(list); + } else { + return Collections.emptySet(); + } + } + + @Nonnull + public static Set splitToStringSet(@Nullable String input, Function transform) { + return input != null && !input.isEmpty() ? splitToStringList(input, null).stream().map(transform).collect(Collectors.toSet()) : Collections.emptySet(); + } + + @Nonnull + public static List> stringListListToStringSetList(@Nonnull List> group) { + return group.stream().map(HashSet::new).collect(Collectors.toList()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/AnyBooleanValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/AnyBooleanValidator.java new file mode 100644 index 0000000..65b6363 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/AnyBooleanValidator.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import java.util.Objects; +import javax.annotation.Nonnull; + +public class AnyBooleanValidator extends Validator { + @Nonnull + private final String[] attributes; + + private AnyBooleanValidator(@Nonnull String[] attributes) { + Objects.requireNonNull(attributes); + this.attributes = attributes; + } + + public static boolean test(@Nonnull boolean[] values) { + for (boolean value : values) { + if (value) { + return true; + } + } + + return false; + } + + @Nonnull + public static String errorMessage(String[] attributes) { + return "At least one of " + String.join(" ", attributes) + " must be true"; + } + + @Nonnull + public String errorMessage() { + return errorMessage(this.attributes); + } + + @Nonnull + public static AnyBooleanValidator withAttributes(String attribute1, String attribute2) { + return new AnyBooleanValidator(new String[]{attribute1, attribute2}); + } + + @Nonnull + public static AnyBooleanValidator withAttributes(@Nonnull String[] attributes) { + return new AnyBooleanValidator(attributes); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/AnyPresentValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/AnyPresentValidator.java new file mode 100644 index 0000000..b4887c0 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/AnyPresentValidator.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectHelper; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class AnyPresentValidator extends Validator { + @Nonnull + private final String[] attributes; + + private AnyPresentValidator(@Nonnull String[] attributes) { + Objects.requireNonNull(attributes); + this.attributes = attributes; + } + + public static boolean test(@Nonnull BuilderObjectHelper[] objects) { + for (BuilderObjectHelper object : objects) { + if (object.isPresent()) { + return true; + } + } + + return false; + } + + @Nonnull + public static String errorMessage(String[] attributes) { + return "At least one of " + String.join(" ", attributes) + " must be present"; + } + + @Nonnull + public String errorMessage() { + return errorMessage(this.attributes); + } + + @Nonnull + public static AnyPresentValidator withAttributes(String attribute1, String attribute2) { + return new AnyPresentValidator(new String[]{attribute1, attribute2}); + } + + @Nonnull + public static AnyPresentValidator withAttributes(@Nonnull String[] attributes) { + return new AnyPresentValidator(attributes); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/ArrayNotEmptyValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/ArrayNotEmptyValidator.java new file mode 100644 index 0000000..2b09792 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/ArrayNotEmptyValidator.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectArrayHelper; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ArrayNotEmptyValidator extends ArrayValidator { + private static final ArrayNotEmptyValidator INSTANCE = new ArrayNotEmptyValidator(); + + private ArrayNotEmptyValidator() { + } + + @Override + public boolean test(@Nonnull BuilderObjectArrayHelper builderObjectArrayHelper) { + return builderObjectArrayHelper.isPresent(); + } + + @Nonnull + @Override + public String errorMessage(String name, BuilderObjectArrayHelper builderObjectArrayHelper) { + return errorMessage(name); + } + + @Nonnull + @Override + public String errorMessage(BuilderObjectArrayHelper builderObjectArrayHelper) { + return errorMessage((String)null); + } + + @Nonnull + public static String errorMessage(@Nullable String name) { + if (name == null) { + name = "Array"; + } else { + name = "'" + name + "'"; + } + + return name + " must not be empty"; + } + + public static ArrayNotEmptyValidator get() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/ArrayValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/ArrayValidator.java new file mode 100644 index 0000000..dc442d5 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/ArrayValidator.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectArrayHelper; + +public abstract class ArrayValidator extends Validator { + public ArrayValidator() { + } + + public abstract boolean test(BuilderObjectArrayHelper var1); + + public abstract String errorMessage(String var1, BuilderObjectArrayHelper var2); + + public abstract String errorMessage(BuilderObjectArrayHelper var1); +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/ArraysOneSetValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/ArraysOneSetValidator.java new file mode 100644 index 0000000..a9d2078 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/ArraysOneSetValidator.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ArraysOneSetValidator extends Validator { + private final String[] attributes; + + private ArraysOneSetValidator(String[] attributes) { + this.attributes = attributes; + } + + public static boolean validate(String[] value1, String[] value2) { + return arrayContainsNonEmptyString(value1) || arrayContainsNonEmptyString(value2); + } + + private static boolean arrayContainsNonEmptyString(@Nullable String[] array) { + if (array != null) { + for (String value : array) { + if (value != null && !value.isEmpty()) { + return true; + } + } + } + + return false; + } + + @Nonnull + public static String formatErrorMessage(String attr1, String attr2, String context) { + return String.format("%s or %s must be provided in %s!", attr1, attr2, context); + } + + @Nonnull + public static ArraysOneSetValidator withAttributes(String attribute1, String attribute2) { + return new ArraysOneSetValidator(new String[]{attribute1, attribute2}); + } + + @Nonnull + public static ArraysOneSetValidator withAttributes(String[] attributes) { + return new ArraysOneSetValidator(attributes); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/AssetValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/AssetValidator.java new file mode 100644 index 0000000..4e6cd13 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/AssetValidator.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import com.hypixel.hytale.codec.schema.config.StringSchema; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public abstract class AssetValidator { + public static final EnumSet CanBeEmpty = EnumSet.of(AssetValidator.Config.NULLABLE, AssetValidator.Config.CAN_BE_EMPTY); + public static final EnumSet ListCanBeEmpty = EnumSet.of(AssetValidator.Config.LIST_NULLABLE, AssetValidator.Config.LIST_CAN_BE_EMPTY); + private final EnumSet config; + + public AssetValidator(EnumSet config) { + this.config = config; + } + + public AssetValidator() { + this(EnumSet.noneOf(AssetValidator.Config.class)); + } + + public boolean isNullable() { + return this.config.contains(AssetValidator.Config.NULLABLE); + } + + public boolean canBeEmpty() { + return this.config.contains(AssetValidator.Config.CAN_BE_EMPTY); + } + + public boolean isListNullable() { + return this.config.contains(AssetValidator.Config.LIST_NULLABLE); + } + + public boolean canListBeEmpty() { + return this.config.contains(AssetValidator.Config.LIST_CAN_BE_EMPTY); + } + + public boolean isMatcher() { + return this.config.contains(AssetValidator.Config.MATCHER); + } + + public abstract String getDomain(); + + public abstract boolean test(String var1); + + public abstract String errorMessage(String var1, String var2); + + public abstract String getAssetName(); + + public void updateSchema(@Nonnull StringSchema schema) { + if (!this.isMatcher()) { + schema.setHytaleAssetRef(this.getAssetName()); + } + } + + public static enum Config { + NULLABLE, + CAN_BE_EMPTY, + LIST_NULLABLE, + LIST_CAN_BE_EMPTY, + MATCHER; + + private Config() { + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/AtMostOneBooleanValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/AtMostOneBooleanValidator.java new file mode 100644 index 0000000..7c551fa --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/AtMostOneBooleanValidator.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import java.util.Objects; +import javax.annotation.Nonnull; + +public class AtMostOneBooleanValidator extends Validator { + @Nonnull + private final String[] attributes; + + private AtMostOneBooleanValidator(@Nonnull String[] attributes) { + Objects.requireNonNull(attributes); + this.attributes = attributes; + } + + public static boolean test(@Nonnull boolean[] values) { + int count = 0; + + for (boolean value : values) { + if (value) { + count++; + } + } + + return count <= 1; + } + + @Nonnull + public static String errorMessage(String[] attributes) { + return "At most one of " + String.join(" ", attributes) + " can be true"; + } + + @Nonnull + public String errorMessage() { + return errorMessage(this.attributes); + } + + @Nonnull + public static AtMostOneBooleanValidator withAttributes(String attribute1, String attribute2) { + return new AtMostOneBooleanValidator(new String[]{attribute1, attribute2}); + } + + @Nonnull + public static AtMostOneBooleanValidator withAttributes(@Nonnull String[] attributes) { + return new AtMostOneBooleanValidator(attributes); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/AttributeRelationValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/AttributeRelationValidator.java new file mode 100644 index 0000000..50bacd6 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/AttributeRelationValidator.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; + +public class AttributeRelationValidator extends Validator { + private final String firstAttribute; + private final RelationalOperator relation; + private final String secondAttribute; + + private AttributeRelationValidator(String firstAttribute, RelationalOperator relation, String secondAttribute) { + this.firstAttribute = firstAttribute; + this.relation = relation; + this.secondAttribute = secondAttribute; + } + + @Nonnull + public static AttributeRelationValidator withAttributes(String firstAttribute, RelationalOperator relation, String secondAttribute) { + return new AttributeRelationValidator(firstAttribute, relation, secondAttribute); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/BooleanArrayValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/BooleanArrayValidator.java new file mode 100644 index 0000000..fb53603 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/BooleanArrayValidator.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +public abstract class BooleanArrayValidator extends Validator { + public BooleanArrayValidator() { + } + + public abstract boolean test(boolean[] var1); + + public abstract String errorMessage(String var1, boolean[] var2); + + public abstract String errorMessage(boolean[] var1); +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/BooleanImplicationValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/BooleanImplicationValidator.java new file mode 100644 index 0000000..4459f4e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/BooleanImplicationValidator.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class BooleanImplicationValidator extends Validator { + private final String[] antecedentSet; + private final boolean antecedentState; + private final String[] consequentSet; + private final boolean consequentState; + private final boolean anyAntecedent; + + private BooleanImplicationValidator(String[] antecedentSet, boolean antecedentState, String[] consequentSet, boolean consequentState, boolean anyAntecedent) { + this.antecedentSet = antecedentSet; + this.antecedentState = antecedentState; + this.consequentSet = consequentSet; + this.consequentState = consequentState; + this.anyAntecedent = anyAntecedent; + } + + public boolean test(@Nonnull boolean[] antecedents, @Nonnull boolean[] consequents) { + boolean antecedent = this.anyAntecedent ? this.anyMatch(antecedents, this.antecedentState) : this.allMatch(antecedents, this.antecedentState); + return !antecedent || this.allMatch(consequents, this.consequentState); + } + + private boolean allMatch(@Nonnull boolean[] values, boolean expected) { + for (boolean value : values) { + if (value != expected) { + return false; + } + } + + return true; + } + + private boolean anyMatch(@Nonnull boolean[] values, boolean expected) { + for (boolean value : values) { + if (value == expected) { + return true; + } + } + + return false; + } + + @Nonnull + public String errorMessage() { + return String.format( + "If %s%s%s %s, all members of %s must be %s", + this.anyAntecedent ? "any of " : "all members of ", + Arrays.toString((Object[])this.antecedentSet), + this.anyAntecedent ? " is set to" : " are set to", + this.antecedentState, + Arrays.toString((Object[])this.consequentSet), + this.consequentState + ); + } + + @Nonnull + public static BooleanImplicationValidator withAttributes( + String[] antecedentSet, boolean antecedentState, String[] consequentSet, boolean consequentState, boolean anyAntecedent + ) { + return new BooleanImplicationValidator(antecedentSet, antecedentState, consequentSet, consequentState, anyAntecedent); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/ComponentOnlyValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/ComponentOnlyValidator.java new file mode 100644 index 0000000..1b397eb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/ComponentOnlyValidator.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +public class ComponentOnlyValidator extends Validator { + public static final ComponentOnlyValidator INSTANCE = new ComponentOnlyValidator(); + + private ComponentOnlyValidator() { + } + + public static ComponentOnlyValidator get() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleArrayValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleArrayValidator.java new file mode 100644 index 0000000..89ec154 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleArrayValidator.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +public abstract class DoubleArrayValidator extends Validator { + public DoubleArrayValidator() { + } + + public abstract boolean test(double[] var1); + + public abstract String errorMessage(double[] var1, String var2); + + public abstract String errorMessage(double[] var1); +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleOrValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleOrValidator.java new file mode 100644 index 0000000..6fcd9c3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleOrValidator.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; + +public class DoubleOrValidator extends DoubleValidator { + private static final DoubleOrValidator GREATER_EQUAL_0_OR_MINUS_1 = new DoubleOrValidator( + RelationalOperator.GreaterEqual, 0.0, RelationalOperator.Equal, -1.0 + ); + private final RelationalOperator relationOne; + private final RelationalOperator relationTwo; + private final double valueOne; + private final double valueTwo; + + private DoubleOrValidator(RelationalOperator relationOne, double valueOne, RelationalOperator relationTwo, double valueTwo) { + this.relationOne = relationOne; + this.valueOne = valueOne; + this.relationTwo = relationTwo; + this.valueTwo = valueTwo; + } + + @Override + public boolean test(double value) { + return compare(value, this.relationOne, this.valueOne) || compare(value, this.relationTwo, this.valueTwo); + } + + @Nonnull + @Override + public String errorMessage(double value) { + return this.errorMessage0(value, "Value"); + } + + @Nonnull + @Override + public String errorMessage(double value, String name) { + return this.errorMessage0(value, "\"" + name + "\""); + } + + @Nonnull + private String errorMessage0(double value, String name) { + return name + + " should be " + + this.relationOne.asText() + + " " + + this.valueOne + + " or " + + this.relationTwo.asText() + + " " + + this.valueTwo + + ", but is " + + value; + } + + public static DoubleOrValidator greaterEqual0OrMinus1() { + return GREATER_EQUAL_0_OR_MINUS_1; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleRangeValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleRangeValidator.java new file mode 100644 index 0000000..6132641 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleRangeValidator.java @@ -0,0 +1,71 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; + +public class DoubleRangeValidator extends DoubleValidator { + private static final DoubleRangeValidator VALIDATOR_BETWEEN_01 = new DoubleRangeValidator( + RelationalOperator.GreaterEqual, 0.0, RelationalOperator.LessEqual, 1.0 + ); + private final RelationalOperator relationLower; + private final double lower; + private final RelationalOperator relationUpper; + private final double upper; + + private DoubleRangeValidator(RelationalOperator relationLower, double lower, RelationalOperator relationUpper, double upper) { + this.lower = lower; + this.upper = upper; + this.relationLower = relationLower; + this.relationUpper = relationUpper; + } + + public static DoubleRangeValidator between01() { + return VALIDATOR_BETWEEN_01; + } + + @Nonnull + public static DoubleRangeValidator between(double lower, double upper) { + return new DoubleRangeValidator(RelationalOperator.GreaterEqual, lower, RelationalOperator.LessEqual, upper); + } + + @Nonnull + public static DoubleRangeValidator fromExclToIncl(double lower, double upper) { + return new DoubleRangeValidator(RelationalOperator.Greater, lower, RelationalOperator.LessEqual, upper); + } + + @Nonnull + public static DoubleRangeValidator fromExclToExcl(double lower, double upper) { + return new DoubleRangeValidator(RelationalOperator.Greater, lower, RelationalOperator.Less, upper); + } + + @Override + public boolean test(double value) { + return compare(value, this.relationLower, this.lower) && compare(value, this.relationUpper, this.upper); + } + + @Nonnull + @Override + public String errorMessage(double value) { + return this.errorMessage0(value, "Value"); + } + + @Nonnull + @Override + public String errorMessage(double value, String name) { + return this.errorMessage0(value, "\"" + name + "\""); + } + + @Nonnull + private String errorMessage0(double value, String name) { + return name + + " should be " + + this.relationLower.asText() + + " " + + this.lower + + " and " + + this.relationUpper.asText() + + " " + + this.upper + + " but is " + + value; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleSequenceValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleSequenceValidator.java new file mode 100644 index 0000000..0fbce06 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleSequenceValidator.java @@ -0,0 +1,125 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class DoubleSequenceValidator extends DoubleArrayValidator { + private static final DoubleSequenceValidator VALIDATOR_BETWEEN_01 = new DoubleSequenceValidator( + RelationalOperator.GreaterEqual, 0.0, RelationalOperator.LessEqual, 1.0, null + ); + private static final DoubleSequenceValidator VALIDATOR_BETWEEN_01_WEAKLY_MONOTONIC = new DoubleSequenceValidator( + RelationalOperator.GreaterEqual, 0.0, RelationalOperator.LessEqual, 1.0, RelationalOperator.LessEqual + ); + private static final DoubleSequenceValidator VALIDATOR_BETWEEN_01_MONOTONIC = new DoubleSequenceValidator( + RelationalOperator.GreaterEqual, 0.0, RelationalOperator.LessEqual, 1.0, RelationalOperator.Less + ); + private static final DoubleSequenceValidator VALIDATOR_WEAKLY_MONOTONIC = new DoubleSequenceValidator( + RelationalOperator.GreaterEqual, -Double.MAX_VALUE, RelationalOperator.LessEqual, Double.MAX_VALUE, RelationalOperator.LessEqual + ); + private static final DoubleSequenceValidator VALIDATOR_MONOTONIC = new DoubleSequenceValidator( + RelationalOperator.GreaterEqual, -Double.MAX_VALUE, RelationalOperator.LessEqual, Double.MAX_VALUE, RelationalOperator.Less + ); + private final RelationalOperator relationLower; + private final double lower; + private final RelationalOperator relationUpper; + private final double upper; + private final RelationalOperator relationSequence; + + private DoubleSequenceValidator( + RelationalOperator relationLower, double lower, RelationalOperator relationUpper, double upper, RelationalOperator relationSequence + ) { + this.lower = lower; + this.upper = upper; + this.relationLower = relationLower; + this.relationUpper = relationUpper; + this.relationSequence = relationSequence; + } + + public static DoubleSequenceValidator between01() { + return VALIDATOR_BETWEEN_01; + } + + public static DoubleSequenceValidator between01WeaklyMonotonic() { + return VALIDATOR_BETWEEN_01_WEAKLY_MONOTONIC; + } + + public static DoubleSequenceValidator between01Monotonic() { + return VALIDATOR_BETWEEN_01_MONOTONIC; + } + + @Nonnull + public static DoubleSequenceValidator between(double lower, double upper) { + return new DoubleSequenceValidator(RelationalOperator.GreaterEqual, lower, RelationalOperator.LessEqual, upper, null); + } + + @Nonnull + public static DoubleSequenceValidator betweenWeaklyMonotonic(double lower, double upper) { + return new DoubleSequenceValidator(RelationalOperator.GreaterEqual, lower, RelationalOperator.LessEqual, upper, RelationalOperator.LessEqual); + } + + @Nonnull + public static DoubleSequenceValidator betweenMonotonic(double lower, double upper) { + return new DoubleSequenceValidator(RelationalOperator.GreaterEqual, lower, RelationalOperator.LessEqual, upper, RelationalOperator.Less); + } + + @Nonnull + public static DoubleSequenceValidator fromExclToIncl(double lower, double upper) { + return new DoubleSequenceValidator(RelationalOperator.Greater, lower, RelationalOperator.LessEqual, upper, null); + } + + @Nonnull + public static DoubleSequenceValidator fromExclToInclWeaklyMonotonic(double lower, double upper) { + return new DoubleSequenceValidator(RelationalOperator.Greater, lower, RelationalOperator.LessEqual, upper, RelationalOperator.LessEqual); + } + + @Nonnull + public static DoubleSequenceValidator fromExclToInclMonotonic(double lower, double upper) { + return new DoubleSequenceValidator(RelationalOperator.Greater, lower, RelationalOperator.LessEqual, upper, RelationalOperator.Less); + } + + public static DoubleSequenceValidator monotonic() { + return VALIDATOR_MONOTONIC; + } + + public static DoubleSequenceValidator weaklyMonotonic() { + return VALIDATOR_WEAKLY_MONOTONIC; + } + + @Override + public boolean test(@Nonnull double[] values) { + for (int i = 0; i < values.length; i++) { + double value = values[i]; + if (!DoubleValidator.compare(value, this.relationLower, this.lower) && DoubleValidator.compare(value, this.relationUpper, this.upper)) { + return false; + } + + if (i > 0 && this.relationSequence != null && !DoubleValidator.compare(values[i - 1], this.relationSequence, value)) { + return false; + } + } + + return true; + } + + @Nonnull + @Override + public String errorMessage(double[] value) { + return this.errorMessage0(value, "Array"); + } + + @Nonnull + @Override + public String errorMessage(double[] value, String name) { + return this.errorMessage0(value, "\"" + name + "\""); + } + + @Nonnull + private String errorMessage0(double[] value, String name) { + return name + + (this.relationLower == null ? "" : " values should be " + this.relationLower.asText() + " " + this.lower + " and ") + + (this.relationUpper == null ? "" : " values should be " + this.relationUpper.asText() + " " + this.upper + " and ") + + (this.relationSequence == null ? "" : " succeeding values should be " + this.relationSequence.asText() + " preceding values ") + + " but is " + + Arrays.toString(value); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleSingleValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleSingleValidator.java new file mode 100644 index 0000000..dff33c1 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleSingleValidator.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; + +public class DoubleSingleValidator extends DoubleValidator { + private static final DoubleSingleValidator VALIDATOR_GREATER_0 = new DoubleSingleValidator(RelationalOperator.Greater, 0.0); + private static final DoubleSingleValidator VALIDATOR_GREATER_EQUAL_0 = new DoubleSingleValidator(RelationalOperator.GreaterEqual, 0.0); + private final RelationalOperator relation; + private final double value; + + private DoubleSingleValidator(RelationalOperator relation, double value) { + this.value = value; + this.relation = relation; + } + + public static DoubleSingleValidator greater0() { + return VALIDATOR_GREATER_0; + } + + @Nonnull + public static DoubleSingleValidator greater(double threshold) { + return new DoubleSingleValidator(RelationalOperator.Greater, threshold); + } + + public static DoubleSingleValidator greaterEqual0() { + return VALIDATOR_GREATER_EQUAL_0; + } + + @Override + public boolean test(double value) { + return compare(value, this.relation, this.value); + } + + @Nonnull + @Override + public String errorMessage(double value) { + return this.errorMessage0(value, "Value"); + } + + @Nonnull + @Override + public String errorMessage(double value, String name) { + return this.errorMessage0(value, "\"" + name + "\""); + } + + @Nonnull + private String errorMessage0(double value, String name) { + return name + " should be " + this.relation.asText() + " " + this.value + " but is " + value; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleValidator.java new file mode 100644 index 0000000..ace725e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/DoubleValidator.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; + +public abstract class DoubleValidator extends Validator { + public DoubleValidator() { + } + + public abstract boolean test(double var1); + + public static boolean compare(double value, @Nonnull RelationalOperator predicate, double c) { + return switch (predicate) { + case NotEqual -> value != c; + case Less -> value < c; + case LessEqual -> value <= c; + case Greater -> value > c; + case GreaterEqual -> value >= c; + case Equal -> value == c; + }; + } + + public abstract String errorMessage(double var1); + + public abstract String errorMessage(double var1, String var3); +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/EnumArrayNoDuplicatesValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/EnumArrayNoDuplicatesValidator.java new file mode 100644 index 0000000..d8f01b0 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/EnumArrayNoDuplicatesValidator.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import java.util.Arrays; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class EnumArrayNoDuplicatesValidator extends EnumArrayValidator { + private static final EnumArrayNoDuplicatesValidator INSTANCE = new EnumArrayNoDuplicatesValidator(); + + private EnumArrayNoDuplicatesValidator() { + } + + @Override + public > boolean test(@Nonnull T[] array, Class clazz) { + EnumSet set = EnumSet.noneOf(clazz); + + for (T item : array) { + if (!set.add(item)) { + return false; + } + } + + return true; + } + + @Nonnull + @Override + public > String errorMessage(String name, T[] array) { + return String.format("%s must not contain duplicates: %s", name, Arrays.toString((Object[])array)); + } + + public static EnumArrayNoDuplicatesValidator get() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/EnumArrayValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/EnumArrayValidator.java new file mode 100644 index 0000000..3097d94 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/EnumArrayValidator.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +public abstract class EnumArrayValidator extends Validator { + public EnumArrayValidator() { + } + + public abstract > boolean test(T[] var1, Class var2); + + public abstract > String errorMessage(String var1, T[] var2); +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/ExistsIfParameterSetValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/ExistsIfParameterSetValidator.java new file mode 100644 index 0000000..b347035 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/ExistsIfParameterSetValidator.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; + +public class ExistsIfParameterSetValidator extends Validator { + private final String parameter; + private final String attribute; + + private ExistsIfParameterSetValidator(String parameter, String attribute) { + this.parameter = parameter; + this.attribute = attribute; + } + + @Nonnull + public static String errorMessage(String parameter, String attribute) { + return String.format("If %s is set, %s must be present", parameter, attribute); + } + + @Nonnull + public static ExistsIfParameterSetValidator withAttributes(String parameter, String attribute) { + return new ExistsIfParameterSetValidator(parameter, attribute); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/InstructionContextValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/InstructionContextValidator.java new file mode 100644 index 0000000..acd6a7f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/InstructionContextValidator.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import com.hypixel.hytale.server.npc.asset.builder.ComponentContext; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class InstructionContextValidator extends Validator { + private final EnumSet instructionTypes; + private final EnumSet componentContexts; + + private InstructionContextValidator(EnumSet instructionTypes, EnumSet componentContexts) { + this.instructionTypes = instructionTypes; + this.componentContexts = componentContexts; + } + + @Nonnull + public static String getErrorMessage( + @Nonnull String value, + @Nonnull InstructionType instructionContext, + boolean instructionMatched, + @Nonnull ComponentContext componentContext, + boolean extraMatched, + String breadcrumbs + ) { + StringBuilder sb = new StringBuilder(value).append(" not valid"); + if (!instructionMatched) { + sb.append(" in instruction ").append(instructionContext.get()); + } + + if (!extraMatched) { + sb.append(" in context ").append(componentContext.get()); + } + + sb.append(" at: ").append(breadcrumbs); + return sb.toString(); + } + + @Nonnull + public static InstructionContextValidator inInstructions(EnumSet instructionTypes, EnumSet componentContexts) { + return new InstructionContextValidator(instructionTypes, componentContexts); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntArrayValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntArrayValidator.java new file mode 100644 index 0000000..df699a3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntArrayValidator.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +public abstract class IntArrayValidator extends Validator { + public IntArrayValidator() { + } + + public abstract boolean test(int[] var1); + + public abstract String errorMessage(int[] var1, String var2); + + public abstract String errorMessage(int[] var1); +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntOrValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntOrValidator.java new file mode 100644 index 0000000..48e351f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntOrValidator.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; + +public class IntOrValidator extends IntValidator { + private final RelationalOperator relationOne; + private final RelationalOperator relationTwo; + private final int valueOne; + private final int valueTwo; + + private IntOrValidator(RelationalOperator relationOne, int valueOne, RelationalOperator relationTwo, int valueTwo) { + this.relationOne = relationOne; + this.valueOne = valueOne; + this.relationTwo = relationTwo; + this.valueTwo = valueTwo; + } + + @Override + public boolean test(int value) { + return compare(value, this.relationOne, this.valueOne) || compare(value, this.relationTwo, this.valueTwo); + } + + @Nonnull + @Override + public String errorMessage(int value) { + return this.errorMessage0(value, "Value"); + } + + @Nonnull + @Override + public String errorMessage(int value, String name) { + return this.errorMessage0(value, "\"" + name + "\""); + } + + @Nonnull + private String errorMessage0(int value, String name) { + return name + + " should be " + + this.relationOne.asText() + + " " + + this.valueOne + + " or " + + this.relationTwo.asText() + + " " + + this.valueTwo + + ", but is " + + value; + } + + @Nonnull + public static IntOrValidator greater0OrMinus1() { + return new IntOrValidator(RelationalOperator.Greater, 0, RelationalOperator.Equal, -1); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntRangeValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntRangeValidator.java new file mode 100644 index 0000000..a147587 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntRangeValidator.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; + +public class IntRangeValidator extends IntValidator { + private final RelationalOperator relationLower; + private final int lower; + private final RelationalOperator relationUpper; + private final int upper; + + public IntRangeValidator(RelationalOperator relationLower, int lower, RelationalOperator relationUpper, int upper) { + this.lower = lower; + this.upper = upper; + this.relationLower = relationLower; + this.relationUpper = relationUpper; + } + + @Override + public boolean test(int value) { + return compare(value, this.relationLower, this.lower) && compare(value, this.relationUpper, this.upper); + } + + @Nonnull + @Override + public String errorMessage(int value) { + return this.errorMessage0(value, "Value"); + } + + @Nonnull + @Override + public String errorMessage(int value, String name) { + return this.errorMessage0(value, "\"" + name + "\""); + } + + @Nonnull + private String errorMessage0(int value, String name) { + return name + + " should be " + + this.relationLower.asText() + + " " + + this.lower + + " and " + + this.relationUpper.asText() + + " " + + this.upper + + " but is " + + value; + } + + @Nonnull + public static IntRangeValidator fromInclToExcl(int lower, int upper) { + return new IntRangeValidator(RelationalOperator.GreaterEqual, lower, RelationalOperator.Less, upper); + } + + @Nonnull + public static IntRangeValidator fromExclToIncl(int lower, int upper) { + return new IntRangeValidator(RelationalOperator.Greater, lower, RelationalOperator.LessEqual, upper); + } + + @Nonnull + public static IntRangeValidator between(int lower, int upper) { + return new IntRangeValidator(RelationalOperator.GreaterEqual, lower, RelationalOperator.LessEqual, upper); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntSequenceValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntSequenceValidator.java new file mode 100644 index 0000000..e051598 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntSequenceValidator.java @@ -0,0 +1,109 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class IntSequenceValidator extends IntArrayValidator { + private static final IntSequenceValidator VALIDATOR_BETWEEN_01 = new IntSequenceValidator( + RelationalOperator.GreaterEqual, 0, RelationalOperator.LessEqual, 1, null + ); + private static final IntSequenceValidator VALIDATOR_BETWEEN_01_WEAKLY_MONOTONIC = new IntSequenceValidator( + RelationalOperator.GreaterEqual, 0, RelationalOperator.LessEqual, 1, RelationalOperator.LessEqual + ); + private static final IntSequenceValidator VALIDATOR_BETWEEN_01_MONOTONIC = new IntSequenceValidator( + RelationalOperator.GreaterEqual, 0, RelationalOperator.LessEqual, 1, RelationalOperator.Less + ); + private final RelationalOperator relationLower; + private final int lower; + private final RelationalOperator relationUpper; + private final int upper; + private final RelationalOperator relationSequence; + + private IntSequenceValidator(RelationalOperator relationLower, int lower, RelationalOperator relationUpper, int upper, RelationalOperator relationSequence) { + this.lower = lower; + this.upper = upper; + this.relationLower = relationLower; + this.relationUpper = relationUpper; + this.relationSequence = relationSequence; + } + + public static IntSequenceValidator between01() { + return VALIDATOR_BETWEEN_01; + } + + public static IntSequenceValidator between01WeaklyMonotonic() { + return VALIDATOR_BETWEEN_01_WEAKLY_MONOTONIC; + } + + public static IntSequenceValidator between01Monotonic() { + return VALIDATOR_BETWEEN_01_MONOTONIC; + } + + @Nonnull + public static IntSequenceValidator between(int lower, int upper) { + return new IntSequenceValidator(RelationalOperator.GreaterEqual, lower, RelationalOperator.LessEqual, upper, null); + } + + @Nonnull + public static IntSequenceValidator betweenWeaklyMonotonic(int lower, int upper) { + return new IntSequenceValidator(RelationalOperator.GreaterEqual, lower, RelationalOperator.LessEqual, upper, RelationalOperator.LessEqual); + } + + @Nonnull + public static IntSequenceValidator betweenMonotonic(int lower, int upper) { + return new IntSequenceValidator(RelationalOperator.GreaterEqual, lower, RelationalOperator.LessEqual, upper, RelationalOperator.Less); + } + + @Nonnull + public static IntSequenceValidator fromExclToIncl(int lower, int upper) { + return new IntSequenceValidator(RelationalOperator.Greater, lower, RelationalOperator.LessEqual, upper, null); + } + + @Nonnull + public static IntSequenceValidator fromExclToInclWeaklyMonotonic(int lower, int upper) { + return new IntSequenceValidator(RelationalOperator.Greater, lower, RelationalOperator.LessEqual, upper, RelationalOperator.LessEqual); + } + + @Nonnull + public static IntSequenceValidator fromExclToInclMonotonic(int lower, int upper) { + return new IntSequenceValidator(RelationalOperator.Greater, lower, RelationalOperator.LessEqual, upper, RelationalOperator.Less); + } + + @Override + public boolean test(@Nonnull int[] values) { + for (int i = 0; i < values.length; i++) { + int value = values[i]; + if (!IntValidator.compare(value, this.relationLower, this.lower) && IntValidator.compare(value, this.relationUpper, this.upper)) { + return false; + } + + if (i > 0 && this.relationSequence != null && !IntValidator.compare(values[i - 1], this.relationSequence, value)) { + return false; + } + } + + return true; + } + + @Nonnull + @Override + public String errorMessage(int[] value) { + return this.errorMessage0(value, "Array"); + } + + @Nonnull + @Override + public String errorMessage(int[] value, String name) { + return this.errorMessage0(value, "\"" + name + "\""); + } + + @Nonnull + private String errorMessage0(int[] value, String name) { + return name + + (this.relationLower == null ? "" : " values should be " + this.relationLower.asText() + " " + this.lower + " and ") + + (this.relationUpper == null ? "" : " values should be " + this.relationUpper.asText() + " " + this.upper + " and ") + + (this.relationSequence == null ? "" : " succeeding values should be " + this.relationSequence.asText() + " preceding values ") + + " but is " + + Arrays.toString(value); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntSingleValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntSingleValidator.java new file mode 100644 index 0000000..09a4dfb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntSingleValidator.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; + +public class IntSingleValidator extends IntValidator { + private static final IntSingleValidator VALIDATOR_GREATER_EQUAL_0 = new IntSingleValidator(RelationalOperator.GreaterEqual, 0); + private static final IntSingleValidator VALIDATOR_GREATER_0 = new IntSingleValidator(RelationalOperator.Greater, 0); + private final RelationalOperator relation; + private final int value; + + private IntSingleValidator(RelationalOperator relation, int value) { + this.value = value; + this.relation = relation; + } + + @Override + public boolean test(int value) { + return compare(value, this.relation, this.value); + } + + @Nonnull + @Override + public String errorMessage(int value) { + return this.errorMessage0(value, "Value"); + } + + @Nonnull + @Override + public String errorMessage(int value, String name) { + return this.errorMessage0(value, "\"" + name + "\""); + } + + @Nonnull + private String errorMessage0(int value, String name) { + return name + " should be " + this.relation.asText() + " " + this.value + " but is " + value; + } + + public static IntValidator greaterEqual0() { + return VALIDATOR_GREATER_EQUAL_0; + } + + public static IntValidator greater0() { + return VALIDATOR_GREATER_0; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntValidator.java new file mode 100644 index 0000000..3163cca --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/IntValidator.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; + +public abstract class IntValidator extends Validator { + public IntValidator() { + } + + public abstract boolean test(int var1); + + public static boolean compare(int value, @Nonnull RelationalOperator op, int c) { + return switch (op) { + case NotEqual -> value != c; + case Less -> value < c; + case LessEqual -> value <= c; + case Greater -> value > c; + case GreaterEqual -> value >= c; + case Equal -> value == c; + }; + } + + public abstract String errorMessage(int var1); + + public abstract String errorMessage(int var1, String var2); +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/NoDuplicatesValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/NoDuplicatesValidator.java new file mode 100644 index 0000000..5872474 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/NoDuplicatesValidator.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nonnull; + +public class NoDuplicatesValidator extends Validator { + private final Iterable iterable; + private final String variableName; + + private NoDuplicatesValidator(Iterable iterable, String variableName) { + this.iterable = iterable; + this.variableName = variableName; + } + + public boolean test() { + Set set = new HashSet<>(); + + for (T each : this.iterable) { + if (!set.add(each)) { + return false; + } + } + + return true; + } + + @Nonnull + public String errorMessage() { + return "There are not allowed to be duplicate entries in the \"" + this.variableName + "\" list."; + } + + @Nonnull + public static NoDuplicatesValidator withAttributes(Iterable iterable, String variableName) { + return new NoDuplicatesValidator<>(iterable, variableName); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/OneOrNonePresentValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/OneOrNonePresentValidator.java new file mode 100644 index 0000000..9d4b1bf --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/OneOrNonePresentValidator.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectHelper; +import java.util.Objects; +import java.util.function.IntPredicate; +import javax.annotation.Nonnull; + +public class OneOrNonePresentValidator extends Validator { + @Nonnull + private final String[] attributes; + + private OneOrNonePresentValidator(String... attributes) { + this.attributes = Objects.requireNonNull(attributes, "Attributes in OneOrNonePresentValidator must not be null"); + } + + public static boolean test(@Nonnull BuilderObjectHelper[] objects) { + return OnePresentValidator.countPresent(objects.length, i -> objects[i].isPresent()) <= 1; + } + + public static boolean test(@Nonnull boolean[] readStatus) { + return OnePresentValidator.countPresent(readStatus.length, i -> readStatus[i]) <= 1; + } + + public static boolean test(@Nonnull BuilderObjectHelper objectHelper1, @Nonnull BuilderObjectHelper objectHelper2) { + return OnePresentValidator.countPresent(objectHelper1, objectHelper2) <= 1; + } + + public static boolean test( + @Nonnull BuilderObjectHelper objectHelper1, @Nonnull BuilderObjectHelper objectHelper2, @Nonnull BuilderObjectHelper objectHelper3 + ) { + return OnePresentValidator.countPresent(objectHelper1, objectHelper2, objectHelper3) <= 1; + } + + @Nonnull + public static String errorMessage(@Nonnull String[] attributes, BuilderObjectHelper[] objectHelpers) { + return errorMessage(attributes, i -> objectHelpers[i].isPresent()); + } + + @Nonnull + public static String errorMessage(@Nonnull String[] attributes, boolean[] readStatus) { + return errorMessage(attributes, i -> readStatus[i]); + } + + @Nonnull + public static String errorMessage(@Nonnull String[] attributes, @Nonnull IntPredicate presentPredicate) { + StringBuilder result = new StringBuilder("Exactly one or none of "); + String sep = ", "; + + for (int i = 0; i < attributes.length; i++) { + if (i == attributes.length - 1) { + sep = ""; + } + + result.append(String.format("'%s'%s%s", attributes[i], presentPredicate.test(i) ? "(Present)" : "", sep)); + } + + return result + " must be present"; + } + + @Nonnull + public static OneOrNonePresentValidator withAttributes(String... attributes) { + return new OneOrNonePresentValidator(attributes); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/OnePresentValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/OnePresentValidator.java new file mode 100644 index 0000000..47de56e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/OnePresentValidator.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectHelper; +import java.util.Objects; +import java.util.function.IntPredicate; +import javax.annotation.Nonnull; + +public class OnePresentValidator extends Validator { + @Nonnull + private final String[] attributes; + + private OnePresentValidator(String... attributes) { + this.attributes = Objects.requireNonNull(attributes, "Attributes in OnePresentValidator must not be null"); + } + + public static int countPresent(int size, @Nonnull IntPredicate presentPredicate) { + int count = 0; + + for (int i = 0; i < size; i++) { + if (presentPredicate.test(i)) { + count++; + } + } + + return count; + } + + public static boolean test(@Nonnull BuilderObjectHelper[] objects) { + return countPresent(objects.length, i -> objects[i].isPresent()) == 1; + } + + public static boolean test(@Nonnull boolean[] readStatus) { + return countPresent(readStatus.length, i -> readStatus[i]) == 1; + } + + public static int countPresent(@Nonnull BuilderObjectHelper objectHelper) { + return objectHelper.isPresent() ? 1 : 0; + } + + public static int countPresent(@Nonnull BuilderObjectHelper objectHelper1, @Nonnull BuilderObjectHelper objectHelper2) { + return countPresent(objectHelper1) + countPresent(objectHelper2); + } + + public static int countPresent( + @Nonnull BuilderObjectHelper objectHelper1, @Nonnull BuilderObjectHelper objectHelper2, @Nonnull BuilderObjectHelper objectHelper3 + ) { + return countPresent(objectHelper1) + countPresent(objectHelper2, objectHelper3); + } + + public static boolean test(@Nonnull BuilderObjectHelper objectHelper1, @Nonnull BuilderObjectHelper objectHelper2) { + return countPresent(objectHelper1, objectHelper2) == 1; + } + + public static boolean test( + @Nonnull BuilderObjectHelper objectHelper1, @Nonnull BuilderObjectHelper objectHelper2, @Nonnull BuilderObjectHelper objectHelper3 + ) { + return countPresent(objectHelper1, objectHelper2, objectHelper3) == 1; + } + + @Nonnull + public static String errorMessage(@Nonnull String[] attributes, BuilderObjectHelper[] objects) { + return errorMessage(attributes, i -> objects[i].isPresent()); + } + + @Nonnull + public static String errorMessage(@Nonnull String[] attributes, boolean[] readStatus) { + return errorMessage(attributes, i -> readStatus[i]); + } + + @Nonnull + public static String errorMessage(@Nonnull String[] attributes, @Nonnull IntPredicate presentPredicate) { + StringBuilder result = new StringBuilder("Exactly one of "); + String sep = ", "; + + for (int i = 0; i < attributes.length; i++) { + if (i == attributes.length - 1) { + sep = ""; + } + + result.append(String.format("'%s'%s%s", attributes[i], presentPredicate.test(i) ? "(Present)" : "", sep)); + } + + return result + " must be present"; + } + + @Nonnull + public static OnePresentValidator withAttributes(String... attributes) { + return new OnePresentValidator(attributes); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/RelationalOperator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/RelationalOperator.java new file mode 100644 index 0000000..09c17e0 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/RelationalOperator.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import java.util.function.Supplier; + +public enum RelationalOperator implements Supplier { + NotEqual("not equal to"), + Less("less than"), + LessEqual("less than or equal to"), + Greater("greater than"), + GreaterEqual("greater than or equal to"), + Equal("equal to"); + + private final String asText; + + private RelationalOperator(String text) { + this.asText = text; + } + + public String asText() { + return this.asText; + } + + public String get() { + return this.asText; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/RequiredFeatureValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/RequiredFeatureValidator.java new file mode 100644 index 0000000..2933d42 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/RequiredFeatureValidator.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import com.hypixel.hytale.server.npc.asset.builder.FeatureEvaluatorHelper; +import javax.annotation.Nullable; + +public abstract class RequiredFeatureValidator extends Validator { + public RequiredFeatureValidator() { + } + + public abstract boolean validate(FeatureEvaluatorHelper var1); + + @Nullable + public abstract String getErrorMessage(String var1); +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/RequiresFeatureIfEnumValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/RequiresFeatureIfEnumValidator.java new file mode 100644 index 0000000..9381ebb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/RequiresFeatureIfEnumValidator.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.FeatureEvaluatorHelper; +import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.FeatureProviderEvaluator; +import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.ProviderEvaluator; +import java.util.EnumSet; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RequiresFeatureIfEnumValidator & Supplier> extends RequiredFeatureValidator { + @Nonnull + private final String[] description; + private final String attribute; + private final E value; + + private RequiresFeatureIfEnumValidator(String attribute, E value, @Nonnull EnumSet feature) { + this.attribute = attribute; + this.description = BuilderBase.getDescriptionArray(feature); + this.value = value; + } + + @Override + public boolean validate(FeatureEvaluatorHelper evaluatorHelper) { + return false; + } + + @Nullable + @Override + public String getErrorMessage(String context) { + return null; + } + + public static & Supplier> boolean staticValidate( + @Nonnull FeatureEvaluatorHelper evaluatorHelper, EnumSet requiredFeature, E requiredValue, E value + ) { + if (requiredValue != value) { + return true; + } else { + for (ProviderEvaluator providedFeature : evaluatorHelper.getProviders()) { + if (providedFeature instanceof FeatureProviderEvaluator && ((FeatureProviderEvaluator)providedFeature).provides(requiredFeature)) { + return true; + } + } + + return false; + } + } + + @Nonnull + public static & Supplier> RequiresFeatureIfEnumValidator withAttributes( + String attribute, E value, @Nonnull EnumSet feature + ) { + return new RequiresFeatureIfEnumValidator(attribute, value, feature); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/RequiresFeatureIfValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/RequiresFeatureIfValidator.java new file mode 100644 index 0000000..91a066b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/RequiresFeatureIfValidator.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.FeatureEvaluatorHelper; +import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.FeatureProviderEvaluator; +import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.ProviderEvaluator; +import java.util.EnumSet; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RequiresFeatureIfValidator extends RequiredFeatureValidator { + @Nonnull + private final String[] description; + private final String attribute; + private final boolean value; + + private RequiresFeatureIfValidator(String attribute, boolean value, @Nonnull EnumSet feature) { + this.attribute = attribute; + this.description = BuilderBase.getDescriptionArray(feature); + this.value = value; + } + + @Override + public boolean validate(FeatureEvaluatorHelper evaluatorHelper) { + return false; + } + + @Nullable + @Override + public String getErrorMessage(String context) { + return null; + } + + public static boolean staticValidate(@Nonnull FeatureEvaluatorHelper evaluatorHelper, EnumSet requiredFeature, boolean requiredValue, boolean value) { + if (requiredValue != value) { + return true; + } else { + for (ProviderEvaluator providedFeature : evaluatorHelper.getProviders()) { + if (providedFeature instanceof FeatureProviderEvaluator && ((FeatureProviderEvaluator)providedFeature).provides(requiredFeature)) { + return true; + } + } + + return false; + } + } + + @Nonnull + public static RequiresFeatureIfValidator withAttributes(String attribute, boolean value, @Nonnull EnumSet feature) { + return new RequiresFeatureIfValidator(attribute, value, feature); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/RequiresOneOfFeaturesValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/RequiresOneOfFeaturesValidator.java new file mode 100644 index 0000000..777b88a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/RequiresOneOfFeaturesValidator.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.FeatureEvaluatorHelper; +import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.FeatureProviderEvaluator; +import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.ProviderEvaluator; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class RequiresOneOfFeaturesValidator extends RequiredFeatureValidator { + @Nonnull + private final EnumSet requiredFeature; + @Nonnull + private final String[] description; + + private RequiresOneOfFeaturesValidator(@Nonnull EnumSet requiredFeature) { + this.requiredFeature = requiredFeature; + this.description = BuilderBase.getDescriptionArray(requiredFeature); + } + + @Override + public boolean validate(@Nonnull FeatureEvaluatorHelper evaluatorHelper) { + for (ProviderEvaluator provider : evaluatorHelper.getProviders()) { + if (provider instanceof FeatureProviderEvaluator && ((FeatureProviderEvaluator)provider).provides(this.requiredFeature)) { + return true; + } + } + + return false; + } + + @Nonnull + @Override + public String getErrorMessage(String context) { + return String.format("At least one of required features %s must be provided at %s", String.join(", ", this.description), context); + } + + @Nonnull + public static RequiresOneOfFeaturesValidator withFeatures(@Nonnull EnumSet requiredFeature) { + return new RequiresOneOfFeaturesValidator(requiredFeature); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/StateStringValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StateStringValidator.java new file mode 100644 index 0000000..a3da281 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StateStringValidator.java @@ -0,0 +1,102 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StateStringValidator extends StringValidator { + private String[] stateParts; + private final boolean allowEmptyMain; + private final boolean mainStateOnly; + private final boolean allowNull; + + private StateStringValidator(boolean allowEmptyMain, boolean mainStateOnly, boolean allowNull) { + this.allowEmptyMain = allowEmptyMain; + this.mainStateOnly = mainStateOnly; + this.allowNull = allowNull; + } + + @Override + public boolean test(@Nullable String value) { + if (value == null) { + return this.allowNull; + } else if (value.isEmpty()) { + return false; + } else { + this.stateParts = value.split("\\."); + if (this.stateParts.length > 2) { + return false; + } else if (this.stateParts.length > 1 && this.mainStateOnly) { + return false; + } else if (this.stateParts.length > 1 && this.allowEmptyMain) { + String statePart = this.stateParts[1]; + return statePart != null && !statePart.isEmpty(); + } else { + return this.stateParts.length == 0 ? false : this.stateParts[0] != null && !this.stateParts[0].isEmpty(); + } + } + } + + @Nonnull + @Override + public String errorMessage(String value) { + return String.format( + "%s is not a valid format for a state string. May only contain one . separator and must not be empty.%s%s", + value, + this.allowEmptyMain ? "" : " Main state must not be empty.", + this.mainStateOnly ? " Sub state must not be set." : "" + ); + } + + @Nonnull + @Override + public String errorMessage(String value, String name) { + return String.format( + "Parameter %s, %s is not a valid format for a state string. May only contain one . separator and must not be empty.%s%s", + name, + value, + this.allowEmptyMain ? "" : " Main state must not be empty.", + this.mainStateOnly ? " Sub state must not be set." : "" + ); + } + + public boolean hasMainState() { + if (this.stateParts.length <= 0) { + return false; + } else { + String statePart = this.stateParts[0]; + return statePart != null && !statePart.isEmpty(); + } + } + + public boolean hasSubState() { + return this.stateParts.length > 1 && this.stateParts[1] != null && !this.stateParts[1].isEmpty(); + } + + public String getMainState() { + return this.stateParts[0]; + } + + public String getSubState() { + return this.stateParts[1]; + } + + @Nonnull + public static StateStringValidator get() { + return new StateStringValidator(true, false, false); + } + + @Nonnull + public static StateStringValidator mainStateOnly() { + return new StateStringValidator(false, true, false); + } + + @Nonnull + public static StateStringValidator requireMainState() { + return new StateStringValidator(false, false, false); + } + + @Nonnull + public static StateStringValidator requireMainStateOrNull() { + return new StateStringValidator(false, false, true); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringArrayNoEmptyStringsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringArrayNoEmptyStringsValidator.java new file mode 100644 index 0000000..ce17c34 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringArrayNoEmptyStringsValidator.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StringArrayNoEmptyStringsValidator extends StringArrayValidator { + private static final StringArrayNoEmptyStringsValidator INSTANCE = new StringArrayNoEmptyStringsValidator(); + + private StringArrayNoEmptyStringsValidator() { + } + + @Override + public boolean test(@Nullable String[] list) { + if (list == null) { + return true; + } else { + for (String s : list) { + if (s == null || s.isEmpty()) { + return false; + } + } + + return true; + } + } + + @Nonnull + @Override + public String errorMessage(@Nullable String name, String[] list) { + if (name == null) { + name = "StringList"; + } else { + name = "'" + name + "'"; + } + + return name + " must not contain empty strings"; + } + + @Nonnull + @Override + public String errorMessage(String[] list) { + return this.errorMessage(null, list); + } + + public static StringArrayNoEmptyStringsValidator get() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringArrayNotEmptyValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringArrayNotEmptyValidator.java new file mode 100644 index 0000000..6748a9c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringArrayNotEmptyValidator.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StringArrayNotEmptyValidator extends StringArrayValidator { + private static final StringArrayNotEmptyValidator INSTANCE = new StringArrayNotEmptyValidator(); + + private StringArrayNotEmptyValidator() { + } + + @Override + public boolean test(@Nullable String[] list) { + if (list != null && list.length != 0) { + for (String s : list) { + if (s == null || s.isEmpty()) { + return false; + } + } + + return true; + } else { + return false; + } + } + + @Nonnull + @Override + public String errorMessage(@Nullable String name, String[] list) { + if (name == null) { + name = "StringList"; + } else { + name = "'" + name + "'"; + } + + return name + " must not be empty or contain empty strings"; + } + + @Nonnull + @Override + public String errorMessage(String[] list) { + return this.errorMessage(null, list); + } + + public static StringArrayNotEmptyValidator get() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringArrayValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringArrayValidator.java new file mode 100644 index 0000000..a88780a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringArrayValidator.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +public abstract class StringArrayValidator extends Validator { + public StringArrayValidator() { + } + + public abstract boolean test(String[] var1); + + public abstract String errorMessage(String var1, String[] var2); + + public abstract String errorMessage(String[] var1); +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringNotEmptyValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringNotEmptyValidator.java new file mode 100644 index 0000000..425dda4 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringNotEmptyValidator.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StringNotEmptyValidator extends StringValidator { + private static final StringNotEmptyValidator INSTANCE = new StringNotEmptyValidator(); + + private StringNotEmptyValidator() { + } + + @Override + public boolean test(@Nullable String value) { + return value != null && !value.isEmpty(); + } + + @Nonnull + @Override + public String errorMessage(String value) { + return this.errorMessage0(value, "Value"); + } + + @Nonnull + @Override + public String errorMessage(String value, String name) { + return this.errorMessage0(value, "\"" + name + "\""); + } + + @Nonnull + private String errorMessage0(String value, String name) { + return name + " must not be an empty string"; + } + + public static StringNotEmptyValidator get() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringNullOrNotEmptyValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringNullOrNotEmptyValidator.java new file mode 100644 index 0000000..4d56dbe --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringNullOrNotEmptyValidator.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StringNullOrNotEmptyValidator extends StringValidator { + private static final StringNullOrNotEmptyValidator INSTANCE = new StringNullOrNotEmptyValidator(); + + private StringNullOrNotEmptyValidator() { + } + + @Override + public boolean test(@Nullable String value) { + return value == null || !value.isEmpty(); + } + + @Nonnull + @Override + public String errorMessage(String value) { + return this.errorMessage0(value, "Value"); + } + + @Nonnull + @Override + public String errorMessage(String value, String name) { + return this.errorMessage0(value, "\"" + name + "\""); + } + + @Nonnull + private String errorMessage0(String value, String name) { + return name + " must be null or not be an empty string and is '" + value + "'"; + } + + public static StringNullOrNotEmptyValidator get() { + return INSTANCE; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringValidator.java new file mode 100644 index 0000000..787d4a8 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringValidator.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +public abstract class StringValidator extends Validator { + public StringValidator() { + } + + public abstract boolean test(String var1); + + public abstract String errorMessage(String var1); + + public abstract String errorMessage(String var1, String var2); +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringsAtMostOneValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringsAtMostOneValidator.java new file mode 100644 index 0000000..428e89d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringsAtMostOneValidator.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StringsAtMostOneValidator extends Validator { + private final String[] attributes; + + private StringsAtMostOneValidator(String[] attributes) { + this.attributes = attributes; + } + + public static boolean test(@Nullable String string1, @Nullable String string2) { + return string1 == null || string1.isEmpty() || string2 == null || string2.isEmpty(); + } + + @Nonnull + public static String errorMessage(String string1, String string2, String context) { + return errorMessage(string1, "Value1", string2, "Value2", context); + } + + @Nonnull + public static String errorMessage(String string1, String attribute1, String string2, String attribute2, String context) { + return String.format("Both %s and %s are set to values. At most only 1 of the variables should be set in %s.", attribute1, attribute2, context); + } + + @Nonnull + public static StringsAtMostOneValidator withAttributes(String attribute1, String attribute2) { + return new StringsAtMostOneValidator(new String[]{attribute1, attribute2}); + } + + @Nonnull + public static StringsAtMostOneValidator withAttributes(String[] attributes) { + return new StringsAtMostOneValidator(attributes); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringsNotEmptyValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringsNotEmptyValidator.java new file mode 100644 index 0000000..c8d621e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringsNotEmptyValidator.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StringsNotEmptyValidator extends Validator { + private final String[] attributes; + + private StringsNotEmptyValidator(String[] attributes) { + this.attributes = attributes; + } + + public static boolean test(@Nullable String string1, @Nullable String string2) { + return string1 != null && !string1.isEmpty() || string2 != null && !string2.isEmpty(); + } + + @Nonnull + public static String errorMessage(String string1, String string2, String context) { + return errorMessage(string1, "Value1", string2, "Value2", context); + } + + @Nonnull + public static String errorMessage(String string1, String attribute1, String string2, String attribute2, String context) { + return String.format("Either '%s' or '%s' must be non empty in %s", attribute1, attribute2, context); + } + + @Nonnull + public static StringsNotEmptyValidator withAttributes(String attribute1, String attribute2) { + return new StringsNotEmptyValidator(new String[]{attribute1, attribute2}); + } + + @Nonnull + public static StringsNotEmptyValidator withAttributes(String[] attributes) { + return new StringsNotEmptyValidator(attributes); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringsOneSetValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringsOneSetValidator.java new file mode 100644 index 0000000..73a00fb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/StringsOneSetValidator.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class StringsOneSetValidator extends Validator { + private final String[] attributes; + + private StringsOneSetValidator(String[] attributes) { + this.attributes = attributes; + } + + public static boolean test(@Nullable String string1, @Nullable String string2) { + boolean str1IsEmpty = string1 == null || string1.isEmpty(); + boolean str2IsEmpty = string2 == null || string2.isEmpty(); + return str1IsEmpty != str2IsEmpty; + } + + @Nonnull + public static String errorMessage(String string1, String string2, String context) { + return errorMessage(string1, "Value1", string2, "Value2", context); + } + + @Nonnull + public static String errorMessage(String string1, String attribute1, String string2, String attribute2, String context) { + return formatErrorMessage(string1, attribute1, string2, attribute2, context); + } + + @Nonnull + public static String formatErrorMessage(String string1, String attribute1, String string2, String attribute2, String context) { + return String.format("Only %s or %s must be set to some value in %s.", attribute1, attribute2, context); + } + + @Nonnull + public static StringsOneSetValidator withAttributes(String attribute1, String attribute2) { + return new StringsOneSetValidator(new String[]{attribute1, attribute2}); + } + + @Nonnull + public static StringsOneSetValidator withAttributes(String[] attributes) { + return new StringsOneSetValidator(attributes); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/SubTypeTypeAdapterFactory.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/SubTypeTypeAdapterFactory.java new file mode 100644 index 0000000..e378ba0 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/SubTypeTypeAdapterFactory.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.internal.Streams; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SubTypeTypeAdapterFactory implements TypeAdapterFactory { + private final Class baseClassType; + private final String typeFieldName; + private final Map, String> classToName = new HashMap<>(); + + private SubTypeTypeAdapterFactory(Class baseClassType, String typeFieldName) { + this.baseClassType = baseClassType; + this.typeFieldName = typeFieldName; + } + + @Nonnull + public static SubTypeTypeAdapterFactory of(Class baseClass, String typeFieldName) { + return new SubTypeTypeAdapterFactory(baseClass, typeFieldName); + } + + @Nonnull + public SubTypeTypeAdapterFactory registerSubType(Class clazz, String name) { + if (this.classToName.containsKey(clazz)) { + throw new IllegalArgumentException(); + } else if (this.classToName.containsValue(name)) { + throw new IllegalArgumentException(); + } else { + this.classToName.put(clazz, name); + return this; + } + } + + @Nullable + @Override + public TypeAdapter create(@Nonnull Gson gson, @Nonnull TypeToken type) { + if (type.getRawType() != this.baseClassType) { + return null; + } else { + final Map, Entry>> delegateMap = new HashMap<>(); + this.classToName + .forEach((aClass, name) -> delegateMap.put((Class)aClass, Map.entry(name, gson.getDelegateAdapter(this, TypeToken.get((Class)aClass))))); + return (new TypeAdapter() { + @Override + public void write(JsonWriter out, @Nonnull T value) throws IOException { + Entry> entry = delegateMap.get(value.getClass()); + if (entry == null) { + throw new IllegalArgumentException(); + } else { + JsonObject result = new JsonObject(); + JsonObject obj = ((TypeAdapter)entry.getValue()).toJsonTree(value).getAsJsonObject(); + result.addProperty(SubTypeTypeAdapterFactory.this.typeFieldName, entry.getKey()); + obj.entrySet().forEach(stringJsonElementEntry -> result.add(stringJsonElementEntry.getKey(), stringJsonElementEntry.getValue())); + Streams.write(result, out); + } + } + + @Override + public T read(JsonReader in) { + throw new RuntimeException("Unsupported"); + } + }).nullSafe(); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/TagSetExistsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/TagSetExistsValidator.java new file mode 100644 index 0000000..7baaad5 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/TagSetExistsValidator.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import com.hypixel.hytale.builtin.tagset.config.NPCGroup; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class TagSetExistsValidator extends AssetValidator { + private static final TagSetExistsValidator DEFAULT_INSTANCE = new TagSetExistsValidator(); + + private TagSetExistsValidator() { + } + + private TagSetExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "TagSet"; + } + + @Override + public boolean test(String value) { + return NPCGroup.getAssetMap().getIndex(value) != Integer.MIN_VALUE; + } + + @Nonnull + @Override + public String errorMessage(String value, String attribute) { + return "The NPC group tag set with the name \"" + value + "\" does not exist in attribute \"" + attribute + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return NPCGroup.class.getSimpleName(); + } + + public static TagSetExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static TagSetExistsValidator withConfig(EnumSet config) { + return new TagSetExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/TemporalArrayValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/TemporalArrayValidator.java new file mode 100644 index 0000000..5cd3802 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/TemporalArrayValidator.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import java.time.temporal.TemporalAmount; + +public abstract class TemporalArrayValidator extends Validator { + public TemporalArrayValidator() { + } + + public abstract boolean test(TemporalAmount[] var1); + + public abstract String errorMessage(String var1, TemporalAmount[] var2); +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/TemporalSequenceValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/TemporalSequenceValidator.java new file mode 100644 index 0000000..d0a1378 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/TemporalSequenceValidator.java @@ -0,0 +1,92 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.Period; +import java.time.temporal.TemporalAmount; +import java.util.Arrays; +import javax.annotation.Nonnull; + +public class TemporalSequenceValidator extends TemporalArrayValidator { + private final RelationalOperator relationLower; + private final TemporalAmount lower; + private final RelationalOperator relationUpper; + private final TemporalAmount upper; + private final RelationalOperator relationSequence; + + private TemporalSequenceValidator( + RelationalOperator relationLower, TemporalAmount lower, RelationalOperator relationUpper, TemporalAmount upper, RelationalOperator relationSequence + ) { + this.lower = lower; + this.upper = upper; + this.relationLower = relationLower; + this.relationUpper = relationUpper; + this.relationSequence = relationSequence; + } + + @Nonnull + public static TemporalSequenceValidator betweenMonotonic(TemporalAmount lower, TemporalAmount upper) { + return new TemporalSequenceValidator(RelationalOperator.GreaterEqual, lower, RelationalOperator.LessEqual, upper, RelationalOperator.Less); + } + + @Nonnull + public static TemporalSequenceValidator betweenWeaklyMonotonic(TemporalAmount lower, TemporalAmount upper) { + return new TemporalSequenceValidator(RelationalOperator.GreaterEqual, lower, RelationalOperator.LessEqual, upper, RelationalOperator.LessEqual); + } + + public static boolean compare(@Nonnull LocalDateTime value, @Nonnull RelationalOperator op, LocalDateTime c) { + return switch (op) { + case NotEqual -> !value.equals(c); + case Less -> value.isBefore(c); + case LessEqual -> !value.isAfter(c); + case Greater -> value.isAfter(c); + case GreaterEqual -> !value.isBefore(c); + case Equal -> value.equals(c); + }; + } + + @Override + public boolean test(@Nonnull TemporalAmount[] values) { + LocalDateTime zeroDate = LocalDateTime.ofInstant(WorldTimeResource.ZERO_YEAR, WorldTimeResource.ZONE_OFFSET); + LocalDateTime min = zeroDate.plus(this.lower); + LocalDateTime max = zeroDate.plus(this.upper); + boolean expectPeriod = values[0] instanceof Period; + + for (int i = 0; i < values.length; i++) { + TemporalAmount value = values[i]; + if (value instanceof Period && !expectPeriod) { + return false; + } + + if (value instanceof Duration && expectPeriod) { + return false; + } + + LocalDateTime dateValue = zeroDate.plus(values[i]); + if (!compare(dateValue, this.relationLower, min) && compare(dateValue, this.relationUpper, max)) { + return false; + } + + if (i > 0 && this.relationSequence != null) { + LocalDateTime previousValue = zeroDate.plus(values[i - 1]); + if (!compare(previousValue, this.relationSequence, dateValue)) { + return false; + } + } + } + + return true; + } + + @Nonnull + @Override + public String errorMessage(String name, TemporalAmount[] value) { + return name + + (this.relationLower == null ? "" : " values should be " + this.relationLower.asText() + " " + this.lower + " and") + + (this.relationUpper == null ? "" : " values should be " + this.relationUpper.asText() + " " + this.upper + " and") + + (this.relationSequence == null ? "" : " succeeding values should be " + this.relationSequence.asText() + " preceding values and") + + " values must all either be periods or durations but is " + + Arrays.toString((Object[])value); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/ValidateAssetIfEnumIsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/ValidateAssetIfEnumIsValidator.java new file mode 100644 index 0000000..fe1031c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/ValidateAssetIfEnumIsValidator.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class ValidateAssetIfEnumIsValidator & Supplier> extends Validator { + private final String parameter1; + private final transient AssetValidator validator; + private final String parameter2; + private final E enumValue; + + private ValidateAssetIfEnumIsValidator(String p1, AssetValidator validator, String p2, E value) { + this.parameter1 = p1; + this.validator = validator; + this.parameter2 = p2; + this.enumValue = value; + } + + @Nonnull + public static & Supplier> ValidateAssetIfEnumIsValidator withAttributes( + String p1, AssetValidator validator, String p2, E value + ) { + return new ValidateAssetIfEnumIsValidator<>(p1, validator, p2, value); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/ValidateIfEnumIsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/ValidateIfEnumIsValidator.java new file mode 100644 index 0000000..67bab49 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/ValidateIfEnumIsValidator.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class ValidateIfEnumIsValidator & Supplier> extends Validator { + private final String parameter1; + private final Validator validator; + private final String parameter2; + private final E enumValue; + + private ValidateIfEnumIsValidator(String p1, Validator validator, String p2, E value) { + this.parameter1 = p1; + this.validator = validator; + this.parameter2 = p2; + this.enumValue = value; + } + + @Nonnull + public static & Supplier> ValidateIfEnumIsValidator withAttributes(String p1, Validator validator, String p2, E value) { + return new ValidateIfEnumIsValidator<>(p1, validator, p2, value); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/Validator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/Validator.java new file mode 100644 index 0000000..f6ea86a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/Validator.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +public abstract class Validator { + public Validator() { + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/ValidatorTypeRegistry.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/ValidatorTypeRegistry.java new file mode 100644 index 0000000..c257849 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/ValidatorTypeRegistry.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators; + +import com.google.gson.GsonBuilder; +import javax.annotation.Nonnull; + +public class ValidatorTypeRegistry { + public ValidatorTypeRegistry() { + } + + @Nonnull + public static GsonBuilder registerTypes(@Nonnull GsonBuilder gsonBuilder) { + SubTypeTypeAdapterFactory factory = SubTypeTypeAdapterFactory.of(Validator.class, "Type"); + factory.registerSubType(StringNotEmptyValidator.class, "StringNotEmpty"); + factory.registerSubType(StringNullOrNotEmptyValidator.class, "StringNullOrNotEmpty"); + factory.registerSubType(StringsAtMostOneValidator.class, "StringsAtMostOne"); + factory.registerSubType(StringsOneSetValidator.class, "StringsOneSet"); + factory.registerSubType(StringsNotEmptyValidator.class, "NotAllStringsEmpty"); + factory.registerSubType(IntSingleValidator.class, "Int"); + factory.registerSubType(IntOrValidator.class, "IntOr"); + factory.registerSubType(IntRangeValidator.class, "IntRange"); + factory.registerSubType(DoubleSingleValidator.class, "Double"); + factory.registerSubType(DoubleOrValidator.class, "DoubleOr"); + factory.registerSubType(DoubleRangeValidator.class, "DoubleRange"); + factory.registerSubType(AttributeRelationValidator.class, "NumericRelation"); + factory.registerSubType(ArrayNotEmptyValidator.class, "ArrayNotEmpty"); + factory.registerSubType(AnyPresentValidator.class, "AnyPresent"); + factory.registerSubType(OnePresentValidator.class, "OnePresent"); + factory.registerSubType(OneOrNonePresentValidator.class, "OneOrNonePresent"); + factory.registerSubType(AnyBooleanValidator.class, "AnyTrue"); + factory.registerSubType(StringArrayNotEmptyValidator.class, "StringListNotEmpty"); + factory.registerSubType(StringArrayNoEmptyStringsValidator.class, "StringListNoEmptyStrings"); + factory.registerSubType(DoubleSequenceValidator.class, "DoubleSequenceValidator"); + factory.registerSubType(IntSequenceValidator.class, "IntSequenceValidator"); + factory.registerSubType(ExistsIfParameterSetValidator.class, "ExistsIfParameterSet"); + factory.registerSubType(TemporalSequenceValidator.class, "TemporalSequenceValidator"); + factory.registerSubType(RequiresFeatureIfValidator.class, "RequiresFeatureIf"); + factory.registerSubType(RequiresOneOfFeaturesValidator.class, "RequiresOneOfFeatures"); + factory.registerSubType(StateStringValidator.class, "StateString"); + factory.registerSubType(ValidateIfEnumIsValidator.class, "ValidateIfEnumIs"); + factory.registerSubType(ValidateAssetIfEnumIsValidator.class, "ValidateAssetIfEnumIs"); + factory.registerSubType(ComponentOnlyValidator.class, "ComponentOnly"); + factory.registerSubType(RequiresFeatureIfEnumValidator.class, "RequiresFeatureIfEnum"); + factory.registerSubType(EnumArrayNoDuplicatesValidator.class, "EnumArrayNoDuplicates"); + factory.registerSubType(ArraysOneSetValidator.class, "ArraysOneSet"); + factory.registerSubType(BooleanImplicationValidator.class, "BooleanImplication"); + factory.registerSubType(InstructionContextValidator.class, "InstructionContext"); + factory.registerSubType(AtMostOneBooleanValidator.class, "AtMostOneBoolean"); + gsonBuilder.registerTypeAdapterFactory(factory); + return gsonBuilder; + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/AttitudeGroupExistsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/AttitudeGroupExistsValidator.java new file mode 100644 index 0000000..4e712e4 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/AttitudeGroupExistsValidator.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.config.AttitudeGroup; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class AttitudeGroupExistsValidator extends AssetValidator { + private static final AttitudeGroupExistsValidator DEFAULT_INSTANCE = new AttitudeGroupExistsValidator(); + + private AttitudeGroupExistsValidator() { + } + + private AttitudeGroupExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "AttitudeGroup"; + } + + @Override + public boolean test(String attitudeGroup) { + return AttitudeGroup.getAssetMap().getAsset(attitudeGroup) != null; + } + + @Nonnull + @Override + public String errorMessage(String attitudeGroup, String attributeName) { + return "The attitude group with the name \"" + attitudeGroup + "\" does not exist for attribute \"" + attributeName + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return AttitudeGroup.class.getSimpleName(); + } + + public static AttitudeGroupExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static AttitudeGroupExistsValidator withConfig(EnumSet config) { + return new AttitudeGroupExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/BeaconSpawnExistsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/BeaconSpawnExistsValidator.java new file mode 100644 index 0000000..8767b81 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/BeaconSpawnExistsValidator.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.spawning.assets.spawns.config.BeaconNPCSpawn; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class BeaconSpawnExistsValidator extends AssetValidator { + private static final BeaconSpawnExistsValidator DEFAULT_INSTANCE = new BeaconSpawnExistsValidator(); + + private BeaconSpawnExistsValidator() { + } + + private BeaconSpawnExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "BeaconNPCSpawn"; + } + + @Override + public boolean test(String beacon) { + return BeaconNPCSpawn.getAssetMap().getAsset(beacon) != null; + } + + @Nonnull + @Override + public String errorMessage(String beacon, String attributeName) { + return "The beacon spawn with the name \"" + beacon + "\" does not exist for attribute \"" + attributeName + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return BeaconNPCSpawn.class.getSimpleName(); + } + + public static BeaconSpawnExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static BeaconSpawnExistsValidator withConfig(EnumSet config) { + return new BeaconSpawnExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/BlockSetExistsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/BlockSetExistsValidator.java new file mode 100644 index 0000000..4e72d4a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/BlockSetExistsValidator.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class BlockSetExistsValidator extends AssetValidator { + private static final BlockSetExistsValidator DEFAULT_INSTANCE = new BlockSetExistsValidator(); + + private BlockSetExistsValidator() { + } + + private BlockSetExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "BlockSet"; + } + + @Override + public boolean test(String blockSet) { + return BlockSet.getAssetMap().getAsset(blockSet) != null; + } + + @Nonnull + @Override + public String errorMessage(String blockSet, String attribute) { + return "The block set with the name \"" + blockSet + "\" does not exist in attribute \"" + attribute + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return BlockSet.class.getSimpleName(); + } + + public static BlockSetExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static BlockSetExistsValidator withConfig(EnumSet config) { + return new BlockSetExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/CombatInteractionValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/CombatInteractionValidator.java new file mode 100644 index 0000000..6ab0ff0 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/CombatInteractionValidator.java @@ -0,0 +1,133 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.BreakBlockInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.ChangeBlockInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.MovementConditionInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.PlaceBlockInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.ListCollector; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.role.support.CombatSupport; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; + +public class CombatInteractionValidator extends AssetValidator { + private static final Set> DISALLOWED_INTERACTION_TYPES = Set.of( + BreakBlockInteraction.class, PlaceBlockInteraction.class, ChangeBlockInteraction.class, MovementConditionInteraction.class + ); + private final List disallowedInteractions = new ObjectArrayList<>(); + private boolean assetExists; + private boolean attackTag; + private boolean onlyOneAttackType; + private boolean onlyOneAimingReference; + + private CombatInteractionValidator() { + } + + private CombatInteractionValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "Interaction"; + } + + @Override + public boolean test(String value) { + RootInteraction interaction = RootInteraction.getAssetMap().getAsset(value); + this.assetExists = interaction != null; + if (!this.assetExists) { + return false; + } else { + this.attackTag = testAttackTag(interaction); + this.onlyOneAttackType = testOnlyOneAttackType(interaction); + Set aimingReferenceInteractions = new HashSet<>(); + this.disallowedInteractions.clear(); + Set aimingReferenceTags = Interaction.getAssetMap().getKeysForTag(CombatSupport.AIMING_REFERENCE_TAG_INDEX); + ListCollector collector = new ListCollector<>((collectorTag, interactionContext, iteratedInteraction) -> { + if (aimingReferenceTags.contains(iteratedInteraction.getId())) { + aimingReferenceInteractions.add(iteratedInteraction.getId()); + } + + if (DISALLOWED_INTERACTION_TYPES.contains(iteratedInteraction.getClass())) { + this.disallowedInteractions.add(iteratedInteraction.getClass().getSimpleName()); + } + + return null; + }); + InteractionManager.walkChain(collector, InteractionType.Primary, InteractionContext.withoutEntity(), interaction); + this.onlyOneAimingReference = aimingReferenceInteractions.size() <= 1; + return this.attackTag && this.onlyOneAttackType && this.onlyOneAimingReference && this.disallowedInteractions.isEmpty(); + } + } + + @Nonnull + @Override + public String errorMessage(String value, String attribute) { + if (!this.assetExists) { + return "Interaction \"" + value + "\" does not exist for attribute \"" + attribute + "\""; + } else { + StringBuilder sb = new StringBuilder("Attribute \"").append(attribute).append("\" uses interaction with name \"").append(value).append("\" which:"); + if (!this.attackTag) { + sb.append("\n - Is not marked with the \"").append("Attack").append("\" tag"); + } + + if (!this.onlyOneAttackType) { + sb.append("\n - Has too many attack types (only one may be defined)"); + } + + if (!this.onlyOneAimingReference) { + sb.append("\n - Has too many ").append("AimingReference").append(" tags"); + } + + if (!this.disallowedInteractions.isEmpty()) { + sb.append("\n - Contains the following disallowed interaction types: ").append(String.join(", ", this.disallowedInteractions)); + } + + return sb.toString(); + } + } + + @Nonnull + @Override + public String getAssetName() { + return RootInteraction.class.getSimpleName(); + } + + public static boolean testAttackTag(@Nonnull RootInteraction interaction) { + return RootInteraction.getAssetMap().getKeysForTag(CombatSupport.ATTACK_TAG_INDEX).contains(interaction.getId()); + } + + public static boolean testOnlyOneAttackType(@Nonnull RootInteraction interaction) { + IndexedLookupTableAssetMap assetMap = RootInteraction.getAssetMap(); + boolean meleeTag = assetMap.getKeysForTag(CombatSupport.MELEE_TAG_INDEX).contains(interaction.getId()); + boolean rangedTag = assetMap.getKeysForTag(CombatSupport.RANGED_TAG_INDEX).contains(interaction.getId()); + boolean blockTag = assetMap.getKeysForTag(CombatSupport.BLOCK_TAG_INDEX).contains(interaction.getId()); + if (!meleeTag) { + return rangedTag ? !blockTag : true; + } else { + return !rangedTag && !blockTag; + } + } + + @Nonnull + public static CombatInteractionValidator required() { + return new CombatInteractionValidator(); + } + + @Nonnull + public static CombatInteractionValidator withConfig(EnumSet config) { + return new CombatInteractionValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/EntityEffectExistsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/EntityEffectExistsValidator.java new file mode 100644 index 0000000..8b0b5b8 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/EntityEffectExistsValidator.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class EntityEffectExistsValidator extends AssetValidator { + private static final EntityEffectExistsValidator DEFAULT_INSTANCE = new EntityEffectExistsValidator(); + + private EntityEffectExistsValidator() { + } + + private EntityEffectExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "EntityEffect"; + } + + @Override + public boolean test(String effect) { + return EntityEffect.getAssetMap().getAsset(effect) != null; + } + + @Nonnull + @Override + public String errorMessage(String effect, String attributeName) { + return "The entity effect with the name \"" + effect + "\" does not exist for attribute \"" + attributeName + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return EntityEffect.class.getSimpleName(); + } + + public static EntityEffectExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static EntityEffectExistsValidator withConfig(EnumSet config) { + return new EntityEffectExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/EntityStatExistsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/EntityStatExistsValidator.java new file mode 100644 index 0000000..9836e03 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/EntityStatExistsValidator.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class EntityStatExistsValidator extends AssetValidator { + private static final EntityStatExistsValidator DEFAULT_INSTANCE = new EntityStatExistsValidator(); + + private EntityStatExistsValidator() { + } + + private EntityStatExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "EntityStat"; + } + + @Override + public boolean test(String entityStat) { + return EntityStatType.getAssetMap().getAsset(entityStat) != null; + } + + @Nonnull + @Override + public String errorMessage(String entityStat, String attributeName) { + return "The entity stat with the name \"" + entityStat + "\" does not exist for attribute \"" + attributeName + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return EntityStatType.class.getSimpleName(); + } + + public static EntityStatExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static EntityStatExistsValidator withConfig(EnumSet config) { + return new EntityStatExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/EnvironmentExistsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/EnvironmentExistsValidator.java new file mode 100644 index 0000000..4d112ee --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/EnvironmentExistsValidator.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class EnvironmentExistsValidator extends AssetValidator { + public static final EnvironmentExistsValidator DEFAULT_INSTANCE = new EnvironmentExistsValidator(); + + private EnvironmentExistsValidator() { + } + + private EnvironmentExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "Environment"; + } + + @Override + public boolean test(String envName) { + return Environment.getAssetMap().getAsset(envName) != null; + } + + @Nonnull + @Override + public String errorMessage(String envName, String attribute) { + return "The environment with the file name \"" + envName + "\" does not exist in attribute \"" + attribute + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return Environment.class.getSimpleName(); + } + + public static EnvironmentExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static EnvironmentExistsValidator withConfig(EnumSet config) { + return new EnvironmentExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/FlockAssetExistsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/FlockAssetExistsValidator.java new file mode 100644 index 0000000..358e6f7 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/FlockAssetExistsValidator.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.server.flock.config.FlockAsset; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class FlockAssetExistsValidator extends AssetValidator { + private static final FlockAssetExistsValidator DEFAULT_INSTANCE = new FlockAssetExistsValidator(); + + private FlockAssetExistsValidator() { + } + + private FlockAssetExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "FlockAsset"; + } + + @Override + public boolean test(String flockAsset) { + return FlockAsset.getAssetMap().getAsset(flockAsset) != null; + } + + @Nonnull + @Override + public String errorMessage(String flockAsset, String attribute) { + return "The flock asset with the name \"" + flockAsset + "\" does not exist in attribute \"" + attribute + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return FlockAsset.class.getSimpleName(); + } + + public static FlockAssetExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static FlockAssetExistsValidator withConfig(EnumSet config) { + return new FlockAssetExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ItemAttitudeGroupExistsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ItemAttitudeGroupExistsValidator.java new file mode 100644 index 0000000..e611258 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ItemAttitudeGroupExistsValidator.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.config.ItemAttitudeGroup; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class ItemAttitudeGroupExistsValidator extends AssetValidator { + private static final ItemAttitudeGroupExistsValidator DEFAULT_INSTANCE = new ItemAttitudeGroupExistsValidator(); + + private ItemAttitudeGroupExistsValidator() { + } + + private ItemAttitudeGroupExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "ItemAttitudeGroup"; + } + + @Override + public boolean test(String attitudeGroup) { + return ItemAttitudeGroup.getAssetMap().getAsset(attitudeGroup) != null; + } + + @Nonnull + @Override + public String errorMessage(String attitudeGroup, String attributeName) { + return "The item attitude group with the name \"" + attitudeGroup + "\" does not exist for attribute \"" + attributeName + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return ItemAttitudeGroup.class.getSimpleName(); + } + + public static ItemAttitudeGroupExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static ItemAttitudeGroupExistsValidator withConfig(EnumSet config) { + return new ItemAttitudeGroupExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ItemDropListExistsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ItemDropListExistsValidator.java new file mode 100644 index 0000000..c8f97f2 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ItemDropListExistsValidator.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.util.InventoryHelper; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class ItemDropListExistsValidator extends AssetValidator { + private static final ItemDropListExistsValidator DEFAULT_INSTANCE = new ItemDropListExistsValidator(); + + private ItemDropListExistsValidator() { + } + + private ItemDropListExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "ItemDropList"; + } + + @Override + public boolean test(String value) { + return InventoryHelper.itemDropListKeyExists(value); + } + + @Nonnull + @Override + public String errorMessage(String value, String attribute) { + return "The item drop list with the name \"" + value + "\" does not exist for attribute \"" + attribute + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return ItemDropList.class.getSimpleName(); + } + + public static ItemDropListExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static ItemDropListExistsValidator withConfig(EnumSet config) { + return new ItemDropListExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ItemExistsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ItemExistsValidator.java new file mode 100644 index 0000000..dd2dbaf --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ItemExistsValidator.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.util.InventoryHelper; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class ItemExistsValidator extends AssetValidator { + private static final ItemExistsValidator DEFAULT_INSTANCE = new ItemExistsValidator(); + public static final String DROPLIST_PREFIX = "Droplist:"; + private boolean requireBlock; + private boolean allowDroplist; + + private ItemExistsValidator() { + } + + private ItemExistsValidator(boolean requireBlock, boolean allowDroplist) { + this.requireBlock = requireBlock; + this.allowDroplist = allowDroplist; + } + + private ItemExistsValidator(EnumSet config, boolean requireBlock, boolean allowDroplist) { + super(config); + this.requireBlock = requireBlock; + this.allowDroplist = allowDroplist; + } + + @Nonnull + @Override + public String getDomain() { + return "Item"; + } + + @Override + public boolean test(String item) { + if (item == null || item.isEmpty()) { + return false; + } else if (item.startsWith("Droplist:")) { + return ItemDropList.getAssetMap().getAsset(item.substring("Droplist:".length())) != null; + } else { + return this.requireBlock ? InventoryHelper.itemKeyIsBlockType(item) : InventoryHelper.itemKeyExists(item); + } + } + + @Nonnull + @Override + public String errorMessage(String item, String attributeName) { + return "The item " + + (this.allowDroplist ? "or droplist " : "") + + "with the name \"" + + item + + "\" does not exist" + + (this.requireBlock ? " or is not a block" : "") + + " for attribute \"" + + attributeName + + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return Item.class.getSimpleName(); + } + + public static ItemExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static ItemExistsValidator requireBlock() { + return new ItemExistsValidator(true, false); + } + + @Nonnull + public static ItemExistsValidator orDroplist() { + return new ItemExistsValidator(false, true); + } + + @Nonnull + public static ItemExistsValidator withConfig(EnumSet config) { + return new ItemExistsValidator(config, false, false); + } + + @Nonnull + public static ItemExistsValidator orDroplistWithConfig(EnumSet config) { + return new ItemExistsValidator(config, false, true); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ManualSpawnMarkerExistsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ManualSpawnMarkerExistsValidator.java new file mode 100644 index 0000000..1daafb9 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ManualSpawnMarkerExistsValidator.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.spawning.assets.spawnmarker.config.SpawnMarker; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class ManualSpawnMarkerExistsValidator extends AssetValidator { + private static final ManualSpawnMarkerExistsValidator DEFAULT_INSTANCE = new ManualSpawnMarkerExistsValidator(); + + private ManualSpawnMarkerExistsValidator() { + } + + private ManualSpawnMarkerExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "SpawnMarker"; + } + + @Override + public boolean test(String marker) { + SpawnMarker spawner = SpawnMarker.getAssetMap().getAsset(marker); + return spawner != null && spawner.isManualTrigger(); + } + + @Nonnull + @Override + public String errorMessage(String marker, String attributeName) { + return "The spawn marker with the name \"" + marker + "\" does not exist or is not a manual spawn marker for attribute \"" + attributeName + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return SpawnMarker.class.getSimpleName(); + } + + public static ManualSpawnMarkerExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static ManualSpawnMarkerExistsValidator withConfig(EnumSet config) { + return new ManualSpawnMarkerExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ModelExistsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ModelExistsValidator.java new file mode 100644 index 0000000..a88e610 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ModelExistsValidator.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class ModelExistsValidator extends AssetValidator { + private static final ModelExistsValidator DEFAULT_INSTANCE = new ModelExistsValidator(); + + private ModelExistsValidator() { + } + + private ModelExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "Model"; + } + + @Override + public boolean test(String model) { + return ModelAsset.getAssetMap().getAsset(model) != null; + } + + @Nonnull + @Override + public String errorMessage(String model, String attributeName) { + return "The model with the name \"" + model + "\" does not exist for attribute \"" + attributeName + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return ModelAsset.class.getSimpleName(); + } + + public static ModelExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static ModelExistsValidator withConfig(EnumSet config) { + return new ModelExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ParticleSystemExistsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ParticleSystemExistsValidator.java new file mode 100644 index 0000000..cbab2eb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/ParticleSystemExistsValidator.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class ParticleSystemExistsValidator extends AssetValidator { + private static final ParticleSystemExistsValidator DEFAULT_INSTANCE = new ParticleSystemExistsValidator(); + + private ParticleSystemExistsValidator() { + } + + private ParticleSystemExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "ParticleSystem"; + } + + @Override + public boolean test(String particleSystem) { + return ParticleSystem.getAssetMap().getAsset(particleSystem) != null; + } + + @Nonnull + @Override + public String errorMessage(String particleSystem, String attributeName) { + return "The particle system with the name \"" + particleSystem + "\" does not exist for attribute \"" + attributeName + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return ParticleSystem.class.getSimpleName(); + } + + public static ParticleSystemExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static ParticleSystemExistsValidator withConfig(EnumSet config) { + return new ParticleSystemExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/RoleExistsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/RoleExistsValidator.java new file mode 100644 index 0000000..04545be --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/RoleExistsValidator.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class RoleExistsValidator extends AssetValidator { + private static final RoleExistsValidator DEFAULT_INSTANCE = new RoleExistsValidator(); + + private RoleExistsValidator() { + } + + private RoleExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "Role"; + } + + @Override + public boolean test(String role) { + return NPCPlugin.get().hasRoleName(role); + } + + @Nonnull + @Override + public String errorMessage(String role, String attributeName) { + return "The Role with the name \"" + role + "\" does not exist for attribute \"" + attributeName + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return "NPCRole"; + } + + public static RoleExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static RoleExistsValidator withConfig(EnumSet config) { + return new RoleExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/RootInteractionValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/RootInteractionValidator.java new file mode 100644 index 0000000..a4c931e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/RootInteractionValidator.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class RootInteractionValidator extends AssetValidator { + private RootInteractionValidator() { + } + + private RootInteractionValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "Interaction"; + } + + @Override + public boolean test(String value) { + return RootInteraction.getAssetMap().getAsset(value) != null; + } + + @Nonnull + @Override + public String errorMessage(String value, String attribute) { + return "Interaction \"" + value + "\" does not exist for attribute \"" + attribute + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return RootInteraction.class.getSimpleName(); + } + + @Nonnull + public static RootInteractionValidator required() { + return new RootInteractionValidator(); + } + + @Nonnull + public static RootInteractionValidator withConfig(EnumSet config) { + return new RootInteractionValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/SoundEventExistsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/SoundEventExistsValidator.java new file mode 100644 index 0000000..73e8f0f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/SoundEventExistsValidator.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class SoundEventExistsValidator extends AssetValidator { + private static final SoundEventExistsValidator DEFAULT_INSTANCE = new SoundEventExistsValidator(); + + private SoundEventExistsValidator() { + } + + private SoundEventExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "SoundEvent"; + } + + @Override + public boolean test(String soundEvent) { + return SoundEvent.getAssetMap().getAsset(soundEvent) != null; + } + + @Nonnull + @Override + public String errorMessage(String soundEvent, String attributeName) { + return "The sound event with the name \"" + soundEvent + "\" does not exist for attribute \"" + attributeName + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return SoundEvent.class.getSimpleName(); + } + + public static SoundEventExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static SoundEventExistsValidator withConfig(EnumSet config) { + return new SoundEventExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/WeatherExistsValidator.java b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/WeatherExistsValidator.java new file mode 100644 index 0000000..98422a9 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/asset/builder/validators/asset/WeatherExistsValidator.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.asset.builder.validators.asset; + +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class WeatherExistsValidator extends AssetValidator { + private static final WeatherExistsValidator DEFAULT_INSTANCE = new WeatherExistsValidator(); + + private WeatherExistsValidator() { + } + + private WeatherExistsValidator(EnumSet config) { + super(config); + } + + @Nonnull + @Override + public String getDomain() { + return "Weather"; + } + + @Override + public boolean test(String value) { + return Weather.getAssetMap().getAsset(value) != null; + } + + @Nonnull + @Override + public String errorMessage(String value, String attribute) { + return "The weather with the name \"" + value + "\" does not exist for attribute \"" + attribute + "\""; + } + + @Nonnull + @Override + public String getAssetName() { + return Weather.class.getSimpleName(); + } + + public static WeatherExistsValidator required() { + return DEFAULT_INSTANCE; + } + + @Nonnull + public static WeatherExistsValidator withConfig(EnumSet config) { + return new WeatherExistsValidator(config); + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/Blackboard.java b/src/com/hypixel/hytale/server/npc/blackboard/Blackboard.java new file mode 100644 index 0000000..f645b96 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/Blackboard.java @@ -0,0 +1,117 @@ +package com.hypixel.hytale.server.npc.blackboard; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.event.events.ecs.BreakBlockEvent; +import com.hypixel.hytale.server.core.event.events.ecs.DamageBlockEvent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.blackboard.view.IBlackboardView; +import com.hypixel.hytale.server.npc.blackboard.view.IBlackboardViewManager; +import com.hypixel.hytale.server.npc.blackboard.view.SingletonBlackboardViewManager; +import com.hypixel.hytale.server.npc.blackboard.view.attitude.AttitudeView; +import com.hypixel.hytale.server.npc.blackboard.view.blocktype.BlockTypeView; +import com.hypixel.hytale.server.npc.blackboard.view.blocktype.BlockTypeViewManager; +import com.hypixel.hytale.server.npc.blackboard.view.event.block.BlockEventView; +import com.hypixel.hytale.server.npc.blackboard.view.event.entity.EntityEventView; +import com.hypixel.hytale.server.npc.blackboard.view.interaction.InteractionView; +import com.hypixel.hytale.server.npc.blackboard.view.resource.ResourceView; +import com.hypixel.hytale.server.npc.blackboard.view.resource.ResourceViewManager; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +public class Blackboard implements Resource { + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + private final ConcurrentHashMap>, IBlackboardViewManager> views = new ConcurrentHashMap<>(); + + public Blackboard() { + } + + public static ResourceType getResourceType() { + return NPCPlugin.get().getBlackboardResourceType(); + } + + public void init(@Nonnull World world) { + this.registerViewType(BlockTypeView.class, new BlockTypeViewManager()); + this.registerViewType(BlockEventView.class, new SingletonBlackboardViewManager<>(new BlockEventView(world))); + this.registerViewType(EntityEventView.class, new SingletonBlackboardViewManager<>(new EntityEventView(world))); + this.registerViewType(ResourceView.class, new ResourceViewManager()); + this.registerViewType(AttitudeView.class, new SingletonBlackboardViewManager<>(new AttitudeView(world))); + this.registerViewType(InteractionView.class, new SingletonBlackboardViewManager<>(new InteractionView(world))); + } + + public void onEntityDamageBlock(@Nonnull Ref ref, @Nonnull DamageBlockEvent event) { + for (IBlackboardViewManager manager : this.views.values()) { + manager.forEachView(view -> { + if (view instanceof BlockEventView blockEventView) { + blockEventView.onEntityDamageBlock(ref, event); + } + }); + } + } + + public void onEntityBreakBlock(@Nonnull Ref ref, @Nonnull BreakBlockEvent event) { + for (IBlackboardViewManager manager : this.views.values()) { + manager.forEachView(view -> { + if (view instanceof BlockEventView blockEventView) { + blockEventView.onEntityBreakBlock(ref, event); + } + }); + } + } + + private > void registerViewType(@Nonnull Class clazz, @Nonnull IBlackboardViewManager holder) { + this.views.put(clazz, holder); + } + + public void cleanupViews() { + this.views.forEach((clazz, manager) -> manager.cleanup()); + } + + public void clear() { + this.views.forEach((clazz, manager) -> manager.clear()); + } + + public void onWorldRemoved() { + this.views.forEach((clazz, manager) -> manager.onWorldRemoved()); + } + + public > void forEachView(Class viewTypeClass, Consumer consumer) { + this.getViewManager(viewTypeClass).forEachView(consumer); + } + + public > View getView(Class viewTypeClass, Ref ref, ComponentAccessor componentAccessor) { + return this.getViewManager(viewTypeClass).get(ref, this, componentAccessor); + } + + public > View getView(Class viewTypeClass, int chunkX, int chunkZ) { + return this.getViewManager(viewTypeClass).get(chunkX, chunkZ, this); + } + + public > View getView(Class viewTypeClass, long index) { + return this.getViewManager(viewTypeClass).get(index, this); + } + + public > View getIfExists(Class viewTypeClass, long index) { + return this.getViewManager(viewTypeClass).getIfExists(index); + } + + @Nonnull + private > IBlackboardViewManager getViewManager(Class viewTypeClass) { + return Objects.requireNonNull((IBlackboardViewManager)this.views.get(viewTypeClass), "View type manager not registered!"); + } + + @Nonnull + @Override + public Resource clone() { + Blackboard blackboard = new Blackboard(); + blackboard.views.putAll(this.views); + return blackboard; + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/BlockRegionView.java b/src/com/hypixel/hytale/server/npc/blackboard/view/BlockRegionView.java new file mode 100644 index 0000000..2c5f59e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/BlockRegionView.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.npc.blackboard.view; + +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import javax.annotation.Nonnull; + +public abstract class BlockRegionView> implements IBlackboardView { + public static final int BITS = 7; + public static final int SIZE = 128; + public static final int SIZE_MASK = 127; + public static final int BITS2 = 14; + + public BlockRegionView() { + } + + public static int toRegionalBlackboardCoordinate(int pos) { + return pos >> 7; + } + + public static int toWorldCoordinate(int pos) { + return pos << 7; + } + + public static int chunkToRegionalBlackboardCoordinate(int pos) { + return pos >> 2; + } + + public static long indexView(int x, int z) { + return ChunkUtil.indexChunk(x, z); + } + + public static int indexSection(int y) { + return y >> 7; + } + + public static int xOfViewIndex(long index) { + return ChunkUtil.xOfChunkIndex(index); + } + + public static int zOfViewIndex(long index) { + return ChunkUtil.zOfChunkIndex(index); + } + + public static long indexViewFromChunkCoordinates(int x, int z) { + return indexView(toRegionalBlackboardCoordinate(x), toRegionalBlackboardCoordinate(z)); + } + + public static long indexViewFromWorldPosition(@Nonnull Vector3d pos) { + int blackboardX = toRegionalBlackboardCoordinate(MathUtil.floor(pos.getX())); + int blackboardZ = toRegionalBlackboardCoordinate(MathUtil.floor(pos.getZ())); + return indexView(blackboardX, blackboardZ); + } + + public static int indexBlock(int x, int y, int z) { + return (y & 127) << 14 | (z & 127) << 7 | x & 127; + } + + public static int xFromIndex(int index) { + return index & 127; + } + + public static int yFromIndex(int index) { + return index >> 14 & 127; + } + + public static int zFromIndex(int index) { + return index >> 7 & 127; + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/BlockRegionViewManager.java b/src/com/hypixel/hytale/server/npc/blackboard/view/BlockRegionViewManager.java new file mode 100644 index 0000000..2f152a0 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/BlockRegionViewManager.java @@ -0,0 +1,86 @@ +package com.hypixel.hytale.server.npc.blackboard.view; + +import com.hypixel.fastutil.longs.Long2ObjectConcurrentHashMap; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.blackboard.Blackboard; +import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +public abstract class BlockRegionViewManager> implements IBlackboardViewManager { + @Nonnull + protected Long2ObjectConcurrentHashMap views = new Long2ObjectConcurrentHashMap<>(true, ChunkUtil.NOT_FOUND); + @Nonnull + protected LongArrayFIFOQueue removalQueue = new LongArrayFIFOQueue(); + + public BlockRegionViewManager() { + } + + public Type get(@Nonnull Ref ref, Blackboard blackboard, @Nonnull ComponentAccessor componentAccessor) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + return this.get(transformComponent.getPosition(), blackboard); + } + + public Type get(@Nonnull Vector3d position, Blackboard blackboard) { + long index = BlockRegionView.indexViewFromWorldPosition(position); + return this.get(index, blackboard); + } + + public Type get(int chunkX, int chunkZ, Blackboard blackboard) { + long index = BlockRegionView.indexViewFromChunkCoordinates(chunkX, chunkZ); + return this.get(index, blackboard); + } + + public Type get(long index, Blackboard blackboard) { + Type view = this.views.getOrDefault(index, null); + if (view == null) { + view = this.createView(index, blackboard); + this.views.put(index, view); + } + + return view; + } + + protected abstract Type createView(long var1, Blackboard var3); + + public Type getIfExists(long index) { + return this.views.get(index); + } + + @Override + public void cleanup() { + this.views.forEach((index, entry, viewManager) -> { + if (viewManager.shouldCleanup((Type)entry)) { + viewManager.removalQueue.enqueue(index); + } + }, this); + + while (!this.removalQueue.isEmpty()) { + this.views.remove(this.removalQueue.dequeueLong()); + } + } + + protected abstract boolean shouldCleanup(Type var1); + + @Override + public void onWorldRemoved() { + } + + @Override + public void forEachView(@Nonnull Consumer consumer) { + this.views.forEach((index, view) -> consumer.accept((Type)view)); + } + + @Override + public void clear() { + this.views.clear(); + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/IBlackboardView.java b/src/com/hypixel/hytale/server/npc/blackboard/view/IBlackboardView.java new file mode 100644 index 0000000..95196af --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/IBlackboardView.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.server.npc.blackboard.view; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import javax.annotation.Nonnull; + +public interface IBlackboardView> { + boolean isOutdated(@Nonnull Ref var1, @Nonnull Store var2); + + View getUpdatedView(@Nonnull Ref var1, @Nonnull ComponentAccessor var2); + + void initialiseEntity(@Nonnull Ref var1, @Nonnull NPCEntity var2); + + void cleanup(); + + void onWorldRemoved(); +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/IBlackboardViewManager.java b/src/com/hypixel/hytale/server/npc/blackboard/view/IBlackboardViewManager.java new file mode 100644 index 0000000..17a8768 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/IBlackboardViewManager.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.npc.blackboard.view; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.blackboard.Blackboard; +import java.util.function.Consumer; + +public interface IBlackboardViewManager> { + View get(Ref var1, Blackboard var2, ComponentAccessor var3); + + View get(Vector3d var1, Blackboard var2); + + View get(int var1, int var2, Blackboard var3); + + View get(long var1, Blackboard var3); + + View getIfExists(long var1); + + void cleanup(); + + void onWorldRemoved(); + + void forEachView(Consumer var1); + + void clear(); +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/PrioritisedProviderView.java b/src/com/hypixel/hytale/server/npc/blackboard/view/PrioritisedProviderView.java new file mode 100644 index 0000000..6a38703 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/PrioritisedProviderView.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.server.npc.blackboard.view; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; + +public abstract class PrioritisedProviderView> implements IBlackboardView { + public static final int LOWEST_PRIORITY = Integer.MAX_VALUE; + @Nonnull + protected List> providers = new ObjectArrayList<>(); + + public PrioritisedProviderView() { + } + + public void registerProvider(int priority, T provider) { + this.providers.add(new PrioritisedProviderView.PrioritisedProvider<>(priority, provider)); + Collections.sort(this.providers); + } + + public static class PrioritisedProvider implements Comparable> { + private final int priority; + private final T provider; + + public PrioritisedProvider(int priority, T provider) { + this.priority = priority; + this.provider = provider; + } + + public T getProvider() { + return this.provider; + } + + public int compareTo(@Nonnull PrioritisedProviderView.PrioritisedProvider other) { + return Integer.compare(this.priority, other.priority); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/SingletonBlackboardViewManager.java b/src/com/hypixel/hytale/server/npc/blackboard/view/SingletonBlackboardViewManager.java new file mode 100644 index 0000000..310b7e3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/SingletonBlackboardViewManager.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.server.npc.blackboard.view; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.blackboard.Blackboard; +import java.util.function.Consumer; +import javax.annotation.Nonnull; + +public class SingletonBlackboardViewManager> implements IBlackboardViewManager { + private final View view; + + public SingletonBlackboardViewManager(View view) { + this.view = view; + } + + @Override + public View get(Ref ref, Blackboard blackboard, ComponentAccessor componentAccessor) { + return this.view; + } + + @Override + public View get(Vector3d position, Blackboard blackboard) { + return this.view; + } + + @Override + public View get(int chunkX, int chunkZ, Blackboard blackboard) { + return this.view; + } + + @Override + public View get(long index, Blackboard blackboard) { + return this.view; + } + + @Override + public View getIfExists(long index) { + return this.view; + } + + @Override + public void cleanup() { + this.view.cleanup(); + } + + @Override + public void onWorldRemoved() { + this.view.onWorldRemoved(); + } + + @Override + public void forEachView(@Nonnull Consumer consumer) { + consumer.accept(this.view); + } + + @Override + public void clear() { + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/attitude/AttitudeMap.java b/src/com/hypixel/hytale/server/npc/blackboard/view/attitude/AttitudeMap.java new file mode 100644 index 0000000..8d3e107 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/attitude/AttitudeMap.java @@ -0,0 +1,133 @@ +package com.hypixel.hytale.server.npc.blackboard.view.attitude; + +import com.hypixel.hytale.builtin.tagset.TagSetPlugin; +import com.hypixel.hytale.builtin.tagset.config.NPCGroup; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderManager; +import com.hypixel.hytale.server.npc.config.AttitudeGroup; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.Map; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AttitudeMap { + @Nullable + private static final ComponentType NPC_COMPONENT_TYPE = NPCEntity.getComponentType(); + private static final ComponentType PLAYER_COMPONENT_TYPE = Player.getComponentType(); + private final Int2ObjectMap[] map; + + private AttitudeMap(Int2ObjectMap[] map) { + this.map = map; + } + + @Nullable + public Attitude getAttitude(@Nonnull Role role, @Nonnull Ref target, @Nonnull ComponentAccessor componentAccessor) { + int group = role.getWorldSupport().getAttitudeGroup(); + if (group == Integer.MIN_VALUE) { + return null; + } else { + Int2ObjectMap attitudeMap = this.map[group]; + if (attitudeMap == null) { + return null; + } else { + NPCEntity npc = componentAccessor.getComponent(target, NPC_COMPONENT_TYPE); + int targetId; + if (npc != null) { + targetId = npc.getRoleIndex(); + } else { + if (!componentAccessor.getArchetype(target).contains(PLAYER_COMPONENT_TYPE)) { + return null; + } + + targetId = BuilderManager.getPlayerGroupID(); + } + + return targetId == role.getRoleIndex() ? attitudeMap.get(BuilderManager.getSelfGroupID()) : attitudeMap.get(targetId); + } + } + } + + public int getAttitudeGroupCount() { + return this.map.length; + } + + public void updateAttitudeGroup(int id, @Nonnull AttitudeGroup group) { + Int2ObjectMap groupMap = AttitudeMap.Builder.createGroupMap(group); + this.map[id] = groupMap; + } + + public static class Builder { + private final Int2ObjectMap[] map = new Int2ObjectMap[AttitudeGroup.getAssetMap().getNextIndex()]; + + public Builder() { + } + + public void addAttitudeGroups(@Nonnull Map groups) { + groups.forEach((id, group) -> this.addAttitudeGroup(group)); + } + + private void addAttitudeGroup(@Nonnull AttitudeGroup group) { + String key = group.getId(); + int index = AttitudeGroup.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } else { + this.map[index] = createGroupMap(group); + } + } + + @Nonnull + private static Int2ObjectMap createGroupMap(@Nonnull AttitudeGroup group) { + TagSetPlugin.TagSetLookup npcGroups = TagSetPlugin.get(NPCGroup.class); + Int2ObjectOpenHashMap groupMap = new Int2ObjectOpenHashMap<>(); + + for (Attitude attitude : Attitude.VALUES) { + putGroups(group.getId(), npcGroups, group.getAttitudeGroups().get(attitude), attitude, groupMap); + } + + groupMap.trim(); + return groupMap; + } + + private static void putGroups( + String attitudeGroup, + @Nonnull TagSetPlugin.TagSetLookup npcGroupLookup, + @Nullable String[] group, + Attitude targetAttitude, + @Nonnull Int2ObjectMap targetMap + ) { + if (group != null) { + for (String item : group) { + int index = NPCGroup.getAssetMap().getIndex(item); + if (index == Integer.MIN_VALUE) { + NPCPlugin.get() + .getLogger() + .at(Level.SEVERE) + .log("Creating attitude groups: NPC Group '%s' does not exist in attitude group '%s'!", item, attitudeGroup); + } else { + IntSet set = npcGroupLookup.getSet(index); + if (set != null) { + set.forEach(i -> targetMap.put(i, targetAttitude)); + } + } + } + } + } + + @Nonnull + public AttitudeMap build() { + return new AttitudeMap(this.map); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/attitude/AttitudeView.java b/src/com/hypixel/hytale/server/npc/blackboard/view/attitude/AttitudeView.java new file mode 100644 index 0000000..c1734cc --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/attitude/AttitudeView.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.server.npc.blackboard.view.attitude; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.blackboard.Blackboard; +import com.hypixel.hytale.server.npc.blackboard.view.PrioritisedProviderView; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.WorldSupport; +import javax.annotation.Nonnull; + +public class AttitudeView extends PrioritisedProviderView { + private final World world; + + public AttitudeView(World world) { + this.world = world; + this.registerProvider(0, (ref, self, target, accessor) -> self.getWorldSupport().getOverriddenAttitude(target)); + this.registerProvider(200, (ref, self, target, accessor) -> NPCPlugin.get().getAttitudeMap().getAttitude(self, target, accessor)); + this.registerProvider( + Integer.MAX_VALUE, + (ref, self, target, accessor) -> { + WorldSupport worldSupport = self.getWorldSupport(); + return accessor.getArchetype(target).contains(Player.getComponentType()) + ? worldSupport.getDefaultPlayerAttitude() + : worldSupport.getDefaultNPCAttitude(); + } + ); + } + + @Nonnull + public Attitude getAttitude( + @Nonnull Ref ref, @Nonnull Role self, @Nonnull Ref target, @Nonnull ComponentAccessor componentAccessor + ) { + Attitude result = null; + int pos = 0; + + while (result == null) { + if (pos >= this.providers.size()) { + return Attitude.NEUTRAL; + } + + result = this.providers.get(pos++).getProvider().getAttitude(ref, self, target, componentAccessor); + } + + return result; + } + + @Override + public boolean isOutdated(@Nonnull Ref ref, @Nonnull Store store) { + return false; + } + + public AttitudeView getUpdatedView(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + World entityWorld = componentAccessor.getExternalData().getWorld(); + if (!entityWorld.equals(this.world)) { + Blackboard blackboardResource = componentAccessor.getResource(Blackboard.getResourceType()); + return blackboardResource.getView(AttitudeView.class, ref, componentAccessor); + } else { + return this; + } + } + + @Override + public void initialiseEntity(@Nonnull Ref ref, @Nonnull NPCEntity npcComponent) { + } + + @Override + public void cleanup() { + } + + @Override + public void onWorldRemoved() { + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/attitude/IAttitudeProvider.java b/src/com/hypixel/hytale/server/npc/blackboard/view/attitude/IAttitudeProvider.java new file mode 100644 index 0000000..9799dd6 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/attitude/IAttitudeProvider.java @@ -0,0 +1,14 @@ +package com.hypixel.hytale.server.npc.blackboard.view.attitude; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.role.Role; +import javax.annotation.Nonnull; + +public interface IAttitudeProvider { + int OVERRIDE_PRIORITY = 0; + + Attitude getAttitude(@Nonnull Ref var1, @Nonnull Role var2, @Nonnull Ref var3, @Nonnull ComponentAccessor var4); +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/attitude/ItemAttitudeMap.java b/src/com/hypixel/hytale/server/npc/blackboard/view/attitude/ItemAttitudeMap.java new file mode 100644 index 0000000..2290b0d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/attitude/ItemAttitudeMap.java @@ -0,0 +1,98 @@ +package com.hypixel.hytale.server.npc.blackboard.view.attitude; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.npc.config.ItemAttitudeGroup; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ItemAttitudeMap { + private final Map[] map; + + private ItemAttitudeMap(Map[] map) { + this.map = map; + } + + @Nullable + public Attitude getAttitude(@Nonnull NPCEntity parent, @Nullable ItemStack item) { + if (item == null) { + return null; + } else { + int group = parent.getRole().getWorldSupport().getItemAttitudeGroup(); + if (group == Integer.MIN_VALUE) { + return null; + } else { + Map attitudeMap = this.map[group]; + if (attitudeMap == null) { + return null; + } else { + String targetId = item.getItemId(); + return attitudeMap.get(targetId); + } + } + } + } + + public int getAttitudeGroupCount() { + return this.map.length; + } + + public void updateAttitudeGroup(int id, @Nonnull ItemAttitudeGroup group) { + Map groupMap = ItemAttitudeMap.Builder.createGroupMap(group); + this.map[id] = groupMap; + } + + public static class Builder { + private final Map[] map = new HashMap[ItemAttitudeGroup.getAssetMap().getNextIndex()]; + + public Builder() { + } + + public void addAttitudeGroups(@Nonnull Map groups) { + groups.forEach((id, group) -> this.addAttitudeGroup(group)); + } + + private void addAttitudeGroup(@Nonnull ItemAttitudeGroup group) { + String key = group.getId(); + int index = ItemAttitudeGroup.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } else { + this.map[index] = createGroupMap(group); + } + } + + @Nonnull + private static Map createGroupMap(@Nonnull ItemAttitudeGroup group) { + HashMap groupMap = new HashMap<>(); + + for (Attitude attitude : Attitude.VALUES) { + putGroups(group.getAttitudes().get(attitude), attitude, groupMap); + } + + return groupMap; + } + + private static void putGroups(@Nullable String[] group, Attitude targetAttitude, @Nonnull HashMap targetMap) { + if (group != null) { + for (String item : group) { + Set set = Item.getAssetMap().getKeysForTag(AssetRegistry.getOrCreateTagIndex(item)); + if (set != null) { + set.forEach(k -> targetMap.put(k, targetAttitude)); + } + } + } + } + + @Nonnull + public ItemAttitudeMap build() { + return new ItemAttitudeMap(this.map); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/blocktype/BlockPositionEntryGenerator.java b/src/com/hypixel/hytale/server/npc/blackboard/view/blocktype/BlockPositionEntryGenerator.java new file mode 100644 index 0000000..efcd5e5 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/blocktype/BlockPositionEntryGenerator.java @@ -0,0 +1,125 @@ +package com.hypixel.hytale.server.npc.blackboard.view.blocktype; + +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.server.core.modules.blockset.BlockSetModule; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSectionReference; +import com.hypixel.hytale.server.core.universe.world.chunk.section.blockpositions.BlockPositionData; +import com.hypixel.hytale.server.core.universe.world.chunk.section.blockpositions.BlockPositionProvider; +import com.hypixel.hytale.server.core.universe.world.chunk.section.blockpositions.IBlockPositionData; +import com.hypixel.hytale.server.npc.NPCPlugin; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntConsumer; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.BitSet; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockPositionEntryGenerator { + private final BlockPositionEntryGenerator.FoundBlockConsumer foundBlockConsumer = new BlockPositionEntryGenerator.FoundBlockConsumer(); + private final IntSet internalIdHolder = new IntOpenHashSet(); + + public BlockPositionEntryGenerator() { + } + + @Nonnull + public BlockPositionProvider generate( + short changeCounter, int sectionIndex, @Nonnull BlockChunk chunk, IntList unifiedBlocksOfInterest, @Nonnull BitSet searchedBlockSets + ) { + BlockSection section = chunk.getSectionAtIndex(sectionIndex); + if (section.isSolidAir()) { + return new BlockPositionProvider(searchedBlockSets, null, changeCounter); + } else if (!section.containsAny(unifiedBlocksOfInterest)) { + return new BlockPositionProvider(searchedBlockSets, null, changeCounter); + } else { + ChunkSectionReference chunkSectionPointer = new ChunkSectionReference(chunk, section, sectionIndex); + this.foundBlockConsumer.init(chunkSectionPointer, searchedBlockSets); + section.find(unifiedBlocksOfInterest, this.internalIdHolder, this.foundBlockConsumer); + this.internalIdHolder.clear(); + Int2ObjectOpenHashMap> blockData = this.foundBlockConsumer.getBlockData(); + this.foundBlockConsumer.release(); + return new BlockPositionProvider(searchedBlockSets, blockData, changeCounter); + } + } + + private static class FoundBlockConsumer implements IntConsumer { + private final Int2IntMap blockSetCounts = new Int2IntOpenHashMap(); + @Nullable + private ChunkSectionReference sectionPointer; + @Nullable + private BitSet searchedBlockSets; + private int maxBlockType; + @Nullable + private Int2ObjectOpenHashMap> blockData; + + private FoundBlockConsumer() { + } + + public void init(ChunkSectionReference sectionPointer, BitSet searchedBlockSets) { + this.sectionPointer = sectionPointer; + this.searchedBlockSets = searchedBlockSets; + this.maxBlockType = NPCPlugin.get().getMaxBlackboardBlockCountPerType(); + this.blockData = new Int2ObjectOpenHashMap<>(); + } + + public void release() { + this.blockSetCounts.clear(); + this.sectionPointer = null; + this.searchedBlockSets = null; + this.blockData = null; + } + + @Override + public void accept(int blockIndex) { + BlockSetModule blockSetModule = BlockSetModule.getInstance(); + int type = this.sectionPointer.getSection().get(blockIndex); + BlockPositionData data = null; + + for (int i = this.searchedBlockSets.nextSetBit(0); i >= 0; i = this.searchedBlockSets.nextSetBit(i + 1)) { + if (blockSetModule.blockInSet(i, type)) { + List entry = this.blockData.getOrDefault(i, null); + if (entry == null) { + entry = new ObjectArrayList<>(); + this.blockData.put(i, entry); + } + + int count = this.blockSetCounts.getOrDefault(i, 0); + if (count < this.maxBlockType) { + if (data == null) { + data = new BlockPositionData(blockIndex, this.sectionPointer, type); + } + + entry.add(data); + } else { + int j = RandomExtra.randomRange(count + 1); + if (j < this.maxBlockType) { + if (data == null) { + data = new BlockPositionData(blockIndex, this.sectionPointer, type); + } + + entry.set(j, data); + } + } + + this.blockSetCounts.put(i, count + 1); + } + + if (i == Integer.MAX_VALUE) { + break; + } + } + } + + public Int2ObjectOpenHashMap> getBlockData() { + this.blockData.trim(); + return this.blockData; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/blocktype/BlockTypeView.java b/src/com/hypixel/hytale/server/npc/blackboard/view/blocktype/BlockTypeView.java new file mode 100644 index 0000000..c4037d9 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/blocktype/BlockTypeView.java @@ -0,0 +1,366 @@ +package com.hypixel.hytale.server.npc.blackboard.view.blocktype; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.blockset.BlockSetModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.blockpositions.BlockPositionProvider; +import com.hypixel.hytale.server.core.universe.world.chunk.section.blockpositions.IBlockPositionData; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.blackboard.Blackboard; +import com.hypixel.hytale.server.npc.blackboard.view.BlockRegionView; +import com.hypixel.hytale.server.npc.blackboard.view.resource.ResourceView; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntIterator; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.BitSet; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.BiPredicate; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockTypeView extends BlockRegionView { + private final long index; + private final Blackboard blackboard; + private final BitSet allBlockSets = new BitSet(); + private final Set> entities = new HashSet<>(); + private final IntArrayList blockSetAggregate = new IntArrayList(); + private final IntArrayList crossViewBlockSetAggregate = new IntArrayList(); + private boolean aggregateNeedsRebuild; + private final Int2IntMap blockSetCounts = new Int2IntOpenHashMap(); + private final List foundBlocks = new ObjectArrayList<>(); + private final BlockPositionEntryGenerator generator; + private final BiPredicate reservedBlockFilter = (data, view) -> view.isBlockReserved(data.getX(), data.getY(), data.getZ()); + + public BlockTypeView(long index, Blackboard blackboard, BlockPositionEntryGenerator generator) { + this.index = index; + this.blackboard = blackboard; + this.generator = generator; + } + + public long getIndex() { + return this.index; + } + + @Override + public boolean isOutdated(@Nonnull Ref ref, @Nonnull Store store) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + return indexViewFromWorldPosition(transformComponent.getPosition()) != this.index; + } + + @Nonnull + public BlockTypeView getUpdatedView(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + Blackboard blackBoardResource = componentAccessor.getResource(Blackboard.getResourceType()); + BlockTypeView blockTypeView = blackBoardResource.getView(BlockTypeView.class, ref, componentAccessor); + NPCEntity npcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + IntList blockSets = npcComponent.getBlackboardBlockTypeSets(); + this.removeSearchedBlockSets(ref, npcComponent, blockSets); + blockTypeView.addSearchedBlockSets(ref, npcComponent, blockSets); + return blockTypeView; + } + + @Override + public void initialiseEntity(@Nonnull Ref ref, @Nonnull NPCEntity npcComponent) { + this.addSearchedBlockSets(ref, npcComponent, npcComponent.getBlackboardBlockTypeSets()); + } + + @Override + public void cleanup() { + } + + @Override + public void onWorldRemoved() { + } + + public void addSearchedBlockSets(@Nonnull Ref ref, @Nonnull NPCEntity entity, @Nonnull IntList blockSets) { + HytaleLogger.Api context = Blackboard.LOGGER.at(Level.FINEST); + if (context.isEnabled()) { + context.log( + "Registering new entity %s (reference:%s) with partial blackboard view %s, %s", + entity.getRoleName(), + ref, + xOfViewIndex(this.index), + zOfViewIndex(this.index) + ); + } + + this.entities.add(ref); + + for (int i = 0; i < blockSets.size(); i++) { + this.addSearchedBlockSet(blockSets.getInt(i)); + } + } + + private void addSearchedBlockSet(int blockSet) { + int existingCount = this.blockSetCounts.getOrDefault(blockSet, 0); + if (existingCount == 0) { + this.allBlockSets.set(blockSet); + this.aggregateNeedsRebuild = true; + } + + this.blockSetCounts.put(blockSet, existingCount + 1); + } + + public void removeSearchedBlockSets(@Nonnull Ref ref, @Nonnull NPCEntity npcComponent, @Nonnull IntList blockSets) { + if (!this.entities.remove(ref)) { + throw new IllegalStateException( + String.format( + "Attempting to unregister entity %s (reference:%s) from partial blackboard view %s at %s, %s when not registered", + npcComponent.getRoleName(), + ref, + this.index, + xOfViewIndex(this.index), + zOfViewIndex(this.index) + ) + ); + } else { + HytaleLogger.Api context = Blackboard.LOGGER.at(Level.FINEST); + if (context.isEnabled()) { + context.log( + "Unregistering entity %s (reference:%s) from partial blackboard view %s, %s", + npcComponent.getRoleName(), + ref, + xOfViewIndex(this.index), + zOfViewIndex(this.index) + ); + } + + for (int i = 0; i < blockSets.size(); i++) { + this.removeSearchedBlockSet(blockSets.getInt(i)); + } + } + } + + private void removeSearchedBlockSet(int blockSet) { + int newCount = this.blockSetCounts.getOrDefault(blockSet, 0) - 1; + if (newCount < 0) { + throw new IllegalStateException( + String.format( + "Attempting to unregister blockset %s from partial blackboard view %s at %s, %s when not registered", + blockSet, + this.index, + xOfViewIndex(this.index), + zOfViewIndex(this.index) + ) + ); + } else { + if (newCount == 0) { + this.allBlockSets.clear(blockSet); + this.aggregateNeedsRebuild = true; + } + + this.blockSetCounts.put(blockSet, newCount); + } + } + + @Nullable + public IBlockPositionData findBlock( + int blockSet, double range, double yMax, boolean pickRandom, @Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor + ) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d entityPos = transformComponent.getPosition(); + NPCEntity npcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + String roleName = npcComponent.getRoleName(); + int entityX = MathUtil.floor(entityPos.x); + int entityZ = MathUtil.floor(entityPos.z); + int entityY = MathUtil.floor(entityPos.y); + World world = componentAccessor.getExternalData().getWorld(); + int maxRange = MathUtil.ceil(range); + int yRange = MathUtil.ceil(yMax); + int minY = MathUtil.clamp(entityY - yRange & -32, 0, 320); + int maxY = MathUtil.clamp(entityY + yRange, 0, 320); + BitSet clonedBitSet = null; + ChunkStore chunkStore = componentAccessor.getExternalData().getWorld().getChunkStore(); + Store chunkStoreStore = chunkStore.getStore(); + + for (int x = entityX - maxRange & -32; x < entityX + maxRange; x += 32) { + for (int z = entityZ - maxRange & -32; z < entityZ + maxRange; z += 32) { + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(x, z)); + if (chunk != null) { + long chunkIndex = chunk.getIndex(); + BlockChunk blockChunk = chunk.getBlockChunk(); + + for (int y = minY; y < maxY; y += 32) { + int sectionIndex = ChunkUtil.indexSection(y); + BlockSection section = blockChunk.getSectionAtIndex(sectionIndex); + Ref sectionRef = chunkStore.getChunkSectionReference(ChunkUtil.chunkCoordinate(x), sectionIndex, ChunkUtil.chunkCoordinate(z)); + if (sectionRef != null) { + BlockPositionProvider entry = chunkStoreStore.getComponent(sectionRef, BlockPositionProvider.getComponentType()); + if (entry == null || entry.isStale(blockSet, section)) { + short changeCounter = section.getLocalChangeCounter(); + if (indexViewFromChunkCoordinates(x, z) == this.index) { + if (this.aggregateNeedsRebuild) { + HytaleLogger.Api context = Blackboard.LOGGER.at(Level.FINEST); + if (context.isEnabled()) { + context.log( + "Rebuilding blocktype aggregate in partial blackboard view %s, %s with %s blocksets", + xOfViewIndex(this.index), + zOfViewIndex(this.index), + this.allBlockSets.cardinality() + ); + } + + this.aggregateNeedsRebuild = false; + rebuildBlockTypeAggregate(this.blockSetAggregate, this.allBlockSets); + } + + HytaleLogger.Api context = Blackboard.LOGGER.at(Level.FINEST); + if (context.isEnabled()) { + context.log( + "Entity %s (reference:%s) generating new entry for chunk %s section %s in view %s, %s", + roleName, + ref, + chunkIndex, + sectionIndex, + xOfViewIndex(this.index), + zOfViewIndex(this.index) + ); + } + + if (clonedBitSet == null) { + clonedBitSet = (BitSet)this.allBlockSets.clone(); + } + + entry = this.generator.generate(changeCounter, sectionIndex, blockChunk, this.blockSetAggregate, clonedBitSet); + } else { + HytaleLogger.Api contextx = Blackboard.LOGGER.at(Level.FINEST); + BitSet combinedClonedBlockSets; + if (entry != null) { + if (contextx.isEnabled()) { + contextx.log( + "Entity %s (reference:%s) generating new entry for chunk %s section %s across border using existing entry", + roleName, + ref, + chunkIndex, + sectionIndex + ); + } + + combinedClonedBlockSets = entry.getSearchedBlockSets(); + } else { + if (contextx.isEnabled()) { + contextx.log( + "Entity %s (reference:%s) generating new entry for chunk %s section %s across border", + roleName, + ref, + chunkIndex, + sectionIndex + ); + } + + BlockTypeView otherView = this.blackboard.getView(BlockTypeView.class, x, z); + if (otherView != null) { + combinedClonedBlockSets = (BitSet)otherView.allBlockSets.clone(); + } else { + combinedClonedBlockSets = new BitSet(); + } + } + + combinedClonedBlockSets.or(this.allBlockSets); + rebuildBlockTypeAggregate(this.crossViewBlockSetAggregate, combinedClonedBlockSets); + entry = this.generator.generate(changeCounter, sectionIndex, blockChunk, this.crossViewBlockSetAggregate, combinedClonedBlockSets); + } + + chunkStoreStore.putComponent(sectionRef, BlockPositionProvider.getComponentType(), entry); + } + + ResourceView resourceView = this.blackboard.getIfExists(ResourceView.class, indexViewFromChunkCoordinates(x, z)); + entry.findBlocks( + this.foundBlocks, blockSet, range, yRange, ref, resourceView != null ? this.reservedBlockFilter : null, resourceView, componentAccessor + ); + } + } + } + } + } + + if (this.foundBlocks.isEmpty()) { + return null; + } else { + IBlockPositionData data = null; + if (pickRandom) { + data = RandomExtra.randomElement(this.foundBlocks); + } else { + double minDist2 = Double.MAX_VALUE; + + for (int i = 0; i < this.foundBlocks.size(); i++) { + IBlockPositionData block = this.foundBlocks.get(i); + double dist2 = entityPos.distanceSquaredTo(block.getXCentre(), block.getYCentre(), block.getZCentre()); + if (dist2 < minDist2) { + minDist2 = dist2; + data = block; + } + } + } + + this.foundBlocks.clear(); + return data; + } + } + + @Nonnull + public Set> getEntities() { + return this.entities; + } + + @Nonnull + public BitSet getAllBlockSets() { + return this.allBlockSets; + } + + @Nonnull + public Int2IntMap getBlockSetCounts() { + return this.blockSetCounts; + } + + private static void rebuildBlockTypeAggregate(@Nonnull IntArrayList aggregate, @Nonnull BitSet searchedBlockSets) { + aggregate.clear(); + Int2ObjectMap blockSets = BlockSetModule.getInstance().getBlockSets(); + + for (int i = searchedBlockSets.nextSetBit(0); i >= 0; i = searchedBlockSets.nextSetBit(i + 1)) { + IntSet set = blockSets.get(i); + IntIterator iterator = set.iterator(); + + while (iterator.hasNext()) { + int next = iterator.nextInt(); + aggregate.add(next); + } + + if (i == Integer.MAX_VALUE) { + break; + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/blocktype/BlockTypeViewManager.java b/src/com/hypixel/hytale/server/npc/blackboard/view/blocktype/BlockTypeViewManager.java new file mode 100644 index 0000000..38c9c33 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/blocktype/BlockTypeViewManager.java @@ -0,0 +1,21 @@ +package com.hypixel.hytale.server.npc.blackboard.view.blocktype; + +import com.hypixel.hytale.server.npc.blackboard.Blackboard; +import com.hypixel.hytale.server.npc.blackboard.view.BlockRegionViewManager; +import javax.annotation.Nonnull; + +public class BlockTypeViewManager extends BlockRegionViewManager { + private final BlockPositionEntryGenerator generator = new BlockPositionEntryGenerator(); + + public BlockTypeViewManager() { + } + + @Nonnull + protected BlockTypeView createView(long index, Blackboard blackboard) { + return new BlockTypeView(index, blackboard, this.generator); + } + + protected boolean shouldCleanup(@Nonnull BlockTypeView view) { + return view.getEntities().isEmpty(); + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/combat/CombatViewSystems.java b/src/com/hypixel/hytale/server/npc/blackboard/view/combat/CombatViewSystems.java new file mode 100644 index 0000000..7b12469 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/combat/CombatViewSystems.java @@ -0,0 +1,226 @@ +package com.hypixel.hytale.server.npc.blackboard.view.combat; + +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.HolderSystem; +import com.hypixel.hytale.component.system.tick.EntityTickingSystem; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.modules.entity.AllLegacyEntityTypesQuery; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.ChargingInteraction; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.role.support.CombatSupport; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; + +public class CombatViewSystems { + public CombatViewSystems() { + } + + private static void clearCombatData(@Nonnull CombatViewSystems.CombatData combatData, @Nonnull CombatViewSystems.CombatDataPool dataPool) { + if (combatData.interpreted) { + List dataList = combatData.combatData; + + for (int i = 0; i < dataList.size(); i++) { + dataPool.releaseCombatData(dataList.get(i)); + } + + dataList.clear(); + combatData.interpreted = false; + } + } + + @Nonnull + public static List getCombatData(@Nonnull Ref reference) { + CombatViewSystems.CombatData combatData = reference.getStore().getComponent(reference, CombatViewSystems.CombatData.getComponentType()); + if (combatData.interpreted) { + return combatData.unmodifiableCombatData; + } else { + InteractionManager interactionManager = reference.getStore().getComponent(reference, InteractionModule.get().getInteractionManagerComponent()); + CombatViewSystems.CombatDataPool combatDataPool = reference.getStore().getResource(CombatViewSystems.CombatDataPool.getResourceType()); + List dataList = combatData.combatData; + IndexedLookupTableAssetMap interactionAssetMap = RootInteraction.getAssetMap(); + Set attackInteractions = interactionAssetMap.getKeysForTag(CombatSupport.ATTACK_TAG_INDEX); + Set meleeInteractions = interactionAssetMap.getKeysForTag(CombatSupport.MELEE_TAG_INDEX); + Set rangedInteractions = interactionAssetMap.getKeysForTag(CombatSupport.RANGED_TAG_INDEX); + Set blockInteractions = interactionAssetMap.getKeysForTag(CombatSupport.BLOCK_TAG_INDEX); + LivingEntity entity = (LivingEntity)EntityUtils.getEntity(reference, reference.getStore()); + interactionManager.forEachInteraction((chain, interaction, list) -> { + String rootId = chain.getRootInteraction().getId(); + if (!attackInteractions.contains(rootId)) { + return list; + } else { + InterpretedCombatData entry = combatDataPool.getEmptyCombatData(); + entry.setAttack(rootId); + entry.setCurrentElapsedTime(chain.getTimeInSeconds()); + entry.setCharging(interaction instanceof ChargingInteraction); + entry.setPerformingMeleeAttack(meleeInteractions.contains(rootId)); + entry.setPerformingRangedAttack(rangedInteractions.contains(rootId)); + entry.setPerformingBlock(blockInteractions.contains(rootId)); + list.add(entry); + return list; + } + }, dataList); + combatData.interpreted = true; + return combatData.unmodifiableCombatData; + } + } + + public static class CombatData implements Component { + private final List combatData = new ObjectArrayList<>(); + private final List unmodifiableCombatData = Collections.unmodifiableList(this.combatData); + private boolean interpreted; + + public CombatData() { + } + + public static ComponentType getComponentType() { + return NPCPlugin.get().getCombatDataComponentType(); + } + + @Nonnull + @Override + public Component clone() { + CombatViewSystems.CombatData data = new CombatViewSystems.CombatData(); + data.interpreted = this.interpreted; + + for (int i = 0; i < this.combatData.size(); i++) { + data.combatData.add(this.combatData.get(i).clone()); + } + + return data; + } + } + + public static class CombatDataPool implements Resource { + private final ArrayDeque combatDataPool = new ArrayDeque<>(); + + public CombatDataPool() { + } + + public static ResourceType getResourceType() { + return NPCPlugin.get().getCombatDataPoolResourceType(); + } + + @Nonnull + @Override + public Resource clone() { + return new CombatViewSystems.CombatDataPool(); + } + + public InterpretedCombatData getEmptyCombatData() { + return this.combatDataPool.isEmpty() ? new InterpretedCombatData() : this.combatDataPool.poll(); + } + + public void releaseCombatData(@Nonnull InterpretedCombatData combatData) { + this.combatDataPool.push(combatData); + } + } + + public static class Ensure extends HolderSystem { + private final ComponentType combatDataComponentType; + + public Ensure(ComponentType combatDataComponentType) { + this.combatDataComponentType = combatDataComponentType; + } + + @Nonnull + @Override + public Query getQuery() { + return AllLegacyEntityTypesQuery.INSTANCE; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + holder.ensureComponent(this.combatDataComponentType); + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + } + } + + public static class EntityRemoved extends HolderSystem { + private final ComponentType combatDataComponentType; + private final ResourceType dataPoolResourceType; + + public EntityRemoved( + ComponentType combatDataComponentType, + ResourceType dataPoolResourceType + ) { + this.combatDataComponentType = combatDataComponentType; + this.dataPoolResourceType = dataPoolResourceType; + } + + @Override + public Query getQuery() { + return this.combatDataComponentType; + } + + @Override + public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { + } + + @Override + public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { + CombatViewSystems.CombatData combatData = holder.getComponent(this.combatDataComponentType); + CombatViewSystems.CombatDataPool dataPool = store.getResource(this.dataPoolResourceType); + CombatViewSystems.clearCombatData(combatData, dataPool); + } + } + + public static class Ticking extends EntityTickingSystem { + private final ComponentType combatDataComponentType; + private final ResourceType dataPoolResourceType; + + public Ticking( + ComponentType combatDataComponentType, + ResourceType dataPoolResourceType + ) { + this.combatDataComponentType = combatDataComponentType; + this.dataPoolResourceType = dataPoolResourceType; + } + + @Override + public Query getQuery() { + return this.combatDataComponentType; + } + + @Override + public boolean isParallel(int archetypeChunkSize, int taskCount) { + return false; + } + + @Override + public void tick( + float dt, + int index, + @Nonnull ArchetypeChunk archetypeChunk, + @Nonnull Store store, + @Nonnull CommandBuffer commandBuffer + ) { + CombatViewSystems.CombatData combatData = archetypeChunk.getComponent(index, this.combatDataComponentType); + CombatViewSystems.CombatDataPool dataPool = store.getResource(this.dataPoolResourceType); + CombatViewSystems.clearCombatData(combatData, dataPool); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/combat/InterpretedCombatData.java b/src/com/hypixel/hytale/server/npc/blackboard/view/combat/InterpretedCombatData.java new file mode 100644 index 0000000..ffb7e19 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/combat/InterpretedCombatData.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.server.npc.blackboard.view.combat; + +import javax.annotation.Nonnull; + +public class InterpretedCombatData { + private String attack; + private boolean charging; + private float currentElapsedTime; + private boolean performingMeleeAttack; + private boolean performingRangedAttack; + private boolean performingBlock; + + public InterpretedCombatData() { + } + + public String getAttack() { + return this.attack; + } + + public void setAttack(String attack) { + this.attack = attack; + } + + public boolean isCharging() { + return this.charging; + } + + public void setCharging(boolean charging) { + this.charging = charging; + } + + public float getCurrentElapsedTime() { + return this.currentElapsedTime; + } + + public void setCurrentElapsedTime(float currentElapsedTime) { + this.currentElapsedTime = currentElapsedTime; + } + + public boolean isPerformingMeleeAttack() { + return this.performingMeleeAttack; + } + + public void setPerformingMeleeAttack(boolean performingMeleeAttack) { + this.performingMeleeAttack = performingMeleeAttack; + } + + public boolean isPerformingRangedAttack() { + return this.performingRangedAttack; + } + + public void setPerformingRangedAttack(boolean performingRangedAttack) { + this.performingRangedAttack = performingRangedAttack; + } + + public boolean isPerformingBlock() { + return this.performingBlock; + } + + public void setPerformingBlock(boolean performingBlock) { + this.performingBlock = performingBlock; + } + + @Nonnull + public InterpretedCombatData clone() { + InterpretedCombatData data = new InterpretedCombatData(); + data.attack = this.attack; + data.charging = this.charging; + data.currentElapsedTime = this.currentElapsedTime; + data.performingMeleeAttack = this.performingMeleeAttack; + data.performingRangedAttack = this.performingRangedAttack; + data.performingBlock = this.performingBlock; + return data; + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/event/EntityEventNotification.java b/src/com/hypixel/hytale/server/npc/blackboard/view/event/EntityEventNotification.java new file mode 100644 index 0000000..e9670cf --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/event/EntityEventNotification.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.server.npc.blackboard.view.event; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public class EntityEventNotification extends EventNotification { + private Ref flockReference; + + public EntityEventNotification() { + } + + public Ref getFlockReference() { + return this.flockReference; + } + + public void setFlockReference(Ref flockReference) { + this.flockReference = flockReference; + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/event/EventNotification.java b/src/com/hypixel/hytale/server/npc/blackboard/view/event/EventNotification.java new file mode 100644 index 0000000..d485c0b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/event/EventNotification.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.npc.blackboard.view.event; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EventNotification { + private final Vector3d position = new Vector3d(); + private Ref initiator; + private int set; + + public EventNotification() { + } + + @Nonnull + public Vector3d getPosition() { + return this.position; + } + + public void setPosition(double x, double y, double z) { + this.position.assign(x, y, z); + } + + public Ref getInitiator() { + return this.initiator; + } + + public void setInitiator(Ref initiator) { + this.initiator = initiator; + } + + public int getSet() { + return this.set; + } + + public void setSet(int set) { + this.set = set; + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/event/EventTypeRegistration.java b/src/com/hypixel/hytale/server/npc/blackboard/view/event/EventTypeRegistration.java new file mode 100644 index 0000000..89295b5 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/event/EventTypeRegistration.java @@ -0,0 +1,115 @@ +package com.hypixel.hytale.server.npc.blackboard.view.event; + +import com.hypixel.hytale.common.util.ListUtil; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.function.consumer.IntObjectConsumer; +import com.hypixel.hytale.function.predicate.BiIntPredicate; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntIterator; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.BitSet; +import java.util.List; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EventTypeRegistration, NotificationType extends EventNotification> { + @Nullable + private static final ComponentType NPC_COMPONENT_TYPE = NPCEntity.getComponentType(); + private final EventType type; + private final BitSet eventSets = new BitSet(); + private final Int2ObjectMap>> entitiesBySet = new Int2ObjectOpenHashMap<>(); + private final BiIntPredicate setTester; + private final IEventCallback eventCallback; + + public EventTypeRegistration(EventType type, BiIntPredicate setTester, IEventCallback eventCallback) { + this.type = type; + this.setTester = setTester; + this.eventCallback = eventCallback; + } + + public void initialiseEntity(Ref ref, @Nonnull IntSet changeSets) { + IntIterator it = changeSets.iterator(); + + while (it.hasNext()) { + int set = it.nextInt(); + this.eventSets.set(set); + this.entitiesBySet.computeIfAbsent(set, k -> new ObjectArrayList<>()).add(ref); + } + } + + public void relayEvent( + int senderTypeId, + @Nonnull NotificationType reusableEventNotification, + Ref skipEntityReference, + @Nonnull ComponentAccessor componentAccessor + ) { + Ref initiator = reusableEventNotification.getInitiator(); + + for (int set = this.eventSets.nextSetBit(0); set >= 0; set = this.eventSets.nextSetBit(set + 1)) { + if (this.setTester.test(set, senderTypeId)) { + List> entities = this.entitiesBySet.get(set); + if (entities != null) { + reusableEventNotification.setSet(set); + + for (int j = 0; j < entities.size(); j++) { + Ref entity = entities.get(j); + if (entity.isValid() && !entity.equals(initiator) && !entity.equals(skipEntityReference)) { + NPCEntity npc = componentAccessor.getComponent(entity, NPC_COMPONENT_TYPE); + this.eventCallback.notify(npc, this.type, reusableEventNotification); + } + } + } + } + + if (set == Integer.MAX_VALUE) { + break; + } + } + } + + public int getSetCount() { + return this.eventSets.cardinality(); + } + + public void forEach(@Nonnull IntObjectConsumer setConsumer, @Nonnull Consumer> npcConsumer) { + for (int set = this.eventSets.nextSetBit(0); set >= 0; set = this.eventSets.nextSetBit(set + 1)) { + setConsumer.accept(set, this.type); + List> entities = this.entitiesBySet.get(set); + if (entities != null) { + for (int i = 0; i < entities.size(); i++) { + Ref entity = entities.get(i); + if (entity.isValid()) { + npcConsumer.accept(entity); + } + } + + if (set == Integer.MAX_VALUE) { + break; + } + } + } + } + + public void cleanup() { + for (int set = this.eventSets.nextSetBit(0); set >= 0; set = this.eventSets.nextSetBit(set + 1)) { + List> entities = this.entitiesBySet.getOrDefault(set, null); + if (entities != null) { + ListUtil.removeIf(entities, r -> !r.isValid()); + if (entities.isEmpty()) { + this.eventSets.clear(set); + } + } + + if (set == Integer.MAX_VALUE) { + break; + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/event/EventView.java b/src/com/hypixel/hytale/server/npc/blackboard/view/event/EventView.java new file mode 100644 index 0000000..25820bf --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/event/EventView.java @@ -0,0 +1,99 @@ +package com.hypixel.hytale.server.npc.blackboard.view.event; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.event.EventRegistry; +import com.hypixel.hytale.function.consumer.IntObjectConsumer; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.blackboard.view.IBlackboardView; +import java.util.EnumMap; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class EventView, EventType extends Enum, NotificationType extends EventNotification> + implements IBlackboardView { + @Nonnull + protected final Map> entityMapsByEventType; + @Nonnull + protected final World world; + protected final EventType[] eventTypes; + @Nullable + protected EventRegistry eventRegistry; + @Nullable + protected ComponentRegistryProxy entityStoreRegistry; + protected boolean shutdown; + protected final NotificationType reusableEventNotification; + + protected EventView(Class type, EventType[] eventTypes, NotificationType reusableEventNotification, @Nonnull World world) { + this.entityMapsByEventType = new EnumMap<>(type); + this.eventTypes = eventTypes; + this.reusableEventNotification = reusableEventNotification; + this.world = world; + this.entityStoreRegistry = NPCPlugin.get().getEntityStoreRegistry(); + this.eventRegistry = new EventRegistry( + new CopyOnWriteArrayList<>(), + () -> !this.shutdown, + String.format("EventView for world %s is not enabled!", world.getName()), + NPCPlugin.get().getEventRegistry() + ); + } + + @Override + public boolean isOutdated(@Nonnull Ref ref, @Nonnull Store store) { + return false; + } + + @Override + public void onWorldRemoved() { + this.shutdown = true; + if (this.eventRegistry != null) { + this.eventRegistry.shutdown(); + this.eventRegistry = null; + } + } + + @Override + public void cleanup() { + for (EventType eventType : this.eventTypes) { + this.entityMapsByEventType.get(eventType).cleanup(); + } + } + + public int getSetCount() { + int count = 0; + + for (EventType type : this.eventTypes) { + count += this.entityMapsByEventType.get(type).getSetCount(); + } + + return count; + } + + public void forEach(@Nonnull IntObjectConsumer setConsumer, @Nonnull Consumer> npcConsumer) { + for (EventType eventType : this.eventTypes) { + this.entityMapsByEventType.get(eventType).forEach(setConsumer, npcConsumer); + } + } + + protected void onEvent( + int senderTypeId, + double x, + double y, + double z, + Ref initiator, + Ref skip, + @Nonnull ComponentAccessor componentAccessor, + EventType type + ) { + this.reusableEventNotification.setPosition(x, y, z); + this.reusableEventNotification.setInitiator(initiator); + this.entityMapsByEventType.get(type).relayEvent(senderTypeId, this.reusableEventNotification, skip, componentAccessor); + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/event/IEventCallback.java b/src/com/hypixel/hytale/server/npc/blackboard/view/event/IEventCallback.java new file mode 100644 index 0000000..fedda5d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/event/IEventCallback.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.server.npc.blackboard.view.event; + +import com.hypixel.hytale.server.npc.entities.NPCEntity; + +@FunctionalInterface +public interface IEventCallback { + void notify(NPCEntity var1, EventType var2, NotificationType var3); +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/event/block/BlockEventType.java b/src/com/hypixel/hytale/server/npc/blackboard/view/event/block/BlockEventType.java new file mode 100644 index 0000000..4b3a73e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/event/block/BlockEventType.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.server.npc.blackboard.view.event.block; + +import java.util.function.Supplier; + +public enum BlockEventType implements Supplier { + DAMAGE("On block damage"), + DESTRUCTION("On block destruction"), + INTERACTION("On block use interaction"); + + public static final BlockEventType[] VALUES = values(); + private final String description; + + private BlockEventType(String description) { + this.description = description; + } + + public String get() { + return this.description; + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/event/block/BlockEventView.java b/src/com/hypixel/hytale/server/npc/blackboard/view/event/block/BlockEventView.java new file mode 100644 index 0000000..41b4d69 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/event/block/BlockEventView.java @@ -0,0 +1,125 @@ +package com.hypixel.hytale.server.npc.blackboard.view.event.block; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.event.events.ecs.BreakBlockEvent; +import com.hypixel.hytale.server.core.event.events.ecs.DamageBlockEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerInteractEvent; +import com.hypixel.hytale.server.core.modules.blockset.BlockSetModule; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.blackboard.Blackboard; +import com.hypixel.hytale.server.npc.blackboard.view.event.EventNotification; +import com.hypixel.hytale.server.npc.blackboard.view.event.EventTypeRegistration; +import com.hypixel.hytale.server.npc.blackboard.view.event.EventView; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import it.unimi.dsi.fastutil.ints.IntSet; +import javax.annotation.Nonnull; + +public class BlockEventView extends EventView { + public BlockEventView(@Nonnull World world) { + super(BlockEventType.class, BlockEventType.VALUES, new EventNotification(), world); + this.eventRegistry.register(PlayerInteractEvent.class, world.getName(), this::onPlayerInteraction); + + for (BlockEventType eventType : BlockEventType.VALUES) { + this.entityMapsByEventType + .put( + eventType, + new EventTypeRegistration<>(eventType, (set, blockId) -> BlockSetModule.getInstance().blockInSet(set, blockId), NPCEntity::notifyBlockChange) + ); + } + } + + public BlockEventView getUpdatedView(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + World entityWorld = componentAccessor.getExternalData().getWorld(); + if (!entityWorld.equals(this.world)) { + Blackboard blackboardResource = componentAccessor.getResource(Blackboard.getResourceType()); + return blackboardResource.getView(BlockEventView.class, ref, componentAccessor); + } else { + return this; + } + } + + @Override + public void initialiseEntity(@Nonnull Ref ref, @Nonnull NPCEntity npcComponent) { + for (int i = 0; i < BlockEventType.VALUES.length; i++) { + BlockEventType type = BlockEventType.VALUES[i]; + IntSet eventSets = npcComponent.getBlackboardBlockChangeSet(type); + if (eventSets != null) { + this.entityMapsByEventType.get(type).initialiseEntity(npcComponent.getReference(), eventSets); + } + } + } + + protected void onEvent( + int senderTypeId, + double x, + double y, + double z, + Ref initiator, + Ref skip, + @Nonnull ComponentAccessor componentAccessor, + BlockEventType type + ) { + super.onEvent(senderTypeId, x + 0.5, y + 0.5, z + 0.5, initiator, skip, componentAccessor, type); + } + + public void onEntityDamageBlock(@Nonnull Ref ref, @Nonnull DamageBlockEvent event) { + if (!event.isCancelled()) { + this.processDamagedBlock(ref, event.getBlockType().getId(), event.getTargetBlock(), BlockEventType.DAMAGE); + } + } + + public void onEntityBreakBlock(@Nonnull Ref ref, @Nonnull BreakBlockEvent event) { + if (!event.isCancelled()) { + this.processDamagedBlock(ref, event.getBlockType().getId(), event.getTargetBlock(), BlockEventType.DESTRUCTION); + } + } + + private void processDamagedBlock(@Nonnull Ref initiatorRef, String block, @Nonnull Vector3i position, @Nonnull BlockEventType type) { + Store store = initiatorRef.getStore(); + Player playerComponent = store.getComponent(initiatorRef, Player.getComponentType()); + if (playerComponent != null && playerComponent.getGameMode() == GameMode.Creative) { + PlayerSettings playerSettingsComponent = store.getComponent(initiatorRef, PlayerSettings.getComponentType()); + if (playerSettingsComponent == null || !playerSettingsComponent.creativeSettings().allowNPCDetection()) { + return; + } + } + + int blockId = BlockType.getAssetMap().getIndex(block); + if (blockId == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + block); + } else { + this.onEvent(blockId, (double)position.x, (double)position.y, (double)position.z, initiatorRef, null, store, type); + } + } + + private void onPlayerInteraction(@Nonnull PlayerInteractEvent event) { + if (!event.isCancelled()) { + Player playerComponent = event.getPlayer(); + Ref ref = event.getPlayerRef(); + Store store = ref.getStore(); + if (playerComponent.getGameMode() == GameMode.Creative) { + PlayerSettings playerSettingsComponent = store.getComponent(ref, PlayerSettings.getComponentType()); + if (playerSettingsComponent == null || !playerSettingsComponent.creativeSettings().allowNPCDetection()) { + return; + } + } + + Vector3i blockPosition = event.getTargetBlock(); + if (blockPosition != null && event.getActionType() == InteractionType.Use) { + World world = store.getExternalData().getWorld(); + int blockId = world.getBlock(blockPosition.x, blockPosition.y, blockPosition.z); + Vector3i targetBlock = event.getTargetBlock(); + this.onEvent(blockId, (double)targetBlock.x, (double)targetBlock.y, (double)targetBlock.z, ref, null, store, BlockEventType.INTERACTION); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/event/entity/EntityEventType.java b/src/com/hypixel/hytale/server/npc/blackboard/view/event/entity/EntityEventType.java new file mode 100644 index 0000000..51b5528 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/event/entity/EntityEventType.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.server.npc.blackboard.view.event.entity; + +import java.util.function.Supplier; + +public enum EntityEventType implements Supplier { + DAMAGE("On taking damage"), + DEATH("On dying"), + INTERACTION("On use interaction"); + + public static final EntityEventType[] VALUES = values(); + private final String description; + + private EntityEventType(String description) { + this.description = description; + } + + public String get() { + return this.description; + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/event/entity/EntityEventView.java b/src/com/hypixel/hytale/server/npc/blackboard/view/event/entity/EntityEventView.java new file mode 100644 index 0000000..9314714 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/event/entity/EntityEventView.java @@ -0,0 +1,128 @@ +package com.hypixel.hytale.server.npc.blackboard.view.event.entity; + +import com.hypixel.hytale.builtin.tagset.TagSetPlugin; +import com.hypixel.hytale.builtin.tagset.config.NPCGroup; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.event.events.player.PlayerInteractEvent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.FlockMembership; +import com.hypixel.hytale.server.npc.asset.builder.BuilderManager; +import com.hypixel.hytale.server.npc.blackboard.Blackboard; +import com.hypixel.hytale.server.npc.blackboard.view.event.EntityEventNotification; +import com.hypixel.hytale.server.npc.blackboard.view.event.EventTypeRegistration; +import com.hypixel.hytale.server.npc.blackboard.view.event.EventView; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import it.unimi.dsi.fastutil.ints.IntSet; +import javax.annotation.Nonnull; + +public class EntityEventView extends EventView { + public EntityEventView(@Nonnull World world) { + super(EntityEventType.class, EntityEventType.VALUES, new EntityEventNotification(), world); + this.eventRegistry.register(PlayerInteractEvent.class, world.getName(), this::onPlayerInteraction); + + for (EntityEventType eventType : EntityEventType.VALUES) { + this.entityMapsByEventType + .put( + eventType, + new EventTypeRegistration<>( + eventType, (set, roleIndex) -> TagSetPlugin.get(NPCGroup.class).tagInSet(set, roleIndex), NPCEntity::notifyEntityEvent + ) + ); + } + } + + public EntityEventView getUpdatedView(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + World entityWorld = componentAccessor.getExternalData().getWorld(); + if (!entityWorld.equals(this.world)) { + Blackboard blackboardResource = componentAccessor.getResource(Blackboard.getResourceType()); + return blackboardResource.getView(EntityEventView.class, ref, componentAccessor); + } else { + return this; + } + } + + @Override + public void initialiseEntity(@Nonnull Ref ref, @Nonnull NPCEntity npcComponent) { + for (int i = 0; i < EntityEventType.VALUES.length; i++) { + EntityEventType type = EntityEventType.VALUES[i]; + IntSet eventSets = npcComponent.getBlackboardEntityEventSet(type); + if (eventSets != null) { + this.entityMapsByEventType.get(type).initialiseEntity(ref, eventSets); + } + } + } + + protected void onEvent( + int senderTypeId, + double x, + double y, + double z, + Ref initiator, + @Nonnull Ref skip, + @Nonnull ComponentAccessor componentAccessor, + EntityEventType type + ) { + FlockMembership membership = componentAccessor.getComponent(skip, FlockMembership.getComponentType()); + Ref flockReference = membership != null ? membership.getFlockRef() : null; + this.reusableEventNotification.setFlockReference(flockReference); + super.onEvent(senderTypeId, x, y, z, initiator, skip, componentAccessor, type); + } + + private void onPlayerInteraction(@Nonnull PlayerInteractEvent event) { + Player playerComponent = event.getPlayer(); + Ref playerRef = playerComponent.getReference(); + Store store = playerRef.getStore(); + if (!event.isCancelled()) { + if (playerComponent.getGameMode() == GameMode.Creative) { + PlayerSettings playerSettingsComponent = store.getComponent(playerRef, PlayerSettings.getComponentType()); + if (playerSettingsComponent == null || !playerSettingsComponent.creativeSettings().allowNPCDetection()) { + return; + } + } + + Entity entity = event.getTargetEntity(); + if (entity != null && event.getActionType() == InteractionType.Use && entity instanceof NPCEntity) { + Ref entityRef = event.getTargetRef(); + TransformComponent transformComponent = store.getComponent(entityRef, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d pos = transformComponent.getPosition(); + this.onEvent(((NPCEntity)entity).getRoleIndex(), pos.x, pos.y, pos.z, playerRef, entityRef, store, EntityEventType.INTERACTION); + } + } + } + + public void processAttackedEvent( + @Nonnull Ref victim, + @Nonnull Ref attacker, + @Nonnull ComponentAccessor componentAccessor, + EntityEventType eventType + ) { + int roleIndex; + if (componentAccessor.getArchetype(victim).contains(Player.getComponentType())) { + roleIndex = BuilderManager.getPlayerGroupID(); + } else { + NPCEntity npc = componentAccessor.getComponent(victim, NPCEntity.getComponentType()); + if (npc == null) { + return; + } + + roleIndex = npc.getRoleIndex(); + } + + Store store = victim.getStore(); + Vector3d pos = store.getComponent(victim, TransformComponent.getComponentType()).getPosition(); + this.onEvent(roleIndex, pos.x, pos.y, pos.z, attacker, attacker, componentAccessor, eventType); + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/interaction/InteractionView.java b/src/com/hypixel/hytale/server/npc/blackboard/view/interaction/InteractionView.java new file mode 100644 index 0000000..d404250 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/interaction/InteractionView.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.server.npc.blackboard.view.interaction; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.blackboard.Blackboard; +import com.hypixel.hytale.server.npc.blackboard.view.PrioritisedProviderView; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import java.util.UUID; +import javax.annotation.Nonnull; + +public class InteractionView extends PrioritisedProviderView { + private final World world; + + public InteractionView(World world) { + this.world = world; + this.registerProvider(Integer.MAX_VALUE, (npcRef, playerRef, componentAccessor) -> { + NPCEntity npcComponent = componentAccessor.getComponent(npcRef, NPCEntity.getComponentType()); + + assert npcComponent != null; + + if (!npcComponent.isReserved()) { + return ReservationStatus.NOT_RESERVED; + } else { + UUIDComponent playerUUIDComponent = componentAccessor.getComponent(playerRef, UUIDComponent.getComponentType()); + + assert playerUUIDComponent != null; + + UUID playerUUID = playerUUIDComponent.getUuid(); + return npcComponent.isReservedBy(playerUUID) ? ReservationStatus.RESERVED_THIS : ReservationStatus.RESERVED_OTHER; + } + }); + } + + @Override + public boolean isOutdated(@Nonnull Ref ref, @Nonnull Store store) { + return false; + } + + public InteractionView getUpdatedView(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + World entityWorld = componentAccessor.getExternalData().getWorld(); + if (!entityWorld.equals(this.world)) { + Blackboard blackboardResource = componentAccessor.getResource(Blackboard.getResourceType()); + return blackboardResource.getView(InteractionView.class, ref, componentAccessor); + } else { + return this; + } + } + + @Override + public void initialiseEntity(@Nonnull Ref ref, @Nonnull NPCEntity npcComponent) { + } + + @Override + public void cleanup() { + } + + @Override + public void onWorldRemoved() { + } + + @Nonnull + public ReservationStatus getReservationStatus( + @Nonnull Ref npcRef, @Nonnull Ref playerRef, @Nonnull ComponentAccessor componentAccessor + ) { + for (int i = 0; i < this.providers.size(); i++) { + ReservationStatus status = this.providers.get(i).getProvider().getReservationStatus(npcRef, playerRef, componentAccessor); + if (status != ReservationStatus.NOT_RESERVED) { + return status; + } + } + + return ReservationStatus.NOT_RESERVED; + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/interaction/ReservationProvider.java b/src/com/hypixel/hytale/server/npc/blackboard/view/interaction/ReservationProvider.java new file mode 100644 index 0000000..acefe56 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/interaction/ReservationProvider.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.npc.blackboard.view.interaction; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public interface ReservationProvider { + @Nonnull + ReservationStatus getReservationStatus(@Nonnull Ref var1, @Nonnull Ref var2, @Nonnull ComponentAccessor var3); +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/interaction/ReservationStatus.java b/src/com/hypixel/hytale/server/npc/blackboard/view/interaction/ReservationStatus.java new file mode 100644 index 0000000..541295f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/interaction/ReservationStatus.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.npc.blackboard.view.interaction; + +public enum ReservationStatus { + NOT_RESERVED, + RESERVED_OTHER, + RESERVED_THIS; + + private ReservationStatus() { + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/resource/ResourceView.java b/src/com/hypixel/hytale/server/npc/blackboard/view/resource/ResourceView.java new file mode 100644 index 0000000..a3b12b2 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/resource/ResourceView.java @@ -0,0 +1,98 @@ +package com.hypixel.hytale.server.npc.blackboard.view.resource; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.blackboard.view.BlockRegionView; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class ResourceView extends BlockRegionView { + private final long index; + private final IntSet[] reservationsBySection = new IntSet[10]; + private final Map, ResourceView.BlockReservation> reservationsByEntity = new HashMap<>(); + + public ResourceView(long index) { + this.index = index; + } + + @Override + public boolean isOutdated(@Nonnull Ref ref, @Nonnull Store store) { + return false; + } + + @Nonnull + public ResourceView getUpdatedView(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + return this; + } + + @Override + public void initialiseEntity(@Nonnull Ref ref, @Nonnull NPCEntity npcComponent) { + } + + @Override + public void cleanup() { + } + + @Override + public void onWorldRemoved() { + } + + public boolean isBlockReserved(int x, int y, int z) { + IntSet section = this.reservationsBySection[indexSection(y)]; + return section == null ? false : section.contains(indexBlock(x, y, z)); + } + + public void reserveBlock(@Nonnull NPCEntity entity, int x, int y, int z) { + int sectionIndex = indexSection(y); + IntSet section = this.reservationsBySection[sectionIndex]; + if (section == null) { + section = new IntOpenHashSet(); + this.reservationsBySection[sectionIndex] = section; + } + + int blockIndex = indexBlock(x, y, z); + section.add(blockIndex); + this.reservationsByEntity.put(entity.getReference(), new ResourceView.BlockReservation(sectionIndex, blockIndex)); + } + + public void clearReservation(@Nonnull Ref ref) { + ResourceView.BlockReservation reservation = this.reservationsByEntity.remove(ref); + if (reservation != null) { + IntSet section = this.reservationsBySection[reservation.getSectionIndex()]; + section.remove(reservation.getBlockIndex()); + } + } + + public long getIndex() { + return this.index; + } + + @Nonnull + public Map, ResourceView.BlockReservation> getReservationsByEntity() { + return this.reservationsByEntity; + } + + public static class BlockReservation { + private final int sectionIndex; + private final int blockIndex; + + public BlockReservation(int sectionIndex, int blockIndex) { + this.sectionIndex = sectionIndex; + this.blockIndex = blockIndex; + } + + public int getSectionIndex() { + return this.sectionIndex; + } + + public int getBlockIndex() { + return this.blockIndex; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/blackboard/view/resource/ResourceViewManager.java b/src/com/hypixel/hytale/server/npc/blackboard/view/resource/ResourceViewManager.java new file mode 100644 index 0000000..c16f74a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/blackboard/view/resource/ResourceViewManager.java @@ -0,0 +1,19 @@ +package com.hypixel.hytale.server.npc.blackboard.view.resource; + +import com.hypixel.hytale.server.npc.blackboard.Blackboard; +import com.hypixel.hytale.server.npc.blackboard.view.BlockRegionViewManager; +import javax.annotation.Nonnull; + +public class ResourceViewManager extends BlockRegionViewManager { + public ResourceViewManager() { + } + + @Nonnull + protected ResourceView createView(long index, Blackboard blackboard) { + return new ResourceView(index); + } + + protected boolean shouldCleanup(@Nonnull ResourceView view) { + return view.getReservationsByEntity().isEmpty(); + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCAllCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCAllCommand.java new file mode 100644 index 0000000..295590b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCAllCommand.java @@ -0,0 +1,91 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.Frozen; +import com.hypixel.hytale.server.core.entity.nameplate.Nameplate; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.util.NPCPhysicsMath; +import it.unimi.dsi.fastutil.Pair; +import java.util.List; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class NPCAllCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_NPC_ALL_NO_ROLES_TO_SPAWN = Message.translation("server.commands.npc.all.noRolesToSpawn"); + @Nonnull + private final OptionalArg distanceArg = this.withOptionalArg("distance", "server.commands.npc.all.distance", ArgTypes.DOUBLE) + .addValidator(Validators.greaterThan(0.0)); + + public NPCAllCommand() { + super("all", "server.commands.npc.all.desc", true); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + double distance = this.distanceArg.provided(context) ? this.distanceArg.get(context) : 4.0; + NPCPlugin npcModule = NPCPlugin.get(); + List roles = npcModule.getRoleTemplateNames(true); + if (roles.isEmpty()) { + playerRef.sendMessage(MESSAGE_COMMANDS_NPC_ALL_NO_ROLES_TO_SPAWN); + } else { + roles.sort(String::compareToIgnoreCase); + int columns = MathUtil.ceil(Math.sqrt(roles.size())); + double squareSideLength = (columns - 1) * distance; + double squareSideLengthHalf = squareSideLength / 2.0; + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + double px = position.getX() - squareSideLengthHalf; + double pz = position.getZ() - squareSideLengthHalf; + Vector3d pos = new Vector3d(); + + for (int index = 0; index < roles.size(); index++) { + String name = roles.get(index); + if (name != null && !name.isEmpty()) { + try { + double x = px + distance * (index % columns); + double z = pz + distance * (index / columns); + double y = NPCPhysicsMath.heightOverGround(world, x, z); + if (!(y < 0.0)) { + pos.assign(x, y, z); + int roleIndex = npcModule.getIndex(name); + if (roleIndex < 0) { + throw new IllegalStateException("No such valid role: " + name); + } + + Pair, NPCEntity> npcPair = npcModule.spawnEntity(store, roleIndex, pos, null, null, null); + Ref npcRef = npcPair.first(); + + assert npcRef != null; + + store.putComponent(npcRef, Nameplate.getComponentType(), new Nameplate(name)); + store.ensureComponent(npcRef, Frozen.getComponentType()); + } + } catch (Throwable var33) { + playerRef.sendMessage(Message.translation("server.commands.npc.all.failedToSpawn").param("role", name)); + npcModule.getLogger().at(Level.WARNING).log("Error spawning NPC with role: %s", name, var33); + } + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCAppearanceCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCAppearanceCommand.java new file mode 100644 index 0000000..8751f39 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCAppearanceCommand.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import javax.annotation.Nonnull; + +public class NPCAppearanceCommand extends NPCWorldCommandBase { + @Nonnull + private final RequiredArg modelArg = this.withRequiredArg("model", "server.commands.npc.appearance.model.desc", ArgTypes.MODEL_ASSET); + + public NPCAppearanceCommand() { + super("appearance", "server.commands.npc.appearance.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull NPCEntity npc, @Nonnull World world, @Nonnull Store store, @Nonnull Ref ref + ) { + ModelAsset model = this.modelArg.get(context); + npc.setAppearance(ref, model, store); + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCAttackCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCAttackCommand.java new file mode 100644 index 0000000..5cfcb40 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCAttackCommand.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.support.CombatSupport; +import java.util.List; +import javax.annotation.Nonnull; + +public class NPCAttackCommand extends AbstractCommandCollection { + public NPCAttackCommand() { + super("attack", "server.commands.npc.attack.desc"); + this.addSubCommand(new NPCAttackCommand.SetAttackCommand()); + this.addSubCommand(new NPCAttackCommand.ClearAttackCommand()); + } + + public static class ClearAttackCommand extends NPCWorldCommandBase { + public ClearAttackCommand() { + super("clear", "server.commands.npc.attack.clear.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull NPCEntity npc, @Nonnull World world, @Nonnull Store store, @Nonnull Ref ref + ) { + npc.getRole().getCombatSupport().clearAttackOverrides(); + } + } + + public static class SetAttackCommand extends NPCWorldCommandBase { + @Nonnull + private final OptionalArg> attackArg = this.withListOptionalArg( + "attack", "server.commands.npc.attack.sequence", ArgTypes.INTERACTION_ASSET + ); + + public SetAttackCommand() { + super("", "server.commands.npc.attack.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull NPCEntity npc, @Nonnull World world, @Nonnull Store store, @Nonnull Ref ref + ) { + if (this.attackArg.provided(context)) { + List sequences = this.attackArg.get(context); + CombatSupport combatSupport = npc.getRole().getCombatSupport(); + combatSupport.clearAttackOverrides(); + + for (Interaction sequence : sequences) { + combatSupport.addAttackOverride(sequence.getId()); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCBenchmarkCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCBenchmarkCommand.java new file mode 100644 index 0000000..57f0fbe --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCBenchmarkCommand.java @@ -0,0 +1,137 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.benchmark.TimeDistributionRecorder; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.util.SensorSupportBenchmark; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import java.util.Formatter; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class NPCBenchmarkCommand extends CommandBase { + @Nonnull + private static final Message MESSAGE_COMMANDS_NPC_BENCHMARK_START_FAILED = Message.translation("server.commands.npc.benchmark.startFailed"); + @Nonnull + private static final Message MESSAGE_COMMANDS_NPC_BENCHMARK_DONE = Message.translation("server.commands.npc.benchmark.done"); + @Nonnull + private final FlagArg roleArg = this.withFlagArg("roles", "server.commands.npc.benchmark.role.desc"); + @Nonnull + private final FlagArg sensorSupportArg = this.withFlagArg("sensorsupport", "server.commands.npc.benchmark.sensor.desc"); + @Nonnull + private final OptionalArg secondsArg = this.withOptionalArg("seconds", "server.commands.npc.benchmark.role.seconds", ArgTypes.DOUBLE) + .addValidator(Validators.greaterThan(0.0)); + + public NPCBenchmarkCommand() { + super("benchmark", "server.commands.npc.benchmark.desc"); + } + + @Override + protected void executeSync(@Nonnull CommandContext context) { + double seconds = this.secondsArg.provided(context) ? this.secondsArg.get(context) : 30.0; + boolean success; + if (this.roleArg.get(context)) { + success = NPCPlugin.get() + .startRoleBenchmark( + seconds, + distribution -> { + StringBuilder sb = new StringBuilder().append("Role benchmark seconds=").append(seconds).append('\n'); + Formatter formatter = new Formatter(sb); + if (!distribution.isEmpty()) { + TimeDistributionRecorder recorder = distribution.get(-1); + recorder.formatHeader(formatter); + sb.append('\n'); + IntArrayList sortedIndices = new IntArrayList(distribution.keySet()); + sortedIndices.rem(-1); + sortedIndices.sort( + (o1, o2) -> Double.compare( + ((TimeDistributionRecorder)distribution.get(o1)).getAverage(), ((TimeDistributionRecorder)distribution.get(o2)).getAverage() + ) + ); + + for (int i = 0; i < sortedIndices.size(); i++) { + int role = sortedIndices.getInt(i); + logRoleDistribution(distribution.get(role), sb, formatter, NPCPlugin.get().getName(role)); + } + + logRoleDistribution(distribution.get(-1), sb, formatter, "ALL"); + } + + context.sendMessage(MESSAGE_COMMANDS_NPC_BENCHMARK_DONE); + NPCPlugin.get().getLogger().at(Level.INFO).log(sb.toString()); + } + ); + } else if (this.sensorSupportArg.get(context)) { + success = NPCPlugin.get().startSensorSupportBenchmark(seconds, sensorSupportData -> { + StringBuilder sb = new StringBuilder().append("PositionCache benchmark seconds=").append(seconds).append('\n'); + Formatter formatter = new Formatter(sb); + if (!sensorSupportData.isEmpty()) { + IntArrayList sortedIndices = new IntArrayList(sensorSupportData.keySet()); + sortedIndices.rem(-1); + sortedIndices.sort((o1, o2) -> NPCPlugin.get().getName(o1).compareToIgnoreCase(NPCPlugin.get().getName(o2))); + SensorSupportBenchmark data = sensorSupportData.get(-1); + sb.append("PositionCache Update Times\n"); + data.formatHeaderUpdateTimes(formatter); + sb.append('\n'); + + for (int i = 0; i < sortedIndices.size(); i++) { + int role = sortedIndices.getInt(i); + SensorSupportBenchmark bm = sensorSupportData.get(role); + if (bm.haveUpdateTimes()) { + logSensorSupportUpdateTime(bm, sb, formatter, NPCPlugin.get().getName(role)); + } + } + + logSensorSupportUpdateTime(sensorSupportData.get(-1), sb, formatter, "ALL"); + sb.append("PositionCache Line of sight\n"); + data.formatHeaderLoS(formatter); + sb.append('\n'); + + for (int ix = 0; ix < sortedIndices.size(); ix++) { + int role = sortedIndices.getInt(ix); + logSensorSupportLoS(sensorSupportData.get(role), sb, formatter, NPCPlugin.get().getName(role)); + } + + logSensorSupportLoS(sensorSupportData.get(-1), sb, formatter, "ALL"); + } + + context.sendMessage(MESSAGE_COMMANDS_NPC_BENCHMARK_DONE); + NPCPlugin.get().getLogger().at(Level.INFO).log(sb.toString()); + }); + } else { + success = false; + } + + if (success) { + context.sendMessage(Message.translation("server.commands.npc.benchmark.startedFor").param("seconds", seconds)); + } else { + context.sendMessage(MESSAGE_COMMANDS_NPC_BENCHMARK_START_FAILED); + } + } + + private static void logRoleDistribution(@Nonnull TimeDistributionRecorder rec, @Nonnull StringBuilder sb, @Nonnull Formatter formatter, @Nonnull String name) { + rec.formatValues(formatter, 10000L); + sb.append("|").append(name).append('\n'); + } + + private static void logSensorSupportUpdateTime( + @Nonnull SensorSupportBenchmark bm, @Nonnull StringBuilder sb, @Nonnull Formatter formatter, @Nonnull String name + ) { + bm.formatValuesUpdateTimePlayer(formatter); + sb.append('|').append(name).append('\n'); + bm.formatValuesUpdateTimeEntity(formatter); + sb.append('|').append(name).append('\n'); + } + + private static void logSensorSupportLoS(@Nonnull SensorSupportBenchmark bm, @Nonnull StringBuilder sb, @Nonnull Formatter formatter, @Nonnull String name) { + if (bm.formatValuesLoS(formatter)) { + sb.append('|').append(name).append('\n'); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCBlackboardCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCBlackboardCommand.java new file mode 100644 index 0000000..9d367f3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCBlackboardCommand.java @@ -0,0 +1,693 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.builtin.tagset.config.NPCGroup; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.AndQuery; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector2i; +import com.hypixel.hytale.math.vector.Vector3i; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeChunkPosition; +import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeIntPosition; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.blockpositions.BlockPositionProvider; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.blackboard.Blackboard; +import com.hypixel.hytale.server.npc.blackboard.view.BlockRegionView; +import com.hypixel.hytale.server.npc.blackboard.view.blocktype.BlockTypeView; +import com.hypixel.hytale.server.npc.blackboard.view.event.block.BlockEventView; +import com.hypixel.hytale.server.npc.blackboard.view.event.entity.EntityEventView; +import com.hypixel.hytale.server.npc.blackboard.view.interaction.InteractionView; +import com.hypixel.hytale.server.npc.blackboard.view.interaction.ReservationStatus; +import com.hypixel.hytale.server.npc.blackboard.view.resource.ResourceView; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.IntList; +import java.util.BitSet; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NPCBlackboardCommand extends AbstractCommandCollection { + public NPCBlackboardCommand() { + super("blackboard", "server.commands.npc.blackboard.desc"); + this.addSubCommand(new NPCBlackboardCommand.ChunksCommand()); + this.addSubCommand(new NPCBlackboardCommand.ChunkCommand()); + this.addSubCommand(new NPCBlackboardCommand.DropCommand()); + this.addSubCommand(new NPCBlackboardCommand.ViewsCommand()); + this.addSubCommand(new NPCBlackboardCommand.ViewCommand()); + this.addSubCommand(new NPCBlackboardCommand.BlockEventsCommand()); + this.addSubCommand(new NPCBlackboardCommand.EntityEventsCommand()); + this.addSubCommand(new NPCBlackboardCommand.ResourceViewsCommand()); + this.addSubCommand(new NPCBlackboardCommand.ResourceViewCommand()); + this.addSubCommand(new NPCBlackboardCommand.ReserveCommand()); + this.addSubCommand(new NPCBlackboardCommand.ReservationCommand()); + } + + public static class BlockEventsCommand extends AbstractWorldCommand { + public BlockEventsCommand() { + super("blockevents", "server.commands.npc.blackboard.blockevents.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + BlockEventView blockEventView = store.getResource(Blackboard.getResourceType()).getView(BlockEventView.class, 0L); + StringBuilder sb = new StringBuilder("Block Event View:\n"); + sb.append(" Total BlockSets: ").append(blockEventView.getSetCount()); + sb.append("\n BlockSets:\n"); + Message msg = Message.translation("server.commands.npc.blackboard.blockevents.title").param("count", blockEventView.getSetCount()); + blockEventView.forEach((b, t) -> { + sb.append(" ").append(BlockSet.getAssetMap().getAsset(b).getId()).append(" (").append(t.get()).append("):\n"); + msg.insert(" " + BlockSet.getAssetMap().getAsset(b).getId() + " (" + t.get() + "):\n"); + }, e -> { + UUIDComponent uuidComponent = store.getComponent(e, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID uuid = uuidComponent.getUuid(); + NPCEntity npcComponent = store.getComponent(e, NPCEntity.getComponentType()); + + assert npcComponent != null; + + String roleName = npcComponent.getRoleName(); + sb.append(" ").append(uuid).append(": ").append(roleName).append("\n"); + msg.insert(" " + uuid + ": " + roleName + "\n"); + }); + context.sendMessage(msg); + NPCPlugin.get().getLogger().at(Level.INFO).log(sb.toString()); + } + } + + public static class ChunkCommand extends AbstractWorldCommand { + @Nonnull + private final RequiredArg positionArg = this.withRequiredArg( + "position", "server.commands.npc.blackboard.chunk.position.desc", ArgTypes.RELATIVE_BLOCK_POSITION + ); + + public ChunkCommand() { + super("chunk", "server.commands.npc.blackboard.chunk.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Vector3i blockPosition = this.positionArg.get(context).getBlockPosition(context, store); + Vector3i position = new Vector3i( + ChunkUtil.chunkCoordinate(blockPosition.x), ChunkUtil.chunkCoordinate(blockPosition.y), ChunkUtil.chunkCoordinate(blockPosition.z) + ); + long chunkIndex = ChunkUtil.indexChunk(position.x, position.z); + StringBuilder sb = new StringBuilder("Blackboard chunk entry " + chunkIndex); + sb.append(" (").append(position.x).append(", ").append(position.y).append(", ").append(position.z).append("):\n"); + sb.append(" Partial blackboard grid coordinates: "); + sb.append(BlockRegionView.chunkToRegionalBlackboardCoordinate(position.x)).append(", "); + sb.append(BlockRegionView.chunkToRegionalBlackboardCoordinate(position.z)).append('\n'); + Message msg = Message.translation("server.commands.npc.blackboard.chunk.entry") + .param("index", chunkIndex) + .param("chunkX", position.x) + .param("chunkY", position.y) + .param("chunkZ", position.z) + .param("regionChunkX", BlockRegionView.chunkToRegionalBlackboardCoordinate(position.x)) + .param("regionChunkZ", BlockRegionView.chunkToRegionalBlackboardCoordinate(position.z)) + .insert("\n"); + ChunkStore chunkStore = world.getChunkStore(); + Store chunkStoreStore = chunkStore.getStore(); + Ref chunkSection = chunkStore.getChunkSectionReference(position.x, position.y, position.z); + if (chunkSection == null) { + sb.append(" Chunk not loaded"); + msg.insert(Message.translation("server.commands.npc.blackboard.chunk.notLoaded")); + } else { + BlockPositionProvider entry = chunkStoreStore.getComponent(chunkSection, BlockPositionProvider.getComponentType()); + if (entry == null) { + sb.append(" No entry exists"); + msg.insert(Message.translation("server.commands.npc.blackboard.chunk.noEntry")); + } else { + sb.append(" Searched BlockSets: [ "); + msg.insert(Message.translation("server.commands.npc.blockSetsSearched")); + BitSet searchedBlockSets = entry.getSearchedBlockSets(); + boolean subsequent = false; + + for (int i = searchedBlockSets.nextSetBit(0); i >= 0; i = searchedBlockSets.nextSetBit(i + 1)) { + if (subsequent) { + sb.append(", "); + msg.insert(", "); + } + + sb.append(BlockSet.getAssetMap().getAsset(i).getId()); + msg.insert(BlockSet.getAssetMap().getAsset(i).getId()); + subsequent = true; + if (i == Integer.MAX_VALUE) { + break; + } + } + + sb.append(" ]\n Entries:\n"); + msg.insert(Message.translation("server.commands.npc.blackboard.chunk.entries")); + entry.forEachBlockSet( + (blockSet, list) -> { + sb.append(" BlockSet: ").append(BlockSet.getAssetMap().getAsset(blockSet).getId()).append("\n Blocks:\n"); + msg.insert( + Message.translation("server.commands.npc.blackboard.chunk.blockSet").param("id", BlockSet.getAssetMap().getAsset(blockSet).getId()) + ); + msg.insert("\n"); + list.forEach( + dataEntry -> { + sb.append(" [ ").append(BlockType.getAssetMap().getAsset(dataEntry.getBlockType()).getId()); + sb.append(" (").append(dataEntry.getX()).append(", ").append(dataEntry.getY()).append(", ").append(dataEntry.getZ()).append(") ]\n"); + msg.insert( + " [ " + + BlockType.getAssetMap().getAsset(dataEntry.getBlockType()).getId() + + " (" + + dataEntry.getX() + + ", " + + dataEntry.getY() + + ", " + + dataEntry.getZ() + + ") ]" + ); + } + ); + } + ); + } + } + + context.sendMessage(msg); + NPCPlugin.get().getLogger().at(Level.INFO).log(sb.toString()); + } + } + + public static class ChunksCommand extends AbstractWorldCommand { + public ChunksCommand() { + super("chunks", "server.commands.npc.blackboard.chunks.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Store chunkStore = world.getChunkStore().getStore(); + StringBuilder sb = new StringBuilder("Blackboard chunk info:\n"); + int[] count = new int[]{0}; + chunkStore.forEachChunk(BlockPositionProvider.getComponentType(), (archetypeChunk, commandBuffer) -> count[0] += archetypeChunk.size()); + sb.append(" Total sections: ").append(count[0]).append('\n'); + sb.append(" Chunk sections:\n"); + Message msg = Message.translation("server.commands.npc.blackboard.chunks.chunkInfo").param("nb", count[0]); + AndQuery query = Query.and(ChunkSection.getComponentType(), BlockPositionProvider.getComponentType()); + chunkStore.forEachChunk( + query, + (archetypeChunk, commandBuffer) -> { + for (int index = 0; index < archetypeChunk.size(); index++) { + BlockPositionProvider blockPositionProviderComponent = archetypeChunk.getComponent(index, BlockPositionProvider.getComponentType()); + + assert blockPositionProviderComponent != null; + + ChunkSection chunkSectionComponent = archetypeChunk.getComponent(index, ChunkSection.getComponentType()); + + assert chunkSectionComponent != null; + + int x = chunkSectionComponent.getX(); + int z = chunkSectionComponent.getZ(); + sb.append(' ').append(x).append(", ").append(chunkSectionComponent.getY()).append(", ").append(z); + int[] entryCount = new int[]{0}; + blockPositionProviderComponent.forEachBlockSet((set, data) -> entryCount[0] += data.size()); + sb.append(" (") + .append(entryCount[0]) + .append(" entries, ") + .append(blockPositionProviderComponent.getSearchedBlockSets().cardinality()) + .append(" BlockSets)\n"); + msg.insert( + Message.translation("server.commands.npc.blackboard.chunks.detailed_entry") + .param("x", x) + .param("y", chunkSectionComponent.getY()) + .param("z", z) + .param("count", entryCount[0]) + .param("blockSets", blockPositionProviderComponent.getSearchedBlockSets().cardinality()) + ); + } + } + ); + context.sendMessage(msg); + NPCPlugin.get().getLogger().at(Level.INFO).log(sb.toString()); + } + } + + public static class DropCommand extends AbstractWorldCommand { + public DropCommand() { + super("drop", "server.commands.npc.blackboard.drop.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + store.getResource(Blackboard.getResourceType()).clear(); + context.sendMessage(Message.translation("server.commands.npc.blackboard.cleared")); + } + } + + public static class EntityEventsCommand extends AbstractWorldCommand { + public EntityEventsCommand() { + super("entityevents", "server.commands.npc.blackboard.entityevents.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + EntityEventView view = store.getResource(Blackboard.getResourceType()).getView(EntityEventView.class, 0L); + StringBuilder sb = new StringBuilder("Entity Event View:\n"); + sb.append(" Total NPCGroups: ").append(view.getSetCount()); + sb.append("\n NPCGroups:\n"); + Message msg = Message.translation("server.commands.npc.blackboard.entityevents.title").param("count", view.getSetCount()); + view.forEach((b, t) -> { + sb.append(" ").append(NPCGroup.getAssetMap().getAsset(b).getId()).append(" (").append(t.get()).append("):\n"); + msg.insert(" " + NPCGroup.getAssetMap().getAsset(b).getId() + " (" + t.get() + "):\n"); + }, e -> { + UUIDComponent uuidComponent = store.getComponent(e, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID uuid = uuidComponent.getUuid(); + NPCEntity npcComponent = store.getComponent(e, NPCEntity.getComponentType()); + + assert npcComponent != null; + + String roleName = npcComponent.getRoleName(); + sb.append(" ").append(uuid).append(": ").append(roleName).append("\n"); + msg.insert(" " + uuid + ": " + roleName + "\n"); + }); + context.sendMessage(msg); + NPCPlugin.get().getLogger().at(Level.INFO).log(sb.toString()); + } + } + + public static class ReservationCommand extends AbstractPlayerCommand { + @Nonnull + private final EntityWrappedArg entityArg = this.withOptionalArg("entity", "server.commands.entity.entity.desc", ArgTypes.ENTITY_ID); + + public ReservationCommand() { + super("reservation", "server.commands.npc.blackboard.reservation.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Ref npcRef = this.getNPCRef(context, store); + if (npcRef != null) { + NPCEntity npcEntity = store.getComponent(npcRef, NPCEntity.getComponentType()); + + assert npcEntity != null; + + Blackboard blackBoardResource = store.getResource(Blackboard.getResourceType()); + InteractionView reservationView = blackBoardResource.getView(InteractionView.class, 0L); + ReservationStatus reservationStatus = reservationView.getReservationStatus(npcRef, ref, store); + context.sendMessage(Message.translation("server.commands.npc.blackboard.reservationStatus").param("status", reservationStatus.toString())); + } + } + + @Nullable + private Ref getNPCRef(@Nonnull CommandContext context, @Nonnull Store store) { + Ref ref; + if (this.entityArg.provided(context)) { + ref = this.entityArg.get(store, context); + } else { + Ref playerRef = context.senderAsPlayerRef(); + if (playerRef == null || !playerRef.isValid()) { + context.sendMessage(Message.translation("server.commands.errors.playerOrArg").param("option", "entity")); + return null; + } + + ref = TargetUtil.getTargetEntity(playerRef, store); + if (ref == null) { + context.sendMessage(Message.translation("server.commands.errors.no_entity_in_view").param("option", "entity")); + return null; + } + } + + if (ref == null) { + return null; + } else { + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + if (npcComponent == null) { + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID uuid = uuidComponent.getUuid(); + context.sendMessage(Message.translation("server.commands.errors.not_npc").param("uuid", uuid.toString())); + return null; + } else { + return ref; + } + } + } + } + + public static class ReserveCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg reserveArg = this.withRequiredArg("reserve", "server.commands.npc.blackboard.reserve.reserve.desc", ArgTypes.BOOLEAN); + @Nonnull + private final EntityWrappedArg entityArg = this.withOptionalArg("entity", "server.commands.entity.entity.desc", ArgTypes.ENTITY_ID); + + public ReserveCommand() { + super("reserve", "server.commands.npc.blackboard.reserve.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID playerUUID = uuidComponent.getUuid(); + Ref npcRef = this.getNPCRef(context, store); + if (npcRef != null) { + NPCEntity npcEntity = store.getComponent(npcRef, NPCEntity.getComponentType()); + + assert npcEntity != null; + + if (this.reserveArg.get(context)) { + npcEntity.addReservation(playerUUID); + context.sendMessage(Message.translation("server.commands.npc.blackboard.roleReserved").param("role", npcEntity.getRoleName())); + } else { + npcEntity.removeReservation(playerUUID); + context.sendMessage(Message.translation("server.commands.npc.blackboard.roleReleased").param("role", npcEntity.getRoleName())); + } + } + } + + @Nullable + private Ref getNPCRef(@Nonnull CommandContext context, @Nonnull Store store) { + Ref ref; + if (this.entityArg.provided(context)) { + ref = this.entityArg.get(store, context); + } else { + Ref playerRef = context.senderAsPlayerRef(); + if (playerRef == null || !playerRef.isValid()) { + context.sendMessage(Message.translation("server.commands.errors.playerOrArg").param("option", "entity")); + return null; + } + + ref = TargetUtil.getTargetEntity(playerRef, store); + if (ref == null) { + context.sendMessage(Message.translation("server.commands.errors.no_entity_in_view").param("option", "entity")); + return null; + } + } + + if (ref == null) { + return null; + } else { + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + if (npcComponent == null) { + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID uuid = uuidComponent.getUuid(); + context.sendMessage(Message.translation("server.commands.errors.not_npc").param("uuid", uuid.toString())); + return null; + } else { + return ref; + } + } + } + } + + public static class ResourceViewCommand extends AbstractWorldCommand { + @Nonnull + private final RequiredArg chunkArg = this.withRequiredArg( + "chunk", "server.commands.npc.blackboard.resourceview.chunk.desc", ArgTypes.RELATIVE_CHUNK_POSITION + ); + + public ResourceViewCommand() { + super("resourceview", "server.commands.npc.blackboard.resourceview.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Vector2i chunkPosition = this.chunkArg.get(context).getChunkPosition(context, store); + long viewIndex = ResourceView.indexView(chunkPosition.x, chunkPosition.y); + Blackboard blackboard = store.getResource(Blackboard.getResourceType()); + ResourceView view = blackboard.getView(ResourceView.class, viewIndex); + int viewX = ResourceView.xOfViewIndex(viewIndex); + int viewZ = ResourceView.zOfViewIndex(viewIndex); + StringBuilder sb = new StringBuilder("View ("); + sb.append(viewX).append(", ").append(viewZ).append(")\n"); + sb.append(" Spans world coordinates: (").append(ResourceView.toWorldCoordinate(viewX)).append(", ").append(ResourceView.toWorldCoordinate(viewZ)); + sb.append(") to (").append(ResourceView.toWorldCoordinate(viewX + 1)).append(", ").append(ResourceView.toWorldCoordinate(viewZ + 1)).append(")\n"); + Message msg = Message.translation("server.commands.npc.blackboard.view.title").param("x", viewX).param("z", viewZ); + msg.insert( + Message.translation("server.commands.npc.blackboard.view.coordinates") + .param("x1", ResourceView.toWorldCoordinate(viewX)) + .param("z1", ResourceView.toWorldCoordinate(viewZ)) + .param("x2", ResourceView.toWorldCoordinate(viewX + 1)) + .param("z2", ResourceView.toWorldCoordinate(viewZ + 1)) + ); + msg.insert("\n"); + if (view == null) { + sb.append(" No resource view exists"); + msg.insert(Message.translation("server.commands.npc.blackboard.resourceview.noResourceView")); + } else { + sb.append(" Reservations: [ ").append('\n'); + msg.insert(Message.translation("server.commands.npc.blackboard.resourceview.reservations")); + view.getReservationsByEntity() + .forEach( + (ref, reservation) -> { + if (!ref.isValid()) { + sb.append("!!!INVALID ENTITY!!!"); + msg.insert(Message.translation("server.commands.npc.blackboard.view.invalidEntity")); + } else { + UUIDComponent uuidComponent = store.getComponent((Ref)ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID uuid = uuidComponent.getUuid(); + sb.append(" ").append(uuid).append(": "); + msg.insert(" " + uuid + ": "); + NPCEntity npc = store.getComponent((Ref)ref, NPCEntity.getComponentType()); + if (npc == null) { + sb.append("!!!NON-NPC ENTITY!!!"); + msg.insert(Message.translation("server.commands.npc.blackboard.view.nonNpcEntity")); + } else { + sb.append(npc.getRoleName()); + msg.insert(npc.getRoleName()); + } + + int blockIndex = reservation.getBlockIndex(); + int blockX = ResourceView.xFromIndex(blockIndex) + (viewX << 7); + int blockY = ResourceView.yFromIndex(blockIndex) + (reservation.getSectionIndex() << 5); + int blockZ = ResourceView.zFromIndex(blockIndex) + (viewZ << 7); + BlockType blockType = BlockType.getAssetMap().getAsset(world.getBlock(blockX, blockY, blockZ)); + sb.append(" reserved block ") + .append(blockType.getId()) + .append(" at ") + .append(blockX) + .append(", ") + .append(blockY) + .append(", ") + .append(blockZ) + .append('\n'); + msg.insert( + Message.translation("server.commands.npc.blackboard.resourceview.reservedBlock") + .param("name", blockType.getId()) + .param("x", blockX) + .param("y", blockY) + .param("z", blockZ) + ); + } + } + ); + sb.append(" ]"); + msg.insert(" ]"); + } + + context.sendMessage(msg); + NPCPlugin.get().getLogger().at(Level.INFO).log(sb.toString()); + } + } + + public static class ResourceViewsCommand extends AbstractWorldCommand { + public ResourceViewsCommand() { + super("resourceviews", "server.commands.npc.blackboard.resourceviews.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + StringBuilder sb = new StringBuilder("Resource views:\n"); + Message msg = Message.translation("server.commands.npc.blackboard.resourceviews.title"); + Blackboard blackboard = store.getResource(Blackboard.getResourceType()); + int[] count = new int[]{0}; + blackboard.forEachView(ResourceView.class, entry -> count[0]++); + sb.append(" Total resource views: ").append(count[0]).append('\n').append(" Views:\n"); + msg.insert(Message.translation("server.commands.npc.blackboard.resourceviews.totalViews").param("count", count[0])); + blackboard.forEachView( + ResourceView.class, + entry -> { + sb.append(" View (").append(ResourceView.xOfViewIndex(entry.getIndex())).append(", ").append(ResourceView.zOfViewIndex(entry.getIndex())); + sb.append(") Reservations: ").append(entry.getReservationsByEntity().size()).append('\n'); + msg.insert( + Message.translation("server.commands.npc.blackboard.resourceviews.view") + .param("x", ResourceView.xOfViewIndex(entry.getIndex())) + .param("z", ResourceView.zOfViewIndex(entry.getIndex())) + .param("count", entry.getReservationsByEntity().size()) + ); + } + ); + context.sendMessage(msg); + NPCPlugin.get().getLogger().at(Level.INFO).log(sb.toString()); + } + } + + public static class ViewCommand extends AbstractWorldCommand { + @Nonnull + private final RequiredArg chunkArg = this.withRequiredArg( + "chunk", "server.commands.npc.blackboard.view.chunk.desc", ArgTypes.RELATIVE_CHUNK_POSITION + ); + + public ViewCommand() { + super("view", "server.commands.npc.blackboard.view.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + Vector2i chunkPosition = this.chunkArg.get(context).getChunkPosition(context, store); + long viewIndex = BlockTypeView.indexView(chunkPosition.x, chunkPosition.y); + Blackboard blackboard = store.getResource(Blackboard.getResourceType()); + BlockTypeView view = blackboard.getView(BlockTypeView.class, viewIndex); + int viewX = BlockTypeView.xOfViewIndex(viewIndex); + int viewZ = BlockTypeView.zOfViewIndex(viewIndex); + StringBuilder sb = new StringBuilder("View ("); + sb.append(viewX).append(", ").append(viewZ).append(")\n"); + sb.append(" Spans world coordinates: (").append(BlockTypeView.toWorldCoordinate(viewX)).append(", ").append(BlockTypeView.toWorldCoordinate(viewZ)); + sb.append(") to (").append(BlockTypeView.toWorldCoordinate(viewX + 1)).append(", ").append(BlockTypeView.toWorldCoordinate(viewZ + 1)).append(")\n"); + Message msg = Message.translation("server.commands.npc.blackboard.view.title").param("x", viewX).param("z", viewZ); + msg.insert( + Message.translation("server.commands.npc.blackboard.view.coordinates") + .param("x1", BlockTypeView.toWorldCoordinate(viewX)) + .param("z1", BlockTypeView.toWorldCoordinate(viewZ)) + .param("x2", BlockTypeView.toWorldCoordinate(viewX + 1)) + .param("z2", BlockTypeView.toWorldCoordinate(viewZ + 1)) + ); + msg.insert("\n"); + if (view == null) { + sb.append(" No partial view exists"); + msg.insert(Message.translation("server.commands.npc.blackboard.view.noPartialViews")); + } else { + sb.append(" Searched BlockSets: [ "); + msg.insert(Message.translation("server.commands.npc.blockSetsSearched")); + BitSet searchedBlockSets = view.getAllBlockSets(); + Int2IntMap counts = view.getBlockSetCounts(); + boolean subsequent = false; + + for (int i = searchedBlockSets.nextSetBit(0); i >= 0; i = searchedBlockSets.nextSetBit(i + 1)) { + if (subsequent) { + sb.append(", "); + msg.insert(", "); + } + + sb.append(BlockSet.getAssetMap().getAsset(i).getId()).append(" (").append(counts.getOrDefault(i, 0)).append(')'); + msg.insert(BlockSet.getAssetMap().getAsset(i).getId() + " (" + counts.getOrDefault(i, 0) + ")"); + subsequent = true; + if (i == Integer.MAX_VALUE) { + break; + } + } + + Set> entities = view.getEntities(); + sb.append(" ]\n Entities (").append(entities.size()).append("):\n"); + msg.insert(Message.translation("server.commands.npc.blackboard.view.entities").param("count", entities.size())); + entities.forEach(ref -> { + sb.append(" [").append(ref.getIndex()).append("] "); + msg.insert(" [" + ref.getIndex() + "] "); + if (!ref.isValid()) { + sb.append("!!!INVALID ENTITY!!!"); + msg.insert(Message.translation("server.commands.npc.blackboard.view.invalidEntity")); + } else { + NPCEntity npc = store.getComponent((Ref)ref, NPCEntity.getComponentType()); + if (npc == null) { + sb.append("!!!NON-NPC ENTITY!!!\n"); + msg.insert(Message.translation("server.commands.npc.blackboard.view.nonNpcEntity")); + } else { + sb.append(npc.getRoleName()).append("\n BlockSets: [ "); + msg.insert(Message.translation("server.commands.npc.blackboard.view.blockSets")); + IntList blockSets = npc.getBlackboardBlockTypeSets(); + + for (int i = 0; i < blockSets.size(); i++) { + if (i > 0) { + sb.append(", "); + msg.insert(", "); + } + + String blockSetId = BlockSet.getAssetMap().getAsset(blockSets.getInt(i)).getId(); + sb.append(blockSetId); + msg.insert(blockSetId); + } + + sb.append(" ]\n"); + msg.insert(" ]\n"); + } + } + }); + } + + context.sendMessage(msg); + NPCPlugin.get().getLogger().at(Level.INFO).log(sb.toString()); + } + } + + public static class ViewsCommand extends AbstractWorldCommand { + public ViewsCommand() { + super("views", "server.commands.npc.blackboard.views.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + StringBuilder sb = new StringBuilder("Blackboard views:\n"); + Message msg = Message.translation("server.commands.npc.blackboard.views.title"); + Blackboard blackboard = store.getResource(Blackboard.getResourceType()); + int[] count = new int[]{0}; + blackboard.forEachView(BlockTypeView.class, entry -> count[0]++); + sb.append(" Total partial views: ").append(count[0]).append('\n').append(" Views:\n"); + msg.insert(Message.translation("server.commands.npc.blackboard.views.partialViews").param("count", count[0])); + msg.insert("\n"); + blackboard.forEachView( + BlockTypeView.class, + entry -> { + sb.append(" View (").append(BlockTypeView.xOfViewIndex(entry.getIndex())).append(", ").append(BlockTypeView.zOfViewIndex(entry.getIndex())); + sb.append(") Entities: ").append(entry.getEntities().size()).append(", BlockSets: ").append(entry.getAllBlockSets().cardinality()).append('\n'); + msg.insert( + Message.translation("server.commands.npc.blackboard.views.view") + .param("x", BlockTypeView.xOfViewIndex(entry.getIndex())) + .param("z", BlockTypeView.zOfViewIndex(entry.getIndex())) + .param("size", entry.getEntities().size()) + .param("cardinal", entry.getAllBlockSets().cardinality()) + ); + msg.insert("\n"); + } + ); + context.sendMessage(msg); + NPCPlugin.get().getLogger().at(Level.INFO).log(sb.toString()); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCCleanCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCCleanCommand.java new file mode 100644 index 0000000..610e542 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCCleanCommand.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import javax.annotation.Nonnull; + +public class NPCCleanCommand extends AbstractWorldCommand { + public NPCCleanCommand() { + super("clean", "server.commands.npc.clean.desc", true); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + store.forEachEntityParallel( + NPCEntity.getComponentType(), + (index, archetypeChunk, commandBuffer) -> commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE) + ); + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCCommand.java new file mode 100644 index 0000000..6ca283e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCCommand.java @@ -0,0 +1,89 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandSender; +import com.hypixel.hytale.server.core.command.system.CommandUtil; +import com.hypixel.hytale.server.core.command.system.ParseResult; +import com.hypixel.hytale.server.core.command.system.arguments.types.SingleArgumentType; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.suggestion.SuggestionResult; +import com.hypixel.hytale.server.flock.commands.NPCFlockCommand; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderInfo; +import java.awt.Color; +import java.util.List; +import javax.annotation.Nonnull; + +public class NPCCommand extends AbstractCommandCollection { + public static final SingleArgumentType NPC_ROLE = new SingleArgumentType( + "server.commands.parsing.argtype.npcrole.name", "server.commands.parsing.argtype.npcrole.usage" + ) { + public BuilderInfo parse(@Nonnull String input, @Nonnull ParseResult parseResult) { + try { + NPCPlugin npcPlugin = NPCPlugin.get(); + int index = npcPlugin.getIndex(input); + if (index == Integer.MIN_VALUE) { + List roles = npcPlugin.getRoleTemplateNames(false); + parseResult.fail( + Message.translation("server.commands.notfound").param("type", "NPC Role").param("id", input).color(Color.RED), + Message.translation("server.general.failed.didYouMean") + .param("choices", StringUtil.sortByFuzzyDistance(input, roles, CommandUtil.RECOMMEND_COUNT).toString()) + ); + return null; + } else { + BuilderInfo builderInfo = npcPlugin.getRoleBuilderInfo(index); + if (builderInfo == null) { + parseResult.fail(Message.translation("server.commands.notfound").param("type", "NPC Role").param("id", input).color(Color.RED)); + return null; + } else { + return builderInfo; + } + } + } catch (Exception var6) { + parseResult.fail(Message.translation("server.commands.notfound").param("type", "NPC Role").param("id", input).color(Color.RED)); + return null; + } + } + + @Override + public void suggest(@Nonnull CommandSender sender, @Nonnull String textAlreadyEntered, int numParametersTyped, @Nonnull SuggestionResult result) { + try { + NPCPlugin npcPlugin = NPCPlugin.get(); + List roles = npcPlugin.getRoleTemplateNames(false); + textAlreadyEntered = textAlreadyEntered.toLowerCase(); + + for (String role : roles) { + if (role.toLowerCase().startsWith(textAlreadyEntered)) { + result.suggest(role); + } + } + } catch (Exception var9) { + } + } + }; + + public NPCCommand() { + super("npc", "server.commands.npc"); + this.addSubCommand(new NPCAllCommand()); + this.addSubCommand(new NPCAppearanceCommand()); + this.addSubCommand(new NPCAttackCommand()); + this.addSubCommand(new NPCBenchmarkCommand()); + this.addSubCommand(new NPCBlackboardCommand()); + this.addSubCommand(new NPCCleanCommand()); + this.addSubCommand(new NPCDebugCommand()); + this.addSubCommand(new NPCDumpCommand()); + this.addSubCommand(new NPCFlockCommand()); + this.addSubCommand(new NPCFreezeCommand()); + this.addSubCommand(new NPCGiveCommand()); + this.addSubCommand(new NPCPathCommand()); + this.addSubCommand(new NPCRoleCommand()); + this.addSubCommand(new NPCRunTestsCommand()); + this.addSubCommand(new NPCSensorStatsCommand()); + this.addSubCommand(new NPCSpawnCommand()); + this.addSubCommand(new NPCStepCommand()); + this.addSubCommand(new NPCTestCommand()); + this.addSubCommand(new NPCThawCommand()); + this.addSubCommand(new NPCMessageCommand()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCCommandUtils.java b/src/com/hypixel/hytale/server/npc/commands/NPCCommandUtils.java new file mode 100644 index 0000000..95c6fdc --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCCommandUtils.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import it.unimi.dsi.fastutil.Pair; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NPCCommandUtils { + public NPCCommandUtils() { + } + + @Nullable + public static Pair, NPCEntity> getTargetNpc( + @Nonnull CommandContext context, @Nonnull EntityWrappedArg arg, @Nonnull Store store + ) { + Ref ref; + if (arg.provided(context)) { + ref = arg.get(store, context); + } else { + if (!context.isPlayer()) { + context.sendMessage(Message.translation("server.commands.errors.playerOrArg").param("option", "entity")); + return null; + } + + Ref playerRef = context.senderAsPlayerRef(); + if (playerRef == null || !playerRef.isValid()) { + context.sendMessage(Message.translation("server.commands.errors.playerOrArg").param("option", "entity")); + return null; + } + + ref = TargetUtil.getTargetEntity(playerRef, store); + if (ref == null) { + context.sendMessage(Message.translation("server.commands.errors.no_entity_in_view").param("option", "entity")); + return null; + } + } + + if (ref != null && ref.isValid()) { + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + if (npcComponent == null) { + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID uuid = uuidComponent.getUuid(); + context.sendMessage(Message.translation("server.commands.errors.not_npc").param("uuid", uuid.toString())); + return null; + } else { + return Pair.of(ref, npcComponent); + } + } else { + return null; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCDebugCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCDebugCommand.java new file mode 100644 index 0000000..e45d4fb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCDebugCommand.java @@ -0,0 +1,197 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.entity.nameplate.Nameplate; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.RoleDebugFlags; +import java.util.EnumSet; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NPCDebugCommand extends AbstractCommandCollection { + public NPCDebugCommand() { + super("debug", "server.commands.npc.debug.desc"); + this.addSubCommand(new NPCDebugCommand.ShowCommand()); + this.addSubCommand(new NPCDebugCommand.SetCommand()); + this.addSubCommand(new NPCDebugCommand.ToggleCommand()); + this.addSubCommand(new NPCDebugCommand.DefaultsCommand()); + this.addSubCommand(new NPCDebugCommand.ClearCommand()); + this.addSubCommand(new NPCDebugCommand.PresetsCommand()); + } + + private static void modifyFlags( + @Nonnull CommandContext context, + @Nonnull NPCEntity npc, + @Nonnull Ref ref, + @Nonnull EnumSet flags, + @Nonnull BiFunction, EnumSet, EnumSet> flagsModifier, + @Nonnull Store store + ) { + EnumSet newFlags = flagsModifier.apply(npc.getRoleDebugFlags(), flags); + if (newFlags != null) { + safeSetRoleDebugFlags(npc, ref, newFlags, store); + printNewFlags(npc, context, newFlags); + } + } + + private static void safeSetRoleDebugFlags( + @Nonnull NPCEntity npc, @Nonnull Ref ref, @Nonnull EnumSet flags, @Nonnull Store store + ) { + store.tryRemoveComponent(ref, Nameplate.getComponentType()); + npc.setRoleDebugFlags(flags); + } + + private static void printNewFlags(@Nonnull NPCEntity npc, @Nonnull CommandContext context, @Nonnull EnumSet newFlags) { + String flags = getListOfFlags(newFlags).toString(); + context.sendMessage( + Message.translation("server.commands.npc.debug.debugFlagsSet").param("role", npc.getRoleName()).param("flags", !flags.isEmpty() ? flags : "") + ); + } + + @Nonnull + private static StringBuilder getListOfFlags(@Nonnull EnumSet flags) { + return RoleDebugFlags.getListOfFlags(flags, new StringBuilder()); + } + + public static class ClearCommand extends NPCMultiSelectCommandBase { + public ClearCommand() { + super("clear", "server.commands.npc.debug.clear.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull NPCEntity npc, @Nonnull World world, @Nonnull Store store, @Nonnull Ref ref + ) { + NPCDebugCommand.safeSetRoleDebugFlags(npc, ref, EnumSet.noneOf(RoleDebugFlags.class), store); + } + } + + public static class DefaultsCommand extends NPCMultiSelectCommandBase { + public DefaultsCommand() { + super("defaults", "server.commands.npc.debug.defaults.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull NPCEntity npc, @Nonnull World world, @Nonnull Store store, @Nonnull Ref ref + ) { + NPCDebugCommand.safeSetRoleDebugFlags(npc, ref, RoleDebugFlags.getPreset("default"), store); + } + } + + public static class PresetsCommand extends AbstractCommand { + @Nonnull + private final OptionalArg presetArg = this.withOptionalArg("preset", "server.commands.npc.debug.presets.preset.desc", ArgTypes.STRING); + + public PresetsCommand() { + super("presets", "server.commands.npc.debug.presets.desc"); + } + + @Nullable + @Override + protected CompletableFuture execute(@Nonnull CommandContext context) { + if (this.presetArg.provided(context)) { + String presetName = this.presetArg.get(context); + if (!presetName.isEmpty() && RoleDebugFlags.havePreset(presetName)) { + EnumSet flags = RoleDebugFlags.getPreset(presetName); + String flagString = NPCDebugCommand.getListOfFlags(flags).toString(); + context.sendMessage( + Message.translation("server.commands.npc.debug.preset.info") + .param("preset", presetName) + .param("flags", !flagString.isEmpty() ? flagString : "") + ); + return null; + } else { + context.sendMessage(Message.translation("server.commands.errors.npc.unknown_debug_preset").param("preset", presetName)); + return null; + } + } else { + String flags = RoleDebugFlags.getListOfAllFlags(new StringBuilder()).toString(); + String presets = RoleDebugFlags.getListOfAllPresets(new StringBuilder()).toString(); + Message message = Message.translation("server.commands.npc.debug.presets.info").param("flags", flags).param("presets", presets); + context.sendMessage(message); + return null; + } + } + } + + public static class SetCommand extends NPCMultiSelectCommandBase { + @Nonnull + private final RequiredArg flagsArg = this.withRequiredArg("flags", "server.commands.npc.debug.flags.desc", ArgTypes.STRING); + + public SetCommand() { + super("set", "server.commands.npc.debug.set.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull NPCEntity npc, @Nonnull World world, @Nonnull Store store, @Nonnull Ref ref + ) { + String flagsString = this.flagsArg.get(context); + EnumSet flags = RoleDebugFlags.getFlags(flagsString.split(",")); + NPCDebugCommand.modifyFlags(context, npc, ref, flags, (oldFlags, argFlags) -> argFlags, store); + } + } + + public static class ShowCommand extends NPCMultiSelectCommandBase { + public ShowCommand() { + super("show", "server.commands.npc.debug.show.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull NPCEntity npc, @Nonnull World world, @Nonnull Store store, @Nonnull Ref ref + ) { + String flags = NPCDebugCommand.getListOfFlags(npc.getRoleDebugFlags()).toString(); + context.sendMessage( + Message.translation("server.commands.npc.debug.currentFlags").param("role", npc.getRoleName()).param("flags", !flags.isEmpty() ? flags : "") + ); + } + } + + public static class ToggleCommand extends NPCMultiSelectCommandBase { + @Nonnull + private final RequiredArg flagsArg = this.withRequiredArg("flags", "server.commands.npc.debug.flags.desc", ArgTypes.STRING); + + public ToggleCommand() { + super("toggle", "server.commands.npc.debug.toggle.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull NPCEntity npc, @Nonnull World world, @Nonnull Store store, @Nonnull Ref ref + ) { + String flagsString = this.flagsArg.get(context); + EnumSet flags = RoleDebugFlags.getFlags(flagsString.split(",")); + NPCDebugCommand.modifyFlags(context, npc, ref, flags, (oldFlags, argFlags) -> { + if (argFlags.isEmpty()) { + return null; + } else { + EnumSet newFlags = oldFlags.clone(); + + for (RoleDebugFlags flag : argFlags) { + if (newFlags.contains(flag)) { + newFlags.remove(flag); + } else { + newFlags.add(flag); + } + } + + return newFlags; + } + }, store); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCDumpCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCDumpCommand.java new file mode 100644 index 0000000..5f3c766 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCDumpCommand.java @@ -0,0 +1,115 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.util.ComponentInfo; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponent; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponentCollection; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class NPCDumpCommand extends NPCWorldCommandBase { + @Nonnull + private final FlagArg jsonArg = this.withFlagArg("json", "server.commands.npc.dump.json"); + + public NPCDumpCommand() { + super("dump", "server.commands.npc.dump.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull NPCEntity npc, @Nonnull World world, @Nonnull Store store, @Nonnull Ref ref + ) { + StringBuilder sb = new StringBuilder(npc.getRoleName()); + sb.append(":\n"); + Role role = npc.getRole(); + if (role != null) { + if (!this.jsonArg.get(context)) { + List componentInfoList = new ObjectArrayList<>(); + dumpComponent(role, role, -1, 0, componentInfoList); + + for (ComponentInfo info : componentInfoList) { + sb.append(info).append('\n'); + } + } else { + JsonObject obj = new JsonObject(); + dumpComponentsAsJson(role, role, -1, 0, obj); + sb.append(obj); + } + } + + NPCPlugin.get().getLogger().at(Level.INFO).log(sb.toString()); + } + + private static void dumpComponent( + @Nonnull Role role, @Nonnull IAnnotatedComponent component, int index, int nestingDepth, @Nonnull List infoList + ) { + ComponentInfo componentInfo = new ComponentInfo(component.getClass().getSimpleName(), index, nestingDepth); + infoList.add(componentInfo); + if (component instanceof IAnnotatedComponentCollection aggregate) { + int nestedComponentCount = aggregate.componentCount(); + + for (int i = 0; i < nestedComponentCount; i++) { + IAnnotatedComponent nestedComponent = aggregate.getComponent(i); + if (nestedComponent != null) { + dumpComponent(role, nestedComponent, i, nestingDepth + 1, infoList); + } + } + } + + component.getInfo(role, componentInfo); + } + + private static void dumpComponentsAsJson( + @Nonnull Role role, @Nonnull IAnnotatedComponent component, int index, int nestingDepth, @Nonnull JsonElement parent + ) { + ComponentInfo componentInfo = new ComponentInfo(component.getClass().getSimpleName(), index, nestingDepth); + JsonObject object = parent.isJsonObject() ? parent.getAsJsonObject() : new JsonObject(); + object.add("name", new JsonPrimitive(componentInfo.getName())); + if (componentInfo.getIndex() >= 0) { + object.add("index", new JsonPrimitive(componentInfo.getIndex())); + } + + if (component instanceof IAnnotatedComponentCollection aggregate) { + JsonArray array = new JsonArray(); + object.add("children", array); + int nestedComponentCount = aggregate.componentCount(); + + for (int i = 0; i < nestedComponentCount; i++) { + IAnnotatedComponent nestedComponent = aggregate.getComponent(i); + if (nestedComponent != null) { + dumpComponentsAsJson(role, nestedComponent, i, nestingDepth + 1, array); + } + } + } + + component.getInfo(role, componentInfo); + List fields = componentInfo.getFields(); + if (!fields.isEmpty()) { + JsonArray array = new JsonArray(); + + for (String field : fields) { + array.add(field); + } + + object.add("parameters", array); + } + + if (parent.isJsonArray()) { + parent.getAsJsonArray().add(object); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCFreezeCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCFreezeCommand.java new file mode 100644 index 0000000..79b9bc0 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCFreezeCommand.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.entity.Frozen; +import com.hypixel.hytale.server.core.modules.entity.component.Interactable; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import it.unimi.dsi.fastutil.Pair; +import javax.annotation.Nonnull; + +public class NPCFreezeCommand extends AbstractWorldCommand { + @Nonnull + private final FlagArg allArg = this.withFlagArg("all", "server.commands.npc.freeze.all"); + @Nonnull + private final FlagArg toggleArg = this.withFlagArg("toggle", "server.commands.npc.freeze.toggle"); + @Nonnull + private final EntityWrappedArg entityArg = this.withOptionalArg("entity", "server.commands.entity.entity.desc", ArgTypes.ENTITY_ID); + + public NPCFreezeCommand() { + super("freeze", "server.commands.npc.freeze.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + if (this.allArg.get(context)) { + store.forEachEntityParallel( + NPCEntity.getComponentType(), + (index, archetypeChunk, commandBuffer) -> commandBuffer.ensureComponent(archetypeChunk.getReferenceTo(index), Frozen.getComponentType()) + ); + store.forEachEntityParallel(ItemComponent.getComponentType(), (index, archetypeChunk, commandBuffer) -> { + Ref ref = archetypeChunk.getReferenceTo(index); + commandBuffer.ensureComponent(ref, Frozen.getComponentType()); + commandBuffer.ensureComponent(ref, Interactable.getComponentType()); + }); + } else { + Pair, NPCEntity> targetNpcPair = NPCCommandUtils.getTargetNpc(context, this.entityArg, store); + if (targetNpcPair != null) { + Ref targetNpcRef = targetNpcPair.first(); + String roleName = targetNpcPair.second().getRoleName(); + if (this.toggleArg.get(context)) { + boolean wasFrozen = store.getArchetype(targetNpcRef).contains(Frozen.getComponentType()); + if (wasFrozen) { + store.tryRemoveComponent(targetNpcRef, Frozen.getComponentType()); + context.sendMessage(Message.translation("server.commands.npc.thaw.npc").param("role", roleName)); + } else { + store.ensureComponent(targetNpcRef, Frozen.getComponentType()); + context.sendMessage(Message.translation("server.commands.npc.freeze.npc").param("role", roleName)); + } + } else { + store.ensureComponent(targetNpcRef, Frozen.getComponentType()); + context.sendMessage(Message.translation("server.commands.npc.freeze.npc").param("role", roleName)); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCGiveCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCGiveCommand.java new file mode 100644 index 0000000..c3e347c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCGiveCommand.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.RoleUtils; +import javax.annotation.Nonnull; + +public class NPCGiveCommand extends NPCWorldCommandBase { + @Nonnull + private final RequiredArg itemArg = this.withRequiredArg("item", "server.commands.npc.give.item.desc", ArgTypes.ITEM_ASSET); + + public NPCGiveCommand() { + super("give", "server.commands.npc.give.desc"); + this.addSubCommand(new NPCGiveCommand.GiveNothingCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull NPCEntity npc, @Nonnull World world, @Nonnull Store store, @Nonnull Ref ref + ) { + Item item = this.itemArg.get(context); + String itemName = item.getId(); + if (item.getArmor() != null) { + RoleUtils.setArmor(npc, itemName); + } else { + RoleUtils.setItemInHand(npc, itemName); + } + } + + public static class GiveNothingCommand extends NPCWorldCommandBase { + public GiveNothingCommand() { + super("nothing", "server.commands.npc.give.nothing.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull NPCEntity npc, @Nonnull World world, @Nonnull Store store, @Nonnull Ref ref + ) { + RoleUtils.setItemInHand(npc, null); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCMessageCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCMessageCommand.java new file mode 100644 index 0000000..dddc6dd --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCMessageCommand.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.components.messaging.BeaconSupport; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import it.unimi.dsi.fastutil.Pair; +import javax.annotation.Nonnull; + +public class NPCMessageCommand extends AbstractPlayerCommand { + @Nonnull + private final RequiredArg messageArg = this.withRequiredArg("message", "server.commands.npc.message.message.desc", ArgTypes.STRING); + @Nonnull + private final OptionalArg expirationTimeArg = this.withOptionalArg("expiration", "server.commands.npc.message.expiration", ArgTypes.DOUBLE); + @Nonnull + private final FlagArg allArg = this.withFlagArg("all", "server.commands.npc.message.all"); + @Nonnull + private final EntityWrappedArg entityArg = this.withOptionalArg("entity", "server.commands.entity.entity.desc", ArgTypes.ENTITY_ID); + + public NPCMessageCommand() { + super("message", "server.commands.npc.message.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + String msg = this.messageArg.get(context); + double expiration = this.expirationTimeArg.provided(context) ? this.expirationTimeArg.get(context) : 1.0; + if (this.allArg.get(context)) { + store.forEachEntityParallel(NPCEntity.getComponentType(), (index, archetypeChunk, commandBuffer) -> { + BeaconSupport beaconSupport = archetypeChunk.getComponent(index, BeaconSupport.getComponentType()); + if (beaconSupport != null) { + beaconSupport.postMessage(msg, ref, expiration); + } + }); + } else { + Pair, NPCEntity> targetNpcPair = NPCCommandUtils.getTargetNpc(context, this.entityArg, store); + if (targetNpcPair != null) { + Ref targetNpcRef = targetNpcPair.first(); + BeaconSupport beaconSupportComponent = store.getComponent(targetNpcRef, BeaconSupport.getComponentType()); + if (beaconSupportComponent != null) { + beaconSupportComponent.postMessage(msg, ref, expiration); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCMultiSelectCommandBase.java b/src/com/hypixel/hytale/server/npc/commands/NPCMultiSelectCommandBase.java new file mode 100644 index 0000000..a17f348 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCMultiSelectCommandBase.java @@ -0,0 +1,236 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; + +public abstract class NPCMultiSelectCommandBase extends NPCWorldCommandBase { + protected static final float DEFAULT_CONE_ANGLE = 30.0F; + protected static final float DEFAULT_RANGE = 8.0F; + protected static final float RANGE_MIN = 0.0F; + protected static final float RANGE_MAX = 2048.0F; + protected static final float CONE_ANGLE_MIN = 0.0F; + protected static final float CONE_ANGLE_MAX = 180.0F; + @Nonnull + protected final OptionalArg coneAngleArg = this.withOptionalArg("angle", "server.commands.npc.command.angle.desc", ArgTypes.FLOAT); + @Nonnull + protected final OptionalArg rangeArg = this.withOptionalArg("range", "server.commands.npc.command.range.desc", ArgTypes.FLOAT); + @Nonnull + private final OptionalArg rolesArg = this.withOptionalArg("roles", "server.commands.npc.command.roles.desc", ArgTypes.STRING); + @Nonnull + private final FlagArg nearestArg = this.withFlagArg("nearest", "server.commands.npc.command.nearest.desc"); + @Nonnull + private final FlagArg presetCone30 = this.withFlagArg("cone", "server.commands.npc.command.preset.cone.desc"); + @Nonnull + private final FlagArg presetCone30all = this.withFlagArg("coneAll", "server.commands.npc.command.preset.cone_all.desc"); + @Nonnull + private final FlagArg presetSphere = this.withFlagArg("sphere", "server.commands.npc.command.preset.sphere.desc"); + @Nonnull + private final FlagArg presetRay = this.withFlagArg("ray", "server.commands.npc.command.preset.ray.desc"); + + public NPCMultiSelectCommandBase(@Nonnull String name, @Nonnull String description) { + super(name, description); + } + + public NPCMultiSelectCommandBase(@Nonnull String name, @Nonnull String description, boolean requiresConfirmation) { + super(name, description, requiresConfirmation); + } + + public NPCMultiSelectCommandBase(@Nonnull String description) { + super(description); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + if (this.entityArg.provided(context)) { + Ref ref = this.entityArg.get(store, context); + if (ref != null) { + NPCEntity npc = ensureIsNPC(context, store, ref); + if (npc != null) { + this.execute(context, npc, world, store, ref); + } + } + } else { + Ref playerRef = null; + if (context.isPlayer()) { + playerRef = context.senderAsPlayerRef(); + } + + if (playerRef != null && playerRef.isValid()) { + Set roleSet = new HashSet<>(); + if (this.rolesArg.provided(context)) { + String roleString = this.rolesArg.get(context); + if (roleString == null || roleString.isEmpty()) { + context.sendMessage(Message.translation("server.commands.errors.npc.no_role_list_provided")); + return; + } + + String[] roles = roleString.split(","); + + for (String role : roles) { + if (!role.isBlank()) { + if (!NPCPlugin.get().hasRoleName(role)) { + context.sendMessage(Message.translation("server.commands.errors.npc.unknown_role").param("role", role)); + return; + } + + roleSet.add(role); + } + } + } + + float range = this.rangeArg.provided(context) ? this.rangeArg.get(context) : 8.0F; + if (!(range < 0.0F) && !(range > 2048.0F)) { + float coneAngleDeg; + boolean nearest; + if (this.presetCone30.provided(context)) { + coneAngleDeg = 30.0F; + nearest = true; + } else if (this.presetCone30all.provided(context)) { + coneAngleDeg = 30.0F; + nearest = false; + } else if (this.presetSphere.provided(context)) { + coneAngleDeg = 180.0F; + nearest = false; + } else if (this.presetRay.provided(context)) { + coneAngleDeg = 0.0F; + nearest = true; + } else { + coneAngleDeg = this.coneAngleArg.provided(context) ? this.coneAngleArg.get(context).intValue() : 30.0F; + if (coneAngleDeg < 0.0F || coneAngleDeg > 180.0F) { + context.sendMessage( + Message.translation("server.commands.errors.validation.range.between_inclusive") + .param("param", "angle") + .param("min", 0.0F) + .param("max", 180.0F) + .param("value", coneAngleDeg) + ); + return; + } + + nearest = this.nearestArg.provided(context); + } + + List> refs = null; + ComponentType npcEntityComponentType = NPCEntity.getComponentType(); + + assert npcEntityComponentType != null; + + Vector3d eyePosition; + if (coneAngleDeg == 0.0F) { + Ref ref = TargetUtil.getTargetEntity(playerRef, range, store); + if (ref != null && store.getComponent(ref, npcEntityComponentType) != null) { + refs = new ArrayList<>(); + refs.add(ref); + } + + eyePosition = Vector3d.ZERO; + } else { + TransformComponent playerTransform = store.getComponent(playerRef, TransformComponent.getComponentType()); + + assert playerTransform != null; + + Transform viewTransform = TargetUtil.getLook(playerRef, store); + eyePosition = viewTransform.getPosition(); + Vector3d eyeDirection = viewTransform.getDirection(); + + assert eyePosition.length() == 1.0; + + refs = TargetUtil.getAllEntitiesInSphere(eyePosition, range, store); + float cosineConeAngle = (float)Math.cos((float)Math.toRadians(coneAngleDeg)); + + assert coneAngleDeg != 180.0F || cosineConeAngle == -1.0F; + + refs.removeIf(entityRef -> { + if (store.getComponent((Ref)entityRef, npcEntityComponentType) == null) { + return true; + } else if (cosineConeAngle <= -1.0F) { + return false; + } else { + TransformComponent entityTransform = store.getComponent((Ref)entityRef, TransformComponent.getComponentType()); + + assert entityTransform != null; + + Vector3d direction = Vector3d.directionTo(eyePosition, entityTransform.getPosition()); + double lengthDirection = direction.length(); + return lengthDirection < 1.0E-4 ? true : eyeDirection.dot(direction) < cosineConeAngle * lengthDirection; + } + }); + } + + if (refs != null && !refs.isEmpty() && !roleSet.isEmpty()) { + refs.removeIf(refx -> { + NPCEntity npc = store.getComponent(refx, npcEntityComponentType); + return !roleSet.contains(npc.getRoleName()); + }); + } + + if (refs != null && !refs.isEmpty()) { + if (nearest && refs.size() > 1) { + Ref nearestRef = refs.getFirst(); + double nearestDistanceSq = Double.MAX_VALUE; + + for (Ref ref : refs) { + TransformComponent npcTransform = store.getComponent(ref, TransformComponent.getComponentType()); + + assert npcTransform != null; + + double distanceSq = Vector3d.directionTo(eyePosition, npcTransform.getPosition()).squaredLength(); + if (distanceSq < nearestDistanceSq) { + nearestDistanceSq = distanceSq; + nearestRef = ref; + } + } + + refs = List.of(nearestRef); + } + + this.processEntityList(context, world, store, refs); + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_NO_ENTITY_IN_VIEW); + } + } else { + context.sendMessage( + Message.translation("server.commands.errors.validation.range.between_inclusive") + .param("param", "range") + .param("min", 0.0F) + .param("max", 2048.0F) + .param("value", range) + ); + } + } else { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_OR_ARG); + } + } + } + + protected void processEntityList( + @Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store, @Nonnull List> refs + ) { + refs.forEach(ref -> { + NPCEntity npc = store.getComponent((Ref)ref, NPCEntity.getComponentType()); + + assert npc != null; + + this.execute(context, npc, world, store, (Ref)ref); + }); + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCPathCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCPathCommand.java new file mode 100644 index 0000000..56bfe7a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCPathCommand.java @@ -0,0 +1,122 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.builtin.path.path.TransientPath; +import com.hypixel.hytale.builtin.path.waypoint.RelativeWaypointDefinition; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import java.util.ArrayDeque; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NPCPathCommand extends AbstractCommandCollection { + public NPCPathCommand() { + super("path", "server.commands.npc.path.desc"); + this.addSubCommand(new NPCPathCommand.SetPathCommand()); + this.addSubCommand(new NPCPathCommand.PolygonPathCommand()); + } + + public static class PolygonPathCommand extends NPCWorldCommandBase { + @Nonnull + private final RequiredArg sidesArg = this.withRequiredArg("sides", "server.commands.npc.path.polygon.sides.desc", ArgTypes.INTEGER) + .addValidator(Validators.greaterThan(0)); + @Nonnull + private final OptionalArg lengthArg = this.withOptionalArg("length", "server.commands.npc.path.length.desc", ArgTypes.DOUBLE) + .addValidator(Validators.greaterThan(0.0)); + + public PolygonPathCommand() { + super("polygon", "server.commands.npc.path.polygon.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull NPCEntity npc, @Nonnull World world, @Nonnull Store store, @Nonnull Ref ref + ) { + ArrayDeque instructions = new ArrayDeque<>(); + Integer sides = this.sidesArg.get(context); + float angle = (float) (Math.PI * 2) / sides.intValue(); + double length = this.lengthArg.provided(context) ? this.lengthArg.get(context) : 5.0; + + for (int i = 0; i < sides; i++) { + instructions.add(new RelativeWaypointDefinition(angle, length)); + } + + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3d position = transformComponent.getPosition(); + Vector3f headRotation = headRotationComponent.getRotation(); + npc.getPathManager().setTransientPath(TransientPath.buildPath(position, headRotation, instructions, 1.0)); + } + } + + public static class SetPathCommand extends NPCWorldCommandBase { + @Nonnull + private final RequiredArg instructionsArg = this.withRequiredArg("instructions", "server.commands.npc.path.instructions.desc", ArgTypes.STRING); + + public SetPathCommand() { + super("", "server.commands.npc.path.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull NPCEntity npc, @Nonnull World world, @Nonnull Store store, @Nonnull Ref ref + ) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + String instructionsString = this.instructionsArg.get(context); + ArrayDeque instructions = this.parseInstructions(context, instructionsString); + if (instructions != null) { + npc.getPathManager() + .setTransientPath(TransientPath.buildPath(transformComponent.getPosition(), headRotationComponent.getRotation(), instructions, 1.0)); + } + } + + @Nullable + private ArrayDeque parseInstructions(@Nonnull CommandContext context, @Nonnull String str) { + ArrayDeque instructions = new ArrayDeque<>(); + String[] parts = str.split(","); + int index = 0; + + try { + while (index < parts.length) { + float rotation = Float.parseFloat(parts[index++]) * (float) (Math.PI / 180.0); + double distance = Double.parseDouble(parts[index++]); + instructions.add(new RelativeWaypointDefinition(rotation, distance)); + } + + return instructions; + } catch (NumberFormatException var9) { + context.sendMessage(Message.raw("Invalid number format: " + var9.getMessage())); + return null; + } catch (IndexOutOfBoundsException var10) { + context.sendMessage(Message.raw("Instructions must be defined in pairs! Missing distance value.")); + return null; + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCRoleCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCRoleCommand.java new file mode 100644 index 0000000..cb36d90 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCRoleCommand.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderInfo; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.systems.RoleChangeSystem; +import javax.annotation.Nonnull; + +public class NPCRoleCommand extends NPCWorldCommandBase { + @Nonnull + private final RequiredArg roleArg = this.withRequiredArg("role", "server.commands.npc.role.role.desc", NPCCommand.NPC_ROLE); + + public NPCRoleCommand() { + super("role", "server.commands.npc.role.desc"); + this.addUsageVariant(new NPCRoleCommand.GetRoleCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull NPCEntity npc, @Nonnull World world, @Nonnull Store store, @Nonnull Ref ref + ) { + BuilderInfo roleInfo = this.roleArg.get(context); + if (npc.getRole().isRoleChangeRequested()) { + context.sendMessage(Message.translation("server.commands.npc.role.unableToSetRole")); + } else { + RoleChangeSystem.requestRoleChange(ref, npc.getRole(), roleInfo.getIndex(), true, store); + context.sendMessage(Message.translation("server.commands.npc.role.roleSet").param("role", roleInfo.getKeyName())); + } + } + + public static class GetRoleCommand extends NPCWorldCommandBase { + public GetRoleCommand() { + super("server.commands.npc.role.get.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull NPCEntity npc, @Nonnull World world, @Nonnull Store store, @Nonnull Ref ref + ) { + context.sendMessage(Message.translation("server.commands.npc.role.npcHasRole").param("role", npc.getRoleName())); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCRunTestsCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCRunTestsCommand.java new file mode 100644 index 0000000..895d63f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCRunTestsCommand.java @@ -0,0 +1,276 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.group.EntityGroup; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.FlockPlugin; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NPCRunTestsCommand extends AbstractPlayerCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_NPC_RUN_TESTS_SPECIFY_ROLES = Message.translation("server.commands.npc.runtests.specifyroles"); + @Nonnull + private final OptionalArg rolesArg = this.withOptionalArg("roles", "server.commands.npc.runtests.roles.desc", ArgTypes.STRING); + @Nonnull + private final FlagArg presetArg = this.withFlagArg("preset", "server.commands.npc.runtests.preset.desc"); + @Nonnull + private final FlagArg passArg = this.withFlagArg("pass", "server.commands.npc.runtests.pass.desc"); + @Nonnull + private final FlagArg failArg = this.withFlagArg("fail", "server.commands.npc.runtests.fail.desc"); + @Nonnull + private final FlagArg abortArg = this.withFlagArg("abort", "server.commands.npc.runtests.abort.desc"); + + public NPCRunTestsCommand() { + super("runtests", "server.commands.npc.runtests.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + NPCRunTestsCommand.NPCTestData testDataComponent = store.ensureAndGetComponent(ref, NPCRunTestsCommand.NPCTestData.getComponentType()); + TransformComponent playerTransformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert playerTransformComponent != null; + + if (!testDataComponent.npcRoles.isEmpty()) { + if (this.passArg.get(context)) { + setNextRole(testDataComponent, ref, store, world); + return; + } + + if (this.failArg.get(context)) { + testDataComponent.failedRoles.add(testDataComponent.npcRoles.getInt(testDataComponent.index)); + setNextRole(testDataComponent, ref, store, world); + return; + } + + if (this.abortArg.get(context)) { + reportResults(ref, testDataComponent, store); + Ref npcRef = world.getEntityRef(testDataComponent.targetUUID); + if (npcRef != null) { + cleanupNPC(npcRef, store); + } + + store.removeComponent(ref, NPCRunTestsCommand.NPCTestData.getComponentType()); + return; + } + } + + String[] roles; + if (this.presetArg.get(context)) { + roles = NPCPlugin.get().getPresetCoverageTestNPCs(); + } else { + if (!this.rolesArg.provided(context)) { + context.sendMessage(MESSAGE_COMMANDS_NPC_RUN_TESTS_SPECIFY_ROLES); + store.removeComponent(ref, NPCRunTestsCommand.NPCTestData.getComponentType()); + return; + } + + String roleString = this.rolesArg.get(context); + if (roleString == null || roleString.isEmpty()) { + context.sendMessage(MESSAGE_COMMANDS_NPC_RUN_TESTS_SPECIFY_ROLES); + store.removeComponent(ref, NPCRunTestsCommand.NPCTestData.getComponentType()); + return; + } + + roles = roleString.split(","); + } + + for (String role : roles) { + int flockSize; + try { + int idx = role.indexOf(35); + flockSize = idx < 0 ? 1 : Integer.parseInt(role.substring(idx + 1)); + if (idx > 0) { + role = role.substring(0, idx); + } + } catch (NumberFormatException var15) { + context.sendMessage(Message.translation("server.commands.npc.runtests.invalidflocksize").param("role", role)); + continue; + } + + int builderIndex = NPCPlugin.get().getIndex(role); + if (builderIndex == Integer.MIN_VALUE) { + context.sendMessage(Message.translation("server.commands.npc.spawn.templateNotFound").param("template", role)); + } else { + testDataComponent.npcRoles.add(builderIndex); + testDataComponent.flockSizes.add(flockSize); + } + } + + if (testDataComponent.targetUUID == null) { + spawnNPC(ref, testDataComponent, 0, playerTransformComponent.getPosition(), playerTransformComponent.getRotation(), store); + } + } + + private static void setNextRole( + @Nonnull NPCRunTestsCommand.NPCTestData testData, @Nonnull Ref reference, @Nonnull Store store, @Nonnull World world + ) { + Ref npcReference = world.getEntityRef(testData.targetUUID); + testData.index++; + if (testData.index >= testData.npcRoles.size()) { + reportResults(reference, testData, store); + if (npcReference != null) { + cleanupNPC(npcReference, store); + } + + store.removeComponent(reference, NPCRunTestsCommand.NPCTestData.getComponentType()); + } else { + Vector3d position; + Vector3f rotation; + if (npcReference != null) { + TransformComponent npcTransformComponent = store.getComponent(npcReference, TransformComponent.getComponentType()); + + assert npcTransformComponent != null; + + position = npcTransformComponent.getPosition(); + rotation = npcTransformComponent.getRotation(); + cleanupNPC(npcReference, store); + } else { + TransformComponent transformComponent = store.getComponent(reference, TransformComponent.getComponentType()); + + assert transformComponent != null; + + position = transformComponent.getPosition(); + rotation = transformComponent.getRotation(); + } + + spawnNPC(reference, testData, testData.index, position, rotation, store); + } + } + + private static void cleanupNPC(@Nonnull Ref ref, @Nonnull Store store) { + Ref flockReference = FlockPlugin.getFlockReference(ref, store); + if (flockReference != null) { + ObjectArrayList> members = new ObjectArrayList<>(); + EntityGroup entityGroupComponent = store.getComponent(flockReference, EntityGroup.getComponentType()); + + assert entityGroupComponent != null; + + entityGroupComponent.forEachMember((index, memberx, list) -> list.add(memberx), members); + + for (Ref member : members) { + store.removeEntity(member, RemoveReason.REMOVE); + } + } + + store.removeEntity(ref, RemoveReason.REMOVE); + } + + private static void spawnNPC( + @Nonnull Ref playerReference, + @Nonnull NPCRunTestsCommand.NPCTestData testData, + int index, + @Nonnull Vector3d position, + @Nullable Vector3f rotation, + @Nonnull Store store + ) { + Pair, NPCEntity> npcPair = NPCPlugin.get().spawnEntity(store, testData.npcRoles.getInt(index), position, rotation, null, null); + Ref npcRef = npcPair.first(); + NPCEntity npcComponent = npcPair.second(); + int flockSize = testData.flockSizes.getInt(index); + if (flockSize > 1) { + TransformComponent npcTransformComponent = store.getComponent(npcRef, TransformComponent.getComponentType()); + + assert npcTransformComponent != null; + + FlockPlugin.trySpawnFlock( + npcRef, npcComponent, store, npcComponent.getRoleIndex(), npcTransformComponent.getPosition(), npcTransformComponent.getRotation(), flockSize, null + ); + } + + String roleName = npcComponent.getRoleName(); + PlayerRef playerRefComponent = store.getComponent(playerReference, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerRefComponent.sendMessage( + Message.translation("server.commands.npc.runtests.testing") + .param("role", roleName) + .insert("\n") + .insert(Message.translation("server.npc.tests." + roleName)) + ); + UUIDComponent npcUUIDComponent = store.getComponent(npcRef, UUIDComponent.getComponentType()); + + assert npcUUIDComponent != null; + + testData.targetUUID = npcUUIDComponent.getUuid(); + } + + private static void reportResults( + @Nonnull Ref playerReference, @Nonnull NPCRunTestsCommand.NPCTestData testData, @Nonnull Store store + ) { + NPCPlugin npcPlugin = NPCPlugin.get(); + Message msg = Message.translation("server.commands.npc.runtests.results"); + + for (int i = 0; i < testData.npcRoles.size(); i++) { + int index = testData.npcRoles.getInt(i); + msg.insert(" " + npcPlugin.getName(index) + ": "); + String result = i >= testData.index + ? "server.commands.npc.runtests.notrun" + : (testData.failedRoles.contains(index) ? "server.commands.npc.runtests.fail" : "server.commands.npc.runtests.pass"); + msg.insert(Message.translation(result)); + msg.insert("\n"); + } + + PlayerRef playerRef = store.getComponent(playerReference, PlayerRef.getComponentType()); + + assert playerRef != null; + + playerRef.sendMessage(msg); + npcPlugin.getLogger().at(Level.INFO).log(msg.getRawText()); + } + + public static class NPCTestData implements Component { + private final IntList npcRoles = new IntArrayList(); + private final IntList flockSizes = new IntArrayList(); + private final IntSet failedRoles = new IntOpenHashSet(); + private int index; + private UUID targetUUID; + + public NPCTestData() { + } + + public static ComponentType getComponentType() { + return NPCPlugin.get().getNpcTestDataComponentType(); + } + + @Nonnull + @Override + public Component clone() { + NPCRunTestsCommand.NPCTestData data = new NPCRunTestsCommand.NPCTestData(); + data.npcRoles.addAll(this.npcRoles); + data.index = this.index; + data.failedRoles.addAll(this.failedRoles); + return data; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCSensorStatsCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCSensorStatsCommand.java new file mode 100644 index 0000000..09b378e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCSensorStatsCommand.java @@ -0,0 +1,141 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderInfo; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.RoleStats; +import com.hypixel.hytale.server.npc.systems.PositionCacheSystems; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import java.util.List; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class NPCSensorStatsCommand extends AbstractPlayerCommand { + public NPCSensorStatsCommand() { + super("sensorstats", "server.commands.npc.sensorstats.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + NPCPlugin npcPlugin = NPCPlugin.get(); + List roles = npcPlugin.getRoleTemplateNames(true); + if (roles.isEmpty()) { + context.sendMessage(Message.translation("server.commands.npc.sensorstats.noroles")); + } else { + roles.sort(String::compareToIgnoreCase); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d pos = new Vector3d(transformComponent.getPosition()); + String name = roles.getFirst(); + int roleIndex = NPCPlugin.get().getIndex(name); + if (roleIndex < 0) { + throw new IllegalStateException("No such valid role: " + name); + } else { + Pair, NPCEntity> npcPair = npcPlugin.spawnEntity(store, roleIndex, pos, null, null, null); + NPCEntity npcComponent = npcPair.second(); + StringBuilder out = new StringBuilder(); + RoleStats roleStats = new RoleStats(); + + for (int i = 0; i < roles.size(); i++) { + String roleName = roles.get(i); + + try { + roleStats.clear(); + BuilderInfo builderInfo = NPCPlugin.get().prepareRoleBuilderInfo(NPCPlugin.get().getIndex(roleName)); + Builder roleBuilder = (Builder)builderInfo.getBuilder(); + BuilderSupport builderSupport = new BuilderSupport( + NPCPlugin.get().getBuilderManager(), npcComponent, EntityStore.REGISTRY.newHolder(), new ExecutionContext(), roleBuilder, roleStats + ); + Role role = NPCPlugin.buildRole(roleBuilder, builderInfo, builderSupport, roleIndex); + PositionCacheSystems.initialisePositionCache(role, builderSupport.getStateEvaluator(), 0.0); + } catch (Throwable var22) { + context.sendMessage(Message.translation("server.commands.npc.spawn.templateNotFound").param("template", roleName)); + npcPlugin.getLogger().at(Level.WARNING).log("Error spawning role " + roleName + ": " + var22.getMessage()); + continue; + } + + if (!isRangesEmpty(roleStats, true)) { + out.append('\n').append("PLY "); + formatRanges(out, roleStats, "S=", true, RoleStats.RangeType.SORTED, 25); + formatRanges(out, roleStats, "U=", true, RoleStats.RangeType.UNSORTED, 9); + formatRanges(out, roleStats, "A=", true, RoleStats.RangeType.AVOIDANCE, 9); + formatBuckets(out, roleStats, "B=", true, 20); + out.append(roleName); + } + + if (!isRangesEmpty(roleStats, false)) { + out.append('\n').append("ENT "); + formatRanges(out, roleStats, "S=", false, RoleStats.RangeType.SORTED, 25); + formatRanges(out, roleStats, "U=", false, RoleStats.RangeType.UNSORTED, 9); + formatRanges(out, roleStats, "A=", false, RoleStats.RangeType.AVOIDANCE, 9); + formatBuckets(out, roleStats, "B=", false, 20); + out.append(roleName); + } + } + + npcPlugin.getLogger().at(Level.INFO).log(out.toString()); + npcComponent.remove(); + } + } + } + + private static boolean isRangesEmpty(@Nonnull RoleStats roleStats, boolean isPlayer) { + return roleStats.getRanges(isPlayer, RoleStats.RangeType.SORTED) == null + && roleStats.getRanges(isPlayer, RoleStats.RangeType.UNSORTED) == null + && roleStats.getRanges(isPlayer, RoleStats.RangeType.AVOIDANCE) == null; + } + + private static void formatBuckets(@Nonnull StringBuilder builder, @Nonnull RoleStats roleStats, @Nonnull String label, boolean isPlayer, int width) { + builder.append(label); + int length = builder.length(); + IntArrayList buckets = roleStats.getBuckets(isPlayer); + + for (int i = 0; i < buckets.size(); i++) { + builder.append(buckets.getInt(i)).append(" "); + } + + length = width + length - builder.length(); + if (length > 0) { + builder.append(" ".repeat(length)); + } + } + + private static void formatRanges( + @Nonnull StringBuilder builder, @Nonnull RoleStats roleStats, @Nonnull String label, boolean isPlayer, @Nonnull RoleStats.RangeType rangeType, int width + ) { + builder.append(label); + int length = builder.length(); + int[] ranges = roleStats.getRangesSorted(isPlayer, rangeType); + if (ranges != null && ranges.length != 0) { + for (int range : ranges) { + builder.append(range).append(" "); + } + } else { + builder.append("- "); + } + + length = width + length - builder.length(); + if (length > 0) { + builder.append(" ".repeat(length)); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCSpawnCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCSpawnCommand.java new file mode 100644 index 0000000..8b262e7 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCSpawnCommand.java @@ -0,0 +1,394 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.common.util.RandomUtil; +import com.hypixel.hytale.component.AddReason; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.function.consumer.TriConsumer; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.PlayerSkin; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; +import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule; +import com.hypixel.hytale.server.core.entity.Frozen; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.player.ApplyRandomSkinPersistedComponent; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSkinComponent; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.FlockPlugin; +import com.hypixel.hytale.server.flock.config.FlockAsset; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderInfo; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.pages.EntitySpawnPage; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.RoleDebugFlags; +import com.hypixel.hytale.server.spawning.ISpawnableWithModel; +import com.hypixel.hytale.server.spawning.SpawnTestResult; +import com.hypixel.hytale.server.spawning.SpawningContext; +import it.unimi.dsi.fastutil.Pair; +import java.util.EnumSet; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NPCSpawnCommand extends AbstractPlayerCommand { + private static final double PLAYER_FOOT_POINT_EPSILON = 0.01; + @Nonnull + private final RequiredArg roleArg = this.withRequiredArg("role", "server.commands.npc.spawn.role.desc", NPCCommand.NPC_ROLE); + @Nonnull + private final OptionalArg countArg = this.withOptionalArg("count", "server.commands.npc.spawn.count.desc", ArgTypes.INTEGER) + .addValidator(Validators.greaterThan(0)); + @Nonnull + private final OptionalArg radiusArg = this.withOptionalArg("radius", "server.commands.npc.spawn.radius.desc", ArgTypes.DOUBLE) + .addValidator(Validators.greaterThan(0.0)); + @Nonnull + private final OptionalArg flagsArg = this.withOptionalArg("flags", "server.commands.npc.spawn.flags.desc", ArgTypes.STRING); + @Nonnull + private final OptionalArg speedArg = this.withOptionalArg("speed", "server.commands.npc.spawn.speed.desc", ArgTypes.DOUBLE) + .addValidator(Validators.greaterThan(0.0)); + @Nonnull + private final FlagArg nonRandomArg = this.withFlagArg("nonrandom", "server.commands.npc.spawn.random.desc"); + @Nonnull + private final OptionalArg positionSetArg = this.withOptionalArg("position", "server.commands.npc.spawn.position.desc", ArgTypes.STRING); + @Nonnull + private final OptionalArg posOffsetArg = this.withOptionalArg("posOffset", "server.commands.npc.spawn.posOffset.desc", ArgTypes.STRING); + @Nonnull + private final OptionalArg headRotationArg = this.withOptionalArg("headRotation", "server.commands.npc.spawn.headRotation.desc", ArgTypes.STRING); + @Nonnull + private final OptionalArg bodyRotationArg = this.withOptionalArg("bodyRotation", "server.commands.npc.spawn.bodyRotation.desc", ArgTypes.STRING); + @Nonnull + private final FlagArg randomRotationArg = this.withFlagArg("randomRotation", "server.commands.npc.spawn.randomRotation.desc"); + @Nonnull + private final FlagArg facingRotationArg = this.withFlagArg("facingRotation", "server.commands.npc.spawn.facingRotation.desc"); + @Nonnull + private final OptionalArg flockArg = this.withOptionalArg("flock", "server.commands.npc.spawn.flock.desc", ArgTypes.STRING); + @Nonnull + private final FlagArg testArg = this.withFlagArg("test", "server.commands.npc.spawn.test.desc"); + @Nonnull + private final FlagArg spawnOnGroundArg = this.withFlagArg("spawnOnGround", "server.commands.npc.spawn.spawnOnGround.desc"); + @Nonnull + private final FlagArg frozenArg = this.withFlagArg("frozen", "server.commands.npc.spawn.frozen.desc"); + @Nonnull + private final FlagArg randomModelArg = this.withFlagArg("randomModel", "server.commands.npc.spawn.randomModel.desc"); + @Nonnull + private final OptionalArg scaleArg = this.withOptionalArg("scale", "server.commands.npc.spawn.scale.desc", ArgTypes.FLOAT); + @Nonnull + private final FlagArg bypassScaleLimitsArg = this.withFlagArg("bypassScaleLimits", "server.commands.npc.spawn.bypassScaleLimits.desc"); + + public NPCSpawnCommand() { + super("spawn", "server.commands.npc.spawn.desc"); + this.addUsageVariant(new NPCSpawnCommand.SpawnPageCommand()); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + NPCPlugin npcPlugin = NPCPlugin.get(); + BuilderInfo roleInfo = this.roleArg.get(context); + int roleIndex = roleInfo.getIndex(); + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3f playerHeadRotation = headRotationComponent.getRotation(); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d playerPosition = transformComponent.getPosition(); + BoundingBox boundingBoxComponent = store.getComponent(ref, BoundingBox.getComponentType()); + + assert boundingBoxComponent != null; + + Box playerBoundingBox = boundingBoxComponent.getBoundingBox(); + int count = this.countArg.provided(context) ? this.countArg.get(context) : 1; + double radius = this.radiusArg.provided(context) ? this.radiusArg.get(context) : 8.0; + String flagsString = this.flagsArg.provided(context) ? this.flagsArg.get(context) : null; + EnumSet flags = flagsString != null ? RoleDebugFlags.getFlags(flagsString.split(",")) : RoleDebugFlags.getPreset("none"); + Vector3d velocity = new Vector3d(Vector3d.ZERO); + if (this.speedArg.provided(context)) { + PhysicsMath.vectorFromAngles(playerHeadRotation.getYaw(), playerHeadRotation.getPitch(), velocity); + velocity.setLength(this.speedArg.get(context)); + } + + Random random = (Random)(this.nonRandomArg.get(context) ? new Random(0L) : ThreadLocalRandom.current()); + Vector3d posOffset = this.posOffsetArg.provided(context) ? this.parseVector3d(context, this.posOffsetArg.get(context)) : null; + Vector3f headRotation = this.headRotationArg.provided(context) ? this.parseVector3f(context, this.headRotationArg.get(context)) : null; + boolean randomRotation = false; + Vector3f rotation = playerHeadRotation; + if (this.bodyRotationArg.provided(context)) { + rotation = this.parseVector3f(context, this.bodyRotationArg.get(context)); + } else if (this.randomRotationArg.get(context)) { + randomRotation = true; + } else if (this.facingRotationArg.get(context)) { + playerHeadRotation.setY(playerHeadRotation.getY() - (float) Math.PI); + } + + String flockSizeString = this.flockArg.provided(context) ? this.flockArg.get(context) : "1"; + Integer flockSize = this.parseFlockSize(context, flockSizeString); + if (flockSize != null) { + Boolean frozen = this.frozenArg.get(context); + npcPlugin.forceValidation(roleIndex); + if (!npcPlugin.testAndValidateRole(roleInfo)) { + throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.validation_failed")); + } else { + try { + for (int i = 0; i < count; i++) { + Builder roleBuilder = npcPlugin.tryGetCachedValidRole(roleIndex); + if (roleBuilder == null) { + throw new IllegalArgumentException("Can't find a matching role builder"); + } + + if (!(roleBuilder instanceof ISpawnableWithModel spawnable)) { + throw new IllegalArgumentException("Role builder must support ISpawnableWithModel interface"); + } + + if (!roleBuilder.isSpawnable()) { + throw new IllegalArgumentException("Abstract role templates cannot be spawned directly - a variant needs to be created!"); + } + + SpawningContext spawningContext = new SpawningContext(); + if (!spawningContext.setSpawnable(spawnable)) { + throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.cantSetRolebuilder")); + } + + TriConsumer, Store> skinApplyingFunction = null; + Model model; + if (this.randomModelArg.get(context)) { + PlayerSkin playerSkin = CosmeticsModule.get().generateRandomSkin(RandomUtil.getSecureRandom()); + model = CosmeticsModule.get().createModel(playerSkin); + skinApplyingFunction = (npcEntity, entityStoreRef, entityStore) -> { + entityStore.putComponent(entityStoreRef, PlayerSkinComponent.getComponentType(), new PlayerSkinComponent(playerSkin)); + entityStore.putComponent( + entityStoreRef, ApplyRandomSkinPersistedComponent.getComponentType(), ApplyRandomSkinPersistedComponent.INSTANCE + ); + }; + } else { + model = spawningContext.getModel(); + } + + if (randomRotation) { + rotation = new Vector3f(0.0F, (float)(2.0 * random.nextDouble() * Math.PI), 0.0F); + } + + if (this.scaleArg.provided(context)) { + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(model.getModelAssetId()); + + assert modelAsset != null; + + Float scale = this.scaleArg.get(context); + if (!this.bypassScaleLimitsArg.get(context)) { + scale = MathUtil.clamp(scale, modelAsset.getMinScale(), modelAsset.getMaxScale()); + } + + model = Model.createScaledModel(modelAsset, scale); + } + + Ref npcRef; + NPCEntity npc; + if (count == 1 && this.testArg.get(context)) { + if (!spawningContext.set(world, playerPosition.x, playerPosition.y, playerPosition.z)) { + throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.cantSpawnNotEnoughSpace")); + } + + if (spawnable.canSpawn(spawningContext) != SpawnTestResult.TEST_OK) { + throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.cantSpawnNotSuitable")); + } + + Vector3d spawnPosition = spawningContext.newPosition(); + if (posOffset != null) { + spawnPosition.add(posOffset); + } + + Pair, NPCEntity> npcPair = npcPlugin.spawnEntity(store, roleIndex, spawnPosition, rotation, model, skinApplyingFunction); + npcRef = npcPair.first(); + npc = npcPair.second(); + if (flockSize > 1) { + FlockPlugin.trySpawnFlock(npcRef, npc, store, roleIndex, spawnPosition, rotation, flockSize, skinApplyingFunction); + } + } else { + Vector3d position; + if (this.positionSetArg.provided(context)) { + position = this.parseVector3d(context, this.positionSetArg.get(context)); + if (position == null) { + return; + } + + position.y = position.y - model.getBoundingBox().min.y; + } else { + position = new Vector3d(playerPosition); + position.y = Math.floor(position.y + playerBoundingBox.min.y + 0.01) - model.getBoundingBox().min.y; + } + + if (posOffset != null) { + position.add(posOffset); + } + + Pair, NPCEntity> npcPair = npcPlugin.spawnEntity(store, roleIndex, position, rotation, model, skinApplyingFunction); + npcRef = npcPair.first(); + npc = npcPair.second(); + if (flockSize > 1) { + FlockPlugin.trySpawnFlock(npcRef, npc, store, roleIndex, position, rotation, flockSize, skinApplyingFunction); + } + } + + TransformComponent npcTransformComponent = store.getComponent(npcRef, TransformComponent.getComponentType()); + + assert npcTransformComponent != null; + + HeadRotation npcHeadRotationComponent = store.getComponent(npcRef, HeadRotation.getComponentType()); + + assert npcHeadRotationComponent != null; + + UUIDComponent npcUuidComponent = store.getComponent(npcRef, UUIDComponent.getComponentType()); + + assert npcUuidComponent != null; + + if (headRotation != null) { + npcHeadRotationComponent.getRotation().assign(headRotation); + store.ensureComponent(npcRef, Frozen.getComponentType()); + } + + Vector3d npcPosition = npcTransformComponent.getPosition(); + double x = npcPosition.getX(); + double y = npcPosition.getY(); + double z = npcPosition.getZ(); + if (count > 1) { + x += random.nextDouble() * 2.0 * radius - radius; + z += random.nextDouble() * 2.0 * radius - radius; + y += this.spawnOnGroundArg.get(context) ? 0.1 : random.nextDouble() * 2.0 + 5.0; + } else { + y += 0.1; + } + + npcPosition.assign(x, y, z); + npc.saveLeashInformation(npcPosition, npcTransformComponent.getRotation()); + if (!velocity.equals(Vector3d.ZERO)) { + npc.getRole().forceVelocity(velocity, null, false); + } + + if (frozen) { + store.ensureComponent(npcRef, Frozen.getComponentType()); + } + + EnumSet debugFlags = npc.getRoleDebugFlags().clone(); + debugFlags.addAll(flags); + if (!debugFlags.isEmpty()) { + Holder holder = store.removeEntity(npcRef, RemoveReason.UNLOAD); + npc.setRoleDebugFlags(debugFlags); + store.addEntity(holder, AddReason.LOAD); + } + + NPCPlugin.get() + .getLogger() + .at(Level.INFO) + .log("%s created with id %s at position %s", npc.getRoleName(), npcUuidComponent.getUuid(), Vector3d.formatShortString(npcPosition)); + } + } catch (IllegalStateException | NullPointerException | IllegalArgumentException var49) { + NPCPlugin.get().getLogger().at(Level.WARNING).log("Spawn failed: " + var49.getMessage()); + throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.failed").param("reason", var49.getMessage())); + } + } + } + } + + @Nullable + private Vector3d parseVector3d(@Nonnull CommandContext context, @Nonnull String str) { + String[] parts = str.split(","); + if (parts.length != 3) { + context.sendMessage(Message.raw("Invalid Vector3d format: must be three comma-separated doubles")); + return null; + } else { + try { + return new Vector3d(Double.parseDouble(parts[0]), Double.parseDouble(parts[1]), Double.parseDouble(parts[2])); + } catch (NumberFormatException var5) { + context.sendMessage(Message.raw("Invalid Vector3d format: " + var5.getMessage())); + return null; + } + } + } + + @Nullable + private Vector3f parseVector3f(@Nonnull CommandContext context, @Nonnull String str) { + String[] parts = str.split(","); + if (parts.length != 3) { + context.sendMessage(Message.raw("Invalid Vector3f format: must be three comma-separated floats")); + return null; + } else { + try { + return new Vector3f(Float.parseFloat(parts[0]), Float.parseFloat(parts[1]), Float.parseFloat(parts[2])); + } catch (NumberFormatException var5) { + context.sendMessage(Message.raw("Invalid Vector3f format: " + var5.getMessage())); + return null; + } + } + } + + @Nullable + private Integer parseFlockSize(@Nonnull CommandContext context, @Nonnull String str) { + try { + Integer size = Integer.valueOf(str); + if (size <= 0) { + context.sendMessage(Message.raw("Flock size must be greater than 0!")); + return null; + } else { + return size; + } + } catch (NumberFormatException var5) { + FlockAsset flockDefinition = FlockAsset.getAssetMap().getAsset(str); + if (flockDefinition == null) { + context.sendMessage(Message.raw("No such flock asset: " + str)); + return null; + } else { + return flockDefinition.pickFlockSize(); + } + } + } + + public static class SpawnPageCommand extends AbstractPlayerCommand { + public SpawnPageCommand() { + super("server.commands.npc.spawn.page.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); + + assert playerRefComponent != null; + + playerComponent.getPageManager().openCustomPage(ref, store, new EntitySpawnPage(playerRefComponent)); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCStepCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCStepCommand.java new file mode 100644 index 0000000..af3a7f0 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCStepCommand.java @@ -0,0 +1,94 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.entity.Frozen; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import com.hypixel.hytale.server.npc.components.StepComponent; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NPCStepCommand extends AbstractWorldCommand { + @Nonnull + private final FlagArg allArg = this.withFlagArg("all", "server.commands.npc.step.all"); + @Nonnull + private final EntityWrappedArg entityArg = this.withOptionalArg("entity", "server.commands.entity.entity.desc", ArgTypes.ENTITY_ID); + @Nonnull + private final OptionalArg dtArg = this.withOptionalArg("dt", "server.commands.npc.step.dt.desc", ArgTypes.FLOAT) + .addValidator(Validators.greaterThan(0.0F)); + + public NPCStepCommand() { + super("step", "server.commands.npc.step.desc"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + float dt = this.dtArg.provided(context) ? this.dtArg.get(context) : 1.0F / world.getTps(); + if (this.allArg.get(context)) { + store.forEachEntityParallel(NPCEntity.getComponentType(), (index, archetypeChunk, commandBuffer) -> { + commandBuffer.ensureComponent(archetypeChunk.getReferenceTo(index), Frozen.getComponentType()); + commandBuffer.addComponent(archetypeChunk.getReferenceTo(index), StepComponent.getComponentType(), new StepComponent(dt)); + }); + } else { + NPCEntity npc = this.getNPC(context, store); + if (npc != null) { + Ref ref = npc.getReference(); + if (ref != null && ref.isValid()) { + store.ensureComponent(ref, Frozen.getComponentType()); + store.addComponent(ref, StepComponent.getComponentType(), new StepComponent(dt)); + } + } + } + } + + @Nullable + private NPCEntity getNPC(@Nonnull CommandContext context, @Nonnull Store store) { + Ref ref; + if (this.entityArg.provided(context)) { + ref = this.entityArg.get(store, context); + } else { + if (!context.isPlayer()) { + context.sendMessage(Message.translation("server.commands.errors.playerOrArg").param("option", "entity")); + return null; + } + + Ref playerRef = context.senderAsPlayerRef(); + if (playerRef == null || !playerRef.isValid()) { + context.sendMessage(Message.translation("server.commands.errors.playerOrArg").param("option", "entity")); + return null; + } + + ref = TargetUtil.getTargetEntity(playerRef, store); + if (ref == null) { + context.sendMessage(Message.translation("server.commands.errors.no_entity_in_view").param("option", "entity")); + return null; + } + } + + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + if (npcComponent == null) { + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID uuid = uuidComponent.getUuid(); + context.sendMessage(Message.translation("server.commands.errors.not_npc").param("uuid", uuid.toString())); + return null; + } else { + return npcComponent; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCTestCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCTestCommand.java new file mode 100644 index 0000000..968d9d6 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCTestCommand.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.collision.CollisionModule; +import com.hypixel.hytale.server.core.modules.collision.CollisionResult; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.util.PositionProbeAir; +import com.hypixel.hytale.server.npc.util.PositionProbeWater; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class NPCTestCommand extends AbstractCommandCollection { + public NPCTestCommand() { + super("test", "server.commands.npc.test.desc"); + this.addSubCommand(new NPCTestCommand.ProbeTestCommand()); + } + + public static class ProbeTestCommand extends AbstractPlayerCommand { + public ProbeTestCommand() { + super("probe", "server.commands.npc.test.probe.desc"); + } + + @Override + protected void execute( + @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world + ) { + CollisionResult collisionResult = new CollisionResult(); + PositionProbeAir probeAir = new PositionProbeAir(); + PositionProbeWater probeWater = new PositionProbeWater(); + Player playerComponent = store.getComponent(ref, Player.getComponentType()); + + assert playerComponent != null; + + BoundingBox boundingBoxComponent = store.getComponent(ref, BoundingBox.getComponentType()); + + assert boundingBoxComponent != null; + + Box playerCollider = boundingBoxComponent.getBoundingBox(); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + ModelComponent modelComponent = store.getComponent(ref, ModelComponent.getComponentType()); + + assert modelComponent != null; + + Vector3d position = transformComponent.getPosition(); + Model model = modelComponent.getModel(); + float eyeHeight = model != null ? model.getEyeHeight(ref, store) : 0.0F; + boolean testAir = probeAir.probePosition(ref, playerCollider, position, collisionResult, store); + boolean testWater = probeWater.probePosition(ref, playerCollider, position, collisionResult, eyeHeight, store); + boolean validatePosition = CollisionModule.get() + .validatePosition( + world, + playerCollider, + position, + 4, + null, + (_this, collisionCode, collision, collisionConfig) -> collisionConfig.blockId != -1, + collisionResult + ) + != -1; + NPCPlugin npcPlugin = NPCPlugin.get(); + String text = "Pos Y [" + + position.y + + ", " + + playerCollider + + "] Height=" + + world.getChunk(ChunkUtil.indexChunkFromBlock(position.x, position.z)).getHeight(MathUtil.floor(position.x), MathUtil.floor(position.z)); + context.sendMessage(Message.raw(text)); + npcPlugin.getLogger().at(Level.INFO).log(text); + text = "Air " + testAir + " " + probeAir; + context.sendMessage(Message.raw(text)); + npcPlugin.getLogger().at(Level.INFO).log(text); + text = "Water " + testWater + " " + probeWater; + context.sendMessage(Message.raw(text)); + npcPlugin.getLogger().at(Level.INFO).log(text); + text = "ValidatePosition " + validatePosition; + context.sendMessage(Message.raw(text)); + npcPlugin.getLogger().at(Level.INFO).log(text); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCThawCommand.java b/src/com/hypixel/hytale/server/npc/commands/NPCThawCommand.java new file mode 100644 index 0000000..5afedaa --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCThawCommand.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.entity.Frozen; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import it.unimi.dsi.fastutil.Pair; +import javax.annotation.Nonnull; + +public class NPCThawCommand extends AbstractWorldCommand { + @Nonnull + private static final Message MESSAGE_COMMANDS_NPC_THAW_ALL = Message.translation("server.commands.npc.thaw.all"); + @Nonnull + private final FlagArg allArg = this.withFlagArg("all", "server.commands.npc.thaw.arg.all"); + @Nonnull + private final EntityWrappedArg entityArg = this.withOptionalArg("entity", "server.commands.entity.entity.desc", ArgTypes.ENTITY_ID); + + public NPCThawCommand() { + super("thaw", "server.commands.npc.thaw.desc"); + this.addAliases("unfreeze"); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + if (this.allArg.get(context)) { + store.forEachEntityParallel( + NPCEntity.getComponentType(), + (index, archetypeChunk, commandBuffer) -> commandBuffer.tryRemoveComponent(archetypeChunk.getReferenceTo(index), Frozen.getComponentType()) + ); + context.sendMessage(MESSAGE_COMMANDS_NPC_THAW_ALL); + } else { + Pair, NPCEntity> targetNpcPair = NPCCommandUtils.getTargetNpc(context, this.entityArg, store); + if (targetNpcPair != null) { + Ref targetNpcRef = targetNpcPair.first(); + NPCEntity targetNpcComponent = targetNpcPair.second(); + store.tryRemoveComponent(targetNpcRef, Frozen.getComponentType()); + context.sendMessage(Message.translation("server.commands.npc.thaw.npc").param("role", targetNpcComponent.getRoleName())); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/commands/NPCWorldCommandBase.java b/src/com/hypixel/hytale/server/npc/commands/NPCWorldCommandBase.java new file mode 100644 index 0000000..dda8bf1 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/commands/NPCWorldCommandBase.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.server.npc.commands; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; +import com.hypixel.hytale.server.core.command.system.arguments.types.EntityWrappedArg; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.core.util.TargetUtil; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class NPCWorldCommandBase extends AbstractWorldCommand { + @Nonnull + protected static final Message MESSAGE_COMMANDS_ERRORS_PLAYER_OR_ARG = Message.translation("server.commands.errors.playerOrArg").param("option", "entity"); + @Nonnull + protected static final Message MESSAGE_COMMANDS_ERRORS_NO_ENTITY_IN_VIEW = Message.translation("server.commands.errors.no_entity_in_view") + .param("option", "entity"); + @Nonnull + protected final EntityWrappedArg entityArg = this.withOptionalArg("entity", "server.commands.entity.entity.desc", ArgTypes.ENTITY_ID); + + public NPCWorldCommandBase(@Nonnull String name, @Nonnull String description) { + super(name, description); + } + + public NPCWorldCommandBase(@Nonnull String name, @Nonnull String description, boolean requiresConfirmation) { + super(name, description, requiresConfirmation); + } + + public NPCWorldCommandBase(@Nonnull String description) { + super(description); + } + + @Override + protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { + NPCEntity npc = this.getNPC(context, store); + if (npc != null) { + Ref ref = npc.getReference(); + + assert ref != null; + + assert ref.isValid(); + + this.execute(context, npc, world, store, ref); + } + } + + protected abstract void execute( + @Nonnull CommandContext var1, @Nonnull NPCEntity var2, @Nonnull World var3, @Nonnull Store var4, @Nonnull Ref var5 + ); + + @Nullable + private NPCEntity getNPC(@Nonnull CommandContext context, @Nonnull Store store) { + Ref ref; + if (this.entityArg.provided(context)) { + ref = this.entityArg.get(store, context); + } else { + if (!context.isPlayer()) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_OR_ARG); + return null; + } + + Ref playerRef = context.senderAsPlayerRef(); + if (playerRef == null || !playerRef.isValid()) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_PLAYER_OR_ARG); + return null; + } + + ref = TargetUtil.getTargetEntity(playerRef, store); + if (ref == null) { + context.sendMessage(MESSAGE_COMMANDS_ERRORS_NO_ENTITY_IN_VIEW); + return null; + } + } + + return ref == null ? null : ensureIsNPC(context, store, ref); + } + + @Nullable + protected static NPCEntity ensureIsNPC(@Nonnull CommandContext context, @Nonnull Store store, Ref ref) { + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + if (npcComponent == null) { + UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + UUID uuid = uuidComponent.getUuid(); + context.sendMessage(Message.translation("server.commands.errors.not_npc").param("uuid", uuid.toString())); + return null; + } else { + return npcComponent; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/components/FailedSpawnComponent.java b/src/com/hypixel/hytale/server/npc/components/FailedSpawnComponent.java new file mode 100644 index 0000000..b962c6f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/components/FailedSpawnComponent.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.server.npc.components; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import javax.annotation.Nonnull; + +public class FailedSpawnComponent implements Component { + public static ComponentType getComponentType() { + return NPCPlugin.get().getFailedSpawnComponentType(); + } + + public FailedSpawnComponent() { + } + + @Nonnull + @Override + public Component clone() { + return new FailedSpawnComponent(); + } +} diff --git a/src/com/hypixel/hytale/server/npc/components/SortBufferProviderResource.java b/src/com/hypixel/hytale/server/npc/components/SortBufferProviderResource.java new file mode 100644 index 0000000..41e0646 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/components/SortBufferProviderResource.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.npc.components; + +import com.hypixel.hytale.common.collection.BucketList; +import com.hypixel.hytale.component.Resource; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import javax.annotation.Nonnull; + +public class SortBufferProviderResource implements Resource { + private final BucketList.SortBufferProvider sortBufferProvider = new BucketList.SortBufferProvider(); + + public SortBufferProviderResource() { + } + + public static ResourceType getResourceType() { + return NPCPlugin.get().getSortBufferProviderResourceResourceType(); + } + + @Nonnull + public BucketList.SortBufferProvider getSortBufferProvider() { + return this.sortBufferProvider; + } + + @Nonnull + @Override + public Resource clone() { + return new SortBufferProviderResource(); + } +} diff --git a/src/com/hypixel/hytale/server/npc/components/SpawnBeaconReference.java b/src/com/hypixel/hytale/server/npc/components/SpawnBeaconReference.java new file mode 100644 index 0000000..15e38ed --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/components/SpawnBeaconReference.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.npc.components; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.spawning.SpawningPlugin; +import javax.annotation.Nonnull; + +public class SpawnBeaconReference extends SpawnReference { + public static final BuilderCodec CODEC = BuilderCodec.builder(SpawnBeaconReference.class, SpawnBeaconReference::new, BASE_CODEC) + .build(); + + public SpawnBeaconReference() { + } + + public static ComponentType getComponentType() { + return SpawningPlugin.get().getSpawnBeaconReferenceComponentType(); + } + + @Nonnull + @Override + public Component clone() { + SpawnBeaconReference reference = new SpawnBeaconReference(); + reference.reference = this.reference; + return reference; + } +} diff --git a/src/com/hypixel/hytale/server/npc/components/SpawnMarkerReference.java b/src/com/hypixel/hytale/server/npc/components/SpawnMarkerReference.java new file mode 100644 index 0000000..32b2dfc --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/components/SpawnMarkerReference.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.npc.components; + +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.spawning.SpawningPlugin; +import javax.annotation.Nonnull; + +public class SpawnMarkerReference extends SpawnReference { + public static final BuilderCodec CODEC = BuilderCodec.builder(SpawnMarkerReference.class, SpawnMarkerReference::new, BASE_CODEC) + .build(); + + public SpawnMarkerReference() { + } + + public static ComponentType getComponentType() { + return SpawningPlugin.get().getSpawnMarkerReferenceComponentType(); + } + + @Nonnull + @Override + public Component clone() { + SpawnMarkerReference reference = new SpawnMarkerReference(); + reference.reference = this.reference; + return reference; + } +} diff --git a/src/com/hypixel/hytale/server/npc/components/SpawnReference.java b/src/com/hypixel/hytale/server/npc/components/SpawnReference.java new file mode 100644 index 0000000..4506417 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/components/SpawnReference.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.server.npc.components; + +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.server.core.entity.reference.InvalidatablePersistentRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public abstract class SpawnReference implements Component { + protected static final BuilderCodec BASE_CODEC = BuilderCodec.abstractBuilder(SpawnReference.class) + .append( + new KeyedCodec<>("SpawnMarker", InvalidatablePersistentRef.CODEC), + (reference, entityReference) -> reference.reference = entityReference, + reference -> reference.reference + ) + .add() + .build(); + public static final float MARKER_LOST_TIMEOUT = 30.0F; + protected InvalidatablePersistentRef reference = new InvalidatablePersistentRef(); + private float markerLostTimeoutCounter; + + public SpawnReference() { + } + + public InvalidatablePersistentRef getReference() { + return this.reference; + } + + public boolean tickMarkerLostTimeoutCounter(float dt) { + return (this.markerLostTimeoutCounter -= dt) <= 0.0F; + } + + public void refreshTimeoutCounter() { + this.markerLostTimeoutCounter = 30.0F; + } + + @Override + public abstract Component clone(); +} diff --git a/src/com/hypixel/hytale/server/npc/components/StepComponent.java b/src/com/hypixel/hytale/server/npc/components/StepComponent.java new file mode 100644 index 0000000..e9b6adb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/components/StepComponent.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.npc.components; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import javax.annotation.Nonnull; + +public class StepComponent implements Component { + private final float tickLength; + + public static ComponentType getComponentType() { + return NPCPlugin.get().getStepComponentType(); + } + + public StepComponent(float tickLength) { + this.tickLength = tickLength; + } + + public float getTickLength() { + return this.tickLength; + } + + @Nonnull + @Override + public Component clone() { + return new StepComponent(this.tickLength); + } +} diff --git a/src/com/hypixel/hytale/server/npc/components/Timers.java b/src/com/hypixel/hytale/server/npc/components/Timers.java new file mode 100644 index 0000000..1cfc58f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/components/Timers.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.npc.components; + +import com.hypixel.hytale.common.thread.ticking.Tickable; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import javax.annotation.Nonnull; + +public class Timers implements Component { + private final Tickable[] timers; + + public static ComponentType getComponentType() { + return NPCPlugin.get().getTimersComponentType(); + } + + public Timers(Tickable[] timers) { + this.timers = timers; + } + + public Tickable[] getTimers() { + return this.timers; + } + + @Nonnull + @Override + public Component clone() { + return new Timers(this.timers); + } +} diff --git a/src/com/hypixel/hytale/server/npc/components/messaging/BeaconSupport.java b/src/com/hypixel/hytale/server/npc/components/messaging/BeaconSupport.java new file mode 100644 index 0000000..8caed37 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/components/messaging/BeaconSupport.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.server.npc.components.messaging; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BeaconSupport extends MessageSupport implements Component { + private NPCMessage[] messageSlots; + private Object2IntMap messageIndices; + private Int2ObjectMap indicesToMessages; + + public BeaconSupport() { + } + + public static ComponentType getComponentType() { + return NPCPlugin.get().getBeaconSupportComponentType(); + } + + public void postMessage(String message, Ref target, double age) { + if (this.messageSlots != null) { + if (!(age < 0.0) || age == -1.0) { + int slot = this.messageIndices.getInt(message); + if (slot != Integer.MIN_VALUE && this.messageSlots[slot].isEnabled()) { + this.messageSlots[slot].activate(target, age); + } + } + } + } + + @Nullable + public Ref pollMessage(int messageIndex) { + NPCMessage beacon = this.messageSlots[messageIndex]; + beacon.deactivate(); + return beacon.getTarget(); + } + + @Nullable + public Ref peekMessage(int messageIndex) { + return this.messageSlots[messageIndex].getTarget(); + } + + public void initialise(@Nonnull Object2IntMap messageIndices) { + this.messageIndices = messageIndices; + this.indicesToMessages = new Int2ObjectOpenHashMap<>(); + messageIndices.forEach((key, value) -> this.indicesToMessages.put(value, key)); + NPCMessage[] messages = new NPCMessage[messageIndices.size()]; + + for (int i = 0; i < messages.length; i++) { + messages[i] = new NPCMessage(); + } + + this.messageSlots = messages; + } + + public String getMessageTextForIndex(int messageIndex) { + return this.indicesToMessages.get(messageIndex); + } + + @Override + public NPCMessage[] getMessageSlots() { + return this.messageSlots; + } + + @Nonnull + @Override + public Component clone() { + BeaconSupport beaconSupport = new BeaconSupport(); + beaconSupport.messageSlots = new NPCMessage[this.messageSlots.length]; + + for (int i = 0; i < beaconSupport.messageSlots.length; i++) { + beaconSupport.messageSlots[i] = this.messageSlots[i].clone(); + } + + beaconSupport.messageIndices = this.messageIndices; + return beaconSupport; + } +} diff --git a/src/com/hypixel/hytale/server/npc/components/messaging/EntityEventSupport.java b/src/com/hypixel/hytale/server/npc/components/messaging/EntityEventSupport.java new file mode 100644 index 0000000..d8f8d59 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/components/messaging/EntityEventSupport.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.npc.components.messaging; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.FlockMembership; +import com.hypixel.hytale.server.npc.blackboard.view.event.EntityEventNotification; +import com.hypixel.hytale.server.npc.blackboard.view.event.entity.EntityEventType; +import javax.annotation.Nonnull; + +public abstract class EntityEventSupport extends EventSupport { + public EntityEventSupport() { + } + + public void postMessage( + EntityEventType type, @Nonnull EntityEventNotification notification, @Nonnull Ref parent, @Nonnull Store store + ) { + EventMessage slot = this.getMessageSlot(type, notification); + if (slot != null && slot.isEnabled()) { + Vector3d parentEntityPosition = store.getComponent(parent, TransformComponent.getComponentType()).getPosition(); + Vector3d pos = notification.getPosition(); + double x = pos.getX(); + double y = pos.getY(); + double z = pos.getZ(); + double distanceSquared = parentEntityPosition.distanceSquaredTo(x, y, z); + if (!(distanceSquared > slot.getMaxRangeSquared())) { + FlockMembership flockMembership = store.getComponent(parent, FlockMembership.getComponentType()); + Ref flockReference = flockMembership != null ? flockMembership.getFlockRef() : null; + boolean isSameFlock = flockReference != null && flockReference.equals(notification.getFlockReference()); + if (!slot.isActivated() || distanceSquared < slot.getPosition().distanceSquaredTo(parentEntityPosition) || !slot.isSameFlock() && isSameFlock) { + slot.activate(x, y, z, notification.getInitiator(), 2.0); + slot.setSameFlock(isSameFlock); + } + } + } + } + + public boolean hasFlockMatchingMessage(int messageIndex, @Nonnull Vector3d parentPosition, double range, boolean flockOnly) { + if (!this.isMessageQueued(messageIndex)) { + return false; + } else { + EventMessage event = this.messageSlots[messageIndex]; + return flockOnly && !event.isSameFlock() ? false : event.getPosition().distanceSquaredTo(parentPosition) < range * range; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/components/messaging/EventMessage.java b/src/com/hypixel/hytale/server/npc/components/messaging/EventMessage.java new file mode 100644 index 0000000..593e43f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/components/messaging/EventMessage.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.components.messaging; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; + +public class EventMessage extends NPCMessage { + private final Vector3d position = new Vector3d(); + private final double maxRangeSquared; + private boolean sameFlock; + + public EventMessage(double maxRange) { + this.maxRangeSquared = maxRange * maxRange; + } + + private EventMessage(@Nonnull Vector3d position, double maxRangeSquared, boolean sameFlock) { + this.position.assign(position); + this.maxRangeSquared = maxRangeSquared; + this.sameFlock = sameFlock; + } + + @Nonnull + public Vector3d getPosition() { + return this.position; + } + + public double getMaxRangeSquared() { + return this.maxRangeSquared; + } + + public boolean isSameFlock() { + return this.sameFlock; + } + + public void setSameFlock(boolean sameFlock) { + this.sameFlock = sameFlock; + } + + public void activate(double x, double y, double z, Ref target, double age) { + super.activate(target, age); + this.position.assign(x, y, z); + } + + @Nonnull + public EventMessage clone() { + return new EventMessage(this.position, this.maxRangeSquared, this.sameFlock); + } +} diff --git a/src/com/hypixel/hytale/server/npc/components/messaging/EventSupport.java b/src/com/hypixel/hytale/server/npc/components/messaging/EventSupport.java new file mode 100644 index 0000000..3719184 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/components/messaging/EventSupport.java @@ -0,0 +1,95 @@ +package com.hypixel.hytale.server.npc.components.messaging; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.blackboard.view.event.EventNotification; +import it.unimi.dsi.fastutil.ints.Int2DoubleMap; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class EventSupport, NotificationType extends EventNotification> extends MessageSupport { + protected static final double EVENT_AGE = 2.0; + protected EventMessage[] messageSlots; + protected Map messageIndices; + + public EventSupport() { + } + + public void postMessage(EventType type, @Nonnull NotificationType notification, @Nonnull Ref parent, @Nonnull Store store) { + EventMessage slot = this.getMessageSlot(type, notification); + if (slot != null && slot.isEnabled()) { + Vector3d parentEntityPosition = store.getComponent(parent, TransformComponent.getComponentType()).getPosition(); + Vector3d pos = notification.getPosition(); + double x = pos.getX(); + double y = pos.getY(); + double z = pos.getZ(); + double distanceSquared = parentEntityPosition.distanceSquaredTo(x, y, z); + if (distanceSquared <= slot.getMaxRangeSquared() + && (!slot.isActivated() || distanceSquared < slot.getPosition().distanceSquaredTo(parentEntityPosition))) { + slot.activate(x, y, z, notification.getInitiator(), 2.0); + } + } + } + + @Nullable + public EventMessage getMessageSlot(EventType type, @Nonnull NotificationType notification) { + if (this.messageSlots == null) { + return null; + } else { + Int2IntMap typeSlots = this.messageIndices.get(type); + if (typeSlots == null) { + return null; + } else { + int slotIdx = typeSlots.get(notification.getSet()); + return slotIdx == Integer.MIN_VALUE ? null : this.messageSlots[slotIdx]; + } + } + } + + public boolean hasMatchingMessage(int messageIndex, @Nonnull Vector3d parentPosition, double range) { + if (!this.isMessageQueued(messageIndex)) { + return false; + } else { + EventMessage event = this.messageSlots[messageIndex]; + return event.getPosition().distanceSquaredTo(parentPosition) < range * range; + } + } + + @Nullable + public Ref pollMessage(int messageIndex) { + EventMessage event = this.messageSlots[messageIndex]; + event.deactivate(); + return event.getTarget(); + } + + public void initialise(Map setIndices, @Nonnull Int2DoubleMap messageRanges, int count) { + this.messageIndices = setIndices; + EventMessage[] messages = new EventMessage[count]; + + for (int i = 0; i < messages.length; i++) { + messages[i] = new EventMessage(messageRanges.get(i)); + } + + this.messageSlots = messages; + } + + public void cloneTo(@Nonnull EventSupport other) { + other.messageSlots = new EventMessage[this.messageSlots.length]; + + for (int i = 0; i < other.messageSlots.length; i++) { + other.messageSlots[i] = this.messageSlots[i].clone(); + } + + other.messageIndices = this.messageIndices; + } + + @Override + public NPCMessage[] getMessageSlots() { + return this.messageSlots; + } +} diff --git a/src/com/hypixel/hytale/server/npc/components/messaging/MessageSupport.java b/src/com/hypixel/hytale/server/npc/components/messaging/MessageSupport.java new file mode 100644 index 0000000..8db9684 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/components/messaging/MessageSupport.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.server.npc.components.messaging; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +public abstract class MessageSupport implements Component { + public MessageSupport() { + } + + public abstract NPCMessage[] getMessageSlots(); + + public boolean isMessageQueued(int messageIndex) { + return this.getMessageSlots() == null ? false : this.getMessageSlots()[messageIndex].isActivated(); + } + + public boolean isMessageEnabled(int messageIndex) { + return this.getMessageSlots() == null ? false : this.getMessageSlots()[messageIndex].isEnabled(); + } + + @Override + public abstract Component clone(); +} diff --git a/src/com/hypixel/hytale/server/npc/components/messaging/NPCBlockEventSupport.java b/src/com/hypixel/hytale/server/npc/components/messaging/NPCBlockEventSupport.java new file mode 100644 index 0000000..d9447b6 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/components/messaging/NPCBlockEventSupport.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.npc.components.messaging; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.blackboard.view.event.EventNotification; +import com.hypixel.hytale.server.npc.blackboard.view.event.block.BlockEventType; +import javax.annotation.Nonnull; + +public class NPCBlockEventSupport extends EventSupport implements Component { + public NPCBlockEventSupport() { + } + + public static ComponentType getComponentType() { + return NPCPlugin.get().getNpcBlockEventSupportComponentType(); + } + + @Nonnull + @Override + public Component clone() { + NPCBlockEventSupport support = new NPCBlockEventSupport(); + this.cloneTo(support); + return support; + } +} diff --git a/src/com/hypixel/hytale/server/npc/components/messaging/NPCEntityEventSupport.java b/src/com/hypixel/hytale/server/npc/components/messaging/NPCEntityEventSupport.java new file mode 100644 index 0000000..262610d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/components/messaging/NPCEntityEventSupport.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.npc.components.messaging; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import javax.annotation.Nonnull; + +public class NPCEntityEventSupport extends EntityEventSupport implements Component { + public NPCEntityEventSupport() { + } + + public static ComponentType getComponentType() { + return NPCPlugin.get().getNpcEntityEventSupportComponentType(); + } + + @Nonnull + @Override + public Component clone() { + NPCEntityEventSupport support = new NPCEntityEventSupport(); + this.cloneTo(support); + return support; + } +} diff --git a/src/com/hypixel/hytale/server/npc/components/messaging/NPCMessage.java b/src/com/hypixel/hytale/server/npc/components/messaging/NPCMessage.java new file mode 100644 index 0000000..d2cb013 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/components/messaging/NPCMessage.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.npc.components.messaging; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NPCMessage { + public static final double AGE_INFINITE = -1.0; + private boolean enabled = true; + private boolean activated = false; + private double age; + private Ref target; + + public NPCMessage() { + } + + public boolean tickAge(float dt) { + return (this.age -= dt) <= 0.0; + } + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isActivated() { + return this.activated; + } + + public boolean isInfinite() { + return this.age == -1.0; + } + + @Nullable + public Ref getTarget() { + return this.target != null && this.target.isValid() ? this.target : null; + } + + public void activate(Ref target, double age) { + this.age = age; + this.activated = true; + this.target = target; + } + + public void deactivate() { + this.activated = false; + } + + @Nonnull + public NPCMessage clone() { + NPCMessage message = new NPCMessage(); + message.enabled = this.enabled; + message.activated = this.activated; + message.age = this.age; + message.target = this.target; + return message; + } +} diff --git a/src/com/hypixel/hytale/server/npc/components/messaging/PlayerBlockEventSupport.java b/src/com/hypixel/hytale/server/npc/components/messaging/PlayerBlockEventSupport.java new file mode 100644 index 0000000..f7a97fe --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/components/messaging/PlayerBlockEventSupport.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.npc.components.messaging; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.blackboard.view.event.EventNotification; +import com.hypixel.hytale.server.npc.blackboard.view.event.block.BlockEventType; +import javax.annotation.Nonnull; + +public class PlayerBlockEventSupport extends EventSupport implements Component { + public PlayerBlockEventSupport() { + } + + public static ComponentType getComponentType() { + return NPCPlugin.get().getPlayerBlockEventSupportComponentType(); + } + + @Nonnull + @Override + public Component clone() { + PlayerBlockEventSupport support = new PlayerBlockEventSupport(); + this.cloneTo(support); + return support; + } +} diff --git a/src/com/hypixel/hytale/server/npc/components/messaging/PlayerEntityEventSupport.java b/src/com/hypixel/hytale/server/npc/components/messaging/PlayerEntityEventSupport.java new file mode 100644 index 0000000..b1f6ccd --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/components/messaging/PlayerEntityEventSupport.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.npc.components.messaging; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import javax.annotation.Nonnull; + +public class PlayerEntityEventSupport extends EntityEventSupport implements Component { + public PlayerEntityEventSupport() { + } + + public static ComponentType getComponentType() { + return NPCPlugin.get().getPlayerEntityEventSupportComponentType(); + } + + @Nonnull + @Override + public Component clone() { + PlayerEntityEventSupport support = new PlayerEntityEventSupport(); + this.cloneTo(support); + return support; + } +} diff --git a/src/com/hypixel/hytale/server/npc/config/AttitudeGroup.java b/src/com/hypixel/hytale/server/npc/config/AttitudeGroup.java new file mode 100644 index 0000000..5da1f6f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/config/AttitudeGroup.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.server.npc.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import java.util.Collections; +import java.util.Map; + +public class AttitudeGroup implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + AttitudeGroup.class, AttitudeGroup::new, Codec.STRING, (t, k) -> t.id = k, t -> t.id, (asset, data) -> asset.data = data, asset -> asset.data + ) + .documentation("Defines attitudes towards specific groups of NPCs.") + .>append( + new KeyedCodec<>("Groups", new EnumMapCodec<>(Attitude.class, Codec.STRING_ARRAY)), + (group, map) -> group.attitudeGroups = map, + group -> group.attitudeGroups + ) + .documentation("A map of attitudes to NPC groups.") + .add() + .build(); + private static IndexedLookupTableAssetMap ASSET_MAP; + protected AssetExtraInfo.Data data; + protected String id; + protected Map attitudeGroups = Collections.emptyMap(); + + public static IndexedLookupTableAssetMap getAssetMap() { + if (ASSET_MAP == null) { + ASSET_MAP = (IndexedLookupTableAssetMap)AssetRegistry.getAssetStore(AttitudeGroup.class).getAssetMap(); + } + + return ASSET_MAP; + } + + public AttitudeGroup(String id) { + this.id = id; + } + + protected AttitudeGroup() { + } + + public String getId() { + return this.id; + } + + public Map getAttitudeGroups() { + return this.attitudeGroups; + } +} diff --git a/src/com/hypixel/hytale/server/npc/config/ItemAttitudeGroup.java b/src/com/hypixel/hytale/server/npc/config/ItemAttitudeGroup.java new file mode 100644 index 0000000..c3de25e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/config/ItemAttitudeGroup.java @@ -0,0 +1,94 @@ +package com.hypixel.hytale.server.npc.config; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec; +import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.codecs.map.EnumMapCodec; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import java.util.Collections; +import java.util.EnumMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class ItemAttitudeGroup implements JsonAssetWithMap> { + public static final AssetBuilderCodec CODEC = AssetBuilderCodec.builder( + ItemAttitudeGroup.class, ItemAttitudeGroup::new, Codec.STRING, (t, k) -> t.id = k, t -> t.id, (asset, data) -> asset.data = data, asset -> asset.data + ) + .documentation("Defines attitudes towards specific items by tag.") + .>append( + new KeyedCodec<>("Attitudes", new EnumMapCodec<>(ItemAttitudeGroup.Sentiment.class, Codec.STRING_ARRAY)), + (group, map) -> group.sentiments = map, + group -> group.sentiments + ) + .documentation("A map of attitudes to item tags.") + .add() + .afterDecode(itemAttitudeGroup -> { + if (!itemAttitudeGroup.sentiments.isEmpty()) { + itemAttitudeGroup.attitudes = new EnumMap<>(Attitude.class); + + for (Entry entry : itemAttitudeGroup.sentiments.entrySet()) { + itemAttitudeGroup.attitudes.put(entry.getKey().attitude, entry.getValue()); + } + } + }) + .build(); + private static IndexedLookupTableAssetMap ASSET_MAP; + protected AssetExtraInfo.Data data; + protected String id; + protected Map sentiments = Collections.emptyMap(); + @Nonnull + protected Map attitudes = Collections.emptyMap(); + + public static IndexedLookupTableAssetMap getAssetMap() { + if (ASSET_MAP == null) { + ASSET_MAP = (IndexedLookupTableAssetMap)AssetRegistry.getAssetStore(ItemAttitudeGroup.class).getAssetMap(); + } + + return ASSET_MAP; + } + + public ItemAttitudeGroup(String id) { + this.id = id; + } + + protected ItemAttitudeGroup() { + } + + public String getId() { + return this.id; + } + + @Nonnull + public Map getAttitudes() { + return this.attitudes; + } + + public static enum Sentiment implements Supplier { + Ignore(Attitude.IGNORE), + Dislike(Attitude.HOSTILE), + Neutral(Attitude.NEUTRAL), + Like(Attitude.FRIENDLY), + Love(Attitude.REVERED); + + private final Attitude attitude; + + private Sentiment(Attitude attitude) { + this.attitude = attitude; + } + + @Nonnull + public String get() { + return this.name(); + } + + public Attitude getAttitude() { + return this.attitude; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/config/balancing/BalanceAsset.java b/src/com/hypixel/hytale/server/npc/config/balancing/BalanceAsset.java new file mode 100644 index 0000000..e3f5921 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/config/balancing/BalanceAsset.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.server.npc.config.balancing; + +import com.hypixel.hytale.assetstore.AssetExtraInfo; +import com.hypixel.hytale.assetstore.AssetKeyValidator; +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.assetstore.AssetStore; +import com.hypixel.hytale.assetstore.codec.AssetCodecMapCodec; +import com.hypixel.hytale.assetstore.codec.ContainedAssetCodec; +import com.hypixel.hytale.assetstore.map.DefaultAssetMap; +import com.hypixel.hytale.assetstore.map.JsonAssetWithMap; +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.lookup.Priority; +import com.hypixel.hytale.codec.validation.ValidatorCache; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import javax.annotation.Nonnull; + +public class BalanceAsset implements JsonAssetWithMap> { + public static final BuilderCodec ABSTRACT_CODEC = BuilderCodec.abstractBuilder(BalanceAsset.class) + .documentation("Defines various parameters for NPCs relating to combat balancing.") + .build(); + public static final BuilderCodec BASE_CODEC = BuilderCodec.builder(BalanceAsset.class, BalanceAsset::new, ABSTRACT_CODEC) + .appendInherited( + new KeyedCodec<>("EntityEffect", EntityEffect.CHILD_ASSET_CODEC), + (e, s) -> e.entityEffect = s, + e -> e.entityEffect, + (e, p) -> e.entityEffect = p.entityEffect + ) + .addValidator(EntityEffect.VALIDATOR_CACHE.getValidator()) + .documentation("An entity effect to apply to the NPC at spawn time.") + .add() + .build(); + public static final AssetCodecMapCodec CODEC = new AssetCodecMapCodec( + Codec.STRING, (t, k) -> t.id = k, t -> t.id, (t, data) -> t.data = data, t -> t.data, true + ) + .register(Priority.DEFAULT, "Default", BalanceAsset.class, BASE_CODEC); + public static final Codec CHILD_ASSET_CODEC = new ContainedAssetCodec<>(BalanceAsset.class, CODEC); + public static final ValidatorCache VALIDATOR_CACHE = new ValidatorCache<>(new AssetKeyValidator<>(BalanceAsset::getAssetStore)); + private static AssetStore> ASSET_STORE; + private AssetExtraInfo.Data data; + protected String id; + protected String entityEffect; + + public static AssetStore> getAssetStore() { + if (ASSET_STORE == null) { + ASSET_STORE = AssetRegistry.getAssetStore(BalanceAsset.class); + } + + return ASSET_STORE; + } + + public static DefaultAssetMap getAssetMap() { + return (DefaultAssetMap)getAssetStore().getAssetMap(); + } + + protected BalanceAsset() { + } + + public String getId() { + return this.id; + } + + public String getEntityEffect() { + return this.entityEffect; + } + + @Nonnull + @Override + public String toString() { + return "BalanceAsset{data=" + this.data + ", id='" + this.id + "', entityEffect='" + this.entityEffect + "'}"; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/ActionBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/ActionBase.java new file mode 100644 index 0000000..828a644 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/ActionBase.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.server.npc.corecomponents; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.instructions.Action; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public abstract class ActionBase extends AnnotatedComponentBase implements Action { + protected boolean once; + protected boolean triggered; + protected boolean active; + + public ActionBase(@Nonnull BuilderActionBase builderActionBase) { + this.once = builderActionBase.isOnce(); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return !this.once || !this.triggered; + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + this.setOnce(); + return true; + } + + @Override + public void activate(Role role, InfoProvider infoProvider) { + this.active = true; + } + + @Override + public void deactivate(Role role, InfoProvider infoProvider) { + this.active = false; + } + + @Override + public boolean isActivated() { + return this.active; + } + + @Override + public boolean isTriggered() { + return this.triggered; + } + + @Override + public void clearOnce() { + this.triggered = false; + } + + @Override + public void setOnce() { + this.triggered = true; + } + + @Override + public boolean processDelay(float dt) { + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/ActionWithDelay.java b/src/com/hypixel/hytale/server/npc/corecomponents/ActionWithDelay.java new file mode 100644 index 0000000..cf5f9b4 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/ActionWithDelay.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.npc.corecomponents; + +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionWithDelay; +import com.hypixel.hytale.server.npc.role.support.EntitySupport; +import javax.annotation.Nonnull; + +public abstract class ActionWithDelay extends ActionBase { + private final double[] delayRange; + private double delay; + private boolean isDelaying; + + public ActionWithDelay(@Nonnull BuilderActionWithDelay builder, @Nonnull BuilderSupport support) { + super(builder); + this.delayRange = builder.getDelayRange(support); + } + + @Override + public boolean processDelay(float dt) { + if (!this.isDelaying) { + return true; + } else { + if ((this.delay -= dt) <= 0.0) { + this.isDelaying = false; + } + + return !this.isDelaying; + } + } + + protected boolean isDelaying() { + return this.isDelaying; + } + + protected boolean isDelayPrepared() { + return this.delay > 0.0; + } + + protected void prepareDelay() { + this.delay = RandomExtra.randomRange(this.delayRange); + this.isDelaying = false; + } + + protected void clearDelay() { + this.delay = 0.0; + this.isDelaying = false; + } + + protected void startDelay(@Nonnull EntitySupport support) { + support.registerDelay(this); + this.isDelaying = true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/AnnotatedComponentBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/AnnotatedComponentBase.java new file mode 100644 index 0000000..82a105c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/AnnotatedComponentBase.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.npc.corecomponents; + +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.util.ComponentInfo; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponent; + +public abstract class AnnotatedComponentBase implements IAnnotatedComponent { + protected IAnnotatedComponent parent; + protected int index; + + public AnnotatedComponentBase() { + } + + @Override + public void getInfo(Role role, ComponentInfo holder) { + } + + @Override + public void setContext(IAnnotatedComponent parent, int index) { + this.parent = parent; + this.index = index; + } + + @Override + public IAnnotatedComponent getParent() { + return this.parent; + } + + @Override + public int getIndex() { + return this.index; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/BlockTarget.java b/src/com/hypixel/hytale/server/npc/corecomponents/BlockTarget.java new file mode 100644 index 0000000..288393e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/BlockTarget.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.server.npc.corecomponents; + +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.npc.blackboard.Blackboard; +import com.hypixel.hytale.server.npc.blackboard.view.resource.ResourceView; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockTarget { + private final Vector3d position = new Vector3d(Vector3d.MIN); + private int chunkChangeRevision = -1; + private int foundBlockType = Integer.MIN_VALUE; + @Nullable + private ResourceView reservationHolder; + + public BlockTarget() { + } + + @Nonnull + public Vector3d getPosition() { + return this.position; + } + + public int getChunkChangeRevision() { + return this.chunkChangeRevision; + } + + public int getFoundBlockType() { + return this.foundBlockType; + } + + public void setChunkChangeRevision(int chunkChangeRevision) { + this.chunkChangeRevision = chunkChangeRevision; + } + + public void setFoundBlockType(int foundBlockType) { + this.foundBlockType = foundBlockType; + } + + public void setReservationHolder(ResourceView resourceView) { + this.reservationHolder = resourceView; + } + + public void reset(@Nonnull NPCEntity parent) { + if (this.reservationHolder != null) { + this.reservationHolder.clearReservation(parent.getReference()); + Blackboard.LOGGER.at(Level.FINE).log("Entity %s cleared reservation at %s", parent.getRoleName(), this.position); + } + + this.reservationHolder = null; + this.position.assign(Vector3d.MIN); + this.chunkChangeRevision = -1; + this.foundBlockType = Integer.MIN_VALUE; + } + + public boolean isActive() { + return this.foundBlockType >= 0; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/BodyMotionBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/BodyMotionBase.java new file mode 100644 index 0000000..62bdffc --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/BodyMotionBase.java @@ -0,0 +1,10 @@ +package com.hypixel.hytale.server.npc.corecomponents; + +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderBodyMotionBase; +import com.hypixel.hytale.server.npc.instructions.BodyMotion; +import javax.annotation.Nonnull; + +public abstract class BodyMotionBase extends MotionBase implements BodyMotion { + public BodyMotionBase(@Nonnull BuilderBodyMotionBase builderMotionBase) { + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/EntityFilterBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/EntityFilterBase.java new file mode 100644 index 0000000..18e6f0d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/EntityFilterBase.java @@ -0,0 +1,6 @@ +package com.hypixel.hytale.server.npc.corecomponents; + +public abstract class EntityFilterBase extends AnnotatedComponentBase implements IEntityFilter { + public EntityFilterBase() { + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/HeadMotionBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/HeadMotionBase.java new file mode 100644 index 0000000..cb553e3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/HeadMotionBase.java @@ -0,0 +1,9 @@ +package com.hypixel.hytale.server.npc.corecomponents; + +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderHeadMotionBase; +import com.hypixel.hytale.server.npc.instructions.HeadMotion; + +public abstract class HeadMotionBase extends MotionBase implements HeadMotion { + public HeadMotionBase(BuilderHeadMotionBase builderMotionBase) { + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/IEntityFilter.java b/src/com/hypixel/hytale/server/npc/corecomponents/IEntityFilter.java new file mode 100644 index 0000000..77b5239 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/IEntityFilter.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.npc.corecomponents; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.instructions.RoleStateChange; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponent; +import java.util.Arrays; +import java.util.Comparator; +import javax.annotation.Nonnull; + +public interface IEntityFilter extends RoleStateChange, IAnnotatedComponent { + IEntityFilter[] EMPTY_ARRAY = new IEntityFilter[0]; + int MINIMAL_COST = 0; + int LOW_COST = 100; + int MID_COST = 200; + int HIGH_COST = 300; + int EXTREME_COST = 400; + + boolean matchesEntity(@Nonnull Ref var1, @Nonnull Ref var2, @Nonnull Role var3, @Nonnull Store var4); + + int cost(); + + static void prioritiseFilters(@Nonnull IEntityFilter[] filters) { + Arrays.sort(filters, Comparator.comparingInt(IEntityFilter::cost)); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/ISensorEntityCollector.java b/src/com/hypixel/hytale/server/npc/corecomponents/ISensorEntityCollector.java new file mode 100644 index 0000000..eea1875 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/ISensorEntityCollector.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.server.npc.corecomponents; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.instructions.RoleStateChange; +import com.hypixel.hytale.server.npc.role.Role; +import javax.annotation.Nonnull; + +public interface ISensorEntityCollector extends RoleStateChange { + ISensorEntityCollector DEFAULT = new ISensorEntityCollector() { + @Override + public void init(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + } + + @Override + public void collectMatching(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor) { + } + + @Override + public void collectNonMatching(@Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor) { + } + + @Override + public boolean terminateOnFirstMatch() { + return true; + } + + @Override + public void cleanup() { + } + }; + + void init(@Nonnull Ref var1, @Nonnull Role var2, @Nonnull ComponentAccessor var3); + + void collectMatching(@Nonnull Ref var1, @Nonnull Ref var2, @Nonnull ComponentAccessor var3); + + void collectNonMatching(@Nonnull Ref var1, @Nonnull ComponentAccessor var2); + + boolean terminateOnFirstMatch(); + + void cleanup(); +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/ISensorEntityPrioritiser.java b/src/com/hypixel/hytale/server/npc/corecomponents/ISensorEntityPrioritiser.java new file mode 100644 index 0000000..420e7ad --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/ISensorEntityPrioritiser.java @@ -0,0 +1,24 @@ +package com.hypixel.hytale.server.npc.corecomponents; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.instructions.RoleStateChange; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.util.IEntityByPriorityFilter; +import java.util.List; + +public interface ISensorEntityPrioritiser extends RoleStateChange { + IEntityByPriorityFilter getNPCPrioritiser(); + + IEntityByPriorityFilter getPlayerPrioritiser(); + + Ref pickTarget( + Ref var1, Role var2, Vector3d var3, Ref var4, Ref var5, boolean var6, Store var7 + ); + + boolean providesFilters(); + + void buildProvidedFilters(List var1); +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/MotionBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/MotionBase.java new file mode 100644 index 0000000..4c5fcb2 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/MotionBase.java @@ -0,0 +1,8 @@ +package com.hypixel.hytale.server.npc.corecomponents; + +import com.hypixel.hytale.server.npc.instructions.Motion; + +public abstract class MotionBase extends AnnotatedComponentBase implements Motion { + public MotionBase() { + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/SensorBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/SensorBase.java new file mode 100644 index 0000000..e9616c6 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/SensorBase.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.server.npc.corecomponents; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.role.Role; +import javax.annotation.Nonnull; + +public abstract class SensorBase extends AnnotatedComponentBase implements Sensor { + protected final boolean once; + protected boolean triggered; + + public SensorBase(@Nonnull BuilderSensorBase builderSensorBase) { + this.once = builderSensorBase.getOnce(); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + return !this.once || !this.triggered; + } + + @Override + public void clearOnce() { + this.triggered = false; + } + + @Override + public void setOnce() { + this.triggered = true; + } + + @Override + public boolean isTriggered() { + return this.triggered; + } + + @Override + public boolean processDelay(float dt) { + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/SensorWithEntityFilters.java b/src/com/hypixel/hytale/server/npc/corecomponents/SensorWithEntityFilters.java new file mode 100644 index 0000000..1050214 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/SensorWithEntityFilters.java @@ -0,0 +1,109 @@ +package com.hypixel.hytale.server.npc.corecomponents; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponent; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponentCollection; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class SensorWithEntityFilters extends SensorBase implements IAnnotatedComponentCollection { + @Nonnull + private final IEntityFilter[] filters; + + public SensorWithEntityFilters(@Nonnull BuilderSensorBase builderSensorBase, @Nonnull IEntityFilter[] filters) { + super(builderSensorBase); + this.filters = filters; + IEntityFilter.prioritiseFilters(this.filters); + } + + @Override + public void registerWithSupport(Role role) { + for (IEntityFilter filter : this.filters) { + filter.registerWithSupport(role); + } + } + + @Override + public void motionControllerChanged( + @Nullable Ref ref, + @Nonnull NPCEntity npcComponent, + MotionController motionController, + @Nullable ComponentAccessor componentAccessor + ) { + for (IEntityFilter filter : this.filters) { + filter.motionControllerChanged(ref, npcComponent, motionController, componentAccessor); + } + } + + @Override + public void loaded(Role role) { + for (IEntityFilter filter : this.filters) { + filter.loaded(role); + } + } + + @Override + public void spawned(Role role) { + for (IEntityFilter filter : this.filters) { + filter.spawned(role); + } + } + + @Override + public void unloaded(Role role) { + for (IEntityFilter filter : this.filters) { + filter.unloaded(role); + } + } + + @Override + public void removed(Role role) { + for (IEntityFilter filter : this.filters) { + filter.removed(role); + } + } + + @Override + public void teleported(Role role, World from, World to) { + for (IEntityFilter filter : this.filters) { + filter.teleported(role, from, to); + } + } + + @Override + public int componentCount() { + return this.filters.length; + } + + @Override + public IAnnotatedComponent getComponent(int index) { + return this.filters[index]; + } + + @Override + public void setContext(IAnnotatedComponent parent, int index) { + super.setContext(parent, index); + + for (int i = 0; i < this.filters.length; i++) { + this.filters[i].setContext(this, i); + } + } + + protected boolean matchesFilters(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + for (IEntityFilter filter : this.filters) { + if (!filter.matchesEntity(ref, targetRef, role, store)) { + return false; + } + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/WeightedAction.java b/src/com/hypixel/hytale/server/npc/corecomponents/WeightedAction.java new file mode 100644 index 0000000..76e8814 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/WeightedAction.java @@ -0,0 +1,122 @@ +package com.hypixel.hytale.server.npc.corecomponents; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderWeightedAction; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.instructions.Action; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.ComponentInfo; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class WeightedAction extends AnnotatedComponentBase implements Action { + @Nullable + private final Action action; + private final double weight; + + public WeightedAction(@Nonnull BuilderWeightedAction builder, @Nonnull BuilderSupport support) { + this.action = builder.getAction(support); + this.weight = builder.getWeight(support); + } + + public double getWeight() { + return this.weight; + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return this.action.canExecute(ref, role, sensorInfo, dt, store); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return this.action.execute(ref, role, sensorInfo, dt, store); + } + + @Override + public void activate(Role role, InfoProvider infoProvider) { + this.action.activate(role, infoProvider); + } + + @Override + public void deactivate(Role role, InfoProvider infoProvider) { + this.action.deactivate(role, infoProvider); + } + + @Override + public boolean isActivated() { + return this.action.isActivated(); + } + + @Override + public void getInfo(Role role, ComponentInfo holder) { + this.action.getInfo(role, holder); + } + + @Override + public boolean processDelay(float dt) { + return this.action.processDelay(dt); + } + + @Override + public void clearOnce() { + this.action.clearOnce(); + } + + @Override + public void setOnce() { + this.action.setOnce(); + } + + @Override + public boolean isTriggered() { + return this.action.isTriggered(); + } + + @Override + public void registerWithSupport(Role role) { + this.action.registerWithSupport(role); + } + + @Override + public void motionControllerChanged( + @Nullable Ref ref, + @Nonnull NPCEntity npcComponent, + MotionController motionController, + @Nullable ComponentAccessor componentAccessor + ) { + this.action.motionControllerChanged(ref, npcComponent, motionController, componentAccessor); + } + + @Override + public void loaded(Role role) { + this.action.loaded(role); + } + + @Override + public void spawned(Role role) { + this.action.spawned(role); + } + + @Override + public void unloaded(Role role) { + this.action.unloaded(role); + } + + @Override + public void removed(Role role) { + this.action.removed(role); + } + + @Override + public void teleported(Role role, World from, World to) { + this.action.teleported(role, from, to); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionAppearance.java b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionAppearance.java new file mode 100644 index 0000000..b96bca7 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionAppearance.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.server.npc.corecomponents.audiovisual; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionAppearance; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionAppearance extends ActionBase { + protected final String appearance; + + public ActionAppearance(@Nonnull BuilderActionAppearance builderActionAppearance) { + super(builderActionAppearance); + this.appearance = builderActionAppearance.getAppearance(); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && this.appearance != null && !this.appearance.isEmpty(); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + NPCEntity.setAppearance(ref, this.appearance, store); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionDisplayName.java b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionDisplayName.java new file mode 100644 index 0000000..5d980a8 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionDisplayName.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.server.npc.corecomponents.audiovisual; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionDisplayName; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionDisplayName extends ActionBase { + protected final String displayName; + + public ActionDisplayName(@Nonnull BuilderActionDisplayName builder, @Nonnull BuilderSupport support) { + super(builder); + this.displayName = builder.getDisplayName(support); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && this.displayName != null; + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + role.getEntitySupport().nominateDisplayName(this.displayName); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionModelAttachment.java b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionModelAttachment.java new file mode 100644 index 0000000..3b05283 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionModelAttachment.java @@ -0,0 +1,72 @@ +package com.hypixel.hytale.server.npc.corecomponents.audiovisual; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionModelAttachment; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionModelAttachment extends ActionBase { + @Nonnull + protected final String slot; + @Nonnull + protected final String attachment; + + public ActionModelAttachment(@Nonnull BuilderActionModelAttachment builder, @Nonnull BuilderSupport support) { + super(builder); + this.slot = builder.getSlot(support); + this.attachment = builder.getAttachment(support); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + setModelAttachment(ref, this.slot, this.attachment, store); + return true; + } + + private static void setModelAttachment( + @Nonnull Ref ref, @Nonnull String slot, @Nullable String attachment, @Nonnull ComponentAccessor componentAccessor + ) { + if (slot.isEmpty()) { + throw new IllegalArgumentException("Slot must be specified!"); + } else { + ModelComponent modelComponent = componentAccessor.getComponent(ref, ModelComponent.getComponentType()); + + assert modelComponent != null; + + NPCEntity npcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + Model model = modelComponent.getModel(); + float scale = model.getScale(); + ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(model.getModelAssetId()); + Map randomAttachments = model.getRandomAttachmentIds() != null ? new HashMap<>(model.getRandomAttachmentIds()) : new HashMap<>(); + if (attachment != null && !attachment.isEmpty()) { + randomAttachments.put(slot, attachment); + } else { + randomAttachments.remove(slot); + } + + model = Model.createScaledModel(modelAsset, scale, randomAttachments); + componentAccessor.putComponent(ref, ModelComponent.getComponentType(), new ModelComponent(model)); + Role role = npcComponent.getRole(); + if (role != null) { + role.updateMotionControllers(ref, model, model.getBoundingBox(), componentAccessor); + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionPlayAnimation.java b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionPlayAnimation.java new file mode 100644 index 0000000..827d9a5 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionPlayAnimation.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.server.npc.corecomponents.audiovisual; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.animations.NPCAnimationSlot; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionPlayAnimation; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionPlayAnimation extends ActionBase { + protected final NPCAnimationSlot slot; + @Nullable + protected String animationId; + + public ActionPlayAnimation(@Nonnull BuilderActionPlayAnimation builderActionPlayAnimation, @Nonnull BuilderSupport support) { + super(builderActionPlayAnimation); + this.slot = builderActionPlayAnimation.getSlot(); + this.animationId = builderActionPlayAnimation.getAnimationId(support); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + npcComponent.playAnimation(ref, this.slot.getMappedSlot(), this.animationId, store); + return true; + } + + protected void setAnimationId(String animationId) { + this.animationId = animationId; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionPlaySound.java b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionPlaySound.java new file mode 100644 index 0000000..c754c4e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionPlaySound.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.npc.corecomponents.audiovisual; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.SoundUtil; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionPlaySound; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionPlaySound extends ActionBase { + protected final int soundEventIndex; + + public ActionPlaySound(@Nonnull BuilderActionPlaySound builderActionPlaySound, @Nonnull BuilderSupport support) { + super(builderActionPlaySound); + this.soundEventIndex = builderActionPlaySound.getSoundEventIndex(support); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + Vector3d position = transformComponent.getPosition(); + SoundUtil.playSoundEvent3d(ref, this.soundEventIndex, position.getX(), position.getY(), position.getZ(), false, store); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionSpawnParticles.java b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionSpawnParticles.java new file mode 100644 index 0000000..454c670 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/ActionSpawnParticles.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.npc.corecomponents.audiovisual; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.ParticleUtil; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionSpawnParticles; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import it.unimi.dsi.fastutil.objects.ObjectList; +import javax.annotation.Nonnull; + +public class ActionSpawnParticles extends ActionBase { + protected final String particleSystem; + protected final double range; + protected final Vector3d offset; + + public ActionSpawnParticles(@Nonnull BuilderActionSpawnParticles builder, @Nonnull BuilderSupport support) { + super(builder); + this.particleSystem = builder.getParticleSystem(support); + this.offset = builder.getOffset(); + this.range = builder.getRange(); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = new Vector3d(this.offset).rotateY(transformComponent.getRotation().getYaw()).add(transformComponent.getPosition()); + SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get().getPlayerSpatialResourceType()); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(position, this.range, results); + ParticleUtil.spawnParticleEffect(this.particleSystem, position, results, store); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/SensorAnimation.java b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/SensorAnimation.java new file mode 100644 index 0000000..c3039af --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/SensorAnimation.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.npc.corecomponents.audiovisual; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.entity.component.ActiveAnimationComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.animations.NPCAnimationSlot; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderSensorAnimation; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import java.util.Objects; +import javax.annotation.Nonnull; + +public class SensorAnimation extends SensorBase { + protected final NPCAnimationSlot slot; + protected final String animationId; + + public SensorAnimation(@Nonnull BuilderSensorAnimation builder, @Nonnull BuilderSupport support) { + super(builder); + this.slot = builder.getAnimationSlot(support); + this.animationId = builder.getAnimationId(support); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + ActiveAnimationComponent activeAnimationComponent = store.getComponent(ref, ActiveAnimationComponent.getComponentType()); + + assert activeAnimationComponent != null; + + return super.matches(ref, role, dt, store) + && Objects.equals(activeAnimationComponent.getActiveAnimations()[this.slot.getMappedSlot().ordinal()], this.animationId); + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionAppearance.java b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionAppearance.java new file mode 100644 index 0000000..78e6286 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionAppearance.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.ModelExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.ActionAppearance; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import javax.annotation.Nonnull; + +public class BuilderActionAppearance extends BuilderActionBase { + protected String appearance; + + public BuilderActionAppearance() { + } + + @Nonnull + public ActionAppearance build(BuilderSupport builderSupport) { + return new ActionAppearance(this); + } + + @Nonnull + public BuilderActionAppearance readConfig(@Nonnull JsonElement data) { + this.requireAsset(data, "Appearance", s -> this.appearance = s, ModelExistsValidator.required(), BuilderDescriptorState.Stable, "Model name to use", null); + return this; + } + + @Nonnull + @Override + public String getShortDescription() { + return "Set model displayed for NPC"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Change model of NPC to given appearance."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + public String getAppearance() { + return this.appearance; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionDisplayName.java b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionDisplayName.java new file mode 100644 index 0000000..70aa2cb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionDisplayName.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.ActionDisplayName; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import javax.annotation.Nonnull; + +public class BuilderActionDisplayName extends BuilderActionBase { + protected final StringHolder displayName = new StringHolder(); + + public BuilderActionDisplayName() { + } + + @Nonnull + public ActionDisplayName build(@Nonnull BuilderSupport builderSupport) { + return new ActionDisplayName(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Set display name."; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Set the name displayed above NPC"; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionDisplayName readConfig(@Nonnull JsonElement data) { + this.requireString(data, "DisplayName", this.displayName, null, BuilderDescriptorState.Stable, "Name to display above NPC", null); + return this; + } + + public String getDisplayName(@Nonnull BuilderSupport support) { + return this.displayName.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionModelAttachment.java b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionModelAttachment.java new file mode 100644 index 0000000..c8551cc --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionModelAttachment.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.ActionModelAttachment; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import javax.annotation.Nonnull; + +public class BuilderActionModelAttachment extends BuilderActionBase { + protected final StringHolder slot = new StringHolder(); + protected final StringHolder attachment = new StringHolder(); + + public BuilderActionModelAttachment() { + } + + @Nonnull + public ActionModelAttachment build(@Nonnull BuilderSupport builderSupport) { + return new ActionModelAttachment(this, builderSupport); + } + + @Nonnull + public BuilderActionModelAttachment readConfig(@Nonnull JsonElement data) { + this.requireString(data, "Slot", this.slot, StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The attachment slot to set", null); + this.requireString(data, "Attachment", this.attachment, null, BuilderDescriptorState.Stable, "The attachment to set, or empty to remove", null); + return this; + } + + @Nonnull + @Override + public String getShortDescription() { + return "Set an attachment on the current NPC model"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + public String getSlot(@Nonnull BuilderSupport support) { + return this.slot.get(support.getExecutionContext()); + } + + public String getAttachment(@Nonnull BuilderSupport support) { + return this.attachment.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionPlayAnimation.java b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionPlayAnimation.java new file mode 100644 index 0000000..977395a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionPlayAnimation.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.animations.NPCAnimationSlot; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.ActionPlayAnimation; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderActionPlayAnimation extends BuilderActionBase { + protected NPCAnimationSlot slot; + protected final StringHolder animationId = new StringHolder(); + + public BuilderActionPlayAnimation() { + } + + public ActionPlayAnimation build(@Nonnull BuilderSupport builderSupport) { + return new ActionPlayAnimation(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Play an animation"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Play an animation"; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + public BuilderActionPlayAnimation readConfig(@Nonnull JsonElement data) { + this.requireEnum(data, "Slot", e -> this.slot = e, NPCAnimationSlot.class, BuilderDescriptorState.Stable, "The animation slot to play on", null); + this.getString(data, "Animation", this.animationId, null, null, BuilderDescriptorState.Stable, "The animation ID to play", null); + return this; + } + + @Override + protected void runLoadTimeValidationHelper0( + String configName, @Nonnull NPCLoadTimeValidationHelper loadTimeValidationHelper, ExecutionContext context, List errors + ) { + loadTimeValidationHelper.validateAnimation(this.animationId.get(context)); + } + + public NPCAnimationSlot getSlot() { + return this.slot; + } + + @Nullable + public String getAnimationId(@Nonnull BuilderSupport support) { + String anim = this.animationId.get(support.getExecutionContext()); + return anim != null && !anim.isEmpty() ? anim : null; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionPlaySound.java b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionPlaySound.java new file mode 100644 index 0000000..1b893a0 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionPlaySound.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.SoundEventExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.ActionPlaySound; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import javax.annotation.Nonnull; + +public class BuilderActionPlaySound extends BuilderActionBase { + protected final AssetHolder soundEventId = new AssetHolder(); + + public BuilderActionPlaySound() { + } + + @Nonnull + public ActionPlaySound build(@Nonnull BuilderSupport builderSupport) { + return new ActionPlaySound(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Plays a sound to players within a specified range."; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Plays a sound to players within a specified range."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionPlaySound readConfig(@Nonnull JsonElement data) { + this.requireAsset( + data, "SoundEventId", this.soundEventId, SoundEventExistsValidator.required(), BuilderDescriptorState.Stable, "The sound event to play", null + ); + return this; + } + + public String getSoundEventId(@Nonnull BuilderSupport support) { + return this.soundEventId.get(support.getExecutionContext()); + } + + public int getSoundEventIndex(@Nonnull BuilderSupport support) { + String key = this.soundEventId.get(support.getExecutionContext()); + int index = SoundEvent.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } else { + return index; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionSpawnParticles.java b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionSpawnParticles.java new file mode 100644 index 0000000..3953ade --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderActionSpawnParticles.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.ParticleSystemExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.ActionSpawnParticles; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import javax.annotation.Nonnull; + +public class BuilderActionSpawnParticles extends BuilderActionBase { + protected final AssetHolder particleSystem = new AssetHolder(); + protected double range; + protected double[] offset; + + public BuilderActionSpawnParticles() { + } + + @Nonnull + public ActionSpawnParticles build(@Nonnull BuilderSupport builderSupport) { + return new ActionSpawnParticles(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Spawn particle system visible within a given range with an offset relative to npc heading"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.WorkInProgress; + } + + @Nonnull + public BuilderActionSpawnParticles readConfig(@Nonnull JsonElement data) { + this.requireAsset( + data, "ParticleSystem", this.particleSystem, ParticleSystemExistsValidator.required(), BuilderDescriptorState.Stable, "Particle system to spawn", null + ); + this.getDouble( + data, "Range", v -> this.range = v, 75.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "Maximum visibility range", null + ); + this.getVector3d( + data, "Offset", v -> this.offset = v, null, null, BuilderDescriptorState.Stable, "Offset relative to footpoint in view direction of NPC", null + ); + return this; + } + + public String getParticleSystem(@Nonnull BuilderSupport support) { + return this.particleSystem.get(support.getExecutionContext()); + } + + public double getRange() { + return this.range; + } + + public Vector3d getOffset() { + return createVector3d(this.offset, Vector3d.ZERO::clone); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderSensorAnimation.java b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderSensorAnimation.java new file mode 100644 index 0000000..7778f91 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/audiovisual/builders/BuilderSensorAnimation.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.animations.NPCAnimationSlot; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.audiovisual.SensorAnimation; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorAnimation extends BuilderSensorBase { + protected final EnumHolder animationSlot = new EnumHolder<>(); + protected final StringHolder animationId = new StringHolder(); + + public BuilderSensorAnimation() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Check if a given animation is being played"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorAnimation(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireEnum(data, "Slot", this.animationSlot, NPCAnimationSlot.class, BuilderDescriptorState.Stable, "The animation slot to check", null); + this.requireString( + data, "Animation", this.animationId, StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The animation ID to check for", null + ); + return this; + } + + public NPCAnimationSlot getAnimationSlot(@Nonnull BuilderSupport support) { + return this.animationSlot.get(support.getExecutionContext()); + } + + public String getAnimationId(@Nonnull BuilderSupport support) { + return this.animationId.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderActionBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderActionBase.java new file mode 100644 index 0000000..4b99b32 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderActionBase.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.npc.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.instructions.Action; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import javax.annotation.Nonnull; + +public abstract class BuilderActionBase extends BuilderBase { + protected boolean once; + + public BuilderActionBase() { + } + + @Override + public boolean canRequireFeature() { + return true; + } + + @Nonnull + @Override + public Builder readCommonConfig(@Nonnull JsonElement data) { + super.readCommonConfig(data); + this.getBoolean(data, "Once", v -> this.once = v, false, BuilderDescriptorState.Stable, "Execute only once", null); + return this; + } + + @Nonnull + @Override + public final Class category() { + return Action.class; + } + + @Override + public final boolean isEnabled(ExecutionContext context) { + return true; + } + + public boolean isOnce() { + return this.once; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderActionWithDelay.java b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderActionWithDelay.java new file mode 100644 index 0000000..d449cca --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderActionWithDelay.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.server.npc.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public abstract class BuilderActionWithDelay extends BuilderActionBase { + public static final double[] DEFAULT_TIMEOUT_RANGE = new double[]{1.0, 1.0}; + protected final NumberArrayHolder delayRange = new NumberArrayHolder(); + + public BuilderActionWithDelay() { + } + + @Nonnull + @Override + public Builder readCommonConfig(@Nonnull JsonElement data) { + super.readCommonConfig(data); + this.getDoubleRange( + data, + "Delay", + this.delayRange, + this.getDefaultTimeoutRange(), + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "Range of time to delay in seconds", + null + ); + return this; + } + + public double[] getDelayRange(@Nonnull BuilderSupport support) { + return this.delayRange.get(support.getExecutionContext()); + } + + protected double[] getDefaultTimeoutRange() { + return DEFAULT_TIMEOUT_RANGE; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderBodyMotionBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderBodyMotionBase.java new file mode 100644 index 0000000..6731ed1 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderBodyMotionBase.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.server.npc.corecomponents.builders; + +import com.hypixel.hytale.server.npc.instructions.BodyMotion; +import javax.annotation.Nonnull; + +public abstract class BuilderBodyMotionBase extends BuilderMotionBase { + public BuilderBodyMotionBase() { + } + + @Nonnull + @Override + public final Class category() { + return BodyMotion.class; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderEntityFilterBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderEntityFilterBase.java new file mode 100644 index 0000000..54599b1 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderEntityFilterBase.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.server.npc.corecomponents.builders; + +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; + +public abstract class BuilderEntityFilterBase extends BuilderEntityFilterWithToggle { + public BuilderEntityFilterBase() { + } + + @Override + public boolean validate( + String configName, @Nonnull NPCLoadTimeValidationHelper validationHelper, ExecutionContext context, Scope globalScope, @Nonnull List errors + ) { + boolean result = super.validate(configName, validationHelper, context, globalScope, errors); + String type = this.getTypeName(); + if (validationHelper.isFilterExternallyProvided(type)) { + result = false; + errors.add( + String.format( + "%s: includes a filter of type %s which is already externally provided (such as by a prioritiser) at %s", + configName, + type, + this.getBreadCrumbs() + ) + ); + } + + if (validationHelper.hasSeenFilter(type)) { + result = false; + errors.add(String.format("%s: has defined a filter of type %s more than once at %s", configName, type, this.getBreadCrumbs())); + } + + return result; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderEntityFilterWithToggle.java b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderEntityFilterWithToggle.java new file mode 100644 index 0000000..28942aa --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderEntityFilterWithToggle.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.server.npc.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import javax.annotation.Nonnull; + +public abstract class BuilderEntityFilterWithToggle extends BuilderBase { + protected final BooleanHolder enabled = new BooleanHolder(); + + public BuilderEntityFilterWithToggle() { + } + + @Nonnull + @Override + public Builder readCommonConfig(@Nonnull JsonElement data) { + super.readCommonConfig(data); + this.getBoolean(data, "Enabled", this.enabled, true, BuilderDescriptorState.Stable, "Whether this entity filter should be enabled", null); + return this; + } + + @Nonnull + @Override + public Class category() { + return IEntityFilter.class; + } + + @Override + public boolean isEnabled(ExecutionContext context) { + return this.enabled.get(context); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderHeadMotionBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderHeadMotionBase.java new file mode 100644 index 0000000..75364e8 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderHeadMotionBase.java @@ -0,0 +1,15 @@ +package com.hypixel.hytale.server.npc.corecomponents.builders; + +import com.hypixel.hytale.server.npc.instructions.HeadMotion; +import javax.annotation.Nonnull; + +public abstract class BuilderHeadMotionBase extends BuilderMotionBase { + public BuilderHeadMotionBase() { + } + + @Nonnull + @Override + public final Class category() { + return HeadMotion.class; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderMotionBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderMotionBase.java new file mode 100644 index 0000000..526b4c2 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderMotionBase.java @@ -0,0 +1,46 @@ +package com.hypixel.hytale.server.npc.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.instructions.Motion; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; + +public abstract class BuilderMotionBase extends BuilderBase { + public BuilderMotionBase() { + } + + @Override + public boolean canRequireFeature() { + return true; + } + + @Override + public Builder readCommonConfig(JsonElement data) { + this.requireInstructionType(InstructionType.MotionAllowedInstructions); + return super.readCommonConfig(data); + } + + @Override + public final boolean isEnabled(ExecutionContext context) { + return true; + } + + @Override + public boolean validate( + String configName, @Nonnull NPCLoadTimeValidationHelper validationHelper, ExecutionContext context, Scope globalScope, @Nonnull List errors + ) { + boolean result = super.validate(configName, validationHelper, context, globalScope, errors); + if (validationHelper.isParentSensorOnce()) { + errors.add(String.format("%s: Once is set on a sensor controlling a step with a motion at: %s", configName, this.getBreadCrumbs())); + return false; + } else { + return result; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderSensorBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderSensorBase.java new file mode 100644 index 0000000..cfa3727 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderSensorBase.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.server.npc.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; + +public abstract class BuilderSensorBase extends BuilderBase { + protected boolean once; + protected final BooleanHolder enabled = new BooleanHolder(); + + public BuilderSensorBase() { + } + + @Nonnull + @Override + public Builder readCommonConfig(@Nonnull JsonElement data) { + super.readCommonConfig(data); + this.getBoolean(data, "Once", aBoolean -> this.once = aBoolean, false, BuilderDescriptorState.Stable, "Sensor only triggers once", null); + this.getBoolean(data, "Enabled", this.enabled, true, BuilderDescriptorState.Stable, "Whether this sensor should be enabled on the NPC", null); + return this; + } + + @Nonnull + @Override + public Class category() { + return Sensor.class; + } + + public boolean getOnce() { + return this.once; + } + + public void setOnce(boolean once) { + this.once = once; + } + + @Override + public boolean isEnabled(ExecutionContext context) { + return this.enabled.get(context); + } + + @Override + public boolean validate( + String configName, @Nonnull NPCLoadTimeValidationHelper validationHelper, ExecutionContext context, Scope globalScope, @Nonnull List errors + ) { + validationHelper.updateParentSensorOnce(this.once); + return super.validate(configName, validationHelper, context, globalScope, errors); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderSensorEntityPrioritiserBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderSensorEntityPrioritiserBase.java new file mode 100644 index 0000000..212f502 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderSensorEntityPrioritiserBase.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.server.npc.corecomponents.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; +import com.hypixel.hytale.server.npc.corecomponents.ISensorEntityPrioritiser; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; + +public abstract class BuilderSensorEntityPrioritiserBase extends BuilderBase { + private final Set providedFilterTypes; + + protected BuilderSensorEntityPrioritiserBase(Set providedFilterTypes) { + this.providedFilterTypes = providedFilterTypes; + } + + @Nonnull + @Override + public Class category() { + return ISensorEntityPrioritiser.class; + } + + @Override + public boolean isEnabled(ExecutionContext context) { + return true; + } + + @Override + public boolean validate( + String configName, @Nonnull NPCLoadTimeValidationHelper validationHelper, ExecutionContext context, Scope globalScope, @Nonnull List errors + ) { + validationHelper.setPrioritiserProvidedFilterTypes(this.providedFilterTypes); + return super.validate(configName, validationHelper, context, globalScope, errors); + } + + protected Set getProvidedFilterTypes() { + return this.providedFilterTypes; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderSensorWithEntityFilters.java b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderSensorWithEntityFilters.java new file mode 100644 index 0000000..1d5ca8b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderSensorWithEntityFilters.java @@ -0,0 +1,69 @@ +package com.hypixel.hytale.server.npc.corecomponents.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectListHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.BuilderValidationHelper; +import com.hypixel.hytale.server.npc.asset.builder.ComponentContext; +import com.hypixel.hytale.server.npc.asset.builder.InstructionContextHelper; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.ISensorEntityPrioritiser; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class BuilderSensorWithEntityFilters extends BuilderSensorBase { + protected final BuilderObjectListHelper filters = new BuilderObjectListHelper<>(IEntityFilter.class, this); + + public BuilderSensorWithEntityFilters() { + } + + @Override + public boolean validate( + String configName, + @Nonnull NPCLoadTimeValidationHelper validationHelper, + @Nonnull ExecutionContext context, + Scope globalScope, + @Nonnull List errors + ) { + boolean result = super.validate(configName, validationHelper, context, globalScope, errors); + validationHelper.pushFilterSet(); + result &= this.filters.validate(configName, validationHelper, this.builderManager, context, globalScope, errors); + validationHelper.popFilterSet(); + return result; + } + + @Nonnull + public IEntityFilter[] getFilters(@Nonnull BuilderSupport support, @Nullable ISensorEntityPrioritiser prioritiser, ComponentContext context) { + if (this.filters.isPresent() && !this.filters.isEmpty()) { + support.setCurrentComponentContext(context); + List builtFilters = this.filters.build(support); + if (prioritiser != null) { + prioritiser.buildProvidedFilters(builtFilters); + } + + support.setCurrentComponentContext(null); + return builtFilters.toArray(IEntityFilter[]::new); + } else if (prioritiser != null && prioritiser.providesFilters()) { + ObjectArrayList builtFilters = new ObjectArrayList<>(); + prioritiser.buildProvidedFilters(builtFilters); + return builtFilters.toArray(IEntityFilter[]::new); + } else { + return IEntityFilter.EMPTY_ARRAY; + } + } + + @Nonnull + protected BuilderValidationHelper createFilterValidationHelper(ComponentContext context) { + InstructionContextHelper instructionContextHelper = new InstructionContextHelper( + this.isCreatingDescriptor() ? null : this.getInstructionContextHelper().getInstructionContext() + ); + instructionContextHelper.setComponentContext(context); + return new BuilderValidationHelper( + this.fileName, null, this.internalReferenceResolver, null, instructionContextHelper, this.extraInfo, this.evaluators, this.readErrors + ); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderWeightedAction.java b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderWeightedAction.java new file mode 100644 index 0000000..61d9cf7 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/builders/BuilderWeightedAction.java @@ -0,0 +1,92 @@ +package com.hypixel.hytale.server.npc.corecomponents.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectReferenceHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.WeightedAction; +import com.hypixel.hytale.server.npc.instructions.Action; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderWeightedAction extends BuilderBase { + private final BuilderObjectReferenceHelper action = new BuilderObjectReferenceHelper<>(Action.class, this); + private final DoubleHolder weight = new DoubleHolder(); + + public BuilderWeightedAction() { + } + + @Nonnull + public WeightedAction build(@Nonnull BuilderSupport builderSupport) { + return new WeightedAction(this, builderSupport); + } + + @Nonnull + @Override + public Class category() { + return WeightedAction.class; + } + + @Override + public boolean isEnabled(ExecutionContext context) { + return true; + } + + @Nonnull + @Override + public String getShortDescription() { + return "A wrapped and weighted action intended to be used for Random action lists."; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireObject(data, "Action", this.action, BuilderDescriptorState.Stable, "The action to run", null, this.validationHelper); + this.requireDouble( + data, + "Weight", + this.weight, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "The weight representing how likely this action is to run", + null + ); + return this; + } + + @Override + public boolean validate( + String configName, NPCLoadTimeValidationHelper validationHelper, @Nonnull ExecutionContext context, Scope globalScope, @Nonnull List errors + ) { + return this.action.validate(configName, validationHelper, this.builderManager, context, globalScope, errors); + } + + @Nullable + public Action getAction(@Nonnull BuilderSupport support) { + return this.action.build(support); + } + + public double getWeight(@Nonnull BuilderSupport support) { + return this.weight.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/combat/ActionApplyEntityEffect.java b/src/com/hypixel/hytale/server/npc/corecomponents/combat/ActionApplyEntityEffect.java new file mode 100644 index 0000000..b3cf9df --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/combat/ActionApplyEntityEffect.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.corecomponents.combat; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderActionApplyEntityEffect; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionApplyEntityEffect extends ActionBase { + protected final int entityEffectId; + protected final boolean useTarget; + + public ActionApplyEntityEffect(@Nonnull BuilderActionApplyEntityEffect builder, @Nonnull BuilderSupport support) { + super(builder); + this.entityEffectId = builder.getEntityEffect(support); + this.useTarget = builder.isUseTarget(support); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && (!this.useTarget || sensorInfo != null && sensorInfo.hasPosition()); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, @Nonnull InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + Ref targetRef = this.useTarget ? sensorInfo.getPositionProvider().getTarget() : ref; + if (targetRef != null && targetRef.isValid()) { + EntityEffect entityEffect = EntityEffect.getAssetMap().getAsset(this.entityEffectId); + if (entityEffect != null) { + EffectControllerComponent effectControllerComponent = store.getComponent(targetRef, EffectControllerComponent.getComponentType()); + if (effectControllerComponent != null) { + effectControllerComponent.addEffect(targetRef, this.entityEffectId, entityEffect, store); + } + } + + return true; + } else { + return true; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/combat/ActionAttack.java b/src/com/hypixel/hytale/server/npc/corecomponents/combat/ActionAttack.java new file mode 100644 index 0000000..b2f9038 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/combat/ActionAttack.java @@ -0,0 +1,344 @@ +package com.hypixel.hytale.server.npc.corecomponents.combat; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.InteractionType; +import com.hypixel.hytale.server.core.entity.InteractionChain; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.entity.InteractionManager; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.interaction.InteractionModule; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.SingleCollector; +import com.hypixel.hytale.server.core.modules.projectile.config.BallisticData; +import com.hypixel.hytale.server.core.modules.projectile.config.BallisticDataProvider; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderActionAttack; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.interactions.NPCInteractionSimulationHandler; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.CombatSupport; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.sensorinfo.parameterproviders.ParameterProvider; +import com.hypixel.hytale.server.npc.sensorinfo.parameterproviders.StringParameterProvider; +import com.hypixel.hytale.server.npc.util.AimingData; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionAttack extends ActionBase { + @Nonnull + public static final ThreadLocal> THREAD_LOCAL_COLLECTOR = ThreadLocal.withInitial( + () -> new SingleCollector<>( + (collectorTag, interactionContext, interaction) -> interaction instanceof BallisticDataProvider ballisticDataProvider + && Interaction.getAssetMap().getKeysForTag(CombatSupport.AIMING_REFERENCE_TAG_INDEX).contains(interaction.getId()) + ? ballisticDataProvider.getBallisticData() + : null + ) + ); + protected final int id; + @Nullable + protected String attack; + protected final InteractionType interactionType; + protected final float chargeFor; + protected final double[] attackPauseRange; + protected final double[] aimingTimeRange; + protected final double meleeConeAngle; + protected final ActionAttack.BallisticMode ballisticMode; + protected final boolean checkLineOfSight; + protected final boolean avoidFriendlyFire; + protected final boolean damageFriendlies; + protected final boolean skipAiming; + protected final double chargeDistance; + protected final int attackParameterSlot; + @Nullable + protected final Map interactionVars; + protected boolean attackReady; + @Nullable + protected String attackInteraction; + protected boolean ballisticShort; + protected StringParameterProvider cachedAttackProvider; + protected boolean initialised; + protected double aimingTimeRemaining; + protected Role ownerRole; + + public ActionAttack(@Nonnull BuilderActionAttack builderActionAttack, @Nonnull BuilderSupport builderSupport) { + super(builderActionAttack); + this.id = builderSupport.getNextAttackIndex(); + this.attack = builderActionAttack.getAttack(builderSupport); + this.interactionType = builderActionAttack.getAttackType(builderSupport).getInteractionType(); + this.chargeFor = builderActionAttack.getChargeTime(builderSupport); + this.attackPauseRange = builderActionAttack.getAttackPauseRange(builderSupport); + this.aimingTimeRange = builderActionAttack.getAimingTimeRange(builderSupport); + this.meleeConeAngle = builderActionAttack.getMeleeConeAngle(); + this.ballisticMode = builderActionAttack.getBallisticMode(); + this.checkLineOfSight = builderActionAttack.isCheckLineOfSight(); + this.avoidFriendlyFire = builderActionAttack.isAvoidFriendlyFire(); + this.damageFriendlies = builderActionAttack.isDamageFriendlies(); + this.skipAiming = builderActionAttack.isSkipAiming(); + this.chargeDistance = builderActionAttack.getChargeDistance(builderSupport); + this.attackParameterSlot = builderActionAttack.getAttackParameterSlot(builderSupport); + this.interactionVars = builderActionAttack.getInteractionVars(); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && !role.getCombatSupport().isExecutingAttack(); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + this.ownerRole = role; + CombatSupport combatSupport = role.getCombatSupport(); + InteractionManager interactionManagerComponent = store.getComponent(ref, InteractionModule.get().getInteractionManagerComponent()); + + assert interactionManagerComponent != null; + + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + if (!this.initialised) { + ParameterProvider parameterProvider = this.attackParameterSlot >= 0 && sensorInfo != null + ? sensorInfo.getParameterProvider(this.attackParameterSlot) + : null; + if (parameterProvider instanceof StringParameterProvider) { + this.cachedAttackProvider = (StringParameterProvider)parameterProvider; + } + + this.initialised = true; + } + + if (this.cachedAttackProvider != null) { + this.attackInteraction = this.cachedAttackProvider.getStringParameter(); + } + + AimingData aimingDataInfo = sensorInfo != null ? sensorInfo.getPassedExtraInfo(AimingData.class) : null; + AimingData aimingData = aimingDataInfo != null && aimingDataInfo.isClaimedBy(this.id) ? aimingDataInfo : null; + if (!this.attackReady) { + this.attackReady = true; + this.aimingTimeRemaining = this.newAimingTime(); + String nextOverride = combatSupport.getNextAttackOverride(); + if (nextOverride != null) { + if (!RootInteraction.getAssetMap().getKeysForTag(CombatSupport.ATTACK_TAG_INDEX).contains(nextOverride)) { + this.attackReady = false; + NPCPlugin.get() + .getLogger() + .at(Level.WARNING) + .atMostEvery(1, TimeUnit.MINUTES) + .log("NPC: %s using interaction %s that is not tagged as an 'Attack' usable by NPCs", npcComponent.getRoleName(), nextOverride); + return true; + } + + this.attackInteraction = nextOverride; + } else if (this.attack != null && !this.attack.isEmpty()) { + this.attackInteraction = this.attack; + } else { + ItemStack itemInHand = npcComponent.getInventory().getItemInHand(); + InteractionContext context = InteractionContext.forInteraction(interactionManagerComponent, ref, this.interactionType, store); + String interaction = context.getRootInteractionId(this.interactionType); + if (interaction == null) { + this.attackReady = false; + NPCPlugin.get() + .getLogger() + .at(Level.WARNING) + .atMostEvery(1, TimeUnit.MINUTES) + .log( + "NPC: %s using nonexistent interaction of type %s using weapon %s in Attack action", + npcComponent.getRoleName(), + this.interactionType, + itemInHand == null ? "empty" : itemInHand.getItemId() + ); + return true; + } + + Set validKeys = RootInteraction.getAssetMap().getKeysForTag(CombatSupport.ATTACK_TAG_INDEX); + if (!validKeys.contains(interaction)) { + this.attackReady = false; + NPCPlugin.get() + .getLogger() + .at(Level.WARNING) + .atMostEvery(1, TimeUnit.MINUTES) + .log( + "NPC: %s with weapon %s, using interaction of type %s that is not tagged as an 'Attack' usable by NPCs", + npcComponent.getRoleName(), + itemInHand == null ? "empty" : itemInHand.getItemId(), + this.interactionType + ); + return true; + } + + this.attackInteraction = interaction; + } + + if (!this.skipAiming && aimingData != null) { + SingleCollector collector = THREAD_LOCAL_COLLECTOR.get(); + interactionManagerComponent.walkChain( + ref, collector, InteractionType.Primary, RootInteraction.getAssetMap().getAsset(this.attackInteraction), store + ); + BallisticData ballisticData = collector.getResult(); + if (ballisticData != null) { + aimingData.requireBallistic(ballisticData); + + this.ballisticShort = switch (this.ballisticMode) { + case Short -> true; + case Long -> false; + case Alternate -> !this.ballisticShort; + case Random -> RandomExtra.randomBoolean(); + }; + aimingData.setUseFlatTrajectory(this.ballisticShort); + } else { + if (this.chargeDistance > 0.0) { + aimingData.setChargeDistance(this.chargeDistance); + aimingData.setDesiredHitAngle(this.meleeConeAngle); + } + + aimingData.requireCloseCombat(); + } + + return false; + } + } + + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3f rotation = aimingData != null && aimingData.getChargeDistance() > 0.0 ? transformComponent.getRotation() : headRotationComponent.getRotation(); + if (this.hasTimeForAiming(dt) && aimingData != null && !aimingData.isOnTarget(rotation.getYaw(), rotation.getPitch(), this.meleeConeAngle)) { + aimingData.clearSolution(); + return false; + } else { + Ref target = aimingData != null ? aimingData.getTarget() : null; + if (!this.checkLineOfSight || target != null && role.getPositionCache().hasLineOfSight(ref, target, store)) { + if (this.avoidFriendlyFire && target != null && role.getPositionCache().isFriendlyBlockingLineOfSight(ref, target, store)) { + aimingData.clearSolution(); + return true; + } else { + ((NPCInteractionSimulationHandler)interactionManagerComponent.getInteractionSimulationHandler()).requestChargeTime(this.chargeFor); + InteractionContext contextx = InteractionContext.forInteraction(interactionManagerComponent, ref, this.interactionType, store); + contextx.setInteractionVarsGetter(this::getInteractionVars); + InteractionChain chain = interactionManagerComponent.initChain( + this.interactionType, contextx, RootInteraction.getRootInteractionOrUnknown(this.attackInteraction), false + ); + interactionManagerComponent.queueExecuteChain(chain); + combatSupport.setExecutingAttack(chain, this.damageFriendlies, this.newAttackPause()); + if (aimingData != null) { + aimingData.setHaveAttacked(true); + } + + this.attackReady = false; + return true; + } + } else { + if (aimingData != null) { + aimingData.clearSolution(); + } + + return true; + } + } + } + + @Override + public void activate(Role role, @Nullable InfoProvider infoProvider) { + super.activate(role, infoProvider); + if (infoProvider != null) { + AimingData aimingData = infoProvider.getPassedExtraInfo(AimingData.class); + if (aimingData != null) { + aimingData.tryClaim(this.id); + } + } + } + + @Override + public void deactivate(Role role, @Nullable InfoProvider infoProvider) { + super.deactivate(role, infoProvider); + if (infoProvider != null) { + AimingData aimingData = infoProvider.getPassedExtraInfo(AimingData.class); + if (aimingData != null) { + aimingData.release(); + } + } + } + + protected boolean hasTimeForAiming(double dt) { + if (this.aimingTimeRemaining > 0.0) { + this.aimingTimeRemaining -= dt; + return true; + } else { + return false; + } + } + + protected double newAimingTime() { + return this.aimingTimeRange[1] > 0.0 ? RandomExtra.randomRange(this.aimingTimeRange) : 0.0; + } + + protected double newAttackPause() { + return RandomExtra.randomRange(this.attackPauseRange[0], this.attackPauseRange[1]); + } + + @Nullable + private Map getInteractionVars(InteractionContext c) { + return this.interactionVars == null ? this.ownerRole.getInteractionVars() : this.interactionVars; + } + + public static enum AttackType implements Supplier { + Primary(InteractionType.Primary, "Primary attack"), + Secondary(InteractionType.Secondary, "Secondary attack"), + Ability1(InteractionType.Ability1, "Ability 1"), + Ability2(InteractionType.Ability2, "Ability 2"), + Ability3(InteractionType.Ability3, "Ability 3"); + + private final String description; + private final InteractionType interactionType; + + private AttackType(InteractionType interactionType, String description) { + this.interactionType = interactionType; + this.description = description; + } + + public String get() { + return this.description; + } + + public InteractionType getInteractionType() { + return this.interactionType; + } + } + + public static enum BallisticMode implements Supplier { + Short("Shorter flight curve"), + Long("Longer flight curve"), + Alternate("Alternate between long and short"), + Random("Random long or short"); + + private final String description; + + private BallisticMode(String description) { + this.description = description; + } + + public String get() { + return this.description; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/combat/BodyMotionAimCharge.java b/src/com/hypixel/hytale/server/npc/corecomponents/combat/BodyMotionAimCharge.java new file mode 100644 index 0000000..092cdc4 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/combat/BodyMotionAimCharge.java @@ -0,0 +1,100 @@ +package com.hypixel.hytale.server.npc.corecomponents.combat; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.BodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderBodyMotionAimCharge; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.movement.controllers.ProbeMoveData; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.AimingData; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BodyMotionAimCharge extends BodyMotionBase { + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected final double relativeTurnSpeed; + protected final AimingData aimingData = new AimingData(); + protected final Vector3d direction = new Vector3d(); + protected final ProbeMoveData probeMoveData = new ProbeMoveData(); + + public BodyMotionAimCharge(@Nonnull BuilderBodyMotionAimCharge builder, @Nonnull BuilderSupport support) { + super(builder); + this.relativeTurnSpeed = builder.getRelativeTurnSpeed(support); + } + + @Override + public void preComputeSteering(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, @Nonnull Store store) { + if (sensorInfo != null) { + sensorInfo.passExtraInfo(this.aimingData); + } + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role role, + @Nullable InfoProvider sensorInfo, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + if (sensorInfo != null && sensorInfo.getPositionProvider().providePosition(this.direction)) { + if (this.aimingData.isHaveAttacked()) { + if (role.getCombatSupport().isExecutingAttack()) { + desiredSteering.clear(); + return true; + } + + this.aimingData.setHaveAttacked(false); + } + + Vector3d selfPosition = componentAccessor.getComponent(ref, TRANSFORM_COMPONENT_TYPE).getPosition(); + double distanceToTarget = role.getActiveMotionController().waypointDistance(selfPosition, this.direction); + if (distanceToTarget > this.aimingData.getChargeDistance()) { + this.aimingData.clearSolution(); + return true; + } else { + this.direction.subtract(selfPosition); + this.direction.setLength(this.aimingData.getChargeDistance()); + double x = this.direction.getX(); + double y = this.direction.getY(); + double z = this.direction.getZ(); + float yaw = PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(x, z)); + float pitch = PhysicsMath.pitchFromDirection(x, y, z); + desiredSteering.setYaw(yaw); + desiredSteering.setPitch(pitch); + desiredSteering.setRelativeTurnSpeed(this.relativeTurnSpeed); + TransformComponent transformComponent = componentAccessor.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + Vector3f bodyRotation = transformComponent.getRotation(); + this.aimingData.setOrientation(yaw, pitch); + if (!this.aimingData.isOnTarget(bodyRotation.getYaw(), bodyRotation.getPitch(), this.aimingData.getDesiredHitAngle())) { + this.aimingData.clearSolution(); + return true; + } else { + double distance = role.getActiveMotionController().probeMove(ref, selfPosition, this.direction, this.probeMoveData, componentAccessor); + if (distance < distanceToTarget - 1.0E-6) { + this.aimingData.clearSolution(); + return true; + } else { + Ref target = sensorInfo.getPositionProvider().getTarget(); + this.aimingData.setTarget(target); + return true; + } + } + } + } else { + desiredSteering.clear(); + return true; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/combat/HeadMotionAim.java b/src/com/hypixel/hytale/server/npc/corecomponents/combat/HeadMotionAim.java new file mode 100644 index 0000000..aed2296 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/combat/HeadMotionAim.java @@ -0,0 +1,190 @@ +package com.hypixel.hytale.server.npc.corecomponents.combat; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.physics.component.Velocity; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.modules.projectile.config.BallisticData; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.HeadMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderHeadMotionAim; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.IPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.AimingData; +import com.hypixel.hytale.server.npc.util.NPCPhysicsMath; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HeadMotionAim extends HeadMotionBase { + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected static final ComponentType MODEL_COMPONENT_TYPE = ModelComponent.getComponentType(); + protected static final ComponentType BOUNDING_BOX_COMPONENT_TYPE = BoundingBox.getComponentType(); + protected final double spread; + protected final boolean deflection; + protected final double hitProbability; + protected final double relativeTurnSpeed; + protected final AimingData aimingData = new AimingData(); + protected Ref lastTargetReference; + protected double spreadX; + protected double spreadY; + protected double spreadZ; + + public HeadMotionAim(@Nonnull BuilderHeadMotionAim builder, @Nonnull BuilderSupport support) { + super(builder); + this.spread = builder.getSpread(); + this.hitProbability = builder.getHitProbability(); + this.deflection = builder.isDeflection(); + this.relativeTurnSpeed = builder.getRelativeTurnSpeed(support); + } + + @Override + public void preComputeSteering(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, @Nonnull Store store) { + if (sensorInfo != null) { + sensorInfo.passExtraInfo(this.aimingData); + } + } + + @Override + public void activate(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + this.aimingData.setHaveAttacked(true); + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role support, + @Nullable InfoProvider sensorInfo, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + if (sensorInfo != null && sensorInfo.hasPosition()) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + ModelComponent modelComponent = componentAccessor.getComponent(ref, MODEL_COMPONENT_TYPE); + + assert modelComponent != null; + + Vector3d position = transformComponent.getPosition(); + IPositionProvider positionProvider = sensorInfo.getPositionProvider(); + double x = positionProvider.getX() - position.getX(); + double y = positionProvider.getY() - position.getY() - modelComponent.getModel().getEyeHeight(); + double z = positionProvider.getZ() - position.getZ(); + double vx = 0.0; + double vy = 0.0; + double vz = 0.0; + Ref targetRef = positionProvider.getTarget(); + if (targetRef != null) { + Velocity targetVelocityComponent = componentAccessor.getComponent(targetRef, Velocity.getComponentType()); + + assert targetVelocityComponent != null; + + BoundingBox boundingBoxComponent = componentAccessor.getComponent(ref, BOUNDING_BOX_COMPONENT_TYPE); + Box boundingBox = boundingBoxComponent != null ? boundingBoxComponent.getBoundingBox() : null; + if (this.aimingData.isBallistic()) { + if (boundingBox != null) { + x += (boundingBox.getMax().getX() + boundingBox.getMin().getX()) / 2.0; + y += (boundingBox.getMax().getY() + boundingBox.getMin().getY()) / 2.0; + z += (boundingBox.getMax().getZ() + boundingBox.getMin().getZ()) / 2.0; + } + + if (this.deflection) { + Vector3d steeringVelocity = targetVelocityComponent.getVelocity(); + vx = steeringVelocity.getX(); + vy = steeringVelocity.getY(); + vz = steeringVelocity.getZ(); + } + } else if (boundingBox != null) { + double minY = y + boundingBox.getMin().y; + double maxY = y + boundingBox.getMax().y; + if (minY > 0.0) { + y = minY; + } else if (maxY < 0.0) { + y = maxY; + } else { + y = 0.0; + } + } + } + + if (this.aimingData.isBallistic()) { + BallisticData ballisticData = this.aimingData.getBallisticData(); + if (ballisticData != null) { + y += ballisticData.getVerticalCenterShot(); + this.aimingData.setDepthOffset(ballisticData.getDepthShot(), ballisticData.isPitchAdjustShot()); + } else { + this.aimingData.setDepthOffset(0.0, false); + } + + if (targetRef != null && (this.lastTargetReference == null || !this.lastTargetReference.equals(targetRef))) { + this.lastTargetReference = targetRef; + this.aimingData.setHaveAttacked(true); + } + + if (this.aimingData.isHaveAttacked()) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + if (this.spread > 0.0 && random.nextDouble() > this.hitProbability) { + double spread2 = 2.0 * this.spread * Math.sqrt(NPCPhysicsMath.dotProduct(x, y, z)) / 10.0; + this.spreadX = this.spreadX + spread2 * (random.nextDouble() - 0.5); + this.spreadY = this.spreadY + spread2 * (random.nextDouble() - 0.5); + this.spreadZ = this.spreadZ + spread2 * (random.nextDouble() - 0.5); + } else { + this.spreadX = 0.0; + this.spreadY = 0.0; + this.spreadZ = 0.0; + } + + this.aimingData.setHaveAttacked(false); + } + + x += this.spreadX; + y += this.spreadY; + z += this.spreadZ; + } + + float pitch; + float yaw; + if (this.aimingData.computeSolution(x, y, z, vx, vy, vz)) { + yaw = this.aimingData.getYaw(); + pitch = this.aimingData.getPitch(); + this.aimingData.setTarget(targetRef); + } else { + HeadRotation headRotationComponent = componentAccessor.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + double xxzz = x * x + z * z; + double xxyyzz = xxzz + y * y; + Vector3f headRotation = headRotationComponent.getRotation(); + yaw = xxzz >= 1.0E-4 ? PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(x, z)) : headRotation.getYaw(); + pitch = xxyyzz >= 1.0E-4 ? PhysicsMath.pitchFromDirection(x, y, z) : headRotation.getPitch(); + this.aimingData.setOrientation(yaw, pitch); + this.aimingData.setTarget(null); + } + + desiredSteering.clearTranslation(); + desiredSteering.setYaw(yaw); + desiredSteering.setPitch(pitch); + desiredSteering.setRelativeTurnSpeed(this.relativeTurnSpeed); + return true; + } else { + desiredSteering.clear(); + return true; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/combat/SensorDamage.java b/src/com/hypixel/hytale/server/npc/corecomponents/combat/SensorDamage.java new file mode 100644 index 0000000..5336e06 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/combat/SensorDamage.java @@ -0,0 +1,95 @@ +package com.hypixel.hytale.server.npc.corecomponents.combat; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderSensorDamage; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.CombatSupport; +import com.hypixel.hytale.server.npc.role.support.WorldSupport; +import com.hypixel.hytale.server.npc.sensorinfo.EntityPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.DamageData; +import javax.annotation.Nonnull; + +public class SensorDamage extends SensorBase { + protected final boolean combatDamage; + protected final boolean friendlyDamage; + protected final boolean drowningDamage; + protected final boolean environmentDamage; + protected final boolean otherDamage; + protected final int targetSlot; + protected final EntityPositionProvider positionProvider = new EntityPositionProvider(); + + public SensorDamage(@Nonnull BuilderSensorDamage builder, @Nonnull BuilderSupport support) { + super(builder); + this.combatDamage = builder.isCombatDamage(); + this.friendlyDamage = builder.isFriendlyDamage(); + this.drowningDamage = builder.isDrowningDamage(); + this.environmentDamage = builder.isEnvironmentDamage(); + this.otherDamage = builder.isOtherDamage(); + this.targetSlot = builder.getTargetSlot(support); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + this.positionProvider.clear(); + return false; + } else { + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + CombatSupport combatSupport = role.getCombatSupport(); + DamageData damageData = npcComponent.getDamageData(); + if (this.combatDamage) { + Ref attackerRef = damageData.getMostDamagingAttacker(); + if (attackerRef == null) { + attackerRef = damageData.getAnyAttacker(); + } + + attackerRef = this.positionProvider.setTarget(attackerRef, store); + if (attackerRef != null) { + if (this.friendlyDamage) { + int[] damageGroups = combatSupport.getDisableDamageGroups(); + if (!WorldSupport.isGroupMember(npcComponent.getRoleIndex(), attackerRef, damageGroups, store)) { + return false; + } + } + + if (this.targetSlot >= 0) { + role.getMarkedEntitySupport().setMarkedEntity(this.targetSlot, attackerRef); + } + + return true; + } + + if (damageData.hasSufferedDamage(DamageCause.PHYSICAL) || damageData.hasSufferedDamage(DamageCause.PROJECTILE)) { + this.positionProvider.clear(); + return true; + } + } + + this.positionProvider.clear(); + return this.drowningDamage && damageData.hasSufferedDamage(DamageCause.DROWNING) + || this.environmentDamage && damageData.hasSufferedDamage(DamageCause.ENVIRONMENT) + || this.otherDamage + && ( + damageData.hasSufferedDamage(DamageCause.FALL) + || damageData.hasSufferedDamage(DamageCause.OUT_OF_WORLD) + || damageData.hasSufferedDamage(DamageCause.SUFFOCATION) + || damageData.hasSufferedDamage(DamageCause.COMMAND) + ); + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/combat/SensorIsBackingAway.java b/src/com/hypixel/hytale/server/npc/corecomponents/combat/SensorIsBackingAway.java new file mode 100644 index 0000000..9d134e0 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/combat/SensorIsBackingAway.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.npc.corecomponents.combat; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderSensorIsBackingAway; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorIsBackingAway extends SensorBase { + public SensorIsBackingAway(@Nonnull BuilderSensorIsBackingAway builder) { + super(builder); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + return !super.matches(ref, role, dt, store) ? false : role.isBackingAway(); + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderActionApplyEntityEffect.java b/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderActionApplyEntityEffect.java new file mode 100644 index 0000000..3812935 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderActionApplyEntityEffect.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.server.npc.corecomponents.combat.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.EntityEffectExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.combat.ActionApplyEntityEffect; +import javax.annotation.Nonnull; + +public class BuilderActionApplyEntityEffect extends BuilderActionBase { + protected final AssetHolder entityEffect = new AssetHolder(); + protected final BooleanHolder useTarget = new BooleanHolder(); + + public BuilderActionApplyEntityEffect() { + } + + @Nonnull + public ActionApplyEntityEffect build(@Nonnull BuilderSupport builderSupport) { + return new ActionApplyEntityEffect(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Applies an entity effect to the target or self"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionApplyEntityEffect readConfig(@Nonnull JsonElement data) { + this.requireAsset( + data, "EntityEffect", this.entityEffect, EntityEffectExistsValidator.required(), BuilderDescriptorState.Stable, "The entity effect to apply", null + ); + this.getBoolean( + data, "UseTarget", this.useTarget, true, BuilderDescriptorState.Stable, "Use the sensor-provided target for the action, self otherwise", null + ); + this.requireFeatureIf(this.useTarget, true, Feature.LiveEntity); + return this; + } + + public int getEntityEffect(@Nonnull BuilderSupport support) { + return EntityEffect.getAssetMap().getIndex(this.entityEffect.get(support.getExecutionContext())); + } + + public boolean isUseTarget(@Nonnull BuilderSupport support) { + return this.useTarget.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderActionAttack.java b/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderActionAttack.java new file mode 100644 index 0000000..57136f9 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderActionAttack.java @@ -0,0 +1,246 @@ +package com.hypixel.hytale.server.npc.corecomponents.combat.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.npc.asset.builder.BuilderCodecObjectHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.FloatHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.CombatInteractionValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.combat.ActionAttack; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderActionAttack extends BuilderActionBase { + private static final String ATTACK_PARAMETER = "Attack"; + public static final String[] ANTECEDENT = new String[]{"SkipAiming"}; + public static final String[] SUBSEQUENT = new String[]{"LineOfSight", "AvoidFriendlyFire"}; + public static final double[] DEFAULT_ATTACK_PAUSE_RANGE = new double[]{0.0, 0.0}; + public static final double[] DEFAULT_AIMING_TIME_RANGE = new double[]{0.0, 0.0}; + protected final AssetHolder attack = new AssetHolder(); + protected final EnumHolder attackType = new EnumHolder<>(); + protected final FloatHolder chargeFor = new FloatHolder(); + protected final NumberArrayHolder attackPauseRange = new NumberArrayHolder(); + protected final NumberArrayHolder aimingTimeRange = new NumberArrayHolder(); + protected double meleeConeAngle; + protected ActionAttack.BallisticMode ballisticMode = ActionAttack.BallisticMode.Short; + protected boolean checkLineOfSight; + protected boolean avoidFriendlyFire; + protected boolean damageFriendlies; + protected boolean skipAiming; + protected DoubleHolder chargeDistance = new DoubleHolder(); + protected final BuilderCodecObjectHelper> interactionVars = new BuilderCodecObjectHelper<>( + RootInteraction.class, RootInteraction.CHILD_ASSET_CODEC_MAP, null + ); + protected boolean attackProvided; + + public BuilderActionAttack() { + } + + @Nonnull + public ActionAttack build(@Nonnull BuilderSupport builderSupport) { + return new ActionAttack(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Starts attack"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Let NPC start an attack. When an attack is running no new attack is started."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + public BuilderActionAttack readConfig(@Nonnull JsonElement data) { + this.attackProvided = this.getAsset( + data, + "Attack", + this.attack, + null, + CombatInteractionValidator.withConfig(AssetValidator.CanBeEmpty), + BuilderDescriptorState.Experimental, + "Attack pattern to use", + "Attack pattern to use. If omitted, will cancel current attack" + ); + this.getEnum( + data, + "AttackType", + this.attackType, + ActionAttack.AttackType.class, + ActionAttack.AttackType.Primary, + BuilderDescriptorState.Stable, + "The interaction type to use", + null + ); + this.getFloat( + data, + "ChargeFor", + this.chargeFor, + 0.0, + DoubleSingleValidator.greaterEqual0(), + BuilderDescriptorState.Stable, + "How long to charge for", + "How long to charge for. 0 indicates no charging. Also doubles as how long to block for" + ); + this.getDoubleRange( + data, + "AttackPauseRange", + this.attackPauseRange, + DEFAULT_ATTACK_PAUSE_RANGE, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "Range of minimum pause between attacks", + null + ); + this.getDoubleRange( + data, + "AimingTimeRange", + this.aimingTimeRange, + DEFAULT_AIMING_TIME_RANGE, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "A range from which to pick a random value denoting the max time the NPC will wait for aiming before launching the attack.", + null + ); + this.getBoolean( + data, "LineOfSight", b -> this.checkLineOfSight = b, false, BuilderDescriptorState.Experimental, "Check Line of Sight before firing", null + ); + this.getBoolean( + data, "AvoidFriendlyFire", b -> this.avoidFriendlyFire = b, true, BuilderDescriptorState.Experimental, "Tries to avoid friendly fire if true", null + ); + this.getEnum( + data, + "BallisticMode", + e -> this.ballisticMode = e, + ActionAttack.BallisticMode.class, + ActionAttack.BallisticMode.Short, + BuilderDescriptorState.WorkInProgress, + "Trajectory to use", + null + ); + this.getDouble( + data, + "MeleeConeAngle", + d -> this.meleeConeAngle = d, + 30.0, + DoubleRangeValidator.fromExclToIncl(0.0, 360.0), + BuilderDescriptorState.WorkInProgress, + "Cone angle considered for on target for melee", + null + ); + this.getBoolean( + data, + "DamageFriendlies", + d -> this.damageFriendlies = d, + false, + BuilderDescriptorState.Stable, + "Whether this attack should bypass ignored damage groups and deal damage to the target", + null + ); + this.getBoolean( + data, + "SkipAiming", + b -> this.skipAiming = b, + false, + BuilderDescriptorState.Stable, + "Whether aiming should be skipped an the attack just executed immediately.", + null + ); + this.getDouble( + data, + "ChargeDistance", + this.chargeDistance, + 0.0, + DoubleSingleValidator.greaterEqual0(), + BuilderDescriptorState.Stable, + "If this is a charge attack, the distance required for the charge", + null + ); + this.getCodecObject( + data, "InteractionVars", this.interactionVars, BuilderDescriptorState.Stable, "Set of interaction vars for modifying the interaction", null + ); + this.validateBooleanImplicationAnyAntecedent( + ANTECEDENT, new boolean[]{this.skipAiming}, true, SUBSEQUENT, new boolean[]{this.checkLineOfSight, this.avoidFriendlyFire}, false + ); + return this; + } + + @Nullable + public String getAttack(@Nonnull BuilderSupport builderSupport) { + String computedAttack = this.attack.get(builderSupport.getExecutionContext()); + return computedAttack != null && !computedAttack.isEmpty() ? computedAttack : null; + } + + public ActionAttack.AttackType getAttackType(@Nonnull BuilderSupport support) { + return this.attackType.get(support.getExecutionContext()); + } + + public float getChargeTime(@Nonnull BuilderSupport support) { + return this.chargeFor.get(support.getExecutionContext()); + } + + public double[] getAttackPauseRange(@Nonnull BuilderSupport support) { + return this.attackPauseRange.get(support.getExecutionContext()); + } + + public double[] getAimingTimeRange(@Nonnull BuilderSupport support) { + return this.aimingTimeRange.get(support.getExecutionContext()); + } + + public double getMeleeConeAngle() { + return this.meleeConeAngle / 2.0 * (float) (Math.PI / 180.0); + } + + public ActionAttack.BallisticMode getBallisticMode() { + return this.ballisticMode; + } + + public boolean isCheckLineOfSight() { + return this.checkLineOfSight; + } + + public boolean isAvoidFriendlyFire() { + return this.avoidFriendlyFire; + } + + public boolean isDamageFriendlies() { + return this.damageFriendlies; + } + + public boolean isSkipAiming() { + return this.skipAiming; + } + + public double getChargeDistance(@Nonnull BuilderSupport support) { + return this.chargeDistance.get(support.getExecutionContext()); + } + + public int getAttackParameterSlot(@Nonnull BuilderSupport support) { + return this.attackProvided ? Integer.MIN_VALUE : support.getParameterSlot("Attack"); + } + + @Nullable + public Map getInteractionVars() { + return this.interactionVars.build(); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderBodyMotionAimCharge.java b/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderBodyMotionAimCharge.java new file mode 100644 index 0000000..da43167 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderBodyMotionAimCharge.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.npc.corecomponents.combat.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderBodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.combat.BodyMotionAimCharge; +import com.hypixel.hytale.server.npc.instructions.BodyMotion; +import javax.annotation.Nonnull; + +public class BuilderBodyMotionAimCharge extends BuilderBodyMotionBase { + protected final DoubleHolder relativeTurnSpeed = new DoubleHolder(); + + public BuilderBodyMotionAimCharge() { + } + + @Nonnull + public BodyMotion build(@Nonnull BuilderSupport builderSupport) { + return new BodyMotionAimCharge(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Aim the NPC at a target position for performing a charge based on aiming information and ensure that the charge is possible before it's executed."; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderBodyMotionAimCharge readConfig(@Nonnull JsonElement data) { + this.getDouble( + data, + "RelativeTurnSpeed", + this.relativeTurnSpeed, + 1.0, + DoubleRangeValidator.fromExclToIncl(0.0, 2.0), + BuilderDescriptorState.Stable, + "The relative turn speed modifier", + null + ); + this.requireFeature(Feature.AnyPosition); + return this; + } + + public double getRelativeTurnSpeed(@Nonnull BuilderSupport support) { + return this.relativeTurnSpeed.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderHeadMotionAim.java b/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderHeadMotionAim.java new file mode 100644 index 0000000..bdd8bf6 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderHeadMotionAim.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.server.npc.corecomponents.combat.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderHeadMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.combat.HeadMotionAim; +import javax.annotation.Nonnull; + +public class BuilderHeadMotionAim extends BuilderHeadMotionBase { + protected double spread; + protected boolean deflection; + protected double hitProbability; + protected final DoubleHolder relativeTurnSpeed = new DoubleHolder(); + + public BuilderHeadMotionAim() { + } + + @Nonnull + public HeadMotionAim build(@Nonnull BuilderSupport builderSupport) { + return new HeadMotionAim(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Aim at target"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Aim at target considering weapon in hand."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderHeadMotionAim readConfig(@Nonnull JsonElement data) { + this.getDouble( + data, "Spread", d -> this.spread = d, 1.0, DoubleRangeValidator.between(0.0, 5.0), BuilderDescriptorState.Experimental, "Random targeting error", null + ); + this.getDouble( + data, + "HitProbability", + d -> this.hitProbability = d, + 0.33, + DoubleRangeValidator.between01(), + BuilderDescriptorState.Experimental, + "Probability of shot being straight on target", + null + ); + this.getBoolean(data, "Deflection", b -> this.deflection = b, true, BuilderDescriptorState.Experimental, "Compute deflection for moving targets", null); + this.getDouble( + data, + "RelativeTurnSpeed", + this.relativeTurnSpeed, + 1.0, + DoubleRangeValidator.fromExclToIncl(0.0, 2.0), + BuilderDescriptorState.Stable, + "The relative turn speed modifier", + null + ); + this.requireFeature(Feature.AnyPosition); + return this; + } + + public double getSpread() { + return this.spread; + } + + public boolean isDeflection() { + return this.deflection; + } + + public double getHitProbability() { + return this.hitProbability; + } + + public double getRelativeTurnSpeed(@Nonnull BuilderSupport support) { + return this.relativeTurnSpeed.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderSensorDamage.java b/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderSensorDamage.java new file mode 100644 index 0000000..cbb54c2 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderSensorDamage.java @@ -0,0 +1,107 @@ +package com.hypixel.hytale.server.npc.corecomponents.combat.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNullOrNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.combat.SensorDamage; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorDamage extends BuilderSensorBase { + public static final String[] REQUIRE_ONE_OF = new String[]{"Combat", "Drowning", "Environment", "Other"}; + public static final String[] ANTECEDENT = new String[]{"TargetSlot"}; + public static final String[] SUBSEQUENT = new String[]{"Drowning", "Environment", "Other"}; + protected boolean combatDamage; + protected boolean friendlyDamage; + protected boolean drowningDamage; + protected boolean environmentDamage; + protected boolean otherDamage; + protected String targetSlot; + + public BuilderSensorDamage() { + } + + @Nonnull + public SensorDamage build(@Nonnull BuilderSupport builderSupport) { + return new SensorDamage(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test if NPC suffered damage"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Test if NPC suffered damage. A position is only returned when NPC suffered combat damage."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getBoolean(data, "Combat", v -> this.combatDamage = v, true, BuilderDescriptorState.Stable, "Test for combat damage", null); + this.getBoolean( + data, "Friendly", v -> this.friendlyDamage = v, false, BuilderDescriptorState.Stable, "Test for damage from usually disabled damage groups", null + ); + this.getBoolean(data, "Drowning", v -> this.drowningDamage = v, false, BuilderDescriptorState.Stable, "Test for damage from drowning", null); + this.getBoolean(data, "Environment", v -> this.environmentDamage = v, false, BuilderDescriptorState.Stable, "Test for damage from environment", null); + this.getBoolean(data, "Other", v -> this.otherDamage = v, false, BuilderDescriptorState.Stable, "Test for other damage", null); + this.getString( + data, + "TargetSlot", + v -> this.targetSlot = v, + null, + StringNullOrNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "The slot to use for locking on the target if damage is taken. If omitted, target will not be locked", + null + ); + this.validateAny(REQUIRE_ONE_OF, new boolean[]{this.combatDamage, this.drowningDamage, this.environmentDamage, this.otherDamage}); + this.validateBooleanImplicationAnyAntecedent( + ANTECEDENT, + new boolean[]{this.targetSlot != null}, + true, + SUBSEQUENT, + new boolean[]{this.drowningDamage, this.environmentDamage, this.otherDamage}, + false + ); + this.provideFeature(Feature.AnyEntity); + return this; + } + + public boolean isCombatDamage() { + return this.combatDamage; + } + + public boolean isFriendlyDamage() { + return this.friendlyDamage; + } + + public boolean isDrowningDamage() { + return this.drowningDamage; + } + + public boolean isEnvironmentDamage() { + return this.environmentDamage; + } + + public boolean isOtherDamage() { + return this.otherDamage; + } + + public int getTargetSlot(@Nonnull BuilderSupport support) { + return this.targetSlot == null ? Integer.MIN_VALUE : support.getTargetSlot(this.targetSlot); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderSensorIsBackingAway.java b/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderSensorIsBackingAway.java new file mode 100644 index 0000000..0d3885e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/combat/builders/BuilderSensorIsBackingAway.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.npc.corecomponents.combat.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.combat.SensorIsBackingAway; +import javax.annotation.Nonnull; + +public class BuilderSensorIsBackingAway extends BuilderSensorBase { + public BuilderSensorIsBackingAway() { + } + + @Nonnull + public SensorIsBackingAway build(@Nonnull BuilderSupport builderSupport) { + return new SensorIsBackingAway(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test if the NPC is currently backing away from something."; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Test if the NPC is currently backing away from something."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/debug/ActionLog.java b/src/com/hypixel/hytale/server/npc/corecomponents/debug/ActionLog.java new file mode 100644 index 0000000..d69bc4c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/debug/ActionLog.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.npc.corecomponents.debug; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.debug.builders.BuilderActionLog; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class ActionLog extends ActionBase { + protected final String text; + + public ActionLog(@Nonnull BuilderActionLog builder, @Nonnull BuilderSupport support) { + super(builder); + this.text = builder.getText(support); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && this.text != null; + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + NPCPlugin.get().getLogger().at(Level.INFO).atMostEvery(1, TimeUnit.SECONDS).log("[%d]<%s>: %s", ref.getIndex(), role.getRoleName(), this.text); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/debug/ActionTest.java b/src/com/hypixel/hytale/server/npc/corecomponents/debug/ActionTest.java new file mode 100644 index 0000000..6451679 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/debug/ActionTest.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.npc.corecomponents.debug; + +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.debug.builders.BuilderActionTest; +import java.util.Arrays; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class ActionTest extends ActionBase { + public ActionTest(@Nonnull BuilderActionTest builder, @Nonnull BuilderSupport support) { + super(builder); + HytaleLogger logger = NPCPlugin.get().getLogger(); + logger.at(Level.INFO).log("==== Test Action Build Start ==="); + logger.at(Level.INFO).log("Boolean %s", builder.getBoolean(support)); + logger.at(Level.INFO).log("Double %s", builder.getDouble(support)); + logger.at(Level.INFO).log("Float %s", builder.getFloat(support)); + logger.at(Level.INFO).log("Int %s", builder.getInt(support)); + logger.at(Level.INFO).log("String %s", builder.getString(support)); + logger.at(Level.INFO).log("Enum %s", builder.getEnum(support)); + logger.at(Level.INFO).log("EnumSet %s", builder.getEnumSet(support)); + logger.at(Level.INFO).log("Asset %s", builder.getAsset(support)); + logger.at(Level.INFO).log("DoubleArray %s", Arrays.toString(builder.getNumberArray(support))); + logger.at(Level.INFO).log("StringArray %s", Arrays.toString((Object[])builder.getStringArray(support))); + logger.at(Level.INFO).log("===== Test Action Build End ===="); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/debug/BodyMotionTestProbe.java b/src/com/hypixel/hytale/server/npc/corecomponents/debug/BodyMotionTestProbe.java new file mode 100644 index 0000000..20936b3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/debug/BodyMotionTestProbe.java @@ -0,0 +1,118 @@ +package com.hypixel.hytale.server.npc.corecomponents.debug; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.BodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.debug.builders.BuilderBodyMotionTestProbe; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.movement.controllers.ProbeMoveData; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.RoleDebugFlags; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BodyMotionTestProbe extends BodyMotionBase { + protected final double adjustX; + protected final double adjustZ; + protected final double adjustDistance; + protected final float snapAngle; + protected boolean displayText; + protected final Vector3d direction = new Vector3d(); + protected final ProbeMoveData probeMoveData = new ProbeMoveData(); + + public BodyMotionTestProbe(@Nonnull BuilderBodyMotionTestProbe builderBodyMotionTestProbe) { + super(builderBodyMotionTestProbe); + this.probeMoveData.setSaveSegments(true); + this.adjustX = builderBodyMotionTestProbe.getAdjustX(); + this.adjustZ = builderBodyMotionTestProbe.getAdjustZ(); + this.adjustDistance = builderBodyMotionTestProbe.getAdjustDistance(); + this.snapAngle = builderBodyMotionTestProbe.getSnapAngle() * (float) (Math.PI / 180.0); + this.probeMoveData.setAvoidingBlockDamage(builderBodyMotionTestProbe.isAvoidingBlockDamage()); + this.probeMoveData.setRelaxedMoveConstraints(builderBodyMotionTestProbe.isRelaxedMoveConstraints()); + } + + @Override + public void activate(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + this.displayText = role.getDebugSupport().isDebugFlagSet(RoleDebugFlags.DisplayCustom); + if (!(this.adjustX < 0.0) || !(this.adjustZ < 0.0)) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + if (transformComponent != null) { + Vector3d position = transformComponent.getPosition(); + double x = position.x; + double z = position.z; + if (this.adjustX >= 0.0) { + x = MathUtil.fastFloor(x) + this.adjustX; + } + + if (this.adjustZ >= 0.0) { + z = MathUtil.fastFloor(z) + this.adjustZ; + } + + NPCEntity npcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + npcComponent.moveTo(ref, x, position.y, z, componentAccessor); + } + } + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role role, + @Nullable InfoProvider sensorInfo, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + desiredSteering.clear(); + if (sensorInfo != null && sensorInfo.getPositionProvider().providePosition(this.direction)) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + if (transformComponent == null) { + return false; + } else { + Vector3d position = transformComponent.getPosition(); + this.direction.subtract(position); + if (!this.displayText) { + return true; + } else { + double length = this.direction.length(); + if (length <= 1.0E-6) { + return true; + } else { + if (this.adjustDistance > 0.0) { + length = this.adjustDistance; + this.direction.setLength(this.adjustDistance); + } + + double x = this.direction.getX(); + double y = this.direction.getY(); + double z = this.direction.getZ(); + float yaw = PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(x, z)); + float pitch = PhysicsMath.pitchFromDirection(x, y, z); + if (this.snapAngle > 0.0F && this.snapAngle < (float) Math.PI) { + yaw = MathUtil.fastRound(yaw / this.snapAngle) * this.snapAngle; + PhysicsMath.vectorFromAngles(yaw, pitch, this.direction).setLength(length); + } + + desiredSteering.setYaw(yaw); + desiredSteering.setPitch(pitch); + double distance = role.getActiveMotionController().probeMove(ref, position, this.direction, this.probeMoveData, componentAccessor); + role.getDebugSupport().setDisplayCustomString(String.format("PR: %.2f DX: %.2f DZ: %.2f", distance, this.direction.x, this.direction.z)); + return true; + } + } + } + } else { + return false; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/debug/builders/BuilderActionLog.java b/src/com/hypixel/hytale/server/npc/corecomponents/debug/builders/BuilderActionLog.java new file mode 100644 index 0000000..6c2769c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/debug/builders/BuilderActionLog.java @@ -0,0 +1,49 @@ +package com.hypixel.hytale.server.npc.corecomponents.debug.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.debug.ActionLog; +import javax.annotation.Nonnull; + +public class BuilderActionLog extends BuilderActionBase { + protected final StringHolder text = new StringHolder(); + + public BuilderActionLog() { + } + + @Nonnull + public ActionLog build(@Nonnull BuilderSupport builderSupport) { + return new ActionLog(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Log a message to console."; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionLog readConfig(@Nonnull JsonElement data) { + this.requireString(data, "Message", this.text, null, BuilderDescriptorState.Stable, "Text to print to console.", null); + return this; + } + + public String getText(@Nonnull BuilderSupport support) { + return this.text.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/debug/builders/BuilderActionTest.java b/src/com/hypixel/hytale/server/npc/corecomponents/debug/builders/BuilderActionTest.java new file mode 100644 index 0000000..2eae6f8 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/debug/builders/BuilderActionTest.java @@ -0,0 +1,149 @@ +package com.hypixel.hytale.server.npc.corecomponents.debug.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumSetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.FloatHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.IntHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.ModelExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.debug.ActionTest; +import com.hypixel.hytale.server.npc.role.RoleDebugFlags; +import java.util.EnumSet; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderActionTest extends BuilderActionBase { + protected final BooleanHolder booleanHolder = new BooleanHolder(); + protected final DoubleHolder doubleHolder = new DoubleHolder(); + protected final FloatHolder floatHolder = new FloatHolder(); + protected final IntHolder intHolder = new IntHolder(); + protected final StringHolder stringHolder = new StringHolder(); + protected final EnumHolder enumHolder = new EnumHolder<>(); + protected final EnumSetHolder enumSetHolder = new EnumSetHolder<>(); + protected final AssetHolder assetHolder = new AssetHolder(); + protected final NumberArrayHolder numberArrayHolder = new NumberArrayHolder(); + protected final StringArrayHolder stringArrayHolder = new StringArrayHolder(); + + public BuilderActionTest() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test action to exercise attribute evaluation (DO NOT USE)"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + public ActionTest build(@Nonnull BuilderSupport builderSupport) { + return new ActionTest(this, builderSupport); + } + + @Nonnull + public BuilderActionTest readConfig(@Nonnull JsonElement data) { + this.getBoolean(data, "Boolean", this.booleanHolder, true, BuilderDescriptorState.Deprecated, "Boolean True", null); + this.getDouble(data, "Double", this.doubleHolder, 0.0, null, BuilderDescriptorState.Deprecated, "Double 0", null); + this.getFloat(data, "Float", this.floatHolder, 0.0, null, BuilderDescriptorState.Deprecated, "Float 0", null); + this.getInt(data, "Int", this.intHolder, 0, null, BuilderDescriptorState.Deprecated, "Int 0", null); + this.getString(data, "String", this.stringHolder, "Test", null, BuilderDescriptorState.Deprecated, "String Test", null); + this.getEnum( + data, + "Enum", + this.enumHolder, + RoleDebugFlags.class, + RoleDebugFlags.Collisions, + BuilderDescriptorState.Deprecated, + "Enum RoleDebugFlags Collisions", + null + ); + this.getEnumSet( + data, + "EnumSet", + this.enumSetHolder, + RoleDebugFlags.class, + EnumSet.of(RoleDebugFlags.Collisions, RoleDebugFlags.Flock), + BuilderDescriptorState.Deprecated, + "EnumSet Collisions Flock", + null + ); + this.getAsset( + data, + "Asset", + this.assetHolder, + "Sheep", + ModelExistsValidator.withConfig(EnumSet.of(AssetValidator.Config.MATCHER)), + BuilderDescriptorState.Deprecated, + "Asset Sheep", + null + ); + this.getDoubleArray( + data, "DoubleArray", this.numberArrayHolder, new double[]{1.0, 2.0}, 0, 10, null, BuilderDescriptorState.Deprecated, "DoubleArray [1,2] 0-10", null + ); + this.getStringArray( + data, "StringArray", this.stringArrayHolder, new String[]{"a", "b"}, 0, 10, null, BuilderDescriptorState.Deprecated, "StringArray [a,b] 0-10", null + ); + return this; + } + + public boolean getBoolean(@Nonnull BuilderSupport support) { + return this.booleanHolder.get(support.getExecutionContext()); + } + + public double getDouble(@Nonnull BuilderSupport support) { + return this.doubleHolder.get(support.getExecutionContext()); + } + + public float getFloat(@Nonnull BuilderSupport support) { + return this.floatHolder.get(support.getExecutionContext()); + } + + public int getInt(@Nonnull BuilderSupport support) { + return this.intHolder.get(support.getExecutionContext()); + } + + public String getString(@Nonnull BuilderSupport support) { + return this.stringHolder.get(support.getExecutionContext()); + } + + public RoleDebugFlags getEnum(@Nonnull BuilderSupport support) { + return this.enumHolder.get(support.getExecutionContext()); + } + + public EnumSet getEnumSet(@Nonnull BuilderSupport support) { + return this.enumSetHolder.get(support.getExecutionContext()); + } + + public String getAsset(@Nonnull BuilderSupport support) { + return this.assetHolder.get(support.getExecutionContext()); + } + + public double[] getNumberArray(@Nonnull BuilderSupport support) { + return this.numberArrayHolder.get(support.getExecutionContext()); + } + + @Nullable + public String[] getStringArray(@Nonnull BuilderSupport support) { + return this.stringArrayHolder.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/debug/builders/BuilderBodyMotionTestProbe.java b/src/com/hypixel/hytale/server/npc/corecomponents/debug/builders/BuilderBodyMotionTestProbe.java new file mode 100644 index 0000000..4e13152 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/debug/builders/BuilderBodyMotionTestProbe.java @@ -0,0 +1,105 @@ +package com.hypixel.hytale.server.npc.corecomponents.debug.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderBodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.debug.BodyMotionTestProbe; +import javax.annotation.Nonnull; + +public class BuilderBodyMotionTestProbe extends BuilderBodyMotionBase { + protected double adjustX; + protected double adjustZ; + protected double adjustDistance; + protected float snapAngle; + protected boolean isAvoidingBlockDamage; + protected boolean isRelaxedMoveConstraints; + + public BuilderBodyMotionTestProbe() { + } + + @Nonnull + public BodyMotionTestProbe build(BuilderSupport builderSupport) { + return new BodyMotionTestProbe(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Debugging - Test probing"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + public BuilderBodyMotionTestProbe readConfig(@Nonnull JsonElement data) { + this.getDouble(data, "AdjustX", v -> this.adjustX = v, -1.0, null, BuilderDescriptorState.Experimental, "X block position adjustment", null); + this.getDouble(data, "AdjustZ", v -> this.adjustZ = v, -1.0, null, BuilderDescriptorState.Experimental, "Y block position adjustment", null); + this.getDouble( + data, + "AdjustDistance", + v -> this.adjustDistance = v, + -1.0, + null, + BuilderDescriptorState.Experimental, + "Set probe direction length for debugging", + null + ); + this.getFloat( + data, "SnapAngle", v -> this.snapAngle = v, -1.0F, null, BuilderDescriptorState.Experimental, "Snap angle to multiples of value for debugging", null + ); + this.getBoolean( + data, + "AvoidBlockDamage", + v -> this.isAvoidingBlockDamage = v, + true, + BuilderDescriptorState.Stable, + "Should avoid environmental damage from blocks", + null + ); + this.getBoolean( + data, + "RelaxedMoveConstraints", + v -> this.isRelaxedMoveConstraints = v, + false, + BuilderDescriptorState.Stable, + "NPC can do movements like wading (depends on motion controller type)", + null + ); + return this; + } + + public double getAdjustX() { + return this.adjustX; + } + + public double getAdjustZ() { + return this.adjustZ; + } + + public double getAdjustDistance() { + return this.adjustDistance; + } + + public float getSnapAngle() { + return this.snapAngle; + } + + public boolean isAvoidingBlockDamage() { + return this.isAvoidingBlockDamage; + } + + public boolean isRelaxedMoveConstraints() { + return this.isRelaxedMoveConstraints; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionBeacon.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionBeacon.java new file mode 100644 index 0000000..713f4d2 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionBeacon.java @@ -0,0 +1,167 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.matrix.Matrix4d; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.modules.debug.DebugUtils; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.components.messaging.BeaconSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionBeacon; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.RoleDebugFlags; +import com.hypixel.hytale.server.npc.role.support.PositionCache; +import com.hypixel.hytale.server.npc.role.support.WorldSupport; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionBeacon extends ActionBase { + protected final String message; + protected final double range; + protected final int[] targetGroups; + protected final int targetToSendSlot; + protected final double expirationTime; + protected final int sendCount; + @Nullable + protected final List> sendList; + + public ActionBeacon(@Nonnull BuilderActionBeacon builderActionBeacon, @Nonnull BuilderSupport support) { + super(builderActionBeacon); + this.message = builderActionBeacon.getMessage(support); + this.range = builderActionBeacon.getRange(); + this.targetGroups = builderActionBeacon.getTargetGroups(support); + this.targetToSendSlot = builderActionBeacon.getTargetToSendSlot(support); + this.expirationTime = builderActionBeacon.getExpirationTime(); + this.sendCount = builderActionBeacon.getSendCount(); + this.sendList = this.sendCount > 0 ? new ObjectArrayList<>(this.sendCount) : null; + } + + @Override + public void registerWithSupport(@Nonnull Role role) { + role.getPositionCache().requireEntityDistanceUnsorted(this.range); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return !super.canExecute(ref, role, sensorInfo, dt, store) + ? false + : this.targetToSendSlot == Integer.MIN_VALUE || role.getMarkedEntitySupport().hasMarkedEntityInSlot(this.targetToSendSlot); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + Ref target = this.targetToSendSlot >= 0 ? role.getMarkedEntitySupport().getMarkedEntityRef(this.targetToSendSlot) : ref; + PositionCache positionCache = role.getPositionCache(); + if (this.sendCount <= 0) { + positionCache.forEachNPCUnordered( + this.range, + ActionBeacon::filterNPCs, + (_ref, _this, _target, _self) -> _this.sendNPCMessage(_self, _ref, _target, _self.getStore()), + this, + role, + target, + ref, + store + ); + return true; + } else { + positionCache.forEachNPCUnordered( + this.range, + ActionBeacon::filterNPCs, + (npcEntity, _this, _sendList, _self) -> RandomExtra.reservoirSample(npcEntity, _this.sendCount, _sendList), + this, + role, + this.sendList, + ref, + store + ); + + for (int i = 0; i < this.sendList.size(); i++) { + this.sendNPCMessage(ref, this.sendList.get(i), target, store); + } + + this.sendList.clear(); + return true; + } + } + + protected static boolean filterNPCs( + @Nonnull Ref ref, @Nonnull ActionBeacon _this, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor + ) { + return ref.getStore().getComponent(ref, BeaconSupport.getComponentType()) != null + && WorldSupport.isGroupMember(role.getRoleIndex(), ref, _this.targetGroups, componentAccessor); + } + + protected void sendNPCMessage( + @Nonnull Ref self, + @Nonnull Ref targetRef, + @Nonnull Ref target, + @Nonnull ComponentAccessor componentAccessor + ) { + NPCEntity npcComponent = componentAccessor.getComponent(self, NPCEntity.getComponentType()); + + assert npcComponent != null; + + Role role = npcComponent.getRole(); + if (role.getDebugSupport().isDebugFlagSet(RoleDebugFlags.BeaconMessages)) { + NPCPlugin.get() + .getLogger() + .atInfo() + .log("ID %d sent message '%s' with target ID %d to ID %d", self.getIndex(), this.message, target.getIndex(), targetRef.getIndex()); + ThreadLocalRandom random = ThreadLocalRandom.current(); + Vector3f color = new Vector3f(random.nextFloat(), random.nextFloat(), random.nextFloat()); + Matrix4d matrix = new Matrix4d(); + matrix.identity(); + Matrix4d tmp = new Matrix4d(); + TransformComponent transformComponent = componentAccessor.getComponent(self, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d pos = transformComponent.getPosition(); + ModelComponent modelComponent = componentAccessor.getComponent(self, ModelComponent.getComponentType()); + + assert modelComponent != null; + + Model model = modelComponent.getModel(); + double x = pos.x; + double y = pos.y + (model != null ? model.getEyeHeight(self, componentAccessor) : 0.0F); + double z = pos.z; + matrix.translate(x, y + random.nextFloat() * 0.5 - 0.25, z); + TransformComponent targetTransformComponent = componentAccessor.getComponent(targetRef, TransformComponent.getComponentType()); + + assert targetTransformComponent != null; + + Vector3d targetPos = targetTransformComponent.getPosition(); + ModelComponent targetModelComponent = componentAccessor.getComponent(targetRef, ModelComponent.getComponentType()); + float targetEyeHeight = targetModelComponent != null ? targetModelComponent.getModel().getEyeHeight(targetRef, componentAccessor) : 0.0F; + x -= targetPos.getX(); + y -= targetPos.getY() + targetEyeHeight; + z -= targetPos.getZ(); + double angleY = Math.atan2(-z, -x); + matrix.rotateAxis(angleY + (float) (Math.PI / 2), 0.0, 1.0, 0.0, tmp); + double angleX = Math.atan2(Math.sqrt(x * x + z * z), -y); + matrix.rotateAxis(angleX, 1.0, 0.0, 0.0, tmp); + DebugUtils.addArrow(componentAccessor.getExternalData().getWorld(), matrix, color, pos.distanceTo(targetPos), 5.0F, true); + } + + BeaconSupport beaconSupportComponent = componentAccessor.getComponent(targetRef, BeaconSupport.getComponentType()); + if (beaconSupportComponent != null) { + beaconSupportComponent.postMessage(this.message, target, this.expirationTime); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionIgnoreForAvoidance.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionIgnoreForAvoidance.java new file mode 100644 index 0000000..34b0641 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionIgnoreForAvoidance.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionIgnoreForAvoidance; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionIgnoreForAvoidance extends ActionBase { + private final int targetSlot; + + public ActionIgnoreForAvoidance(@Nonnull BuilderActionIgnoreForAvoidance builder, @Nonnull BuilderSupport support) { + super(builder); + this.targetSlot = builder.getTargetSlot(support); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + role.getMarkedEntitySupport().setTargetSlotToIgnoreForAvoidance(this.targetSlot); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionNotify.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionNotify.java new file mode 100644 index 0000000..253fff4 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionNotify.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.components.messaging.BeaconSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionNotify; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionNotify extends ActionBase { + protected final String message; + protected final double expirationTime; + protected final int usedTargetSlot; + + public ActionNotify(@Nonnull BuilderActionNotify builderActionBase, @Nonnull BuilderSupport support) { + super(builderActionBase); + this.message = builderActionBase.getMessage(support); + this.expirationTime = builderActionBase.getExpirationTime(); + this.usedTargetSlot = builderActionBase.getUsedTargetSlot(support); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, @Nonnull InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + Ref targetRef; + if (this.usedTargetSlot >= 0) { + targetRef = role.getMarkedEntitySupport().getMarkedEntityRef(this.usedTargetSlot); + } else { + targetRef = sensorInfo.getPositionProvider().getTarget(); + } + + if (targetRef != null) { + BeaconSupport beaconSupport = store.getComponent(targetRef, BeaconSupport.getComponentType()); + if (beaconSupport != null) { + beaconSupport.postMessage(this.message, ref, this.expirationTime); + } + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionOverrideAttitude.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionOverrideAttitude.java new file mode 100644 index 0000000..fea467f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionOverrideAttitude.java @@ -0,0 +1,42 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionOverrideAttitude; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionOverrideAttitude extends ActionBase { + protected final Attitude attitude; + protected final double duration; + + public ActionOverrideAttitude(@Nonnull BuilderActionOverrideAttitude builder, @Nonnull BuilderSupport support) { + super(builder); + this.attitude = builder.getAttitude(support); + this.duration = builder.getDuration(support); + support.requireAttitudeOverrideMemory(); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && sensorInfo != null && sensorInfo.hasPosition(); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, @Nonnull InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + Ref target = sensorInfo.getPositionProvider().getTarget(); + if (target == null) { + return true; + } else { + role.getWorldSupport().overrideAttitude(target, this.attitude, this.duration); + return true; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionReleaseTarget.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionReleaseTarget.java new file mode 100644 index 0000000..6564cd2 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionReleaseTarget.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionReleaseTarget; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionReleaseTarget extends ActionBase { + protected final int targetSlot; + + public ActionReleaseTarget(@Nonnull BuilderActionReleaseTarget builder, @Nonnull BuilderSupport support) { + super(builder); + this.targetSlot = builder.getTargetSlot(support); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + role.getMarkedEntitySupport().clearMarkedEntity(this.targetSlot); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionSetMarkedTarget.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionSetMarkedTarget.java new file mode 100644 index 0000000..d517692 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionSetMarkedTarget.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionSetMarkedTarget; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionSetMarkedTarget extends ActionBase { + protected final int targetSlot; + + public ActionSetMarkedTarget(@Nonnull BuilderActionSetMarkedTarget builder, @Nonnull BuilderSupport support) { + super(builder); + this.targetSlot = builder.getTargetSlot(support); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + role.getMarkedEntitySupport().setMarkedEntity(this.targetSlot, sensorInfo.getPositionProvider().getTarget()); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionSetStat.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionSetStat.java new file mode 100644 index 0000000..f08c01c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/ActionSetStat.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionSetStat; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionSetStat extends ActionBase { + protected static final ComponentType STAT_MAP_COMPONENT_TYPE = EntityStatMap.getComponentType(); + protected final int stat; + protected final float value; + protected final boolean add; + + public ActionSetStat(@Nonnull BuilderActionSetStat builder, @Nonnull BuilderSupport support) { + super(builder); + this.stat = builder.getStat(support); + this.value = builder.getValue(support); + this.add = builder.isAdd(support); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return !super.canExecute(ref, role, sensorInfo, dt, store) ? false : store.getComponent(ref, STAT_MAP_COMPONENT_TYPE).get(this.stat) != null; + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + EntityStatMap entityStatMapComponent = store.getComponent(ref, STAT_MAP_COMPONENT_TYPE); + + assert entityStatMapComponent != null; + + if (this.add) { + entityStatMapComponent.addStatValue(this.stat, this.value); + } else { + entityStatMapComponent.setStatValue(this.stat, this.value); + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/HeadMotionWatch.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/HeadMotionWatch.java new file mode 100644 index 0000000..8b7a8ae --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/HeadMotionWatch.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.HeadMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderHeadMotionWatch; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.IPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HeadMotionWatch extends HeadMotionBase { + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected static final ComponentType MODEL_COMPONENT_TYPE = ModelComponent.getComponentType(); + protected final double relativeTurnSpeed; + + public HeadMotionWatch(@Nonnull BuilderHeadMotionWatch builderHeadMotionWatch, @Nonnull BuilderSupport support) { + super(builderHeadMotionWatch); + this.relativeTurnSpeed = builderHeadMotionWatch.getRelativeTurnSpeed(support); + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role role, + @Nullable InfoProvider sensorInfo, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + if (sensorInfo != null && sensorInfo.hasPosition()) { + IPositionProvider positionProvider = sensorInfo.getPositionProvider(); + Ref targetRef = positionProvider.getTarget(); + double x = positionProvider.getX(); + double y = positionProvider.getY(); + double z = positionProvider.getZ(); + if (targetRef != null) { + ModelComponent targetModelComponent = componentAccessor.getComponent(targetRef, MODEL_COMPONENT_TYPE); + y += targetModelComponent != null ? targetModelComponent.getModel().getEyeHeight() : 0.0; + } + + TransformComponent transformComponent = componentAccessor.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + ModelComponent modelComponent = componentAccessor.getComponent(ref, MODEL_COMPONENT_TYPE); + + assert modelComponent != null; + + Model model = modelComponent.getModel(); + Vector3d position = transformComponent.getPosition(); + x -= position.getX(); + y -= position.getY() + model.getEyeHeight(); + z -= position.getZ(); + float yaw = PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(x, z)); + float pitch = PhysicsMath.pitchFromDirection(x, y, z); + desiredSteering.clearTranslation(); + desiredSteering.setYaw(yaw); + desiredSteering.setPitch(pitch); + desiredSteering.setRelativeTurnSpeed(this.relativeTurnSpeed); + return true; + } else { + desiredSteering.clear(); + return true; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorBeacon.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorBeacon.java new file mode 100644 index 0000000..c9957b6 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorBeacon.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.components.messaging.BeaconSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorBeacon; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.RoleDebugFlags; +import com.hypixel.hytale.server.npc.sensorinfo.EntityPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorBeacon extends SensorBase { + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected final int messageIndex; + protected final double range; + protected final int targetSlot; + protected final boolean consume; + private final EntityPositionProvider positionProvider = new EntityPositionProvider(); + + public SensorBeacon(@Nonnull BuilderSensorBeacon builderSensorBeacon, @Nonnull BuilderSupport builderSupport) { + super(builderSensorBeacon); + this.messageIndex = builderSensorBeacon.getMessageSlot(builderSupport); + this.range = builderSensorBeacon.getRange(builderSupport); + this.targetSlot = builderSensorBeacon.getTargetSlot(builderSupport); + this.consume = builderSensorBeacon.isConsume(); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + return false; + } else { + BeaconSupport beaconSupportComponent = store.getComponent(ref, BeaconSupport.getComponentType()); + if (beaconSupportComponent == null) { + this.positionProvider.clear(); + return false; + } else if (!beaconSupportComponent.isMessageQueued(this.messageIndex)) { + this.positionProvider.clear(); + return false; + } else { + Ref target = this.consume + ? beaconSupportComponent.pollMessage(this.messageIndex) + : beaconSupportComponent.peekMessage(this.messageIndex); + if (target == null) { + this.positionProvider.clear(); + return false; + } else { + Ref targetRef = this.positionProvider.setTarget(target, store); + if (targetRef != null && targetRef.isValid()) { + TransformComponent targetTransformComponent = store.getComponent(targetRef, TRANSFORM_COMPONENT_TYPE); + + assert targetTransformComponent != null; + + Vector3d targetPosition = targetTransformComponent.getPosition(); + TransformComponent transformComponent = store.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + if (targetPosition.distanceSquaredTo(position) > this.range * this.range) { + this.positionProvider.clear(); + return false; + } else { + if (this.targetSlot >= 0) { + role.getMarkedEntitySupport().setMarkedEntity(this.targetSlot, targetRef); + } + + if (role.getDebugSupport().isDebugFlagSet(RoleDebugFlags.BeaconMessages)) { + NPCPlugin.get() + .getLogger() + .atInfo() + .log( + "ID %d received message '%s' with target ID %d", + ref.getIndex(), + beaconSupportComponent.getMessageTextForIndex(this.messageIndex), + target.getIndex() + ); + } + + return true; + } + } else { + this.positionProvider.clear(); + return false; + } + } + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorCount.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorCount.java new file mode 100644 index 0000000..46debf5 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorCount.java @@ -0,0 +1,82 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderManager; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorCount; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.WorldSupport; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorCount extends SensorBase { + protected final int minCount; + protected final int maxCount; + protected final double minRange; + protected final double maxRange; + protected final int[] includeGroups; + protected final int[] excludeGroups; + protected boolean findPlayers; + protected final boolean haveIncludeGroups; + protected final boolean haveExcludeGroups; + + public SensorCount(@Nonnull BuilderSensorCount builderSensorCount, @Nonnull BuilderSupport support) { + super(builderSensorCount); + int[] count = builderSensorCount.getCount(support); + this.minCount = count[0]; + this.maxCount = count[1]; + double[] range = builderSensorCount.getRange(support); + this.minRange = range[0]; + this.maxRange = range[1]; + this.includeGroups = builderSensorCount.getIncludeGroups(); + this.excludeGroups = builderSensorCount.getExcludeGroups(); + this.haveIncludeGroups = this.includeGroups != null && this.includeGroups.length > 0; + this.haveExcludeGroups = this.excludeGroups != null && this.excludeGroups.length > 0; + } + + @Override + public void registerWithSupport(@Nonnull Role role) { + if (this.haveIncludeGroups) { + this.findPlayers = groupListHasPlayer(this.includeGroups); + } else { + this.findPlayers = !this.haveExcludeGroups || !groupListHasPlayer(this.excludeGroups); + } + + role.getPositionCache().requireEntityDistanceSorted(this.maxRange); + if (this.findPlayers) { + role.getPositionCache().requirePlayerDistanceSorted(this.maxRange); + } + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + return super.matches(ref, role, dt, store) + && role.getPositionCache() + .isEntityCountInRange(this.minRange, this.maxRange, this.minCount, this.maxCount, this.findPlayers, role, SensorCount::filterNPC, this, store); + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } + + protected static boolean groupListHasPlayer(@Nonnull int[] groups) { + for (int group : groups) { + if (WorldSupport.hasTagInGroup(group, BuilderManager.getPlayerGroupID())) { + return true; + } + } + + return false; + } + + protected boolean filterNPC(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + int roleIndex = role.getRoleIndex(); + return (!this.haveIncludeGroups || WorldSupport.isGroupMember(roleIndex, ref, this.includeGroups, componentAccessor)) + && (!this.haveExcludeGroups || !WorldSupport.isGroupMember(roleIndex, ref, this.excludeGroups, componentAccessor)); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorEntity.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorEntity.java new file mode 100644 index 0000000..87d406a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorEntity.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorEntity; +import javax.annotation.Nonnull; + +public class SensorEntity extends SensorEntityBase { + protected final boolean getPlayers; + protected final boolean getNPCs; + protected final boolean excludeOwnType; + + public SensorEntity(@Nonnull BuilderSensorEntity builder, @Nonnull BuilderSupport builderSupport) { + super(builder, builder.getPrioritiser(builderSupport), builderSupport); + this.getPlayers = builder.isGetPlayers(builderSupport); + this.getNPCs = builder.isGetNPCs(builderSupport); + this.excludeOwnType = builder.isExcludeOwnType(builderSupport); + this.initialisePrioritiser(); + } + + @Override + public boolean isGetPlayers() { + return this.getPlayers; + } + + @Override + public boolean isGetNPCs() { + return this.getNPCs; + } + + @Override + public boolean isExcludingOwnType() { + return this.excludeOwnType; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorEntityBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorEntityBase.java new file mode 100644 index 0000000..5b22877 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorEntityBase.java @@ -0,0 +1,357 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.ComponentContext; +import com.hypixel.hytale.server.npc.corecomponents.ISensorEntityCollector; +import com.hypixel.hytale.server.npc.corecomponents.ISensorEntityPrioritiser; +import com.hypixel.hytale.server.npc.corecomponents.SensorWithEntityFilters; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorEntityBase; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.EntityPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.IEntityByPriorityFilter; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class SensorEntityBase extends SensorWithEntityFilters { + private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + @Nullable + protected static final ComponentType NPC_COMPONENT_TYPE = NPCEntity.getComponentType(); + protected static final ComponentType PLAYER_COMPONENT_TYPE = Player.getComponentType(); + protected static final ComponentType DEATH_COMPONENT_TYPE = DeathComponent.getComponentType(); + protected final double range; + protected final double minRange; + protected final boolean useProjectedDistance; + protected final boolean lockOnTarget; + protected final boolean autoUnlockTarget; + protected final boolean onlyLockedTarget; + protected final int lockedTargetSlot; + protected final int ignoredTargetSlot; + protected final ISensorEntityPrioritiser prioritiser; + protected IEntityByPriorityFilter npcPrioritiser; + protected IEntityByPriorityFilter playerPrioritiser; + @Nullable + protected final ISensorEntityCollector collector; + protected int ownRole; + protected final EntityPositionProvider positionProvider = new EntityPositionProvider(); + + public SensorEntityBase(@Nonnull BuilderSensorEntityBase builder, ISensorEntityPrioritiser prioritiser, @Nonnull BuilderSupport builderSupport) { + super(builder, builder.getFilters(builderSupport, prioritiser, ComponentContext.SensorEntity)); + this.range = builder.getRange(builderSupport); + this.minRange = builder.getMinRange(builderSupport); + this.lockOnTarget = builder.isLockOnTarget(builderSupport); + this.autoUnlockTarget = builder.isAutoUnlockTarget(builderSupport); + this.onlyLockedTarget = builder.isOnlyLockedTarget(builderSupport); + this.useProjectedDistance = builder.isUseProjectedDistance(builderSupport); + this.lockedTargetSlot = builder.getLockedTargetSlot(builderSupport); + this.ignoredTargetSlot = builder.getIgnoredTargetSlot(builderSupport); + this.prioritiser = prioritiser; + this.collector = builder.getCollector(builderSupport); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + this.positionProvider.clear(); + return false; + } else { + TransformComponent transformComponent = store.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + this.ownRole = role.getRoleIndex(); + if (this.ignoredTargetSlot == Integer.MIN_VALUE || this.ignoredTargetSlot != this.lockedTargetSlot) { + Ref targetRef = this.filterLockedEntity(ref, position, role, store); + if (targetRef != null) { + this.collector.init(ref, role, store); + if (!this.collector.terminateOnFirstMatch()) { + this.findPlayerOrEntity(ref, position, role, store); + } + + this.collector.cleanup(); + return this.positionProvider.setTarget(targetRef, store) != null; + } + } + + if (this.onlyLockedTarget) { + this.positionProvider.clear(); + return false; + } else { + this.collector.init(ref, role, store); + Ref targetRef = this.findPlayerOrEntity(ref, position, role, store); + this.collector.cleanup(); + if (targetRef == null) { + this.positionProvider.clear(); + return false; + } else { + this.positionProvider.setTarget(targetRef, store); + if (this.lockOnTarget) { + role.getMarkedEntitySupport().setMarkedEntity(this.lockedTargetSlot, targetRef); + } + + return true; + } + } + } + } + + @Override + public void done() { + this.positionProvider.clear(); + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } + + @Override + public void registerWithSupport(@Nonnull Role role) { + super.registerWithSupport(role); + if (this.isGetPlayers()) { + role.getPositionCache().requirePlayerDistanceSorted(this.range); + } + + if (this.isGetNPCs()) { + role.getPositionCache().requireEntityDistanceSorted(this.range); + } + + this.prioritiser.registerWithSupport(role); + this.collector.registerWithSupport(role); + } + + @Override + public void motionControllerChanged( + @Nullable Ref ref, + @Nonnull NPCEntity npcComponent, + MotionController motionController, + @Nullable ComponentAccessor componentAccessor + ) { + super.motionControllerChanged(ref, npcComponent, motionController, componentAccessor); + this.prioritiser.motionControllerChanged(ref, npcComponent, motionController, componentAccessor); + this.collector.motionControllerChanged(ref, npcComponent, motionController, componentAccessor); + } + + @Override + public void loaded(Role role) { + super.loaded(role); + this.prioritiser.loaded(role); + this.collector.loaded(role); + } + + @Override + public void spawned(Role role) { + super.spawned(role); + this.prioritiser.spawned(role); + this.collector.spawned(role); + } + + @Override + public void unloaded(Role role) { + super.unloaded(role); + this.prioritiser.unloaded(role); + this.collector.unloaded(role); + } + + @Override + public void removed(Role role) { + super.removed(role); + this.prioritiser.removed(role); + this.collector.removed(role); + } + + @Override + public void teleported(Role role, World from, World to) { + super.teleported(role, from, to); + this.prioritiser.teleported(role, from, to); + this.collector.teleported(role, from, to); + } + + protected void initialisePrioritiser() { + this.npcPrioritiser = this.isGetNPCs() ? this.prioritiser.getNPCPrioritiser() : null; + this.playerPrioritiser = this.isGetPlayers() ? this.prioritiser.getPlayerPrioritiser() : null; + } + + protected abstract boolean isGetPlayers(); + + protected abstract boolean isGetNPCs(); + + protected boolean isExcludingOwnType() { + return false; + } + + @Nullable + protected Ref filterLockedEntity( + @Nonnull Ref ref, @Nonnull Vector3d position, @Nonnull Role role, @Nonnull Store store + ) { + Ref target = this.lockedTargetSlot >= 0 ? role.getMarkedEntitySupport().getMarkedEntityRef(this.lockedTargetSlot) : null; + if (target == null) { + return null; + } else if (this.filterEntityWithRange(ref, target, position, role, store)) { + return target; + } else { + if (this.autoUnlockTarget) { + role.getMarkedEntitySupport().clearMarkedEntity(this.lockedTargetSlot); + } + + return null; + } + } + + protected boolean filterEntityWithRange( + @Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Vector3d position, @Nonnull Role role, @Nonnull Store store + ) { + Player playerComponent = store.getComponent(targetRef, Player.getComponentType()); + if (playerComponent != null) { + if (!this.isGetPlayers()) { + return false; + } + + GameMode gameMode = playerComponent.getGameMode(); + if (gameMode == GameMode.Creative) { + PlayerSettings playerSettingsComponent = store.getComponent(targetRef, PlayerSettings.getComponentType()); + boolean allowDetection = playerSettingsComponent != null && playerSettingsComponent.creativeSettings().allowNPCDetection(); + if (!allowDetection) { + return false; + } + } + } else { + if (!store.getArchetype(targetRef).contains(NPC_COMPONENT_TYPE)) { + return false; + } + + if (!this.isGetNPCs()) { + return false; + } + } + + TransformComponent targetTransformComponent = store.getComponent(targetRef, TRANSFORM_COMPONENT_TYPE); + + assert targetTransformComponent != null; + + Vector3d pos = targetTransformComponent.getPosition(); + double squaredDistance = role.getActiveMotionController().getSquaredDistance(position, pos, this.useProjectedDistance); + return !(squaredDistance < this.minRange * this.minRange) && !(squaredDistance > this.range * this.range) + ? this.filterEntity(ref, targetRef, role, store) + : false; + } + + protected boolean filterEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + if (store.getArchetype(targetRef).contains(DEATH_COMPONENT_TYPE)) { + return false; + } else { + NPCEntity npcComponent = store.getComponent(targetRef, NPC_COMPONENT_TYPE); + return this.isExcludingOwnType() && npcComponent != null && this.ownRole == npcComponent.getRoleIndex() + ? false + : this.matchesFilters(ref, targetRef, role, store); + } + } + + protected boolean filterPrioritisedPlayer( + @Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store + ) { + return this.filterPrioritisedEntity(ref, targetRef, role, store, this.playerPrioritiser); + } + + protected boolean filterPrioritisedNPC( + @Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store + ) { + return this.filterPrioritisedEntity(ref, targetRef, role, store, this.npcPrioritiser); + } + + protected boolean filterPrioritisedEntity( + @Nonnull Ref ref, + @Nonnull Ref targetRef, + @Nonnull Role role, + @Nonnull Store store, + @Nonnull IEntityByPriorityFilter playerPrioritiser + ) { + if (!this.filterEntity(ref, targetRef, role, store)) { + this.collector.collectNonMatching(targetRef, store); + return false; + } else { + boolean match = playerPrioritiser.test(ref, targetRef, store); + if (match) { + this.collector.collectMatching(ref, targetRef, store); + } else { + this.collector.collectNonMatching(targetRef, store); + } + + return this.collector.terminateOnFirstMatch() && match; + } + } + + @Nullable + protected Ref findPlayerOrEntity( + @Nonnull Ref ref, @Nonnull Vector3d position, @Nonnull Role role, @Nonnull Store store + ) { + Ref player = null; + Ref npc = null; + Ref ignoredEntity = this.ignoredTargetSlot >= 0 ? role.getMarkedEntitySupport().getMarkedEntityRef(this.ignoredTargetSlot) : null; + if (this.isGetPlayers()) { + this.playerPrioritiser.init(role); + role.getPositionCache() + .processPlayersInRange( + ref, + this.minRange, + this.range, + this.useProjectedDistance, + ignoredEntity, + role, + (sensorEntityBase, targetRef, role1, ref1) -> sensorEntityBase.filterPrioritisedPlayer(ref1, targetRef, role1, ref1.getStore()), + this, + ref, + store + ); + player = this.playerPrioritiser.getHighestPriorityTarget(); + this.playerPrioritiser.cleanup(); + } + + if (this.isGetNPCs()) { + this.npcPrioritiser.init(role); + role.getPositionCache() + .processNPCsInRange( + ref, + this.minRange, + this.range, + this.useProjectedDistance, + ignoredEntity, + role, + (sensorEntityBase, targetRef, role1, ref1) -> sensorEntityBase.filterPrioritisedNPC(ref1, targetRef, role1, ref1.getStore()), + this, + ref, + store + ); + npc = this.npcPrioritiser.getHighestPriorityTarget(); + this.npcPrioritiser.cleanup(); + } + + Ref target; + if (npc == null) { + target = player; + } else if (player == null) { + target = npc; + } else { + target = this.prioritiser.pickTarget(ref, role, position, player, npc, this.useProjectedDistance, store); + } + + return target; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorKill.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorKill.java new file mode 100644 index 0000000..6740fa8 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorKill.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorKill; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.sensorinfo.PositionProvider; +import com.hypixel.hytale.server.npc.util.DamageData; +import javax.annotation.Nonnull; + +public class SensorKill extends SensorBase { + protected final int targetSlot; + protected final PositionProvider positionProvider = new PositionProvider(); + + public SensorKill(@Nonnull BuilderSensorKill builder, @Nonnull BuilderSupport support) { + super(builder); + this.targetSlot = builder.getTargetSlot(support); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + DamageData damageData = npcComponent.getDamageData(); + if (super.matches(ref, role, dt, store) && damageData.haveKill()) { + Ref targetRef; + if (this.targetSlot >= 0) { + targetRef = role.getMarkedEntitySupport().getMarkedEntityRef(this.targetSlot); + if (targetRef == null || !damageData.haveKilled(targetRef)) { + this.positionProvider.clear(); + return false; + } + } else { + targetRef = damageData.getAnyKilled(); + } + + if (targetRef == null) { + this.positionProvider.clear(); + return false; + } else { + Vector3d killPosition = damageData.getKillPosition(targetRef); + if (killPosition == null) { + this.positionProvider.clear(); + return false; + } else { + this.positionProvider.setTarget(killPosition); + return true; + } + } + } else { + this.positionProvider.clear(); + return false; + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorPlayer.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorPlayer.java new file mode 100644 index 0000000..02b327b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorPlayer.java @@ -0,0 +1,22 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorPlayer; +import javax.annotation.Nonnull; + +public class SensorPlayer extends SensorEntityBase { + public SensorPlayer(@Nonnull BuilderSensorPlayer builder, @Nonnull BuilderSupport builderSupport) { + super(builder, builder.getPrioritiser(builderSupport), builderSupport); + this.initialisePrioritiser(); + } + + @Override + public boolean isGetPlayers() { + return true; + } + + @Override + public boolean isGetNPCs() { + return false; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorSelf.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorSelf.java new file mode 100644 index 0000000..43a162b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorSelf.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.ComponentContext; +import com.hypixel.hytale.server.npc.corecomponents.SensorWithEntityFilters; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorSelf; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.sensorinfo.PositionProvider; +import javax.annotation.Nonnull; + +public class SensorSelf extends SensorWithEntityFilters { + protected final PositionProvider positionProvider = new PositionProvider(); + + public SensorSelf(@Nonnull BuilderSensorSelf builder, @Nonnull BuilderSupport support) { + super(builder, builder.getFilters(support, null, ComponentContext.SensorSelf)); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (super.matches(ref, role, dt, store) && this.matchesFilters(ref, ref, role, store)) { + this.positionProvider.setTarget(store.getComponent(ref, TransformComponent.getComponentType()).getPosition()); + return true; + } else { + return false; + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorTarget.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorTarget.java new file mode 100644 index 0000000..594c99c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/SensorTarget.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.ComponentContext; +import com.hypixel.hytale.server.npc.corecomponents.SensorWithEntityFilters; +import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorTarget; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.EntityPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorTarget extends SensorWithEntityFilters { + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected final int targetSlot; + protected final boolean autoUnlockTarget; + protected final double range; + protected final EntityPositionProvider positionProvider = new EntityPositionProvider(); + + public SensorTarget(@Nonnull BuilderSensorTarget builderSensorTarget, @Nonnull BuilderSupport support) { + super(builderSensorTarget, builderSensorTarget.getFilters(support, null, ComponentContext.SensorTarget)); + this.targetSlot = builderSensorTarget.getTargetSlot(support); + this.range = builderSensorTarget.getRange(support); + this.autoUnlockTarget = builderSensorTarget.getAutoUnlockTarget(support); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + this.positionProvider.clear(); + return false; + } else { + Ref target = role.getMarkedEntitySupport().getMarkedEntityRef(this.targetSlot); + if (target == null) { + return false; + } else if (!this.fulfilsRequirements(ref, role, target, store)) { + if (this.autoUnlockTarget) { + this.positionProvider.clear(); + role.getMarkedEntitySupport().clearMarkedEntity(this.targetSlot); + } + + return false; + } else { + return this.positionProvider.setTarget(target, store) != null; + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } + + protected boolean fulfilsRequirements(@Nonnull Ref ref, @Nonnull Role role, @Nonnull Ref target, @Nonnull Store store) { + TransformComponent transformComponent = store.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + if (transformComponent == null) { + return false; + } else { + Vector3d position = transformComponent.getPosition(); + if (this.range != Double.MAX_VALUE) { + TransformComponent targetTransformComponent = store.getComponent(target, TRANSFORM_COMPONENT_TYPE); + if (targetTransformComponent == null) { + return false; + } + + double squaredDistance = position.distanceSquaredTo(targetTransformComponent.getPosition()); + if (squaredDistance > this.range * this.range) { + return false; + } + } + + return this.matchesFilters(ref, target, role, store); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionBeacon.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionBeacon.java new file mode 100644 index 0000000..8000a88 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionBeacon.java @@ -0,0 +1,142 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleOrValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.IntOrValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNullOrNotEmptyValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.TagSetExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.ActionBeacon; +import com.hypixel.hytale.server.npc.role.support.WorldSupport; +import java.util.Set; +import javax.annotation.Nonnull; + +public class BuilderActionBeacon extends BuilderActionBase { + protected final StringHolder message = new StringHolder(); + protected double range; + protected final AssetArrayHolder targetGroups = new AssetArrayHolder(); + protected final StringHolder targetToSendSlot = new StringHolder(); + protected double expirationTime; + protected int sendCount; + + public BuilderActionBeacon() { + } + + @Nonnull + public ActionBeacon build(@Nonnull BuilderSupport builderSupport) { + return new ActionBeacon(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Send Beacon Message"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Let the NPC send out a message to a target group of entities within a certain distance."; + } + + @Override + public void registerTags(@Nonnull Set tags) { + super.registerTags(tags); + tags.add("message"); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + public BuilderActionBeacon readConfig(@Nonnull JsonElement data) { + this.requireString(data, "Message", this.message, StringNotEmptyValidator.get(), BuilderDescriptorState.Experimental, "Message to send to targets", null); + this.getDouble( + data, + "Range", + d -> this.range = d, + 64.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Experimental, + "The maximum range to send the message", + null + ); + this.requireAssetArray( + data, + "TargetGroups", + this.targetGroups, + 0, + Integer.MAX_VALUE, + TagSetExistsValidator.withConfig(AssetValidator.ListCanBeEmpty), + BuilderDescriptorState.Experimental, + "The target group(s) to send the message to", + null + ); + this.getString( + data, + "SendTargetSlot", + this.targetToSendSlot, + null, + StringNullOrNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "The target slot of the marked entity to send. Omit to send own position", + null + ); + this.getDouble( + data, + "ExpirationTime", + d -> this.expirationTime = d, + 1.0, + DoubleOrValidator.greaterEqual0OrMinus1(), + BuilderDescriptorState.Experimental, + "The number of seconds that the message should last. -1 represents infinite time.", + "The number of seconds that the message should last and be acknowledged by the receiving NPC. -1 represents infinite time." + ); + this.getInt( + data, + "SendCount", + i -> this.sendCount = i, + -1, + IntOrValidator.greater0OrMinus1(), + BuilderDescriptorState.Experimental, + "Tne number of entities to send the message to. -1 will send to all.", + "Tne number of entities to send the message to. -1 will send to all. Entities will be chosen with a roughly even random distribution using reservoir sampling" + ); + return this; + } + + public String getMessage(@Nonnull BuilderSupport support) { + return this.message.get(support.getExecutionContext()); + } + + public double getRange() { + return this.range; + } + + public int[] getTargetGroups(@Nonnull BuilderSupport support) { + return WorldSupport.createTagSetIndexArray(this.targetGroups.get(support.getExecutionContext())); + } + + public double getExpirationTime() { + return this.expirationTime; + } + + public int getSendCount() { + return this.sendCount; + } + + public int getTargetToSendSlot(@Nonnull BuilderSupport support) { + String slotName = this.targetToSendSlot.get(support.getExecutionContext()); + return slotName == null ? Integer.MIN_VALUE : support.getTargetSlot(slotName); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionIgnoreForAvoidance.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionIgnoreForAvoidance.java new file mode 100644 index 0000000..85445cd --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionIgnoreForAvoidance.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.ActionIgnoreForAvoidance; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public class BuilderActionIgnoreForAvoidance extends BuilderActionBase { + protected final StringHolder targetSlot = new StringHolder(); + + public BuilderActionIgnoreForAvoidance() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Set the target slot of an entity that should be ignored during avoidance"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionIgnoreForAvoidance(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionIgnoreForAvoidance readConfig(@Nonnull JsonElement data) { + this.requireString( + data, + "TargetSlot", + this.targetSlot, + StringNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "The target slot containing the entity to be ignored", + null + ); + return this; + } + + public int getTargetSlot(@Nonnull BuilderSupport support) { + return support.getTargetSlot(this.targetSlot.get(support.getExecutionContext())); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionNotify.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionNotify.java new file mode 100644 index 0000000..e5dac32 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionNotify.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleOrValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNullOrNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.ActionNotify; +import com.hypixel.hytale.server.npc.instructions.Action; +import java.util.EnumSet; +import java.util.Set; +import javax.annotation.Nonnull; + +public class BuilderActionNotify extends BuilderActionBase { + protected final StringHolder message = new StringHolder(); + protected double expirationTime; + protected String usedTargetSlot; + + public BuilderActionNotify() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Directly notifies a target NPC with a beacon message"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Override + public void registerTags(@Nonnull Set tags) { + super.registerTags(tags); + tags.add("message"); + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionNotify(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionNotify readConfig(@Nonnull JsonElement data) { + this.requireString(data, "Message", this.message, StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The message to send", null); + this.getDouble( + data, + "ExpirationTime", + v -> this.expirationTime = v, + 1.0, + DoubleOrValidator.greaterEqual0OrMinus1(), + BuilderDescriptorState.Experimental, + "The number of seconds that the message should last. -1 represents infinite time.", + "The number of seconds that the message should last and be acknowledged by the receiving NPC. -1 represents infinite time." + ); + this.getString( + data, + "UseTargetSlot", + s -> this.usedTargetSlot = s, + null, + StringNullOrNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "A marked target to send to instead of the target provided by a sensor. Omit to use the target provided by the sensor.", + null + ); + this.requireFeatureIf("UseTargetSlot", false, this.usedTargetSlot != null, EnumSet.of(Feature.NPC)); + return this; + } + + public String getMessage(@Nonnull BuilderSupport support) { + return this.message.get(support.getExecutionContext()); + } + + public double getExpirationTime() { + return this.expirationTime; + } + + public int getUsedTargetSlot(@Nonnull BuilderSupport support) { + return this.usedTargetSlot == null ? Integer.MIN_VALUE : support.getTargetSlot(this.usedTargetSlot); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionOverrideAttitude.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionOverrideAttitude.java new file mode 100644 index 0000000..cfb1456 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionOverrideAttitude.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.ActionOverrideAttitude; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public class BuilderActionOverrideAttitude extends BuilderActionBase { + protected final EnumHolder attitude = new EnumHolder<>(); + protected final DoubleHolder duration = new DoubleHolder(); + + public BuilderActionOverrideAttitude() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Override this NPCs attitude towards the provided target for a given duration"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionOverrideAttitude(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionOverrideAttitude readConfig(@Nonnull JsonElement data) { + this.requireEnum(data, "Attitude", this.attitude, Attitude.class, BuilderDescriptorState.Stable, "The attitude to set", null); + this.getDouble( + data, "Duration", this.duration, 10.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "The duration to override for", null + ); + this.requireFeature(Feature.LiveEntity); + return this; + } + + public Attitude getAttitude(@Nonnull BuilderSupport support) { + return this.attitude.get(support.getExecutionContext()); + } + + public double getDuration(@Nonnull BuilderSupport support) { + return this.duration.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionReleaseTarget.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionReleaseTarget.java new file mode 100644 index 0000000..076837b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionReleaseTarget.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.ActionReleaseTarget; +import javax.annotation.Nonnull; + +public class BuilderActionReleaseTarget extends BuilderActionBase { + protected final StringHolder targetSlot = new StringHolder(); + + public BuilderActionReleaseTarget() { + } + + @Nonnull + public ActionReleaseTarget build(@Nonnull BuilderSupport builderSupport) { + return new ActionReleaseTarget(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Clear locked target"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Clear locked target for NPC."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionReleaseTarget readConfig(@Nonnull JsonElement data) { + this.getString( + data, "TargetSlot", this.targetSlot, "LockedTarget", StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The target slot to release", null + ); + return this; + } + + public int getTargetSlot(@Nonnull BuilderSupport support) { + return support.getTargetSlot(this.targetSlot.get(support.getExecutionContext())); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionSetMarkedTarget.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionSetMarkedTarget.java new file mode 100644 index 0000000..532d9ec --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionSetMarkedTarget.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.ActionSetMarkedTarget; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public class BuilderActionSetMarkedTarget extends BuilderActionBase { + protected final StringHolder targetSlot = new StringHolder(); + + public BuilderActionSetMarkedTarget() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Explicitly sets a marked target in a given slot."; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionSetMarkedTarget(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionSetMarkedTarget readConfig(@Nonnull JsonElement data) { + this.getString( + data, + "TargetSlot", + this.targetSlot, + "LockedTarget", + StringNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "The target slot to set a target to.", + null + ); + this.requireFeature(Feature.LiveEntity); + return this; + } + + public int getTargetSlot(@Nonnull BuilderSupport support) { + return support.getTargetSlot(this.targetSlot.get(support.getExecutionContext())); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionSetStat.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionSetStat.java new file mode 100644 index 0000000..3710138 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderActionSetStat.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.FloatHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.EntityStatExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.ActionSetStat; +import javax.annotation.Nonnull; + +public class BuilderActionSetStat extends BuilderActionBase { + protected final AssetHolder stat = new AssetHolder(); + protected final FloatHolder value = new FloatHolder(); + protected final BooleanHolder add = new BooleanHolder(); + + public BuilderActionSetStat() { + } + + @Nonnull + public ActionSetStat build(@Nonnull BuilderSupport builderSupport) { + return new ActionSetStat(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Sets (or adds to) an entity stat on the NPC."; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionSetStat readConfig(@Nonnull JsonElement data) { + this.requireAsset(data, "Stat", this.stat, EntityStatExistsValidator.required(), BuilderDescriptorState.Stable, "The entity stat to affect.", null); + this.requireFloat(data, "Value", this.value, null, BuilderDescriptorState.Stable, "The value to set the stat to.", null); + this.getBoolean(data, "Add", this.add, false, BuilderDescriptorState.Stable, "Add the value to the existing value instead of setting it.", null); + return this; + } + + public int getStat(@Nonnull BuilderSupport support) { + return EntityStatType.getAssetMap().getIndex(this.stat.get(support.getExecutionContext())); + } + + public float getValue(@Nonnull BuilderSupport support) { + return this.value.get(support.getExecutionContext()); + } + + public boolean isAdd(@Nonnull BuilderSupport support) { + return this.add.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderHeadMotionWatch.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderHeadMotionWatch.java new file mode 100644 index 0000000..df01aa8 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderHeadMotionWatch.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderHeadMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.HeadMotionWatch; +import javax.annotation.Nonnull; + +public class BuilderHeadMotionWatch extends BuilderHeadMotionBase { + protected final DoubleHolder relativeTurnSpeed = new DoubleHolder(); + + public BuilderHeadMotionWatch() { + } + + @Nonnull + public HeadMotionWatch build(@Nonnull BuilderSupport builderSupport) { + return new HeadMotionWatch(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Rotate to target"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Rotate to target."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderHeadMotionWatch readConfig(@Nonnull JsonElement data) { + this.getDouble( + data, + "RelativeTurnSpeed", + this.relativeTurnSpeed, + 1.0, + DoubleRangeValidator.fromExclToIncl(0.0, 2.0), + BuilderDescriptorState.Stable, + "The relative turn speed modifier", + null + ); + this.requireFeature(Feature.AnyPosition); + return this; + } + + public double getRelativeTurnSpeed(@Nonnull BuilderSupport support) { + return this.relativeTurnSpeed.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorBeacon.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorBeacon.java new file mode 100644 index 0000000..03561f6 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorBeacon.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNullOrNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.SensorBeacon; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorBeacon extends BuilderSensorBase { + protected final StringHolder message = new StringHolder(); + protected final DoubleHolder range = new DoubleHolder(); + protected String targetSlot; + protected boolean consume; + + public BuilderSensorBeacon() { + } + + @Nonnull + public SensorBeacon build(@Nonnull BuilderSupport builderSupport) { + return new SensorBeacon(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Checks to see if any messages have been broadcasted by nearby NPCs"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Checks to see if any messages have been broadcasted by nearby NPCs."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireString(data, "Message", this.message, StringNotEmptyValidator.get(), BuilderDescriptorState.Experimental, "The message to listen for", null); + this.getDouble( + data, + "Range", + this.range, + 64.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Experimental, + "The max distance beacons should be received from", + null + ); + this.getString( + data, + "TargetSlot", + s -> this.targetSlot = s, + null, + StringNullOrNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "A slot to store the sender as a target. If omitted no target will be stored", + null + ); + this.getBoolean( + data, "ConsumeMessage", b -> this.consume = b, true, BuilderDescriptorState.Stable, "Whether the message should be consumed by this sensor", null + ); + this.provideFeature(Feature.AnyEntity); + return this; + } + + public int getMessageSlot(@Nonnull BuilderSupport builderSupport) { + String name = this.message.get(builderSupport.getExecutionContext()); + return builderSupport.getBeaconMessageSlot(name); + } + + public double getRange(@Nonnull BuilderSupport builderSupport) { + return this.range.get(builderSupport.getExecutionContext()); + } + + public int getTargetSlot(@Nonnull BuilderSupport support) { + return this.targetSlot == null ? Integer.MIN_VALUE : support.getTargetSlot(this.targetSlot); + } + + public boolean isConsume() { + return this.consume; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorCount.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorCount.java new file mode 100644 index 0000000..22296b6 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorCount.java @@ -0,0 +1,111 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.IntSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.TagSetExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.SensorCount; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.role.support.WorldSupport; +import javax.annotation.Nonnull; + +public class BuilderSensorCount extends BuilderSensorBase { + protected final NumberArrayHolder count = new NumberArrayHolder(); + protected final NumberArrayHolder range = new NumberArrayHolder(); + protected String[] includeGroups; + protected String[] excludeGroups; + + public BuilderSensorCount() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Check if there is a certain number of NPCs or players within a specific range"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Check if there is a certain number of NPCs or players within a specific range"; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public SensorCount build(@Nonnull BuilderSupport builderSupport) { + return new SensorCount(this, builderSupport); + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireIntRange( + data, + "Count", + this.count, + IntSequenceValidator.betweenWeaklyMonotonic(0, Integer.MAX_VALUE), + BuilderDescriptorState.Stable, + "Specifies the allowed number of entities (inclusive)", + null + ); + this.requireDoubleRange( + data, + "Range", + this.range, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "Range to find entities in (inclusive)", + null + ); + this.getAssetArray( + data, + "IncludeGroups", + t -> this.includeGroups = t, + null, + null, + TagSetExistsValidator.withConfig(AssetValidator.ListCanBeEmpty), + BuilderDescriptorState.Stable, + "Match for NPCs in these groups", + null + ); + this.getAssetArray( + data, + "ExcludeGroups", + t -> this.excludeGroups = t, + null, + null, + TagSetExistsValidator.withConfig(AssetValidator.ListCanBeEmpty), + BuilderDescriptorState.Stable, + "Never match NPCs in these groups", + null + ); + return this; + } + + public int[] getCount(@Nonnull BuilderSupport builderSupport) { + return this.count.getIntArray(builderSupport.getExecutionContext()); + } + + public double[] getRange(@Nonnull BuilderSupport builderSupport) { + return this.range.get(builderSupport.getExecutionContext()); + } + + public int[] getIncludeGroups() { + return WorldSupport.createTagSetIndexArray(this.includeGroups); + } + + public int[] getExcludeGroups() { + return WorldSupport.createTagSetIndexArray(this.excludeGroups); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorEntity.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorEntity.java new file mode 100644 index 0000000..a643e70 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorEntity.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.corecomponents.entity.SensorEntity; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorEntity extends BuilderSensorEntityBase { + protected final BooleanHolder getPlayers = new BooleanHolder(); + protected final BooleanHolder getNPCs = new BooleanHolder(); + protected final BooleanHolder excludeOwnType = new BooleanHolder(); + + public BuilderSensorEntity() { + } + + @Nonnull + public SensorEntity build(@Nonnull BuilderSupport builderSupport) { + return new SensorEntity(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test if entity matching specific attributes and filters is in range"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Return true if entity matching specific attributes and filters is in range. Target is entity."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + super.readConfig(data); + this.getBoolean(data, "GetPlayers", this.getPlayers, false, BuilderDescriptorState.Stable, "Test players", null); + this.getBoolean(data, "GetNPCs", this.getNPCs, true, BuilderDescriptorState.Stable, "Test mobs/NPCs", null); + this.getBoolean(data, "ExcludeOwnType", this.excludeOwnType, true, BuilderDescriptorState.Stable, "Exclude NPCs of same type as current NPC", null); + this.validateAny(this.getPlayers, this.getNPCs); + return this; + } + + public boolean isGetPlayers(@Nonnull BuilderSupport support) { + return this.getPlayers.get(support.getExecutionContext()); + } + + public boolean isGetNPCs(@Nonnull BuilderSupport support) { + return this.getNPCs.get(support.getExecutionContext()); + } + + public boolean isExcludeOwnType(@Nonnull BuilderSupport support) { + return this.excludeOwnType.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorEntityBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorEntityBase.java new file mode 100644 index 0000000..5d2860e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorEntityBase.java @@ -0,0 +1,175 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectReferenceHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.BuilderValidationHelper; +import com.hypixel.hytale.server.npc.asset.builder.ComponentContext; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.RelationalOperator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNullOrNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.ISensorEntityCollector; +import com.hypixel.hytale.server.npc.corecomponents.ISensorEntityPrioritiser; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorWithEntityFilters; +import com.hypixel.hytale.server.npc.corecomponents.entity.prioritisers.SensorEntityPrioritiserDefault; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class BuilderSensorEntityBase extends BuilderSensorWithEntityFilters { + protected final DoubleHolder range = new DoubleHolder(); + protected final DoubleHolder minRange = new DoubleHolder(); + protected final BooleanHolder lockOnTarget = new BooleanHolder(); + protected final BooleanHolder autoUnlockTarget = new BooleanHolder(); + protected final BooleanHolder onlyLockedTarget = new BooleanHolder(); + protected final StringHolder lockedTargetSlot = new StringHolder(); + protected final StringHolder ignoredTargetSlot = new StringHolder(); + protected final BooleanHolder useProjectedDistance = new BooleanHolder(); + protected final BuilderObjectReferenceHelper prioritiser = new BuilderObjectReferenceHelper<>(ISensorEntityPrioritiser.class, this); + protected final BuilderObjectReferenceHelper collector = new BuilderObjectReferenceHelper<>(ISensorEntityCollector.class, this); + + public BuilderSensorEntityBase() { + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getDouble( + data, "MinRange", this.minRange, 0.0, DoubleSingleValidator.greaterEqual0(), BuilderDescriptorState.Stable, "Minimum range to test entities in", null + ); + this.requireDouble(data, "Range", this.range, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "Maximum range to test entities in", null); + this.getBoolean(data, "LockOnTarget", this.lockOnTarget, false, BuilderDescriptorState.Stable, "Matched target becomes locked target", null); + this.getString( + data, + "LockedTargetSlot", + this.lockedTargetSlot, + "LockedTarget", + StringNullOrNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "The target slot to use for locking on or unlocking", + null + ); + this.getBoolean( + data, + "AutoUnlockTarget", + this.autoUnlockTarget, + false, + BuilderDescriptorState.Stable, + "Unlock locked target when sensor not matching it anymore", + null + ); + this.getBoolean(data, "OnlyLockedTarget", this.onlyLockedTarget, false, BuilderDescriptorState.Stable, "Test only locked target", null); + this.getString( + data, + "IgnoredTargetSlot", + this.ignoredTargetSlot, + null, + StringNullOrNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "The target slot to use for ignoring", + null + ); + this.getBoolean( + data, + "UseProjectedDistance", + this.useProjectedDistance, + false, + BuilderDescriptorState.Stable, + "Use the projected movement direction vector for distance, rather than the Euclidean distance", + null + ); + this.getObject( + data, + "Prioritiser", + this.prioritiser, + BuilderDescriptorState.Stable, + "A prioritiser for selecting results based on additional parameters", + null, + this.validationHelper + ); + this.getObject( + data, + "Collector", + this.collector, + BuilderDescriptorState.Stable, + "A collector which can process all checked entities and act on them based on whether they match or not", + null, + this.validationHelper + ); + BuilderValidationHelper builderHelper = this.createFilterValidationHelper(ComponentContext.SensorEntity); + this.getArray(data, "Filters", this.filters, null, BuilderDescriptorState.Stable, "A series of entity filter sensors to test", null, builderHelper); + this.validateDoubleRelation(this.range, RelationalOperator.GreaterEqual, this.minRange); + this.provideFeature(Feature.LiveEntity); + return this; + } + + @Override + public boolean validate( + String configName, + @Nonnull NPCLoadTimeValidationHelper validationHelper, + @Nonnull ExecutionContext context, + Scope globalScope, + @Nonnull List errors + ) { + boolean result = this.prioritiser.validate(configName, validationHelper, this.builderManager, context, globalScope, errors) + & super.validate(configName, validationHelper, context, globalScope, errors) + & this.collector.validate(configName, validationHelper, this.builderManager, context, globalScope, errors); + validationHelper.clearPrioritiserProvidedFilterTypes(); + return result; + } + + public double getRange(@Nonnull BuilderSupport builderSupport) { + return this.range.get(builderSupport.getExecutionContext()); + } + + public double getMinRange(@Nonnull BuilderSupport builderSupport) { + return this.minRange.get(builderSupport.getExecutionContext()); + } + + public boolean isLockOnTarget(@Nonnull BuilderSupport builderSupport) { + return this.lockOnTarget.get(builderSupport.getExecutionContext()); + } + + public boolean isOnlyLockedTarget(@Nonnull BuilderSupport builderSupport) { + return this.onlyLockedTarget.get(builderSupport.getExecutionContext()); + } + + public int getLockedTargetSlot(@Nonnull BuilderSupport support) { + return !this.lockOnTarget.get(support.getExecutionContext()) && !this.onlyLockedTarget.get(support.getExecutionContext()) + ? Integer.MIN_VALUE + : support.getTargetSlot(this.lockedTargetSlot.get(support.getExecutionContext())); + } + + public int getIgnoredTargetSlot(@Nonnull BuilderSupport support) { + String slot = this.ignoredTargetSlot.get(support.getExecutionContext()); + return slot == null ? Integer.MIN_VALUE : support.getTargetSlot(slot); + } + + public boolean isAutoUnlockTarget(@Nonnull BuilderSupport builderSupport) { + return this.autoUnlockTarget.get(builderSupport.getExecutionContext()); + } + + public boolean isUseProjectedDistance(@Nonnull BuilderSupport support) { + return this.useProjectedDistance.get(support.getExecutionContext()); + } + + @Nullable + public ISensorEntityPrioritiser getPrioritiser(@Nonnull BuilderSupport support) { + return (ISensorEntityPrioritiser)(!this.prioritiser.isPresent() ? new SensorEntityPrioritiserDefault() : this.prioritiser.build(support)); + } + + @Nullable + public ISensorEntityCollector getCollector(@Nonnull BuilderSupport support) { + return this.collector.isPresent() ? this.collector.build(support) : ISensorEntityCollector.DEFAULT; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorKill.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorKill.java new file mode 100644 index 0000000..a4c0878 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorKill.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNullOrNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.SensorKill; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorKill extends BuilderSensorBase { + protected final StringHolder targetSlot = new StringHolder(); + + public BuilderSensorKill() { + } + + @Nonnull + public SensorKill build(@Nonnull BuilderSupport builderSupport) { + return new SensorKill(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test if NPC made a kill"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Return true if NPC made a kill. Target position is killed entity position."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getString( + data, + "TargetSlot", + this.targetSlot, + null, + StringNullOrNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "The target slot to check if killed. If omitted, will accept any entity killed", + null + ); + this.provideFeature(Feature.Position); + return this; + } + + public int getTargetSlot(@Nonnull BuilderSupport support) { + String slot = this.targetSlot.get(support.getExecutionContext()); + return slot == null ? Integer.MIN_VALUE : support.getTargetSlot(slot); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorPlayer.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorPlayer.java new file mode 100644 index 0000000..9b5e451 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorPlayer.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.entity.SensorPlayer; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorPlayer extends BuilderSensorEntityBase { + public BuilderSensorPlayer() { + } + + @Nonnull + public SensorPlayer build(@Nonnull BuilderSupport builderSupport) { + return new SensorPlayer(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test if player matching specific attributes and filters is in range"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Return true if player matching specific attributes and filters is in range. Target is player."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + super.readConfig(data); + return this; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorSelf.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorSelf.java new file mode 100644 index 0000000..bf828c7 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorSelf.java @@ -0,0 +1,50 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.BuilderValidationHelper; +import com.hypixel.hytale.server.npc.asset.builder.ComponentContext; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorWithEntityFilters; +import com.hypixel.hytale.server.npc.corecomponents.entity.SensorSelf; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorSelf extends BuilderSensorWithEntityFilters { + public BuilderSensorSelf() { + } + + @Nonnull + public SensorSelf build(@Nonnull BuilderSupport builderSupport) { + return new SensorSelf(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test if the NPC itself matches a set of entity filters"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + BuilderValidationHelper builderHelper = this.createFilterValidationHelper(ComponentContext.SensorSelf); + this.requireArray(data, "Filters", this.filters, null, BuilderDescriptorState.Stable, "A series of entity filter sensors to test", null, builderHelper); + this.provideFeature(Feature.Position); + return this; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorTarget.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorTarget.java new file mode 100644 index 0000000..54c4fd9 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/builders/BuilderSensorTarget.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.BuilderValidationHelper; +import com.hypixel.hytale.server.npc.asset.builder.ComponentContext; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorWithEntityFilters; +import com.hypixel.hytale.server.npc.corecomponents.entity.SensorTarget; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorTarget extends BuilderSensorWithEntityFilters { + protected final DoubleHolder range = new DoubleHolder(); + protected final BooleanHolder autoUnlockTarget = new BooleanHolder(); + protected final StringHolder targetSlot = new StringHolder(); + + public BuilderSensorTarget() { + } + + @Nonnull + public SensorTarget build(@Nonnull BuilderSupport builderSupport) { + return new SensorTarget(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test if given target matches a series of criteria and optional entity filters"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getString( + data, "TargetSlot", this.targetSlot, "LockedTarget", StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The target slot to check", null + ); + this.getDouble( + data, "Range", this.range, Double.MAX_VALUE, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "Maximum range of locked target", null + ); + this.getBoolean(data, "AutoUnlockTarget", this.autoUnlockTarget, false, BuilderDescriptorState.Stable, "Unlock locked target if match fails", null); + BuilderValidationHelper builderHelper = this.createFilterValidationHelper(ComponentContext.SensorTarget); + this.getArray(data, "Filters", this.filters, null, BuilderDescriptorState.Stable, "A series of entity filter sensors to test", null, builderHelper); + this.provideFeature(Feature.LiveEntity); + return this; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + public double getRange(@Nonnull BuilderSupport support) { + return this.range.get(support.getExecutionContext()); + } + + public boolean getAutoUnlockTarget(@Nonnull BuilderSupport support) { + return this.autoUnlockTarget.get(support.getExecutionContext()); + } + + public int getTargetSlot(@Nonnull BuilderSupport builderSupport) { + return builderSupport.getTargetSlot(this.targetSlot.get(builderSupport.getExecutionContext())); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterAltitude.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterAltitude.java new file mode 100644 index 0000000..aa833a3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterAltitude.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.EntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterAltitude; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityFilterAltitude extends EntityFilterBase { + public static final int COST = 0; + @Nullable + protected static final ComponentType NPC_COMPONENT_TYPE = NPCEntity.getComponentType(); + protected final double[] altitudeRange; + + public EntityFilterAltitude(@Nonnull BuilderEntityFilterAltitude builder, @Nonnull BuilderSupport support) { + this.altitudeRange = builder.getAltitudeRange(support); + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + NPCEntity targetNpcComponent = store.getComponent(targetRef, NPC_COMPONENT_TYPE); + double heightOverGround; + if (targetNpcComponent != null) { + MotionController targetActiveMotionController = targetNpcComponent.getRole().getActiveMotionController(); + heightOverGround = targetActiveMotionController.getHeightOverGround(); + } else { + heightOverGround = 0.0; + } + + return heightOverGround >= this.altitudeRange[0] && heightOverGround <= this.altitudeRange[1]; + } + + @Override + public int cost() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterAnd.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterAnd.java new file mode 100644 index 0000000..5a782b5 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterAnd.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.role.Role; +import java.util.List; +import javax.annotation.Nonnull; + +public class EntityFilterAnd extends EntityFilterMany { + public EntityFilterAnd(@Nonnull List filters) { + super(filters); + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + for (IEntityFilter filter : this.filters) { + if (!filter.matchesEntity(ref, targetRef, role, store)) { + return false; + } + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterAttitude.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterAttitude.java new file mode 100644 index 0000000..8db772f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterAttitude.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.EntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterAttitude; +import com.hypixel.hytale.server.npc.role.Role; +import java.util.Arrays; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class EntityFilterAttitude extends EntityFilterBase { + public static final String TYPE = "Attitude"; + public static final int COST = 0; + protected final EnumSet attitudes; + + public EntityFilterAttitude(@Nonnull BuilderEntityFilterAttitude builder, @Nonnull BuilderSupport support) { + this.attitudes = builder.getAttitudes(support); + } + + public EntityFilterAttitude(Attitude[] attitudes) { + this.attitudes = EnumSet.noneOf(Attitude.class); + this.attitudes.addAll(Arrays.asList(attitudes)); + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + Attitude attitude = role.getWorldSupport().getAttitude(ref, targetRef, store); + return attitude != null && this.attitudes.contains(attitude); + } + + @Override + public int cost() { + return 0; + } + + @Override + public void registerWithSupport(@Nonnull Role role) { + role.getWorldSupport().requireAttitudeCache(); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterCombat.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterCombat.java new file mode 100644 index 0000000..12a80b7 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterCombat.java @@ -0,0 +1,99 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.blackboard.view.combat.CombatViewSystems; +import com.hypixel.hytale.server.npc.blackboard.view.combat.InterpretedCombatData; +import com.hypixel.hytale.server.npc.corecomponents.EntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterCombat; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.EntityPositionProvider; +import java.util.List; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class EntityFilterCombat extends EntityFilterBase { + public static final int COST = 100; + protected final String sequence; + protected final double minTimeElapsed; + protected final double maxTimeElapsed; + protected final EntityFilterCombat.Mode combatMode; + protected final EntityPositionProvider positionProvider = new EntityPositionProvider(); + + public EntityFilterCombat(@Nonnull BuilderEntityFilterCombat builder, @Nonnull BuilderSupport builderSupport) { + this.sequence = builder.getSequence(builderSupport); + double[] timeElapsedRange = builder.getTimeElapsedRange(builderSupport); + this.minTimeElapsed = timeElapsedRange[0]; + this.maxTimeElapsed = timeElapsedRange[1]; + this.combatMode = builder.getCombatMode(builderSupport); + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + List combatData = CombatViewSystems.getCombatData(targetRef); + + for (int i = 0; i < combatData.size(); i++) { + InterpretedCombatData data = combatData.get(i); + + boolean var10000 = switch (this.combatMode) { + case Sequence -> { + if (!data.getAttack().equals(this.sequence)) { + yield false; + } else { + float time = data.getCurrentElapsedTime(); + yield time >= this.minTimeElapsed && time <= this.maxTimeElapsed; + } + } + case Charging -> { + if (!data.isCharging()) { + yield false; + } else { + float currentTime = data.getCurrentElapsedTime(); + yield currentTime >= this.minTimeElapsed && currentTime <= this.maxTimeElapsed; + } + } + case Attacking -> data.isPerformingMeleeAttack() || data.isPerformingRangedAttack(); + case Melee -> data.isPerformingMeleeAttack(); + case Ranged -> data.isPerformingRangedAttack(); + case Blocking -> data.isPerformingBlock(); + case Any -> true; + case None -> false; + }; + + boolean matches = var10000; + if (matches) { + return true; + } + } + + return this.combatMode == EntityFilterCombat.Mode.None; + } + + @Override + public int cost() { + return 100; + } + + public static enum Mode implements Supplier { + Sequence("Combat sequence"), + Charging("Weapon charging"), + Attacking("Attacking"), + Melee("Melee"), + Ranged("Ranged"), + Blocking("Blocking"), + Any("Any"), + None("None"); + + private final String description; + + private Mode(String description) { + this.description = description; + } + + public String get() { + return this.description; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterHeightDifference.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterHeightDifference.java new file mode 100644 index 0000000..600afd8 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterHeightDifference.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.EntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterHeightDifference; +import com.hypixel.hytale.server.npc.role.Role; +import javax.annotation.Nonnull; + +public class EntityFilterHeightDifference extends EntityFilterBase { + public static final int COST = 200; + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected static final ComponentType MODEL_COMPONENT_TYPE = ModelComponent.getComponentType(); + protected static final ComponentType BOUNDING_BOX_COMPONENT_TYPE = BoundingBox.getComponentType(); + protected final double minHeightDifference; + protected final double maxHeightDifference; + protected final boolean useEyePosition; + + public EntityFilterHeightDifference(@Nonnull BuilderEntityFilterHeightDifference builder, @Nonnull BuilderSupport support) { + double[] heightDifference = builder.getHeightDifference(support); + this.minHeightDifference = heightDifference[0]; + this.maxHeightDifference = heightDifference[1]; + this.useEyePosition = builder.isUseEyePosition(support); + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + BoundingBox targetBoundingBoxComponent = store.getComponent(targetRef, BOUNDING_BOX_COMPONENT_TYPE); + if (targetBoundingBoxComponent == null) { + return false; + } else { + TransformComponent targetTransformComponent = store.getComponent(targetRef, TRANSFORM_COMPONENT_TYPE); + + assert targetTransformComponent != null; + + ModelComponent targetModelComponent = store.getComponent(targetRef, MODEL_COMPONENT_TYPE); + float targetEyeHeight = targetModelComponent != null ? targetModelComponent.getModel().getEyeHeight() : 0.0F; + Vector3d targetPosition = targetTransformComponent.getPosition(); + double targetY = targetPosition.y; + Box box = targetBoundingBoxComponent.getBoundingBox(); + double minY = targetY + box.min.y; + double maxY = targetY + box.max.y; + TransformComponent transformComponent = store.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + double posY = transformComponent.getPosition().y; + if (this.useEyePosition) { + posY += targetEyeHeight; + } + + return minY - posY < this.maxHeightDifference && maxY - posY > this.minHeightDifference; + } + } + + @Override + public int cost() { + return 200; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterInsideBlock.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterInsideBlock.java new file mode 100644 index 0000000..43d3186 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterInsideBlock.java @@ -0,0 +1,89 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.blockset.BlockSetModule; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.EntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterInsideBlock; +import com.hypixel.hytale.server.npc.role.Role; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityFilterInsideBlock extends EntityFilterBase { + public static final int COST = 400; + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected static final ComponentType BOUNDING_BOX_COMPONENT_TYPE = BoundingBox.getComponentType(); + protected static final ComponentType BLOCK_CHUNK_COMPONENT_TYPE = BlockChunk.getComponentType(); + protected final int blockSet; + @Nullable + protected ChunkStore chunkStore; + protected long chunkIndex = ChunkUtil.NOT_FOUND; + @Nullable + protected BlockChunk blockChunk; + protected int chunkSectionIndex = Integer.MIN_VALUE; + @Nullable + protected BlockSection chunkSection; + protected boolean matches; + + public EntityFilterInsideBlock(@Nonnull BuilderEntityFilterInsideBlock builder, @Nonnull BuilderSupport support) { + this.blockSet = builder.getBlockSet(support); + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + this.chunkStore = store.getExternalData().getWorld().getChunkStore(); + this.matches = false; + Vector3d position = store.getComponent(targetRef, TRANSFORM_COMPONENT_TYPE).getPosition(); + Box boundingBox = store.getComponent(targetRef, BOUNDING_BOX_COMPONENT_TYPE).getBoundingBox(); + boundingBox.forEachBlock(position.x, position.y, position.z, this, EntityFilterInsideBlock::accept); + this.chunkStore = null; + this.chunkIndex = ChunkUtil.NOT_FOUND; + this.blockChunk = null; + this.chunkSectionIndex = Integer.MIN_VALUE; + this.chunkSection = null; + return this.matches; + } + + @Override + public int cost() { + return 400; + } + + private static boolean accept(int x, int y, int z, @Nonnull EntityFilterInsideBlock filter) { + long index = ChunkUtil.indexChunkFromBlock(x, z); + if (index != filter.chunkIndex) { + filter.chunkIndex = index; + filter.blockChunk = filter.chunkStore.getChunkComponent(index, BLOCK_CHUNK_COMPONENT_TYPE); + } + + if (filter.blockChunk == null) { + return false; + } else { + int section = ChunkUtil.indexSection(y); + if (section != filter.chunkSectionIndex) { + filter.chunkSectionIndex = section; + filter.chunkSection = section >= 0 && section < 10 ? filter.blockChunk.getSectionAtIndex(section) : null; + } + + if (filter.chunkSection == null) { + filter.matches = BlockSetModule.getInstance().blockInSet(filter.blockSet, 0); + return !filter.matches; + } else { + int blockId = filter.chunkSection.get(x, y, z); + filter.matches = BlockSetModule.getInstance().blockInSet(filter.blockSet, blockId); + return !filter.matches; + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterInventory.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterInventory.java new file mode 100644 index 0000000..cc348ef --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterInventory.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.EntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterInventory; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.util.InventoryHelper; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityFilterInventory extends EntityFilterBase { + public static final int COST = 300; + @Nullable + protected final List items; + protected final int minCount; + protected final int maxCount; + protected final int minFreeSlots; + protected final int maxFreeSlots; + protected final boolean checkFreeSlots; + + public EntityFilterInventory(@Nonnull BuilderEntityFilterInventory builder, @Nonnull BuilderSupport support) { + String[] itemArray = builder.getItems(support); + this.items = itemArray != null ? List.of(itemArray) : null; + int[] countRange = builder.getCount(support); + this.minCount = countRange[0]; + this.maxCount = countRange[1]; + int[] freeSlotsRange = builder.getFreeSlotsRange(support); + this.minFreeSlots = freeSlotsRange[0]; + this.maxFreeSlots = freeSlotsRange[1]; + this.checkFreeSlots = this.minFreeSlots != BuilderEntityFilterInventory.DEFAULT_FREE_SLOT_RANGE[0] + || this.maxFreeSlots != BuilderEntityFilterInventory.DEFAULT_FREE_SLOT_RANGE[1]; + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + LivingEntity entity = (LivingEntity)EntityUtils.getEntity(targetRef, store); + Inventory inventory = entity.getInventory(); + CombinedItemContainer container = inventory.getCombinedHotbarUtilityConsumableStorage(); + int count = InventoryHelper.countItems(container, this.items); + if (count < this.minCount || count > this.maxCount) { + return false; + } else if (!this.checkFreeSlots) { + return true; + } else { + int freeSlots = InventoryHelper.countFreeSlots(container); + return freeSlots >= this.minFreeSlots && freeSlots <= this.maxFreeSlots; + } + } + + @Override + public int cost() { + return 300; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterItemInHand.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterItemInHand.java new file mode 100644 index 0000000..e7a3960 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterItemInHand.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.EntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterItemInHand; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.util.InventoryHelper; +import java.util.List; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityFilterItemInHand extends EntityFilterBase { + public static final int COST = 300; + @Nullable + protected final List items; + protected final EntityFilterItemInHand.WieldingHand hand; + + public EntityFilterItemInHand(@Nonnull BuilderEntityFilterItemInHand builder, @Nonnull BuilderSupport builderSupport) { + String[] itemArray = builder.getItems(builderSupport); + this.items = itemArray != null ? List.of(itemArray) : null; + this.hand = builder.getHand(builderSupport); + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + LivingEntity entity = (LivingEntity)EntityUtils.getEntity(targetRef, store); + Inventory inventory = entity.getInventory(); + + return switch (this.hand) { + case Main -> InventoryHelper.matchesItem(this.items, inventory.getItemInHand()); + case OffHand -> InventoryHelper.matchesItem(this.items, inventory.getUtilityItem()); + default -> InventoryHelper.matchesItem(this.items, inventory.getItemInHand()) || InventoryHelper.matchesItem(this.items, inventory.getUtilityItem()); + }; + } + + @Override + public int cost() { + return 300; + } + + public static enum WieldingHand implements Supplier { + Main("The main hand"), + OffHand("The off-hand"), + Both("Both hands"); + + private final String description; + + private WieldingHand(String description) { + this.description = description; + } + + public String get() { + return this.description; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterLineOfSight.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterLineOfSight.java new file mode 100644 index 0000000..0ece21c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterLineOfSight.java @@ -0,0 +1,25 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.EntityFilterBase; +import com.hypixel.hytale.server.npc.role.Role; +import javax.annotation.Nonnull; + +public class EntityFilterLineOfSight extends EntityFilterBase { + public static final int COST = 400; + + public EntityFilterLineOfSight() { + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + return role.getPositionCache().hasLineOfSight(ref, targetRef, store); + } + + @Override + public int cost() { + return 400; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterMany.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterMany.java new file mode 100644 index 0000000..9712a11 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterMany.java @@ -0,0 +1,123 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.EntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponent; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponentCollection; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class EntityFilterMany extends EntityFilterBase implements IAnnotatedComponentCollection { + @Nonnull + protected final IEntityFilter[] filters; + protected final int cost; + + public EntityFilterMany(@Nonnull List filters) { + if (filters == null) { + throw new IllegalArgumentException("Filter list can't be null"); + } else { + this.filters = filters.toArray(IEntityFilter[]::new); + + for (IEntityFilter filter : this.filters) { + if (filter == null) { + throw new IllegalArgumentException("Filter cannot be null in filter list"); + } + } + + IEntityFilter.prioritiseFilters(this.filters); + int cost = 0; + + for (int i = 0; i < this.filters.length; i++) { + cost = (int)(cost + this.filters[i].cost() * (1.0 / Math.pow(2.0, i))); + } + + this.cost = cost; + } + } + + @Override + public int cost() { + return this.cost; + } + + @Override + public void registerWithSupport(Role role) { + for (IEntityFilter filter : this.filters) { + filter.registerWithSupport(role); + } + } + + @Override + public void motionControllerChanged( + @Nullable Ref ref, + @Nonnull NPCEntity npcComponent, + MotionController motionController, + @Nullable ComponentAccessor componentAccessor + ) { + for (IEntityFilter filter : this.filters) { + filter.motionControllerChanged(ref, npcComponent, motionController, componentAccessor); + } + } + + @Override + public void loaded(Role role) { + for (IEntityFilter filter : this.filters) { + filter.loaded(role); + } + } + + @Override + public void spawned(Role role) { + for (IEntityFilter filter : this.filters) { + filter.spawned(role); + } + } + + @Override + public void unloaded(Role role) { + for (IEntityFilter filter : this.filters) { + filter.unloaded(role); + } + } + + @Override + public void removed(Role role) { + for (IEntityFilter filter : this.filters) { + filter.removed(role); + } + } + + @Override + public void teleported(Role role, World from, World to) { + for (IEntityFilter filter : this.filters) { + filter.teleported(role, from, to); + } + } + + @Override + public int componentCount() { + return this.filters.length; + } + + @Override + public IAnnotatedComponent getComponent(int index) { + return this.filters[index]; + } + + @Override + public void setContext(IAnnotatedComponent parent, int index) { + super.setContext(parent, index); + + for (int i = 0; i < this.filters.length; i++) { + this.filters[i].setContext(this, i); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterMovementState.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterMovementState.java new file mode 100644 index 0000000..b8605fd --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterMovementState.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.EntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterMovementState; +import com.hypixel.hytale.server.npc.movement.MovementState; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import javax.annotation.Nonnull; + +public class EntityFilterMovementState extends EntityFilterBase { + public static final int COST = 0; + protected final MovementState movementState; + + public EntityFilterMovementState(@Nonnull BuilderEntityFilterMovementState builder) { + this.movementState = builder.getMovementState(); + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + return MotionController.isInMovementState(targetRef, this.movementState, store); + } + + @Override + public int cost() { + return 0; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterNPCGroup.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterNPCGroup.java new file mode 100644 index 0000000..012628a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterNPCGroup.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.EntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterNPCGroup; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.WorldSupport; +import javax.annotation.Nonnull; + +public class EntityFilterNPCGroup extends EntityFilterBase { + public static final int COST = 200; + protected final int[] includeGroups; + protected final int[] excludeGroups; + + public EntityFilterNPCGroup(@Nonnull BuilderEntityFilterNPCGroup builder, @Nonnull BuilderSupport support) { + this.includeGroups = builder.getIncludeGroups(support); + this.excludeGroups = builder.getExcludeGroups(support); + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + if (this.includeGroups != null && this.includeGroups.length > 0 && !WorldSupport.isGroupMember(role.getRoleIndex(), targetRef, this.includeGroups, store) + ) + { + return false; + } else { + return this.excludeGroups != null && this.excludeGroups.length > 0 + ? !WorldSupport.isGroupMember(role.getRoleIndex(), targetRef, this.excludeGroups, store) + : true; + } + } + + @Override + public int cost() { + return 200; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterNot.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterNot.java new file mode 100644 index 0000000..b26dbba --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterNot.java @@ -0,0 +1,94 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.EntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponent; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponentCollection; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityFilterNot extends EntityFilterBase implements IAnnotatedComponentCollection { + protected final IEntityFilter filter; + + public EntityFilterNot(IEntityFilter filter) { + this.filter = filter; + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + return !this.filter.matchesEntity(ref, targetRef, role, store); + } + + @Override + public int cost() { + return this.filter.cost(); + } + + @Override + public void registerWithSupport(Role role) { + this.filter.registerWithSupport(role); + } + + @Override + public void motionControllerChanged( + @Nullable Ref ref, + @Nonnull NPCEntity npcComponent, + MotionController motionController, + @Nullable ComponentAccessor componentAccessor + ) { + this.filter.motionControllerChanged(ref, npcComponent, motionController, componentAccessor); + } + + @Override + public void loaded(Role role) { + this.filter.loaded(role); + } + + @Override + public void spawned(Role role) { + this.filter.spawned(role); + } + + @Override + public void unloaded(Role role) { + this.filter.unloaded(role); + } + + @Override + public void removed(Role role) { + this.filter.removed(role); + } + + @Override + public void teleported(Role role, World from, World to) { + this.filter.teleported(role, from, to); + } + + @Override + public int componentCount() { + return 1; + } + + @Override + public IAnnotatedComponent getComponent(int index) { + if (index >= this.componentCount()) { + throw new IndexOutOfBoundsException(); + } else { + return this.filter; + } + } + + @Override + public void setContext(IAnnotatedComponent parent, int index) { + super.setContext(parent, index); + this.filter.setContext(this, index); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterOr.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterOr.java new file mode 100644 index 0000000..dc2c037 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterOr.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.role.Role; +import java.util.List; +import javax.annotation.Nonnull; + +public class EntityFilterOr extends EntityFilterMany { + public EntityFilterOr(@Nonnull List filters) { + super(filters); + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + for (IEntityFilter filter : this.filters) { + if (filter.matchesEntity(ref, targetRef, role, store)) { + return true; + } + } + + return false; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterSpotsMe.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterSpotsMe.java new file mode 100644 index 0000000..717b751 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterSpotsMe.java @@ -0,0 +1,89 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.TrigMathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.EntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterSpotsMe; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.util.NPCPhysicsMath; +import com.hypixel.hytale.server.npc.util.ViewTest; +import javax.annotation.Nonnull; + +public class EntityFilterSpotsMe extends EntityFilterBase { + public static final int COST = 400; + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected final float viewAngle; + protected final boolean testLineOfSight; + protected final ViewTest viewTest; + protected final Vector3d view = new Vector3d(); + + public EntityFilterSpotsMe(@Nonnull BuilderEntityFilterSpotsMe builder) { + this.viewAngle = builder.getViewAngle(); + this.testLineOfSight = builder.testLineOfSight(); + this.viewTest = builder.getViewTest(); + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + return this.inViewTest(targetRef, ref, store) && (!this.testLineOfSight || role.getPositionCache().hasInverseLineOfSight(ref, targetRef, store)); + } + + @Override + public int cost() { + return 400; + } + + protected boolean inViewTest(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Store store) { + return switch (this.viewTest) { + case VIEW_CONE -> this.inViewCone(ref, targetRef, store); + case VIEW_SECTOR -> this.inViewSector(ref, targetRef, store); + default -> false; + }; + } + + protected boolean inViewSector(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Store store) { + TransformComponent transformComponent = store.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + TransformComponent targetTransformComponent = store.getComponent(targetRef, TRANSFORM_COMPONENT_TYPE); + + assert targetTransformComponent != null; + + Vector3d targetPosition = targetTransformComponent.getPosition(); + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + return NPCPhysicsMath.inViewSector( + position.getX(), position.getZ(), headRotationComponent.getRotation().getYaw(), this.viewAngle, targetPosition.getX(), targetPosition.getZ() + ); + } + + protected boolean inViewCone(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Store store) { + TransformComponent transformComponent = store.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + TransformComponent targetTransformComponent = store.getComponent(targetRef, TRANSFORM_COMPONENT_TYPE); + + assert targetTransformComponent != null; + + NPCPhysicsMath.getViewDirection(headRotationComponent.getRotation(), this.view); + this.view.normalize(); + return NPCPhysicsMath.isInViewCone( + transformComponent.getPosition(), this.view, TrigMathUtil.cos(this.viewAngle / 2.0F), targetTransformComponent.getPosition() + ); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterStandingOnBlock.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterStandingOnBlock.java new file mode 100644 index 0000000..d655be3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterStandingOnBlock.java @@ -0,0 +1,71 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.blockset.BlockSetModule; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.EntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterStandingOnBlock; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class EntityFilterStandingOnBlock extends EntityFilterBase { + public static final int COST = 300; + @Nullable + protected static final ComponentType NPC_COMPONENT_TYPE = NPCEntity.getComponentType(); + @Nonnull + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected final int blockSet; + + public EntityFilterStandingOnBlock(@Nonnull BuilderEntityFilterStandingOnBlock builder, @Nonnull BuilderSupport support) { + this.blockSet = builder.getBlockSet(support); + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + NPCEntity targetNpcComponent = store.getComponent(targetRef, NPC_COMPONENT_TYPE); + if (targetNpcComponent != null) { + Role targetRole = targetNpcComponent.getRole(); + + assert targetRole != null; + + MotionController motionController = targetRole.getActiveMotionController(); + return motionController.standingOnBlockOfType(this.blockSet); + } else { + TransformComponent transformComponent = store.getComponent(targetRef, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + Ref chunkRef = transformComponent.getChunkRef(); + if (chunkRef != null && chunkRef.isValid()) { + World world = store.getExternalData().getWorld(); + Store chunkStore = world.getChunkStore().getStore(); + BlockChunk blockChunkComponent = chunkStore.getComponent(chunkRef, BlockChunk.getComponentType()); + + assert blockChunkComponent != null; + + Vector3d pos = transformComponent.getPosition(); + int blockId = blockChunkComponent.getBlock(MathUtil.floor(pos.x), MathUtil.floor(pos.y - 1.0), MathUtil.floor(pos.z)); + return BlockSetModule.getInstance().blockInSet(this.blockSet, blockId); + } else { + return false; + } + } + } + + @Override + public int cost() { + return 300; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterStat.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterStat.java new file mode 100644 index 0000000..22dc1b2 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterStat.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.function.function.ToFloatFunction; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.EntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterStat; +import com.hypixel.hytale.server.npc.role.Role; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class EntityFilterStat extends EntityFilterBase { + public static final int COST = 200; + protected static final ComponentType ENTITY_STAT_MAP_COMPONENT_TYPE = EntityStatMap.getComponentType(); + protected final int stat; + protected final EntityFilterStat.EntityStatTarget statTarget; + protected final int relativeTo; + protected final EntityFilterStat.EntityStatTarget relativeToTarget; + protected final double minValue; + protected final double maxValue; + + public EntityFilterStat(@Nonnull BuilderEntityFilterStat builder, @Nonnull BuilderSupport support) { + this.stat = builder.getStat(support); + this.statTarget = builder.getStatTarget(support); + this.relativeTo = builder.getRelativeTo(support); + this.relativeToTarget = builder.getRelativeToTarget(support); + double[] valueRange = builder.getValueRange(support); + this.minValue = valueRange[0]; + this.maxValue = valueRange[1]; + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + EntityStatMap entityStatMap = store.getComponent(targetRef, ENTITY_STAT_MAP_COMPONENT_TYPE); + EntityStatValue entityStatValue = entityStatMap.get(this.stat); + EntityStatValue relativeEntityStatValue = entityStatMap.get(this.relativeTo); + float statValue = this.statTarget.get(entityStatValue); + float relativeStatValue = this.relativeToTarget.get(relativeEntityStatValue); + double ratio = statValue / relativeStatValue; + return ratio >= this.minValue && ratio <= this.maxValue; + } + + @Override + public int cost() { + return 200; + } + + public static enum EntityStatTarget implements Supplier { + Value("Current value", EntityStatValue::get), + Min("Min value", EntityStatValue::getMin), + Max("Max value", EntityStatValue::getMax); + + private final String description; + private final ToFloatFunction getter; + + private EntityStatTarget(String description, ToFloatFunction getter) { + this.description = description; + this.getter = getter; + } + + public String get() { + return this.description; + } + + public float get(EntityStatValue entityStatValue) { + return this.getter.applyAsFloat(entityStatValue); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterViewSector.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterViewSector.java new file mode 100644 index 0000000..9ea7376 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/EntityFilterViewSector.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.EntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterViewSector; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.util.NPCPhysicsMath; +import javax.annotation.Nonnull; + +public class EntityFilterViewSector extends EntityFilterBase { + public static final int COST = 300; + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected final float viewCone; + + public EntityFilterViewSector(@Nonnull BuilderEntityFilterViewSector builder, @Nonnull BuilderSupport support) { + this.viewCone = builder.getViewSectorRadians(support); + } + + @Override + public boolean matchesEntity(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull Role role, @Nonnull Store store) { + TransformComponent transformComponent = store.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + TransformComponent targetTransformComponent = store.getComponent(targetRef, TRANSFORM_COMPONENT_TYPE); + + assert targetTransformComponent != null; + + Vector3d targetPosition = targetTransformComponent.getPosition(); + return NPCPhysicsMath.inViewSector( + position.getX(), position.getZ(), headRotationComponent.getRotation().getYaw(), this.viewCone, targetPosition.getX(), targetPosition.getZ() + ); + } + + @Override + public int cost() { + return 300; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterAltitude.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterAltitude.java new file mode 100644 index 0000000..a3d5bb6 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterAltitude.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderEntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterAltitude; +import javax.annotation.Nonnull; + +public class BuilderEntityFilterAltitude extends BuilderEntityFilterBase { + protected final NumberArrayHolder altitudeRange = new NumberArrayHolder(); + + public BuilderEntityFilterAltitude() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Matches targets if they're within the defined range above the ground"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public IEntityFilter build(@Nonnull BuilderSupport builderSupport) { + return new EntityFilterAltitude(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireDoubleArray( + data, + "AltitudeRange", + this.altitudeRange, + 0, + Integer.MAX_VALUE, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "The range above the ground to match", + null + ); + return this; + } + + public double[] getAltitudeRange(@Nonnull BuilderSupport support) { + return this.altitudeRange.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterAnd.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterAnd.java new file mode 100644 index 0000000..e041d93 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterAnd.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterAnd; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderEntityFilterAnd extends BuilderEntityFilterMany { + public BuilderEntityFilterAnd() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Logical AND of a list of filters"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nullable + public IEntityFilter build(@Nonnull BuilderSupport builderSupport) { + List filters = this.objectListHelper.build(builderSupport); + return filters.isEmpty() ? null : new EntityFilterAnd(filters); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterAttitude.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterAttitude.java new file mode 100644 index 0000000..dfc3dc9 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterAttitude.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.ComponentContext; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumSetHolder; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderEntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterAttitude; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class BuilderEntityFilterAttitude extends BuilderEntityFilterBase { + protected final EnumSetHolder attitudes = new EnumSetHolder<>(); + + public BuilderEntityFilterAttitude() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Matches the attitude towards the locked target"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public IEntityFilter build(@Nonnull BuilderSupport builderSupport) { + return new EntityFilterAttitude(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireEnumSet(data, "Attitudes", this.attitudes, Attitude.class, BuilderDescriptorState.Stable, "The attitudes to match", null); + this.requireContext(InstructionType.Any, ComponentContext.NotSelfEntitySensor); + return this; + } + + public EnumSet getAttitudes(@Nonnull BuilderSupport support) { + return this.attitudes.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterCombat.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterCombat.java new file mode 100644 index 0000000..d67eecd --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterCombat.java @@ -0,0 +1,99 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.CombatInteractionValidator; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderEntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterCombat; +import javax.annotation.Nonnull; + +public class BuilderEntityFilterCombat extends BuilderEntityFilterBase { + public static final double MAX_ELAPSED_TIME = Float.MAX_VALUE; + public static final double[] DEFAULT_TIME_ELAPSED_RANGE = new double[]{0.0, Float.MAX_VALUE}; + protected final AssetHolder sequence = new AssetHolder(); + protected final NumberArrayHolder elapsedTimeRange = new NumberArrayHolder(); + protected final EnumHolder mode = new EnumHolder<>(); + + public BuilderEntityFilterCombat() { + } + + @Nonnull + public EntityFilterCombat build(@Nonnull BuilderSupport builderSupport) { + return new EntityFilterCombat(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Check the target's combat state"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Check the target's combat state. This includes whether they're attacking at all, if they're using a particular attack, etc"; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getAsset( + data, + "Sequence", + this.sequence, + null, + CombatInteractionValidator.withConfig(AssetValidator.CanBeEmpty), + BuilderDescriptorState.Stable, + "The attack ID to check for.", + null + ); + this.getDoubleRange( + data, + "TimeElapsedRange", + this.elapsedTimeRange, + DEFAULT_TIME_ELAPSED_RANGE, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, Float.MAX_VALUE), + BuilderDescriptorState.Stable, + "The acceptable elapsed time range in seconds.", + null + ); + this.getEnum( + data, + "Mode", + this.mode, + EntityFilterCombat.Mode.class, + EntityFilterCombat.Mode.Attacking, + BuilderDescriptorState.Stable, + "Type of combat to trigger on", + null + ); + this.validateAssetIfEnumIs(this.sequence, CombatInteractionValidator.required(), this.mode, EntityFilterCombat.Mode.Sequence); + return this; + } + + public String getSequence(@Nonnull BuilderSupport builderSupport) { + return this.sequence.get(builderSupport.getExecutionContext()); + } + + public EntityFilterCombat.Mode getCombatMode(@Nonnull BuilderSupport builderSupport) { + return this.mode.get(builderSupport.getExecutionContext()); + } + + public double[] getTimeElapsedRange(@Nonnull BuilderSupport builderSupport) { + return this.elapsedTimeRange.get(builderSupport.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterHeightDifference.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterHeightDifference.java new file mode 100644 index 0000000..f68acb8 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterHeightDifference.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.ComponentContext; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderEntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterHeightDifference; +import javax.annotation.Nonnull; + +public class BuilderEntityFilterHeightDifference extends BuilderEntityFilterBase { + public static final double[] DEFAULT_HEIGHT_DIFFERENCE_RANGE = new double[]{-Double.MAX_VALUE, Double.MAX_VALUE}; + protected final BooleanHolder useEyePosition = new BooleanHolder(); + protected final NumberArrayHolder heightDifference = new NumberArrayHolder(); + + public BuilderEntityFilterHeightDifference() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Matches entities within the given height range"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public IEntityFilter build(@Nonnull BuilderSupport builderSupport) { + return new EntityFilterHeightDifference(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getDoubleRange( + data, + "HeightDifference", + this.heightDifference, + DEFAULT_HEIGHT_DIFFERENCE_RANGE, + DoubleSequenceValidator.betweenWeaklyMonotonic(-Double.MAX_VALUE, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "The height range to test entities in", + "The height range to test entities in. Extends into negatives for positions below the NPC" + ); + this.getBoolean(data, "UseEyePosition", this.useEyePosition, true, BuilderDescriptorState.Stable, "Use eye position for height difference checks", null); + this.requireContext(InstructionType.Any, ComponentContext.NotSelfEntitySensor); + return this; + } + + public double[] getHeightDifference(@Nonnull BuilderSupport support) { + return this.heightDifference.get(support.getExecutionContext()); + } + + public boolean isUseEyePosition(@Nonnull BuilderSupport support) { + return this.useEyePosition.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterInsideBlock.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterInsideBlock.java new file mode 100644 index 0000000..be5503f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterInsideBlock.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.BlockSetExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderEntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterInsideBlock; +import javax.annotation.Nonnull; + +public class BuilderEntityFilterInsideBlock extends BuilderEntityFilterBase { + protected final AssetHolder blockSet = new AssetHolder(); + + public BuilderEntityFilterInsideBlock() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Matches if the entity is inside any of the blocks in the BlockSet"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public IEntityFilter build(@Nonnull BuilderSupport builderSupport) { + return new EntityFilterInsideBlock(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireAsset( + data, "BlockSet", this.blockSet, BlockSetExistsValidator.required(), BuilderDescriptorState.Stable, "The BlockSet to match against", null + ); + return this; + } + + public int getBlockSet(@Nonnull BuilderSupport support) { + String key = this.blockSet.get(support.getExecutionContext()); + int index = BlockSet.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } else { + return index; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterInventory.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterInventory.java new file mode 100644 index 0000000..95e1087 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterInventory.java @@ -0,0 +1,103 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.IntSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.ItemExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderEntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterInventory; +import java.util.EnumSet; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderEntityFilterInventory extends BuilderEntityFilterBase { + public static final int[] DEFAULT_FREE_SLOT_RANGE = new int[]{0, Integer.MAX_VALUE}; + public static final int[] DEFAULT_ITEM_COUNT_RANGE = new int[]{1, Integer.MAX_VALUE}; + public static final String[] DEFAULT_ITEM_PATTERNS = new String[]{"*"}; + protected final AssetArrayHolder items = new AssetArrayHolder(); + protected final NumberArrayHolder count = new NumberArrayHolder(); + protected final NumberArrayHolder freeSlots = new NumberArrayHolder(); + + public BuilderEntityFilterInventory() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test various conditions relating to entity inventory"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Test various conditions relating to entity inventory. This includes whether it contains a specific item, item count, and free slots"; + } + + @Nonnull + public IEntityFilter build(@Nonnull BuilderSupport builderSupport) { + return new EntityFilterInventory(this, builderSupport); + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getAssetArray( + data, + "Items", + this.items, + DEFAULT_ITEM_PATTERNS, + 0, + Integer.MAX_VALUE, + ItemExistsValidator.withConfig(EnumSet.of(AssetValidator.Config.MATCHER)), + BuilderDescriptorState.Stable, + "A list of glob item patterns to match", + null + ); + this.getIntRange( + data, + "CountRange", + this.count, + DEFAULT_ITEM_COUNT_RANGE, + IntSequenceValidator.betweenWeaklyMonotonic(0, Integer.MAX_VALUE), + BuilderDescriptorState.Stable, + "The range of number of items that need to match the patterns", + null + ); + this.getIntRange( + data, + "FreeSlotRange", + this.freeSlots, + DEFAULT_FREE_SLOT_RANGE, + IntSequenceValidator.betweenWeaklyMonotonic(0, Integer.MAX_VALUE), + BuilderDescriptorState.Stable, + "The range designating the number of required free slots", + "The range designating the number of required free slots. Setting min and max to zero would check if full" + ); + return this; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nullable + public String[] getItems(@Nonnull BuilderSupport support) { + return this.items.get(support.getExecutionContext()); + } + + public int[] getCount(@Nonnull BuilderSupport support) { + return this.count.getIntArray(support.getExecutionContext()); + } + + public int[] getFreeSlotsRange(@Nonnull BuilderSupport support) { + return this.freeSlots.getIntArray(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterItemInHand.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterItemInHand.java new file mode 100644 index 0000000..8294cfa --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterItemInHand.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.ItemExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderEntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterItemInHand; +import java.util.EnumSet; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderEntityFilterItemInHand extends BuilderEntityFilterBase { + protected final AssetArrayHolder items = new AssetArrayHolder(); + protected final EnumHolder hand = new EnumHolder<>(); + + public BuilderEntityFilterItemInHand() { + } + + @Nonnull + public EntityFilterItemInHand build(@Nonnull BuilderSupport builderSupport) { + return new EntityFilterItemInHand(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Check if entity is holding an item"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireAssetArray( + data, + "Items", + this.items, + 0, + Integer.MAX_VALUE, + ItemExistsValidator.withConfig(EnumSet.of(AssetValidator.Config.MATCHER)), + BuilderDescriptorState.Stable, + "A list of glob item patterns to match", + null + ); + this.getEnum( + data, + "Hand", + this.hand, + EntityFilterItemInHand.WieldingHand.class, + EntityFilterItemInHand.WieldingHand.Both, + BuilderDescriptorState.Stable, + "Which hand to check", + null + ); + return this; + } + + @Nullable + public String[] getItems(@Nonnull BuilderSupport support) { + return this.items.get(support.getExecutionContext()); + } + + public EntityFilterItemInHand.WieldingHand getHand(@Nonnull BuilderSupport support) { + return this.hand.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterLineOfSight.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterLineOfSight.java new file mode 100644 index 0000000..33ddf97 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterLineOfSight.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.ComponentContext; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderEntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterLineOfSight; +import javax.annotation.Nonnull; + +public class BuilderEntityFilterLineOfSight extends BuilderEntityFilterBase { + public BuilderEntityFilterLineOfSight() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Matches if there is line of sight to the target"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public IEntityFilter build(BuilderSupport builderSupport) { + return new EntityFilterLineOfSight(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(JsonElement data) { + this.requireContext(InstructionType.Any, ComponentContext.NotSelfEntitySensor); + return this; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterMany.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterMany.java new file mode 100644 index 0000000..407943b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterMany.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectListHelper; +import com.hypixel.hytale.server.npc.asset.builder.validators.ArrayNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderEntityFilterWithToggle; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; + +public abstract class BuilderEntityFilterMany extends BuilderEntityFilterWithToggle { + @Nonnull + protected BuilderObjectListHelper objectListHelper = new BuilderObjectListHelper<>(IEntityFilter.class, this); + + public BuilderEntityFilterMany() { + } + + @Override + public void registerTags(@Nonnull Set tags) { + super.registerTags(tags); + tags.add("logic"); + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireArray( + data, "Filters", this.objectListHelper, ArrayNotEmptyValidator.get(), BuilderDescriptorState.Stable, "List of filters", null, this.validationHelper + ); + return this; + } + + @Override + public boolean validate( + String configName, + @Nonnull NPCLoadTimeValidationHelper validationHelper, + @Nonnull ExecutionContext context, + Scope globalScope, + @Nonnull List errors + ) { + boolean result = super.validate(configName, validationHelper, context, globalScope, errors); + validationHelper.pushFilterSet(); + result &= this.objectListHelper.validate(configName, validationHelper, this.builderManager, context, globalScope, errors); + validationHelper.popFilterSet(); + return result; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterMovementState.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterMovementState.java new file mode 100644 index 0000000..fa79dba --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterMovementState.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderEntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterMovementState; +import com.hypixel.hytale.server.npc.movement.MovementState; +import javax.annotation.Nonnull; + +public class BuilderEntityFilterMovementState extends BuilderEntityFilterBase { + protected MovementState movementState; + + public BuilderEntityFilterMovementState() { + } + + @Nonnull + public EntityFilterMovementState build(BuilderSupport builderSupport) { + return new EntityFilterMovementState(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Check if the entity is in the given movement state"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireEnum(data, "State", e -> this.movementState = e, MovementState.class, BuilderDescriptorState.Stable, "The movement state to check", null); + return this; + } + + public MovementState getMovementState() { + return this.movementState; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterNPCGroup.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterNPCGroup.java new file mode 100644 index 0000000..5cf8a1f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterNPCGroup.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.TagSetExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderEntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterNPCGroup; +import com.hypixel.hytale.server.npc.role.support.WorldSupport; +import javax.annotation.Nonnull; + +public class BuilderEntityFilterNPCGroup extends BuilderEntityFilterBase { + protected final AssetArrayHolder includeGroups = new AssetArrayHolder(); + protected final AssetArrayHolder excludeGroups = new AssetArrayHolder(); + + public BuilderEntityFilterNPCGroup() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Returns whether the entity matches one of the provided NPCGroups"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public IEntityFilter build(@Nonnull BuilderSupport builderSupport) { + return new EntityFilterNPCGroup(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getAssetArray( + data, + "IncludeGroups", + this.includeGroups, + null, + 0, + Integer.MAX_VALUE, + TagSetExistsValidator.withConfig(AssetValidator.ListCanBeEmpty), + BuilderDescriptorState.Stable, + "A set of NPCGroups to include in the match", + null + ); + this.getAssetArray( + data, + "ExcludeGroups", + this.excludeGroups, + null, + 0, + Integer.MAX_VALUE, + TagSetExistsValidator.withConfig(AssetValidator.ListCanBeEmpty), + BuilderDescriptorState.Stable, + "A set of NPCGroups to exclude from the match", + null + ); + this.validateOneSetAssetArray(this.includeGroups, this.excludeGroups); + return this; + } + + public int[] getIncludeGroups(@Nonnull BuilderSupport builderSupport) { + return WorldSupport.createTagSetIndexArray(this.includeGroups.get(builderSupport.getExecutionContext())); + } + + public int[] getExcludeGroups(@Nonnull BuilderSupport builderSupport) { + return WorldSupport.createTagSetIndexArray(this.excludeGroups.get(builderSupport.getExecutionContext())); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterNot.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterNot.java new file mode 100644 index 0000000..3a493fb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterNot.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectReferenceHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderEntityFilterWithToggle; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterNot; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderEntityFilterNot extends BuilderEntityFilterWithToggle { + protected final BuilderObjectReferenceHelper filter = new BuilderObjectReferenceHelper<>(IEntityFilter.class, this); + + public BuilderEntityFilterNot() { + } + + @Nullable + public IEntityFilter build(@Nonnull BuilderSupport builderSupport) { + IEntityFilter filter = this.getFilter(builderSupport); + return filter == null ? null : new EntityFilterNot(filter); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Invert filter test"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Override + public void registerTags(@Nonnull Set tags) { + super.registerTags(tags); + tags.add("logic"); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.WorkInProgress; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireObject(data, "Filter", this.filter, BuilderDescriptorState.Stable, "Filter to test", null, this.validationHelper); + return this; + } + + @Override + public boolean validate( + String configName, + @Nonnull NPCLoadTimeValidationHelper validationHelper, + @Nonnull ExecutionContext context, + Scope globalScope, + @Nonnull List errors + ) { + return super.validate(configName, validationHelper, context, globalScope, errors) + & this.filter.validate(configName, validationHelper, this.builderManager, context, globalScope, errors); + } + + @Nullable + public IEntityFilter getFilter(@Nonnull BuilderSupport support) { + return this.filter.build(support); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterOr.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterOr.java new file mode 100644 index 0000000..5313bf2 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterOr.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterOr; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderEntityFilterOr extends BuilderEntityFilterMany { + public BuilderEntityFilterOr() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Logical OR of a list of filters"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nullable + public IEntityFilter build(@Nonnull BuilderSupport builderSupport) { + List filters = this.objectListHelper.build(builderSupport); + return filters.isEmpty() ? null : new EntityFilterOr(filters); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterSpotsMe.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterSpotsMe.java new file mode 100644 index 0000000..bc871a8 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterSpotsMe.java @@ -0,0 +1,88 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.ComponentContext; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderEntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterSpotsMe; +import com.hypixel.hytale.server.npc.util.ViewTest; +import javax.annotation.Nonnull; + +public class BuilderEntityFilterSpotsMe extends BuilderEntityFilterBase { + protected float viewAngle; + protected boolean testLineOfSight; + protected ViewTest viewTest; + + public BuilderEntityFilterSpotsMe() { + } + + @Nonnull + public EntityFilterSpotsMe build(BuilderSupport builderSupport) { + return new EntityFilterSpotsMe(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Checks if the entity can view the NPC in a given view sector or cone and without obstruction."; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getFloat( + data, + "ViewAngle", + f -> this.viewAngle = f * (float) (Math.PI / 180.0), + 90.0F, + DoubleRangeValidator.between(0.0, 360.0), + BuilderDescriptorState.Stable, + "Angle used for view sector and view cone test", + null + ); + this.getEnum( + data, + "ViewTest", + e -> this.viewTest = e, + ViewTest.class, + ViewTest.VIEW_SECTOR, + BuilderDescriptorState.Stable, + "Check if the entity is in the view cone, view sector, or neither", + null + ); + this.getBoolean( + data, "TestLineOfSight", b -> this.testLineOfSight = b, true, BuilderDescriptorState.Stable, "Check if view to the npc is not obstructed", null + ); + this.requireContext(InstructionType.Any, ComponentContext.NotSelfEntitySensor); + return this; + } + + public float getViewAngle() { + return this.viewAngle; + } + + public boolean testLineOfSight() { + return this.testLineOfSight; + } + + public ViewTest getViewTest() { + return this.viewTest; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterStandingOnBlock.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterStandingOnBlock.java new file mode 100644 index 0000000..f02a4d1 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterStandingOnBlock.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.BlockSetExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderEntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterStandingOnBlock; +import javax.annotation.Nonnull; + +public class BuilderEntityFilterStandingOnBlock extends BuilderEntityFilterBase { + protected final AssetHolder blockSet = new AssetHolder(); + + public BuilderEntityFilterStandingOnBlock() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Matches the block directly beneath the entity against a BlockSet"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public IEntityFilter build(@Nonnull BuilderSupport builderSupport) { + return new EntityFilterStandingOnBlock(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireAsset( + data, "BlockSet", this.blockSet, BlockSetExistsValidator.required(), BuilderDescriptorState.Stable, "The BlockSet to match against", null + ); + return this; + } + + public int getBlockSet(@Nonnull BuilderSupport support) { + String key = this.blockSet.get(support.getExecutionContext()); + int index = BlockSet.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } else { + return index; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterStat.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterStat.java new file mode 100644 index 0000000..8770317 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterStat.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.EntityStatExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderEntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterStat; +import javax.annotation.Nonnull; + +public class BuilderEntityFilterStat extends BuilderEntityFilterBase { + protected final AssetHolder stat = new AssetHolder(); + protected final EnumHolder statTarget = new EnumHolder<>(); + protected final AssetHolder relativeTo = new AssetHolder(); + protected final EnumHolder relativeToTarget = new EnumHolder<>(); + protected final NumberArrayHolder valueRange = new NumberArrayHolder(); + + public BuilderEntityFilterStat() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Match stat values of the entity"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public IEntityFilter build(@Nonnull BuilderSupport builderSupport) { + return new EntityFilterStat(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireAsset(data, "Stat", this.stat, EntityStatExistsValidator.required(), BuilderDescriptorState.Stable, "The stat value to check", null); + this.requireEnum(data, "StatTarget", this.statTarget, EntityFilterStat.EntityStatTarget.class, BuilderDescriptorState.Stable, "The stat target", null); + this.requireAsset( + data, "RelativeTo", this.relativeTo, EntityStatExistsValidator.required(), BuilderDescriptorState.Stable, "The stat value to check against", null + ); + this.requireEnum( + data, "RelativeToTarget", this.relativeToTarget, EntityFilterStat.EntityStatTarget.class, BuilderDescriptorState.Stable, "The stat target", null + ); + this.requireDoubleRange( + data, + "ValueRange", + this.valueRange, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "The fractional range within which to trigger, with 1 being equal", + null + ); + return this; + } + + public int getStat(@Nonnull BuilderSupport support) { + return EntityStatType.getAssetMap().getIndex(this.stat.get(support.getExecutionContext())); + } + + public EntityFilterStat.EntityStatTarget getStatTarget(@Nonnull BuilderSupport support) { + return this.statTarget.get(support.getExecutionContext()); + } + + public int getRelativeTo(@Nonnull BuilderSupport support) { + return EntityStatType.getAssetMap().getIndex(this.relativeTo.get(support.getExecutionContext())); + } + + public EntityFilterStat.EntityStatTarget getRelativeToTarget(@Nonnull BuilderSupport support) { + return this.relativeToTarget.get(support.getExecutionContext()); + } + + public double[] getValueRange(@Nonnull BuilderSupport support) { + return this.valueRange.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterViewSector.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterViewSector.java new file mode 100644 index 0000000..f654b7c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/filters/builders/BuilderEntityFilterViewSector.java @@ -0,0 +1,65 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.ComponentContext; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.asset.builder.holder.FloatHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderEntityFilterBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterViewSector; +import javax.annotation.Nonnull; + +public class BuilderEntityFilterViewSector extends BuilderEntityFilterBase { + protected final FloatHolder viewSector = new FloatHolder(); + + public BuilderEntityFilterViewSector() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Matches entities within the given view sector"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public IEntityFilter build(@Nonnull BuilderSupport builderSupport) { + return new EntityFilterViewSector(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getFloat( + data, + "ViewSector", + this.viewSector, + 0.0, + DoubleRangeValidator.between(0.0, 360.0), + BuilderDescriptorState.Stable, + "View sector to test entities in", + null + ); + this.requireContext(InstructionType.Any, ComponentContext.NotSelfEntitySensor); + return this; + } + + public float getViewSectorRadians(@Nonnull BuilderSupport builderSupport) { + return (float) (Math.PI / 180.0) * this.viewSector.get(builderSupport.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/prioritisers/SensorEntityPrioritiserAttitude.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/prioritisers/SensorEntityPrioritiserAttitude.java new file mode 100644 index 0000000..a8fd9ce --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/prioritisers/SensorEntityPrioritiserAttitude.java @@ -0,0 +1,151 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.prioritisers; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.ISensorEntityPrioritiser; +import com.hypixel.hytale.server.npc.corecomponents.entity.filters.EntityFilterAttitude; +import com.hypixel.hytale.server.npc.corecomponents.entity.prioritisers.builders.BuilderSensorEntityPrioritiserAttitude; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.WorldSupport; +import com.hypixel.hytale.server.npc.util.IEntityByPriorityFilter; +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SensorEntityPrioritiserAttitude implements ISensorEntityPrioritiser { + private static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + private final Attitude[] attitudeByPriority; + + public SensorEntityPrioritiserAttitude(@Nonnull BuilderSensorEntityPrioritiserAttitude builder, @Nonnull BuilderSupport support) { + this.attitudeByPriority = builder.getPrioritisedAttitudes(support); + } + + @Override + public void registerWithSupport(@Nonnull Role role) { + role.getWorldSupport().requireAttitudeCache(); + } + + @Nonnull + @Override + public IEntityByPriorityFilter getNPCPrioritiser() { + return new SensorEntityPrioritiserAttitude.AttitudePrioritiser(this.attitudeByPriority); + } + + @Nonnull + @Override + public IEntityByPriorityFilter getPlayerPrioritiser() { + return new SensorEntityPrioritiserAttitude.AttitudePrioritiser(this.attitudeByPriority); + } + + @Nonnull + @Override + public Ref pickTarget( + @Nonnull Ref ref, + @Nonnull Role role, + @Nonnull Vector3d position, + @Nonnull Ref playerRef, + @Nonnull Ref npcRef, + boolean useProjectedDistance, + @Nonnull Store store + ) { + WorldSupport worldSupport = role.getWorldSupport(); + int playerPriority = this.getPriority(ref, worldSupport, playerRef, store); + int npcPriority = this.getPriority(ref, worldSupport, npcRef, store); + if (playerPriority != npcPriority) { + return playerPriority <= npcPriority ? playerRef : npcRef; + } else { + MotionController motionController = role.getActiveMotionController(); + TransformComponent playerTransformComponent = store.getComponent(playerRef, TRANSFORM_COMPONENT_TYPE); + + assert playerTransformComponent != null; + + TransformComponent npcTransformComponent = store.getComponent(npcRef, TRANSFORM_COMPONENT_TYPE); + + assert npcTransformComponent != null; + + return motionController.getSquaredDistance(position, playerTransformComponent.getPosition(), useProjectedDistance) + <= motionController.getSquaredDistance(position, npcTransformComponent.getPosition(), useProjectedDistance) + ? playerRef + : npcRef; + } + } + + @Override + public boolean providesFilters() { + return true; + } + + @Override + public void buildProvidedFilters(@Nonnull List filters) { + filters.add(new EntityFilterAttitude(this.attitudeByPriority)); + } + + protected int getPriority( + @Nonnull Ref ref, @Nonnull WorldSupport support, @Nonnull Ref targetRef, @Nonnull Store store + ) { + Attitude attitude = support.getAttitude(ref, targetRef, store); + + for (int i = 0; i < this.attitudeByPriority.length; i++) { + if (attitude == this.attitudeByPriority[i]) { + return i; + } + } + + throw new IllegalStateException(String.format("Attitude %s was not specified in the priority list but an NPC with that attitude was picked", attitude)); + } + + public static class AttitudePrioritiser implements IEntityByPriorityFilter { + private final Attitude[] attitudeByPriority; + private final Ref[] targetsByAttitude = new Ref[Attitude.VALUES.length]; + @Nullable + private WorldSupport support; + + public AttitudePrioritiser(Attitude[] attitudeByPriority) { + this.attitudeByPriority = attitudeByPriority; + } + + @Override + public void init(@Nonnull Role role) { + this.support = role.getWorldSupport(); + } + + public boolean test(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor) { + Attitude attitude = this.support.getAttitude(ref, targetRef, componentAccessor); + if (this.targetsByAttitude[attitude.ordinal()] == null) { + this.targetsByAttitude[attitude.ordinal()] = targetRef; + } + + return attitude == this.attitudeByPriority[0]; + } + + @Nullable + @Override + public Ref getHighestPriorityTarget() { + for (int i = 0; i < this.attitudeByPriority.length; i++) { + int attitudeIdx = this.attitudeByPriority[i].ordinal(); + Ref target = this.targetsByAttitude[attitudeIdx]; + if (target != null) { + return target; + } + } + + return null; + } + + @Override + public void cleanup() { + this.support = null; + Arrays.fill(this.targetsByAttitude, null); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/prioritisers/SensorEntityPrioritiserDefault.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/prioritisers/SensorEntityPrioritiserDefault.java new file mode 100644 index 0000000..93f9b9d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/prioritisers/SensorEntityPrioritiserDefault.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.prioritisers; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter; +import com.hypixel.hytale.server.npc.corecomponents.ISensorEntityPrioritiser; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.util.IEntityByPriorityFilter; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SensorEntityPrioritiserDefault implements ISensorEntityPrioritiser { + private static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + private final SensorEntityPrioritiserDefault.DefaultPrioritiser playerPrioritiser = new SensorEntityPrioritiserDefault.DefaultPrioritiser(); + private final SensorEntityPrioritiserDefault.DefaultPrioritiser npcPrioritiser = new SensorEntityPrioritiserDefault.DefaultPrioritiser(); + + public SensorEntityPrioritiserDefault() { + } + + @Nonnull + @Override + public IEntityByPriorityFilter getNPCPrioritiser() { + return this.npcPrioritiser; + } + + @Nonnull + @Override + public IEntityByPriorityFilter getPlayerPrioritiser() { + return this.playerPrioritiser; + } + + @Nonnull + @Override + public Ref pickTarget( + Ref ref, + @Nonnull Role role, + @Nonnull Vector3d position, + @Nonnull Ref playerRef, + @Nonnull Ref npcRef, + boolean useProjectedDistance, + @Nonnull Store store + ) { + MotionController motionController = role.getActiveMotionController(); + TransformComponent playerTransformComponent = store.getComponent(playerRef, TRANSFORM_COMPONENT_TYPE); + + assert playerTransformComponent != null; + + TransformComponent npcTransformComponent = store.getComponent(npcRef, TRANSFORM_COMPONENT_TYPE); + + assert npcTransformComponent != null; + + return motionController.getSquaredDistance(position, playerTransformComponent.getPosition(), useProjectedDistance) + <= motionController.getSquaredDistance(position, npcTransformComponent.getPosition(), useProjectedDistance) + ? playerRef + : npcRef; + } + + @Override + public boolean providesFilters() { + return false; + } + + @Override + public void buildProvidedFilters(List filters) { + } + + public static class DefaultPrioritiser implements IEntityByPriorityFilter { + @Nullable + private Ref target; + + private DefaultPrioritiser() { + } + + @Override + public void init(Role role) { + } + + @Nullable + @Override + public Ref getHighestPriorityTarget() { + return this.target; + } + + @Override + public void cleanup() { + this.target = null; + } + + public boolean test(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor) { + if (this.target == null) { + this.target = targetRef; + } + + return true; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/entity/prioritisers/builders/BuilderSensorEntityPrioritiserAttitude.java b/src/com/hypixel/hytale/server/npc/corecomponents/entity/prioritisers/builders/BuilderSensorEntityPrioritiserAttitude.java new file mode 100644 index 0000000..2247846 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/entity/prioritisers/builders/BuilderSensorEntityPrioritiserAttitude.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.server.npc.corecomponents.entity.prioritisers.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.EnumArrayNoDuplicatesValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorEntityPrioritiserBase; +import com.hypixel.hytale.server.npc.corecomponents.entity.prioritisers.SensorEntityPrioritiserAttitude; +import java.util.Set; +import javax.annotation.Nonnull; + +public class BuilderSensorEntityPrioritiserAttitude extends BuilderSensorEntityPrioritiserBase { + protected final EnumArrayHolder prioritisedAttitudes = new EnumArrayHolder<>(); + + public BuilderSensorEntityPrioritiserAttitude() { + super(Set.of("Attitude")); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Prioritises return entities by attitude"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public SensorEntityPrioritiserAttitude build(@Nonnull BuilderSupport builderSupport) { + return new SensorEntityPrioritiserAttitude(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderSensorEntityPrioritiserAttitude readConfig(@Nonnull JsonElement data) { + super.readConfig(data); + this.requireEnumArray( + data, + "AttitudesByPriority", + this.prioritisedAttitudes, + Attitude.class, + EnumArrayNoDuplicatesValidator.get(), + BuilderDescriptorState.Stable, + "A prioritised list of attitudes", + null + ); + return this; + } + + public Attitude[] getPrioritisedAttitudes(@Nonnull BuilderSupport support) { + return this.prioritisedAttitudes.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/interaction/ActionLockOnInteractionTarget.java b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/ActionLockOnInteractionTarget.java new file mode 100644 index 0000000..c577a4e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/ActionLockOnInteractionTarget.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.npc.corecomponents.interaction; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.interaction.builders.BuilderActionLockOnInteractionTarget; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionLockOnInteractionTarget extends ActionBase { + protected final int targetSlot; + + public ActionLockOnInteractionTarget(@Nonnull BuilderActionLockOnInteractionTarget builderActionBase, @Nonnull BuilderSupport support) { + super(builderActionBase); + this.targetSlot = builderActionBase.getTargetSlot(support); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && role.getStateSupport().getInteractionIterationTarget() != null; + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + Ref target = role.getStateSupport().getInteractionIterationTarget(); + role.getMarkedEntitySupport().setMarkedEntity(this.targetSlot, target); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/interaction/ActionSetInteractable.java b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/ActionSetInteractable.java new file mode 100644 index 0000000..428b477 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/ActionSetInteractable.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.npc.corecomponents.interaction; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.interaction.builders.BuilderActionSetInteractable; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.StateSupport; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionSetInteractable extends ActionBase { + protected final boolean setTo; + @Nullable + protected final String hint; + protected final boolean showPrompt; + + public ActionSetInteractable(@Nonnull BuilderActionSetInteractable builder, @Nonnull BuilderSupport support) { + super(builder); + this.setTo = builder.getSetTo(support); + this.hint = builder.getHint(); + this.showPrompt = builder.getShowPrompt(); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && role.getStateSupport().getInteractionIterationTarget() != null; + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + StateSupport stateSupport = role.getStateSupport(); + stateSupport.setInteractable(ref, stateSupport.getInteractionIterationTarget(), this.setTo, this.hint, this.showPrompt, store); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/interaction/SensorCanInteract.java b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/SensorCanInteract.java new file mode 100644 index 0000000..1d98f5a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/SensorCanInteract.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.server.npc.corecomponents.interaction; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.interaction.builders.BuilderSensorCanInteract; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.NPCPhysicsMath; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class SensorCanInteract extends SensorBase { + protected final float viewCone; + protected final EnumSet attitudes; + + public SensorCanInteract(@Nonnull BuilderSensorCanInteract builder, @Nonnull BuilderSupport support) { + super(builder); + this.viewCone = builder.getViewSectorRadians(support); + this.attitudes = builder.getAttitudes(support); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + return false; + } else { + Ref targetRef = role.getStateSupport().getInteractionIterationTarget(); + if (targetRef == null) { + return false; + } else { + Archetype targetArchetype = store.getArchetype(targetRef); + if (targetArchetype.contains(DeathComponent.getComponentType())) { + return false; + } else { + TransformComponent targetTransformComponent = store.getComponent(targetRef, TransformComponent.getComponentType()); + if (targetTransformComponent == null) { + return false; + } else { + Vector3d targetPosition = targetTransformComponent.getPosition(); + if (this.viewCone > 0.0F) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3d position = transformComponent.getPosition(); + float headRotationYaw = headRotationComponent.getRotation().getYaw(); + if (!NPCPhysicsMath.inViewSector( + position.getX(), position.getZ(), headRotationYaw, this.viewCone, targetPosition.getX(), targetPosition.getZ() + )) { + return false; + } + } + + Attitude attitude = role.getWorldSupport().getAttitude(ref, targetRef, store); + return this.attitudes.contains(attitude); + } + } + } + } + } + + @Override + public void registerWithSupport(@Nonnull Role role) { + role.getWorldSupport().requireAttitudeCache(); + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/interaction/SensorHasInteracted.java b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/SensorHasInteracted.java new file mode 100644 index 0000000..38818fb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/SensorHasInteracted.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.server.npc.corecomponents.interaction; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorHasInteracted extends SensorBase { + public SensorHasInteracted(@Nonnull BuilderSensorBase builderSensorBase) { + super(builderSensorBase); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + return false; + } else { + Ref target = role.getStateSupport().getInteractionIterationTarget(); + if (target == null) { + return false; + } else { + Archetype targetArchetype = store.getArchetype(target); + return targetArchetype.contains(DeathComponent.getComponentType()) ? false : role.getStateSupport().consumeInteraction(target); + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/interaction/SensorInteractionContext.java b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/SensorInteractionContext.java new file mode 100644 index 0000000..fe2937f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/SensorInteractionContext.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.npc.corecomponents.interaction; + +import com.hypixel.hytale.component.Archetype; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.interaction.builders.BuilderSensorInteractionContext; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorInteractionContext extends SensorBase { + private final String interactionContext; + + public SensorInteractionContext(@Nonnull BuilderSensorInteractionContext builder, @Nonnull BuilderSupport support) { + super(builder); + this.interactionContext = builder.getInteractionContext(support); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + return false; + } else { + Ref target = role.getStateSupport().getInteractionIterationTarget(); + if (target == null) { + return false; + } else { + Archetype targetArchetype = store.getArchetype(target); + return targetArchetype.contains(DeathComponent.getComponentType()) + ? false + : role.getStateSupport().hasContextualInteraction(target, this.interactionContext); + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/interaction/builders/BuilderActionLockOnInteractionTarget.java b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/builders/BuilderActionLockOnInteractionTarget.java new file mode 100644 index 0000000..b1cf065 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/builders/BuilderActionLockOnInteractionTarget.java @@ -0,0 +1,56 @@ +package com.hypixel.hytale.server.npc.corecomponents.interaction.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.interaction.ActionLockOnInteractionTarget; +import com.hypixel.hytale.server.npc.instructions.Action; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class BuilderActionLockOnInteractionTarget extends BuilderActionBase { + protected final StringHolder targetSlot = new StringHolder(); + + public BuilderActionLockOnInteractionTarget() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Locks on to the currently iterated player in the interaction instruction"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionLockOnInteractionTarget(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionLockOnInteractionTarget readConfig(@Nonnull JsonElement data) { + this.getString( + data, "TargetSlot", this.targetSlot, "LockedTarget", StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The target slot to use", null + ); + this.requireInstructionType(EnumSet.of(InstructionType.Interaction)); + return this; + } + + public int getTargetSlot(@Nonnull BuilderSupport support) { + return support.getTargetSlot(this.targetSlot.get(support.getExecutionContext())); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/interaction/builders/BuilderActionSetInteractable.java b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/builders/BuilderActionSetInteractable.java new file mode 100644 index 0000000..b2b222d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/builders/BuilderActionSetInteractable.java @@ -0,0 +1,92 @@ +package com.hypixel.hytale.server.npc.corecomponents.interaction.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.interaction.ActionSetInteractable; +import com.hypixel.hytale.server.npc.instructions.Action; +import java.util.EnumSet; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderActionSetInteractable extends BuilderActionBase { + protected final BooleanHolder setTo = new BooleanHolder(); + protected String hint; + protected boolean showPrompt = true; + + public BuilderActionSetInteractable() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Set whether the currently iterated player in the interaction instruction should be able to interact with this NPC"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionSetInteractable(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionSetInteractable readConfig(@Nonnull JsonElement data) { + this.getBoolean( + data, + "Interactable", + this.setTo, + true, + BuilderDescriptorState.Stable, + "Toggle whether the currently iterated player in the interaction instruction should be able to interact with this NPC", + null + ); + this.getString( + data, + "Hint", + h -> this.hint = h, + null, + null, + BuilderDescriptorState.Stable, + "The interaction hint translation key to show for this player (e.g. 'interactionHints.trade')", + null + ); + this.getBoolean( + data, + "ShowPrompt", + b -> this.showPrompt = b, + true, + BuilderDescriptorState.Stable, + "Whether to show the F-key interaction prompt. Set to false for contextual-only interactions (e.g. shearing with tools). Defaults to true.", + null + ); + this.requireInstructionType(EnumSet.of(InstructionType.Interaction)); + return this; + } + + public boolean getSetTo(@Nonnull BuilderSupport support) { + return this.setTo.get(support.getExecutionContext()); + } + + @Nullable + public String getHint() { + return this.hint; + } + + public boolean getShowPrompt() { + return this.showPrompt; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/interaction/builders/BuilderSensorCanInteract.java b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/builders/BuilderSensorCanInteract.java new file mode 100644 index 0000000..36863c3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/builders/BuilderSensorCanInteract.java @@ -0,0 +1,82 @@ +package com.hypixel.hytale.server.npc.corecomponents.interaction.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumSetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.FloatHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.interaction.SensorCanInteract; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class BuilderSensorCanInteract extends BuilderSensorBase { + protected final FloatHolder viewSector = new FloatHolder(); + protected final EnumSetHolder attitudes = new EnumSetHolder<>(); + + public BuilderSensorCanInteract() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Checks whether or not the player being iterated by the interaction instruction can interact with this NPC"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorCanInteract(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getFloat( + data, + "ViewSector", + this.viewSector, + 0.0, + DoubleRangeValidator.between(0.0, 360.0), + BuilderDescriptorState.Stable, + "View sector to test the player in", + null + ); + this.getEnumSet( + data, + "Attitudes", + this.attitudes, + Attitude.class, + EnumSet.of(Attitude.NEUTRAL, Attitude.FRIENDLY, Attitude.REVERED), + BuilderDescriptorState.Stable, + "A set of attitudes to match", + null + ); + this.requireInstructionType(EnumSet.of(InstructionType.Interaction)); + return this; + } + + public float getViewSectorRadians(@Nonnull BuilderSupport builderSupport) { + return (float) (Math.PI / 180.0) * this.viewSector.get(builderSupport.getExecutionContext()); + } + + public EnumSet getAttitudes(@Nonnull BuilderSupport support) { + return this.attitudes.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/interaction/builders/BuilderSensorHasInteracted.java b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/builders/BuilderSensorHasInteracted.java new file mode 100644 index 0000000..5e251d5 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/builders/BuilderSensorHasInteracted.java @@ -0,0 +1,47 @@ +package com.hypixel.hytale.server.npc.corecomponents.interaction.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.interaction.SensorHasInteracted; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class BuilderSensorHasInteracted extends BuilderSensorBase { + public BuilderSensorHasInteracted() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Checks whether the currently iterated player in the interaction instruction has interacted with this NPC"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Sensor build(BuilderSupport builderSupport) { + return new SensorHasInteracted(this); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(JsonElement data) { + this.requireInstructionType(EnumSet.of(InstructionType.Interaction)); + return this; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/interaction/builders/BuilderSensorInteractionContext.java b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/builders/BuilderSensorInteractionContext.java new file mode 100644 index 0000000..4db6a2b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/interaction/builders/BuilderSensorInteractionContext.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.npc.corecomponents.interaction.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.interaction.SensorInteractionContext; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class BuilderSensorInteractionContext extends BuilderSensorBase { + protected final StringHolder interactionContext = new StringHolder(); + + public BuilderSensorInteractionContext() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Checks whether the currently iterated player in the interaction instruction has interacted with this NPC in the given context"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorInteractionContext(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireString( + data, "Context", this.interactionContext, StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The context of the interaction", null + ); + this.requireInstructionType(EnumSet.of(InstructionType.Interaction)); + return this; + } + + public String getInteractionContext(@Nonnull BuilderSupport support) { + return this.interactionContext.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/items/ActionDropItem.java b/src/com/hypixel/hytale/server/npc/corecomponents/items/ActionDropItem.java new file mode 100644 index 0000000..fe37bdf --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/items/ActionDropItem.java @@ -0,0 +1,120 @@ +package com.hypixel.hytale.server.npc.corecomponents.items; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.entity.ItemUtils; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.item.ItemModule; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionWithDelay; +import com.hypixel.hytale.server.npc.corecomponents.items.builders.BuilderActionDropItem; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.AimingHelper; +import com.hypixel.hytale.server.npc.util.InventoryHelper; +import javax.annotation.Nonnull; + +public class ActionDropItem extends ActionWithDelay { + protected final String item; + protected final String dropList; + protected final float dropSectorStart; + protected final float dropSectorEnd; + protected final double minDistance; + protected final double maxDistance; + protected final boolean highPitch; + protected final float[] pitch = new float[2]; + protected final Vector3d dropDirection = new Vector3d(); + protected float throwSpeed; + + public ActionDropItem(@Nonnull BuilderActionDropItem builder, @Nonnull BuilderSupport support) { + super(builder, support); + this.item = builder.getItem(support); + this.dropList = builder.getDropList(support); + double[] distance = builder.getDistance(); + this.minDistance = distance[0]; + this.maxDistance = distance[1]; + this.throwSpeed = builder.getThrowSpeed(); + double[] dropSector = builder.getDropSectorRadians(); + this.dropSectorStart = (float) (Math.PI / 180.0) * (float)dropSector[0]; + float end = (float) (Math.PI / 180.0) * (float)dropSector[1]; + if (this.dropSectorStart > end) { + end += (float) (Math.PI * 2); + } + + this.dropSectorEnd = end; + this.highPitch = builder.isHighPitch(); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && !this.isDelaying(); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + this.prepareDelay(); + this.startDelay(role.getEntitySupport()); + ModelComponent modelComponent = store.getComponent(ref, ModelComponent.getComponentType()); + float eyeHeight = modelComponent != null ? modelComponent.getModel().getEyeHeight(ref, store) : 0.0F; + float height = -eyeHeight; + if (this.item != null) { + this.newDirection(ref, this.pickDistance(), height, store); + ItemStack drop = InventoryHelper.createItem(this.item); + if (drop != null) { + ItemUtils.throwItem(ref, store, drop, this.dropDirection, this.throwSpeed); + } + + return true; + } else { + ItemModule itemModule = ItemModule.get(); + if (itemModule.isEnabled()) { + for (ItemStack randomItem : itemModule.getRandomItemDrops(this.dropList)) { + this.newDirection(ref, this.pickDistance(), height, store); + ItemUtils.throwItem(ref, store, randomItem, this.dropDirection, this.throwSpeed); + } + } + + return true; + } + } + + protected double pickDistance() { + return RandomExtra.randomRange(this.minDistance, this.maxDistance); + } + + protected void newDirection(@Nonnull Ref ref, double distance, double height, @Nonnull ComponentAccessor componentAccessor) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = componentAccessor.getComponent(ref, HeadRotation.getComponentType()); + Vector3d direction; + if (headRotationComponent != null) { + direction = headRotationComponent.getDirection(); + } else { + direction = transformComponent.getRotation().toVector3d(); + } + + this.dropDirection.assign(direction); + this.dropDirection.rotateY(RandomExtra.randomRange(this.dropSectorStart, this.dropSectorEnd)); + if (!AimingHelper.computePitch(distance, height, this.throwSpeed, 32.0, this.pitch)) { + throw new IllegalStateException( + String.format( + "Error in computing pitch with distance %s, height %s, and speed %s that was not caught in validation", distance, height, this.throwSpeed + ) + ); + } else { + float heading = PhysicsMath.headingFromDirection(this.dropDirection.x, this.dropDirection.z); + PhysicsMath.vectorFromAngles(heading, this.highPitch ? this.pitch[1] : this.pitch[0], this.dropDirection).normalize(); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/items/ActionInventory.java b/src/com/hypixel/hytale/server/npc/corecomponents/items/ActionInventory.java new file mode 100644 index 0000000..91cd268 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/items/ActionInventory.java @@ -0,0 +1,168 @@ +package com.hypixel.hytale.server.npc.corecomponents.items; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.item.config.Item; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; +import com.hypixel.hytale.server.core.modules.item.ItemModule; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.items.builders.BuilderActionInventory; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.InventoryHelper; +import java.util.EnumSet; +import java.util.function.Supplier; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionInventory extends ActionBase { + private static final EnumSet ITEM_FREE_OPERATIONS = EnumSet.of( + ActionInventory.Operation.ClearHeldItem, + ActionInventory.Operation.RemoveHeldItem, + ActionInventory.Operation.EquipHotbar, + ActionInventory.Operation.EquipOffHand + ); + protected final ActionInventory.Operation operation; + protected final String item; + protected final int count; + protected final boolean useTarget; + protected final byte slot; + + public ActionInventory(@Nonnull BuilderActionInventory builder, @Nonnull BuilderSupport support) { + super(builder); + this.operation = builder.getOperation(support); + this.count = builder.getCount(support); + this.item = builder.getItem(support); + this.useTarget = builder.getUseTarget(support); + this.slot = (byte)builder.getSlot(support); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return !super.canExecute(ref, role, sensorInfo, dt, store) + ? false + : (!this.useTarget || sensorInfo != null && sensorInfo.hasPosition()) + && (ITEM_FREE_OPERATIONS.contains(this.operation) || this.item != null && !this.item.isEmpty()); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, @Nonnull InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + Ref entityRef = this.useTarget ? sensorInfo.getPositionProvider().getTarget() : ref; + LivingEntity entity = (LivingEntity)EntityUtils.getEntity(entityRef, store); + if (entity == null) { + return false; + } else { + Inventory inventory = entity.getInventory(); + if (this.operation == ActionInventory.Operation.ClearHeldItem) { + InventoryHelper.clearItemInHand(inventory, (byte)-1); + return true; + } else if (this.operation == ActionInventory.Operation.RemoveHeldItem) { + InventoryHelper.removeItemInHand(inventory); + return true; + } else if (this.operation != ActionInventory.Operation.EquipHotbar || this.item != null && !this.item.isEmpty()) { + if (this.operation != ActionInventory.Operation.EquipOffHand || this.item != null && !this.item.isEmpty()) { + String itemStackKey = this.item; + if (itemStackKey != null && !"Empty".equals(itemStackKey) && !"Unknown".equals(itemStackKey) && ItemModule.exists(itemStackKey)) { + CombinedItemContainer combinedStorage = inventory.getCombinedHotbarFirst(); + ItemStack itemStack = new ItemStack(itemStackKey, this.count); + switch (this.operation) { + case Add: + if (this.count > 0) { + combinedStorage.addItemStack(itemStack); + } + break; + case Remove: + if (this.count > 0) { + combinedStorage.removeItemStack(itemStack); + } + break; + case Equip: + Item item = itemStack.getItem(); + if (item.getArmor() != null) { + InventoryHelper.useArmor(inventory.getArmor(), itemStack); + } else { + InventoryHelper.useItem(inventory, item.getId()); + } + case ClearHeldItem: + case RemoveHeldItem: + default: + break; + case SetHotbar: + if (InventoryHelper.checkHotbarSlot(inventory, this.slot)) { + inventory.getHotbar().setItemStackForSlot(this.slot, itemStack); + } + break; + case EquipHotbar: + if (InventoryHelper.checkHotbarSlot(inventory, this.slot)) { + inventory.getHotbar().setItemStackForSlot(this.slot, itemStack); + } + + if (inventory.getActiveHotbarSlot() != this.slot && InventoryHelper.checkHotbarSlot(inventory, this.slot)) { + inventory.setActiveHotbarSlot(this.slot); + } + break; + case SetOffHand: + if (InventoryHelper.checkOffHandSlot(inventory, this.slot)) { + inventory.getUtility().setItemStackForSlot(this.slot, itemStack); + } + break; + case EquipOffHand: + if (InventoryHelper.checkOffHandSlot(inventory, this.slot)) { + inventory.getUtility().setItemStackForSlot(this.slot, itemStack); + } + + InventoryHelper.setOffHandSlot(inventory, this.slot); + } + + return true; + } else { + NPCPlugin.get().getLogger().at(Level.WARNING).log("Unknown item %s in Inventory action", this.item); + return true; + } + } else { + InventoryHelper.setOffHandSlot(inventory, this.slot); + return true; + } + } else if (inventory.getActiveHotbarSlot() == this.slot) { + return true; + } else { + if (InventoryHelper.checkHotbarSlot(inventory, this.slot)) { + inventory.setActiveHotbarSlot(this.slot); + } + + return true; + } + } + } + + public static enum Operation implements Supplier { + Add("Add items to inventory"), + Remove("Remove items from inventory"), + Equip("Equip item as weapon or armour"), + ClearHeldItem("Clear the held item"), + RemoveHeldItem("Destroy the held item"), + SetHotbar("Sets the hotbar item in a specific slot"), + EquipHotbar("Equips the item from a specific hotbar slot"), + SetOffHand("Sets the off-hand item in a specific slot"), + EquipOffHand("Equips the item from a specific off-hand slot"); + + private final String description; + + private Operation(String description) { + this.description = description; + } + + public String get() { + return this.description; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/items/ActionPickUpItem.java b/src/com/hypixel/hytale/server/npc/corecomponents/items/ActionPickUpItem.java new file mode 100644 index 0000000..3cb6be5 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/items/ActionPickUpItem.java @@ -0,0 +1,156 @@ +package com.hypixel.hytale.server.npc.corecomponents.items; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.inventory.Inventory; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionWithDelay; +import com.hypixel.hytale.server.npc.corecomponents.items.builders.BuilderActionPickUpItem; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.InventoryHelper; +import java.util.List; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionPickUpItem extends ActionWithDelay { + protected static final ComponentType ITEM_COMPONENT_TYPE = ItemComponent.getComponentType(); + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected final double range; + protected final ActionPickUpItem.StorageTarget storageTarget; + protected final boolean hoover; + @Nullable + protected final List hooverItems; + + public ActionPickUpItem(@Nonnull BuilderActionPickUpItem builder, @Nonnull BuilderSupport support) { + super(builder, support); + this.range = builder.getRange(support); + this.storageTarget = builder.getStorageTarget(support); + this.hoover = builder.getHoover(); + String[] items = builder.getItems(support); + this.hooverItems = items != null ? List.of(items) : null; + } + + @Override + public void registerWithSupport(@Nonnull Role role) { + role.getPositionCache().requireDroppedItemDistance(this.range); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, double dt, @Nonnull Store store) { + if (!super.canExecute(ref, role, sensorInfo, dt, store)) { + return false; + } else { + if (!this.hoover) { + if (sensorInfo == null || !sensorInfo.hasPosition()) { + return false; + } + + Ref targetRef = sensorInfo.getPositionProvider().getTarget(); + if (targetRef == null) { + return false; + } + + ItemComponent itemComponent = store.getComponent(targetRef, ITEM_COMPONENT_TYPE); + if (itemComponent == null || !itemComponent.canPickUp()) { + return false; + } + + TransformComponent selfTransformComponent = store.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + + assert selfTransformComponent != null; + + Vector3d selfPosition = selfTransformComponent.getPosition(); + TransformComponent targetTransformComponent = store.getComponent(targetRef, TRANSFORM_COMPONENT_TYPE); + + assert targetTransformComponent != null; + + Vector3d targetPosition = targetTransformComponent.getPosition(); + double distanceSquared = selfPosition.distanceSquaredTo(targetPosition); + if (distanceSquared > this.range * this.range) { + return false; + } + } else if (role.getPositionCache().getDroppedItemList().isEmpty()) { + return false; + } + + return !this.isDelaying(); + } + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + Ref itemRef = null; + if (!this.hoover) { + if (sensorInfo != null) { + itemRef = sensorInfo.getPositionProvider().getTarget(); + } + } else { + itemRef = role.getPositionCache().getClosestDroppedItemInRange(ref, 0.0, this.range, ActionPickUpItem::filterItem, role, this, store); + } + + this.prepareDelay(); + this.startDelay(role.getEntitySupport()); + if (itemRef == null) { + return false; + } else { + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + Inventory inventory = npcComponent.getInventory(); + switch (this.storageTarget) { + case Hotbar: + ItemComponent.addToItemContainer(store, itemRef, inventory.getCombinedHotbarFirst()); + break; + case Inventory: + ItemComponent.addToItemContainer(store, itemRef, inventory.getCombinedStorageFirst()); + break; + case Destroy: + store.removeEntity(itemRef, RemoveReason.REMOVE); + } + + return true; + } + } + + protected boolean filterItem(@Nonnull Ref ref, Role role, @Nonnull ComponentAccessor componentAccessor) { + if (!ref.isValid()) { + return false; + } else if (this.hooverItems == null) { + return true; + } else { + ItemComponent itemComponent = componentAccessor.getComponent(ref, ITEM_COMPONENT_TYPE); + + assert itemComponent != null; + + return InventoryHelper.matchesItem(this.hooverItems, itemComponent.getItemStack()); + } + } + + public static enum StorageTarget implements Supplier { + Hotbar("Prioritise hotbar"), + Inventory("Prioritise inventory"), + Destroy("Destroy the item"); + + private final String description; + + private StorageTarget(String description) { + this.description = description; + } + + public String get() { + return this.description; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/items/SensorDroppedItem.java b/src/com/hypixel/hytale/server/npc/corecomponents/items/SensorDroppedItem.java new file mode 100644 index 0000000..438ab44 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/items/SensorDroppedItem.java @@ -0,0 +1,131 @@ +package com.hypixel.hytale.server.npc.corecomponents.items; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.items.builders.BuilderSensorDroppedItem; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.EntityPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.InventoryHelper; +import com.hypixel.hytale.server.npc.util.NPCPhysicsMath; +import java.util.EnumSet; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SensorDroppedItem extends SensorBase { + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + @Nullable + protected final List items; + @Nonnull + protected final EnumSet attitudes; + protected final double range; + protected final float viewCone; + protected final boolean hasLineOfSight; + protected float heading; + protected final EntityPositionProvider positionProvider = new EntityPositionProvider(); + + public SensorDroppedItem(@Nonnull BuilderSensorDroppedItem builder, @Nonnull BuilderSupport support) { + super(builder); + String[] itemArray = builder.getItems(support); + this.items = itemArray != null ? List.of(itemArray) : null; + this.attitudes = builder.getAttitudes(support); + this.range = builder.getRange(support); + this.viewCone = builder.getViewSectorRadians(support); + this.hasLineOfSight = builder.getHasLineOfSight(support); + } + + @Override + public void registerWithSupport(@Nonnull Role role) { + role.getPositionCache().requireDroppedItemDistance(this.range); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + this.positionProvider.clear(); + return false; + } else { + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + this.heading = headRotationComponent.getRotation().getYaw(); + Ref droppedItem = role.getPositionCache() + .getClosestDroppedItemInRange( + ref, + 0.0, + this.range, + (sensorDroppedItem, itemRef, role1, componentAccessor) -> sensorDroppedItem.filterItem(ref, itemRef, role1, componentAccessor), + role, + this, + store + ); + if (droppedItem == null) { + this.positionProvider.clear(); + return false; + } else { + this.positionProvider.setTarget(droppedItem, store); + return true; + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } + + protected boolean filterItem( + @Nonnull Ref ref, @Nonnull Ref itemRef, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor + ) { + if (!itemRef.isValid()) { + return false; + } else { + if (this.viewCone > 0.0F) { + TransformComponent selfTransformComponent = componentAccessor.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + + assert selfTransformComponent != null; + + Vector3d selfPosition = selfTransformComponent.getPosition(); + TransformComponent itemTransformComponent = componentAccessor.getComponent(itemRef, TRANSFORM_COMPONENT_TYPE); + + assert itemTransformComponent != null; + + Vector3d itemPosition = itemTransformComponent.getPosition(); + if (!NPCPhysicsMath.inViewSector(selfPosition.getX(), selfPosition.getZ(), this.heading, this.viewCone, itemPosition.getX(), itemPosition.getZ())) { + return false; + } + } + + if (this.hasLineOfSight && !role.getPositionCache().hasLineOfSight(ref, itemRef, componentAccessor)) { + return false; + } else if (this.items == null && this.attitudes.isEmpty()) { + return true; + } else { + ItemComponent itemComponent = componentAccessor.getComponent(itemRef, ItemComponent.getComponentType()); + + assert itemComponent != null; + + ItemStack itemStack = itemComponent.getItemStack(); + if (InventoryHelper.matchesItem(this.items, itemStack)) { + return true; + } else { + Attitude attitude = role.getWorldSupport().getItemAttitude(itemStack); + return attitude != null && this.attitudes.contains(attitude); + } + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/items/builders/BuilderActionDropItem.java b/src/com/hypixel/hytale/server/npc/corecomponents/items/builders/BuilderActionDropItem.java new file mode 100644 index 0000000..75648a3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/items/builders/BuilderActionDropItem.java @@ -0,0 +1,163 @@ +package com.hypixel.hytale.server.npc.corecomponents.items.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.ItemDropListExistsValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.ItemExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionWithDelay; +import com.hypixel.hytale.server.npc.corecomponents.items.ActionDropItem; +import com.hypixel.hytale.server.npc.instructions.Action; +import com.hypixel.hytale.server.npc.util.AimingHelper; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; + +public class BuilderActionDropItem extends BuilderActionWithDelay { + public static final double[] DEFAULT_THROW_DISTANCE = new double[]{1.0, 1.0}; + public static final double[] DEFAULT_DROP_SECTOR = new double[]{0.0, 0.0}; + protected final AssetHolder item = new AssetHolder(); + protected final AssetHolder dropList = new AssetHolder(); + protected float throwSpeed; + protected double[] distance; + protected double[] dropSector; + protected boolean highPitch; + + public BuilderActionDropItem() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Drop an item"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Drop an item. Can be a specific item, or from a drop table"; + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionDropItem(this, builderSupport); + } + + @Nonnull + public BuilderActionDropItem readConfig(@Nonnull JsonElement data) { + this.getAsset( + data, + "Item", + this.item, + null, + ItemExistsValidator.withConfig(AssetValidator.CanBeEmpty), + BuilderDescriptorState.Stable, + "A specific item to drop", + null + ); + this.getAsset( + data, + "DropList", + this.dropList, + null, + ItemDropListExistsValidator.withConfig(AssetValidator.CanBeEmpty), + BuilderDescriptorState.Stable, + "A reference to an item drop list", + null + ); + this.getFloat( + data, + "ThrowSpeed", + s -> this.throwSpeed = s, + 1.0F, + DoubleRangeValidator.fromExclToIncl(0.0, Float.MAX_VALUE), + BuilderDescriptorState.Stable, + "The throw speed to use", + null + ); + this.getDoubleRange( + data, + "Distance", + s -> this.distance = s, + DEFAULT_THROW_DISTANCE, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "The range from which to pick a distance to throw the item", + null + ); + this.getDoubleRange( + data, + "DropSector", + s -> this.dropSector = s, + DEFAULT_DROP_SECTOR, + DoubleSequenceValidator.betweenWeaklyMonotonic(-360.0, 360.0), + BuilderDescriptorState.Stable, + "The sector to spread drops in relative to view direction of NPC", + "The sector to spread drops in relative to view direction of NPC in degrees." + ); + this.getBoolean(data, "PitchHigh", v -> this.highPitch = v, false, BuilderDescriptorState.Stable, "Whether to pitch high or pitch low instead", null); + this.validateOneSetAsset(this.item, this.dropList); + return this; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Override + public boolean validate( + String configName, @Nonnull NPCLoadTimeValidationHelper validationHelper, ExecutionContext context, Scope globalScope, @Nonnull List errors + ) { + boolean result = super.validate(configName, validationHelper, context, globalScope, errors); + Model model = validationHelper.getSpawnModel(); + float height = model.getEyeHeight(); + double newThrowSpeed = AimingHelper.ensurePossibleThrowSpeed(this.distance[1], height, 32.0, this.throwSpeed); + if (newThrowSpeed > this.throwSpeed) { + errors.add( + String.format( + "%s: Throw speed %.2f is too low to achieve distance of %.2f in DropItem action. Needs to be at least %.2f", + configName, + this.throwSpeed, + this.distance[1], + Math.ceil(newThrowSpeed * 100.0) / 100.0 + ) + ); + result = false; + } + + return result; + } + + public String getItem(@Nonnull BuilderSupport support) { + return this.item.get(support.getExecutionContext()); + } + + public String getDropList(@Nonnull BuilderSupport support) { + return this.dropList.get(support.getExecutionContext()); + } + + public float getThrowSpeed() { + return this.throwSpeed; + } + + public double[] getDropSectorRadians() { + return this.dropSector; + } + + public double[] getDistance() { + return this.distance; + } + + public boolean isHighPitch() { + return this.highPitch; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/items/builders/BuilderActionInventory.java b/src/com/hypixel/hytale/server/npc/corecomponents/items/builders/BuilderActionInventory.java new file mode 100644 index 0000000..b1962be --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/items/builders/BuilderActionInventory.java @@ -0,0 +1,134 @@ +package com.hypixel.hytale.server.npc.corecomponents.items.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.IntHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.IntSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.ItemExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.items.ActionInventory; +import com.hypixel.hytale.server.npc.instructions.Action; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; + +public class BuilderActionInventory extends BuilderActionBase { + protected final EnumHolder operation = new EnumHolder<>(); + protected final AssetHolder item = new AssetHolder(); + protected final IntHolder count = new IntHolder(); + protected final BooleanHolder useTarget = new BooleanHolder(); + protected final IntHolder slot = new IntHolder(); + + public BuilderActionInventory() { + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionInventory(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Add or remove items from inventory."; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Add or remove a number of items from an inventory. Can also be used to equip them."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getEnum( + data, + "Operation", + this.operation, + ActionInventory.Operation.class, + ActionInventory.Operation.Add, + BuilderDescriptorState.Stable, + "Operation to perform", + null + ); + this.getInt(data, "Count", this.count, 1, IntSingleValidator.greater0(), BuilderDescriptorState.Stable, "Number of items to add/remove", null); + this.getAsset( + data, + "Item", + this.item, + "", + ItemExistsValidator.withConfig(AssetValidator.CanBeEmpty), + BuilderDescriptorState.Stable, + "Item type to add, remove, or equip", + null + ); + this.getBoolean(data, "UseTarget", this.useTarget, true, BuilderDescriptorState.Stable, "Use the sensor-provided target for the action", null); + this.getInt( + data, + "Slot", + this.slot, + 0, + null, + BuilderDescriptorState.Stable, + "The hotbar or off-hand slot to affect", + "The hotbar or off-hand to effect. Only valid for Hotbar/OffHand Set/Equip operations" + ); + this.requireFeatureIf(this.useTarget, true, Feature.LiveEntity); + return this; + } + + @Override + public boolean validate( + String configName, @Nonnull NPCLoadTimeValidationHelper validationHelper, ExecutionContext context, Scope globalScope, @Nonnull List errors + ) { + boolean result = super.validate(configName, validationHelper, context, globalScope, errors); + ActionInventory.Operation op = this.operation.get(context); + switch (op) { + case SetHotbar: + case EquipHotbar: + result &= validationHelper.validateHotbarHasSlot(this.slot.get(context), "ActionInventory", errors); + break; + case SetOffHand: + case EquipOffHand: + result &= validationHelper.validateOffHandHasSlot(this.slot.get(context), "ActionInventory", errors); + } + + return result; + } + + public ActionInventory.Operation getOperation(@Nonnull BuilderSupport builderSupport) { + return this.operation.get(builderSupport.getExecutionContext()); + } + + public String getItem(@Nonnull BuilderSupport builderSupport) { + return this.item.get(builderSupport.getExecutionContext()); + } + + public int getCount(@Nonnull BuilderSupport builderSupport) { + return this.count.get(builderSupport.getExecutionContext()); + } + + public boolean getUseTarget(@Nonnull BuilderSupport support) { + return this.useTarget.get(support.getExecutionContext()); + } + + public int getSlot(@Nonnull BuilderSupport support) { + return this.slot.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/items/builders/BuilderActionPickUpItem.java b/src/com/hypixel/hytale/server/npc/corecomponents/items/builders/BuilderActionPickUpItem.java new file mode 100644 index 0000000..392fdc5 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/items/builders/BuilderActionPickUpItem.java @@ -0,0 +1,106 @@ +package com.hypixel.hytale.server.npc.corecomponents.items.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.ItemExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionWithDelay; +import com.hypixel.hytale.server.npc.corecomponents.items.ActionPickUpItem; +import com.hypixel.hytale.server.npc.instructions.Action; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class BuilderActionPickUpItem extends BuilderActionWithDelay { + protected final DoubleHolder range = new DoubleHolder(); + protected final EnumHolder pickupTarget = new EnumHolder<>(); + protected AssetArrayHolder items = new AssetArrayHolder(); + protected boolean hoover; + + public BuilderActionPickUpItem() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Pick up an item"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Pick up an item. In hoover mode, will match to the Item array. Otherwise, requires a target to be provided e.g. by a DroppedItemSensor"; + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionPickUpItem(this, builderSupport); + } + + @Nonnull + public BuilderActionPickUpItem readConfig(@Nonnull JsonElement data) { + this.getDouble( + data, "Range", this.range, 1.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "The range the item will be picked up from", null + ); + this.getEnum( + data, + "StorageTarget", + this.pickupTarget, + ActionPickUpItem.StorageTarget.class, + ActionPickUpItem.StorageTarget.Hotbar, + BuilderDescriptorState.Experimental, + "Where to prioritise putting the item", + null + ); + this.getBoolean( + data, + "Hoover", + s -> this.hoover = s, + false, + BuilderDescriptorState.Stable, + "Suck up all items in range", + "Suck up all items in range with optional cooldown. Can be filtered with a list of glob patterns. Ignored outside hoover mode" + ); + this.getAssetArray( + data, + "Items", + this.items, + null, + 0, + Integer.MAX_VALUE, + ItemExistsValidator.withConfig(EnumSet.of(AssetValidator.Config.LIST_NULLABLE, AssetValidator.Config.LIST_CAN_BE_EMPTY, AssetValidator.Config.MATCHER)), + BuilderDescriptorState.Stable, + "A list of glob item patterns to match", + "A list of glob item patterns to match for hoover mode. If omitted, will match any item. Ignored outside hoover mode" + ); + this.requireFeatureIf("Hoover", false, this.hoover, EnumSet.of(Feature.Drop)); + return this; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + public String[] getItems(BuilderSupport support) { + return this.items.get(support.getExecutionContext()); + } + + public boolean getHoover() { + return this.hoover; + } + + public double getRange(@Nonnull BuilderSupport support) { + return this.range.get(support.getExecutionContext()); + } + + public ActionPickUpItem.StorageTarget getStorageTarget(@Nonnull BuilderSupport support) { + return this.pickupTarget.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/items/builders/BuilderSensorDroppedItem.java b/src/com/hypixel/hytale/server/npc/corecomponents/items/builders/BuilderSensorDroppedItem.java new file mode 100644 index 0000000..5cd03b6 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/items/builders/BuilderSensorDroppedItem.java @@ -0,0 +1,128 @@ +package com.hypixel.hytale.server.npc.corecomponents.items.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumSetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.FloatHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.ItemExistsValidator; +import com.hypixel.hytale.server.npc.config.ItemAttitudeGroup; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.items.SensorDroppedItem; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import java.util.EnumSet; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderSensorDroppedItem extends BuilderSensorBase { + protected final DoubleHolder range = new DoubleHolder(); + protected final FloatHolder viewSector = new FloatHolder(); + protected final BooleanHolder hasLineOfSight = new BooleanHolder(); + protected final AssetArrayHolder items = new AssetArrayHolder(); + protected final EnumSetHolder attitudes = new EnumSetHolder<>(); + + public BuilderSensorDroppedItem() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Triggers if a given item is within a certain range of the NPC."; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Triggers if a given item is within a certain range of the NPC. Will match anything if Items and Attitudes are not defined, otherwise will match items meeting either criteria."; + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorDroppedItem(this, builderSupport); + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireDouble(data, "Range", this.range, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "The range within which to look", null); + this.getFloat( + data, + "ViewSector", + this.viewSector, + 0.0, + DoubleRangeValidator.between(0.0, 360.0), + BuilderDescriptorState.Stable, + "View sector in which to look", + null + ); + this.getBoolean(data, "LineOfSight", this.hasLineOfSight, false, BuilderDescriptorState.Stable, "Requires line of sight to item", null); + this.getAssetArray( + data, + "Items", + this.items, + null, + 0, + Integer.MAX_VALUE, + ItemExistsValidator.withConfig(EnumSet.of(AssetValidator.Config.LIST_NULLABLE, AssetValidator.Config.LIST_CAN_BE_EMPTY, AssetValidator.Config.MATCHER)), + BuilderDescriptorState.Stable, + "A list of glob item patterns to match. If omitted, will match any item", + null + ); + this.getEnumSet( + data, + "Attitudes", + this.attitudes, + ItemAttitudeGroup.Sentiment.class, + EnumSet.noneOf(ItemAttitudeGroup.Sentiment.class), + BuilderDescriptorState.Stable, + "Attitudes to match", + null + ); + this.provideFeature(Feature.Drop); + return this; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + public double getRange(@Nonnull BuilderSupport support) { + return this.range.get(support.getExecutionContext()); + } + + public float getViewSectorRadians(@Nonnull BuilderSupport builderSupport) { + return (float) (Math.PI / 180.0) * this.viewSector.get(builderSupport.getExecutionContext()); + } + + public boolean getHasLineOfSight(@Nonnull BuilderSupport support) { + return this.hasLineOfSight.get(support.getExecutionContext()); + } + + @Nullable + public String[] getItems(@Nonnull BuilderSupport support) { + return this.items.get(support.getExecutionContext()); + } + + @Nonnull + public EnumSet getAttitudes(@Nonnull BuilderSupport support) { + EnumSet set = this.attitudes.get(support.getExecutionContext()); + EnumSet attitudes = EnumSet.noneOf(Attitude.class); + + for (ItemAttitudeGroup.Sentiment sentiment : set) { + attitudes.add(sentiment.getAttitude()); + } + + return attitudes; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionDelayDespawn.java b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionDelayDespawn.java new file mode 100644 index 0000000..a07eb15 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionDelayDespawn.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.server.npc.corecomponents.lifecycle; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionDelayDespawn; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionDelayDespawn extends ActionBase { + protected final float time; + protected final boolean shorten; + + public ActionDelayDespawn(@Nonnull BuilderActionDelayDespawn builderActionDelayDespawn) { + super(builderActionDelayDespawn); + this.time = builderActionDelayDespawn.getTime(); + this.shorten = builderActionDelayDespawn.getShorten(); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + NPCEntity npcEntity = store.getComponent(ref, NPCEntity.getComponentType()); + + assert npcEntity != null; + + double delay = npcEntity.getDespawnTime(); + if (this.shorten && delay < this.time || !this.shorten && delay > this.time) { + npcEntity.setDespawnTime(this.time); + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionDespawn.java b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionDespawn.java new file mode 100644 index 0000000..65b66fb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionDespawn.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.server.npc.corecomponents.lifecycle; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionDespawn; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionDespawn extends ActionBase { + protected final boolean force; + + public ActionDespawn(@Nonnull BuilderActionDespawn builderActionDespawn) { + super(builderActionDespawn); + this.force = builderActionDespawn.isForced(); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + if (this.force) { + store.removeEntity(ref, RemoveReason.REMOVE); + } else { + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + npcComponent.setToDespawn(); + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionDie.java b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionDie.java new file mode 100644 index 0000000..9b072e6 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionDie.java @@ -0,0 +1,27 @@ +package com.hypixel.hytale.server.npc.corecomponents.lifecycle; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.entity.damage.Damage; +import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionDie; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionDie extends ActionBase { + public ActionDie(@Nonnull BuilderActionDie builder) { + super(builder); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + DeathComponent.tryAddComponent(store, ref, new Damage(Damage.NULL_SOURCE, DamageCause.PHYSICAL, 0.0F)); + role.setReachedTerminalAction(true); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionRemove.java b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionRemove.java new file mode 100644 index 0000000..a677c08 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionRemove.java @@ -0,0 +1,39 @@ +package com.hypixel.hytale.server.npc.corecomponents.lifecycle; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionRemove; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionRemove extends ActionBase { + protected final boolean useTarget; + + public ActionRemove(@Nonnull BuilderActionRemove builder, @Nonnull BuilderSupport builderSupport) { + super(builder); + this.useTarget = builder.getUseTarget(builderSupport); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && (!this.useTarget || sensorInfo != null && sensorInfo.hasPosition()); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, @Nonnull InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + Ref targetRef = this.useTarget ? sensorInfo.getPositionProvider().getTarget() : ref; + if (!store.getArchetype(targetRef).contains(Player.getComponentType())) { + store.removeEntity(targetRef, RemoveReason.REMOVE); + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionRole.java b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionRole.java new file mode 100644 index 0000000..1913faa --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionRole.java @@ -0,0 +1,57 @@ +package com.hypixel.hytale.server.npc.corecomponents.lifecycle; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionRole; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.systems.RoleChangeSystem; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionRole extends ActionBase { + protected final int roleIndex; + protected final String kind; + protected final boolean changeAppearance; + @Nullable + protected final String state; + @Nullable + protected final String subState; + + public ActionRole(@Nonnull BuilderActionRole builder, @Nonnull BuilderSupport builderSupport) { + super(builder); + this.kind = builder.getRole(builderSupport); + this.roleIndex = NPCPlugin.get().getIndex(this.kind); + this.changeAppearance = builder.getChangeAppearance(builderSupport); + String stateString = builder.getState(builderSupport); + if (stateString != null) { + String[] split = stateString.split("\\."); + this.state = split[0]; + this.subState = split.length > 1 && split[1] != null && !split[1].isEmpty() ? split[1] : null; + } else { + this.state = null; + this.subState = null; + } + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && this.roleIndex >= 0; + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + if (role.isRoleChangeRequested()) { + return false; + } else { + RoleChangeSystem.requestRoleChange(ref, role, this.roleIndex, this.changeAppearance, this.state, this.subState, store); + role.setReachedTerminalAction(true); + return true; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionSpawn.java b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionSpawn.java new file mode 100644 index 0000000..708928f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/ActionSpawn.java @@ -0,0 +1,311 @@ +package com.hypixel.hytale.server.npc.corecomponents.lifecycle; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.FlockMembershipSystems; +import com.hypixel.hytale.server.flock.FlockPlugin; +import com.hypixel.hytale.server.flock.config.FlockAsset; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionSpawn; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.movement.controllers.MotionControllerFly; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.systems.NewSpawnStartTickingSystem; +import com.hypixel.hytale.server.npc.util.AimingHelper; +import com.hypixel.hytale.server.spawning.ISpawnableWithModel; +import com.hypixel.hytale.server.spawning.SpawnTestResult; +import com.hypixel.hytale.server.spawning.SpawningContext; +import it.unimi.dsi.fastutil.Pair; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionSpawn extends ActionBase { + protected final float spawnDirection; + protected final float spawnAngle; + protected final boolean fanOut; + protected final double minDistance; + protected final double maxDistance; + protected final String kind; + protected final String flock; + protected final int roleIndex; + protected final int maxCount; + protected final int minCount; + protected final double minDelay; + protected final double maxDelay; + protected final Vector3d position = new Vector3d(); + protected final Vector3f rotation = new Vector3f(); + protected final boolean launchAtTarget; + protected final boolean pitchHigh; + protected final Vector3d targetPosition = new Vector3d(); + protected final Vector3d launchDirection = new Vector3d(); + @Nullable + protected final float[] pitch; + protected final double spread; + protected final boolean joinFlock; + protected final String spawnState; + protected final String spawnSubState; + protected int spawnsLeft; + protected int maxTries; + protected float yaw0; + protected float yawIncrement; + protected double spawnDelay; + protected Ref parent; + + public ActionSpawn(@Nonnull BuilderActionSpawn builderActionSpawn, @Nonnull BuilderSupport builderSupport) { + super(builderActionSpawn); + this.spawnDirection = builderActionSpawn.getSpawnDirection(builderSupport); + this.spawnAngle = builderActionSpawn.getSpawnAngle(builderSupport); + this.fanOut = builderActionSpawn.isFanOut(builderSupport); + double[] distanceRange = builderActionSpawn.getDistanceRange(builderSupport); + this.minDistance = distanceRange[0]; + this.maxDistance = distanceRange[1]; + int[] countRange = builderActionSpawn.getCountRange(builderSupport); + this.minCount = countRange[0]; + this.maxCount = countRange[1]; + double[] delayRange = builderActionSpawn.getDelayRange(builderSupport); + this.minDelay = delayRange[0]; + this.maxDelay = delayRange[1]; + this.kind = builderActionSpawn.getKind(builderSupport); + this.flock = builderActionSpawn.getFlock(builderSupport); + this.roleIndex = NPCPlugin.get().getIndex(this.kind); + this.launchAtTarget = builderActionSpawn.isLaunchAtTarget(builderSupport); + this.pitchHigh = builderActionSpawn.isPitchHigh(builderSupport); + this.spread = builderActionSpawn.getSpread(builderSupport); + this.joinFlock = builderActionSpawn.isJoinFlock(builderSupport); + this.pitch = this.launchAtTarget ? new float[2] : null; + this.spawnState = builderActionSpawn.getSpawnState(builderSupport); + this.spawnSubState = builderActionSpawn.getSpawnSubState(builderSupport); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, double dt, @Nonnull Store store) { + if (super.canExecute(ref, role, sensorInfo, dt, store) && this.roleIndex >= 0 && this.spawnsLeft <= 0) { + if (NPCPlugin.get().tryGetCachedValidRole(this.roleIndex) == null) { + NPCPlugin.get() + .getLogger() + .at(Level.SEVERE) + .log("NPC of type '%s': Unable to spawn NPC of type '%s' from Action Spawn", role.getRoleName(), this.kind); + this.setOnce(); + this.once = true; + return false; + } else { + return !this.launchAtTarget ? true : sensorInfo != null && sensorInfo.hasPosition(); + } + } else { + return false; + } + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, @Nonnull InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + this.spawnsLeft = RandomExtra.randomRange(this.minCount, this.maxCount); + if (this.spawnsLeft == 0) { + return true; + } else { + this.maxTries = this.spawnsLeft * 5; + if (this.launchAtTarget) { + sensorInfo.getPositionProvider().providePosition(this.targetPosition); + } else if (this.fanOut) { + if (this.spawnsLeft == 1) { + this.yaw0 = this.spawnDirection; + } else { + this.yaw0 = this.spawnDirection - this.spawnAngle / 2.0F; + this.yawIncrement = this.spawnAngle / (this.spawnsLeft - 1); + } + } else { + this.yaw0 = this.spawnDirection - this.spawnAngle / 2.0F; + } + + SpawningContext spawningContext = new SpawningContext(); + this.parent = ref; + Builder roleBuilder = NPCPlugin.get().tryGetCachedValidRole(this.roleIndex); + if ((roleBuilder.isSpawnable() || roleBuilder instanceof ISpawnableWithModel) && spawningContext.setSpawnable((ISpawnableWithModel)roleBuilder)) { + while (this.spawnsLeft > 0) { + if (this.trySpawn(ref, spawningContext, store)) { + if (--this.spawnsLeft == 0) { + return true; + } + + if (this.minDelay > 0.0 || this.maxDelay > 0.0) { + break; + } + + spawningContext.newModel(); + } + + if (--this.maxTries == 0) { + this.spawnsLeft = 0; + return true; + } + } + + this.spawnDelay = RandomExtra.randomRange(this.minDelay, this.maxDelay); + role.addDeferredAction(this::deferredSpawning); + return true; + } else { + this.spawnsLeft = 0; + return true; + } + } + } + + protected boolean trySpawn(@Nonnull Ref ref, @Nonnull SpawningContext spawningContext, @Nonnull Store store) { + World world = store.getExternalData().getWorld(); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + ModelComponent modelComponent = store.getComponent(ref, ModelComponent.getComponentType()); + Vector3d position = transformComponent.getPosition(); + Vector3f bodyRotation = transformComponent.getRotation(); + double x; + double z; + double y; + if (this.launchAtTarget) { + float eyeHeight = modelComponent != null ? modelComponent.getModel().getEyeHeight() : 0.0F; + x = position.getX(); + z = position.getZ(); + y = position.getY() + eyeHeight; + } else { + double distance = RandomExtra.randomRange(this.minDistance, this.maxDistance); + float yaw = bodyRotation.getYaw() + this.yaw0; + if (!this.fanOut) { + yaw += RandomExtra.randomRange(0.0F, this.spawnAngle); + } + + x = position.getX() + PhysicsMath.headingX(yaw) * distance; + z = position.getZ() + PhysicsMath.headingZ(yaw) * distance; + y = position.getY(); + } + + if (spawningContext.set(world, x, y, z) && spawningContext.canSpawn() == SpawnTestResult.TEST_OK) { + this.position.assign(spawningContext.xSpawn, spawningContext.ySpawn, spawningContext.zSpawn); + this.rotation.assign(bodyRotation); + FlockAsset flockDefinition = this.flock != null ? FlockAsset.getAssetMap().getAsset(this.flock) : null; + Pair, NPCEntity> npcPair = NPCPlugin.get() + .spawnEntity(store, this.roleIndex, this.position, this.rotation, spawningContext.getModel(), this::postSpawn); + FlockPlugin.trySpawnFlock(npcPair.first(), npcPair.second(), store, this.roleIndex, this.position, this.rotation, flockDefinition, this::postSpawn); + if (this.fanOut) { + this.yaw0 = this.yaw0 + this.yawIncrement; + } + + return true; + } else { + return false; + } + } + + protected void postSpawn(@Nonnull NPCEntity npcComponent, @Nonnull Ref ref, @Nonnull Store store) { + NPCEntity parentNpcComponent = store.getComponent(this.parent, NPCEntity.getComponentType()); + + assert parentNpcComponent != null; + + this.joinFlock(ref, store); + this.launchAtTarget(ref, store); + if (this.spawnState != null) { + npcComponent.getRole().getStateSupport().setState(ref, this.spawnState, this.spawnSubState, store); + } + + npcComponent.setSpawnConfiguration(parentNpcComponent.getSpawnConfiguration()); + NewSpawnStartTickingSystem.queueNewSpawn(ref, store); + } + + protected void joinFlock(@Nonnull Ref targetRef, @Nonnull Store store) { + if (this.joinFlock) { + NPCEntity parentNpcComponent = store.getComponent(this.parent, NPCEntity.getComponentType()); + + assert parentNpcComponent != null; + + Ref flockReference = FlockPlugin.getFlockReference(this.parent, store); + if (flockReference == null) { + flockReference = FlockPlugin.createFlock(store, parentNpcComponent.getRole()); + FlockMembershipSystems.join(this.parent, flockReference, store); + } + + FlockMembershipSystems.join(targetRef, flockReference, store); + } + } + + protected void launchAtTarget(@Nonnull Ref ref, @Nonnull Store store) { + if (this.launchAtTarget) { + if (this.spread > 0.0) { + this.targetPosition.add(RandomExtra.randomRange(-this.spread, this.spread), 0.0, RandomExtra.randomRange(-this.spread, this.spread)); + } + + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + Role role = npcComponent.getRole(); + Vector3d position = transformComponent.getPosition(); + this.launchDirection.assign(this.targetPosition).subtract(position).normalize(); + double distance = position.distanceTo(this.targetPosition); + if (role.getActiveMotionController() instanceof MotionControllerFly flyController) { + double endVelocity = flyController.getMinSpeedAfterForceSquared(); + double acceleration = -flyController.getDampingDeceleration(); + double v0 = Math.sqrt(endVelocity - 2.0 * acceleration * distance); + this.launchDirection.scale(v0); + role.forceVelocity(this.launchDirection, null, false); + } else { + double height = this.targetPosition.y - position.y; + double gravity = role.getActiveMotionController().getGravity() * 5.0; + double throwSpeed = AimingHelper.ensurePossibleThrowSpeed(distance, height, gravity, 1.0); + if (!AimingHelper.computePitch(distance, height, throwSpeed, gravity, this.pitch)) { + throw new IllegalStateException( + String.format( + "Error in computing pitch with distance %s, height %s, and speed %s despite ensuring possible throw speed", distance, height, throwSpeed + ) + ); + } else { + float heading = PhysicsMath.headingFromDirection(this.launchDirection.x, this.launchDirection.z); + PhysicsMath.vectorFromAngles(heading, this.pitchHigh ? this.pitch[1] : this.pitch[0], this.launchDirection).normalize(); + this.launchDirection.scale(throwSpeed); + role.forceVelocity(this.launchDirection, null, true); + } + } + } + } + + protected boolean deferredSpawning(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + this.spawnDelay -= dt; + if (this.spawnDelay > 0.0) { + return false; + } else { + this.spawnDelay = RandomExtra.randomRange(this.minDelay, this.maxDelay); + SpawningContext spawningContext = new SpawningContext(); + Builder roleBuilder = NPCPlugin.get().tryGetCachedValidRole(this.roleIndex); + if (roleBuilder.isSpawnable() && roleBuilder instanceof ISpawnableWithModel && spawningContext.setSpawnable((ISpawnableWithModel)roleBuilder)) { + while (!this.trySpawn(ref, spawningContext, store)) { + this.maxTries--; + if (this.maxTries <= 0) { + this.spawnsLeft = 0; + return true; + } + } + + this.spawnsLeft--; + return this.spawnsLeft == 0; + } else { + return true; + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/SensorAge.java b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/SensorAge.java new file mode 100644 index 0000000..35a1edd --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/SensorAge.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.server.npc.corecomponents.lifecycle; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderSensorAge; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import java.time.Instant; +import javax.annotation.Nonnull; + +public class SensorAge extends SensorBase { + protected final Instant minAgeInstant; + protected final Instant maxAgeInstant; + + public SensorAge(@Nonnull BuilderSensorAge builderSensorAge, @Nonnull BuilderSupport builderSupport) { + super(builderSensorAge); + Instant[] ageRange = builderSensorAge.getAgeRange(builderSupport); + this.minAgeInstant = ageRange[0]; + this.maxAgeInstant = ageRange[1]; + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + return false; + } else { + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + Instant currentInstant = worldTimeResource.getGameTime(); + return !currentInstant.isBefore(this.minAgeInstant) && !currentInstant.isAfter(this.maxAgeInstant); + } + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionDelayDespawn.java b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionDelayDespawn.java new file mode 100644 index 0000000..8b89793 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionDelayDespawn.java @@ -0,0 +1,63 @@ +package com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.ActionDelayDespawn; +import javax.annotation.Nonnull; + +public class BuilderActionDelayDespawn extends BuilderActionBase { + protected float time; + protected boolean shorten; + + public BuilderActionDelayDespawn() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Delay the despawning cycle for some amount of time"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Delay the despawning cycle for some amount of time"; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public ActionDelayDespawn build(BuilderSupport builderSupport) { + return new ActionDelayDespawn(this); + } + + @Nonnull + public BuilderActionDelayDespawn readConfig(@Nonnull JsonElement data) { + this.requireFloat(data, "Time", d -> this.time = d, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "How long to set the to delay", null); + this.getBoolean( + data, + "Shorten", + b -> this.shorten = b, + false, + BuilderDescriptorState.Stable, + "Set the delay to either the current delay or the given time. Whatever is smaller.", + null + ); + return this; + } + + public float getTime() { + return this.time; + } + + public boolean getShorten() { + return this.shorten; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionDespawn.java b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionDespawn.java new file mode 100644 index 0000000..c828911 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionDespawn.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.ActionDespawn; +import javax.annotation.Nonnull; + +public class BuilderActionDespawn extends BuilderActionBase { + protected boolean force; + + public BuilderActionDespawn() { + } + + @Nonnull + public ActionDespawn build(BuilderSupport builderSupport) { + return new ActionDespawn(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Trigger the NPC to despawn"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Trigger the NPC to start the despawning cycle. If the script contains a despawn sensor it will run that action/motion before removing."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionDespawn readConfig(@Nonnull JsonElement data) { + this.getBoolean(data, "Force", b -> this.force = b, false, BuilderDescriptorState.Stable, "Force the NPC to remove automatically", null); + return this; + } + + public boolean isForced() { + return this.force; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionDie.java b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionDie.java new file mode 100644 index 0000000..8a2de80 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionDie.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.ActionDie; +import javax.annotation.Nonnull; + +public class BuilderActionDie extends BuilderActionBase { + public BuilderActionDie() { + } + + @Nonnull + public ActionDie build(BuilderSupport builderSupport) { + return new ActionDie(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Kill the NPC"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionRemove.java b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionRemove.java new file mode 100644 index 0000000..d5b372b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionRemove.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.ActionRemove; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public class BuilderActionRemove extends BuilderActionBase { + protected final BooleanHolder useTarget = new BooleanHolder(); + + public BuilderActionRemove() { + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionRemove(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Erase the target entity from the world (no death animation)."; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getBoolean(data, "UseTarget", this.useTarget, true, BuilderDescriptorState.Stable, "Use the sensor-provided target for the action", null); + this.requireFeatureIf(this.useTarget, true, Feature.LiveEntity); + return this; + } + + public boolean getUseTarget(@Nonnull BuilderSupport support) { + return this.useTarget.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionRole.java b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionRole.java new file mode 100644 index 0000000..08afb44 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionRole.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DeferEvaluateAssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StateStringValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.RoleExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.ActionRole; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public class BuilderActionRole extends BuilderActionBase { + protected final DeferEvaluateAssetHolder role = new DeferEvaluateAssetHolder(); + protected final BooleanHolder changeAppearance = new BooleanHolder(); + protected final StringHolder state = new StringHolder(); + + public BuilderActionRole() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Change the Role of the NPC"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionRole(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireAsset(data, "Role", this.role, RoleExistsValidator.required(), BuilderDescriptorState.Stable, "The name of the Role to change to", null); + this.getBoolean( + data, "ChangeAppearance", this.changeAppearance, true, BuilderDescriptorState.Stable, "Whether the appearance of the new Role should be used", null + ); + this.getString(data, "State", this.state, null, StateStringValidator.requireMainStateOrNull(), BuilderDescriptorState.Stable, "State name to set", null); + return this; + } + + public String getRole(@Nonnull BuilderSupport support) { + return this.role.get(support.getExecutionContext()); + } + + public boolean getChangeAppearance(@Nonnull BuilderSupport support) { + return this.changeAppearance.get(support.getExecutionContext()); + } + + public String getState(@Nonnull BuilderSupport support) { + return this.state.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionSpawn.java b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionSpawn.java new file mode 100644 index 0000000..ba38f73 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderActionSpawn.java @@ -0,0 +1,229 @@ +package com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.FloatHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.IntSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNullOrNotEmptyValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.FlockAssetExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.ActionSpawn; +import javax.annotation.Nonnull; + +public class BuilderActionSpawn extends BuilderActionBase { + public static final double[] DEFAULT_DISTANCE_RANGE = new double[]{1.0, 1.0}; + public static final int[] DEFAULT_COUNT_RANGE = new int[]{5, 5}; + public static final double[] DEFAULT_DELAY_RANGE = new double[]{0.25, 0.25}; + protected final FloatHolder spawnDirection = new FloatHolder(); + protected final FloatHolder spawnAngle = new FloatHolder(); + protected final BooleanHolder fanOut = new BooleanHolder(); + protected final NumberArrayHolder distanceRange = new NumberArrayHolder(); + protected final NumberArrayHolder countRange = new NumberArrayHolder(); + protected final NumberArrayHolder delayRange = new NumberArrayHolder(); + protected final StringHolder kind = new StringHolder(); + protected final AssetHolder flock = new AssetHolder(); + protected final BooleanHolder launchAtTarget = new BooleanHolder(); + protected final BooleanHolder pitchHigh = new BooleanHolder(); + protected final DoubleHolder spread = new DoubleHolder(); + protected final BooleanHolder joinFlock = new BooleanHolder(); + protected final StringHolder spawnState = new StringHolder(); + protected final StringHolder spawnSubState = new StringHolder(); + + public BuilderActionSpawn() { + } + + @Nonnull + public ActionSpawn build(@Nonnull BuilderSupport builderSupport) { + return new ActionSpawn(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Spawn an NPC"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Spawn an NPC"; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + public BuilderActionSpawn readConfig(@Nonnull JsonElement data) { + this.getFloat( + data, + "SpawnDirection", + this.spawnDirection, + 0.0, + DoubleRangeValidator.between(-180.0, 180.0), + BuilderDescriptorState.Experimental, + "Direction of spawn cone relative to view direction (in degrees)", + null + ); + this.getFloat( + data, + "SpawnAngle", + this.spawnAngle, + 360.0, + DoubleRangeValidator.between(0.0, 360.0), + BuilderDescriptorState.Experimental, + "Cone width of spawn direction (in degrees)", + null + ); + this.getBoolean(data, "FanOut", this.fanOut, false, BuilderDescriptorState.Experimental, "Fan NPCs out equally over angle", null); + this.getDoubleRange( + data, + "DistanceRange", + this.distanceRange, + DEFAULT_DISTANCE_RANGE, + DoubleSequenceValidator.fromExclToInclWeaklyMonotonic(0.0, 128.0), + BuilderDescriptorState.Stable, + "Distance from spawner to spawn", + null + ); + this.getIntRange( + data, + "CountRange", + this.countRange, + DEFAULT_COUNT_RANGE, + IntSequenceValidator.fromExclToInclWeaklyMonotonic(0, 100), + BuilderDescriptorState.Stable, + "Number of NPCs to spawn", + null + ); + this.getDoubleRange( + data, + "DelayRange", + this.delayRange, + DEFAULT_DELAY_RANGE, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "Time between consecutive spawns in seconds", + null + ); + this.requireString(data, "Kind", this.kind, StringNotEmptyValidator.get(), BuilderDescriptorState.Experimental, "NPC role to spawn", null); + this.getAsset( + data, + "Flock", + this.flock, + null, + FlockAssetExistsValidator.withConfig(AssetValidator.CanBeEmpty), + BuilderDescriptorState.Stable, + "Flock definition to spawn", + null + ); + this.getBoolean( + data, "LaunchAtTarget", this.launchAtTarget, false, BuilderDescriptorState.WorkInProgress, "Launch the spawned NPC at target position/entity", null + ); + this.getBoolean(data, "PitchHigh", this.pitchHigh, true, BuilderDescriptorState.Stable, "If launching at a target, use high pitch", null); + this.getDouble( + data, + "LaunchSpread", + this.spread, + 0.0, + DoubleSingleValidator.greaterEqual0(), + BuilderDescriptorState.Stable, + "The radius of the circle centred on the target within which to spread thrown NPCs", + null + ); + this.getBoolean(data, "JoinFlock", this.joinFlock, false, BuilderDescriptorState.Stable, "Whether to join the parent NPC's flock", null); + this.getString( + data, + "SpawnState", + this.spawnState, + null, + StringNullOrNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "An optional state to set on the spawned NPC if it exists", + null + ); + this.getString( + data, + "SpawnSubState", + this.spawnSubState, + null, + StringNullOrNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "An optional substate to set on the spawned NPC if it exists", + null + ); + this.requireFeatureIf(this.launchAtTarget, true, Feature.AnyPosition); + return this; + } + + public float getSpawnDirection(@Nonnull BuilderSupport support) { + float spawnDirection = this.spawnDirection.get(support.getExecutionContext()); + return (float) (Math.PI / 180.0) * (spawnDirection < 0.0F ? spawnDirection + (float) (Math.PI * 2) : spawnDirection); + } + + public float getSpawnAngle(@Nonnull BuilderSupport support) { + return (float) (Math.PI / 180.0) * this.spawnAngle.get(support.getExecutionContext()); + } + + public boolean isFanOut(@Nonnull BuilderSupport support) { + return this.fanOut.get(support.getExecutionContext()); + } + + public double[] getDistanceRange(@Nonnull BuilderSupport support) { + return this.distanceRange.get(support.getExecutionContext()); + } + + public String getKind(@Nonnull BuilderSupport support) { + return this.kind.get(support.getExecutionContext()); + } + + public String getFlock(@Nonnull BuilderSupport support) { + return this.flock.get(support.getExecutionContext()); + } + + public int[] getCountRange(@Nonnull BuilderSupport support) { + return this.countRange.getIntArray(support.getExecutionContext()); + } + + public double[] getDelayRange(@Nonnull BuilderSupport support) { + return this.delayRange.get(support.getExecutionContext()); + } + + public boolean isLaunchAtTarget(@Nonnull BuilderSupport support) { + return this.launchAtTarget.get(support.getExecutionContext()); + } + + public boolean isPitchHigh(@Nonnull BuilderSupport support) { + return this.pitchHigh.get(support.getExecutionContext()); + } + + public double getSpread(@Nonnull BuilderSupport support) { + return this.spread.get(support.getExecutionContext()); + } + + public boolean isJoinFlock(@Nonnull BuilderSupport support) { + return this.joinFlock.get(support.getExecutionContext()); + } + + public String getSpawnState(@Nonnull BuilderSupport support) { + return this.spawnState.get(support.getExecutionContext()); + } + + public String getSpawnSubState(@Nonnull BuilderSupport support) { + return this.spawnSubState.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderSensorAge.java b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderSensorAge.java new file mode 100644 index 0000000..eb89d23 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/lifecycle/builders/BuilderSensorAge.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.TemporalArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.TemporalSequenceValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.lifecycle.SensorAge; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.Period; +import java.time.temporal.TemporalAmount; +import javax.annotation.Nonnull; + +public class BuilderSensorAge extends BuilderSensorBase { + public static final TemporalAmount MIN_TIME = Duration.ZERO; + public static final TemporalAmount MAX_TIME = Period.ofDays(Integer.MAX_VALUE); + protected final TemporalArrayHolder ageRange = new TemporalArrayHolder(); + + public BuilderSensorAge() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Triggers when the age of the NPC falls between a certain range"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Triggers when the age of the NPC falls between a certain range. Range is defined in terms of period (e.g. 1Y2M3W4D - 1 year, 2 months, 3 weeks, 4 days) or duration (e.g. 2DT3H4M - 2 days, 3 hours, 4 minutes)"; + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorAge(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireTemporalRange( + data, + "AgeRange", + this.ageRange, + TemporalSequenceValidator.betweenMonotonic(MIN_TIME, MAX_TIME), + BuilderDescriptorState.Stable, + "The age range within which to trigger", + null + ); + return this; + } + + @Nonnull + public Instant[] getAgeRange(@Nonnull BuilderSupport support) { + Instant spawnInstant = support.getEntity().getSpawnInstant(); + LocalDateTime spawnTime = LocalDateTime.ofInstant(spawnInstant, WorldTimeResource.ZONE_OFFSET); + TemporalAmount[] range = this.ageRange.getTemporalArray(support.getExecutionContext()); + Instant[] ageInstants = new Instant[range.length]; + ageInstants[0] = spawnTime.plus(range[0]).toInstant(WorldTimeResource.ZONE_OFFSET); + ageInstants[1] = spawnTime.plus(range[1]).toInstant(WorldTimeResource.ZONE_OFFSET); + return ageInstants; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/ActionCrouch.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/ActionCrouch.java new file mode 100644 index 0000000..539bc5a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/ActionCrouch.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionCrouch extends ActionBase { + private final boolean crouching; + + public ActionCrouch(@Nonnull BuilderActionBase builderActionBase, boolean crouching) { + super(builderActionBase); + this.crouching = crouching; + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + MovementStatesComponent movementStatesComponent = store.getComponent(ref, MovementStatesComponent.getComponentType()); + if (movementStatesComponent != null) { + movementStatesComponent.getMovementStates().crouching = this.crouching; + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/ActionOverrideAltitude.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/ActionOverrideAltitude.java new file mode 100644 index 0000000..47b9585 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/ActionOverrideAltitude.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderActionOverrideAltitude; +import com.hypixel.hytale.server.npc.movement.controllers.MotionControllerFly; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionOverrideAltitude extends ActionBase { + private final double[] desiredRange; + + public ActionOverrideAltitude(@Nonnull BuilderActionOverrideAltitude builder, @Nonnull BuilderSupport support) { + super(builder); + this.desiredRange = builder.getDesiredAltitudeRange(support); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && "Fly".equals(role.getActiveMotionController().getType()); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + ((MotionControllerFly)role.getActiveMotionController()).setDesiredAltitudeOverride(this.desiredRange); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/ActionRecomputePath.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/ActionRecomputePath.java new file mode 100644 index 0000000..0c9cb7a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/ActionRecomputePath.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderActionRecomputePath; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionRecomputePath extends ActionBase { + public ActionRecomputePath(@Nonnull BuilderActionRecomputePath builder) { + super(builder); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider infoProvider, double dt, @Nonnull Store store) { + super.execute(ref, role, infoProvider, dt, store); + role.getActiveMotionController().setForceRecomputePath(true); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionFind.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionFind.java new file mode 100644 index 0000000..f3733bb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionFind.java @@ -0,0 +1,241 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionFind; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.movement.steeringforces.SteeringForcePursue; +import com.hypixel.hytale.server.npc.navigation.AStarBase; +import com.hypixel.hytale.server.npc.navigation.AStarNode; +import com.hypixel.hytale.server.npc.navigation.AStarWithTarget; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.NPCPhysicsMath; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BodyMotionFind extends BodyMotionFindWithTarget { + protected final double distance; + protected final double distanceSquared; + protected final boolean reachable; + protected final double heightDifferenceMin; + protected final double heightDifferenceMax; + protected final double abortDistance; + protected final double abortDistanceSquared; + protected final double switchToSteeringDistance; + protected final double switchToSteeringDistanceSquared; + protected final SteeringForcePursue seek = new SteeringForcePursue(); + protected final Vector3d tempDirectionVector = new Vector3d(); + protected double effectiveDistanceSquared; + + public BodyMotionFind(@Nonnull BuilderBodyMotionFind builderMotionFind, @Nonnull BuilderSupport support) { + super(builderMotionFind, support); + this.reachable = builderMotionFind.getReachable(support); + this.distance = builderMotionFind.getStopDistance(support); + this.distanceSquared = this.distance * this.distance; + double[] heightDifference = builderMotionFind.getHeightDifference(support); + this.heightDifferenceMin = heightDifference[0]; + this.heightDifferenceMax = heightDifference[1]; + this.seek.setDistances(builderMotionFind.getSlowDownDistance(support), builderMotionFind.getStopDistance(support)); + this.seek.setFalloff(builderMotionFind.getFalloff(support)); + this.abortDistance = builderMotionFind.getAbortDistance(support); + this.abortDistanceSquared = this.abortDistance * this.abortDistance; + this.switchToSteeringDistance = builderMotionFind.getSwitchToSteeringDistance(support); + this.switchToSteeringDistanceSquared = this.switchToSteeringDistance * this.switchToSteeringDistance; + } + + @Override + protected boolean canSwitchToSteering( + @Nonnull Ref ref, @Nonnull MotionController motionController, @Nonnull ComponentAccessor componentAccessor + ) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + if (motionController.waypointDistanceSquared(position, this.getLastTargetPosition()) > this.switchToSteeringDistanceSquared) { + return false; + } else { + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFind: computing canSwitchToSteering"); + } + + return this.canReachTarget( + ref, motionController, position, this.getLastAccessibleTargetPosition(motionController, true, componentAccessor), componentAccessor + ); + } + } + + @Override + protected boolean shouldSkipSteering( + @Nonnull Ref ref, + @Nonnull MotionController activeMotionController, + @Nonnull Vector3d position, + @Nonnull ComponentAccessor componentAccessor + ) { + Vector3d targetPosition = this.getLastAccessibleTargetPosition(activeMotionController, true, componentAccessor); + this.probeMoveData.setPosition(position).setTargetPosition(targetPosition); + activeMotionController.probeMove(ref, this.probeMoveData, componentAccessor); + return !this.isGoalReached(ref, activeMotionController, this.probeMoveData.probePosition, targetPosition, componentAccessor); + } + + @Override + protected boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role role, + @Nonnull Vector3d position, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + this.seek.setPositions(position, this.getLastTargetPosition()); + MotionController motionController = role.getActiveMotionController(); + this.seek.setComponentSelector(motionController.getComponentSelector()); + double desiredAltitudeWeight = this.desiredAltitudeWeight >= 0.0 ? this.desiredAltitudeWeight : motionController.getDesiredAltitudeWeight(); + return this.scaleSteering(ref, role, this.seek, desiredSteering, desiredAltitudeWeight, componentAccessor); + } + + @Override + public boolean canComputeMotion( + @Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider infoProvider, @Nonnull ComponentAccessor componentAccessor + ) { + if (super.canComputeMotion(ref, role, infoProvider, componentAccessor) + && ( + !(this.abortDistance > 0.0) + || !(role.getActiveMotionController().waypointDistanceSquared(ref, this.getLastTargetPosition(), componentAccessor) >= this.abortDistanceSquared) + )) { + if (this.selfBoundingBox != null && this.adjustRangeByHitboxSize) { + double effectiveDistance = this.distance + Math.max(this.selfBoundingBox.width(), this.selfBoundingBox.depth()); + if (this.targetBoundingBox != null) { + effectiveDistance += Math.max(this.targetBoundingBox.width(), this.targetBoundingBox.depth()); + } + + this.effectiveDistanceSquared = effectiveDistance * effectiveDistance; + } else { + this.effectiveDistanceSquared = this.distanceSquared; + } + + return this.selfBoundingBox != null; + } else { + return false; + } + } + + @Override + protected boolean isGoalReached( + @Nonnull Ref ref, + @Nonnull MotionController motionController, + @Nonnull Vector3d position, + @Nonnull Vector3d targetPosition, + @Nonnull ComponentAccessor componentAccessor + ) { + double differenceY = targetPosition.y - position.y; + if (!(differenceY < this.heightDifferenceMin) && !(differenceY > this.heightDifferenceMax)) { + return motionController.waypointDistanceSquared(position, targetPosition) > this.effectiveDistanceSquared + ? false + : !this.reachable || this.canReachTarget(ref, motionController, position, targetPosition, componentAccessor); + } else { + return false; + } + } + + @Override + public boolean isGoalReached( + @Nonnull Ref ref, + AStarBase aStarBase, + @Nonnull AStarNode aStarNode, + @Nonnull MotionController motionController, + @Nonnull ComponentAccessor componentAccessor + ) { + AStarWithTarget aStarWithTarget = (AStarWithTarget)aStarBase; + return this.isGoalReached(ref, motionController, aStarNode.getPosition(), aStarWithTarget.getTargetPosition(), componentAccessor); + } + + @Override + public float estimateToGoal(@Nonnull AStarBase aStarBase, @Nonnull Vector3d fromPosition, MotionController motionController) { + return (float)((AStarWithTarget)aStarBase).getTargetPosition().distanceTo(fromPosition); + } + + @Override + public void findBestPath(@Nonnull AStarBase aStarBase, MotionController controller) { + aStarBase.buildBestPath(AStarNode::getEstimateToGoal, (oldV, v) -> v < oldV, Float.MAX_VALUE); + } + + @Override + protected void onThrottling( + MotionController motionController, @Nonnull Ref ref, @Nonnull Steering steering, @Nonnull ComponentAccessor componentAccessor + ) { + super.onThrottling(motionController, ref, steering, componentAccessor); + this.lookAtTarget(ref, steering, componentAccessor); + } + + @Override + protected void onDeferring( + MotionController motionController, @Nonnull Ref ref, @Nonnull Steering steering, @Nonnull ComponentAccessor componentAccessor + ) { + super.onDeferring(motionController, ref, steering, componentAccessor); + this.lookAtTarget(ref, steering, componentAccessor); + } + + protected void lookAtTarget(@Nonnull Ref ref, @Nonnull Steering steering, @Nonnull ComponentAccessor componentAccessor) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + Vector3f bodyRotation = transformComponent.getRotation(); + this.tempDirectionVector.assign(this.getLastTargetPosition()).subtract(position); + steering.setYaw(NPCPhysicsMath.headingFromDirection(this.tempDirectionVector.x, this.tempDirectionVector.z, bodyRotation.getYaw())); + steering.setPitch( + NPCPhysicsMath.pitchFromDirection(this.tempDirectionVector.x, this.tempDirectionVector.y, this.tempDirectionVector.z, bodyRotation.getPitch()) + ); + } + + protected boolean canReachTarget( + @Nonnull Ref ref, + @Nonnull MotionController motionController, + @Nonnull Vector3d position, + @Nonnull Vector3d targetPosition, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.isBoundingBoxesOverlapping(position, targetPosition)) { + return true; + } else { + Vector3d direction = this.tempDirectionVector.assign(targetPosition).subtract(position); + motionController.probeMove(ref, position, direction, this.probeMoveData, componentAccessor); + return this.isBoundingBoxesOverlapping(this.probeMoveData.probePosition, targetPosition); + } + } + + protected boolean isBoundingBoxesOverlapping(@Nonnull Vector3d position, @Nonnull Vector3d endPosition) { + return this.targetBoundingBox == null + ? this.containsPosition(position, endPosition) + : containsPosition( + position.x, this.selfBoundingBox.min.x - this.targetBoundingBox.max.x, this.selfBoundingBox.max.x - this.targetBoundingBox.min.x, endPosition.x + ) + && containsPosition( + position.y, this.selfBoundingBox.min.y - this.targetBoundingBox.max.y, this.selfBoundingBox.max.y - this.targetBoundingBox.min.y, endPosition.y + ) + && containsPosition( + position.z, this.selfBoundingBox.min.z - this.targetBoundingBox.max.z, this.selfBoundingBox.max.z - this.targetBoundingBox.min.z, endPosition.z + ); + } + + protected boolean containsPosition(@Nonnull Vector3d position, @Nonnull Vector3d endPosition) { + return containsPosition(position.x, this.selfBoundingBox.min.x, this.selfBoundingBox.max.x, endPosition.x) + && containsPosition(position.y, this.selfBoundingBox.min.y, this.selfBoundingBox.max.y, endPosition.y) + && containsPosition(position.z, this.selfBoundingBox.min.z, this.selfBoundingBox.max.z, endPosition.z); + } + + protected static boolean containsPosition(double p, double min, double max, double v) { + v -= p; + return v >= min && v < max; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionFindBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionFindBase.java new file mode 100644 index 0000000..436bce2 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionFindBase.java @@ -0,0 +1,724 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.BodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionFindBase; +import com.hypixel.hytale.server.npc.movement.NavState; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.movement.controllers.ProbeMoveData; +import com.hypixel.hytale.server.npc.movement.steeringforces.SteeringForceWithTarget; +import com.hypixel.hytale.server.npc.navigation.AStarBase; +import com.hypixel.hytale.server.npc.navigation.AStarDebugBase; +import com.hypixel.hytale.server.npc.navigation.AStarEvaluator; +import com.hypixel.hytale.server.npc.navigation.AStarNode; +import com.hypixel.hytale.server.npc.navigation.AStarNodePoolProvider; +import com.hypixel.hytale.server.npc.navigation.AStarNodePoolProviderSimple; +import com.hypixel.hytale.server.npc.navigation.PathFollower; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.RoleDebugFlags; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import java.util.EnumSet; +import java.util.function.Supplier; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class BodyMotionFindBase extends BodyMotionBase implements AStarEvaluator { + protected final int nodesPerTick; + protected final boolean useBestPath; + protected final double throttleDelayMin; + protected final double throttleDelayMax; + protected final int throttleIgnoreCount; + protected final boolean useSteering; + protected final boolean usePathfinder; + protected final boolean skipSteering; + protected final double minPathLength; + protected final double minPathLengthSquared; + protected final boolean canSkipSteering; + protected final boolean isAvoidingBlockDamage; + protected final boolean isRelaxedMoveConstraints; + protected final double desiredAltitudeWeight; + protected final boolean dbgStatus; + protected final boolean dbgProfile; + protected final boolean dbgMaps; + protected final boolean dbgOpens; + protected final boolean dbgPath; + protected final boolean dbgRebuild; + protected final boolean dbgNodes; + protected final boolean dbgStay; + protected final boolean dbgMotionState; + @Nonnull + protected final T aStar; + @Nonnull + protected final AStarDebugBase aStarDebug; + protected final PathFollower pathFollower = new PathFollower(); + protected final ProbeMoveData probeMoveData = new ProbeMoveData(); + protected AStarNodePoolProvider sharedNodePoolProvider; + protected int throttleCount; + protected double throttleTime; + protected double targetDeltaSquared; + protected boolean wasSteering; + protected double throttleDelay; + protected boolean passedWaypoint; + protected boolean wasAvoidingBlockDamage; + protected boolean dbgDisplayString; + protected StringBuilder debugString; + + public BodyMotionFindBase(@Nonnull BuilderBodyMotionFindBase builderBodyMotionFindBase, @Nonnull BuilderSupport support, @Nonnull T aStar) { + super(builderBodyMotionFindBase); + this.aStar = aStar; + this.aStarDebug = aStar.createDebugHelper(HytaleLogger.forEnclosingClass()); + this.useBestPath = builderBodyMotionFindBase.getUseBestPath(support); + this.nodesPerTick = builderBodyMotionFindBase.getNodesPerTick(support); + this.usePathfinder = builderBodyMotionFindBase.isUsePathfinder(support); + this.useSteering = builderBodyMotionFindBase.isUseSteering(support); + this.skipSteering = builderBodyMotionFindBase.isSkipSteering(support); + this.canSkipSteering = this.skipSteering || this.usePathfinder; + this.minPathLength = builderBodyMotionFindBase.getMinPathLength(support); + this.minPathLengthSquared = this.minPathLength * this.minPathLength; + double[] throttleDelayRange = builderBodyMotionFindBase.getThrottleDelayRange(support); + this.throttleDelayMin = throttleDelayRange[0]; + this.throttleDelayMax = throttleDelayRange[1]; + this.throttleIgnoreCount = builderBodyMotionFindBase.getThrottleIgnoreCount(support); + this.isRelaxedMoveConstraints = builderBodyMotionFindBase.isRelaxedMoveConstraints(support); + this.isAvoidingBlockDamage = builderBodyMotionFindBase.isAvoidingBlockDamage(support); + this.desiredAltitudeWeight = builderBodyMotionFindBase.getDesiredAltitudeWeight(support); + this.probeMoveData.setRelaxedMoveConstraints(this.isRelaxedMoveConstraints); + this.pathFollower.setPathSmoothing(builderBodyMotionFindBase.getPathSmoothing(support)); + this.pathFollower.setRelativeSpeed(builderBodyMotionFindBase.getRelativeSpeed(support)); + this.pathFollower.setRelativeSpeedWaypoint(builderBodyMotionFindBase.getRelativeSpeedWaypoint(support)); + this.pathFollower.setWaypointRadius(builderBodyMotionFindBase.getWaypointRadius(support)); + this.pathFollower.setRejectionWeight(builderBodyMotionFindBase.getRejectionWeight(support)); + this.pathFollower.setBlendHeading(builderBodyMotionFindBase.getBlendHeading(support)); + this.aStar.setMaxPathLength(builderBodyMotionFindBase.getMaxPathLength(support)); + this.aStar.setOpenNodesLimit(builderBodyMotionFindBase.getMaxOpenNodes(support)); + this.aStar.setTotalNodesLimit(builderBodyMotionFindBase.getMaxTotalNodes(support)); + this.aStar.setCanMoveDiagonal(builderBodyMotionFindBase.isDiagonalMoves(support)); + this.aStar.setOptimizedBuildPath(builderBodyMotionFindBase.isBuildOptimisedPath(support)); + EnumSet debugFlags = builderBodyMotionFindBase.getParsedDebugFlags(); + this.dbgRebuild = debugFlags.contains(BodyMotionFindBase.DebugFlags.Rebuild); + this.dbgNodes = debugFlags.contains(BodyMotionFindBase.DebugFlags.Nodes); + this.dbgProfile = debugFlags.contains(BodyMotionFindBase.DebugFlags.Profile); + this.dbgPath = debugFlags.contains(BodyMotionFindBase.DebugFlags.Path); + this.dbgOpens = debugFlags.contains(BodyMotionFindBase.DebugFlags.Opens); + this.dbgMaps = debugFlags.contains(BodyMotionFindBase.DebugFlags.Maps); + this.dbgStatus = debugFlags.contains(BodyMotionFindBase.DebugFlags.Status); + this.dbgStay = debugFlags.contains(BodyMotionFindBase.DebugFlags.Stay); + this.dbgMotionState = debugFlags.contains(BodyMotionFindBase.DebugFlags.Motion); + this.dbgDisplayString = false; + this.pathFollower.setDebugNodes(this.dbgNodes); + } + + @Override + public void activate(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + MotionController activeMotionController = role.getActiveMotionController(); + this.sharedNodePoolProvider = componentAccessor.getResource(AStarNodePoolProviderSimple.getResourceType()); + this.dbgDisplayString = role.getDebugSupport().getDebugFlags().contains(RoleDebugFlags.Pathfinder); + this.setNavStateInit(activeMotionController); + this.wasSteering = false; + this.wasAvoidingBlockDamage = this.isAvoidingBlockDamage && activeMotionController.isAvoidingBlockDamage(); + this.aStar.setStartPosition(transformComponent.getPosition()); + } + + @Override + public void deactivate(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + this.pathFollower.clearPath(); + this.aStar.clearPath(); + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role role, + @Nullable InfoProvider infoProvider, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + desiredSteering.clear(); + MotionController activeMotionController = role.getActiveMotionController(); + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + if (!this.canComputeMotion(ref, role, infoProvider, componentAccessor)) { + this.setNavStateAborted(activeMotionController); + this.wasSteering = false; + return false; + } else { + if (!this.isAvoidingBlockDamage) { + activeMotionController.setAvoidingBlockDamage(false); + } + + activeMotionController.setRelaxedMoveConstraints(this.isRelaxedMoveConstraints); + this.probeMoveData.setAvoidingBlockDamage(activeMotionController.isAvoidingBlockDamage()); + if (this.isGoalReached(ref, activeMotionController, position, componentAccessor)) { + this.setNavStateAtGoal(role.getActiveMotionController()); + this.wasSteering = false; + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: At Goal"); + } + + return false; + } else { + if (this.throttleCount > this.throttleIgnoreCount) { + this.throttleTime += dt; + } + + this.throttleDelay -= dt; + if (this.throttleDelay > 0.0) { + this.wasSteering = false; + if (!this.mustAbortThrottling(activeMotionController, ref)) { + this.onThrottling(activeMotionController, ref, desiredSteering, componentAccessor); + this.setNavStateThrottling(activeMotionController); + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: Delaying"); + } + + return true; + } + + this.resetThrottleCount(); + } + + boolean unobstructed = !activeMotionController.isObstructed(); + if (this.passedWaypoint && this.pathFollower.getCurrentWaypoint() != null && this.useSteering && unobstructed) { + if (this.canSwitchToSteering(ref, activeMotionController, componentAccessor)) { + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: Switch to steering"); + } + + this.forceRecomputePath(activeMotionController); + this.wasSteering = true; + } + + this.passedWaypoint = false; + } + + if (this.pathFollower.getCurrentWaypoint() == null && !this.aStar.isComputing() && this.useSteering) { + if (unobstructed + && (this.wasSteering || !this.canSkipSteering || !this.shouldSkipSteering(ref, activeMotionController, position, componentAccessor)) + && this.computeSteering(ref, role, position, desiredSteering, componentAccessor)) { + this.setNavStateSteering(role.getActiveMotionController()); + this.onSteering(activeMotionController, ref, componentAccessor); + this.wasSteering = true; + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: Steering"); + } + + return true; + } + + if (!this.usePathfinder) { + this.setNavStateBlocked(role.getActiveMotionController()); + this.wasSteering = false; + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: Blocked"); + } + + return false; + } + + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: End of steering - Fall through path finder"); + } + + this.forceRecomputePath(activeMotionController); + } + + this.wasSteering = false; + boolean mustRecomputePath = this.mustRecomputePath(activeMotionController); + boolean forceRecomputePath = activeMotionController.isForceRecomputePath(); + if (mustRecomputePath || forceRecomputePath) { + if (this.dbgMotionState) { + NPCPlugin.get() + .getLogger() + .at(Level.INFO) + .every(100) + .log("MotionFindBase: Trigger Recomputing path reason mustRecompute=%s forceRecompute=%s", mustRecomputePath, forceRecomputePath); + } + + this.forceRecomputePath(activeMotionController); + } + + if (this.pathFollower.getCurrentWaypoint() != null) { + this.updatePathFollower(ref, position, activeMotionController, componentAccessor); + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: Updating path follower"); + } + } + + if (this.pathFollower.pathInFinalStage() && activeMotionController.canAct(ref, componentAccessor)) { + if (this.pathFollower.getCurrentWaypoint() == null) { + if (this.aStar.getPath() != null) { + this.setPath(ref, position, activeMotionController, componentAccessor); + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: End of path - Precomputed exists"); + } + } else if (!this.aStar.isComputing()) { + if (unobstructed && (!this.canSkipSteering || !this.shouldSkipSteering(ref, activeMotionController, position, componentAccessor))) { + this.setNavStateSteering(activeMotionController); + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: End of path - Switch back to steering"); + } + + return false; + } + + if (this.shouldDeferPathComputation(activeMotionController, position, componentAccessor)) { + this.onDeferring(activeMotionController, ref, desiredSteering, componentAccessor); + this.setNavStateDeferred(activeMotionController); + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: End of path - Deferring computations"); + } + + return true; + } + + if (!this.startPathFinder(ref, position, role, activeMotionController, componentAccessor)) { + this.onNoPathFound(activeMotionController); + this.setNavStateAborted(activeMotionController); + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: End of path - Start path finder failed"); + } + + return false; + } + + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: End of path - Restarted path finder"); + } + } + } else if (this.pathFollower.isWaypointFrozen()) { + this.aStar.clearPath(); + Vector3d targetPosition = this.pathFollower.getCurrentWaypointPosition(); + if (targetPosition.distanceSquaredTo(position) < 1.0) { + this.pathFollower.setWaypointFrozen(false); + if (this.canSkipSteering && this.shouldSkipSteering(ref, activeMotionController, targetPosition, componentAccessor)) { + this.startPathFinder(ref, position, role, activeMotionController, componentAccessor); + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: Early start path computation"); + } + } else if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: Skipped early start path computation"); + } + } + } + } + + if (this.aStar.isComputing() && this.continuePathFinder(ref, activeMotionController, componentAccessor)) { + if (this.pathFollower.getCurrentWaypoint() == null) { + this.setNavStateComputing(activeMotionController); + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: Computing - no follower"); + } + + return true; + } + + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: Computing"); + } + } + + if (this.pathFollower.getCurrentWaypoint() == null) { + if (this.aStar.getPath() == null) { + this.setNavStateThrottling(activeMotionController); + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: No path - throttling"); + } + + return false; + } + + this.setPath(ref, position, activeMotionController, componentAccessor); + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: No path - setting new path"); + } + } + + if (activeMotionController.canAct(ref, componentAccessor) && !this.dbgStay) { + this.pathFollower.executePath(position, activeMotionController, desiredSteering); + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFindBase: Executing path"); + } + } + + this.setNavStateFollowing(activeMotionController); + return true; + } + } + } + + public abstract void findBestPath(AStarBase var1, MotionController var2); + + protected boolean startPathFinder( + @Nonnull Ref ref, + @Nonnull Vector3d position, + Role role, + @Nonnull MotionController activeMotionController, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.dbgProfile) { + long time = System.nanoTime(); + AStarBase.Progress progress = this.startComputePath(ref, role, activeMotionController, position, componentAccessor); + if (progress == AStarBase.Progress.COMPUTING) { + progress = this.aStar.computePath(ref, activeMotionController, this.probeMoveData, Integer.MAX_VALUE, componentAccessor); + } + + time = System.nanoTime() - time; + NPCPlugin.get().getLogger().at(Level.INFO).log("Path computation profile %s in %s", progress.toString(), time / 1000L); + if (progress != AStarBase.Progress.ACCOMPLISHED) { + return false; + } + + if (this.dbgPath) { + this.aStarDebug.dumpPath(); + this.aStarDebug.dumpMap(true, activeMotionController); + } + } else { + AStarBase.Progress progressx = this.startComputePath(ref, role, activeMotionController, position, componentAccessor); + if (progressx != AStarBase.Progress.COMPUTING) { + if (this.dbgStatus) { + NPCPlugin.get().getLogger().at(Level.INFO).log("Path computation start failed %s", progressx.toString()); + } + + return false; + } + } + + return true; + } + + protected boolean continuePathFinder( + @Nonnull Ref ref, @Nonnull MotionController activeMotionController, @Nonnull ComponentAccessor componentAccessor + ) { + AStarBase.Progress progress = this.aStar.computePath(ref, activeMotionController, this.probeMoveData, this.nodesPerTick, componentAccessor); + if (progress == AStarBase.Progress.COMPUTING) { + this.setNavStateComputing(activeMotionController); + if (this.dbgOpens) { + this.aStarDebug.dumpOpens(activeMotionController); + } + + if (this.dbgMaps) { + this.aStarDebug.dumpMap(true, activeMotionController); + } + + return true; + } else { + if (progress == AStarBase.Progress.ACCOMPLISHED) { + this.resetThrottleCount(); + } else if (this.useBestPath) { + this.findBestPath(this.aStar, activeMotionController); + this.throttleCount++; + if (this.aStar.getPath() != null) { + progress = AStarBase.Progress.ACCOMPLISHED; + } + } + + if (progress != AStarBase.Progress.ACCOMPLISHED) { + if (this.dbgStatus) { + NPCPlugin.get().getLogger().at(Level.INFO).log("Path computation failed %s", progress.toString()); + } + + this.aStar.clearPath(); + this.onNoPathFound(activeMotionController); + return false; + } else { + double pathLengthSquared = this.aStar.getEndPosition().distanceSquaredTo(this.aStar.getPosition()); + if (pathLengthSquared < this.minPathLengthSquared) { + if (this.dbgStatus) { + NPCPlugin.get().getLogger().at(Level.INFO).log("Path computation failed. Path to short length=%s", Math.sqrt(pathLengthSquared)); + } + + this.aStar.clearPath(); + this.onNoPathFound(activeMotionController); + return false; + } else { + if (this.dbgPath) { + this.aStarDebug.dumpPath(); + } + + if (this.dbgPath || this.dbgMaps) { + this.aStarDebug.dumpMap(true, activeMotionController); + } + + return false; + } + } + } + } + + protected boolean updatePathFollower( + @Nonnull Ref ref, + @Nonnull Vector3d position, + @Nonnull MotionController activeMotionController, + @Nonnull ComponentAccessor componentAccessor + ) { + if (!this.pathFollower.updateCurrentTarget(position, activeMotionController)) { + return false; + } else { + if (this.pathFollower.shouldSmoothPath()) { + this.passedWaypoint = true; + this.pathFollower.smoothPath(ref, position, activeMotionController, this.probeMoveData, componentAccessor); + } + + return true; + } + } + + protected boolean canSwitchToSteering( + @Nonnull Ref ref, MotionController motionController, @Nonnull ComponentAccessor componentAccessor + ) { + return false; + } + + protected boolean shouldSkipSteering( + @Nonnull Ref ref, MotionController activeMotionController, Vector3d position, @Nonnull ComponentAccessor componentAccessor + ) { + return !this.useSteering; + } + + protected boolean computeSteering( + @Nonnull Ref ref, Role role, Vector3d position, Steering desiredSteering, @Nonnull ComponentAccessor componentAccessor + ) { + return false; + } + + protected boolean scaleSteering( + @Nonnull Ref ref, + @Nonnull Role role, + @Nonnull SteeringForceWithTarget steeringForce, + @Nonnull Steering desiredSteering, + double desiredAltitudeWeight, + @Nonnull ComponentAccessor componentAccessor + ) { + MotionController motionController = role.getActiveMotionController(); + boolean approachDesiredHeight = !motionController.is2D() && desiredAltitudeWeight > 0.0; + boolean withinRange = approachDesiredHeight && motionController.getDesiredVerticalRange(ref, componentAccessor).isWithinRange(); + if (withinRange) { + steeringForce.setComponentSelector(role.getActiveMotionController().getPlanarComponentSelector()); + } else { + steeringForce.setComponentSelector(role.getActiveMotionController().getComponentSelector()); + } + + if (!steeringForce.compute(desiredSteering)) { + return false; + } else { + desiredSteering.scaleTranslation(this.getRelativeSpeed()); + if (approachDesiredHeight && !withinRange) { + MotionController.VerticalRange desiredAltitudeRange = motionController.getDesiredVerticalRange(ref, componentAccessor); + if (desiredAltitudeRange.current > desiredAltitudeRange.max) { + desiredSteering.setY(-this.computeDesiredYTranslation(desiredSteering, motionController.getMaxSinkAngle(), desiredAltitudeWeight)); + } else if (desiredAltitudeRange.current < desiredAltitudeRange.min) { + desiredSteering.setY(this.computeDesiredYTranslation(desiredSteering, motionController.getMaxClimbAngle(), desiredAltitudeWeight)); + } + } + + motionController.requireDepthProbing(); + return true; + } + } + + protected double computeDesiredYTranslation(@Nonnull Steering desiredSteering, float maxAngle, double desiredAltitudeWeight) { + double dirX = desiredSteering.getX(); + double dirZ = desiredSteering.getZ(); + double length = Math.sqrt(dirX * dirX + dirZ * dirZ); + return length * TrigMathUtil.sin(maxAngle * desiredAltitudeWeight); + } + + protected void onNoPathFound(MotionController motionController) { + this.aStar.clearPath(); + } + + protected void onBlockedPath() { + } + + protected void onSteering(MotionController activeMotionController, Ref ref, ComponentAccessor componentAccessor) { + } + + protected void onThrottling(MotionController motionController, Ref ref, Steering steering, ComponentAccessor componentAccessor) { + } + + protected void onDeferring(MotionController motionController, Ref ref, Steering steering, ComponentAccessor componentAccessor) { + } + + protected boolean mustAbortThrottling(MotionController motionController, Ref ref) { + return false; + } + + protected abstract boolean isGoalReached(Ref var1, MotionController var2, Vector3d var3, ComponentAccessor var4); + + protected void setNavState(NavState state, String label, boolean reset, @Nonnull MotionController activeMotionController) { + if (reset) { + this.resetThrottleCount(); + this.targetDeltaSquared = 0.0; + this.forceRecomputePath(activeMotionController); + } + + activeMotionController.setNavState(state, this.throttleTime, this.targetDeltaSquared); + if (this.dbgDisplayString) { + if (this.debugString == null) { + this.debugString = new StringBuilder(); + } + + this.debugString.append(label).append(" TC:").append(this.throttleCount).append(" TT:").append(MathUtil.floor(this.throttleTime * 10.0) / 10.0); + this.decorateDebugString(this.debugString); + activeMotionController.getRole().getDebugSupport().setDisplayPathfinderString(this.debugString.toString()); + this.debugString.setLength(0); + } + } + + protected void decorateDebugString(StringBuilder dbgString) { + } + + protected void setNavStateInit(@Nonnull MotionController motionController) { + this.setNavState(NavState.INIT, "I-START", true, motionController); + } + + protected void setNavStateComputing(@Nonnull MotionController motionController) { + this.setNavState(NavState.PROGRESSING, "P-COMPT", false, motionController); + } + + protected void setNavStateDeferred(@Nonnull MotionController motionController) { + this.setNavState(NavState.DEFER, "D-DEFER", false, motionController); + } + + protected void setNavStateAtGoal(@Nonnull MotionController motionController) { + this.setNavState(NavState.AT_GOAL, "G-GOAL", true, motionController); + } + + protected void setNavStateFollowing(@Nonnull MotionController motionController) { + this.setNavState(NavState.PROGRESSING, "P-FOLLW", false, motionController); + } + + protected void setNavStateSteering(@Nonnull MotionController motionController) { + this.setNavState(NavState.PROGRESSING, "P-STEER", false, motionController); + } + + protected void setNavStateBlocked(@Nonnull MotionController motionController) { + this.setNavState(NavState.BLOCKED, "B-BLOCK", false, motionController); + } + + protected void setNavStateAborted(@Nonnull MotionController motionController) { + this.setNavState(NavState.ABORTED, "A-ABORT", true, motionController); + } + + protected void setNavStateThrottling(@Nonnull MotionController motionController) { + if (this.throttleDelay <= 0.0 && this.throttleDelayMax > 0.0 && this.throttleCount > this.throttleIgnoreCount) { + this.throttleDelay = RandomExtra.randomRange(this.throttleDelayMin, this.throttleDelayMax); + } + + this.setNavState(NavState.PROGRESSING, "P-THRTL", false, motionController); + } + + protected void setPath( + @Nonnull Ref ref, + @Nonnull Vector3d position, + @Nonnull MotionController activeMotionController, + @Nonnull ComponentAccessor componentAccessor + ) { + AStarNode aStarPath = this.aStar.getPath(); + this.pathFollower.setPath(aStarPath, position); + this.passedWaypoint = false; + this.updatePathFollower(ref, position, activeMotionController, componentAccessor); + } + + protected void resetThrottleCount() { + this.throttleTime = 0.0; + this.throttleCount = 0; + this.throttleDelay = 0.0; + } + + protected AStarBase.Progress startComputePath( + @Nonnull Ref ref, + Role role, + @Nonnull MotionController activeMotionController, + @Nonnull Vector3d position, + @Nonnull ComponentAccessor componentAccessor + ) { + return this.aStar.initComputePath(ref, position, this, activeMotionController, this.probeMoveData, this.sharedNodePoolProvider, componentAccessor); + } + + protected boolean shouldDeferPathComputation(MotionController motionController, Vector3d position, @Nonnull ComponentAccessor componentAccessor) { + return false; + } + + protected boolean canComputeMotion( + @Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider positionProvider, @Nonnull ComponentAccessor componentAccessor + ) { + return true; + } + + protected boolean mustRecomputePath(@Nonnull MotionController activeMotionController) { + if (this.dbgRebuild) { + return true; + } else if (activeMotionController.isObstructed()) { + if (this.dbgStatus) { + NPCPlugin.get().getLogger().at(Level.INFO).log("Recomputing Path - Blocked"); + } + + this.onBlockedPath(); + return true; + } else { + boolean avoidingBlockDamage = activeMotionController.isAvoidingBlockDamage(); + if (this.wasAvoidingBlockDamage != avoidingBlockDamage) { + this.wasAvoidingBlockDamage = avoidingBlockDamage; + if (this.dbgStatus) { + NPCPlugin.get().getLogger().at(Level.INFO).log("Recomputing Path - AvoidBlockDamage changed"); + } + + return true; + } else { + return false; + } + } + } + + protected double getRelativeSpeed() { + return this.pathFollower.getRelativeSpeed(); + } + + protected void forceRecomputePath(MotionController activeMotionController) { + this.aStar.clearPath(); + this.pathFollower.clearPath(); + } + + public static enum DebugFlags implements Supplier { + Opens("Display open nodes each step when computing"), + Maps("Display map each step when computing"), + Path("Display map when path was found"), + Status("Display status messages"), + Rebuild("Force immediate rebuild of path"), + Profile("Measure time for path finding"), + Nodes("Display walk node information"), + Motion("Display Motion state changes"), + Stay("Don't move. Only compute path"); + + private final String description; + + private DebugFlags(String description) { + this.description = description; + } + + public String get() { + return this.description; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionFindWithTarget.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionFindWithTarget.java new file mode 100644 index 0000000..f64c077 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionFindWithTarget.java @@ -0,0 +1,279 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.util.TrigMathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionFindWithTarget; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.navigation.AStarBase; +import com.hypixel.hytale.server.npc.navigation.AStarWithTarget; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.IPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.NPCPhysicsMath; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class BodyMotionFindWithTarget extends BodyMotionFindBase { + protected final double minMoveDistanceWait; + protected final double minMoveDistanceWaitSquared; + protected final double minMoveDistanceRecompute; + protected final double minMoveDistanceRecomputeSquared; + protected final float cosHalfRecomputeConeAngle; + protected final double minMoveDistanceReproject; + protected final double minMoveDistanceReprojectSquared; + protected final boolean adjustRangeByHitboxSize; + protected final Vector3d lastPathedPosition = new Vector3d(); + protected final Vector3d conePosition = new Vector3d(); + protected final Vector3d coneDirection = new Vector3d(); + @Nullable + protected Box targetBoundingBox; + protected Box selfBoundingBox; + protected boolean waitForTargetMovement = false; + private final Vector3d lastTargetPosition = new Vector3d(); + private final Vector3d lastAccessibleTargetPosition = new Vector3d(); + private boolean haveValidTargetPosition; + private boolean haveAccessibleTargetPosition; + private boolean lastAccessibleTargetPositionIsCurrent; + protected String self; + protected String other; + + public BodyMotionFindWithTarget(@Nonnull BuilderBodyMotionFindWithTarget builderMotionFindWithTarget, @Nonnull BuilderSupport support) { + super(builderMotionFindWithTarget, support, new AStarWithTarget()); + this.adjustRangeByHitboxSize = builderMotionFindWithTarget.isAdjustRangeByHitboxSize(support); + this.minMoveDistanceWait = builderMotionFindWithTarget.getMinMoveDistanceWait(support); + this.minMoveDistanceWaitSquared = this.minMoveDistanceWait * this.minMoveDistanceWait; + this.minMoveDistanceRecompute = builderMotionFindWithTarget.getMinMoveDistanceRecompute(support); + this.minMoveDistanceRecomputeSquared = this.minMoveDistanceRecompute * this.minMoveDistanceRecompute; + this.minMoveDistanceReproject = builderMotionFindWithTarget.getMinMoveDistanceReproject(support); + this.minMoveDistanceReprojectSquared = this.minMoveDistanceReproject * this.minMoveDistanceReproject; + float cosine = TrigMathUtil.cos(builderMotionFindWithTarget.getRecomputeConeAngle(support) / 2.0); + this.cosHalfRecomputeConeAngle = cosine == -1.0F ? 1.0F : cosine; + } + + @Override + public void activate(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + super.activate(ref, role, componentAccessor); + this.haveValidTargetPosition = false; + this.haveAccessibleTargetPosition = false; + this.waitForTargetMovement = false; + this.targetBoundingBox = null; + this.lastPathedPosition.assign(Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE); + this.self = role.getRoleName(); + } + + @Override + public boolean canComputeMotion( + @Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider infoProvider, @Nonnull ComponentAccessor componentAccessor + ) { + BoundingBox boundingBoxComponent = componentAccessor.getComponent(ref, BoundingBox.getComponentType()); + + assert boundingBoxComponent != null; + + this.targetBoundingBox = null; + this.selfBoundingBox = boundingBoxComponent.getBoundingBox(); + this.lastAccessibleTargetPositionIsCurrent = false; + if (infoProvider != null && infoProvider.hasPosition()) { + IPositionProvider positionProvider = infoProvider.getPositionProvider(); + if (positionProvider.providePosition(this.lastTargetPosition)) { + this.haveValidTargetPosition = true; + Ref targetEntityReference = positionProvider.getTarget(); + if (targetEntityReference != null) { + BoundingBox targetEntityBoundingBoxComponent = componentAccessor.getComponent(targetEntityReference, BoundingBox.getComponentType()); + + assert targetEntityBoundingBoxComponent != null; + + NPCEntity npcEntityComponent = componentAccessor.getComponent(targetEntityReference, NPCEntity.getComponentType()); + this.other = npcEntityComponent != null ? npcEntityComponent.getRoleName() : null; + this.targetBoundingBox = targetEntityBoundingBoxComponent.getBoundingBox(); + MovementStatesComponent movementStatesComponent = componentAccessor.getComponent( + targetEntityReference, MovementStatesComponent.getComponentType() + ); + Entity entity = EntityUtils.getEntity(targetEntityReference, targetEntityReference.getStore()); + if (entity instanceof LivingEntity && movementStatesComponent != null && movementStatesComponent.getMovementStates().onGround) { + this.lastAccessibleTargetPosition.assign(this.lastTargetPosition); + this.haveAccessibleTargetPosition = true; + this.lastAccessibleTargetPositionIsCurrent = true; + } + } else { + this.targetBoundingBox = null; + } + } + } + + this.targetDeltaSquared = this.haveValidTargetPosition + ? role.getActiveMotionController().waypointDistanceSquared(this.getLastTargetPosition(), this.lastPathedPosition) + : Double.MAX_VALUE; + return this.haveValidTargetPosition; + } + + @Override + public boolean mustRecomputePath(@Nonnull MotionController activeMotionController) { + if (super.mustRecomputePath(activeMotionController)) { + return true; + } else if (this.minMoveDistanceRecomputeSquared > 0.0 && this.targetDeltaSquared > this.minMoveDistanceRecomputeSquared) { + if (this.dbgStatus) { + NPCPlugin.get().getLogger().at(Level.INFO).log("Recomputing Path - Target moved"); + } + + this.resetThrottleCount(); + return true; + } else { + if (this.cosHalfRecomputeConeAngle < 1.0F) { + if (activeMotionController.is2D()) { + if (NPCPhysicsMath.isInViewCone( + this.conePosition, + this.coneDirection, + this.cosHalfRecomputeConeAngle, + this.getLastTargetPosition(), + activeMotionController.getComponentSelector() + )) { + if (this.dbgStatus) { + NPCPlugin.get().getLogger().at(Level.INFO).log("Recomputing Path - Target left 2D cone"); + } + + return true; + } + } else if (NPCPhysicsMath.isInViewCone(this.conePosition, this.coneDirection, this.cosHalfRecomputeConeAngle, this.getLastTargetPosition())) { + if (this.dbgStatus) { + NPCPlugin.get().getLogger().at(Level.INFO).log("Recomputing Path - Target left cone"); + } + + return true; + } + } + + return false; + } + } + + @Override + public void forceRecomputePath(MotionController activeMotionController) { + super.forceRecomputePath(activeMotionController); + } + + @Override + public boolean shouldDeferPathComputation( + @Nonnull MotionController motionController, Vector3d position, @Nonnull ComponentAccessor componentAccessor + ) { + if (this.throttleCount > this.throttleIgnoreCount) { + double distanceSquared = this.getLastTargetPosition().distanceSquaredTo(this.lastPathedPosition); + if (distanceSquared < 1.0000000000000002E-10 || this.waitForTargetMovement && distanceSquared < this.minMoveDistanceWaitSquared) { + return true; + } + } + + this.waitForTargetMovement = false; + this.lastPathedPosition.assign(this.getLastAccessibleTargetPosition(motionController, false, componentAccessor)); + return false; + } + + @Override + protected boolean mustAbortThrottling(MotionController motionController, Ref ref) { + if (super.mustAbortThrottling(motionController, ref)) { + return true; + } else if (this.minMoveDistanceRecomputeSquared > 0.0 && this.targetDeltaSquared > this.minMoveDistanceRecomputeSquared) { + if (this.dbgMotionState) { + NPCPlugin.get().getLogger().at(Level.INFO).log("MotionFindWithTarget: Aborting throttling - Target moved"); + } + + return true; + } else { + return false; + } + } + + @Override + public boolean isGoalReached( + Ref ref, MotionController activeMotionController, Vector3d position, ComponentAccessor componentAccessor + ) { + return this.isGoalReached(ref, activeMotionController, position, this.getLastTargetPosition(), componentAccessor); + } + + @Override + public AStarBase.Progress startComputePath( + @Nonnull Ref ref, + Role role, + @Nonnull MotionController activeMotionController, + @Nonnull Vector3d position, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.cosHalfRecomputeConeAngle < 1.0F) { + this.conePosition.assign(position); + this.coneDirection.assign(this.lastPathedPosition).subtract(position); + } + + return this.aStar + .initComputePath( + ref, position, this.lastPathedPosition, this, activeMotionController, this.probeMoveData, this.sharedNodePoolProvider, componentAccessor + ); + } + + @Override + public void onBlockedPath() { + super.onBlockedPath(); + } + + @Override + public void onNoPathFound(MotionController motionController) { + super.onNoPathFound(motionController); + this.waitForTargetMovement = true; + } + + @Override + protected void onSteering(MotionController activeMotionController, @Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + this.lastPathedPosition.assign(transformComponent.getPosition()); + } + + @Override + protected void decorateDebugString(@Nonnull StringBuilder dbgString) { + dbgString.append("D:").append(MathUtil.round(Math.sqrt(this.targetDeltaSquared), 1)); + } + + protected abstract boolean isGoalReached(Ref var1, MotionController var2, Vector3d var3, Vector3d var4, ComponentAccessor var5); + + protected Vector3d getLastTargetPosition() { + return this.lastTargetPosition; + } + + protected Vector3d getLastAccessibleTargetPosition( + @Nonnull MotionController motionController, boolean approximate, @Nonnull ComponentAccessor componentAccessor + ) { + if (!this.lastAccessibleTargetPositionIsCurrent + && ( + !approximate + || !this.haveAccessibleTargetPosition + || !(motionController.waypointDistanceSquared(this.lastTargetPosition, this.lastAccessibleTargetPosition) < this.minMoveDistanceReprojectSquared) + )) { + if (this.dbgMotionState && motionController.is2D()) { + NPCPlugin.get().getLogger().at(Level.INFO).log("MotionFindWithTarget: Reprojecting %s -> %s", this.self, this.other); + } + + this.lastAccessibleTargetPosition.assign(this.lastTargetPosition); + motionController.translateToAccessiblePosition(this.lastAccessibleTargetPosition, this.targetBoundingBox, 0.0, 320.0, componentAccessor); + this.haveAccessibleTargetPosition = true; + this.lastAccessibleTargetPositionIsCurrent = true; + return this.lastAccessibleTargetPosition; + } else { + return this.lastAccessibleTargetPosition; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionLand.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionLand.java new file mode 100644 index 0000000..8e40e98 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionLand.java @@ -0,0 +1,83 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionLand; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.movement.controllers.MotionControllerFly; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BodyMotionLand extends BodyMotionFind { + protected final double goalLenience; + protected final double goalLenienceSquared; + + public BodyMotionLand(@Nonnull BuilderBodyMotionLand builderMotionLand, @Nonnull BuilderSupport support) { + super(builderMotionLand, support); + this.goalLenience = builderMotionLand.getGoalLenience(support); + this.goalLenienceSquared = this.goalLenience * this.goalLenience; + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role role, + @Nullable InfoProvider infoProvider, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + boolean result = super.computeSteering(ref, role, infoProvider, dt, desiredSteering, componentAccessor); + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + if (this.isGoalReached(ref, role.getActiveMotionController(), transformComponent.getPosition(), componentAccessor)) { + NPCEntity npcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + role.setActiveMotionController(ref, npcComponent, "Walk", componentAccessor); + return false; + } else { + return result; + } + } + + @Override + public boolean canComputeMotion( + @Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider positionProvider, @Nonnull ComponentAccessor componentAccessor + ) { + MotionController activeMotionController = role.getActiveMotionController(); + return activeMotionController.matchesType(MotionControllerFly.class) + && !activeMotionController.isObstructed() + && super.canComputeMotion(ref, role, positionProvider, componentAccessor); + } + + @Override + protected boolean isGoalReached( + @Nonnull Ref ref, + @Nonnull MotionController motionController, + @Nonnull Vector3d position, + @Nonnull Vector3d targetPosition, + @Nonnull ComponentAccessor componentAccessor + ) { + double differenceY = targetPosition.y - position.y; + if (!(differenceY < this.heightDifferenceMin) && !(differenceY > this.heightDifferenceMax)) { + double waypointDistanceSquared = motionController.waypointDistanceSquared(position, targetPosition); + return waypointDistanceSquared > this.effectiveDistanceSquared && waypointDistanceSquared > this.goalLenienceSquared + ? false + : !this.reachable || this.canReachTarget(ref, motionController, position, targetPosition, componentAccessor); + } else { + return false; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionLeave.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionLeave.java new file mode 100644 index 0000000..cf178fc --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionLeave.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionLeave; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.navigation.AStarBase; +import com.hypixel.hytale.server.npc.navigation.AStarNode; +import javax.annotation.Nonnull; + +public class BodyMotionLeave extends BodyMotionFindBase { + protected final double distanceSquared; + + public BodyMotionLeave(@Nonnull BuilderBodyMotionLeave builderMotionLeave, @Nonnull BuilderSupport support) { + super(builderMotionLeave, support, new AStarBase()); + double distance = builderMotionLeave.getDistance(support); + this.distanceSquared = distance * distance; + } + + @Override + public boolean isGoalReached(Ref ref, @Nonnull MotionController controller, Vector3d position, ComponentAccessor componentAccessor) { + return controller.waypointDistanceSquared(this.aStar.getStartPosition(), position) >= this.distanceSquared; + } + + @Override + public boolean isGoalReached( + Ref ref, + @Nonnull AStarBase aStarBase, + @Nonnull AStarNode aStarNode, + @Nonnull MotionController controller, + ComponentAccessor componentAccessor + ) { + return controller.waypointDistanceSquared(aStarBase.getStartPosition(), aStarNode.getPosition()) >= this.distanceSquared; + } + + @Override + public float estimateToGoal(AStarBase aStarBase, Vector3d fromPosition, MotionController motionController) { + return 0.0F; + } + + @Override + public void findBestPath(@Nonnull AStarBase aStarBase, MotionController controller) { + aStarBase.buildFurthestPath(); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionMaintainDistance.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionMaintainDistance.java new file mode 100644 index 0000000..e3ee328 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionMaintainDistance.java @@ -0,0 +1,326 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.BodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionMaintainDistance; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.movement.controllers.MotionControllerWalk; +import com.hypixel.hytale.server.npc.movement.steeringforces.SteeringForceEvade; +import com.hypixel.hytale.server.npc.movement.steeringforces.SteeringForcePursue; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.IPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.sensorinfo.parameterproviders.DoubleParameterProvider; +import com.hypixel.hytale.server.npc.sensorinfo.parameterproviders.ParameterProvider; +import com.hypixel.hytale.server.npc.util.NPCPhysicsMath; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BodyMotionMaintainDistance extends BodyMotionBase { + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected static final float POSITIONING_ANGLE_THRESHOLD = 0.08726646F; + protected final double[] initialDesiredDistanceRange; + protected final double moveThreshold; + @Nonnull + protected final double[] thresholdDistanceRangeSquared; + protected final double targetDistanceFactor; + protected final double relativeForwardsSpeed; + protected final double relativeBackwardsSpeed; + protected final double moveTowardsSlowdownThreshold; + protected final double[] strafingDurationRange; + protected final double[] strafingFrequencyRange; + protected final int minRangeProviderSlot; + protected final int maxRangeProviderSlot; + protected final int positioningAngleProviderSlot; + protected final double[] desiredDistanceRange = new double[2]; + protected double targetDistanceSquared; + protected boolean approaching; + protected boolean movingAway; + protected int strafingDirection = 1; + protected double strafingDelay; + protected boolean pauseStrafing; + protected final SteeringForceEvade flee = new SteeringForceEvade(); + protected final SteeringForcePursue seek = new SteeringForcePursue(); + protected final Vector3d targetPosition = new Vector3d(); + protected final Vector3d toTarget = new Vector3d(); + protected DoubleParameterProvider cachedMinRangeProvider; + protected DoubleParameterProvider cachedMaxRangeProvider; + protected DoubleParameterProvider cachedPositioningAngleProvider; + protected boolean initialised; + + public BodyMotionMaintainDistance(@Nonnull BuilderBodyMotionMaintainDistance builder, @Nonnull BuilderSupport support) { + super(builder); + this.initialDesiredDistanceRange = builder.getDesiredDistanceRange(support); + this.desiredDistanceRange[0] = this.initialDesiredDistanceRange[0]; + this.desiredDistanceRange[1] = this.initialDesiredDistanceRange[1]; + this.targetDistanceFactor = builder.getTargetDistanceFactor(support); + this.moveThreshold = builder.getMoveThreshold(support); + double min = Math.max(0.0, this.initialDesiredDistanceRange[0] - this.moveThreshold); + double max = this.initialDesiredDistanceRange[1] + this.moveThreshold; + this.thresholdDistanceRangeSquared = new double[2]; + this.thresholdDistanceRangeSquared[0] = min * min; + this.thresholdDistanceRangeSquared[1] = max * max; + this.relativeForwardsSpeed = builder.getRelativeForwardsSpeed(support); + this.relativeBackwardsSpeed = builder.getRelativeBackwardsSpeed(support); + this.moveTowardsSlowdownThreshold = builder.getMoveTowardsSlowdownThreshold(support); + this.strafingDurationRange = builder.getStrafingDurationRange(support); + this.strafingFrequencyRange = builder.getStrafingFrequencyRange(support); + this.minRangeProviderSlot = support.getParameterSlot("MinRange"); + this.maxRangeProviderSlot = support.getParameterSlot("MaxRange"); + this.positioningAngleProviderSlot = support.getParameterSlot("PositioningAngle"); + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role support, + @Nullable InfoProvider sensorInfo, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + desiredSteering.clear(); + if (!support.getActiveMotionController().matchesType(MotionControllerWalk.class)) { + support.setBackingAway(false); + return false; + } else { + if (!this.initialised) { + if (sensorInfo != null) { + ParameterProvider parameterProvider = sensorInfo.getParameterProvider(this.minRangeProviderSlot); + if (parameterProvider instanceof DoubleParameterProvider) { + this.cachedMinRangeProvider = (DoubleParameterProvider)parameterProvider; + } + + parameterProvider = sensorInfo.getParameterProvider(this.maxRangeProviderSlot); + if (parameterProvider instanceof DoubleParameterProvider) { + this.cachedMaxRangeProvider = (DoubleParameterProvider)parameterProvider; + } + + parameterProvider = sensorInfo.getParameterProvider(this.positioningAngleProviderSlot); + if (parameterProvider instanceof DoubleParameterProvider) { + this.cachedPositioningAngleProvider = (DoubleParameterProvider)parameterProvider; + } + } + + this.initialised = true; + } + + boolean recalculateMinThreshold = false; + boolean forceNewTargetRange = false; + if (this.cachedMinRangeProvider != null) { + double value = this.cachedMinRangeProvider.getDoubleParameter(); + double before = this.desiredDistanceRange[0]; + if (value != -Double.MAX_VALUE) { + this.desiredDistanceRange[0] = this.cachedMinRangeProvider.getDoubleParameter(); + } else { + this.desiredDistanceRange[0] = this.initialDesiredDistanceRange[0]; + } + + recalculateMinThreshold = true; + if (before != this.desiredDistanceRange[0]) { + forceNewTargetRange = true; + } + } + + if (this.cachedMaxRangeProvider != null) { + double valuex = this.cachedMaxRangeProvider.getDoubleParameter(); + double beforex = this.desiredDistanceRange[1]; + if (valuex != -Double.MAX_VALUE) { + this.desiredDistanceRange[1] = this.cachedMaxRangeProvider.getDoubleParameter(); + } else { + this.desiredDistanceRange[1] = this.initialDesiredDistanceRange[1]; + } + + double max = this.desiredDistanceRange[1] + this.moveThreshold; + this.thresholdDistanceRangeSquared[1] = max * max; + if (beforex != this.desiredDistanceRange[1]) { + forceNewTargetRange = true; + } + } + + double positioningAngle = Double.MAX_VALUE; + if (this.cachedPositioningAngleProvider != null) { + positioningAngle = this.cachedPositioningAngleProvider.getDoubleParameter(); + } + + NPCEntity npcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + if (this.desiredDistanceRange[0] > this.desiredDistanceRange[1]) { + NPCPlugin.get() + .getLogger() + .at(Level.WARNING) + .atMostEvery(1, TimeUnit.MINUTES) + .log( + "Attempting to set min distance for %s to a value higher than its max distance [min=%d max=%s]", + npcComponent.getRoleName(), + this.desiredDistanceRange[0], + this.desiredDistanceRange[1] + ); + this.desiredDistanceRange[0] = this.desiredDistanceRange[1]; + recalculateMinThreshold = true; + } + + if (recalculateMinThreshold) { + double min = Math.max(0.0, this.desiredDistanceRange[0] - this.moveThreshold); + this.thresholdDistanceRangeSquared[0] = min * min; + } + + if (sensorInfo != null && sensorInfo.hasPosition()) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + IPositionProvider positionProvider = sensorInfo.getPositionProvider(); + positionProvider.providePosition(this.targetPosition); + Vector3d selfPosition = transformComponent.getPosition(); + double distanceSquared = selfPosition.distanceSquaredTo(this.targetPosition); + if (forceNewTargetRange) { + double targetDistance; + if (distanceSquared > this.thresholdDistanceRangeSquared[1]) { + targetDistance = MathUtil.lerp(this.desiredDistanceRange[0], this.desiredDistanceRange[1], 1.0 - this.targetDistanceFactor); + } else { + targetDistance = MathUtil.lerp(this.desiredDistanceRange[0], this.desiredDistanceRange[1], this.targetDistanceFactor); + } + + this.targetDistanceSquared = targetDistance * targetDistance; + } + + if (!(distanceSquared > this.thresholdDistanceRangeSquared[1]) && (!this.approaching || !(distanceSquared > this.targetDistanceSquared))) { + if (!(distanceSquared < this.thresholdDistanceRangeSquared[0]) && (!this.movingAway || !(distanceSquared < this.targetDistanceSquared))) { + if (this.approaching || this.movingAway) { + this.approaching = false; + this.movingAway = false; + } + } else { + if (!this.movingAway) { + double targetDistance = MathUtil.lerp(this.desiredDistanceRange[0], this.desiredDistanceRange[1], this.targetDistanceFactor); + this.targetDistanceSquared = targetDistance * targetDistance; + this.movingAway = true; + this.approaching = false; + support.setBackingAway(true); + } + + this.flee.setPositions(selfPosition, this.targetPosition); + MotionController activeMotionController = support.getActiveMotionController(); + this.flee.setComponentSelector(activeMotionController.getComponentSelector()); + this.flee.compute(desiredSteering); + desiredSteering.scaleTranslation(this.relativeBackwardsSpeed); + } + } else { + if (!this.approaching) { + double targetDistance = MathUtil.lerp(this.desiredDistanceRange[0], this.desiredDistanceRange[1], 1.0 - this.targetDistanceFactor); + this.targetDistanceSquared = targetDistance * targetDistance; + this.seek.setDistances(targetDistance + this.moveTowardsSlowdownThreshold, targetDistance); + this.approaching = true; + this.movingAway = false; + support.setBackingAway(false); + } + + this.seek.setPositions(selfPosition, this.targetPosition); + MotionController activeMotionController = support.getActiveMotionController(); + this.seek.setComponentSelector(activeMotionController.getComponentSelector()); + this.seek.compute(desiredSteering); + desiredSteering.scaleTranslation(this.relativeForwardsSpeed); + } + + double x = this.targetPosition.getX() - selfPosition.getX(); + double z = this.targetPosition.getZ() - selfPosition.getZ(); + float targetYaw = PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(x, z)); + MotionController motionController = support.getActiveMotionController(); + if (this.strafingDurationRange[1] > 0.0 || positioningAngle != Double.MAX_VALUE) { + if (positioningAngle == Double.MAX_VALUE) { + if (!this.tickStrafingDelay(dt)) { + if (this.pauseStrafing) { + this.strafingDelay = RandomExtra.randomRange(this.strafingDurationRange); + this.strafingDirection = RandomExtra.randomBoolean() ? 1 : -1; + this.pauseStrafing = false; + } else { + this.strafingDelay = RandomExtra.randomRange(this.strafingFrequencyRange); + this.pauseStrafing = true; + } + } + } else { + Ref targetRef = positionProvider.getTarget(); + if (targetRef != null) { + TransformComponent targetTransformComponent = componentAccessor.getComponent(targetRef, TRANSFORM_COMPONENT_TYPE); + + assert targetTransformComponent != null; + + float selfYaw = NPCPhysicsMath.lookatHeading(selfPosition, this.targetPosition, transformComponent.getRotation().getYaw()); + float difference = PhysicsMath.normalizeTurnAngle(targetTransformComponent.getRotation().getYaw() - selfYaw - (float)positioningAngle); + if (Math.abs(difference) > 0.08726646F) { + this.strafingDirection = difference > 0.0F ? -1 : 1; + this.pauseStrafing = false; + } else { + this.pauseStrafing = true; + } + } else { + this.pauseStrafing = true; + } + } + + if (!this.pauseStrafing) { + float angle; + if (!desiredSteering.hasTranslation()) { + this.toTarget.add(this.targetPosition).subtract(selfPosition).setY(0.0); + this.toTarget.normalize(); + desiredSteering.setTranslation(this.toTarget); + Vector3d translation = desiredSteering.getTranslation(); + double newX = translation.getZ() * this.strafingDirection; + double newZ = translation.getX() * -this.strafingDirection; + translation.setX(newX); + translation.setZ(newZ); + desiredSteering.scaleTranslation(this.relativeForwardsSpeed); + angle = this.strafingDirection * (float) (Math.PI / 4); + } else { + angle = this.strafingDirection * (this.movingAway ? (float) (-Math.PI / 4) : (float) (Math.PI / 4)); + desiredSteering.getTranslation().rotateY(angle); + } + + support.setBackingAway(true); + if (!motionController.isObstructed()) { + targetYaw += angle; + } + } + } + + motionController.requireDepthProbing(); + desiredSteering.setYaw(targetYaw); + return false; + } else { + return false; + } + } + } + + protected boolean tickStrafingDelay(double dt) { + if (this.strafingDelay > 0.0) { + this.strafingDelay -= dt; + return true; + } else { + return false; + } + } + + @Override + public void deactivate(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + super.deactivate(ref, role, componentAccessor); + role.setBackingAway(false); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionMatchLook.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionMatchLook.java new file mode 100644 index 0000000..c5b2a46 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionMatchLook.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.BodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderBodyMotionBase; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BodyMotionMatchLook extends BodyMotionBase { + public BodyMotionMatchLook(@Nonnull BuilderBodyMotionBase builderMotionBase) { + super(builderMotionBase); + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role role, + @Nullable InfoProvider sensorInfo, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + HeadRotation headRotationComponent = componentAccessor.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + float headYaw = headRotationComponent.getRotation().getYaw(); + desiredSteering.setYaw(headYaw); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionMoveAway.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionMoveAway.java new file mode 100644 index 0000000..aae64d4 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionMoveAway.java @@ -0,0 +1,156 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionMoveAway; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.movement.steeringforces.SteeringForceEvade; +import com.hypixel.hytale.server.npc.navigation.AStarBase; +import com.hypixel.hytale.server.npc.navigation.AStarNode; +import com.hypixel.hytale.server.npc.navigation.AStarWithTarget; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.NPCPhysicsMath; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BodyMotionMoveAway extends BodyMotionFindWithTarget { + protected final double stopDistance; + protected final double stopDistanceSquared; + protected final double[] holdDirectionDurationRange; + protected final float changeDirectionViewSector; + protected final float jitterAngle; + protected final double erraticDistanceSquared; + protected final float erraticJitter; + protected final double erraticChangeDurationMultiplier; + protected final SteeringForceEvade evade = new SteeringForceEvade(); + protected float fleeDirection; + protected double holdDirectionTimeRemaining; + + public BodyMotionMoveAway(@Nonnull BuilderBodyMotionMoveAway builderMotionFind, @Nonnull BuilderSupport support) { + super(builderMotionFind, support); + this.stopDistance = builderMotionFind.getStopDistance(support); + this.stopDistanceSquared = this.stopDistance * this.stopDistance; + this.holdDirectionDurationRange = builderMotionFind.getHoldDirectionDurationRange(support); + this.changeDirectionViewSector = builderMotionFind.getChangeDirectionViewSectorRadians(support); + this.jitterAngle = builderMotionFind.getDirectionJitterRadians(support); + double erraticDistance = builderMotionFind.getErraticDistance(support); + this.erraticDistanceSquared = erraticDistance * erraticDistance; + float erraticExtraJitter = builderMotionFind.getErraticExtraJitterRadians(support); + this.erraticJitter = MathUtil.clamp(this.jitterAngle + erraticExtraJitter, 0.0F, (float) Math.PI); + this.erraticChangeDurationMultiplier = builderMotionFind.getErraticChangeDurationMultiplier(support); + this.evade.setDistances(builderMotionFind.getSlowdownDistance(support), this.stopDistance); + this.evade.setFalloff(builderMotionFind.getFalloff(support)); + this.evade.setAdhereToDirectionHint(true); + } + + @Override + public void activate(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + super.activate(ref, role, componentAccessor); + this.holdDirectionTimeRemaining = 0.0; + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role role, + @Nullable InfoProvider infoProvider, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + NPCEntity npcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + float speedMultiplier = npcComponent.getCurrentHorizontalSpeedMultiplier(ref, componentAccessor); + if (speedMultiplier == 0.0F) { + desiredSteering.clear(); + return true; + } else { + this.holdDirectionTimeRemaining -= dt * speedMultiplier; + return super.computeSteering(ref, role, infoProvider, dt, desiredSteering, componentAccessor); + } + } + + @Override + protected boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role role, + Vector3d position, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d selfPosition = transformComponent.getPosition(); + Vector3f bodyRotation = transformComponent.getRotation(); + Vector3d lastTargetPosition = this.getLastTargetPosition(); + if (NPCPhysicsMath.inViewSector( + selfPosition.x, selfPosition.z, bodyRotation.getYaw(), this.changeDirectionViewSector, lastTargetPosition.x, lastTargetPosition.z + )) { + this.holdDirectionTimeRemaining = 0.0; + } + + if (this.holdDirectionTimeRemaining <= 0.0) { + boolean inErraticRange = selfPosition.distanceSquaredTo(lastTargetPosition) < this.erraticDistanceSquared; + float jitter = inErraticRange ? this.erraticJitter : this.jitterAngle; + this.fleeDirection = PhysicsMath.headingFromDirection(selfPosition.x - lastTargetPosition.x, selfPosition.z - lastTargetPosition.z) + + RandomExtra.randomRange(-jitter, jitter); + this.holdDirectionTimeRemaining = RandomExtra.randomRange(this.holdDirectionDurationRange); + if (inErraticRange) { + this.holdDirectionTimeRemaining = this.holdDirectionTimeRemaining * this.erraticChangeDurationMultiplier; + } + } + + this.evade.setPositions(selfPosition, lastTargetPosition); + this.evade.setDirectionHint(this.fleeDirection); + MotionController motionController = role.getActiveMotionController(); + double desiredAltitudeWeight = this.desiredAltitudeWeight >= 0.0 ? this.desiredAltitudeWeight : motionController.getDesiredAltitudeWeight(); + return this.scaleSteering(ref, role, this.evade, desiredSteering, desiredAltitudeWeight, componentAccessor); + } + + @Override + public boolean isGoalReached( + Ref ref, + AStarBase aStarBase, + @Nonnull AStarNode aStarNode, + MotionController motionController, + ComponentAccessor componentAccessor + ) { + return aStarNode.getEstimateToGoal() <= 0.0F; + } + + @Override + protected boolean isGoalReached( + Ref ref, + @Nonnull MotionController motionController, + Vector3d position, + Vector3d lastTestedPosition, + ComponentAccessor componentAccessor + ) { + return motionController.waypointDistanceSquared(position, lastTestedPosition) >= this.stopDistanceSquared; + } + + @Override + public float estimateToGoal(@Nonnull AStarBase aStarBase, Vector3d fromPosition, @Nonnull MotionController motionController) { + return Math.max(0.0F, (float)(this.stopDistance - motionController.waypointDistance(fromPosition, ((AStarWithTarget)aStarBase).getTargetPosition()))); + } + + @Override + public void findBestPath(@Nonnull AStarBase aStarBase, MotionController controller) { + aStarBase.buildBestPath(AStarNode::getEstimateToGoal, (oldV, v) -> v < oldV, Float.MAX_VALUE); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionTakeOff.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionTakeOff.java new file mode 100644 index 0000000..bf65bce --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionTakeOff.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.BodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionTakeOff; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.movement.controllers.MotionControllerFly; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BodyMotionTakeOff extends BodyMotionBase { + protected final double jumpSpeed; + + public BodyMotionTakeOff(@Nonnull BuilderBodyMotionTakeOff builderBodyMotionTakeOff) { + super(builderBodyMotionTakeOff); + this.jumpSpeed = builderBodyMotionTakeOff.getJumpSpeed(); + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role role, + @Nullable InfoProvider sensorInfo, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + if (!role.getActiveMotionController().matchesType(MotionControllerFly.class)) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + NPCEntity npcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + role.setActiveMotionController(ref, npcComponent, "Fly", componentAccessor); + Vector3d position = transformComponent.getPosition(); + position.setY(transformComponent.getPosition().getY() + 0.1); + ((MotionControllerFly)role.getActiveMotionController()).takeOff(ref, this.jumpSpeed, componentAccessor); + } + + return false; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionTeleport.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionTeleport.java new file mode 100644 index 0000000..f2517c7 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionTeleport.java @@ -0,0 +1,179 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.BodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionTeleport; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BodyMotionTeleport extends BodyMotionBase { + public static final int MAX_TRIES = 10; + public static final int MIN_MOVE_CHANGE = 1; + public static final double TELEPORT_COOLDOWN = 0.5; + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected static final ComponentType BOUNDING_BOX_COMPONENT_TYPE = BoundingBox.getComponentType(); + protected final double minOffset; + protected final double maxOffset; + protected final double maxYOffset; + protected final float angle; + protected final BodyMotionTeleport.Orientation orientation; + protected final Vector3d target = new Vector3d(); + protected final Vector3d offsetVector = new Vector3d(); + protected final Vector3d lastTriedTarget = new Vector3d(); + protected int tries; + protected double cooldown; + + public BodyMotionTeleport(@Nonnull BuilderBodyMotionTeleport builder) { + super(builder); + double[] offset = builder.getOffsetRadius(); + this.minOffset = offset[0]; + this.maxOffset = offset[1]; + this.maxYOffset = builder.getMaxYOffset(); + this.angle = builder.getSectorRadians() / 2.0F; + this.orientation = builder.getOrientation(); + } + + @Override + public void activate(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + this.tries = 10; + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role role, + @Nullable InfoProvider sensorInfo, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + if (sensorInfo != null && sensorInfo.getPositionProvider().providePosition(this.target)) { + double dist = this.target.distanceSquaredTo(this.lastTriedTarget); + if ((this.tries > 0 || !(dist < 1.0)) && !this.tickCooldown(dt)) { + if (dist > 1.0) { + this.tries = 10; + } + + this.lastTriedTarget.assign(this.target); + TransformComponent transformComponent = componentAccessor.getComponent(ref, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + Vector3d selfPosition = transformComponent.getPosition(); + double distance = selfPosition.distanceSquaredTo(this.target); + double maxOffset2 = this.maxOffset * this.maxOffset; + if (distance <= maxOffset2 + 1.0E-5) { + return false; + } else { + this.offsetVector.assign(selfPosition).subtract(this.target).setY(0.0); + this.offsetVector.setLength(RandomExtra.randomRange(this.minOffset, this.maxOffset)); + this.offsetVector.rotateY(RandomExtra.randomRange(-this.angle, this.angle)); + this.target.add(this.offsetVector); + MotionController motionController = role.getActiveMotionController(); + BoundingBox boundingBoxComponent = componentAccessor.getComponent(ref, BOUNDING_BOX_COMPONENT_TYPE); + if (motionController.translateToAccessiblePosition( + this.target, + boundingBoxComponent != null ? boundingBoxComponent.getBoundingBox() : null, + this.target.y - this.maxYOffset, + this.target.y + this.maxYOffset, + componentAccessor + ) + && motionController.isValidPosition(this.target, componentAccessor)) { + switch (this.orientation) { + case Unchanged: { + Vector3f bodyRotation = transformComponent.getRotation(); + componentAccessor.addComponent(ref, Teleport.getComponentType(), new Teleport(this.target, bodyRotation)); + break; + } + case TowardsTarget: { + double x = this.lastTriedTarget.getX() - this.target.getX(); + double y = this.lastTriedTarget.getY() - this.target.getY(); + double z = this.lastTriedTarget.getZ() - this.target.getZ(); + Vector3f bodyRotation = transformComponent.getRotation(); + float yaw; + float pitch; + if (x * x + z * z < 1.0E-5) { + yaw = bodyRotation.getYaw(); + pitch = bodyRotation.getPitch(); + } else { + yaw = PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(x, z)); + pitch = PhysicsMath.pitchFromDirection(x, y, z); + } + + componentAccessor.addComponent( + ref, Teleport.getComponentType(), new Teleport(this.target, new Vector3f(yaw, pitch, bodyRotation.getRoll())) + ); + break; + } + case UseTarget: { + Ref targetRef = sensorInfo.hasPosition() ? sensorInfo.getPositionProvider().getTarget() : null; + if (targetRef == null) { + return false; + } + + TransformComponent targetTransformComponent = componentAccessor.getComponent(targetRef, TRANSFORM_COMPONENT_TYPE); + + assert targetTransformComponent != null; + + Vector3f bodyRotation = targetTransformComponent.getRotation(); + componentAccessor.addComponent(ref, Teleport.getComponentType(), new Teleport(this.target, bodyRotation)); + } + } + + this.tries = 10; + this.cooldown = 0.5; + desiredSteering.clear(); + return false; + } else { + this.tries--; + return true; + } + } + } else { + return false; + } + } else { + return false; + } + } + + protected boolean tickCooldown(double dt) { + if (this.cooldown > 0.0) { + this.cooldown -= dt; + return true; + } else { + return false; + } + } + + public static enum Orientation implements Supplier { + Unchanged("Do not change orientation"), + TowardsTarget("Face towards the target"), + UseTarget("Use the target's orientation"); + + private final String description; + + private Orientation(String description) { + this.description = description; + } + + public String get() { + return this.description; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionWander.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionWander.java new file mode 100644 index 0000000..81da23e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionWander.java @@ -0,0 +1,28 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionWander; +import com.hypixel.hytale.server.npc.role.Role; +import javax.annotation.Nonnull; + +public class BodyMotionWander extends BodyMotionWanderBase { + public BodyMotionWander(@Nonnull BuilderBodyMotionWander builder, @Nonnull BuilderSupport builderSupport) { + super(builder, builderSupport); + } + + @Override + protected double constrainMove( + @Nonnull Ref ref, + @Nonnull Role role, + @Nonnull Vector3d probePosition, + @Nonnull Vector3d targetPosition, + double moveDist, + @Nonnull ComponentAccessor componentAccessor + ) { + return moveDist; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionWanderBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionWanderBase.java new file mode 100644 index 0000000..9848f0a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionWanderBase.java @@ -0,0 +1,484 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.entity.nameplate.Nameplate; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.BodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionWanderBase; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.movement.controllers.ProbeMoveData; +import com.hypixel.hytale.server.npc.movement.steeringforces.SteeringForcePursue; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.RoleDebugFlags; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.NPCPhysicsMath; +import java.util.Arrays; +import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class BodyMotionWanderBase extends BodyMotionBase { + public static final HytaleLogger LOGGER = NPCPlugin.get().getLogger(); + public static final int DIRECTION_COUNT = 32; + public static final float SEGMENT_ANGLE = (float) (Math.PI / 16); + public static final double MIN_DISTANCE_SHRINK = 0.3; + public static final double MIN_DISTANCE_SHRINK_SCALE = -1.4; + protected final double minWalkTime; + protected final double maxWalkTime; + protected final float minHeadingChange; + protected final float maxHeadingChange; + protected final byte minDirection; + protected final byte maxDirection; + protected final boolean relaxHeadingChange; + protected final double relativeSpeed; + protected final double minMoveDistance; + protected final double stopDistance; + protected final int testsPerTick; + protected final boolean isAvoidingBlockDamage; + protected final boolean isRelaxedMoveConstraints; + protected final double desiredAltitudeWeight; + protected final byte[] preOrderedDirections = new byte[32]; + protected final int insideConeCount; + protected final Vector3d targetPosition = new Vector3d(); + protected final Vector3d probeDirection = new Vector3d(); + protected final Vector3d probePosition = new Vector3d(); + protected final SteeringForcePursue seekTarget = new SteeringForcePursue(); + protected final ProbeMoveData probeMoveData = new ProbeMoveData(); + protected boolean debugSteer; + protected BodyMotionWanderBase.State state; + protected float angleOffset; + protected double probeDY; + protected double maxDistanceAbove; + protected double maxDistanceBelow; + protected double walkTime; + protected float walkHeading; + protected double walkDistance; + protected int directionIndex; + protected double desiredWalkDistance; + protected final double[] walkDistances = new double[32]; + protected final byte[] walkDirections = new byte[32]; + + public BodyMotionWanderBase(@Nonnull BuilderBodyMotionWanderBase builder, @Nonnull BuilderSupport builderSupport) { + super(builder); + this.minWalkTime = builder.getMinWalkTime(builderSupport); + this.maxWalkTime = builder.getMaxWalkTime(builderSupport); + this.minHeadingChange = (float) (Math.PI / 180.0) * builder.getMinHeadingChange(builderSupport); + this.maxHeadingChange = (float) (Math.PI / 180.0) * builder.getMaxHeadingChange(builderSupport); + this.relaxHeadingChange = builder.isRelaxHeadingChange(builderSupport); + this.minDirection = (byte)MathUtil.fastFloor(this.minHeadingChange / (float) (Math.PI / 16)); + this.maxDirection = (byte)MathUtil.fastCeil(this.maxHeadingChange / (float) (Math.PI / 16)); + this.relativeSpeed = builder.getRelativeSpeed(builderSupport); + this.minMoveDistance = builder.getMinMoveDistance(builderSupport); + this.stopDistance = builder.getStopDistance(builderSupport); + this.testsPerTick = builder.getTestsPerTick(builderSupport); + this.desiredAltitudeWeight = builder.getDesiredAltitudeWeight(builderSupport); + boolean avoidingBlockDamage = builder.isAvoidingBlockDamage(builderSupport); + this.isAvoidingBlockDamage = avoidingBlockDamage; + this.probeMoveData.setAvoidingBlockDamage(avoidingBlockDamage); + boolean relaxedMoveConstraints = builder.isRelaxedMoveConstraints(builderSupport); + this.isRelaxedMoveConstraints = relaxedMoveConstraints; + this.probeMoveData.setRelaxedMoveConstraints(relaxedMoveConstraints); + int count = 0; + + for (int i = this.minDirection; i <= this.maxDirection; i++) { + count = this.addPreOrderedDirection(i, count); + } + + this.insideConeCount = count; + + for (int var7 = 0; var7 < this.minDirection; var7++) { + count = this.addPreOrderedDirection(var7, count); + } + + for (int var8 = this.maxDirection + 1; var8 <= 16; var8++) { + count = this.addPreOrderedDirection(var8, count); + } + } + + @Override + public void activate(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + this.debugSteer = role.getDebugSupport().isDebugFlagSet(RoleDebugFlags.MotionControllerSteer); + NPCEntity npcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + this.restartSearch(ref, npcComponent, role.getActiveMotionController(), componentAccessor); + } + + @Override + public void deactivate(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + if (this.debugSteer) { + componentAccessor.removeComponent(ref, Nameplate.getComponentType()); + } + } + + @Override + public void motionControllerChanged( + @Nullable Ref ref, + @Nonnull NPCEntity npcComponent, + @Nonnull MotionController motionController, + @Nullable ComponentAccessor componentAccessor + ) { + this.restartSearch(ref, npcComponent, motionController, componentAccessor); + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role role, + @Nullable InfoProvider sensorInfo, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + NPCEntity npcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + MotionController activeMotionController = role.getActiveMotionController(); + if (this.debugSteer) { + LOGGER.at(Level.INFO) + .log( + "Wander compute: state=%s canAct=%s blocked=%s walkTime=%s", + this.state.toString(), + activeMotionController.canAct(ref, componentAccessor), + activeMotionController.isObstructed(), + this.walkTime + ); + String headline = this.state.toString(); + componentAccessor.putComponent(ref, Nameplate.getComponentType(), new Nameplate(headline)); + } + + desiredSteering.clear(); + float currentHorizontalSpeedMultiplier = npcComponent.getCurrentHorizontalSpeedMultiplier(ref, componentAccessor); + if (currentHorizontalSpeedMultiplier == 0.0F) { + this.state = BodyMotionWanderBase.State.STOPPED; + return true; + } else { + if (this.state == BodyMotionWanderBase.State.STOPPED) { + this.restartSearch(ref, npcComponent, activeMotionController, componentAccessor); + } + + if (activeMotionController.isInProgress()) { + if (this.state == BodyMotionWanderBase.State.WALKING) { + this.walkTime -= dt; + activeMotionController.setRelaxedMoveConstraints(this.isRelaxedMoveConstraints); + activeMotionController.setAvoidingBlockDamage(this.isAvoidingBlockDamage && activeMotionController.isAvoidingBlockDamage()); + } + + return true; + } else if (!activeMotionController.canAct(ref, componentAccessor)) { + return true; + } else { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3f bodyRotation = transformComponent.getRotation(); + if (activeMotionController.isObstructed() && this.state == BodyMotionWanderBase.State.WALKING) { + this.restartSearch(ref, npcComponent, activeMotionController, componentAccessor); + if (this.debugSteer) { + LOGGER.at(Level.INFO) + .log( + "Wander: Blocked state=%s directionIndex=%s walkTime=%s yaw=%s newYaw=%s", + this.state.toString(), + this.directionIndex, + this.walkTime, + (180.0F / (float)Math.PI) * bodyRotation.getYaw(), + (180.0F / (float)Math.PI) * this.walkHeading + ); + } + } + + if (this.state == BodyMotionWanderBase.State.SEARCHING) { + int testCount = 0; + + while (true) { + if (this.directionIndex == 32 || !this.relaxHeadingChange && this.directionIndex == this.insideConeCount) { + if (this.findBestDirection(ref, componentAccessor)) { + break; + } + + this.restartSearch(ref, npcComponent, activeMotionController, componentAccessor); + } else { + if (this.probeDirection(ref, this.directionIndex, role, componentAccessor)) { + break; + } + + this.directionIndex++; + } + + if (++testCount >= this.testsPerTick) { + return true; + } + } + + double stopDistance = Math.min(Math.max(this.stopDistance, activeMotionController.getCurrentTurnRadius()), this.walkDistance); + double slowdownDistance = Math.min(2.0 * stopDistance, this.walkDistance); + this.seekTarget.setDistances(slowdownDistance, stopDistance); + this.seekTarget.setComponentSelector(activeMotionController.getComponentSelector()); + this.seekTarget.setTargetPosition(this.targetPosition); + this.state = BodyMotionWanderBase.State.TURNING; + if (this.debugSteer) { + LOGGER.at(Level.INFO) + .log( + "Wander: Found move state=%s directionIndex=%s yaw=%s newYaw=%s", + this.state.toString(), + this.directionIndex, + (180.0F / (float)Math.PI) * bodyRotation.getYaw(), + (180.0F / (float)Math.PI) * this.walkHeading + ); + } + } + + if (this.state == BodyMotionWanderBase.State.TURNING) { + float heading = bodyRotation.getYaw(); + double turnAngle = NPCPhysicsMath.turnAngle(this.walkHeading, heading); + if (!(Math.abs(turnAngle) < 0.05235988F)) { + desiredSteering.setYaw(this.walkHeading); + if (this.debugSteer) { + LOGGER.at(Level.INFO) + .log( + "Wander: Turn state=%s turnAngle=%s heading=%s walkHeading=%s", + this.state.toString(), + 180.0F / (float)Math.PI * turnAngle, + (180.0F / (float)Math.PI) * heading, + (180.0F / (float)Math.PI) * this.walkHeading + ); + } + + return true; + } + + if (this.debugSteer) { + LOGGER.at(Level.INFO) + .log( + "Wander: Walk state=%s yaw=%s desiredYaw=%s walkTime=%s", + this.state.toString(), + (180.0F / (float)Math.PI) * bodyRotation.getYaw(), + (180.0F / (float)Math.PI) * this.walkHeading, + this.walkTime + ); + } + + this.state = BodyMotionWanderBase.State.WALKING; + } + + if (this.state == BodyMotionWanderBase.State.WALKING) { + this.seekTarget.setSelfPosition(transformComponent.getPosition()); + this.walkTime -= dt; + if (!this.seekTarget.compute(desiredSteering) || this.walkTime <= 0.0) { + this.restartSearch(ref, npcComponent, activeMotionController, componentAccessor); + if (this.debugSteer) { + LOGGER.at(Level.INFO) + .log( + "Wander: Walk done state=%s directionIndex=%s yaw=%s desiredYaw=%s", + this.state.toString(), + this.directionIndex, + (180.0F / (float)Math.PI) * bodyRotation.getYaw(), + (180.0F / (float)Math.PI) * this.walkHeading + ); + } + } + + activeMotionController.setRelaxedMoveConstraints(this.isRelaxedMoveConstraints); + activeMotionController.setAvoidingBlockDamage(this.isAvoidingBlockDamage && activeMotionController.isAvoidingBlockDamage()); + desiredSteering.scaleTranslation(this.relativeSpeed); + } + + return true; + } + } + } + + protected boolean findBestDirection(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + int index = -1; + double distance = 0.0; + double count = 0.0; + double average = 0.0; + + for (int i = 0; i < this.directionIndex; i++) { + double d = this.walkDistances[i]; + if (d > 0.0) { + count++; + average += d; + } + } + + if (count > 0.0) { + average /= count; + ThreadLocalRandom random = ThreadLocalRandom.current(); + + for (int ix = 0; ix < this.directionIndex; ix++) { + double d = this.walkDistances[ix]; + if (d > distance) { + distance = d; + index = ix; + if (!(d < average)) { + double r = random.nextDouble(); + if (r <= 0.5) { + double scale = r * -1.4 + 1.0; + distance = d * scale; + break; + } + } + } + } + } + + if (index == -1) { + return false; + } else { + this.walkHeading = this.toAngle(ref, this.walkDirections[index], componentAccessor); + this.walkDistance = distance; + this.computeTargetPosition(ref, this.walkHeading, this.walkDistance, componentAccessor); + return true; + } + } + + protected abstract double constrainMove( + @Nonnull Ref var1, + @Nonnull Role var2, + @Nonnull Vector3d var3, + @Nonnull Vector3d var4, + double var5, + @Nonnull ComponentAccessor var7 + ); + + protected void restartSearch( + @Nullable Ref ref, + @Nonnull NPCEntity npcComponent, + @Nonnull MotionController activeMotionController, + @Nullable ComponentAccessor componentAccessor + ) { + this.state = BodyMotionWanderBase.State.SEARCHING; + float currentHorizontalSpeedMultiplier = npcComponent.getCurrentHorizontalSpeedMultiplier(ref, componentAccessor); + this.walkTime = RandomExtra.randomRange(this.minWalkTime, this.maxWalkTime) / currentHorizontalSpeedMultiplier; + this.desiredWalkDistance = this.relativeSpeed * activeMotionController.getMaximumSpeed() * this.walkTime; + this.directionIndex = 0; + this.angleOffset = this.relaxHeadingChange ? ThreadLocalRandom.current().nextFloat() * (float) (Math.PI / 16) : 0.0F; + if (ref != null && componentAccessor != null) { + this.computeHeightRange(ref, activeMotionController, componentAccessor); + } + + this.probeDY = RandomExtra.randomRange(-this.maxDistanceBelow, this.maxDistanceAbove); + System.arraycopy(this.preOrderedDirections, 0, this.walkDirections, 0, 32); + ArrayUtil.shuffleArray(this.walkDirections, 0, this.insideConeCount, ThreadLocalRandom.current()); + if (this.insideConeCount < 31) { + ArrayUtil.shuffleArray(this.walkDirections, this.insideConeCount, 32, ThreadLocalRandom.current()); + } + + Arrays.fill(this.walkDistances, 0.0); + } + + protected void computeHeightRange( + @Nonnull Ref ref, @Nonnull MotionController motionController, @Nonnull ComponentAccessor componentAccessor + ) { + this.maxDistanceAbove = 0.0; + this.maxDistanceBelow = 0.0; + if (!motionController.is2D()) { + double wanderVerticalMovementRatio = motionController.getWanderVerticalMovementRatio(); + double maxVerticalDistance = wanderVerticalMovementRatio * this.desiredWalkDistance; + if (maxVerticalDistance != 0.0) { + MotionController.VerticalRange verticalRange = motionController.getDesiredVerticalRange(ref, componentAccessor); + double desiredAltitudeWeight = this.desiredAltitudeWeight >= 0.0 ? this.desiredAltitudeWeight : motionController.getDesiredAltitudeWeight(); + if (desiredAltitudeWeight > 0.0 && !verticalRange.isWithinRange()) { + maxVerticalDistance = this.desiredWalkDistance * (wanderVerticalMovementRatio + (1.0 - wanderVerticalMovementRatio) * desiredAltitudeWeight); + } + + double y = verticalRange.current; + double negativeMaxVerticalDistance = -maxVerticalDistance * desiredAltitudeWeight; + this.maxDistanceAbove = MathUtil.clamp(verticalRange.max - y, negativeMaxVerticalDistance, maxVerticalDistance); + this.maxDistanceBelow = MathUtil.clamp(y - verticalRange.min, negativeMaxVerticalDistance, maxVerticalDistance); + } + } + } + + protected boolean probeDirection(@Nonnull Ref ref, int dirIndex, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + int direction = this.walkDirections[dirIndex]; + MotionController motionController = role.getActiveMotionController(); + float heading = this.toAngle(ref, direction, componentAccessor); + this.computeTargetPosition(ref, heading, this.desiredWalkDistance, componentAccessor); + double constrainDistance = this.constrainMove(ref, role, this.probePosition, this.targetPosition, this.desiredWalkDistance, componentAccessor); + if (constrainDistance < 1.0E-5) { + return false; + } else { + if (constrainDistance < this.desiredWalkDistance) { + this.probeDirection.scale(constrainDistance / this.desiredWalkDistance); + } + + this.probeMoveData.setAvoidingBlockDamage(!motionController.willReceiveBlockDamage()); + double moveDistance = motionController.probeMove(ref, this.probePosition, this.probeDirection, this.probeMoveData, componentAccessor); + if (moveDistance < 1.0E-5) { + return false; + } else { + this.walkDistances[dirIndex] = moveDistance; + if (moveDistance < this.desiredWalkDistance) { + return false; + } else { + if (moveDistance < constrainDistance) { + this.probeDirection.scale(moveDistance / constrainDistance); + } + + this.walkDistance = moveDistance; + this.walkHeading = heading; + this.targetPosition.assign(this.probePosition).add(this.probeDirection); + return true; + } + } + } + } + + private void computeTargetPosition(@Nonnull Ref ref, float heading, double distance, @Nonnull ComponentAccessor componentAccessor) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + this.probePosition.assign(transformComponent.getPosition()); + this.probeDirection.x = PhysicsMath.headingX(heading) * distance; + this.probeDirection.y = this.probeDY * distance / this.desiredWalkDistance; + this.probeDirection.z = PhysicsMath.headingZ(heading) * distance; + this.targetPosition.assign(this.probePosition).add(this.probeDirection); + } + + protected float toAngle(@Nonnull Ref ref, int direction, @Nonnull ComponentAccessor componentAccessor) { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + return PhysicsMath.normalizeAngle(transformComponent.getRotation().getYaw() + direction * (float) (Math.PI / 16) + this.angleOffset); + } + + private int addPreOrderedDirection(int direction, int count) { + this.preOrderedDirections[count++] = (byte)direction; + if (direction != 0 && direction != 16) { + this.preOrderedDirections[count++] = (byte)(32 - direction); + } + + return count; + } + + public static enum State { + SEARCHING, + TURNING, + WALKING, + STOPPED; + + private State() { + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionWanderInCircle.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionWanderInCircle.java new file mode 100644 index 0000000..bb76577 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionWanderInCircle.java @@ -0,0 +1,111 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.entity.group.EntityGroup; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.flock.FlockPlugin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionWanderInCircle; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.util.NPCPhysicsMath; +import javax.annotation.Nonnull; + +public class BodyMotionWanderInCircle extends BodyMotionWanderBase { + protected final double radius; + protected final boolean flock; + protected final boolean useSphere; + protected final Vector3d referencePoint = new Vector3d(); + + public BodyMotionWanderInCircle(@Nonnull BuilderBodyMotionWanderInCircle builder, @Nonnull BuilderSupport builderSupport) { + super(builder, builderSupport); + this.radius = builder.getRadius(builderSupport); + this.flock = builder.isFlock(); + this.useSphere = builder.isUseSphere(); + } + + @Override + protected double constrainMove( + @Nonnull Ref ref, + @Nonnull Role role, + @Nonnull Vector3d probePosition, + @Nonnull Vector3d targetPosition, + double moveDist, + @Nonnull ComponentAccessor componentAccessor + ) { + Vector3d referencePoint = this.getReferencePoint(ref, componentAccessor); + double r2 = this.radius * this.radius; + MotionController activeMotionController = role.getActiveMotionController(); + if (this.useSphere) { + double endDist2 = activeMotionController.waypointDistanceSquared(targetPosition, referencePoint); + if (endDist2 <= r2) { + return moveDist; + } else { + double startDist2 = activeMotionController.waypointDistanceSquared(probePosition, referencePoint); + if (startDist2 >= r2) { + return endDist2 <= startDist2 ? moveDist : 0.0; + } else { + return NPCPhysicsMath.intersectLineSphereLerp( + referencePoint, this.radius, probePosition, targetPosition, activeMotionController.getComponentSelector() + ) + * moveDist; + } + } + } else { + Vector3d n = activeMotionController.getWorldNormal(); + double endDist2 = NPCPhysicsMath.squaredDistProjected(targetPosition.getX(), targetPosition.getY(), targetPosition.getZ(), referencePoint, n); + if (endDist2 <= r2) { + return moveDist; + } else { + double startDist2 = NPCPhysicsMath.squaredDistProjected(probePosition.getX(), probePosition.getY(), probePosition.getZ(), referencePoint, n); + if (startDist2 >= r2) { + return endDist2 <= startDist2 ? moveDist : 0.0; + } else { + return moveDist * Math.max(0.0, NPCPhysicsMath.rayCircleIntersect(probePosition, targetPosition, referencePoint, this.radius, n)); + } + } + } + } + + protected Vector3d getReferencePoint(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + if (this.flock) { + World world = componentAccessor.getExternalData().getWorld(); + TransformComponent entityTransformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert entityTransformComponent != null; + + Vector3d entityPosition = entityTransformComponent.getPosition(); + Ref flockReference = FlockPlugin.getFlockReference(ref, componentAccessor); + if (flockReference != null) { + EntityGroup entityGroupComponent = componentAccessor.getComponent(flockReference, EntityGroup.getComponentType()); + + assert entityGroupComponent != null; + + Ref leaderRef = entityGroupComponent.getLeaderRef(); + if (leaderRef.isValid()) { + TransformComponent leaderTransformComponent = componentAccessor.getComponent(leaderRef, TransformComponent.getComponentType()); + + assert leaderTransformComponent != null; + + Vector3d leaderPosition = leaderTransformComponent.getPosition(); + this.referencePoint.assign(leaderPosition.getX(), leaderPosition.getY(), leaderPosition.getZ()); + return this.referencePoint; + } + } + + this.referencePoint.assign(entityPosition); + return this.referencePoint; + } else { + NPCEntity npcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + return npcComponent.getLeashPoint(); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionWanderInRect.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionWanderInRect.java new file mode 100644 index 0000000..c3d577c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/BodyMotionWanderInRect.java @@ -0,0 +1,131 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionWanderInRect; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import javax.annotation.Nonnull; + +public class BodyMotionWanderInRect extends BodyMotionWanderBase { + public static final int LEFT = 1; + public static final int RIGHT = 2; + public static final int BOTTOM = 4; + public static final int TOP = 8; + public static final int VERTICAL_MASK = 12; + public static final int HORIZONTAL_MASK = 3; + protected final double width; + protected final double depth; + protected final double halfWidth; + protected final double halfDepth; + + public BodyMotionWanderInRect(@Nonnull BuilderBodyMotionWanderInRect builder, @Nonnull BuilderSupport builderSupport) { + super(builder, builderSupport); + this.width = builder.getWidth(); + this.halfWidth = this.width / 2.0; + this.depth = builder.getDepth(); + this.halfDepth = this.depth / 2.0; + } + + @Override + protected double constrainMove( + @Nonnull Ref ref, + @Nonnull Role role, + @Nonnull Vector3d probePosition, + @Nonnull Vector3d targetPosition, + double moveDist, + @Nonnull ComponentAccessor componentAccessor + ) { + NPCEntity npcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + Vector3d leash = npcComponent.getLeashPoint(); + double leashX = leash.getX(); + double leashZ = leash.getZ(); + double endX = targetPosition.x - leashX; + double endZ = targetPosition.z - leashZ; + int endCode = this.sectorCode(endX, endZ); + if (endCode == 0) { + return moveDist; + } else { + double startX = probePosition.x - leashX; + double startZ = probePosition.z - leashZ; + int startCode = this.sectorCode(startX, startZ); + if (startCode != 0) { + if ((startCode & endCode) != 0) { + return this.distanceSquared(endX, endZ, endCode) < this.distanceSquared(startX, startZ, startCode) ? moveDist : 0.0; + } else { + int or = startCode | endCode; + if ((or & 12) == 12 || (or & 3) == 3) { + return 0.0; + } else { + return this.distanceSquared(endX, endZ, endCode) < this.distanceSquared(startX, startZ, startCode) ? moveDist : 0.0; + } + } + } else { + double dx = endX - startX; + double dz = endZ - startZ; + double scaleX; + if ((endCode & 1) == 1) { + scaleX = (-this.halfWidth - startX) / dx; + } else if ((endCode & 2) == 2) { + scaleX = (this.halfWidth - startX) / dx; + } else { + scaleX = 1.0; + } + + double scaleZ; + if ((endCode & 4) == 4) { + scaleZ = (-this.halfDepth - startZ) / dz; + } else if ((endCode & 8) == 8) { + scaleZ = (this.halfDepth - startZ) / dz; + } else { + scaleZ = 1.0; + } + + if (scaleX < 0.0 || scaleX > 1.0) { + throw new IllegalArgumentException("WanderInRect: Constrained X outside of allowed range!"); + } else if (!(scaleZ < 0.0) && !(scaleZ > 1.0)) { + return moveDist * Math.min(scaleX, scaleZ); + } else { + throw new IllegalArgumentException("WanderInRect: Constrained Z outside of allowed range!"); + } + } + } + } + + protected int sectorCode(double x, double z) { + int code = 0; + if (x < -this.halfWidth) { + code |= 1; + } else if (x > this.halfWidth) { + code |= 2; + } + + if (z < -this.halfDepth) { + code |= 4; + } else if (z > this.halfDepth) { + code |= 8; + } + + return code; + } + + protected double distanceSquared(double x, double z, int sector) { + return switch (sector) { + case 1 -> (x + this.halfWidth) * (x + this.halfWidth); + case 2 -> (x - this.halfWidth) * (x - this.halfWidth); + default -> 0.0; + case 4 -> (z + this.halfDepth) * (z + this.halfDepth); + case 5 -> (x + this.halfWidth) * (x + this.halfWidth) + (z + this.halfDepth) * (z + this.halfDepth); + case 6 -> (x - this.halfWidth) * (x - this.halfWidth) + (z + this.halfDepth) * (z + this.halfDepth); + case 8 -> (z - this.halfDepth) * (z - this.halfDepth); + case 9 -> (x + this.halfWidth) * (x + this.halfWidth) + (z - this.halfDepth) * (z - this.halfDepth); + case 10 -> (x - this.halfWidth) * (x - this.halfWidth) + (z - this.halfDepth) * (z - this.halfDepth); + }; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/SensorInAir.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/SensorInAir.java new file mode 100644 index 0000000..2335ebb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/SensorInAir.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorInAir extends SensorBase { + public SensorInAir(@Nonnull BuilderSensorBase builderSensorBase) { + super(builderSensorBase); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + return super.matches(ref, role, dt, store) && role.getActiveMotionController().inAir(); + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/SensorMotionController.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/SensorMotionController.java new file mode 100644 index 0000000..6a923a2 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/SensorMotionController.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderSensorMotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorMotionController extends SensorBase { + protected final String motionControllerName; + + public SensorMotionController(@Nonnull BuilderSensorMotionController builderSensorMotionController) { + super(builderSensorMotionController); + this.motionControllerName = builderSensorMotionController.getMotionControllerName(); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + return super.matches(ref, role, dt, store) && this.motionControllerName.equalsIgnoreCase(role.getActiveMotionController().getType()); + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/SensorNav.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/SensorNav.java new file mode 100644 index 0000000..9b18a27 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/SensorNav.java @@ -0,0 +1,45 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderSensorNav; +import com.hypixel.hytale.server.npc.movement.NavState; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class SensorNav extends SensorBase { + protected final EnumSet navStates; + protected final double throttleDuration; + protected final double targetDeltaSquared; + + public SensorNav(@Nonnull BuilderSensorNav builderSensorNav, @Nonnull BuilderSupport builderSupport) { + super(builderSensorNav); + this.navStates = builderSensorNav.getNavStates(builderSupport); + this.throttleDuration = builderSensorNav.getThrottleDuration(builderSupport); + double targetDelta = builderSensorNav.getTargetDelta(builderSupport); + this.targetDeltaSquared = targetDelta * targetDelta; + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + return false; + } else { + MotionController motionController = role.getActiveMotionController(); + return (this.throttleDuration == 0.0 || motionController.getThrottleDuration() >= this.throttleDuration) + && (this.targetDeltaSquared == 0.0 || motionController.getTargetDeltaSquared() >= this.targetDeltaSquared) + && (this.navStates.isEmpty() || this.navStates.contains(motionController.getNavState())); + } + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/SensorOnGround.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/SensorOnGround.java new file mode 100644 index 0000000..83c47bf --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/SensorOnGround.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorOnGround extends SensorBase { + public SensorOnGround(@Nonnull BuilderSensorBase builderSensorBase) { + super(builderSensorBase); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + return super.matches(ref, role, dt, store) && role.getActiveMotionController().onGround(); + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderActionCrouch.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderActionCrouch.java new file mode 100644 index 0000000..c91e67d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderActionCrouch.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.ActionCrouch; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public class BuilderActionCrouch extends BuilderActionBase { + protected final BooleanHolder crouching = new BooleanHolder(); + + public BuilderActionCrouch() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Set NPC crouching state"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionCrouch(this, this.crouching.get(builderSupport.getExecutionContext())); + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getBoolean(data, "Crouch", this.crouching, true, BuilderDescriptorState.Stable, "True for crouching, false for non-crouching", null); + return this; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderActionOverrideAltitude.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderActionOverrideAltitude.java new file mode 100644 index 0000000..312fa89 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderActionOverrideAltitude.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.ActionOverrideAltitude; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public class BuilderActionOverrideAltitude extends BuilderActionBase { + protected final NumberArrayHolder desiredAltitudeRange = new NumberArrayHolder(); + + public BuilderActionOverrideAltitude() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Temporarily override the preferred altitude of a flying NPC"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Temporarily override the preferred altitude of a flying NPC. Must be refreshed each tick"; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionOverrideAltitude(this, builderSupport); + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireDoubleRange( + data, + "DesiredAltitudeRange", + this.desiredAltitudeRange, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "The desired altitude range", + null + ); + return this; + } + + public double[] getDesiredAltitudeRange(@Nonnull BuilderSupport support) { + return this.desiredAltitudeRange.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderActionRecomputePath.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderActionRecomputePath.java new file mode 100644 index 0000000..81e11fd --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderActionRecomputePath.java @@ -0,0 +1,43 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.ActionRecomputePath; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public class BuilderActionRecomputePath extends BuilderActionBase implements Builder { + public BuilderActionRecomputePath() { + } + + @Nonnull + public ActionRecomputePath build(BuilderSupport builderSupport) { + return new ActionRecomputePath(this); + } + + @Nonnull + public BuilderActionRecomputePath readConfig(JsonElement data) { + return this; + } + + @Nonnull + @Override + public String getShortDescription() { + return "Force recomputation of path finder solution"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionFind.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionFind.java new file mode 100644 index 0000000..ee98db5 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionFind.java @@ -0,0 +1,128 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.RelationalOperator; +import com.hypixel.hytale.server.npc.corecomponents.movement.BodyMotionFind; +import javax.annotation.Nonnull; + +public class BuilderBodyMotionFind extends BuilderBodyMotionFindWithTarget { + private static final double[] DEFAULT_HEIGHT_DIFFERENCE = new double[]{-1.0, 1.0}; + private final BooleanHolder reachable = new BooleanHolder(); + private final NumberArrayHolder heightDifference = new NumberArrayHolder(); + private final DoubleHolder slowDownDistance = new DoubleHolder(); + private final DoubleHolder stopDistance = new DoubleHolder(); + private final DoubleHolder abortDistance = new DoubleHolder(); + private final DoubleHolder falloff = new DoubleHolder(); + private final DoubleHolder switchToSteeringDistance = new DoubleHolder(); + + public BuilderBodyMotionFind() { + } + + public BodyMotionFind build(@Nonnull BuilderSupport builderSupport) { + return new BodyMotionFind(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Chase target"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Move towards a target using path finding or steering"; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + public BuilderBodyMotionFind readConfig(@Nonnull JsonElement data) { + super.readConfig(data); + this.getBoolean( + data, "Reachable", this.reachable, false, BuilderDescriptorState.Experimental, "Target must be reachable so that hitboxes can overlap", null + ); + this.getDoubleRange( + data, + "HeightDifference", + this.heightDifference, + DEFAULT_HEIGHT_DIFFERENCE, + DoubleSequenceValidator.monotonic(), + BuilderDescriptorState.Experimental, + "Height difference allowed to target", + null + ); + this.getDouble( + data, + "SlowDownDistance", + this.slowDownDistance, + 8.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "Distance when to slow down when approaching", + null + ); + this.getDouble( + data, "StopDistance", this.stopDistance, 10.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "Distance to stop at", null + ); + this.getDouble( + data, "AbortDistance", this.abortDistance, 96.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "Distance to abort behaviour", null + ); + this.getDouble( + data, "Falloff", this.falloff, 3.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "Deceleration when approaching target", null + ); + this.getDouble( + data, + "SwitchToSteeringDistance", + this.switchToSteeringDistance, + 20.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "Distance below NPC can test if target is reachable and abort existing path", + null + ); + this.validateDoubleRelation(this.slowDownDistance, RelationalOperator.GreaterEqual, this.stopDistance); + this.requireFeature(Feature.AnyPosition); + return this; + } + + public boolean getReachable(@Nonnull BuilderSupport support) { + return this.reachable.get(support.getExecutionContext()); + } + + public double getStopDistance(@Nonnull BuilderSupport support) { + return this.stopDistance.get(support.getExecutionContext()); + } + + public double[] getHeightDifference(@Nonnull BuilderSupport support) { + return this.heightDifference.get(support.getExecutionContext()); + } + + public double getAbortDistance(@Nonnull BuilderSupport support) { + return this.abortDistance.get(support.getExecutionContext()); + } + + public double getFalloff(@Nonnull BuilderSupport support) { + return this.falloff.get(support.getExecutionContext()); + } + + public double getSlowDownDistance(@Nonnull BuilderSupport builderSupport) { + return this.slowDownDistance.get(builderSupport.getExecutionContext()); + } + + public double getSwitchToSteeringDistance(@Nonnull BuilderSupport support) { + return this.switchToSteeringDistance.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionFindBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionFindBase.java new file mode 100644 index 0000000..f672e78 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionFindBase.java @@ -0,0 +1,314 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.IntHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.IntSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderBodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.BodyMotionFindBase; +import com.hypixel.hytale.server.npc.instructions.BodyMotion; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public abstract class BuilderBodyMotionFindBase extends BuilderBodyMotionBase implements Builder { + protected static final double[] THROTTLE_DELAY = new double[]{3.0, 5.0}; + @Nonnull + protected EnumSet parsedDebugFlags = EnumSet.noneOf(BodyMotionFindBase.DebugFlags.class); + protected String debugFlags; + protected final IntHolder nodesPerTick = new IntHolder(); + protected final IntHolder maxPathLength = new IntHolder(); + protected final IntHolder maxOpenNodes = new IntHolder(); + protected final IntHolder maxTotalNodes = new IntHolder(); + protected final BooleanHolder diagonalMoves = new BooleanHolder(); + protected final BooleanHolder useBestPath = new BooleanHolder(); + protected final BooleanHolder buildOptimisedPath = new BooleanHolder(); + protected final IntHolder pathSmoothing = new IntHolder(); + protected final DoubleHolder relativeSpeed = new DoubleHolder(); + protected final DoubleHolder relativeSpeedWaypoint = new DoubleHolder(); + protected final DoubleHolder waypointRadius = new DoubleHolder(); + protected final DoubleHolder rejectionWeight = new DoubleHolder(); + protected final DoubleHolder blendHeading = new DoubleHolder(); + protected final BooleanHolder isAvoidingBlockDamage = new BooleanHolder(); + protected final BooleanHolder isRelaxedMoveConstraints = new BooleanHolder(); + protected final NumberArrayHolder throttleDelayRangeHolder = new NumberArrayHolder(); + protected final IntHolder throttleIgnoreCount = new IntHolder(); + protected final BooleanHolder useSteering = new BooleanHolder(); + protected final BooleanHolder usePathfinder = new BooleanHolder(); + protected final BooleanHolder skipSteering = new BooleanHolder(); + protected final DoubleHolder minPathLength = new DoubleHolder(); + protected final DoubleHolder desiredAltitudeWeight = new DoubleHolder(); + protected final boolean enableSteering; + + public BuilderBodyMotionFindBase() { + this.enableSteering = true; + } + + public BuilderBodyMotionFindBase(boolean enableSteering) { + this.enableSteering = enableSteering; + } + + @Nonnull + public BuilderBodyMotionFindBase readConfig(@Nonnull JsonElement data) { + this.getDouble( + data, + "RelativeSpeed", + this.relativeSpeed, + 1.0, + DoubleRangeValidator.fromExclToIncl(0.0, 2.0), + BuilderDescriptorState.Stable, + "Maximum relative speed the NPC should move", + null + ); + this.getDouble( + data, + "RelativeSpeedWaypoint", + this.relativeSpeedWaypoint, + 0.5, + DoubleRangeValidator.fromExclToIncl(0.0, 1.0), + BuilderDescriptorState.Stable, + "Maximum relative speed the NPC should move close to waypoints", + null + ); + this.getDouble( + data, + "WaypointRadius", + this.waypointRadius, + 0.5, + DoubleSingleValidator.greater(0.1), + BuilderDescriptorState.Stable, + "Radius to slow down around waypoints", + null + ); + this.getBoolean(data, "UseBestPath", this.useBestPath, true, BuilderDescriptorState.Stable, "Use best partial path if goal can't be reached", null); + this.getDoubleRange( + data, + "ThrottleDelayRange", + this.throttleDelayRangeHolder, + THROTTLE_DELAY, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "Time to delay after no path finding solution found", + null + ); + this.getInt( + data, + "ThrottleIgnoreCount", + this.throttleIgnoreCount, + 3, + IntSingleValidator.greaterEqual0(), + BuilderDescriptorState.Stable, + "How often no valid path solution can be found before throttling delay is applied", + null + ); + this.getBoolean( + data, "BuildOptimisedPath", this.buildOptimisedPath, true, BuilderDescriptorState.Stable, "Try to reduce number of nodes of generated path", null + ); + this.getBoolean( + data, "AvoidBlockDamage", this.isAvoidingBlockDamage, true, BuilderDescriptorState.Stable, "Should avoid environmental damage from blocks", null + ); + this.getBoolean( + data, + "RelaxedMoveConstraints", + this.isRelaxedMoveConstraints, + true, + BuilderDescriptorState.Stable, + "NPC can do movements like wading (depends on motion controller type)", + null + ); + this.getDouble( + data, + "BlendHeading", + this.blendHeading, + 0.5, + DoubleRangeValidator.between01(), + BuilderDescriptorState.Stable, + "Relative rotation angle into next waypoint when arriving at current waypoint", + null + ); + this.getInt( + data, + "PathSmoothing", + this.pathSmoothing, + 2, + IntSingleValidator.greaterEqual0(), + BuilderDescriptorState.Stable, + "Try to smooth followed path. Larger values smooth more.", + null + ); + this.getDouble( + data, + "RejectionWeight", + this.rejectionWeight, + 3.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "Weight of rejection vector pushing entity closer to original path", + null + ); + if (this.enableSteering) { + this.getBoolean(data, "UseSteering", this.useSteering, true, BuilderDescriptorState.Stable, "Use simple/cheap steering if available", null); + this.getBoolean(data, "SkipSteering", this.skipSteering, true, BuilderDescriptorState.Experimental, "Skip steering if target not reachable", null); + this.getBoolean(data, "UsePathfinder", this.usePathfinder, true, BuilderDescriptorState.Stable, "Use path finder", null); + } + + this.getDouble( + data, + "MinPathLength", + this.minPathLength, + 2.0, + DoubleSingleValidator.greaterEqual0(), + BuilderDescriptorState.Experimental, + "Minimum length of path required when not able to reach target (should be greater equal 2)", + null + ); + this.getBoolean(data, "DiagonalMoves", this.diagonalMoves, true, BuilderDescriptorState.Stable, "Allow diagonal moves", null); + this.getInt(data, "StepsPerTick", this.nodesPerTick, 50, IntSingleValidator.greater0(), BuilderDescriptorState.Stable, "Steps per iteration", null); + this.getInt( + data, + "MaxPathLength", + this.maxPathLength, + 200, + IntSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "Max path steps before aborting path finding", + null + ); + this.getInt( + data, + "MaxOpenNodes", + this.maxOpenNodes, + 200, + IntSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "Max open nodes before aborting path finding", + null + ); + this.getInt( + data, + "MaxTotalNodes", + this.maxTotalNodes, + 900, + IntSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "Max total node before aborting path finding", + null + ); + this.getString(data, "Debug", e -> this.debugFlags = e, "", null, BuilderDescriptorState.Stable, "Debugging flags", null); + this.getDouble( + data, + "DesiredAltitudeWeight", + this.desiredAltitudeWeight, + -1.0, + DoubleRangeValidator.between(-1.0, 1.0), + BuilderDescriptorState.Stable, + "How much this NPC prefers being within the desired height range", + "How much this NPC prefers being within the desired height range. 0 means it doesn't care much, 1 means it will do its best to get there fast. Values below 0 mean the default in the motion controller will be used." + ); + if (this.debugFlags != null && !this.debugFlags.isEmpty()) { + this.toSet("Debug", BodyMotionFindBase.DebugFlags.class, this.parsedDebugFlags, this.debugFlags); + } + + return this; + } + + @Nonnull + public EnumSet getParsedDebugFlags() { + return this.parsedDebugFlags; + } + + public int getNodesPerTick(@Nonnull BuilderSupport support) { + return this.nodesPerTick.get(support.getExecutionContext()); + } + + public int getMaxPathLength(@Nonnull BuilderSupport support) { + return this.maxPathLength.get(support.getExecutionContext()); + } + + public int getMaxOpenNodes(@Nonnull BuilderSupport support) { + return this.maxOpenNodes.get(support.getExecutionContext()); + } + + public int getMaxTotalNodes(@Nonnull BuilderSupport support) { + return this.maxTotalNodes.get(support.getExecutionContext()); + } + + public boolean isDiagonalMoves(@Nonnull BuilderSupport support) { + return this.diagonalMoves.get(support.getExecutionContext()); + } + + public boolean getUseBestPath(@Nonnull BuilderSupport support) { + return this.useBestPath.get(support.getExecutionContext()); + } + + public boolean isBuildOptimisedPath(@Nonnull BuilderSupport support) { + return this.buildOptimisedPath.get(support.getExecutionContext()); + } + + public int getPathSmoothing(@Nonnull BuilderSupport support) { + return this.pathSmoothing.get(support.getExecutionContext()); + } + + public double getRelativeSpeed(@Nonnull BuilderSupport support) { + return this.relativeSpeed.get(support.getExecutionContext()); + } + + public double getRelativeSpeedWaypoint(@Nonnull BuilderSupport support) { + return this.relativeSpeedWaypoint.get(support.getExecutionContext()); + } + + public double getWaypointRadius(@Nonnull BuilderSupport support) { + return this.waypointRadius.get(support.getExecutionContext()); + } + + public double getRejectionWeight(@Nonnull BuilderSupport support) { + return this.rejectionWeight.get(support.getExecutionContext()); + } + + public double getBlendHeading(@Nonnull BuilderSupport support) { + return this.blendHeading.get(support.getExecutionContext()); + } + + public boolean isAvoidingBlockDamage(@Nonnull BuilderSupport support) { + return this.isAvoidingBlockDamage.get(support.getExecutionContext()); + } + + public boolean isRelaxedMoveConstraints(@Nonnull BuilderSupport support) { + return this.isRelaxedMoveConstraints.get(support.getExecutionContext()); + } + + public double[] getThrottleDelayRange(@Nonnull BuilderSupport support) { + return this.throttleDelayRangeHolder.get(support.getExecutionContext()); + } + + public int getThrottleIgnoreCount(@Nonnull BuilderSupport support) { + return this.throttleIgnoreCount.get(support.getExecutionContext()); + } + + public boolean isUseSteering(@Nonnull BuilderSupport support) { + return this.enableSteering && this.useSteering.get(support.getExecutionContext()); + } + + public boolean isUsePathfinder(@Nonnull BuilderSupport support) { + return !this.enableSteering || this.usePathfinder.get(support.getExecutionContext()); + } + + public boolean isSkipSteering(@Nonnull BuilderSupport support) { + return this.enableSteering && this.skipSteering.get(support.getExecutionContext()); + } + + public double getMinPathLength(@Nonnull BuilderSupport support) { + return this.minPathLength.get(support.getExecutionContext()); + } + + public double getDesiredAltitudeWeight(@Nonnull BuilderSupport support) { + return this.desiredAltitudeWeight.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionFindWithTarget.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionFindWithTarget.java new file mode 100644 index 0000000..26235b3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionFindWithTarget.java @@ -0,0 +1,104 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.FloatHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import javax.annotation.Nonnull; + +public abstract class BuilderBodyMotionFindWithTarget extends BuilderBodyMotionFindBase { + protected final DoubleHolder minMoveDistanceWait = new DoubleHolder(); + protected final DoubleHolder minMoveDistanceRecompute = new DoubleHolder(); + protected final FloatHolder recomputeConeAngle = new FloatHolder(); + protected final DoubleHolder minMoveDistanceReproject = new DoubleHolder(); + protected final BooleanHolder adjustRangeByHitboxSize = new BooleanHolder(); + + public BuilderBodyMotionFindWithTarget() { + } + + public BuilderBodyMotionFindWithTarget(boolean enableSteering) { + super(enableSteering); + } + + @Nonnull + @Override + public BuilderBodyMotionFindBase readConfig(@Nonnull JsonElement data) { + super.readConfig(data); + this.getDouble( + data, + "WaitDistance", + this.minMoveDistanceWait, + 1.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Experimental, + "Minimum distance target needs to move before recomputing path when no path can be found", + null + ); + this.getDouble( + data, + "RecomputeDistance", + this.minMoveDistanceRecompute, + 10.0, + DoubleSingleValidator.greaterEqual0(), + BuilderDescriptorState.Experimental, + "Maximum distance target can move before path is recomputed or 0 to supress recomputation", + null + ); + this.getDouble( + data, + "ReprojectDistance", + this.minMoveDistanceReproject, + 0.5, + DoubleSingleValidator.greaterEqual0(), + BuilderDescriptorState.Experimental, + "Maximum distance target can move before position is reprojected", + null + ); + this.getBoolean( + data, + "AdjustRangeByHitboxSize", + this.adjustRangeByHitboxSize, + false, + BuilderDescriptorState.Stable, + "Correct range by hitbox sizes of involved entities", + null + ); + this.getFloat( + data, + "RecomputeConeAngle", + this.recomputeConeAngle, + 0.0, + DoubleRangeValidator.between(0.0, 360.0), + BuilderDescriptorState.Experimental, + "Recompute path when target leaves cone from initial position to target", + null + ); + this.requireFeature(Feature.AnyPosition); + return this; + } + + public double getMinMoveDistanceWait(@Nonnull BuilderSupport support) { + return this.minMoveDistanceWait.get(support.getExecutionContext()); + } + + public double getMinMoveDistanceRecompute(@Nonnull BuilderSupport support) { + return this.minMoveDistanceRecompute.get(support.getExecutionContext()); + } + + public double getRecomputeConeAngle(@Nonnull BuilderSupport support) { + return this.recomputeConeAngle.get(support.getExecutionContext()) * (float) (Math.PI / 180.0); + } + + public boolean isAdjustRangeByHitboxSize(@Nonnull BuilderSupport support) { + return this.adjustRangeByHitboxSize.get(support.getExecutionContext()); + } + + public double getMinMoveDistanceReproject(@Nonnull BuilderSupport support) { + return this.minMoveDistanceReproject.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionLand.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionLand.java new file mode 100644 index 0000000..8ca33ad --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionLand.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.movement.BodyMotionLand; +import com.hypixel.hytale.server.npc.movement.controllers.MotionControllerFly; +import com.hypixel.hytale.server.npc.movement.controllers.MotionControllerWalk; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; + +public class BuilderBodyMotionLand extends BuilderBodyMotionFind { + protected final DoubleHolder goalLenience = new DoubleHolder(); + + public BuilderBodyMotionLand() { + } + + @Nonnull + public BodyMotionLand build(@Nonnull BuilderSupport builderSupport) { + return new BodyMotionLand(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Try to land at the given position"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Try to land at the given position using a seek like motion"; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + public BuilderBodyMotionLand readConfig(@Nonnull JsonElement data) { + super.readConfig(data); + this.getDouble( + data, + "GoalLenience", + this.goalLenience, + 2.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Experimental, + "The distance from the target landing point that is acceptable to land at", + null + ); + return this; + } + + @Override + public boolean validate( + String configName, @Nonnull NPCLoadTimeValidationHelper validationHelper, ExecutionContext context, Scope globalScope, @Nonnull List errors + ) { + boolean result = super.validate(configName, validationHelper, context, globalScope, errors); + validationHelper.requireMotionControllerType(MotionControllerFly.class); + validationHelper.requireMotionControllerType(MotionControllerWalk.class); + return result; + } + + public double getGoalLenience(@Nonnull BuilderSupport support) { + return this.goalLenience.get(support.getExecutionContext()); + } + + @Override + public double getDesiredAltitudeWeight(BuilderSupport support) { + return 0.0; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionLeave.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionLeave.java new file mode 100644 index 0000000..fef5257 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionLeave.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.movement.BodyMotionLeave; +import javax.annotation.Nonnull; + +public class BuilderBodyMotionLeave extends BuilderBodyMotionFindBase { + protected final DoubleHolder distance = new DoubleHolder(); + + public BuilderBodyMotionLeave() { + super(false); + } + + @Nonnull + public BodyMotionLeave build(@Nonnull BuilderSupport builderSupport) { + return new BodyMotionLeave(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Leave place"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Get away from current position using path finding"; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + @Override + public BuilderBodyMotionFindBase readConfig(@Nonnull JsonElement data) { + super.readConfig(data); + this.requireDouble( + data, "Distance", this.distance, DoubleSingleValidator.greater0(), BuilderDescriptorState.Experimental, "Minimum distance required", null + ); + return this; + } + + public double getDistance(@Nonnull BuilderSupport support) { + return this.distance.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionMaintainDistance.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionMaintainDistance.java new file mode 100644 index 0000000..d8b1037 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionMaintainDistance.java @@ -0,0 +1,188 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderBodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.BodyMotionMaintainDistance; +import com.hypixel.hytale.server.npc.movement.controllers.MotionControllerWalk; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; + +public class BuilderBodyMotionMaintainDistance extends BuilderBodyMotionBase { + public static final String MIN_RANGE_PARAMETER = "MinRange"; + public static final String MAX_RANGE_PARAMETER = "MaxRange"; + public static final String POSITIONING_ANGLE_PARAMETER = "PositioningAngle"; + public static final double NO_POSITIONING = Double.MAX_VALUE; + public static final double[] DEFAULT_STRAFING_DURATION_RANGE = new double[]{0.0, 0.0}; + public static final double[] DEFAULT_STRAFING_FREQUENCY_RANGE = new double[]{2.0, 2.0}; + protected final NumberArrayHolder desiredDistanceRange = new NumberArrayHolder(); + protected final DoubleHolder targetDistanceFactor = new DoubleHolder(); + protected final DoubleHolder moveThreshold = new DoubleHolder(); + protected final DoubleHolder relativeForwardsSpeed = new DoubleHolder(); + protected final DoubleHolder relativeBackwardsSpeed = new DoubleHolder(); + protected final DoubleHolder moveTowardsSlowdownThreshold = new DoubleHolder(); + protected final NumberArrayHolder strafingDurationRange = new NumberArrayHolder(); + protected final NumberArrayHolder strafingFrequencyRange = new NumberArrayHolder(); + + public BuilderBodyMotionMaintainDistance() { + } + + @Nonnull + public BodyMotionMaintainDistance build(@Nonnull BuilderSupport builderSupport) { + return new BodyMotionMaintainDistance(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Maintain distance from a given position"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderBodyMotionMaintainDistance readConfig(@Nonnull JsonElement data) { + this.requireDoubleRange( + data, + "DesiredDistanceRange", + this.desiredDistanceRange, + DoubleSequenceValidator.betweenWeaklyMonotonic(-Double.MAX_VALUE, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "The desired distance to remain at.", + null + ); + this.getDouble( + data, + "TargetDistanceFactor", + this.targetDistanceFactor, + 0.5, + DoubleRangeValidator.between(0.0, 1.0), + BuilderDescriptorState.Stable, + "A factor used to decide what distance to move to within the target range.", + "A factor used to decide what distance to move to within the target range when the target falls outside of it. 0 will result in moving the shortest distance to fall within the range, 1 the furthest distance, and 0.5 roughly the middle of the range." + ); + this.getDouble( + data, + "MoveThreshold", + this.moveThreshold, + 1.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "An extra threshold distance on either side of the desired range before the NPC will trigger movement.", + null + ); + this.getDouble( + data, + "RelativeForwardsSpeed", + this.relativeForwardsSpeed, + 1.0, + DoubleRangeValidator.fromExclToIncl(0.0, 2.0), + BuilderDescriptorState.Stable, + "Maximum relative speed for the NPC moving forwards", + null + ); + this.getDouble( + data, + "RelativeBackwardsSpeed", + this.relativeBackwardsSpeed, + 1.0, + DoubleRangeValidator.fromExclToIncl(0.0, 2.0), + BuilderDescriptorState.Stable, + "Maximum relative speed for the NPC moving backwards", + null + ); + this.getDouble( + data, + "MoveTowardsSlowdownThreshold", + this.moveTowardsSlowdownThreshold, + 2.0, + DoubleSingleValidator.greaterEqual0(), + BuilderDescriptorState.Stable, + "The distance away from the target stopping point at which the NPC will start to slow down while moving towards the target", + null + ); + this.getDoubleRange( + data, + "StrafingDurationRange", + this.strafingDurationRange, + DEFAULT_STRAFING_DURATION_RANGE, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "How long to strafe for.", + "How long to strafe for (moving left or right around the target). If set to [ 0, 0 ], will not move horizontally at all." + ); + this.getDoubleRange( + data, + "StrafingFrequencyRange", + this.strafingFrequencyRange, + DEFAULT_STRAFING_FREQUENCY_RANGE, + DoubleSequenceValidator.fromExclToInclWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "How frequently to execute strafing", + null + ); + this.requireFeature(Feature.AnyPosition); + return this; + } + + @Override + public boolean validate( + String configName, @Nonnull NPCLoadTimeValidationHelper validationHelper, ExecutionContext context, Scope globalScope, @Nonnull List errors + ) { + boolean result = super.validate(configName, validationHelper, context, globalScope, errors); + validationHelper.requireMotionControllerType(MotionControllerWalk.class); + return result; + } + + public double[] getDesiredDistanceRange(@Nonnull BuilderSupport support) { + return this.desiredDistanceRange.get(support.getExecutionContext()); + } + + public double getTargetDistanceFactor(@Nonnull BuilderSupport support) { + return this.targetDistanceFactor.get(support.getExecutionContext()); + } + + public double getMoveThreshold(@Nonnull BuilderSupport support) { + return this.moveThreshold.get(support.getExecutionContext()); + } + + public double getRelativeForwardsSpeed(@Nonnull BuilderSupport support) { + return this.relativeForwardsSpeed.get(support.getExecutionContext()); + } + + public double getRelativeBackwardsSpeed(@Nonnull BuilderSupport support) { + return this.relativeBackwardsSpeed.get(support.getExecutionContext()); + } + + public double getMoveTowardsSlowdownThreshold(@Nonnull BuilderSupport support) { + return this.moveTowardsSlowdownThreshold.get(support.getExecutionContext()); + } + + public double[] getStrafingDurationRange(@Nonnull BuilderSupport support) { + return this.strafingDurationRange.get(support.getExecutionContext()); + } + + public double[] getStrafingFrequencyRange(@Nonnull BuilderSupport support) { + return this.strafingFrequencyRange.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionMatchLook.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionMatchLook.java new file mode 100644 index 0000000..e533748 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionMatchLook.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderBodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.BodyMotionMatchLook; +import javax.annotation.Nonnull; + +public class BuilderBodyMotionMatchLook extends BuilderBodyMotionBase { + public BuilderBodyMotionMatchLook() { + } + + @Nonnull + public BodyMotionMatchLook build(BuilderSupport builderSupport) { + return new BodyMotionMatchLook(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Make NPC body rotate to match look direction"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderBodyMotionMatchLook readConfig(JsonElement data) { + return this; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionMoveAway.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionMoveAway.java new file mode 100644 index 0000000..d0d6dc6 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionMoveAway.java @@ -0,0 +1,185 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.RelationalOperator; +import com.hypixel.hytale.server.npc.corecomponents.movement.BodyMotionMoveAway; +import javax.annotation.Nonnull; + +public class BuilderBodyMotionMoveAway extends BuilderBodyMotionFindWithTarget { + private static final double[] DEFAULT_HOLD_DIRECTION_DURATION_RANGE = new double[]{2.0, 5.0}; + protected final DoubleHolder slowdownDistance = new DoubleHolder(); + protected final DoubleHolder stopDistance = new DoubleHolder(); + protected final DoubleHolder falloff = new DoubleHolder(); + protected final NumberArrayHolder holdDirectionDurationRange = new NumberArrayHolder(); + protected final DoubleHolder changeDirectionViewSector = new DoubleHolder(); + protected final DoubleHolder directionJitter = new DoubleHolder(); + protected final DoubleHolder erraticDistance = new DoubleHolder(); + protected final DoubleHolder erraticExtraJitter = new DoubleHolder(); + protected final DoubleHolder erraticChangeDurationMultiplier = new DoubleHolder(); + + public BuilderBodyMotionMoveAway() { + } + + @Nonnull + public BodyMotionMoveAway build(@Nonnull BuilderSupport builderSupport) { + return new BodyMotionMoveAway(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Move away from target"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Move away from a target using path finding or steering"; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + public BuilderBodyMotionMoveAway readConfig(@Nonnull JsonElement data) { + super.readConfig(data); + this.getDouble( + data, + "SlowDownDistance", + this.slowdownDistance, + 8.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "Distance from target when NPC should start to slowdown", + null + ); + this.getDouble( + data, + "StopDistance", + this.stopDistance, + 10.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "Distance from target when NPC should halt", + null + ); + this.getDouble( + data, + "Falloff", + this.falloff, + 3.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "Rate how fast the slowdown should happen relative to distance", + null + ); + this.getDoubleRange( + data, + "HoldDirectionTimeRange", + this.holdDirectionDurationRange, + DEFAULT_HOLD_DIRECTION_DURATION_RANGE, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "How often to change heading", + null + ); + this.getDouble( + data, + "ChangeDirectionViewSector", + this.changeDirectionViewSector, + 230.0, + DoubleRangeValidator.between(0.0, 360.0), + BuilderDescriptorState.Stable, + "The view sector the NPC uses to decide if it should switch direction", + null + ); + this.getDouble( + data, + "DirectionJitter", + this.directionJitter, + 45.0, + DoubleRangeValidator.between(0.0, 180.0), + BuilderDescriptorState.Stable, + "How much jitter in degrees to add to the heading the NPC uses", + null + ); + this.getDouble( + data, + "ErraticDistance", + this.erraticDistance, + 4.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "If the player is closer than this distance, the NPC will behave more erratically using the additional jitter parameter", + null + ); + this.getDouble( + data, + "ErraticExtraJitter", + this.erraticExtraJitter, + 45.0, + DoubleRangeValidator.between(0.0, 180.0), + BuilderDescriptorState.Stable, + "Extra jitter to add to the NPC heading on top of the standard when the target is too close", + null + ); + this.getDouble( + data, + "ErraticChangeDurationMultiplier", + this.erraticChangeDurationMultiplier, + 0.5, + DoubleRangeValidator.fromExclToIncl(0.0, 1.0), + BuilderDescriptorState.Stable, + "A multiplier to decrease the duration between direction changes when the target is too close", + null + ); + this.validateDoubleRelation(this.slowdownDistance, RelationalOperator.LessEqual, this.stopDistance); + return this; + } + + public double getSlowdownDistance(@Nonnull BuilderSupport support) { + return this.slowdownDistance.get(support.getExecutionContext()); + } + + public double getStopDistance(@Nonnull BuilderSupport support) { + return this.stopDistance.get(support.getExecutionContext()); + } + + public double getFalloff(@Nonnull BuilderSupport support) { + return this.falloff.get(support.getExecutionContext()); + } + + public double[] getHoldDirectionDurationRange(@Nonnull BuilderSupport support) { + return this.holdDirectionDurationRange.get(support.getExecutionContext()); + } + + public float getChangeDirectionViewSectorRadians(@Nonnull BuilderSupport support) { + return (float)(this.changeDirectionViewSector.get(support.getExecutionContext()) * (float) (Math.PI / 180.0)); + } + + public float getDirectionJitterRadians(@Nonnull BuilderSupport support) { + return (float)(this.directionJitter.get(support.getExecutionContext()) * (float) (Math.PI / 180.0)); + } + + public double getErraticDistance(@Nonnull BuilderSupport support) { + return this.erraticDistance.get(support.getExecutionContext()); + } + + public float getErraticExtraJitterRadians(@Nonnull BuilderSupport support) { + return (float)(this.erraticExtraJitter.get(support.getExecutionContext()) * (float) (Math.PI / 180.0)); + } + + public double getErraticChangeDurationMultiplier(@Nonnull BuilderSupport support) { + return this.erraticChangeDurationMultiplier.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionTakeOff.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionTakeOff.java new file mode 100644 index 0000000..97091ee --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionTakeOff.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderBodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.BodyMotionTakeOff; +import com.hypixel.hytale.server.npc.instructions.BodyMotion; +import com.hypixel.hytale.server.npc.movement.controllers.MotionControllerFly; +import com.hypixel.hytale.server.npc.movement.controllers.MotionControllerWalk; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; + +public class BuilderBodyMotionTakeOff extends BuilderBodyMotionBase { + protected double jumpSpeed = 1.0; + + public BuilderBodyMotionTakeOff() { + } + + @Nonnull + public BodyMotion build(BuilderSupport builderSupport) { + return new BodyMotionTakeOff(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Switch NPC from walking to flying motion controller"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getDouble( + data, "JumpSpeed", v -> this.jumpSpeed = v, 0.0, DoubleSingleValidator.greaterEqual0(), BuilderDescriptorState.Experimental, "Speed to jump off", null + ); + return this; + } + + @Override + public boolean validate( + String configName, @Nonnull NPCLoadTimeValidationHelper validationHelper, ExecutionContext context, Scope globalScope, @Nonnull List errors + ) { + boolean result = super.validate(configName, validationHelper, context, globalScope, errors); + validationHelper.requireMotionControllerType(MotionControllerWalk.class); + validationHelper.requireMotionControllerType(MotionControllerFly.class); + return result; + } + + public double getJumpSpeed() { + return this.jumpSpeed; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionTeleport.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionTeleport.java new file mode 100644 index 0000000..1c0fd25 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionTeleport.java @@ -0,0 +1,110 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderBodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.BodyMotionTeleport; +import com.hypixel.hytale.server.npc.instructions.BodyMotion; +import javax.annotation.Nonnull; + +public class BuilderBodyMotionTeleport extends BuilderBodyMotionBase { + public static final double[] DEFAULT_OFFSET_RADIUS = new double[]{0.0, 0.0}; + protected double[] offsetRadius; + protected double maxYOffset; + protected float sector; + protected BodyMotionTeleport.Orientation orientation; + + public BuilderBodyMotionTeleport() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Teleport NPC to a position given by a sensor"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Teleport NPC to a position given by a sensor or to a random position nearby with an optional minimum offset up to a maximum offset"; + } + + @Nonnull + public BodyMotion build(BuilderSupport builderSupport) { + return new BodyMotionTeleport(this); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + public BuilderBodyMotionTeleport readConfig(@Nonnull JsonElement data) { + this.getDoubleRange( + data, + "OffsetRange", + v -> this.offsetRadius = v, + DEFAULT_OFFSET_RADIUS, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Experimental, + "The minimum and maximum offset the NPC can be spawned from the target position", + null + ); + this.getDouble( + data, + "MaxYOffset", + v -> this.maxYOffset = v, + 5.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Experimental, + "Maximum vertical offset from the target position in case of terrain obstacles", + null + ); + this.getFloat( + data, + "OffsetSector", + v -> this.sector = v, + 0.0F, + DoubleRangeValidator.between(0.0, 360.0), + BuilderDescriptorState.Experimental, + "The sector around the target in which to teleport to", + "The sector around the target in which to teleport to. The origin point is directly between the target and the NPC teleporting" + ); + this.getEnum( + data, + "Orientation", + v -> this.orientation = v, + BodyMotionTeleport.Orientation.class, + BodyMotionTeleport.Orientation.Unchanged, + BuilderDescriptorState.Experimental, + "The direction to face after teleporting", + null + ); + this.requireFeature(Feature.AnyPosition); + this.requireFeatureIf("Orientation", BodyMotionTeleport.Orientation.UseTarget, this.orientation, Feature.AnyEntity); + return this; + } + + public double[] getOffsetRadius() { + return this.offsetRadius; + } + + public double getMaxYOffset() { + return this.maxYOffset; + } + + public float getSectorRadians() { + return (float) (Math.PI / 180.0) * this.sector; + } + + public BodyMotionTeleport.Orientation getOrientation() { + return this.orientation; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionWander.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionWander.java new file mode 100644 index 0000000..938ef82 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionWander.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.movement.BodyMotionWander; +import javax.annotation.Nonnull; + +public class BuilderBodyMotionWander extends BuilderBodyMotionWanderBase { + public BuilderBodyMotionWander() { + } + + @Nonnull + public BodyMotionWander build(@Nonnull BuilderSupport builderSupport) { + return new BodyMotionWander(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Random movement"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Random movement in short linear pieces."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderBodyMotionWanderBase readConfig(JsonElement data) { + return this; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionWanderBase.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionWanderBase.java new file mode 100644 index 0000000..46a97df --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionWanderBase.java @@ -0,0 +1,195 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.FloatHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.IntHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.IntSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.RelationalOperator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderBodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.BodyMotionWanderBase; +import com.hypixel.hytale.server.npc.instructions.BodyMotion; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class BuilderBodyMotionWanderBase extends BuilderBodyMotionBase { + protected final DoubleHolder minWalkTime = new DoubleHolder(); + protected final DoubleHolder maxWalkTime = new DoubleHolder(); + protected final FloatHolder minHeadingChange = new FloatHolder(); + protected final FloatHolder maxHeadingChange = new FloatHolder(); + protected final BooleanHolder relaxHeadingChange = new BooleanHolder(); + protected final DoubleHolder relativeSpeed = new DoubleHolder(); + protected final DoubleHolder minMoveDistance = new DoubleHolder(); + protected final DoubleHolder stopDistance = new DoubleHolder(); + protected final BooleanHolder isAvoidingBlockDamage = new BooleanHolder(); + protected final BooleanHolder isRelaxedMoveConstraints = new BooleanHolder(); + protected final IntHolder testsPerTick = new IntHolder(); + protected final DoubleHolder desiredAltitudeWeight = new DoubleHolder(); + + public BuilderBodyMotionWanderBase() { + } + + @Nullable + public BodyMotionWanderBase build(@Nonnull BuilderSupport builderSupport) { + builderSupport.setRequireLeashPosition(); + return null; + } + + @Nonnull + @Override + public Builder readCommonConfig(@Nonnull JsonElement data) { + super.readCommonConfig(data); + this.getDouble( + data, + "MinWalkTime", + this.minWalkTime, + 2.0, + DoubleSingleValidator.greaterEqual0(), + BuilderDescriptorState.Stable, + "Minimum time to wander for a segment.", + null + ); + this.getDouble( + data, + "MaxWalkTime", + this.maxWalkTime, + 4.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "Maximum time to wander for a segment.", + null + ); + this.getFloat( + data, + "MinHeadingChange", + this.minHeadingChange, + 0.0, + DoubleRangeValidator.between(0.0, 180.0), + BuilderDescriptorState.Stable, + "Approximate minimum heading change between segments", + null + ); + this.getFloat( + data, + "MaxHeadingChange", + this.maxHeadingChange, + 90.0, + DoubleRangeValidator.between(0.0, 180.0), + BuilderDescriptorState.Stable, + "Approximate maximum heading change between segments", + null + ); + this.getBoolean( + data, + "RelaxHeadingChange", + this.relaxHeadingChange, + true, + BuilderDescriptorState.Stable, + "Allow other directions when preferred directions blocked", + null + ); + this.getDouble( + data, + "RelativeSpeed", + this.relativeSpeed, + 0.5, + DoubleRangeValidator.fromExclToIncl(0.0, 2.0), + BuilderDescriptorState.Stable, + "Relative wander speed", + null + ); + this.getDouble( + data, + "MinMoveDistance", + this.minMoveDistance, + 0.5, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "Minimum distance to move in a segment", + null + ); + this.getDouble( + data, "StopDistance", this.stopDistance, 0.5, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "Distance to stop at target", null + ); + this.getInt(data, "TestsPerTick", this.testsPerTick, 1, IntSingleValidator.greater0(), BuilderDescriptorState.Stable, "Direction tests per tick", null); + this.getBoolean( + data, "AvoidBlockDamage", this.isAvoidingBlockDamage, true, BuilderDescriptorState.Stable, "Should avoid environmental damage from blocks", null + ); + this.getBoolean( + data, + "RelaxedMoveConstraints", + this.isRelaxedMoveConstraints, + false, + BuilderDescriptorState.Stable, + "NPC can do movements like wading (depends on motion controller type)", + null + ); + this.getDouble( + data, + "DesiredAltitudeWeight", + this.desiredAltitudeWeight, + -1.0, + DoubleRangeValidator.between(-1.0, 1.0), + BuilderDescriptorState.Stable, + "How much this NPC prefers being within the desired height range", + "How much this NPC prefers being within the desired height range. 0 means it doesn't care much, 1 means it will do its best to get there fast. Values below 0 mean the default in the motion controller will be used." + ); + this.validateDoubleRelation(this.minWalkTime, RelationalOperator.LessEqual, this.maxWalkTime); + this.validateFloatRelation(this.minHeadingChange, RelationalOperator.LessEqual, this.maxHeadingChange); + return this; + } + + public double getMinWalkTime(@Nonnull BuilderSupport support) { + return this.minWalkTime.get(support.getExecutionContext()); + } + + public double getMaxWalkTime(@Nonnull BuilderSupport support) { + return this.maxWalkTime.get(support.getExecutionContext()); + } + + public float getMinHeadingChange(@Nonnull BuilderSupport support) { + return this.minHeadingChange.get(support.getExecutionContext()); + } + + public float getMaxHeadingChange(@Nonnull BuilderSupport support) { + return this.maxHeadingChange.get(support.getExecutionContext()); + } + + public boolean isRelaxHeadingChange(@Nonnull BuilderSupport support) { + return this.relaxHeadingChange.get(support.getExecutionContext()); + } + + public double getRelativeSpeed(@Nonnull BuilderSupport support) { + return this.relativeSpeed.get(support.getExecutionContext()); + } + + public double getMinMoveDistance(@Nonnull BuilderSupport support) { + return this.minMoveDistance.get(support.getExecutionContext()); + } + + public double getStopDistance(@Nonnull BuilderSupport support) { + return this.stopDistance.get(support.getExecutionContext()); + } + + public boolean isAvoidingBlockDamage(@Nonnull BuilderSupport support) { + return this.isAvoidingBlockDamage.get(support.getExecutionContext()); + } + + public boolean isRelaxedMoveConstraints(@Nonnull BuilderSupport support) { + return this.isRelaxedMoveConstraints.get(support.getExecutionContext()); + } + + public int getTestsPerTick(@Nonnull BuilderSupport support) { + return this.testsPerTick.get(support.getExecutionContext()); + } + + public double getDesiredAltitudeWeight(@Nonnull BuilderSupport support) { + return this.desiredAltitudeWeight.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionWanderInCircle.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionWanderInCircle.java new file mode 100644 index 0000000..69773ce --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionWanderInCircle.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.movement.BodyMotionWanderInCircle; +import javax.annotation.Nonnull; + +public class BuilderBodyMotionWanderInCircle extends BuilderBodyMotionWanderBase { + protected final DoubleHolder radius = new DoubleHolder(); + protected boolean flock; + protected boolean useSphere; + + public BuilderBodyMotionWanderInCircle() { + } + + @Nonnull + public BodyMotionWanderInCircle build(@Nonnull BuilderSupport builderSupport) { + super.build(builderSupport); + return new BodyMotionWanderInCircle(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Random movement in circle around spawn position"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Random movement in short linear pieces inside circle around spawn position."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderBodyMotionWanderInCircle readConfig(@Nonnull JsonElement data) { + this.getDouble(data, "Radius", this.radius, 10.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "Radius of circle to wander in", null); + this.getBoolean(data, "Flock", b -> this.flock = b, false, BuilderDescriptorState.Experimental, "Do not use", null); + this.getBoolean(data, "UseSphere", b -> this.useSphere = b, false, BuilderDescriptorState.Stable, "Use sphere", "Use a sphere instead of circle cylinder"); + return this; + } + + public double getRadius(@Nonnull BuilderSupport builderSupport) { + return this.radius.get(builderSupport.getExecutionContext()); + } + + public boolean isFlock() { + return this.flock; + } + + public boolean isUseSphere() { + return this.useSphere; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionWanderInRect.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionWanderInRect.java new file mode 100644 index 0000000..3be9f6c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderBodyMotionWanderInRect.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.movement.BodyMotionWanderInRect; +import javax.annotation.Nonnull; + +public class BuilderBodyMotionWanderInRect extends BuilderBodyMotionWanderBase { + protected double width; + protected double depth; + + public BuilderBodyMotionWanderInRect() { + } + + @Nonnull + public BodyMotionWanderInRect build(@Nonnull BuilderSupport builderSupport) { + super.build(builderSupport); + return new BodyMotionWanderInRect(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Random movement in rectangle around spawn position"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Random movement in short linear pieces inside rectangle around spawn position."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderBodyMotionWanderInRect readConfig(@Nonnull JsonElement data) { + this.getDouble(data, "Width", w -> this.width = w, 10.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "Rectangle width", null); + this.getDouble(data, "Depth", d -> this.depth = d, 10.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "Rectangle depth", null); + return this; + } + + public double getWidth() { + return this.width; + } + + public double getDepth() { + return this.depth; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderSensorInAir.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderSensorInAir.java new file mode 100644 index 0000000..420e9d4 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderSensorInAir.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.SensorInAir; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorInAir extends BuilderSensorBase { + public BuilderSensorInAir() { + } + + @Nonnull + public SensorInAir build(BuilderSupport builderSupport) { + return new SensorInAir(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test if NPC is not on ground"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Return true if NPC is not on ground. No target is returned."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(JsonElement data) { + return this; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderSensorMotionController.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderSensorMotionController.java new file mode 100644 index 0000000..95bf956 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderSensorMotionController.java @@ -0,0 +1,60 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.SensorMotionController; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorMotionController extends BuilderSensorBase { + protected String motionControllerName; + + public BuilderSensorMotionController() { + } + + @Nonnull + public Sensor build(BuilderSupport builderSupport) { + return new SensorMotionController(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test if specific motion controller is active."; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireString( + data, + "MotionController", + s -> this.motionControllerName = s, + StringNotEmptyValidator.get(), + BuilderDescriptorState.Experimental, + "Motion controller name to test for", + null + ); + return this; + } + + public String getMotionControllerName() { + return this.motionControllerName; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderSensorNav.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderSensorNav.java new file mode 100644 index 0000000..931181b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderSensorNav.java @@ -0,0 +1,95 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumSetHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.SensorNav; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.movement.NavState; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class BuilderSensorNav extends BuilderSensorBase { + protected final EnumSetHolder navStateEnumSetHolder = new EnumSetHolder<>(); + protected final DoubleHolder throttleDuration = new DoubleHolder(); + protected final DoubleHolder targetDelta = new DoubleHolder(); + + public BuilderSensorNav() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Queries navigation state"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorNav(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getEnumSet( + data, + "NavStates", + this.navStateEnumSetHolder, + NavState.class, + EnumSet.noneOf(NavState.class), + BuilderDescriptorState.Stable, + "Trigger when path finder is in one of the states or empty to match all", + null + ); + this.getDouble( + data, + "ThrottleDuration", + this.throttleDuration, + 0.0, + DoubleSingleValidator.greaterEqual0(), + BuilderDescriptorState.Stable, + "Minimum time in seconds the path finder isn't able to reach target or 0 to ignore", + null + ); + this.getDouble( + data, + "TargetDelta", + this.targetDelta, + 0.0, + DoubleSingleValidator.greaterEqual0(), + BuilderDescriptorState.Stable, + "Minimum distance target has moved since path was computed or 0 to ignore", + null + ); + return this; + } + + public EnumSet getNavStates(@Nonnull BuilderSupport builderSupport) { + return this.navStateEnumSetHolder.get(builderSupport.getExecutionContext()); + } + + public double getThrottleDuration(@Nonnull BuilderSupport support) { + return this.throttleDuration.get(support.getExecutionContext()); + } + + public double getTargetDelta(@Nonnull BuilderSupport support) { + return this.targetDelta.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderSensorOnGround.java b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderSensorOnGround.java new file mode 100644 index 0000000..4339932 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/movement/builders/BuilderSensorOnGround.java @@ -0,0 +1,44 @@ +package com.hypixel.hytale.server.npc.corecomponents.movement.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.movement.SensorOnGround; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorOnGround extends BuilderSensorBase { + public BuilderSensorOnGround() { + } + + @Nonnull + public SensorOnGround build(BuilderSupport builderSupport) { + return new SensorOnGround(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test if NPC is on ground"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Return true if NPC is on ground. No target is returned."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(JsonElement data) { + return this; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/ActionParentState.java b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/ActionParentState.java new file mode 100644 index 0000000..66e1f9b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/ActionParentState.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.server.npc.corecomponents.statemachine; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.StatePair; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.statemachine.builders.BuilderActionParentState; +import com.hypixel.hytale.server.npc.decisionmaker.stateevaluator.StateEvaluator; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionParentState extends ActionBase { + protected final int state; + protected final int subState; + + public ActionParentState(@Nonnull BuilderActionParentState builderActionState, @Nonnull BuilderSupport support) { + super(builderActionState); + StatePair statePair = builderActionState.getStatePair(support); + this.state = statePair.getState(); + this.subState = statePair.getSubState(); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + StateEvaluator stateEvaluatorComponent = store.getComponent(ref, StateEvaluator.getComponentType()); + if (stateEvaluatorComponent == null || !stateEvaluatorComponent.isActive()) { + role.getStateSupport().setState(this.state, this.subState, true, false); + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/ActionState.java b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/ActionState.java new file mode 100644 index 0000000..e176b01 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/ActionState.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.server.npc.corecomponents.statemachine; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.statemachine.builders.BuilderActionState; +import com.hypixel.hytale.server.npc.decisionmaker.stateevaluator.StateEvaluator; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.ComponentInfo; +import javax.annotation.Nonnull; + +public class ActionState extends ActionBase { + protected final int state; + protected final int subState; + protected final boolean clearOnce; + protected final boolean componentLocal; + protected final int componentIndex; + + public ActionState(@Nonnull BuilderActionState builderActionState, @Nonnull BuilderSupport support) { + super(builderActionState); + this.state = builderActionState.getStateIndex(); + this.subState = builderActionState.getSubStateIndex(); + this.clearOnce = builderActionState.isClearState(); + this.componentLocal = builderActionState.isComponentLocal(); + this.componentIndex = support.getComponentIndex(); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + if (this.componentLocal) { + role.getStateSupport().setComponentState(this.componentIndex, this.state); + return true; + } else { + StateEvaluator stateEvaluatorComponent = store.getComponent(ref, StateEvaluator.getComponentType()); + if (stateEvaluatorComponent == null || !stateEvaluatorComponent.isActive()) { + role.getStateSupport().setState(this.state, this.subState, this.clearOnce, false); + } + + return true; + } + } + + @Override + public void getInfo(@Nonnull Role role, @Nonnull ComponentInfo holder) { + if (this.componentLocal) { + holder.addField("Component local state: " + this.state); + } else { + holder.addField("State: " + role.getStateSupport().getStateName(this.state, this.subState)); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/ActionToggleStateEvaluator.java b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/ActionToggleStateEvaluator.java new file mode 100644 index 0000000..8db7b4c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/ActionToggleStateEvaluator.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.npc.corecomponents.statemachine; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.statemachine.builders.BuilderActionToggleStateEvaluator; +import com.hypixel.hytale.server.npc.decisionmaker.stateevaluator.StateEvaluator; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionToggleStateEvaluator extends ActionBase { + protected final boolean enable; + + public ActionToggleStateEvaluator(@Nonnull BuilderActionToggleStateEvaluator builder) { + super(builder); + this.enable = builder.isEnable(); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + StateEvaluator stateEvaluatorComponent = store.getComponent(ref, StateEvaluator.getComponentType()); + + assert stateEvaluatorComponent != null; + + stateEvaluatorComponent.setActive(this.enable); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/SensorIsBusy.java b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/SensorIsBusy.java new file mode 100644 index 0000000..e5b34fc --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/SensorIsBusy.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.npc.corecomponents.statemachine; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorIsBusy extends SensorBase { + public SensorIsBusy(@Nonnull BuilderSensorBase builderSensorBase) { + super(builderSensorBase); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + return super.matches(ref, role, dt, store) && role.getStateSupport().isInBusyState(); + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/SensorState.java b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/SensorState.java new file mode 100644 index 0000000..fe45fc0 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/SensorState.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.server.npc.corecomponents.statemachine; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.statemachine.builders.BuilderSensorState; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.StateSupport; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.ComponentInfo; +import javax.annotation.Nonnull; + +public class SensorState extends SensorBase { + protected final int state; + protected final boolean defaultSubState; + protected final int subState; + protected final boolean componentLocal; + protected final int componentIndex; + + public SensorState(@Nonnull BuilderSensorState builder, @Nonnull BuilderSupport support) { + super(builder); + this.state = builder.getState(); + this.defaultSubState = builder.isDefaultSubState(); + this.subState = builder.getSubStateIndex(); + this.componentLocal = builder.isComponentLocal(); + this.componentIndex = support.getComponentIndex(); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + StateSupport stateSupport = role.getStateSupport(); + return this.componentLocal + ? super.matches(ref, role, dt, store) && stateSupport.isComponentInState(this.componentIndex, this.state) + : super.matches(ref, role, dt, store) && stateSupport.inState(this.state) && (this.defaultSubState || stateSupport.inSubState(this.subState)); + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } + + @Override + public void getInfo(@Nonnull Role role, @Nonnull ComponentInfo holder) { + if (this.componentLocal) { + holder.addField("Component local state: " + this.state); + } else { + holder.addField("State: " + role.getStateSupport().getStateName(this.state, this.subState)); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/builders/BuilderActionParentState.java b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/builders/BuilderActionParentState.java new file mode 100644 index 0000000..f90c6a3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/builders/BuilderActionParentState.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.server.npc.corecomponents.statemachine.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.asset.builder.StatePair; +import com.hypixel.hytale.server.npc.asset.builder.validators.StateStringValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.statemachine.ActionParentState; +import javax.annotation.Nonnull; + +public class BuilderActionParentState extends BuilderActionBase { + protected String state; + + public BuilderActionParentState() { + } + + @Nonnull + public ActionParentState build(@Nonnull BuilderSupport builderSupport) { + return new ActionParentState(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Set the main state of NPC from within a component"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Set the main state of NPC from within a component"; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionParentState readConfig(@Nonnull JsonElement data) { + this.validateIsComponent(); + StateStringValidator validator = StateStringValidator.mainStateOnly(); + this.requireString( + data, + "State", + v -> this.state = validator.getMainState(), + validator, + BuilderDescriptorState.Stable, + "The alias of the external state to set, as defined by _ImportStates in parameters", + null + ); + this.registerStateSetter(this.state, null, (m, s) -> {}); + this.requireInstructionType(InstructionType.StateChangeAllowedInstructions); + return this; + } + + @Nonnull + public StatePair getStatePair(@Nonnull BuilderSupport support) { + return support.getMappedStatePair(this.stateHelper.getComponentImportStateIndex(this.state)); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/builders/BuilderActionState.java b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/builders/BuilderActionState.java new file mode 100644 index 0000000..d307916 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/builders/BuilderActionState.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.server.npc.corecomponents.statemachine.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.InstructionType; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.statemachine.ActionState; +import javax.annotation.Nonnull; + +public class BuilderActionState extends BuilderActionBase { + protected String state; + protected String subState; + protected int stateIndex; + protected int subStateIndex; + protected boolean clearState; + protected boolean componentLocal; + + public BuilderActionState() { + } + + @Nonnull + public ActionState build(@Nonnull BuilderSupport builderSupport) { + return new ActionState(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Set state of NPC"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Set state of NPC. The state can be queried with a sensor later on."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionState readConfig(@Nonnull JsonElement data) { + this.requireStateString(data, "State", true, (state, subState, isDefault) -> { + this.state = state; + this.subState = subState; + }, BuilderDescriptorState.Stable, "State name to set", null); + this.getBoolean( + data, "ClearState", v -> this.clearState = v, true, BuilderDescriptorState.Stable, "Clear the state of things like set once flags on transition", null + ); + this.componentLocal = this.isComponent(); + this.registerStateSetter(this.state, this.subState, (main, sub) -> { + this.stateIndex = main; + this.subStateIndex = sub; + }); + this.requireInstructionType(InstructionType.StateChangeAllowedInstructions); + return this; + } + + public int getStateIndex() { + return this.stateIndex; + } + + public int getSubStateIndex() { + return this.subStateIndex; + } + + public boolean isClearState() { + return this.clearState; + } + + public boolean isComponentLocal() { + return this.componentLocal; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/builders/BuilderActionToggleStateEvaluator.java b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/builders/BuilderActionToggleStateEvaluator.java new file mode 100644 index 0000000..a0fc3c9 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/builders/BuilderActionToggleStateEvaluator.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.server.npc.corecomponents.statemachine.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.statemachine.ActionToggleStateEvaluator; +import javax.annotation.Nonnull; + +public class BuilderActionToggleStateEvaluator extends BuilderActionBase { + protected boolean enable; + + public BuilderActionToggleStateEvaluator() { + } + + @Nonnull + public ActionToggleStateEvaluator build(BuilderSupport builderSupport) { + return new ActionToggleStateEvaluator(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Enable or disable the NPC's state evaluator"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionToggleStateEvaluator readConfig(@Nonnull JsonElement data) { + this.requireBoolean(data, "Enabled", b -> this.enable = b, BuilderDescriptorState.Stable, "Whether or not to enable the state evaluator", null); + if (!this.isCreatingDescriptor()) { + this.stateHelper.setRequiresStateEvaluator(); + } + + return this; + } + + public boolean isEnable() { + return this.enable; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/builders/BuilderSensorIsBusy.java b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/builders/BuilderSensorIsBusy.java new file mode 100644 index 0000000..cc182d3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/builders/BuilderSensorIsBusy.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.server.npc.corecomponents.statemachine.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.statemachine.SensorIsBusy; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorIsBusy extends BuilderSensorBase { + public BuilderSensorIsBusy() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Tests if an NPC is in one of the defined Busy States."; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Sensor build(BuilderSupport builderSupport) { + return new SensorIsBusy(this); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/builders/BuilderSensorState.java b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/builders/BuilderSensorState.java new file mode 100644 index 0000000..8479a70 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/statemachine/builders/BuilderSensorState.java @@ -0,0 +1,93 @@ +package com.hypixel.hytale.server.npc.corecomponents.statemachine.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.statemachine.SensorState; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorState extends BuilderSensorBase { + protected String state; + protected String subState; + protected int stateIndex; + protected int subStateIndex; + protected boolean defaultSubState; + protected boolean ignoreMissingSetState; + protected boolean componentLocal; + + public BuilderSensorState() { + } + + @Nonnull + public SensorState build(@Nonnull BuilderSupport builderSupport) { + return new SensorState(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test for a specific state"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Signal if NPC is set to specific state."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireStateString(data, "State", true, (state, subState, isDefault) -> { + this.state = state; + this.subState = subState; + this.defaultSubState = isDefault; + }, BuilderDescriptorState.Stable, "State to compare to", null); + this.getBoolean( + data, + "IgnoreMissingSetState", + v -> this.ignoreMissingSetState = v, + false, + BuilderDescriptorState.Stable, + "Override and ignore checks for matching setter action that sets this state", + "Override and ignore checks for matching setter action that sets this state. Intended for use in cases such as the FlockState action which sets the state via another NPC" + ); + this.registerStateSensor(this.state, this.subState, this::setIndexes); + if (this.ignoreMissingSetState) { + this.registerStateSetter(this.state, this.subState, (m, v) -> {}); + } + + this.componentLocal = this.isComponent(); + return this; + } + + public int getState() { + return this.stateIndex; + } + + public void setIndexes(int main, int sub) { + this.stateIndex = main; + this.subStateIndex = sub; + } + + public boolean isDefaultSubState() { + return this.defaultSubState; + } + + public int getSubStateIndex() { + return this.subStateIndex; + } + + public boolean isComponentLocal() { + return this.componentLocal; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/ActionSetAlarm.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/ActionSetAlarm.java new file mode 100644 index 0000000..22d9bff --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/ActionSetAlarm.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionSetAlarm; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.Alarm; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAmount; +import javax.annotation.Nonnull; + +public class ActionSetAlarm extends ActionBase { + protected final Alarm alarm; + protected final TemporalAmount minDuration; + protected final long randomVariation; + protected final boolean cancel; + + public ActionSetAlarm(@Nonnull BuilderActionSetAlarm builder, @Nonnull BuilderSupport support) { + super(builder); + this.alarm = builder.getAlarm(support); + TemporalAmount[] durations = builder.getDurationRange(support); + this.minDuration = durations[0]; + Instant max = Instant.EPOCH.plus(durations[1]); + this.randomVariation = max.minus(this.minDuration).getEpochSecond(); + this.cancel = max.getEpochSecond() == 0L; + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + if (this.cancel) { + this.alarm.set(ref, null, store); + } else { + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + this.alarm + .set(ref, worldTimeResource.getGameTime().plus(this.minDuration).plus(RandomExtra.randomRange(0L, this.randomVariation), ChronoUnit.SECONDS), store); + } + + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/ActionTimer.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/ActionTimer.java new file mode 100644 index 0000000..d6fc36b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/ActionTimer.java @@ -0,0 +1,161 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimer; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerContinue; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerModify; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerPause; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerRestart; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerStart; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerStop; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.Timer; +import javax.annotation.Nonnull; + +public class ActionTimer extends ActionBase { + protected final Timer timer; + protected final Timer.TimerAction action; + protected double minStartValue; + protected double maxStartValue; + protected double minRestartValue; + protected double maxValue; + protected double rate; + protected double increaseValue; + protected boolean modifyRepeating; + protected boolean repeating; + + public ActionTimer(@Nonnull BuilderActionTimer builderActionTimer, @Nonnull BuilderSupport builderSupport) { + super(builderActionTimer); + this.timer = builderActionTimer.getTimer(builderSupport); + this.action = builderActionTimer.getTimerAction(); + } + + public ActionTimer(@Nonnull BuilderActionTimerStart builderActionTimerStart, @Nonnull BuilderSupport builderSupport) { + this((BuilderActionTimer)builderActionTimerStart, builderSupport); + double[] startValueRange = builderActionTimerStart.getStartValueRange(builderSupport); + this.minStartValue = startValueRange[0]; + this.maxStartValue = startValueRange[1]; + double[] restartValueRange = builderActionTimerStart.getRestartValueRange(builderSupport); + this.minRestartValue = restartValueRange[0]; + this.maxValue = restartValueRange[1]; + this.rate = builderActionTimerStart.getRate(builderSupport); + this.repeating = builderActionTimerStart.isRepeating(builderSupport); + } + + public ActionTimer(@Nonnull BuilderActionTimerModify builderActionTimerModify, @Nonnull BuilderSupport builderSupport) { + this((BuilderActionTimer)builderActionTimerModify, builderSupport); + this.increaseValue = builderActionTimerModify.getIncreaseValue(builderSupport); + double[] restartValueRange = builderActionTimerModify.getRestartValueRange(builderSupport); + this.minRestartValue = restartValueRange[0]; + this.maxValue = restartValueRange[1]; + this.rate = builderActionTimerModify.getRate(builderSupport); + this.minStartValue = builderActionTimerModify.getSetValue(builderSupport); + this.repeating = builderActionTimerModify.isRepeating(builderSupport); + this.modifyRepeating = builderActionTimerModify.isModifyRepeating(); + } + + public ActionTimer(BuilderActionTimerPause builderActionTimerPause, @Nonnull BuilderSupport builderSupport) { + this((BuilderActionTimer)builderActionTimerPause, builderSupport); + } + + public ActionTimer(BuilderActionTimerStop builderActionTimerStop, @Nonnull BuilderSupport builderSupport) { + this((BuilderActionTimer)builderActionTimerStop, builderSupport); + } + + public ActionTimer(BuilderActionTimerContinue builderActionTimerContinue, @Nonnull BuilderSupport builderSupport) { + this((BuilderActionTimer)builderActionTimerContinue, builderSupport); + } + + public ActionTimer(BuilderActionTimerRestart builderActionTimerRestart, @Nonnull BuilderSupport builderSupport) { + this((BuilderActionTimer)builderActionTimerRestart, builderSupport); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + switch (this.action) { + case START: + this.executeStartAction(); + break; + case PAUSE: + this.executePauseAction(); + break; + case STOP: + this.executeStopAction(); + break; + case MODIFY: + this.executeModifyAction(); + break; + case CONTINUE: + this.executeContinueAction(); + break; + case RESTART: + this.executeRestartAction(); + } + + return true; + } + + protected void executeRestartAction() { + if (this.timer.isInitialised()) { + this.timer.restart(); + } + } + + protected void executeModifyAction() { + if (this.timer.isInitialised() && !this.timer.isStopped()) { + if (this.minRestartValue > 0.0) { + this.timer.setMinRestartValue(this.minRestartValue); + } + + if (this.maxValue > 0.0) { + this.timer.setMaxValue(this.maxValue); + } + + if (this.minStartValue > 0.0) { + this.timer.setValue(this.minStartValue); + } + + if (this.increaseValue > 0.0) { + this.timer.addValue(this.increaseValue); + } + + if (this.rate > 0.0) { + this.timer.setRate(this.rate); + } + + if (this.modifyRepeating) { + this.timer.setRepeating(this.repeating); + } + } + } + + protected void executeContinueAction() { + if (this.timer.isInitialised()) { + this.timer.resume(); + } + } + + protected void executePauseAction() { + if (this.timer.isInitialised()) { + this.timer.pause(); + } + } + + protected void executeStopAction() { + if (this.timer.isInitialised()) { + this.timer.stop(); + } + } + + protected void executeStartAction() { + if (!this.timer.isInitialised()) { + this.timer.start(this.minStartValue, this.maxStartValue, this.minRestartValue, this.maxValue, this.rate, this.repeating); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/BodyMotionTimer.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/BodyMotionTimer.java new file mode 100644 index 0000000..d23adec --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/BodyMotionTimer.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderBodyMotionTimer; +import com.hypixel.hytale.server.npc.instructions.BodyMotion; +import javax.annotation.Nonnull; + +public class BodyMotionTimer extends MotionTimer implements BodyMotion { + public BodyMotionTimer(@Nonnull BuilderBodyMotionTimer builder, @Nonnull BuilderSupport builderSupport, BodyMotion motion) { + super(builder, builderSupport, motion); + } + + @Override + public BodyMotion getSteeringMotion() { + return this.motion.getSteeringMotion(); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/HeadMotionTimer.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/HeadMotionTimer.java new file mode 100644 index 0000000..ac52d35 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/HeadMotionTimer.java @@ -0,0 +1,12 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderHeadMotionTimer; +import com.hypixel.hytale.server.npc.instructions.HeadMotion; +import javax.annotation.Nonnull; + +public class HeadMotionTimer extends MotionTimer implements HeadMotion { + public HeadMotionTimer(@Nonnull BuilderHeadMotionTimer builder, @Nonnull BuilderSupport builderSupport, HeadMotion motion) { + super(builder, builderSupport, motion); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/MotionTimer.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/MotionTimer.java new file mode 100644 index 0000000..be58291 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/MotionTimer.java @@ -0,0 +1,107 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.MotionBase; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderMotionTimer; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.instructions.Motion; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class MotionTimer extends MotionBase { + protected final T motion; + protected final double atLeastSeconds; + protected final double atMostSeconds; + protected double activeTime; + protected double timeToLive; + + public MotionTimer(@Nonnull BuilderMotionTimer builder, @Nonnull BuilderSupport builderSupport, T motion) { + double[] timerRange = builder.getTimerRange(builderSupport); + this.atLeastSeconds = timerRange[0]; + this.atMostSeconds = timerRange[1]; + this.motion = motion; + } + + @Override + public void activate(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + this.activeTime = 0.0; + this.timeToLive = RandomExtra.randomRange(this.atLeastSeconds, this.atMostSeconds); + this.motion.activate(ref, role, componentAccessor); + } + + @Override + public void deactivate(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + this.motion.deactivate(ref, role, componentAccessor); + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role support, + @Nullable InfoProvider sensorInfo, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.activeTime >= this.timeToLive) { + return false; + } else { + this.activeTime += dt; + if (!this.motion.computeSteering(ref, support, sensorInfo, dt, desiredSteering, componentAccessor)) { + this.activeTime = this.timeToLive; + return false; + } else { + return true; + } + } + } + + @Override + public void registerWithSupport(Role role) { + this.motion.registerWithSupport(role); + } + + @Override + public void motionControllerChanged( + @Nullable Ref ref, + @Nonnull NPCEntity npcComponent, + MotionController motionController, + @Nullable ComponentAccessor componentAccessor + ) { + this.motion.motionControllerChanged(ref, npcComponent, motionController, componentAccessor); + } + + @Override + public void loaded(Role role) { + this.motion.loaded(role); + } + + @Override + public void spawned(Role role) { + this.motion.spawned(role); + } + + @Override + public void unloaded(Role role) { + this.motion.unloaded(role); + } + + @Override + public void removed(Role role) { + this.motion.removed(role); + } + + @Override + public void teleported(Role role, World from, World to) { + this.motion.teleported(role, from, to); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/SensorAlarm.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/SensorAlarm.java new file mode 100644 index 0000000..1d54cfe --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/SensorAlarm.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderSensorAlarm; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.Alarm; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class SensorAlarm extends SensorBase { + protected final Alarm alarm; + protected final SensorAlarm.State state; + protected final boolean clear; + + public SensorAlarm(@Nonnull BuilderSensorAlarm builder, @Nonnull BuilderSupport support) { + super(builder); + this.alarm = builder.getAlarm(support); + this.state = builder.getState(support); + this.clear = builder.isClear(support); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + return false; + } else { + switch (this.state) { + case SET: + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + return this.alarm.isSet() && !this.alarm.hasPassed(worldTimeResource.getGameTime()); + case UNSET: + return !this.alarm.isSet(); + case PASSED: + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + boolean passed = this.alarm.hasPassed(worldTimeResource.getGameTime()); + if (passed && this.clear) { + this.alarm.set(ref, null, store); + } + + return passed; + default: + return false; + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } + + public static enum State implements Supplier { + SET("Set"), + UNSET("Not set"), + PASSED("Passed"); + + private final String asText; + + private State(String text) { + this.asText = text; + } + + public String asText() { + return this.asText; + } + + public String get() { + return this.asText; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/SensorTimer.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/SensorTimer.java new file mode 100644 index 0000000..83cd40b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/SensorTimer.java @@ -0,0 +1,48 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderSensorTimer; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.Timer; +import javax.annotation.Nonnull; + +public class SensorTimer extends SensorBase { + protected final double minTimeRemaining; + protected final double maxTimeRemaining; + protected final Timer timer; + protected final Timer.TimerState timerState; + + public SensorTimer(@Nonnull BuilderSensorTimer builderSensorTimer, @Nonnull BuilderSupport builderSupport) { + super(builderSensorTimer); + this.timer = builderSensorTimer.getTimer(builderSupport); + double[] timerThresholds = builderSensorTimer.getRemainingTimeRange(builderSupport); + this.minTimeRemaining = timerThresholds[0]; + this.maxTimeRemaining = timerThresholds[1]; + this.timerState = builderSensorTimer.getTimerState(); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + return false; + } else { + return !this.timer.isInitialised() + ? (this.timerState == Timer.TimerState.ANY || this.timerState == Timer.TimerState.STOPPED) && this.isBetween(0.0) + : this.timer.isInState(this.timerState) && this.isBetween(this.timer.getValue()); + } + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } + + protected boolean isBetween(double value) { + return value >= this.minTimeRemaining && value <= this.maxTimeRemaining; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionSetAlarm.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionSetAlarm.java new file mode 100644 index 0000000..dbf290c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionSetAlarm.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.TemporalArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.TemporalSequenceValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.timer.ActionSetAlarm; +import com.hypixel.hytale.server.npc.util.Alarm; +import java.time.Duration; +import java.time.Period; +import java.time.temporal.TemporalAmount; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderActionSetAlarm extends BuilderActionBase { + public static final TemporalAmount MIN_TIME = Duration.ZERO; + public static final TemporalAmount MAX_TIME = Period.ofDays(Integer.MAX_VALUE); + protected final StringHolder name = new StringHolder(); + protected final TemporalArrayHolder durationRange = new TemporalArrayHolder(); + + public BuilderActionSetAlarm() { + } + + @Nonnull + public ActionSetAlarm build(@Nonnull BuilderSupport builderSupport) { + return new ActionSetAlarm(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Set a named alarm on the NPC"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionSetAlarm readConfig(@Nonnull JsonElement data) { + this.requireString(data, "Name", this.name, StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The name of the alarm to set", null); + this.requireTemporalRange( + data, + "DurationRange", + this.durationRange, + TemporalSequenceValidator.betweenWeaklyMonotonic(MIN_TIME, MAX_TIME), + BuilderDescriptorState.Stable, + "The duration range from which to pick a duration to set the alarm for", + "The duration range from which to pick a duration to set the alarm for. [ \"P0D\", \"P0D\" ] will unset the alarm" + ); + return this; + } + + public Alarm getAlarm(@Nonnull BuilderSupport support) { + return support.getAlarm(this.name.get(support.getExecutionContext())); + } + + @Nullable + public TemporalAmount[] getDurationRange(@Nonnull BuilderSupport support) { + return this.durationRange.getTemporalArray(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimer.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimer.java new file mode 100644 index 0000000..0e6aabf --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimer.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.util.Timer; +import javax.annotation.Nonnull; + +public abstract class BuilderActionTimer extends BuilderActionBase { + protected final StringHolder name = new StringHolder(); + + public BuilderActionTimer() { + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionTimer readConfig(@Nonnull JsonElement data) { + this.requireString(data, "Name", this.name, StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The name of the timer", null); + return this; + } + + public abstract Timer.TimerAction getTimerAction(); + + public Timer getTimer(@Nonnull BuilderSupport support) { + return support.getTimerByName(this.name.get(support.getExecutionContext())); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerContinue.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerContinue.java new file mode 100644 index 0000000..40346cc --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerContinue.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.timer.ActionTimer; +import com.hypixel.hytale.server.npc.util.Timer; +import javax.annotation.Nonnull; + +public class BuilderActionTimerContinue extends BuilderActionTimer { + public BuilderActionTimerContinue() { + } + + @Nonnull + public ActionTimer build(@Nonnull BuilderSupport builderSupport) { + return new ActionTimer(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Continue a timer"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Continue a timer"; + } + + @Nonnull + @Override + public Timer.TimerAction getTimerAction() { + return Timer.TimerAction.CONTINUE; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerModify.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerModify.java new file mode 100644 index 0000000..9bc6559 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerModify.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.timer.ActionTimer; +import com.hypixel.hytale.server.npc.util.Timer; +import javax.annotation.Nonnull; + +public class BuilderActionTimerModify extends BuilderActionTimer { + private static final double[] DEFAULT_RESTART_VALUE_RANGE = new double[]{0.0, 0.0}; + protected final DoubleHolder increaseValue = new DoubleHolder(); + protected final DoubleHolder setValue = new DoubleHolder(); + protected final NumberArrayHolder restartValueRange = new NumberArrayHolder(); + protected final DoubleHolder rate = new DoubleHolder(); + protected final BooleanHolder repeating = new BooleanHolder(); + protected boolean modifyRepeating; + + public BuilderActionTimerModify() { + } + + @Nonnull + public ActionTimer build(@Nonnull BuilderSupport builderSupport) { + return new ActionTimer(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Modify values of a timer"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Modify values of a timer"; + } + + @Nonnull + @Override + public BuilderActionTimer readConfig(@Nonnull JsonElement data) { + super.readConfig(data); + this.getDouble( + data, "AddValue", this.increaseValue, 0.0, DoubleSingleValidator.greaterEqual0(), BuilderDescriptorState.Stable, "Add value to the timer", null + ); + this.getDoubleRange( + data, + "MaxValue", + this.restartValueRange, + DEFAULT_RESTART_VALUE_RANGE, + DoubleSequenceValidator.between(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "Set the restart value range the timer can have", + "Set the restart value range the timer can have. If [ 0, 0 ] (default) it will be ignored" + ); + this.getDouble( + data, + "Rate", + this.rate, + 0.0, + DoubleSingleValidator.greaterEqual0(), + BuilderDescriptorState.Stable, + "Set the rate at which the timer will decrease", + "Set the rate at which the timer will decrease. If 0 (default) it will be ignored" + ); + this.getDouble( + data, + "SetValue", + this.setValue, + 0.0, + DoubleSingleValidator.greaterEqual0(), + BuilderDescriptorState.Stable, + "Set the value of the timer", + "Set the value of the timer. If 0 (default) it will be ignored" + ); + this.modifyRepeating = this.getBoolean( + data, "Repeating", this.repeating, false, BuilderDescriptorState.Stable, "Whether to repeat the timer when countdown finishes", null + ); + return this; + } + + @Nonnull + @Override + public Timer.TimerAction getTimerAction() { + return Timer.TimerAction.MODIFY; + } + + public double getIncreaseValue(@Nonnull BuilderSupport builderSupport) { + return this.increaseValue.get(builderSupport.getExecutionContext()); + } + + public double[] getRestartValueRange(@Nonnull BuilderSupport builderSupport) { + return this.restartValueRange.get(builderSupport.getExecutionContext()); + } + + public double getRate(@Nonnull BuilderSupport builderSupport) { + return this.rate.get(builderSupport.getExecutionContext()); + } + + public double getSetValue(@Nonnull BuilderSupport builderSupport) { + return this.setValue.get(builderSupport.getExecutionContext()); + } + + public boolean isModifyRepeating() { + return this.modifyRepeating; + } + + public boolean isRepeating(@Nonnull BuilderSupport support) { + return this.repeating.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerPause.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerPause.java new file mode 100644 index 0000000..dd74747 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerPause.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.timer.ActionTimer; +import com.hypixel.hytale.server.npc.util.Timer; +import javax.annotation.Nonnull; + +public class BuilderActionTimerPause extends BuilderActionTimer { + public BuilderActionTimerPause() { + } + + @Nonnull + public ActionTimer build(@Nonnull BuilderSupport builderSupport) { + return new ActionTimer(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Pause a timer"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Pause a timer"; + } + + @Nonnull + @Override + public Timer.TimerAction getTimerAction() { + return Timer.TimerAction.PAUSE; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerRestart.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerRestart.java new file mode 100644 index 0000000..d8226db --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerRestart.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.timer.ActionTimer; +import com.hypixel.hytale.server.npc.util.Timer; +import javax.annotation.Nonnull; + +public class BuilderActionTimerRestart extends BuilderActionTimer { + public BuilderActionTimerRestart() { + } + + @Nonnull + public ActionTimer build(@Nonnull BuilderSupport builderSupport) { + return new ActionTimer(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Restart a timer"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Restart a timer. Will be set to the original initial values."; + } + + @Nonnull + @Override + public Timer.TimerAction getTimerAction() { + return Timer.TimerAction.RESTART; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerStart.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerStart.java new file mode 100644 index 0000000..828d108 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerStart.java @@ -0,0 +1,91 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.timer.ActionTimer; +import com.hypixel.hytale.server.npc.util.Timer; +import javax.annotation.Nonnull; + +public class BuilderActionTimerStart extends BuilderActionTimer { + protected final NumberArrayHolder startValueRange = new NumberArrayHolder(); + protected final NumberArrayHolder restartValueRange = new NumberArrayHolder(); + protected final DoubleHolder rate = new DoubleHolder(); + protected final BooleanHolder repeating = new BooleanHolder(); + + public BuilderActionTimerStart() { + } + + @Nonnull + public ActionTimer build(@Nonnull BuilderSupport builderSupport) { + return new ActionTimer(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Start a timer"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Start a timer"; + } + + @Nonnull + @Override + public BuilderActionTimer readConfig(@Nonnull JsonElement data) { + super.readConfig(data); + this.requireDoubleRange( + data, + "StartValueRange", + this.startValueRange, + DoubleSequenceValidator.fromExclToInclWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "The range from which to pick an initial value to start at", + null + ); + this.requireDoubleRange( + data, + "RestartValueRange", + this.restartValueRange, + DoubleSequenceValidator.fromExclToInclWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "The range from which to pick a value when the timer is restarted", + "The range from which to pick a value when the timer is restarted. The upper bound is also the timer max" + ); + this.getDouble( + data, "Rate", this.rate, 1.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "The rate at which the timer will decrease", null + ); + this.getBoolean(data, "Repeating", this.repeating, false, BuilderDescriptorState.Stable, "Whether to repeat the timer when countdown finishes", null); + return this; + } + + @Nonnull + @Override + public Timer.TimerAction getTimerAction() { + return Timer.TimerAction.START; + } + + public double[] getStartValueRange(@Nonnull BuilderSupport builderSupport) { + return this.startValueRange.get(builderSupport.getExecutionContext()); + } + + public double[] getRestartValueRange(@Nonnull BuilderSupport builderSupport) { + return this.restartValueRange.get(builderSupport.getExecutionContext()); + } + + public double getRate(@Nonnull BuilderSupport builderSupport) { + return this.rate.get(builderSupport.getExecutionContext()); + } + + public boolean isRepeating(@Nonnull BuilderSupport support) { + return this.repeating.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerStop.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerStop.java new file mode 100644 index 0000000..754d7de --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderActionTimerStop.java @@ -0,0 +1,34 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.timer.ActionTimer; +import com.hypixel.hytale.server.npc.util.Timer; +import javax.annotation.Nonnull; + +public class BuilderActionTimerStop extends BuilderActionTimer { + public BuilderActionTimerStop() { + } + + @Nonnull + public ActionTimer build(@Nonnull BuilderSupport builderSupport) { + return new ActionTimer(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Stop a timer"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Stop a timer"; + } + + @Nonnull + @Override + public Timer.TimerAction getTimerAction() { + return Timer.TimerAction.STOP; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderBodyMotionTimer.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderBodyMotionTimer.java new file mode 100644 index 0000000..65537f2 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderBodyMotionTimer.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectReferenceHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.timer.BodyMotionTimer; +import com.hypixel.hytale.server.npc.instructions.BodyMotion; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderBodyMotionTimer extends BuilderMotionTimer { + public BuilderBodyMotionTimer() { + this.motion = new BuilderObjectReferenceHelper<>(BodyMotion.class, this); + } + + @Nullable + public BodyMotionTimer build(@Nonnull BuilderSupport builderSupport) { + BodyMotion motion = this.getMotion(builderSupport); + return motion == null ? null : new BodyMotionTimer(this, builderSupport, motion); + } + + @Nonnull + @Override + public final Class category() { + return BodyMotion.class; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderHeadMotionTimer.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderHeadMotionTimer.java new file mode 100644 index 0000000..70f2005 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderHeadMotionTimer.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectReferenceHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.timer.HeadMotionTimer; +import com.hypixel.hytale.server.npc.instructions.HeadMotion; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderHeadMotionTimer extends BuilderMotionTimer { + public BuilderHeadMotionTimer() { + this.motion = new BuilderObjectReferenceHelper<>(HeadMotion.class, this); + } + + @Nullable + public HeadMotionTimer build(@Nonnull BuilderSupport builderSupport) { + HeadMotion motion = this.getMotion(builderSupport); + return motion == null ? null : new HeadMotionTimer(this, builderSupport, motion); + } + + @Nonnull + @Override + public final Class category() { + return HeadMotion.class; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderMotionTimer.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderMotionTimer.java new file mode 100644 index 0000000..4016b1c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderMotionTimer.java @@ -0,0 +1,80 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectReferenceHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderMotionBase; +import com.hypixel.hytale.server.npc.instructions.Motion; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class BuilderMotionTimer extends BuilderMotionBase { + public static final double[] DEFAULT_TIMER_RANGE = new double[]{1.0, 1.0}; + protected final NumberArrayHolder timerRange = new NumberArrayHolder(); + protected BuilderObjectReferenceHelper motion; + + public BuilderMotionTimer() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Execute a Motion for a specific maximum time"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Execute a Motion for a specific maximum time. If the motion finishes earlier the Timer also finishes."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderMotionTimer readConfig(@Nonnull JsonElement data) { + this.getDoubleRange( + data, + "Time", + this.timerRange, + DEFAULT_TIMER_RANGE, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "Range of time from which the random timer length can be chosen", + null + ); + this.requireObject(data, "Motion", this.motion, BuilderDescriptorState.Stable, "Motion to execute", null, this.validationHelper); + return this; + } + + @Override + public boolean validate( + String configName, + @Nonnull NPCLoadTimeValidationHelper validationHelper, + @Nonnull ExecutionContext context, + Scope globalScope, + @Nonnull List errors + ) { + return super.validate(configName, validationHelper, context, globalScope, errors) + & this.motion.validate(configName, validationHelper, this.builderManager, context, globalScope, errors); + } + + public double[] getTimerRange(@Nonnull BuilderSupport support) { + return this.timerRange.get(support.getExecutionContext()); + } + + @Nullable + public T getMotion(@Nonnull BuilderSupport builderSupport) { + return this.motion.build(builderSupport); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderSensorAlarm.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderSensorAlarm.java new file mode 100644 index 0000000..00eb8a5 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderSensorAlarm.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.timer.SensorAlarm; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.util.Alarm; +import javax.annotation.Nonnull; + +public class BuilderSensorAlarm extends BuilderSensorBase { + protected final StringHolder name = new StringHolder(); + protected final EnumHolder state = new EnumHolder<>(); + protected final BooleanHolder clear = new BooleanHolder(); + + public BuilderSensorAlarm() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Check the state of a named alarm"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Check the state of a named alarm and optionally clear it if the time has passed"; + } + + @Nonnull + public SensorAlarm build(@Nonnull BuilderSupport builderSupport) { + return new SensorAlarm(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireString(data, "Name", this.name, StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The name of the alarm to check", null); + this.requireEnum(data, "State", this.state, SensorAlarm.State.class, BuilderDescriptorState.Stable, "The state to check for", null); + this.getBoolean(data, "Clear", this.clear, false, BuilderDescriptorState.Stable, "Whether to clear the alarm (unset it) if it has passed", null); + return this; + } + + public Alarm getAlarm(@Nonnull BuilderSupport support) { + return support.getAlarm(this.name.get(support.getExecutionContext())); + } + + public SensorAlarm.State getState(@Nonnull BuilderSupport support) { + return this.state.get(support.getExecutionContext()); + } + + public boolean isClear(@Nonnull BuilderSupport support) { + return this.clear.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderSensorTimer.java b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderSensorTimer.java new file mode 100644 index 0000000..1ebba59 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/timer/builders/BuilderSensorTimer.java @@ -0,0 +1,87 @@ +package com.hypixel.hytale.server.npc.corecomponents.timer.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.timer.SensorTimer; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.util.Timer; +import javax.annotation.Nonnull; + +public class BuilderSensorTimer extends BuilderSensorBase { + public static final double[] DEFAULT_TIME_ELAPSED_RANGE = new double[]{0.0, Double.MAX_VALUE}; + protected final NumberArrayHolder timeRemainingRange = new NumberArrayHolder(); + protected final StringHolder name = new StringHolder(); + protected Timer.TimerState timerState; + + public BuilderSensorTimer() { + } + + @Nonnull + public SensorTimer build(@Nonnull BuilderSupport builderSupport) { + return new SensorTimer(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Tests if a timer exists and the value is within a certain range"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Tests if a timer exists and the value is within a certain range."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireString(data, "Name", this.name, StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The name of the timer", null); + this.getEnum( + data, + "State", + e -> this.timerState = e, + Timer.TimerState.class, + Timer.TimerState.ANY, + BuilderDescriptorState.Stable, + "The timer's state to check", + null + ); + this.getDoubleRange( + data, + "TimeRemainingRange", + this.timeRemainingRange, + DEFAULT_TIME_ELAPSED_RANGE, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "The acceptable remaining time on the timer.", + null + ); + return this; + } + + public double[] getRemainingTimeRange(@Nonnull BuilderSupport support) { + return this.timeRemainingRange.get(support.getExecutionContext()); + } + + public Timer getTimer(@Nonnull BuilderSupport support) { + return support.getTimerByName(this.name.get(support.getExecutionContext())); + } + + public Timer.TimerState getTimerState() { + return this.timerState; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionNothing.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionNothing.java new file mode 100644 index 0000000..8a5d9ad --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionNothing.java @@ -0,0 +1,11 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import javax.annotation.Nonnull; + +public class ActionNothing extends ActionBase { + public ActionNothing(@Nonnull BuilderActionBase builder) { + super(builder); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionRandom.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionRandom.java new file mode 100644 index 0000000..977d94d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionRandom.java @@ -0,0 +1,151 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.WeightedAction; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderActionRandom; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import java.util.concurrent.ThreadLocalRandom; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionRandom extends ActionBase { + @Nonnull + protected final WeightedAction[] actions; + @Nonnull + protected final WeightedAction[] availableActions; + protected int availableActionsCount; + protected double totalWeight; + @Nullable + protected WeightedAction current; + + public ActionRandom(@Nonnull BuilderActionRandom builder, @Nonnull BuilderSupport support) { + super(builder); + this.actions = builder.getActions(support).toArray(WeightedAction[]::new); + + for (WeightedAction action : this.actions) { + if (action == null) { + throw new IllegalArgumentException("WeightedAction in Random actions list can't be null"); + } + } + + this.availableActions = new WeightedAction[this.actions.length]; + this.availableActionsCount = 0; + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + int length = this.actions.length; + if (super.canExecute(ref, role, sensorInfo, dt, store) && length != 0) { + if (this.current != null) { + return this.current.canExecute(ref, role, sensorInfo, dt, store); + } else { + this.availableActionsCount = 0; + this.totalWeight = 0.0; + + for (WeightedAction action : this.actions) { + if (action.canExecute(ref, role, sensorInfo, dt, store)) { + this.availableActions[this.availableActionsCount++] = action; + this.totalWeight = this.totalWeight + action.getWeight(); + } + } + + return this.availableActionsCount > 0; + } + } else { + return false; + } + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + if (this.availableActionsCount == 0) { + return true; + } else { + if (this.current == null) { + this.current = this.availableActions[0]; + this.totalWeight = this.totalWeight * ThreadLocalRandom.current().nextDouble(); + this.totalWeight = this.totalWeight - this.current.getWeight(); + + for (int i = 1; i < this.availableActionsCount && this.totalWeight >= 0.0; i++) { + this.current = this.availableActions[i]; + this.totalWeight = this.totalWeight - this.current.getWeight(); + } + + this.current.activate(role, sensorInfo); + } + + boolean finished = this.current.execute(ref, role, sensorInfo, dt, store); + if (finished) { + this.current.clearOnce(); + this.current.deactivate(role, sensorInfo); + this.current = null; + } + + return finished; + } + } + + @Override + public void registerWithSupport(Role role) { + for (WeightedAction action : this.actions) { + action.registerWithSupport(role); + } + } + + @Override + public void motionControllerChanged( + @Nullable Ref ref, + @Nonnull NPCEntity npcComponent, + MotionController motionController, + @Nullable ComponentAccessor componentAccessor + ) { + for (WeightedAction action : this.actions) { + action.motionControllerChanged(ref, npcComponent, motionController, componentAccessor); + } + } + + @Override + public void loaded(Role role) { + for (WeightedAction action : this.actions) { + action.loaded(role); + } + } + + @Override + public void spawned(Role role) { + for (WeightedAction action : this.actions) { + action.spawned(role); + } + } + + @Override + public void unloaded(Role role) { + for (WeightedAction action : this.actions) { + action.unloaded(role); + } + } + + @Override + public void removed(Role role) { + for (WeightedAction action : this.actions) { + action.removed(role); + } + } + + @Override + public void teleported(Role role, World from, World to) { + for (WeightedAction action : this.actions) { + action.teleported(role, from, to); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionResetInstructions.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionResetInstructions.java new file mode 100644 index 0000000..76d9c2f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionResetInstructions.java @@ -0,0 +1,40 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderActionResetInstructions; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionResetInstructions extends ActionBase { + protected final int[] instructions; + + public ActionResetInstructions(@Nonnull BuilderActionResetInstructions builder, @Nonnull BuilderSupport support) { + super(builder); + this.instructions = builder.getInstructions(support); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + role.addDeferredAction((_ref, _role, _dt, _store) -> this.resetInstructions(_role, _dt)); + return true; + } + + protected boolean resetInstructions(@Nonnull Role role, double dt) { + if (this.instructions.length == 0) { + role.resetAllInstructions(); + return true; + } else { + for (int instruction : this.instructions) { + role.resetInstruction(instruction); + } + + return true; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionSequence.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionSequence.java new file mode 100644 index 0000000..9319a10 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionSequence.java @@ -0,0 +1,108 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderActionSequence; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.instructions.ActionList; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponent; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponentCollection; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionSequence extends ActionBase implements IAnnotatedComponentCollection { + @Nonnull + protected final ActionList actions; + + public ActionSequence(@Nonnull BuilderActionSequence builder, @Nonnull BuilderSupport support) { + super(builder); + this.actions = builder.getActionList(support); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && this.actions.canExecute(ref, role, sensorInfo, dt, store); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + return this.actions.execute(ref, role, sensorInfo, dt, store); + } + + @Override + public void registerWithSupport(Role role) { + this.actions.registerWithSupport(role); + } + + @Override + public void motionControllerChanged( + @Nullable Ref ref, + @Nonnull NPCEntity npcComponent, + MotionController motionController, + @Nullable ComponentAccessor componentAccessor + ) { + this.actions.motionControllerChanged(ref, npcComponent, motionController, componentAccessor); + } + + @Override + public void loaded(Role role) { + this.actions.loaded(role); + } + + @Override + public void spawned(Role role) { + this.actions.spawned(role); + } + + @Override + public void unloaded(Role role) { + this.actions.unloaded(role); + } + + @Override + public void removed(Role role) { + this.actions.removed(role); + } + + @Override + public void teleported(Role role, World from, World to) { + this.actions.teleported(role, from, to); + } + + @Override + public void clearOnce() { + super.clearOnce(); + this.actions.clearOnce(); + } + + @Override + public void setOnce() { + super.setOnce(); + this.actions.setOnce(); + } + + @Override + public int componentCount() { + return this.actions.actionCount(); + } + + @Override + public IAnnotatedComponent getComponent(int index) { + return this.actions.getComponent(index); + } + + @Override + public void setContext(IAnnotatedComponent parent, int index) { + super.setContext(parent, index); + this.actions.setContext(parent); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionSetFlag.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionSetFlag.java new file mode 100644 index 0000000..05dcd8b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionSetFlag.java @@ -0,0 +1,29 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderActionSetFlag; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionSetFlag extends ActionBase { + protected final int flagIndex; + protected final boolean value; + + public ActionSetFlag(@Nonnull BuilderActionSetFlag builder, @Nonnull BuilderSupport support) { + super(builder); + this.flagIndex = builder.getFlagSlot(support); + this.value = builder.getValue(support); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + role.setFlag(this.flagIndex, this.value); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionTimeout.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionTimeout.java new file mode 100644 index 0000000..f440655 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/ActionTimeout.java @@ -0,0 +1,140 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionWithDelay; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderActionTimeout; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.instructions.Action; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponent; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponentCollection; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionTimeout extends ActionWithDelay implements IAnnotatedComponentCollection { + protected final boolean delayAfter; + @Nullable + protected final Action action; + + public ActionTimeout(@Nonnull BuilderActionTimeout builderActionTimeout, @Nonnull BuilderSupport builderSupport) { + super(builderActionTimeout, builderSupport); + this.action = builderActionTimeout.getAction(builderSupport); + this.delayAfter = builderActionTimeout.isDelayAfter(); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + if (super.canExecute(ref, role, sensorInfo, dt, store) && (this.action == null || this.action.canExecute(ref, role, sensorInfo, dt, store))) { + if (!this.isDelaying() && this.isDelayPrepared()) { + this.startDelay(role.getEntitySupport()); + } + + return !this.isDelaying(); + } else { + return false; + } + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + if (this.action != null) { + this.action.execute(ref, role, sensorInfo, dt, store); + } + + this.prepareDelay(); + return true; + } + + @Override + public void registerWithSupport(Role role) { + if (this.action != null) { + this.action.registerWithSupport(role); + } + + if (this.delayAfter) { + this.clearDelay(); + } else { + this.prepareDelay(); + } + } + + @Override + public void motionControllerChanged( + @Nullable Ref ref, + @Nonnull NPCEntity npcComponent, + MotionController motionController, + @Nullable ComponentAccessor componentAccessor + ) { + if (this.action != null) { + this.action.motionControllerChanged(ref, npcComponent, motionController, componentAccessor); + } + } + + @Override + public void loaded(Role role) { + if (this.action != null) { + this.action.loaded(role); + } + } + + @Override + public void spawned(Role role) { + if (this.action != null) { + this.action.spawned(role); + } + } + + @Override + public void unloaded(Role role) { + if (this.action != null) { + this.action.unloaded(role); + } + } + + @Override + public void removed(Role role) { + if (this.action != null) { + this.action.removed(role); + } + } + + @Override + public void teleported(Role role, World from, World to) { + if (this.action != null) { + this.action.teleported(role, from, to); + } + } + + @Override + public void clearOnce() { + super.clearOnce(); + if (this.delayAfter) { + this.clearDelay(); + } else { + this.prepareDelay(); + } + } + + @Override + public int componentCount() { + return this.action != null ? 1 : 0; + } + + @Nullable + @Override + public IAnnotatedComponent getComponent(int index) { + if (index >= this.componentCount()) { + throw new IndexOutOfBoundsException(); + } else { + return this.action; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/BodyMotionNothing.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/BodyMotionNothing.java new file mode 100644 index 0000000..56121ee --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/BodyMotionNothing.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.BodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderBodyMotionBase; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BodyMotionNothing extends BodyMotionBase { + public BodyMotionNothing(@Nonnull BuilderBodyMotionBase builderMotionBase) { + super(builderMotionBase); + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role support, + @Nullable InfoProvider sensorInfo, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + desiredSteering.clear(); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/BodyMotionSequence.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/BodyMotionSequence.java new file mode 100644 index 0000000..4b7bcde --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/BodyMotionSequence.java @@ -0,0 +1,20 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderBodyMotionSequence; +import com.hypixel.hytale.server.npc.instructions.BodyMotion; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponentCollection; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BodyMotionSequence extends MotionSequence implements BodyMotion, IAnnotatedComponentCollection { + public BodyMotionSequence(@Nonnull BuilderBodyMotionSequence builder, @Nonnull BuilderSupport support) { + super(builder, builder.getSteps(support)); + } + + @Nullable + @Override + public BodyMotion getSteeringMotion() { + return this.activeMotion == null ? null : this.activeMotion.getSteeringMotion(); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/HeadMotionNothing.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/HeadMotionNothing.java new file mode 100644 index 0000000..10469ca --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/HeadMotionNothing.java @@ -0,0 +1,31 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.HeadMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderHeadMotionBase; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HeadMotionNothing extends HeadMotionBase { + public HeadMotionNothing(BuilderHeadMotionBase builderMotionBase) { + super(builderMotionBase); + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role support, + @Nullable InfoProvider sensorInfo, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + desiredSteering.clear(); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/HeadMotionSequence.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/HeadMotionSequence.java new file mode 100644 index 0000000..5e6dfc7 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/HeadMotionSequence.java @@ -0,0 +1,13 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderHeadMotionSequence; +import com.hypixel.hytale.server.npc.instructions.HeadMotion; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponentCollection; +import javax.annotation.Nonnull; + +public class HeadMotionSequence extends MotionSequence implements HeadMotion, IAnnotatedComponentCollection { + public HeadMotionSequence(@Nonnull BuilderHeadMotionSequence builder, @Nonnull BuilderSupport support) { + super(builder, builder.getSteps(support)); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/MotionSequence.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/MotionSequence.java new file mode 100644 index 0000000..c9a138a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/MotionSequence.java @@ -0,0 +1,190 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.MotionBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderMotionSequence; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.instructions.Motion; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponent; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponentCollection; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class MotionSequence extends MotionBase implements IAnnotatedComponentCollection { + protected final boolean looped; + protected final boolean restartOnActivate; + protected final T[] steps; + protected boolean finished; + protected int index; + @Nullable + protected T activeMotion; + + public MotionSequence(@Nonnull BuilderMotionSequence builder, T[] steps) { + this.restart(); + this.looped = builder.isLooped(); + this.restartOnActivate = builder.isRestartOnActivate(); + this.steps = steps; + } + + @Override + public void activate(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + if (this.restartOnActivate) { + this.deactivate(ref, role, componentAccessor); + this.restart(); + } + + if (!this.finished) { + this.doActivate(ref, role, componentAccessor); + } + } + + @Override + public void deactivate(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + if (this.activeMotion != null) { + this.activeMotion.deactivate(ref, role, componentAccessor); + this.activeMotion = null; + } + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role role, + @Nullable InfoProvider sensorInfo, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.finished) { + desiredSteering.clear(); + return false; + } else { + T currentActiveMotion = this.activeMotion; + + do { + Objects.requireNonNull(this.activeMotion, "Active motion not set"); + if (this.activeMotion.computeSteering(ref, role, sensorInfo, dt, desiredSteering, componentAccessor)) { + return true; + } + + if (this.index + 1 < this.steps.length) { + this.activateNext(ref, this.index + 1, role, componentAccessor); + } else { + if (!this.looped) { + break; + } + + this.activateNext(ref, 0, role, componentAccessor); + } + } while (this.activeMotion != currentActiveMotion); + + this.deactivate(ref, role, componentAccessor); + this.finished = true; + return false; + } + } + + @Override + public void registerWithSupport(Role role) { + for (T step : this.steps) { + step.registerWithSupport(role); + } + } + + @Override + public void motionControllerChanged( + @Nullable Ref ref, + @Nonnull NPCEntity npcComponent, + MotionController motionController, + @Nullable ComponentAccessor componentAccessor + ) { + for (T step : this.steps) { + step.motionControllerChanged(ref, npcComponent, motionController, componentAccessor); + } + } + + @Override + public void loaded(Role role) { + for (T step : this.steps) { + step.loaded(role); + } + } + + @Override + public void spawned(Role role) { + for (T step : this.steps) { + step.spawned(role); + } + } + + @Override + public void unloaded(Role role) { + for (T step : this.steps) { + step.unloaded(role); + } + } + + @Override + public void removed(Role role) { + for (T step : this.steps) { + step.removed(role); + } + } + + @Override + public void teleported(Role role, World from, World to) { + for (T step : this.steps) { + step.teleported(role, from, to); + } + } + + @Override + public int componentCount() { + return this.steps.length; + } + + @Override + public IAnnotatedComponent getComponent(int index) { + return this.steps[index]; + } + + @Override + public void setContext(IAnnotatedComponent parent, int index) { + for (int i = 0; i < this.steps.length; i++) { + this.steps[i].setContext(parent, i); + } + } + + public void restart() { + this.index = 0; + this.finished = false; + } + + protected void doActivate(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + if (this.steps.length == 0) { + throw new IllegalArgumentException("Motion sequence must have steps!"); + } else if (this.index >= 0 && this.index < this.steps.length) { + this.activeMotion = this.steps[this.index]; + Objects.requireNonNull(this.activeMotion, "Active motion must not be null"); + this.activeMotion.activate(ref, role, componentAccessor); + } else { + throw new IndexOutOfBoundsException( + String.format("Motion sequence index out of range (%s) must be less than size (%s)", this.index, this.steps.length) + ); + } + } + + protected void activateNext(@Nonnull Ref ref, int newIndex, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + this.activeMotion.deactivate(ref, role, componentAccessor); + this.index = newIndex; + this.doActivate(ref, role, componentAccessor); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorAdjustPosition.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorAdjustPosition.java new file mode 100644 index 0000000..b5df2f1 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorAdjustPosition.java @@ -0,0 +1,131 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorAdjustPosition; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.DebugSupport; +import com.hypixel.hytale.server.npc.sensorinfo.IPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.sensorinfo.PositionProvider; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponent; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponentCollection; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SensorAdjustPosition extends SensorBase implements IAnnotatedComponentCollection { + protected final Sensor sensor; + @Nonnull + protected final Vector3d offset; + protected final PositionProvider positionProvider = new PositionProvider(); + + public SensorAdjustPosition(@Nonnull BuilderSensorAdjustPosition builder, @Nonnull BuilderSupport support, Sensor sensor) { + super(builder); + this.sensor = sensor; + this.offset = builder.getOffset(support); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (super.matches(ref, role, dt, store) && this.sensor.matches(ref, role, dt, store)) { + InfoProvider sensorInfo = this.sensor.getSensorInfo(); + if (sensorInfo == null) { + return false; + } else { + IPositionProvider otherPositionProvider = sensorInfo.getPositionProvider(); + double x = otherPositionProvider.getX() + this.offset.x; + double y = otherPositionProvider.getY() + this.offset.y; + double z = otherPositionProvider.getZ() + this.offset.z; + this.positionProvider.setTarget(x, y, z); + return true; + } + } else { + this.positionProvider.clear(); + DebugSupport debugSupport = role.getDebugSupport(); + if (debugSupport.isTraceSensorFails()) { + debugSupport.setLastFailingSensor(this.sensor); + } + + return false; + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } + + @Override + public void registerWithSupport(Role role) { + this.sensor.registerWithSupport(role); + } + + @Override + public void motionControllerChanged( + @Nullable Ref ref, + @Nonnull NPCEntity npcComponent, + MotionController motionController, + @Nullable ComponentAccessor componentAccessor + ) { + this.sensor.motionControllerChanged(ref, npcComponent, motionController, componentAccessor); + } + + @Override + public void loaded(Role role) { + this.sensor.loaded(role); + } + + @Override + public void spawned(Role role) { + this.sensor.spawned(role); + } + + @Override + public void unloaded(Role role) { + this.sensor.unloaded(role); + } + + @Override + public void removed(Role role) { + this.sensor.removed(role); + } + + @Override + public void teleported(Role role, World from, World to) { + this.sensor.teleported(role, from, to); + } + + @Override + public void done() { + this.sensor.done(); + } + + @Override + public int componentCount() { + return 1; + } + + @Override + public IAnnotatedComponent getComponent(int index) { + if (index >= this.componentCount()) { + throw new IndexOutOfBoundsException(); + } else { + return this.sensor; + } + } + + @Override + public void setContext(IAnnotatedComponent parent, int index) { + super.setContext(parent, index); + this.sensor.setContext(this, index); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorAnd.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorAnd.java new file mode 100644 index 0000000..6d55d1d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorAnd.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorAnd; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.DebugSupport; +import com.hypixel.hytale.server.npc.sensorinfo.WrappedInfoProvider; +import java.util.List; +import javax.annotation.Nonnull; + +public class SensorAnd extends SensorMany { + public SensorAnd(@Nonnull BuilderSensorAnd builder, @Nonnull BuilderSupport support, @Nonnull List sensors) { + super(builder, support, sensors); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + this.infoProvider.clearPositionMatch(); + DebugSupport debugSupport = role.getDebugSupport(); + int length = this.sensors.length; + if (super.matches(ref, role, dt, store) && length != 0) { + for (Sensor s : this.sensors) { + if (!s.matches(ref, role, dt, store)) { + if (this.autoUnlockTargetSlot >= 0) { + role.getMarkedEntitySupport().clearMarkedEntity(this.autoUnlockTargetSlot); + } + + this.infoProvider.clearPositionMatch(); + if (debugSupport.isTraceSensorFails()) { + debugSupport.setLastFailingSensor(s); + } + + return false; + } + + if (!this.infoProvider.hasPosition() && s.getSensorInfo() != null && s.getSensorInfo().hasPosition()) { + this.infoProvider.setPositionMatch(s.getSensorInfo().getPositionProvider()); + } + } + + return true; + } else { + if (this.autoUnlockTargetSlot >= 0) { + role.getMarkedEntitySupport().clearMarkedEntity(this.autoUnlockTargetSlot); + } + + if (debugSupport.isTraceSensorFails()) { + debugSupport.setLastFailingSensor(this); + } + + return false; + } + } + + @Nonnull + @Override + protected WrappedInfoProvider createInfoProvider() { + return new WrappedInfoProvider(this.sensors); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorAny.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorAny.java new file mode 100644 index 0000000..809643e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorAny.java @@ -0,0 +1,17 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorAny extends SensorBase { + public SensorAny(@Nonnull BuilderSensorBase builderSensorBase) { + super(builderSensorBase); + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorEval.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorEval.java new file mode 100644 index 0000000..40ca7ab --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorEval.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorEval; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.StdScope; +import com.hypixel.hytale.server.npc.util.expression.ValueType; +import com.hypixel.hytale.server.npc.util.expression.compile.CompileContext; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +public class SensorEval extends SensorBase { + protected final String expression; + @Nonnull + protected final CompileContext compileContext; + protected ExecutionContext.Instruction[] instructions; + protected boolean isValid; + + public SensorEval(@Nonnull BuilderSensorEval builderSensorEval, @Nonnull BuilderSupport support) { + super(builderSensorEval); + this.expression = builderSensorEval.getExpression(); + this.compileContext = new CompileContext(); + this.isValid = true; + + try { + ObjectArrayList instructions = new ObjectArrayList<>(); + StdScope scope = support.getSensorScope(); + ValueType valueType = this.compile(this.expression, scope, instructions); + if (valueType != ValueType.BOOLEAN) { + this.isValid = false; + throw new IllegalStateException("Expression '" + this.expression + "' must return boolean value but is:" + valueType); + } else { + this.instructions = instructions.toArray(ExecutionContext.Instruction[]::new); + } + } catch (RuntimeException var6) { + this.isValid = false; + throw new RuntimeException("Error evaluating '" + this.expression + "'", var6); + } + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + return super.matches(ref, role, dt, store) && this.isValid && this.evalBoolean(role.getEntitySupport().getSensorScope(), this.instructions); + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } + + protected ValueType compile(@Nonnull String expression, StdScope sensorScope, List instructions) { + return this.compileContext.compile(expression, sensorScope, true, instructions); + } + + protected boolean evalBoolean(StdScope sensorScope, @Nonnull ExecutionContext.Instruction[] instructions) { + ExecutionContext executionContext = this.compileContext.getExecutionContext(); + if (executionContext.execute(instructions, sensorScope) != ValueType.BOOLEAN) { + throw new IllegalStateException("Expression must return boolean value"); + } else { + return executionContext.popBoolean(); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorFlag.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorFlag.java new file mode 100644 index 0000000..ba6d455 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorFlag.java @@ -0,0 +1,32 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorFlag; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorFlag extends SensorBase { + protected final int flagIndex; + protected final boolean value; + + public SensorFlag(@Nonnull BuilderSensorFlag builder, @Nonnull BuilderSupport support) { + super(builder); + this.flagIndex = builder.getFlagSlot(support); + this.value = builder.getValue(support); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + return super.matches(ref, role, dt, store) && role.isFlagSet(this.flagIndex) == this.value; + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorMany.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorMany.java new file mode 100644 index 0000000..f4c339d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorMany.java @@ -0,0 +1,132 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorMany; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.sensorinfo.WrappedInfoProvider; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponent; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponentCollection; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class SensorMany extends SensorBase implements IAnnotatedComponentCollection { + @Nonnull + protected final Sensor[] sensors; + protected final int autoUnlockTargetSlot; + protected final WrappedInfoProvider infoProvider; + + public SensorMany(@Nonnull BuilderSensorMany builder, @Nonnull BuilderSupport support, @Nonnull List sensors) { + super(builder); + if (sensors == null) { + throw new IllegalArgumentException("Sensor list can't be null"); + } else { + this.sensors = sensors.toArray(Sensor[]::new); + + for (Sensor sensor : this.sensors) { + if (sensor == null) { + throw new IllegalArgumentException("Sensor in sensor list can't be null"); + } + } + + this.autoUnlockTargetSlot = builder.getAutoUnlockedTargetSlot(support); + this.infoProvider = this.createInfoProvider(); + } + } + + @Override + public void done() { + for (Sensor s : this.sensors) { + s.done(); + } + } + + @Override + public void registerWithSupport(Role role) { + for (Sensor sensor : this.sensors) { + sensor.registerWithSupport(role); + } + } + + @Override + public void motionControllerChanged( + @Nullable Ref ref, + @Nonnull NPCEntity npcComponent, + MotionController motionController, + @Nullable ComponentAccessor componentAccessor + ) { + for (Sensor sensor : this.sensors) { + sensor.motionControllerChanged(ref, npcComponent, motionController, componentAccessor); + } + } + + @Override + public void loaded(Role role) { + for (Sensor sensor : this.sensors) { + sensor.loaded(role); + } + } + + @Override + public void spawned(Role role) { + for (Sensor sensor : this.sensors) { + sensor.spawned(role); + } + } + + @Override + public void unloaded(Role role) { + for (Sensor sensor : this.sensors) { + sensor.unloaded(role); + } + } + + @Override + public void removed(Role role) { + for (Sensor sensor : this.sensors) { + sensor.removed(role); + } + } + + @Override + public void teleported(Role role, World from, World to) { + for (Sensor sensor : this.sensors) { + sensor.teleported(role, from, to); + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.infoProvider; + } + + @Override + public int componentCount() { + return this.sensors.length; + } + + @Override + public IAnnotatedComponent getComponent(int index) { + return this.sensors[index]; + } + + @Override + public void setContext(IAnnotatedComponent parent, int index) { + super.setContext(parent, index); + + for (int i = 0; i < this.sensors.length; i++) { + this.sensors[i].setContext(this, i); + } + } + + protected abstract WrappedInfoProvider createInfoProvider(); +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorNot.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorNot.java new file mode 100644 index 0000000..b948c2a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorNot.java @@ -0,0 +1,126 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorNot; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.DebugSupport; +import com.hypixel.hytale.server.npc.sensorinfo.EntityPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponent; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponentCollection; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SensorNot extends SensorBase implements IAnnotatedComponentCollection { + protected final Sensor sensor; + protected final int targetSlot; + protected final int autoUnlockTargetSlot; + protected final EntityPositionProvider positionProvider = new EntityPositionProvider(); + + public SensorNot(@Nonnull BuilderSensorNot builder, @Nonnull BuilderSupport support, Sensor sensor) { + super(builder); + this.sensor = sensor; + this.targetSlot = builder.getUsedTargetSlot(support); + this.autoUnlockTargetSlot = builder.getAutoUnlockTargetSlot(support); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (super.matches(ref, role, dt, store) && !this.sensor.matches(ref, role, dt, store)) { + Ref targetRef = this.targetSlot >= 0 ? role.getMarkedEntitySupport().getMarkedEntityRef(this.targetSlot) : null; + this.positionProvider.setTarget(targetRef, store); + return true; + } else { + this.positionProvider.clear(); + if (this.autoUnlockTargetSlot >= 0) { + role.getMarkedEntitySupport().clearMarkedEntity(this.autoUnlockTargetSlot); + } + + DebugSupport debugSupport = role.getDebugSupport(); + if (debugSupport.isTraceSensorFails()) { + debugSupport.setLastFailingSensor(this.sensor); + } + + return false; + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } + + @Override + public void registerWithSupport(Role role) { + this.sensor.registerWithSupport(role); + } + + @Override + public void motionControllerChanged( + @Nullable Ref ref, + @Nonnull NPCEntity npcComponent, + MotionController motionController, + @Nullable ComponentAccessor componentAccessor + ) { + this.sensor.motionControllerChanged(ref, npcComponent, motionController, componentAccessor); + } + + @Override + public void loaded(Role role) { + this.sensor.loaded(role); + } + + @Override + public void spawned(Role role) { + this.sensor.spawned(role); + } + + @Override + public void unloaded(Role role) { + this.sensor.unloaded(role); + } + + @Override + public void removed(Role role) { + this.sensor.removed(role); + } + + @Override + public void teleported(Role role, World from, World to) { + this.sensor.teleported(role, from, to); + } + + @Override + public void done() { + this.sensor.done(); + } + + @Override + public int componentCount() { + return 1; + } + + @Override + public IAnnotatedComponent getComponent(int index) { + if (index >= this.componentCount()) { + throw new IndexOutOfBoundsException(); + } else { + return this.sensor; + } + } + + @Override + public void setContext(IAnnotatedComponent parent, int index) { + super.setContext(parent, index); + this.sensor.setContext(this, index); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorOr.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorOr.java new file mode 100644 index 0000000..79890b4 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorOr.java @@ -0,0 +1,73 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorOr; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.DebugSupport; +import com.hypixel.hytale.server.npc.sensorinfo.WrappedInfoProvider; +import java.util.List; +import javax.annotation.Nonnull; + +public class SensorOr extends SensorMany { + public SensorOr(@Nonnull BuilderSensorOr builder, @Nonnull BuilderSupport support, @Nonnull List sensors) { + super(builder, support, sensors); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + this.infoProvider.clearMatches(); + this.infoProvider.clearPositionMatch(); + DebugSupport debugSupport = role.getDebugSupport(); + if (!super.matches(ref, role, dt, store)) { + if (this.autoUnlockTargetSlot >= 0) { + role.getMarkedEntitySupport().clearMarkedEntity(this.autoUnlockTargetSlot); + } + + if (debugSupport.isTraceSensorFails()) { + debugSupport.setLastFailingSensor(this); + } + + return false; + } else { + Sensor match = null; + if (super.matches(ref, role, dt, store)) { + for (Sensor s : this.sensors) { + if (s.matches(ref, role, dt, store)) { + if (match == null) { + match = s; + } + + this.infoProvider.addMatch(s); + if (!this.infoProvider.hasPosition() && s.getSensorInfo() != null && s.getSensorInfo().hasPosition()) { + this.infoProvider.setPositionMatch(s.getSensorInfo().getPositionProvider()); + } + } + } + } + + if (match == null) { + if (this.autoUnlockTargetSlot >= 0) { + role.getMarkedEntitySupport().clearMarkedEntity(this.autoUnlockTargetSlot); + } + + if (debugSupport.isTraceSensorFails()) { + debugSupport.setLastFailingSensor(this); + } + + return false; + } else { + return true; + } + } + } + + @Nonnull + @Override + protected WrappedInfoProvider createInfoProvider() { + return new WrappedInfoProvider(); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorRandom.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorRandom.java new file mode 100644 index 0000000..bce0601 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorRandom.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorRandom; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorRandom extends SensorBase { + protected final double minFalseDuration; + protected final double maxFalseDuration; + protected final double minTrueDuration; + protected final double maxTrueDuration; + protected double remainingDuration; + protected boolean state; + + public SensorRandom(@Nonnull BuilderSensorRandom builder, @Nonnull BuilderSupport support) { + super(builder); + double[] falseDuration = builder.getFalseRange(support); + this.minFalseDuration = falseDuration[0]; + this.maxFalseDuration = falseDuration[1]; + double[] trueDuration = builder.getTrueRange(support); + this.minTrueDuration = trueDuration[0]; + this.maxTrueDuration = trueDuration[1]; + this.state = RandomExtra.randomBoolean(); + this.remainingDuration = this.pickNextDuration(); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + return false; + } else { + if ((this.remainingDuration -= dt) <= 0.0) { + this.state = !this.state; + this.remainingDuration = this.pickNextDuration(); + } + + return this.state; + } + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } + + protected double pickNextDuration() { + return this.state + ? RandomExtra.randomRange(this.minTrueDuration, this.maxTrueDuration) + : RandomExtra.randomRange(this.minFalseDuration, this.maxFalseDuration); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorSwitch.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorSwitch.java new file mode 100644 index 0000000..671093d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorSwitch.java @@ -0,0 +1,30 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorSwitch; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorSwitch extends SensorBase { + protected final boolean flag; + + public SensorSwitch(@Nonnull BuilderSensorSwitch builderSensorSwitch, @Nonnull BuilderSupport builderSupport) { + super(builderSensorSwitch); + this.flag = builderSensorSwitch.getSwitch(builderSupport); + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + return super.matches(ref, role, dt, store) && this.flag; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorValueProviderWrapper.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorValueProviderWrapper.java new file mode 100644 index 0000000..0d72e9d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/SensorValueProviderWrapper.java @@ -0,0 +1,212 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorValueProviderWrapper; +import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderValueToParameterMapping; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.DebugSupport; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.sensorinfo.ValueWrappedInfoProvider; +import com.hypixel.hytale.server.npc.sensorinfo.parameterproviders.MultipleParameterProvider; +import com.hypixel.hytale.server.npc.sensorinfo.parameterproviders.SingleDoubleParameterProvider; +import com.hypixel.hytale.server.npc.sensorinfo.parameterproviders.SingleIntParameterProvider; +import com.hypixel.hytale.server.npc.sensorinfo.parameterproviders.SingleStringParameterProvider; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponent; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponentCollection; +import com.hypixel.hytale.server.npc.valuestore.ValueStore; +import it.unimi.dsi.fastutil.ints.IntObjectPair; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SensorValueProviderWrapper extends SensorBase implements IAnnotatedComponentCollection { + protected static final IntObjectPair[] EMPTY_ARRAY = new IntObjectPair[0]; + @Nonnull + protected final Sensor sensor; + protected final boolean passValues; + @Nonnull + protected final IntObjectPair[] stringParameterProviders; + @Nonnull + protected final IntObjectPair[] intParameterProviders; + @Nonnull + protected final IntObjectPair[] doubleParameterProviders; + @Nonnull + protected final ValueWrappedInfoProvider infoProvider; + protected final MultipleParameterProvider multipleParameterProvider = new MultipleParameterProvider(); + protected final ComponentType valueStoreComponentType; + + public SensorValueProviderWrapper(@Nonnull BuilderSensorValueProviderWrapper builder, @Nonnull BuilderSupport support, @Nonnull Sensor sensor) { + super(builder); + this.sensor = sensor; + this.passValues = builder.isPassValues(support); + this.infoProvider = new ValueWrappedInfoProvider(sensor.getSensorInfo(), this.multipleParameterProvider); + ObjectArrayList> stringMappings = new ObjectArrayList<>(); + ObjectArrayList> intMappings = new ObjectArrayList<>(); + ObjectArrayList> doubleMappings = new ObjectArrayList<>(); + List parameterMappings = builder.getParameterMappings(support); + if (parameterMappings != null) { + for (int i = 0; i < parameterMappings.size(); i++) { + BuilderValueToParameterMapping.ValueToParameterMapping mapping = parameterMappings.get(i); + int slot = mapping.getToParameterSlot(); + switch (mapping.getType()) { + case String: { + SingleStringParameterProvider provider = new SingleStringParameterProvider(slot); + this.multipleParameterProvider.addParameterProvider(slot, provider); + stringMappings.add(IntObjectPair.of(mapping.getFromValueSlot(), provider)); + break; + } + case Int: { + SingleIntParameterProvider provider = new SingleIntParameterProvider(slot); + this.multipleParameterProvider.addParameterProvider(slot, provider); + intMappings.add(IntObjectPair.of(mapping.getFromValueSlot(), provider)); + break; + } + case Double: { + SingleDoubleParameterProvider provider = new SingleDoubleParameterProvider(slot); + this.multipleParameterProvider.addParameterProvider(slot, provider); + doubleMappings.add(IntObjectPair.of(mapping.getFromValueSlot(), provider)); + } + } + } + } + + if (stringMappings.isEmpty()) { + this.stringParameterProviders = (IntObjectPair[])EMPTY_ARRAY; + } else { + this.stringParameterProviders = stringMappings.toArray(IntObjectPair[]::new); + } + + if (intMappings.isEmpty()) { + this.intParameterProviders = (IntObjectPair[])EMPTY_ARRAY; + } else { + this.intParameterProviders = intMappings.toArray(IntObjectPair[]::new); + } + + if (doubleMappings.isEmpty()) { + this.doubleParameterProviders = (IntObjectPair[])EMPTY_ARRAY; + } else { + this.doubleParameterProviders = doubleMappings.toArray(IntObjectPair[]::new); + } + + this.valueStoreComponentType = ValueStore.getComponentType(); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store) || !this.sensor.matches(ref, role, dt, store)) { + DebugSupport debugSupport = role.getDebugSupport(); + if (debugSupport.isTraceSensorFails()) { + debugSupport.setLastFailingSensor(this.sensor); + } + + this.multipleParameterProvider.clear(); + return false; + } else if (!this.passValues) { + return true; + } else { + ValueStore valueStore = store.getComponent(ref, this.valueStoreComponentType); + if (valueStore == null) { + return false; + } else { + for (IntObjectPair provider : this.stringParameterProviders) { + String value = valueStore.readString(provider.firstInt()); + provider.value().overrideString(value); + } + + for (IntObjectPair provider : this.intParameterProviders) { + int value = valueStore.readInt(provider.firstInt()); + provider.value().overrideInt(value); + } + + for (IntObjectPair provider : this.doubleParameterProviders) { + double value = valueStore.readDouble(provider.firstInt()); + provider.value().overrideDouble(value); + } + + return true; + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.infoProvider; + } + + @Override + public void registerWithSupport(Role role) { + this.sensor.registerWithSupport(role); + } + + @Override + public void motionControllerChanged( + @Nullable Ref ref, + @Nonnull NPCEntity npcComponent, + MotionController motionController, + @Nullable ComponentAccessor componentAccessor + ) { + this.sensor.motionControllerChanged(ref, npcComponent, motionController, componentAccessor); + } + + @Override + public void loaded(Role role) { + this.sensor.loaded(role); + } + + @Override + public void spawned(Role role) { + this.sensor.spawned(role); + } + + @Override + public void unloaded(Role role) { + this.sensor.unloaded(role); + } + + @Override + public void removed(Role role) { + this.sensor.removed(role); + } + + @Override + public void teleported(Role role, World from, World to) { + this.sensor.teleported(role, from, to); + } + + @Override + public void done() { + this.sensor.done(); + } + + @Override + public int componentCount() { + return 1; + } + + @Nonnull + @Override + public IAnnotatedComponent getComponent(int index) { + if (index >= this.componentCount()) { + throw new IndexOutOfBoundsException(); + } else { + return this.sensor; + } + } + + @Override + public void setContext(IAnnotatedComponent parent, int index) { + super.setContext(parent, index); + this.sensor.setContext(this, index); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionNothing.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionNothing.java new file mode 100644 index 0000000..ea13ac0 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionNothing.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.ActionNothing; +import javax.annotation.Nonnull; + +public class BuilderActionNothing extends BuilderActionBase { + public BuilderActionNothing() { + } + + @Nonnull + public ActionNothing build(BuilderSupport builderSupport) { + return new ActionNothing(this); + } + + @Nonnull + public BuilderActionNothing readConfig(JsonElement data) { + return this; + } + + @Nonnull + @Override + public String getShortDescription() { + return "Do nothing"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Do nothing. Used often as placeholder."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionRandom.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionRandom.java new file mode 100644 index 0000000..148be3a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionRandom.java @@ -0,0 +1,68 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectListHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.WeightedAction; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.ActionRandom; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderActionRandom extends BuilderActionBase { + protected final BuilderObjectListHelper actions = new BuilderObjectListHelper<>(WeightedAction.class, this); + + public BuilderActionRandom() { + } + + @Nonnull + public ActionRandom build(@Nonnull BuilderSupport builderSupport) { + return new ActionRandom(this, builderSupport); + } + + @Nonnull + public BuilderActionRandom readConfig(@Nonnull JsonElement data) { + this.requireArray(data, "Actions", this.actions, null, BuilderDescriptorState.Stable, "List of possible actions", null, this.validationHelper); + return this; + } + + @Nonnull + @Override + public String getShortDescription() { + return "Execute a single random action from a list of weighted actions."; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Override + public boolean validate( + String configName, + @Nonnull NPCLoadTimeValidationHelper validationHelper, + @Nonnull ExecutionContext context, + Scope globalScope, + @Nonnull List errors + ) { + return super.validate(configName, validationHelper, context, globalScope, errors) + & this.actions.validate(configName, validationHelper, this.builderManager, context, globalScope, errors); + } + + @Nullable + public List getActions(@Nonnull BuilderSupport builderSupport) { + return this.actions.build(builderSupport); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionResetInstructions.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionResetInstructions.java new file mode 100644 index 0000000..58237ec --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionResetInstructions.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringArrayNoEmptyStringsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.ActionResetInstructions; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public class BuilderActionResetInstructions extends BuilderActionBase { + protected final StringArrayHolder instructions = new StringArrayHolder(); + + public BuilderActionResetInstructions() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Force reset instructionList"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Force reset instructionList, either by name, or as a whole"; + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionResetInstructions(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionResetInstructions readConfig(@Nonnull JsonElement data) { + this.getStringArray( + data, + "Instructions", + this.instructions, + null, + 0, + Integer.MAX_VALUE, + StringArrayNoEmptyStringsValidator.get(), + BuilderDescriptorState.Stable, + "The instructionList to reset", + "The instructionList to reset. If left empty, will reset all instructionList" + ); + return this; + } + + public int[] getInstructions(@Nonnull BuilderSupport support) { + String[] instructionNames = this.instructions.get(support.getExecutionContext()); + if (instructionNames == null) { + return ArrayUtil.EMPTY_INT_ARRAY; + } else { + int[] instructionIndexes = new int[instructionNames.length]; + + for (int i = 0; i < instructionIndexes.length; i++) { + instructionIndexes[i] = support.getInstructionSlot(instructionNames[i]); + } + + return instructionIndexes; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionSequence.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionSequence.java new file mode 100644 index 0000000..e13f2d8 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionSequence.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectReferenceHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.ActionSequence; +import com.hypixel.hytale.server.npc.instructions.ActionList; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; + +public class BuilderActionSequence extends BuilderActionBase { + protected final BuilderObjectReferenceHelper actions = new BuilderObjectReferenceHelper<>(ActionList.class, this); + protected boolean blocking; + protected boolean atomic; + + public BuilderActionSequence() { + } + + @Nonnull + public ActionSequence build(@Nonnull BuilderSupport builderSupport) { + return new ActionSequence(this, builderSupport); + } + + @Nonnull + public BuilderActionSequence readConfig(@Nonnull JsonElement data) { + this.getBoolean( + data, + "Blocking", + b -> this.blocking = b, + false, + BuilderDescriptorState.Stable, + "Do not execute an action unless the previous action could execute", + null + ); + this.getBoolean(data, "Atomic", b -> this.atomic = b, false, BuilderDescriptorState.Stable, "Only execute actions if all actions can be executed", null); + this.requireObject(data, "Actions", this.actions, BuilderDescriptorState.Stable, "List of actions", null, this.validationHelper); + return this; + } + + @Nonnull + @Override + public String getShortDescription() { + return "List of actions."; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Execute list of actions."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Override + public boolean validate( + String configName, + @Nonnull NPCLoadTimeValidationHelper validationHelper, + @Nonnull ExecutionContext context, + Scope globalScope, + @Nonnull List errors + ) { + return super.validate(configName, validationHelper, context, globalScope, errors) + & this.actions.validate(configName, validationHelper, this.builderManager, context, globalScope, errors); + } + + @Nonnull + public ActionList getActionList(@Nonnull BuilderSupport builderSupport) { + ActionList actions = this.actions.build(builderSupport); + if (actions != ActionList.EMPTY_ACTION_LIST) { + actions.setAtomic(this.atomic); + actions.setBlocking(this.blocking); + } + + return actions; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionSetFlag.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionSetFlag.java new file mode 100644 index 0000000..6e22e9a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionSetFlag.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.ActionSetFlag; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public class BuilderActionSetFlag extends BuilderActionBase { + protected final StringHolder name = new StringHolder(); + protected final BooleanHolder value = new BooleanHolder(); + + public BuilderActionSetFlag() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Set a named flag to a boolean value"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionSetFlag(this, builderSupport); + } + + @Nonnull + public BuilderActionSetFlag readConfig(@Nonnull JsonElement data) { + this.requireString(data, "Name", this.name, StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The name of the flag", null); + this.getBoolean(data, "SetTo", this.value, true, BuilderDescriptorState.Stable, "The value to set the flag to", null); + return this; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + public int getFlagSlot(@Nonnull BuilderSupport support) { + String flag = this.name.get(support.getExecutionContext()); + return support.getFlagSlot(flag); + } + + public boolean getValue(@Nonnull BuilderSupport support) { + return this.value.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionTimeout.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionTimeout.java new file mode 100644 index 0000000..f00c742 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderActionTimeout.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectReferenceHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionWithDelay; +import com.hypixel.hytale.server.npc.corecomponents.utility.ActionTimeout; +import com.hypixel.hytale.server.npc.instructions.Action; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderActionTimeout extends BuilderActionWithDelay { + protected boolean delayAfter; + protected final BuilderObjectReferenceHelper action = new BuilderObjectReferenceHelper<>(Action.class, this); + + public BuilderActionTimeout() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Delay an action, or insert a delay in a sequence of actions"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Delay an action by a time which is randomly picked between a given minimum and maximum value."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public ActionTimeout build(@Nonnull BuilderSupport builderSupport) { + return new ActionTimeout(this, builderSupport); + } + + @Nonnull + public BuilderActionTimeout readConfig(@Nonnull JsonElement data) { + this.getBoolean(data, "DelayAfter", b -> this.delayAfter = b, false, BuilderDescriptorState.Stable, "Delay after executing the action", null); + this.getObject(data, "Action", this.action, BuilderDescriptorState.Stable, "Optional action to delay", null, this.validationHelper); + return this; + } + + @Override + public boolean validate( + String configName, + @Nonnull NPCLoadTimeValidationHelper validationHelper, + @Nonnull ExecutionContext context, + Scope globalScope, + @Nonnull List errors + ) { + return super.validate(configName, validationHelper, context, globalScope, errors) + & this.action.validate(configName, validationHelper, this.builderManager, context, globalScope, errors); + } + + public boolean isDelayAfter() { + return this.delayAfter; + } + + @Nullable + public Action getAction(@Nonnull BuilderSupport builderSupport) { + return this.action.build(builderSupport); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderBodyMotionNothing.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderBodyMotionNothing.java new file mode 100644 index 0000000..713a7d0 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderBodyMotionNothing.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderBodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.BodyMotionNothing; +import javax.annotation.Nonnull; + +public class BuilderBodyMotionNothing extends BuilderBodyMotionBase { + public BuilderBodyMotionNothing() { + } + + @Nonnull + public BodyMotionNothing build(BuilderSupport builderSupport) { + return new BodyMotionNothing(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Do nothing"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Do nothing"; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderBodyMotionSequence.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderBodyMotionSequence.java new file mode 100644 index 0000000..8a93b72 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderBodyMotionSequence.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectListHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.utility.BodyMotionSequence; +import com.hypixel.hytale.server.npc.instructions.BodyMotion; +import java.util.List; +import javax.annotation.Nonnull; + +public class BuilderBodyMotionSequence extends BuilderMotionSequence { + public static final BodyMotion[] EMPTY_SEQUENCE = new BodyMotion[0]; + + public BuilderBodyMotionSequence() { + this.steps = new BuilderObjectListHelper<>(BodyMotion.class, this); + } + + @Nonnull + public BodyMotionSequence build(@Nonnull BuilderSupport builderSupport) { + return new BodyMotionSequence(this, builderSupport); + } + + @Nonnull + @Override + public final Class category() { + return BodyMotion.class; + } + + @Nonnull + public BodyMotion[] getSteps(@Nonnull BuilderSupport builderSupport) { + List motions = this.steps.build(builderSupport); + return motions == null ? EMPTY_SEQUENCE : motions.toArray(BodyMotion[]::new); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderHeadMotionNothing.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderHeadMotionNothing.java new file mode 100644 index 0000000..9b70b51 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderHeadMotionNothing.java @@ -0,0 +1,35 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderHeadMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.HeadMotionNothing; +import javax.annotation.Nonnull; + +public class BuilderHeadMotionNothing extends BuilderHeadMotionBase { + public BuilderHeadMotionNothing() { + } + + @Nonnull + public HeadMotionNothing build(BuilderSupport builderSupport) { + return new HeadMotionNothing(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Do nothing"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Do nothing"; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderHeadMotionSequence.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderHeadMotionSequence.java new file mode 100644 index 0000000..1560e8e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderHeadMotionSequence.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectListHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.utility.HeadMotionSequence; +import com.hypixel.hytale.server.npc.instructions.HeadMotion; +import java.util.List; +import javax.annotation.Nonnull; + +public class BuilderHeadMotionSequence extends BuilderMotionSequence { + public static final HeadMotion[] EMPTY_SEQUENCE = new HeadMotion[0]; + + public BuilderHeadMotionSequence() { + this.steps = new BuilderObjectListHelper<>(HeadMotion.class, this); + } + + @Nonnull + public HeadMotionSequence build(@Nonnull BuilderSupport builderSupport) { + return new HeadMotionSequence(this, builderSupport); + } + + @Nonnull + @Override + public final Class category() { + return HeadMotion.class; + } + + @Nonnull + public HeadMotion[] getSteps(@Nonnull BuilderSupport builderSupport) { + List motions = this.steps.build(builderSupport); + return motions == null ? EMPTY_SEQUENCE : motions.toArray(HeadMotion[]::new); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderMotionSequence.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderMotionSequence.java new file mode 100644 index 0000000..183be4c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderMotionSequence.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectListHelper; +import com.hypixel.hytale.server.npc.asset.builder.validators.ArrayNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderMotionBase; +import com.hypixel.hytale.server.npc.instructions.Motion; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import javax.annotation.Nonnull; + +public abstract class BuilderMotionSequence extends BuilderMotionBase { + protected BuilderObjectListHelper steps; + protected boolean looped; + protected boolean restartOnActivate; + + public BuilderMotionSequence() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "(Looped)Sequence of motions"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Sequence of motions. Can be used in conjunction with 'Timer' to model more complex motions."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderMotionSequence readConfig(@Nonnull JsonElement data) { + this.getBoolean(data, "Looped", b -> this.looped = b, true, BuilderDescriptorState.Stable, "When true restart after last motion is finished", null); + this.getBoolean( + data, + "RestartOnActivate", + b -> this.restartOnActivate = b, + false, + BuilderDescriptorState.Experimental, + "Restart from first motion when NPC is activated.", + null + ); + this.requireArray( + data, "Motions", this.steps, ArrayNotEmptyValidator.get(), BuilderDescriptorState.Stable, "Array of motions", null, this.validationHelper + ); + return this; + } + + @Override + public boolean validate( + String configName, + @Nonnull NPCLoadTimeValidationHelper validationHelper, + @Nonnull ExecutionContext context, + Scope globalScope, + @Nonnull List errors + ) { + return super.validate(configName, validationHelper, context, globalScope, errors) + & this.steps.validate(configName, validationHelper, this.builderManager, context, globalScope, errors); + } + + public boolean isLooped() { + return this.looped; + } + + public boolean isRestartOnActivate() { + return this.restartOnActivate; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorAdjustPosition.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorAdjustPosition.java new file mode 100644 index 0000000..eac0a3e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorAdjustPosition.java @@ -0,0 +1,90 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectReferenceHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.SensorAdjustPosition; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderSensorAdjustPosition extends BuilderSensorBase { + protected final BuilderObjectReferenceHelper sensor = new BuilderObjectReferenceHelper<>(Sensor.class, this); + protected final NumberArrayHolder offset = new NumberArrayHolder(); + + public BuilderSensorAdjustPosition() { + } + + @Nullable + public SensorAdjustPosition build(@Nonnull BuilderSupport builderSupport) { + Sensor sensor = this.getSensor(builderSupport); + return sensor == null ? null : new SensorAdjustPosition(this, builderSupport, sensor); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Perform adjustments to the wrapped sensor's returned position"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Override + public void registerTags(@Nonnull Set tags) { + super.registerTags(tags); + tags.add("logic"); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderSensorAdjustPosition readConfig(@Nonnull JsonElement data) { + this.requireObject(data, "Sensor", this.sensor, BuilderDescriptorState.Stable, "Sensor to wrap", null, this.validationHelper); + this.requireVector3d( + data, "Offset", this.offset, null, BuilderDescriptorState.Stable, "The offset to apply to the returned position from the sensor", null + ); + this.provideFeature(Feature.Position); + return this; + } + + @Override + public boolean validate( + String configName, + @Nonnull NPCLoadTimeValidationHelper validationHelper, + @Nonnull ExecutionContext context, + Scope globalScope, + @Nonnull List errors + ) { + return super.validate(configName, validationHelper, context, globalScope, errors) + & this.sensor.validate(configName, validationHelper, this.builderManager, context, globalScope, errors); + } + + @Nullable + public Sensor getSensor(@Nonnull BuilderSupport support) { + return this.sensor.build(support); + } + + @Nonnull + public Vector3d getOffset(@Nonnull BuilderSupport support) { + double[] offsetArray = this.offset.get(support.getExecutionContext()); + return new Vector3d(offsetArray[0], offsetArray[1], offsetArray[2]); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorAnd.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorAnd.java new file mode 100644 index 0000000..3458c67 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorAnd.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.utility.SensorAnd; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderSensorAnd extends BuilderSensorMany { + public BuilderSensorAnd() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Logical AND of list of sensors"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Evaluate all sensors and execute action only when all sensor signal true. Target is provided by first sensor."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nullable + public SensorAnd build(@Nonnull BuilderSupport builderSupport) { + List sensors = this.objectListHelper.build(builderSupport); + return sensors.isEmpty() ? null : new SensorAnd(this, builderSupport, sensors); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorAny.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorAny.java new file mode 100644 index 0000000..d8741f9 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorAny.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.SensorAny; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import java.util.Set; +import javax.annotation.Nonnull; + +public class BuilderSensorAny extends BuilderSensorBase { + public BuilderSensorAny() { + } + + @Nonnull + public Sensor build(BuilderSupport builderSupport) { + return (Sensor)(!this.once ? Sensor.NULL : new SensorAny(this)); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Return always true"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Sensor always signals true but doesn't return a target."; + } + + @Override + public void registerTags(@Nonnull Set tags) { + super.registerTags(tags); + tags.add("logic"); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(JsonElement data) { + return this; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorEval.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorEval.java new file mode 100644 index 0000000..764af6c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorEval.java @@ -0,0 +1,54 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.SensorEval; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorEval extends BuilderSensorBase { + protected String expression; + + public BuilderSensorEval() { + } + + @Nonnull + public SensorEval build(@Nonnull BuilderSupport builderSupport) { + return new SensorEval(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Evaluate javascript expression and test if true"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Evaluate javascript expression and test truth value. Current values accessible are 'health' and 'blocked'."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireString( + data, "Expression", s -> this.expression = s, StringNotEmptyValidator.get(), BuilderDescriptorState.Experimental, "Javascript expression", null + ); + return this; + } + + public String getExpression() { + return this.expression; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorFlag.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorFlag.java new file mode 100644 index 0000000..46609d4 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorFlag.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.SensorFlag; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorFlag extends BuilderSensorBase { + protected final StringHolder name = new StringHolder(); + protected final BooleanHolder value = new BooleanHolder(); + + public BuilderSensorFlag() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test if a named flag is set or not"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorFlag(this, builderSupport); + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireString(data, "Name", this.name, StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The name of the flag", null); + this.getBoolean(data, "Set", this.value, true, BuilderDescriptorState.Stable, "Whether the flag should be set or not", null); + return this; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + public int getFlagSlot(@Nonnull BuilderSupport support) { + String flag = this.name.get(support.getExecutionContext()); + return support.getFlagSlot(flag); + } + + public boolean getValue(@Nonnull BuilderSupport support) { + return this.value.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorMany.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorMany.java new file mode 100644 index 0000000..d902dc9 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorMany.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectListHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.ArrayNotEmptyValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNullOrNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; + +public abstract class BuilderSensorMany extends BuilderSensorBase { + @Nonnull + protected BuilderObjectListHelper objectListHelper = new BuilderObjectListHelper<>(Sensor.class, this); + protected final StringHolder unlockTargetSlot = new StringHolder(); + + public BuilderSensorMany() { + } + + @Override + public void registerTags(@Nonnull Set tags) { + super.registerTags(tags); + tags.add("logic"); + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.preventParameterOverride(); + this.requireArray( + data, "Sensors", this.objectListHelper, ArrayNotEmptyValidator.get(), BuilderDescriptorState.Stable, "List of sensors", null, this.validationHelper + ); + this.getString( + data, + "AutoUnlockTargetSlot", + this.unlockTargetSlot, + null, + StringNullOrNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "A target slot to unlock when sensor doesn't match anymore", + null + ); + return this; + } + + @Override + public boolean validate( + String configName, + @Nonnull NPCLoadTimeValidationHelper validationHelper, + @Nonnull ExecutionContext context, + Scope globalScope, + @Nonnull List errors + ) { + return super.validate(configName, validationHelper, context, globalScope, errors) + & this.objectListHelper.validate(configName, validationHelper, this.builderManager, context, globalScope, errors); + } + + public int getAutoUnlockedTargetSlot(@Nonnull BuilderSupport support) { + String slot = this.unlockTargetSlot.get(support.getExecutionContext()); + return slot == null ? Integer.MIN_VALUE : support.getTargetSlot(slot); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorNot.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorNot.java new file mode 100644 index 0000000..4b88b21 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorNot.java @@ -0,0 +1,111 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectReferenceHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNullOrNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.SensorNot; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderSensorNot extends BuilderSensorBase { + protected final BuilderObjectReferenceHelper sensor = new BuilderObjectReferenceHelper<>(Sensor.class, this); + protected final StringHolder targetSlot = new StringHolder(); + protected final StringHolder autoUnlockTargetSlot = new StringHolder(); + + public BuilderSensorNot() { + } + + @Nullable + public SensorNot build(@Nonnull BuilderSupport builderSupport) { + Sensor sensor = this.getSensor(builderSupport); + return sensor == null ? null : new SensorNot(this, builderSupport, sensor); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Invert sensor test"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Return true when the given sensor test fails."; + } + + @Override + public void registerTags(@Nonnull Set tags) { + super.registerTags(tags); + tags.add("logic"); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.WorkInProgress; + } + + @Nonnull + public BuilderSensorNot readConfig(@Nonnull JsonElement data) { + this.preventParameterOverride(); + this.requireObject(data, "Sensor", this.sensor, BuilderDescriptorState.Stable, "Sensor to test", null, this.validationHelper); + this.getString( + data, + "UseTargetSlot", + this.targetSlot, + null, + StringNullOrNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "A locked target slot to feed to action (if available)", + null + ); + this.getString( + data, + "AutoUnlockTargetSlot", + this.autoUnlockTargetSlot, + null, + StringNullOrNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "A target slot to unlock when sensor doesn't match anymore", + null + ); + return this; + } + + @Override + public boolean validate( + String configName, + @Nonnull NPCLoadTimeValidationHelper validationHelper, + @Nonnull ExecutionContext context, + Scope globalScope, + @Nonnull List errors + ) { + return super.validate(configName, validationHelper, context, globalScope, errors) + & this.sensor.validate(configName, validationHelper, this.builderManager, context, globalScope, errors); + } + + @Nullable + public Sensor getSensor(@Nonnull BuilderSupport support) { + return this.sensor.build(support); + } + + public int getUsedTargetSlot(@Nonnull BuilderSupport support) { + String slot = this.targetSlot.get(support.getExecutionContext()); + return slot == null ? Integer.MIN_VALUE : support.getTargetSlot(slot); + } + + public int getAutoUnlockTargetSlot(@Nonnull BuilderSupport support) { + String slot = this.autoUnlockTargetSlot.get(support.getExecutionContext()); + return slot == null ? Integer.MIN_VALUE : support.getTargetSlot(slot); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorOr.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorOr.java new file mode 100644 index 0000000..48f665c --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorOr.java @@ -0,0 +1,38 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.utility.SensorOr; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderSensorOr extends BuilderSensorMany { + public BuilderSensorOr() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Logical OR of list of sensors"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Evaluate sensors and execute action when at least one sensor signals true. Target is provided by first sensor signalling true."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nullable + public SensorOr build(@Nonnull BuilderSupport builderSupport) { + List sensors = this.objectListHelper.build(builderSupport); + return sensors.isEmpty() ? null : new SensorOr(this, builderSupport, sensors); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorRandom.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorRandom.java new file mode 100644 index 0000000..b7a48a9 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorRandom.java @@ -0,0 +1,75 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.SensorRandom; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorRandom extends BuilderSensorBase { + protected final NumberArrayHolder falseRange = new NumberArrayHolder(); + protected final NumberArrayHolder trueRange = new NumberArrayHolder(); + + public BuilderSensorRandom() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Alternates between returning true and false for specified random durations"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorRandom(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireDoubleRange( + data, + "TrueDurationRange", + this.trueRange, + DoubleSequenceValidator.fromExclToInclWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "The duration range to pick a random period to return true", + null + ); + this.requireDoubleRange( + data, + "FalseDurationRange", + this.falseRange, + DoubleSequenceValidator.fromExclToInclWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "The duration range to pick a random period to return false", + null + ); + return this; + } + + public double[] getFalseRange(@Nonnull BuilderSupport support) { + return this.falseRange.get(support.getExecutionContext()); + } + + public double[] getTrueRange(@Nonnull BuilderSupport support) { + return this.trueRange.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorSwitch.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorSwitch.java new file mode 100644 index 0000000..fcbce40 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorSwitch.java @@ -0,0 +1,59 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.SensorSwitch; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import java.util.Set; +import javax.annotation.Nonnull; + +public class BuilderSensorSwitch extends BuilderSensorBase { + protected final BooleanHolder switchHolder = new BooleanHolder(); + + public BuilderSensorSwitch() { + } + + @Nonnull + public SensorSwitch build(@Nonnull BuilderSupport builderSupport) { + return new SensorSwitch(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Check if a computed boolean is true"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Check if a computed boolean is true"; + } + + @Override + public void registerTags(@Nonnull Set tags) { + super.registerTags(tags); + tags.add("logic"); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireBoolean(data, "Switch", this.switchHolder, BuilderDescriptorState.Stable, "The switch to check", "The switch to check"); + return this; + } + + public boolean getSwitch(@Nonnull BuilderSupport builderSupport) { + return this.switchHolder.get(builderSupport.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorValueProviderWrapper.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorValueProviderWrapper.java new file mode 100644 index 0000000..d5f543e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderSensorValueProviderWrapper.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectReferenceHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectStaticListHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.ArrayNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.utility.SensorValueProviderWrapper; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderSensorValueProviderWrapper extends BuilderSensorBase { + protected final BooleanHolder passValues = new BooleanHolder(); + protected final BuilderObjectReferenceHelper sensor = new BuilderObjectReferenceHelper<>(Sensor.class, this); + protected final BuilderObjectStaticListHelper parameterMappings = new BuilderObjectStaticListHelper<>( + BuilderValueToParameterMapping.ValueToParameterMapping.class, this + ); + + public BuilderSensorValueProviderWrapper() { + } + + @Nullable + public SensorValueProviderWrapper build(@Nonnull BuilderSupport builderSupport) { + Sensor sensor = this.getSensor(builderSupport); + return sensor == null ? null : new SensorValueProviderWrapper(this, builderSupport, sensor); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Wraps a sensor and passes down some additional parameter overrides pulled from the value store"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Override + public void registerTags(@Nonnull Set tags) { + super.registerTags(tags); + tags.add("logic"); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderSensorValueProviderWrapper readConfig(@Nonnull JsonElement data) { + this.getBoolean(data, "PassValues", this.passValues, true, BuilderDescriptorState.Stable, "Used to enable/disable passing of values in components", null); + this.requireObject(data, "Sensor", this.sensor, BuilderDescriptorState.Stable, "Sensor to wrap", null, this.validationHelper); + this.requireArray( + data, + "ValueToParameterMappings", + this.parameterMappings, + ArrayNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "The mappings of values to override parameters", + null, + this.validationHelper + ); + return this; + } + + @Override + public boolean validate( + String configName, + @Nonnull NPCLoadTimeValidationHelper validationHelper, + @Nonnull ExecutionContext context, + Scope globalScope, + @Nonnull List errors + ) { + boolean valid = super.validate(configName, validationHelper, context, globalScope, errors) + & this.sensor.validate(configName, validationHelper, this.builderManager, context, globalScope, errors) + & this.parameterMappings.validate(configName, validationHelper, this.builderManager, context, globalScope, errors); + HashSet parameterSlotNames = new HashSet<>(); + + for (BuilderValueToParameterMapping.ValueToParameterMapping mapping : this.parameterMappings.staticBuild(this.builderManager)) { + String name = mapping.getToParameterSlotName(); + if (!parameterSlotNames.add(name)) { + errors.add(String.format("%s: Cannot write values to the same parameter override from more than one source: %s", configName, name)); + valid = false; + } + } + + return valid; + } + + public boolean isPassValues(@Nonnull BuilderSupport support) { + return this.passValues.get(support.getExecutionContext()); + } + + @Nullable + public Sensor getSensor(@Nonnull BuilderSupport support) { + return this.sensor.build(support); + } + + @Nullable + public List getParameterMappings(@Nonnull BuilderSupport support) { + return !this.passValues.get(support.getExecutionContext()) ? null : this.parameterMappings.build(support); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderValueToParameterMapping.java b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderValueToParameterMapping.java new file mode 100644 index 0000000..8ee4783 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/utility/builders/BuilderValueToParameterMapping.java @@ -0,0 +1,130 @@ +package com.hypixel.hytale.server.npc.corecomponents.utility.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.valuestore.ValueStore; +import com.hypixel.hytale.server.npc.valuestore.ValueStoreValidator; +import java.util.function.ToIntFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderValueToParameterMapping extends BuilderBase { + protected ValueStore.Type type; + protected String fromValue; + protected ToIntFunction fromSlot; + protected String toParameter; + + public BuilderValueToParameterMapping() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "An entry containing a list of actions to execute when moving from one state to another"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public BuilderValueToParameterMapping.ValueToParameterMapping build(BuilderSupport builderSupport) { + return new BuilderValueToParameterMapping.ValueToParameterMapping(this, builderSupport); + } + + @Nonnull + @Override + public Class category() { + return BuilderValueToParameterMapping.ValueToParameterMapping.class; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Override + public boolean isEnabled(ExecutionContext context) { + return true; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireEnum(data, "ValueType", e -> this.type = e, ValueStore.Type.class, BuilderDescriptorState.Stable, "The type of the value being mapped", null); + this.requireString( + data, + "FromValue", + s -> this.fromValue = s, + StringNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "The value to read from the value store", + null + ); + if (this.builderDescriptor == null) { + this.fromSlot = switch (this.type) { + case String -> this.requireStringValueStoreParameter(this.fromValue, ValueStoreValidator.UseType.READ); + case Int -> this.requireIntValueStoreParameter(this.fromValue, ValueStoreValidator.UseType.READ); + case Double -> this.requireDoubleValueStoreParameter(this.fromValue, ValueStoreValidator.UseType.READ); + }; + } + + this.requireString( + data, "ToParameter", s -> this.toParameter = s, StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The parameter name to override", null + ); + return this; + } + + public ValueStore.Type getType() { + return this.type; + } + + public int getFromSlot(BuilderSupport support) { + return this.fromSlot.applyAsInt(support); + } + + public String getToParameter() { + return this.toParameter; + } + + public static class ValueToParameterMapping { + private final ValueStore.Type type; + private int fromValueSlot; + private int toParameterSlot; + private String toParameterSlotName; + + private ValueToParameterMapping(@Nonnull BuilderValueToParameterMapping builder, @Nullable BuilderSupport support) { + this.type = builder.getType(); + if (support != null) { + this.fromValueSlot = builder.getFromSlot(support); + this.toParameterSlot = support.getParameterSlot(builder.getToParameter()); + } else { + this.toParameterSlotName = builder.getToParameter(); + } + } + + public ValueStore.Type getType() { + return this.type; + } + + public int getFromValueSlot() { + return this.fromValueSlot; + } + + public int getToParameterSlot() { + return this.toParameterSlot; + } + + public String getToParameterSlotName() { + return this.toParameterSlotName; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionMakePath.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionMakePath.java new file mode 100644 index 0000000..707d36e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionMakePath.java @@ -0,0 +1,55 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.builtin.path.path.TransientPathDefinition; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.path.IPath; +import com.hypixel.hytale.server.core.universe.world.path.SimplePathWaypoint; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionMakePath; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionMakePath extends ActionBase { + @Nullable + protected final TransientPathDefinition pathDefinition; + protected boolean built; + + public ActionMakePath(@Nonnull BuilderActionMakePath builder, @Nonnull BuilderSupport support) { + super(builder); + this.pathDefinition = builder.getPath(support); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && !this.built; + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + IPath path = this.pathDefinition.buildPath(transformComponent.getPosition(), headRotationComponent.getRotation()); + npcComponent.getPathManager().setTransientPath(path); + this.built = true; + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionPlaceBlock.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionPlaceBlock.java new file mode 100644 index 0000000..8ebb502 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionPlaceBlock.java @@ -0,0 +1,89 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionPlaceBlock; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.CachedPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.BlockPlacementHelper; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionPlaceBlock extends ActionBase { + protected static final ComponentType BOUNDING_BOX_COMPONENT_TYPE = BoundingBox.getComponentType(); + protected final double range; + protected final boolean allowEmptyMaterials; + protected final Vector3d target = new Vector3d(); + + public ActionPlaceBlock(@Nonnull BuilderActionPlaceBlock builder, @Nonnull BuilderSupport support) { + super(builder); + this.range = builder.getRange(support); + this.allowEmptyMaterials = builder.isAllowEmptyMaterials(support); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, double dt, @Nonnull Store store) { + if (super.canExecute(ref, role, sensorInfo, dt, store) && sensorInfo != null && sensorInfo.hasPosition()) { + String blockToPlace = role.getWorldSupport().getBlockToPlace(); + if (blockToPlace == null) { + return false; + } else { + BlockType placedBlockType = BlockType.getAssetMap().getAsset(blockToPlace); + if (placedBlockType == null) { + return false; + } else { + sensorInfo.getPositionProvider().providePosition(this.target); + World world = store.getExternalData().getWorld(); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + double maxDistance = this.range; + BoundingBox hitBox = store.getComponent(ref, BOUNDING_BOX_COMPONENT_TYPE); + if (hitBox != null) { + maxDistance += hitBox.getBoundingBox().getMaximumExtent(); + } + + int x = MathUtil.floor(this.target.getX()); + int y = MathUtil.floor(this.target.getY()); + int z = MathUtil.floor(this.target.getZ()); + if (transformComponent.getPosition().distanceSquaredTo(x, y, z) > maxDistance * maxDistance) { + return false; + } else if (sensorInfo instanceof CachedPositionProvider && !((CachedPositionProvider)sensorInfo).isFromCache()) { + return true; + } else { + return !BlockPlacementHelper.canPlaceUnitBlock(world, placedBlockType, this.allowEmptyMaterials, x, y, z) + ? false + : BlockPlacementHelper.canPlaceBlock(world, placedBlockType, 0, this.allowEmptyMaterials, x, y, z); + } + } + } + } else { + return false; + } + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + World world = store.getExternalData().getWorld(); + WorldChunk chunk = world.getNonTickingChunk(ChunkUtil.indexChunkFromBlock(this.target.getX(), this.target.getZ())); + chunk.setBlock( + MathUtil.floor(this.target.getX()), MathUtil.floor(this.target.getY()), MathUtil.floor(this.target.getZ()), role.getWorldSupport().getBlockToPlace() + ); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionResetBlockSensors.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionResetBlockSensors.java new file mode 100644 index 0000000..1d76e98 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionResetBlockSensors.java @@ -0,0 +1,41 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionResetBlockSensors; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.WorldSupport; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionResetBlockSensors extends ActionBase { + protected final int[] blockSets; + + public ActionResetBlockSensors(@Nonnull BuilderActionResetBlockSensors builder, @Nonnull BuilderSupport support) { + super(builder); + this.blockSets = builder.getBlockSets(support); + + for (int blockSet : this.blockSets) { + support.registerBlockSensorResetAction(blockSet); + } + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + WorldSupport worldSupport = role.getWorldSupport(); + if (this.blockSets.length == 0) { + worldSupport.resetAllBlockSensors(); + return true; + } else { + for (int blockSet : this.blockSets) { + worldSupport.resetBlockSensorFoundBlock(blockSet); + } + + return true; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionResetPath.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionResetPath.java new file mode 100644 index 0000000..3efc46f --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionResetPath.java @@ -0,0 +1,23 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionResetPath; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionResetPath extends ActionBase { + public ActionResetPath(@Nonnull BuilderActionResetPath builder) { + super(builder); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + role.getWorldSupport().requestNewPath(); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionResetSearchRays.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionResetSearchRays.java new file mode 100644 index 0000000..f6e42ac --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionResetSearchRays.java @@ -0,0 +1,37 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionResetSearchRays; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.WorldSupport; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionResetSearchRays extends ActionBase { + protected final int[] searchRayIds; + + public ActionResetSearchRays(@Nonnull BuilderActionResetSearchRays builder, @Nonnull BuilderSupport support) { + super(builder); + this.searchRayIds = builder.getIds(support); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + WorldSupport worldSupport = role.getWorldSupport(); + if (this.searchRayIds.length == 0) { + worldSupport.resetAllCachedSearchRayPositions(); + return true; + } else { + for (int id : this.searchRayIds) { + worldSupport.resetCachedSearchRayPosition(id); + } + + return true; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionSetBlockToPlace.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionSetBlockToPlace.java new file mode 100644 index 0000000..1a29bad --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionSetBlockToPlace.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionSetBlockToPlace; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class ActionSetBlockToPlace extends ActionBase { + protected final String blockType; + + public ActionSetBlockToPlace(@Nonnull BuilderActionSetBlockToPlace builder, @Nonnull BuilderSupport support) { + super(builder); + this.blockType = builder.getBlockType(support); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && BlockType.getAssetMap().getAsset(this.blockType) != null; + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + role.getWorldSupport().setBlockToPlace(this.blockType); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionSetLeashPosition.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionSetLeashPosition.java new file mode 100644 index 0000000..e11c168 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionSetLeashPosition.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionSetLeashPosition; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionSetLeashPosition extends ActionBase { + protected final boolean toTarget; + protected final boolean toCurrent; + + public ActionSetLeashPosition(@Nonnull BuilderActionSetLeashPosition builderActionSetLeashPosition) { + super(builderActionSetLeashPosition); + this.toCurrent = builderActionSetLeashPosition.isToCurrent(); + this.toTarget = builderActionSetLeashPosition.isToTarget(); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + if (this.toCurrent) { + setLeashPosition(ref, ref, store); + } else if (this.toTarget && sensorInfo != null) { + Ref targetRef = sensorInfo.hasPosition() ? sensorInfo.getPositionProvider().getTarget() : null; + if (targetRef != null) { + setLeashPosition(ref, targetRef, store); + } + } + + return true; + } + + protected static void setLeashPosition( + @Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor + ) { + NPCEntity selfNpcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType()); + + assert selfNpcComponent != null; + + TransformComponent entityTransformComponent = componentAccessor.getComponent(targetRef, TransformComponent.getComponentType()); + + assert entityTransformComponent != null; + + Vector3f entityBodyRotation = entityTransformComponent.getRotation(); + selfNpcComponent.getLeashPoint().assign(entityTransformComponent.getPosition()); + selfNpcComponent.setLeashPitch(entityBodyRotation.getPitch()); + selfNpcComponent.setLeashHeading(entityBodyRotation.getYaw()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionStorePosition.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionStorePosition.java new file mode 100644 index 0000000..43a251b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionStorePosition.java @@ -0,0 +1,33 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionStorePosition; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionStorePosition extends ActionBase { + protected final int slot; + + public ActionStorePosition(@Nonnull BuilderActionStorePosition builder, @Nonnull BuilderSupport support) { + super(builder); + this.slot = builder.getSlot(support); + } + + @Override + public boolean canExecute(@Nonnull Ref ref, @Nonnull Role role, @Nullable InfoProvider sensorInfo, double dt, @Nonnull Store store) { + return super.canExecute(ref, role, sensorInfo, dt, store) && sensorInfo != null && sensorInfo.hasPosition(); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, @Nonnull InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + sensorInfo.getPositionProvider().providePosition(role.getMarkedEntitySupport().getStoredPosition(this.slot)); + return true; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionTriggerSpawners.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionTriggerSpawners.java new file mode 100644 index 0000000..9aee612 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/ActionTriggerSpawners.java @@ -0,0 +1,105 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.ActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionTriggerSpawners; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.spawning.spawnmarkers.SpawnMarkerEntity; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ActionTriggerSpawners extends ActionBase { + protected static final ComponentType SPAWN_MARKER_ENTITY_COMPONENT_TYPE = SpawnMarkerEntity.getComponentType(); + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected final String spawner; + protected final double range; + protected final double rangeSquared; + protected final int count; + @Nullable + protected final List> triggerList; + protected Ref parentRef; + + public ActionTriggerSpawners(@Nonnull BuilderActionTriggerSpawners builder, @Nonnull BuilderSupport support) { + super(builder); + this.spawner = builder.getSpawner(support); + this.range = builder.getRange(support); + this.rangeSquared = this.range * this.range; + this.count = builder.getCount(support); + this.triggerList = this.count > 0 ? new ObjectArrayList<>(this.count) : null; + } + + @Override + public void registerWithSupport(@Nonnull Role role) { + role.getPositionCache().requireSpawnMarkerDistance(this.range); + } + + @Override + public boolean execute(@Nonnull Ref ref, @Nonnull Role role, InfoProvider sensorInfo, double dt, @Nonnull Store store) { + super.execute(ref, role, sensorInfo, dt, store); + this.parentRef = ref; + List> spawners = role.getPositionCache().getSpawnMarkerList(); + if (this.count <= 0) { + for (int i = 0; i < spawners.size(); i++) { + Ref spawnMarkerRef = this.filterMarker(spawners.get(i), store); + if (spawnMarkerRef != null) { + SpawnMarkerEntity spawnMarkerEntityComponent = store.getComponent(spawnMarkerRef, SPAWN_MARKER_ENTITY_COMPONENT_TYPE); + + assert spawnMarkerEntityComponent != null; + + spawnMarkerEntityComponent.trigger(spawnMarkerRef, store); + } + } + + return true; + } else { + RandomExtra.reservoirSample(spawners, (reference, _this, _store) -> _this.filterMarker(reference, _store), this.count, this.triggerList, this, store); + + for (int ix = 0; ix < this.triggerList.size(); ix++) { + Ref spawnMarkerRef = this.triggerList.get(ix); + SpawnMarkerEntity spawnMarkerEntityComponent = store.getComponent(spawnMarkerRef, SPAWN_MARKER_ENTITY_COMPONENT_TYPE); + + assert spawnMarkerEntityComponent != null; + + spawnMarkerEntityComponent.trigger(spawnMarkerRef, store); + } + + this.triggerList.clear(); + return true; + } + } + + @Nullable + protected Ref filterMarker(@Nonnull Ref targetRef, @Nonnull Store store) { + if (!targetRef.isValid()) { + return null; + } else { + TransformComponent parentTransformComponent = store.getComponent(this.parentRef, TRANSFORM_COMPONENT_TYPE); + + assert parentTransformComponent != null; + + Vector3d parentPosition = parentTransformComponent.getPosition(); + TransformComponent targetTransformComponent = store.getComponent(targetRef, TRANSFORM_COMPONENT_TYPE); + + assert targetTransformComponent != null; + + Vector3d targetPosition = targetTransformComponent.getPosition(); + SpawnMarkerEntity targetMarkerEntityComponent = store.getComponent(targetRef, SPAWN_MARKER_ENTITY_COMPONENT_TYPE); + return targetMarkerEntityComponent == null + || !targetMarkerEntityComponent.isManualTrigger() + || !(parentPosition.distanceSquaredTo(targetPosition) <= this.rangeSquared) + || this.spawner != null && !this.spawner.equals(targetMarkerEntityComponent.getSpawnMarkerId()) + ? null + : targetRef; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/BodyMotionPath.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/BodyMotionPath.java new file mode 100644 index 0000000..57a6d44 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/BodyMotionPath.java @@ -0,0 +1,553 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.path.IPath; +import com.hypixel.hytale.server.core.universe.world.path.IPathWaypoint; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.BodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderBodyMotionPath; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.movement.steeringforces.SteeringForcePursue; +import com.hypixel.hytale.server.npc.movement.steeringforces.SteeringForceRotate; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.WorldSupport; +import com.hypixel.hytale.server.npc.sensorinfo.IPathProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.NPCPhysicsMath; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntLists; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BodyMotionPath extends BodyMotionBase { + public static final double MIN_GUARD_POINT_WAIT_TIME = 1.0; + public static final boolean TESTING = false; + protected final BodyMotionPath.Shape shape; + protected final double pathWidth; + protected final double nodeWidth; + protected final double minRelativeSpeed; + protected final double maxRelativeSpeed; + protected final double minWalkDistance; + protected final double maxWalkDistance; + protected final boolean startAtNearestNode; + protected final BodyMotionPath.Direction direction; + protected final double minNodeDelay; + protected final double maxNodeDelay; + protected final int viewSegments; + protected final boolean useNodeViewDirection; + protected final boolean pickRandomAngle; + protected final double minDelayScale; + protected final double maxDelayScale; + protected final double minPercentage; + protected final double maxPercentage; + protected int currentWaypointIndex = -1; + @Nullable + protected BodyMotionPath.Direction currentDirection; + protected final Vector3d currentWaypointPosition = new Vector3d(); + protected final Vector3d lastWaypointPosition = new Vector3d(); + protected final IntList visitOrder = new IntArrayList(); + protected int visitIndex; + protected final SteeringForceRotate steeringForceRotate = new SteeringForceRotate(); + protected final SteeringForcePursue steeringForcePursue = new SteeringForcePursue(); + protected double currentSpeed; + protected final Vector3d currentPosition = new Vector3d(); + protected final Vector3d nextPosition = new Vector3d(); + protected boolean nextPositionValid; + protected double currentNodeDelay; + protected boolean pendingNodeDelay; + protected boolean rotatingToView; + protected float nodeViewDirection; + protected double nodeWaitTime; + protected float observationSector; + protected double currentObservationDelay; + protected boolean rotating; + protected final Vector3d previousSteeringTranslation = new Vector3d(Vector3d.MIN); + protected int currentViewSegment; + + public BodyMotionPath(@Nonnull BuilderBodyMotionPath builder, @Nonnull BuilderSupport support) { + super(builder); + this.shape = builder.getShape(support); + this.pathWidth = builder.getPathWidth(); + this.nodeWidth = builder.getNodeWidth(); + this.minRelativeSpeed = builder.getMinRelativeSpeed(); + this.maxRelativeSpeed = builder.getMaxRelativeSpeed(); + this.minWalkDistance = builder.getMinWalkDistance(); + this.maxWalkDistance = builder.getMaxWalkDistance(); + this.startAtNearestNode = builder.isStartAtNearestNode(); + this.direction = builder.getDirection(); + this.minNodeDelay = builder.getMinNodeDelay(); + this.maxNodeDelay = builder.getMaxNodeDelay(); + this.useNodeViewDirection = builder.isUseNodeViewDirection(); + double[] delayScaleRange = builder.getDelayScaleRange(support); + this.minDelayScale = delayScaleRange[0]; + this.maxDelayScale = delayScaleRange[1]; + double[] delayPercentRange = builder.getPercentDelayRange(support); + this.minPercentage = delayPercentRange[0]; + this.maxPercentage = delayPercentRange[1]; + this.pickRandomAngle = builder.isPickRandomAngle(); + this.viewSegments = builder.getViewSegments(support); + } + + @Override + public void activate(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + this.reset(); + } + + @Override + public void loaded(Role role) { + this.invalidateWaypoint(); + this.reset(); + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role role, + @Nullable InfoProvider sensorInfo, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + desiredSteering.clear(); + if (!role.getActiveMotionController().canAct(ref, componentAccessor)) { + return true; + } else { + IPathProvider info = sensorInfo.getExtraInfo(IPathProvider.class); + if (info != null && info.hasPath()) { + IPath path = info.getPath(); + int numWaypoints = path.length(); + if (this.visitOrder.size() != numWaypoints) { + this.visitOrder.clear(); + + for (int i = 0; i < numWaypoints; i++) { + this.visitOrder.add(i); + } + + IntLists.shuffle(this.visitOrder, ThreadLocalRandom.current()); + this.visitIndex = 0; + this.invalidateWaypoint(); + } + + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + if (this.currentWaypointIndex == -1) { + if (!this.getFirstWaypoint(ref, role, path, position, componentAccessor)) { + return false; + } + + this.nextPositionValid = false; + this.currentNodeDelay = 0.0; + } + + float heading = transformComponent.getRotation().getYaw(); + if (this.currentNodeDelay > 0.0) { + this.currentNodeDelay -= dt; + if (this.observationSector != 0.0F || numWaypoints == 1) { + this.steeringForceRotate.setHeading(heading); + if (this.steeringForceRotate.compute(desiredSteering)) { + return true; + } + + desiredSteering.setYaw(heading); + if (this.tickObservationDelay(dt)) { + return true; + } + + this.pickNextObservationAngle(); + } + + return true; + } else { + MotionController activeMotionController = role.getActiveMotionController(); + Vector3d componentSelector = activeMotionController.getComponentSelector(); + WorldSupport worldSupport = role.getWorldSupport(); + this.currentPosition.assign(position.getX(), position.getY(), position.getZ()); + int lastIndex = this.currentWaypointIndex; + this.lastWaypointPosition.assign(this.currentWaypointPosition); + + while (this.closeToPosition(this.currentWaypointPosition, activeMotionController)) { + if (this.nextPositionValid || numWaypoints == 1) { + IPathWaypoint wayPoint = path.get(this.currentWaypointIndex); + if (wayPoint == null) { + return false; + } + + this.nodeViewDirection = wayPoint.getWaypointRotation(componentAccessor).getYaw(); + this.nodeWaitTime = wayPoint.getPauseTime(); + this.observationSector = wayPoint.getObservationAngle() / 2.0F; + this.currentViewSegment = 0; + if (numWaypoints == 1) { + this.currentNodeDelay = MathUtil.maxValue(this.nodeWaitTime, 1.0); + this.pickNextObservationAngle(); + desiredSteering.setYaw(heading); + return true; + } + } + + this.nextPositionValid = false; + if (!this.nextWayPoint(path, worldSupport, componentAccessor)) { + if (worldSupport.hasRequestedNewPath()) { + desiredSteering.setTranslation(this.previousSteeringTranslation); + } + + return false; + } + + if (this.currentWaypointIndex == lastIndex) { + return false; + } + } + + if (!this.nextPositionValid || this.closeToPosition(this.nextPosition, activeMotionController)) { + if (this.pathWidth == 0.0) { + this.nextPosition.assign(this.currentWaypointPosition); + } else { + double maxDistance = NPCPhysicsMath.dotProduct( + this.currentWaypointPosition, this.lastWaypointPosition, this.currentPosition, componentSelector + ); + double distance = Math.min(RandomExtra.randomRange(this.minWalkDistance, this.maxWalkDistance), maxDistance); + if (distance >= maxDistance - this.nodeWidth) { + this.nextPosition.assign(this.currentWaypointPosition); + } else { + NPCPhysicsMath.orthoComposition( + this.lastWaypointPosition, + this.currentWaypointPosition, + distance, + Vector3d.UP, + RandomExtra.randomRange(-this.pathWidth / 2.0, this.pathWidth / 2.0), + this.nextPosition + ); + } + } + + this.nextPositionValid = true; + this.currentSpeed = RandomExtra.randomRange(this.minRelativeSpeed, this.maxRelativeSpeed); + this.steeringForcePursue.setTargetPosition(this.nextPosition); + this.steeringForcePursue.setDistances(this.nodeWidth * 1.0, 0.1); + this.steeringForcePursue.setComponentSelector(componentSelector); + if ((!this.useNodeViewDirection || !(this.minNodeDelay > 0.0)) && !(this.nodeWaitTime > 0.0)) { + this.steeringForceRotate.setDesiredHeading(NPCPhysicsMath.lookatHeading(this.currentPosition, this.nextPosition, heading)); + this.pendingNodeDelay = this.minNodeDelay > 0.0; + } else { + this.pickNextObservationAngle(); + this.rotatingToView = true; + this.pendingNodeDelay = false; + } + + this.rotating = true; + } + + if (this.rotating) { + this.steeringForceRotate.setHeading(heading); + if (this.useNodeViewDirection && this.rotatingToView) { + if (this.steeringForceRotate.compute(desiredSteering)) { + return true; + } + + desiredSteering.setYaw(heading); + if (this.tickObservationDelay(dt)) { + return true; + } + + this.rotatingToView = false; + this.steeringForceRotate.setDesiredHeading(NPCPhysicsMath.lookatHeading(this.currentPosition, this.nextPosition, heading)); + if (this.minNodeDelay > 0.0) { + this.currentNodeDelay = RandomExtra.randomRange(this.minNodeDelay, this.maxNodeDelay); + } + + if (this.nodeWaitTime > this.currentNodeDelay) { + this.currentNodeDelay = this.nodeWaitTime; + } + + if (this.currentNodeDelay > 0.0) { + this.pickNextObservationAngle(); + } + + return true; + } + + if (this.steeringForceRotate.compute(desiredSteering)) { + return true; + } + + if (this.pendingNodeDelay) { + this.currentNodeDelay = RandomExtra.randomRange(this.minNodeDelay, this.maxNodeDelay); + this.pendingNodeDelay = false; + return true; + } + + this.rotating = false; + } + + this.steeringForcePursue.setSelfPosition(this.currentPosition); + this.steeringForcePursue.setComponentSelector(activeMotionController.getComponentSelector()); + this.nextPositionValid = this.steeringForcePursue.compute(desiredSteering); + if (desiredSteering.hasTranslation()) { + desiredSteering.scaleTranslation(this.currentSpeed); + } + + this.previousSteeringTranslation.assign(desiredSteering.getTranslation()); + return true; + } + } else { + return false; + } + } + } + + protected boolean tickObservationDelay(double dt) { + if (this.currentObservationDelay > 0.0) { + this.currentObservationDelay -= dt; + return true; + } else { + return false; + } + } + + protected void pickNextObservationAngle() { + if (this.pickRandomAngle) { + float angle = RandomExtra.randomRange(-this.observationSector, this.observationSector); + this.steeringForceRotate.setDesiredHeading(this.nodeViewDirection + angle); + } else if (this.viewSegments > 1) { + float fullSector = this.observationSector * 2.0F; + float start = this.nodeViewDirection - this.observationSector; + float segment = fullSector / (this.viewSegments - 1); + int thisSegment = this.currentViewSegment++; + this.currentViewSegment = this.currentViewSegment % this.viewSegments; + this.steeringForceRotate.setDesiredHeading(start + thisSegment * segment); + } else { + this.steeringForceRotate.setDesiredHeading(this.nodeViewDirection + this.observationSector); + this.observationSector *= -1.0F; + } + + this.currentObservationDelay = this.nodeWaitTime * RandomExtra.randomRange(this.minDelayScale, this.maxDelayScale); + this.currentObservationDelay = this.currentObservationDelay + + this.currentObservationDelay * RandomExtra.randomRange(this.minPercentage, this.maxPercentage); + } + + protected boolean closeToPosition(Vector3d position, @Nonnull MotionController motionController) { + return motionController.waypointDistanceSquared(this.currentPosition, position) <= this.nodeWidth * this.nodeWidth; + } + + protected void invalidateWaypoint() { + this.currentWaypointIndex = -1; + this.currentDirection = null; + } + + protected boolean nextWayPoint(@Nonnull IPath path, @Nonnull WorldSupport support, @Nonnull ComponentAccessor componentAccessor) { + if (this.currentWaypointIndex == -1) { + return false; + } else { + int numWaypoints = path.length(); + switch (this.shape) { + case LINE: + case LOOP: + case CHAIN: + if (this.direction == BodyMotionPath.Direction.RANDOM || this.currentDirection == null) { + this.currentDirection = RandomExtra.randomBoolean() ? BodyMotionPath.Direction.FORWARD : BodyMotionPath.Direction.BACKWARD; + } + + this.currentWaypointIndex = this.currentWaypointIndex + (this.currentDirection == BodyMotionPath.Direction.FORWARD ? 1 : -1); + if (this.currentWaypointIndex < 0 || this.currentWaypointIndex >= numWaypoints) { + if (this.shape == BodyMotionPath.Shape.LOOP) { + this.currentWaypointIndex = (this.currentWaypointIndex + numWaypoints) % numWaypoints; + } else if (this.currentWaypointIndex < 0) { + if (this.shape == BodyMotionPath.Shape.CHAIN) { + this.currentWaypointIndex = -1; + support.requestNewPath(); + return false; + } + + this.currentWaypointIndex = 1; + this.currentDirection = BodyMotionPath.Direction.FORWARD; + } else if (this.currentWaypointIndex >= numWaypoints) { + if (this.shape == BodyMotionPath.Shape.CHAIN) { + this.currentWaypointIndex = -1; + support.requestNewPath(); + return false; + } + + this.currentWaypointIndex = numWaypoints - 2; + this.currentDirection = BodyMotionPath.Direction.BACKWARD; + } + } + break; + case POINTS: + if (this.direction == BodyMotionPath.Direction.RANDOM) { + int index = RandomExtra.randomRange(numWaypoints); + if (index == this.currentWaypointIndex) { + index = (index + RandomExtra.randomRange(2) * 2 - 1 + numWaypoints) % numWaypoints; + } + + this.currentWaypointIndex = index; + } else { + this.visitIndex++; + if (this.visitIndex >= this.visitOrder.size()) { + this.visitIndex = 0; + IntLists.shuffle(this.visitOrder, ThreadLocalRandom.current()); + } + + if (this.visitOrder.getInt(this.visitIndex) == this.currentWaypointIndex) { + this.visitIndex++; + } + + this.currentWaypointIndex = this.visitOrder.getInt(this.visitIndex); + } + + this.currentDirection = BodyMotionPath.Direction.FORWARD; + } + + this.waypointIndexUpdated(path, componentAccessor); + return true; + } + } + + protected boolean getFirstWaypoint( + @Nonnull Ref ref, + @Nonnull Role role, + @Nullable IPath path, + @Nonnull Vector3d lastPos, + @Nonnull ComponentAccessor componentAccessor + ) { + this.invalidateWaypoint(); + if (path != null && path.length() != 0) { + this.initializeCurrentDirection(); + if (this.startAtNearestNode) { + double distanceSquared = Double.MAX_VALUE; + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d pos = transformComponent.getPosition(); + MotionController activeMotionController = role.getActiveMotionController(); + + for (int i = 0; i < path.length(); i++) { + IPathWaypoint pathWaypoint = path.get(i); + if (pathWaypoint == null) { + return false; + } + + double distance = activeMotionController.waypointDistanceSquared(pos, pathWaypoint.getWaypointPosition(componentAccessor)); + if (distance < distanceSquared) { + this.currentWaypointIndex = i; + distanceSquared = distance; + } + } + + switch (this.shape) { + case LINE: + case CHAIN: + if (this.currentWaypointIndex == 0) { + this.currentDirection = BodyMotionPath.Direction.FORWARD; + } else if (this.currentWaypointIndex == path.length() - 1) { + this.currentDirection = BodyMotionPath.Direction.BACKWARD; + } + case LOOP: + default: + break; + case POINTS: + if (this.direction != BodyMotionPath.Direction.RANDOM) { + IntLists.shuffle(this.visitOrder, ThreadLocalRandom.current()); + this.visitIndex = this.visitOrder.indexOf(this.currentWaypointIndex); + } + } + } else { + switch (this.shape) { + case LINE: + case CHAIN: + this.currentWaypointIndex = this.currentDirection == BodyMotionPath.Direction.FORWARD ? 0 : path.length() - 1; + break; + case LOOP: + this.currentWaypointIndex = 0; + break; + case POINTS: + if (this.direction != BodyMotionPath.Direction.RANDOM) { + IntLists.shuffle(this.visitOrder, ThreadLocalRandom.current()); + this.visitIndex = 0; + this.currentWaypointIndex = this.visitOrder.getInt(this.visitIndex); + } else { + this.currentWaypointIndex = RandomExtra.randomRange(path.length()); + } + } + } + + this.waypointIndexUpdated(path, componentAccessor); + this.lastWaypointPosition.assign(lastPos.getX(), lastPos.getY(), lastPos.getZ()); + return true; + } else { + return false; + } + } + + protected void waypointIndexUpdated(@Nonnull IPath path, @Nonnull ComponentAccessor componentAccessor) { + IPathWaypoint pathWaypoint = path.get(this.currentWaypointIndex); + if (pathWaypoint != null) { + Vector3d pathWaypointPosition = pathWaypoint.getWaypointPosition(componentAccessor); + this.currentWaypointPosition.assign(pathWaypointPosition); + } + } + + protected void initializeCurrentDirection() { + if (this.direction != BodyMotionPath.Direction.RANDOM && this.direction != BodyMotionPath.Direction.ANY) { + this.currentDirection = this.direction; + } else { + this.currentDirection = RandomExtra.randomBoolean() ? BodyMotionPath.Direction.FORWARD : BodyMotionPath.Direction.BACKWARD; + } + } + + protected void reset() { + this.pendingNodeDelay = false; + this.currentNodeDelay = 0.0; + this.nextPositionValid = false; + this.currentViewSegment = 0; + this.nodeWaitTime = 0.0; + this.rotating = false; + } + + public static enum Direction implements Supplier { + FORWARD("Start visiting nodes in order"), + BACKWARD("Start visiting nodes in reverse order "), + RANDOM("Can change direction between nodes and randomly pick target node in Points shape mode"), + ANY("Pick any start direction"); + + private final String description; + + private Direction(String description) { + this.description = description; + } + + public String get() { + return this.description; + } + } + + public static enum Shape implements Supplier { + LINE("Nodes form an open path of line segments"), + LOOP("Nodes form a closed loop of line segments (last node leads to first node)"), + POINTS("Any path between nodes is possible"), + CHAIN("Nodes form an open path of line segments and will chain together with the next nearest path upon reaching the final node"); + + private final String description; + + private Shape(String description) { + this.description = description; + } + + public String get() { + return this.description; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/HeadMotionObserve.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/HeadMotionObserve.java new file mode 100644 index 0000000..1050b72 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/HeadMotionObserve.java @@ -0,0 +1,146 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.math.random.RandomExtra; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.protocol.Rangef; +import com.hypixel.hytale.server.core.asset.type.model.config.camera.CameraSettings; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.HeadMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderHeadMotionObserve; +import com.hypixel.hytale.server.npc.movement.Steering; +import com.hypixel.hytale.server.npc.movement.steeringforces.SteeringForceRotate; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class HeadMotionObserve extends HeadMotionBase { + protected final float[] angleRange; + protected final double[] pauseTimeRange; + protected final boolean pickRandomAngle; + protected final int viewSegments; + protected final double relativeTurnSpeed; + protected double preDelay; + protected double delay; + protected int currentViewSegment; + protected boolean invertedDirection; + protected float targetBodyOffsetYaw; + protected final SteeringForceRotate steeringForceRotate = new SteeringForceRotate(); + + public HeadMotionObserve(@Nonnull BuilderHeadMotionObserve builder, @Nonnull BuilderSupport support) { + super(builder); + this.angleRange = builder.getAngleRange(support); + this.pauseTimeRange = builder.getPauseTimeRange(support); + this.pickRandomAngle = builder.isPickRandomAngle(support); + this.viewSegments = builder.getViewSegments(support); + this.relativeTurnSpeed = builder.getRelativeTurnSpeed(support); + } + + @Override + public void activate(@Nonnull Ref ref, @Nonnull Role role, @Nonnull ComponentAccessor componentAccessor) { + this.preDelay = RandomExtra.randomRange(this.pauseTimeRange); + this.currentViewSegment = 0; + this.pickNextAngle(ref, componentAccessor); + } + + @Override + public boolean computeSteering( + @Nonnull Ref ref, + @Nonnull Role role, + @Nullable InfoProvider sensorInfo, + double dt, + @Nonnull Steering desiredSteering, + @Nonnull ComponentAccessor componentAccessor + ) { + if (this.tickPreDelay(dt)) { + return true; + } else { + TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = componentAccessor.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3f headRotation = headRotationComponent.getRotation(); + this.steeringForceRotate.setHeading(headRotation.getYaw()); + this.steeringForceRotate.setDesiredHeading(transformComponent.getRotation().getYaw() + this.targetBodyOffsetYaw); + if (this.steeringForceRotate.compute(desiredSteering)) { + desiredSteering.setRelativeTurnSpeed(this.relativeTurnSpeed); + return true; + } else { + desiredSteering.setYaw(transformComponent.getRotation().getYaw() + this.targetBodyOffsetYaw); + if (this.tickDelay(dt)) { + return true; + } else { + this.pickNextAngle(ref, componentAccessor); + return true; + } + } + } + } + + protected boolean tickPreDelay(double dt) { + if (this.preDelay > 0.0) { + this.preDelay -= dt; + return true; + } else { + return false; + } + } + + protected boolean tickDelay(double dt) { + if (this.delay > 0.0) { + this.delay -= dt; + return true; + } else { + return false; + } + } + + protected void pickNextAngle(@Nonnull Ref ref, @Nonnull ComponentAccessor componentAccessor) { + ModelComponent modelComponent = componentAccessor.getComponent(ref, ModelComponent.getComponentType()); + + assert modelComponent != null; + + CameraSettings headRotationRestrictions = modelComponent.getModel().getCamera(); + float limitMin; + float limitMax; + if (headRotationRestrictions != null && headRotationRestrictions.getYaw() != null && headRotationRestrictions.getYaw().getAngleRange() != null) { + Rangef yawRange = headRotationRestrictions.getYaw().getAngleRange(); + limitMin = yawRange.min; + limitMax = yawRange.max; + } else { + limitMin = (float) (-Math.PI / 4); + limitMax = (float) (Math.PI / 4); + } + + float min = Math.max(this.angleRange[0], limitMin); + float max = Math.min(this.angleRange[1], limitMax); + if (this.pickRandomAngle) { + this.targetBodyOffsetYaw = RandomExtra.randomRange(min, max); + } else if (this.viewSegments > 1) { + float fullSector = MathUtil.wrapAngle(max - min); + float segment = fullSector / (this.viewSegments - 1); + int thisSegment = this.currentViewSegment++; + this.currentViewSegment = this.currentViewSegment % this.viewSegments; + this.targetBodyOffsetYaw = min + thisSegment * segment; + } else if (!this.invertedDirection) { + this.targetBodyOffsetYaw = min; + this.invertedDirection = true; + } else { + this.targetBodyOffsetYaw = max; + this.invertedDirection = false; + } + + this.delay = RandomExtra.randomRange(this.pauseTimeRange); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorBlock.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorBlock.java new file mode 100644 index 0000000..4a958e5 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorBlock.java @@ -0,0 +1,116 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.chunk.section.blockpositions.IBlockPositionData; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.blackboard.Blackboard; +import com.hypixel.hytale.server.npc.blackboard.view.blocktype.BlockTypeView; +import com.hypixel.hytale.server.npc.blackboard.view.resource.ResourceView; +import com.hypixel.hytale.server.npc.corecomponents.BlockTarget; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorBlock; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.sensorinfo.PositionProvider; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public class SensorBlock extends SensorBase { + protected final double range; + protected final double yRange; + protected final int blockSet; + protected final boolean pickRandom; + protected final boolean reserveBlock; + protected final PositionProvider positionProvider = new PositionProvider(); + + public SensorBlock(@Nonnull BuilderSensorBlock builder, @Nonnull BuilderSupport support) { + super(builder); + this.range = builder.getRange(support); + this.yRange = builder.getYRange(support); + this.blockSet = builder.getBlockSet(support); + this.pickRandom = builder.isPickRandom(support); + this.reserveBlock = builder.isReserveBlock(support); + support.requireBlockTypeBlackboard(this.blockSet); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + this.positionProvider.clear(); + return false; + } else { + World world = store.getExternalData().getWorld(); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + Vector3d entityPos = transformComponent.getPosition(); + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + BlockTarget target = role.getWorldSupport().getCachedBlockTarget(this.blockSet); + Vector3d position = target.getPosition(); + if (!position.equals(Vector3d.MIN)) { + WorldChunk targetChunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(position.x, position.z)); + if (targetChunk != null) { + BlockSection section = targetChunk.getBlockChunk().getSectionAtBlockY(MathUtil.floor(position.y)); + if (section.getLocalChangeCounter() == target.getChunkChangeRevision() + || section.get(MathUtil.floor(position.x), MathUtil.floor(position.y), MathUtil.floor(position.z)) == target.getFoundBlockType()) { + if (!(Math.abs(entityPos.y - position.y) > this.yRange) && !(entityPos.distanceSquaredTo(position) > this.range * this.range)) { + this.positionProvider.setTarget(position); + return true; + } else { + this.positionProvider.clear(); + return false; + } + } + } + } + + if (target.isActive()) { + target.reset(npcComponent); + } + + BlockTypeView blackboard = npcComponent.getBlockTypeBlackboardView(ref, store); + IBlockPositionData blockData = blackboard.findBlock(this.blockSet, this.range, this.yRange, this.pickRandom, ref, store); + if (blockData == null) { + this.positionProvider.clear(); + return false; + } else { + position.assign(blockData.getXCentre(), blockData.getYCentre(), blockData.getZCentre()); + int blockTypeId = blockData.getBlockType(); + target.setFoundBlockType(blockTypeId); + target.setChunkChangeRevision(blockData.getChunkSection().getLocalChangeCounter()); + BlockType blockType = BlockType.getAssetMap().getAsset(blockTypeId); + if (this.reserveBlock || !blockType.isAllowsMultipleUsers()) { + ResourceView resourceView = store.getResource(Blackboard.getResourceType()) + .getView(ResourceView.class, ResourceView.indexViewFromWorldPosition(position)); + resourceView.reserveBlock(npcComponent, blockData.getX(), blockData.getY(), blockData.getZ()); + target.setReservationHolder(resourceView); + Blackboard.LOGGER.at(Level.FINE).log("Entity %s reserved block from set %s at %s", npcComponent.getRoleName(), this.blockSet, position); + } + + Blackboard.LOGGER.at(Level.FINE).log("Entity %s found block from set %s at %s", npcComponent.getRoleName(), this.blockSet, position); + this.positionProvider.setTarget(position); + return true; + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorBlockChange.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorBlockChange.java new file mode 100644 index 0000000..a901706 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorBlockChange.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.blackboard.view.event.block.BlockEventType; +import com.hypixel.hytale.server.npc.components.messaging.NPCBlockEventSupport; +import com.hypixel.hytale.server.npc.components.messaging.PlayerBlockEventSupport; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorBlockChange; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SensorBlockChange extends SensorEvent { + public SensorBlockChange(@Nonnull BuilderSensorBlockChange builder, @Nonnull BuilderSupport support) { + super(builder, support); + BlockEventType type = builder.getEventType(support); + int blockSet = builder.getBlockSet(support); + switch (this.searchType) { + case PlayerFirst: + case NpcFirst: + this.playerEventMessageSlot = support.getBlockEventSlot(type, blockSet, this.range, true); + this.npcEventMessageSlot = support.getBlockEventSlot(type, blockSet, this.range, false); + break; + case PlayerOnly: + this.playerEventMessageSlot = support.getBlockEventSlot(type, blockSet, this.range, true); + this.npcEventMessageSlot = -1; + break; + case NpcOnly: + this.playerEventMessageSlot = -1; + this.npcEventMessageSlot = support.getBlockEventSlot(type, blockSet, this.range, false); + break; + default: + this.playerEventMessageSlot = -1; + this.npcEventMessageSlot = -1; + } + } + + @Nullable + @Override + protected Ref getPlayerTarget(@Nonnull Ref parent, @Nonnull Store store) { + PlayerBlockEventSupport blockEventSupportComponent = store.getComponent(parent, PlayerBlockEventSupport.getComponentType()); + + assert blockEventSupportComponent != null; + + TransformComponent transformComponent = store.getComponent(parent, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + return !blockEventSupportComponent.hasMatchingMessage(this.playerEventMessageSlot, position, this.range) + ? null + : blockEventSupportComponent.pollMessage(this.playerEventMessageSlot); + } + + @Nullable + @Override + protected Ref getNpcTarget(@Nonnull Ref parent, @Nonnull Store store) { + NPCBlockEventSupport blockEventSupportComponent = store.getComponent(parent, NPCBlockEventSupport.getComponentType()); + + assert blockEventSupportComponent != null; + + TransformComponent transformComponent = store.getComponent(parent, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + return !blockEventSupportComponent.hasMatchingMessage(this.npcEventMessageSlot, position, this.range) + ? null + : blockEventSupportComponent.pollMessage(this.npcEventMessageSlot); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorBlockType.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorBlockType.java new file mode 100644 index 0000000..63f61d3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorBlockType.java @@ -0,0 +1,136 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.server.core.modules.blockset.BlockSetModule; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorBlockType; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import com.hypixel.hytale.server.npc.movement.controllers.MotionController; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.IPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponent; +import com.hypixel.hytale.server.npc.util.IAnnotatedComponentCollection; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SensorBlockType extends SensorBase implements IAnnotatedComponentCollection { + protected final Sensor sensor; + protected final int blockSet; + + public SensorBlockType(@Nonnull BuilderSensorBlockType builder, @Nonnull BuilderSupport support, Sensor sensor) { + super(builder); + this.sensor = sensor; + this.blockSet = builder.getBlockSet(support); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (super.matches(ref, role, dt, store) && this.sensor.matches(ref, role, dt, store)) { + InfoProvider sensorInfo = this.sensor.getSensorInfo(); + if (sensorInfo == null) { + return false; + } else { + IPositionProvider positionProvider = sensorInfo.getPositionProvider(); + int x = MathUtil.floor(positionProvider.getX()); + int y = MathUtil.floor(positionProvider.getY()); + int z = MathUtil.floor(positionProvider.getZ()); + World world = store.getExternalData().getWorld(); + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(x, z)); + if (chunk == null) { + positionProvider.clear(); + return false; + } else { + int blockId = chunk.getBlock(x, y, z); + if (!BlockSetModule.getInstance().blockInSet(this.blockSet, blockId)) { + positionProvider.clear(); + return false; + } else { + return true; + } + } + } + } else { + return false; + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.sensor.getSensorInfo(); + } + + @Override + public void registerWithSupport(Role role) { + this.sensor.registerWithSupport(role); + } + + @Override + public void motionControllerChanged( + @Nullable Ref ref, + @Nonnull NPCEntity npcComponent, + MotionController motionController, + @Nullable ComponentAccessor componentAccessor + ) { + this.sensor.motionControllerChanged(ref, npcComponent, motionController, componentAccessor); + } + + @Override + public void loaded(Role role) { + this.sensor.loaded(role); + } + + @Override + public void spawned(Role role) { + this.sensor.spawned(role); + } + + @Override + public void unloaded(Role role) { + this.sensor.unloaded(role); + } + + @Override + public void removed(Role role) { + this.sensor.removed(role); + } + + @Override + public void teleported(Role role, World from, World to) { + this.sensor.teleported(role, from, to); + } + + @Override + public void done() { + this.sensor.done(); + } + + @Override + public int componentCount() { + return 1; + } + + @Override + public IAnnotatedComponent getComponent(int index) { + if (index >= this.componentCount()) { + throw new IndexOutOfBoundsException(); + } else { + return this.sensor; + } + } + + @Override + public void setContext(IAnnotatedComponent parent, int index) { + super.setContext(parent, index); + this.sensor.setContext(this, index); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorCanPlace.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorCanPlace.java new file mode 100644 index 0000000..de2bd53 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorCanPlace.java @@ -0,0 +1,178 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.shape.Box; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorCanPlace; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.CachedPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.util.BlockPlacementHelper; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +public class SensorCanPlace extends SensorBase { + protected static final ComponentType BOUNDING_BOX_COMPONENT_TYPE = BoundingBox.getComponentType(); + protected final SensorCanPlace.Direction direction; + protected final SensorCanPlace.Offset offset; + protected final double retryDelay; + protected final boolean allowEmptyMaterials; + protected final Vector3d transform = new Vector3d(); + protected final CachedPositionProvider positionProvider = new CachedPositionProvider(); + protected final Vector3d cachedPosition = new Vector3d(); + protected boolean cachedResult; + protected double delay; + + public SensorCanPlace(@Nonnull BuilderSensorCanPlace builder, @Nonnull BuilderSupport support) { + super(builder); + this.direction = builder.getDirection(support); + this.offset = builder.getOffset(support); + this.retryDelay = builder.getRetryDelay(support); + this.allowEmptyMaterials = builder.isAllowEmptyMaterials(support); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (super.matches(ref, role, dt, store) && role.getWorldSupport().getBlockToPlace() != null) { + BlockType placedBlockType = BlockType.getAssetMap().getAsset(role.getWorldSupport().getBlockToPlace()); + if (placedBlockType == null) { + this.positionProvider.clear(); + return false; + } else { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + World world = store.getExternalData().getWorld(); + float yaw = transformComponent.getRotation().getYaw(); + float piQuarter = (float) (Math.PI / 4); + yaw = Math.round(yaw / piQuarter) * piQuarter; + this.direction.apply(this.transform, yaw); + BoundingBox boundingBoxComponent = store.getComponent(ref, BOUNDING_BOX_COMPONENT_TYPE); + if (boundingBoxComponent != null) { + Box boundingBox = boundingBoxComponent.getBoundingBox(); + Box blockBox = BlockBoundingBoxes.getAssetMap().getAsset(placedBlockType.getHitboxTypeIndex()).get(0).getBoundingBox(); + boolean xNegative = this.transform.x < 0.0; + boolean zNegative = this.transform.z < 0.0; + boolean xPositive = this.transform.x - 1.0E-5 > 0.0; + boolean zPositive = this.transform.z - 1.0E-5 > 0.0; + double npcX = xNegative ? boundingBox.min.x : boundingBox.max.x; + double npcZ = zNegative ? boundingBox.min.z : boundingBox.max.z; + double blockX = xNegative ? blockBox.max.x : blockBox.min.x; + double blockZ = zNegative ? blockBox.max.z : blockBox.min.z; + double transformX = xNegative ? -this.transform.x : this.transform.x; + double transformZ = zNegative ? -this.transform.z : this.transform.z; + double magnitude = Math.sqrt(npcX * npcX * transformX + npcZ * npcZ * transformZ) + + Math.sqrt(blockX * blockX * transformX + blockZ * blockZ * transformZ); + this.transform.setLength(magnitude); + this.transform.add(xPositive ? 1.0 : 0.0, 0.0, zPositive ? 1.0 : 0.0); + } + + this.offset.apply(this.transform); + Vector3d position = transformComponent.getPosition(); + this.transform.add(position.getX(), position.getY(), position.getZ()).floor(); + int x = (int)this.transform.getX(); + int y = (int)this.transform.getY(); + int z = (int)this.transform.getZ(); + if (!this.cachedPosition.equals(this.transform)) { + this.delay = 0.0; + } + + boolean canPlaceUnitBlock = BlockPlacementHelper.canPlaceUnitBlock(world, placedBlockType, this.allowEmptyMaterials, x, y, z); + if (canPlaceUnitBlock != this.cachedResult) { + this.delay = 0.0; + } + + if (!((this.delay -= dt) > 0.0)) { + this.cachedResult = canPlaceUnitBlock && BlockPlacementHelper.canPlaceBlock(world, placedBlockType, 0, this.allowEmptyMaterials, x, y, z); + this.cachedPosition.assign(this.transform); + this.delay = this.retryDelay; + if (!this.cachedResult) { + this.positionProvider.clear(); + return false; + } else { + this.positionProvider.setIsFromCache(false); + this.positionProvider.setTarget(this.transform); + return true; + } + } else if (this.cachedResult) { + this.positionProvider.setIsFromCache(true); + this.positionProvider.setTarget(this.transform); + return true; + } else { + this.positionProvider.clear(); + return false; + } + } + } else { + this.positionProvider.clear(); + return false; + } + } + + @Override + public void clearOnce() { + super.clearOnce(); + this.delay = 0.0; + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } + + public static enum Direction implements Supplier { + Forward(Vector3d.FORWARD), + Backward(Vector3d.BACKWARD), + Left(Vector3d.LEFT), + Right(Vector3d.RIGHT); + + private final Vector3d direction; + + private Direction(Vector3d direction) { + this.direction = direction; + } + + @Nonnull + public Vector3d apply(@Nonnull Vector3d target, float rotation) { + return target.assign(this.direction).rotateY(rotation); + } + + @Nonnull + public String get() { + return this.name(); + } + } + + public static enum Offset implements Supplier { + HeadPosition(Vector3d.UP), + BodyPosition(Vector3d.ZERO), + FootPosition(Vector3d.DOWN); + + private final Vector3d offset; + + private Offset(Vector3d offset) { + this.offset = offset; + } + + @Nonnull + public Vector3d apply(@Nonnull Vector3d target) { + return target.add(this.offset); + } + + @Nonnull + public String get() { + return this.name(); + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorEntityEvent.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorEntityEvent.java new file mode 100644 index 0000000..8668c53 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorEntityEvent.java @@ -0,0 +1,76 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.blackboard.view.event.entity.EntityEventType; +import com.hypixel.hytale.server.npc.components.messaging.PlayerEntityEventSupport; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorEntityEvent; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SensorEntityEvent extends SensorEvent { + private final boolean flockOnly; + + public SensorEntityEvent(@Nonnull BuilderSensorEntityEvent builder, @Nonnull BuilderSupport support) { + super(builder, support); + this.flockOnly = builder.isFlockOnly(support); + EntityEventType type = builder.getEventType(support); + int npcGroup = builder.getNPCGroup(support); + switch (this.searchType) { + case PlayerFirst: + case NpcFirst: + this.playerEventMessageSlot = support.getEntityEventSlot(type, npcGroup, this.range, true); + this.npcEventMessageSlot = support.getEntityEventSlot(type, npcGroup, this.range, false); + break; + case PlayerOnly: + this.playerEventMessageSlot = support.getEntityEventSlot(type, npcGroup, this.range, true); + this.npcEventMessageSlot = -1; + break; + case NpcOnly: + this.playerEventMessageSlot = -1; + this.npcEventMessageSlot = support.getEntityEventSlot(type, npcGroup, this.range, false); + break; + default: + this.playerEventMessageSlot = -1; + this.npcEventMessageSlot = -1; + } + } + + @Nullable + @Override + protected Ref getPlayerTarget(@Nonnull Ref parent, @Nonnull Store store) { + PlayerEntityEventSupport entityEventSupportComponent = store.getComponent(parent, PlayerEntityEventSupport.getComponentType()); + + assert entityEventSupportComponent != null; + + TransformComponent transformComponent = store.getComponent(parent, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + return !entityEventSupportComponent.hasFlockMatchingMessage(this.playerEventMessageSlot, position, this.range, this.flockOnly) + ? null + : entityEventSupportComponent.pollMessage(this.playerEventMessageSlot); + } + + @Nullable + @Override + protected Ref getNpcTarget(@Nonnull Ref parent, @Nonnull Store store) { + PlayerEntityEventSupport entityEventSupportComponent = store.getComponent(parent, PlayerEntityEventSupport.getComponentType()); + + assert entityEventSupportComponent != null; + + TransformComponent transformComponent = store.getComponent(parent, TRANSFORM_COMPONENT_TYPE); + + assert transformComponent != null; + + Vector3d position = transformComponent.getPosition(); + return !entityEventSupportComponent.hasFlockMatchingMessage(this.npcEventMessageSlot, position, this.range, this.flockOnly) + ? null + : entityEventSupportComponent.pollMessage(this.npcEventMessageSlot); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorEvent.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorEvent.java new file mode 100644 index 0000000..0a96a9a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorEvent.java @@ -0,0 +1,114 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorEvent; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.MarkedEntitySupport; +import com.hypixel.hytale.server.npc.sensorinfo.EntityPositionProvider; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class SensorEvent extends SensorBase { + protected static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); + protected final double range; + protected final SensorEvent.EventSearchType searchType; + protected final int lockOnTargetSlot; + protected int playerEventMessageSlot; + protected int npcEventMessageSlot; + protected final EntityPositionProvider positionProvider = new EntityPositionProvider(); + + public SensorEvent(@Nonnull BuilderSensorEvent builder, @Nonnull BuilderSupport support) { + super(builder); + this.range = builder.getRange(support); + this.searchType = builder.getEventSearchType(support); + this.lockOnTargetSlot = builder.getLockOnTargetSlot(support); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + this.positionProvider.clear(); + return false; + } else { + switch (this.searchType) { + case PlayerFirst: + Ref playerFirstTarget = this.getPlayerTarget(ref, store); + if (playerFirstTarget == null) { + playerFirstTarget = this.getNpcTarget(ref, store); + } + + return this.setTarget(role.getMarkedEntitySupport(), playerFirstTarget, store); + case PlayerOnly: + return this.setTarget(role.getMarkedEntitySupport(), this.getPlayerTarget(ref, store), store); + case NpcFirst: + Ref npcFirstTarget = this.getNpcTarget(ref, store); + if (npcFirstTarget == null) { + npcFirstTarget = this.getPlayerTarget(ref, store); + } + + return this.setTarget(role.getMarkedEntitySupport(), npcFirstTarget, store); + case NpcOnly: + return this.setTarget(role.getMarkedEntitySupport(), this.getNpcTarget(ref, store), store); + default: + return false; + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } + + protected boolean setTarget( + @Nonnull MarkedEntitySupport support, @Nullable Ref target, @Nonnull ComponentAccessor componentAccessor + ) { + if (target == null) { + this.positionProvider.clear(); + return false; + } else { + Ref set = this.positionProvider.setTarget(target, componentAccessor); + if (set == null) { + return false; + } else { + if (this.lockOnTargetSlot >= 0) { + support.setMarkedEntity(this.lockOnTargetSlot, set); + } + + return true; + } + } + } + + @Nullable + protected abstract Ref getPlayerTarget(@Nonnull Ref var1, @Nonnull Store var2); + + @Nullable + protected abstract Ref getNpcTarget(@Nonnull Ref var1, @Nonnull Store var2); + + public static enum EventSearchType implements Supplier { + PlayerFirst("search for events triggered by players first"), + PlayerOnly("search only for events triggered by players"), + NpcFirst("search for events triggered by npcs first"), + NpcOnly("search only for events triggered by npcs"); + + private final String description; + + private EventSearchType(String description) { + this.description = description; + } + + public String get() { + return this.description; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorInWater.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorInWater.java new file mode 100644 index 0000000..91229a3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorInWater.java @@ -0,0 +1,26 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorInWater; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorInWater extends SensorBase { + public SensorInWater(@Nonnull BuilderSensorInWater builderSensorBase) { + super(builderSensorBase); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + return super.matches(ref, role, dt, store) && role.getActiveMotionController().inWater(); + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorLeash.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorLeash.java new file mode 100644 index 0000000..aa3a9d9 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorLeash.java @@ -0,0 +1,53 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorLeash; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.sensorinfo.PositionProvider; +import javax.annotation.Nonnull; + +public class SensorLeash extends SensorBase { + protected final double range; + protected final double rangeSq; + protected final PositionProvider positionProvider = new PositionProvider(); + + public SensorLeash(@Nonnull BuilderSensorLeash builderSensorLeash, @Nonnull BuilderSupport builderSupport) { + super(builderSensorLeash); + this.range = builderSensorLeash.getRange(builderSupport); + this.rangeSq = this.range * this.range; + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + return false; + } else { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + Vector3d leashPoint = npcComponent.getLeashPoint(); + if (transformComponent.getPosition().distanceSquaredTo(leashPoint) > this.rangeSq) { + this.positionProvider.setTarget(leashPoint); + return true; + } else { + this.positionProvider.clear(); + return false; + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorLight.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorLight.java new file mode 100644 index 0000000..7c3e968 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorLight.java @@ -0,0 +1,61 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorLight; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.spawning.util.LightRangePredicate; +import javax.annotation.Nonnull; + +public class SensorLight extends SensorBase { + protected final int useTargetSlot; + @Nonnull + protected final LightRangePredicate lightRangePredicate; + + public SensorLight(@Nonnull BuilderSensorLight builderSensorLight, @Nonnull BuilderSupport builderSupport) { + super(builderSensorLight); + this.useTargetSlot = builderSensorLight.getUsedTargetSlot(builderSupport); + this.lightRangePredicate = new LightRangePredicate(); + this.lightRangePredicate.setLightRange(builderSensorLight.getLightRange(builderSupport)); + this.lightRangePredicate.setSkyLightRange(builderSensorLight.getSkyLightRange(builderSupport)); + this.lightRangePredicate.setSunlightRange(builderSensorLight.getSunlightRange(builderSupport)); + this.lightRangePredicate.setRedLightRange(builderSensorLight.getRedLightRange(builderSupport)); + this.lightRangePredicate.setGreenLightRange(builderSensorLight.getGreenLightRange(builderSupport)); + this.lightRangePredicate.setBlueLightRange(builderSensorLight.getBlueLightRange(builderSupport)); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + return false; + } else { + Ref entityReference; + if (this.useTargetSlot >= 0) { + entityReference = role.getMarkedEntitySupport().getMarkedEntityRef(this.useTargetSlot); + if (entityReference == null) { + return false; + } + } else { + entityReference = ref; + } + + TransformComponent transformComponent = store.getComponent(entityReference, TransformComponent.getComponentType()); + + assert transformComponent != null; + + World world = store.getExternalData().getWorld(); + return this.lightRangePredicate.test(world, transformComponent.getPosition(), store); + } + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorPath.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorPath.java new file mode 100644 index 0000000..8eeccfb --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorPath.java @@ -0,0 +1,319 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.assetstore.AssetRegistry; +import com.hypixel.hytale.builtin.path.PathPlugin; +import com.hypixel.hytale.builtin.path.WorldPathData; +import com.hypixel.hytale.builtin.path.entities.PatrolPathMarkerEntity; +import com.hypixel.hytale.builtin.path.path.IPrefabPath; +import com.hypixel.hytale.component.ComponentAccessor; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.ResourceType; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.component.WorldGenId; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.path.IPath; +import com.hypixel.hytale.server.core.universe.world.path.IPathWaypoint; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.NPCPlugin; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorPath; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.entities.PathManager; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.sensorinfo.PathProvider; +import com.hypixel.hytale.server.npc.sensorinfo.PositionProvider; +import it.unimi.dsi.fastutil.objects.ObjectList; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.function.Supplier; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SensorPath extends SensorBase { + protected final double range; + protected final SensorPath.PathType pathType; + protected final Vector3d closestWaypoint = new Vector3d(Vector3d.MIN); + protected final HashSet disallowedPaths = new HashSet<>(); + protected final PathProvider pathProvider = new PathProvider(); + protected final PositionProvider positionProvider = new PositionProvider(null, this.pathProvider); + protected final ResourceType, EntityStore>> prefabPathSpatialResource; + @Nullable + protected final ComponentType patrolPathMarkerEntityComponentType; + protected final ComponentType worldGenIdComponentType; + @Nullable + protected String path; + protected int pathIndex; + protected int pathChangeRevision; + protected double distanceSquared = Double.MAX_VALUE; + @Nonnull + protected SensorPath.LoadStatus loadStatus = SensorPath.LoadStatus.WAITING; + + public SensorPath(@Nonnull BuilderSensorPath builder, @Nonnull BuilderSupport support) { + super(builder); + this.path = builder.getPath(support); + if (this.path != null && !this.path.isEmpty()) { + this.pathIndex = AssetRegistry.getOrCreateTagIndex(this.path); + } + + this.range = builder.getRange(support); + this.pathType = builder.getPathType(support); + this.prefabPathSpatialResource = PathPlugin.get().getPrefabPathSpatialResource(); + this.patrolPathMarkerEntityComponentType = PatrolPathMarkerEntity.getComponentType(); + this.worldGenIdComponentType = WorldGenId.getComponentType(); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (super.matches(ref, role, dt, store) && this.loadStatus != SensorPath.LoadStatus.FAILED) { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + Vector3d position = transformComponent.getPosition(); + PathManager pathManager = npcComponent.getPathManager(); + int newRevision = NPCPlugin.get().getPathChangeRevision(); + boolean newPathRequested = role.getWorldSupport().consumeNewPathRequested(); + if (pathManager.isFollowingPath() && newRevision == this.pathChangeRevision && !newPathRequested) { + IPath path = pathManager.getPath(ref, store); + if (path == null) { + this.pathProvider.clear(); + this.positionProvider.clear(); + return false; + } else { + this.findClosestWaypoint(path, position, this.closestWaypoint, store); + if (this.pathMatches(path) && this.isInRange(this.distanceSquared)) { + this.pathProvider.setPath((IPath)path); + this.positionProvider.setTarget(this.closestWaypoint); + return true; + } else { + this.pathProvider.clear(); + this.positionProvider.clear(); + return false; + } + } + } else { + this.pathChangeRevision = newRevision; + if (newPathRequested && pathManager.isFollowingPath()) { + UUID pathId = pathManager.getCurrentPathHint(); + if (pathId != null) { + this.disallowedPaths.add(pathId); + } + } + + IPath path = this.findPath(ref, position, store, this.disallowedPaths, newPathRequested); + if (path == null) { + this.pathProvider.clear(); + this.positionProvider.clear(); + return false; + } else { + this.closestWaypoint.assign(Vector3d.MIN); + this.findClosestWaypoint(path, position, this.closestWaypoint, store); + if (!this.isInRange(this.distanceSquared)) { + this.pathProvider.clear(); + this.positionProvider.clear(); + return false; + } else { + if (this.pathType == SensorPath.PathType.WorldPath) { + pathManager.setTransientPath(path); + } else { + pathManager.setPrefabPath(path.getId(), (IPrefabPath)path); + store.putComponent(ref, this.worldGenIdComponentType, new WorldGenId(((IPrefabPath)path).getWorldGenId())); + } + + this.disallowedPaths.clear(); + this.pathProvider.setPath((IPath)path); + this.positionProvider.setTarget(this.closestWaypoint); + return true; + } + } + } + } else { + this.pathProvider.clear(); + this.positionProvider.clear(); + return false; + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } + + protected boolean pathMatches(@Nonnull IPath path) { + return this.path == null || this.path.isEmpty() || this.path.equals(path.getName()); + } + + protected boolean isInRange(double squaredDistance) { + return this.range <= 0.0 || this.range * this.range > squaredDistance; + } + + @Nullable + protected IPath findPath( + @Nonnull Ref ref, + @Nonnull Vector3d position, + @Nonnull Store store, + @Nonnull Set disallowedPaths, + boolean newPathRequested + ) { + World world = store.getExternalData().getWorld(); + NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType()); + + assert npcComponent != null; + + PathManager pathManager = npcComponent.getPathManager(); + WorldGenId worldGenIdComponent = store.getComponent(ref, this.worldGenIdComponentType); + int worldGenId = worldGenIdComponent != null ? worldGenIdComponent.getWorldGenId() : 0; + IPath path; + switch (this.pathType) { + case WorldPath: + path = world.getWorldPathConfig().getPath(this.path); + if (path == null) { + NPCPlugin.get().getLogger().at(Level.WARNING).log("Path sensor: Path %s does not exist", this.path); + this.loadStatus = SensorPath.LoadStatus.FAILED; + return null; + } + break; + case CurrentPrefabPath: + WorldPathData worldPathData = store.getResource(WorldPathData.getResourceType()); + if (this.path == null) { + path = worldPathData.getNearestPrefabPath(worldGenId, position, disallowedPaths, store); + } else { + UUID entityPath = pathManager.getCurrentPathHint(); + if (entityPath != null && !newPathRequested) { + path = worldPathData.getPrefabPath(worldGenId, entityPath, false); + } else { + path = worldPathData.getNearestPrefabPath(worldGenId, this.pathIndex, position, disallowedPaths, store); + } + } + + if (path == null || !((IPrefabPath)path).isFullyLoaded()) { + this.loadStatus = SensorPath.LoadStatus.WAITING; + return null; + } + + this.path = path.getName(); + break; + case AnyPrefabPath: + SpatialResource, EntityStore> spatialResource = store.getResource(this.prefabPathSpatialResource); + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + spatialResource.getSpatialStructure().ordered(position, this.range, results); + if (results.isEmpty()) { + this.loadStatus = SensorPath.LoadStatus.WAITING; + return null; + } + + double nearest2 = Double.MAX_VALUE; + PatrolPathMarkerEntity nearestWaypoint = null; + int i = 0; + + for (; i < results.size(); i++) { + Ref eRef = results.get(i); + PatrolPathMarkerEntity ePatrolPathMarkerEntityComponent = store.getComponent(eRef, this.patrolPathMarkerEntityComponentType); + + assert ePatrolPathMarkerEntityComponent != null; + + if (!disallowedPaths.contains(ePatrolPathMarkerEntityComponent.getParentPath().getId())) { + TransformComponent eTransformComponent = store.getComponent(eRef, TransformComponent.getComponentType()); + + assert eTransformComponent != null; + + double dist2 = position.distanceSquaredTo(eTransformComponent.getPosition()); + if (dist2 < nearest2) { + nearest2 = dist2; + nearestWaypoint = ePatrolPathMarkerEntityComponent; + } + } + } + + if (nearestWaypoint == null) { + this.loadStatus = SensorPath.LoadStatus.WAITING; + return null; + } + + path = nearestWaypoint.getParentPath(); + if (path == null || !((IPrefabPath)path).isFullyLoaded()) { + this.loadStatus = SensorPath.LoadStatus.WAITING; + return null; + } + break; + case TransientPath: + path = (IPath)pathManager.getPath(ref, store); + this.path = null; + break; + default: + throw new IllegalStateException(); + } + + this.loadStatus = SensorPath.LoadStatus.SUCCESS; + return path; + } + + protected void findClosestWaypoint( + @Nonnull IPath path, @Nonnull Vector3d position, @Nonnull Vector3d cachedTarget, @Nonnull ComponentAccessor componentAccessor + ) { + double prevDistanceSquared = this.distanceSquared; + if (!cachedTarget.equals(Vector3d.MIN)) { + double newDistance = position.distanceSquaredTo(cachedTarget); + if (newDistance <= this.distanceSquared) { + this.distanceSquared = newDistance; + return; + } + } + + this.distanceSquared = Double.MAX_VALUE; + + for (int i = 0; i < path.length(); i++) { + IPathWaypoint pathWaypoint = path.get(i); + if (pathWaypoint != null) { + Vector3d waypoint = pathWaypoint.getWaypointPosition(componentAccessor); + double distance = position.distanceSquaredTo(waypoint); + if (distance < this.distanceSquared) { + this.distanceSquared = distance; + cachedTarget.assign(waypoint); + } + } + } + + if (this.distanceSquared == Double.MAX_VALUE) { + this.distanceSquared = prevDistanceSquared; + } + } + + protected static enum LoadStatus { + WAITING, + FAILED, + SUCCESS; + + private LoadStatus() { + } + } + + public static enum PathType implements Supplier { + WorldPath("named world path"), + CurrentPrefabPath("a path from the prefab the NPC spawned in"), + AnyPrefabPath("a path from any prefab"), + TransientPath("a transient path (testing purposes only)"); + + private final String description; + + private PathType(String description) { + this.description = description; + } + + public String get() { + return this.description; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorReadPosition.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorReadPosition.java new file mode 100644 index 0000000..081dad5 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorReadPosition.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorReadPosition; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.sensorinfo.PositionProvider; +import javax.annotation.Nonnull; + +public class SensorReadPosition extends SensorBase { + protected final int slot; + protected final boolean useMarkedTarget; + protected final double minRange; + protected final double range; + protected final PositionProvider positionProvider = new PositionProvider(); + + public SensorReadPosition(@Nonnull BuilderSensorReadPosition builder, @Nonnull BuilderSupport support) { + super(builder); + this.slot = builder.getSlot(support); + this.useMarkedTarget = builder.isUseMarkedTarget(support); + this.minRange = builder.getMinRange(support); + this.range = builder.getRange(support); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + this.positionProvider.clear(); + return false; + } else { + Vector3d position; + if (this.useMarkedTarget) { + Ref entityRef = role.getMarkedEntitySupport().getMarkedEntityRef(this.slot); + if (entityRef == null) { + this.positionProvider.clear(); + return false; + } + + TransformComponent entityTransformComponent = store.getComponent(entityRef, TransformComponent.getComponentType()); + + assert entityTransformComponent != null; + + position = entityTransformComponent.getPosition(); + } else { + position = role.getMarkedEntitySupport().getStoredPosition(this.slot); + } + + if (position.equals(Vector3d.MIN)) { + this.positionProvider.clear(); + return false; + } else { + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + double dist2 = transformComponent.getPosition().distanceSquaredTo(position); + if (!(dist2 > this.range * this.range) && !(dist2 < this.minRange * this.minRange)) { + this.positionProvider.setTarget(position); + return true; + } else { + this.positionProvider.clear(); + return false; + } + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorSearchRay.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorSearchRay.java new file mode 100644 index 0000000..de922b3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorSearchRay.java @@ -0,0 +1,118 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.util.ChunkUtil; +import com.hypixel.hytale.math.util.MathUtil; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; +import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorSearchRay; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import com.hypixel.hytale.server.npc.sensorinfo.PositionProvider; +import com.hypixel.hytale.server.npc.util.RayBlockHitTest; +import javax.annotation.Nonnull; + +public class SensorSearchRay extends SensorBase { + protected final int id; + protected final float angle; + protected final double range; + protected final int blockSet; + protected final float minRetestAngle; + protected final double minRetestMoveSquared; + protected final double throttleTime; + protected final PositionProvider positionProvider = new PositionProvider(); + protected final Vector3d lastCheckedPosition = new Vector3d(); + protected float lastCheckedYaw = Float.MAX_VALUE; + protected short lastBlockRevision; + protected double throttleTimeRemaining; + + public SensorSearchRay(@Nonnull BuilderSensorSearchRay builder, @Nonnull BuilderSupport support) { + super(builder); + this.id = builder.getId(support); + this.angle = -builder.getAngle(support); + this.range = builder.getRange(support); + this.blockSet = builder.getBlockSet(support); + this.minRetestAngle = builder.getMinRetestAngle(support); + double minRetestMove = builder.getMinRetestMove(support); + this.minRetestMoveSquared = minRetestMove * minRetestMove; + this.throttleTime = builder.getThrottleTime(support); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + this.positionProvider.clear(); + return false; + } else { + World world = store.getExternalData().getWorld(); + TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); + + assert transformComponent != null; + + HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); + + assert headRotationComponent != null; + + Vector3d position = transformComponent.getPosition(); + Vector3f headRotation = headRotationComponent.getRotation(); + Vector3d cachedPosition = role.getWorldSupport().getCachedSearchRayPosition(this.id); + if (!cachedPosition.equals(Vector3d.MIN)) { + WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(cachedPosition.x, cachedPosition.z)); + if (chunk != null) { + BlockSection section = chunk.getBlockChunk().getSectionAtBlockY(MathUtil.floor(cachedPosition.y)); + if (section.getLocalChangeCounter() == this.lastBlockRevision) { + this.positionProvider.setTarget(cachedPosition); + return true; + } + + cachedPosition.assign(Vector3d.MIN); + this.positionProvider.clear(); + } + } else if ((this.throttleTimeRemaining -= dt) > 0.0 + && Math.abs(headRotation.getYaw() - this.lastCheckedYaw) <= this.minRetestAngle + && position.distanceSquaredTo(this.lastCheckedPosition) <= this.minRetestMoveSquared) { + this.positionProvider.clear(); + return false; + } + + RayBlockHitTest blockRaySearch = RayBlockHitTest.THREAD_LOCAL.get(); + if (!blockRaySearch.init(ref, this.blockSet, this.angle, store)) { + cachedPosition.assign(Vector3d.MIN); + this.positionProvider.clear(); + blockRaySearch.clear(); + return false; + } else { + this.lastCheckedPosition.assign(position); + this.lastCheckedYaw = headRotation.getYaw(); + this.throttleTimeRemaining = this.throttleTime; + boolean result = blockRaySearch.run(this.range); + if (result) { + this.lastBlockRevision = blockRaySearch.getLastBlockRevision(); + Vector3d targetPosition = blockRaySearch.getHitPosition(); + cachedPosition.assign(targetPosition.x + 0.5, targetPosition.y + 0.5, targetPosition.z + 0.5); + this.positionProvider.setTarget(cachedPosition); + } else { + cachedPosition.assign(Vector3d.MIN); + this.positionProvider.clear(); + } + + blockRaySearch.clear(); + return result; + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return this.positionProvider; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorTime.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorTime.java new file mode 100644 index 0000000..bfe9f90 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorTime.java @@ -0,0 +1,52 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorTime; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; + +public class SensorTime extends SensorBase { + protected final double minTime; + protected final double maxTime; + protected final boolean checkDay; + protected final boolean checkYear; + protected final boolean scaleDayTimeRange; + + public SensorTime(@Nonnull BuilderSensorTime builderSensorTime, @Nonnull BuilderSupport support) { + super(builderSensorTime); + double[] timePeriod = builderSensorTime.getPeriod(support); + this.minTime = timePeriod[0] / WorldTimeResource.HOURS_PER_DAY; + this.maxTime = timePeriod[1] / WorldTimeResource.HOURS_PER_DAY; + this.checkDay = builderSensorTime.isCheckDay(); + this.checkYear = builderSensorTime.isCheckYear(); + this.scaleDayTimeRange = builderSensorTime.isScaleDayTimeRange(); + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + return false; + } else { + WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); + if (this.checkDay) { + boolean withinTimeRange = this.scaleDayTimeRange + ? worldTimeResource.isScaledDayTimeWithinRange(this.minTime, this.maxTime) + : worldTimeResource.isDayTimeWithinRange(this.minTime, this.maxTime); + return withinTimeRange && (!this.checkYear || worldTimeResource.isYearWithinRange(this.minTime, this.maxTime)); + } else { + return this.checkYear ? worldTimeResource.isYearWithinRange(this.minTime, this.maxTime) : false; + } + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorWeather.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorWeather.java new file mode 100644 index 0000000..df6ceaf --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/SensorWeather.java @@ -0,0 +1,64 @@ +package com.hypixel.hytale.server.npc.corecomponents.world; + +import com.hypixel.hytale.common.util.StringUtil; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.SensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorWeather; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SensorWeather extends SensorBase { + @Nullable + protected final String[] weathers; + protected int prevWeatherIndex; + protected boolean cachedResult; + + public SensorWeather(@Nonnull BuilderSensorWeather builder, @Nonnull BuilderSupport support) { + super(builder); + this.weathers = builder.getWeathers(support); + } + + @Override + public boolean matches(@Nonnull Ref ref, @Nonnull Role role, double dt, @Nonnull Store store) { + if (!super.matches(ref, role, dt, store)) { + return false; + } else { + int weatherIndex = role.getWorldSupport().getCurrentWeatherIndex(store); + if (weatherIndex == 0) { + return false; + } else if (weatherIndex == this.prevWeatherIndex) { + return this.cachedResult; + } else { + String weatherAssetId = Weather.getAssetMap().getAsset(weatherIndex).getId(); + this.prevWeatherIndex = weatherIndex; + this.cachedResult = this.matchesWeather(weatherAssetId); + return this.cachedResult; + } + } + } + + @Override + public InfoProvider getSensorInfo() { + return null; + } + + protected boolean matchesWeather(@Nullable String weather) { + if (weather == null) { + return false; + } else { + for (String weatherMatcher : this.weathers) { + if (StringUtil.isGlobMatching(weatherMatcher, weather)) { + return true; + } + } + + return false; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionMakePath.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionMakePath.java new file mode 100644 index 0000000..ab878d2 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionMakePath.java @@ -0,0 +1,85 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.builtin.path.path.TransientPathDefinition; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectReferenceHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.BuilderValidationHelper; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.ActionMakePath; +import com.hypixel.hytale.server.npc.instructions.Action; +import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; +import com.hypixel.hytale.server.npc.util.expression.Scope; +import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderActionMakePath extends BuilderActionBase { + protected final BuilderObjectReferenceHelper transientPath = new BuilderObjectReferenceHelper<>(TransientPathDefinition.class, this); + + public BuilderActionMakePath() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Constructs a transient path for the NPC based on a series of rotations and distances"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionMakePath(this, builderSupport); + } + + @Nonnull + public BuilderActionMakePath readConfig(@Nonnull JsonElement data) { + this.requireObject( + data, + "Path", + this.transientPath, + BuilderDescriptorState.WorkInProgress, + "A transient path definition", + null, + new BuilderValidationHelper(this.fileName, null, this.internalReferenceResolver, null, null, this.extraInfo, null, this.readErrors) + ); + return this; + } + + @Override + public void registerTags(@Nonnull Set tags) { + super.registerTags(tags); + tags.add("path"); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.WorkInProgress; + } + + @Override + public boolean validate( + String configName, + @Nonnull NPCLoadTimeValidationHelper validationHelper, + @Nonnull ExecutionContext context, + Scope globalScope, + @Nonnull List errors + ) { + return super.validate(configName, validationHelper, context, globalScope, errors) + & this.transientPath.validate(configName, validationHelper, this.builderManager, context, globalScope, errors); + } + + @Nullable + public TransientPathDefinition getPath(@Nonnull BuilderSupport support) { + return this.transientPath.build(support); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionPlaceBlock.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionPlaceBlock.java new file mode 100644 index 0000000..02eb40e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionPlaceBlock.java @@ -0,0 +1,78 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.ActionPlaceBlock; +import com.hypixel.hytale.server.npc.instructions.Action; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class BuilderActionPlaceBlock extends BuilderActionBase { + protected final DoubleHolder range = new DoubleHolder(); + protected final BooleanHolder allowEmptyMaterials = new BooleanHolder(); + + public BuilderActionPlaceBlock() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Place a block (chosen by another action) at a position returned by a Sensor if close enough"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionPlaceBlock(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionPlaceBlock readConfig(@Nonnull JsonElement data) { + this.getDouble( + data, + "Range", + this.range, + 3.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "The range to target position before block will be placed", + null + ); + this.getBoolean( + data, + "AllowEmptyMaterials", + this.allowEmptyMaterials, + false, + BuilderDescriptorState.Stable, + "Whether it should be possible to replace blocks that have empty material", + null + ); + this.requireFeature(EnumSet.of(Feature.Position)); + return this; + } + + public double getRange(@Nonnull BuilderSupport support) { + return this.range.get(support.getExecutionContext()); + } + + public boolean isAllowEmptyMaterials(@Nonnull BuilderSupport support) { + return this.allowEmptyMaterials.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionResetBlockSensors.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionResetBlockSensors.java new file mode 100644 index 0000000..e7be948 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionResetBlockSensors.java @@ -0,0 +1,81 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.BlockSetExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.ActionResetBlockSensors; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public class BuilderActionResetBlockSensors extends BuilderActionBase { + protected final AssetArrayHolder blockSets = new AssetArrayHolder(); + + public BuilderActionResetBlockSensors() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Resets a specific block sensor by name, or all block sensors"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Resets a specific block sensor by name, or all block sensors by clearing the current targeted block"; + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionResetBlockSensors(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionResetBlockSensors readConfig(@Nonnull JsonElement data) { + this.getAssetArray( + data, + "BlockSets", + this.blockSets, + null, + 0, + Integer.MAX_VALUE, + BlockSetExistsValidator.withConfig(AssetValidator.ListCanBeEmpty), + BuilderDescriptorState.Stable, + "The searched blocksets to reset block sensors for", + "The searched blocksets to reset block sensors for. If left empty, will reset all block sensors and found blocks" + ); + return this; + } + + public int[] getBlockSets(@Nonnull BuilderSupport support) { + String[] names = this.blockSets.get(support.getExecutionContext()); + if (names == null) { + return ArrayUtil.EMPTY_INT_ARRAY; + } else { + int[] indexes = new int[names.length]; + + for (int i = 0; i < indexes.length; i++) { + int index = BlockSet.getAssetMap().getIndex(names[i]); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + names[i]); + } + + indexes[i] = index; + } + + return indexes; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionResetPath.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionResetPath.java new file mode 100644 index 0000000..c701928 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionResetPath.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.ActionResetPath; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public class BuilderActionResetPath extends BuilderActionBase { + public BuilderActionResetPath() { + } + + @Nonnull + public Action build(BuilderSupport builderSupport) { + return new ActionResetPath(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Resets the current patrol path this NPC follows."; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionResetSearchRays.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionResetSearchRays.java new file mode 100644 index 0000000..3474821 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionResetSearchRays.java @@ -0,0 +1,74 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.common.util.ArrayUtil; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringArrayNoEmptyStringsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.ActionResetSearchRays; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public class BuilderActionResetSearchRays extends BuilderActionBase { + protected final AssetArrayHolder names = new AssetArrayHolder(); + + public BuilderActionResetSearchRays() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Resets a specific search ray sensor cached position by name, or all search ray sensors"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionResetSearchRays(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionResetSearchRays readConfig(@Nonnull JsonElement data) { + this.getStringArray( + data, + "Names", + this.names, + null, + 0, + Integer.MAX_VALUE, + StringArrayNoEmptyStringsValidator.get(), + BuilderDescriptorState.Stable, + "The search ray sensor ids", + "The search ray sensor ids. If left empty, will reset all search ray sensors" + ); + return this; + } + + public int[] getIds(@Nonnull BuilderSupport support) { + String[] ids = this.names.get(support.getExecutionContext()); + if (ids == null) { + return ArrayUtil.EMPTY_INT_ARRAY; + } else { + int[] indexes = new int[ids.length]; + + for (int i = 0; i < indexes.length; i++) { + indexes[i] = support.getSearchRaySlot(ids[i]); + } + + return indexes; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionSetBlockToPlace.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionSetBlockToPlace.java new file mode 100644 index 0000000..5da2e73 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionSetBlockToPlace.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.ItemExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.ActionSetBlockToPlace; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public class BuilderActionSetBlockToPlace extends BuilderActionBase { + protected final AssetHolder block = new AssetHolder(); + + public BuilderActionSetBlockToPlace() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Set the block type the NPC will place"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionSetBlockToPlace(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionSetBlockToPlace readConfig(@Nonnull JsonElement data) { + this.requireAsset(data, "Block", this.block, ItemExistsValidator.requireBlock(), BuilderDescriptorState.Stable, "The block item type", null); + return this; + } + + public String getBlockType(@Nonnull BuilderSupport support) { + return this.block.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionSetLeashPosition.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionSetLeashPosition.java new file mode 100644 index 0000000..934ff5e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionSetLeashPosition.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.ActionSetLeashPosition; +import javax.annotation.Nonnull; + +public class BuilderActionSetLeashPosition extends BuilderActionBase { + protected boolean toTarget; + protected boolean toCurrent; + + public BuilderActionSetLeashPosition() { + } + + @Nonnull + public ActionSetLeashPosition build(@Nonnull BuilderSupport builderSupport) { + builderSupport.setRequireLeashPosition(); + return new ActionSetLeashPosition(this); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Sets the NPCs current position to the spawn/leash position"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Sets the NPCs current position to the spawn/leash position to be used with the Leash Sensor."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionSetLeashPosition readConfig(@Nonnull JsonElement data) { + this.getBoolean(data, "ToCurrent", b -> this.toCurrent = b, false, BuilderDescriptorState.Stable, "Set to the NPCs current position.", null); + this.getBoolean(data, "ToTarget", b -> this.toTarget = b, false, BuilderDescriptorState.Stable, "Set to the target position.", null); + this.validateAny("ToCurrent", this.toCurrent, "ToTarget", this.toTarget); + this.requireFeatureIf("ToTarget", true, this.toTarget, Feature.AnyEntity); + return this; + } + + public boolean isToTarget() { + return this.toTarget; + } + + public boolean isToCurrent() { + return this.toCurrent; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionStorePosition.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionStorePosition.java new file mode 100644 index 0000000..90ef21d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionStorePosition.java @@ -0,0 +1,51 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.ActionStorePosition; +import com.hypixel.hytale.server.npc.instructions.Action; +import javax.annotation.Nonnull; + +public class BuilderActionStorePosition extends BuilderActionBase { + protected final StringHolder slot = new StringHolder(); + + public BuilderActionStorePosition() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Store the position from the attached sensor"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionStorePosition(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderActionStorePosition readConfig(@Nonnull JsonElement data) { + this.requireString(data, "Slot", this.slot, StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The slot to store the position in", null); + return this; + } + + public int getSlot(@Nonnull BuilderSupport support) { + return support.getPositionSlot(this.slot.get(support.getExecutionContext())); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionTriggerSpawners.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionTriggerSpawners.java new file mode 100644 index 0000000..0b8113d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderActionTriggerSpawners.java @@ -0,0 +1,89 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.IntHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.IntSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.ManualSpawnMarkerExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderActionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.ActionTriggerSpawners; +import com.hypixel.hytale.server.npc.instructions.Action; +import java.util.EnumSet; +import javax.annotation.Nonnull; + +public class BuilderActionTriggerSpawners extends BuilderActionBase { + protected final AssetHolder spawner = new AssetHolder(); + protected final DoubleHolder range = new DoubleHolder(); + protected final IntHolder count = new IntHolder(); + + public BuilderActionTriggerSpawners() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Trigger all, or up to a certain number of manual spawn markers in a radius around the NPC"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Action build(@Nonnull BuilderSupport builderSupport) { + return new ActionTriggerSpawners(this, builderSupport); + } + + @Nonnull + public BuilderActionTriggerSpawners readConfig(@Nonnull JsonElement data) { + this.getAsset( + data, + "SpawnMarker", + this.spawner, + null, + ManualSpawnMarkerExistsValidator.withConfig(EnumSet.of(AssetValidator.Config.NULLABLE)), + BuilderDescriptorState.Stable, + "The spawn marker type to trigger", + null + ); + this.requireDouble( + data, "Range", this.range, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "The range within which to trigger spawn markers", null + ); + this.getInt( + data, + "Count", + this.count, + 0, + IntSingleValidator.greaterEqual0(), + BuilderDescriptorState.Stable, + "The number of markers to randomly trigger (0 will trigger all matching validators)", + null + ); + return this; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + public String getSpawner(@Nonnull BuilderSupport support) { + return this.spawner.get(support.getExecutionContext()); + } + + public double getRange(@Nonnull BuilderSupport support) { + return this.range.get(support.getExecutionContext()); + } + + public int getCount(@Nonnull BuilderSupport support) { + return this.count.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderBodyMotionPath.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderBodyMotionPath.java new file mode 100644 index 0000000..e74dce3 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderBodyMotionPath.java @@ -0,0 +1,279 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.IntHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.IntSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.RelationalOperator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderBodyMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.BodyMotionPath; +import java.util.EnumSet; +import java.util.Set; +import javax.annotation.Nonnull; + +public class BuilderBodyMotionPath extends BuilderBodyMotionBase { + public static final double[] DEFAULT_DELAY_SCALE_RANGE = new double[]{0.2, 0.4}; + public static final double[] DEFAULT_PERCENT_DELAY_RANGE = new double[]{0.0, 0.2}; + protected final EnumHolder shape = new EnumHolder<>(); + protected final NumberArrayHolder delayScaleRange = new NumberArrayHolder(); + protected final NumberArrayHolder percentDelayRange = new NumberArrayHolder(); + protected final IntHolder viewSegments = new IntHolder(); + protected double pathWidth; + protected double nodeWidth; + protected double minRelativeSpeed; + protected double maxRelativeSpeed; + protected double minWalkDistance; + protected double maxWalkDistance; + protected boolean startAtNearestNode; + protected BodyMotionPath.Direction direction; + protected double minNodeDelay; + protected double maxNodeDelay; + protected boolean useNodeViewDirection; + protected boolean pickRandomAngle; + + public BuilderBodyMotionPath() { + } + + @Nonnull + public BodyMotionPath build(@Nonnull BuilderSupport builderSupport) { + return new BodyMotionPath(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Walk along a path"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Walk along a path."; + } + + @Override + public void registerTags(@Nonnull Set tags) { + super.registerTags(tags); + tags.add("path"); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderBodyMotionPath readConfig(@Nonnull JsonElement data) { + this.getBoolean(data, "StartAtNearestNode", b -> this.startAtNearestNode = b, true, BuilderDescriptorState.Stable, "Start at closest warp point", null); + this.getDouble( + data, + "PathWidth", + w -> this.pathWidth = w, + 0.0, + DoubleSingleValidator.greaterEqual0(), + BuilderDescriptorState.Experimental, + "Walking corridor width", + null + ); + this.getDouble( + data, + "NodeWidth", + w -> this.nodeWidth = w, + 0.2, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Experimental, + "Radius of warp point node", + null + ); + this.getDouble( + data, + "MinRelSpeed", + s -> this.minRelativeSpeed = s, + 0.5, + DoubleRangeValidator.fromExclToIncl(0.0, 1.0), + BuilderDescriptorState.Stable, + "Minimum relative walk speed", + null + ); + this.getDouble( + data, + "MaxRelSpeed", + s -> this.maxRelativeSpeed = s, + 0.5, + DoubleRangeValidator.fromExclToIncl(0.0, 1.0), + BuilderDescriptorState.Stable, + "Maximum relative walk speed", + null + ); + this.getDouble( + data, + "MinWalkDistance", + d -> this.minWalkDistance = d, + 0.0, + DoubleSingleValidator.greaterEqual0(), + BuilderDescriptorState.Experimental, + "Minimum walk distance when PathWidth greater 0", + null + ); + this.getDouble( + data, + "MaxWalkDistance", + d -> this.maxWalkDistance = d, + 0.0, + DoubleSingleValidator.greaterEqual0(), + BuilderDescriptorState.Experimental, + "Maximum walk distance when PathWidth greater 0", + null + ); + this.getEnum( + data, + "Direction", + d -> this.direction = d, + BodyMotionPath.Direction.class, + BodyMotionPath.Direction.FORWARD, + BuilderDescriptorState.Stable, + "Walking direction relative to order of nodes", + null + ); + this.getEnum(data, "Shape", this.shape, BodyMotionPath.Shape.class, BodyMotionPath.Shape.LOOP, BuilderDescriptorState.Stable, "Shape of Path", null); + this.getDouble( + data, + "MinNodeDelay", + d -> this.minNodeDelay = d, + 0.0, + DoubleSingleValidator.greaterEqual0(), + BuilderDescriptorState.Stable, + "Minimum resting time at a node", + null + ); + this.getDouble( + data, + "MaxNodeDelay", + d -> this.maxNodeDelay = d, + 0.0, + DoubleSingleValidator.greaterEqual0(), + BuilderDescriptorState.Stable, + "Maximum resting time at a node", + null + ); + this.getBoolean( + data, "UseNodeViewDirection", b -> this.useNodeViewDirection = b, false, BuilderDescriptorState.Stable, "Look into next node direction at node", null + ); + this.getDoubleRange( + data, + "NodePauseScaleRange", + this.delayScaleRange, + DEFAULT_DELAY_SCALE_RANGE, + DoubleSequenceValidator.fromExclToInclWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "The range from which to pick a value that defines the portion of the total node pause time that should be spent facing a direction before turning", + null + ); + this.getDoubleRange( + data, + "NodePauseExtraPercentRange", + this.percentDelayRange, + DEFAULT_PERCENT_DELAY_RANGE, + DoubleSequenceValidator.between01WeaklyMonotonic(), + BuilderDescriptorState.Stable, + "A range from which to pick the additional percentage of the directional pause time to add to it", + null + ); + this.getBoolean( + data, + "PickRandomAngle", + b -> this.pickRandomAngle = b, + false, + BuilderDescriptorState.Stable, + "Whether to sweep left and right using the observation angle, or pick a random angle within the sector each time", + null + ); + this.getInt( + data, + "ViewSegments", + this.viewSegments, + 1, + IntSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "The number of distinct segments to stop at when sweeping from left to right using the observation angle", + null + ); + this.validateDoubleRelation("MinRelativeSpeed", this.minRelativeSpeed, RelationalOperator.LessEqual, "MaxRelativeSpeed", this.maxRelativeSpeed); + this.validateDoubleRelation("MinWalkDistance", this.minWalkDistance, RelationalOperator.LessEqual, "MaxWalkDistance", this.maxWalkDistance); + this.validateDoubleRelation("MinNodeDelay", this.minNodeDelay, RelationalOperator.LessEqual, "MaxNodeDelay", this.maxNodeDelay); + this.requireFeature(EnumSet.of(Feature.Path)); + return this; + } + + public double getPathWidth() { + return this.pathWidth; + } + + public double getNodeWidth() { + return this.nodeWidth; + } + + public double getMinRelativeSpeed() { + return this.minRelativeSpeed; + } + + public double getMaxRelativeSpeed() { + return this.maxRelativeSpeed; + } + + public double getMinWalkDistance() { + return this.minWalkDistance; + } + + public double getMaxWalkDistance() { + return this.maxWalkDistance; + } + + public boolean isStartAtNearestNode() { + return this.startAtNearestNode; + } + + public BodyMotionPath.Direction getDirection() { + return this.direction; + } + + public BodyMotionPath.Shape getShape(@Nonnull BuilderSupport support) { + return this.shape.get(support.getExecutionContext()); + } + + public double getMinNodeDelay() { + return this.minNodeDelay; + } + + public double getMaxNodeDelay() { + return this.maxNodeDelay; + } + + public boolean isUseNodeViewDirection() { + return this.useNodeViewDirection; + } + + public double[] getDelayScaleRange(@Nonnull BuilderSupport support) { + return this.delayScaleRange.get(support.getExecutionContext()); + } + + public double[] getPercentDelayRange(@Nonnull BuilderSupport support) { + return this.percentDelayRange.get(support.getExecutionContext()); + } + + public boolean isPickRandomAngle() { + return this.pickRandomAngle; + } + + public int getViewSegments(@Nonnull BuilderSupport support) { + return this.viewSegments.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderHeadMotionObserve.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderHeadMotionObserve.java new file mode 100644 index 0000000..7bda5e1 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderHeadMotionObserve.java @@ -0,0 +1,124 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.IntHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.IntSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderHeadMotionBase; +import com.hypixel.hytale.server.npc.corecomponents.world.HeadMotionObserve; +import javax.annotation.Nonnull; + +public class BuilderHeadMotionObserve extends BuilderHeadMotionBase { + protected static final double[] DEFAULT_PAUSE_TIME_RANGE = new double[]{2.0, 2.0}; + protected final NumberArrayHolder angleRange = new NumberArrayHolder(); + protected final NumberArrayHolder pauseTimeRange = new NumberArrayHolder(); + protected final BooleanHolder pickRandomAngle = new BooleanHolder(); + protected final IntHolder viewSegments = new IntHolder(); + protected final DoubleHolder relativeTurnSpeed = new DoubleHolder(); + + public BuilderHeadMotionObserve() { + } + + @Nonnull + public HeadMotionObserve build(@Nonnull BuilderSupport builderSupport) { + return new HeadMotionObserve(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Observe surroundings in various ways."; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Observe surroundings in various ways. This includes looking in random directions within an angle, or sweeping the head left and right, etc. Angles are relative to body angle at any given time."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + public BuilderHeadMotionObserve readConfig(@Nonnull JsonElement data) { + this.requireDoubleRange( + data, + "AngleRange", + this.angleRange, + DoubleSequenceValidator.betweenWeaklyMonotonic(-180.0, 180.0), + BuilderDescriptorState.Stable, + "The angle range to observe in degrees", + "The angle range to observe in degrees, offset from facing forwards" + ); + this.getDoubleRange( + data, + "PauseTimeRange", + this.pauseTimeRange, + DEFAULT_PAUSE_TIME_RANGE, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "How long to continue looking in a given direction", + null + ); + this.getBoolean( + data, + "PickRandomAngle", + this.pickRandomAngle, + false, + BuilderDescriptorState.Stable, + "Whether to pick random angles within the range", + "Whether to pick random angles within the range. If false, will instead sweep across the range, pausing at either end." + ); + this.getInt( + data, + "ViewSegments", + this.viewSegments, + 1, + IntSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "The number of distinct segments to stop at when sweeping from left to right", + null + ); + this.getDouble( + data, + "RelativeTurnSpeed", + this.relativeTurnSpeed, + 1.0, + DoubleRangeValidator.fromExclToIncl(0.0, 2.0), + BuilderDescriptorState.Stable, + "The relative turn speed modifier", + null + ); + return this; + } + + public float[] getAngleRange(@Nonnull BuilderSupport support) { + double[] range = this.angleRange.get(support.getExecutionContext()); + return new float[]{(float)(range[0] * (float) (Math.PI / 180.0)), (float)(range[1] * (float) (Math.PI / 180.0))}; + } + + public double[] getPauseTimeRange(@Nonnull BuilderSupport support) { + return this.pauseTimeRange.get(support.getExecutionContext()); + } + + public boolean isPickRandomAngle(@Nonnull BuilderSupport support) { + return this.pickRandomAngle.get(support.getExecutionContext()); + } + + public int getViewSegments(@Nonnull BuilderSupport support) { + return this.viewSegments.get(support.getExecutionContext()); + } + + public double getRelativeTurnSpeed(@Nonnull BuilderSupport support) { + return this.relativeTurnSpeed.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorBlock.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorBlock.java new file mode 100644 index 0000000..9fcbb72 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorBlock.java @@ -0,0 +1,124 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.BlockSetExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.SensorBlock; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorBlock extends BuilderSensorBase { + protected final DoubleHolder range = new DoubleHolder(); + protected final DoubleHolder yRange = new DoubleHolder(); + protected final AssetHolder blockSet = new AssetHolder(); + protected final BooleanHolder pickRandom = new BooleanHolder(); + protected final BooleanHolder reserveBlock = new BooleanHolder(); + + public BuilderSensorBlock() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Checks for one of a set of blocks in the nearby area"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Checks for one of a set of blocks in the nearby area and caches the result until explicitly reset or the targeted block changes/is removed. All block sensors with the same sought blockset share the same targeted block once found"; + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorBlock(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Experimental; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireDouble( + data, + "Range", + this.range, + DoubleRangeValidator.fromExclToIncl(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "The range to search for the blocks in", + null + ); + this.getDouble( + data, + "MaxHeight", + this.yRange, + 4.0, + DoubleRangeValidator.fromExclToIncl(0.0, Double.MAX_VALUE), + BuilderDescriptorState.Stable, + "The vertical range to search for the blocks in", + null + ); + this.requireAsset( + data, "Blocks", this.blockSet, BlockSetExistsValidator.required(), BuilderDescriptorState.Stable, "The set of blocks to search for", null + ); + this.getBoolean( + data, + "Random", + this.pickRandom, + false, + BuilderDescriptorState.Stable, + "Whether to pick at random from within the matched blocks or pick the closest", + null + ); + this.getBoolean( + data, + "Reserve", + this.reserveBlock, + false, + BuilderDescriptorState.Stable, + "Whether to reserve the found block to prevent other NPCs selecting it", + null + ); + this.provideFeature(Feature.Position); + return this; + } + + public double getRange(@Nonnull BuilderSupport support) { + return this.range.get(support.getExecutionContext()); + } + + public double getYRange(@Nonnull BuilderSupport support) { + return this.yRange.get(support.getExecutionContext()); + } + + public int getBlockSet(@Nonnull BuilderSupport support) { + String key = this.blockSet.get(support.getExecutionContext()); + int index = BlockSet.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } else { + return index; + } + } + + public boolean isPickRandom(@Nonnull BuilderSupport support) { + return this.pickRandom.get(support.getExecutionContext()); + } + + public boolean isReserveBlock(@Nonnull BuilderSupport support) { + return this.reserveBlock.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorBlockChange.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorBlockChange.java new file mode 100644 index 0000000..c508dab --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorBlockChange.java @@ -0,0 +1,77 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.BlockSetExistsValidator; +import com.hypixel.hytale.server.npc.blackboard.view.event.block.BlockEventType; +import com.hypixel.hytale.server.npc.corecomponents.world.SensorBlockChange; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorBlockChange extends BuilderSensorEvent { + protected final AssetHolder blockSet = new AssetHolder(); + protected final EnumHolder blockEventType = new EnumHolder<>(); + + public BuilderSensorBlockChange() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Matches when a block from a blockset within a certain range is changed or interacted with"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorBlockChange(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + super.readConfig(data); + this.requireAsset(data, "BlockSet", this.blockSet, BlockSetExistsValidator.required(), BuilderDescriptorState.Stable, "Block set to listen for", null); + this.getEnum( + data, + "EventType", + this.blockEventType, + BlockEventType.class, + BlockEventType.DAMAGE, + BuilderDescriptorState.Stable, + "The event type to listen for", + null + ); + return this; + } + + public int getBlockSet(@Nonnull BuilderSupport support) { + String key = this.blockSet.get(support.getExecutionContext()); + int index = BlockSet.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } else { + return index; + } + } + + public BlockEventType getEventType(@Nonnull BuilderSupport support) { + return this.blockEventType.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorBlockType.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorBlockType.java new file mode 100644 index 0000000..bd5b1b9 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorBlockType.java @@ -0,0 +1,70 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectReferenceHelper; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.BlockSetExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.SensorBlockType; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderSensorBlockType extends BuilderSensorBase { + protected final BuilderObjectReferenceHelper sensor = new BuilderObjectReferenceHelper<>(Sensor.class, this); + protected final AssetHolder blockSet = new AssetHolder(); + + public BuilderSensorBlockType() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Checks if the block at the given position matches the provided block set"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nullable + public Sensor build(@Nonnull BuilderSupport builderSupport) { + Sensor sensor = this.getSensor(builderSupport); + return sensor == null ? null : new SensorBlockType(this, builderSupport, sensor); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireObject(data, "Sensor", this.sensor, BuilderDescriptorState.Stable, "Sensor to wrap", null, this.validationHelper); + this.requireAsset(data, "BlockSet", this.blockSet, BlockSetExistsValidator.required(), BuilderDescriptorState.Stable, "Block set to check against", null); + return this; + } + + @Nullable + public Sensor getSensor(@Nonnull BuilderSupport support) { + return this.sensor.build(support); + } + + public int getBlockSet(@Nonnull BuilderSupport support) { + String key = this.blockSet.get(support.getExecutionContext()); + int index = BlockSet.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } else { + return index; + } + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorCanPlace.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorCanPlace.java new file mode 100644 index 0000000..2d0244b --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorCanPlace.java @@ -0,0 +1,110 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.SensorCanPlace; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorCanPlace extends BuilderSensorBase { + protected final EnumHolder direction = new EnumHolder<>(); + protected final EnumHolder offset = new EnumHolder<>(); + protected final DoubleHolder retryDelay = new DoubleHolder(); + protected final BooleanHolder allowEmptyMaterials = new BooleanHolder(); + + public BuilderSensorCanPlace() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Test if the currently set block can be placed at the relative position given direction and offset"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorCanPlace(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getEnum( + data, + "Direction", + this.direction, + SensorCanPlace.Direction.class, + SensorCanPlace.Direction.Forward, + BuilderDescriptorState.Stable, + "The direction to place relative to heading", + null + ); + this.getEnum( + data, + "Offset", + this.offset, + SensorCanPlace.Offset.class, + SensorCanPlace.Offset.BodyPosition, + BuilderDescriptorState.Stable, + "The offset to place at", + null + ); + this.getDouble( + data, + "RetryDelay", + this.retryDelay, + 5.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "The amount of time to delay if a placement fails before trying to place something again", + null + ); + this.getBoolean( + data, + "AllowEmptyMaterials", + this.allowEmptyMaterials, + false, + BuilderDescriptorState.Stable, + "Whether it should be possible to replace blocks that have empty material", + null + ); + this.provideFeature(Feature.Position); + return this; + } + + public SensorCanPlace.Direction getDirection(@Nonnull BuilderSupport support) { + return this.direction.get(support.getExecutionContext()); + } + + public SensorCanPlace.Offset getOffset(@Nonnull BuilderSupport support) { + return this.offset.get(support.getExecutionContext()); + } + + public double getRetryDelay(@Nonnull BuilderSupport support) { + return this.retryDelay.get(support.getExecutionContext()); + } + + public boolean isAllowEmptyMaterials(@Nonnull BuilderSupport support) { + return this.allowEmptyMaterials.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorEntityEvent.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorEntityEvent.java new file mode 100644 index 0000000..06a9013 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorEntityEvent.java @@ -0,0 +1,84 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.builtin.tagset.config.NPCGroup; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.TagSetExistsValidator; +import com.hypixel.hytale.server.npc.blackboard.view.event.entity.EntityEventType; +import com.hypixel.hytale.server.npc.corecomponents.world.SensorEntityEvent; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorEntityEvent extends BuilderSensorEvent { + protected final AssetHolder npcGroup = new AssetHolder(); + protected final EnumHolder entityEventType = new EnumHolder<>(); + protected final BooleanHolder flockOnly = new BooleanHolder(); + + public BuilderSensorEntityEvent() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Matches when an entity from a specific NPC group within a certain range is damaged, killed, or interacted with"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorEntityEvent(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + super.readConfig(data); + this.requireAsset(data, "NPCGroup", this.npcGroup, TagSetExistsValidator.required(), BuilderDescriptorState.Stable, "NPC group to listen for", null); + this.getEnum( + data, + "EventType", + this.entityEventType, + EntityEventType.class, + EntityEventType.DAMAGE, + BuilderDescriptorState.Stable, + "The event type to listen for", + null + ); + this.getBoolean(data, "FlockOnly", this.flockOnly, false, BuilderDescriptorState.Stable, "Whether to only listen for flock events", null); + return this; + } + + public int getNPCGroup(@Nonnull BuilderSupport support) { + String key = this.npcGroup.get(support.getExecutionContext()); + int index = NPCGroup.getAssetMap().getIndex(key); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + key); + } else { + return index; + } + } + + public EntityEventType getEventType(@Nonnull BuilderSupport support) { + return this.entityEventType.get(support.getExecutionContext()); + } + + public boolean isFlockOnly(@Nonnull BuilderSupport support) { + return this.flockOnly.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorEvent.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorEvent.java new file mode 100644 index 0000000..465dc54 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorEvent.java @@ -0,0 +1,66 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNullOrNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.SensorEvent; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public abstract class BuilderSensorEvent extends BuilderSensorBase { + protected final DoubleHolder range = new DoubleHolder(); + protected final EnumHolder searchType = new EnumHolder<>(); + protected final StringHolder lockOnTargetSlot = new StringHolder(); + + public BuilderSensorEvent() { + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireDouble(data, "Range", this.range, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "Max range to listen in", null); + this.getEnum( + data, + "SearchType", + this.searchType, + SensorEvent.EventSearchType.class, + SensorEvent.EventSearchType.PlayerOnly, + BuilderDescriptorState.Stable, + "Whether to listen for events triggered by players, npcs, or both in a certain order", + null + ); + this.getString( + data, + "TargetSlot", + this.lockOnTargetSlot, + null, + StringNullOrNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "A target slot to place the target in. If omitted, no slot will be used", + null + ); + this.provideFeature(Feature.LiveEntity); + return this; + } + + public double getRange(@Nonnull BuilderSupport support) { + return this.range.get(support.getExecutionContext()); + } + + public SensorEvent.EventSearchType getEventSearchType(@Nonnull BuilderSupport support) { + return this.searchType.get(support.getExecutionContext()); + } + + public int getLockOnTargetSlot(@Nonnull BuilderSupport support) { + String slot = this.lockOnTargetSlot.get(support.getExecutionContext()); + return slot == null ? Integer.MIN_VALUE : support.getTargetSlot(slot); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorInWater.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorInWater.java new file mode 100644 index 0000000..e7b06df --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorInWater.java @@ -0,0 +1,36 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.SensorInWater; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorInWater extends BuilderSensorBase { + public BuilderSensorInWater() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Check if NPC is currently in water"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Sensor build(BuilderSupport builderSupport) { + return new SensorInWater(this); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorLeash.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorLeash.java new file mode 100644 index 0000000..1e0102d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorLeash.java @@ -0,0 +1,58 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.SensorLeash; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorLeash extends BuilderSensorBase { + protected final DoubleHolder range = new DoubleHolder(); + + public BuilderSensorLeash() { + } + + @Nonnull + public SensorLeash build(@Nonnull BuilderSupport builderSupport) { + builderSupport.setRequireLeashPosition(); + return new SensorLeash(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Triggers when the NPC is outside a specified range from the leash point"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Triggers when the NPC is outside a specified range from the leash point"; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireDouble( + data, "Range", this.range, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "The farthest distance allowed from the leash point", null + ); + this.provideFeature(Feature.Position); + return this; + } + + public double getRange(@Nonnull BuilderSupport builderSupport) { + return this.range.get(builderSupport.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorLight.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorLight.java new file mode 100644 index 0000000..e4e4e76 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorLight.java @@ -0,0 +1,156 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNullOrNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.SensorLight; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorLight extends BuilderSensorBase { + public static final double[] FULL_LIGHT_RANGE = new double[]{0.0, 100.0}; + protected final StringHolder useTargetSlot = new StringHolder(); + protected final NumberArrayHolder lightRange = new NumberArrayHolder(); + protected final NumberArrayHolder skyLightRange = new NumberArrayHolder(); + protected final NumberArrayHolder sunlightRange = new NumberArrayHolder(); + protected final NumberArrayHolder redLightRange = new NumberArrayHolder(); + protected final NumberArrayHolder greenLightRange = new NumberArrayHolder(); + protected final NumberArrayHolder blueLightRange = new NumberArrayHolder(); + + public BuilderSensorLight() { + } + + @Nonnull + public SensorLight build(@Nonnull BuilderSupport builderSupport) { + return new SensorLight(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Check the light levels of the block an entity is standing on"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Check the light levels of the block an entity is standing on. Can test light intensity, sky light or block channel levels."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getDoubleRange( + data, + "LightRange", + this.lightRange, + FULL_LIGHT_RANGE, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, 100.0), + BuilderDescriptorState.Stable, + "The light intensity percentage range", + null + ); + this.getDoubleRange( + data, + "SkyLightRange", + this.skyLightRange, + FULL_LIGHT_RANGE, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, 100.0), + BuilderDescriptorState.Stable, + "The sky light percentage range", + null + ); + this.getDoubleRange( + data, + "SunlightRange", + this.sunlightRange, + FULL_LIGHT_RANGE, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, 100.0), + BuilderDescriptorState.Stable, + "The sunlight percentage range", + null + ); + this.getDoubleRange( + data, + "RedLightRange", + this.redLightRange, + FULL_LIGHT_RANGE, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, 100.0), + BuilderDescriptorState.Stable, + "The red light percentage range", + null + ); + this.getDoubleRange( + data, + "GreenLightRange", + this.greenLightRange, + FULL_LIGHT_RANGE, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, 100.0), + BuilderDescriptorState.Stable, + "The green light percentage range", + null + ); + this.getDoubleRange( + data, + "BlueLightRange", + this.blueLightRange, + FULL_LIGHT_RANGE, + DoubleSequenceValidator.betweenWeaklyMonotonic(0.0, 100.0), + BuilderDescriptorState.Stable, + "The blue light percentage range", + null + ); + this.getString( + data, + "UseTargetSlot", + this.useTargetSlot, + null, + StringNullOrNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "A target slot to check. If omitted, will check self", + null + ); + return this; + } + + public int getUsedTargetSlot(@Nonnull BuilderSupport support) { + String slot = this.useTargetSlot.get(support.getExecutionContext()); + return slot == null ? Integer.MIN_VALUE : support.getTargetSlot(slot); + } + + public double[] getLightRange(@Nonnull BuilderSupport builderSupport) { + return this.lightRange.get(builderSupport.getExecutionContext()); + } + + public double[] getSkyLightRange(@Nonnull BuilderSupport builderSupport) { + return this.skyLightRange.get(builderSupport.getExecutionContext()); + } + + public double[] getSunlightRange(@Nonnull BuilderSupport builderSupport) { + return this.sunlightRange.get(builderSupport.getExecutionContext()); + } + + public double[] getRedLightRange(@Nonnull BuilderSupport builderSupport) { + return this.redLightRange.get(builderSupport.getExecutionContext()); + } + + public double[] getGreenLightRange(@Nonnull BuilderSupport builderSupport) { + return this.greenLightRange.get(builderSupport.getExecutionContext()); + } + + public double[] getBlueLightRange(@Nonnull BuilderSupport builderSupport) { + return this.blueLightRange.get(builderSupport.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorPath.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorPath.java new file mode 100644 index 0000000..59ff79d --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorPath.java @@ -0,0 +1,99 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.SensorPath; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import java.util.Set; +import javax.annotation.Nonnull; + +public class BuilderSensorPath extends BuilderSensorBase { + protected final StringHolder name = new StringHolder(); + protected final DoubleHolder range = new DoubleHolder(); + protected final EnumHolder pathType = new EnumHolder<>(); + + public BuilderSensorPath() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Find a path based on various criteria"; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Find a path based on various criteria. Provides the position of the nearest waypoint and the path itself"; + } + + @Override + public void registerTags(@Nonnull Set tags) { + super.registerTags(tags); + tags.add("path"); + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorPath(this, builderSupport); + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.getString( + data, "Path", this.name, null, null, BuilderDescriptorState.Stable, "The name of the path. If left blank, will find the nearest path", null + ); + this.getDouble( + data, + "Range", + this.range, + 10.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "The range to test to nearest waypoint. 0 is unlimited", + null + ); + this.getEnum( + data, + "PathType", + this.pathType, + SensorPath.PathType.class, + SensorPath.PathType.AnyPrefabPath, + BuilderDescriptorState.Stable, + "The type of path to search for", + null + ); + this.validateStringIfEnumIs(this.name, StringNotEmptyValidator.get(), this.pathType, SensorPath.PathType.WorldPath); + this.provideFeature(Feature.Position); + this.provideFeature(Feature.Path); + return this; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + public String getPath(@Nonnull BuilderSupport support) { + return this.name.get(support.getExecutionContext()); + } + + public double getRange(@Nonnull BuilderSupport support) { + return this.range.get(support.getExecutionContext()); + } + + public SensorPath.PathType getPathType(@Nonnull BuilderSupport support) { + return this.pathType.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorReadPosition.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorReadPosition.java new file mode 100644 index 0000000..810f4e7 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorReadPosition.java @@ -0,0 +1,87 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.SensorReadPosition; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorReadPosition extends BuilderSensorBase { + protected final StringHolder slot = new StringHolder(); + protected final BooleanHolder useMarkedTarget = new BooleanHolder(); + protected final DoubleHolder range = new DoubleHolder(); + protected final DoubleHolder minRange = new DoubleHolder(); + + public BuilderSensorReadPosition() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Read a stored position with some conditions"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorReadPosition(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireString(data, "Slot", this.slot, StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The slot to read the position from", null); + this.getDouble( + data, "MinRange", this.minRange, 0.0, DoubleSingleValidator.greaterEqual0(), BuilderDescriptorState.Stable, "Minimum range from stored position", null + ); + this.requireDouble(data, "Range", this.range, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "Maximum range from stored position", null); + this.getBoolean( + data, + "UseMarkedTarget", + this.useMarkedTarget, + false, + BuilderDescriptorState.Stable, + "Whether to read from a marked target slot instead of a position slot", + null + ); + this.provideFeature(Feature.Position); + return this; + } + + public int getSlot(@Nonnull BuilderSupport support) { + String slotName = this.slot.get(support.getExecutionContext()); + return this.useMarkedTarget.get(support.getExecutionContext()) ? support.getTargetSlot(slotName) : support.getPositionSlot(slotName); + } + + public boolean isUseMarkedTarget(@Nonnull BuilderSupport support) { + return this.useMarkedTarget.get(support.getExecutionContext()); + } + + public double getMinRange(@Nonnull BuilderSupport support) { + return this.minRange.get(support.getExecutionContext()); + } + + public double getRange(@Nonnull BuilderSupport support) { + return this.range.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorSearchRay.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorSearchRay.java new file mode 100644 index 0000000..5754ebe --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorSearchRay.java @@ -0,0 +1,147 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.Feature; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.FloatHolder; +import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.BlockSetExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.SensorSearchRay; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorSearchRay extends BuilderSensorBase { + protected final StringHolder id = new StringHolder(); + protected final FloatHolder angle = new FloatHolder(); + protected final DoubleHolder range = new DoubleHolder(); + protected final AssetHolder blockSet = new AssetHolder(); + protected final FloatHolder minRetestAngle = new FloatHolder(); + protected final DoubleHolder minRetestMove = new DoubleHolder(); + protected final DoubleHolder throttleTime = new DoubleHolder(); + + public BuilderSensorSearchRay() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Fire a ray at a specific angle to see if what it hits matches a given sought block"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorSearchRay(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireString( + data, + "Name", + this.id, + StringNotEmptyValidator.get(), + BuilderDescriptorState.Stable, + "The id of this search ray sensor so the position can be cached", + null + ); + this.requireFloat( + data, + "Angle", + this.angle, + DoubleRangeValidator.between(-90.0, 90.0), + BuilderDescriptorState.Stable, + "Angle to fire the ray", + "Angle to fire the ray. Horizontal is 0. Positive is downwards" + ); + this.requireDouble(data, "Range", this.range, DoubleRangeValidator.fromExclToIncl(0.0, 96.0), BuilderDescriptorState.Stable, "How far to search", null); + this.requireAsset(data, "Blocks", this.blockSet, BlockSetExistsValidator.required(), BuilderDescriptorState.Stable, "The blockset to search for", null); + this.getFloat( + data, + "MinRetestAngle", + this.minRetestAngle, + 5.0, + DoubleRangeValidator.between(0.0, 360.0), + BuilderDescriptorState.Stable, + "The minimum change in NPC rotation before rays stop being throttled", + null + ); + this.getDouble( + data, + "MinRetestMove", + this.minRetestMove, + 1.0, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "The minimum distance the NPC needs to move while facing the same direction before rays stop being throttled", + null + ); + this.getDouble( + data, + "ThrottleTime", + this.throttleTime, + 0.5, + DoubleSingleValidator.greater0(), + BuilderDescriptorState.Stable, + "The delay between retests when an NPC is facing the same direction", + null + ); + this.provideFeature(Feature.Position); + return this; + } + + public float getAngle(@Nonnull BuilderSupport support) { + return this.angle.get(support.getExecutionContext()) * (float) (Math.PI / 180.0); + } + + public double getRange(@Nonnull BuilderSupport support) { + return this.range.get(support.getExecutionContext()); + } + + public int getBlockSet(@Nonnull BuilderSupport support) { + String blockSetId = this.blockSet.get(support.getExecutionContext()); + int index = BlockSet.getAssetMap().getIndex(blockSetId); + if (index == Integer.MIN_VALUE) { + throw new IllegalArgumentException("Unknown key! " + blockSetId); + } else { + return index; + } + } + + public float getMinRetestAngle(@Nonnull BuilderSupport support) { + return this.minRetestAngle.get(support.getExecutionContext()) * (float) (Math.PI / 180.0); + } + + public double getMinRetestMove(@Nonnull BuilderSupport support) { + return this.minRetestMove.get(support.getExecutionContext()); + } + + public double getThrottleTime(@Nonnull BuilderSupport support) { + return this.throttleTime.get(support.getExecutionContext()); + } + + public int getId(@Nonnull BuilderSupport support) { + return support.getSearchRaySlot(this.id.get(support.getExecutionContext())); + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorTime.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorTime.java new file mode 100644 index 0000000..2106308 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorTime.java @@ -0,0 +1,97 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.SensorTime; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import javax.annotation.Nonnull; + +public class BuilderSensorTime extends BuilderSensorBase { + protected final NumberArrayHolder period = new NumberArrayHolder(); + protected boolean checkDay; + protected boolean checkYear; + protected boolean scaleDayTimeRange; + + public BuilderSensorTime() { + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorTime(this, builderSupport); + } + + @Nonnull + @Override + public String getShortDescription() { + return "Check if the day/year time is within some specified time."; + } + + @Nonnull + @Override + public String getLongDescription() { + return "Check if the day/year time is within some specified time. If you want to check a range of time which crosses through midnight and switches to the next day, use the greater time as the min value and the lesser value as the max value."; + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireDoubleRange( + data, "Period", this.period, DoubleSequenceValidator.between(0.0, 24.0), BuilderDescriptorState.Stable, "The time period to trigger within", null + ); + this.getBoolean( + data, + "CheckDay", + b -> this.checkDay = b, + true, + BuilderDescriptorState.Stable, + "Check the day time.", + "Check the day time. When using a double the values go from [.00, .99]. Don't get confused with there only being 60 minutes in an hour." + ); + this.getBoolean( + data, + "CheckYear", + b -> this.checkYear = b, + false, + BuilderDescriptorState.WorkInProgress, + "Check the year time.", + "Check the year time. When using a double the values go from [.00, .99]. Don't get confused with there only being 60 minutes in an hour." + ); + this.getBoolean( + data, + "ScaleDayTimeRange", + b -> this.scaleDayTimeRange = b, + true, + BuilderDescriptorState.Stable, + "Whether to use a relative scale for the day time", + "Whether to use a relative scale for the day time. Sunrise will be at relative 6, Noon at 12, and Sunset at 18, regardless of actual in-game time" + ); + return this; + } + + public double[] getPeriod(@Nonnull BuilderSupport support) { + return this.period.get(support.getExecutionContext()); + } + + public boolean isCheckDay() { + return this.checkDay; + } + + public boolean isCheckYear() { + return this.checkYear; + } + + public boolean isScaleDayTimeRange() { + return this.scaleDayTimeRange; + } +} diff --git a/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorWeather.java b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorWeather.java new file mode 100644 index 0000000..7e2a38e --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/corecomponents/world/builders/BuilderSensorWeather.java @@ -0,0 +1,67 @@ +package com.hypixel.hytale.server.npc.corecomponents.world.builders; + +import com.google.gson.JsonElement; +import com.hypixel.hytale.server.npc.asset.builder.Builder; +import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; +import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; +import com.hypixel.hytale.server.npc.asset.builder.holder.AssetArrayHolder; +import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator; +import com.hypixel.hytale.server.npc.asset.builder.validators.asset.WeatherExistsValidator; +import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase; +import com.hypixel.hytale.server.npc.corecomponents.world.SensorWeather; +import com.hypixel.hytale.server.npc.instructions.Sensor; +import java.util.EnumSet; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BuilderSensorWeather extends BuilderSensorBase { + protected final AssetArrayHolder weathers = new AssetArrayHolder(); + + public BuilderSensorWeather() { + } + + @Nonnull + @Override + public String getShortDescription() { + return "Matches the current weather at the NPCs position against a set of weather globs"; + } + + @Nonnull + @Override + public String getLongDescription() { + return this.getShortDescription(); + } + + @Nonnull + public Sensor build(@Nonnull BuilderSupport builderSupport) { + return new SensorWeather(this, builderSupport); + } + + @Nonnull + @Override + public BuilderDescriptorState getBuilderDescriptorState() { + return BuilderDescriptorState.Stable; + } + + @Nonnull + @Override + public Builder readConfig(@Nonnull JsonElement data) { + this.requireAssetArray( + data, + "Weathers", + this.weathers, + 0, + Integer.MAX_VALUE, + WeatherExistsValidator.withConfig(EnumSet.of(AssetValidator.Config.MATCHER)), + BuilderDescriptorState.Stable, + "The glob patterns to match against weather", + null + ); + return this; + } + + @Nullable + public String[] getWeathers(@Nonnull BuilderSupport support) { + return this.weathers.get(support.getExecutionContext()); + } +} diff --git a/src/com/hypixel/hytale/server/npc/decisionmaker/core/EvaluationContext.java b/src/com/hypixel/hytale/server/npc/decisionmaker/core/EvaluationContext.java new file mode 100644 index 0000000..1a2da04 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/decisionmaker/core/EvaluationContext.java @@ -0,0 +1,62 @@ +package com.hypixel.hytale.server.npc.decisionmaker.core; + +public class EvaluationContext { + private double minimumUtility; + private double minimumWeightCoefficient; + private float predictability; + private long lastUsedNanos; + + public EvaluationContext() { + } + + public double getMinimumUtility() { + return this.minimumUtility; + } + + public void setMinimumUtility(double minimumUtility) { + if (!(minimumUtility >= 1.0) && !(minimumUtility < 0.0)) { + this.minimumUtility = minimumUtility; + } else { + throw new IllegalArgumentException("Minimum utility must be greater than or equal to 0 and less than 1!"); + } + } + + public double getMinimumWeightCoefficient() { + return this.minimumWeightCoefficient; + } + + public void setMinimumWeightCoefficient(double minimumWeightCoefficient) { + if (minimumWeightCoefficient < 0.0) { + throw new IllegalArgumentException("Minimum weight coefficient must be greater than or equal to 0!"); + } else { + this.minimumWeightCoefficient = minimumWeightCoefficient; + } + } + + public float getPredictability() { + return this.predictability; + } + + public void setPredictability(float predictability) { + if (!(predictability > 1.0F) && !(predictability < 0.0F)) { + this.predictability = predictability; + } else { + throw new IllegalArgumentException("Predictability must be a value between 0 and 1!"); + } + } + + public long getLastUsedNanos() { + return this.lastUsedNanos; + } + + public void setLastUsedNanos(long lastUsedNanos) { + this.lastUsedNanos = lastUsedNanos; + } + + public void reset() { + this.minimumUtility = 0.0; + this.minimumWeightCoefficient = 0.0; + this.predictability = 1.0F; + this.lastUsedNanos = Evaluator.NOT_USED; + } +} diff --git a/src/com/hypixel/hytale/server/npc/decisionmaker/core/Evaluator.java b/src/com/hypixel/hytale/server/npc/decisionmaker/core/Evaluator.java new file mode 100644 index 0000000..b890e7a --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/decisionmaker/core/Evaluator.java @@ -0,0 +1,147 @@ +package com.hypixel.hytale.server.npc.decisionmaker.core; + +import com.hypixel.hytale.common.map.IWeightedElement; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class Evaluator { + public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); + public static long NOT_USED = 0L; + protected List.OptionHolder> options; + + public Evaluator() { + } + + public void initialise() { + this.options.sort(Comparator.comparingDouble(Evaluator.OptionHolder::getWeightCoefficient).reversed()); + + for (Evaluator.OptionHolder optionHolder : this.options) { + optionHolder.option.sortConditions(); + } + } + + public void setupNPC(Role role) { + for (Evaluator.OptionHolder optionHolder : this.options) { + optionHolder.option.setupNPC(role); + } + } + + public void setupNPC(Holder holder) { + for (Evaluator.OptionHolder optionHolder : this.options) { + optionHolder.option.setupNPC(holder); + } + } + + @Nullable + public Evaluator.OptionHolder evaluate( + int index, @Nonnull ArchetypeChunk archetypeChunk, CommandBuffer commandBuffer, @Nonnull EvaluationContext context + ) { + NPCEntity npcComponent = archetypeChunk.getComponent(index, NPCEntity.getComponentType()); + + assert npcComponent != null; + + UUIDComponent uuidComponent = archetypeChunk.getComponent(index, UUIDComponent.getComponentType()); + + assert uuidComponent != null; + + Evaluator.OptionHolder bestOption = null; + double minimumWeight = context.getMinimumWeightCoefficient(); + int nonMatchingIndex = this.options.size(); + + for (int i = 0; i < this.options.size(); i++) { + Evaluator.OptionHolder optionHolder = this.options.get(i); + if (optionHolder.getWeightCoefficient() < minimumWeight) { + nonMatchingIndex = i; + break; + } + + double utility = optionHolder.calculateUtility(index, archetypeChunk, commandBuffer, context); + HytaleLogger.Api logContext = LOGGER.at(Level.FINE); + if (logContext.isEnabled()) { + logContext.log("%s with uuid %s: Scored option %s at %s", npcComponent.getRoleName(), uuidComponent.getUuid(), optionHolder.option, utility); + } + + if (!(utility <= 0.0) && (bestOption == null || utility > bestOption.utility)) { + bestOption = optionHolder; + } + } + + if (bestOption == null) { + return null; + } else { + float predictability = context.getPredictability(); + if (predictability == 1.0F) { + return bestOption; + } else { + double threshold = bestOption.utility * predictability; + double sum = 0.0; + + for (int i = 0; i < nonMatchingIndex; i++) { + Evaluator.OptionHolder optionHolderx = this.options.get(i); + if (optionHolderx.utility >= threshold) { + sum += optionHolderx.getTotalUtility(threshold); + } + } + + double randomWeight = ThreadLocalRandom.current().nextDouble(sum); + + for (int ix = 0; ix < nonMatchingIndex; ix++) { + Evaluator.OptionHolder optionHolderx = this.options.get(ix); + if (!(optionHolderx.utility < threshold)) { + randomWeight = optionHolderx.tryPick(randomWeight, threshold); + if (randomWeight <= 0.0) { + bestOption = optionHolderx; + break; + } + } + } + + return bestOption; + } + } + } + + public abstract class OptionHolder implements IWeightedElement { + protected final OptionType option; + protected double utility; + + public OptionHolder(OptionType option) { + this.option = option; + } + + @Override + public double getWeight() { + return this.utility; + } + + public double getWeightCoefficient() { + return this.option.getWeightCoefficient(); + } + + public OptionType getOption() { + return this.option; + } + + public double getTotalUtility(double threshold) { + return this.utility; + } + + public double tryPick(double currentWeight, double threshold) { + return currentWeight - this.utility; + } + + public abstract double calculateUtility(int var1, ArchetypeChunk var2, CommandBuffer var3, EvaluationContext var4); + } +} diff --git a/src/com/hypixel/hytale/server/npc/decisionmaker/core/Option.java b/src/com/hypixel/hytale/server/npc/decisionmaker/core/Option.java new file mode 100644 index 0000000..08c5855 --- /dev/null +++ b/src/com/hypixel/hytale/server/npc/decisionmaker/core/Option.java @@ -0,0 +1,165 @@ +package com.hypixel.hytale.server.npc.decisionmaker.core; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import com.hypixel.hytale.codec.validation.Validators; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.logger.HytaleLogger; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.decisionmaker.core.conditions.base.Condition; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import java.lang.ref.WeakReference; +import java.util.Arrays; +import java.util.Comparator; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +public abstract class Option { + public static final BuilderCodec